diff --git a/qt/aqt/addcards.py b/qt/aqt/addcards.py index 50df50134..2e2f88f04 100644 --- a/qt/aqt/addcards.py +++ b/qt/aqt/addcards.py @@ -1,7 +1,7 @@ # Copyright: Ankitects Pty Ltd and contributors # -*- coding: utf-8 -*- # License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html -from typing import Callable, List +from typing import Callable, List, Optional import aqt.deckchooser import aqt.editor @@ -143,7 +143,7 @@ class AddCards(QDialog): self.history = self.history[:15] self.historyButton.setEnabled(True) - def onHistory(self): + def onHistory(self) -> None: m = QMenu(self) for nid in self.history: if self.mw.col.findNotes("nid:%s" % nid): @@ -152,7 +152,7 @@ class AddCards(QDialog): if len(txt) > 30: txt = txt[:30] + "..." a = m.addAction(_('Edit "%s"') % txt) - a.triggered.connect(lambda b, nid=nid: self.editHistory(nid)) + qconnect(a.triggered, lambda b, nid=nid: self.editHistory(nid)) else: a = m.addAction(_("(Note deleted)")) a.setEnabled(False) @@ -164,12 +164,12 @@ class AddCards(QDialog): browser.form.searchEdit.lineEdit().setText("nid:%d" % nid) browser.onSearchActivated() - def addNote(self, note): + def addNote(self, note) -> Optional[Note]: note.model()["did"] = self.deckChooser.selectedId() ret = note.dupeOrEmpty() if ret == 1: showWarning(_("The first field is empty."), help="AddItems#AddError") - return + return None if "{{cloze:" in note.model()["tmpls"][0]["qfmt"]: if not self.mw.col.models._availClozeOrds( note.model(), note.joinedFields(), False @@ -180,7 +180,7 @@ class AddCards(QDialog): "but have not made any cloze deletions. Proceed?" ) ): - return + return None cards = self.mw.col.addNote(note) if not cards: showWarning( @@ -191,7 +191,7 @@ question on all cards.""" ), help="AddItems", ) - return + return None self.mw.col.clearUndo() self.addHistory(note) self.mw.requireReset() diff --git a/qt/aqt/browser.py b/qt/aqt/browser.py index a7c5c57fe..cb5b0cfd7 100644 --- a/qt/aqt/browser.py +++ b/qt/aqt/browser.py @@ -9,11 +9,12 @@ import sre_constants import time import unicodedata from operator import itemgetter -from typing import Callable, List, Optional +from typing import Callable, List, Optional, Union import anki import aqt.forms from anki import hooks +from anki.cards import Card from anki.collection import _Collection from anki.consts import * from anki.lang import _, ngettext @@ -27,6 +28,7 @@ from anki.utils import ( isWin, ) from aqt import AnkiQt, gui_hooks +from aqt.editor import Editor from aqt.qt import * from aqt.sound import allSounds, clearAudioQueue, play from aqt.utils import ( @@ -556,8 +558,9 @@ class Browser(QMainWindow): model: DataModel mw: AnkiQt col: _Collection + editor: Optional[Editor] - def __init__(self, mw: AnkiQt): + def __init__(self, mw: AnkiQt) -> None: QMainWindow.__init__(self, None, Qt.Window) self.mw = mw self.col = self.mw.col @@ -572,7 +575,7 @@ class Browser(QMainWindow): restoreState(self, "editor") restoreSplitter(self.form.splitter, "editor3") self.form.splitter.setChildrenCollapsible(False) - self.card = None + self.card: Optional[Card] = None self.setupColumns() self.setupTable() self.setupMenus() @@ -584,7 +587,7 @@ class Browser(QMainWindow): self.setupSearch() self.show() - def setupMenus(self): + def setupMenus(self) -> None: # pylint: disable=unnecessary-lambda # actions f = self.form @@ -635,9 +638,9 @@ class Browser(QMainWindow): f.actionGuide.triggered.connect(self.onHelp) # keyboard shortcut for shift+home/end self.pgUpCut = QShortcut(QKeySequence("Shift+Home"), self) - self.pgUpCut.activated.connect(self.onFirstCard) + qconnect(self.pgUpCut.activated, self.onFirstCard) self.pgDownCut = QShortcut(QKeySequence("Shift+End"), self) - self.pgDownCut.activated.connect(self.onLastCard) + qconnect(self.pgDownCut.activated, self.onLastCard) # add-on hook gui_hooks.browser_menus_did_init(self) self.mw.maybeHideAccelerators(self) @@ -646,14 +649,14 @@ class Browser(QMainWindow): self.form.tableView.setContextMenuPolicy(Qt.CustomContextMenu) self.form.tableView.customContextMenuRequested.connect(self.onContextMenu) - def onContextMenu(self, _point): + def onContextMenu(self, _point) -> None: m = QMenu() for act in self.form.menu_Cards.actions(): m.addAction(act) m.addSeparator() for act in self.form.menu_Notes.actions(): m.addAction(act) - gui_hooks.browser_will_show_context_menu(self, m) + gui_hooks.browser_will_show_context_menu(self) qtMenuShortcutWorkaround(m) m.exec_(QCursor.pos()) @@ -771,7 +774,7 @@ class Browser(QMainWindow): self.search() # search triggered programmatically. caller must have saved note first. - def search(self): + def search(self) -> None: if "is:current" in self._lastSearchTxt: # show current card if there is one c = self.mw.reviewer.card @@ -827,7 +830,7 @@ class Browser(QMainWindow): "Update current note and hide/show editor." self.editor.saveNow(lambda: self._onRowChanged(current, previous)) - def _onRowChanged(self, current, previous): + def _onRowChanged(self, current, previous) -> None: update = self.updateTitle() show = self.model.cards and update == 1 self.form.splitter.widget(1).setVisible(not not show) @@ -841,7 +844,7 @@ class Browser(QMainWindow): else: self.editor.setNote(self.card.note(reload=True), focusTo=self.focusTo) self.focusTo = None - self.editor.card = self.card + self.editor.card = self.card # type: ignore self.singleCard = True self._updateFlagsMenu() gui_hooks.browser_did_change_row(self) @@ -1530,7 +1533,7 @@ where id in %s""" ###################################################################### _previewTimer = None - _lastPreviewRender = 0 + _lastPreviewRender: Union[int,float] = 0 _lastPreviewState = None _previewCardChanged = False @@ -1669,7 +1672,7 @@ where id in %s""" self._previewTimer.stop() self._previewTimer = None - def _renderScheduledPreview(self): + def _renderScheduledPreview(self) -> None: self._cancelPreviewTimer() self._lastPreviewRender = time.time() @@ -2019,7 +2022,7 @@ update cards set usn=?, mod=?, did=? where id in """ # Edit: undo ###################################################################### - def setupHooks(self): + def setupHooks(self) -> None: gui_hooks.undo_state_did_change.append(self.onUndoState) gui_hooks.state_did_reset.append(self.onReset) gui_hooks.editor_did_fire_typing_timer.append(self.refreshCurrentCard) @@ -2029,7 +2032,7 @@ update cards set usn=?, mod=?, did=? where id in """ hooks.note_type_added.append(self.maybeRefreshSidebar) hooks.deck_added.append(self.maybeRefreshSidebar) - def teardownHooks(self): + def teardownHooks(self) -> None: gui_hooks.undo_state_did_change.remove(self.onUndoState) gui_hooks.state_did_reset.remove(self.onReset) gui_hooks.editor_did_fire_typing_timer.remove(self.refreshCurrentCard) @@ -2261,7 +2264,7 @@ update cards set usn=?, mod=?, did=? where id in """ class ChangeModel(QDialog): - def __init__(self, browser, nids): + def __init__(self, browser, nids) -> None: QDialog.__init__(self, browser) self.browser = browser self.nids = nids @@ -2394,7 +2397,7 @@ class ChangeModel(QDialog): old=self.oldModel["flds"], combos=self.fcombos, new=self.targetModel["flds"] ) - def cleanup(self): + def cleanup(self) -> None: gui_hooks.state_did_reset.remove(self.onReset) gui_hooks.current_note_type_did_change.remove(self.onReset) self.modelChooser.cleanup() diff --git a/qt/aqt/clayout.py b/qt/aqt/clayout.py index 39a991750..ca0f0772d 100644 --- a/qt/aqt/clayout.py +++ b/qt/aqt/clayout.py @@ -326,7 +326,7 @@ Please create a new card type first.""" self._previewTimer.stop() self._previewTimer = None - def _renderPreview(self): + def _renderPreview(self) -> None: self.cancelPreviewTimer() c = self.card diff --git a/qt/aqt/deckbrowser.py b/qt/aqt/deckbrowser.py index 9c8d9cfe5..6a5eb8e60 100644 --- a/qt/aqt/deckbrowser.py +++ b/qt/aqt/deckbrowser.py @@ -235,16 +235,16 @@ where id > ?""", # Options ########################################################################## - def _showOptions(self, did): + def _showOptions(self, did) -> None: m = QMenu(self.mw) a = m.addAction(_("Rename")) - a.triggered.connect(lambda b, did=did: self._rename(did)) + qconnect(a.triggered, lambda b, did=did: self._rename(did)) a = m.addAction(_("Options")) - a.triggered.connect(lambda b, did=did: self._options(did)) + qconnect(a.triggered, lambda b, did=did: self._options(did)) a = m.addAction(_("Export")) - a.triggered.connect(lambda b, did=did: self._export(did)) + qconnect(a.triggered, lambda b, did=did: self._export(did)) a = m.addAction(_("Delete")) - a.triggered.connect(lambda b, did=did: self._delete(did)) + qconnect(a.triggered, lambda b, did=did: self._delete(did)) gui_hooks.deck_browser_will_show_options_menu(m, did) m.exec_(QCursor.pos()) diff --git a/qt/aqt/deckchooser.py b/qt/aqt/deckchooser.py index 15d4d3466..5efe6f0b0 100644 --- a/qt/aqt/deckchooser.py +++ b/qt/aqt/deckchooser.py @@ -9,9 +9,9 @@ from aqt.utils import shortcut class DeckChooser(QHBoxLayout): - def __init__(self, mw, widget, label=True, start=None): + def __init__(self, mw, widget: QWidget, label=True, start=None) -> None: QHBoxLayout.__init__(self) - self.widget = widget + self.widget = widget # type: ignore self.mw = mw self.deck = mw.col self.label = label @@ -63,7 +63,7 @@ class DeckChooser(QHBoxLayout): def hide(self): self.widget.hide() - def cleanup(self): + def cleanup(self) -> None: gui_hooks.current_note_type_did_change.remove(self.onModelChange) def onModelChange(self): diff --git a/qt/aqt/editcurrent.py b/qt/aqt/editcurrent.py index 4e2520b01..e612f6c65 100644 --- a/qt/aqt/editcurrent.py +++ b/qt/aqt/editcurrent.py @@ -10,7 +10,7 @@ from aqt.utils import restoreGeom, saveGeom, tooltip class EditCurrent(QDialog): - def __init__(self, mw): + def __init__(self, mw) -> None: QDialog.__init__(self, None, Qt.Window) mw.setupDialogGC(self) self.mw = mw @@ -33,7 +33,7 @@ class EditCurrent(QDialog): # pylint: disable=unnecessary-lambda self.mw.progress.timer(100, lambda: self.editor.web.setFocus(), False) - def onReset(self): + def onReset(self) -> None: # lazy approach for now: throw away edits try: n = self.editor.note @@ -58,7 +58,7 @@ class EditCurrent(QDialog): def saveAndClose(self): self.editor.saveNow(self._saveAndClose) - def _saveAndClose(self): + def _saveAndClose(self) -> None: gui_hooks.state_did_reset.remove(self.onReset) r = self.mw.reviewer try: diff --git a/qt/aqt/editor.py b/qt/aqt/editor.py index 98b2a8671..022d13d39 100644 --- a/qt/aqt/editor.py +++ b/qt/aqt/editor.py @@ -21,6 +21,7 @@ import aqt import aqt.sound from anki.hooks import runFilter from anki.lang import _ +from anki.notes import Note from anki.sync import AnkiRequestsClient from anki.utils import checksum, isWin, namedtmp, stripHTMLMedia from aqt import AnkiQt, gui_hooks @@ -67,13 +68,13 @@ html { background: %s; } # caller is responsible for resetting note on reset class Editor: - def __init__(self, mw: AnkiQt, widget, parentWindow, addMode=False): + def __init__(self, mw: AnkiQt, widget, parentWindow, addMode=False) -> None: self.mw = mw self.widget = widget self.parentWindow = parentWindow - self.note = None + self.note: Optional[Note] = None self.addMode = addMode - self.currentField = None + self.currentField: Optional[int] = None # current card, for card layout self.card = None self.setupOuter() @@ -91,7 +92,7 @@ class Editor: self.widget.setLayout(l) self.outerLayout = l - def setupWeb(self): + def setupWeb(self) -> None: self.web = EditorWebView(self.widget, self) self.web.title = "editor" self.web.allowDrops = True @@ -159,7 +160,7 @@ class Editor: fldsTitle=_("Customize Fields"), cardsTitle=shortcut(_("Customize Card Templates (Ctrl+L)")), ) - bgcol = self.mw.app.palette().window().color().name() + bgcol = self.mw.app.palette().window().color().name() # type: ignore # then load page self.web.stdHtml( _html % (bgcol, bgcol, topbuts, _("Show Duplicates")), @@ -260,7 +261,7 @@ class Editor: ) ) - def setupShortcuts(self): + def setupShortcuts(self) -> None: # if a third element is provided, enable shortcut even when no field selected cuts: List[Tuple] = [ ("Ctrl+L", self.onCardLayout, True), @@ -292,7 +293,7 @@ class Editor: fn = self._addFocusCheck(fn) else: keys, fn, _ = row - QShortcut(QKeySequence(keys), self.widget, activated=fn) + QShortcut(QKeySequence(keys), self.widget, activated=fn) # type: ignore def _addFocusCheck(self, fn): def checkFocus(): @@ -329,7 +330,7 @@ class Editor: # JS->Python bridge ###################################################################### - def onBridgeCmd(self, cmd): + def onBridgeCmd(self, cmd) -> None: if not self.note: # shutdown return @@ -399,7 +400,7 @@ class Editor: def loadNoteKeepingFocus(self): self.loadNote(self.currentField) - def loadNote(self, focusTo=None): + def loadNote(self, focusTo=None) -> None: if not self.note: return @@ -426,7 +427,7 @@ class Editor: ) self.web.evalWithCallback(js, oncallback) - def fonts(self): + def fonts(self) -> List[Tuple[str,int,bool]]: return [ (gui_hooks.editor_will_use_font_for_field(f["font"]), f["size"], f["rtl"]) for f in self.note.model()["flds"] @@ -528,7 +529,7 @@ class Editor: if not self.tags.text() or not self.addMode: self.tags.setText(self.note.stringTags().strip()) - def saveTags(self): + def saveTags(self) -> None: if not self.note: return tagsTxt = unicodedata.normalize("NFC", self.tags.text()) @@ -1103,14 +1104,14 @@ class EditorWebView(AnkiWebView): mime.setHtml("" + html) clip.setMimeData(mime) - def contextMenuEvent(self, evt): + def contextMenuEvent(self, evt) -> None: m = QMenu(self) a = m.addAction(_("Cut")) - a.triggered.connect(self.onCut) + qconnect(a.triggered, self.onCut) a = m.addAction(_("Copy")) - a.triggered.connect(self.onCopy) + qconnect(a.triggered, self.onCopy) a = m.addAction(_("Paste")) - a.triggered.connect(self.onPaste) + qconnect(a.triggered, self.onPaste) gui_hooks.editor_will_show_context_menu(self, m) m.popup(QCursor.pos()) diff --git a/qt/aqt/importing.py b/qt/aqt/importing.py index 074584a67..2c649e7c5 100644 --- a/qt/aqt/importing.py +++ b/qt/aqt/importing.py @@ -73,7 +73,7 @@ class ChangeMap(QDialog): class ImportDialog(QDialog): - def __init__(self, mw: AnkiQt, importer): + def __init__(self, mw: AnkiQt, importer) -> None: QDialog.__init__(self, mw, Qt.Window) self.mw = mw self.importer = importer @@ -278,7 +278,7 @@ you can enter it here. Use \\t to represent tab.""" else: self.showMapping(keepMapping=True) - def reject(self): + def reject(self) -> None: self.modelChooser.cleanup() self.deck.cleanup() gui_hooks.current_note_type_did_change.remove(self.modelChanged) diff --git a/qt/aqt/main.py b/qt/aqt/main.py index c702e93cc..fdfa21672 100644 --- a/qt/aqt/main.py +++ b/qt/aqt/main.py @@ -569,7 +569,7 @@ from the profile screen." def _deckBrowserState(self, oldState: str) -> None: self.deckBrowser.show() - def _colLoadingState(self, oldState): + def _colLoadingState(self, oldState) -> None: "Run once, when col is loaded." self.enableColMenuItems() # ensure cwd is set if media dir exists @@ -918,7 +918,7 @@ QTreeWidget { # Undo & autosave ########################################################################## - def onUndo(self): + def onUndo(self) -> None: n = self.col.undoName() if not n: return diff --git a/qt/aqt/modelchooser.py b/qt/aqt/modelchooser.py index 0ddc0a8ee..b81f9ec29 100644 --- a/qt/aqt/modelchooser.py +++ b/qt/aqt/modelchooser.py @@ -9,9 +9,9 @@ from aqt.utils import shortcut class ModelChooser(QHBoxLayout): - def __init__(self, mw, widget, label=True): + def __init__(self, mw, widget, label=True) -> None: QHBoxLayout.__init__(self) - self.widget = widget + self.widget = widget # type: ignore self.mw = mw self.deck = mw.col self.label = label @@ -19,7 +19,7 @@ class ModelChooser(QHBoxLayout): self.setSpacing(8) self.setupModels() gui_hooks.state_did_reset.append(self.onReset) - self.widget.setLayout(self) + self.widget.setLayout(self) # type: ignore def setupModels(self): if self.label: @@ -40,7 +40,7 @@ class ModelChooser(QHBoxLayout): self.models.setSizePolicy(sizePolicy) self.updateModels() - def cleanup(self): + def cleanup(self) -> None: gui_hooks.state_did_reset.remove(self.onReset) def onReset(self): @@ -57,12 +57,12 @@ class ModelChooser(QHBoxLayout): aqt.models.Models(self.mw, self.widget) - def onModelChange(self): + def onModelChange(self) -> None: from aqt.studydeck import StudyDeck current = self.deck.models.current()["name"] # edit button - edit = QPushButton(_("Manage"), clicked=self.onEdit) + edit = QPushButton(_("Manage"), clicked=self.onEdit) # type: ignore def nameFunc(): return sorted(self.deck.models.allNames()) diff --git a/qt/aqt/qt.py b/qt/aqt/qt.py index 23f146537..9552f6ef5 100644 --- a/qt/aqt/qt.py +++ b/qt/aqt/qt.py @@ -14,6 +14,7 @@ from PyQt5.QtCore import pyqtRemoveInputHook # pylint: disable=no-name-in-modul from PyQt5.QtGui import * # type: ignore from PyQt5.QtWebEngineWidgets import * # type: ignore from PyQt5.QtWidgets import * +from typing import Callable from anki.utils import isMac, isWin @@ -52,3 +53,7 @@ qtpoint = QT_VERSION & 0xFF if qtmajor != 5 or qtminor < 9 or qtminor == 10: raise Exception("Anki does not support your Qt version.") + +def qconnect(signal: Callable, func: Callable) -> None: + "Helper to work around type checking not working with signal.connect(func)." + signal.connect(func) # type: ignore \ No newline at end of file diff --git a/qt/aqt/reviewer.py b/qt/aqt/reviewer.py index 2fc860c64..42524df2e 100644 --- a/qt/aqt/reviewer.py +++ b/qt/aqt/reviewer.py @@ -8,7 +8,7 @@ import html.parser import json import re import unicodedata as ucd -from typing import List +from typing import List, Optional import aqt from anki import hooks @@ -33,13 +33,13 @@ class Reviewer: def __init__(self, mw: AnkiQt) -> None: self.mw = mw self.web = mw.web - self.card = None + self.card: Optional[Card] = None self.cardQueue: List[Card] = [] self.hadCardQueue = False self._answeredIds: List[int] = [] self._recordedAudio = None self.typeCorrect = None # web init happens before this is set - self.state = None + self.state: Optional[str] = None self.bottom = aqt.toolbar.BottomBar(mw, mw.bottomWeb) hooks.card_did_leech.append(self.onLeech) @@ -61,7 +61,7 @@ class Reviewer: # id was deleted return - def cleanup(self): + def cleanup(self) -> None: gui_hooks.reviewer_will_end() # Fetching a card @@ -170,7 +170,7 @@ class Reviewer: def _mungeQA(self, buf): return self.typeAnsFilter(mungeQA(self.mw.col, buf)) - def _showQuestion(self): + def _showQuestion(self) -> None: self._reps += 1 self.state = "question" self.typedAnswer = None @@ -217,7 +217,7 @@ The front of this card is empty. Please run Tools>Empty Cards.""" # Showing the answer ########################################################################## - def _showAnswer(self): + def _showAnswer(self) -> None: if self.mw.state != "review": # showing resetRequired screen; ignore space return @@ -689,7 +689,7 @@ time = %(time)d; ] return opts - def showContextMenu(self): + def showContextMenu(self) -> None: opts = self._contextMenu() m = QMenu(self.mw) self._addMenuItems(m, opts) diff --git a/qt/aqt/studydeck.py b/qt/aqt/studydeck.py index 986992e11..8a331f5e6 100644 --- a/qt/aqt/studydeck.py +++ b/qt/aqt/studydeck.py @@ -23,7 +23,7 @@ class StudyDeck(QDialog): dyn=False, buttons=None, geomKey="default", - ): + ) -> None: QDialog.__init__(self, parent or mw) if buttons is None: buttons = [] @@ -118,7 +118,7 @@ class StudyDeck(QDialog): self.origNames = self.nameFunc() self.redraw(self.filt, self.focus) - def accept(self): + def accept(self) -> None: saveGeom(self, self.geomKey) gui_hooks.state_did_reset.remove(self.onReset) row = self.form.list.currentRow() @@ -128,12 +128,12 @@ class StudyDeck(QDialog): self.name = self.names[self.form.list.currentRow()] QDialog.accept(self) - def reject(self): + def reject(self) -> None: saveGeom(self, self.geomKey) gui_hooks.state_did_reset.remove(self.onReset) QDialog.reject(self) - def onAddDeck(self): + def onAddDeck(self) -> None: row = self.form.list.currentRow() if row < 0: default = self.form.filter.text() diff --git a/qt/aqt/webview.py b/qt/aqt/webview.py index fcae897e9..b4a27e084 100644 --- a/qt/aqt/webview.py +++ b/qt/aqt/webview.py @@ -178,10 +178,10 @@ class AnkiWebView(QWebEngineView): # type: ignore def onSelectAll(self): self.triggerPageAction(QWebEnginePage.SelectAll) - def contextMenuEvent(self, evt): + def contextMenuEvent(self, evt) -> None: m = QMenu(self) a = m.addAction(_("Copy")) - a.triggered.connect(self.onCopy) + a.triggered.connect(self.onCopy) # type: ignore gui_hooks.webview_will_show_context_menu(self, m) m.popup(QCursor.pos())