MainWindow.cc 50 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

107
    new MainWindow(splashScreen);
108

Don Gagne's avatar
Don Gagne committed
109 110 111
    // _instance is set in constructor
    Q_ASSERT(_instance);

112 113 114
    return _instance;
}

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

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

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

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

144
    loadSettings();
145

146
    // Select the proper view. Default to the flight view or load the last one used if it's supported.
147 148 149 150 151 152 153 154
    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:
155
#ifdef QGC_OSG_ENABLED
156
        case VIEW_LOCAL3D:
157 158
#endif
#ifdef QGC_GOOGLE_EARTH_ENABLED
159
        case VIEW_GOOGLEEARTH:
160
#endif
161 162
            _currentView = currentViewCandidate;
            break;
163

164 165 166
        default:
            // Leave _currentView to the default
            break;
167
    }
168

169 170
    // Put it back, which will set it to a valid value
    settings.setValue("CURRENT_VIEW", _currentView);
171

172
    emit initStatusChanged(tr("Setting up user interface"), Qt::AlignLeft | Qt::AlignBottom, QColor(62, 93, 141));
173 174 175

    // Setup user interface
    ui.setupUi(this);
176

177 178 179
    // Setup central widget with a layout to hold the views
    _centralLayout = new QVBoxLayout();
    centralWidget()->setLayout(_centralLayout);
180

181 182 183 184 185 186
    // Set dock options
    setDockOptions(AnimatedDocks | AllowTabbedDocks | AllowNestedDocks);

    configureWindowName();

    // Setup corners
187
    setCorner(Qt::BottomRightCorner, Qt::BottomDockWidgetArea);
188

189 190 191 192 193
    // 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
194 195 196 197 198 199
    
#ifdef UNITTEST_BUILD
    QAction* qmlTestAction = new QAction("Test QML palette and controls", NULL);
    connect(qmlTestAction, &QAction::triggered, this, &MainWindow::_showQmlTestWidget);
    ui.menuTools->addAction(qmlTestAction);
#endif
200

201
    // Setup UI state machines
202
    centerStackActionGroup->setExclusive(true);
203 204

    // Load Toolbar
Don Gagne's avatar
Don Gagne committed
205 206
    toolBar = new QGCToolBar(this);
    this->addToolBar(toolBar);
207

Bryant's avatar
Bryant committed
208
    // Add the perspectives to the toolbar
Don Gagne's avatar
Don Gagne committed
209
    QList<QAction*> actions;
210
    actions << ui.actionSetup;
Don Gagne's avatar
Don Gagne committed
211 212
    actions << ui.actionMissionView;
    actions << ui.actionFlightView;
213
    actions << ui.actionEngineersView;
Don Gagne's avatar
Don Gagne committed
214
    toolBar->setPerspectiveChangeActions(actions);
215

Don Gagne's avatar
Don Gagne committed
216 217 218 219 220 221 222
    // Add actions for advanced users (displayed in dropdown under "advanced")
    QList<QAction*> advancedActions;
    advancedActions << ui.actionGoogleEarthView;
    advancedActions << ui.actionLocal3DView;
    advancedActions << ui.actionTerminalView;
    advancedActions << ui.actionSimulationView;
    toolBar->setPerspectiveChangeAdvancedActions(advancedActions);
223

224
    setStatusBar(new QStatusBar(this));
225
    statusBar()->setSizeGripEnabled(true);
226

227
    emit initStatusChanged(tr("Building common widgets."), Qt::AlignLeft | Qt::AlignBottom, QColor(62, 93, 141));
228

229
    _buildCommonWidgets();
230

231
    emit initStatusChanged(tr("Building common actions"), Qt::AlignLeft | Qt::AlignBottom, QColor(62, 93, 141));
232 233 234 235 236

    // Create actions
    connectCommonActions();

    // Connect user interface devices
237
    emit initStatusChanged(tr("Initializing joystick interface"), Qt::AlignLeft | Qt::AlignBottom, QColor(62, 93, 141));
238 239
    joystick = new JoystickInput();

240
#ifdef QGC_MOUSE_ENABLED_WIN
241
    emit initStatusChanged(tr("Initializing 3D mouse interface"), Qt::AlignLeft | Qt::AlignBottom, QColor(62, 93, 141));
242 243 244

    mouseInput = new Mouse3DInput(this);
    mouse = new Mouse6dofInput(mouseInput);
245
#endif //QGC_MOUSE_ENABLED_WIN
246

247
#if QGC_MOUSE_ENABLED_LINUX
248
    emit initStatusChanged(tr("Initializing 3D mouse interface"), Qt::AlignLeft | Qt::AlignBottom, QColor(62, 93, 141));
249 250

    mouse = new Mouse6dofInput(this);
251
    connect(this, SIGNAL(x11EventOccured(XEvent*)), mouse, SLOT(handleX11Event(XEvent*)));
252
#endif //QGC_MOUSE_ENABLED_LINUX
253

254 255 256
    // Connect link
    if (autoReconnect)
    {
257
        restoreLastUsedConnection();
258 259 260 261 262
    }

    // Set low power mode
    enableLowPowerMode(lowPowerMode);

263
    emit initStatusChanged(tr("Restoring last view state"), Qt::AlignLeft | Qt::AlignBottom, QColor(62, 93, 141));
264 265

    // Restore the window setup
266
    _loadCurrentViewState();
267

268
    emit initStatusChanged(tr("Restoring last window size"), Qt::AlignLeft | Qt::AlignBottom, QColor(62, 93, 141));
