MainWindow.cc 50 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
/*=====================================================================

QGroundControl Open Source Ground Control Station

(c) 2009 - 2011 QGROUNDCONTROL PROJECT <http://www.qgroundcontrol.org>

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/>.

pixhawk's avatar
pixhawk committed
22 23 24 25
======================================================================*/

/**
 * @file
26
 *   @brief Implementation of class MainWindow
27
 *   @author Lorenz Meier <mail@qgroundcontrol.org>
pixhawk's avatar
pixhawk committed
28 29 30 31 32 33 34 35 36
 */

#include <QSettings>
#include <QDockWidget>
#include <QNetworkInterface>
#include <QMessageBox>
#include <QDebug>
#include <QTimer>
#include <QHostInfo>
lm's avatar
lm committed
37
#include <QSplashScreen>
pixhawk's avatar
pixhawk committed
38

39
#include "QGC.h"
pixhawk's avatar
pixhawk committed
40 41 42 43 44
#include "MAVLinkSimulationLink.h"
#include "SerialLink.h"
#include "UDPLink.h"
#include "MAVLinkProtocol.h"
#include "CommConfigurationWindow.h"
45
#include "QGCWaypointListMulti.h"
pixhawk's avatar
pixhawk committed
46 47
#include "MainWindow.h"
#include "JoystickWidget.h"
pixhawk's avatar
pixhawk committed
48
#include "GAudioOutput.h"
49
#include "QGCToolWidget.h"
50
#include "QGCMAVLinkLogPlayer.h"
51
#include "QGCSettingsWidget.h"
52
#include "QGCMapTool.h"
LM's avatar
LM committed
53
#include "MAVLinkDecoder.h"
54

55
#ifdef QGC_OSG_ENABLED
56
#include "Q3DWidgetFactory.h"
57
#endif
pixhawk's avatar
pixhawk committed
58

lm's avatar
lm committed
59 60 61 62
// FIXME Move
#include "PxQuadMAV.h"
#include "SlugsMAV.h"

pixhawk's avatar
pixhawk committed
63

64
#include "LogCompressor.h"
pixhawk's avatar
pixhawk committed
65

lm's avatar
lm committed
66
MainWindow* MainWindow::instance(QSplashScreen* screen)
67
{
68
    static MainWindow* _instance = 0;
69 70
    if(_instance == 0)
    {
71
        _instance = new MainWindow();
lm's avatar
lm committed
72
        if (screen) connect(_instance, SIGNAL(initStatusChanged(QString)), screen, SLOT(showMessage(QString)));
73

74
        /* Set the application as parent to ensure that this object
75
                 * will be destroyed when the main application exits */
76 77 78
        //_instance->setParent(qApp);
    }
    return _instance;
79 80
}

pixhawk's avatar
pixhawk committed
81 82 83 84 85 86 87
/**
* Create new mainwindow. The constructor instantiates all parts of the user
* interface. It does NOT show the mainwindow. To display it, call the show()
* method.
*
* @see QMainWindow::show()
**/
88
MainWindow::MainWindow(QWidget *parent):
89 90 91 92 93 94
    QMainWindow(parent),
    currentView(VIEW_UNCONNECTED),
    aboutToCloseFlag(false),
    changingViewsFlag(false),
    styleFileName(QCoreApplication::applicationDirPath() + "/style-indoor.css"),
    autoReconnect(false),
95
    currentStyle(QGC_MAINWINDOW_STYLE_INDOOR),
96 97
    centerStackActionGroup(this),
    lowPowerMode(false)
pixhawk's avatar
pixhawk committed
98
{
lm's avatar
lm committed
99 100
    hide();
    emit initStatusChanged("Loading UI Settings..");
101
    loadSettings();
102 103
    if (!settings.contains("CURRENT_VIEW"))
    {
104 105
        // Set this view as default view
        settings.setValue("CURRENT_VIEW", currentView);
106 107 108
    }
    else
    {
109 110 111
        // LOAD THE LAST VIEW
        VIEW_SECTIONS currentViewCandidate = (VIEW_SECTIONS) settings.value("CURRENT_VIEW", currentView).toInt();
        if (currentViewCandidate != VIEW_ENGINEER &&
112
                currentViewCandidate != VIEW_OPERATOR &&
113 114 115
                currentViewCandidate != VIEW_PILOT &&
                currentViewCandidate != VIEW_FULL)
        {
116
            currentView = currentViewCandidate;
117
        }
118 119
    }

120 121
    settings.sync();

lm's avatar
lm committed
122
    emit initStatusChanged("Loading Style.");
123
    loadStyle(currentStyle);
124

lm's avatar
lm committed
125 126
    emit initStatusChanged("Setting up user interface.");

pixhawk's avatar
pixhawk committed
127 128
    // Setup user interface
    ui.setupUi(this);
lm's avatar
lm committed
129
    hide();
pixhawk's avatar
pixhawk committed
130

131 132 133 134 135 136
    // Set dock options
    setDockOptions(AnimatedDocks | AllowTabbedDocks | AllowNestedDocks);
    statusBar()->setSizeGripEnabled(true);

    configureWindowName();

137 138 139
    // Setup corners
    setCorner(Qt::BottomRightCorner, Qt::RightDockWidgetArea);

140 141 142
    // Setup UI state machines
    centerStackActionGroup.setExclusive(true);

143 144
    centerStack = new QStackedWidget(this);
    setCentralWidget(centerStack);
145

lm's avatar
lm committed
146 147 148 149 150 151 152 153
    // Load Toolbar
    toolBar = new QGCToolBar(this);
    this->addToolBar(toolBar);
    // Add actions
    toolBar->addPerspectiveChangeAction(ui.actionOperatorsView);
    toolBar->addPerspectiveChangeAction(ui.actionEngineersView);
    toolBar->addPerspectiveChangeAction(ui.actionPilotsView);

lm's avatar
lm committed
154 155
    emit initStatusChanged("Building common widgets.");

156 157
    buildCommonWidgets();
    connectCommonWidgets();
158

lm's avatar
lm committed
159 160
    emit initStatusChanged("Building common actions.");

161
    // Create actions
162
    connectCommonActions();
163

164
    // Populate link menu
lm's avatar
lm committed
165
    emit initStatusChanged("Populating link menu");
166
    QList<LinkInterface*> links = LinkManager::instance()->getLinks();
167 168
    foreach(LinkInterface* link, links)
    {
169 170
        this->addLink(link);
    }
171

172 173
    connect(LinkManager::instance(), SIGNAL(newLink(LinkInterface*)), this, SLOT(addLink(LinkInterface*)));

174
    // Connect user interface devices
lm's avatar
lm committed
175
    emit initStatusChanged("Initializing joystick interface.");
176 177
    joystickWidget = 0;
    joystick = new JoystickInput();
178

179
    // Connect link
180 181
    if (autoReconnect)
    {
182 183 184 185 186 187
        SerialLink* link = new SerialLink();
        // Add to registry
        LinkManager::instance()->add(link);
        LinkManager::instance()->addProtocol(link, mavlink);
        link->connect();
    }
188

189 190 191
    // Set low power mode
    enableLowPowerMode(lowPowerMode);

192 193
    // Initialize window state
    windowStateVal = windowState();
194

lm's avatar
lm committed
195 196
    emit initStatusChanged("Restoring last view state.");

197 198 199
    // Restore the window setup
    loadViewState();

lm's avatar
lm committed
200
    emit initStatusChanged("Restoring last window size.");
201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224
    // Restore the window position and size
    if (settings.contains(getWindowGeometryKey()))
    {
        // Restore the window geometry
        restoreGeometry(settings.value(getWindowGeometryKey()).toByteArray());
        show();
    }
    else
    {
        // Adjust the size
        const int screenWidth = QApplication::desktop()->width();
        const int screenHeight = QApplication::desktop()->height();

        if (screenWidth < 1200)
        {
            showFullScreen();
        }
        else
        {
            resize(screenWidth*0.67f, qMin(screenHeight, (int)(screenWidth*0.67f*0.67f)));
            show();
        }

    }
225 226 227

    connect(&windowNameUpdateTimer, SIGNAL(timeout()), this, SLOT(configureWindowName()));
    windowNameUpdateTimer.start(15000);
lm's avatar
lm committed
228 229
    emit initStatusChanged("Done.");
    show();
pixhawk's avatar
pixhawk committed
230 231
}

