QGCApplication.cc 32.6 KB
Newer Older
1
/****************************************************************************
2 3 4 5 6 7 8
 *
 *   (c) 2009-2016 QGROUNDCONTROL PROJECT <http://www.qgroundcontrol.org>
 *
 * QGroundControl is licensed according to the terms in the file
 * COPYING.md in the root of the source code directory.
 *
 ****************************************************************************/
9

pixhawk's avatar
pixhawk committed
10 11
/**
 * @file
Don Gagne's avatar
Don Gagne committed
12
 *   @brief Implementation of class QGCApplication
pixhawk's avatar
pixhawk committed
13 14 15 16 17
 *
 *   @author Lorenz Meier <mavteam@student.ethz.ch>
 *
 */

18 19
#include <QAction>
#include <QDesktopWidget>
pixhawk's avatar
pixhawk committed
20 21 22
#include <QFile>
#include <QFlags>
#include <QPainter>
23
#include <QPixmap>
Don Gagne's avatar
Don Gagne committed
24
#include <QRegularExpression>
25 26
#include <QStringListModel>
#include <QStyleFactory>
pixhawk's avatar
pixhawk committed
27

dogmaphobic's avatar
dogmaphobic committed
28 29 30 31
#ifdef QGC_ENABLE_BLUETOOTH
#include <QBluetoothLocalDevice>
#endif

32 33
#include <QDebug>

34
#include "VideoStreaming.h"
Gus Grubba's avatar
Gus Grubba committed
35

36
#include "AppMessages.h"
37
#include "AudioOutput.h"
38 39
#include "AutoPilotPlugin.h"
#include "CameraCalc.h"
40
#include "CmdLineOptParser.h"
41
#include "CoordinateVector.h"
Don Gagne's avatar
Don Gagne committed
42
#include "CustomCommandWidgetController.h"
dogmaphobic's avatar
dogmaphobic committed
43
#include "ESP8266ComponentController.h"
44 45 46
#include "EditPositionDialogController.h"
#include "FactValueSliderListModel.h"
#include "FirmwareImage.h"
Don Gagne's avatar
Don Gagne committed
47
#include "FirmwarePluginManager.h"
48 49
#include "FlightMapSettings.h"
#include "FollowMe.h"
50
#include "JoystickConfigController.h"
51
#include "JoystickManager.h"
52 53 54
#include "LinkManager.h"
#include "LogDownloadController.h"
#include "MissionCommandTree.h"
Don Gagne's avatar
Don Gagne committed
55
#include "MissionManager.h"
56 57 58
#include "MultiVehicleManager.h"
#include "ParameterEditorController.h"
#include "ParameterManager.h"
59
#include "PlanMasterController.h"
Jimmy Johnson's avatar
Jimmy Johnson committed
60
#include "PositionManager.h"
61 62 63 64 65 66 67 68
#include "QGC.h"
#include "QGCApplication.h"
#include "QGCCameraManager.h"
#include "QGCCorePlugin.h"
#include "QGCFileDialogController.h"
#include "QGCFileDownload.h"
#include "QGCGeoBoundingCube.h"
#include "QGCLoggingCategory.h"
69
#include "QGCMapCircle.h"
70 71 72 73 74 75 76 77
#include "QGCMapPalette.h"
#include "QGCMapPolygon.h"
#include "QGCPalette.h"
#include "QGCTemporaryFile.h"
#include "QGroundControlQmlGlobal.h"
#include "QmlObjectListModel.h"
#include "RCChannelMonitorController.h"
#include "ScreenToolsController.h"
78
#include "SettingsManager.h"
79
#include "ShapeFileHelper.h"
80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95
#include "SimulatedPosition.h"
#include "SyslinkComponentController.h"
#include "UASMessageHandler.h"
#include "UDPLink.h"
#include "ValuesWidgetController.h"
#include "Vehicle.h"
#include "VehicleComponent.h"
#include "VideoManager.h"
#include "VideoReceiver.h"
#include "VideoSurface.h"
#include "ViewWidgetController.h"
#include "VisualMissionItem.h"
#include "Wima/Snake/NemoInterface.h"
#include "Wima/WimaBridge.h"
#include "Wima/WimaController.h"
#include "Wima/WimaPlaner.h"
96

Gus Grubba's avatar
Gus Grubba committed
97
#ifndef NO_SERIAL_LINK
98
#include "SerialLink.h"
99 100 101
#endif

#ifndef __mobile__
102
#include "FirmwareUpgradeController.h"
103
#include "GPS/GPSManager.h"
104
#include "GeoTagController.h"
105
#include "MainWindow.h"
106
#include "MavlinkConsoleController.h"
107 108
#include "QGCMessageBox.h"
#include "QGCQFileDialog.h"
109
#endif
110

111
#ifdef QGC_RTLAB_ENABLED
112
#include "OpalLink.h"
113
#endif
114

Don Gagne's avatar
Don Gagne committed
115 116 117
#ifdef Q_OS_LINUX
#ifndef __mobile__
#include <sys/types.h>
118
#include <unistd.h>
Don Gagne's avatar
Don Gagne committed
119 120
#endif
#endif
121

dogmaphobic's avatar
dogmaphobic committed
122 123
#include "QGCMapEngine.h"

124
QGCApplication *QGCApplication::_app = nullptr;
pixhawk's avatar
pixhawk committed
125

126 127
const char *QGCApplication::_deleteAllSettingsKey = "DeleteAllSettingsNextBoot";
const char *QGCApplication::_settingsVersionKey = "SettingsVersion";
Don Gagne's avatar
Don Gagne committed
128

129 130
const char *QGCApplication::_darkStyleFile = ":/res/styles/style-dark.css";
const char *QGCApplication::_lightStyleFile = ":/res/styles/style-light.css";
131

132 133 134
// Mavlink status structures for entire app
mavlink_status_t m_mavlink_status[MAVLINK_COMM_NUM_BUFFERS];

135
// Qml Singleton factories
136

137 138 139 140
static QObject *screenToolsControllerSingletonFactory(QQmlEngine *,
                                                      QJSEngine *) {
  ScreenToolsController *screenToolsController = new ScreenToolsController;
  return screenToolsController;
141 142
}

143 144 145 146 147 148
static QObject *qgroundcontrolQmlGlobalSingletonFactory(QQmlEngine *,
                                                        QJSEngine *) {
  // We create this object as a QGCTool even though it isn't in the toolbox
  QGroundControlQmlGlobal *qmlGlobal =
      new QGroundControlQmlGlobal(qgcApp(), qgcApp()->toolbox());
  qmlGlobal->setToolbox(qgcApp()->toolbox());
149

150
  return qmlGlobal;
151 152
}

153 154
static QObject *shapeFileHelperSingletonFactory(QQmlEngine *, QJSEngine *) {
  return new ShapeFileHelper;
155
}
pixhawk's avatar
pixhawk committed
156

157
QGCApplication::QGCApplication(int &argc, char *argv[], bool unitTesting)
Don Gagne's avatar
Don Gagne committed
158
#ifdef __mobile__
159
    : QGuiApplication(argc, argv), _qmlAppEngine(nullptr)
160
#else
161
    : QApplication(argc, argv)
162
#endif
163 164 165 166 167 168 169 170 171 172 173 174 175 176
      ,
      _runningUnitTests(unitTesting), _logOutput(false), _fakeMobile(false),
      _settingsUpgraded(false), _majorVersion(0), _minorVersion(0),
      _buildVersion(0), _currentVersionDownload(nullptr),
      _gpsRtkFactGroup(nullptr), _toolbox(nullptr), _bluetoothAvailable(false) {
  _app = this;

  QLocale locale = QLocale::system();
  //-- Some forced locales for testing
  // QLocale locale = QLocale(QLocale::German);
  // QLocale locale = QLocale(QLocale::French);
  // QLocale locale = QLocale(QLocale::Chinese);
#if defined(__macos__)
  locale = QLocale(locale.name());
177
#endif
178 179 180 181
  qDebug() << "System reported locale:" << locale << locale.name();
  //-- Our localization
  if (_QGCTranslator.load(locale, "qgc_", "", ":/localization"))
    _app->installTranslator(&_QGCTranslator);
182

183 184
    // This prevents usage of QQuickWidget to fail since it doesn't support
    // native widget siblings
dogmaphobic's avatar
dogmaphobic committed
185
#ifndef __android__
186
  setAttribute(Qt::AA_DontCreateNativeWidgetSiblings);
dogmaphobic's avatar
dogmaphobic committed
187
#endif
dogmaphobic's avatar
dogmaphobic committed
188

189 190
  // Setup for network proxy support
  QNetworkProxyFactory::setUseSystemConfiguration(true);
191

192 193
#ifdef Q_OS_LINUX
#ifndef __mobile__
194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210
  if (!_runningUnitTests) {
    if (getuid() == 0) {
      QMessageBox msgBox;
      msgBox.setInformativeText(
          tr("You are running %1 as root. "
             "You should not do this since it will cause other issues with %1. "
             "%1 will now exit. "
             "If you are having serial port issues on Ubuntu, execute the "
             "following commands to fix most issues:\n"
             "sudo usermod -a -G dialout $USER\n"
             "sudo apt-get remove modemmanager")
              .arg(qgcApp()->applicationName()));
      msgBox.setStandardButtons(QMessageBox::Ok);
      msgBox.setDefaultButton(QMessageBox::Ok);
      msgBox.exec();
      _exit(0);
    }
211

212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230
    // Determine if we have the correct permissions to access USB serial devices
    QFile permFile("/etc/group");
    if (permFile.open(QIODevice::ReadOnly)) {
      while (!permFile.atEnd()) {
        QString line = permFile.readLine();
        if (line.contains("dialout") && !line.contains(getenv("USER"))) {
          QMessageBox msgBox;
          msgBox.setInformativeText(
              "The current user does not have the correct permissions to "
              "access serial devices. "
              "You should also remove modemmanager since it also interferes. "
              "If you are using Ubuntu, execute the following commands to fix "
              "these issues:\n"
              "sudo usermod -a -G dialout $USER\n"
              "sudo apt-get remove modemmanager");
          msgBox.setStandardButtons(QMessageBox::Ok);
          msgBox.setDefaultButton(QMessageBox::Ok);
          msgBox.exec();
          break;
231
        }
232 233
      }
      permFile.close();
234
    }
235
  }
236 237 238
#endif
#endif

239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284
  // Parse command line options

  bool fClearSettingsOptions = false; // Clear stored settings
  bool fClearCache = false;           // Clear parameter/airframe caches
  bool logging = false;               // Turn on logging
  QString loggingOptions;

  CmdLineOpt_t rgCmdLineOptions[] = {
      {"--clear-settings", &fClearSettingsOptions, nullptr},
      {"--clear-cache", &fClearCache, nullptr},
      {"--logging", &logging, &loggingOptions},
      {"--fake-mobile", &_fakeMobile, nullptr},
      {"--log-output", &_logOutput, nullptr},
      // Add additional command line option flags here
  };

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

  // Set up timer for delayed missing fact display
  _missingParamsDelayedDisplayTimer.setSingleShot(true);
  _missingParamsDelayedDisplayTimer.setInterval(
      _missingParamsDelayedDisplayTimerTimeout);
  connect(&_missingParamsDelayedDisplayTimer, &QTimer::timeout, this,
          &QGCApplication::_missingParamsDisplay);

  // Set application information
  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);

  this->setApplicationVersion(QString(GIT_VERSION));

  // Set settings format
  QSettings::setDefaultFormat(QSettings::IniFormat);
  QSettings settings;
  qDebug() << "Settings location" << settings.fileName()
           << "Is writable?:" << settings.isWritable();
