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 "GAudioOutput.h"
#include "QGCMAVLinkLogPlayer.h"
#include "MAVLinkDecoder.h"
#include "QGCDataPlot2D.h"
#include "Linecharts.h"
#include "QGCUASFileViewMulti.h"
#include "UASQuickView.h"
#include "QGCTabbedInfoView.h"
#include "CustomCommandWidget.h"
#include "QGCDockWidget.h"
#include "AppMessages.h"
#endif
#ifndef __ios__
#include "SerialLink.h"
#endif
#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,
ONBOARD_FILES,
INFO_VIEW,
HIL_CONFIG,
LOG_DOWNLOAD
};
static const char *rgDockWidgetNames[] = {
"MAVLink Inspector",
"Custom Command",
"Onboard Files",
"Info View",
"HIL Config",
"Log Download"
};
#define ARRAY_SIZE(ARRAY) (sizeof(ARRAY) / sizeof(ARRAY[0]))
static const char* _visibleWidgetsKey = "VisibleWidgets";
static MainWindow* _instance = NULL; ///< @brief MainWindow singleton
// _instance is set in constructor
Q_ASSERT(_instance);
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.
, _showStatusBar(false)
Q_ASSERT(_instance == NULL);
_instance = this;
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));
_ui.setupUi(this);
// Make sure tool bar elements all fit before changing minimum width
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(QLatin1String("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);
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();
// Connect user interface devices
emit initStatusChanged(tr("Initializing 3D mouse interface"), Qt::AlignLeft | Qt::AlignBottom, QColor(62, 93, 141));
mouseInput = new Mouse3DInput(this);
mouse = new Mouse6dofInput(mouseInput);
emit initStatusChanged(tr("Initializing 3D mouse interface"), Qt::AlignLeft | Qt::AlignBottom, QColor(62, 93, 141));
mouse = new Mouse6dofInput(this);
connect(this, &MainWindow::x11EventOccured, mouse, &Mouse6dofInput::handleX11Event);
// 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();
// TODO HACK
// This is a really ugly hack. For whatever reason, by having a QQuickWidget inside a
// QDockWidget (MainToolBar above), the main menu is not shown when the app first
// starts. I looked everywhere and I could not find a solution. What I did notice was
// that if any other window gets focus, the menu comes up when you come back to QGC.
// That is, if you were to click on another window and then back to QGC, the menus
// would appear. This hack below creates a 0x0 dialog and immediately closes it.
// That works around the issue and it will do until I find the root of the problem.
QDialog qd(this);
qd.show();
qd.raise();
qd.activateWindow();
qd.close();
#endif
dogmaphobic
committed
}
#ifndef __mobile__
_loadVisibleWidgetsSettings();
#endif
//-- Enable message handler display of messages in main window
UASMessageHandler* msgHandler = qgcApp()->toolbox()->uasMessageHandler();
if(msgHandler) {
msgHandler->showErrorsInToolbar();
}
}
MainWindow::~MainWindow()
{
// 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";
}
void MainWindow::_buildCommonWidgets(void)
{
// Add generic MAVLink decoder
mavlinkDecoder = new MAVLinkDecoder(qgcApp()->toolbox()->mavlinkProtocol(), this);
connect(mavlinkDecoder.data(), &MAVLinkDecoder::valueChanged, this, &MainWindow::valueChanged);
// 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(tr(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)) {
if(!_createInnerDockWidget(name)) {
qWarning() << "Trying to load non existing widget:" << name;
return;
}
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 LOG_DOWNLOAD:
widget = new LogDownload(widgetName, action, this);
break;
case HIL_CONFIG:
widget = new HILDockWidget(widgetName, action, this);
break;
case ANALYZE:
widget = new Linecharts(widgetName, action, mavlinkDecoder, this);
break;
case INFO_VIEW:
widget= new QGCTabbedInfoView(widgetName, action, this);
break;
}
if(action->data().toInt() == INFO_VIEW) {
qobject_cast<QGCTabbedInfoView*>(widget)->addSource(mavlinkDecoder);
}
if(widget) {
_mapName2DockWidget[widgetName] = widget;
}
Michael Carpenter
committed
}
void MainWindow::_hideAllDockWidgets(void)
{
foreach(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();
}
void MainWindow::reallyClose(void)
_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()
{
QList<QHostAddress> hostAddresses = QNetworkInterface::allAddresses();
QString windowname = qApp->applicationName() + " " + qApp->applicationVersion();
// XXX we do have UDP MAVLink heartbeat broadcast now in SITL and will have it on the
// WIFI radio, so people should not be in need any more of knowing their IP.
// this can go once we are certain its not needed any more.
#if 0
windowname.append(" (" + QHostInfo::localHostName() + ": ");
for (int i = 0; i < hostAddresses.size(); i++)
{
// Exclude loopback IPv4 and all IPv6 addresses
if (hostAddresses.at(i) != QHostAddress("127.0.0.1") && !hostAddresses.at(i).toString().contains(":"))
{
if(prevAddr) windowname.append("/");
windowname.append(hostAddresses.at(i).toString());
prevAddr = true;
}
}
windowname.append(")");
setWindowTitle(windowname);
}
/**
* @brief Create all actions associated to the main window
*
**/
void MainWindow::connectCommonActions()
{
// Audio output
_ui.actionMuteAudioOutput->setChecked(qgcApp()->toolbox()->audioOutput()->isMuted());
connect(qgcApp()->toolbox()->audioOutput(), &GAudioOutput::mutedChanged, _ui.actionMuteAudioOutput, &QAction::setChecked);
connect(_ui.actionMuteAudioOutput, &QAction::triggered, qgcApp()->toolbox()->audioOutput(), &GAudioOutput::mute);
connect(qgcApp()->toolbox()->multiVehicleManager(), &MultiVehicleManager::vehicleAdded, this, &MainWindow::_vehicleAdded);
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)
foreach(QGCDockWidget* dockWidget, _mapName2DockWidget) {
dockWidget->saveSettings();
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);
}
bool MainWindow::x11Event(XEvent *event)
{
emit x11EventOccured(event);
Matthias Krebs
committed
return false;
#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(",");
foreach (const QString &name, nameList) {
_showDockWidget(name, true);
}
}
}
void MainWindow::_storeVisibleWidgetsSettings(void)
{
QString widgetNames;
bool firstWidget = true;
foreach (const QString &name, _mapName2DockWidget.keys()) {
if (_mapName2DockWidget[name]->isVisible()) {
if (!firstWidget) {
widgetNames += ",";
} else {
firstWidget = false;
}
settings.setValue(_visibleWidgetsKey, widgetNames);
}
#endif