QGCApplication.cc 29.3 KB
Newer Older
1
 /*=====================================================================
2

Don Gagne's avatar
Don Gagne committed
3
 QGroundControl Open Source Ground Control Station
4

Don Gagne's avatar
Don Gagne committed
5
 (c) 2009 - 2015 QGROUNDCONTROL PROJECT <http://www.qgroundcontrol.org>
6

Don Gagne's avatar
Don Gagne committed
7
 This file is part of the QGROUNDCONTROL project
8

Don Gagne's avatar
Don Gagne committed
9 10 11 12
 QGROUNDCONTROL 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 3 of the License, or
 (at your option) any later version.
13

Don Gagne's avatar
Don Gagne committed
14 15 16 17
 QGROUNDCONTROL 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.
18

Don Gagne's avatar
Don Gagne committed
19 20
 You should have received a copy of the GNU General Public License
 along with QGROUNDCONTROL. If not, see <http://www.gnu.org/licenses/>.
21

Don Gagne's avatar
Don Gagne committed
22
 ======================================================================*/
pixhawk's avatar
pixhawk committed
23 24 25

/**
 * @file
Don Gagne's avatar
Don Gagne committed
26
 *   @brief Implementation of class QGCApplication
pixhawk's avatar
pixhawk committed
27 28 29 30 31 32 33 34 35 36 37 38 39 40
 *
 *   @author Lorenz Meier <mavteam@student.ethz.ch>
 *
 */

#include <QFile>
#include <QFlags>
#include <QSplashScreen>
#include <QPixmap>
#include <QDesktopWidget>
#include <QPainter>
#include <QStyleFactory>
#include <QAction>

41 42
#include <QDebug>

43
#include "VideoStreaming.h"
Gus Grubba's avatar
Gus Grubba committed
44

45
#include "QGC.h"
Don Gagne's avatar
Don Gagne committed
46
#include "QGCApplication.h"
pixhawk's avatar
pixhawk committed
47
#include "MainWindow.h"
pixhawk's avatar
pixhawk committed
48
#include "GAudioOutput.h"
49
#include "CmdLineOptParser.h"
Don Gagne's avatar
Don Gagne committed
50
#include "QGCMessageBox.h"
51 52 53 54
#include "MainWindow.h"
#include "UDPLink.h"
#include "QGCSingleton.h"
#include "LinkManager.h"
55
#include "HomePositionManager.h"
56
#include "UASMessageHandler.h"
57
#include "AutoPilotPluginManager.h"
58
#include "QGCTemporaryFile.h"
59
#include "QGCFileDialog.h"
60
#include "QGCPalette.h"
61
#include "QGCLoggingCategory.h"
Don Gagne's avatar
Don Gagne committed
62 63
#include "ViewWidgetController.h"
#include "ParameterEditorController.h"
Don Gagne's avatar
Don Gagne committed
64
#include "CustomCommandWidgetController.h"
Don Gagne's avatar
Don Gagne committed
65 66 67
#include "FlightModesComponentController.h"
#include "AirframeComponentController.h"
#include "SensorsComponentController.h"
68
#include "PowerComponentController.h"
Don Gagne's avatar
Don Gagne committed
69
#include "RadioComponentController.h"
Don Gagne's avatar
Don Gagne committed
70
#include "ScreenToolsController.h"
71 72
#include "AutoPilotPlugin.h"
#include "VehicleComponent.h"
Don Gagne's avatar
Don Gagne committed
73
#include "FirmwarePluginManager.h"
74
#include "MultiVehicleManager.h"
Don Gagne's avatar
Don Gagne committed
75
#include "Generic/GenericFirmwarePlugin.h"
76
#include "APM/ArduCopterFirmwarePlugin.h"
Don Gagne's avatar
Don Gagne committed
77
#include "PX4/PX4FirmwarePlugin.h"
78
#include "Vehicle.h"
79
#include "MavlinkQmlSingleton.h"
80
#include "JoystickManager.h"
81
#include "QmlObjectListModel.h"
Don Gagne's avatar
Don Gagne committed
82
#include "MissionManager.h"
83 84
#include "QGroundControlQmlGlobal.h"
#include "HomePositionManager.h"
85
#include "FlightMapSettings.h"
86 87
#include "QGCQGeoCoordinate.h"
#include "CoordinateVector.h"
88 89 90 91 92 93 94 95 96

#ifndef __ios__
    #include "SerialLink.h"
#endif

#ifndef __mobile__
    #include "FirmwareUpgradeController.h"
    #include "JoystickConfigController.h"
#endif
97

98
#ifdef QGC_RTLAB_ENABLED
99
    #include "OpalLink.h"
100
#endif
101 102 103


QGCApplication* QGCApplication::_app = NULL;
pixhawk's avatar
pixhawk committed
104

Don Gagne's avatar
Don Gagne committed
105 106 107 108
const char* QGCApplication::_deleteAllSettingsKey = "DeleteAllSettingsNextBoot";
const char* QGCApplication::_settingsVersionKey = "SettingsVersion";
const char* QGCApplication::_savedFilesLocationKey = "SavedFilesLocation";
const char* QGCApplication::_promptFlightDataSave = "PromptFLightDataSave";
109
const char* QGCApplication::_styleKey = "StyleIsDark";
Don Gagne's avatar
Don Gagne committed
110 111 112 113

