From 7c4446ae0ddb2b4f03ce818bff88e4ef2a914f01 Mon Sep 17 00:00:00 2001 From: beyondcompute Date: Mon, 22 Dec 2025 06:23:39 +0200 Subject: [PATCH 1/3] Add global Ctrl/Cmd+W handler to close active window/dialog Before this change, developer needed to add such a handler to each window/dialog separately --- qt/aqt/__init__.py | 16 ++++++++++++++++ qt/aqt/main.py | 16 ++++++++++++++++ 2 files changed, 32 insertions(+) diff --git a/qt/aqt/__init__.py b/qt/aqt/__init__.py index 53bdc3c92..11193cb00 100644 --- a/qt/aqt/__init__.py +++ b/qt/aqt/__init__.py @@ -407,6 +407,22 @@ class AnkiApp(QApplication): def eventFilter(self, src: Any, evt: QEvent | None) -> bool: assert evt is not None + # Handle Close shortcut here because modal dialogs disable main-window shortcuts + if (is_mac or is_lin) and evt.type() == QEvent.Type.KeyPress: + key_event = cast(QKeyEvent, evt) + if not key_event.isAutoRepeat(): + mods = cast(int, key_event.modifiers().value) + seq = QKeySequence(mods | key_event.key()) + if any( + seq == binding + for binding in QKeySequence.keyBindings( + QKeySequence.StandardKey.Close + ) + ): + if mw is not None: + mw._close_active_window() + return True + pointer_classes = ( QPushButton, QCheckBox, diff --git a/qt/aqt/main.py b/qt/aqt/main.py index c707d1b2a..df55b68f6 100644 --- a/qt/aqt/main.py +++ b/qt/aqt/main.py @@ -1184,6 +1184,22 @@ title="{}" {}>{}""".format( self.applyShortcuts(globalShortcuts) self.stateShortcuts: list[QShortcut] = [] + def _close_active_window(self) -> None: + window = ( + QApplication.activeModalWidget() + or current_window() + or self.app.activeWindow() + ) + if not window or window is self: + return + if window is getattr(self, "profileDiag", None): + # Do not allow closing of ProfileManager + return + if isinstance(window, QDialog): + window.reject() + else: + window.close() + def _normalize_shortcuts( self, shortcuts: Sequence[tuple[str, Callable]] ) -> Sequence[tuple[QKeySequence, Callable]]: From 4847eaa2ab209031322075c8b0763703b84dd6b3 Mon Sep 17 00:00:00 2001 From: beyondcompute Date: Mon, 22 Dec 2025 06:31:36 +0200 Subject: [PATCH 2/3] Remove calls of `add_close_shortcut` everywhere because global Ctrl/Cmd+W handler was added in a previous commit --- qt/aqt/addcards.py | 2 -- qt/aqt/addons.py | 2 -- qt/aqt/browser/browser.py | 3 --- qt/aqt/browser/card_info.py | 2 -- qt/aqt/changenotetype.py | 2 -- qt/aqt/clayout.py | 2 -- qt/aqt/customstudy.py | 3 +-- qt/aqt/deckdescription.py | 3 +-- qt/aqt/deckoptions.py | 2 -- qt/aqt/editcurrent.py | 3 +-- qt/aqt/exporting.py | 2 -- qt/aqt/fields.py | 2 -- qt/aqt/import_export/exporting.py | 2 -- qt/aqt/import_export/import_dialog.py | 3 +-- qt/aqt/operations/scheduling.py | 4 +--- qt/aqt/preferences.py | 2 -- qt/aqt/stats.py | 3 --- qt/aqt/studydeck.py | 2 -- qt/aqt/sync.py | 2 -- qt/aqt/utils.py | 1 - 20 files changed, 5 insertions(+), 42 deletions(-) diff --git a/qt/aqt/addcards.py b/qt/aqt/addcards.py index 01d7423d8..cde529d18 100644 --- a/qt/aqt/addcards.py +++ b/qt/aqt/addcards.py @@ -21,7 +21,6 @@ from aqt.qt import * from aqt.sound import av_player from aqt.utils import ( HelpPage, - add_close_shortcut, ask_user_dialog, askUser, downArrow, @@ -49,7 +48,6 @@ class AddCards(QMainWindow): self.setMinimumWidth(400) self.setup_choosers() self.setupEditor() - add_close_shortcut(self) self._load_new_note() self.setupButtons() self.history: list[NoteId] = [] diff --git a/qt/aqt/addons.py b/qt/aqt/addons.py index a940fb208..d21df05bb 100644 --- a/qt/aqt/addons.py +++ b/qt/aqt/addons.py @@ -40,7 +40,6 @@ from aqt import gui_hooks from aqt.log import ADDON_LOGGER_PREFIX, find_addon_logger, get_addon_logs_folder from aqt.qt import * from aqt.utils import ( - add_close_shortcut, askUser, disable_help_button, getFile, @@ -829,7 +828,6 @@ class AddonsDialog(QDialog): self.setAcceptDrops(True) self.redrawAddons() restoreGeom(self, "addons") - add_close_shortcut(self) gui_hooks.addons_dialog_will_show(self) self._onAddonSelectionChanged() self.show() diff --git a/qt/aqt/browser/browser.py b/qt/aqt/browser/browser.py index d935905f6..fddfeae64 100644 --- a/qt/aqt/browser/browser.py +++ b/qt/aqt/browser/browser.py @@ -60,7 +60,6 @@ from aqt.undo import UndoActionsInfo from aqt.utils import ( HelpPage, KeyboardModifiersPressed, - add_close_shortcut, add_ellipsis_to_action_label, current_window, ensure_editor_saved, @@ -1123,8 +1122,6 @@ class Browser(QMainWindow): dialog.setWindowTitle(tr.actions_grade_now()) layout = QHBoxLayout() dialog.setLayout(layout) - add_close_shortcut(dialog) - # Add grade buttons for ease, label in [ (1, tr.studying_again()), diff --git a/qt/aqt/browser/card_info.py b/qt/aqt/browser/card_info.py index c925d43bb..86eb9cc39 100644 --- a/qt/aqt/browser/card_info.py +++ b/qt/aqt/browser/card_info.py @@ -14,7 +14,6 @@ from anki.errors import NotFoundError from anki.lang import without_unicode_isolation from aqt.qt import * from aqt.utils import ( - add_close_shortcut, disable_help_button, qconnect, restoreGeom, @@ -54,7 +53,6 @@ class CardInfoDialog(QDialog): self.setMinimumSize(400, 300) disable_help_button(self) restoreGeom(self, self.GEOMETRY_KEY, default_size=(800, 800)) - add_close_shortcut(self) setWindowIcon(self) self.web: AnkiWebView | None = AnkiWebView( diff --git a/qt/aqt/changenotetype.py b/qt/aqt/changenotetype.py index 962f49280..70567a2e9 100644 --- a/qt/aqt/changenotetype.py +++ b/qt/aqt/changenotetype.py @@ -15,7 +15,6 @@ from anki.notes import NoteId from aqt.operations.notetype import change_notetype_of_notes from aqt.qt import * from aqt.utils import ( - add_close_shortcut, disable_help_button, restoreGeom, saveGeom, @@ -49,7 +48,6 @@ class ChangeNotetypeDialog(QDialog): self.setMinimumSize(400, 300) disable_help_button(self) restoreGeom(self, self.TITLE, default_size=(800, 800)) - add_close_shortcut(self) self.web = AnkiWebView(kind=AnkiWebViewKind.CHANGE_NOTETYPE) self.web.setVisible(False) diff --git a/qt/aqt/clayout.py b/qt/aqt/clayout.py index aec5326f4..51f0941a8 100644 --- a/qt/aqt/clayout.py +++ b/qt/aqt/clayout.py @@ -27,7 +27,6 @@ from aqt.sound import av_player, play_clicked_audio from aqt.theme import theme_manager from aqt.utils import ( HelpPage, - add_close_shortcut, ask_user_dialog, askUser, disable_help_button, @@ -91,7 +90,6 @@ class CardLayout(QDialog): gui_hooks.card_layout_will_show(self) self.redraw_everything() restoreGeom(self, "CardLayout") - add_close_shortcut(self) restoreSplitter(self.mainArea, "CardLayoutMainArea") self.setWindowModality(Qt.WindowModality.ApplicationModal) self.show() diff --git a/qt/aqt/customstudy.py b/qt/aqt/customstudy.py index ce4e68a30..eb8921627 100644 --- a/qt/aqt/customstudy.py +++ b/qt/aqt/customstudy.py @@ -15,7 +15,7 @@ from aqt.operations import QueryOp from aqt.operations.scheduling import custom_study from aqt.qt import * from aqt.taglimit import TagLimit -from aqt.utils import add_close_shortcut, disable_help_button, tr +from aqt.utils import disable_help_button, tr RADIO_NEW = 1 RADIO_REV = 2 @@ -63,7 +63,6 @@ class CustomStudy(QDialog): self.form.setupUi(self) disable_help_button(self) self.setupSignals() - add_close_shortcut(self) self.form.radioNew.click() self.open() diff --git a/qt/aqt/deckdescription.py b/qt/aqt/deckdescription.py index e218c0a79..2720ebe51 100644 --- a/qt/aqt/deckdescription.py +++ b/qt/aqt/deckdescription.py @@ -10,7 +10,7 @@ from anki.decks import DeckDict from aqt.operations import QueryOp from aqt.operations.deck import update_deck_dict from aqt.qt import * -from aqt.utils import add_close_shortcut, disable_help_button, restoreGeom, saveGeom, tr +from aqt.utils import disable_help_button, restoreGeom, saveGeom, tr class DeckDescriptionDialog(QDialog): @@ -45,7 +45,6 @@ class DeckDescriptionDialog(QDialog): self.setMinimumWidth(400) disable_help_button(self) restoreGeom(self, self.TITLE) - add_close_shortcut(self) box = QVBoxLayout() diff --git a/qt/aqt/deckoptions.py b/qt/aqt/deckoptions.py index cb886a10c..b252b3863 100644 --- a/qt/aqt/deckoptions.py +++ b/qt/aqt/deckoptions.py @@ -13,7 +13,6 @@ from aqt import gui_hooks from aqt.qt import * from aqt.utils import ( KeyboardModifiersPressed, - add_close_shortcut, disable_help_button, restoreGeom, saveGeom, @@ -42,7 +41,6 @@ class DeckOptionsDialog(QDialog): self.setMinimumWidth(400) disable_help_button(self) restoreGeom(self, self.TITLE, default_size=(800, 800)) - add_close_shortcut(self) self.web = AnkiWebView(kind=AnkiWebViewKind.DECK_OPTIONS) self.web.load_sveltekit_page(f"deck-options/{self._deck['id']}") diff --git a/qt/aqt/editcurrent.py b/qt/aqt/editcurrent.py index 6ec4938b7..f52e1439c 100644 --- a/qt/aqt/editcurrent.py +++ b/qt/aqt/editcurrent.py @@ -9,7 +9,7 @@ from anki.collection import OpChanges from anki.errors import NotFoundError from aqt import gui_hooks from aqt.qt import * -from aqt.utils import add_close_shortcut, restoreGeom, saveGeom, tr +from aqt.utils import restoreGeom, saveGeom, tr class EditCurrent(QMainWindow): @@ -36,7 +36,6 @@ class EditCurrent(QMainWindow): close_button = self.form.buttonBox.button(QDialogButtonBox.StandardButton.Close) assert close_button is not None close_button.setShortcut(QKeySequence("Ctrl+Return")) - add_close_shortcut(self) # qt5.14+ doesn't handle numpad enter on Windows self.compat_add_shorcut = QShortcut(QKeySequence("Ctrl+Enter"), self) qconnect(self.compat_add_shorcut.activated, close_button.click) diff --git a/qt/aqt/exporting.py b/qt/aqt/exporting.py index cadbaef0c..5b8f0183f 100644 --- a/qt/aqt/exporting.py +++ b/qt/aqt/exporting.py @@ -19,7 +19,6 @@ from aqt import gui_hooks from aqt.errors import show_exception from aqt.qt import * from aqt.utils import ( - add_close_shortcut, checkInvalidFilename, disable_help_button, getSaveFile, @@ -47,7 +46,6 @@ class ExportDialog(QDialog): self.cids = cids disable_help_button(self) self.setup(did) - add_close_shortcut(self) self.exec() def setup(self, did: DeckId | None) -> None: diff --git a/qt/aqt/fields.py b/qt/aqt/fields.py index 16a3b2779..69e38f446 100644 --- a/qt/aqt/fields.py +++ b/qt/aqt/fields.py @@ -15,7 +15,6 @@ from aqt.qt import * from aqt.schema_change_tracker import ChangeTracker from aqt.utils import ( HelpPage, - add_close_shortcut, askUser, disable_help_button, getOnlyText, @@ -51,7 +50,6 @@ class FieldDialog(QDialog): without_unicode_isolation(tr.fields_fields_for(val=self.model["name"])) ) - add_close_shortcut(self) disable_help_button(self) help_button = self.form.buttonBox.button(QDialogButtonBox.StandardButton.Help) assert help_button is not None diff --git a/qt/aqt/import_export/exporting.py b/qt/aqt/import_export/exporting.py index c6532ba02..4903faea3 100644 --- a/qt/aqt/import_export/exporting.py +++ b/qt/aqt/import_export/exporting.py @@ -27,7 +27,6 @@ from aqt.operations import QueryOp from aqt.progress import ProgressUpdate from aqt.qt import * from aqt.utils import ( - add_close_shortcut, checkInvalidFilename, disable_help_button, getSaveFile, @@ -54,7 +53,6 @@ class ExportDialog(QDialog): self.nids = nids disable_help_button(self) self.setup(did) - add_close_shortcut(self) self.open() def setup(self, did: DeckId | None) -> None: diff --git a/qt/aqt/import_export/import_dialog.py b/qt/aqt/import_export/import_dialog.py index 0726d64e8..cc604ea99 100644 --- a/qt/aqt/import_export/import_dialog.py +++ b/qt/aqt/import_export/import_dialog.py @@ -12,7 +12,7 @@ import aqt.deckconf import aqt.main import aqt.operations from aqt.qt import * -from aqt.utils import add_close_shortcut, disable_help_button, restoreGeom, saveGeom, tr +from aqt.utils import disable_help_button, restoreGeom, saveGeom, tr from aqt.webview import AnkiWebView, AnkiWebViewKind @@ -62,7 +62,6 @@ class ImportDialog(QDialog): self.setMinimumSize(*self.MIN_SIZE) disable_help_button(self) restoreGeom(self, self.args.title, default_size=self.DEFAULT_SIZE) - add_close_shortcut(self) self.web: AnkiWebView | None = AnkiWebView(kind=self.args.kind) self.web.setVisible(False) diff --git a/qt/aqt/operations/scheduling.py b/qt/aqt/operations/scheduling.py index 399db76b1..958448388 100644 --- a/qt/aqt/operations/scheduling.py +++ b/qt/aqt/operations/scheduling.py @@ -24,7 +24,7 @@ from anki.scheduler.v3 import CardAnswer from anki.scheduler.v3 import Scheduler as V3Scheduler from aqt.operations import CollectionOp from aqt.qt import * -from aqt.utils import add_close_shortcut, disable_help_button, getText, tooltip, tr +from aqt.utils import disable_help_button, getText, tooltip, tr def set_due_date_dialog( @@ -104,7 +104,6 @@ def forget_cards( dialog = QDialog(parent) disable_help_button(dialog) - add_close_shortcut(dialog) form = aqt.forms.forget.Ui_Dialog() form.setupUi(dialog) @@ -154,7 +153,6 @@ def reposition_new_cards_dialog( dialog = QDialog(parent) disable_help_button(dialog) - add_close_shortcut(dialog) dialog.setWindowModality(Qt.WindowModality.WindowModal) form = aqt.forms.reposition.Ui_Dialog() form.setupUi(dialog) diff --git a/qt/aqt/preferences.py b/qt/aqt/preferences.py index 939dd8c2c..23ee963b2 100644 --- a/qt/aqt/preferences.py +++ b/qt/aqt/preferences.py @@ -23,7 +23,6 @@ from aqt.theme import Theme from aqt.url_schemes import show_url_schemes_dialog from aqt.utils import ( HelpPage, - add_close_shortcut, add_ellipsis_to_action_label, askUser, disable_help_button, @@ -67,7 +66,6 @@ class Preferences(QDialog): self.setup_profile() self.setup_global() self.setup_configurable_answer_keys() - add_close_shortcut(self) self.show() def setup_configurable_answer_keys(self): diff --git a/qt/aqt/stats.py b/qt/aqt/stats.py index 0b8a8a3ee..77ea18470 100644 --- a/qt/aqt/stats.py +++ b/qt/aqt/stats.py @@ -16,7 +16,6 @@ from aqt.operations.deck import set_current_deck from aqt.qt import * from aqt.theme import theme_manager from aqt.utils import ( - add_close_shortcut, disable_help_button, getSaveFile, maybeHideClose, @@ -69,7 +68,6 @@ class NewDeckStats(QDialog): assert b is not None b.setAutoDefault(False) maybeHideClose(self.form.buttonBox) - add_close_shortcut(self) gui_hooks.stats_dialog_will_show(self) self.form.web.hide_while_preserving_layout() self.show() @@ -182,7 +180,6 @@ class DeckStats(QDialog): qconnect(f.year.clicked, lambda: self.changePeriod(1)) qconnect(f.life.clicked, lambda: self.changePeriod(2)) maybeHideClose(self.form.buttonBox) - add_close_shortcut(self) gui_hooks.stats_dialog_old_will_show(self) self.show() self.refresh() diff --git a/qt/aqt/studydeck.py b/qt/aqt/studydeck.py index 58d205c18..d1c0a5c47 100644 --- a/qt/aqt/studydeck.py +++ b/qt/aqt/studydeck.py @@ -16,7 +16,6 @@ from aqt.qt import * from aqt.utils import ( HelpPage, HelpPageArgument, - add_close_shortcut, disable_help_button, openHelp, restoreGeom, @@ -53,7 +52,6 @@ class StudyDeck(QDialog): gui_hooks.state_did_reset.append(self.onReset) self.geomKey = f"studyDeck-{geomKey}" restoreGeom(self, self.geomKey) - add_close_shortcut(self) disable_help_button(self) if not cancel: self.form.buttonBox.removeButton( diff --git a/qt/aqt/sync.py b/qt/aqt/sync.py index 75bdeca89..050f457dd 100644 --- a/qt/aqt/sync.py +++ b/qt/aqt/sync.py @@ -28,7 +28,6 @@ from aqt.qt import ( qconnect, ) from aqt.utils import ( - add_close_shortcut, ask_user_dialog, disable_help_button, show_warning, @@ -391,7 +390,6 @@ def get_id_and_pass_from_user( qconnect(bb.accepted, diag.accept) qconnect(bb.rejected, diag.reject) vbox.addWidget(bb) - add_close_shortcut(diag) diag.setLayout(vbox) diag.adjustSize() diag.show() diff --git a/qt/aqt/utils.py b/qt/aqt/utils.py index ae88dadcb..3327d70cc 100644 --- a/qt/aqt/utils.py +++ b/qt/aqt/utils.py @@ -567,7 +567,6 @@ def getText( d = GetTextDialog( parent, prompt, help=help, edit=edit, default=default, title=title, **kwargs ) - add_close_shortcut(d) d.setWindowModality(Qt.WindowModality.WindowModal) if geomKey: restoreGeom(d, geomKey) From 815c8a4d08e71772577fa81d8072ed69f7292772 Mon Sep 17 00:00:00 2001 From: beyondcompute Date: Mon, 22 Dec 2025 06:35:25 +0200 Subject: [PATCH 3/3] Remove `add_close_shortcut` method from Utils --- qt/aqt/utils.py | 9 --------- 1 file changed, 9 deletions(-) diff --git a/qt/aqt/utils.py b/qt/aqt/utils.py index 3327d70cc..58dc81825 100644 --- a/qt/aqt/utils.py +++ b/qt/aqt/utils.py @@ -45,7 +45,6 @@ from aqt.qt import ( QFrame, QHeaderView, QIcon, - QKeySequence, QLabel, QLineEdit, QListWidget, @@ -61,7 +60,6 @@ from aqt.qt import ( QPlainTextEdit, QPoint, QPushButton, - QShortcut, QSize, QSplitter, QStandardPaths, @@ -1020,13 +1018,6 @@ def maybeHideClose(bbox: QDialogButtonBox) -> None: bbox.removeButton(b) -def add_close_shortcut(widg: QWidget) -> None: - if not is_mac: - return - shortcut = QShortcut(QKeySequence("Ctrl+W"), widg) - qconnect(shortcut.activated, widg.close) - - def downArrow() -> str: if is_win: return "▼"