pass instance to webview_did_receive_js_message instead of string

This commit is contained in:
Damien Elmes 2020-02-09 08:59:29 +10:00
parent 5bd67509ae
commit 7fcb6b5672
11 changed files with 129 additions and 41 deletions

View file

@ -49,6 +49,15 @@ from aqt.utils import (
)
from aqt.webview import AnkiWebView
class PreviewDialog(QDialog):
pass
class FindDupesDialog(QDialog):
pass
# Data model
##########################################################################
@ -1536,7 +1545,7 @@ where id in %s"""
def _openPreview(self):
self._previewState = "question"
self._lastPreviewState = None
self._previewWindow = QDialog(None, Qt.Window)
self._previewWindow = PreviewDialog(None, Qt.Window)
self._previewWindow.setWindowTitle(_("Preview"))
self._previewWindow.finished.connect(self._onPreviewFinished)
@ -1641,7 +1650,9 @@ where id in %s"""
self._previewWeb.stdHtml(
self.mw.reviewer.revHtml(), css=["reviewer.css"], js=jsinc
)
self._previewWeb.set_bridge_command(self._on_preview_bridge_cmd, "preview")
self._previewWeb.set_bridge_command(
self._on_preview_bridge_cmd, self._previewWindow
)
def _on_preview_bridge_cmd(self, cmd: str) -> Any:
if cmd.startswith("play:"):
@ -2118,7 +2129,7 @@ update cards set usn=?, mod=?, did=? where id in """
self.editor.saveNow(self._onFindDupes)
def _onFindDupes(self):
d = QDialog(self)
d = FindDupesDialog(self)
self.mw.setupDialogGC(d)
frm = aqt.forms.finddupes.Ui_Dialog()
frm.setupUi(d)
@ -2129,7 +2140,7 @@ update cards set usn=?, mod=?, did=? where id in """
frm.fields.addItems(fields)
self._dupesButton = None
# links
frm.webView.set_bridge_command(self.dupeLinkClicked, "find_dupes")
frm.webView.set_bridge_command(self.dupeLinkClicked, d)
def onFin(code):
saveGeom(d, "findDupes")

View file

@ -220,8 +220,8 @@ class CardLayout(QDialog):
pform.backWeb.stdHtml(
self.mw.reviewer.revHtml(), css=["reviewer.css"], js=jsinc
)
pform.frontWeb.set_bridge_command(self._on_bridge_cmd, "card_layout")
pform.backWeb.set_bridge_command(self._on_bridge_cmd, "card_layout")
pform.frontWeb.set_bridge_command(self._on_bridge_cmd, self)
pform.backWeb.set_bridge_command(self._on_bridge_cmd, self)
def _on_bridge_cmd(self, cmd: str) -> Any:
if cmd.startswith("play:"):

View file

