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
#include "LogCompressor.h"
60
#include "UAS.h"
61 62

#ifndef __mobile__
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
const char* MainWindow::_mavlinkDockWidgetName = "MAVLINK_INSPECTOR_DOCKWIDGET";
Don Gagne's avatar
Don Gagne committed
92
const char* MainWindow::_customCommandWidgetName = "CUSTOM_COMMAND_DOCKWIDGET";
93 94 95 96 97
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";
98
const char* MainWindow::_hilDockWidgetName = "HIL_DOCKWIDGET";
99
#endif
100

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

343
#ifndef __mobile__
344 345 346
void MainWindow::_createDockWidget(const QString& title, const QString& name, Qt::DockWidgetArea area, QWidget* innerWidget)
{
    Q_ASSERT(!_mapName2DockWidget.contains(name));
347
	
348 349 350 351 352
    // Add to menu
    QAction* action = new QAction(title, NULL);
    action->setCheckable(true);
    action->setData(name);
    connect(action, &QAction::triggered, this, &MainWindow::_showDockWidgetAction);
353
    _ui.menuWidgets->addAction(action);
354 355 356 357 358 359 360 361 362 363 364 365 366
	
	// 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);
	}
	
367 368 369 370 371 372
    _mapName2DockWidget[name] = dockWidget;
    _mapDockWidget2Action[dockWidget] = action;
    addDockWidget(area, dockWidget);
}

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

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

385 386 387
    // 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.
388

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

395 396
    static const struct DockWidgetInfo rgDockWidgetInfo[] = {
        { _mavlinkDockWidgetName,           "MAVLink Inspector",        Qt::RightDockWidgetArea },
Don Gagne's avatar
Don Gagne committed
397
        { _customCommandWidgetName,         "Custom Command",			Qt::RightDockWidgetArea },
398 399 400 401 402
        { _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 },
403
        { _hilDockWidgetName,               "HIL Config",               Qt::LeftDockWidgetArea },
404 405
    };
    static const size_t cDockWidgetInfo = sizeof(rgDockWidgetInfo) / sizeof(rgDockWidgetInfo[0]);
406

407 408 409
    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 */);
410
    }
411
}
412

413 414
/// Shows or hides the specified dock widget, creating if necessary
void MainWindow::_showDockWidget(const QString& name, bool show)
415
{
416
    if (!_mapName2DockWidget.contains(name)) {
Don Gagne's avatar
Don Gagne committed
417 418
        // 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.
419 420
        return;
    }
421
    
422 423 424
    // Create the inner widget if we need to
    if (!_mapName2DockWidget[name]->widget()) {
        _createInnerDockWidget(name);
425
    }
426
    
427 428 429
    Q_ASSERT(_mapName2DockWidget.contains(name));
    QDockWidget* dockWidget = _mapName2DockWidget[name];
    Q_ASSERT(dockWidget);
430
    
431
    dockWidget->setVisible(show);
432
    
433 434 435 436 437 438 439 440 441
    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
442
    
443
    QWidget* widget = NULL;
444
    
445
    if (widgetName == _mavlinkDockWidgetName) {
446
        widget = new QGCMAVLinkInspector(MAVLinkProtocol::instance(),this);
Don Gagne's avatar
Don Gagne committed
447 448
    } else if (widgetName == _customCommandWidgetName) {
        widget = new CustomCommandWidget(this);
449 450 451 452 453
    } else if (widgetName == _filesDockWidgetName) {
        widget = new QGCUASFileViewMulti(this);
    } else if (widgetName == _uasStatusDetailsDockWidgetName) {
        widget = new UASInfoWidget(this);
    } else if (widgetName == _pfdDockWidgetName) {
454
        widget = new FlightDisplayWidget(this);
Don Gagne's avatar
Don Gagne committed
455
#ifndef __mobile__
456 457
    } else if (widgetName == _hilDockWidgetName) {
        widget = new HILDockWidget(this);
Don Gagne's avatar
Don Gagne committed
458
#endif
459
    } else if (widgetName == _uasInfoViewDockWidgetName) {
460 461 462
        QGCTabbedInfoView* pInfoView = new QGCTabbedInfoView(this);
        pInfoView->addSource(mavlinkDecoder);
        widget = pInfoView;
463 464
    } else {
        qWarning() << "Attempt to create unknown Inner Dock Widget" << widgetName;
465
    }
466
    
467 468 469 470 471
    if (widget) {
        QDockWidget* dockWidget = _mapName2DockWidget[widgetName];
        Q_CHECK_PTR(dockWidget);
        widget->setParent(dockWidget);
        dockWidget->setWidget(widget);
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 521
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);
    }
}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

750 751
/// Stores the state of the toolbar, status bar and widgets associated with the current view
void MainWindow::_storeCurrentViewState(void)
752
{
Don Gagne's avatar
Don Gagne committed
753
#ifndef __mobile__
754 755 756 757 758 759 760 761 762 763 764
    // 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;
        }
765
    }
dogmaphobic's avatar
dogmaphobic committed
766
    settings.setValue(_getWindowStateKey() + "WIDGETS", widgetNames);
Don Gagne's avatar
Don Gagne committed
767
#endif
dogmaphobic's avatar
dogmaphobic committed
768 769
    settings.setValue(_getWindowStateKey(), saveState());
    settings.setValue(_getWindowGeometryKey(), saveGeometry());
770 771
}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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