MainWindow.cc 48.1 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 36 37 38

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>
#include <QGCHilLink.h>
#include <QGCHilConfiguration.h>
#include <QGCHilFlightGearConfiguration.h>
39
#include <QQuickView>
40 41
#include <QDesktopWidget>

42 43 44 45 46 47 48 49 50 51
#include "QGC.h"
#include "MAVLinkSimulationLink.h"
#include "SerialLink.h"
#include "MAVLinkProtocol.h"
#include "QGCWaypointListMulti.h"
#include "MainWindow.h"
#include "JoystickWidget.h"
#include "GAudioOutput.h"
#include "QGCToolWidget.h"
#include "QGCMAVLinkLogPlayer.h"
Don Gagne's avatar
Don Gagne committed
52
#include "SettingsDialog.h"
53 54 55 56
#include "QGCMapTool.h"
#include "MAVLinkDecoder.h"
#include "QGCMAVLinkMessageSender.h"
#include "QGCRGBDView.h"
57
#include "UASQuickView.h"
58 59
#include "QGCDataPlot2D.h"
#include "Linecharts.h"
60 61
#include "QGCTabbedInfoView.h"
#include "UASRawStatusView.h"
62
#include "PrimaryFlightDisplay.h"
63
#include "SetupView.h"
64 65
#include "SerialSettingsDialog.h"
#include "terminalconsole.h"
66
#include "QGCUASFileViewMulti.h"
Don Gagne's avatar
Don Gagne committed
67
#include "QGCApplication.h"
68
#include "QGCFileDialog.h"
Don Gagne's avatar
Don Gagne committed
69
#include "QGCMessageBox.h"
70
#include "QGCDockWidget.h"
71

72 73 74 75
#ifdef UNITTEST_BUILD
#include "QmlControls/QmlTestWidget.h"
#endif

76 77 78 79 80 81
#ifdef QGC_OSG_ENABLED
#include "Q3DWidgetFactory.h"
#endif

#include "LogCompressor.h"

82 83 84
/// The key under which the Main Window settings are saved
const char* MAIN_SETTINGS_GROUP = "QGC_MAINWINDOW";

85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100
const char* MainWindow::_uasControlDockWidgetName = "UNMANNED_SYSTEM_CONTROL_DOCKWIDGET";
const char* MainWindow::_uasListDockWidgetName = "UNMANNED_SYSTEM_LIST_DOCKWIDGET";
const char* MainWindow::_waypointsDockWidgetName = "WAYPOINT_LIST_DOCKWIDGET";
const char* MainWindow::_mavlinkDockWidgetName = "MAVLINK_INSPECTOR_DOCKWIDGET";
const char* MainWindow::_parametersDockWidgetName = "PARAMETER_INTERFACE_DOCKWIDGET";
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::_hsiDockWidgetName = "HORIZONTAL_SITUATION_INDICATOR_DOCKWIDGET";
const char* MainWindow::_hdd1DockWidgetName = "HEAD_DOWN_DISPLAY_1_DOCKWIDGET";
const char* MainWindow::_hdd2DockWidgetName = "HEAD_DOWN_DISPLAY_2_DOCKWIDGET";
const char* MainWindow::_pfdDockWidgetName = "PRIMARY_FLIGHT_DISPLAY_DOCKWIDGET";
const char* MainWindow::_hudDockWidgetName = "HEAD_UP_DISPLAY_DOCKWIDGET";
const char* MainWindow::_uasInfoViewDockWidgetName = "UAS_INFO_INFOVIEW_DOCKWIDGET";
const char* MainWindow::_debugConsoleDockWidgetName = "COMMUNICATION_CONSOLE_DOCKWIDGET";

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 128 129 130 131 132 133
MainWindow::MainWindow(QSplashScreen* splashScreen)
    : _autoReconnect(false)
    , _lowPowerMode(false)
    , _centerStackActionGroup(new QActionGroup(this))
    , _simulationLink(NULL)
    , _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

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

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

158 159 160 161 162
    // Qt 4 on Ubuntu does place the native menubar correctly so on Linux we revert back to in-window menu bar.
    // TODO: Check that this is still necessary on Qt5 on Ubuntu
#ifdef Q_OS_LINUX
    menuBar()->setNativeMenuBar(false);
#endif
163 164 165 166
    
#ifdef UNITTEST_BUILD
    QAction* qmlTestAction = new QAction("Test QML palette and controls", NULL);
    connect(qmlTestAction, &QAction::triggered, this, &MainWindow::_showQmlTestWidget);
dogmaphobic's avatar
dogmaphobic committed
167
    _ui.menuTools->addAction(qmlTestAction);
168
#endif
169

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

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

188
    emit initStatusChanged(tr("Building common widgets."), Qt::AlignLeft | Qt::AlignBottom, QColor(62, 93, 141));
189
    _buildCommonWidgets();
190
    emit initStatusChanged(tr("Building common actions"), Qt::AlignLeft | Qt::AlignBottom, QColor(62, 93, 141));
191 192 193
    // Create actions
    connectCommonActions();
    // Connect user interface devices
194
    emit initStatusChanged(tr("Initializing joystick interface"), Qt::AlignLeft | Qt::AlignBottom, QColor(62, 93, 141));
195 196
    joystick = new JoystickInput();

197
#ifdef QGC_MOUSE_ENABLED_WIN
198
    emit initStatusChanged(tr("Initializing 3D mouse interface"), Qt::AlignLeft | Qt::AlignBottom, QColor(62, 93, 141));
199 200
    mouseInput = new Mouse3DInput(this);
    mouse = new Mouse6dofInput(mouseInput);
201
#endif //QGC_MOUSE_ENABLED_WIN
202

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

    mouse = new Mouse6dofInput(this);
207
    connect(this, SIGNAL(x11EventOccured(XEvent*)), mouse, SLOT(handleX11Event(XEvent*)));
208
#endif //QGC_MOUSE_ENABLED_LINUX
209

210
    // Connect link
dogmaphobic's avatar
dogmaphobic committed
211
    if (_autoReconnect)
212
    {
213
        restoreLastUsedConnection();
214 215 216
    }

    // Set low power mode
dogmaphobic's avatar
dogmaphobic committed
217
    enableLowPowerMode(_lowPowerMode);
218
    emit initStatusChanged(tr("Restoring last view state"), Qt::AlignLeft | Qt::AlignBottom, QColor(62, 93, 141));
219
    // Restore the window setup
220
    _loadCurrentViewState();
221
    // Restore the window position and size
dogmaphobic's avatar
dogmaphobic committed
222 223
    emit initStatusChanged(tr("Restoring last window size"), Qt::AlignLeft | Qt::AlignBottom, QColor(62, 93, 141));
    if (settings.contains(_getWindowGeometryKey()))
