MainWindow.cc 31.9 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 50
#include "QGCDataPlot2D.h"
#include "Linecharts.h"
51
#include "FlightDisplayView.h"
52
#include "SetupView.h"
Don Gagne's avatar
Don Gagne committed
53
#include "QGCApplication.h"
54
#include "QGCFileDialog.h"
Don Gagne's avatar
Don Gagne committed
55
#include "QGCMessageBox.h"
56
#include "MultiVehicleManager.h"
57
#include "HomePositionManager.h"
Don Gagne's avatar
Don Gagne committed
58
#include "MissionEditor.h"
59 60 61
#include "LogCompressor.h"

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

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

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

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


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

89
#ifndef __mobile__
90
const char* MainWindow::_mavlinkDockWidgetName = "MAVLINK_INSPECTOR_DOCKWIDGET";
Don Gagne's avatar
Don Gagne committed
91
const char* MainWindow::_customCommandWidgetName = "CUSTOM_COMMAND_DOCKWIDGET";
92 93 94 95 96
const char* MainWindow::_filesDockWidgetName = "FILE_VIEW_DOCKWIDGET";
const char* MainWindow::_uasStatusDetailsDockWidgetName = "UAS_STATUS_DETAILS_DOCKWIDGET";
const char* MainWindow::_mapViewDockWidgetName = "MAP_VIEW_DOCKWIDGET";
const char* MainWindow::_pfdDockWidgetName = "PRIMARY_FLIGHT_DISPLAY_DOCKWIDGET";
const char* MainWindow::_uasInfoViewDockWidgetName = "UAS_INFO_INFOVIEW_DOCKWIDGET";
97
const char* MainWindow::_hilDockWidgetName = "HIL_DOCKWIDGET";
98
#endif
99

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

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

Don Gagne's avatar
Don Gagne committed
111
MainWindow* MainWindow::instance(void)
112
{
Don Gagne's avatar
Don Gagne committed
113
    return _instance;
114 115
}

116 117
void MainWindow::deleteInstance(void)
{
Don Gagne's avatar
Don Gagne committed
118
    delete this;
119 120
}

Don Gagne's avatar
Don Gagne committed
121 122 123
/// @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
124 125 126
MainWindow::MainWindow(QSplashScreen* splashScreen)
    : _autoReconnect(false)
    , _lowPowerMode(false)
127
    , _showStatusBar(false)
dogmaphobic's avatar
dogmaphobic committed
128 129 130 131 132
    , _centerStackActionGroup(new QActionGroup(this))
    , _centralLayout(NULL)
    , _currentViewWidget(NULL)
    , _splashScreen(splashScreen)
    , _currentView(VIEW_SETUP)
133
{
Don Gagne's avatar
Don Gagne committed
134 135
    Q_ASSERT(_instance == NULL);
    _instance = this;
136

137 138 139
    if (splashScreen) {
        connect(this, &MainWindow::initStatusChanged, splashScreen, &QSplashScreen::showMessage);
    }
140

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

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

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

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

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

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

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

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

    mouse = new Mouse6dofInput(this);
210
    connect(this, SIGNAL(x11EventOccured(XEvent*)), mouse, SLOT(handleX11Event(XEvent*)));
211
#endif //QGC_MOUSE_ENABLED_LINUX
212

213 214 215 216
    // 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);

217
    // Connect link
dogmaphobic's avatar
dogmaphobic committed
218
    if (_autoReconnect)
219
    {
220
        restoreLastUsedConnection();
221 222 223
    }

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

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

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

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

271
    connect(_ui.actionStatusBar,  &QAction::triggered, this, &MainWindow::showStatusBarCallback);
272

273 274
    // 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
275
    _ui.actionSetup->setShortcut(QApplication::translate("MainWindow", "Meta+1", 0));
276 277 278
    _ui.actionPlan->setShortcut(QApplication::translate("MainWindow", "Meta+2", 0));
    _ui.actionFlight->setShortcut(QApplication::translate("MainWindow", "Meta+3", 0));
    _ui.actionAnalyze->setShortcut(QApplication::translate("MainWindow", "Meta+4", 0));
dogmaphobic's avatar
dogmaphobic committed
279
    _ui.actionFullscreen->setShortcut(QApplication::translate("MainWindow", "Meta+Return", 0));
280
#else
dogmaphobic's avatar
dogmaphobic committed
281
    _ui.actionSetup->setShortcut(QApplication::translate("MainWindow", "Ctrl+1", 0));
Gus Grubba's avatar
Gus Grubba committed
282
    _ui.actionPlan->setShortcut(QApplication::translate("MainWindow", "Ctrl+2", 0));
283
    _ui.actionFlight->setShortcut(QApplication::translate("MainWindow", "Ctrl+3", 0));
Gus Grubba's avatar
Gus Grubba committed
284
    _ui.actionAnalyze->setShortcut(QApplication::translate("MainWindow", "Ctrl+4", 0));
dogmaphobic's avatar
dogmaphobic committed
285
    _ui.actionFullscreen->setShortcut(QApplication::translate("MainWindow", "Ctrl+Return", 0));
286 287
#endif

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

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

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

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

dogmaphobic's avatar
dogmaphobic committed
332
QString MainWindow::_getWindowStateKey()
333
{
334
	return QString::number(_currentView)+"_windowstate_";
335 336
}

dogmaphobic's avatar
dogmaphobic committed
337
QString MainWindow::_getWindowGeometryKey()
338 339 340 341
{
    return "_geometry";
}

342
#ifndef __mobile__
343 344 345
void MainWindow::_createDockWidget(const QString& title, const QString& name, Qt::DockWidgetArea area, QWidget* innerWidget)
{
    Q_ASSERT(!_mapName2DockWidget.contains(name));
346
	
347 348 349 350 351
    // Add to menu
    QAction* action = new QAction(title, NULL);
    action->setCheckable(true);
    action->setData(name);
    connect(action, &QAction::triggered, this, &MainWindow::_showDockWidgetAction);
352
    _ui.menuWidgets->addAction(action);
353 354 355 356 357 358 359 360 361 362 363 364 365
	
	// Create widget
	QGCDockWidget* dockWidget = new QGCDockWidget(title, action, this);
	Q_CHECK_PTR(dockWidget);
	dockWidget->setObjectName(name);
	dockWidget->setVisible (false);
	if (innerWidget) {
		// Put inner widget inside QDockWidget
		innerWidget->setParent(dockWidget);
		dockWidget->setWidget(innerWidget);
		innerWidget->setVisible(true);
	}
	
366 367 368 369 370 371
    _mapName2DockWidget[name] = dockWidget;
    _mapDockWidget2Action[dockWidget] = action;
    addDockWidget(area, dockWidget);
}

void MainWindow::_buildCommonWidgets(void)
372 373
{
    // Add generic MAVLink decoder
dogmaphobic's avatar
dogmaphobic committed
374
    // TODO: This is never deleted
375
    mavlinkDecoder = new MAVLinkDecoder(MAVLinkProtocol::instance(), this);
John Tapsell's avatar
John Tapsell committed
376 377
    connect(mavlinkDecoder, SIGNAL(valueChanged(int,QString,QString,QVariant,quint64)),
                      this, SIGNAL(valueChanged(int,QString,QString,QVariant,quint64)));
378

379
    // Log player
dogmaphobic's avatar
dogmaphobic committed
380
    // TODO: Make this optional with a preferences setting or under a "View" menu
Don Gagne's avatar
Don Gagne committed
381
    logPlayer = new QGCMAVLinkLogPlayer(statusBar());
382
    statusBar()->addPermanentWidget(logPlayer);
383

384 385 386
    // In order for Qt to save and restore state of widgets all widgets must be created ahead of time. We only create the QDockWidget
    // holders. We do not create the actual inner widget until it is needed. This saves memory and cpu from running widgets that are
    // never shown.
387

388 389 390 391 392
    struct DockWidgetInfo {
        const char* name;
        const char* title;
        Qt::DockWidgetArea area;
    };
393

394 395
    static const struct DockWidgetInfo rgDockWidgetInfo[] = {
        { _mavlinkDockWidgetName,           "MAVLink Inspector",        Qt::RightDockWidgetArea },
Don Gagne's avatar
Don Gagne committed
396
        { _customCommandWidgetName,         "Custom Command",			Qt::RightDockWidgetArea },
397 398 399 400 401
        { _filesDockWidgetName,             "Onboard Files",            Qt::RightDockWidgetArea },
        { _uasStatusDetailsDockWidgetName,  "Status Details",           Qt::RightDockWidgetArea },
        { _mapViewDockWidgetName,           "Map view",                 Qt::RightDockWidgetArea },
        { _pfdDockWidgetName,               "Primary Flight Display",   Qt::RightDockWidgetArea },
        { _uasInfoViewDockWidgetName,       "Info View",                Qt::LeftDockWidgetArea },
402
        { _hilDockWidgetName,               "HIL Config",               Qt::LeftDockWidgetArea },
403 404
    };
    static const size_t cDockWidgetInfo = sizeof(rgDockWidgetInfo) / sizeof(rgDockWidgetInfo[0]);
405

406 407 408
    for (size_t i=0; i<cDockWidgetInfo; i++) {
        const struct DockWidgetInfo* pDockInfo = &rgDockWidgetInfo[i];
        _createDockWidget(pDockInfo->title, pDockInfo->name, pDockInfo->area, NULL /* no inner widget yet */);
409
    }
410
}
411

412 413
/// Shows or hides the specified dock widget, creating if necessary
void MainWindow::_showDockWidget(const QString& name, bool show)
414
{
415
    if (!_mapName2DockWidget.contains(name)) {
Don Gagne's avatar
Don Gagne committed
416 417
        // Don't show any sort of warning here. Dock Widgets which have been remove could still be in settings.
        // Which would cause us to end up here.
418 419
        return;
    }
420
    
421 422 423
    // Create the inner widget if we need to
    if (!_mapName2DockWidget[name]->widget()) {
        _createInnerDockWidget(name);
424
    }
425
    
426 427 428
    Q_ASSERT(_mapName2DockWidget.contains(name));
    QDockWidget* dockWidget = _mapName2DockWidget[name];
    Q_ASSERT(dockWidget);
429
    
430
    dockWidget->setVisible(show);
431
    
432 433 434 435 436 437 438 439 440
    Q_ASSERT(_mapDockWidget2Action.contains(dockWidget));
    _mapDockWidget2Action[dockWidget]->setChecked(show);
}

/// Creates the specified inner dock widget and adds to the QDockWidget
void MainWindow::_createInnerDockWidget(const QString& widgetName)
{
    Q_ASSERT(_mapName2DockWidget.contains(widgetName)); // QDockWidget should already exist
    Q_ASSERT(!_mapName2DockWidget[widgetName]->widget());     // Inner widget should not
441
    
442
    QWidget* widget = NULL;
443
    
444
    if (widgetName == _mavlinkDockWidgetName) {
445
        widget = new QGCMAVLinkInspector(MAVLinkProtocol::instance(),this);
Don Gagne's avatar
Don Gagne committed
446 447
    } else if (widgetName == _customCommandWidgetName) {
        widget = new CustomCommandWidget(this);
448 449 450 451 452
    } else if (widgetName == _filesDockWidgetName) {
        widget = new QGCUASFileViewMulti(this);
    } else if (widgetName == _uasStatusDetailsDockWidgetName) {
        widget = new UASInfoWidget(this);
    } else if (widgetName == _pfdDockWidgetName) {
453
        widget = new FlightDisplayWidget(this);
Don Gagne's avatar
Don Gagne committed
454
#ifndef __mobile__
455 456
    } else if (widgetName == _hilDockWidgetName) {
        widget = new HILDockWidget(this);
Don Gagne's avatar
Don Gagne committed
457
#endif
458
    } else if (widgetName == _uasInfoViewDockWidgetName) {
459 460 461
        QGCTabbedInfoView* pInfoView = new QGCTabbedInfoView(this);
        pInfoView->addSource(mavlinkDecoder);
        widget = pInfoView;
462 463
    } else {
        qWarning() << "Attempt to create unknown Inner Dock Widget" << widgetName;
464
    }
465
    
466 467 468 469 470
    if (widget) {
        QDockWidget* dockWidget = _mapName2DockWidget[widgetName];
        Q_CHECK_PTR(dockWidget);
        widget->setParent(dockWidget);
        dockWidget->setWidget(widget);
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 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520
void MainWindow::_hideAllDockWidgets(void)
{
    foreach(QDockWidget* dockWidget, _mapName2DockWidget) {
        dockWidget->setVisible(false);
    }
}

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

void MainWindow::_buildAnalyzeView(void)
{
    if (!_analyzeView) {
        _analyzeView = new QGCDataPlot2D(this);
        _analyzeView->setVisible(false);
    }
}

521
void MainWindow::fullScreenActionItemCallback(bool)
522
{
dogmaphobic's avatar
dogmaphobic committed
523
    _ui.actionNormal->setChecked(false);
524 525
}

526
void MainWindow::normalActionItemCallback(bool)
527
{
dogmaphobic's avatar
dogmaphobic committed
528
    _ui.actionFullscreen->setChecked(false);
529 530
}

531 532 533 534 535 536
void MainWindow::showStatusBarCallback(bool checked)
{
    _showStatusBar = checked;
    checked ? statusBar()->show() : statusBar()->hide();
}

537 538
void MainWindow::closeEvent(QCloseEvent *event)
{
539
    // Disallow window close if there are active connections
540
    if (LinkManager::instance()->anyConnectedLinks()) {
dogmaphobic's avatar
dogmaphobic committed
541 542 543 544 545 546
        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);
547 548 549 550 551 552 553 554 555 556
		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;
557 558
    }

559 560
    // This will process any remaining flight log save dialogs
    qgcApp()->processEvents(QEventLoop::ExcludeUserInputEvents);
561
    
562
    // Should not be any active connections
563 564
    Q_ASSERT(!LinkManager::instance()->anyConnectedLinks());
    
565
    _storeCurrentViewState();
566
    storeSettings();
567
    event->accept();
568 569 570 571
}

void MainWindow::loadSettings()
{
572
    // Why the screaming?
573
    QSettings settings;
574
    settings.beginGroup(MAIN_SETTINGS_GROUP);
575 576 577
    _autoReconnect  = settings.value("AUTO_RECONNECT",      _autoReconnect).toBool();
    _lowPowerMode   = settings.value("LOW_POWER_MODE",      _lowPowerMode).toBool();
    _showStatusBar  = settings.value("SHOW_STATUSBAR",      _showStatusBar).toBool();
578 579 580 581 582 583
    settings.endGroup();
}

void MainWindow::storeSettings()
{
    QSettings settings;
584
    settings.beginGroup(MAIN_SETTINGS_GROUP);
585 586 587
    settings.setValue("AUTO_RECONNECT",     _autoReconnect);
    settings.setValue("LOW_POWER_MODE",     _lowPowerMode);
    settings.setValue("SHOW_STATUSBAR",     _showStatusBar);
588
    settings.endGroup();
dogmaphobic's avatar
dogmaphobic committed
589
    settings.setValue(_getWindowGeometryKey(), saveGeometry());
590
	
591
    // Save the last current view in any case
592
    settings.setValue("CURRENT_VIEW", _currentView);
593
    settings.setValue(_getWindowStateKey(), saveState());
594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617
}

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
618
    _autoReconnect = enabled;
619 620 621 622 623 624 625 626 627
}

/**
* @brief Create all actions associated to the main window
*
**/
void MainWindow::connectCommonActions()
{
    // Bind together the perspective actions
dogmaphobic's avatar
dogmaphobic committed
628
    QActionGroup* perspectives = new QActionGroup(_ui.menuPerspectives);
629 630 631
    perspectives->addAction(_ui.actionAnalyze);
    perspectives->addAction(_ui.actionFlight);
    perspectives->addAction(_ui.actionPlan);
dogmaphobic's avatar
dogmaphobic committed
632
    perspectives->addAction(_ui.actionSetup);
633 634 635
    perspectives->setExclusive(true);

    // Mark the right one as selected
636
    if (_currentView == VIEW_ANALYZE)
637
    {
638 639
        _ui.actionAnalyze->setChecked(true);
        _ui.actionAnalyze->activate(QAction::Trigger);
640
    }
641
    if (_currentView == VIEW_FLIGHT)
642
    {
643 644
        _ui.actionFlight->setChecked(true);
        _ui.actionFlight->activate(QAction::Trigger);
645
    }
646
    if (_currentView == VIEW_MISSIONEDITOR)
647
    {
648 649 650
        _ui.actionPlan->setChecked(true);
        _ui.actionPlan->activate(QAction::Trigger);
    }
651
    if (_currentView == VIEW_SETUP)
652
    {
dogmaphobic's avatar
dogmaphobic committed
653 654
        _ui.actionSetup->setChecked(true);
        _ui.actionSetup->activate(QAction::Trigger);
655
    }
656 657

    // Connect actions from ui
dogmaphobic's avatar
dogmaphobic committed
658
    connect(_ui.actionAdd_Link, SIGNAL(triggered()), this, SLOT(manageLinks()));
659 660

    // Connect internal actions
661
    connect(MultiVehicleManager::instance(), &MultiVehicleManager::vehicleAdded, this, &MainWindow::_vehicleAdded);
662 663

    // Views actions
Don Gagne's avatar
Don Gagne committed
664 665 666
    connect(_ui.actionFlight,           SIGNAL(triggered()), this, SLOT(loadFlightView()));
    connect(_ui.actionAnalyze,          SIGNAL(triggered()), this, SLOT(loadAnalyzeView()));
    connect(_ui.actionPlan,             SIGNAL(triggered()), this, SLOT(loadPlanView()));
667
    
668
    // Help Actions
dogmaphobic's avatar
dogmaphobic committed
669 670 671
    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()));
