]> git.mxchange.org Git - flightgear.git/blob - src/GUI/QtLauncher.cxx
Remove some debugs.
[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 AircraftProxyModel : public QSortFilterProxyModel
264 {
265     Q_OBJECT
266 public:
267     AircraftProxyModel(QObject* pr) :
268         QSortFilterProxyModel(pr),
269         m_ratingsFilter(true)
270     {
271         for (int i=0; i<4; ++i) {
272             m_ratings[i] = 3;
273         }
274     }
275
276     void setRatings(int* ratings)
277     {
278         ::memcpy(m_ratings, ratings, sizeof(int) * 4);
279         invalidate();
280     }
281
282 public slots:
283     void setRatingFilterEnabled(bool e)
284     {
285         if (e == m_ratingsFilter) {
286             return;
287         }
288
289         m_ratingsFilter = e;
290         invalidate();
291     }
292
293 protected:
294     bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const
295     {
296         if (!QSortFilterProxyModel::filterAcceptsRow(sourceRow, sourceParent)) {
297             return false;
298         }
299
300         if (m_ratingsFilter) {
301             QModelIndex index = sourceModel()->index(sourceRow, 0, sourceParent);
302             for (int i=0; i<4; ++i) {
303                 if (m_ratings[i] > index.data(AircraftRatingRole + i).toInt()) {
304                     return false;
305                 }
306             }
307         }
308
309         return true;
310     }
311
312 private:
313     bool m_ratingsFilter;
314     int m_ratings[4];
315 };
316
317 static void initQtResources()
318 {
319     Q_INIT_RESOURCE(resources);
320 }
321
322 namespace flightgear
323 {
324
325 void initApp(int& argc, char** argv)
326 {
327     static bool qtInitDone = false;
328     if (!qtInitDone) {
329         qtInitDone = true;
330
331         QApplication* app = new QApplication(argc, argv);
332         app->setOrganizationName("FlightGear");
333         app->setApplicationName("FlightGear");
334         app->setOrganizationDomain("flightgear.org");
335
336         // avoid double Apple menu and other weirdness if both Qt and OSG
337         // try to initialise various Cocoa structures.
338         flightgear::WindowBuilder::setPoseAsStandaloneApp(false);
339
340         Qt::KeyboardModifiers mods = app->queryKeyboardModifiers();
341         if (mods & Qt::AltModifier) {
342             qWarning() << "Alt pressed during launch";
343
344             // wipe out our settings
345             QSettings settings;
346             settings.clear();
347
348
349             Options::sharedInstance()->addOption("restore-defaults", "");
350         }
351     }
352 }
353
354 bool runLauncherDialog()
355 {
356     sglog().setLogLevels( SG_ALL, SG_INFO );
357
358     initQtResources(); // can't be called inside a namespaceb
359
360     // startup the nav-cache now. This pre-empts normal startup of
361     // the cache, but no harm done. (Providing scenery paths are consistent)
362
363     initNavCache();
364
365     fgInitPackageRoot();
366
367     // startup the HTTP system now since packages needs it
368     FGHTTPClient* http = new FGHTTPClient;
369     globals->add_subsystem("http", http);
370     // we guard against re-init in the global phase; bind and postinit
371     // will happen as normal
372     http->init();
373
374     // setup scenery paths now, especially TerraSync path for airport
375     // parking locations (after they're downloaded)
376
377     QtLauncher dlg;
378     dlg.exec();
379     if (dlg.result() != QDialog::Accepted) {
380         return false;
381     }
382
383     return true;
384 }
385
386 bool runInAppLauncherDialog()
387 {
388     QtLauncher dlg;
389     dlg.setInAppMode();
390     dlg.exec();
391     if (dlg.result() != QDialog::Accepted) {
392         return false;
393     }
394
395     return true;
396 }
397
398 } // of namespace flightgear
399
400 QtLauncher::QtLauncher() :
401     QDialog(),
402     m_ui(NULL),
403     m_subsystemIdleTimer(NULL),
404     m_inAppMode(false)
405 {
406     m_ui.reset(new Ui::Launcher);
407     m_ui->setupUi(this);
408
409 #if QT_VERSION >= 0x050300
410     // don't require Qt 5.3
411     m_ui->commandLineArgs->setPlaceholderText("--option=value --prop:/sim/name=value");
412 #endif
413
414 #if QT_VERSION >= 0x050200
415     m_ui->aircraftFilter->setClearButtonEnabled(true);
416 #endif
417
418     for (int i=0; i<4; ++i) {
419         m_ratingFilters[i] = 3;
420     }
421
422     m_subsystemIdleTimer = new QTimer(this);
423     m_subsystemIdleTimer->setInterval(0);
424     connect(m_subsystemIdleTimer, &QTimer::timeout,
425             this, &QtLauncher::onSubsytemIdleTimeout);
426     m_subsystemIdleTimer->start();
427
428     // create and configure the proxy model
429     m_aircraftProxy = new AircraftProxyModel(this);
430     connect(m_ui->ratingsFilterCheck, &QAbstractButton::toggled,
431             m_aircraftProxy, &AircraftProxyModel::setRatingFilterEnabled);
432     connect(m_ui->aircraftFilter, &QLineEdit::textChanged,
433             m_aircraftProxy, &QSortFilterProxyModel::setFilterFixedString);
434
435     connect(m_ui->runButton, SIGNAL(clicked()), this, SLOT(onRun()));
436     connect(m_ui->quitButton, SIGNAL(clicked()), this, SLOT(onQuit()));
437
438     connect(m_ui->aircraftHistory, &QPushButton::clicked,
439           this, &QtLauncher::onPopupAircraftHistory);
440
441     connect(m_ui->location, &LocationWidget::descriptionChanged,
442             m_ui->locationDescription, &QLabel::setText);
443
444     QAction* qa = new QAction(this);
445     qa->setShortcut(QKeySequence("Ctrl+Q"));
446     connect(qa, &QAction::triggered, this, &QtLauncher::onQuit);
447     addAction(qa);
448
449     connect(m_ui->editRatingFilter, &QPushButton::clicked,
450             this, &QtLauncher::onEditRatingsFilter);
451
452     QIcon historyIcon(":/history-icon");
453     m_ui->aircraftHistory->setIcon(historyIcon);
454
455     connect(m_ui->timeOfDayCombo, SIGNAL(currentIndexChanged(int)),
456             this, SLOT(updateSettingsSummary()));
457     connect(m_ui->seasonCombo, SIGNAL(currentIndexChanged(int)),
458             this, SLOT(updateSettingsSummary()));
459     connect(m_ui->fetchRealWxrCheckbox, SIGNAL(toggled(bool)),
460             this, SLOT(updateSettingsSummary()));
461     connect(m_ui->rembrandtCheckbox, SIGNAL(toggled(bool)),
462             this, SLOT(updateSettingsSummary()));
463     connect(m_ui->terrasyncCheck, SIGNAL(toggled(bool)),
464             this, SLOT(updateSettingsSummary()));
465     connect(m_ui->startPausedCheck, SIGNAL(toggled(bool)),
466             this, SLOT(updateSettingsSummary()));
467     connect(m_ui->msaaCheckbox, SIGNAL(toggled(bool)),
468             this, SLOT(updateSettingsSummary()));
469
470     connect(m_ui->rembrandtCheckbox, SIGNAL(toggled(bool)),
471             this, SLOT(onRembrandtToggled(bool)));
472     connect(m_ui->terrasyncCheck, &QCheckBox::toggled,
473             this, &QtLauncher::onToggleTerrasync);
474     updateSettingsSummary();
475
476     m_aircraftModel = new AircraftItemModel(this, RootRef(globals->packageRoot()));
477     m_aircraftProxy->setSourceModel(m_aircraftModel);
478
479     m_aircraftProxy->setFilterCaseSensitivity(Qt::CaseInsensitive);
480     m_aircraftProxy->setSortCaseSensitivity(Qt::CaseInsensitive);
481     m_aircraftProxy->setSortRole(Qt::DisplayRole);
482     m_aircraftProxy->setDynamicSortFilter(true);
483
484     m_ui->aircraftList->setModel(m_aircraftProxy);
485     m_ui->aircraftList->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
486     AircraftItemDelegate* delegate = new AircraftItemDelegate(m_ui->aircraftList);
487     m_ui->aircraftList->setItemDelegate(delegate);
488     m_ui->aircraftList->setSelectionMode(QAbstractItemView::SingleSelection);
489     connect(m_ui->aircraftList, &QListView::clicked,
490             this, &QtLauncher::onAircraftSelected);
491     connect(delegate, &AircraftItemDelegate::variantChanged,
492             this, &QtLauncher::onAircraftSelected);
493     connect(delegate, &AircraftItemDelegate::requestInstall,
494             this, &QtLauncher::onRequestPackageInstall);
495     connect(delegate, &AircraftItemDelegate::cancelDownload,
496             this, &QtLauncher::onCancelDownload);
497
498     connect(m_aircraftModel, &AircraftItemModel::aircraftInstallCompleted,
499             this, &QtLauncher::onAircraftInstalledCompleted);
500     connect(m_aircraftModel, &AircraftItemModel::aircraftInstallFailed,
501             this, &QtLauncher::onAircraftInstallFailed);
502     connect(m_aircraftModel, &AircraftItemModel::scanCompleted,
503             this, &QtLauncher::updateSelectedAircraft);
504     connect(m_ui->pathsButton, &QPushButton::clicked,
505             this, &QtLauncher::onEditPaths);
506
507     restoreSettings();
508
509     QSettings settings;
510     m_aircraftModel->setPaths(settings.value("aircraft-paths").toStringList());
511     m_aircraftModel->scanDirs();
512 }
513
514 QtLauncher::~QtLauncher()
515 {
516
517 }
518
519 void QtLauncher::setInAppMode()
520 {
521   m_inAppMode = true;
522   m_ui->tabWidget->removeTab(2);
523   m_ui->runButton->setText(tr("Apply"));
524   m_ui->quitButton->setText(tr("Cancel"));
525
526   disconnect(m_ui->runButton, SIGNAL(clicked()), this, SLOT(onRun()));
527   connect(m_ui->runButton, SIGNAL(clicked()), this, SLOT(onApply()));
528 }
529
530 bool QtLauncher::runLauncherDialog()
531 {
532     sglog().setLogLevels( SG_ALL, SG_INFO );
533     Q_INIT_RESOURCE(resources);
534
535     static bool qtInitDone = false;
536     static int s_argc;
537     if (!qtInitDone) {
538         qtInitDone = true;
539         s_argc = argc; // QApplication only stores a reference to argc,
540         // and may crash if it is freed
541         // http://doc.qt.io/qt-5/qguiapplication.html#QGuiApplication
542
543         QApplication* app = new QApplication(s_argc, argv);
544         app->setOrganizationName("FlightGear");
545         app->setApplicationName("FlightGear");
546         app->setOrganizationDomain("flightgear.org");
547
548         // avoid double Apple menu and other weirdness if both Qt and OSG
549         // try to initialise various Cocoa structures.
550         flightgear::WindowBuilder::setPoseAsStandaloneApp(false);
551
552         Qt::KeyboardModifiers mods = app->queryKeyboardModifiers();
553         if (mods & Qt::AltModifier) {
554             qWarning() << "Alt pressed during launch";
555
556             // wipe out our settings
557             QSettings settings;
558             settings.clear();
559
560             Options::sharedInstance()->addOption("restore-defaults", "");
561         }
562     }
563
564     // startup the nav-cache now. This preempts normal startup of
565     // the cache, but no harm done. (Providing scenery paths are consistent)
566
567     initNavCache();
568
569   // setup scenery paths now, especially TerraSync path for airport
570   // parking locations (after they're downloaded)
571
572     QtLauncher dlg;
573     dlg.exec();
574     if (dlg.result() != QDialog::Accepted) {
575         return false;
576     }
577 }
578
579 void QtLauncher::restoreSettings()
580 {
581     QSettings settings;
582     m_ui->rembrandtCheckbox->setChecked(settings.value("enable-rembrandt", false).toBool());
583     m_ui->terrasyncCheck->setChecked(settings.value("enable-terrasync", true).toBool());
584     m_ui->fullScreenCheckbox->setChecked(settings.value("start-fullscreen", false).toBool());
585     m_ui->msaaCheckbox->setChecked(settings.value("enable-msaa", false).toBool());
586     m_ui->fetchRealWxrCheckbox->setChecked(settings.value("enable-realwx", true).toBool());
587     m_ui->startPausedCheck->setChecked(settings.value("start-paused", false).toBool());
588     m_ui->timeOfDayCombo->setCurrentIndex(settings.value("timeofday", 0).toInt());
589     m_ui->seasonCombo->setCurrentIndex(settings.value("season", 0).toInt());
590
591     // full paths to -set.xml files
592     m_recentAircraft = QUrl::fromStringList(settings.value("recent-aircraft").toStringList());
593
594     if (!m_recentAircraft.empty()) {
595         m_selectedAircraft = m_recentAircraft.front();
596     } else {
597         // select the default C172p
598     }
599
600     updateSelectedAircraft();
601     m_ui->location->restoreSettings();
602
603     // rating filters
604     m_ui->ratingsFilterCheck->setChecked(settings.value("ratings-filter", true).toBool());
605     int index = 0;
606     Q_FOREACH(QVariant v, settings.value("min-ratings").toList()) {
607         m_ratingFilters[index++] = v.toInt();
608     }
609
610     m_aircraftProxy->setRatingFilterEnabled(m_ui->ratingsFilterCheck->isChecked());
611     m_aircraftProxy->setRatings(m_ratingFilters);
612
613     m_ui->commandLineArgs->setPlainText(settings.value("additional-args").toString());
614 }
615
616 void QtLauncher::saveSettings()
617 {
618     QSettings settings;
619     settings.setValue("enable-rembrandt", m_ui->rembrandtCheckbox->isChecked());
620     settings.setValue("enable-terrasync", m_ui->terrasyncCheck->isChecked());
621     settings.setValue("enable-msaa", m_ui->msaaCheckbox->isChecked());
622     settings.setValue("start-fullscreen", m_ui->fullScreenCheckbox->isChecked());
623     settings.setValue("enable-realwx", m_ui->fetchRealWxrCheckbox->isChecked());
624     settings.setValue("start-paused", m_ui->startPausedCheck->isChecked());
625     settings.setValue("ratings-filter", m_ui->ratingsFilterCheck->isChecked());
626     settings.setValue("recent-aircraft", QUrl::toStringList(m_recentAircraft));
627
628     settings.setValue("timeofday", m_ui->timeOfDayCombo->currentIndex());
629     settings.setValue("season", m_ui->seasonCombo->currentIndex());
630     settings.setValue("additional-args", m_ui->commandLineArgs->toPlainText());
631
632     m_ui->location->saveSettings();
633     qDebug() << "saving settings";
634 }
635
636 void QtLauncher::setEnableDisableOptionFromCheckbox(QCheckBox* cbox, QString name) const
637 {
638     flightgear::Options* opt = flightgear::Options::sharedInstance();
639     std::string stdName(name.toStdString());
640     if (cbox->isChecked()) {
641         opt->addOption("enable-" + stdName, "");
642     } else {
643         opt->addOption("disable-" + stdName, "");
644     }
645 }
646
647 void QtLauncher::onRun()
648 {
649     accept();
650
651     flightgear::Options* opt = flightgear::Options::sharedInstance();
652     setEnableDisableOptionFromCheckbox(m_ui->terrasyncCheck, "terrasync");
653     setEnableDisableOptionFromCheckbox(m_ui->fetchRealWxrCheckbox, "real-weather-fetch");
654     setEnableDisableOptionFromCheckbox(m_ui->rembrandtCheckbox, "rembrandt");
655     setEnableDisableOptionFromCheckbox(m_ui->fullScreenCheckbox, "fullscreen");
656 //    setEnableDisableOptionFromCheckbox(m_ui->startPausedCheck, "freeze");
657
658     bool startPaused = m_ui->startPausedCheck->isChecked() ||
659             m_ui->location->shouldStartPaused();
660     if (startPaused) {
661         qDebug() << "will start paused";
662         opt->addOption("enable-freeze", "");
663     }
664
665     // MSAA is more complex
666     if (!m_ui->rembrandtCheckbox->isChecked()) {
667         if (m_ui->msaaCheckbox->isChecked()) {
668             globals->get_props()->setIntValue("/sim/rendering/multi-sample-buffers", 1);
669             globals->get_props()->setIntValue("/sim/rendering/multi-samples", 4);
670         } else {
671             globals->get_props()->setIntValue("/sim/rendering/multi-sample-buffers", 0);
672         }
673     }
674
675     // aircraft
676     if (!m_selectedAircraft.isEmpty()) {
677         if (m_selectedAircraft.isLocalFile()) {
678             QFileInfo setFileInfo(m_selectedAircraft.toLocalFile());
679             opt->addOption("aircraft-dir", setFileInfo.dir().absolutePath().toStdString());
680             QString setFile = setFileInfo.fileName();
681             Q_ASSERT(setFile.endsWith("-set.xml"));
682             setFile.truncate(setFile.count() - 8); // drop the '-set.xml' portion
683             opt->addOption("aircraft", setFile.toStdString());
684         } else if (m_selectedAircraft.scheme() == "package") {
685             PackageRef pkg = packageForAircraftURI(m_selectedAircraft);
686             // no need to set aircraft-dir, handled by the corresponding code
687             // in fgInitAircraft
688             opt->addOption("aircraft", pkg->qualifiedId());
689         } else {
690             qWarning() << "unsupported aircraft launch URL" << m_selectedAircraft;
691         }
692
693       // manage aircraft history
694         if (m_recentAircraft.contains(m_selectedAircraft))
695           m_recentAircraft.removeOne(m_selectedAircraft);
696         m_recentAircraft.prepend(m_selectedAircraft);
697         if (m_recentAircraft.size() > MAX_RECENT_AIRCRAFT)
698           m_recentAircraft.pop_back();
699
700         qDebug() << "recent aircraft is now" << m_recentAircraft;
701     }
702
703     m_ui->location->setLocationOptions();
704
705     // time of day
706     if (m_ui->timeOfDayCombo->currentIndex() != 0) {
707         QString dayval = m_ui->timeOfDayCombo->currentText().toLower();
708         opt->addOption("timeofday", dayval.toStdString());
709     }
710
711     if (m_ui->seasonCombo->currentIndex() != 0) {
712         QString dayval = m_ui->timeOfDayCombo->currentText().toLower();
713         opt->addOption("season", dayval.toStdString());
714     }
715
716     QSettings settings;
717     QString downloadDir = settings.value("download-dir").toString();
718     if (!downloadDir.isEmpty()) {
719         QDir d(downloadDir);
720         if (!d.exists()) {
721             int result = QMessageBox::question(this, tr("Create download folder?"),
722                                   tr("The selected location for downloads does not exist. Create it?"),
723                                                QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel);
724             if (result == QMessageBox::Cancel) {
725                 return;
726             }
727
728             if (result == QMessageBox::Yes) {
729                 d.mkpath(downloadDir);
730             }
731         }
732
733         qDebug() << "Download dir is:" << downloadDir;
734         opt->addOption("download-dir", downloadDir.toStdString());
735     }
736
737     // scenery paths
738     Q_FOREACH(QString path, settings.value("scenery-paths").toStringList()) {
739         opt->addOption("fg-scenery", path.toStdString());
740     }
741
742     // aircraft paths
743     Q_FOREACH(QString path, settings.value("aircraft-paths").toStringList()) {
744         // can't use fg-aircraft for this, as it is processed before the launcher is run
745         globals->append_aircraft_path(path.toStdString());
746     }
747
748     // additional arguments
749     ArgumentsTokenizer tk;
750     Q_FOREACH(ArgumentsTokenizer::Arg a, tk.tokenize(m_ui->commandLineArgs->toPlainText())) {
751         if (a.arg.startsWith("prop:")) {
752             QString v = a.arg.mid(5) + "=" + a.value;
753             opt->addOption("prop", v.toStdString());
754         } else {
755             opt->addOption(a.arg.toStdString(), a.value.toStdString());
756         }
757     }
758
759     saveSettings();
760 }
761
762
763 void QtLauncher::onApply()
764 {
765     accept();
766
767     // aircraft
768     if (!m_selectedAircraft.isEmpty()) {
769         std::string aircraftPropValue,
770             aircraftDir;
771
772         if (m_selectedAircraft.isLocalFile()) {
773             QFileInfo setFileInfo(m_selectedAircraft.toLocalFile());
774             QString setFile = setFileInfo.fileName();
775             Q_ASSERT(setFile.endsWith("-set.xml"));
776             setFile.truncate(setFile.count() - 8); // drop the '-set.xml' portion
777             aircraftDir = setFileInfo.dir().absolutePath().toStdString();
778             aircraftPropValue = setFile.toStdString();
779         } else if (m_selectedAircraft.scheme() == "package") {
780             PackageRef pkg = packageForAircraftURI(m_selectedAircraft);
781             // no need to set aircraft-dir, handled by the corresponding code
782             // in fgInitAircraft
783             aircraftPropValue = pkg->qualifiedId();
784         } else {
785             qWarning() << "unsupported aircraft launch URL" << m_selectedAircraft;
786         }
787
788         // manage aircraft history
789         if (m_recentAircraft.contains(m_selectedAircraft))
790             m_recentAircraft.removeOne(m_selectedAircraft);
791         m_recentAircraft.prepend(m_selectedAircraft);
792         if (m_recentAircraft.size() > MAX_RECENT_AIRCRAFT)
793             m_recentAircraft.pop_back();
794
795         globals->get_props()->setStringValue("/sim/aircraft", aircraftPropValue);
796         globals->get_props()->setStringValue("/sim/aircraft-dir", aircraftDir);
797     }
798
799
800     saveSettings();
801 }
802
803 void QtLauncher::onQuit()
804 {
805     reject();
806 }
807
808 void QtLauncher::onToggleTerrasync(bool enabled)
809 {
810     if (enabled) {
811         QSettings settings;
812         QString downloadDir = settings.value("download-dir").toString();
813         if (downloadDir.isEmpty()) {
814             downloadDir = QString::fromStdString(flightgear::defaultDownloadDir());
815         }
816
817         QFileInfo info(downloadDir);
818         if (!info.exists()) {
819             QMessageBox msg;
820             msg.setWindowTitle(tr("Create download folder?"));
821             msg.setText(tr("The download folder '%1' does not exist, create it now? "
822                            "Click 'change location' to choose another folder "
823                            "to store downloaded files").arg(downloadDir));
824             msg.addButton(QMessageBox::Yes);
825             msg.addButton(QMessageBox::Cancel);
826             msg.addButton(tr("Change location"), QMessageBox::ActionRole);
827             int result = msg.exec();
828
829             if (result == QMessageBox::Cancel) {
830                 m_ui->terrasyncCheck->setChecked(false);
831                 return;
832             }
833
834             if (result == QMessageBox::ActionRole) {
835                 onEditPaths();
836                 return;
837             }
838
839             QDir d(downloadDir);
840             d.mkpath(downloadDir);
841         }
842     } // of is enabled
843 }
844
845 void QtLauncher::onAircraftInstalledCompleted(QModelIndex index)
846 {
847     maybeUpdateSelectedAircraft(index);
848 }
849
850 void QtLauncher::onAircraftInstallFailed(QModelIndex index, QString errorMessage)
851 {
852     qWarning() << Q_FUNC_INFO << index.data(AircraftURIRole) << errorMessage;
853
854     QMessageBox msg;
855     msg.setWindowTitle(tr("Aircraft installation failed"));
856     msg.setText(tr("An error occurred installing the aircraft %1: %2").
857                 arg(index.data(Qt::DisplayRole).toString()).arg(errorMessage));
858     msg.addButton(QMessageBox::Ok);
859     msg.exec();
860
861     maybeUpdateSelectedAircraft(index);
862 }
863
864 void QtLauncher::onAircraftSelected(const QModelIndex& index)
865 {
866     m_selectedAircraft = index.data(AircraftURIRole).toUrl();
867     qDebug() << "selected aircraft is now" << m_selectedAircraft;
868     updateSelectedAircraft();
869 }
870
871 void QtLauncher::onRequestPackageInstall(const QModelIndex& index)
872 {
873     QString pkg = index.data(AircraftPackageIdRole).toString();
874     simgear::pkg::PackageRef pref = globals->packageRoot()->getPackageById(pkg.toStdString());
875     if (pref->isInstalled()) {
876         InstallRef install = pref->existingInstall();
877         if (install && install->hasUpdate()) {
878             globals->packageRoot()->scheduleToUpdate(install);
879         }
880     } else {
881         pref->install();
882     }
883 }
884
885 void QtLauncher::onCancelDownload(const QModelIndex& index)
886 {
887     QString pkg = index.data(AircraftPackageIdRole).toString();
888     simgear::pkg::PackageRef pref = globals->packageRoot()->getPackageById(pkg.toStdString());
889     simgear::pkg::InstallRef i = pref->existingInstall();
890     i->cancelDownload();
891 }
892
893 void QtLauncher::maybeUpdateSelectedAircraft(QModelIndex index)
894 {
895     QUrl u = index.data(AircraftURIRole).toUrl();
896     if (u == m_selectedAircraft) {
897         // potentially enable the run button now!
898         updateSelectedAircraft();
899     }
900 }
901
902 void QtLauncher::updateSelectedAircraft()
903 {
904     QModelIndex index = m_aircraftModel->indexOfAircraftURI(m_selectedAircraft);
905     if (index.isValid()) {
906         QPixmap pm = index.data(Qt::DecorationRole).value<QPixmap>();
907         m_ui->thumbnail->setPixmap(pm);
908         m_ui->aircraftDescription->setText(index.data(Qt::DisplayRole).toString());
909
910         int status = index.data(AircraftPackageStatusRole).toInt();
911         bool canRun = (status == PackageInstalled);
912         m_ui->runButton->setEnabled(canRun);
913     } else {
914         m_ui->thumbnail->setPixmap(QPixmap());
915         m_ui->aircraftDescription->setText("");
916         m_ui->runButton->setEnabled(false);
917     }
918 }
919
920 QModelIndex QtLauncher::proxyIndexForAircraftURI(QUrl uri) const
921 {
922   return m_aircraftProxy->mapFromSource(sourceIndexForAircraftURI(uri));
923 }
924
925 QModelIndex QtLauncher::sourceIndexForAircraftURI(QUrl uri) const
926 {
927     AircraftItemModel* sourceModel = qobject_cast<AircraftItemModel*>(m_aircraftProxy->sourceModel());
928     Q_ASSERT(sourceModel);
929     return sourceModel->indexOfAircraftURI(uri);
930 }
931
932 void QtLauncher::onPopupAircraftHistory()
933 {
934     if (m_recentAircraft.isEmpty()) {
935         return;
936     }
937
938     QMenu m;
939     Q_FOREACH(QUrl uri, m_recentAircraft) {
940         QModelIndex index = sourceIndexForAircraftURI(uri);
941         if (!index.isValid()) {
942             // not scanned yet
943             continue;
944         }
945         QAction* act = m.addAction(index.data(Qt::DisplayRole).toString());
946         act->setData(uri);
947     }
948
949     QPoint popupPos = m_ui->aircraftHistory->mapToGlobal(m_ui->aircraftHistory->rect().bottomLeft());
950     QAction* triggered = m.exec(popupPos);
951     if (triggered) {
952         m_selectedAircraft = triggered->data().toUrl();
953         QModelIndex index = proxyIndexForAircraftURI(m_selectedAircraft);
954         m_ui->aircraftList->selectionModel()->setCurrentIndex(index,
955                                                               QItemSelectionModel::ClearAndSelect);
956         m_ui->aircraftFilter->clear();
957         updateSelectedAircraft();
958     }
959 }
960
961 void QtLauncher::onEditRatingsFilter()
962 {
963     EditRatingsFilterDialog dialog(this);
964     dialog.setRatings(m_ratingFilters);
965
966     dialog.exec();
967     if (dialog.result() == QDialog::Accepted) {
968         QVariantList vl;
969         for (int i=0; i<4; ++i) {
970             m_ratingFilters[i] = dialog.getRating(i);
971             vl.append(m_ratingFilters[i]);
972         }
973         m_aircraftProxy->setRatings(m_ratingFilters);
974
975         QSettings settings;
976         settings.setValue("min-ratings", vl);
977     }
978 }
979
980 void QtLauncher::updateSettingsSummary()
981 {
982     QStringList summary;
983     if (m_ui->timeOfDayCombo->currentIndex() > 0) {
984         summary.append(QString(m_ui->timeOfDayCombo->currentText().toLower()));
985     }
986
987     if (m_ui->seasonCombo->currentIndex() > 0) {
988         summary.append(QString(m_ui->seasonCombo->currentText().toLower()));
989     }
990
991     if (m_ui->rembrandtCheckbox->isChecked()) {
992         summary.append("Rembrandt enabled");
993     } else if (m_ui->msaaCheckbox->isChecked()) {
994         summary.append("anti-aliasing");
995     }
996
997     if (m_ui->fetchRealWxrCheckbox->isChecked()) {
998         summary.append("live weather");
999     }
1000
1001     if (m_ui->terrasyncCheck->isChecked()) {
1002         summary.append("automatic scenery downloads");
1003     }
1004
1005     if (m_ui->startPausedCheck->isChecked()) {
1006         summary.append("paused");
1007     }
1008
1009     QString s = summary.join(", ");
1010     s[0] = s[0].toUpper();
1011     m_ui->settingsDescription->setText(s);
1012 }
1013
1014 void QtLauncher::onRembrandtToggled(bool b)
1015 {
1016     // Rembrandt and multi-sample are exclusive
1017     m_ui->msaaCheckbox->setEnabled(!b);
1018 }
1019
1020 void QtLauncher::onSubsytemIdleTimeout()
1021 {
1022     globals->get_subsystem_mgr()->update(0.0);
1023 }
1024
1025 void QtLauncher::onEditPaths()
1026 {
1027     PathsDialog dlg(this, globals->packageRoot());
1028     dlg.exec();
1029     if (dlg.result() == QDialog::Accepted) {
1030         // re-scan the aircraft list
1031         QSettings settings;
1032         m_aircraftModel->setPaths(settings.value("aircraft-paths").toStringList());
1033         m_aircraftModel->scanDirs();
1034     }
1035 }
1036
1037 simgear::pkg::PackageRef QtLauncher::packageForAircraftURI(QUrl uri) const
1038 {
1039     if (uri.scheme() != "package") {
1040         qWarning() << "invalid URL scheme:" << uri;
1041         return simgear::pkg::PackageRef();
1042     }
1043
1044     QString ident = uri.path();
1045     return globals->packageRoot()->getPackageById(ident.toStdString());
1046 }
1047
1048
1049 #include "QtLauncher.moc"