224
    {
dogmaphobic's avatar
dogmaphobic committed
225
        restoreGeometry(settings.value(_getWindowGeometryKey()).toByteArray());
226 227 228 229
    }
    else
    {
        // Adjust the size
dogmaphobic's avatar
dogmaphobic committed
230
        const int screenWidth  = QApplication::desktop()->width();
231
        const int screenHeight = QApplication::desktop()->height();
Lorenz Meier's avatar
Lorenz Meier committed
232
        if (screenWidth < 1500)
233
        {
234
            resize(screenWidth, screenHeight - 80);
235 236 237 238 239 240 241
        }
        else
        {
            resize(screenWidth*0.67f, qMin(screenHeight, (int)(screenWidth*0.67f*0.67f)));
        }
    }

242 243 244
    // Make sure the proper fullscreen/normal menu item is checked properly.
    if (isFullScreen())
    {
dogmaphobic's avatar
dogmaphobic committed
245 246
        _ui.actionFullscreen->setChecked(true);
        _ui.actionNormal->setChecked(false);
247 248 249
    }
    else
    {
dogmaphobic's avatar
dogmaphobic committed
250 251
        _ui.actionFullscreen->setChecked(false);
        _ui.actionNormal->setChecked(true);
252 253 254
    }

    // And that they will stay checked properly after user input
dogmaphobic's avatar
dogmaphobic committed
255 256
    QObject::connect(_ui.actionFullscreen, SIGNAL(triggered()), this, SLOT(fullScreenActionItemCallback()));
    QObject::connect(_ui.actionNormal,     SIGNAL(triggered()), this, SLOT(normalActionItemCallback()));
257

258 259
    // 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
260 261 262 263 264 265 266 267 268
    _ui.actionSetup->setShortcut(QApplication::translate("MainWindow", "Meta+1", 0));
    _ui.actionMissionView->setShortcut(QApplication::translate("MainWindow", "Meta+2", 0));
    _ui.actionFlightView->setShortcut(QApplication::translate("MainWindow", "Meta+3", 0));
    _ui.actionEngineersView->setShortcut(QApplication::translate("MainWindow", "Meta+4", 0));
    _ui.actionGoogleEarthView->setShortcut(QApplication::translate("MainWindow", "Meta+5", 0));
    _ui.actionLocal3DView->setShortcut(QApplication::translate("MainWindow", "Meta+6", 0));
    _ui.actionTerminalView->setShortcut(QApplication::translate("MainWindow", "Meta+7", 0));
    _ui.actionSimulationView->setShortcut(QApplication::translate("MainWindow", "Meta+8", 0));
    _ui.actionFullscreen->setShortcut(QApplication::translate("MainWindow", "Meta+Return", 0));
269
#else
dogmaphobic's avatar
dogmaphobic committed
270 271 272 273 274 275 276 277 278
    _ui.actionSetup->setShortcut(QApplication::translate("MainWindow", "Ctrl+1", 0));
    _ui.actionMissionView->setShortcut(QApplication::translate("MainWindow", "Ctrl+2", 0));
    _ui.actionFlightView->setShortcut(QApplication::translate("MainWindow", "Ctrl+3", 0));
    _ui.actionEngineersView->setShortcut(QApplication::translate("MainWindow", "Ctrl+4", 0));
    _ui.actionGoogleEarthView->setShortcut(QApplication::translate("MainWindow", "Ctrl+5", 0));
    _ui.actionLocal3DView->setShortcut(QApplication::translate("MainWindow", "Ctrl+6", 0));
    _ui.actionTerminalView->setShortcut(QApplication::translate("MainWindow", "Ctrl+7", 0));
    _ui.actionSimulationView->setShortcut(QApplication::translate("MainWindow", "Ctrl+8", 0));
    _ui.actionFullscreen->setShortcut(QApplication::translate("MainWindow", "Ctrl+Return", 0));
279 280
#endif

281 282
    connect(&windowNameUpdateTimer, SIGNAL(timeout()), this, SLOT(configureWindowName()));
    windowNameUpdateTimer.start(15000);
283
    emit initStatusChanged(tr("Done"), Qt::AlignLeft | Qt::AlignBottom, QColor(62, 93, 141));
284 285 286

    if (!qgcApp()->runningUnitTests()) {
        show();
dogmaphobic's avatar
dogmaphobic committed
287 288 289 290 291 292 293 294 295 296 297 298 299 300 301
#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
302
    }
303 304 305 306
}

MainWindow::~MainWindow()
{
dogmaphobic's avatar
dogmaphobic committed
307
    if (_simulationLink)
308
    {
dogmaphobic's avatar
dogmaphobic committed
309 310
        delete _simulationLink;
        _simulationLink = NULL;
311
    }
312 313
    if (joystick)
    {
314 315
        joystick->shutdown();
        joystick->wait(5000);
316 317 318 319
        delete joystick;
        joystick = NULL;
    }
    // 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 335
    if (UASManager::instance()->getActiveUAS())
    {
336
        return QString::number(_currentView)+"_windowstate_" + UASManager::instance()->getActiveUAS()->getAutopilotTypeName();
337 338
    }
    else
339
        return QString::number(_currentView)+"_windowstate_";
340 341
}

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

347
void MainWindow::_buildCustomWidgets(void)
348
{
349
    Q_ASSERT(_customWidgets.count() == 0);
350
    // Create custom widgets
351 352
    _customWidgets = QGCToolWidget::createWidgetsFromSettings(this);
    if (_customWidgets.size() > 0)
353
    {
dogmaphobic's avatar
dogmaphobic committed
354
        _ui.menuTools->addSeparator();
355
    }
356
    foreach(QGCToolWidget* tool, _customWidgets) {
357 358
        // Check if this widget already has a parent, do not create it in this case
        QDockWidget* dock = dynamic_cast<QDockWidget*>(tool->parentWidget());
359 360
        if (!dock) {
            _createDockWidget(tool->getTitle(), tool->objectName(), Qt::BottomDockWidgetArea, tool);
361 362 363 364
        }
    }
}

365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382
void MainWindow::_createDockWidget(const QString& title, const QString& name, Qt::DockWidgetArea area, QWidget* innerWidget)
{
    Q_ASSERT(!_mapName2DockWidget.contains(name));
    QGCDockWidget* dockWidget = new QGCDockWidget(title, 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);
    }
    // Add to menu
    QAction* action = new QAction(title, NULL);
    action->setCheckable(true);
    action->setData(name);
    connect(action, &QAction::triggered, this, &MainWindow::_showDockWidgetAction);
dogmaphobic's avatar
dogmaphobic committed
383
    _ui.menuTools->addAction(action);
384 385 386 387 388 389
    _mapName2DockWidget[name] = dockWidget;
    _mapDockWidget2Action[dockWidget] = action;
    addDockWidget(area, dockWidget);
}