Don Gagne's avatar
Don Gagne committed
285

286
#ifdef UNITTEST_BUILD
287 288 289
  if (!settings.isWritable()) {
    qWarning() << "Setings location is not writable";
  }
290
#endif
291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312
  // The setting will delete all settings on this boot
  fClearSettingsOptions |= settings.contains(_deleteAllSettingsKey);

  if (_runningUnitTests) {
    // Unit tests run with clean settings
    fClearSettingsOptions = true;
  }

  if (fClearSettingsOptions) {
    // User requested settings to be cleared on command line
    settings.clear();

    // Clear parameter cache
    QDir paramDir(ParameterManager::parameterCacheDir());
    paramDir.removeRecursively();
    paramDir.mkpath(paramDir.absolutePath());
  } else {
    // Determine if upgrade message for settings version bump is required. Check
    // and clear must happen before toolbox is started since that will write
    // some settings.
    if (settings.contains(_settingsVersionKey)) {
      if (settings.value(_settingsVersionKey).toInt() != QGC_SETTINGS_VERSION) {
313
        settings.clear();
314 315 316 317 318 319 320
        _settingsUpgraded = true;
      }
    } else if (settings.allKeys().count()) {
      // Settings version key is missing and there are settings. This is an
      // upgrade scenario.
      settings.clear();
      _settingsUpgraded = true;
Don Gagne's avatar
Don Gagne committed
321
    }
322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338
  }
  settings.setValue(_settingsVersionKey, QGC_SETTINGS_VERSION);

  if (fClearCache) {
    QDir dir(ParameterManager::parameterCacheDir());
    dir.removeRecursively();
    QFile airframe(cachedAirframeMetaDataFile());
    airframe.remove();
    QFile parameter(cachedParameterMetaDataFile());
    parameter.remove();
  }

  // Set up our logging filters
  QGCLoggingCategoryRegister::instance()->setFilterRulesFromSettings(
      loggingOptions);

  // Initialize Bluetooth
Valentin Platzgummer's avatar
Valentin Platzgummer committed
339
#ifdef QGC_ENABLE_BLUETOOTHsubPolylines
340 341 342 343
  QBluetoothLocalDevice localDevice;
  if (localDevice.isValid()) {
    _bluetoothAvailable = true;
  }
dogmaphobic's avatar
dogmaphobic committed
344 345
#endif

346
  // Gstreamer debug settings
347
#if defined(__ios__) || defined(__android__)
348 349
  // Initialize Video Streaming
  initializeVideoStreaming(argc, argv, nullptr, nullptr);
350
#else
351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368
  QString savePath, gstDebugLevel;
  if (settings.contains(AppSettings::savePathName)) {
    savePath = settings.value(AppSettings::savePathName).toString();
  }
  if (savePath.isEmpty()) {
    savePath = "/tmp";
  }
  savePath = savePath + "/Logs/gst";
  if (!QDir(savePath).exists()) {
    QDir().mkpath(savePath);
  }
  if (settings.contains(AppSettings::gstDebugLevelName)) {
    gstDebugLevel =
        "*:" + settings.value(AppSettings::gstDebugLevelName).toString();
  }
  // Initialize Video Streaming
  initializeVideoStreaming(argc, argv, savePath.toUtf8().data(),
                           gstDebugLevel.toUtf8().data());
369
#endif
370

371 372
  _toolbox = new QGCToolbox(this);
  _toolbox->setChildToolboxes();
Don Gagne's avatar
Don Gagne committed
373

