28#include <QApplication>
33#include <QOffscreenSurface>
34#include <QOpenGLContext>
37#include <QProgressDialog>
47#include <simgear/timing/timestamp.hxx>
48#include <simgear/props/props_io.hxx>
49#include <simgear/structure/exception.hxx>
50#include <simgear/structure/subsystem_mgr.hxx>
51#include <simgear/misc/sg_path.hxx>
52#include <simgear/misc/strutils.hxx>
53#include <simgear/package/Root.hxx>
54#include <simgear/package/Catalog.hxx>
55#include <simgear/package/Package.hxx>
56#include <simgear/package/Install.hxx>
57#include <simgear/debug/logstream.hxx>
93#if defined(OSG_OPENGL)
94 #error "Don't include osg/GL in this file, it will cause problems"
103struct ProgressLabel {
108static std::initializer_list<ProgressLabel> progressStrings = {
118 const char* baseLabelKey = QT_TRANSLATE_NOOP(
"initNavCache",
"Initialising navigation data, this may take several minutes");
119 QString baseLabel= qApp->translate(
"initNavCache", baseLabelKey);
121 const auto wflags = Qt::Dialog | Qt::CustomizeWindowHint | Qt::WindowTitleHint | Qt::WindowSystemMenuHint | Qt::MSWindowsFixedSizeDialogHint;
124 const char* waitForOtherMsg = QT_TRANSLATE_NOOP(
"initNavCache",
"Another copy of FlightGear is creating the navigation database. Waiting for it to finish.");
125 QString m = qApp->translate(
"initNavCache", waitForOtherMsg);
127 addSentryBreadcrumb(
"Launcher: showing wait for other process NavCache rebuild dialog",
"info");
128 QProgressDialog waitForRebuild(m,
132 waitForRebuild.setWindowModality(Qt::WindowModal);
133 waitForRebuild.setMinimumWidth(600);
134 waitForRebuild.setAutoReset(
false);
135 waitForRebuild.setAutoClose(
false);
136 waitForRebuild.show();
139 updateTimer.setInterval(500);
140 bool rebuildIsDone =
false;
142 QObject::connect(&updateTimer, &QTimer::timeout, [&waitForRebuild, &rebuildIsDone]() {
144 waitForRebuild.done(0);
145 rebuildIsDone =
true;
151 waitForRebuild.exec();
154 if (!rebuildIsDone) {
159 addSentryBreadcrumb(
"Launcher: done waiting for other process NavCache rebuild dialog",
"info");
164 QProgressDialog rebuildProgress(baseLabel,
168 rebuildProgress.setWindowModality(Qt::WindowModal);
169 rebuildProgress.setMinimumWidth(600);
170 rebuildProgress.setAutoReset(
false);
171 rebuildProgress.setAutoClose(
false);
172 rebuildProgress.show();
175 updateTimer.setInterval(100);
177 bool didComplete =
false;
179 QObject::connect(&updateTimer, &QTimer::timeout, [&cache, &rebuildProgress, &baseLabel, &didComplete]() {
182 rebuildProgress.done(0);
187 auto it = std::find_if(progressStrings.begin(), progressStrings.end(), [phase]
188 (
const ProgressLabel& l) { return l.phase == phase; });
189 if (it == progressStrings.end()) {
190 rebuildProgress.setLabelText(baseLabel);
192 QString trans = qApp->translate(
"initNavCache", it->label);
193 rebuildProgress.setLabelText(trans);
197 rebuildProgress.setValue(0);
198 rebuildProgress.setMaximum(0);
201 rebuildProgress.setMaximum(100);
206 rebuildProgress.exec();
220class NaturalEarthDataLoaderThread :
public QThread
225 NaturalEarthDataLoaderThread()
227 connect(
this, &QThread::finished,
this, &NaturalEarthDataLoaderThread::onFinished);
243 const std::initializer_list<FileAndType> files = {
250 for (
const auto& d : files) {
255 loadNaturalEarthFile(d.file, d.type, d.closed);
260 Q_SLOT
void onFinished()
270 void loadNaturalEarthFile(
const std::string& aFileName,
275 path.append(
"Geodata" );
276 path.append(aFileName);
284 bool m_abandoned =
false;
291 Q_INIT_RESOURCE(resources);
292#if defined(HAVE_QRC_TRANSLATIONS)
293 Q_INIT_RESOURCE(translations);
299 sgDebugPriority mappedPriority = SG_WARN;
301 case QtDebugMsg: mappedPriority = SG_DEBUG;
break;
302 case QtInfoMsg: mappedPriority = SG_INFO;
break;
303 case QtWarningMsg: mappedPriority = SG_WARN;
break;
304 case QtCriticalMsg: mappedPriority = SG_ALERT;
break;
305 case QtFatalMsg: mappedPriority = SG_POPUP;
break;
308 static const char* nullFile =
"";
309 const char* file = context.file ? context.file : nullFile;
310 const auto s = msg.toStdString();
312 sglog().logCopyingFilename(SG_GUI, mappedPriority, file, context.line,
"" , s);
313 if (type == QtFatalMsg) {
338 QStringList uiLanguages = QLocale::system().uiLanguages();
341 for (
auto locale : uiLanguages) {
343 locale = QLocale(locale).name();
344 locale.replace(
'-',
'_');
346 QTranslator* translator =
new QTranslator;
349 if (translator->load(
"FlightGear_" + locale, QLatin1String(
":/"))) {
360void initApp(
int& argc,
char** argv,
bool doInitQSettings)
362 static bool qtInitDone =
false;
371 qputenv(
"QT_LOGGING_RULES",
"qt.qml.connections.warning=false");
386 QCoreApplication::setAttribute(Qt::AA_UseDesktopOpenGL);
393#if !defined (SG_WINDOWS) && (QT_VERSION < QT_VERSION_CHECK(6, 0, 0))
394 QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
399 static_qApp->setOrganizationDomain(
"flightgear.org");
402 QStringLiteral(
"org.flightgear.FlightGear.desktop"));
404 QTranslator* fallbackTranslator =
new QTranslator(
static_qApp.get());
405 if (!fallbackTranslator->load(QLatin1String(
":/FlightGear_en_US.qm"))) {
406 qWarning() <<
"Failed to load the built-in launcher fallback"
407 <<
"translation (English). If you compiled FlightGear"
408 <<
"yourself, you may want to pass -DFG_DATA_DIR to"
409 <<
"CMake so as to allow the FlightGear build system"
410 <<
"to find FGData.";
411 delete fallbackTranslator;
413 static_qApp->installTranslator(fallbackTranslator);
418 auto lang = simgear::strutils::replace(
423 QTranslator* translator =
new QTranslator(
static_qApp.get());
424 QString localeFile =
"FlightGear_" + QString::fromStdString(lang);
425 if (translator->load(localeFile, QLatin1String(
":/"))) {
426 qInfo() <<
"Loaded translations based on --language from:" << localeFile;
429 qInfo() <<
"--language was set, but no translations found at:" << localeFile;
438 ::setlocale(LC_NUMERIC,
"C");
439 ::setlocale(LC_COLLATE,
"C");
445 const char* titleKey = QT_TRANSLATE_NOOP(
"macTranslationWarning",
"Application running from download location");
446 const char* key = QT_TRANSLATE_NOOP(
"macTranslationWarning",
"FlightGear is running from the download image. For better performance and to avoid potential problems, "
447 "please copy FlightGear to some other location, such as your desktop or Applications folder.");
448 QString msg = qApp->translate(
"macTranslationWarning", key);
449 QString title = qApp->translate(
"macTranslationWarning", titleKey);
451 QMessageBox::warning(
nullptr, title, msg);
456 if (doInitQSettings) {
465 qInstallMessageHandler(
nullptr);
472 static bool qSettingsInitDone =
false;
474 if (!qSettingsInitDone) {
475 qRegisterMetaType<QuantityValue>();
476#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
477 qRegisterMetaTypeStreamOperators<QuantityValue>(
"QuantityValue");
480 qSettingsInitDone =
true;
481 string fgHome =
globals->get_fg_home().utf8Str();
483 QSettings::setDefaultFormat(QSettings::IniFormat);
484 QSettings::setPath(QSettings::IniFormat, QSettings::UserScope,
485 QString::fromStdString(fgHome));
493 const auto altState = GetAsyncKeyState(VK_MENU);
494 const auto shiftState = GetAsyncKeyState(VK_SHIFT);
495 if ((altState < 0) || (shiftState < 0))
497 Qt::KeyboardModifiers mods = qApp->queryKeyboardModifiers();
498 if (mods & (Qt::AltModifier | Qt::ShiftModifier))
501 qWarning() <<
"Alt/shift pressed during launch";
521 QDir dir(qApp->applicationDirPath());
526 args <<
"-n" << dir.absolutePath() <<
"--args" <<
"--launcher" << fgArgs;
527 qDebug() <<
"args" << args;
528 proc.startDetached(
"open", args);
530 args <<
"--launcher" << fgArgs;
531 proc.startDetached(qApp->applicationFilePath(), args);
539 for (
const auto& arg : originalCommandLine) {
540 fgArgs.append(QString::fromStdString(arg));
545 QDir dir(qApp->applicationDirPath());
552 args <<
"-n" << dir.absolutePath() <<
"--args" << fgArgs;
553 qDebug() <<
"args" << args;
554 proc.startDetached(
"open", args);
556 proc.startDetached(qApp->applicationFilePath(), fgArgs);
566 for (
const auto& arg : commandLineSceneryPaths) {
568 globals->append_fg_scenery(SGPath::pathsFromUtf8(arg));
576 globals->append_fg_scenery(path.toStdString());
580 QString downloadDir = settings.value(
"download-dir").toString();
581 if (downloadDir.isEmpty()) {
585 SGPath terraSyncDir(downloadDir.toStdString());
586 terraSyncDir.append(
"TerraSync");
587 if (terraSyncDir.exists()) {
588 globals->append_fg_scenery(terraSyncDir);
593 const SGPath rootScenery =
globals->get_fg_root() /
"Scenery";
594 if (rootScenery.exists()) {
595 globals->append_fg_scenery(rootScenery);
606 bool ok = initNavCache();
612 if (
options->isOptionSet(
"download-dir")) {
618 QString downloadDir = settings.value(
"download-dir").toString();
619 if (!downloadDir.isEmpty()) {
620 options->setOption(
"download-dir", downloadDir.toStdString());
627 auto lang =
options->valueForOption(
"language");
629 const auto langName = QLocale::languageToString(QLocale{}.language());
630 lang = langName.toStdString();
635 globals->get_locale()->selectLanguage(lang);
636 globals->packageRoot()->setLocale(
globals->get_locale()->getPreferredLanguage());
641 QPointer<NaturalEarthDataLoaderThread> naturalEarthLoader =
new NaturalEarthDataLoaderThread;
642 naturalEarthLoader->start();
650 if (
options->isOptionSet(
"fullscreen")) {
651 dlg.showFullScreen();
653 dlg.setVisible(
true);
656 int appResult = qApp->exec();
659 if (naturalEarthLoader) {
660 naturalEarthLoader->abandon();
669 globals->get_locale()->clear();
671 if (appResult <= 0) {
690 QT_TRANSLATE_NOOP(
"LockFileDialog",
"Multiple copies of FlightGear running");
692 QT_TRANSLATE_NOOP(
"LockFileDialog",
693 "FlightGear has detected another copy is already running. "
694 "This copy will run in read-only mode, so downloads will not be possible, "
695 "and settings will not be saved.");
697 QT_TRANSLATE_NOOP(
"LockFileDialog",
698 "If you are sure another copy is not running on this computer, "
699 "you can choose to reset the lock file, and run this copy as normal. "
700 "Alternatively, you can close this copy of the software.");
711 mb.setIconPixmap(QPixmap(
":/app-icon-large"));
712 mb.setWindowTitle(title);
714 mb.setInformativeText(infoText);
715 mb.addButton(QMessageBox::Ok);
716 mb.setDefaultButton(QMessageBox::Ok);
717 mb.addButton(QMessageBox::Reset);
718 mb.addButton(QMessageBox::Close);
721 if (r == QMessageBox::Reset)
723 if (r == QMessageBox::Close)
740 QMessageBox::critical(
742 "Unable to create OpenGL 4.1 core profile context",
743 "FlightGear detected that your system does not support the required OpenGL version. "
744 "This is normally due to outdated graphics drivers, please check if updates are available.");
749#include "QtLauncher.moc"
bool cocoaIsRunningTranslocated()
helper to detect if we're running translocated or not.
bool options(int, char **)
void fgqt_setPoseAsStandaloneApp(bool b)
static void simgearMessageOutput(QtMsgType type, const QMessageLogContext &context, const QString &msg)
static void initQtResources()
const SGPath & get_fg_root() const
static FGHTTPClient * getOrCreate()
static void setEnableDownloadDirUI(bool enableDownloadDirUI)
static QStringList readEnabledPaths(QString settingsKey)
static flightgear::SetupRootResult restoreUserSelectedRoot(SGPath &path)
static bool runDialog(bool usingDefaultRoot)
static bool isAnotherProcessRebuilding()
static NavDataCache * createInstance()
@ REBUILD_LOADING_AIRPORTS
@ REBUILD_READING_APT_DAT_FILES
unsigned int rebuildPhaseCompletionPercentage() const
bool isRebuildRequired()
predicate - check if the cache needs to be rebuilt.
RebuildPhase rebuild()
run the cache rebuild - returns the current phase or 'done'
string_list valuesForOption(const std::string &key) const
return all values for a multi-valued option
static std::string getArgValue(int argc, char *argv[], const char *checkArg)
getArgValue - get the value of an argument if it exists, or an empty string otherwise
static Options * sharedInstance()
@ RIVER
state / province / country / department
static void bulkAddToSpatialIndex(PolyLineList::const_iterator begin, PolyLineList::const_iterator end)
static void parsePolyLines(const SGPath &, PolyLine::Type aTy, PolyLineList &aResult, bool aClosed)
Parse a shape file containing PolyLine data.
FlightGear Localization Support.
FlightPlan.hxx - defines a full flight-plan object, including departure, cruise, arrival information ...
std::vector< PolyLineRef > PolyLineList
SGPath defaultDownloadDir()
return the default platform dependant download directory.
void restartTheApp()
restartTheApp quit the application and relaunch it, passing the –launcher flag explicitly.
bool runInAppLauncherDialog()
void startLaunchOnExit(const std::vector< std::string > &originalCommandLine)
@ brief helper to re-open the launcher once FLightGear exits cleanly
void initApp(int &argc, char **argv, bool doInitQSettings)
void selectUITranslation()
static const char * static_lockFileDialog_Title
static const char * static_lockFileDialog_Text
void addSentryBreadcrumb(const std::string &, const std::string &)
void warnAboutGLVersion()
bool showSetupRootDialog(bool usingDefaultRoot)
static std::unique_ptr< QApplication > static_qApp
bool checkKeyboardModifiersForSettingFGRoot()
SetupRootResult restoreUserSelectedRoot(SGPath &path)
static const char * static_lockFileDialog_Info
LockFileDialogResult showLockFileDialog()
void launcherSetSceneryPaths()