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, bypassNavigationCheck = false) {
180 let dialog = simpleMessageDialogComponent.createObject(owner, { title: dialogTitle, text: dialogText, buttons: buttons, acceptFunction: acceptFunction, closeFunction: closeFunction, bypassNavigationCheck: bypassNavigationCheck })
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
205 property bool suppressCriticalVehicleMessages: false
207 function finishCloseProcess() {
209 // For some reason on the Qml side Qt doesn't automatically disconnect a signal when an object is destroyed.
210 // So we have to do it ourselves otherwise the signal flows through on app shutdown to an object which no longer exists.
211 firstRunPromptManager.clearNextPromptSignal()
212 QGroundControl.linkManager.shutdown()
213 QGroundControl.videoManager.stopVideo();
217 // Check for things which should prevent the app from closing
218 // Returns true if it is OK to close
219 readonly property int _skipUnsavedMissionCheckMask: 0x01
220 readonly property int _skipPendingParameterWritesCheckMask: 0x02
221 readonly property int _skipActiveConnectionsCheckMask: 0x04
222 property int _closeChecksToSkip: 0
223 property bool _reentrantCloseGuard: false
224 function performCloseChecks() {
225 if (!(_closeChecksToSkip & _skipUnsavedMissionCheckMask) && !checkForUnsavedMission()) {
228 if (!(_closeChecksToSkip & _skipPendingParameterWritesCheckMask) && !checkForPendingParameterWrites()) {
231 if (!(_closeChecksToSkip & _skipActiveConnectionsCheckMask) && !checkForActiveConnections()) {
238 function checkForUnsavedMission() {
239 if (planView._planMasterController.dirtyForSave || planView._planMasterController.dirtyForUpload) {
241 _reentrantCloseGuard = true
242 _showMessageDialogWorker(mainWindow, qsTr("Unsaved Mission"),
243 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?"),
244 Dialog.Yes | Dialog.No,
245 function() { accepted = true; _closeChecksToSkip |= _skipUnsavedMissionCheckMask; performCloseChecks() },
246 function() { if (!accepted) _reentrantCloseGuard = false },
247 true /* bypassNavigationCheck */)
254 function checkForPendingParameterWrites() {
255 for (var index=0; index<QGroundControl.multiVehicleManager.vehicles.count; index++) {
256 if (QGroundControl.multiVehicleManager.vehicles.get(index).parameterManager.pendingWrites) {
258 _reentrantCloseGuard = true
259 _showMessageDialogWorker(mainWindow, qsTr("Pending Parameter Updates"),
260 qsTr("You have pending parameter updates to a vehicle. If you close you will lose changes. Are you sure you want to close?"),
261 Dialog.Yes | Dialog.No,
262 function() { accepted = true; _closeChecksToSkip |= _skipPendingParameterWritesCheckMask; performCloseChecks() },
263 function() { if (!accepted) _reentrantCloseGuard = false },
264 true /* bypassNavigationCheck */)
271 function checkForActiveConnections() {
272 if (QGroundControl.multiVehicleManager.activeVehicle) {
274 _reentrantCloseGuard = true
275 _showMessageDialogWorker(mainWindow, qsTr("Active Vehicle Connections"),
276 qsTr("There are still active connections to vehicles. Are you sure you want to exit?"),
277 Dialog.Yes | Dialog.No,
278 function() { accepted = true; _closeChecksToSkip |= _skipActiveConnectionsCheckMask; performCloseChecks() },
279 function() { if (!accepted) _reentrantCloseGuard = false },
280 true /* bypassNavigationCheck */)
287 onClosing: (close) => {
289 if (_reentrantCloseGuard) {
290 close.accepted = false
293 _closeChecksToSkip = 0
294 close.accepted = performCloseChecks()
298 background: Rectangle {
300 color: QGroundControl.globalPalette.window
305 objectName: "mainView_fly"
311 objectName: "mainView_plan"
316 footer: LogReplayStatusBar {
317 visible: QGroundControl.settingsManager.flyViewSettings.showLogReplayStatusBar.rawValue
321 id: showTouchAreasNotification
322 title: qsTr("Debug Touch Areas")
323 text: qsTr("Touch Area display toggled")
324 buttons: MessageDialog.Ok
328 id: advancedModeOnConfirmation
329 title: qsTr("Advanced Mode")
330 text: QGroundControl.corePlugin.showAdvancedUIMessage
331 buttons: MessageDialog.Yes | MessageDialog.No
332 onButtonClicked: function (button, role) {
333 if (button === MessageDialog.Yes) {
334 QGroundControl.corePlugin.showAdvancedUI = true
340 id: advancedModeOffConfirmation
341 title: qsTr("Advanced Mode")
342 text: qsTr("Turn off Advanced Mode?")
343 buttons: MessageDialog.Yes | MessageDialog.No
344 onButtonClicked: function (button, role) {
345 if (button === MessageDialog.Yes) {
346 QGroundControl.corePlugin.showAdvancedUI = false
351 function showToolSelectDialog() {
352 if (mainWindow.allowViewSwitch()) {
353 mainWindow.showIndicatorDrawer(toolSelectComponent, null)
357 // Toast notification shown when a view switch is blocked by a validation error
359 id: validationErrorToast
360 x: (mainWindow.width - width) / 2
361 y: mainWindow.height - height - ScreenTools.defaultFontPixelHeight * 3
363 closePolicy: Popup.NoAutoClose
364 text: qsTr("Please correct the invalid value before continuing")
366 background: Rectangle {
367 color: qgcPal.alertBackground
368 radius: ScreenTools.defaultFontPixelWidth / 2
371 contentItem: QGCLabel {
372 text: validationErrorToast.text
373 color: qgcPal.alertText
378 id: toolSelectComponent
386 objectName: "mainView_toolDrawer"
391 property var backIcon
392 property string toolTitle
393 property alias toolSource: toolDrawerLoader.source
394 property var toolIcon
397 if (!toolDrawer.visible) {
398 toolDrawerLoader.source = ""
402 // This need to block click event leakage to underlying map.
408 id: toolDrawerToolbar
409 anchors.left: parent.left
410 anchors.right: parent.right
411 anchors.top: parent.top
412 height: ScreenTools.toolbarHeight
413 color: qgcPal.toolbarBackground
416 id: toolDrawerToolbarLayout
417 anchors.leftMargin: ScreenTools.defaultFontPixelWidth
418 anchors.left: parent.left
419 anchors.top: parent.top
420 anchors.bottom: parent.bottom
421 spacing: ScreenTools.defaultFontPixelWidth
425 objectName: "toolbar_qgcLogo"
426 height: parent.height
427 icon.source: "/res/QGCLogoFull.svg"
429 onClicked: mainWindow.showToolSelectDialog()
433 id: toolbarDrawerText
434 text: toolDrawer.toolTitle
435 font.pointSize: ScreenTools.largeFontPointSize
442 anchors.left: parent.left
443 anchors.right: parent.right
444 anchors.top: toolDrawerToolbar.bottom
445 anchors.bottom: parent.bottom
449 //-------------------------------------------------------------------------
450 //-- Critical Vehicle Message Popup
452 function showCriticalVehicleMessage(message) {
453 if (suppressCriticalVehicleMessages) {
456 if (criticalVehicleMessagePopup.visible || QGroundControl.videoManager.fullScreen) {
457 // We received additional warning message while an older warning message was still displayed.
458 // When the user close the older one drop the message indicator tool so they can see the rest of them.
459 criticalVehicleMessagePopup.additionalCriticalMessagesReceived = true
461 criticalVehicleMessagePopup.criticalVehicleMessage = message
462 criticalVehicleMessagePopup.additionalCriticalMessagesReceived = false
463 criticalVehicleMessagePopup.open()
468 id: criticalVehicleMessagePopup
469 y: ScreenTools.toolbarHeight + ScreenTools.defaultFontPixelHeight
470 x: Math.round((mainWindow.width - width) * 0.5)
471 width: mainWindow.width * 0.55
472 height: criticalVehicleMessageText.contentHeight + ScreenTools.defaultFontPixelHeight * 2
476 property alias criticalVehicleMessage: criticalVehicleMessageText.text
477 property bool additionalCriticalMessagesReceived: false
479 background: Rectangle {
481 color: qgcPal.alertBackground
482 radius: ScreenTools.defaultFontPixelHeight * 0.5
483 border.color: qgcPal.alertBorder
487 anchors.horizontalCenter: parent.horizontalCenter
488 anchors.top: parent.top
489 anchors.topMargin: -(height / 2)
490 color: qgcPal.alertBackground
491 radius: ScreenTools.defaultFontPixelHeight * 0.25
492 border.color: qgcPal.alertBorder
494 width: vehicleWarningLabel.contentWidth + _margins
495 height: vehicleWarningLabel.contentHeight + _margins
497 property real _margins: ScreenTools.defaultFontPixelHeight * 0.25
500 id: vehicleWarningLabel
501 anchors.centerIn: parent
502 text: qsTr("Vehicle Error")
503 font.pointSize: ScreenTools.smallFontPointSize
504 color: qgcPal.alertText
509 id: additionalErrorsIndicator
510 anchors.horizontalCenter: parent.horizontalCenter
511 anchors.bottom: parent.bottom
512 anchors.bottomMargin: -(height / 2)
513 color: qgcPal.alertBackground
514 radius: ScreenTools.defaultFontPixelHeight * 0.25
515 border.color: qgcPal.alertBorder
517 width: additionalErrorsLabel.contentWidth + _margins
518 height: additionalErrorsLabel.contentHeight + _margins
519 visible: criticalVehicleMessagePopup.additionalCriticalMessagesReceived
521 property real _margins: ScreenTools.defaultFontPixelHeight * 0.25
524 id: additionalErrorsLabel
525 anchors.centerIn: parent
526 text: qsTr("Additional errors received")
527 font.pointSize: ScreenTools.smallFontPointSize
528 color: qgcPal.alertText
534 id: criticalVehicleMessageText
535 width: criticalVehicleMessagePopup.width - ScreenTools.defaultFontPixelHeight
536 anchors.centerIn: parent
537 wrapMode: Text.WordWrap
538 color: qgcPal.alertText
539 textFormat: TextEdit.RichText
545 criticalVehicleMessagePopup.close()
546 if (criticalVehicleMessagePopup.additionalCriticalMessagesReceived) {
547 criticalVehicleMessagePopup.additionalCriticalMessagesReceived = false;
548 flyView.dropMainStatusIndicatorTool();
549 } else if (QGroundControl.multiVehicleManager.activeVehicle) {
550 QGroundControl.multiVehicleManager.activeVehicle.resetErrorLevelMessages();
556 //-------------------------------------------------------------------------
557 //-- Indicator Drawer
559 function showIndicatorDrawer(drawerComponent, indicatorItem) {
560 indicatorDrawer.sourceComponent = drawerComponent
561 indicatorDrawer.indicatorItem = indicatorItem
562 indicatorDrawer.open()
565 function closeIndicatorDrawer() {
566 indicatorDrawer.close()
572 y: ScreenTools.toolbarHeight + _margins
577 padding: _margins * 2
581 closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutside
583 property var sourceComponent
584 property var indicatorItem
586 property bool _expanded: false
587 property real _margins: ScreenTools.defaultFontPixelHeight / 4
589 function calcXPosition() {
591 var xCenter = indicatorItem.mapToItem(mainWindow.contentItem, indicatorItem.width / 2, 0).x
592 return Math.max(_margins, Math.min(xCenter - (contentItem.implicitWidth / 2), mainWindow.contentItem.width - contentItem.implicitWidth - _margins - (indicatorDrawer.padding * 2) - (ScreenTools.defaultFontPixelHeight / 2)))
600 indicatorDrawerLoader.sourceComponent = indicatorDrawer.sourceComponent
604 indicatorItem = undefined
605 indicatorDrawerLoader.sourceComponent = undefined
612 color: QGroundControl.globalPalette.window
613 radius: indicatorDrawer._margins
618 objectName: "indicatorDrawerExpandButton"
619 anchors.horizontalCenter: backgroundRect.right
620 anchors.verticalCenter: backgroundRect.top
621 width: ScreenTools.largeFontPixelHeight
624 color: QGroundControl.globalPalette.button
625 border.color: QGroundControl.globalPalette.buttonText
626 visible: indicatorDrawerLoader.item && indicatorDrawerLoader.item._showExpand && !indicatorDrawer._expanded
629 anchors.centerIn: parent
631 color: QGroundControl.globalPalette.buttonText
636 onClicked: indicatorDrawer._expanded = true
641 contentItem: QGCFlickable {
642 id: indicatorDrawerLoaderFlickable
643 implicitWidth: Math.min(mainWindow.contentItem.width - (2 * indicatorDrawer._margins) - (indicatorDrawer.padding * 2), indicatorDrawerLoader.width)
644 implicitHeight: Math.min(mainWindow.contentItem.height - ScreenTools.toolbarHeight - (2 * indicatorDrawer._margins) - (indicatorDrawer.padding * 2), indicatorDrawerLoader.height)
645 contentWidth: indicatorDrawerLoader.width
646 contentHeight: indicatorDrawerLoader.height
649 id: indicatorDrawerLoader
650 objectName: "indicatorDrawerLoader"
653 target: indicatorDrawerLoader.item
655 value: indicatorDrawer._expanded
659 target: indicatorDrawerLoader.item
661 value: indicatorDrawer
667 // Analyze page items (both in-panel and popped-out windows) are created with mainWindow as their
668 // QObject parent so their lifetime is not tied to AnalyzeView. This lets a popped-out window
669 // survive AnalyzeView being unloaded from the tool drawer.
671 // Tracks the analyze page item currently shown inside AnalyzeView's panel (not popped out).
672 // null when no page is loaded or the item has been handed off to a popup window.
673 property var _inPanelAnalyzePage: null
675 // Called by AnalyzeView.Component.onDestruction to destroy the in-panel item while
676 // panelContainer is still alive.
677 function destroyInPanelAnalyzePage() {
678 if (_inPanelAnalyzePage) {
679 _inPanelAnalyzePage.destroy()
680 _inPanelAnalyzePage = null
684 // Called by AnalyzeView to create an analyze page item owned by mainWindow.
685 // The caller sets the visual parent to panelContainer after creation.
686 function createAnalyzePage(source) {
687 if (_inPanelAnalyzePage) {
688 _inPanelAnalyzePage.destroy()
689 _inPanelAnalyzePage = null
691 var component = Qt.createComponent(source)
692 if (component.status !== Component.Ready) {
693 console.warn("createAnalyzePage failed source:", source, "errorString:", component.errorString())
696 _inPanelAnalyzePage = component.createObject(mainWindow)
697 return _inPanelAnalyzePage
700 // Called by AnalyzeView when the in-panel item is handed off to a popup window.
701 // Clears _inPanelAnalyzePage so destroyInPanelAnalyzePage() does not destroy it
702 // when AnalyzeView is torn down.
703 function analyzePageMovedToPopup() {
704 _inPanelAnalyzePage = null
707 function createWindowedAnalyzePage(title, source, requiresVehicle, existingItem) {
708 var windowedPage = windowedAnalyzePage.createObject(mainWindow)
709 windowedPage.title = title
710 windowedPage.requiresVehicle = requiresVehicle
712 windowedPage.adoptItem(existingItem)
714 windowedPage.source = source
716 windowedPage.visible = true
720 id: windowedAnalyzePage
723 width: ScreenTools.defaultFontPixelWidth * 100
724 height: ScreenTools.defaultFontPixelHeight * 40
727 property alias source: loader.source
728 property bool requiresVehicle: false
730 function adoptItem(item) {
731 loader.visible = false
733 item.parent = contentRect
734 item.anchors.fill = contentRect
740 target: QGroundControl.multiVehicleManager
741 function onActiveVehicleChanged() {
742 if (requiresVehicle) {
750 color: QGroundControl.globalPalette.window
756 onLoaded: item.popped = true
762 // Destroy any reparented children (not owned by loader)
763 for (var i = contentRect.children.length - 1; i >= 0; i--) {
764 var child = contentRect.children[i]
765 if (child !== loader) {
770 Qt.callLater(destroy)