mirror of
https://github.com/ankitects/anki.git
synced 2025-09-18 14:02:21 -04:00
Fix AnkiWebPage
not being initialized for default web view kinds (e.g. in add-ons) (#3933)
* add AnkiWebView subclasses for stats, empty cards and find dupes ui * update ui files to use subclassed webviews instead * remove superfluous calls to AnkiWebView.set_kind * Avoid set_kind() race condition in legacy stats webview Replacing the web view is a hacky workaround, but likely a reasonable compromise for a legacy view that we do not want to maintain a separate Qt form for. * Slightly refactor AnkiWebView subclass creation and tweak inline comment + Extend create_ankiwebview_subclass() with the ability to set any init time AnkiWebView argument + Introduce some nice-to-haves in terms of static type checking support and IDE autocompletion + Mark helper function as private to discourage add-on use * Drop `AnkiWebView.set_kind` completely There no longer is an Anki-internal use case for changing the web view kind after initializing a web view, and add-ons almost certainly do not have any use for it either. Given that setting the kind after web view construction can lead to known race conditions with `domDone` signals, we should remove this method to discourage uses like this in both Anki code and add-on consumers. There currenty only seems to be one add-on calling `set_kind()`, so this seem like a justifiable API change. --------- Co-authored-by: llama <100429699+iamllama@users.noreply.github.com>
This commit is contained in:
parent
a74fd74631
commit
5b0f371791
7 changed files with 66 additions and 26 deletions
|
@ -14,7 +14,6 @@ from anki.collection import SearchNode
|
|||
from anki.notes import NoteId
|
||||
from aqt.qt import *
|
||||
from aqt.qt import sip
|
||||
from aqt.webview import AnkiWebViewKind
|
||||
|
||||
from ..operations import QueryOp
|
||||
from ..operations.tag import add_tags_to_notes
|
||||
|
@ -52,7 +51,6 @@ class FindDuplicatesDialog(QDialog):
|
|||
self._dupes: list[tuple[str, list[NoteId]]] = []
|
||||
|
||||
# links
|
||||
form.webView.set_kind(AnkiWebViewKind.FIND_DUPLICATES)
|
||||
form.webView.set_bridge_command(self._on_duplicate_clicked, context=self)
|
||||
form.webView.stdHtml("", context=self)
|
||||
|
||||
|
|
|
@ -15,7 +15,6 @@ 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:
|
||||
|
@ -47,7 +46,6 @@ 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_kind(AnkiWebViewKind.EMPTY_CARDS)
|
||||
self.form.webview.set_bridge_command(self._on_note_link_clicked, self)
|
||||
|
||||
gui_hooks.empty_cards_will_show(self)
|
||||
|
|
|
@ -30,7 +30,7 @@
|
|||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="AnkiWebView" name="webview" native="true">
|
||||
<widget class="EmptyCardsWebView" name="webview" native="true">
|
||||
<property name="url" stdset="0">
|
||||
<url>
|
||||
<string notr="true">about:blank</string>
|
||||
|
@ -81,7 +81,7 @@
|
|||
</widget>
|
||||
<customwidgets>
|
||||
<customwidget>
|
||||
<class>AnkiWebView</class>
|
||||
<class>EmptyCardsWebView</class>
|
||||
<extends>QWidget</extends>
|
||||
<header location="global">aqt/webview</header>
|
||||
<container>1</container>
|
||||
|
|
|
@ -73,7 +73,7 @@
|
|||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="AnkiWebView" name="webView" native="true">
|
||||
<widget class="FindDupesWebView" name="webView" native="true">
|
||||
<property name="url" stdset="0">
|
||||
<url>
|
||||
<string notr="true">about:blank</string>
|
||||
|
@ -98,7 +98,7 @@
|
|||
</widget>
|
||||
<customwidgets>
|
||||
<customwidget>
|
||||
<class>AnkiWebView</class>
|
||||
<class>FindDupesWebView</class>
|
||||
<extends>QWidget</extends>
|
||||
<header location="global">aqt/webview</header>
|
||||
<container>1</container>
|
||||
|
|
|
@ -30,7 +30,7 @@
|
|||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="AnkiWebView" name="web" native="true">
|
||||
<widget class="StatsWebView" name="web" native="true">
|
||||
<property name="url" stdset="0">
|
||||
<url>
|
||||
<string notr="true">about:blank</string>
|
||||
|
@ -146,7 +146,7 @@
|
|||
</widget>
|
||||
<customwidgets>
|
||||
<customwidget>
|
||||
<class>AnkiWebView</class>
|
||||
<class>StatsWebView</class>
|
||||
<extends>QWidget</extends>
|
||||
<header location="global">aqt/webview</header>
|
||||
<container>1</container>
|
||||
|
|
|
@ -25,7 +25,7 @@ from aqt.utils import (
|
|||
tooltip,
|
||||
tr,
|
||||
)
|
||||
from aqt.webview import AnkiWebViewKind
|
||||
from aqt.webview import LegacyStatsWebView
|
||||
|
||||
|
||||
class NewDeckStats(QDialog):
|
||||
|
@ -71,7 +71,6 @@ 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()
|
||||
|
@ -154,6 +153,9 @@ class DeckStats(QDialog):
|
|||
self.name = "deckStats"
|
||||
self.period = 0
|
||||
self.form = aqt.forms.stats.Ui_Dialog()
|
||||
# Hack: Switch out web views dynamically to avoid maintaining multiple
|
||||
# Qt forms for different versions of the stats dialog.
|
||||
self.form.web = LegacyStatsWebView(self.mw)
|
||||
self.oldPos = None
|
||||
self.wholeCollection = False
|
||||
self.setMinimumWidth(700)
|
||||
|
@ -232,7 +234,6 @@ class DeckStats(QDialog):
|
|||
stats = self.mw.col.stats()
|
||||
stats.wholeCollection = self.wholeCollection
|
||||
self.report = stats.report(type=self.period)
|
||||
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"],
|
||||
|
|
|
@ -10,7 +10,9 @@ import re
|
|||
import sys
|
||||
from collections.abc import Callable, Sequence
|
||||
from enum import Enum
|
||||
from typing import TYPE_CHECKING, Any, cast
|
||||
from typing import TYPE_CHECKING, Any, Type, cast
|
||||
|
||||
from typing_extensions import TypedDict, Unpack
|
||||
|
||||
import anki
|
||||
import anki.lang
|
||||
|
@ -360,7 +362,9 @@ class AnkiWebView(QWebEngineView):
|
|||
kind: AnkiWebViewKind = AnkiWebViewKind.DEFAULT,
|
||||
) -> None:
|
||||
QWebEngineView.__init__(self, parent=parent)
|
||||
self.set_kind(kind)
|
||||
self._kind = kind
|
||||
self.set_title(kind.value)
|
||||
self.setPage(AnkiWebPage(self._onBridgeCmd, kind, self))
|
||||
# reduce flicker
|
||||
self.page().setBackgroundColor(theme_manager.qcolor(colors.CANVAS))
|
||||
|
||||
|
@ -390,17 +394,6 @@ class AnkiWebView(QWebEngineView):
|
|||
"""
|
||||
)
|
||||
|
||||
def set_kind(self, kind: AnkiWebViewKind) -> None:
|
||||
self._kind = kind
|
||||
self.set_title(kind.value)
|
||||
# this is an ugly hack to avoid breakages caused by
|
||||
# creating a default webview then immediately calling set_kind, which results
|
||||
# in the creation of two pages, and the second fails as the domDone
|
||||
# signal from the first one is received
|
||||
if kind != AnkiWebViewKind.DEFAULT:
|
||||
self.setPage(AnkiWebPage(self._onBridgeCmd, kind, self))
|
||||
self.page().setBackgroundColor(theme_manager.qcolor(colors.CANVAS))
|
||||
|
||||
def page(self) -> AnkiWebPage:
|
||||
return cast(AnkiWebPage, super().page())
|
||||
|
||||
|
@ -968,3 +961,53 @@ html {{ {font} }}
|
|||
@deprecated(info="use theme_manager.qcolor() instead")
|
||||
def get_window_bg_color(self, night_mode: bool | None = None) -> QColor:
|
||||
return theme_manager.qcolor(colors.CANVAS)
|
||||
|
||||
|
||||
# Pre-configured classes for use in Qt Designer
|
||||
##########################################################################
|
||||
|
||||
|
||||
class _AnkiWebViewKwargs(TypedDict, total=False):
|
||||
parent: QWidget | None
|
||||
title: str
|
||||
kind: AnkiWebViewKind
|
||||
|
||||
|
||||
def _create_ankiwebview_subclass(
|
||||
name: str,
|
||||
/,
|
||||
**fixed_kwargs: Unpack[_AnkiWebViewKwargs],
|
||||
) -> Type[AnkiWebView]:
|
||||
|
||||
def __init__(self, *args: Any, **kwargs: _AnkiWebViewKwargs) -> None:
|
||||
# user‑supplied kwargs override fixed kwargs
|
||||
merged = cast(_AnkiWebViewKwargs, {**fixed_kwargs, **kwargs})
|
||||
AnkiWebView.__init__(self, *args, **merged)
|
||||
|
||||
__init__.__qualname__ = f"{name}.__init__"
|
||||
if fixed_kwargs:
|
||||
__init__.__doc__ = (
|
||||
f"Auto‑generated wrapper that pre‑sets "
|
||||
f"{', '.join(f'{k}={v!r}' for k, v in fixed_kwargs.items())}."
|
||||
)
|
||||
|
||||
cls: Type[AnkiWebView] = type(name, (AnkiWebView,), {"__init__": __init__})
|
||||
|
||||
return cls
|
||||
|
||||
|
||||
# These subclasses are used in Qt Designer UI files to allow for configuring
|
||||
# web views at initialization time (custom widgets can otherwise only be
|
||||
# initialized with the default constructor)
|
||||
StatsWebView = _create_ankiwebview_subclass(
|
||||
"StatsWebView", kind=AnkiWebViewKind.DECK_STATS
|
||||
)
|
||||
LegacyStatsWebView = _create_ankiwebview_subclass(
|
||||
"LegacyStatsWebView", kind=AnkiWebViewKind.LEGACY_DECK_STATS
|
||||
)
|
||||
EmptyCardsWebView = _create_ankiwebview_subclass(
|
||||
"EmptyCardsWebView", kind=AnkiWebViewKind.EMPTY_CARDS
|
||||
)
|
||||
FindDupesWebView = _create_ankiwebview_subclass(
|
||||
"FindDupesWebView", kind=AnkiWebViewKind.FIND_DUPLICATES
|
||||
)
|
||||
|
|
Loading…
Reference in a new issue