374
#ifndef __mobile__
375 376 377 378 379 380 381 382 383 384 385 386
  _gpsRtkFactGroup = new GPSRTKFactGroup(this);
  GPSManager *gpsManager = _toolbox->gpsManager();
  if (gpsManager) {
    connect(gpsManager, &GPSManager::onConnect, this,
            &QGCApplication::_onGPSConnect);
    connect(gpsManager, &GPSManager::onDisconnect, this,
            &QGCApplication::_onGPSDisconnect);
    connect(gpsManager, &GPSManager::surveyInStatus, this,
            &QGCApplication::_gpsSurveyInStatus);
    connect(gpsManager, &GPSManager::satelliteUpdate, this,
            &QGCApplication::_gpsNumSatellites);
  }
387 388
#endif /* __mobile__ */

389
  _checkForNewVersion();
390 391
}

392 393 394 395 396 397 398
void QGCApplication::_shutdown(void) {
  // This code is specifically not in the destructor since the application
  // object may not be available in the destructor. This cause problems for
  // deleting object like settings which are in the toolbox which may have qml
  // references. By moving them here and having main.cc call this prior to
  // deleting the app object we make sure app object is still around while these
  // things are shutting down.
Don Gagne's avatar
Don Gagne committed
399
#ifndef __mobile__
400 401 402 403
  MainWindow *mainWindow = MainWindow::instance();
  if (mainWindow) {
    delete mainWindow;
  }
Don Gagne's avatar
Don Gagne committed
404
#endif
405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505
  shutdownVideoStreaming();
  delete _toolbox;
}

QGCApplication::~QGCApplication() {
  // Place shutdown code in _shutdown
  _app = nullptr;
}

