From 335047187a7a787dc7816c51dfb0077681abebce Mon Sep 17 00:00:00 2001 From: Glutanimate Date: Mon, 24 Feb 2020 13:42:30 +0100 Subject: [PATCH 1/3] Add hooks for extending the deck options dialog Introduces three new hooks: * deck_conf_will_show: Allows adding or modifying widgets * deck_conf_did_load_config: Allows add-on widgets to read from config * deck_conf_will_save_config: Allows add-on widgets to write to config --- qt/aqt/deckconf.py | 4 ++ qt/aqt/gui_hooks.py | 80 ++++++++++++++++++++++++++++++++++++++++ qt/tools/genhooks_gui.py | 17 +++++++++ 3 files changed, 101 insertions(+) diff --git a/qt/aqt/deckconf.py b/qt/aqt/deckconf.py index 629ef354f..bacfd816e 100644 --- a/qt/aqt/deckconf.py +++ b/qt/aqt/deckconf.py @@ -6,6 +6,7 @@ from operator import itemgetter import aqt from anki.consts import NEW_CARDS_RANDOM from anki.lang import _, ngettext +from aqt import gui_hooks from aqt.qt import * from aqt.utils import ( askUser, @@ -40,6 +41,7 @@ class DeckConf(QDialog): self.setWindowTitle(_("Options for %s") % self.deck["name"]) # qt doesn't size properly with altered fonts otherwise restoreGeom(self, "deckconf", adjustSize=True) + gui_hooks.deck_conf_will_show(self) self.show() self.exec_() saveGeom(self, "deckconf") @@ -218,6 +220,7 @@ class DeckConf(QDialog): f.replayQuestion.setChecked(c.get("replayq", True)) # description f.desc.setPlainText(self.deck["desc"]) + gui_hooks.deck_conf_did_load_config(self, self.conf) def onRestore(self): self.mw.progress.start() @@ -301,6 +304,7 @@ class DeckConf(QDialog): c["replayq"] = f.replayQuestion.isChecked() # description self.deck["desc"] = f.desc.toPlainText() + gui_hooks.deck_conf_will_save_config(self, self.deck, self.conf) self.mw.col.decks.save(self.deck) self.mw.col.decks.save(self.conf) diff --git a/qt/aqt/gui_hooks.py b/qt/aqt/gui_hooks.py index bc6dd98e7..80c7b4630 100644 --- a/qt/aqt/gui_hooks.py +++ b/qt/aqt/gui_hooks.py @@ -518,6 +518,86 @@ class _DeckBrowserWillShowOptionsMenuHook: deck_browser_will_show_options_menu = _DeckBrowserWillShowOptionsMenuHook() +class _DeckConfDidLoadConfigHook: + """Called once widget state has been set from deck config""" + + _hooks: List[Callable[["aqt.deckconf.DeckConf", Any], None]] = [] + + def append(self, cb: Callable[["aqt.deckconf.DeckConf", Any], None]) -> None: + """(deck_conf: aqt.deckconf.DeckConf, config: Any)""" + self._hooks.append(cb) + + def remove(self, cb: Callable[["aqt.deckconf.DeckConf", Any], None]) -> None: + if cb in self._hooks: + self._hooks.remove(cb) + + def __call__(self, deck_conf: aqt.deckconf.DeckConf, config: Any) -> None: + for hook in self._hooks: + try: + hook(deck_conf, config) + except: + # if the hook fails, remove it + self._hooks.remove(hook) + raise + + +deck_conf_did_load_config = _DeckConfDidLoadConfigHook() + + +class _DeckConfWillSaveConfigHook: + """Called before widget state is saved to config""" + + _hooks: List[Callable[["aqt.deckconf.DeckConf", Any, Any], None]] = [] + + def append(self, cb: Callable[["aqt.deckconf.DeckConf", Any, Any], None]) -> None: + """(deck_conf: aqt.deckconf.DeckConf, config: Any, deck: Any)""" + self._hooks.append(cb) + + def remove(self, cb: Callable[["aqt.deckconf.DeckConf", Any, Any], None]) -> None: + if cb in self._hooks: + self._hooks.remove(cb) + + def __call__( + self, deck_conf: aqt.deckconf.DeckConf, config: Any, deck: Any + ) -> None: + for hook in self._hooks: + try: + hook(deck_conf, config, deck) + except: + # if the hook fails, remove it + self._hooks.remove(hook) + raise + + +deck_conf_will_save_config = _DeckConfWillSaveConfigHook() + + +class _DeckConfWillShowHook: + """Allows modifying the deck options dialog before it is shown""" + + _hooks: List[Callable[["aqt.deckconf.DeckConf"], None]] = [] + + def append(self, cb: Callable[["aqt.deckconf.DeckConf"], None]) -> None: + """(deck_conf: aqt.deckconf.DeckConf)""" + self._hooks.append(cb) + + def remove(self, cb: Callable[["aqt.deckconf.DeckConf"], None]) -> None: + if cb in self._hooks: + self._hooks.remove(cb) + + def __call__(self, deck_conf: aqt.deckconf.DeckConf) -> None: + for hook in self._hooks: + try: + hook(deck_conf) + except: + # if the hook fails, remove it + self._hooks.remove(hook) + raise + + +deck_conf_will_show = _DeckConfWillShowHook() + + class _EditorDidFireTypingTimerHook: _hooks: List[Callable[["anki.notes.Note"], None]] = [] diff --git a/qt/tools/genhooks_gui.py b/qt/tools/genhooks_gui.py index 17e55ea25..0d475b53a 100644 --- a/qt/tools/genhooks_gui.py +++ b/qt/tools/genhooks_gui.py @@ -121,6 +121,23 @@ hooks = [ legacy_hook="prepareQA", doc="Can modify card text before review/preview.", ), + # Deck options + ################### + Hook( + name="deck_conf_will_show", + args=["deck_conf: aqt.deckconf.DeckConf"], + doc="Allows modifying the deck options dialog before it is shown", + ), + Hook( + name="deck_conf_did_load_config", + args=["deck_conf: aqt.deckconf.DeckConf", "config: Any"], + doc="Called once widget state has been set from deck config", + ), + Hook( + name="deck_conf_will_save_config", + args=["deck_conf: aqt.deckconf.DeckConf", "config: Any", "deck: Any"], + doc="Called before widget state is saved to config", + ), # Browser ################### Hook( From 7cc9311b79ec3ae7592902db4c5f9a76943cf353 Mon Sep 17 00:00:00 2001 From: Glutanimate Date: Mon, 24 Feb 2020 15:29:23 +0100 Subject: [PATCH 2/3] Add deck_conf_did_setup_ui_form hook Called earlier than deck_conf_will_show, allowing add-on authors to perform UI modifications before the deck config is loaded. --- qt/aqt/deckconf.py | 1 + qt/aqt/gui_hooks.py | 26 ++++++++++++++++++++++++++ qt/tools/genhooks_gui.py | 5 +++++ 3 files changed, 32 insertions(+) diff --git a/qt/aqt/deckconf.py b/qt/aqt/deckconf.py index bacfd816e..219632ebc 100644 --- a/qt/aqt/deckconf.py +++ b/qt/aqt/deckconf.py @@ -29,6 +29,7 @@ class DeckConf(QDialog): self._origNewOrder = None self.form = aqt.forms.dconf.Ui_Dialog() self.form.setupUi(self) + gui_hooks.deck_conf_did_setup_ui_form(self) self.mw.checkpoint(_("Options")) self.setupCombos() self.setupConfs() diff --git a/qt/aqt/gui_hooks.py b/qt/aqt/gui_hooks.py index 80c7b4630..f019a803f 100644 --- a/qt/aqt/gui_hooks.py +++ b/qt/aqt/gui_hooks.py @@ -544,6 +544,32 @@ class _DeckConfDidLoadConfigHook: deck_conf_did_load_config = _DeckConfDidLoadConfigHook() +class _DeckConfDidSetupUiFormHook: + """Allows modifying or adding widgets in the deck options UI form""" + + _hooks: List[Callable[["aqt.deckconf.DeckConf"], None]] = [] + + def append(self, cb: Callable[["aqt.deckconf.DeckConf"], None]) -> None: + """(deck_conf: aqt.deckconf.DeckConf)""" + self._hooks.append(cb) + + def remove(self, cb: Callable[["aqt.deckconf.DeckConf"], None]) -> None: + if cb in self._hooks: + self._hooks.remove(cb) + + def __call__(self, deck_conf: aqt.deckconf.DeckConf) -> None: + for hook in self._hooks: + try: + hook(deck_conf) + except: + # if the hook fails, remove it + self._hooks.remove(hook) + raise + + +deck_conf_did_setup_ui_form = _DeckConfDidSetupUiFormHook() + + class _DeckConfWillSaveConfigHook: """Called before widget state is saved to config""" diff --git a/qt/tools/genhooks_gui.py b/qt/tools/genhooks_gui.py index 0d475b53a..c7d959f51 100644 --- a/qt/tools/genhooks_gui.py +++ b/qt/tools/genhooks_gui.py @@ -123,6 +123,11 @@ hooks = [ ), # Deck options ################### + Hook( + name="deck_conf_did_setup_ui_form", + args=["deck_conf: aqt.deckconf.DeckConf"], + doc="Allows modifying or adding widgets in the deck options UI form", + ), Hook( name="deck_conf_will_show", args=["deck_conf: aqt.deckconf.DeckConf"], From 8454e27efb6d3180b983c84ed354f599c905a82d Mon Sep 17 00:00:00 2001 From: Glutanimate Date: Mon, 24 Feb 2020 15:47:48 +0100 Subject: [PATCH 3/3] Use a consistent function signature across load and save hooks --- qt/aqt/deckconf.py | 2 +- qt/aqt/gui_hooks.py | 20 +++++++++++--------- qt/tools/genhooks_gui.py | 4 ++-- 3 files changed, 14 insertions(+), 12 deletions(-) diff --git a/qt/aqt/deckconf.py b/qt/aqt/deckconf.py index 219632ebc..6527ff22b 100644 --- a/qt/aqt/deckconf.py +++ b/qt/aqt/deckconf.py @@ -221,7 +221,7 @@ class DeckConf(QDialog): f.replayQuestion.setChecked(c.get("replayq", True)) # description f.desc.setPlainText(self.deck["desc"]) - gui_hooks.deck_conf_did_load_config(self, self.conf) + gui_hooks.deck_conf_did_load_config(self, self.deck, self.conf) def onRestore(self): self.mw.progress.start() diff --git a/qt/aqt/gui_hooks.py b/qt/aqt/gui_hooks.py index f019a803f..de0ec671d 100644 --- a/qt/aqt/gui_hooks.py +++ b/qt/aqt/gui_hooks.py @@ -521,20 +521,22 @@ deck_browser_will_show_options_menu = _DeckBrowserWillShowOptionsMenuHook() class _DeckConfDidLoadConfigHook: """Called once widget state has been set from deck config""" - _hooks: List[Callable[["aqt.deckconf.DeckConf", Any], None]] = [] + _hooks: List[Callable[["aqt.deckconf.DeckConf", Any, Any], None]] = [] - def append(self, cb: Callable[["aqt.deckconf.DeckConf", Any], None]) -> None: - """(deck_conf: aqt.deckconf.DeckConf, config: Any)""" + def append(self, cb: Callable[["aqt.deckconf.DeckConf", Any, Any], None]) -> None: + """(deck_conf: aqt.deckconf.DeckConf, deck: Any, config: Any)""" self._hooks.append(cb) - def remove(self, cb: Callable[["aqt.deckconf.DeckConf", Any], None]) -> None: + def remove(self, cb: Callable[["aqt.deckconf.DeckConf", Any, Any], None]) -> None: if cb in self._hooks: self._hooks.remove(cb) - def __call__(self, deck_conf: aqt.deckconf.DeckConf, config: Any) -> None: + def __call__( + self, deck_conf: aqt.deckconf.DeckConf, deck: Any, config: Any + ) -> None: for hook in self._hooks: try: - hook(deck_conf, config) + hook(deck_conf, deck, config) except: # if the hook fails, remove it self._hooks.remove(hook) @@ -576,7 +578,7 @@ class _DeckConfWillSaveConfigHook: _hooks: List[Callable[["aqt.deckconf.DeckConf", Any, Any], None]] = [] def append(self, cb: Callable[["aqt.deckconf.DeckConf", Any, Any], None]) -> None: - """(deck_conf: aqt.deckconf.DeckConf, config: Any, deck: Any)""" + """(deck_conf: aqt.deckconf.DeckConf, deck: Any, config: Any)""" self._hooks.append(cb) def remove(self, cb: Callable[["aqt.deckconf.DeckConf", Any, Any], None]) -> None: @@ -584,11 +586,11 @@ class _DeckConfWillSaveConfigHook: self._hooks.remove(cb) def __call__( - self, deck_conf: aqt.deckconf.DeckConf, config: Any, deck: Any + self, deck_conf: aqt.deckconf.DeckConf, deck: Any, config: Any ) -> None: for hook in self._hooks: try: - hook(deck_conf, config, deck) + hook(deck_conf, deck, config) except: # if the hook fails, remove it self._hooks.remove(hook) diff --git a/qt/tools/genhooks_gui.py b/qt/tools/genhooks_gui.py index c7d959f51..b92d0a969 100644 --- a/qt/tools/genhooks_gui.py +++ b/qt/tools/genhooks_gui.py @@ -135,12 +135,12 @@ hooks = [ ), Hook( name="deck_conf_did_load_config", - args=["deck_conf: aqt.deckconf.DeckConf", "config: Any"], + args=["deck_conf: aqt.deckconf.DeckConf", "deck: Any", "config: Any"], doc="Called once widget state has been set from deck config", ), Hook( name="deck_conf_will_save_config", - args=["deck_conf: aqt.deckconf.DeckConf", "config: Any", "deck: Any"], + args=["deck_conf: aqt.deckconf.DeckConf", "deck: Any", "config: Any"], doc="Called before widget state is saved to config", ), # Browser