QGCApplication.cc 25.8 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
 *
 *   @author Lorenz Meier <mavteam@student.ethz.ch>
 *
 */

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

40 41
#include <QDebug>

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

44
#include "QGC.h"
Don Gagne's avatar
Don Gagne committed
45
#include "QGCApplication.h"
pixhawk's avatar
pixhawk committed
46
#include "MainWindow.h"
pixhawk's avatar
pixhawk committed
47
#include "GAudioOutput.h"
48
#include "CmdLineOptParser.h"
49 50 51
#include "MainWindow.h"
#include "UDPLink.h"
#include "LinkManager.h"
52
#include "HomePositionManager.h"
53
#include "UASMessageHandler.h"
54
#include "AutoPilotPluginManager.h"
55
#include "QGCTemporaryFile.h"
56
#include "QGCPalette.h"
Don Gagne's avatar
Don Gagne committed
57
#include "QGCMapPalette.h"
58
#include "QGCLoggingCategory.h"
Don Gagne's avatar
Don Gagne committed
59 60
#include "ViewWidgetController.h"
#include "ParameterEditorController.h"
Don Gagne's avatar
Don Gagne committed
61
#include "CustomCommandWidgetController.h"
Don Gagne's avatar
Don Gagne committed
62
#include "FlightModesComponentController.h"
Don Gagne's avatar
Don Gagne committed
63
#include "APMFlightModesComponentController.h"
Don Gagne's avatar
Don Gagne committed
64 65
#include "AirframeComponentController.h"
#include "SensorsComponentController.h"
66
#include "PowerComponentController.h"
Don Gagne's avatar
Don Gagne committed
67
#include "RadioComponentController.h"
Don Gagne's avatar
Don Gagne committed
68
#include "ScreenToolsController.h"
69 70
#include "AutoPilotPlugin.h"
#include "VehicleComponent.h"
Don Gagne's avatar
Don Gagne committed
71
#include "FirmwarePluginManager.h"
72
#include "MultiVehicleManager.h"
Don Gagne's avatar
Don Gagne committed
73
#include "Generic/GenericFirmwarePlugin.h"
74
#include "APM/ArduCopterFirmwarePlugin.h"
75 76
#include "APM/ArduPlaneFirmwarePlugin.h"
#include "APM/ArduRoverFirmwarePlugin.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
#include "MainToolBarController.h"
89
#include "MissionController.h"
Don Gagne's avatar
Don Gagne committed
90
#include "MissionCommands.h"
91 92 93
#include "FlightDisplayViewController.h"
#include "VideoSurface.h"
#include "VideoReceiver.h"
94 95 96 97 98 99

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

#ifndef __mobile__
100 101
    #include "QGCFileDialog.h"
    #include "QGCMessageBox.h"
102 103 104
    #include "FirmwareUpgradeController.h"
    #include "JoystickConfigController.h"
#endif
105

106
#ifdef QGC_RTLAB_ENABLED
107
    #include "OpalLink.h"
108
#endif
109 110 111


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

113 114 115 116 117 118
const char* QGCApplication::_deleteAllSettingsKey           = "DeleteAllSettingsNextBoot";
const char* QGCApplication::_settingsVersionKey             = "SettingsVersion";
const char* QGCApplication::_savedFilesLocationKey          = "SavedFilesLocation";
const char* QGCApplication::_promptFlightDataSave           = "PromptFLightDataSave";
const char* QGCApplication::_promptFlightDataSaveNotArmed   = "PromptFLightDataSaveNotArmed";
const char* QGCApplication::_styleKey                       = "StyleIsDark";
Don Gagne's avatar
Don Gagne committed
119

dogmaphobic's avatar
dogmaphobic committed
120 121 122
const char* QGCApplication::_defaultSavedFileDirectoryName      = "QGroundControl";
const char* QGCApplication::_savedFileMavlinkLogDirectoryName   = "FlightData";
const char* QGCApplication::_savedFileParameterDirectoryName    = "SavedParameters";
pixhawk's avatar
pixhawk committed
123

