mirror of
https://github.com/ankitects/anki.git
synced 2025-09-24 16:56:36 -04:00
Merge pull request #483 from alanhdu/monkeytype
Add some typehints to qt/aqt [3/n]
This commit is contained in:
commit
4617a4b1f4
6 changed files with 128 additions and 118 deletions
|
@ -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)
|
||||||
|
|
|
@ -87,7 +87,7 @@ class AVPlayer:
|
||||||
# audio be stopped?
|
# audio be stopped?
|
||||||
interrupt_current_audio = True
|
interrupt_current_audio = True
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self) -> None:
|
||||||
self._enqueued: List[AVTag] = []
|
self._enqueued: List[AVTag] = []
|
||||||
self.current_player: Optional[Player] = None
|
self.current_player: Optional[Player] = None
|
||||||
|
|
||||||
|
@ -112,7 +112,7 @@ class AVPlayer:
|
||||||
self._enqueued.insert(0, SoundOrVideoTag(filename=filename))
|
self._enqueued.insert(0, SoundOrVideoTag(filename=filename))
|
||||||
self._play_next_if_idle()
|
self._play_next_if_idle()
|
||||||
|
|
||||||
def toggle_pause(self):
|
def toggle_pause(self) -> None:
|
||||||
if self.current_player:
|
if self.current_player:
|
||||||
self.current_player.toggle_pause()
|
self.current_player.toggle_pause()
|
||||||
|
|
||||||
|
@ -179,7 +179,7 @@ av_player = AVPlayer()
|
||||||
|
|
||||||
# return modified command array that points to bundled command, and return
|
# return modified command array that points to bundled command, and return
|
||||||
# required environment
|
# required environment
|
||||||
def _packagedCmd(cmd) -> Tuple[Any, Dict[str, str]]:
|
def _packagedCmd(cmd: List[str]) -> Tuple[Any, Dict[str, str]]:
|
||||||
cmd = cmd[:]
|
cmd = cmd[:]
|
||||||
env = os.environ.copy()
|
env = os.environ.copy()
|
||||||
if "LD_LIBRARY_PATH" in env:
|
if "LD_LIBRARY_PATH" in env:
|
||||||
|
@ -205,7 +205,7 @@ def _packagedCmd(cmd) -> Tuple[Any, Dict[str, str]]:
|
||||||
si = startup_info()
|
si = startup_info()
|
||||||
|
|
||||||
# osx throws interrupted system call errors frequently
|
# osx throws interrupted system call errors frequently
|
||||||
def retryWait(proc) -> Any:
|
def retryWait(proc: subprocess.Popen) -> int:
|
||||||
while 1:
|
while 1:
|
||||||
try:
|
try:
|
||||||
return proc.wait()
|
return proc.wait()
|
||||||
|
@ -227,7 +227,7 @@ class SimpleProcessPlayer(Player): # pylint: disable=abstract-method
|
||||||
args: List[str] = []
|
args: List[str] = []
|
||||||
env: Optional[Dict[str, str]] = None
|
env: Optional[Dict[str, str]] = None
|
||||||
|
|
||||||
def __init__(self, taskman: TaskManager):
|
def __init__(self, taskman: TaskManager) -> None:
|
||||||
self._taskman = taskman
|
self._taskman = taskman
|
||||||
self._terminate_flag = False
|
self._terminate_flag = False
|
||||||
self._process: Optional[subprocess.Popen] = None
|
self._process: Optional[subprocess.Popen] = None
|
||||||
|
@ -238,7 +238,7 @@ class SimpleProcessPlayer(Player): # pylint: disable=abstract-method
|
||||||
lambda: self._play(tag), lambda res: self._on_done(res, on_done)
|
lambda: self._play(tag), lambda res: self._on_done(res, on_done)
|
||||||
)
|
)
|
||||||
|
|
||||||
def stop(self):
|
def stop(self) -> None:
|
||||||
self._terminate_flag = True
|
self._terminate_flag = True
|
||||||
# block until stopped
|
# block until stopped
|
||||||
t = time.time()
|
t = time.time()
|
||||||
|
@ -252,7 +252,7 @@ class SimpleProcessPlayer(Player): # pylint: disable=abstract-method
|
||||||
)
|
)
|
||||||
self._wait_for_termination(tag)
|
self._wait_for_termination(tag)
|
||||||
|
|
||||||
def _wait_for_termination(self, tag: AVTag):
|
def _wait_for_termination(self, tag: AVTag) -> None:
|
||||||
self._taskman.run_on_main(
|
self._taskman.run_on_main(
|
||||||
lambda: gui_hooks.av_player_did_begin_playing(self, tag)
|
lambda: gui_hooks.av_player_did_begin_playing(self, tag)
|
||||||
)
|
)
|
||||||
|
@ -359,7 +359,7 @@ class MpvManager(MPV, SoundOrVideoPlayer):
|
||||||
def toggle_pause(self) -> None:
|
def toggle_pause(self) -> None:
|
||||||
self.set_property("pause", not self.get_property("pause"))
|
self.set_property("pause", not self.get_property("pause"))
|
||||||
|
|
||||||
def seek_relative(self, secs) -> None:
|
def seek_relative(self, secs: int) -> None:
|
||||||
self.command("seek", secs, "relative")
|
self.command("seek", secs, "relative")
|
||||||
|
|
||||||
def on_idle(self) -> None:
|
def on_idle(self) -> None:
|
||||||
|
@ -401,7 +401,7 @@ class SimpleMplayerSlaveModePlayer(SimpleMplayerPlayer):
|
||||||
)
|
)
|
||||||
self._wait_for_termination(tag)
|
self._wait_for_termination(tag)
|
||||||
|
|
||||||
def command(self, *args) -> None:
|
def command(self, *args: Any) -> None:
|
||||||
"""Send a command over the slave interface.
|
"""Send a command over the slave interface.
|
||||||
|
|
||||||
The trailing newline is automatically added."""
|
The trailing newline is automatically added."""
|
||||||
|
@ -412,7 +412,7 @@ class SimpleMplayerSlaveModePlayer(SimpleMplayerPlayer):
|
||||||
def seek_relative(self, secs: int) -> None:
|
def seek_relative(self, secs: int) -> None:
|
||||||
self.command("seek", secs, 0)
|
self.command("seek", secs, 0)
|
||||||
|
|
||||||
def toggle_pause(self):
|
def toggle_pause(self) -> None:
|
||||||
self.command("pause")
|
self.command("pause")
|
||||||
|
|
||||||
|
|
||||||
|
@ -458,12 +458,12 @@ class _Recorder:
|
||||||
|
|
||||||
|
|
||||||
class PyAudioThreadedRecorder(threading.Thread):
|
class PyAudioThreadedRecorder(threading.Thread):
|
||||||
def __init__(self, startupDelay) -> None:
|
def __init__(self, startupDelay: float) -> None:
|
||||||
threading.Thread.__init__(self)
|
threading.Thread.__init__(self)
|
||||||
self.startupDelay = startupDelay
|
self.startupDelay = startupDelay
|
||||||
self.finish = False
|
self.finish = False
|
||||||
|
|
||||||
def run(self) -> Any:
|
def run(self) -> None:
|
||||||
chunk = 1024
|
chunk = 1024
|
||||||
p = pyaudio.PyAudio()
|
p = pyaudio.PyAudio()
|
||||||
|
|
||||||
|
@ -499,7 +499,7 @@ class PyAudioRecorder(_Recorder):
|
||||||
# discard first 250ms which may have pops/cracks
|
# discard first 250ms which may have pops/cracks
|
||||||
startupDelay = 0.25
|
startupDelay = 0.25
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self) -> None:
|
||||||
for t in recFiles + [processingSrc, processingDst]:
|
for t in recFiles + [processingSrc, processingDst]:
|
||||||
try:
|
try:
|
||||||
os.unlink(t)
|
os.unlink(t)
|
||||||
|
@ -507,15 +507,15 @@ class PyAudioRecorder(_Recorder):
|
||||||
pass
|
pass
|
||||||
self.encode = False
|
self.encode = False
|
||||||
|
|
||||||
def start(self):
|
def start(self) -> None:
|
||||||
self.thread = PyAudioThreadedRecorder(startupDelay=self.startupDelay)
|
self.thread = PyAudioThreadedRecorder(startupDelay=self.startupDelay)
|
||||||
self.thread.start()
|
self.thread.start()
|
||||||
|
|
||||||
def stop(self):
|
def stop(self) -> None:
|
||||||
self.thread.finish = True
|
self.thread.finish = True
|
||||||
self.thread.join()
|
self.thread.join()
|
||||||
|
|
||||||
def file(self):
|
def file(self) -> str:
|
||||||
if self.encode:
|
if self.encode:
|
||||||
tgt = "rec%d.mp3" % time.time()
|
tgt = "rec%d.mp3" % time.time()
|
||||||
os.rename(processingDst, tgt)
|
os.rename(processingDst, tgt)
|
||||||
|
@ -530,7 +530,7 @@ Recorder = PyAudioRecorder
|
||||||
##########################################################################
|
##########################################################################
|
||||||
|
|
||||||
|
|
||||||
def getAudio(parent, encode=True):
|
def getAudio(parent: QWidget, encode: bool = True) -> Optional[str]:
|
||||||
"Record and return filename"
|
"Record and return filename"
|
||||||
# record first
|
# record first
|
||||||
r = Recorder()
|
r = Recorder()
|
||||||
|
@ -547,16 +547,16 @@ def getAudio(parent, encode=True):
|
||||||
t = time.time()
|
t = time.time()
|
||||||
r.start()
|
r.start()
|
||||||
time.sleep(r.startupDelay)
|
time.sleep(r.startupDelay)
|
||||||
QApplication.instance().processEvents()
|
QApplication.instance().processEvents() # type: ignore
|
||||||
while not mb.clickedButton():
|
while not mb.clickedButton():
|
||||||
txt = _("Recording...<br>Time: %0.1f")
|
txt = _("Recording...<br>Time: %0.1f")
|
||||||
mb.setText(txt % (time.time() - t))
|
mb.setText(txt % (time.time() - t))
|
||||||
mb.show()
|
mb.show()
|
||||||
QApplication.instance().processEvents()
|
QApplication.instance().processEvents() # type: ignore
|
||||||
if mb.clickedButton() == mb.escapeButton():
|
if mb.clickedButton() == mb.escapeButton():
|
||||||
r.stop()
|
r.stop()
|
||||||
r.cleanup()
|
r.cleanup()
|
||||||
return
|
return None
|
||||||
saveGeom(mb, "audioRecorder")
|
saveGeom(mb, "audioRecorder")
|
||||||
# ensure at least a second captured
|
# ensure at least a second captured
|
||||||
while time.time() - t < 1:
|
while time.time() - t < 1:
|
||||||
|
|
|
@ -197,7 +197,7 @@ QTabWidget { background-color: %s; }
|
||||||
|
|
||||||
app.setPalette(palette)
|
app.setPalette(palette)
|
||||||
|
|
||||||
def _update_stat_colors(self):
|
def _update_stat_colors(self) -> None:
|
||||||
import anki.stats as s
|
import anki.stats as s
|
||||||
|
|
||||||
s.colLearn = self.str_color("new-count")
|
s.colLearn = self.str_color("new-count")
|
||||||
|
|
|
@ -15,13 +15,13 @@ from aqt.webview import AnkiWebView
|
||||||
|
|
||||||
# wrapper class for set_bridge_command()
|
# wrapper class for set_bridge_command()
|
||||||
class TopToolbar:
|
class TopToolbar:
|
||||||
def __init__(self, toolbar: Toolbar):
|
def __init__(self, toolbar: Toolbar) -> None:
|
||||||
self.toolbar = toolbar
|
self.toolbar = toolbar
|
||||||
|
|
||||||
|
|
||||||
# wrapper class for set_bridge_command()
|
# wrapper class for set_bridge_command()
|
||||||
class BottomToolbar:
|
class BottomToolbar:
|
||||||
def __init__(self, toolbar: Toolbar):
|
def __init__(self, toolbar: Toolbar) -> None:
|
||||||
self.toolbar = toolbar
|
self.toolbar = toolbar
|
||||||
|
|
||||||
|
|
||||||
|
@ -40,7 +40,7 @@ class Toolbar:
|
||||||
buf: str = "",
|
buf: str = "",
|
||||||
web_context: Optional[Any] = None,
|
web_context: Optional[Any] = None,
|
||||||
link_handler: Optional[Callable[[str], Any]] = None,
|
link_handler: Optional[Callable[[str], Any]] = None,
|
||||||
):
|
) -> None:
|
||||||
web_context = web_context or TopToolbar(self)
|
web_context = web_context or TopToolbar(self)
|
||||||
link_handler = link_handler or self._linkHandler
|
link_handler = link_handler or self._linkHandler
|
||||||
self.web.set_bridge_command(link_handler, web_context)
|
self.web.set_bridge_command(link_handler, web_context)
|
||||||
|
@ -90,7 +90,7 @@ class Toolbar:
|
||||||
f"""{label}</a>"""
|
f"""{label}</a>"""
|
||||||
)
|
)
|
||||||
|
|
||||||
def _centerLinks(self):
|
def _centerLinks(self) -> str:
|
||||||
links = [
|
links = [
|
||||||
self.create_link(
|
self.create_link(
|
||||||
"decks",
|
"decks",
|
||||||
|
@ -149,15 +149,15 @@ class Toolbar:
|
||||||
# Link handling
|
# Link handling
|
||||||
######################################################################
|
######################################################################
|
||||||
|
|
||||||
def _linkHandler(self, link):
|
def _linkHandler(self, link: str) -> bool:
|
||||||
if link in self.link_handlers:
|
if link in self.link_handlers:
|
||||||
self.link_handlers[link]()
|
self.link_handlers[link]()
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def _deckLinkHandler(self):
|
def _deckLinkHandler(self) -> None:
|
||||||
self.mw.moveToState("deckBrowser")
|
self.mw.moveToState("deckBrowser")
|
||||||
|
|
||||||
def _studyLinkHandler(self):
|
def _studyLinkHandler(self) -> None:
|
||||||
# if overview already shown, switch to review
|
# if overview already shown, switch to review
|
||||||
if self.mw.state == "overview":
|
if self.mw.state == "overview":
|
||||||
self.mw.col.startTimebox()
|
self.mw.col.startTimebox()
|
||||||
|
@ -165,16 +165,16 @@ class Toolbar:
|
||||||
else:
|
else:
|
||||||
self.mw.onOverview()
|
self.mw.onOverview()
|
||||||
|
|
||||||
def _addLinkHandler(self):
|
def _addLinkHandler(self) -> None:
|
||||||
self.mw.onAddCard()
|
self.mw.onAddCard()
|
||||||
|
|
||||||
def _browseLinkHandler(self):
|
def _browseLinkHandler(self) -> None:
|
||||||
self.mw.onBrowse()
|
self.mw.onBrowse()
|
||||||
|
|
||||||
def _statsLinkHandler(self):
|
def _statsLinkHandler(self) -> None:
|
||||||
self.mw.onStats()
|
self.mw.onStats()
|
||||||
|
|
||||||
def _syncLinkHandler(self):
|
def _syncLinkHandler(self) -> None:
|
||||||
self.mw.onSync()
|
self.mw.onSync()
|
||||||
|
|
||||||
# HTML & CSS
|
# HTML & CSS
|
||||||
|
@ -206,7 +206,7 @@ class BottomBar(Toolbar):
|
||||||
buf: str = "",
|
buf: str = "",
|
||||||
web_context: Optional[Any] = None,
|
web_context: Optional[Any] = None,
|
||||||
link_handler: Optional[Callable[[str], Any]] = None,
|
link_handler: Optional[Callable[[str], Any]] = None,
|
||||||
):
|
) -> None:
|
||||||
# note: some screens may override this
|
# note: some screens may override this
|
||||||
web_context = web_context or BottomToolbar(self)
|
web_context = web_context or BottomToolbar(self)
|
||||||
link_handler = link_handler or self._linkHandler
|
link_handler = link_handler or self._linkHandler
|
||||||
|
|
|
@ -5,7 +5,7 @@ import dataclasses
|
||||||
import json
|
import json
|
||||||
import math
|
import math
|
||||||
import sys
|
import sys
|
||||||
from typing import Any, List, Optional, Tuple
|
from typing import Any, Callable, List, Optional, Sequence, Tuple
|
||||||
|
|
||||||
from anki.lang import _
|
from anki.lang import _
|
||||||
from anki.utils import isLin, isMac, isWin
|
from anki.utils import isLin, isMac, isWin
|
||||||
|
@ -176,7 +176,7 @@ class AnkiWebView(QWebEngineView): # type: ignore
|
||||||
self.onBridgeCmd: Callable[[str], Any] = self.defaultOnBridgeCmd
|
self.onBridgeCmd: Callable[[str], Any] = self.defaultOnBridgeCmd
|
||||||
|
|
||||||
self._domDone = True
|
self._domDone = True
|
||||||
self._pendingActions: List[Tuple[str, List[Any]]] = []
|
self._pendingActions: List[Tuple[str, Sequence[Any]]] = []
|
||||||
self.requiresCol = True
|
self.requiresCol = True
|
||||||
self.setPage(self._page)
|
self.setPage(self._page)
|
||||||
|
|
||||||
|
@ -258,13 +258,13 @@ class AnkiWebView(QWebEngineView): # type: ignore
|
||||||
def dropEvent(self, evt):
|
def dropEvent(self, evt):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def setHtml(self, html):
|
def setHtml(self, html: str) -> None:
|
||||||
# discard any previous pending actions
|
# discard any previous pending actions
|
||||||
self._pendingActions = []
|
self._pendingActions = []
|
||||||
self._domDone = True
|
self._domDone = True
|
||||||
self._queueAction("setHtml", html)
|
self._queueAction("setHtml", html)
|
||||||
|
|
||||||
def _setHtml(self, html):
|
def _setHtml(self, html: str) -> None:
|
||||||
app = QApplication.instance()
|
app = QApplication.instance()
|
||||||
oldFocus = app.focusWidget()
|
oldFocus = app.focusWidget()
|
||||||
self._domDone = False
|
self._domDone = False
|
||||||
|
@ -273,7 +273,7 @@ class AnkiWebView(QWebEngineView): # type: ignore
|
||||||
if oldFocus:
|
if oldFocus:
|
||||||
oldFocus.setFocus()
|
oldFocus.setFocus()
|
||||||
|
|
||||||
def zoomFactor(self):
|
def zoomFactor(self) -> float:
|
||||||
# overridden scale factor?
|
# overridden scale factor?
|
||||||
webscale = os.environ.get("ANKI_WEBSCALE")
|
webscale = os.environ.get("ANKI_WEBSCALE")
|
||||||
if webscale:
|
if webscale:
|
||||||
|
@ -295,7 +295,7 @@ class AnkiWebView(QWebEngineView): # type: ignore
|
||||||
newFactor = desiredScale / qtIntScale
|
newFactor = desiredScale / qtIntScale
|
||||||
return max(1, newFactor)
|
return max(1, newFactor)
|
||||||
|
|
||||||
def _getQtIntScale(self, screen):
|
def _getQtIntScale(self, screen) -> int:
|
||||||
# try to detect if Qt has scaled the screen
|
# try to detect if Qt has scaled the screen
|
||||||
# - qt will round the scale factor to a whole number, so a dpi of 125% = 1x,
|
# - qt will round the scale factor to a whole number, so a dpi of 125% = 1x,
|
||||||
# and a dpi of 150% = 2x
|
# and a dpi of 150% = 2x
|
||||||
|
@ -430,13 +430,13 @@ body {{ zoom: {}; background: {}; {} }}
|
||||||
fname
|
fname
|
||||||
)
|
)
|
||||||
|
|
||||||
def eval(self, js):
|
def eval(self, js: str) -> None:
|
||||||
self.evalWithCallback(js, None)
|
self.evalWithCallback(js, None)
|
||||||
|
|
||||||
def evalWithCallback(self, js, cb):
|
def evalWithCallback(self, js: str, cb: Callable) -> None:
|
||||||
self._queueAction("eval", js, cb)
|
self._queueAction("eval", js, cb)
|
||||||
|
|
||||||
def _evalWithCallback(self, js, cb):
|
def _evalWithCallback(self, js: str, cb: Callable[[Any], Any]) -> None:
|
||||||
if cb:
|
if cb:
|
||||||
|
|
||||||
def handler(val):
|
def handler(val):
|
||||||
|
@ -449,11 +449,11 @@ body {{ zoom: {}; background: {}; {} }}
|
||||||
else:
|
else:
|
||||||
self.page().runJavaScript(js)
|
self.page().runJavaScript(js)
|
||||||
|
|
||||||
def _queueAction(self, name, *args):
|
def _queueAction(self, name: str, *args: Any) -> None:
|
||||||
self._pendingActions.append((name, args))
|
self._pendingActions.append((name, args))
|
||||||
self._maybeRunActions()
|
self._maybeRunActions()
|
||||||
|
|
||||||
def _maybeRunActions(self):
|
def _maybeRunActions(self) -> None:
|
||||||
while self._pendingActions and self._domDone:
|
while self._pendingActions and self._domDone:
|
||||||
name, args = self._pendingActions.pop(0)
|
name, args = self._pendingActions.pop(0)
|
||||||
|
|
||||||
|
@ -464,10 +464,10 @@ body {{ zoom: {}; background: {}; {} }}
|
||||||
else:
|
else:
|
||||||
raise Exception("unknown action: {}".format(name))
|
raise Exception("unknown action: {}".format(name))
|
||||||
|
|
||||||
def _openLinksExternally(self, url):
|
def _openLinksExternally(self, url: str) -> None:
|
||||||
openLink(url)
|
openLink(url)
|
||||||
|
|
||||||
def _shouldIgnoreWebEvent(self):
|
def _shouldIgnoreWebEvent(self) -> bool:
|
||||||
# async web events may be received after the profile has been closed
|
# async web events may be received after the profile has been closed
|
||||||
# or the underlying webview has been deleted
|
# or the underlying webview has been deleted
|
||||||
from aqt import mw
|
from aqt import mw
|
||||||
|
@ -499,18 +499,18 @@ body {{ zoom: {}; background: {}; {} }}
|
||||||
else:
|
else:
|
||||||
return self.onBridgeCmd(cmd)
|
return self.onBridgeCmd(cmd)
|
||||||
|
|
||||||
def defaultOnBridgeCmd(self, cmd: str) -> Any:
|
def defaultOnBridgeCmd(self, cmd: str) -> None:
|
||||||
print("unhandled bridge cmd:", cmd)
|
print("unhandled bridge cmd:", cmd)
|
||||||
|
|
||||||
# legacy
|
# legacy
|
||||||
def resetHandlers(self):
|
def resetHandlers(self) -> None:
|
||||||
self.onBridgeCmd = self.defaultOnBridgeCmd
|
self.onBridgeCmd = self.defaultOnBridgeCmd
|
||||||
self._bridge_context = None
|
self._bridge_context = None
|
||||||
|
|
||||||
def adjustHeightToFit(self):
|
def adjustHeightToFit(self) -> None:
|
||||||
self.evalWithCallback("$(document.body).height()", self._onHeight)
|
self.evalWithCallback("$(document.body).height()", self._onHeight)
|
||||||
|
|
||||||
def _onHeight(self, qvar):
|
def _onHeight(self, qvar: Optional[int]) -> None:
|
||||||
from aqt import mw
|
from aqt import mw
|
||||||
|
|
||||||
if qvar is None:
|
if qvar is None:
|
||||||
|
|
Loading…
Reference in a new issue