MainWindow.cc 28.8 KB
Newer Older
1 2 3 4
/*=====================================================================

QGroundControl Open Source Ground Control Station

5
(c) 2009 - 2013 QGROUNDCONTROL PROJECT <http://www.qgroundcontrol.org>
6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35

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/>.

======================================================================*/

/**
 * @file
 *   @brief Implementation of class MainWindow
 *   @author Lorenz Meier <mail@qgroundcontrol.org>
 */

#include <QSettings>
#include <QNetworkInterface>
#include <QDebug>
#include <QTimer>
#include <QHostInfo>
#include <QSplashScreen>
36
#include <QQuickView>
37
#include <QDesktopWidget>
38 39
#include <QScreen>
#include <QDesktopServices>
40
#include <QDockWidget>
41

42 43 44 45 46
#include "QGC.h"
#include "MAVLinkProtocol.h"
#include "MainWindow.h"
#include "GAudioOutput.h"
#include "QGCMAVLinkLogPlayer.h"
Don Gagne's avatar
Don Gagne committed
47
#include "SettingsDialog.h"
48
#include "MAVLinkDecoder.h"
49
#include "FlightDisplayView.h"
50
#include "SetupView.h"
Don Gagne's avatar
Don Gagne committed
51
#include "QGCApplication.h"
52
#include "QGCFileDialog.h"
Don Gagne's avatar
Don Gagne committed
53
#include "QGCMessageBox.h"
54
#include "MultiVehicleManager.h"
55
#include "HomePositionManager.h"
Don Gagne's avatar
Don Gagne committed
56
#include "MissionEditor.h"
57
#include "LogCompressor.h"
58
#include "UAS.h"
59 60

#ifndef __mobile__
61 62
#include "QGCDataPlot2D.h"
#include "Linecharts.h"
63 64 65 66 67 68 69 70
#include "QGCUASFileViewMulti.h"
#include "UASQuickView.h"
#include "QGCTabbedInfoView.h"
#include "UASRawStatusView.h"
#include "CustomCommandWidget.h"
#include "QGCDockWidget.h"
#include "FlightDisplayWidget.h"
#include "UASInfoWidget.h"
71 72 73 74 75 76
#include "HILDockWidget.h"
#endif

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

78 79 80 81
#ifdef UNITTEST_BUILD
#include "QmlControls/QmlTestWidget.h"
#endif

82 83 84 85 86
#ifdef QGC_OSG_ENABLED
#include "Q3DWidgetFactory.h"
#endif


87 88 89
/// The key under which the Main Window settings are saved
const char* MAIN_SETTINGS_GROUP = "QGC_MAINWINDOW";

90
#ifndef __mobile__
91 92 93 94 95 96 97
const char* MainWindow::_mavlinkDockWidgetName =            "MAVLink Inspector";
const char* MainWindow::_customCommandWidgetName =          "Custom Command";
const char* MainWindow::_filesDockWidgetName =              "Onboard Files";
const char* MainWindow::_uasStatusDetailsDockWidgetName =   "Status Details";
const char* MainWindow::_pfdDockWidgetName =                "Primary Flight Display";
const char* MainWindow::_uasInfoViewDockWidgetName =        "Info View";
const char* MainWindow::_hilDockWidgetName =                "HIL Config";
Don Gagne's avatar
Don Gagne committed
98
const char* MainWindow::_analyzeDockWidgetName =            "Analyze";
99 100

const char* MainWindow::_visibleWidgetsKey =                "VisibleWidgets";
101
#endif
102

Don Gagne's avatar
Don Gagne committed
103 104
static MainWindow* _instance = NULL;   ///< @brief MainWindow singleton

105
MainWindow* MainWindow::_create(QSplashScreen* splashScreen)
106
{
Don Gagne's avatar
Don Gagne committed
107
    Q_ASSERT(_instance == NULL);
108
    new MainWindow(splashScreen);
Don Gagne's avatar
Don Gagne committed
109 110
    // _instance is set in constructor
    Q_ASSERT(_instance);
111 112 113
    return _instance;
}

Don Gagne's avatar
Don Gagne committed
114
MainWindow* MainWindow::instance(void)
115
{
Don Gagne's avatar
Don Gagne committed
116
    return _instance;
117 118
}

119 120
void MainWindow::deleteInstance(void)
{
Don Gagne's avatar
Don Gagne committed
121
    delete this;
122 123
}

Don Gagne's avatar
Don Gagne committed
124 125 126
/// @brief Private constructor for MainWindow. MainWindow singleton is only ever created
///         by MainWindow::_create method. Hence no other code should have access to
///         constructor.
dogmaphobic's avatar
dogmaphobic committed
127 128 129
MainWindow::MainWindow(QSplashScreen* splashScreen)
    : _autoReconnect(false)
    , _lowPowerMode(false)