dogmaphobic's avatar
dogmaphobic committed
124 125
const char* QGCApplication::_darkStyleFile          = ":/res/styles/style-dark.css";
const char* QGCApplication::_lightStyleFile         = ":/res/styles/style-light.css";
126

127 128

// Qml Singleton factories
129

Don Gagne's avatar
Don Gagne committed
130
static QObject* screenToolsControllerSingletonFactory(QQmlEngine*, QJSEngine*)
131
{
Don Gagne's avatar
Don Gagne committed
132 133
    ScreenToolsController* screenToolsController = new ScreenToolsController;
    return screenToolsController;
134 135
}

136 137 138 139 140
static QObject* mavlinkQmlSingletonFactory(QQmlEngine*, QJSEngine*)
{
    return new MavlinkQmlSingleton;
}

141 142
static QObject* qgroundcontrolQmlGlobalSingletonFactory(QQmlEngine*, QJSEngine*)
{
143
    return new QGroundControlQmlGlobal(qgcApp()->toolbox());
144 145
}

pixhawk's avatar
pixhawk committed
146 147 148 149 150 151 152 153 154 155
/**
 * @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
 **/

156 157 158
QGCApplication::QGCApplication(int &argc, char* argv[], bool unitTesting)
    : QApplication(argc, argv)
    , _runningUnitTests(unitTesting)
dogmaphobic's avatar
dogmaphobic committed
159 160 161
#if defined (__mobile__)
    , _styleIsDark(false)
#else
162
    , _styleIsDark(true)
dogmaphobic's avatar
dogmaphobic committed
163
#endif
dogmaphobic's avatar
dogmaphobic committed
164
    , _fakeMobile(false)
165 166 167
#ifdef QT_DEBUG
    , _testHighDPI(false)
#endif
168
    , _toolbox(NULL)
pixhawk's avatar
pixhawk committed
169
{
170 171
    Q_ASSERT(_app == NULL);
    _app = this;
172

173
    // This prevents usage of QQuickWidget to fail since it doesn't support native widget siblings
dogmaphobic's avatar
dogmaphobic committed
174
#ifndef __android__
175
    setAttribute(Qt::AA_DontCreateNativeWidgetSiblings);
dogmaphobic's avatar
dogmaphobic committed
176
#endif
dogmaphobic's avatar
dogmaphobic committed
177

178
    // Parse command line options
dogmaphobic's avatar
dogmaphobic committed
179

180
    bool fClearSettingsOptions = false; // Clear stored settings
181 182
    bool logging = false;               // Turn on logging
    QString loggingOptions;
dogmaphobic's avatar
dogmaphobic committed
183

184
    CmdLineOpt_t rgCmdLineOptions[] = {
185 186
        { "--clear-settings",   &fClearSettingsOptions, NULL },
        { "--logging",          &logging,               &loggingOptions },
dogmaphobic's avatar
dogmaphobic committed
187
        { "--fake-mobile",      &_fakeMobile,           NULL },
188
#ifdef QT_DEBUG
189
        { "--test-high-dpi",    &_testHighDPI,          NULL },
190
#endif
191 192
        // Add additional command line option flags here
    };
dogmaphobic's avatar
dogmaphobic committed
193

194
    ParseCmdLineOptions(argc, argv, rgCmdLineOptions, sizeof(rgCmdLineOptions)/sizeof(rgCmdLineOptions[0]), false);
195

dogmaphobic's avatar
dogmaphobic committed
196
#ifdef __mobile__
Don Gagne's avatar
Don Gagne committed
197
    QLoggingCategory::setFilterRules(QStringLiteral("*Log.debug=false"));
198
#else
199
    QString filterRules;
dogmaphobic's avatar
dogmaphobic committed
200

201 202
    // Turn off bogus ssl warning
    filterRules += "qt.network.ssl.warning=false\n";
dogmaphobic's avatar
dogmaphobic committed
203

204 205
    if (logging) {
        QStringList logList = loggingOptions.split(",");
dogmaphobic's avatar
dogmaphobic committed
206

207 208 209 210 211 212 213 214 215 216 217 218
        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";
            }
        }
