Commit 70b593b3 authored by Don Gagne's avatar Don Gagne

Merge pull request #2583 from DonLakeFlyer/MissionClose

Prevent loss of mission due to load or close app
parents 75c306a2 a1a2b9c3
...@@ -40,6 +40,8 @@ import QGroundControl.Controllers 1.0 ...@@ -40,6 +40,8 @@ import QGroundControl.Controllers 1.0
QGCView { QGCView {
id: _root id: _root
property bool syncNeeded: controller.missionItems.dirty // Unsaved changes, visible to parent container
viewPanel: panel viewPanel: panel
topDialogMargin: height - mainWindow.availableHeight topDialogMargin: height - mainWindow.availableHeight
...@@ -73,7 +75,6 @@ QGCView { ...@@ -73,7 +75,6 @@ QGCView {
property var liveHomePositionAvailable: controller.liveHomePositionAvailable property var liveHomePositionAvailable: controller.liveHomePositionAvailable
property var homePosition: _defaultVehicleCoordinate property var homePosition: _defaultVehicleCoordinate
property bool _syncNeeded: controller.missionItems.dirty
property bool _syncInProgress: _activeVehicle ? _activeVehicle.missionManager.inProgress : false property bool _syncInProgress: _activeVehicle ? _activeVehicle.missionManager.inProgress : false
property bool _showHelp: QGroundControl.flightMapSettings.loadBoolMapSetting(editorMap.mapName, _showHelpKey, true) property bool _showHelp: QGroundControl.flightMapSettings.loadBoolMapSetting(editorMap.mapName, _showHelpKey, true)
...@@ -458,7 +459,7 @@ QGCView { ...@@ -458,7 +459,7 @@ QGCView {
DropButton { DropButton {
id: syncButton id: syncButton
dropDirection: dropRight dropDirection: dropRight
buttonImage: _syncNeeded ? "/qmlimages/MapSyncChanged.svg" : "/qmlimages/MapSync.svg" buttonImage: syncNeeded ? "/qmlimages/MapSyncChanged.svg" : "/qmlimages/MapSync.svg"
viewportMargins: ScreenTools.defaultFontPixelWidth / 2 viewportMargins: ScreenTools.defaultFontPixelWidth / 2
exclusiveGroup: _dropButtonsExclusiveGroup exclusiveGroup: _dropButtonsExclusiveGroup
z: QGroundControl.zOrderWidgets z: QGroundControl.zOrderWidgets
...@@ -594,6 +595,34 @@ QGCView { ...@@ -594,6 +595,34 @@ QGCView {
} // Item - split view container } // Item - split view container
} // QGCViewPanel } // QGCViewPanel
Component {
id: syncLoadFromVehicleOverwrite
QGCViewMessage {
id: syncLoadFromVehicleCheck
message: "Load vehicle check"
function accept() {
hideDialog()
controller.getMissionItems()
}
}
}
Component {
id: syncLoadFromFileOverwrite
QGCViewMessage {
id: syncLoadFromVehicleCheck
message: "Load file check"
function accept() {
hideDialog()
controller.loadMissionFromFile()
}
}
}
Component { Component {
id: syncDropDownComponent id: syncDropDownComponent
...@@ -604,7 +633,7 @@ QGCView { ...@@ -604,7 +633,7 @@ QGCView {
QGCLabel { QGCLabel {
width: columnHolder.width width: columnHolder.width
wrapMode: Text.WordWrap wrapMode: Text.WordWrap
text: _syncNeeded && !controller.autoSync ? text: syncNeeded && !controller.autoSync ?
"You have unsaved changed to you mission. You should send to your vehicle, or save to a file:" : "You have unsaved changed to you mission. You should send to your vehicle, or save to a file:" :
"Sync:" "Sync:"
} }
...@@ -629,7 +658,11 @@ QGCView { ...@@ -629,7 +658,11 @@ QGCView {
onClicked: { onClicked: {
syncButton.hideDropDown() syncButton.hideDropDown()
controller.getMissionItems() if (syncNeeded) {
_root.showDialog(syncLoadFromVehicleOverwrite, "Mission overwrite", _root.showDialogDefaultWidth, StandardButton.Yes | StandardButton.Cancel)
} else {
controller.getMissionItems()
}
} }
} }
} }
...@@ -652,7 +685,11 @@ QGCView { ...@@ -652,7 +685,11 @@ QGCView {
onClicked: { onClicked: {
syncButton.hideDropDown() syncButton.hideDropDown()
controller.loadMissionFromFile() if (syncNeeded) {
_root.showDialog(syncLoadFromFileOverwrite, "Mission overwrite", _root.showDialogDefaultWidth, StandardButton.Yes | StandardButton.Cancel)
} else {
controller.loadMissionFromFile()
}
} }
} }
} }
......
...@@ -692,9 +692,9 @@ void QGCApplication::showSetupView(void) ...@@ -692,9 +692,9 @@ void QGCApplication::showSetupView(void)
QMetaObject::invokeMethod(_rootQmlObject(), "showSetupView"); QMetaObject::invokeMethod(_rootQmlObject(), "showSetupView");
} }
void QGCApplication::showWindowCloseMessage(void) void QGCApplication::qmlAttemptWindowClose(void)
{ {
QMetaObject::invokeMethod(_rootQmlObject(), "showWindowCloseMessage"); QMetaObject::invokeMethod(_rootQmlObject(), "attemptWindowClose");
} }
......
...@@ -135,7 +135,7 @@ public slots: ...@@ -135,7 +135,7 @@ public slots:
void showPlanView(void); void showPlanView(void);
void showSetupView(void); void showSetupView(void);
void showWindowCloseMessage(void); void qmlAttemptWindowClose(void);
#ifndef __mobile__ #ifndef __mobile__
/// Save the specified Flight Data Log /// Save the specified Flight Data Log
......
...@@ -135,6 +135,7 @@ MainWindow::MainWindow() ...@@ -135,6 +135,7 @@ MainWindow::MainWindow()
: _lowPowerMode(false) : _lowPowerMode(false)
, _showStatusBar(false) , _showStatusBar(false)
, _mainQmlWidgetHolder(NULL) , _mainQmlWidgetHolder(NULL)
, _forceClose(false)
{ {
Q_ASSERT(_instance == NULL); Q_ASSERT(_instance == NULL);
_instance = this; _instance = this;
...@@ -161,6 +162,7 @@ MainWindow::MainWindow() ...@@ -161,6 +162,7 @@ MainWindow::MainWindow()
_centralLayout->addWidget(_mainQmlWidgetHolder); _centralLayout->addWidget(_mainQmlWidgetHolder);
_mainQmlWidgetHolder->setVisible(true); _mainQmlWidgetHolder->setVisible(true);
QQmlEngine::setObjectOwnership(this, QQmlEngine::CppOwnership);
_mainQmlWidgetHolder->setContextPropertyObject("controller", this); _mainQmlWidgetHolder->setContextPropertyObject("controller", this);
_mainQmlWidgetHolder->setSource(QUrl::fromUserInput("qrc:qml/MainWindowHybrid.qml")); _mainQmlWidgetHolder->setSource(QUrl::fromUserInput("qrc:qml/MainWindowHybrid.qml"));
...@@ -407,56 +409,29 @@ void MainWindow::showStatusBarCallback(bool checked) ...@@ -407,56 +409,29 @@ void MainWindow::showStatusBarCallback(bool checked)
checked ? statusBar()->show() : statusBar()->hide(); checked ? statusBar()->show() : statusBar()->hide();
} }
void MainWindow::acceptWindowClose(void) void MainWindow::reallyClose(void)
{ {
qgcApp()->toolbox()->linkManager()->shutdown(); _forceClose = true;
// The above shutdown causes a flurry of activity as the vehicle components are removed. This in turn close();
// causes the Windows Version of Qt to crash if you allow the close event to be accepted. In order to prevent
// the crash, we ignore the close event and setup a delayed timer to close the window after things settle down.
QTimer::singleShot(1500, this, &MainWindow::_closeWindow);
} }
void MainWindow::closeEvent(QCloseEvent *event) void MainWindow::closeEvent(QCloseEvent *event)
{ {
// Disallow window close if there are active connections if (!_forceClose) {
if (qgcApp()->toolbox()->multiVehicleManager()->vehicles()->count()) { // Attemp close from within the root Qml item
qgcApp()->showWindowCloseMessage(); qgcApp()->qmlAttemptWindowClose();
event->ignore(); event->ignore();
return; return;
} }
// We still need to shutdown LinkManager even though no active connections so that we don't get any
// more auto-connect links during shutdown.
qgcApp()->toolbox()->linkManager()->shutdown();
// This will process any remaining flight log save dialogs
qgcApp()->processEvents(QEventLoop::ExcludeUserInputEvents);
// Should not be any active connections // Should not be any active connections
if (qgcApp()->toolbox()->linkManager()->anyActiveLinks()) { if (qgcApp()->toolbox()->linkManager()->anyActiveLinks()) {
qWarning() << "All links should be disconnected by now"; qWarning() << "All links should be disconnected by now";
} }
// We have to pull out the QmlWidget from the main window and delete it here, before
// the MainWindow ends up getting deleted. Otherwise the Qml has a reference to MainWindow
// inside it which in turn causes a shutdown crash.
//-- Unit test gets here with _mainQmlWidgetHolder being NULL
if(_mainQmlWidgetHolder)
{
// Remove image provider
_mainQmlWidgetHolder->getEngine()->removeImageProvider(QLatin1String("QGCImages"));
_centralLayout->removeWidget(_mainQmlWidgetHolder);
delete _mainQmlWidgetHolder;
_mainQmlWidgetHolder = NULL;
}
_storeCurrentViewState(); _storeCurrentViewState();
storeSettings(); storeSettings();
event->accept();
//-- TODO: This effectively causes the QGCApplication destructor to not being able //-- TODO: This effectively causes the QGCApplication destructor to not being able
// to access the pointer it is trying to delete. // to access the pointer it is trying to delete.
_instance = NULL; _instance = NULL;
......
...@@ -96,7 +96,7 @@ public: ...@@ -96,7 +96,7 @@ public:
void saveLastUsedConnection(const QString connection); void saveLastUsedConnection(const QString connection);
// Called from MainWindow.qml when the user accepts the window close dialog // Called from MainWindow.qml when the user accepts the window close dialog
Q_INVOKABLE void acceptWindowClose(void); Q_INVOKABLE void reallyClose(void);
/// @return Root qml object of main window QML /// @return Root qml object of main window QML
QObject* rootQmlObject(void); QObject* rootQmlObject(void);
...@@ -230,6 +230,8 @@ private: ...@@ -230,6 +230,8 @@ private:
QGCQmlWidgetHolder* _mainQmlWidgetHolder; QGCQmlWidgetHolder* _mainQmlWidgetHolder;
bool _forceClose;
QString _getWindowGeometryKey(); QString _getWindowGeometryKey();
}; };
......
...@@ -25,7 +25,8 @@ import QtQuick 2.5 ...@@ -25,7 +25,8 @@ import QtQuick 2.5
import QtQuick.Controls 1.2 import QtQuick.Controls 1.2
import QtQuick.Dialogs 1.2 import QtQuick.Dialogs 1.2
import QGroundControl.Controls 1.0 import QGroundControl 1.0
import QGroundControl.Controls 1.0
/// Native QML top level window /// Native QML top level window
Item { Item {
...@@ -41,8 +42,8 @@ Item { ...@@ -41,8 +42,8 @@ Item {
mainWindowInner.item.showSetupView() mainWindowInner.item.showSetupView()
} }
function showWindowCloseMessage() { function attemptWindowClose() {
windowCloseDialog.open() mainWindowInner.item.attemptWindowClose()
} }
// The following are use for unit testing only // The following are use for unit testing only
...@@ -71,17 +72,11 @@ Item { ...@@ -71,17 +72,11 @@ Item {
id: mainWindowInner id: mainWindowInner
anchors.fill: parent anchors.fill: parent
source: "MainWindowInner.qml" source: "MainWindowInner.qml"
}
MessageDialog { Connections {
id: windowCloseDialog target: mainWindowInner.item
title: "QGroundControl close"
text: "There are still active connections to vehicles. Do you want to disconnect these before closing?"
standardButtons: StandardButton.Yes | StandardButton.Cancel
modality: Qt.ApplicationModal
visible: false
onYes: controller.acceptWindowClose() onReallyClose: controller.reallyClose()
}
} }
} }
...@@ -37,6 +37,8 @@ import QGroundControl.MultiVehicleManager 1.0 ...@@ -37,6 +37,8 @@ import QGroundControl.MultiVehicleManager 1.0
Item { Item {
id: mainWindow id: mainWindow
signal reallyClose
readonly property string _planViewSource: "MissionEditor.qml" readonly property string _planViewSource: "MissionEditor.qml"
readonly property string _setupViewSource: "SetupView.qml" readonly property string _setupViewSource: "SetupView.qml"
...@@ -111,6 +113,68 @@ Item { ...@@ -111,6 +113,68 @@ Item {
setupViewLoader.item.showVehicleComponentPanel(vehicleComponent) setupViewLoader.item.showVehicleComponentPanel(vehicleComponent)
} }
/// Start the process of closing QGroundControl. Prompts the user are needed.
function attemptWindowClose() {
unsavedMissionCloseDialog.check()
}
function finishCloseProcess() {
QGroundControl.linkManager.shutdown()
// The above shutdown causes a flurry of activity as the vehicle components are removed. This in turn
// causes the Windows Version of Qt to crash if you allow the close event to be accepted. In order to prevent
// the crash, we ignore the close event and setup a delayed timer to close the window after things settle down.
delayedWindowCloseTimer.start()
}
MessageDialog {
id: unsavedMissionCloseDialog
title: "QGroundControl close"
text: "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: activeConnectionsCloseDialog.check()
function check() {
if (planViewLoader.item && planViewLoader.item.syncNeeded) {
unsavedMissionCloseDialog.open()
} else {
activeConnectionsCloseDialog.check()
}
}
}
MessageDialog {
id: activeConnectionsCloseDialog
title: "QGroundControl close"
text: "There are still active connections to vehicles. Do you want to disconnect these before closing?"
standardButtons: StandardButton.Yes | StandardButton.Cancel
modality: Qt.ApplicationModal
visible: false
onYes: finishCloseProcess()
function check() {
if (QGroundControl.multiVehicleManager.activeVehicle) {
activeConnectionsCloseDialog.open()
} else {
finishCloseProcess()
}
}
}
Timer {
id: delayedWindowCloseTimer
interval: 1500
running: false
repeat: false
onTriggered: {
mainWindow.reallyClose()
}
}
//-- Detect tablet position //-- Detect tablet position
PositionSource { PositionSource {
id: positionSource id: positionSource
......
...@@ -32,17 +32,13 @@ Window { ...@@ -32,17 +32,13 @@ Window {
id: _rootWindow id: _rootWindow
visible: true visible: true
property bool _forceClose: false
onClosing: { onClosing: {
// Disallow window close if there are active connections if (!_forceClose) {
if (QGroundControl.multiVehicleManager.activeVehicle) { mainWindowInner.item.attemptWindowClose()
showWindowCloseMessage()
close.accepted = false close.accepted = false
return
} }
// We still need to shutdown LinkManager even though no active connections so that we don't get any
// more auto-connect links during shutdown.
QGroundControl.linkManager.shutdown();
} }
function showFlyView() { function showFlyView() {
...@@ -57,10 +53,6 @@ Window { ...@@ -57,10 +53,6 @@ Window {
mainWindowInner.item.showSetupView() mainWindowInner.item.showSetupView()
} }
function showWindowCloseMessage() {
windowCloseDialog.open()
}
// The following are use for unit testing only // The following are use for unit testing only
function showSetupFirmware() { function showSetupFirmware() {
...@@ -87,33 +79,15 @@ Window { ...@@ -87,33 +79,15 @@ Window {
id: mainWindowInner id: mainWindowInner
anchors.fill: parent anchors.fill: parent
source: "MainWindowInner.qml" source: "MainWindowInner.qml"
}
MessageDialog { Connections {
id: windowCloseDialog target: mainWindowInner.item
title: "QGroundControl close"
text: "There are still active connections to vehicles. Do you want to disconnect these before closing?"
standardButtons: StandardButton.Yes | StandardButton.Cancel
modality: Qt.ApplicationModal
visible: false
onYes: {
QGroundControl.linkManager.shutdown()
// The above shutdown causes a flurry of activity as the vehicle components are removed. This in turn
// causes the Windows Version of Qt to crash if you allow the close event to be accepted. In order to prevent
// the crash, we ignore the close event and setup a delayed timer to close the window after things settle down.
delayedWindowCloseTimer.start()
}
}
Timer {
id: delayedWindowCloseTimer
interval: 1500
running: false
repeat: false
onTriggered: _rootWindow.close() onReallyClose: {
_forceClose = true
_rootWindow.close()
}
}
} }
} }
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment