forked from ankitects/anki-docs
-
Notifications
You must be signed in to change notification settings - Fork 0
/
addons21.txt
1802 lines (1406 loc) · 66.1 KB
/
addons21.txt
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
/////
++++++++++++++++++++++++++++++
<%def name="title()">
Writing Anki 2.1.x Add-ons
</%def>
<h1>Writing Anki 2.1.x Add-ons</h1>
++++++++++++++++++++++++++++++
/////
_ Anki 2.1.x アドオンの作成 _
/////
= Other Versions =
/////
= 他のバージョン =
/////
This document covers add-on writing for the (not yet released) Anki 2.1.x. For
instructions on writing add-ons for Anki 2.0.x, please see
https://apps.ankiweb.net/docs/addons.html
/////
この文書では、まだリリースしていませんが Anki 2.1.x 用のアドオンの作成について扱います。Anki 2.0.x 用については https://apps.ankiweb.net/docs/addons.html をご覧ください。
/////
= Translations =
/////
= 他の言語 =
/////
* 日本語: http://rs.luminousspice.com/ankiaddons21/
* по-русски: http://finpapa.ucoz.ru/ankitest-addons.html#addons21
/////
* 英語 (原文): https://apps.ankiweb.net/docs/addons21.html
* ロシア語: http://finpapa.ucoz.ru/ankitest-addons.html#addons21
/////
= Overview =
/////
= 概要 =
/////
Anki is written in a user-friendly language called Python. If you're not
familiar with Python, please read the http://docs.python.org/tutorial/[Python
tutorial] before proceeding with the rest of this document.
/////
Anki は、ユーザーフレンドリーなプログラミング言語 Python で作成しています。
Python についてあまり詳しくない方は、この文書の先を読む前に
http://docs.python.org/tutorial/[Python tutorial] をお読みください。
(訳注: https://docs.python.org/ja/3/tutorial/[Python チュートリアル
日本語版])
/////
Because Python is a dynamic language, add-ons are extremely powerful in Anki -
not only can they extend the program, but they can also modify arbitrary
aspects of it, such as altering the way scheduling works, modifying the UI,
and so on.
/////
Python は動的言語なため、Anki ではアドオンが極めて強力に機能します。アドオンは Anki の処理
を拡張するだけでなく、Anki の任意の側面に変更を加えることができます。例えば、スケジュール
設定の処理を変更したり、ユーザーインターフェイスを修正したりすることができます。
/////
No special development environment is required to develop add-ons. All you
need is a text editor. If you're on Windows or a Mac, please use the packaged
version of Anki that's provided on the website, as there are no instructions
available for building it from scratch on those platforms.
/////
プラグインの開発には、特別な開発環境は必要ありません。テキストエディタがあれば十分です。
Windows や Mac をお使いの方は、このサイトで提供しているパッケージ版の Anki をお使いください。これらの
プラットフォーム上でゼロから Anki を構築するための説明が入手できないためです。
/////
While you can write plugins in a simple text editor like notepad, you may want
to look into an editor that can provide syntax highlighting (colouring of the
code) to make things easier.
/////
メモ帳のような単純なテキストエディタでプラグインを作成できますが、シンタックスハイライト
機能 (コードの色分け) を持ったエディタを探してみると、作業がより簡単になります。
/////
Anki is comprised of two parts:
/////
Anki を構成する2つの要素
/////
'anki' contains all the "backend" code - opening collections, fetching and
answering cards, and so on. It is used by Anki's GUI, and can also be included
in command line programs to access Anki decks without the GUI.
/////
'anki' には、「背後」で動作する全てのコードが含まれています。コレクションを開いたり、
カードを取得し、回答する処理などです。これは、Anki の GUI が使用していますが、GUI を使わず
に Anki 単語帳にアクセスするコマンドラインプログラムに含めることもできます。
/////
'aqt' contains the UI part of Anki. Anki's UI is built upon PyQt, Python
bindings for the cross-platform GUI toolkit Qt. PyQt follows Qt's API very
closely, so the documentation can be very useful when you want to know how to
use a particular GUI component.
/////
'aqt' には、Anki のユーザーインターフェイスの部分が含まれています。Anki のユーザー
インターフェイスは、PyQt 上に構築されています。PyQt とは、クロスプラットフォーム GUI
ツールキット Qt に対する Python バインディングです。PyQt は、Qt の API に密接に動作
します。Qt documentation は、特定の GUI
コンポーネントの使い方を調べたい時に、非常に役立ちます。
/////
Anki 2.1.x uses http://doc.qt.io/qt-5/index.html[Qt 5.9]
/////
Anki 2.1.x は http://doc.qt.io/qt-5/index.html[Qt 5.9] を使用しています。
/////
When Anki starts up, it checks for modules in the add-ons folder, and runs
each one it finds. When add-ons are run, they typically modify existing code
or add new menu items to provide a new feature.
/////
Anki が起動すると、アドオンフォルダ内のモジュールを確認し、見つけたモジュールを一つづつ実行します。アドオンを実行すると、通常は既存のコードを変更したり、新しい機能を呼び出すメニュー項目を新たに追加します。
/////
= Add-on folders =
/////
= アドオンフォルダ =
/////
You can access the top level add-ons folder by going to the Tools>Add-ons menu
item in the main Anki window. Click on the View Files button, and a folder
will pop up. If you had no add-ons installed, the top level add-ons folder
will be shown. If you had an add-on selected, the add-on's module folder will
be shown, and you will need to go up one level.
/////
Anki のメインウィンドウのメニューから Tools>Add-ons と選ぶと、アドオンフォルダの最上位階層にアクセスできます。View Files ボタンを押すとフォルダがポップアップします。アドオンをまだ何もインストールしていない場合は、最上位のアドオンフォルダが開きます。アドオンを選択している場合は、アドオンのモジュールフォルダが開きます。これは最上位階層の一つ下の階層になります。
/////
The add-ons folder is named "addons21", corresponding to Anki 2.1. If you have
an "addons" folder, it is because you have previously used Anki 2.0.x.
/////
アドオンフォルダの名前は、"addons21" です。Anki 2.1 に対応しています。"addons" があるのは、以前 Anki 2.0.x を使っていたためです。
/////
Each add-on uses one folder inside the add-on folder. Anki looks for a
file called `__init__.py` file inside the folder, eg:
/////
それぞれのアドオンは、アドオンフォルダの中の一つのフォルダを使います。Anki はそのフォルダの中にあるファイル `__init__.py` を探します。
addons21/my_addon/__init__.py
/////
If `__init__.py` does not exist, Anki will ignore the folder.
/////
もし `__init__.py` がなければ、Anki はそのフォルダを無視します。
/////
When choosing a folder name, it is recommended to stick to a-z and 0-9
characters to avoid problems with Python's module system.
/////
フォルダ名を決めるときには、a-z と 0-9 の範囲の文字から選ぶことおすすめします。これによって、Python モジュールシステムによる問題を避けることができます。
/////
While you can use whatever folder name you wish for folders you create
yourself, when you download an add-on from AnkiWeb, Anki will use the item's
ID as the folder name, such as:
/////
自分でフォルダを作るときはどんな名前でも使えますが、AnkiWeb からアドオンをダウンロードするときは、Anki はそのアドオンの ID をフォルダ名に使います。例えば次の通りです。
addons21/48927303923/__init__.py
/////
Anki will also place a meta.json file in the folder, which keeps track of the
original add-on name, when it was downloaded, and whether it's enabled or not.
/////
Anki はさらにフォルダに meta.json ファイルを保存して、ダウンロードした時の元のアドオン名と、アドオンの利用許可を追跡ます。
/////
You should not store user data in the add-on folder, as it's
<<configuration,deleted when the user upgrades an add-on>>.
/////
ユーザーデータをアドオンフォルダに保存できません。<<configuration,そのようなデータは、ユーザーがアドオンをアップグレードすると、削除される>>からです。
/////
= A Simple Add-On =
/////
= 簡単なアドオンの一例 =
/////
Add the following to `my_first_addon/__init__.py` in your add-ons folder:
/////
次に示す `my_first_addon/__init__.py` を、自分のアドオンフォルダに追加してみてください:
/////
-----
# import the main window object (mw) from aqt
from aqt import mw
# import the "show info" tool from utils.py
from aqt.utils import showInfo
# import all of the Qt GUI library
from aqt.qt import *
# We're going to add a menu item below. First we want to create a function to
# be called when the menu item is activated.
def testFunction():
# get the number of cards in the current collection, which is stored in
# the main window
cardCount = mw.col.cardCount()
# show a message box
showInfo("Card count: %d" % cardCount)
# create a new menu item, "test"
action = QAction("test", mw)
# set it to call testFunction when it's clicked
action.triggered.connect(testFunction)
# and add it to the tools menu
mw.form.menuTools.addAction(action)
-----
/////
-----
# aqt からメインウィンドウオブジェクト (mw) を読み込みます
from aqt import mw
# utils.py から "show info" ツールを読み込みます
from aqt.utils import showInfo
# Qt GUI ライブラリの全てを読み込みます
from aqt.qt import *
# 次のようなメニュー項目を追加してみましょう。まず最初にメニュー項目が利用可能になったら
# 呼び出す関数を作成します。
def testFunction():
# 現在使用中のコレクションの中のカードの枚数を取得します
# このコレクションはメインウィンドウの中に保存しています
cardCount = mw.col.cardCount()
# メッセージボックスを表示します
showInfo("Card count: %d" % cardCount)
# 新しいメニュー項目 "test" を作成します。
action = QAction("test", mw)
# この項目をクリックしたら testFunction を呼び出すように設定します。
action.triggered.connect(testFunction)
# そして、この設定をツールメニューに反映します。
mw.form.menuTools.addAction(action)
-----
/////
Restart Anki, and you should find a 'test' item in the tools menu. Running it
will display a dialog with the card count.
/////
Anki を再起動すると、ツールメニューの中に 'test' 項目が追加されていることに気づくでしょう。
この項目を選択して実行するとカード枚数を表示するダイアログが現れます。
/////
If you make a mistake when entering in the plugin, Anki will show an error
message on startup indicating where the problem is.
/////
プラグインの入力中に間違いがあった場合には、Anki は起動時にエラーメッセージを表示して
どこに問題があるか指摘します。
/////
= The Collection =
/////
= コレクション =
/////
All operations on a collection file are accessed via mw.col. Some basic
examples of what you can do follow. Please note that you should put these in
testFunction() as above. You can't run them directly in an add-on, as add-ons
are initialized during Anki startup, before any collection or profile has been
loaded.
/////
コレクションファイル上の全ての操作は、mw.col を通じてアクセスします。基本的な例で
何ができるがご紹介します。注意してほしいのは、上の例のように testFunction() の中で行ってください。
アドオンの中で直接実行することはできません。それは、Anki を起動中にアドオンが初期化し、その後にコレクションやプロファイルを
読み込むからです。
/////
*Get a due card:*
/////
*復習時期のカードの取得:*
/////
-----
card = mw.col.sched.getCard()
if not card:
# current deck is finished
-----
/////
-----
card = mw.col.sched.getCard()
if not card:
# 現在の単語帳は復習済み
-----
/////
*Answer the card:*
/////
*カードを解答する:*
-----
mw.col.sched.answerCard(card, ease)
-----
/////
*Edit a note (append " new" to the end of each field):*
/////
*ノートを編集する (各フィールドの最後に " new" を追加):*
-----
note = card.note()
for (name, value) in note.items():
note[name] = value + " new"
note.flush()
-----
/////
*Get card IDs for notes with tag x:*
/////
*ノートにタグ x を持つカードの ID を取得する:*
-----
ids = mw.col.findCards("tag:x")
-----
/////
*Get question and answer for each of those ids:*
/////
*指定したカード ID から質問と解答を取得する:*
-----
for id in ids:
card = mw.col.getCard(id)
question = card.q()
answer = card.a()
-----
/////
*Reset the scheduler after any DB changes. Note that we call reset() on the
main window, since the GUI has to be updated as well:*
/////
*データベースの変更後にスケジュールをリセットする。GUI も更新しなければならないので、
メインウィンドウ上で reset() を呼び出すことに注意してください:*
-----
mw.reset()
-----
/////
*Import a text file into the collection*
/////
*テキストファイルをコレクションに読み込む*
/////
-----
from anki.importing import TextImporter
file = u"/path/to/text.txt"
# select deck
did = mw.col.decks.id("ImportDeck")
mw.col.decks.select(did)
# set note type for deck
m = mw.col.models.byName("Basic")
deck = mw.col.decks.get(did)
deck['mid'] = m['id']
mw.col.decks.save(deck)
# import into the collection
ti = TextImporter(mw.col, file)
ti.initMapping()
ti.run()
-----
/////
-----
from anki.importing import TextImporter
file = u"/path/to/text.txt"
# 単語帳を選択
did = mw.col.decks.id("ImportDeck")
mw.col.decks.select(did)
# 単語帳にノートタイプを設定
m = mw.col.models.byName("Basic")
deck = mw.col.decks.get(did)
deck['mid'] = m['id']
mw.col.decks.save(deck)
# コレクションに読み込む
ti = TextImporter(mw.col, file)
ti.initMapping()
ti.run()
-----
/////
Almost every GUI operation has an associated function in anki, so any of
the operations that Anki makes available can also be called in an add-on.
/////
ほとんど全ての GUI 処理は 'anki' 内に関連する関数を持っています。このため、Anki が利用
できるどんな処理でも、アドオンの中で同様に呼び出すことができます。
/////
If you want to access the collection outside of the GUI, you can do so with
the following code:
/////
GUI の外側のコレクションにアクセスする場合は、次のようなコードを使います:
-----
from anki import Collection
col = Collection("/path/to/collection.anki2")
-----
/////
If you make any modifications to the collection outside of Anki,
you must make sure to call col.close() when you're done,
or those changes will be lost.
/////
Anki の外部のコレクションに何らかの修正を加えたときは、修正が済んだら col.close() を必ず呼び出さなければなりません。
これを怠ると修正点は失われます。
/////
= The Database =
/////
= データベース =
/////
When you need to perform operations that are not already supported by anki,
you can access the database directly. Anki collections are stored in SQLite
files. Please see the http://www.sqlite.org/lang.html[SQLite documentation]
for more information.
/////
'anki' がサポートしていない処理を実行する必要がある場合は、データベースに直接アクセスする
ことができます。Anki コレクションは、SQLite ファイル内に保存されています。詳しい情報は、
http://www.sqlite.org/lang.html[SQLite documentation]をご覧ください。
/////
Anki's DB object supports the following functions:
/////
Anki のデータベースオブジェクトは次のような関数をサポートしています:
/////
*execute() allows you to perform an insert or update operation. Use named
arguments with ?. eg:*
/////
*execute() は、挿入と更新処理を実行します。指定した引数は ? を一緒に使います。例えば:*
-----
mw.col.db.execute("update cards set ivl = ? where id = ?", newIvl, cardId)
-----
/////
*executemany() allows you to perform bulk update or insert operations. For
large updates, this is much faster than calling execute() for each data point.
eg:*
/////
*executemany() は、更新と挿入を一括処理します。大規模な更新にはこの関数の方が、
execute() で個別にデータを処理するよりも非常に高速に処理します。例えば:*
-----
data = [[newIvl1, cardId1], [newIvl2, cardId2]]
mw.col.db.executemany(same_sql_as_above, data)
-----
/////
*scalar() returns a single item:*
/////
*scalar() は、単一の項目を返します:*
-----
showInfo("card count: %d" % mw.col.db.scalar("select count() from cards"))
-----
/////
*list() returns a list of the first column in each row, eg [1, 2, 3]:*
/////
*list() は、各行の最初の列をリストで返します。次のコードの戻り値は [1, 2, 3]です:*
-----
ids = mw.col.db.list("select id from cards limit 3")
-----
/////
*all() returns a list of rows, where each row is a list:*
/////
*all() は、各行がリストの場合、行のリストを返します:*
-----
ids_and_ivl = mw.col.db.all("select id, ivl from cards")
-----
/////
*execute() can also be used to iterate over a result set without building an
intermediate list. eg:*
/////
*execute() は、中間リストを作らずに結果の集合への処理を繰り返すのに使えます。例:*
-----
for id, ivl in mw.col.db.execute("select id, ivl from cards limit 3"):
showInfo("card id %d has ivl %d" % (id, ivl))
-----
/////
Add-ons should never modify the schema of existing tables, as that may
break future versions of Anki.
/////
アドオンは、既存のテーブルのスキーマを決して変更してはいけません。そうすると将来のバージョンの Anki を破壊するかもしれないからです。
/////
If you need to store addon-specific data, consider using Anki's
<<configuration>> support.
/////
アドオン独自のデータが必要な場合は、Anki の <<configuration, 設定>> サポートの使用を考慮してください。
/////
If you need the data to sync across devices, small options can be stored
within mw.col.conf. Please don't store large amounts of data there, as
it's sent on every sync.
/////
デバイス間でデータを同期する必要する場合は、小さな設定項目を mw.col.conf に保存できます。そこに大量なデータは保存しないでください。同期のたびに送ることになるからです。
/////
= Hooks =
/////
= フック =
/////
Hooks have been added to a few parts of the code to make writing add-ons
easier. There are two types: 'hooks' take some arguments and return no value,
and 'filters' take a value and return it (perhaps modified).
/////
フックをコードのわずかな箇所に追加して、アドオンの作成がもっと簡単になるようにしました。
フックは 2 種類あります。'hooks' は引数を取り、戻り値はありませんが、'filters' 引数を取り、
(おそらく何らかの修正を加えて) 値を返します。
/////
A simple example of the former is in the leech handling. When the scheduler
(anki/sched.py) discovers a leech, it calls:
/////
'hook' の簡単な例は、無駄なカード (leech) の処理の中に見つかります。スケジューラー
(anki/sched.py) が、無駄なカードを見つけると、'hook' を呼び出します。
-----
runHook("leech", card)
-----
/////
If you wished to perform a special operation when a leech was discovered, such
as moving the card to a "Difficult" deck, you could do it with the following
code:
/////
無駄なカードが現れた時に、特定の処理を行いたい場合、例えばそのカードを "Difficult"
という名前の単語帳に移動する場合、次のようなコードで実現できます。
/////
-----
from anki.hooks import addHook
from aqt import mw
def onLeech(card):
# can modify without .flush(), as scheduler will do it for us
card.did = mw.col.decks.id("Difficult")
# if the card was in a cram deck, we have to put back the original due
# time and original deck
card.odid = 0
if card.odue:
card.due = card.odue
card.odue = 0
addHook("leech", onLeech)
-----
/////
-----
from anki.hooks import addHook
from aqt import mw
def onLeech(card):
# スケジューラーが修正する際には、 .flush() を使わずに修正できます。
card.did = mw.col.decks.id("Difficult")
# カードがフィルター単語帳の中にある場合は、復習時期を元に戻して取得元の単語帳に
# 戻さなければなりません
card.odid = 0
if card.odue:
card.due = card.odue
card.odue = 0
addHook("leech", onLeech)
-----
/////
An example of a filter is in aqt/editor.py. The editor calls the
"editFocusLost" filter each time a field loses focus, so that add-ons can
apply changes to the note:
/////
aqt/editor.py の中に 'filter' の例があります。エディターは、入力欄からフォーカスが外れる
と "editFocusLost" filter を呼び出します。そして、アドオンはノートに変更を加えます。
/////
-----
if runFilter(
"editFocusLost", False, self.note, self.currentField):
# something updated the note; schedule reload
def onUpdate():
self.loadNote()
self.checkValid()
self.mw.progress.timer(100, onUpdate, False)
-----
/////
-----
if runFilter(
"editFocusLost", False, self.note, self.currentField):
# ノートを更新して、スケジュールを再度読み込む
def onUpdate():
self.loadNote()
self.checkValid()
self.mw.progress.timer(100, onUpdate, False)
-----
/////
Each filter in this example accepts three arguments: a modified flag, the
note, and the current field. If a filter makes no changes it returns the
modified flag the same as it received it; if it makes a change it returns
True. In this way, if any single add-on makes a change, the UI will reload the
note to show updates.
/////
このサンプルでは、それぞれの filter は 3 つの引数を受け取ります。修正フラグ、ノート、現在のフィールドです。
filter が変更を加えない場合は、修正フラグは受け取った値と同じ値を返します。
変更を加えた場合は、True を返します。このようにして、どんなアドオンでも変更を加えると
ユーザーインターフェイスは、ノートを読み込み直して、更新内容を表示します。
/////
The Japanese Support add-on uses this hook to automatically generate one field
from another. A slightly simplified version is presented below:
/////
Japanese Support アドオンは、このフックを使って別のフィールドからフィールドを自動的に生成します。
単純化したものを次に示します。
/////
-----
def onFocusLost(flag, n, fidx):
from aqt import mw
# japanese model?
if "japanese" not in n.model()['name'].lower():
return flag
# have src and dst fields?
for c, name in enumerate(mw.col.models.fieldNames(n.model())):
for f in srcFields:
if name == f:
src = f
srcIdx = c
for f in dstFields:
if name == f:
dst = f
if not src or not dst:
return flag
# dst field already filled?
if n[dst]:
return flag
# event coming from src field?
if fidx != srcIdx:
return flag
# grab source text
srcTxt = mw.col.media.strip(n[src])
if not srcTxt:
return flag
# update field
try:
n[dst] = mecab.reading(srcTxt)
except Exception, e:
mecab = None
raise
return True
addHook('editFocusLost', onFocusLost)
-----
/////
-----
def onFocusLost(flag, n, fidx):
from aqt import mw
# japanese model か?
if "japanese" not in n.model()['name'].lower():
return flag
# src フィールドと dst フィールドがあるか?
for c, name in enumerate(mw.col.models.fieldNames(n.model())):
for f in srcFields:
if name == f:
src = f
srcIdx = c
for f in dstFields:
if name == f:
dst = f
if not src or not dst:
return flag
# dst フィールドは入力済みか?
if n[dst]:
return flag
# イベントは src フィールドで発生したか?
if fidx != srcIdx:
return flag
# ソーステキストを取得
srcTxt = mw.col.media.strip(n[src])
if not srcTxt:
return flag
# 欄を更新
try:
n[dst] = mecab.reading(srcTxt)
except Exception, e:
mecab = None
raise
return True
addHook('editFocusLost', onFocusLost)
-----
/////
The first argument of a filter is the argument that should be returned. In the
focus lost filter this is a flag, but in other cases it may be some other
object. For example, in anki/collection.py, _renderQA() calls the "mungeQA"
filter which contains the generated HTML for the front and back of cards.
latex.py uses this filter to convert text in LaTeX tags into images.
/////
filter の第一引数は、必ず返される引数です。このフォーカスを失った時の filter の中では、
引数はフラグですが、別のオブジェクトになる場合もあります。例えば、anki/collection.py
の中では、_renderQA() は、カードの表面と裏面用に生成した HTML を収容する "mungeQA" filter
を呼び出します。latex.py は、この filter を LaTeX タグの中のテキストを画像に変換する
のに使っています。
/////
In Anki 2.1, a hook was added for adding buttons to the editor. It can be used
like so:
/////
Anki 2.1 では、エディタにボタンを追加するフックを追加しました。次のように使います。
-----
from aqt.utils import showInfo
from anki.hooks import addHook
# cross out the currently selected text
def onStrike(editor):
editor.web.eval("wrap('<del>', '</del>');")
def addMyButton(buttons, editor):
editor._links['strike'] = onStrike
return buttons + [editor._addButton(
"iconname", # "/full/path/to/icon.png",
"strike", # link name
"tooltip")]
addHook("setupEditorButtons", addMyButton)
-----
/////
= Monkey Patching and Method Wrapping =
/////
= モンキーパッチとメソッドの隠蔽 =
/////
If you want to modify a function that doesn't already have a hook, it's
possible to overwrite that function with a custom version instead. This is
sometimes referred to as 'monkey patching'.
/////
フックを持っていない関数を修正したい場合には、カスタム版の関数で上書きすることが可能です。
このことを、「モンキーパッチ」を呼ぶことがあります
/////
In aqt/editor.py there is a function setupButtons() which creates the buttons
like bold, italics and so on that you see in the editor. Let's imagine you
want to add another button in your add-on.
/////
aqt/editor.py には、setupButtons() という関数があり、エディターの中にある太字ボタン、
斜字体ボタンのようなボタンを生成します。自分のアドオンに違ったボタンを追加することを考えて
みましょう。
/////
WARNING: Anki 2.1 no longer uses setupButtons(). The code below is still
useful to understand how monkey patching works, but for adding buttons to the
editor please see the setupEditorButtons hook described in the previous
section.
/////
警告: Anki 2.1 は、setupButtons() をもう使用していません。このコードは、モンキーパッチがどのように動作しているか、理解するのに役立ちますが、エディタにボタンと追加するには、前の項目で説明した setupEditorButtons フックを見てください。
/////
The simplest way is to copy and paste the function from the Anki source code,
add your text to the bottom, and then overwrite the original, like so:
/////
一番簡単な方法は、Anki のソースコードからその関数をコピーペーストして、自分のテキストを
ボタンに追加します。そして、元の関数を上書きします。次の通りです。
/////
-----
from aqt.editor import Editor
def mySetupButtons(self):
<copy & pasted code from original>
<custom add-on code>
Editor.setupButtons = mySetupButtons
-----
/////
-----
from aqt.editor import Editor
def mySetupButtons(self):
<オリジナルからコピーペーストしたコード>
<カスタムアドオンのコード>
Editor.setupButtons = mySetupButtons
-----
/////
This approach is fragile however, as if the original code is updated in a
future version of Anki, you would also have to update your add-on. A better
approach would be to save the original, and call it in our custom version:
/////
この方法は、将来の Anki のバージョンで元のコードが更新されるような場合に、自分のアドオンも
更新する必要になる問題をはらんでいます。もっと良い方法は、オリジナルの関数を保存しておいて
自分のカスタムバージョンの中で呼び出すことです。
/////
-----
from aqt.editor import Editor
def mySetupButtons(self):
origSetupButtons(self)
<custom add-on code>
origSetupButtons = Editor.setupButtons
Editor.setupButtons = mySetupButtons
-----
/////
-----
from aqt.editor import Editor
def mySetupButtons(self):
origSetupButtons(self)
<カスタムアドオンのコード>
origSetupButtons = Editor.setupButtons
Editor.setupButtons = mySetupButtons
-----
/////
Because this is a common operation, Anki provides a function called wrap()
which makes this a little more convenient. A real example:
/////
これはよく行われる処理なので、Anki では wrap() という関数を提供して、もう少し使いやすく
しています。実際の例をご紹介します。
/////
-----
from anki.hooks import wrap
from aqt.editor import Editor
from aqt.utils import showInfo
def buttonPressed(self):
showInfo("pressed " + `self`)
def mySetupButtons(self):
# - size=False tells Anki not to use a small button
# - the lambda is necessary to pass the editor instance to the
# callback, as we're passing in a function rather than a bound
# method
self._addButton("mybutton", lambda s=self: buttonPressed(self),
text="PressMe", size=False)
Editor.setupButtons = wrap(Editor.setupButtons, mySetupButtons)
-----
/////
-----
from anki.hooks import wrap
from aqt.editor import Editor
from aqt.utils import showInfo
def buttonPressed(self):
showInfo("pressed " + `self`)
def mySetupButtons(self):
# - size=False は、小さいボタンは使わない
# - lambda は、予め設定されているメソッドの代わりに関数の中で
# エディタインスタンスをコールバックに渡す時に必要
self._addButton("mybutton", lambda s=self: buttonPressed(self),
text="PressMe", size=False)
Editor.setupButtons = wrap(Editor.setupButtons, mySetupButtons)
-----
/////
By default, wrap() runs your custom code after the original code. You can pass
a third argument, "before", to reverse this. If you need to run code both
before and after the original version, you can do so like so:
/////
既定では、wrap() は元のコードの後にカスタムコードを実行します。第3引数 "before" を渡すと
これを逆転できます。元のバージョンの前と後の両方で実行する必要がある場合は、次のようにします。
/////
-----
from anki.hooks import wrap
from aqt.editor import Editor
def mySetupButtons(self, _old):
<before code>
ret = _old(self)
<after code>
return ret
Editor.setupButtons = wrap(Editor.setupButtons, mySetupButtons, "around")
-----
/////
-----
from anki.hooks import wrap
from aqt.editor import Editor
def mySetupButtons(self, _old):
<オリジナルの前で実行するコード>
ret = _old(self)
<オリジナルの後で実行するコード>
return ret
Editor.setupButtons = wrap(Editor.setupButtons, mySetupButtons, "around")
-----
/////
If you need to modify the middle of a function rather than run code before or
after it, there may a good argument for adding a hook to that function in the
original code. In these situations, please post on the support site and ask
for a hook to be added.
/////
関数の前後でコードを実行するのではなく、関数の中を修正する必要がある場合には、元のコードの
中の対象とする関数にフックを追加するのが良い方法かも知れません。このような場合には、