diff --git a/qt/aqt/browser/card_info.py b/qt/aqt/browser/card_info.py index 51ef5053c..f2b0de09c 100644 --- a/qt/aqt/browser/card_info.py +++ b/qt/aqt/browser/card_info.py @@ -19,7 +19,7 @@ from aqt.utils import ( setWindowIcon, tr, ) -from aqt.webview import AnkiWebView +from aqt.webview import AnkiWebView, AnkiWebViewKind class CardInfoDialog(QDialog): @@ -52,7 +52,7 @@ class CardInfoDialog(QDialog): addCloseShortcut(self) setWindowIcon(self) - self.web = AnkiWebView(title=self.TITLE) + self.web = AnkiWebView(kind=AnkiWebViewKind.BROWSER_CARD_INFO) self.web.setVisible(False) self.web.load_ts_page("card-info") layout = QVBoxLayout() diff --git a/qt/aqt/browser/find_duplicates.py b/qt/aqt/browser/find_duplicates.py index f7924ccc2..0211f8181 100644 --- a/qt/aqt/browser/find_duplicates.py +++ b/qt/aqt/browser/find_duplicates.py @@ -13,6 +13,7 @@ import aqt.forms from anki.collection import SearchNode from anki.notes import NoteId from aqt.qt import * +from aqt.webview import AnkiWebViewKind from ..operations import QueryOp from ..operations.tag import add_tags_to_notes @@ -50,7 +51,7 @@ class FindDuplicatesDialog(QDialog): self._dupes: list[tuple[str, list[NoteId]]] = [] # links - form.webView.set_title("find duplicates") + form.webView.set_kind(AnkiWebViewKind.FIND_DUPLICATES) form.webView.set_bridge_command(self._on_duplicate_clicked, context=self) form.webView.stdHtml("", context=self) diff --git a/qt/aqt/browser/previewer.py b/qt/aqt/browser/previewer.py index 5ed549286..db09ff121 100644 --- a/qt/aqt/browser/previewer.py +++ b/qt/aqt/browser/previewer.py @@ -29,7 +29,7 @@ from aqt.reviewer import replay_audio from aqt.sound import av_player, play_clicked_audio from aqt.theme import theme_manager from aqt.utils import disable_help_button, restoreGeom, saveGeom, setWindowIcon, tr -from aqt.webview import AnkiWebView +from aqt.webview import AnkiWebView, AnkiWebViewKind LastStateAndMod = tuple[str, int, int] @@ -78,7 +78,7 @@ class Previewer(QDialog): self.silentlyClose = True self.vbox = QVBoxLayout() self.vbox.setContentsMargins(0, 0, 0, 0) - self._web = AnkiWebView(title="previewer") + self._web = AnkiWebView(kind=AnkiWebViewKind.PREVIEWER) self.vbox.addWidget(self._web) self.bbox = QDialogButtonBox() self.bbox.setLayoutDirection(Qt.LayoutDirection.LeftToRight) diff --git a/qt/aqt/changenotetype.py b/qt/aqt/changenotetype.py index b79f38c43..848222f41 100644 --- a/qt/aqt/changenotetype.py +++ b/qt/aqt/changenotetype.py @@ -23,7 +23,7 @@ from aqt.utils import ( tooltip, tr, ) -from aqt.webview import AnkiWebView +from aqt.webview import AnkiWebView, AnkiWebViewKind class ChangeNotetypeDialog(QDialog): @@ -52,7 +52,7 @@ class ChangeNotetypeDialog(QDialog): restoreGeom(self, self.TITLE, default_size=(800, 800)) addCloseShortcut(self) - self.web = AnkiWebView(title=self.TITLE) + self.web = AnkiWebView(kind=AnkiWebViewKind.CHANGE_NOTETYPE) self.web.setVisible(False) self.web.load_ts_page("change-notetype") layout = QVBoxLayout() diff --git a/qt/aqt/clayout.py b/qt/aqt/clayout.py index 3e87b0379..c2ea9a399 100644 --- a/qt/aqt/clayout.py +++ b/qt/aqt/clayout.py @@ -35,7 +35,7 @@ from aqt.utils import ( tooltip, tr, ) -from aqt.webview import AnkiWebView +from aqt.webview import AnkiWebView, AnkiWebViewKind class CardLayout(QDialog): @@ -334,7 +334,7 @@ class CardLayout(QDialog): def setup_preview(self) -> None: pform = self.pform - self.preview_web = AnkiWebView(title="card layout") + self.preview_web = AnkiWebView(kind=AnkiWebViewKind.CARD_LAYOUT) pform.verticalLayout.addWidget(self.preview_web) pform.verticalLayout.setStretch(1, 99) pform.preview_front.isChecked() diff --git a/qt/aqt/deckoptions.py b/qt/aqt/deckoptions.py index 431622adb..2e8d2d4a4 100644 --- a/qt/aqt/deckoptions.py +++ b/qt/aqt/deckoptions.py @@ -19,7 +19,7 @@ from aqt.utils import ( saveGeom, tr, ) -from aqt.webview import AnkiWebView +from aqt.webview import AnkiWebView, AnkiWebViewKind class DeckOptionsDialog(QDialog): @@ -42,7 +42,7 @@ class DeckOptionsDialog(QDialog): restoreGeom(self, self.TITLE, default_size=(800, 800)) addCloseShortcut(self) - self.web = AnkiWebView(title=self.TITLE) + self.web = AnkiWebView(kind=AnkiWebViewKind.DECK_OPTIONS) self.web.load_ts_page("deck-options") layout = QVBoxLayout() layout.setContentsMargins(0, 0, 0, 0) diff --git a/qt/aqt/editor.py b/qt/aqt/editor.py index 54758c40e..d73c04962 100644 --- a/qt/aqt/editor.py +++ b/qt/aqt/editor.py @@ -55,7 +55,7 @@ from aqt.utils import ( tooltip, tr, ) -from aqt.webview import AnkiWebView +from aqt.webview import AnkiWebView, AnkiWebViewKind pics = ("jpg", "jpeg", "png", "tif", "tiff", "gif", "svg", "webp", "ico") audio = ( @@ -1211,7 +1211,7 @@ require("anki/ui").loaded.then(() => require("anki/NoteEditor").instances[0].too class EditorWebView(AnkiWebView): def __init__(self, parent: QWidget, editor: Editor) -> None: - AnkiWebView.__init__(self, title="editor") + AnkiWebView.__init__(self, kind=AnkiWebViewKind.EDITOR) self.editor = editor self.setAcceptDrops(True) self._markInternal = False diff --git a/qt/aqt/emptycards.py b/qt/aqt/emptycards.py index 12026cb14..eeab4a63b 100644 --- a/qt/aqt/emptycards.py +++ b/qt/aqt/emptycards.py @@ -15,6 +15,7 @@ from anki.collection import EmptyCardsReport from aqt import gui_hooks from aqt.qt import QDialog, QDialogButtonBox, qconnect from aqt.utils import disable_help_button, restoreGeom, saveGeom, tooltip, tr +from aqt.webview import AnkiWebViewKind def show_empty_cards(mw: aqt.main.AnkiQt) -> None: @@ -46,7 +47,7 @@ class EmptyCardsDialog(QDialog): self.setWindowTitle(tr.empty_cards_window_title()) disable_help_button(self) self.form.keep_notes.setText(tr.empty_cards_preserve_notes_checkbox()) - self.form.webview.set_title("empty cards") + self.form.webview.set_kind(AnkiWebViewKind.EMPTY_CARDS) self.form.webview.set_bridge_command(self._on_note_link_clicked, self) gui_hooks.empty_cards_will_show(self) diff --git a/qt/aqt/fields.py b/qt/aqt/fields.py index 5de5f4643..62e7ad979 100644 --- a/qt/aqt/fields.py +++ b/qt/aqt/fields.py @@ -27,6 +27,7 @@ from aqt.utils import ( tooltip, tr, ) +from aqt.webview import AnkiWebViewKind class FieldDialog(QDialog): @@ -55,7 +56,7 @@ class FieldDialog(QDialog): form.setupUi(self) self.webview = form.webview - self.webview.set_title("fields") + self.webview.set_kind(AnkiWebViewKind.FIELDS) self.show() self.refresh() diff --git a/qt/aqt/import_export/import_csv_dialog.py b/qt/aqt/import_export/import_csv_dialog.py index b09cf691e..cd877d4af 100644 --- a/qt/aqt/import_export/import_csv_dialog.py +++ b/qt/aqt/import_export/import_csv_dialog.py @@ -10,7 +10,7 @@ import aqt.operations from anki.collection import ImportCsvRequest from aqt.qt import * from aqt.utils import addCloseShortcut, disable_help_button, restoreGeom, saveGeom, tr -from aqt.webview import AnkiWebView +from aqt.webview import AnkiWebView, AnkiWebViewKind class ImportCsvDialog(QDialog): @@ -38,7 +38,7 @@ class ImportCsvDialog(QDialog): restoreGeom(self, self.TITLE, default_size=(800, 800)) addCloseShortcut(self) - self.web = AnkiWebView(title=self.TITLE) + self.web = AnkiWebView(kind=AnkiWebViewKind.IMPORT_CSV) self.web.setVisible(False) self.web.load_ts_page("import-csv") layout = QVBoxLayout() diff --git a/qt/aqt/main.py b/qt/aqt/main.py index 77668a5bd..2f40772ba 100644 --- a/qt/aqt/main.py +++ b/qt/aqt/main.py @@ -91,7 +91,7 @@ from aqt.utils import ( tooltip, tr, ) -from aqt.webview import AnkiWebView +from aqt.webview import AnkiWebView, AnkiWebViewKind install_pylib_legacy() @@ -105,7 +105,7 @@ T = TypeVar("T") class MainWebView(AnkiWebView): def __init__(self, mw: AnkiQt) -> None: - AnkiWebView.__init__(self, title="main webview") + AnkiWebView.__init__(self, kind=AnkiWebViewKind.MAIN) self.mw = mw self.setFocusPolicy(Qt.FocusPolicy.WheelFocus) self.setMinimumWidth(400) @@ -879,12 +879,12 @@ title="{}" {}>{}""".format( self.form = aqt.forms.main.Ui_MainWindow() self.form.setupUi(self) # toolbar - tweb = self.toolbarWeb = TopWebView(self, title="top toolbar") + tweb = self.toolbarWeb = TopWebView(self) self.toolbar = Toolbar(self, tweb) # main area self.web = MainWebView(self) # bottom area - sweb = self.bottomWeb = BottomWebView(self, title="bottom toolbar") + sweb = self.bottomWeb = BottomWebView(self) sweb.setFocusPolicy(Qt.FocusPolicy.WheelFocus) sweb.disable_zoom() # add in a layout diff --git a/qt/aqt/stats.py b/qt/aqt/stats.py index 6ea980469..b1d0055ab 100644 --- a/qt/aqt/stats.py +++ b/qt/aqt/stats.py @@ -21,6 +21,7 @@ from aqt.utils import ( tooltip, tr, ) +from aqt.webview import AnkiWebViewKind class NewDeckStats(QDialog): @@ -50,6 +51,7 @@ class NewDeckStats(QDialog): maybeHideClose(self.form.buttonBox) addCloseShortcut(self) gui_hooks.stats_dialog_will_show(self) + self.form.web.set_kind(AnkiWebViewKind.DECK_STATS) self.form.web.hide_while_preserving_layout() self.show() self.refresh() @@ -198,7 +200,7 @@ class DeckStats(QDialog): stats = self.mw.col.stats() stats.wholeCollection = self.wholeCollection self.report = stats.report(type=self.period) - self.form.web.set_title("deck stats") + self.form.web.set_kind(AnkiWebViewKind.LEGACY_DECK_STATS) self.form.web.stdHtml( f"{self.report}", js=["js/vendor/jquery.min.js", "js/vendor/plot.js"], diff --git a/qt/aqt/toolbar.py b/qt/aqt/toolbar.py index 645b6240f..9fd44abdf 100644 --- a/qt/aqt/toolbar.py +++ b/qt/aqt/toolbar.py @@ -13,7 +13,7 @@ from aqt.qt import * from aqt.sync import get_sync_status from aqt.theme import theme_manager from aqt.utils import tr -from aqt.webview import AnkiWebView +from aqt.webview import AnkiWebView, AnkiWebViewKind class HideMode(enum.IntEnum): @@ -36,8 +36,8 @@ class BottomToolbar: class ToolbarWebView(AnkiWebView): hide_condition: Callable[..., bool] - def __init__(self, mw: aqt.AnkiQt, title: str) -> None: - AnkiWebView.__init__(self, mw, title=title) + def __init__(self, mw: aqt.AnkiQt, kind: AnkiWebViewKind | None = None) -> None: + AnkiWebView.__init__(self, mw, kind=kind) self.mw = mw self.setFocusPolicy(Qt.FocusPolicy.WheelFocus) self.disable_zoom() @@ -58,8 +58,8 @@ class ToolbarWebView(AnkiWebView): class TopWebView(ToolbarWebView): - def __init__(self, mw: aqt.AnkiQt, title: str) -> None: - super().__init__(mw, title=title) + def __init__(self, mw: aqt.AnkiQt) -> None: + super().__init__(mw, kind=AnkiWebViewKind.TOP_TOOLBAR) self.web_height = 0 qconnect(self.hide_timer.timeout, self.hide_if_allowed) @@ -179,8 +179,8 @@ class TopWebView(ToolbarWebView): class BottomWebView(ToolbarWebView): - def __init__(self, mw: aqt.AnkiQt, title: str) -> None: - super().__init__(mw, title=title) + def __init__(self, mw: aqt.AnkiQt) -> None: + super().__init__(mw, kind=AnkiWebViewKind.BOTTOM_TOOLBAR) qconnect(self.hide_timer.timeout, self.hide_if_allowed) def eventFilter(self, obj, evt): diff --git a/qt/aqt/webview.py b/qt/aqt/webview.py index 694849875..b6616ed9c 100644 --- a/qt/aqt/webview.py +++ b/qt/aqt/webview.py @@ -1,10 +1,13 @@ # Copyright: Ankitects Pty Ltd and contributors # License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html +from __future__ import annotations + import dataclasses import json import re import sys +from enum import Enum from typing import Any, Callable, Optional, Sequence, cast import anki @@ -222,16 +225,46 @@ class WebContent: ########################################################################## +class AnkiWebViewKind(Enum): + """Enum registry of all web views managed by Anki + + The value of each entry corresponds to the web view's title. + + When introducing a new web view, please add it to the registry below. + """ + + MAIN = "main webview" + TOP_TOOLBAR = "top toolbar" + BOTTOM_TOOLBAR = "bottom toolbar" + DECK_OPTIONS = "deck options" + EDITOR = "editor" + LEGACY_DECK_STATS = "legacy deck stats" + DECK_STATS = "deck stats" + PREVIEWER = "previewer" + CHANGE_NOTETYPE = "change notetype" + CARD_LAYOUT = "card layout" + BROWSER_CARD_INFO = "browser card info" + IMPORT_CSV = "csv import" + EMPTY_CARDS = "empty cards" + FIND_DUPLICATES = "find duplicates" + FIELDS = "fields" + + class AnkiWebView(QWebEngineView): allow_drops = False + _kind: AnkiWebViewKind | None def __init__( self, - parent: Optional[QWidget] = None, + parent: QWidget | None = None, title: str = "default", + kind: AnkiWebViewKind | None = None, ) -> None: QWebEngineView.__init__(self, parent=parent) - self.set_title(title) + if kind: + self.set_kind(kind) + else: + self.set_title(title) self._page = AnkiWebPage(self._onBridgeCmd) # reduce flicker self._page.setBackgroundColor(theme_manager.qcolor(colors.CANVAS)) @@ -263,6 +296,15 @@ class AnkiWebView(QWebEngineView): """ ) + def set_kind(self, kind: AnkiWebViewKind) -> None: + self._kind = kind + self.set_title(kind.value) + + @property + def kind(self) -> AnkiWebViewKind | None: + """Used by add-ons to identify the webview kind""" + return self._kind + def set_title(self, title: str) -> None: self.title = title # type: ignore[assignment] @@ -654,8 +696,8 @@ html {{ {font} }} self.setSizePolicy(sp) self.hide() - def add_dynamic_css_and_classes_then_show(self) -> None: - "Add dynamic styling, set platform-specific body classes and reveal." + def add_dynamic_styling_and_props_then_show(self) -> None: + "Add dynamic styling, title, set platform-specific body classes and reveal." css = self.standard_css() body_classes = theme_manager.body_class().split(" ") @@ -670,6 +712,7 @@ html {{ {font} }} self.evalWithCallback( f""" (function(){{ + document.title = `{self.title}`; const style = document.createElement('style'); style.innerHTML = `{css}`; document.head.appendChild(style); @@ -689,7 +732,7 @@ html {{ {font} }} else: extra = "" self.load_url(QUrl(f"{mw.serverURL()}_anki/pages/{name}.html{extra}")) - self.add_dynamic_css_and_classes_then_show() + self.add_dynamic_styling_and_props_then_show() def force_load_hack(self) -> None: """Force process to initialize. diff --git a/qt/tools/genhooks_gui.py b/qt/tools/genhooks_gui.py index bc31dc2d4..e5208b9fb 100644 --- a/qt/tools/genhooks_gui.py +++ b/qt/tools/genhooks_gui.py @@ -705,9 +705,8 @@ mutate the DOM before the page is revealed. For example: -def mytest(web: AnkiWebView): - page = os.path.basename(web.page().url().path()) - if page != "graphs.html": +def mytest(webview: AnkiWebView): + if webview.kind != AnkiWebViewKind.DECK_STATS: return web.eval( """