672 673

    // Audio output
dogmaphobic's avatar
dogmaphobic committed
674 675 676
    _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)));
677 678

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

dogmaphobic's avatar
dogmaphobic committed
681
    // Update Tool Bar
682
    _mainToolBar->setCurrentView(_currentView);
683 684
}

Don Gagne's avatar
Don Gagne committed
685
void MainWindow::_openUrl(const QString& url, const QString& errorMessage)
686
{
Don Gagne's avatar
Don Gagne committed
687
    if(!QDesktopServices::openUrl(QUrl(url))) {
dogmaphobic's avatar
dogmaphobic committed
688 689 690 691
        QMessageBox::critical(
            this,
            tr("Could not open information in browser"),
            errorMessage);
692 693 694
    }
}

Don Gagne's avatar
Don Gagne committed
695 696
void MainWindow::showHelp()
{
dogmaphobic's avatar
dogmaphobic committed
697 698 699
    _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
700 701
}

702 703
void MainWindow::showCredits()
{
dogmaphobic's avatar
dogmaphobic committed
704 705 706
    _openUrl(
        "http://qgroundcontrol.org/credits",
        tr("To get to the credits, please open http://qgroundcontrol.org/credits in a browser."));
707 708 709 710
}

void MainWindow::showRoadMap()
{
dogmaphobic's avatar
dogmaphobic committed
711 712 713
    _openUrl(
        "http://qgroundcontrol.org/dev/roadmap",
        tr("To get to the online help, please open http://qgroundcontrol.org/roadmap in a browser."));
714 715 716 717
}

