From 68c5fd50f9924d6b00b219ff8e88562d5c4d9148 Mon Sep 17 00:00:00 2001 From: Glutanimate Date: Fri, 24 Jan 2020 15:36:05 +0100 Subject: [PATCH 1/2] Equip Reviewer._showAnswer with hooks covering common add-on usages --- qt/aqt/gui_hooks.py | 73 ++++++++++++++++++++++++++++++++++++++++ qt/aqt/reviewer.py | 6 ++++ qt/tools/genhooks_gui.py | 21 ++++++++++++ 3 files changed, 100 insertions(+) diff --git a/qt/aqt/gui_hooks.py b/qt/aqt/gui_hooks.py index ed1648373..3ef1709f9 100644 --- a/qt/aqt/gui_hooks.py +++ b/qt/aqt/gui_hooks.py @@ -650,6 +650,30 @@ class _ReviewDidUndoHook: review_did_undo = _ReviewDidUndoHook() +class _ReviewerDidAnswerCardHook: + _hooks: List[Callable[["aqt.reviewer.Reviewer", Card, int], None]] = [] + + def append(self, cb: Callable[["aqt.reviewer.Reviewer", Card, int], None]) -> None: + """(reviewer: aqt.reviewer.Reviewer, card: Card, ease: int)""" + self._hooks.append(cb) + + def remove(self, cb: Callable[["aqt.reviewer.Reviewer", Card, int], None]) -> None: + if cb in self._hooks: + self._hooks.remove(cb) + + def __call__(self, reviewer: aqt.reviewer.Reviewer, card: Card, ease: int) -> None: + for hook in self._hooks: + try: + hook(reviewer, card, ease) + except: + # if the hook fails, remove it + self._hooks.remove(hook) + raise + + +reviewer_did_answer_card = _ReviewerDidAnswerCardHook() + + class _ReviewerDidShowAnswerHook: _hooks: List[Callable[[Card], None]] = [] @@ -702,6 +726,55 @@ class _ReviewerDidShowQuestionHook: reviewer_did_show_question = _ReviewerDidShowQuestionHook() +class _ReviewerWillAnswerCardFilter: + """Used to modify the ease at which a card is rated or to bypass + rating the card completely. + + ease_tuple is a tuple consisting of a boolean expressing whether the reviewer + should continue with rating the card, and an integer expressing the ease at + which the card should be rated. + + If your code just needs to be notified of the card rating event, you should use + the reviewer_did_answer_card hook instead.""" + + _hooks: List[ + Callable[[Tuple[bool, int], "aqt.reviewer.Reviewer", Card], Tuple[bool, int]] + ] = [] + + def append( + self, + cb: Callable[ + [Tuple[bool, int], "aqt.reviewer.Reviewer", Card], Tuple[bool, int] + ], + ) -> None: + """(ease_tuple: Tuple[bool, int], reviewer: aqt.reviewer.Reviewer, card: Card)""" + self._hooks.append(cb) + + def remove( + self, + cb: Callable[ + [Tuple[bool, int], "aqt.reviewer.Reviewer", Card], Tuple[bool, int] + ], + ) -> None: + if cb in self._hooks: + self._hooks.remove(cb) + + def __call__( + self, ease_tuple: Tuple[bool, int], reviewer: aqt.reviewer.Reviewer, card: Card + ) -> Tuple[bool, int]: + for filter in self._hooks: + try: + ease_tuple = filter(ease_tuple, reviewer, card) + except: + # if the hook fails, remove it + self._hooks.remove(filter) + raise + return ease_tuple + + +reviewer_will_answer_card = _ReviewerWillAnswerCardFilter() + + class _ReviewerWillEndHook: """Called before Anki transitions from the review screen to another screen.""" diff --git a/qt/aqt/reviewer.py b/qt/aqt/reviewer.py index 163f6dd89..426296f2b 100644 --- a/qt/aqt/reviewer.py +++ b/qt/aqt/reviewer.py @@ -242,7 +242,13 @@ The front of this card is empty. Please run Tools>Empty Cards.""" return if self.mw.col.sched.answerButtons(self.card) < ease: return + proceed, ease = gui_hooks.reviewer_will_answer_card( + (True, ease), self, self.card + ) + if not proceed: + return self.mw.col.sched.answerCard(self.card, ease) + gui_hooks.reviewer_did_answer_card(self, self.card, ease) self._answeredIds.append(self.card.id) self.mw.autosave() self.nextCard() diff --git a/qt/tools/genhooks_gui.py b/qt/tools/genhooks_gui.py index 679b71efd..d37f08b19 100644 --- a/qt/tools/genhooks_gui.py +++ b/qt/tools/genhooks_gui.py @@ -31,6 +31,27 @@ hooks = [ legacy_hook="showAnswer", legacy_no_args=True, ), + Hook( + name="reviewer_will_answer_card", + args=[ + "ease_tuple: Tuple[bool, int]", + "reviewer: aqt.reviewer.Reviewer", "card: Card", + ], + return_type="Tuple[bool, int]", + doc="""Used to modify the ease at which a card is rated or to bypass + rating the card completely. + + ease_tuple is a tuple consisting of a boolean expressing whether the reviewer + should continue with rating the card, and an integer expressing the ease at + which the card should be rated. + + If your code just needs to be notified of the card rating event, you should use + the reviewer_did_answer_card hook instead.""", + ), + Hook( + name="reviewer_did_answer_card", + args=["reviewer: aqt.reviewer.Reviewer", "card: Card", "ease: int"], + ), Hook( name="reviewer_will_show_context_menu", args=["reviewer: aqt.reviewer.Reviewer", "menu: QMenu"], From 44c09db4507538fab78a0f2ef313a914131b05ba Mon Sep 17 00:00:00 2001 From: Glutanimate Date: Fri, 24 Jan 2020 15:48:05 +0100 Subject: [PATCH 2/2] Fix formatting error --- qt/tools/genhooks_gui.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/qt/tools/genhooks_gui.py b/qt/tools/genhooks_gui.py index d37f08b19..c3ebf0272 100644 --- a/qt/tools/genhooks_gui.py +++ b/qt/tools/genhooks_gui.py @@ -35,7 +35,8 @@ hooks = [ name="reviewer_will_answer_card", args=[ "ease_tuple: Tuple[bool, int]", - "reviewer: aqt.reviewer.Reviewer", "card: Card", + "reviewer: aqt.reviewer.Reviewer", + "card: Card", ], return_type="Tuple[bool, int]", doc="""Used to modify the ease at which a card is rated or to bypass