diff --git a/pylib/anki/collection.py b/pylib/anki/collection.py index f06bd038d..bd5795024 100644 --- a/pylib/anki/collection.py +++ b/pylib/anki/collection.py @@ -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"]: diff --git a/qt/aqt/reviewer.py b/qt/aqt/reviewer.py index 316c33803..b02b96ab5 100644 --- a/qt/aqt/reviewer.py +++ b/qt/aqt/reviewer.py @@ -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("
", "") hadHR = len(buf) != origSize # munge correct value - parser = html.parser.HTMLParser() cor = self.mw.col.media.strip(self.typeCorrect) cor = re.sub("(\n|
|)+", " ", 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 "" + html.escape(s) + "" - def bad(s): + def bad(s: str) -> str: return "" + html.escape(s) + "" - def missed(s): + def missed(s: str) -> str: return "" + html.escape(s) + "" if given == correct: @@ -519,24 +527,24 @@ Please run Tools>Empty Cards""" res = "
" + res + "
" 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 """
@@ -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 + "%s" % 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; """ return buf + script - def _buttonTime(self, i): + def _buttonTime(self, i: int) -> str: if not self.mw.col.conf["estTimes"]: return "
" 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) diff --git a/qt/aqt/sound.py b/qt/aqt/sound.py index 16611950c..12d3e8214 100644 --- a/qt/aqt/sound.py +++ b/qt/aqt/sound.py @@ -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...
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: diff --git a/qt/aqt/theme.py b/qt/aqt/theme.py index 6a25f9d6d..492538534 100644 --- a/qt/aqt/theme.py +++ b/qt/aqt/theme.py @@ -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") diff --git a/qt/aqt/toolbar.py b/qt/aqt/toolbar.py index 17fc7dfc3..cec90d4b2 100644 --- a/qt/aqt/toolbar.py +++ b/qt/aqt/toolbar.py @@ -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}""" ) - 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 diff --git a/qt/aqt/webview.py b/qt/aqt/webview.py index 0efc96969..675b5ec52 100644 --- a/qt/aqt/webview.py +++ b/qt/aqt/webview.py @@ -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: