mirror of
https://github.com/ankitects/anki.git
synced 2025-09-18 22:12:21 -04:00
fix a bunch of qt typing issues uncovered by the following commit
This commit is contained in:
parent
75a0f165c6
commit
0c59c8b591
23 changed files with 198 additions and 139 deletions
|
@ -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()
|
||||||
|
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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(
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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,
|
||||||
):
|
):
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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):
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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"))
|
||||||
|
|
Loading…
Reference in a new issue