269 270 271 272 273 274 275 276 277 278 279 280
    // Restore the window position and size
    if (settings.contains(getWindowGeometryKey()))
    {
        // Restore the window geometry
        restoreGeometry(settings.value(getWindowGeometryKey()).toByteArray());
    }
    else
    {
        // Adjust the size
        const int screenWidth = QApplication::desktop()->width();
        const int screenHeight = QApplication::desktop()->height();

Lorenz Meier's avatar
Lorenz Meier committed
281
        if (screenWidth < 1500)
282
        {
283
            resize(screenWidth, screenHeight - 80);
284 285 286 287 288 289 290 291
        }
        else
        {
            resize(screenWidth*0.67f, qMin(screenHeight, (int)(screenWidth*0.67f*0.67f)));
        }

    }

292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308
    // Make sure the proper fullscreen/normal menu item is checked properly.
    if (isFullScreen())
    {
        ui.actionFullscreen->setChecked(true);
        ui.actionNormal->setChecked(false);
    }
    else
    {
        ui.actionFullscreen->setChecked(false);
        ui.actionNormal->setChecked(true);
    }

    // And that they will stay checked properly after user input
    QObject::connect(ui.actionFullscreen, SIGNAL(triggered()), this, SLOT(fullScreenActionItemCallback()));
    QObject::connect(ui.actionNormal, SIGNAL(triggered()), this,SLOT(normalActionItemCallback()));


309 310
    // Set OS dependent keyboard shortcuts for the main window, non OS dependent shortcuts are set in MainWindow.ui
#ifdef Q_OS_MACX
311 312 313
    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));
314 315 316 317 318
    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));
319
    ui.actionFullscreen->setShortcut(QApplication::translate("MainWindow", "Meta+Return", 0));
320
#else
321 322 323
    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));
324 325 326 327 328
    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));
329
    ui.actionFullscreen->setShortcut(QApplication::translate("MainWindow", "Ctrl+Return", 0));
330 331
#endif

332 333
    connect(&windowNameUpdateTimer, SIGNAL(timeout()), this, SLOT(configureWindowName()));
    windowNameUpdateTimer.start(15000);
334
    emit initStatusChanged(tr("Done"), Qt::AlignLeft | Qt::AlignBottom, QColor(62, 93, 141));
335 336 337 338

    if (!qgcApp()->runningUnitTests()) {
        show();
    }
339 340 341 342
}

MainWindow::~MainWindow()
{
343 344 345 346 347
    if (simulationLink)
    {
        delete simulationLink;
        simulationLink = NULL;
    }
348 349
    if (joystick)
    {
350 351
        joystick->shutdown();
        joystick->wait(5000);
352 353 354 355 356
        delete joystick;
        joystick = NULL;
    }

    // Delete all UAS objects
357 358 359 360
    for (int i=0;i<commsWidgetList.size();i++)
    {
        commsWidgetList[i]->deleteLater();
    }
361

Don Gagne's avatar
Don Gagne committed
362
    _instance = NULL;
363 364 365 366 367 368 369 370 371
}

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

QString MainWindow::getWindowStateKey()
{
372 373
    if (UASManager::instance()->getActiveUAS())
    {
374
        return QString::number(_currentView)+"_windowstate_" + UASManager::instance()->getActiveUAS()->getAutopilotTypeName();
375 376
    }
    else
377
        return QString::number(_currentView)+"_windowstate_";
378 379 380 381 382 383 384
}

QString MainWindow::getWindowGeometryKey()
{
    return "_geometry";
}

385
void MainWindow::_buildCustomWidgets(void)
386
{
387
    Q_ASSERT(_customWidgets.count() == 0);
388

389
    // Create custom widgets
390
    _customWidgets = QGCToolWidget::createWidgetsFromSettings(this);
391

392
    if (_customWidgets.size() > 0)
393 394 395
    {
        ui.menuTools->addSeparator();
    }
396

397
    foreach(QGCToolWidget* tool, _customWidgets) {
398 399
        // Check if this widget already has a parent, do not create it in this case
        QDockWidget* dock = dynamic_cast<QDockWidget*>(tool->parentWidget());
400

401 402
        if (!dock) {
            _createDockWidget(tool->getTitle(), tool->objectName(), Qt::BottomDockWidgetArea, tool);
403 404 405 406
        }
    }
}

407 408 409
void MainWindow::_createDockWidget(const QString& title, const QString& name, Qt::DockWidgetArea area, QWidget* innerWidget)
{
    Q_ASSERT(!_mapName2DockWidget.contains(name));
410

411 412 413 414
    QGCDockWidget* dockWidget = new QGCDockWidget(title, this);
    Q_CHECK_PTR(dockWidget);
    dockWidget->setObjectName(name);
    dockWidget->setVisible (false);
415

416 417 418 419 420 421
    if (innerWidget) {
        // Put inner widget inside QDockWidget
        innerWidget->setParent(dockWidget);
        dockWidget->setWidget(innerWidget);
        innerWidget->setVisible(true);
    }
422

423
    // Add to menu
424

425 426 427
    QAction* action = new QAction(title, NULL);
    action->setCheckable(true);
    action->setData(name);
428

429
    connect(action, &QAction::triggered, this, &MainWindow::_showDockWidgetAction);
430

431
    ui.menuTools->addAction(action);
432

433 434
    _mapName2DockWidget[name] = dockWidget;
    _mapDockWidget2Action[dockWidget] = action;
435

436 437 438 439
    addDockWidget(area, dockWidget);
}

void MainWindow::_buildCommonWidgets(void)
440 441
{
    // Add generic MAVLink decoder
442
    mavlinkDecoder = new MAVLinkDecoder(MAVLinkProtocol::instance(), this);
John Tapsell's avatar
John Tapsell committed
443 444
    connect(mavlinkDecoder, SIGNAL(valueChanged(int,QString,QString,QVariant,quint64)),
                      this, SIGNAL(valueChanged(int,QString,QString,QVariant,quint64)));
445

446
    // Log player
447
    logPlayer = new QGCMAVLinkLogPlayer(MAVLinkProtocol::instance(), statusBar());
448
    statusBar()->addPermanentWidget(logPlayer);
449

450 451 452
    // 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.
453

454 455 456 457 458
    struct DockWidgetInfo {
        const char* name;
        const char* title;
        Qt::DockWidgetArea area;
    };
459

460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477
    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]);
478

479 480
    for (size_t i=0; i<cDockWidgetInfo; i++) {
        const struct DockWidgetInfo* pDockInfo = &rgDockWidgetInfo[i];
481

482
        _createDockWidget(pDockInfo->title, pDockInfo->name, pDockInfo->area, NULL /* no inner widget yet */);
483 484
    }

485 486
    _buildCustomWidgets();
}
487

488 489 490 491 492
void MainWindow::_buildPlannerView(void)
{
    if (!_plannerView) {
        _plannerView = new QGCMapTool(this);
        _plannerView->setVisible(false);
493
    }
494
}
495

496 497 498 499 500 501
void MainWindow::_buildPilotView(void)
{
    if (!_pilotView) {
        _pilotView = new PrimaryFlightDisplay(this);
        _pilotView->setVisible(false);
    }
502 503
}

504
void MainWindow::_buildSetupView(void)
505
{
506 507 508 509
    if (!_setupView) {
        _setupView = new SetupView(this);
        _setupView->setVisible(false);
    }
510 511
}

512
void MainWindow::_buildEngineeringView(void)
513
{
514 515 516 517 518
    if (!_engineeringView) {
        _engineeringView = new QGCDataPlot2D(this);
        _engineeringView->setVisible(false);
    }
}
519

520 521 522 523 524 525
void MainWindow::_buildSimView(void)
{
    if (!_simView) {
        _simView = new QGCMapTool(this);
        _simView->setVisible(false);
    }
526
}
527

528
void MainWindow::_buildTerminalView(void)
529
{
530 531 532 533
    if (!_terminalView) {
        _terminalView = new TerminalConsole(this);
        _terminalView->setVisible(false);
    }
534 535
}

536
void MainWindow::_buildGoogleEarthView(void)
537
{
538 539 540 541 542 543
#ifdef QGC_GOOGLE_EARTH_ENABLED
    if (!_googleEarthView) {
        _googleEarthView = new QGCGoogleEarthView(this);
        _googleEarthView->setVisible(false);
    }
#endif
544 545
}

546
void MainWindow::_buildLocal3DView(void)
547
{
548 549 550 551 552 553
#ifdef QGC_OSG_ENABLED
    if (!_local3DView) {
        _local3DView = Q3DWidgetFactory::get("PIXHAWK", this);
        _local3DView->setVisible(false);
    }
#endif
554 555
}

556 557
/// Shows or hides the specified dock widget, creating if necessary
void MainWindow::_showDockWidget(const QString& name, bool show)
558
{
559 560
    if (!_mapName2DockWidget.contains(name)) {
        qWarning() << "Attempt to show unknown dock widget" << name;
561 562
        return;
    }
563

564 565 566
    // Create the inner widget if we need to
    if (!_mapName2DockWidget[name]->widget()) {
        _createInnerDockWidget(name);
567
    }
568 569 570 571

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

573
    dockWidget->setVisible(show);
574

575 576 577 578 579 580 581 582 583
    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
584

585
    QWidget* widget = NULL;
586

587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605
    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) {
606 607 608 609
        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");
610 611
        HDDisplay *hddisplay = new HDDisplay(acceptList,"Flight Display",this);
        hddisplay->addSource(mavlinkDecoder);
612

613 614
        widget = hddisplay;
    } else if (widgetName == _hdd2DockWidgetName) {
615 616 617
        QStringList acceptList;
        acceptList.append("0,RAW_PRESSURE.pres_abs,hPa,65500");
        HDDisplay *hddisplay = new HDDisplay(acceptList,"Actuator Status",this);
618
        hddisplay->addSource(mavlinkDecoder);
619

620 621 622 623 624 625 626 627 628 629 630
        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;
631
    }
632

633 634 635
    if (widget) {
        QDockWidget* dockWidget = _mapName2DockWidget[widgetName];
        Q_CHECK_PTR(dockWidget);
636

637 638
        widget->setParent(dockWidget);
        dockWidget->setWidget(widget);
639 640
    }
}
641

642
void MainWindow::_showHILConfigurationWidgets(void)
643
{
644
    UASInterface* uas = UASManager::instance()->getActiveUAS();
645

646 647 648
    if (!uas) {
        return;
    }
649

650 651
    UAS* mav = dynamic_cast<UAS*>(uas);
    Q_ASSERT(mav);
652

653
    int uasId = mav->getUASID();
654

655
    if (!_mapUasId2HilDockWidget.contains(uasId)) {
656

657 658 659 660 661
        // 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);
662

663 664
        // Create inner widget and set it
        QWidget* widget = new QGCHilConfiguration(mav, dockWidget);
665

666 667
        widget->setParent(dockWidget);
        dockWidget->setWidget(widget);
668

669
        _mapUasId2HilDockWidget[uasId] = dockWidget;
670

671 672
        addDockWidget(Qt::LeftDockWidgetArea, dockWidget);
    }
673

674 675 676 677 678
    if (_currentView == VIEW_SIMULATION) {
        // HIL dock widgets only show up on simulation view
        foreach (QDockWidget* dockWidget, _mapUasId2HilDockWidget) {
            dockWidget->setVisible(true);
        }
679 680 681
    }
}

682
void MainWindow::fullScreenActionItemCallback()
683
{
684
    ui.actionNormal->setChecked(false);
685 686
}

