FlightGear next
LauncherController.cxx
Go to the documentation of this file.
1#include "config.h"
2
4
5// Qt headers
6#include <QDebug>
7#include <QDesktopServices>
8#include <QFileDialog>
9#include <QJSEngine>
10#include <QMessageBox>
11#include <QNetworkAccessManager>
12#include <QNetworkDiskCache>
13#include <QPushButton>
14#include <QQmlComponent>
15#include <QQuickWindow>
16#include <QSettings>
17
18// simgear headers
19#include <simgear/package/Install.hxx>
20#include <simgear/environment/metar.hxx>
21#include <simgear/structure/exception.hxx>
22
23// FlightGear headers
25#include <Main/globals.hxx>
26#include <Airports/airport.hxx>
27#include <Main/options.hxx>
28#include <Main/fg_init.hxx>
29#include <Main/fg_props.hxx>
31
32#include "AircraftItemModel.hxx"
34#include "AirportDiagram.hxx"
35#include "CarrierDiagram.hxx"
40#include "GettingStartedTip.hxx"
42#include "HoverArea.hxx"
43#include "LaunchConfig.hxx"
47#include "MPServersModel.h"
49#include "NavaidDiagram.hxx"
50#include "NavaidSearchModel.hxx"
51#include "PathUrlHelper.hxx"
52#include "PixmapImageItem.hxx"
53#include "PreviewImageItem.hxx"
54#include "QmlAircraftInfo.hxx"
55#include "QmlPositioned.hxx"
58#include "QtLauncher.hxx"
61#include "RouteDiagram.hxx"
62#include "SetupRootDialog.hxx"
63#include "StackController.hxx"
65#include "TipBackgroundBox.hxx"
66#include "UnitsModel.hxx"
67#include "UpdateChecker.hxx"
68
69
70#include <flightgearBuildId.h>
71
72using namespace simgear::pkg;
73
74LauncherController::LauncherController(QObject *parent, QWindow* window) :
75 QObject(parent),
76 m_window(window)
77{
78 m_serversModel = new MPServersModel(this);
79 m_location = new LocationController(this);
80 m_locationHistory = new RecentLocationsModel(this);
81 m_selectedAircraftInfo = new QmlAircraftInfo(this);
82
83 m_config = new LaunchConfig(this);
84 connect(m_config, &LaunchConfig::collect, this, &LauncherController::collectAircraftArgs);
85 connect(m_config, &LaunchConfig::save, this, &LauncherController::saveAircraft);
86 connect(m_config, &LaunchConfig::restore, this, &LauncherController::restoreAircraft);
87
88 m_flightPlan = new FlightPlanController(this, m_config);
89
90 m_location->setLaunchConfig(m_config);
91 connect(m_location, &LocationController::descriptionChanged,
93
94 m_aircraftModel = new AircraftItemModel(this);
95 m_installedAircraftModel = new AircraftProxyModel(this, m_aircraftModel);
96 m_installedAircraftModel->setInstalledFilterEnabled(true);
97
98 m_aircraftWithUpdatesModel = new AircraftProxyModel(this, m_aircraftModel);
99 m_aircraftWithUpdatesModel->setInstalledFilterEnabled(true);
100 m_aircraftWithUpdatesModel->setHaveUpdateFilterEnabled(true);
101
102 m_browseAircraftModel = new AircraftProxyModel(this, m_aircraftModel);
103 m_browseAircraftModel->setRatingFilterEnabled(true);
104
105 m_aircraftSearchModel = new AircraftProxyModel(this, m_aircraftModel);
106
107 m_favouriteAircraftModel = new AircraftProxyModel(this, m_aircraftModel);
108 m_favouriteAircraftModel->setShowFavourites(true);
109
110 m_aircraftHistory = new RecentAircraftModel(m_aircraftModel, this);
111
112 connect(m_aircraftModel, &AircraftItemModel::aircraftInstallCompleted,
113 this, &LauncherController::onAircraftInstalledCompleted);
114 connect(m_aircraftModel, &AircraftItemModel::aircraftInstallFailed,
115 this, &LauncherController::onAircraftInstallFailed);
116
119 this, &LauncherController::updateSelectedAircraft);
120
121 QSettings settings;
122 m_aircraftModel->setPackageRoot(globals->packageRoot());
123
124 m_aircraftGridMode = settings.value("aircraftGridMode").toBool();
125
126 m_subsystemIdleTimer = new QTimer(this);
127 m_subsystemIdleTimer->setInterval(5);
128 connect(m_subsystemIdleTimer, &QTimer::timeout, []()
129 {globals->get_subsystem_mgr()->update(0.0);});
130 m_subsystemIdleTimer->start();
131
132 QRect winRect = settings.value("window-geometry").toRect();
133
134 if (winRect.isValid()) {
135 m_window->setGeometry(winRect);
136 } else {
137 m_window->setWidth(600);
138 m_window->setHeight(800);
139 }
140
141 if (settings.contains("window-state")) {
142 const auto ws = static_cast<Qt::WindowState>(settings.value("window-state").toInt());
144 if (ws == Qt::WindowState::WindowFullScreen && options->isBoolOptionDisable("fullscreen")) {
145 // Last time we used fullscreen, but now we explicitly turn off fullscreen
146 m_window->setWindowState(Qt::WindowState::WindowNoState);
147 }
148 else {
149 m_window->setWindowState(ws);
150 }
151 }
152
153 // count launches; we use this to trigger first-run and periodic notices
154 // in the UI.
155 m_launchCount = settings.value("launch-count", 0).toInt();
156 settings.setValue("launch-count", m_launchCount + 1);
157
158 std::ostringstream os;
159 string_list versionParts = simgear::strutils::split(FLIGHTGEAR_VERSION, ".");
160 if (versionParts.size() >= 2) {
161 // build a setting key like launch-count-2020-2
162 QString versionedCountKey = QString::fromStdString("launch-count-" + versionParts.at(0) + "-" + versionParts.at(1));
163 m_versionLaunchCount = settings.value(versionedCountKey, 0).toInt();
164 settings.setValue(versionedCountKey, m_versionLaunchCount + 1);
165 }
166
167 QTimer::singleShot(2000, this, &LauncherController::checkForOldDownloadDir);
168}
169
170void LauncherController::initQML(int& styleTypeId)
171{
172 qmlRegisterUncreatableType<LauncherController>("FlightGear.Launcher", 1, 0, "LauncherController", "no");
173 qmlRegisterUncreatableType<LocationController>("FlightGear.Launcher", 1, 0, "LocationController", "no");
174 qmlRegisterUncreatableType<FlightPlanController>("FlightGear.Launcher", 1, 0, "FlightPlanController", "no");
175 qmlRegisterUncreatableType<UpdateChecker>("FlightGear.Launcher", 1, 0, "UpdateChecker", "for enums");
176
177 qmlRegisterType<LauncherArgumentTokenizer>("FlightGear.Launcher", 1, 0, "ArgumentTokenizer");
178 qmlRegisterUncreatableType<QAbstractItemModel>("FlightGear.Launcher", 1, 0, "QAIM", "no");
179 qmlRegisterUncreatableType<AircraftProxyModel>("FlightGear.Launcher", 1, 0, "AircraftProxyModel", "no");
180 qmlRegisterUncreatableType<RecentAircraftModel>("FlightGear.Launcher", 1, 0, "RecentAircraftModel", "no");
181 qmlRegisterUncreatableType<RecentLocationsModel>("FlightGear.Launcher", 1, 0, "RecentLocationsModel", "no");
182 qmlRegisterUncreatableType<LaunchConfig>("FlightGear.Launcher", 1, 0, "LaunchConfig", "Singleton API");
183 qmlRegisterUncreatableType<MPServersModel>("FlightGear.Launcher", 1, 0, "MPServers", "Singleton API");
184
185 qmlRegisterType<NavaidSearchModel>("FlightGear", 1, 0, "NavaidSearch");
186 qmlRegisterType<CarriersLocationModel>("FlightGear", 1, 0, "CarriersModel");
187
188 qmlRegisterUncreatableType<Units>("FlightGear", 1, 0, "Units", "Only for enum");
189 qmlRegisterType<UnitsModel>("FlightGear", 1, 0, "UnitsModel");
190
191 qmlRegisterType<FileDialogWrapper>("FlightGear", 1, 0, "FileDialog");
192 qmlRegisterType<QmlAircraftInfo>("FlightGear.Launcher", 1, 0, "AircraftInfo");
193
194 qmlRegisterUncreatableType<LocalAircraftCache>("FlightGear.Launcher", 1, 0, "LocalAircraftCache", "Aircraft cache");
195 qmlRegisterUncreatableType<AircraftItemModel>("FlightGear.Launcher", 1, 0, "AircraftModel", "Built-in model");
196 qmlRegisterType<ThumbnailImageItem>("FlightGear.Launcher", 1, 0, "ThumbnailImage");
197 qmlRegisterType<PreviewImageItem>("FlightGear.Launcher", 1, 0, "PreviewImage");
198
199 qmlRegisterType<QmlPositioned>("FlightGear", 1, 0, "Positioned");
200 // this is a Q_GADGET, but we need to register it for use in return types, etc
201 qRegisterMetaType<QmlGeod>();
202
203 qmlRegisterType<PixmapImageItem>("FlightGear", 1, 0, "PixmapImage");
204 qmlRegisterType<AirportDiagram>("FlightGear", 1, 0, "AirportDiagram");
205 qmlRegisterType<CarrierDiagram>("FlightGear", 1, 0, "CarrierDiagram");
206 qmlRegisterType<NavaidDiagram>("FlightGear", 1, 0, "NavaidDiagram");
207 qmlRegisterType<RouteDiagram>("FlightGear", 1, 0, "RouteDiagram");
208 qmlRegisterType<QmlRadioButtonGroup>("FlightGear", 1, 0, "RadioButtonGroup");
209 qmlRegisterType<HoverArea>("FlightGear", 1, 0, "HoverArea");
210 qmlRegisterType<StackController>("FlightGear", 1, 0, "StackController");
211
212 qmlRegisterType<ModelDataExtractor>("FlightGear", 1, 0, "ModelDataExtractor");
213 qmlRegisterType<QmlStringListModel>("FlightGear", 1, 0, "StringListModel");
214
215
216 qmlRegisterSingletonType(QUrl("qrc:/qml/OverlayShared.qml"), "FlightGear", 1, 0, "OverlayShared");
217 styleTypeId = qmlRegisterSingletonType(QUrl("qrc:/qml/Style.qml"), "FlightGear", 1, 0, "Style");
218
219 qmlRegisterType<GettingStartedScope>("FlightGear", 1, 0, "GettingStartedScope");
220 qmlRegisterType<GettingStartedTipsController>("FlightGear", 1, 0, "GettingStartedController");
221 qmlRegisterType<GettingStartedTip>("FlightGear", 1, 0, "GettingStartedTip");
222 qmlRegisterType<TipBackgroundBox>("FlightGear", 1, 0, "TipBackgroundBox");
223
224 QNetworkDiskCache* diskCache = new QNetworkDiskCache(this);
225 SGPath cachePath = globals->get_fg_home() / "PreviewsCache";
226 diskCache->setCacheDirectory(QString::fromStdString(cachePath.utf8Str()));
227
228 QNetworkAccessManager* netAccess = new QNetworkAccessManager(this);
229 netAccess->setCache(diskCache);
231}
232
234{
235 m_inAppMode = true;
236 m_keepRunningInAppMode = true;
237 m_appModeResult = true;
238 emit inAppChanged();
239}
240
242{
243 return m_keepRunningInAppMode;
244}
245
247{
248 return m_appModeResult;
249}
250
252{
253 m_selectedAircraft = m_aircraftHistory->mostRecent();
254 if (m_selectedAircraft.isEmpty()) {
255 // select the default aircraft specified in defaults.xml
257 if (da.foundPath().exists()) {
258 m_selectedAircraft = QUrl::fromLocalFile(QString::fromStdString(da.foundPath().utf8Str()));
259 qDebug() << "Restored default aircraft:" << m_selectedAircraft;
260 } else {
261 qWarning() << "Completely failed to find the default aircraft";
262 }
263 }
264
265 m_location->restoreSearchHistory();
266 QVariantMap currentLocation = m_locationHistory->mostRecent();
267 if (currentLocation.isEmpty()) {
268 // use the default
269 std::string defaultAirport = flightgear::defaultAirportICAO();
270 FGAirportRef apt = FGAirport::findByIdent(defaultAirport);
271 if (apt) {
272 currentLocation["location-id"] = static_cast<qlonglong>(apt->guid());
273 currentLocation["location-apt-runway"] = "ACTIVE";
274 } // otherwise we failed to find the default airport in the nav-db :(
275 }
276 m_location->restoreLocation(currentLocation);
277
278 emit selectedAircraftChanged(m_selectedAircraft);
279
280 updateSelectedAircraft();
281 m_serversModel->requestRestore();
282 m_aircraftState = m_config->getValueForKey("", "selected-aircraft-state", QString()).toString();
284
285 emit summaryChanged();
286 }
287
289{
290 QSettings settings;
291 if (m_window->windowState() != Qt::WindowMaximized) {
292 settings.setValue("window-geometry", m_window->geometry());
293 }
294 settings.setValue("window-state", m_window->windowState());
295
296 m_config->saveConfigToINI();
297 m_aircraftHistory->saveToSettings();
298 m_locationHistory->saveToSettings();
299}
300
301void LauncherController::collectAircraftArgs()
302{
303 if (m_skipAircraftFromArgs)
304 return;
305
306 // aircraft
307 if (!m_selectedAircraft.isEmpty()) {
308 if (m_selectedAircraft.isLocalFile()) {
309 QFileInfo setFileInfo(m_selectedAircraft.toLocalFile());
310 m_config->setArg("aircraft-dir", setFileInfo.dir().absolutePath());
311 QString setFile = setFileInfo.fileName();
312 Q_ASSERT(setFile.endsWith("-set.xml"));
313 setFile.truncate(setFile.size() - 8); // drop the '-set.xml' portion
314 m_config->setArg("aircraft", setFile);
315 } else if (m_selectedAircraft.scheme() == "package") {
316 // no need to set aircraft-dir, handled by the corresponding code
317 // in fgInitAircraft
318 m_config->setArg("aircraft", m_selectedAircraft.path());
319 } else {
320 qWarning() << "unsupported aircraft launch URL" << m_selectedAircraft;
321 }
322 }
323
324 if (m_selectedAircraftInfo->hasStates() && !m_aircraftState.isEmpty()) {
325 QString state = m_aircraftState;
326 if ((m_aircraftState == "auto") && !m_selectedAircraftInfo->haveExplicitAutoState()) {
327 state = selectAircraftStateAutomatically();
328 }
329
330 if (!state.isEmpty()) {
331 m_config->setArg("state", state);
332 }
333 }
334}
335
336void LauncherController::saveAircraft()
337{
338 m_config->setValueForKey("", "selected-aircraft", m_selectedAircraft);
339 if (!m_aircraftState.isEmpty()) {
340 m_config->setValueForKey("", "selected-aircraft-state", m_aircraftState);
341 }
342}
343
344void LauncherController::restoreAircraft()
345{
346 m_selectedAircraft = m_config->getValueForKey("", "selected-aircraft", QUrl()).toUrl();
347 m_aircraftState = m_config->getValueForKey("", "selected-aircraft-state", QString()).toString();
348 emit selectedAircraftChanged(m_selectedAircraft);
349 updateSelectedAircraft();
351}
352
354{
355 flightgear::addSentryBreadcrumb("Launcher: fly!:" + m_selectedAircraft.toString().toStdString(), "info");
357 m_config->reset();
358 m_config->collect();
359
360 m_aircraftHistory->insert(m_selectedAircraft);
361
362 QVariant locSet = m_location->saveLocation();
363 m_locationHistory->insert(locSet);
364
365 flightgear::addSentryBreadcrumb("acft path:" + m_selectedAircraftInfo->pathOnDisk().toStdString(), "info");
366
367 // aircraft paths
368 QSettings settings;
369 QString downloadDir = settings.value("downloadSettings/downloadDir").toString();
370 if (!downloadDir.isEmpty()) {
371 QDir d(downloadDir);
372 if (!d.exists()) {
373 int result = QMessageBox::question(nullptr, tr("Create download folder?"),
374 tr("The selected location for downloads does not exist. (%1) Create it?").arg(downloadDir),
375 QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel);
376 if (result == QMessageBox::Cancel) {
377 return;
378 }
379
380 if (result == QMessageBox::Yes) {
381 d.mkpath(downloadDir);
382 }
383 }
384 }
385
386 if (settings.contains("restore-defaults-on-run")) {
387 settings.remove("restore-defaults-on-run");
388 opt->addOption("restore-defaults", "");
389 }
390
391 m_config->applyToOptions();
392 saveSettings();
393}
394
396{
397 // aircraft
398 if (!m_selectedAircraft.isEmpty()) {
399 std::string aircraftPropValue,
400 aircraftDir;
401
402 if (m_selectedAircraft.isLocalFile()) {
403 QFileInfo setFileInfo(m_selectedAircraft.toLocalFile());
404 QString setFile = setFileInfo.fileName();
405 Q_ASSERT(setFile.endsWith("-set.xml"));
406 setFile.truncate(setFile.size() - 8); // drop the '-set.xml' portion
407 aircraftDir = setFileInfo.dir().absolutePath().toStdString();
408 aircraftPropValue = setFile.toStdString();
409 } else if (m_selectedAircraft.scheme() == "package") {
410 // no need to set aircraft-dir, handled by the corresponding code
411 // in fgInitAircraft
412 aircraftPropValue = m_selectedAircraft.path().toStdString();
413 } else {
414 qWarning() << "unsupported aircraft launch URL" << m_selectedAircraft;
415 }
416
417 m_aircraftHistory->insert(m_selectedAircraft);
418 globals->get_props()->setStringValue("/sim/aircraft", aircraftPropValue);
419 globals->get_props()->setStringValue("/sim/aircraft-dir", aircraftDir);
420 }
421
422 m_location->setLocationProperties();
423 saveSettings();
424}
425
426
427QString LauncherController::selectAircraftStateAutomatically()
428{
429 if (!m_selectedAircraftInfo)
430 return {};
431
432 if (m_location->isAirborneLocation() && m_selectedAircraftInfo->hasState("cruise")) {
433 const double altitudeFt = m_location->altitude().convertToUnit(Units::FeetMSL).value;
434 if (altitudeFt > 6000) {
435 return "cruise";
436 }
437 }
438
439 if (m_location->isCarrier() && m_location->isAirborneLocation() && m_selectedAircraftInfo->hasState("carrier-approach")) {
440 return "carrier-approach";
441 }
442
443 if (m_location->isAirborneLocation() && m_selectedAircraftInfo->hasState("approach")) {
444 return "approach";
445 }
446
447 if (m_location->isParkedLocation()) {
448 if (m_selectedAircraftInfo->hasState("parked")) {
449 return "parked";
450 }
451 if (m_selectedAircraftInfo->hasState("parking")) {
452 return "parking";
453 }
454 }
455
456 if (m_location->isCarrier() && m_selectedAircraftInfo->hasState("carrier-take-off")) {
457 return "carrier-take-off";
458 }
459
460 if (m_selectedAircraftInfo->hasState("take-off")) {
461 return "take-off";
462 }
463
464 return {}; // failed to compute, give up
465}
466
467void LauncherController::maybeUpdateSelectedAircraft(QModelIndex index)
468{
469 QUrl u = index.data(AircraftURIRole).toUrl();
470 if (u == m_selectedAircraft) {
471 // potentially enable the run button now!
472 updateSelectedAircraft();
473 }
474}
475
476void LauncherController::updateSelectedAircraft()
477{
478 m_selectedAircraftInfo->setUri(m_selectedAircraft);
479 QModelIndex index = m_aircraftModel->indexOfAircraftURI(m_selectedAircraft);
480 if (index.isValid()) {
481 // we have to default to unknown here, until we have an explicit
482 // way to determine if it's a regular aircraft or not
483 m_aircraftType = Unknown;
484 if (index.data(AircraftIsHelicopterRole).toBool()) {
485 m_aircraftType = Helicopter;
486 } else if (index.data(AircraftIsSeaplaneRole).toBool()) {
487 m_aircraftType = Seaplane;
488 }
489 }
490
491 if (!m_aircraftState.isEmpty()) {
492 if (!m_selectedAircraftInfo->hasState(m_aircraftState)) {
493 m_aircraftState.clear();
495 }
496 }
497
498 emit canFlyChanged();
499}
500
502{
503 QModelIndex index = m_aircraftModel->indexOfAircraftURI(m_selectedAircraft);
504 if (!index.isValid()) {
505 return false;
506 }
507
508 int status = index.data(AircraftPackageStatusRole).toInt();
510 return canRun;
511}
512
514{
515 // this can get run if the UI is disabled, just bail out before doing
516 // anything permanent.
517 if (!m_config->enableDownloadDirUI()) {
518 return;
519 }
520
521 // if the default dir is passed in, map that back to the empty string
522 if (path == m_config->defaultDownloadDir()) {
523 path.clear();
524 }
525
527 if (options->valueForOption("download-dir") == path.toStdString()) {
528 // this works because we propogate the value from QSettings to
529 // the flightgear::Options object in runLauncherDialog()
530 // so the options object always contains our current idea of this
531 // value
532 return;
533 }
534
535 if (!path.isEmpty()) {
536 options->setOption("download-dir", path.toStdString());
537 } else {
538 options->clearOption("download-dir");
539 }
540
541 m_config->setValueForKey("", "download-dir", path);
542 saveSettings();
544}
545
547{
548 return m_selectedAircraftInfo;
549}
550
552{
553 m_location->restoreLocation(var.toMap());
554}
555
557{
558 return m_selectedAircraft;
559}
560
561bool LauncherController::matchesSearch(QString term, QStringList keywords) const
562{
563 Q_FOREACH(QString s, keywords) {
564 if (s.contains(term, Qt::CaseInsensitive)) {
565 return true;
566 }
567 }
568
569 return false;
570}
571
573{
574 return !m_settingsSearchTerm.isEmpty();
575}
576
578{
579 return m_settingsSummary;
580}
581
583{
584 return m_environmentSummary;
585}
586
587
589{
590 if (m_selectedAircraft == selectedAircraft)
591 return;
592
593 m_selectedAircraft = selectedAircraft;
594 m_aircraftState.clear();
595
596 updateSelectedAircraft();
597 emit selectedAircraftChanged(m_selectedAircraft);
599}
600
602{
603 if (m_settingsSearchTerm == settingsSearchTerm)
604 return;
605
606 m_settingsSearchTerm = settingsSearchTerm;
607 emit searchChanged();
608}
609
611{
612 if (m_settingsSummary == settingsSummary)
613 return;
614
615 m_settingsSummary = settingsSummary;
616 emit summaryChanged();
617}
618
620{
621 if (m_environmentSummary == environmentSummary)
622 return;
623
624 m_environmentSummary = environmentSummary;
625 emit summaryChanged();
626}
627
629{
630 // avoid duplicate calls to fly, if the user's system is slow, and they
631 // generate multiple clicks / events before the qApp->exit() fires.
632 // without this, we can apply options, etc twice which breaks.
633 if (m_flyRequested)
634 return;
635 m_flyRequested = true;
636
637 if (m_inAppMode) {
638 doApply();
639 m_keepRunningInAppMode = false;
640 m_appModeResult = true;
641 } else {
642 doRun();
643 qApp->exit(1);
644 }
645}
646
648{
649 if (m_inAppMode) {
650 m_keepRunningInAppMode = false;
651 m_appModeResult = false;
652 } else {
653 saveSettings();
654 qApp->exit(0);
655 }
656}
657
659{
660 return m_settingsSummary + m_environmentSummary;
661}
662
663simgear::pkg::PackageRef LauncherController::packageForAircraftURI(QUrl uri) const
664{
665 if (uri.scheme() != "package") {
666 qWarning() << "invalid URL scheme:" << uri;
667 return simgear::pkg::PackageRef();
668 }
669
670 QString ident = uri.path();
671 return globals->packageRoot()->getPackageById(ident.toStdString());
672}
673
675{
676 if (metar.isEmpty()) {
677 return true;
678 }
679
680 try {
681 std::string s = metar.toStdString();
682 SGMetar theMetar(s);
683 } catch (sg_io_exception&) {
684 return false;
685 }
686
687 return true;
688}
689
691{
692 simgear::pkg::PackageRef pref = packageForAircraftURI(aircraftUri);
693 if (pref) {
694 if (pref->isInstalled()) {
695 InstallRef install = pref->existingInstall();
696 if (install->hasUpdate()) {
697 globals->packageRoot()->scheduleToUpdate(install);
698 }
699 } else {
700 pref->install();
701 }
702 }
703}
704
706{
707 simgear::pkg::PackageRef pref = packageForAircraftURI(aircraftUri);
708 if (pref) {
709 simgear::pkg::InstallRef i = pref->existingInstall();
710 if (i) {
711 i->uninstall();
712 }
713 }
714}
715
717{
718 simgear::pkg::PackageRef pref = packageForAircraftURI(aircraftUri);
719 if (pref) {
720 simgear::pkg::InstallRef i = pref->existingInstall();
721 if (i) {
722 i->cancelDownload();
723 }
724 }
725}
726
728{
729 const auto pkgRoot = globals->packageRoot();
730 const PackageList& toBeUpdated = pkgRoot->packagesNeedingUpdate();
731 std::for_each(toBeUpdated.begin(), toBeUpdated.end(), [pkgRoot](PackageRef pkg) {
732 const auto ins = pkg->install();
733 if (!pkgRoot->isInstallQueued(ins)) {
734 pkgRoot->scheduleToUpdate(ins);
735 }
736 });
737}
738
740{
741 m_serversModel->refresh();
742}
743
745{
746 if (strcmp(FG_BUILD_TYPE, "Nightly") == 0) {
747 return " Nightly " BUILD_DATE;
748 }
749
750 if (strcmp(FG_BUILD_TYPE, "Dev") == 0) {
751 return " (Dev)";
752 }
753
754 return FLIGHTGEAR_VERSION;
755}
756
758{
759 return m_aircraftHistory;
760}
761
763{
764 return m_locationHistory;
765}
766
768{
769 QDesktopServices::openUrl(url);
770}
771
773{
774 QVariantList urls;
775
776 for (auto path : flightgear::defaultSplashScreenPaths()) {
777 QUrl url = QUrl::fromLocalFile(QString::fromStdString(path));
778 urls.append(url);
779 }
780
781 return urls;
782}
783
784QVariant LauncherController::loadUISetting(QString name, QVariant defaultValue) const
785{
786 QSettings settings;
787 if (!settings.contains(name))
788 return defaultValue;
789 return settings.value(name);
790}
791
792void LauncherController::saveUISetting(QString name, QVariant value) const
793{
794 QSettings settings;
795 settings.setValue(name, value);
796}
797
798void LauncherController::onAircraftInstalledCompleted(QModelIndex index)
799{
800 maybeUpdateSelectedAircraft(index);
801}
802
803void LauncherController::onAircraftInstallFailed(QModelIndex index, QString errorMessage)
804{
805 qWarning() << Q_FUNC_INFO << index.data(AircraftURIRole) << errorMessage;
806
807 QMessageBox msg;
808 msg.setWindowTitle(tr("Aircraft installation failed"));
809 msg.setText(tr("An error occurred installing the aircraft %1: %2").
810 arg(index.data(Qt::DisplayRole).toString()).arg(errorMessage));
811 msg.addButton(QMessageBox::Ok);
812 msg.exec();
813
814 maybeUpdateSelectedAircraft(index);
815}
816
817
818QPointF LauncherController::mapToGlobal(QQuickItem *item, const QPointF &pos) const
819{
820 QPointF scenePos = item->mapToScene(pos);
821 QQuickWindow* win = item->window();
822 return win->mapToGlobal(scenePos.toPoint());
823}
824
826{
827 QMessageBox mbox;
828 mbox.setText(tr("Restore all settings to defaults?"));
829 mbox.setInformativeText(tr("Restoring settings to their defaults may affect available add-ons such as scenery or aircraft."));
830 QPushButton* quitButton = mbox.addButton(tr("Restore and restart now"), QMessageBox::YesRole);
831 mbox.addButton(QMessageBox::Cancel);
832 mbox.setDefaultButton(QMessageBox::Cancel);
833 mbox.setIconPixmap(QPixmap(":/app-icon-large"));
834
835 mbox.exec();
836 if (mbox.clickedButton() != quitButton) {
837 return;
838 }
839
840 {
841 QSettings settings;
842 settings.clear();
843 settings.setValue("restore-defaults-on-run", true);
844 }
845
847}
848
850{
851 QString currentLocText;
852 QSettings settings;
853 QString root = settings.value(SetupRootDialog::rootPathKey()).toString();
854 if (root.isNull()) {
855 currentLocText = tr("Currently the built-in data files are being used");
856 }
857 else {
858 currentLocText = tr("Currently using location: %1").arg(root);
859 }
860
861 QMessageBox mbox;
862 mbox.setText(tr("Change the data files used by FlightGear?"));
863 mbox.setInformativeText(tr("FlightGear requires additional files to operate. "
864 "(Also called the base package, or fg-data) "
865 "You can restart FlightGear and choose a "
866 "different data files location, or restore the default setting. %1").arg(currentLocText));
867 QPushButton* quitButton = mbox.addButton(tr("Restart FlightGear now"), QMessageBox::YesRole);
868 mbox.addButton(QMessageBox::Cancel);
869 mbox.setDefaultButton(QMessageBox::Cancel);
870 mbox.setIconPixmap(QPixmap(":/app-icon-large"));
871
872 mbox.exec();
873 if (mbox.clickedButton() != quitButton) {
874 return;
875 }
876
879}
880
882{
883 QString file = QFileDialog::getOpenFileName(nullptr, tr("Choose a saved configuration"),
884 {}, "*.fglaunch");
885 if (file.isEmpty())
886 return;
887
888 m_config->loadConfigFromFile(file);
889}
890
892{
893 QString file = QFileDialog::getSaveFileName(nullptr, tr("Save the current configuration"),
894 {}, "*.fglaunch");
895 if (file.isEmpty())
896 return;
897 if (!file.endsWith(".fglaunch")) {
898 file += ".fglaunch";
899 }
900
901 m_config->saveConfigToFile(file);
902}
903
905{
906 if (m_aircraftGridMode == aircraftGridMode)
907 return;
908
909 QSettings settings;
910 settings.setValue("aircraftGridMode", aircraftGridMode);
911 m_aircraftGridMode = aircraftGridMode;
912 emit aircraftGridModeChanged(m_aircraftGridMode);
913}
914
916{
917 {
918 QSettings settings;
919 settings.beginGroup("GettingStarted-DontShow");
920 settings.remove(""); // remove all keys in the current group
921 settings.endGroup();
922 } // ensure settings are written, before we emit the signal
923
925}
926
928{
929 if (sz == m_minWindowSize)
930 return;
931
932 m_window->setMinimumSize(sz);
934}
935
937{
938 if (m_aircraftType == Helicopter) {
939 return QUrl{"image://colored-icon/toolbox-fly-heli"};
940 } else if (m_selectedAircraftInfo) {
941 if (m_selectedAircraftInfo->hasTag("spaceship")) {
942 return QUrl{"image://colored-icon/toolbox-fly-alt"};
943 }
944 }
945
946 return QUrl{"image://colored-icon/toolbox-fly"};
947}
948
950{
951 if (m_aircraftType == Helicopter) {
952 return tr("Fly!", "For a helicopter");
953 } else if (m_selectedAircraftInfo) {
954 if (m_selectedAircraftInfo->hasTag("spaceship")) {
955 return tr("Fly!", "For a spaceship");
956 }
957 }
958
959 return tr("Fly!");
960}
961
962QUrl LauncherController::urlToDataPath(QString relPath) const
963{
964 QString absFilePath = QString::fromStdString(globals->get_fg_root().utf8Str());
965 if (!relPath.startsWith("/")) {
966 relPath.prepend("/");
967 }
968 return QUrl::fromLocalFile(absFilePath + relPath);
969}
970
971void LauncherController::checkForOldDownloadDir()
972{
973#if defined(Q_OS_WIN)
975 if (options->valueForOption("download-dir") != std::string{}) {
976 return; // if we're using a custom value, nothing to do
977 }
978
979 if (haveOldWindowsDownloadDir()) {
980 // the notifications logic handles 'don't show again' lgoic internally,
981 // so we can always trigger this check
983 QJSValue args = nc->jsEngine()->newObject();
984
985 const auto oldPath = SGPath::documents() / "FlightGear";
986 const auto newPath = flightgear::defaultDownloadDir();
987
988 const QUrl oldLocURI = QUrl::fromLocalFile(QString::fromStdString(oldPath.utf8Str()));
989 const QUrl newLocURI = QUrl::fromLocalFile(QString::fromStdString(newPath.utf8Str()));
990
991 args.setProperty("oldLocation", oldLocURI.toString());
992 args.setProperty("newLocation", newLocURI.toString());
993 args.setProperty("persistent-dismiss", true);
994
995 nc->postNotification("have-old-downloads-location", QUrl{"qrc:///qml/DownloadsInDocumentsWarning.qml"}, args);
996 }
997#endif
998}
999
1000bool LauncherController::haveOldWindowsDownloadDir() const
1001{
1002 const SGPath p = SGPath::documents() / "FlightGear";
1003 if ((p / "TerraSync").exists() || (p / "Aircraft").exists()) {
1004 return true;
1005 }
1006
1007 // tex-cache dir is created by default, so check if it's populated
1008 simgear::Dir texCacheDir(p / "TextureCache");
1009 return (texCacheDir.exists() && !texCacheDir.isEmpty());
1010}
const int AircraftIsSeaplaneRole
const int AircraftIsHelicopterRole
const int AircraftURIRole
const int AircraftPackageStatusRole
#define p(x)
bool options(int, char **)
Definition JSBSim.cpp:568
#define i(x)
SGSharedPtr< FGAirport > FGAirportRef
void aircraftInstallFailed(QModelIndex index, QString errorMessage)
void aircraftInstallCompleted(QModelIndex index)
static FGAirportRef findByIdent(const std::string &aIdent)
Helper to look up an FGAirport instance by unique ident.
Definition airport.cxx:489
simgear::pkg::Root * packageRoot()
Definition globals.cxx:992
void restore()
void collect()
Q_INVOKABLE void setArg(QString name, QString value=QString(), Origin origin=Launcher)
Q_INVOKABLE QVariant loadUISetting(QString name, QVariant defaultValue) const
RecentAircraftModel * aircraftHistory
Q_INVOKABLE QUrl urlToDataPath(QString relPath) const
urlToDataPath - convetr a FGData path into a gloabl file:/// URL suitable for Qt.openExternally()
Q_INVOKABLE void requestInstallUpdate(QUrl aircraftUri)
bool keepRunningInAppMode() const
Q_INVOKABLE void launchUrl(QUrl url)
Q_INVOKABLE QPointF mapToGlobal(QQuickItem *item, const QPointF &pos) const
void setSettingsSummary(QStringList settingsSummary)
Q_INVOKABLE void requestUpdateAllAircraft()
void setEnvironmentSummary(QStringList environmentSummary)
Q_INVOKABLE void requestInstallCancel(QUrl aircraftUri)
void setSelectedAircraft(QUrl selectedAircraft)
QmlAircraftInfo * selectedAircraftInfo
Q_INVOKABLE bool matchesSearch(QString term, QStringList keywords) const
Q_INVOKABLE QVariantList defaultSplashUrls() const
void selectedAircraftStateChanged()
void aircraftGridModeChanged(bool aircraftGridMode)
Q_INVOKABLE void downloadDirChanged(QString path)
void minWindowSizeChanged()
void selectedAircraftChanged(QUrl selectedAircraft)
Q_INVOKABLE void requestUninstall(QUrl aircraftUri)
void initQML(int &styleTypeId)
Q_INVOKABLE void queryMPServers()
RecentLocationsModel * locationHistory
Q_INVOKABLE void restoreLocation(QVariant var)
void didResetGettingStartedTips()
Q_INVOKABLE bool validateMetarString(QString metar)
void setMinWindowSize(QSize sz)
void setAircraftGridMode(bool aircraftGridMode)
void setSettingsSearchTerm(QString settingsSearchTerm)
LauncherController(QObject *parent, QWindow *win)
Q_INVOKABLE void saveUISetting(QString name, QVariant value) const
static LauncherNotificationsController * instance()
static LocalAircraftCache * instance()
static void setGlobalNetworkAccess(QNetworkAccessManager *netAccess)
static QString rootPathKey()
static void askRootOnNextLaunch()
we don't want to rely on the main AircraftModel threaded scan, to find the default aircraft,...
int addOption(const std::string &key, const std::string &value)
set an option value, assuming it is not already set (or multiple values are permitted) This can be us...
Definition options.cxx:2859
static Options * sharedInstance()
Definition options.cxx:2345
const char * name
static int status
FGGlobals * globals
Definition globals.cxx:142
std::vector< std::string > string_list
Definition globals.hxx:36
SGPath defaultDownloadDir()
return the default platform dependant download directory.
Definition options.cxx:3001
void restartTheApp()
restartTheApp quit the application and relaunch it, passing the –launcher flag explicitly.
void addSentryBreadcrumb(const std::string &, const std::string &)
string_list defaultSplashScreenPaths()
std::string defaultAirportICAO()