pixhawk's avatar
pixhawk committed
232
MainWindow::~MainWindow()
pixhawk's avatar
pixhawk committed
233
{
234
    delete mavlink;
235
    delete joystick;
lm's avatar
lm committed
236

237 238 239 240 241 242
    // Get and delete all dockwidgets and contained
    // widgets
    QObjectList childList( this->children() );

    QObjectList::iterator i;
    QDockWidget* dockWidget;
243 244
    for (i = childList.begin(); i != childList.end(); ++i)
    {
245
        dockWidget = dynamic_cast<QDockWidget*>(*i);
246 247
        if (dockWidget)
        {
248 249 250 251 252
            // Remove dock widget from main window
            removeDockWidget(dockWidget);
            delete dockWidget->widget();
            delete dockWidget;
        }
LM's avatar
LM committed
253 254 255 256
        else
        {
            delete dynamic_cast<QObject*>(*i);
        }
257 258
    }

LM's avatar
LM committed
259
    // Delete all UAS objects
260 261
}

lm's avatar
lm committed
262 263
void MainWindow::resizeEvent(QResizeEvent * event)
{
264 265
    if (height() < 800)
    {
lm's avatar
lm committed
266
        ui.statusBar->setVisible(false);
267 268 269
    }
    else
    {
lm's avatar
lm committed
270
        ui.statusBar->setVisible(true);
271
        ui.statusBar->setSizeGripEnabled(true);
lm's avatar
lm committed
272
    }
273 274 275 276 277 278 279 280 281 282 283

    if (width() > 1200)
    {
        toolBar->setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
    }
    else
    {
        toolBar->setToolButtonStyle(Qt::ToolButtonIconOnly);
    }

    QMainWindow::resizeEvent(event);
lm's avatar
lm committed
284 285
}

286 287
QString MainWindow::getWindowStateKey()
{
288
    return QString::number(currentView)+"_windowstate";
289 290 291 292
}

QString MainWindow::getWindowGeometryKey()
{
293 294
    //return QString::number(currentView)+"_geometry";
    return "_geometry";
295 296
}

lm's avatar
lm committed
297 298
void MainWindow::buildCustomWidget()
{
lm's avatar
lm committed
299 300
    // Create custom widgets
    QList<QGCToolWidget*> widgets = QGCToolWidget::createWidgetsFromSettings(this);
lm's avatar
lm committed
301

lm's avatar
lm committed
302 303 304 305
    if (widgets.size() > 0)
    {
        ui.menuTools->addSeparator();
    }
lm's avatar
lm committed
306

lm's avatar
lm committed
307 308 309 310 311 312
    for(int i = 0; i < widgets.size(); ++i)
    {
        // Check if this widget already has a parent, do not create it in this case
        QGCToolWidget* tool = widgets.at(i);
        QDockWidget* dock = dynamic_cast<QDockWidget*>(tool->parentWidget());
        if (!dock)
313
        {
lm's avatar
lm committed
314 315 316 317 318 319 320 321 322 323 324 325 326 327 328
            QDockWidget* dock = new QDockWidget(tool->windowTitle(), this);
            dock->setObjectName(tool->objectName()+"_DOCK");
            dock->setWidget(tool);
            connect(tool, SIGNAL(destroyed()), dock, SLOT(deleteLater()));
            QAction* showAction = new QAction(widgets.at(i)->windowTitle(), this);
            showAction->setCheckable(true);
            connect(showAction, SIGNAL(triggered(bool)), dock, SLOT(setVisible(bool)));
            connect(dock, SIGNAL(visibilityChanged(bool)), showAction, SLOT(setChecked(bool)));
            widgets.at(i)->setMainMenuAction(showAction);
            ui.menuTools->addAction(showAction);

            // Load dock widget location (default is bottom)
            Qt::DockWidgetArea location = static_cast <Qt::DockWidgetArea>(tool->getDockWidgetArea(currentView));

            addDockWidget(location, dock);
lm's avatar
lm committed
329 330 331 332
        }
    }
}

