地面站终端 App
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

804 lines
31 KiB

/****************************************************************************
*
* (c) 2009-2020 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.11
import QtQuick.Controls 2.4
import QtQuick.Dialogs 1.3
import QtQuick.Layouts 1.11
import QtQuick.Window 2.11
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
/// @brief Native QML top level window
/// All properties defined here are visible to all QML pages.
ApplicationWindow {
id: mainWindow
minimumWidth: ScreenTools.isMobile ? Screen.width : Math.min(ScreenTools.defaultFontPixelWidth * 100, Screen.width)
minimumHeight: ScreenTools.isMobile ? Screen.height : Math.min(ScreenTools.defaultFontPixelWidth * 50, Screen.height)
visible: true
Component.onCompleted: {
//-- Full screen on mobile or tiny screens
if (ScreenTools.isMobile || Screen.height / ScreenTools.realPixelDensity < 120) {
mainWindow.showFullScreen()
} 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)
}
// Start the sequence of first run prompt(s)
firstRunPromptManager.nextPrompt()
}
QtObject {
id: firstRunPromptManager
property var currentDialog: null
property var rgPromptIds: QGroundControl.corePlugin.firstRunPromptsToShow()
property int nextPromptIdIndex: 0
onRgPromptIdsChanged: console.log(QGroundControl.corePlugin, QGroundControl.corePlugin.firstRunPromptsToShow())
function clearNextPromptSignal() {
if (currentDialog) {
currentDialog.closed.disconnect(nextPrompt)
}
}
function nextPrompt() {
if (nextPromptIdIndex < rgPromptIds.length) {
currentDialog = showPopupDialogFromSource(QGroundControl.corePlugin.firstRunPromptResource(rgPromptIds[nextPromptIdIndex]))
currentDialog.closed.connect(nextPrompt)
nextPromptIdIndex++
} else {
currentDialog = null
showPreFlightChecklistIfNeeded()
}
}
}
property var _rgPreventViewSwitch: [ false ]
readonly property real _topBottomMargins: ScreenTools.defaultFontPixelHeight * 0.5
//-------------------------------------------------------------------------
//-- Global Scope Variables
property var activeVehicle: QGroundControl.multiVehicleManager.activeVehicle
property string formatedMessage: activeVehicle ? activeVehicle.formatedMessage : ""
/// Indicates usable height between toolbar and footer
property real availableHeight: mainWindow.height - mainWindow.header.height - mainWindow.footer.height
property var planMasterControllerPlanView: null
property var currentPlanMissionItem: planMasterControllerPlanView ? planMasterControllerPlanView.missionController.currentPlanViewItem : null
property var planMasterControllerFlyView: flightView.planController
property var guidedControllerFlyView: flightView.guidedController
readonly property string navButtonWidth: ScreenTools.defaultFontPixelWidth * 24
readonly property real defaultTextHeight: ScreenTools.defaultFontPixelHeight
readonly property real defaultTextWidth: ScreenTools.defaultFontPixelWidth
/// Default color palette used throughout the UI
QGCPalette { id: qgcPal; colorGroupEnabled: true }
//-------------------------------------------------------------------------
//-- Actions
signal armVehicleRequest
signal disarmVehicleRequest
signal vtolTransitionToFwdFlightRequest
signal vtolTransitionToMRFlightRequest
signal showPreFlightChecklistIfNeeded
//-------------------------------------------------------------------------
//-- Global Scope Functions
/// Prevent view switching
function pushPreventViewSwitch() {
_rgPreventViewSwitch.push(true)
}
/// Allow view switching
function popPreventViewSwitch() {
if (_rgPreventViewSwitch.length == 1) {
console.warn("mainWindow.popPreventViewSwitch called when nothing pushed")
return
}
_rgPreventViewSwitch.pop()
}
/// @return true: View switches are not currently allowed
function preventViewSwitch() {
return _rgPreventViewSwitch[_rgPreventViewSwitch.length - 1]
}
function viewSwitch(currentToolbar) {
settingsWindow.visible = false
setupWindow.visible = false
analyzeWindow.visible = false
flightView.visible = false
planViewLoader.visible = false
toolbar.currentToolbar = currentToolbar
}
function showFlyView() {
if (!flightView.visible) {
mainWindow.showPreFlightChecklistIfNeeded()
}
viewSwitch(toolbar.flyViewToolbar)
flightView.visible = true
}
function showPlanView() {
viewSwitch(toolbar.planViewToolbar)
planViewLoader.visible = true
}
function showAnalyzeView() {
viewSwitch(toolbar.simpleToolbar)
analyzeWindow.visible = true
}
function showSetupView() {
viewSwitch(toolbar.simpleToolbar)
setupWindow.visible = true
}
function showSettingsView() {
viewSwitch(toolbar.simpleToolbar)
settingsWindow.visible = true
}
//-------------------------------------------------------------------------
//-- Global simple message dialog
function showMessageDialog(title, text) {
var dialog = simpleMessageDialog.createObject(mainWindow, { title: title, text: text })
dialog.open()
}
Component {
id: simpleMessageDialog
MessageDialog {
standardButtons: StandardButton.Ok
modality: Qt.ApplicationModal
visible: false
}
}
/// Saves main window position and size
MainWindowSavedState {
window: mainWindow
}
//-------------------------------------------------------------------------
//-- Global complex dialog
/// Shows a QGCViewDialogContainer based dialog
/// @param component The dialog contents
/// @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 showComponentDialog(component, title, charWidth, buttons) {
var dialogWidth = charWidth === showDialogFullWidth ? mainWindow.width : ScreenTools.defaultFontPixelWidth * charWidth
var dialog = dialogDrawerComponent.createObject(mainWindow, { width: dialogWidth, dialogComponent: component, dialogTitle: title, dialogButtons: buttons })
mainWindow.pushPreventViewSwitch()
dialog.open()
}
Component {
id: dialogDrawerComponent
QGCViewDialogContainer {
y: mainWindow.header.height
height: mainWindow.height - mainWindow.header.height
onClosed: mainWindow.popPreventViewSwitch()
}
}
// Dialogs based on QGCPopupDialog
function showPopupDialogFromComponent(component, properties) {
var dialog = popupDialogContainerComponent.createObject(mainWindow, { dialogComponent: component, dialogProperties: properties })
dialog.open()
return dialog
}
function showPopupDialogFromSource(source, properties) {
var dialog = popupDialogContainerComponent.createObject(mainWindow, { dialogSource: source, dialogProperties: properties })
dialog.open()
return dialog
}
Component {
id: popupDialogContainerComponent
QGCPopupDialogContainer { }
}
property bool _forceClose: false
function finishCloseProcess() {
_forceClose = true
// For some reason on the Qml side Qt doesn't automatically disconnect a signal when an object is destroyed.
// So we have to do it ourselves otherwise the signal flows through on app shutdown to an object which no longer exists.
firstRunPromptManager.clearNextPromptSignal()
QGroundControl.linkManager.shutdown()
QGroundControl.videoManager.stopVideo();
mainWindow.close()
}
// On attempting an application close we check for:
// Unsaved missions - then
// Pending parameter writes - then
// Active connections
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: pendingParameterWritesCloseDialog.check()
function check() {
if (planMasterControllerPlanView && planMasterControllerPlanView.dirty) {
unsavedMissionCloseDialog.open()
} else {
pendingParameterWritesCloseDialog.check()
}
}
}
MessageDialog {
id: pendingParameterWritesCloseDialog
title: qsTr("%1 close").arg(QGroundControl.appName)
text: qsTr("You have pending parameter updates to a vehicle. 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() {
for (var index=0; index<QGroundControl.multiVehicleManager.vehicles.count; index++) {
if (QGroundControl.multiVehicleManager.vehicles.get(index).parameterManager.pendingWrites) {
pendingParameterWritesCloseDialog.open()
return
}
}
activeConnectionsCloseDialog.check()
}
}
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()
}
}
}
//-------------------------------------------------------------------------
/// Main, full window background (Fly View)
background: Item {
id: rootBackground
anchors.fill: parent
}
//-------------------------------------------------------------------------
/// Toolbar
header: MainToolBar {
id: toolbar
height: ScreenTools.toolbarHeight
visible: !QGroundControl.videoManager.fullScreen
}
footer: LogReplayStatusBar {
visible: QGroundControl.settingsManager.flyViewSettings.showLogReplayStatusBar.rawValue
}
Drawer {
id: viewSelectDrawer
height: mainWindow.height
edge: Qt.LeftEdge
interactive: true
dragMargin: 0
visible: false
property var _mainWindow: mainWindow
property real _toolButtonHeight: ScreenTools.defaultFontPixelHeight * 3
Rectangle {
width: mainLayout.width + (mainLayout.anchors.margins * 2)
height: parent.height
color: qgcPal.window
QGCFlickable {
anchors.top: parent.top
anchors.bottom: qgcVersionLayout.top
anchors.left: parent.left
anchors.right: parent.right
contentHeight: mainLayout.height + (mainLayout.anchors.margins * 2)
flickableDirection: QGCFlickable.VerticalFlick
ColumnLayout {
id: mainLayout
anchors.margins: ScreenTools.defaultFontPixelWidth
anchors.left: parent.left
anchors.top: parent.top
spacing: ScreenTools.defaultFontPixelWidth
SubMenuButton {
id: flyButton
height: viewSelectDrawer._toolButtonHeight
Layout.fillWidth: true
text: qsTr("Fly View")
imageResource: "/qmlimages/PaperPlane.svg"
imageColor: qgcPal.text
onClicked: {
if (toolbar.viewButtonClicked(this)) {
mainWindow.showFlyView()
}
}
}
SubMenuButton {
id: planButton
height: viewSelectDrawer._toolButtonHeight
Layout.fillWidth: true
text: qsTr("Plan View")
imageResource: "/qmlimages/Plan.svg"
imageColor: qgcPal.text
onClicked: {
if (toolbar.viewButtonClicked(this)) {
mainWindow.showPlanView()
}
}
}
SubMenuButton {
id: analyzeButton
height: viewSelectDrawer._toolButtonHeight
Layout.fillWidth: true
text: qsTr("Analyze Tools")
imageResource: "/qmlimages/Analyze.svg"
imageColor: qgcPal.text
visible: QGroundControl.corePlugin.showAdvancedUI
onClicked: {
if (toolbar.viewButtonClicked(this)) {
mainWindow.showAnalyzeView()
}
}
}
SubMenuButton {
id: setupButton
height: viewSelectDrawer._toolButtonHeight
Layout.fillWidth: true
text: qsTr("Vehicle Setup")
imageColor: qgcPal.text
imageResource: "/qmlimages/Gears.svg"
onClicked: {
if (toolbar.viewButtonClicked(this)) {
mainWindow.showSetupView()
}
}
}
SubMenuButton {
id: settingsButton
height: viewSelectDrawer._toolButtonHeight
Layout.fillWidth: true
text: qsTr("Application Settings")
imageResource: "/res/QGCLogoFull"
imageColor: "transparent"
visible: !QGroundControl.corePlugin.options.combineSettingsAndSetup
onClicked: {
if (toolbar.viewButtonClicked(this)) {
mainWindow.showSettingsView()
}
}
}
}
}
ColumnLayout {
id: qgcVersionLayout
anchors.left: parent.left
anchors.right: parent.right
anchors.bottom: parent.bottom
spacing: 0
QGCLabel {
text: qsTr("%1 Version").arg(QGroundControl.appName)
font.pointSize: ScreenTools.smallFontPointSize
wrapMode: QGCLabel.WordWrap
Layout.maximumWidth: parent.width
Layout.alignment: Qt.AlignHCenter
}
QGCLabel {
text: QGroundControl.qgcVersion
font.pointSize: ScreenTools.smallFontPointSize
wrapMode: QGCLabel.WrapAnywhere
Layout.maximumWidth: parent.width
Layout.alignment: Qt.AlignHCenter
}
}
QGCMouseArea {
anchors.fill: qgcVersionLayout
onClicked: {
if (mouse.modifiers & Qt.ShiftModifier) {
QGroundControl.corePlugin.showTouchAreas = !QGroundControl.corePlugin.showTouchAreas
} else {
if(!QGroundControl.corePlugin.showAdvancedUI) {
advancedModeConfirmation.open()
} else {
QGroundControl.corePlugin.showAdvancedUI = false
}
}
}
MessageDialog {
id: advancedModeConfirmation
title: qsTr("Advanced Mode")
text: QGroundControl.corePlugin.showAdvancedUIMessage
standardButtons: StandardButton.Yes | StandardButton.No
onYes: {
QGroundControl.corePlugin.showAdvancedUI = true
advancedModeConfirmation.close()
}
}
}
}
}
//-------------------------------------------------------------------------
/// Fly View
FlyView {
id: flightView
anchors.fill: parent
}
//-------------------------------------------------------------------------
/// Plan View
Loader {
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"
}
//-------------------------------------------------------------------------
/// Analyze
Loader {
id: analyzeWindow
anchors.fill: parent
visible: false
source: "AnalyzeView.qml"
}
//-------------------------------------------------------------------------
//-- 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()
}
}
onFormatedMessageChanged: {
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
x: Math.round((mainWindow.width - width) * 0.5)
y: Math.round((mainWindow.height - height) * 0.5)
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
}
}
//-- 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 var _messageQueue: []
property string _systemMessage: ""
function showVehicleMessage(message) {
vehicleMessageArea.close()
if(systemMessageArea.visible || QGroundControl.videoManager.fullScreen) {
_messageQueue.push(message)
} else {
_systemMessage = message
systemMessageArea.open()
}
}
Popup {
id: systemMessageArea
y: ScreenTools.defaultFontPixelHeight
x: Math.round((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]
if(i) mainWindow._systemMessage += "<br>"
mainWindow._systemMessage += text
}
//-- Clear it
mainWindow._messageQueue = []
systemMessageArea.open()
} else {
mainWindow._systemMessage = ""
}
}
Flickable {
id: systemMessageFlick
anchors.margins: ScreenTools.defaultFontPixelHeight * 0.5
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.defaultFontPixelHeight
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(item, dropItem) {
indicatorDropdown.currentIndicator = dropItem
indicatorDropdown.currentItem = item
indicatorDropdown.open()
}
function hidePopUp() {
indicatorDropdown.close()
indicatorDropdown.currentItem = null
indicatorDropdown.currentIndicator = null
}
Popup {
id: indicatorDropdown
padding: ScreenTools.defaultFontPixelWidth * 0.75
modal: true
focus: true
closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutside
property var currentItem: null
property var currentIndicator: null
background: Rectangle {
width: loader.width
height: loader.height
color: Qt.rgba(0,0,0,0)
}
Loader {
id: loader
onLoaded: {
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
}
}
onOpened: {
loader.sourceComponent = indicatorDropdown.currentIndicator
}
onClosed: {
loader.sourceComponent = null
indicatorDropdown.currentIndicator = null
}
}
}