Rework & unify webview identification and title setting (#2366)

* Create common web view registry and unify title setting

* Consistently use space-separated naming for webview titles

None of the modified titles seem to be in use by add-ons, so we are not bound to the current naming.

The old naming was likely following camelCase as the name was also acting as a key for saveGeom, which is no longer the case.

* Update webview_did_inject_style_into_page example

* Add docstring to addon-targeted method

* Change AnkiWebView.origin to property

* Fix dupe enum value

* Tweak method name

* Add semicolon

* Rename `AnkiWebViewOrigin` to `AnkiWebViewKind`
This commit is contained in:
Aristotelis 2023-02-10 05:53:11 +01:00 committed by GitHub
parent f616bea580
commit 0f86c9fd11
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
15 changed files with 84 additions and 37 deletions

View file

@ -19,7 +19,7 @@ from aqt.utils import (
setWindowIcon, setWindowIcon,
tr, tr,
) )
from aqt.webview import AnkiWebView from aqt.webview import AnkiWebView, AnkiWebViewKind
class CardInfoDialog(QDialog): class CardInfoDialog(QDialog):
@ -52,7 +52,7 @@ class CardInfoDialog(QDialog):
addCloseShortcut(self) addCloseShortcut(self)
setWindowIcon(self) setWindowIcon(self)
self.web = AnkiWebView(title=self.TITLE) self.web = AnkiWebView(kind=AnkiWebViewKind.BROWSER_CARD_INFO)
self.web.setVisible(False) self.web.setVisible(False)
self.web.load_ts_page("card-info") self.web.load_ts_page("card-info")
layout = QVBoxLayout() layout = QVBoxLayout()

View file

@ -13,6 +13,7 @@ import aqt.forms
from anki.collection import SearchNode from anki.collection import SearchNode
from anki.notes import NoteId from anki.notes import NoteId
from aqt.qt import * from aqt.qt import *
from aqt.webview import AnkiWebViewKind
from ..operations import QueryOp from ..operations import QueryOp
from ..operations.tag import add_tags_to_notes from ..operations.tag import add_tags_to_notes
@ -50,7 +51,7 @@ class FindDuplicatesDialog(QDialog):
self._dupes: list[tuple[str, list[NoteId]]] = [] self._dupes: list[tuple[str, list[NoteId]]] = []
# links # 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.set_bridge_command(self._on_duplicate_clicked, context=self)
form.webView.stdHtml("", context=self) form.webView.stdHtml("", context=self)

View file

@ -29,7 +29,7 @@ from aqt.reviewer import replay_audio
from aqt.sound import av_player, play_clicked_audio from aqt.sound import av_player, play_clicked_audio
from aqt.theme import theme_manager from aqt.theme import theme_manager
from aqt.utils import disable_help_button, restoreGeom, saveGeom, setWindowIcon, tr 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] LastStateAndMod = tuple[str, int, int]
@ -78,7 +78,7 @@ class Previewer(QDialog):
self.silentlyClose = True self.silentlyClose = True
self.vbox = QVBoxLayout() self.vbox = QVBoxLayout()
self.vbox.setContentsMargins(0, 0, 0, 0) self.vbox.setContentsMargins(0, 0, 0, 0)
self._web = AnkiWebView(title="previewer") self._web = AnkiWebView(kind=AnkiWebViewKind.PREVIEWER)
self.vbox.addWidget(self._web) self.vbox.addWidget(self._web)
self.bbox = QDialogButtonBox() self.bbox = QDialogButtonBox()
self.bbox.setLayoutDirection(Qt.LayoutDirection.LeftToRight) self.bbox.setLayoutDirection(Qt.LayoutDirection.LeftToRight)

View file

@ -23,7 +23,7 @@ from aqt.utils import (
tooltip, tooltip,
tr, tr,
) )
from aqt.webview import AnkiWebView from aqt.webview import AnkiWebView, AnkiWebViewKind
class ChangeNotetypeDialog(QDialog): class ChangeNotetypeDialog(QDialog):
@ -52,7 +52,7 @@ class ChangeNotetypeDialog(QDialog):
restoreGeom(self, self.TITLE, default_size=(800, 800)) restoreGeom(self, self.TITLE, default_size=(800, 800))
addCloseShortcut(self) addCloseShortcut(self)
self.web = AnkiWebView(title=self.TITLE) self.web = AnkiWebView(kind=AnkiWebViewKind.CHANGE_NOTETYPE)
self.web.setVisible(False) self.web.setVisible(False)
self.web.load_ts_page("change-notetype") self.web.load_ts_page("change-notetype")
layout = QVBoxLayout() layout = QVBoxLayout()

