diff --git a/qt/aqt/gui_hooks.py b/qt/aqt/gui_hooks.py index 535f3c595..980742c29 100644 --- a/qt/aqt/gui_hooks.py +++ b/qt/aqt/gui_hooks.py @@ -2213,6 +2213,69 @@ class _ReviewerWillEndHook: reviewer_will_end = _ReviewerWillEndHook() +class _ReviewerWillInitAnswerButtonsFilter: + """Used to modify list of answer buttons + + buttons_tuple is a tuple of buttons, with each button represented by a + tuple containing an int for the button's ease and a string for the + button's label. + + Return a tuple of the form ((int, str), ...), e.g.: + ((1, "Label1"), (2, "Label2"), ...) + + Note: import _ from anki.lang to support translation, using, e.g., + ((1, _("Label1")), ...) + """ + + _hooks: List[ + Callable[ + ["Tuple[Tuple[int, str], ...]", "aqt.reviewer.Reviewer", Card], + Tuple[Tuple[int, str], ...], + ] + ] = [] + + def append( + self, + cb: Callable[ + ["Tuple[Tuple[int, str], ...]", "aqt.reviewer.Reviewer", Card], + Tuple[Tuple[int, str], ...], + ], + ) -> None: + """(buttons_tuple: Tuple[Tuple[int, str], ...], reviewer: aqt.reviewer.Reviewer, card: Card)""" + self._hooks.append(cb) + + def remove( + self, + cb: Callable[ + ["Tuple[Tuple[int, str], ...]", "aqt.reviewer.Reviewer", Card], + Tuple[Tuple[int, str], ...], + ], + ) -> None: + if cb in self._hooks: + self._hooks.remove(cb) + + def count(self) -> int: + return len(self._hooks) + + def __call__( + self, + buttons_tuple: Tuple[Tuple[int, str], ...], + reviewer: aqt.reviewer.Reviewer, + card: Card, + ) -> Tuple[Tuple[int, str], ...]: + for filter in self._hooks: + try: + buttons_tuple = filter(buttons_tuple, reviewer, card) + except: + # if the hook fails, remove it + self._hooks.remove(filter) + raise + return buttons_tuple + + +reviewer_will_init_answer_buttons = _ReviewerWillInitAnswerButtonsFilter() + + class _ReviewerWillPlayAnswerSoundsHook: """Called before showing the answer/back side. diff --git a/qt/aqt/reviewer.py b/qt/aqt/reviewer.py index 01d847afe..4560f51e5 100644 --- a/qt/aqt/reviewer.py +++ b/qt/aqt/reviewer.py @@ -621,15 +621,16 @@ time = %(time)d; else: return 2 - def _answerButtonList(self) -> Sequence[Tuple[int, str]]: - l = ((1, _("Again")),) - cnt = self.mw.col.sched.answerButtons(self.card) - if cnt == 2: - return l + ((2, _("Good")),) - elif cnt == 3: - return l + ((2, _("Good")), (3, _("Easy"))) + def _answerButtonList(self) -> Tuple[Tuple[int, str], ...]: + button_count = self.mw.col.sched.answerButtons(self.card) + if button_count == 2: + buttons_tuple: Tuple[Tuple[int, str], ...] = ((1, _("Again")), (2, _("Good")),) + elif button_count == 3: + buttons_tuple = ((1, _("Again")), (2, _("Good")), (3, _("Easy"))) else: - return l + ((2, _("Hard")), (3, _("Good")), (4, _("Easy"))) + buttons_tuple = ((1, _("Again")), (2, _("Hard")), (3, _("Good")), (4, _("Easy"))) + buttons_tuple = gui_hooks.reviewer_will_init_answer_buttons(buttons_tuple, self, self.card) + return buttons_tuple def _answerButtons(self) -> str: default = self._defaultEase() diff --git a/qt/tools/genhooks_gui.py b/qt/tools/genhooks_gui.py index ca00e4578..ec67df847 100644 --- a/qt/tools/genhooks_gui.py +++ b/qt/tools/genhooks_gui.py @@ -58,6 +58,27 @@ hooks = [ legacy_hook="showAnswer", legacy_no_args=True, ), + Hook( + name="reviewer_will_init_answer_buttons", + args=[ + "buttons_tuple: Tuple[Tuple[int, str], ...]", + "reviewer: aqt.reviewer.Reviewer", + "card: Card", + ], + return_type="Tuple[Tuple[int, str], ...]", + doc="""Used to modify list of answer buttons + + buttons_tuple is a tuple of buttons, with each button represented by a + tuple containing an int for the button's ease and a string for the + button's label. + + Return a tuple of the form ((int, str), ...), e.g.: + ((1, "Label1"), (2, "Label2"), ...) + + Note: import _ from anki.lang to support translation, using, e.g., + ((1, _("Label1")), ...) + """, + ), Hook( name="reviewer_will_answer_card", args=[