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