void MainWindow::showSettings()
{
dogmaphobic's avatar
dogmaphobic committed
718
    SettingsDialog settings(this);
Don Gagne's avatar
Don Gagne committed
719
    settings.exec();
720 721
}

722 723
void MainWindow::commsWidgetDestroyed(QObject *obj)
{
724 725
    // 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
726
    if (_commsWidgetList.contains(obj))
727
    {
dogmaphobic's avatar
dogmaphobic committed
728
        _commsWidgetList.removeOne(obj);
729 730
    }
}
731

732
void MainWindow::_vehicleAdded(Vehicle* vehicle)
733
{
734
    connect(vehicle->uas(), SIGNAL(valueChanged(int,QString,QString,QVariant,quint64)), this, SIGNAL(valueChanged(int,QString,QString,QVariant,quint64)));
735

736 737 738
    if (!linechartWidget)
    {
        linechartWidget = new Linecharts(this);
739
        linechartWidget->setVisible(false);
740
    }
741

742
    linechartWidget->addSource(mavlinkDecoder);
743
    if (_analyzeView != linechartWidget)
744
    {
745
        _analyzeView = linechartWidget;
746
    }
747 748
}

749 750
/// Stores the state of the toolbar, status bar and widgets associated with the current view
void MainWindow::_storeCurrentViewState(void)
751
{
Don Gagne's avatar
Don Gagne committed
752
#ifndef __mobile__
753 754 755 756 757 758 759 760 761 762 763
    // Save list of visible widgets
    bool firstWidget = true;
    QString widgetNames = "";
    foreach(QDockWidget* dockWidget, _mapName2DockWidget) {
        if (dockWidget->isVisible()) {
            if (!firstWidget) {
                widgetNames += ",";
            }
            widgetNames += dockWidget->objectName();
            firstWidget = false;
        }
764
    }
dogmaphobic's avatar
dogmaphobic committed
765
    settings.setValue(_getWindowStateKey() + "WIDGETS", widgetNames);
Don Gagne's avatar
Don Gagne committed
766
#endif
dogmaphobic's avatar
dogmaphobic committed
767 768
    settings.setValue(_getWindowStateKey(), saveState());
    settings.setValue(_getWindowGeometryKey(), saveGeometry());
769 770
}

