/**************************************************************************** * * (c) 2009-2016 QGROUNDCONTROL PROJECT * * QGroundControl is licensed according to the terms in the file * COPYING.md in the root of the source code directory. * ****************************************************************************/ import QtQuick 2.3 import QtQuick.Controls 1.2 import QtQuick.Controls.Styles 1.4 import QtQuick.Dialogs 1.2 import QtLocation 5.3 import QtPositioning 5.3 import QtQuick.Layouts 1.2 import QtQuick.Window 2.2 import QtQml.Models 2.1 import QGroundControl 1.0 import QGroundControl.Airspace 1.0 import QGroundControl.Controllers 1.0 import QGroundControl.Controls 1.0 import QGroundControl.FactSystem 1.0 import QGroundControl.FlightDisplay 1.0 import QGroundControl.FlightMap 1.0 import QGroundControl.Palette 1.0 import QGroundControl.ScreenTools 1.0 import QGroundControl.Vehicle 1.0 import Wima 1.0 /// Flight Display View QGCView { id: root viewPanel: _panel QGCPalette { id: qgcPal; colorGroupEnabled: enabled } property alias guidedController: guidedActionsController property bool activeVehicleJoystickEnabled: _activeVehicle ? _activeVehicle.joystickEnabled : false property var _planMasterController: masterController property var _missionController: _planMasterController.missionController property var _geoFenceController: _planMasterController.geoFenceController property var _rallyPointController: _planMasterController.rallyPointController property var _activeVehicle: QGroundControl.multiVehicleManager.activeVehicle property bool _mainIsMap: QGroundControl.videoManager.hasVideo ? QGroundControl.loadBoolGlobalSetting(_mainIsMapKey, true) : true property bool _isPipVisible: QGroundControl.videoManager.hasVideo ? QGroundControl.loadBoolGlobalSetting(_PIPVisibleKey, true) : false property bool _useChecklist: QGroundControl.settingsManager.appSettings.useChecklist.rawValue property real _savedZoomLevel: 0 property real _margins: ScreenTools.defaultFontPixelWidth / 2 property real _pipSize: flightView.width * 0.2 property alias _guidedController: guidedActionsController property alias _altitudeSlider: altitudeSlider readonly property var _dynamicCameras: _activeVehicle ? _activeVehicle.dynamicCameras : null readonly property bool _isCamera: _dynamicCameras ? _dynamicCameras.cameras.count > 0 : false readonly property bool isBackgroundDark: _mainIsMap ? (_flightMap ? _flightMap.isSatelliteMap : true) : true readonly property real _defaultRoll: 0 readonly property real _defaultPitch: 0 readonly property real _defaultHeading: 0 readonly property real _defaultAltitudeAMSL: 0 readonly property real _defaultGroundSpeed: 0 readonly property real _defaultAirSpeed: 0 readonly property string _mapName: "FlightDisplayView" readonly property string _showMapBackgroundKey: "/showMapBackground" readonly property string _mainIsMapKey: "MainFlyWindowIsMap" readonly property string _PIPVisibleKey: "IsPIPVisible" function setStates() { QGroundControl.saveBoolGlobalSetting(_mainIsMapKey, _mainIsMap) if(_mainIsMap) { //-- Adjust Margins _flightMapContainer.state = "fullMode" _flightVideo.state = "pipMode" //-- Save/Restore Map Zoom Level if(_savedZoomLevel != 0) _flightMap.zoomLevel = _savedZoomLevel else _savedZoomLevel = _flightMap.zoomLevel } else { //-- Adjust Margins _flightMapContainer.state = "pipMode" _flightVideo.state = "fullMode" //-- Set Map Zoom Level _savedZoomLevel = _flightMap.zoomLevel _flightMap.zoomLevel = _savedZoomLevel - 3 } } function setPipVisibility(state) { _isPipVisible = state; QGroundControl.saveBoolGlobalSetting(_PIPVisibleKey, state) } function isInstrumentRight() { if(QGroundControl.corePlugin.options.instrumentWidget) { if(QGroundControl.corePlugin.options.instrumentWidget.source.toString().length) { switch(QGroundControl.corePlugin.options.instrumentWidget.widgetPosition) { case CustomInstrumentWidget.POS_TOP_LEFT: case CustomInstrumentWidget.POS_BOTTOM_LEFT: case CustomInstrumentWidget.POS_CENTER_LEFT: return false; } } } return true; } PlanMasterController { id: masterController Component.onCompleted: start(true /* flyView */) } WimaController { id: wimaController Component.onCompleted: { wimaController.dataContainer = Qt.binding(function() { return dataContainerPointer }) } } BuiltInPreFlightCheckModel { id: preFlightCheckModel } Connections { target: _missionController onResumeMissionUploadFail: guidedActionsController.confirmAction(guidedActionsController.actionResumeMissionUploadFail) } Component.onCompleted: { setStates() if(QGroundControl.corePlugin.options.flyViewOverlay.toString().length) { flyViewOverlay.source = QGroundControl.corePlugin.options.flyViewOverlay } wimaController.masterController = Qt.binding(function() { return masterController; }) wimaController.missionController = Qt.binding(function() { return masterController.missionController; }) } // The following code is used to track vehicle states such that we prompt to remove mission from vehicle when mission completes property bool vehicleArmed: _activeVehicle ? _activeVehicle.armed : true // true here prevents pop up from showing during shutdown property bool vehicleWasArmed: false property bool vehicleInMissionFlightMode: _activeVehicle ? (_activeVehicle.flightMode === _activeVehicle.missionFlightMode) : false property bool promptForMissionRemove: false onVehicleArmedChanged: { if (vehicleArmed) { if (!promptForMissionRemove) { promptForMissionRemove = vehicleInMissionFlightMode vehicleWasArmed = true } } else { if (promptForMissionRemove && (_missionController.containsItems || _geoFenceController.containsItems || _rallyPointController.containsItems)) { // ArduPilot has a strange bug which prevents mission clear from working at certain times, so we can't show this dialog if (!_activeVehicle.apmFirmware) { root.showDialog(missionCompleteDialogComponent, qsTr("Flight Plan complete"), showDialogDefaultWidth, StandardButton.Close) } } promptForMissionRemove = false } } onVehicleInMissionFlightModeChanged: { if (!promptForMissionRemove && vehicleArmed) { promptForMissionRemove = true } } Component { id: missionCompleteDialogComponent QGCViewDialog { property var activeVehicleCopy: _activeVehicle onActiveVehicleCopyChanged: if (!activeVehicleCopy) { hideDialog() } QGCFlickable { anchors.fill: parent contentHeight: column.height ColumnLayout { id: column anchors.margins: _margins anchors.left: parent.left anchors.right: parent.right ColumnLayout { Layout.fillWidth: true spacing: ScreenTools.defaultFontPixelHeight visible: !_activeVehicle.connectionLost || !_guidedController.showResumeMission QGCLabel { Layout.fillWidth: true text: qsTr("%1 Images Taken").arg(_activeVehicle.cameraTriggerPoints.count) horizontalAlignment: Text.AlignHCenter visible: _activeVehicle.cameraTriggerPoints.count != 0 } QGCButton { Layout.fillWidth: true text: qsTr("Remove plan from vehicle") onClicked: { _planMasterController.removeAllFromVehicle() hideDialog() } } QGCButton { Layout.fillWidth: true Layout.alignment: Qt.AlignHCenter text: qsTr("Leave plan on vehicle") onClicked: hideDialog() } Rectangle { Layout.fillWidth: true color: qgcPal.text height: 1 } QGCButton { Layout.fillWidth: true Layout.alignment: Qt.AlignHCenter text: qsTr("Resume Mission From Waypoint %1").arg(_guidedController._resumeMissionIndex) visible: _guidedController.showResumeMission onClicked: { guidedController.executeAction(_guidedController.actionResumeMission, null, null) hideDialog() } } QGCLabel { Layout.fillWidth: true wrapMode: Text.WordWrap text: qsTr("Resume Mission will rebuild the current mission from the last flown waypoint and upload it to the vehicle for the next flight.") visible: _guidedController.showResumeMission } QGCLabel { Layout.fillWidth: true wrapMode: Text.WordWrap color: qgcPal.warningText text: qsTr("If you are changing batteries for Resume Mission do not disconnect from the vehicle when communication is lost.") visible: _guidedController.showResumeMission } } ColumnLayout { Layout.fillWidth: true spacing: ScreenTools.defaultFontPixelHeight visible: _activeVehicle.connectionLost && _guidedController.showResumeMission QGCLabel { Layout.fillWidth: true wrapMode: Text.WordWrap color: qgcPal.warningText text: qsTr("If you are changing batteries for Resume Mission do not disconnect from the vehicle.") } } } } } } Window { id: videoWindow width: !_mainIsMap ? _panel.width : _pipSize height: !_mainIsMap ? _panel.height : _pipSize * (9/16) visible: false Item { id: videoItem anchors.fill: parent } onClosing: { _flightVideo.state = "unpopup" videoWindow.visible = false } } /* This timer will startVideo again after the popup window appears and is loaded. * Such approach was the only one to avoid a crash for windows users */ Timer { id: videoPopUpTimer interval: 2000; running: false; repeat: false onTriggered: { // If state is popup, the next one will be popup-finished if (_flightVideo.state == "popup") { _flightVideo.state = "popup-finished" } QGroundControl.videoManager.startVideo() } } QGCMapPalette { id: mapPal; lightColors: _mainIsMap ? _flightMap.isSatelliteMap : true } QGCViewPanel { id: _panel anchors.fill: parent //-- Map View // For whatever reason, if FlightDisplayViewMap is the _panel item, changing // width/height has no effect. Item { id: _flightMapContainer z: _mainIsMap ? _panel.z + 1 : _panel.z + 2 anchors.left: _panel.left anchors.bottom: _panel.bottom visible: _mainIsMap || _isPipVisible && !QGroundControl.videoManager.fullScreen width: _mainIsMap ? _panel.width : _pipSize height: _mainIsMap ? _panel.height : _pipSize * (9/16) states: [ State { name: "pipMode" PropertyChanges { target: _flightMapContainer anchors.margins: ScreenTools.defaultFontPixelHeight } }, State { name: "fullMode" PropertyChanges { target: _flightMapContainer anchors.margins: 0 } } ] FlightDisplayViewMap { id: _flightMap anchors.fill: parent planMasterController: masterController wimaController: wimaController guidedActionsController: _guidedController flightWidgets: flightDisplayViewWidgets rightPanelWidth: ScreenTools.defaultFontPixelHeight * 9 qgcView: root multiVehicleView: !singleVehicleView.checked scaleState: (_mainIsMap && flyViewOverlay.item) ? (flyViewOverlay.item.scaleState ? flyViewOverlay.item.scaleState : "bottomMode") : "bottomMode" } } //-- Video View Item { id: _flightVideo z: _mainIsMap ? _panel.z + 2 : _panel.z + 1 width: !_mainIsMap ? _panel.width : _pipSize height: !_mainIsMap ? _panel.height : _pipSize * (9/16) anchors.left: _panel.left anchors.bottom: _panel.bottom visible: QGroundControl.videoManager.hasVideo && (!_mainIsMap || _isPipVisible) onParentChanged: { /* If video comes back from popup * correct anchors. * Such thing is not possible with ParentChange. */ if(parent == _panel) { // Do anchors again after popup anchors.left = _panel.left anchors.bottom = _panel.bottom anchors.margins = ScreenTools.defaultFontPixelHeight } } states: [ State { name: "pipMode" PropertyChanges { target: _flightVideo anchors.margins: ScreenTools.defaultFontPixelHeight } PropertyChanges { target: _flightVideoPipControl inPopup: false } }, State { name: "fullMode" PropertyChanges { target: _flightVideo anchors.margins: 0 } PropertyChanges { target: _flightVideoPipControl inPopup: false } }, State { name: "popup" StateChangeScript { script: { // Stop video, restart it again with Timer // Avoiding crashs if ParentChange is not yet done QGroundControl.videoManager.stopVideo() videoPopUpTimer.running = true } } PropertyChanges { target: _flightVideoPipControl inPopup: true } }, State { name: "popup-finished" ParentChange { target: _flightVideo parent: videoItem x: 0 y: 0 width: videoItem.width height: videoItem.height } }, State { name: "unpopup" StateChangeScript { script: { QGroundControl.videoManager.stopVideo() videoPopUpTimer.running = true } } ParentChange { target: _flightVideo parent: _panel } PropertyChanges { target: _flightVideoPipControl inPopup: false } } ] //-- Video Streaming FlightDisplayViewVideo { id: videoStreaming anchors.fill: parent visible: QGroundControl.videoManager.isGStreamer } //-- UVC Video (USB Camera or Video Device) Loader { id: cameraLoader anchors.fill: parent visible: !QGroundControl.videoManager.isGStreamer source: QGroundControl.videoManager.uvcEnabled ? "qrc:/qml/FlightDisplayViewUVC.qml" : "qrc:/qml/FlightDisplayViewDummy.qml" } } QGCPipable { id: _flightVideoPipControl z: _flightVideo.z + 3 width: _pipSize height: _pipSize * (9/16) anchors.left: _panel.left anchors.bottom: _panel.bottom anchors.margins: ScreenTools.defaultFontPixelHeight visible: QGroundControl.videoManager.hasVideo && !QGroundControl.videoManager.fullScreen && _flightVideo.state != "popup" isHidden: !_isPipVisible isDark: isBackgroundDark enablePopup: _mainIsMap onActivated: { _mainIsMap = !_mainIsMap setStates() } onHideIt: { setPipVisibility(!state) } onPopup: { videoWindow.visible = true _flightVideo.state = "popup" } onNewWidth: { _pipSize = newWidth } } Row { id: singleMultiSelector anchors.topMargin: ScreenTools.toolbarHeight + _margins anchors.rightMargin: _margins anchors.right: parent.right anchors.top: parent.top spacing: ScreenTools.defaultFontPixelWidth z: _panel.z + 4 visible: QGroundControl.multiVehicleManager.vehicles.count > 1 ExclusiveGroup { id: multiVehicleSelectorGroup } QGCRadioButton { id: singleVehicleView exclusiveGroup: multiVehicleSelectorGroup text: qsTr("Single") checked: true textColor: mapPal.text } QGCRadioButton { exclusiveGroup: multiVehicleSelectorGroup text: qsTr("Multi-Vehicle") textColor: mapPal.text } } FlightDisplayViewWidgets { id: flightDisplayViewWidgets z: _panel.z + 4 height: ScreenTools.availableHeight - (singleMultiSelector.visible ? singleMultiSelector.height + _margins : 0) anchors.left: parent.left anchors.right: altitudeSlider.visible ? altitudeSlider.left : parent.right anchors.bottom: parent.bottom qgcView: root useLightColors: isBackgroundDark missionController: _missionController visible: singleVehicleView.checked && !QGroundControl.videoManager.fullScreen } FlightDisplayWimaMenu { id: wimaMenu z: _panel.z + 4 anchors.left: parent.left anchors.bottom: parent.bottom anchors.leftMargin: ScreenTools.defaultFontPixelHeight * 0.25 anchors.bottomMargin: ScreenTools.defaultFontPixelHeight * 0.25 height: 300 width: 300 wimaController: wimaController } //------------------------------------------------------------------------- //-- Loader helper for plugins to overlay elements over the fly view Loader { id: flyViewOverlay z: flightDisplayViewWidgets.z + 1 visible: !QGroundControl.videoManager.fullScreen height: ScreenTools.availableHeight anchors.left: parent.left anchors.right: altitudeSlider.visible ? altitudeSlider.left : parent.right anchors.bottom: parent.bottom property var qgcView: root } MultiVehicleList { anchors.margins: _margins anchors.top: singleMultiSelector.bottom anchors.right: parent.right anchors.bottom: parent.bottom width: ScreenTools.defaultFontPixelWidth * 30 visible: !singleVehicleView.checked && !QGroundControl.videoManager.fullScreen z: _panel.z + 4 guidedActionsController: _guidedController } //-- Virtual Joystick Loader { id: virtualJoystickMultiTouch z: _panel.z + 5 width: parent.width - (_flightVideoPipControl.width / 2) height: Math.min(ScreenTools.availableHeight * 0.25, ScreenTools.defaultFontPixelWidth * 16) visible: (_virtualJoystick ? _virtualJoystick.value : false) && !QGroundControl.videoManager.fullScreen && !(_activeVehicle ? _activeVehicle.highLatencyLink : false) anchors.bottom: _flightVideoPipControl.top anchors.bottomMargin: ScreenTools.defaultFontPixelHeight * 2 anchors.horizontalCenter: flightDisplayViewWidgets.horizontalCenter source: "qrc:/qml/VirtualJoystick.qml" active: (_virtualJoystick ? _virtualJoystick.value : false) && !(_activeVehicle ? _activeVehicle.highLatencyLink : false) property bool useLightColors: isBackgroundDark property Fact _virtualJoystick: QGroundControl.settingsManager.appSettings.virtualJoystick } ToolStrip { visible: (_activeVehicle ? _activeVehicle.guidedModeSupported : true) && !QGroundControl.videoManager.fullScreen id: toolStrip anchors.leftMargin: isInstrumentRight() ? ScreenTools.defaultFontPixelWidth : undefined anchors.left: isInstrumentRight() ? _panel.left : undefined anchors.rightMargin:isInstrumentRight() ? undefined : ScreenTools.defaultFontPixelWidth anchors.right: isInstrumentRight() ? undefined : _panel.right anchors.topMargin: ScreenTools.toolbarHeight + (_margins * 2) anchors.top: _panel.top z: _panel.z + 4 title: qsTr("Fly") maxHeight: (_flightVideo.visible ? _flightVideo.y : parent.height) - toolStrip.y buttonVisible: [ _useChecklist, _guidedController.showTakeoff || !_guidedController.showLand, _guidedController.showLand && !_guidedController.showTakeoff, true, true, true ] buttonEnabled: [ _useChecklist && _activeVehicle, _guidedController.showTakeoff, _guidedController.showLand, _guidedController.showRTL, _guidedController.showPause, _anyActionAvailable ] property bool _anyActionAvailable: _guidedController.showStartMission || _guidedController.showResumeMission || _guidedController.showChangeAlt || _guidedController.showLandAbort property var _actionModel: [ { title: _guidedController.startMissionTitle, text: _guidedController.startMissionMessage, action: _guidedController.actionStartMission, visible: _guidedController.showStartMission }, { title: _guidedController.continueMissionTitle, text: _guidedController.continueMissionMessage, action: _guidedController.actionContinueMission, visible: _guidedController.showContinueMission }, { title: _guidedController.resumeMissionTitle, text: _guidedController.resumeMissionMessage, action: _guidedController.actionResumeMission, visible: _guidedController.showResumeMission }, { title: _guidedController.changeAltTitle, text: _guidedController.changeAltMessage, action: _guidedController.actionChangeAlt, visible: _guidedController.showChangeAlt }, { title: _guidedController.landAbortTitle, text: _guidedController.landAbortMessage, action: _guidedController.actionLandAbort, visible: _guidedController.showLandAbort } ] model: [ { name: "Checklist", iconSource: "/qmlimages/check.svg", dropPanelComponent: checklistDropPanel }, { name: _guidedController.takeoffTitle, iconSource: "/res/takeoff.svg", action: _guidedController.actionTakeoff }, { name: _guidedController.landTitle, iconSource: "/res/land.svg", action: _guidedController.actionLand }, { name: _guidedController.rtlTitle, iconSource: "/res/rtl.svg", action: _guidedController.actionRTL }, { name: _guidedController.pauseTitle, iconSource: "/res/pause-mission.svg", action: _guidedController.actionPause }, { name: qsTr("Action"), iconSource: "/res/action.svg", action: -1 } ] onClicked: { guidedActionsController.closeAll() var action = model[index].action if (action === -1) { guidedActionList.model = _actionModel guidedActionList.visible = true } else { _guidedController.confirmAction(action) } } } GuidedActionsController { id: guidedActionsController missionController: _missionController confirmDialog: guidedActionConfirm actionList: guidedActionList altitudeSlider: _altitudeSlider z: _flightVideoPipControl.z + 1 onShowStartMissionChanged: { if (showStartMission) { confirmAction(actionStartMission) } } onShowContinueMissionChanged: { if (showContinueMission) { confirmAction(actionContinueMission) } } onShowLandAbortChanged: { if (showLandAbort) { confirmAction(actionLandAbort) } } /// Close all dialogs function closeAll() { mainWindow.enableToolbar() rootLoader.sourceComponent = null guidedActionConfirm.visible = false guidedActionList.visible = false altitudeSlider.visible = false } } GuidedActionConfirm { id: guidedActionConfirm anchors.margins: _margins anchors.bottom: parent.bottom anchors.horizontalCenter: parent.horizontalCenter guidedController: _guidedController altitudeSlider: _altitudeSlider } GuidedActionList { id: guidedActionList anchors.margins: _margins anchors.bottom: parent.bottom anchors.horizontalCenter: parent.horizontalCenter guidedController: _guidedController } //-- Altitude slider GuidedAltitudeSlider { id: altitudeSlider anchors.margins: _margins anchors.right: parent.right anchors.topMargin: ScreenTools.toolbarHeight + _margins anchors.top: parent.top anchors.bottom: parent.bottom z: _guidedController.z radius: ScreenTools.defaultFontPixelWidth / 2 width: ScreenTools.defaultFontPixelWidth * 10 color: qgcPal.window visible: false } } //-- Airspace Indicator Rectangle { id: airspaceIndicator width: airspaceRow.width + (ScreenTools.defaultFontPixelWidth * 3) height: airspaceRow.height * 1.25 color: qgcPal.globalTheme === QGCPalette.Light ? Qt.rgba(1,1,1,0.95) : Qt.rgba(0,0,0,0.75) visible: QGroundControl.airmapSupported && _mainIsMap && flightPermit && flightPermit !== AirspaceFlightPlanProvider.PermitNone && !messageArea.visible && !criticalMmessageArea.visible radius: 3 border.width: 1 border.color: qgcPal.globalTheme === QGCPalette.Light ? Qt.rgba(0,0,0,0.35) : Qt.rgba(1,1,1,0.35) anchors.top: parent.top anchors.topMargin: ScreenTools.toolbarHeight + (ScreenTools.defaultFontPixelHeight * 0.25) anchors.horizontalCenter: parent.horizontalCenter Row { id: airspaceRow spacing: ScreenTools.defaultFontPixelWidth anchors.centerIn: parent QGCLabel { text: airspaceIndicator.providerName+":"; anchors.verticalCenter: parent.verticalCenter; } QGCLabel { text: { if(airspaceIndicator.flightPermit) { if(airspaceIndicator.flightPermit === AirspaceFlightPlanProvider.PermitPending) return qsTr("Approval Pending") if(airspaceIndicator.flightPermit === AirspaceFlightPlanProvider.PermitAccepted || airspaceIndicator.flightPermit === AirspaceFlightPlanProvider.PermitNotRequired) return qsTr("Flight Approved") if(airspaceIndicator.flightPermit === AirspaceFlightPlanProvider.PermitRejected) return qsTr("Flight Rejected") } return "" } color: { if(airspaceIndicator.flightPermit) { if(airspaceIndicator.flightPermit === AirspaceFlightPlanProvider.PermitPending) return qgcPal.colorOrange if(airspaceIndicator.flightPermit === AirspaceFlightPlanProvider.PermitAccepted || airspaceIndicator.flightPermit === AirspaceFlightPlanProvider.PermitNotRequired) return qgcPal.colorGreen } return qgcPal.colorRed } anchors.verticalCenter: parent.verticalCenter; } } property var flightPermit: QGroundControl.airmapSupported ? QGroundControl.airspaceManager.flightPlan.flightPermitStatus : null property string providerName: QGroundControl.airspaceManager.providerName } //-- Checklist GUI Component { id: checklistDropPanel PreFlightCheckList { model: preFlightCheckModel } } }