130
    , _showStatusBar(false)
dogmaphobic's avatar
dogmaphobic committed
131 132 133 134 135
    , _centerStackActionGroup(new QActionGroup(this))
    , _centralLayout(NULL)
    , _currentViewWidget(NULL)
    , _splashScreen(splashScreen)
    , _currentView(VIEW_SETUP)
136
{
Don Gagne's avatar
Don Gagne committed
137 138
    Q_ASSERT(_instance == NULL);
    _instance = this;
139

140 141 142
    if (splashScreen) {
        connect(this, &MainWindow::initStatusChanged, splashScreen, &QSplashScreen::showMessage);
    }
143

144 145 146 147
    // 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
dogmaphobic's avatar
dogmaphobic committed
148
    // Setup user interface
149
    loadSettings();
150
    emit initStatusChanged(tr("Setting up user interface"), Qt::AlignLeft | Qt::AlignBottom, QColor(62, 93, 141));
dogmaphobic's avatar
dogmaphobic committed
151 152
    _ui.setupUi(this);
    // Make sure tool bar elements all fit before changing minimum width
dogmaphobic's avatar
dogmaphobic committed
153
    setMinimumWidth(1008);
dogmaphobic's avatar
dogmaphobic committed
154
    configureWindowName();
155

156 157
    // Setup central widget with a layout to hold the views
    _centralLayout = new QVBoxLayout();
158
    _centralLayout->setContentsMargins(0,0,0,0);
159
    centralWidget()->setLayout(_centralLayout);
160 161 162
    // Set dock options
    setDockOptions(AnimatedDocks | AllowTabbedDocks | AllowNestedDocks);
    // Setup corners
163
    setCorner(Qt::BottomRightCorner, Qt::BottomDockWidgetArea);
164

dogmaphobic's avatar
dogmaphobic committed
165 166 167
    // On Mobile devices, we don't want any main menus at all.
#ifdef __mobile__
    menuBar()->setNativeMenuBar(false);
dogmaphobic's avatar
dogmaphobic committed
168
#endif
dogmaphobic's avatar
dogmaphobic committed
169

170
#ifndef __mobile__
171 172 173
#ifdef UNITTEST_BUILD
    QAction* qmlTestAction = new QAction("Test QML palette and controls", NULL);
    connect(qmlTestAction, &QAction::triggered, this, &MainWindow::_showQmlTestWidget);
174
    _ui.menuWidgets->addAction(qmlTestAction);
175
#endif
176
#endif
177

dogmaphobic's avatar
dogmaphobic committed
178 179 180 181
    // Load QML Toolbar
    QDockWidget* widget = new QDockWidget(this);
    widget->setObjectName("ToolBarDockWidget");
    qmlRegisterType<MainToolBar>("QGroundControl.MainToolBar", 1, 0, "MainToolBar");
182
    _mainToolBar = new MainToolBar(widget);
dogmaphobic's avatar
dogmaphobic committed
183 184 185 186
    widget->setWidget(_mainToolBar);
    widget->setFeatures(QDockWidget::NoDockWidgetFeatures);
    widget->setTitleBarWidget(new QWidget(this)); // Disables the title bar
    addDockWidget(Qt::TopDockWidgetArea, widget);
187

dogmaphobic's avatar
dogmaphobic committed
188 189 190
    // Setup UI state machines
    _centerStackActionGroup->setExclusive(true);
    // Status Bar
191
    setStatusBar(new QStatusBar(this));
192
    statusBar()->setSizeGripEnabled(true);
193

194
#ifndef __mobile__
195
    emit initStatusChanged(tr("Building common widgets."), Qt::AlignLeft | Qt::AlignBottom, QColor(62, 93, 141));
196
    _buildCommonWidgets();
197
    emit initStatusChanged(tr("Building common actions"), Qt::AlignLeft | Qt::AlignBottom, QColor(62, 93, 141));
198 199
#endif
    
200 201 202
    // Create actions
    connectCommonActions();
    // Connect user interface devices
203
#ifdef QGC_MOUSE_ENABLED_WIN
204
    emit initStatusChanged(tr("Initializing 3D mouse interface"), Qt::AlignLeft | Qt::AlignBottom, QColor(62, 93, 141));
205 206
    mouseInput = new Mouse3DInput(this);
    mouse = new Mouse6dofInput(mouseInput);
207
#endif //QGC_MOUSE_ENABLED_WIN
208

209
#if QGC_MOUSE_ENABLED_LINUX
210
    emit initStatusChanged(tr("Initializing 3D mouse interface"), Qt::AlignLeft | Qt::AlignBottom, QColor(62, 93, 141));
211 212

    mouse = new Mouse6dofInput(this);
213
    connect(this, SIGNAL(x11EventOccured(XEvent*)), mouse, SLOT(handleX11Event(XEvent*)));
214
#endif //QGC_MOUSE_ENABLED_LINUX
215

216 217 218 219
    // These also cause the screen to redraw so we need to update any OpenGL canvases in QML controls
    connect(LinkManager::instance(), &LinkManager::linkConnected,    this, &MainWindow::_linkStateChange);
    connect(LinkManager::instance(), &LinkManager::linkDisconnected, this, &MainWindow::_linkStateChange);

220
    // Connect link
dogmaphobic's avatar
dogmaphobic committed
221
    if (_autoReconnect)
222
    {
223
        restoreLastUsedConnection();
224 225 226
    }

    // Set low power mode
dogmaphobic's avatar
dogmaphobic committed
227
    enableLowPowerMode(_lowPowerMode);
228
    emit initStatusChanged(tr("Restoring last view state"), Qt::AlignLeft | Qt::AlignBottom, QColor(62, 93, 141));
229
    // Restore the window setup
230
    _loadCurrentViewState();
dogmaphobic's avatar
dogmaphobic committed
231
#ifndef __mobile__
232

233
    // Restore the window position and size
dogmaphobic's avatar
dogmaphobic committed
234 235
    emit initStatusChanged(tr("Restoring last window size"), Qt::AlignLeft | Qt::AlignBottom, QColor(62, 93, 141));
    if (settings.contains(_getWindowGeometryKey()))
236
    {
dogmaphobic's avatar
dogmaphobic committed
237
        restoreGeometry(settings.value(_getWindowGeometryKey()).toByteArray());
238 239 240 241
    }
    else
    {
        // Adjust the size
242 243 244
        QScreen* scr = QApplication::primaryScreen();
        QSize scrSize = scr->availableSize();
        if (scrSize.width() <= 1280)
245
        {
246
            resize(scrSize.width(), scrSize.height());
247 248 249
        }
        else
        {
250 251 252
            int w = scrSize.width()  > 1600 ? 1600 : scrSize.width();
            int h = scrSize.height() >  800 ?  800 : scrSize.height();
            resize(w, h);
253
            move((scrSize.width() - w) / 2, (scrSize.height() - h) / 2);
254 255 256
        }
    }

257 258 259
    // Make sure the proper fullscreen/normal menu item is checked properly.
    if (isFullScreen())
    {
dogmaphobic's avatar
dogmaphobic committed
260 261
        _ui.actionFullscreen->setChecked(true);
        _ui.actionNormal->setChecked(false);
262 263 264
    }
    else
    {
dogmaphobic's avatar
dogmaphobic committed
265 266
        _ui.actionFullscreen->setChecked(false);
        _ui.actionNormal->setChecked(true);
267 268 269
    }

    // And that they will stay checked properly after user input
270 271
    connect(_ui.actionFullscreen, &QAction::triggered, this, &MainWindow::fullScreenActionItemCallback);
    connect(_ui.actionNormal,     &QAction::triggered, this, &MainWindow::normalActionItemCallback);
272 273
#endif

274
    connect(_ui.actionStatusBar,  &QAction::triggered, this, &MainWindow::showStatusBarCallback);
275

276 277
    // Set OS dependent keyboard shortcuts for the main window, non OS dependent shortcuts are set in MainWindow.ui
#ifdef Q_OS_MACX
dogmaphobic's avatar
dogmaphobic committed
278
    _ui.actionSetup->setShortcut(QApplication::translate("MainWindow", "Meta+1", 0));
279 280
    _ui.actionPlan->setShortcut(QApplication::translate("MainWindow", "Meta+2", 0));
    _ui.actionFlight->setShortcut(QApplication::translate("MainWindow", "Meta+3", 0));
dogmaphobic's avatar
dogmaphobic committed
281
    _ui.actionFullscreen->setShortcut(QApplication::translate("MainWindow", "Meta+Return", 0));
282
#else
dogmaphobic's avatar
dogmaphobic committed
283
    _ui.actionSetup->setShortcut(QApplication::translate("MainWindow", "Ctrl+1", 0));
Gus Grubba's avatar
Gus Grubba committed
284
    _ui.actionPlan->setShortcut(QApplication::translate("MainWindow", "Ctrl+2", 0));
285
    _ui.actionFlight->setShortcut(QApplication::translate("MainWindow", "Ctrl+3", 0));
dogmaphobic's avatar
dogmaphobic committed
286
    _ui.actionFullscreen->setShortcut(QApplication::translate("MainWindow", "Ctrl+Return", 0));
287 288
#endif

289 290
    connect(&windowNameUpdateTimer, SIGNAL(timeout()), this, SLOT(configureWindowName()));
    windowNameUpdateTimer.start(15000);
291
    emit initStatusChanged(tr("Done"), Qt::AlignLeft | Qt::AlignBottom, QColor(62, 93, 141));
292 293

    if (!qgcApp()->runningUnitTests()) {
294 295
        _ui.actionStatusBar->setChecked(_showStatusBar);
        showStatusBarCallback(_showStatusBar);
dogmaphobic's avatar
dogmaphobic committed
296
#ifdef __mobile__
297 298
        menuBar()->hide();
#endif
299
        show();
dogmaphobic's avatar
dogmaphobic committed
300 301 302 303 304 305 306 307 308 309 310 311 312 313 314
#ifdef Q_OS_MAC
        // 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
315
    }
316 317 318 319
    
#ifndef __mobile__
    _loadVisibleWidgetsSettings();
#endif
320 321 322 323 324
}

