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,
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()

View file

@ -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)

View file

@ -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)

View file

@ -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()

View file

@ -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()

View file

@ -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)

View file

@ -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

View file

@ -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)

View file

@ -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()

View file

@ -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()

View file

@ -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="{}" {}>{}</button>""".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

View file

@ -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"<html><body>{self.report}</body></html>",
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.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):

View file

@ -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.

View file

@ -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(
"""