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 toolDrawer.visible = false
140 }
141
142 function showFlyView() {
143 flyView.visible = true
144 planView.visible = false
145 toolDrawer.visible = false
146 }
147
148 function showTool(toolTitle, toolSource, toolIcon) {
149 toolDrawer.backIcon = flyView.visible ? "/qmlimages/PaperPlane.svg" : "/qmlimages/Plan.svg"
150 toolDrawer.toolTitle = toolTitle
151 toolDrawer.toolSource = toolSource
152 toolDrawer.toolIcon = toolIcon
153 toolDrawer.visible = true
154 }
155
156 function showAnalyzeTool() {
157 showTool(qsTr("Analyze Tools"), "qrc:/qml/QGroundControl/AnalyzeView/AnalyzeView.qml", "/qmlimages/Analyze.svg")
158 }
159
160 function showVehicleConfig() {
161 showTool(qsTr("Vehicle Configuration"), "qrc:/qml/QGroundControl/VehicleSetup/VehicleConfigView.qml", "/qmlimages/Gears.svg")
162 }
163
164 function showVehicleConfigParametersPage() {
165 showVehicleConfig()
166 toolDrawerLoader.item.showParametersPanel()
167 }
168
169 function showKnownVehicleComponentConfigPage(knownVehicleComponent) {
170 showVehicleConfig()
171 let vehicleComponent = globals.activeVehicle.autopilotPlugin.findKnownVehicleComponent(knownVehicleComponent)
172 if (vehicleComponent) {
173 toolDrawerLoader.item.showVehicleComponentPanel(vehicleComponent)
174 }
175 }
176
177 function showSettingsTool(settingsPage = "") {
178 showTool(qsTr("Application Settings"), "qrc:/qml/QGroundControl/Controls/AppSettings.qml", "/res/QGCLogoWhite")
179 if (settingsPage !== "") {
180 toolDrawerLoader.item.showSettingsPage(settingsPage)
181 }
182 }
183
184 //-------------------------------------------------------------------------
185 //-- Global simple message dialog
186
187 function _showMessageDialogWorker(owner, dialogTitle, dialogText, buttons = Dialog.Ok, acceptFunction = null, closeFunction = null, bypassNavigationCheck = false) {
188 let dialog = simpleMessageDialogComponent.createObject(owner, { title: dialogTitle, text: dialogText, buttons: buttons, acceptFunction: acceptFunction, closeFunction: closeFunction, bypassNavigationCheck: bypassNavigationCheck })
189 dialog.open()
190 }
191
192 // This variant is only meant to be called by QGCApplication
193 function _showMessageDialog(dialogTitle, dialogText) {
194 _showMessageDialogWorker(mainWindow, dialogTitle, dialogText)
195 }
196
197 Connections {
198 target: QGroundControl
199
200 function onShowMessageDialogRequested(owner, title, text, buttons, acceptFunction, closeFunction) {
201 _showMessageDialogWorker(owner, title, text, buttons, acceptFunction, closeFunction)
202 }
203 }
204
205 Component {
206 id: simpleMessageDialogComponent
207
208 QGCSimpleMessageDialog {
209 }
210 }
211
212 property bool _forceClose: false
213 property bool suppressCriticalVehicleMessages: false
214
215 function finishCloseProcess() {
216 _forceClose = true
217 // For some reason on the Qml side Qt doesn't automatically disconnect a signal when an object is destroyed.
218 // So we have to do it ourselves otherwise the signal flows through on app shutdown to an object which no longer exists.
219 firstRunPromptManager.clearNextPromptSignal()
220 QGroundControl.linkManager.shutdown()
221 QGroundControl.videoManager.stopVideo();
222 mainWindow.close()
223 }
224
225 // Check for things which should prevent the app from closing
226 // Returns true if it is OK to close
227 readonly property int _skipUnsavedMissionCheckMask: 0x01
228 readonly property int _skipPendingParameterWritesCheckMask: 0x02
229 readonly property int _skipActiveConnectionsCheckMask: 0x04
230 property int _closeChecksToSkip: 0
231 property bool _reentrantCloseGuard: false
232 function performCloseChecks() {
233 if (!(_closeChecksToSkip & _skipUnsavedMissionCheckMask) && !checkForUnsavedMission()) {
234 return false
235 }
236 if (!(_closeChecksToSkip & _skipPendingParameterWritesCheckMask) && !checkForPendingParameterWrites()) {
237 return false
238 }
239 if (!(_closeChecksToSkip & _skipActiveConnectionsCheckMask) && !checkForActiveConnections()) {
240 return false
241 }
242 finishCloseProcess()
243 return true
244 }
245
246 function checkForUnsavedMission() {
247 // Only warn when edits are neither saved to disk nor uploaded to the vehicle.
248 // If either happened the edits are recoverable, so closing loses nothing.
249 // With no active vehicle an upload can't have happened, so treat the plan as
250 // not uploaded regardless of dirtyForUpload.
251 if (planView._planMasterController.dirtyForSave &&
252 (planView._planMasterController.dirtyForUpload || !QGroundControl.multiVehicleManager.activeVehicle)) {
253 let accepted = false
254 _reentrantCloseGuard = true
255 _showMessageDialogWorker(mainWindow, qsTr("Unsaved Mission"),
256 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?"),
257 Dialog.Yes | Dialog.No,
258 function() { accepted = true; _closeChecksToSkip |= _skipUnsavedMissionCheckMask; performCloseChecks() },
259 function() { if (!accepted) _reentrantCloseGuard = false },
260 true /* bypassNavigationCheck */)
261 return false
262 } else {
263 return true
264 }
265 }
266
267 function checkForPendingParameterWrites() {
268 for (var index=0; index<QGroundControl.multiVehicleManager.vehicles.count; index++) {
269 if (QGroundControl.multiVehicleManager.vehicles.get(index).parameterManager.pendingWrites) {
270 let accepted = false
271 _reentrantCloseGuard = true
272 _showMessageDialogWorker(mainWindow, qsTr("Pending Parameter Updates"),
273 qsTr("You have pending parameter updates to a vehicle. If you close you will lose changes. Are you sure you want to close?"),
274 Dialog.Yes | Dialog.No,
275 function() { accepted = true; _closeChecksToSkip |= _skipPendingParameterWritesCheckMask; performCloseChecks() },
276 function() { if (!accepted) _reentrantCloseGuard = false },
277 true /* bypassNavigationCheck */)
278 return false
279 }
280 }
281 return true
282 }
283
284 function checkForActiveConnections() {
285 if (QGroundControl.multiVehicleManager.activeVehicle) {
286 let accepted = false
287 _reentrantCloseGuard = true
288 _showMessageDialogWorker(mainWindow, qsTr("Active Vehicle Connections"),
289 qsTr("There are still active connections to vehicles. Are you sure you want to exit?"),
290 Dialog.Yes | Dialog.No,
291 function() { accepted = true; _closeChecksToSkip |= _skipActiveConnectionsCheckMask; performCloseChecks() },
292 function() { if (!accepted) _reentrantCloseGuard = false },
293 true /* bypassNavigationCheck */)
294 return false
295 } else {
296 return true
297 }
298 }
299
300 onClosing: (close) => {
301 if (!_forceClose) {
302 if (_reentrantCloseGuard) {
303 close.accepted = false
304 return
305 }
306 _closeChecksToSkip = 0
307 close.accepted = performCloseChecks()
308 }
309 }
310
311 background: Rectangle {
312 anchors.fill: parent
313 color: QGroundControl.globalPalette.window
314 }
315
316 // Disabled under the tool drawer: pointer handlers (e.g. GeoMap DragHandler) still get presses through it and steal drags
317 FlyView {
318 id: flyView
319 objectName: "mainView_fly"
320 anchors.fill: parent
321 enabled: !toolDrawer.visible
322 }
323
324 PlanView {
325 id: planView
326 objectName: "mainView_plan"
327 anchors.fill: parent
328 visible: false
329 enabled: !toolDrawer.visible
330 }
331
332 footer: LogReplayStatusBar {
333 visible: QGroundControl.settingsManager.flyViewSettings.showLogReplayStatusBar.rawValue
334 }
335
336 MessageDialog {
337 id: showTouchAreasNotification
338 title: qsTr("Debug Touch Areas")
339 text: qsTr("Touch Area display toggled")
340 buttons: MessageDialog.Ok
341 }
342
343 MessageDialog {
344 id: advancedModeOnConfirmation
345 title: qsTr("Advanced Mode")
346 text: QGroundControl.corePlugin.showAdvancedUIMessage
347 buttons: MessageDialog.Yes | MessageDialog.No
348 onButtonClicked: function (button, role) {
349 if (button === MessageDialog.Yes) {
350 QGroundControl.corePlugin.showAdvancedUI = true
351 }
352 }
353 }
354
355 MessageDialog {
356 id: advancedModeOffConfirmation
357 title: qsTr("Advanced Mode")
358 text: qsTr("Turn off Advanced Mode?")
359 buttons: MessageDialog.Yes | MessageDialog.No
360 onButtonClicked: function (button, role) {
361 if (button === MessageDialog.Yes) {
362 QGroundControl.corePlugin.showAdvancedUI = false
363 }
364 }
365 }
366
367 function showToolSelectDialog() {
368 if (mainWindow.allowViewSwitch()) {
369 mainWindow.showIndicatorDrawer(toolSelectComponent, null)
370 }
371 }
372
373 // Toast notification shown when a view switch is blocked by a validation error
374 ToolTip {
375 id: validationErrorToast
376 x: (mainWindow.width - width) / 2
377 y: mainWindow.height - height - ScreenTools.defaultFontPixelHeight * 3
378 timeout: 3000
379 closePolicy: Popup.NoAutoClose
380 text: qsTr("Please correct the invalid value before continuing")
381
382 background: Rectangle {
383 color: qgcPal.alertBackground
384 radius: ScreenTools.defaultFontPixelWidth / 2
385 }
386
387 contentItem: QGCLabel {
388 text: validationErrorToast.text
389 color: qgcPal.alertText
390 }
391 }
392
393 Component {
394 id: toolSelectComponent
395
396 SelectViewDropdown {
397 }
398 }
399
400 Rectangle {
401 id: toolDrawer
402 objectName: "mainView_toolDrawer"
403 anchors.fill: parent
404 visible: false
405 color: qgcPal.window
406
407 property var backIcon
408 property string toolTitle
409 property alias toolSource: toolDrawerLoader.source
410 property var toolIcon
411
412 onVisibleChanged: {
413 if (!toolDrawer.visible) {
414 toolDrawerLoader.source = ""
415 }
416 }
417
418 // This need to block click event leakage to underlying map.
419 DeadMouseArea {
420 anchors.fill: parent
421 }
422
423 Rectangle {
424 id: toolDrawerToolbar
425 anchors.left: parent.left
426 anchors.right: parent.right
427 anchors.top: parent.top
428 height: ScreenTools.toolbarHeight
429 color: qgcPal.toolbarBackground
430
431 RowLayout {
432 id: toolDrawerToolbarLayout
433 anchors.leftMargin: ScreenTools.defaultFontPixelWidth
434 anchors.left: parent.left
435 anchors.top: parent.top
436 anchors.bottom: parent.bottom
437 spacing: ScreenTools.defaultFontPixelWidth
438
439 QGCToolBarButton {
440 id: qgcButton
441 objectName: "toolbar_qgcLogo"
442 height: parent.height
443 icon.source: "/res/QGCLogoFull.svg"
444 logo: true
445 onClicked: mainWindow.showToolSelectDialog()
446 }
447
448 QGCLabel {
449 id: toolbarDrawerText
450 text: toolDrawer.toolTitle
451 font.pointSize: ScreenTools.largeFontPointSize
452 }
453 }
454 }
455
456 Loader {
457 id: toolDrawerLoader
458 anchors.left: parent.left
459 anchors.right: parent.right
460 anchors.top: toolDrawerToolbar.bottom
461 anchors.bottom: parent.bottom
462 }
463 }
464
465 //-------------------------------------------------------------------------
466 //-- Critical Vehicle Message Popup
467
468 function showCriticalVehicleMessage(message) {
469 if (suppressCriticalVehicleMessages) {
470 return
471 }
472 if (criticalVehicleMessagePopup.visible) {
473 // Once a message is held back, later ones are too so the shown messages stay in order
474 if (!criticalVehicleMessagePopup.additionalCriticalMessagesReceived && criticalVehicleMessagePopup.appendMessageIfRoom(message)) {
475 criticalVehicleMessageDismissTimer.restart()
476 } else if (!criticalVehicleMessagePopup.additionalCriticalMessagesReceived) {
477 // When the user closes the popup drop the message indicator tool so they can see the rest.
478 criticalVehicleMessagePopup.additionalCriticalMessagesReceived = true
479 // Leave time to act on the new "Click to see more" prompt
480 criticalVehicleMessageDismissTimer.restart()
481 }
482 } else if (QGroundControl.videoManager.fullScreen) {
483 criticalVehicleMessagePopup.additionalCriticalMessagesReceived = true
484 } else {
485 criticalVehicleMessagePopup.criticalVehicleMessage = message
486 criticalVehicleMessagePopup.shownMessageCount = 1
487 criticalVehicleMessagePopup.additionalCriticalMessagesReceived = false
488 criticalVehicleMessagePopup.open()
489 }
490 }
491
492 // No focus and no Escape handler: either would steal keys from whatever the user is typing in.
493 Popup {
494 id: criticalVehicleMessagePopup
495 objectName: "criticalVehicleMessage_popup"
496 y: ScreenTools.toolbarHeight + ScreenTools.defaultFontPixelHeight - topInset
497 x: Math.round((mainWindow.width - width) * 0.5)
498 width: mainWindow.width * 0.55
499 height: criticalVehicleMessageText.contentHeight + _chromeHeight
500 topInset: vehicleWarningHeading.height / 2
501 topPadding: padding + topInset
502 modal: false
503 closePolicy: Popup.CloseOnPressOutside
504
505 property alias criticalVehicleMessage: criticalVehicleMessageText.text
506 property bool additionalCriticalMessagesReceived: false
507 property int shownMessageCount: 0
508 readonly property int maxShownMessages: 5
509
510 readonly property real _chromeHeight: topInset + ScreenTools.defaultFontPixelHeight
511 readonly property real _maxHeight: mainWindow.height - y - ScreenTools.defaultFontPixelHeight
512
513 function appendMessageIfRoom(message) {
514 if (shownMessageCount >= maxShownMessages) {
515 return false
516 }
517 const previousMessages = criticalVehicleMessage
518 criticalVehicleMessage += "<br/>" + message
519 if (criticalVehicleMessageText.contentHeight + _chromeHeight > _maxHeight) {
520 criticalVehicleMessage = previousMessages
521 return false
522 }
523 shownMessageCount++
524 return true
525 }
526
527 function acknowledge() {
528 close()
529 if (additionalCriticalMessagesReceived) {
530 additionalCriticalMessagesReceived = false
531 flyView.dropMainStatusIndicatorTool()
532 }
533 }
534
535 background: Rectangle {
536 color: qgcPal.alertBackground
537 radius: ScreenTools.defaultFontPixelHeight * 0.5
538 border.color: qgcPal.alertBorder
539 border.width: 2
540
541 Rectangle {
542 id: vehicleWarningHeading
543 objectName: "criticalVehicleMessage_heading"
544 anchors.horizontalCenter: parent.horizontalCenter
545 anchors.top: parent.top
546 anchors.topMargin: -(height / 2)
547 color: qgcPal.alertBackground
548 radius: ScreenTools.defaultFontPixelHeight * 0.25
549 border.color: qgcPal.alertBorder
550 border.width: 1
551 width: Math.min(vehicleWarningLabel.implicitWidth + _margins, parent.width - ScreenTools.defaultFontPixelHeight)
552 height: vehicleWarningLabel.contentHeight + _margins
553
554 property real _margins: ScreenTools.defaultFontPixelHeight * 0.25
555
556 QGCLabel {
557 id: vehicleWarningLabel
558 anchors.centerIn: parent
559 width: parent.width - parent._margins
560 horizontalAlignment: Text.AlignHCenter
561 elide: Text.ElideRight
562 text: criticalVehicleMessagePopup.additionalCriticalMessagesReceived ?
563 qsTr("Vehicle Alert - Click to see more") :
564 qsTr("Vehicle Alert")
565 font.pointSize: ScreenTools.smallFontPointSize
566 color: qgcPal.alertText
567 }
568 }
569
570 // Extends over the heading's overhang so clicking the heading acknowledges too
571 MouseArea {
572 anchors.fill: parent
573 anchors.topMargin: -criticalVehicleMessagePopup.topInset
574 onClicked: criticalVehicleMessagePopup.acknowledge()
575 }
576 }
577
578 QGCLabel {
579 id: criticalVehicleMessageText
580 objectName: "criticalVehicleMessage_text"
581 width: criticalVehicleMessagePopup.width - ScreenTools.defaultFontPixelHeight
582 anchors.centerIn: parent
583 wrapMode: Text.WordWrap
584 color: qgcPal.alertText
585 textFormat: TextEdit.RichText
586 }
587
588 // Plain close, not acknowledge(): a timeout must not drop the status indicator drawer.
589 Timer {
590 id: criticalVehicleMessageDismissTimer
591 objectName: "criticalVehicleMessage_dismissTimer"
592 interval: 10000
593 running: criticalVehicleMessagePopup.visible
594 onTriggered: criticalVehicleMessagePopup.close()
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 // Fires before the active vehicle is cleared, so drawer contents never see it go null
612 Connections {
613 target: QGroundControl.multiVehicleManager
614 function onActiveVehicleAvailableChanged(activeVehicleAvailable) {
615 if (!activeVehicleAvailable) {
616 closeIndicatorDrawer()
617 }
618 }
619 }
620
621 Popup {
622 id: indicatorDrawer
623 x: calcXPosition()
624 y: ScreenTools.toolbarHeight + _margins
625 leftInset: 0
626 rightInset: 0
627 topInset: 0
628 bottomInset: 0
629 padding: _margins * 2
630 visible: false
631 modal: true
632 focus: true
633 closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutside
634
635 property var sourceComponent
636 property var indicatorItem
637
638 property bool _expanded: false
639 property real _margins: ScreenTools.defaultFontPixelHeight / 4
640 property bool _fillWindow: indicatorDrawerLoader.item ? indicatorDrawerLoader.item.fillWindow === true : false
641
642 function calcXPosition() {
643 if (_fillWindow) {
644 return _margins
645 }
646 if (indicatorItem) {
647 var xCenter = indicatorItem.mapToItem(mainWindow.contentItem, indicatorItem.width / 2, 0).x
648 return Math.max(_margins, Math.min(xCenter - (contentItem.implicitWidth / 2), mainWindow.contentItem.width - contentItem.implicitWidth - _margins - (indicatorDrawer.padding * 2) - (ScreenTools.defaultFontPixelHeight / 2)))
649 } else {
650 return _margins
651 }
652 }
653
654 onOpened: {
655 _expanded = false;
656 indicatorDrawerLoader.sourceComponent = indicatorDrawer.sourceComponent
657 }
658 onClosed: {
659 _expanded = false
660 indicatorItem = undefined
661 indicatorDrawerLoader.sourceComponent = undefined
662 }
663
664 background: Item {
665 Rectangle {
666 id: backgroundRect
667 anchors.fill: parent
668 color: QGroundControl.globalPalette.window
669 radius: indicatorDrawer._margins
670 opacity: 0.85
671 }
672
673 Rectangle {
674 objectName: "indicatorDrawerExpandButton"
675 anchors.horizontalCenter: backgroundRect.right
676 anchors.verticalCenter: backgroundRect.top
677 width: ScreenTools.largeFontPixelHeight
678 height: width
679 radius: width / 2
680 color: QGroundControl.globalPalette.button
681 border.color: QGroundControl.globalPalette.buttonText
682 visible: indicatorDrawerLoader.item && indicatorDrawerLoader.item._showExpand && !indicatorDrawer._expanded
683
684 QGCLabel {
685 anchors.centerIn: parent
686 text: ">"
687 color: QGroundControl.globalPalette.buttonText
688 }
689
690 QGCMouseArea {
691 fillItem: parent
692 onClicked: indicatorDrawer._expanded = true
693 }
694 }
695 }
696
697 contentItem: QGCFlickable {
698 id: indicatorDrawerLoaderFlickable
699 implicitWidth: indicatorDrawer._fillWindow ? _availableWidth : Math.min(_availableWidth, indicatorDrawerLoader.width)
700 implicitHeight: indicatorDrawer._fillWindow ? _availableHeight : Math.min(_availableHeight, indicatorDrawerLoader.height)
701 contentWidth: indicatorDrawerLoader.width
702 contentHeight: indicatorDrawerLoader.height
703
704 property real _availableWidth: mainWindow.contentItem.width - (2 * indicatorDrawer._margins) - (indicatorDrawer.padding * 2)
705 property real _availableHeight: mainWindow.contentItem.height - ScreenTools.toolbarHeight - (2 * indicatorDrawer._margins) - (indicatorDrawer.padding * 2)
706
707 Loader {
708 id: indicatorDrawerLoader
709 objectName: "indicatorDrawerLoader"
710 width: indicatorDrawer._fillWindow ? indicatorDrawerLoaderFlickable.width : undefined
711
712 Binding {
713 target: indicatorDrawerLoader.item
714 property: "expanded"
715 value: indicatorDrawer._expanded
716 }
717
718 Binding {
719 target: indicatorDrawerLoader.item
720 property: "drawer"
721 value: indicatorDrawer
722 }
723 }
724 }
725 }
726
727 // Analyze page items (both in-panel and popped-out windows) are created with mainWindow as their
728 // QObject parent so their lifetime is not tied to AnalyzeView. This lets a popped-out window
729 // survive AnalyzeView being unloaded from the tool drawer.
730
731 // Tracks the analyze page item currently shown inside AnalyzeView's panel (not popped out).
732 // null when no page is loaded or the item has been handed off to a popup window.
733 property var _inPanelAnalyzePage: null
734
735 // Called by AnalyzeView.Component.onDestruction to destroy the in-panel item while
736 // panelContainer is still alive.
737 function destroyInPanelAnalyzePage() {
738 if (_inPanelAnalyzePage) {
739 _inPanelAnalyzePage.destroy()
740 _inPanelAnalyzePage = null
741 }
742 }
743
744 // Called by AnalyzeView to create an analyze page item owned by mainWindow.
745 // The caller sets the visual parent to panelContainer after creation.
746 function createAnalyzePage(source) {
747 if (_inPanelAnalyzePage) {
748 _inPanelAnalyzePage.destroy()
749 _inPanelAnalyzePage = null
750 }
751 var component = Qt.createComponent(source)
752 if (component.status !== Component.Ready) {
753 console.warn("createAnalyzePage failed source:", source, "errorString:", component.errorString())
754 return null
755 }
756 _inPanelAnalyzePage = component.createObject(mainWindow)
757 return _inPanelAnalyzePage
758 }
759
760 // Called by AnalyzeView when the in-panel item is handed off to a popup window.
761 // Clears _inPanelAnalyzePage so destroyInPanelAnalyzePage() does not destroy it
762 // when AnalyzeView is torn down.
763 function analyzePageMovedToPopup() {
764 _inPanelAnalyzePage = null
765 }
766
767 function createWindowedAnalyzePage(title, source, requiresVehicle, existingItem) {
768 var windowedPage = windowedAnalyzePage.createObject(mainWindow)
769 windowedPage.title = title
770 windowedPage.requiresVehicle = requiresVehicle
771 if (existingItem) {
772 windowedPage.adoptItem(existingItem)
773 } else {
774 windowedPage.source = source
775 }
776 windowedPage.visible = true
777 }
778
779 Component {
780 id: windowedAnalyzePage
781
782 Window {
783 width: ScreenTools.defaultFontPixelWidth * 100
784 height: ScreenTools.defaultFontPixelHeight * 40
785 visible: false
786
787 property alias source: loader.source
788 property bool requiresVehicle: false
789
790 function adoptItem(item) {
791 loader.visible = false
792 loader.source = ""
793 item.parent = contentRect
794 item.anchors.fill = contentRect
795 item.popped = true
796 item.visible = true
797 }
798
799 Connections {
800 target: QGroundControl.multiVehicleManager
801 function onActiveVehicleChanged() {
802 if (requiresVehicle) {
803 close()
804 }
805 }
806 }
807
808 Rectangle {
809 id: contentRect
810 color: QGroundControl.globalPalette.window
811 anchors.fill: parent
812
813 Loader {
814 id: loader
815 anchors.fill: parent
816 onLoaded: item.popped = true
817 }
818 }
819
820 onClosing: {
821 visible = false
822 // Destroy any reparented children (not owned by loader)
823 for (var i = contentRect.children.length - 1; i >= 0; i--) {
824 var child = contentRect.children[i]
825 if (child !== loader) {
826 child.destroy()
827 }
828 }
829 source = ""
830 Qt.callLater(destroy)
831 }
832 }
833 }
834}