333 334 335 336
void MainWindow::buildCommonWidgets()
{
    //TODO:  move protocol outside UI
    mavlink     = new MAVLinkProtocol();
337
    connect(mavlink, SIGNAL(protocolStatusMessage(QString,QString)), this, SLOT(showCriticalMessage(QString,QString)), Qt::QueuedConnection);
LM's avatar
LM committed
338 339
    // Add generic MAVLink decoder
    mavlinkDecoder = new MAVLinkDecoder(mavlink, this);
340 341

    // Dock widgets
342 343
    if (!controlDockWidget)
    {
344
        controlDockWidget = new QDockWidget(tr("Control"), this);
345
        controlDockWidget->setObjectName("UNMANNED_SYSTEM_CONTROL_DOCKWIDGET");
346
        controlDockWidget->setWidget( new UASControlWidget(this) );
347
        addTool(controlDockWidget, tr("Control"), Qt::LeftDockWidgetArea);
348
    }
349

350 351
    if (!listDockWidget)
    {
352 353
        listDockWidget = new QDockWidget(tr("Unmanned Systems"), this);
        listDockWidget->setWidget( new UASListWidget(this) );
354
        listDockWidget->setObjectName("UNMANNED_SYSTEMS_LIST_DOCKWIDGET");
355
        addTool(listDockWidget, tr("Unmanned Systems"), Qt::RightDockWidgetArea);
356
    }
357

358 359
    if (!waypointsDockWidget)
    {
360
        waypointsDockWidget = new QDockWidget(tr("Mission Plan"), this);
361
        waypointsDockWidget->setWidget( new QGCWaypointListMulti(this) );
362
        waypointsDockWidget->setObjectName("WAYPOINT_LIST_DOCKWIDGET");
363
        addTool(waypointsDockWidget, tr("Mission Plan"), Qt::BottomDockWidgetArea);
364
    }
365

366 367
    if (!infoDockWidget)
    {
368 369
        infoDockWidget = new QDockWidget(tr("Status Details"), this);
        infoDockWidget->setWidget( new UASInfoWidget(this) );
pixhawk's avatar
pixhawk committed
370
        infoDockWidget->setObjectName("UAS_STATUS_DETAILS_DOCKWIDGET");
371
        addTool(infoDockWidget, tr("Status Details"), Qt::RightDockWidgetArea);
372
    }
373

374 375
    if (!debugConsoleDockWidget)
    {
376 377
        debugConsoleDockWidget = new QDockWidget(tr("Communication Console"), this);
        debugConsoleDockWidget->setWidget( new DebugConsole(this) );
378
        debugConsoleDockWidget->setObjectName("COMMUNICATION_DEBUG_CONSOLE_DOCKWIDGET");
LM's avatar
LM committed
379 380 381 382

        DebugConsole *debugConsole = dynamic_cast<DebugConsole*>(debugConsoleDockWidget->widget());
        connect(mavlinkDecoder, SIGNAL(textMessageReceived(int, int, int, const QString)), debugConsole, SLOT(receiveTextMessage(int, int, int, const QString)));

383
        addTool(debugConsoleDockWidget, tr("Communication Console"), Qt::BottomDockWidgetArea);
384
    }
385

386 387
    if (!logPlayerDockWidget)
    {
388
        logPlayerDockWidget = new QDockWidget(tr("MAVLink Log Player"), this);
lm's avatar
lm committed
389 390 391
        logPlayer = new QGCMAVLinkLogPlayer(mavlink, this);
        toolBar->setLogPlayer(logPlayer);
        logPlayerDockWidget->setWidget(logPlayer);
392
        logPlayerDockWidget->setObjectName("MAVLINK_LOG_PLAYER_DOCKWIDGET");
393
        addTool(logPlayerDockWidget, tr("MAVLink Log Replay"), Qt::RightDockWidgetArea);
394 395
    }

lm's avatar
lm committed
396 397 398 399 400
    if (!mavlinkInspectorWidget)
    {
        mavlinkInspectorWidget = new QDockWidget(tr("MAVLink Message Inspector"), this);
        mavlinkInspectorWidget->setWidget( new QGCMAVLinkInspector(mavlink, this) );
        mavlinkInspectorWidget->setObjectName("MAVLINK_INSPECTOR_DOCKWIDGET");
lm's avatar
lm committed
401
        addTool(mavlinkInspectorWidget, tr("MAVLink Inspector"), Qt::RightDockWidgetArea);
lm's avatar
lm committed
402 403
    }

pixhawk's avatar
pixhawk committed
404 405
    //FIXME: memory of acceptList will never be freed again
    QStringList* acceptList = new QStringList();
pixhawk's avatar
pixhawk committed
406 407 408
    acceptList->append("-105,roll deg,deg,+105,s");
    acceptList->append("-105,pitch deg,deg,+105,s");
    acceptList->append("-105,heading deg,deg,+105,s");
409

pixhawk's avatar
pixhawk committed
410 411
    //FIXME: memory of acceptList2 will never be freed again
    QStringList* acceptList2 = new QStringList();
lm's avatar
lm committed
412
    acceptList2->append("0,abs pressure,hPa,65500");
413

lm's avatar
lm committed
414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493
    if (!parametersDockWidget) {
        parametersDockWidget = new QDockWidget(tr("Calibration and Onboard Parameters"), this);
        parametersDockWidget->setWidget( new ParameterInterface(this) );
        parametersDockWidget->setObjectName("PARAMETER_INTERFACE_DOCKWIDGET");
        addTool(parametersDockWidget, tr("Calibration and Parameters"), Qt::RightDockWidgetArea);
    }

    if (!hsiDockWidget) {
        hsiDockWidget = new QDockWidget(tr("Horizontal Situation Indicator"), this);
        hsiDockWidget->setWidget( new HSIDisplay(this) );
        hsiDockWidget->setObjectName("HORIZONTAL_SITUATION_INDICATOR_DOCK_WIDGET");
        addTool(hsiDockWidget, tr("Horizontal Situation"), Qt::BottomDockWidgetArea);
    }

    if (!headDown1DockWidget) {
        headDown1DockWidget = new QDockWidget(tr("Flight Display"), this);
        headDown1DockWidget->setWidget( new HDDisplay(acceptList, "Flight Display", this) );
        headDown1DockWidget->setObjectName("HEAD_DOWN_DISPLAY_1_DOCK_WIDGET");
        addTool(headDown1DockWidget, tr("Flight Display"), Qt::RightDockWidgetArea);
    }

    if (!headDown2DockWidget) {
        headDown2DockWidget = new QDockWidget(tr("Actuator Status"), this);
        headDown2DockWidget->setWidget( new HDDisplay(acceptList2, "Actuator Status", this) );
        headDown2DockWidget->setObjectName("HEAD_DOWN_DISPLAY_2_DOCK_WIDGET");
        addTool(headDown2DockWidget, tr("Actuator Status"), Qt::RightDockWidgetArea);
    }

    if (!rcViewDockWidget) {
        rcViewDockWidget = new QDockWidget(tr("Radio Control"), this);
        rcViewDockWidget->setWidget( new QGCRemoteControlView(this) );
        rcViewDockWidget->setObjectName("RADIO_CONTROL_CHANNELS_DOCK_WIDGET");
        addTool(rcViewDockWidget, tr("Radio Control"), Qt::BottomDockWidgetArea);
    }

    if (!headUpDockWidget) {
        headUpDockWidget = new QDockWidget(tr("HUD"), this);
        headUpDockWidget->setWidget( new HUD(320, 240, this));
        headUpDockWidget->setObjectName("HEAD_UP_DISPLAY_DOCK_WIDGET");
        addTool(headUpDockWidget, tr("Head Up Display"), Qt::RightDockWidgetArea);
    }

    if (!video1DockWidget) {
        video1DockWidget = new QDockWidget(tr("Video Stream 1"), this);
        HUD* video1 =  new HUD(160, 120, this);
        video1->enableHUDInstruments(false);
        video1->enableVideo(true);
        // FIXME select video stream as well
        video1DockWidget->setWidget(video1);
        video1DockWidget->setObjectName("VIDEO_STREAM_1_DOCK_WIDGET");
        addTool(video1DockWidget, tr("Video Stream 1"), Qt::LeftDockWidgetArea);
    }

    if (!video2DockWidget) {
        video2DockWidget = new QDockWidget(tr("Video Stream 2"), this);
        HUD* video2 =  new HUD(160, 120, this);
        video2->enableHUDInstruments(false);
        video2->enableVideo(true);
        // FIXME select video stream as well
        video2DockWidget->setWidget(video2);
        video2DockWidget->setObjectName("VIDEO_STREAM_2_DOCK_WIDGET");
        addTool(video2DockWidget, tr("Video Stream 2"), Qt::LeftDockWidgetArea);
    }

    // Custom widgets, added last to all menus and layouts
    buildCustomWidget();

    // Center widgets
    if (!mapWidget)
    {
        mapWidget = new QGCMapTool(this);
        addCentralWidget(mapWidget, "Maps");
    }

    if (!protocolWidget)
    {
        protocolWidget    = new XMLCommProtocolWidget(this);
        addCentralWidget(protocolWidget, "Mavlink Generator");
    }

494
    if (!hudWidget) {
495
        hudWidget         = new HUD(320, 240, this);
496
        addCentralWidget(hudWidget, tr("Head Up Display"));
lm's avatar
lm committed
497 498
    }

499
    if (!dataplotWidget) {
500
        dataplotWidget    = new QGCDataPlot2D(this);
501
        addCentralWidget(dataplotWidget, tr("Logfile Plot"));
502
    }
503

504
#ifdef QGC_OSG_ENABLED
505
    if (!_3DWidget) {
506
        _3DWidget         = Q3DWidgetFactory::get("PIXHAWK");
507
        addCentralWidget(_3DWidget, tr("Local 3D"));
508
    }
509
#endif
510

511
#if (defined _MSC_VER) | (defined Q_OS_MAC)
512
    if (!gEarthWidget) {
513
        gEarthWidget = new QGCGoogleEarthView(this);
514
        addCentralWidget(gEarthWidget, tr("Google Earth"));
515
    }
516
#endif
lm's avatar
lm committed
517
}
518

519
void MainWindow::addTool(QDockWidget* widget, const QString& title, Qt::DockWidgetArea area)
lm's avatar
lm committed
520
{
521
    QAction* tempAction = ui.menuTools->addAction(title);
522

523
    tempAction->setCheckable(true);
524 525 526 527
    QVariant var;
    var.setValue((QWidget*)widget);
    tempAction->setData(var);
    connect(tempAction,SIGNAL(triggered(bool)),this, SLOT(showTool(bool)));
LM's avatar
LM committed
528 529
    connect(widget, SIGNAL(visibilityChanged(bool)), tempAction, SLOT(setChecked(bool)));
    tempAction->setChecked(widget->isVisible());
530
    addDockWidget(area, widget);
531 532 533
}


534
void MainWindow::showTool(bool show)
lm's avatar
lm committed
535
{
536 537 538
    QAction* act = qobject_cast<QAction *>(sender());
    QWidget* widget = qVariantValue<QWidget *>(act->data());
    widget->setVisible(show);
539
}
540

541
void MainWindow::addCentralWidget(QWidget* widget, const QString& title)
lm's avatar
lm committed
542
{
543 544 545 546
    // Check if this widget already has been added
    if (centerStack->indexOf(widget) == -1)
    {
        centerStack->addWidget(widget);
547

548
        QAction* tempAction = ui.menuMain->addAction(title);
549

550 551 552 553 554 555
        tempAction->setCheckable(true);
        QVariant var;
        var.setValue((QWidget*)widget);
        tempAction->setData(var);
        centerStackActionGroup.addAction(tempAction);
        connect(tempAction,SIGNAL(triggered()),this, SLOT(showCentralWidget()));
LM's avatar
LM committed
556 557
        connect(widget, SIGNAL(visibilityChanged(bool)), tempAction, SLOT(setChecked(bool)));
        tempAction->setChecked(widget->isVisible());
558 559 560 561
    }
}


562
void MainWindow::showCentralWidget()
563
{
564 565 566
    QAction* act = qobject_cast<QAction *>(sender());
    QWidget* widget = qVariantValue<QWidget *>(act->data());
    centerStack->setCurrentWidget(widget);
567 568
}

569 570
void MainWindow::closeEvent(QCloseEvent *event)
{
pixhawk's avatar
pixhawk committed
571
    if (isVisible()) storeViewState();
572
    storeSettings();
573
    aboutToCloseFlag = true;
574
    mavlink->storeSettings();
575
    UASManager::instance()->storeSettings();
576 577
    QMainWindow::closeEvent(event);
}
578 579 580 581

/**
 * Connect the signals and slots of the common window widgets
 */
