Merge pull request #483 from alanhdu/monkeytype

Add some typehints to qt/aqt [3/n]
This commit is contained in:
Damien Elmes 2020-03-02 15:38:21 +10:00 committed by GitHub
commit 4617a4b1f4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 128 additions and 118 deletions

View File

@ -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"]:

View File

@ -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(" ", "&nbsp;")
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 "&nbsp;"
@ -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)

View File

@ -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:

View File

@ -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")

View File

@ -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

View File

@ -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: