diff --git a/src/GUI/CMakeLists.txt b/src/GUI/CMakeLists.txt index b93440a4c..534c4682f 100644 --- a/src/GUI/CMakeLists.txt +++ b/src/GUI/CMakeLists.txt @@ -140,6 +140,10 @@ if (HAVE_QT) MPServersModel.h PathUrlHelper.cxx PathUrlHelper.hxx + RecentAircraftModel.cxx + RecentAircraftModel.hxx + RecentLocationsModel.cxx + RecentLocationsModel.hxx ${uic_sources} ${qrc_sources} ${qml_sources}) diff --git a/src/GUI/DefaultAircraftLocator.cxx b/src/GUI/DefaultAircraftLocator.cxx index 3986339b0..b14021a9d 100644 --- a/src/GUI/DefaultAircraftLocator.cxx +++ b/src/GUI/DefaultAircraftLocator.cxx @@ -34,6 +34,20 @@ std::string defaultAirportICAO() return airportCode; } +string_list defaultSplashScreenPaths() +{ + string_list result; + SGPath tpath = globals->get_fg_root() / "Textures"; + simgear::Dir d(tpath); + for (auto c : d.children(simgear::Dir::TYPE_FILE, ".png")) { + if (c.file_base().find("Splash") == 0) { + result.push_back(c.utf8Str()); + } + } + + return result; +} + DefaultAircraftLocator::DefaultAircraftLocator() { SGPropertyNode_ptr root = loadXMLDefaults(); diff --git a/src/GUI/DefaultAircraftLocator.hxx b/src/GUI/DefaultAircraftLocator.hxx index 0cc350fe2..b68373d29 100644 --- a/src/GUI/DefaultAircraftLocator.hxx +++ b/src/GUI/DefaultAircraftLocator.hxx @@ -13,6 +13,8 @@ namespace flightgear std::string defaultAirportICAO(); +string_list defaultSplashScreenPaths(); + /** * we don't want to rely on the main AircraftModel threaded scan, to find the * default aircraft, so we do a synchronous scan here, on the assumption that diff --git a/src/GUI/Launcher.ui b/src/GUI/Launcher.ui index 9d80698e4..b909f4ecb 100644 --- a/src/GUI/Launcher.ui +++ b/src/GUI/Launcher.ui @@ -250,231 +250,31 @@ 0 - - - - - - - 48 - 75 - true - - - - FlightGear 2017.1.0 - - - - - - - false - - - - - - - - 16 - - - - State: - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - - - 16 - - - - aircraft - - - true - - - - - - - - 16 - - - - Settings: - - - Qt::AlignRight|Qt::AlignTop|Qt::AlignTrailing - - - - - - - - 16 - - - - Aircraft: - - - Qt::AlignRight|Qt::AlignTop|Qt::AlignTrailing - - - - - - - - 16 - - - - location - - - true - - - - - - - - 0 - 0 - - - - - 171 - 128 - - - - TextLabel - - - - - - - Qt::Vertical - - - - 20 - 294 - - - - - - - - - 11 - - - - TextLabel - - - true - - - - - - - - - - <html><head/><body><p>©2017 FlightGear contributors. Licensed under the GNU Public License. See <a href="http://www.flightgear.org"><span style=" text-decoration: underline; color:#0000ff;">here</span></a> for more information</p></body></html> - - - true - - - true - - - - - - - - 16 - - - - Location: - - - Qt::AlignRight|Qt::AlignTop|Qt::AlignTrailing - - - - - - - - - - :/app-icon-large - - - - - - - false - - - - - - - - 16 - - - - settings - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop - - - true - - - - - - - - 11 - - - - TextLabel - - + + + + + 4 + + + 4 + + + 4 + + + 4 + + + 4 + + + + + diff --git a/src/GUI/LauncherMainWindow.cxx b/src/GUI/LauncherMainWindow.cxx index 24097b3d7..733b36cb3 100644 --- a/src/GUI/LauncherMainWindow.cxx +++ b/src/GUI/LauncherMainWindow.cxx @@ -9,6 +9,9 @@ #include #include #include +#include +#include +#include #include #include @@ -49,11 +52,11 @@ #include "LauncherArgumentTokenizer.hxx" #include "PathUrlHelper.hxx" #include "PopupWindowTracker.hxx" +#include "RecentAircraftModel.hxx" +#include "RecentLocationsModel.hxx" #include "ui_Launcher.h" -const int MAX_RECENT_AIRCRAFT = 20; - using namespace simgear::pkg; extern void restartTheApp(QStringList fgArgs); @@ -75,14 +78,11 @@ QQmlPrivate::AutoParentResult launcher_autoParent(QObject* thing, QObject* paren LauncherMainWindow::LauncherMainWindow() : QMainWindow(), - m_ui(NULL), m_subsystemIdleTimer(NULL) { m_ui.reset(new Ui::Launcher); m_ui->setupUi(this); - m_ui->appTitleLabel->setText(tr("FlightGear %1").arg(FLIGHTGEAR_VERSION)); - QMenuBar* mb = menuBar(); #if !defined(Q_OS_MAC) @@ -108,15 +108,7 @@ LauncherMainWindow::LauncherMainWindow() : m_serversModel = new MPServersModel(this); - // keep the description QLabel in sync as the current item changes - connect(m_ui->stateCombo, - static_cast(&QComboBox::currentIndexChanged), - [this](int) - { - auto v = m_ui->stateCombo->currentData(QmlAircraftInfo::StateDescriptionRole); - m_ui->stateDescription->setText(v.toString()); - m_ui->stateDescription->setVisible(!v.toString().isEmpty()); - }); + m_locationHistory = new RecentLocationsModel(this); m_selectedAircraftInfo = new QmlAircraftInfo(this); initQML(); @@ -135,23 +127,14 @@ LauncherMainWindow::LauncherMainWindow() : connect(m_ui->settingsButton, &QAbstractButton::clicked, this, &LauncherMainWindow::onClickToolboxButton); connect(m_ui->addOnsButton, &QAbstractButton::clicked, this, &LauncherMainWindow::onClickToolboxButton); - connect(m_ui->aircraftHistory, &QPushButton::clicked, - this, &LauncherMainWindow::onPopupAircraftHistory); - connect(m_ui->locationHistory, &QPushButton::clicked, - this, &LauncherMainWindow::onPopupLocationHistory); - connect(m_ui->location, &LocationWidget::descriptionChanged, - m_ui->locationDescription, &QLabel::setText); + this, &LauncherMainWindow::summaryChanged); QAction* qa = new QAction(this); qa->setShortcut(QKeySequence("Ctrl+Q")); connect(qa, &QAction::triggered, this, &LauncherMainWindow::onQuit); addAction(qa); - QIcon historyIcon(":/history-icon"); - m_ui->aircraftHistory->setIcon(historyIcon); - m_ui->locationHistory->setIcon(historyIcon); - m_aircraftModel = new AircraftItemModel(this); m_installedAircraftModel = new AircraftProxyModel(this, m_aircraftModel); m_installedAircraftModel->setInstalledFilterEnabled(true); @@ -161,40 +144,13 @@ LauncherMainWindow::LauncherMainWindow() : m_aircraftSearchModel = new AircraftProxyModel(this, m_aircraftModel); - m_ui->aircraftList->setResizeMode(QQuickWidget::SizeRootObjectToView); - - m_ui->aircraftList->engine()->addImportPath("qrc:///"); - m_ui->aircraftList->engine()->rootContext()->setContextProperty("_launcher", this); - m_ui->aircraftList->engine()->setObjectOwnership(this, QQmlEngine::CppOwnership); - - m_ui->aircraftList->setSource(QUrl("qrc:///qml/AircraftList.qml")); - - m_ui->settings->engine()->addImportPath("qrc:///"); - m_ui->settings->engine()->rootContext()->setContextProperty("_launcher", this); - m_ui->settings->engine()->rootContext()->setContextProperty("_mpServers", m_serversModel); - - m_ui->settings->engine()->setObjectOwnership(this, QQmlEngine::CppOwnership); - - m_ui->settings->setResizeMode(QQuickWidget::SizeRootObjectToView); - m_ui->settings->setSource(QUrl("qrc:///qml/Settings.qml")); - - m_ui->environmentPage->engine()->addImportPath("qrc:///"); - m_ui->environmentPage->engine()->rootContext()->setContextProperty("_launcher", this); - auto weatherScenariosModel = new flightgear::WeatherScenariosModel(this); - m_ui->environmentPage->engine()->rootContext()->setContextProperty("_weatherScenarios", weatherScenariosModel); - - m_ui->environmentPage->engine()->setObjectOwnership(this, QQmlEngine::CppOwnership); - m_ui->environmentPage->engine()->rootContext()->setContextProperty("_config", m_config); - - m_ui->environmentPage->setResizeMode(QQuickWidget::SizeRootObjectToView); - m_ui->environmentPage->setSource(QUrl("qrc:///qml/Environment.qml")); + m_aircraftHistory = new RecentAircraftModel(m_aircraftModel, this); connect(m_aircraftModel, &AircraftItemModel::aircraftInstallCompleted, this, &LauncherMainWindow::onAircraftInstalledCompleted); connect(m_aircraftModel, &AircraftItemModel::aircraftInstallFailed, this, &LauncherMainWindow::onAircraftInstallFailed); - connect(LocalAircraftCache::instance(), &LocalAircraftCache::scanCompleted, this, &LauncherMainWindow::updateSelectedAircraft); @@ -219,8 +175,50 @@ LauncherMainWindow::LauncherMainWindow() : m_ui->stack->addWidget(m_viewCommandLinePage); restoreSettings(); - updateSettingsSummary(); + + emit summaryChanged(); emit showNoOfficialHangarChanged(); + + ///////////// + // aircraft + m_ui->aircraftList->setResizeMode(QQuickWidget::SizeRootObjectToView); + + m_ui->aircraftList->engine()->addImportPath("qrc:///"); + m_ui->aircraftList->engine()->rootContext()->setContextProperty("_launcher", this); + m_ui->aircraftList->engine()->setObjectOwnership(this, QQmlEngine::CppOwnership); + + m_ui->aircraftList->setSource(QUrl("qrc:///qml/AircraftList.qml")); + + // settings + m_ui->settings->engine()->addImportPath("qrc:///"); + m_ui->settings->engine()->rootContext()->setContextProperty("_launcher", this); + m_ui->settings->engine()->rootContext()->setContextProperty("_mpServers", m_serversModel); + + m_ui->settings->engine()->setObjectOwnership(this, QQmlEngine::CppOwnership); + + m_ui->settings->setResizeMode(QQuickWidget::SizeRootObjectToView); + m_ui->settings->setSource(QUrl("qrc:///qml/Settings.qml")); + + // environemnt + m_ui->environmentPage->engine()->addImportPath("qrc:///"); + m_ui->environmentPage->engine()->rootContext()->setContextProperty("_launcher", this); + auto weatherScenariosModel = new flightgear::WeatherScenariosModel(this); + m_ui->environmentPage->engine()->rootContext()->setContextProperty("_weatherScenarios", weatherScenariosModel); + + m_ui->environmentPage->engine()->setObjectOwnership(this, QQmlEngine::CppOwnership); + m_ui->environmentPage->engine()->rootContext()->setContextProperty("_config", m_config); + + m_ui->environmentPage->setResizeMode(QQuickWidget::SizeRootObjectToView); + m_ui->environmentPage->setSource(QUrl("qrc:///qml/Environment.qml")); + + // summary + m_ui->summary->engine()->addImportPath("qrc:///"); + m_ui->summary->engine()->rootContext()->setContextProperty("_launcher", this); + m_ui->summary->engine()->setObjectOwnership(this, QQmlEngine::CppOwnership); + m_ui->summary->setResizeMode(QQuickWidget::SizeRootObjectToView); + m_ui->summary->setSource(QUrl("qrc:///qml/Summary.qml")); + ////////////////////////// + } void LauncherMainWindow::initQML() @@ -240,6 +238,9 @@ void LauncherMainWindow::initQML() qmlRegisterUncreatableType("FlightGear.Launcher", 1, 0, "QAIM", "no"); qmlRegisterUncreatableType("FlightGear.Launcher", 1, 0, "AircraftProxyModel", "no"); + qmlRegisterUncreatableType("FlightGear.Launcher", 1, 0, "RecentAircraftModel", "no"); + qmlRegisterUncreatableType("FlightGear.Launcher", 1, 0, "RecentLocationsModel", "no"); + qmlRegisterUncreatableType("FlightGear.Launcher", 1, 0, "Control", "Base class"); qmlRegisterUncreatableType("FlightGear.Launcher", 1, 0, "LaunchConfig", "Singleton API"); qmlRegisterType("FlightGear.Launcher", 1, 0, "FileDialog"); @@ -265,6 +266,9 @@ void LauncherMainWindow::initQML() settingsContext->setContextProperty("_config", m_config); settingsContext->setContextProperty("_osName", osName); + QQmlContext* summaryContext = m_ui->summary->engine()->rootContext(); + summaryContext->setContextProperty("_config", m_config); + qmlRegisterUncreatableType("FlightGear.Launcher", 1, 0, "LocalAircraftCache", "Aircraft cache"); qmlRegisterUncreatableType("FlightGear.Launcher", 1, 0, "AircraftModel", "Built-in model"); qmlRegisterType("FlightGear.Launcher", 1, 0, "ThumbnailImage"); @@ -311,12 +315,8 @@ void LauncherMainWindow::restoreSettings() restoreGeometry(settings.value("window-geometry").toByteArray()); - // full paths to -set.xml files - m_recentAircraft = QUrl::fromStringList(settings.value("recent-aircraft").toStringList()); - - if (!m_recentAircraft.empty()) { - m_selectedAircraft = m_recentAircraft.front(); - } else { + m_selectedAircraft = m_aircraftHistory->mostRecent(); + if (m_selectedAircraft.isEmpty()) { // select the default aircraft specified in defaults.xml flightgear::DefaultAircraftLocator da; if (da.foundPath().exists()) { @@ -332,22 +332,16 @@ void LauncherMainWindow::restoreSettings() } m_ui->location->restoreSettings(); - m_recentLocations = settings.value("recent-location-sets").toList(); - QVariantMap currentLocation; - if (m_recentLocations.isEmpty()) { + QVariantMap currentLocation = m_locationHistory->mostRecent(); + if (currentLocation.isEmpty()) { // use the default std::string defaultAirport = flightgear::defaultAirportICAO(); FGAirportRef apt = FGAirport::findByIdent(defaultAirport); if (apt) { currentLocation["location-id"] = static_cast(apt->guid()); currentLocation["location-apt-runway"] = "active"; - qDebug() << "restored default airport:" << QString::fromStdString(defaultAirport); } // otherwise we failed to find the default airport in the nav-db :( - } else { - // we have a valid current location - currentLocation = m_recentLocations.front().toMap(); } - m_ui->location->restoreLocation(currentLocation); updateSelectedAircraft(); @@ -363,9 +357,10 @@ void LauncherMainWindow::saveSettings() { emit requestSaveState(); + m_aircraftHistory->saveToSettings(); + m_locationHistory->saveToSettings(); + QSettings settings; - settings.setValue("recent-aircraft", QUrl::toStringList(m_recentAircraft)); - settings.setValue("recent-location-sets", m_recentLocations); settings.setValue("window-geometry", saveGeometry()); Q_FOREACH(SettingsSection* ss, findChildren()) { @@ -416,41 +411,13 @@ void LauncherMainWindow::onRun() m_config->reset(); m_config->collect(); - // aircraft - if (!m_selectedAircraft.isEmpty()) { - // manage aircraft history - if (m_recentAircraft.contains(m_selectedAircraft)) - m_recentAircraft.removeOne(m_selectedAircraft); - m_recentAircraft.prepend(m_selectedAircraft); - if (m_recentAircraft.size() > MAX_RECENT_AIRCRAFT) - m_recentAircraft.pop_back(); - } + m_aircraftHistory->insert(m_selectedAircraft); - if (m_ui->stateCombo->isVisible()) { - // apply state setting - std::string tag = m_ui->stateCombo->currentData(QmlAircraftInfo::StateTagRole). - toString().toStdString(); - - // implicit auto behaviour disabled for 2018.1, since it - // needs a bit more work -#if 0 - if (tag == "auto") { - bool isExplictAuto = m_ui->stateCombo->currentData(QmlAircraftInfo::StateExplicitRole).toBool(); - if (!isExplictAuto) { - tag = selectStateAutomatically(); - qInfo() << "automatic state selection: picked:" << QString::fromStdString(tag); - } - } -#endif - if (!tag.empty() && (tag != "__default__")) { - m_config->setArg("state", tag); - } - } // of applying state selection + QVariant locSet = m_ui->location->saveLocation(); + m_locationHistory->insert(locSet); // aircraft paths QSettings settings; - updateLocationHistory(); - QString downloadDir = settings.value("downloadSettings/downloadDir").toString(); if (!downloadDir.isEmpty()) { QDir d(downloadDir); @@ -480,7 +447,7 @@ void LauncherMainWindow::onRun() qApp->exit(1); } -std::string LauncherMainWindow::selectStateAutomatically() +QString LauncherMainWindow::selectAircraftStateAutomatically() { if (m_ui->location->isAirborneLocation()) { return "approach"; @@ -517,13 +484,7 @@ void LauncherMainWindow::onApply() qWarning() << "unsupported aircraft launch URL" << m_selectedAircraft; } - // manage aircraft history - if (m_recentAircraft.contains(m_selectedAircraft)) - m_recentAircraft.removeOne(m_selectedAircraft); - m_recentAircraft.prepend(m_selectedAircraft); - if (m_recentAircraft.size() > MAX_RECENT_AIRCRAFT) - m_recentAircraft.pop_back(); - + m_aircraftHistory->insert(m_selectedAircraft); globals->get_props()->setStringValue("/sim/aircraft", aircraftPropValue); globals->get_props()->setStringValue("/sim/aircraft-dir", aircraftDir); } @@ -536,23 +497,6 @@ void LauncherMainWindow::onApply() m_runInApp = false; } -void LauncherMainWindow::updateLocationHistory() -{ - QVariant locSet = m_ui->location->saveLocation(); - - // check for existing; let's use description to imply uniqueness. This means - // 'A1111' parkings get merged but I prefer that to keep the menu usable - QVariant locDesc = locSet.toMap().value("text"); - auto it = std::remove_if(m_recentLocations.begin(), m_recentLocations.end(), - [locDesc](QVariant v) { return v.toMap().value("text") == locDesc; }); - m_recentLocations.erase(it, m_recentLocations.end()); - - // now we can always prepend - m_recentLocations.prepend(locSet); - if (m_recentLocations.size() > MAX_RECENT_AIRCRAFT) - m_recentLocations.pop_back(); -} - void LauncherMainWindow::onQuit() { if (m_inAppMode) { @@ -628,6 +572,7 @@ void LauncherMainWindow::updateSelectedAircraft() m_selectedAircraftInfo->setUri(m_selectedAircraft); QModelIndex index = m_aircraftModel->indexOfAircraftURI(m_selectedAircraft); if (index.isValid()) { +#if 0 QPixmap pm = index.data(Qt::DecorationRole).value(); m_ui->thumbnail->setPixmap(pm); @@ -640,7 +585,7 @@ void LauncherMainWindow::updateSelectedAircraft() QVariant longDesc = index.data(AircraftLongDescriptionRole); m_ui->aircraftDescription->setVisible(!longDesc.isNull()); m_ui->aircraftDescription->setText(longDesc.toString()); - +#endif int status = index.data(AircraftPackageStatusRole).toInt(); bool canRun = (status == LocalAircraftCache::PackageInstalled); m_ui->flyButton->setEnabled(canRun); @@ -655,6 +600,7 @@ void LauncherMainWindow::updateSelectedAircraft() m_ui->location->setAircraftType(aircraftType); const bool hasStates = m_selectedAircraftInfo->hasStates(); +#if 0 m_ui->stateCombo->setVisible(hasStates); m_ui->stateLabel->setVisible(hasStates); m_ui->stateDescription->setVisible(false); @@ -664,13 +610,16 @@ void LauncherMainWindow::updateSelectedAircraft() // hiden when no description is present m_ui->stateDescription->setVisible(!m_ui->stateDescription->text().isEmpty()); } +#endif } else { +#if 0 m_ui->thumbnail->setPixmap(QPixmap()); m_ui->aircraftName->setText(""); m_ui->aircraftDescription->hide(); m_ui->stateCombo->hide(); m_ui->stateLabel->hide(); m_ui->stateDescription->hide(); +#endif m_ui->flyButton->setEnabled(false); } } @@ -690,58 +639,6 @@ void LauncherMainWindow::setSceneryPaths() flightgear::launcherSetSceneryPaths(); } -void LauncherMainWindow::onPopupAircraftHistory() -{ - if (m_recentAircraft.isEmpty()) { - return; - } - - QMenu m; - Q_FOREACH(QUrl uri, m_recentAircraft) { - QString nm = m_aircraftModel->nameForAircraftURI(uri); - if (nm.isEmpty()) { - continue; - } - - QAction* act = m.addAction(nm); - act->setData(uri); - } - - QPoint popupPos = m_ui->aircraftHistory->mapToGlobal(m_ui->aircraftHistory->rect().bottomLeft()); - QAction* triggered = m.exec(popupPos); - if (triggered) { - setSelectedAircraft(triggered->data().toUrl()); - } -} - -void LauncherMainWindow::onPopupLocationHistory() -{ - if (m_recentLocations.isEmpty()) { - return; - } - - QMenu m; - Q_FOREACH(QVariant loc, m_recentLocations) { - QString summary = loc.toMap().value("text").toString(); - QAction* act = m.addAction(summary); - act->setData(loc); - } - - QPoint popupPos = m_ui->locationHistory->mapToGlobal(m_ui->locationHistory->rect().bottomLeft()); - QAction* triggered = m.exec(popupPos); - if (triggered) { - m_ui->location->restoreLocation(triggered->data().toMap()); - } -} - -void LauncherMainWindow::updateSettingsSummary() -{ - const QStringList summary = m_settingsSummary + m_environmentSummary; - QString s = summary.join(", "); - s[0] = s[0].toUpper(); - m_ui->settingsDescription->setText(s); -} - void LauncherMainWindow::onSubsytemIdleTimeout() { globals->get_subsystem_mgr()->update(0.0); @@ -836,6 +733,11 @@ QmlAircraftInfo *LauncherMainWindow::selectedAircraftInfo() const return m_selectedAircraftInfo; } +void LauncherMainWindow::restoreLocation(QVariant var) +{ + m_ui->location->restoreLocation(var.toMap()); +} + bool LauncherMainWindow::matchesSearch(QString term, QStringList keywords) const { Q_FOREACH(QString s, keywords) { @@ -888,7 +790,6 @@ void LauncherMainWindow::setSettingsSummary(QStringList settingsSummary) m_settingsSummary = settingsSummary; emit summaryChanged(); - updateSettingsSummary(); } void LauncherMainWindow::setEnvironmentSummary(QStringList environmentSummary) @@ -898,7 +799,16 @@ void LauncherMainWindow::setEnvironmentSummary(QStringList environmentSummary) m_environmentSummary = environmentSummary; emit summaryChanged(); - updateSettingsSummary(); +} + +QStringList LauncherMainWindow::combinedSummary() const +{ + return m_settingsSummary + m_environmentSummary; +} + +QString LauncherMainWindow::locationDescription() const +{ + return m_ui->location->locationDescription(); } simgear::pkg::PackageRef LauncherMainWindow::packageForAircraftURI(QUrl uri) const @@ -1040,3 +950,34 @@ bool LauncherMainWindow::showNoOfficialHanger() const return shouldShowOfficialCatalogMessage(); } +QString LauncherMainWindow::versionString() const +{ + return FLIGHTGEAR_VERSION; +} + +RecentAircraftModel *LauncherMainWindow::aircraftHistory() +{ + return m_aircraftHistory; +} + +RecentLocationsModel *LauncherMainWindow::locationHistory() +{ + return m_locationHistory; +} + +void LauncherMainWindow::launchUrl(QUrl url) +{ + QDesktopServices::openUrl(url); +} + +QVariantList LauncherMainWindow::defaultSplashUrls() const +{ + QVariantList urls; + + for (auto path : flightgear::defaultSplashScreenPaths()) { + QUrl url = QUrl::fromLocalFile(QString::fromStdString(path)); + urls.append(url); + } + + return urls; +} diff --git a/src/GUI/LauncherMainWindow.hxx b/src/GUI/LauncherMainWindow.hxx index 5ab9bd5a4..a12bdb7b1 100644 --- a/src/GUI/LauncherMainWindow.hxx +++ b/src/GUI/LauncherMainWindow.hxx @@ -48,6 +48,9 @@ class ViewCommandLinePage; class MPServersModel; class QQuickItem; class QmlAircraftInfo; +class QStandardItemModel; +class RecentAircraftModel; +class RecentLocationsModel; class LauncherMainWindow : public QMainWindow { @@ -72,6 +75,14 @@ class LauncherMainWindow : public QMainWindow Q_PROPERTY(QStringList settingsSummary READ settingsSummary WRITE setSettingsSummary NOTIFY summaryChanged) Q_PROPERTY(QStringList environmentSummary READ environmentSummary WRITE setEnvironmentSummary NOTIFY summaryChanged) + Q_PROPERTY(QString locationDescription READ locationDescription NOTIFY summaryChanged) + Q_PROPERTY(QStringList combinedSummary READ combinedSummary NOTIFY summaryChanged) + + Q_PROPERTY(QString versionString READ versionString CONSTANT) + + Q_PROPERTY(RecentAircraftModel* aircraftHistory READ aircraftHistory CONSTANT) + Q_PROPERTY(RecentLocationsModel* locationHistory READ locationHistory CONSTANT) + public: LauncherMainWindow(); virtual ~LauncherMainWindow(); @@ -105,6 +116,8 @@ public: QmlAircraftInfo* selectedAircraftInfo() const; + Q_INVOKABLE void restoreLocation(QVariant var); + Q_INVOKABLE bool matchesSearch(QString term, QStringList keywords) const; bool isSearchActive() const; @@ -118,6 +131,25 @@ public: QStringList environmentSummary() const; + QStringList combinedSummary() const; + + QString locationDescription() const; + + QString versionString() const; + + RecentAircraftModel* aircraftHistory(); + + RecentLocationsModel* locationHistory(); + + Q_INVOKABLE void launchUrl(QUrl url); + + // list of QUrls containing the default splash images from FGData. + // used on the summary screen + Q_INVOKABLE QVariantList defaultSplashUrls() const; + + + Q_INVOKABLE QString selectAircraftStateAutomatically(); + public slots: void setSelectedAircraft(QUrl selectedAircraft); @@ -155,11 +187,6 @@ private slots: void onQuit(); - void onPopupAircraftHistory(); - void onPopupLocationHistory(); - - void updateSettingsSummary(); - void onSubsytemIdleTimeout(); void onAircraftInstalledCompleted(QModelIndex index); @@ -192,13 +219,11 @@ private: // scrolling, to give the view time it seems. void delayedAircraftModelReset(); - void updateLocationHistory(); bool shouldShowOfficialCatalogMessage() const; void collectAircraftArgs(); void initQML(); - std::string selectStateAutomatically(); QScopedPointer m_ui; AircraftProxyModel* m_installedAircraftModel; @@ -208,21 +233,20 @@ private: MPServersModel* m_serversModel = nullptr; QUrl m_selectedAircraft; - QList m_recentAircraft; QTimer* m_subsystemIdleTimer; bool m_inAppMode = false; bool m_runInApp = false; bool m_accepted = false; int m_ratingFilters[4] = {3, 3, 3, 3}; - QVariantList m_recentLocations; QQmlEngine* m_qmlEngine = nullptr; LaunchConfig* m_config = nullptr; - ExtraSettingsSection* m_extraSettings = nullptr; ViewCommandLinePage* m_viewCommandLinePage = nullptr; QmlAircraftInfo* m_selectedAircraftInfo = nullptr; QString m_settingsSearchTerm; QStringList m_settingsSummary, - m_environmentSummary; + m_environmentSummary; + RecentAircraftModel* m_aircraftHistory = nullptr; + RecentLocationsModel* m_locationHistory = nullptr; }; #endif // of LAUNCHER_MAIN_WINDOW_HXX diff --git a/src/GUI/QmlAircraftInfo.cxx b/src/GUI/QmlAircraftInfo.cxx index c449204e0..2c4b1ba2e 100644 --- a/src/GUI/QmlAircraftInfo.cxx +++ b/src/GUI/QmlAircraftInfo.cxx @@ -2,6 +2,7 @@ #include #include +#include #include #include @@ -86,9 +87,12 @@ private: struct StateInfo { - std::string tag; // internal XML name + string_list tags; QString name; // human-readable name, or blank if we auto-generate this QString description; // human-readable description + + std::string primaryTag() const + { return tags.front(); } }; using AircraftStateVec = std::vector; @@ -111,12 +115,20 @@ static AircraftStateVec readAircraftStates(const SGPath& setXMLPath) AircraftStateVec result; result.reserve(nodes.size()); for (auto cn : nodes) { - result.push_back({cn->getStringValue("name"), + string_list stateNames; + for (auto nameNode : cn->getChildren("name")) { + stateNames.push_back(nameNode->getStringValue()); + } + + if (stateNames.empty()) { + qWarning() << "state with no names defined, skipping"; + continue; + } + + result.push_back({stateNames, QString::fromStdString(cn->getStringValue("readable-name")), QString::fromStdString(cn->getStringValue("description")) }); - - qInfo() << QString::fromStdString(result.back().tag) << result.back().description; } return result; @@ -141,35 +153,34 @@ QString humanNameFromStateTag(const std::string& tag) class StatesModel : public QAbstractListModel { Q_OBJECT + + Q_PROPERTY(bool hasExplicitAuto READ hasExplicitAuto CONSTANT) public: StatesModel(const AircraftStateVec& states) : _data(states) { // sort which places 'auto' item at the front if it exists std::sort(_data.begin(), _data.end(), [](const StateInfo& a, const StateInfo& b) { - if (a.tag == "auto") return true; - if (b.tag == "auto") return false; - return a.tag < b.tag; + if (a.primaryTag() == "auto") return true; + if (b.primaryTag() == "auto") return false; + return a.primaryTag() < b.primaryTag(); }); - if (_data.front().tag == "auto") { + if (_data.front().primaryTag() == "auto") { // track if the aircraft supplied an 'auto' state, in which case // we will not run our own selection logic _explicitAutoState = true; } else { // disabling this code for 2018.1, since it needs more testing -#if 0 - _data.insert(_data.begin(), {"auto", {}, tr("Select state based on startup position.")}); -#else - _data.insert(_data.begin(), {"__default__", tr("Parked"), tr("Default state for the aircraft (usually cold and dark)")}); -#endif + _data.insert(_data.begin(), {{"auto"}, {}, tr("Select state based on startup position.")}); } } int indexForTag(const std::string &tag) const { auto it = std::find_if(_data.begin(), _data.end(), [tag](const StateInfo& i) { - return i.tag == tag; + auto tagIt = std::find(i.tags.begin(), i.tags.end(), tag); + return (tagIt != i.tags.end()); }); if (it == _data.end()) @@ -186,18 +197,17 @@ public: QVariant data(const QModelIndex &index, int role) const override { const StateInfo& s = _data.at(index.row()); - // qInfo() << index.row() << s.name << QString::fromStdString(s.tag); if (role == Qt::DisplayRole) { if (s.name.isEmpty()) { - return humanNameFromStateTag(s.tag); + return humanNameFromStateTag(s.primaryTag()); } return s.name; } else if (role == QmlAircraftInfo::StateTagRole) { - return QString::fromStdString(s.tag); + return QString::fromStdString(s.primaryTag()); } else if (role == QmlAircraftInfo::StateDescriptionRole) { return s.description; } else if (role == QmlAircraftInfo::StateExplicitRole) { - if (s.tag == "auto") + if (s.primaryTag() == "auto") return _explicitAutoState; return true; } @@ -213,6 +223,28 @@ public: result[QmlAircraftInfo::StateDescriptionRole] = "description"; return result; } + + Q_INVOKABLE QString descriptionForState(int row) const + { + if ((row < 0) || (row >= _data.size())) + return {}; + + const StateInfo& s = _data.at(row); + return s.description; + } + + Q_INVOKABLE QString tagForState(int row) const + { + if ((row < 0) || (row >= _data.size())) + return {}; + + return QString::fromStdString(_data.at(row).primaryTag()); + } + + bool hasExplicitAuto() const + { + return _explicitAutoState; + } private: AircraftStateVec _data; bool _explicitAutoState = false; @@ -224,6 +256,7 @@ QmlAircraftInfo::QmlAircraftInfo(QObject *parent) : QObject(parent) , _delegate(new Delegate(this)) { + qmlRegisterUncreatableType("FlightGear.Launcher", 1, 0, "StatesModel", "no"); } QmlAircraftInfo::~QmlAircraftInfo() @@ -621,7 +654,7 @@ bool QmlAircraftInfo::isPackaged() const return _package != PackageRef(); } -QAbstractListModel *QmlAircraftInfo::statesModel() +StatesModel *QmlAircraftInfo::statesModel() { if (!hasStates()) return nullptr; diff --git a/src/GUI/QmlAircraftInfo.hxx b/src/GUI/QmlAircraftInfo.hxx index e767cc144..8a1755240 100644 --- a/src/GUI/QmlAircraftInfo.hxx +++ b/src/GUI/QmlAircraftInfo.hxx @@ -14,6 +14,8 @@ struct AircraftItem; typedef QSharedPointer AircraftItemPtr; +class StatesModel; + class QmlAircraftInfo : public QObject { Q_OBJECT @@ -57,7 +59,7 @@ class QmlAircraftInfo : public QObject Q_PROPERTY(bool hasStates READ hasStates NOTIFY infoChanged) - Q_PROPERTY(QAbstractListModel* statesModel READ statesModel NOTIFY infoChanged) + Q_PROPERTY(StatesModel* statesModel READ statesModel NOTIFY infoChanged) public: explicit QmlAircraftInfo(QObject *parent = nullptr); @@ -110,7 +112,7 @@ public: static const int StateDescriptionRole; static const int StateExplicitRole; - QAbstractListModel* statesModel(); + StatesModel* statesModel(); signals: void uriChanged(); void infoChanged(); @@ -134,7 +136,7 @@ private: AircraftItemPtr _item; int _variant = 0; int _downloadBytes = 0; - QScopedPointer _statesModel; + QScopedPointer _statesModel; }; #endif // QMLAIRCRAFTINFO_HXX diff --git a/src/GUI/RecentAircraftModel.cxx b/src/GUI/RecentAircraftModel.cxx new file mode 100644 index 000000000..ff5fd9ec5 --- /dev/null +++ b/src/GUI/RecentAircraftModel.cxx @@ -0,0 +1,92 @@ +#include "RecentAircraftModel.hxx" + +#include +#include + +#include "AircraftModel.hxx" + +const int MAX_RECENT_AIRCRAFT = 20; + + +RecentAircraftModel::RecentAircraftModel(AircraftItemModel* acModel, QObject* pr) : + QAbstractListModel(pr), + m_aircraftModel(acModel) +{ + QSettings settings; + const QStringList urls = settings.value("recent-aircraft").toStringList(); + m_data = QUrl::fromStringList(urls); +} + +void RecentAircraftModel::saveToSettings() +{ + QSettings settings; + settings.setValue("recent-aircraft", QUrl::toStringList(m_data)); +} + +QUrl RecentAircraftModel::uriAt(int index) const +{ + return m_data.at(index); +} + +QVariant RecentAircraftModel::data(const QModelIndex &index, int role) const +{ + const QUrl uri = m_data.at(index.row()); + if (role == Qt::DisplayRole) { + return m_aircraftModel->nameForAircraftURI(uri); + } else if (role == Qt::UserRole) { + return uri; + } + + return {}; +} + +int RecentAircraftModel::rowCount(const QModelIndex &parent) const +{ + return m_data.size(); +} + +QHash RecentAircraftModel::roleNames() const +{ + QHash result = QAbstractListModel::roleNames(); + result[Qt::DisplayRole] = "display"; + result[Qt::UserRole] = "uri"; + return result; +} + +QUrl RecentAircraftModel::mostRecent() const +{ + if (m_data.empty()) { + return {}; + } + + return m_data.front(); +} + +void RecentAircraftModel::insert(QUrl aircraftUrl) +{ + if (aircraftUrl.isEmpty()) + return; + + int existingIndex = m_data.indexOf(aircraftUrl); + if (existingIndex == 0) { + // special, common case - nothing to do + return; + } + + if (existingIndex >= 0) { + beginRemoveRows(QModelIndex(), existingIndex, existingIndex); + m_data.removeAt(existingIndex); + endRemoveRows(); + } + + beginInsertRows(QModelIndex(), 0, 0); + m_data.push_front(aircraftUrl); + endInsertRows(); + + if (m_data.size() > MAX_RECENT_AIRCRAFT) { + beginRemoveRows(QModelIndex(), MAX_RECENT_AIRCRAFT, m_data.size() - 1); + // truncate the data at the correct size + m_data = m_data.mid(0, MAX_RECENT_AIRCRAFT); + endRemoveRows(); + } +} diff --git a/src/GUI/RecentAircraftModel.hxx b/src/GUI/RecentAircraftModel.hxx new file mode 100644 index 000000000..24af11275 --- /dev/null +++ b/src/GUI/RecentAircraftModel.hxx @@ -0,0 +1,34 @@ +#ifndef RECENTAIRCRAFTMODEL_HXX +#define RECENTAIRCRAFTMODEL_HXX + +#include +#include + +// forward decls +class AircraftItemModel; + +class RecentAircraftModel : public QAbstractListModel +{ + Q_OBJECT +public: + RecentAircraftModel(AircraftItemModel *acModel, QObject* pr = nullptr); + + QVariant data(const QModelIndex &index, int role) const override; + + int rowCount(const QModelIndex &parent) const override; + + QHash roleNames() const override; + + QUrl mostRecent() const; + + void insert(QUrl aircraftUrl); + + void saveToSettings(); + + Q_INVOKABLE QUrl uriAt(int index) const; +private: + AircraftItemModel* m_aircraftModel; + QList m_data; +}; + +#endif // RECENTAIRCRAFTMODEL_HXX diff --git a/src/GUI/RecentLocationsModel.cxx b/src/GUI/RecentLocationsModel.cxx new file mode 100644 index 000000000..517e602c6 --- /dev/null +++ b/src/GUI/RecentLocationsModel.cxx @@ -0,0 +1,91 @@ +#include "RecentLocationsModel.hxx" + +#include +#include + + +const int MAX_RECENT_LOCATIONS = 20; + +RecentLocationsModel::RecentLocationsModel(QObject* pr) : + QAbstractListModel(pr) +{ + QSettings settings; + m_data = settings.value("recent-location-sets").toList(); +} + +void RecentLocationsModel::saveToSettings() +{ + QSettings settings; + settings.setValue("recent-location-sets", m_data); +} + +QVariantMap RecentLocationsModel::locationAt(int index) const +{ + return m_data.at(index).toMap(); +} + +QVariant RecentLocationsModel::data(const QModelIndex &index, int role) const +{ + const QVariantMap loc = m_data.at(index.row()).toMap(); + if (role == Qt::DisplayRole) { + return loc.value("text"); + } else if (role == Qt::UserRole) { + return loc; + } + + return {}; +} + +int RecentLocationsModel::rowCount(const QModelIndex &parent) const +{ + return m_data.size(); +} + +QHash RecentLocationsModel::roleNames() const +{ + QHash result = QAbstractListModel::roleNames(); + result[Qt::DisplayRole] = "display"; + // result[Qt::UserRole] = "uri"; + return result; +} + +QVariantMap RecentLocationsModel::mostRecent() const +{ + if (m_data.empty()) { + return {}; + } + + return m_data.front().toMap(); +} + +void RecentLocationsModel::insert(QVariant location) +{ + if (location.toMap().isEmpty()) + return; + + QVariant locDesc = location.toMap().value("text"); + auto it = std::find_if(m_data.begin(), m_data.end(), + [locDesc](QVariant v) { return v.toMap().value("text") == locDesc; }); + if (it == m_data.begin()) { + // special, common case - nothing to do + return; + } + + if (it != m_data.end()) { + int existingIndex = std::distance(m_data.begin(), it); + beginRemoveRows(QModelIndex(), existingIndex, existingIndex); + m_data.erase(it); + endRemoveRows(); + } + + beginInsertRows(QModelIndex(), 0, 0); + m_data.push_front(location); + endInsertRows(); + + if (m_data.size() > MAX_RECENT_LOCATIONS) { + beginRemoveRows(QModelIndex(), MAX_RECENT_LOCATIONS, m_data.size() - 1); + // truncate the data at the correct size + m_data = m_data.mid(0, MAX_RECENT_LOCATIONS); + endRemoveRows(); + } +} diff --git a/src/GUI/RecentLocationsModel.hxx b/src/GUI/RecentLocationsModel.hxx new file mode 100644 index 000000000..552bbc6b9 --- /dev/null +++ b/src/GUI/RecentLocationsModel.hxx @@ -0,0 +1,31 @@ +#ifndef RECENTLOCATIONSMODEL_HXX +#define RECENTLOCATIONSMODEL_HXX + +#include +#include + + +class RecentLocationsModel : public QAbstractListModel +{ + Q_OBJECT +public: + RecentLocationsModel(QObject* pr = nullptr); + + QVariant data(const QModelIndex &index, int role) const override; + + int rowCount(const QModelIndex &parent) const override; + + QHash roleNames() const override; + + QVariantMap mostRecent() const; + + void insert(QVariant location); + + void saveToSettings(); + + Q_INVOKABLE QVariantMap locationAt(int index) const; +private: + QVariantList m_data; +}; + +#endif // RECENTLOCATIONSMODEL_HXX diff --git a/src/GUI/qml/ClickableText.qml b/src/GUI/qml/ClickableText.qml index a203247b3..c817c3f54 100644 --- a/src/GUI/qml/ClickableText.qml +++ b/src/GUI/qml/ClickableText.qml @@ -4,7 +4,8 @@ import "." Text { signal clicked(); - color: mouse.containsMouse ? Style.themeColor : Style.baseTextColor + property color baseTextColor: Style.baseTextColor + color: mouse.containsMouse ? Style.themeColor : baseTextColor MouseArea { id: mouse @@ -12,5 +13,7 @@ Text { anchors.fill: parent onClicked: parent.clicked(); + + cursorShape: Qt.PointingHandCursor } } diff --git a/src/GUI/qml/HistoryPopup.qml b/src/GUI/qml/HistoryPopup.qml new file mode 100644 index 000000000..97e62b247 --- /dev/null +++ b/src/GUI/qml/HistoryPopup.qml @@ -0,0 +1,100 @@ +import QtQuick 2.0 +import QtQuick.Window 2.0 +import FlightGear.Launcher 1.0 + +import "." + +Item { + id: root + + property var model: undefined + property string displayRole: "display" + property bool enabled: true + + implicitHeight: button.height + implicitWidth: button.width + + signal selected(var index); + + Rectangle { + id: button + + width: icon.width + height: icon.height + radius: Style.roundRadius + + color: enabled ? (mouse.containsMouse ? Style.activeColor : Style.themeColor) : Style.disabledThemeColor + + Image { + id: icon + source: "qrc:///history-icon" + anchors.centerIn: parent + } + + MouseArea { + anchors.fill: parent + id: mouse + hoverEnabled: true + enabled: root.enabled + onClicked: { + var screenPos = _launcher.mapToGlobal(button, Qt.point(-popupFrame.width, 0)) + popupFrame.x = screenPos.x; + popupFrame.y = screenPos.y; + popupFrame.visible = true + tracker.window = popupFrame + } + } + } + + PopupWindowTracker { + id: tracker + } + + Window { + id: popupFrame + + flags: Qt.Popup + height: choicesColumn.childrenRect.height + Style.margin * 2 + width: choicesColumn.childrenRect.width + Style.margin * 2 + visible: false + color: "white" + + Rectangle { + border.width: 1 + border.color: Style.minorFrameColor + anchors.fill: parent + } + + // text repeater + Column { + id: choicesColumn + spacing: Style.margin + x: Style.margin + y: Style.margin + + Repeater { + id: choicesRepeater + model: root.model + delegate: + Text { + id: choiceText + + // Taken from TableViewItemDelegateLoader.qml to follow QML role conventions + text: model && model.hasOwnProperty(displayRole) ? model[displayRole] // Qml ListModel and QAbstractItemModel + : modelData && modelData.hasOwnProperty(displayRole) ? modelData[displayRole] // QObjectList / QObject + : modelData != undefined ? modelData : "" // Models without role + height: implicitHeight + Style.margin + + MouseArea { + width: popupFrame.width // full width of the popup + height: parent.height + onClicked: { + popupFrame.visible = false + root.selected(model.index); + } + } + } // of Text delegate + } // text repeater + } // text column + } // of popup Window +} diff --git a/src/GUI/qml/PopupChoice.qml b/src/GUI/qml/PopupChoice.qml index b77d78170..b94031a12 100644 --- a/src/GUI/qml/PopupChoice.qml +++ b/src/GUI/qml/PopupChoice.qml @@ -14,7 +14,7 @@ Item { property int currentIndex: 0 property bool __dummy: false - implicitHeight: label.implicitHeight + implicitHeight: Math.max(label.implicitHeight, currentChoiceFrame.height) Item { Repeater { @@ -40,6 +40,10 @@ Item { __dummy = !__dummy } + onModelChanged: { + __dummy = !__dummy // force update of currentText + } + function currentText() { var foo = __dummy; // fake propery dependency to update this diff --git a/src/GUI/qml/Style.qml b/src/GUI/qml/Style.qml index 782069b9a..5869e0419 100644 --- a/src/GUI/qml/Style.qml +++ b/src/GUI/qml/Style.qml @@ -21,6 +21,10 @@ QtObject readonly property string baseTextColor: "#3f3f3f" readonly property int baseFontPixelSize: 12 + readonly property int headingFontPixelSize: 18 + readonly property string disabledTextColor: "#5f5f5f" + + readonly property double panelOpacity: 0.8 } diff --git a/src/GUI/qml/Summary.qml b/src/GUI/qml/Summary.qml new file mode 100644 index 000000000..e22d00072 --- /dev/null +++ b/src/GUI/qml/Summary.qml @@ -0,0 +1,321 @@ +import QtQuick 2.0 +import FlightGear.Launcher 1.0 +import "." + +Item { + id: root + + Rectangle { + anchors.fill: parent + color: "#7f7f7f" + } + + property string __aircraftDescription + + // conditional bindings on aircraft description + Binding { + when: _launcher.selectedAircraftInfo != undefined + target: root + property: "__aircraftDescription" + value: _launcher.selectedAircraftInfo.description + } + + Binding { + when: _launcher.selectedAircraftInfo == undefined + target: root + property: "__aircraftDescription" + value: "" + } + + // base image when preview not available + Rectangle { + anchors.fill: parent + color: "magenta" + } + + PreviewImage { + id: preview + anchors.centerIn: parent + + // over-zoom the preview to fill the entire space available + readonly property double scale: Math.max(root.width / sourceSize.width, + root.height / sourceSize.height) + + width: height * aspectRatio + height: scale * sourceSize.height + + property var urlsList: [] + property int __currentUrl: 0 + + onUrlsListChanged: { + __currentUrl = 0; + } + + Timer { + running: true + interval: 8000 + repeat: true + onTriggered: { + var len = preview.urlsList.length + preview.__currentUrl = (preview.__currentUrl + 1) % len + } + } + + visible: imageUrl != "" + imageUrl: urlsList[__currentUrl] + +// conditional binding when we have valid previews + Binding { + when: _launcher.selectedAircraftInfo != undefined && + (_launcher.selectedAircraftInfo.previews.length > 0) + target: preview + property: "urlsList" + value: _launcher.selectedAircraftInfo.previews + } + + Binding { + when: _launcher.selectedAircraftInfo == undefined || + _launcher.selectedAircraftInfo.previews.length == 0 + target: preview + property: "urlsList" + value: _launcher.defaultSplashUrls() + } + } + + Text { + id: logoText + font.pointSize: Style.strutSize * 2 + font.italic: true + font.bold: true + anchors { + left: parent.left + right: parent.right + top: parent.top + margins: Style.strutSize + } + fontSizeMode: Text.Fit + text: "FlightGear " + _launcher.versionString + color: "white" + style: Text.Outline + styleColor: "black" + } + + ClickableText { + anchors { + left: logoText.left + right: logoText.right + } + + // anchoring to logoText bottom doesn't work as expected because of + // dynamic text sizing, so bind it manually + y: logoText.y + Style.margin + logoText.contentHeight + wrapMode: Text.WordWrap + text: "Licenced under the GNU Public License (GPL) version 2.0 - click for more info" + baseTextColor: "white" + style: Text.Outline + styleColor: "black" + + onClicked: { + _launcher.launchUrl("http://flightgear.org/license.html"); + } + } + + Rectangle { + id: summaryPanel + + color: "white" + opacity: Style.panelOpacity + // radius: Style.roundRadius + border.width: 1 + border.color: Style.frameColor + + anchors { + left: parent.left + right: parent.right + bottom: parent.bottom + margins: Style.strutSize + } + + height: summaryGrid.height + Style.margin * 2 + + Grid { + id: summaryGrid + columns: 3 + columnSpacing: Style.margin + rowSpacing: Style.margin + + readonly property int middleColumnWidth: summaryGrid.width - (locationLabel.width + Style.margin * 2 + aircraftHistoryPopup.width) + + anchors { + left: parent.left + right: parent.right + top: parent.top + margins: Style.margin + } + + // aircraft name row + Text { + text: qsTr("Aircraft:") + horizontalAlignment: Text.AlignRight + font.pixelSize: Style.headingFontPixelSize + } + + // TODO - make clickable, jump to to the aircraft in the installed + // aircraft list + Text { + text: _launcher.selectedAircraftInfo.name + font.pixelSize: Style.headingFontPixelSize + } + + HistoryPopup { + id: aircraftHistoryPopup + model: _launcher.aircraftHistory + onSelected: { + _launcher.selectedAircraft = _launcher.aircraftHistory.uriAt(index) + } + } + + // empty space in next row (thumbnail, long aircraft description) + Item { + width: 1; height: 1 + } + + Item { + id: aircraftDetailsRow + width: summaryGrid.middleColumnWidth + height: Math.max(thumbnail.height, aircraftDescriptionText.height) + + ThumbnailImage { + id: thumbnail + aircraftUri: _launcher.selectedAircraft + maximumSize.width: 172 + maximumSize.height: 128 + } + + Text { + id: aircraftDescriptionText + anchors { + left: thumbnail.right + leftMargin: Style.margin + right: parent.right + } + + text: root.__aircraftDescription + visible: root.__aircraftDescription != "" + wrapMode: Text.WordWrap + maximumLineCount: 5 + elide: Text.ElideRight + } + } + + + + Item { + width: 1; height: 1 + } + + // aircraft state row, if enabled + + Item { + width: 1; height: 1 + visible: stateSelectionGroup.visible + } + + Column { + id: stateSelectionGroup + visible: _launcher.selectedAircraftInfo.hasStates + width: summaryGrid.middleColumnWidth + spacing: Style.margin + + PopupChoice { + id: stateSelectionCombo + model: _launcher.selectedAircraftInfo.statesModel + displayRole: "name" + label: qsTr("State:") + width: parent.width + } + + Text { + id: stateDescriptionText + wrapMode: Text.WordWrap + maximumLineCount: 5 + elide: Text.ElideRight + width: parent.width + + Binding { + when: _launcher.selectedAircraftInfo.statesModel != null + target: stateDescriptionText + property: "text" + value: _launcher.selectedAircraftInfo.statesModel.descriptionForState(stateSelectionCombo.currentIndex) + } + } + + Connections { + target: _config + onCollect: { + if (!_launcher.selectedAircraftInfo.hasStates) + return; + + var state = _launcher.selectedAircraftInfo.statesModel.tagForState(stateSelectionCombo.currentIndex); + if (state === "auto" && !_launcher.selectedAircraftInfo.statesModel.hasExplicitAuto) { + // auto state selection if not handled by aircraft + state = _launcher.selectAircraftStateAutomatically(); + console.info("launcher auto state selection, picked:" + state) + } + + if (state !== "__default__") { // don't set arg in default case + _config.setArg("state", state); + } + } + } // of connections + } + + Item { + width: 1; height: 1 + visible: stateSelectionGroup.visible + } + + // location summary row + Text { + id: locationLabel + text: qsTr("Location:") + horizontalAlignment: Text.AlignRight + font.pixelSize: Style.headingFontPixelSize + } + + // TODO - make clickable, jump to the location page + Text { + text: _launcher.locationDescription + font.pixelSize: Style.headingFontPixelSize + } + + HistoryPopup { + id: locationHistoryPopup + model: _launcher.locationHistory + onSelected: { + _launcher.restoreLocation(_launcher.locationHistory.locationAt(index)) + } + } + + // settings summary row + Text { + text: qsTr("Settings:") + horizontalAlignment: Text.AlignRight + font.pixelSize: Style.headingFontPixelSize + } + + Text { + text: _launcher.combinedSummary.join(", ") + font.pixelSize: Style.headingFontPixelSize + wrapMode: Text.WordWrap + maximumLineCount: 2 + elide: Text.ElideRight + width: summaryGrid.middleColumnWidth + } + + Item { + width: 1; height: 1 + } + } + } +} diff --git a/src/GUI/resources.qrc b/src/GUI/resources.qrc index 73c3e8883..cb6b8b12e 100644 --- a/src/GUI/resources.qrc +++ b/src/GUI/resources.qrc @@ -71,21 +71,12 @@ qml/DateTimeEdit.qml qml/DateTimeValueEdit.qml qml/SettingsDateTimePicker.qml + qml/Summary.qml + qml/HistoryPopup.qml preview-close.png preview-left-arrow.png preview-right-arrow.png - - DownloadSettings.qml - GeneralSettings.qml - MPSettings.qml - RenderSettings.qml - ViewSettings.qml - - - TimeSettings.qml - Weather.qml -