582
void MainWindow::connectCommonWidgets()
583
{
584
    if (infoDockWidget && infoDockWidget->widget()) {
pixhawk's avatar
pixhawk committed
585 586 587
        connect(mavlink, SIGNAL(receiveLossChanged(int, float)),
                infoDockWidget->widget(), SLOT(updateSendLoss(int, float)));
    }
588
}
589

590 591
void MainWindow::createCustomWidget()
{
592 593
    QDockWidget* dock = new QDockWidget("Unnamed Tool", this);
    QGCToolWidget* tool = new QGCToolWidget("Unnamed Tool", dock);
lm's avatar
lm committed
594

LM's avatar
LM committed
595 596
    if (QGCToolWidget::instances()->size() < 2)
    {
lm's avatar
lm committed
597 598 599 600 601
        // This is the first widget
        ui.menuTools->addSeparator();
    }

    connect(tool, SIGNAL(destroyed()), dock, SLOT(deleteLater()));
602
    dock->setWidget(tool);
603

604 605 606 607 608 609 610 611 612 613 614 615 616 617
    QAction* showAction = new QAction(tool->getTitle(), this);
    showAction->setCheckable(true);
    connect(dock, SIGNAL(visibilityChanged(bool)), showAction, SLOT(setChecked(bool)));
    connect(showAction, SIGNAL(triggered(bool)), dock, SLOT(setVisible(bool)));
    tool->setMainMenuAction(showAction);
    ui.menuTools->addAction(showAction);
    this->addDockWidget(Qt::BottomDockWidgetArea, dock);
    dock->setVisible(true);
}

void MainWindow::loadCustomWidget()
{
    QString widgetFileExtension(".qgw");
    QString fileName = QFileDialog::getOpenFileName(this, tr("Specify Widget File Name"), QDesktopServices::storageLocation(QDesktopServices::DesktopLocation), tr("QGroundControl Widget (*%1);;").arg(widgetFileExtension));
618
    if (fileName != "") loadCustomWidget(fileName);
LM's avatar
LM committed
619
}
620

LM's avatar
LM committed
621 622 623 624
void MainWindow::loadCustomWidget(const QString& fileName, bool singleinstance)
{
    QGCToolWidget* tool = new QGCToolWidget("", this);
    if (tool->loadSettings(fileName, true) || !singleinstance)
LM's avatar
LM committed
625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640
    {
        // Add widget to UI
        QDockWidget* dock = new QDockWidget(tool->getTitle(), this);
        connect(tool, SIGNAL(destroyed()), dock, SLOT(deleteLater()));
        dock->setWidget(tool);
        tool->setParent(dock);

        QAction* showAction = new QAction(tool->getTitle(), this);
        showAction->setCheckable(true);
        connect(dock, SIGNAL(visibilityChanged(bool)), showAction, SLOT(setChecked(bool)));
        connect(showAction, SIGNAL(triggered(bool)), dock, SLOT(setVisible(bool)));
        tool->setMainMenuAction(showAction);
        ui.menuTools->addAction(showAction);
        this->addDockWidget(Qt::BottomDockWidgetArea, dock);
        dock->setVisible(true);
    }
LM's avatar
LM committed
641 642 643
    else
    {
        return;
644
    }
645 646
}

LM's avatar
LM committed
647
void MainWindow::loadCustomWidgetsFromDefaults(const QString& systemType, const QString& autopilotType)
648
{
649
    QString defaultsDir = qApp->applicationDirPath() + "/files/" + autopilotType.toLower() + "/" + systemType.toLower() + "/widgets/";
650

LM's avatar
LM committed
651 652 653 654 655 656
    QDir widgets(defaultsDir);
    QStringList files = widgets.entryList();
    if (files.count() == 0)
    {
        qDebug() << "No default custom widgets for system " << systemType << "autopilot" << autopilotType << " found";
        qDebug() << "Tried with path: " << defaultsDir;
lm's avatar
lm committed
657
        showStatusMessage(tr("Did not find any custom widgets in %1").arg(defaultsDir));
658
    }
659

LM's avatar
LM committed
660 661
    // Load all custom widgets found in the AP folder
    for(int i = 0; i < files.count(); ++i)
lm's avatar
lm committed
662
    {
LM's avatar
LM committed
663 664 665 666 667
        QString file = files[i];
        if (file.endsWith(".qgw"))
        {
            // Will only be loaded if not already a custom widget with
            // the same name is present
668
            loadCustomWidget(defaultsDir+"/"+file, true);
lm's avatar
lm committed
669
            showStatusMessage(tr("Loaded custom widget %1").arg(defaultsDir+"/"+file));
LM's avatar
LM committed
670
        }
lm's avatar
lm committed
671
    }
672 673
}

674 675 676 677 678 679
void MainWindow::loadSettings()
{
    QSettings settings;
    settings.beginGroup("QGC_MAINWINDOW");
    autoReconnect = settings.value("AUTO_RECONNECT", autoReconnect).toBool();
    currentStyle = (QGC_MAINWINDOW_STYLE)settings.value("CURRENT_STYLE", currentStyle).toInt();
680
    lowPowerMode = settings.value("LOW_POWER_MODE", lowPowerMode).toBool();
681 682 683 684 685 686 687 688 689 690
    settings.endGroup();
}

void MainWindow::storeSettings()
{
    QSettings settings;
    settings.beginGroup("QGC_MAINWINDOW");
    settings.setValue("AUTO_RECONNECT", autoReconnect);
    settings.setValue("CURRENT_STYLE", currentStyle);
    settings.endGroup();
pixhawk's avatar
pixhawk committed
691 692 693 694 695 696 697 698 699 700 701
    if (!aboutToCloseFlag && isVisible())
    {
        settings.setValue(getWindowGeometryKey(), saveGeometry());
        // Save the last current view in any case
        settings.setValue("CURRENT_VIEW", currentView);
        // 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(QGC::applicationVersion()));
        // Save the current view only if a UAS is connected
        if (UASManager::instance()->getUASList().length() > 0) settings.setValue("CURRENT_VIEW_WITH_UAS_CONNECTED", currentView);
        // Save the current power mode
    }
702
    settings.setValue("LOW_POWER_MODE", lowPowerMode);
703 704 705
    settings.sync();
}

706 707
void MainWindow::configureWindowName()
{
pixhawk's avatar
pixhawk committed
708 709 710
    QList<QHostAddress> hostAddresses = QNetworkInterface::allAddresses();
    QString windowname = qApp->applicationName() + " " + qApp->applicationVersion();
    bool prevAddr = false;
711

pixhawk's avatar
pixhawk committed
712
    windowname.append(" (" + QHostInfo::localHostName() + ": ");
713

714 715
    for (int i = 0; i < hostAddresses.size(); i++)
    {
pixhawk's avatar
pixhawk committed
716
        // Exclude loopback IPv4 and all IPv6 addresses
717 718
        if (hostAddresses.at(i) != QHostAddress("127.0.0.1") && !hostAddresses.at(i).toString().contains(":"))
        {
pixhawk's avatar
pixhawk committed
719 720 721 722 723
            if(prevAddr) windowname.append("/");
            windowname.append(hostAddresses.at(i).toString());
            prevAddr = true;
        }
    }
724

pixhawk's avatar
pixhawk committed
725
    windowname.append(")");
726

pixhawk's avatar
pixhawk committed
727
    setWindowTitle(windowname);
728 729

#ifndef Q_WS_MAC
pixhawk's avatar
pixhawk committed
730
    //qApp->setWindowIcon(QIcon(":/core/images/qtcreator_logo_128.png"));
731 732 733
#endif
}

pixhawk's avatar
pixhawk committed
734
void MainWindow::startVideoCapture()
pixhawk's avatar
pixhawk committed
735 736 737 738 739
{
    QString format = "bmp";
    QString initialPath = QDir::currentPath() + tr("/untitled.") + format;

    QString screenFileName = QFileDialog::getSaveFileName(this, tr("Save As"),
lm's avatar
lm committed
740 741 742 743
                                                          initialPath,
                                                          tr("%1 Files (*.%2);;All Files (*)")
                                                          .arg(format.toUpper())
                                                          .arg(format));
pixhawk's avatar
pixhawk committed
744 745
    delete videoTimer;
    videoTimer = new QTimer(this);
746 747 748
    //videoTimer->setInterval(40);
    //connect(videoTimer, SIGNAL(timeout()), this, SLOT(saveScreen()));
    //videoTimer->stop();
pixhawk's avatar
pixhawk committed
749 750
}