void MainWindow::_buildCommonWidgets(void)
390 391
{
    // Add generic MAVLink decoder
dogmaphobic's avatar
dogmaphobic committed
392
    // TODO: This is never deleted
393
    mavlinkDecoder = new MAVLinkDecoder(MAVLinkProtocol::instance(), this);
John Tapsell's avatar
John Tapsell committed
394 395
    connect(mavlinkDecoder, SIGNAL(valueChanged(int,QString,QString,QVariant,quint64)),
                      this, SIGNAL(valueChanged(int,QString,QString,QVariant,quint64)));
396

397
    // Log player
dogmaphobic's avatar
dogmaphobic committed
398
    // TODO: Make this optional with a preferences setting or under a "View" menu
399
    logPlayer = new QGCMAVLinkLogPlayer(MAVLinkProtocol::instance(), statusBar());
400
    statusBar()->addPermanentWidget(logPlayer);
401

402 403 404
    // 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.
405

406 407 408 409 410
    struct DockWidgetInfo {
        const char* name;
        const char* title;
        Qt::DockWidgetArea area;
    };
411

412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429
    static const struct DockWidgetInfo rgDockWidgetInfo[] = {
        { _uasControlDockWidgetName,        "Control",                  Qt::LeftDockWidgetArea },
        { _uasListDockWidgetName,           "Unmanned Systems",         Qt::RightDockWidgetArea },
        { _waypointsDockWidgetName,         "Mission Plan",             Qt::BottomDockWidgetArea },
        { _mavlinkDockWidgetName,           "MAVLink Inspector",        Qt::RightDockWidgetArea },
        { _parametersDockWidgetName,        "Onboard Parameters",       Qt::RightDockWidgetArea },
        { _filesDockWidgetName,             "Onboard Files",            Qt::RightDockWidgetArea },
        { _uasStatusDetailsDockWidgetName,  "Status Details",           Qt::RightDockWidgetArea },
        { _mapViewDockWidgetName,           "Map view",                 Qt::RightDockWidgetArea },
        { _hsiDockWidgetName,               "Horizontal Situation",     Qt::BottomDockWidgetArea },
        { _hdd1DockWidgetName,              "Flight Display",           Qt::RightDockWidgetArea },
        { _hdd2DockWidgetName,              "Actuator Status",          Qt::RightDockWidgetArea },
        { _pfdDockWidgetName,               "Primary Flight Display",   Qt::RightDockWidgetArea },
        { _hudDockWidgetName,               "Video Downlink",           Qt::RightDockWidgetArea },
        { _uasInfoViewDockWidgetName,       "Info View",                Qt::LeftDockWidgetArea },
        { _debugConsoleDockWidgetName,      "Communications Console",   Qt::LeftDockWidgetArea }
    };
    static const size_t cDockWidgetInfo = sizeof(rgDockWidgetInfo) / sizeof(rgDockWidgetInfo[0]);
430

431 432 433
    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 */);
434 435
    }

436 437
    _buildCustomWidgets();
}
438

439 440 441 442 443
void MainWindow::_buildPlannerView(void)
{
    if (!_plannerView) {
        _plannerView = new QGCMapTool(this);
        _plannerView->setVisible(false);
444
    }
445
}
446

447 448 449 450 451 452
void MainWindow::_buildPilotView(void)
{
    if (!_pilotView) {
        _pilotView = new PrimaryFlightDisplay(this);
        _pilotView->setVisible(false);
    }
453 454
}

455
void MainWindow::_buildSetupView(void)
456
{
457 458 459 460
    if (!_setupView) {
        _setupView = new SetupView(this);
        _setupView->setVisible(false);
    }
461 462
}

463
void MainWindow::_buildEngineeringView(void)
464
{
465 466 467 468 469
    if (!_engineeringView) {
        _engineeringView = new QGCDataPlot2D(this);
        _engineeringView->setVisible(false);
    }
}
470

471 472 473 474 475 476
void MainWindow::_buildSimView(void)
{
    if (!_simView) {
        _simView = new QGCMapTool(this);
        _simView->setVisible(false);
    }
477
}
478

479
void MainWindow::_buildTerminalView(void)
480
{
481 482 483 484
    if (!_terminalView) {
        _terminalView = new TerminalConsole(this);
        _terminalView->setVisible(false);
    }
485 486
}

487
void MainWindow::_buildGoogleEarthView(void)
488
{
489 490 491 492 493 494
#ifdef QGC_GOOGLE_EARTH_ENABLED
    if (!_googleEarthView) {
        _googleEarthView = new QGCGoogleEarthView(this);
        _googleEarthView->setVisible(false);
    }
#endif
495 496
}

497
void MainWindow::_buildLocal3DView(void)
498
{
499 500 501 502 503 504
#ifdef QGC_OSG_ENABLED
    if (!_local3DView) {
        _local3DView = Q3DWidgetFactory::get("PIXHAWK", this);
        _local3DView->setVisible(false);
    }
#endif
505 506
}

507 508
/// Shows or hides the specified dock widget, creating if necessary
void MainWindow::_showDockWidget(const QString& name, bool show)
509
{
510 511
    if (!_mapName2DockWidget.contains(name)) {
        qWarning() << "Attempt to show unknown dock widget" << name;
512 513
        return;
    }
514

515 516 517
    // Create the inner widget if we need to
    if (!_mapName2DockWidget[name]->widget()) {
        _createInnerDockWidget(name);
518
    }
519 520 521 522

    Q_ASSERT(_mapName2DockWidget.contains(name));
    QDockWidget* dockWidget = _mapName2DockWidget[name];
    Q_ASSERT(dockWidget);
523

524
    dockWidget->setVisible(show);
525

526 527 528 529 530 531 532 533 534
    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
535

536
    QWidget* widget = NULL;
537

538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556
    if (widgetName == _uasControlDockWidgetName) {
        widget = new UASControlWidget(this);
    } else if (widgetName == _uasListDockWidgetName) {
        widget = new UASListWidget(this);
    } else if (widgetName == _waypointsDockWidgetName) {
        widget = new QGCWaypointListMulti(this);
    } else if (widgetName == _mavlinkDockWidgetName) {
        widget = new QGCMAVLinkInspector(MAVLinkProtocol::instance(),this);
    } else if (widgetName == _parametersDockWidgetName) {
        widget = new ParameterInterface(this);
    } else if (widgetName == _filesDockWidgetName) {
        widget = new QGCUASFileViewMulti(this);
    } else if (widgetName == _uasStatusDetailsDockWidgetName) {
        widget = new UASInfoWidget(this);
    } else if (widgetName == _mapViewDockWidgetName) {
        widget = new QGCMapTool(this);
    } else if (widgetName == _hsiDockWidgetName) {
        widget = new HSIDisplay(this);
    } else if (widgetName == _hdd1DockWidgetName) {
557 558 559 560
        QStringList acceptList;
        acceptList.append("-3.3,ATTITUDE.roll,rad,+3.3,s");
        acceptList.append("-3.3,ATTITUDE.pitch,deg,+3.3,s");
        acceptList.append("-3.3,ATTITUDE.yaw,deg,+3.3,s");
561 562
        HDDisplay *hddisplay = new HDDisplay(acceptList,"Flight Display",this);
        hddisplay->addSource(mavlinkDecoder);
563

564 565
        widget = hddisplay;
    } else if (widgetName == _hdd2DockWidgetName) {
566 567 568
        QStringList acceptList;
        acceptList.append("0,RAW_PRESSURE.pres_abs,hPa,65500");
        HDDisplay *hddisplay = new HDDisplay(acceptList,"Actuator Status",this);
569
        hddisplay->addSource(mavlinkDecoder);
570

571 572 573 574 575 576 577 578 579 580 581
        widget = hddisplay;
    } else if (widgetName == _pfdDockWidgetName) {
        widget = new PrimaryFlightDisplay(this);
    } else if (widgetName == _hudDockWidgetName) {
        widget = new HUD(320,240,this);
    } else if (widgetName == _uasInfoViewDockWidgetName) {
        widget = new QGCTabbedInfoView(this);
    } else if (widgetName == _debugConsoleDockWidgetName) {
        widget = new DebugConsole(this);
    } else {
        qWarning() << "Attempt to create unknown Inner Dock Widget" << widgetName;
582
    }
583

584 585 586 587 588
    if (widget) {
        QDockWidget* dockWidget = _mapName2DockWidget[widgetName];
        Q_CHECK_PTR(dockWidget);
        widget->setParent(dockWidget);
        dockWidget->setWidget(widget);
589 590
    }
}
591