const char* QGCApplication::_defaultSavedFileDirectoryName = "QGroundControl";
const char* QGCApplication::_savedFileMavlinkLogDirectoryName = "FlightData";
const char* QGCApplication::_savedFileParameterDirectoryName = "SavedParameters";
pixhawk's avatar
pixhawk committed
114

Don Gagne's avatar
Don Gagne committed
115 116
const char* QGCApplication::_darkStyleFile = ":/res/styles/style-dark.css";
const char* QGCApplication::_lightStyleFile = ":/res/styles/style-light.css";
117

118 119

// Qml Singleton factories
120

Don Gagne's avatar
Don Gagne committed
121
static QObject* screenToolsControllerSingletonFactory(QQmlEngine*, QJSEngine*)
122
{
Don Gagne's avatar
Don Gagne committed
123 124
    ScreenToolsController* screenToolsController = new ScreenToolsController;
    return screenToolsController;
125 126
}

127 128 129 130 131
static QObject* mavlinkQmlSingletonFactory(QQmlEngine*, QJSEngine*)
{
    return new MavlinkQmlSingleton;
}

132 133 134 135 136
static QObject* qgroundcontrolQmlGlobalSingletonFactory(QQmlEngine*, QJSEngine*)
{
    return new QGroundControlQmlGlobal;
}

dogmaphobic's avatar
dogmaphobic committed
137
#if defined(QGC_GST_STREAMING)
138 139
#ifdef Q_OS_MAC
#ifndef __ios__
140
#ifdef QGC_INSTALL_RELEASE
dogmaphobic's avatar
dogmaphobic committed
141 142 143 144 145 146
static void qgcputenv(const QString& key, const QString& root, const QString& path)
{
    QString value = root + path;
    qputenv(key.toStdString().c_str(), QByteArray(value.toStdString().c_str()));
}
#endif
147 148
#endif
#endif
149
#endif
dogmaphobic's avatar
dogmaphobic committed
150

pixhawk's avatar
pixhawk committed
151 152 153 154 155 156 157 158 159 160
/**
 * @brief Constructor for the main application.
 *
 * This constructor initializes and starts the whole application. It takes standard
 * command-line parameters
 *
 * @param argc The number of command-line parameters
 * @param argv The string array of parameters
 **/

161 162 163 164
QGCApplication::QGCApplication(int &argc, char* argv[], bool unitTesting)
    : QApplication(argc, argv)
    , _runningUnitTests(unitTesting)
    , _styleIsDark(true)
165
	, _fakeMobile(false)
Don Gagne's avatar
Don Gagne committed
166 167 168
#ifdef UNITTEST_BUILD
    , _useNewMissionEditor(true)    // Unit Tests run new mission editor
#endif
169 170 171
#ifdef QT_DEBUG
    , _testHighDPI(false)
