Merge pull request #430 from glutanimate/add-reviewer-answercard-hooks

Equip Reviewer._showAnswer with hooks covering common add-on usages
This commit is contained in:
Damien Elmes 2020-01-25 13:34:29 +10:00 committed by GitHub
commit d8e3bc519b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 101 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,28 @@ 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"],