add a bunch of return types

This commit is contained in:
Damien Elmes 2021-02-01 23:28:21 +10:00
parent f15715fb07
commit a56b09b987
41 changed files with 359 additions and 338 deletions

View file

@ -10,7 +10,7 @@ import os
import sys import sys
import tempfile import tempfile
import traceback import traceback
from typing import Any, Callable, Dict, Optional, Union from typing import Any, Callable, Dict, List, Optional, Tuple, Union
import anki.lang import anki.lang
from anki import version as _version from anki import version as _version
@ -102,10 +102,10 @@ class DialogManager:
self._dialogs[name][1] = instance self._dialogs[name][1] = instance
return instance return instance
def markClosed(self, name: str): def markClosed(self, name: str) -> None:
self._dialogs[name] = [self._dialogs[name][0], None] self._dialogs[name] = [self._dialogs[name][0], None]
def allClosed(self): def allClosed(self) -> bool:
return not any(x[1] for x in self._dialogs.values()) return not any(x[1] for x in self._dialogs.values())
def closeAll(self, onsuccess: Callable[[], None]) -> Optional[bool]: def closeAll(self, onsuccess: Callable[[], None]) -> Optional[bool]:
@ -119,7 +119,7 @@ class DialogManager:
if not instance: if not instance:
continue continue
def callback(): def callback() -> None:
if self.allClosed(): if self.allClosed():
onsuccess() onsuccess()
else: else:
@ -189,12 +189,12 @@ def setupLangAndBackend(
pass pass
# add _ and ngettext globals used by legacy code # add _ and ngettext globals used by legacy code
def fn__(arg): def fn__(arg) -> None:
print("".join(traceback.format_stack()[-2])) print("".join(traceback.format_stack()[-2]))
print("_ global will break in the future; please see anki/lang.py") print("_ global will break in the future; please see anki/lang.py")
return arg return arg
def fn_ngettext(a, b, c): def fn_ngettext(a, b, c) -> None:
print("".join(traceback.format_stack()[-2])) print("".join(traceback.format_stack()[-2]))
print("ngettext global will break in the future; please see anki/lang.py") print("ngettext global will break in the future; please see anki/lang.py")
return b return b
@ -244,11 +244,11 @@ class AnkiApp(QApplication):
KEY = "anki" + checksum(getpass.getuser()) KEY = "anki" + checksum(getpass.getuser())
TMOUT = 30000 TMOUT = 30000
def __init__(self, argv): def __init__(self, argv) -> None:
QApplication.__init__(self, argv) QApplication.__init__(self, argv)
self._argv = argv self._argv = argv
def secondInstance(self): def secondInstance(self) -> bool:
# we accept only one command line argument. if it's missing, send # we accept only one command line argument. if it's missing, send
# a blank screen to just raise the existing window # a blank screen to just raise the existing window
opts, args = parseArgs(self._argv) opts, args = parseArgs(self._argv)
@ -267,7 +267,7 @@ class AnkiApp(QApplication):
self._srv.listen(self.KEY) self._srv.listen(self.KEY)
return False return False
def sendMsg(self, txt): def sendMsg(self, txt) -> bool:
sock = QLocalSocket(self) sock = QLocalSocket(self)
sock.connectToServer(self.KEY, QIODevice.WriteOnly) sock.connectToServer(self.KEY, QIODevice.WriteOnly)
if not sock.waitForConnected(self.TMOUT): if not sock.waitForConnected(self.TMOUT):
@ -286,7 +286,7 @@ class AnkiApp(QApplication):
sock.disconnectFromServer() sock.disconnectFromServer()
return True return True
def onRecv(self): def onRecv(self) -> None:
sock = self._srv.nextPendingConnection() sock = self._srv.nextPendingConnection()
if not sock.waitForReadyRead(self.TMOUT): if not sock.waitForReadyRead(self.TMOUT):
sys.stderr.write(sock.errorString()) sys.stderr.write(sock.errorString())
@ -298,14 +298,14 @@ class AnkiApp(QApplication):
# OS X file/url handler # OS X file/url handler
################################################## ##################################################
def event(self, evt): def event(self, evt) -> bool:
if evt.type() == QEvent.FileOpen: if evt.type() == QEvent.FileOpen:
self.appMsg.emit(evt.file() or "raise") # type: ignore self.appMsg.emit(evt.file() or "raise") # type: ignore
return True return True
return QApplication.event(self, evt) return QApplication.event(self, evt)
def parseArgs(argv): def parseArgs(argv) -> Tuple[argparse.Namespace, List[str]]:
"Returns (opts, args)." "Returns (opts, args)."
# py2app fails to strip this in some instances, then anki dies # py2app fails to strip this in some instances, then anki dies
# as there's no such profile # as there's no such profile
@ -330,7 +330,7 @@ def parseArgs(argv):
return parser.parse_known_args(argv[1:]) return parser.parse_known_args(argv[1:])
def setupGL(pm): def setupGL(pm) -> None:
if isMac: if isMac:
return return
@ -343,7 +343,7 @@ def setupGL(pm):
ctypes.CDLL("libGL.so.1", ctypes.RTLD_GLOBAL) ctypes.CDLL("libGL.so.1", ctypes.RTLD_GLOBAL)
# catch opengl errors # catch opengl errors
def msgHandler(category, ctx, msg): def msgHandler(category, ctx, msg) -> None:
if category == QtDebugMsg: if category == QtDebugMsg:
category = "debug" category = "debug"
elif category == QtInfoMsg: elif category == QtInfoMsg:
@ -400,7 +400,7 @@ def setupGL(pm):
PROFILE_CODE = os.environ.get("ANKI_PROFILE_CODE") PROFILE_CODE = os.environ.get("ANKI_PROFILE_CODE")
def write_profile_results(): def write_profile_results() -> None:
profiler.disable() profiler.disable()
profiler.dump_stats("anki.prof") profiler.dump_stats("anki.prof")
@ -408,7 +408,7 @@ def write_profile_results():
print("use 'bazel run qt:profile' to explore") print("use 'bazel run qt:profile' to explore")
def run(): def run() -> None:
try: try:
_run() _run()
except Exception as e: except Exception as e:
@ -420,7 +420,7 @@ def run():
) )
def _run(argv=None, exec=True): def _run(argv=None, exec=True) -> Optional[AnkiApp]:
"""Start AnkiQt application or reuse an existing instance if one exists. """Start AnkiQt application or reuse an existing instance if one exists.
If the function is invoked with exec=False, the AnkiQt will not enter If the function is invoked with exec=False, the AnkiQt will not enter
@ -441,12 +441,12 @@ def _run(argv=None, exec=True):
if opts.version: if opts.version:
print(f"Anki {appVersion}") print(f"Anki {appVersion}")
return return None
elif opts.syncserver: elif opts.syncserver:
from anki.syncserver import serve from anki.syncserver import serve
serve() serve()
return return None
if PROFILE_CODE: if PROFILE_CODE:
@ -465,7 +465,7 @@ def _run(argv=None, exec=True):
except AnkiRestart as error: except AnkiRestart as error:
if error.exitcode: if error.exitcode:
sys.exit(error.exitcode) sys.exit(error.exitcode)
return return None
except: except:
# will handle below # will handle below
traceback.print_exc() traceback.print_exc()
@ -500,7 +500,7 @@ def _run(argv=None, exec=True):
app = AnkiApp(argv) app = AnkiApp(argv)
if app.secondInstance(): if app.secondInstance():
# we've signaled the primary instance, so we should close # we've signaled the primary instance, so we should close
return return None
if not pm: if not pm:
QMessageBox.critical( QMessageBox.critical(
@ -508,7 +508,7 @@ def _run(argv=None, exec=True):
tr(TR.QT_MISC_ERROR), tr(TR.QT_MISC_ERROR),
tr(TR.PROFILES_COULD_NOT_CREATE_DATA_FOLDER), tr(TR.PROFILES_COULD_NOT_CREATE_DATA_FOLDER),
) )
return return None
# disable icons on mac; this must be done before window created # disable icons on mac; this must be done before window created
if isMac: if isMac:
@ -548,7 +548,7 @@ def _run(argv=None, exec=True):
tr(TR.QT_MISC_ERROR), tr(TR.QT_MISC_ERROR),
tr(TR.QT_MISC_NO_TEMP_FOLDER), tr(TR.QT_MISC_NO_TEMP_FOLDER),
) )
return return None
if pmLoadResult.firstTime: if pmLoadResult.firstTime:
pm.setDefaultLang(lang[0]) pm.setDefaultLang(lang[0])
@ -590,3 +590,5 @@ def _run(argv=None, exec=True):
if PROFILE_CODE: if PROFILE_CODE:
write_profile_results() write_profile_results()
return None

View file

@ -13,20 +13,20 @@ from aqt.utils import TR, disable_help_button, supportText, tooltip, tr
class ClosableQDialog(QDialog): class ClosableQDialog(QDialog):
def reject(self): def reject(self) -> None:
aqt.dialogs.markClosed("About") aqt.dialogs.markClosed("About")
QDialog.reject(self) QDialog.reject(self)
def accept(self): def accept(self) -> None:
aqt.dialogs.markClosed("About") aqt.dialogs.markClosed("About")
QDialog.accept(self) QDialog.accept(self)
def closeWithCallback(self, callback): def closeWithCallback(self, callback) -> None:
self.reject() self.reject()
callback() callback()
def show(mw): def show(mw) -> QDialog:
dialog = ClosableQDialog(mw) dialog = ClosableQDialog(mw)
disable_help_button(dialog) disable_help_button(dialog)
mw.setupDialogGC(dialog) mw.setupDialogGC(dialog)
@ -55,7 +55,7 @@ def show(mw):
modified = "mod" modified = "mod"
return f"{name} ['{addon.dir_name}', {installed}, '{addon.human_version}', {modified}]" return f"{name} ['{addon.dir_name}', {installed}, '{addon.human_version}', {modified}]"
def onCopy(): def onCopy() -> None:
addmgr = mw.addonManager addmgr = mw.addonManager
active = [] active = []
activeids = [] activeids = []

View file

@ -65,7 +65,7 @@ class AddCards(QDialog):
) )
self.deckChooser = aqt.deckchooser.DeckChooser(self.mw, self.form.deckArea) self.deckChooser = aqt.deckchooser.DeckChooser(self.mw, self.form.deckArea)
def helpRequested(self): def helpRequested(self) -> None:
openHelp(HelpPage.ADDING_CARD_AND_NOTE) openHelp(HelpPage.ADDING_CARD_AND_NOTE)
def setupButtons(self) -> None: def setupButtons(self) -> None:
@ -161,7 +161,7 @@ class AddCards(QDialog):
gui_hooks.add_cards_will_show_history_menu(self, m) gui_hooks.add_cards_will_show_history_menu(self, m)
m.exec_(self.historyButton.mapToGlobal(QPoint(0, 0))) m.exec_(self.historyButton.mapToGlobal(QPoint(0, 0)))
def editHistory(self, nid): def editHistory(self, nid) -> None:
self.mw.browser_search(SearchTerm(nid=nid)) self.mw.browser_search(SearchTerm(nid=nid))
def addNote(self, note) -> Optional[Note]: def addNote(self, note) -> Optional[Note]:
@ -225,7 +225,7 @@ class AddCards(QDialog):
QDialog.reject(self) QDialog.reject(self)
def ifCanClose(self, onOk: Callable) -> None: def ifCanClose(self, onOk: Callable) -> None:
def afterSave(): def afterSave() -> None:
ok = self.editor.fieldsAreBlank(self.previousNote) or askUser( ok = self.editor.fieldsAreBlank(self.previousNote) or askUser(
tr(TR.ADDING_CLOSE_AND_LOSE_CURRENT_INPUT), defaultno=True tr(TR.ADDING_CLOSE_AND_LOSE_CURRENT_INPUT), defaultno=True
) )
@ -234,8 +234,8 @@ class AddCards(QDialog):
self.editor.saveNow(afterSave) self.editor.saveNow(afterSave)
def closeWithCallback(self, cb): def closeWithCallback(self, cb) -> None:
def doClose(): def doClose() -> None:
self._reject() self._reject()
cb() cb()

View file

@ -605,7 +605,7 @@ class AddonManager:
def _addon_schema_path(self, dir: str) -> str: def _addon_schema_path(self, dir: str) -> str:
return os.path.join(self.addonsFolder(dir), "config.schema.json") return os.path.join(self.addonsFolder(dir), "config.schema.json")
def _addon_schema(self, dir: str): def _addon_schema(self, dir: str) -> Any:
path = self._addon_schema_path(dir) path = self._addon_schema_path(dir)
try: try:
if not os.path.exists(path): if not os.path.exists(path):

View file