#endif
pixhawk's avatar
pixhawk committed
172
{
173 174
    Q_ASSERT(_app == NULL);
    _app = this;
175

176
    // This prevents usage of QQuickWidget to fail since it doesn't support native widget siblings
dogmaphobic's avatar
dogmaphobic committed
177
#ifndef __android__
178
    setAttribute(Qt::AA_DontCreateNativeWidgetSiblings);
dogmaphobic's avatar
dogmaphobic committed
179
#endif
180 181 182 183
    
    // Parse command line options
    
    bool fClearSettingsOptions = false; // Clear stored settings
184 185
    bool logging = false;               // Turn on logging
    QString loggingOptions;
186 187
    
    CmdLineOpt_t rgCmdLineOptions[] = {
188 189 190
        { "--clear-settings",   &fClearSettingsOptions, NULL },
        { "--logging",          &logging,               &loggingOptions },
		{ "--fake-mobile",      &_fakeMobile,           NULL },
191
#ifdef QT_DEBUG
192
        { "--test-high-dpi",    &_testHighDPI,          NULL },
193
#endif
194 195 196 197
        // Add additional command line option flags here
    };
    
    ParseCmdLineOptions(argc, argv, rgCmdLineOptions, sizeof(rgCmdLineOptions)/sizeof(rgCmdLineOptions[0]), false);
198

dogmaphobic's avatar
dogmaphobic committed
199
#ifdef __mobile__
Don Gagne's avatar
Don Gagne committed
200
    QLoggingCategory::setFilterRules(QStringLiteral("*Log.debug=false"));
201
#else
202 203 204 205 206
    QString filterRules;
    
    // Turn off bogus ssl warning
    filterRules += "qt.network.ssl.warning=false\n";
    
207 208 209 210 211 212 213 214 215 216 217 218 219 220 221
    if (logging) {
        QStringList logList = loggingOptions.split(",");
        
        if (logList[0] == "full") {
            filterRules += "*Log.debug=true\n";
            for(int i=1; i<logList.count(); i++) {
                filterRules += logList[i];
                filterRules += ".debug=false\n";
            }
        } else {
            foreach(QString rule, logList) {
                filterRules += rule;
                filterRules += ".debug=true\n";
            }
        }
Don Gagne's avatar
Don Gagne committed
222 223 224 225 226
        
        if (_runningUnitTests) {
            // We need to turn off these warnings until the firmware meta data is cleaned up
            filterRules += "PX4ParameterLoaderLog.warning=false\n";
        }
227
    } else {
Don Gagne's avatar
Don Gagne committed
228 229 230 231 232
        if (_runningUnitTests) {
            // We need to turn off these warnings until the firmware meta data is cleaned up
            QLoggingCategory::setFilterRules(QStringLiteral("PX4ParameterLoaderLog.warning=false"));
        }
        
233 234
        // First thing we want to do is set up the qtlogging.ini file. If it doesn't already exist we copy
        // it to the correct location. This way default debug builds will have logging turned off.
235

236 237 238
        static const char* qtProjectDir = "QtProject";
        static const char* qtLoggingFile = "qtlogging.ini";
        bool loggingDirectoryOk = false;
239

240 241 242 243 244 245 246 247 248
        QDir iniFileLocation(QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation));
        if (!iniFileLocation.cd(qtProjectDir)) {
            if (!iniFileLocation.mkdir(qtProjectDir)) {
                qDebug() << "Unable to create qtlogging.ini directory" << iniFileLocation.filePath(qtProjectDir);
            } else {
                if (!iniFileLocation.cd(qtProjectDir)) {
                    qDebug() << "Unable to access qtlogging.ini directory" << iniFileLocation.filePath(qtProjectDir);;
                }
                loggingDirectoryOk = true;
249
            }
250
        } else {
251 252
            loggingDirectoryOk = true;
        }
253

254
        if (loggingDirectoryOk) {
Don Gagne's avatar
Don Gagne committed
255
            qDebug () << "Logging ini file directory" << iniFileLocation.absolutePath();
256 257 258 259 260 261 262 263 264 265 266
            if (!iniFileLocation.exists(qtLoggingFile)) {
                QFile loggingFile(iniFileLocation.filePath(qtLoggingFile));
                if (loggingFile.open(QIODevice::WriteOnly | QIODevice::Text)) {
                    QTextStream out(&loggingFile);
                    out << "[Rules]\n";
                    out << "*Log.debug=false\n";
                    foreach(QString category, QGCLoggingCategoryRegister::instance()->registeredCategories()) {
                        out << category << ".debug=false\n";
                    }
                } else {
                    qDebug() << "Unable to create logging file" << QString(qtLoggingFile) << "in" << iniFileLocation;
267
                }
268 269 270
            }
        }
    }
271 272 273
    
    qDebug() << "Filter rules" << filterRules;
    QLoggingCategory::setFilterRules(filterRules);
Don Gagne's avatar
Don Gagne committed
274
#endif
275

276
    // Set up timer for delayed missing fact display
Don Gagne's avatar
Don Gagne committed
277 278 279
    _missingParamsDelayedDisplayTimer.setSingleShot(true);
    _missingParamsDelayedDisplayTimer.setInterval(_missingParamsDelayedDisplayTimerTimeout);
    connect(&_missingParamsDelayedDisplayTimer, &QTimer::timeout, this, &QGCApplication::_missingParamsDisplay);
Don Gagne's avatar
Don Gagne committed
280
    
Don Gagne's avatar
Don Gagne committed
281
    // Set application information
282 283 284 285 286 287 288 289 290
    if (_runningUnitTests) {
        // We don't want unit tests to use the same QSettings space as the normal app. So we tweak the app
        // name. Also we want to run unit tests with clean settings every time.
        setApplicationName(QString("%1_unittest").arg(QGC_APPLICATION_NAME));
    } else {
        setApplicationName(QGC_APPLICATION_NAME);
    }
    setOrganizationName(QGC_ORG_NAME);
    setOrganizationDomain(QGC_ORG_DOMAIN);
291

292 293 294 295
    // Version string is build from component parts. Format is:
    //  vMajor.Minor.BuildNumber BuildType
    QString versionString("v%1.%2.%3 %4");
    versionString = versionString.arg(QGC_APPLICATION_VERSION_MAJOR).arg(QGC_APPLICATION_VERSION_MINOR).arg(QGC_APPLICATION_VERSION_BUILDNUMBER).arg(QGC_APPLICATION_VERSION_BUILDTYPE);
Don Gagne's avatar
Don Gagne committed
296
    this->setApplicationVersion(versionString);
297

298 299
    // Set settings format
    QSettings::setDefaultFormat(QSettings::IniFormat);
300

301
    QSettings settings;
302 303 304 305
#ifdef UNITTEST_BUILD
    qDebug() << "Settings location" << settings.fileName();
    Q_ASSERT(settings.isWritable());
#endif
306
    // The setting will delete all settings on this boot
Don Gagne's avatar
Don Gagne committed
307
    fClearSettingsOptions |= settings.contains(_deleteAllSettingsKey);
308

Don Gagne's avatar
Don Gagne committed
309
    if (_runningUnitTests) {
310
        // Unit tests run with clean settings
Don Gagne's avatar
Don Gagne committed
311 312
        fClearSettingsOptions = true;
    }
313

314 315 316
    if (fClearSettingsOptions) {
        // User requested settings to be cleared on command line
        settings.clear();
317
        settings.setValue(_settingsVersionKey, QGC_SETTINGS_VERSION);
318
    }
Gus Grubba's avatar
Gus Grubba committed
319

320 321
    // Initialize Video Streaming
    initializeVideoStreaming(argc, argv);
Gus Grubba's avatar
Gus Grubba committed
322

323 324
}

Don Gagne's avatar
Don Gagne committed
325 326
QGCApplication::~QGCApplication()
{
Don Gagne's avatar
Don Gagne committed
327
    _destroySingletons();
328
    shutdownVideoStreaming();
Don Gagne's avatar
Don Gagne committed
329 330
}

331
void QGCApplication::_initCommon(void)
332 333
{
    QSettings settings;
334

Don Gagne's avatar
Don Gagne committed
335 336 337 338
    // Register our Qml objects
    
    qmlRegisterType<QGCPalette>("QGroundControl.Palette", 1, 0, "QGCPalette");
    
339 340 341 342 343 344 345 346
    qmlRegisterUncreatableType<AutoPilotPlugin>     ("QGroundControl.AutoPilotPlugin",  1, 0, "AutoPilotPlugin",        "Reference only");
    qmlRegisterUncreatableType<VehicleComponent>    ("QGroundControl.AutoPilotPlugin",  1, 0, "VehicleComponent",       "Reference only");
    qmlRegisterUncreatableType<Vehicle>             ("QGroundControl.Vehicle",          1, 0, "Vehicle",                "Reference only");
    qmlRegisterUncreatableType<MissionItem>         ("QGroundControl.Vehicle",          1, 0, "MissionItem",            "Reference only");
    qmlRegisterUncreatableType<MissionManager>      ("QGroundControl.Vehicle",          1, 0, "MissionManager",         "Reference only");
    qmlRegisterUncreatableType<JoystickManager>     ("QGroundControl.JoystickManager",  1, 0, "JoystickManager",        "Reference only");
    qmlRegisterUncreatableType<Joystick>            ("QGroundControl.JoystickManager",  1, 0, "Joystick",               "Reference only");
    qmlRegisterUncreatableType<QmlObjectListModel>  ("QGroundControl",                  1, 0, "QmlObjectListModel",     "Reference only");
347 348
    qmlRegisterUncreatableType<QGCQGeoCoordinate>   ("QGroundControl",                  1, 0, "QGCQGeoCoordinate",      "Reference only");
    qmlRegisterUncreatableType<CoordinateVector>    ("QGroundControl",                  1, 0, "CoordinateVector",       "Reference only");
Don Gagne's avatar
Don Gagne committed
349
    
350 351 352 353 354 355 356 357 358
    qmlRegisterType<ViewWidgetController>           ("QGroundControl.Controllers", 1, 0, "ViewWidgetController");
    qmlRegisterType<ParameterEditorController>      ("QGroundControl.Controllers", 1, 0, "ParameterEditorController");
    qmlRegisterType<CustomCommandWidgetController>  ("QGroundControl.Controllers", 1, 0, "CustomCommandWidgetController");
    qmlRegisterType<FlightModesComponentController> ("QGroundControl.Controllers", 1, 0, "FlightModesComponentController");
    qmlRegisterType<AirframeComponentController>    ("QGroundControl.Controllers", 1, 0, "AirframeComponentController");
    qmlRegisterType<SensorsComponentController>     ("QGroundControl.Controllers", 1, 0, "SensorsComponentController");
    qmlRegisterType<PowerComponentController>       ("QGroundControl.Controllers", 1, 0, "PowerComponentController");
    qmlRegisterType<RadioComponentController>       ("QGroundControl.Controllers", 1, 0, "RadioComponentController");
    qmlRegisterType<ScreenToolsController>          ("QGroundControl.Controllers", 1, 0, "ScreenToolsController");
Don Gagne's avatar
Don Gagne committed
359 360
    
#ifndef __mobile__
361
    qmlRegisterType<FirmwareUpgradeController>      ("QGroundControl.Controllers", 1, 0, "FirmwareUpgradeController");
362
    qmlRegisterType<JoystickConfigController>       ("QGroundControl.Controllers", 1, 0, "JoystickConfigController");
Don Gagne's avatar
Don Gagne committed
363 364
#endif
    
365
    // Register Qml Singletons
366 367 368
    qmlRegisterSingletonType<QGroundControlQmlGlobal>   ("QGroundControl",                          1, 0, "QGroundControl",         qgroundcontrolQmlGlobalSingletonFactory);
    qmlRegisterSingletonType<ScreenToolsController>     ("QGroundControl.ScreenToolsController",    1, 0, "ScreenToolsController",  screenToolsControllerSingletonFactory);
    qmlRegisterSingletonType<MavlinkQmlSingleton>       ("QGroundControl.Mavlink",                  1, 0, "Mavlink",                mavlinkQmlSingletonFactory);
Don Gagne's avatar
Don Gagne committed
369
    
370 371 372 373 374
    // Show user an upgrade message if the settings version has been bumped up
    bool settingsUpgraded = false;
    if (settings.contains(_settingsVersionKey)) {
        if (settings.value(_settingsVersionKey).toInt() != QGC_SETTINGS_VERSION) {
            settingsUpgraded = true;
375
        }
376 377 378 379
    } else if (settings.allKeys().count()) {
        // Settings version key is missing and there are settings. This is an upgrade scenario.
        settingsUpgraded = true;
    }
380

381
    if (settingsUpgraded) {
382
        settings.clear();
383
        settings.setValue(_settingsVersionKey, QGC_SETTINGS_VERSION);
384 385 386
        QGCMessageBox::information(tr("Settings Cleared"),
                                   tr("The format for QGroundControl saved settings has been modified. "
                                      "Your saved settings have been reset to defaults."));
387
    }
388

Don Gagne's avatar
Don Gagne committed
389
    // Load saved files location and validate
390

Don Gagne's avatar
Don Gagne committed
391 392 393
    QString savedFilesLocation;
    if (settings.contains(_savedFilesLocationKey)) {
        savedFilesLocation = settings.value(_savedFilesLocationKey).toString();
394 395 396 397 398 399 400
        if (!validatePossibleSavedFilesLocation(savedFilesLocation)) {
            savedFilesLocation.clear();
        }
    }
    
    if (savedFilesLocation.isEmpty()) {
        // No location set (or invalid). Create a default one in Documents standard location.
401

Don Gagne's avatar
Don Gagne committed
402
        QString documentsLocation = QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation);
403

Don Gagne's avatar
Don Gagne committed
404
        QDir documentsDir(documentsLocation);
405 406 407
        if (!documentsDir.exists()) {
            qWarning() << "Documents directory doesn't exist" << documentsDir.absolutePath();
        }
408

409
        bool pathCreated = documentsDir.mkpath(_defaultSavedFileDirectoryName);
410
        Q_UNUSED(pathCreated);
411 412
        Q_ASSERT(pathCreated);
        savedFilesLocation = documentsDir.filePath(_defaultSavedFileDirectoryName);
Don Gagne's avatar
Don Gagne committed
413
    }