void QGCApplication::_initCommon(void) {
  static const char *kRefOnly = "Reference only";
  static const char *kQGCControllers = "QGroundControl.Controllers";
  static const char *kQGCVehicle = "QGroundControl.Vehicle";

  QSettings settings;

  // Register our Qml objects

  qmlRegisterType<QGCPalette>("QGroundControl.Palette", 1, 0, "QGCPalette");
  qmlRegisterType<QGCMapPalette>("QGroundControl.Palette", 1, 0,
                                 "QGCMapPalette");

  qmlRegisterUncreatableType<Vehicle>(kQGCVehicle, 1, 0, "Vehicle", kRefOnly);
  qmlRegisterUncreatableType<MissionItem>(kQGCVehicle, 1, 0, "MissionItem",
                                          kRefOnly);
  qmlRegisterUncreatableType<MissionManager>(kQGCVehicle, 1, 0,
                                             "MissionManager", kRefOnly);
  qmlRegisterUncreatableType<ParameterManager>(kQGCVehicle, 1, 0,
                                               "ParameterManager", kRefOnly);
  qmlRegisterUncreatableType<QGCCameraManager>(kQGCVehicle, 1, 0,
                                               "QGCCameraManager", kRefOnly);
  qmlRegisterUncreatableType<QGCCameraControl>(kQGCVehicle, 1, 0,
                                               "QGCCameraControl", kRefOnly);
  qmlRegisterUncreatableType<QGCVideoStreamInfo>(
      kQGCVehicle, 1, 0, "QGCVideoStreamInfo", kRefOnly);
  qmlRegisterUncreatableType<LinkInterface>(kQGCVehicle, 1, 0, "LinkInterface",
                                            kRefOnly);
  qmlRegisterUncreatableType<MissionController>(kQGCControllers, 1, 0,
                                                "MissionController", kRefOnly);
  qmlRegisterUncreatableType<GeoFenceController>(
      kQGCControllers, 1, 0, "GeoFenceController", kRefOnly);
  qmlRegisterUncreatableType<RallyPointController>(
      kQGCControllers, 1, 0, "RallyPointController", kRefOnly);
  qmlRegisterUncreatableType<VisualMissionItem>(kQGCControllers, 1, 0,
                                                "VisualMissionItem", kRefOnly);

  qmlRegisterUncreatableType<CoordinateVector>("QGroundControl", 1, 0,
                                               "CoordinateVector", kRefOnly);
  qmlRegisterUncreatableType<QmlObjectListModel>(
      "QGroundControl", 1, 0, "QmlObjectListModel", kRefOnly);
  qmlRegisterUncreatableType<MissionCommandTree>(
      "QGroundControl", 1, 0, "MissionCommandTree", kRefOnly);
  qmlRegisterUncreatableType<CameraCalc>("QGroundControl", 1, 0, "CameraCalc",
                                         kRefOnly);

  qmlRegisterUncreatableType<AutoPilotPlugin>(
      "QGroundControl.AutoPilotPlugin", 1, 0, "AutoPilotPlugin", kRefOnly);
  qmlRegisterUncreatableType<VehicleComponent>(
      "QGroundControl.AutoPilotPlugin", 1, 0, "VehicleComponent", kRefOnly);
  qmlRegisterUncreatableType<JoystickManager>(
      "QGroundControl.JoystickManager", 1, 0, "JoystickManager", kRefOnly);
  qmlRegisterUncreatableType<Joystick>("QGroundControl.JoystickManager", 1, 0,
                                       "Joystick", kRefOnly);
  qmlRegisterUncreatableType<QGCPositionManager>(
      "QGroundControl.QGCPositionManager", 1, 0, "QGCPositionManager",
      kRefOnly);
  qmlRegisterUncreatableType<FactValueSliderListModel>(
      "QGroundControl.FactControls", 1, 0, "FactValueSliderListModel",
      kRefOnly);

  qmlRegisterUncreatableType<QGCMapPolygon>("QGroundControl.FlightMap", 1, 0,
                                            "QGCMapPolygon", kRefOnly);

  qmlRegisterUncreatableType<QGCGeoBoundingCube>(
      "QGroundControl.FlightMap", 1, 0, "QGCGeoBoundingCube", kRefOnly);

  qmlRegisterType<QGCMapCircle>("QGroundControl.FlightMap", 1, 0,
                                "QGCMapCircle");

  qmlRegisterType<ParameterEditorController>(kQGCControllers, 1, 0,
                                             "ParameterEditorController");
  qmlRegisterType<ESP8266ComponentController>(kQGCControllers, 1, 0,
                                              "ESP8266ComponentController");
  qmlRegisterType<ScreenToolsController>(kQGCControllers, 1, 0,
                                         "ScreenToolsController");
  qmlRegisterType<PlanMasterController>(kQGCControllers, 1, 0,
                                        "PlanMasterController");
  qmlRegisterType<ValuesWidgetController>(kQGCControllers, 1, 0,
                                          "ValuesWidgetController");
  qmlRegisterType<QGCFileDialogController>(kQGCControllers, 1, 0,
                                           "QGCFileDialogController");
  qmlRegisterType<RCChannelMonitorController>(kQGCControllers, 1, 0,
                                              "RCChannelMonitorController");
  qmlRegisterType<JoystickConfigController>(kQGCControllers, 1, 0,
                                            "JoystickConfigController");
  qmlRegisterType<LogDownloadController>(kQGCControllers, 1, 0,
                                         "LogDownloadController");
  qmlRegisterType<SyslinkComponentController>(kQGCControllers, 1, 0,
                                              "SyslinkComponentController");
  qmlRegisterType<EditPositionDialogController>(kQGCControllers, 1, 0,
                                                "EditPositionDialogController");
506

Don Gagne's avatar
Don Gagne committed
507
#ifndef __mobile__
508 509 510 511
  qmlRegisterType<ViewWidgetController>(kQGCControllers, 1, 0,
                                        "ViewWidgetController");
  qmlRegisterType<CustomCommandWidgetController>(
      kQGCControllers, 1, 0, "CustomCommandWidgetController");
512
#ifndef NO_SERIAL_LINK
513 514
  qmlRegisterType<FirmwareUpgradeController>(kQGCControllers, 1, 0,
                                             "FirmwareUpgradeController");
515
#endif
516 517 518
  qmlRegisterType<GeoTagController>(kQGCControllers, 1, 0, "GeoTagController");
  qmlRegisterType<MavlinkConsoleController>(kQGCControllers, 1, 0,
                                            "MavlinkConsoleController");
Don Gagne's avatar
Don Gagne committed
519
#endif
520 521 522 523 524
  // Wima
  qmlRegisterType<WimaController>("Wima", 1, 0, "WimaController");
  qmlRegisterType<WimaPlaner>("Wima", 1, 0, "WimaPlaner");
  qmlRegisterType<WimaBridge>("Wima", 1, 0, "WimaBridge");
  qmlRegisterType<NemoInterface>("Wima", 1, 0, "NemoInterface");
525

526 527 528 529 530 531 532 533 534 535
  // Register Qml Singletons
  qmlRegisterSingletonType<QGroundControlQmlGlobal>(
      "QGroundControl", 1, 0, "QGroundControl",
      qgroundcontrolQmlGlobalSingletonFactory);
  qmlRegisterSingletonType<ScreenToolsController>(
      "QGroundControl.ScreenToolsController", 1, 0, "ScreenToolsController",
      screenToolsControllerSingletonFactory);
  qmlRegisterSingletonType<ShapeFileHelper>("QGroundControl.ShapeFileHelper", 1,
                                            0, "ShapeFileHelper",
                                            shapeFileHelperSingletonFactory);
536 537
}

538 539
bool QGCApplication::_initForNormalAppBoot(void) {
  QSettings settings;
540

541
  _loadCurrentStyleSheet();
Lorenz Meier's avatar
Lorenz Meier committed
542

543 544
  // Exit main application when last window is closed
  connect(this, &QGCApplication::lastWindowClosed, this, QGCApplication::quit);
545

Don Gagne's avatar
Don Gagne committed
546
#ifdef __mobile__
547
  _qmlAppEngine = toolbox()->corePlugin()->createRootWindow(this);
Don Gagne's avatar
Don Gagne committed
548
#else
549 550 551
  // Start the user interface
  MainWindow *mainWindow = MainWindow::_create();
  Q_CHECK_PTR(mainWindow);
552
#endif
553

554 555 556 557
  // Now that main window is up check for lost log files
  connect(this, &QGCApplication::checkForLostLogFiles,
          toolbox()->mavlinkProtocol(), &MAVLinkProtocol::checkForLostLogFiles);
  emit checkForLostLogFiles();
558

559 560
  // Load known link configurations
  toolbox()->linkManager()->loadLinkConfigurationList();
561

562 563
  // Probe for joysticks
  toolbox()->joystickManager()->init();
564

565 566 567 568 569 570
  if (_settingsUpgraded) {
    showMessage(
        QString(tr("The format for %1 saved settings has been modified. "
                   "Your saved settings have been reset to defaults."))
            .arg(applicationName()));
  }
Don Gagne's avatar
Don Gagne committed
571

572 573
  // Connect links with flag AutoconnectLink
  toolbox()->linkManager()->startAutoConnectedLinks();
574

575 576 577 578
  if (getQGCMapEngine()->wasCacheReset()) {
    showMessage(tr("The Offline Map Cache database has been upgraded. "
                   "Your old map cache sets have been reset."));
  }
dogmaphobic's avatar
dogmaphobic committed
579

580 581
  settings.sync();
  return true;
pixhawk's avatar
pixhawk committed
582 583
}

