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.Toolbar
13
14/// @brief Native QML top level window
15/// All properties defined here are visible to all QML pages.
16ApplicationWindow {
17 id: mainWindow
18 visible: true
19 // The special casing for android prevents white bars from showing up on the edges of the screen with newer android versions
20 flags: Qt.Window | (ScreenTools.isAndroid ? Qt.ExpandedClientAreaHint | Qt.NoTitleBarBackgroundHint : 0)
21
22 Component.onCompleted: {
23 // Start the sequence of first run prompt(s)
24 firstRunPromptManager.nextPrompt()
25 }
26
27 /// Saves main window position and size and re-opens it in the same position and size next time
28 MainWindowSavedState {
29 window: mainWindow
30 }
31
32 QtObject {
33 id: firstRunPromptManager
34
35 property var currentDialog: null
36 property var rgPromptIds: QGroundControl.corePlugin.firstRunPromptsToShow()
37 property int nextPromptIdIndex: 0
38
39 function clearNextPromptSignal() {
40 if (currentDialog) {
41 currentDialog.closed.disconnect(nextPrompt)
42 }
43 }
44
45 function nextPrompt() {
46 if (nextPromptIdIndex < rgPromptIds.length) {
47 var component = Qt.createComponent(QGroundControl.corePlugin.firstRunPromptResource(rgPromptIds[nextPromptIdIndex]));
48 currentDialog = component.createObject(mainWindow)
49 currentDialog.closed.connect(nextPrompt)
50 currentDialog.open()
51 nextPromptIdIndex++
52 } else {
53 currentDialog = null
54 showPreFlightChecklistIfNeeded()
55 }
56 }
57 }
58
59 readonly property real _topBottomMargins: ScreenTools.defaultFontPixelHeight * 0.5
60
61 //-------------------------------------------------------------------------
62 //-- Global Scope Variables
63
64 QtObject {
65 id: globals
66
67 readonly property var activeVehicle: QGroundControl.multiVehicleManager.activeVehicle
68 readonly property real defaultTextHeight: ScreenTools.defaultFontPixelHeight
69 readonly property real defaultTextWidth: ScreenTools.defaultFontPixelWidth
70 readonly property var planMasterControllerFlyView: flyView.planController
71 readonly property var guidedControllerFlyView: flyView.guidedController
72
73 // Number of QGCTextField's with validation errors. Used to prevent closing panels with validation errors.
74 property int validationErrorCount: 0
75
76 // Property to manage RemoteID quick access to settings page
77 property bool commingFromRIDIndicator: false
78 }
79
80 /// Default color palette used throughout the UI
81 QGCPalette { id: qgcPal; colorGroupEnabled: true }
82
83 //-------------------------------------------------------------------------
84 //-- Actions
85
86 signal armVehicleRequest
87 signal forceArmVehicleRequest
88 signal disarmVehicleRequest
89 signal vtolTransitionToFwdFlightRequest
90 signal vtolTransitionToMRFlightRequest
91 signal showPreFlightChecklistIfNeeded
92
93 //-------------------------------------------------------------------------
94 //-- Global Scope Functions
95
96 // This function is used to prevent view switching if there are validation errors
97 function allowViewSwitch(previousValidationErrorCount = 0) {
98 // Run validation on active focus control to ensure it is valid before switching views
99 if (mainWindow.activeFocusControl instanceof FactTextField) {
100 mainWindow.activeFocusControl._onEditingFinished()
101 }
102 return globals.validationErrorCount <= previousValidationErrorCount
103 }
104
105 function showPlanView() {
106 flyView.visible = false
107 planView.visible = true
108 toolDrawer.visible = false
109 }
110
111 function showFlyView() {
112 flyView.visible = true
113 planView.visible = false
114 toolDrawer.visible = false
115 }
116
117 function showTool(toolTitle, toolSource, toolIcon) {
118 toolDrawer.backIcon = flyView.visible ? "/qmlimages/PaperPlane.svg" : "/qmlimages/Plan.svg"
119 toolDrawer.toolTitle = toolTitle
120 toolDrawer.toolSource = toolSource
121 toolDrawer.toolIcon = toolIcon
122 toolDrawer.visible = true
123 }
124
125 function showAnalyzeTool() {
126 showTool(qsTr("Analyze Tools"), "qrc:/qml/QGroundControl/AnalyzeView/AnalyzeView.qml", "/qmlimages/Analyze.svg")
127 }
128
129 function showVehicleConfig() {
130 showTool(qsTr("Vehicle Configuration"), "qrc:/qml/QGroundControl/VehicleSetup/SetupView.qml", "/qmlimages/Gears.svg")
131 }
132
133 function showVehicleConfigParametersPage() {
134 showVehicleConfig()
135 toolDrawerLoader.item.showParametersPanel()
136 }
137
138 function showKnownVehicleComponentConfigPage(knownVehicleComponent) {
139 showVehicleConfig()
140 let vehicleComponent = globals.activeVehicle.autopilotPlugin.findKnownVehicleComponent(knownVehicleComponent)
141 if (vehicleComponent) {
142 toolDrawerLoader.item.showVehicleComponentPanel(vehicleComponent)
143 }
144 }
145
146 function showSettingsTool(settingsPage = "") {
147 showTool(qsTr("Application Settings"), "qrc:/qml/QGroundControl/Controls/AppSettings.qml", "/res/QGCLogoWhite")
148 if (settingsPage !== "") {
149 toolDrawerLoader.item.showSettingsPage(settingsPage)
150 }
151 }
152
153 //-------------------------------------------------------------------------
154 //-- Global simple message dialog
155
156 function _showMessageDialogWorker(owner, dialogTitle, dialogText, buttons = Dialog.Ok, acceptFunction = null, closeFunction = null) {
157 let dialog = simpleMessageDialogComponent.createObject(owner, { title: dialogTitle, text: dialogText, buttons: buttons, acceptFunction: acceptFunction, closeFunction: closeFunction })
158 dialog.open()
159 }
160
161 // This variant is only meant to be called by QGCApplication
162 function _showMessageDialog(dialogTitle, dialogText) {
163 _showMessageDialogWorker(mainWindow, dialogTitle, dialogText)
164 }
165
166 Connections {
167 target: QGroundControl
168
169 function onShowMessageDialogRequested(owner, title, text, buttons, acceptFunction, closeFunction) {
170 _showMessageDialogWorker(owner, title, text, buttons, acceptFunction, closeFunction)
171 }
172 }
173
174 Component {
175 id: simpleMessageDialogComponent
176
177 QGCSimpleMessageDialog {
178 }
179 }
180
181 property bool _forceClose: false
182
183 function finishCloseProcess() {
184 _forceClose = true
185 // For some reason on the Qml side Qt doesn't automatically disconnect a signal when an object is destroyed.
186 // So we have to do it ourselves otherwise the signal flows through on app shutdown to an object which no longer exists.
187 firstRunPromptManager.clearNextPromptSignal()
188 QGroundControl.linkManager.shutdown()
189 QGroundControl.videoManager.stopVideo();
190 mainWindow.close()
191 }
192
193 // Check for things which should prevent the app from closing
194 // Returns true if it is OK to close
195 readonly property int _skipUnsavedMissionCheckMask: 0x01
196 readonly property int _skipPendingParameterWritesCheckMask: 0x02
197 readonly property int _skipActiveConnectionsCheckMask: 0x04
198 property int _closeChecksToSkip: 0
199 function performCloseChecks() {
200 if (!(_closeChecksToSkip & _skipUnsavedMissionCheckMask) && !checkForUnsavedMission()) {
201 return false
202 }
203 if (!(_closeChecksToSkip & _skipPendingParameterWritesCheckMask) && !checkForPendingParameterWrites()) {
204 return false
205 }
206 if (!(_closeChecksToSkip & _skipActiveConnectionsCheckMask) && !checkForActiveConnections()) {
207 return false
208 }
209 finishCloseProcess()
210 return true
211 }
212
213 property string closeDialogTitle: qsTr("Close %1").arg(QGroundControl.appName)
214
215 function checkForUnsavedMission() {
216 if (planView._planMasterController.dirtyForSave || planView._planMasterController.dirtyForUpload) {
217 QGroundControl.showMessageDialog(mainWindow, closeDialogTitle,
218 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?"),
219 Dialog.Yes | Dialog.No,
220 function() { _closeChecksToSkip |= _skipUnsavedMissionCheckMask; performCloseChecks() })
221 return false
222 } else {
223 return true
224 }
225 }
226
227 function checkForPendingParameterWrites() {
228 for (var index=0; index<QGroundControl.multiVehicleManager.vehicles.count; index++) {
229 if (QGroundControl.multiVehicleManager.vehicles.get(index).parameterManager.pendingWrites) {
230 QGroundControl.showMessageDialog(mainWindow, closeDialogTitle,
231 qsTr("You have pending parameter updates to a vehicle. If you close you will lose changes. Are you sure you want to close?"),
232 Dialog.Yes | Dialog.No,
233 function() { _closeChecksToSkip |= _skipPendingParameterWritesCheckMask; performCloseChecks() })
234 return false
235 }
236 }
237 return true
238 }
239
240 function checkForActiveConnections() {
241 if (QGroundControl.multiVehicleManager.activeVehicle) {
242 QGroundControl.showMessageDialog(mainWindow, closeDialogTitle,
243 qsTr("There are still active connections to vehicles. Are you sure you want to exit?"),
244 Dialog.Yes | Dialog.No,
245 function() { _closeChecksToSkip |= _skipActiveConnectionsCheckMask; performCloseChecks() })
246 return false
247 } else {
248 return true
249 }
250 }
251
252 onClosing: (close) => {
253 if (!_forceClose) {
254 _closeChecksToSkip = 0
255 close.accepted = performCloseChecks()
256 }
257 }
258
259 background: Rectangle {
260 anchors.fill: parent
261 color: QGroundControl.globalPalette.window
262 }
263
264 FlyView {
265 id: flyView
266 anchors.fill: parent
267 }
268
269 PlanView {
270 id: planView
271 anchors.fill: parent
272 visible: false
273 }
274
275 footer: LogReplayStatusBar {
276 visible: QGroundControl.settingsManager.flyViewSettings.showLogReplayStatusBar.rawValue
277 }
278
279 MessageDialog {
280 id: showTouchAreasNotification
281 title: qsTr("Debug Touch Areas")
282 text: qsTr("Touch Area display toggled")
283 buttons: MessageDialog.Ok
284 }
285
286 MessageDialog {
287 id: advancedModeOnConfirmation
288 title: qsTr("Advanced Mode")
289 text: QGroundControl.corePlugin.showAdvancedUIMessage
290 buttons: MessageDialog.Yes | MessageDialog.No
291 onButtonClicked: function (button, role) {
292 if (button === MessageDialog.Yes) {
293 QGroundControl.corePlugin.showAdvancedUI = true
294 }
295 }
296 }
297
298 MessageDialog {
299 id: advancedModeOffConfirmation
300 title: qsTr("Advanced Mode")
301 text: qsTr("Turn off Advanced Mode?")
302 buttons: MessageDialog.Yes | MessageDialog.No
303 onButtonClicked: function (button, role) {
304 if (button === MessageDialog.Yes) {
305 QGroundControl.corePlugin.showAdvancedUI = false
306 }
307 }
308 }
309
310 function showToolSelectDialog() {
311 if (mainWindow.allowViewSwitch()) {
312 mainWindow.showIndicatorDrawer(toolSelectComponent, null)
313 }
314 }
315
316 Component {
317 id: toolSelectComponent
318
319 SelectViewDropdown {
320 }
321 }
322
323 Rectangle {
324 id: toolDrawer
325 anchors.fill: parent
326 visible: false
327 color: qgcPal.window
328
329 property var backIcon
330 property string toolTitle
331 property alias toolSource: toolDrawerLoader.source
332 property var toolIcon
333
334 onVisibleChanged: {
335 if (!toolDrawer.visible) {
336 toolDrawerLoader.source = ""
337 }
338 }
339
340 // This need to block click event leakage to underlying map.
341 DeadMouseArea {
342 anchors.fill: parent
343 }
344
345 Rectangle {
346 id: toolDrawerToolbar
347 anchors.left: parent.left
348 anchors.right: parent.right
349 anchors.top: parent.top
350 height: ScreenTools.toolbarHeight
351 color: qgcPal.toolbarBackground
352
353 RowLayout {
354 id: toolDrawerToolbarLayout
355 anchors.leftMargin: ScreenTools.defaultFontPixelWidth
356 anchors.left: parent.left
357 anchors.top: parent.top
358 anchors.bottom: parent.bottom
359 spacing: ScreenTools.defaultFontPixelWidth
360
361 QGCToolBarButton {
362 id: qgcButton
363 height: parent.height
364 icon.source: "/res/QGCLogoFull.svg"
365 logo: true
366 onClicked: mainWindow.showToolSelectDialog()
367 }
368
369 QGCLabel {
370 id: toolbarDrawerText
371 text: toolDrawer.toolTitle
372 font.pointSize: ScreenTools.largeFontPointSize
373 }
374 }
375 }
376
377 Loader {
378 id: toolDrawerLoader
379 anchors.left: parent.left
380 anchors.right: parent.right
381 anchors.top: toolDrawerToolbar.bottom
382 anchors.bottom: parent.bottom
383
384 Connections {
385 target: toolDrawerLoader.item
386 ignoreUnknownSignals: true
387 function onPopout() { toolDrawer.visible = false }
388 }
389 }
390 }
391
392 //-------------------------------------------------------------------------
393 //-- Critical Vehicle Message Popup
394
395 function showCriticalVehicleMessage(message) {
396 closeIndicatorDrawer()
397 if (criticalVehicleMessagePopup.visible || QGroundControl.videoManager.fullScreen) {
398 // We received additional warning message while an older warning message was still displayed.
399 // When the user close the older one drop the message indicator tool so they can see the rest of them.
400 criticalVehicleMessagePopup.additionalCriticalMessagesReceived = true
401 } else {
402 criticalVehicleMessagePopup.criticalVehicleMessage = message
403 criticalVehicleMessagePopup.additionalCriticalMessagesReceived = false
404 criticalVehicleMessagePopup.open()
405 }
406 }
407
408 Popup {
409 id: criticalVehicleMessagePopup
410 y: ScreenTools.toolbarHeight + ScreenTools.defaultFontPixelHeight
411 x: Math.round((mainWindow.width - width) * 0.5)
412 width: mainWindow.width * 0.55
413 height: criticalVehicleMessageText.contentHeight + ScreenTools.defaultFontPixelHeight * 2
414 modal: false
415 focus: true
416
417 property alias criticalVehicleMessage: criticalVehicleMessageText.text
418 property bool additionalCriticalMessagesReceived: false
419
420 background: Rectangle {
421 anchors.fill: parent
422 color: qgcPal.alertBackground
423 radius: ScreenTools.defaultFontPixelHeight * 0.5
424 border.color: qgcPal.alertBorder
425 border.width: 2
426
427 Rectangle {
428 anchors.horizontalCenter: parent.horizontalCenter
429 anchors.top: parent.top
430 anchors.topMargin: -(height / 2)
431 color: qgcPal.alertBackground
432 radius: ScreenTools.defaultFontPixelHeight * 0.25
433 border.color: qgcPal.alertBorder
434 border.width: 1
435 width: vehicleWarningLabel.contentWidth + _margins
436 height: vehicleWarningLabel.contentHeight + _margins
437
438 property real _margins: ScreenTools.defaultFontPixelHeight * 0.25
439
440 QGCLabel {
441 id: vehicleWarningLabel
442 anchors.centerIn: parent
443 text: qsTr("Vehicle Error")
444 font.pointSize: ScreenTools.smallFontPointSize
445 color: qgcPal.alertText
446 }
447 }
448
449 Rectangle {
450 id: additionalErrorsIndicator
451 anchors.horizontalCenter: parent.horizontalCenter
452 anchors.bottom: parent.bottom
453 anchors.bottomMargin: -(height / 2)
454 color: qgcPal.alertBackground
455 radius: ScreenTools.defaultFontPixelHeight * 0.25
456 border.color: qgcPal.alertBorder
457 border.width: 1
458 width: additionalErrorsLabel.contentWidth + _margins
459 height: additionalErrorsLabel.contentHeight + _margins
460 visible: criticalVehicleMessagePopup.additionalCriticalMessagesReceived
461
462 property real _margins: ScreenTools.defaultFontPixelHeight * 0.25
463
464 QGCLabel {
465 id: additionalErrorsLabel
466 anchors.centerIn: parent
467 text: qsTr("Additional errors received")
468 font.pointSize: ScreenTools.smallFontPointSize
469 color: qgcPal.alertText
470 }
471 }
472 }
473
474 QGCLabel {
475 id: criticalVehicleMessageText
476 width: criticalVehicleMessagePopup.width - ScreenTools.defaultFontPixelHeight
477 anchors.centerIn: parent
478 wrapMode: Text.WordWrap
479 color: qgcPal.alertText
480 textFormat: TextEdit.RichText
481 }
482
483 MouseArea {
484 anchors.fill: parent
485 onClicked: {
486 criticalVehicleMessagePopup.close()
487 if (criticalVehicleMessagePopup.additionalCriticalMessagesReceived) {
488 criticalVehicleMessagePopup.additionalCriticalMessagesReceived = false;
489 flyView.dropMainStatusIndicatorTool();
490 } else {
491 QGroundControl.multiVehicleManager.activeVehicle.resetErrorLevelMessages();
492 }
493 }
494 }
495 }
496
497 //-------------------------------------------------------------------------
498 //-- Indicator Drawer
499
500 function showIndicatorDrawer(drawerComponent, indicatorItem) {
501 indicatorDrawer.sourceComponent = drawerComponent
502 indicatorDrawer.indicatorItem = indicatorItem
503 indicatorDrawer.open()
504 }
505
506 function closeIndicatorDrawer() {
507 indicatorDrawer.close()
508 }
509
510 Popup {
511 id: indicatorDrawer
512 x: calcXPosition()
513 y: ScreenTools.toolbarHeight + _margins
514 leftInset: 0
515 rightInset: 0
516 topInset: 0
517 bottomInset: 0
518 padding: _margins * 2
519 visible: false
520 modal: true
521 focus: true
522 closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutside
523
524 property var sourceComponent
525 property var indicatorItem
526
527 property bool _expanded: false
528 property real _margins: ScreenTools.defaultFontPixelHeight / 4
529
530 function calcXPosition() {
531 if (indicatorItem) {
532 var xCenter = indicatorItem.mapToItem(mainWindow.contentItem, indicatorItem.width / 2, 0).x
533 return Math.max(_margins, Math.min(xCenter - (contentItem.implicitWidth / 2), mainWindow.contentItem.width - contentItem.implicitWidth - _margins - (indicatorDrawer.padding * 2) - (ScreenTools.defaultFontPixelHeight / 2)))
534 } else {
535 return _margins
536 }
537 }
538
539 onOpened: {
540 _expanded = false;
541 indicatorDrawerLoader.sourceComponent = indicatorDrawer.sourceComponent
542 }
543 onClosed: {
544 _expanded = false
545 indicatorItem = undefined
546 indicatorDrawerLoader.sourceComponent = undefined
547 }
548
549 background: Item {
550 Rectangle {
551 id: backgroundRect
552 anchors.fill: parent
553 color: QGroundControl.globalPalette.window
554 radius: indicatorDrawer._margins
555 opacity: 0.85
556 }
557
558 Rectangle {
559 anchors.horizontalCenter: backgroundRect.right
560 anchors.verticalCenter: backgroundRect.top
561 width: ScreenTools.largeFontPixelHeight
562 height: width
563 radius: width / 2
564 color: QGroundControl.globalPalette.button
565 border.color: QGroundControl.globalPalette.buttonText
566 visible: indicatorDrawerLoader.item && indicatorDrawerLoader.item._showExpand && !indicatorDrawer._expanded
567
568 QGCLabel {
569 anchors.centerIn: parent
570 text: ">"
571 color: QGroundControl.globalPalette.buttonText
572 }
573
574 QGCMouseArea {
575 fillItem: parent
576 onClicked: indicatorDrawer._expanded = true
577 }
578 }
579 }
580
581 contentItem: QGCFlickable {
582 id: indicatorDrawerLoaderFlickable
583 implicitWidth: Math.min(mainWindow.contentItem.width - (2 * indicatorDrawer._margins) - (indicatorDrawer.padding * 2), indicatorDrawerLoader.width)
584 implicitHeight: Math.min(mainWindow.contentItem.height - ScreenTools.toolbarHeight - (2 * indicatorDrawer._margins) - (indicatorDrawer.padding * 2), indicatorDrawerLoader.height)
585 contentWidth: indicatorDrawerLoader.width
586 contentHeight: indicatorDrawerLoader.height
587
588 Loader {
589 id: indicatorDrawerLoader
590
591 Binding {
592 target: indicatorDrawerLoader.item
593 property: "expanded"
594 value: indicatorDrawer._expanded
595 }
596
597 Binding {
598 target: indicatorDrawerLoader.item
599 property: "drawer"
600 value: indicatorDrawer
601 }
602 }
603 }
604 }
605
606 // We have to create the popup windows for the Analyze pages here so that the creation context is rooted
607 // to mainWindow. Otherwise if they are rooted to the AnalyzeView itself they will die when the analyze viewSwitch
608 // closes.
609
610 function createrWindowedAnalyzePage(title, source) {
611 var windowedPage = windowedAnalyzePage.createObject(mainWindow)
612 windowedPage.title = title
613 windowedPage.source = source
614 }
615
616 Component {
617 id: windowedAnalyzePage
618
619 Window {
620 width: ScreenTools.defaultFontPixelWidth * 100
621 height: ScreenTools.defaultFontPixelHeight * 40
622 visible: true
623
624 property alias source: loader.source
625
626 Rectangle {
627 color: QGroundControl.globalPalette.window
628 anchors.fill: parent
629
630 Loader {
631 id: loader
632 anchors.fill: parent
633 onLoaded: item.popped = true
634 }
635 }
636
637 onClosing: {
638 visible = false
639 source = ""
640 }
641 }
642 }
643}