@ -4,7 +4,7 @@
import copy import copy
import json import json
import re import re
from typing import Any, Dict, List, Optional from typing import Any, Dict, List, Match, Optional
import aqt import aqt
from anki.cards import Card from anki.cards import Card
@ -140,7 +140,7 @@ class CardLayout(QDialog):
combo.setEnabled(not self._isCloze()) combo.setEnabled(not self._isCloze())
self.ignore_change_signals = False self.ignore_change_signals = False
def _summarizedName(self, idx: int, tmpl: Dict): def _summarizedName(self, idx: int, tmpl: Dict) -> str:
return "{}: {}: {} -> {}".format( return "{}: {}: {} -> {}".format(
idx + 1, idx + 1,
tmpl["name"], tmpl["name"],
@ -284,7 +284,7 @@ class CardLayout(QDialog):
self.fill_fields_from_template() self.fill_fields_from_template()
def on_search_changed(self, text: str): def on_search_changed(self, text: str) -> None:
editor = self.tform.edit_area editor = self.tform.edit_area
if not editor.find(text): if not editor.find(text):
# try again from top # try again from top
@ -294,7 +294,7 @@ class CardLayout(QDialog):
if not editor.find(text): if not editor.find(text):
tooltip("No matches found.") tooltip("No matches found.")
def on_search_next(self): def on_search_next(self) -> None:
text = self.tform.search_edit.text() text = self.tform.search_edit.text()
self.on_search_changed(text) self.on_search_changed(text)
@ -341,7 +341,7 @@ class CardLayout(QDialog):
self.fill_empty_action_toggled = not self.fill_empty_action_toggled self.fill_empty_action_toggled = not self.fill_empty_action_toggled
self.on_preview_toggled() self.on_preview_toggled()
def on_night_mode_action_toggled(self): def on_night_mode_action_toggled(self) -> None:
self.night_mode_is_enabled = not self.night_mode_is_enabled self.night_mode_is_enabled = not self.night_mode_is_enabled
self.on_preview_toggled() self.on_preview_toggled()
@ -520,7 +520,7 @@ class CardLayout(QDialog):
txt = txt.replace("<hr id=answer>", "") txt = txt.replace("<hr id=answer>", "")
hadHR = origLen != len(txt) hadHR = origLen != len(txt)
def answerRepl(match): def answerRepl(match: Match) -> str:
res = self.mw.reviewer.correct("exomple", "an example") res = self.mw.reviewer.correct("exomple", "an example")
if hadHR: if hadHR:
res = "<hr id=answer>" + res res = "<hr id=answer>" + res
@ -556,14 +556,15 @@ class CardLayout(QDialog):
# Card operations # Card operations
###################################################################### ######################################################################
def onRemove(self): def onRemove(self) -> None:
if len(self.templates) < 2: if len(self.templates) < 2:
return showInfo(tr(TR.CARD_TEMPLATES_AT_LEAST_ONE_CARD_TYPE_IS)) showInfo(tr(TR.CARD_TEMPLATES_AT_LEAST_ONE_CARD_TYPE_IS))
return
def get_count(): def get_count() -> int:
return self.mm.template_use_count(self.model["id"], self.ord) return self.mm.template_use_count(self.model["id"], self.ord)
def on_done(fut): def on_done(fut) -> None:
card_cnt = fut.result() card_cnt = fut.result()
template = self.current_template() template = self.current_template()
@ -593,7 +594,7 @@ class CardLayout(QDialog):
self.redraw_everything() self.redraw_everything()
def onRename(self): def onRename(self) -> None:
template = self.current_template() template = self.current_template()
name = getOnlyText(tr(TR.ACTIONS_NEW_NAME), default=template["name"]).replace( name = getOnlyText(tr(TR.ACTIONS_NEW_NAME), default=template["name"]).replace(
'"', "" '"', ""
@ -604,7 +605,7 @@ class CardLayout(QDialog):
template["name"] = name template["name"] = name
self.redraw_everything() self.redraw_everything()
def onReorder(self): def onReorder(self) -> None:
n = len(self.templates) n = len(self.templates)
template = self.current_template() template = self.current_template()
current_pos = self.templates.index(template) + 1 current_pos = self.templates.index(template) + 1
@ -629,7 +630,7 @@ class CardLayout(QDialog):
self.ord = new_idx self.ord = new_idx
self.redraw_everything() self.redraw_everything()
def _newCardName(self): def _newCardName(self) -> str:
n = len(self.templates) + 1 n = len(self.templates) + 1
while 1: while 1:
name = without_unicode_isolation(tr(TR.CARD_TEMPLATES_CARD, val=n)) name = without_unicode_isolation(tr(TR.CARD_TEMPLATES_CARD, val=n))
@ -638,7 +639,7 @@ class CardLayout(QDialog):
n += 1 n += 1
return name return name
def onAddCard(self): def onAddCard(self) -> None:
cnt = self.mw.col.models.useCount(self.model) cnt = self.mw.col.models.useCount(self.model)
txt = tr(TR.CARD_TEMPLATES_THIS_WILL_CREATE_CARD_PROCEED, count=cnt) txt = tr(TR.CARD_TEMPLATES_THIS_WILL_CREATE_CARD_PROCEED, count=cnt)
if not askUser(txt): if not askUser(txt):
@ -654,12 +655,12 @@ class CardLayout(QDialog):
self.ord = len(self.templates) - 1 self.ord = len(self.templates) - 1
self.redraw_everything() self.redraw_everything()
def onFlip(self): def onFlip(self) -> None:
old = self.current_template() old = self.current_template()
self._flipQA(old, old) self._flipQA(old, old)
self.redraw_everything() self.redraw_everything()
def _flipQA(self, src, dst): def _flipQA(self, src, dst) -> None:
m = re.match("(?s)(.+)<hr id=answer>(.+)", src["afmt"]) m = re.match("(?s)(.+)<hr id=answer>(.+)", src["afmt"])
if not m: if not m:
showInfo(tr(TR.CARD_TEMPLATES_ANKI_COULDNT_FIND_THE_LINE_BETWEEN)) showInfo(tr(TR.CARD_TEMPLATES_ANKI_COULDNT_FIND_THE_LINE_BETWEEN))
@ -667,7 +668,6 @@ class CardLayout(QDialog):
self.change_tracker.mark_basic() self.change_tracker.mark_basic()
dst["afmt"] = "{{FrontSide}}\n\n<hr id=answer>\n\n%s" % src["qfmt"] dst["afmt"] = "{{FrontSide}}\n\n<hr id=answer>\n\n%s" % src["qfmt"]
dst["qfmt"] = m.group(2).strip() dst["qfmt"] = m.group(2).strip()
return True
def onMore(self) -> None: def onMore(self) -> None:
m = QMenu(self) m = QMenu(self)
@ -728,7 +728,7 @@ class CardLayout(QDialog):
if key in t: if key in t:
del t[key] del t[key]
def onTargetDeck(self): def onTargetDeck(self) -> None:
from aqt.tagedit import TagEdit from aqt.tagedit import TagEdit
t = self.current_template() t = self.current_template()
@ -760,7 +760,7 @@ class CardLayout(QDialog):
else: else:
t["did"] = self.col.decks.id(te.text()) t["did"] = self.col.decks.id(te.text())
def onAddField(self): def onAddField(self) -> None:
diag = QDialog(self) diag = QDialog(self)
form = aqt.forms.addfield.Ui_Dialog() form = aqt.forms.addfield.Ui_Dialog()
form.setupUi(diag) form.setupUi(diag)
@ -780,7 +780,7 @@ class CardLayout(QDialog):
form.size.value(), form.size.value(),
) )
def _addField(self, field, font, size): def _addField(self, field, font, size) -> None:
text = self.tform.edit_area.toPlainText() text = self.tform.edit_area.toPlainText()
text += "\n<div style='font-family: %s; font-size: %spx;'>{{%s}}</div>\n" % ( text += "\n<div style='font-family: %s; font-size: %spx;'>{{%s}}</div>\n" % (
font, font,
@ -795,10 +795,10 @@ class CardLayout(QDialog):
###################################################################### ######################################################################
def accept(self) -> None: def accept(self) -> None:
def save(): def save() -> None:
self.mm.save(self.model) self.mm.save(self.model)
def on_done(fut): def on_done(fut) -> None:
try: try:
fut.result() fut.result()
except TemplateError as e: except TemplateError as e:
@ -829,5 +829,5 @@ class CardLayout(QDialog):
self.rendered_card = None self.rendered_card = None
self.mw = None self.mw = None
def onHelp(self): def onHelp(self) -> None:
openHelp(HelpPage.TEMPLATES) openHelp(HelpPage.TEMPLATES)

View file

@ -57,7 +57,7 @@ class CustomStudy(QDialog):
typeShow = False typeShow = False
ok = tr(TR.CUSTOM_STUDY_OK) ok = tr(TR.CUSTOM_STUDY_OK)
def plus(num): def plus(num) -> str:
if num == 1000: if num == 1000:
num = "1000+" num = "1000+"
return "<b>" + str(num) + "</b>" return "<b>" + str(num) + "</b>"

View file

@ -9,7 +9,7 @@ from aqt.qt import *
from aqt.utils import showText, tooltip from aqt.utils import showText, tooltip
def on_progress(mw: aqt.main.AnkiQt): def on_progress(mw: aqt.main.AnkiQt) -> None:
progress = mw.col.latest_progress() progress = mw.col.latest_progress()
if progress.kind != ProgressKind.DatabaseCheck: if progress.kind != ProgressKind.DatabaseCheck:
return return
@ -24,14 +24,14 @@ def on_progress(mw: aqt.main.AnkiQt):
def check_db(mw: aqt.AnkiQt) -> None: def check_db(mw: aqt.AnkiQt) -> None:
def on_timer(): def on_timer() -> None:
on_progress(mw) on_progress(mw)
timer = QTimer(mw) timer = QTimer(mw)
qconnect(timer.timeout, on_timer) qconnect(timer.timeout, on_timer)
timer.start(100) timer.start(100)
def on_future_done(fut): def on_future_done(fut) -> None:
timer.stop() timer.stop()
ret, ok = fut.result() ret, ok = fut.result()

View file

@ -19,7 +19,7 @@ from aqt.utils import TR, askUser, getOnlyText, openLink, shortcut, showWarning,
class DeckBrowserBottomBar: class DeckBrowserBottomBar:
def __init__(self, deck_browser: DeckBrowser): def __init__(self, deck_browser: DeckBrowser) -> None:
self.deck_browser = deck_browser self.deck_browser = deck_browser
@ -51,14 +51,14 @@ class DeckBrowser:
self.bottom = BottomBar(mw, mw.bottomWeb) self.bottom = BottomBar(mw, mw.bottomWeb)
self.scrollPos = QPoint(0, 0) self.scrollPos = QPoint(0, 0)
def show(self): def show(self) -> None:
av_player.stop_and_clear_queue() av_player.stop_and_clear_queue()
self.web.set_bridge_command(self._linkHandler, self) self.web.set_bridge_command(self._linkHandler, self)
self._renderPage() self._renderPage()
# redraw top bar for theme change # redraw top bar for theme change
self.mw.toolbar.redraw() self.mw.toolbar.redraw()
def refresh(self): def refresh(self) -> None:
self._renderPage() self._renderPage()
# Event handlers # Event handlers
@ -90,7 +90,7 @@ class DeckBrowser:
self._collapse(int(arg)) self._collapse(int(arg))
return False return False
def _selDeck(self, did): def _selDeck(self, did) -> None:
self.mw.col.decks.select(did) self.mw.col.decks.select(did)
self.mw.onOverview() self.mw.onOverview()
@ -108,14 +108,14 @@ class DeckBrowser:
</center> </center>
""" """
def _renderPage(self, reuse=False): def _renderPage(self, reuse=False) -> None:
if not reuse: if not reuse:
self._dueTree = self.mw.col.sched.deck_due_tree() self._dueTree = self.mw.col.sched.deck_due_tree()
self.__renderPage(None) self.__renderPage(None)
return return
self.web.evalWithCallback("window.pageYOffset", self.__renderPage) self.web.evalWithCallback("window.pageYOffset", self.__renderPage)
def __renderPage(self, offset): def __renderPage(self, offset) -> None:
content = DeckBrowserContent( content = DeckBrowserContent(
tree=self._renderDeckTree(self._dueTree), tree=self._renderDeckTree(self._dueTree),
stats=self._renderStats(), stats=self._renderStats(),
@ -137,10 +137,10 @@ class DeckBrowser:
self._scrollToOffset(offset) self._scrollToOffset(offset)
gui_hooks.deck_browser_did_render(self) gui_hooks.deck_browser_did_render(self)
def _scrollToOffset(self, offset): def _scrollToOffset(self, offset) -> None:
self.web.eval("$(function() { window.scrollTo(0, %d, 'instant'); });" % offset) self.web.eval("$(function() { window.scrollTo(0, %d, 'instant'); });" % offset)
def _renderStats(self): def _renderStats(self) -> str:
return '<div id="studiedToday"><span>{}</span></div>'.format( return '<div id="studiedToday"><span>{}</span></div>'.format(
self.mw.col.studied_today(), self.mw.col.studied_today(),
) )
@ -170,7 +170,7 @@ class DeckBrowser:
due = node.review_count + node.learn_count due = node.review_count + node.learn_count
def indent(): def indent() -> str:
return "&nbsp;" * 6 * (node.level - 1) return "&nbsp;" * 6 * (node.level - 1)
if node.deck_id == ctx.current_deck_id: if node.deck_id == ctx.current_deck_id:
@ -202,7 +202,7 @@ class DeckBrowser:
node.name, node.name,
) )
# due counts # due counts
def nonzeroColour(cnt, klass): def nonzeroColour(cnt: int, klass: str) -> str:
if not cnt: if not cnt:
klass = "zero-count" klass = "zero-count"
return f'<span class="{klass}">{cnt}</span>' return f'<span class="{klass}">{cnt}</span>'
@ -222,7 +222,7 @@ class DeckBrowser:
buf += self._render_deck_node(child, ctx) buf += self._render_deck_node(child, ctx)
return buf return buf
def _topLevelDragRow(self): def _topLevelDragRow(self) -> str:
return "<tr class='top-level-drag-row'><td colspan='6'>&nbsp;</td></tr>" return "<tr class='top-level-drag-row'><td colspan='6'>&nbsp;</td></tr>"
# Options # Options
@ -260,7 +260,7 @@ class DeckBrowser:
return return
self.show() self.show()
def _options(self, did): def _options(self, did) -> None:
# select the deck first, because the dyn deck conf assumes the deck # select the deck first, because the dyn deck conf assumes the deck
# we're editing is the current one # we're editing is the current one
self.mw.col.decks.select(did) self.mw.col.decks.select(did)
@ -297,10 +297,10 @@ class DeckBrowser:
def _delete(self, did: int) -> None: def _delete(self, did: int) -> None:
if self.ask_delete_deck(did): if self.ask_delete_deck(did):
def do_delete(): def do_delete() -> None:
return self.mw.col.decks.rem(did, True) return self.mw.col.decks.rem(did, True)
def on_done(fut: Future): def on_done(fut: Future) -> None:
self.show() self.show()
res = fut.result() # Required to check for errors res = fut.result() # Required to check for errors
@ -316,7 +316,7 @@ class DeckBrowser:
["Ctrl+Shift+I", "import", tr(TR.DECKS_IMPORT_FILE)], ["Ctrl+Shift+I", "import", tr(TR.DECKS_IMPORT_FILE)],
] ]
def _drawButtons(self): def _drawButtons(self) -> None:
buf = "" buf = ""
drawLinks = deepcopy(self.drawLinks) drawLinks = deepcopy(self.drawLinks)
for b in drawLinks: for b in drawLinks:
@ -332,5 +332,5 @@ class DeckBrowser:
web_context=DeckBrowserBottomBar(self), web_context=DeckBrowserBottomBar(self),
) )
def _onShared(self): def _onShared(self) -> None:
openLink(aqt.appShared + "decks/") openLink(aqt.appShared + "decks/")

View file

