]> git.mxchange.org Git - flightgear.git/blob - src/GUI/LocationWidget.cxx
Trying to bullet-proof the traffic code.
[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 #include <QMovie>
30 #include <QPainter>
31
32 #include "AirportDiagram.hxx"
33 #include "NavaidDiagram.hxx"
34
35 #include <Airports/airport.hxx>
36 #include <Airports/dynamics.hxx> // for parking
37 #include <Main/globals.hxx>
38 #include <Navaids/NavDataCache.hxx>
39 #include <Navaids/navrecord.hxx>
40 #include <Main/options.hxx>
41 #include <Main/fg_init.hxx>
42 #include <Main/fg_props.hxx> // for fgSetDouble
43
44 using namespace flightgear;
45
46 const unsigned int MAX_RECENT_LOCATIONS = 64;
47
48 QString fixNavaidName(QString s)
49 {
50     // split into words
51     QStringList words = s.split(QChar(' '));
52     QStringList changedWords;
53     Q_FOREACH(QString w, words) {
54         QString up = w.toUpper();
55
56         // expand common abbreviations
57         if (up == "FLD") {
58             changedWords.append("Field");
59             continue;
60         }
61
62         if (up == "CO") {
63             changedWords.append("County");
64             continue;
65         }
66
67         if ((up == "MUNI") || (up == "MUN")) {
68             changedWords.append("Municipal");
69             continue;
70         }
71
72         if (up == "MEM") {
73             changedWords.append("Memorial");
74             continue;
75         }
76
77         if (up == "RGNL") {
78             changedWords.append("Regional");
79             continue;
80         }
81
82         if (up == "CTR") {
83             changedWords.append("Center");
84             continue;
85         }
86
87         if (up == "INTL") {
88             changedWords.append("International");
89             continue;
90         }
91
92         // occurs in many Australian airport names in our DB
93         if (up == "(NSW)") {
94             changedWords.append("(New South Wales)");
95             continue;
96         }
97
98         if ((up == "VOR") || (up == "NDB")
99                 || (up == "VOR-DME") || (up == "VORTAC")
100                 || (up == "NDB-DME")
101                 || (up == "AFB") || (up == "RAF"))
102         {
103             changedWords.append(w);
104             continue;
105         }
106
107         if ((up =="[X]") || (up == "[H]") || (up == "[S]")) {
108             continue; // consume
109         }
110
111         QChar firstChar = w.at(0).toUpper();
112         w = w.mid(1).toLower();
113         w.prepend(firstChar);
114
115         changedWords.append(w);
116     }
117
118     return changedWords.join(QChar(' '));
119 }
120
121 QString formatGeodAsString(const SGGeod& geod)
122 {
123     QChar ns = (geod.getLatitudeDeg() > 0.0) ? 'N' : 'S';
124     QChar ew = (geod.getLongitudeDeg() > 0.0) ? 'E' : 'W';
125
126     return QString::number(fabs(geod.getLongitudeDeg()), 'f',2 ) + ew + " " +
127             QString::number(fabs(geod.getLatitudeDeg()), 'f',2 ) + ns;
128 }
129
130 bool parseStringAsGeod(const QString& s, SGGeod& result)
131 {
132     int commaPos = s.indexOf(QChar(','));
133     if (commaPos < 0)
134         return false;
135
136     bool ok;
137     double lon = s.leftRef(commaPos).toDouble(&ok);
138     if (!ok)
139         return false;
140
141     double lat = s.midRef(commaPos+1).toDouble(&ok);
142     if (!ok)
143         return false;
144
145     result = SGGeod::fromDeg(lon, lat);
146     return true;
147 }
148
149 QVariant savePositionList(const FGPositionedList& posList)
150 {
151     QVariantList vl;
152     FGPositionedList::const_iterator it;
153     for (it = posList.begin(); it != posList.end(); ++it) {
154         QVariantMap vm;
155         FGPositionedRef pos = *it;
156         vm.insert("ident", QString::fromStdString(pos->ident()));
157         vm.insert("type", pos->type());
158         vm.insert("lat", pos->geod().getLatitudeDeg());
159         vm.insert("lon", pos->geod().getLongitudeDeg());
160         vl.append(vm);
161     }
162     return vl;
163 }
164
165 FGPositionedList loadPositionedList(QVariant v)
166 {
167     QVariantList vl = v.toList();
168     FGPositionedList result;
169     result.reserve(vl.size());
170     NavDataCache* cache = NavDataCache::instance();
171
172     Q_FOREACH(QVariant v, vl) {
173         QVariantMap vm = v.toMap();
174         std::string ident(vm.value("ident").toString().toStdString());
175         double lat = vm.value("lat").toDouble();
176         double lon = vm.value("lon").toDouble();
177         FGPositioned::Type ty(static_cast<FGPositioned::Type>(vm.value("type").toInt()));
178         FGPositioned::TypeFilter filter(ty);
179         FGPositionedRef pos = cache->findClosestWithIdent(ident,
180                                                           SGGeod::fromDeg(lon, lat),
181                                                           &filter);
182         if (pos)
183             result.push_back(pos);
184     }
185
186     return result;
187 }
188
189 class IdentSearchFilter : public FGPositioned::TypeFilter
190 {
191 public:
192     IdentSearchFilter(LauncherAircraftType aircraft)
193     {
194         addType(FGPositioned::VOR);
195         addType(FGPositioned::FIX);
196         addType(FGPositioned::NDB);
197
198         if (aircraft == Helicopter) {
199             addType(FGPositioned::HELIPAD);
200         }
201
202         if (aircraft == Seaplane) {
203             addType(FGPositioned::SEAPORT);
204         } else {
205             addType(FGPositioned::AIRPORT);
206         }
207     }
208 };
209
210 class NavSearchModel : public QAbstractListModel
211 {
212     Q_OBJECT
213 public:
214     NavSearchModel() :
215         m_searchActive(false)
216     {
217     }
218
219     void setSearch(QString t, LauncherAircraftType aircraft)
220     {
221         beginResetModel();
222
223         m_items.clear();
224         m_ids.clear();
225
226         std::string term(t.toUpper().toStdString());
227
228         IdentSearchFilter filter(aircraft);
229         FGPositionedList exactMatches = NavDataCache::instance()->findAllWithIdent(term, &filter, true);
230
231         for (unsigned int i=0; i<exactMatches.size(); ++i) {
232             m_ids.push_back(exactMatches[i]->guid());
233             m_items.push_back(exactMatches[i]);
234         }
235         endResetModel();
236
237         m_search.reset(new NavDataCache::ThreadedGUISearch(term));
238         QTimer::singleShot(100, this, SLOT(onSearchResultsPoll()));
239         m_searchActive = true;
240     }
241
242     bool isSearchActive() const
243     {
244         return m_searchActive;
245     }
246
247     virtual int rowCount(const QModelIndex&) const
248     {
249         // if empty, return 1 for special 'no matches'?
250         return m_ids.size();
251     }
252
253     virtual QVariant data(const QModelIndex& index, int role) const
254     {
255         if (!index.isValid())
256             return QVariant();
257
258         FGPositionedRef pos = itemAtRow(index.row());
259         if (role == Qt::DisplayRole) {
260             if (pos->type() == FGPositioned::FIX) {
261                 // fixes don't have a name, show position instead
262                 return QString("Fix %1 (%2)").arg(QString::fromStdString(pos->ident()))
263                         .arg(formatGeodAsString(pos->geod()));
264             } else {
265                 QString name = fixNavaidName(QString::fromStdString(pos->name()));
266                 return QString("%1: %2").arg(QString::fromStdString(pos->ident())).arg(name);
267             }
268         }
269
270         if (role == Qt::DecorationRole) {
271             return AirportDiagram::iconForPositioned(pos,
272                                                      AirportDiagram::SmallIcons | AirportDiagram::LargeAirportPlans);
273         }
274
275         if (role == Qt::EditRole) {
276             return QString::fromStdString(pos->ident());
277         }
278
279         if (role == Qt::UserRole) {
280             return static_cast<qlonglong>(m_ids[index.row()]);
281         }
282
283         return QVariant();
284     }
285
286     FGPositionedRef itemAtRow(unsigned int row) const
287     {
288         FGPositionedRef pos = m_items[row];
289         if (!pos.valid()) {
290             pos = NavDataCache::instance()->loadById(m_ids[row]);
291             m_items[row] = pos;
292         }
293
294         return pos;
295     }
296
297     void setItems(const FGPositionedList& items)
298     {
299         beginResetModel();
300         m_searchActive = false;
301         m_items = items;
302
303         m_ids.clear();
304         for (unsigned int i=0; i < items.size(); ++i) {
305             m_ids.push_back(m_items[i]->guid());
306         }
307
308         endResetModel();
309     }
310
311 Q_SIGNALS:
312     void searchComplete();
313
314 private slots:
315
316     void onSearchResultsPoll()
317     {
318         PositionedIDVec newIds = m_search->results();
319
320         beginInsertRows(QModelIndex(), m_ids.size(), newIds.size() - 1);
321         for (unsigned int i=m_ids.size(); i < newIds.size(); ++i) {
322             m_ids.push_back(newIds[i]);
323             m_items.push_back(FGPositionedRef()); // null ref
324         }
325         endInsertRows();
326
327         if (m_search->isComplete()) {
328             m_searchActive = false;
329             m_search.reset();
330             emit searchComplete();
331         } else {
332             QTimer::singleShot(100, this, SLOT(onSearchResultsPoll()));
333         }
334     }
335
336 private:
337     PositionedIDVec m_ids;
338     mutable FGPositionedList m_items;
339     bool m_searchActive;
340     QScopedPointer<NavDataCache::ThreadedGUISearch> m_search;
341 };
342
343
344 LocationWidget::LocationWidget(QWidget *parent) :
345     QWidget(parent),
346     m_ui(new Ui::LocationWidget),
347     m_locationIsLatLon(false),
348     m_aircraftType(Airplane)
349 {
350     m_ui->setupUi(this);
351
352     QIcon historyIcon(":/history-icon");
353     m_ui->searchHistory->setIcon(historyIcon);
354
355     QByteArray format;
356     m_ui->searchIcon->setMovie(new QMovie(":/spinner", format, this));
357
358     m_searchModel = new NavSearchModel;
359     m_ui->searchResultsList->setModel(m_searchModel);
360     connect(m_ui->searchResultsList, &QListView::clicked,
361             this, &LocationWidget::onSearchResultSelected);
362     connect(m_searchModel, &NavSearchModel::searchComplete,
363             this, &LocationWidget::onSearchComplete);
364
365     connect(m_ui->runwayCombo, SIGNAL(currentIndexChanged(int)),
366             this, SLOT(updateDescription()));
367     connect(m_ui->parkingCombo, SIGNAL(currentIndexChanged(int)),
368             this, SLOT(updateDescription()));
369     connect(m_ui->runwayRadio, SIGNAL(toggled(bool)),
370             this, SLOT(updateDescription()));
371     connect(m_ui->parkingRadio, SIGNAL(toggled(bool)),
372             this, SLOT(updateDescription()));
373     connect(m_ui->onFinalCheckbox, SIGNAL(toggled(bool)),
374             this, SLOT(updateDescription()));
375     connect(m_ui->approachDistanceSpin, SIGNAL(valueChanged(int)),
376             this, SLOT(updateDescription()));
377
378     connect(m_ui->airportDiagram, &AirportDiagram::clickedRunway,
379             this, &LocationWidget::onAirportRunwayClicked);
380     connect(m_ui->airportDiagram, &AirportDiagram::clickedParking,
381             this, &LocationWidget::onAirportParkingClicked);
382
383     connect(m_ui->locationSearchEdit, &QLineEdit::returnPressed,
384             this, &LocationWidget::onSearch);
385
386     connect(m_ui->searchHistory, &QPushButton::clicked,
387             this, &LocationWidget::onShowHistory);
388
389     connect(m_ui->trueBearing, &QCheckBox::toggled,
390             this, &LocationWidget::onOffsetBearingTrueChanged);
391     connect(m_ui->offsetGroup, &QGroupBox::toggled,
392             this, &LocationWidget::onOffsetEnabledToggled);
393     connect(m_ui->trueBearing, &QCheckBox::toggled, this,
394             &LocationWidget::onOffsetDataChanged);
395     connect(m_ui->offsetBearingSpinbox, SIGNAL(valueChanged(int)),
396             this, SLOT(onOffsetDataChanged()));
397     connect(m_ui->offsetNmSpinbox, SIGNAL(valueChanged(double)),
398             this, SLOT(onOffsetDataChanged()));
399     connect(m_ui->headingSpinbox, SIGNAL(valueChanged(int)),
400             this, SLOT(onHeadingChanged()));
401
402     m_backButton = new QToolButton(this);
403     m_backButton->setGeometry(0, 0, 64, 32);
404     m_backButton->setText("<< Back");
405     m_backButton->raise();
406
407     connect(m_backButton, &QAbstractButton::clicked,
408             this, &LocationWidget::onBackToSearch);
409
410 // force various pieces of UI into sync
411     onOffsetEnabledToggled(m_ui->offsetGroup->isChecked());
412     onOffsetBearingTrueChanged(m_ui->trueBearing->isChecked());
413     onBackToSearch();
414 }
415
416 LocationWidget::~LocationWidget()
417 {
418     delete m_ui;
419 }
420
421 void LocationWidget::restoreSettings()
422 {
423     QSettings settings;
424
425     if (settings.contains("location-lat")) {
426         m_locationIsLatLon = true;
427         m_geodLocation = SGGeod::fromDeg(settings.value("location-lon").toDouble(),
428                                          settings.value("location-lat").toDouble());
429     } else if (settings.contains("location-id")) {
430         m_location = NavDataCache::instance()->loadById(settings.value("location-id").toULongLong());
431         m_locationIsLatLon = false;
432     }
433
434     m_ui->altitudeSpinbox->setValue(settings.value("altitude", 6000).toInt());
435     m_ui->airspeedSpinbox->setValue(settings.value("speed", 120).toInt());
436     m_ui->headingSpinbox->setValue(settings.value("heading").toInt());
437     m_ui->offsetGroup->setChecked(settings.value("offset-enabled").toBool());
438     m_ui->offsetBearingSpinbox->setValue(settings.value("offset-bearing").toInt());
439     m_ui->offsetNmSpinbox->setValue(settings.value("offset-distance", 10).toInt());
440
441     m_recentLocations = loadPositionedList(settings.value("recent-locations"));
442     m_searchModel->setItems(m_recentLocations);
443
444     onLocationChanged();
445     updateDescription();
446 }
447
448 bool LocationWidget::shouldStartPaused() const
449 {
450     if (!m_location) {
451         return false; // defaults to on-ground at KSFO
452     }
453
454     if (FGAirport::isAirportType(m_location.ptr())) {
455         return m_ui->onFinalCheckbox->isChecked();
456     } else {
457         // navaid, start paused
458         return true;
459     }
460
461     return false;
462 }
463
464 void LocationWidget::saveSettings()
465 {
466     QSettings settings;
467
468     settings.remove("location-id");
469     settings.remove("location-lon");
470     settings.remove("location-lat");
471     if (m_locationIsLatLon) {
472         settings.setValue("location-lat", m_geodLocation.getLatitudeDeg());
473         settings.setValue("location-lon", m_geodLocation.getLongitudeDeg());
474
475     } else if (m_location) {
476         settings.setValue("location-id", static_cast<qlonglong>(m_location->guid()));
477     }
478
479     settings.setValue("altitude", m_ui->altitudeSpinbox->value());
480     settings.setValue("speed", m_ui->airspeedSpinbox->value());
481
482     settings.setValue("offset-enabled", m_ui->offsetGroup->isChecked());
483     settings.setValue("offset-bearing", m_ui->offsetBearingSpinbox->value());
484     settings.setValue("offset-distance", m_ui->offsetNmSpinbox->value());
485
486     // recent locations is saved on modification
487 }
488
489 void LocationWidget::setLocationOptions()
490 {
491     flightgear::Options* opt = flightgear::Options::sharedInstance();
492
493     std::string altStr = QString::number(m_ui->altitudeSpinbox->value()).toStdString();
494     std::string vcStr = QString::number(m_ui->airspeedSpinbox->value()).toStdString();
495     std::string headingStr = QString::number(m_ui->headingSpinbox->value()).toStdString();
496
497     // flip direction of azimuth to balance the flip done in fgApplyStartOffset
498     // I don't know why that flip exists but changing it there will break
499     // command-line compatability so compensating here instead
500     int offsetAzimuth = m_ui->offsetBearingSpinbox->value() - 180;
501     std::string azimuthStr = QString::number(offsetAzimuth).toStdString();
502     std::string distanceStr = QString::number(m_ui->offsetNmSpinbox->value()).toStdString();
503
504     if (m_locationIsLatLon) {
505         // bypass the options mechanism because converting to deg:min:sec notation
506         // just to parse back again is nasty.
507         fgSetDouble("/sim/presets/latitude-deg", m_geodLocation.getLatitudeDeg());
508         fgSetDouble("/position/latitude-deg", m_geodLocation.getLatitudeDeg());
509         fgSetDouble("/sim/presets/longitude-deg", m_geodLocation.getLongitudeDeg());
510         fgSetDouble("/position/longitude-deg", m_geodLocation.getLongitudeDeg());
511
512         opt->addOption("altitude", altStr);
513         opt->addOption("vc", vcStr);
514         opt->addOption("heading", headingStr);
515
516         if (m_ui->offsetGroup->isChecked()) {
517             opt->addOption("offset-azimuth", azimuthStr);
518             opt->addOption("offset-distance", distanceStr);
519         }
520         return;
521     }
522
523     if (!m_location) {
524         return;
525     }
526
527     if (FGAirport::isAirportType(m_location.ptr())) {
528         FGAirport* apt = static_cast<FGAirport*>(m_location.ptr());
529         opt->addOption("airport", apt->ident());
530
531         if (m_ui->runwayRadio->isChecked()) {
532             if (apt->type() == FGPositioned::AIRPORT) {
533                 int index = m_ui->runwayCombo->itemData(m_ui->runwayCombo->currentIndex()).toInt();
534                 if (index >= 0) {
535                     // explicit runway choice
536                     FGRunwayRef runway = apt->getRunwayByIndex(index);
537                     opt->addOption("runway", runway->ident());
538
539                     // set nav-radio 1 based on selected runway
540                     if (runway->ILS()) {
541                         double mhz = runway->ILS()->get_freq() / 100.0;
542                         QString navOpt = QString("%1:%2").arg(runway->headingDeg()).arg(mhz);
543                         opt->addOption("nav1", navOpt.toStdString());
544                     }
545                 }
546
547                 if (m_ui->onFinalCheckbox->isChecked()) {
548                     opt->addOption("glideslope", "3.0");
549                     double offsetNm = m_ui->approachDistanceSpin->value();
550                     opt->addOption("offset-distance", QString::number(offsetNm).toStdString());
551                 }
552             } else if (apt->type() == FGPositioned::HELIPORT) {
553                 int index = m_ui->runwayCombo->itemData(m_ui->runwayCombo->currentIndex()).toInt();
554                 if (index >= 0)  {
555                     // explicit pad choice
556                     FGHelipadRef pad = apt->getHelipadByIndex(index);
557                     opt->addOption("runway", pad->ident());
558                 }
559             } else {
560                 qWarning() << Q_FUNC_INFO << "implement me";
561             }
562
563         } else if (m_ui->parkingRadio->isChecked()) {
564             // parking selection
565             opt->addOption("parkpos", m_ui->parkingCombo->currentText().toStdString());
566         }
567         // of location is an airport
568     } else {
569         // location is a navaid
570         // note setting the ident here is ambigious, we really only need and
571         // want the 'navaid-id' property. However setting the 'real' option
572         // gives a better UI experience (eg existing Position in Air dialog)
573         FGPositioned::Type ty = m_location->type();
574         switch (ty) {
575         case FGPositioned::VOR:
576             opt->addOption("vor", m_location->ident());
577             setNavRadioOption();
578             break;
579
580         case FGPositioned::NDB:
581             opt->addOption("ndb", m_location->ident());
582             setNavRadioOption();
583             break;
584
585         case FGPositioned::FIX:
586             opt->addOption("fix", m_location->ident());
587             break;
588         default:
589             break;
590         }
591
592         opt->addOption("altitude", altStr);
593         opt->addOption("vc", vcStr);
594         opt->addOption("heading", headingStr);
595
596         // set disambiguation property
597         globals->get_props()->setIntValue("/sim/presets/navaid-id",
598                                           static_cast<int>(m_location->guid()));
599
600         if (m_ui->offsetGroup->isChecked()) {
601             opt->addOption("offset-azimuth", azimuthStr);
602             opt->addOption("offset-distance", distanceStr);
603         }
604     } // of navaid location
605 }
606
607 void LocationWidget::setNavRadioOption()
608 {
609     flightgear::Options* opt = flightgear::Options::sharedInstance();
610
611     if (m_location->type() == FGPositioned::VOR) {
612         FGNavRecordRef nav(static_cast<FGNavRecord*>(m_location.ptr()));
613         double mhz = nav->get_freq() / 100.0;
614         int heading = 0; // add heading support
615         QString navOpt = QString("%1:%2").arg(heading).arg(mhz);
616         opt->addOption("nav1", navOpt.toStdString());
617     } else {
618         FGNavRecordRef nav(static_cast<FGNavRecord*>(m_location.ptr()));
619         int khz = nav->get_freq() / 100;
620         int heading = 0;
621         QString adfOpt = QString("%1:%2").arg(heading).arg(khz);
622         qDebug() << "ADF opt is:" << adfOpt;
623         opt->addOption("adf1", adfOpt.toStdString());
624     }
625 }
626
627 void LocationWidget::onSearch()
628 {
629     QString search = m_ui->locationSearchEdit->text();
630
631     m_locationIsLatLon = parseStringAsGeod(search, m_geodLocation);
632     if (m_locationIsLatLon) {
633         m_ui->searchIcon->setVisible(false);
634         m_ui->searchStatusText->setText(QString("Position '%1'").arg(formatGeodAsString(m_geodLocation)));
635         m_location.clear();
636         onLocationChanged();
637         updateDescription();
638         return;
639     }
640
641     m_searchModel->setSearch(search, m_aircraftType);
642
643     if (m_searchModel->isSearchActive()) {
644         m_ui->searchStatusText->setText(QString("Searching for '%1'").arg(search));
645         m_ui->searchIcon->setVisible(true);
646         m_ui->searchIcon->movie()->start();
647     } else if (m_searchModel->rowCount(QModelIndex()) == 1) {
648         setBaseLocation(m_searchModel->itemAtRow(0));
649     }
650 }
651
652 void LocationWidget::onSearchComplete()
653 {
654     QString search = m_ui->locationSearchEdit->text();
655     m_ui->searchIcon->setVisible(false);
656     m_ui->searchStatusText->setText(QString("Results for '%1'").arg(search));
657
658     int numResults = m_searchModel->rowCount(QModelIndex());
659     if (numResults == 0) {
660         m_ui->searchStatusText->setText(QString("No matches for '%1'").arg(search));
661     } else if (numResults == 1) {
662         addToRecent(m_searchModel->itemAtRow(0));
663         setBaseLocation(m_searchModel->itemAtRow(0));
664     }
665 }
666
667 void LocationWidget::onLocationChanged()
668 {
669     bool locIsAirport = FGAirport::isAirportType(m_location.ptr());
670     m_backButton->show();
671
672     if (locIsAirport) {
673         m_ui->stack->setCurrentIndex(0);
674         FGAirport* apt = static_cast<FGAirport*>(m_location.ptr());
675         m_ui->airportDiagram->setAirport(apt);
676
677         m_ui->runwayRadio->setChecked(true); // default back to runway mode
678         // unless multiplayer is enabled ?
679         m_ui->airportDiagram->setEnabled(true);
680
681         m_ui->runwayCombo->clear();
682         m_ui->runwayCombo->addItem("Automatic", -1);
683
684         if (apt->type() == FGPositioned::HELIPORT) {
685             for (unsigned int r=0; r<apt->numHelipads(); ++r) {
686                 FGHelipadRef pad = apt->getHelipadByIndex(r);
687                 // add pad with index as data role
688                 m_ui->runwayCombo->addItem(QString::fromStdString(pad->ident()), r);
689
690                 m_ui->airportDiagram->addHelipad(pad);
691             }
692         } else {
693             for (unsigned int r=0; r<apt->numRunways(); ++r) {
694                 FGRunwayRef rwy = apt->getRunwayByIndex(r);
695                 // add runway with index as data role
696                 m_ui->runwayCombo->addItem(QString::fromStdString(rwy->ident()), r);
697
698                 m_ui->airportDiagram->addRunway(rwy);
699             }
700         }
701
702 #if 0
703         m_ui->parkingCombo->clear();
704         FGAirportDynamics* dynamics = apt->getDynamics();
705         PositionedIDVec parkings = NavDataCache::instance()->airportItemsOfType(m_location->guid(),
706                                                                                 FGPositioned::PARKING);
707         if (parkings.empty()) {
708             m_ui->parkingCombo->setEnabled(false);
709             m_ui->parkingRadio->setEnabled(false);
710         } else {
711             m_ui->parkingCombo->setEnabled(true);
712             m_ui->parkingRadio->setEnabled(true);
713             Q_FOREACH(PositionedID parking, parkings) {
714                 FGParking* park = dynamics->getParking(parking);
715                 m_ui->parkingCombo->addItem(QString::fromStdString(park->getName()),
716                                             static_cast<qlonglong>(parking));
717
718                 m_ui->airportDiagram->addParking(park);
719             }
720         }
721 #endif
722
723     } else if (m_locationIsLatLon) {
724         m_ui->stack->setCurrentIndex(1);
725         m_ui->navaidDiagram->setGeod(m_geodLocation);
726     } else if (m_location) {
727         // navaid
728         m_ui->stack->setCurrentIndex(1);
729         m_ui->navaidDiagram->setNavaid(m_location);
730     }
731 }
732
733 void LocationWidget::onOffsetEnabledToggled(bool on)
734 {
735     m_ui->navaidDiagram->setOffsetEnabled(on);
736     updateDescription();
737 }
738
739 void LocationWidget::onAirportRunwayClicked(FGRunwayRef rwy)
740 {
741     if (rwy) {
742         m_ui->runwayRadio->setChecked(true);
743         int rwyIndex = m_ui->runwayCombo->findText(QString::fromStdString(rwy->ident()));
744         m_ui->runwayCombo->setCurrentIndex(rwyIndex);
745         m_ui->airportDiagram->setSelectedRunway(rwy);
746     }
747
748     updateDescription();
749 }
750
751 void LocationWidget::onAirportParkingClicked(FGParkingRef park)
752 {
753     if (park) {
754         m_ui->parkingRadio->setChecked(true);
755         int parkingIndex = m_ui->parkingCombo->findText(QString::fromStdString(park->name()));
756         m_ui->parkingCombo->setCurrentIndex(parkingIndex);
757         m_ui->airportDiagram->setSelectedRunway(FGRunwayRef());
758     }
759
760     updateDescription();
761 }
762
763 QString compassPointFromHeading(int heading)
764 {
765     const int labelArc = 360 / 8;
766     heading += (labelArc >> 1);
767     SG_NORMALIZE_RANGE(heading, 0, 359);
768
769     switch (heading / labelArc) {
770     case 0: return "N";
771     case 1: return "NE";
772     case 2: return "E";
773     case 3: return "SE";
774     case 4: return "S";
775     case 5: return "SW";
776     case 6: return "W";
777     case 7: return "NW";
778     }
779
780     return QString();
781 }
782
783 QString LocationWidget::locationDescription() const
784 {
785     if (!m_location) {
786         if (m_locationIsLatLon) {
787             return QString("at position %1").arg(formatGeodAsString(m_geodLocation));
788         }
789
790         return QString("No location selected");
791     }
792
793     bool locIsAirport = FGAirport::isAirportType(m_location.ptr());
794     QString ident = QString::fromStdString(m_location->ident()),
795         name = QString::fromStdString(m_location->name());
796
797     name = fixNavaidName(name);
798
799     if (locIsAirport) {
800         //FGAirport* apt = static_cast<FGAirport*>(m_location.ptr());
801         QString locationOnAirport;
802
803         if (m_ui->runwayRadio->isChecked()) {
804             bool onFinal = m_ui->onFinalCheckbox->isChecked();
805             int comboIndex = m_ui->runwayCombo->currentIndex();
806             QString runwayName = (comboIndex == 0) ?
807                 "active runway" :
808                 QString("runway %1").arg(m_ui->runwayCombo->currentText());
809
810             if (onFinal) {
811                 int finalDistance = m_ui->approachDistanceSpin->value();
812                 locationOnAirport = QString("on %2-mile final to %1").arg(runwayName).arg(finalDistance);
813             } else {
814                 locationOnAirport = QString("on %1").arg(runwayName);
815             }
816         } else if (m_ui->parkingRadio->isChecked()) {
817             locationOnAirport = QString("at parking position %1").arg(m_ui->parkingCombo->currentText());
818         }
819
820         return QString("%2 (%1): %3").arg(ident).arg(name).arg(locationOnAirport);
821     } else {
822         QString offsetDesc = tr("at");
823         if (m_ui->offsetGroup->isChecked()) {
824             offsetDesc = QString("%1nm %2 of").
825                     arg(m_ui->offsetNmSpinbox->value(), 0, 'f', 1).
826                     arg(compassPointFromHeading(m_ui->offsetBearingSpinbox->value()));
827         }
828
829         QString navaidType;
830         switch (m_location->type()) {
831         case FGPositioned::VOR:
832             navaidType = QString("VOR"); break;
833         case FGPositioned::NDB:
834             navaidType = QString("NDB"); break;
835         case FGPositioned::FIX:
836             return QString("%2 waypoint %1").arg(ident).arg(offsetDesc);
837         default:
838             // unsupported type
839             break;
840         }
841
842         return QString("%4 %1 %2 (%3)").arg(navaidType).arg(ident).arg(name).arg(offsetDesc);
843     }
844
845     return tr("No location selected");
846 }
847
848
849 void LocationWidget::updateDescription()
850 {
851     bool locIsAirport = FGAirport::isAirportType(m_location.ptr());
852     if (locIsAirport) {
853         FGAirport* apt = static_cast<FGAirport*>(m_location.ptr());
854
855         if (m_ui->runwayRadio->isChecked()) {
856             int comboIndex = m_ui->runwayCombo->currentIndex();
857             int runwayIndex = m_ui->runwayCombo->itemData(comboIndex).toInt();
858             if (apt->type() == FGPositioned::HELIPORT) {
859                 m_ui->airportDiagram->setSelectedRunway(FGRunwayRef());
860                 FGHelipadRef pad = (runwayIndex >= 0) ?
861                             apt->getHelipadByIndex(runwayIndex) : FGHelipadRef();
862                 m_ui->airportDiagram->setSelectedHelipad(pad);
863             } else {
864                 // we can't figure out the active runway in the launcher (yet)
865                 FGRunwayRef rwy = (runwayIndex >= 0) ?
866                     apt->getRunwayByIndex(runwayIndex) : FGRunwayRef();
867                 m_ui->airportDiagram->setSelectedRunway(rwy);
868             }
869         }
870
871         if (m_ui->onFinalCheckbox->isChecked()) {
872             m_ui->airportDiagram->setApproachExtensionDistance(m_ui->approachDistanceSpin->value());
873         } else {
874             m_ui->airportDiagram->setApproachExtensionDistance(0.0);
875         }
876     } else {
877
878     }
879
880 #if 0
881
882     QString locationOnAirport;
883     if (m_ui->runwayRadio->isChecked()) {
884
885
886     } else if (m_ui->parkingRadio->isChecked()) {
887         locationOnAirport =  QString("at parking position %1").arg(m_ui->parkingCombo->currentText());
888     }
889
890     m_ui->airportDescription->setText();
891 #endif
892
893     emit descriptionChanged(locationDescription());
894 }
895
896 void LocationWidget::onSearchResultSelected(const QModelIndex& index)
897 {
898     FGPositionedRef pos = m_searchModel->itemAtRow(index.row());
899     addToRecent(pos);
900     setBaseLocation(pos);
901 }
902
903 void LocationWidget::onOffsetBearingTrueChanged(bool on)
904 {
905     m_ui->offsetBearingLabel->setText(on ? tr("True bearing:") :
906                                            tr("Magnetic bearing:"));
907 }
908
909 void LocationWidget::addToRecent(FGPositionedRef pos)
910 {
911     FGPositionedList::iterator it = std::find(m_recentLocations.begin(),
912                                               m_recentLocations.end(),
913                                               pos);
914     if (it != m_recentLocations.end()) {
915         m_recentLocations.erase(it);
916     }
917
918     if (m_recentLocations.size() >= MAX_RECENT_LOCATIONS) {
919         m_recentLocations.pop_back();
920     }
921
922     m_recentLocations.insert(m_recentLocations.begin(), pos);
923     QSettings settings;
924     settings.setValue("recent-locations", savePositionList(m_recentLocations));
925 }
926
927
928 void LocationWidget::onShowHistory()
929 {
930     qDebug() << Q_FUNC_INFO;
931     m_searchModel->setItems(m_recentLocations);
932 }
933
934 void LocationWidget::setBaseLocation(FGPositionedRef ref)
935 {
936     m_locationIsLatLon = false;
937     if (m_location == ref)
938         return;
939
940     m_location = ref;
941     onLocationChanged();
942
943     updateDescription();
944 }
945
946 void LocationWidget::setAircraftType(LauncherAircraftType ty)
947 {
948     m_aircraftType = ty;
949     // nothing happens until next search
950     m_ui->navaidDiagram->setAircraftType(ty);
951     m_ui->airportDiagram->setAircraftType(ty);
952 }
953
954 void LocationWidget::onOffsetDataChanged()
955 {
956     m_ui->navaidDiagram->setOffsetEnabled(m_ui->offsetGroup->isChecked());
957     m_ui->navaidDiagram->setOffsetBearingDeg(m_ui->offsetBearingSpinbox->value());
958     m_ui->navaidDiagram->setOffsetDistanceNm(m_ui->offsetNmSpinbox->value());
959
960     updateDescription();
961 }
962
963 void LocationWidget::onHeadingChanged()
964 {
965     m_ui->navaidDiagram->setHeadingDeg(m_ui->headingSpinbox->value());
966 }
967
968 void LocationWidget::onBackToSearch()
969 {
970     m_ui->stack->setCurrentIndex(2);
971     m_backButton->hide();
972 }
973
974 #include "LocationWidget.moc"