From 42022df009f940d97da8b2337e983494fece090d Mon Sep 17 00:00:00 2001 From: Philippe Renon Date: Thu, 11 Apr 2013 19:47:05 +0200 Subject: [PATCH] OP-69 allow to set language (translation) in use for first launch through factory defaults OP-723 do not hard code locale to en_US in order to allow locale specific display and entry of numbers and dates + revisited the GCS startup sequence --- ground/openpilotgcs/src/app/main.cpp | 399 ++++++++++++++++----------- 1 file changed, 245 insertions(+), 154 deletions(-) diff --git a/ground/openpilotgcs/src/app/main.cpp b/ground/openpilotgcs/src/app/main.cpp index 1c3e64d59..d051f06ca 100644 --- a/ground/openpilotgcs/src/app/main.cpp +++ b/ground/openpilotgcs/src/app/main.cpp @@ -26,6 +26,32 @@ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ +/* + The GCS locale is set to the system locale by default unless the "hidden" setting General/Locale has a value. + The user can not change General/Locale from the Options dialog. + + The GCS language will default to the GCS locale unless the General/OverrideLanguage has a value. + The user can change General/OverrideLanguage to any available language from the Options dialog. + + Both General/Locale and General/OverrideLanguage can be set from the command line or through the the factory defaults file. + + + + + The -reset switch will clear all the user settings and will trigger a reload of the factory defaults. + + You can combine it with the -configfile= command line argument to quickly switch between multiple settings files. + + [code] + openpilotgcs -reset -configfile=./MyOpenPilotGCS.xml + [/code] + + The specified file will be used to load the factory defaults from but only when the user settings are empty. + If the user settings are not empty the file will not be used. + This switch is useful on the 1st run when the user settings are empty or in combination with -reset. + + */ + #include "qtsingleapplication.h" #include "utils/xmlconfig.h" #include "gcssplashscreen.h" @@ -35,6 +61,7 @@ #include #include +#include #include #include #include @@ -50,12 +77,28 @@ #include #include -#include +namespace { -enum { OptionIndent = 4, DescriptionIndent = 24 }; +typedef QList PluginSpecSet; +typedef QMap AppOptions; +typedef QMap FoundAppOptions; + +enum { + OptionIndent = 4, DescriptionIndent = 24 +}; static const char *appNameC = "OpenPilot GCS"; + static const char *corePluginNameC = "Core"; + +#ifdef Q_OS_MAC +static const char *SHARE_PATH = "/../Resources"; +#else +static const char *SHARE_PATH = "/../share/openpilotgcs"; +#endif + +static const char *DEFAULT_CONFIG_FILENAME = "OpenPilotGCS.xml"; + static const char *fixedOptionsC = " [OPTION]... [FILE]...\n" "Options:\n" @@ -65,25 +108,24 @@ static const char *fixedOptionsC = " -clean-config Delete all existing configuration settings\n" " -exit-after-config Exit GCS after manipulating configuration settings\n" " -D key=value Override configuration settings e.g: -D General/OverrideLanguage=de\n" -" -configfile=value Default configuration file to load if settings file is empty\n"; -static const char *HELP_OPTION1 = "-h"; -static const char *HELP_OPTION2 = "-help"; -static const char *HELP_OPTION3 = "/h"; -static const char *HELP_OPTION4 = "--help"; -static const char *VERSION_OPTION = "-version"; -static const char *CLIENT_OPTION = "-client"; -static const char *CONFIG_OPTION = "-D"; -static const char *CLEAN_CONFIG_OPTION = "-clean-config"; -static const char *EXIT_AFTER_CONFIG_OPTION = "-exit-after-config"; +" -reset Reset user settings to factory defaults.\n"; -typedef QList PluginSpecSet; - -static const char *DEFAULT_CONFIG_FILENAME = "OpenPilotGCS.xml"; +static QLatin1String HELP_OPTION1("-h"); +static QLatin1String HELP_OPTION2("-help"); +static QLatin1String HELP_OPTION3("/h"); +static QLatin1String HELP_OPTION4("--help"); +static QLatin1String VERSION_OPTION("-version"); +static QLatin1String CLIENT_OPTION("-client"); +static QLatin1String CONFIG_OPTION("-D"); +static QLatin1String CLEAN_CONFIG_OPTION("-clean-config"); +static QLatin1String EXIT_AFTER_CONFIG_OPTION("-exit-after-config"); +static QLatin1String RESET("-reset"); +static QLatin1String NO_SPLASH("-no-splash"); // Helpers for displaying messages. Note that there is no console on Windows. #ifdef Q_OS_WIN // Format as
 HTML