@ -2,12 +2,13 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html # License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
from operator import itemgetter from operator import itemgetter
from typing import Any, Dict from typing import Any, Dict, Optional
from PyQt5.QtWidgets import QLineEdit from PyQt5.QtWidgets import QLineEdit
import aqt import aqt
from anki.consts import NEW_CARDS_RANDOM from anki.consts import NEW_CARDS_RANDOM
from anki.decks import DeckConfig
from anki.lang import without_unicode_isolation from anki.lang import without_unicode_isolation
from aqt import gui_hooks from aqt import gui_hooks
from aqt.qt import * from aqt.qt import *
@ -28,7 +29,7 @@ from aqt.utils import (
class DeckConf(QDialog): class DeckConf(QDialog):
def __init__(self, mw: aqt.AnkiQt, deck: Dict): def __init__(self, mw: aqt.AnkiQt, deck: Dict) -> None:
QDialog.__init__(self, mw) QDialog.__init__(self, mw)
self.mw = mw self.mw = mw
self.deck = deck self.deck = deck
@ -60,7 +61,7 @@ class DeckConf(QDialog):
self.exec_() self.exec_()
saveGeom(self, "deckconf") saveGeom(self, "deckconf")
def setupCombos(self): def setupCombos(self) -> None:
import anki.consts as cs import anki.consts as cs
f = self.form f = self.form
@ -70,12 +71,12 @@ class DeckConf(QDialog):
# Conf list # Conf list
###################################################################### ######################################################################
def setupConfs(self): def setupConfs(self) -> None:
qconnect(self.form.dconf.currentIndexChanged, self.onConfChange) qconnect(self.form.dconf.currentIndexChanged, self.onConfChange)
self.conf = None self.conf: Optional[DeckConfig] = None
self.loadConfs() self.loadConfs()
def loadConfs(self): def loadConfs(self) -> None:
current = self.deck["conf"] current = self.deck["conf"]
self.confList = self.mw.col.decks.allConf() self.confList = self.mw.col.decks.allConf()
self.confList.sort(key=itemgetter("name")) self.confList.sort(key=itemgetter("name"))
@ -92,7 +93,7 @@ class DeckConf(QDialog):
self._origNewOrder = self.confList[startOn]["new"]["order"] self._origNewOrder = self.confList[startOn]["new"]["order"]
self.onConfChange(startOn) self.onConfChange(startOn)
def confOpts(self): def confOpts(self) -> None:
m = QMenu(self.mw) m = QMenu(self.mw)
a = m.addAction(tr(TR.ACTIONS_ADD)) a = m.addAction(tr(TR.ACTIONS_ADD))
qconnect(a.triggered, self.addGroup) qconnect(a.triggered, self.addGroup)
@ -106,7 +107,7 @@ class DeckConf(QDialog):
a.setEnabled(False) a.setEnabled(False)
m.exec_(QCursor.pos()) m.exec_(QCursor.pos())
def onConfChange(self, idx): def onConfChange(self, idx) -> None:
if self.ignoreConfChange: if self.ignoreConfChange:
return return
if self.conf: if self.conf:
@ -159,7 +160,7 @@ class DeckConf(QDialog):
self.saveConf() self.saveConf()
self.loadConfs() self.loadConfs()
def setChildren(self): def setChildren(self) -> None:
if not askUser(tr(TR.SCHEDULING_SET_ALL_DECKS_BELOW_TO, val=self.deck["name"])): if not askUser(tr(TR.SCHEDULING_SET_ALL_DECKS_BELOW_TO, val=self.deck["name"])):
return return
for did in self.childDids: for did in self.childDids:
@ -173,8 +174,8 @@ class DeckConf(QDialog):
# Loading # Loading
################################################## ##################################################
def listToUser(self, l): def listToUser(self, l) -> str:
def num_to_user(n: Union[int, float]): def num_to_user(n: Union[int, float]) -> str:
if n == round(n): if n == round(n):
return str(int(n)) return str(int(n))
else: else:
@ -182,7 +183,7 @@ class DeckConf(QDialog):
return " ".join(map(num_to_user, l)) return " ".join(map(num_to_user, l))
def parentLimText(self, type="new"): def parentLimText(self, type="new") -> str:
# top level? # top level?
if "::" not in self.deck["name"]: if "::" not in self.deck["name"]:
return "" return ""
@ -196,7 +197,7 @@ class DeckConf(QDialog):
lim = min(x, lim) lim = min(x, lim)
return tr(TR.SCHEDULING_PARENT_LIMIT, val=lim) return tr(TR.SCHEDULING_PARENT_LIMIT, val=lim)
def loadConf(self): def loadConf(self) -> None:
self.conf = self.mw.col.decks.confForDid(self.deck["id"]) self.conf = self.mw.col.decks.confForDid(self.deck["id"])
# new # new
c = self.conf["new"] c = self.conf["new"]
@ -238,7 +239,7 @@ class DeckConf(QDialog):
f.desc.setPlainText(self.deck["desc"]) f.desc.setPlainText(self.deck["desc"])
gui_hooks.deck_conf_did_load_config(self, self.deck, self.conf) gui_hooks.deck_conf_did_load_config(self, self.deck, self.conf)
def onRestore(self): def onRestore(self) -> None:
self.mw.progress.start() self.mw.progress.start()
self.mw.col.decks.restoreToDefault(self.conf) self.mw.col.decks.restoreToDefault(self.conf)
self.mw.progress.finish() self.mw.progress.finish()
@ -247,7 +248,7 @@ class DeckConf(QDialog):
# New order # New order
################################################## ##################################################
def onNewOrderChanged(self, new): def onNewOrderChanged(self, new) -> None:
old = self.conf["new"]["order"] old = self.conf["new"]["order"]
if old == new: if old == new:
return return
@ -280,7 +281,7 @@ class DeckConf(QDialog):
return return
conf[key] = ret conf[key] = ret
def saveConf(self): def saveConf(self) -> None:
# new # new
c = self.conf["new"] c = self.conf["new"]
f = self.form f = self.form
@ -324,10 +325,10 @@ class DeckConf(QDialog):
self.mw.col.decks.save(self.deck) self.mw.col.decks.save(self.deck)
self.mw.col.decks.save(self.conf) self.mw.col.decks.save(self.conf)
def reject(self): def reject(self) -> None:
self.accept() self.accept()
def accept(self): def accept(self) -> None:
self.saveConf() self.saveConf()
self.mw.reset() self.mw.reset()
QDialog.accept(self) QDialog.accept(self)

View file

@ -145,7 +145,7 @@ class DeckConf(QDialog):
self.mw.col.decks.save(d) self.mw.col.decks.save(d)
def reject(self): def reject(self) -> None:
self.ok = False self.ok = False
QDialog.reject(self) QDialog.reject(self)
@ -164,7 +164,7 @@ class DeckConf(QDialog):
# Step load/save - fixme: share with std options screen # Step load/save - fixme: share with std options screen
######################################################## ########################################################
def listToUser(self, l): def listToUser(self, l) -> str:
return " ".join([str(x) for x in l]) return " ".join([str(x) for x in l])
def userToList(self, w, minSize=1) -> Optional[List[Union[float, int]]]: def userToList(self, w, minSize=1) -> Optional[List[Union[float, int]]]:

View file

@ -48,7 +48,7 @@ class EditCurrent(QDialog):
return return
self.editor.setNote(n) self.editor.setNote(n)
def reopen(self, mw): def reopen(self, mw) -> None:
tooltip("Please finish editing the existing card first.") tooltip("Please finish editing the existing card first.")
self.onReset() self.onReset()
@ -74,8 +74,8 @@ class EditCurrent(QDialog):
aqt.dialogs.markClosed("EditCurrent") aqt.dialogs.markClosed("EditCurrent")
QDialog.reject(self) QDialog.reject(self)
def closeWithCallback(self, onsuccess): def closeWithCallback(self, onsuccess) -> None:
def callback(): def callback() -> None:
self._saveAndClose() self._saveAndClose()
onsuccess() onsuccess()

View file

@ -15,7 +15,7 @@ from aqt.utils import TR, disable_help_button, restoreGeom, saveGeom, tooltip, t
def show_empty_cards(mw: aqt.main.AnkiQt) -> None: def show_empty_cards(mw: aqt.main.AnkiQt) -> None:
mw.progress.start() mw.progress.start()
def on_done(fut): def on_done(fut) -> None:
mw.progress.finish() mw.progress.finish()
report: EmptyCardsReport = fut.result() report: EmptyCardsReport = fut.result()
if not report.notes: if not report.notes:
@ -54,7 +54,7 @@ class EmptyCardsDialog(QDialog):
style = "<style>.allempty { color: red; }</style>" style = "<style>.allempty { color: red; }</style>"
self.form.webview.stdHtml(style + html, context=self) self.form.webview.stdHtml(style + html, context=self)
def on_finished(code): def on_finished(code) -> None:
saveGeom(self, "emptycards") saveGeom(self, "emptycards")
qconnect(self.finished, on_finished) qconnect(self.finished, on_finished)
@ -65,16 +65,16 @@ class EmptyCardsDialog(QDialog):
self._delete_button.setAutoDefault(False) self._delete_button.setAutoDefault(False)
self._delete_button.clicked.connect(self._on_delete) self._delete_button.clicked.connect(self._on_delete)
def _on_note_link_clicked(self, link): def _on_note_link_clicked(self, link) -> None:
self.mw.browser_search(link) self.mw.browser_search(link)
def _on_delete(self) -> None: def _on_delete(self) -> None:
self.mw.progress.start() self.mw.progress.start()
def delete(): def delete() -> int:
return self._delete_cards(self.form.keep_notes.isChecked()) return self._delete_cards(self.form.keep_notes.isChecked())
def on_done(fut): def on_done(fut) -> None:
self.mw.progress.finish() self.mw.progress.finish()
try: try:
count = fut.result() count = fut.result()

View file

@ -16,7 +16,7 @@ from aqt.utils import TR, showText, showWarning, supportText, tr
if not os.environ.get("DEBUG"): if not os.environ.get("DEBUG"):
def excepthook(etype, val, tb): def excepthook(etype, val, tb) -> None:
sys.stderr.write( sys.stderr.write(
"Caught exception:\n%s\n" "Caught exception:\n%s\n"
% ("".join(traceback.format_exception(etype, val, tb))) % ("".join(traceback.format_exception(etype, val, tb)))
@ -65,7 +65,7 @@ class ErrorHandler(QObject):
self.timer.setSingleShot(True) self.timer.setSingleShot(True)
self.timer.start() self.timer.start()
def tempFolderMsg(self): def tempFolderMsg(self) -> str:
return tr(TR.QT_MISC_UNABLE_TO_ACCESS_ANKI_MEDIA_FOLDER) return tr(TR.QT_MISC_UNABLE_TO_ACCESS_ANKI_MEDIA_FOLDER)
def onTimeout(self) -> None: def onTimeout(self) -> None:
@ -105,7 +105,7 @@ class ErrorHandler(QObject):
txt = txt + "<div style='white-space: pre-wrap'>" + error + "</div>" txt = txt + "<div style='white-space: pre-wrap'>" + error + "</div>"
showText(txt, type="html", copyBtn=True) showText(txt, type="html", copyBtn=True)
def _addonText(self, error): def _addonText(self, error: str) -> str:
matches = re.findall(r"addons21/(.*?)/", error) matches = re.findall(r"addons21/(.*?)/", error)
if not matches: if not matches:
return "" return ""

View file

@ -42,7 +42,7 @@ class ExportDialog(QDialog):
self.setup(did) self.setup(did)
self.exec_() self.exec_()
def setup(self, did: Optional[int]): def setup(self, did: Optional[int]) -> None:
self.exporters = exporters(self.col) self.exporters = exporters(self.col)
# if a deck specified, start with .apkg type selected # if a deck specified, start with .apkg type selected
idx = 0 idx = 0
@ -155,17 +155,17 @@ class ExportDialog(QDialog):
os.unlink(file) os.unlink(file)
# progress handler # progress handler
def exported_media(cnt): def exported_media(cnt) -> None:
self.mw.taskman.run_on_main( self.mw.taskman.run_on_main(
lambda: self.mw.progress.update( lambda: self.mw.progress.update(
label=tr(TR.EXPORTING_EXPORTED_MEDIA_FILE, count=cnt) label=tr(TR.EXPORTING_EXPORTED_MEDIA_FILE, count=cnt)
) )
) )
def do_export(): def do_export() -> None:
self.exporter.exportInto(file) self.exporter.exportInto(file)
def on_done(future: Future): def on_done(future: Future) -> None:
self.mw.progress.finish() self.mw.progress.finish()
hooks.media_files_did_export.remove(exported_media) hooks.media_files_did_export.remove(exported_media)
# raises if exporter failed # raises if exporter failed

View file

@ -23,7 +23,7 @@ from aqt.utils import (
class FieldDialog(QDialog): class FieldDialog(QDialog):
def __init__(self, mw: AnkiQt, nt: NoteType, parent=None): def __init__(self, mw: AnkiQt, nt: NoteType, parent=None) -> None:
QDialog.__init__(self, parent or mw) QDialog.__init__(self, parent or mw)
self.mw = mw self.mw = mw
self.col = self.mw.col self.col = self.mw.col
@ -68,7 +68,7 @@ class FieldDialog(QDialog):
qconnect(f.sortField.clicked, self.onSortField) qconnect(f.sortField.clicked, self.onSortField)
qconnect(f.buttonBox.helpRequested, self.onHelp) qconnect(f.buttonBox.helpRequested, self.onHelp)
def onDrop(self, ev): def onDrop(self, ev) -> None:
fieldList = self.form.fieldList fieldList = self.form.fieldList
indicatorPos = fieldList.dropIndicatorPosition() indicatorPos = fieldList.dropIndicatorPosition()
dropPos = fieldList.indexAt(ev.pos()).row() dropPos = fieldList.indexAt(ev.pos()).row()
@ -113,7 +113,7 @@ class FieldDialog(QDialog):
return None return None
return txt return txt
def onRename(self): def onRename(self) -> None:
idx = self.currentIdx idx = self.currentIdx
f = self.model["flds"][idx] f = self.model["flds"][idx]
name = self._uniqueName(tr(TR.ACTIONS_NEW_NAME), self.currentIdx, f["name"]) name = self._uniqueName(tr(TR.ACTIONS_NEW_NAME), self.currentIdx, f["name"])
@ -141,9 +141,10 @@ class FieldDialog(QDialog):
self.fillFields() self.fillFields()
self.form.fieldList.setCurrentRow(len(self.model["flds"]) - 1) self.form.fieldList.setCurrentRow(len(self.model["flds"]) - 1)
def onDelete(self): def onDelete(self) -> None:
if len(self.model["flds"]) < 2: if len(self.model["flds"]) < 2:
return showWarning(tr(TR.FIELDS_NOTES_REQUIRE_AT_LEAST_ONE_FIELD)) showWarning(tr(TR.FIELDS_NOTES_REQUIRE_AT_LEAST_ONE_FIELD))
return
count = self.mm.useCount(self.model) count = self.mm.useCount(self.model)
c = tr(TR.BROWSING_NOTE_COUNT, count=count) c = tr(TR.BROWSING_NOTE_COUNT, count=count)
if not askUser(tr(TR.FIELDS_DELETE_FIELD_FROM, val=c)): if not askUser(tr(TR.FIELDS_DELETE_FIELD_FROM, val=c)):
@ -157,7 +158,7 @@ class FieldDialog(QDialog):
self.fillFields() self.fillFields()
self.form.fieldList.setCurrentRow(0) self.form.fieldList.setCurrentRow(0)
def onPosition(self, delta=-1): def onPosition(self, delta=-1) -> None:
idx = self.currentIdx idx = self.currentIdx
l = len(self.model["flds"]) l = len(self.model["flds"])
txt = getOnlyText(tr(TR.FIELDS_NEW_POSITION_1, val=l), default=str(idx + 1)) txt = getOnlyText(tr(TR.FIELDS_NEW_POSITION_1, val=l), default=str(idx + 1))
@ -171,16 +172,16 @@ class FieldDialog(QDialog):
return return
self.moveField(pos) self.moveField(pos)
def onSortField(self): def onSortField(self) -> None:
if not self.change_tracker.mark_schema(): if not self.change_tracker.mark_schema():
return False return
# don't allow user to disable; it makes no sense # don't allow user to disable; it makes no sense
self.form.sortField.setChecked(True) self.form.sortField.setChecked(True)
self.mm.set_sort_index(self.model, self.form.fieldList.currentRow()) self.mm.set_sort_index(self.model, self.form.fieldList.currentRow())
def moveField(self, pos): def moveField(self, pos) -> None:
if not self.change_tracker.mark_schema(): if not self.change_tracker.mark_schema():
return False return
self.saveField() self.saveField()
f = self.model["flds"][self.currentIdx] f = self.model["flds"][self.currentIdx]
self.mm.reposition_field(self.model, f, pos - 1) self.mm.reposition_field(self.model, f, pos - 1)
@ -231,10 +232,10 @@ class FieldDialog(QDialog):
def accept(self) -> None: def accept(self) -> None:
self.saveField() self.saveField()
def save(): def save() -> None:
self.mm.save(self.model) self.mm.save(self.model)
def on_done(fut): def on_done(fut) -> None:
try: try:
fut.result() fut.result()
except TemplateError as e: except TemplateError as e:
@ -247,5 +248,5 @@ class FieldDialog(QDialog):
self.mw.taskman.with_progress(save, on_done, self) self.mw.taskman.with_progress(save, on_done, self)
def onHelp(self): def onHelp(self) -> None:
openHelp(HelpPage.CUSTOMIZING_FIELDS) openHelp(HelpPage.CUSTOMIZING_FIELDS)

View file

@ -35,7 +35,7 @@ from aqt.utils import (
class ChangeMap(QDialog): class ChangeMap(QDialog):
def __init__(self, mw: AnkiQt, model, current): def __init__(self, mw: AnkiQt, model, current) -> None:
QDialog.__init__(self, mw, Qt.Window) QDialog.__init__(self, mw, Qt.Window)
self.mw = mw self.mw = mw
self.model = model self.model = model
@ -60,11 +60,11 @@ class ChangeMap(QDialog):
self.frm.fields.setCurrentRow(n + 1) self.frm.fields.setCurrentRow(n + 1)
self.field: Optional[str] = None self.field: Optional[str] = None
def getField(self): def getField(self) -> str:
self.exec_() self.exec_()
return self.field return self.field
def accept(self): def accept(self) -> None:
row = self.frm.fields.currentRow() row = self.frm.fields.currentRow()
if row < len(self.model["flds"]): if row < len(self.model["flds"]):
self.field = self.model["flds"][row]["name"] self.field = self.model["flds"][row]["name"]
@ -74,7 +74,7 @@ class ChangeMap(QDialog):
self.field = None self.field = None
QDialog.accept(self) QDialog.accept(self)
def reject(self): def reject(self) -> None:
self.accept() self.accept()
@ -119,7 +119,7 @@ class ImportDialog(QDialog):
self.importer.initMapping() self.importer.initMapping()
self.showMapping() self.showMapping()
def onDelimiter(self): def onDelimiter(self) -> None:
str = ( str = (
getOnlyText( getOnlyText(
tr(TR.IMPORTING_BY_DEFAULT_ANKI_WILL_DETECT_THE), tr(TR.IMPORTING_BY_DEFAULT_ANKI_WILL_DETECT_THE),
@ -136,7 +136,7 @@ class ImportDialog(QDialog):
return return
self.hideMapping() self.hideMapping()
def updateDelim(): def updateDelim() -> None:
self.importer.delimiter = str self.importer.delimiter = str
self.importer.updateDelimiter() self.importer.updateDelimiter()
@ -183,7 +183,7 @@ class ImportDialog(QDialog):
self.mw.progress.start() self.mw.progress.start()
self.mw.checkpoint(tr(TR.ACTIONS_IMPORT)) self.mw.checkpoint(tr(TR.ACTIONS_IMPORT))
def on_done(future: Future): def on_done(future: Future) -> None:
self.mw.progress.finish() self.mw.progress.finish()
try: try:
@ -221,7 +221,7 @@ class ImportDialog(QDialog):
self.mapbox.setContentsMargins(0, 0, 0, 0) self.mapbox.setContentsMargins(0, 0, 0, 0)
self.mapwidget: Optional[QWidget] = None self.mapwidget: Optional[QWidget] = None
def hideMapping(self): def hideMapping(self) -> None:
self.frm.mappingGroup.hide() self.frm.mappingGroup.hide()
def showMapping( def showMapping(
@ -258,7 +258,7 @@ class ImportDialog(QDialog):
self.grid.addWidget(button, num, 2) self.grid.addWidget(button, num, 2)
qconnect(button.clicked, lambda _, s=self, n=num: s.changeMappingNum(n)) qconnect(button.clicked, lambda _, s=self, n=num: s.changeMappingNum(n))
def changeMappingNum(self, n): def changeMappingNum(self, n) -> None:
f = ChangeMap(self.mw, self.importer.model, self.mapping[n]).getField() f = ChangeMap(self.mw, self.importer.model, self.mapping[n]).getField()
try: try:
# make sure we don't have it twice # make sure we don't have it twice
@ -270,7 +270,7 @@ class ImportDialog(QDialog):
if getattr(self.importer, "delimiter", False): if getattr(self.importer, "delimiter", False):
self.savedDelimiter = self.importer.delimiter self.savedDelimiter = self.importer.delimiter
def updateDelim(): def updateDelim() -> None:
self.importer.delimiter = self.savedDelimiter self.importer.delimiter = self.savedDelimiter
self.showMapping(hook=updateDelim, keepMapping=True) self.showMapping(hook=updateDelim, keepMapping=True)
@ -283,17 +283,17 @@ class ImportDialog(QDialog):
gui_hooks.current_note_type_did_change.remove(self.modelChanged) gui_hooks.current_note_type_did_change.remove(self.modelChanged)
QDialog.reject(self) QDialog.reject(self)
def helpRequested(self): def helpRequested(self) -> None:
openHelp(HelpPage.IMPORTING) openHelp(HelpPage.IMPORTING)
def importModeChanged(self, newImportMode): def importModeChanged(self, newImportMode) -> None:
if newImportMode == 0: if newImportMode == 0:
self.frm.tagModified.setEnabled(True) self.frm.tagModified.setEnabled(True)
else: else:
self.frm.tagModified.setEnabled(False) self.frm.tagModified.setEnabled(False)
def showUnicodeWarning(): def showUnicodeWarning() -> None:
"""Shorthand to show a standard warning.""" """Shorthand to show a standard warning."""
showWarning(tr(TR.IMPORTING_SELECTED_FILE_WAS_NOT_IN_UTF8)) showWarning(tr(TR.IMPORTING_SELECTED_FILE_WAS_NOT_IN_UTF8))
@ -374,7 +374,7 @@ def importFile(mw: AnkiQt, file: str) -> None:
# importing non-colpkg files # importing non-colpkg files
mw.progress.start(immediate=True) mw.progress.start(immediate=True)
def on_done(future: Future): def on_done(future: Future) -> None:
mw.progress.finish() mw.progress.finish()
try: try:
future.result() future.result()
@ -405,7 +405,7 @@ def importFile(mw: AnkiQt, file: str) -> None:
mw.taskman.run_in_background(importer.run, on_done) mw.taskman.run_in_background(importer.run, on_done)
def invalidZipMsg(): def invalidZipMsg() -> str:
return tr(TR.IMPORTING_THIS_FILE_DOES_NOT_APPEAR_TO) return tr(TR.IMPORTING_THIS_FILE_DOES_NOT_APPEAR_TO)
@ -430,14 +430,14 @@ def setupApkgImport(mw: AnkiQt, importer: AnkiPackageImporter) -> bool:
return False return False
def replaceWithApkg(mw, file, backup): def replaceWithApkg(mw, file, backup) -> None:
mw.unloadCollection(lambda: _replaceWithApkg(mw, file, backup)) mw.unloadCollection(lambda: _replaceWithApkg(mw, file, backup))
def _replaceWithApkg(mw, filename, backup): def _replaceWithApkg(mw, filename, backup) -> None:
mw.progress.start(immediate=True) mw.progress.start(immediate=True)
def do_import(): def do_import() -> None:
z = zipfile.ZipFile(filename) z = zipfile.ZipFile(filename)
# v2 scheduler? # v2 scheduler?
@ -472,7 +472,7 @@ def _replaceWithApkg(mw, filename, backup):
z.close() z.close()
def on_done(future: Future): def on_done(future: Future) -> None:
mw.progress.finish() mw.progress.finish()
try: try:

View file

@ -6,7 +6,7 @@
Legacy support Legacy support
""" """
from typing import List from typing import Any, List
import anki import anki
import aqt import aqt
@ -31,7 +31,14 @@ def stripSounds(text) -> str:
return aqt.mw.col.media.strip_av_tags(text) return aqt.mw.col.media.strip_av_tags(text)
def fmtTimeSpan(time, pad=0, point=0, short=False, inTime=False, unit=99): def fmtTimeSpan(
time: Any,
pad: Any = 0,
point: Any = 0,
short: Any = False,
inTime: Any = False,
unit: Any = 99,
) -> Any:
print("fmtTimeSpan() has become col.format_timespan()") print("fmtTimeSpan() has become col.format_timespan()")
return aqt.mw.col.format_timespan(time) return aqt.mw.col.format_timespan(time)

View file

@ -87,7 +87,7 @@ class ResetReason(enum.Enum):
class ResetRequired: class ResetRequired:
def __init__(self, mw: AnkiQt): def __init__(self, mw: AnkiQt) -> None:
self.mw = mw self.mw = mw
@ -139,7 +139,7 @@ class AnkiQt(QMainWindow):
else: else:
fn = self.setupProfile fn = self.setupProfile
def on_window_init(): def on_window_init() -> None:
fn() fn()
gui_hooks.main_window_did_init() gui_hooks.main_window_did_init()
@ -175,7 +175,7 @@ class AnkiQt(QMainWindow):
"Actions that are deferred until after add-on loading." "Actions that are deferred until after add-on loading."
self.toolbar.draw() self.toolbar.draw()
def setupProfileAfterWebviewsLoaded(self): def setupProfileAfterWebviewsLoaded(self) -> None:
for w in (self.web, self.bottomWeb): for w in (self.web, self.bottomWeb):
if not w._domDone: if not w._domDone:
self.progress.timer( self.progress.timer(
@ -206,7 +206,7 @@ class AnkiQt(QMainWindow):
self.onClose.emit() # type: ignore self.onClose.emit() # type: ignore
evt.accept() evt.accept()
def closeWithoutQuitting(self): def closeWithoutQuitting(self) -> None:
self.closeFires = False self.closeFires = False
self.close() self.close()
self.closeFires = True self.closeFires = True
@ -275,9 +275,10 @@ class AnkiQt(QMainWindow):
name = self.pm.profiles()[n] name = self.pm.profiles()[n]
self.pm.load(name) self.pm.load(name)
def openProfile(self): def openProfile(self) -> None:
name = self.pm.profiles()[self.profileForm.profiles.currentRow()] name = self.pm.profiles()[self.profileForm.profiles.currentRow()]
return self.pm.load(name) self.pm.load(name)
return
def onOpenProfile(self) -> None: def onOpenProfile(self) -> None:
self.profileDiag.hide() self.profileDiag.hide()
@ -288,34 +289,37 @@ class AnkiQt(QMainWindow):
def profileNameOk(self, name: str) -> bool: def profileNameOk(self, name: str) -> bool:
return not checkInvalidFilename(name) and name != "addons21" return not checkInvalidFilename(name) and name != "addons21"
def onAddProfile(self): def onAddProfile(self) -> None:
name = getOnlyText(tr(TR.ACTIONS_NAME)).strip() name = getOnlyText(tr(TR.ACTIONS_NAME)).strip()
if name: if name:
if name in self.pm.profiles(): if name in self.pm.profiles():
return showWarning(tr(TR.QT_MISC_NAME_EXISTS)) showWarning(tr(TR.QT_MISC_NAME_EXISTS))
return
if not self.profileNameOk(name): if not self.profileNameOk(name):
return return
self.pm.create(name) self.pm.create(name)
self.pm.name = name self.pm.name = name
self.refreshProfilesList() self.refreshProfilesList()
def onRenameProfile(self): def onRenameProfile(self) -> None:
name = getOnlyText(tr(TR.ACTIONS_NEW_NAME), default=self.pm.name).strip() name = getOnlyText(tr(TR.ACTIONS_NEW_NAME), default=self.pm.name).strip()
if not name: if not name:
return return
if name == self.pm.name: if name == self.pm.name:
return return
if name in self.pm.profiles(): if name in self.pm.profiles():
return showWarning(tr(TR.QT_MISC_NAME_EXISTS)) showWarning(tr(TR.QT_MISC_NAME_EXISTS))
return
if not self.profileNameOk(name): if not self.profileNameOk(name):
return return
self.pm.rename(name) self.pm.rename(name)
self.refreshProfilesList() self.refreshProfilesList()
def onRemProfile(self): def onRemProfile(self) -> None:
profs = self.pm.profiles() profs = self.pm.profiles()
if len(profs) < 2: if len(profs) < 2:
return showWarning(tr(TR.QT_MISC_THERE_MUST_BE_AT_LEAST_ONE)) showWarning(tr(TR.QT_MISC_THERE_MUST_BE_AT_LEAST_ONE))
return
# sure? # sure?
if not askUser( if not askUser(
tr(TR.QT_MISC_ALL_CARDS_NOTES_AND_MEDIA_FOR), tr(TR.QT_MISC_ALL_CARDS_NOTES_AND_MEDIA_FOR),
@ -326,7 +330,7 @@ class AnkiQt(QMainWindow):
self.pm.remove(self.pm.name) self.pm.remove(self.pm.name)
self.refreshProfilesList() self.refreshProfilesList()
def onOpenBackup(self): def onOpenBackup(self) -> None:
if not askUser( if not askUser(
tr(TR.QT_MISC_REPLACE_YOUR_COLLECTION_WITH_AN_EARLIER), tr(TR.QT_MISC_REPLACE_YOUR_COLLECTION_WITH_AN_EARLIER),
msgfunc=QMessageBox.warning, msgfunc=QMessageBox.warning,
@ -334,7 +338,7 @@ class AnkiQt(QMainWindow):
): ):
return return
def doOpen(path): def doOpen(path) -> None:
self._openBackup(path) self._openBackup(path)
getFile( getFile(
@ -345,7 +349,7 @@ class AnkiQt(QMainWindow):
dir=self.pm.backupFolder(), dir=self.pm.backupFolder(),
) )
def _openBackup(self, path): def _openBackup(self, path) -> None:
try: try:
# move the existing collection to the trash, as it may not open # move the existing collection to the trash, as it may not open
self.pm.trashCollection() self.pm.trashCollection()
@ -360,14 +364,14 @@ class AnkiQt(QMainWindow):
self.onOpenProfile() self.onOpenProfile()
def _on_downgrade(self): def _on_downgrade(self) -> None:
self.progress.start() self.progress.start()
profiles = self.pm.profiles() profiles = self.pm.profiles()
def downgrade(): def downgrade() -> List[str]:
return self.pm.downgrade(profiles) return self.pm.downgrade(profiles)
def on_done(future): def on_done(future) -> None:
self.progress.finish() self.progress.finish()
problems = future.result() problems = future.result()
if not problems: if not problems:
@ -409,7 +413,7 @@ class AnkiQt(QMainWindow):
self.pendingImport = None self.pendingImport = None
gui_hooks.profile_did_open() gui_hooks.profile_did_open()
def _onsuccess(): def _onsuccess() -> None:
self._refresh_after_sync() self._refresh_after_sync()
if onsuccess: if onsuccess:
onsuccess() onsuccess()
@ -417,7 +421,7 @@ class AnkiQt(QMainWindow):
self.maybe_auto_sync_on_open_close(_onsuccess) self.maybe_auto_sync_on_open_close(_onsuccess)
def unloadProfile(self, onsuccess: Callable) -> None: def unloadProfile(self, onsuccess: Callable) -> None:
def callback(): def callback() -> None:
self._unloadProfile() self._unloadProfile()
onsuccess() onsuccess()
@ -447,7 +451,7 @@ class AnkiQt(QMainWindow):
def unloadProfileAndExit(self) -> None: def unloadProfileAndExit(self) -> None:
self.unloadProfile(self.cleanupAndExit) self.unloadProfile(self.cleanupAndExit)
def unloadProfileAndShowProfileManager(self): def unloadProfileAndShowProfileManager(self) -> None:
self.unloadProfile(self.showProfileManager) self.unloadProfile(self.showProfileManager)
def cleanupAndExit(self) -> None: def cleanupAndExit(self) -> None:
@ -520,14 +524,14 @@ class AnkiQt(QMainWindow):
self.col.reopen() self.col.reopen()
def unloadCollection(self, onsuccess: Callable) -> None: def unloadCollection(self, onsuccess: Callable) -> None:
def after_media_sync(): def after_media_sync() -> None:
self._unloadCollection() self._unloadCollection()
onsuccess() onsuccess()
def after_sync(): def after_sync() -> None:
self.media_syncer.show_diag_until_finished(after_media_sync) self.media_syncer.show_diag_until_finished(after_media_sync)
def before_sync(): def before_sync() -> None:
self.setEnabled(False) self.setEnabled(False)
self.maybe_auto_sync_on_open_close(after_sync) self.maybe_auto_sync_on_open_close(after_sync)
@ -565,7 +569,7 @@ class AnkiQt(QMainWindow):
########################################################################## ##########################################################################
class BackupThread(Thread): class BackupThread(Thread):
def __init__(self, path, data): def __init__(self, path, data) -> None:
Thread.__init__(self) Thread.__init__(self)
self.path = path self.path = path
self.data = data self.data = data
@ -574,7 +578,7 @@ class AnkiQt(QMainWindow):
with open(self.path, "wb") as file: with open(self.path, "wb") as file:
pass pass
def run(self): def run(self) -> None:
z = zipfile.ZipFile(self.path, "w", zipfile.ZIP_DEFLATED) z = zipfile.ZipFile(self.path, "w", zipfile.ZIP_DEFLATED)
z.writestr("collection.anki2", self.data) z.writestr("collection.anki2", self.data)
z.writestr("media", "{}") z.writestr("media", "{}")
@ -700,7 +704,7 @@ class AnkiQt(QMainWindow):
self.state = self.returnState self.state = self.returnState
self.reset() self.reset()
def delayedMaybeReset(self): def delayedMaybeReset(self) -> None:
# if we redraw the page in a button click event it will often crash on # if we redraw the page in a button click event it will often crash on
# windows # windows
self.progress.timer(100, self.maybeReset, False) self.progress.timer(100, self.maybeReset, False)
@ -801,9 +805,9 @@ title="%s" %s>%s</button>""" % (
signal.signal(signal.SIGINT, self.onUnixSignal) signal.signal(signal.SIGINT, self.onUnixSignal)
signal.signal(signal.SIGTERM, self.onUnixSignal) signal.signal(signal.SIGTERM, self.onUnixSignal)
def onUnixSignal(self, signum, frame): def onUnixSignal(self, signum, frame) -> None:
# schedule a rollback & quit # schedule a rollback & quit
def quit(): def quit() -> None:
self.col.db.rollback() self.col.db.rollback()
self.close() self.close()
@ -888,13 +892,13 @@ title="%s" %s>%s</button>""" % (
def _refresh_after_sync(self) -> None: def _refresh_after_sync(self) -> None:
self.toolbar.redraw() self.toolbar.redraw()
def _sync_collection_and_media(self, after_sync: Callable[[], None]): def _sync_collection_and_media(self, after_sync: Callable[[], None]) -> None:
"Caller should ensure auth available." "Caller should ensure auth available."
# start media sync if not already running # start media sync if not already running
if not self.media_syncer.is_syncing(): if not self.media_syncer.is_syncing():
self.media_syncer.start() self.media_syncer.start()
def on_collection_sync_finished(): def on_collection_sync_finished() -> None:
self.col.clearUndo() self.col.clearUndo()
self.col.models._clear_cache() self.col.models._clear_cache()
gui_hooks.sync_did_finish() gui_hooks.sync_did_finish()
@ -927,7 +931,7 @@ title="%s" %s>%s</button>""" % (
) )
# legacy # legacy
def _sync(self): def _sync(self) -> None:
pass pass
onSync = on_sync_button_clicked onSync = on_sync_button_clicked
@ -1057,7 +1061,7 @@ title="%s" %s>%s</button>""" % (
def onEditCurrent(self) -> None: def onEditCurrent(self) -> None:
aqt.dialogs.open("EditCurrent", self) aqt.dialogs.open("EditCurrent", self)
def onDeckConf(self, deck=None): def onDeckConf(self, deck=None) -> None:
if not deck: if not deck:
deck = self.col.decks.current() deck = self.col.decks.current()
if deck["dyn"]: if deck["dyn"]:
@ -1069,7 +1073,7 @@ title="%s" %s>%s</button>""" % (
aqt.deckconf.DeckConf(self, deck) aqt.deckconf.DeckConf(self, deck)
def onOverview(self): def onOverview(self) -> None:
self.col.reset() self.col.reset()
self.moveToState("overview") self.moveToState("overview")
@ -1083,7 +1087,7 @@ title="%s" %s>%s</button>""" % (
else: else:
aqt.dialogs.open("NewDeckStats", self) aqt.dialogs.open("NewDeckStats", self)
def onPrefs(self): def onPrefs(self) -> None:
aqt.dialogs.open("Preferences", self) aqt.dialogs.open("Preferences", self)
def onNoteTypes(self) -> None: def onNoteTypes(self) -> None:
@ -1091,13 +1095,13 @@ title="%s" %s>%s</button>""" % (
aqt.models.Models(self, self, fromMain=True) aqt.models.Models(self, self, fromMain=True)
def onAbout(self): def onAbout(self) -> None:
aqt.dialogs.open("About", self) aqt.dialogs.open("About", self)
def onDonate(self): def onDonate(self) -> None:
openLink(aqt.appDonate) openLink(aqt.appDonate)
def onDocumentation(self): def onDocumentation(self) -> None:
openHelp(HelpPage.INDEX) openHelp(HelpPage.INDEX)
# Importing & exporting # Importing & exporting
@ -1126,7 +1130,7 @@ title="%s" %s>%s</button>""" % (
# Installing add-ons from CLI / mimetype handler # Installing add-ons from CLI / mimetype handler
########################################################################## ##########################################################################
def installAddon(self, path: str, startup: bool = False): def installAddon(self, path: str, startup: bool = False) -> None:
from aqt.addons import installAddonPackages from aqt.addons import installAddonPackages
installAddonPackages( installAddonPackages(
@ -1201,14 +1205,14 @@ title="%s" %s>%s</button>""" % (
qconnect(self.autoUpdate.clockIsOff, self.clockIsOff) qconnect(self.autoUpdate.clockIsOff, self.clockIsOff)
self.autoUpdate.start() self.autoUpdate.start()
def newVerAvail(self, ver): def newVerAvail(self, ver) -> None:
if self.pm.meta.get("suppressUpdate", None) != ver: if self.pm.meta.get("suppressUpdate", None) != ver:
aqt.update.askAndUpdate(self, ver) aqt.update.askAndUpdate(self, ver)
def newMsg(self, data): def newMsg(self, data) -> None:
aqt.update.showMessages(self, data) aqt.update.showMessages(self, data)
def clockIsOff(self, diff): def clockIsOff(self, diff) -> None:
if devMode: if devMode:
print("clock is off; ignoring") print("clock is off; ignoring")
return return
@ -1229,7 +1233,7 @@ title="%s" %s>%s</button>""" % (
# SIGINT/SIGTERM is processed without a long delay # SIGINT/SIGTERM is processed without a long delay
self.progress.timer(1000, lambda: None, True, False) self.progress.timer(1000, lambda: None, True, False)
def onRefreshTimer(self): def onRefreshTimer(self) -> None:
if self.state == "deckBrowser": if self.state == "deckBrowser":
self.deckBrowser.refresh() self.deckBrowser.refresh()
elif self.state == "overview": elif self.state == "overview":
@ -1256,7 +1260,7 @@ title="%s" %s>%s</button>""" % (
self._activeWindowOnPlay: Optional[QWidget] = None self._activeWindowOnPlay: Optional[QWidget] = None
def onOdueInvalid(self): def onOdueInvalid(self) -> None:
showWarning(tr(TR.QT_MISC_INVALID_PROPERTY_FOUND_ON_CARD_PLEASE)) showWarning(tr(TR.QT_MISC_INVALID_PROPERTY_FOUND_ON_CARD_PLEASE))
def _isVideo(self, tag: AVTag) -> bool: def _isVideo(self, tag: AVTag) -> bool:
@ -1343,11 +1347,11 @@ title="%s" %s>%s</button>""" % (
# Debugging # Debugging
###################################################################### ######################################################################
def onDebug(self): def onDebug(self) -> None:
frm = self.debug_diag_form = aqt.forms.debug.Ui_Dialog() frm = self.debug_diag_form = aqt.forms.debug.Ui_Dialog()
class DebugDialog(QDialog): class DebugDialog(QDialog):
def reject(self): def reject(self) -> None:
super().reject() super().reject()
saveSplitter(frm.splitter, "DebugConsoleWindow") saveSplitter(frm.splitter, "DebugConsoleWindow")
saveGeom(self, "DebugConsoleWindow") saveGeom(self, "DebugConsoleWindow")
@ -1394,7 +1398,7 @@ title="%s" %s>%s</button>""" % (
mw = self mw = self
class Stream: class Stream:
def write(self, data): def write(self, data) -> None:
mw._output += data mw._output += data
if on: if on:
@ -1445,7 +1449,7 @@ title="%s" %s>%s</button>""" % (
self._card_repr(card) self._card_repr(card)
return card return card
def onDebugPrint(self, frm): def onDebugPrint(self, frm) -> None:
cursor = frm.text.textCursor() cursor = frm.text.textCursor()
position = cursor.position() position = cursor.position()
cursor.select(QTextCursor.LineUnderCursor) cursor.select(QTextCursor.LineUnderCursor)
@ -1458,7 +1462,7 @@ title="%s" %s>%s</button>""" % (
frm.text.setTextCursor(cursor) frm.text.setTextCursor(cursor)
self.onDebugRet(frm) self.onDebugRet(frm)
def onDebugRet(self, frm): def onDebugRet(self, frm) -> None:
import pprint import pprint
import traceback import traceback

View file

@ -136,7 +136,7 @@ class MediaChecker:
diag.exec_() diag.exec_()
saveGeom(diag, "checkmediadb") saveGeom(diag, "checkmediadb")
def _on_render_latex(self): def _on_render_latex(self) -> None:
self.progress_dialog = self.mw.progress.start() self.progress_dialog = self.mw.progress.start()
try: try:
out = self.mw.col.media.render_all_latex(self._on_render_latex_progress) out = self.mw.col.media.render_all_latex(self._on_render_latex_progress)
@ -160,7 +160,7 @@ class MediaChecker:
self.mw.progress.update(tr(TR.MEDIA_CHECK_CHECKED, count=count)) self.mw.progress.update(tr(TR.MEDIA_CHECK_CHECKED, count=count))
return True return True
def _on_trash_files(self, fnames: Sequence[str]): def _on_trash_files(self, fnames: Sequence[str]) -> None:
if not askUser(tr(TR.MEDIA_CHECK_DELETE_UNUSED_CONFIRM)): if not askUser(tr(TR.MEDIA_CHECK_DELETE_UNUSED_CONFIRM)):
return return
@ -183,14 +183,14 @@ class MediaChecker:
tooltip(tr(TR.MEDIA_CHECK_DELETE_UNUSED_COMPLETE, count=total)) tooltip(tr(TR.MEDIA_CHECK_DELETE_UNUSED_COMPLETE, count=total))
def _on_empty_trash(self): def _on_empty_trash(self) -> None:
self.progress_dialog = self.mw.progress.start() self.progress_dialog = self.mw.progress.start()
self._set_progress_enabled(True) self._set_progress_enabled(True)
def empty_trash(): def empty_trash() -> None:
self.mw.col.media.empty_trash() self.mw.col.media.empty_trash()
def on_done(fut: Future): def on_done(fut: Future) -> None:
self.mw.progress.finish() self.mw.progress.finish()
self._set_progress_enabled(False) self._set_progress_enabled(False)
# check for errors # check for errors
@ -200,14 +200,14 @@ class MediaChecker:
self.mw.taskman.run_in_background(empty_trash, on_done) self.mw.taskman.run_in_background(empty_trash, on_done)
def _on_restore_trash(self): def _on_restore_trash(self) -> None:
self.progress_dialog = self.mw.progress.start() self.progress_dialog = self.mw.progress.start()
self._set_progress_enabled(True) self._set_progress_enabled(True)
def restore_trash(): def restore_trash() -> None:
self.mw.col.media.restore_trash() self.mw.col.media.restore_trash()
def on_done(fut: Future): def on_done(fut: Future) -> None:
self.mw.progress.finish() self.mw.progress.finish()
self._set_progress_enabled(False) self._set_progress_enabled(False)
# check for errors # check for errors

View file

@ -12,6 +12,7 @@ import threading
import time import time
import traceback import traceback
from http import HTTPStatus from http import HTTPStatus
from typing import Tuple
import flask import flask
import flask_cors # type: ignore import flask_cors # type: ignore
@ -52,11 +53,11 @@ class MediaServer(threading.Thread):
_ready = threading.Event() _ready = threading.Event()
daemon = True daemon = True
def __init__(self, mw: aqt.main.AnkiQt, *args, **kwargs): def __init__(self, mw: aqt.main.AnkiQt, *args, **kwargs) -> None:
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
self.is_shutdown = False self.is_shutdown = False
def run(self): def run(self) -> None:
try: try:
if devMode: if devMode:
# idempotent if logging has already been set up # idempotent if logging has already been set up
@ -97,7 +98,7 @@ class MediaServer(threading.Thread):
@app.route("/<path:pathin>", methods=["GET", "POST"]) @app.route("/<path:pathin>", methods=["GET", "POST"])
def allroutes(pathin): def allroutes(pathin) -> Response:
try: try:
directory, path = _redirectWebExports(pathin) directory, path = _redirectWebExports(pathin)
except TypeError: except TypeError:
@ -171,7 +172,7 @@ def allroutes(pathin):
) )
def _redirectWebExports(path): def _redirectWebExports(path) -> Tuple[str, str]:
# catch /_anki references and rewrite them to web export folder # catch /_anki references and rewrite them to web export folder
targetPath = "_anki/" targetPath = "_anki/"
if path.startswith(targetPath): if path.startswith(targetPath):

View file

@ -28,14 +28,14 @@ class LogEntryWithTime:
class MediaSyncer: class MediaSyncer:
def __init__(self, mw: aqt.main.AnkiQt): def __init__(self, mw: aqt.main.AnkiQt) -> None:
self.mw = mw self.mw = mw
self._syncing: bool = False self._syncing: bool = False
self._log: List[LogEntryWithTime] = [] self._log: List[LogEntryWithTime] = []
self._progress_timer: Optional[QTimer] = None self._progress_timer: Optional[QTimer] = None
gui_hooks.media_sync_did_start_or_stop.append(self._on_start_stop) gui_hooks.media_sync_did_start_or_stop.append(self._on_start_stop)
def _on_progress(self): def _on_progress(self) -> None:
progress = self.mw.col.latest_progress() progress = self.mw.col.latest_progress()
if progress.kind != ProgressKind.MediaSync: if progress.kind != ProgressKind.MediaSync:
return return
@ -88,7 +88,7 @@ class MediaSyncer:
else: else:
self._log_and_notify(tr(TR.SYNC_MEDIA_COMPLETE)) self._log_and_notify(tr(TR.SYNC_MEDIA_COMPLETE))
def _handle_sync_error(self, exc: BaseException): def _handle_sync_error(self, exc: BaseException) -> None:
if isinstance(exc, Interrupted): if isinstance(exc, Interrupted):
self._log_and_notify(tr(TR.SYNC_MEDIA_ABORTED)) self._log_and_notify(tr(TR.SYNC_MEDIA_ABORTED))
return return
@ -116,10 +116,10 @@ class MediaSyncer:
def _on_start_stop(self, running: bool) -> None: def _on_start_stop(self, running: bool) -> None:
self.mw.toolbar.set_sync_active(running) self.mw.toolbar.set_sync_active(running)
def show_sync_log(self): def show_sync_log(self) -> None:
aqt.dialogs.open("sync_log", self.mw, self) aqt.dialogs.open("sync_log", self.mw, self)
def show_diag_until_finished(self, on_finished: Callable[[], None]): def show_diag_until_finished(self, on_finished: Callable[[], None]) -> None:
# nothing to do if not syncing # nothing to do if not syncing
if not self.is_syncing(): if not self.is_syncing():
return on_finished() return on_finished()
@ -129,7 +129,7 @@ class MediaSyncer:
timer: Optional[QTimer] = None timer: Optional[QTimer] = None
def check_finished(): def check_finished() -> None:
if not self.is_syncing(): if not self.is_syncing():
timer.stop() timer.stop()
on_finished() on_finished()
@ -197,7 +197,7 @@ class MediaSyncDialog(QDialog):
asctime = time.asctime(time.localtime(stamp)) asctime = time.asctime(time.localtime(stamp))
return f"{asctime}: {text}" return f"{asctime}: {text}"
def _entry_to_text(self, entry: LogEntryWithTime): def _entry_to_text(self, entry: LogEntryWithTime) -> str:
if isinstance(entry.entry, str): if isinstance(entry.entry, str):
txt = entry.entry txt = entry.entry
elif isinstance(entry.entry, MediaSyncProgress): elif isinstance(entry.entry, MediaSyncProgress):
@ -209,7 +209,7 @@ class MediaSyncDialog(QDialog):
def _logentry_to_text(self, e: MediaSyncProgress) -> str: def _logentry_to_text(self, e: MediaSyncProgress) -> str:
return f"{e.added}, {e.removed}, {e.checked}" return f"{e.added}, {e.removed}, {e.checked}"
def _on_log_entry(self, entry: LogEntryWithTime): def _on_log_entry(self, entry: LogEntryWithTime) -> None:
self.form.plainTextEdit.appendPlainText(self._entry_to_text(entry)) self.form.plainTextEdit.appendPlainText(self._entry_to_text(entry))
if not self._syncer.is_syncing(): if not self._syncer.is_syncing():
self.abort_button.setHidden(True) self.abort_button.setHidden(True)

View file

@ -1,7 +1,7 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# Copyright: Ankitects Pty Ltd and contributors # Copyright: Ankitects Pty Ltd and contributors
# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html # License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
from typing import Optional from typing import List, Optional
from aqt import AnkiQt, gui_hooks from aqt import AnkiQt, gui_hooks
from aqt.qt import * from aqt.qt import *
@ -74,7 +74,7 @@ class ModelChooser(QHBoxLayout):
# edit button # edit button
edit = QPushButton(tr(TR.QT_MISC_MANAGE), clicked=self.onEdit) # type: ignore edit = QPushButton(tr(TR.QT_MISC_MANAGE), clicked=self.onEdit) # type: ignore
def nameFunc(): def nameFunc() -> List[str]:
return sorted(self.deck.models.allNames()) return sorted(self.deck.models.allNames())
ret = StudyDeck( ret = StudyDeck(

View file

@ -219,7 +219,7 @@ class Models(QDialog):
class AddModel(QDialog): class AddModel(QDialog):
model: Optional[NoteType] model: Optional[NoteType]
def __init__(self, mw: AnkiQt, parent: Optional[QWidget] = None): def __init__(self, mw: AnkiQt, parent: Optional[QWidget] = None) -> None:
self.parent_ = parent or mw self.parent_ = parent or mw
self.mw = mw self.mw = mw
self.col = mw.col self.col = mw.col

View file

@ -15,7 +15,7 @@ from aqt.utils import TR, askUserDialog, openLink, shortcut, tooltip, tr
class OverviewBottomBar: class OverviewBottomBar:
def __init__(self, overview: Overview): def __init__(self, overview: Overview) -> None:
self.overview = overview self.overview = overview
@ -104,12 +104,12 @@ class Overview:
def _filteredDeck(self) -> int: def _filteredDeck(self) -> int:
return self.mw.col.decks.current()["dyn"] return self.mw.col.decks.current()["dyn"]
def onRebuildKey(self): def onRebuildKey(self) -> None:
if self._filteredDeck(): if self._filteredDeck():
self.mw.col.sched.rebuild_filtered_deck(self.mw.col.decks.selected()) self.mw.col.sched.rebuild_filtered_deck(self.mw.col.decks.selected())
self.mw.reset() self.mw.reset()
def onEmptyKey(self): def onEmptyKey(self) -> None:
if self._filteredDeck(): if self._filteredDeck():
self.mw.col.sched.empty_filtered_deck(self.mw.col.decks.selected()) self.mw.col.sched.empty_filtered_deck(self.mw.col.decks.selected())
self.mw.reset() self.mw.reset()
@ -118,7 +118,7 @@ class Overview:
if not self._filteredDeck(): if not self._filteredDeck():
self.onStudyMore() self.onStudyMore()
def onUnbury(self): def onUnbury(self) -> None:
if self.mw.col.schedVer() == 1: if self.mw.col.schedVer() == 1:
self.mw.col.sched.unburyCardsForDeck() self.mw.col.sched.unburyCardsForDeck()
self.mw.reset() self.mw.reset()

View file

@ -34,7 +34,7 @@ def video_driver_name_for_platform(driver: VideoDriver) -> str:
class Preferences(QDialog): class Preferences(QDialog):
def __init__(self, mw: AnkiQt): def __init__(self, mw: AnkiQt) -> None:
QDialog.__init__(self, mw, Qt.Window) QDialog.__init__(self, mw, Qt.Window)
self.mw = mw self.mw = mw
self.prof = self.mw.pm.profile self.prof = self.mw.pm.profile
@ -55,7 +55,7 @@ class Preferences(QDialog):
self.setupOptions() self.setupOptions()
self.show() self.show()
def accept(self): def accept(self) -> None:
# avoid exception if main window is already closed # avoid exception if main window is already closed
if not self.mw.col: if not self.mw.col:
return return
@ -68,19 +68,19 @@ class Preferences(QDialog):
self.done(0) self.done(0)
aqt.dialogs.markClosed("Preferences") aqt.dialogs.markClosed("Preferences")
def reject(self): def reject(self) -> None:
self.accept() self.accept()
# Language # Language
###################################################################### ######################################################################
def setupLang(self): def setupLang(self) -> None:
f = self.form f = self.form
f.lang.addItems([x[0] for x in anki.lang.langs]) f.lang.addItems([x[0] for x in anki.lang.langs])
f.lang.setCurrentIndex(self.langIdx()) f.lang.setCurrentIndex(self.langIdx())
qconnect(f.lang.currentIndexChanged, self.onLangIdxChanged) qconnect(f.lang.currentIndexChanged, self.onLangIdxChanged)
def langIdx(self): def langIdx(self) -> int:
codes = [x[1] for x in anki.lang.langs] codes = [x[1] for x in anki.lang.langs]
lang = anki.lang.currentLang lang = anki.lang.currentLang
if lang in anki.lang.compatMap: if lang in anki.lang.compatMap:
@ -92,7 +92,7 @@ class Preferences(QDialog):
except: except:
return codes.index("en_US") return codes.index("en_US")
def onLangIdxChanged(self, idx): def onLangIdxChanged(self, idx) -> None:
code = anki.lang.langs[idx][1] code = anki.lang.langs[idx][1]
self.mw.pm.setLang(code) self.mw.pm.setLang(code)
showInfo( showInfo(
@ -102,7 +102,7 @@ class Preferences(QDialog):
# Collection options # Collection options
###################################################################### ######################################################################
def setupCollection(self): def setupCollection(self) -> None:
import anki.consts as c import anki.consts as c
f = self.form f = self.form
@ -130,7 +130,7 @@ class Preferences(QDialog):
f.newSched.setChecked(True) f.newSched.setChecked(True)
f.new_timezone.setChecked(s.new_timezone) f.new_timezone.setChecked(s.new_timezone)
def setup_video_driver(self): def setup_video_driver(self) -> None:
self.video_drivers = VideoDriver.all_for_platform() self.video_drivers = VideoDriver.all_for_platform()
names = [ names = [
tr(TR.PREFERENCES_VIDEO_DRIVER, driver=video_driver_name_for_platform(d)) tr(TR.PREFERENCES_VIDEO_DRIVER, driver=video_driver_name_for_platform(d))
@ -141,13 +141,13 @@ class Preferences(QDialog):
self.video_drivers.index(self.mw.pm.video_driver()) self.video_drivers.index(self.mw.pm.video_driver())
) )
def update_video_driver(self): def update_video_driver(self) -> None:
new_driver = self.video_drivers[self.form.video_driver.currentIndex()] new_driver = self.video_drivers[self.form.video_driver.currentIndex()]
if new_driver != self.mw.pm.video_driver(): if new_driver != self.mw.pm.video_driver():
self.mw.pm.set_video_driver(new_driver) self.mw.pm.set_video_driver(new_driver)
showInfo(tr(TR.PREFERENCES_CHANGES_WILL_TAKE_EFFECT_WHEN_YOU)) showInfo(tr(TR.PREFERENCES_CHANGES_WILL_TAKE_EFFECT_WHEN_YOU))
def updateCollection(self): def updateCollection(self) -> None:
f = self.form f = self.form
d = self.mw.col d = self.mw.col
@ -176,7 +176,7 @@ class Preferences(QDialog):
# Scheduler version # Scheduler version
###################################################################### ######################################################################
def _updateSchedVer(self, wantNew): def _updateSchedVer(self, wantNew) -> None:
haveNew = self.mw.col.schedVer() == 2 haveNew = self.mw.col.schedVer() == 2
# nothing to do? # nothing to do?
@ -194,7 +194,7 @@ class Preferences(QDialog):
# Network # Network
###################################################################### ######################################################################
def setupNetwork(self): def setupNetwork(self) -> None:
self.form.media_log.setText(tr(TR.SYNC_MEDIA_LOG_BUTTON)) self.form.media_log.setText(tr(TR.SYNC_MEDIA_LOG_BUTTON))
qconnect(self.form.media_log.clicked, self.on_media_log) qconnect(self.form.media_log.clicked, self.on_media_log)
self.form.syncOnProgramOpen.setChecked(self.prof["autoSync"]) self.form.syncOnProgramOpen.setChecked(self.prof["autoSync"])
@ -207,10 +207,10 @@ class Preferences(QDialog):
qconnect(self.form.syncDeauth.clicked, self.onSyncDeauth) qconnect(self.form.syncDeauth.clicked, self.onSyncDeauth)
self.form.syncDeauth.setText(tr(TR.SYNC_LOG_OUT_BUTTON)) self.form.syncDeauth.setText(tr(TR.SYNC_LOG_OUT_BUTTON))
def on_media_log(self): def on_media_log(self) -> None:
self.mw.media_syncer.show_sync_log() self.mw.media_syncer.show_sync_log()
def _hideAuth(self): def _hideAuth(self) -> None:
self.form.syncDeauth.setVisible(False) self.form.syncDeauth.setVisible(False)
self.form.syncUser.setText("") self.form.syncUser.setText("")
self.form.syncLabel.setText( self.form.syncLabel.setText(
@ -225,7 +225,7 @@ class Preferences(QDialog):
self.mw.col.media.force_resync() self.mw.col.media.force_resync()
self._hideAuth() self._hideAuth()
def updateNetwork(self): def updateNetwork(self) -> None:
self.prof["autoSync"] = self.form.syncOnProgramOpen.isChecked() self.prof["autoSync"] = self.form.syncOnProgramOpen.isChecked()
self.prof["syncMedia"] = self.form.syncMedia.isChecked() self.prof["syncMedia"] = self.form.syncMedia.isChecked()
self.mw.pm.set_auto_sync_media_minutes( self.mw.pm.set_auto_sync_media_minutes(
@ -238,16 +238,16 @@ class Preferences(QDialog):
# Backup # Backup
###################################################################### ######################################################################
def setupBackup(self): def setupBackup(self) -> None:
self.form.numBackups.setValue(self.prof["numBackups"]) self.form.numBackups.setValue(self.prof["numBackups"])
def updateBackup(self): def updateBackup(self) -> None:
self.prof["numBackups"] = self.form.numBackups.value() self.prof["numBackups"] = self.form.numBackups.value()
# Basic & Advanced Options # Basic & Advanced Options
###################################################################### ######################################################################
def setupOptions(self): def setupOptions(self) -> None:
self.form.pastePNG.setChecked(self.prof.get("pastePNG", False)) self.form.pastePNG.setChecked(self.prof.get("pastePNG", False))
self.form.uiScale.setValue(int(self.mw.pm.uiScale() * 100)) self.form.uiScale.setValue(int(self.mw.pm.uiScale() * 100))
self.form.pasteInvert.setChecked(self.prof.get("pasteInvert", False)) self.form.pasteInvert.setChecked(self.prof.get("pasteInvert", False))
@ -270,7 +270,7 @@ class Preferences(QDialog):
self._recording_drivers.index(self.mw.pm.recording_driver()) self._recording_drivers.index(self.mw.pm.recording_driver())
) )
def updateOptions(self): def updateOptions(self) -> None:
restart_required = False restart_required = False
self.prof["pastePNG"] = self.form.pastePNG.isChecked() self.prof["pastePNG"] = self.form.pastePNG.isChecked()

View file

@ -40,7 +40,9 @@ class Previewer(QDialog):
_timer: Optional[QTimer] = None _timer: Optional[QTimer] = None
_show_both_sides = False _show_both_sides = False
def __init__(self, parent: QWidget, mw: AnkiQt, on_close: Callable[[], None]): def __init__(
self, parent: QWidget, mw: AnkiQt, on_close: Callable[[], None]
) -> None:
super().__init__(None, Qt.Window) super().__init__(None, Qt.Window)
self._open = True self._open = True
self._parent = parent self._parent = parent
@ -259,14 +261,14 @@ class MultiCardPreviewer(Previewer):
qconnect(self._prev.clicked, self._on_prev) qconnect(self._prev.clicked, self._on_prev)
qconnect(self._next.clicked, self._on_next) qconnect(self._next.clicked, self._on_next)
def _on_prev(self): def _on_prev(self) -> None:
if self._state == "answer" and not self._show_both_sides: if self._state == "answer" and not self._show_both_sides:
self._state = "question" self._state = "question"
self.render_card() self.render_card()
else: else:
self._on_prev_card() self._on_prev_card()
def _on_prev_card(self): def _on_prev_card(self) -> None:
pass pass
def _on_next(self) -> None: def _on_next(self) -> None:
@ -276,7 +278,7 @@ class MultiCardPreviewer(Previewer):
else: else:
self._on_next_card() self._on_next_card()
def _on_next_card(self): def _on_next_card(self) -> None:
pass pass
def _updateButtons(self) -> None: def _updateButtons(self) -> None:
@ -315,7 +317,7 @@ class BrowserPreviewer(MultiCardPreviewer):
self._last_card_id = c.id self._last_card_id = c.id
return changed return changed
def _on_prev_card(self): def _on_prev_card(self) -> None:
self._parent.editor.saveNow( self._parent.editor.saveNow(
lambda: self._parent._moveCur(QAbstractItemView.MoveUp) lambda: self._parent._moveCur(QAbstractItemView.MoveUp)
) )

View file

@ -113,13 +113,13 @@ class LoadMetaResult:
class AnkiRestart(SystemExit): class AnkiRestart(SystemExit):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs) -> None:
self.exitcode = kwargs.pop("exitcode", 0) self.exitcode = kwargs.pop("exitcode", 0)
super().__init__(*args, **kwargs) # type: ignore super().__init__(*args, **kwargs) # type: ignore
class ProfileManager: class ProfileManager:
def __init__(self, base: Optional[str] = None) -> None: def __init__(self, base: Optional[str] = None) -> None: #
## Settings which should be forgotten each Anki restart ## Settings which should be forgotten each Anki restart
self.session: Dict[str, Any] = {} self.session: Dict[str, Any] = {}
self.name: Optional[str] = None self.name: Optional[str] = None
@ -185,7 +185,7 @@ class ProfileManager:
self.base = newBase self.base = newBase
shutil.move(oldBase, self.base) shutil.move(oldBase, self.base)
def _tryToMigrateFolder(self, oldBase): def _tryToMigrateFolder(self, oldBase) -> None:
from PyQt5 import QtGui, QtWidgets from PyQt5 import QtGui, QtWidgets
app = QtWidgets.QApplication([]) app = QtWidgets.QApplication([])
@ -269,7 +269,7 @@ class ProfileManager:
fn = super().find_class(module, name) fn = super().find_class(module, name)
if module == "sip" and name == "_unpickle_type": if module == "sip" and name == "_unpickle_type":
def wrapper(mod, obj, args): def wrapper(mod, obj, args) -> Any:
if mod.startswith("PyQt4") and obj == "QByteArray": if mod.startswith("PyQt4") and obj == "QByteArray":
# can't trust str objects from python 2 # can't trust str objects from python 2
return QByteArray() return QByteArray()
@ -534,7 +534,7 @@ create table if not exists profiles
def setDefaultLang(self, idx: int) -> None: def setDefaultLang(self, idx: int) -> None:
# create dialog # create dialog
class NoCloseDiag(QDialog): class NoCloseDiag(QDialog):
def reject(self): def reject(self) -> None:
pass pass
d = self.langDiag = NoCloseDiag() d = self.langDiag = NoCloseDiag()
@ -665,7 +665,7 @@ create table if not exists profiles
pass pass
return RecordingDriver.QtAudioInput return RecordingDriver.QtAudioInput
def set_recording_driver(self, driver: RecordingDriver): def set_recording_driver(self, driver: RecordingDriver) -> None:
self.profile["recordingDriver"] = driver.value self.profile["recordingDriver"] = driver.value
###################################################################### ######################################################################

View file

@ -43,7 +43,7 @@ class ProgressManager:
timer to fire even when there is no collection, but will still timer to fire even when there is no collection, but will still
only fire when there is no current progress dialog.""" only fire when there is no current progress dialog."""
def handler(): def handler() -> None:
if requiresCollection and not self.mw.col: if requiresCollection and not self.mw.col:
# no current collection; timer is no longer valid # no current collection; timer is no longer valid
print("Ignored progress func as collection unloaded: %s" % repr(func)) print("Ignored progress func as collection unloaded: %s" % repr(func))
@ -225,14 +225,14 @@ class ProgressDialog(QDialog):
self._closingDown = True self._closingDown = True
self.hide() self.hide()
def closeEvent(self, evt): def closeEvent(self, evt) -> None:
if self._closingDown: if self._closingDown:
evt.accept() evt.accept()
else: else:
self.wantCancel = True self.wantCancel = True
evt.ignore() evt.ignore()
def keyPressEvent(self, evt): def keyPressEvent(self, evt) -> None:
if evt.key() == Qt.Key_Escape: if evt.key() == Qt.Key_Escape:
evt.ignore() evt.ignore()
self.wantCancel = True self.wantCancel = True

View file

@ -30,7 +30,7 @@ except ImportError:
import sip # type: ignore import sip # type: ignore
def debug(): def debug() -> None:
from pdb import set_trace from pdb import set_trace
pyqtRemoveInputHook() pyqtRemoveInputHook()
@ -39,7 +39,7 @@ def debug():
if os.environ.get("DEBUG"): if os.environ.get("DEBUG"):
def info(type, value, tb): def info(type, value, tb) -> None:
for line in traceback.format_exception(type, value, tb): for line in traceback.format_exception(type, value, tb):
sys.stdout.write(line) sys.stdout.write(line)
pyqtRemoveInputHook() pyqtRemoveInputHook()

View file

@ -8,7 +8,7 @@ import html
import json import json
import re import re
import unicodedata as ucd import unicodedata as ucd
from typing import Any, Callable, List, Optional, Tuple, Union from typing import Any, Callable, List, Match, Optional, Tuple, Union
from PyQt5.QtCore import Qt from PyQt5.QtCore import Qt
@ -426,7 +426,7 @@ class Reviewer:
# compare with typed answer # compare with typed answer
res = self.correct(given, cor, showBad=False) res = self.correct(given, cor, showBad=False)
# and update the type answer area # and update the type answer area
def repl(match): def repl(match: Match) -> str:
# can't pass a string in directly, and can't use re.escape as it # can't pass a string in directly, and can't use re.escape as it
# escapes too much # escapes too much
s = """ s = """
@ -448,7 +448,7 @@ class Reviewer:
if not matches: if not matches:
return None return None
def noHint(txt): def noHint(txt) -> str:
if "::" in txt: if "::" in txt:
return txt.split("::")[0] return txt.split("::")[0]
return txt return txt
@ -652,7 +652,7 @@ time = %(time)d;
def _answerButtons(self) -> str: def _answerButtons(self) -> str:
default = self._defaultEase() default = self._defaultEase()
def but(i, label): def but(i, label) -> str:
if i == default: if i == default:
extra = """id="defease" class="focus" """ extra = """id="defease" class="focus" """
else: else:
@ -834,7 +834,7 @@ time = %(time)d;
tooltip(tr(TR.STUDYING_NOTE_BURIED)) tooltip(tr(TR.STUDYING_NOTE_BURIED))
def onRecordVoice(self) -> None: def onRecordVoice(self) -> None:
def after_record(path: str): def after_record(path: str) -> None:
self._recordedAudio = path self._recordedAudio = path
self.onReplayRecorded() self.onReplayRecorded()

View file

@ -17,7 +17,7 @@ class Change(enum.Enum):
class ChangeTracker: class ChangeTracker:
_changed = Change.NO_CHANGE _changed = Change.NO_CHANGE
def __init__(self, mw: AnkiQt): def __init__(self, mw: AnkiQt) -> None:
self.mw = mw self.mw = mw
def mark_basic(self) -> None: def mark_basic(self) -> None:

View file

@ -438,7 +438,7 @@ class MpvManager(MPV, SoundOrVideoPlayer):
class SimpleMplayerSlaveModePlayer(SimpleMplayerPlayer): class SimpleMplayerSlaveModePlayer(SimpleMplayerPlayer):
def __init__(self, taskman: TaskManager): def __init__(self, taskman: TaskManager) -> None:
super().__init__(taskman) super().__init__(taskman)
self.args.append("-slave") self.args.append("-slave")
@ -494,7 +494,7 @@ def encode_mp3(mw: aqt.AnkiQt, src_wav: str, on_done: Callable[[str], None]) ->
"Encode the provided wav file to .mp3, and call on_done() with the path." "Encode the provided wav file to .mp3, and call on_done() with the path."
dst_mp3 = src_wav.replace(".wav", "%d.mp3" % time.time()) dst_mp3 = src_wav.replace(".wav", "%d.mp3" % time.time())
def _on_done(fut: Future): def _on_done(fut: Future) -> None:
fut.result() fut.result()
on_done(dst_mp3) on_done(dst_mp3)
@ -509,7 +509,7 @@ class Recorder(ABC):
# seconds to wait before recording # seconds to wait before recording
STARTUP_DELAY = 0.3 STARTUP_DELAY = 0.3
def __init__(self, output_path: str): def __init__(self, output_path: str) -> None:
self.output_path = output_path self.output_path = output_path
def start(self, on_done: Callable[[], None]) -> None: def start(self, on_done: Callable[[], None]) -> None:
@ -517,7 +517,7 @@ class Recorder(ABC):
self._started_at = time.time() self._started_at = time.time()
on_done() on_done()
def stop(self, on_done: Callable[[str], None]): def stop(self, on_done: Callable[[str], None]) -> None:
"Stop recording, then call on_done() when finished." "Stop recording, then call on_done() when finished."
on_done(self.output_path) on_done(self.output_path)
@ -525,7 +525,7 @@ class Recorder(ABC):
"Seconds since recording started." "Seconds since recording started."
return time.time() - self._started_at return time.time() - self._started_at
def on_timer(self): def on_timer(self) -> None:
"Will be called periodically." "Will be called periodically."
@ -534,7 +534,7 @@ class Recorder(ABC):
class QtAudioInputRecorder(Recorder): class QtAudioInputRecorder(Recorder):
def __init__(self, output_path: str, mw: aqt.AnkiQt, parent: QWidget): def __init__(self, output_path: str, mw: aqt.AnkiQt, parent: QWidget) -> None:
super().__init__(output_path) super().__init__(output_path)
self.mw = mw self.mw = mw
@ -567,11 +567,11 @@ class QtAudioInputRecorder(Recorder):
self._iodevice.readyRead.connect(self._on_read_ready) # type: ignore self._iodevice.readyRead.connect(self._on_read_ready) # type: ignore
super().start(on_done) super().start(on_done)
def _on_read_ready(self): def _on_read_ready(self) -> None:
self._buffer += self._iodevice.readAll() self._buffer += self._iodevice.readAll()
def stop(self, on_done: Callable[[str], None]): def stop(self, on_done: Callable[[str], None]) -> None:
def on_stop_timer(): def on_stop_timer() -> None:
# read anything remaining in buffer & stop # read anything remaining in buffer & stop
self._on_read_ready() self._on_read_ready()
self._audio_input.stop() self._audio_input.stop()
@ -580,7 +580,7 @@ class QtAudioInputRecorder(Recorder):
showWarning(f"recording failed: {err}") showWarning(f"recording failed: {err}")
return return
def write_file(): def write_file() -> None:
# swallow the first 300ms to allow audio device to quiesce # swallow the first 300ms to allow audio device to quiesce
wait = int(44100 * self.STARTUP_DELAY) wait = int(44100 * self.STARTUP_DELAY)
if len(self._buffer) <= wait: if len(self._buffer) <= wait:
@ -595,7 +595,7 @@ class QtAudioInputRecorder(Recorder):
wf.writeframes(self._buffer) wf.writeframes(self._buffer)
wf.close() wf.close()
def and_then(fut): def and_then(fut) -> None:
fut.result() fut.result()
Recorder.stop(self, on_done) Recorder.stop(self, on_done)
@ -672,7 +672,7 @@ class PyAudioThreadedRecorder(threading.Thread):
class PyAudioRecorder(Recorder): class PyAudioRecorder(Recorder):
def __init__(self, mw: aqt.AnkiQt, output_path: str): def __init__(self, mw: aqt.AnkiQt, output_path: str) -> None:
super().__init__(output_path) super().__init__(output_path)
self.mw = mw self.mw = mw
@ -686,7 +686,7 @@ class PyAudioRecorder(Recorder):
while self.duration() < 1: while self.duration() < 1:
time.sleep(0.1) time.sleep(0.1)
def func(fut): def func(fut) -> None:
Recorder.stop(self, on_done) Recorder.stop(self, on_done)
self.thread.finish = True self.thread.finish = True
@ -715,7 +715,7 @@ class RecordDialog(QDialog):
self._start_recording() self._start_recording()
self._setup_dialog() self._setup_dialog()
def _setup_dialog(self): def _setup_dialog(self) -> None:
self.setWindowTitle("Anki") self.setWindowTitle("Anki")
icon = QLabel() icon = QLabel()
icon.setPixmap(QPixmap(":/icons/media-record.png")) icon.setPixmap(QPixmap(":/icons/media-record.png"))
@ -740,10 +740,10 @@ class RecordDialog(QDialog):
restoreGeom(self, "audioRecorder2") restoreGeom(self, "audioRecorder2")
self.show() self.show()
def _save_diag(self): def _save_diag(self) -> None:
saveGeom(self, "audioRecorder2") saveGeom(self, "audioRecorder2")
def _start_recording(self): def _start_recording(self) -> None:
driver = self.mw.pm.recording_driver() driver = self.mw.pm.recording_driver()
if driver is RecordingDriver.PyAudio: if driver is RecordingDriver.PyAudio:
self._recorder = PyAudioRecorder(self.mw, namedtmp("rec.wav")) self._recorder = PyAudioRecorder(self.mw, namedtmp("rec.wav"))
@ -755,18 +755,18 @@ class RecordDialog(QDialog):
assert_exhaustive(driver) assert_exhaustive(driver)
self._recorder.start(self._start_timer) self._recorder.start(self._start_timer)
def _start_timer(self): def _start_timer(self) -> None:
self._timer = t = QTimer(self._parent) self._timer = t = QTimer(self._parent)
t.timeout.connect(self._on_timer) # type: ignore t.timeout.connect(self._on_timer) # type: ignore
t.setSingleShot(False) t.setSingleShot(False)
t.start(100) t.start(100)
def _on_timer(self): def _on_timer(self) -> None:
self._recorder.on_timer() self._recorder.on_timer()
duration = self._recorder.duration() duration = self._recorder.duration()
self.label.setText(tr(TR.MEDIA_RECORDINGTIME, secs="%0.1f" % duration)) self.label.setText(tr(TR.MEDIA_RECORDINGTIME, secs="%0.1f" % duration))
def accept(self): def accept(self) -> None:
self._timer.stop() self._timer.stop()
try: try:
@ -775,10 +775,10 @@ class RecordDialog(QDialog):
finally: finally:
QDialog.accept(self) QDialog.accept(self)
def reject(self): def reject(self) -> None:
self._timer.stop() self._timer.stop()
def cleanup(out: str): def cleanup(out: str) -> None:
os.unlink(out) os.unlink(out)
try: try:
@ -790,7 +790,7 @@ class RecordDialog(QDialog):
def record_audio( def record_audio(
parent: QWidget, mw: aqt.AnkiQt, encode: bool, on_done: Callable[[str], None] parent: QWidget, mw: aqt.AnkiQt, encode: bool, on_done: Callable[[str], None]
): ):
def after_record(path: str): def after_record(path: str) -> None:
if not encode: if not encode:
on_done(path) on_done(path)
else: else:

View file

@ -25,7 +25,7 @@ from aqt.utils import (
class NewDeckStats(QDialog): class NewDeckStats(QDialog):
"""New deck stats.""" """New deck stats."""
def __init__(self, mw: aqt.main.AnkiQt): def __init__(self, mw: aqt.main.AnkiQt) -> None:
QDialog.__init__(self, mw, Qt.Window) QDialog.__init__(self, mw, Qt.Window)
mw.setupDialogGC(self) mw.setupDialogGC(self)
self.mw = mw self.mw = mw
@ -60,11 +60,11 @@ class NewDeckStats(QDialog):
aqt.dialogs.markClosed("NewDeckStats") aqt.dialogs.markClosed("NewDeckStats")
QDialog.reject(self) QDialog.reject(self)
def closeWithCallback(self, callback): def closeWithCallback(self, callback) -> None:
self.reject() self.reject()
callback() callback()
def _imagePath(self): def _imagePath(self) -> str:
name = time.strftime("-%Y-%m-%d@%H-%M-%S.pdf", time.localtime(time.time())) name = time.strftime("-%Y-%m-%d@%H-%M-%S.pdf", time.localtime(time.time()))
name = "anki-" + tr(TR.STATISTICS_STATS) + name name = "anki-" + tr(TR.STATISTICS_STATS) + name
file = getSaveFile( file = getSaveFile(
@ -77,17 +77,17 @@ class NewDeckStats(QDialog):
) )
return file return file
def saveImage(self): def saveImage(self) -> None:
path = self._imagePath() path = self._imagePath()
if not path: if not path:
return return
self.form.web.page().printToPdf(path) self.form.web.page().printToPdf(path)
tooltip(tr(TR.STATISTICS_SAVED)) tooltip(tr(TR.STATISTICS_SAVED))
def changePeriod(self, n): def changePeriod(self, n) -> None:
pass pass
def changeScope(self, type): def changeScope(self, type) -> None:
pass pass
def _on_bridge_cmd(self, cmd: str) -> bool: def _on_bridge_cmd(self, cmd: str) -> bool:
@ -149,11 +149,11 @@ class DeckStats(QDialog):
aqt.dialogs.markClosed("DeckStats") aqt.dialogs.markClosed("DeckStats")
QDialog.reject(self) QDialog.reject(self)
def closeWithCallback(self, callback): def closeWithCallback(self, callback) -> None:
self.reject() self.reject()
callback() callback()
def _imagePath(self): def _imagePath(self) -> str:
name = time.strftime("-%Y-%m-%d@%H-%M-%S.pdf", time.localtime(time.time())) name = time.strftime("-%Y-%m-%d@%H-%M-%S.pdf", time.localtime(time.time()))
name = "anki-" + tr(TR.STATISTICS_STATS) + name name = "anki-" + tr(TR.STATISTICS_STATS) + name
file = getSaveFile( file = getSaveFile(
@ -166,7 +166,7 @@ class DeckStats(QDialog):
) )
return file return file
def saveImage(self): def saveImage(self) -> None:
path = self._imagePath() path = self._imagePath()
if not path: if not path:
return return

View file

@ -135,7 +135,7 @@ class StudyDeck(QDialog):
return False return False
return True return True
def onReset(self): def onReset(self) -> None:
# model updated? # model updated?
if self.nameFunc: if self.nameFunc:
self.origNames = self.nameFunc() self.origNames = self.nameFunc()

View file

@ -40,12 +40,14 @@ class FullSyncChoice(enum.Enum):
DOWNLOAD = 2 DOWNLOAD = 2
def get_sync_status(mw: aqt.main.AnkiQt, callback: Callable[[SyncStatus], None]): def get_sync_status(
mw: aqt.main.AnkiQt, callback: Callable[[SyncStatus], None]
) -> None:
auth = mw.pm.sync_auth() auth = mw.pm.sync_auth()
if not auth: if not auth:
return SyncStatus(required=SyncStatus.NO_CHANGES) # pylint:disable=no-member callback(SyncStatus(required=SyncStatus.NO_CHANGES)) # pylint:disable=no-member
def on_future_done(fut): def on_future_done(fut) -> None:
try: try:
out = fut.result() out = fut.result()
except Exception as e: except Exception as e:
@ -57,7 +59,7 @@ def get_sync_status(mw: aqt.main.AnkiQt, callback: Callable[[SyncStatus], None])
mw.taskman.run_in_background(lambda: mw.col.sync_status(auth), on_future_done) mw.taskman.run_in_background(lambda: mw.col.sync_status(auth), on_future_done)
def handle_sync_error(mw: aqt.main.AnkiQt, err: Exception): def handle_sync_error(mw: aqt.main.AnkiQt, err: Exception) -> None:
if isinstance(err, SyncError): if isinstance(err, SyncError):
if err.is_auth_error(): if err.is_auth_error():
mw.pm.clear_sync_auth() mw.pm.clear_sync_auth()
@ -87,14 +89,14 @@ def sync_collection(mw: aqt.main.AnkiQt, on_done: Callable[[], None]) -> None:
auth = mw.pm.sync_auth() auth = mw.pm.sync_auth()
assert auth assert auth
def on_timer(): def on_timer() -> None:
on_normal_sync_timer(mw) on_normal_sync_timer(mw)
timer = QTimer(mw) timer = QTimer(mw)
qconnect(timer.timeout, on_timer) qconnect(timer.timeout, on_timer)
timer.start(150) timer.start(150)
def on_future_done(fut): def on_future_done(fut) -> None:
mw.col.db.begin() mw.col.db.begin()
timer.stop() timer.stop()
try: try:
@ -171,14 +173,14 @@ def on_full_sync_timer(mw: aqt.main.AnkiQt) -> None:
def full_download(mw: aqt.main.AnkiQt, on_done: Callable[[], None]) -> None: def full_download(mw: aqt.main.AnkiQt, on_done: Callable[[], None]) -> None:
mw.col.close_for_full_sync() mw.col.close_for_full_sync()
def on_timer(): def on_timer() -> None:
on_full_sync_timer(mw) on_full_sync_timer(mw)
timer = QTimer(mw) timer = QTimer(mw)
qconnect(timer.timeout, on_timer) qconnect(timer.timeout, on_timer)
timer.start(150) timer.start(150)
def on_future_done(fut): def on_future_done(fut) -> None:
timer.stop() timer.stop()
mw.col.reopen(after_full_sync=True) mw.col.reopen(after_full_sync=True)
mw.reset() mw.reset()
@ -199,14 +201,14 @@ def full_download(mw: aqt.main.AnkiQt, on_done: Callable[[], None]) -> None:
def full_upload(mw: aqt.main.AnkiQt, on_done: Callable[[], None]) -> None: def full_upload(mw: aqt.main.AnkiQt, on_done: Callable[[], None]) -> None:
mw.col.close_for_full_sync() mw.col.close_for_full_sync()
def on_timer(): def on_timer() -> None:
on_full_sync_timer(mw) on_full_sync_timer(mw)
timer = QTimer(mw) timer = QTimer(mw)
qconnect(timer.timeout, on_timer) qconnect(timer.timeout, on_timer)
timer.start(150) timer.start(150)
def on_future_done(fut): def on_future_done(fut) -> None:
timer.stop() timer.stop()
mw.col.reopen(after_full_sync=True) mw.col.reopen(after_full_sync=True)
mw.reset() mw.reset()
@ -235,7 +237,7 @@ def sync_login(
if username and password: if username and password:
break break
def on_future_done(fut): def on_future_done(fut) -> None:
try: try:
auth = fut.result() auth = fut.result()
except SyncError as e: except SyncError as e:

View file

@ -68,7 +68,7 @@ class TagLimit(QDialog):
idx = self.dialog.inactiveList.indexFromItem(item) idx = self.dialog.inactiveList.indexFromItem(item)
self.dialog.inactiveList.selectionModel().select(idx, mode) self.dialog.inactiveList.selectionModel().select(idx, mode)
def reject(self): def reject(self) -> None:
self.tags = "" self.tags = ""
QDialog.reject(self) QDialog.reject(self)

View file

@ -31,7 +31,7 @@ class TaskManager(QObject):
self._closures_lock = Lock() self._closures_lock = Lock()
qconnect(self._closures_pending, self._on_closures_pending) qconnect(self._closures_pending, self._on_closures_pending)
def run_on_main(self, closure: Closure): def run_on_main(self, closure: Closure) -> None:
"Run the provided closure on the main thread." "Run the provided closure on the main thread."
with self._closures_lock: with self._closures_lock:
self._closures.append(closure) self._closures.append(closure)
@ -71,7 +71,7 @@ class TaskManager(QObject):
): ):
self.mw.progress.start(parent=parent, label=label, immediate=immediate) self.mw.progress.start(parent=parent, label=label, immediate=immediate)
def wrapped_done(fut): def wrapped_done(fut) -> None:
self.mw.progress.finish() self.mw.progress.finish()
if on_done: if on_done:
on_done(fut) on_done(fut)

View file

@ -481,7 +481,7 @@ if isWin:
return [] return []
return list(map(self._voice_to_object, self.speaker.GetVoices())) return list(map(self._voice_to_object, self.speaker.GetVoices()))
def _voice_to_object(self, voice: Any): def _voice_to_object(self, voice: Any) -> WindowsVoice:
lang = voice.GetAttribute("language") lang = voice.GetAttribute("language")
lang = lcid_hex_str_to_lang_code(lang) lang = lcid_hex_str_to_lang_code(lang)
name = self._tidy_name(voice.GetAttribute("name")) name = self._tidy_name(voice.GetAttribute("name"))
@ -561,7 +561,7 @@ if isWin:
) )
asyncio.run(self.speakText(tag, voice.id)) asyncio.run(self.speakText(tag, voice.id))
def _on_done(self, ret: Future, cb: OnDoneCallback): def _on_done(self, ret: Future, cb: OnDoneCallback) -> None:
ret.result() ret.result()
# inject file into the top of the audio queue # inject file into the top of the audio queue
@ -572,7 +572,7 @@ if isWin:
# then tell player to advance, which will cause the file to be played # then tell player to advance, which will cause the file to be played
cb() cb()
async def speakText(self, tag: TTSTag, voice_id): async def speakText(self, tag: TTSTag, voice_id) -> None:
import winrt.windows.media.speechsynthesis as speechsynthesis # type: ignore import winrt.windows.media.speechsynthesis as speechsynthesis # type: ignore
import winrt.windows.storage.streams as streams # type: ignore import winrt.windows.storage.streams as streams # type: ignore

View file

@ -2,6 +2,7 @@
# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html # License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
import time import time
from typing import Any, Dict
import requests import requests
@ -23,7 +24,7 @@ class LatestVersionFinder(QThread):
self.main = main self.main = main
self.config = main.pm.meta self.config = main.pm.meta
def _data(self): def _data(self) -> Dict[str, Any]:
return { return {
"ver": versionWithBuild(), "ver": versionWithBuild(),
"os": platDesc(), "os": platDesc(),
@ -32,7 +33,7 @@ class LatestVersionFinder(QThread):
"crt": self.config["created"], "crt": self.config["created"],
} }
def run(self): def run(self) -> None:
if not self.config["updates"]: if not self.config["updates"]:
return return
d = self._data() d = self._data()
@ -55,7 +56,7 @@ class LatestVersionFinder(QThread):
self.clockIsOff.emit(diff) # type: ignore self.clockIsOff.emit(diff) # type: ignore
def askAndUpdate(mw, ver): def askAndUpdate(mw, ver) -> None:
baseStr = tr(TR.QT_MISC_ANKI_UPDATEDANKI_HAS_BEEN_RELEASED, val=ver) baseStr = tr(TR.QT_MISC_ANKI_UPDATEDANKI_HAS_BEEN_RELEASED, val=ver)
msg = QMessageBox(mw) msg = QMessageBox(mw)
msg.setStandardButtons(QMessageBox.Yes | QMessageBox.No) # type: ignore msg.setStandardButtons(QMessageBox.Yes | QMessageBox.No) # type: ignore
@ -72,6 +73,6 @@ def askAndUpdate(mw, ver):
openLink(aqt.appWebsite) openLink(aqt.appWebsite)
def showMessages(mw, data): def showMessages(mw, data) -> None:
showText(data["msg"], parent=mw, type="html") showText(data["msg"], parent=mw, type="html")
mw.pm.meta["lastMsg"] = data["msgId"] mw.pm.meta["lastMsg"] = data["msgId"]

View file

@ -22,7 +22,7 @@ serverbaseurl = re.compile(r"^.+:\/\/[^\/]+")
class AnkiWebPage(QWebEnginePage): class AnkiWebPage(QWebEnginePage):
def __init__(self, onBridgeCmd): def __init__(self, onBridgeCmd) -> None:
QWebEnginePage.__init__(self) QWebEnginePage.__init__(self)
self._onBridgeCmd = onBridgeCmd self._onBridgeCmd = onBridgeCmd
self._setupBridge() self._setupBridge()
@ -31,7 +31,7 @@ class AnkiWebPage(QWebEnginePage):
def _setupBridge(self) -> None: def _setupBridge(self) -> None:
class Bridge(QObject): class Bridge(QObject):
@pyqtSlot(str, result=str) # type: ignore @pyqtSlot(str, result=str) # type: ignore
def cmd(self, str): def cmd(self, str) -> Any:
return json.dumps(self.onCmd(str)) return json.dumps(self.onCmd(str))
self._bridge = Bridge() self._bridge = Bridge()
@ -74,7 +74,7 @@ class AnkiWebPage(QWebEnginePage):
script.setRunsOnSubFrames(False) script.setRunsOnSubFrames(False)
self.profile().scripts().insert(script) self.profile().scripts().insert(script)
def javaScriptConsoleMessage(self, level, msg, line, srcID): def javaScriptConsoleMessage(self, level, msg, line, srcID) -> None:
# not translated because console usually not visible, # not translated because console usually not visible,
# and may only accept ascii text # and may only accept ascii text
if srcID.startswith("data"): if srcID.startswith("data"):
@ -101,7 +101,7 @@ class AnkiWebPage(QWebEnginePage):
# https://github.com/ankitects/anki/pull/560 # https://github.com/ankitects/anki/pull/560
sys.stdout.write(buf) sys.stdout.write(buf)
def acceptNavigationRequest(self, url, navType, isMainFrame): def acceptNavigationRequest(self, url, navType, isMainFrame) -> bool:
if not self.open_links_externally: if not self.open_links_externally:
return super().acceptNavigationRequest(url, navType, isMainFrame) return super().acceptNavigationRequest(url, navType, isMainFrame)
@ -120,10 +120,10 @@ class AnkiWebPage(QWebEnginePage):
openLink(url) openLink(url)
return False return False
def _onCmd(self, str): def _onCmd(self, str) -> None:
return self._onBridgeCmd(str) return self._onBridgeCmd(str)
def javaScriptAlert(self, url: QUrl, text: str): def javaScriptAlert(self, url: QUrl, text: str) -> None:
showInfo(text) showInfo(text)
@ -150,7 +150,7 @@ class WebContent:
You should avoid overwriting or interfering with existing data as much You should avoid overwriting or interfering with existing data as much
as possible, instead opting to append your own changes, e.g.: as possible, instead opting to append your own changes, e.g.:
def on_webview_will_set_content(web_content: WebContent, context): def on_webview_will_set_content(web_content: WebContent, context) -> None:
web_content.body += "<my_html>" web_content.body += "<my_html>"
web_content.head += "<my_head>" web_content.head += "<my_head>"
@ -173,7 +173,7 @@ class WebContent:
Then append the subpaths to the corresponding web_content fields Then append the subpaths to the corresponding web_content fields
within a function subscribing to gui_hooks.webview_will_set_content: within a function subscribing to gui_hooks.webview_will_set_content:
def on_webview_will_set_content(web_content: WebContent, context): def on_webview_will_set_content(web_content: WebContent, context) -> None:
addon_package = mw.addonManager.addonFromModule(__name__) addon_package = mw.addonManager.addonFromModule(__name__)
web_content.css.append( web_content.css.append(
f"/_addons/{addon_package}/web/my-addon.css") f"/_addons/{addon_package}/web/my-addon.css")
@ -251,7 +251,7 @@ class AnkiWebView(QWebEngineView):
def set_open_links_externally(self, enable: bool) -> None: def set_open_links_externally(self, enable: bool) -> None:
self._page.open_links_externally = enable self._page.open_links_externally = enable
def onEsc(self): def onEsc(self) -> None:
w = self.parent() w = self.parent()
while w: while w:
if isinstance(w, QDialog) or isinstance(w, QMainWindow): if isinstance(w, QDialog) or isinstance(w, QMainWindow):
@ -266,7 +266,7 @@ class AnkiWebView(QWebEngineView):
break break
w = w.parent() w = w.parent()
def onCopy(self): def onCopy(self) -> None:
if not self.selectedText(): if not self.selectedText():
ctx = self._page.contextMenuData() ctx = self._page.contextMenuData()
if ctx and ctx.mediaType() == QWebEngineContextMenuData.MediaTypeImage: if ctx and ctx.mediaType() == QWebEngineContextMenuData.MediaTypeImage:
@ -274,16 +274,16 @@ class AnkiWebView(QWebEngineView):
else: else:
self.triggerPageAction(QWebEnginePage.Copy) self.triggerPageAction(QWebEnginePage.Copy)
def onCut(self): def onCut(self) -> None:
self.triggerPageAction(QWebEnginePage.Cut) self.triggerPageAction(QWebEnginePage.Cut)
def onPaste(self): def onPaste(self) -> None:
self.triggerPageAction(QWebEnginePage.Paste) self.triggerPageAction(QWebEnginePage.Paste)
def onMiddleClickPaste(self): def onMiddleClickPaste(self) -> None:
self.triggerPageAction(QWebEnginePage.Paste) self.triggerPageAction(QWebEnginePage.Paste)
def onSelectAll(self): def onSelectAll(self) -> None:
self.triggerPageAction(QWebEnginePage.SelectAll) self.triggerPageAction(QWebEnginePage.SelectAll)
def contextMenuEvent(self, evt: QContextMenuEvent) -> None: def contextMenuEvent(self, evt: QContextMenuEvent) -> None:
@ -293,7 +293,7 @@ class AnkiWebView(QWebEngineView):
gui_hooks.webview_will_show_context_menu(self, m) gui_hooks.webview_will_show_context_menu(self, m)
m.popup(QCursor.pos()) m.popup(QCursor.pos())
def dropEvent(self, evt): def dropEvent(self, evt) -> None:
pass pass
def setHtml(self, html: str) -> None: # type: ignore def setHtml(self, html: str) -> None: # type: ignore
@ -312,7 +312,7 @@ class AnkiWebView(QWebEngineView):
if oldFocus: if oldFocus:
oldFocus.setFocus() oldFocus.setFocus()
def load(self, url: QUrl): def load(self, url: QUrl) -> None:
# allow queuing actions when loading url directly # allow queuing actions when loading url directly
self._domDone = False self._domDone = False
super().load(url) super().load(url)
@ -364,7 +364,7 @@ class AnkiWebView(QWebEngineView):
else: else:
return 3 return 3
def _getWindowColor(self): def _getWindowColor(self) -> QColor:
if theme_manager.night_mode: if theme_manager.night_mode:
return theme_manager.qcolor("window-bg") return theme_manager.qcolor("window-bg")
if isMac: if isMac:
@ -508,7 +508,7 @@ body {{ zoom: {zoom}; background: {background}; direction: {lang_dir}; {font} }}
def _evalWithCallback(self, js: str, cb: Callable[[Any], Any]) -> None: def _evalWithCallback(self, js: str, cb: Callable[[Any], Any]) -> None:
if cb: if cb:
def handler(val): def handler(val) -> None:
if self._shouldIgnoreWebEvent(): if self._shouldIgnoreWebEvent():
print("ignored late js callback", cb) print("ignored late js callback", cb)
return return
@ -597,18 +597,18 @@ body {{ zoom: {zoom}; background: {background}; direction: {lang_dir}; {font} }}
self.onBridgeCmd = func self.onBridgeCmd = func
self._bridge_context = context self._bridge_context = context
def hide_while_preserving_layout(self): def hide_while_preserving_layout(self) -> None:
"Hide but keep existing size." "Hide but keep existing size."
sp = self.sizePolicy() sp = self.sizePolicy()
sp.setRetainSizeWhenHidden(True) sp.setRetainSizeWhenHidden(True)
self.setSizePolicy(sp) self.setSizePolicy(sp)
self.hide() self.hide()
def inject_dynamic_style_and_show(self): def inject_dynamic_style_and_show(self) -> None:
"Add dynamic styling, and reveal." "Add dynamic styling, and reveal."
css = self.standard_css() css = self.standard_css()
def after_style(arg): def after_style(arg) -> None:
gui_hooks.webview_did_inject_style_into_page(self) gui_hooks.webview_did_inject_style_into_page(self)
self.show() self.show()

View file

@ -9,5 +9,5 @@
(sleep 1 && touch aqt) (sleep 1 && touch aqt)
. ~/pyenv/bin/activate . ~/pyenv/bin/activate
fswatch -o aqt | xargs -n1 -I{} sh -c 'printf \\033c; dmypy run aqt' fswatch -o aqt | xargs -n1 -I{} sh -c 'printf \\033c\\n; dmypy run aqt'