8import QGroundControl.Controls
9import QGroundControl.FactControls
10import QGroundControl.FlyView
11import QGroundControl.FlightMap
12import QGroundControl.PlanView
13import QGroundControl.Toolbar
15/// @brief Native QML top level window
16/// All properties defined here are visible to all QML pages.
20 // The special casing for android prevents white bars from showing up on the edges of the screen with newer android versions
21 flags: Qt.Window | (ScreenTools.isAndroid ? Qt.ExpandedClientAreaHint | Qt.NoTitleBarBackgroundHint : 0)
23 Component.onCompleted: {
24 // Start the sequence of first run prompt(s)
25 firstRunPromptManager.nextPrompt()
28 /// Saves main window position and size and re-opens it in the same position and size next time
29 MainWindowSavedState {
34 id: firstRunPromptManager
36 property var currentDialog: null
37 property var rgPromptIds: QGroundControl.corePlugin.firstRunPromptsToShow()
38 property int nextPromptIdIndex: 0
40 function clearNextPromptSignal() {
42 currentDialog.closed.disconnect(nextPrompt)
46 function nextPrompt() {
47 if (nextPromptIdIndex < rgPromptIds.length) {
48 var component = Qt.createComponent(QGroundControl.corePlugin.firstRunPromptResource(rgPromptIds[nextPromptIdIndex]));
49 currentDialog = component.createObject(mainWindow)
50 currentDialog.closed.connect(nextPrompt)
55 showPreFlightChecklistIfNeeded()
60 readonly property real _topBottomMargins: ScreenTools.defaultFontPixelHeight * 0.5
62 //-------------------------------------------------------------------------
63 //-- Global Scope Variables
68 readonly property var activeVehicle: QGroundControl.multiVehicleManager.activeVehicle
69 readonly property real defaultTextHeight: ScreenTools.defaultFontPixelHeight
70 readonly property real defaultTextWidth: ScreenTools.defaultFontPixelWidth
71 readonly property var planMasterControllerFlyView: flyView.planController
72 readonly property var guidedControllerFlyView: flyView.guidedController
74 // Number of QGCTextField's with validation errors. Used to prevent closing panels with validation errors.
75 property int validationErrorCount: 0
77 // Set to a non-empty string to block navigation with a custom reason (e.g. during calibration)
78 property string navigationBlockedReason: ""
80 // Property to manage RemoteID quick access to settings page
81 property bool commingFromRIDIndicator: false
84 /// Default color palette used throughout the UI
85 QGCPalette { id: qgcPal; colorGroupEnabled: true }
87 //-------------------------------------------------------------------------
90 signal armVehicleRequest
91 signal forceArmVehicleRequest
92 signal disarmVehicleRequest
93 signal vtolTransitionToFwdFlightRequest
94 signal vtolTransitionToMRFlightRequest
95 signal showPreFlightChecklistIfNeeded
97 //-------------------------------------------------------------------------
98 //-- Global Scope Functions
100 // This function is used to prevent view switching if there are validation errors
101 function allowViewSwitch(previousValidationErrorCount = 0, showErrorOnDisallow = true) {
102 // Check for explicit navigation block (e.g. calibration in progress)
103 if (globals.navigationBlockedReason !== "") {
104 if (showErrorOnDisallow) {
105 validationErrorToast.text = globals.navigationBlockedReason
106 if (validationErrorToast.visible) {
107 validationErrorToast.close()
109 validationErrorToast.open()
113 // Run validation on active focus control to ensure it is valid before switching views
114 if (mainWindow.activeFocusControl instanceof FactTextField) {
115 mainWindow.activeFocusControl._onEditingFinished()
117 var allowed = globals.validationErrorCount <= previousValidationErrorCount
118 if (!allowed && showErrorOnDisallow) {
119 validationErrorToast.text = qsTr("Please correct the invalid value before continuing")
120 if (validationErrorToast.visible) {
121 validationErrorToast.close()
123 validationErrorToast.open()
128 function showPlanView() {
129 flyView.visible = false
130 planView.visible = true
131 toolDrawer.visible = false
134 function showFlyView() {
135 flyView.visible = true
136 planView.visible = false
137 toolDrawer.visible = false
140 function showTool(toolTitle, toolSource, toolIcon) {
141 toolDrawer.backIcon = flyView.visible ? "/qmlimages/PaperPlane.svg" : "/qmlimages/Plan.svg"
142 toolDrawer.toolTitle = toolTitle
143 toolDrawer.toolSource = toolSource
144 toolDrawer.toolIcon = toolIcon
145 toolDrawer.visible = true
148 function showAnalyzeTool() {
149 showTool(qsTr("Analyze Tools"), "qrc:/qml/QGroundControl/AnalyzeView/AnalyzeView.qml", "/qmlimages/Analyze.svg")
152 function showVehicleConfig() {
153 showTool(qsTr("Vehicle Configuration"), "qrc:/qml/QGroundControl/VehicleSetup/VehicleConfigView.qml", "/qmlimages/Gears.svg")
156 function showVehicleConfigParametersPage() {
158 toolDrawerLoader.item.showParametersPanel()
161 function showKnownVehicleComponentConfigPage(knownVehicleComponent) {
163 let vehicleComponent = globals.activeVehicle.autopilotPlugin.findKnownVehicleComponent(knownVehicleComponent)
164 if (vehicleComponent) {
165 toolDrawerLoader.item.showVehicleComponentPanel(vehicleComponent)
169 function showSettingsTool(settingsPage = "") {
170 showTool(qsTr("Application Settings"), "qrc:/qml/QGroundControl/Controls/AppSettings.qml", "/res/QGCLogoWhite")
171 if (settingsPage !== "") {
172 toolDrawerLoader.item.showSettingsPage(settingsPage)
176 //-------------------------------------------------------------------------
177 //-- Global simple message dialog
179 function _showMessageDialogWorker(owner, dialogTitle, dialogText, buttons = Dialog.Ok, acceptFunction = null, closeFunction = null) {
180 let dialog = simpleMessageDialogComponent.createObject(owner, { title: dialogTitle, text: dialogText, buttons: buttons, acceptFunction: acceptFunction, closeFunction: closeFunction })
184 // This variant is only meant to be called by QGCApplication
185 function _showMessageDialog(dialogTitle, dialogText) {
186 _showMessageDialogWorker(mainWindow, dialogTitle, dialogText)
190 target: QGroundControl
192 function onShowMessageDialogRequested(owner, title, text, buttons, acceptFunction, closeFunction) {
193 _showMessageDialogWorker(owner, title, text, buttons, acceptFunction, closeFunction)
198 id: simpleMessageDialogComponent
200 QGCSimpleMessageDialog {
204 property bool _forceClose: false
206 function finishCloseProcess() {
208 // For some reason on the Qml side Qt doesn't automatically disconnect a signal when an object is destroyed.
209 // So we have to do it ourselves otherwise the signal flows through on app shutdown to an object which no longer exists.
210 firstRunPromptManager.clearNextPromptSignal()
211 QGroundControl.linkManager.shutdown()
212 QGroundControl.videoManager.stopVideo();
216 // Check for things which should prevent the app from closing
217 // Returns true if it is OK to close
218 readonly property int _skipUnsavedMissionCheckMask: 0x01
219 readonly property int _skipPendingParameterWritesCheckMask: 0x02
220 readonly property int _skipActiveConnectionsCheckMask: 0x04
221 property int _closeChecksToSkip: 0
222 function performCloseChecks() {
223 if (!(_closeChecksToSkip & _skipUnsavedMissionCheckMask) && !checkForUnsavedMission()) {
226 if (!(_closeChecksToSkip & _skipPendingParameterWritesCheckMask) && !checkForPendingParameterWrites()) {
229 if (!(_closeChecksToSkip & _skipActiveConnectionsCheckMask) && !checkForActiveConnections()) {
236 property string closeDialogTitle: qsTr("Close %1").arg(QGroundControl.appName)
238 function checkForUnsavedMission() {
239 if (planView._planMasterController.dirtyForSave || planView._planMasterController.dirtyForUpload) {
240 QGroundControl.showMessageDialog(mainWindow, closeDialogTitle,
241 qsTr("You have a mission edit in progress which has not been saved/uploaded. If you close you will lose changes. Are you sure you want to close?"),
242 Dialog.Yes | Dialog.No,
243 function() { _closeChecksToSkip |= _skipUnsavedMissionCheckMask; performCloseChecks() })
250 function checkForPendingParameterWrites() {
251 for (var index=0; index<QGroundControl.multiVehicleManager.vehicles.count; index++) {
252 if (QGroundControl.multiVehicleManager.vehicles.get(index).parameterManager.pendingWrites) {
253 QGroundControl.showMessageDialog(mainWindow, closeDialogTitle,
254 qsTr("You have pending parameter updates to a vehicle. If you close you will lose changes. Are you sure you want to close?"),
255 Dialog.Yes | Dialog.No,
256 function() { _closeChecksToSkip |= _skipPendingParameterWritesCheckMask; performCloseChecks() })
263 function checkForActiveConnections() {
264 if (QGroundControl.multiVehicleManager.activeVehicle) {
265 QGroundControl.showMessageDialog(mainWindow, closeDialogTitle,
266 qsTr("There are still active connections to vehicles. Are you sure you want to exit?"),
267 Dialog.Yes | Dialog.No,
268 function() { _closeChecksToSkip |= _skipActiveConnectionsCheckMask; performCloseChecks() })
275 onClosing: (close) => {
277 _closeChecksToSkip = 0
278 close.accepted = performCloseChecks()
282 background: Rectangle {
284 color: QGroundControl.globalPalette.window
298 footer: LogReplayStatusBar {
299 visible: QGroundControl.settingsManager.flyViewSettings.showLogReplayStatusBar.rawValue
303 id: showTouchAreasNotification
304 title: qsTr("Debug Touch Areas")
305 text: qsTr("Touch Area display toggled")
306 buttons: MessageDialog.Ok
310 id: advancedModeOnConfirmation
311 title: qsTr("Advanced Mode")
312 text: QGroundControl.corePlugin.showAdvancedUIMessage
313 buttons: MessageDialog.Yes | MessageDialog.No
314 onButtonClicked: function (button, role) {
315 if (button === MessageDialog.Yes) {
316 QGroundControl.corePlugin.showAdvancedUI = true
322 id: advancedModeOffConfirmation
323 title: qsTr("Advanced Mode")
324 text: qsTr("Turn off Advanced Mode?")
325 buttons: MessageDialog.Yes | MessageDialog.No
326 onButtonClicked: function (button, role) {
327 if (button === MessageDialog.Yes) {
328 QGroundControl.corePlugin.showAdvancedUI = false
333 function showToolSelectDialog() {
334 if (mainWindow.allowViewSwitch()) {
335 mainWindow.showIndicatorDrawer(toolSelectComponent, null)
339 // Toast notification shown when a view switch is blocked by a validation error
341 id: validationErrorToast
342 x: (mainWindow.width - width) / 2
343 y: mainWindow.height - height - ScreenTools.defaultFontPixelHeight * 3
345 closePolicy: Popup.NoAutoClose
346 text: qsTr("Please correct the invalid value before continuing")
348 background: Rectangle {
349 color: qgcPal.alertBackground
350 radius: ScreenTools.defaultFontPixelWidth / 2
353 contentItem: QGCLabel {
354 text: validationErrorToast.text
355 color: qgcPal.alertText
360 id: toolSelectComponent
372 property var backIcon
373 property string toolTitle
374 property alias toolSource: toolDrawerLoader.source
375 property var toolIcon
378 if (!toolDrawer.visible) {
379 toolDrawerLoader.source = ""
383 // This need to block click event leakage to underlying map.
389 id: toolDrawerToolbar
390 anchors.left: parent.left
391 anchors.right: parent.right
392 anchors.top: parent.top
393 height: ScreenTools.toolbarHeight
394 color: qgcPal.toolbarBackground
397 id: toolDrawerToolbarLayout
398 anchors.leftMargin: ScreenTools.defaultFontPixelWidth
399 anchors.left: parent.left
400 anchors.top: parent.top
401 anchors.bottom: parent.bottom
402 spacing: ScreenTools.defaultFontPixelWidth
406 height: parent.height
407 icon.source: "/res/QGCLogoFull.svg"
409 onClicked: mainWindow.showToolSelectDialog()
413 id: toolbarDrawerText
414 text: toolDrawer.toolTitle
415 font.pointSize: ScreenTools.largeFontPointSize
422 anchors.left: parent.left
423 anchors.right: parent.right
424 anchors.top: toolDrawerToolbar.bottom
425 anchors.bottom: parent.bottom
428 target: toolDrawerLoader.item
429 ignoreUnknownSignals: true
430 function onPopout() { toolDrawer.visible = false }
435 //-------------------------------------------------------------------------
436 //-- Critical Vehicle Message Popup
438 function showCriticalVehicleMessage(message) {
439 closeIndicatorDrawer()
440 if (criticalVehicleMessagePopup.visible || QGroundControl.videoManager.fullScreen) {
441 // We received additional warning message while an older warning message was still displayed.
442 // When the user close the older one drop the message indicator tool so they can see the rest of them.
443 criticalVehicleMessagePopup.additionalCriticalMessagesReceived = true
445 criticalVehicleMessagePopup.criticalVehicleMessage = message
446 criticalVehicleMessagePopup.additionalCriticalMessagesReceived = false
447 criticalVehicleMessagePopup.open()
452 id: criticalVehicleMessagePopup
453 y: ScreenTools.toolbarHeight + ScreenTools.defaultFontPixelHeight
454 x: Math.round((mainWindow.width - width) * 0.5)
455 width: mainWindow.width * 0.55
456 height: criticalVehicleMessageText.contentHeight + ScreenTools.defaultFontPixelHeight * 2
460 property alias criticalVehicleMessage: criticalVehicleMessageText.text
461 property bool additionalCriticalMessagesReceived: false
463 background: Rectangle {
465 color: qgcPal.alertBackground
466 radius: ScreenTools.defaultFontPixelHeight * 0.5
467 border.color: qgcPal.alertBorder
471 anchors.horizontalCenter: parent.horizontalCenter
472 anchors.top: parent.top
473 anchors.topMargin: -(height / 2)
474 color: qgcPal.alertBackground
475 radius: ScreenTools.defaultFontPixelHeight * 0.25
476 border.color: qgcPal.alertBorder
478 width: vehicleWarningLabel.contentWidth + _margins
479 height: vehicleWarningLabel.contentHeight + _margins
481 property real _margins: ScreenTools.defaultFontPixelHeight * 0.25
484 id: vehicleWarningLabel
485 anchors.centerIn: parent
486 text: qsTr("Vehicle Error")
487 font.pointSize: ScreenTools.smallFontPointSize
488 color: qgcPal.alertText
493 id: additionalErrorsIndicator
494 anchors.horizontalCenter: parent.horizontalCenter
495 anchors.bottom: parent.bottom
496 anchors.bottomMargin: -(height / 2)
497 color: qgcPal.alertBackground
498 radius: ScreenTools.defaultFontPixelHeight * 0.25
499 border.color: qgcPal.alertBorder
501 width: additionalErrorsLabel.contentWidth + _margins
502 height: additionalErrorsLabel.contentHeight + _margins
503 visible: criticalVehicleMessagePopup.additionalCriticalMessagesReceived
505 property real _margins: ScreenTools.defaultFontPixelHeight * 0.25
508 id: additionalErrorsLabel
509 anchors.centerIn: parent
510 text: qsTr("Additional errors received")
511 font.pointSize: ScreenTools.smallFontPointSize
512 color: qgcPal.alertText
518 id: criticalVehicleMessageText
519 width: criticalVehicleMessagePopup.width - ScreenTools.defaultFontPixelHeight
520 anchors.centerIn: parent
521 wrapMode: Text.WordWrap
522 color: qgcPal.alertText
523 textFormat: TextEdit.RichText
529 criticalVehicleMessagePopup.close()
530 if (criticalVehicleMessagePopup.additionalCriticalMessagesReceived) {
531 criticalVehicleMessagePopup.additionalCriticalMessagesReceived = false;
532 flyView.dropMainStatusIndicatorTool();
534 QGroundControl.multiVehicleManager.activeVehicle.resetErrorLevelMessages();
540 //-------------------------------------------------------------------------
541 //-- Indicator Drawer
543 function showIndicatorDrawer(drawerComponent, indicatorItem) {
544 indicatorDrawer.sourceComponent = drawerComponent
545 indicatorDrawer.indicatorItem = indicatorItem
546 indicatorDrawer.open()
549 function closeIndicatorDrawer() {
550 indicatorDrawer.close()
556 y: ScreenTools.toolbarHeight + _margins
561 padding: _margins * 2
565 closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutside
567 property var sourceComponent
568 property var indicatorItem
570 property bool _expanded: false
571 property real _margins: ScreenTools.defaultFontPixelHeight / 4
573 function calcXPosition() {
575 var xCenter = indicatorItem.mapToItem(mainWindow.contentItem, indicatorItem.width / 2, 0).x
576 return Math.max(_margins, Math.min(xCenter - (contentItem.implicitWidth / 2), mainWindow.contentItem.width - contentItem.implicitWidth - _margins - (indicatorDrawer.padding * 2) - (ScreenTools.defaultFontPixelHeight / 2)))
584 indicatorDrawerLoader.sourceComponent = indicatorDrawer.sourceComponent
588 indicatorItem = undefined
589 indicatorDrawerLoader.sourceComponent = undefined
596 color: QGroundControl.globalPalette.window
597 radius: indicatorDrawer._margins
602 anchors.horizontalCenter: backgroundRect.right
603 anchors.verticalCenter: backgroundRect.top
604 width: ScreenTools.largeFontPixelHeight
607 color: QGroundControl.globalPalette.button
608 border.color: QGroundControl.globalPalette.buttonText
609 visible: indicatorDrawerLoader.item && indicatorDrawerLoader.item._showExpand && !indicatorDrawer._expanded
612 anchors.centerIn: parent
614 color: QGroundControl.globalPalette.buttonText
619 onClicked: indicatorDrawer._expanded = true
624 contentItem: QGCFlickable {
625 id: indicatorDrawerLoaderFlickable
626 implicitWidth: Math.min(mainWindow.contentItem.width - (2 * indicatorDrawer._margins) - (indicatorDrawer.padding * 2), indicatorDrawerLoader.width)
627 implicitHeight: Math.min(mainWindow.contentItem.height - ScreenTools.toolbarHeight - (2 * indicatorDrawer._margins) - (indicatorDrawer.padding * 2), indicatorDrawerLoader.height)
628 contentWidth: indicatorDrawerLoader.width
629 contentHeight: indicatorDrawerLoader.height
632 id: indicatorDrawerLoader
635 target: indicatorDrawerLoader.item
637 value: indicatorDrawer._expanded
641 target: indicatorDrawerLoader.item
643 value: indicatorDrawer
649 // We have to create the popup windows for the Analyze pages here so that the creation context is rooted
650 // to mainWindow. Otherwise if they are rooted to the AnalyzeView itself they will die when the analyze viewSwitch
653 function createWindowedAnalyzePage(title, source, requiresVehicle, existingItem) {
654 var windowedPage = windowedAnalyzePage.createObject(mainWindow)
655 windowedPage.title = title
656 windowedPage.requiresVehicle = requiresVehicle
658 windowedPage.adoptItem(existingItem)
660 windowedPage.source = source
662 windowedPage.visible = true
666 id: windowedAnalyzePage
669 width: ScreenTools.defaultFontPixelWidth * 100
670 height: ScreenTools.defaultFontPixelHeight * 40
673 property alias source: loader.source
674 property bool requiresVehicle: false
676 function adoptItem(item) {
677 loader.visible = false
679 item.parent = contentRect
680 item.anchors.fill = contentRect
686 target: QGroundControl.multiVehicleManager
687 function onActiveVehicleChanged() {
688 if (requiresVehicle) {
696 color: QGroundControl.globalPalette.window
702 onLoaded: item.popped = true
708 // Destroy any reparented children (not owned by loader)
709 for (var i = contentRect.children.length - 1; i >= 0; i--) {
710 var child = contentRect.children[i]
711 if (child !== loader) {
716 Qt.callLater(destroy)