687
void MainWindow::normalActionItemCallback()
688
{
689
    ui.actionFullscreen->setChecked(false);
690 691 692 693
}

void MainWindow::closeEvent(QCloseEvent *event)
{
694
    // Disallow window close if there are active connections
695

696 697 698 699 700 701 702
    bool foundConnections = false;
    foreach(LinkInterface* link, LinkManager::instance()->getLinks()) {
        if (link->isConnected()) {
            foundConnections = true;
            break;
        }
    }
703

704
    if (foundConnections) {
705 706 707 708 709 710 711 712 713 714 715 716
        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);
        if (button == QMessageBox::Yes) {
            foreach(LinkInterface* link, LinkManager::instance()->getLinks()) {
                LinkManager::instance()->disconnectLink(link);
            }
        } else {
            event->ignore();
            return;
        }
717 718
    }

719 720 721
    // This will process any remaining flight log save dialogs
    qgcApp()->processEvents(QEventLoop::ExcludeUserInputEvents);

722 723
    // Should not be any active connections
    foreach(LinkInterface* link, LinkManager::instance()->getLinks()) {
Don Gagne's avatar
Don Gagne committed
724
        Q_UNUSED(link);
725 726 727
        Q_ASSERT(!link->isConnected());
    }

728
    _storeCurrentViewState();
729
    storeSettings();
730
    UASManager::instance()->storeSettings();
731
    event->accept();
732 733
}

734
void MainWindow::_createNewCustomWidget(void)
735
{
736
    if (QGCToolWidget::instances()->isEmpty())
737 738 739 740
    {
        // This is the first widget
        ui.menuTools->addSeparator();
    }
741 742 743 744 745 746 747 748 749 750
    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 );

751
    QGCToolWidget* tool = new QGCToolWidget(objectName, title);
752 753
    tool->resize(100, 100);
    _createDockWidget(title, objectName, Qt::BottomDockWidgetArea, tool);
754

755
    _mapName2DockWidget[objectName]->setVisible(true);
756 757
}

758
void MainWindow::_loadCustomWidgetFromFile(void)
759
{
760 761 762
    QString fileName = QGCFileDialog::getOpenFileName(
        this, tr("Load Widget File"),
        QStandardPaths::writableLocation(QStandardPaths::DesktopLocation),
763
        tr("QGroundControl Widgets (*.qgw);;All Files (*)"));
764
    if (!fileName.isEmpty()) {
765 766 767
        QGCToolWidget* tool = new QGCToolWidget("", "", this);
        if (tool->loadSettings(fileName, true)) {
            QString objectName = tool->objectName() + "DOCK";
768

769 770
            _createDockWidget(tool->getTitle(), objectName, Qt::LeftDockWidgetArea, tool);
            _mapName2DockWidget[objectName]->widget()->setVisible(true);
771 772
        }
    }
773
    // TODO Add error dialog if widget could not be loaded
774 775 776 777
}

void MainWindow::loadSettings()
{
778
    // Why the screaming?
779
    QSettings settings;
780
    settings.beginGroup(MAIN_SETTINGS_GROUP);
781
    autoReconnect = settings.value("AUTO_RECONNECT", autoReconnect).toBool();
782
    lowPowerMode  = settings.value("LOW_POWER_MODE", lowPowerMode).toBool();
783 784 785 786 787 788
    settings.endGroup();
}

void MainWindow::storeSettings()
{
    QSettings settings;
789

790
    settings.beginGroup(MAIN_SETTINGS_GROUP);
791 792
    settings.setValue("AUTO_RECONNECT", autoReconnect);
    settings.setValue("LOW_POWER_MODE", lowPowerMode);
793 794 795 796 797
    settings.endGroup();

    settings.setValue(getWindowGeometryKey(), saveGeometry());

    // Save the last current view in any case
798
    settings.setValue("CURRENT_VIEW", _currentView);
799 800 801 802 803

    // Save the current window state, but only if a system is connected (else no real number of widgets would be present))
    if (UASManager::instance()->getUASList().length() > 0) settings.setValue(getWindowStateKey(), saveState());

    // Save the current UAS view if a UAS is connected
804
    if (UASManager::instance()->getUASList().length() > 0) settings.setValue("CURRENT_VIEW_WITH_UAS_CONNECTED", _currentView);
805 806

    // And save any custom weidgets
807
    QGCToolWidget::storeWidgetsToSettings(settings);
808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835
}

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::startVideoCapture()
{
836
    // TODO: What is this? What kind of "Video" is saved to bmp?
837
    QString format("bmp");
838
    QString initialPath = QDir::currentPath() + tr("/untitled.") + format;
839 840 841 842 843 844
    QString screenFileName = QGCFileDialog::getSaveFileName(
        this, tr("Save Video Capture"),
        initialPath,
        tr("%1 Files (*.%2);;All Files (*)")
        .arg(format.toUpper())
        .arg(format),
845
        format);
846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863
    delete videoTimer;
    videoTimer = new QTimer(this);
}

void MainWindow::stopVideoCapture()
{
    videoTimer->stop();

    // TODO Convert raw images to PNG
}

void MainWindow::saveScreen()
{
    QPixmap window = QPixmap::grabWindow(this->winId());
    QString format = "bmp";

    if (!screenFileName.isEmpty())
    {
864
        window.save(screenFileName, format.toLatin1());
865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881
    }
}

void MainWindow::enableAutoReconnect(bool enabled)
{
    autoReconnect = enabled;
}

/**
* @brief Create all actions associated to the main window
*
**/
void MainWindow::connectCommonActions()
{
    // Bind together the perspective actions
    QActionGroup* perspectives = new QActionGroup(ui.menuPerspectives);
    perspectives->addAction(ui.actionEngineersView);
882
    perspectives->addAction(ui.actionFlightView);
883
    perspectives->addAction(ui.actionSimulationView);
884
    perspectives->addAction(ui.actionMissionView);
885
    perspectives->addAction(ui.actionSetup);
886
    perspectives->addAction(ui.actionTerminalView);
Lorenz Meier's avatar
Lorenz Meier committed
887 888
    perspectives->addAction(ui.actionGoogleEarthView);
    perspectives->addAction(ui.actionLocal3DView);
889 890
    perspectives->setExclusive(true);

891
    /* Hide the actions that are not relevant */
892 893 894 895 896 897 898
#ifndef QGC_GOOGLE_EARTH_ENABLED
    ui.actionGoogleEarthView->setVisible(false);
#endif
#ifndef QGC_OSG_ENABLED
    ui.actionLocal3DView->setVisible(false);
#endif

899
    // Mark the right one as selected
900
    if (_currentView == VIEW_ENGINEER)
901 902 903 904
    {
        ui.actionEngineersView->setChecked(true);
        ui.actionEngineersView->activate(QAction::Trigger);
    }
905
    if (_currentView == VIEW_FLIGHT)
906 907 908 909
    {
        ui.actionFlightView->setChecked(true);
        ui.actionFlightView->activate(QAction::Trigger);
    }
910
    if (_currentView == VIEW_SIMULATION)
911
    {
912 913
        ui.actionSimulationView->setChecked(true);
        ui.actionSimulationView->activate(QAction::Trigger);
914
    }
915
    if (_currentView == VIEW_MISSION)
916 917 918 919
    {
        ui.actionMissionView->setChecked(true);
        ui.actionMissionView->activate(QAction::Trigger);
    }
920
    if (_currentView == VIEW_SETUP)
921
    {
922 923
        ui.actionSetup->setChecked(true);
        ui.actionSetup->activate(QAction::Trigger);
924
    }
925
    if (_currentView == VIEW_TERMINAL)
926 927 928 929
    {
        ui.actionTerminalView->setChecked(true);
        ui.actionTerminalView->activate(QAction::Trigger);
    }
930
    if (_currentView == VIEW_GOOGLEEARTH)
931 932 933 934
    {
        ui.actionGoogleEarthView->setChecked(true);
        ui.actionGoogleEarthView->activate(QAction::Trigger);
    }
935
    if (_currentView == VIEW_LOCAL3D)
936 937 938 939
    {
        ui.actionLocal3DView->setChecked(true);
        ui.actionLocal3DView->activate(QAction::Trigger);
    }
940 941 942 943 944 945 946 947 948

    // The UAS actions are not enabled without connection to system
    ui.actionLiftoff->setEnabled(false);
    ui.actionLand->setEnabled(false);
    ui.actionEmergency_Kill->setEnabled(false);
    ui.actionEmergency_Land->setEnabled(false);
    ui.actionShutdownMAV->setEnabled(false);

    // Connect actions from ui
949
    connect(ui.actionAdd_Link, SIGNAL(triggered()), this, SLOT(manageLinks()));
950 951 952 953 954 955 956 957 958 959 960 961 962

    // 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
    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()));

    // Views actions
963
    connect(ui.actionFlightView, SIGNAL(triggered()), this, SLOT(loadPilotView()));
964
    connect(ui.actionSimulationView, SIGNAL(triggered()), this, SLOT(loadSimulationView()));
965
    connect(ui.actionEngineersView, SIGNAL(triggered()), this, SLOT(loadEngineerView()));
966
    connect(ui.actionMissionView, SIGNAL(triggered()), this, SLOT(loadOperatorView()));
967
    connect(ui.actionSetup,SIGNAL(triggered()),this,SLOT(loadSetupView()));
968 969
    connect(ui.actionGoogleEarthView, SIGNAL(triggered()), this, SLOT(loadGoogleEarthView()));
    connect(ui.actionLocal3DView, SIGNAL(triggered()), this, SLOT(loadLocal3DView()));
970
    connect(ui.actionTerminalView,SIGNAL(triggered()),this,SLOT(loadTerminalView()));
971 972 973 974

    // Help Actions
    connect(ui.actionOnline_Documentation, SIGNAL(triggered()), this, SLOT(showHelp()));
    connect(ui.actionDeveloper_Credits, SIGNAL(triggered()), this, SLOT(showCredits()));
975
    connect(ui.actionProject_Roadmap, SIGNAL(triggered()), this, SLOT(showRoadMap()));
976 977

    // Custom widget actions
978 979
    connect(ui.actionNewCustomWidget, SIGNAL(triggered()), this, SLOT(_createNewCustomWidget()));
    connect(ui.actionLoadCustomWidgetFile, SIGNAL(triggered()), this, SLOT(_loadCustomWidgetFromFile()));
980 981 982 983 984 985 986 987

    // Audio output
    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)));

    // Application Settings
    connect(ui.actionSettings, SIGNAL(triggered()), this, SLOT(showSettings()));
988 989

    connect(ui.actionSimulate, SIGNAL(triggered(bool)), this, SLOT(simulateLink(bool)));
990 991
}

Don Gagne's avatar
Don Gagne committed
992
void MainWindow::_openUrl(const QString& url, const QString& errorMessage)
993
{
Don Gagne's avatar
Don Gagne committed
994 995 996 997
    if(!QDesktopServices::openUrl(QUrl(url))) {
        QMessageBox::critical(this,
                              tr("Could not open information in browser"),
                              errorMessage);
998 999 1000
    }
}

Don Gagne's avatar
Don Gagne committed
1001 1002 1003 1004 1005 1006
void MainWindow::showHelp()
{
    _openUrl("http://qgroundcontrol.org/users/start",
             tr("To get to the online help, please open http://qgroundcontrol.org/user_guide in a browser."));
}

1007 1008
void MainWindow::showCredits()
{
Don Gagne's avatar
Don Gagne committed
1009 1010
    _openUrl("http://qgroundcontrol.org/credits",
             tr("To get to the credits, please open http://qgroundcontrol.org/credits in a browser."));
1011 1012 1013 1014
}

void MainWindow::showRoadMap()
{
Don Gagne's avatar
Don Gagne committed
1015 1016
    _openUrl("http://qgroundcontrol.org/dev/roadmap",
             tr("To get to the online help, please open http://qgroundcontrol.org/roadmap in a browser."));
1017 1018 1019 1020
}

void MainWindow::showSettings()
{
Don Gagne's avatar
Don Gagne committed
1021 1022
    SettingsDialog settings(joystick, this);
    settings.exec();
1023 1024
}

1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037
bool MainWindow::configLink(LinkInterface *link)
{
    // Go searching for this link's configuration window
    QList<QAction*> actions = ui.menuNetwork->actions();

    bool found(false);

    const int32_t& linkIndex(LinkManager::instance()->getLinks().indexOf(link));
    const int32_t& linkID(LinkManager::instance()->getLinks()[linkIndex]->getId());

    foreach (QAction* action, actions)
    {
        if (action->data().toInt() == linkID)
1038
        {
1039 1040 1041 1042 1043 1044 1045 1046
            found = true;
            action->trigger(); // Show the Link Config Dialog
        }
    }

    return found;
}

1047
void MainWindow::simulateLink(bool simulate) {
Don Gagne's avatar
Don Gagne committed
1048 1049 1050 1051 1052 1053 1054 1055 1056 1057
    if (simulate) {
        if (!simulationLink) {
            simulationLink = new MAVLinkSimulationLink(":/demo-log.txt");
            Q_CHECK_PTR(simulationLink);
        }
        LinkManager::instance()->connectLink(simulationLink);
    } else {
        Q_ASSERT(simulationLink);
        LinkManager::instance()->disconnectLink(simulationLink);
    }
1058 1059
}

1060 1061
void MainWindow::commsWidgetDestroyed(QObject *obj)
{
1062 1063 1064
    // Do not dynamic cast or de-reference QObject, since object is either in destructor or may have already
    // been destroyed.

1065 1066 1067 1068 1069
    if (commsWidgetList.contains(obj))
    {
        commsWidgetList.removeOne(obj);
    }
}
1070 1071 1072

void MainWindow::setActiveUAS(UASInterface* uas)
{
1073
    Q_UNUSED(uas);
1074 1075
    if (settings.contains(getWindowStateKey()))
    {
1076
        restoreState(settings.value(getWindowStateKey()).toByteArray());
1077 1078
    }

1079 1080 1081 1082
}

void MainWindow::UASSpecsChanged(int uas)
{
1083 1084
    Q_UNUSED(uas);
    // TODO: Update UAS properties if its specs change
1085 1086 1087 1088
}

void MainWindow::UASCreated(UASInterface* uas)
{
1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 1120 1121 1122 1123 1124 1125 1126 1127 1128 1129 1130 1131 1132 1133 1134 1135 1136 1137 1138 1139 1140 1141 1142 1143 1144 1145 1146 1147 1148 1149 1150 1151 1152 1153 1154 1155 1156 1157
    // The UAS actions are not enabled without connection to system
    ui.actionLiftoff->setEnabled(true);
    ui.actionLand->setEnabled(true);
    ui.actionEmergency_Kill->setEnabled(true);
    ui.actionEmergency_Land->setEnabled(true);
    ui.actionShutdownMAV->setEnabled(true);

    QIcon icon;
    // Set matching icon
    switch (uas->getSystemType())
    {
    case MAV_TYPE_GENERIC:
        icon = QIcon(":files/images/mavs/generic.svg");
        break;
    case MAV_TYPE_FIXED_WING:
        icon = QIcon(":files/images/mavs/fixed-wing.svg");
        break;
    case MAV_TYPE_QUADROTOR:
        icon = QIcon(":files/images/mavs/quadrotor.svg");
        break;
    case MAV_TYPE_COAXIAL:
        icon = QIcon(":files/images/mavs/coaxial.svg");
        break;
    case MAV_TYPE_HELICOPTER:
        icon = QIcon(":files/images/mavs/helicopter.svg");
        break;
    case MAV_TYPE_ANTENNA_TRACKER:
        icon = QIcon(":files/images/mavs/antenna-tracker.svg");
        break;
    case MAV_TYPE_GCS:
        icon = QIcon(":files/images/mavs/groundstation.svg");
        break;
    case MAV_TYPE_AIRSHIP:
        icon = QIcon(":files/images/mavs/airship.svg");
        break;
    case MAV_TYPE_FREE_BALLOON:
        icon = QIcon(":files/images/mavs/free-balloon.svg");
        break;
    case MAV_TYPE_ROCKET:
        icon = QIcon(":files/images/mavs/rocket.svg");
        break;
    case MAV_TYPE_GROUND_ROVER:
        icon = QIcon(":files/images/mavs/ground-rover.svg");
        break;
    case MAV_TYPE_SURFACE_BOAT:
        icon = QIcon(":files/images/mavs/surface-boat.svg");
        break;
    case MAV_TYPE_SUBMARINE:
        icon = QIcon(":files/images/mavs/submarine.svg");
        break;
    case MAV_TYPE_HEXAROTOR:
        icon = QIcon(":files/images/mavs/hexarotor.svg");
        break;
    case MAV_TYPE_OCTOROTOR:
        icon = QIcon(":files/images/mavs/octorotor.svg");
        break;
    case MAV_TYPE_TRICOPTER:
        icon = QIcon(":files/images/mavs/tricopter.svg");
        break;
    case MAV_TYPE_FLAPPING_WING:
        icon = QIcon(":files/images/mavs/flapping-wing.svg");
        break;
    case MAV_TYPE_KITE:
        icon = QIcon(":files/images/mavs/kite.svg");
        break;
    default:
        icon = QIcon(":files/images/mavs/unknown.svg");
        break;
    }
1158

1159
    connect(uas, SIGNAL(systemSpecsChanged(int)), this, SLOT(UASSpecsChanged(int)));
John Tapsell's avatar
John Tapsell committed
1160
    connect(uas, SIGNAL(valueChanged(int,QString,QString,QVariant,quint64)), this, SIGNAL(valueChanged(int,QString,QString,QVariant,quint64)));
1161
    connect(uas, SIGNAL(misconfigurationDetected(UASInterface*)), this, SLOT(handleMisconfiguration(UASInterface*)));
1162

1163
    // HIL
1164
    _showHILConfigurationWidgets();
1165

1166 1167 1168
    if (!linechartWidget)
    {
        linechartWidget = new Linecharts(this);
1169
        linechartWidget->setVisible(false);
1170
    }
1171

1172
    linechartWidget->addSource(mavlinkDecoder);
1173
    if (_engineeringView != linechartWidget)
1174
    {
1175
        _engineeringView = linechartWidget;
1176
    }
1177 1178

    // Reload view state in case new widgets were added
1179
    _loadCurrentViewState();
1180 1181 1182 1183
}

