]> git.mxchange.org Git - flightgear.git/blob - src/GUI/QtLauncher.cxx
Start-paused for in-air starts
[flightgear.git] / src / GUI / QtLauncher.cxx
1 // QtLauncher.cxx - GUI launcher dialog using Qt5
2 //
3 // Written by James Turner, started December 2014.
4 //
5 // Copyright (C) 2014 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 "QtLauncher.hxx"
22 #include "QtLauncher_private.hxx"
23
24 // Qt
25 #include <QProgressDialog>
26 #include <QCoreApplication>
27 #include <QAbstractListModel>
28 #include <QDir>
29 #include <QFileInfo>
30 #include <QPixmap>
31 #include <QTimer>
32 #include <QDebug>
33 #include <QCompleter>
34 #include <QListView>
35 #include <QSettings>
36 #include <QSortFilterProxyModel>
37 #include <QMenu>
38 #include <QDesktopServices>
39 #include <QUrl>
40 #include <QAction>
41 #include <QFileDialog>
42 #include <QMessageBox>
43 #include <QDateTime>
44 #include <QApplication>
45 #include <QSpinBox>
46 #include <QDoubleSpinBox>
47
48 // Simgear
49 #include <simgear/timing/timestamp.hxx>
50 #include <simgear/props/props_io.hxx>
51 #include <simgear/structure/exception.hxx>
52 #include <simgear/structure/subsystem_mgr.hxx>
53 #include <simgear/misc/sg_path.hxx>
54 #include <simgear/package/Catalog.hxx>
55 #include <simgear/package/Package.hxx>
56 #include <simgear/package/Install.hxx>
57
58 #include "ui_Launcher.h"
59 #include "EditRatingsFilterDialog.hxx"
60 #include "AircraftItemDelegate.hxx"
61 #include "AircraftModel.hxx"
62 #include "PathsDialog.hxx"
63
64 #include <Main/globals.hxx>
65 #include <Navaids/NavDataCache.hxx>
66 #include <Navaids/navrecord.hxx>
67
68 #include <Main/options.hxx>
69 #include <Main/fg_init.hxx>
70 #include <Viewer/WindowBuilder.hxx>
71 #include <Network/HTTPClient.hxx>
72
73 using namespace flightgear;
74 using namespace simgear::pkg;
75
76 const int MAX_RECENT_AIRCRAFT = 20;
77
78 namespace { // anonymous namespace
79
80 void initNavCache()
81 {
82     QString baseLabel = QT_TR_NOOP("Initialising navigation data, this may take several minutes");
83     NavDataCache* cache = NavDataCache::createInstance();
84     if (cache->isRebuildRequired()) {
85         QProgressDialog rebuildProgress(baseLabel,
86                                        QString() /* cancel text */,
87                                        0, 100);
88         rebuildProgress.setWindowModality(Qt::WindowModal);
89         rebuildProgress.show();
90
91         NavDataCache::RebuildPhase phase = cache->rebuild();
92
93         while (phase != NavDataCache::REBUILD_DONE) {
94             // sleep to give the rebuild thread more time
95             SGTimeStamp::sleepForMSec(50);
96             phase = cache->rebuild();
97
98             switch (phase) {
99             case NavDataCache::REBUILD_AIRPORTS:
100                 rebuildProgress.setLabelText(QT_TR_NOOP("Loading airport data"));
101                 break;
102
103             case NavDataCache::REBUILD_FIXES:
104                 rebuildProgress.setLabelText(QT_TR_NOOP("Loading waypoint data"));
105                 break;
106
107             case NavDataCache::REBUILD_NAVAIDS:
108                 rebuildProgress.setLabelText(QT_TR_NOOP("Loading navigation data"));
109                 break;
110
111
112             case NavDataCache::REBUILD_POIS:
113                 rebuildProgress.setLabelText(QT_TR_NOOP("Loading point-of-interest data"));
114                 break;
115
116             default:
117                 rebuildProgress.setLabelText(baseLabel);
118             }
119
120             if (phase == NavDataCache::REBUILD_UNKNOWN) {
121                 rebuildProgress.setValue(0);
122                 rebuildProgress.setMaximum(0);
123             } else {
124                 rebuildProgress.setValue(cache->rebuildPhaseCompletionPercentage());
125                 rebuildProgress.setMaximum(100);
126             }
127
128             QCoreApplication::processEvents();
129         }
130     }
131 }
132
133 class ArgumentsTokenizer
134 {
135 public:
136     class Arg
137     {
138     public:
139         explicit Arg(QString k, QString v = QString()) : arg(k), value(v) {}
140
141         QString arg;
142         QString value;
143     };
144
145     QList<Arg> tokenize(QString in) const
146     {
147         int index = 0;
148         const int len = in.count();
149         QChar c, nc;
150         State state = Start;
151         QString key, value;
152         QList<Arg> result;
153
154         for (; index < len; ++index) {
155             c = in.at(index);
156             nc = index < (len - 1) ? in.at(index + 1) : QChar();
157
158             switch (state) {
159             case Start:
160                 if (c == QChar('-')) {
161                     if (nc == QChar('-')) {
162                         state = Key;
163                         key.clear();
164                         ++index;
165                     } else {
166                         // should we pemit single hyphen arguments?
167                         // choosing to fail for now
168                         return QList<Arg>();
169                     }
170                 } else if (c == QChar('#')) {
171                     state = Comment;
172                     break;
173                 } else if (c.isSpace()) {
174                     break;
175                 }
176                 break;
177
178             case Key:
179                 if (c == QChar('=')) {
180                     state = Value;
181                     value.clear();
182                 } else if (c.isSpace()) {
183                     state = Start;
184                     result.append(Arg(key));
185                 } else {
186                     // could check for illegal charatcers here
187                     key.append(c);
188                 }
189                 break;
190
191             case Value:
192                 if (c == QChar('"')) {
193                     state = Quoted;
194                 } else if (c.isSpace()) {
195                     state = Start;
196                     result.append(Arg(key, value));
197                 } else {
198                     value.append(c);
199                 }
200                 break;
201
202             case Quoted:
203                 if (c == QChar('\\')) {
204                     // check for escaped double-quote inside quoted value
205                     if (nc == QChar('"')) {
206                         ++index;
207                     }
208                 } else if (c == QChar('"')) {
209                     state = Value;
210                 } else {
211                     value.append(c);
212                 }
213                 break;
214
215             case Comment:
216                 if ((c == QChar('\n')) || (c == QChar('\r'))) {
217                     state = Start;
218                     break;
219                 } else {
220                     // nothing to do, eat comment chars
221                 }
222                 break;
223             } // of state switch
224         } // of character loop
225
226         // ensure last argument isn't lost
227         if (state == Key) {
228             result.append(Arg(key));
229         } else if (state == Value) {
230             result.append(Arg(key, value));
231         }
232
233         return result;
234     }
235
236 private:
237     enum State {
238         Start = 0,
239         Key,
240         Value,
241         Quoted,
242         Comment
243     };
244 };
245
246 } // of anonymous namespace
247
248 <<<<<<< 56d7d049bc0b7361d1799298c38e61084f5d5e3f
249 class IdentSearchFilter : public FGPositioned::TypeFilter
250 {
251 public:
252     IdentSearchFilter()
253     {
254         addType(FGPositioned::AIRPORT);
255         addType(FGPositioned::SEAPORT);
256         addType(FGPositioned::HELIPAD);
257         addType(FGPositioned::VOR);
258         addType(FGPositioned::FIX);
259         addType(FGPositioned::NDB);
260     }
261 };
262
263 class AirportSearchModel : public QAbstractListModel
264 {
265     Q_OBJECT
266 public:
267     AirportSearchModel() :
268         m_searchActive(false)
269     {
270     }
271
272     void setSearch(QString t)
273     {
274         beginResetModel();
275
276         m_items.clear();
277         m_ids.clear();
278
279         std::string term(t.toUpper().toStdString());
280
281         IdentSearchFilter filter;
282         FGPositionedList exactMatches = NavDataCache::instance()->findAllWithIdent(term, &filter, true);
283
284         for (unsigned int i=0; i<exactMatches.size(); ++i) {
285             m_ids.push_back(exactMatches[i]->guid());
286             m_items.push_back(exactMatches[i]);
287         }
288         endResetModel();
289
290
291         m_search.reset(new NavDataCache::ThreadedGUISearch(term));
292         QTimer::singleShot(100, this, SLOT(onSearchResultsPoll()));
293         m_searchActive = true;
294         endResetModel();
295     }
296
297     bool isSearchActive() const
298     {
299         return m_searchActive;
300     }
301
302     virtual int rowCount(const QModelIndex&) const
303     {
304         // if empty, return 1 for special 'no matches'?
305         return m_ids.size();
306     }
307
308     virtual QVariant data(const QModelIndex& index, int role) const
309     {
310         if (!index.isValid())
311             return QVariant();
312
313         FGPositionedRef pos = itemAtRow(index.row());
314         if (role == Qt::DisplayRole) {
315             QString name = QString::fromStdString(pos->name());
316             return QString("%1: %2").arg(QString::fromStdString(pos->ident())).arg(name);
317         }
318
319         if (role == Qt::EditRole) {
320             return QString::fromStdString(pos->ident());
321         }
322
323         if (role == Qt::UserRole) {
324             return static_cast<qlonglong>(m_ids[index.row()]);
325         }
326
327         return QVariant();
328     }
329
330     FGPositionedRef itemAtRow(unsigned int row) const
331     {
332         FGPositionedRef pos = m_items[row];
333         if (!pos.valid()) {
334             pos = NavDataCache::instance()->loadById(m_ids[row]);
335             m_items[row] = pos;
336         }
337
338         return pos;
339     }
340 Q_SIGNALS:
341     void searchComplete();
342
343 private slots:
344     void onSearchResultsPoll()
345     {
346         PositionedIDVec newIds = m_search->results();
347
348         beginInsertRows(QModelIndex(), m_ids.size(), newIds.size() - 1);
349         for (unsigned int i=m_ids.size(); i < newIds.size(); ++i) {
350             m_ids.push_back(newIds[i]);
351             m_items.push_back(FGPositionedRef()); // null ref
352         }
353         endInsertRows();
354
355         if (m_search->isComplete()) {
356             m_searchActive = false;
357             m_search.reset();
358             emit searchComplete();
359         } else {
360             QTimer::singleShot(100, this, SLOT(onSearchResultsPoll()));
361         }
362     }
363
364 private:
365     PositionedIDVec m_ids;
366     mutable FGPositionedList m_items;
367     bool m_searchActive;
368     QScopedPointer<NavDataCache::ThreadedGUISearch> m_search;
369 };
370 =======
371 >>>>>>> Work on LocationWidget for Qt launcher
372
373 class AircraftProxyModel : public QSortFilterProxyModel
374 {
375     Q_OBJECT
376 public:
377     AircraftProxyModel(QObject* pr) :
378         QSortFilterProxyModel(pr),
379         m_ratingsFilter(true)
380     {
381         for (int i=0; i<4; ++i) {
382             m_ratings[i] = 3;
383         }
384     }
385
386     void setRatings(int* ratings)
387     {
388         ::memcpy(m_ratings, ratings, sizeof(int) * 4);
389         invalidate();
390     }
391
392 public slots:
393     void setRatingFilterEnabled(bool e)
394     {
395         if (e == m_ratingsFilter) {
396             return;
397         }
398
399         m_ratingsFilter = e;
400         invalidate();
401     }
402
403 protected:
404     bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const
405     {
406         if (!QSortFilterProxyModel::filterAcceptsRow(sourceRow, sourceParent)) {
407             return false;
408         }
409
410         if (m_ratingsFilter) {
411             QModelIndex index = sourceModel()->index(sourceRow, 0, sourceParent);
412             for (int i=0; i<4; ++i) {
413                 if (m_ratings[i] > index.data(AircraftRatingRole + i).toInt()) {
414                     return false;
415                 }
416             }
417         }
418
419         return true;
420     }
421
422 private:
423     bool m_ratingsFilter;
424     int m_ratings[4];
425 };
426
427 static void initQtResources()
428 {
429     Q_INIT_RESOURCE(resources);
430 }
431
432 namespace flightgear
433 {
434
435 void initApp(int& argc, char** argv)
436 {
437     static bool qtInitDone = false;
438     if (!qtInitDone) {
439         qtInitDone = true;
440
441         QApplication* app = new QApplication(argc, argv);
442         app->setOrganizationName("FlightGear");
443         app->setApplicationName("FlightGear");
444         app->setOrganizationDomain("flightgear.org");
445
446         // avoid double Apple menu and other weirdness if both Qt and OSG
447         // try to initialise various Cocoa structures.
448         flightgear::WindowBuilder::setPoseAsStandaloneApp(false);
449
450         Qt::KeyboardModifiers mods = app->queryKeyboardModifiers();
451         if (mods & Qt::AltModifier) {
452             qWarning() << "Alt pressed during launch";
453
454             // wipe out our settings
455             QSettings settings;
456             settings.clear();
457
458
459             Options::sharedInstance()->addOption("restore-defaults", "");
460         }
461     }
462 }
463
464 bool runLauncherDialog()
465 {
466     sglog().setLogLevels( SG_ALL, SG_INFO );
467
468
469     initQtResources(); // can't be called inside a namespaceb
470
471     // startup the nav-cache now. This pre-empts normal startup of
472     // the cache, but no harm done. (Providing scenery paths are consistent)
473
474     initNavCache();
475
476
477     fgInitPackageRoot();
478
479     // startup the HTTP system now since packages needs it
480     FGHTTPClient* http = new FGHTTPClient;
481     globals->add_subsystem("http", http);
482     // we guard against re-init in the global phase; bind and postinit
483     // will happen as normal
484     http->init();
485
486
487     // setup scenery paths now, especially TerraSync path for airport
488     // parking locations (after they're downloaded)
489
490     QtLauncher dlg;
491     dlg.exec();
492     if (dlg.result() != QDialog::Accepted) {
493         return false;
494     }
495
496     return true;
497 }
498
499 bool runInAppLauncherDialog()
500 {
501     QtLauncher dlg;
502     dlg.setInAppMode();
503     dlg.exec();
504     if (dlg.result() != QDialog::Accepted) {
505         return false;
506     }
507
508     return true;
509 }
510
511 } // of namespace flightgear
512
513 QtLauncher::QtLauncher() :
514     QDialog(),
515     m_ui(NULL),
516     m_subsystemIdleTimer(NULL),
517     m_inAppMode(false)
518 {
519     m_ui.reset(new Ui::Launcher);
520     m_ui->setupUi(this);
521
522 #if QT_VERSION >= 0x050300
523     // don't require Qt 5.3
524     m_ui->commandLineArgs->setPlaceholderText("--option=value --prop:/sim/name=value");
525 #endif
526
527 #if QT_VERSION >= 0x050200
528     m_ui->aircraftFilter->setClearButtonEnabled(true);
529 #endif
530
531     for (int i=0; i<4; ++i) {
532         m_ratingFilters[i] = 3;
533     }
534
535     m_subsystemIdleTimer = new QTimer(this);
536     m_subsystemIdleTimer->setInterval(0);
537     connect(m_subsystemIdleTimer, &QTimer::timeout,
538             this, &QtLauncher::onSubsytemIdleTimeout);
539     m_subsystemIdleTimer->start();
540
541     // create and configure the proxy model
542     m_aircraftProxy = new AircraftProxyModel(this);
543     connect(m_ui->ratingsFilterCheck, &QAbstractButton::toggled,
544             m_aircraftProxy, &AircraftProxyModel::setRatingFilterEnabled);
545     connect(m_ui->aircraftFilter, &QLineEdit::textChanged,
546             m_aircraftProxy, &QSortFilterProxyModel::setFilterFixedString);
547
548 <<<<<<< 56d7d049bc0b7361d1799298c38e61084f5d5e3f
549     connect(m_ui->runwayCombo, SIGNAL(currentIndexChanged(int)),
550             this, SLOT(updateLocationDescription()));
551     connect(m_ui->parkingCombo, SIGNAL(currentIndexChanged(int)),
552             this, SLOT(updateLocationDescription()));
553     connect(m_ui->runwayRadio, SIGNAL(toggled(bool)),
554             this, SLOT(updateLocationDescription()));
555     connect(m_ui->parkingRadio, SIGNAL(toggled(bool)),
556             this, SLOT(updateLocationDescription()));
557     connect(m_ui->onFinalCheckbox, SIGNAL(toggled(bool)),
558             this, SLOT(updateLocationDescription()));
559
560
561     connect(m_ui->airportDiagram, &AirportDiagram::clickedRunway,
562             this, &QtLauncher::onAirportDiagramClicked);
563 =======
564 >>>>>>> Work on LocationWidget for Qt launcher
565
566     connect(m_ui->runButton, SIGNAL(clicked()), this, SLOT(onRun()));
567     connect(m_ui->quitButton, SIGNAL(clicked()), this, SLOT(onQuit()));
568
569     connect(m_ui->aircraftHistory, &QPushButton::clicked,
570           this, &QtLauncher::onPopupAircraftHistory);
571
572     connect(m_ui->location, &LocationWidget::descriptionChanged,
573             m_ui->locationDescription, &QLabel::setText);
574
575     QAction* qa = new QAction(this);
576     qa->setShortcut(QKeySequence("Ctrl+Q"));
577     connect(qa, &QAction::triggered, this, &QtLauncher::onQuit);
578     addAction(qa);
579
580     connect(m_ui->editRatingFilter, &QPushButton::clicked,
581             this, &QtLauncher::onEditRatingsFilter);
582
583     QIcon historyIcon(":/history-icon");
584     m_ui->aircraftHistory->setIcon(historyIcon);
585
586     connect(m_ui->timeOfDayCombo, SIGNAL(currentIndexChanged(int)),
587             this, SLOT(updateSettingsSummary()));
588     connect(m_ui->seasonCombo, SIGNAL(currentIndexChanged(int)),
589             this, SLOT(updateSettingsSummary()));
590     connect(m_ui->fetchRealWxrCheckbox, SIGNAL(toggled(bool)),
591             this, SLOT(updateSettingsSummary()));
592     connect(m_ui->rembrandtCheckbox, SIGNAL(toggled(bool)),
593             this, SLOT(updateSettingsSummary()));
594     connect(m_ui->terrasyncCheck, SIGNAL(toggled(bool)),
595             this, SLOT(updateSettingsSummary()));
596     connect(m_ui->startPausedCheck, SIGNAL(toggled(bool)),
597             this, SLOT(updateSettingsSummary()));
598     connect(m_ui->msaaCheckbox, SIGNAL(toggled(bool)),
599             this, SLOT(updateSettingsSummary()));
600
601     connect(m_ui->rembrandtCheckbox, SIGNAL(toggled(bool)),
602             this, SLOT(onRembrandtToggled(bool)));
603     connect(m_ui->terrasyncCheck, &QCheckBox::toggled,
604             this, &QtLauncher::onToggleTerrasync);
605     updateSettingsSummary();
606
607     m_aircraftModel = new AircraftItemModel(this, RootRef(globals->packageRoot()));
608     m_aircraftProxy->setSourceModel(m_aircraftModel);
609
610     m_aircraftProxy->setFilterCaseSensitivity(Qt::CaseInsensitive);
611     m_aircraftProxy->setSortCaseSensitivity(Qt::CaseInsensitive);
612     m_aircraftProxy->setSortRole(Qt::DisplayRole);
613     m_aircraftProxy->setDynamicSortFilter(true);
614
615     m_ui->aircraftList->setModel(m_aircraftProxy);
616     m_ui->aircraftList->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
617     AircraftItemDelegate* delegate = new AircraftItemDelegate(m_ui->aircraftList);
618     m_ui->aircraftList->setItemDelegate(delegate);
619     m_ui->aircraftList->setSelectionMode(QAbstractItemView::SingleSelection);
620     connect(m_ui->aircraftList, &QListView::clicked,
621             this, &QtLauncher::onAircraftSelected);
622     connect(delegate, &AircraftItemDelegate::variantChanged,
623             this, &QtLauncher::onAircraftSelected);
624     connect(delegate, &AircraftItemDelegate::requestInstall,
625             this, &QtLauncher::onRequestPackageInstall);
626     connect(delegate, &AircraftItemDelegate::cancelDownload,
627             this, &QtLauncher::onCancelDownload);
628
629     connect(m_aircraftModel, &AircraftItemModel::aircraftInstallCompleted,
630             this, &QtLauncher::onAircraftInstalledCompleted);
631     connect(m_aircraftModel, &AircraftItemModel::aircraftInstallFailed,
632             this, &QtLauncher::onAircraftInstallFailed);
633
634     connect(m_ui->pathsButton, &QPushButton::clicked,
635             this, &QtLauncher::onEditPaths);
636
637     restoreSettings();
638
639     QSettings settings;
640     m_aircraftModel->setPaths(settings.value("aircraft-paths").toStringList());
641     m_aircraftModel->scanDirs();
642 }
643
644 QtLauncher::~QtLauncher()
645 {
646
647 }
648
649 void QtLauncher::setInAppMode()
650 {
651   m_inAppMode = true;
652   m_ui->tabWidget->removeTab(2);
653   m_ui->runButton->setText(tr("Apply"));
654   m_ui->quitButton->setText(tr("Cancel"));
655
656   disconnect(m_ui->runButton, SIGNAL(clicked()), this, SLOT(onRun()));
657   connect(m_ui->runButton, SIGNAL(clicked()), this, SLOT(onApply()));
658 }
659
660 bool QtLauncher::runLauncherDialog()
661 {
662     sglog().setLogLevels( SG_ALL, SG_INFO );
663     Q_INIT_RESOURCE(resources);
664
665     static bool qtInitDone = false;
666     static int s_argc;
667     if (!qtInitDone) {
668         qtInitDone = true;
669         s_argc = argc; // QApplication only stores a reference to argc,
670         // and may crash if it is freed
671         // http://doc.qt.io/qt-5/qguiapplication.html#QGuiApplication
672
673         QApplication* app = new QApplication(s_argc, argv);
674         app->setOrganizationName("FlightGear");
675         app->setApplicationName("FlightGear");
676         app->setOrganizationDomain("flightgear.org");
677
678         // avoid double Apple menu and other weirdness if both Qt and OSG
679         // try to initialise various Cocoa structures.
680         flightgear::WindowBuilder::setPoseAsStandaloneApp(false);
681
682         Qt::KeyboardModifiers mods = app->queryKeyboardModifiers();
683         if (mods & Qt::AltModifier) {
684             qWarning() << "Alt pressed during launch";
685
686             // wipe out our settings
687             QSettings settings;
688             settings.clear();
689
690             Options::sharedInstance()->addOption("restore-defaults", "");
691         }
692     }
693
694     // startup the nav-cache now. This preempts normal startup of
695     // the cache, but no harm done. (Providing scenery paths are consistent)
696
697     initNavCache();
698
699   // setup scenery paths now, especially TerraSync path for airport
700   // parking locations (after they're downloaded)
701
702     QtLauncher dlg;
703     dlg.exec();
704     if (dlg.result() != QDialog::Accepted) {
705         return false;
706     }
707 }
708
709 void QtLauncher::restoreSettings()
710 {
711     QSettings settings;
712     m_ui->rembrandtCheckbox->setChecked(settings.value("enable-rembrandt", false).toBool());
713     m_ui->terrasyncCheck->setChecked(settings.value("enable-terrasync", true).toBool());
714     m_ui->fullScreenCheckbox->setChecked(settings.value("start-fullscreen", false).toBool());
715     m_ui->msaaCheckbox->setChecked(settings.value("enable-msaa", false).toBool());
716     m_ui->fetchRealWxrCheckbox->setChecked(settings.value("enable-realwx", true).toBool());
717     m_ui->startPausedCheck->setChecked(settings.value("start-paused", false).toBool());
718     m_ui->timeOfDayCombo->setCurrentIndex(settings.value("timeofday", 0).toInt());
719     m_ui->seasonCombo->setCurrentIndex(settings.value("season", 0).toInt());
720
721     // full paths to -set.xml files
722     m_recentAircraft = QUrl::fromStringList(settings.value("recent-aircraft").toStringList());
723
724     if (!m_recentAircraft.empty()) {
725         m_selectedAircraft = m_recentAircraft.front();
726     } else {
727         // select the default C172p
728     }
729
730     updateSelectedAircraft();
731     m_ui->location->restoreSettings();
732
733     // rating filters
734     m_ui->ratingsFilterCheck->setChecked(settings.value("ratings-filter", true).toBool());
735     int index = 0;
736     Q_FOREACH(QVariant v, settings.value("min-ratings").toList()) {
737         m_ratingFilters[index++] = v.toInt();
738     }
739
740     m_aircraftProxy->setRatingFilterEnabled(m_ui->ratingsFilterCheck->isChecked());
741     m_aircraftProxy->setRatings(m_ratingFilters);
742
743     m_ui->commandLineArgs->setPlainText(settings.value("additional-args").toString());
744 }
745
746 void QtLauncher::saveSettings()
747 {
748     QSettings settings;
749     settings.setValue("enable-rembrandt", m_ui->rembrandtCheckbox->isChecked());
750     settings.setValue("enable-terrasync", m_ui->terrasyncCheck->isChecked());
751     settings.setValue("enable-msaa", m_ui->msaaCheckbox->isChecked());
752     settings.setValue("start-fullscreen", m_ui->fullScreenCheckbox->isChecked());
753     settings.setValue("enable-realwx", m_ui->fetchRealWxrCheckbox->isChecked());
754     settings.setValue("start-paused", m_ui->startPausedCheck->isChecked());
755     settings.setValue("ratings-filter", m_ui->ratingsFilterCheck->isChecked());
756     settings.setValue("recent-aircraft", QUrl::toStringList(m_recentAircraft));
757
758     settings.setValue("timeofday", m_ui->timeOfDayCombo->currentIndex());
759     settings.setValue("season", m_ui->seasonCombo->currentIndex());
760     settings.setValue("additional-args", m_ui->commandLineArgs->toPlainText());
761
762     m_ui->location->saveSettings();
763 }
764
765 void QtLauncher::setEnableDisableOptionFromCheckbox(QCheckBox* cbox, QString name) const
766 {
767     flightgear::Options* opt = flightgear::Options::sharedInstance();
768     std::string stdName(name.toStdString());
769     if (cbox->isChecked()) {
770         opt->addOption("enable-" + stdName, "");
771     } else {
772         opt->addOption("disable-" + stdName, "");
773     }
774 }
775
776 void QtLauncher::onRun()
777 {
778     accept();
779
780     flightgear::Options* opt = flightgear::Options::sharedInstance();
781     setEnableDisableOptionFromCheckbox(m_ui->terrasyncCheck, "terrasync");
782     setEnableDisableOptionFromCheckbox(m_ui->fetchRealWxrCheckbox, "real-weather-fetch");
783     setEnableDisableOptionFromCheckbox(m_ui->rembrandtCheckbox, "rembrandt");
784     setEnableDisableOptionFromCheckbox(m_ui->fullScreenCheckbox, "fullscreen");
785 //    setEnableDisableOptionFromCheckbox(m_ui->startPausedCheck, "freeze");
786
787     bool startPaused = m_ui->startPausedCheck->isChecked() ||
788             m_ui->location->shouldStartPaused();
789     if (startPaused) {
790         opt->addOption("enable-freeze", "");
791     }
792
793     // MSAA is more complex
794     if (!m_ui->rembrandtCheckbox->isChecked()) {
795         if (m_ui->msaaCheckbox->isChecked()) {
796             globals->get_props()->setIntValue("/sim/rendering/multi-sample-buffers", 1);
797             globals->get_props()->setIntValue("/sim/rendering/multi-samples", 4);
798         } else {
799             globals->get_props()->setIntValue("/sim/rendering/multi-sample-buffers", 0);
800         }
801     }
802
803     // aircraft
804     if (!m_selectedAircraft.isEmpty()) {
805         if (m_selectedAircraft.isLocalFile()) {
806             QFileInfo setFileInfo(m_selectedAircraft.toLocalFile());
807             opt->addOption("aircraft-dir", setFileInfo.dir().absolutePath().toStdString());
808             QString setFile = setFileInfo.fileName();
809             Q_ASSERT(setFile.endsWith("-set.xml"));
810             setFile.truncate(setFile.count() - 8); // drop the '-set.xml' portion
811             opt->addOption("aircraft", setFile.toStdString());
812         } else if (m_selectedAircraft.scheme() == "package") {
813             PackageRef pkg = packageForAircraftURI(m_selectedAircraft);
814             // no need to set aircraft-dir, handled by the corresponding code
815             // in fgInitAircraft
816             opt->addOption("aircraft", pkg->qualifiedId());
817         } else {
818             qWarning() << "unsupported aircraft launch URL" << m_selectedAircraft;
819         }
820
821       // manage aircraft history
822         if (m_recentAircraft.contains(m_selectedAircraft))
823           m_recentAircraft.removeOne(m_selectedAircraft);
824         m_recentAircraft.prepend(m_selectedAircraft);
825         if (m_recentAircraft.size() > MAX_RECENT_AIRCRAFT)
826           m_recentAircraft.pop_back();
827     }
828
829     m_ui->location->setLocationOptions();
830
831     // time of day
832     if (m_ui->timeOfDayCombo->currentIndex() != 0) {
833         QString dayval = m_ui->timeOfDayCombo->currentText().toLower();
834         opt->addOption("timeofday", dayval.toStdString());
835     }
836
837     if (m_ui->seasonCombo->currentIndex() != 0) {
838         QString dayval = m_ui->timeOfDayCombo->currentText().toLower();
839         opt->addOption("season", dayval.toStdString());
840     }
841
842     QSettings settings;
843     QString downloadDir = settings.value("download-dir").toString();
844     if (!downloadDir.isEmpty()) {
845         QDir d(downloadDir);
846         if (!d.exists()) {
847             int result = QMessageBox::question(this, tr("Create download folder?"),
848                                   tr("The selected location for downloads does not exist. Create it?"),
849                                                QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel);
850             if (result == QMessageBox::Cancel) {
851                 return;
852             }
853
854             if (result == QMessageBox::Yes) {
855                 d.mkpath(downloadDir);
856             }
857         }
858
859         qDebug() << "Download dir is:" << downloadDir;
860         opt->addOption("download-dir", downloadDir.toStdString());
861     }
862
863     // scenery paths
864     Q_FOREACH(QString path, settings.value("scenery-paths").toStringList()) {
865         opt->addOption("fg-scenery", path.toStdString());
866     }
867
868     // aircraft paths
869     Q_FOREACH(QString path, settings.value("aircraft-paths").toStringList()) {
870         // can't use fg-aircraft for this, as it is processed before the launcher is run
871         globals->append_aircraft_path(path.toStdString());
872     }
873
874     // additional arguments
875     ArgumentsTokenizer tk;
876     Q_FOREACH(ArgumentsTokenizer::Arg a, tk.tokenize(m_ui->commandLineArgs->toPlainText())) {
877         if (a.arg.startsWith("prop:")) {
878             QString v = a.arg.mid(5) + "=" + a.value;
879             opt->addOption("prop", v.toStdString());
880         } else {
881             opt->addOption(a.arg.toStdString(), a.value.toStdString());
882         }
883     }
884
885     saveSettings();
886 }
887
888
889 void QtLauncher::onApply()
890 {
891     accept();
892
893     // aircraft
894     if (!m_selectedAircraft.isEmpty()) {
895         std::string aircraftPropValue,
896             aircraftDir;
897
898         if (m_selectedAircraft.isLocalFile()) {
899             QFileInfo setFileInfo(m_selectedAircraft.toLocalFile());
900             QString setFile = setFileInfo.fileName();
901             Q_ASSERT(setFile.endsWith("-set.xml"));
902             setFile.truncate(setFile.count() - 8); // drop the '-set.xml' portion
903             aircraftDir = setFileInfo.dir().absolutePath().toStdString();
904             aircraftPropValue = setFile.toStdString();
905         } else if (m_selectedAircraft.scheme() == "package") {
906             PackageRef pkg = packageForAircraftURI(m_selectedAircraft);
907             // no need to set aircraft-dir, handled by the corresponding code
908             // in fgInitAircraft
909             aircraftPropValue = pkg->qualifiedId();
910         } else {
911             qWarning() << "unsupported aircraft launch URL" << m_selectedAircraft;
912         }
913
914         // manage aircraft history
915         if (m_recentAircraft.contains(m_selectedAircraft))
916             m_recentAircraft.removeOne(m_selectedAircraft);
917         m_recentAircraft.prepend(m_selectedAircraft);
918         if (m_recentAircraft.size() > MAX_RECENT_AIRCRAFT)
919             m_recentAircraft.pop_back();
920
921         globals->get_props()->setStringValue("/sim/aircraft", aircraftPropValue);
922         globals->get_props()->setStringValue("/sim/aircraft-dir", aircraftDir);
923     }
924
925
926     saveSettings();
927 }
928
929 void QtLauncher::onQuit()
930 {
931     reject();
932 }
933
934 <<<<<<< 56d7d049bc0b7361d1799298c38e61084f5d5e3f
935 void QtLauncher::onSearchAirports()
936 {
937     QString search = m_ui->airportEdit->text();
938     m_airportsModel->setSearch(search);
939
940     if (m_airportsModel->isSearchActive()) {
941         m_ui->searchStatusText->setText(QString("Searching for '%1'").arg(search));
942         m_ui->locationStack->setCurrentIndex(2);
943     } else if (m_airportsModel->rowCount(QModelIndex()) == 1) {
944         setBaseLocation(m_airportsModel->itemAtRow(0));
945         m_ui->locationStack->setCurrentIndex(0);
946     }
947 }
948
949 void QtLauncher::onAirportSearchComplete()
950 {
951     int numResults = m_airportsModel->rowCount(QModelIndex());
952     if (numResults == 0) {
953         m_ui->searchStatusText->setText(QString("No matching airports for '%1'").arg(m_ui->airportEdit->text()));
954     } else if (numResults == 1) {
955         setBaseLocation(m_airportsModel->itemAtRow(0));
956         m_ui->locationStack->setCurrentIndex(0);
957     } else {
958         m_ui->locationStack->setCurrentIndex(1);
959     }
960 }
961
962 void QtLauncher::onLocationChanged()
963 {
964     bool locIsAirport = FGAirport::isAirportType(m_location.ptr());
965
966     m_ui->runwayCombo->setEnabled(locIsAirport);
967     m_ui->parkingCombo->setEnabled(locIsAirport);
968     if (locIsAirport) {
969         FGAirport* apt = static_cast<FGAirport*>(m_location.ptr());
970         m_ui->airportDiagram->setAirport(apt);
971
972         m_ui->runwayRadio->setChecked(true); // default back to runway mode
973         // unless multiplayer is enabled ?
974         m_ui->airportDiagram->setEnabled(true);
975
976         m_ui->runwayCombo->clear();
977         m_ui->runwayCombo->addItem("Automatic", -1);
978         for (unsigned int r=0; r<apt->numRunways(); ++r) {
979             FGRunwayRef rwy = apt->getRunwayByIndex(r);
980             // add runway with index as data role
981             m_ui->runwayCombo->addItem(QString::fromStdString(rwy->ident()), r);
982
983             m_ui->airportDiagram->addRunway(rwy);
984         }
985
986         m_ui->parkingCombo->clear();
987         FGAirportDynamics* dynamics = apt->getDynamics();
988         PositionedIDVec parkings = NavDataCache::instance()->airportItemsOfType(m_location->guid(),
989                                                                                 FGPositioned::PARKING);
990         if (parkings.empty()) {
991             m_ui->parkingCombo->setEnabled(false);
992             m_ui->parkingRadio->setEnabled(false);
993         } else {
994             m_ui->parkingCombo->setEnabled(true);
995             m_ui->parkingRadio->setEnabled(true);
996             Q_FOREACH(PositionedID parking, parkings) {
997                 FGParking* park = dynamics->getParking(parking);
998                 m_ui->parkingCombo->addItem(QString::fromStdString(park->getName()),
999                                             static_cast<qlonglong>(parking));
1000
1001                 m_ui->airportDiagram->addParking(park);
1002             }
1003         }
1004
1005
1006     } // of location is aiport
1007
1008
1009
1010
1011 }
1012
1013 void QtLauncher::onOffsetRadioToggled(bool on)
1014 {
1015     m_ui->offsetNmSpinbox->setEnabled(on);
1016     m_ui->offsetBearingSpinbox->setEnabled(on);
1017     m_ui->trueBearing->setEnabled(on);
1018     m_ui->offsetBearingLabel->setEnabled(on);
1019     m_ui->offsetDistanceLabel->setEnabled(on);
1020 }
1021
1022 void QtLauncher::onAirportDiagramClicked(FGRunwayRef rwy)
1023 {
1024     if (rwy) {
1025         m_ui->runwayRadio->setChecked(true);
1026         int rwyIndex = m_ui->runwayCombo->findText(QString::fromStdString(rwy->ident()));
1027         m_ui->runwayCombo->setCurrentIndex(rwyIndex);
1028     }
1029
1030     updateLocationDescription();
1031 }
1032 =======
1033 >>>>>>> Work on LocationWidget for Qt launcher
1034
1035 void QtLauncher::onToggleTerrasync(bool enabled)
1036 {
1037     if (enabled) {
1038         QSettings settings;
1039         QString downloadDir = settings.value("download-dir").toString();
1040         if (downloadDir.isEmpty()) {
1041             downloadDir = QString::fromStdString(flightgear::defaultDownloadDir());
1042         }
1043
1044         QFileInfo info(downloadDir);
1045         if (!info.exists()) {
1046             QMessageBox msg;
1047             msg.setWindowTitle(tr("Create download folder?"));
1048             msg.setText(tr("The download folder '%1' does not exist, create it now? "
1049                            "Click 'change location' to choose another folder "
1050                            "to store downloaded files").arg(downloadDir));
1051             msg.addButton(QMessageBox::Yes);
1052             msg.addButton(QMessageBox::Cancel);
1053             msg.addButton(tr("Change location"), QMessageBox::ActionRole);
1054             int result = msg.exec();
1055
1056             if (result == QMessageBox::Cancel) {
1057                 m_ui->terrasyncCheck->setChecked(false);
1058                 return;
1059             }
1060
1061             if (result == QMessageBox::ActionRole) {
1062                 onEditPaths();
1063                 return;
1064             }
1065
1066             QDir d(downloadDir);
1067             d.mkpath(downloadDir);
1068         }
1069     } // of is enabled
1070 }
1071
1072 void QtLauncher::onAircraftInstalledCompleted(QModelIndex index)
1073 {
1074     maybeUpdateSelectedAircraft(index);
1075 }
1076
1077 void QtLauncher::onAircraftInstallFailed(QModelIndex index, QString errorMessage)
1078 {
1079     qWarning() << Q_FUNC_INFO << index.data(AircraftURIRole) << errorMessage;
1080
1081     QMessageBox msg;
1082     msg.setWindowTitle(tr("Aircraft installation failed"));
1083     msg.setText(tr("An error occurred installing the aircraft %1: %2").
1084                 arg(index.data(Qt::DisplayRole).toString()).arg(errorMessage));
1085     msg.addButton(QMessageBox::Ok);
1086     msg.exec();
1087
1088     maybeUpdateSelectedAircraft(index);
1089 }
1090
1091 void QtLauncher::onAircraftSelected(const QModelIndex& index)
1092 {
1093     m_selectedAircraft = index.data(AircraftURIRole).toUrl();
1094     updateSelectedAircraft();
1095 }
1096
1097 void QtLauncher::onRequestPackageInstall(const QModelIndex& index)
1098 {
1099     QString pkg = index.data(AircraftPackageIdRole).toString();
1100     simgear::pkg::PackageRef pref = globals->packageRoot()->getPackageById(pkg.toStdString());
1101     if (pref->isInstalled()) {
1102         InstallRef install = pref->existingInstall();
1103         if (install && install->hasUpdate()) {
1104             globals->packageRoot()->scheduleToUpdate(install);
1105         }
1106     } else {
1107         pref->install();
1108     }
1109 }
1110
1111 void QtLauncher::onCancelDownload(const QModelIndex& index)
1112 {
1113     QString pkg = index.data(AircraftPackageIdRole).toString();
1114     simgear::pkg::PackageRef pref = globals->packageRoot()->getPackageById(pkg.toStdString());
1115     simgear::pkg::InstallRef i = pref->existingInstall();
1116     i->cancelDownload();
1117 }
1118
1119 void QtLauncher::maybeUpdateSelectedAircraft(QModelIndex index)
1120 {
1121     QUrl u = index.data(AircraftURIRole).toUrl();
1122     if (u == m_selectedAircraft) {
1123         // potentially enable the run button now!
1124         updateSelectedAircraft();
1125     }
1126 }
1127
1128 void QtLauncher::updateSelectedAircraft()
1129 {
1130     QModelIndex index = m_aircraftModel->indexOfAircraftURI(m_selectedAircraft);
1131     if (index.isValid()) {
1132         QPixmap pm = index.data(Qt::DecorationRole).value<QPixmap>();
1133         m_ui->thumbnail->setPixmap(pm);
1134         m_ui->aircraftDescription->setText(index.data(Qt::DisplayRole).toString());
1135
1136         int status = index.data(AircraftPackageStatusRole).toInt();
1137         bool canRun = (status == PackageInstalled);
1138         m_ui->runButton->setEnabled(canRun);
1139     } else {
1140         m_ui->thumbnail->setPixmap(QPixmap());
1141         m_ui->aircraftDescription->setText("");
1142         m_ui->runButton->setEnabled(false);
1143     }
1144 }
1145
1146 QModelIndex QtLauncher::proxyIndexForAircraftURI(QUrl uri) const
1147 {
1148   return m_aircraftProxy->mapFromSource(sourceIndexForAircraftURI(uri));
1149 }
1150
1151 QModelIndex QtLauncher::sourceIndexForAircraftURI(QUrl uri) const
1152 {
1153     AircraftItemModel* sourceModel = qobject_cast<AircraftItemModel*>(m_aircraftProxy->sourceModel());
1154     Q_ASSERT(sourceModel);
1155     return sourceModel->indexOfAircraftURI(uri);
1156 }
1157
1158 void QtLauncher::onPopupAircraftHistory()
1159 {
1160     if (m_recentAircraft.isEmpty()) {
1161         return;
1162     }
1163
1164     QMenu m;
1165     Q_FOREACH(QUrl uri, m_recentAircraft) {
1166         QModelIndex index = sourceIndexForAircraftURI(uri);
1167         if (!index.isValid()) {
1168             // not scanned yet
1169             continue;
1170         }
1171         QAction* act = m.addAction(index.data(Qt::DisplayRole).toString());
1172         act->setData(uri);
1173     }
1174
1175     QPoint popupPos = m_ui->aircraftHistory->mapToGlobal(m_ui->aircraftHistory->rect().bottomLeft());
1176     QAction* triggered = m.exec(popupPos);
1177     if (triggered) {
1178         m_selectedAircraft = triggered->data().toUrl();
1179         QModelIndex index = proxyIndexForAircraftURI(m_selectedAircraft);
1180         m_ui->aircraftList->selectionModel()->setCurrentIndex(index,
1181                                                               QItemSelectionModel::ClearAndSelect);
1182         m_ui->aircraftFilter->clear();
1183         updateSelectedAircraft();
1184     }
1185 }
1186
1187 void QtLauncher::onEditRatingsFilter()
1188 {
1189     EditRatingsFilterDialog dialog(this);
1190     dialog.setRatings(m_ratingFilters);
1191
1192     dialog.exec();
1193     if (dialog.result() == QDialog::Accepted) {
1194         QVariantList vl;
1195         for (int i=0; i<4; ++i) {
1196             m_ratingFilters[i] = dialog.getRating(i);
1197             vl.append(m_ratingFilters[i]);
1198         }
1199         m_aircraftProxy->setRatings(m_ratingFilters);
1200
1201         QSettings settings;
1202         settings.setValue("min-ratings", vl);
1203     }
1204 }
1205
1206 void QtLauncher::updateSettingsSummary()
1207 {
1208     QStringList summary;
1209     if (m_ui->timeOfDayCombo->currentIndex() > 0) {
1210         summary.append(QString(m_ui->timeOfDayCombo->currentText().toLower()));
1211     }
1212
1213     if (m_ui->seasonCombo->currentIndex() > 0) {
1214         summary.append(QString(m_ui->seasonCombo->currentText().toLower()));
1215     }
1216
1217     if (m_ui->rembrandtCheckbox->isChecked()) {
1218         summary.append("Rembrandt enabled");
1219     } else if (m_ui->msaaCheckbox->isChecked()) {
1220         summary.append("anti-aliasing");
1221     }
1222
1223     if (m_ui->fetchRealWxrCheckbox->isChecked()) {
1224         summary.append("live weather");
1225     }
1226
1227     if (m_ui->terrasyncCheck->isChecked()) {
1228         summary.append("automatic scenery downloads");
1229     }
1230
1231     if (m_ui->startPausedCheck->isChecked()) {
1232         summary.append("paused");
1233     }
1234
1235     QString s = summary.join(", ");
1236     s[0] = s[0].toUpper();
1237     m_ui->settingsDescription->setText(s);
1238 }
1239
1240 void QtLauncher::onRembrandtToggled(bool b)
1241 {
1242     // Rembrandt and multi-sample are exclusive
1243     m_ui->msaaCheckbox->setEnabled(!b);
1244 }
1245
1246 void QtLauncher::onSubsytemIdleTimeout()
1247 {
1248     globals->get_subsystem_mgr()->update(0.0);
1249 }
1250
1251 void QtLauncher::onEditPaths()
1252 {
1253     PathsDialog dlg(this, globals->packageRoot());
1254     dlg.exec();
1255     if (dlg.result() == QDialog::Accepted) {
1256         // re-scan the aircraft list
1257         QSettings settings;
1258         m_aircraftModel->setPaths(settings.value("aircraft-paths").toStringList());
1259         m_aircraftModel->scanDirs();
1260     }
1261 }
1262
1263 simgear::pkg::PackageRef QtLauncher::packageForAircraftURI(QUrl uri) const
1264 {
1265     if (uri.scheme() != "package") {
1266         qWarning() << "invalid URL scheme:" << uri;
1267         return simgear::pkg::PackageRef();
1268     }
1269
1270     QString ident = uri.path();
1271     return globals->packageRoot()->getPackageById(ident.toStdString());
1272 }
1273
1274
1275 #include "QtLauncher.moc"