771 772
/// Restores the state of the toolbar, status bar and widgets associated with the current view
void MainWindow::_loadCurrentViewState(void)
773
{
Don Gagne's avatar
Don Gagne committed
774
    QWidget* centerView = NULL;
775
    QString defaultWidgets;
776

777
    switch (_currentView) {
778
        case VIEW_SETUP:
779 780
            _buildSetupView();
            centerView = _setupView;
781
            break;
782

783 784 785
        case VIEW_ANALYZE:
            _buildAnalyzeView();
            centerView = _analyzeView;
786
            defaultWidgets = "PARAMETER_INTERFACE_DOCKWIDGET,FILE_VIEW_DOCKWIDGET";
787
            break;
788

789
        case VIEW_FLIGHT:
790 791
            _buildFlightView();
            centerView = _flightView;
792
            defaultWidgets = "COMMUNICATION_CONSOLE_DOCKWIDGET,UAS_INFO_INFOVIEW_DOCKWIDGET";
793
            break;
794

Don Gagne's avatar
Don Gagne committed
795 796 797 798 799
        case VIEW_MISSIONEDITOR:
            _buildMissionEditorView();
            centerView = _missionEditorView;
            break;

800 801
        default:
            Q_ASSERT(false);
802
            break;
803
    }
804

805 806 807 808 809 810 811 812
    // Remove old view
    if (_currentViewWidget) {
        _currentViewWidget->setVisible(false);
        Q_ASSERT(_centralLayout->count() == 1);
        QLayoutItem *child = _centralLayout->takeAt(0);
        Q_ASSERT(child);
        delete child;
    }
813

814 815 816 817 818
    // Add the new one
    Q_ASSERT(centerView);
    Q_ASSERT(_centralLayout->count() == 0);
    _currentViewWidget = centerView;
    _centralLayout->addWidget(_currentViewWidget);
819
    _centralLayout->setContentsMargins(0, 0, 0, 0);
820
    _currentViewWidget->setVisible(true);
821

822
#ifndef __mobile__
823 824 825 826
    // Hide all widgets from previous view
    _hideAllDockWidgets();

    // Restore the widgets for the new view
dogmaphobic's avatar
dogmaphobic committed
827
    QString widgetNames = settings.value(_getWindowStateKey() + "WIDGETS", defaultWidgets).toString();
828
    qDebug() << widgetNames;
829 830 831 832 833
    if (!widgetNames.isEmpty()) {
        QStringList split = widgetNames.split(",");
        foreach (QString widgetName, split) {
            Q_ASSERT(!widgetName.isEmpty());
            _showDockWidget(widgetName, true);
834 835
        }
    }
Don Gagne's avatar
Don Gagne committed
836
#endif
837

dogmaphobic's avatar
dogmaphobic committed
838 839
    if (settings.contains(_getWindowStateKey())) {
        restoreState(settings.value(_getWindowStateKey()).toByteArray());
840
    }
841

842 843 844
    // 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();
845 846
}