void MainWindow::UASDeleted(UASInterface* uas)
{
1184
    Q_UNUSED(uas);
1185
    // TODO: Update the UI when a UAS is deleted
1186 1187
}

1188 1189
/// Stores the state of the toolbar, status bar and widgets associated with the current view
void MainWindow::_storeCurrentViewState(void)
1190
{
1191 1192
    // HIL dock widgets are dynamic and are not part of the saved state
    _hideAllHilDockWidgets();
1193

1194 1195 1196 1197 1198 1199 1200 1201 1202 1203 1204 1205
    // 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;
        }
1206
    }
1207

1208 1209
    settings.setValue(getWindowStateKey() + "WIDGETS", widgetNames);
    settings.setValue(getWindowStateKey(), saveState());
1210
    settings.setValue(getWindowGeometryKey(), saveGeometry());
1211 1212
}

1213 1214
/// Restores the state of the toolbar, status bar and widgets associated with the current view
void MainWindow::_loadCurrentViewState(void)
1215
{
Don Gagne's avatar
Don Gagne committed
1216
    QWidget* centerView = NULL;
1217
    QString defaultWidgets;
1218

1219
    switch (_currentView) {
1220
        case VIEW_SETUP:
1221 1222
            _buildSetupView();
            centerView = _setupView;
1223
            break;
1224

1225
        case VIEW_ENGINEER:
1226 1227 1228
            _buildEngineeringView();
            centerView = _engineeringView;
            defaultWidgets = "MAVLINK_INSPECTOR_DOCKWIDGET,PARAMETER_INTERFACE_DOCKWIDGET,FILE_VIEW_DOCKWIDGET,HEAD_UP_DISPLAY_DOCKWIDGET";
1229
            break;
1230

1231
        case VIEW_FLIGHT:
1232 1233 1234
            _buildPilotView();
            centerView = _pilotView;
            defaultWidgets = "COMMUNICATION_CONSOLE_DOCKWIDGET,UAS_INFO_INFOVIEW_DOCKWIDGET";
1235
            break;
1236

1237
        case VIEW_MISSION:
1238 1239 1240
            _buildPlannerView();
            centerView = _plannerView;
            defaultWidgets = "UNMANNED_SYSTEM_LIST_DOCKWIDGET,WAYPOINT_LIST_DOCKWIDGET";
1241
            break;
1242

1243
        case VIEW_SIMULATION:
1244 1245 1246
            _buildSimView();
            centerView = _simView;
            defaultWidgets = "UNMANNED_SYSTEM_CONTROL_DOCKWIDGET,WAYPOINT_LIST_DOCKWIDGET,PARAMETER_INTERFACE_DOCKWIDGET,PRIMARY_FLIGHT_DISPLAY_DOCKWIDGET";
1247
            break;
1248

1249
        case VIEW_TERMINAL:
1250 1251
            _buildTerminalView();
            centerView = _terminalView;
1252
            break;
1253

1254
        case VIEW_GOOGLEEARTH:
1255 1256
            _buildGoogleEarthView();
            centerView = _googleEarthView;
1257
            break;
1258

1259
        case VIEW_LOCAL3D:
1260 1261
            _buildLocal3DView();
            centerView = _local3DView;
1262
            break;
1263
    }
1264

1265 1266 1267 1268 1269 1270 1271 1272
    // Remove old view
    if (_currentViewWidget) {
        _currentViewWidget->setVisible(false);
        Q_ASSERT(_centralLayout->count() == 1);
        QLayoutItem *child = _centralLayout->takeAt(0);
        Q_ASSERT(child);
        delete child;
    }
1273

1274 1275 1276 1277 1278 1279
    // Add the new one
    Q_ASSERT(centerView);
    Q_ASSERT(_centralLayout->count() == 0);
    _currentViewWidget = centerView;
    _centralLayout->addWidget(_currentViewWidget);
    _currentViewWidget->setVisible(true);
1280

1281 1282 1283 1284 1285 1286 1287 1288 1289 1290
    // Hide all widgets from previous view
    _hideAllDockWidgets();

    // Restore the widgets for the new view
    QString widgetNames = settings.value(getWindowStateKey() + "WIDGETS", defaultWidgets).toString();
    if (!widgetNames.isEmpty()) {
        QStringList split = widgetNames.split(",");
        foreach (QString widgetName, split) {
            Q_ASSERT(!widgetName.isEmpty());
            _showDockWidget(widgetName, true);
1291 1292 1293
        }
    }

1294 1295
    if (settings.contains(getWindowStateKey())) {
        restoreState(settings.value(getWindowStateKey()).toByteArray());
1296
    }
1297

1298 1299 1300 1301 1302 1303 1304 1305 1306 1307 1308 1309 1310 1311 1312 1313
    // 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);
1314
    }