592
void MainWindow::_showHILConfigurationWidgets(void)
593
{
594
    UASInterface* uas = UASManager::instance()->getActiveUAS();
595

596 597 598
    if (!uas) {
        return;
    }
599

600 601
    UAS* mav = dynamic_cast<UAS*>(uas);
    Q_ASSERT(mav);
602

603
    int uasId = mav->getUASID();
604

605
    if (!_mapUasId2HilDockWidget.contains(uasId)) {
606

607 608 609 610 611
        // Create QDockWidget
        QGCDockWidget* dockWidget = new QGCDockWidget(tr("HIL Config %1").arg(uasId), this);
        Q_CHECK_PTR(dockWidget);
        dockWidget->setObjectName(tr("HIL_CONFIG_%1").arg(uasId));
        dockWidget->setVisible (false);
612

613 614
        // Create inner widget and set it
        QWidget* widget = new QGCHilConfiguration(mav, dockWidget);
615

616 617
        widget->setParent(dockWidget);
        dockWidget->setWidget(widget);
618

619
        _mapUasId2HilDockWidget[uasId] = dockWidget;
620

621 622
        addDockWidget(Qt::LeftDockWidgetArea, dockWidget);
    }
623

624 625 626 627 628
    if (_currentView == VIEW_SIMULATION) {
        // HIL dock widgets only show up on simulation view
        foreach (QDockWidget* dockWidget, _mapUasId2HilDockWidget) {
            dockWidget->setVisible(true);
        }
629 630 631
    }
}

632
void MainWindow::fullScreenActionItemCallback()
633
{
dogmaphobic's avatar
dogmaphobic committed
634
    _ui.actionNormal->setChecked(false);
635 636
}

637
void MainWindow::normalActionItemCallback()
638
{
dogmaphobic's avatar
dogmaphobic committed
639
    _ui.actionFullscreen->setChecked(false);
640 641 642 643
}

void MainWindow::closeEvent(QCloseEvent *event)
{
644 645 646 647 648 649 650 651
    // Disallow window close if there are active connections
    bool foundConnections = false;
    foreach(LinkInterface* link, LinkManager::instance()->getLinks()) {
        if (link->isConnected()) {
            foundConnections = true;
            break;
        }
    }
652

653
    if (foundConnections) {
dogmaphobic's avatar
dogmaphobic committed
654 655 656 657 658 659
        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);
660 661 662 663 664 665 666 667
        if (button == QMessageBox::Yes) {
            foreach(LinkInterface* link, LinkManager::instance()->getLinks()) {
                LinkManager::instance()->disconnectLink(link);
            }
        } else {
            event->ignore();
            return;
        }
668 669
    }

670 671
    // This will process any remaining flight log save dialogs
    qgcApp()->processEvents(QEventLoop::ExcludeUserInputEvents);
672 673
    // Should not be any active connections
    foreach(LinkInterface* link, LinkManager::instance()->getLinks()) {
Don Gagne's avatar
Don Gagne committed
674
        Q_UNUSED(link);
675 676
        Q_ASSERT(!link->isConnected());
    }
677
    _storeCurrentViewState();
678
    storeSettings();
679
    UASManager::instance()->storeSettings();
680
    event->accept();
681 682
}

683
void MainWindow::_createNewCustomWidget(void)
684
{
685
    if (QGCToolWidget::instances()->isEmpty())
686 687
    {
        // This is the first widget
dogmaphobic's avatar
dogmaphobic committed
688
        _ui.menuTools->addSeparator();
689
    }
690 691 692 693 694 695 696 697
    QString objectName;
    int customToolIndex = 0;
    //Find the next unique object name that we can use
    do {
        ++customToolIndex;
        objectName = QString("CUSTOM_TOOL_%1").arg(customToolIndex) + "DOCK";
    } while(QGCToolWidget::instances()->contains(objectName));
    QString title = tr("Custom Tool %1").arg(customToolIndex );
698
    QGCToolWidget* tool = new QGCToolWidget(objectName, title);
699 700 701
    tool->resize(100, 100);
    _createDockWidget(title, objectName, Qt::BottomDockWidgetArea, tool);
    _mapName2DockWidget[objectName]->setVisible(true);
702 703
}

704
void MainWindow::_loadCustomWidgetFromFile(void)
705
{
706 707 708
    QString fileName = QGCFileDialog::getOpenFileName(
        this, tr("Load Widget File"),
        QStandardPaths::writableLocation(QStandardPaths::DesktopLocation),
709
        tr("QGroundControl Widgets (*.qgw);;All Files (*)"));
710
    if (!fileName.isEmpty()) {
711 712 713
        QGCToolWidget* tool = new QGCToolWidget("", "", this);
        if (tool->loadSettings(fileName, true)) {
            QString objectName = tool->objectName() + "DOCK";
714

715 716
            _createDockWidget(tool->getTitle(), objectName, Qt::LeftDockWidgetArea, tool);
            _mapName2DockWidget[objectName]->widget()->setVisible(true);
717 718
        }
    }
719
    // TODO Add error dialog if widget could not be loaded
720 721 722 723
}

void MainWindow::loadSettings()
{
724
    // Why the screaming?
725
    QSettings settings;
726
    settings.beginGroup(MAIN_SETTINGS_GROUP);
dogmaphobic's avatar
dogmaphobic committed
727 728
    _autoReconnect = settings.value("AUTO_RECONNECT", _autoReconnect).toBool();
    _lowPowerMode  = settings.value("LOW_POWER_MODE", _lowPowerMode).toBool();
729
    settings.endGroup();
dogmaphobic's avatar
dogmaphobic committed
730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752
    // Select the proper view. Default to the flight view or load the last one used if it's supported.
    VIEW_SECTIONS currentViewCandidate = (VIEW_SECTIONS) settings.value("CURRENT_VIEW", _currentView).toInt();
    switch (currentViewCandidate) {
        case VIEW_ENGINEER:
        case VIEW_MISSION:
        case VIEW_FLIGHT:
        case VIEW_SIMULATION:
        case VIEW_SETUP:
        case VIEW_TERMINAL:
#ifdef QGC_OSG_ENABLED
        case VIEW_LOCAL3D:
#endif
#ifdef QGC_GOOGLE_EARTH_ENABLED
        case VIEW_GOOGLEEARTH:
#endif
            _currentView = currentViewCandidate;
            break;
        default:
            // Leave _currentView to the default
            break;
    }
    // Put it back, which will set it to a valid value
    settings.setValue("CURRENT_VIEW", _currentView);
753 754 755 756 757
}

void MainWindow::storeSettings()
{
    QSettings settings;
758
    settings.beginGroup(MAIN_SETTINGS_GROUP);
dogmaphobic's avatar
dogmaphobic committed
759 760
    settings.setValue("AUTO_RECONNECT", _autoReconnect);
    settings.setValue("LOW_POWER_MODE", _lowPowerMode);
761
    settings.endGroup();
dogmaphobic's avatar
dogmaphobic committed
762
    settings.setValue(_getWindowGeometryKey(), saveGeometry());
763
    // Save the last current view in any case
764
    settings.setValue("CURRENT_VIEW", _currentView);
765
    // Save the current window state, but only if a system is connected (else no real number of widgets would be present))
dogmaphobic's avatar
dogmaphobic committed
766
    if (UASManager::instance()->getUASList().length() > 0) settings.setValue(_getWindowStateKey(), saveState());
767
    // Save the current UAS view if a UAS is connected
768
    if (UASManager::instance()->getUASList().length() > 0) settings.setValue("CURRENT_VIEW_WITH_UAS_CONNECTED", _currentView);
769
    // And save any custom weidgets
770
    QGCToolWidget::storeWidgetsToSettings(settings);
771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792
}

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

dogmaphobic's avatar
dogmaphobic committed
793
// TODO: This is not used
794 795
void MainWindow::startVideoCapture()
{
796
    // TODO: What is this? What kind of "Video" is saved to bmp?
797
    QString format("bmp");
798
    QString initialPath = QDir::currentPath() + tr("/untitled.") + format;
dogmaphobic's avatar
dogmaphobic committed
799
    _screenFileName = QGCFileDialog::getSaveFileName(
800 801 802 803 804
        this, tr("Save Video Capture"),
        initialPath,
        tr("%1 Files (*.%2);;All Files (*)")
        .arg(format.toUpper())
        .arg(format),
805
        format);
806 807 808 809
    delete videoTimer;
    videoTimer = new QTimer(this);
}

dogmaphobic's avatar
dogmaphobic committed
810
// TODO: This is not used
811 812 813 814 815 816
void MainWindow::stopVideoCapture()
{
    videoTimer->stop();
    // TODO Convert raw images to PNG
}

dogmaphobic's avatar
dogmaphobic committed
817
// TODO: This is not used
818 819 820 821
void MainWindow::saveScreen()
{
    QPixmap window = QPixmap::grabWindow(this->winId());
    QString format = "bmp";
dogmaphobic's avatar
dogmaphobic committed
822
    if (!_screenFileName.isEmpty())
823
    {
dogmaphobic's avatar
dogmaphobic committed
824
        window.save(_screenFileName, format.toLatin1());
825 826 827 828 829
    }
}

void MainWindow::enableAutoReconnect(bool enabled)
{
dogmaphobic's avatar
dogmaphobic committed
830
    _autoReconnect = enabled;
831 832 833 834 835 836 837 838 839
}