414

Don Gagne's avatar
Don Gagne committed
415 416 417 418 419
    if (!savedFilesLocation.isEmpty()) {
        if (!validatePossibleSavedFilesLocation(savedFilesLocation)) {
            savedFilesLocation.clear();
        }
    }
420
    qDebug() << "Saved files location" << savedFilesLocation;
421 422 423 424 425 426
    settings.setValue(_savedFilesLocationKey, savedFilesLocation);
}

bool QGCApplication::_initForNormalAppBoot(void)
{
    QSettings settings;
427

428
    _createSingletons();
429

430 431 432
    _styleIsDark = settings.value(_styleKey, _styleIsDark).toBool();
    _loadCurrentStyle();
    
433
    // Temp hack for new mission editor
434 435 436 437
#ifdef __mobile__
    // Mobile builds always use new Mission Editor
    _useNewMissionEditor = true;
#else
438
    _useNewMissionEditor = settings.value("UseNewMissionEditor", false).toBool();
439
#endif
440
    
441
    // Show splash screen
Don Gagne's avatar
Don Gagne committed
442
    QPixmap splashImage(":/res/SplashScreen");
443
    QSplashScreen* splashScreen = new QSplashScreen(splashImage);
444 445
    // Delete splash screen after mainWindow was displayed
    splashScreen->setAttribute(Qt::WA_DeleteOnClose);
446
    splashScreen->show();
lm's avatar
lm committed
447
    processEvents();
448
    splashScreen->showMessage(tr("Loading application fonts"), Qt::AlignLeft | Qt::AlignBottom, QColor(62, 93, 141));
449 450 451
    // Exit main application when last window is closed
    connect(this, SIGNAL(lastWindowClosed()), this, SLOT(quit()));

pixhawk's avatar
pixhawk committed
452
    // Start the user interface
453
    splashScreen->showMessage(tr("Starting user interface"), Qt::AlignLeft | Qt::AlignBottom, QColor(62, 93, 141));
454
    MainWindow* mainWindow = MainWindow::_create(splashScreen);
Don Gagne's avatar
Don Gagne committed
455
    Q_CHECK_PTR(mainWindow);
456

457 458 459 460 461
    // If we made it this far and we still don't have a location. Either the specfied location was invalid
    // or we coudn't create a default location. Either way, we need to let the user know and prompt for a new
    /// settings.
    QString savedFilesLocation = settings.value(_savedFilesLocationKey).toString();
    if (savedFilesLocation.isEmpty()) {
462 463 464
        QGCMessageBox::warning(
            tr("Bad save location"),
            tr("The location to save files to is invalid, or cannot be written to. Please provide a new one."));
465 466
        mainWindow->showSettings();
    }
467

lm's avatar
lm committed
468
    // Remove splash screen
Don Gagne's avatar
Don Gagne committed
469 470
    splashScreen->finish(mainWindow);
    mainWindow->splashScreenFinished();
471

472
    // Now that main window is up check for lost log files
473 474
    connect(this, &QGCApplication::checkForLostLogFiles, MAVLinkProtocol::instance(), &MAVLinkProtocol::checkForLostLogFiles);
    emit checkForLostLogFiles();
475 476 477 478

    // Load known link configurations
    LinkManager::instance()->loadLinkConfigurationList();

479
    return true;
pixhawk's avatar
pixhawk committed
480 481
}

