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 Component.onCompleted: {
24 // Start the sequence of first run prompt(s)
25 firstRunPromptManager.nextPrompt()
26 }
27
28 /// Saves main window position and size and re-opens it in the same position and size next time
29 MainWindowSavedState {
30 window: mainWindow
31 }
32
33 QtObject {
34 id: firstRunPromptManager
35
36 property var currentDialog: null
37 property var rgPromptIds: QGroundControl.corePlugin.firstRunPromptsToShow()
38 property int nextPromptIdIndex: 0
39
40 function clearNextPromptSignal() {
41 if (currentDialog) {
42 currentDialog.closed.disconnect(nextPrompt)
43 }
44 }
45
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)
51 currentDialog.open()
52 nextPromptIdIndex++
53 } else {
54 currentDialog = null
55 showPreFlightChecklistIfNeeded()
56 }
57 }
58 }
59
60 readonly property real _topBottomMargins: ScreenTools.defaultFontPixelHeight * 0.5
61
62 //-------------------------------------------------------------------------
63 //-- Global Scope Variables
64
65 QtObject {
66 id: globals
67
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
73
74 // Number of QGCTextField's with validation errors. Used to prevent closing panels with validation errors.
75 property int validationErrorCount: 0
76
77 // Set to a non-empty string to block navigation with a custom reason (e.g. during calibration)
78 property string navigationBlockedReason: ""
79
80 // Property to manage RemoteID quick access to settings page
81 property bool commingFromRIDIndicator: false
82 }
83
84 /// Default color palette used throughout the UI
85 QGCPalette { id: qgcPal; colorGroupEnabled: true }
86
87 //-------------------------------------------------------------------------
88 //-- Actions
89
90 signal armVehicleRequest
91 signal forceArmVehicleRequest
92 signal disarmVehicleRequest
93 signal vtolTransitionToFwdFlightRequest
94 signal vtolTransitionToMRFlightRequest
95 signal showPreFlightChecklistIfNeeded
96
97 //-------------------------------------------------------------------------
98 //-- Global Scope Functions
99
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()
108 }
109 validationErrorToast.open()
110 }
111 return false
112 }
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()
116 }
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()
122 }
123 validationErrorToast.open()
124 }
125 return allowed
126 }
127
128 function showPlanView() {
129 flyView.visible = false
130 planView.visible = true
131 toolDrawer.visible = false
132 }
133
134 function showFlyView() {
135 flyView.visible = true
136 planView.visible = false
137 toolDrawer.visible = false
138 }
139
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
146 }
147
148 function showAnalyzeTool() {
149 showTool(qsTr("Analyze Tools"), "qrc:/qml/QGroundControl/AnalyzeView/AnalyzeView.qml", "/qmlimages/Analyze.svg")
150 }
151
152 function showVehicleConfig() {
153 showTool(qsTr("Vehicle Configuration"), "qrc:/qml/QGroundControl/VehicleSetup/VehicleConfigView.qml", "/qmlimages/Gears.svg")
154 }
155
156 function showVehicleConfigParametersPage() {
157 showVehicleConfig()
158 toolDrawerLoader.item.showParametersPanel()
159 }
160
161 function showKnownVehicleComponentConfigPage(knownVehicleComponent) {
162 showVehicleConfig()
163 let vehicleComponent = globals.activeVehicle.autopilotPlugin.findKnownVehicleComponent(knownVehicleComponent)
164 if (vehicleComponent) {
165 toolDrawerLoader.item.showVehicleComponentPanel(vehicleComponent)
166 }
167 }
168
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)
173 }
174 }
175
176 //-------------------------------------------------------------------------
177 //-- Global simple message dialog
178
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 })
181 dialog.open()
182 }
183
184 // This variant is only meant to be called by QGCApplication
185 function _showMessageDialog(dialogTitle, dialogText) {
186 _showMessageDialogWorker(mainWindow, dialogTitle, dialogText)
187 }
188
189 Connections {
190 target: QGroundControl
191
192 function onShowMessageDialogRequested(owner, title, text, buttons, acceptFunction, closeFunction) {
193 _showMessageDialogWorker(owner, title, text, buttons, acceptFunction, closeFunction)
194 }
195 }
196
197 Component {
198 id: simpleMessageDialogComponent
199
200 QGCSimpleMessageDialog {
201 }
202 }
203
204 property bool _forceClose: false
205
206 function finishCloseProcess() {
207 _forceClose = true
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();
213 mainWindow.close()
214 }
215
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()) {
224 return false
225 }
226 if (!(_closeChecksToSkip & _skipPendingParameterWritesCheckMask) && !checkForPendingParameterWrites()) {
227 return false
228 }
229 if (!(_closeChecksToSkip & _skipActiveConnectionsCheckMask) && !checkForActiveConnections()) {
230 return false
231 }
232 finishCloseProcess()
233 return true
234 }
235
236 property string closeDialogTitle: qsTr("Close %1").arg(QGroundControl.appName)
237
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() })
244 return false
245 } else {
246 return true
247 }
248 }
249
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() })
257 return false
258 }
259 }
260 return true
261 }
262
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() })
269 return false
270 } else {
271 return true
272 }
273 }
274
275 onClosing: (close) => {
276 if (!_forceClose) {
277 _closeChecksToSkip = 0
278 close.accepted = performCloseChecks()
279 }
280 }
281
282 background: Rectangle {
283 anchors.fill: parent
284 color: QGroundControl.globalPalette.window
285 }
286
287 FlyView {
288 id: flyView
289 anchors.fill: parent
290 }
291
292 PlanView {
293 id: planView
294 anchors.fill: parent
295 visible: false
296 }
297
298 footer: LogReplayStatusBar {
299 visible: QGroundControl.settingsManager.flyViewSettings.showLogReplayStatusBar.rawValue
300 }
301
302 MessageDialog {
303 id: showTouchAreasNotification
304 title: qsTr("Debug Touch Areas")
305 text: qsTr("Touch Area display toggled")
306 buttons: MessageDialog.Ok
307 }
308
309 MessageDialog {
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
317 }
318 }
319 }
320
321 MessageDialog {
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
329 }
330 }
331 }
332
333 function showToolSelectDialog() {
334 if (mainWindow.allowViewSwitch()) {
335 mainWindow.showIndicatorDrawer(toolSelectComponent, null)
336 }
337 }
338
339 // Toast notification shown when a view switch is blocked by a validation error
340 ToolTip {
341 id: validationErrorToast
342 x: (mainWindow.width - width) / 2
343 y: mainWindow.height - height - ScreenTools.defaultFontPixelHeight * 3
344 timeout: 3000
345 closePolicy: Popup.NoAutoClose
346 text: qsTr("Please correct the invalid value before continuing")
347
348 background: Rectangle {
349 color: qgcPal.alertBackground
350 radius: ScreenTools.defaultFontPixelWidth / 2
351 }
352
353 contentItem: QGCLabel {
354 text: validationErrorToast.text
355 color: qgcPal.alertText
356 }
357 }
358
359 Component {
360 id: toolSelectComponent
361
362 SelectViewDropdown {
363 }
364 }
365
366 Rectangle {
367 id: toolDrawer
368 anchors.fill: parent
369 visible: false
370 color: qgcPal.window
371
372 property var backIcon
373 property string toolTitle
374 property alias toolSource: toolDrawerLoader.source
375 property var toolIcon
376
377 onVisibleChanged: {
378 if (!toolDrawer.visible) {
379 toolDrawerLoader.source = ""
380 }
381 }
382
383 // This need to block click event leakage to underlying map.
384 DeadMouseArea {
385 anchors.fill: parent
386 }
387
388 Rectangle {
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
395
396 RowLayout {
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
403
404 QGCToolBarButton {
405 id: qgcButton
406 height: parent.height
407 icon.source: "/res/QGCLogoFull.svg"
408 logo: true
409 onClicked: mainWindow.showToolSelectDialog()
410 }
411
412 QGCLabel {
413 id: toolbarDrawerText
414 text: toolDrawer.toolTitle
415 font.pointSize: ScreenTools.largeFontPointSize
416 }
417 }
418 }
419
420 Loader {
421 id: toolDrawerLoader
422 anchors.left: parent.left
423 anchors.right: parent.right
424 anchors.top: toolDrawerToolbar.bottom
425 anchors.bottom: parent.bottom
426
427 Connections {
428 target: toolDrawerLoader.item
429 ignoreUnknownSignals: true
430 function onPopout() { toolDrawer.visible = false }
431 }
432 }
433 }
434
435 //-------------------------------------------------------------------------
436 //-- Critical Vehicle Message Popup
437
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
444 } else {
445 criticalVehicleMessagePopup.criticalVehicleMessage = message
446 criticalVehicleMessagePopup.additionalCriticalMessagesReceived = false
447 criticalVehicleMessagePopup.open()
448 }
449 }
450
451 Popup {
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
457 modal: false
458 focus: true
459
460 property alias criticalVehicleMessage: criticalVehicleMessageText.text
461 property bool additionalCriticalMessagesReceived: false
462
463 background: Rectangle {
464 anchors.fill: parent
465 color: qgcPal.alertBackground
466 radius: ScreenTools.defaultFontPixelHeight * 0.5
467 border.color: qgcPal.alertBorder
468 border.width: 2
469
470 Rectangle {
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
477 border.width: 1
478 width: vehicleWarningLabel.contentWidth + _margins
479 height: vehicleWarningLabel.contentHeight + _margins
480
481 property real _margins: ScreenTools.defaultFontPixelHeight * 0.25
482
483 QGCLabel {
484 id: vehicleWarningLabel
485 anchors.centerIn: parent
486 text: qsTr("Vehicle Error")
487 font.pointSize: ScreenTools.smallFontPointSize
488 color: qgcPal.alertText
489 }
490 }
491
492 Rectangle {
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
500 border.width: 1
501 width: additionalErrorsLabel.contentWidth + _margins
502 height: additionalErrorsLabel.contentHeight + _margins
503 visible: criticalVehicleMessagePopup.additionalCriticalMessagesReceived
504
505 property real _margins: ScreenTools.defaultFontPixelHeight * 0.25
506
507 QGCLabel {
508 id: additionalErrorsLabel
509 anchors.centerIn: parent
510 text: qsTr("Additional errors received")
511 font.pointSize: ScreenTools.smallFontPointSize
512 color: qgcPal.alertText
513 }
514 }
515 }
516
517 QGCLabel {
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
524 }
525
526 MouseArea {
527 anchors.fill: parent
528 onClicked: {
529 criticalVehicleMessagePopup.close()
530 if (criticalVehicleMessagePopup.additionalCriticalMessagesReceived) {
531 criticalVehicleMessagePopup.additionalCriticalMessagesReceived = false;
532 flyView.dropMainStatusIndicatorTool();
533 } else {
534 QGroundControl.multiVehicleManager.activeVehicle.resetErrorLevelMessages();
535 }
536 }
537 }
538 }
539
540 //-------------------------------------------------------------------------
541 //-- Indicator Drawer
542
543 function showIndicatorDrawer(drawerComponent, indicatorItem) {
544 indicatorDrawer.sourceComponent = drawerComponent
545 indicatorDrawer.indicatorItem = indicatorItem
546 indicatorDrawer.open()
547 }
548
549 function closeIndicatorDrawer() {
550 indicatorDrawer.close()
551 }
552
553 Popup {
554 id: indicatorDrawer
555 x: calcXPosition()
556 y: ScreenTools.toolbarHeight + _margins
557 leftInset: 0
558 rightInset: 0
559 topInset: 0
560 bottomInset: 0
561 padding: _margins * 2
562 visible: false
563 modal: true
564 focus: true
565 closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutside
566
567 property var sourceComponent
568 property var indicatorItem
569
570 property bool _expanded: false
571 property real _margins: ScreenTools.defaultFontPixelHeight / 4
572
573 function calcXPosition() {
574 if (indicatorItem) {
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)))
577 } else {
578 return _margins
579 }
580 }
581
582 onOpened: {
583 _expanded = false;
584 indicatorDrawerLoader.sourceComponent = indicatorDrawer.sourceComponent
585 }
586 onClosed: {
587 _expanded = false
588 indicatorItem = undefined
589 indicatorDrawerLoader.sourceComponent = undefined
590 }
591
592 background: Item {
593 Rectangle {
594 id: backgroundRect
595 anchors.fill: parent
596 color: QGroundControl.globalPalette.window
597 radius: indicatorDrawer._margins
598 opacity: 0.85
599 }
600
601 Rectangle {
602 anchors.horizontalCenter: backgroundRect.right
603 anchors.verticalCenter: backgroundRect.top
604 width: ScreenTools.largeFontPixelHeight
605 height: width
606 radius: width / 2
607 color: QGroundControl.globalPalette.button
608 border.color: QGroundControl.globalPalette.buttonText
609 visible: indicatorDrawerLoader.item && indicatorDrawerLoader.item._showExpand && !indicatorDrawer._expanded
610
611 QGCLabel {
612 anchors.centerIn: parent
613 text: ">"
614 color: QGroundControl.globalPalette.buttonText
615 }
616
617 QGCMouseArea {
618 fillItem: parent
619 onClicked: indicatorDrawer._expanded = true
620 }
621 }
622 }
623
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
630
631 Loader {
632 id: indicatorDrawerLoader
633
634 Binding {
635 target: indicatorDrawerLoader.item
636 property: "expanded"
637 value: indicatorDrawer._expanded
638 }
639
640 Binding {
641 target: indicatorDrawerLoader.item
642 property: "drawer"
643 value: indicatorDrawer
644 }
645 }
646 }
647 }
648
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
651 // closes.
652
653 function createWindowedAnalyzePage(title, source, requiresVehicle, existingItem) {
654 var windowedPage = windowedAnalyzePage.createObject(mainWindow)
655 windowedPage.title = title
656 windowedPage.requiresVehicle = requiresVehicle
657 if (existingItem) {
658 windowedPage.adoptItem(existingItem)
659 } else {
660 windowedPage.source = source
661 }
662 windowedPage.visible = true
663 }
664
665 Component {
666 id: windowedAnalyzePage
667
668 Window {
669 width: ScreenTools.defaultFontPixelWidth * 100
670 height: ScreenTools.defaultFontPixelHeight * 40
671 visible: false
672
673 property alias source: loader.source
674 property bool requiresVehicle: false
675
676 function adoptItem(item) {
677 loader.visible = false
678 loader.source = ""
679 item.parent = contentRect
680 item.anchors.fill = contentRect
681 item.popped = true
682 item.visible = true
683 }
684
685 Connections {
686 target: QGroundControl.multiVehicleManager
687 function onActiveVehicleChanged() {
688 if (requiresVehicle) {
689 close()
690 }
691 }
692 }
693
694 Rectangle {
695 id: contentRect
696 color: QGroundControl.globalPalette.window
697 anchors.fill: parent
698
699 Loader {
700 id: loader
701 anchors.fill: parent
702 onLoaded: item.popped = true
703 }
704 }
705
706 onClosing: {
707 visible = false
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) {
712 child.destroy()
713 }
714 }
715 source = ""
716 Qt.callLater(destroy)
717 }
718 }
719 }
720}