From ea5587bee307af232f861ad1d56d79726351eb59 Mon Sep 17 00:00:00 2001 From: Loren Eteval Date: Sun, 28 Apr 2024 22:43:09 +0800 Subject: [PATCH] Move to asynchronous implementation on mbox/dialog Signed-off-by: Loren Eteval --- Furious/QtFramework/UpdatesManager.py | 41 ++++---- Furious/TrayActions/Connect.py | 12 +-- Furious/TrayActions/Import.py | 27 +++-- Furious/TrayActions/Settings.py | 4 +- Furious/Widget/UserServersQTableWidget.py | 26 +++-- Furious/Widget/UserSubsQTableWidget.py | 36 ++++--- Furious/Widget/XrayAssetViewerQListWidget.py | 94 ++++++++++------- Furious/Window/LogViewerWindow.py | 4 +- Furious/Window/TextEditorWindow.py | 100 +++++++++++-------- Furious/Window/UserSubsWindow.py | 41 +++++--- 10 files changed, 233 insertions(+), 152 deletions(-) diff --git a/Furious/QtFramework/UpdatesManager.py b/Furious/QtFramework/UpdatesManager.py index ad92366..ebddfe2 100644 --- a/Furious/QtFramework/UpdatesManager.py +++ b/Furious/QtFramework/UpdatesManager.py @@ -98,8 +98,7 @@ class UpdatesManager(AppQNetworkAccessManager): def __init__(self, parent=None): super().__init__(parent) - @staticmethod - def handleFinishedByNetworkReply(networkReply): + def handleFinishedByNetworkReply(self, networkReply): assert isinstance(networkReply, QNetworkReply) if networkReply.error() != QNetworkReply.NetworkError.NoError: @@ -109,8 +108,8 @@ class UpdatesManager(AppQNetworkAccessManager): mbox.setWindowTitle(_(APPLICATION_NAME)) mbox.setText(_('Check for updates failed')) - # Show the MessageBox and wait for user to close it - mbox.exec() + # Show the MessageBox asynchronously + mbox.open() else: logger.info('check for updates success') @@ -118,29 +117,37 @@ class UpdatesManager(AppQNetworkAccessManager): info = UJSONEncoder.decode(networkReply.readAll().data()) if versionToNumber(info['tag_name']) > versionToNumber(APPLICATION_VERSION): + + def handleResultCode(code): + if code == PySide6LegacyEnumValueWrapper( + AppQMessageBox.StandardButton.Yes + ): + if QDesktopServices.openUrl(QtCore.QUrl(info['html_url'])): + logger.info('open download page success') + else: + logger.error('open download page failed') + else: + # Do nothing + pass + mbox = QuestionUpdateMBox(icon=AppQMessageBox.Icon.Information) mbox.version = info['tag_name'] mbox.setText(mbox.customText()) mbox.setInformativeText(_('Go to download page?')) + mbox.finished.connect(functools.partial(handleResultCode)) - # Show the MessageBox and wait for user to close it - if mbox.exec() == PySide6LegacyEnumValueWrapper( - AppQMessageBox.StandardButton.Yes - ): - if QDesktopServices.openUrl(QtCore.QUrl(info['html_url'])): - logger.info('open download page success') - else: - logger.error('open download page failed') - else: - # Do nothing - pass + # dummy ref + setattr(self, '_questionUpdateMBox', mbox) + + # Show the MessageBox asynchronously + mbox.open() else: mbox = AppQMessageBox(icon=AppQMessageBox.Icon.Information) mbox.setWindowTitle(_(APPLICATION_NAME)) mbox.setText(_(f'{APPLICATION_NAME} is already the latest version')) - # Show the MessageBox and wait for user to close it - mbox.exec() + # Show the MessageBox asynchronously + mbox.open() def configureHttpProxy(self, httpProxy: Union[str, None]) -> bool: useProxy = super().configureHttpProxy(httpProxy) diff --git a/Furious/TrayActions/Connect.py b/Furious/TrayActions/Connect.py index b93d0a5..f5312e9 100644 --- a/Furious/TrayActions/Connect.py +++ b/Furious/TrayActions/Connect.py @@ -193,8 +193,8 @@ class ConnectAction(AppQAction): _('Server configuration empty. Please configure your server first') ) - # Show the MessageBox and wait for user to close it - mbox.exec() + # Show the MessageBox asynchronously + mbox.open() return @@ -209,8 +209,8 @@ class ConnectAction(AppQAction): _('Select and double click to activate configuration and connect') ) - # Show the MessageBox and wait for user to close it - mbox.exec() + # Show the MessageBox asynchronously + mbox.open() return @@ -242,8 +242,8 @@ class ConnectAction(AppQAction): ) mbox.setInformativeText(_('Please complete your server configuration')) - # Show the MessageBox and wait for user to close it - mbox.exec() + # Show the MessageBox asynchronously + mbox.open() return diff --git a/Furious/TrayActions/Import.py b/Furious/TrayActions/Import.py index b74f65d..289e7cd 100644 --- a/Furious/TrayActions/Import.py +++ b/Furious/TrayActions/Import.py @@ -55,7 +55,8 @@ def showImportErrorMBox(clipboard: str): mbox.setText(_('Invalid data. The content of the clipboard is:')) mbox.setInformativeText(clipboard) - mbox.exec() + # Show the MessageBox asynchronously + mbox.open() def importItemFromClipboard(clipboard: str): @@ -69,7 +70,9 @@ def importItemFromClipboard(clipboard: str): mbox = ImportSuccessMBox(icon=AppQMessageBox.Icon.Information) mbox.remark = factory.getExtras('remark') mbox.setText(mbox.customText()) - mbox.exec() + + # Show the MessageBox asynchronously + mbox.open() needTrans('Import') @@ -193,8 +196,8 @@ class ImportFromFileAction(AppQAction): mbox.setText(_('Invalid configuration file')) mbox.setInformativeText(str(ex)) - # Show the MessageBox and wait for user to close it - mbox.exec() + # Show the MessageBox asynchronously + mbox.open() else: factory = constructFromAny(plainText, remark=os.path.basename(filename)) @@ -204,12 +207,16 @@ class ImportFromFileAction(AppQAction): mbox = ImportSuccessMBox(icon=AppQMessageBox.Icon.Information) mbox.remark = factory.getExtras('remark') mbox.setText(mbox.customText()) - mbox.exec() + + # Show the MessageBox asynchronously + mbox.open() else: mbox = ImportErrorMBox(icon=AppQMessageBox.Icon.Critical) mbox.setText(_('Invalid data')) mbox.setInformativeText('') - mbox.show() + + # Show the MessageBox asynchronously + mbox.open() needTrans('Import Share Link From Clipboard') @@ -248,13 +255,17 @@ class ImportURIFromClipboardAction(AppQAction): mbox = ImportSuccessMBox(icon=AppQMessageBox.Icon.Information) mbox.remark = imported[0] mbox.setText(mbox.customText()) - mbox.exec() + + # Show the MessageBox asynchronously + mbox.open() else: mbox = ImportMultiSuccessMBox(icon=AppQMessageBox.Icon.Information) mbox.imported = imported mbox.rowCount = rowCount mbox.setText(mbox.customText()) - mbox.exec() + + # Show the MessageBox asynchronously + mbox.open() needTrans('Import JSON Configuration From Clipboard') diff --git a/Furious/TrayActions/Settings.py b/Furious/TrayActions/Settings.py index 3d77dc3..a2cd7b8 100644 --- a/Furious/TrayActions/Settings.py +++ b/Furious/TrayActions/Settings.py @@ -63,7 +63,9 @@ class VPNModeAction(AppQAction): try: if APP().isSystemTrayConnected(): mbox = NewChangesNextTimeMBox() - mbox.exec() + + # Show the MessageBox asynchronously + mbox.open() except Exception: # Any non-exit exceptions diff --git a/Furious/Widget/UserServersQTableWidget.py b/Furious/Widget/UserServersQTableWidget.py index f98e310..7cc39ff 100644 --- a/Furious/Widget/UserServersQTableWidget.py +++ b/Furious/Widget/UserServersQTableWidget.py @@ -868,8 +868,9 @@ class UserServersQTableWidget(QTranslatable, AppQTableWidget): # Show the MessageBox asynchronously mbox.open() else: - # Show the MessageBox and wait for user to close it - mbox.exec() + # Show the MessageBox asynchronously + # TODO: Verify + mbox.open() return @@ -939,7 +940,9 @@ class UserServersQTableWidget(QTranslatable, AppQTableWidget): try: if modified and APP().isSystemTrayConnected(): mbox = NewChangesNextTimeMBox() - mbox.exec() + + # Show the MessageBox asynchronously + mbox.open() except Exception: # Any non-exit exceptions @@ -1220,18 +1223,23 @@ class UserServersQTableWidget(QTranslatable, AppQTableWidget): # Nothing selected. Do nothing return + def handleResultCode(_indexes, code): + if code == PySide6LegacyEnumValueWrapper(AppQMessageBox.StandardButton.Yes): + self.deleteItemByIndex(_indexes) + else: + pass + mbox = QuestionDeleteMBox(icon=AppQMessageBox.Icon.Question) mbox.isMulti = bool(len(indexes) > 1) mbox.possibleRemark = f'{indexes[0] + 1} - {self.item(indexes[0], 0).text()}' mbox.setText(mbox.customText()) + mbox.finished.connect(functools.partial(handleResultCode, indexes)) - if mbox.exec() == PySide6LegacyEnumValueWrapper( - AppQMessageBox.StandardButton.No - ): - # Do not delete - return + # dummy ref + setattr(self, '_questionDeleteMBox', mbox) - self.deleteItemByIndex(indexes) + # Show the MessageBox asynchronously + mbox.open() def editSelectedItemConfiguration(self): indexes = self.selectedIndex diff --git a/Furious/Widget/UserSubsQTableWidget.py b/Furious/Widget/UserSubsQTableWidget.py index 79f0f96..8d98365 100644 --- a/Furious/Widget/UserSubsQTableWidget.py +++ b/Furious/Widget/UserSubsQTableWidget.py @@ -151,27 +151,33 @@ class UserSubsQTableWidget(QTranslatable, AppQTableWidget): # Nothing to do return + def handleResultCode(_indexes, code): + if code == PySide6LegacyEnumValueWrapper(AppQMessageBox.StandardButton.Yes): + for i in range(len(_indexes)): + deleteIndex = _indexes[i] - i + deleteUnique = list(AS_UserSubscription().keys())[deleteIndex] + + self.removeRow(deleteIndex) + + AS_UserSubscription().pop(deleteUnique) + + if callable(self.deleteUniqueCallback): + self.deleteUniqueCallback(deleteUnique) + else: + # Do not delete + pass + mbox = QuestionDeleteMBox(icon=AppQMessageBox.Icon.Question) mbox.isMulti = bool(len(indexes) > 1) mbox.possibleRemark = self.item(indexes[0], 0).text() mbox.setText(mbox.customText()) + mbox.finished.connect(functools.partial(handleResultCode, indexes)) - if mbox.exec() == PySide6LegacyEnumValueWrapper( - AppQMessageBox.StandardButton.No - ): - # Do not delete - return + # dummy ref + setattr(self, '_questionDeleteMBox', mbox) - for i in range(len(indexes)): - deleteIndex = indexes[i] - i - deleteUnique = list(AS_UserSubscription().keys())[deleteIndex] - - self.removeRow(deleteIndex) - - AS_UserSubscription().pop(deleteUnique) - - if callable(self.deleteUniqueCallback): - self.deleteUniqueCallback(deleteUnique) + # Show the MessageBox asynchronously + mbox.open() def flushItem(self, row, column, item): header = self.Headers[column] diff --git a/Furious/Widget/XrayAssetViewerQListWidget.py b/Furious/Widget/XrayAssetViewerQListWidget.py index 8b4139b..7b8746e 100644 --- a/Furious/Widget/XrayAssetViewerQListWidget.py +++ b/Furious/Widget/XrayAssetViewerQListWidget.py @@ -132,44 +132,58 @@ class XrayAssetViewerQListWidget(SupportThemeChangedCallback, AppQListWidget): self.flushItemByTheme(darkdetect.theme()) def appendNewItem(self, filename: str): + def append(_filename): + try: + shutil.copy(_filename, XRAY_ASSET_DIR) + except shutil.SameFileError: + # Same file imported. Do nothing + pass + except Exception as ex: + # Any non-exit exception + + _mbox = AppQMessageBox(icon=AppQMessageBox.Icon.Critical) + _mbox.setWindowTitle(_('Import')) + _mbox.setText(_('Error import asset file')) + _mbox.setInformativeText(str(ex)) + + # Show the MessageBox asynchronously + _mbox.open() + else: + self.flushItem() + + _mbox = AppQMessageBox(icon=AppQMessageBox.Icon.Information) + _mbox.setWindowTitle(_('Import')) + _mbox.setText(_('Import asset file success')) + + # Show the MessageBox asynchronously + _mbox.open() + basename = os.path.basename(filename) if os.path.isfile(XRAY_ASSET_DIR / basename): + + def handleResultCode(_filename, code): + if code == PySide6LegacyEnumValueWrapper( + AppQMessageBox.StandardButton.Yes + ): + append(_filename) + else: + # Do not overwrite + pass + mbox = AssetExistsMBox(icon=AppQMessageBox.Icon.Question) mbox.setWindowTitle(_('Import')) mbox.setText(_('Asset file already exists. Overwrite?')) mbox.setInformativeText(basename) + mbox.finished.connect(functools.partial(handleResultCode, filename)) - if mbox.exec() == PySide6LegacyEnumValueWrapper( - AppQMessageBox.StandardButton.No - ): - # Do not overwrite - return + # dummy ref + setattr(self, '_assetExistsMBox', mbox) - try: - shutil.copy(filename, XRAY_ASSET_DIR) - except shutil.SameFileError: - # Same file imported. Do nothing - pass - except Exception as ex: - # Any non-exit exception - - mbox = AppQMessageBox(icon=AppQMessageBox.Icon.Critical) - mbox.setWindowTitle(_('Import')) - mbox.setText(_('Error import asset file')) - mbox.setInformativeText(str(ex)) - - # Show the MessageBox and wait for user to close it - mbox.exec() + # Show the MessageBox asynchronously + mbox.open() else: - self.flushItem() - - mbox = AppQMessageBox(icon=AppQMessageBox.Icon.Information) - mbox.setWindowTitle(_('Import')) - mbox.setText(_('Import asset file success')) - - # Show the MessageBox and wait for user to close it - mbox.exec() + append(filename) def deleteSelectedItem(self): indexes = self.selectedIndex @@ -178,21 +192,27 @@ class XrayAssetViewerQListWidget(SupportThemeChangedCallback, AppQListWidget): # Nothing selected return + def handleResultCode(_indexes, code): + if code == PySide6LegacyEnumValueWrapper(AppQMessageBox.StandardButton.Yes): + for index in _indexes: + os.remove(XRAY_ASSET_DIR / self.item(index).text()) + + self.flushItem() + else: + # Do not delete + pass + mbox = QuestionDeleteMBox(icon=AppQMessageBox.Icon.Question) mbox.isMulti = bool(len(indexes) > 1) mbox.possibleRemark = f'{self.item(indexes[0]).text()}' mbox.setText(mbox.customText()) + mbox.finished.connect(functools.partial(handleResultCode, indexes)) - if mbox.exec() == PySide6LegacyEnumValueWrapper( - AppQMessageBox.StandardButton.No - ): - # Do not delete - return + # dummy ref + setattr(self, '_questionDeleteMBox', mbox) - for index in indexes: - os.remove(XRAY_ASSET_DIR / self.item(index).text()) - - self.flushItem() + # Show the MessageBox asynchronously + mbox.open() def keyPressEvent(self, event): if event.key() == QtCore.Qt.Key.Key_Delete: diff --git a/Furious/Window/LogViewerWindow.py b/Furious/Window/LogViewerWindow.py index 5e12d6a..fc24666 100644 --- a/Furious/Window/LogViewerWindow.py +++ b/Furious/Window/LogViewerWindow.py @@ -75,8 +75,8 @@ def saveAsFile(content: str): mbox.setWindowTitle(_('Error saving log')) mbox.setText(mbox.customText()) - # Show the MessageBox and wait for user to close it - mbox.exec() + # Show the MessageBox asynchronously + mbox.open() needTrans( diff --git a/Furious/Window/TextEditorWindow.py b/Furious/Window/TextEditorWindow.py index f153fae..52d9d89 100644 --- a/Furious/Window/TextEditorWindow.py +++ b/Furious/Window/TextEditorWindow.py @@ -118,8 +118,6 @@ class TextEditorWindow(AppQMainWindow): self.setWindowModality(QtCore.Qt.WindowModality.WindowModal) self.setFixedSize(520, int(520 * GOLDEN_RATIO)) - self.indentSpinBox = IndentSpinBox(parent=self) - # Current editing index self.currentIndex = -1 @@ -270,24 +268,32 @@ class TextEditorWindow(AppQMainWindow): def questionSave(self): if self.modified: - mbox = QuestionSaveMBox(icon=AppQMessageBox.Icon.Question) - code = mbox.exec() - if code == PySide6LegacyEnumValueWrapper( - AppQMessageBox.ButtonRole.AcceptRole - ): - if self.save(): + def handleResultCode(code): + if code == PySide6LegacyEnumValueWrapper( + AppQMessageBox.ButtonRole.AcceptRole + ): + if self.save(): + self.hide() + if code == PySide6LegacyEnumValueWrapper( + AppQMessageBox.ButtonRole.DestructiveRole + ): + self.markAsSaved() self.hide() - if code == PySide6LegacyEnumValueWrapper( - AppQMessageBox.ButtonRole.DestructiveRole - ): - self.markAsSaved() - self.hide() - if code == PySide6LegacyEnumValueWrapper( - AppQMessageBox.ButtonRole.RejectRole - ): - # Cancel. Do nothing - pass + if code == PySide6LegacyEnumValueWrapper( + AppQMessageBox.ButtonRole.RejectRole + ): + # Cancel. Do nothing + pass + + mbox = QuestionSaveMBox(icon=AppQMessageBox.Icon.Question) + mbox.finished.connect(functools.partial(handleResultCode)) + + # dummy ref + setattr(self, '_questionSaveMBox', mbox) + + # Show the MessageBox asynchronously + mbox.open() else: self.hide() @@ -319,8 +325,8 @@ class TextEditorWindow(AppQMainWindow): mbox.setWindowTitle(_('Error saving configuration')) mbox.setText(mbox.customText()) - # Show the MessageBox and wait for user to close it - mbox.exec() + # Show the MessageBox asynchronously + mbox.open() return False else: @@ -342,7 +348,9 @@ class TextEditorWindow(AppQMainWindow): try: if APP().isSystemTrayConnected(): mbox = NewChangesNextTimeMBox() - mbox.exec() + + # Show the MessageBox asynchronously + mbox.open() except Exception: # Any non-exit exceptions @@ -369,34 +377,44 @@ class TextEditorWindow(AppQMainWindow): mbox.setText(_('Invalid server configuration')) mbox.setInformativeText(str(ex)) - # Show the MessageBox and wait for user to close it - mbox.exec() + # Show the MessageBox asynchronously + mbox.open() def setIndent(self): - code = self.indentSpinBox.exec() + def handleResultCode(_indentSpinBox, code): + if code == PySide6LegacyEnumValueWrapper(AppQDialog.DialogCode.Accepted): + plain = self.jsonEditor.toPlainText() - if code == PySide6LegacyEnumValueWrapper(AppQDialog.DialogCode.Accepted): - plain = self.jsonEditor.toPlainText() + try: + jsonObject = JSONEncoder.decode(plain) + except Exception as ex: + # Any non-exit exceptions - try: - jsonObject = JSONEncoder.decode(plain) - except Exception as ex: - # Any non-exit exceptions + mbox = JSONDecodeErrorMBox(icon=AppQMessageBox.Icon.Critical) + mbox.error = str(ex) + mbox.setWindowTitle(_('Error setting indent')) + mbox.setText(mbox.customText()) - mbox = JSONDecodeErrorMBox(icon=AppQMessageBox.Icon.Critical) - mbox.error = str(ex) - mbox.setWindowTitle(_('Error setting indent')) - mbox.setText(mbox.customText()) + # Show the MessageBox asynchronously + mbox.open() + else: + text = JSONEncoder.encode(jsonObject, indent=_indentSpinBox.value()) - # Show the MessageBox and wait for user to close it - mbox.exec() + self.setPlainText(text, False) else: - text = JSONEncoder.encode(jsonObject, indent=self.indentSpinBox.value()) + # Do nothing + pass - self.setPlainText(text, False) - else: - # Do nothing - pass + indentSpinBox = IndentSpinBox(parent=self) + indentSpinBox.finished.connect( + functools.partial(handleResultCode, indentSpinBox) + ) + + # dummy ref + setattr(self, '_IndentSpinBox', indentSpinBox) + + # Show the MessageBox asynchronously + indentSpinBox.open() def showTabAndSpaces(self): textOption = QTextOption() diff --git a/Furious/Window/UserSubsWindow.py b/Furious/Window/UserSubsWindow.py index c5e2574..655bb57 100644 --- a/Furious/Window/UserSubsWindow.py +++ b/Furious/Window/UserSubsWindow.py @@ -97,9 +97,9 @@ class UserSubsWindow(AppQMainWindow): super().__init__(*args, **kwargs) + self.setWindowModality(QtCore.Qt.WindowModality.WindowModal) self.setWindowTitle(_('Edit Subscription')) - self.addSubsDialog = AddSubsDialog() self.userSubsQTableWidget = UserSubsQTableWidget(deleteUniqueCallback=callback) self.userSubsTab = AppQTabWidget(self) @@ -135,23 +135,32 @@ class UserSubsWindow(AppQMainWindow): self.setGeometry(100, 100, 360 * GOLDEN_RATIO, 360) def addSubs(self): - # Show the Dialog and wait for user to close it - if self.addSubsDialog.exec() == PySide6LegacyEnumValueWrapper( - AppQDialog.DialogCode.Accepted - ): - remark = self.addSubsDialog.subsRemark() - webURL = self.addSubsDialog.subsWebURL() + def handleResultCode(_addSubsDialog, code): + if code == PySide6LegacyEnumValueWrapper(AppQDialog.DialogCode.Accepted): + remark = _addSubsDialog.subsRemark() + webURL = _addSubsDialog.subsWebURL() - if remark: - # Unique id. Used by display and deletion - unique = str(uuid.uuid4()) + if remark: + # Unique id. Used by display and deletion + unique = str(uuid.uuid4()) - self.userSubsQTableWidget.appendNewItem( - unique=unique, remark=remark, webURL=webURL - ) - else: - # Do nothing - pass + self.userSubsQTableWidget.appendNewItem( + unique=unique, remark=remark, webURL=webURL + ) + else: + # Do nothing + pass + + addSubsDialog = AddSubsDialog(parent=self) + addSubsDialog.finished.connect( + functools.partial(handleResultCode, addSubsDialog) + ) + + # dummy ref + setattr(self, '_addSubsDialog', addSubsDialog) + + # Show the MessageBox asynchronously + addSubsDialog.open() def deleteSelectedItem(self): self.userSubsQTableWidget.deleteSelectedItem()