482
bool QGCApplication::_initForUnitTests(void)
pixhawk's avatar
pixhawk committed
483
{
484
    return true;
pixhawk's avatar
pixhawk committed
485 486
}

Don Gagne's avatar
Don Gagne committed
487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507
void QGCApplication::deleteAllSettingsNextBoot(void)
{
    QSettings settings;
    settings.setValue(_deleteAllSettingsKey, true);
}

void QGCApplication::clearDeleteAllSettingsNextBoot(void)
{
    QSettings settings;
    settings.remove(_deleteAllSettingsKey);
}

void QGCApplication::setSavedFilesLocation(QString& location)
{
    QSettings settings;
    settings.setValue(_savedFilesLocationKey, location);
}

bool QGCApplication::validatePossibleSavedFilesLocation(QString& location)
{
    // Make sure we can write to the directory
508

Don Gagne's avatar
Don Gagne committed
509
    QString filename = QDir(location).filePath("QGCTempXXXXXXXX.tmp");
510
    QGCTemporaryFile tempFile(filename);
Don Gagne's avatar
Don Gagne committed
511 512 513
    if (!tempFile.open()) {
        return false;
    }
514

515
    tempFile.remove();
516

Don Gagne's avatar
Don Gagne committed
517 518 519 520 521 522
    return true;
}

QString QGCApplication::savedFilesLocation(void)
{
    QSettings settings;
523

Don Gagne's avatar
Don Gagne committed
524 525 526 527 528 529 530
    return settings.value(_savedFilesLocationKey).toString();
}

QString QGCApplication::savedParameterFilesLocation(void)
{
    QString location;
    QDir    parentDir(savedFilesLocation());
531

Don Gagne's avatar
Don Gagne committed
532
    location = parentDir.filePath(_savedFileParameterDirectoryName);
533

Don Gagne's avatar
Don Gagne committed
534 535 536 537 538 539 540
    if (!QDir(location).exists()) {
        // If directory doesn't exist, try to create it
        if (!parentDir.mkpath(_savedFileParameterDirectoryName)) {
            // Return an error
            location.clear();
        }
    }
541

Don Gagne's avatar
Don Gagne committed
542 543 544 545 546 547 548
    return location;
}

QString QGCApplication::mavlinkLogFilesLocation(void)
{
    QString location;
    QDir    parentDir(savedFilesLocation());
549

Don Gagne's avatar
Don Gagne committed
550
    location = parentDir.filePath(_savedFileMavlinkLogDirectoryName);
551

Don Gagne's avatar
Don Gagne committed
552 553 554 555 556 557 558
    if (!QDir(location).exists()) {
        // If directory doesn't exist, try to create it
        if (!parentDir.mkpath(_savedFileMavlinkLogDirectoryName)) {
            // Return an error
            location.clear();
        }
    }
559

Don Gagne's avatar
Don Gagne committed
560 561 562 563 564 565
    return location;
}

bool QGCApplication::promptFlightDataSave(void)
{
    QSettings settings;
566

Don Gagne's avatar
Don Gagne committed
567 568 569 570 571 572 573 574 575 576 577 578
    return settings.value(_promptFlightDataSave, true).toBool();
}

void QGCApplication::setPromptFlightDataSave(bool promptForSave)
{
    QSettings settings;
    settings.setValue(_promptFlightDataSave, promptForSave);
}

/// @brief Returns the QGCApplication object singleton.
QGCApplication* qgcApp(void)
{
579 580 581 582 583 584 585 586 587
    Q_ASSERT(QGCApplication::_app);
    return QGCApplication::_app;
}