/**
* @brief Create all actions associated to the main window
*
**/
void MainWindow::connectCommonActions()
{
    // Bind together the perspective actions
dogmaphobic's avatar
dogmaphobic committed
840 841 842 843 844 845 846 847 848
    QActionGroup* perspectives = new QActionGroup(_ui.menuPerspectives);
    perspectives->addAction(_ui.actionEngineersView);
    perspectives->addAction(_ui.actionFlightView);
    perspectives->addAction(_ui.actionSimulationView);
    perspectives->addAction(_ui.actionMissionView);
    perspectives->addAction(_ui.actionSetup);
    perspectives->addAction(_ui.actionTerminalView);
    perspectives->addAction(_ui.actionGoogleEarthView);
    perspectives->addAction(_ui.actionLocal3DView);
849 850
    perspectives->setExclusive(true);

851
    /* Hide the actions that are not relevant */
852
#ifndef QGC_GOOGLE_EARTH_ENABLED
dogmaphobic's avatar
dogmaphobic committed
853
    _ui.actionGoogleEarthView->setVisible(false);
854 855
#endif
#ifndef QGC_OSG_ENABLED
dogmaphobic's avatar
dogmaphobic committed
856
    _ui.actionLocal3DView->setVisible(false);
857 858
#endif

859
    // Mark the right one as selected
860
    if (_currentView == VIEW_ENGINEER)
861
    {
dogmaphobic's avatar
dogmaphobic committed
862 863
        _ui.actionEngineersView->setChecked(true);
        _ui.actionEngineersView->activate(QAction::Trigger);
864
    }
865
    if (_currentView == VIEW_FLIGHT)
866
    {
dogmaphobic's avatar
dogmaphobic committed
867 868
        _ui.actionFlightView->setChecked(true);
        _ui.actionFlightView->activate(QAction::Trigger);
869
    }
870
    if (_currentView == VIEW_SIMULATION)
871
    {
dogmaphobic's avatar
dogmaphobic committed
872 873
        _ui.actionSimulationView->setChecked(true);
        _ui.actionSimulationView->activate(QAction::Trigger);
874
    }
875
    if (_currentView == VIEW_MISSION)
876
    {
dogmaphobic's avatar
dogmaphobic committed
877 878
        _ui.actionMissionView->setChecked(true);
        _ui.actionMissionView->activate(QAction::Trigger);
879
    }
880
    if (_currentView == VIEW_SETUP)
881
    {
dogmaphobic's avatar
dogmaphobic committed
882 883
        _ui.actionSetup->setChecked(true);
        _ui.actionSetup->activate(QAction::Trigger);
884
    }
885
    if (_currentView == VIEW_TERMINAL)
886
    {
dogmaphobic's avatar
dogmaphobic committed
887 888
        _ui.actionTerminalView->setChecked(true);
        _ui.actionTerminalView->activate(QAction::Trigger);
889
    }
890
    if (_currentView == VIEW_GOOGLEEARTH)
891
    {
dogmaphobic's avatar
dogmaphobic committed
892 893
        _ui.actionGoogleEarthView->setChecked(true);
        _ui.actionGoogleEarthView->activate(QAction::Trigger);
894
    }
895
    if (_currentView == VIEW_LOCAL3D)
896
    {
dogmaphobic's avatar
dogmaphobic committed
897 898
        _ui.actionLocal3DView->setChecked(true);
        _ui.actionLocal3DView->activate(QAction::Trigger);
899
    }
900 901

    // The UAS actions are not enabled without connection to system
dogmaphobic's avatar
dogmaphobic committed
902 903 904 905 906
    _ui.actionLiftoff->setEnabled(false);
    _ui.actionLand->setEnabled(false);
    _ui.actionEmergency_Kill->setEnabled(false);
    _ui.actionEmergency_Land->setEnabled(false);
    _ui.actionShutdownMAV->setEnabled(false);
907 908

    // Connect actions from ui
dogmaphobic's avatar
dogmaphobic committed
909
    connect(_ui.actionAdd_Link, SIGNAL(triggered()), this, SLOT(manageLinks()));
910 911 912 913 914 915

    // Connect internal actions
    connect(UASManager::instance(), SIGNAL(UASCreated(UASInterface*)), this, SLOT(UASCreated(UASInterface*)));
    connect(UASManager::instance(), SIGNAL(activeUASSet(UASInterface*)), this, SLOT(setActiveUAS(UASInterface*)));

    // Unmanned System controls
dogmaphobic's avatar
dogmaphobic committed
916 917 918 919 920
    connect(_ui.actionLiftoff, SIGNAL(triggered()), UASManager::instance(), SLOT(launchActiveUAS()));
    connect(_ui.actionLand, SIGNAL(triggered()), UASManager::instance(), SLOT(returnActiveUAS()));
    connect(_ui.actionEmergency_Land, SIGNAL(triggered()), UASManager::instance(), SLOT(stopActiveUAS()));
    connect(_ui.actionEmergency_Kill, SIGNAL(triggered()), UASManager::instance(), SLOT(killActiveUAS()));
    connect(_ui.actionShutdownMAV, SIGNAL(triggered()), UASManager::instance(), SLOT(shutdownActiveUAS()));
921 922

    // Views actions
dogmaphobic's avatar
dogmaphobic committed
923 924 925 926 927 928 929 930
    connect(_ui.actionFlightView, SIGNAL(triggered()), this, SLOT(loadPilotView()));
    connect(_ui.actionSimulationView, SIGNAL(triggered()), this, SLOT(loadSimulationView()));
    connect(_ui.actionEngineersView, SIGNAL(triggered()), this, SLOT(loadEngineerView()));
    connect(_ui.actionMissionView, SIGNAL(triggered()), this, SLOT(loadOperatorView()));
    connect(_ui.actionSetup,SIGNAL(triggered()),this,SLOT(loadSetupView()));
    connect(_ui.actionGoogleEarthView, SIGNAL(triggered()), this, SLOT(loadGoogleEarthView()));
    connect(_ui.actionLocal3DView, SIGNAL(triggered()), this, SLOT(loadLocal3DView()));
    connect(_ui.actionTerminalView,SIGNAL(triggered()),this,SLOT(loadTerminalView()));
931 932

    // Help Actions
dogmaphobic's avatar
dogmaphobic committed
933 934 935
    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()));
936 937

    // Custom widget actions
dogmaphobic's avatar
dogmaphobic committed
938 939
    connect(_ui.actionNewCustomWidget, SIGNAL(triggered()), this, SLOT(_createNewCustomWidget()));
    connect(_ui.actionLoadCustomWidgetFile, SIGNAL(triggered()), this, SLOT(_loadCustomWidgetFromFile()));
940 941

    // Audio output
dogmaphobic's avatar
dogmaphobic committed
942 943 944
    _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)));
945 946

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

dogmaphobic's avatar
dogmaphobic committed
949 950 951 952
    connect(_ui.actionSimulate, SIGNAL(triggered(bool)), this, SLOT(simulateLink(bool)));

    // Update Tool Bar
    _mainToolBar->setCurrentView((MainToolBar::ViewType_t)_currentView);
953 954
}

Don Gagne's avatar
Don Gagne committed
955
void MainWindow::_openUrl(const QString& url, const QString& errorMessage)
956
{
Don Gagne's avatar
Don Gagne committed
957
    if(!QDesktopServices::openUrl(QUrl(url))) {
dogmaphobic's avatar
dogmaphobic committed
958 959 960 961
        QMessageBox::critical(
            this,
            tr("Could not open information in browser"),
            errorMessage);
962 963 964
    }
}

Don Gagne's avatar
Don Gagne committed
965 966
void MainWindow::showHelp()
{
dogmaphobic's avatar
dogmaphobic committed
967 968 969
    _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
970 971
}

972 973
void MainWindow::showCredits()
{
dogmaphobic's avatar
dogmaphobic committed
974 975 976
    _openUrl(
        "http://qgroundcontrol.org/credits",
        tr("To get to the credits, please open http://qgroundcontrol.org/credits in a browser."));
977 978 979 980
}

void MainWindow::showRoadMap()
{
dogmaphobic's avatar
dogmaphobic committed
981 982 983
    _openUrl(
        "http://qgroundcontrol.org/dev/roadmap",
        tr("To get to the online help, please open http://qgroundcontrol.org/roadmap in a browser."));
984 985 986 987
}

void MainWindow::showSettings()
{
Don Gagne's avatar
Don Gagne committed
988 989
    SettingsDialog settings(joystick, this);
    settings.exec();
990 991
}

992
void MainWindow::simulateLink(bool simulate) {
Don Gagne's avatar
Don Gagne committed
993
    if (simulate) {
dogmaphobic's avatar
dogmaphobic committed
994 995 996
        if (!_simulationLink) {
            _simulationLink = new MAVLinkSimulationLink(":/demo-log.txt");
            Q_CHECK_PTR(_simulationLink);
Don Gagne's avatar
Don Gagne committed
997
        }
dogmaphobic's avatar
dogmaphobic committed
998
        LinkManager::instance()->connectLink(_simulationLink);
Don Gagne's avatar
Don Gagne committed
999
    } else {
dogmaphobic's avatar
dogmaphobic committed
1000 1001
        Q_ASSERT(_simulationLink);
        LinkManager::instance()->disconnectLink(_simulationLink);
Don Gagne's avatar
Don Gagne committed
1002
    }
1003 1004
}

1005 1006
void MainWindow::commsWidgetDestroyed(QObject *obj)
{
1007 1008
    // 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
1009
    if (_commsWidgetList.contains(obj))
1010
    {
dogmaphobic's avatar
dogmaphobic committed
1011
        _commsWidgetList.removeOne(obj);
1012 1013
    }
}
1014 1015 1016

void MainWindow::setActiveUAS(UASInterface* uas)
{
1017
    Q_UNUSED(uas);
dogmaphobic's avatar
dogmaphobic committed
1018
    if (settings.contains(_getWindowStateKey()))
1019
    {
dogmaphobic's avatar
dogmaphobic committed
1020
        restoreState(settings.value(_getWindowStateKey()).toByteArray());
1021
    }
1022 1023 1024 1025
}

