MainWindow.cc 48.7 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
    // Load QML Toolbar
    QDockWidget* widget = new QDockWidget(this);
    widget->setObjectName("ToolBarDockWidget");
    qmlRegisterType<MainToolBar>("QGroundControl.MainToolBar", 1, 0, "MainToolBar");
174
    _mainToolBar = new MainToolBar(widget);
dogmaphobic's avatar
dogmaphobic committed
175 176 177 178
    widget->setWidget(_mainToolBar);
    widget->setFeatures(QDockWidget::NoDockWidgetFeatures);
    widget->setTitleBarWidget(new QWidget(this)); // Disables the title bar
    addDockWidget(Qt::TopDockWidgetArea, widget);
179

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

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

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

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

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

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

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

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

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

256 257
    // 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
258 259 260 261 262 263 264 265 266
    _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));
267
#else
dogmaphobic's avatar
dogmaphobic committed
268 269 270 271 272 273 274 275 276
    _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));
277 278
#endif

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

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

MainWindow::~MainWindow()
{
dogmaphobic's avatar
dogmaphobic committed
305
    if (_simulationLink)
306
    {
dogmaphobic's avatar
dogmaphobic committed
307 308
        delete _simulationLink;
        _simulationLink = NULL;
309
    }
310 311
    if (joystick)
    {
312 313
        joystick->shutdown();
        joystick->wait(5000);
314 315 316 317
        delete joystick;
        joystick = NULL;
    }
    // Delete all UAS objects
dogmaphobic's avatar
dogmaphobic committed
318
    for (int i=0;i<_commsWidgetList.size();i++)
319
    {
dogmaphobic's avatar
dogmaphobic committed
320
        _commsWidgetList[i]->deleteLater();
321
    }
Don Gagne's avatar
Don Gagne committed
322
    _instance = NULL;
323 324 325 326 327 328 329
}

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

dogmaphobic's avatar
dogmaphobic committed
330
QString MainWindow::_getWindowStateKey()
331
{
332 333
    if (UASManager::instance()->getActiveUAS())
    {
334
        return QString::number(_currentView)+"_windowstate_" + UASManager::instance()->getActiveUAS()->getAutopilotTypeName();
335 336
    }
    else
337
        return QString::number(_currentView)+"_windowstate_";
338 339
}

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

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

363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380
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
381
    _ui.menuTools->addAction(action);
382 383 384 385 386 387
    _mapName2DockWidget[name] = dockWidget;
    _mapDockWidget2Action[dockWidget] = action;
    addDockWidget(area, dockWidget);
}

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

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

400 401 402
    // 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.
403

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

410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427
    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]);
428

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

434 435
    _buildCustomWidgets();
}
436

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

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

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

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

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

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

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

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

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

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

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

522
    dockWidget->setVisible(show);
523

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

534
    QWidget* widget = NULL;
535

536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554
    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) {
555 556 557 558
        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");
559 560
        HDDisplay *hddisplay = new HDDisplay(acceptList,"Flight Display",this);
        hddisplay->addSource(mavlinkDecoder);
561

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

569 570 571 572 573 574 575 576 577 578 579
        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;
580
    }
581

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

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

594 595 596
    if (!uas) {
        return;
    }
597

598 599
    UAS* mav = dynamic_cast<UAS*>(uas);
    Q_ASSERT(mav);
600

601
    int uasId = mav->getUASID();
602

603
    if (!_mapUasId2HilDockWidget.contains(uasId)) {
604

605 606 607 608 609
        // 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);
610

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

614 615
        widget->setParent(dockWidget);
        dockWidget->setWidget(widget);
616

617
        _mapUasId2HilDockWidget[uasId] = dockWidget;
618

619 620
        addDockWidget(Qt::LeftDockWidgetArea, dockWidget);
    }
621

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

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

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

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

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

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

681
void MainWindow::_createNewCustomWidget(void)
682
{
683
    if (QGCToolWidget::instances()->isEmpty())
684 685
    {
        // This is the first widget
dogmaphobic's avatar
dogmaphobic committed
686
        _ui.menuTools->addSeparator();
687
    }
688 689 690 691 692 693 694 695
    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 );
696
    QGCToolWidget* tool = new QGCToolWidget(objectName, title);
697 698 699
    tool->resize(100, 100);
    _createDockWidget(title, objectName, Qt::BottomDockWidgetArea, tool);
    _mapName2DockWidget[objectName]->setVisible(true);
700 701
}

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

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

void MainWindow::loadSettings()
{
722
    // Why the screaming?
723
    QSettings settings;
724
    settings.beginGroup(MAIN_SETTINGS_GROUP);
dogmaphobic's avatar
dogmaphobic committed
725 726
    _autoReconnect = settings.value("AUTO_RECONNECT", _autoReconnect).toBool();
    _lowPowerMode  = settings.value("LOW_POWER_MODE", _lowPowerMode).toBool();
727
    settings.endGroup();
dogmaphobic's avatar
dogmaphobic committed
728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750
    // 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);
751 752 753 754 755
}