MainWindow::~MainWindow()
{
    // Delete all UAS objects
dogmaphobic's avatar
dogmaphobic committed
325
    for (int i=0;i<_commsWidgetList.size();i++)
326
    {
dogmaphobic's avatar
dogmaphobic committed
327
        _commsWidgetList[i]->deleteLater();
328
    }
Don Gagne's avatar
Don Gagne committed
329
    _instance = NULL;
330 331 332 333 334 335 336
}

void MainWindow::resizeEvent(QResizeEvent * event)
{
    QMainWindow::resizeEvent(event);
}

dogmaphobic's avatar
dogmaphobic committed
337
QString MainWindow::_getWindowStateKey()
338
{
339
	return QString::number(_currentView)+"_windowstate_";
340 341
}

dogmaphobic's avatar
dogmaphobic committed
342
QString MainWindow::_getWindowGeometryKey()
343 344 345 346
{
    return "_geometry";
}

347
#ifndef __mobile__
348
void MainWindow::_buildCommonWidgets(void)
349 350
{
    // Add generic MAVLink decoder
dogmaphobic's avatar
dogmaphobic committed
351
    // TODO: This is never deleted
352
    mavlinkDecoder = new MAVLinkDecoder(MAVLinkProtocol::instance(), this);
John Tapsell's avatar
John Tapsell committed
353 354
    connect(mavlinkDecoder, SIGNAL(valueChanged(int,QString,QString,QVariant,quint64)),
                      this, SIGNAL(valueChanged(int,QString,QString,QVariant,quint64)));
355

356
    // Log player
dogmaphobic's avatar
dogmaphobic committed
357
    // TODO: Make this optional with a preferences setting or under a "View" menu
Don Gagne's avatar
Don Gagne committed
358
    logPlayer = new QGCMAVLinkLogPlayer(statusBar());
359
    statusBar()->addPermanentWidget(logPlayer);
360

361 362 363 364 365 366 367 368
    static const char* rgDockWidgetNames[] = {
        _mavlinkDockWidgetName,
        _customCommandWidgetName,
        _filesDockWidgetName,
        _uasStatusDetailsDockWidgetName,
        _pfdDockWidgetName,
        _uasInfoViewDockWidgetName,
        _hilDockWidgetName,
Don Gagne's avatar
Don Gagne committed
369
        _analyzeDockWidgetName,
370
    };
371
    static const size_t cDockWidgetNames = sizeof(rgDockWidgetNames) / sizeof(rgDockWidgetNames[0]);
372

373 374 375 376 377 378 379 380 381 382 383
    for (size_t i=0; i<cDockWidgetNames; i++) {
        const char* pDockWidgetName = rgDockWidgetNames[i];
        
        // Add to menu
        QAction* action = new QAction(pDockWidgetName, NULL);
        action->setCheckable(true);
        action->setData(pDockWidgetName);
        connect(action, &QAction::triggered, this, &MainWindow::_showDockWidgetAction);
        _ui.menuWidgets->addAction(action);
        
        _mapName2Action[pDockWidgetName] = action;
384
    }
385
}
386