219 220 221
    } else {
        // 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.
222

223 224 225
        static const char* qtProjectDir = "QtProject";
        static const char* qtLoggingFile = "qtlogging.ini";
        bool loggingDirectoryOk = false;
226

227 228 229 230 231 232 233 234 235
        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;
236
            }
237
        } else {
238 239
            loggingDirectoryOk = true;
        }
240

241
        if (loggingDirectoryOk) {
Don Gagne's avatar
Don Gagne committed
242
            qDebug () << "Logging ini file directory" << iniFileLocation.absolutePath();
243 244 245 246 247 248 249 250 251 252 253
            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;
254
                }
255 256 257
            }
        }
    }
dogmaphobic's avatar
dogmaphobic committed
258

259 260
    qDebug() << "Filter rules" << filterRules;
    QLoggingCategory::setFilterRules(filterRules);
Don Gagne's avatar
Don Gagne committed
261
#endif
262

263
    // Set up timer for delayed missing fact display
Don Gagne's avatar
Don Gagne committed
264 265 266
    _missingParamsDelayedDisplayTimer.setSingleShot(true);
    _missingParamsDelayedDisplayTimer.setInterval(_missingParamsDelayedDisplayTimerTimeout);
    connect(&_missingParamsDelayedDisplayTimer, &QTimer::timeout, this, &QGCApplication::_missingParamsDisplay);
dogmaphobic's avatar
dogmaphobic committed
267

Don Gagne's avatar
Don Gagne committed
268
    // Set application information
269 270 271 272 273 274 275 276 277
    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);
278

dogmaphobic's avatar
dogmaphobic committed
279
    QString versionString(GIT_VERSION);
Daniel Agar's avatar
Daniel Agar committed
280 281 282 283 284
    // stable versions are on tags (v1.2.3)
    // development versions are full git describe versions (v1.2.3-18-g879e8b3)
    if (versionString.length() > 8) {
        versionString.append(" (Development)");
    }
Don Gagne's avatar
Don Gagne committed
285
    this->setApplicationVersion(versionString);
286

287
    // Set settings format
dogmaphobic's avatar
dogmaphobic committed
288
#if !defined(__mobile__) && !defined(__macos__)
289
    QSettings::setDefaultFormat(QSettings::IniFormat);
dogmaphobic's avatar
dogmaphobic committed
290 291 292 293 294 295 296
#else
    QString settingsLocation = QStandardPaths::writableLocation(QStandardPaths::ConfigLocation);
    if(!settingsLocation.isEmpty())
    {
        QSettings::setPath(QSettings::NativeFormat, QSettings::UserScope, settingsLocation);
    }
#endif
297

298
    QSettings settings;
Don Gagne's avatar
Don Gagne committed
299 300
    qDebug() << "Settings location" << settings.fileName() << settings.isWritable();

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

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

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

318 319
    // Initialize Video Streaming
    initializeVideoStreaming(argc, argv);
320 321

    _toolbox = new QGCToolbox(this);
322 323
}

Don Gagne's avatar
Don Gagne committed
324 325
QGCApplication::~QGCApplication()
{
Don Gagne's avatar
Don Gagne committed
326 327 328 329
    MainWindow* mainWindow = MainWindow::instance();
    if (mainWindow) {
        delete mainWindow;
    }
330
    shutdownVideoStreaming();
331
    delete _toolbox;
Don Gagne's avatar
Don Gagne committed
332 333
}