pixhawk's avatar
pixhawk committed
751
void MainWindow::stopVideoCapture()
pixhawk's avatar
pixhawk committed
752 753 754 755 756 757
{
    videoTimer->stop();

    // TODO Convert raw images to PNG
}

pixhawk's avatar
pixhawk committed
758
void MainWindow::saveScreen()
pixhawk's avatar
pixhawk committed
759 760 761 762
{
    QPixmap window = QPixmap::grabWindow(this->winId());
    QString format = "bmp";

763 764
    if (!screenFileName.isEmpty())
    {
pixhawk's avatar
pixhawk committed
765 766 767 768
        window.save(screenFileName, format.toAscii());
    }
}

769
void MainWindow::enableAutoReconnect(bool enabled)
pixhawk's avatar
pixhawk committed
770
{
771 772
    autoReconnect = enabled;
}
773

774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790
void MainWindow::loadNativeStyle()
{
    loadStyle(QGC_MAINWINDOW_STYLE_NATIVE);
}

void MainWindow::loadIndoorStyle()
{
    loadStyle(QGC_MAINWINDOW_STYLE_INDOOR);
}

void MainWindow::loadOutdoorStyle()
{
    loadStyle(QGC_MAINWINDOW_STYLE_OUTDOOR);
}

void MainWindow::loadStyle(QGC_MAINWINDOW_STYLE style)
{
791 792 793 794 795 796 797 798 799
    switch (style) {
    case QGC_MAINWINDOW_STYLE_NATIVE: {
        // Native mode means setting no style
        // so if we were already in native mode
        // take no action
        // Only if a style was set, remove it.
        if (style != currentStyle) {
            qApp->setStyleSheet("");
            showInfoMessage(tr("Please restart QGroundControl"), tr("Please restart QGroundControl to switch to fully native look and feel. Currently you have loaded Qt's plastique style."));
800
        }
801 802
    }
    break;
803 804 805 806 807 808 809 810 811 812 813 814 815 816
    case QGC_MAINWINDOW_STYLE_INDOOR:
        qApp->setStyle("plastique");
        styleFileName = ":/images/style-mission.css";
        reloadStylesheet();
        break;
    case QGC_MAINWINDOW_STYLE_OUTDOOR:
        qApp->setStyle("plastique");
        styleFileName = ":/images/style-outdoor.css";
        reloadStylesheet();
        break;
    }
    currentStyle = style;
}

817
void MainWindow::selectStylesheet()
pixhawk's avatar
pixhawk committed
818
{
819
    // Let user select style sheet
820
    styleFileName = QFileDialog::getOpenFileName(this, tr("Specify stylesheet"), styleFileName, tr("CSS Stylesheet (*.css);;"));
821

822
    if (!styleFileName.endsWith(".css")) {
823 824 825 826 827 828 829 830 831 832
        QMessageBox msgBox;
        msgBox.setIcon(QMessageBox::Information);
        msgBox.setText(tr("QGroundControl did lot load a new style"));
        msgBox.setInformativeText(tr("No suitable .css file selected. Please select a valid .css file."));
        msgBox.setStandardButtons(QMessageBox::Ok);
        msgBox.setDefaultButton(QMessageBox::Ok);
        msgBox.exec();
        return;
    }

pixhawk's avatar
pixhawk committed
833
    // Load style sheet
834 835 836 837 838 839 840
    reloadStylesheet();
}

void MainWindow::reloadStylesheet()
{
    // Load style sheet
    QFile* styleSheet = new QFile(styleFileName);
841
    if (!styleSheet->exists()) {
842 843
        styleSheet = new QFile(":/images/style-mission.css");
    }
844
    if (styleSheet->open(QIODevice::ReadOnly | QIODevice::Text)) {
845 846
        QString style = QString(styleSheet->readAll());
        style.replace("ICONDIR", QCoreApplication::applicationDirPath()+ "/images/");
pixhawk's avatar
pixhawk committed
847
        qApp->setStyleSheet(style);
848
    } else {
849 850 851
        QMessageBox msgBox;
        msgBox.setIcon(QMessageBox::Information);
        msgBox.setText(tr("QGroundControl did lot load a new style"));
852
        msgBox.setInformativeText(tr("Stylesheet file %1 was not readable").arg(styleFileName));
853 854 855
        msgBox.setStandardButtons(QMessageBox::Ok);
        msgBox.setDefaultButton(QMessageBox::Ok);
        msgBox.exec();
pixhawk's avatar
pixhawk committed
856
    }
857
    delete styleSheet;
pixhawk's avatar
pixhawk committed
858 859
}

860 861 862 863 864 865
/**
 * The status message will be overwritten if a new message is posted to this function
 *
 * @param status message text
 * @param timeout how long the status should be displayed
 */
pixhawk's avatar
pixhawk committed
866
void MainWindow::showStatusMessage(const QString& status, int timeout)
pixhawk's avatar
pixhawk committed
867
{
lm's avatar
lm committed
868
    statusBar()->showMessage(status, timeout);
pixhawk's avatar
pixhawk committed
869 870
}

871 872 873 874 875 876
/**
 * The status message will be overwritten if a new message is posted to this function.
 * it will be automatically hidden after 5 seconds.
 *
 * @param status message text
 */
877
void MainWindow::showStatusMessage(const QString& status)
pixhawk's avatar
pixhawk committed
878
{
lm's avatar
lm committed
879
    statusBar()->showMessage(status, 20000);
880 881 882 883
}

void MainWindow::showCriticalMessage(const QString& title, const QString& message)
{
884
    QMessageBox msgBox(this);
885 886 887 888 889 890
    msgBox.setIcon(QMessageBox::Critical);
    msgBox.setText(title);
    msgBox.setInformativeText(message);
    msgBox.setStandardButtons(QMessageBox::Ok);
    msgBox.setDefaultButton(QMessageBox::Ok);
    msgBox.exec();
pixhawk's avatar
pixhawk committed
891 892
}

lm's avatar
lm committed
893 894
void MainWindow::showInfoMessage(const QString& title, const QString& message)
{
895
    QMessageBox msgBox(this);
lm's avatar
lm committed
896 897 898 899 900 901 902 903
    msgBox.setIcon(QMessageBox::Information);
    msgBox.setText(title);
    msgBox.setInformativeText(message);
    msgBox.setStandardButtons(QMessageBox::Ok);
    msgBox.setDefaultButton(QMessageBox::Ok);
    msgBox.exec();
}

pixhawk's avatar
pixhawk committed
904 905 906 907
/**
* @brief Create all actions associated to the main window
*
**/
908
void MainWindow::connectCommonActions()
pixhawk's avatar
pixhawk committed
909
{
910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932
    // Bind together the perspective actions
    QActionGroup* perspectives = new QActionGroup(ui.menuPerspectives);
    perspectives->addAction(ui.actionEngineersView);
    perspectives->addAction(ui.actionMavlinkView);
    perspectives->addAction(ui.actionPilotsView);
    perspectives->addAction(ui.actionOperatorsView);
    perspectives->addAction(ui.actionUnconnectedView);
    perspectives->setExclusive(true);

    // Mark the right one as selected
    if (currentView == VIEW_ENGINEER) ui.actionEngineersView->setChecked(true);
    if (currentView == VIEW_MAVLINK) ui.actionMavlinkView->setChecked(true);
    if (currentView == VIEW_PILOT) ui.actionPilotsView->setChecked(true);
    if (currentView == VIEW_OPERATOR) ui.actionOperatorsView->setChecked(true);
    if (currentView == VIEW_UNCONNECTED) ui.actionUnconnectedView->setChecked(true);

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

pixhawk's avatar
pixhawk committed
933 934 935 936 937
    // Connect actions from ui
    connect(ui.actionAdd_Link, SIGNAL(triggered()), this, SLOT(addLink()));

    // Connect internal actions
    connect(UASManager::instance(), SIGNAL(UASCreated(UASInterface*)), this, SLOT(UASCreated(UASInterface*)));
938
    connect(UASManager::instance(), SIGNAL(activeUASSet(UASInterface*)), this, SLOT(setActiveUAS(UASInterface*)));
pixhawk's avatar
pixhawk committed
939

940
    // Unmanned System controls
pixhawk's avatar
pixhawk committed
941 942 943 944
    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()));
