2015-03-07 23:52:03 +00:00
// QtLauncher.cxx - GUI launcher dialog using Qt5
//
// Written by James Turner, started December 2014.
//
// Copyright (C) 2014 James Turner <zakalawe@mac.com>
//
// 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.
2018-08-28 22:34:44 +00:00
# include "config.h"
2014-12-26 12:20:51 +00:00
# include "QtLauncher.hxx"
2016-02-02 20:44:46 +00:00
# include <locale.h>
2014-12-26 12:20:51 +00:00
// Qt
2017-05-16 11:51:46 +00:00
# include <QtGlobal>
# include <QString>
2014-12-26 12:20:51 +00:00
# include <QProgressDialog>
# include <QDir>
# include <QFileInfo>
# include <QTimer>
# include <QDebug>
# include <QSettings>
# include <QUrl>
2015-03-06 18:52:06 +00:00
# include <QApplication>
2016-08-08 22:13:57 +00:00
# include <QThread>
2017-02-20 00:19:13 +00:00
# include <QProcess>
2018-08-27 12:20:23 +00:00
# include <QTranslator>
2020-05-03 17:02:20 +00:00
# include <QMessageBox>
2020-11-02 14:55:35 +00:00
# include <QPointer>
2014-12-26 12:20:51 +00:00
// Simgear
# include <simgear/timing/timestamp.hxx>
# include <simgear/props/props_io.hxx>
# include <simgear/structure/exception.hxx>
2015-03-10 08:30:55 +00:00
# include <simgear/structure/subsystem_mgr.hxx>
2014-12-26 12:20:51 +00:00
# include <simgear/misc/sg_path.hxx>
2020-07-14 10:38:09 +00:00
# include <simgear/misc/strutils.hxx>
2020-03-17 11:46:26 +00:00
# include <simgear/package/Root.hxx>
2015-08-03 20:53:56 +00:00
# include <simgear/package/Catalog.hxx>
# include <simgear/package/Package.hxx>
# include <simgear/package/Install.hxx>
2017-01-29 15:16:28 +00:00
# include <simgear/debug/logstream.hxx>
2014-12-26 12:20:51 +00:00
2020-10-25 18:26:00 +00:00
# include <Add-ons/AddonManager.hxx>
# include <Airports/airport.hxx>
2016-07-08 08:36:42 +00:00
# include <Main/fg_props.hxx>
2020-10-25 18:26:00 +00:00
# include <Main/globals.hxx>
# include <Main/sentryIntegration.hxx>
2014-12-26 12:20:51 +00:00
# include <Navaids/NavDataCache.hxx>
2015-11-26 16:48:23 +00:00
# include <Navaids/SHPParser.hxx>
2020-10-25 18:26:00 +00:00
# include <Navaids/navrecord.hxx>
2019-02-02 20:46:09 +00:00
2015-10-16 01:05:17 +00:00
2015-03-10 08:30:55 +00:00
# include <Main/fg_init.hxx>
2020-06-17 15:15:01 +00:00
# include <Main/locale.hxx>
# include <Main/options.hxx>
2015-03-10 08:30:55 +00:00
# include <Network/HTTPClient.hxx>
2020-06-17 15:15:01 +00:00
# include <Viewer/WindowBuilder.hxx>
2014-12-26 12:20:51 +00:00
2017-07-19 18:24:33 +00:00
# include "LaunchConfig.hxx"
2020-10-25 18:26:00 +00:00
# include "LauncherMainWindow.hxx"
# include "LocalAircraftCache.hxx"
2020-02-24 11:32:43 +00:00
# include "PathListModel.hxx"
2020-10-25 18:26:00 +00:00
# include "UnitsModel.hxx"
2020-11-10 11:09:46 +00:00
# include "GettingStartedTip.hxx"
2017-01-30 07:00:37 +00:00
2021-01-10 12:51:41 +00:00
# if defined(SG_MAC)
# include <GUI/CocoaHelpers.h>
# endif
2014-12-26 12:20:51 +00:00
using namespace flightgear ;
2015-08-03 20:53:56 +00:00
using namespace simgear : : pkg ;
Split flightgear::initApp() to make it usable even without FGGlobals initialized
- Add an optional argument to flightgear::initApp(): doInitQSettings.
This argument defaults to true, preserving initApp()'s behavior in
this respect. If this argument is set to false, FGGlobals doesn't have
to be initialized.
- New function flightgear::initQSettings(), called by
flightgear::initApp() when its 'doInitQSettings' argument is true.
This allows initializing the QSettings exactly when it is needed.
- New function flightgear::checkKeyboardModifiersForSettingFGRoot().
The code it contains used to be run from initApp(), which is
undesirable because:
1) initApp() is not only called at FG initialization (fgMainInit()),
but also from QtMessageBox(), from QtFileDialog::exec() and twice
from Options::setupRoot(). However, checking the Alt and Shift
modifiers to set 'fg-root' in QSettings to the special value
"!ask" only makes sense in fgMainInit(), not in these other
places.
2) This code relies on the QSettings to be set up, and therefore on
FGGlobals. Thus, freeing initApp() of its dependency on FGGlobals
requires splitting this keyboard modifiers checking code out of
initApp().
2016-12-26 20:45:44 +00:00
using std : : string ;
2014-12-26 12:20:51 +00:00
namespace { // anonymous namespace
2020-04-29 10:16:52 +00:00
struct ProgressLabel {
NavDataCache : : RebuildPhase phase ;
const char * label ;
} ;
static std : : initializer_list < ProgressLabel > progressStrings = {
{ NavDataCache : : REBUILD_READING_APT_DAT_FILES , QT_TRANSLATE_NOOP ( " initNavCache " , " Reading airport data " ) } ,
{ NavDataCache : : REBUILD_LOADING_AIRPORTS , QT_TRANSLATE_NOOP ( " initNavCache " , " Loading airports " ) } ,
{ NavDataCache : : REBUILD_FIXES , QT_TRANSLATE_NOOP ( " initNavCache " , " Loading waypoint data " ) } ,
{ NavDataCache : : REBUILD_NAVAIDS , QT_TRANSLATE_NOOP ( " initNavCache " , " Loading navigation data " ) } ,
{ NavDataCache : : REBUILD_POIS , QT_TRANSLATE_NOOP ( " initNavCache " , " Loading point-of-interest data " ) }
} ;
2014-12-26 12:20:51 +00:00
void initNavCache ( )
{
2020-04-29 10:16:52 +00:00
const char * baseLabelKey = QT_TRANSLATE_NOOP ( " initNavCache " , " Initialising navigation data, this may take several minutes " ) ;
QString baseLabel = qApp - > translate ( " initNavCache " , baseLabelKey ) ;
2020-11-10 11:42:46 +00:00
const auto wflags = Qt : : Dialog | Qt : : CustomizeWindowHint | Qt : : WindowTitleHint | Qt : : WindowSystemMenuHint | Qt : : MSWindowsFixedSizeDialogHint ;
if ( NavDataCache : : isAnotherProcessRebuilding ( ) ) {
const char * waitForOtherMsg = QT_TRANSLATE_NOOP ( " initNavCache " , " Another copy of FlightGear is creating the navigation database. Waiting for it to finish. " ) ;
QString m = qApp - > translate ( " initNavCache " , waitForOtherMsg ) ;
2020-11-10 11:09:46 +00:00
addSentryBreadcrumb ( " Launcher: showing wait for other process NavCache rebuild dialog " , " info " ) ;
2020-11-10 11:42:46 +00:00
QProgressDialog waitForRebuild ( m ,
QString ( ) /* cancel text */ ,
0 , 0 , Q_NULLPTR ,
wflags ) ;
waitForRebuild . setWindowModality ( Qt : : WindowModal ) ;
waitForRebuild . setMinimumWidth ( 600 ) ;
waitForRebuild . setAutoReset ( false ) ;
waitForRebuild . setAutoClose ( false ) ;
waitForRebuild . show ( ) ;
QTimer updateTimer ;
updateTimer . setInterval ( 500 ) ;
QObject : : connect ( & updateTimer , & QTimer : : timeout , [ & waitForRebuild ] ( ) {
if ( ! NavDataCache : : isAnotherProcessRebuilding ( ) ) {
waitForRebuild . done ( 0 ) ;
return ;
}
} ) ;
updateTimer . start ( ) ; // timer won't actually run until we process events
waitForRebuild . exec ( ) ;
updateTimer . stop ( ) ;
2020-11-10 11:09:46 +00:00
addSentryBreadcrumb ( " Launcher: done waiting for other process NavCache rebuild dialog " , " info " ) ;
2020-11-10 11:42:46 +00:00
}
2015-03-08 00:40:22 +00:00
NavDataCache * cache = NavDataCache : : createInstance ( ) ;
2014-12-26 12:20:51 +00:00
if ( cache - > isRebuildRequired ( ) ) {
2015-03-19 17:01:38 +00:00
QProgressDialog rebuildProgress ( baseLabel ,
2020-11-10 11:42:46 +00:00
QString ( ) /* cancel text */ ,
0 , 100 , Q_NULLPTR ,
wflags ) ;
2014-12-26 12:20:51 +00:00
rebuildProgress . setWindowModality ( Qt : : WindowModal ) ;
2020-05-12 21:00:43 +00:00
rebuildProgress . setMinimumWidth ( 600 ) ;
2020-05-18 10:47:11 +00:00
rebuildProgress . setAutoReset ( false ) ;
2020-05-18 10:46:17 +00:00
rebuildProgress . setAutoClose ( false ) ;
2014-12-26 12:20:51 +00:00
rebuildProgress . show ( ) ;
2020-11-10 11:42:46 +00:00
2020-05-12 21:00:43 +00:00
QTimer updateTimer ;
2020-05-13 14:51:42 +00:00
updateTimer . setInterval ( 100 ) ;
2020-11-10 11:42:46 +00:00
2020-05-12 21:00:43 +00:00
QObject : : connect ( & updateTimer , & QTimer : : timeout , [ & cache , & rebuildProgress , & baseLabel ] ( ) {
auto phase = cache - > rebuild ( ) ;
if ( phase = = NavDataCache : : REBUILD_DONE ) {
rebuildProgress . done ( 0 ) ;
return ;
}
2020-04-29 10:16:52 +00:00
auto it = std : : find_if ( progressStrings . begin ( ) , progressStrings . end ( ) , [ phase ]
( const ProgressLabel & l ) { return l . phase = = phase ; } ) ;
if ( it = = progressStrings . end ( ) ) {
2015-03-19 17:01:38 +00:00
rebuildProgress . setLabelText ( baseLabel ) ;
2020-04-29 10:16:52 +00:00
} else {
QString trans = qApp - > translate ( " initNavCache " , it - > label ) ;
rebuildProgress . setLabelText ( trans ) ;
2015-03-19 17:01:38 +00:00
}
if ( phase = = NavDataCache : : REBUILD_UNKNOWN ) {
rebuildProgress . setValue ( 0 ) ;
rebuildProgress . setMaximum ( 0 ) ;
} else {
2020-04-29 10:16:52 +00:00
rebuildProgress . setValue ( static_cast < int > ( cache - > rebuildPhaseCompletionPercentage ( ) ) ) ;
2015-03-19 17:01:38 +00:00
rebuildProgress . setMaximum ( 100 ) ;
}
2020-05-12 21:00:43 +00:00
} ) ;
updateTimer . start ( ) ; // timer won't actually run until we process events
rebuildProgress . exec ( ) ;
updateTimer . stop ( ) ;
2020-11-11 21:33:16 +00:00
flightgear : : addSentryBreadcrumb ( " Launcher nav-cache rebuild complete " , " info " ) ;
2014-12-26 12:20:51 +00:00
}
}
2016-08-08 22:13:57 +00:00
class NaturalEarthDataLoaderThread : public QThread
{
Q_OBJECT
public :
2016-08-09 08:38:54 +00:00
NaturalEarthDataLoaderThread ( ) :
m_lineInsertCount ( 0 )
2016-08-08 22:13:57 +00:00
{
connect ( this , & QThread : : finished , this , & NaturalEarthDataLoaderThread : : onFinished ) ;
}
2020-11-02 14:55:35 +00:00
void abandon ( )
{
m_abandoned = true ;
}
2016-08-08 22:13:57 +00:00
protected :
2018-09-01 14:20:05 +00:00
void run ( ) override
2016-08-08 22:13:57 +00:00
{
loadNaturalEarthFile ( " ne_10m_coastline.shp " , flightgear : : PolyLine : : COASTLINE , false ) ;
loadNaturalEarthFile ( " ne_10m_rivers_lake_centerlines.shp " , flightgear : : PolyLine : : RIVER , false ) ;
loadNaturalEarthFile ( " ne_10m_lakes.shp " , flightgear : : PolyLine : : LAKE , true ) ;
loadNaturalEarthFile ( " ne_10m_urban_areas.shp " , flightgear : : PolyLine : : URBAN , true ) ;
}
private :
2016-08-15 22:10:06 +00:00
Q_SLOT void onFinished ( )
2016-08-08 22:13:57 +00:00
{
2020-11-02 14:55:35 +00:00
if ( m_abandoned )
return ;
2016-08-09 08:38:54 +00:00
flightgear : : PolyLineList : : const_iterator begin = m_parsedLines . begin ( ) + m_lineInsertCount ;
2018-09-01 14:20:05 +00:00
unsigned int numToAdd = std : : min < unsigned int > ( 1000U , static_cast < unsigned int > ( m_parsedLines . size ( ) ) - m_lineInsertCount ) ;
2016-08-09 08:38:54 +00:00
flightgear : : PolyLineList : : const_iterator end = begin + numToAdd ;
flightgear : : PolyLine : : bulkAddToSpatialIndex ( begin , end ) ;
if ( end = = m_parsedLines . end ( ) ) {
deleteLater ( ) ; // commit suicide
} else {
m_lineInsertCount + = 1000 ;
2016-08-11 21:32:29 +00:00
QTimer : : singleShot ( 50 , this , SLOT ( onFinished ( ) ) ) ;
2016-08-09 08:38:54 +00:00
}
2016-08-08 22:13:57 +00:00
}
void loadNaturalEarthFile ( const std : : string & aFileName ,
flightgear : : PolyLine : : Type aType ,
bool areClosed )
{
SGPath path ( globals - > get_fg_root ( ) ) ;
path . append ( " Geodata " ) ;
path . append ( aFileName ) ;
if ( ! path . exists ( ) )
return ; // silently fail for now
flightgear : : PolyLineList lines ;
flightgear : : SHPParser : : parsePolyLines ( path , aType , m_parsedLines , areClosed ) ;
}
2017-02-19 22:59:39 +00:00
2016-08-08 22:13:57 +00:00
flightgear : : PolyLineList m_parsedLines ;
2016-08-09 08:38:54 +00:00
unsigned int m_lineInsertCount ;
2020-11-02 14:55:35 +00:00
bool m_abandoned = false ;
2016-08-08 22:13:57 +00:00
} ;
2014-12-26 12:20:51 +00:00
} // of anonymous namespace
2015-10-16 01:05:17 +00:00
static void initQtResources ( )
{
Q_INIT_RESOURCE ( resources ) ;
2018-08-28 16:12:31 +00:00
# if defined(HAVE_QRC_TRANSLATIONS)
2018-08-27 12:20:23 +00:00
Q_INIT_RESOURCE ( translations ) ;
2018-08-28 16:12:31 +00:00
# endif
2015-10-16 01:05:17 +00:00
}
2017-01-29 15:16:28 +00:00
static void simgearMessageOutput ( QtMsgType type , const QMessageLogContext & context , const QString & msg )
{
sgDebugPriority mappedPriority = SG_WARN ;
switch ( type ) {
case QtDebugMsg : mappedPriority = SG_DEBUG ; break ;
case QtInfoMsg : mappedPriority = SG_INFO ; break ;
case QtWarningMsg : mappedPriority = SG_WARN ; break ;
case QtCriticalMsg : mappedPriority = SG_ALERT ; break ;
case QtFatalMsg : mappedPriority = SG_POPUP ; break ;
}
static const char * nullFile = " " ;
const char * file = context . file ? context . file : nullFile ;
2020-09-05 09:44:21 +00:00
const auto s = msg . toStdString ( ) ;
// important we copy the file name here, since QMessageLogContext doesn't
2020-09-26 15:37:19 +00:00
sglog ( ) . logCopyingFilename ( SG_GUI , mappedPriority , file , context . line , " " /*function*/ , s ) ;
2017-01-29 15:16:28 +00:00
if ( type = = QtFatalMsg ) {
abort ( ) ;
}
}
2015-10-16 01:05:17 +00:00
namespace flightgear
{
2017-06-07 15:25:19 +00:00
// making this a unique ptr ensures the QApplication will be deleted
// event if we forget to call shutdownQtApp. Cleanly destroying this is
// important so QPA resources, in particular the XCB thread, are exited
// cleanly on quit. However, at present, the official policy is that static
// destruction is too late to call this, hence why we have shutdownQtApp()
2018-08-27 12:20:23 +00:00
static std : : unique_ptr < QApplication > static_qApp ;
2017-06-07 15:25:19 +00:00
Split flightgear::initApp() to make it usable even without FGGlobals initialized
- Add an optional argument to flightgear::initApp(): doInitQSettings.
This argument defaults to true, preserving initApp()'s behavior in
this respect. If this argument is set to false, FGGlobals doesn't have
to be initialized.
- New function flightgear::initQSettings(), called by
flightgear::initApp() when its 'doInitQSettings' argument is true.
This allows initializing the QSettings exactly when it is needed.
- New function flightgear::checkKeyboardModifiersForSettingFGRoot().
The code it contains used to be run from initApp(), which is
undesirable because:
1) initApp() is not only called at FG initialization (fgMainInit()),
but also from QtMessageBox(), from QtFileDialog::exec() and twice
from Options::setupRoot(). However, checking the Alt and Shift
modifiers to set 'fg-root' in QSettings to the special value
"!ask" only makes sense in fgMainInit(), not in these other
places.
2) This code relies on the QSettings to be set up, and therefore on
FGGlobals. Thus, freeing initApp() of its dependency on FGGlobals
requires splitting this keyboard modifiers checking code out of
initApp().
2016-12-26 20:45:44 +00:00
// Only requires FGGlobals to be initialized if 'doInitQSettings' is true.
// Safe to call several times.
void initApp ( int & argc , char * * argv , bool doInitQSettings )
2015-10-16 01:05:17 +00:00
{
static bool qtInitDone = false ;
2015-11-26 16:49:55 +00:00
static int s_argc ;
2015-10-16 01:05:17 +00:00
if ( ! qtInitDone ) {
qtInitDone = true ;
2016-02-02 20:44:46 +00:00
2020-09-17 20:44:50 +00:00
// Disable Qt 5.15 warnings about obsolete Connections/onFoo: syntax
// we cannot use the new syntax
// as long as we have to support Qt 5.9
qputenv ( " QT_LOGGING_RULES " , " qt.qml.connections.warning=false " ) ;
2016-02-02 20:44:46 +00:00
initQtResources ( ) ; // can't be called from a namespace
2015-11-26 16:49:55 +00:00
s_argc = argc ; // QApplication only stores a reference to argc,
// and may crash if it is freed
// http://doc.qt.io/qt-5/qguiapplication.html#QGuiApplication
2015-10-16 01:05:17 +00:00
2017-01-29 15:16:28 +00:00
// log to simgear instead of the console from Qt, so we go to
// whichever log locations SimGear has configured
qInstallMessageHandler ( simgearMessageOutput ) ;
2018-04-27 08:53:49 +00:00
// ensure we use desktop OpenGL, don't even fall back to ANGLE, since
// this gets into a knot on Optimus setups (since we export the magic
// Optimus / AMD symbols in main.cxx).
QCoreApplication : : setAttribute ( Qt : : AA_UseDesktopOpenGL ) ;
2018-07-03 08:48:49 +00:00
// becuase on Windows, Qt only supports integer scaling factors,
// forceibly enabling HighDpiScaling is controversial.
// leave things unset here, so users can use env var
// QT_AUTO_SCREEN_SCALE_FACTOR=1 to enable it at runtime
// QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
2020-06-18 12:23:26 +00:00
2017-06-07 15:25:19 +00:00
static_qApp . reset ( new QApplication ( s_argc , argv ) ) ;
static_qApp - > setOrganizationName ( " FlightGear " ) ;
static_qApp - > setApplicationName ( " FlightGear " ) ;
static_qApp - > setOrganizationDomain ( " flightgear.org " ) ;
2015-10-16 01:05:17 +00:00
2017-06-07 15:25:19 +00:00
static_qApp - > setDesktopFileName (
2017-05-16 11:51:46 +00:00
QStringLiteral ( " org.flightgear.FlightGear.desktop " ) ) ;
2020-06-18 12:23:26 +00:00
2018-08-27 12:20:23 +00:00
QTranslator * fallbackTranslator = new QTranslator ( static_qApp . get ( ) ) ;
2020-05-08 13:54:53 +00:00
if ( ! fallbackTranslator - > load ( QLatin1String ( " :/FlightGear_en_US.qm " ) ) ) {
2018-08-27 12:20:23 +00:00
qWarning ( ) < < " Failed to load default (en) translations " ;
delete fallbackTranslator ;
} else {
static_qApp - > installTranslator ( fallbackTranslator ) ;
}
2021-01-28 14:21:32 +00:00
qWarning ( ) < < " UI languages: " < < QLocale ( ) . uiLanguages ( ) ;
2019-04-17 14:11:15 +00:00
2018-08-27 12:20:23 +00:00
QTranslator * translator = new QTranslator ( static_qApp . get ( ) ) ;
2018-10-12 10:37:39 +00:00
// check for --langauge=xx option and prefer that over QLocale
// detection of the locale if it exists
2020-07-14 10:38:09 +00:00
auto lang = simgear : : strutils : : replace (
Options : : getArgValue ( argc , argv , " --language " ) ,
" - " ,
" _ " ) ;
2018-10-12 10:37:39 +00:00
if ( ! lang . empty ( ) ) {
QString localeFile = " FlightGear_ " + QString : : fromStdString ( lang ) ;
if ( translator - > load ( localeFile , QLatin1String ( " :/ " ) ) ) {
qInfo ( ) < < " Loaded translations based on --language from: " < < localeFile ;
static_qApp - > installTranslator ( translator ) ;
} else {
qInfo ( ) < < " --langauge was set, but no translations found at: " < < localeFile ;
delete translator ;
}
} else if ( translator - > load ( QLocale ( ) , QLatin1String ( " FlightGear " ) , QLatin1String ( " _ " ) , QLatin1String ( " :/ " ) ) ) {
2018-10-11 20:37:33 +00:00
// QLocale().name() looks like ' "it_IT" ' (without the outer
// quotes) when running FG on Linux with LANG=it_IT.UTF-8.
2021-01-28 14:21:32 +00:00
qWarning ( ) < < " Loaded translations for locale " < < QLocale ( ) . name ( ) ;
2018-08-27 12:20:23 +00:00
static_qApp - > installTranslator ( translator ) ;
} else {
delete translator ;
}
2017-05-16 11:51:46 +00:00
2016-02-02 20:44:46 +00:00
// reset numeric / collation locales as described at:
// http://doc.qt.io/qt-5/qcoreapplication.html#details
: : setlocale ( LC_NUMERIC , " C " ) ;
: : setlocale ( LC_COLLATE , " C " ) ;
2021-01-10 12:51:41 +00:00
# if defined(SG_MAC)
if ( cocoaIsRunningTranslocated ( ) ) {
addSentryBreadcrumb ( " did show translocation warning " , " info " ) ;
const char * titleKey = QT_TRANSLATE_NOOP ( " macTranslationWarning " , " Application running from download location " ) ;
const char * key = QT_TRANSLATE_NOOP ( " macTranslationWarning " , " FlightGear is running from the download image. For better performance and to avoid potential problems, "
" please copy FlightGear to some other location, such as your desktop or Applications folder. " ) ;
QString msg = qApp - > translate ( " macTranslationWarning " , key ) ;
QString title = qApp - > translate ( " macTranslationWarning " , titleKey ) ;
QMessageBox : : warning ( nullptr , title , msg ) ;
}
# endif
Split flightgear::initApp() to make it usable even without FGGlobals initialized
- Add an optional argument to flightgear::initApp(): doInitQSettings.
This argument defaults to true, preserving initApp()'s behavior in
this respect. If this argument is set to false, FGGlobals doesn't have
to be initialized.
- New function flightgear::initQSettings(), called by
flightgear::initApp() when its 'doInitQSettings' argument is true.
This allows initializing the QSettings exactly when it is needed.
- New function flightgear::checkKeyboardModifiersForSettingFGRoot().
The code it contains used to be run from initApp(), which is
undesirable because:
1) initApp() is not only called at FG initialization (fgMainInit()),
but also from QtMessageBox(), from QtFileDialog::exec() and twice
from Options::setupRoot(). However, checking the Alt and Shift
modifiers to set 'fg-root' in QSettings to the special value
"!ask" only makes sense in fgMainInit(), not in these other
places.
2) This code relies on the QSettings to be set up, and therefore on
FGGlobals. Thus, freeing initApp() of its dependency on FGGlobals
requires splitting this keyboard modifiers checking code out of
initApp().
2016-12-26 20:45:44 +00:00
}
2016-02-02 20:44:46 +00:00
Split flightgear::initApp() to make it usable even without FGGlobals initialized
- Add an optional argument to flightgear::initApp(): doInitQSettings.
This argument defaults to true, preserving initApp()'s behavior in
this respect. If this argument is set to false, FGGlobals doesn't have
to be initialized.
- New function flightgear::initQSettings(), called by
flightgear::initApp() when its 'doInitQSettings' argument is true.
This allows initializing the QSettings exactly when it is needed.
- New function flightgear::checkKeyboardModifiersForSettingFGRoot().
The code it contains used to be run from initApp(), which is
undesirable because:
1) initApp() is not only called at FG initialization (fgMainInit()),
but also from QtMessageBox(), from QtFileDialog::exec() and twice
from Options::setupRoot(). However, checking the Alt and Shift
modifiers to set 'fg-root' in QSettings to the special value
"!ask" only makes sense in fgMainInit(), not in these other
places.
2) This code relies on the QSettings to be set up, and therefore on
FGGlobals. Thus, freeing initApp() of its dependency on FGGlobals
requires splitting this keyboard modifiers checking code out of
initApp().
2016-12-26 20:45:44 +00:00
if ( doInitQSettings ) {
initQSettings ( ) ;
}
}
2017-06-07 15:25:19 +00:00
void shutdownQtApp ( )
{
2018-07-02 22:13:36 +00:00
// restore default message handler, otherwise Qt logging on
// shutdown crashes once sglog is killed
qInstallMessageHandler ( nullptr ) ;
2017-06-07 15:25:19 +00:00
static_qApp . reset ( ) ;
}
Split flightgear::initApp() to make it usable even without FGGlobals initialized
- Add an optional argument to flightgear::initApp(): doInitQSettings.
This argument defaults to true, preserving initApp()'s behavior in
this respect. If this argument is set to false, FGGlobals doesn't have
to be initialized.
- New function flightgear::initQSettings(), called by
flightgear::initApp() when its 'doInitQSettings' argument is true.
This allows initializing the QSettings exactly when it is needed.
- New function flightgear::checkKeyboardModifiersForSettingFGRoot().
The code it contains used to be run from initApp(), which is
undesirable because:
1) initApp() is not only called at FG initialization (fgMainInit()),
but also from QtMessageBox(), from QtFileDialog::exec() and twice
from Options::setupRoot(). However, checking the Alt and Shift
modifiers to set 'fg-root' in QSettings to the special value
"!ask" only makes sense in fgMainInit(), not in these other
places.
2) This code relies on the QSettings to be set up, and therefore on
FGGlobals. Thus, freeing initApp() of its dependency on FGGlobals
requires splitting this keyboard modifiers checking code out of
initApp().
2016-12-26 20:45:44 +00:00
// Requires FGGlobals to be initialized. Safe to call several times.
void initQSettings ( )
{
static bool qSettingsInitDone = false ;
if ( ! qSettingsInitDone ) {
2018-07-17 10:55:25 +00:00
qRegisterMetaType < QuantityValue > ( ) ;
qRegisterMetaTypeStreamOperators < QuantityValue > ( " QuantityValue " ) ;
Split flightgear::initApp() to make it usable even without FGGlobals initialized
- Add an optional argument to flightgear::initApp(): doInitQSettings.
This argument defaults to true, preserving initApp()'s behavior in
this respect. If this argument is set to false, FGGlobals doesn't have
to be initialized.
- New function flightgear::initQSettings(), called by
flightgear::initApp() when its 'doInitQSettings' argument is true.
This allows initializing the QSettings exactly when it is needed.
- New function flightgear::checkKeyboardModifiersForSettingFGRoot().
The code it contains used to be run from initApp(), which is
undesirable because:
1) initApp() is not only called at FG initialization (fgMainInit()),
but also from QtMessageBox(), from QtFileDialog::exec() and twice
from Options::setupRoot(). However, checking the Alt and Shift
modifiers to set 'fg-root' in QSettings to the special value
"!ask" only makes sense in fgMainInit(), not in these other
places.
2) This code relies on the QSettings to be set up, and therefore on
FGGlobals. Thus, freeing initApp() of its dependency on FGGlobals
requires splitting this keyboard modifiers checking code out of
initApp().
2016-12-26 20:45:44 +00:00
qSettingsInitDone = true ;
string fgHome = globals - > get_fg_home ( ) . utf8Str ( ) ;
QSettings : : setDefaultFormat ( QSettings : : IniFormat ) ;
QSettings : : setPath ( QSettings : : IniFormat , QSettings : : UserScope ,
QString : : fromStdString ( fgHome ) ) ;
}
}
bool checkKeyboardModifiersForSettingFGRoot ( )
{
initQSettings ( ) ;
2020-03-26 17:10:14 +00:00
# if defined(Q_OS_WIN)
const auto altState = GetAsyncKeyState ( VK_MENU ) ;
const auto shiftState = GetAsyncKeyState ( VK_SHIFT ) ;
if ( ( altState < 0 ) | | ( shiftState < 0 ) )
# else
Split flightgear::initApp() to make it usable even without FGGlobals initialized
- Add an optional argument to flightgear::initApp(): doInitQSettings.
This argument defaults to true, preserving initApp()'s behavior in
this respect. If this argument is set to false, FGGlobals doesn't have
to be initialized.
- New function flightgear::initQSettings(), called by
flightgear::initApp() when its 'doInitQSettings' argument is true.
This allows initializing the QSettings exactly when it is needed.
- New function flightgear::checkKeyboardModifiersForSettingFGRoot().
The code it contains used to be run from initApp(), which is
undesirable because:
1) initApp() is not only called at FG initialization (fgMainInit()),
but also from QtMessageBox(), from QtFileDialog::exec() and twice
from Options::setupRoot(). However, checking the Alt and Shift
modifiers to set 'fg-root' in QSettings to the special value
"!ask" only makes sense in fgMainInit(), not in these other
places.
2) This code relies on the QSettings to be set up, and therefore on
FGGlobals. Thus, freeing initApp() of its dependency on FGGlobals
requires splitting this keyboard modifiers checking code out of
initApp().
2016-12-26 20:45:44 +00:00
Qt : : KeyboardModifiers mods = qApp - > queryKeyboardModifiers ( ) ;
2020-03-26 17:10:14 +00:00
if ( mods & ( Qt : : AltModifier | Qt : : ShiftModifier ) )
# endif
{
Split flightgear::initApp() to make it usable even without FGGlobals initialized
- Add an optional argument to flightgear::initApp(): doInitQSettings.
This argument defaults to true, preserving initApp()'s behavior in
this respect. If this argument is set to false, FGGlobals doesn't have
to be initialized.
- New function flightgear::initQSettings(), called by
flightgear::initApp() when its 'doInitQSettings' argument is true.
This allows initializing the QSettings exactly when it is needed.
- New function flightgear::checkKeyboardModifiersForSettingFGRoot().
The code it contains used to be run from initApp(), which is
undesirable because:
1) initApp() is not only called at FG initialization (fgMainInit()),
but also from QtMessageBox(), from QtFileDialog::exec() and twice
from Options::setupRoot(). However, checking the Alt and Shift
modifiers to set 'fg-root' in QSettings to the special value
"!ask" only makes sense in fgMainInit(), not in these other
places.
2) This code relies on the QSettings to be set up, and therefore on
FGGlobals. Thus, freeing initApp() of its dependency on FGGlobals
requires splitting this keyboard modifiers checking code out of
initApp().
2016-12-26 20:45:44 +00:00
qWarning ( ) < < " Alt/shift pressed during launch " ;
return true ;
2015-10-16 01:05:17 +00:00
}
Split flightgear::initApp() to make it usable even without FGGlobals initialized
- Add an optional argument to flightgear::initApp(): doInitQSettings.
This argument defaults to true, preserving initApp()'s behavior in
this respect. If this argument is set to false, FGGlobals doesn't have
to be initialized.
- New function flightgear::initQSettings(), called by
flightgear::initApp() when its 'doInitQSettings' argument is true.
This allows initializing the QSettings exactly when it is needed.
- New function flightgear::checkKeyboardModifiersForSettingFGRoot().
The code it contains used to be run from initApp(), which is
undesirable because:
1) initApp() is not only called at FG initialization (fgMainInit()),
but also from QtMessageBox(), from QtFileDialog::exec() and twice
from Options::setupRoot(). However, checking the Alt and Shift
modifiers to set 'fg-root' in QSettings to the special value
"!ask" only makes sense in fgMainInit(), not in these other
places.
2) This code relies on the QSettings to be set up, and therefore on
FGGlobals. Thus, freeing initApp() of its dependency on FGGlobals
requires splitting this keyboard modifiers checking code out of
initApp().
2016-12-26 20:45:44 +00:00
return false ;
2015-10-16 01:05:17 +00:00
}
2017-02-20 00:19:13 +00:00
void restartTheApp ( )
2014-12-26 12:20:51 +00:00
{
2017-02-20 00:19:13 +00:00
QStringList fgArgs ;
2014-12-26 12:20:51 +00:00
2017-02-20 00:19:13 +00:00
// Spawn a new instance of myApplication:
QProcess proc ;
QStringList args ;
2014-12-26 12:20:51 +00:00
2018-07-02 22:13:36 +00:00
// ensure we release whatever mutex/lock file we have in home,
// so the new instance runs in writeable mode
fgShutdownHome ( ) ;
2017-02-20 00:19:13 +00:00
# if defined(Q_OS_MAC)
QDir dir ( qApp - > applicationDirPath ( ) ) ; // returns the 'MacOS' dir
dir . cdUp ( ) ; // up to 'contents' dir
dir . cdUp ( ) ; // up to .app dir
// see 'man open' for details, but '-n' ensures we launch a new instance,
// and we want to pass remaining arguments to us, not open.
args < < " -n " < < dir . absolutePath ( ) < < " --args " < < " --launcher " < < fgArgs ;
qDebug ( ) < < " args " < < args ;
proc . startDetached ( " open " , args ) ;
# else
args < < " --launcher " < < fgArgs ;
proc . startDetached ( qApp - > applicationFilePath ( ) , args ) ;
2017-01-30 07:00:37 +00:00
# endif
2017-02-20 00:19:13 +00:00
qApp - > exit ( - 1 ) ;
2014-12-26 12:20:51 +00:00
}
2017-02-20 00:19:13 +00:00
void launcherSetSceneryPaths ( )
2016-01-10 22:38:01 +00:00
{
globals - > clear_fg_scenery ( ) ;
2017-02-20 00:19:13 +00:00
// mimic what options.cxx does, so we can find airport data for parking
2016-01-10 22:38:01 +00:00
// positions
QSettings settings ;
// append explicit scenery paths
2020-03-10 16:10:55 +00:00
Q_FOREACH ( QString path , PathListModel : : readEnabledPaths ( " scenery-paths-v2 " ) ) {
2016-01-10 22:38:01 +00:00
globals - > append_fg_scenery ( path . toStdString ( ) ) ;
}
// append the TerraSync path
QString downloadDir = settings . value ( " download-dir " ) . toString ( ) ;
if ( downloadDir . isEmpty ( ) ) {
2016-06-23 13:26:34 +00:00
downloadDir = QString : : fromStdString ( flightgear : : defaultDownloadDir ( ) . utf8Str ( ) ) ;
2016-01-10 22:38:01 +00:00
}
SGPath terraSyncDir ( downloadDir . toStdString ( ) ) ;
terraSyncDir . append ( " TerraSync " ) ;
if ( terraSyncDir . exists ( ) ) {
2016-06-21 11:29:04 +00:00
globals - > append_fg_scenery ( terraSyncDir ) ;
2016-01-10 22:38:01 +00:00
}
2016-08-19 11:12:02 +00:00
// add the installation path since it contains default airport data,
// if terrasync is disabled or on first-launch
2018-11-06 17:06:57 +00:00
const SGPath rootScenery = globals - > get_fg_root ( ) / " Scenery " ;
if ( rootScenery . exists ( ) ) {
globals - > append_fg_scenery ( rootScenery ) ;
}
2016-01-10 22:38:01 +00:00
}
2017-02-20 00:19:13 +00:00
bool runLauncherDialog ( )
2015-10-16 01:05:17 +00:00
{
2017-02-20 00:19:13 +00:00
// Used for NavDataCache initialization: needed to find the apt.dat files
launcherSetSceneryPaths ( ) ;
// startup the nav-cache now. This pre-empts normal startup of
// the cache, but no harm done. (Providing scenery paths are consistent)
2017-01-26 00:31:41 +00:00
2017-02-20 00:19:13 +00:00
initNavCache ( ) ;
2014-12-26 12:20:51 +00:00
2017-07-19 18:24:33 +00:00
auto options = flightgear : : Options : : sharedInstance ( ) ;
if ( options - > isOptionSet ( " download-dir " ) ) {
// user set download-dir on command line, don't mess with it in the
// launcher GUI. We'll disable the UI.
LaunchConfig : : setEnableDownloadDirUI ( false ) ;
} else {
QSettings settings ;
2017-12-15 15:42:36 +00:00
QString downloadDir = settings . value ( " download-dir " ) . toString ( ) ;
2017-07-19 18:24:33 +00:00
if ( ! downloadDir . isEmpty ( ) ) {
options - > setOption ( " download-dir " , downloadDir . toStdString ( ) ) ;
}
2016-11-07 11:54:38 +00:00
}
2014-12-26 12:20:51 +00:00
2017-02-20 00:19:13 +00:00
fgInitPackageRoot ( ) ;
2016-01-10 18:56:46 +00:00
2020-03-17 11:46:26 +00:00
// setup package language
auto lang = options - > valueForOption ( " language " ) ;
2020-06-17 15:15:01 +00:00
if ( lang . empty ( ) ) {
2020-03-17 11:46:26 +00:00
const auto langName = QLocale : : languageToString ( QLocale { } . language ( ) ) ;
2020-06-17 15:15:01 +00:00
lang = langName . toStdString ( ) ;
2020-03-17 11:46:26 +00:00
}
2020-06-17 15:15:01 +00:00
// we will re-do this later, but we want to access translated strings
// from within the launcher
globals - > get_locale ( ) - > selectLanguage ( lang ) ;
2020-06-19 09:45:47 +00:00
globals - > packageRoot ( ) - > setLocale ( globals - > get_locale ( ) - > getPreferredLanguage ( ) ) ;
2020-06-17 15:15:01 +00:00
2017-02-20 00:19:13 +00:00
// startup the HTTP system now since packages needs it
FGHTTPClient * http = globals - > add_new_subsystem < FGHTTPClient > ( ) ;
2014-12-26 12:20:51 +00:00
2017-02-20 00:19:13 +00:00
// we guard against re-init in the global phase; bind and postinit
// will happen as normal
http - > init ( ) ;
2014-12-26 12:20:51 +00:00
2020-11-02 14:55:35 +00:00
QPointer < NaturalEarthDataLoaderThread > naturalEarthLoader = new NaturalEarthDataLoaderThread ;
2017-02-20 00:19:13 +00:00
naturalEarthLoader - > start ( ) ;
2016-07-08 08:36:42 +00:00
2017-02-20 00:19:13 +00:00
// avoid double Apple menu and other weirdness if both Qt and OSG
// try to initialise various Cocoa structures.
flightgear : : WindowBuilder : : setPoseAsStandaloneApp ( false ) ;
2014-12-26 12:20:51 +00:00
2020-07-30 11:30:23 +00:00
LauncherMainWindow dlg ( false ) ;
2019-12-17 22:40:25 +00:00
if ( options - > isOptionSet ( " enable-fullscreen " ) ) {
dlg . showFullScreen ( ) ;
} else {
dlg . show ( ) ;
}
2016-01-10 19:47:57 +00:00
2017-02-20 00:19:13 +00:00
int appResult = qApp - > exec ( ) ;
if ( appResult < = 0 ) {
return false ; // quit
2016-01-10 19:47:57 +00:00
}
2015-10-25 00:40:41 +00:00
2020-11-02 14:55:35 +00:00
// avoid crashes / NavCache races if the loader is still running after
// the launcher exits
if ( naturalEarthLoader ) {
naturalEarthLoader - > abandon ( ) ;
}
2020-10-25 18:26:00 +00:00
// avoid a race-y crash on the locale, if a scan thread is
// still running: this reset will cancel any running scan
LocalAircraftCache : : reset ( ) ;
2017-02-20 00:19:13 +00:00
// don't set scenery paths twice
globals - > clear_fg_scenery ( ) ;
2020-06-17 15:15:01 +00:00
globals - > get_locale ( ) - > clear ( ) ;
2015-11-03 21:28:36 +00:00
2017-02-20 00:19:13 +00:00
return true ;
2014-12-26 12:20:51 +00:00
}
2017-02-20 00:19:13 +00:00
bool runInAppLauncherDialog ( )
2014-12-26 12:20:51 +00:00
{
2020-07-30 11:30:23 +00:00
LauncherMainWindow dlg ( true ) ;
2017-02-20 00:19:13 +00:00
bool accepted = dlg . execInApp ( ) ;
if ( ! accepted ) {
return false ;
2014-12-26 12:20:51 +00:00
}
2017-02-20 00:19:13 +00:00
return true ;
2016-02-24 19:50:37 +00:00
}
2020-05-07 15:49:50 +00:00
static const char * static_lockFileDialog_Title =
QT_TRANSLATE_NOOP ( " LockFileDialog " , " Multiple copies of FlightGear running " ) ;
static const char * static_lockFileDialog_Text =
QT_TRANSLATE_NOOP ( " LockFileDialog " ,
" FlightGear has detected another copy is already running. "
" This copy will run in read-only mode, so downloads will not be possible, "
" and settings will not be saved. " ) ;
static const char * static_lockFileDialog_Info =
QT_TRANSLATE_NOOP ( " LockFileDialog " ,
" If you are sure another copy is not running on this computer, "
" you can choose to reset the lock file, and run this copy as normal. "
" Alternatively, you can close this copy of the software. " ) ;
2020-05-03 17:02:20 +00:00
LockFileDialogResult showLockFileDialog ( )
{
2020-10-25 18:26:00 +00:00
flightgear : : addSentryBreadcrumb ( " showing lock-file dialog " , " info " ) ;
2020-05-07 15:49:50 +00:00
QString title = qApp - > translate ( " LockFileDialog " , static_lockFileDialog_Title ) ;
QString text = qApp - > translate ( " LockFileDialog " , static_lockFileDialog_Text ) ;
QString infoText = qApp - > translate ( " LockFileDialog " , static_lockFileDialog_Info ) ;
2020-05-03 17:02:20 +00:00
QMessageBox mb ;
mb . setIconPixmap ( QPixmap ( " :/app-icon-large " ) ) ;
2020-05-07 15:49:50 +00:00
mb . setWindowTitle ( title ) ;
mb . setText ( text ) ;
mb . setInformativeText ( infoText ) ;
2020-05-03 17:02:20 +00:00
mb . addButton ( QMessageBox : : Ok ) ;
mb . setDefaultButton ( QMessageBox : : Ok ) ;
mb . addButton ( QMessageBox : : Reset ) ;
2020-05-07 15:49:50 +00:00
mb . addButton ( QMessageBox : : Close ) ;
2020-05-03 17:02:20 +00:00
int r = mb . exec ( ) ;
if ( r = = QMessageBox : : Reset )
return LockFileReset ;
2020-05-07 15:49:50 +00:00
if ( r = = QMessageBox : : Close )
return LockFileQuit ;
2020-05-03 17:02:20 +00:00
return LockFileContinue ;
}
2017-02-20 00:19:13 +00:00
} // of namespace flightgear
2016-01-17 19:10:22 +00:00
2015-10-16 01:05:17 +00:00
# include "QtLauncher.moc"