334
void QGCApplication::_initCommon(void)
335 336
{
    QSettings settings;
337

Don Gagne's avatar
Don Gagne committed
338
    // Register our Qml objects
dogmaphobic's avatar
dogmaphobic committed
339

Don Gagne's avatar
Don Gagne committed
340 341
    qmlRegisterType<QGCPalette>     ("QGroundControl.Palette", 1, 0, "QGCPalette");
    qmlRegisterType<QGCMapPalette>  ("QGroundControl.Palette", 1, 0, "QGCMapPalette");
dogmaphobic's avatar
dogmaphobic committed
342

Don Gagne's avatar
Don Gagne committed
343 344 345 346 347 348 349
    qmlRegisterUncreatableType<CoordinateVector>    ("QGroundControl",                  1, 0, "CoordinateVector",       "Reference only");
    qmlRegisterUncreatableType<MissionCommands>     ("QGroundControl",                  1, 0, "MissionCommands",        "Reference only");
    qmlRegisterUncreatableType<QGCQGeoCoordinate>   ("QGroundControl",                  1, 0, "QGCQGeoCoordinate",      "Reference only");
    qmlRegisterUncreatableType<QmlObjectListModel>  ("QGroundControl",                  1, 0, "QmlObjectListModel",     "Reference only");
    qmlRegisterUncreatableType<VideoReceiver>       ("QGroundControl",                  1, 0, "VideoReceiver",          "Reference only");
    qmlRegisterUncreatableType<VideoSurface>        ("QGroundControl",                  1, 0, "VideoSurface",           "Reference only");

350 351 352 353 354 355 356
    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");
357

Don Gagne's avatar
Don Gagne committed
358 359 360 361 362 363 364 365 366 367 368
    qmlRegisterType<ParameterEditorController>          ("QGroundControl.Controllers", 1, 0, "ParameterEditorController");
    qmlRegisterType<APMFlightModesComponentController>  ("QGroundControl.Controllers", 1, 0, "APMFlightModesComponentController");
    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");
    qmlRegisterType<MainToolBarController>              ("QGroundControl.Controllers", 1, 0, "MainToolBarController");
    qmlRegisterType<MissionController>                  ("QGroundControl.Controllers", 1, 0, "MissionController");
    qmlRegisterType<FlightDisplayViewController>        ("QGroundControl.Controllers", 1, 0, "FlightDisplayViewController");
369

Don Gagne's avatar
Don Gagne committed
370
#ifndef __mobile__
371 372
    qmlRegisterType<ViewWidgetController>           ("QGroundControl.Controllers", 1, 0, "ViewWidgetController");
    qmlRegisterType<CustomCommandWidgetController>  ("QGroundControl.Controllers", 1, 0, "CustomCommandWidgetController");
373
    qmlRegisterType<FirmwareUpgradeController>      ("QGroundControl.Controllers", 1, 0, "FirmwareUpgradeController");
374
    qmlRegisterType<JoystickConfigController>       ("QGroundControl.Controllers", 1, 0, "JoystickConfigController");
Don Gagne's avatar
Don Gagne committed
375
#endif
dogmaphobic's avatar
dogmaphobic committed
376

377
    // Register Qml Singletons
378 379 380
    qmlRegisterSingletonType<QGroundControlQmlGlobal>   ("QGroundControl",                          1, 0, "QGroundControl",         qgroundcontrolQmlGlobalSingletonFactory);
    qmlRegisterSingletonType<ScreenToolsController>     ("QGroundControl.ScreenToolsController",    1, 0, "ScreenToolsController",  screenToolsControllerSingletonFactory);
    qmlRegisterSingletonType<MavlinkQmlSingleton>       ("QGroundControl.Mavlink",                  1, 0, "Mavlink",                mavlinkQmlSingletonFactory);
dogmaphobic's avatar
dogmaphobic committed
381

382 383 384 385 386
    // 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;
387
        }
