MainRootWindow.qml 23.2 KB
Newer Older
1 2 3 4 5 6 7 8 9 10
/****************************************************************************
 *
 *   (c) 2009-2016 QGROUNDCONTROL PROJECT <http://www.qgroundcontrol.org>
 *
 * QGroundControl is licensed according to the terms in the file
 * COPYING.md in the root of the source code directory.
 *
 ****************************************************************************/


Gus Grubba's avatar
Gus Grubba committed
11
import QtQuick          2.11
12 13
import QtQuick.Controls 2.4
import QtQuick.Dialogs  1.3
Gus Grubba's avatar
Gus Grubba committed
14
import QtQuick.Layouts  1.11
15
import QtQuick.Window   2.11
16 17 18 19 20 21 22 23 24 25

import QGroundControl               1.0
import QGroundControl.Palette       1.0
import QGroundControl.Controls      1.0
import QGroundControl.ScreenTools   1.0
import QGroundControl.FlightDisplay 1.0
import QGroundControl.FlightMap     1.0

/// Native QML top level window
ApplicationWindow {
26
    id:             mainWindow
27 28
    minimumWidth:   ScreenTools.isMobile ? Screen.width  : Math.min(215 * Screen.pixelDensity, Screen.width)
    minimumHeight:  ScreenTools.isMobile ? Screen.height : Math.min(120 * Screen.pixelDensity, Screen.height)
29 30
    visible:        true

31 32 33
    Component.onCompleted: {
        if(ScreenTools.isMobile) {
            mainWindow.showFullScreen()
Gus Grubba's avatar
Gus Grubba committed
34 35 36
        } else {
            width   = ScreenTools.isMobile ? Screen.width  : Math.min(250 * Screen.pixelDensity, Screen.width)
            height  = ScreenTools.isMobile ? Screen.height : Math.min(150 * Screen.pixelDensity, Screen.height)
37 38 39
        }
    }

Gus Grubba's avatar
Gus Grubba committed
40
    readonly property real      _topBottomMargins:          ScreenTools.defaultFontPixelHeight * 0.5
41 42
    readonly property string    _mainToolbar:               QGroundControl.corePlugin.options.mainToolbarUrl
    readonly property string    _planToolbar:               QGroundControl.corePlugin.options.planToolbarUrl
43

44 45 46
    //-------------------------------------------------------------------------
    //-- Global Scope Variables

Gus Grubba's avatar
Gus Grubba committed
47 48 49 50 51
    property var                activeVehicle:              QGroundControl.multiVehicleManager.activeVehicle
    property bool               communicationLost:          activeVehicle ? activeVehicle.connectionLost : false
    property string             formatedMessage:            activeVehicle ? activeVehicle.formatedMessage : ""
    property real               availableHeight:            mainWindow.height - mainWindow.header.height

52
    property var                currentPlanMissionItem:     planMasterControllerPlan ? planMasterControllerPlan.missionController.currentPlanViewItem : null
Gus Grubba's avatar
Gus Grubba committed
53 54
    property var                planMasterControllerPlan:   null
    property var                planMasterControllerView:   null
55
    property var                flightDisplayMap:           null
Gus Grubba's avatar
Gus Grubba committed
56 57 58 59

    readonly property string    navButtonWidth:             ScreenTools.defaultFontPixelWidth * 24
    readonly property real      defaultTextHeight:          ScreenTools.defaultFontPixelHeight
    readonly property real      defaultTextWidth:           ScreenTools.defaultFontPixelWidth
60 61 62

    QGCPalette { id: qgcPal; colorGroupEnabled: true }

63 64 65 66 67 68 69 70
    //-------------------------------------------------------------------------
    //-- Actions

    signal armVehicle
    signal disarmVehicle
    signal vtolTransitionToFwdFlight
    signal vtolTransitionToMRFlight

71 72 73 74
    //-------------------------------------------------------------------------
    //-- Global Scope Functions

    function viewSwitch(isPlanView) {
75 76 77 78 79
        settingsWindow.visible  = false
        setupWindow.visible     = false
        analyzeWindow.visible   = false
        rootBackground.visible  = false
        planViewLoader.visible  = false
80
        if(isPlanView) {
81
            toolbar.source  = _planToolbar
82
        } else {
83
            toolbar.source  = _mainToolbar
84 85 86 87 88
        }
    }

    function showFlyView() {
        viewSwitch(false)
89
        rootBackground.visible = true
90 91 92 93
    }

    function showPlanView() {
        viewSwitch(true)
94
        planViewLoader.visible = true
95 96 97 98
    }

    function showAnalyzeView() {
        viewSwitch(false)
99
        analyzeWindow.visible = true
100 101 102 103
    }

    function showSetupView() {
        viewSwitch(false)
104
        setupWindow.visible = true
105 106 107 108
    }

    function showSettingsView() {
        viewSwitch(false)
109
        settingsWindow.visible = true
110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228
    }

    //-------------------------------------------------------------------------
    //-- Global simple message dialog

    function showMessageDialog(title, text) {
        if(simpleMessageDialog.visible) {
            simpleMessageDialog.close()
        }
        simpleMessageDialog.title = title
        simpleMessageDialog.text  = text
        simpleMessageDialog.open()
    }

    MessageDialog {
        id:                 simpleMessageDialog
        standardButtons:    StandardButton.Ok
        modality:           Qt.ApplicationModal
        visible:            false
    }

    //-------------------------------------------------------------------------
    //-- Global complex dialog

    /// Shows a QGCViewDialog component
    ///     @param component QGCViewDialog component
    ///     @param title Title for dialog
    ///     @param charWidth Width of dialog in characters
    ///     @param buttons Buttons to show in dialog using StandardButton enum

    readonly property int showDialogFullWidth:      -1  ///< Use for full width dialog
    readonly property int showDialogDefaultWidth:   40  ///< Use for default dialog width

    function showDialog(component, title, charWidth, buttons) {
        var dialogWidth = charWidth === showDialogFullWidth ? mainWindow.width : ScreenTools.defaultFontPixelWidth * charWidth
        mainWindowDialog.width = dialogWidth
        mainWindowDialog.dialogComponent = component
        mainWindowDialog.dialogTitle = title
        mainWindowDialog.dialogButtons = buttons
        mainWindowDialog.open()
    }

    Drawer {
        id:             mainWindowDialog
        y:              mainWindow.header.height
        height:         mainWindow.height - mainWindow.header.height
        edge:           Qt.RightEdge
        interactive:    false
        background: Rectangle {
            color:  qgcPal.windowShadeDark
        }
        property var    dialogComponent: null
        property var    dialogButtons: null
        property string dialogTitle: ""
        Loader {
            id:             dlgLoader
            anchors.fill:   parent
            onLoaded: {
                item.setupDialogButtons()
            }
        }
        onOpened: {
            dlgLoader.source = "QGCViewDialogContainer.qml"
        }
        onClosed: {
            dlgLoader.source = ""
        }
    }

    //-------------------------------------------------------------------------
    //-- Weird hack that has to be fixed elsewhere and have this removed

    property bool _forceClose: false

    function reallyClose() {
        _forceClose = true
        mainWindow.close()
    }

    function finishCloseProcess() {
        QGroundControl.linkManager.shutdown()
        // The above shutdown causes a flurry of activity as the vehicle components are removed. This in turn
        // causes the Windows Version of Qt to crash if you allow the close event to be accepted. In order to prevent
        // the crash, we ignore the close event and setup a delayed timer to close the window after things settle down.
        if(ScreenTools.isWindows) {
            delayedWindowCloseTimer.start()
        } else {
            reallyClose()
        }
    }

    Timer {
        id:         delayedWindowCloseTimer
        interval:   1500
        running:    false
        repeat:     false
        onTriggered: {
            reallyClose()
        }
    }

    MessageDialog {
        id:                 activeConnectionsCloseDialog
        title:              qsTr("%1 close").arg(QGroundControl.appName)
        text:               qsTr("There are still active connections to vehicles. Are you sure you want to exit?")
        standardButtons:    StandardButton.Yes | StandardButton.Cancel
        modality:           Qt.ApplicationModal
        visible:            false
        onYes:              finishCloseProcess()
        function check() {
            if (QGroundControl.multiVehicleManager.activeVehicle) {
                activeConnectionsCloseDialog.open()
            } else {
                finishCloseProcess()
            }
        }
    }

    //-------------------------------------------------------------------------
Gus Grubba's avatar
Gus Grubba committed
229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246
    //-- Check for unsaved missions

    onClosing: {
        if (!_forceClose) {
            unsavedMissionCloseDialog.check()
            close.accepted = false
        }
    }

    MessageDialog {
        id:                 unsavedMissionCloseDialog
        title:              qsTr("%1 close").arg(QGroundControl.appName)
        text:               qsTr("You have a mission edit in progress which has not been saved/sent. If you close you will lose changes. Are you sure you want to close?")
        standardButtons:    StandardButton.Yes | StandardButton.No
        modality:           Qt.ApplicationModal
        visible:            false
        onYes:              activeConnectionsCloseDialog.check()
        function check() {
247
            if (planMasterControllerPlan && planMasterControllerPlan.dirty) {
Gus Grubba's avatar
Gus Grubba committed
248 249 250 251 252 253 254 255 256
                unsavedMissionCloseDialog.open()
            } else {
                activeConnectionsCloseDialog.check()
            }
        }
    }

    //-------------------------------------------------------------------------
    //-- Main, full window background (Fly View)
257 258 259 260 261 262
    background: Item {
        id:             rootBackground
        anchors.fill:   parent
        FlightDisplayView {
            id:             flightView
            anchors.fill:   parent
Gus Grubba's avatar
Gus Grubba committed
263
            //-----------------------------------------------------------------
Gus Grubba's avatar
Gus Grubba committed
264 265
            //-- Loader helper for any child, no matter how deep, to display
            //   elements on top of the fly (video) window.
Gus Grubba's avatar
Gus Grubba committed
266
            Loader {
Gus Grubba's avatar
Gus Grubba committed
267
                id: rootVideoLoader
Gus Grubba's avatar
Gus Grubba committed
268 269
                anchors.centerIn: parent
            }
270 271 272 273
        }
    }

    //-------------------------------------------------------------------------
274
    //-- Toolbar
275 276 277 278
    header: ToolBar {
        height:         ScreenTools.toolbarHeight
        visible:        !QGroundControl.videoManager.fullScreen
        background:     Rectangle {
279
            color:      qgcPal.globalTheme === QGCPalette.Light ? QGroundControl.corePlugin.options.toolbarBackgroundLight : QGroundControl.corePlugin.options.toolbarBackgroundDark
280
        }
281 282
        Loader {
            id:             toolbar
283
            anchors.fill:   parent
284
            source:         _mainToolbar
285 286 287 288 289 290 291 292 293 294 295 296
            //-- Toggle Full Screen / Windowed
            MouseArea {
                anchors.fill:   parent
                enabled:        !ScreenTools.isMobile
                onDoubleClicked: {
                    if(mainWindow.visibility === Window.Windowed) {
                        mainWindow.showFullScreen()
                    } else {
                        mainWindow.showNormal()
                    }
                }
            }
297 298 299
        }
    }

300 301 302
    //-------------------------------------------------------------------------
    //-- Plan View
    Loader {
303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324
        id:             planViewLoader
        anchors.fill:   parent
        visible:        false
        source:         "PlanView.qml"
    }

    //-------------------------------------------------------------------------
    //-- Settings
    Loader {
        id:             settingsWindow
        anchors.fill:   parent
        visible:        false
        source:         "AppSettings.qml"
    }

    //-------------------------------------------------------------------------
    //-- Setup
    Loader {
        id:             setupWindow
        anchors.fill:   parent
        visible:        false
        source:         "SetupView.qml"
325 326
    }

327
    //-------------------------------------------------------------------------
328
    //-- Analyze
329
    Loader {
330
        id:             analyzeWindow
331
        anchors.fill:   parent
332 333
        visible:        false
        source:         "AnalyzeView.qml"
334 335 336
    }

    //-------------------------------------------------------------------------
Gus Grubba's avatar
Gus Grubba committed
337
    //-- Loader helper for any child, no matter how deep, to display elements
338
    //   on top of the main window.
339
    //   This is DEPRECATED. Use Popup instead.
340
    Loader {
Gus Grubba's avatar
Gus Grubba committed
341
        id: rootLoader
342 343 344 345
        anchors.centerIn: parent
    }

    //-------------------------------------------------------------------------
346
    //-- Vehicle Messages
347 348 349 350 351 352 353 354

    function formatMessage(message) {
        message = message.replace(new RegExp("<#E>", "g"), "color: " + qgcPal.warningText + "; font: " + (ScreenTools.defaultFontPointSize.toFixed(0) - 1) + "pt monospace;");
        message = message.replace(new RegExp("<#I>", "g"), "color: " + qgcPal.warningText + "; font: " + (ScreenTools.defaultFontPointSize.toFixed(0) - 1) + "pt monospace;");
        message = message.replace(new RegExp("<#N>", "g"), "color: " + qgcPal.text + "; font: " + (ScreenTools.defaultFontPointSize.toFixed(0) - 1) + "pt monospace;");
        return message;
    }

355 356
    function showVehicleMessages() {
        if(!vehicleMessageArea.visible) {
357 358 359 360 361 362 363 364 365
            if(QGroundControl.multiVehicleManager.activeVehicleAvailable) {
                messageText.text = formatMessage(activeVehicle.formatedMessages)
                //-- Hack to scroll to last message
                for (var i = 0; i < activeVehicle.messageCount; i++)
                    messageFlick.flick(0,-5000)
                activeVehicle.resetMessages()
            } else {
                messageText.text = qsTr("No Messages")
            }
366
            vehicleMessageArea.open()
367 368 369 370
        }
    }

    onFormatedMessageChanged: {
371
        if(vehicleMessageArea.visible) {
372 373 374 375 376 377 378
            messageText.append(formatMessage(formatedMessage))
            //-- Hack to scroll down
            messageFlick.flick(0,-500)
        }
    }

    Popup {
379
        id:                 vehicleMessageArea
380 381 382 383
        width:              mainWindow.width  * 0.666
        height:             mainWindow.height * 0.666
        modal:              true
        focus:              true
Gus Grubba's avatar
Gus Grubba committed
384 385
        x:                  Math.round((mainWindow.width  - width)  * 0.5)
        y:                  Math.round((mainWindow.height - height) * 0.5)
386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407
        closePolicy:        Popup.CloseOnEscape | Popup.CloseOnPressOutside
        background: Rectangle {
            anchors.fill:   parent
            color:          qgcPal.window
            border.color:   qgcPal.text
            radius:         ScreenTools.defaultFontPixelHeight * 0.5
        }
        QGCFlickable {
            id:                 messageFlick
            anchors.margins:    ScreenTools.defaultFontPixelHeight
            anchors.fill:       parent
            contentHeight:      messageText.height
            contentWidth:       messageText.width
            pixelAligned:       true
            clip:               true
            TextEdit {
                id:             messageText
                readOnly:       true
                textFormat:     TextEdit.RichText
                color:          qgcPal.text
            }
        }
408
        //-- Dismiss Vehicle Messages
409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424
        QGCColoredImage {
            anchors.margins:    ScreenTools.defaultFontPixelHeight * 0.5
            anchors.top:        parent.top
            anchors.right:      parent.right
            width:              ScreenTools.isMobile ? ScreenTools.defaultFontPixelHeight * 1.5 : ScreenTools.defaultFontPixelHeight
            height:             width
            sourceSize.height:  width
            source:             "/res/XDelete.svg"
            fillMode:           Image.PreserveAspectFit
            mipmap:             true
            smooth:             true
            color:              qgcPal.text
            MouseArea {
                anchors.fill:       parent
                anchors.margins:    ScreenTools.isMobile ? -ScreenTools.defaultFontPixelHeight : 0
                onClicked: {
425
                    vehicleMessageArea.close()
426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446
                }
            }
        }
        //-- Clear Messages
        QGCColoredImage {
            anchors.bottom:     parent.bottom
            anchors.right:      parent.right
            anchors.margins:    ScreenTools.defaultFontPixelHeight * 0.5
            height:             ScreenTools.isMobile ? ScreenTools.defaultFontPixelHeight * 1.5 : ScreenTools.defaultFontPixelHeight
            width:              height
            sourceSize.height:   height
            source:             "/res/TrashDelete.svg"
            fillMode:           Image.PreserveAspectFit
            mipmap:             true
            smooth:             true
            color:              qgcPal.text
            MouseArea {
                anchors.fill:   parent
                onClicked: {
                    if(QGroundControl.multiVehicleManager.activeVehicleAvailable) {
                        activeVehicle.clearMessages();
447
                        vehicleMessageArea.close()
448 449 450 451 452 453 454
                    }
                }
            }
        }
    }

    //-------------------------------------------------------------------------
455
    //-- System Messages
456 457

    property var    _messageQueue:      []
Gus Grubba's avatar
Gus Grubba committed
458
    property string _systemMessage:     ""
459 460

    function showMessage(message) {
461 462
        vehicleMessageArea.close()
        if(systemMessageArea.visible || QGroundControl.videoManager.fullScreen) {
463 464
            _messageQueue.push(message)
        } else {
465 466
            _systemMessage = message
            systemMessageArea.open()
467 468 469 470 471 472 473 474 475 476 477 478
        }
    }

    function showMissingParameterOverlay(missingParamName) {
        showError(qsTr("Parameters missing: %1").arg(missingParamName))
    }

    function showFactError(errorMsg) {
        showError(qsTr("Fact error: %1").arg(errorMsg))
    }

    Popup {
479
        id:                 systemMessageArea
480
        y:                  ScreenTools.defaultFontPixelHeight
Gus Grubba's avatar
Gus Grubba committed
481
        x:                  Math.round((mainWindow.width - width) * 0.5)
482
        width:              mainWindow.width  * 0.55
Gus Grubba's avatar
Gus Grubba committed
483
        height:             ScreenTools.defaultFontPixelHeight * 6
484 485 486 487 488 489 490 491 492 493 494 495 496
        modal:              false
        focus:              true
        closePolicy:        Popup.CloseOnEscape

        background: Rectangle {
            anchors.fill:   parent
            color:          qgcPal.alertBackground
            radius:         ScreenTools.defaultFontPixelHeight * 0.5
            border.color:   qgcPal.alertBorder
            border.width:   2
        }

        onOpened: {
497
            systemMessageText.text = mainWindow._systemMessage
498 499 500 501 502
        }

        onClosed: {
            //-- Are there messages in the waiting queue?
            if(mainWindow._messageQueue.length) {
503
                mainWindow._systemMessage = ""
504 505 506
                //-- Show all messages in queue
                for (var i = 0; i < mainWindow._messageQueue.length; i++) {
                    var text = mainWindow._messageQueue[i]
Gus Grubba's avatar
Gus Grubba committed
507 508
                    if(i) mainWindow._systemMessage += "<br>"
                    mainWindow._systemMessage += text
509 510 511
                }
                //-- Clear it
                mainWindow._messageQueue = []
512
                systemMessageArea.open()
513
            } else {
514
                mainWindow._systemMessage = ""
515 516 517 518
            }
        }

        Flickable {
519
            id:                 systemMessageFlick
Gus Grubba's avatar
Gus Grubba committed
520
            anchors.margins:    ScreenTools.defaultFontPixelHeight * 0.5
521
            anchors.fill:       parent
522 523
            contentHeight:      systemMessageText.height
            contentWidth:       systemMessageText.width
524 525 526 527
            boundsBehavior:     Flickable.StopAtBounds
            pixelAligned:       true
            clip:               true
            TextEdit {
528 529 530
                id:             systemMessageText
                width:          systemMessageArea.width - systemMessageClose.width - (ScreenTools.defaultFontPixelHeight * 2)
                anchors.centerIn: parent
531 532 533 534 535 536 537 538 539 540 541
                readOnly:       true
                textFormat:     TextEdit.RichText
                font.pointSize: ScreenTools.defaultFontPointSize
                font.family:    ScreenTools.demiboldFontFamily
                wrapMode:       TextEdit.WordWrap
                color:          qgcPal.alertText
            }
        }

        //-- Dismiss Critical Message
        QGCColoredImage {
542
            id:                 systemMessageClose
543 544 545 546 547 548 549 550 551 552 553
            anchors.margins:    ScreenTools.defaultFontPixelHeight * 0.5
            anchors.top:        parent.top
            anchors.right:      parent.right
            width:              ScreenTools.isMobile ? ScreenTools.defaultFontPixelHeight * 1.5 : ScreenTools.defaultFontPixelHeight
            height:             width
            sourceSize.height:  width
            source:             "/res/XDelete.svg"
            fillMode:           Image.PreserveAspectFit
            color:              qgcPal.alertText
            MouseArea {
                anchors.fill:       parent
Gus Grubba's avatar
Gus Grubba committed
554
                anchors.margins:    -ScreenTools.defaultFontPixelHeight
555
                onClicked: {
556
                    systemMessageArea.close()
557 558 559 560 561 562 563 564 565 566 567 568 569 570
                }
            }
        }

        //-- More text below indicator
        QGCColoredImage {
            anchors.margins:    ScreenTools.defaultFontPixelHeight * 0.5
            anchors.bottom:     parent.bottom
            anchors.right:      parent.right
            width:              ScreenTools.isMobile ? ScreenTools.defaultFontPixelHeight * 1.5 : ScreenTools.defaultFontPixelHeight
            height:             width
            sourceSize.height:  width
            source:             "/res/ArrowDown.svg"
            fillMode:           Image.PreserveAspectFit
571
            visible:            systemMessageText.lineCount > 5
572 573 574 575
            color:              qgcPal.alertText
            MouseArea {
                anchors.fill:   parent
                onClicked: {
576
                    systemMessageFlick.flick(0,-500)
577 578 579 580 581 582 583 584
                }
            }
        }
    }

    //-------------------------------------------------------------------------
    //-- Indicator Popups

585
    function showPopUp(item, dropItem) {
586
        indicatorDropdown.currentIndicator = dropItem
587
        indicatorDropdown.currentItem = item
588 589 590 591 592 593 594 595 596
        indicatorDropdown.open()
    }

    Popup {
        id:             indicatorDropdown
        y:              ScreenTools.defaultFontPixelHeight
        modal:          true
        focus:          true
        closePolicy:    Popup.CloseOnEscape | Popup.CloseOnPressOutside
597 598
        property var    currentItem:        null
        property var    currentIndicator:   null
599 600 601 602 603 604 605 606
        background: Rectangle {
            width:  loader.width
            height: loader.height
            color:  Qt.rgba(0,0,0,0)
        }
        Loader {
            id:             loader
            onLoaded: {
607 608 609 610 611
                var centerX = mainWindow.contentItem.mapFromItem(indicatorDropdown.currentItem, 0, 0).x - (loader.width * 0.5)
                if((centerX + indicatorDropdown.width) > (mainWindow.width - ScreenTools.defaultFontPixelWidth)) {
                    centerX = mainWindow.width - indicatorDropdown.width - ScreenTools.defaultFontPixelWidth
                }
                indicatorDropdown.x = centerX
612 613 614 615 616 617 618 619 620 621 622 623
            }
        }
        onOpened: {
            loader.sourceComponent = indicatorDropdown.currentIndicator
        }
        onClosed: {
            loader.sourceComponent = null
            indicatorDropdown.currentIndicator = null
        }
    }

}