@ -1,6 +1,9 @@
# Copyright: Ankitects Pty Ltd and contributors
# -*- coding: utf-8 -*-
# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
from __future__ import annotations
from copy import deepcopy
from typing import Any
@ -15,6 +18,11 @@ from aqt.toolbar import BottomBar
from aqt.utils import askUser, getOnlyText, openHelp, openLink, shortcut, showWarning
class DeckBrowserBottomBar:
def __init__(self, deck_browser: DeckBrowser):
self.deck_browser = deck_browser
class DeckBrowser:
_dueTree: Any
@ -26,7 +34,7 @@ class DeckBrowser:
def show(self):
av_player.stop_and_clear_queue()
self.web.set_bridge_command(self._linkHandler, "deck_browser")
self.web.set_bridge_command(self._linkHandler, self)
self._renderPage()
# redraw top bar for theme change
self.mw.toolbar.draw()
@ -333,7 +341,9 @@ where id > ?""",
b
)
self.bottom.draw(buf)
self.bottom.web.set_bridge_command(self._linkHandler, "deck_browser_bottom_bar")
self.bottom.web.set_bridge_command(
self._linkHandler, DeckBrowserBottomBar(self)
)
def _onShared(self):
openLink(aqt.appShared + "decks/")

View file

@ -97,7 +97,7 @@ class Editor:
self.web = EditorWebView(self.widget, self)
self.web.title = "editor"
self.web.allowDrops = True
self.web.set_bridge_command(self.onBridgeCmd, "editor")
self.web.set_bridge_command(self.onBridgeCmd, self)
self.outerLayout.addWidget(self.web, 1)
righttopbtns: List[str] = [

View file

@ -1070,33 +1070,51 @@ undo_state_did_change = _UndoStateDidChangeHook()
class _WebviewDidReceiveJsMessageFilter:
"""Used to handle pycmd() messages sent from Javascript.
Message is the string passed to pycmd(). Context is what was
passed to set_bridge_command(), such as 'editor' or 'reviewer'.
Message is the string passed to pycmd().
For messages you don't want to handle, return handled unchanged.
For messages you don't want to handle, return 'handled' unchanged.
If you handle a message and don't want it passed to the original
bridge command handler, return (True, None).
If you want to pass a value to pycmd's result callback, you can
return it with (True, some_value)."""
return it with (True, some_value).
_hooks: List[Callable[[Tuple[bool, Any], str, str], Tuple[bool, Any]]] = []
Context is the instance that was passed to set_bridge_command().
It can be inspected to check which screen this hook is firing
in, and to get a reference to the screen. For example, if your
code wishes to function only in the review screen, you could do:
if not isinstance(context, aqt.reviewer.Reviewer):
# not reviewer, pass on message
return handled
if message == "my-mark-action":
# our message, call onMark() on the reviewer instance
context.onMark()
# and don't pass message to other handlers
return (True, None)
else:
# some other command, pass it on
return handled
"""
_hooks: List[Callable[[Tuple[bool, Any], str, Any], Tuple[bool, Any]]] = []
def append(
self, cb: Callable[[Tuple[bool, Any], str, str], Tuple[bool, Any]]
self, cb: Callable[[Tuple[bool, Any], str, Any], Tuple[bool, Any]]
) -> None:
"""(handled: Tuple[bool, Any], message: str, context: str)"""
"""(handled: Tuple[bool, Any], message: str, context: Any)"""
self._hooks.append(cb)
def remove(
self, cb: Callable[[Tuple[bool, Any], str, str], Tuple[bool, Any]]
self, cb: Callable[[Tuple[bool, Any], str, Any], Tuple[bool, Any]]
) -> None:
if cb in self._hooks:
self._hooks.remove(cb)
def __call__(
self, handled: Tuple[bool, Any], message: str, context: str
self, handled: Tuple[bool, Any], message: str, context: Any
) -> Tuple[bool, Any]:
for filter in self._hooks:
try:

View file

