From 7be08919e28f79533defb4b70fe412bfc671de21 Mon Sep 17 00:00:00 2001 From: Thomas B Date: Fri, 14 Aug 2020 13:52:20 -0400 Subject: [PATCH 1/9] Add hook for initializing answer buttons --- qt/aqt/gui_hooks.py | 46 ++++++++++++++++++++++++++++++++++++++++ qt/aqt/reviewer.py | 18 ++++++++++------ qt/tools/genhooks_gui.py | 11 ++++++++++ 3 files changed, 68 insertions(+), 7 deletions(-) diff --git a/qt/aqt/gui_hooks.py b/qt/aqt/gui_hooks.py index 71aec7506..b66efa6ea 100644 --- a/qt/aqt/gui_hooks.py +++ b/qt/aqt/gui_hooks.py @@ -2079,6 +2079,52 @@ class _ReviewerDidShowQuestionHook: reviewer_did_show_question = _ReviewerDidShowQuestionHook() +class _ReviewerWillInitAnswerButtonsFilter: + """Used to modify the answer buttons shown for a card. + """ + + _hooks: List[ + Callable["aqt.reviewer.Reviewer", Card], Tuple[Tuple[int, str]]] + ] = [] + + def append( + self, + cb: Callable[ + "aqt.reviewer.Reviewer", Card], Tuple[Tuple[int, str]]] + ], + ) -> None: + """(reviewer: aqt.reviewer.Reviewer, card: Card)""" + self._hooks.append(cb) + + def remove( + self, + cb: Callable[ + "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, reviewer: aqt.reviewer.Reviewer, card: Card + ) -> Tuple[Tuple[int, str]]: + buttons_tuple = None + for filter in self._hooks: + try: + buttons_tuple = filter(reviewer, card) + except: + # if the hook fails, remove it + self._hooks.remove(filter) + raise + return buttons_tuple + + +reviewer_will_init_answer_buttons = _ReviewerWillInitAnswerButtonsFilter() + + class _ReviewerWillAnswerCardFilter: """Used to modify the ease at which a card is rated or to bypass rating the card completely. diff --git a/qt/aqt/reviewer.py b/qt/aqt/reviewer.py index 01d847afe..f39feb97d 100644 --- a/qt/aqt/reviewer.py +++ b/qt/aqt/reviewer.py @@ -622,14 +622,18 @@ time = %(time)d; 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"))) + buttons_tuple = gui_hooks.reviewer_will_init_answer_buttons(self, self.card) + if buttons_tuple is not None: + return buttons_tuple else: - return l + ((2, _("Hard")), (3, _("Good")), (4, _("Easy"))) + 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"))) + else: + return l + ((2, _("Hard")), (3, _("Good")), (4, _("Easy"))) def _answerButtons(self) -> str: default = self._defaultEase() diff --git a/qt/tools/genhooks_gui.py b/qt/tools/genhooks_gui.py index a9d5b1096..118e783bf 100644 --- a/qt/tools/genhooks_gui.py +++ b/qt/tools/genhooks_gui.py @@ -58,6 +58,17 @@ hooks = [ legacy_hook="showAnswer", legacy_no_args=True, ), + Hook( + name="reviewer_will_init_answer_buttons", + args=["reviewer: aqt.reviewer.Reviewer", "card: Card"], + doc="""Used to modify list of answer buttons + + Return a tuple of the form ((1, "Label1"), (2, "Label2")) + + import _ from anki.lang to support translation, as + ((1, _("Label1)), ...) + """, + ), Hook( name="reviewer_will_answer_card", args=[ From e92b0f8d63242f32477a52f087cdb263811f5b24 Mon Sep 17 00:00:00 2001 From: Thomas B Date: Tue, 18 Aug 2020 10:37:45 -0400 Subject: [PATCH 2/9] Bugfix proposed filter post-testing Mirrored filter more closely on _ReviewerWillAnswerCardFilter, including taking and returning the value to be modified. --- qt/aqt/gui_hooks.py | 105 +++++++++++++++++++++++---------------- qt/aqt/reviewer.py | 21 ++++---- qt/tools/genhooks_gui.py | 16 ++++-- 3 files changed, 82 insertions(+), 60 deletions(-) diff --git a/qt/aqt/gui_hooks.py b/qt/aqt/gui_hooks.py index b66efa6ea..a2eabf8b0 100644 --- a/qt/aqt/gui_hooks.py +++ b/qt/aqt/gui_hooks.py @@ -2081,50 +2081,6 @@ reviewer_did_show_question = _ReviewerDidShowQuestionHook() class _ReviewerWillInitAnswerButtonsFilter: """Used to modify the answer buttons shown for a card. - """ - - _hooks: List[ - Callable["aqt.reviewer.Reviewer", Card], Tuple[Tuple[int, str]]] - ] = [] - - def append( - self, - cb: Callable[ - "aqt.reviewer.Reviewer", Card], Tuple[Tuple[int, str]]] - ], - ) -> None: - """(reviewer: aqt.reviewer.Reviewer, card: Card)""" - self._hooks.append(cb) - - def remove( - self, - cb: Callable[ - "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, reviewer: aqt.reviewer.Reviewer, card: Card - ) -> Tuple[Tuple[int, str]]: - buttons_tuple = None - for filter in self._hooks: - try: - buttons_tuple = filter(reviewer, card) - except: - # if the hook fails, remove it - self._hooks.remove(filter) - raise - return buttons_tuple - - -reviewer_will_init_answer_buttons = _ReviewerWillInitAnswerButtonsFilter() - - class _ReviewerWillAnswerCardFilter: """Used to modify the ease at which a card is rated or to bypass rating the card completely. @@ -2208,6 +2164,67 @@ 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 number and a string for the button's label. + + Return a tuple of the form ((1, "Label1"), (2, "Label2"), ...) + + Note: import _ from anki.lang to support automatic translation, using, e.g., + ((1, _("Label1")), ...) + """ + + _hooks: List[ + Callable[ + ["Optional[Tuple[Tuple[int, str], ...]]", "aqt.reviewer.Reviewer", Card], + Tuple[Tuple[int, str], ...], + ] + ] = [] + + def append( + self, + cb: Callable[ + ["Optional[Tuple[Tuple[int, str], ...]]", "aqt.reviewer.Reviewer", Card], + Tuple[Tuple[int, str], ...], + ], + ) -> None: + """(buttons_tuple: Optional[Tuple[Tuple[int, str], ...]], reviewer: aqt.reviewer.Reviewer, card: Card)""" + self._hooks.append(cb) + + def remove( + self, + cb: Callable[ + ["Optional[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: Optional[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 f39feb97d..d30436de8 100644 --- a/qt/aqt/reviewer.py +++ b/qt/aqt/reviewer.py @@ -621,19 +621,16 @@ time = %(time)d; else: return 2 - def _answerButtonList(self) -> Sequence[Tuple[int, str]]: - buttons_tuple = gui_hooks.reviewer_will_init_answer_buttons(self, self.card) - if buttons_tuple is not None: - return buttons_tuple + def _answerButtonList(self) -> Sequence[Tuple[int, str], ...]: + button_count = self.mw.col.sched.answerButtons(self.card) + if button_count == 2: + buttons_tuple = ((1, _("Again")), (2, _("Good")),) + elif button_count == 3: + buttons_tuple = ((1, _("Again")), (2, _("Good")), (3, _("Easy"))) else: - 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"))) - 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 118e783bf..bf84ce664 100644 --- a/qt/tools/genhooks_gui.py +++ b/qt/tools/genhooks_gui.py @@ -60,13 +60,21 @@ hooks = [ ), Hook( name="reviewer_will_init_answer_buttons", - args=["reviewer: aqt.reviewer.Reviewer", "card: Card"], + args=[ + "buttons_tuple: Optional[Tuple[Tuple[int, str], ...]]", + "reviewer: aqt.reviewer.Reviewer", + "card: Card", + ], + return_type="Tuple[Tuple[int, str], ...]", doc="""Used to modify list of answer buttons - Return a tuple of the form ((1, "Label1"), (2, "Label2")) + buttons_tuple is a tuple of buttons, with each button represented by a tuple + containing an int for the button's number and a string for the button's label. - import _ from anki.lang to support translation, as - ((1, _("Label1)), ...) + Return a tuple of the form ((1, "Label1"), (2, "Label2"), ...) + + Note: import _ from anki.lang to support automatic translation, using, e.g., + ((1, _("Label1")), ...) """, ), Hook( From 649142e5b363ef3e5b6418f7a61d7dae0279cf04 Mon Sep 17 00:00:00 2001 From: Thomas B Date: Tue, 18 Aug 2020 10:38:59 -0400 Subject: [PATCH 3/9] Update as part of previous commit --- qt/aqt/gui_hooks.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/qt/aqt/gui_hooks.py b/qt/aqt/gui_hooks.py index a2eabf8b0..f3397f90e 100644 --- a/qt/aqt/gui_hooks.py +++ b/qt/aqt/gui_hooks.py @@ -2079,8 +2079,6 @@ class _ReviewerDidShowQuestionHook: reviewer_did_show_question = _ReviewerDidShowQuestionHook() -class _ReviewerWillInitAnswerButtonsFilter: - """Used to modify the answer buttons shown for a card. class _ReviewerWillAnswerCardFilter: """Used to modify the ease at which a card is rated or to bypass rating the card completely. From 897ec726088efd6af53ef2f9448240c796351ee5 Mon Sep 17 00:00:00 2001 From: Thomas B Date: Wed, 19 Aug 2020 16:15:49 -0400 Subject: [PATCH 4/9] Fixing type hints Matched all type hints, changed the original Sequence[] type hint for _answerButtonList() in reviewer.py on mypy's recommendation. --- qt/aqt/gui_hooks.py | 11 ++++++----- qt/aqt/reviewer.py | 4 ++-- qt/tools/genhooks_gui.py | 10 ++++++---- 3 files changed, 14 insertions(+), 11 deletions(-) diff --git a/qt/aqt/gui_hooks.py b/qt/aqt/gui_hooks.py index f3397f90e..83ab69d9b 100644 --- a/qt/aqt/gui_hooks.py +++ b/qt/aqt/gui_hooks.py @@ -7,7 +7,7 @@ See pylib/anki/hooks.py from __future__ import annotations -from typing import Any, Callable, List, Optional, Tuple +from typing import Any, Callable, List, Optional, Tuple, Sequence import anki import aqt @@ -2177,6 +2177,7 @@ class _ReviewerWillInitAnswerButtonsFilter: _hooks: List[ Callable[ ["Optional[Tuple[Tuple[int, str], ...]]", "aqt.reviewer.Reviewer", Card], + ["Tuple[Tuple[int, str], ...]", "aqt.reviewer.Reviewer", Card], Tuple[Tuple[int, str], ...], ] ] = [] @@ -2184,17 +2185,17 @@ class _ReviewerWillInitAnswerButtonsFilter: def append( self, cb: Callable[ - ["Optional[Tuple[Tuple[int, str], ...]]", "aqt.reviewer.Reviewer", Card], + ["Tuple[Tuple[int, str], ...]", "aqt.reviewer.Reviewer", Card], Tuple[Tuple[int, str], ...], ], ) -> None: - """(buttons_tuple: Optional[Tuple[Tuple[int, str], ...]], reviewer: aqt.reviewer.Reviewer, card: Card)""" + """(buttons_tuple: Tuple[Tuple[int, str], ...], reviewer: aqt.reviewer.Reviewer, card: Card)""" self._hooks.append(cb) def remove( self, cb: Callable[ - ["Optional[Tuple[Tuple[int, str], ...]]", "aqt.reviewer.Reviewer", Card], + ["Tuple[Tuple[int, str], ...]", "aqt.reviewer.Reviewer", Card], Tuple[Tuple[int, str], ...], ], ) -> None: @@ -2206,7 +2207,7 @@ class _ReviewerWillInitAnswerButtonsFilter: def __call__( self, - buttons_tuple: Optional[Tuple[Tuple[int, str], ...]], + buttons_tuple: Tuple[Tuple[int, str], ...], reviewer: aqt.reviewer.Reviewer, card: Card, ) -> Tuple[Tuple[int, str], ...]: diff --git a/qt/aqt/reviewer.py b/qt/aqt/reviewer.py index d30436de8..4560f51e5 100644 --- a/qt/aqt/reviewer.py +++ b/qt/aqt/reviewer.py @@ -621,10 +621,10 @@ time = %(time)d; else: return 2 - def _answerButtonList(self) -> Sequence[Tuple[int, str], ...]: + def _answerButtonList(self) -> Tuple[Tuple[int, str], ...]: button_count = self.mw.col.sched.answerButtons(self.card) if button_count == 2: - buttons_tuple = ((1, _("Again")), (2, _("Good")),) + buttons_tuple: Tuple[Tuple[int, str], ...] = ((1, _("Again")), (2, _("Good")),) elif button_count == 3: buttons_tuple = ((1, _("Again")), (2, _("Good")), (3, _("Easy"))) else: diff --git a/qt/tools/genhooks_gui.py b/qt/tools/genhooks_gui.py index bf84ce664..32d840b82 100644 --- a/qt/tools/genhooks_gui.py +++ b/qt/tools/genhooks_gui.py @@ -61,19 +61,21 @@ hooks = [ Hook( name="reviewer_will_init_answer_buttons", args=[ - "buttons_tuple: Optional[Tuple[Tuple[int, str], ...]]", + "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 number and a string for the button's label. + buttons_tuple is a sequence of buttons, with each button represented + by a tuple containing an int for the button's number and a string for + the button's label. Return a tuple of the form ((1, "Label1"), (2, "Label2"), ...) - Note: import _ from anki.lang to support automatic translation, using, e.g., + Note: import _ from anki.lang to support automatic translation, using, + e.g., ((1, _("Label1")), ...) """, ), From 73883f3f21292c9b6970ee8c081a2a507738eab1 Mon Sep 17 00:00:00 2001 From: Thomas B Date: Wed, 19 Aug 2020 16:15:56 -0400 Subject: [PATCH 5/9] Update gui_hooks.py --- qt/aqt/gui_hooks.py | 1 - 1 file changed, 1 deletion(-) diff --git a/qt/aqt/gui_hooks.py b/qt/aqt/gui_hooks.py index 83ab69d9b..ca8c4bb90 100644 --- a/qt/aqt/gui_hooks.py +++ b/qt/aqt/gui_hooks.py @@ -2176,7 +2176,6 @@ class _ReviewerWillInitAnswerButtonsFilter: _hooks: List[ Callable[ - ["Optional[Tuple[Tuple[int, str], ...]]", "aqt.reviewer.Reviewer", Card], ["Tuple[Tuple[int, str], ...]", "aqt.reviewer.Reviewer", Card], Tuple[Tuple[int, str], ...], ] From 67e3f25ad14b4bc5cbcc5b60cf448fe28267aeda Mon Sep 17 00:00:00 2001 From: Thomas B Date: Wed, 19 Aug 2020 16:19:15 -0400 Subject: [PATCH 6/9] Add both Union and Sequence to typing import Resolve conflict. --- qt/aqt/gui_hooks.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qt/aqt/gui_hooks.py b/qt/aqt/gui_hooks.py index ca8c4bb90..d96bfb277 100644 --- a/qt/aqt/gui_hooks.py +++ b/qt/aqt/gui_hooks.py @@ -7,7 +7,7 @@ See pylib/anki/hooks.py from __future__ import annotations -from typing import Any, Callable, List, Optional, Tuple, Sequence +from typing import Any, Callable, List, Optional, Tuple, Union, Sequence import anki import aqt From fe6144ff61a63159c562283c750cb87a78a4b934 Mon Sep 17 00:00:00 2001 From: Thomas B Date: Thu, 20 Aug 2020 10:30:31 -0400 Subject: [PATCH 7/9] Clarify docstring --- qt/aqt/gui_hooks.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/qt/aqt/gui_hooks.py b/qt/aqt/gui_hooks.py index d96bfb277..93987e739 100644 --- a/qt/aqt/gui_hooks.py +++ b/qt/aqt/gui_hooks.py @@ -2165,12 +2165,14 @@ 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 number and a string for the button's label. + 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 ((1, "Label1"), (2, "Label2"), ...) + Return a tuple of the form ((int, str), ...), e.g.: + ((1, "Label1"), (2, "Label2"), ...) - Note: import _ from anki.lang to support automatic translation, using, e.g., + Note: import _ from anki.lang to support translation, using, e.g., ((1, _("Label1")), ...) """ From 6d43ab2fd59f6b0c3ca97c0062dada1f84372d8a Mon Sep 17 00:00:00 2001 From: Thomas B Date: Thu, 20 Aug 2020 10:33:46 -0400 Subject: [PATCH 8/9] Fix other docstring to match. --- qt/tools/genhooks_gui.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/qt/tools/genhooks_gui.py b/qt/tools/genhooks_gui.py index 32d840b82..ee1a0ecaa 100644 --- a/qt/tools/genhooks_gui.py +++ b/qt/tools/genhooks_gui.py @@ -68,14 +68,14 @@ hooks = [ return_type="Tuple[Tuple[int, str], ...]", doc="""Used to modify list of answer buttons - buttons_tuple is a sequence of buttons, with each button represented - by a tuple containing an int for the button's number and a string for - the button's label. + 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 ((1, "Label1"), (2, "Label2"), ...) + Return a tuple of the form ((int, str), ...), e.g.: + ((1, "Label1"), (2, "Label2"), ...) - Note: import _ from anki.lang to support automatic translation, using, - e.g., + Note: import _ from anki.lang to support translation, using, e.g., ((1, _("Label1")), ...) """, ), From c920dee63809a24abf37f7d04d5f13a579d0062d Mon Sep 17 00:00:00 2001 From: Thomas B Date: Thu, 20 Aug 2020 10:34:08 -0400 Subject: [PATCH 9/9] Remove conflict- Sequence type no longer needed --- qt/aqt/gui_hooks.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qt/aqt/gui_hooks.py b/qt/aqt/gui_hooks.py index 93987e739..bbafc8231 100644 --- a/qt/aqt/gui_hooks.py +++ b/qt/aqt/gui_hooks.py @@ -7,7 +7,7 @@ See pylib/anki/hooks.py from __future__ import annotations -from typing import Any, Callable, List, Optional, Tuple, Union, Sequence +from typing import Any, Callable, List, Optional, Tuple, Union import anki import aqt