void MainWindow::storeSettings()
{
    QSettings settings;
756
    settings.beginGroup(MAIN_SETTINGS_GROUP);
dogmaphobic's avatar
dogmaphobic committed
757 758
    settings.setValue("AUTO_RECONNECT", _autoReconnect);
    settings.setValue("LOW_POWER_MODE", _lowPowerMode);
759
    settings.endGroup();
dogmaphobic's avatar
dogmaphobic committed
760
    settings.setValue(_getWindowGeometryKey(), saveGeometry());
761
    // Save the last current view in any case
762
    settings.setValue("CURRENT_VIEW", _currentView);
763
    // 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
764
    if (UASManager::instance()->getUASList().length() > 0) settings.setValue(_getWindowStateKey(), saveState());
765
    // Save the current UAS view if a UAS is connected
766
    if (UASManager::instance()->getUASList().length() > 0) settings.setValue("CURRENT_VIEW_WITH_UAS_CONNECTED", _currentView);
767
    // And save any custom weidgets
768
    QGCToolWidget::storeWidgetsToSettings(settings);
769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790
}

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

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

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

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

/**
* @brief Create all actions associated to the main window
*
**/
void MainWindow::connectCommonActions()
{
    // Bind together the perspective actions
dogmaphobic's avatar
dogmaphobic committed
838 839 840 841 842 843 844 845 846
    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);
847 848
    perspectives->setExclusive(true);

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

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

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

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

    // 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
914 915 916 917 918
    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()));
919 920

    // Views actions
dogmaphobic's avatar
dogmaphobic committed
921 922 923 924 925 926 927 928
    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()));
929 930

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1041
    // HIL
1042
    _showHILConfigurationWidgets();
1043

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

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

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

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

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

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

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

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

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

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

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

1124
        case VIEW_TERMINAL:
1125 1126
            _buildTerminalView();
            centerView = _terminalView;
1127
            break;
1128

1129
        case VIEW_GOOGLEEARTH:
1130 1131
            _buildGoogleEarthView();
            centerView = _googleEarthView;
1132
            break;
1133

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

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

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

1156 1157 1158 1159
    // Hide all widgets from previous view
    _hideAllDockWidgets();

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

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

1173 1174 1175 1176 1177 1178 1179 1180 1181 1182 1183 1184 1185 1186 1187 1188
    // 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);
1189
    }
1190
    _hideAllHilDockWidgets();
1191
}
1192 1193

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

1200

1201 1202 1203 1204 1205 1206 1207 1208 1209 1210 1211 1212
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
1213 1214 1215 1216 1217 1218 1219
    // 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
1220
    if (button == QMessageBox::Ok) {
dogmaphobic's avatar
dogmaphobic committed
1221
        // They want to handle it, make sure this system is selected
1222 1223
        UASManager::instance()->setActiveUAS(uas);
        // Flick to config view
1224
        loadSetupView();
1225 1226 1227
    }
}

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

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

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

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

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

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

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

Don Gagne's avatar
Don Gagne committed
1315
/// @brief Hides the spash screen if it is currently being shown
1316
void MainWindow::hideSplashScreen(void)
Don Gagne's avatar
Don Gagne committed
1317 1318 1319 1320 1321 1322 1323
{
    if (_splashScreen) {
        _splashScreen->hide();
        _splashScreen = NULL;
    }
}

1324 1325 1326 1327 1328 1329 1330 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
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
1360

1361
#ifdef QGC_MOUSE_ENABLED_LINUX
1362 1363 1364
bool MainWindow::x11Event(XEvent *event)
{
    emit x11EventOccured(event);
1365
    return false;
1366
}
1367
#endif // QGC_MOUSE_ENABLED_LINUX
1368 1369 1370 1371 1372 1373 1374 1375

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

1376 1377 1378 1379 1380 1381 1382 1383 1384 1385 1386 1387 1388 1389 1390 1391 1392 1393
// There is a bug in Qt where a Canvas element inside a QQuickWidget does not
// receive update requests. We hook into this event and notify the tool bar
// to update its canvas elements. If other QQuickWidgets start using Canvas
// and this bug is not fixed, this should be turned into a signal emited by
// MainWindow and the various QQuickWidgets that need it should connect to it.
bool MainWindow::event(QEvent* e)
{
    bool result = true;
    switch (e->type()) {
        case QEvent::Paint:
            result = QMainWindow::event(e);
            _mainToolBar->updateCanvas();
            return result;
        default:
            break;
    }
    return QMainWindow::event(e);
}