add some more hooks; stringify fully qualified types

To avoid circular imports, types that contain a '.' are automatically
converted to strings.
This commit is contained in:
Damien Elmes 2020-01-15 08:53:57 +10:00
parent 2921037c7b
commit 2fa662f7ae
7 changed files with 210 additions and 19 deletions

View file

@ -355,13 +355,17 @@ prepare_searches_hook = PrepareSearchesHook()
class RemoveNotesHook:
_hooks: List[Callable[[anki.storage._Collection, List[int]], None]] = []
_hooks: List[Callable[["anki.storage._Collection", List[int]], None]] = []
def append(self, cb: Callable[[anki.storage._Collection, List[int]], None]) -> None:
def append(
self, cb: Callable[["anki.storage._Collection", List[int]], None]
) -> None:
"""(col: anki.storage._Collection, ids: List[int])"""
self._hooks.append(cb)
def remove(self, cb: Callable[[anki.storage._Collection, List[int]], None]) -> None:
def remove(
self, cb: Callable[["anki.storage._Collection", List[int]], None]
) -> None:
self._hooks.remove(cb)
def __call__(self, col: anki.storage._Collection, ids: List[int]) -> None:
@ -388,7 +392,7 @@ class RenderedCardTemplateFilter:
Dict[str, str],
Dict[str, Any],
QAData,
anki.storage._Collection,
"anki.storage._Collection",
],
str,
]
@ -403,7 +407,7 @@ class RenderedCardTemplateFilter:
Dict[str, str],
Dict[str, Any],
QAData,
anki.storage._Collection,
"anki.storage._Collection",
],
str,
],
@ -420,7 +424,7 @@ class RenderedCardTemplateFilter:
Dict[str, str],
Dict[str, Any],
QAData,
anki.storage._Collection,
"anki.storage._Collection",
],
str,
],

View file

@ -32,7 +32,9 @@ class Hook:
types = []
for arg in self.args or []:
(name, type) = arg.split(":")
types.append(type.strip())
if "." in type:
type = '"' + type.strip() + '"'
types.append(type)
types_str = ", ".join(types)
return f"Callable[[{types_str}], {self.return_type or 'None'}]"

View file

@ -15,7 +15,7 @@ import anki
import aqt.forms
from anki.collection import _Collection
from anki.consts import *
from anki.hooks import addHook, remHook, runFilter, runHook
from anki.hooks import addHook, remHook
from anki.lang import _, ngettext
from anki.utils import (
bodyClass,
@ -26,7 +26,7 @@ from anki.utils import (
isMac,
isWin,
)
from aqt import AnkiQt
from aqt import AnkiQt, gui_hooks
from aqt.qt import *
from aqt.sound import allSounds, clearAudioQueue, play
from aqt.utils import (
@ -639,7 +639,7 @@ class Browser(QMainWindow):
self.pgDownCut = QShortcut(QKeySequence("Shift+End"), self)
self.pgDownCut.activated.connect(self.onLastCard)
# add-on hook
runHook("browser.setupMenus", self)
gui_hooks.browser_setup_menus_hook(self)
self.mw.maybeHideAccelerators(self)
# context menu
@ -653,8 +653,7 @@ class Browser(QMainWindow):
m.addSeparator()
for act in self.form.menu_Notes.actions():
m.addAction(act)
runHook("browser.onContextMenu", self, m)
gui_hooks.browser_context_menu_hook(self, m)
qtMenuShortcutWorkaround(m)
m.exec_(QCursor.pos())
@ -845,7 +844,7 @@ class Browser(QMainWindow):
self.editor.card = self.card
self.singleCard = True
self._updateFlagsMenu()
runHook("browser.rowChanged", self)
gui_hooks.browser_row_changed_hook(self)
self._renderPreview(True)
def refreshCurrentCard(self, note):
@ -1717,8 +1716,8 @@ where id in %s"""
play(audio)
txt = mungeQA(self.col, txt)
txt = runFilter(
"prepareQA", txt, c, "preview" + self._previewState.capitalize()
gui_hooks.card_text_filter(
txt, c, "preview" + self._previewState.capitalize()
)
self._lastPreviewState = self._previewStateAndMod()
self._updatePreviewButtons()

View file

@ -9,8 +9,10 @@ from __future__ import annotations
from typing import Any, Callable, Dict, List # pylint: disable=unused-import
import aqt
from anki.cards import Card
from anki.hooks import runFilter, runHook # pylint: disable=unused-import
from aqt.qt import QMenu
# New hook/filter handling
##############################################################################
@ -20,6 +22,132 @@ from anki.hooks import runFilter, runHook # pylint: disable=unused-import
# @@AUTOGEN@@
class BrowserContextMenuHook:
_hooks: List[Callable[["aqt.browser.Browser", QMenu], None]] = []
def append(self, cb: Callable[["aqt.browser.Browser", QMenu], None]) -> None:
"""(browser: aqt.browser.Browser, menu: QMenu)"""
self._hooks.append(cb)
def remove(self, cb: Callable[["aqt.browser.Browser", QMenu], None]) -> None:
self._hooks.remove(cb)
def __call__(self, browser: aqt.browser.Browser, menu: QMenu) -> None:
for hook in self._hooks:
try:
hook(browser, menu)
except:
# if the hook fails, remove it
self._hooks.remove(hook)
raise
# legacy support
runHook("browser.onContextMenu", browser, menu)
browser_context_menu_hook = BrowserContextMenuHook()
class BrowserRowChangedHook:
_hooks: List[Callable[["aqt.browser.Browser"], None]] = []
def append(self, cb: Callable[["aqt.browser.Browser"], None]) -> None:
"""(browser: aqt.browser.Browser)"""
self._hooks.append(cb)
def remove(self, cb: Callable[["aqt.browser.Browser"], None]) -> None:
self._hooks.remove(cb)
def __call__(self, browser: aqt.browser.Browser) -> None:
for hook in self._hooks:
try:
hook(browser)
except:
# if the hook fails, remove it
self._hooks.remove(hook)
raise
# legacy support
runHook("browser.rowChanged", browser)
browser_row_changed_hook = BrowserRowChangedHook()
class BrowserSetupMenusHook:
_hooks: List[Callable[["aqt.browser.Browser"], None]] = []
def append(self, cb: Callable[["aqt.browser.Browser"], None]) -> None:
"""(browser: aqt.browser.Browser)"""
self._hooks.append(cb)
def remove(self, cb: Callable[["aqt.browser.Browser"], None]) -> None:
self._hooks.remove(cb)
def __call__(self, browser: aqt.browser.Browser) -> None:
for hook in self._hooks:
try:
hook(browser)
except:
# if the hook fails, remove it
self._hooks.remove(hook)
raise
# legacy support
runHook("browser.setupMenus", browser)
browser_setup_menus_hook = BrowserSetupMenusHook()
class CardTextFilter:
_hooks: List[Callable[[str, Card, str], str]] = []
def append(self, cb: Callable[[str, Card, str], str]) -> None:
"""(text: str, card: Card, kind: str)"""
self._hooks.append(cb)
def remove(self, cb: Callable[[str, Card, str], str]) -> None:
self._hooks.remove(cb)
def __call__(self, text: str, card: Card, kind: str) -> str:
for filter in self._hooks:
try:
text = filter(text, card, kind)
except:
# if the hook fails, remove it
self._hooks.remove(filter)
raise
# legacy support
runFilter("prepareQA", text, card, kind)
return text
card_text_filter = CardTextFilter()
class CurrentNoteTypeChangedHook:
_hooks: List[Callable[[Dict[str, Any]], None]] = []
def append(self, cb: Callable[[Dict[str, Any]], None]) -> None:
"""(notetype: Dict[str, Any])"""
self._hooks.append(cb)
def remove(self, cb: Callable[[Dict[str, Any]], None]) -> None:
self._hooks.remove(cb)
def __call__(self, notetype: Dict[str, Any]) -> None:
for hook in self._hooks:
try:
hook(notetype)
except:
# if the hook fails, remove it
self._hooks.remove(hook)
raise
# legacy support
runHook("currentModelChanged")
current_note_type_changed_hook = CurrentNoteTypeChangedHook()
class MpvIdleHook:
_hooks: List[Callable[[], None]] = []
@ -116,4 +244,29 @@ class ReviewerShowingQuestionHook:
reviewer_showing_question_hook = ReviewerShowingQuestionHook()
class WebviewContextMenuHook:
_hooks: List[Callable[["aqt.webview.AnkiWebView", QMenu], None]] = []
def append(self, cb: Callable[["aqt.webview.AnkiWebView", QMenu], None]) -> None:
"""(webview: aqt.webview.AnkiWebView, menu: QMenu)"""
self._hooks.append(cb)
def remove(self, cb: Callable[["aqt.webview.AnkiWebView", QMenu], None]) -> None:
self._hooks.remove(cb)
def __call__(self, webview: aqt.webview.AnkiWebView, menu: QMenu) -> None:
for hook in self._hooks:
try:
hook(webview, menu)
except:
# if the hook fails, remove it
self._hooks.remove(hook)
raise
# legacy support
runHook("AnkiWebView.contextMenuEvent", webview, menu)
webview_context_menu_hook = WebviewContextMenuHook()
# @@AUTOGEN@@

View file

@ -2,8 +2,9 @@
# Copyright: Ankitects Pty Ltd and contributors
# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
from anki.hooks import addHook, remHook, runHook
from anki.hooks import addHook, remHook
from anki.lang import _
from aqt import gui_hooks
from aqt.qt import *
from aqt.utils import shortcut
@ -86,7 +87,7 @@ class ModelChooser(QHBoxLayout):
cdeck = self.deck.decks.current()
cdeck["mid"] = m["id"]
self.deck.decks.save(cdeck)
runHook("currentModelChanged")
gui_hooks.current_note_type_changed_hook(current)
self.mw.reset()
def updateModels(self):

View file

@ -5,9 +5,9 @@ import json
import math
import sys
from anki.hooks import runHook
from anki.lang import _
from anki.utils import isLin, isMac, isWin
from aqt import gui_hooks
from aqt.qt import *
from aqt.utils import openLink
@ -182,7 +182,7 @@ class AnkiWebView(QWebEngineView): # type: ignore
m = QMenu(self)
a = m.addAction(_("Copy"))
a.triggered.connect(self.onCopy)
runHook("AnkiWebView.contextMenuEvent", self, m)
gui_hooks.webview_context_menu_hook(self, m)
m.popup(QCursor.pos())
def dropEvent(self, evt):

View file

@ -27,6 +27,38 @@ hooks = [
legacy_hook="showAnswer",
legacy_no_args=True,
),
Hook(
name="current_note_type_changed",
args=["notetype: Dict[str, Any]"],
legacy_hook="currentModelChanged",
legacy_no_args=True,
),
Hook(
name="browser_setup_menus",
args=["browser: aqt.browser.Browser"],
legacy_hook="browser.setupMenus",
),
Hook(
name="browser_context_menu",
args=["browser: aqt.browser.Browser", "menu: QMenu"],
legacy_hook="browser.onContextMenu",
),
Hook(
name="browser_row_changed",
args=["browser: aqt.browser.Browser"],
legacy_hook="browser.rowChanged",
),
Hook(
name="card_text",
args=["text: str", "card: Card", "kind: str"],
return_type="str",
legacy_hook="prepareQA",
),
Hook(
name="webview_context_menu",
args=["webview: aqt.webview.AnkiWebView", "menu: QMenu"],
legacy_hook="AnkiWebView.contextMenuEvent",
),
]
if __name__ == "__main__":