diff --git a/src/GUI/CMakeLists.txt b/src/GUI/CMakeLists.txt index d2873b87f..91eb1cef6 100644 --- a/src/GUI/CMakeLists.txt +++ b/src/GUI/CMakeLists.txt @@ -124,6 +124,8 @@ if (HAVE_QT) RecentAircraftModel.hxx RecentLocationsModel.cxx RecentLocationsModel.hxx + LauncherController.cxx + LauncherController.hxx ${uic_sources} ${qrc_sources} ${qml_sources}) diff --git a/src/GUI/LauncherController.cxx b/src/GUI/LauncherController.cxx new file mode 100644 index 000000000..9cfbfb563 --- /dev/null +++ b/src/GUI/LauncherController.cxx @@ -0,0 +1,645 @@ +#include "LauncherController.hxx" + +// Qt headers +#include +#include +#include +#include +#include +#include +#include +#include + +// simgear headers +#include +#include +#include + +// FlightGear headers +#include +#include
+#include +#include
+#include
+#include
+#include "version.h" + +#include "QtLauncher.hxx" +#include "QmlAircraftInfo.hxx" +#include "LauncherArgumentTokenizer.hxx" +#include "PathUrlHelper.hxx" +#include "PopupWindowTracker.hxx" +#include "RecentAircraftModel.hxx" +#include "RecentLocationsModel.hxx" +#include "ThumbnailImageItem.hxx" +#include "PreviewImageItem.hxx" +#include "FlickableExtentQuery.hxx" +#include "MPServersModel.h" +#include "AircraftSearchFilterModel.hxx" +#include "DefaultAircraftLocator.hxx" +#include "LaunchConfig.hxx" +#include "AircraftModel.hxx" + +// remove me once location widget is ported to Quick +#include "LocationWidget.hxx" +#include "PathsDialog.hxx" + +using namespace simgear::pkg; + + +LauncherController::LauncherController(QObject *parent, + LocationWidget* loc) : + QObject(parent), + m_locationWidget_FIXME(loc) +{ + m_serversModel = new MPServersModel(this); + m_locationHistory = new RecentLocationsModel(this); + m_selectedAircraftInfo = new QmlAircraftInfo(this); + + m_config = new LaunchConfig(this); + connect(m_config, &LaunchConfig::collect, this, &LauncherController::collectAircraftArgs); + + connect(m_locationWidget_FIXME, &LocationWidget::descriptionChanged, + this, &LauncherController::summaryChanged); + + initQML(); + + m_aircraftModel = new AircraftItemModel(this); + m_installedAircraftModel = new AircraftProxyModel(this, m_aircraftModel); + m_installedAircraftModel->setInstalledFilterEnabled(true); + + m_browseAircraftModel = new AircraftProxyModel(this, m_aircraftModel); + m_browseAircraftModel->setRatingFilterEnabled(true); + + m_aircraftSearchModel = new AircraftProxyModel(this, m_aircraftModel); + + m_aircraftHistory = new RecentAircraftModel(m_aircraftModel, this); + + connect(m_aircraftModel, &AircraftItemModel::aircraftInstallCompleted, + this, &LauncherController::onAircraftInstalledCompleted); + connect(m_aircraftModel, &AircraftItemModel::aircraftInstallFailed, + this, &LauncherController::onAircraftInstallFailed); + + connect(LocalAircraftCache::instance(), + &LocalAircraftCache::scanCompleted, + this, &LauncherController::updateSelectedAircraft); + + QSettings settings; + LocalAircraftCache::instance()->setPaths(settings.value("aircraft-paths").toStringList()); + LocalAircraftCache::instance()->scanDirs(); + m_aircraftModel->setPackageRoot(globals->packageRoot()); + +} + +void LauncherController::initQML() +{ + qmlRegisterType("FlightGear.Launcher", 1, 0, "ArgumentTokenizer"); + 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, "LaunchConfig", "Singleton API"); + qmlRegisterUncreatableType("FlightGear.Launcher", 1, 0, "MPServers", "Singleton API"); + + qmlRegisterType("FlightGear.Launcher", 1, 0, "FileDialog"); + qmlRegisterType("FlightGear.Launcher", 1, 0, "FlickableExtentQuery"); + qmlRegisterType("FlightGear.Launcher", 1, 0, "AircraftInfo"); + qmlRegisterType("FlightGear.Launcher", 1, 0, "PopupWindowTracker"); + + qmlRegisterUncreatableType("FlightGear.Launcher", 1, 0, "LocalAircraftCache", "Aircraft cache"); + qmlRegisterUncreatableType("FlightGear.Launcher", 1, 0, "AircraftModel", "Built-in model"); + qmlRegisterType("FlightGear.Launcher", 1, 0, "ThumbnailImage"); + qmlRegisterType("FlightGear.Launcher", 1, 0, "PreviewImage"); + + QNetworkDiskCache* diskCache = new QNetworkDiskCache(this); + SGPath cachePath = globals->get_fg_home() / "PreviewsCache"; + diskCache->setCacheDirectory(QString::fromStdString(cachePath.utf8Str())); + + QNetworkAccessManager* netAccess = new QNetworkAccessManager(this); + netAccess->setCache(diskCache); + PreviewImageItem::setGlobalNetworkAccess(netAccess); +} + +void LauncherController::restoreSettings() +{ + m_selectedAircraft = m_aircraftHistory->mostRecent(); + if (m_selectedAircraft.isEmpty()) { + // select the default aircraft specified in defaults.xml + flightgear::DefaultAircraftLocator da; + if (da.foundPath().exists()) { + m_selectedAircraft = QUrl::fromLocalFile(QString::fromStdString(da.foundPath().utf8Str())); + qDebug() << "Restored default aircraft:" << m_selectedAircraft; + } else { + qWarning() << "Completely failed to find the default aircraft"; + } + } + + + m_locationWidget_FIXME->restoreSettings(); + 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"; + } // otherwise we failed to find the default airport in the nav-db :( + } + m_locationWidget_FIXME->restoreLocation(currentLocation); + + updateSelectedAircraft(); + m_serversModel->requestRestore(); + + emit summaryChanged(); + emit showNoOfficialHangarChanged(); + } + +void LauncherController::saveSettings() +{ + emit requestSaveState(); + + m_aircraftHistory->saveToSettings(); + m_locationHistory->saveToSettings(); +} + +void LauncherController::collectAircraftArgs() +{ + // aircraft + if (!m_selectedAircraft.isEmpty()) { + if (m_selectedAircraft.isLocalFile()) { + QFileInfo setFileInfo(m_selectedAircraft.toLocalFile()); + m_config->setArg("aircraft-dir", setFileInfo.dir().absolutePath()); + QString setFile = setFileInfo.fileName(); + Q_ASSERT(setFile.endsWith("-set.xml")); + setFile.truncate(setFile.count() - 8); // drop the '-set.xml' portion + m_config->setArg("aircraft", setFile); + } else if (m_selectedAircraft.scheme() == "package") { + // no need to set aircraft-dir, handled by the corresponding code + // in fgInitAircraft + m_config->setArg("aircraft", m_selectedAircraft.path()); + } else { + qWarning() << "unsupported aircraft launch URL" << m_selectedAircraft; + } + } + + // scenery paths + QSettings settings; + Q_FOREACH(QString path, settings.value("scenery-paths").toStringList()) { + m_config->setArg("fg-scenery", path); + } + + // aircraft paths + Q_FOREACH(QString path, settings.value("aircraft-paths").toStringList()) { + m_config->setArg("fg-aircraft", path); + } +} + +void LauncherController::doRun() +{ + flightgear::Options* opt = flightgear::Options::sharedInstance(); + m_config->reset(); + m_config->collect(); + + m_aircraftHistory->insert(m_selectedAircraft); + + QVariant locSet = m_locationWidget_FIXME->saveLocation(); + m_locationHistory->insert(locSet); + + // aircraft paths + QSettings settings; + QString downloadDir = settings.value("downloadSettings/downloadDir").toString(); + if (!downloadDir.isEmpty()) { + QDir d(downloadDir); + if (!d.exists()) { + int result = QMessageBox::question(nullptr, tr("Create download folder?"), + tr("The selected location for downloads does not exist. (%1) Create it?").arg(downloadDir), + QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel); + if (result == QMessageBox::Cancel) { + return; + } + + if (result == QMessageBox::Yes) { + d.mkpath(downloadDir); + } + } + } + + if (settings.contains("restore-defaults-on-run")) { + settings.remove("restore-defaults-on-run"); + opt->addOption("restore-defaults", ""); + } + + m_config->applyToOptions(); + saveSettings(); +} + +void LauncherController::doApply() +{ + // aircraft + if (!m_selectedAircraft.isEmpty()) { + std::string aircraftPropValue, + aircraftDir; + + if (m_selectedAircraft.isLocalFile()) { + QFileInfo setFileInfo(m_selectedAircraft.toLocalFile()); + QString setFile = setFileInfo.fileName(); + Q_ASSERT(setFile.endsWith("-set.xml")); + setFile.truncate(setFile.count() - 8); // drop the '-set.xml' portion + aircraftDir = setFileInfo.dir().absolutePath().toStdString(); + aircraftPropValue = setFile.toStdString(); + } else if (m_selectedAircraft.scheme() == "package") { + // no need to set aircraft-dir, handled by the corresponding code + // in fgInitAircraft + aircraftPropValue = m_selectedAircraft.path().toStdString(); + } else { + qWarning() << "unsupported aircraft launch URL" << m_selectedAircraft; + } + + m_aircraftHistory->insert(m_selectedAircraft); + globals->get_props()->setStringValue("/sim/aircraft", aircraftPropValue); + globals->get_props()->setStringValue("/sim/aircraft-dir", aircraftDir); + } + + // location + m_locationWidget_FIXME->setLocationProperties(); + saveSettings(); +} + + +QString LauncherController::selectAircraftStateAutomatically() +{ + if (m_locationWidget_FIXME->isAirborneLocation()) { + return "approach"; + } + + if (m_locationWidget_FIXME->isParkedLocation()) { + return "parked"; + } else { + return "take-off"; + } + + return {}; // failed to compute, give up +} + +void LauncherController::maybeUpdateSelectedAircraft(QModelIndex index) +{ + QUrl u = index.data(AircraftURIRole).toUrl(); + if (u == m_selectedAircraft) { + // potentially enable the run button now! + updateSelectedAircraft(); + } +} + +void LauncherController::updateSelectedAircraft() +{ + m_selectedAircraftInfo->setUri(m_selectedAircraft); + QModelIndex index = m_aircraftModel->indexOfAircraftURI(m_selectedAircraft); + if (index.isValid()) { + LauncherAircraftType aircraftType = Airplane; + if (index.data(AircraftIsHelicopterRole).toBool()) { + aircraftType = Helicopter; + } else if (index.data(AircraftIsSeaplaneRole).toBool()) { + aircraftType = Seaplane; + } + + m_locationWidget_FIXME->setAircraftType(aircraftType); + } + + emit canFlyChanged(); +} + +bool LauncherController::canFly() const +{ + QModelIndex index = m_aircraftModel->indexOfAircraftURI(m_selectedAircraft); + if (!index.isValid()) { + return false; + } + + int status = index.data(AircraftPackageStatusRole).toInt(); + bool canRun = (status == LocalAircraftCache::PackageInstalled); + return canRun; +} + +void LauncherController::downloadDirChanged(QString path) +{ + // this can get run if the UI is disabled, just bail out before doing + // anything permanent. + if (!m_config->enableDownloadDirUI()) { + return; + } + + // if the default dir is passed in, map that back to the emptru string + if (path == m_config->defaultDownloadDir()) { + path.clear();; + } + + auto options = flightgear::Options::sharedInstance(); + if (options->valueForOption("download-dir") == path.toStdString()) { + // this works because we propogate the value from QSettings to + // the flightgear::Options object in runLauncherDialog() + // so the options object always contains our current idea of this + // value + return; + } + + if (!path.isEmpty()) { + options->setOption("download-dir", path.toStdString()); + } else { + options->clearOption("download-dir"); + } + + // replace existing package root + globals->get_subsystem()->shutdown(); + globals->setPackageRoot(simgear::pkg::RootRef()); + + // create new root with updated download-dir value + fgInitPackageRoot(); + + globals->get_subsystem()->init(); + + QSettings settings; + // re-scan the aircraft list + m_aircraftModel->setPackageRoot(globals->packageRoot()); + + auto aircraftCache = LocalAircraftCache::instance(); + aircraftCache->setPaths(settings.value("aircraft-paths").toStringList()); + aircraftCache->scanDirs(); + + emit showNoOfficialHangarChanged(); + + // re-set scenery dirs + setSceneryPaths(); +} + +bool LauncherController::shouldShowOfficialCatalogMessage() const +{ + QSettings settings; + bool showOfficialCatalogMesssage = !globals->get_subsystem()->isDefaultCatalogInstalled(); + if (settings.value("hide-official-catalog-message").toBool()) { + showOfficialCatalogMesssage = false; + } + return showOfficialCatalogMesssage; +} + +QmlAircraftInfo *LauncherController::selectedAircraftInfo() const +{ + return m_selectedAircraftInfo; +} + +void LauncherController::restoreLocation(QVariant var) +{ + m_locationWidget_FIXME->restoreLocation(var.toMap()); +} + + +QUrl LauncherController::selectedAircraft() const +{ + return m_selectedAircraft; +} + +bool LauncherController::matchesSearch(QString term, QStringList keywords) const +{ + Q_FOREACH(QString s, keywords) { + if (s.contains(term, Qt::CaseInsensitive)) { + return true; + } + } + + return false; +} + +bool LauncherController::isSearchActive() const +{ + return !m_settingsSearchTerm.isEmpty(); +} + +QStringList LauncherController::settingsSummary() const +{ + return m_settingsSummary; +} + +QStringList LauncherController::environmentSummary() const +{ + return m_environmentSummary; +} + + +void LauncherController::setSelectedAircraft(QUrl selectedAircraft) +{ + if (m_selectedAircraft == selectedAircraft) + return; + + m_selectedAircraft = selectedAircraft; + updateSelectedAircraft(); + emit selectedAircraftChanged(m_selectedAircraft); +} + +void LauncherController::setSettingsSearchTerm(QString settingsSearchTerm) +{ + if (m_settingsSearchTerm == settingsSearchTerm) + return; + + m_settingsSearchTerm = settingsSearchTerm; + emit searchChanged(); +} + +void LauncherController::setSettingsSummary(QStringList settingsSummary) +{ + if (m_settingsSummary == settingsSummary) + return; + + m_settingsSummary = settingsSummary; + emit summaryChanged(); +} + +void LauncherController::setEnvironmentSummary(QStringList environmentSummary) +{ + if (m_environmentSummary == environmentSummary) + return; + + m_environmentSummary = environmentSummary; + emit summaryChanged(); +} + +QStringList LauncherController::combinedSummary() const +{ + return m_settingsSummary + m_environmentSummary; +} + +QString LauncherController::locationDescription() const +{ + return m_locationWidget_FIXME->locationDescription(); +} + +simgear::pkg::PackageRef LauncherController::packageForAircraftURI(QUrl uri) const +{ + if (uri.scheme() != "package") { + qWarning() << "invalid URL scheme:" << uri; + return simgear::pkg::PackageRef(); + } + + QString ident = uri.path(); + return globals->packageRoot()->getPackageById(ident.toStdString()); +} + +void LauncherController::onAircraftPathsChanged() +{ + QSettings settings; + auto aircraftCache = LocalAircraftCache::instance(); + aircraftCache->setPaths(settings.value("aircraft-paths").toStringList()); + aircraftCache->scanDirs(); +} + +bool LauncherController::validateMetarString(QString metar) +{ + if (metar.isEmpty()) { + return true; + } + + try { + std::string s = metar.toStdString(); + SGMetar theMetar(s); + } catch (sg_io_exception&) { + return false; + } + + return true; +} + +void LauncherController::requestInstallUpdate(QUrl aircraftUri) +{ + // also select, otherwise UI is confusing + m_selectedAircraft = aircraftUri; + updateSelectedAircraft(); + + simgear::pkg::PackageRef pref = packageForAircraftURI(aircraftUri); + if (pref) { + if (pref->isInstalled()) { + InstallRef install = pref->existingInstall(); + if (install->hasUpdate()) { + globals->packageRoot()->scheduleToUpdate(install); + } + } else { + pref->install(); + } + } +} + +void LauncherController::requestUninstall(QUrl aircraftUri) +{ + simgear::pkg::PackageRef pref = packageForAircraftURI(aircraftUri); + if (pref) { + simgear::pkg::InstallRef i = pref->existingInstall(); + if (i) { + i->uninstall(); + } + } +} + +void LauncherController::requestInstallCancel(QUrl aircraftUri) +{ + simgear::pkg::PackageRef pref = packageForAircraftURI(aircraftUri); + if (pref) { + simgear::pkg::InstallRef i = pref->existingInstall(); + if (i) { + i->cancelDownload(); + } + } +} + +void LauncherController::requestUpdateAllAircraft() +{ + const PackageList& toBeUpdated = globals->packageRoot()->packagesNeedingUpdate(); + std::for_each(toBeUpdated.begin(), toBeUpdated.end(), [](PackageRef pkg) { + globals->packageRoot()->scheduleToUpdate(pkg->install()); + }); +} + +void LauncherController::queryMPServers() +{ + m_serversModel->refresh(); +} + +bool LauncherController::showNoOfficialHanger() const +{ + return shouldShowOfficialCatalogMessage(); +} + +QString LauncherController::versionString() const +{ + return FLIGHTGEAR_VERSION; +} + +RecentAircraftModel *LauncherController::aircraftHistory() +{ + return m_aircraftHistory; +} + +RecentLocationsModel *LauncherController::locationHistory() +{ + return m_locationHistory; +} + +void LauncherController::launchUrl(QUrl url) +{ + QDesktopServices::openUrl(url); +} + +QVariantList LauncherController::defaultSplashUrls() const +{ + QVariantList urls; + + for (auto path : flightgear::defaultSplashScreenPaths()) { + QUrl url = QUrl::fromLocalFile(QString::fromStdString(path)); + urls.append(url); + } + + return urls; +} + +void LauncherController::onAircraftInstalledCompleted(QModelIndex index) +{ + maybeUpdateSelectedAircraft(index); +} + +void LauncherController::onAircraftInstallFailed(QModelIndex index, QString errorMessage) +{ + qWarning() << Q_FUNC_INFO << index.data(AircraftURIRole) << errorMessage; + + QMessageBox msg; + msg.setWindowTitle(tr("Aircraft installation failed")); + msg.setText(tr("An error occurred installing the aircraft %1: %2"). + arg(index.data(Qt::DisplayRole).toString()).arg(errorMessage)); + msg.addButton(QMessageBox::Ok); + msg.exec(); + + maybeUpdateSelectedAircraft(index); +} + + +void LauncherController::setSceneryPaths() +{ + flightgear::launcherSetSceneryPaths(); +} + +void LauncherController::officialCatalogAction(QString s) +{ + if (s == "hide") { + QSettings settings; + settings.setValue("hide-official-catalog-message", true); + } else if (s == "add-official") { + // TEMPROARY, FIXME + AddOnsPage::addDefaultCatalog(nullptr, false /* not silent */); + } + + emit showNoOfficialHangarChanged(); +} + + +QPointF LauncherController::mapToGlobal(QQuickItem *item, const QPointF &pos) const +{ + QPointF scenePos = item->mapToScene(pos); + QQuickWindow* win = item->window(); + return win->mapToGlobal(scenePos.toPoint()); +} diff --git a/src/GUI/LauncherController.hxx b/src/GUI/LauncherController.hxx new file mode 100644 index 000000000..8450a10e0 --- /dev/null +++ b/src/GUI/LauncherController.hxx @@ -0,0 +1,224 @@ +// LauncherController.hxx - GUI launcher dialog using Qt5 +// +// Written by James Turner, started March 2018. +// +// Copyright (C) 2018 James Turner +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation; either version 2 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +#ifndef LAUNCHERCONTROLLER_HXX +#define LAUNCHERCONTROLLER_HXX + +#include +#include +#include + +#include +#include + +// forward decls +class AircraftProxyModel; +class QmlAircraftInfo; +class RecentAircraftModel; +class RecentLocationsModel; +class MPServersModel; +class AircraftItemModel; +class LocationWidget; +class QQuickItem; +class LaunchConfig; + +class LauncherController : public QObject +{ + Q_OBJECT + + Q_PROPERTY(bool showNoOfficialHanger READ showNoOfficialHanger NOTIFY showNoOfficialHangarChanged) + + Q_PROPERTY(AircraftProxyModel* installedAircraftModel MEMBER m_installedAircraftModel CONSTANT) + Q_PROPERTY(AircraftProxyModel* browseAircraftModel MEMBER m_browseAircraftModel CONSTANT) + Q_PROPERTY(AircraftProxyModel* searchAircraftModel MEMBER m_aircraftSearchModel CONSTANT) + + Q_PROPERTY(AircraftItemModel* baseAircraftModel MEMBER m_aircraftModel CONSTANT) + + Q_PROPERTY(MPServersModel* mpServersModel MEMBER m_serversModel CONSTANT) + + Q_PROPERTY(QUrl selectedAircraft READ selectedAircraft WRITE setSelectedAircraft NOTIFY selectedAircraftChanged) + + Q_PROPERTY(QmlAircraftInfo* selectedAircraftInfo READ selectedAircraftInfo NOTIFY selectedAircraftChanged) + + Q_PROPERTY(bool isSearchActive READ isSearchActive NOTIFY searchChanged) + Q_PROPERTY(QString settingsSearchTerm READ settingsSearchTerm WRITE setSettingsSearchTerm NOTIFY searchChanged) + + 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) + + Q_PROPERTY(bool canFly READ canFly NOTIFY canFlyChanged) +public: + explicit LauncherController(QObject *parent, + LocationWidget* loc); + + void initQML(); + + Q_INVOKABLE bool validateMetarString(QString metar); + + Q_INVOKABLE void requestInstallUpdate(QUrl aircraftUri); + + Q_INVOKABLE void requestUninstall(QUrl aircraftUri); + + Q_INVOKABLE void requestInstallCancel(QUrl aircraftUri); + + Q_INVOKABLE void downloadDirChanged(QString path); + + Q_INVOKABLE void requestUpdateAllAircraft(); + + Q_INVOKABLE void queryMPServers(); + + bool showNoOfficialHanger() const; + + Q_INVOKABLE void officialCatalogAction(QString s); + + QUrl selectedAircraft() const; + + // work around the fact, that this is not available on QQuickItem until 5.7 + Q_INVOKABLE QPointF mapToGlobal(QQuickItem* item, const QPointF& pos) const; + + QmlAircraftInfo* selectedAircraftInfo() const; + + Q_INVOKABLE void restoreLocation(QVariant var); + + Q_INVOKABLE bool matchesSearch(QString term, QStringList keywords) const; + + bool isSearchActive() const; + + QString settingsSearchTerm() const + { + return m_settingsSearchTerm; + } + + QStringList settingsSummary() const; + + 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(); + + LaunchConfig* config() const + { return m_config; } + + void doRun(); + void doApply(); + + bool canFly() const; + + void setSceneryPaths(); + + AircraftItemModel* baseAircraftModel() const + { return m_aircraftModel; } + + void restoreSettings(); + void saveSettings(); +signals: + + void showNoOfficialHangarChanged(); + void selectedAircraftChanged(QUrl selectedAircraft); + + void searchChanged(); + void summaryChanged(); + + void canFlyChanged(); + + /** + * @brief requestSaveState - signal to request QML settings to save their + * state to persistent storage + */ + void requestSaveState(); +public slots: + void setSelectedAircraft(QUrl selectedAircraft); + + void setSettingsSearchTerm(QString settingsSearchTerm); + + void setSettingsSummary(QStringList settingsSummary); + + void setEnvironmentSummary(QStringList environmentSummary); + + void onAircraftPathsChanged(); + +private slots: + + void onAircraftInstalledCompleted(QModelIndex index); + void onAircraftInstallFailed(QModelIndex index, QString errorMessage); + +private: + /** + * Check if the passed index is the selected aircraft, and if so, refresh + * the associated UI data + */ + void maybeUpdateSelectedAircraft(QModelIndex index); + void updateSelectedAircraft(); + + + simgear::pkg::PackageRef packageForAircraftURI(QUrl uri) const; + + // need to wait after a model reset before restoring selection and + // scrolling, to give the view time it seems. + void delayedAircraftModelReset(); + + bool shouldShowOfficialCatalogMessage() const; + + void collectAircraftArgs(); + +private: + AircraftProxyModel* m_installedAircraftModel; + AircraftItemModel* m_aircraftModel; + AircraftProxyModel* m_aircraftSearchModel; + AircraftProxyModel* m_browseAircraftModel; + MPServersModel* m_serversModel = nullptr; + + QUrl m_selectedAircraft; + int m_ratingFilters[4] = {3, 3, 3, 3}; + LaunchConfig* m_config = nullptr; + QmlAircraftInfo* m_selectedAircraftInfo = nullptr; + QString m_settingsSearchTerm; + QStringList m_settingsSummary, m_environmentSummary; + RecentAircraftModel* m_aircraftHistory = nullptr; + RecentLocationsModel* m_locationHistory = nullptr; + + LocationWidget* m_locationWidget_FIXME = nullptr; +}; + +#endif // LAUNCHERCONTROLLER_HXX diff --git a/src/GUI/LauncherMainWindow.cxx b/src/GUI/LauncherMainWindow.cxx index 363c5e7bd..0a4f85b16 100644 --- a/src/GUI/LauncherMainWindow.cxx +++ b/src/GUI/LauncherMainWindow.cxx @@ -7,8 +7,7 @@ #include #include #include -#include -#include + #include #include #include @@ -35,27 +34,16 @@ // launcher headers #include "QtLauncher.hxx" -#include "AircraftModel.hxx" #include "PathsDialog.hxx" -#include "AircraftSearchFilterModel.hxx" -#include "DefaultAircraftLocator.hxx" -#include "LaunchConfig.hxx" + #include "ViewCommandLinePage.hxx" -#include "MPServersModel.h" -#include "ThumbnailImageItem.hxx" -#include "PreviewImageItem.hxx" -#include "FlickableExtentQuery.hxx" +#include "AircraftModel.hxx" #include "LocalAircraftCache.hxx" -#include "QmlAircraftInfo.hxx" -#include "LauncherArgumentTokenizer.hxx" -#include "PathUrlHelper.hxx" -#include "PopupWindowTracker.hxx" -#include "RecentAircraftModel.hxx" -#include "RecentLocationsModel.hxx" +#include "LauncherController.hxx" +#include "DefaultAircraftLocator.hxx" #include "ui_Launcher.h" -using namespace simgear::pkg; extern void restartTheApp(QStringList fgArgs); @@ -78,6 +66,14 @@ LauncherMainWindow::LauncherMainWindow() : #endif + m_controller = new LauncherController(this, m_ui->location); + m_controller->initQML(); + + connect(m_controller, &LauncherController::canFlyChanged, + this, &LauncherMainWindow::onCanFlyChanged); + + m_ui->location->setLaunchConfig(m_controller->config()); + QMenu* toolsMenu = mb->addMenu(tr("Tools")); QAction* restoreDefaultsAction = toolsMenu->addAction(tr("Restore defaults...")); connect(restoreDefaultsAction, &QAction::triggered, @@ -91,12 +87,6 @@ LauncherMainWindow::LauncherMainWindow() : connect(viewCommandLineAction, &QAction::triggered, this, &LauncherMainWindow::onViewCommandLine); - m_serversModel = new MPServersModel(this); - - m_locationHistory = new RecentLocationsModel(this); - - m_selectedAircraftInfo = new QmlAircraftInfo(this); - initQML(); m_subsystemIdleTimer = new QTimer(this); m_subsystemIdleTimer->setInterval(0); @@ -112,64 +102,48 @@ LauncherMainWindow::LauncherMainWindow() : connect(m_ui->settingsButton, &QAbstractButton::clicked, this, &LauncherMainWindow::onClickToolboxButton); connect(m_ui->addOnsButton, &QAbstractButton::clicked, this, &LauncherMainWindow::onClickToolboxButton); - connect(m_ui->location, &LocationWidget::descriptionChanged, - this, &LauncherMainWindow::summaryChanged); - QAction* qa = new QAction(this); qa->setShortcut(QKeySequence("Ctrl+Q")); connect(qa, &QAction::triggered, this, &LauncherMainWindow::onQuit); addAction(qa); - m_aircraftModel = new AircraftItemModel(this); - m_installedAircraftModel = new AircraftProxyModel(this, m_aircraftModel); - m_installedAircraftModel->setInstalledFilterEnabled(true); - - m_browseAircraftModel = new AircraftProxyModel(this, m_aircraftModel); - m_browseAircraftModel->setRatingFilterEnabled(true); - - m_aircraftSearchModel = new AircraftProxyModel(this, m_aircraftModel); - - 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); AddOnsPage* addOnsPage = new AddOnsPage(NULL, globals->packageRoot()); connect(addOnsPage, &AddOnsPage::sceneryPathsChanged, - this, &LauncherMainWindow::setSceneryPaths); + m_controller, &LauncherController::setSceneryPaths); connect(addOnsPage, &AddOnsPage::aircraftPathsChanged, - this, &LauncherMainWindow::onAircraftPathsChanged); + m_controller, &LauncherController::onAircraftPathsChanged); m_ui->stack->addWidget(addOnsPage); - connect (m_aircraftModel, &AircraftItemModel::catalogsRefreshed, + connect(m_controller->baseAircraftModel(), &AircraftItemModel::catalogsRefreshed, addOnsPage, &AddOnsPage::onCatalogsRefreshed); - QSettings settings; - LocalAircraftCache::instance()->setPaths(settings.value("aircraft-paths").toStringList()); - LocalAircraftCache::instance()->scanDirs(); - m_aircraftModel->setPackageRoot(globals->packageRoot()); m_viewCommandLinePage = new ViewCommandLinePage; - m_viewCommandLinePage->setLaunchConfig(m_config); + m_viewCommandLinePage->setLaunchConfig(m_controller->config()); m_ui->stack->addWidget(m_viewCommandLinePage); + QSettings settings; + restoreGeometry(settings.value("window-geometry").toByteArray()); + + m_controller->restoreSettings(); restoreSettings(); - emit summaryChanged(); - emit showNoOfficialHangarChanged(); + //////////// +#if defined(Q_OS_WIN) + const QString osName("win"); +#elif defined(Q_OS_MAC) + const QString osName("mac"); +#else + const QString osName("unix"); +#endif ///////////// // 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()->rootContext()->setContextProperty("_launcher", m_controller); connect( m_ui->aircraftList, &QQuickWidget::statusChanged, this, &LauncherMainWindow::onQuickStatusChanged); @@ -177,8 +151,11 @@ LauncherMainWindow::LauncherMainWindow() : // settings m_ui->settings->engine()->addImportPath("qrc:///"); - m_ui->settings->engine()->rootContext()->setContextProperty("_launcher", this); - m_ui->settings->engine()->rootContext()->setContextProperty("_mpServers", m_serversModel); + QQmlContext* settingsContext = m_ui->settings->engine()->rootContext(); + settingsContext->setContextProperty("_launcher", m_controller); + settingsContext->setContextProperty("_osName", osName); + settingsContext->setContextProperty("_config", m_controller->config()); + m_ui->settings->setResizeMode(QQuickWidget::SizeRootObjectToView); connect( m_ui->settings, &QQuickWidget::statusChanged, this, &LauncherMainWindow::onQuickStatusChanged); @@ -186,10 +163,10 @@ LauncherMainWindow::LauncherMainWindow() : // environemnt m_ui->environmentPage->engine()->addImportPath("qrc:///"); - m_ui->environmentPage->engine()->rootContext()->setContextProperty("_launcher", this); + m_ui->environmentPage->engine()->rootContext()->setContextProperty("_launcher", m_controller); auto weatherScenariosModel = new flightgear::WeatherScenariosModel(this); m_ui->environmentPage->engine()->rootContext()->setContextProperty("_weatherScenarios", weatherScenariosModel); - m_ui->environmentPage->engine()->rootContext()->setContextProperty("_config", m_config); + m_ui->environmentPage->engine()->rootContext()->setContextProperty("_config", m_controller->config()); m_ui->environmentPage->setResizeMode(QQuickWidget::SizeRootObjectToView); @@ -199,7 +176,9 @@ LauncherMainWindow::LauncherMainWindow() : // summary m_ui->summary->engine()->addImportPath("qrc:///"); - m_ui->summary->engine()->rootContext()->setContextProperty("_launcher", this); + m_ui->summary->engine()->rootContext()->setContextProperty("_launcher", m_controller); + m_ui->summary->engine()->rootContext()->setContextProperty("_config", m_controller->config()); + m_ui->summary->setResizeMode(QQuickWidget::SizeRootObjectToView); connect( m_ui->summary, &QQuickWidget::statusChanged, @@ -209,52 +188,17 @@ LauncherMainWindow::LauncherMainWindow() : } -void LauncherMainWindow::initQML() +void LauncherMainWindow::restoreSettings() { - qmlRegisterType("FlightGear.Launcher", 1, 0, "ArgumentTokenizer"); - 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, "LaunchConfig", "Singleton API"); + if (!m_inAppMode) { + m_controller->setSceneryPaths(); + } +} - qmlRegisterType("FlightGear.Launcher", 1, 0, "FileDialog"); - qmlRegisterType("FlightGear.Launcher", 1, 0, "FlickableExtentQuery"); - qmlRegisterType("FlightGear.Launcher", 1, 0, "AircraftInfo"); - qmlRegisterType("FlightGear.Launcher", 1, 0, "PopupWindowTracker"); - - qmlRegisterUncreatableType("FlightGear.Launcher", 1, 0, "LocalAircraftCache", "Aircraft cache"); - qmlRegisterUncreatableType("FlightGear.Launcher", 1, 0, "AircraftModel", "Built-in model"); - qmlRegisterType("FlightGear.Launcher", 1, 0, "ThumbnailImage"); - qmlRegisterType("FlightGear.Launcher", 1, 0, "PreviewImage"); - - m_config = new LaunchConfig(this); - connect(m_config, &LaunchConfig::collect, this, &LauncherMainWindow::collectAircraftArgs); - m_ui->location->setLaunchConfig(m_config); - -#if defined(Q_OS_WIN) - const QString osName("win"); -#elif defined(Q_OS_MAC) - const QString osName("mac"); -#else - const QString osName("unix"); -#endif - - QQmlContext* settingsContext = m_ui->settings->engine()->rootContext(); - settingsContext->setContextProperty("_mpServers", m_serversModel); - settingsContext->setContextProperty("_config", m_config); - settingsContext->setContextProperty("_osName", osName); - - QQmlContext* summaryContext = m_ui->summary->engine()->rootContext(); - summaryContext->setContextProperty("_config", m_config); - - QNetworkDiskCache* diskCache = new QNetworkDiskCache(this); - SGPath cachePath = globals->get_fg_home() / "PreviewsCache"; - diskCache->setCacheDirectory(QString::fromStdString(cachePath.utf8Str())); - - QNetworkAccessManager* netAccess = new QNetworkAccessManager(this); - netAccess->setCache(diskCache); - PreviewImageItem::setGlobalNetworkAccess(netAccess); +void LauncherMainWindow::saveSettings() +{ + QSettings settings; + settings.setValue("window-geometry", saveGeometry()); } void LauncherMainWindow::onQuickStatusChanged(QQuickWidget::Status status) @@ -275,32 +219,13 @@ void LauncherMainWindow::onQuickStatusChanged(QQuickWidget::Status status) } } +void LauncherMainWindow::onCanFlyChanged() +{ + m_ui->flyButton->setEnabled(m_controller->canFly()); +} + LauncherMainWindow::~LauncherMainWindow() { - std::vector quickWidgets = { m_ui->aircraftList, - m_ui->settings, - m_ui->summary, - m_ui->environmentPage}; - - // candidate work-around for crash on deleting the launcher window - for (auto qw : quickWidgets) { - qw->setSource(QUrl{}); - } - -#if 1 - for (auto qw : quickWidgets) { - auto rootContext = qw->engine()->rootContext(); - rootContext->setContextProperty("_launcher", nullptr); - } -#endif - - // candidate work-around for crash on deleting the launcher window - // this un-parents the quick widgets so they won't be freed -#if 0 - for (auto qw : quickWidgets) { - qw->setParent(nullptr); - } -#endif } bool LauncherMainWindow::execInApp() @@ -321,180 +246,24 @@ bool LauncherMainWindow::execInApp() return m_accepted; } -void LauncherMainWindow::restoreSettings() -{ - QSettings settings; - restoreGeometry(settings.value("window-geometry").toByteArray()); - - m_selectedAircraft = m_aircraftHistory->mostRecent(); - if (m_selectedAircraft.isEmpty()) { - // select the default aircraft specified in defaults.xml - flightgear::DefaultAircraftLocator da; - if (da.foundPath().exists()) { - m_selectedAircraft = QUrl::fromLocalFile(QString::fromStdString(da.foundPath().utf8Str())); - qDebug() << "Restored default aircraft:" << m_selectedAircraft; - } else { - qWarning() << "Completely failed to find the default aircraft"; - } - } - - if (!m_inAppMode) { - setSceneryPaths(); - } - - m_ui->location->restoreSettings(); - 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"; - } // otherwise we failed to find the default airport in the nav-db :( - } - m_ui->location->restoreLocation(currentLocation); - - updateSelectedAircraft(); - m_serversModel->requestRestore(); - } - -void LauncherMainWindow::saveSettings() -{ - emit requestSaveState(); - - m_aircraftHistory->saveToSettings(); - m_locationHistory->saveToSettings(); - - QSettings settings; - settings.setValue("window-geometry", saveGeometry()); -} void LauncherMainWindow::closeEvent(QCloseEvent *event) { qApp->exit(-1); } -void LauncherMainWindow::collectAircraftArgs() -{ - // aircraft - if (!m_selectedAircraft.isEmpty()) { - if (m_selectedAircraft.isLocalFile()) { - QFileInfo setFileInfo(m_selectedAircraft.toLocalFile()); - m_config->setArg("aircraft-dir", setFileInfo.dir().absolutePath()); - QString setFile = setFileInfo.fileName(); - Q_ASSERT(setFile.endsWith("-set.xml")); - setFile.truncate(setFile.count() - 8); // drop the '-set.xml' portion - m_config->setArg("aircraft", setFile); - } else if (m_selectedAircraft.scheme() == "package") { - // no need to set aircraft-dir, handled by the corresponding code - // in fgInitAircraft - m_config->setArg("aircraft", m_selectedAircraft.path()); - } else { - qWarning() << "unsupported aircraft launch URL" << m_selectedAircraft; - } - } - - // scenery paths - QSettings settings; - Q_FOREACH(QString path, settings.value("scenery-paths").toStringList()) { - m_config->setArg("fg-scenery", path); - } - - // aircraft paths - Q_FOREACH(QString path, settings.value("aircraft-paths").toStringList()) { - m_config->setArg("fg-aircraft", path); - } -} void LauncherMainWindow::onRun() { - flightgear::Options* opt = flightgear::Options::sharedInstance(); - m_config->reset(); - m_config->collect(); - - m_aircraftHistory->insert(m_selectedAircraft); - - QVariant locSet = m_ui->location->saveLocation(); - m_locationHistory->insert(locSet); - - // aircraft paths - QSettings settings; - QString downloadDir = settings.value("downloadSettings/downloadDir").toString(); - if (!downloadDir.isEmpty()) { - QDir d(downloadDir); - if (!d.exists()) { - int result = QMessageBox::question(this, tr("Create download folder?"), - tr("The selected location for downloads does not exist. (%1) Create it?").arg(downloadDir), - QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel); - if (result == QMessageBox::Cancel) { - return; - } - - if (result == QMessageBox::Yes) { - d.mkpath(downloadDir); - } - } - } - - if (settings.contains("restore-defaults-on-run")) { - settings.remove("restore-defaults-on-run"); - opt->addOption("restore-defaults", ""); - } - - m_config->applyToOptions(); + m_controller->doRun(); saveSettings(); - - // set a positive value here so we can detect this case in runLauncherDialog qApp->exit(1); } -QString LauncherMainWindow::selectAircraftStateAutomatically() -{ - if (m_ui->location->isAirborneLocation()) { - return "approach"; - } - - if (m_ui->location->isParkedLocation()) { - return "parked"; - } else { - return "take-off"; - } - - return {}; // failed to compute, give up -} - void LauncherMainWindow::onApply() { - // aircraft - if (!m_selectedAircraft.isEmpty()) { - std::string aircraftPropValue, - aircraftDir; - - if (m_selectedAircraft.isLocalFile()) { - QFileInfo setFileInfo(m_selectedAircraft.toLocalFile()); - QString setFile = setFileInfo.fileName(); - Q_ASSERT(setFile.endsWith("-set.xml")); - setFile.truncate(setFile.count() - 8); // drop the '-set.xml' portion - aircraftDir = setFileInfo.dir().absolutePath().toStdString(); - aircraftPropValue = setFile.toStdString(); - } else if (m_selectedAircraft.scheme() == "package") { - // no need to set aircraft-dir, handled by the corresponding code - // in fgInitAircraft - aircraftPropValue = m_selectedAircraft.path().toStdString(); - } else { - qWarning() << "unsupported aircraft launch URL" << m_selectedAircraft; - } - - m_aircraftHistory->insert(m_selectedAircraft); - globals->get_props()->setStringValue("/sim/aircraft", aircraftPropValue); - globals->get_props()->setStringValue("/sim/aircraft-dir", aircraftDir); - } - - // location - m_ui->location->setLocationProperties(); - + m_controller->doApply(); saveSettings(); m_accepted = true; m_runInApp = false; @@ -509,25 +278,6 @@ void LauncherMainWindow::onQuit() } } -void LauncherMainWindow::onAircraftInstalledCompleted(QModelIndex index) -{ - maybeUpdateSelectedAircraft(index); -} - -void LauncherMainWindow::onAircraftInstallFailed(QModelIndex index, QString errorMessage) -{ - qWarning() << Q_FUNC_INFO << index.data(AircraftURIRole) << errorMessage; - - QMessageBox msg; - msg.setWindowTitle(tr("Aircraft installation failed")); - msg.setText(tr("An error occurred installing the aircraft %1: %2"). - arg(index.data(Qt::DisplayRole).toString()).arg(errorMessage)); - msg.addButton(QMessageBox::Ok); - msg.exec(); - - maybeUpdateSelectedAircraft(index); -} - void LauncherMainWindow::onRestoreDefaults() { QMessageBox mbox(this); @@ -561,36 +311,6 @@ void LauncherMainWindow::onViewCommandLine() m_viewCommandLinePage->update(); } -void LauncherMainWindow::maybeUpdateSelectedAircraft(QModelIndex index) -{ - QUrl u = index.data(AircraftURIRole).toUrl(); - if (u == m_selectedAircraft) { - // potentially enable the run button now! - updateSelectedAircraft(); - } -} - -void LauncherMainWindow::updateSelectedAircraft() -{ - m_selectedAircraftInfo->setUri(m_selectedAircraft); - QModelIndex index = m_aircraftModel->indexOfAircraftURI(m_selectedAircraft); - if (index.isValid()) { - int status = index.data(AircraftPackageStatusRole).toInt(); - bool canRun = (status == LocalAircraftCache::PackageInstalled); - m_ui->flyButton->setEnabled(canRun); - - LauncherAircraftType aircraftType = Airplane; - if (index.data(AircraftIsHelicopterRole).toBool()) { - aircraftType = Helicopter; - } else if (index.data(AircraftIsSeaplaneRole).toBool()) { - aircraftType = Seaplane; - } - - m_ui->location->setAircraftType(aircraftType); - } else { - m_ui->flyButton->setEnabled(false); - } -} void LauncherMainWindow::onClickToolboxButton() { @@ -602,202 +322,11 @@ void LauncherMainWindow::onClickToolboxButton() saveSettings(); } -void LauncherMainWindow::setSceneryPaths() -{ - flightgear::launcherSetSceneryPaths(); -} - void LauncherMainWindow::onSubsytemIdleTimeout() { globals->get_subsystem_mgr()->update(0.0); } -void LauncherMainWindow::downloadDirChanged(QString path) -{ - // this can get run if the UI is disabled, just bail out before doing - // anything permanent. - if (!m_config->enableDownloadDirUI()) { - return; - } - - // if the default dir is passed in, map that back to the emptru string - if (path == m_config->defaultDownloadDir()) { - path.clear();; - } - - auto options = flightgear::Options::sharedInstance(); - if (options->valueForOption("download-dir") == path.toStdString()) { - // this works because we propogate the value from QSettings to - // the flightgear::Options object in runLauncherDialog() - // so the options object always contains our current idea of this - // value - return; - } - - if (!path.isEmpty()) { - options->setOption("download-dir", path.toStdString()); - } else { - options->clearOption("download-dir"); - } - - // replace existing package root - globals->get_subsystem()->shutdown(); - globals->setPackageRoot(simgear::pkg::RootRef()); - - // create new root with updated download-dir value - fgInitPackageRoot(); - - globals->get_subsystem()->init(); - - QSettings settings; - // re-scan the aircraft list - m_aircraftModel->setPackageRoot(globals->packageRoot()); - - auto aircraftCache = LocalAircraftCache::instance(); - aircraftCache->setPaths(settings.value("aircraft-paths").toStringList()); - aircraftCache->scanDirs(); - - emit showNoOfficialHangarChanged(); - - // re-set scenery dirs - setSceneryPaths(); -} - -bool LauncherMainWindow::shouldShowOfficialCatalogMessage() const -{ - QSettings settings; - bool showOfficialCatalogMesssage = !globals->get_subsystem()->isDefaultCatalogInstalled(); - if (settings.value("hide-official-catalog-message").toBool()) { - showOfficialCatalogMesssage = false; - } - return showOfficialCatalogMesssage; -} - -void LauncherMainWindow::officialCatalogAction(QString s) -{ - if (s == "hide") { - QSettings settings; - settings.setValue("hide-official-catalog-message", true); - } else if (s == "add-official") { - AddOnsPage::addDefaultCatalog(this, false /* not silent */); - } - - emit showNoOfficialHangarChanged(); -} - -QUrl LauncherMainWindow::selectedAircraft() const -{ - return m_selectedAircraft; -} - -QPointF LauncherMainWindow::mapToGlobal(QQuickItem *item, const QPointF &pos) const -{ - QPointF scenePos = item->mapToScene(pos); - return m_ui->aircraftList->mapToGlobal(scenePos.toPoint()); -} - -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) { - if (s.contains(term, Qt::CaseInsensitive)) { - return true; - } - } - - return false; -} - -bool LauncherMainWindow::isSearchActive() const -{ - return !m_settingsSearchTerm.isEmpty(); -} - -QStringList LauncherMainWindow::settingsSummary() const -{ - return m_settingsSummary; -} - -QStringList LauncherMainWindow::environmentSummary() const -{ - return m_environmentSummary; -} - -void LauncherMainWindow::setSelectedAircraft(QUrl selectedAircraft) -{ - if (m_selectedAircraft == selectedAircraft) - return; - - m_selectedAircraft = selectedAircraft; - updateSelectedAircraft(); - emit selectedAircraftChanged(m_selectedAircraft); -} - -void LauncherMainWindow::setSettingsSearchTerm(QString settingsSearchTerm) -{ - if (m_settingsSearchTerm == settingsSearchTerm) - return; - - m_settingsSearchTerm = settingsSearchTerm; - emit searchChanged(); -} - -void LauncherMainWindow::setSettingsSummary(QStringList settingsSummary) -{ - if (m_settingsSummary == settingsSummary) - return; - - m_settingsSummary = settingsSummary; - emit summaryChanged(); -} - -void LauncherMainWindow::setEnvironmentSummary(QStringList environmentSummary) -{ - if (m_environmentSummary == environmentSummary) - return; - - m_environmentSummary = environmentSummary; - emit summaryChanged(); -} - -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 -{ - if (uri.scheme() != "package") { - qWarning() << "invalid URL scheme:" << uri; - return simgear::pkg::PackageRef(); - } - - QString ident = uri.path(); - return globals->packageRoot()->getPackageById(ident.toStdString()); -} - -void LauncherMainWindow::onAircraftPathsChanged() -{ - QSettings settings; - auto aircraftCache = LocalAircraftCache::instance(); - aircraftCache->setPaths(settings.value("aircraft-paths").toStringList()); - aircraftCache->scanDirs(); -} - void LauncherMainWindow::onChangeDataDir() { QString currentLocText; @@ -835,109 +364,4 @@ void LauncherMainWindow::onChangeDataDir() } -bool LauncherMainWindow::validateMetarString(QString metar) -{ - if (metar.isEmpty()) { - return true; - } - try { - std::string s = metar.toStdString(); - SGMetar theMetar(s); - } catch (sg_io_exception&) { - return false; - } - - return true; -} - -void LauncherMainWindow::requestInstallUpdate(QUrl aircraftUri) -{ - // also select, otherwise UI is confusing - m_selectedAircraft = aircraftUri; - updateSelectedAircraft(); - - simgear::pkg::PackageRef pref = packageForAircraftURI(aircraftUri); - if (pref) { - if (pref->isInstalled()) { - InstallRef install = pref->existingInstall(); - if (install->hasUpdate()) { - globals->packageRoot()->scheduleToUpdate(install); - } - } else { - pref->install(); - } - } -} - -void LauncherMainWindow::requestUninstall(QUrl aircraftUri) -{ - simgear::pkg::PackageRef pref = packageForAircraftURI(aircraftUri); - if (pref) { - simgear::pkg::InstallRef i = pref->existingInstall(); - if (i) { - i->uninstall(); - } - } -} - -void LauncherMainWindow::requestInstallCancel(QUrl aircraftUri) -{ - simgear::pkg::PackageRef pref = packageForAircraftURI(aircraftUri); - if (pref) { - simgear::pkg::InstallRef i = pref->existingInstall(); - if (i) { - i->cancelDownload(); - } - } -} - -void LauncherMainWindow::requestUpdateAllAircraft() -{ - const PackageList& toBeUpdated = globals->packageRoot()->packagesNeedingUpdate(); - std::for_each(toBeUpdated.begin(), toBeUpdated.end(), [](PackageRef pkg) { - globals->packageRoot()->scheduleToUpdate(pkg->install()); - }); -} - -void LauncherMainWindow::queryMPServers() -{ - m_serversModel->refresh(); -} - -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 ff7bf6728..23bd3b41f 100644 --- a/src/GUI/LauncherMainWindow.hxx +++ b/src/GUI/LauncherMainWindow.hxx @@ -29,61 +29,21 @@ #include #include -#include -#include - namespace Ui { class Launcher; } class QModelIndex; -class AircraftProxyModel; -class AircraftItemModel; -class QCheckBox; -class CatalogListModel; class QQmlEngine; class LaunchConfig; -class ExtraSettingsSection; class ViewCommandLinePage; -class MPServersModel; class QQuickItem; -class QmlAircraftInfo; -class QStandardItemModel; -class RecentAircraftModel; -class RecentLocationsModel; +class LauncherController; class LauncherMainWindow : public QMainWindow { Q_OBJECT - - Q_PROPERTY(bool showNoOfficialHanger READ showNoOfficialHanger NOTIFY showNoOfficialHangarChanged) - - Q_PROPERTY(AircraftProxyModel* installedAircraftModel MEMBER m_installedAircraftModel CONSTANT) - Q_PROPERTY(AircraftProxyModel* browseAircraftModel MEMBER m_browseAircraftModel CONSTANT) - Q_PROPERTY(AircraftProxyModel* searchAircraftModel MEMBER m_aircraftSearchModel CONSTANT) - - Q_PROPERTY(AircraftItemModel* baseAircraftModel MEMBER m_aircraftModel CONSTANT) - - Q_PROPERTY(QUrl selectedAircraft READ selectedAircraft WRITE setSelectedAircraft NOTIFY selectedAircraftChanged) - - - Q_PROPERTY(QmlAircraftInfo* selectedAircraftInfo READ selectedAircraftInfo NOTIFY selectedAircraftChanged) - - Q_PROPERTY(bool isSearchActive READ isSearchActive NOTIFY searchChanged) - Q_PROPERTY(QString settingsSearchTerm READ settingsSearchTerm WRITE setSettingsSearchTerm NOTIFY searchChanged) - - 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(); @@ -92,88 +52,7 @@ public: bool wasRejected(); - Q_INVOKABLE bool validateMetarString(QString metar); - Q_INVOKABLE void requestInstallUpdate(QUrl aircraftUri); - - Q_INVOKABLE void requestUninstall(QUrl aircraftUri); - - Q_INVOKABLE void requestInstallCancel(QUrl aircraftUri); - - Q_INVOKABLE void downloadDirChanged(QString path); - - Q_INVOKABLE void requestUpdateAllAircraft(); - - Q_INVOKABLE void queryMPServers(); - - bool showNoOfficialHanger() const; - - Q_INVOKABLE void officialCatalogAction(QString s); - - QUrl selectedAircraft() const; - - // work around the fact, that this is not available on QQuickItem until 5.7 - Q_INVOKABLE QPointF mapToGlobal(QQuickItem* item, const QPointF& pos) const; - - QmlAircraftInfo* selectedAircraftInfo() const; - - Q_INVOKABLE void restoreLocation(QVariant var); - - Q_INVOKABLE bool matchesSearch(QString term, QStringList keywords) const; - - bool isSearchActive() const; - - QString settingsSearchTerm() const - { - return m_settingsSearchTerm; - } - - QStringList settingsSummary() const; - - 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); - - void setSettingsSearchTerm(QString settingsSearchTerm); - - void setSettingsSummary(QStringList settingsSummary); - - void setEnvironmentSummary(QStringList environmentSummary); - -signals: - void showNoOfficialHangarChanged(); - - void selectedAircraftChanged(QUrl selectedAircraft); - - void searchChanged(); - - void summaryChanged(); - - /** - * @brief requestSaveState - signal to request QML settings to save their - * state to persistent storage - */ - void requestSaveState(); protected: virtual void closeEvent(QCloseEvent *event) override; @@ -190,64 +69,32 @@ private slots: void onSubsytemIdleTimeout(); - void onAircraftInstalledCompleted(QModelIndex index); - void onAircraftInstallFailed(QModelIndex index, QString errorMessage); void onRestoreDefaults(); void onViewCommandLine(); void onClickToolboxButton(); - void setSceneryPaths(); - void onAircraftPathsChanged(); - void onChangeDataDir(); + void onQuickStatusChanged(QQuickWidget::Status status); + + void onCanFlyChanged(); + + void onChangeDataDir(); + private: - /** - * Check if the passed index is the selected aircraft, and if so, refresh - * the associated UI data - */ - void maybeUpdateSelectedAircraft(QModelIndex index); - void updateSelectedAircraft(); - - void restoreSettings(); - void saveSettings(); - - simgear::pkg::PackageRef packageForAircraftURI(QUrl uri) const; - - // need to wait after a model reset before restoring selection and - // scrolling, to give the view time it seems. - void delayedAircraftModelReset(); - - bool shouldShowOfficialCatalogMessage() const; - - void collectAircraftArgs(); - void initQML(); - - + LauncherController* m_controller; QScopedPointer m_ui; - AircraftProxyModel* m_installedAircraftModel; - AircraftItemModel* m_aircraftModel; - AircraftProxyModel* m_aircraftSearchModel; - AircraftProxyModel* m_browseAircraftModel; - MPServersModel* m_serversModel = nullptr; - - QUrl m_selectedAircraft; QTimer* m_subsystemIdleTimer; bool m_inAppMode = false; bool m_runInApp = false; bool m_accepted = false; - int m_ratingFilters[4] = {3, 3, 3, 3}; - QQmlEngine* m_qmlEngine = nullptr; - LaunchConfig* m_config = nullptr; + ViewCommandLinePage* m_viewCommandLinePage = nullptr; - QmlAircraftInfo* m_selectedAircraftInfo = nullptr; - QString m_settingsSearchTerm; - QStringList m_settingsSummary, - m_environmentSummary; - RecentAircraftModel* m_aircraftHistory = nullptr; - RecentLocationsModel* m_locationHistory = nullptr; + + void restoreSettings(); + void saveSettings(); }; #endif // of LAUNCHER_MAIN_WINDOW_HXX diff --git a/src/GUI/qml/Settings.qml b/src/GUI/qml/Settings.qml index 202cc671b..453fe3579 100644 --- a/src/GUI/qml/Settings.qml +++ b/src/GUI/qml/Settings.qml @@ -186,9 +186,9 @@ Item { label: qsTr("Server") enabled: enableMP.checked description: qsTr("Select a server close to you for better responsiveness and reduced lag when flying online.") - choices: _mpServers + choices: _launcher.mpServersModel - readonly property bool currentIsCustom: (_mpServers.serverForIndex(selectedIndex) == "__custom__") + readonly property bool currentIsCustom: (_launcher.mpServersModel.serverForIndex(selectedIndex) == "__custom__") property string __savedServer; keywords: ["server", "hostname"] @@ -202,7 +202,7 @@ Item { { // these values match the code in MPServersModel.cpp, sorry for that // nastyness - _config.setValueForKey("mpSettings", "mp-server", _mpServers.serverForIndex(selectedIndex) ); + _config.setValueForKey("mpSettings", "mp-server", _launcher.mpServersModel.serverForIndex(selectedIndex) ); } function restoreState() @@ -212,7 +212,7 @@ Item { Connections { - target: _mpServers + target: _launcher.mpServersModel onRestoreIndex: { mpServer.selectedIndex = index } } },