Newer
Older
/*=====================================================================
QGroundControl Open Source Ground Control Station
(c) 2009 - 2015 QGROUNDCONTROL PROJECT <http://www.qgroundcontrol.org>
This file is part of the QGROUNDCONTROL project
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.
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.
You should have received a copy of the GNU General Public License
along with QGROUNDCONTROL. If not, see <http://www.gnu.org/licenses/>.
======================================================================*/
*
* @author Lorenz Meier <mavteam@student.ethz.ch>
*
*/
#include <QFile>
#include <QFlags>
#include <QSplashScreen>
#include <QPixmap>
#include <QDesktopWidget>
#include <QPainter>
#include <QStyleFactory>
#include <QAction>
#include "QGC.h"
#include "MainWindow.h"
#include "UDPLink.h"
#include "MAVLinkSimulationLink.h"
#include "SerialLink.h"
#include "QGCSingleton.h"
#include "LinkManager.h"
#include "UASManager.h"
#include "AutoPilotPluginManager.h"
#ifdef QGC_RTLAB_ENABLED
#include "OpalLink.h"
#endif
QGCApplication* QGCApplication::_app = NULL;
const char* QGCApplication::_deleteAllSettingsKey = "DeleteAllSettingsNextBoot";
const char* QGCApplication::_settingsVersionKey = "SettingsVersion";
const char* QGCApplication::_savedFilesLocationKey = "SavedFilesLocation";
const char* QGCApplication::_promptFlightDataSave = "PromptFLightDataSave";
const char* QGCApplication::_styleKey = "StyleIsDark";
const char* QGCApplication::_defaultSavedFileDirectoryName = "QGroundControl";
const char* QGCApplication::_savedFileMavlinkLogDirectoryName = "FlightData";
const char* QGCApplication::_savedFileParameterDirectoryName = "SavedParameters";
const char* QGCApplication::_darkStyleFile = ":files/styles/style-dark.css";
const char* QGCApplication::_lightStyleFile = ":files/styles/style-light.css";
/**
* @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
**/
Mariano Lizarraga
committed
QGCApplication::QGCApplication(int &argc, char* argv[], bool unitTesting) :
QApplication(argc, argv),
_runningUnitTests(unitTesting),
_styleIsDark(true)
Q_ASSERT(_app == NULL);
_app = this;
#ifdef QT_DEBUG
// 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.
static const char* qtProjectDir = "QtProject";
static const char* qtLoggingFile = "qtlogging.ini";
bool loggingDirectoryOk = false;
QDir iniFileLocation(QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation));
if (!iniFileLocation.cd(qtProjectDir)) {
if (!iniFileLocation.mkdir(qtProjectDir)) {
qDebug() << "Unable to create qtlogging.ini directory" << iniFileLocation.filePath(qtProjectDir);
if (!iniFileLocation.cd(qtProjectDir)) {
qDebug() << "Unable to access qtlogging.ini directory" << iniFileLocation.filePath(qtProjectDir);;
}
loggingDirectoryOk = true;
}
} else {
loggingDirectoryOk = true;
}
if (loggingDirectoryOk) {
qDebug () << iniFileLocation;
if (!iniFileLocation.exists(qtLoggingFile)) {
if (!QFile::copy(":QLoggingCategory/qtlogging.ini", iniFileLocation.filePath(qtLoggingFile))) {
qDebug() << "Unable to copy" << QString(qtLoggingFile) << "to" << iniFileLocation;
}
}
}
#endif
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);
// 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);
this->setApplicationVersion(versionString);
// Set settings format
QSettings::setDefaultFormat(QSettings::IniFormat);
// Parse command line options
bool fClearSettingsOptions = false; // Clear stored settings
CmdLineOpt_t rgCmdLineOptions[] = {
{ "--clear-settings", &fClearSettingsOptions, QString() },
// Add additional command line option flags here
};
ParseCmdLineOptions(argc, argv, rgCmdLineOptions, sizeof(rgCmdLineOptions)/sizeof(rgCmdLineOptions[0]), false);
QSettings settings;
// The setting will delete all settings on this boot
fClearSettingsOptions |= settings.contains(_deleteAllSettingsKey);
// Unit tests run with clean settings
if (fClearSettingsOptions) {
// User requested settings to be cleared on command line
settings.clear();
settings.setValue(_settingsVersionKey, QGC_SETTINGS_VERSION);
// 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;
}
} else if (settings.allKeys().count()) {
// Settings version key is missing and there are settings. This is an upgrade scenario.
settingsUpgraded = true;
}
if (settingsUpgraded) {
settings.clear();
settings.setValue(_settingsVersionKey, QGC_SETTINGS_VERSION);
QGCMessageBox::information(tr("Settings Cleared"),
tr("The format for QGroundControl saved settings has been modified. "
"Your saved settings have been reset to defaults."));
}
_styleIsDark = settings.value(_styleKey, _styleIsDark).toBool();
_loadCurrentStyle();
// Load saved files location and validate
QString savedFilesLocation;
if (settings.contains(_savedFilesLocationKey)) {
savedFilesLocation = settings.value(_savedFilesLocationKey).toString();
} else {
// No location set. Create a default one in Documents standard location.
QString documentsLocation = QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation);
QDir documentsDir(documentsLocation);
Q_ASSERT(documentsDir.exists());
bool pathCreated = documentsDir.mkpath(_defaultSavedFileDirectoryName);
Q_ASSERT(pathCreated);
savedFilesLocation = documentsDir.filePath(_defaultSavedFileDirectoryName);
}
if (!savedFilesLocation.isEmpty()) {
if (!validatePossibleSavedFilesLocation(savedFilesLocation)) {
savedFilesLocation.clear();
}
}
settings.setValue(_savedFilesLocationKey, savedFilesLocation);
// Load application font
QFontDatabase fontDatabase = QFontDatabase();
const QString fontFileName = ":/general/vera.ttf"; ///< Font file is part of the QRC file and compiled into the app
//const QString fontFamilyName = "Bitstream Vera Sans";
if(!QFile::exists(fontFileName)) printf("ERROR! font file: %s DOES NOT EXIST!\n", fontFileName.toStdString().c_str());
fontDatabase.addApplicationFont(fontFileName);
// Avoid Using setFont(). In the Qt docu you can read the following:
// "Warning: Do not use this function in conjunction with Qt Style Sheets."
// setFont(fontDatabase.font(fontFamilyName, "Roman", 12));
}
bool QGCApplication::_initForNormalAppBoot(void)
{
QSettings settings;
enum MainWindow::CUSTOM_MODE mode = (enum MainWindow::CUSTOM_MODE) settings.value("QGC_CUSTOM_MODE", (int)MainWindow::CUSTOM_MODE_PX4).toInt();
Jessica
committed
QPixmap splashImage(":/files/images/splash.png");
QSplashScreen* splashScreen = new QSplashScreen(splashImage);
// Delete splash screen after mainWindow was displayed
splashScreen->setAttribute(Qt::WA_DeleteOnClose);
splashScreen->showMessage(tr("Loading application fonts"), Qt::AlignLeft | Qt::AlignBottom, QColor(62, 93, 141));
// Exit main application when last window is closed
connect(this, SIGNAL(lastWindowClosed()), this, SLOT(quit()));
splashScreen->showMessage(tr("Starting user interface"), Qt::AlignLeft | Qt::AlignBottom, QColor(62, 93, 141));
MainWindow* mainWindow = MainWindow::_create(splashScreen, mode);
// 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()) {
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."));
mainWindow->showSettings();
}
UDPLink* udpLink = NULL;
if (mainWindow->getCustomMode() == MainWindow::CUSTOM_MODE_WIFI)
{
// Connect links
// to make sure that all components are initialized when the
// first messages arrive
udpLink = new UDPLink(QHostAddress::Any, 14550);
LinkManager::instance()->addLink(udpLink);
} else {
// We want to have a default serial link available for "quick" connecting.
SerialLink *slink = new SerialLink();
LinkManager::instance()->addLink(slink);
#ifdef QGC_RTLAB_ENABLED
// Add OpalRT Link, but do not connect
OpalLink* opalLink = new OpalLink();
#endif
splashScreen->finish(mainWindow);
mainWindow->splashScreenFinished();
Lorenz Meier
committed
// Check if link could be connected
if (udpLink && LinkManager::instance()->connectLink(udpLink))
Lorenz Meier
committed
{
QMessageBox::StandardButton button = QGCMessageBox::critical(tr("Could not connect UDP port. Is an instance of %1 already running?").arg(qAppName()),
tr("It is recommended to close the application and stop all instances. Click Yes to close."),
QMessageBox::Yes | QMessageBox::No,
QMessageBox::No);
Lorenz Meier
committed
// Exit application
Lorenz Meier
committed
{
//mainWindow->close();
Lorenz Meier
committed
}
}
// Now that main window is upcheck for lost log files
connect(this, &QGCApplication::checkForLostLogFiles, MAVLinkProtocol::instance(), &MAVLinkProtocol::checkForLostLogFiles);
emit checkForLostLogFiles();
bool QGCApplication::_initForUnitTests(void)
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
QString filename = QDir(location).filePath("QGCTempXXXXXXXX.tmp");
if (!tempFile.open()) {
return false;
}
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
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
return true;
}
QString QGCApplication::savedFilesLocation(void)
{
QSettings settings;
Q_ASSERT(settings.contains(_savedFilesLocationKey));
return settings.value(_savedFilesLocationKey).toString();
}
QString QGCApplication::savedParameterFilesLocation(void)
{
QString location;
QDir parentDir(savedFilesLocation());
location = parentDir.filePath(_savedFileParameterDirectoryName);
if (!QDir(location).exists()) {
// If directory doesn't exist, try to create it
if (!parentDir.mkpath(_savedFileParameterDirectoryName)) {
// Return an error
location.clear();
}
}
return location;
}
QString QGCApplication::mavlinkLogFilesLocation(void)
{
QString location;
QDir parentDir(savedFilesLocation());
location = parentDir.filePath(_savedFileMavlinkLogDirectoryName);
if (!QDir(location).exists()) {
// If directory doesn't exist, try to create it
if (!parentDir.mkpath(_savedFileMavlinkLogDirectoryName)) {
// Return an error
location.clear();
}
}
return location;
}
bool QGCApplication::promptFlightDataSave(void)
{
QSettings settings;
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)
{
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)
{
// The order here is important since the singletons reference each other
GAudioOutput* audio = GAudioOutput::_createSingleton();
Q_UNUSED(audio);
Q_ASSERT(audio);
LinkManager* linkManager = LinkManager::_createSingleton();
Q_UNUSED(linkManager);
Q_ASSERT(linkManager);
// Needs LinkManager
UASManagerInterface* uasManager = UASManager::_createSingleton();
Q_UNUSED(uasManager);
Q_ASSERT(uasManager);
// Need UASManager
AutoPilotPluginManager* pluginManager = AutoPilotPluginManager::_createSingleton();
Q_UNUSED(pluginManager);
Q_ASSERT(pluginManager);
// Needs UASManager
FactSystem* factSystem = FactSystem::_createSingleton();
// Needs everything!
MAVLinkProtocol* mavlink = MAVLinkProtocol::_createSingleton();
Q_UNUSED(mavlink);
Q_ASSERT(mavlink);
if (MainWindow::instance()) {
delete MainWindow::instance();
}
if (LinkManager::instance(true /* nullOk */)) {
// This will close/delete all connections
LinkManager::instance()->_shutdown();
}
if (UASManager::instance(true /* nullOk */)) {
// This will delete all uas from the system
UASManager::instance()->_shutdown();
}
// Let the signals flow through the main thread
processEvents(QEventLoop::ExcludeUserInputEvents);
MAVLinkProtocol::_deleteSingleton();
FactSystem::_deleteSingleton();
AutoPilotPluginManager::_deleteSingleton();
UASManager::_deleteSingleton();
LinkManager::_deleteSingleton();
GAudioOutput::_deleteSingleton();
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
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)
{
QString saveFilename = QGCFileDialog::getSaveFileName(MainWindow::instance(),
tr("Select file to save Flight Data Log"),
qgcApp()->mavlinkLogFilesLocation(),
tr("Flight Data Log (*.mavlink)"));
if (!saveFilename.isEmpty()) {
QFile::copy(tempLogfile, saveFilename);
}
QFile::remove(tempLogfile);
}
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
void QGCApplication::setStyle(bool styleIsDark)
{
QSettings settings;
settings.setValue(_styleKey, styleIsDark);
_styleIsDark = styleIsDark;
_loadCurrentStyle();
emit styleChanged(_styleIsDark);
}
void QGCApplication::_loadCurrentStyle(void)
{
bool success = true;
QString styles;
// Signal to the user that the app will pause to apply a new stylesheet
setOverrideCursor(Qt::WaitCursor);
// 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 && !_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;
}
}
if (!styles.isEmpty()) {
setStyleSheet(styles);
}
if (!success) {
// Fall back to plastique if we can't load our own
setStyle("plastique");
}
// Finally restore the cursor before returning.
restoreOverrideCursor();
}