1315

1316
    _hideAllHilDockWidgets();
1317
}
1318 1319

void MainWindow::_showDockWidgetAction(bool show)
1320
{
1321 1322
    QAction* action = dynamic_cast<QAction*>(QObject::sender());
    Q_ASSERT(action);
1323

1324
    _showDockWidget(action->data().toString(), show);
1325
}
1326

1327

1328 1329 1330
void MainWindow::handleMisconfiguration(UASInterface* uas)
{
    static QTime lastTime;
1331

1332 1333 1334 1335 1336 1337 1338 1339 1340
    // We have to debounce this signal
    if (!lastTime.isValid()) {
        lastTime.start();
    } else {
        if (lastTime.elapsed() < 10000) {
            lastTime.start();
            return;
        }
    }
1341

1342
    // Ask user if he wants to handle this now
Don Gagne's avatar
Don Gagne committed
1343 1344 1345 1346 1347
    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);
    if (button == QMessageBox::Ok) {
1348 1349 1350 1351
        // He wants to handle it, make sure this system is selected
        UASManager::instance()->setActiveUAS(uas);

        // Flick to config view
1352
        loadSetupView();
1353 1354 1355
    }
}

1356 1357
void MainWindow::loadEngineerView()
{
1358
    if (_currentView != VIEW_ENGINEER)
1359
    {
1360 1361
        _storeCurrentViewState();
        _currentView = VIEW_ENGINEER;
1362
        ui.actionEngineersView->setChecked(true);
1363
        _loadCurrentViewState();
1364 1365 1366 1367 1368
    }
}

void MainWindow::loadOperatorView()
{
1369
    if (_currentView != VIEW_MISSION)
1370
    {
1371 1372
        _storeCurrentViewState();
        _currentView = VIEW_MISSION;
1373
        ui.actionMissionView->setChecked(true);
1374
        _loadCurrentViewState();
1375 1376
    }
}
1377
void MainWindow::loadSetupView()
1378
{
1379
    if (_currentView != VIEW_SETUP)
1380
    {
1381 1382
        _storeCurrentViewState();
        _currentView = VIEW_SETUP;
1383
        ui.actionSetup->setChecked(true);
1384
        _loadCurrentViewState();
1385 1386 1387
    }
}

1388 1389
void MainWindow::loadTerminalView()
{
1390
    if (_currentView != VIEW_TERMINAL)
1391
    {
1392 1393
        _storeCurrentViewState();
        _currentView = VIEW_TERMINAL;
1394
        ui.actionTerminalView->setChecked(true);
1395
        _loadCurrentViewState();
1396 1397 1398
    }
}

1399 1400
void MainWindow::loadGoogleEarthView()
{
1401
    if (_currentView != VIEW_GOOGLEEARTH)
1402
    {
1403 1404
        _storeCurrentViewState();
        _currentView = VIEW_GOOGLEEARTH;
1405
        ui.actionGoogleEarthView->setChecked(true);
1406
        _loadCurrentViewState();
1407 1408 1409 1410 1411
    }
}

void MainWindow::loadLocal3DView()
{
1412
    if (_currentView != VIEW_LOCAL3D)
1413
    {
1414 1415
        _storeCurrentViewState();
        _currentView = VIEW_LOCAL3D;
1416
        ui.actionLocal3DView->setChecked(true);
1417
        _loadCurrentViewState();
1418 1419
    }
}
1420

1421 1422
void MainWindow::loadPilotView()
{
1423
    if (_currentView != VIEW_FLIGHT)
1424
    {
1425 1426
        _storeCurrentViewState();
        _currentView = VIEW_FLIGHT;
1427
        ui.actionFlightView->setChecked(true);
1428
        _loadCurrentViewState();
1429 1430 1431
    }
}

1432 1433
void MainWindow::loadSimulationView()
{
1434
    if (_currentView != VIEW_SIMULATION)
1435
    {
1436 1437
        _storeCurrentViewState();
        _currentView = VIEW_SIMULATION;
1438
        ui.actionSimulationView->setChecked(true);
1439
        _loadCurrentViewState();
1440 1441 1442
    }
}

1443
QList<QAction*> MainWindow::listLinkMenuActions()
1444 1445 1446
{
    return ui.menuNetwork->actions();
}
1447

Don Gagne's avatar
Don Gagne committed
1448
/// @brief Hides the spash screen if it is currently being shown
1449
void MainWindow::hideSplashScreen(void)
Don Gagne's avatar
Don Gagne committed
1450 1451 1452 1453 1454 1455 1456
{
    if (_splashScreen) {
        _splashScreen->hide();
        _splashScreen = NULL;
    }
}

1457 1458 1459 1460 1461 1462 1463 1464 1465 1466 1467 1468 1469 1470 1471 1472 1473 1474 1475 1476 1477 1478 1479 1480 1481 1482 1483 1484 1485 1486 1487 1488 1489 1490 1491 1492
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
1493

1494
#ifdef QGC_MOUSE_ENABLED_LINUX
1495 1496 1497
bool MainWindow::x11Event(XEvent *event)
{
    emit x11EventOccured(event);
1498
    return false;
1499
}
1500
#endif // QGC_MOUSE_ENABLED_LINUX
1501 1502 1503 1504 1505 1506 1507 1508

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