fix a bunch of qt typing issues uncovered by the following commit

This commit is contained in:
Damien Elmes 2021-03-17 14:51:59 +10:00
parent 75a0f165c6
commit 0c59c8b591
23 changed files with 198 additions and 139 deletions

View file

@ -10,7 +10,7 @@ import os
import sys
import tempfile
import traceback
from typing import Any, Callable, Dict, List, Optional, Tuple, Union
from typing import Any, Callable, Dict, List, Optional, Tuple, Union, cast
import anki.lang
from anki import version as _version
@ -299,7 +299,7 @@ class AnkiApp(QApplication):
if not sock.waitForReadyRead(self.TMOUT):
sys.stderr.write(sock.errorString())
return
path = bytes(sock.readAll()).decode("utf8")
path = bytes(cast(bytes, sock.readAll())).decode("utf8")
self.appMsg.emit(path) # type: ignore
sock.disconnectFromServer()

View file

@ -715,7 +715,7 @@ class AddonsDialog(QDialog):
gui_hooks.addons_dialog_will_show(self)
self.show()
def dragEnterEvent(self, event: QEvent) -> None:
def dragEnterEvent(self, event: QDragEnterEvent) -> None:
mime = event.mimeData()
if not mime.hasUrls():
return None
@ -724,7 +724,7 @@ class AddonsDialog(QDialog):
if all(url.toLocalFile().endswith(ext) for url in urls):
event.acceptProposedAction()
def dropEvent(self, event: QEvent) -> None:
def dropEvent(self, event: QDropEvent) -> None:
mime = event.mimeData()
paths = []
for url in mime.urls():
@ -908,7 +908,7 @@ class AddonsDialog(QDialog):
class GetAddons(QDialog):
def __init__(self, dlg: QDialog) -> None:
def __init__(self, dlg: AddonsDialog) -> None:
QDialog.__init__(self, dlg)
self.addonsDlg = dlg
self.mgr = dlg.mgr
@ -1079,7 +1079,9 @@ class DownloaderInstaller(QObject):
self.on_done = on_done
self.mgr.mw.progress.start(immediate=True, parent=self.parent())
parent = self.parent()
assert isinstance(parent, QWidget)
self.mgr.mw.progress.start(immediate=True, parent=parent)
self.mgr.mw.taskman.run_in_background(self._download_all, self._download_done)
def _progress_callback(self, up: int, down: int) -> None:
@ -1438,7 +1440,7 @@ def prompt_to_update(
class ConfigEditor(QDialog):
def __init__(self, dlg: QDialog, addon: str, conf: Dict) -> None:
def __init__(self, dlg: AddonsDialog, addon: str, conf: Dict) -> None:
super().__init__(dlg)
self.addon = addon
self.conf = conf
@ -1506,7 +1508,7 @@ class ConfigEditor(QDialog):
txt = gui_hooks.addon_config_editor_will_save_json(txt)
try:
new_conf = json.loads(txt)
jsonschema.validate(new_conf, self.parent().mgr._addon_schema(self.addon))
jsonschema.validate(new_conf, self.mgr._addon_schema(self.addon))
except ValidationError as e:
# The user did edit the configuration and entered a value
# which can not be interpreted.

View file

@ -35,11 +35,12 @@ from aqt.scheduling_ops import (
suspend_cards,
unsuspend_cards,
)
from aqt.sidebar import SidebarSearchBar, SidebarToolbar, SidebarTreeView
from aqt.sidebar import SidebarTreeView
from aqt.theme import theme_manager
from aqt.utils import (
TR,
HelpPage,
KeyboardModifiersPressed,
askUser,
current_top_level_widget,
disable_help_button,
@ -832,7 +833,9 @@ QTableView {{ gridline-color: {grid} }}
gui_hooks.editor_did_init_left_buttons.remove(add_preview_button)
@ensure_editor_saved
def onRowChanged(self, current: Optional[QItemSelection], previous: Optional[QItemSelection]) -> None:
def onRowChanged(
self, current: Optional[QItemSelection], previous: Optional[QItemSelection]
) -> None:
"""Update current note and hide/show editor."""
if self._closeEventHasCleanedUp:
return
@ -975,15 +978,13 @@ QTableView {{ gridline-color: {grid} }}
self.sidebar = SidebarTreeView(self)
self.sidebarTree = self.sidebar # legacy alias
dw.setWidget(self.sidebar)
self.sidebar.toolbar = toolbar = SidebarToolbar(self.sidebar)
self.sidebar.searchBar = searchBar = SidebarSearchBar(self.sidebar)
qconnect(
self.form.actionSidebarFilter.triggered,
self.focusSidebarSearchBar,
)
grid = QGridLayout()
grid.addWidget(searchBar, 0, 0)
grid.addWidget(toolbar, 0, 1)
grid.addWidget(self.sidebar.searchBar, 0, 0)
grid.addWidget(self.sidebar.toolbar, 0, 1)
grid.addWidget(self.sidebar, 1, 0, 1, 2)
grid.setContentsMargins(0, 0, 0, 0)
grid.setSpacing(0)
@ -1116,10 +1117,7 @@ where id in %s"""
def createFilteredDeck(self) -> None:
search = self.form.searchEdit.lineEdit().text()
if (
self.mw.col.schedVer() != 1
and self.mw.app.keyboardModifiers() & Qt.AltModifier
):
if self.mw.col.schedVer() != 1 and KeyboardModifiersPressed().alt:
aqt.dialogs.open("DynDeckConfDialog", self.mw, search_2=search)
else:
aqt.dialogs.open("DynDeckConfDialog", self.mw, search=search)
@ -1482,9 +1480,9 @@ where id in %s"""
combo = "BrowserFindAndReplace"
findhistory = restore_combo_history(frm.find, combo + "Find")
frm.find.completer().setCaseSensitivity(True)
frm.find._completer().setCaseSensitivity(True)
replacehistory = restore_combo_history(frm.replace, combo + "Replace")
frm.replace.completer().setCaseSensitivity(True)
frm.replace._completer().setCaseSensitivity(True)
restore_is_checked(frm.re, combo + "Regex")
restore_is_checked(frm.ignoreCase, combo + "ignoreCase")
@ -1668,7 +1666,7 @@ where id in %s"""
sm = self.form.tableView.selectionModel()
idx = sm.currentIndex()
self._moveCur(None, self.model.index(0, 0))
if not self.mw.app.keyboardModifiers() & Qt.ShiftModifier:
if not KeyboardModifiersPressed().shift:
return
idx2 = sm.currentIndex()
item = QItemSelection(idx2, idx)
@ -1678,7 +1676,7 @@ where id in %s"""
sm = self.form.tableView.selectionModel()
idx = sm.currentIndex()
self._moveCur(None, self.model.index(len(self.model.cards) - 1, 0))
if not self.mw.app.keyboardModifiers() & Qt.ShiftModifier:
if not KeyboardModifiersPressed().shift:
return
idx2 = sm.currentIndex()
item = QItemSelection(idx, idx2)
@ -1728,6 +1726,9 @@ class ChangeModel(QDialog):
restoreGeom(self, "changeModel")
gui_hooks.state_did_reset.append(self.onReset)
gui_hooks.current_note_type_did_change.append(self.on_note_type_change)
# ugh - these are set dynamically by rebuildTemplateMap()
self.tcombos: List[QComboBox] = []
self.fcombos: List[QComboBox] = []
self.exec_()
def on_note_type_change(self, notetype: NoteType) -> None:

View file

@ -795,7 +795,7 @@ class CardLayout(QDialog):
showWarning(str(e))
return
self.mw.reset()
tooltip(tr(TR.CARD_TEMPLATES_CHANGES_SAVED), parent=self.parent())
tooltip(tr(TR.CARD_TEMPLATES_CHANGES_SAVED), parent=self.parentWidget())
self.cleanup()
gui_hooks.sidebar_should_refresh_notetypes()
return QDialog.accept(self)

View file

@ -6,14 +6,14 @@ from __future__ import annotations
from typing import Sequence
from anki.lang import TR
from aqt import AnkiQt, QDialog
from aqt import AnkiQt, QWidget
from aqt.utils import tooltip, tr
def remove_decks(
*,
mw: AnkiQt,
parent: QDialog,
parent: QWidget,
deck_ids: Sequence[int],
) -> None:
mw.perform_op(

View file

@ -162,7 +162,6 @@ class DeckBrowser:
],
context=self,
)
self.web.key = "deckBrowser"
self._drawButtons()
if offset is not None:
self._scrollToOffset(offset)

View file

@ -34,6 +34,7 @@ from aqt.theme import theme_manager
from aqt.utils import (
TR,
HelpPage,
KeyboardModifiersPressed,
disable_help_button,
getFile,
openHelp,
@ -753,7 +754,7 @@ class Editor:
if m:
highest = max(highest, sorted([int(x) for x in m])[-1])
# reuse last?
if not self.mw.app.keyboardModifiers() & Qt.AltModifier:
if not KeyboardModifiersPressed().alt:
highest += 1
# must start at 1
highest = max(1, highest)
@ -1130,7 +1131,7 @@ class EditorWebView(AnkiWebView):
strip_html = self.editor.mw.col.get_config_bool(
Config.Bool.PASTE_STRIPS_FORMATTING
)
if self.editor.mw.app.queryKeyboardModifiers() & Qt.ShiftModifier:
if KeyboardModifiersPressed().shift:
strip_html = not strip_html
return strip_html

View file

@ -4,7 +4,7 @@ import html
import re
import sys
import traceback
from typing import Optional
from typing import Optional, TextIO, cast
from markdown import markdown
@ -37,7 +37,7 @@ class ErrorHandler(QObject):
qconnect(self.errorTimer, self._setTimer)
self.pool = ""
self._oldstderr = sys.stderr
sys.stderr = self
sys.stderr = cast(TextIO, self)
def unload(self) -> None:
sys.stderr = self._oldstderr

View file

@ -26,7 +26,7 @@ from aqt.utils import (
class FieldDialog(QDialog):
def __init__(
self, mw: AnkiQt, nt: NoteType, parent: Optional[QDialog] = None
self, mw: AnkiQt, nt: NoteType, parent: Optional[QWidget] = None
) -> None:
QDialog.__init__(self, parent or mw)
self.mw = mw

View file

@ -72,6 +72,7 @@ from aqt.theme import theme_manager
from aqt.utils import (
TR,
HelpPage,
KeyboardModifiersPressed,
askUser,
checkInvalidFilename,
current_top_level_widget,
@ -121,7 +122,7 @@ class AnkiQt(QMainWindow):
def __init__(
self,
app: QApplication,
app: aqt.AnkiApp,
profileManager: ProfileManagerType,
backend: _RustBackend,
opts: Namespace,
@ -138,9 +139,7 @@ class AnkiQt(QMainWindow):
self.app = app
self.pm = profileManager
# init rest of app
self.safeMode = (
self.app.queryKeyboardModifiers() & Qt.ShiftModifier
) or self.opts.safemode
self.safeMode = (KeyboardModifiersPressed().shift) or self.opts.safemode
try:
self.setupUI()
self.setupAddons(args)
@ -927,10 +926,8 @@ title="%s" %s>%s</button>""" % (
# force webengine processes to load before cwd is changed
if isWin:
for o in self.web, self.bottomWeb:
o.requiresCol = False
o._domReady = False
o._page.setContent(bytes("", "ascii"))
for webview in self.web, self.bottomWeb:
webview.force_load_hack()
def closeAllWindows(self, onsuccess: Callable) -> None:
aqt.dialogs.closeAll(onsuccess)
@ -1103,8 +1100,7 @@ title="%s" %s>%s</button>""" % (
("y", self.on_sync_button_clicked),
]
self.applyShortcuts(globalShortcuts)
self.stateShortcuts: Sequence[Tuple[str, Callable]] = []
self.stateShortcuts: List[QShortcut] = []
def applyShortcuts(
self, shortcuts: Sequence[Tuple[str, Callable]]
@ -1281,7 +1277,7 @@ title="%s" %s>%s</button>""" % (
deck = self._selectedDeck()
if not deck:
return
want_old = self.app.queryKeyboardModifiers() & Qt.ShiftModifier
want_old = KeyboardModifiersPressed().shift
if want_old:
aqt.dialogs.open("DeckStats", self)
else:
@ -1538,13 +1534,14 @@ title="%s" %s>%s</button>""" % (
frm = self.debug_diag_form = aqt.forms.debug.Ui_Dialog()
class DebugDialog(QDialog):
silentlyClose = True
def reject(self) -> None:
super().reject()
saveSplitter(frm.splitter, "DebugConsoleWindow")
saveGeom(self, "DebugConsoleWindow")
d = self.debugDiag = DebugDialog()
d.silentlyClose = True
disable_help_button(d)
frm.setupUi(d)
restoreGeom(d, "DebugConsoleWindow")
@ -1708,7 +1705,8 @@ title="%s" %s>%s</button>""" % (
if not self.hideMenuAccels:
return
tgt = tgt or self
for action in tgt.findChildren(QAction):
for action_ in tgt.findChildren(QAction):
action = cast(QAction, action_)
txt = str(action.text())
m = re.match(r"^(.+)\(&.+\)(.+)?", txt)
if m:
@ -1716,7 +1714,7 @@ title="%s" %s>%s</button>""" % (
def hideStatusTips(self) -> None:
for action in self.findChildren(QAction):
action.setStatusTip("")
cast(QAction, action).setStatusTip("")
def onMacMinimize(self) -> None:
self.setWindowState(self.windowState() | Qt.WindowMinimized) # type: ignore

View file

@ -31,7 +31,7 @@ class Models(QDialog):
def __init__(
self,
mw: AnkiQt,
parent: Optional[QDialog] = None,
parent: Optional[QWidget] = None,
fromMain: bool = False,
selected_notetype_id: Optional[int] = None,
):

View file

@ -7,9 +7,8 @@ from typing import Callable, Optional, Sequence
from anki.lang import TR
from anki.notes import Note
from aqt import AnkiQt
from aqt import AnkiQt, QWidget
from aqt.main import PerformOpOptionalSuccessCallback
from aqt.qt import QDialog
from aqt.utils import show_invalid_search_error, showInfo, tr
@ -52,7 +51,7 @@ def remove_tags(
def find_and_replace(
*,
mw: AnkiQt,
parent: QDialog,
parent: QWidget,
note_ids: Sequence[int],
search: str,
replacement: str,

View file

@ -1,11 +1,14 @@
# Copyright: Ankitects Pty Ltd and contributors
# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
# mypy: check-untyped-defs
from __future__ import annotations
import json
import re
import time
from typing import Any, Callable, Optional, Tuple, Union
import aqt.browser
from anki.cards import Card
from anki.collection import Config
from aqt import AnkiQt, gui_hooks
@ -300,6 +303,12 @@ class MultiCardPreviewer(Previewer):
class BrowserPreviewer(MultiCardPreviewer):
_last_card_id = 0
_parent: Optional[aqt.browser.Browser]
def __init__(
self, parent: aqt.browser.Browser, mw: AnkiQt, on_close: Callable[[], None]
) -> None:
super().__init__(parent=parent, mw=mw, on_close=on_close)
def card(self) -> Optional[Card]:
if self._parent.singleCard:

View file

@ -16,7 +16,7 @@ from aqt.utils import TR, disable_help_button, tr
class ProgressManager:
def __init__(self, mw: aqt.AnkiQt) -> None:
self.mw = mw
self.app = QApplication.instance()
self.app = mw.app
self.inDB = False
self.blockUpdates = False
self._show_timer: Optional[QTimer] = None
@ -75,7 +75,7 @@ class ProgressManager:
max: int = 0,
min: int = 0,
label: Optional[str] = None,
parent: Optional[QDialog] = None,
parent: Optional[QWidget] = None,
immediate: bool = False,
) -> Optional[ProgressDialog]:
self._levels += 1

View file

@ -17,7 +17,7 @@ from aqt.utils import getText, tooltip, tr
def set_due_date_dialog(
*,
mw: aqt.AnkiQt,
parent: QDialog,
parent: QWidget,
card_ids: List[int],
config_key: Optional[Config.String.Key.V],
) -> None:
@ -51,7 +51,7 @@ def set_due_date_dialog(
)
def forget_cards(*, mw: aqt.AnkiQt, parent: QDialog, card_ids: List[int]) -> None:
def forget_cards(*, mw: aqt.AnkiQt, parent: QWidget, card_ids: List[int]) -> None:
if not card_ids:
return

View file

@ -23,6 +23,7 @@ from aqt.qt import *
from aqt.theme import ColoredIcon, theme_manager
from aqt.utils import (
TR,
KeyboardModifiersPressed,
askUser,
getOnlyText,
show_invalid_search_error,
@ -254,9 +255,7 @@ class SidebarModel(QAbstractItemModel):
return QVariant(item.tooltip)
return QVariant(theme_manager.icon_from_resources(item.icon))
def setData(
self, index: QModelIndex, text: QVariant, _role: int = Qt.EditRole
) -> bool:
def setData(self, index: QModelIndex, text: str, _role: int = Qt.EditRole) -> bool:
return self.sidebar._on_rename(index.internalPointer(), text)
def supportedDropActions(self) -> Qt.DropActions:
@ -354,6 +353,10 @@ def _want_right_border() -> bool:
return not isMac or theme_manager.night_mode
# fixme: we should have a top-level Sidebar class inheriting from QWidget that
# handles the treeview, search bar and so on. Currently the treeview embeds the
# search bar which is wrong, and the layout code is handled in browser.py instead
# of here
class SidebarTreeView(QTreeView):
def __init__(self, browser: aqt.browser.Browser) -> None:
super().__init__()
@ -390,6 +393,10 @@ class SidebarTreeView(QTreeView):
self.setStyleSheet("QTreeView { %s }" % ";".join(styles))
# these do not really belong here, they should be in a higher-level class
self.toolbar = SidebarToolbar(self)
self.searchBar = SidebarSearchBar(self)
@property
def tool(self) -> SidebarTool:
return self._tool
@ -410,7 +417,7 @@ class SidebarTreeView(QTreeView):
self.setExpandsOnDoubleClick(double_click_expands)
def model(self) -> SidebarModel:
return super().model()
return cast(SidebarModel, super().model())
# Refreshing
###########################
@ -512,22 +519,22 @@ class SidebarTreeView(QTreeView):
joiner: SearchJoiner = "AND",
) -> None:
"""Modify the current search string based on modifier keys, then refresh."""
mods = self.mw.app.keyboardModifiers()
mods = KeyboardModifiersPressed()
previous = SearchNode(parsable_text=self.browser.current_search())
current = self.mw.col.group_searches(*terms, joiner=joiner)
# if Alt pressed, invert
if mods & Qt.AltModifier:
if mods.alt:
current = SearchNode(negated=current)
try:
if mods & Qt.ControlModifier and mods & Qt.ShiftModifier:
if mods.control and mods.shift:
# If Ctrl+Shift, replace searches nodes of the same type.
search = self.col.replace_in_search_node(previous, current)
elif mods & Qt.ControlModifier:
elif mods.control:
# If Ctrl, AND with previous
search = self.col.join_searches(previous, current, "AND")
elif mods & Qt.ShiftModifier:
elif mods.shift:
# If Shift, OR with previous
search = self.col.join_searches(previous, current, "OR")
else:

View file

@ -15,7 +15,7 @@ import wave
from abc import ABC, abstractmethod
from concurrent.futures import Future
from operator import itemgetter
from typing import TYPE_CHECKING, Any, Callable, Dict, List, Optional, Tuple
from typing import TYPE_CHECKING, Any, Callable, Dict, List, Optional, Tuple, cast
import aqt
from anki import hooks
@ -568,7 +568,7 @@ class QtAudioInputRecorder(Recorder):
super().start(on_done)
def _on_read_ready(self) -> None:
self._buffer += self._iodevice.readAll()
self._buffer += cast(bytes, self._iodevice.readAll())
def stop(self, on_done: Callable[[str], None]) -> None:
def on_stop_timer() -> None:

View file

@ -33,9 +33,9 @@ class StudyDeck(QDialog):
help: HelpPageArgument = HelpPage.KEYBOARD_SHORTCUTS,
current: Optional[str] = None,
cancel: bool = True,
parent: Optional[QDialog] = None,
parent: Optional[QWidget] = None,
dyn: bool = False,
buttons: Optional[List[str]] = None,
buttons: Optional[List[Union[str, QPushButton]]] = None,
geomKey: str = "default",
) -> None:
QDialog.__init__(self, parent or mw)
@ -53,8 +53,10 @@ class StudyDeck(QDialog):
self.form.buttonBox.button(QDialogButtonBox.Cancel)
)
if buttons is not None:
for b in buttons:
self.form.buttonBox.addButton(b, QDialogButtonBox.ActionRole)
for button_or_label in buttons:
self.form.buttonBox.addButton(
button_or_label, QDialogButtonBox.ActionRole
)
else:
b = QPushButton(tr(TR.ACTIONS_ADD))
b.setShortcut(QKeySequence("Ctrl+N"))
@ -89,7 +91,7 @@ class StudyDeck(QDialog):
self.exec_()
def eventFilter(self, obj: QObject, evt: QEvent) -> bool:
if evt.type() == QEvent.KeyPress:
if isinstance(evt, QKeyEvent) and evt.type() == QEvent.KeyPress:
new_row = current_row = self.form.list.currentRow()
rows_count = self.form.list.count()
key = evt.key()
@ -98,7 +100,10 @@ class StudyDeck(QDialog):
new_row = current_row - 1
elif key == Qt.Key_Down:
new_row = current_row + 1
elif evt.modifiers() & Qt.ControlModifier and Qt.Key_1 <= key <= Qt.Key_9:
elif (
int(evt.modifiers()) & Qt.ControlModifier
and Qt.Key_1 <= key <= Qt.Key_9
):
row_index = key - Qt.Key_1
if row_index < rows_count:
new_row = row_index

View file

@ -12,24 +12,24 @@ from aqt.qt import *
class TagEdit(QLineEdit):
completer: Union[QCompleter, TagCompleter]
_completer: Union[QCompleter, TagCompleter]
lostFocus = pyqtSignal()
# 0 = tags, 1 = decks
def __init__(self, parent: QDialog, type: int = 0) -> None:
def __init__(self, parent: QWidget, type: int = 0) -> None:
QLineEdit.__init__(self, parent)
self.col: Optional[Collection] = None
self.model = QStringListModel()
self.type = type
if type == 0:
self.completer = TagCompleter(self.model, parent, self)
self._completer = TagCompleter(self.model, parent, self)
else:
self.completer = QCompleter(self.model, parent)
self.completer.setCompletionMode(QCompleter.PopupCompletion)
self.completer.setCaseSensitivity(Qt.CaseInsensitive)
self.completer.setFilterMode(Qt.MatchContains)
self.setCompleter(self.completer)
self._completer = QCompleter(self.model, parent)
self._completer.setCompletionMode(QCompleter.PopupCompletion)
self._completer.setCaseSensitivity(Qt.CaseInsensitive)
self._completer.setFilterMode(Qt.MatchContains)
self.setCompleter(self._completer)
def setCol(self, col: Collection) -> None:
"Set the current col, updating list of available tags."
@ -47,29 +47,29 @@ class TagEdit(QLineEdit):
def keyPressEvent(self, evt: QKeyEvent) -> None:
if evt.key() in (Qt.Key_Up, Qt.Key_Down):
# show completer on arrow key up/down
if not self.completer.popup().isVisible():
if not self._completer.popup().isVisible():
self.showCompleter()
return
if evt.key() == Qt.Key_Tab and evt.modifiers() & Qt.ControlModifier:
if evt.key() == Qt.Key_Tab and int(evt.modifiers()) & Qt.ControlModifier:
# select next completion
if not self.completer.popup().isVisible():
if not self._completer.popup().isVisible():
self.showCompleter()
index = self.completer.currentIndex()
self.completer.popup().setCurrentIndex(index)
index = self._completer.currentIndex()
self._completer.popup().setCurrentIndex(index)
cur_row = index.row()
if not self.completer.setCurrentRow(cur_row + 1):
self.completer.setCurrentRow(0)
if not self._completer.setCurrentRow(cur_row + 1):
self._completer.setCurrentRow(0)
return
if (
evt.key() in (Qt.Key_Enter, Qt.Key_Return)
and self.completer.popup().isVisible()
and self._completer.popup().isVisible()
):
# apply first completion if no suggestion selected
selected_row = self.completer.popup().currentIndex().row()
selected_row = self._completer.popup().currentIndex().row()
if selected_row == -1:
self.completer.setCurrentRow(0)
index = self.completer.currentIndex()
self.completer.popup().setCurrentIndex(index)
self._completer.setCurrentRow(0)
index = self._completer.currentIndex()
self._completer.popup().setCurrentIndex(index)
self.hideCompleter()
QWidget.keyPressEvent(self, evt)
return
@ -90,18 +90,18 @@ class TagEdit(QLineEdit):
gui_hooks.tag_editor_did_process_key(self, evt)
def showCompleter(self) -> None:
self.completer.setCompletionPrefix(self.text())
self.completer.complete()
self._completer.setCompletionPrefix(self.text())
self._completer.complete()
def focusOutEvent(self, evt: QFocusEvent) -> None:
QLineEdit.focusOutEvent(self, evt)
self.lostFocus.emit() # type: ignore
self.completer.popup().hide()
self._completer.popup().hide()
def hideCompleter(self) -> None:
if sip.isdeleted(self.completer):
if sip.isdeleted(self._completer):
return
self.completer.popup().hide()
self._completer.popup().hide()
class TagCompleter(QCompleter):

View file

@ -15,8 +15,8 @@ class TagLimit(QDialog):
self.tags: str = ""
self.tags_list: List[str] = []
self.mw = mw
self.parent: Optional[QWidget] = parent
self.deck = self.parent.deck
self.parent_: Optional[CustomStudy] = parent
self.deck = self.parent_.deck
self.dialog = aqt.forms.taglimit.Ui_Dialog()
self.dialog.setupUi(self)
disable_help_button(self)

View file

@ -86,12 +86,12 @@ class ThemeManager:
else:
# specified colours
icon = QIcon(path.path)
img = icon.pixmap(16)
painter = QPainter(img)
pixmap = icon.pixmap(16)
painter = QPainter(pixmap)
painter.setCompositionMode(QPainter.CompositionMode_SourceIn)
painter.fillRect(img.rect(), QColor(path.current_color(self.night_mode)))
painter.fillRect(pixmap.rect(), QColor(path.current_color(self.night_mode)))
painter.end()
icon = QIcon(img)
icon = QIcon(pixmap)
return icon
return cache.setdefault(path, icon)

View file

@ -111,7 +111,7 @@ def openHelp(section: HelpPageArgument) -> None:
openLink(link)
def openLink(link: str) -> None:
def openLink(link: Union[str, QUrl]) -> None:
tooltip(tr(TR.QT_MISC_LOADING), period=1000)
with noBundledLibs():
QDesktopServices.openUrl(QUrl(link))
@ -119,7 +119,7 @@ def openLink(link: str) -> None:
def showWarning(
text: str,
parent: Optional[QDialog] = None,
parent: Optional[QWidget] = None,
help: HelpPageArgument = "",
title: str = "Anki",
textFormat: Optional[TextFormat] = None,
@ -139,7 +139,7 @@ def showCritical(
return showInfo(text, parent, help, "critical", title=title, textFormat=textFormat)
def show_invalid_search_error(err: Exception, parent: Optional[QDialog] = None) -> None:
def show_invalid_search_error(err: Exception, parent: Optional[QWidget] = None) -> None:
"Render search errors in markdown, then display a warning."
text = str(err)
if isinstance(err, InvalidInput):
@ -149,7 +149,7 @@ def show_invalid_search_error(err: Exception, parent: Optional[QDialog] = None)
def showInfo(
text: str,
parent: Union[Literal[False], QDialog] = False,
parent: Optional[QWidget] = None,
help: HelpPageArgument = "",
type: str = "info",
title: str = "Anki",
@ -158,7 +158,7 @@ def showInfo(
) -> int:
"Show a small info window with an OK button."
parent_widget: QWidget
if parent is False:
if parent is None:
parent_widget = aqt.mw.app.activeWindow() or aqt.mw
else:
parent_widget = parent
@ -214,6 +214,7 @@ def showText(
disable_help_button(diag)
layout = QVBoxLayout(diag)
diag.setLayout(layout)
text: Union[QPlainTextEdit, QTextBrowser]
if plain_text_edit:
# used by the importer
text = QPlainTextEdit()
@ -222,10 +223,10 @@ def showText(
else:
text = QTextBrowser()
text.setOpenExternalLinks(True)
if type == "text":
text.setPlainText(txt)
else:
text.setHtml(txt)
if type == "text":
text.setPlainText(txt)
else:
text.setHtml(txt)
layout.addWidget(text)
box = QDialogButtonBox(QDialogButtonBox.Close)
layout.addWidget(box)
@ -263,7 +264,7 @@ def showText(
def askUser(
text: str,
parent: QDialog = None,
parent: QWidget = None,
help: HelpPageArgument = None,
defaultno: bool = False,
msgfunc: Optional[Callable] = None,
@ -296,7 +297,7 @@ class ButtonedDialog(QMessageBox):
self,
text: str,
buttons: List[str],
parent: Optional[QDialog] = None,
parent: Optional[QWidget] = None,
help: HelpPageArgument = None,
title: str = "Anki",
):
@ -329,7 +330,7 @@ class ButtonedDialog(QMessageBox):
def askUserDialog(
text: str,
buttons: List[str],
parent: Optional[QDialog] = None,
parent: Optional[QWidget] = None,
help: HelpPageArgument = None,
title: str = "Anki",
) -> ButtonedDialog:
@ -342,7 +343,7 @@ def askUserDialog(
class GetTextDialog(QDialog):
def __init__(
self,
parent: Optional[QDialog],
parent: Optional[QWidget],
question: str,
help: HelpPageArgument = None,
edit: Optional[QLineEdit] = None,
@ -389,7 +390,7 @@ class GetTextDialog(QDialog):
def getText(
prompt: str,
parent: Optional[QDialog] = None,
parent: Optional[QWidget] = None,
help: HelpPageArgument = None,
edit: Optional[QLineEdit] = None,
default: str = "",
@ -446,7 +447,7 @@ def chooseList(
def getTag(
parent: QDialog, deck: Collection, question: str, **kwargs: Any
parent: QWidget, deck: Collection, question: str, **kwargs: Any
) -> Tuple[str, int]:
from aqt.tagedit import TagEdit
@ -459,7 +460,8 @@ def getTag(
def disable_help_button(widget: QWidget) -> None:
"Disable the help button in the window titlebar."
flags = cast(Qt.WindowType, widget.windowFlags() & ~Qt.WindowContextHelpButtonHint)
flags_int = int(widget.windowFlags()) & ~Qt.WindowContextHelpButtonHint
flags = Qt.WindowFlags(flags_int) # type: ignore
widget.setWindowFlags(flags)
@ -468,7 +470,7 @@ def disable_help_button(widget: QWidget) -> None:
def getFile(
parent: QDialog,
parent: QWidget,
title: str,
# single file returned unless multi=True
cb: Optional[Callable[[Union[str, Sequence[str]]], None]],
@ -548,9 +550,9 @@ def getSaveFile(
return file
def saveGeom(widget: QDialog, key: str) -> None:
def saveGeom(widget: QWidget, key: str) -> None:
key += "Geom"
if isMac and widget.windowState() & Qt.WindowFullScreen:
if isMac and int(widget.windowState()) & Qt.WindowFullScreen:
geom = None
else:
geom = widget.saveGeometry()
@ -600,12 +602,12 @@ def ensureWidgetInScreenBoundaries(widget: QWidget) -> None:
widget.move(x, y)
def saveState(widget: QFileDialog, key: str) -> None:
def saveState(widget: Union[QFileDialog, QMainWindow], key: str) -> None:
key += "State"
aqt.mw.pm.profile[key] = widget.saveState()
def restoreState(widget: Union[aqt.AnkiQt, QFileDialog], key: str) -> None:
def restoreState(widget: Union[QFileDialog, QMainWindow], key: str) -> None:
key += "State"
if aqt.mw.pm.profile.get(key):
widget.restoreState(aqt.mw.pm.profile[key])
@ -633,12 +635,12 @@ def restoreHeader(widget: QHeaderView, key: str) -> None:
widget.restoreState(aqt.mw.pm.profile[key])
def save_is_checked(widget: QWidget, key: str) -> None:
def save_is_checked(widget: QCheckBox, key: str) -> None:
key += "IsChecked"
aqt.mw.pm.profile[key] = widget.isChecked()
def restore_is_checked(widget: QWidget, key: str) -> None:
def restore_is_checked(widget: QCheckBox, key: str) -> None:
key += "IsChecked"
if aqt.mw.pm.profile.get(key) is not None:
widget.setChecked(aqt.mw.pm.profile[key])
@ -719,8 +721,9 @@ def maybeHideClose(bbox: QDialogButtonBox) -> None:
def addCloseShortcut(widg: QDialog) -> None:
if not isMac:
return
widg._closeShortcut = QShortcut(QKeySequence("Ctrl+W"), widg)
qconnect(widg._closeShortcut.activated, widg.reject)
shortcut = QShortcut(QKeySequence("Ctrl+W"), widg)
qconnect(shortcut.activated, widg.reject)
setattr(widg, "_closeShortcut", shortcut)
def downArrow() -> str:
@ -732,7 +735,7 @@ def downArrow() -> str:
def top_level_widget(widget: QWidget) -> QWidget:
window = None
while widget := widget.parent():
while widget := widget.parentWidget():
window = widget
return window
@ -754,7 +757,7 @@ _tooltipLabel: Optional[QLabel] = None
def tooltip(
msg: str,
period: int = 3000,
parent: Optional[aqt.AnkiQt] = None,
parent: Optional[QWidget] = None,
x_offset: int = 0,
y_offset: int = 100,
) -> None:
@ -1015,3 +1018,24 @@ def ensure_editor_saved_on_trigger(func: Callable) -> Callable:
into functions connected to a `triggered` signal.
"""
return pyqtSlot()(ensure_editor_saved(func)) # type: ignore
class KeyboardModifiersPressed:
"Util for type-safe checks of currently-pressed modifier keys."
def __init__(self) -> None:
from aqt import mw
self._modifiers = int(mw.app.keyboardModifiers())
@property
def shift(self) -> bool:
return bool(self._modifiers & Qt.ShiftModifier)
@property
def control(self) -> bool:
return bool(self._modifiers & Qt.ControlModifier)
@property
def alt(self) -> bool:
return bool(self._modifiers & Qt.AltModifier)

View file

@ -1,5 +1,6 @@
# Copyright: Ankitects Pty Ltd and contributors
# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
import dataclasses
import json
import re
@ -31,12 +32,15 @@ class AnkiWebPage(QWebEnginePage):
def _setupBridge(self) -> None:
class Bridge(QObject):
def __init__(self, bridge_handler: Callable[[str], Any]) -> None:
super().__init__()
self.onCmd = bridge_handler
@pyqtSlot(str, result=str) # type: ignore
def cmd(self, str: str) -> Any:
return json.dumps(self.onCmd(str))
self._bridge = Bridge()
self._bridge.onCmd = self._onCmd
self._bridge = Bridge(self._onCmd)
self._channel = QWebChannel(self)
self._channel.registerObject("py", self._bridge)
@ -46,7 +50,7 @@ class AnkiWebPage(QWebEnginePage):
jsfile = QFile(qwebchannel)
if not jsfile.open(QIODevice.ReadOnly):
print(f"Error opening '{qwebchannel}': {jsfile.error()}", file=sys.stderr)
jstext = bytes(jsfile.readAll()).decode("utf-8")
jstext = bytes(cast(bytes, jsfile.readAll())).decode("utf-8")
jsfile.close()
script = QWebEngineScript()
@ -131,7 +135,7 @@ class AnkiWebPage(QWebEnginePage):
openLink(url)
return False
def _onCmd(self, str: str) -> None:
def _onCmd(self, str: str) -> Any:
return self._onBridgeCmd(str)
def javaScriptAlert(self, url: QUrl, text: str) -> None:
@ -252,7 +256,7 @@ class AnkiWebView(QWebEngineView):
# disable pinch to zoom gesture
if isinstance(evt, QNativeGestureEvent):
return True
elif evt.type() == QEvent.MouseButtonRelease:
elif isinstance(evt, QMouseEvent) and evt.type() == QEvent.MouseButtonRelease:
if evt.button() == Qt.MidButton and isLin:
self.onMiddleClickPaste()
return True
@ -273,7 +277,9 @@ class AnkiWebView(QWebEngineView):
w.close()
else:
# in the main window, removes focus from type in area
self.parent().setFocus()
parent = self.parent()
assert isinstance(parent, QWidget)
parent.setFocus()
break
w = w.parent()
@ -315,15 +321,16 @@ class AnkiWebView(QWebEngineView):
self.set_open_links_externally(True)
def _setHtml(self, html: str) -> None:
app = QApplication.instance()
oldFocus = app.focusWidget()
from aqt import mw
oldFocus = mw.app.focusWidget()
self._domDone = False
self._page.setHtml(html)
# work around webengine stealing focus on setHtml()
if oldFocus:
oldFocus.setFocus()
def load(self, url: QUrl) -> None:
def load_url(self, url: QUrl) -> None:
# allow queuing actions when loading url directly
self._domDone = False
super().load(url)
@ -641,5 +648,12 @@ document.head.appendChild(style);
else:
extra = ""
self.hide_while_preserving_layout()
self.load(QUrl(f"{mw.serverURL()}_anki/pages/{name}.html{extra}"))
self.load_url(QUrl(f"{mw.serverURL()}_anki/pages/{name}.html{extra}"))
self.inject_dynamic_style_and_show()
def force_load_hack(self) -> None:
"""Force process to initialize.
Must be done on Windows prior to changing current working directory."""
self.requiresCol = False
self._domReady = False
self._page.setContent(bytes("", "ascii"))