388 389 390
    } else if (settings.allKeys().count()) {
        // Settings version key is missing and there are settings. This is an upgrade scenario.
        settingsUpgraded = true;
Don Gagne's avatar
Don Gagne committed
391 392
    } else {
        settings.setValue(_settingsVersionKey, QGC_SETTINGS_VERSION);
393
    }
394

395
    if (settingsUpgraded) {
396
        settings.clear();
397
        settings.setValue(_settingsVersionKey, QGC_SETTINGS_VERSION);
398 399
        showMessage("The format for QGroundControl saved settings has been modified. "
                    "Your saved settings have been reset to defaults.");
400
    }
401

Don Gagne's avatar
Don Gagne committed
402
    // Load saved files location and validate
403

Don Gagne's avatar
Don Gagne committed
404 405 406
    QString savedFilesLocation;
    if (settings.contains(_savedFilesLocationKey)) {
        savedFilesLocation = settings.value(_savedFilesLocationKey).toString();
407 408 409 410
        if (!validatePossibleSavedFilesLocation(savedFilesLocation)) {
            savedFilesLocation.clear();
        }
    }
dogmaphobic's avatar
dogmaphobic committed
411

412 413
    if (savedFilesLocation.isEmpty()) {
        // No location set (or invalid). Create a default one in Documents standard location.
414

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

Don Gagne's avatar
Don Gagne committed
417
        QDir documentsDir(documentsLocation);
418 419 420
        if (!documentsDir.exists()) {
            qWarning() << "Documents directory doesn't exist" << documentsDir.absolutePath();
        }
421

422
        bool pathCreated = documentsDir.mkpath(_defaultSavedFileDirectoryName);
423
        Q_UNUSED(pathCreated);
424 425
        Q_ASSERT(pathCreated);
        savedFilesLocation = documentsDir.filePath(_defaultSavedFileDirectoryName);
Don Gagne's avatar
Don Gagne committed
426
    }
427

Don Gagne's avatar
Don Gagne committed
428 429 430 431 432
    if (!savedFilesLocation.isEmpty()) {
        if (!validatePossibleSavedFilesLocation(savedFilesLocation)) {
            savedFilesLocation.clear();
        }
    }
433
    qDebug() << "Saved files location" << savedFilesLocation;
434
    settings.setValue(_savedFilesLocationKey, savedFilesLocation);
Don Gagne's avatar
Don Gagne committed
435 436

    settings.sync();
437 438 439 440 441
}

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

443 444
    _styleIsDark = settings.value(_styleKey, _styleIsDark).toBool();
    _loadCurrentStyle();
Lorenz Meier's avatar
Lorenz Meier committed
445

446 447 448
    // Exit main application when last window is closed
    connect(this, SIGNAL(lastWindowClosed()), this, SLOT(quit()));

pixhawk's avatar
pixhawk committed
449
    // Start the user interface
Lorenz Meier's avatar
Lorenz Meier committed
450
    MainWindow* mainWindow = MainWindow::_create();
Don Gagne's avatar
Don Gagne committed
451
    Q_CHECK_PTR(mainWindow);
452

453
#ifndef __mobile__
454 455 456 457 458
    // 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()) {
Don Gagne's avatar
Don Gagne committed
459
        showMessage("The location to save files to is invalid, or cannot be written to. Please provide a new one.");
460 461
        mainWindow->showSettings();
    }
462

463
    // Now that main window is up check for lost log files
464
    connect(this, &QGCApplication::checkForLostLogFiles, toolbox()->mavlinkProtocol(), &MAVLinkProtocol::checkForLostLogFiles);
465
    emit checkForLostLogFiles();
Don Gagne's avatar
Don Gagne committed
466
#endif
467 468

    // Load known link configurations
469
    toolbox()->linkManager()->loadLinkConfigurationList();
470

471
    return true;
pixhawk's avatar
pixhawk committed
472 473
}

474
bool QGCApplication::_initForUnitTests(void)
pixhawk's avatar
pixhawk committed
475
{
476
    return true;
pixhawk's avatar
pixhawk committed
477 478
}

