Newer
Older
/****************************************************************************
*
* (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.
*
****************************************************************************/
/**
* @file
* @brief Implementation of class MainWindow
* @author Lorenz Meier <mail@qgroundcontrol.org>
*/
#include <QSettings>
#include <QNetworkInterface>
#include <QDebug>
#include <QTimer>
#include <QHostInfo>
#include <QQuickView>
#include <QScreen>
#include <QDesktopServices>
#include "QGC.h"
#include "MAVLinkProtocol.h"
#include "MainWindow.h"
#include "QGCMAVLinkLogPlayer.h"
#include "MAVLinkDecoder.h"
#include "QGCUASFileViewMulti.h"
#include "CustomCommandWidget.h"
#include "QGCDockWidget.h"
#include "AppMessages.h"
#ifdef UNITTEST_BUILD
#include "QmlControls/QmlTestWidget.h"
#endif
dogmaphobic
committed
/// The key under which the Main Window settings are saved
const char* MAIN_SETTINGS_GROUP = "QGC_MAINWINDOW";
enum DockWidgetTypes {
MAVLINK_INSPECTOR,
CUSTOM_COMMAND,
};
static const char *rgDockWidgetNames[] = {
"MAVLink Inspector",
"Custom Command",
"Onboard Files",
"HIL Config",
};
#define ARRAY_SIZE(ARRAY) (sizeof(ARRAY) / sizeof(ARRAY[0]))
static const char* _visibleWidgetsKey = "VisibleWidgets";
static MainWindow* _instance = NULL; ///< @brief MainWindow singleton
return _instance;
}
void MainWindow::deleteInstance(void)
{
/// @brief Private constructor for MainWindow. MainWindow singleton is only ever created
/// by MainWindow::_create method. Hence no other code should have access to
/// constructor.
: _mavlinkDecoder (NULL)
, _lowPowerMode (false)
, _showStatusBar (false)
, _mainQmlWidgetHolder (NULL)
, _forceClose (false)
dogmaphobic
committed
//-- Load fonts
if(QFontDatabase::addApplicationFont(":/fonts/opensans") < 0) {
qWarning() << "Could not load /fonts/opensans font";
}
if(QFontDatabase::addApplicationFont(":/fonts/opensans-demibold") < 0) {
qWarning() << "Could not load /fonts/opensans-demibold font";
}
// Qt 4/5 on Ubuntu does place the native menubar correctly so on Linux we revert back to in-window menu bar.
#ifdef Q_OS_LINUX
menuBar()->setNativeMenuBar(false);
#endif
loadSettings();
emit initStatusChanged(tr("Setting up user interface"), Qt::AlignLeft | Qt::AlignBottom, QColor(62, 93, 141));
dogmaphobic
committed
// Setup central widget with a layout to hold the views
_centralLayout = new QVBoxLayout();
_centralLayout->setContentsMargins(0, 0, 0, 0);
centralWidget()->setLayout(_centralLayout);
_mainQmlWidgetHolder = new QGCQmlWidgetHolder(QString(), NULL, this);
_centralLayout->addWidget(_mainQmlWidgetHolder);
_mainQmlWidgetHolder->setVisible(true);
QQmlEngine::setObjectOwnership(this, QQmlEngine::CppOwnership);
_mainQmlWidgetHolder->setContextPropertyObject("controller", this);
_mainQmlWidgetHolder->setContextPropertyObject("debugMessageModel", AppMessages::getModel());
_mainQmlWidgetHolder->setSource(QUrl::fromUserInput("qrc:qml/MainWindowHybrid.qml"));
// Image provider
QQuickImageProvider* pImgProvider = dynamic_cast<QQuickImageProvider*>(qgcApp()->toolbox()->imageProvider());
_mainQmlWidgetHolder->getEngine()->addImageProvider(QStringLiteral("QGCImages"), pImgProvider);
// Set dock options
setCorner(Qt::BottomRightCorner, Qt::BottomDockWidgetArea);
// On Mobile devices, we don't want any main menus at all.
#ifdef __mobile__
menuBar()->setNativeMenuBar(false);
#ifdef UNITTEST_BUILD
QAction* qmlTestAction = new QAction("Test QML palette and controls", NULL);
connect(qmlTestAction, &QAction::triggered, this, &MainWindow::_showQmlTestWidget);
_ui.menuWidgets->addAction(qmlTestAction);
connect(qgcApp()->toolbox()->corePlugin(), &QGCCorePlugin::showAdvancedUIChanged, this, &MainWindow::_showAdvancedUIChanged);
_showAdvancedUIChanged(qgcApp()->toolbox()->corePlugin()->showAdvancedUI());
statusBar()->setSizeGripEnabled(true);
Michael Carpenter
committed
Lorenz Meier
committed
emit initStatusChanged(tr("Building common widgets."), Qt::AlignLeft | Qt::AlignBottom, QColor(62, 93, 141));
emit initStatusChanged(tr("Building common actions"), Qt::AlignLeft | Qt::AlignBottom, QColor(62, 93, 141));
// Create actions
connectCommonActions();
// Set low power mode
emit initStatusChanged(tr("Restoring last view state"), Qt::AlignLeft | Qt::AlignBottom, QColor(62, 93, 141));
// Restore the window position and size
emit initStatusChanged(tr("Restoring last window size"), Qt::AlignLeft | Qt::AlignBottom, QColor(62, 93, 141));
if (settings.contains(_getWindowGeometryKey()))
restoreGeometry(settings.value(_getWindowGeometryKey()).toByteArray());
}
else
{
// Adjust the size
QScreen* scr = QApplication::primaryScreen();
QSize scrSize = scr->availableSize();
if (scrSize.width() <= 1280)
int w = scrSize.width() > 1600 ? 1600 : scrSize.width();
int h = scrSize.height() > 800 ? 800 : scrSize.height();
resize(w, h);
move((scrSize.width() - w) / 2, (scrSize.height() - h) / 2);
connect(_ui.actionStatusBar, &QAction::triggered, this, &MainWindow::showStatusBarCallback);
connect(&windowNameUpdateTimer, &QTimer::timeout, this, &MainWindow::configureWindowName);
windowNameUpdateTimer.start(15000);
emit initStatusChanged(tr("Done"), Qt::AlignLeft | Qt::AlignBottom, QColor(62, 93, 141));
dogmaphobic
committed
if (!qgcApp()->runningUnitTests()) {
_ui.actionStatusBar->setChecked(_showStatusBar);
showStatusBarCallback(_showStatusBar);
menuBar()->hide();
#endif
dogmaphobic
committed
show();
}
#ifndef __mobile__
_loadVisibleWidgetsSettings();
#endif
//-- Enable message handler display of messages in main window
UASMessageHandler* msgHandler = qgcApp()->toolbox()->uasMessageHandler();
if(msgHandler) {
msgHandler->showErrorsInToolbar();
}
}
MainWindow::~MainWindow()
{
if (_mavlinkDecoder) {
// Enforce thread-safe shutdown of the mavlink decoder
_mavlinkDecoder->finish();
_mavlinkDecoder->wait(1000);
_mavlinkDecoder->deleteLater();
_mavlinkDecoder = NULL;
}
// This needs to happen before we get into the QWidget dtor
// otherwise the QML engine reads freed data and tries to
// destroy MainWindow a second time.
delete _mainQmlWidgetHolder;
{
return "_geometry";
}
MAVLinkDecoder* MainWindow::_mavLinkDecoderInstance(void)
_mavlinkDecoder = new MAVLinkDecoder(qgcApp()->toolbox()->mavlinkProtocol());
connect(_mavlinkDecoder, &MAVLinkDecoder::valueChanged, this, &MainWindow::valueChanged);
}
return _mavlinkDecoder;
}
void MainWindow::_buildCommonWidgets(void)
{
// TODO: Make this optional with a preferences setting or under a "View" menu
for (int i = 0, end = ARRAY_SIZE(rgDockWidgetNames); i < end; i++) {
const char* pDockWidgetName = rgDockWidgetNames[i];
QAction* action = new QAction(pDockWidgetName, this);
connect(action, &QAction::triggered, this, &MainWindow::_showDockWidgetAction);
_ui.menuWidgets->addAction(action);
_mapName2Action[pDockWidgetName] = action;
/// Shows or hides the specified dock widget, creating if necessary
void MainWindow::_showDockWidget(const QString& name, bool show)
Michael Carpenter
committed
{
// Create the inner widget if we need to
if (!_mapName2DockWidget.contains(name)) {
qWarning() << "Trying to load non existent widget:" << name;
Michael Carpenter
committed
}
Q_ASSERT(_mapName2DockWidget.contains(name));
QGCDockWidget* dockWidget = _mapName2DockWidget[name];
Q_ASSERT(dockWidget);
dockWidget->setVisible(show);
Q_ASSERT(_mapName2Action.contains(name));
_mapName2Action[name]->setChecked(show);
}
/// Creates the specified inner dock widget and adds to the QDockWidget
bool MainWindow::_createInnerDockWidget(const QString& widgetName)
QAction *action = _mapName2Action[widgetName];
if(action) {
switch(action->data().toInt()) {
case MAVLINK_INSPECTOR:
widget = new QGCMAVLinkInspector(widgetName, action, qgcApp()->toolbox()->mavlinkProtocol(),this);
break;
case CUSTOM_COMMAND:
widget = new CustomCommandWidget(widgetName, action, this);
break;
case ONBOARD_FILES:
widget = new QGCUASFileViewMulti(widgetName, action, this);
break;
case HIL_CONFIG:
widget = new HILDockWidget(widgetName, action, this);
break;
case ANALYZE:
widget = new Linecharts(widgetName, action, _mavLinkDecoderInstance(), this);
break;
}
if(widget) {
_mapName2DockWidget[widgetName] = widget;
}
Michael Carpenter
committed
}
void MainWindow::_hideAllDockWidgets(void)
{
for(QGCDockWidget* dockWidget: _mapName2DockWidget) {
dockWidget->setVisible(false);
}
}
void MainWindow::_showDockWidgetAction(bool show)
{
QAction* action = qobject_cast<QAction*>(QObject::sender());
_showDockWidget(rgDockWidgetNames[action->data().toInt()], show);
void MainWindow::showStatusBarCallback(bool checked)
{
_showStatusBar = checked;
checked ? statusBar()->show() : statusBar()->hide();
}
_forceClose = true;
close();
void MainWindow::closeEvent(QCloseEvent *event)
{
qgcApp()->qmlAttemptWindowClose();
}
// Should not be any active connections
if (qgcApp()->toolbox()->multiVehicleManager()->activeVehicle()) {
qWarning() << "All links should be disconnected by now";
}
_storeCurrentViewState();
Michael Carpenter
committed
storeSettings();
}
void MainWindow::loadSettings()
{
dogmaphobic
committed
// Why the screaming?
QSettings settings;
dogmaphobic
committed
settings.beginGroup(MAIN_SETTINGS_GROUP);
_lowPowerMode = settings.value("LOW_POWER_MODE", _lowPowerMode).toBool();
_showStatusBar = settings.value("SHOW_STATUSBAR", _showStatusBar).toBool();
settings.endGroup();
}
void MainWindow::storeSettings()
{
QSettings settings;
dogmaphobic
committed
settings.beginGroup(MAIN_SETTINGS_GROUP);
settings.setValue("LOW_POWER_MODE", _lowPowerMode);
settings.setValue("SHOW_STATUSBAR", _showStatusBar);
settings.setValue(_getWindowGeometryKey(), saveGeometry());
#ifndef __mobile__
_storeVisibleWidgetsSettings();
#endif
}
void MainWindow::configureWindowName()
{
setWindowTitle(qApp->applicationName() + " " + qApp->applicationVersion());
}
/**
* @brief Create all actions associated to the main window
*
**/
void MainWindow::connectCommonActions()
{
connect(qgcApp()->toolbox()->multiVehicleManager(), &MultiVehicleManager::vehicleAdded, this, &MainWindow::_vehicleAdded);
connect(this, &MainWindow::reallyClose, this, &MainWindow::_reallyClose, Qt::QueuedConnection); // Queued to allow closeEvent to fully unwind before _reallyClose is called
void MainWindow::_openUrl(const QString& url, const QString& errorMessage)
qgcApp()->showMessage(QString("Could not open information in browser: %1").arg(errorMessage));
void MainWindow::_vehicleAdded(Vehicle* vehicle)
connect(vehicle->uas(), &UAS::valueChanged, this, &MainWindow::valueChanged);
/// Stores the state of the toolbar, status bar and widgets associated with the current view
void MainWindow::_storeCurrentViewState(void)
for(QGCDockWidget* dockWidget: _mapName2DockWidget) {
settings.setValue(_getWindowGeometryKey(), saveGeometry());
dogmaphobic
committed
/// @brief Saves the last used connection
void MainWindow::saveLastUsedConnection(const QString connection)
{
QSettings settings;
QString key(MAIN_SETTINGS_GROUP);
key += "/LAST_CONNECTION";
settings.setValue(key, connection);
}
#ifdef UNITTEST_BUILD
void MainWindow::_showQmlTestWidget(void)
{
new QmlTestWidget();
}
#endif
#ifndef __mobile__
void MainWindow::_loadVisibleWidgetsSettings(void)
{
QSettings settings;
QString widgets = settings.value(_visibleWidgetsKey).toString();
if (!widgets.isEmpty()) {
QStringList nameList = widgets.split(",");
for (const QString &name: nameList) {
_showDockWidget(name, true);
}
}
}
void MainWindow::_storeVisibleWidgetsSettings(void)
{
QString widgetNames;
bool firstWidget = true;
for (const QString &name: _mapName2DockWidget.keys()) {
if (_mapName2DockWidget[name]->isVisible()) {
if (!firstWidget) {
widgetNames += ",";
} else {
firstWidget = false;
}
settings.setValue(_visibleWidgetsKey, widgetNames);
}
#endif