]> git.mxchange.org Git - flightgear.git/blob - src/GUI/LocationWidget.cxx
Lat-lon position support
[flightgear.git] / src / GUI / LocationWidget.cxx
1 // LocationWidget.cxx - GUI launcher dialog using Qt5
2 //
3 // Written by James Turner, started October 2015.
4 //
5 // Copyright (C) 2015 James Turner <zakalawe@mac.com>
6 //
7 // This program is free software; you can redistribute it and/or
8 // modify it under the terms of the GNU General Public License as
9 // published by the Free Software Foundation; either version 2 of the
10 // License, or (at your option) any later version.
11 //
12 // This program is distributed in the hope that it will be useful, but
13 // WITHOUT ANY WARRANTY; without even the implied warranty of
14 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15 // General Public License for more details.
16 //
17 // You should have received a copy of the GNU General Public License
18 // along with this program; if not, write to the Free Software
19 // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
20
21 #include "LocationWidget.hxx"
22 #include "ui_LocationWidget.h"
23
24 #include <QSettings>
25 #include <QAbstractListModel>
26 #include <QTimer>
27 #include <QDebug>
28 #include <QToolButton>
29
30 #include "AirportDiagram.hxx"
31 #include "NavaidDiagram.hxx"
32
33 #include <Airports/airport.hxx>
34 #include <Airports/dynamics.hxx> // for parking
35 #include <Main/globals.hxx>
36 #include <Navaids/NavDataCache.hxx>
37 #include <Navaids/navrecord.hxx>
38 #include <Main/options.hxx>
39 #include <Main/fg_init.hxx>
40 #include <Main/fg_props.hxx> // for fgSetDouble
41
42 const int MAX_RECENT_AIRPORTS = 32;
43
44 using namespace flightgear;
45
46 QString fixNavaidName(QString s)
47 {
48     // split into words
49     QStringList words = s.split(QChar(' '));
50     QStringList changedWords;
51     Q_FOREACH(QString w, words) {
52         QString up = w.toUpper();
53
54         // expand common abbreviations
55         if (up == "FLD") {
56             changedWords.append("Field");
57             continue;
58         }
59
60         if (up == "MUNI") {
61             changedWords.append("Municipal");
62             continue;
63         }
64
65         if (up == "RGNL") {
66             changedWords.append("Regional");
67             continue;
68         }
69
70         if (up == "CTR") {
71             changedWords.append("Center");
72             continue;
73         }
74
75         if (up == "INTL") {
76             changedWords.append("International");
77             continue;
78         }
79
80         // occurs in many Australian airport names in our DB
81         if (up == "(NSW)") {
82             changedWords.append("(New South Wales)");
83             continue;
84         }
85
86         if ((up == "VOR") || (up == "NDB") || (up == "VOR-DME") || (up == "VORTAC") || (up == "NDB-DME")) {
87             changedWords.append(w);
88             continue;
89         }
90
91         QChar firstChar = w.at(0).toUpper();
92         w = w.mid(1).toLower();
93         w.prepend(firstChar);
94
95         changedWords.append(w);
96     }
97
98     return changedWords.join(QChar(' '));
99 }
100
101 QString formatGeodAsString(const SGGeod& geod)
102 {
103     QChar ns = (geod.getLatitudeDeg() > 0.0) ? 'N' : 'S';
104     QChar ew = (geod.getLongitudeDeg() > 0.0) ? 'E' : 'W';
105
106     return QString::number(fabs(geod.getLongitudeDeg()), 'f',2 ) + ew + " " +
107             QString::number(fabs(geod.getLatitudeDeg()), 'f',2 ) + ns;
108 }
109
110 bool parseStringAsGeod(const QString& s, SGGeod& result)
111 {
112     int commaPos = s.indexOf(QChar(','));
113     if (commaPos < 0)
114         return false;
115
116     bool ok;
117     double lon = s.leftRef(commaPos).toDouble(&ok);
118     if (!ok)
119         return false;
120
121     double lat = s.midRef(commaPos+1).toDouble(&ok);
122     if (!ok)
123         return false;
124
125     result = SGGeod::fromDeg(lon, lat);
126     return true;
127 }
128
129 class IdentSearchFilter : public FGPositioned::TypeFilter
130 {
131 public:
132     IdentSearchFilter()
133     {
134         addType(FGPositioned::AIRPORT);
135         addType(FGPositioned::SEAPORT);
136         addType(FGPositioned::HELIPAD);
137         addType(FGPositioned::VOR);
138         addType(FGPositioned::FIX);
139         addType(FGPositioned::NDB);
140     }
141 };
142
143 class NavSearchModel : public QAbstractListModel
144 {
145     Q_OBJECT
146 public:
147     NavSearchModel() :
148         m_searchActive(false)
149     {
150     }
151
152     void setSearch(QString t)
153     {
154         beginResetModel();
155
156         m_items.clear();
157         m_ids.clear();
158
159         std::string term(t.toUpper().toStdString());
160
161         IdentSearchFilter filter;
162         FGPositionedList exactMatches = NavDataCache::instance()->findAllWithIdent(term, &filter, true);
163
164         for (unsigned int i=0; i<exactMatches.size(); ++i) {
165             m_ids.push_back(exactMatches[i]->guid());
166             m_items.push_back(exactMatches[i]);
167         }
168         endResetModel();
169
170
171         m_search.reset(new NavDataCache::ThreadedGUISearch(term));
172         QTimer::singleShot(100, this, &NavSearchModel::onSearchResultsPoll);
173         m_searchActive = true;
174         endResetModel();
175     }
176
177     bool isSearchActive() const
178     {
179         return m_searchActive;
180     }
181
182     virtual int rowCount(const QModelIndex&) const
183     {
184         // if empty, return 1 for special 'no matches'?
185         return m_ids.size();
186     }
187
188     virtual QVariant data(const QModelIndex& index, int role) const
189     {
190         if (!index.isValid())
191             return QVariant();
192
193         FGPositionedRef pos = itemAtRow(index.row());
194         if (role == Qt::DisplayRole) {
195             if (pos->type() == FGPositioned::FIX) {
196                 // fixes don't have a name, show position instead
197                 return QString("Fix %1 (%2)").arg(QString::fromStdString(pos->ident()))
198                         .arg(formatGeodAsString(pos->geod()));
199             } else {
200                 QString name = fixNavaidName(QString::fromStdString(pos->name()));
201                 return QString("%1: %2").arg(QString::fromStdString(pos->ident())).arg(name);
202             }
203         }
204
205         if (role == Qt::EditRole) {
206             return QString::fromStdString(pos->ident());
207         }
208
209         if (role == Qt::UserRole) {
210             return static_cast<qlonglong>(m_ids[index.row()]);
211         }
212
213         return QVariant();
214     }
215
216     FGPositionedRef itemAtRow(unsigned int row) const
217     {
218         FGPositionedRef pos = m_items[row];
219         if (!pos.valid()) {
220             pos = NavDataCache::instance()->loadById(m_ids[row]);
221             m_items[row] = pos;
222         }
223
224         return pos;
225     }
226 Q_SIGNALS:
227     void searchComplete();
228
229 private:
230
231
232     void onSearchResultsPoll()
233     {
234         PositionedIDVec newIds = m_search->results();
235
236         beginInsertRows(QModelIndex(), m_ids.size(), newIds.size() - 1);
237         for (unsigned int i=m_ids.size(); i < newIds.size(); ++i) {
238             m_ids.push_back(newIds[i]);
239             m_items.push_back(FGPositionedRef()); // null ref
240         }
241         endInsertRows();
242
243         if (m_search->isComplete()) {
244             m_searchActive = false;
245             m_search.reset();
246             emit searchComplete();
247         } else {
248             QTimer::singleShot(100, this, &NavSearchModel::onSearchResultsPoll);
249         }
250     }
251
252 private:
253     PositionedIDVec m_ids;
254     mutable FGPositionedList m_items;
255     bool m_searchActive;
256     QScopedPointer<NavDataCache::ThreadedGUISearch> m_search;
257 };
258
259
260 LocationWidget::LocationWidget(QWidget *parent) :
261     QWidget(parent),
262     m_ui(new Ui::LocationWidget)
263 {
264     m_ui->setupUi(this);
265
266
267     QIcon historyIcon(":/history-icon");
268     m_ui->searchHistory->setIcon(historyIcon);
269
270     m_ui->searchIcon->setPixmap(QPixmap(":/search-icon"));
271
272     m_searchModel = new NavSearchModel;
273     m_ui->searchResultsList->setModel(m_searchModel);
274     connect(m_ui->searchResultsList, &QListView::clicked,
275             this, &LocationWidget::onSearchResultSelected);
276     connect(m_searchModel, &NavSearchModel::searchComplete,
277             this, &LocationWidget::onSearchComplete);
278
279     connect(m_ui->runwayCombo, SIGNAL(currentIndexChanged(int)),
280             this, SLOT(updateDescription()));
281     connect(m_ui->parkingCombo, SIGNAL(currentIndexChanged(int)),
282             this, SLOT(updateDescription()));
283     connect(m_ui->runwayRadio, SIGNAL(toggled(bool)),
284             this, SLOT(updateDescription()));
285     connect(m_ui->parkingRadio, SIGNAL(toggled(bool)),
286             this, SLOT(updateDescription()));
287     connect(m_ui->onFinalCheckbox, SIGNAL(toggled(bool)),
288             this, SLOT(updateDescription()));
289     connect(m_ui->approachDistanceSpin, SIGNAL(valueChanged(int)),
290             this, SLOT(updateDescription()));
291
292     connect(m_ui->airportDiagram, &AirportDiagram::clickedRunway,
293             this, &LocationWidget::onAirportDiagramClicked);
294
295     connect(m_ui->locationSearchEdit, &QLineEdit::returnPressed,
296             this, &LocationWidget::onSearch);
297
298     connect(m_ui->searchHistory, &QPushButton::clicked,
299             this, &LocationWidget::onPopupHistory);
300
301     connect(m_ui->trueBearing, &QCheckBox::toggled,
302             this, &LocationWidget::onOffsetBearingTrueChanged);
303     connect(m_ui->offsetGroup, &QGroupBox::toggled,
304             this, &LocationWidget::onOffsetEnabledToggled);
305     connect(m_ui->trueBearing, &QCheckBox::toggled, this,
306             &LocationWidget::onOffsetDataChanged);
307     connect(m_ui->offsetBearingSpinbox, SIGNAL(valueChanged(int)),
308             this, SLOT(onOffsetDataChanged()));
309     connect(m_ui->offsetNmSpinbox, SIGNAL(valueChanged(double)),
310             this, SLOT(onOffsetDataChanged()));
311
312     m_backButton = new QToolButton(this);
313     m_backButton->setGeometry(0, 0, 32, 32);
314     m_backButton->setIcon(QIcon(":/search-icon"));
315     m_backButton->raise();
316
317     connect(m_backButton, &QAbstractButton::clicked,
318             this, &LocationWidget::onBackToSearch);
319
320 // force various pieces of UI into sync
321     onOffsetEnabledToggled(m_ui->offsetGroup->isChecked());
322     onOffsetBearingTrueChanged(m_ui->trueBearing->isChecked());
323     onBackToSearch();
324 }
325
326 LocationWidget::~LocationWidget()
327 {
328     delete m_ui;
329 }
330
331 void LocationWidget::restoreSettings()
332 {
333     QSettings settings;
334     Q_FOREACH(QVariant v, settings.value("recent-locations").toList()) {
335         m_recentAirports.push_back(v.toLongLong());
336     }
337
338     if (!m_recentAirports.empty()) {
339         setBaseLocation(NavDataCache::instance()->loadById(m_recentAirports.front()));
340     }
341
342     updateDescription();
343 }
344
345 bool LocationWidget::shouldStartPaused() const
346 {
347     if (!m_location) {
348         return false; // defaults to on-ground at KSFO
349     }
350
351     if (FGAirport::isAirportType(m_location.ptr())) {
352         return m_ui->onFinalCheckbox->isChecked();
353     } else {
354         // navaid, start paused
355         return true;
356     }
357
358     return false;
359 }
360
361 void LocationWidget::saveSettings()
362 {
363     QSettings settings;
364
365     QVariantList locations;
366     Q_FOREACH(PositionedID v, m_recentAirports) {
367         locations.push_back(v);
368     }
369
370     settings.setValue("recent-airports", locations);
371 }
372
373 void LocationWidget::setLocationOptions()
374 {
375     flightgear::Options* opt = flightgear::Options::sharedInstance();
376
377     if (m_locationIsLatLon) {
378         // bypass the options mechanism because converting to deg:min:sec notation
379         // just to parse back again is nasty.
380         fgSetDouble("/sim/presets/latitude-deg", m_geodLocation.getLatitudeDeg());
381         fgSetDouble("/position/latitude-deg", m_geodLocation.getLatitudeDeg());
382         fgSetDouble("/sim/presets/longitude-deg", m_geodLocation.getLongitudeDeg());
383         fgSetDouble("/position/longitude-deg", m_geodLocation.getLongitudeDeg());
384         return;
385     }
386
387     if (!m_location) {
388         return;
389     }
390
391     if (FGAirport::isAirportType(m_location.ptr())) {
392         FGAirport* apt = static_cast<FGAirport*>(m_location.ptr());
393         opt->addOption("airport", apt->ident());
394
395         if (m_ui->runwayRadio->isChecked()) {
396             int index = m_ui->runwayCombo->itemData(m_ui->runwayCombo->currentIndex()).toInt();
397             if (index >= 0) {
398                 // explicit runway choice
399                 opt->addOption("runway", apt->getRunwayByIndex(index)->ident());
400             }
401
402             if (m_ui->onFinalCheckbox->isChecked()) {
403                 opt->addOption("glideslope", "3.0");
404                 opt->addOption("offset-distance", "10.0"); // in nautical miles
405             }
406         } else if (m_ui->parkingRadio->isChecked()) {
407             // parking selection
408             opt->addOption("parkpos", m_ui->parkingCombo->currentText().toStdString());
409         }
410         // of location is an airport
411     }
412
413     FGPositioned::Type ty = m_location->type();
414     switch (ty) {
415     case FGPositioned::VOR:
416     case FGPositioned::NDB:
417     case FGPositioned::FIX:
418         // set disambiguation property
419         globals->get_props()->setIntValue("/sim/presets/navaid-id",
420                                           static_cast<int>(m_location->guid()));
421
422         // we always set 'fix', but really this is just to force positionInit
423         // code to check for the navaid-id value above.
424         opt->addOption("fix", m_location->ident());
425         break;
426     default:
427         break;
428     }
429 }
430
431 void LocationWidget::onSearch()
432 {
433     QString search = m_ui->locationSearchEdit->text();
434
435     m_locationIsLatLon = parseStringAsGeod(search, m_geodLocation);
436     if (m_locationIsLatLon) {
437         m_ui->searchIcon->setVisible(false);
438         m_ui->searchStatusText->setText(QString("Position '%1'").arg(formatGeodAsString(m_geodLocation)));
439         m_location.clear();
440         onLocationChanged();
441         updateDescription();
442         return;
443     }
444
445     m_searchModel->setSearch(search);
446
447     if (m_searchModel->isSearchActive()) {
448         m_ui->searchStatusText->setText(QString("Searching for '%1'").arg(search));
449         m_ui->searchIcon->setVisible(true);
450     } else if (m_searchModel->rowCount(QModelIndex()) == 1) {
451         setBaseLocation(m_searchModel->itemAtRow(0));
452     }
453 }
454
455 void LocationWidget::onSearchComplete()
456 {
457     QString search = m_ui->locationSearchEdit->text();
458     m_ui->searchIcon->setVisible(false);
459     m_ui->searchStatusText->setText(QString("Results for '%1'").arg(search));
460
461     int numResults = m_searchModel->rowCount(QModelIndex());
462     if (numResults == 0) {
463         m_ui->searchStatusText->setText(QString("No matches for '%1'").arg(search));
464     } else if (numResults == 1) {
465         setBaseLocation(m_searchModel->itemAtRow(0));
466     }
467 }
468
469 void LocationWidget::onLocationChanged()
470 {
471     bool locIsAirport = FGAirport::isAirportType(m_location.ptr());
472     m_backButton->show();
473
474     if (locIsAirport) {
475         m_ui->stack->setCurrentIndex(0);
476         FGAirport* apt = static_cast<FGAirport*>(m_location.ptr());
477         m_ui->airportDiagram->setAirport(apt);
478
479         m_ui->runwayRadio->setChecked(true); // default back to runway mode
480         // unless multiplayer is enabled ?
481         m_ui->airportDiagram->setEnabled(true);
482
483         m_ui->runwayCombo->clear();
484         m_ui->runwayCombo->addItem("Automatic", -1);
485         for (unsigned int r=0; r<apt->numRunways(); ++r) {
486             FGRunwayRef rwy = apt->getRunwayByIndex(r);
487             // add runway with index as data role
488             m_ui->runwayCombo->addItem(QString::fromStdString(rwy->ident()), r);
489
490             m_ui->airportDiagram->addRunway(rwy);
491         }
492
493         m_ui->parkingCombo->clear();
494         FGAirportDynamics* dynamics = apt->getDynamics();
495         PositionedIDVec parkings = NavDataCache::instance()->airportItemsOfType(m_location->guid(),
496                                                                                 FGPositioned::PARKING);
497         if (parkings.empty()) {
498             m_ui->parkingCombo->setEnabled(false);
499             m_ui->parkingRadio->setEnabled(false);
500         } else {
501             m_ui->parkingCombo->setEnabled(true);
502             m_ui->parkingRadio->setEnabled(true);
503             Q_FOREACH(PositionedID parking, parkings) {
504                 FGParking* park = dynamics->getParking(parking);
505                 m_ui->parkingCombo->addItem(QString::fromStdString(park->getName()),
506                                             static_cast<qlonglong>(parking));
507
508                 m_ui->airportDiagram->addParking(park);
509             }
510         }
511
512     } else if (m_locationIsLatLon) {
513         m_ui->stack->setCurrentIndex(1);
514         m_ui->navaidDiagram->setGeod(m_geodLocation);
515     } else {
516         // navaid
517         m_ui->stack->setCurrentIndex(1);
518         m_ui->navaidDiagram->setNavaid(m_location);
519     }
520 }
521
522 void LocationWidget::onOffsetEnabledToggled(bool on)
523 {
524     m_ui->navaidDiagram->setOffsetEnabled(on);
525 }
526
527 void LocationWidget::onAirportDiagramClicked(FGRunwayRef rwy)
528 {
529     if (rwy) {
530         m_ui->runwayRadio->setChecked(true);
531         int rwyIndex = m_ui->runwayCombo->findText(QString::fromStdString(rwy->ident()));
532         m_ui->runwayCombo->setCurrentIndex(rwyIndex);
533         m_ui->airportDiagram->setSelectedRunway(rwy);
534     }
535
536     updateDescription();
537 }
538
539 QString LocationWidget::locationDescription() const
540 {
541     if (!m_location) {
542         if (m_locationIsLatLon) {
543             return QString("at position %1").arg(formatGeodAsString(m_geodLocation));
544         }
545
546         return QString("No location selected");
547     }
548
549     bool locIsAirport = FGAirport::isAirportType(m_location.ptr());
550     QString ident = QString::fromStdString(m_location->ident()),
551         name = QString::fromStdString(m_location->name());
552
553     name = fixNavaidName(name);
554
555     if (locIsAirport) {
556         //FGAirport* apt = static_cast<FGAirport*>(m_location.ptr());
557         QString locationOnAirport;
558
559         if (m_ui->runwayRadio->isChecked()) {
560             bool onFinal = m_ui->onFinalCheckbox->isChecked();
561             int comboIndex = m_ui->runwayCombo->currentIndex();
562             QString runwayName = (comboIndex == 0) ?
563                 "active runway" :
564                 QString("runway %1").arg(m_ui->runwayCombo->currentText());
565
566             if (onFinal) {
567                 int finalDistance = m_ui->approachDistanceSpin->value();
568                 locationOnAirport = QString("on %2-mile final to %1").arg(runwayName).arg(finalDistance);
569             } else {
570                 locationOnAirport = QString("on %1").arg(runwayName);
571             }
572         } else if (m_ui->parkingRadio->isChecked()) {
573             locationOnAirport = QString("at parking position %1").arg(m_ui->parkingCombo->currentText());
574         }
575
576         return QString("%2 (%1): %3").arg(ident).arg(name).arg(locationOnAirport);
577     } else {
578         QString navaidType;
579         switch (m_location->type()) {
580         case FGPositioned::VOR:
581             navaidType = QString("VOR"); break;
582         case FGPositioned::NDB:
583             navaidType = QString("NDB"); break;
584         case FGPositioned::FIX:
585             return QString("at waypoint %1").arg(ident);
586         default:
587             // unsupported type
588             break;
589         }
590
591         return QString("at %1 %2 (%3)").arg(navaidType).arg(ident).arg(name);
592     }
593
594     return QString("No location selected");
595 }
596
597
598 void LocationWidget::updateDescription()
599 {
600     bool locIsAirport = FGAirport::isAirportType(m_location.ptr());
601     if (locIsAirport) {
602         FGAirport* apt = static_cast<FGAirport*>(m_location.ptr());
603
604         if (m_ui->runwayRadio->isChecked()) {
605             int comboIndex = m_ui->runwayCombo->currentIndex();
606             int runwayIndex = m_ui->runwayCombo->itemData(comboIndex).toInt();
607             // we can't figure out the active runway in the launcher (yet)
608             FGRunwayRef rwy = (runwayIndex >= 0) ?
609                 apt->getRunwayByIndex(runwayIndex) : FGRunwayRef();
610             m_ui->airportDiagram->setSelectedRunway(rwy);
611         }
612
613         if (m_ui->onFinalCheckbox->isChecked()) {
614             m_ui->airportDiagram->setApproachExtensionDistance(m_ui->approachDistanceSpin->value());
615         } else {
616             m_ui->airportDiagram->setApproachExtensionDistance(0.0);
617         }        
618     } else {
619
620     }
621
622 #if 0
623
624     QString locationOnAirport;
625     if (m_ui->runwayRadio->isChecked()) {
626
627
628     } else if (m_ui->parkingRadio->isChecked()) {
629         locationOnAirport =  QString("at parking position %1").arg(m_ui->parkingCombo->currentText());
630     }
631
632     m_ui->airportDescription->setText();
633 #endif
634
635     emit descriptionChanged(locationDescription());
636 }
637
638 void LocationWidget::onSearchResultSelected(const QModelIndex& index)
639 {
640     setBaseLocation(m_searchModel->itemAtRow(index.row()));
641 }
642
643 void LocationWidget::onOffsetBearingTrueChanged(bool on)
644 {
645     m_ui->offsetBearingLabel->setText(on ? tr("True bearing:") :
646                                       tr("Magnetic bearing:"));
647 }
648
649
650 void LocationWidget::onPopupHistory()
651 {
652     if (m_recentAirports.isEmpty()) {
653         return;
654     }
655
656 #if 0
657     QMenu m;
658     Q_FOREACH(QString aptCode, m_recentAirports) {
659         FGAirportRef apt = FGAirport::findByIdent(aptCode.toStdString());
660         QString name = QString::fromStdString(apt->name());
661         QAction* act = m.addAction(QString("%1 - %2").arg(aptCode).arg(name));
662         act->setData(aptCode);
663     }
664
665     QPoint popupPos = m_ui->airportHistory->mapToGlobal(m_ui->airportHistory->rect().bottomLeft());
666     QAction* triggered = m.exec(popupPos);
667     if (triggered) {
668         FGAirportRef apt = FGAirport::findByIdent(triggered->data().toString().toStdString());
669         setAirport(apt);
670         m_ui->airportEdit->clear();
671         m_ui->locationStack->setCurrentIndex(0);
672     }
673 #endif
674 }
675
676 void LocationWidget::setBaseLocation(FGPositionedRef ref)
677 {
678     if (m_location == ref)
679         return;
680
681     m_location = ref;
682     onLocationChanged();
683
684 #if 0
685     if (ref.valid()) {
686         // maintain the recent airport list
687         QString icao = QString::fromStdString(ref->ident());
688         if (m_recentAirports.contains(icao)) {
689             // move to front
690             m_recentAirports.removeOne(icao);
691             m_recentAirports.push_front(icao);
692         } else {
693             // insert and trim list if necessary
694             m_recentAirports.push_front(icao);
695             if (m_recentAirports.size() > MAX_RECENT_AIRPORTS) {
696                 m_recentAirports.pop_back();
697             }
698         }
699     }
700 #endif
701     updateDescription();
702 }
703
704 void LocationWidget::onOffsetDataChanged()
705 {
706     m_ui->navaidDiagram->setOffsetEnabled(m_ui->offsetGroup->isChecked());
707     m_ui->navaidDiagram->setOffsetBearingDeg(m_ui->offsetBearingSpinbox->value());
708     m_ui->navaidDiagram->setOffsetDistanceNm(m_ui->offsetNmSpinbox->value());
709 }
710
711 void LocationWidget::onBackToSearch()
712 {
713     m_ui->stack->setCurrentIndex(2);
714     m_backButton->hide();
715 }
716
717 #include "LocationWidget.moc"