847
void MainWindow::loadAnalyzeView()
848
{
849
    if (_currentView != VIEW_ANALYZE)
850
    {
851
        _storeCurrentViewState();
852 853
        _currentView = VIEW_ANALYZE;
        _ui.actionAnalyze->setChecked(true);
854
        _loadCurrentViewState();
855 856 857
    }
}

858
void MainWindow::loadPlanView()
859
{
860 861 862 863 864 865
    if (_currentView != VIEW_MISSIONEDITOR)
    {
        _storeCurrentViewState();
        _currentView = VIEW_MISSIONEDITOR;
        _ui.actionPlan->setChecked(true);
        _loadCurrentViewState();
Don Gagne's avatar
Don Gagne committed
866 867 868
    }
}

869
void MainWindow::loadSetupView()
870
{
871
    if (_currentView != VIEW_SETUP)
872
    {
873 874
        _storeCurrentViewState();
        _currentView = VIEW_SETUP;
dogmaphobic's avatar
dogmaphobic committed
875
        _ui.actionSetup->setChecked(true);
876
        _loadCurrentViewState();
877 878 879
    }
}

880
void MainWindow::loadFlightView()
881
{
882
    if (_currentView != VIEW_FLIGHT)
883
    {
884 885
        _storeCurrentViewState();
        _currentView = VIEW_FLIGHT;
886
        _ui.actionFlight->setChecked(true);
887
        _loadCurrentViewState();
888 889 890
    }
}

