/**************************************************************************** * * (c) 2009-2020 QGROUNDCONTROL PROJECT * * QGroundControl is licensed according to the terms in the file * COPYING.md in the root of the source code directory. * ****************************************************************************/ import QtQuick 2.11 import QtQuick.Controls 2.4 import QtQuick.Dialogs 1.3 import QtQuick.Layouts 1.11 import QtQuick.Window 2.11 import QGroundControl 1.0 import QGroundControl.Palette 1.0 import QGroundControl.Controls 1.0 import QGroundControl.ScreenTools 1.0 import QGroundControl.FlightDisplay 1.0 import QGroundControl.FlightMap 1.0 /// @brief Native QML top level window /// All properties defined here are visible to all QML pages. ApplicationWindow { id: mainWindow minimumWidth: ScreenTools.isMobile ? Screen.width : Math.min(ScreenTools.defaultFontPixelWidth * 100, Screen.width) minimumHeight: ScreenTools.isMobile ? Screen.height : Math.min(ScreenTools.defaultFontPixelWidth * 50, Screen.height) visible: true Component.onCompleted: { //-- Full screen on mobile or tiny screens if (ScreenTools.isMobile || Screen.height / ScreenTools.realPixelDensity < 120) { mainWindow.showFullScreen() } else { width = ScreenTools.isMobile ? Screen.width : Math.min(250 * Screen.pixelDensity, Screen.width) height = ScreenTools.isMobile ? Screen.height : Math.min(150 * Screen.pixelDensity, Screen.height) } // Start the sequence of first run prompt(s) firstRunPromptManager.nextPrompt() } QtObject { id: firstRunPromptManager property var currentDialog: null property var rgPromptIds: QGroundControl.corePlugin.firstRunPromptsToShow() property int nextPromptIdIndex: 0 onRgPromptIdsChanged: console.log(QGroundControl.corePlugin, QGroundControl.corePlugin.firstRunPromptsToShow()) function clearNextPromptSignal() { if (currentDialog) { currentDialog.closed.disconnect(nextPrompt) } } function nextPrompt() { if (nextPromptIdIndex < rgPromptIds.length) { currentDialog = showPopupDialogFromSource(QGroundControl.corePlugin.firstRunPromptResource(rgPromptIds[nextPromptIdIndex])) currentDialog.closed.connect(nextPrompt) nextPromptIdIndex++ } else { currentDialog = null showPreFlightChecklistIfNeeded() } } } property var _rgPreventViewSwitch: [ false ] readonly property real _topBottomMargins: ScreenTools.defaultFontPixelHeight * 0.5 //------------------------------------------------------------------------- //-- Global Scope Variables QtObject { id: globals readonly property var activeVehicle: QGroundControl.multiVehicleManager.activeVehicle readonly property real defaultTextHeight: ScreenTools.defaultFontPixelHeight readonly property real defaultTextWidth: ScreenTools.defaultFontPixelWidth readonly property var planMasterControllerFlyView: flightView.planController readonly property var guidedControllerFlyView: flightView.guidedController property var planMasterControllerPlanView: null property var currentPlanMissionItem: planMasterControllerPlanView ? planMasterControllerPlanView.missionController.currentPlanViewItem : null } /// Default color palette used throughout the UI QGCPalette { id: qgcPal; colorGroupEnabled: true } //------------------------------------------------------------------------- //-- Actions signal armVehicleRequest signal forceArmVehicleRequest signal disarmVehicleRequest signal vtolTransitionToFwdFlightRequest signal vtolTransitionToMRFlightRequest signal showPreFlightChecklistIfNeeded //------------------------------------------------------------------------- //-- Global Scope Functions /// Prevent view switching function pushPreventViewSwitch() { _rgPreventViewSwitch.push(true) } /// Allow view switching function popPreventViewSwitch() { if (_rgPreventViewSwitch.length == 1) { console.warn("mainWindow.popPreventViewSwitch called when nothing pushed") return } _rgPreventViewSwitch.pop() } /// @return true: View switches are not currently allowed function preventViewSwitch() { return _rgPreventViewSwitch[_rgPreventViewSwitch.length - 1] } function viewSwitch(currentToolbar) { toolDrawer.visible = false toolDrawer.toolSource = "" flightView.visible = false planView.visible = false toolbar.currentToolbar = currentToolbar } function showFlyView() { if (!flightView.visible) { mainWindow.showPreFlightChecklistIfNeeded() } viewSwitch(toolbar.flyViewToolbar) flightView.visible = true } function showPlanView() { viewSwitch(toolbar.planViewToolbar) planView.visible = true } function showTool(toolTitle, toolSource, toolIcon) { toolDrawer.backIcon = flightView.visible ? "/qmlimages/PaperPlane.svg" : "/qmlimages/Plan.svg" toolDrawer.toolTitle = toolTitle toolDrawer.toolSource = toolSource toolDrawer.toolIcon = toolIcon toolDrawer.visible = true } function showAnalyzeTool() { showTool(qsTr("Analyze Tools"), "AnalyzeView.qml", "/qmlimages/Analyze.svg") } function showSetupTool() { showTool(qsTr("Vehicle Setup"), "SetupView.qml", "/qmlimages/Gears.svg") } function showSettingsTool() { showTool(qsTr("Application Settings"), "AppSettings.qml", "/res/QGCLogoWhite") } //------------------------------------------------------------------------- //-- Global simple message dialog function showMessageDialog(title, text) { var dialog = simpleMessageDialog.createObject(mainWindow, { title: title, text: text }) dialog.open() } Component { id: simpleMessageDialog MessageDialog { standardButtons: StandardButton.Ok modality: Qt.ApplicationModal visible: false } } /// Saves main window position and size MainWindowSavedState { window: mainWindow } //------------------------------------------------------------------------- //-- Global complex dialog /// Shows a QGCViewDialogContainer based dialog /// @param component The dialog contents /// @param title Title for dialog /// @param charWidth Width of dialog in characters /// @param buttons Buttons to show in dialog using StandardButton enum readonly property int showDialogFullWidth: -1 ///< Use for full width dialog readonly property int showDialogDefaultWidth: 40 ///< Use for default dialog width function showComponentDialog(component, title, charWidth, buttons) { var dialogWidth = charWidth === showDialogFullWidth ? mainWindow.width : ScreenTools.defaultFontPixelWidth * charWidth var dialog = dialogDrawerComponent.createObject(mainWindow, { width: dialogWidth, dialogComponent: component, dialogTitle: title, dialogButtons: buttons }) mainWindow.pushPreventViewSwitch() dialog.open() } Component { id: dialogDrawerComponent QGCViewDialogContainer { y: mainWindow.header.height height: mainWindow.height - mainWindow.header.height onClosed: mainWindow.popPreventViewSwitch() } } // Dialogs based on QGCPopupDialog function showPopupDialogFromComponent(component, properties) { var dialog = popupDialogContainerComponent.createObject(mainWindow, { dialogComponent: component, dialogProperties: properties }) dialog.open() return dialog } function showPopupDialogFromSource(source, properties) { var dialog = popupDialogContainerComponent.createObject(mainWindow, { dialogSource: source, dialogProperties: properties }) dialog.open() return dialog } Component { id: popupDialogContainerComponent QGCPopupDialogContainer { } } property bool _forceClose: false function finishCloseProcess() { _forceClose = true // For some reason on the Qml side Qt doesn't automatically disconnect a signal when an object is destroyed. // So we have to do it ourselves otherwise the signal flows through on app shutdown to an object which no longer exists. firstRunPromptManager.clearNextPromptSignal() QGroundControl.linkManager.shutdown() QGroundControl.videoManager.stopVideo(); mainWindow.close() } // On attempting an application close we check for: // Unsaved missions - then // Pending parameter writes - then // Active connections onClosing: { if (!_forceClose) { unsavedMissionCloseDialog.check() close.accepted = false } } MessageDialog { id: unsavedMissionCloseDialog title: qsTr("%1 close").arg(QGroundControl.appName) text: qsTr("You have a mission edit in progress which has not been saved/sent. If you close you will lose changes. Are you sure you want to close?") standardButtons: StandardButton.Yes | StandardButton.No modality: Qt.ApplicationModal visible: false onYes: pendingParameterWritesCloseDialog.check() function check() { if (globals.planMasterControllerPlanView && globals.planMasterControllerPlanView.dirty) { unsavedMissionCloseDialog.open() } else { pendingParameterWritesCloseDialog.check() } } } MessageDialog { id: pendingParameterWritesCloseDialog title: qsTr("%1 close").arg(QGroundControl.appName) text: qsTr("You have pending parameter updates to a vehicle. If you close you will lose changes. Are you sure you want to close?") standardButtons: StandardButton.Yes | StandardButton.No modality: Qt.ApplicationModal visible: false onYes: activeConnectionsCloseDialog.check() function check() { for (var index=0; index 5 color: qgcPal.alertText MouseArea { anchors.fill: parent onClicked: { criticalVehicleMessageFlick.flick(0,-500) } } } } //------------------------------------------------------------------------- //-- Indicator Popups function showIndicatorPopup(item, dropItem) { indicatorPopup.currentIndicator = dropItem indicatorPopup.currentItem = item indicatorPopup.open() } function hideIndicatorPopup() { indicatorPopup.close() indicatorPopup.currentItem = null indicatorPopup.currentIndicator = null } Popup { id: indicatorPopup padding: ScreenTools.defaultFontPixelWidth * 0.75 modal: true focus: true closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutside property var currentItem: null property var currentIndicator: null background: Rectangle { width: loader.width height: loader.height color: Qt.rgba(0,0,0,0) } Loader { id: loader onLoaded: { var centerX = mainWindow.contentItem.mapFromItem(indicatorPopup.currentItem, 0, 0).x - (loader.width * 0.5) if((centerX + indicatorPopup.width) > (mainWindow.width - ScreenTools.defaultFontPixelWidth)) { centerX = mainWindow.width - indicatorPopup.width - ScreenTools.defaultFontPixelWidth } indicatorPopup.x = centerX } } onOpened: { loader.sourceComponent = indicatorPopup.currentIndicator } onClosed: { loader.sourceComponent = null indicatorPopup.currentIndicator = null } } }