View file

@ -35,7 +35,7 @@ from aqt.utils import (
tooltip, tooltip,
tr, tr,
) )
from aqt.webview import AnkiWebView from aqt.webview import AnkiWebView, AnkiWebViewKind
class CardLayout(QDialog): class CardLayout(QDialog):
@ -334,7 +334,7 @@ class CardLayout(QDialog):
def setup_preview(self) -> None: def setup_preview(self) -> None:
pform = self.pform 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.addWidget(self.preview_web)
pform.verticalLayout.setStretch(1, 99) pform.verticalLayout.setStretch(1, 99)
pform.preview_front.isChecked() pform.preview_front.isChecked()

View file

@ -19,7 +19,7 @@ from aqt.utils import (
saveGeom, saveGeom,
tr, tr,
) )
from aqt.webview import AnkiWebView from aqt.webview import AnkiWebView, AnkiWebViewKind
class DeckOptionsDialog(QDialog): class DeckOptionsDialog(QDialog):
@ -42,7 +42,7 @@ class DeckOptionsDialog(QDialog):
restoreGeom(self, self.TITLE, default_size=(800, 800)) restoreGeom(self, self.TITLE, default_size=(800, 800))
addCloseShortcut(self) addCloseShortcut(self)
self.web = AnkiWebView(title=self.TITLE) self.web = AnkiWebView(kind=AnkiWebViewKind.DECK_OPTIONS)
self.web.load_ts_page("deck-options") self.web.load_ts_page("deck-options")
layout = QVBoxLayout() layout = QVBoxLayout()
layout.setContentsMargins(0, 0, 0, 0) layout.setContentsMargins(0, 0, 0, 0)

View file

@ -55,7 +55,7 @@ from aqt.utils import (
tooltip, tooltip,
tr, tr,
) )
from aqt.webview import AnkiWebView from aqt.webview import AnkiWebView, AnkiWebViewKind
pics = ("jpg", "jpeg", "png", "tif", "tiff", "gif", "svg", "webp", "ico") pics = ("jpg", "jpeg", "png", "tif", "tiff", "gif", "svg", "webp", "ico")
audio = ( audio = (
@ -1211,7 +1211,7 @@ require("anki/ui").loaded.then(() => require("anki/NoteEditor").instances[0].too
class EditorWebView(AnkiWebView): class EditorWebView(AnkiWebView):
def __init__(self, parent: QWidget, editor: Editor) -> None: def __init__(self, parent: QWidget, editor: Editor) -> None:
AnkiWebView.__init__(self, title="editor") AnkiWebView.__init__(self, kind=AnkiWebViewKind.EDITOR)
self.editor = editor self.editor = editor
self.setAcceptDrops(True) self.setAcceptDrops(True)
self._markInternal = False self._markInternal = False

View file

@ -15,6 +15,7 @@ from anki.collection import EmptyCardsReport
from aqt import gui_hooks from aqt import gui_hooks
from aqt.qt import QDialog, QDialogButtonBox, qconnect from aqt.qt import QDialog, QDialogButtonBox, qconnect
from aqt.utils import disable_help_button, restoreGeom, saveGeom, tooltip, tr 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: def show_empty_cards(mw: aqt.main.AnkiQt) -> None:
@ -46,7 +47,7 @@ class EmptyCardsDialog(QDialog):
self.setWindowTitle(tr.empty_cards_window_title()) self.setWindowTitle(tr.empty_cards_window_title())
disable_help_button(self) disable_help_button(self)
self.form.keep_notes.setText(tr.empty_cards_preserve_notes_checkbox()) 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) self.form.webview.set_bridge_command(self._on_note_link_clicked, self)
gui_hooks.empty_cards_will_show(self) gui_hooks.empty_cards_will_show(self)

View file

@ -27,6 +27,7 @@ from aqt.utils import (
tooltip, tooltip,
tr, tr,
) )
from aqt.webview import AnkiWebViewKind
class FieldDialog(QDialog): class FieldDialog(QDialog):
@ -55,7 +56,7 @@ class FieldDialog(QDialog):
form.setupUi(self) form.setupUi(self)
self.webview = form.webview self.webview = form.webview
self.webview.set_title("fields") self.webview.set_kind(AnkiWebViewKind.FIELDS)
self.show() self.show()
self.refresh() self.refresh()

View file

@ -10,7 +10,7 @@ import aqt.operations
from anki.collection import ImportCsvRequest from anki.collection import ImportCsvRequest
from aqt.qt import * from aqt.qt import *
from aqt.utils import addCloseShortcut, disable_help_button, restoreGeom, saveGeom, tr 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): class ImportCsvDialog(QDialog):
@ -38,7 +38,7 @@ class ImportCsvDialog(QDialog):
restoreGeom(self, self.TITLE, default_size=(800, 800)) restoreGeom(self, self.TITLE, default_size=(800, 800))
addCloseShortcut(self) addCloseShortcut(self)
self.web = AnkiWebView(title=self.TITLE) self.web = AnkiWebView(kind=AnkiWebViewKind.IMPORT_CSV)
self.web.setVisible(False) self.web.setVisible(False)
self.web.load_ts_page("import-csv") self.web.load_ts_page("import-csv")
layout = QVBoxLayout() layout = QVBoxLayout()

View file

@ -91,7 +91,7 @@ from aqt.utils import (
tooltip, tooltip,
tr, tr,
) )
from aqt.webview import AnkiWebView from aqt.webview import AnkiWebView, AnkiWebViewKind
install_pylib_legacy() install_pylib_legacy()
@ -105,7 +105,7 @@ T = TypeVar("T")
class MainWebView(AnkiWebView): class MainWebView(AnkiWebView):
def __init__(self, mw: AnkiQt) -> None: def __init__(self, mw: AnkiQt) -> None:
AnkiWebView.__init__(self, title="main webview") AnkiWebView.__init__(self, kind=AnkiWebViewKind.MAIN)
self.mw = mw self.mw = mw
self.setFocusPolicy(Qt.FocusPolicy.WheelFocus) self.setFocusPolicy(Qt.FocusPolicy.WheelFocus)
self.setMinimumWidth(400) self.setMinimumWidth(400)
@ -879,12 +879,12 @@ title="{}" {}>{}</button>""".format(
self.form = aqt.forms.main.Ui_MainWindow() self.form = aqt.forms.main.Ui_MainWindow()
self.form.setupUi(self) self.form.setupUi(self)
# toolbar # toolbar
tweb = self.toolbarWeb = TopWebView(self, title="top toolbar") tweb = self.toolbarWeb = TopWebView(self)
self.toolbar = Toolbar(self, tweb) self.toolbar = Toolbar(self, tweb)
# main area # main area
self.web = MainWebView(self) self.web = MainWebView(self)
# bottom area # bottom area
sweb = self.bottomWeb = BottomWebView(self, title="bottom toolbar") sweb = self.bottomWeb = BottomWebView(self)
sweb.setFocusPolicy(Qt.FocusPolicy.WheelFocus) sweb.setFocusPolicy(Qt.FocusPolicy.WheelFocus)
sweb.disable_zoom() sweb.disable_zoom()
# add in a layout # add in a layout

View file

@ -21,6 +21,7 @@ from aqt.utils import (
tooltip, tooltip,
tr, tr,
) )
from aqt.webview import AnkiWebViewKind
class NewDeckStats(QDialog): class NewDeckStats(QDialog):
@ -50,6 +51,7 @@ class NewDeckStats(QDialog):
maybeHideClose(self.form.buttonBox) maybeHideClose(self.form.buttonBox)
addCloseShortcut(self) addCloseShortcut(self)
gui_hooks.stats_dialog_will_show(self) gui_hooks.stats_dialog_will_show(self)
self.form.web.set_kind(AnkiWebViewKind.DECK_STATS)
self.form.web.hide_while_preserving_layout() self.form.web.hide_while_preserving_layout()
self.show() self.show()
self.refresh() self.refresh()
@ -198,7 +200,7 @@ class DeckStats(QDialog):
stats = self.mw.col.stats() stats = self.mw.col.stats()
stats.wholeCollection = self.wholeCollection stats.wholeCollection = self.wholeCollection
self.report = stats.report(type=self.period) 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( self.form.web.stdHtml(
f"<html><body>{self.report}</body></html>", f"<html><body>{self.report}</body></html>",
js=["js/vendor/jquery.min.js", "js/vendor/plot.js"], js=["js/vendor/jquery.min.js", "js/vendor/plot.js"],

View file

@ -13,7 +13,7 @@ from aqt.qt import *
from aqt.sync import get_sync_status from aqt.sync import get_sync_status
from aqt.theme import theme_manager from aqt.theme import theme_manager
from aqt.utils import tr from aqt.utils import tr
from aqt.webview import AnkiWebView from aqt.webview import AnkiWebView, AnkiWebViewKind
class HideMode(enum.IntEnum): class HideMode(enum.IntEnum):
@ -36,8 +36,8 @@ class BottomToolbar:
class ToolbarWebView(AnkiWebView): class ToolbarWebView(AnkiWebView):
hide_condition: Callable[..., bool] hide_condition: Callable[..., bool]
def __init__(self, mw: aqt.AnkiQt, title: str) -> None: def __init__(self, mw: aqt.AnkiQt, kind: AnkiWebViewKind | None = None) -> None:
AnkiWebView.__init__(self, mw, title=title) AnkiWebView.__init__(self, mw, kind=kind)
self.mw = mw self.mw = mw
self.setFocusPolicy(Qt.FocusPolicy.WheelFocus) self.setFocusPolicy(Qt.FocusPolicy.WheelFocus)
self.disable_zoom() self.disable_zoom()
@ -58,8 +58,8 @@ class ToolbarWebView(AnkiWebView):
class TopWebView(ToolbarWebView): class TopWebView(ToolbarWebView):
def __init__(self, mw: aqt.AnkiQt, title: str) -> None: def __init__(self, mw: aqt.AnkiQt) -> None:
super().__init__(mw, title=title) super().__init__(mw, kind=AnkiWebViewKind.TOP_TOOLBAR)
self.web_height = 0 self.web_height = 0
qconnect(self.hide_timer.timeout, self.hide_if_allowed) qconnect(self.hide_timer.timeout, self.hide_if_allowed)
@ -179,8 +179,8 @@ class TopWebView(ToolbarWebView):
class BottomWebView(ToolbarWebView): class BottomWebView(ToolbarWebView):
def __init__(self, mw: aqt.AnkiQt, title: str) -> None: def __init__(self, mw: aqt.AnkiQt) -> None:
super().__init__(mw, title=title) super().__init__(mw, kind=AnkiWebViewKind.BOTTOM_TOOLBAR)
qconnect(self.hide_timer.timeout, self.hide_if_allowed) qconnect(self.hide_timer.timeout, self.hide_if_allowed)
def eventFilter(self, obj, evt): def eventFilter(self, obj, evt):

View file

@ -1,10 +1,13 @@
# 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
from __future__ import annotations
import dataclasses import dataclasses
import json import json
import re import re
import sys import sys
from enum import Enum
from typing import Any, Callable, Optional, Sequence, cast from typing import Any, Callable, Optional, Sequence, cast
import anki import anki
@ -222,15 +225,45 @@ 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): class AnkiWebView(QWebEngineView):
allow_drops = False allow_drops = False
_kind: AnkiWebViewKind | None
def __init__( def __init__(
self, self,
parent: Optional[QWidget] = None, parent: QWidget | None = None,
title: str = "default", title: str = "default",
kind: AnkiWebViewKind | None = None,
) -> None: ) -> None:
QWebEngineView.__init__(self, parent=parent) QWebEngineView.__init__(self, parent=parent)
if kind:
self.set_kind(kind)
else:
self.set_title(title) self.set_title(title)
self._page = AnkiWebPage(self._onBridgeCmd) self._page = AnkiWebPage(self._onBridgeCmd)
# reduce flicker # reduce flicker
@ -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: def set_title(self, title: str) -> None:
self.title = title # type: ignore[assignment] self.title = title # type: ignore[assignment]
@ -654,8 +696,8 @@ html {{ {font} }}
self.setSizePolicy(sp) self.setSizePolicy(sp)
self.hide() self.hide()
def add_dynamic_css_and_classes_then_show(self) -> None: def add_dynamic_styling_and_props_then_show(self) -> None:
"Add dynamic styling, set platform-specific body classes and reveal." "Add dynamic styling, title, set platform-specific body classes and reveal."
css = self.standard_css() css = self.standard_css()
body_classes = theme_manager.body_class().split(" ") body_classes = theme_manager.body_class().split(" ")
@ -670,6 +712,7 @@ html {{ {font} }}
self.evalWithCallback( self.evalWithCallback(
f""" f"""
(function(){{ (function(){{
document.title = `{self.title}`;
const style = document.createElement('style'); const style = document.createElement('style');
style.innerHTML = `{css}`; style.innerHTML = `{css}`;
document.head.appendChild(style); document.head.appendChild(style);
@ -689,7 +732,7 @@ html {{ {font} }}
else: else:
extra = "" extra = ""
self.load_url(QUrl(f"{mw.serverURL()}_anki/pages/{name}.html{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: def force_load_hack(self) -> None:
"""Force process to initialize. """Force process to initialize.

View file

@ -705,9 +705,8 @@ mutate the DOM before the page is revealed.
For example: For example:
def mytest(web: AnkiWebView): def mytest(webview: AnkiWebView):
page = os.path.basename(web.page().url().path()) if webview.kind != AnkiWebViewKind.DECK_STATS:
if page != "graphs.html":
return return
web.eval( web.eval(
""" """