anki/qt/tools/genhooks_gui.py

358 lines
12 KiB
Python
Raw Normal View History

2020-01-13 05:38:05 +01:00
# 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
import sys
2020-01-13 05:38:05 +01:00
pylib = os.path.join(os.path.dirname(__file__), "..", "..", "pylib")
sys.path.append(pylib)
from tools.hookslib import Hook, update_file
2020-01-13 05:38:05 +01:00
# Hook list
######################################################################
hooks = [
2020-01-15 04:03:11 +01:00
# Reviewing
###################
Hook(
name="overview_did_refresh",
args=["overview: aqt.overview.Overview"],
doc="""Allow to update the overview window. E.g. add the deck name in the
title.""",
),
Hook(
name="deck_browser_did_render",
args=["deck_browser: aqt.deckbrowser.DeckBrowser"],
doc="""Allow to update the deck browser window. E.g. change its title.""",
),
Hook(
name="reviewer_did_show_question",
args=["card: Card"],
legacy_hook="showQuestion",
legacy_no_args=True,
),
Hook(
name="reviewer_did_show_answer",
args=["card: Card"],
legacy_hook="showAnswer",
legacy_no_args=True,
),
Hook(
name="reviewer_will_answer_card",
args=[
"ease_tuple: Tuple[bool, int]",
2020-01-24 15:48:05 +01:00
"reviewer: aqt.reviewer.Reviewer",
"card: Card",
],
return_type="Tuple[bool, int]",
doc="""Used to modify the ease at which a card is rated or to bypass
rating the card completely.
ease_tuple is a tuple consisting of a boolean expressing whether the reviewer
should continue with rating the card, and an integer expressing the ease at
which the card should be rated.
If your code just needs to be notified of the card rating event, you should use
the reviewer_did_answer_card hook instead.""",
),
Hook(
name="reviewer_did_answer_card",
args=["reviewer: aqt.reviewer.Reviewer", "card: Card", "ease: int"],
),
Hook(
name="reviewer_will_show_context_menu",
2020-01-15 04:03:11 +01:00
args=["reviewer: aqt.reviewer.Reviewer", "menu: QMenu"],
legacy_hook="Reviewer.contextMenuEvent",
),
Hook(
name="reviewer_will_end",
legacy_hook="reviewCleanup",
doc="Called before Anki transitions from the review screen to another screen.",
),
Hook(
name="card_will_show",
2020-01-15 04:03:11 +01:00
args=["text: str", "card: Card", "kind: str"],
return_type="str",
legacy_hook="prepareQA",
doc="Can modify card text before review/preview.",
),
2020-01-15 04:03:11 +01:00
# Browser
###################
Hook(
name="browser_menus_did_init",
args=["browser: aqt.browser.Browser"],
legacy_hook="browser.setupMenus",
),
Hook(
name="browser_will_show_context_menu",
args=["browser: aqt.browser.Browser", "menu: QMenu"],
legacy_hook="browser.onContextMenu",
),
Hook(
name="browser_did_change_row",
args=["browser: aqt.browser.Browser"],
legacy_hook="browser.rowChanged",
),
Hook(
name="browser_will_build_tree",
args=[
"handled: bool",
"tree: aqt.browser.SidebarItem",
"stage: aqt.browser.SidebarStage",
"browser: aqt.browser.Browser",
],
return_type="bool",
doc="""Used to add or replace items in the browser sidebar tree
'tree' is the root SidebarItem that all other items are added to.
'stage' is an enum describing the different construction stages of
the sidebar tree at which you can interject your changes.
The different values can be inspected by looking at
aqt.browser.SidebarStage.
If you want Anki to proceed with the construction of the tree stage
in question after your have performed your changes or additions,
return the 'handled' boolean unchanged.
On the other hand, if you want to prevent Anki from adding its own
items at a particular construction stage (e.g. in case your add-on
implements its own version of that particular stage), return 'True'.
If you return 'True' at SidebarStage.ROOT, the sidebar will not be
populated by any of the other construction stages. For any other stage
the tree construction will just continue as usual.
For example, if your code wishes to replace the tag tree, you could do:
def on_browser_will_build_tree(handled, root, stage, browser):
if stage != SidebarStage.TAGS:
# not at tag tree building stage, pass on
return handled
# your tag tree construction code
# root.addChild(...)
# your code handled tag tree construction, no need for Anki
# or other add-ons to build the tag tree
return True
""",
),
2020-01-15 04:03:11 +01:00
# States
###################
Hook(
name="state_will_change",
args=["new_state: str", "old_state: str"],
legacy_hook="beforeStateChange",
),
Hook(
name="state_did_change",
args=["new_state: str", "old_state: str"],
legacy_hook="afterStateChange",
),
# different sig to original
Hook(
name="state_shortcuts_will_change",
args=["state: str", "shortcuts: List[Tuple[str, Callable]]"],
),
Hook(
name="state_did_revert",
args=["action: str"],
legacy_hook="revertedState",
doc="Called when user used the undo option to restore to an earlier database state.",
),
Hook(
name="state_did_reset",
legacy_hook="reset",
doc="Called when the interface needs to be redisplayed after non-trivial changes have been made.",
),
# Webview
###################
Hook(
name="webview_did_receive_js_message",
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().
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).
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_set_content",
args=["web_content: aqt.webview.WebContent", "context: Optional[Any]",],
doc="""Used to modify web content before it is rendered.
Web_content contains the HTML, JS, and CSS the web view will be
populated with.
Context is the instance that was passed to stdHtml().
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:
def on_webview_will_set_content(web_content: WebContent, context):
if not isinstance(context, aqt.reviewer.Reviewer):
# not reviewer, do not modify content
return
# reviewer, perform changes to content
context: aqt.reviewer.Reviewer
addon_package = mw.addonManager.addonFromModule(__name__)
web_content.css.append(
f"/_addons/{addon_package}/web/my-addon.css")
web_content.js.append(
f"/_addons/{addon_package}/web/my-addon.js")
web_content.head += "<script>console.log('my-addon')</script>"
web_content.body += "<div id='my-addon'></div>"
""",
),
Hook(
name="webview_will_show_context_menu",
args=["webview: aqt.webview.AnkiWebView", "menu: QMenu"],
legacy_hook="AnkiWebView.contextMenuEvent",
),
2020-01-15 04:03:11 +01:00
# Main
###################
Hook(name="profile_did_open", legacy_hook="profileLoaded"),
Hook(name="profile_will_close", legacy_hook="unloadProfile"),
Hook(
name="collection_did_load",
args=["col: anki.storage._Collection"],
legacy_hook="colLoading",
),
Hook(
name="undo_state_did_change", args=["can_undo: bool"], legacy_hook="undoState"
),
Hook(name="review_did_undo", args=["card_id: int"], legacy_hook="revertedCard"),
Hook(
name="style_did_init",
args=["style: str"],
return_type="str",
legacy_hook="setupStyle",
),
Hook(
name="media_sync_did_progress", args=["entry: aqt.mediasync.LogEntryWithTime"],
),
2020-01-15 04:03:11 +01:00
# Adding cards
###################
Hook(
name="add_cards_will_show_history_menu",
args=["addcards: aqt.addcards.AddCards", "menu: QMenu"],
legacy_hook="AddCards.onHistory",
),
Hook(
name="add_cards_did_add_note",
args=["note: anki.notes.Note"],
legacy_hook="AddCards.noteAdded",
),
2020-01-15 04:03:11 +01:00
# Editing
###################
Hook(
name="editor_did_init_buttons",
args=["buttons: List", "editor: aqt.editor.Editor"],
),
Hook(
name="editor_did_init_shortcuts",
args=["shortcuts: List[Tuple]", "editor: aqt.editor.Editor"],
legacy_hook="setupEditorShortcuts",
),
Hook(
name="editor_will_show_context_menu",
args=["editor_webview: aqt.editor.EditorWebView", "menu: QMenu"],
legacy_hook="EditorWebView.contextMenuEvent",
),
Hook(
name="editor_did_fire_typing_timer",
args=["note: anki.notes.Note"],
legacy_hook="editTimer",
),
Hook(
name="editor_did_focus_field",
args=["note: anki.notes.Note", "current_field_idx: int"],
legacy_hook="editFocusGained",
),
Hook(
name="editor_did_unfocus_field",
args=["changed: bool", "note: anki.notes.Note", "current_field_idx: int"],
return_type="bool",
legacy_hook="editFocusLost",
),
Hook(
name="editor_did_load_note",
args=["editor: aqt.editor.Editor"],
legacy_hook="loadNote",
),
Hook(
name="editor_did_update_tags",
args=["note: anki.notes.Note"],
legacy_hook="tagsUpdated",
),
Hook(
name="editor_will_use_font_for_field",
args=["font: str"],
return_type="str",
legacy_hook="mungeEditingFontName",
),
# Sound/video
###################
Hook(name="av_player_will_play", args=["tag: anki.sound.AVTag"]),
2020-01-22 05:39:18 +01:00
Hook(
name="av_player_did_begin_playing",
args=["player: aqt.sound.Player", "tag: anki.sound.AVTag"],
),
Hook(name="av_player_did_end_playing", args=["player: aqt.sound.Player"]),
2020-01-15 04:03:11 +01:00
# Other
###################
Hook(
name="current_note_type_did_change",
args=["notetype: Dict[str, Any]"],
legacy_hook="currentModelChanged",
legacy_no_args=True,
),
Hook(
name="deck_browser_will_show_options_menu",
2020-01-15 04:03:11 +01:00
args=["menu: QMenu", "deck_id: int"],
legacy_hook="showDeckOptions",
),
2020-01-13 05:38:05 +01:00
]
if __name__ == "__main__":
path = os.path.join(os.path.dirname(__file__), "..", "aqt", "gui_hooks.py")
update_file(path, hooks)