Don Gagne's avatar
Don Gagne committed
479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499
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
500

Don Gagne's avatar
Don Gagne committed
501
    QString filename = QDir(location).filePath("QGCTempXXXXXXXX.tmp");
502
    QGCTemporaryFile tempFile(filename);
Don Gagne's avatar
Don Gagne committed
503 504 505
    if (!tempFile.open()) {
        return false;
    }
506

507
    tempFile.remove();
508

Don Gagne's avatar
Don Gagne committed
509 510 511 512 513 514
    return true;
}

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

Don Gagne's avatar
Don Gagne committed
516 517 518 519 520 521 522
    return settings.value(_savedFilesLocationKey).toString();
}

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

Don Gagne's avatar
Don Gagne committed
524
    location = parentDir.filePath(_savedFileParameterDirectoryName);
525

Don Gagne's avatar
Don Gagne committed
526 527 528 529 530 531 532
    if (!QDir(location).exists()) {
        // If directory doesn't exist, try to create it
        if (!parentDir.mkpath(_savedFileParameterDirectoryName)) {
            // Return an error
            location.clear();
        }
    }
533

Don Gagne's avatar
Don Gagne committed
534 535 536 537 538 539 540
    return location;
}

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

Don Gagne's avatar
Don Gagne committed
542
    location = parentDir.filePath(_savedFileMavlinkLogDirectoryName);
543

Don Gagne's avatar
Don Gagne committed
544 545 546 547 548 549 550
    if (!QDir(location).exists()) {
        // If directory doesn't exist, try to create it
        if (!parentDir.mkpath(_savedFileMavlinkLogDirectoryName)) {
            // Return an error
            location.clear();
        }
    }
551

Don Gagne's avatar
Don Gagne committed
552 553 554 555 556 557
    return location;
}

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

Don Gagne's avatar
Don Gagne committed
559 560 561
    return settings.value(_promptFlightDataSave, true).toBool();
}

562 563 564 565 566 567 568
bool QGCApplication::promptFlightDataSaveNotArmed(void)
{
    QSettings settings;

    return settings.value(_promptFlightDataSaveNotArmed, false).toBool();
}

Don Gagne's avatar
Don Gagne committed
569 570 571 572 573 574
void QGCApplication::setPromptFlightDataSave(bool promptForSave)
{
    QSettings settings;
    settings.setValue(_promptFlightDataSave, promptForSave);
}

575 576 577 578 579 580
void QGCApplication::setPromptFlightDataSaveNotArmed(bool promptForSave)
{
    QSettings settings;
    settings.setValue(_promptFlightDataSaveNotArmed, promptForSave);
}

Don Gagne's avatar
Don Gagne committed
581 582 583
/// @brief Returns the QGCApplication object singleton.
QGCApplication* qgcApp(void)
{
584 585 586 587
    Q_ASSERT(QGCApplication::_app);
    return QGCApplication::_app;
}

588 589
void QGCApplication::informationMessageBoxOnMainThread(const QString& title, const QString& msg)
{
590 591
    Q_UNUSED(title);
    showMessage(msg);
592 593 594 595
}

void QGCApplication::warningMessageBoxOnMainThread(const QString& title, const QString& msg)
{
596 597 598 599
#ifdef __mobile__
    Q_UNUSED(title)
    showMessage(msg);
#else
600
    QGCMessageBox::warning(title, msg);
601
#endif
602 603 604 605
}

void QGCApplication::criticalMessageBoxOnMainThread(const QString& title, const QString& msg)
{
606 607 608 609
#ifdef __mobile__
    Q_UNUSED(title)
    showMessage(msg);
#else
610
    QGCMessageBox::critical(title, msg);
611
#endif
612 613
}

