Monkeytype qt/aqt/reviewer.py
This commit is contained in:
parent
96ca469d12
commit
6c2dda6c9c
@ -657,6 +657,7 @@ where c.nid = n.id and c.id in %s group by nid"""
|
|||||||
self._startTime = time.time()
|
self._startTime = time.time()
|
||||||
self._startReps = self.sched.reps
|
self._startReps = self.sched.reps
|
||||||
|
|
||||||
|
# FIXME: Use Literal[False] when on Python 3.8
|
||||||
def timeboxReached(self) -> Union[bool, Tuple[Any, int]]:
|
def timeboxReached(self) -> Union[bool, Tuple[Any, int]]:
|
||||||
"Return (elapsedTime, reps) if timebox reached, or False."
|
"Return (elapsedTime, reps) if timebox reached, or False."
|
||||||
if not self.conf["timeLim"]:
|
if not self.conf["timeLim"]:
|
||||||
|
@ -6,11 +6,12 @@ from __future__ import annotations
|
|||||||
|
|
||||||
import difflib
|
import difflib
|
||||||
import html
|
import html
|
||||||
import html.parser
|
|
||||||
import json
|
import json
|
||||||
import re
|
import re
|
||||||
import unicodedata as ucd
|
import unicodedata as ucd
|
||||||
from typing import List, Optional
|
from typing import Callable, List, Optional, Sequence, Tuple, Union
|
||||||
|
|
||||||
|
from PyQt5.QtCore import Qt
|
||||||
|
|
||||||
from anki import hooks
|
from anki import hooks
|
||||||
from anki.cards import Card
|
from anki.cards import Card
|
||||||
@ -25,7 +26,7 @@ from aqt.utils import askUserDialog, downArrow, qtMenuShortcutWorkaround, toolti
|
|||||||
|
|
||||||
|
|
||||||
class ReviewerBottomBar:
|
class ReviewerBottomBar:
|
||||||
def __init__(self, reviewer: Reviewer):
|
def __init__(self, reviewer: Reviewer) -> None:
|
||||||
self.reviewer = reviewer
|
self.reviewer = reviewer
|
||||||
|
|
||||||
|
|
||||||
@ -39,28 +40,29 @@ class Reviewer:
|
|||||||
self.cardQueue: List[Card] = []
|
self.cardQueue: List[Card] = []
|
||||||
self.hadCardQueue = False
|
self.hadCardQueue = False
|
||||||
self._answeredIds: List[int] = []
|
self._answeredIds: List[int] = []
|
||||||
self._recordedAudio = None
|
self._recordedAudio: Optional[str] = None
|
||||||
self.typeCorrect = None # web init happens before this is set
|
self.typeCorrect: str = None # web init happens before this is set
|
||||||
self.state: Optional[str] = None
|
self.state: Optional[str] = None
|
||||||
self.bottom = BottomBar(mw, mw.bottomWeb)
|
self.bottom = BottomBar(mw, mw.bottomWeb)
|
||||||
hooks.card_did_leech.append(self.onLeech)
|
hooks.card_did_leech.append(self.onLeech)
|
||||||
|
|
||||||
def show(self):
|
def show(self) -> None:
|
||||||
self.mw.col.reset()
|
self.mw.col.reset()
|
||||||
self.mw.setStateShortcuts(self._shortcutKeys())
|
self.mw.setStateShortcuts(self._shortcutKeys()) # type: ignore
|
||||||
self.web.set_bridge_command(self._linkHandler, self)
|
self.web.set_bridge_command(self._linkHandler, self)
|
||||||
self.bottom.web.set_bridge_command(self._linkHandler, ReviewerBottomBar(self))
|
self.bottom.web.set_bridge_command(self._linkHandler, ReviewerBottomBar(self))
|
||||||
self._reps = None
|
self._reps: int = None
|
||||||
self.nextCard()
|
self.nextCard()
|
||||||
|
|
||||||
def lastCard(self):
|
def lastCard(self) -> Optional[Card]:
|
||||||
if self._answeredIds:
|
if self._answeredIds:
|
||||||
if not self.card or self._answeredIds[-1] != self.card.id:
|
if not self.card or self._answeredIds[-1] != self.card.id:
|
||||||
try:
|
try:
|
||||||
return self.mw.col.getCard(self._answeredIds[-1])
|
return self.mw.col.getCard(self._answeredIds[-1])
|
||||||
except TypeError:
|
except TypeError:
|
||||||
# id was deleted
|
# id was deleted
|
||||||
return
|
return None
|
||||||
|
return None
|
||||||
|
|
||||||
def cleanup(self) -> None:
|
def cleanup(self) -> None:
|
||||||
gui_hooks.reviewer_will_end()
|
gui_hooks.reviewer_will_end()
|
||||||
@ -68,9 +70,10 @@ class Reviewer:
|
|||||||
# Fetching a card
|
# Fetching a card
|
||||||
##########################################################################
|
##########################################################################
|
||||||
|
|
||||||
def nextCard(self):
|
def nextCard(self) -> None:
|
||||||
elapsed = self.mw.col.timeboxReached()
|
elapsed = self.mw.col.timeboxReached()
|
||||||
if elapsed:
|
if elapsed:
|
||||||
|
assert not isinstance(elapsed, bool)
|
||||||
part1 = (
|
part1 = (
|
||||||
ngettext("%d card studied in", "%d cards studied in", elapsed[1])
|
ngettext("%d card studied in", "%d cards studied in", elapsed[1])
|
||||||
% elapsed[1]
|
% elapsed[1]
|
||||||
@ -125,7 +128,7 @@ class Reviewer:
|
|||||||
# Initializing the webview
|
# Initializing the webview
|
||||||
##########################################################################
|
##########################################################################
|
||||||
|
|
||||||
def revHtml(self):
|
def revHtml(self) -> str:
|
||||||
extra = self.mw.col.conf.get("reviewExtra", "")
|
extra = self.mw.col.conf.get("reviewExtra", "")
|
||||||
fade = ""
|
fade = ""
|
||||||
if self.mw.pm.glMode() == "software":
|
if self.mw.pm.glMode() == "software":
|
||||||
@ -140,7 +143,7 @@ class Reviewer:
|
|||||||
fade, extra
|
fade, extra
|
||||||
)
|
)
|
||||||
|
|
||||||
def _initWeb(self):
|
def _initWeb(self) -> None:
|
||||||
self._reps = 0
|
self._reps = 0
|
||||||
# main window
|
# main window
|
||||||
self.web.stdHtml(
|
self.web.stdHtml(
|
||||||
@ -167,13 +170,13 @@ class Reviewer:
|
|||||||
# Showing the question
|
# Showing the question
|
||||||
##########################################################################
|
##########################################################################
|
||||||
|
|
||||||
def _mungeQA(self, buf):
|
def _mungeQA(self, buf: str) -> str:
|
||||||
return self.typeAnsFilter(self.mw.prepare_card_text_for_display(buf))
|
return self.typeAnsFilter(self.mw.prepare_card_text_for_display(buf))
|
||||||
|
|
||||||
def _showQuestion(self) -> None:
|
def _showQuestion(self) -> None:
|
||||||
self._reps += 1
|
self._reps += 1
|
||||||
self.state = "question"
|
self.state = "question"
|
||||||
self.typedAnswer = None
|
self.typedAnswer: str = None
|
||||||
c = self.card
|
c = self.card
|
||||||
# grab the question and play audio
|
# grab the question and play audio
|
||||||
if c.isEmpty():
|
if c.isEmpty():
|
||||||
@ -206,17 +209,17 @@ The front of this card is empty. Please run Tools>Empty Cards."""
|
|||||||
# user hook
|
# user hook
|
||||||
gui_hooks.reviewer_did_show_question(c)
|
gui_hooks.reviewer_did_show_question(c)
|
||||||
|
|
||||||
def autoplay(self, card):
|
def autoplay(self, card: Card) -> bool:
|
||||||
return self.mw.col.decks.confForDid(card.odid or card.did)["autoplay"]
|
return self.mw.col.decks.confForDid(card.odid or card.did)["autoplay"]
|
||||||
|
|
||||||
def _replayq(self, card, previewer=None):
|
def _replayq(self, card, previewer=None):
|
||||||
s = previewer if previewer else self
|
s = previewer if previewer else self
|
||||||
return s.mw.col.decks.confForDid(s.card.odid or s.card.did).get("replayq", True)
|
return s.mw.col.decks.confForDid(s.card.odid or s.card.did).get("replayq", True)
|
||||||
|
|
||||||
def _drawFlag(self):
|
def _drawFlag(self) -> None:
|
||||||
self.web.eval("_drawFlag(%s);" % self.card.userFlag())
|
self.web.eval("_drawFlag(%s);" % self.card.userFlag())
|
||||||
|
|
||||||
def _drawMark(self):
|
def _drawMark(self) -> None:
|
||||||
self.web.eval("_drawMark(%s);" % json.dumps(self.card.note().hasTag("marked")))
|
self.web.eval("_drawMark(%s);" % json.dumps(self.card.note().hasTag("marked")))
|
||||||
|
|
||||||
# Showing the answer
|
# Showing the answer
|
||||||
@ -246,7 +249,7 @@ The front of this card is empty. Please run Tools>Empty Cards."""
|
|||||||
# Answering a card
|
# Answering a card
|
||||||
############################################################
|
############################################################
|
||||||
|
|
||||||
def _answerCard(self, ease):
|
def _answerCard(self, ease: int) -> None:
|
||||||
"Reschedule card and show next."
|
"Reschedule card and show next."
|
||||||
if self.mw.state != "review":
|
if self.mw.state != "review":
|
||||||
# showing resetRequired screen; ignore key
|
# showing resetRequired screen; ignore key
|
||||||
@ -269,7 +272,9 @@ The front of this card is empty. Please run Tools>Empty Cards."""
|
|||||||
# Handlers
|
# Handlers
|
||||||
############################################################
|
############################################################
|
||||||
|
|
||||||
def _shortcutKeys(self):
|
def _shortcutKeys(
|
||||||
|
self,
|
||||||
|
) -> List[Union[Tuple[str, Callable], Tuple[Qt.Key, Callable]]]:
|
||||||
return [
|
return [
|
||||||
("e", self.mw.onEditCurrent),
|
("e", self.mw.onEditCurrent),
|
||||||
(" ", self.onEnterKey),
|
(" ", self.onEnterKey),
|
||||||
@ -299,18 +304,18 @@ The front of this card is empty. Please run Tools>Empty Cards."""
|
|||||||
("7", self.on_seek_forward),
|
("7", self.on_seek_forward),
|
||||||
]
|
]
|
||||||
|
|
||||||
def on_pause_audio(self):
|
def on_pause_audio(self) -> None:
|
||||||
av_player.toggle_pause()
|
av_player.toggle_pause()
|
||||||
|
|
||||||
seek_secs = 5
|
seek_secs = 5
|
||||||
|
|
||||||
def on_seek_backward(self):
|
def on_seek_backward(self) -> None:
|
||||||
av_player.seek_relative(-self.seek_secs)
|
av_player.seek_relative(-self.seek_secs)
|
||||||
|
|
||||||
def on_seek_forward(self):
|
def on_seek_forward(self) -> None:
|
||||||
av_player.seek_relative(self.seek_secs)
|
av_player.seek_relative(self.seek_secs)
|
||||||
|
|
||||||
def onEnterKey(self):
|
def onEnterKey(self) -> None:
|
||||||
if self.state == "question":
|
if self.state == "question":
|
||||||
self._getTypedAnswer()
|
self._getTypedAnswer()
|
||||||
elif self.state == "answer":
|
elif self.state == "answer":
|
||||||
@ -318,14 +323,14 @@ The front of this card is empty. Please run Tools>Empty Cards."""
|
|||||||
"selectedAnswerButton()", self._onAnswerButton
|
"selectedAnswerButton()", self._onAnswerButton
|
||||||
)
|
)
|
||||||
|
|
||||||
def _onAnswerButton(self, val):
|
def _onAnswerButton(self, val: str) -> None:
|
||||||
# button selected?
|
# button selected?
|
||||||
if val and val in "1234":
|
if val and val in "1234":
|
||||||
self._answerCard(int(val))
|
self._answerCard(int(val))
|
||||||
else:
|
else:
|
||||||
self._answerCard(self._defaultEase())
|
self._answerCard(self._defaultEase())
|
||||||
|
|
||||||
def _linkHandler(self, url):
|
def _linkHandler(self, url: str) -> None:
|
||||||
if url == "ans":
|
if url == "ans":
|
||||||
self._getTypedAnswer()
|
self._getTypedAnswer()
|
||||||
elif url.startswith("ease"):
|
elif url.startswith("ease"):
|
||||||
@ -344,13 +349,13 @@ The front of this card is empty. Please run Tools>Empty Cards."""
|
|||||||
|
|
||||||
typeAnsPat = r"\[\[type:(.+?)\]\]"
|
typeAnsPat = r"\[\[type:(.+?)\]\]"
|
||||||
|
|
||||||
def typeAnsFilter(self, buf):
|
def typeAnsFilter(self, buf: str) -> str:
|
||||||
if self.state == "question":
|
if self.state == "question":
|
||||||
return self.typeAnsQuestionFilter(buf)
|
return self.typeAnsQuestionFilter(buf)
|
||||||
else:
|
else:
|
||||||
return self.typeAnsAnswerFilter(buf)
|
return self.typeAnsAnswerFilter(buf)
|
||||||
|
|
||||||
def typeAnsQuestionFilter(self, buf):
|
def typeAnsQuestionFilter(self, buf: str) -> str:
|
||||||
self.typeCorrect = None
|
self.typeCorrect = None
|
||||||
clozeIdx = None
|
clozeIdx = None
|
||||||
m = re.search(self.typeAnsPat, buf)
|
m = re.search(self.typeAnsPat, buf)
|
||||||
@ -397,20 +402,19 @@ Please run Tools>Empty Cards"""
|
|||||||
buf,
|
buf,
|
||||||
)
|
)
|
||||||
|
|
||||||
def typeAnsAnswerFilter(self, buf):
|
def typeAnsAnswerFilter(self, buf: str) -> str:
|
||||||
if not self.typeCorrect:
|
if not self.typeCorrect:
|
||||||
return re.sub(self.typeAnsPat, "", buf)
|
return re.sub(self.typeAnsPat, "", buf)
|
||||||
origSize = len(buf)
|
origSize = len(buf)
|
||||||
buf = buf.replace("<hr id=answer>", "")
|
buf = buf.replace("<hr id=answer>", "")
|
||||||
hadHR = len(buf) != origSize
|
hadHR = len(buf) != origSize
|
||||||
# munge correct value
|
# munge correct value
|
||||||
parser = html.parser.HTMLParser()
|
|
||||||
cor = self.mw.col.media.strip(self.typeCorrect)
|
cor = self.mw.col.media.strip(self.typeCorrect)
|
||||||
cor = re.sub("(\n|<br ?/?>|</?div>)+", " ", cor)
|
cor = re.sub("(\n|<br ?/?>|</?div>)+", " ", cor)
|
||||||
cor = stripHTML(cor)
|
cor = stripHTML(cor)
|
||||||
# ensure we don't chomp multiple whitespace
|
# ensure we don't chomp multiple whitespace
|
||||||
cor = cor.replace(" ", " ")
|
cor = cor.replace(" ", " ")
|
||||||
cor = parser.unescape(cor)
|
cor = html.unescape(cor)
|
||||||
cor = cor.replace("\xa0", " ")
|
cor = cor.replace("\xa0", " ")
|
||||||
cor = cor.strip()
|
cor = cor.strip()
|
||||||
given = self.typedAnswer
|
given = self.typedAnswer
|
||||||
@ -434,7 +438,7 @@ Please run Tools>Empty Cards"""
|
|||||||
|
|
||||||
return re.sub(self.typeAnsPat, repl, buf)
|
return re.sub(self.typeAnsPat, repl, buf)
|
||||||
|
|
||||||
def _contentForCloze(self, txt, idx):
|
def _contentForCloze(self, txt: str, idx) -> str:
|
||||||
matches = re.findall(r"\{\{c%s::(.+?)\}\}" % idx, txt, re.DOTALL)
|
matches = re.findall(r"\{\{c%s::(.+?)\}\}" % idx, txt, re.DOTALL)
|
||||||
if not matches:
|
if not matches:
|
||||||
return None
|
return None
|
||||||
@ -452,24 +456,28 @@ Please run Tools>Empty Cards"""
|
|||||||
txt = ", ".join(matches)
|
txt = ", ".join(matches)
|
||||||
return txt
|
return txt
|
||||||
|
|
||||||
def tokenizeComparison(self, given, correct):
|
def tokenizeComparison(
|
||||||
|
self, given: str, correct: str
|
||||||
|
) -> Tuple[List[Tuple[bool, str]], List[Tuple[bool, str]]]:
|
||||||
# compare in NFC form so accents appear correct
|
# compare in NFC form so accents appear correct
|
||||||
given = ucd.normalize("NFC", given)
|
given = ucd.normalize("NFC", given)
|
||||||
correct = ucd.normalize("NFC", correct)
|
correct = ucd.normalize("NFC", correct)
|
||||||
s = difflib.SequenceMatcher(None, given, correct, autojunk=False)
|
s = difflib.SequenceMatcher(None, given, correct, autojunk=False)
|
||||||
givenElems = []
|
givenElems: List[Tuple[bool, str]] = []
|
||||||
correctElems = []
|
correctElems: List[Tuple[bool, str]] = []
|
||||||
givenPoint = 0
|
givenPoint = 0
|
||||||
correctPoint = 0
|
correctPoint = 0
|
||||||
offby = 0
|
offby = 0
|
||||||
|
|
||||||
def logBad(old, new, str, array):
|
def logBad(old: int, new: int, s: str, array: List[Tuple[bool, str]]) -> None:
|
||||||
if old != new:
|
if old != new:
|
||||||
array.append((False, str[old:new]))
|
array.append((False, s[old:new]))
|
||||||
|
|
||||||
def logGood(start, cnt, str, array):
|
def logGood(
|
||||||
|
start: int, cnt: int, s: str, array: List[Tuple[bool, str]]
|
||||||
|
) -> None:
|
||||||
if cnt:
|
if cnt:
|
||||||
array.append((True, str[start : start + cnt]))
|
array.append((True, s[start : start + cnt]))
|
||||||
|
|
||||||
for x, y, cnt in s.get_matching_blocks():
|
for x, y, cnt in s.get_matching_blocks():
|
||||||
# if anything was missed in correct, pad given
|
# if anything was missed in correct, pad given
|
||||||
@ -486,17 +494,17 @@ Please run Tools>Empty Cards"""
|
|||||||
logGood(y, cnt, correct, correctElems)
|
logGood(y, cnt, correct, correctElems)
|
||||||
return givenElems, correctElems
|
return givenElems, correctElems
|
||||||
|
|
||||||
def correct(self, given, correct, showBad=True):
|
def correct(self, given: str, correct: str, showBad: bool = True) -> str:
|
||||||
"Diff-corrects the typed-in answer."
|
"Diff-corrects the typed-in answer."
|
||||||
givenElems, correctElems = self.tokenizeComparison(given, correct)
|
givenElems, correctElems = self.tokenizeComparison(given, correct)
|
||||||
|
|
||||||
def good(s):
|
def good(s: str) -> str:
|
||||||
return "<span class=typeGood>" + html.escape(s) + "</span>"
|
return "<span class=typeGood>" + html.escape(s) + "</span>"
|
||||||
|
|
||||||
def bad(s):
|
def bad(s: str) -> str:
|
||||||
return "<span class=typeBad>" + html.escape(s) + "</span>"
|
return "<span class=typeBad>" + html.escape(s) + "</span>"
|
||||||
|
|
||||||
def missed(s):
|
def missed(s: str) -> str:
|
||||||
return "<span class=typeMissed>" + html.escape(s) + "</span>"
|
return "<span class=typeMissed>" + html.escape(s) + "</span>"
|
||||||
|
|
||||||
if given == correct:
|
if given == correct:
|
||||||
@ -519,24 +527,24 @@ Please run Tools>Empty Cards"""
|
|||||||
res = "<div><code id=typeans>" + res + "</code></div>"
|
res = "<div><code id=typeans>" + res + "</code></div>"
|
||||||
return res
|
return res
|
||||||
|
|
||||||
def _noLoneMarks(self, s):
|
def _noLoneMarks(self, s: str) -> str:
|
||||||
# ensure a combining character at the start does not join to
|
# ensure a combining character at the start does not join to
|
||||||
# previous text
|
# previous text
|
||||||
if s and ucd.category(s[0]).startswith("M"):
|
if s and ucd.category(s[0]).startswith("M"):
|
||||||
return "\xa0" + s
|
return "\xa0" + s
|
||||||
return s
|
return s
|
||||||
|
|
||||||
def _getTypedAnswer(self):
|
def _getTypedAnswer(self) -> None:
|
||||||
self.web.evalWithCallback("typeans ? typeans.value : null", self._onTypedAnswer)
|
self.web.evalWithCallback("typeans ? typeans.value : null", self._onTypedAnswer)
|
||||||
|
|
||||||
def _onTypedAnswer(self, val):
|
def _onTypedAnswer(self, val: None) -> None:
|
||||||
self.typedAnswer = val or ""
|
self.typedAnswer = val or ""
|
||||||
self._showAnswer()
|
self._showAnswer()
|
||||||
|
|
||||||
# Bottom bar
|
# Bottom bar
|
||||||
##########################################################################
|
##########################################################################
|
||||||
|
|
||||||
def _bottomHTML(self):
|
def _bottomHTML(self) -> str:
|
||||||
return """
|
return """
|
||||||
<center id=outer>
|
<center id=outer>
|
||||||
<table id=innertable width=100%% cellspacing=0 cellpadding=0>
|
<table id=innertable width=100%% cellspacing=0 cellpadding=0>
|
||||||
@ -565,7 +573,7 @@ time = %(time)d;
|
|||||||
time=self.card.timeTaken() // 1000,
|
time=self.card.timeTaken() // 1000,
|
||||||
)
|
)
|
||||||
|
|
||||||
def _showAnswerButton(self):
|
def _showAnswerButton(self) -> None:
|
||||||
if not self.typeCorrect:
|
if not self.typeCorrect:
|
||||||
self.bottom.web.setFocus()
|
self.bottom.web.setFocus()
|
||||||
middle = """
|
middle = """
|
||||||
@ -587,17 +595,17 @@ time = %(time)d;
|
|||||||
self.bottom.web.eval("showQuestion(%s,%d);" % (json.dumps(middle), maxTime))
|
self.bottom.web.eval("showQuestion(%s,%d);" % (json.dumps(middle), maxTime))
|
||||||
self.bottom.web.adjustHeightToFit()
|
self.bottom.web.adjustHeightToFit()
|
||||||
|
|
||||||
def _showEaseButtons(self):
|
def _showEaseButtons(self) -> None:
|
||||||
self.bottom.web.setFocus()
|
self.bottom.web.setFocus()
|
||||||
middle = self._answerButtons()
|
middle = self._answerButtons()
|
||||||
self.bottom.web.eval("showAnswer(%s);" % json.dumps(middle))
|
self.bottom.web.eval("showAnswer(%s);" % json.dumps(middle))
|
||||||
|
|
||||||
def _remaining(self):
|
def _remaining(self) -> str:
|
||||||
if not self.mw.col.conf["dueCounts"]:
|
if not self.mw.col.conf["dueCounts"]:
|
||||||
return ""
|
return ""
|
||||||
if self.hadCardQueue:
|
if self.hadCardQueue:
|
||||||
# if it's come from the undo queue, don't count it separately
|
# if it's come from the undo queue, don't count it separately
|
||||||
counts = list(self.mw.col.sched.counts())
|
counts: List[Union[int, str]] = list(self.mw.col.sched.counts())
|
||||||
else:
|
else:
|
||||||
counts = list(self.mw.col.sched.counts(self.card))
|
counts = list(self.mw.col.sched.counts(self.card))
|
||||||
idx = self.mw.col.sched.countIdx(self.card)
|
idx = self.mw.col.sched.countIdx(self.card)
|
||||||
@ -608,13 +616,13 @@ time = %(time)d;
|
|||||||
ctxt += space + "<span class=review-count>%s</span>" % counts[2]
|
ctxt += space + "<span class=review-count>%s</span>" % counts[2]
|
||||||
return ctxt
|
return ctxt
|
||||||
|
|
||||||
def _defaultEase(self):
|
def _defaultEase(self) -> int:
|
||||||
if self.mw.col.sched.answerButtons(self.card) == 4:
|
if self.mw.col.sched.answerButtons(self.card) == 4:
|
||||||
return 3
|
return 3
|
||||||
else:
|
else:
|
||||||
return 2
|
return 2
|
||||||
|
|
||||||
def _answerButtonList(self):
|
def _answerButtonList(self) -> Sequence[Tuple[int, str]]:
|
||||||
l = ((1, _("Again")),)
|
l = ((1, _("Again")),)
|
||||||
cnt = self.mw.col.sched.answerButtons(self.card)
|
cnt = self.mw.col.sched.answerButtons(self.card)
|
||||||
if cnt == 2:
|
if cnt == 2:
|
||||||
@ -624,7 +632,7 @@ time = %(time)d;
|
|||||||
else:
|
else:
|
||||||
return l + ((2, _("Hard")), (3, _("Good")), (4, _("Easy")))
|
return l + ((2, _("Hard")), (3, _("Good")), (4, _("Easy")))
|
||||||
|
|
||||||
def _answerButtons(self):
|
def _answerButtons(self) -> str:
|
||||||
default = self._defaultEase()
|
default = self._defaultEase()
|
||||||
|
|
||||||
def but(i, label):
|
def but(i, label):
|
||||||
@ -652,7 +660,7 @@ time = %(time)d;
|
|||||||
<script>$(function () { $("#defease").focus(); });</script>"""
|
<script>$(function () { $("#defease").focus(); });</script>"""
|
||||||
return buf + script
|
return buf + script
|
||||||
|
|
||||||
def _buttonTime(self, i):
|
def _buttonTime(self, i: int) -> str:
|
||||||
if not self.mw.col.conf["estTimes"]:
|
if not self.mw.col.conf["estTimes"]:
|
||||||
return "<div class=spacer></div>"
|
return "<div class=spacer></div>"
|
||||||
txt = self.mw.col.sched.nextIvlStr(self.card, i, True) or " "
|
txt = self.mw.col.sched.nextIvlStr(self.card, i, True) or " "
|
||||||
@ -661,7 +669,7 @@ time = %(time)d;
|
|||||||
# Leeches
|
# Leeches
|
||||||
##########################################################################
|
##########################################################################
|
||||||
|
|
||||||
def onLeech(self, card):
|
def onLeech(self, card: Card) -> None:
|
||||||
# for now
|
# for now
|
||||||
s = _("Card was a leech.")
|
s = _("Card was a leech.")
|
||||||
if card.queue < 0:
|
if card.queue < 0:
|
||||||
@ -730,7 +738,7 @@ time = %(time)d;
|
|||||||
qtMenuShortcutWorkaround(m)
|
qtMenuShortcutWorkaround(m)
|
||||||
m.exec_(QCursor.pos())
|
m.exec_(QCursor.pos())
|
||||||
|
|
||||||
def _addMenuItems(self, m, rows):
|
def _addMenuItems(self, m, rows) -> None:
|
||||||
for row in rows:
|
for row in rows:
|
||||||
if not row:
|
if not row:
|
||||||
m.addSeparator()
|
m.addSeparator()
|
||||||
@ -753,10 +761,10 @@ time = %(time)d;
|
|||||||
a.setChecked(True)
|
a.setChecked(True)
|
||||||
a.triggered.connect(func)
|
a.triggered.connect(func)
|
||||||
|
|
||||||
def onOptions(self):
|
def onOptions(self) -> None:
|
||||||
self.mw.onDeckConf(self.mw.col.decks.get(self.card.odid or self.card.did))
|
self.mw.onDeckConf(self.mw.col.decks.get(self.card.odid or self.card.did))
|
||||||
|
|
||||||
def setFlag(self, flag):
|
def setFlag(self, flag: int) -> None:
|
||||||
# need to toggle off?
|
# need to toggle off?
|
||||||
if self.card.userFlag() == flag:
|
if self.card.userFlag() == flag:
|
||||||
flag = 0
|
flag = 0
|
||||||
@ -764,7 +772,7 @@ time = %(time)d;
|
|||||||
self.card.flush()
|
self.card.flush()
|
||||||
self._drawFlag()
|
self._drawFlag()
|
||||||
|
|
||||||
def onMark(self):
|
def onMark(self) -> None:
|
||||||
f = self.card.note()
|
f = self.card.note()
|
||||||
if f.hasTag("marked"):
|
if f.hasTag("marked"):
|
||||||
f.delTag("marked")
|
f.delTag("marked")
|
||||||
@ -773,19 +781,19 @@ time = %(time)d;
|
|||||||
f.flush()
|
f.flush()
|
||||||
self._drawMark()
|
self._drawMark()
|
||||||
|
|
||||||
def onSuspend(self):
|
def onSuspend(self) -> None:
|
||||||
self.mw.checkpoint(_("Suspend"))
|
self.mw.checkpoint(_("Suspend"))
|
||||||
self.mw.col.sched.suspendCards([c.id for c in self.card.note().cards()])
|
self.mw.col.sched.suspendCards([c.id for c in self.card.note().cards()])
|
||||||
tooltip(_("Note suspended."))
|
tooltip(_("Note suspended."))
|
||||||
self.mw.reset()
|
self.mw.reset()
|
||||||
|
|
||||||
def onSuspendCard(self):
|
def onSuspendCard(self) -> None:
|
||||||
self.mw.checkpoint(_("Suspend"))
|
self.mw.checkpoint(_("Suspend"))
|
||||||
self.mw.col.sched.suspendCards([self.card.id])
|
self.mw.col.sched.suspendCards([self.card.id])
|
||||||
tooltip(_("Card suspended."))
|
tooltip(_("Card suspended."))
|
||||||
self.mw.reset()
|
self.mw.reset()
|
||||||
|
|
||||||
def onDelete(self):
|
def onDelete(self) -> None:
|
||||||
# need to check state because the shortcut is global to the main
|
# need to check state because the shortcut is global to the main
|
||||||
# window
|
# window
|
||||||
if self.mw.state != "review" or not self.card:
|
if self.mw.state != "review" or not self.card:
|
||||||
@ -801,23 +809,24 @@ time = %(time)d;
|
|||||||
% cnt
|
% cnt
|
||||||
)
|
)
|
||||||
|
|
||||||
def onBuryCard(self):
|
def onBuryCard(self) -> None:
|
||||||
self.mw.checkpoint(_("Bury"))
|
self.mw.checkpoint(_("Bury"))
|
||||||
self.mw.col.sched.buryCards([self.card.id])
|
self.mw.col.sched.buryCards([self.card.id])
|
||||||
self.mw.reset()
|
self.mw.reset()
|
||||||
tooltip(_("Card buried."))
|
tooltip(_("Card buried."))
|
||||||
|
|
||||||
def onBuryNote(self):
|
def onBuryNote(self) -> None:
|
||||||
self.mw.checkpoint(_("Bury"))
|
self.mw.checkpoint(_("Bury"))
|
||||||
self.mw.col.sched.buryNote(self.card.nid)
|
self.mw.col.sched.buryNote(self.card.nid)
|
||||||
self.mw.reset()
|
self.mw.reset()
|
||||||
tooltip(_("Note buried."))
|
tooltip(_("Note buried."))
|
||||||
|
|
||||||
def onRecordVoice(self):
|
def onRecordVoice(self) -> None:
|
||||||
self._recordedAudio = getAudio(self.mw, encode=False)
|
self._recordedAudio = getAudio(self.mw, encode=False)
|
||||||
self.onReplayRecorded()
|
self.onReplayRecorded()
|
||||||
|
|
||||||
def onReplayRecorded(self):
|
def onReplayRecorded(self) -> None:
|
||||||
if not self._recordedAudio:
|
if not self._recordedAudio:
|
||||||
return tooltip(_("You haven't recorded your voice yet."))
|
tooltip(_("You haven't recorded your voice yet."))
|
||||||
|
return
|
||||||
av_player.play_file(self._recordedAudio)
|
av_player.play_file(self._recordedAudio)
|
||||||
|
Loading…
Reference in New Issue
Block a user