mirror of
https://github.com/ankitects/anki.git
synced 2025-09-19 06:22:22 -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>
(cherry picked from commit 5b0f371791
)
This commit is contained in:
parent
fa1d6eae84
commit
e9dfb7a13d
7 changed files with 66 additions and 26 deletions
|
@ -14,7 +14,6 @@ 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.qt import sip
|
from aqt.qt import sip
|
||||||
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
|
||||||
|
@ -52,7 +51,6 @@ class FindDuplicatesDialog(QDialog):
|
||||||
self._dupes: list[tuple[str, list[NoteId]]] = []
|
self._dupes: list[tuple[str, list[NoteId]]] = []
|
||||||
|
|
||||||
# links
|
# links
|
||||||
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)
|
||||||
|
|
||||||
|
|
|
@ -15,7 +15,6 @@ 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:
|
||||||
|
@ -47,7 +46,6 @@ 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_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)
|
||||||
|
|
|
@ -30,7 +30,7 @@
|
||||||
<number>0</number>
|
<number>0</number>
|
||||||
</property>
|
</property>
|
||||||
<item>
|
<item>
|
||||||
<widget class="AnkiWebView" name="webview" native="true">
|
<widget class="EmptyCardsWebView" name="webview" native="true">
|
||||||
<property name="url" stdset="0">
|
<property name="url" stdset="0">
|
||||||
<url>
|
<url>
|
||||||
<string notr="true">about:blank</string>
|
<string notr="true">about:blank</string>
|
||||||
|
@ -81,7 +81,7 @@
|
||||||
</widget>
|
</widget>
|
||||||
<customwidgets>
|
<customwidgets>
|
||||||
<customwidget>
|
<customwidget>
|
||||||
<class>AnkiWebView</class>
|
<class>EmptyCardsWebView</class>
|
||||||
<extends>QWidget</extends>
|
<extends>QWidget</extends>
|
||||||
<header location="global">aqt/webview</header>
|
<header location="global">aqt/webview</header>
|
||||||
<container>1</container>
|
<container>1</container>
|
||||||
|
|
|
@ -73,7 +73,7 @@
|
||||||
<number>0</number>
|
<number>0</number>
|
||||||
</property>
|
</property>
|
||||||
<item>
|
<item>
|
||||||
<widget class="AnkiWebView" name="webView" native="true">
|
<widget class="FindDupesWebView" name="webView" native="true">
|
||||||
<property name="url" stdset="0">
|
<property name="url" stdset="0">
|
||||||
<url>
|
<url>
|
||||||
<string notr="true">about:blank</string>
|
<string notr="true">about:blank</string>
|
||||||
|
@ -98,7 +98,7 @@
|
||||||
</widget>
|
</widget>
|
||||||
<customwidgets>
|
<customwidgets>
|
||||||
<customwidget>
|
<customwidget>
|
||||||
<class>AnkiWebView</class>
|
<class>FindDupesWebView</class>
|
||||||
<extends>QWidget</extends>
|
<extends>QWidget</extends>
|
||||||
<header location="global">aqt/webview</header>
|
<header location="global">aqt/webview</header>
|
||||||
<container>1</container>
|
<container>1</container>
|
||||||
|
|
|
@ -30,7 +30,7 @@
|
||||||
<number>0</number>
|
<number>0</number>
|
||||||
</property>
|
</property>
|
||||||
<item>
|
<item>
|
||||||
<widget class="AnkiWebView" name="web" native="true">
|
<widget class="StatsWebView" name="web" native="true">
|
||||||
<property name="url" stdset="0">
|
<property name="url" stdset="0">
|
||||||
<url>
|
<url>
|
||||||
<string notr="true">about:blank</string>
|
<string notr="true">about:blank</string>
|
||||||
|
@ -146,7 +146,7 @@
|
||||||
</widget>
|
</widget>
|
||||||
<customwidgets>
|
<customwidgets>
|
||||||
<customwidget>
|
<customwidget>
|
||||||
<class>AnkiWebView</class>
|
<class>StatsWebView</class>
|
||||||
<extends>QWidget</extends>
|
<extends>QWidget</extends>
|
||||||
<header location="global">aqt/webview</header>
|
<header location="global">aqt/webview</header>
|
||||||
<container>1</container>
|
<container>1</container>
|
||||||
|
|
|
@ -25,7 +25,7 @@ from aqt.utils import (
|
||||||
tooltip,
|
tooltip,
|
||||||
tr,
|
tr,
|
||||||
)
|
)
|
||||||
from aqt.webview import AnkiWebViewKind
|
from aqt.webview import LegacyStatsWebView
|
||||||
|
|
||||||
|
|
||||||
class NewDeckStats(QDialog):
|
class NewDeckStats(QDialog):
|
||||||
|
@ -71,7 +71,6 @@ 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()
|
||||||
|
@ -154,6 +153,9 @@ class DeckStats(QDialog):
|
||||||
self.name = "deckStats"
|
self.name = "deckStats"
|
||||||
self.period = 0
|
self.period = 0
|
||||||
self.form = aqt.forms.stats.Ui_Dialog()
|
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.oldPos = None
|
||||||
self.wholeCollection = False
|
self.wholeCollection = False
|
||||||
self.setMinimumWidth(700)
|
self.setMinimumWidth(700)
|
||||||
|
@ -232,7 +234,6 @@ 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_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"],
|
||||||
|
|
|
@ -10,7 +10,9 @@ import re
|
||||||
import sys
|
import sys
|
||||||
from collections.abc import Callable, Sequence
|
from collections.abc import Callable, Sequence
|
||||||
from enum import Enum
|
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
|
||||||
import anki.lang
|
import anki.lang
|
||||||
|
@ -360,7 +362,9 @@ class AnkiWebView(QWebEngineView):
|
||||||
kind: AnkiWebViewKind = AnkiWebViewKind.DEFAULT,
|
kind: AnkiWebViewKind = AnkiWebViewKind.DEFAULT,
|
||||||
) -> None:
|
) -> None:
|
||||||
QWebEngineView.__init__(self, parent=parent)
|
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
|
# reduce flicker
|
||||||
self.page().setBackgroundColor(theme_manager.qcolor(colors.CANVAS))
|
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:
|
def page(self) -> AnkiWebPage:
|
||||||
return cast(AnkiWebPage, super().page())
|
return cast(AnkiWebPage, super().page())
|
||||||
|
|
||||||
|
@ -965,3 +958,53 @@ html {{ {font} }}
|
||||||
@deprecated(info="use theme_manager.qcolor() instead")
|
@deprecated(info="use theme_manager.qcolor() instead")
|
||||||
def get_window_bg_color(self, night_mode: bool | None = None) -> QColor:
|
def get_window_bg_color(self, night_mode: bool | None = None) -> QColor:
|
||||||
return theme_manager.qcolor(colors.CANVAS)
|
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