584
bool QGCApplication::_initForUnitTests(void) { return true; }
pixhawk's avatar
pixhawk committed
585

586 587 588
void QGCApplication::deleteAllSettingsNextBoot(void) {
  QSettings settings;
  settings.setValue(_deleteAllSettingsKey, true);
Don Gagne's avatar
Don Gagne committed
589 590
}

591 592 593
void QGCApplication::clearDeleteAllSettingsNextBoot(void) {
  QSettings settings;
  settings.remove(_deleteAllSettingsKey);
Don Gagne's avatar
Don Gagne committed
594 595 596
}

/// @brief Returns the QGCApplication object singleton.
597
QGCApplication *qgcApp(void) { return QGCApplication::_app; }
598

599 600 601 602
void QGCApplication::informationMessageBoxOnMainThread(const QString &title,
                                                       const QString &msg) {
  Q_UNUSED(title);
  showMessage(msg);
603 604
}

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

615 616
void QGCApplication::criticalMessageBoxOnMainThread(const QString &title,
                                                    const QString &msg) {
617
#ifdef __mobile__
618 619
  Q_UNUSED(title)
  showMessage(msg);
620
#else
621
  QGCMessageBox::critical(title, msg);
622
#endif
623 624
}

625 626 627 628
void QGCApplication::saveTelemetryLogOnMainThread(QString tempLogfile) {
  // The vehicle is gone now and we are shutting down so we need to use a
  // message box for errors to hold shutdown and show the error
  if (_checkTelemetrySavePath(true /* useMessageBox */)) {
629

630 631 632
    QString saveDirPath =
        _toolbox->settingsManager()->appSettings()->telemetrySavePath();
    QDir saveDir(saveDirPath);
633

634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661
    QString nameFormat("%1%2.%3");
    QString dtFormat("yyyy-MM-dd hh-mm-ss");

    int tryIndex = 1;
    QString saveFileName =
        nameFormat.arg(QDateTime::currentDateTime().toString(dtFormat))
            .arg("")
            .arg(toolbox()
                     ->settingsManager()
                     ->appSettings()
                     ->telemetryFileExtension);
    while (saveDir.exists(saveFileName)) {
      saveFileName =
          nameFormat.arg(QDateTime::currentDateTime().toString(dtFormat))
              .arg(QStringLiteral(".%1").arg(tryIndex++))
              .arg(toolbox()
                       ->settingsManager()
                       ->appSettings()
                       ->telemetryFileExtension);
    }
    QString saveFilePath = saveDir.absoluteFilePath(saveFileName);

    QFile tempFile(tempLogfile);
    if (!tempFile.copy(saveFilePath)) {
      QString error = tr("Unable to save telemetry log. Error copying "
                         "telemetry to '%1': '%2'.")
                          .arg(saveFilePath)
                          .arg(tempFile.errorString());
662
#ifndef __mobile__
663
      QGCMessageBox::warning(tr("Telemetry Save Error"), error);
664
#else
665
      showMessage(error);
666
#endif
667
    }
668 669
  }
  QFile::remove(tempLogfile);
670
}
671

672 673 674 675
void QGCApplication::checkTelemetrySavePathOnMainThread(void) {
  // This is called with an active vehicle so don't pop message boxes which
  // holds ui thread
  _checkTelemetrySavePath(false /* useMessageBox */);
676 677
}

678 679
bool QGCApplication::_checkTelemetrySavePath(bool useMessageBox) {
  QString errorTitle = tr("Telemetry save error");
680

681 682 683 684 685
  QString saveDirPath =
      _toolbox->settingsManager()->appSettings()->telemetrySavePath();
  if (saveDirPath.isEmpty()) {
    QString error = tr(
        "Unable to save telemetry log. Application save directory is not set.");
686
#ifndef __mobile__
687 688 689
    if (useMessageBox) {
      QGCMessageBox::warning(errorTitle, error);
    } else {
690
#endif
691 692
      Q_UNUSED(useMessageBox);
      showMessage(error);
693
#ifndef __mobile__
694
    }
695 696 697
#endif
    return false;
  }
698

699 700 701 702 703
  QDir saveDir(saveDirPath);
  if (!saveDir.exists()) {
    QString error = tr("Unable to save telemetry log. Telemetry save directory "
                       "\"%1\" does not exist.")
                        .arg(saveDirPath);
704
#ifndef __mobile__
705 706 707
    if (useMessageBox) {
      QGCMessageBox::warning(errorTitle, error);
    } else {
708
#endif
709
      showMessage(error);
710
#ifndef __mobile__
711
    }
712 713 714
#endif
    return false;
  }
715

716
  return true;
717
}
718

