mirror of
https://github.com/ankitects/anki.git
synced 2025-12-20 10:22:57 -05:00
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:
parent
2921037c7b
commit
2fa662f7ae
7 changed files with 210 additions and 19 deletions
|
|
@ -355,13 +355,17 @@ prepare_searches_hook = PrepareSearchesHook()
|
||||||
|
|
||||||
|
|
||||||
class RemoveNotesHook:
|
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])"""
|
"""(col: anki.storage._Collection, ids: List[int])"""
|
||||||
self._hooks.append(cb)
|
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)
|
self._hooks.remove(cb)
|
||||||
|
|
||||||
def __call__(self, col: anki.storage._Collection, ids: List[int]) -> None:
|
def __call__(self, col: anki.storage._Collection, ids: List[int]) -> None:
|
||||||
|
|
@ -388,7 +392,7 @@ class RenderedCardTemplateFilter:
|
||||||
Dict[str, str],
|
Dict[str, str],
|
||||||
Dict[str, Any],
|
Dict[str, Any],
|
||||||
QAData,
|
QAData,
|
||||||
anki.storage._Collection,
|
"anki.storage._Collection",
|
||||||
],
|
],
|
||||||
str,
|
str,
|
||||||
]
|
]
|
||||||
|
|
@ -403,7 +407,7 @@ class RenderedCardTemplateFilter:
|
||||||
Dict[str, str],
|
Dict[str, str],
|
||||||
Dict[str, Any],
|
Dict[str, Any],
|
||||||
QAData,
|
QAData,
|
||||||
anki.storage._Collection,
|
"anki.storage._Collection",
|
||||||
],
|
],
|
||||||
str,
|
str,
|
||||||
],
|
],
|
||||||
|
|
@ -420,7 +424,7 @@ class RenderedCardTemplateFilter:
|
||||||
Dict[str, str],
|
Dict[str, str],
|
||||||
Dict[str, Any],
|
Dict[str, Any],
|
||||||
QAData,
|
QAData,
|
||||||
anki.storage._Collection,
|
"anki.storage._Collection",
|
||||||
],
|
],
|
||||||
str,
|
str,
|
||||||
],
|
],
|
||||||
|
|
|
||||||
|
|
@ -32,7 +32,9 @@ class Hook:
|
||||||
types = []
|
types = []
|
||||||
for arg in self.args or []:
|
for arg in self.args or []:
|
||||||
(name, type) = arg.split(":")
|
(name, type) = arg.split(":")
|
||||||
types.append(type.strip())
|
if "." in type:
|
||||||
|
type = '"' + type.strip() + '"'
|
||||||
|
types.append(type)
|
||||||
types_str = ", ".join(types)
|
types_str = ", ".join(types)
|
||||||
return f"Callable[[{types_str}], {self.return_type or 'None'}]"
|
return f"Callable[[{types_str}], {self.return_type or 'None'}]"
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@ import anki
|
||||||
import aqt.forms
|
import aqt.forms
|
||||||
from anki.collection import _Collection
|
from anki.collection import _Collection
|
||||||
from anki.consts import *
|
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.lang import _, ngettext
|
||||||
from anki.utils import (
|
from anki.utils import (
|
||||||
bodyClass,
|
bodyClass,
|
||||||
|
|
@ -26,7 +26,7 @@ from anki.utils import (
|
||||||
isMac,
|
isMac,
|
||||||
isWin,
|
isWin,
|
||||||
)
|
)
|
||||||
from aqt import AnkiQt
|
from aqt import AnkiQt, gui_hooks
|
||||||
from aqt.qt import *
|
from aqt.qt import *
|
||||||
from aqt.sound import allSounds, clearAudioQueue, play
|
from aqt.sound import allSounds, clearAudioQueue, play
|
||||||
from aqt.utils import (
|
from aqt.utils import (
|
||||||
|
|
@ -639,7 +639,7 @@ class Browser(QMainWindow):
|
||||||
self.pgDownCut = QShortcut(QKeySequence("Shift+End"), self)
|
self.pgDownCut = QShortcut(QKeySequence("Shift+End"), self)
|
||||||
self.pgDownCut.activated.connect(self.onLastCard)
|
self.pgDownCut.activated.connect(self.onLastCard)
|
||||||
# add-on hook
|
# add-on hook
|
||||||
runHook("browser.setupMenus", self)
|
gui_hooks.browser_setup_menus_hook(self)
|
||||||
self.mw.maybeHideAccelerators(self)
|
self.mw.maybeHideAccelerators(self)
|
||||||
|
|
||||||
# context menu
|
# context menu
|
||||||
|
|
@ -653,8 +653,7 @@ class Browser(QMainWindow):
|
||||||
m.addSeparator()
|
m.addSeparator()
|
||||||
for act in self.form.menu_Notes.actions():
|
for act in self.form.menu_Notes.actions():
|
||||||
m.addAction(act)
|
m.addAction(act)
|
||||||
runHook("browser.onContextMenu", self, m)
|
gui_hooks.browser_context_menu_hook(self, m)
|
||||||
|
|
||||||
qtMenuShortcutWorkaround(m)
|
qtMenuShortcutWorkaround(m)
|
||||||
m.exec_(QCursor.pos())
|
m.exec_(QCursor.pos())
|
||||||
|
|
||||||
|
|
@ -845,7 +844,7 @@ class Browser(QMainWindow):
|
||||||
self.editor.card = self.card
|
self.editor.card = self.card
|
||||||
self.singleCard = True
|
self.singleCard = True
|
||||||
self._updateFlagsMenu()
|
self._updateFlagsMenu()
|
||||||
runHook("browser.rowChanged", self)
|
gui_hooks.browser_row_changed_hook(self)
|
||||||
self._renderPreview(True)
|
self._renderPreview(True)
|
||||||
|
|
||||||
def refreshCurrentCard(self, note):
|
def refreshCurrentCard(self, note):
|
||||||
|
|
@ -1717,8 +1716,8 @@ where id in %s"""
|
||||||
play(audio)
|
play(audio)
|
||||||
|
|
||||||
txt = mungeQA(self.col, txt)
|
txt = mungeQA(self.col, txt)
|
||||||
txt = runFilter(
|
gui_hooks.card_text_filter(
|
||||||
"prepareQA", txt, c, "preview" + self._previewState.capitalize()
|
txt, c, "preview" + self._previewState.capitalize()
|
||||||
)
|
)
|
||||||
self._lastPreviewState = self._previewStateAndMod()
|
self._lastPreviewState = self._previewStateAndMod()
|
||||||
self._updatePreviewButtons()
|
self._updatePreviewButtons()
|
||||||
|
|
|
||||||
|
|
@ -9,8 +9,10 @@ from __future__ import annotations
|
||||||
|
|
||||||
from typing import Any, Callable, Dict, List # pylint: disable=unused-import
|
from typing import Any, Callable, Dict, List # pylint: disable=unused-import
|
||||||
|
|
||||||
|
import aqt
|
||||||
from anki.cards import Card
|
from anki.cards import Card
|
||||||
from anki.hooks import runFilter, runHook # pylint: disable=unused-import
|
from anki.hooks import runFilter, runHook # pylint: disable=unused-import
|
||||||
|
from aqt.qt import QMenu
|
||||||
|
|
||||||
# New hook/filter handling
|
# New hook/filter handling
|
||||||
##############################################################################
|
##############################################################################
|
||||||
|
|
@ -20,6 +22,132 @@ from anki.hooks import runFilter, runHook # pylint: disable=unused-import
|
||||||
# @@AUTOGEN@@
|
# @@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:
|
class MpvIdleHook:
|
||||||
_hooks: List[Callable[[], None]] = []
|
_hooks: List[Callable[[], None]] = []
|
||||||
|
|
||||||
|
|
@ -116,4 +244,29 @@ class ReviewerShowingQuestionHook:
|
||||||
|
|
||||||
|
|
||||||
reviewer_showing_question_hook = 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@@
|
# @@AUTOGEN@@
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,9 @@
|
||||||
# Copyright: Ankitects Pty Ltd and contributors
|
# Copyright: Ankitects Pty Ltd and contributors
|
||||||
# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
# 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 anki.lang import _
|
||||||
|
from aqt import gui_hooks
|
||||||
from aqt.qt import *
|
from aqt.qt import *
|
||||||
from aqt.utils import shortcut
|
from aqt.utils import shortcut
|
||||||
|
|
||||||
|
|
@ -86,7 +87,7 @@ class ModelChooser(QHBoxLayout):
|
||||||
cdeck = self.deck.decks.current()
|
cdeck = self.deck.decks.current()
|
||||||
cdeck["mid"] = m["id"]
|
cdeck["mid"] = m["id"]
|
||||||
self.deck.decks.save(cdeck)
|
self.deck.decks.save(cdeck)
|
||||||
runHook("currentModelChanged")
|
gui_hooks.current_note_type_changed_hook(current)
|
||||||
self.mw.reset()
|
self.mw.reset()
|
||||||
|
|
||||||
def updateModels(self):
|
def updateModels(self):
|
||||||
|
|
|
||||||
|
|
@ -5,9 +5,9 @@ import json
|
||||||
import math
|
import math
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
from anki.hooks import runHook
|
|
||||||
from anki.lang import _
|
from anki.lang import _
|
||||||
from anki.utils import isLin, isMac, isWin
|
from anki.utils import isLin, isMac, isWin
|
||||||
|
from aqt import gui_hooks
|
||||||
from aqt.qt import *
|
from aqt.qt import *
|
||||||
from aqt.utils import openLink
|
from aqt.utils import openLink
|
||||||
|
|
||||||
|
|
@ -182,7 +182,7 @@ class AnkiWebView(QWebEngineView): # type: ignore
|
||||||
m = QMenu(self)
|
m = QMenu(self)
|
||||||
a = m.addAction(_("Copy"))
|
a = m.addAction(_("Copy"))
|
||||||
a.triggered.connect(self.onCopy)
|
a.triggered.connect(self.onCopy)
|
||||||
runHook("AnkiWebView.contextMenuEvent", self, m)
|
gui_hooks.webview_context_menu_hook(self, m)
|
||||||
m.popup(QCursor.pos())
|
m.popup(QCursor.pos())
|
||||||
|
|
||||||
def dropEvent(self, evt):
|
def dropEvent(self, evt):
|
||||||
|
|
|
||||||
|
|
@ -27,6 +27,38 @@ hooks = [
|
||||||
legacy_hook="showAnswer",
|
legacy_hook="showAnswer",
|
||||||
legacy_no_args=True,
|
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__":
|
if __name__ == "__main__":
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue