1 // QtLauncher.cxx - GUI launcher dialog using Qt5
3 // Written by James Turner, started December 2014.
5 // Copyright (C) 2014 James Turner <zakalawe@mac.com>
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.
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.
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.
21 #include "QtLauncher.hxx"
22 #include "QtLauncher_private.hxx"
25 #include <QProgressDialog>
26 #include <QCoreApplication>
27 #include <QAbstractListModel>
36 #include <QSortFilterProxyModel>
38 #include <QDesktopServices>
41 #include <QFileDialog>
42 #include <QMessageBox>
44 #include <QApplication>
46 #include <QDoubleSpinBox>
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>
58 #include "ui_Launcher.h"
59 #include "EditRatingsFilterDialog.hxx"
60 #include "AircraftItemDelegate.hxx"
61 #include "AircraftModel.hxx"
62 #include "PathsDialog.hxx"
64 #include <Main/globals.hxx>
65 #include <Navaids/NavDataCache.hxx>
66 #include <Navaids/navrecord.hxx>
67 #include <Navaids/SHPParser.hxx>
69 #include <Main/options.hxx>
70 #include <Main/fg_init.hxx>
71 #include <Viewer/WindowBuilder.hxx>
72 #include <Network/HTTPClient.hxx>
74 using namespace flightgear;
75 using namespace simgear::pkg;
77 const int MAX_RECENT_AIRCRAFT = 20;
79 namespace { // anonymous namespace
83 QString baseLabel = QT_TR_NOOP("Initialising navigation data, this may take several minutes");
84 NavDataCache* cache = NavDataCache::createInstance();
85 if (cache->isRebuildRequired()) {
86 QProgressDialog rebuildProgress(baseLabel,
87 QString() /* cancel text */,
89 rebuildProgress.setWindowModality(Qt::WindowModal);
90 rebuildProgress.show();
92 NavDataCache::RebuildPhase phase = cache->rebuild();
94 while (phase != NavDataCache::REBUILD_DONE) {
95 // sleep to give the rebuild thread more time
96 SGTimeStamp::sleepForMSec(50);
97 phase = cache->rebuild();
100 case NavDataCache::REBUILD_AIRPORTS:
101 rebuildProgress.setLabelText(QT_TR_NOOP("Loading airport data"));
104 case NavDataCache::REBUILD_FIXES:
105 rebuildProgress.setLabelText(QT_TR_NOOP("Loading waypoint data"));
108 case NavDataCache::REBUILD_NAVAIDS:
109 rebuildProgress.setLabelText(QT_TR_NOOP("Loading navigation data"));
113 case NavDataCache::REBUILD_POIS:
114 rebuildProgress.setLabelText(QT_TR_NOOP("Loading point-of-interest data"));
118 rebuildProgress.setLabelText(baseLabel);
121 if (phase == NavDataCache::REBUILD_UNKNOWN) {
122 rebuildProgress.setValue(0);
123 rebuildProgress.setMaximum(0);
125 rebuildProgress.setValue(cache->rebuildPhaseCompletionPercentage());
126 rebuildProgress.setMaximum(100);
129 QCoreApplication::processEvents();
134 class ArgumentsTokenizer
140 explicit Arg(QString k, QString v = QString()) : arg(k), value(v) {}
146 QList<Arg> tokenize(QString in) const
149 const int len = in.count();
155 for (; index < len; ++index) {
157 nc = index < (len - 1) ? in.at(index + 1) : QChar();
161 if (c == QChar('-')) {
162 if (nc == QChar('-')) {
167 // should we pemit single hyphen arguments?
168 // choosing to fail for now
171 } else if (c == QChar('#')) {
174 } else if (c.isSpace()) {
180 if (c == QChar('=')) {
183 } else if (c.isSpace()) {
185 result.append(Arg(key));
187 // could check for illegal charatcers here
193 if (c == QChar('"')) {
195 } else if (c.isSpace()) {
197 result.append(Arg(key, value));
204 if (c == QChar('\\')) {
205 // check for escaped double-quote inside quoted value
206 if (nc == QChar('"')) {
209 } else if (c == QChar('"')) {
217 if ((c == QChar('\n')) || (c == QChar('\r'))) {
221 // nothing to do, eat comment chars
225 } // of character loop
227 // ensure last argument isn't lost
229 result.append(Arg(key));
230 } else if (state == Value) {
231 result.append(Arg(key, value));
247 } // of anonymous namespace
249 class AircraftProxyModel : public QSortFilterProxyModel
253 AircraftProxyModel(QObject* pr) :
254 QSortFilterProxyModel(pr),
255 m_ratingsFilter(true)
257 for (int i=0; i<4; ++i) {
262 void setRatings(int* ratings)
264 ::memcpy(m_ratings, ratings, sizeof(int) * 4);
269 void setRatingFilterEnabled(bool e)
271 if (e == m_ratingsFilter) {
280 bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const
282 if (!QSortFilterProxyModel::filterAcceptsRow(sourceRow, sourceParent)) {
286 if (m_ratingsFilter) {
287 QModelIndex index = sourceModel()->index(sourceRow, 0, sourceParent);
288 for (int i=0; i<4; ++i) {
289 if (m_ratings[i] > index.data(AircraftRatingRole + i).toInt()) {
299 bool m_ratingsFilter;
303 static void initQtResources()
305 Q_INIT_RESOURCE(resources);
311 void initApp(int& argc, char** argv)
313 sglog().setLogLevels( SG_ALL, SG_INFO );
314 initQtResources(); // can't be called from a namespace
316 static bool qtInitDone = false;
321 s_argc = argc; // QApplication only stores a reference to argc,
322 // and may crash if it is freed
323 // http://doc.qt.io/qt-5/qguiapplication.html#QGuiApplication
325 QApplication* app = new QApplication(s_argc, argv);
326 app->setOrganizationName("FlightGear");
327 app->setApplicationName("FlightGear");
328 app->setOrganizationDomain("flightgear.org");
330 // avoid double Apple menu and other weirdness if both Qt and OSG
331 // try to initialise various Cocoa structures.
332 flightgear::WindowBuilder::setPoseAsStandaloneApp(false);
334 Qt::KeyboardModifiers mods = app->queryKeyboardModifiers();
335 if (mods & Qt::AltModifier) {
336 qWarning() << "Alt pressed during launch";
338 // wipe out our settings
343 Options::sharedInstance()->addOption("restore-defaults", "");
348 void loadNaturalEarthFile(const std::string& aFileName,
349 flightgear::PolyLine::Type aType,
352 SGPath path(globals->get_fg_root());
353 path.append( "Geodata" );
354 path.append(aFileName);
356 return; // silently fail for now
358 flightgear::PolyLineList lines;
359 flightgear::SHPParser::parsePolyLines(path, aType, lines, areClosed);
360 flightgear::PolyLineList::iterator it;
361 for (it=lines.begin(); it != lines.end(); ++it) {
362 (*it)->addToSpatialIndex();
366 void loadNaturalEarthData()
371 loadNaturalEarthFile("ne_10m_coastline.shp", flightgear::PolyLine::COASTLINE, false);
372 loadNaturalEarthFile("ne_10m_rivers_lake_centerlines.shp", flightgear::PolyLine::RIVER, false);
373 loadNaturalEarthFile("ne_10m_lakes.shp", flightgear::PolyLine::LAKE, true);
375 qDebug() << "load basic data took" << st.elapsedMSec();
379 loadNaturalEarthFile("ne_10m_urban_areas.shp", flightgear::PolyLine::URBAN, true);
381 qDebug() << "loading urban areas took:" << st.elapsedMSec();
384 bool runLauncherDialog()
386 // startup the nav-cache now. This pre-empts normal startup of
387 // the cache, but no harm done. (Providing scenery paths are consistent)
393 // startup the HTTP system now since packages needs it
394 FGHTTPClient* http = new FGHTTPClient;
395 globals->add_subsystem("http", http);
396 // we guard against re-init in the global phase; bind and postinit
397 // will happen as normal
400 loadNaturalEarthData();
402 // setup scenery paths now, especially TerraSync path for airport
403 // parking locations (after they're downloaded)
407 if (dlg.result() != QDialog::Accepted) {
414 bool runInAppLauncherDialog()
419 if (dlg.result() != QDialog::Accepted) {
426 } // of namespace flightgear
428 QtLauncher::QtLauncher() :
431 m_subsystemIdleTimer(NULL),
434 m_ui.reset(new Ui::Launcher);
437 #if QT_VERSION >= 0x050300
438 // don't require Qt 5.3
439 m_ui->commandLineArgs->setPlaceholderText("--option=value --prop:/sim/name=value");
442 #if QT_VERSION >= 0x050200
443 m_ui->aircraftFilter->setClearButtonEnabled(true);
446 for (int i=0; i<4; ++i) {
447 m_ratingFilters[i] = 3;
450 m_subsystemIdleTimer = new QTimer(this);
451 m_subsystemIdleTimer->setInterval(0);
452 connect(m_subsystemIdleTimer, &QTimer::timeout,
453 this, &QtLauncher::onSubsytemIdleTimeout);
454 m_subsystemIdleTimer->start();
456 // create and configure the proxy model
457 m_aircraftProxy = new AircraftProxyModel(this);
458 connect(m_ui->ratingsFilterCheck, &QAbstractButton::toggled,
459 m_aircraftProxy, &AircraftProxyModel::setRatingFilterEnabled);
460 connect(m_ui->aircraftFilter, &QLineEdit::textChanged,
461 m_aircraftProxy, &QSortFilterProxyModel::setFilterFixedString);
463 connect(m_ui->runButton, SIGNAL(clicked()), this, SLOT(onRun()));
464 connect(m_ui->quitButton, SIGNAL(clicked()), this, SLOT(onQuit()));
466 connect(m_ui->aircraftHistory, &QPushButton::clicked,
467 this, &QtLauncher::onPopupAircraftHistory);
469 connect(m_ui->location, &LocationWidget::descriptionChanged,
470 m_ui->locationDescription, &QLabel::setText);
472 QAction* qa = new QAction(this);
473 qa->setShortcut(QKeySequence("Ctrl+Q"));
474 connect(qa, &QAction::triggered, this, &QtLauncher::onQuit);
477 connect(m_ui->editRatingFilter, &QPushButton::clicked,
478 this, &QtLauncher::onEditRatingsFilter);
480 QIcon historyIcon(":/history-icon");
481 m_ui->aircraftHistory->setIcon(historyIcon);
483 connect(m_ui->timeOfDayCombo, SIGNAL(currentIndexChanged(int)),
484 this, SLOT(updateSettingsSummary()));
485 connect(m_ui->seasonCombo, SIGNAL(currentIndexChanged(int)),
486 this, SLOT(updateSettingsSummary()));
487 connect(m_ui->fetchRealWxrCheckbox, SIGNAL(toggled(bool)),
488 this, SLOT(updateSettingsSummary()));
489 connect(m_ui->rembrandtCheckbox, SIGNAL(toggled(bool)),
490 this, SLOT(updateSettingsSummary()));
491 connect(m_ui->terrasyncCheck, SIGNAL(toggled(bool)),
492 this, SLOT(updateSettingsSummary()));
493 connect(m_ui->startPausedCheck, SIGNAL(toggled(bool)),
494 this, SLOT(updateSettingsSummary()));
495 connect(m_ui->msaaCheckbox, SIGNAL(toggled(bool)),
496 this, SLOT(updateSettingsSummary()));
498 connect(m_ui->rembrandtCheckbox, SIGNAL(toggled(bool)),
499 this, SLOT(onRembrandtToggled(bool)));
500 connect(m_ui->terrasyncCheck, &QCheckBox::toggled,
501 this, &QtLauncher::onToggleTerrasync);
502 updateSettingsSummary();
504 m_aircraftModel = new AircraftItemModel(this, RootRef(globals->packageRoot()));
505 m_aircraftProxy->setSourceModel(m_aircraftModel);
507 m_aircraftProxy->setFilterCaseSensitivity(Qt::CaseInsensitive);
508 m_aircraftProxy->setSortCaseSensitivity(Qt::CaseInsensitive);
509 m_aircraftProxy->setSortRole(Qt::DisplayRole);
510 m_aircraftProxy->setDynamicSortFilter(true);
512 m_ui->aircraftList->setModel(m_aircraftProxy);
513 m_ui->aircraftList->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
514 AircraftItemDelegate* delegate = new AircraftItemDelegate(m_ui->aircraftList);
515 m_ui->aircraftList->setItemDelegate(delegate);
516 m_ui->aircraftList->setSelectionMode(QAbstractItemView::SingleSelection);
517 connect(m_ui->aircraftList, &QListView::clicked,
518 this, &QtLauncher::onAircraftSelected);
519 connect(delegate, &AircraftItemDelegate::variantChanged,
520 this, &QtLauncher::onAircraftSelected);
521 connect(delegate, &AircraftItemDelegate::requestInstall,
522 this, &QtLauncher::onRequestPackageInstall);
523 connect(delegate, &AircraftItemDelegate::cancelDownload,
524 this, &QtLauncher::onCancelDownload);
526 connect(m_aircraftModel, &AircraftItemModel::aircraftInstallCompleted,
527 this, &QtLauncher::onAircraftInstalledCompleted);
528 connect(m_aircraftModel, &AircraftItemModel::aircraftInstallFailed,
529 this, &QtLauncher::onAircraftInstallFailed);
530 connect(m_aircraftModel, &AircraftItemModel::scanCompleted,
531 this, &QtLauncher::updateSelectedAircraft);
532 connect(m_ui->pathsButton, &QPushButton::clicked,
533 this, &QtLauncher::onEditPaths);
538 m_aircraftModel->setPaths(settings.value("aircraft-paths").toStringList());
539 m_aircraftModel->scanDirs();
542 QtLauncher::~QtLauncher()
547 void QtLauncher::setInAppMode()
550 m_ui->tabWidget->removeTab(2);
551 m_ui->runButton->setText(tr("Apply"));
552 m_ui->quitButton->setText(tr("Cancel"));
554 disconnect(m_ui->runButton, SIGNAL(clicked()), this, SLOT(onRun()));
555 connect(m_ui->runButton, SIGNAL(clicked()), this, SLOT(onApply()));
558 void QtLauncher::restoreSettings()
561 m_ui->rembrandtCheckbox->setChecked(settings.value("enable-rembrandt", false).toBool());
562 m_ui->terrasyncCheck->setChecked(settings.value("enable-terrasync", true).toBool());
563 m_ui->fullScreenCheckbox->setChecked(settings.value("start-fullscreen", false).toBool());
564 m_ui->msaaCheckbox->setChecked(settings.value("enable-msaa", false).toBool());
565 m_ui->fetchRealWxrCheckbox->setChecked(settings.value("enable-realwx", true).toBool());
566 m_ui->startPausedCheck->setChecked(settings.value("start-paused", false).toBool());
567 m_ui->timeOfDayCombo->setCurrentIndex(settings.value("timeofday", 0).toInt());
568 m_ui->seasonCombo->setCurrentIndex(settings.value("season", 0).toInt());
570 // full paths to -set.xml files
571 m_recentAircraft = QUrl::fromStringList(settings.value("recent-aircraft").toStringList());
573 if (!m_recentAircraft.empty()) {
574 m_selectedAircraft = m_recentAircraft.front();
576 // select the default C172p
579 updateSelectedAircraft();
580 m_ui->location->restoreSettings();
583 m_ui->ratingsFilterCheck->setChecked(settings.value("ratings-filter", true).toBool());
585 Q_FOREACH(QVariant v, settings.value("min-ratings").toList()) {
586 m_ratingFilters[index++] = v.toInt();
589 m_aircraftProxy->setRatingFilterEnabled(m_ui->ratingsFilterCheck->isChecked());
590 m_aircraftProxy->setRatings(m_ratingFilters);
592 m_ui->commandLineArgs->setPlainText(settings.value("additional-args").toString());
595 void QtLauncher::saveSettings()
598 settings.setValue("enable-rembrandt", m_ui->rembrandtCheckbox->isChecked());
599 settings.setValue("enable-terrasync", m_ui->terrasyncCheck->isChecked());
600 settings.setValue("enable-msaa", m_ui->msaaCheckbox->isChecked());
601 settings.setValue("start-fullscreen", m_ui->fullScreenCheckbox->isChecked());
602 settings.setValue("enable-realwx", m_ui->fetchRealWxrCheckbox->isChecked());
603 settings.setValue("start-paused", m_ui->startPausedCheck->isChecked());
604 settings.setValue("ratings-filter", m_ui->ratingsFilterCheck->isChecked());
605 settings.setValue("recent-aircraft", QUrl::toStringList(m_recentAircraft));
607 settings.setValue("timeofday", m_ui->timeOfDayCombo->currentIndex());
608 settings.setValue("season", m_ui->seasonCombo->currentIndex());
609 settings.setValue("additional-args", m_ui->commandLineArgs->toPlainText());
611 m_ui->location->saveSettings();
612 qDebug() << "saving settings";
615 void QtLauncher::setEnableDisableOptionFromCheckbox(QCheckBox* cbox, QString name) const
617 flightgear::Options* opt = flightgear::Options::sharedInstance();
618 std::string stdName(name.toStdString());
619 if (cbox->isChecked()) {
620 opt->addOption("enable-" + stdName, "");
622 opt->addOption("disable-" + stdName, "");
626 void QtLauncher::onRun()
630 flightgear::Options* opt = flightgear::Options::sharedInstance();
631 setEnableDisableOptionFromCheckbox(m_ui->terrasyncCheck, "terrasync");
632 setEnableDisableOptionFromCheckbox(m_ui->fetchRealWxrCheckbox, "real-weather-fetch");
633 setEnableDisableOptionFromCheckbox(m_ui->rembrandtCheckbox, "rembrandt");
634 setEnableDisableOptionFromCheckbox(m_ui->fullScreenCheckbox, "fullscreen");
635 // setEnableDisableOptionFromCheckbox(m_ui->startPausedCheck, "freeze");
637 bool startPaused = m_ui->startPausedCheck->isChecked() ||
638 m_ui->location->shouldStartPaused();
640 qDebug() << "will start paused";
641 opt->addOption("enable-freeze", "");
644 // MSAA is more complex
645 if (!m_ui->rembrandtCheckbox->isChecked()) {
646 if (m_ui->msaaCheckbox->isChecked()) {
647 globals->get_props()->setIntValue("/sim/rendering/multi-sample-buffers", 1);
648 globals->get_props()->setIntValue("/sim/rendering/multi-samples", 4);
650 globals->get_props()->setIntValue("/sim/rendering/multi-sample-buffers", 0);
655 if (!m_selectedAircraft.isEmpty()) {
656 if (m_selectedAircraft.isLocalFile()) {
657 QFileInfo setFileInfo(m_selectedAircraft.toLocalFile());
658 opt->addOption("aircraft-dir", setFileInfo.dir().absolutePath().toStdString());
659 QString setFile = setFileInfo.fileName();
660 Q_ASSERT(setFile.endsWith("-set.xml"));
661 setFile.truncate(setFile.count() - 8); // drop the '-set.xml' portion
662 opt->addOption("aircraft", setFile.toStdString());
663 } else if (m_selectedAircraft.scheme() == "package") {
664 PackageRef pkg = packageForAircraftURI(m_selectedAircraft);
665 // no need to set aircraft-dir, handled by the corresponding code
667 opt->addOption("aircraft", pkg->qualifiedId());
669 qWarning() << "unsupported aircraft launch URL" << m_selectedAircraft;
672 // manage aircraft history
673 if (m_recentAircraft.contains(m_selectedAircraft))
674 m_recentAircraft.removeOne(m_selectedAircraft);
675 m_recentAircraft.prepend(m_selectedAircraft);
676 if (m_recentAircraft.size() > MAX_RECENT_AIRCRAFT)
677 m_recentAircraft.pop_back();
680 m_ui->location->setLocationOptions();
683 if (m_ui->timeOfDayCombo->currentIndex() != 0) {
684 QString dayval = m_ui->timeOfDayCombo->currentText().toLower();
685 opt->addOption("timeofday", dayval.toStdString());
688 if (m_ui->seasonCombo->currentIndex() != 0) {
689 QString dayval = m_ui->timeOfDayCombo->currentText().toLower();
690 opt->addOption("season", dayval.toStdString());
694 QString downloadDir = settings.value("download-dir").toString();
695 if (!downloadDir.isEmpty()) {
698 int result = QMessageBox::question(this, tr("Create download folder?"),
699 tr("The selected location for downloads does not exist. Create it?"),
700 QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel);
701 if (result == QMessageBox::Cancel) {
705 if (result == QMessageBox::Yes) {
706 d.mkpath(downloadDir);
710 qDebug() << "Download dir is:" << downloadDir;
711 opt->addOption("download-dir", downloadDir.toStdString());
715 Q_FOREACH(QString path, settings.value("scenery-paths").toStringList()) {
716 opt->addOption("fg-scenery", path.toStdString());
720 Q_FOREACH(QString path, settings.value("aircraft-paths").toStringList()) {
721 // can't use fg-aircraft for this, as it is processed before the launcher is run
722 globals->append_aircraft_path(path.toStdString());
725 // additional arguments
726 ArgumentsTokenizer tk;
727 Q_FOREACH(ArgumentsTokenizer::Arg a, tk.tokenize(m_ui->commandLineArgs->toPlainText())) {
728 if (a.arg.startsWith("prop:")) {
729 QString v = a.arg.mid(5) + "=" + a.value;
730 opt->addOption("prop", v.toStdString());
732 opt->addOption(a.arg.toStdString(), a.value.toStdString());
740 void QtLauncher::onApply()
745 if (!m_selectedAircraft.isEmpty()) {
746 std::string aircraftPropValue,
749 if (m_selectedAircraft.isLocalFile()) {
750 QFileInfo setFileInfo(m_selectedAircraft.toLocalFile());
751 QString setFile = setFileInfo.fileName();
752 Q_ASSERT(setFile.endsWith("-set.xml"));
753 setFile.truncate(setFile.count() - 8); // drop the '-set.xml' portion
754 aircraftDir = setFileInfo.dir().absolutePath().toStdString();
755 aircraftPropValue = setFile.toStdString();
756 } else if (m_selectedAircraft.scheme() == "package") {
757 PackageRef pkg = packageForAircraftURI(m_selectedAircraft);
758 // no need to set aircraft-dir, handled by the corresponding code
760 aircraftPropValue = pkg->qualifiedId();
762 qWarning() << "unsupported aircraft launch URL" << m_selectedAircraft;
765 // manage aircraft history
766 if (m_recentAircraft.contains(m_selectedAircraft))
767 m_recentAircraft.removeOne(m_selectedAircraft);
768 m_recentAircraft.prepend(m_selectedAircraft);
769 if (m_recentAircraft.size() > MAX_RECENT_AIRCRAFT)
770 m_recentAircraft.pop_back();
772 globals->get_props()->setStringValue("/sim/aircraft", aircraftPropValue);
773 globals->get_props()->setStringValue("/sim/aircraft-dir", aircraftDir);
780 void QtLauncher::onQuit()
785 void QtLauncher::onToggleTerrasync(bool enabled)
789 QString downloadDir = settings.value("download-dir").toString();
790 if (downloadDir.isEmpty()) {
791 downloadDir = QString::fromStdString(flightgear::defaultDownloadDir());
794 QFileInfo info(downloadDir);
795 if (!info.exists()) {
797 msg.setWindowTitle(tr("Create download folder?"));
798 msg.setText(tr("The download folder '%1' does not exist, create it now? "
799 "Click 'change location' to choose another folder "
800 "to store downloaded files").arg(downloadDir));
801 msg.addButton(QMessageBox::Yes);
802 msg.addButton(QMessageBox::Cancel);
803 msg.addButton(tr("Change location"), QMessageBox::ActionRole);
804 int result = msg.exec();
806 if (result == QMessageBox::Cancel) {
807 m_ui->terrasyncCheck->setChecked(false);
811 if (result == QMessageBox::ActionRole) {
817 d.mkpath(downloadDir);
822 void QtLauncher::onAircraftInstalledCompleted(QModelIndex index)
824 maybeUpdateSelectedAircraft(index);
827 void QtLauncher::onAircraftInstallFailed(QModelIndex index, QString errorMessage)
829 qWarning() << Q_FUNC_INFO << index.data(AircraftURIRole) << errorMessage;
832 msg.setWindowTitle(tr("Aircraft installation failed"));
833 msg.setText(tr("An error occurred installing the aircraft %1: %2").
834 arg(index.data(Qt::DisplayRole).toString()).arg(errorMessage));
835 msg.addButton(QMessageBox::Ok);
838 maybeUpdateSelectedAircraft(index);
841 void QtLauncher::onAircraftSelected(const QModelIndex& index)
843 m_selectedAircraft = index.data(AircraftURIRole).toUrl();
844 updateSelectedAircraft();
847 void QtLauncher::onRequestPackageInstall(const QModelIndex& index)
849 QString pkg = index.data(AircraftPackageIdRole).toString();
850 simgear::pkg::PackageRef pref = globals->packageRoot()->getPackageById(pkg.toStdString());
851 if (pref->isInstalled()) {
852 InstallRef install = pref->existingInstall();
853 if (install && install->hasUpdate()) {
854 globals->packageRoot()->scheduleToUpdate(install);
861 void QtLauncher::onCancelDownload(const QModelIndex& index)
863 QString pkg = index.data(AircraftPackageIdRole).toString();
864 simgear::pkg::PackageRef pref = globals->packageRoot()->getPackageById(pkg.toStdString());
865 simgear::pkg::InstallRef i = pref->existingInstall();
869 void QtLauncher::maybeUpdateSelectedAircraft(QModelIndex index)
871 QUrl u = index.data(AircraftURIRole).toUrl();
872 if (u == m_selectedAircraft) {
873 // potentially enable the run button now!
874 updateSelectedAircraft();
878 void QtLauncher::updateSelectedAircraft()
880 QModelIndex index = m_aircraftModel->indexOfAircraftURI(m_selectedAircraft);
881 if (index.isValid()) {
882 QPixmap pm = index.data(Qt::DecorationRole).value<QPixmap>();
883 m_ui->thumbnail->setPixmap(pm);
884 m_ui->aircraftDescription->setText(index.data(Qt::DisplayRole).toString());
886 int status = index.data(AircraftPackageStatusRole).toInt();
887 bool canRun = (status == PackageInstalled);
888 m_ui->runButton->setEnabled(canRun);
890 LauncherAircraftType aircraftType = Airplane;
891 if (index.data(AircraftIsHelicopterRole).toBool()) {
892 aircraftType = Helicopter;
893 } else if (index.data(AircraftIsSeaplaneRole).toBool()) {
894 aircraftType = Seaplane;
897 m_ui->location->setAircraftType(aircraftType);
899 m_ui->thumbnail->setPixmap(QPixmap());
900 m_ui->aircraftDescription->setText("");
901 m_ui->runButton->setEnabled(false);
905 QModelIndex QtLauncher::proxyIndexForAircraftURI(QUrl uri) const
907 return m_aircraftProxy->mapFromSource(sourceIndexForAircraftURI(uri));
910 QModelIndex QtLauncher::sourceIndexForAircraftURI(QUrl uri) const
912 AircraftItemModel* sourceModel = qobject_cast<AircraftItemModel*>(m_aircraftProxy->sourceModel());
913 Q_ASSERT(sourceModel);
914 return sourceModel->indexOfAircraftURI(uri);
917 void QtLauncher::onPopupAircraftHistory()
919 if (m_recentAircraft.isEmpty()) {
924 Q_FOREACH(QUrl uri, m_recentAircraft) {
925 QModelIndex index = sourceIndexForAircraftURI(uri);
926 if (!index.isValid()) {
930 QAction* act = m.addAction(index.data(Qt::DisplayRole).toString());
934 QPoint popupPos = m_ui->aircraftHistory->mapToGlobal(m_ui->aircraftHistory->rect().bottomLeft());
935 QAction* triggered = m.exec(popupPos);
937 m_selectedAircraft = triggered->data().toUrl();
938 QModelIndex index = proxyIndexForAircraftURI(m_selectedAircraft);
939 m_ui->aircraftList->selectionModel()->setCurrentIndex(index,
940 QItemSelectionModel::ClearAndSelect);
941 m_ui->aircraftFilter->clear();
942 updateSelectedAircraft();
946 void QtLauncher::onEditRatingsFilter()
948 EditRatingsFilterDialog dialog(this);
949 dialog.setRatings(m_ratingFilters);
952 if (dialog.result() == QDialog::Accepted) {
954 for (int i=0; i<4; ++i) {
955 m_ratingFilters[i] = dialog.getRating(i);
956 vl.append(m_ratingFilters[i]);
958 m_aircraftProxy->setRatings(m_ratingFilters);
961 settings.setValue("min-ratings", vl);
965 void QtLauncher::updateSettingsSummary()
968 if (m_ui->timeOfDayCombo->currentIndex() > 0) {
969 summary.append(QString(m_ui->timeOfDayCombo->currentText().toLower()));
972 if (m_ui->seasonCombo->currentIndex() > 0) {
973 summary.append(QString(m_ui->seasonCombo->currentText().toLower()));
976 if (m_ui->rembrandtCheckbox->isChecked()) {
977 summary.append("Rembrandt enabled");
978 } else if (m_ui->msaaCheckbox->isChecked()) {
979 summary.append("anti-aliasing");
982 if (m_ui->fetchRealWxrCheckbox->isChecked()) {
983 summary.append("live weather");
986 if (m_ui->terrasyncCheck->isChecked()) {
987 summary.append("automatic scenery downloads");
990 if (m_ui->startPausedCheck->isChecked()) {
991 summary.append("paused");
994 QString s = summary.join(", ");
995 s[0] = s[0].toUpper();
996 m_ui->settingsDescription->setText(s);
999 void QtLauncher::onRembrandtToggled(bool b)
1001 // Rembrandt and multi-sample are exclusive
1002 m_ui->msaaCheckbox->setEnabled(!b);
1005 void QtLauncher::onSubsytemIdleTimeout()
1007 globals->get_subsystem_mgr()->update(0.0);
1010 void QtLauncher::onEditPaths()
1012 PathsDialog dlg(this, globals->packageRoot());
1014 if (dlg.result() == QDialog::Accepted) {
1015 // re-scan the aircraft list
1017 m_aircraftModel->setPaths(settings.value("aircraft-paths").toStringList());
1018 m_aircraftModel->scanDirs();
1022 simgear::pkg::PackageRef QtLauncher::packageForAircraftURI(QUrl uri) const
1024 if (uri.scheme() != "package") {
1025 qWarning() << "invalid URL scheme:" << uri;
1026 return simgear::pkg::PackageRef();
1029 QString ident = uri.path();
1030 return globals->packageRoot()->getPackageById(ident.toStdString());
1034 #include "QtLauncher.moc"