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"],