719
void QGCApplication::_loadCurrentStyleSheet(void) {
Don Gagne's avatar
Don Gagne committed
720
#ifndef __mobile__
721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742
  bool success = true;
  QString styles;

  // 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;
  }

  if (success && !_toolbox->settingsManager()
                      ->appSettings()
                      ->indoorPalette()
                      ->rawValue()
                      .toBool()) {
    // Load the slave light stylesheet.
    QFile styleSheet(_lightStyleFile);
    if (styleSheet.open(QIODevice::ReadOnly | QIODevice::Text)) {
      styles += styleSheet.readAll();
743
    } else {
744 745
      qWarning() << "Unable to load slave light sheet:";
      success = false;
746
    }
747
  }
748

749
  setStyleSheet(styles);
750

751 752 753 754
  if (!success) {
    // Fall back to plastique if we can't load our own
    setStyle("plastique");
  }
Don Gagne's avatar
Don Gagne committed
755
#endif
756
}
Don Gagne's avatar
Don Gagne committed
757

758 759 760 761
void QGCApplication::reportMissingParameter(int componentId,
                                            const QString &name) {
  _missingParams += QString("%1:%2").arg(componentId).arg(name);
  _missingParamsDelayedDisplayTimer.start();
762
}
763

Don Gagne's avatar
Don Gagne committed
764
/// Called when the delay timer fires to show the missing parameters warning
765 766 767 768 769 770 771 772 773
void QGCApplication::_missingParamsDisplay(void) {
  if (_missingParams.count()) {
    QString params;
    foreach (const QString &name, _missingParams) {
      if (params.isEmpty()) {
        params += name;
      } else {
        params += QString(", %1").arg(name);
      }
DonLakeFlyer's avatar
DonLakeFlyer committed
774
    }
775 776 777 778 779 780 781
    _missingParams.clear();

    showMessage(tr("Parameters are missing from firmware. You may be running a "
                   "version of firmware QGC does not work correctly with or "
                   "your firmware has a bug in it. Missing params: %1")
                    .arg(params));
  }
782 783
}

784
QObject *QGCApplication::_rootQmlObject() {
Don Gagne's avatar
Don Gagne committed
785
#ifdef __mobile__
786 787 788
  if (_qmlAppEngine && _qmlAppEngine->rootObjects().size())
    return _qmlAppEngine->rootObjects()[0];
  return nullptr;
Don Gagne's avatar
Don Gagne committed
789
#else
790 791 792 793 794 795 796 797 798 799
  MainWindow *mainWindow = MainWindow::instance();
  if (mainWindow) {
    return mainWindow->rootQmlObject();
  } else if (runningUnitTests()) {
    // Unit test can run without a main window
    return nullptr;
  } else {
    qWarning() << "Why is MainWindow missing?";
    return nullptr;
  }
Don Gagne's avatar
Don Gagne committed
800 801 802
#endif
}

803 804 805 806 807 808
void QGCApplication::showMessage(const QString &message) {
  // PreArm messages are handled by Vehicle and shown in Map
  if (message.startsWith(QStringLiteral("PreArm")) ||
      message.startsWith(QStringLiteral("preflight"), Qt::CaseInsensitive)) {
    return;
  }
Don Gagne's avatar
Don Gagne committed
809

810
  QObject *rootQmlObject = _rootQmlObject();
811

812 813 814
  if (rootQmlObject) {
    QVariant varReturn;
    QVariant varMessage = QVariant::fromValue(message);
Don Gagne's avatar
Don Gagne committed
815

816 817 818
    QMetaObject::invokeMethod(_rootQmlObject(), "showMessage",
                              Q_RETURN_ARG(QVariant, varReturn),
                              Q_ARG(QVariant, varMessage));
819
#ifndef __mobile__
820 821 822 823
  } else if (runningUnitTests()) {
    // Unit test can run without a main window which will lead to no root qml
    // object. Use QGCMessageBox instead
    QGCMessageBox::information("Unit Test", message);
824
#endif
825 826 827
  } else {
    qWarning() << "Internal error";
  }
Don Gagne's avatar
Don Gagne committed
828 829
}

830 831 832 833
void QGCApplication::showSetupView(void) {
  if (_rootQmlObject()) {
    QMetaObject::invokeMethod(_rootQmlObject(), "showSetupView");
  }
Don Gagne's avatar
Don Gagne committed
834 835
}

836 837 838 839
void QGCApplication::qmlAttemptWindowClose(void) {
  if (_rootQmlObject()) {
    QMetaObject::invokeMethod(_rootQmlObject(), "attemptWindowClose");
  }
Don Gagne's avatar
Don Gagne committed
840
}
841

842 843
bool QGCApplication::isInternetAvailable() {
  return getQGCMapEngine()->isInternetActive();
844
}
Don Gagne's avatar
Don Gagne committed
845