/// @brief We create all the non-ui based singletons here instead of allowing them to be created randomly
///         by calls to instance. The reason being that depending on boot sequence the singleton may end
///         up being creating on something other than the main thread.
void QGCApplication::_createSingletons(void)
{
Don Gagne's avatar
Don Gagne committed
588
    // The order here is important since the singletons reference each other
589

590 591 592 593 594 595 596 597 598 599
    // No dependencies
    FlightMapSettings* flightMapSettings = FlightMapSettings::_createSingleton();
    Q_UNUSED(flightMapSettings);
    Q_ASSERT(flightMapSettings);
    
    // No dependencies
    HomePositionManager* homePositionManager = HomePositionManager::_createSingleton();
    Q_UNUSED(homePositionManager);
    Q_ASSERT(homePositionManager);

Don Gagne's avatar
Don Gagne committed
600 601 602 603 604 605 606
    // No dependencies
    FirmwarePlugin* firmwarePlugin = GenericFirmwarePlugin::_createSingleton();
    Q_UNUSED(firmwarePlugin);
    Q_ASSERT(firmwarePlugin);
    
    // No dependencies
    firmwarePlugin = PX4FirmwarePlugin::_createSingleton();
607
    firmwarePlugin = ArduCopterFirmwarePlugin::_createSingleton();
Don Gagne's avatar
Don Gagne committed
608 609 610 611 612 613
    
    // No dependencies
    FirmwarePluginManager* firmwarePluginManager = FirmwarePluginManager::_createSingleton();
    Q_UNUSED(firmwarePluginManager);
    Q_ASSERT(firmwarePluginManager);
    
614 615 616 617 618
    // No dependencies
    MultiVehicleManager* multiVehicleManager = MultiVehicleManager::_createSingleton();
    Q_UNUSED(multiVehicleManager);
    Q_ASSERT(multiVehicleManager);
    
619 620 621 622 623
    // No dependencies
    JoystickManager* joystickManager = JoystickManager::_createSingleton();
    Q_UNUSED(joystickManager);
    Q_ASSERT(joystickManager);
    
Don Gagne's avatar
Don Gagne committed
624
    // No dependencies
625 626 627
    GAudioOutput* audio = GAudioOutput::_createSingleton();
    Q_UNUSED(audio);
    Q_ASSERT(audio);
628

Don Gagne's avatar
Don Gagne committed
629
    // No dependencies
Don Gagne's avatar
Don Gagne committed
630
    LinkManager* linkManager = LinkManager::_createSingleton();
631 632 633
    Q_UNUSED(linkManager);
    Q_ASSERT(linkManager);

634
    // Need MultiVehicleManager
Don Gagne's avatar
Don Gagne committed
635
    AutoPilotPluginManager* pluginManager = AutoPilotPluginManager::_createSingleton();
636 637
    Q_UNUSED(pluginManager);
    Q_ASSERT(pluginManager);
Don Gagne's avatar
Don Gagne committed
638

639
    // Need MultiVehicleManager
640
    UASMessageHandler* messageHandler = UASMessageHandler::_createSingleton();
641 642 643
    Q_UNUSED(messageHandler);
    Q_ASSERT(messageHandler);

644
    // Needs MultiVehicleManager
Don Gagne's avatar
Don Gagne committed
645
    FactSystem* factSystem = FactSystem::_createSingleton();
Don Gagne's avatar
Don Gagne committed
646 647
    Q_UNUSED(factSystem);
    Q_ASSERT(factSystem);
648

649 650 651 652
    // Needs everything!
    MAVLinkProtocol* mavlink = MAVLinkProtocol::_createSingleton();
    Q_UNUSED(mavlink);
    Q_ASSERT(mavlink);
653

654 655
}

Don Gagne's avatar
Don Gagne committed
656
void QGCApplication::_destroySingletons(void)
657
{
658 659 660
    if (MainWindow::instance()) {
        delete MainWindow::instance();
    }
661

662 663 664 665 666 667 668
    if (LinkManager::instance(true /* nullOk */)) {
        // This will close/delete all connections
        LinkManager::instance()->_shutdown();
    }

    // Let the signals flow through the main thread
    processEvents(QEventLoop::ExcludeUserInputEvents);
669

Don Gagne's avatar
Don Gagne committed
670
    // Take down singletons in reverse order of creation
671

672
    MAVLinkProtocol::_deleteSingleton();
Don Gagne's avatar
Don Gagne committed
673
    FactSystem::_deleteSingleton();
674
    UASMessageHandler::_deleteSingleton();
Don Gagne's avatar
Don Gagne committed
675 676
    AutoPilotPluginManager::_deleteSingleton();
    LinkManager::_deleteSingleton();
677
    GAudioOutput::_deleteSingleton();
678
    JoystickManager::_deleteSingleton();
679
    MultiVehicleManager::_deleteSingleton();
Don Gagne's avatar
Don Gagne committed
680 681 682
    FirmwarePluginManager::_deleteSingleton();
    GenericFirmwarePlugin::_deleteSingleton();
    PX4FirmwarePlugin::_deleteSingleton();
683
    ArduCopterFirmwarePlugin::_deleteSingleton();
684 685
    HomePositionManager::_deleteSingleton();
    FlightMapSettings::_deleteSingleton();
Don Gagne's avatar
Don Gagne committed
686
}
687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704

void QGCApplication::informationMessageBoxOnMainThread(const QString& title, const QString& msg)
{
    QGCMessageBox::information(title, msg);
}

void QGCApplication::warningMessageBoxOnMainThread(const QString& title, const QString& msg)
{
    QGCMessageBox::warning(title, msg);
}

void QGCApplication::criticalMessageBoxOnMainThread(const QString& title, const QString& msg)
{
    QGCMessageBox::critical(title, msg);
}