387 388
/// Shows or hides the specified dock widget, creating if necessary
void MainWindow::_showDockWidget(const QString& name, bool show)
389
{
390
    // Create the inner widget if we need to
391
    if (!_mapName2DockWidget.contains(name)) {
392
        _createInnerDockWidget(name);
393
    }
394
    
395
    Q_ASSERT(_mapName2DockWidget.contains(name));
396
    QGCDockWidget* dockWidget = _mapName2DockWidget[name];
397
    Q_ASSERT(dockWidget);
398
    
399
    dockWidget->setVisible(show);
400
    
401 402
    Q_ASSERT(_mapName2Action.contains(name));
    _mapName2Action[name]->setChecked(show);
403 404 405 406 407
}

/// Creates the specified inner dock widget and adds to the QDockWidget
void MainWindow::_createInnerDockWidget(const QString& widgetName)
{
408
    QGCDockWidget* widget = NULL;
409
    
410
    if (widgetName == _mavlinkDockWidgetName) {
411
        widget = new QGCMAVLinkInspector(widgetName, _mapName2Action[widgetName], MAVLinkProtocol::instance(),this);
Don Gagne's avatar
Don Gagne committed
412
    } else if (widgetName == _customCommandWidgetName) {
413
        widget = new CustomCommandWidget(widgetName, _mapName2Action[widgetName], this);
414
    } else if (widgetName == _filesDockWidgetName) {
415
        widget = new QGCUASFileViewMulti(widgetName, _mapName2Action[widgetName], this);
416
    } else if (widgetName == _uasStatusDetailsDockWidgetName) {
417
        widget = new UASInfoWidget(widgetName, _mapName2Action[widgetName], this);
418
    } else if (widgetName == _pfdDockWidgetName) {
419
        widget = new FlightDisplayWidget(widgetName, _mapName2Action[widgetName], this);
420
    } else if (widgetName == _hilDockWidgetName) {
421
        widget = new HILDockWidget(widgetName, _mapName2Action[widgetName], this);
Don Gagne's avatar
Don Gagne committed
422 423
    } else if (widgetName == _analyzeDockWidgetName) {
        widget = new Linecharts(widgetName, _mapName2Action[widgetName], mavlinkDecoder, this);
424
    } else if (widgetName == _uasInfoViewDockWidgetName) {
425
        QGCTabbedInfoView* pInfoView = new QGCTabbedInfoView(widgetName, _mapName2Action[widgetName], this);
426 427
        pInfoView->addSource(mavlinkDecoder);
        widget = pInfoView;
428 429
    } else {
        qWarning() << "Attempt to create unknown Inner Dock Widget" << widgetName;
430
    }
431
    
432
    _mapName2DockWidget[widgetName] = widget;
433
}
434