-static inline void toHtml(QString &t)
+inline void toHtml(QString &t)
 {
     t.replace(QLatin1Char('&'), QLatin1String("&"));
     t.replace(QLatin1Char('<'), QLatin1String("<"));
@@ -92,58 +134,57 @@ static inline void toHtml(QString &t)
     t.append(QLatin1String("
")); } -static void displayHelpText(QString t) // No console on Windows. +void displayHelpText(QString t) // No console on Windows. { toHtml(t); QMessageBox::information(0, QLatin1String(appNameC), t); } -static void displayError(const QString &t) // No console on Windows. +void displayError(const QString &t) // No console on Windows. { QMessageBox::critical(0, QLatin1String(appNameC), t); } #else -static void displayHelpText(const QString &t) +void displayHelpText(const QString &t) { qWarning("%s", qPrintable(t)); } -static void displayError(const QString &t) +void displayError(const QString &t) { qCritical("%s", qPrintable(t)); } #endif -static void printVersion(const ExtensionSystem::PluginSpec *coreplugin, - const ExtensionSystem::PluginManager &pm) +void printVersion(const ExtensionSystem::PluginSpec *coreplugin, const ExtensionSystem::PluginManager &pm) { QString version; QTextStream str(&version); - str << '\n' << appNameC << ' ' << coreplugin->version()<< " based on Qt " << qVersion() << "\n\n"; + str << '\n' << appNameC << ' ' << coreplugin->version() << " based on Qt " << qVersion() << "\n\n"; pm.formatPluginVersions(str); str << '\n' << coreplugin->copyright() << '\n'; displayHelpText(version); } -static void printHelp(const QString &a0, const ExtensionSystem::PluginManager &pm) +void printHelp(const QString &a0, const ExtensionSystem::PluginManager &pm) { QString help; QTextStream str(&help); - str << "Usage: " << a0 << fixedOptionsC; + str << "Usage: " << a0 << fixedOptionsC; ExtensionSystem::PluginManager::formatOptions(str, OptionIndent, DescriptionIndent); - pm.formatPluginOptions(str, OptionIndent, DescriptionIndent); + pm.formatPluginOptions(str, OptionIndent, DescriptionIndent); displayHelpText(help); } -static inline QString msgCoreLoadFailure(const QString &why) +inline QString msgCoreLoadFailure(const QString &why) { return QCoreApplication::translate("Application", "Failed to load core: %1").arg(why); } -static inline QString msgSendArgumentFailed() +inline QString msgSendArgumentFailed() { return QCoreApplication::translate("Application", "Unable to send command line arguments to the already running instance. It appears to be not responding."); } @@ -151,18 +192,20 @@ static inline QString msgSendArgumentFailed() // Prepare a remote argument: If it is a relative file, add the current directory // since the the central instance might be running in a different directory. -static inline QString prepareRemoteArgument(const QString &a) +inline QString prepareRemoteArgument(const QString &a) { QFileInfo fi(a); - if (!fi.exists()) + if (!fi.exists()) { return a; - if (fi.isRelative()) + } + if (fi.isRelative()) { return fi.absoluteFilePath(); + } return a; } // Send the arguments to an already running instance of OpenPilot GCS -static bool sendArguments(SharedTools::QtSingleApplication &app, const QStringList &arguments) +bool sendArguments(SharedTools::QtSingleApplication &app, const QStringList &arguments) { if (!arguments.empty()) { // Send off arguments @@ -182,7 +225,21 @@ static bool sendArguments(SharedTools::QtSingleApplication &app, const QStringLi return true; } -static inline QStringList getPluginPaths() +void systemInit() +{ +#ifdef Q_OS_MAC + // increase the number of file that can be opened in OpenPilot GCS + struct rlimit rl; + getrlimit(RLIMIT_NOFILE, &rl); + rl.rlim_cur = rl.rlim_max; + setrlimit(RLIMIT_NOFILE, &rl); +#endif +#ifdef Q_OS_LINUX + QApplication::setAttribute(Qt::AA_X11InitThreads, true); +#endif +} + +inline QStringList getPluginPaths() { QStringList rc; // Figure out root: Up one from 'bin' @@ -206,22 +263,43 @@ static inline QStringList getPluginPaths() return rc; } -static void loadFactorySettings(QSettings &settings) +AppOptions options() { - QDir directory(QCoreApplication::applicationDirPath()); -#ifdef Q_OS_MAC - directory.cdUp(); - directory.cd("Resources"); -#else - directory.cdUp(); - directory.cd("share"); - directory.cd("openpilotgcs"); -#endif - directory.cd("default_configurations"); + AppOptions appOptions; + appOptions.insert(HELP_OPTION1, false); + appOptions.insert(HELP_OPTION2, false); + appOptions.insert(HELP_OPTION3, false); + appOptions.insert(HELP_OPTION4, false); + appOptions.insert(VERSION_OPTION, false); + appOptions.insert(CLIENT_OPTION, false); + appOptions.insert(CONFIG_OPTION, true); + appOptions.insert(CLEAN_CONFIG_OPTION, false); + appOptions.insert(EXIT_AFTER_CONFIG_OPTION, false); + appOptions.insert(RESET, false); + appOptions.insert(NO_SPLASH, false); + return appOptions; +} + +FoundAppOptions parseCommandLine(SharedTools::QtSingleApplication &app, ExtensionSystem::PluginManager &pluginManager, QString &errorMessage) +{ + FoundAppOptions foundAppOptions; + const QStringList arguments = app.arguments(); + if (arguments.size() > 1) { + AppOptions appOptions = options(); + if (!pluginManager.parseOptions(arguments, appOptions, &foundAppOptions, &errorMessage)) { +// displayError(errorMessage); +// printHelp(QFileInfo(app.applicationFilePath()).baseName(), pluginManager); + } + } + return foundAppOptions; +} + +void loadFactoryDefaults(QSettings &settings) +{ + QDir directory(QCoreApplication::applicationDirPath() + QString(SHARE_PATH) + QString("/default_configurations")); qDebug() << "Looking for configuration files in:" << directory.absolutePath(); - QString filename; // check if command line contains a config file name QString commandLine; foreach(QString str, qApp->arguments()) { @@ -229,6 +307,7 @@ static void loadFactorySettings(QSettings &settings) commandLine = str.split("=").at(1); } } + QString filename; if (!commandLine.isEmpty() && QFile::exists(directory.absolutePath() + QDir::separator() + commandLine)) { // use file name specified on command line filename = directory.absolutePath() + QDir::separator() + commandLine; @@ -244,21 +323,18 @@ static void loadFactorySettings(QSettings &settings) } // create settings from file - QSettings *qs = new QSettings(filename, XmlConfig::XmlSettingsFormat); + QSettings qs(filename, XmlConfig::XmlSettingsFormat); // transfer loaded settings to application settings - QStringList keys = qs->allKeys(); + QStringList keys = qs.allKeys(); foreach(QString key, keys) { - settings.setValue(key, qs->value(key)); + settings.setValue(key, qs.value(key)); } - // and delete loaded settings - delete qs; - qDebug() << "Configuration file" << filename << "was loaded."; } -static void overrideSettings(QSettings &settings, int argc, char **argv) +void overrideSettings(QSettings &settings, int argc, char **argv) { // Options like -DMy/setting=test QRegExp rx("([^=]+)=(.*)"); @@ -277,73 +353,24 @@ static void overrideSettings(QSettings &settings, int argc, char **argv) QList keys = settingOptions.keys(); foreach (QString key, keys) { + qDebug() << "Overriding user setting:" << key << "with value" << settingOptions.value(key); settings.setValue(key, settingOptions.value(key)); } + settings.sync(); } -#ifdef Q_OS_MAC -# define SHARE_PATH "/../Resources" -#else -# define SHARE_PATH "/../share/openpilotgcs" -#endif - -int main(int argc, char **argv) +void loadTranslators(QString language) { - QElapsedTimer timer; - timer.start(); + // TODO static!?! + static QTranslator translator; + static QTranslator qtTranslator; -#ifdef Q_OS_MAC - // increase the number of file that can be opened in OpenPilot GCS - struct rlimit rl; - getrlimit(RLIMIT_NOFILE, &rl); - rl.rlim_cur = rl.rlim_max; - setrlimit(RLIMIT_NOFILE, &rl); -#endif -#ifdef Q_OS_LINUX - QApplication::setAttribute(Qt::AA_X11InitThreads, true); -#endif - - // Set the default locale to EN, if this is not set the system locale will be used - // and as of now we dont want that behaviour. - QLocale::setDefault(QLocale::English); - - SharedTools::QtSingleApplication app((QLatin1String(appNameC)), argc, argv); - - // Open splash screen - GCSSplashScreen splash; - splash.show(); - - // Must be done before any QSettings class is created - QSettings::setPath(XmlConfig::XmlSettingsFormat, QSettings::SystemScope, - QCoreApplication::applicationDirPath() + QLatin1String(SHARE_PATH)); - // keep this in sync with the MainWindow ctor in coreplugin/mainwindow.cpp - QSettings settings(XmlConfig::XmlSettingsFormat, QSettings::UserScope, QLatin1String("OpenPilot"), - QLatin1String("OpenPilotGCS_config")); - - if (!settings.allKeys().count()) { - qDebug() << "No user setting, loading factory defaults."; - // no user settings, so try to load some default ones - loadFactorySettings(settings); - } - - // override setting with command line provided settings - overrideSettings(settings, argc, argv); - - QString locale = QLocale::system().name(); - qDebug() << "main - system locale:" << locale; - - QString language = QLocale::system().name(); - language = settings.value("General/OverrideLanguage", language).toString(); - qDebug() << "main - language:" << language; - - QTranslator translator; - const QString &creatorTrPath = QCoreApplication::applicationDirPath() + QLatin1String(SHARE_PATH "/translations"); + const QString &creatorTrPath = QCoreApplication::applicationDirPath() + QLatin1String(SHARE_PATH) + QLatin1String("/translations"); if (translator.load(QLatin1String("openpilotgcs_") + language, creatorTrPath)) { const QString &qtTrPath = QLibraryInfo::location(QLibraryInfo::TranslationsPath); const QString &qtTrFile = QLatin1String("qt_") + language; // Binary installer puts Qt tr files into creatorTrPath - QTranslator qtTranslator; if (qtTranslator.load(qtTrFile, qtTrPath) || qtTranslator.load(qtTrFile, creatorTrPath)) { QCoreApplication::installTranslator(&translator); QCoreApplication::installTranslator(&qtTranslator); @@ -352,38 +379,98 @@ int main(int argc, char **argv) translator.load(QString()); } } - app.setProperty("qtc_locale", locale); // Do we need this? +} - splash.showProgressMessage(QObject::tr("Application starting...")); +} // namespace anonymous - // Load +int main(int argc, char **argv) +{ + QElapsedTimer timer; + timer.start(); + + // low level init + systemInit(); + + // create application + SharedTools::QtSingleApplication app((QLatin1String(appNameC)), argc, argv); + + // initialize the plugin manager ExtensionSystem::PluginManager pluginManager; pluginManager.setFileExtension(QLatin1String("pluginspec")); + pluginManager.setPluginPaths(getPluginPaths()); - const QStringList pluginPaths = getPluginPaths(); - pluginManager.setPluginPaths(pluginPaths); - - const QStringList arguments = app.arguments(); - QMap foundAppOptions; - if (arguments.size() > 1) { - QMap appOptions; - appOptions.insert(QLatin1String(HELP_OPTION1), false); - appOptions.insert(QLatin1String(HELP_OPTION2), false); - appOptions.insert(QLatin1String(HELP_OPTION3), false); - appOptions.insert(QLatin1String(HELP_OPTION4), false); - appOptions.insert(QLatin1String(VERSION_OPTION), false); - appOptions.insert(QLatin1String(CLIENT_OPTION), false); - appOptions.insert(QLatin1String(CONFIG_OPTION), true); - appOptions.insert(QLatin1String(CLEAN_CONFIG_OPTION), false); - appOptions.insert(QLatin1String(EXIT_AFTER_CONFIG_OPTION), false); - QString errorMessage; - if (!pluginManager.parseOptions(arguments, appOptions, &foundAppOptions, &errorMessage)) { - displayError(errorMessage); - printHelp(QFileInfo(app.applicationFilePath()).baseName(), pluginManager); - return -1; - } + // parse command line + qDebug() << "Command line" << app.arguments();; + QString errorMessage; + FoundAppOptions foundAppOptions = parseCommandLine(app, pluginManager, errorMessage); + if (!errorMessage.isEmpty()) { + displayError(errorMessage); + printHelp(QFileInfo(app.applicationFilePath()).baseName(), pluginManager); + return -1; } + // load user settings + // Must be done before any QSettings class is created + // keep this in sync with the MainWindow ctor in coreplugin/mainwindow.cpp + QString settingsPath = QCoreApplication::applicationDirPath() + QLatin1String(SHARE_PATH); + qDebug() << "Loading user settings from" << settingsPath; + QSettings::setPath(XmlConfig::XmlSettingsFormat, QSettings::SystemScope, settingsPath); + QSettings settings(XmlConfig::XmlSettingsFormat, QSettings::UserScope, QLatin1String("OpenPilot"), + QLatin1String("OpenPilotGCS_config")); + + // need to reset all user settings? + if (foundAppOptions.contains(RESET)) { + qDebug() << "Resetting user settings!"; + settings.clear(); + } + + // check if we have user settings + if (!settings.allKeys().count()) { + // no user settings, load the factory defaults + qDebug() << "No user settings found, loading factory defaults..."; + loadFactoryDefaults(settings); + } + + // override settings with command line provided values + // take notice that the overridden values will be saved in the user settings and will continue to be effective + // in subsequent GCS runs + overrideSettings(settings, argc, argv); + + // initialize GCS locale + // use the value defined by the General/Locale setting or default to system locale. + // the General/Locale setting is not available in the Options dialog, it is a hidden setting but can still be changed: + // - through the command line + // - editing the factory defaults XML file before 1st launch + // - editing the user XML file + QString localeName = settings.value("General/Locale", QLocale::system().name()).toString(); + QLocale::setDefault(localeName); + + // some debuging + qDebug() << "main - system locale:" << QLocale::system().name(); + qDebug() << "main - GCS locale:" << QLocale().name(); + + // load translation file + // the language used is defined by the General/OverrideLanguage setting (defaults to GCS locale) + // if the translation file for the given language is not found, GCS will default to built in English. + QString language = settings.value("General/OverrideLanguage", localeName).toString(); + qDebug() << "main - translation language:" << language; + loadTranslators(language); + + app.setProperty("qtc_locale", localeName); // Do we need this? + + // open the splash screen + GCSSplashScreen *splash = 0; + if (!foundAppOptions.contains(NO_SPLASH)) { + splash = new GCSSplashScreen(); + // show splash + splash->showProgressMessage(QObject::tr("Application starting...")); + splash->show(); + // connect to track progress of plugin manager + QObject::connect(&pluginManager, SIGNAL(pluginAboutToBeLoaded(ExtensionSystem::PluginSpec*)), + splash, SLOT(showPluginLoadingProgress(ExtensionSystem::PluginSpec*))); + } + + // find and load core plugin const PluginSpecSet plugins = pluginManager.plugins(); ExtensionSystem::PluginSpec *coreplugin = 0; foreach (ExtensionSystem::PluginSpec *spec, plugins) { @@ -393,7 +480,7 @@ int main(int argc, char **argv) } } if (!coreplugin) { - QString nativePaths = QDir::toNativeSeparators(pluginPaths.join(QLatin1String(","))); + QString nativePaths = QDir::toNativeSeparators(getPluginPaths().join(QLatin1String(","))); const QString reason = QCoreApplication::translate("Application", "Could not find 'Core.pluginspec' in %1").arg( nativePaths); displayError(msgCoreLoadFailure(reason)); @@ -403,28 +490,27 @@ int main(int argc, char **argv) displayError(msgCoreLoadFailure(coreplugin->errorString())); return 1; } - if (foundAppOptions.contains(QLatin1String(VERSION_OPTION))) { + + if (foundAppOptions.contains(VERSION_OPTION)) { printVersion(coreplugin, pluginManager); return 0; } - if (foundAppOptions.contains(QLatin1String(EXIT_AFTER_CONFIG_OPTION))) { + if (foundAppOptions.contains(EXIT_AFTER_CONFIG_OPTION)) { return 0; } - if (foundAppOptions.contains(QLatin1String(HELP_OPTION1)) - || foundAppOptions.contains(QLatin1String(HELP_OPTION2)) - || foundAppOptions.contains(QLatin1String(HELP_OPTION3)) - || foundAppOptions.contains(QLatin1String(HELP_OPTION4))) { + if (foundAppOptions.contains(HELP_OPTION1) + || foundAppOptions.contains(HELP_OPTION2) + || foundAppOptions.contains(HELP_OPTION3) + || foundAppOptions.contains(HELP_OPTION4)) { printHelp(QFileInfo(app.applicationFilePath()).baseName(), pluginManager); return 0; } + const bool isFirstInstance = !app.isRunning(); - if (!isFirstInstance && foundAppOptions.contains(QLatin1String(CLIENT_OPTION))) { + if (!isFirstInstance && foundAppOptions.contains(CLIENT_OPTION)) { return sendArguments(app, pluginManager.arguments()) ? 0 : -1; } - QObject::connect(&pluginManager, SIGNAL(pluginAboutToBeLoaded(ExtensionSystem::PluginSpec*)), - &splash, SLOT(showPluginLoadingProgress(ExtensionSystem::PluginSpec*))); - pluginManager.loadPlugins(); if (coreplugin->hasError()) { @@ -434,19 +520,21 @@ int main(int argc, char **argv) { QStringList errors; - foreach (ExtensionSystem::PluginSpec *p, pluginManager.plugins()) - if (p->hasError()) + foreach (ExtensionSystem::PluginSpec *p, pluginManager.plugins()) { + if (p->hasError()) { errors.append(p->errorString()); - if (!errors.isEmpty()) + } + } + if (!errors.isEmpty()) { QMessageBox::warning(0, - QCoreApplication::translate("Application", "OpenPilot GCS - Plugin loader messages"), - errors.join(QString::fromLatin1("\n\n"))); + QCoreApplication::translate("Application", "OpenPilot GCS - Plugin loader messages"), + errors.join(QString::fromLatin1("\n\n"))); + } } if (isFirstInstance) { // Set up lock and remote arguments for the first instance only. - // Silently fallback to unconnected instances for any subsequent - // instances. + // Silently fallback to unconnected instances for any subsequent instances. app.initialize(); QObject::connect(&app, SIGNAL(messageReceived(QString)), coreplugin->plugin(), SLOT(remoteArgument(QString))); } @@ -455,9 +543,12 @@ int main(int argc, char **argv) // Do this after the event loop has started QTimer::singleShot(100, &pluginManager, SLOT(startTests())); - //Update message and postpone closing of splashscreen 3 seconds - splash.showProgressMessage(QObject::tr("Application started.")); - QTimer::singleShot(1500, &splash, SLOT(close())); + if (splash) { + // Update message and postpone closing of splashscreen 3 seconds + splash->showProgressMessage(QObject::tr("Application started.")); + QTimer::singleShot(1500, splash, SLOT(close())); + // TODO delete splash + } qDebug() << "main - main took" << timer.elapsed() << "ms";