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 sys
import tempfile import tempfile
import traceback 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 import anki.lang
from anki import version as _version from anki import version as _version
@ -299,7 +299,7 @@ class AnkiApp(QApplication):
if not sock.waitForReadyRead(self.TMOUT): if not sock.waitForReadyRead(self.TMOUT):
sys.stderr.write(sock.errorString()) sys.stderr.write(sock.errorString())
return return
path = bytes(sock.readAll()).decode("utf8") path = bytes(cast(bytes, sock.readAll())).decode("utf8")
self.appMsg.emit(path) # type: ignore self.appMsg.emit(path) # type: ignore
sock.disconnectFromServer() sock.disconnectFromServer()

View file

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

View file

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

View file

@ -795,7 +795,7 @@ class CardLayout(QDialog):
showWarning(str(e)) showWarning(str(e))
return return
self.mw.reset() 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() self.cleanup()
gui_hooks.sidebar_should_refresh_notetypes() gui_hooks.sidebar_should_refresh_notetypes()
return QDialog.accept(self) return QDialog.accept(self)

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -31,7 +31,7 @@ class Models(QDialog):
def __init__( def __init__(
self, self,
mw: AnkiQt, mw: AnkiQt,
parent: Optional[QDialog] = None, parent: Optional[QWidget] = None,
fromMain: bool = False, fromMain: bool = False,
selected_notetype_id: Optional[int] = None, 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.lang import TR
from anki.notes import Note from anki.notes import Note
from aqt import AnkiQt from aqt import AnkiQt, QWidget
from aqt.main import PerformOpOptionalSuccessCallback from aqt.main import PerformOpOptionalSuccessCallback
from aqt.qt import QDialog
from aqt.utils import show_invalid_search_error, showInfo, tr from aqt.utils import show_invalid_search_error, showInfo, tr
@ -52,7 +51,7 @@ def remove_tags(
def find_and_replace( def find_and_replace(
*, *,
mw: AnkiQt, mw: AnkiQt,
parent: QDialog, parent: QWidget,
note_ids: Sequence[int], note_ids: Sequence[int],
search: str, search: str,
replacement: str, replacement: str,

View file

@ -1,11 +1,14 @@
# 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
# mypy: check-untyped-defs
from __future__ import annotations
import json import json
import re import re
import time import time
from typing import Any, Callable, Optional, Tuple, Union from typing import Any, Callable, Optional, Tuple, Union
import aqt.browser
from anki.cards import Card from anki.cards import Card
from anki.collection import Config from anki.collection import Config
from aqt import AnkiQt, gui_hooks from aqt import AnkiQt, gui_hooks
@ -300,6 +303,12 @@ class MultiCardPreviewer(Previewer):
class BrowserPreviewer(MultiCardPreviewer): class BrowserPreviewer(MultiCardPreviewer):
_last_card_id = 0 _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]: def card(self) -> Optional[Card]:
if self._parent.singleCard: if self._parent.singleCard:

View file

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

View file

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

View file

@ -23,6 +23,7 @@ from aqt.qt import *
from aqt.theme import ColoredIcon, theme_manager from aqt.theme import ColoredIcon, theme_manager
from aqt.utils import ( from aqt.utils import (
TR, TR,
KeyboardModifiersPressed,
askUser, askUser,
getOnlyText, getOnlyText,
show_invalid_search_error, show_invalid_search_error,
@ -254,9 +255,7 @@ class SidebarModel(QAbstractItemModel):
return QVariant(item.tooltip) return QVariant(item.tooltip)
return QVariant(theme_manager.icon_from_resources(item.icon)) return QVariant(theme_manager.icon_from_resources(item.icon))
def setData( def setData(self, index: QModelIndex, text: str, _role: int = Qt.EditRole) -> bool:
self, index: QModelIndex, text: QVariant, _role: int = Qt.EditRole
) -> bool:
return self.sidebar._on_rename(index.internalPointer(), text) return self.sidebar._on_rename(index.internalPointer(), text)
def supportedDropActions(self) -> Qt.DropActions: def supportedDropActions(self) -> Qt.DropActions:
@ -354,6 +353,10 @@ def _want_right_border() -> bool:
return not isMac or theme_manager.night_mode 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): class SidebarTreeView(QTreeView):
def __init__(self, browser: aqt.browser.Browser) -> None: def __init__(self, browser: aqt.browser.Browser) -> None:
super().__init__() super().__init__()
@ -390,6 +393,10 @@ class SidebarTreeView(QTreeView):
self.setStyleSheet("QTreeView { %s }" % ";".join(styles)) 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 @property
def tool(self) -> SidebarTool: def tool(self) -> SidebarTool:
return self._tool return self._tool
@ -410,7 +417,7 @@ class SidebarTreeView(QTreeView):
self.setExpandsOnDoubleClick(double_click_expands) self.setExpandsOnDoubleClick(double_click_expands)
def model(self) -> SidebarModel: def model(self) -> SidebarModel:
return super().model() return cast(SidebarModel, super().model())
# Refreshing # Refreshing
########################### ###########################
@ -512,22 +519,22 @@ class SidebarTreeView(QTreeView):
joiner: SearchJoiner = "AND", joiner: SearchJoiner = "AND",
) -> None: ) -> None:
"""Modify the current search string based on modifier keys, then refresh.""" """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()) previous = SearchNode(parsable_text=self.browser.current_search())
current = self.mw.col.group_searches(*terms, joiner=joiner) current = self.mw.col.group_searches(*terms, joiner=joiner)
# if Alt pressed, invert # if Alt pressed, invert
if mods & Qt.AltModifier: if mods.alt:
current = SearchNode(negated=current) current = SearchNode(negated=current)
try: 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. # If Ctrl+Shift, replace searches nodes of the same type.
search = self.col.replace_in_search_node(previous, current) search = self.col.replace_in_search_node(previous, current)
elif mods & Qt.ControlModifier: elif mods.control:
# If Ctrl, AND with previous # If Ctrl, AND with previous
search = self.col.join_searches(previous, current, "AND") search = self.col.join_searches(previous, current, "AND")
elif mods & Qt.ShiftModifier: elif mods.shift:
# If Shift, OR with previous # If Shift, OR with previous
search = self.col.join_searches(previous, current, "OR") search = self.col.join_searches(previous, current, "OR")
else: else:

View file

@ -15,7 +15,7 @@ import wave
from abc import ABC, abstractmethod from abc import ABC, abstractmethod
from concurrent.futures import Future from concurrent.futures import Future
from operator import itemgetter 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 import aqt
from anki import hooks from anki import hooks
@ -568,7 +568,7 @@ class QtAudioInputRecorder(Recorder):
super().start(on_done) super().start(on_done)
def _on_read_ready(self) -> None: 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 stop(self, on_done: Callable[[str], None]) -> None:
def on_stop_timer() -> None: def on_stop_timer() -> None:

View file

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

View file

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

View file

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

View file

@ -86,12 +86,12 @@ class ThemeManager:
else: else:
# specified colours # specified colours
icon = QIcon(path.path) icon = QIcon(path.path)
img = icon.pixmap(16) pixmap = icon.pixmap(16)
painter = QPainter(img) painter = QPainter(pixmap)
painter.setCompositionMode(QPainter.CompositionMode_SourceIn) 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() painter.end()
icon = QIcon(img) icon = QIcon(pixmap)
return icon return icon
return cache.setdefault(path, icon) return cache.setdefault(path, icon)

View file

@ -111,7 +111,7 @@ def openHelp(section: HelpPageArgument) -> None:
openLink(link) openLink(link)
def openLink(link: str) -> None: def openLink(link: Union[str, QUrl]) -> None:
tooltip(tr(TR.QT_MISC_LOADING), period=1000) tooltip(tr(TR.QT_MISC_LOADING), period=1000)
with noBundledLibs(): with noBundledLibs():
QDesktopServices.openUrl(QUrl(link)) QDesktopServices.openUrl(QUrl(link))
@ -119,7 +119,7 @@ def openLink(link: str) -> None:
def showWarning( def showWarning(
text: str, text: str,
parent: Optional[QDialog] = None, parent: Optional[QWidget] = None,
help: HelpPageArgument = "", help: HelpPageArgument = "",
title: str = "Anki", title: str = "Anki",
textFormat: Optional[TextFormat] = None, textFormat: Optional[TextFormat] = None,
@ -139,7 +139,7 @@ def showCritical(
return showInfo(text, parent, help, "critical", title=title, textFormat=textFormat) 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." "Render search errors in markdown, then display a warning."
text = str(err) text = str(err)
if isinstance(err, InvalidInput): if isinstance(err, InvalidInput):
@ -149,7 +149,7 @@ def show_invalid_search_error(err: Exception, parent: Optional[QDialog] = None)
def showInfo( def showInfo(
text: str, text: str,
parent: Union[Literal[False], QDialog] = False, parent: Optional[QWidget] = None,
help: HelpPageArgument = "", help: HelpPageArgument = "",
type: str = "info", type: str = "info",
title: str = "Anki", title: str = "Anki",
@ -158,7 +158,7 @@ def showInfo(
) -> int: ) -> int:
"Show a small info window with an OK button." "Show a small info window with an OK button."
parent_widget: QWidget parent_widget: QWidget
if parent is False: if parent is None:
parent_widget = aqt.mw.app.activeWindow() or aqt.mw parent_widget = aqt.mw.app.activeWindow() or aqt.mw
else: else:
parent_widget = parent parent_widget = parent
@ -214,6 +214,7 @@ def showText(
disable_help_button(diag) disable_help_button(diag)
layout = QVBoxLayout(diag) layout = QVBoxLayout(diag)
diag.setLayout(layout) diag.setLayout(layout)
text: Union[QPlainTextEdit, QTextBrowser]
if plain_text_edit: if plain_text_edit:
# used by the importer # used by the importer
text = QPlainTextEdit() text = QPlainTextEdit()
@ -222,10 +223,10 @@ def showText(
else: else:
text = QTextBrowser() text = QTextBrowser()
text.setOpenExternalLinks(True) text.setOpenExternalLinks(True)
if type == "text": if type == "text":
text.setPlainText(txt) text.setPlainText(txt)
else: else:
text.setHtml(txt) text.setHtml(txt)
layout.addWidget(text) layout.addWidget(text)
box = QDialogButtonBox(QDialogButtonBox.Close) box = QDialogButtonBox(QDialogButtonBox.Close)
layout.addWidget(box) layout.addWidget(box)
@ -263,7 +264,7 @@ def showText(
def askUser( def askUser(
text: str, text: str,
parent: QDialog = None, parent: QWidget = None,
help: HelpPageArgument = None, help: HelpPageArgument = None,
defaultno: bool = False, defaultno: bool = False,
msgfunc: Optional[Callable] = None, msgfunc: Optional[Callable] = None,
@ -296,7 +297,7 @@ class ButtonedDialog(QMessageBox):
self, self,
text: str, text: str,
buttons: List[str], buttons: List[str],
parent: Optional[QDialog] = None, parent: Optional[QWidget] = None,
help: HelpPageArgument = None, help: HelpPageArgument = None,
title: str = "Anki", title: str = "Anki",
): ):
@ -329,7 +330,7 @@ class ButtonedDialog(QMessageBox):
def askUserDialog( def askUserDialog(
text: str, text: str,
buttons: List[str], buttons: List[str],
parent: Optional[QDialog] = None, parent: Optional[QWidget] = None,
help: HelpPageArgument = None, help: HelpPageArgument = None,
title: str = "Anki", title: str = "Anki",
) -> ButtonedDialog: ) -> ButtonedDialog:
@ -342,7 +343,7 @@ def askUserDialog(
class GetTextDialog(QDialog): class GetTextDialog(QDialog):
def __init__( def __init__(
self, self,
parent: Optional[QDialog], parent: Optional[QWidget],
question: str, question: str,
help: HelpPageArgument = None, help: HelpPageArgument = None,
edit: Optional[QLineEdit] = None, edit: Optional[QLineEdit] = None,
@ -389,7 +390,7 @@ class GetTextDialog(QDialog):
def getText( def getText(
prompt: str, prompt: str,
parent: Optional[QDialog] = None, parent: Optional[QWidget] = None,
help: HelpPageArgument = None, help: HelpPageArgument = None,
edit: Optional[QLineEdit] = None, edit: Optional[QLineEdit] = None,
default: str = "", default: str = "",
@ -446,7 +447,7 @@ def chooseList(
def getTag( def getTag(
parent: QDialog, deck: Collection, question: str, **kwargs: Any parent: QWidget, deck: Collection, question: str, **kwargs: Any
) -> Tuple[str, int]: ) -> Tuple[str, int]:
from aqt.tagedit import TagEdit from aqt.tagedit import TagEdit
@ -459,7 +460,8 @@ def getTag(
def disable_help_button(widget: QWidget) -> None: def disable_help_button(widget: QWidget) -> None:
"Disable the help button in the window titlebar." "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) widget.setWindowFlags(flags)
@ -468,7 +470,7 @@ def disable_help_button(widget: QWidget) -> None:
def getFile( def getFile(
parent: QDialog, parent: QWidget,
title: str, title: str,
# single file returned unless multi=True # single file returned unless multi=True
cb: Optional[Callable[[Union[str, Sequence[str]]], None]], cb: Optional[Callable[[Union[str, Sequence[str]]], None]],
@ -548,9 +550,9 @@ def getSaveFile(
return file return file
def saveGeom(widget: QDialog, key: str) -> None: def saveGeom(widget: QWidget, key: str) -> None:
key += "Geom" key += "Geom"
if isMac and widget.windowState() & Qt.WindowFullScreen: if isMac and int(widget.windowState()) & Qt.WindowFullScreen:
geom = None geom = None
else: else:
geom = widget.saveGeometry() geom = widget.saveGeometry()
@ -600,12 +602,12 @@ def ensureWidgetInScreenBoundaries(widget: QWidget) -> None:
widget.move(x, y) widget.move(x, y)
def saveState(widget: QFileDialog, key: str) -> None: def saveState(widget: Union[QFileDialog, QMainWindow], key: str) -> None:
key += "State" key += "State"
aqt.mw.pm.profile[key] = widget.saveState() 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" key += "State"
if aqt.mw.pm.profile.get(key): if aqt.mw.pm.profile.get(key):
widget.restoreState(aqt.mw.pm.profile[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]) 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" key += "IsChecked"
aqt.mw.pm.profile[key] = widget.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" key += "IsChecked"
if aqt.mw.pm.profile.get(key) is not None: if aqt.mw.pm.profile.get(key) is not None:
widget.setChecked(aqt.mw.pm.profile[key]) widget.setChecked(aqt.mw.pm.profile[key])
@ -719,8 +721,9 @@ def maybeHideClose(bbox: QDialogButtonBox) -> None:
def addCloseShortcut(widg: QDialog) -> None: def addCloseShortcut(widg: QDialog) -> None:
if not isMac: if not isMac:
return return
widg._closeShortcut = QShortcut(QKeySequence("Ctrl+W"), widg) shortcut = QShortcut(QKeySequence("Ctrl+W"), widg)
qconnect(widg._closeShortcut.activated, widg.reject) qconnect(shortcut.activated, widg.reject)
setattr(widg, "_closeShortcut", shortcut)
def downArrow() -> str: def downArrow() -> str:
@ -732,7 +735,7 @@ def downArrow() -> str:
def top_level_widget(widget: QWidget) -> QWidget: def top_level_widget(widget: QWidget) -> QWidget:
window = None window = None
while widget := widget.parent(): while widget := widget.parentWidget():
window = widget window = widget
return window return window
@ -754,7 +757,7 @@ _tooltipLabel: Optional[QLabel] = None
def tooltip( def tooltip(
msg: str, msg: str,
period: int = 3000, period: int = 3000,
parent: Optional[aqt.AnkiQt] = None, parent: Optional[QWidget] = None,
x_offset: int = 0, x_offset: int = 0,
y_offset: int = 100, y_offset: int = 100,
) -> None: ) -> None:
@ -1015,3 +1018,24 @@ def ensure_editor_saved_on_trigger(func: Callable) -> Callable:
into functions connected to a `triggered` signal. into functions connected to a `triggered` signal.
""" """
return pyqtSlot()(ensure_editor_saved(func)) # type: ignore 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 # 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
import dataclasses import dataclasses
import json import json
import re import re
@ -31,12 +32,15 @@ class AnkiWebPage(QWebEnginePage):
def _setupBridge(self) -> None: def _setupBridge(self) -> None:
class Bridge(QObject): class Bridge(QObject):
def __init__(self, bridge_handler: Callable[[str], Any]) -> None:
super().__init__()
self.onCmd = bridge_handler
@pyqtSlot(str, result=str) # type: ignore @pyqtSlot(str, result=str) # type: ignore
def cmd(self, str: str) -> Any: def cmd(self, str: str) -> Any:
return json.dumps(self.onCmd(str)) return json.dumps(self.onCmd(str))
self._bridge = Bridge() self._bridge = Bridge(self._onCmd)
self._bridge.onCmd = self._onCmd
self._channel = QWebChannel(self) self._channel = QWebChannel(self)
self._channel.registerObject("py", self._bridge) self._channel.registerObject("py", self._bridge)
@ -46,7 +50,7 @@ class AnkiWebPage(QWebEnginePage):
jsfile = QFile(qwebchannel) jsfile = QFile(qwebchannel)
if not jsfile.open(QIODevice.ReadOnly): if not jsfile.open(QIODevice.ReadOnly):
print(f"Error opening '{qwebchannel}': {jsfile.error()}", file=sys.stderr) 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() jsfile.close()
script = QWebEngineScript() script = QWebEngineScript()
@ -131,7 +135,7 @@ class AnkiWebPage(QWebEnginePage):
openLink(url) openLink(url)
return False return False
def _onCmd(self, str: str) -> None: def _onCmd(self, str: str) -> Any:
return self._onBridgeCmd(str) return self._onBridgeCmd(str)
def javaScriptAlert(self, url: QUrl, text: str) -> None: def javaScriptAlert(self, url: QUrl, text: str) -> None:
@ -252,7 +256,7 @@ class AnkiWebView(QWebEngineView):
# disable pinch to zoom gesture # disable pinch to zoom gesture
if isinstance(evt, QNativeGestureEvent): if isinstance(evt, QNativeGestureEvent):
return True return True
elif evt.type() == QEvent.MouseButtonRelease: elif isinstance(evt, QMouseEvent) and evt.type() == QEvent.MouseButtonRelease:
if evt.button() == Qt.MidButton and isLin: if evt.button() == Qt.MidButton and isLin:
self.onMiddleClickPaste() self.onMiddleClickPaste()
return True return True
@ -273,7 +277,9 @@ class AnkiWebView(QWebEngineView):
w.close() w.close()
else: else:
# in the main window, removes focus from type in area # in the main window, removes focus from type in area
self.parent().setFocus() parent = self.parent()
assert isinstance(parent, QWidget)
parent.setFocus()
break break
w = w.parent() w = w.parent()
@ -315,15 +321,16 @@ class AnkiWebView(QWebEngineView):
self.set_open_links_externally(True) self.set_open_links_externally(True)
def _setHtml(self, html: str) -> None: def _setHtml(self, html: str) -> None:
app = QApplication.instance() from aqt import mw
oldFocus = app.focusWidget()
oldFocus = mw.app.focusWidget()
self._domDone = False self._domDone = False
self._page.setHtml(html) self._page.setHtml(html)
# work around webengine stealing focus on setHtml() # work around webengine stealing focus on setHtml()
if oldFocus: if oldFocus:
oldFocus.setFocus() oldFocus.setFocus()
def load(self, url: QUrl) -> None: def load_url(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)
@ -641,5 +648,12 @@ document.head.appendChild(style);
else: else:
extra = "" extra = ""
self.hide_while_preserving_layout() 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() 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"))