435 436
void MainWindow::_hideAllDockWidgets(void)
{
437
    foreach(QGCDockWidget* dockWidget, _mapName2DockWidget) {
438 439 440 441 442 443 444 445
        dockWidget->setVisible(false);
    }
}

void MainWindow::_showDockWidgetAction(bool show)
{
    QAction* action = dynamic_cast<QAction*>(QObject::sender());
    Q_ASSERT(action);
446
    _showDockWidget(action->text(), show);
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
}
#endif

void MainWindow::_buildMissionEditorView(void)
{
    if (!_missionEditorView) {
        _missionEditorView = new MissionEditor(this);
        _missionEditorView->setVisible(false);
    }
}

void MainWindow::_buildFlightView(void)
{
    if (!_flightView) {
        _flightView = new FlightDisplayView(this);
        _flightView->setVisible(false);
    }
}

void MainWindow::_buildSetupView(void)
{
    if (!_setupView) {
        _setupView = new SetupView(this);
        _setupView->setVisible(false);
    }
}

474
void MainWindow::fullScreenActionItemCallback(bool)
475
{
dogmaphobic's avatar
dogmaphobic committed
476
    _ui.actionNormal->setChecked(false);
477 478
}

479
void MainWindow::normalActionItemCallback(bool)
480
{
dogmaphobic's avatar
dogmaphobic committed
481
    _ui.actionFullscreen->setChecked(false);
482 483
}

484 485 486 487 488 489
void MainWindow::showStatusBarCallback(bool checked)
{
    _showStatusBar = checked;
    checked ? statusBar()->show() : statusBar()->hide();
}

490 491
void MainWindow::closeEvent(QCloseEvent *event)
{
492
    // Disallow window close if there are active connections
493
    if (LinkManager::instance()->anyConnectedLinks()) {
dogmaphobic's avatar
dogmaphobic committed
494 495 496 497 498 499
        QGCMessageBox::StandardButton button =
            QGCMessageBox::warning(
                tr("QGroundControl close"),
                tr("There are still active connections to vehicles. Do you want to disconnect these before closing?"),
                QMessageBox::Yes | QMessageBox::Cancel,
                QMessageBox::Cancel);
500 501 502 503 504 505 506 507 508 509
		if (button == QMessageBox::Yes) {
			LinkManager::instance()->disconnectAll();
			// The above disconnect causes a flurry of activity as the vehicle components are removed. This in turn
			// causes the Windows Version of Qt to crash if you allow the close event to be accepted. In order to prevent
			// the crash, we ignore the close event and setup a delayed timer to close the window after things settle down.
			QTimer::singleShot(1500, this, &MainWindow::_closeWindow);
		}

        event->ignore();
        return;
510 511
    }

512 513
    // This will process any remaining flight log save dialogs
    qgcApp()->processEvents(QEventLoop::ExcludeUserInputEvents);
514
    
515
    // Should not be any active connections
516 517
    Q_ASSERT(!LinkManager::instance()->anyConnectedLinks());
    
518
    _storeCurrentViewState();
519
    storeSettings();
520
    event->accept();
521 522 523 524
}

