Merge pull request #483 from alanhdu/monkeytype
Add some typehints to qt/aqt [3/n]
This commit is contained in:
commit
4617a4b1f4
@ -657,6 +657,7 @@ where c.nid = n.id and c.id in %s group by nid"""
|
||||
self._startTime = time.time()
|
||||
self._startReps = self.sched.reps
|
||||
|
||||
# FIXME: Use Literal[False] when on Python 3.8
|
||||
def timeboxReached(self) -> Union[bool, Tuple[Any, int]]:
|
||||
"Return (elapsedTime, reps) if timebox reached, or False."
|
||||
if not self.conf["timeLim"]:
|
||||
|
@ -6,11 +6,12 @@ from __future__ import annotations
|
||||
|
||||
import difflib
|
||||
import html
|
||||
import html.parser
|
||||
import json
|
||||
import re
|
||||
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.cards import Card
|
||||
@ -25,7 +26,7 @@ from aqt.utils import askUserDialog, downArrow, qtMenuShortcutWorkaround, toolti
|
||||
|
||||
|
||||
class ReviewerBottomBar:
|
||||
def __init__(self, reviewer: Reviewer):
|
||||
def __init__(self, reviewer: Reviewer) -> None:
|
||||
self.reviewer = reviewer
|
||||
|
||||
|
||||
@ -39,28 +40,29 @@ class Reviewer:
|
||||
self.cardQueue: List[Card] = []
|
||||
self.hadCardQueue = False
|
||||
self._answeredIds: List[int] = []
|
||||
self._recordedAudio = None
|
||||
self.typeCorrect = None # web init happens before this is set
|
||||
self._recordedAudio: Optional[str] = None
|
||||
self.typeCorrect: str = None # web init happens before this is set
|
||||
self.state: Optional[str] = None
|
||||
self.bottom = BottomBar(mw, mw.bottomWeb)
|
||||
hooks.card_did_leech.append(self.onLeech)
|
||||
|
||||
def show(self):
|
||||
def show(self) -> None:
|
||||
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.bottom.web.set_bridge_command(self._linkHandler, ReviewerBottomBar(self))
|
||||
self._reps = None
|
||||
self._reps: int = None
|
||||
self.nextCard()
|
||||
|
||||
def lastCard(self):
|
||||
def lastCard(self) -> Optional[Card]:
|
||||
if self._answeredIds:
|
||||
if not self.card or self._answeredIds[-1] != self.card.id:
|
||||
try:
|
||||
return self.mw.col.getCard(self._answeredIds[-1])
|
||||
except TypeError:
|
||||
# id was deleted
|
||||
return
|
||||
return None
|
||||
return None
|
||||
|
||||
def cleanup(self) -> None:
|
||||
gui_hooks.reviewer_will_end()
|
||||
@ -68,9 +70,10 @@ class Reviewer:
|
||||
# Fetching a card
|
||||
##########################################################################
|
||||
|
||||
def nextCard(self):
|
||||
def nextCard(self) -> None:
|
||||
elapsed = self.mw.col.timeboxReached()
|
||||
if elapsed:
|
||||
assert not isinstance(elapsed, bool)
|
||||
part1 = (
|
||||
ngettext("%d card studied in", "%d cards studied in", elapsed[1])
|
||||
% elapsed[1]
|
||||
@ -125,7 +128,7 @@ class Reviewer:
|
||||
# Initializing the webview
|
||||
##########################################################################
|
||||
|
||||
def revHtml(self):
|
||||
def revHtml(self) -> str:
|
||||
extra = self.mw.col.conf.get("reviewExtra", "")
|
||||
fade = ""
|
||||
if self.mw.pm.glMode() == "software":
|
||||
@ -140,7 +143,7 @@ class Reviewer:
|
||||
fade, extra
|
||||
)
|
||||
|
||||
def _initWeb(self):
|
||||
def _initWeb(self) -> None:
|
||||
self._reps = 0
|
||||
# main window
|
||||
self.web.stdHtml(
|
||||
@ -167,13 +170,13 @@ class Reviewer:
|
||||
# Showing the question
|
||||
##########################################################################
|
||||
|
||||
def _mungeQA(self, buf):
|
||||
def _mungeQA(self, buf: str) -> str:
|
||||
return self.typeAnsFilter(self.mw.prepare_card_text_for_display(buf))
|
||||
|
||||
def _showQuestion(self) -> None:
|
||||
self._reps += 1
|
||||
self.state = "question"
|
||||
self.typedAnswer = None
|
||||
self.typedAnswer: str = None
|
||||
c = self.card
|
||||
# grab the question and play audio
|
||||
if c.isEmpty():
|
||||
@ -206,17 +209,17 @@ The front of this card is empty. Please run Tools>Empty Cards."""
|
||||
# user hook
|
||||
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"]
|
||||
|
||||
def _replayq(self, card, previewer=None):
|
||||
s = previewer if previewer else self
|
||||
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())
|
||||
|
||||
def _drawMark(self):
|
||||
def _drawMark(self) -> None:
|
||||
self.web.eval("_drawMark(%s);" % json.dumps(self.card.note().hasTag("marked")))
|
||||
|
||||
# Showing the answer
|
||||
@ -246,7 +249,7 @@ The front of this card is empty. Please run Tools>Empty Cards."""
|
||||
# Answering a card
|
||||
############################################################
|
||||
|
||||
def _answerCard(self, ease):
|
||||
def _answerCard(self, ease: int) -> None:
|
||||
"Reschedule card and show next."
|
||||
if self.mw.state != "review":
|
||||
# showing resetRequired screen; ignore key
|
||||
@ -269,7 +272,9 @@ The front of this card is empty. Please run Tools>Empty Cards."""
|
||||
# Handlers
|
||||
############################################################
|
||||
|
||||
def _shortcutKeys(self):
|
||||
def _shortcutKeys(
|
||||
self,
|
||||
) -> List[Union[Tuple[str, Callable], Tuple[Qt.Key, Callable]]]:
|
||||
return [
|
||||
("e", self.mw.onEditCurrent),
|
||||
(" ", self.onEnterKey),
|
||||
@ -299,18 +304,18 @@ The front of this card is empty. Please run Tools>Empty Cards."""
|
||||
("7", self.on_seek_forward),
|
||||
]
|
||||
|
||||
def on_pause_audio(self):
|
||||
def on_pause_audio(self) -> None:
|
||||
av_player.toggle_pause()
|
||||
|
||||
seek_secs = 5
|
||||
|
||||
def on_seek_backward(self):
|
||||
def on_seek_backward(self) -> None:
|
||||
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)
|
||||
|
||||
def onEnterKey(self):
|
||||
def onEnterKey(self) -> None:
|
||||
if self.state == "question":
|
||||
self._getTypedAnswer()
|
||||
elif self.state == "answer":
|
||||
@ -318,14 +323,14 @@ The front of this card is empty. Please run Tools>Empty Cards."""
|
||||
"selectedAnswerButton()", self._onAnswerButton
|
||||
)
|
||||
|
||||
def _onAnswerButton(self, val):
|
||||
def _onAnswerButton(self, val: str) -> None:
|
||||
# button selected?
|
||||
if val and val in "1234":
|
||||
self._answerCard(int(val))
|
||||
else:
|
||||
self._answerCard(self._defaultEase())
|
||||
|
||||
def _linkHandler(self, url):
|
||||
def _linkHandler(self, url: str) -> None:
|
||||
if url == "ans":
|
||||
self._getTypedAnswer()
|
||||
elif url.startswith("ease"):
|
||||
@ -344,13 +349,13 @@ The front of this card is empty. Please run Tools>Empty Cards."""
|
||||
|
||||
typeAnsPat = r"\[\[type:(.+?)\]\]"
|
||||
|
||||
def typeAnsFilter(self, buf):
|
||||
def typeAnsFilter(self, buf: str) -> str:
|
||||
if self.state == "question":
|
||||
return self.typeAnsQuestionFilter(buf)
|
||||
else:
|
||||
return self.typeAnsAnswerFilter(buf)
|
||||
|
||||
def typeAnsQuestionFilter(self, buf):
|
||||
def typeAnsQuestionFilter(self, buf: str) -> str:
|
||||
self.typeCorrect = None
|
||||
clozeIdx = None
|
||||
m = re.search(self.typeAnsPat, buf)
|
||||
@ -397,20 +402,19 @@ Please run Tools>Empty Cards"""
|
||||
buf,
|
||||
)
|
||||
|
||||
def typeAnsAnswerFilter(self, buf):
|
||||
def typeAnsAnswerFilter(self, buf: str) -> str:
|
||||
if not self.typeCorrect:
|
||||
return re.sub(self.typeAnsPat, "", buf)
|
||||
origSize = len(buf)
|
||||
buf = buf.replace("<hr id=answer>", "")
|
||||
hadHR = len(buf) != origSize
|
||||
# munge correct value
|
||||
parser = html.parser.HTMLParser()
|
||||
cor = self.mw.col.media.strip(self.typeCorrect)
|
||||
cor = re.sub("(\n|<br ?/?>|</?div>)+", " ", cor)
|
||||
cor = stripHTML(cor)
|
||||
# ensure we don't chomp multiple whitespace
|
||||
cor = cor.replace(" ", " ")
|
||||
cor = parser.unescape(cor)
|
||||
cor = html.unescape(cor)
|
||||
cor = cor.replace("\xa0", " ")
|
||||
cor = cor.strip()
|
||||
given = self.typedAnswer
|
||||
@ -434,7 +438,7 @@ Please run Tools>Empty Cards"""
|
||||
|
||||
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)
|
||||
if not matches:
|
||||
return None
|
||||
@ -452,24 +456,28 @@ Please run Tools>Empty Cards"""
|
||||
txt = ", ".join(matches)
|
||||
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
|
||||
given = ucd.normalize("NFC", given)
|
||||
correct = ucd.normalize("NFC", correct)
|
||||
s = difflib.SequenceMatcher(None, given, correct, autojunk=False)
|
||||
givenElems = []
|
||||
correctElems = []
|
||||
givenElems: List[Tuple[bool, str]] = []
|
||||
correctElems: List[Tuple[bool, str]] = []
|
||||
givenPoint = 0
|
||||
correctPoint = 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:
|
||||
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:
|
||||
array.append((True, str[start : start + cnt]))
|
||||
array.append((True, s[start : start + cnt]))
|
||||
|
||||
for x, y, cnt in s.get_matching_blocks():
|
||||
# if anything was missed in correct, pad given
|
||||
@ -486,17 +494,17 @@ Please run Tools>Empty Cards"""
|
||||
logGood(y, cnt, correct, 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."
|
||||
givenElems, correctElems = self.tokenizeComparison(given, correct)
|
||||
|
||||
def good(s):
|
||||
def good(s: str) -> str:
|
||||
return "<span class=typeGood>" + html.escape(s) + "</span>"
|
||||
|
||||
def bad(s):
|
||||
def bad(s: str) -> str:
|
||||
return "<span class=typeBad>" + html.escape(s) + "</span>"
|
||||
|
||||
def missed(s):
|
||||
def missed(s: str) -> str:
|
||||
return "<span class=typeMissed>" + html.escape(s) + "</span>"
|
||||
|
||||
if given == correct:
|
||||
@ -519,24 +527,24 @@ Please run Tools>Empty Cards"""
|
||||
res = "<div><code id=typeans>" + res + "</code></div>"
|
||||
return res
|
||||
|
||||
def _noLoneMarks(self, s):
|
||||
def _noLoneMarks(self, s: str) -> str:
|
||||
# ensure a combining character at the start does not join to
|
||||
# previous text
|
||||
if s and ucd.category(s[0]).startswith("M"):
|
||||
return "\xa0" + s
|
||||
return s
|
||||
|
||||
def _getTypedAnswer(self):
|
||||
def _getTypedAnswer(self) -> None:
|
||||
self.web.evalWithCallback("typeans ? typeans.value : null", self._onTypedAnswer)
|
||||
|
||||
def _onTypedAnswer(self, val):
|
||||
def _onTypedAnswer(self, val: None) -> None:
|
||||
self.typedAnswer = val or ""
|
||||
self._showAnswer()
|
||||
|
||||
# Bottom bar
|
||||
##########################################################################
|
||||
|
||||
def _bottomHTML(self):
|
||||
def _bottomHTML(self) -> str:
|
||||
return """
|
||||
<center id=outer>
|
||||
<table id=innertable width=100%% cellspacing=0 cellpadding=0>
|
||||
@ -565,7 +573,7 @@ time = %(time)d;
|
||||
time=self.card.timeTaken() // 1000,
|
||||
)
|
||||
|
||||
def _showAnswerButton(self):
|
||||
def _showAnswerButton(self) -> None:
|
||||
if not self.typeCorrect:
|
||||
self.bottom.web.setFocus()
|
||||
middle = """
|
||||
@ -587,17 +595,17 @@ time = %(time)d;
|
||||
self.bottom.web.eval("showQuestion(%s,%d);" % (json.dumps(middle), maxTime))
|
||||
self.bottom.web.adjustHeightToFit()
|
||||
|
||||
def _showEaseButtons(self):
|
||||
def _showEaseButtons(self) -> None:
|
||||
self.bottom.web.setFocus()
|
||||
middle = self._answerButtons()
|
||||
self.bottom.web.eval("showAnswer(%s);" % json.dumps(middle))
|
||||
|
||||
def _remaining(self):
|
||||
def _remaining(self) -> str:
|
||||
if not self.mw.col.conf["dueCounts"]:
|
||||
return ""
|
||||
if self.hadCardQueue:
|
||||
# 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:
|
||||
counts = list(self.mw.col.sched.counts(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]
|
||||
return ctxt
|
||||
|
||||
def _defaultEase(self):
|
||||
def _defaultEase(self) -> int:
|
||||
if self.mw.col.sched.answerButtons(self.card) == 4:
|
||||
return 3
|
||||
else:
|
||||
return 2
|
||||
|
||||
def _answerButtonList(self):
|
||||
def _answerButtonList(self) -> Sequence[Tuple[int, str]]:
|
||||
l = ((1, _("Again")),)
|
||||
cnt = self.mw.col.sched.answerButtons(self.card)
|
||||
if cnt == 2:
|
||||
@ -624,7 +632,7 @@ time = %(time)d;
|
||||
else:
|
||||
return l + ((2, _("Hard")), (3, _("Good")), (4, _("Easy")))
|
||||
|
||||
def _answerButtons(self):
|
||||
def _answerButtons(self) -> str:
|
||||
default = self._defaultEase()
|
||||
|
||||
def but(i, label):
|
||||
@ -652,7 +660,7 @@ time = %(time)d;
|
||||
<script>$(function () { $("#defease").focus(); });</script>"""
|
||||
return buf + script
|
||||
|
||||
def _buttonTime(self, i):
|
||||
def _buttonTime(self, i: int) -> str:
|
||||
if not self.mw.col.conf["estTimes"]:
|
||||
return "<div class=spacer></div>"
|
||||
txt = self.mw.col.sched.nextIvlStr(self.card, i, True) or " "
|
||||
@ -661,7 +669,7 @@ time = %(time)d;
|
||||
# Leeches
|
||||
##########################################################################
|
||||
|
||||
def onLeech(self, card):
|
||||
def onLeech(self, card: Card) -> None:
|
||||
# for now
|
||||
s = _("Card was a leech.")
|
||||
if card.queue < 0:
|
||||
@ -730,7 +738,7 @@ time = %(time)d;
|
||||
qtMenuShortcutWorkaround(m)
|
||||
m.exec_(QCursor.pos())
|
||||
|
||||
def _addMenuItems(self, m, rows):
|
||||
def _addMenuItems(self, m, rows) -> None:
|
||||
for row in rows:
|
||||
if not row:
|
||||
m.addSeparator()
|
||||
@ -753,10 +761,10 @@ time = %(time)d;
|
||||
a.setChecked(True)
|
||||
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))
|
||||
|
||||
def setFlag(self, flag):
|
||||
def setFlag(self, flag: int) -> None:
|
||||
# need to toggle off?
|
||||
if self.card.userFlag() == flag:
|
||||
flag = 0
|
||||
@ -764,7 +772,7 @@ time = %(time)d;
|
||||
self.card.flush()
|
||||
self._drawFlag()
|
||||
|
||||
def onMark(self):
|
||||
def onMark(self) -> None:
|
||||
f = self.card.note()
|
||||
if f.hasTag("marked"):
|
||||
f.delTag("marked")
|
||||
@ -773,19 +781,19 @@ time = %(time)d;
|
||||
f.flush()
|
||||
self._drawMark()
|
||||
|
||||
def onSuspend(self):
|
||||
def onSuspend(self) -> None:
|
||||
self.mw.checkpoint(_("Suspend"))
|
||||
self.mw.col.sched.suspendCards([c.id for c in self.card.note().cards()])
|
||||
tooltip(_("Note suspended."))
|
||||
self.mw.reset()
|
||||
|
||||
def onSuspendCard(self):
|
||||
def onSuspendCard(self) -> None:
|
||||
self.mw.checkpoint(_("Suspend"))
|
||||
self.mw.col.sched.suspendCards([self.card.id])
|
||||
tooltip(_("Card suspended."))
|
||||
self.mw.reset()
|
||||
|
||||
def onDelete(self):
|
||||
def onDelete(self) -> None:
|
||||
# need to check state because the shortcut is global to the main
|
||||
# window
|
||||
if self.mw.state != "review" or not self.card:
|
||||
@ -801,23 +809,24 @@ time = %(time)d;
|
||||
% cnt
|
||||
)
|
||||
|
||||
def onBuryCard(self):
|
||||
def onBuryCard(self) -> None:
|
||||
self.mw.checkpoint(_("Bury"))
|
||||
self.mw.col.sched.buryCards([self.card.id])
|
||||
self.mw.reset()
|
||||
tooltip(_("Card buried."))
|
||||
|
||||
def onBuryNote(self):
|
||||
def onBuryNote(self) -> None:
|
||||
self.mw.checkpoint(_("Bury"))
|
||||
self.mw.col.sched.buryNote(self.card.nid)
|
||||
self.mw.reset()
|
||||
tooltip(_("Note buried."))
|
||||
|
||||
def onRecordVoice(self):
|
||||
def onRecordVoice(self) -> None:
|
||||
self._recordedAudio = getAudio(self.mw, encode=False)
|
||||
self.onReplayRecorded()
|
||||
|
||||
def onReplayRecorded(self):
|
||||
def onReplayRecorded(self) -> None:
|
||||
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)
|
||||
|
@ -87,7 +87,7 @@ class AVPlayer:
|
||||
# audio be stopped?
|
||||
interrupt_current_audio = True
|
||||
|
||||
def __init__(self):
|
||||
def __init__(self) -> None:
|
||||
self._enqueued: List[AVTag] = []
|
||||
self.current_player: Optional[Player] = None
|
||||
|
||||
@ -112,7 +112,7 @@ class AVPlayer:
|
||||
self._enqueued.insert(0, SoundOrVideoTag(filename=filename))
|
||||
self._play_next_if_idle()
|
||||
|
||||
def toggle_pause(self):
|
||||
def toggle_pause(self) -> None:
|
||||
if self.current_player:
|
||||
self.current_player.toggle_pause()
|
||||
|
||||
@ -179,7 +179,7 @@ av_player = AVPlayer()
|
||||
|
||||
# return modified command array that points to bundled command, and return
|
||||
# required environment
|
||||
def _packagedCmd(cmd) -> Tuple[Any, Dict[str, str]]:
|
||||
def _packagedCmd(cmd: List[str]) -> Tuple[Any, Dict[str, str]]:
|
||||
cmd = cmd[:]
|
||||
env = os.environ.copy()
|
||||
if "LD_LIBRARY_PATH" in env:
|
||||
@ -205,7 +205,7 @@ def _packagedCmd(cmd) -> Tuple[Any, Dict[str, str]]:
|
||||
si = startup_info()
|
||||
|
||||
# osx throws interrupted system call errors frequently
|
||||
def retryWait(proc) -> Any:
|
||||
def retryWait(proc: subprocess.Popen) -> int:
|
||||
while 1:
|
||||
try:
|
||||
return proc.wait()
|
||||
@ -227,7 +227,7 @@ class SimpleProcessPlayer(Player): # pylint: disable=abstract-method
|
||||
args: List[str] = []
|
||||
env: Optional[Dict[str, str]] = None
|
||||
|
||||
def __init__(self, taskman: TaskManager):
|
||||
def __init__(self, taskman: TaskManager) -> None:
|
||||
self._taskman = taskman
|
||||
self._terminate_flag = False
|
||||
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)
|
||||
)
|
||||
|
||||
def stop(self):
|
||||
def stop(self) -> None:
|
||||
self._terminate_flag = True
|
||||
# block until stopped
|
||||
t = time.time()
|
||||
@ -252,7 +252,7 @@ class SimpleProcessPlayer(Player): # pylint: disable=abstract-method
|
||||
)
|
||||
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(
|
||||
lambda: gui_hooks.av_player_did_begin_playing(self, tag)
|
||||
)
|
||||
@ -359,7 +359,7 @@ class MpvManager(MPV, SoundOrVideoPlayer):
|
||||
def toggle_pause(self) -> None:
|
||||
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")
|
||||
|
||||
def on_idle(self) -> None:
|
||||
@ -401,7 +401,7 @@ class SimpleMplayerSlaveModePlayer(SimpleMplayerPlayer):
|
||||
)
|
||||
self._wait_for_termination(tag)
|
||||
|
||||
def command(self, *args) -> None:
|
||||
def command(self, *args: Any) -> None:
|
||||
"""Send a command over the slave interface.
|
||||
|
||||
The trailing newline is automatically added."""
|
||||
@ -412,7 +412,7 @@ class SimpleMplayerSlaveModePlayer(SimpleMplayerPlayer):
|
||||
def seek_relative(self, secs: int) -> None:
|
||||
self.command("seek", secs, 0)
|
||||
|
||||
def toggle_pause(self):
|
||||
def toggle_pause(self) -> None:
|
||||
self.command("pause")
|
||||
|
||||
|
||||
@ -458,12 +458,12 @@ class _Recorder:
|
||||
|
||||
|
||||
class PyAudioThreadedRecorder(threading.Thread):
|
||||
def __init__(self, startupDelay) -> None:
|
||||
def __init__(self, startupDelay: float) -> None:
|
||||
threading.Thread.__init__(self)
|
||||
self.startupDelay = startupDelay
|
||||
self.finish = False
|
||||
|
||||
def run(self) -> Any:
|
||||
def run(self) -> None:
|
||||
chunk = 1024
|
||||
p = pyaudio.PyAudio()
|
||||
|
||||
@ -499,7 +499,7 @@ class PyAudioRecorder(_Recorder):
|
||||
# discard first 250ms which may have pops/cracks
|
||||
startupDelay = 0.25
|
||||
|
||||
def __init__(self):
|
||||
def __init__(self) -> None:
|
||||
for t in recFiles + [processingSrc, processingDst]:
|
||||
try:
|
||||
os.unlink(t)
|
||||
@ -507,15 +507,15 @@ class PyAudioRecorder(_Recorder):
|
||||
pass
|
||||
self.encode = False
|
||||
|
||||
def start(self):
|
||||
def start(self) -> None:
|
||||
self.thread = PyAudioThreadedRecorder(startupDelay=self.startupDelay)
|
||||
self.thread.start()
|
||||
|
||||
def stop(self):
|
||||
def stop(self) -> None:
|
||||
self.thread.finish = True
|
||||
self.thread.join()
|
||||
|
||||
def file(self):
|
||||
def file(self) -> str:
|
||||
if self.encode:
|
||||
tgt = "rec%d.mp3" % time.time()
|
||||
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 first
|
||||
r = Recorder()
|
||||
@ -547,16 +547,16 @@ def getAudio(parent, encode=True):
|
||||
t = time.time()
|
||||
r.start()
|
||||
time.sleep(r.startupDelay)
|
||||
QApplication.instance().processEvents()
|
||||
QApplication.instance().processEvents() # type: ignore
|
||||
while not mb.clickedButton():
|
||||
txt = _("Recording...<br>Time: %0.1f")
|
||||
mb.setText(txt % (time.time() - t))
|
||||
mb.show()
|
||||
QApplication.instance().processEvents()
|
||||
QApplication.instance().processEvents() # type: ignore
|
||||
if mb.clickedButton() == mb.escapeButton():
|
||||
r.stop()
|
||||
r.cleanup()
|
||||
return
|
||||
return None
|
||||
saveGeom(mb, "audioRecorder")
|
||||
# ensure at least a second captured
|
||||
while time.time() - t < 1:
|
||||
|
@ -197,7 +197,7 @@ QTabWidget { background-color: %s; }
|
||||
|
||||
app.setPalette(palette)
|
||||
|
||||
def _update_stat_colors(self):
|
||||
def _update_stat_colors(self) -> None:
|
||||
import anki.stats as s
|
||||
|
||||
s.colLearn = self.str_color("new-count")
|
||||
|
@ -15,13 +15,13 @@ from aqt.webview import AnkiWebView
|
||||
|
||||
# wrapper class for set_bridge_command()
|
||||
class TopToolbar:
|
||||
def __init__(self, toolbar: Toolbar):
|
||||
def __init__(self, toolbar: Toolbar) -> None:
|
||||
self.toolbar = toolbar
|
||||
|
||||
|
||||
# wrapper class for set_bridge_command()
|
||||
class BottomToolbar:
|
||||
def __init__(self, toolbar: Toolbar):
|
||||
def __init__(self, toolbar: Toolbar) -> None:
|
||||
self.toolbar = toolbar
|
||||
|
||||
|
||||
@ -40,7 +40,7 @@ class Toolbar:
|
||||
buf: str = "",
|
||||
web_context: Optional[Any] = None,
|
||||
link_handler: Optional[Callable[[str], Any]] = None,
|
||||
):
|
||||
) -> None:
|
||||
web_context = web_context or TopToolbar(self)
|
||||
link_handler = link_handler or self._linkHandler
|
||||
self.web.set_bridge_command(link_handler, web_context)
|
||||
@ -90,7 +90,7 @@ class Toolbar:
|
||||
f"""{label}</a>"""
|
||||
)
|
||||
|
||||
def _centerLinks(self):
|
||||
def _centerLinks(self) -> str:
|
||||
links = [
|
||||
self.create_link(
|
||||
"decks",
|
||||
@ -149,15 +149,15 @@ class Toolbar:
|
||||
# Link handling
|
||||
######################################################################
|
||||
|
||||
def _linkHandler(self, link):
|
||||
def _linkHandler(self, link: str) -> bool:
|
||||
if link in self.link_handlers:
|
||||
self.link_handlers[link]()
|
||||
return False
|
||||
|
||||
def _deckLinkHandler(self):
|
||||
def _deckLinkHandler(self) -> None:
|
||||
self.mw.moveToState("deckBrowser")
|
||||
|
||||
def _studyLinkHandler(self):
|
||||
def _studyLinkHandler(self) -> None:
|
||||
# if overview already shown, switch to review
|
||||
if self.mw.state == "overview":
|
||||
self.mw.col.startTimebox()
|
||||
@ -165,16 +165,16 @@ class Toolbar:
|
||||
else:
|
||||
self.mw.onOverview()
|
||||
|
||||
def _addLinkHandler(self):
|
||||
def _addLinkHandler(self) -> None:
|
||||
self.mw.onAddCard()
|
||||
|
||||
def _browseLinkHandler(self):
|
||||
def _browseLinkHandler(self) -> None:
|
||||
self.mw.onBrowse()
|
||||
|
||||
def _statsLinkHandler(self):
|
||||
def _statsLinkHandler(self) -> None:
|
||||
self.mw.onStats()
|
||||
|
||||
def _syncLinkHandler(self):
|
||||
def _syncLinkHandler(self) -> None:
|
||||
self.mw.onSync()
|
||||
|
||||
# HTML & CSS
|
||||
@ -206,7 +206,7 @@ class BottomBar(Toolbar):
|
||||
buf: str = "",
|
||||
web_context: Optional[Any] = None,
|
||||
link_handler: Optional[Callable[[str], Any]] = None,
|
||||
):
|
||||
) -> None:
|
||||
# note: some screens may override this
|
||||
web_context = web_context or BottomToolbar(self)
|
||||
link_handler = link_handler or self._linkHandler
|
||||
|
@ -5,7 +5,7 @@ import dataclasses
|
||||
import json
|
||||
import math
|
||||
import sys
|
||||
from typing import Any, List, Optional, Tuple
|
||||
from typing import Any, Callable, List, Optional, Sequence, Tuple
|
||||
|
||||
from anki.lang import _
|
||||
from anki.utils import isLin, isMac, isWin
|
||||
@ -176,7 +176,7 @@ class AnkiWebView(QWebEngineView): # type: ignore
|
||||
self.onBridgeCmd: Callable[[str], Any] = self.defaultOnBridgeCmd
|
||||
|
||||
self._domDone = True
|
||||
self._pendingActions: List[Tuple[str, List[Any]]] = []
|
||||
self._pendingActions: List[Tuple[str, Sequence[Any]]] = []
|
||||
self.requiresCol = True
|
||||
self.setPage(self._page)
|
||||
|
||||
@ -258,13 +258,13 @@ class AnkiWebView(QWebEngineView): # type: ignore
|
||||
def dropEvent(self, evt):
|
||||
pass
|
||||
|
||||
def setHtml(self, html):
|
||||
def setHtml(self, html: str) -> None:
|
||||
# discard any previous pending actions
|
||||
self._pendingActions = []
|
||||
self._domDone = True
|
||||
self._queueAction("setHtml", html)
|
||||
|
||||
def _setHtml(self, html):
|
||||
def _setHtml(self, html: str) -> None:
|
||||
app = QApplication.instance()
|
||||
oldFocus = app.focusWidget()
|
||||
self._domDone = False
|
||||
@ -273,7 +273,7 @@ class AnkiWebView(QWebEngineView): # type: ignore
|
||||
if oldFocus:
|
||||
oldFocus.setFocus()
|
||||
|
||||
def zoomFactor(self):
|
||||
def zoomFactor(self) -> float:
|
||||
# overridden scale factor?
|
||||
webscale = os.environ.get("ANKI_WEBSCALE")
|
||||
if webscale:
|
||||
@ -295,7 +295,7 @@ class AnkiWebView(QWebEngineView): # type: ignore
|
||||
newFactor = desiredScale / qtIntScale
|
||||
return max(1, newFactor)
|
||||
|
||||
def _getQtIntScale(self, screen):
|
||||
def _getQtIntScale(self, screen) -> int:
|
||||
# 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,
|
||||
# and a dpi of 150% = 2x
|
||||
@ -430,13 +430,13 @@ body {{ zoom: {}; background: {}; {} }}
|
||||
fname
|
||||
)
|
||||
|
||||
def eval(self, js):
|
||||
def eval(self, js: str) -> None:
|
||||
self.evalWithCallback(js, None)
|
||||
|
||||
def evalWithCallback(self, js, cb):
|
||||
def evalWithCallback(self, js: str, cb: Callable) -> None:
|
||||
self._queueAction("eval", js, cb)
|
||||
|
||||
def _evalWithCallback(self, js, cb):
|
||||
def _evalWithCallback(self, js: str, cb: Callable[[Any], Any]) -> None:
|
||||
if cb:
|
||||
|
||||
def handler(val):
|
||||
@ -449,11 +449,11 @@ body {{ zoom: {}; background: {}; {} }}
|
||||
else:
|
||||
self.page().runJavaScript(js)
|
||||
|
||||
def _queueAction(self, name, *args):
|
||||
def _queueAction(self, name: str, *args: Any) -> None:
|
||||
self._pendingActions.append((name, args))
|
||||
self._maybeRunActions()
|
||||
|
||||
def _maybeRunActions(self):
|
||||
def _maybeRunActions(self) -> None:
|
||||
while self._pendingActions and self._domDone:
|
||||
name, args = self._pendingActions.pop(0)
|
||||
|
||||
@ -464,10 +464,10 @@ body {{ zoom: {}; background: {}; {} }}
|
||||
else:
|
||||
raise Exception("unknown action: {}".format(name))
|
||||
|
||||
def _openLinksExternally(self, url):
|
||||
def _openLinksExternally(self, url: str) -> None:
|
||||
openLink(url)
|
||||
|
||||
def _shouldIgnoreWebEvent(self):
|
||||
def _shouldIgnoreWebEvent(self) -> bool:
|
||||
# async web events may be received after the profile has been closed
|
||||
# or the underlying webview has been deleted
|
||||
from aqt import mw
|
||||
@ -499,18 +499,18 @@ body {{ zoom: {}; background: {}; {} }}
|
||||
else:
|
||||
return self.onBridgeCmd(cmd)
|
||||
|
||||
def defaultOnBridgeCmd(self, cmd: str) -> Any:
|
||||
def defaultOnBridgeCmd(self, cmd: str) -> None:
|
||||
print("unhandled bridge cmd:", cmd)
|
||||
|
||||
# legacy
|
||||
def resetHandlers(self):
|
||||
def resetHandlers(self) -> None:
|
||||
self.onBridgeCmd = self.defaultOnBridgeCmd
|
||||
self._bridge_context = None
|
||||
|
||||
def adjustHeightToFit(self):
|
||||
def adjustHeightToFit(self) -> None:
|
||||
self.evalWithCallback("$(document.body).height()", self._onHeight)
|
||||
|
||||
def _onHeight(self, qvar):
|
||||
def _onHeight(self, qvar: Optional[int]) -> None:
|
||||
from aqt import mw
|
||||
|
||||
if qvar is None:
|
||||
|
Loading…
Reference in New Issue
Block a user