Equip Reviewer._showAnswer with hooks covering common add-on usages

This commit is contained in:
Glutanimate 2020-01-24 15:36:05 +01:00
parent d428b3b4c0
commit 68c5fd50f9
3 changed files with 100 additions and 0 deletions

View file

@ -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."""

View file

@ -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()

View file

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