/**************************************************************************** * * (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 /// Flight Display View Item { PlanMasterController { id: _planController Component.onCompleted: { start(true /* flyView */) mainWindow.planMasterControllerView = _planController } } property alias guidedController: guidedActionsController property bool activeVehicleJoystickEnabled: activeVehicle ? activeVehicle.joystickEnabled : false property var _missionController: _planController.missionController property var _geoFenceController: _planController.geoFenceController property var _rallyPointController: _planController.rallyPointController 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: mainWindow.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 ? (mainWindow.flightDisplayMap ? mainWindow.flightDisplayMap.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) { if(mainWindow.flightDisplayMap) { mainWindow.flightDisplayMap.zoomLevel = _savedZoomLevel } } else { if(mainWindow.flightDisplayMap) { _savedZoomLevel = mainWindow.flightDisplayMap.zoomLevel } } } else { //-- Adjust Margins _flightMapContainer.state = "pipMode" _flightVideo.state = "fullMode" //-- Set Map Zoom Level if(mainWindow.flightDisplayMap) { _savedZoomLevel = mainWindow.flightDisplayMap.zoomLevel mainWindow.flightDisplayMap.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; } BuiltInPreFlightCheckModel { id: preFlightCheckModel } Connections { target: _missionController onResumeMissionUploadFail: guidedActionsController.confirmAction(guidedActionsController.actionResumeMissionUploadFail) } Connections { target: mainWindow onArmVehicle: guidedController.confirmAction(guidedController.actionArm) onDisarmVehicle: { if (guidedController.showEmergenyStop) { guidedController.confirmAction(guidedController.actionEmergencyStop) } else { guidedController.confirmAction(guidedController.actionDisarm) } } onVtolTransitionToFwdFlight: guidedController.confirmAction(guidedController.actionVtolTransitionToFwdFlight) onVtolTransitionToMRFlight: guidedController.confirmAction(guidedController.actionVtolTransitionToMRFlight) onFlightDisplayMapChanged: setStates() } Component.onCompleted: { if(QGroundControl.corePlugin.options.flyViewOverlay.toString().length) { flyViewOverlay.source = QGroundControl.corePlugin.options.flyViewOverlay } } // 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) { mainWindow.showDialog(missionCompleteDialogComponent, qsTr("Flight Plan complete"), mainWindow.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: { _planController.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 ? _mapAndVideo.width : _pipSize height: !_mainIsMap ? _mapAndVideo.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 ? mainWindow.flightDisplayMap.isSatelliteMap : true } Item { id: _mapAndVideo anchors.fill: parent //-- Map View Item { id: _flightMapContainer z: _mainIsMap ? _mapAndVideo.z + 1 : _mapAndVideo.z + 2 anchors.left: _mapAndVideo.left anchors.bottom: _mapAndVideo.bottom visible: _mainIsMap || _isPipVisible && !QGroundControl.videoManager.fullScreen width: _mainIsMap ? _mapAndVideo.width : _pipSize height: _mainIsMap ? _mapAndVideo.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: _fMap anchors.fill: parent guidedActionsController: _guidedController missionController: _planController flightWidgets: flightDisplayViewWidgets rightPanelWidth: ScreenTools.defaultFontPixelHeight * 9 multiVehicleView: !singleVehicleView.checked scaleState: (_mainIsMap && flyViewOverlay.item) ? (flyViewOverlay.item.scaleState ? flyViewOverlay.item.scaleState : "bottomMode") : "bottomMode" Component.onCompleted: { mainWindow.flightDisplayMap = _fMap } } } //-- Video View Item { id: _flightVideo z: _mainIsMap ? _mapAndVideo.z + 2 : _mapAndVideo.z + 1 width: !_mainIsMap ? _mapAndVideo.width : _pipSize height: !_mainIsMap ? _mapAndVideo.height : _pipSize * (9/16) anchors.left: _mapAndVideo.left anchors.bottom: _mapAndVideo.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 == _mapAndVideo) { // Do anchors again after popup anchors.left = _mapAndVideo.left anchors.bottom = _mapAndVideo.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: _mapAndVideo } 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: _mapAndVideo.left anchors.bottom: _mapAndVideo.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: _mapAndVideo.z + 4 visible: QGroundControl.multiVehicleManager.vehicles.count > 1 && QGroundControl.corePlugin.options.enableMultiVehicleList QGCRadioButton { id: singleVehicleView text: qsTr("Single") checked: true textColor: mapPal.text } QGCRadioButton { text: qsTr("Multi-Vehicle") textColor: mapPal.text } } FlightDisplayViewWidgets { id: flightDisplayViewWidgets z: _mapAndVideo.z + 4 height: availableHeight - (singleMultiSelector.visible ? singleMultiSelector.height + _margins : 0) - (ScreenTools.defaultFontPixelHeight * 0.5) anchors.left: parent.left anchors.right: altitudeSlider.visible ? altitudeSlider.left : parent.right anchors.bottom: parent.bottom useLightColors: isBackgroundDark missionController: _missionController visible: singleVehicleView.checked && !QGroundControl.videoManager.fullScreen } //------------------------------------------------------------------------- //-- Loader helper for plugins to overlay elements over the fly view Loader { id: flyViewOverlay z: flightDisplayViewWidgets.z + 1 visible: !QGroundControl.videoManager.fullScreen height: mainWindow.height - mainWindow.header.height anchors.left: parent.left anchors.right: altitudeSlider.visible ? altitudeSlider.left : parent.right anchors.bottom: parent.bottom } 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 && QGroundControl.corePlugin.options.enableMultiVehicleList z: _mapAndVideo.z + 4 guidedActionsController: _guidedController } //-- Virtual Joystick Loader { id: virtualJoystickMultiTouch z: _mapAndVideo.z + 5 width: parent.width - (_flightVideoPipControl.width / 2) height: Math.min(mainWindow.height * 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 * 2 : undefined anchors.left: isInstrumentRight() ? _mapAndVideo.left : undefined anchors.rightMargin:isInstrumentRight() ? undefined : ScreenTools.defaultFontPixelWidth anchors.right: isInstrumentRight() ? undefined : _mapAndVideo.right anchors.topMargin: mainWindow.header.height + (ScreenTools.defaultFontPixelHeight * 0.5) anchors.top: parent.top z: _mapAndVideo.z + 4 maxHeight: (_flightVideo.visible ? _flightVideo.y : parent.height) - toolStrip.y buttonVisible: [true, _useChecklist, _guidedController.showTakeoff || !_guidedController.showLand, _guidedController.showLand && !_guidedController.showTakeoff, true, _guidedController.showPause, !_guidedController.showPause ] buttonEnabled: [true, _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: "Plan", iconSource: "/qmlimages/Plan.svg", }, { 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() if(index === 0) { mainWindow.showPlanView() } else { 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() { 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 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 } } }