void MainWindow::loadSettings()
{
525
    // Why the screaming?
526
    QSettings settings;
527
    settings.beginGroup(MAIN_SETTINGS_GROUP);
528 529 530
    _autoReconnect  = settings.value("AUTO_RECONNECT",      _autoReconnect).toBool();
    _lowPowerMode   = settings.value("LOW_POWER_MODE",      _lowPowerMode).toBool();
    _showStatusBar  = settings.value("SHOW_STATUSBAR",      _showStatusBar).toBool();
531 532 533 534 535 536
    settings.endGroup();
}

void MainWindow::storeSettings()
{
    QSettings settings;
537
    settings.beginGroup(MAIN_SETTINGS_GROUP);
538 539 540
    settings.setValue("AUTO_RECONNECT",     _autoReconnect);
    settings.setValue("LOW_POWER_MODE",     _lowPowerMode);
    settings.setValue("SHOW_STATUSBAR",     _showStatusBar);
541
    settings.endGroup();
dogmaphobic's avatar
dogmaphobic committed
542
    settings.setValue(_getWindowGeometryKey(), saveGeometry());
543
	
544
    // Save the last current view in any case
545
    settings.setValue("CURRENT_VIEW", _currentView);
546
    settings.setValue(_getWindowStateKey(), saveState());
547 548 549 550
    
#ifndef __mobile__
    _storeVisibleWidgetsSettings();
#endif
551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574
}

void MainWindow::configureWindowName()
{
    QList<QHostAddress> hostAddresses = QNetworkInterface::allAddresses();
    QString windowname = qApp->applicationName() + " " + qApp->applicationVersion();
    bool prevAddr = false;
    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);
}

void MainWindow::enableAutoReconnect(bool enabled)
{
dogmaphobic's avatar
dogmaphobic committed
575
    _autoReconnect = enabled;
576 577 578 579 580 581 582 583 584
}

/**
* @brief Create all actions associated to the main window
*
**/
void MainWindow::connectCommonActions()
{
    // Bind together the perspective actions
dogmaphobic's avatar
dogmaphobic committed
585
    QActionGroup* perspectives = new QActionGroup(_ui.menuPerspectives);
586
    perspectives->addAction(_ui.actionPlan);
dogmaphobic's avatar
dogmaphobic committed
587
    perspectives->addAction(_ui.actionSetup);
588 589
    perspectives->setExclusive(true);

590
    if (_currentView == VIEW_FLIGHT)
591
    {
592 593
        _ui.actionFlight->setChecked(true);
        _ui.actionFlight->activate(QAction::Trigger);
594
    }
595
    if (_currentView == VIEW_MISSIONEDITOR)
596
    {
597 598 599
        _ui.actionPlan->setChecked(true);
        _ui.actionPlan->activate(QAction::Trigger);
    }
600
    if (_currentView == VIEW_SETUP)
601
    {
dogmaphobic's avatar
dogmaphobic committed
602 603
        _ui.actionSetup->setChecked(true);
        _ui.actionSetup->activate(QAction::Trigger);
604
    }
605 606

    // Connect actions from ui
dogmaphobic's avatar
dogmaphobic committed
607
    connect(_ui.actionAdd_Link, SIGNAL(triggered()), this, SLOT(manageLinks()));
608 609

    // Connect internal actions
610
    connect(MultiVehicleManager::instance(), &MultiVehicleManager::vehicleAdded, this, &MainWindow::_vehicleAdded);
611 612

    // Views actions
Don Gagne's avatar
Don Gagne committed
613 614
    connect(_ui.actionFlight,           SIGNAL(triggered()), this, SLOT(loadFlightView()));
    connect(_ui.actionPlan,             SIGNAL(triggered()), this, SLOT(loadPlanView()));
615
    
616
    // Help Actions
dogmaphobic's avatar
dogmaphobic committed
617 618 619
    connect(_ui.actionOnline_Documentation, SIGNAL(triggered()), this, SLOT(showHelp()));
    connect(_ui.actionDeveloper_Credits, SIGNAL(triggered()), this, SLOT(showCredits()));
    connect(_ui.actionProject_Roadmap, SIGNAL(triggered()), this, SLOT(showRoadMap()));
620 621

    // Audio output
dogmaphobic's avatar
dogmaphobic committed
622 623 624
    _ui.actionMuteAudioOutput->setChecked(GAudioOutput::instance()->isMuted());
    connect(GAudioOutput::instance(), SIGNAL(mutedChanged(bool)), _ui.actionMuteAudioOutput, SLOT(setChecked(bool)));
    connect(_ui.actionMuteAudioOutput, SIGNAL(triggered(bool)), GAudioOutput::instance(), SLOT(mute(bool)));
625 626

    // Application Settings
dogmaphobic's avatar
dogmaphobic committed
627
    connect(_ui.actionSettings, SIGNAL(triggered()), this, SLOT(showSettings()));
628

dogmaphobic's avatar
dogmaphobic committed
629
    // Update Tool Bar
630
    _mainToolBar->setCurrentView(_currentView);
631 632
}