945
    connect(ui.actionShutdownMAV, SIGNAL(triggered()), UASManager::instance(), SLOT(shutdownActiveUAS()));
pixhawk's avatar
pixhawk committed
946 947
    connect(ui.actionConfiguration, SIGNAL(triggered()), UASManager::instance(), SLOT(configureActiveUAS()));

948
    // Views actions
949
    connect(ui.actionPilotsView, SIGNAL(triggered()), this, SLOT(loadPilotView()));
950
    connect(ui.actionEngineersView, SIGNAL(triggered()), this, SLOT(loadEngineerView()));
951
    connect(ui.actionOperatorsView, SIGNAL(triggered()), this, SLOT(loadOperatorView()));
952
    connect(ui.actionUnconnectedView, SIGNAL(triggered()), this, SLOT(loadUnconnectedView()));
953

954
    connect(ui.actionMavlinkView, SIGNAL(triggered()), this, SLOT(loadMAVLinkView()));
955 956
    connect(ui.actionReloadStylesheet, SIGNAL(triggered()), this, SLOT(reloadStylesheet()));
    connect(ui.actionSelectStylesheet, SIGNAL(triggered()), this, SLOT(selectStylesheet()));
957

958
    // Help Actions
959
    connect(ui.actionOnline_Documentation, SIGNAL(triggered()), this, SLOT(showHelp()));
960
    connect(ui.actionDeveloper_Credits, SIGNAL(triggered()), this, SLOT(showCredits()));
961
    connect(ui.actionProject_Roadmap_2, SIGNAL(triggered()), this, SLOT(showRoadMap()));
962 963 964

    // Custom widget actions
    connect(ui.actionNewCustomWidget, SIGNAL(triggered()), this, SLOT(createCustomWidget()));
965
    connect(ui.actionLoadCustomWidgetFile, SIGNAL(triggered()), this, SLOT(loadCustomWidget()));
966 967 968

    // Audio output
    ui.actionMuteAudioOutput->setChecked(GAudioOutput::instance()->isMuted());
969
    connect(GAudioOutput::instance(), SIGNAL(mutedChanged(bool)), ui.actionMuteAudioOutput, SLOT(setChecked(bool)));
970
    connect(ui.actionMuteAudioOutput, SIGNAL(triggered(bool)), GAudioOutput::instance(), SLOT(mute(bool)));
971 972 973 974 975 976 977

    // User interaction
    // NOTE: Joystick thread is not started and
    // configuration widget is not instantiated
    // unless it is actually used
    // so no ressources spend on this.
    ui.actionJoystickSettings->setVisible(true);
978 979 980

    // Configuration
    // Joystick
981
    connect(ui.actionJoystickSettings, SIGNAL(triggered()), this, SLOT(configure()));
982 983
    // Application Settings
    connect(ui.actionSettings, SIGNAL(triggered()), this, SLOT(showSettings()));
984 985
}

986 987
void MainWindow::showHelp()
{
lm's avatar
lm committed
988
    if(!QDesktopServices::openUrl(QUrl("http://qgroundcontrol.org/users/start"))) {
989 990 991 992 993 994 995 996 997 998 999 1000
        QMessageBox msgBox;
        msgBox.setIcon(QMessageBox::Critical);
        msgBox.setText("Could not open help in browser");
        msgBox.setInformativeText("To get to the online help, please open http://qgroundcontrol.org/user_guide in a browser.");
        msgBox.setStandardButtons(QMessageBox::Ok);
        msgBox.setDefaultButton(QMessageBox::Ok);
        msgBox.exec();
    }
}

void MainWindow::showCredits()
{
1001
    if(!QDesktopServices::openUrl(QUrl("http://qgroundcontrol.org/credits"))) {
1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013
        QMessageBox msgBox;
        msgBox.setIcon(QMessageBox::Critical);
        msgBox.setText("Could not open credits in browser");
        msgBox.setInformativeText("To get to the online help, please open http://qgroundcontrol.org/credits in a browser.");
        msgBox.setStandardButtons(QMessageBox::Ok);
        msgBox.setDefaultButton(QMessageBox::Ok);
        msgBox.exec();
    }
}

void MainWindow::showRoadMap()
{
lm's avatar
lm committed
1014
    if(!QDesktopServices::openUrl(QUrl("http://qgroundcontrol.org/dev/roadmap"))) {
1015 1016 1017 1018 1019 1020 1021 1022 1023 1024
        QMessageBox msgBox;
        msgBox.setIcon(QMessageBox::Critical);
        msgBox.setText("Could not open roadmap in browser");
        msgBox.setInformativeText("To get to the online help, please open http://qgroundcontrol.org/roadmap in a browser.");
        msgBox.setStandardButtons(QMessageBox::Ok);
        msgBox.setDefaultButton(QMessageBox::Ok);
        msgBox.exec();
    }
}

pixhawk's avatar
pixhawk committed
1025
void MainWindow::configure()
pixhawk's avatar
pixhawk committed
1026
{
1027 1028
    if (!joystickWidget) {
        if (!joystick->isRunning()) {
1029 1030 1031 1032 1033
            joystick->start();
        }
        joystickWidget = new JoystickWidget(joystick);
    }
    joystickWidget->show();
pixhawk's avatar
pixhawk committed
1034 1035
}

1036 1037 1038 1039 1040 1041
void MainWindow::showSettings()
{
    QGCSettingsWidget* settings = new QGCSettingsWidget(this);
    settings->show();
}

pixhawk's avatar
pixhawk committed
1042
void MainWindow::addLink()
pixhawk's avatar
pixhawk committed
1043 1044 1045 1046
{
    SerialLink* link = new SerialLink();
    // TODO This should be only done in the dialog itself

1047
    LinkManager::instance()->add(link);
pixhawk's avatar
pixhawk committed
1048 1049
    LinkManager::instance()->addProtocol(link, mavlink);

1050 1051
    // Go fishing for this link's configuration window
    QList<QAction*> actions = ui.menuNetwork->actions();
pixhawk's avatar
pixhawk committed
1052

lm's avatar
lm committed
1053 1054 1055 1056
    foreach (QAction* act, actions)
    {
        if (act->data().toInt() == LinkManager::instance()->getLinks().indexOf(link))
        {
1057 1058 1059 1060
            act->trigger();
            break;
        }
    }
pixhawk's avatar
pixhawk committed
1061 1062
}

pixhawk's avatar
pixhawk committed
1063 1064
void MainWindow::addLink(LinkInterface *link)
{
1065 1066 1067 1068
    // IMPORTANT! KEEP THESE TWO LINES
    // THEY MAKE SURE THE LINK IS PROPERLY REGISTERED
    // BEFORE LINKING THE UI AGAINST IT
    // Register (does nothing if already registered)
1069
    LinkManager::instance()->add(link);
1070
    LinkManager::instance()->addProtocol(link, mavlink);
1071

1072 1073
    // Go fishing for this link's configuration window
    QList<QAction*> actions = ui.menuNetwork->actions();
pixhawk's avatar
pixhawk committed
1074

1075
    bool found = false;
1076

1077 1078 1079 1080
    foreach (QAction* act, actions)
    {
        if (act->data().toInt() == LinkManager::instance()->getLinks().indexOf(link))
        {
1081 1082 1083 1084 1085
            found = true;
        }
    }

    UDPLink* udp = dynamic_cast<UDPLink*>(link);
1086

1087 1088
    if (!found || udp)
    {
1089 1090 1091 1092 1093 1094 1095 1096
        CommConfigurationWindow* commWidget = new CommConfigurationWindow(link, mavlink, this);
        QAction* action = commWidget->getAction();
        ui.menuNetwork->addAction(action);

        // Error handling
        connect(link, SIGNAL(communicationError(QString,QString)), this, SLOT(showCriticalMessage(QString,QString)), Qt::QueuedConnection);
        // Special case for simulationlink
        MAVLinkSimulationLink* sim = dynamic_cast<MAVLinkSimulationLink*>(link);
1097 1098
        if (sim)
        {
1099 1100 1101
            //connect(sim, SIGNAL(valueChanged(int,QString,double,quint64)), linechart, SLOT(appendData(int,QString,double,quint64)));
            connect(ui.actionSimulate, SIGNAL(triggered(bool)), sim, SLOT(connectLink(bool)));
        }
pixhawk's avatar
pixhawk committed
1102 1103 1104
    }
}