846
void QGCApplication::_checkForNewVersion(void) {
Don Gagne's avatar
Don Gagne committed
847
#ifndef __mobile__
848 849 850 851 852 853 854 855 856 857 858 859 860
  if (!_runningUnitTests) {
    if (_parseVersionText(applicationVersion(), _majorVersion, _minorVersion,
                          _buildVersion)) {
      QString versionCheckFile =
          toolbox()->corePlugin()->stableVersionCheckFileUrl();
      if (!versionCheckFile.isEmpty()) {
        _currentVersionDownload = new QGCFileDownload(this);
        connect(_currentVersionDownload, &QGCFileDownload::downloadFinished,
                this, &QGCApplication::_currentVersionDownloadFinished);
        connect(_currentVersionDownload, &QGCFileDownload::error, this,
                &QGCApplication::_currentVersionDownloadError);
        _currentVersionDownload->download(versionCheckFile);
      }
Don Gagne's avatar
Don Gagne committed
861
    }
862
  }
Don Gagne's avatar
Don Gagne committed
863 864 865
#endif
}

866 867 868
void QGCApplication::_currentVersionDownloadFinished(QString remoteFile,
                                                     QString localFile) {
  Q_UNUSED(remoteFile);
Don Gagne's avatar
Don Gagne committed
869

Don Gagne's avatar
Don Gagne committed
870
#ifdef __mobile__
871
  Q_UNUSED(localFile);
Don Gagne's avatar
Don Gagne committed
872
#else
873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892
  QFile versionFile(localFile);
  if (versionFile.open(QIODevice::ReadOnly)) {
    QTextStream textStream(&versionFile);
    QString version = textStream.readLine();

    qDebug() << version;

    int majorVersion, minorVersion, buildVersion;
    if (_parseVersionText(version, majorVersion, minorVersion, buildVersion)) {
      if (_majorVersion < majorVersion ||
          (_majorVersion == majorVersion && _minorVersion < minorVersion) ||
          (_majorVersion == majorVersion && _minorVersion == minorVersion &&
           _buildVersion < buildVersion)) {
        QGCMessageBox::information(
            tr("New Version Available"),
            tr("There is a newer version of %1 available. You can download it "
               "from %2.")
                .arg(applicationName())
                .arg(toolbox()->corePlugin()->stableDownloadLocation()));
      }
Don Gagne's avatar
Don Gagne committed
893
    }
894
  }
Don Gagne's avatar
Don Gagne committed
895

896
  _currentVersionDownload->deleteLater();
Don Gagne's avatar
Don Gagne committed
897
#endif
Don Gagne's avatar
Don Gagne committed
898 899
}

900 901 902
void QGCApplication::_currentVersionDownloadError(QString errorMsg) {
  Q_UNUSED(errorMsg);
  _currentVersionDownload->deleteLater();
Don Gagne's avatar
Don Gagne committed
903 904
}

905 906 907 908 909 910 911 912 913 914 915
bool QGCApplication::_parseVersionText(const QString &versionString,
                                       int &majorVersion, int &minorVersion,
                                       int &buildVersion) {
  QRegularExpression regExp("v(\\d+)\\.(\\d+)\\.(\\d+)");
  QRegularExpressionMatch match = regExp.match(versionString);
  if (match.hasMatch() && match.lastCapturedIndex() == 3) {
    majorVersion = match.captured(1).toInt();
    minorVersion = match.captured(2).toInt();
    buildVersion = match.captured(3).toInt();
    return true;
  }
Don Gagne's avatar
Don Gagne committed
916

917
  return false;
Don Gagne's avatar
Don Gagne committed
918
}
919

920 921
void QGCApplication::_onGPSConnect() {
  _gpsRtkFactGroup->connected()->setRawValue(true);
922 923
}

924 925
void QGCApplication::_onGPSDisconnect() {
  _gpsRtkFactGroup->connected()->setRawValue(false);
926 927
}

928 929 930 931 932 933 934 935 936 937 938
void QGCApplication::_gpsSurveyInStatus(float duration, float accuracyMM,
                                        double latitude, double longitude,
                                        float altitude, bool valid,
                                        bool active) {
  _gpsRtkFactGroup->currentDuration()->setRawValue(duration);
  _gpsRtkFactGroup->currentAccuracy()->setRawValue(accuracyMM / 1000.0);
  _gpsRtkFactGroup->currentLatitude()->setRawValue(latitude);
  _gpsRtkFactGroup->currentLongitude()->setRawValue(longitude);
  _gpsRtkFactGroup->currentAltitude()->setRawValue(altitude);
  _gpsRtkFactGroup->valid()->setRawValue(valid);
  _gpsRtkFactGroup->active()->setRawValue(active);
939 940
}

941 942
void QGCApplication::_gpsNumSatellites(int numSatellites) {
  _gpsRtkFactGroup->numSatellites()->setRawValue(numSatellites);
943 944
}

945 946 947 948
QString QGCApplication::cachedParameterMetaDataFile(void) {
  QSettings settings;
  QDir parameterDir = QFileInfo(settings.fileName()).dir();
  return parameterDir.filePath(QStringLiteral("ParameterFactMetaData.xml"));
949 950
}

951 952 953 954
QString QGCApplication::cachedAirframeMetaDataFile(void) {
  QSettings settings;
  QDir airframeDir = QFileInfo(settings.fileName()).dir();
  return airframeDir.filePath(QStringLiteral("PX4AirframeFactMetaData.xml"));
955
}