void MainWindow::UASSpecsChanged(int uas)
{
1026 1027
    Q_UNUSED(uas);
    // TODO: Update UAS properties if its specs change
1028 1029 1030 1031
}

void MainWindow::UASCreated(UASInterface* uas)
{
1032
    // The UAS actions are not enabled without connection to system
dogmaphobic's avatar
dogmaphobic committed
1033 1034 1035 1036 1037
    _ui.actionLiftoff->setEnabled(true);
    _ui.actionLand->setEnabled(true);
    _ui.actionEmergency_Kill->setEnabled(true);
    _ui.actionEmergency_Land->setEnabled(true);
    _ui.actionShutdownMAV->setEnabled(true);
1038

1039
    connect(uas, SIGNAL(systemSpecsChanged(int)), this, SLOT(UASSpecsChanged(int)));
John Tapsell's avatar
John Tapsell committed
1040
    connect(uas, SIGNAL(valueChanged(int,QString,QString,QVariant,quint64)), this, SIGNAL(valueChanged(int,QString,QString,QVariant,quint64)));
1041
    connect(uas, SIGNAL(misconfigurationDetected(UASInterface*)), this, SLOT(handleMisconfiguration(UASInterface*)));
1042

1043
    // HIL
1044
    _showHILConfigurationWidgets();
1045

1046 1047 1048
    if (!linechartWidget)
    {
        linechartWidget = new Linecharts(this);
1049
        linechartWidget->setVisible(false);
1050
    }
1051

1052
    linechartWidget->addSource(mavlinkDecoder);
1053
    if (_engineeringView != linechartWidget)
1054
    {
1055
        _engineeringView = linechartWidget;
1056
    }
1057 1058

    // Reload view state in case new widgets were added
1059
    _loadCurrentViewState();
1060 1061 1062 1063
}

void MainWindow::UASDeleted(UASInterface* uas)
{
1064
    Q_UNUSED(uas);
1065
    // TODO: Update the UI when a UAS is deleted
1066 1067
}

1068 1069
/// Stores the state of the toolbar, status bar and widgets associated with the current view
void MainWindow::_storeCurrentViewState(void)
1070
{
1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083
    // HIL dock widgets are dynamic and are not part of the saved state
    _hideAllHilDockWidgets();
    // 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;
        }
1084
    }
dogmaphobic's avatar
dogmaphobic committed
1085 1086 1087
    settings.setValue(_getWindowStateKey() + "WIDGETS", widgetNames);
    settings.setValue(_getWindowStateKey(), saveState());
    settings.setValue(_getWindowGeometryKey(), saveGeometry());
1088 1089
}

1090 1091
/// Restores the state of the toolbar, status bar and widgets associated with the current view
void MainWindow::_loadCurrentViewState(void)
1092
{
Don Gagne's avatar
Don Gagne committed
1093
    QWidget* centerView = NULL;
1094
    QString defaultWidgets;
1095

1096
    switch (_currentView) {
1097
        case VIEW_SETUP:
1098 1099
            _buildSetupView();
            centerView = _setupView;
1100
            break;
1101

1102
        case VIEW_ENGINEER:
1103 1104 1105
            _buildEngineeringView();
            centerView = _engineeringView;
            defaultWidgets = "MAVLINK_INSPECTOR_DOCKWIDGET,PARAMETER_INTERFACE_DOCKWIDGET,FILE_VIEW_DOCKWIDGET,HEAD_UP_DISPLAY_DOCKWIDGET";
1106
            break;
1107

1108
        case VIEW_FLIGHT:
1109 1110 1111
            _buildPilotView();
            centerView = _pilotView;
            defaultWidgets = "COMMUNICATION_CONSOLE_DOCKWIDGET,UAS_INFO_INFOVIEW_DOCKWIDGET";
1112
            break;
1113

1114
        case VIEW_MISSION:
1115 1116 1117
            _buildPlannerView();
            centerView = _plannerView;
            defaultWidgets = "UNMANNED_SYSTEM_LIST_DOCKWIDGET,WAYPOINT_LIST_DOCKWIDGET";
1118
            break;
1119

1120
        case VIEW_SIMULATION:
1121 1122 1123
            _buildSimView();
            centerView = _simView;
            defaultWidgets = "UNMANNED_SYSTEM_CONTROL_DOCKWIDGET,WAYPOINT_LIST_DOCKWIDGET,PARAMETER_INTERFACE_DOCKWIDGET,PRIMARY_FLIGHT_DISPLAY_DOCKWIDGET";
1124
            break;
1125

1126
        case VIEW_TERMINAL:
1127 1128
            _buildTerminalView();
            centerView = _terminalView;
1129
            break;
1130

1131
        case VIEW_GOOGLEEARTH:
1132 1133
            _buildGoogleEarthView();
            centerView = _googleEarthView;
1134
            break;
1135

1136
        case VIEW_LOCAL3D:
1137 1138
            _buildLocal3DView();
            centerView = _local3DView;
1139
            break;
1140
    }
1141

1142 1143 1144 1145 1146 1147 1148 1149
    // Remove old view
    if (_currentViewWidget) {
        _currentViewWidget->setVisible(false);
        Q_ASSERT(_centralLayout->count() == 1);
        QLayoutItem *child = _centralLayout->takeAt(0);
        Q_ASSERT(child);
        delete child;
    }
1150

1151 1152 1153 1154 1155 1156
    // Add the new one
    Q_ASSERT(centerView);
    Q_ASSERT(_centralLayout->count() == 0);
    _currentViewWidget = centerView;
    _centralLayout->addWidget(_currentViewWidget);
    _currentViewWidget->setVisible(true);
1157

1158 1159 1160 1161
    // Hide all widgets from previous view
    _hideAllDockWidgets();

    // Restore the widgets for the new view
dogmaphobic's avatar
dogmaphobic committed
1162
    QString widgetNames = settings.value(_getWindowStateKey() + "WIDGETS", defaultWidgets).toString();
1163 1164 1165 1166 1167
    if (!widgetNames.isEmpty()) {
        QStringList split = widgetNames.split(",");
        foreach (QString widgetName, split) {
            Q_ASSERT(!widgetName.isEmpty());
            _showDockWidget(widgetName, true);
1168 1169 1170
        }
    }

dogmaphobic's avatar
dogmaphobic committed
1171 1172
    if (settings.contains(_getWindowStateKey())) {
        restoreState(settings.value(_getWindowStateKey()).toByteArray());
1173
    }
1174

1175 1176 1177 1178 1179 1180 1181 1182 1183 1184 1185 1186 1187 1188 1189 1190
    // HIL dock widget are dynamic and don't take part in the saved window state, so this
    // need to happen after we restore state
    _showHILConfigurationWidgets();
}