Don Gagne's avatar
Don Gagne committed
633
void MainWindow::_openUrl(const QString& url, const QString& errorMessage)
634
{
Don Gagne's avatar
Don Gagne committed
635
    if(!QDesktopServices::openUrl(QUrl(url))) {
dogmaphobic's avatar
dogmaphobic committed
636 637 638 639
        QMessageBox::critical(
            this,
            tr("Could not open information in browser"),
            errorMessage);
640 641 642
    }
}

Don Gagne's avatar
Don Gagne committed
643 644
void MainWindow::showHelp()
{
dogmaphobic's avatar
dogmaphobic committed
645 646 647
    _openUrl(
        "http://qgroundcontrol.org/users/start",
        tr("To get to the online help, please open http://qgroundcontrol.org/user_guide in a browser."));
Don Gagne's avatar
Don Gagne committed
648 649
}

650 651
void MainWindow::showCredits()
{
dogmaphobic's avatar
dogmaphobic committed
652 653 654
    _openUrl(
        "http://qgroundcontrol.org/credits",
        tr("To get to the credits, please open http://qgroundcontrol.org/credits in a browser."));
655 656 657 658
}

void MainWindow::showRoadMap()
{
dogmaphobic's avatar
dogmaphobic committed
659 660 661
    _openUrl(
        "http://qgroundcontrol.org/dev/roadmap",
        tr("To get to the online help, please open http://qgroundcontrol.org/roadmap in a browser."));
662 663 664 665
}

void MainWindow::showSettings()
{
dogmaphobic's avatar
dogmaphobic committed
666
    SettingsDialog settings(this);
Don Gagne's avatar
Don Gagne committed
667
    settings.exec();
668 669
}

670 671
void MainWindow::commsWidgetDestroyed(QObject *obj)
{
672 673
    // Do not dynamic cast or de-reference QObject, since object is either in destructor or may have already
    // been destroyed.
dogmaphobic's avatar
dogmaphobic committed
674
    if (_commsWidgetList.contains(obj))
675
    {
dogmaphobic's avatar
dogmaphobic committed
676
        _commsWidgetList.removeOne(obj);
677 678
    }
}
679

680
void MainWindow::_vehicleAdded(Vehicle* vehicle)
681
{
682
    connect(vehicle->uas(), SIGNAL(valueChanged(int,QString,QString,QVariant,quint64)), this, SIGNAL(valueChanged(int,QString,QString,QVariant,quint64)));
683 684
}

685 686
/// Stores the state of the toolbar, status bar and widgets associated with the current view
void MainWindow::_storeCurrentViewState(void)
687
{
Don Gagne's avatar
Don Gagne committed
688
#ifndef __mobile__
689 690
    foreach(QGCDockWidget* dockWidget, _mapName2DockWidget) {
        dockWidget->saveSettings();
691
    }
Don Gagne's avatar
Don Gagne committed
692
#endif
693
    
dogmaphobic's avatar
dogmaphobic committed
694 695
    settings.setValue(_getWindowStateKey(), saveState());
    settings.setValue(_getWindowGeometryKey(), saveGeometry());
696 697
}

698 699
/// Restores the state of the toolbar, status bar and widgets associated with the current view
void MainWindow::_loadCurrentViewState(void)
700
{
Don Gagne's avatar
Don Gagne committed
701
    QWidget* centerView = NULL;
702

703
    switch (_currentView) {
704
        case VIEW_SETUP:
705 706
            _buildSetupView();
            centerView = _setupView;
707
            break;
708

709
        case VIEW_FLIGHT:
710 711
            _buildFlightView();
            centerView = _flightView;
712
            break;
713

Don Gagne's avatar
Don Gagne committed
714 715 716 717 718
        case VIEW_MISSIONEDITOR:
            _buildMissionEditorView();
            centerView = _missionEditorView;
            break;

719 720
        default:
            Q_ASSERT(false);
721
            break;
722
    }
723

724 725 726 727 728 729 730 731
    // Remove old view
    if (_currentViewWidget) {
        _currentViewWidget->setVisible(false);
        Q_ASSERT(_centralLayout->count() == 1);
        QLayoutItem *child = _centralLayout->takeAt(0);
        Q_ASSERT(child);
        delete child;
    }
732

733 734 735 736 737
    // Add the new one
    Q_ASSERT(centerView);
    Q_ASSERT(_centralLayout->count() == 0);
    _currentViewWidget = centerView;
    _centralLayout->addWidget(_currentViewWidget);
738
    _centralLayout->setContentsMargins(0, 0, 0, 0);
739
    _currentViewWidget->setVisible(true);
740

dogmaphobic's avatar
dogmaphobic committed
741 742
    if (settings.contains(_getWindowStateKey())) {
        restoreState(settings.value(_getWindowStateKey()).toByteArray());
743
    }
744

745 746 747
    // There is a bug in Qt where a Canvas element inside a QQuickWidget does not
    // receive update requests. Here we emit a signal for them to get repainted.
    emit repaintCanvas();
748 749
}

