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::PolyLine::bulkAddToSpatialIndex(lines);
363 void loadNaturalEarthData()
368 loadNaturalEarthFile("ne_10m_coastline.shp", flightgear::PolyLine::COASTLINE, false);
369 loadNaturalEarthFile("ne_10m_rivers_lake_centerlines.shp", flightgear::PolyLine::RIVER, false);
370 loadNaturalEarthFile("ne_10m_lakes.shp", flightgear::PolyLine::LAKE, true);
372 qDebug() << "load basic data took" << st.elapsedMSec();
376 loadNaturalEarthFile("ne_10m_urban_areas.shp", flightgear::PolyLine::URBAN, true);
378 qDebug() << "loading urban areas took:" << st.elapsedMSec();
381 bool runLauncherDialog()
383 // startup the nav-cache now. This pre-empts normal startup of
384 // the cache, but no harm done. (Providing scenery paths are consistent)
390 // startup the HTTP system now since packages needs it
391 FGHTTPClient* http = new FGHTTPClient;
392 globals->add_subsystem("http", http);
393 // we guard against re-init in the global phase; bind and postinit
394 // will happen as normal
397 loadNaturalEarthData();
399 // setup scenery paths now, especially TerraSync path for airport
400 // parking locations (after they're downloaded)
404 if (dlg.result() != QDialog::Accepted) {
411 bool runInAppLauncherDialog()
416 if (dlg.result() != QDialog::Accepted) {
423 } // of namespace flightgear
425 QtLauncher::QtLauncher() :
428 m_subsystemIdleTimer(NULL),
431 m_ui.reset(new Ui::Launcher);
434 #if QT_VERSION >= 0x050300
435 // don't require Qt 5.3
436 m_ui->commandLineArgs->setPlaceholderText("--option=value --prop:/sim/name=value");
439 #if QT_VERSION >= 0x050200
440 m_ui->aircraftFilter->setClearButtonEnabled(true);
443 for (int i=0; i<4; ++i) {
444 m_ratingFilters[i] = 3;
447 m_subsystemIdleTimer = new QTimer(this);
448 m_subsystemIdleTimer->setInterval(0);
449 connect(m_subsystemIdleTimer, &QTimer::timeout,
450 this, &QtLauncher::onSubsytemIdleTimeout);
451 m_subsystemIdleTimer->start();
453 // create and configure the proxy model
454 m_aircraftProxy = new AircraftProxyModel(this);
455 connect(m_ui->ratingsFilterCheck, &QAbstractButton::toggled,
456 m_aircraftProxy, &AircraftProxyModel::setRatingFilterEnabled);
457 connect(m_ui->aircraftFilter, &QLineEdit::textChanged,
458 m_aircraftProxy, &QSortFilterProxyModel::setFilterFixedString);
460 connect(m_ui->runButton, SIGNAL(clicked()), this, SLOT(onRun()));
461 connect(m_ui->quitButton, SIGNAL(clicked()), this, SLOT(onQuit()));
463 connect(m_ui->aircraftHistory, &QPushButton::clicked,
464 this, &QtLauncher::onPopupAircraftHistory);
466 connect(m_ui->location, &LocationWidget::descriptionChanged,
467 m_ui->locationDescription, &QLabel::setText);
469 QAction* qa = new QAction(this);
470 qa->setShortcut(QKeySequence("Ctrl+Q"));
471 connect(qa, &QAction::triggered, this, &QtLauncher::onQuit);
474 connect(m_ui->editRatingFilter, &QPushButton::clicked,
475 this, &QtLauncher::onEditRatingsFilter);
477 QIcon historyIcon(":/history-icon");
478 m_ui->aircraftHistory->setIcon(historyIcon);
480 connect(m_ui->timeOfDayCombo, SIGNAL(currentIndexChanged(int)),
481 this, SLOT(updateSettingsSummary()));
482 connect(m_ui->seasonCombo, SIGNAL(currentIndexChanged(int)),
483 this, SLOT(updateSettingsSummary()));
484 connect(m_ui->fetchRealWxrCheckbox, SIGNAL(toggled(bool)),
485 this, SLOT(updateSettingsSummary()));
486 connect(m_ui->rembrandtCheckbox, SIGNAL(toggled(bool)),
487 this, SLOT(updateSettingsSummary()));
488 connect(m_ui->terrasyncCheck, SIGNAL(toggled(bool)),
489 this, SLOT(updateSettingsSummary()));
490 connect(m_ui->startPausedCheck, SIGNAL(toggled(bool)),
491 this, SLOT(updateSettingsSummary()));
492 connect(m_ui->msaaCheckbox, SIGNAL(toggled(bool)),
493 this, SLOT(updateSettingsSummary()));
495 connect(m_ui->rembrandtCheckbox, SIGNAL(toggled(bool)),
496 this, SLOT(onRembrandtToggled(bool)));
497 connect(m_ui->terrasyncCheck, &QCheckBox::toggled,
498 this, &QtLauncher::onToggleTerrasync);
499 updateSettingsSummary();
501 m_aircraftModel = new AircraftItemModel(this, RootRef(globals->packageRoot()));
502 m_aircraftProxy->setSourceModel(m_aircraftModel);
504 m_aircraftProxy->setFilterCaseSensitivity(Qt::CaseInsensitive);
505 m_aircraftProxy->setSortCaseSensitivity(Qt::CaseInsensitive);
506 m_aircraftProxy->setSortRole(Qt::DisplayRole);
507 m_aircraftProxy->setDynamicSortFilter(true);
509 m_ui->aircraftList->setModel(m_aircraftProxy);
510 m_ui->aircraftList->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
511 AircraftItemDelegate* delegate = new AircraftItemDelegate(m_ui->aircraftList);
512 m_ui->aircraftList->setItemDelegate(delegate);
513 m_ui->aircraftList->setSelectionMode(QAbstractItemView::SingleSelection);
514 connect(m_ui->aircraftList, &QListView::clicked,
515 this, &QtLauncher::onAircraftSelected);
516 connect(delegate, &AircraftItemDelegate::variantChanged,
517 this, &QtLauncher::onAircraftSelected);
518 connect(delegate, &AircraftItemDelegate::requestInstall,
519 this, &QtLauncher::onRequestPackageInstall);
520 connect(delegate, &AircraftItemDelegate::cancelDownload,
521 this, &QtLauncher::onCancelDownload);
523 connect(m_aircraftModel, &AircraftItemModel::aircraftInstallCompleted,
524 this, &QtLauncher::onAircraftInstalledCompleted);
525 connect(m_aircraftModel, &AircraftItemModel::aircraftInstallFailed,
526 this, &QtLauncher::onAircraftInstallFailed);
527 connect(m_aircraftModel, &AircraftItemModel::scanCompleted,
528 this, &QtLauncher::updateSelectedAircraft);
529 connect(m_ui->pathsButton, &QPushButton::clicked,
530 this, &QtLauncher::onEditPaths);
535 m_aircraftModel->setPaths(settings.value("aircraft-paths").toStringList());
536 m_aircraftModel->scanDirs();
539 QtLauncher::~QtLauncher()
544 void QtLauncher::setInAppMode()
547 m_ui->tabWidget->removeTab(2);
548 m_ui->runButton->setText(tr("Apply"));
549 m_ui->quitButton->setText(tr("Cancel"));
551 disconnect(m_ui->runButton, SIGNAL(clicked()), this, SLOT(onRun()));
552 connect(m_ui->runButton, SIGNAL(clicked()), this, SLOT(onApply()));
555 void QtLauncher::restoreSettings()
558 m_ui->rembrandtCheckbox->setChecked(settings.value("enable-rembrandt", false).toBool());
559 m_ui->terrasyncCheck->setChecked(settings.value("enable-terrasync", true).toBool());
560 m_ui->fullScreenCheckbox->setChecked(settings.value("start-fullscreen", false).toBool());
561 m_ui->msaaCheckbox->setChecked(settings.value("enable-msaa", false).toBool());
562 m_ui->fetchRealWxrCheckbox->setChecked(settings.value("enable-realwx", true).toBool());
563 m_ui->startPausedCheck->setChecked(settings.value("start-paused", false).toBool());
564 m_ui->timeOfDayCombo->setCurrentIndex(settings.value("timeofday", 0).toInt());
565 m_ui->seasonCombo->setCurrentIndex(settings.value("season", 0).toInt());
567 // full paths to -set.xml files
568 m_recentAircraft = QUrl::fromStringList(settings.value("recent-aircraft").toStringList());
570 if (!m_recentAircraft.empty()) {
571 m_selectedAircraft = m_recentAircraft.front();
573 // select the default C172p
576 updateSelectedAircraft();
577 m_ui->location->restoreSettings();
580 m_ui->ratingsFilterCheck->setChecked(settings.value("ratings-filter", true).toBool());
582 Q_FOREACH(QVariant v, settings.value("min-ratings").toList()) {
583 m_ratingFilters[index++] = v.toInt();
586 m_aircraftProxy->setRatingFilterEnabled(m_ui->ratingsFilterCheck->isChecked());
587 m_aircraftProxy->setRatings(m_ratingFilters);
589 m_ui->commandLineArgs->setPlainText(settings.value("additional-args").toString());
592 void QtLauncher::saveSettings()
595 settings.setValue("enable-rembrandt", m_ui->rembrandtCheckbox->isChecked());
596 settings.setValue("enable-terrasync", m_ui->terrasyncCheck->isChecked());
597 settings.setValue("enable-msaa", m_ui->msaaCheckbox->isChecked());
598 settings.setValue("start-fullscreen", m_ui->fullScreenCheckbox->isChecked());
599 settings.setValue("enable-realwx", m_ui->fetchRealWxrCheckbox->isChecked());
600 settings.setValue("start-paused", m_ui->startPausedCheck->isChecked());
601 settings.setValue("ratings-filter", m_ui->ratingsFilterCheck->isChecked());
602 settings.setValue("recent-aircraft", QUrl::toStringList(m_recentAircraft));
604 settings.setValue("timeofday", m_ui->timeOfDayCombo->currentIndex());
605 settings.setValue("season", m_ui->seasonCombo->currentIndex());
606 settings.setValue("additional-args", m_ui->commandLineArgs->toPlainText());
608 m_ui->location->saveSettings();
611 void QtLauncher::setEnableDisableOptionFromCheckbox(QCheckBox* cbox, QString name) const
613 flightgear::Options* opt = flightgear::Options::sharedInstance();
614 std::string stdName(name.toStdString());
615 if (cbox->isChecked()) {
616 opt->addOption("enable-" + stdName, "");
618 opt->addOption("disable-" + stdName, "");
622 void QtLauncher::onRun()
626 flightgear::Options* opt = flightgear::Options::sharedInstance();
627 setEnableDisableOptionFromCheckbox(m_ui->terrasyncCheck, "terrasync");
628 setEnableDisableOptionFromCheckbox(m_ui->fetchRealWxrCheckbox, "real-weather-fetch");
629 setEnableDisableOptionFromCheckbox(m_ui->rembrandtCheckbox, "rembrandt");
630 setEnableDisableOptionFromCheckbox(m_ui->fullScreenCheckbox, "fullscreen");
631 // setEnableDisableOptionFromCheckbox(m_ui->startPausedCheck, "freeze");
633 bool startPaused = m_ui->startPausedCheck->isChecked() ||
634 m_ui->location->shouldStartPaused();
636 opt->addOption("enable-freeze", "");
639 // MSAA is more complex
640 if (!m_ui->rembrandtCheckbox->isChecked()) {
641 if (m_ui->msaaCheckbox->isChecked()) {
642 globals->get_props()->setIntValue("/sim/rendering/multi-sample-buffers", 1);
643 globals->get_props()->setIntValue("/sim/rendering/multi-samples", 4);
645 globals->get_props()->setIntValue("/sim/rendering/multi-sample-buffers", 0);
650 if (!m_selectedAircraft.isEmpty()) {
651 if (m_selectedAircraft.isLocalFile()) {
652 QFileInfo setFileInfo(m_selectedAircraft.toLocalFile());
653 opt->addOption("aircraft-dir", setFileInfo.dir().absolutePath().toStdString());
654 QString setFile = setFileInfo.fileName();
655 Q_ASSERT(setFile.endsWith("-set.xml"));
656 setFile.truncate(setFile.count() - 8); // drop the '-set.xml' portion
657 opt->addOption("aircraft", setFile.toStdString());
658 } else if (m_selectedAircraft.scheme() == "package") {
659 PackageRef pkg = packageForAircraftURI(m_selectedAircraft);
660 // no need to set aircraft-dir, handled by the corresponding code
662 opt->addOption("aircraft", pkg->qualifiedId());
664 qWarning() << "unsupported aircraft launch URL" << m_selectedAircraft;
667 // manage aircraft history
668 if (m_recentAircraft.contains(m_selectedAircraft))
669 m_recentAircraft.removeOne(m_selectedAircraft);
670 m_recentAircraft.prepend(m_selectedAircraft);
671 if (m_recentAircraft.size() > MAX_RECENT_AIRCRAFT)
672 m_recentAircraft.pop_back();
675 m_ui->location->setLocationOptions();
678 if (m_ui->timeOfDayCombo->currentIndex() != 0) {
679 QString dayval = m_ui->timeOfDayCombo->currentText().toLower();
680 opt->addOption("timeofday", dayval.toStdString());
683 if (m_ui->seasonCombo->currentIndex() != 0) {
684 QString dayval = m_ui->timeOfDayCombo->currentText().toLower();
685 opt->addOption("season", dayval.toStdString());
689 QString downloadDir = settings.value("download-dir").toString();
690 if (!downloadDir.isEmpty()) {
693 int result = QMessageBox::question(this, tr("Create download folder?"),
694 tr("The selected location for downloads does not exist. Create it?"),
695 QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel);
696 if (result == QMessageBox::Cancel) {
700 if (result == QMessageBox::Yes) {
701 d.mkpath(downloadDir);
705 opt->addOption("download-dir", downloadDir.toStdString());
709 Q_FOREACH(QString path, settings.value("scenery-paths").toStringList()) {
710 opt->addOption("fg-scenery", path.toStdString());
714 Q_FOREACH(QString path, settings.value("aircraft-paths").toStringList()) {
715 // can't use fg-aircraft for this, as it is processed before the launcher is run
716 globals->append_aircraft_path(path.toStdString());
719 // additional arguments
720 ArgumentsTokenizer tk;
721 Q_FOREACH(ArgumentsTokenizer::Arg a, tk.tokenize(m_ui->commandLineArgs->toPlainText())) {
722 if (a.arg.startsWith("prop:")) {
723 QString v = a.arg.mid(5) + "=" + a.value;
724 opt->addOption("prop", v.toStdString());
726 opt->addOption(a.arg.toStdString(), a.value.toStdString());
734 void QtLauncher::onApply()
739 if (!m_selectedAircraft.isEmpty()) {
740 std::string aircraftPropValue,
743 if (m_selectedAircraft.isLocalFile()) {
744 QFileInfo setFileInfo(m_selectedAircraft.toLocalFile());
745 QString setFile = setFileInfo.fileName();
746 Q_ASSERT(setFile.endsWith("-set.xml"));
747 setFile.truncate(setFile.count() - 8); // drop the '-set.xml' portion
748 aircraftDir = setFileInfo.dir().absolutePath().toStdString();
749 aircraftPropValue = setFile.toStdString();
750 } else if (m_selectedAircraft.scheme() == "package") {
751 PackageRef pkg = packageForAircraftURI(m_selectedAircraft);
752 // no need to set aircraft-dir, handled by the corresponding code
754 aircraftPropValue = pkg->qualifiedId();
756 qWarning() << "unsupported aircraft launch URL" << m_selectedAircraft;
759 // manage aircraft history
760 if (m_recentAircraft.contains(m_selectedAircraft))
761 m_recentAircraft.removeOne(m_selectedAircraft);
762 m_recentAircraft.prepend(m_selectedAircraft);
763 if (m_recentAircraft.size() > MAX_RECENT_AIRCRAFT)
764 m_recentAircraft.pop_back();
766 globals->get_props()->setStringValue("/sim/aircraft", aircraftPropValue);
767 globals->get_props()->setStringValue("/sim/aircraft-dir", aircraftDir);
774 void QtLauncher::onQuit()
779 void QtLauncher::onToggleTerrasync(bool enabled)
783 QString downloadDir = settings.value("download-dir").toString();
784 if (downloadDir.isEmpty()) {
785 downloadDir = QString::fromStdString(flightgear::defaultDownloadDir());
788 QFileInfo info(downloadDir);
789 if (!info.exists()) {
791 msg.setWindowTitle(tr("Create download folder?"));
792 msg.setText(tr("The download folder '%1' does not exist, create it now? "
793 "Click 'change location' to choose another folder "
794 "to store downloaded files").arg(downloadDir));
795 msg.addButton(QMessageBox::Yes);
796 msg.addButton(QMessageBox::Cancel);
797 msg.addButton(tr("Change location"), QMessageBox::ActionRole);
798 int result = msg.exec();
800 if (result == QMessageBox::Cancel) {
801 m_ui->terrasyncCheck->setChecked(false);
805 if (result == QMessageBox::ActionRole) {
811 d.mkpath(downloadDir);
816 void QtLauncher::onAircraftInstalledCompleted(QModelIndex index)
818 maybeUpdateSelectedAircraft(index);
821 void QtLauncher::onAircraftInstallFailed(QModelIndex index, QString errorMessage)
823 qWarning() << Q_FUNC_INFO << index.data(AircraftURIRole) << errorMessage;
826 msg.setWindowTitle(tr("Aircraft installation failed"));
827 msg.setText(tr("An error occurred installing the aircraft %1: %2").
828 arg(index.data(Qt::DisplayRole).toString()).arg(errorMessage));
829 msg.addButton(QMessageBox::Ok);
832 maybeUpdateSelectedAircraft(index);
835 void QtLauncher::onAircraftSelected(const QModelIndex& index)
837 m_selectedAircraft = index.data(AircraftURIRole).toUrl();
838 updateSelectedAircraft();
841 void QtLauncher::onRequestPackageInstall(const QModelIndex& index)
843 QString pkg = index.data(AircraftPackageIdRole).toString();
844 simgear::pkg::PackageRef pref = globals->packageRoot()->getPackageById(pkg.toStdString());
845 if (pref->isInstalled()) {
846 InstallRef install = pref->existingInstall();
847 if (install && install->hasUpdate()) {
848 globals->packageRoot()->scheduleToUpdate(install);
855 void QtLauncher::onCancelDownload(const QModelIndex& index)
857 QString pkg = index.data(AircraftPackageIdRole).toString();
858 simgear::pkg::PackageRef pref = globals->packageRoot()->getPackageById(pkg.toStdString());
859 simgear::pkg::InstallRef i = pref->existingInstall();
863 void QtLauncher::maybeUpdateSelectedAircraft(QModelIndex index)
865 QUrl u = index.data(AircraftURIRole).toUrl();
866 if (u == m_selectedAircraft) {
867 // potentially enable the run button now!
868 updateSelectedAircraft();
872 void QtLauncher::updateSelectedAircraft()
874 QModelIndex index = m_aircraftModel->indexOfAircraftURI(m_selectedAircraft);
875 if (index.isValid()) {
876 QPixmap pm = index.data(Qt::DecorationRole).value<QPixmap>();
877 m_ui->thumbnail->setPixmap(pm);
878 m_ui->aircraftDescription->setText(index.data(Qt::DisplayRole).toString());
880 int status = index.data(AircraftPackageStatusRole).toInt();
881 bool canRun = (status == PackageInstalled);
882 m_ui->runButton->setEnabled(canRun);
884 LauncherAircraftType aircraftType = Airplane;
885 if (index.data(AircraftIsHelicopterRole).toBool()) {
886 aircraftType = Helicopter;
887 } else if (index.data(AircraftIsSeaplaneRole).toBool()) {
888 aircraftType = Seaplane;
891 m_ui->location->setAircraftType(aircraftType);
893 m_ui->thumbnail->setPixmap(QPixmap());
894 m_ui->aircraftDescription->setText("");
895 m_ui->runButton->setEnabled(false);
899 QModelIndex QtLauncher::proxyIndexForAircraftURI(QUrl uri) const
901 return m_aircraftProxy->mapFromSource(sourceIndexForAircraftURI(uri));
904 QModelIndex QtLauncher::sourceIndexForAircraftURI(QUrl uri) const
906 AircraftItemModel* sourceModel = qobject_cast<AircraftItemModel*>(m_aircraftProxy->sourceModel());
907 Q_ASSERT(sourceModel);
908 return sourceModel->indexOfAircraftURI(uri);
911 void QtLauncher::onPopupAircraftHistory()
913 if (m_recentAircraft.isEmpty()) {
918 Q_FOREACH(QUrl uri, m_recentAircraft) {
919 QModelIndex index = sourceIndexForAircraftURI(uri);
920 if (!index.isValid()) {
924 QAction* act = m.addAction(index.data(Qt::DisplayRole).toString());
928 QPoint popupPos = m_ui->aircraftHistory->mapToGlobal(m_ui->aircraftHistory->rect().bottomLeft());
929 QAction* triggered = m.exec(popupPos);
931 m_selectedAircraft = triggered->data().toUrl();
932 QModelIndex index = proxyIndexForAircraftURI(m_selectedAircraft);
933 m_ui->aircraftList->selectionModel()->setCurrentIndex(index,
934 QItemSelectionModel::ClearAndSelect);
935 m_ui->aircraftFilter->clear();
936 updateSelectedAircraft();
940 void QtLauncher::onEditRatingsFilter()
942 EditRatingsFilterDialog dialog(this);
943 dialog.setRatings(m_ratingFilters);
946 if (dialog.result() == QDialog::Accepted) {
948 for (int i=0; i<4; ++i) {
949 m_ratingFilters[i] = dialog.getRating(i);
950 vl.append(m_ratingFilters[i]);
952 m_aircraftProxy->setRatings(m_ratingFilters);
955 settings.setValue("min-ratings", vl);
959 void QtLauncher::updateSettingsSummary()
962 if (m_ui->timeOfDayCombo->currentIndex() > 0) {
963 summary.append(QString(m_ui->timeOfDayCombo->currentText().toLower()));
966 if (m_ui->seasonCombo->currentIndex() > 0) {
967 summary.append(QString(m_ui->seasonCombo->currentText().toLower()));
970 if (m_ui->rembrandtCheckbox->isChecked()) {
971 summary.append("Rembrandt enabled");
972 } else if (m_ui->msaaCheckbox->isChecked()) {
973 summary.append("anti-aliasing");
976 if (m_ui->fetchRealWxrCheckbox->isChecked()) {
977 summary.append("live weather");
980 if (m_ui->terrasyncCheck->isChecked()) {
981 summary.append("automatic scenery downloads");
984 if (m_ui->startPausedCheck->isChecked()) {
985 summary.append("paused");
988 QString s = summary.join(", ");
989 s[0] = s[0].toUpper();
990 m_ui->settingsDescription->setText(s);
993 void QtLauncher::onRembrandtToggled(bool b)
995 // Rembrandt and multi-sample are exclusive
996 m_ui->msaaCheckbox->setEnabled(!b);
999 void QtLauncher::onSubsytemIdleTimeout()
1001 globals->get_subsystem_mgr()->update(0.0);
1004 void QtLauncher::onEditPaths()
1006 PathsDialog dlg(this, globals->packageRoot());
1008 if (dlg.result() == QDialog::Accepted) {
1009 // re-scan the aircraft list
1011 m_aircraftModel->setPaths(settings.value("aircraft-paths").toStringList());
1012 m_aircraftModel->scanDirs();
1016 simgear::pkg::PackageRef QtLauncher::packageForAircraftURI(QUrl uri) const
1018 if (uri.scheme() != "package") {
1019 qWarning() << "invalid URL scheme:" << uri;
1020 return simgear::pkg::PackageRef();
1023 QString ident = uri.path();
1024 return globals->packageRoot()->getPackageById(ident.toStdString());
1028 #include "QtLauncher.moc"