void MainWindow::_hideAllHilDockWidgets(void)
{
    foreach(QDockWidget* dockWidget, _mapUasId2HilDockWidget) {
        dockWidget->setVisible(false);
    }
}

void MainWindow::_hideAllDockWidgets(void)
{
    foreach(QDockWidget* dockWidget, _mapName2DockWidget) {
        dockWidget->setVisible(false);
1191
    }
1192
    _hideAllHilDockWidgets();
1193
}
1194 1195

void MainWindow::_showDockWidgetAction(bool show)
1196
{
1197 1198 1199
    QAction* action = dynamic_cast<QAction*>(QObject::sender());
    Q_ASSERT(action);
    _showDockWidget(action->data().toString(), show);
1200
}
1201

1202

1203 1204 1205 1206 1207 1208 1209 1210 1211 1212 1213 1214
void MainWindow::handleMisconfiguration(UASInterface* uas)
{
    static QTime lastTime;
    // We have to debounce this signal
    if (!lastTime.isValid()) {
        lastTime.start();
    } else {
        if (lastTime.elapsed() < 10000) {
            lastTime.start();
            return;
        }
    }
dogmaphobic's avatar
dogmaphobic committed
1215 1216 1217 1218 1219 1220 1221
    // Ask user if they want to handle this now
    QMessageBox::StandardButton button =
        QGCMessageBox::question(
            tr("Missing or Invalid Onboard Configuration"),
            tr("The onboard system configuration is missing or incomplete. Do you want to resolve this now?"),
            QMessageBox::Ok | QMessageBox::Cancel,
            QMessageBox::Ok);
Don Gagne's avatar
Don Gagne committed
1222
    if (button == QMessageBox::Ok) {
dogmaphobic's avatar
dogmaphobic committed
1223
        // They want to handle it, make sure this system is selected
1224 1225
        UASManager::instance()->setActiveUAS(uas);
        // Flick to config view
1226
        loadSetupView();
1227 1228 1229
    }
}

1230 1231
void MainWindow::loadEngineerView()
{
1232
    if (_currentView != VIEW_ENGINEER)
1233
    {
1234 1235
        _storeCurrentViewState();
        _currentView = VIEW_ENGINEER;
dogmaphobic's avatar
dogmaphobic committed
1236
        _ui.actionEngineersView->setChecked(true);
1237
        _loadCurrentViewState();
1238 1239 1240 1241 1242
    }
}

void MainWindow::loadOperatorView()
{
1243
    if (_currentView != VIEW_MISSION)
1244
    {
1245 1246
        _storeCurrentViewState();
        _currentView = VIEW_MISSION;
dogmaphobic's avatar
dogmaphobic committed
1247
        _ui.actionMissionView->setChecked(true);
1248
        _loadCurrentViewState();
1249 1250
    }
}
1251
void MainWindow::loadSetupView()
1252
{
1253
    if (_currentView != VIEW_SETUP)
1254
    {
1255 1256
        _storeCurrentViewState();
        _currentView = VIEW_SETUP;
dogmaphobic's avatar
dogmaphobic committed
1257
        _ui.actionSetup->setChecked(true);
1258
        _loadCurrentViewState();
1259 1260 1261
    }
}

1262 1263
void MainWindow::loadTerminalView()
{
1264
    if (_currentView != VIEW_TERMINAL)
1265
    {
1266 1267
        _storeCurrentViewState();
        _currentView = VIEW_TERMINAL;
dogmaphobic's avatar
dogmaphobic committed
1268
        _ui.actionTerminalView->setChecked(true);
1269
        _loadCurrentViewState();
1270 1271 1272
    }
}

1273 1274
void MainWindow::loadGoogleEarthView()
{
1275
    if (_currentView != VIEW_GOOGLEEARTH)
1276
    {
1277 1278
        _storeCurrentViewState();
        _currentView = VIEW_GOOGLEEARTH;
dogmaphobic's avatar
dogmaphobic committed
1279
        _ui.actionGoogleEarthView->setChecked(true);
1280
        _loadCurrentViewState();
1281 1282 1283 1284 1285
    }
}

void MainWindow::loadLocal3DView()
{
1286
    if (_currentView != VIEW_LOCAL3D)
1287
    {
1288 1289
        _storeCurrentViewState();
        _currentView = VIEW_LOCAL3D;
dogmaphobic's avatar
dogmaphobic committed
1290
        _ui.actionLocal3DView->setChecked(true);
1291
        _loadCurrentViewState();
1292 1293
    }
}
1294

1295 1296
void MainWindow::loadPilotView()
{
1297
    if (_currentView != VIEW_FLIGHT)
1298
    {
1299 1300
        _storeCurrentViewState();
        _currentView = VIEW_FLIGHT;
dogmaphobic's avatar
dogmaphobic committed
1301
        _ui.actionFlightView->setChecked(true);
1302
        _loadCurrentViewState();
1303 1304 1305
    }
}

1306 1307
void MainWindow::loadSimulationView()
{
1308
    if (_currentView != VIEW_SIMULATION)
1309
    {
1310 1311
        _storeCurrentViewState();
        _currentView = VIEW_SIMULATION;
dogmaphobic's avatar
dogmaphobic committed
1312
        _ui.actionSimulationView->setChecked(true);
1313
        _loadCurrentViewState();
1314 1315 1316
    }
}

1317
QList<QAction*> MainWindow::listLinkMenuActions()
1318
{
dogmaphobic's avatar
dogmaphobic committed
1319
    return _ui.menuNetwork->actions();
1320
}
1321

Don Gagne's avatar
Don Gagne committed
1322
/// @brief Hides the spash screen if it is currently being shown
1323
void MainWindow::hideSplashScreen(void)
Don Gagne's avatar
Don Gagne committed
1324 1325 1326 1327 1328 1329 1330
{
    if (_splashScreen) {
        _splashScreen->hide();
        _splashScreen = NULL;
    }
}

1331 1332 1333 1334 1335 1336 1337 1338 1339 1340 1341 1342 1343 1344 1345 1346 1347 1348 1349 1350 1351 1352 1353 1354 1355 1356 1357 1358 1359 1360 1361 1362 1363 1364 1365 1366
void MainWindow::manageLinks()
{
    SettingsDialog settings(joystick, this, SettingsDialog::ShowCommLinks);
    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";
    QString connection;
    if(settings.contains(key)) {
        connection = settings.value(connection).toString();
        // Create a link for it
        LinkInterface* link = LinkManager::instance()->createLink(connection);
        if(link) {
            // Connect it
            LinkManager::instance()->connectLink(link);
        }
    }
}
Don Gagne's avatar
Don Gagne committed
1367

1368
#ifdef QGC_MOUSE_ENABLED_LINUX
1369 1370 1371
bool MainWindow::x11Event(XEvent *event)
{
    emit x11EventOccured(event);
1372
    return false;
1373
}
1374
#endif // QGC_MOUSE_ENABLED_LINUX
1375 1376 1377 1378 1379 1380 1381 1382

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