QGroundControl
Ground Control Station for MAVLink Drones
Loading...
Searching...
No Matches
MainWindow.qml
Go to the documentation of this file.
1import QtQuick
2import QtQuick.Controls
3import QtQuick.Dialogs
4import QtQuick.Layouts
5import QtQuick.Window
6
7import QGroundControl
8import QGroundControl.Controls
9import QGroundControl.FactControls
10import QGroundControl.FlyView
11import QGroundControl.FlightMap
12import QGroundControl.PlanView
13import QGroundControl.Toolbar
14
15/// @brief Native QML top level window
16/// All properties defined here are visible to all QML pages.
17ApplicationWindow {
18 id: mainWindow
19 visible: true
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)
22
23 // Qt 6.9+ auto-sets ApplicationWindow padding to the display safe-area insets on mobile,
24 // which insets our full-bleed content and leaves a blank strip along the screen edge.
25 // QGC draws edge-to-edge and manages its own insets, so zero the padding.
26 topPadding: 0
27 bottomPadding: 0
28 leftPadding: 0
29 rightPadding: 0
30
31 Component.onCompleted: {
32 // Start the sequence of first run prompt(s)
33 firstRunPromptManager.nextPrompt()
34 }
35
36 /// Saves main window position and size and re-opens it in the same position and size next time
37 MainWindowSavedState {
38 window: mainWindow
39 }
40
41 QtObject {
42 id: firstRunPromptManager
43
44 property var currentDialog: null
45 property var rgPromptIds: QGroundControl.corePlugin.firstRunPromptsToShow()
46 property int nextPromptIdIndex: 0
47
48 function clearNextPromptSignal() {
49 if (currentDialog) {
50 currentDialog.closed.disconnect(nextPrompt)
51 }
52 }
53
54 function nextPrompt() {
55 if (nextPromptIdIndex < rgPromptIds.length) {
56 var component = Qt.createComponent(QGroundControl.corePlugin.firstRunPromptResource(rgPromptIds[nextPromptIdIndex]));
57 currentDialog = component.createObject(mainWindow)
58 currentDialog.closed.connect(nextPrompt)
59 currentDialog.open()
60 nextPromptIdIndex++
61 } else {
62 currentDialog = null
63 showPreFlightChecklistIfNeeded()
64 }
65 }
66 }
67
68 readonly property real _topBottomMargins: ScreenTools.defaultFontPixelHeight * 0.5
69
70 //-------------------------------------------------------------------------
71 //-- Global Scope Variables
72
73 QtObject {
74 id: globals
75
76 readonly property var activeVehicle: QGroundControl.multiVehicleManager.activeVehicle
77 readonly property real defaultTextHeight: ScreenTools.defaultFontPixelHeight
78 readonly property real defaultTextWidth: ScreenTools.defaultFontPixelWidth
79 readonly property var planMasterControllerFlyView: flyView.planController
80 readonly property var guidedControllerFlyView: flyView.guidedController
81
82 // Number of QGCTextField's with validation errors. Used to prevent closing panels with validation errors.
83 property int validationErrorCount: 0
84
85 // Set to a non-empty string to block navigation with a custom reason (e.g. during calibration)
86 property string navigationBlockedReason: ""
87
88 // Property to manage RemoteID quick access to settings page
89 property bool commingFromRIDIndicator: false
90 }
91
92 /// Default color palette used throughout the UI
93 QGCPalette { id: qgcPal; colorGroupEnabled: true }
94
95 //-------------------------------------------------------------------------
96 //-- Actions
97
98 signal armVehicleRequest
99 signal forceArmVehicleRequest
100 signal disarmVehicleRequest
101 signal vtolTransitionToFwdFlightRequest
102 signal vtolTransitionToMRFlightRequest
103 signal showPreFlightChecklistIfNeeded
104
105 //-------------------------------------------------------------------------
106 //-- Global Scope Functions
107
108 // This function is used to prevent view switching if there are validation errors
109 function allowViewSwitch(previousValidationErrorCount = 0, showErrorOnDisallow = true) {
110 // Check for explicit navigation block (e.g. calibration in progress)
111 if (globals.navigationBlockedReason !== "") {
112 if (showErrorOnDisallow) {
113 validationErrorToast.text = globals.navigationBlockedReason
114 if (validationErrorToast.visible) {
115 validationErrorToast.close()
116 }
117 validationErrorToast.open()
118 }
119 return false
120 }
121 // Run validation on active focus control to ensure it is valid before switching views
122 if (mainWindow.activeFocusControl instanceof FactTextField) {
123 mainWindow.activeFocusControl._onEditingFinished()
124 }
125 var allowed = globals.validationErrorCount <= previousValidationErrorCount
126 if (!allowed && showErrorOnDisallow) {
127 validationErrorToast.text = qsTr("Please correct the invalid value before continuing")
128 if (validationErrorToast.visible) {
129 validationErrorToast.close()
130 }
131 validationErrorToast.open()
132 }
133 return allowed
134 }
135
136 function showPlanView() {
137 flyView.visible = false
138 planView.visible = true
139 geoView.visible = false
140 toolDrawer.visible = false
141 }
142
143 function showFlyView() {
144 flyView.visible = true
145 planView.visible = false
146 geoView.visible = false
147 toolDrawer.visible = false
148 }
149
150 function showGeoView() {
151 flyView.visible = false
152 planView.visible = false
153 geoView.visible = true
154 toolDrawer.visible = false
155 }
156
157 function showTool(toolTitle, toolSource, toolIcon) {
158 toolDrawer.backIcon = flyView.visible ? "/qmlimages/PaperPlane.svg" : (geoView.visible ? "/InstrumentValueIcons/globe.svg" : "/qmlimages/Plan.svg")
159 toolDrawer.toolTitle = toolTitle
160 toolDrawer.toolSource = toolSource
161 toolDrawer.toolIcon = toolIcon
162 toolDrawer.visible = true
163 }
164
165 function showAnalyzeTool() {
166 showTool(qsTr("Analyze Tools"), "qrc:/qml/QGroundControl/AnalyzeView/AnalyzeView.qml", "/qmlimages/Analyze.svg")
167 }
168
169 function showVehicleConfig() {
170 showTool(qsTr("Vehicle Configuration"), "qrc:/qml/QGroundControl/VehicleSetup/VehicleConfigView.qml", "/qmlimages/Gears.svg")
171 }
172
173 function showVehicleConfigParametersPage() {
174 showVehicleConfig()
175 toolDrawerLoader.item.showParametersPanel()
176 }
177
178 function showKnownVehicleComponentConfigPage(knownVehicleComponent) {
179 showVehicleConfig()
180 let vehicleComponent = globals.activeVehicle.autopilotPlugin.findKnownVehicleComponent(knownVehicleComponent)
181 if (vehicleComponent) {
182 toolDrawerLoader.item.showVehicleComponentPanel(vehicleComponent)
183 }
184 }
185
186 function showSettingsTool(settingsPage = "") {
187 showTool(qsTr("Application Settings"), "qrc:/qml/QGroundControl/Controls/AppSettings.qml", "/res/QGCLogoWhite")
188 if (settingsPage !== "") {
189 toolDrawerLoader.item.showSettingsPage(settingsPage)
190 }
191 }
192
193 //-------------------------------------------------------------------------
194 //-- Global simple message dialog
195
196 function _showMessageDialogWorker(owner, dialogTitle, dialogText, buttons = Dialog.Ok, acceptFunction = null, closeFunction = null, bypassNavigationCheck = false) {
197 let dialog = simpleMessageDialogComponent.createObject(owner, { title: dialogTitle, text: dialogText, buttons: buttons, acceptFunction: acceptFunction, closeFunction: closeFunction, bypassNavigationCheck: bypassNavigationCheck })
198 dialog.open()
199 }
200
201 // This variant is only meant to be called by QGCApplication
202 function _showMessageDialog(dialogTitle, dialogText) {
203 _showMessageDialogWorker(mainWindow, dialogTitle, dialogText)
204 }
205
206 // This variant is only meant to be called by QGCApplication. Ok reboots the active vehicle.
207 function _showRebootVehicleDialog(dialogTitle, dialogText) {
208 _showMessageDialogWorker(mainWindow, dialogTitle,
209 dialogText + " " + qsTr("Click Ok to reboot the vehicle now."),
210 Dialog.Ok | Dialog.Cancel,
211 function() {
212 const activeVehicle = QGroundControl.multiVehicleManager.activeVehicle
213 if (activeVehicle) {
214 activeVehicle.rebootVehicle()
215 }
216 })
217 }
218
219 Connections {
220 target: QGroundControl
221
222 function onShowMessageDialogRequested(owner, title, text, buttons, acceptFunction, closeFunction) {
223 _showMessageDialogWorker(owner, title, text, buttons, acceptFunction, closeFunction)
224 }
225 }
226
227 Component {
228 id: simpleMessageDialogComponent
229
230 QGCSimpleMessageDialog {
231 }
232 }
233
234 property bool _forceClose: false
235 property bool suppressCriticalVehicleMessages: false
236
237 function finishCloseProcess() {
238 _forceClose = true
239 // For some reason on the Qml side Qt doesn't automatically disconnect a signal when an object is destroyed.
240 // So we have to do it ourselves otherwise the signal flows through on app shutdown to an object which no longer exists.
241 firstRunPromptManager.clearNextPromptSignal()
242 QGroundControl.linkManager.shutdown()
243 QGroundControl.videoManager.stopVideo();
244 mainWindow.close()
245 }
246
247 // Check for things which should prevent the app from closing
248 // Returns true if it is OK to close
249 readonly property int _skipUnsavedMissionCheckMask: 0x01
250 readonly property int _skipPendingParameterWritesCheckMask: 0x02
251 readonly property int _skipActiveConnectionsCheckMask: 0x04
252 property int _closeChecksToSkip: 0
253 property bool _reentrantCloseGuard: false
254 function performCloseChecks() {
255 if (!(_closeChecksToSkip & _skipUnsavedMissionCheckMask) && !checkForUnsavedMission()) {
256 return false
257 }
258 if (!(_closeChecksToSkip & _skipPendingParameterWritesCheckMask) && !checkForPendingParameterWrites()) {
259 return false
260 }
261 if (!(_closeChecksToSkip & _skipActiveConnectionsCheckMask) && !checkForActiveConnections()) {
262 return false
263 }
264 finishCloseProcess()
265 return true
266 }
267
268 function checkForUnsavedMission() {
269 // Only warn when edits are neither saved to disk nor uploaded to the vehicle.
270 // If either happened the edits are recoverable, so closing loses nothing.
271 // With no active vehicle an upload can't have happened, so treat the plan as
272 // not uploaded regardless of dirtyForUpload.
273 if (planView._planMasterController.dirtyForSave &&
274 (planView._planMasterController.dirtyForUpload || !QGroundControl.multiVehicleManager.activeVehicle)) {
275 let accepted = false
276 _reentrantCloseGuard = true
277 _showMessageDialogWorker(mainWindow, qsTr("Unsaved Mission"),
278 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?"),
279 Dialog.Yes | Dialog.No,
280 function() { accepted = true; _closeChecksToSkip |= _skipUnsavedMissionCheckMask; performCloseChecks() },
281 function() { if (!accepted) _reentrantCloseGuard = false },
282 true /* bypassNavigationCheck */)
283 return false
284 } else {
285 return true
286 }
287 }
288
289 function checkForPendingParameterWrites() {
290 for (var index=0; index<QGroundControl.multiVehicleManager.vehicles.count; index++) {
291 if (QGroundControl.multiVehicleManager.vehicles.get(index).parameterManager.pendingWrites) {
292 let accepted = false
293 _reentrantCloseGuard = true
294 _showMessageDialogWorker(mainWindow, qsTr("Pending Parameter Updates"),
295 qsTr("You have pending parameter updates to a vehicle. If you close you will lose changes. Are you sure you want to close?"),
296 Dialog.Yes | Dialog.No,
297 function() { accepted = true; _closeChecksToSkip |= _skipPendingParameterWritesCheckMask; performCloseChecks() },
298 function() { if (!accepted) _reentrantCloseGuard = false },
299 true /* bypassNavigationCheck */)
300 return false
301 }
302 }
303 return true
304 }
305
306 function checkForActiveConnections() {
307 if (QGroundControl.multiVehicleManager.activeVehicle) {
308 let accepted = false
309 _reentrantCloseGuard = true
310 _showMessageDialogWorker(mainWindow, qsTr("Active Vehicle Connections"),
311 qsTr("There are still active connections to vehicles. Are you sure you want to exit?"),
312 Dialog.Yes | Dialog.No,
313 function() { accepted = true; _closeChecksToSkip |= _skipActiveConnectionsCheckMask; performCloseChecks() },
314 function() { if (!accepted) _reentrantCloseGuard = false },
315 true /* bypassNavigationCheck */)
316 return false
317 } else {
318 return true
319 }
320 }
321
322 onClosing: (close) => {
323 if (!_forceClose) {
324 if (_reentrantCloseGuard) {
325 close.accepted = false
326 return
327 }
328 _closeChecksToSkip = 0
329 close.accepted = performCloseChecks()
330 }
331 }
332
333 background: Rectangle {
334 anchors.fill: parent
335 color: QGroundControl.globalPalette.window
336 }
337
338 FlyView {
339 id: flyView
340 objectName: "mainView_fly"
341 anchors.fill: parent
342 }
343
344 PlanView {
345 id: planView
346 objectName: "mainView_plan"
347 anchors.fill: parent
348 visible: false
349 }
350
351 FlyViewGeo {
352 id: geoView
353 objectName: "mainView_geo"
354 anchors.fill: parent
355 visible: false
356 }
357
358 footer: LogReplayStatusBar {
359 visible: QGroundControl.settingsManager.flyViewSettings.showLogReplayStatusBar.rawValue
360 }
361
362 MessageDialog {
363 id: showTouchAreasNotification
364 title: qsTr("Debug Touch Areas")
365 text: qsTr("Touch Area display toggled")
366 buttons: MessageDialog.Ok
367 }
368
369 MessageDialog {
370 id: advancedModeOnConfirmation
371 title: qsTr("Advanced Mode")
372 text: QGroundControl.corePlugin.showAdvancedUIMessage
373 buttons: MessageDialog.Yes | MessageDialog.No
374 onButtonClicked: function (button, role) {
375 if (button === MessageDialog.Yes) {
376 QGroundControl.corePlugin.showAdvancedUI = true
377 }
378 }
379 }
380
381 MessageDialog {
382 id: advancedModeOffConfirmation
383 title: qsTr("Advanced Mode")
384 text: qsTr("Turn off Advanced Mode?")
385 buttons: MessageDialog.Yes | MessageDialog.No
386 onButtonClicked: function (button, role) {
387 if (button === MessageDialog.Yes) {
388 QGroundControl.corePlugin.showAdvancedUI = false
389 }
390 }
391 }
392
393 function showToolSelectDialog() {
394 if (mainWindow.allowViewSwitch()) {
395 mainWindow.showIndicatorDrawer(toolSelectComponent, null)
396 }
397 }
398
399 // Toast notification shown when a view switch is blocked by a validation error
400 ToolTip {
401 id: validationErrorToast
402 x: (mainWindow.width - width) / 2
403 y: mainWindow.height - height - ScreenTools.defaultFontPixelHeight * 3
404 timeout: 3000
405 closePolicy: Popup.NoAutoClose
406 text: qsTr("Please correct the invalid value before continuing")
407
408 background: Rectangle {
409 color: qgcPal.alertBackground
410 radius: ScreenTools.defaultFontPixelWidth / 2
411 }
412
413 contentItem: QGCLabel {
414 text: validationErrorToast.text
415 color: qgcPal.alertText
416 }
417 }
418
419 Component {
420 id: toolSelectComponent
421
422 SelectViewDropdown {
423 }
424 }
425
426 Rectangle {
427 id: toolDrawer
428 objectName: "mainView_toolDrawer"
429 anchors.fill: parent
430 visible: false
431 color: qgcPal.window
432
433 property var backIcon
434 property string toolTitle
435 property alias toolSource: toolDrawerLoader.source
436 property var toolIcon
437
438 onVisibleChanged: {
439 if (!toolDrawer.visible) {
440 toolDrawerLoader.source = ""
441 }
442 }
443
444 // This need to block click event leakage to underlying map.
445 DeadMouseArea {
446 anchors.fill: parent
447 }
448
449 Rectangle {
450 id: toolDrawerToolbar
451 anchors.left: parent.left
452 anchors.right: parent.right
453 anchors.top: parent.top
454 height: ScreenTools.toolbarHeight
455 color: qgcPal.toolbarBackground
456
457 RowLayout {
458 id: toolDrawerToolbarLayout
459 anchors.leftMargin: ScreenTools.defaultFontPixelWidth
460 anchors.left: parent.left
461 anchors.top: parent.top
462 anchors.bottom: parent.bottom
463 spacing: ScreenTools.defaultFontPixelWidth
464
465 QGCToolBarButton {
466 id: qgcButton
467 objectName: "toolbar_qgcLogo"
468 height: parent.height
469 icon.source: "/res/QGCLogoFull.svg"
470 logo: true
471 onClicked: mainWindow.showToolSelectDialog()
472 }
473
474 QGCLabel {
475 id: toolbarDrawerText
476 text: toolDrawer.toolTitle
477 font.pointSize: ScreenTools.largeFontPointSize
478 }
479 }
480 }
481
482 Loader {
483 id: toolDrawerLoader
484 anchors.left: parent.left
485 anchors.right: parent.right
486 anchors.top: toolDrawerToolbar.bottom
487 anchors.bottom: parent.bottom
488 }
489 }
490
491 //-------------------------------------------------------------------------
492 //-- Critical Vehicle Message Popup
493
494 function showCriticalVehicleMessage(message) {
495 if (suppressCriticalVehicleMessages) {
496 return
497 }
498 if (criticalVehicleMessagePopup.visible || QGroundControl.videoManager.fullScreen) {
499 // We received additional warning message while an older warning message was still displayed.
500 // When the user close the older one drop the message indicator tool so they can see the rest of them.
501 criticalVehicleMessagePopup.additionalCriticalMessagesReceived = true
502 } else {
503 criticalVehicleMessagePopup.criticalVehicleMessage = message
504 criticalVehicleMessagePopup.additionalCriticalMessagesReceived = false
505 criticalVehicleMessagePopup.open()
506 }
507 }
508
509 Popup {
510 id: criticalVehicleMessagePopup
511 y: ScreenTools.toolbarHeight + ScreenTools.defaultFontPixelHeight
512 x: Math.round((mainWindow.width - width) * 0.5)
513 width: mainWindow.width * 0.55
514 height: criticalVehicleMessageText.contentHeight + ScreenTools.defaultFontPixelHeight * 2
515 modal: false
516 focus: true
517
518 property alias criticalVehicleMessage: criticalVehicleMessageText.text
519 property bool additionalCriticalMessagesReceived: false
520
521 background: Rectangle {
522 anchors.fill: parent
523 color: qgcPal.alertBackground
524 radius: ScreenTools.defaultFontPixelHeight * 0.5
525 border.color: qgcPal.alertBorder
526 border.width: 2
527
528 Rectangle {
529 anchors.horizontalCenter: parent.horizontalCenter
530 anchors.top: parent.top
531 anchors.topMargin: -(height / 2)
532 color: qgcPal.alertBackground
533 radius: ScreenTools.defaultFontPixelHeight * 0.25
534 border.color: qgcPal.alertBorder
535 border.width: 1
536 width: vehicleWarningLabel.contentWidth + _margins
537 height: vehicleWarningLabel.contentHeight + _margins
538
539 property real _margins: ScreenTools.defaultFontPixelHeight * 0.25
540
541 QGCLabel {
542 id: vehicleWarningLabel
543 anchors.centerIn: parent
544 text: qsTr("Vehicle Error")
545 font.pointSize: ScreenTools.smallFontPointSize
546 color: qgcPal.alertText
547 }
548 }
549
550 Rectangle {
551 id: additionalErrorsIndicator
552 anchors.horizontalCenter: parent.horizontalCenter
553 anchors.bottom: parent.bottom
554 anchors.bottomMargin: -(height / 2)
555 color: qgcPal.alertBackground
556 radius: ScreenTools.defaultFontPixelHeight * 0.25
557 border.color: qgcPal.alertBorder
558 border.width: 1
559 width: additionalErrorsLabel.contentWidth + _margins
560 height: additionalErrorsLabel.contentHeight + _margins
561 visible: criticalVehicleMessagePopup.additionalCriticalMessagesReceived
562
563 property real _margins: ScreenTools.defaultFontPixelHeight * 0.25
564
565 QGCLabel {
566 id: additionalErrorsLabel
567 anchors.centerIn: parent
568 text: qsTr("Additional errors received")
569 font.pointSize: ScreenTools.smallFontPointSize
570 color: qgcPal.alertText
571 }
572 }
573 }
574
575 QGCLabel {
576 id: criticalVehicleMessageText
577 width: criticalVehicleMessagePopup.width - ScreenTools.defaultFontPixelHeight
578 anchors.centerIn: parent
579 wrapMode: Text.WordWrap
580 color: qgcPal.alertText
581 textFormat: TextEdit.RichText
582 }
583
584 MouseArea {
585 anchors.fill: parent
586 onClicked: {
587 criticalVehicleMessagePopup.close()
588 if (criticalVehicleMessagePopup.additionalCriticalMessagesReceived) {
589 criticalVehicleMessagePopup.additionalCriticalMessagesReceived = false;
590 flyView.dropMainStatusIndicatorTool();
591 } else if (QGroundControl.multiVehicleManager.activeVehicle) {
592 QGroundControl.multiVehicleManager.activeVehicle.resetErrorLevelMessages();
593 }
594 }
595 }
596 }
597
598 //-------------------------------------------------------------------------
599 //-- Indicator Drawer
600
601 function showIndicatorDrawer(drawerComponent, indicatorItem) {
602 indicatorDrawer.sourceComponent = drawerComponent
603 indicatorDrawer.indicatorItem = indicatorItem
604 indicatorDrawer.open()
605 }
606
607 function closeIndicatorDrawer() {
608 indicatorDrawer.close()
609 }
610
611 Popup {
612 id: indicatorDrawer
613 x: calcXPosition()
614 y: ScreenTools.toolbarHeight + _margins
615 leftInset: 0
616 rightInset: 0
617 topInset: 0
618 bottomInset: 0
619 padding: _margins * 2
620 visible: false
621 modal: true
622 focus: true
623 closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutside
624
625 property var sourceComponent
626 property var indicatorItem
627
628 property bool _expanded: false
629 property real _margins: ScreenTools.defaultFontPixelHeight / 4
630
631 function calcXPosition() {
632 if (indicatorItem) {
633 var xCenter = indicatorItem.mapToItem(mainWindow.contentItem, indicatorItem.width / 2, 0).x
634 return Math.max(_margins, Math.min(xCenter - (contentItem.implicitWidth / 2), mainWindow.contentItem.width - contentItem.implicitWidth - _margins - (indicatorDrawer.padding * 2) - (ScreenTools.defaultFontPixelHeight / 2)))
635 } else {
636 return _margins
637 }
638 }
639
640 onOpened: {
641 _expanded = false;
642 indicatorDrawerLoader.sourceComponent = indicatorDrawer.sourceComponent
643 }
644 onClosed: {
645 _expanded = false
646 indicatorItem = undefined
647 indicatorDrawerLoader.sourceComponent = undefined
648 }
649
650 background: Item {
651 Rectangle {
652 id: backgroundRect
653 anchors.fill: parent
654 color: QGroundControl.globalPalette.window
655 radius: indicatorDrawer._margins
656 opacity: 0.85
657 }
658
659 Rectangle {
660 objectName: "indicatorDrawerExpandButton"
661 anchors.horizontalCenter: backgroundRect.right
662 anchors.verticalCenter: backgroundRect.top
663 width: ScreenTools.largeFontPixelHeight
664 height: width
665 radius: width / 2
666 color: QGroundControl.globalPalette.button
667 border.color: QGroundControl.globalPalette.buttonText
668 visible: indicatorDrawerLoader.item && indicatorDrawerLoader.item._showExpand && !indicatorDrawer._expanded
669
670 QGCLabel {
671 anchors.centerIn: parent
672 text: ">"
673 color: QGroundControl.globalPalette.buttonText
674 }
675
676 QGCMouseArea {
677 fillItem: parent
678 onClicked: indicatorDrawer._expanded = true
679 }
680 }
681 }
682
683 contentItem: QGCFlickable {
684 id: indicatorDrawerLoaderFlickable
685 implicitWidth: Math.min(mainWindow.contentItem.width - (2 * indicatorDrawer._margins) - (indicatorDrawer.padding * 2), indicatorDrawerLoader.width)
686 implicitHeight: Math.min(mainWindow.contentItem.height - ScreenTools.toolbarHeight - (2 * indicatorDrawer._margins) - (indicatorDrawer.padding * 2), indicatorDrawerLoader.height)
687 contentWidth: indicatorDrawerLoader.width
688 contentHeight: indicatorDrawerLoader.height
689
690 Loader {
691 id: indicatorDrawerLoader
692 objectName: "indicatorDrawerLoader"
693
694 Binding {
695 target: indicatorDrawerLoader.item
696 property: "expanded"
697 value: indicatorDrawer._expanded
698 }
699
700 Binding {
701 target: indicatorDrawerLoader.item
702 property: "drawer"
703 value: indicatorDrawer
704 }
705 }
706 }
707 }
708
709 // Analyze page items (both in-panel and popped-out windows) are created with mainWindow as their
710 // QObject parent so their lifetime is not tied to AnalyzeView. This lets a popped-out window
711 // survive AnalyzeView being unloaded from the tool drawer.
712
713 // Tracks the analyze page item currently shown inside AnalyzeView's panel (not popped out).
714 // null when no page is loaded or the item has been handed off to a popup window.
715 property var _inPanelAnalyzePage: null
716
717 // Called by AnalyzeView.Component.onDestruction to destroy the in-panel item while
718 // panelContainer is still alive.
719 function destroyInPanelAnalyzePage() {
720 if (_inPanelAnalyzePage) {
721 _inPanelAnalyzePage.destroy()
722 _inPanelAnalyzePage = null
723 }
724 }
725
726 // Called by AnalyzeView to create an analyze page item owned by mainWindow.
727 // The caller sets the visual parent to panelContainer after creation.
728 function createAnalyzePage(source) {
729 if (_inPanelAnalyzePage) {
730 _inPanelAnalyzePage.destroy()
731 _inPanelAnalyzePage = null
732 }
733 var component = Qt.createComponent(source)
734 if (component.status !== Component.Ready) {
735 console.warn("createAnalyzePage failed source:", source, "errorString:", component.errorString())
736 return null
737 }
738 _inPanelAnalyzePage = component.createObject(mainWindow)
739 return _inPanelAnalyzePage
740 }
741
742 // Called by AnalyzeView when the in-panel item is handed off to a popup window.
743 // Clears _inPanelAnalyzePage so destroyInPanelAnalyzePage() does not destroy it
744 // when AnalyzeView is torn down.
745 function analyzePageMovedToPopup() {
746 _inPanelAnalyzePage = null
747 }
748
749 function createWindowedAnalyzePage(title, source, requiresVehicle, existingItem) {
750 var windowedPage = windowedAnalyzePage.createObject(mainWindow)
751 windowedPage.title = title
752 windowedPage.requiresVehicle = requiresVehicle
753 if (existingItem) {
754 windowedPage.adoptItem(existingItem)
755 } else {
756 windowedPage.source = source
757 }
758 windowedPage.visible = true
759 }
760
761 Component {
762 id: windowedAnalyzePage
763
764 Window {
765 width: ScreenTools.defaultFontPixelWidth * 100
766 height: ScreenTools.defaultFontPixelHeight * 40
767 visible: false
768
769 property alias source: loader.source
770 property bool requiresVehicle: false
771
772 function adoptItem(item) {
773 loader.visible = false
774 loader.source = ""
775 item.parent = contentRect
776 item.anchors.fill = contentRect
777 item.popped = true
778 item.visible = true
779 }
780
781 Connections {
782 target: QGroundControl.multiVehicleManager
783 function onActiveVehicleChanged() {
784 if (requiresVehicle) {
785 close()
786 }
787 }
788 }
789
790 Rectangle {
791 id: contentRect
792 color: QGroundControl.globalPalette.window
793 anchors.fill: parent
794
795 Loader {
796 id: loader
797 anchors.fill: parent
798 onLoaded: item.popped = true
799 }
800 }
801
802 onClosing: {
803 visible = false
804 // Destroy any reparented children (not owned by loader)
805 for (var i = contentRect.children.length - 1; i >= 0; i--) {
806 var child = contentRect.children[i]
807 if (child !== loader) {
808 child.destroy()
809 }
810 }
811 source = ""
812 Qt.callLater(destroy)
813 }
814 }
815 }
816}