/
genhooks_gui.py
1021 lines (941 loc) · 35.3 KB
/
genhooks_gui.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
# 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
from hookslib import Hook, write_file
prefix = """\
# Copyright: Ankitects Pty Ltd and contributors
# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
# This file is automatically generated; edit tools/genhooks_gui.py instead.
# Please import from anki.hooks instead of this file.
from __future__ import annotations
from typing import Any, Callable, Sequence, Literal
import anki
import aqt
from anki.cards import Card
from anki.decks import DeckDict, DeckConfigDict
from anki.hooks import runFilter, runHook
from anki.models import NotetypeDict
from anki.collection import OpChangesAfterUndo
from aqt.qt import QDialog, QEvent, QMenu, QModelIndex, QWidget, QMimeData
from aqt.tagedit import TagEdit
from aqt.undo import UndoActionsInfo
"""
# Hook list
######################################################################
hooks = [
# 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="overview_will_render_content",
args=[
"overview: aqt.overview.Overview",
"content: aqt.overview.OverviewContent",
],
doc="""Used to modify HTML content sections in the overview body
'content' contains the sections of HTML content the overview body
will be updated with.
When modifying the content of a particular section, please make sure your
changes only perform the minimum required edits to make your add-on work.
You should avoid overwriting or interfering with existing data as much
as possible, instead opting to append your own changes, e.g.:
def on_overview_will_render_content(overview, content):
content.table += "\n<div>my html</div>"
""",
),
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_init_answer_buttons",
args=[
"buttons_tuple: tuple[tuple[int, str], ...]",
"reviewer: aqt.reviewer.Reviewer",
"card: Card",
],
return_type="tuple[tuple[int, str], ...]",
doc="""Used to modify list of answer buttons
buttons_tuple is a tuple of buttons, with each button represented by a
tuple containing an int for the button's ease and a string for the
button's label.
Return a tuple of the form ((int, str), ...), e.g.:
((1, "Label1"), (2, "Label2"), ...)
Note: import _ from anki.lang to support translation, using, e.g.,
((1, _("Label1")), ...)
""",
),
Hook(
name="reviewer_will_answer_card",
args=[
"ease_tuple: tuple[bool, Literal[1, 2, 3, 4]]",
"reviewer: aqt.reviewer.Reviewer",
"card: Card",
],
return_type="tuple[bool, Literal[1, 2, 3, 4]]",
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: Literal[1, 2, 3, 4]",
],
),
Hook(
name="reviewer_will_show_context_menu",
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="reviewer_will_play_question_sounds",
args=["card: Card", "tags: list[anki.sound.AVTag]"],
doc="""Called before showing the question/front side.
`tags` can be used to inspect and manipulate the sounds
that will be played (if any).
This won't be called when the user manually plays sounds
using `Replay Audio`.
Note that this hook is called even when the `Automatically play audio`
option is unchecked; This is so as to allow playing custom
sounds regardless of that option.""",
),
Hook(
name="reviewer_will_play_answer_sounds",
args=["card: Card", "tags: list[anki.sound.AVTag]"],
doc="""Called before showing the answer/back side.
`tags` can be used to inspect and manipulate the sounds
that will be played (if any).
This won't be called when the user manually plays sounds
using `Replay Audio`.
Note that this hook is called even when the `Automatically play audio`
option is unchecked; This is so as to allow playing custom
sounds regardless of that option.""",
),
# Debug
###################
Hook(
name="debug_console_will_show",
args=["debug_window: QDialog"],
doc="""Allows editing the debug window. E.g. setting a default code, or
previous code.""",
),
Hook(
name="debug_console_did_evaluate_python",
args=["output: str", "query: str", "debug_window: aqt.forms.debug.Ui_Dialog"],
return_type="str",
doc="""Allows processing the debug result. E.g. logging queries and
result, saving last query to display it later...""",
),
# Card layout
###################
Hook(
name="card_layout_will_show",
args=["clayout: aqt.clayout.CardLayout"],
doc="""Allow to change the display of the card layout. After most values are
set and before the window is actually shown.""",
),
# Multiple windows
###################
# reviewer, clayout and browser
Hook(
name="card_will_show",
args=["text: str", "card: Card", "kind: str"],
return_type="str",
legacy_hook="prepareQA",
doc="Can modify card text before review/preview.",
),
# Deck browser
###################
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="deck_browser_will_render_content",
args=[
"deck_browser: aqt.deckbrowser.DeckBrowser",
"content: aqt.deckbrowser.DeckBrowserContent",
],
doc="""Used to modify HTML content sections in the deck browser body
'content' contains the sections of HTML content the deck browser body
will be updated with.
When modifying the content of a particular section, please make sure your
changes only perform the minimum required edits to make your add-on work.
You should avoid overwriting or interfering with existing data as much
as possible, instead opting to append your own changes, e.g.:
def on_deck_browser_will_render_content(deck_browser, content):
content.stats += "\n<div>my html</div>"
""",
),
# Deck options (legacy screen)
###############################
Hook(
name="deck_conf_did_setup_ui_form",
args=["deck_conf: aqt.deckconf.DeckConf"],
doc="Allows modifying or adding widgets in the deck options UI form",
),
Hook(
name="deck_conf_will_show",
args=["deck_conf: aqt.deckconf.DeckConf"],
doc="Allows modifying the deck options dialog before it is shown",
),
Hook(
name="deck_conf_did_load_config",
args=[
"deck_conf: aqt.deckconf.DeckConf",
"deck: DeckDict",
"config: DeckConfigDict",
],
doc="Called once widget state has been set from deck config",
),
Hook(
name="deck_conf_will_save_config",
args=[
"deck_conf: aqt.deckconf.DeckConf",
"deck: DeckDict",
"config: DeckConfigDict",
],
doc="Called before widget state is saved to config",
),
Hook(
name="deck_conf_did_add_config",
args=[
"deck_conf: aqt.deckconf.DeckConf",
"deck: DeckDict",
"config: DeckConfigDict",
"new_name: str",
"new_conf_id: int",
],
doc="""Allows modification of a newly created config group
This hook is called after the config group was created, but
before initializing the widget state.
`deck_conf` will point to the old config group, `new_conf_id` will
point to the newly created config group.
Config groups are created as clones of the current one.
""",
),
Hook(
name="deck_conf_will_remove_config",
args=[
"deck_conf: aqt.deckconf.DeckConf",
"deck: DeckDict",
"config: DeckConfigDict",
],
doc="Called before current config group is removed",
),
Hook(
name="deck_conf_will_rename_config",
args=[
"deck_conf: aqt.deckconf.DeckConf",
"deck: DeckDict",
"config: DeckConfigDict",
"new_name: str",
],
doc="Called before config group is renamed",
),
# Deck options (new screen)
############################
Hook(
name="deck_options_did_load",
args=[
"deck_options: aqt.deckoptions.DeckOptionsDialog",
],
doc="""Can be used to inject extra options into the config screen.
See the example add-ons at:
https://github.com/ankitects/anki-addons/tree/main/demos/deckoptions_svelte
https://github.com/ankitects/anki-addons/tree/main/demos/deckoptions_raw_html
""",
),
# Filtered deck options
###################
Hook(
name="filtered_deck_dialog_did_load_deck",
args=[
"filtered_deck_dialog: aqt.filtered_deck.FilteredDeckConfigDialog",
"filtered_deck: anki.scheduler.FilteredDeckForUpdate",
],
doc="Allows updating widget state once the filtered deck config is loaded",
),
Hook(
name="filtered_deck_dialog_will_add_or_update_deck",
args=[
"filtered_deck_dialog: aqt.filtered_deck.FilteredDeckConfigDialog",
"filtered_deck: anki.scheduler.FilteredDeckForUpdate",
],
doc="Allows modifying the filtered deck config object before it is written",
),
Hook(
name="filtered_deck_dialog_did_add_or_update_deck",
args=[
"filtered_deck_dialog: aqt.filtered_deck.FilteredDeckConfigDialog",
"filtered_deck: anki.scheduler.FilteredDeckForUpdate",
"deck_id: int",
],
doc="Allows performing changes after a filtered deck has been added or updated",
),
# Browser
###################
Hook(
name="default_search",
args=["current_search: str", "c: Card"],
return_type="str",
doc="Change the default search when the card browser is opened with card `c`.",
),
Hook(name="browser_will_show", args=["browser: aqt.browser.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_sidebar_will_show_context_menu",
args=[
"sidebar: aqt.browser.SidebarTreeView",
"menu: QMenu",
"item: aqt.browser.SidebarItem",
"index: QModelIndex",
],
),
Hook(
name="browser_header_will_show_context_menu",
args=["browser: aqt.browser.Browser", "menu: QMenu"],
),
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
""",
),
Hook(
name="browser_will_search",
args=["context: aqt.browser.SearchContext"],
doc="""Allows you to modify the search text, or perform your own search.
You can modify context.search to change the text that is sent to the
searching backend.
If you set context.card_ids to a list of ids, the regular search will
not be performed, and the provided ids will be used instead.
Your add-on should check if context.card_ids is not None, and return
without making changes if it has been set.
""",
),
Hook(
name="browser_did_search",
args=["context: aqt.browser.SearchContext"],
doc="""Allows you to modify the list of returned card ids from a search.""",
),
Hook(
name="browser_did_fetch_row",
args=[
"card_or_note_id: aqt.browser.ItemId",
"is_note: bool",
"row: aqt.browser.CellRow",
"columns: Sequence[str]",
],
doc="""Allows you to add or modify content to a row in the browser.
You can mutate the row object to change what is displayed. Any columns the
backend did not recognize will be returned as an empty string, and can be
replaced with custom content.
Columns is a list of string values identifying what each column in the row
represents.
""",
),
Hook(
name="browser_did_fetch_columns",
args=["columns: dict[str, aqt.browser.Column]"],
doc="""Allows you to add custom columns to the browser.
columns is a dictionary of data obejcts. You can add an entry with a custom
column to describe how it should be displayed in the browser or modify
existing entries.
Every column in the dictionary will be toggleable by the user.
""",
),
# Main window states
###################
# these refer to things like deckbrowser, overview and reviewer state,
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]]"],
),
# UI state/refreshing
###################
Hook(
name="state_did_revert",
args=["action: str"],
legacy_hook="revertedState",
doc="Legacy hook, called after undoing.",
),
Hook(
name="state_did_undo",
args=["changes: OpChangesAfterUndo"],
doc="Called after backend undoes a change.",
),
Hook(
name="state_did_reset",
legacy_hook="reset",
doc="""Legacy 'reset' hook. Called by mw.reset() and CollectionOp() to redraw the UI.
New code should use `operation_did_execute` instead.
""",
),
Hook(
name="operation_did_execute",
args=["changes: anki.collection.OpChanges", "handler: object | None"],
doc="""Called after an operation completes.
Changes can be inspected to determine whether the UI needs updating.
This will also be called when the legacy mw.reset() is used.
""",
),
Hook(
name="focus_did_change",
args=[
"new: QWidget | None",
"old: QWidget | None",
],
doc="""Called each time the focus changes. Can be used to defer updates from
`operation_did_execute` until a window is brought to the front.""",
),
Hook(
name="backend_will_block",
doc="""Called before one or more DB tasks are run in the background.
Subscribers can use this to set a flag to avoid DB queries until the operation
completes, as doing so will freeze the UI until the long-running operation
completes.
""",
),
Hook(
name="backend_did_block",
doc="""Called after one or more DB tasks finish running in the background.
Called regardless of the success of individual operations, and only called when
there are no outstanding ops.
""",
),
Hook(
name="theme_did_change",
doc="Called after night mode is toggled.",
),
# 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: object | None",
],
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",
),
Hook(
name="webview_did_inject_style_into_page",
args=["webview: aqt.webview.AnkiWebView"],
doc='''Called after standard styling is injected into an external
html file, such as when loading the new graphs. You can use this hook to
mutate the DOM before the page is revealed.
For example:
def mytest(web: AnkiWebView):
page = os.path.basename(web.page().url().path())
if page != "graphs.html":
return
web.eval(
"""
div = document.createElement("div");
div.innerHTML = 'hello';
document.body.appendChild(div);
"""
)
gui_hooks.webview_did_inject_style_into_page.append(mytest)
''',
),
# Main
###################
Hook(
name="main_window_did_init",
doc="""Executed after the main window is fully initialized
A sample use case for this hook would be to delay actions until Anki objects
like the profile or collection are fully initialized. In contrast to
`profile_did_open`, this hook will only fire once per Anki session and
is thus suitable for single-shot subscribers.
""",
),
Hook(
name="main_window_should_require_reset",
args=[
"will_reset: bool",
"reason: aqt.main.ResetReason | str",
"context: object | None",
],
return_type="bool",
doc="""Executed before the main window will require a reset
This hook can be used to change the behavior of the main window,
when other dialogs, like the AddCards or Browser, require a reset
from the main window.
If you decide to use this hook, make you sure you check the reason for the reset.
Some reasons require more attention than others, and skipping important ones might
put the main window into an invalid state (e.g. display a deleted note).
""",
),
Hook(name="backup_did_complete"),
Hook(
name="profile_did_open",
legacy_hook="profileLoaded",
doc="""Executed whenever a user profile has been opened
Please note that this hook will also be called on profile switches, so if you
are looking to simply delay an add-on action in a single-shot manner,
`main_window_did_init` is likely the more suitable choice.
""",
),
Hook(name="profile_will_close", legacy_hook="unloadProfile"),
Hook(
name="collection_did_load",
args=["col: anki.collection.Collection"],
legacy_hook="colLoading",
),
Hook(name="undo_state_did_change", args=["info: UndoActionsInfo"]),
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="top_toolbar_did_init_links",
args=["links: list[str]", "top_toolbar: aqt.toolbar.Toolbar"],
doc="""Used to modify or add links in the top toolbar of Anki's main window
'links' is a list of HTML link elements. Add-ons can generate their own links
by using aqt.toolbar.Toolbar.create_link. Links created in that way can then be
appended to the link list, e.g.:
def on_top_toolbar_did_init_links(links, toolbar):
my_link = toolbar.create_link(...)
links.append(my_link)
""",
),
Hook(
name="top_toolbar_did_redraw",
args=["top_toolbar: aqt.toolbar.Toolbar"],
doc="""Executed when the top toolbar is redrawn""",
),
Hook(
name="media_sync_did_progress",
args=["entry: aqt.mediasync.LogEntryWithTime"],
),
Hook(name="media_sync_did_start_or_stop", args=["running: bool"]),
Hook(
name="empty_cards_will_show",
args=["diag: aqt.emptycards.EmptyCardsDialog"],
doc="""Allows changing the list of cards to delete.""",
),
Hook(name="sync_will_start", args=[]),
Hook(
name="sync_did_finish",
args=[],
doc="""Executes after the sync of the collection concluded.
Note that the media sync did not necessarily finish at this point.""",
),
Hook(name="media_check_will_start", args=[]),
# Dialog Manager
###################
Hook(
name="dialog_manager_did_open_dialog",
args=[
"dialog_manager: aqt.DialogManager",
"dialog_name: str",
"dialog_instance: QWidget",
],
doc="""Executed after aqt.dialogs creates a dialog window""",
),
# 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_init",
args=["addcards: aqt.addcards.AddCards"],
),
Hook(
name="add_cards_did_add_note",
args=["note: anki.notes.Note"],
legacy_hook="AddCards.noteAdded",
),
Hook(
name="add_cards_will_add_note",
args=["problem: str | None", "note: anki.notes.Note"],
return_type="str | None",
doc="""Decides whether the note should be added to the collection or
not. It is assumed to come from the addCards window.
reason_to_already_reject is the first reason to reject that
was found, or None. If your filter wants to reject, it should
replace return the reason to reject. Otherwise return the
input.""",
),
Hook(
name="addcards_will_add_history_entry",
args=["line: str", "note: anki.notes.Note"],
return_type="str",
doc="""Allows changing the history line in the add-card window.""",
),
Hook(
name="add_cards_did_change_note_type",
args=["old: anki.models.NoteType", "new: anki.models.NoteType"],
doc="""Executed after the user selects a new note type when adding
cards.""",
),
Hook(
name="add_cards_did_change_deck",
args=["new_deck_id: int"],
doc="""Executed after the user selects a new different deck when
adding cards.""",
),
# Editing
###################
Hook(
name="editor_did_init_left_buttons",
args=["buttons: list[str]", "editor: aqt.editor.Editor"],
),
Hook(
name="editor_did_init_buttons",
args=["buttons: list[str]", "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_munge_html",
args=["txt: str", "editor: aqt.editor.Editor"],
return_type="str",
doc="""Allows manipulating the text that will be saved by the editor""",
),
Hook(
name="editor_will_use_font_for_field",
args=["font: str"],
return_type="str",
legacy_hook="mungeEditingFontName",
),
Hook(
name="editor_web_view_did_init",
args=["editor_web_view: aqt.editor.EditorWebView"],
),
Hook(
name="editor_did_init",
args=["editor: aqt.editor.Editor"],
),
Hook(
name="editor_will_load_note",
args=["js: str", "note: anki.notes.Note", "editor: aqt.editor.Editor"],
return_type="str",
doc="""Allows changing the javascript commands to load note before
executing it and do change in the QT editor.""",
),
Hook(
name="editor_did_paste",
args=[
"editor: aqt.editor.Editor",
"html: str",
"internal: bool",
"extended: bool",
],
doc="""Called after some data is pasted by python into an editor field.""",
),
Hook(
name="editor_will_process_mime",
args=[
"mime: QMimeData",
"editor_web_view: aqt.editor.EditorWebView",
"internal: bool",
"extended: bool",
"drop_event: bool",
],
return_type="QMimeData",
doc="""
Used to modify MIME data stored in the clipboard after a drop or a paste.
Called after the user pastes or drag-and-drops something to Anki
before Anki processes the data.
The function should return a new or existing QMimeData object.
"mime" contains the corresponding QMimeData object.
"internal" indicates whether the drop or paste is performed between Anki fields.
Most likely you want to skip processing if "internal" was set to True.
"extended" indicates whether the user requested an extended paste.
"drop_event" indicates whether the event was triggered by a drag-and-drop
or by a right-click paste.
""",
),
# Tag
###################
Hook(name="tag_editor_did_process_key", args=["tag_edit: TagEdit", "evt: QEvent"]),
# Sound/video
###################
Hook(name="av_player_will_play", args=["tag: anki.sound.AVTag"]),
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"]),
# Addon
###################
Hook(
name="addon_config_editor_will_display_json",
args=["text: str"],
return_type="str",
doc="""Allows changing the text of the json configuration before actually
displaying it to the user. For example, you can replace "\\\\n" by
some actual new line. Then you can replace the new lines by "\\\\n"
while reading the file and let the user uses real new line in
string instead of its encoding.""",
),
Hook(
name="addon_config_editor_will_save_json",
args=["text: str"],
return_type="str",
doc="""Allows changing the text of the json configuration that was
received from the user before actually reading it. For
example, you can replace new line in strings by some "\\\\n".""",
),
Hook(
name="addons_dialog_will_show",
args=["dialog: aqt.addons.AddonsDialog"],
doc="""Allows changing the add-on dialog before it is shown. E.g. add
buttons.""",
),
Hook(
name="addons_dialog_did_change_selected_addon",
args=["dialog: aqt.addons.AddonsDialog", "add_on: aqt.addons.AddonMeta"],
doc="""Allows doing an action when a single add-on is selected.""",
),
Hook(
name="addons_dialog_will_delete_addons",
args=["dialog: aqt.addons.AddonsDialog", "ids: list[str]"],
doc="""Allows doing an action before an add-on is deleted.""",
),
# Model
###################
Hook(
name="models_advanced_will_show",
args=["advanced: QDialog"],
),
Hook(
name="models_did_init_buttons",
args=[
"buttons: list[tuple[str, Callable[[], None]]]",
"models: aqt.models.Models",
],
return_type="list[tuple[str, Callable[[], None]]]",
doc="""Allows adding buttons to the Model dialog""",
),
# Fields
###################
Hook(
name="fields_did_rename_field",
args=[
"dialog: aqt.fields.FieldDialog",
"field: anki.models.FieldDict",
"old_name: str",
],
),
Hook(
name="fields_did_delete_field",
args=["dialog: aqt.fields.FieldDialog", "field: anki.models.FieldDict"],
),
# Stats
###################
Hook(
name="stats_dialog_will_show",
args=["dialog: aqt.stats.NewDeckStats"],
doc="""Allows changing the stats dialog before it is shown.""",
),
Hook(
name="stats_dialog_old_will_show",
args=["dialog: aqt.stats.DeckStats"],
doc="""Allows changing the old stats dialog before it is shown.""",
),
# Other
###################
Hook(
name="current_note_type_did_change",
args=["notetype: NotetypeDict"],
legacy_hook="currentModelChanged",