Просмотр исходного кода

Add on-exit callback management and check for active USB printing

CURA-5384
Lipu Fei 6 лет назад
Родитель
Сommit
c0b7e40b0d

+ 33 - 4
cura/CuraApplication.py

@@ -5,6 +5,7 @@ import copy
 import os
 import sys
 import time
+from typing import cast, TYPE_CHECKING, Optional
 
 import numpy
 
@@ -13,8 +14,6 @@ from PyQt5.QtGui import QColor, QIcon
 from PyQt5.QtWidgets import QMessageBox
 from PyQt5.QtQml import qmlRegisterUncreatableType, qmlRegisterSingletonType, qmlRegisterType
 
-from typing import cast, TYPE_CHECKING
-
 from UM.Scene.SceneNode import SceneNode
 from UM.Scene.Camera import Camera
 from UM.Math.Vector import Vector
@@ -97,6 +96,8 @@ from . import CuraSplashScreen
 from . import CameraImageProvider
 from . import MachineActionManager
 
+from cura.TaskManagement.OnExitCallbackManager import OnExitCallbackManager
+
 from cura.Settings.MachineManager import MachineManager
 from cura.Settings.ExtruderManager import ExtruderManager
 from cura.Settings.UserChangesModel import UserChangesModel
@@ -158,6 +159,8 @@ class CuraApplication(QtApplication):
 
         self._boot_loading_time = time.time()
 
+        self._on_exit_callback_manager = OnExitCallbackManager(self)
+
         # Variables set from CLI
         self._files_to_open = []
         self._use_single_instance = False
@@ -520,8 +523,8 @@ class CuraApplication(QtApplication):
     def setNeedToShowUserAgreement(self, set_value = True):
         self._need_to_show_user_agreement = set_value
 
-    ## The "Quit" button click event handler.
-    @pyqtSlot()
+    # DO NOT call this function to close the application, use checkAndExitApplication() instead which will perform
+    # pre-exit checks such as checking for in-progress USB printing, etc.
     def closeApplication(self):
         Logger.log("i", "Close application")
         main_window = self.getMainWindow()
@@ -530,6 +533,32 @@ class CuraApplication(QtApplication):
         else:
             self.exit(0)
 
+    # This function first performs all upon-exit checks such as USB printing that is in progress.
+    # Use this to close the application.
+    @pyqtSlot()
+    def checkAndExitApplication(self) -> None:
+        self._on_exit_callback_manager.resetCurrentState()
+        self._on_exit_callback_manager.triggerNextCallback()
+
+    @pyqtSlot(result = bool)
+    def getIsAllChecksPassed(self) -> bool:
+        return self._on_exit_callback_manager.getIsAllChecksPassed()
+
+    def getOnExitCallbackManager(self) -> "OnExitCallbackManager":
+        return self._on_exit_callback_manager
+
+    def triggerNextExitCheck(self) -> None:
+        self._on_exit_callback_manager.triggerNextCallback()
+
+    showConfirmExitDialog = pyqtSignal(str, arguments = ["message"])
+
+    def setConfirmExitDialogCallback(self, callback):
+        self._confirm_exit_dialog_callback = callback
+
+    @pyqtSlot(bool)
+    def callConfirmExitDialogCallback(self, yes_or_no: bool):
+        self._confirm_exit_dialog_callback(yes_or_no)
+
     ##  Signal to connect preferences action in QML
     showPreferencesWindow = pyqtSignal()
 

+ 69 - 0
cura/TaskManagement/OnExitCallbackManager.py

@@ -0,0 +1,69 @@
+# Copyright (c) 2018 Ultimaker B.V.
+# Cura is released under the terms of the LGPLv3 or higher.
+
+from typing import TYPE_CHECKING, List
+
+from UM.Logger import Logger
+
+if TYPE_CHECKING:
+    from cura.CuraApplication import CuraApplication
+
+
+#
+# This class manages a all registered upon-exit checks that need to be perform when the application tries to exit.
+# For example, to show a confirmation dialog when there is USB printing in progress, etc. All callbacks will be called
+# in the order of when they got registered. If all callbacks "passes", that is, for example, if the user clicks "yes"
+# on the exit confirmation dialog or nothing that's blocking the exit, then the application will quit after that.
+#
+class OnExitCallbackManager:
+
+    def __init__(self, application: "CuraApplication") -> None:
+        self._application = application
+        self._on_exit_callback_list = list()  # type: List[callable]
+        self._current_callback_idx = 0
+        self._is_all_checks_passed = False
+
+    def addCallback(self, callback: callable) -> None:
+        self._on_exit_callback_list.append(callback)
+        Logger.log("d", "on-app-exit callback [%s] added.", callback)
+
+    # Reset the current state so the next time it will call all the callbacks again.
+    def resetCurrentState(self) -> None:
+        self._current_callback_idx = 0
+        self._is_all_checks_passed = False
+
+    def getIsAllChecksPassed(self) -> bool:
+        return self._is_all_checks_passed
+
+    # Trigger the next callback if available. If not, it means that all callbacks have "passed", which means we should
+    # not block the application to quit, and it will call the application to actually quit.
+    def triggerNextCallback(self) -> None:
+        # Get the next callback and schedule that if
+        this_callback = None
+        if self._current_callback_idx < len(self._on_exit_callback_list):
+            this_callback = self._on_exit_callback_list[self._current_callback_idx]
+            self._current_callback_idx += 1
+
+        if this_callback is not None:
+            Logger.log("d", "Scheduled the next on-app-exit callback [%s]", this_callback)
+            self._application.callLater(this_callback)
+        else:
+            Logger.log("d", "No more on-app-exit callbacks to process. Tell the app to exit.")
+
+            self._is_all_checks_passed = True
+
+            # Tell the application to exit
+            self._application.callLater(self._application.closeApplication)
+
+    # This is the callback function which an on-exit callback should call when it finishes, it should provide the
+    # "should_proceed" flag indicating whether this check has "passed", or in other words, whether quiting the
+    # application should be blocked. If the last on-exit callback doesn't block the quiting, it will call the next
+    # registered on-exit callback if available.
+    def onCurrentCallbackFinished(self, should_proceed: bool = True) -> None:
+        if not should_proceed:
+            Logger.log("d", "on-app-exit callback finished and we should not proceed.")
+            # Reset the state
+            self.resetCurrentState()
+            return
+
+        self.triggerNextCallback()

+ 0 - 0
cura/TaskManagement/__init__.py


+ 19 - 0
plugins/USBPrinting/USBPrinterOutputDevice.py

@@ -88,6 +88,25 @@ class USBPrinterOutputDevice(PrinterOutputDevice):
         self._command_received = Event()
         self._command_received.set()
 
+        CuraApplication.getInstance().getOnExitCallbackManager().addCallback(self._checkActivePrintingUponAppExit)
+
+    # This is a callback function that checks if there is any printing in progress via USB when the application tries
+    # to exit. If so, it will show a confirmation before
+    def _checkActivePrintingUponAppExit(self) -> None:
+        application = CuraApplication.getInstance()
+        if not self._is_printing:
+            # This USB printer is not printing, so we have nothing to do. Call the next callback if exists.
+            application.triggerNextExitCheck()
+            return
+
+        application.setConfirmExitDialogCallback(self._onConfirmExitDialogResult)
+        application.showConfirmExitDialog.emit("USB printing is in progress")
+
+    def _onConfirmExitDialogResult(self, result: bool) -> None:
+        if result:
+            application = CuraApplication.getInstance()
+            application.triggerNextExitCheck()
+
     ## Reset USB device settings
     #
     def resetDeviceSettings(self):

+ 37 - 5
resources/qml/Cura.qml

@@ -1,11 +1,11 @@
 // Copyright (c) 2017 Ultimaker B.V.
 // Cura is released under the terms of the LGPLv3 or higher.
 
-import QtQuick 2.2
-import QtQuick.Controls 1.1
-import QtQuick.Controls.Styles 1.1
+import QtQuick 2.7
+import QtQuick.Controls 1.4
+import QtQuick.Controls.Styles 1.4
 import QtQuick.Layouts 1.1
-import QtQuick.Dialogs 1.1
+import QtQuick.Dialogs 1.2
 
 import UM 1.3 as UM
 import Cura 1.0 as Cura
@@ -700,10 +700,42 @@ UM.MainWindow
         id: contextMenu
     }
 
+    onPreClosing:
+    {
+        close.accepted = CuraApplication.getIsAllChecksPassed();
+        if (!close.accepted)
+        {
+            CuraApplication.checkAndExitApplication();
+        }
+    }
+
+    MessageDialog
+    {
+        id: exitConfirmationDialog
+        title: catalog.i18nc("@title:window", "Closing Cura")
+        text: catalog.i18nc("@label", "Are you sure you want to exit Cura?")
+        icon: StandardIcon.Question
+        modality: Qt.ApplicationModal
+        standardButtons: StandardButton.Yes | StandardButton.No
+        onYes: CuraApplication.callConfirmExitDialogCallback(true)
+        onNo: CuraApplication.callConfirmExitDialogCallback(false)
+        onRejected: CuraApplication.callConfirmExitDialogCallback(false)
+    }
+
+    Connections
+    {
+        target: CuraApplication
+        onShowConfirmExitDialog:
+        {
+            exitConfirmationDialog.text = message;
+            exitConfirmationDialog.open();
+        }
+    }
+
     Connections
     {
         target: Cura.Actions.quit
-        onTriggered: CuraApplication.closeApplication();
+        onTriggered: CuraApplication.exitApplication();
     }
 
     Connections