1105 1106 1107 1108 1109 1110 1111
void MainWindow::setActiveUAS(UASInterface* uas)
{
    // Enable and rename menu
    ui.menuUnmanned_System->setTitle(uas->getUASName());
    if (!ui.menuUnmanned_System->isEnabled()) ui.menuUnmanned_System->setEnabled(true);
}

1112 1113 1114
void MainWindow::UASSpecsChanged(int uas)
{
    UASInterface* activeUAS = UASManager::instance()->getActiveUAS();
1115 1116 1117 1118
    if (activeUAS)
    {
        if (activeUAS->getUASID() == uas)
        {
1119 1120 1121 1122 1123
            ui.menuUnmanned_System->setTitle(activeUAS->getUASName());
        }
    }
}

pixhawk's avatar
pixhawk committed
1124
void MainWindow::UASCreated(UASInterface* uas)
pixhawk's avatar
pixhawk committed
1125
{
1126

pixhawk's avatar
pixhawk committed
1127 1128
    // Connect the UAS to the full user interface

lm's avatar
lm committed
1129 1130
    if (uas != NULL)
    {
1131
        // The pilot, operator and engineer views were not available on startup, enable them now
1132
        ui.actionPilotsView->setEnabled(true);
1133 1134
        ui.actionOperatorsView->setEnabled(true);
        ui.actionEngineersView->setEnabled(true);
1135 1136 1137 1138 1139 1140
        // 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);
1141

1142 1143
        QIcon icon;
        // Set matching icon
1144
        switch (uas->getSystemType()) {
1145 1146 1147 1148 1149 1150 1151 1152 1153 1154 1155 1156 1157 1158 1159 1160 1161 1162 1163 1164 1165 1166 1167
        case 0:
            icon = QIcon(":/images/mavs/generic.svg");
            break;
        case 1:
            icon = QIcon(":/images/mavs/fixed-wing.svg");
            break;
        case 2:
            icon = QIcon(":/images/mavs/quadrotor.svg");
            break;
        case 3:
            icon = QIcon(":/images/mavs/coaxial.svg");
            break;
        case 4:
            icon = QIcon(":/images/mavs/helicopter.svg");
            break;
        case 5:
            icon = QIcon(":/images/mavs/groundstation.svg");
            break;
        default:
            icon = QIcon(":/images/mavs/unknown.svg");
            break;
        }

1168 1169 1170
        QAction* uasAction = new QAction(icon, tr("Select %1 for control").arg(uas->getUASName()), ui.menuConnected_Systems);
        connect(uas, SIGNAL(systemRemoved()), uasAction, SLOT(deleteLater()));
        connect(uasAction, SIGNAL(triggered()), uas, SLOT(setSelected()));
1171
        connect(uas, SIGNAL(systemSpecsChanged(int)), this, SLOT(UASSpecsChanged(int)));
1172 1173

        ui.menuConnected_Systems->addAction(uasAction);
1174 1175

        // FIXME Should be not inside the mainwindow
1176 1177
        if (debugConsoleDockWidget)
        {
pixhawk's avatar
pixhawk committed
1178
            DebugConsole *debugConsole = dynamic_cast<DebugConsole*>(debugConsoleDockWidget->widget());
1179 1180
            if (debugConsole)
            {
pixhawk's avatar
pixhawk committed
1181 1182 1183
                connect(uas, SIGNAL(textMessageReceived(int,int,int,QString)),
                        debugConsole, SLOT(receiveTextMessage(int,int,int,QString)));
            }
1184
        }
1185 1186

        // Health / System status indicator
1187 1188
        if (infoDockWidget)
        {
pixhawk's avatar
pixhawk committed
1189
            UASInfoWidget *infoWidget = dynamic_cast<UASInfoWidget*>(infoDockWidget->widget());
1190 1191
            if (infoWidget)
            {
pixhawk's avatar
pixhawk committed
1192 1193
                infoWidget->addUAS(uas);
            }
1194
        }
1195 1196

        // UAS List
lm's avatar
lm committed
1197 1198
        if (listDockWidget)
        {
pixhawk's avatar
pixhawk committed
1199
            UASListWidget *listWidget = dynamic_cast<UASListWidget*>(listDockWidget->widget());
lm's avatar
lm committed
1200 1201
            if (listWidget)
            {
pixhawk's avatar
pixhawk committed
1202 1203
                listWidget->addUAS(uas);
            }
1204
        }
1205

1206 1207
        // Line chart
        if (!linechartWidget)
lm's avatar
lm committed
1208
        {
1209
            // Center widgets
LM's avatar
LM committed
1210
            linechartWidget = new Linecharts(this);
1211
            linechartWidget->addSource(mavlinkDecoder);
1212
            addCentralWidget(linechartWidget, tr("Realtime Plot"));
1213
        }
1214

LM's avatar
LM committed
1215
        // Load default custom widgets for this autopilot type
lm's avatar
lm committed
1216
        loadCustomWidgetsFromDefaults(uas->getSystemTypeName(), uas->getAutopilotTypeName());
1217 1218


lm's avatar
lm committed
1219 1220 1221 1222 1223 1224 1225 1226 1227 1228 1229
        if (uas->getAutopilotType() == MAV_AUTOPILOT_PIXHAWK)
        {
            // Dock widgets
            if (!detectionDockWidget)
            {
                detectionDockWidget = new QDockWidget(tr("Object Recognition"), this);
                detectionDockWidget->setWidget( new ObjectDetectionView("images/patterns", this) );
                detectionDockWidget->setObjectName("OBJECT_DETECTION_DOCK_WIDGET");
                addTool(detectionDockWidget, tr("Object Recognition"), Qt::RightDockWidgetArea);
            }

1230 1231
            if (!watchdogControlDockWidget)
            {
lm's avatar
lm committed
1232 1233 1234 1235 1236
                watchdogControlDockWidget = new QDockWidget(tr("Process Control"), this);
                watchdogControlDockWidget->setWidget( new WatchdogControl(this) );
                watchdogControlDockWidget->setObjectName("WATCHDOG_CONTROL_DOCKWIDGET");
                addTool(watchdogControlDockWidget, tr("Process Control"), Qt::BottomDockWidgetArea);
            }
1237 1238 1239 1240 1241 1242
        }

        // Change the view only if this is the first UAS

        // If this is the first connected UAS, it is both created as well as
        // the currently active UAS
1243 1244
        if (UASManager::instance()->getUASList().size() == 1)
        {
1245
            // Load last view if setting is present
1246 1247
            if (settings.contains("CURRENT_VIEW_WITH_UAS_CONNECTED"))
            {
1248
                int view = settings.value("CURRENT_VIEW_WITH_UAS_CONNECTED").toInt();
1249
                switch (view) {
1250 1251 1252 1253 1254 1255 1256 1257 1258 1259 1260 1261 1262 1263 1264 1265
                case VIEW_ENGINEER:
                    loadEngineerView();
                    break;
                case VIEW_MAVLINK:
                    loadMAVLinkView();
                    break;
                case VIEW_PILOT:
                    loadPilotView();
                    break;
                case VIEW_UNCONNECTED:
                    loadUnconnectedView();
                    break;
                case VIEW_OPERATOR:
                default:
                    loadOperatorView();
                    break;
1266
                }
1267 1268 1269
            }
            else
            {
1270
                loadOperatorView();
1271
            }
1272
        }
1273

1274
    }
1275 1276

    if (!ui.menuConnected_Systems->isEnabled()) ui.menuConnected_Systems->setEnabled(true);
lm's avatar
lm committed
1277

1278 1279
    // Reload view state in case new widgets were added
    loadViewState();
pixhawk's avatar
pixhawk committed
1280 1281
}

1282
/**
1283
 * Stores the current view state
1284
 */
1285
void MainWindow::storeViewState()
1286
{
pixhawk's avatar
pixhawk committed
1287
    if (!aboutToCloseFlag)
lm's avatar
lm committed
1288
    {
pixhawk's avatar
pixhawk committed
1289 1290 1291 1292 1293 1294 1295 1296
        // Save current state
        settings.setValue(getWindowStateKey(), saveState(QGC::applicationVersion()));
        settings.setValue(getWindowStateKey()+"CENTER_WIDGET", centerStack->currentIndex());
        // Although we want save the state of the window, we do not want to change the top-leve state (minimized, maximized, etc)
        // therefore this state is stored here and restored after applying the rest of the settings in the new
        // perspective.
        windowStateVal = this->windowState();
        settings.setValue(getWindowGeometryKey(), saveGeometry());
1297
    }
1298
}
1299

1300 1301 1302
void MainWindow::loadViewState()
{
    // Restore center stack state
lm's avatar
lm committed
1303
    int index = settings.value(getWindowStateKey()+"CENTER_WIDGET", -1).toInt();
LM's avatar
LM committed
1304 1305 1306 1307 1308 1309
    // The offline plot view is usually the consequence of a logging run, always show the realtime view first
    if (centerStack->indexOf(dataplotWidget) == index)
    {
        // Rewrite to realtime plot
        index = centerStack->indexOf(linechartWidget);
    }
pixhawk's avatar
pixhawk committed
1310

lm's avatar
lm committed
1311 1312 1313 1314 1315 1316
    if (index != -1)
    {
        centerStack->setCurrentIndex(index);
    }
    else
    {
1317 1318 1319 1320
        // Hide custom widgets
        if (detectionDockWidget) detectionDockWidget->hide();
        if (watchdogControlDockWidget) watchdogControlDockWidget->hide();

lm's avatar
lm committed
1321 1322 1323 1324 1325
        // Load defaults
        switch (currentView)
        {
        case VIEW_ENGINEER:
            centerStack->setCurrentWidget(linechartWidget);
lm's avatar
lm committed
1326 1327 1328 1329 1330 1331 1332 1333 1334 1335 1336 1337 1338 1339 1340
            controlDockWidget->hide();
            listDockWidget->hide();
            waypointsDockWidget->hide();
            infoDockWidget->hide();
            debugConsoleDockWidget->show();
            logPlayerDockWidget->show();
            mavlinkInspectorWidget->show();
            parametersDockWidget->show();
            hsiDockWidget->hide();
            headDown1DockWidget->hide();
            headDown2DockWidget->hide();
            rcViewDockWidget->hide();
            headUpDockWidget->hide();
            video1DockWidget->hide();
            video2DockWidget->hide();
lm's avatar
lm committed
1341 1342 1343
            break;
        case VIEW_PILOT:
            centerStack->setCurrentWidget(hudWidget);
lm's avatar
lm committed
1344 1345 1346 1347 1348 1349 1350 1351 1352 1353 1354 1355 1356
            controlDockWidget->hide();
            listDockWidget->hide();
            waypointsDockWidget->hide();
            infoDockWidget->hide();
            debugConsoleDockWidget->hide();
            logPlayerDockWidget->hide();
            mavlinkInspectorWidget->hide();
            parametersDockWidget->hide();
            hsiDockWidget->show();
            headDown1DockWidget->show();
            headDown2DockWidget->show();
            rcViewDockWidget->hide();
            headUpDockWidget->hide();
1357
            video1DockWidget->hide();
lm's avatar
lm committed
1358
            video2DockWidget->hide();
lm's avatar
lm committed
1359 1360 1361
            break;
        case VIEW_MAVLINK:
            centerStack->setCurrentWidget(protocolWidget);
lm's avatar
lm committed
1362 1363 1364 1365 1366 1367 1368 1369 1370 1371 1372 1373 1374 1375 1376
            controlDockWidget->hide();
            listDockWidget->hide();
            waypointsDockWidget->hide();
            infoDockWidget->hide();
            debugConsoleDockWidget->hide();
            logPlayerDockWidget->hide();
            mavlinkInspectorWidget->hide();
            parametersDockWidget->hide();
            hsiDockWidget->hide();
            headDown1DockWidget->hide();
            headDown2DockWidget->hide();
            rcViewDockWidget->hide();
            headUpDockWidget->hide();
            video1DockWidget->hide();
            video2DockWidget->hide();
lm's avatar
lm committed
1377 1378
            break;
        case VIEW_OPERATOR:
1379 1380 1381 1382 1383 1384 1385 1386 1387 1388 1389 1390 1391 1392 1393 1394 1395
            centerStack->setCurrentWidget(mapWidget);
            controlDockWidget->hide();
            listDockWidget->show();
            waypointsDockWidget->show();
            infoDockWidget->hide();
            debugConsoleDockWidget->show();
            logPlayerDockWidget->show();
            parametersDockWidget->hide();
            hsiDockWidget->show();
            headDown1DockWidget->hide();
            headDown2DockWidget->hide();
            rcViewDockWidget->hide();
            headUpDockWidget->show();
            video1DockWidget->hide();
            video2DockWidget->hide();
            mavlinkInspectorWidget->hide();
            break;
lm's avatar
lm committed
1396 1397 1398 1399
        case VIEW_UNCONNECTED:
        case VIEW_FULL:
        default:
            centerStack->setCurrentWidget(mapWidget);
1400
            controlDockWidget->hide();
lm's avatar
lm committed
1401
            listDockWidget->show();
1402 1403
            waypointsDockWidget->hide();
            infoDockWidget->hide();
lm's avatar
lm committed
1404 1405 1406
            debugConsoleDockWidget->show();
            logPlayerDockWidget->show();
            parametersDockWidget->hide();
1407
            hsiDockWidget->hide();
lm's avatar
lm committed
1408 1409 1410 1411 1412 1413
            headDown1DockWidget->hide();
            headDown2DockWidget->hide();
            rcViewDockWidget->hide();
            headUpDockWidget->show();
            video1DockWidget->hide();
            video2DockWidget->hide();
1414
            mavlinkInspectorWidget->show();
lm's avatar
lm committed
1415
            break;
pixhawk's avatar
pixhawk committed
1416 1417 1418
        }
    }

1419 1420 1421 1422
    // Restore the widget positions and size
    if (settings.contains(getWindowStateKey()))
    {
        restoreState(settings.value(getWindowStateKey()).toByteArray(), QGC::applicationVersion());
pixhawk's avatar
pixhawk committed
1423 1424 1425
    }
}

1426
void MainWindow::loadEngineerView()
lm's avatar
lm committed
1427
{
1428 1429
    if (currentView != VIEW_ENGINEER)
    {
1430
        storeViewState();
1431 1432
        currentView = VIEW_ENGINEER;
        ui.actionEngineersView->setChecked(true);
1433
        loadViewState();
1434
    }
lm's avatar
lm committed
1435 1436
}

1437
void MainWindow::loadOperatorView()
lm's avatar
lm committed
1438
{
1439 1440
    if (currentView != VIEW_OPERATOR)
    {
1441
        storeViewState();
1442 1443
        currentView = VIEW_OPERATOR;
        ui.actionOperatorsView->setChecked(true);
1444
        loadViewState();
1445 1446
    }
}
lm's avatar
lm committed
1447

1448 1449
void MainWindow::loadUnconnectedView()
{
1450 1451 1452
    if (currentView != VIEW_UNCONNECTED)
    {
        storeViewState();
1453 1454
        currentView = VIEW_UNCONNECTED;
        ui.actionUnconnectedView->setChecked(true);
1455
        loadViewState();
1456
    }
lm's avatar
lm committed
1457 1458
}

1459
void MainWindow::loadPilotView()
1460
{
1461 1462 1463
    if (currentView != VIEW_PILOT)
    {
        storeViewState();
1464 1465
        currentView = VIEW_PILOT;
        ui.actionPilotsView->setChecked(true);
1466
        loadViewState();
1467
    }
1468 1469
}

1470
void MainWindow::loadMAVLinkView()
1471
{
1472 1473 1474
    if (currentView != VIEW_MAVLINK)
    {
        storeViewState();
1475 1476
        currentView = VIEW_MAVLINK;
        ui.actionMavlinkView->setChecked(true);
1477
        loadViewState();
1478
    }
1479 1480
}

1481 1482
void MainWindow::loadDataView(QString fileName)
{
1483
    // Plot is now selected, now load data from file
1484 1485 1486 1487 1488 1489 1490 1491
    if (dataplotWidget)
    {
        dataplotWidget->loadFile(fileName);
    }
    QStackedWidget *centerStack = dynamic_cast<QStackedWidget*>(centralWidget());
    if (centerStack)
    {
        centerStack->setCurrentWidget(dataplotWidget);
1492
        dataplotWidget->loadFile(fileName);
1493
    }
1494 1495 1496
}


oberion's avatar
oberion committed
1497 1498
QList<QAction*> MainWindow::listLinkMenuActions(void)
{
lm's avatar
lm committed
1499
    return ui.menuNetwork->actions();
1500
}