Skip to content
MainQmlWindow.qml 28 KiB
Newer Older
/****************************************************************************
 *
 *   (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.
 *
 ****************************************************************************/


import QtQuick          2.12
import QtQuick.Controls 2.4
import QtQuick.Dialogs  1.3
import QtQuick.Layouts  1.12

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 {
    id:         mainWindow
    width:      1024
    height:     768
    visible:    true

Gus Grubba's avatar
Gus Grubba committed
    readonly property real      _topBottomMargins:          ScreenTools.defaultFontPixelHeight * 0.5
    readonly property string    _mainToolbarIndicators:     QGroundControl.corePlugin.options.mainToolbarIndicatorsUrl
    readonly property string    _planToolbarIndicators:     QGroundControl.corePlugin.options.planToolbarIndicatorsUrl
    readonly property string    _settingsViewSource:        "AppSettings.qml"
    readonly property string    _setupViewSource:           "SetupView.qml"
    readonly property string    _planViewSource:            "PlanView.qml"
    readonly property string    _analyzeViewSource:         !ScreenTools.isMobile ? "AnalyzeView.qml" : "MavlinkConsolePage.qml"

    //-------------------------------------------------------------------------
    //-- Global Scope Variables

Gus Grubba's avatar
Gus Grubba committed
    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

    property var                currentPlanMissionItem:     null
    property var                planMasterControllerPlan:   null
    property var                planMasterControllerView:   null

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

    QGCPalette { id: qgcPal; colorGroupEnabled: true }

    //-------------------------------------------------------------------------
    //-- Actions

    signal armVehicle
    signal disarmVehicle
    signal vtolTransitionToFwdFlight
    signal vtolTransitionToMRFlight

    //-------------------------------------------------------------------------
    //-- Global Scope Functions

    function disableToolbar() {
        drawer.enabled = false
    }

    function enableToolbar() {
        drawer.enabled = true
    }

    function viewSwitch(isPlanView) {
        enableToolbar()
        drawer.close()
        if(isPlanView) {
Gus Grubba's avatar
Gus Grubba committed
            rootBackground.visible = false
            planViewLoader.visible = true
            if(toolbarIndicators.source !== _planToolbarIndicators) {
                toolbarIndicators.source  = _planToolbarIndicators
            }
Gus Grubba's avatar
Gus Grubba committed
            rootBackground.visible = true
            planViewLoader.visible = false
            if(toolbarIndicators.source !== _mainToolbarIndicators) {
                toolbarIndicators.source  = _mainToolbarIndicators
            }
        }
    }

    function showFlyView() {
        viewSwitch(false)
        mainContentWindow.source = ""
        if(toolbarIndicators.source !== _mainToolbarIndicators) {
            toolbarIndicators.source  = _mainToolbarIndicators
        }
    }

    function showPlanView() {
        viewSwitch(true)
        if (mainContentWindow.source !== _planViewSource) {
            mainContentWindow.source  = _planViewSource
        }
    }

    function showAnalyzeView() {
        viewSwitch(false)
        if (mainContentWindow.source !== _analyzeViewSource) {
            mainContentWindow.source  = _analyzeViewSource
        }
    }

    function showSetupView() {
        viewSwitch(false)
        if (mainContentWindow.source !== _setupViewSource) {
            mainContentWindow.source  = _setupViewSource
        }
    }

    function showSettingsView() {
        viewSwitch(false)
        if (mainContentWindow.source !== _settingsViewSource) {
            mainContentWindow.source  = _settingsViewSource
        }
    }

    //-------------------------------------------------------------------------
    //-- 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
    //-- 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() {
            if (planViewLoader.item && planViewLoader.item.dirty) {
                unsavedMissionCloseDialog.open()
            } else {
                activeConnectionsCloseDialog.check()
            }
        }
    }

    //-------------------------------------------------------------------------
    //-- Main, full window background (Fly View)
    background: Item {
        id:             rootBackground
        anchors.fill:   parent
        FlightDisplayView {
            id:             flightView
            anchors.fill:   parent
Gus Grubba's avatar
Gus Grubba committed
            //-----------------------------------------------------------------
            //-- Loader helper for any child, no matter how deep can display an
            //   element on top of the video window.
            Loader {
                id:             rootVideoLoader
                anchors.centerIn: parent
            }
Gus Grubba's avatar
Gus Grubba committed
    //-------------------------------------------------------------------------
    //-- Plan View
    Loader {
        id:                 planViewLoader
        anchors.fill:       parent
        visible:            false
        source:             "PlanView.qml"
    }

    //-------------------------------------------------------------------------
    //-- Global Indicator Bar
    header: ToolBar {
        height:         ScreenTools.toolbarHeight
        visible:        !QGroundControl.videoManager.fullScreen
        background:     Rectangle {
            color:      qgcPal.globalTheme === QGCPalette.Light ? Qt.rgba(1,1,1,0.8) : Qt.rgba(0,0,0,0.75)
        }
        RowLayout {
            anchors.fill:               parent
Gus Grubba's avatar
Gus Grubba committed
            spacing:                    0
            Rectangle {
                height:                 parent.height
                width:                  height
                color:                  qgcPal.brandingPurple
                QGCColoredImage {
                    anchors.centerIn:       parent
                    height:                 ScreenTools.defaultFontPixelHeight * 2
                    width:                  height
                    sourceSize.height:      parent.height
                    fillMode:               Image.PreserveAspectFit
                    source:                 "/res/QGCLogoWhite"
                    color:                  "white"
                }
                MouseArea {
                    anchors.fill:       parent
                    onClicked:{
                        if(drawer.visible) {
                            drawer.close()
                        } else {
                            drawer.open()
                        }
                    }
                }
            }
            Loader {
                id:                 toolbarIndicators
                height:             parent.height
Gus Grubba's avatar
Gus Grubba committed
                source:             _mainToolbarIndicators
                Layout.fillWidth:   true
            }
        }
    }

    //-------------------------------------------------------------------------
    // Small parameter download progress bar
    Rectangle {
        x:              0
        y:              header.height
        height:         ScreenTools.toolbarHeight * 0.05
        width:          activeVehicle ? activeVehicle.parameterManager.loadProgress * mainWindow.width : 0
        color:          qgcPal.colorGreen
        visible:        !largeProgressBar.visible
    }

    //-------------------------------------------------------------------------
    // Large parameter download progress bar
    Rectangle {
        id:             largeProgressBar
        x:              0
        y:              header.height
        height:         ScreenTools.toolbarHeight
        width:          mainWindow.width
        color:          qgcPal.window
        visible:        _showLargeProgress

        property bool _initialDownloadComplete: activeVehicle ? activeVehicle.parameterManager.parametersReady : true
        property bool _userHide:                false
        property bool _showLargeProgress:       !_initialDownloadComplete && !_userHide && qgcPal.globalTheme === QGCPalette.Light

        Connections {
            target:                 QGroundControl.multiVehicleManager
            onActiveVehicleChanged: largeProgressBar._userHide = false
        }
        Rectangle {
            anchors.top:    parent.top
            anchors.bottom: parent.bottom
            width:          activeVehicle ? activeVehicle.parameterManager.loadProgress * mainWindow.width : 0
            color:          qgcPal.colorGreen
        }
        QGCLabel {
            anchors.centerIn:   parent
            text:               qsTr("Downloading Parameters")
            font.pointSize:     ScreenTools.largeFontPointSize
        }
        QGCLabel {
            anchors.margins:    _margin
            anchors.right:      parent.right
            anchors.bottom:     parent.bottom
            text:               qsTr("Click anywhere to hide")

            property real _margin: ScreenTools.defaultFontPixelWidth * 0.5
        }
        MouseArea {
            anchors.fill:   parent
            onClicked:      largeProgressBar._userHide = true
        }
    }

    //-------------------------------------------------------------------------
    //-- Navigation Drawer (Left to Right, on command or using touch gestures)
    Drawer {
        id:         drawer
        y:          header.height
        width:      navButtonWidth
        height:     mainWindow.height - header.height
        background: Rectangle {
            color:  qgcPal.globalTheme === QGCPalette.Light ? "white" : "black"
        }
        ButtonGroup {
            buttons: buttons.children
        }
        ColumnLayout {
            id:                     buttons
            anchors.top:            parent.top
            anchors.topMargin:      ScreenTools.defaultFontPixelHeight * 0.5
            anchors.left:           parent.left
            anchors.right:          parent.right
            spacing:                ScreenTools.defaultFontPixelHeight * 0.5
            QGCToolBarButton {
                text:               "Fly"
                icon.source:        "/qmlimages/PaperPlane.svg"
                Layout.fillWidth:   true
                onClicked: {
                    checked = true
                    showFlyView()
                }
            }
            QGCToolBarButton {
                text:               "Plan"
                icon.source:        "/qmlimages/Plan.svg"
                Layout.fillWidth:   true
                onClicked: {
                    checked = true
                    showPlanView()
                }
            }
            QGCToolBarButton {
                text:               "Analyze"
                icon.source:        "/qmlimages/Analyze.svg"
                Layout.fillWidth:   true
                onClicked: {
                    checked = true
                    showAnalyzeView()
                }
            }
            QGCToolBarButton {
                text:               "Vehicle Setup"
                icon.source:        "/qmlimages/Gears.svg"
                Layout.fillWidth:   true
                onClicked: {
                    checked = true
                    showSetupView()
                }
            }
            QGCToolBarButton {
                text:               "Settings"
                icon.source:        "/qmlimages/Gears.svg"
                Layout.fillWidth:   true
                onClicked: {
                    checked = true
                    showSettingsView()
                }
            }
        }
    }

    //-------------------------------------------------------------------------
    //-- Current content
    Loader {
        id:             mainContentWindow
        anchors.fill:   parent
    }

    //-------------------------------------------------------------------------
    //-- Loader helper for any child, no matter how deep can display an element
    //   in the middle of the main window.
    Loader {
        id:             rootLoader
        anchors.centerIn: parent
    }

    //-------------------------------------------------------------------------
    //-- Vehicle Messages

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

    function showVehicleMessages() {
        if(!vehicleMessageArea.visible) {
            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")
            }
            vehicleMessageArea.open()
        if(vehicleMessageArea.visible) {
            messageText.append(formatMessage(formatedMessage))
            //-- Hack to scroll down
            messageFlick.flick(0,-500)
        }
    }

    Popup {
        id:                 vehicleMessageArea
        width:              mainWindow.width  * 0.666
        height:             mainWindow.height * 0.666
        modal:              true
        focus:              true
        closePolicy:        Popup.CloseOnEscape | Popup.CloseOnPressOutside
        anchors.centerIn:   parent
        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
            }
        }
        //-- Dismiss Vehicle Messages
        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: {
                    vehicleMessageArea.close()
                }
            }
        }
        //-- 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();
                        vehicleMessageArea.close()
                    }
                }
            }
        }
    }

    //-------------------------------------------------------------------------
    //-- System Messages
    property string _systemMessage:   ""
        vehicleMessageArea.close()
        if(systemMessageArea.visible || QGroundControl.videoManager.fullScreen) {
            _systemMessage = message
            systemMessageArea.open()
        }
    }

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

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

    Popup {
        id:                 systemMessageArea
        y:                  ScreenTools.defaultFontPixelHeight
        x:                  (mainWindow.width - width) * 0.5
        width:              mainWindow.width  * 0.55
        height:             ScreenTools.defaultFontPixelHeight * 6
        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: {
            systemMessageText.text = mainWindow._systemMessage
        }

        onClosed: {
            //-- Are there messages in the waiting queue?
            if(mainWindow._messageQueue.length) {
                mainWindow._systemMessage = ""
                //-- Show all messages in queue
                for (var i = 0; i < mainWindow._messageQueue.length; i++) {
                    var text = mainWindow._messageQueue[i]
                    mainWindow._systemMessage.append(text)
                systemMessageArea.open()
                mainWindow._systemMessage = ""
            id:                 systemMessageFlick
            anchors.margins:    ScreenTools.defaultFontPixelHeight
            anchors.fill:       parent
            contentHeight:      systemMessageText.height
            contentWidth:       systemMessageText.width
            boundsBehavior:     Flickable.StopAtBounds
            pixelAligned:       true
            clip:               true
            TextEdit {
                id:             systemMessageText
                width:          systemMessageArea.width - systemMessageClose.width - (ScreenTools.defaultFontPixelHeight * 2)
                anchors.centerIn: parent
                readOnly:       true
                textFormat:     TextEdit.RichText
                font.pointSize: ScreenTools.defaultFontPointSize
                font.family:    ScreenTools.demiboldFontFamily
                wrapMode:       TextEdit.WordWrap
                color:          qgcPal.alertText
            }
        }

        //-- Dismiss Critical Message
        QGCColoredImage {
            id:                 systemMessageClose
            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
                anchors.margins:    ScreenTools.isMobile ? -ScreenTools.defaultFontPixelHeight : 0
                onClicked: {
                    systemMessageArea.close()
                }
            }
        }

        //-- 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
            visible:            systemMessageText.lineCount > 5
            color:              qgcPal.alertText
            MouseArea {
                anchors.fill:   parent
                onClicked: {
                    systemMessageFlick.flick(0,-500)
                }
            }
        }
    }

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

    function showPopUp(dropItem, centerX) {
        indicatorDropdown.centerX = centerX
        indicatorDropdown.currentIndicator = dropItem
        indicatorDropdown.open()
    }

    Popup {
        id:             indicatorDropdown
        y:              ScreenTools.defaultFontPixelHeight
        modal:          true
        focus:          true
        closePolicy:    Popup.CloseOnEscape | Popup.CloseOnPressOutside
        property var    currentIndicator: null
        property real   centerX: 0
        background: Rectangle {
            width:  loader.width
            height: loader.height
            color:  Qt.rgba(0,0,0,0)
        }
        Loader {
            id:             loader
            onLoaded: {
                indicatorDropdown.x = mapFromGlobal(indicatorDropdown.centerX, 0).x
                console.log(indicatorDropdown.x)
            }
        }
        onOpened: {
            loader.sourceComponent = indicatorDropdown.currentIndicator
        }
        onClosed: {
            loader.sourceComponent = null
            indicatorDropdown.currentIndicator = null
        }
    }

}