750
void MainWindow::loadPlanView()
751
{
752 753 754 755 756 757
    if (_currentView != VIEW_MISSIONEDITOR)
    {
        _storeCurrentViewState();
        _currentView = VIEW_MISSIONEDITOR;
        _ui.actionPlan->setChecked(true);
        _loadCurrentViewState();
Don Gagne's avatar
Don Gagne committed
758 759 760
    }
}

761
void MainWindow::loadSetupView()
762
{
763
    if (_currentView != VIEW_SETUP)
764
    {
765 766
        _storeCurrentViewState();
        _currentView = VIEW_SETUP;
dogmaphobic's avatar
dogmaphobic committed
767
        _ui.actionSetup->setChecked(true);
768
        _loadCurrentViewState();
769 770 771
    }
}

772
void MainWindow::loadFlightView()
773
{
774
    if (_currentView != VIEW_FLIGHT)
775
    {
776 777
        _storeCurrentViewState();
        _currentView = VIEW_FLIGHT;
778
        _ui.actionFlight->setChecked(true);
779
        _loadCurrentViewState();
780 781 782
    }
}

Don Gagne's avatar
Don Gagne committed
783
/// @brief Hides the spash screen if it is currently being shown
784
void MainWindow::hideSplashScreen(void)
Don Gagne's avatar
Don Gagne committed
785 786 787 788 789 790 791
{
    if (_splashScreen) {
        _splashScreen->hide();
        _splashScreen = NULL;
    }
}

792 793
void MainWindow::manageLinks()
{
dogmaphobic's avatar
dogmaphobic committed
794
    SettingsDialog settings(this, SettingsDialog::ShowCommLinks);
795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817
    settings.exec();
}

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

/// @brief Restore (and connects) the last used connection (if any)
void MainWindow::restoreLastUsedConnection()
{
    // TODO This should check and see of the port/whatever is present
    // first. That is, if the last connection was to a PX4 on some serial
    // port, it should check and see if the port is present before making
    // the connection.
    QSettings settings;
    QString key(MAIN_SETTINGS_GROUP);
    key += "/LAST_CONNECTION";
    if(settings.contains(key)) {
818
        QString connection = settings.value(key).toString();
819
        // Create a link for it
820
        LinkManager::instance()->createConnectedLink(connection);
821 822
    }
}
Don Gagne's avatar
Don Gagne committed
823

824 825 826 827 828
void MainWindow::_linkStateChange(LinkInterface*)
{
    emit repaintCanvas();
}

829
#ifdef QGC_MOUSE_ENABLED_LINUX
830 831 832
bool MainWindow::x11Event(XEvent *event)
{
    emit x11EventOccured(event);
833
    return false;
834
}
835
#endif // QGC_MOUSE_ENABLED_LINUX
836 837 838 839 840 841 842

#ifdef UNITTEST_BUILD
void MainWindow::_showQmlTestWidget(void)
{
    new QmlTestWidget();
}
#endif
843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881

#ifndef __mobile__
void MainWindow::_loadVisibleWidgetsSettings(void)
{
    QSettings settings;
    
    QString widgets = settings.value(_visibleWidgetsKey).toString();
    
    if (!widgets.isEmpty()) {
        QStringList nameList = widgets.split(",");
        
        foreach (QString name, nameList) {
            _showDockWidget(name, true);
        }
    }
}

void MainWindow::_storeVisibleWidgetsSettings(void)
{
    QString widgetNames;
    bool firstWidget = true;
    
    foreach (QString name, _mapName2DockWidget.keys()) {
        if (_mapName2DockWidget[name]->isVisible()) {
            if (!firstWidget) {
                widgetNames += ",";
            } else {
                firstWidget = false;
            }
            
            widgetNames += name;
        }
    }
    
    QSettings settings;
    
    settings.setValue(_visibleWidgetsKey, widgetNames);
}
#endif