mirror of
https://github.com/ankitects/anki.git
synced 2025-09-18 22:12:21 -04:00
add separate file for gui hooks
This commit is contained in:
parent
d92e27ab50
commit
4bb3d7a958
8 changed files with 222 additions and 136 deletions
|
@ -5,7 +5,7 @@ MAKEFLAGS += --warn-undefined-variables
|
|||
MAKEFLAGS += --no-builtin-rules
|
||||
RUNARGS :=
|
||||
.SUFFIXES:
|
||||
BLACKARGS := -t py36 anki tests setup.py --exclude='backend_pb2|buildinfo'
|
||||
BLACKARGS := -t py36 anki tests setup.py tools/*.py --exclude='backend_pb2|buildinfo'
|
||||
ISORTARGS := anki tests setup.py
|
||||
|
||||
$(shell mkdir -p .build ../dist)
|
||||
|
|
129
pylib/anki/hooks_gen.py
Normal file
129
pylib/anki/hooks_gen.py
Normal file
|
@ -0,0 +1,129 @@
|
|||
# Copyright: Ankitects Pty Ltd and contributors
|
||||
# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||
|
||||
"""
|
||||
Code for generating parts of hooks.py
|
||||
"""
|
||||
|
||||
import re
|
||||
from dataclasses import dataclass
|
||||
from operator import attrgetter
|
||||
from typing import List, Optional
|
||||
|
||||
|
||||
@dataclass
|
||||
class Hook:
|
||||
# the name of the hook. _filter or _hook is appending automatically.
|
||||
name: str
|
||||
# string of the typed arguments passed to the callback, eg
|
||||
# "kind: str, val: int"
|
||||
cb_args: str = ""
|
||||
# string of the return type. if set, hook is a filter.
|
||||
return_type: Optional[str] = None
|
||||
# if add-ons may be relying on the legacy hook name, add it here
|
||||
legacy_hook: Optional[str] = None
|
||||
|
||||
def callable(self) -> str:
|
||||
"Convert args into a Callable."
|
||||
types = []
|
||||
for arg in self.cb_args.split(","):
|
||||
if not arg:
|
||||
continue
|
||||
(name, type) = arg.split(":")
|
||||
types.append(type.strip())
|
||||
types_str = ", ".join(types)
|
||||
return f"Callable[[{types_str}], {self.return_type or 'None'}]"
|
||||
|
||||
def arg_names(self) -> List[str]:
|
||||
names = []
|
||||
for arg in self.cb_args.split(","):
|
||||
if not arg:
|
||||
continue
|
||||
(name, type) = arg.split(":")
|
||||
names.append(name.strip())
|
||||
return names
|
||||
|
||||
def full_name(self) -> str:
|
||||
return f"{self.name}_{self.kind()}"
|
||||
|
||||
def kind(self) -> str:
|
||||
if self.return_type is not None:
|
||||
return "filter"
|
||||
else:
|
||||
return "hook"
|
||||
|
||||
def list_code(self) -> str:
|
||||
return f"""\
|
||||
{self.full_name()}: List[{self.callable()}] = []
|
||||
"""
|
||||
|
||||
def fire_code(self) -> str:
|
||||
if self.return_type is not None:
|
||||
# filter
|
||||
return self.filter_fire_code()
|
||||
else:
|
||||
# hook
|
||||
return self.hook_fire_code()
|
||||
|
||||
def hook_fire_code(self) -> str:
|
||||
arg_names = self.arg_names()
|
||||
out = f"""\
|
||||
def run_{self.full_name()}({self.cb_args}) -> None:
|
||||
for hook in {self.full_name()}:
|
||||
try:
|
||||
hook({", ".join(arg_names)})
|
||||
except:
|
||||
# if the hook fails, remove it
|
||||
{self.full_name()}.remove(hook)
|
||||
raise
|
||||
"""
|
||||
if self.legacy_hook:
|
||||
args = ", ".join([f'"{self.legacy_hook}"'] + arg_names)
|
||||
out += f"""\
|
||||
# legacy support
|
||||
runHook({args})
|
||||
"""
|
||||
return out + "\n\n"
|
||||
|
||||
def filter_fire_code(self) -> str:
|
||||
arg_names = self.arg_names()
|
||||
out = f"""\
|
||||
def run_{self.full_name()}({self.cb_args}) -> {self.return_type}:
|
||||
for filter in {self.full_name()}:
|
||||
try:
|
||||
{arg_names[0]} = filter({", ".join(arg_names)})
|
||||
except:
|
||||
# if the hook fails, remove it
|
||||
{self.full_name()}.remove(filter)
|
||||
raise
|
||||
"""
|
||||
if self.legacy_hook:
|
||||
args = ", ".join([f'"{self.legacy_hook}"'] + arg_names)
|
||||
out += f"""\
|
||||
# legacy support
|
||||
runFilter({args})
|
||||
"""
|
||||
|
||||
out += f"""\
|
||||
return {arg_names[0]}
|
||||
"""
|
||||
return out + "\n\n"
|
||||
|
||||
|
||||
def update_file(path: str, hooks: List[Hook]):
|
||||
hooks.sort(key=attrgetter("name"))
|
||||
code = ""
|
||||
for hook in hooks:
|
||||
code += hook.list_code()
|
||||
code += "\n\n"
|
||||
for hook in hooks:
|
||||
code += hook.fire_code()
|
||||
|
||||
orig = open(path).read()
|
||||
new = re.sub(
|
||||
"(?s)# @@AUTOGEN@@.*?# @@AUTOGEN@@\n",
|
||||
f"# @@AUTOGEN@@\n\n{code}# @@AUTOGEN@@\n",
|
||||
orig,
|
||||
)
|
||||
|
||||
open(path, "wb").write(new.encode("utf8"))
|
|
@ -11,109 +11,7 @@ To add a new hook:
|
|||
"""
|
||||
|
||||
import os
|
||||
import re
|
||||
from dataclasses import dataclass
|
||||
from operator import attrgetter
|
||||
from typing import Optional, List
|
||||
|
||||
|
||||
@dataclass
|
||||
class Hook:
|
||||
# the name of the hook. _filter or _hook is appending automatically.
|
||||
name: str
|
||||
# string of the typed arguments passed to the callback, eg
|
||||
# "kind: str, val: int"
|
||||
cb_args: str = ""
|
||||
# string of the return type. if set, hook is a filter.
|
||||
return_type: Optional[str] = None
|
||||
# if add-ons may be relying on the legacy hook name, add it here
|
||||
legacy_hook: Optional[str] = None
|
||||
|
||||
def callable(self) -> str:
|
||||
"Convert args into a Callable."
|
||||
types = []
|
||||
for arg in self.cb_args.split(","):
|
||||
if not arg:
|
||||
continue
|
||||
(name, type) = arg.split(":")
|
||||
types.append(type.strip())
|
||||
types_str = ", ".join(types)
|
||||
return f"Callable[[{types_str}], {self.return_type or 'None'}]"
|
||||
|
||||
def arg_names(self) -> List[str]:
|
||||
names = []
|
||||
for arg in self.cb_args.split(","):
|
||||
if not arg:
|
||||
continue
|
||||
(name, type) = arg.split(":")
|
||||
names.append(name.strip())
|
||||
return names
|
||||
|
||||
def full_name(self) -> str:
|
||||
return f"{self.name}_{self.kind()}"
|
||||
|
||||
def kind(self) -> str:
|
||||
if self.return_type is not None:
|
||||
return "filter"
|
||||
else:
|
||||
return "hook"
|
||||
|
||||
def list_code(self) -> str:
|
||||
return f"""\
|
||||
{self.full_name()}: List[{self.callable()}] = []
|
||||
"""
|
||||
|
||||
def fire_code(self) -> str:
|
||||
if self.return_type is not None:
|
||||
# filter
|
||||
return self.filter_fire_code()
|
||||
else:
|
||||
# hook
|
||||
return self.hook_fire_code()
|
||||
|
||||
def hook_fire_code(self) -> str:
|
||||
arg_names = self.arg_names()
|
||||
out = f"""\
|
||||
def run_{self.full_name()}({self.cb_args}) -> None:
|
||||
for hook in {self.full_name()}:
|
||||
try:
|
||||
hook({", ".join(arg_names)})
|
||||
except:
|
||||
# if the hook fails, remove it
|
||||
{self.full_name()}.remove(hook)
|
||||
raise
|
||||
"""
|
||||
if self.legacy_hook:
|
||||
args = ", ".join([f'"{self.legacy_hook}"'] + arg_names)
|
||||
out += f"""\
|
||||
# legacy support
|
||||
runHook({args})
|
||||
"""
|
||||
return out + "\n\n"
|
||||
|
||||
def filter_fire_code(self) -> str:
|
||||
arg_names = self.arg_names()
|
||||
out = f"""\
|
||||
def run_{self.full_name()}({self.cb_args}) -> {self.return_type}:
|
||||
for filter in {self.full_name()}:
|
||||
try:
|
||||
{arg_names[0]} = filter({", ".join(arg_names)})
|
||||
except:
|
||||
# if the hook fails, remove it
|
||||
{self.full_name()}.remove(filter)
|
||||
raise
|
||||
"""
|
||||
if self.legacy_hook:
|
||||
args = ", ".join([f'"{self.legacy_hook}"'] + arg_names)
|
||||
out += f"""\
|
||||
# legacy support
|
||||
runFilter({args})
|
||||
"""
|
||||
|
||||
out += f"""\
|
||||
return {arg_names[0]}
|
||||
"""
|
||||
return out + "\n\n"
|
||||
from anki.hooks_gen import Hook, update_file
|
||||
|
||||
# Hook list
|
||||
######################################################################
|
||||
|
@ -121,26 +19,9 @@ def run_{self.full_name()}({self.cb_args}) -> {self.return_type}:
|
|||
hooks = [
|
||||
Hook(name="leech", cb_args="card: Card", legacy_hook="leech"),
|
||||
Hook(name="odue_invalid"),
|
||||
Hook(name="mod_schema", cb_args="proceed: bool", return_type="bool")
|
||||
Hook(name="mod_schema", cb_args="proceed: bool", return_type="bool"),
|
||||
]
|
||||
|
||||
hooks.sort(key=attrgetter("name"))
|
||||
|
||||
######################################################################
|
||||
|
||||
tools_dir = os.path.dirname(__file__)
|
||||
hooks_py = os.path.join(tools_dir, "..", "anki", "hooks.py")
|
||||
|
||||
code = ""
|
||||
for hook in hooks:
|
||||
code += hook.list_code()
|
||||
code += "\n\n"
|
||||
for hook in hooks:
|
||||
code += hook.fire_code()
|
||||
|
||||
orig = open(hooks_py).read()
|
||||
new = re.sub("(?s)# @@AUTOGEN@@.*?# @@AUTOGEN@@\n", f"# @@AUTOGEN@@\n\n{code}# @@AUTOGEN@@\n", orig)
|
||||
|
||||
open(hooks_py, "wb").write(new.encode("utf8"))
|
||||
|
||||
print("Updated hooks.py")
|
||||
if __name__ == "__main__":
|
||||
path = os.path.join(os.path.dirname(__file__), "..", "anki", "hooks.py")
|
||||
update_file(path, hooks)
|
||||
|
|
|
@ -5,7 +5,7 @@ MAKEFLAGS += --warn-undefined-variables
|
|||
MAKEFLAGS += --no-builtin-rules
|
||||
.SUFFIXES:
|
||||
|
||||
BLACKARGS := -t py36 aqt tests setup.py --exclude='aqt/forms|buildinfo'
|
||||
BLACKARGS := -t py36 aqt tests setup.py tools/*.py --exclude='aqt/forms|buildinfo'
|
||||
ISORTARGS := aqt tests setup.py
|
||||
|
||||
$(shell mkdir -p .build ../dist)
|
||||
|
@ -35,7 +35,11 @@ TSDEPS := $(wildcard ts/src/*.ts)
|
|||
(cd ts && npm i && npm run build)
|
||||
@touch $@
|
||||
|
||||
BUILD_STEPS := .build/run-deps .build/dev-deps .build/js .build/ui .build/i18n aqt/buildinfo.py
|
||||
.build/hooks: tools/genhooks.py
|
||||
python tools/genhooks.py
|
||||
@touch $@
|
||||
|
||||
BUILD_STEPS := .build/run-deps .build/dev-deps .build/js .build/ui .build/hooks .build/i18n aqt/buildinfo.py
|
||||
|
||||
# Checking
|
||||
######################
|
||||
|
|
47
qt/aqt/gui_hooks.py
Normal file
47
qt/aqt/gui_hooks.py
Normal file
|
@ -0,0 +1,47 @@
|
|||
# Copyright: Ankitects Pty Ltd and contributors
|
||||
# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||
|
||||
"""
|
||||
See pylib/anki/hooks.py
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any, Callable, Dict, List # pylint: disable=unused-import
|
||||
|
||||
from anki.hooks import runFilter, runHook # pylint: disable=unused-import
|
||||
|
||||
# New hook/filter handling
|
||||
##############################################################################
|
||||
# The code in this section is automatically generated - any edits you make
|
||||
# will be lost. To add new hooks, see ../tools/genhooks.py
|
||||
#
|
||||
# @@AUTOGEN@@
|
||||
|
||||
mpv_idle_hook: List[Callable[[], None]] = []
|
||||
mpv_will_play_hook: List[Callable[[str], None]] = []
|
||||
|
||||
|
||||
def run_mpv_idle_hook() -> None:
|
||||
for hook in mpv_idle_hook:
|
||||
try:
|
||||
hook()
|
||||
except:
|
||||
# if the hook fails, remove it
|
||||
mpv_idle_hook.remove(hook)
|
||||
raise
|
||||
|
||||
|
||||
def run_mpv_will_play_hook(file: str) -> None:
|
||||
for hook in mpv_will_play_hook:
|
||||
try:
|
||||
hook(file)
|
||||
except:
|
||||
# if the hook fails, remove it
|
||||
mpv_will_play_hook.remove(hook)
|
||||
raise
|
||||
# legacy support
|
||||
runHook("mpvWillPlay", file)
|
||||
|
||||
|
||||
# @@AUTOGEN@@
|
|
@ -30,6 +30,7 @@ from anki.hooks import addHook, runFilter, runHook
|
|||
from anki.lang import _, ngettext
|
||||
from anki.storage import Collection
|
||||
from anki.utils import devMode, ids2str, intTime, isMac, isWin, splitFields
|
||||
from aqt import gui_hooks
|
||||
from aqt.profiles import ProfileManager as ProfileManagerType
|
||||
from aqt.qt import *
|
||||
from aqt.qt import sip
|
||||
|
@ -1158,9 +1159,10 @@ Difference to correct time: %s."""
|
|||
addHook("remNotes", self.onRemNotes)
|
||||
hooks.odue_invalid_hook.append(self.onOdueInvalid)
|
||||
|
||||
addHook("mpvWillPlay", self.onMpvWillPlay)
|
||||
addHook("mpvIdleHook", self.onMpvIdle)
|
||||
self._activeWindowOnPlay = None
|
||||
gui_hooks.mpv_will_play_hook.append(self.on_mpv_will_play)
|
||||
gui_hooks.mpv_idle_hook.append(self.on_mpv_idle)
|
||||
|
||||
self._activeWindowOnPlay: Optional[QWidget] = None
|
||||
|
||||
def onOdueInvalid(self):
|
||||
showWarning(
|
||||
|
@ -1175,13 +1177,13 @@ and if the problem comes up again, please ask on the support site."""
|
|||
head, ext = os.path.splitext(file.lower())
|
||||
return ext in (".mp4", ".mov", ".mpg", ".mpeg", ".mkv", ".avi")
|
||||
|
||||
def onMpvWillPlay(self, file):
|
||||
def on_mpv_will_play(self, file: str) -> None:
|
||||
if not self._isVideo(file):
|
||||
return
|
||||
|
||||
self._activeWindowOnPlay = self.app.activeWindow() or self._activeWindowOnPlay
|
||||
|
||||
def onMpvIdle(self):
|
||||
def on_mpv_idle(self) -> None:
|
||||
w = self._activeWindowOnPlay
|
||||
if not self.app.activeWindow() and w and not sip.isdeleted(w) and w.isVisible():
|
||||
w.activateWindow()
|
||||
|
|
|
@ -11,10 +11,11 @@ import threading
|
|||
import time
|
||||
from typing import Any, Callable, Dict, List, Optional, Tuple
|
||||
|
||||
from anki.hooks import addHook, runHook
|
||||
from anki.hooks import addHook
|
||||
from anki.lang import _
|
||||
from anki.sound import allSounds
|
||||
from anki.utils import isLin, isMac, isWin, tmpdir
|
||||
from aqt import gui_hooks
|
||||
from aqt.mpv import MPV, MPVBase
|
||||
from aqt.qt import *
|
||||
from aqt.utils import restoreGeom, saveGeom, showWarning
|
||||
|
@ -156,8 +157,8 @@ class MpvManager(MPV):
|
|||
def __init__(self) -> None:
|
||||
super().__init__(window_id=None, debug=False)
|
||||
|
||||
def queueFile(self, file) -> None:
|
||||
runHook("mpvWillPlay", file)
|
||||
def queueFile(self, file: str) -> None:
|
||||
gui_hooks.run_mpv_will_play_hook(file)
|
||||
|
||||
path = os.path.join(os.getcwd(), file)
|
||||
self.command("loadfile", path, "append-play")
|
||||
|
@ -172,7 +173,7 @@ class MpvManager(MPV):
|
|||
self.command("seek", secs, "relative")
|
||||
|
||||
def on_idle(self) -> None:
|
||||
runHook("mpvIdleHook")
|
||||
gui_hooks.run_mpv_idle_hook()
|
||||
|
||||
|
||||
def setMpvConfigBase(base) -> None:
|
||||
|
|
22
qt/tools/genhooks.py
Normal file
22
qt/tools/genhooks.py
Normal file
|
@ -0,0 +1,22 @@
|
|||
# Copyright: Ankitects Pty Ltd and contributors
|
||||
# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||
|
||||
"""
|
||||
See pylib/tools/genhooks.py for more info.
|
||||
"""
|
||||
|
||||
import os
|
||||
|
||||
from anki.hooks_gen import Hook, update_file
|
||||
|
||||
# Hook list
|
||||
######################################################################
|
||||
|
||||
hooks = [
|
||||
Hook(name="mpv_idle"),
|
||||
Hook(name="mpv_will_play", cb_args="file: str", legacy_hook="mpvWillPlay"),
|
||||
]
|
||||
|
||||
if __name__ == "__main__":
|
||||
path = os.path.join(os.path.dirname(__file__), "..", "aqt", "gui_hooks.py")
|
||||
update_file(path, hooks)
|
Loading…
Reference in a new issue