Don Gagne's avatar
Don Gagne committed
891
/// @brief Hides the spash screen if it is currently being shown
892
void MainWindow::hideSplashScreen(void)
Don Gagne's avatar
Don Gagne committed
893 894 895 896 897 898 899
{
    if (_splashScreen) {
        _splashScreen->hide();
        _splashScreen = NULL;
    }
}

900 901
void MainWindow::manageLinks()
{
dogmaphobic's avatar
dogmaphobic committed
902
    SettingsDialog settings(this, SettingsDialog::ShowCommLinks);
903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925
    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)) {
926
        QString connection = settings.value(key).toString();
927
        // Create a link for it
928
        LinkManager::instance()->createConnectedLink(connection);
929 930
    }
}
Don Gagne's avatar
Don Gagne committed
931

932 933 934 935 936
void MainWindow::_linkStateChange(LinkInterface*)
{
    emit repaintCanvas();
}

937
#ifdef QGC_MOUSE_ENABLED_LINUX
938 939 940
bool MainWindow::x11Event(XEvent *event)
{
    emit x11EventOccured(event);
941
    return false;
942
}
943
#endif // QGC_MOUSE_ENABLED_LINUX
944 945 946 947 948 949 950

#ifdef UNITTEST_BUILD
void MainWindow::_showQmlTestWidget(void)
{
    new QmlTestWidget();
}
#endif