void QGCApplication::saveTempFlightDataLogOnMainThread(QString tempLogfile)
{
705 706 707 708 709 710 711 712 713 714 715 716 717
    bool saveError;
    do{
        saveError = false;
        QString saveFilename = QGCFileDialog::getSaveFileName(
            MainWindow::instance(),
            tr("Save Flight Data Log"),
            qgcApp()->mavlinkLogFilesLocation(),
            tr("Flight Data Log Files (*.mavlink)"),
            "mavlink");
    
        if (!saveFilename.isEmpty()) {
            // if file exsits already, try to remove it first to overwrite it
            if(QFile::exists(saveFilename) && !QFile::remove(saveFilename)){
718
                // if the file cannot be removed, prompt user and ask new path
719
                saveError = true;
720
                QGCMessageBox::warning("File Error","Could not overwrite existing file.\nPlease provide a different file name to save to.");
721 722 723
            } else if(!QFile::copy(tempLogfile, saveFilename)) {
                // if file could not be copied, prompt user and ask new path
                saveError = true;
724
                QGCMessageBox::warning("File Error","Could not create file.\nPlease provide a different file name to save to.");
725 726 727
            }
        }
    } while(saveError); // if the file could not be overwritten, ask for new file
728 729
    QFile::remove(tempLogfile);
}
730 731 732 733

void QGCApplication::setStyle(bool styleIsDark)
{
    QSettings settings;
734

735 736 737 738 739 740 741 742 743 744
    settings.setValue(_styleKey, styleIsDark);
    _styleIsDark = styleIsDark;
    _loadCurrentStyle();
    emit styleChanged(_styleIsDark);
}

void QGCApplication::_loadCurrentStyle(void)
{
    bool success = true;
    QString styles;
745

746 747
    // Signal to the user that the app will pause to apply a new stylesheet
    setOverrideCursor(Qt::WaitCursor);
748

749 750 751 752 753 754 755 756 757
    // The dark style sheet is the master. Any other selected style sheet just overrides
    // the colors of the master sheet.
    QFile masterStyleSheet(_darkStyleFile);
    if (masterStyleSheet.open(QIODevice::ReadOnly | QIODevice::Text)) {
        styles = masterStyleSheet.readAll();
    } else {
        qDebug() << "Unable to load master dark style sheet";
        success = false;
    }
758

759 760 761 762 763 764 765 766 767 768 769
    if (success && !_styleIsDark) {
        qDebug() << "LOADING LIGHT";
        // Load the slave light stylesheet.
        QFile styleSheet(_lightStyleFile);
        if (styleSheet.open(QIODevice::ReadOnly | QIODevice::Text)) {
            styles += styleSheet.readAll();
        } else {
            qDebug() << "Unable to load slave light sheet:";
            success = false;
        }
    }
Don Gagne's avatar
Don Gagne committed
770
    
771
    // Now that we have the styles loaded we need to adjust the font sizes.
Don Gagne's avatar
Don Gagne committed
772 773 774 775 776 777
    
    QString fSmall  = QString("%1px;").arg(ScreenToolsController::smallFontPixelSize_s());
    QString fNormal = QString("%1px;").arg(ScreenToolsController::defaultFontPixelSize_s());
    QString fLarge  = QString("%1px;").arg(ScreenToolsController::largeFontPixelSize_s());
    
    qDebug() << fSmall << fNormal << fLarge;
778 779 780 781 782

    styles.replace("FONT_SMALL",  fSmall);
    styles.replace("FONT_NORMAL", fNormal);
    styles.replace("FONT_LARGE",  fLarge);
    setStyleSheet(styles);
783

784 785 786 787
    if (!success) {
        // Fall back to plastique if we can't load our own
        setStyle("plastique");
    }
788

789
    QGCPalette::setGlobalTheme(_styleIsDark ? QGCPalette::Dark : QGCPalette::Light);
790

791 792 793
    // Finally restore the cursor before returning.
    restoreOverrideCursor();
}
Don Gagne's avatar
Don Gagne committed
794

Don Gagne's avatar
Don Gagne committed
795
void QGCApplication::reportMissingParameter(int componentId, const QString& name)
796
{
Don Gagne's avatar
Don Gagne committed
797 798
    _missingParams += QString("%1:%2").arg(componentId).arg(name);
    _missingParamsDelayedDisplayTimer.start();
799
}
800

Don Gagne's avatar
Don Gagne committed
801 802
/// Called when the delay timer fires to show the missing parameters warning
void QGCApplication::_missingParamsDisplay(void)
803
{
Don Gagne's avatar
Don Gagne committed
804
    Q_ASSERT(_missingParams.count());
805
    
Don Gagne's avatar
Don Gagne committed
806 807 808 809
    QString params;
    foreach (QString name, _missingParams) {
        if (params.isEmpty()) {
            params += name;
810
        } else {
Don Gagne's avatar
Don Gagne committed
811
            params += QString(", %1").arg(name);
812 813
        }
    }
Don Gagne's avatar
Don Gagne committed
814
    _missingParams.clear();
815
    
816 817 818 819 820 821
    QGCMessageBox::critical(
        "Missing Parameters",
        QString("Parameters missing from firmware: %1.\n\n"
                "You should quit QGroundControl immediately and update your firmware.").arg(params));
}

Don Gagne's avatar
Don Gagne committed
822 823 824 825 826 827 828 829 830
void QGCApplication::showToolBarMessage(const QString& message)
{
    MainWindow* mainWindow = MainWindow::instance();
    if (mainWindow) {
        mainWindow->getMainToolBar()->showToolBarMessage(message);
    } else {
        QGCMessageBox::information("", message);
    }
}