@ -2,6 +2,8 @@
# -*- coding: utf-8 -*-
# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
from __future__ import annotations
import faulthandler
import gc
import os
@ -58,6 +60,11 @@ from aqt.utils import (
install_pylib_legacy()
class ResetRequired:
def __init__(self, mw: AnkiQt):
self.mw = mw
class AnkiQt(QMainWindow):
col: _Collection
pm: ProfileManagerType
@ -647,14 +654,14 @@ from the profile screen."
# windows
self.progress.timer(100, self.maybeReset, False)
def _resetRequiredState(self, oldState):
def _resetRequiredState(self, oldState: str) -> None:
if oldState != "resetRequired":
self.returnState = oldState
if self.resetModal:
# we don't have to change the webview, as we have a covering window
return
self.web.set_bridge_command(
lambda url: self.delayedMaybeReset(), "reset_required"
lambda url: self.delayedMaybeReset(), ResetRequired(self)
)
i = _("Waiting for editing to finish.")
b = self.button("refresh", _("Resume Now"), id="resume")

View file

@ -12,6 +12,11 @@ from aqt.toolbar import BottomBar
from aqt.utils import askUserDialog, openLink, shortcut, tooltip
class OverviewBottomBar:
def __init__(self, overview: Overview):
self.overview = overview
class Overview:
"Deck overview."
@ -22,7 +27,7 @@ class Overview:
def show(self):
av_player.stop_and_clear_queue()
self.web.set_bridge_command(self._linkHandler, "overview")
self.web.set_bridge_command(self._linkHandler, self)
self.mw.setStateShortcuts(self._shortcutKeys())
self.refresh()
@ -239,7 +244,7 @@ to their original deck."""
b
)
self.bottom.draw(buf)
self.bottom.web.set_bridge_command(self._linkHandler, "overview_bottom")
self.bottom.web.set_bridge_command(self._linkHandler, OverviewBottomBar(self))
# Studying more
######################################################################

View file

@ -2,6 +2,8 @@
# 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 difflib
import html
import html.parser
@ -22,6 +24,11 @@ from aqt.toolbar import BottomBar
from aqt.utils import askUserDialog, downArrow, qtMenuShortcutWorkaround, tooltip
class ReviewerBottomBar:
def __init__(self, reviewer: Reviewer):
self.reviewer = reviewer
class Reviewer:
"Manage reviews. Maintains a separate state."
@ -41,8 +48,8 @@ class Reviewer:
def show(self):
self.mw.col.reset()
self.mw.setStateShortcuts(self._shortcutKeys())
self.web.set_bridge_command(self._linkHandler, "reviewer")
self.bottom.web.set_bridge_command(self._linkHandler, "reviewer_bottom")
self.web.set_bridge_command(self._linkHandler, self)
self.bottom.web.set_bridge_command(self._linkHandler, ReviewerBottomBar(self))
self._reps = None
self.nextCard()

View file

@ -10,6 +10,18 @@ from aqt.qt import *
from aqt.webview import AnkiWebView
# wrapper class for set_bridge_command()
class TopToolbar:
def __init__(self, toolbar: Toolbar):
self.toolbar = toolbar
# wrapper class for set_bridge_command()
class BottomToolbar:
def __init__(self, toolbar: Toolbar):
self.toolbar = toolbar
class Toolbar:
def __init__(self, mw: aqt.AnkiQt, web: AnkiWebView) -> None:
self.mw = mw
@ -26,7 +38,7 @@ class Toolbar:
self.web.requiresCol = False
def draw(self):
self.web.set_bridge_command(self._linkHandler, "top_toolbar")
self.web.set_bridge_command(self._linkHandler, TopToolbar(self))
self.web.stdHtml(self._body % self._centerLinks(), css=["toolbar.css"])
self.web.adjustHeightToFit()
@ -112,7 +124,7 @@ class BottomBar(Toolbar):
def draw(self, buf):
# note: some screens may override this
self.web.set_bridge_command(self._linkHandler, "bottom_toolbar")
self.web.set_bridge_command(self._linkHandler, BottomToolbar(self))
self.web.stdHtml(
self._centerBody % buf, css=["toolbar.css", "toolbar-bottom.css"]
)

View file

@ -414,7 +414,7 @@ body {{ zoom: {}; background: {}; {} }}
self._maybeRunActions()
else:
handled, result = gui_hooks.webview_did_receive_js_message(
(False, None), cmd, self._bridge_command_name
(False, None), cmd, self._bridge_context
)
if handled:
return result
@ -427,7 +427,7 @@ body {{ zoom: {}; background: {}; {} }}
# legacy
def resetHandlers(self):
self.onBridgeCmd = self.defaultOnBridgeCmd
self._bridge_command_name = "unknown"
self._bridge_context = None
def adjustHeightToFit(self):
self.evalWithCallback("$(document.body).height()", self._onHeight)
@ -447,10 +447,10 @@ body {{ zoom: {}; background: {}; {} }}
height = math.ceil(qvar * scaleFactor)
self.setFixedHeight(height)
def set_bridge_command(self, func: Callable[[str], Any], context: str) -> None:
def set_bridge_command(self, func: Callable[[str], Any], context: Any) -> None:
"""Set a handler for pycmd() messages received from Javascript.
Context is a human readable name that is provided to the
webview_did_receive_js_message hook."""
Context is the object calling this routine, eg an instance of
aqt.reviewer.Reviewer or aqt.deckbrowser.DeckBrowser."""
self.onBridgeCmd = func
self._bridge_command_name = context
self._bridge_context = context

View file

@ -130,20 +130,38 @@ hooks = [
###################
Hook(
name="webview_did_receive_js_message",
args=["handled: Tuple[bool, Any]", "message: str", "context: str"],
args=["handled: Tuple[bool, Any]", "message: str", "context: Any"],
return_type="Tuple[bool, Any]",
doc="""Used to handle pycmd() messages sent from Javascript.
Message is the string passed to pycmd(). Context is what was
passed to set_bridge_command(), such as 'editor' or 'reviewer'.
Message is the string passed to pycmd().
For messages you don't want to handle, return handled unchanged.
For messages you don't want to handle, return 'handled' unchanged.
If you handle a message and don't want it passed to the original
bridge command handler, return (True, None).
If you want to pass a value to pycmd's result callback, you can
return it with (True, some_value).""",
return it with (True, some_value).
Context is the instance that was passed to set_bridge_command().
It can be inspected to check which screen this hook is firing
in, and to get a reference to the screen. For example, if your
code wishes to function only in the review screen, you could do:
if not isinstance(context, aqt.reviewer.Reviewer):
# not reviewer, pass on message
return handled
if message == "my-mark-action":
# our message, call onMark() on the reviewer instance
context.onMark()
# and don't pass message to other handlers
return (True, None)
else:
# some other command, pass it on
return handled
""",
),
Hook(
name="webview_will_show_context_menu",