614
#ifndef __mobile__
615 616
void QGCApplication::saveTempFlightDataLogOnMainThread(QString tempLogfile)
{
617 618 619 620 621 622 623 624 625
    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");
dogmaphobic's avatar
dogmaphobic committed
626

627 628 629
        if (!saveFilename.isEmpty()) {
            // if file exsits already, try to remove it first to overwrite it
            if(QFile::exists(saveFilename) && !QFile::remove(saveFilename)){
630
                // if the file cannot be removed, prompt user and ask new path
631
                saveError = true;
632
                QGCMessageBox::warning("File Error","Could not overwrite existing file.\nPlease provide a different file name to save to.");
633 634 635
            } else if(!QFile::copy(tempLogfile, saveFilename)) {
                // if file could not be copied, prompt user and ask new path
                saveError = true;
636
                QGCMessageBox::warning("File Error","Could not create file.\nPlease provide a different file name to save to.");
637 638 639
            }
        }
    } while(saveError); // if the file could not be overwritten, ask for new file
640 641
    QFile::remove(tempLogfile);
}
642
#endif
643 644 645 646

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

648 649 650 651 652 653 654 655 656 657
    settings.setValue(_styleKey, styleIsDark);
    _styleIsDark = styleIsDark;
    _loadCurrentStyle();
    emit styleChanged(_styleIsDark);
}

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

659 660
    // Signal to the user that the app will pause to apply a new stylesheet
    setOverrideCursor(Qt::WaitCursor);
661

662 663 664 665 666 667 668 669 670
    // 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;
    }
671

672 673 674 675 676 677 678 679 680 681 682
    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;
        }
    }
dogmaphobic's avatar
dogmaphobic committed
683

684
    setStyleSheet(styles);
685

686 687 688 689
    if (!success) {
        // Fall back to plastique if we can't load our own
        setStyle("plastique");
    }
690

691
    QGCPalette::setGlobalTheme(_styleIsDark ? QGCPalette::Dark : QGCPalette::Light);
692

693 694 695
    // Finally restore the cursor before returning.
    restoreOverrideCursor();
}
Don Gagne's avatar
Don Gagne committed
696

Don Gagne's avatar
Don Gagne committed
697
void QGCApplication::reportMissingParameter(int componentId, const QString& name)
698
{
Don Gagne's avatar
Don Gagne committed
699 700
    _missingParams += QString("%1:%2").arg(componentId).arg(name);
    _missingParamsDelayedDisplayTimer.start();
701
}
702

Don Gagne's avatar
Don Gagne committed
703 704
/// Called when the delay timer fires to show the missing parameters warning
void QGCApplication::_missingParamsDisplay(void)
705
{
Don Gagne's avatar
Don Gagne committed
706
    Q_ASSERT(_missingParams.count());
dogmaphobic's avatar
dogmaphobic committed
707

Don Gagne's avatar
Don Gagne committed
708 709 710 711
    QString params;
    foreach (QString name, _missingParams) {
        if (params.isEmpty()) {
            params += name;
712
        } else {
Don Gagne's avatar
Don Gagne committed
713
            params += QString(", %1").arg(name);
714 715
        }
    }
Don Gagne's avatar
Don Gagne committed
716
    _missingParams.clear();
dogmaphobic's avatar
dogmaphobic committed
717

Don Gagne's avatar
Don Gagne committed
718
    showMessage(QString("Parameters missing from firmware: %1.\n\nYou should quit QGroundControl immediately and update your firmware.").arg(params));
719 720
}

721
void QGCApplication::showMessage(const QString& message)
Don Gagne's avatar
Don Gagne committed
722 723 724
{
    MainWindow* mainWindow = MainWindow::instance();
    if (mainWindow) {
725
        mainWindow->showMessage(message);
Don Gagne's avatar
Don Gagne committed
726
    } else {
Don Gagne's avatar
Don Gagne committed
727
        qWarning() << "showMessage with no mainWindow" << message;
Don Gagne's avatar
Don Gagne committed
728 729
    }
}