· 9 years ago · Jun 04, 2017, 04:24 PM
1# -*- mode: Python ; coding: utf-8 -*-
2# • Must Have
3# https://ankiweb.net/shared/info/67643234
4# License: GNU GPL, version 3 or later; http://www.gnu.org/copyleft/gpl.html
5# Copyright (c) 2016 Dmitry Mikheev, http://finpapa.ucoz.net/
6#
7# -- MUST HAVE -- All-in-1 SuperPack -- 67643234 --
8# -- ANKI add-on -- highly configurable addon --
9# I did my best, let anybody do better.
10
11# actionDownloadSharedPlugin
12# main.onGetSharedPlugin -- addons.onGetAddons
13
14from __future__ import division
15from __future__ import unicode_literals
16import os, sys, urllib, re
17
18if __name__ == "__main__":
19 print("This is THE add-on for the Anki program and it can't be run directly.")
20 print("Please download Anki 2.0 from http://ankisrs.net/")
21 sys.exit()
22else:
23 # Save a reference to the toolkit onto the mw, preventing garbage collection of PyQT objects
24 #if mw: ... # AnkiBelt AnkiLevel
25 pass
26
27if sys.version[0] == '2': # Python 3 is utf8 only already.
28 if hasattr(sys,'setdefaultencoding'):
29 #reload(sys)
30 sys.setdefaultencoding('utf8')
31
32##########################################
33#
34import datetime, time, io, json
35from datetime import datetime, timedelta
36
37import copy
38
39from anki.collection import _Collection
40from anki.utils import fmtTimeSpan, ids2str
41
42# NB! QKeySequence("Ctrl+PgUp") but Qt.CTRL + Qt.PageUp
43# -- on Mac Meta+SomeKey automatically converted to Ctrl+SomeKey
44
45# Traceback (most recent call last):
46# File "C:\Users\Anki\Documents\Anki2\addons\--Must_Have.py", line 792, in go_edit_layout
47# clayout.CardLayout(mw, ccard.note(), ord=ccard.ord)
48# NameError: global name 'clayout' is not defined
49
50from aqt import mw, clayout, browser, editor
51# -- NameError: global name 'clayout' is not defined
52# -- when next line was commented after adding next line with * -- why?
53from aqt import *
54# import the main window object (mw) from ankiqt
55from aqt.utils import askUser, showInfo, showText, showWarning, showCritical, tooltip, openLink, openFolder, getOnlyText, getText, mungeQA, getBase
56
57import aqt
58
59from aqt.sound import getAudio
60
61__version__ = "2.0.33b"
62
63# This program is free software: you can redistribute it and/or modify #
64# it under the terms of the GNU General Public License as published by #
65# the Free Software Foundation, either version 3 of the License, or #
66# (at your option) any later version. #
67#
68# This program is distributed in the hope that it will be useful, #
69# but WITHOUT ANY WARRANTY; without even the implied warranty of #
70# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
71# GNU General Public License for more details. #
72#
73# You should have received a copy of the GNU General Public License #
74# along with this program. If not, see <http://www.gnu.org/licenses/>. #
75
76from anki.consts import MODEL_STD, MODEL_CLOZE
77
78import difflib, cgi
79import unicodedata as ucd
80import HTMLParser
81
82import anki.sound
83from anki.sound import play
84from anki.sound import mplayerQueue, mplayerClear, mplayerEvt
85from anki.sound import MplayerMonitor
86from anki.sound import playFromText, clearAudioQueue, play
87
88A = {}
89B = {}
90
91HIDE_THEM_ALL = False
92#HIDE_THEM_ALL = True
93
94A['CHECK_ASCII_PATH'] = [None, True, False, ''' Сheck the startup path for invalid characters ''', \
95 """ ПроверÑть путь к программе на наличие в нём Ñимволов кириллицы (и прочего юникода) """]
96
97A['ONESIDED_CARDS'] = [None, True, False, ''' Skip question if BackSide is empty, {{FrontSide}} only or exact CopyPaste of FrontSide. ''', """ Поддержка одноÑторонних карточек """]
98
99B['B11_BUTTON_TITLES'] = [None, True, False, ''' 2C titles (tooltips, baloon tips) ''', \
100 """ Показывать вÑплывающие подÑказки """]
101
102#####################
103# Get language class
104# Выбранный пользователем Ñзык программной оболочки
105import anki.lang
106lang = anki.lang.getLang()
107
108B['B08_BIG_BUTTONS'] = [None, True, False, ''' Big Button Show Answer and so far. ''', \
109 """ Большие кнопки Показать и Оценить ответ """]
110
111###################################
112#
113A['KING_SIZE'] = [None, 32, 24, ''' Icon's size on toolbar (pixels) # 48 ''', \
114 """ Размер меÑта под иконку на панелÑÑ… инÑтрументов (в пикÑелÑÑ…) # как вариант """]
115
116MUSTHAVE_COLOR_ICONS = 'musthave_icons'
117
118##########################
119# -- lang Again Hard Good Easy
120BUTTON_LABELS_LANG = [
121 ['ru', u'ÐЕТ <small>1</small>', u'Трудно <small>2</small>', u'ДР<small>3</small>', u'Легко <small>4</small>'],
122 ['en', u'aw… <small>1</small>', u'huh? <small>2</small>', u'nice <small>3</small>', u'radical! <small>4</small>'],
123 #['ru', u'ÐЕТ <sub>1</sub>', u'Трудно <sub>2</sub>', u'ДР<sub>3</sub>', u'Легко <sub>4</sub>'],
124 #['en', u'aw… <sub>1</sub>', u'huh? <sub>2</sub>', u'nice <sub>3</sub>', u'radical! <sub>4</sub>'],
125 ]
126
127# lang o_O :-( :-| :-)
128BUTTON_LABELS_SMILES = [[lang, u'o_O', u'<b>:-(</b>', u'<b>:-|</b>', u'<b>:-)</b>']]
129
130BUTTON_LABELS = BUTTON_LABELS_LANG
131
132##########################
133# -- Force custom font --
134
135#A['FONT'] = [None, "Times New Roman", "", ''' Default Font for Decks Browser and Deck Overview ''', """ Шрифт Ð´Ð»Ñ ÑпиÑка колод и оÑновных показателей колоды """]
136#A['FONTSIZE'] = [None, 18, 0, ''' ''', """ """]
137
138A['FONT'] = [None, "Calibri", "", ''' ''', """ """]
139A['FONTSIZE'] = [None, 16, 0, ''' Default Font Size for Decks Browser and Deck Overview ''', \
140 """ Размер шрифта Ð´Ð»Ñ ÑпиÑка колод и Ñтраницы оÑновных показателей колоды """]
141
142# hide Edit and More button on left and right bottom corners of reviewer screen
143# -- Edit More -- Редактирование Ещё -- Редактирование Еще --
144# не показывать !!!
145
146B['B13_EDIT_MORE'] = [None, False, True, ''' Edit and More buttons on Study Screen ''', \
147 """ Ðа карточках показывать кнопки Редактирование Ещё """]
148
149MORE_EDIT = " td.stat button { display: none; } "
150EDIT_MORE = " td\.stat button \{ display\: none\; \} "
151
152A['LOCAL_CSS_AND_DIY_NIGHT_MODE'] = [None, True, False, ''' Local CSS and DIY night mode ''', \
153 """ Можно наÑтраивать внешние таблицы Ñтилей под различные пользовательÑкие и вÑтроенные клаÑÑÑ‹ """]
154
155extra_classes_list = [
156 {'class': u'night', 'display': u'.night { &Ðочной режим } ' if lang == 'ru' else '.night { &Night mode } '},
157 {'class': u'contrast', 'display': u'.contrast { &Режим выÑокой контраÑтноÑти } ' if lang == 'ru' else '.contrast { &High contrast mode } '},
158 {'class': u'solarized', 'display': u'.solarized { &Солнечные цвета } ' if lang == 'ru' else '.solarized { &Solarized colors } '},
159 {'class': u'rain', 'display': u'.rain { &Дождливый день } ' if lang == 'ru' else '.rain { &Rainy day } '},
160 ]
161
162###############################
163# -- Replay buttons on card --
164#REPLAY_BUTTONS_ON_CARD = 2 # button's tooltip with filename
165#REPLAY_BUTTONS_ON_CARD = 1 # button's tooltip without filename on FrontSide
166#REPLAY_BUTTONS_ON_CARD = 0 # no buttons at all
167
168A['REPLAY_BUTTONS_ON_CARD'] = [None, 2, 0, \
169 ''' Replay buttons on card # =1 title tooltip on FrontSide without filename ''', \
170 """ Как показывать кнопки Повтор Ðудио на карточках """]
171
172A['CHECK_OLD_ISSUES'] = [None, True, False, ''' Look for old add-ons ''', \
173 """ ПроверÑть Ñтарые Ð´Ð¾Ð¿Ð¾Ð»Ð½ÐµÐ½Ð¸Ñ """]
174
175#################################################################
176# -- Show learn count --
177# Show_learn_count.py by ospalh
178# Show the learn count in the deck browser, too.
179# 3 numbers vs 2 by default
180# New, Learn, Due vs Learn+Due and New
181# Show count of unseen and suspended cards in deck browser.
182
183B['B05_STUDY_BUTTON'] = [None, False, True, ''' Study Deck Button ''', \
184 """ Кнопка Учить колоду """]
185
186B['B03_GEAR_AT_END_OF_LINE'] = [None, True, False, ''' Gear at the end of line ''', \
187 """ ШеÑтерёнка в конце Ñтроки """]
188
189# -- Unseen and buried counts --
190# https://ankiweb.net/shared/info/161964983
191# Show count of unseen and buried cards in deck browser.
192
193# -- More Overview Stats --
194# https://ankiweb.net/shared/info/2116130837
195# This add-on adds a little more info to the overview statistics.
196# Due today: new + learning + review
197# Total reviews: total reviews regardless of daily limit (today and tomorrow limitless)
198# Total new cards: total new cards in deck
199# Total cards: total cards in deck
200# -- More Overview Stats 2 --
201# Unseen, suspended and buried
202# Ðевиданные, иÑключённые и отложенные
203
204B['B00_MORE_OVERVIEW_STATS'] = [None, 3, 0, ''' More Overview Stats # =1 ''', \
205 """ Дополнительные показатели на Ñтранице колоды и на панели колод # =2 """]
206
207B['B04_HIDE_BIG_NUMBER'] = [None, 999, 999999, ''' Hide big numbers ''', \
208 """ СпрÑтать чиÑла больше указанного """]
209
210B['B04_HIDE_BIG_NUMBERS'] = [None, False, True, ''' Don't see the exact number in deck list. ''', \
211 """ ПрÑтать большие чиÑла """]
212
213# -- zoom --
214A['ZOOM'] = [None, True, False, ''' ZOOM and Open Hint and Reply with Mouse Wheel ''', \
215 """ МаÑштаб """]
216
217A['ZOOM_IMAGES'] = [None, True, False, ''' also ZOOM Images as well as text too ''', \
218 """ МаÑштабировать и картинки тоже, как и текÑÑ‚ """]
219
220# Standard zoom factors for the main views of the central area:
221deck_browser_standard_zoom = 1.0
222overview_standard_zoom = 1.0
223reviewer_standard_zoom = 1.0
224# Before you change the reviewer_standard_zoom size, maybe you should
225# use larger fonts in your decks.
226
227A['SEARCH_AND_TRANSLATE'] = [None, True, False, ''' Tatoeba, Google, Yandex, Bing lookup selected on right-click and so on. ''', \
228 """ Search Google Images for selected words -- Get context sentence for language learning """]
229
230FROM_LANGUAGE = 'russian'
231TO_LANGUAGE = 'english'
232
233FROM_LNG = 'rus'
234TO_LNG = 'eng'
235
236FROM_LANG = 'ru'
237TO_LANG = 'en'
238
239A['F3_CARD_INFO_DURING_REVIEW'] = [None, True, False, ''' Card Info During Review ''', \
240 """ Shift+C original -> Shift+F3 == builtin window """]
241
242A['F3_CARD_HISTORY'] = [None, True, False, ''' F3 == popup window ''', \
243 """ ПоÑмотреть вÑе ответы на данную карточку: когда, долго ли думал и наÑколько уÑпешно. """]
244
245A['F3_VIEW_SOURCE'] = [None, True, False, ''' View Source HTML ''', \
246 """ ПоÑмотреть реально работающий иÑходный код HTML-Ñтраницы """]
247
248A['F3_HTML_SOURCE'] = [None, True, False, ''' View Source HTML (Alt+F3)''', \
249 """ ПоÑмотреть реально работающий иÑходный код HTML-Ñтраницы (Alt+F3)"""]
250
251A['EXPAND_AND_COLLAPSE_DECKS'] = [None, True, False, ''' Expand and Collapse Decks ''', \
252 """ Свернуть/развернуть вÑÑ‘ дерево колод Ñразу """]
253
254A['MAXHEIGHT'] = [None, '100px', False, ''' Maximum images height in card editor (preview) ''', \
255 """ Ограничение картинок по выÑоте в полÑÑ… окна добавлениÑ/Ñ€ÐµÐ´Ð°ÐºÑ‚Ð¸Ñ€Ð¾Ð²Ð°Ð½Ð¸Ñ Ð·Ð°Ð¿Ð¸Ñей """]
256
257A['SEARCH_FROM_EDITOR'] = [None, True, False, ''' Search from Editor ''', \
258 """ ПоиÑк через Обзор Anki выделенного в окне правки текÑта """]
259
260A['ADDONS_INSTALL_TOOLTIP'] = [None, True, False, ''' Tools -> Add-ons -> Browse & Install... ''', \
261 """ OK notice without OK button now (showInfo 2 tooltip) """]
262
263##############################################################################
264# -- Customizable Congratulations Message --
265# You can set the message text which is displayed upon completion of the deck:
266CUSTOM_CONGRAT_MSG = [
267 ['en',u'<b>Congratulations! You have finished this deck for now.</b><br><br>Press <b><code>D</code></b> to go to <br><button onclick="py.link(\'decks\');" id=\"study\">Deck browser.</button>'],
268 ['ru',u"<b>Ðа данный момент в Ñтой колоде учить больше нечего.</b><br><br>Ðажмите клавишу <b><code>D</code></b> Ð´Ð»Ñ Ð¿ÐµÑ€ÐµÑ…Ð¾Ð´Ð° к ÑпиÑку колод карточек.<br><button onclick=\"py.link(\'decks\');\" id=\"study\">%s</button>" % _(u'Decks')],
269 ['it',u'<b>Congratulazioni! Hai completato questo mazzo per adesso.</b><br><br>Premere <b><code>D</code></b> per andare a Mazzi'],
270 ]
271
272# ПоздравлÑем! Ð’Ñ‹ завершили Ñту колоду на текущий момент.
273# Congratulations! You have finished this deck for now.
274# Congratulazioni! Hai completato questo mazzo per adesso.
275
276#CUSTOM_CONGRAT_MSG = False
277
278A['CUSTOM_CONGRAT_MSG'] = [None, True, False, ''' Customizable Congratulations Message ''', \
279 """ Заголовки на панели колод и пользовательÑкое Ñообщение о завершении колоды на ÑÐµÐ³Ð¾Ð´Ð½Ñ """]
280
281###########################################################################
282# It is done with the principle of minimal change in buttons functionality.
283
284# This addon maps
285# 2 buttons 1 Again 2 Good 3 Good 4 Good
286# 3 buttons 1 Again 2 Good 3 Good 4 Easy
287# 4 buttons 1 Again 2 Hard 3 Good 4 Easy
288
289# So
290# button 1 and
291# button 2 keep their original action anyway;
292# button 4 means maximum available easy anyway;
293# button 3 means Good anyway
294# (originally it means Easy in 3 buttons set,
295# it is the only change).
296
297# button 0 (Zero) is bound to Show Answer (on FrontSide)
298# button . (Period) is bound to Undo (on FrontSide and BackSide)
299# buttons , (Comma) z Z Ñ Ð¯ are bound to Undo, too.
300
301# -- Ignore space/enter when answer shown --
302# Ignore_spaceenter_when_answer_shown.py
303#
304# -- Refocus Card when Reviewing --
305# Enter keys are blocked on answer (BackSide)
306# to prevent pass through many cards at a moment.
307
308# Default button and Space/Enter,
309# Alt and Ctrl keys work correctly now.
310
311# -- Hint-peeking Keyboard Bindings --
312# button 5 or H peeks multiple hints hint by hint increasingly
313# https://ankiweb.net/shared/info/2616209911
314# If there is a way of showing multiple hints increasingly, it would be better.
315
316A['F9_HINT_PEEKING'] = [None, True, False, ''' Hint-peeking Keyboard Bindings is a way of showing multiple hints increasingly. ''', \
317 """ F9 и Shift+F9 Ð´Ð»Ñ Ð¿Ð¾Ñледовательного/одновременного Ð¾Ñ‚ÐºÑ€Ñ‹Ñ‚Ð¸Ñ Ð¿Ð¾Ð´Ñказок Ñ ÐºÐ»Ð°Ð²Ð¸Ð°Ñ‚ÑƒÑ€Ñ‹ """]
318
319F9_HINT_PEEKED = False
320
321A['KEYS_HANDLER'] = [None, True, False, ''' Apostophe marks note now, too. (tag:marked) ''', \
322 """ e, r and o hotkeys now works in uppercase E, R and O, too. """]
323
324# Actually v Replay own voice
325# and V (Shift+v) Record own voice
326# Caps Lock inverts logic:
327# Caps Lock and Shift+V mean Replay own voice
328# Caps Lock and V mean v Record own voice
329
330# -- Numeric Keypad Remapping --
331# https://ankiweb.net/shared/info/1247735360
332# . (dot)key Undo; buttons func. must be untouched(!)
333#
334# Use buttons 7 8 9 6 on numeric keyboard to right-handed reply.
335# Again - Show Answer - Good - Replay
336# You can switch it off right now.
337#
338A['NUMERIC_KEYPAD_REMAPPING'] = [None, True, False, ''' Numeric Keypad Remapping Use buttons 7 8 9 6 on numeric keyboard to right-handed reply. ''', \
339 """ Ð”Ð»Ñ ÑƒÐ´Ð¾Ð±Ñтва ответов Ñ Ñ†Ð¸Ñ„Ñ€Ð¾Ð²Ð¾Ð¹ клавиатуры назначены дополнительные клавиши 6 7 8 9 """]
340
341B['B12_HARD7'] = [None, False, True, \
342 ''' Use the key 7 on the numeric keypad as key 2 synonim (Hard) instead of key 1 (Again) if you answer `Hard` more often than `Again` ''', \
343 """ Удобно тем, кто чаще отвечает Трудно, чем Снова Ðе знаю. """]
344
345# -- Handy Answer Keys Shortcuts --
346A['RIGHT_HAND_JKL_ANSWER_KEYS_SHORTCUTS'] = [None, True, False, \
347 ''' Use buttons J K L ; on main keyboard to right-handed reply. ''', \
348 """ They work in uppercase, too. You can switch it off right now. """]
349
350# Set up variable ANSWER_BYPASS to True,
351A['ANSWER_BYPASS'] = [None, False, True, ''' if you need to reply on question (FrontSide) directly, ''', \
352 """ without looking at answer (BackSide). """]
353
354# Set up variable ANSWER_USING_REPLY_KEYS to True,
355A['ANSWER_USING_REPLY_KEYS'] = [None, False, True, \
356 ''' if you need to open an answer (BackSide) with any answer key ''', \
357 """ ANSWER_BYPASS take precedence over this one. """]
358# NB! To use this mode is not a good practice.
359
360# -- Bigger Show Answer Button --
361# https://ankiweb.net/shared/info/???
362# Makes the show answer button wide enough to cover all 4 of the review buttons.
363# -- Bigger Show All Answer Button --
364# https://ankiweb.net/shared/info/1867966335
365
366B['B06_WIDE_BUTTONS'] = [None, True, False, ''' Bigger Show All Answer Button ''', \
367 """ Makes the show answer button wide enough to cover all 4 of the review buttons. """]
368
369# -- Button Colours (Good, Again) --
370# https://ankiweb.net/shared/info/2494384865
371# Color buttons are just the same width as in Night Mode
372# Good button expands to empty place of absentee buttons Easy and Hard
373
374B['B07_COLOR_BUTTONS'] = [None, True, False, ''' Color Answer Buttons ''', \
375 """ Цветные кнопки оценки ответа """]
376
377# -- Packed with: --
378
379# -- Toggle Full Screen F11 --
380# https://ankiweb.net/shared/info/1703043345
381A['TOGGLE_FULL_SCREEN_F11'] = [None, True, False, ''' If you want to learn in Full-Sreen, use this. ''', \
382 """ Свернуть/развернуть на веÑÑŒ Ñкран окно Anki """]
383
384# -- Answer Confirmation plugin for Anki 2.0 --
385# https://ankiweb.net/shared/info/3882211885
386# Show pressed Answer Button as tooltip
387# only on pressed key, no tooltip on mouse click.
388
389B['B10_ANSWER_CONFIRMATION'] = [None, 2, 0, ''' =1 tooltip all answers ''', \
390 """ Ð’ÑÐ¿Ð»Ñ‹Ð²Ð°ÑŽÑ‰Ð°Ñ Ð¿Ð¾Ð´Ñказка при ответе Ñ ÐºÐ»Ð°Ð²Ð¸Ð°Ñ‚ÑƒÑ€Ñ‹. """]
391
392# -- Accept Anki 1.2 shortcuts to list decks, add cards and open browser --
393# https://ankiweb.net/shared/info/544525276
394# Allows you to use the old ctrl+d, ctrl+f and ctrl+w shortcuts.
395A['ANKI12SHORTCUTS'] = [None, True, False, ''' not ^D, Control+d excluded, ^F and ^W only. ''', \
396 """ Ðа манер Anki 1.2 Обзор Ctrl+F Колоды Ctrl+W но без Добавить Ctrl+D """]
397
398# -- Hierarchical Tags for Anki --
399# https://ankiweb.net/shared/info/1089921461
400# without any mod.
401A['HIERARCHICAL_TAGS'] = [None, True, False, ''' GroupName::TagName ''', \
402 """ ИерархичеÑкие метки """]
403
404# -- Deck name in title --
405# https://ankiweb.net/shared/info/3895972296
406# v1.2.0 there, v1.3.0 here (from addons-ospalh.zip; with filename.exe in title)
407A['DECK_NAME_IN_TITLE'] = [None, True, False, \
408 ''' Look for deck's name, profile name, cmd parameters in window title. ''', \
409 """ Ðазвание колоды, профилÑ, параметры запуÑка - в заголовоке окна Anki """]
410
411# -- Bigger Show Answer Button --
412# https://ankiweb.net/shared/info/1867966335
413# For people who do their reps with a mouse.
414# Makes the show answer button wide enough to cover all 4 of the review buttons.
415# -- CHK_06_WIDE_BUTTONS
416
417# https://ankiweb.net/shared/info/916405836
418# no comments. must have
419A['DISABLE_DEL'] = [None, True, False, ''' Disable the delete key in reviews ''', \
420 """ Ð—Ð°Ð¿Ñ€Ð¾Ñ Ð¿Ð¾Ð´Ñ‚Ð²ÐµÑ€Ð¶Ð´ÐµÐ½Ð¸Ñ ÑƒÐ´Ð°Ð»ÐµÐ½Ð¸Ñ Ð·Ð°Ð¿Ð¸Ñи по клавише Del во Ð²Ñ€ÐµÐ¼Ñ Ð¿Ñ€Ð¾Ñмотра карточки. """]
421
422# -- Rebuild All --
423# https://ankiweb.net/shared/info/1639597619
424# Menu command Tools - Rebuild All Filtered Decks is available always.
425# Button to the bottom of the main screen, to rebuild all filtered decks at once
426# is available on demand.
427# NB! Some extra <hr> is drawing!
428
429# -- Strikethrough button in editor window --
430# https://ankiweb.net/shared/info/999886206
431# Strike Through
432
433# https://ankiweb.net/shared/info/2062818289
434A['POWER_CREATE_LISTS'] = [None, True, False, ''' Power Create lists ordered unordered and indented O U In buttons in editor window ''', \
435 """ Удобные кнопочки в редакторе Ð´Ð»Ñ ÑÐ¾Ð·Ð´Ð°Ð½Ð¸Ñ ÑпиÑков """]
436
437# -- Deleting Reduant Configurations --
438# -- Removes Empty Note Types --
439# https://ankiweb.net/shared/info/248074683
440# https://ankiweb.net/shared/info/3867500866
441A['REMOVES_EMPTY'] = [None, True, False, ''' new line in Tools menu to Remove ALL Empty Note Types and Delete Redundant Configurations at once ''', \
442 """ Одной командой меню удалить пуÑтые типы запиÑей и группы параметров, а также вÑÑчеÑки протеÑтировать базу. """]
443
444# After some time you pile up a lot of note types that never get deleted?
445# This plugin takes care of it! After installing,
446# click on Tools -> Remove Empty Note Types.
447
448# == Warmest Regards and Much Gratitude: ==
449
450# Decks Total
451# https://ankiweb.net/shared/info/1421528223
452# Shows the total due, learn and new cards from all the decks in the deck browser.
453
454# more_shortcuts
455# from addons-ospalh.zip
456# Keys i (left-handed Dvorak) and 6 (NumPad) to Replay
457
458# Ignore space/enter when answer shown
459# https://ankiweb.net/shared/info/2160758119
460# Only holding of the Enter key goes through questions so fast.
461# There is no need to block the Space key.
462
463# Handy Answer Keys Shortcuts
464# https://ankiweb.net/shared/info/2090822731
465# elif _answerCard() stru., 3 vs cnt = .answerButtons()
466# J K L ; also should work in uppercase. z Z Ñ Ð¯ to Undo.
467
468# Answer Key Remap
469# https://ankiweb.net/shared/info/1446503737
470# Hard is not Again(!), Hard is Good; buttons func. must be untouched(!)
471
472# Answer Key Cascade
473# https://ankiweb.net/shared/info/992946134
474# 3 and 4 answer max Easy; 3 must be Good always(!)
475
476# 0 (Zero) Key to show answer
477# https://ankiweb.net/shared/info/191541298
478A['ZERO_KEY_TO_SHOW_ANSWER'] = [None, True, False, ''' 0 on question FrontSide == Show Answer 0 on answer BackSide == Answer Good and Show Next Card ''', \
479 """ Открывать ответ/Ñледующую карточку клавишей 0 Ñ Ñ†Ð¸Ñ„Ñ€Ð¾Ð²Ð¾Ð¹ клавиатуры """]
480
481#################################################################################
482# Press Gray+ to play next audio file on FrontSide of BackSide. ???
483###
484
485# Frozen Fields
486# Воробьиные Ð¿Ð¾Ð»Ñ Ð½Ð°Ð²Ñегда
487A['FROZEN_FIELDS'] = [None, True, False, ''' A more convenient way to mark fields as sticky. ''', \
488 """ ÐŸÐ¾Ð»Ñ Ð² окне Ð´Ð¾Ð±Ð°Ð²Ð»ÐµÐ½Ð¸Ñ ÐºÐ°Ñ€Ñ‚Ð¾Ñ‡ÐµÐº, ÑохранÑющие поÑледнее значение. """]
489
490A['RESET_CARD_SCHEDULING'] = [None, True, False, ''' Reset card scheduling information / progress ''', \
491 """ СброÑить раÑпиÑание """]
492
493A['OPEN_FOLDERS'] = [None, True, False, ''' Open local Anki folders in file explorer. ''', \
494 """ Команды меню на открытие Ñлужебных папок (директорий, каталогов) Anki в проводнике файловой ÑиÑтемы """]
495
496A['FLIP_FLOP'] = [None, True, False, ''' Show FrontSide/BackSide ''', \
497 """ Показать лицевую/оборотную Ñторону карточки """]
498
499A['RANDOM_ITEM'] = [None, True, False, ''' Random element from Field with commas. ''', \
500 """ Случайное значение из Ð¿Ð¾Ð»Ñ (ÑпиÑок через запÑтую или точку Ñ Ð·Ð°Ð¿Ñтой) """]
501
502A['RANDOM_SOUND'] = [None, True, False, ''' [sound:...][sound:...][sound:...] just once ''', \
503 """ Ð¡Ð»ÑƒÑ‡Ð°Ð¹Ð½Ð°Ñ Ð¾Ð·Ð²ÑƒÑ‡ÐºÐ° из Ð¿Ð¾Ð»Ñ [sound: могут идти без пробелов """]
504
505A['MAKE_LIST'] = [None, True, False, ''' Show {{Field with commas}} on the card as UL/OL. ''', \
506 """ Создать ÑпиÑок OL/UL из одно/двухуровневого ÑпиÑка в поле (,,,;,,) """]
507
508A['ADD_FIELDS_WITHOUT_DIV'] = [None, True, False, ''' Edit Cards Add field W/O DIV style=font ''', \
509 """ Редактирование Карточки Добавить поле Ðе заключать поле в тег DIV Ñ ÑƒÐºÐ°Ð·Ð°Ð½Ð¸ÐµÐ¼ шрифта"""]
510
511A['F6_SOUND_KEY_MENU'] = [None, True, False, ''' F6 pause ^F6 stop Alt+F6 back Shift+F6 forward 5 sec. ''', \
512 """ ОÑтановка долгой озвучки, промотка вперёд/назад на 5 Ñек. """]
513
514A['F6_FAST_SLOW'] = [None, True, False, ''' ^Shift+F6 fast ^Alt+F6 slow ^Alt+Shift+F6 100% ''', \
515 """ Ctrl+Shift+F6 быÑÑ‚. 10% Ctrl+Alt+F6 медл. 10% Ctrl+Alt+Shift+F6 норм. 100% """]
516
517A['SET_INTERVAL'] = [None, True, False, ''' Show card some days later. ''', \
518 """ УÑтановить Ñледующий показ через указанное количеÑтво дней/недель/меÑÑцев """]
519
520A['F4_EDIT'] = [None, True, False, ''' Edit card's templates ''', \
521 """ Редактирование шаблона карточки """]
522
523A['REBUILD_THEM_ALL'] = [None, True, False, ''' Rebuild ALL filtered decks ''', \
524 """ ПереÑтроить ВСЕ фильтр-колоды. """]
525
526A['ANKI_MENU_ICONS'] = [None, True, False, ''' Show menu icons ''', \
527 """ Показывать иконки в меню Anki """]
528
529A['KEY0'] = [None, True, False, ''' To show next card from NumPad. ''', \
530 """ Ðа лицевой Ñтороне - ничего, на оборотной - показать Ñледующую карточку. """]
531
532A['MULTIPLE_TYPING'] = [None, True, False, ''' Check multiple type with default space bar. ''', \
533 """ Проверка ввода в неÑколько полей. """]
534
535# -- Card Browser Lookup --
536# Look up selected word in Anki browser.
537#CARD_BROWSER_LOOKUP = True
538#CARD_BROWSER_LOOKUP = False
539
540A['SEARCH_BROWSER'] = [None, True, False, ''' Find out selected text in Anki's Browser. ''', \
541 """ ПоиÑк выделенного на карточке текÑта через Обзор Anki. """]
542
543A['SEARCH_TIME'] = [None, True, False, ''' Search cards based on review time ''', \
544 """ типа time:5 Ð´Ð»Ñ ÐºÐ°Ñ€Ñ‚Ð¾Ñ‡ÐµÐº Ñо временем ответа 5 Ñек. """]
545
546A['SMALL_ADD_EDIT_DIALOGS'] = [None, True, False, ''' No minimal width/height on Add/Edit window. ''', \
547 """ СнÑтие ограничений на размер окна добавлениÑ/правки запиÑи. """]
548
549A['TIMEBOX_TOOLTIP'] = [None, True, False, ''' Timebox tooltip w/o any askUser. ''', \
550 """ ПодÑказка о количеÑтве карточек за интервал времени уходит Ñама. """]
551
552##########################################################
553## Configuration section -- COLORFUL_TOOLBARS.py
554########################
555
556A['COLORFUL_TOOLBAR'] = [None, True, False, ''' Toolbars on cards. ''', \
557 """ Панели инÑтрументов по бокам карточек. """]
558
559try:
560 MUSTHAVE_COLOR_ICONS = os.path.join(mw.pm.addonFolder(), MUSTHAVE_COLOR_ICONS)
561except:
562 MUSTHAVE_COLOR_ICONS = ''
563
564# Keep this on False for tool bars on top or bottom or set it to True
565# for tool bars at the left an right. The left tool bar wil also get
566# smaller icons.
567netbook_version = True
568#netbook_version = False
569
570## Position of the new toolbar: either starting out above the old tool
571## bar and movable, or below the old tool bar. In that case it can't
572## be dragged to another position.
573qt_toolbar_movable = True
574#qt_toolbar_movable = False
575
576## Do or do not show a button that lets this be the last card reviewed.
577show_toggle_last = True
578#show_toggle_last = False
579
580## Do or do not show a mute button that stops Anki from playing
581## sound/videos initially.
582## NB. The mute is not absolute. When you push the replay button, the
583## sound still gets played.
584show_mute_button = True
585#show_mute_button = False
586
587## Show the suspend card button
588show_suspend_card = True
589#show_suspend_card = False
590
591## Show the suspend note button
592show_suspend_note = True
593#show_suspend_note = False
594
595# Show the tool bars with a gradient background
596#
597# In my opinion it looks a little bit nicer with gradient. The
598# disadvantage is that with the gradient the tool bars don't follow
599# color scheme changes untill you restart Anki.
600do_gradient = True
601#do_gradient = False
602
603GRADIENT_L = 111
604GRADIENT_H = 123
605
606################################
607# End of configuration section
608############################
609
610A['F9_HINT_PEEKING_5'] = [None, True, False, ''' Push 5 button to open next hint. ''', \
611 """ Кнопку 5 можно отключить отдельно. """]
612
613A['F9_HINT_PEEKING_H'] = [None, True, False, ''' Push H to hint hext. ''', \
614 """ Кнопку h тоже. """]
615
616A['DAY_LEARNING'] = [None, True, False, ''' Day learning cards always before new. ''', \
617 """ Изучаемые карточки Ñ Ð¸Ð½Ñ‚ÐµÑ€Ð²Ð°Ð»Ð¾Ð¼ Ñутки и более - вÑегда перед новыми. """]
618
619A['SWAP_FRONT_BACK'] = [None, True, False, ''' Swap Front/Back fields' values ''', """ Обмен значениÑми полей Ð’Ð¾Ð¿Ñ€Ð¾Ñ Ð¸ Ответ """]
620
621A['BROWSER_SEARCH_MODIFIERS'] = [None, True, False, ''' in Browse: CurrentDeck or Card1 only ''', \
622 """ в Обзоре: только Ñ‚ÐµÐºÑƒÑ‰Ð°Ñ ÐºÐ¾Ð»Ð¾Ð´Ð° или только Ð¿ÐµÑ€Ð²Ð°Ñ ÐºÐ°Ñ€Ñ‚Ð¾Ñ‡ÐºÐ° """]
623
624A['CLOZE_EDITOR_HOTKEYS'] = [None, True, False, ''' in Add/Edit: Ctrl+Alt+Space === Ctrl+Alt+Shift+C ''', \
625 """ в Редакторе: ^Space Ð´Ð»Ñ CLOSE как Ñиноним ^Shift+C """]
626
627###########################################
628# Setup initial values
629
630for key, value in A.iteritems():
631 A[key][0] = A[key][1]
632for key, value in B.iteritems():
633 B[key][0] = B[key][1]
634
635# -------------------------------------------------------------------
636from aqt.reviewer import Reviewer
637from aqt.qt import *
638import aqt.reviewer
639from anki.hooks import addHook, wrap, runHook
640from anki.lang import _
641from aqt.webview import AnkiWebView
642import aqt.stats
643
644from PyQt4.QtGui import *
645from PyQt4.QtCore import *
646
647soundtrack_q_number = 0
648soundtrack_q_list = []
649soundtrack_a_number = 0
650soundtrack_a_list = []
651
652###########################
653# Load user's variables
654###########################
655
656CREATE_MUSTHAVE_OPTIONS = True
657#CREATE_MUSTHAVE_OPTIONS = False
658
659READ_MUSTHAVE_OPTIONS = True
660#READ_MUSTHAVE_OPTIONS = False
661
662if CREATE_MUSTHAVE_OPTIONS:
663 line = ''
664
665 def NextLine( English_remark, Russian_comment, Constant_variable, Init_value, Alt_value ):
666 global line
667 str = unicode(Constant_variable)
668 if len(str)>0:
669 if len(English_remark.strip())>0:
670 line += u'# '+ unicode(English_remark.strip()) +'\n'
671 if len(Russian_comment.strip())>0 and lang=='ru':
672 line += u'# '+ unicode(Russian_comment.strip()) +'\n'
673
674 if isinstance(Init_value, basestring):
675 if Init_value.find("'")>=0 and Init_value.find('"')>=0:
676 line += "'" + str + "': '''" + unicode(Init_value) + "''', "
677 elif Init_value.find("'")>=0:
678 line += "'" + str + "': " + '"' + unicode(Init_value) + '", '
679 elif Init_value.find('"')>=0:
680 line += "'" + str + "': '" + unicode(Init_value) + "', "
681 else:
682 line += "'" + str + "': '" + unicode(Init_value) + "', "
683 else:
684 line += "'" + str + "': " + unicode(Init_value) + ", "
685
686 if isinstance(Alt_value, basestring):
687 if Alt_value.find("'")>=0 and Alt_value.find('"')>=0:
688 line += "# '''" + unicode(Alt_value) + "''', # "
689 elif Alt_value.find("'")>=0:
690 line += "# " + '"' + unicode(Alt_value) + '", # '
691 elif Alt_value.find('"')>=0:
692 line += "# '" + unicode(Alt_value) + "', # "
693 else:
694 line += "# '" + unicode(Alt_value) + "', # "
695 else:
696 line += "# " + unicode(Alt_value) + ', # '
697
698 line += '\n\n'
699
700 filo = os.path.join(mw.pm.addonFolder(), "--musthave-options.py")
701 if not os.path.exists(filo):
702 # filo without absolute path will be placed into `c:\Program Files (x86)\Anki` folder.
703 import codecs
704 f = codecs.open(filo, 'w', "utf-8")
705
706 line = u'# -*- mode: Python ; coding: utf-8 -*-\n'+\
707 u'# Place your Must Have configuration variables here.\n'+\
708 (u'# Ваши конÑтанты Ð´Ð»Ñ Ð´Ð¾Ð¿Ð¾Ð»Ð½ÐµÐ½Ð¸Ñ Must Have указывайте здеÑÑŒ:\n' if lang=='ru' else '')+'\n'
709
710 """
711 line += u"#HIDE_THEM_ALL = "+unicode(HIDE_THEM_ALL)+"\n"
712 line += u"#HIDE_THEM_ALL = "+unicode(not HIDE_THEM_ALL)+"\n\n"
713 """
714
715 line += u'deck_browser_standard_zoom = '+str(deck_browser_standard_zoom)+'\n'
716 line += u'overview_standard_zoom = '+str(overview_standard_zoom)+'\n'
717 line += u'reviewer_standard_zoom = '+str(reviewer_standard_zoom)+'\n\n'
718
719 line += u'#'+ '#'*66 +'\n'
720 line += u'# If you want to use alternative value' +'\n'
721 line += u'# simply delete first character # (sharp) on the subsequent line.' +'\n'
722 line += u'# Use Delete or Backspace key, to remove does not mean to replace with Space instead.' +'\n'
723 line += u'# Either you can place cursor right after # (sharp)' +'\n'
724 line += u'# and push Enter to split single line on two separate lines.' +'\n\n'
725
726 line += u'# '+ '-'*33 +'\n# Get language class\nimport anki.lang\nlang = anki.lang.getLang()\n\n'
727
728 line += u"FROM_LANGUAGE = '" + FROM_LANGUAGE + "'\n"
729 line += u"TO_LANGUAGE = '" + TO_LANGUAGE + "'\n\n"
730 line += u"FROM_LNG = '" + FROM_LNG + "' # 3 letters language code\n"
731 line += u"TO_LNG = '" + TO_LNG + "'\n\n"
732 line += u"FROM_LANG = '" + FROM_LANG + "' # 2 letters language code\n"
733 line += u"TO_LANG = '" + TO_LANG + "'\n\n"
734
735 """
736 line += u"#BUTTON_LABELS_LANG = [[lang, u'1', u'2', u'3', u'4']] # hotkeys \n"
737 line += u"#BUTTON_LABELS_LANG = [[lang, u'2', u'3', u'4', u'5']] # Russian school marks \n"
738 """
739
740 line += u"#BUTTON_LABELS_LANG = [[lang, _('Again'), _('Hard'), _('Good'), _('Easy')]] # in Translation \n"
741 line += u"#BUTTON_LABELS_LANG = [[lang, 'Again', 'Hard', 'Good', 'Easy']] # in English \n"
742 line += u"#BUTTON_LABELS_LANG = [[lang, 'Snova', 'Trudno', 'AGA', 'Legko']] # in translit \n"
743 line += u"#BUTTON_LABELS_LANG = [[lang, u'ÐЕТ', u'Трудно', u'ДÐ', u'Легко']] # in Russian anyhow \n"
744 line += u"#BUTTON_LABELS_LANG = [[lang, u'aw…', u'huh?', u'nice', u'radical!']] # in lowercase anyway \n"
745 line += u"#BUTTON_LABELS_LANG = [[lang, u'NO?', u'AH!', u'OK', u'SO…']] # in uppercase anywhere\n\n"
746
747 line += u"Z = { \n\n"
748
749 keylist = A.keys()
750 keylist.sort()
751 if HIDE_THEM_ALL:
752 for key in keylist:
753 A[key][0] = A[key][2]
754 NextLine( A[key][3], A[key][4], key, A[key][2], A[key][1] )
755 else:
756 for key in keylist:
757 A[key][0] = A[key][1]
758 NextLine( A[key][3], A[key][4], key, A[key][1], A[key][2] )
759
760 keylist = B.keys()
761 keylist.sort()
762 if HIDE_THEM_ALL:
763 for key in keylist:
764 B[key][0] = B[key][2]
765 NextLine( B[key][3], B[key][4], key, B[key][2], B[key][1] )
766 else:
767 for key in keylist:
768 B[key][0] = B[key][1]
769 NextLine( B[key][3], B[key][4], key, B[key][1], B[key][2] )
770
771 line += u'}\n # \n\n'
772
773 f.write(unicode(line))
774 f.close()
775
776if READ_MUSTHAVE_OPTIONS:
777 try:
778 #from musthave.options import * # works fine, but I need another filename
779 module = __import__("--musthave-options",globals(),locals(),['*'],-1)
780 for k in dir(module):
781 locals()[k] = getattr(module, k)
782 # from options import * # does not work (maybe options is Python's reserved name)
783 # A = Z
784 for key, value in Z.iteritems():
785 if len(key): # and hasattr(Z,key):
786 try:
787 A[key][0] = Z[key]
788 except KeyError:
789 try:
790 B[key][0] = Z[key]
791 except KeyError:
792 pass
793 except ImportError:
794 pass
795
796# -------------------------------------------------------------------
797# 0 - unknown
798# 1 - switch it off if original was found
799# 2 - fully compatible
800# 3 - original works anyway
801# 4 - incompatible
802# 5 - must have ingredients
803
804if A['CHECK_OLD_ISSUES'][0]:
805
806 old_issues = [
807 #['', '', '', '',1],
808 ['0_Zero_Key_to_show_answer.py', '0 (Zero) Key to show answer', '191541298', '2012-11-29',1],
809 ['Accept_Anki_12_shortcuts_to_list_decks_add_cards_and_open_browser.py',
810 'Accept Anki 1.2 shortcuts to list decks, add cards and open browser', '544525276', '2012-04-19',1],
811 ['Answer_Confirmation.py', 'Answer Confirmation', '3882211885', '2013-02-13',1],
812 ['Answer_Key_Cascade.py', 'Answer Key Cascade', '992946134', '2012-08-23',4],
813 ['Answer_Key_Remap.py', 'Answer Key Remap', '1446503737', '2013-07-28',4],
814 ['Audio_Playback_Speed.py', 'Audio Playback Speed', '234253523', '2015-03-25',1], # !!! 2016-03-06
815 ['Adjust_Audio_Speed.py', 'Adjust Audio Speed', '234253523', '2016-03-25',1], # !!!
816 ['Bigger_Show_Answer_Button.py', 'Bigger Show Answer Button', '1867966335', '2013-05-19',1],
817 ['Bigger_Show_All_Answer_Buttons.py', 'Bigger Show All Answer Buttons', '2034935033', '2015-02-06',1],
818 ['Browser_Search_Modifiers.py', 'Browser Search Modifiers', '594622823', '2016-04-18',1],
819 ['Button_Colours_Good_Again.py', 'Button Colours (Good, Again)', '2494384865', '2012-11-25',2],
820 ['Card_Browser_Lookup.py', 'Card Browser Lookup', '869824347', '2016-02-28',2],
821 # date of renaming to Search browser for selected words; original date is 2015-11-07
822 ['Card_Info_During_Review.py', 'Card Info During Review', '2179254157', '2013-12-08',1],
823 ['Card_time_forecast.py', 'Card time forecast', '2189699505', '2015-12-05',3],
824 ['Clickable_Tags_on_Reviewer.py', 'Clickable Tags on Reviewer', '1321188674', '2016-02-15',2], # 2016-01-26
825 ['colorful_toolbars.py', 'Colorful toolbars', '388296573', '2014-04-02',1],
826 ['Control_Audio_Playback_Pause_Skip_backwards_Skip_Forwards_Stop_Audio.py',
827 'Control Audio Playback (Pause, Skip backwards, Skip Forwards, Stop Audio)', '1591259314', '2014-10-30',1],
828 ['Custom_Keyboard_Shortcuts.py', 'Custom Keyboard Shortcuts', '1483821271', '2015-11-25',4],
829 ['Customizable_Congratulations_Message.py', 'Customizable Congratulations Message', '', '29.10.2015',3],
830 #['', '', '', '',2],
831 ['Deck_name_in_title.py', 'Deck name in title', '3895972296', '2013-04-01',3],
832 ['Deleting_Reduant_Configurations.py', 'Deleting Reduant Configurations', '3867500866', '2013-03-08',1],
833 ['Disable_the_delete_key_in_reviews.py', 'Disable the delete key in reviews', '916405836', '2013-12-17',1],
834 ['Expand_and_Collapse_Decks.py', 'Expand and Collapse Decks', '2554066128', '2013-10-24',2],
835 ['extra_card_stats_.py', 'extra card stats ×', '1581856587', '2016-03-09',3],
836 ['Field_Modifier_Random_Item.py', 'Field Modifier: Random Item', '1484572887', '2015-01-01',1],
837 ['Flip_cards_with_shortcut_key_0.py', 'Flip cards with shortcut key "0".', '844452602', '2016-02-11',1],
838 ['Force_custom_font.py', 'Force custom font', '2103013902', '2012-08-23',3],
839 ['Frozen Fields.py', 'Frozen Fields', '516643804', '2015-10-25',3],
840 ['Full_Screen_F11.py', 'Full Screen F11', '', '21.09.2015',1],
841 #['', '', '', '',4],
842 ['Handy_Answer_Keys_Shortcuts.py', 'Handy Answer Keys Shortcuts', '2090822731', '2013-07-06',3],
843 ['Handy_Hint_and_Answer_Keys.py', 'Handy Hint and Answer Keys', '', '22.10.2015',4],
844 ['HierarchicalTags.py', 'Hierarchical Tags', '1089921461', '2014-04-29',3],
845 ['Hint-peeking_Keyboard_Bindings.py', 'Hint-peeking Keyboard Bindings', '2616209911', '2012-08-20',1],
846 ['Ignore_enter_when_answer_shown.py', 'Ignore enter when answer shown', '', '24.10.2015',4],
847 ['Ignore_spaceenter_when_answer_shown.py', 'Ignore space/enter when answer shown', '2160758119', '2012-12-20',3],
848 ['Local_CSS_and_DIY_night_mode.py', 'Local CSS and DIY night mode', '2587372325', '2013-06-10',1],
849 ['Maximum_images_height_in_card_editor.py', 'Maximum images height in card editor', '229181581', '2015-04-13',1],
850 ['More_Overview_Stats.py', 'More Overview Stats', '2116130837', '2013-01-03',2],
851 ['More_Overview_Stats_2.py', 'More Overview Stats 2', '531984586', '2014-10-25',2],
852 ['Multiple_type_fields_on_card.py', 'Multiple type fields on card', '689574440', '2016-01-22',1],
853 ['Must_Have_Hint_and_Answer_Keys.py', 'Must Have Hint and Answer Keys', '', '28.12.2015',4],
854 ['Numeric_Keypad_Remapping.py', 'Numeric Keypad Remapping', '1247735360', '2014-07-11',4],
855 ['onesided_cards.py', 'One sided cards', '', '06.05.2015',1], # work both, standalone and builtin, do just the same.
856 ['play_button.py', 'Replay buttons on card', '498789867', '2014-07-31',1],
857 ['Power_Create_lists_ordered_unordered_and_indented.py',
858 'Power Create lists ordered unordered and indented', '', '08.08.2015',1],
859 ['put_ALL_due_learning_cards_first_.py', 'put ALL due "learning" cards first ×', '1810271825', '2016-03-07',3],
860 ['Quick_reschedule_in_reviewer.py', 'Quick reschedule in reviewer', '1190809692', '2016-01-30',2],
861 #['', '', '', '',3],
862 ['Rebuild_All.py', 'Rebuild All', '1639597619', '2015-09-28',2],
863 ['Refocus_Card_when_Reviewing.py', 'Refocus Card when Reviewing', '2061394997', '2013-01-27',2],
864 ['Remap_Answer_Key.py', 'Remap Answer Key', '', '24.10.2015',4],
865 ['Removes_Empty_Note_Types.py', 'Removes Empty Note Types', '248074683', '2016-01-15',1],
866 ['Reschedule_as_new_key_shortcut.py', 'Reschedule as new key shortcut', '2124687261', '2015-04-26',2],
867 ['reset_card_scheduling.py', 'Reset card(s) scheduling information / progress', '1432861881', '2016-01-28',2], # 2015-07-06
868 ['Search_browser_for_selected_words.py', 'Search browser for selected words', '869824347', '2016-02-28',1],
869 ['Search_cards_based_on_review_time.py', 'Search cards based on review time', '', '29.10.2015',1],
870 # '3262774902', '2014-01-04',1],
871 ['Search_from_Editor.py', 'Search from Editor', '1559436729', '2014-09-16',2],
872 ['Search_Google_Images_for_selected_words.py', 'Search Google Images for selected words', '800190862', '2015-03-21',2],
873 ['Select_Buttons_Automatically_If_Correct_Answer_Wrong_Answer_or_Nothing.py',
874 'Select Buttons Automatically If Correct Answer, Wrong Answer or Nothing', '2074758752', '2014-07-23',1],
875 ['Show_answers__default_ease_additional_key_binding.py',
876 'Show answers / default ease additional key binding', '730698933', '2013-01-08',1],
877 ['show_learn_count.py', 'Show learn count', '', '29.10.2015',1],
878 ['Small_add_cards_dialog.py', 'Small add cards dialog', '3285086934', '2012-10-06',1],
879 ['strikethrough.py', 'Strikethrough button in editor window', '999886206', '2014-10-06',2],
880 ['Toggle_Full_Screen.py', 'Toggle Full Screen', '1703043345', '2015-10-27',1],
881 ['Unseen_and_buried_counts.py', 'Unseen and buried counts', '161964983', '2014-11-06',3],
882 ['zoom.py', 'Zoom', '1956318463', '2013-06-10',1],
883 #['', '', '', '',0],
884 ['Clear_all_Editor_Fields.py', 'Clear all Editor Fields', '136533494', '2016-03-21',0],
885 ['Progress_Graph.py', 'Progress graph', '763339789', '2014-01-01',0],
886 ['Learning_Achievements_cumulative_total_for_younglearning_card_and_mature_card.py',
887 'Learning Achievements: cumulative total for young/learning card and mature card', '2093985093', '2014-02-09',0],
888 ['Separate_Learn_and_Relearn_in_the_Answer_Buttons_graph.py',
889 'Separate Learn and Relearn in the Answer Buttons graph', '1999018922 ', '2015-02-06',0],
890 ['Daily_Totals.py', 'Daily Totals', '1075380732', '2015-02-21',0],
891 ['Maturing_Cards.py', 'Maturing Cards', '1147586609', '2013-01-12',0],
892 ['Failed_Mature_Cards.py', 'Failed Mature Cards', '1314513660', '2013-01-12',0],
893 ['Stats_Expected_number_of_cards.py', 'Stats: Expected number of cards', '2464818309', '2013-01-23',0],
894 #['', '', '', '',5],
895 ['_Again_Hard.py', '• Again Hard', '1996229983', '2016-04-21',5], #2016-04-05
896 ['_Again_Hard_Good_Easy_wide_big_buttons.py', '• Again Hard Good Easy wide big buttons', '1508882486', '2016-04-21',5], #2016-04-09
897 ['_Alternative_hotkeys_to_cloze_selected_text_in_Add_or_Editor_window.py', '• Alternative hotkeys to cloze selected text in Add or Editor window', '2074653746', '2016-04-22',5], # 2016-04-11
898 ['_Day_learning_cards_always_before_new.py', '• Day learning cards always before new', '1331545236', '2016-04-21',5], #2016-03-18
899 ['_Flip-flop.py', '• Flip-flop', '519426347', '2016-04-21',5], #2016-03-02
900 ['_Insensitive_case_type_field.py', '• Insensitive case type field', '1616934891', '2016-04-21',5], #1 03.04.2016
901 ['_View_HTML_source_with_JavaScript_and_CSS_styles.py', '• View HTML source with JavaScript and CSS styles', '1128123950', '2016-04-21',5], #2016-04-09
902 ['_Prompt_and_set_days_interval.py', '• Prompt and set days interval', 2031109761, '2016-04-21',5],
903 ['_Swap.py', '• Swap', '1040866511', '2016-04-21',5], #2016-04-01
904 ['_Timebox_tooltip.py', '• Timebox tooltip', '2014169675', '2016-04-21', 5], #2016-03-02
905 ['_Young_Mature_Card_Fields.py', '• Young Mature Card Fields ', '1751807495', '2016-04-21',5], # 2016-04-07
906 ['_Zooming.py', '• Zooming', '1071179937', '2016-04-21',5], #2016-03-27
907 # There is no need to issue delete warning? #
908 ['Flip-flop.py', 'Flip-flop', '519426347', ' 21.04.2016. ',5], #2016-03-02
909 ['Timebox_tooltip.py', 'Timebox tooltip', '2014169675', ' 21.04.2016 ', 5], # 2016-03-02
910 #['', '', '', '',4],
911 #['More_Answer_Buttons_for_New_Cards.py', 'More Answer Buttons for New Cards', '153603893', '2016-04-03',4],
912 #['Open_Added_Today_from_Reviewer.py', 'Open 'Added Today' from Reviewer', '861864770', '2016-04-03',4],
913 #['Hide_Toolbar_in_Reviewer.py', 'Hide Toolbar in Reviewer', '701296409', '2016-04-03',4],
914 #['Deck_Overview_Stats_Tooltip.py', 'Deck Overview Stats Tooltip', '1279297937', '2016-04-03',4],
915 #['Browse_Card_Creation.py', 'Browse Card Creation', '2075482801', '2016-04-03',4],
916 ]
917 # Продолжение да воÑпоÑледует.
918
919 # 31.12.2015 packed ~42; total 243 add-ons https://ankiweb.net/shared/addons/
920 # 29.02.2016 packed ~61; total 254 add-ons https://ankiweb.net/shared/addons/
921
922"""
923Answer_Key_Cascade.py
924 a higher ease than possible will answer with the actual highest
925
926Answer_Key_Remap.py
927 Again = 1,2 Good = 3,4
928
929Control_Audio_Playback_Pause_Skip_backwards_Skip_Forwards_Stop_Audio.py
930 5 6 7 8 n
931 pause seek -5 seek +5 stop pause
932
933Custom_Keyboard_Shortcuts.py
934 dsabSy dummy
935
936Disable_the_delete_key_in_reviews.py
937
938Handy_Answer_Keys_Shortcuts.py
939 JKL; answer 1234 right way: 34 anwer Easy maximum available.
940 on FrontSide these keys send answer directly
941
942Hint-peeking_Keyboard_Bindings.py
943 SHOW_HINT_KEY=Qt.Key_H
944 To show hint, simply click all show hint buttons.
945
946Numeric_Keypad_Remapping.py
947 EASE_SHIFT = True
948 UNDO_WITH_PERIOD = True
949 ANSWER_KEY_CASCADE = True
950 SHOW_ANSWER_USING_NUMERICAL_KEYS = True
951 ???!!!
952
953Show_answers__default_ease_additional_key_binding.py
954 5 self._showAnswerHack(),
955 5 self._answerCard(self._defaultEase())
956"""
957
958if A['CHECK_OLD_ISSUES'][0]:
959
960 very_interesting = [
961 ['', '', '', '',0],
962 ['dt.py', 'Decks Total', '1421528223', '2012-12-15',0],
963 # There is no ru_RU Russian in LANG list of localisation.
964
965 ['Get_context_sentence_for_language_learning_-Tatoeba_lookup_on_right-click.py',
966 'Get context sentence for language learning -Tatoeba lookup on right-click', '443435286', '2014-08-18',0],
967 ['', '', '', '',0],
968 ['advanced_browser.py', 'Advanced Browser', '874215009', '2016-02-10',0],
969 ['AwesomeTTS.py', 'AwesomeTTS (text-to-speech playback / recording)', '301952613', '2016-01-17',0],
970 ['download_audio.py', 'Download audio', '3100585138', '2015-10-28',0],
971 ['Ignore_accents_in_browser_search.py', 'Ignore accents in browser search', '1924690148', '2014-09-28',0],
972 ['load_balancer.py', 'Load Balancer', '1417170896', '2015-09-11',0],
973 ['Night_Mode.py', 'Night Mode', '1496166067', '2016-03-05',0], # 2016-01-30 2015-12-16
974
975 ['', '', '', '',0],
976 ['Supplementary Buttons Anki.py', 'Power format pack: Markdown, code blocks, lists, tables, syntax highlight & more', '162313389', '2016-02-10',0],
977 # 2016-01-28
978
979 ['', 'Quick Tagging', '1562438127', '2014-10-11',0],
980 ['', 'Wikitext: format text with Creole markup', '960358571', '2015-05-13',0],
981 ['', 'Quick Colour Changing', '2491935955', '2012-04-22',0],
982 ['', 'AutoDefine - Automatically define vocabulary words with pronunciations and image', '2136497008', '2014-10-18',0],
983 ['Multi-column_note_editor.py', 'Multi-column note editor', '3491767031', '2013-11-10',0],
984 ['', '', '', '',0],
985 #['CSS_formatting.py', 'CSS formatting', '83944088', '2016-02-28',0],
986 ['CSS_styles_formatting.py', 'CSS styles formatting', '83944088', '2016-03-17',0],
987 ['', 'Don\'t remove mark on export', '909480379', '2014-04-16',0], #
988 ['', 'Change Review Key Shortcuts', '1057034736', '07.11.2012',0],
989 ['', 'Quick note and deck buttons', '2181333594', '2015-11-13',0],
990 ['', 'Export Cards As Text', '1589071665', '2014-09-14',0],
991 ['', 'Create Copy of Selected Cards', '787914845', '2015-06-28',0],
992 ['Tag_Toggler.py', 'Tag Toggler', '874498171', '2016-03-31',0], # 2016-02-29
993 ['', 'Transfer Deck', '1921587528', '2014-05-14',0],
994 ['', '', '', '',0],
995 ]
996 # To be continued.
997
998# ---------------------------------
999#
1000if A['CHECK_OLD_ISSUES'][0]:
1001 for idn_issue in range(0, len(old_issues)):
1002 old_issue = old_issues[idn_issue]
1003 if len(old_issue[0]) > 0:
1004 old_filename = os.path.join(mw.pm.addonFolder(), old_issue[0])
1005 old_issues[idn_issue].append(os.path.exists(old_filename))
1006
1007 def found(addon_name):
1008 for old_issue in old_issues:
1009 if addon_name == old_issue[0]:
1010 return old_issue[5]
1011 else:
1012 showCritical('''<center><i>• Must Have:</i> ТребуетÑÑ Ð¾Ð±Ñзательное внеÑение в ÑпиÑок<br> <b>%s</b>
1013<br>ПроÑто Ð½Ð°Ð»Ð¸Ñ‡Ð¸Ñ Ñ„Ð°Ð¹Ð»Ð° в каталоге дополнений недоÑтаточно!!!</center>'''%addon_name)
1014 return False
1015
1016 A['DAY_LEARNING'][0] = False if found('_Day_learning_cards_always_before_new.py') else A['DAY_LEARNING'][0]
1017 A['SWAP_FRONT_BACK'][0] = False if found('_Swap.py') else A['SWAP_FRONT_BACK'][0]
1018 #
1019 A['FLIP_FLOP'][0] = False if found('Flip-flop.py') else A['FLIP_FLOP'][0]
1020 A['FLIP_FLOP'][0] = False if found('_Flip-flop.py') else A['FLIP_FLOP'][0]
1021 A['FLIP_FLOP'][0] = False if found('0_Zero_Key_to_show_answer.py') else A['FLIP_FLOP'][0]
1022 A['FLIP_FLOP'][0] = False if found('Flip_cards_with_shortcut_key_0.py') else A['FLIP_FLOP'][0]
1023 #
1024 A['CLOZE_EDITOR_HOTKEYS'][0] = False if found('_Alternative_hotkeys_to_cloze_selected_text_in_Add_or_Editor_window.py') else A['CLOZE_EDITOR_HOTKEYS'][0]
1025 #
1026 A['COLORFUL_TOOLBAR'][0] = False if found('colorful_toolbars.py') else A['COLORFUL_TOOLBAR'][0]
1027 B['B13_EDIT_MORE'][0] = False if found('colorful_toolbars.py') else B['B13_EDIT_MORE'][0]
1028 #
1029 CUSTOM_CONGRAT_MSG = False if found('Customizable_Congratulations_Message.py') else CUSTOM_CONGRAT_MSG
1030 A['CUSTOM_CONGRAT_MSG'][0] = False if found('Customizable_Congratulations_Message.py') else A['CUSTOM_CONGRAT_MSG'][0]
1031 #
1032 A['BROWSER_SEARCH_MODIFIERS'][0] = False if found('Browser_Search_Modifiers.py') else A['BROWSER_SEARCH_MODIFIERS'][0]
1033 #
1034 A['DISABLE_DEL'][0] = False if found('Disable_the_delete_key_in_reviews.py') else A['DISABLE_DEL'][0]
1035 #
1036 A['KEY0'][0] = False if found('0_Zero_Key_to_show_answer.py') else A['KEY0'][0]
1037 A['KEY0'][0] = False if found('Flip_cards_with_shortcut_key_0.py') else A['KEY0'][0]
1038 #
1039 A['ANKI12SHORTCUTS'][0] = False if found('Accept_Anki_12_shortcuts_to_list_decks_add_cards_and_open_browser.py') else A['ANKI12SHORTCUTS'][0]
1040 B['B10_ANSWER_CONFIRMATION'][0] = 0 if found('Answer_Confirmation.py') else B['B10_ANSWER_CONFIRMATION'][0]
1041 #
1042 A['F3_CARD_INFO_DURING_REVIEW'][0] = False if found('Card_Info_During_Review.py') else A['F3_CARD_INFO_DURING_REVIEW'][0]
1043 A['F3_HTML_SOURCE'][0] = False if found('_View_HTML_source_with_JavaScript_and_CSS_styles.py') else A['F3_HTML_SOURCE'][0]
1044 #
1045 A['F6_SOUND_KEY_MENU'][0] = False if found('Control_Audio_Playback_Pause_Skip_backwards_Skip_Forwards_Stop_Audio.py') else A['F6_SOUND_KEY_MENU'][0]
1046 #
1047 A['F6_FAST_SLOW'][0] = False if found('Audio_Playback_Speed.py') else A['F6_FAST_SLOW'][0]
1048 A['F6_FAST_SLOW'][0] = False if found('Adjust_Audio_Speed.py') else A['F6_FAST_SLOW'][0]
1049 #
1050 A['F9_HINT_PEEKING_5'][0] = False if found('Show_answers__default_ease_additional_key_binding.py') or found('Control_Audio_Playback_Pause_Skip_backwards_Skip_Forwards_Stop_Audio.py') else A['F9_HINT_PEEKING_5'][0]
1051 A['F9_HINT_PEEKING_H'][0] = False if found('Hint-peeking_Keyboard_Bindings.py') else A['F9_HINT_PEEKING_H'][0]
1052 #
1053 A['NUMERIC_KEYPAD_REMAPPING'][0] = False if found('Adjust_Audio_Speed.py') else A['NUMERIC_KEYPAD_REMAPPING'][0]
1054 A['NUMERIC_KEYPAD_REMAPPING'][0] = False if found('Audio_Playback_Speed.py') else A['NUMERIC_KEYPAD_REMAPPING'][0]
1055 # 16.03.2016 Audio_Playback_Speed.py don't use number keys any more.
1056 A['NUMERIC_KEYPAD_REMAPPING'][0] = False if found('Control_Audio_Playback_Pause_Skip_backwards_Skip_Forwards_Stop_Audio.py') else A['NUMERIC_KEYPAD_REMAPPING'][0]
1057 #
1058 A['RANDOM_ITEM'][0] = False if found('Field_Modifier_Random_Item.py') else A['RANDOM_ITEM'][0]
1059 #
1060 A['MAXHEIGHT'][0] = False if found('Maximum_images_height_in_card_editor.py') else A['MAXHEIGHT'][0]
1061 #
1062 B['B00_MORE_OVERVIEW_STATS'][0] = 0 if found('show_learn_count.py') else B['B00_MORE_OVERVIEW_STATS'][0]
1063 #
1064 A['MULTIPLE_TYPING'][0] = False if found('Multiple_type_fields_on_card.py') else A['MULTIPLE_TYPING'][0]
1065 A['MULTIPLE_TYPING'][0] = False if found('Select_Buttons_Automatically_If_Correct_Answer_Wrong_Answer_or_Nothing.py') else A['MULTIPLE_TYPING'][0]
1066 #
1067 A['ONESIDED_CARDS'][0] = False if found('onesided_cards.py') else A['ONESIDED_CARDS'][0]
1068 #
1069 A['REMOVES_EMPTY'][0] = False if found('Deleting_Reduant_Configurations.py') else A['REMOVES_EMPTY'][0]
1070 A['REMOVES_EMPTY'][0] = False if found('Removes_Empty_Note_Types.py') else A['REMOVES_EMPTY'][0]
1071 #
1072 A['REPLAY_BUTTONS_ON_CARD'][0] = 0 if found("play_button.py") else A['REPLAY_BUTTONS_ON_CARD'][0]
1073 A['MULTIPLE_TYPING'][0] = 0 if found("play_button.py") else A['MULTIPLE_TYPING'][0]
1074 #
1075 A['SEARCH_BROWSER'][0] = False if found('Search_browser_for_selected_words.py') else A['SEARCH_BROWSER'][0]
1076 #
1077 A['SEARCH_TIME'][0] = False if found('Search_cards_based_on_review_time.py') else A['SEARCH_TIME'][0]
1078 #
1079 A['SMALL_ADD_EDIT_DIALOGS'][0] = False if found('Small_add_cards_dialog.py') else A['SMALL_ADD_EDIT_DIALOGS'][0]
1080 #
1081 A['SET_INTERVAL'][0] = False if found('_Prompt_and_set_days_interval.py') else A['SET_INTERVAL'][0]
1082 #
1083 A['TIMEBOX_TOOLTIP'][0] = False if found('_Timebox_tooltip.py') else A['TIMEBOX_TOOLTIP'][0]
1084 A['TIMEBOX_TOOLTIP'][0] = False if found('Timebox_tooltip.py') else A['TIMEBOX_TOOLTIP'][0]
1085 #
1086 A['TOGGLE_FULL_SCREEN_F11'][0] = False if found('Full_Screen_F11.py') else A['TOGGLE_FULL_SCREEN_F11'][0]
1087 A['TOGGLE_FULL_SCREEN_F11'][0] = False if found('Toggle_Full_Screen.py') else A['TOGGLE_FULL_SCREEN_F11'][0]
1088 #
1089 A['LOCAL_CSS_AND_DIY_NIGHT_MODE'][0] = False if found('Local_CSS_and_DIY_night_mode.py') else A['LOCAL_CSS_AND_DIY_NIGHT_MODE'][0]
1090 A['POWER_CREATE_LISTS'][0] = False if found('Power_Create_lists_ordered_unordered_and_indented.py') else A['POWER_CREATE_LISTS'][0]
1091 #
1092 B['B06_WIDE_BUTTONS'][0] = False if found('Bigger_Show_All_Answer_Buttons.py') or found('Bigger_Show_Answer_Button.py') else B['B06_WIDE_BUTTONS'][0]
1093 #
1094 A['ZOOM'][0] = False if found('zoom.py') else A['ZOOM'][0]
1095 A['ZOOM'][0] = False if found('_Zooming.py') else A['ZOOM'][0]
1096 #
1097
1098###########################################
1099# Setup alternative values
1100
1101if HIDE_THEM_ALL:
1102
1103 for key in A.keys():
1104 A[key][0] = A[key][2]
1105 for key in B.keys():
1106 B[key][0] = B[key][2]
1107
1108 # --
1109 BUTTON_LABELS_LANG = [[lang, _('Again'), _('Hard'), _('Good'), _('Easy')]] # in Translation
1110 BUTTON_LABELS = BUTTON_LABELS_LANG
1111 CUSTOM_CONGRAT_MSG = False
1112 #"""
1113 do_gradient = False
1114 netbook_version = False
1115 qt_toolbar_movable = False
1116 show_toggle_last = False
1117 show_mute_button = False
1118 show_suspend_card = False
1119 show_suspend_note = False
1120 do_gradient = False
1121 #"""
1122
1123 pass # in case if each parameter will be commented out
1124
1125# -------------------------------------------------------------------
1126
1127if not A['NUMERIC_KEYPAD_REMAPPING'][0]:
1128 A['ZERO_KEY_TO_SHOW_ANSWER'][0] = False
1129
1130if B['B00_MORE_OVERVIEW_STATS'][0] > 1 and B['B00_MORE_OVERVIEW_STATS'][0] < 3:
1131 B['B03_GEAR_AT_END_OF_LINE'][0] = True
1132
1133# If we do not enhance deck panel's columns then we shouldn't enhance it's font.
1134if (B['B00_MORE_OVERVIEW_STATS'][0] == 0):
1135 A['FONT'][0] = ""
1136 A['FONTSIZE'][0] = 0
1137
1138# --
1139def initEditMore(editMore):
1140 B['B13_EDIT_MORE'][0] = editMore
1141 if not editMore:
1142 mw.reviewer._bottomCSS += MORE_EDIT
1143 else:
1144 mw.reviewer._bottomCSS = re.sub(EDIT_MORE, "", mw.reviewer._bottomCSS)
1145
1146initEditMore(B['B13_EDIT_MORE'][0])
1147
1148def onEditMore():
1149 B['B13_EDIT_MORE'][0] = edit_more_action.isChecked()
1150 if not edit_more_action.isChecked():
1151 mw.reviewer._bottomCSS += MORE_EDIT
1152 else:
1153 mw.reviewer._bottomCSS = re.sub(EDIT_MORE, "", mw.reviewer._bottomCSS)
1154
1155 if mw.state == "review":
1156 #mw.reviewer._initWeb()
1157 mw.moveToState("review")
1158
1159# -- width of Show Answer button, triple, double and single answers buttons in pixels
1160BEAMS4 = "99%"
1161BEAMS3 = "74%"
1162BEAMS2 = "48%"
1163BEAMS1 = "24%"
1164
1165# -------------------------------------------------------------------
1166# TOH
1167#
1168
1169HOTKEY = { # in mw Main Window (deckBrowser, Overview, Reviewer)
1170 'F6_pause' : ["F6", 'Alt+A, P ', "Alt+З, П ", ''' ''', """ """],
1171 'F6_stop' : ["Ctrl+F6", 'Alt+A, S ', "Alt+З, С ", ''' ''', """ """],
1172 'F6_forw' : ["Shift+F6", 'Alt+A, F ', "Alt+З, В ", ''' ''', """ """],
1173 'F6_back' : ["Alt+F6", 'Alt+A, B ', "Alt+З, Р", ''' ''', """ """],
1174 'F6_fast' : ["Ctrl+Shift+F6", '', "", ''' ''', """ """],
1175 'F6_slow' : ["Ctrl+Alt+F6", '', "", ''' ''', """ """],
1176 'F6_init' : ["Ctrl+Alt+Shift+F6", '', "", ''' ''', """ """],
1177 'F2_undo' : ["F2", '', "Alt+К, О", ''' ''', """ """],
1178 'F2_ctrl' : ["Ctrl+Alt+Space", '', "Alt+К, Ч", ''' ''', """ """], # ^F2
1179 'F2_card_reschedule' : ["Shift+Space", '', "Alt+К, Ð", ''' ''', """ """], # ~F2
1180 'F2_note_reschedule' : ["Ctrl+Shift+Space", '', "Alt+К, З", ''' ''', """ """], # ~F2
1181 'F3_history' : ["F3", '', "Alt+К, И", ''' ''', """ """], # View
1182 'F3_HTML_source' : ["Ctrl+F3", '', "", ''' ''', """ """],
1183 'F3_Body_source' : ["Alt+F3", '', "Alt+К, Х", ''' ''', """ """],
1184 'F3_card_stats' : ["Shift+F3", '', "Alt+К, П", ''' ''', """ """],
1185 'F3_A' : ["Ctrl+F4", 'Alt+H, R', "Alt+П, У", ''' Help → A variables ''', """ Помощь ⇒ УправлÑющие переменные """],
1186 'F4_edit' : ["F4", '', "Alt+К, Е", ''' ''', """ """],
1187 'F4_edit_layout' : ["Shift+F4", '', "Alt+К, К", ''' ''', """ """],
1188 'edit_layout' : ["Shift+E", '', "", ''' ''', """ """],
1189 'goto_stats' : ["S", '', "", ''' ''', """ """],
1190 'goto_deck' : ["Alt+S", '', "", ''' ''', """ """],
1191 'delete_them_all' : ["Ctrl+Alt+Shift+P", '', "", ''' ''', """ """],
1192 'E' : ["E", '', "", ''' ''', """ """], # Edit
1193 'marked' : ["Shift+F8", '', "", ''' ''', """ """],
1194 'META_Minus' : ["Ctrl+Alt+Shift+-", '', "", ''' ''', """ """],
1195 'autoplay' : ["Ctrl+Alt+Shift+F5", '', "", ''' ''', """ """],
1196 'FrontSide' : ["Ctrl+PgUp", '', "", ''' ''', """ """],
1197 'BackSide' : ["Ctrl+PgDown", '', "", ''' ''', """ """],
1198 'F7_FrontSide' : ["F7", '', "", ''' ''', """ """],
1199 'F8_BackSide' : ["F8", '', "", ''' ''', """ """],
1200 'FrontPage' : ["Ctrl+9", '', "", ''' ''', """ """],
1201 'BackPage' : ["Ctrl+3", '', "", ''' ''', """ """],
1202 'FrontPack' : ["Ctrl+Up", '', "", ''' ''', """ """],
1203 'BackPack' : ["Ctrl+Down", '', "", ''' ''', """ """],
1204 'FrontUp' : ["Ctrl+8", '', "", ''' ''', """ """],
1205 'BackDown' : ["Ctrl+2", '', "", ''' ''', """ """],
1206 'Editor' : ["Ctrl+E", '', "", ''' ''', """ """],
1207 'Statistics' : ["Shift+S", '', "", ''' ''', """ """],
1208 'Rebuild_Them_All' : ["Ctrl+Shift+F", '', "", ''' ''', """ """],
1209 'zoom_info' : ["Alt+0", '', "", ''' ''', """ """],
1210 'zoom_in' : ["Ctrl++", '', "", ''' ''', """ """],
1211 'zoom_out' : ["Ctrl+-", '', "", ''' ''', """ """],
1212 'zoom_reset' : ["Ctrl+0", '', "", ''' ''', """ """],
1213 'zoom_init' : ["Ctrl+Alt+0", '', "", ''' ''', """ """],
1214 'toggle_last' : ["Ctrl+Space", '', "", ''' ''', """ """],
1215 'expand_decks' : ["Ctrl+Shift++", '', "", ''' ''', """ """],
1216 'collapse_decks' : ["Ctrl+Shift+-", '', "", ''' ''', """ """],
1217 'download_addon' : ["Ctrl+Shift+Insert", '', "", ''' ''', """ """],
1218 'download_addons' : ["Ctrl+Alt+Shift+Insert", '', "", ''' ''', """ """],
1219 'next_hint' : ["F9", '', "", ''' ''', """ """],
1220 'all_hints' : ["Shift+F9", '', "", ''' ''', """ """],
1221 'bury_card' : ["-", '', "", ''' ''', """ """],
1222 'bury_note' : ["Shift+-", '', "", ''' ''', """ """],
1223 'suspend_card' : ["Alt+-", '', "", ''' ''', """ """],
1224 'suspend_note' : ["Alt+Shift+-", '', "", ''' ''', """ """],
1225 'audio_list' : ["Alt+F5", '', "", ''' ''', """ """],
1226 'replay_next' : ["Shift+F5", '', "", ''' ''', """ """],
1227 'full_screen' : ["F11", '', "", ''' ''', """ """],
1228 'swap' : ["F12", '', "", ''' ''', """ """],
1229}
1230
1231HOTKEYS = { # in Editor window
1232 'Freeze' : ["F9", '', "", ''' ''', """ """],
1233 'Frozen' : ["Shift+F9", '', "", ''' ''', """ """],
1234 'strike' : ["Ctrl+s", '', "", ''' ''', """ """],
1235 'ordered' : ["Ctrl+Shift+O", '', "", ''' ''', """ """],
1236 'unordered' : ["Ctrl+Shift+U", '', "", ''' ''', """ """],
1237 'indent' : ["Ctrl+Shift+I", '', "", ''' ''', """ """],
1238 'FE' : ["Ctrl+F", '', "", ''' ''', """ """],
1239 'nextCloze' : ["Ctrl+Space", '', "", ''' ''', """ """],
1240 'sameCloze' : ["Ctrl+Alt+Space", '', "", ''' ''', """ """],
1241 'showHTML' : ["F4", '', "", ''' ''', """ """],
1242}
1243
1244HOTKEYZ = { # Anki's default keyz: for information purpose only
1245 'Study_deck' : ["Ctrl+Shift+/", 'Alt+T, <br>Enter', "Alt+И, <br>Enter", ''' Tools -> Study deck ''', """ ИнÑтрументы -> Учить колоду... """],
1246 'Import' : ["Ctrl+Shift+I", 'Alt+F, I', "Alt+Ф, И", ''' File → Import...''', """ Файл ⇒ Импортировать... """],
1247 'Export' : ["Ctrl+Shift+E", 'Alt+F, E', "Alt+Ф, Ð", ''' File → Export...''', """ Файл ⇒ ÐкÑпортировать... """],
1248 'Decks' : ["Ctrl+D", 'Alt+G, D', "Alt+Е, Д", ''' Go → Decks ''', """ Переход ⇒ """],
1249 'Add' : ["Ctrl+A", 'Alt+G, A', "Alt+Е, Ð", ''' Go → Add ''', """ Переход ⇒ """],
1250 'Browse' : ["Ctrl+B", 'Alt+G, B', "Alt+Е, Б", ''' Go → Browse ''', """ Переход ⇒ """],
1251 'Sync' : ["Ctrl+Y", 'Alt+G, Y', "Alt+Е, Ð¥", ''' Go → Synchronize with AnkiWeb ''', """ Переход ⇒ Синхронизировать Ñ AnkiWeb """],
1252 'Synchro' : ["Ctrl+Shift+Y", 'Alt+F, Y', "Alt+Ф, Ð¥", ''' File → Synchronize with AnkiWeb ''', """ Файл ⇒ Синхронизировать Ñ AnkiWeb """],
1253 'record_own_voice' : ["Shift+V", 'Alt+A, C', "Alt+З, З", ''' Audio → ''', """ Звук ⇒ """],
1254 'replay_own_voice' : ["V", 'Alt+A, P', "Alt+З, Ð’", ''' Audio → ''', """ Звук ⇒ """],
1255 'options' : ["O", 'Alt+E, O', "Alt+Ð , Ð", ''' Edit → Options ''', """ Редактирование ⇒ """],
1256}
1257
1258# -------------------------------------------------------------------
1259# Field_Modifier_Random_Item.py
1260# Field Modifier: Random Item
1261# https://ankiweb.net/shared/info/1484572887
1262
1263# Picks a random item from the field. Useful for images. Usage:
1264# {{random:Field Name}}
1265# Items are separated by commas.
1266
1267import random
1268
1269from anki.cards import Card
1270from anki import hooks
1271
1272if A['RANDOM_ITEM'][0]:
1273
1274 def fmod_random(txt, extra, context, tag, fullname):
1275 return random.choice([x.strip() for x in txt.split(',')])
1276
1277 addHook('fmod_random', fmod_random)
1278
1279# -- Cool Idea, would be better if it worked on mobile
1280# Great idea, but troublesome if you frequently use the app rather than desktop version
1281# since the random function doesn't work and you end up with a list separated by commas
1282
1283if A['RANDOM_SOUND'][0]:
1284
1285 def fmod_sound(txt, extra, context, tag, fullname):
1286 _soundReg = "(\[sound:.*?\])"
1287
1288 if txt.find('[sound:')>=0:
1289 #return random.choice(['[sound:'+x.strip() for x in txt.split('[sound:')][1:])
1290 #так вывозит, только еÑли между звуками ничего нет или лишь пробелы
1291 return random.choice(re.findall(_soundReg, txt))
1292 else:
1293 return txt
1294
1295 addHook('fmod_sound', fmod_sound)
1296
1297#_soundReg = "\[sound:(.*?)\]"
1298#
1299#def playFromText(text):
1300# for match in re.findall(_soundReg, text):
1301# play(match)
1302#
1303#def stripSounds(text):
1304# return re.sub(_soundReg, "", text)
1305#
1306#def hasSound(text):
1307# return re.search(_soundReg, text) is not None
1308
1309if A['MAKE_LIST'][0]:
1310
1311 def fmod_list(txt, extra, context, tag, fullname):
1312 retv = ''
1313 if txt.find(';')>=0:
1314 for x in txt.split(';'):
1315 xx = x.strip()
1316 if len(xx)>0:
1317 retv += '<li>'+xx+'</li>'
1318 return ('<ul>'+retv+'</ul>' if len(retv)>0 else '')
1319 elif txt.find(',')>=0:
1320 for x in txt.split(','):
1321 xx = x.strip()
1322 if len(xx)>0:
1323 retv += '<li>'+xx+'</li>'
1324 return ('<ol>'+retv+'</ol>' if len(retv)>0 else '')
1325 else:
1326 return txt
1327
1328 def fmod_sublist(txt, extra, context, tag, fullname):
1329 retv = ''
1330 if txt.find(';')>=0:
1331 for x in txt.split(';'):
1332 x = x.strip()
1333 if x.find(',')>=0:
1334 retv += '<li><ul>'
1335 for xx in x.split(','):
1336 xxx = xx.strip()
1337 if len(xxx)>0:
1338 retv += '<li>'+xxx+'</li>'
1339 retv += '</ul></li>'
1340 else:
1341 if len(x)>0:
1342 retv += '<li>'+x+'</li>'
1343 return ('<ul>'+retv+'</ul>' if len(retv)>0 else '')
1344 elif txt.find(',')>=0:
1345 for x in txt.split(','):
1346 xx = x.strip()
1347 if len(xx)>0:
1348 retv += '<li>'+x.strip()+'</li>'
1349 return ('<ol>'+retv+'</ol>' if len(retv)>0 else '')
1350 else:
1351 return txt
1352
1353 addHook('fmod_list', fmod_list)
1354 addHook('fmod_sublist', fmod_sublist)
1355
1356# -------------------------------------------------------------------
1357from anki.utils import stripHTML, isMac, json
1358
1359if True:
1360
1361 try:
1362 mw.addon_view_menu
1363 except AttributeError:
1364 mw.addon_view_menu = QMenu(_(u"&Вид") if lang == 'ru' else _(u"&View"), mw)
1365 mw.form.menubar.insertMenu(
1366 mw.form.menuTools.menuAction(), mw.addon_view_menu)
1367
1368if A['SET_INTERVAL'][0] or A['F4_EDIT'][0] or B['B00_MORE_OVERVIEW_STATS'][0] or A['COLORFUL_TOOLBAR'][0]:
1369
1370 try:
1371 mw.addon_cards_menu
1372 except AttributeError:
1373 mw.addon_cards_menu = QMenu(_(u"&Карточки") if lang == 'ru' else _(u"&Cards"), mw)
1374 mw.form.menubar.insertMenu(
1375 mw.form.menuTools.menuAction(), mw.addon_cards_menu)
1376
1377if A['F6_SOUND_KEY_MENU'][0] or A['COLORFUL_TOOLBAR'][0]:
1378 try:
1379 mw.addon_audio_menu
1380 except AttributeError:
1381 mw.addon_audio_menu = QMenu(_(u"&Звук") if lang == 'ru' else _(u"&Audio"), mw)
1382 mw.form.menubar.insertMenu(
1383 mw.form.menuTools.menuAction(), mw.addon_audio_menu)
1384
1385if A['COLORFUL_TOOLBAR'][0] or A['FLIP_FLOP'][0]:
1386 try:
1387 mw.addon_go_menu.addSeparator()
1388 except AttributeError:
1389 mw.addon_go_menu = QMenu(u'П&ереход' if lang=='ru' else _(u"&Go"), mw)
1390 mw.form.menubar.insertMenu(
1391 mw.form.menuTools.menuAction(), mw.addon_go_menu)
1392
1393mw_addon_view_menu_exists = hasattr(mw,'addon_view_menu')
1394mw_addon_cards_menu_exists = hasattr(mw,'addon_cards_menu')
1395mw_addon_audio_menu_exists = hasattr(mw,'addon_audio_menu')
1396mw_addon_go_menu_exists = hasattr(mw,'addon_go_menu')
1397
1398# -- _______ /// _______ --
1399
1400audio_speed = 1.0
1401
1402if A['F6_SOUND_KEY_MENU'][0]:
1403
1404 pause_auction = QAction(mw)
1405 pause_auction.setText(u'&Пауза' if lang=='ru' else _(u"&Pause"))
1406 if A['COLORFUL_TOOLBAR'][0]:
1407 pause_auction.setIcon(QIcon(os.path.join(MUSTHAVE_COLOR_ICONS, 'player_pause.png')))
1408 pause_auction.setShortcut(QKeySequence(HOTKEY['F6_pause'][0]))
1409 mw.connect(pause_auction, SIGNAL("triggered()"), \
1410 lambda: anki.sound.mplayerManager.mplayer.stdin.write("pause\n") )
1411
1412 pause_action = QAction(mw)
1413 pause_action.setText(u'&Пауза' if lang=='ru' else _(u"&Pause"))
1414 if A['ANKI_MENU_ICONS'][0]:
1415 pause_action.setIcon(QIcon(os.path.join(MUSTHAVE_COLOR_ICONS, 'player_pause.png')))
1416 pause_action.setShortcut(QKeySequence(HOTKEY['F6_pause'][0]))
1417 if A['COLORFUL_TOOLBAR'][0]:
1418 pause_action.setEnabled(False)
1419 mw.connect(pause_action, SIGNAL("triggered()"), \
1420 lambda: anki.sound.mplayerManager.mplayer.stdin.write("pause\n") )
1421
1422 stop_action = QAction(mw)
1423 stop_action.setText(u'&Стоп' if lang=='ru' else _(u"&Stop"))
1424 if A['ANKI_MENU_ICONS'][0]:
1425 stop_action.setIcon(QIcon(os.path.join(MUSTHAVE_COLOR_ICONS, 'player_stop.png')))
1426 stop_action.setShortcut(QKeySequence(HOTKEY['F6_stop'][0]))
1427 if A['COLORFUL_TOOLBAR'][0]:
1428 stop_action.setEnabled(False)
1429 mw.connect(stop_action, SIGNAL("triggered()"), \
1430 lambda: anki.sound.mplayerManager.mplayer.stdin.write("stop\n") )
1431
1432 forward_action = QAction(mw)
1433 forward_action.setText(u'&Вперёд 5 Ñек.' if lang=='ru' else _(u"&Forward 5 sec."))
1434 if A['ANKI_MENU_ICONS'][0]:
1435 forward_action.setIcon(QIcon(os.path.join(MUSTHAVE_COLOR_ICONS, 'player_fwd.png')))
1436 forward_action.setShortcut(QKeySequence(HOTKEY['F6_forw'][0]))
1437 if A['COLORFUL_TOOLBAR'][0]:
1438 forward_action.setEnabled(False)
1439 mw.connect(forward_action, SIGNAL("triggered()"), \
1440 lambda: anki.sound.mplayerManager.mplayer.stdin.write("seek 5 0 \n") )
1441
1442 backward_action = QAction(mw)
1443 backward_action.setText(u'&Ðазад 5 Ñек.' if lang=='ru' else _(u"&Backward 5 sec."))
1444 if A['ANKI_MENU_ICONS'][0]:
1445 backward_action.setIcon(QIcon(os.path.join(MUSTHAVE_COLOR_ICONS, 'player_rew.png')))
1446 backward_action.setShortcut(QKeySequence(HOTKEY['F6_back'][0]))
1447 if A['COLORFUL_TOOLBAR'][0]:
1448 backward_action.setEnabled(False)
1449 mw.connect(backward_action, SIGNAL("triggered()"), \
1450 lambda: anki.sound.mplayerManager.mplayer.stdin.write("seek -5 0 \n") )
1451
1452if A['F6_SOUND_KEY_MENU'][0] and A['F6_FAST_SLOW'][0]:
1453
1454 def audio_play(speed):
1455 global audio_speed
1456 if speed:
1457 audio_speed = speed
1458 if anki.sound.mplayerManager is not None:
1459 if anki.sound.mplayerManager.mplayer is not None:
1460 anki.sound.mplayerManager.mplayer.stdin.write("af_add scaletempo=stride=10:overlap=0.8\n")
1461 anki.sound.mplayerManager.mplayer.stdin.write("speed_set %f \n"%audio_speed)
1462
1463 fast_action = QAction(mw)
1464 fast_action.setText(u'&БыÑтрее на 10% (до 4.0)' if lang=='ru' else _(u"&Faster by 10% (max 4.0)"))
1465 if A['ANKI_MENU_ICONS'][0]:
1466 fast_action.setIcon(QIcon(os.path.join(MUSTHAVE_COLOR_ICONS, 'audio-fast.png')))
1467 fast_action.setShortcut(QKeySequence(HOTKEY['F6_fast'][0]))
1468 #if A['COLORFUL_TOOLBAR'][0]:
1469 # fast_action.setEnabled(False)
1470 mw.connect(fast_action, SIGNAL("triggered()"), \
1471 lambda: audio_play(min(4.0, audio_speed + 0.1)) )
1472
1473 slow_action = QAction(mw)
1474 slow_action.setText(u'&Медленнее на 10% (до 0.1)' if lang=='ru' else _(u"&Slower by 10% (min 0.1)"))
1475 if A['ANKI_MENU_ICONS'][0]:
1476 slow_action.setIcon(QIcon(os.path.join(MUSTHAVE_COLOR_ICONS, 'audio-slow.png')))
1477 slow_action.setShortcut(QKeySequence(HOTKEY['F6_slow'][0]))
1478 #if A['COLORFUL_TOOLBAR'][0]:
1479 # slow_action.setEnabled(False)
1480 mw.connect(slow_action, SIGNAL("triggered()"), \
1481 lambda: audio_play(max(0.1, audio_speed - 0.1)) )
1482
1483 init_action = QAction(mw)
1484 init_action.setText(u'&С нормальной ÑкороÑтью 100%' if lang=='ru' else _(u"&Initial speed 100%"))
1485 if A['ANKI_MENU_ICONS'][0]:
1486 init_action.setIcon(QIcon(os.path.join(MUSTHAVE_COLOR_ICONS, 'audio-play.png')))
1487 init_action.setShortcut(QKeySequence(HOTKEY['F6_init'][0]))
1488 #if A['COLORFUL_TOOLBAR'][0]:
1489 # init_action.setEnabled(False)
1490 mw.connect(init_action, SIGNAL("triggered()"), \
1491 lambda: audio_play(1.0) )
1492
1493 def my_runHandler(self):
1494 _self = anki.sound.mplayerManager
1495 global mplayerClear
1496 _self.mplayer = None
1497 _self.deadPlayers = []
1498 while 1:
1499 anki.sound.mplayerEvt.wait()
1500 anki.sound.mplayerEvt.clear()
1501
1502 # clearing queue?
1503 if anki.sound.mplayerClear and _self.mplayer:
1504 try:
1505 _self.mplayer.stdin.write("stop\n")
1506 except:
1507 # mplayer quit by user (likely video)
1508 _self.deadPlayers.append(_self.mplayer)
1509 _self.mplayer = None
1510
1511 # loop through files to play
1512 while anki.sound.mplayerQueue:
1513 # ensure started
1514 if not _self.mplayer:
1515 _self.startProcess()
1516 # pop a file
1517 try:
1518 item = anki.sound.mplayerQueue.pop(0)
1519 _self.mplayer.stdin.write("stop\n")
1520 except IndexError:
1521 # queue was cleared by main thread
1522 continue
1523 if anki.sound.mplayerClear:
1524 anki.sound.mplayerClear = False
1525 extra = ""
1526 else:
1527 extra = " 1"
1528 cmd = 'loadfile "%s"%s\n' % (item, extra)
1529
1530 try:
1531 _self.mplayer.stdin.write(cmd)
1532 except:
1533 # mplayer has quit and needs restarting
1534 _self.deadPlayers.append(_self.mplayer)
1535 _self.mplayer = None
1536 _self.startProcess()
1537 _self.mplayer.stdin.write(cmd)
1538
1539 if abs(audio_speed - 1.0) > 0.01:
1540 _self.mplayer.stdin.write("af_add scaletempo=stride=10:overlap=0.8\n")
1541 _self.mplayer.stdin.write("speed_set %f \n" % audio_speed)
1542 _self.mplayer.stdin.write("seek 0 1\n")
1543
1544 # if we feed mplayer too fast it loses files
1545 time.sleep(1)
1546 # wait() on finished processes. we don't want to block on the
1547 # wait, so we keep trying each time we're reactivated
1548 def clean(pl):
1549 if pl.poll() is not None:
1550 pl.wait()
1551 return False
1552 else:
1553 return True
1554 _self.deadPlayers = [pl for pl in _self.deadPlayers if clean(pl)]
1555
1556 def store_file(regex_string):
1557 global audio_file
1558 audio_file = regex_string.group(1)
1559
1560 def audio_filter(qa_html, qa_type, dummy_fields, dummy_model, dummy_data, dummy_col):
1561 re.sub(sound_re, store_file, qa_html)
1562 return qa_html
1563
1564 MplayerMonitor.run = my_runHandler
1565 addHook("mungeQA", audio_filter)
1566
1567# -- _______ /// _______ --
1568
1569def go_edit_current():
1570 """Edit the current card when there is one."""
1571 try:
1572 mw.onEditCurrent()
1573 except AttributeError:
1574 pass
1575
1576def go_edit_layout():
1577 """Edit the current card's note's layout if there is one."""
1578 try:
1579 ccard = mw.reviewer.card
1580 clayout.CardLayout(mw, ccard.note(), ord=ccard.ord)
1581 #go_study()
1582 except AttributeError:
1583 return
1584
1585if mw_addon_cards_menu_exists:
1586
1587 undo_action = QAction(mw)
1588 undo_action.setText(u'&Обратно' if lang=='ru' else _(u"&Go back"))
1589 if A['ANKI_MENU_ICONS'][0]:
1590 undo_action.setIcon(QIcon(os.path.join(MUSTHAVE_COLOR_ICONS, 'undo.png')))
1591 undo_action.setShortcut(QKeySequence(HOTKEY['F2_undo'][0]))
1592 if A['COLORFUL_TOOLBAR'][0]:
1593 undo_action.setEnabled(False)
1594 mw.connect(undo_action, SIGNAL("triggered()"), mw.onUndo)
1595
1596 mw.addon_cards_menu.addAction(undo_action)
1597
1598###################################################################
1599# Reschedule_as_new_key_shortcut.py
1600
1601if A['RESET_CARD_SCHEDULING'][0]:
1602
1603 def onForgetCard(self):
1604 if askUser(u"СброÑить раÑпиÑание Ð´Ð»Ñ Ð´Ð°Ð½Ð½Ð¾Ð¹ карточки?" if lang=="ru" else "Reschedule as new card?"):
1605 self.mw.checkpoint(_("Forget Card"))
1606
1607 self.mw.col.sched.removeLrn( [ self.card.id ] )
1608 self.mw.col.sched.remFromDyn( [ self.card.id ] )
1609 self.mw.col.sched.resetCards( [ self.card.id ] )
1610
1611 tooltip(u"Карточка поÑтавлена в конец очереди новых карточек." if lang=="ru" else _("Card rescheduled as new"))
1612 self.mw.reset()
1613
1614 def onForgetNote(self):
1615 if askUser(u"СброÑить раÑпиÑание Ð´Ð»Ñ Ð²Ñех карточек данной запиÑи?" if lang=="ru" else "Reschedule all cards of current note?"):
1616 self.mw.checkpoint(_("Forget Note"))
1617
1618 self.mw.col.sched.removeLrn( sorted([ c.id for c in self.card.note().cards() ]) )
1619 self.mw.col.sched.remFromDyn( sorted([ c.id for c in self.card.note().cards() ]) )
1620 self.mw.col.sched.resetCards( sorted([ c.id for c in self.card.note().cards() ]) )
1621
1622 tooltip(u"Карточки запиÑи поÑтавлены в конец очереди новых карточек." if lang=="ru" else _("Cards of note are rescheduled as new"))
1623 self.mw.reset()
1624
1625if A['F4_EDIT'][0]:
1626
1627 F4_edit_current_action = QAction(mw)
1628 F4_edit_current_action.setText(u'Р&едактирование...' if lang=='ru' else _(u"&Edit..."))
1629 if A['ANKI_MENU_ICONS'][0]:
1630 F4_edit_current_action.setIcon(QIcon(os.path.join(MUSTHAVE_COLOR_ICONS, 'edit_current.png')))
1631 F4_edit_current_action.setShortcut(QKeySequence(HOTKEY['F4_edit'][0]))
1632 if A['COLORFUL_TOOLBAR'][0]:
1633 F4_edit_current_action.setEnabled(False)
1634 mw.connect(F4_edit_current_action, SIGNAL("triggered()"), go_edit_current)
1635
1636 F4_edit_layout_action = QAction(mw)
1637 F4_edit_layout_action.setText(u'&Карточки...' if lang=='ru' else _(u"&Cards..."))
1638 if A['ANKI_MENU_ICONS'][0]:
1639 F4_edit_layout_action.setIcon(QIcon(os.path.join(MUSTHAVE_COLOR_ICONS, 'edit_layout.png')))
1640 F4_edit_layout_action.setShortcut(QKeySequence(HOTKEY['F4_edit_layout'][0]))
1641 if A['COLORFUL_TOOLBAR'][0]:
1642 F4_edit_layout_action.setEnabled(False)
1643 mw.connect(F4_edit_layout_action, SIGNAL("triggered()"), go_edit_layout)
1644
1645 if mw_addon_cards_menu_exists:
1646 mw.addon_cards_menu.addSeparator()
1647 mw.addon_cards_menu.addAction(F4_edit_current_action)
1648 mw.addon_cards_menu.addAction(F4_edit_layout_action)
1649 mw.addon_cards_menu.addSeparator()
1650
1651if A['RESET_CARD_SCHEDULING'][0]:
1652
1653 #Reviewer.onForget = onForget
1654 F2_FORGET_ME_NOT_action = QAction(mw)
1655 F2_FORGET_ME_NOT_action.setText(u"Только Ñту к&арточку - в конец очереди новых..." if lang=="ru" else "Reschedule Card as &New...")
1656 if A['ANKI_MENU_ICONS'][0]:
1657 F2_FORGET_ME_NOT_action.setIcon(QIcon(os.path.join(MUSTHAVE_COLOR_ICONS, 'new.png')))
1658 F2_FORGET_ME_NOT_action.setShortcut(QKeySequence(HOTKEY['F2_card_reschedule'][0]))
1659 mw.connect(F2_FORGET_ME_NOT_action, SIGNAL("triggered()"), lambda self=mw.reviewer: onForgetCard(self)) #mw.reviewer.onForget)
1660
1661 F2_FORGET_ME_NOTE_action = QAction(mw)
1662 F2_FORGET_ME_NOTE_action.setText(u"Ð’Ñе карточки &запиÑи - в конец очереди новых карточек..." if lang=="ru" else "Reschedule Note as &New Cards...")
1663 if A['ANKI_MENU_ICONS'][0]:
1664 F2_FORGET_ME_NOTE_action.setIcon(QIcon(os.path.join(MUSTHAVE_COLOR_ICONS, 'queue.png')))
1665 F2_FORGET_ME_NOTE_action.setShortcut(QKeySequence(HOTKEY['F2_note_reschedule'][0]))
1666 mw.connect(F2_FORGET_ME_NOTE_action, SIGNAL("triggered()"), lambda self=mw.reviewer: onForgetNote(self)) #mw.reviewer.onForget)
1667
1668 if mw_addon_cards_menu_exists:
1669 mw.addon_cards_menu.addSeparator()
1670 mw.addon_cards_menu.addAction(F2_FORGET_ME_NOT_action)
1671 mw.addon_cards_menu.addAction(F2_FORGET_ME_NOTE_action)
1672
1673# Date: January 27, 2016
1674# Author: Benjamin Gray
1675# File: Quick_Reschedule.py
1676# Purpose: Quickly reschedule cards in anki to a user specified interval using sched.reschedCards()
1677
1678import random
1679
1680# prompt for new interval, and set it
1681def promptNewInterval():
1682 if mw.state == 'review':
1683 if True:
1684 days = unicode(mw.reviewer.card.ivl+1)
1685 dayString = getText((u"Дней до Ñледующего проÑмотра карточки (текущий интервал + 1 = %s ):" \
1686 if lang=='ru' else \
1687 "Number of days until next review (current interval + 1 = %s ):") % (days), default=days)
1688 if dayString[1]:
1689 dayString0 = dayString[0].strip().lower()
1690 dayStringM = False
1691 dayStringW = False
1692 dayStringI = False
1693 if dayString0.endswith('m') or dayString0.endswith(u'м'):
1694 dayString0 = dayString0[:-1].strip()
1695 dayStringM = True
1696 if dayString0.endswith('w') or dayString0.endswith(u'н'):
1697 dayString0 = dayString0[:-1].strip()
1698 dayStringW = True
1699 if len(dayString0)==0:
1700 dayString0 = '1'
1701
1702 try:
1703 days = int(dayString0)
1704 dayz = float(0)
1705 if dayStringM:
1706 dayz = abs(float(days))
1707 days = 0
1708 if dayStringW:
1709 dayz = float(0)
1710 days = abs(days) * 7
1711 if days < 0:
1712 dayStringI = True
1713 except ValueError:
1714 days = mw.reviewer.card.ivl+1
1715 try:
1716 dayz = abs(float(dayString0))
1717 if 0<dayz and dayz<1:
1718 days = int(dayz*10) * 7
1719 dayz = 0
1720 dayStringW = True
1721 else:
1722 days = 0
1723 dayStringM = True
1724 except ValueError:
1725 dayz = float(0)
1726
1727 if dayz > 0: # 3.1 or 1.2 is monthes
1728 days = int(31*dayz)+random.randrange(-15, 15+1, 1)
1729 elif dayStringW: # .2 is two weeks
1730 days = abs(days)+random.randrange(-3, 3+1, 1)
1731 elif days > 10:
1732 days = days + random.randrange(-1, 1+1, 1)
1733 elif days > 0: # from 1 to 9 setup exact number of day
1734 pass # days = days
1735 elif days <= 0:
1736 days = mw.reviewer.card.ivl+1
1737 #days = days * 60 # num<0 == interval in seconds
1738
1739 #showWarning(unicode(dayString)+' '+str(dayz)+' '+str(days))
1740
1741 mw.checkpoint(_("Reschedule card"))
1742 mw.col.sched.reschedCards( [mw.reviewer.card.id], days-1 if days>1 else 1, days+1 )
1743 days_mod = (days % 10) if ( (days%100) < 11 or (days%100) > 14) else (days % 100)
1744 tooltip( (u"Запланирован проÑмотр через <b>%s</b> "+("день" if days_mod==1 else ("днÑ" if days_mod>=2 and days_mod<=4 else "дней")) if lang=='ru' else \
1745 'Rescheduled for review in <b>%s</b> days') % (days) )
1746 mw.reset()
1747 else:
1748 tooltip("Задавать дни до Ñледующего проÑмотра можно только при проÑмотре карточек!" if lang=='ru' else \
1749 'Prompt for new interval available only on answer side (BackSide) of card\'s reviewer.')
1750
1751if A['SET_INTERVAL'][0]:
1752 set_new_int_action = QAction(mw)
1753 set_new_int_action.setText(u'&Через ... дней' if lang=='ru' else _(u"&Prompt and Set ... days interval"))
1754 if A['ANKI_MENU_ICONS'][0]:
1755 set_new_int_action.setIcon(QIcon(os.path.join(MUSTHAVE_COLOR_ICONS, 'schedule.png')))
1756 set_new_int_action.setShortcut(QKeySequence(HOTKEY['F2_ctrl'][0]))
1757 if A['COLORFUL_TOOLBAR'][0]:
1758 set_new_int_action.setEnabled(False)
1759 mw.connect(set_new_int_action, SIGNAL("triggered()"), promptNewInterval)
1760 if mw_addon_cards_menu_exists:
1761 mw.addon_cards_menu.addAction(set_new_int_action)
1762 mw.addon_cards_menu.addSeparator()
1763
1764# ----------------------------------------------------------------------------
1765BUTTON_LABEL = [None, _("Again"), _("Hard"), _("Good"), _("Easy")]
1766
1767# Button_Colours_Good_Again.py
1768# This add-on makes "Good" green and "Again" red. It's a little like Anki 1 or AnkiDroid.
1769
1770# Author: Calumks <calumks@gmail.com>
1771# Get reviewer class -- Monkey patching
1772
1773dflt = '#000'
1774
1775def color_buttons():
1776 global dflt, btn_Later, btn_Again, btn_Hard, btn_Good, btn_Easy
1777 for lbls in BUTTON_LABELS:
1778 if lbls[0] == lang:
1779 BUTTON_LABEL[0] = lang
1780 BUTTON_LABEL[1] = lbls[1]
1781 BUTTON_LABEL[2] = lbls[2]
1782 BUTTON_LABEL[3] = lbls[3]
1783 BUTTON_LABEL[4] = lbls[4]
1784 break
1785 if B['B08_BIG_BUTTONS'][0]:
1786 dflt = '#666'
1787 else:
1788 dflt = '#333' # 'default'
1789 if B['B07_COLOR_BUTTONS'][0]:
1790 btn_Again = ['#c33', BUTTON_LABEL[1]]
1791 btn_Hard = ['#c90', BUTTON_LABEL[2]]
1792 btn_Good = ['#3c3', BUTTON_LABEL[3]]
1793 btn_Easy = ['#69f', BUTTON_LABEL[4]]
1794 else:
1795 btn_Again = [dflt, BUTTON_LABEL[1]]
1796 btn_Hard = [dflt, BUTTON_LABEL[2]]
1797 btn_Good = [dflt, BUTTON_LABEL[3]]
1798 btn_Easy = [dflt, BUTTON_LABEL[4]]
1799color_buttons()
1800
1801##################################################################
1802# Card Info During Review
1803# https://ankiweb.net/shared/info/2179254157
1804
1805# Copyright: Damien Elmes <anki@ichi2.net>
1806# License: GNU GPL, version 3 or later; http://www.gnu.org/copyleft/gpl.html
1807#
1808# Show statistics about the current and previous card while reviewing.
1809# Activate from the tools menu.
1810#
1811# Card stats
1812##########################################################################
1813
1814from anki.stats import CardStats
1815
1816if A['F3_CARD_INFO_DURING_REVIEW'][0]:
1817
1818 # 1300 is the minimum ease, the card may actually be harder.
1819 lowest_ease_possible = 1300
1820
1821 def percFromBaseToExtreme(value, base, extreme):
1822 """Shows, in %, where value is on the way from base to extreme.
1823
1824 Used for coloring numbers.
1825 """
1826 # Examples:
1827 # base = 2500, extreme = 1300; 2500 - 1300 = 1200.
1828 # value 1300: (2500 - 1300) / 1200 = 100%
1829 # value 1900: (2500 - 1900 = 600) / 1200 = 50%
1830 # value 2500: (2500 - 2500) / whatever = 0%
1831 # base = 2500, extreme = 3560, 2500 - 3560 = -1060
1832 # value 2600: (2500 - 2600 = -100) / 1060 = 9%
1833 if base == extreme:
1834 perc = 100
1835 else:
1836 perc = int(round( 100 * (base - value) / (base - extreme), 0 ))
1837 if perc > 100:
1838 perc = 100
1839
1840 return perc
1841
1842 def reps_for_total_ivl(ivl, FACTOR, max_total_ivl):
1843 """Repetitions required to retain during max_total_ivl from now."""
1844
1845 if ivl == 0:
1846 return 0
1847
1848 for (reps, total_ivl) in total_ivls(ivl, FACTOR / 1000.0):
1849
1850 if total_ivl >= max_total_ivl:
1851 return reps
1852
1853 def total_ivls(ivl, ease):
1854 """The total sum of intervals for each number of repetitions.
1855
1856 >>> total_ivls(20, 2.50)[:2]
1857 [(1, 50), (2, 175), (3, 525)]
1858
1859 """
1860
1861 total_ivl = 0
1862
1863 for reps in range(1, 999):
1864
1865 ivl *= ease
1866 total_ivl += ivl
1867
1868 yield (reps, total_ivl)
1869
1870 def get_time_avg(all_times):
1871 """Takes duration of almost every review and returns average.
1872 Skips the oldest reviews if there is enough.
1873
1874 Durations are in seconds.
1875 """
1876
1877 if len(all_times) < 7:
1878 timeList = all_times
1879 else:
1880 oldest_count = len(all_times) // 5
1881 timeList = all_times[oldest_count - 1:]
1882
1883 time_avg = sum(timeList) / float(len(timeList)) # much faster than numpy.mean
1884
1885 return time_avg
1886
1887 def repstime(days, time_avg, ivl, FACTOR):
1888 """Returns time needed to know this card for days days since next
1889 answer (for Review cards only).
1890 """
1891 if days <= 0: # forecast requested for before the due date
1892 return 0
1893 else:
1894 reps = repsForIvlFactorAndMaximum(ivl=ivl, FACTOR=FACTOR, days=days)
1895 return reps * time_avg
1896
1897 def repstime_s(days, FACTOR, time_avg, ivl, cardStatsObject):
1898 """Returns time as "1m 30s"
1899 """
1900 time_num = repstime(days=days, time_avg=time_avg, ivl=ivl, FACTOR=FACTOR)
1901 timestr = cardStatsObject.time(time_num)
1902 if FACTOR <= lowest_ease_possible:
1903 fmt_time = '>=%s' % timestr
1904 else:
1905 fmt_time = '%s' % timestr
1906
1907 # To make forecast times red if they are big.
1908 foretime_base = 60
1909 foretime_max = 330
1910 foretime_red_perc = foretime_green_perc = 0
1911
1912 if (days > (365 * 3) and time_num < 10):
1913 foretime_green_perc = 50
1914 elif days > (365 * 40):
1915 fmt_time = '<small>%s</small>' % fmt_time
1916 elif time_num > foretime_base:
1917 foretime_red_perc = percFromBaseToExtreme(
1918 time_num, foretime_base, foretime_max)
1919
1920 if time_num >= 120:
1921 fmt_time = '<i>%s</i>' % fmt_time
1922
1923 fmt_time = u'<span style="font-style:italic;font-weight:bold;color: rgb({0}%, {1}%, 0%)">{2}</span>'.format(
1924 foretime_red_perc, foretime_green_perc, fmt_time)
1925
1926 return fmt_time
1927
1928 def repsForIvlFactorAndMaximum(ivl, FACTOR, days):
1929 # I guess this is currently just a proxy because I wanted to adapt it
1930 # for cards due in the future. Then it will subtract the "due in"
1931 # time from the "days". Note that getForecast does it now.
1932 return reps_for_total_ivl(ivl=ivl, FACTOR=FACTOR, max_total_ivl=days)
1933
1934 # The following 2 functions are used in other add-ons only.
1935
1936 def getForecastText(self, c, forecast_days):
1937 # Returns text forecast for card c in seconds, with "s" added.
1938 f = getForecast(self, c, forecast_days)
1939 if f:
1940 return (str(int(f)) + ' s')
1941 else:
1942 return ''
1943
1944 # Copyright: Aleksej
1945 # Based on anki.stats from Anki 2.0.15 (the report function by Damien Elmes
1946 # <anki@ichi2.net>).
1947 # License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
1948
1949 def getForecast(self, c, forecast_days):
1950 # Returns forecast for card c in seconds.
1951 if not (c.ivl > 0):
1952 return # in-learning cards not supported
1953
1954 all_times = self.col.db.list(
1955 "select time/1000 from revlog where cid = :id",
1956 id=c.id)
1957
1958 cnt = len(all_times)
1959
1960 if cnt:
1961
1962 time_avg = get_time_avg(all_times)
1963
1964 def repstime_this(days):
1965 return repstime(days=days, time_avg=time_avg, ivl=c.ivl,
1966 FACTOR=c.factor)
1967
1968 if c.queue in (2,3):
1969 if c.due > self.col.sched.today:
1970 forecast_days -= (c.due - self.col.sched.today)
1971
1972 forecast = repstime_this(forecast_days)
1973 if forecast:
1974 return forecast
1975 else:
1976 return None
1977
1978 """
1979https://ankiweb.net/shared/info/2189699505
1980
1981Card time forecast
19820.01MB. Updated 2015-12-05.
1983
1984Description
1985
1986In the card-specific statistics table (which I use with Damien's "Card Info During Review", see below),
1987
1988* shows how much review time may be needed to retain the memory for 5, 10, 15 years, assuming the "Good" button is pressed every time;
1989** it does not consider forgetting index (which is assumed to be about 90%); calculating with it directly every time would be slow (if it affects the relative numbers, maybe the effect could be approximated differently?);
1990** as of 2013-06-18, it uses only the mean average of the recent ≈5 review times; it used also the median in the past, but now the functions are used in add-ons for multi-card forecasts, which need speed (calculating a median with NumPy is almost 10 times slower than calculating a mean manually);
1991* highlights low ease with red, considering its distance from the first ease (only for cards with at least 4 ratings). Also highlights high ease with green, change opt_use_green_for_ease in the code to disable that.
1992* highlights forecast time with red, considering its distance from 60s, with #FF red at 330s+ (5.5m).
1993
1994* I haven't considered Interval modifier; using it may make this add-on show incorrect forecasts and highlights.
1995* The add-on replaces CardStats.report, so if that is updated or replaced by a different add-on, something about card info output might break (but probably no dataloss).
1996
1997System requirements:
1998* Recommended: "Card Info During Review" (to show the info in a sidebar).
1999
2000Links
2001* Source code repository (GitHub): github.com/aleksejrs/anki-2.0-card-time-forecast;
2002* Old repository location (Gitorious): gitorious.org/anki-2-0-card-time-forecast-and-ease-warner;
2003* another Ease-related add-on: Suggest Starting Ease for the deck's options group.
2004* another forecast add-on (depends on this one): Study screen time forecast.
2005* You may be wondering if there is a way to show a forecast for each card in the browser. I have an add-on for that, which is a module for an old alpha version of Advanced Browser. I've renamed the files, so it works alongside current version of AB.
2006
2007Ideas
2008
2009I am not a programmer, so these are ideas, not plans.
2010* Return to using mean for single-card calculation when NumPy is installed?
2011* Younger cards have longer forecasts, so they look harder, and their forecast is colored red.
2012** When coloring, consider total time of the reviews.
2013*** Alone, that will confuse cards whose learning has been restarted (after editing, or acquiring the knowledge necessary to memorize them).
2014* Make the configurability of the medium ease more prominent (currently it's the Starting ease).
2015
2016Feedback?
2017If you want me to notice your feedback soon, please use either:
2018* the TenderApp discussion area (mention the add-on name in the message title);
2019
2020* GitHub issue tracker.
2021
2022Changelog (by date of upload)
2023* 2015-10-16: increased the max number of reviews in a forecast from about 998 to about 99999. I hope it does not affect performance (haven't tested).
2024* (2015-03-25: source code moved to GitHub, because Gitorious will be closed in May)
2025* 2014-12-25: for coloring, use the card's first ease, not the average of it and 250.
2026* 2013-10-23: from Anki 2.0.15: add Note ID and Card ID to the info table.
2027* 2013-06-17: stop using Numpy and calculating median, use average only. It is one dependency less for this add-on, and at least 1.5 times faster for multi-card forecast by other add-ons which will depend on this one (also added two functions for them). This made my forecast times about 10% bigger. A problem this has is for cards which have accidentally been left for a minute. However, that is good if you consider cards that have been left for a minute 2 of 5 times not by an accident. For filtered decks, the source deck is also shown now.
2028* 2013-04-04: colors forecast times over 60s with red (gradient with upper value of 330s); removed my deck-specific tweaks and 1-year forecast; fixed a bug (probably division by zero)
2029* 2013-03-27: added an option to disable green colouring of Ease numbers.
2030* 2013-03-06: first version published here. Unlike the really first version, it highlights ease and has a better code structure.
2031 """
2032
2033if A['F3_CARD_INFO_DURING_REVIEW'][0] or A['F3_CARD_HISTORY'][0]:
2034
2035 def addRLine(self, k, v):
2036 """Add a line with right-aligned caption to a CardStats object"""
2037 self.txt += "<tr><td align=right style='padding:.1em .25em;'><b>%s</b></td><td style='padding:.1em .5em;'>%s</td></tr>" % (k, v)
2038
2039 def glocal_revlogDatum(self, crd):
2040 entries = mw.col.db.all(
2041 "select id/1000.0, ease, ivl, FACTOR, time/1000.0, type "
2042 "from revlog where cid = ? order by id desc limit 1", crd.id)
2043 if not entries:
2044 return ""
2045 s = ""
2046 for (date, ease, ivl, FACTOR, taken, type) in reversed(entries):
2047 #tstr = [_("Learn"), _("Review"), _("Relearn"), _("Filtered"), _("Resched")][type]
2048 import anki.stats as st
2049 fmt = "<span style='color:%s'>%s</span>"
2050 if ease == 1:
2051 retv = fmt % (st.colRelearn, _("Again"))
2052 else:
2053 if type==0 or type==3:
2054 easy = ["", _("Again"),_("Good"),_("Easy"), "", ""]
2055 elif type==2:
2056 easy = ["", _("Again"),_("Good"), "", "", ""]
2057 else:
2058 easy = ["", _("Again"),_("Hard"),_("Good"),_("Easy"), ""]
2059 retv = easy[ease]
2060 if retv == "":
2061 retv = easy[ease-1]
2062 if retv == "":
2063 retv = easy[ease-2]
2064 self.addLine(("ПоÑледний ответ" if lang=="ru" else _('Last answer'))+":", "<span style='color:#666;'>" + unicode(retv) +
2065 "</span> <span style=font-weight:400;>(" + str(ease) + ") <sub>%d</sub> </span>" % type)
2066 self.addLine(("Текущий интервал" if lang=="ru" else _('Current interval'))+":", " <span style=font-weight:700;>" + str(ivl) + " </span>")
2067
2068# by Anki user rjgoif
2069# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
2070# https://ankiweb.net/shared/info/1581856587
2071# extra card stats ×
2072
2073# This addon displays the original due date and intervals for cards in filtered decks.
2074# For all decks (filtered and normal), it also adds another statistic:
2075# if the card is not due today, it also displays the days early or days overdue
2076# and the percent early/late compared to the current interval.
2077# I made this to assist in the development of my other add-ons, but additionally
2078# I just like having that information available when I am studying.
2079
2080###################### VERSION 0004 ######################### 2016-03-09
2081
2082"""
2083A simple add-on that lists a few more stats to your cards, including if you have the add-on that displays stats while you review (which can be found <a href="https://ankiweb.net/shared/info/2179254157" rel="nofollow">here</a>.
2084
2085Stats added:
2086- home deck and home deck (original) due date when studying filtered decks
2087- days overdue in regular and filtered decks
2088- days early in filtered decks
2089- relative lateness/earliness when the card being studied is either early (filtered decks) or late (regular or filtered).
2090
2091Change log:
2092<b>v0004</b>
2093- fixed date formatting bug for lapsed cards
2094
2095<b>v0003</b>
2096- fixed divide-by-zero bug.
2097
2098<b>v0002</b>
2099- fixed bug caused by preview-ahead cards being considered as part of the "new" queue (bug an artifact of developing the add-on with some other unreleased add-ons active. Whoops.)
2100- correctly removed the (Home) Due line from card stats when they are not in a filtered deck.
2101"""
2102
2103if A['F3_CARD_INFO_DURING_REVIEW'][0]:
2104
2105 #class CardStat(object):
2106 # def __init__(self, col, card):
2107 # self.col = col
2108 # self.card = card
2109 # def report(self):
2110 def aleksejCardStatsReportForForecast(self):
2111
2112 # Make Ease number green for easy cards. Low ease numbers are marked
2113 # red, so you may want to disable this if it is difficult for you to
2114 # distinguish red and green.
2115 opt_use_green_for_ease = True
2116
2117 c = self.card
2118 fmt = lambda x, **kwargs: fmtTimeSpan(x, short=True, **kwargs)
2119 self.txt = "<!--style>.cs tr:nth-child(2n){background-color:#eee;}</style--><table class=cs width=100% style='border-collapse:collapse;' cellspacing=0>"
2120
2121 self.glocal_revlogDatum(c)
2122
2123 if c.note().tags:
2124 Tags = ""
2125 for tag in c.note().tags:
2126 Tags += " " + tag
2127 self.addLine(_("Tags"), '<span style="font-weight:bold;color:Chocolate;">'+Tags+'</span>')
2128
2129 first = self.col.db.scalar(
2130 "select min(id) from revlog where cid = ?", c.id)
2131 last = self.col.db.scalar(
2132 "select max(id) from revlog where cid = ?", c.id)
2133
2134 frctOverdue = None
2135 frctOverdueStr = ""
2136 if c.type in (1,2):
2137 if c.odid or c.queue < 0:
2138 next = None
2139 overdue = None
2140 else:
2141 if c.queue in (2,3) or (c.queue == 0 and c.type == 2):
2142 #next = time.time()+((c.due - self.col.sched.today)*86400)
2143 if c.odue:
2144 next = time.time()+((c.odue - self.col.sched.today)*86400)
2145 overdue = self.col.sched.today - c.odue
2146 else:
2147 next = time.time()+((c.due - self.col.sched.today)*86400)
2148 overdue = self.col.sched.today - c.due
2149 if c.ivl > 0:
2150 frctOverdue = abs(overdue / c.ivl)
2151 if round(frctOverdue,3) >= 0.1:
2152 frctOverdueStr = " (%.0f%%)" % (frctOverdue*100)
2153 elif round(frctOverdue,3) >= 0.001:
2154 frctOverdueStr = " (%.1f%%)" % (frctOverdue*100)
2155 else:
2156 frctOverdueStr = " (<0.1%)"
2157 else:
2158 next = c.due
2159 overdue = None
2160 next = self.date(next)
2161 if next and last and next != self.date(last/1000):
2162 self.addLine(_("(Home) Due") if c.odue else _("Due"), '<span style="color:blue;">'+next+'</span>')
2163
2164 if c.lapses > 0:
2165 self.addLine(_("Lapses"), "<span style=color:red;>%d</span>" % c.lapses)
2166
2167 if c.reps > 0:
2168 self.addLine(_("Reviews"), "%d" % c.reps)
2169
2170 if c.type in (1,2):
2171 if c.queue == 2 or (c.queue == 0 and c.type == 2):
2172 if overdue > 0 and frctOverdue:
2173 self.addLine(_("Overdue"), fmt(overdue * 86400) + frctOverdueStr)
2174 elif overdue < 0 and frctOverdue:
2175 self.addLine(_("Early"), fmt(-overdue * 86400) + frctOverdueStr)
2176 self.addLine("<span style = color:green;>" + _("Interval") + "</span>", "<span style = color:green;>" + fmt(c.ivl * 86400) + "</span>")
2177 #self.addLine(_("Ease"), "%d%%" % (c.factor/10.0))
2178
2179 ##
2180
2181 ease_str = '%d%%' % (c.factor / 10.0)
2182 if c.factor <= lowest_ease_possible:
2183 ease_str = '<i>%s</i>' % ease_str
2184
2185 all_times = self.col.db.list(
2186 "select time/1000 from revlog where cid = :id",
2187 id=c.id)
2188
2189 cnt = len(all_times)
2190
2191 ease_green_perc = ease_red_perc = 0
2192 # Making the Ease number red or green.
2193 if cnt > 4:
2194 first_factor = self.col.db.list(
2195 "select FACTOR from revlog where cid = :id and FACTOR <> 0 limit 1",
2196 id=c.id)
2197
2198 # Account for a custom Starting Ease at the first review.
2199 if len(first_factor) > 0:
2200 medium_ease = first_factor[0]
2201 else:
2202 medium_ease = 2500
2203
2204 if c.factor < medium_ease:
2205 ease_green_perc = 0
2206 ease_red_perc = percFromBaseToExtreme(
2207 c.factor, medium_ease, lowest_ease_possible)
2208 # If the card is easy, make the number green.
2209 # XXX: This is not very useful, and may be bad for
2210 # accessibility (color blindness). To disable it, change
2211 # opt_use_green_for_ease above to False.
2212 elif c.factor > medium_ease and opt_use_green_for_ease:
2213 # Precision is probably not important here.
2214 highest_ease_possible = 3560
2215 ease_red_perc = 0
2216 ease_green_perc = percFromBaseToExtreme(
2217 c.factor, medium_ease, highest_ease_possible)
2218
2219 ease_rgb = '<span style="color:rgb({0}%,{1}%,0%);cursor:help;"'+\
2220 (' title=" color:rgb({0}%,{1}%,0%); "' if B['B11_BUTTON_TITLES'][0] else '')+'>'.format(
2221 ease_red_perc, ease_green_perc)
2222
2223 self.addLine(ease_rgb + _("Ease") + "</span>", ease_rgb + ease_str + "</span>")
2224
2225 ##
2226
2227 (cnt, total) = self.col.db.first(
2228 "select count(), sum(time)/1000 from revlog where cid = :id",
2229 id=c.id)
2230
2231 if cnt:
2232 self.addLine(_(u"Time") if c.reps==1 else _("Average Time"), "<span style = color:blueviolet; >" + unicode(self.time(total / float(cnt))) + "</span>")
2233 if c.reps>1:
2234 self.addLine(_("Total Time"), "<span style = color:indigo; >" + unicode(self.timer(total)) + "</span>")
2235
2236 ##
2237
2238 if c.queue==2 and cnt:
2239 time_avg = get_time_avg(all_times)
2240
2241 def repstime_this(days):
2242 return repstime(days=days, time_avg=time_avg,
2243 ivl=c.ivl, FACTOR=c.factor)
2244
2245 def addCardForecast(caption, days):
2246 if not (c.ivl > 0):
2247 return # in-learning cards not supported
2248# caption = fmt(days * 86400)
2249
2250# time_num = repstime(
2251# days=days, time_avg=time_avg,
2252# ivl=c.ivl, FACTOR=c.factor)
2253 time_str = repstime_s(days=days, FACTOR=c.factor,
2254 time_avg=time_avg,
2255 ivl=c.ivl, cardStatsObject=self)
2256
2257 years = days / 365.0
2258
2259 if years == 15:
2260 caption = ('<span style="color: green">%s</span>' %
2261 caption)
2262 elif years == 40:
2263 caption = '<small>%s</small>' % caption
2264
2265 addRLine(self, caption, time_str)
2266
2267 if cnt >= 3 or (cnt >= 2 and c.ivl > 100):
2268 # Account for cards due in the future -- consider the
2269 # due date the date of the next answer.
2270 subtract_from_forecast_days = 0
2271 if c.queue in (2,3) and c.due > self.col.sched.today:
2272 subtract_from_forecast_days = (c.due - self.col.sched.today)
2273
2274# forecast_list = [('1 Y', 365 * 1),
2275 forecast_list = [('5 Y', 365 * 5),
2276 ('10 Y', 365 * 10),
2277 ('15 Y', 365 * 15)]
2278# ('40 Y', 365 * 40)]
2279
2280 forecast_captions, forecast_days = zip(*forecast_list)
2281 forecast_captions, forecast_days = list(forecast_captions), list(forecast_days)
2282 forecast_days.append(365 * 100)
2283
2284 for i in range(len(forecast_list)):
2285 # Show no more than one forecast of 8 seconds or less.
2286 nextIsNotVerySmall = repstime_this(forecast_days[i + 1] - subtract_from_forecast_days) > 8
2287 # Skip the forecast if the next one is the same.
2288 nextIsBigger = (repstime_this(forecast_days[i] - subtract_from_forecast_days) <
2289 repstime_this(forecast_days[i + 1] - subtract_from_forecast_days))
2290
2291 if nextIsNotVerySmall and nextIsBigger:
2292 addCardForecast(forecast_captions[i], forecast_days[i] -
2293 subtract_from_forecast_days)
2294
2295 ##
2296
2297 elif c.queue == 0:
2298 self.addLine(_("Position"), c.due)
2299
2300 if not first or first and self.date(c.id/1000) != self.date(last/1000):
2301 self.addLine(_("Added"), '<span style="color:cornflowerblue;">'+self.date(c.id/1000)+'</span>')
2302 if first and self.date(c.note().mod) != self.date(c.id/1000):
2303 self.addLine(_("Edited"), '<span style="color:royalblue;">'+self.date(c.note().mod)+'</span>')
2304 #if first and self.date(c.mod) != self.date(last/1000):
2305 # self.addLine(_("Changed"), '<span style="color:cornflowerblue;">'+self.date(c.mod)+'</span>')
2306
2307 if first and last and self.date(first/1000) != self.date(last/1000):
2308 self.addLine(_("First Review"), '<span style="color:cornflowerblue;">'+self.date(first/1000)+'</span>')
2309 self.addLine(_("Latest Review"), '<span style="color:royalblue;">'+self.date(last/1000)+'</span>')
2310 elif first and last and self.date(first/1000)==self.date(last/1000):
2311 self.addLine(_("Review"), '<span style="color:royalblue;">'+self.date(first/1000)+'</span>')
2312
2313 self.addLine(_("Card Type")+' '+str(c.ord+1), '<div style="color:cornflowerblue;">'+c.template()['name']+'</div>')
2314
2315 d = c.model()['name']
2316 if d != c.template()['name']:
2317 self.addLine(_("Note Type"), "<div style='color:#333;'>"+d+"</div>")
2318
2319 def ddd(d,t):
2320 if d != c.model()['name']:
2321 dixis = d.split('::')
2322 dd = ''
2323 i = 0
2324 for dixie in dixis:
2325 dd += "<br>" if i > 0 else ""
2326 dd += "– " if i==1 else "— " if i>0 else "" #*i
2327 i += 1
2328 dd += dixie # (dixie[:20]+"…") if len(dixie) > 20 else dixie
2329 self.addLine(t, "<div"+\
2330 (" title=' "+d.replace('::','\n')+" '" if B['B11_BUTTON_TITLES'][0] else "")+\
2331 " style='cursor:help;color:gray;'>"+dd+"</div>")
2332
2333 if c.odid:
2334 ddd( self.col.decks.name(c.odid), "ИÑÑ…Ð¾Ð´Ð½Ð°Ñ ÐºÐ¾Ð»Ð¾Ð´Ð°" if lang=="ru" else _("(Home) Deck") )
2335 ddd( self.col.decks.name(c.did), _("Deck") )
2336
2337 self.addLine(_("Card ID"), '<span style="font-weight:400;color:cornflowerblue;">'+str(c.id)+'</span>')
2338 self.addLine(_("Note ID"), '<span style="font-weight:400;color:royalblue;">'+str(c.nid)+'</span>')
2339 self.addLine(u"ID колоды" if lang=="ru" else _("Deck ID"), '<span style="font-weight:400;color:cornflowerblue;">'+str(c.did)+'</span>')
2340
2341 if c:
2342 self.addLine(
2343 "model queue type",
2344 (_("Cloze") if c.model()['type'] == MODEL_CLOZE else _("Basic")) +
2345 ' ' + ('<big style=font-style:italic;color:red;>' if c.queue != c.type else '') +
2346 " " + ("<b style=color:red;>" if not c.queue in (-2, -1, 0, 1, 2, 3) else "") + str(c.queue) + ("</b>" if not c.queue in (-2, -1, 0, 1, 2, 3) else "") +
2347 ' ' + ("<b style=color:red;>" if not c.type in (0, 1, 2) else "") + str(c.type) + ("</b>" if not c.type in (0, 1, 2) else "") +
2348 " " + ('</big>' if c.queue != c.type else '')
2349 )
2350
2351 self.txt += "</table>"
2352 return self.txt
2353
2354 glocal_i = False
2355 def glocal_addLine(self, k, v):
2356 global glocal_i
2357 self.txt += "<tr style='"+('background-color:#eee;' if glocal_i else '')+"'><td style='white-space:nowrap;padding:.1em .5em;'>%s</td><td style='padding:.25em .5em;'><b>%s</b></td></tr>" % (k, v)
2358 glocal_i = not glocal_i
2359
2360 def glocal_timer(self, seconds):
2361 m, s = divmod(seconds, 60)
2362 h, m = divmod(m, 60)
2363 return "%d:%02d:%02d" % (h, m, s)
2364
2365 def glocal_time(self, tm):
2366 str = ""
2367 if tm >= 60:
2368 str = fmtTimeSpan((tm/60)*60, short=True, point=-1, unit=1)
2369 str += " "
2370 if tm%60 != 0 or not str:
2371 str += fmtTimeSpan(round(tm%60,0), point=2 if not str else -1, short=True)
2372 return str
2373
2374 if True:
2375 #CardStats.report = aleksejCardStatsReportForForecast
2376 CardStats.report = wrap( CardStats.report, aleksejCardStatsReportForForecast )
2377 CardStats.addLine = glocal_addLine
2378 CardStats.addRLine = addRLine
2379 CardStats.glocal_revlogDatum = glocal_revlogDatum
2380 CardStats.time = glocal_time
2381 CardStats.timer = glocal_timer
2382
2383 class glocal_CardStats(object):
2384 def __init__(self, mw):
2385 self.mw = mw
2386 self.shown = False
2387 addHook("showQuestion", self._update)
2388 addHook("deckClosing", self.hide)
2389 addHook("reviewCleanup", self.hide)
2390
2391 def _addDockable(self, title, w):
2392 class DockableWithClose(QDockWidget):
2393 def closeEvent(self, evt):
2394 self.emit(SIGNAL("closed"))
2395 QDockWidget.closeEvent(self, evt)
2396 dock = DockableWithClose(title, mw)
2397 dock.setObjectName(title)
2398 dock.setAllowedAreas(Qt.LeftDockWidgetArea | Qt.RightDockWidgetArea)
2399 dock.setFeatures(QDockWidget.DockWidgetClosable)
2400 dock.setWidget(w)
2401 if mw.width() < 1000: # 1024x768 min
2402 mw.resize(QSize(1000, (740 if mw.height() < 740 else mw.height()) ))
2403 mw.addDockWidget(Qt.RightDockWidgetArea, dock)
2404 return dock
2405
2406 def _remDockable(self, dock):
2407 mw.removeDockWidget(dock)
2408
2409 def show(self):
2410 if not self.shown:
2411 class ThinAnkiWebView(AnkiWebView):
2412 def sizeHint(self):
2413 return QSize(305, 305)
2414 self.web = ThinAnkiWebView()
2415 self.shown = self._addDockable(_("Card Info"), self.web)
2416 self.shown.connect(self.shown, SIGNAL("closed"),
2417 self._onClosed)
2418 self._update()
2419
2420 def hide(self):
2421 if self.shown:
2422 self._remDockable(self.shown)
2423 self.shown = None
2424 #actionself.mw.form.actionCstats.setChecked(False)
2425
2426 def toggle(self):
2427 if self.shown:
2428 self.hide()
2429 else:
2430 self.show()
2431
2432 def _onClosed(self):
2433 # schedule removal for after evt has finished
2434 self.mw.progress.timer(100, self.hide, False)
2435
2436 def _update(self):
2437 if not self.shown:
2438 return
2439 txt = ""
2440
2441 r = self.mw.reviewer
2442 d = self.mw.col
2443 if r.card:
2444 txt += "<h3>" + (u"Ð¢ÐµÐºÑƒÑ‰Ð°Ñ ÐºÐ°Ñ€Ñ‚Ð¾Ñ‡ÐºÐ°" if lang=="ru" else _("Current Card")) + \
2445 ("" if (B['B00_MORE_OVERVIEW_STATS'][0] == 1) else " " + mw.reviewer._remaining()) + "</h3><hr>"
2446 txt += d.cardStats(r.card)
2447 lc = r.lastCard()
2448 if lc:
2449 txt += "<h3>" + (u"ÐŸÑ€ÐµÐ´Ñ‹Ð´ÑƒÑ‰Ð°Ñ ÐºÐ°Ñ€Ñ‚Ð¾Ñ‡ÐºÐ°" if lang=="ru" else _("Last Card")) + "</h3>"
2450 txt += d.cardStats(lc)
2451 if not txt:
2452 txt = _("No current card or last card.")
2453 self.web.setHtml("""
2454<html><head>
2455</head><body><center>%s</center></body></html>
2456"""% txt)
2457
2458 _cs = glocal_CardStats(mw)
2459
2460 def cardStats(): #(on):
2461 _cs.toggle()
2462
2463def glocal_revlogData(self, crd, cs):
2464 entries = self.mw.col.db.all(
2465 "select id/1000.0, ease, ivl, FACTOR, time/1000.0, type "
2466 "from revlog where cid = ?", crd.id)
2467 if not entries:
2468 return ""
2469 s = "<table width=100%%><tr><th align=left>%s</th>" % _("Date")
2470 s += ("<th align=right>%s</th>" * 5) % (
2471 _("Type"), _("Rating"), _("Interval"), _("Ease"), _("Time"))
2472 cnt = 0
2473 for (date, ease, ivl, FACTOR, taken, type) in reversed(entries):
2474 cnt += 1
2475 s += "<tr><td>%s</td>" % time.strftime(_("<b>%Y-%m-%d</b> @ %H:%M"),
2476 time.localtime(date))
2477 tstr = [_("Learn"), _("Review"), _("Relearn"), _("Filtered"),
2478 _("Resched")][type]
2479 import anki.stats as st
2480 fmt = "<span style='color:%s'>%s</span>"
2481 if type == 0:
2482 tstr = fmt % (st.colLearn, tstr)
2483 elif type == 1:
2484 tstr = fmt % (st.colMature, tstr)
2485 elif type == 2:
2486 tstr = fmt % (st.colRelearn, tstr)
2487 elif type == 3:
2488 tstr = fmt % (st.colCram, tstr)
2489 else:
2490 tstr = fmt % ("#000", tstr)
2491 if ease == 1:
2492 ease = fmt % (st.colRelearn, ease)
2493 if ivl == 0:
2494 ivl = _("0d")
2495 elif ivl > 0:
2496 ivl = fmtTimeSpan(ivl*86400, short=True)
2497 else:
2498 ivl = cs.time(-ivl)
2499 s += ("<td align=right>%s</td>" * 5) % (
2500 tstr,
2501 ease, ivl,
2502 "%d%%" % (FACTOR/10) if FACTOR else "",
2503 cs.time(taken)) + "</tr>"
2504 s += "</table>"
2505 if cnt < crd.reps:
2506 s += _("""\
2507Note: Some of the history is missing. For more information, \
2508please see the browser documentation.""")
2509 return s + "<hr>"
2510
2511def showTextik(txt, parent=None, type="text", run=True, geomKey=None, minW=555,minH=666,title=""):
2512 if not parent:
2513 parent = aqt.mw.app.activeWindow() or aqt.mw
2514 diag = QDialog(parent)
2515 diag.setWindowTitle("Anki "+title)
2516 layout = QVBoxLayout(diag)
2517 diag.setLayout(layout)
2518 text = QTextEdit()
2519 text.setReadOnly(True)
2520 if type == "text":
2521 text.setPlainText(txt)
2522 else:
2523 text.setHtml(txt)
2524 layout.addWidget(text)
2525 box = QDialogButtonBox(QDialogButtonBox.Close)
2526 layout.addWidget(box)
2527 def onReject():
2528 if geomKey:
2529 saveGeom(diag, geomKey)
2530 QDialog.reject(diag)
2531 diag.connect(box, SIGNAL("rejected()"), onReject)
2532 diag.setMinimumWidth(minW) # !!!
2533 diag.setMinimumHeight(minH) # !!!
2534 if geomKey:
2535 restoreGeom(diag, geomKey)
2536 if run:
2537 diag.exec_()
2538 else:
2539 return diag, box
2540
2541######################
2542#
2543if mw_addon_view_menu_exists:
2544 mw.addon_view_menu.addSeparator()
2545if mw_addon_cards_menu_exists:
2546 mw.addon_cards_menu.addSeparator()
2547
2548def showBodyClasses():
2549 return "<div style='white-space: pre-wrap;'>" + mw.reviewer.web.page().mainFrame().evaluateJavaScript("""
2550 (function(){
2551 var ret = document.documentElement.classList.toString().trim();
2552 ret = "/* html */<br>." + ret.replace(/(\s+)/g,' { } <br>.') + " { } <br>";
2553 var retv = document.body.classList.toString().trim();
2554 retv = "/* body */<br>." + retv.replace(/(\s+)/g,' { } <br>.') + " { } <br>";
2555 return ret + retv;
2556 }())
2557 """) + "</div>"
2558
2559def onStats():
2560 if not mw.reviewer.card:
2561 return
2562 cs = CardStats(mw.col, mw.reviewer.card)
2563 retv = cs.report()
2564 retw = glocal_revlogData(mw.reviewer, mw.reviewer.card, cs)
2565
2566 lc = mw.reviewer.lastCard()
2567 if lc:
2568 lcs = CardStats(mw.col, mw.reviewer.lastCard())
2569 retx = lcs.report()
2570 rety = glocal_revlogData(mw.reviewer, mw.reviewer.lastCard(), lcs)
2571 else:
2572 lcs = retx = rety = ""
2573
2574 # -- выбраÑывает showText Ñо Ñтандартным заголовком
2575 # Проверьте базу данных, не помогло - Ñкопируйте на форум
2576 #sys.stderr.write("...\n")
2577
2578 showTextik( '' +
2579 'audio_speed = %1.1f<br>\n'%audio_speed +
2580 'Replay Q <b>'+ unicode(soundtrack_q_number)+ '</b> '+ unicode(len(soundtrack_q_list))+ ' '+ unicode(soundtrack_q_list) + '<br>\n' +
2581 'Replay A <b>'+ unicode(soundtrack_a_number)+ '</b> '+ unicode(len(soundtrack_a_list))+ ' '+ unicode(soundtrack_a_list) + '<br>\n<br>' +
2582 "deck_browser_standard_zoom = <b>"+str(deck_browser_current_zoom)+"</b><br>\n"+\
2583 "overview_standard_zoom = <b>"+str(overview_current_zoom)+"</b><br>\n"+\
2584 "reviewer_standard_zoom = <b>"+str(reviewer_current_zoom)+"</b>\n"+
2585 "<h3>" + (u"Ð¢ÐµÐºÑƒÑ‰Ð°Ñ ÐºÐ°Ñ€Ñ‚Ð¾Ñ‡ÐºÐ°" if lang=="ru" else _("Current Card")) + \
2586 ("" if (B['B00_MORE_OVERVIEW_STATS'][0] == 1) else " " + mw.reviewer._remaining()) + "</h3><hr>" +
2587 showBodyClasses()+"<hr>"+
2588 retv+"<hr>"+retw+
2589 ("<h3>" + (u"ÐŸÑ€ÐµÐ´Ñ‹Ð´ÑƒÑ‰Ð°Ñ ÐºÐ°Ñ€Ñ‚Ð¾Ñ‡ÐºÐ°" if lang=="ru" else _("Last Card")) + "</h3><hr>" +
2590 retx+"<hr>" if lc else "")+
2591 rety+"<p> ",type="html")
2592
2593def qViewSource():
2594 if not mw.reviewer.card:
2595 return
2596 c = mw.reviewer.card
2597 showTextik( _('Question') + ':\n----------\n' + c.q(),type="text",title="HTML+CSS insertion into Body " + _("Question") +" c.q() ",minW=666)
2598
2599def aViewSource():
2600 if not mw.reviewer.card:
2601 return
2602 c = mw.reviewer.card
2603 showTextik( _('Answer') + ':\n----------\n' + c.a(),type="text",title="HTML+CSS insertion into Body " + _("Answer") +" c.a() ",minW=666)
2604
2605def cssViewSource():
2606 if not mw.reviewer.card:
2607 return
2608 c = mw.reviewer.card
2609 showTextik( _('CSS') + ':\n----------\n' + c.css(),type="text",title="CSS+ insertion into Body " + _("Styling (shared between cards)") +" c.css() ",minW=666)
2610
2611def q_ViewSource():
2612 if not mw.reviewer.card:
2613 return
2614 c = mw.reviewer.card
2615 q = c.q()
2616 showTextik( _('Question') + ':\n----------\n\n' + c._getQA()['q'],type="text",title="HTML insertion into Body " + _("Question") +" c._getQA()['q'] ",minW=666) # c._getQA()['q']
2617
2618def a_ViewSource():
2619 if not mw.reviewer.card:
2620 return
2621 c = mw.reviewer.card
2622 a = c.a()
2623 showTextik( _('Answer') + ':\n----------\n\n' + c._getQA()['a'],type="text",title="HTML insertion into Body " + _("Answer") +" c._getQA()['a'] ",minW=666) # c._getQA()['a']
2624
2625def css_ViewSource(self):
2626 if not self.mw.reviewer.card:
2627 return
2628 c = self.mw.reviewer.card
2629 showTextik( _('CSS') + ':\n----------\n\n' + c.model()['css'],type="text",title="CSS insertion into Body " + _("Styling (shared between cards)") +" c.model()['css'] ",minW=666)
2630
2631if A['F3_CARD_HISTORY'][0]:
2632 F3_CARD_HISTORY_action = QAction(mw)
2633 F3_CARD_HISTORY_action.setText(u"&ИÑÑ‚Ð¾Ñ€Ð¸Ñ ÐºÐ°Ñ€Ñ‚Ð¾Ñ‡ÐºÐ¸" if lang=="ru" else "Card &Revlog")
2634 F3_CARD_HISTORY_action.setShortcut(HOTKEY['F3_history'][0])
2635 if A['ANKI_MENU_ICONS'][0]:
2636 F3_CARD_HISTORY_action.setIcon(QIcon(os.path.join(MUSTHAVE_COLOR_ICONS, 'view.png')))
2637 mw.connect(F3_CARD_HISTORY_action, SIGNAL("triggered()"), onStats)
2638 if mw_addon_cards_menu_exists:
2639 mw.addon_cards_menu.addAction(F3_CARD_HISTORY_action)
2640 if not A['F3_CARD_INFO_DURING_REVIEW'][0]:
2641 mw.addon_cards_menu.addSeparator()
2642
2643if A['F3_CARD_INFO_DURING_REVIEW'][0]:
2644 F3_CARD_INFO_DURING_REVIEW_action = QAction(mw)
2645 F3_CARD_INFO_DURING_REVIEW_action.setText("ОÑновные &Показатели Карточек" if lang == 'ru' else "Card &Stats")
2646 if A['ANKI_MENU_ICONS'][0]:
2647 F3_CARD_INFO_DURING_REVIEW_action.setIcon(QIcon(os.path.join(MUSTHAVE_COLOR_ICONS, 'sidebar.png')))
2648 F3_CARD_INFO_DURING_REVIEW_action.setShortcut(QKeySequence(HOTKEY['F3_card_stats'][0]))
2649 mw.connect(F3_CARD_INFO_DURING_REVIEW_action, SIGNAL("triggered()"), cardStats)
2650 if mw_addon_cards_menu_exists:
2651 mw.addon_cards_menu.addAction(F3_CARD_INFO_DURING_REVIEW_action)
2652 mw.addon_cards_menu.addSeparator()
2653
2654def _getSourceHTML():
2655 """To look at sourcne HTML+CSS code."""
2656 html = mw.web.page().mainFrame().evaluateJavaScript("""
2657 (function(){
2658 return document.documentElement.outerHTML
2659 }())
2660 """)
2661 showTextik(html,minW=999,title="HTML5+CSS3+JavaScript + jQuery 1.5 Source Code")
2662
2663def _getSourceBody():
2664 """To look at sourcne HTML+CSS code."""
2665 html = '<html class="' +\
2666 unicode(mw.web.page().mainFrame().evaluateJavaScript("""
2667 (function(){
2668 return document.documentElement.className
2669 }())
2670 """)) + '">\n<head>\n' +\
2671 unicode(mw.web.page().mainFrame().evaluateJavaScript("""
2672 (function(){
2673 return document.getElementsByTagName('head')[0].getElementsByTagName('style')[0].outerHTML
2674 }())
2675 """)) + "\n</head>\n" +\
2676 unicode(mw.web.page().mainFrame().evaluateJavaScript("""
2677 (function(){
2678 return document.body.outerHTML
2679 }())
2680 """)) + "\n</html>\n"
2681 showTextik(html,minW=999,title="HTML5+CSS3+JavaScript + jQuery 1.5 Source Code")
2682
2683menu_titles = []
2684
2685def _getA():
2686 """To look at A list."""
2687 html = "<html><head><style>"
2688 html += "h1 { color: dodgerblue; }"
2689 html += "table { border: solid 1px gray; }"
2690 html += "td { padding:5px 15px; }"
2691 html += "</style></head><body><table align=center><tbody><tr><td>"
2692
2693 def _nextA(AZ,ttl,color):
2694 html = ''
2695 html += "<h1 align=center>"+ttl+"</h1><p> "
2696 html += "<table cellspacing=0 align=center><tbody>"
2697 i = False
2698 #for key, value in AZ.iteritems():
2699 keylist = AZ.keys()
2700 keylist.sort()
2701 for key in keylist:
2702 value = AZ[key]
2703 if key:
2704 html += "<tr style='"+('background-color:#eee;' if i else '')+"'>"
2705 if value[0] != value[1] and value[0] != value[2]:
2706 html += "<td style='color:"+color+";'><b>"+key+"</b></td><td><b style=color:red;><big>"+str(value[0])+"</big></b></td>"
2707 elif value[0] != value[1]:
2708 html += "<td style='color:"+color+";'><b>"+key+"</b></td><td><b><big>"+str(value[0])+"</big></b></td>"
2709 else:
2710 html += "<td style='color:"+color+";'>"+key+"</td><td>"+str(value[0])+"</td>"
2711 html += "</tr>"
2712 i = not i
2713 html += "</tbody></table>"
2714 return html
2715
2716 def _nextK(AZ,ttl,color):
2717 html = ''
2718 html += "<h1 align=center>"+ttl+"</h1><p> "
2719 html += "<table cellspacing=0 align=center><tbody>"
2720 i = False
2721 #for key, value in AZ.iteritems():
2722 keylist = AZ.keys()
2723 def sortBy(inputStr):
2724 return AZ[inputStr][2]+" "+inputStr
2725 keylist.sort(key=sortBy)
2726 for key in keylist:
2727 value = AZ[key]
2728 if key:
2729 html += "<tr style='"+('background-color:#eee;' if i else '')+"'>"
2730 html += "<td>"+key+"</td><td style='color:"+color+";'>"+str(value[0])+"</td>"
2731 html += "<td style=color:#369;white-space:nowrap;>"+value[1]+"</td>"
2732 html += "<td style=color:gray;white-space:nowrap;>"+value[2]+"</td>"
2733 html += "</tr>"
2734 i = not i
2735 html += "</tbody></table><p> "
2736 return html
2737
2738 def _nextZ(AZ,ttl,color):
2739 html = ''
2740 html += "<h1 align=center>"+ttl+"</h1><p> "
2741 html += "<table cellspacing=0 align=center><tbody>"
2742 i = False
2743 for value in AZ:
2744 html += "<tr style='"+('background-color:#eee;' if i else '')+"'>"
2745 html += "<td style='color:"+color+";'><BIG>"+str(value)+"</BIG></td>"
2746 html += "</tr>"
2747 i = not i
2748 html += "</tbody></table><p> "
2749 return html
2750
2751 html += '' + \
2752 _nextK(HOTKEY,"HOTKEY list","darkviolet") + \
2753 _nextK(HOTKEYS,"HOTKEYS list","green") + \
2754 _nextK(HOTKEYZ,"HOTKEYZ list","darkred") + '</td><td>' + \
2755 _nextZ(menu_titles,"addons list","purple") + \
2756 _nextA(A,"A list","OliveDrab ") + \
2757 _nextA(B,"B list","Olive ") + \
2758 "</tr></tbody></table><p> "
2759
2760 #html += "<p><code>"+unicode( sorted(["%s==%s" % (i.key, i.version) for i in pip.get_installed_distributions()]) )+"</code>"
2761
2762 html += "<p> </body></html>"
2763 showTextik(html,minW=888,title="A list",type="HTML")
2764
2765if A['F3_VIEW_SOURCE'][0]:
2766
2767 if A['F3_HTML_SOURCE'][0]:
2768
2769 get_Body_Source_action = QAction(mw)
2770 get_Body_Source_action.setText("Показать ИÑ&ходник HTML Body" if lang == 'ru' else "View Source code &Body")
2771 if A['ANKI_MENU_ICONS'][0]:
2772 get_Body_Source_action.setIcon(QIcon(os.path.join(MUSTHAVE_COLOR_ICONS, 'xhtml.png')))
2773 get_Body_Source_action.setShortcut(QKeySequence(HOTKEY['F3_Body_source'][0]))
2774 mw.connect(get_Body_Source_action, SIGNAL("triggered()"), _getSourceBody)
2775
2776 get_HTML_Source_action = QAction(mw)
2777 get_HTML_Source_action.setText("Показать И&Ñходник HTML" if lang == 'ru' else "&View Source code")
2778 if A['ANKI_MENU_ICONS'][0]:
2779 get_HTML_Source_action.setIcon(QIcon(os.path.join(MUSTHAVE_COLOR_ICONS, 'html.png')))
2780 get_HTML_Source_action.setShortcut(QKeySequence(HOTKEY['F3_HTML_source'][0]))
2781 mw.connect(get_HTML_Source_action, SIGNAL("triggered()"), _getSourceHTML)
2782
2783 if hasattr(mw,'addon_cards_menu'):
2784 mw.addon_cards_menu.addAction(get_Body_Source_action)
2785 mw.addon_cards_menu.addAction(get_HTML_Source_action)
2786
2787 mw.debug_submenu = QMenu('О&тладка' if lang == 'ru' else "&"+_(u"Debug"), mw)
2788 if A['ANKI_MENU_ICONS'][0]:
2789 mw.debug_submenu.setIcon(QIcon(os.path.join(MUSTHAVE_COLOR_ICONS, 'source.png')))
2790
2791 debug_console_action = QAction(mw)
2792 debug_console_action.setText( "Python REPL &" + _("Debug Console") ) # Read-Eval-Print Loop
2793 if A['ANKI_MENU_ICONS'][0]:
2794 debug_console_action.setIcon(QIcon(os.path.join(MUSTHAVE_COLOR_ICONS, 'terminal.png')))
2795 debug_console_action.setShortcut(QKeySequence("Ctrl+Shift+:"))
2796 mw.connect(debug_console_action, SIGNAL("triggered()"), mw.onDebug)
2797
2798 View_Question_action = QAction(mw)
2799 View_Question_action.setText( "HTML+CSS Body &" + _("Question") )
2800 View_Question_action.setShortcut(QKeySequence("Alt+Shift+F3"))
2801 mw.connect(View_Question_action, SIGNAL("triggered()"), qViewSource)
2802
2803 View_Answer_action = QAction(mw)
2804 View_Answer_action.setText( "HTML+CSS Body &" + _("Answer") )
2805 View_Answer_action.setShortcut(QKeySequence("Ctrl+Shift+F3")) # Ctrl+Alt+F3 does not work
2806 mw.connect(View_Answer_action, SIGNAL("triggered()"), aViewSource)
2807
2808 View_Ans_action = QAction(mw)
2809 View_Ans_action.setText( "CSS &" + _("Card") )
2810 View_Ans_action.setShortcut(QKeySequence("Ctrl+Alt+Shift+F3"))
2811 mw.connect(View_Ans_action, SIGNAL("triggered()"), cssViewSource)
2812
2813 View_Quest_action = QAction(mw)
2814 View_Quest_action.setText( "HTML Body &" + _("Question") )
2815 View_Quest_action.setShortcut(QKeySequence("Alt+Shift+F4"))
2816 mw.connect(View_Quest_action, SIGNAL("triggered()"), q_ViewSource)
2817
2818 View_Answ_action = QAction(mw)
2819 View_Answ_action.setText( "HTML Body &" + _("Answer") )
2820 View_Answ_action.setShortcut(QKeySequence("Ctrl+Shift+F4"))
2821 mw.connect(View_Answ_action, SIGNAL("triggered()"), a_ViewSource)
2822
2823 View_Answr_action = QAction(mw)
2824 View_Answr_action.setText( "CSS only &" + _("Card") )
2825 View_Answr_action.setShortcut(QKeySequence("Ctrl+Alt+Shift+F4"))
2826 mw.connect(View_Answr_action, SIGNAL("triggered()"), lambda: css_ViewSource(mw.reviewer))
2827
2828 if mw_addon_cards_menu_exists:
2829 mw.addon_cards_menu.addMenu(mw.debug_submenu)
2830
2831 mw.debug_submenu.addAction(debug_console_action)
2832 mw.debug_submenu.addSeparator()
2833 mw.debug_submenu.addAction(View_Question_action)
2834 mw.debug_submenu.addAction(View_Answer_action)
2835 mw.debug_submenu.addAction(View_Ans_action)
2836 mw.debug_submenu.addSeparator()
2837 mw.debug_submenu.addAction(View_Quest_action)
2838 mw.debug_submenu.addAction(View_Answ_action)
2839 mw.debug_submenu.addAction(View_Answr_action)
2840 mw.debug_submenu.addSeparator()
2841
2842##############################
2843
2844if True: #B['B00_MORE_OVERVIEW_STATS'][0]: #CHECKERS:
2845
2846 def on_musthave_study():
2847 global A, B
2848 B['B05_STUDY_BUTTON'][0] = musthave_study_action.isChecked()
2849 if mw.state == "deckBrowser":
2850 mw.moveToState("deckBrowser")
2851
2852 def on_gear_at_end_of_line():
2853 global A, B
2854 B['B03_GEAR_AT_END_OF_LINE'][0] = gear_at_end_of_line_action.isChecked()
2855 if mw.state == "deckBrowser":
2856 mw.moveToState("deckBrowser")
2857
2858 def on_hide_big_numbers():
2859 global A, B
2860 B['B04_HIDE_BIG_NUMBERS'][0] = hide_big_numbers_action.isChecked()
2861 if mw.state == "deckBrowser":
2862 mw.moveToState("deckBrowser")
2863 if mw.state == "overview":
2864 mw.moveToState("overview")
2865
2866 def Unseen_and_buried_counts():
2867 global A, B
2868 if B['B00_MORE_OVERVIEW_STATS'][0] == 0:
2869 return
2870
2871 if B['B00_MORE_OVERVIEW_STATS'][0] == 3:
2872 B['B00_MORE_OVERVIEW_STATS'][0] = 2
2873 else:
2874 B['B00_MORE_OVERVIEW_STATS'][0] = 3
2875
2876 musthave_setup_menu(3)
2877 overview_init()
2878 initDeckBro()
2879
2880 if mw.state == "deckBrowser":
2881 mw.moveToState("deckBrowser")
2882 if mw.state == "overview":
2883 mw.moveToState("overview")
2884
2885 def on_checkers():
2886 global A, B
2887 if checkers_action.isChecked():
2888 #if new_and_due_action.isChecked():
2889 # if unseen_and_suspended_action.isChecked():
2890 # B['B00_MORE_OVERVIEW_STATS'][0] = 3
2891 # else:
2892 # B['B00_MORE_OVERVIEW_STATS'][0] = 2
2893 #else:
2894 # B['B00_MORE_OVERVIEW_STATS'][0] = 1
2895 B['B00_MORE_OVERVIEW_STATS'][0] = 3
2896 else:
2897 B['B00_MORE_OVERVIEW_STATS'][0] = 0
2898
2899 musthave_setup_menu(4)
2900 overview_init()
2901 initDeckBro()
2902
2903 if mw.state == "deckBrowser":
2904 mw.moveToState("deckBrowser")
2905 if mw.state == "overview":
2906 mw.moveToState("overview")
2907
2908 def new_and_due_counts():
2909 global A, B
2910 if B['B00_MORE_OVERVIEW_STATS'][0] == 0:
2911 return
2912
2913 if B['B00_MORE_OVERVIEW_STATS'][0] > 1:
2914 B['B00_MORE_OVERVIEW_STATS'][0] = 1
2915 else:
2916 if unseen_and_suspended_action.isChecked():
2917 B['B00_MORE_OVERVIEW_STATS'][0] = 3
2918 else:
2919 B['B00_MORE_OVERVIEW_STATS'][0] = 2
2920
2921 musthave_setup_menu(5)
2922 overview_init()
2923 initDeckBro()
2924
2925 if mw.state == "deckBrowser":
2926 mw.moveToState("deckBrowser")
2927 if mw.state == "overview":
2928 mw.moveToState("overview")
2929
2930 def show_them_all():
2931 global F9_HINT_PEEKED
2932 color_buttons()
2933 if mw.state == 'review':
2934 if mw.reviewer.state == 'question':
2935 anki.sound.stopMplayer()
2936 F9_HINT_PEEKED = False
2937 mw.reviewer._initWeb() # _showQuestion()
2938 if mw.reviewer.state == 'answer':
2939 anki.sound.stopMplayer()
2940 mw.reviewer._showAnswer()
2941
2942 def on_color_buttons():
2943 global A, B
2944 B['B07_COLOR_BUTTONS'][0] = color_buttons_action.isChecked()
2945 show_them_all()
2946
2947 def on_big_buttons():
2948 global A, B, F9_HINT_PEEKED
2949 B['B08_BIG_BUTTONS'][0] = big_buttons_action.isChecked()
2950 if mw.state == 'review':
2951 if mw.reviewer.state == 'question':
2952 anki.sound.stopMplayer()
2953 F9_HINT_PEEKED = False
2954 mw.reviewer._initWeb() # _showQuestion()
2955 if mw.reviewer.state == 'answer':
2956 anki.sound.stopMplayer()
2957 mw.reviewer._showAnswer()
2958
2959 def on_just_smiles():
2960 global BUTTON_LABELS, F9_HINT_PEEKED
2961 if just_smiles_action.isChecked():
2962 BUTTON_LABELS = BUTTON_LABELS_SMILES
2963 else:
2964 BUTTON_LABELS = BUTTON_LABELS_LANG
2965 color_buttons()
2966 if mw.state == "review":
2967 if mw.reviewer.state == 'question':
2968 anki.sound.stopMplayer()
2969 F9_HINT_PEEKED = False
2970 mw.reviewer._initWeb() # _showQuestion()
2971 if mw.reviewer.state == "answer":
2972 anki.sound.stopMplayer()
2973 mw.reviewer._showAnswer()
2974
2975 def myAnswerButtons(self,_old):
2976 global A, B
2977 if B['B06_WIDE_BUTTONS'][0]:
2978 times = []
2979 default = self._defaultEase()
2980 def but(i, label, beam):
2981 if i == default:
2982 extra = "id=defease"
2983 else:
2984 extra = ""
2985 due = self._buttonTime(i)
2986 return '''
2987 <td align=center style="width:%s;">%s<button %s %s onclick='py.link("ease%d");'>\
2988 %s</button></td>''' % (beam, due, extra, \
2989 ((" title=' "+(_("Shortcut key: %s") % i)+" '") if B['B11_BUTTON_TITLES'][0] else ""), i, label)
2990 buf = "<table cellpading=0 cellspacing=0 width=100%%><tr>"
2991 for ease, lbl, beams in answerButtonList(self):
2992 buf += but(ease, lbl, beams)
2993 buf += "</tr></table>"
2994 script = """
2995 <style>table tr td button { width: 100%; } </style>
2996 <script>$(function () { $("#defease").focus(); });</script>"""
2997 return buf + script
2998 else:
2999 return _old(self)
3000
3001 """
3002 # Bigger Show Answer Button
3003 For people who do their reps with a mouse.
3004 Makes the show answer button wide enough to cover all 4 of the review buttons.
3005 """
3006
3007 def myShowAnswerButton(self,_old):
3008 global A, B
3009 if B['B06_WIDE_BUTTONS'][0]:
3010 self._bottomReady = True
3011 if not self.typeCorrect:
3012 self.bottom.web.setFocus()
3013 middle = '''
3014 <span class=stattxt>%s</span><br>
3015 <button %s id=ansbut style="display:inline-block;width:%s;%s" onclick='py.link(\"ans\");'>%s</button>
3016 </script>
3017 ''' % (
3018 self._remaining(), \
3019 ((" title=' "+(_("Shortcut key: %s") % _("Space"))+" '") if B['B11_BUTTON_TITLES'][0] else ""),
3020 BEAMS4, "font-size:x-large;color:"+dflt if B['B08_BIG_BUTTONS'][0] else "", _("Show Answer"))
3021 # place it in a table so it has the same top margin as the ease buttons
3022 #middle = "<table cellpadding=0><tr><td class=stat2 align=center>%s</td></tr></table>" % middle
3023 middle = "<div class=stat2 align=center style='width:%s!important;'>%s</div>" % (BEAMS4, middle)
3024 if self.card.shouldShowTimer():
3025 maxTime = self.card.timeLimit() / 1000
3026 else:
3027 maxTime = 0
3028 self.bottom.web.eval("showQuestion(%s,%d);" % (
3029 json.dumps(middle), maxTime))
3030 return True
3031 else:
3032 return _old(self)
3033
3034 def wide_buttons_init():
3035 if B['B06_WIDE_BUTTONS'][0]:
3036 if A['COLORFUL_TOOLBAR'][0]:
3037 color_buttons_action.setEnabled(True)
3038 big_buttons_action.setEnabled(True)
3039 just_smiles_action.setEnabled(True)
3040 else:
3041 if A['COLORFUL_TOOLBAR'][0]:
3042 color_buttons_action.setEnabled(False)
3043 big_buttons_action.setEnabled(False)
3044 just_smiles_action.setEnabled(False)
3045
3046 def on_wide_buttons():
3047 global A, B
3048 B['B06_WIDE_BUTTONS'][0] = wide_buttons_action.isChecked()
3049 wide_buttons_init()
3050 show_them_all()
3051
3052 def refresh_reviewer():
3053 global F9_HINT_PEEKED
3054 if mw.state == 'review':
3055 if mw.reviewer.state == 'question':
3056 anki.sound.stopMplayer()
3057 F9_HINT_PEEKED = False
3058 mw.reviewer._initWeb() # _showQuestion()
3059 if mw.reviewer.state == 'answer':
3060 anki.sound.stopMplayer()
3061 mw.reviewer._showAnswer()
3062
3063 def on_Answer_Confirmation():
3064 global A, B
3065 if A['KEYS_HANDLER'][0]:
3066 B['B10_ANSWER_CONFIRMATION'][0] = 2 if answer_confirmation_action.isChecked() else 0
3067 refresh_reviewer()
3068
3069 def on_Titles():
3070 global A, B
3071 B['B11_BUTTON_TITLES'][0] = titles_action.isChecked()
3072 refresh_reviewer()
3073
3074 def on_hard7():
3075 global A, B
3076 B['B12_HARD7'][0] = hard7_action.isChecked()
3077 refresh_reviewer()
3078
3079 mw.musthave_submenu = QMenu('&ÐаÑтройки панели колод и кнопок ответа' if lang == 'ru' else _(u"Decks and Answers &Options"), mw)
3080 if A['ANKI_MENU_ICONS'][0]:
3081 mw.musthave_submenu.setIcon(QIcon(os.path.join(MUSTHAVE_COLOR_ICONS, 'Right_Arrow.png')))
3082
3083 unseen_and_suspended_action = QAction('&Ðевиданные, иÑключённые и отложенные' if lang == 'ru' else _('&Unseen, suspended and buried'), mw)
3084 mw.connect(unseen_and_suspended_action, SIGNAL("triggered()"), Unseen_and_buried_counts)
3085 unseen_and_suspended_action.setCheckable(True)
3086
3087 gear_at_end_of_line_action = QAction('&ШеÑтерёнка в конце Ñтроки' if lang == 'ru' else _('&Gear at the end of line'), mw)
3088 mw.connect(gear_at_end_of_line_action, SIGNAL("triggered()"), on_gear_at_end_of_line)
3089 gear_at_end_of_line_action.setCheckable(True)
3090
3091 musthave_study_action = QAction('&Кнопка Учить колоду' if lang == 'ru' else _('&Study Deck Button'), mw)
3092 mw.connect(musthave_study_action, SIGNAL("triggered()"), on_musthave_study)
3093 musthave_study_action.setCheckable(True)
3094
3095 hide_big_numbers_action = QAction( ('&СпрÑтать чиÑла больше %d' % (B['B04_HIDE_BIG_NUMBER'][0]) ) if lang == 'ru' else _('&Hide big numbers')+(' ( > %s )' % (B['B04_HIDE_BIG_NUMBER'][0]) ), mw)
3096 mw.connect(hide_big_numbers_action, SIGNAL("triggered()"), on_hide_big_numbers)
3097 hide_big_numbers_action.setCheckable(True)
3098
3099 checkers_action = QAction("ÐаÑтроить панель колод и показатели колоды" if lang=="ru" else "Setup Decks panel and Overview deck", mw)
3100 mw.connect(checkers_action, SIGNAL("triggered()"), on_checkers)
3101 checkers_action.setCheckable(True)
3102
3103 new_and_due_action = QAction("%s, %s, %s, %s"%(_('New'),_('Learn'),_('To Review'),_('Due')), mw)
3104 mw.connect(new_and_due_action, SIGNAL("triggered()"), new_and_due_counts)
3105 new_and_due_action.setCheckable(True)
3106
3107 if mw_addon_view_menu_exists:
3108 mw.addon_view_menu.addSeparator()
3109 mw.addon_view_menu.addMenu(mw.musthave_submenu)
3110 mw.musthave_submenu.addAction(checkers_action)
3111 mw.musthave_submenu.addSeparator()
3112 mw.musthave_submenu.addAction(new_and_due_action)
3113 mw.musthave_submenu.addAction(unseen_and_suspended_action)
3114 mw.musthave_submenu.addAction(gear_at_end_of_line_action)
3115 mw.musthave_submenu.addAction(hide_big_numbers_action)
3116 mw.musthave_submenu.addSeparator()
3117 mw.musthave_submenu.addAction(musthave_study_action)
3118 mw.musthave_submenu.addSeparator()
3119
3120 #CHK_07_COLOR_BUTTONS
3121 color_buttons_action = QAction('&Цветные кнопки оценки' if lang == 'ru' else _('&Color Answer Buttons'), mw)
3122 mw.connect(color_buttons_action, SIGNAL("triggered()"), on_color_buttons)
3123 color_buttons_action.setCheckable(True)
3124
3125 #CHK_08_BIG_BUTTONS
3126 big_buttons_action = QAction('&Большие буквы на кнопках' if lang == 'ru' else _('&Big Letters on Buttons'), mw)
3127 mw.connect(big_buttons_action, SIGNAL("triggered()"), on_big_buttons)
3128 big_buttons_action.setCheckable(True)
3129
3130 #JUST_SMILES
3131 just_smiles_action = QAction('&Смайлы по аÑе' if lang == 'ru' else _('&Just smiles'), mw)
3132 mw.connect(just_smiles_action, SIGNAL("triggered()"), on_just_smiles)
3133 just_smiles_action.setCheckable(True)
3134
3135 # -- Now it's a history. --
3136 #CHK_06_WIDE_BUTTONS_answerButtons = Reviewer._answerButtons
3137 #CHK_06_WIDE_BUTTONS_showAnswerButton = Reviewer._showAnswerButton
3138 #if CHK_06_WIDE_BUTTONS:
3139 #Reviewer._answerButtons = myAnswerButtons
3140 #Reviewer._showAnswerButton = myShowAnswerButton
3141 Reviewer._answerButtons = wrap(Reviewer._answerButtons, myAnswerButtons, "around")
3142 Reviewer._showAnswerButton = wrap(Reviewer._showAnswerButton, myShowAnswerButton, "around")
3143
3144 #CHK_06_WIDE_BUTTONS
3145 wide_buttons_action = QAction('&Широкие кнопки оценки ответа' if lang == 'ru' else _('&Wide Answer Buttons'), mw)
3146 mw.connect(wide_buttons_action, SIGNAL("triggered()"), on_wide_buttons)
3147 wide_buttons_action.setCheckable(True)
3148
3149 if not B['B06_WIDE_BUTTONS'][0] and A['COLORFUL_TOOLBAR'][0]:
3150 color_buttons_action.setEnabled(False)
3151 big_buttons_action.setEnabled(False)
3152 just_smiles_action.setEnabled(False)
3153
3154 # ANSWER_CONFIRMATION
3155 answer_confirmation_action = QAction('&Ð’ÑÐ¿Ð»Ñ‹Ð²Ð°ÑŽÑ‰Ð°Ñ Ð¿Ð¾Ð´Ñказка при ответе Ñ ÐºÐ»Ð°Ð²Ð¸Ð°Ñ‚ÑƒÑ€Ñ‹' if lang == 'ru' else _('&Answer_Confirmation'), mw)
3156 mw.connect(answer_confirmation_action, SIGNAL("triggered()"), on_Answer_Confirmation)
3157 answer_confirmation_action.setCheckable(True)
3158
3159 # BUTTON_TITLES
3160 titles_action = QAction('&Ð’Ñплывающие подÑказки над кнопками ответа' if lang == 'ru' else _('&Reply buttons hotkey tooltips '), mw)
3161 mw.connect(titles_action, SIGNAL("triggered()"), on_Titles)
3162 titles_action.setCheckable(True)
3163
3164 # HARD7
3165 hard7_action = QAction('&Чаще отвечать Трудно, чем Снова Ðе знаю' if lang == 'ru' else _('&Often Reply Hard than Again'), mw)
3166 mw.connect(hard7_action, SIGNAL("triggered()"), on_hard7)
3167 hard7_action.setCheckable(True)
3168
3169 # EDIT_MORE
3170 edit_more_action = QAction('Кнопки &Редактирование Ещё' if lang == 'ru' else _('&Edit More Buttons'), mw)
3171 mw.connect(edit_more_action, SIGNAL("triggered()"), onEditMore)
3172 edit_more_action.setCheckable(True)
3173 edit_more_action.setChecked(B['B13_EDIT_MORE'][0])
3174
3175 if mw_addon_view_menu_exists:
3176 mw.musthave_submenu.addAction(wide_buttons_action)
3177 mw.musthave_submenu.addAction(color_buttons_action)
3178 mw.musthave_submenu.addAction(big_buttons_action)
3179 mw.musthave_submenu.addAction(just_smiles_action)
3180 mw.musthave_submenu.addSeparator()
3181 mw.musthave_submenu.addAction(answer_confirmation_action)
3182 mw.musthave_submenu.addAction(titles_action)
3183 mw.musthave_submenu.addAction(hard7_action)
3184 mw.musthave_submenu.addAction(edit_more_action)
3185
3186 def musthave_setup_menu(num):
3187
3188 checkers_action.setChecked(B['B00_MORE_OVERVIEW_STATS'][0] > 0)
3189
3190 unseen_and_suspended_action.setChecked(B['B00_MORE_OVERVIEW_STATS'][0] > 2)
3191 unseen_and_suspended_action.setEnabled(B['B00_MORE_OVERVIEW_STATS'][0] > 1)
3192
3193 musthave_study_action.setChecked(B['B05_STUDY_BUTTON'][0])
3194 musthave_study_action.setEnabled(B['B00_MORE_OVERVIEW_STATS'][0] > 0)
3195
3196 hide_big_numbers_action.setChecked(B['B04_HIDE_BIG_NUMBERS'][0])
3197 hide_big_numbers_action.setEnabled(B['B00_MORE_OVERVIEW_STATS'][0] > 1)
3198
3199 new_and_due_action.setChecked(B['B00_MORE_OVERVIEW_STATS'][0] > 1)
3200 new_and_due_action.setEnabled(B['B00_MORE_OVERVIEW_STATS'][0] > 0)
3201
3202 gear_at_end_of_line_action.setChecked(B['B03_GEAR_AT_END_OF_LINE'][0])
3203 gear_at_end_of_line_action.setEnabled(B['B00_MORE_OVERVIEW_STATS'][0] > 2)
3204
3205 color_buttons_action.setChecked(B['B07_COLOR_BUTTONS'][0])
3206 big_buttons_action.setChecked(B['B08_BIG_BUTTONS'][0])
3207 color_buttons()
3208
3209 wide_buttons_action.setChecked(B['B06_WIDE_BUTTONS'][0])
3210 wide_buttons_init()
3211
3212 just_smiles_action.setChecked(BUTTON_LABELS_SMILES == BUTTON_LABELS)
3213
3214 answer_confirmation_action.setChecked(B['B10_ANSWER_CONFIRMATION'][0]>0)
3215 titles_action.setChecked(B['B11_BUTTON_TITLES'][0])
3216 hard7_action.setChecked(B['B12_HARD7'][0])
3217 edit_more_action.setChecked(B['B13_EDIT_MORE'][0])
3218
3219 musthave_setup_menu(1)
3220
3221if A['COLORFUL_TOOLBAR'][0]:
3222
3223 def toggle_text_tool_bar():
3224 #"""Switch the original toolbar on or off."""
3225 if show_text_tool_bar_action.isChecked():
3226 mw.toolbar.web.show()
3227 else:
3228 mw.toolbar.web.hide()
3229
3230 def toggle_qt_tool_bar():
3231 #"""Switch the new upper tool bar on or off."""
3232 if show_qt_tool_bar_action.isChecked():
3233 mw.qt_tool_bar.show()
3234 else:
3235 mw.qt_tool_bar.hide()
3236
3237 def toggle_more_tool_bar():
3238 #"""Switch the new lower tool bar on or off."""
3239 # No real need to check if we are in review. Only then should we
3240 # be able to activate the action.
3241 if show_more_tool_bar_action.isChecked():
3242 mw.reviewer.more_tool_bar.show()
3243 else:
3244 mw.reviewer.more_tool_bar.hide()
3245
3246 ## Actions to show and hide the different tool bars.
3247 show_text_tool_bar_action = QAction(mw)
3248 show_text_tool_bar_action.setText(u"Показать Колоды Добавить Обзор" if lang=="ru" else _(u"Show text tool bar"))
3249 show_text_tool_bar_action.setCheckable(True)
3250 mw.connect(show_text_tool_bar_action, SIGNAL("triggered()"),
3251 toggle_text_tool_bar)
3252
3253 show_qt_tool_bar_action = QAction(mw)
3254 show_qt_tool_bar_action.setText(u"Показать панель переходов" if lang=="ru" else _(u"Show icon bar"))
3255 show_qt_tool_bar_action.setCheckable(True)
3256 show_qt_tool_bar_action.setChecked(True)
3257 mw.connect(show_qt_tool_bar_action, SIGNAL("triggered()"),
3258 toggle_qt_tool_bar)
3259
3260 show_more_tool_bar_action = QAction(mw)
3261 show_more_tool_bar_action.setText(u"Показать панель редактированиÑ" if lang=="ru" else _(u"Show more tool bar"))
3262 show_more_tool_bar_action.setCheckable(True)
3263 show_more_tool_bar_action.setChecked(True)
3264 show_more_tool_bar_action.setEnabled(False)
3265 mw.connect(show_more_tool_bar_action, SIGNAL("triggered()"),
3266 toggle_more_tool_bar)
3267
3268 if mw_addon_view_menu_exists:
3269 mw.addon_view_menu.addAction(show_qt_tool_bar_action)
3270 mw.addon_view_menu.addAction(show_text_tool_bar_action)
3271 mw.addon_view_menu.addAction(show_more_tool_bar_action)
3272 mw.addon_view_menu.addSeparator()
3273
3274# -------------------------------------------------------------------
3275Keys = {}
3276
3277Keys['Undo'] = [Qt.Key_Period, Qt.Key_Z, Qt.Key_Comma] # Undo - NumPad dot = period in English and comma in Russian
3278Keys['R'] = [Qt.Key_R] # Replay Audio
3279#KeysV = [Qt.Key_V] # Record Voice
3280#KeysG = [Qt.Key_G] # Replay Voice
3281#KeysO = [Qt.Key_O] # Options
3282Keys['A'] = [Qt.Key_A] # Add
3283Keys['B'] = [Qt.Key_B] # Browse
3284Keys['D'] = [Qt.Key_D] # Deck
3285Keys['Y'] = [Qt.Key_Y] # Sync
3286Keys['Plus'] = [Qt.Key_Plus] # Replay Next Audio
3287
3288Keys['0'] = []
3289if A['ZERO_KEY_TO_SHOW_ANSWER'][0]:
3290 Keys['0'] = [Qt.Key_0] # Show Answer
3291
3292Keys['1'] = [Qt.Key_1]
3293Keys['2'] = [Qt.Key_2]
3294Keys['3'] = [Qt.Key_3]
3295Keys['4'] = [Qt.Key_4]
3296Keys['5'] = [] # hint
3297Keys['Aster'] = [Qt.Key_Asterisk] # marked
3298
3299if A['RIGHT_HAND_JKL_ANSWER_KEYS_SHORTCUTS'][0]:
3300 Keys['1'].append(Qt.Key_J) # again
3301 Keys['2'].append(Qt.Key_K) # hard
3302 Keys['3'].append(Qt.Key_L) # good
3303 Keys['4'].append(Qt.Key_Semicolon) # easy
3304 if A['F9_HINT_PEEKING'][0] and A['F9_HINT_PEEKING_H'][0]:
3305 Keys['5'].append(Qt.Key_H) # hint
3306 Keys['Aster'].append(Qt.Key_Apostrophe) # marked
3307
3308Keys8 = []
3309if A['NUMERIC_KEYPAD_REMAPPING'][0]:
3310 if B['B12_HARD7'][0]:
3311 Keys['2'].append(Qt.Key_7)
3312 else:
3313 Keys['1'].append(Qt.Key_7)
3314 Keys8.append(Qt.Key_8)
3315 if A['F9_HINT_PEEKING'][0] and A['F9_HINT_PEEKING_5'][0]:
3316 Keys['5'].append(Qt.Key_5) # hint
3317 Keys['3'].append(Qt.Key_9)
3318 Keys['R'].append(Qt.Key_6)
3319
3320'''
3321if lang == 'ru':
3322 # Russian Edition
3323 Text1 = [ u'о', u'О' ] # again
3324 Text2 = [ u'л', u'Л' ] # hard
3325 Text3 = [ u'д', u'Д' ] # good
3326 Text4 = [ u'ж', u'Ж' ] # easy
3327 Text5 = [ u'Ñ€', u'Ð ' ] # hint
3328 TextUndo = [ u'Ñ', u'Я' ] # Undo
3329 # End of Russian Edition
3330else:
3331 Text1 = []
3332 Text2 = []
3333 Text3 = []
3334 Text4 = []
3335 Text5 = []
3336 TextUndo = []
3337'''
3338
3339if lang == 'ru':
3340 # Russian Edition
3341 SHOW_HIDE_MARKED_STAR_KEYs = [ u'Ñ', u'Ð' ] # marked
3342
3343 REPLAY_BUTTONs = [ u'к', u'К' ] # Replay Audio
3344 RECORD_BUTTONs = [ u'м', u'М' ] # Record Voice
3345 RECORDED_BUTTONs = [ u'п', u'П' ] # Replay Voice
3346 OPTIONS_BUTTONs = [ u'щ', u'Щ' ] # Options
3347 ADD_BUTTONs = [ u'ф', u'Ф' ] # a
3348 BROWSE_BUTTONs = [ u'и', u'И' ] # b
3349 DECK_BUTTONs = [ u'в', u'В' ] # d
3350 SYNC_BUTTONs = [ u'н', u'Ð' ] # y
3351 # End of Russian Edition
3352else:
3353 SHOW_HIDE_MARKED_STAR_KEYs = []
3354 REPLAY_BUTTONs = []
3355 RECORD_BUTTONs = []
3356 RECORDED_BUTTONs = []
3357 OPTIONS_BUTTONs = []
3358 ADD_BUTTONs = []
3359 BROWSE_BUTTONs = []
3360 DECK_BUTTONs = []
3361 SYNC_BUTTONs = []
3362
3363# Author: Ben Lickly <blickly at berkeley dot edu>
3364# License: GNU GPL, version 3 or later; http://www.gnu.org/copyleft/gpl.html
3365# Hint-peeking add-on
3366# This add-on allows peeking at some of the fields in a flashcard
3367# before seeing the answer. This can be used to peek at word context,
3368# example sentences, word pronunciation (especially useful for
3369# Chinese/Japanese/Korean), and much more.
3370
3371def _showHint(self): # self == mw.reviewer
3372 """To show next hint, simply click 5 or H key."""
3373 global F9_HINT_PEEKED
3374 F9_HINT_PEEKED = True
3375 return self.web.page().mainFrame().evaluateJavaScript("""
3376 (function(){
3377 var customEvent = document.createEvent('MouseEvents');
3378 customEvent.initEvent('click', false, true);
3379 var arr = document.getElementsByClassName('hint'); //.getElementsByTagName('a');
3380 if (arr.length>0) {
3381 for (var i=0; i<arr.length; i++) {
3382 var l=arr[i]; //alert(l.outerHTML);
3383 if (l.nodeName.toUpperCase() == 'A') {
3384 if (l.href.charAt(l.href.length-1) === '#' & l.style.display != 'none') {
3385 l.dispatchEvent(customEvent);
3386 var ll = arr[i+1];
3387 var btn = ll.getElementsByClassName('replaybutton');
3388 if (btn.length>0) {
3389 for (var i=0; i<btn.length; i++) {
3390 var lll=btn[i];
3391 if (lll.nodeName.toUpperCase() == 'A') {
3392 var gustomEvent = document.createEvent('MouseEvents');
3393 gustomEvent.initEvent('click', false, true);
3394 lll.dispatchEvent(gustomEvent); //alert(lll.href); //alert(lll.innerHTML);
3395 }
3396 }
3397 }
3398 return true; // break;
3399 }
3400 }
3401 } // for
3402 }
3403 return false;
3404 }())
3405 """)
3406
3407def _doHint(self): # self == mw.reviewer
3408 global F9_HINT_PEEKED
3409 if not _showHint(self):
3410 if self.state == "question":
3411 self._showAnswer() # ._showAnswerHack()
3412 elif self.state == "answer":
3413 F9_HINT_PEEKED = False
3414 self._initWeb() # ._initWeb() # _showQuestion()
3415 #pass
3416
3417def _showAllHints():
3418 """To show hints, simply click all show hint buttons."""
3419 global F9_HINT_PEEKED
3420 F9_HINT_PEEKED = True
3421 mw.reviewer.web.eval("""
3422 (function(){
3423 var customEvent = document.createEvent('MouseEvents');
3424 customEvent.initEvent('click', false, true);
3425 var arr = document.getElementsByClassName('hint'); //.getElementsByTagName('a');
3426 if (arr.length===0) return false;
3427 for (var i=0; i<arr.length; i++) {
3428 var l=arr[i];
3429 if (l.nodeName.toUpperCase() == 'A') {
3430 if (l.href.charAt(l.href.length-1) === '#' & l.style.display != 'none') {
3431 l.dispatchEvent(customEvent);
3432 var ll = arr[i+1]; //alert(ll.innerHTML);
3433 var btn = ll.getElementsByClassName('replaybutton');
3434 if (btn.length===0) continue;
3435 for (var i=0; i<btn.length; i++) {
3436 var lll=btn[i];
3437 if (lll.nodeName.toUpperCase() == 'A') {
3438 var gustomEvent = document.createEvent('MouseEvents');
3439 gustomEvent.initEvent('click', false, true);
3440 lll.dispatchEvent(gustomEvent); //alert(lll.href); //alert(lll.innerHTML);
3441 }
3442 }
3443 }
3444 }
3445 }
3446 }())
3447 """)
3448
3449def on_showHint():
3450 _doHint(mw.reviewer)
3451
3452def on_showAllHints():
3453 _showAllHints()
3454
3455# Copyright © 2013–2014 Roland Sieker <ospalh@gmail.com>
3456# -- from COLORFUL_TOOLBARS.py
3457# License: GNU GPL, version 3 or later; http://www.gnu.org/copyleft/gpl.html
3458
3459def ask_delete():
3460 #"""Delete a note after asking the user."""
3461 if askUser(u"Удалить запиÑÑŒ?" if lang=='ru' else _(u'Delete note?'), defaultno=True):
3462 mw.reviewer.onDelete()
3463
3464# Name: Disable the delete key in reviews
3465if A['DISABLE_DEL'][0]:
3466 aqt.mw.disconnect(aqt.mw.reviewer.delShortcut, aqt.qt.SIGNAL("activated()"), aqt.mw.reviewer.onDelete)
3467 aqt.mw.connect(aqt.mw.reviewer.delShortcut, aqt.qt.SIGNAL("activated()"), ask_delete)
3468
3469def go_deck_browse():
3470 """Open the deck browser."""
3471 mw.moveToState("deckBrowser")
3472
3473def go_study():
3474 """Start studying cards."""
3475 mw.col.reset()
3476 mw.col.startTimebox()
3477 mw.moveToState("review")
3478
3479def go_options():
3480 """Options group..."""
3481 try:
3482 mw.reviewer.onOptions()
3483 except AttributeError:
3484 pass
3485
3486def overeview():
3487 if mw.state == "overview":
3488 mw.col.startTimebox()
3489 mw.moveToState("review")
3490 if mw.state == "overview":
3491 mw.moveToState("deckBrowser")
3492 else:
3493 mw.moveToState("overview")
3494
3495##
3496
3497def new_replayq(self, card, previewer=None):
3498 s = previewer if previewer else self
3499 if not s.card:
3500 return true # false
3501 else:
3502 return s.mw.col.decks.confForDid(
3503 s.card.odid or s.card.did).get('replayq', True)
3504
3505Reviewer._replayq = new_replayq # What is it? This is for Replay Audio.
3506
3507##
3508
3509# -- Answer Confirmation plugin for Anki 2.0 --
3510# https://ankiweb.net/shared/info/3882211885
3511
3512# Author: Albert Lyubarsky
3513# Email: albert.lyubarsky@gmail.com
3514# License: GNU GPL, version 3 or later; http://www.gnu.org/copyleft/gpl.html
3515
3516# Very simple add-on.
3517# Show pressed Answer Button as tooltip
3518# only on pressed key, no tooltip on mouse click.
3519
3520# Useful when answering by pressing shortcut keys
3521# e.i.: 1,2,3,4 or default answer button e.i.: enter or space
3522
3523answerCard_LA = ''
3524
3525# -- if ANSWER_CONFIRMATION: --
3526def answerCard_before(self, ease) :
3527 global answerCard_LA
3528 la = self._answerButtonList()
3529 al = [item for item in la if item[0] == ease]
3530 if len(al) > 0 and ( B['B10_ANSWER_CONFIRMATION'][0] > 1 and ( ease<=1 or ease > 1 and ( ease != self._defaultEase() or ( self.mw.col.sched.answerButtons(self.card) <4 and B['B10_ANSWER_CONFIRMATION'][0] and ease==1 or self.mw.col.sched.answerButtons(self.card) == 4 and B['B10_ANSWER_CONFIRMATION'][0] and ease<3) ) ) ):
3531 tooltip(al[0][1], period=1500)
3532 answerCard_LA = al[0][1]
3533 else:
3534 answerCard_LA = ''
3535
3536"""
3537Name: Answer Key Cascade
3538Filename: Answer_Key_Cascade.py
3539Version: 0.5
3540Author: Kenishi
3541Desc: By default Anki 2 now reduces the button count depending on card age.
3542 Cards can now have anywhere from 2 to 4 buttons max depending on age.
3543 Answering a card with 2 buttons requires you to press 1 or 2 if you are using hotkeys.
3544 This can interfere with review speed if you don't realize its 2 buttons and answer 3/4.
3545 This addon makes it so answering with a higher ease than possible will answer with the actual highest.
3546
3547 *Example* 3 Button card: You press "4", the addon answers with "3".
3548 Thanks go to ospalh(https://github.com/ospalh) for simpler implementation (should also eliminate state bugs)
3549
3550 Report bugs to https://github.com/Kenishi/Answer-Key-Cascade
3551"""
3552
3553# --------------------------
3554# key handler for any window
3555# --------------------------
3556
3557"""
3558 def keyPressEvent(self, evt):
3559 # do we have a delegate?
3560 if self.keyHandler:
3561 # did it eat the key?
3562 if self.keyHandler(evt):
3563 return
3564 # run standard handler
3565 QMainWindow.keyPressEvent(self, evt)
3566 # check global keys
3567 key = unicode(evt.text())
3568 if key == "d":
3569 self.moveToState("deckBrowser")
3570 elif key == "s":
3571 if self.state == "overview":
3572 self.col.startTimebox()
3573 self.moveToState("review")
3574 else:
3575 self.moveToState("overview")
3576 elif key == "a":
3577 self.onAddCard()
3578 elif key == "b":
3579 self.onBrowse()
3580 elif key == "S":
3581 self.onStats()
3582 elif key == "y":
3583 self.onSync()
3584"""
3585
3586def keyPressedEvent(evt):
3587 key = evt.key()
3588 text = unicode(evt.text())
3589
3590 if (key in [Qt.Key_Escape] and mw.state in ['review','overview']):
3591 mw.moveToState("deckBrowser")
3592
3593 if key in [Qt.Key_Space, Qt.Key_Return] and mw.state == 'deckBrowser':
3594 mw.col.startTimebox()
3595 mw.moveToState("review")
3596 return
3597
3598 if (key in Keys['D'] or (text in DECK_BUTTONs)): # key == "d":
3599 if mw.state == "review":
3600 mw.moveToState("deckBrowser")
3601 elif mw.state == "overview":
3602 mw.moveToState("deckBrowser")
3603 elif mw.state == "deckBrowser":
3604 mw.col.startTimebox()
3605 mw.moveToState("review")
3606
3607 # DABSY dab sy -> stats on Shift+S
3608 elif (key in Keys['A'] or text in ADD_BUTTONs): # key == "a":
3609 mw.onAddCard()
3610 elif (key in Keys['B'] or text in BROWSE_BUTTONs): # key == "b":
3611 mw.onBrowse()
3612 elif (key in Keys['Y'] or text in SYNC_BUTTONs): # key == "y":
3613 mw.onSync()
3614 else:
3615 origKeyPressEvent(evt)
3616
3617"""
3618Name: Answer Key Remap
3619Filename: Answer_Key_Remap.py
3620Version: 0.2
3621Author: fisheggs
3622Desc: By default Anki 2 now reduces the button count depending on card age.
3623 Cards can now have anywhere from 2 to 4 buttons max depending on age.
3624 Answering a card with 2 buttons requires you to press 1 or 2 if you are using hotkeys.
3625 This can interfere with review speed if you don't realize its 2 buttons and answer 3/4.
3626
3627 This maps
3628 2 button Again = 1,2 Good = 3,4
3629 3 button Again = 1,2 Good = 3 Easy = 4
3630 4 button Again = 1 Hard = 2 Good = 3 Easy = 4
3631
3632 So
3633 1 is always Again
3634 2 is always harder than Good (either Again or Hard)
3635 3 is always Good
3636 4 is always easier than (or equal to) Good
3637 Thanks to Kenishi for Answer_Key_Cascade from which this code was modified.
3638
3639v0.2 203-07-19 -- buttet proof lookup incase extra buttons are added
3640"""
3641
3642def onKeyPlus():
3643 global soundtrack_q_number, soundtrack_a_number
3644 if mw.state == 'review':
3645 if mw.reviewer.state == 'question':
3646 if soundtrack_q_number >= len(soundtrack_q_list):
3647 soundtrack_q_number = 0
3648 if soundtrack_q_number < len(soundtrack_q_list):
3649 play(soundtrack_q_list[soundtrack_q_number])
3650 soundtrack_q_number += 1
3651 else:
3652 if soundtrack_a_number >= len(soundtrack_a_list):
3653 soundtrack_a_number = 0
3654 if soundtrack_a_number < len(soundtrack_a_list):
3655 play(soundtrack_a_list[soundtrack_a_number])
3656 soundtrack_a_number += 1
3657
3658def onAudioList():
3659 if len(soundtrack_q_list)+len(soundtrack_a_list)>0:
3660 showInfo('' +
3661 'Replay Q <b>'+ unicode(soundtrack_q_number)+ '</b> '+ unicode(len(soundtrack_q_list))+ ' '+ unicode(soundtrack_q_list) + '<br>\n<br>' +
3662 'Replay A <b>'+ unicode(soundtrack_a_number)+ '</b> '+ unicode(len(soundtrack_a_list))+ ' '+ unicode(soundtrack_a_list) + '<br>\n<br>' +
3663 '')
3664 else:
3665 showInfo(u"Звуковых файлов на Ñтой карте нет." if lang=='ru' else 'There is no any sound on this card.')
3666
3667# Name: Handy Answer Keys Shortcuts
3668# Copyright: Vitalie Spinu
3669# License: GNU GPL, version 3 or later; http://www.gnu.org/copyleft/gpl.html
3670#
3671# Bind 'j', 'k', 'l', ';' to card answering actions. This allows answering with
3672# right hand, keeping your thumb on SPC (default action) and other fingers on
3673# 'j', 'k', 'l', ';'. If the number of buttons is less than 4, 'l' and ';' will
3674# do the right thing - choose the maximal ease level.
3675#
3676# If you are in the "question" state (no answer is yet visible) these keys
3677# bypass the display of the answer and automatically set the ease level. So if
3678# you press ';', the note is automatically marked with the highest ease level
3679# (last button in answer state), if you press 'k' or 'l', the note is marked by
3680# the default ease level ("good"), if you press 'j', the note is marked as hard
3681# (first button).
3682# As a bonus 'z' is bound to undo.
3683
3684# -------------------------------
3685# key handler for reviewer window
3686# -------------------------------
3687def newKeyHandler(self, evt):
3688 key = evt.key()
3689 #text = unicode(evt.text())
3690
3691 """Show hint when the hint peeking key is pressed."""
3692 if (self.state == "question" or self.state == "answer"):
3693 if (A['F9_HINT_PEEKING'][0] and (key in Keys['5'])):# or text in Text5)):
3694 _doHint(mw.reviewer)
3695 return True
3696 else:
3697 return _old(self, evt)
3698
3699 isq = self.state == "question"
3700 if isq:
3701 if A['ANSWER_BYPASS'][0] and \
3702 ( key in Keys['1'] or \
3703 key in Keys['2'] or \
3704 key in Keys['3'] or \
3705 key in Keys['4'] ):
3706 self._showAnswer() #._showAnswerHack()
3707 elif A['ANSWER_USING_REPLY_KEYS'][0] and \
3708 ( key in Keys['1'] or \
3709 key in Keys['2'] or \
3710 key in Keys['3'] or \
3711 key in Keys['4'] ):
3712 """
3713 ((key in Keys['1'] or text in Text1) or \
3714 (key in Keys['2'] or text in Text2) or \
3715 (key in Keys['3'] or text in Text3) or \
3716 (key in Keys['4'] or text in Text4)):
3717 """
3718 self._showAnswer()
3719 return
3720
3721 cnt = mw.col.sched.answerButtons(mw.reviewer.card) # Get button count
3722 rst = mw.reviewer.state # self.state
3723
3724# Copyright: Damien Elmes <anki@ichi2.net>
3725# License: GNU GPL, version 3 or later; http://www.gnu.org/copyleft/gpl.html
3726# Ignore_spaceenter_when_answer_shown.ppy
3727# Set focus to middle area when answer shown, so space does not trigger the answer buttons.
3728
3729# Number key 0 shows answer
3730# by Madman Pierre, petere@madmanpierre.com
3731
3732 if (rst == "question" or rst == "answer") and (key in Keys['Undo']):# or text in TextUndo):
3733 try: # throws an error on undo -> do -> undo pattern, otherwise works fine
3734 mw.onUndo()
3735 except:
3736 pass
3737
3738 elif rst == "answer" and key in [Qt.Key_Return, Qt.Key_Enter]:
3739 return True # to prevent pass through multiple cards
3740 elif (key in [Qt.Key_Space]):# or text == " "):
3741 if self.state == "question":
3742 self._showAnswer() #._showAnswerHack()
3743 elif self.state == "answer":
3744 if B['B10_ANSWER_CONFIRMATION'][0]:
3745 answerCard_before(self, self._defaultEase())
3746 self._answerCard(self._defaultEase())
3747 elif rst == "question" and key in Keys8:
3748 self._showAnswer() #._showAnswerHack()
3749 elif rst == "question" and key in Keys['0'] and A['KEY0'][0]:
3750 #self._showAnswer() #._showAnswerHack()
3751 return True
3752 elif rst == "answer" and key in Keys['0'] and A['KEY0'][0]:
3753 if B['B10_ANSWER_CONFIRMATION'][0]:
3754 answerCard_before(self, self._defaultEase())
3755 self._answerCard(self._defaultEase())
3756
3757 elif rst == "answer" and (key in Keys['1']):# or text in Text1):
3758 if B['B10_ANSWER_CONFIRMATION'][0]:
3759 answerCard_before(self, 1)
3760 self._answerCard(1)
3761 elif rst == "answer" and (key in Keys['2']):# or text in Text2):
3762 if B['B10_ANSWER_CONFIRMATION'][0]:
3763 answerCard_before(self, 2)
3764 self._answerCard(2)
3765 elif rst == "answer" and (key in Keys['3']):# or text in Text3):
3766 if cnt == 2 or cnt == 3:
3767 if B['B10_ANSWER_CONFIRMATION'][0]:
3768 answerCard_before(self, 2)
3769 self._answerCard(2)
3770 else: # 4
3771 if B['B10_ANSWER_CONFIRMATION'][0]:
3772 answerCard_before(self, 3)
3773 self._answerCard(3)
3774 elif rst == "answer" and (key in Keys['4']):# or text in Text4):
3775 if B['B10_ANSWER_CONFIRMATION'][0]:
3776 answerCard_before(self, cnt)
3777 self._answerCard(cnt)
3778
3779 elif (key in Keys['Aster']):# or text in SHOW_HIDE_MARKED_STAR_KEYs):
3780 self.onMark()
3781 elif (key in Keys['Plus']): # Replay Next Audio
3782 onKeyPlus()
3783 #elif (key in Keys['R']):# or text in REPLAY_BUTTONs):
3784 # self.replayAudio()
3785 # return True
3786
3787 #elif (key in KeysO or text in OPTIONS_BUTTONs):
3788 # self.onOptions()
3789
3790 #elif (key in KeysG or text in RECORDED_BUTTONs): # key == "V":
3791 # self.onReplayRecorded()
3792 #elif (key in KeysV or text in RECORD_BUTTONs): # key == "v":
3793 # self.onRecordVoice()
3794
3795 #elif (key == Qt.CTRL+Qt.Key_PageUp and self.state == "answer"):
3796 # go_study()
3797 # The keys codes come, but not in sum, just separately, one by one.
3798
3799 #elif (key == Qt.CTRL+Qt.Key_PageDown and self.state == "question"):
3800 # self._showAnswer() #._showAnswerHack()
3801 # приходить-то коды приходÑÑ‚, но делают Ñто по отдельноÑти
3802
3803 else:
3804 origKeyHandler(self, evt)
3805
3806# -- Refocus Card when Reviewing --
3807# Refocus_Card_when_Reviewing.py
3808# Copyright: Edgar Simo-Serra <bobbens@gmail.com>
3809# License: GNU GPL, version 3 or later; http://www.gnu.org/copyleft/gpl.html
3810# def interface_refocus():
3811# mw.web.setFocus()
3812# addHook( "showQuestion", interface_refocus )
3813# addHook( "showAnswer", interface_refocus )
3814
3815# Set focus to middle area when answer shown,
3816# so enter does not trigger the answer buttons.
3817
3818def noAnswer():
3819 mw.web.setFocus() # mw.reviewer.web.setFocus()
3820
3821#############################################################################################
3822
3823if A['KEYS_HANDLER'][0]:
3824
3825 #addHook("showQuestion", noAnswer) # with this hook space don't stop passing through multiple cards at once
3826 addHook("showAnswer", noAnswer)
3827
3828 origKeyHandler = Reviewer._keyHandler
3829 Reviewer._keyHandler = newKeyHandler
3830
3831 origKeyPressEvent = mw.keyPressEvent
3832 mw.keyPressEvent = keyPressedEvent
3833
3834# -- Toggle Full Screen F11 --
3835# https://ankiweb.net/shared/info/1703043345
3836
3837# Copyright: Jannick Drolf; ported from Damien Elmes <anki@ichi2.net>
3838# License: GNU GPL, version 3 or later; http://www.gnu.org/copyleft/gpl.html
3839
3840# This plugin adds the ability to toggle full screen mode.
3841# It adds an item to the tools menu.
3842
3843def onFullScreen():
3844 mw.setWindowState(mw.windowState() ^ Qt.WindowFullScreen)
3845
3846mw.form.menuTools.addSeparator() # WTF?!
3847
3848if A['TOGGLE_FULL_SCREEN_F11'][0]:
3849 F11_action = QAction(mw)
3850 F11_action.setText("Полно&Ñкранный режим" if lang == 'ru' else "&Toggle Full Screen")
3851 if A['ANKI_MENU_ICONS'][0]:
3852 F11_action.setIcon(QIcon(os.path.join(MUSTHAVE_COLOR_ICONS, 'full_screen.png')))
3853 F11_action.setShortcut(HOTKEY['full_screen'][0])
3854 mw.connect(F11_action, SIGNAL("triggered()"), onFullScreen)
3855 if mw_addon_view_menu_exists:
3856 mw.addon_view_menu.addSeparator()
3857 mw.addon_view_menu.addAction(F11_action) # mw.form.menuTools
3858
3859#################################################################################################
3860# hotkeys does not work on context menu
3861# for information purpose only (exploratory testing is anticipated)
3862
3863opts = [
3864 [_("Mark Note"), "*", mw.reviewer.onMark],
3865 None,
3866 [_("Bury Card"), "-", mw.reviewer.onBuryCard],
3867 [_("Bury Note"), "=", mw.reviewer.onBuryNote],
3868 [_("Suspend Card"), "@", mw.reviewer.onSuspendCard],
3869 [_("Suspend Note"), "!", mw.reviewer.onSuspend],
3870 [_("Delete Note"), "Delete", ask_delete], # mw.reviewer.onDelete],
3871 None,
3872 [_("Options"), "O", mw.reviewer.onOptions],
3873]
3874
3875opts.extend([
3876 None,
3877 [_("Replay Audio"), "R", mw.reviewer.replayAudio],
3878 [u"Проиграть Ñледующее аудио" if lang=="ru" else _("Replay Next Audio"), "+", onKeyPlus],
3879 None,
3880])
3881
3882opts.append(
3883 [_("Record Own Voice"), HOTKEYZ['record_own_voice'][0], mw.reviewer.onRecordVoice],
3884)
3885opts.append(
3886 [_("Replay Own Voice"), HOTKEYZ['replay_own_voice'][0], mw.reviewer.onReplayRecorded],
3887)
3888
3889def go_lambda(adres):
3890 openLink(adres)
3891
3892def go_writing():
3893 openLink("http://ankisrs.net/docs/addons.html")
3894
3895def go_AnkiTest():
3896 openLink("http://finpapa.ucoz.ru/index.html")
3897
3898def go_official():
3899 openLink("http://ankisrs.net/")
3900
3901def go_AnkiSRS():
3902 openLink("http://ankisrs.net/#download")
3903
3904def go_AnkiWeb():
3905 openLink("https://ankiweb.net/")
3906
3907def go_AnkiWeb_decks():
3908 openLink("https://ankiweb.net/shared/decks/")
3909
3910def go_AnkiWeb_addons():
3911 openLink("https://ankiweb.net/shared/addons/")
3912
3913def go_media_folder():
3914 d = os.path.join(mw.pm.profileFolder(), "collection.media")
3915 openFolder(d)
3916
3917def go_backups_folder():
3918 d = os.path.join(mw.pm.profileFolder(), "backups")
3919 openFolder(d)
3920
3921def go_addons_folder():
3922 openFolder(mw.addonManager.addonsFolder())
3923
3924def go_data_folder():
3925 openFolder(os.path.join(mw.addonManager.addonsFolder(), '..'))
3926
3927def go_profile_folder():
3928 openFolder(mw.pm.profileFolder())
3929
3930def go_answer():
3931 if mw.state == 'review':
3932 if mw.reviewer.state == 'question':
3933 anki.sound.stopMplayer()
3934 mw.reviewer._showAnswer() # _showAnswerHack()
3935 # mw.reviewer notwithstanding 'review' !!! ,Однако
3936
3937def go_question():
3938 global F9_HINT_PEEKED
3939 if mw.state == 'review':
3940 if mw.reviewer.state == 'answer' or mw.reviewer.state == 'question': # refresh FrontSide on PgUp
3941 anki.sound.stopMplayer()
3942 F9_HINT_PEEKED = True
3943 mw.reviewer._initWeb() # _showQuestion()
3944
3945############
3946# --
3947
3948sync_aktion = QAction(mw)
3949sync_aktion.setText(u"Син&хронизировать Ñ AnkiWeb" if lang=="ru" else _(u"S&ynchronize with AnkiWeb"))
3950if A['ANKI_MENU_ICONS'][0]:
3951 sync_aktion.setIcon(QIcon(os.path.join(MUSTHAVE_COLOR_ICONS, 'sync.png')))
3952sync_aktion.setShortcut(QKeySequence(HOTKEYZ['Synchro'][0]))
3953mw.connect(sync_aktion, SIGNAL("triggered()"), mw.onSync)
3954
3955sync_action = QAction(mw)
3956sync_action.setText(u"Син&хронизировать Ñ AnkiWeb" if lang=="ru" else _(u"S&ynchronize with AnkiWeb"))
3957sync_action.setIcon(QIcon(os.path.join(MUSTHAVE_COLOR_ICONS, 'sync.png')))
3958sync_action.setShortcut(QKeySequence(HOTKEYZ['Sync'][0]))
3959mw.connect(sync_action, SIGNAL("triggered()"), mw.onSync)
3960
3961edit_layout_action = QAction(mw) # TODO !!!
3962edit_layout_action.setText(u'&Карточки...' if lang=='ru' else _(u"&Cards..."))
3963edit_layout_action.setIcon(QIcon(os.path.join(MUSTHAVE_COLOR_ICONS, 'edit_layout.png')))
3964edit_layout_action.setShortcut(QKeySequence(HOTKEY['edit_layout'][0]))
3965mw.connect(edit_layout_action, SIGNAL("triggered()"), go_edit_layout)
3966
3967if A['COLORFUL_TOOLBAR'][0]:
3968 # DAB
3969
3970 decks_action = QAction(mw)
3971 decks_action.setText(u'Коло&ды' if lang=='ru' else _("&Decks"))
3972 decks_action.setIcon(QIcon(os.path.join(MUSTHAVE_COLOR_ICONS, 'deck_browser.png')))
3973 if A['ANKI12SHORTCUTS'][0]:
3974 decks_action.setShortcut(QKeySequence(HOTKEYZ['Decks'][0]))
3975 mw.connect(decks_action, SIGNAL("triggered()"), go_deck_browse)
3976
3977 add_notes_action = QAction(mw)
3978 add_notes_action.setText(u'Доб&авить' if lang=='ru' else _("&Add"))
3979 add_notes_action.setIcon(QIcon(os.path.join(MUSTHAVE_COLOR_ICONS, 'add.png')))
3980 add_notes_action.setShortcut(QKeySequence(HOTKEYZ['Add'][0]))
3981 mw.connect(add_notes_action, SIGNAL("triggered()"), mw.onAddCard)
3982
3983 browse_cards_action = QAction(mw)
3984 browse_cards_action.setText(u'О&бзор' if lang=='ru' else _("&Browse"))
3985 browse_cards_action.setIcon(QIcon(os.path.join(MUSTHAVE_COLOR_ICONS, 'browse.png')))
3986 browse_cards_action.setShortcut(QKeySequence(HOTKEYZ['Browse'][0]))
3987 mw.connect(browse_cards_action, SIGNAL("triggered()"), mw.onBrowse)
3988
3989 decks_aktion = QAction(mw)
3990 decks_aktion.setText(u'Коло&ды' if lang=='ru' else _("&Decks"))
3991 decks_aktion.setIcon(QIcon(os.path.join(MUSTHAVE_COLOR_ICONS, 'g-deck_browser.png')))
3992 mw.connect(decks_aktion, SIGNAL("triggered()"), go_deck_browse)
3993
3994 add_notes_aktion = QAction(mw)
3995 add_notes_aktion.setText(u'Доб&авить' if lang=='ru' else _("&Add"))
3996 add_notes_aktion.setIcon(QIcon(os.path.join(MUSTHAVE_COLOR_ICONS, 'g-add.png')))
3997 mw.connect(add_notes_aktion, SIGNAL("triggered()"), mw.onAddCard)
3998
3999 mw.form.actionImport.setShortcut(QKeySequence(HOTKEYZ['Import'][0]))
4000 mw.form.actionExport.setShortcut(QKeySequence(HOTKEYZ['Export'][0]))
4001
4002 eddit_current_action = QAction(mw)
4003 eddit_current_action.setText(u'Р&едактирование...' if lang=='ru' else _(u"&Edit..."))
4004 eddit_current_action.setIcon(QIcon(os.path.join(MUSTHAVE_COLOR_ICONS, 'edit_current.png')))
4005 eddit_current_action.setShortcut(QKeySequence(HOTKEY['Editor'][0]))
4006 mw.connect(eddit_current_action, SIGNAL("triggered()"), go_edit_current)
4007
4008 options_aktion = QAction(mw)
4009 options_aktion.setText(u'&ÐаÑтройки...' if lang=='ru' else _(u"&Options..."))
4010 options_aktion.setIcon(QIcon(os.path.join(MUSTHAVE_COLOR_ICONS, 'g-options.png')))
4011 mw.connect(options_aktion, SIGNAL("triggered()"), go_options)
4012
4013 statistics_action = QAction(mw)
4014 statistics_action.setText(u'&СтатиÑтика' if lang=='ru' else _(u"&Statistics"))
4015 statistics_action.setIcon(QIcon(os.path.join(MUSTHAVE_COLOR_ICONS, 'statistics.png')))
4016 statistics_action.setShortcut(QKeySequence(HOTKEY['Statistics'][0]))
4017 mw.connect(statistics_action, SIGNAL("triggered()"), mw.onStats)
4018
4019 statistics_aktion = QAction(mw)
4020 statistics_aktion.setText(u'&СтатиÑтика' if lang=='ru' else _(u"&Statistics"))
4021 statistics_aktion.setIcon(QIcon(os.path.join(MUSTHAVE_COLOR_ICONS, 'g-statistics.png')))
4022 mw.connect(statistics_aktion, SIGNAL("triggered()"), mw.onStats)
4023
4024 overeview_action = QAction(mw)
4025 overeview_action.setText(u'Обзор колод&ы' if lang=='ru' else _(u"&Overview Deck"))
4026 overeview_action.setIcon(QIcon(os.path.join(MUSTHAVE_COLOR_ICONS, 'study_options.png')))
4027 overeview_action.setShortcut(QKeySequence(HOTKEY['goto_stats'][0]))
4028 mw.connect(overeview_action, SIGNAL("triggered()"), overeview)
4029
4030 study_action = QAction(mw)
4031 study_action.setText(u'&Учить колоду' if lang=='ru' else _(u"S&tudy deck"))
4032 study_action.setIcon(QIcon(os.path.join(MUSTHAVE_COLOR_ICONS, 'study.png')))
4033 study_action.setShortcut(QKeySequence(HOTKEY['goto_deck'][0]))
4034 mw.connect(study_action, SIGNAL("triggered()"), go_study)
4035
4036 study_aktion = QAction(mw)
4037 study_aktion.setText(u'&Учить колоду' if lang=='ru' else _(u"S&tudy deck"))
4038 study_aktion.setIcon(QIcon(os.path.join(MUSTHAVE_COLOR_ICONS, 'g-study.png')))
4039 mw.connect(study_aktion, SIGNAL("triggered()"), go_study)
4040
4041 def onRollback():
4042 if mw.state == "review":
4043 if mw.reviewer.state == "answer":
4044 mw.reviewer._initWeb()
4045 else:
4046 mw.onUndo()
4047 else:
4048 mw.onUndo()
4049
4050 undo_onF2_action = QAction(mw)
4051 undo_onF2_action.setText(u'&Откат' if lang=='ru' else _(u"&Rollback"))
4052 undo_onF2_action.setIcon(QIcon(os.path.join(MUSTHAVE_COLOR_ICONS, 'undo.png')))
4053 #undo_onF2_action.setShortcut(QKeySequence(QKeySequence.Undo)) # ^Z only
4054 if A['COLORFUL_TOOLBAR'][0]:
4055 undo_onF2_action.setEnabled(False)
4056 mw.connect(undo_onF2_action, SIGNAL("triggered()"), mw.onUndo)
4057
4058 # monkey patch
4059 def maybeF2EnableUndo():
4060 if hasattr(mw,'col'):
4061 if mw.col and mw.col.undoName():
4062 mw.form.actionUndo.setText(_("Undo")+(" %s"%mw.col.undoName()))
4063 if A['COLORFUL_TOOLBAR'][0]:
4064 mw.form.actionUndo.setEnabled(True)
4065 undo_action.setEnabled(True)
4066 runHook("undoState", True)
4067 else:
4068 mw.form.actionUndo.setText(_("Undo"))
4069 if A['COLORFUL_TOOLBAR'][0]:
4070 mw.form.actionUndo.setEnabled(False)
4071 undo_action.setEnabled(False)
4072 runHook("undoState", False)
4073
4074 mw.maybeEnableUndo = maybeF2EnableUndo
4075
4076###############################################################################
4077#
4078 if mw_addon_go_menu_exists:
4079 mw.addon_go_menu.addAction(undo_onF2_action)
4080 mw.addon_go_menu.addSeparator()
4081 mw.addon_go_menu.addAction(eddit_current_action)
4082 mw.addon_go_menu.addAction(edit_layout_action)
4083 mw.addon_go_menu.addSeparator()
4084 # Add DABSY to the new go menu
4085 mw.addon_go_menu.addAction(decks_action)
4086 mw.addon_go_menu.addAction(add_notes_action)
4087 mw.addon_go_menu.addAction(browse_cards_action)
4088 mw.addon_go_menu.addSeparator()
4089 mw.addon_go_menu.addAction(statistics_action)
4090 mw.addon_go_menu.addAction(sync_action)
4091 mw.addon_go_menu.addSeparator()
4092 mw.addon_go_menu.addAction(overeview_action)
4093 mw.addon_go_menu.addAction(study_action)
4094
4095#####################################################
4096#
4097download_addon_action = QAction(mw)
4098download_addon_action.setText(u'Открыть Ñайт Anki&Web Ñ Ð´Ð¾Ð¿Ð¾Ð»Ð½ÐµÐ½Ð¸Ñми' if lang=='ru' else _(u"Open &AnkiWeb shared add-ons site"))
4099mw.connect(download_addon_action, SIGNAL("triggered()"), go_AnkiWeb_addons)
4100mw.form.menuPlugins.insertAction(mw.form.actionOpenPluginFolder, download_addon_action)
4101
4102if A['OPEN_FOLDERS'][0]:
4103
4104 writing_action = QAction(mw)
4105 writing_action.setText(u"&ÐапиÑание дополнений" if lang=="ru" else u"&Writing add-ons")
4106 mw.connect(writing_action, SIGNAL("triggered()"), go_writing)
4107
4108 # Открыть Ñайт Самоучитель Anki 2.0 Tutorial
4109
4110 helpful_action = QAction(mw)
4111 helpful_action.setText(u'Открыть &Ñайт "Cправочник по Anki 2.0"' if lang=='ru' else _(u"&Open Anki 2.0 HandBook"))
4112 if A['ANKI_MENU_ICONS'][0]:
4113 helpful_action.setIcon(QIcon(os.path.join(MUSTHAVE_COLOR_ICONS, 'green_tick_16.png')))
4114 mw.connect(helpful_action, SIGNAL("triggered()"), go_AnkiTest)
4115
4116 def myCreateDeck():
4117 deck = getOnlyText(_("Name for deck:"))
4118 if deck:
4119 mw.col.decks.id(deck)
4120 mw.deckBrowser.refresh()
4121 mw.onAddCard()
4122
4123 create_deck_action = QAction(mw)
4124 create_deck_action.setText(u'Со&здать колоду' if lang=='ru' else _(u"&Create Deck"))
4125 create_deck_action.setToolTip(_(u"Create ParentDeck::ChildSubDeck."))
4126 mw.connect(create_deck_action, SIGNAL("triggered()"), myCreateDeck)
4127
4128 download_deck_action = QAction(mw)
4129 download_deck_action.setText(u'Ска&чать колоды Ñ Ñайта AnkiWeb' if lang=='ru' else _(u"Down&load AnkiWeb Decks"))
4130 download_deck_action.setToolTip(_(u"Open AnkiWeb decks site."))
4131 mw.connect(download_deck_action, SIGNAL("triggered()"), go_AnkiWeb_decks)
4132
4133 go_ankisrs_action = QAction(mw)
4134 go_ankisrs_action.setText(u'&Открыть Ñайт AnkiSRS' if lang=='ru' else _(u"Open AnkiS&RS site"))
4135 mw.connect(go_ankisrs_action, SIGNAL("triggered()"), go_AnkiSRS)
4136
4137 go_official_action = QAction(mw)
4138 go_official_action.setText(u'О&ткрыть официальный Ñайт Anki' if lang=='ru' else _(u"&Official Anki site"))
4139 mw.connect(go_official_action, SIGNAL("triggered()"), go_official)
4140
4141 go_ankiweb_action = QAction(mw)
4142 go_ankiweb_action.setText(u'Открыть Ñ&айт AnkiWeb' if lang=='ru' else _(u"Open Anki&Web site"))
4143 mw.connect(go_ankiweb_action, SIGNAL("triggered()"), go_AnkiWeb)
4144
4145 go_media_action = QAction(mw)
4146 go_media_action.setText(u'Открыть папку Ñ &медиа-файлами коллекции' if lang=='ru' else _(u"Open &collection.media folder"))
4147 mw.connect(go_media_action, SIGNAL("triggered()"), go_media_folder)
4148
4149 go_backups_action = QAction(mw)
4150 go_backups_action.setText(u'Открыть папку Ñ &резервными копиÑми профилÑ' if lang=='ru' else _(u"Open &backups folder"))
4151 mw.connect(go_backups_action, SIGNAL("triggered()"), go_backups_folder)
4152
4153 go_addons_action = QAction(mw)
4154 go_addons_action.setText(u'Открыть папку Ñ &дополнениÑми' if lang=='ru' else _(u"Open add&ons folder"))
4155 mw.connect(go_addons_action, SIGNAL("triggered()"), go_addons_folder)
4156
4157 go_profile_action = QAction(mw)
4158 go_profile_action.setText(u'Открыть папку &профилÑ' if lang=='ru' else _(u"Open &profile folder"))
4159 mw.connect(go_profile_action, SIGNAL("triggered()"), go_profile_folder)
4160
4161 go_data_action = QAction(mw)
4162 go_data_action.setText(u'Открыть папку Ñ Ð´Ð°&нными вÑех профилей' if lang=='ru' else _(u"Open da&ta folder"))
4163 mw.connect(go_data_action, SIGNAL("triggered()"), go_data_folder)
4164
4165 def go_pgm_folder():
4166 fldr = os.path.abspath(sys.argv[0])
4167 try:
4168 if fldr.index('\/'):
4169 fldr = '\/'.join(fldr.split('\/')[:-1])
4170 except ValueError:
4171 pass
4172 try:
4173 if fldr.index('\\'):
4174 fldr = '\\'.join(fldr.split('\\')[:-1])
4175 except ValueError:
4176 pass
4177 openFolder(fldr)
4178
4179 go_pgm_action = QAction(mw)
4180 go_pgm_action.setText(u'Открыть папку про&граммы' if lang=='ru' else _(u"Open progra&m folder"))
4181 mw.connect(go_pgm_action, SIGNAL("triggered()"), go_pgm_folder)
4182
4183 mw.form.menuCol.insertAction(mw.form.actionExit, create_deck_action)
4184 mw.form.menuCol.insertAction(mw.form.actionExit, download_deck_action)
4185 mw.form.menuCol.insertAction(mw.form.actionExit, sync_aktion)
4186
4187 mw.form.menuCol.insertAction(mw.form.actionExit, go_ankisrs_action)
4188 mw.form.menuCol.insertAction(mw.form.actionExit, go_ankiweb_action)
4189 mw.form.menuCol.insertSeparator(mw.form.actionExit)
4190
4191 mw.form.menuCol.insertAction(mw.form.actionExit, go_media_action)
4192 mw.form.menuCol.insertAction(mw.form.actionExit, go_backups_action)
4193 mw.form.menuCol.insertAction(mw.form.actionExit, go_profile_action)
4194 mw.form.menuCol.insertAction(mw.form.actionExit, go_addons_action)
4195 mw.form.menuCol.insertAction(mw.form.actionExit, go_data_action)
4196 mw.form.menuCol.insertAction(mw.form.actionExit, go_pgm_action)
4197 mw.form.menuCol.insertSeparator(mw.form.actionExit)
4198
4199 mw.form.menuHelp.insertAction(mw.form.actionDonate, go_official_action)
4200 if not os.path.exists(os.path.join(mw.pm.addonFolder(), "--handbook.py")):
4201 mw.form.menuHelp.addAction(writing_action)
4202 if lang=='ru':
4203 mw.form.menuHelp.addSeparator()
4204 mw.form.menuHelp.addAction(helpful_action)
4205
4206 download_decks_action = QAction(mw)
4207 download_decks_action.setText(u'Открыть Ñа&йт AnkiWeb Ñ ÐºÐ¾Ð»Ð¾Ð´Ð°Ð¼Ð¸' if lang=='ru' else _(u"Open AnkiWeb shared &Decks"))
4208 mw.connect(download_decks_action, SIGNAL("triggered()"), go_AnkiWeb_decks)
4209
4210 download_addons_action = QAction(mw)
4211 download_addons_action.setText(u'Открыть Ñай&Ñ‚ AnkiWeb Ñ Ð´Ð¾Ð¿Ð¾Ð»Ð½ÐµÐ½Ð¸Ñми' if lang=='ru' else _(u"Open AnkiWeb shared &Add-ons"))
4212 mw.connect(download_addons_action, SIGNAL("triggered()"), go_AnkiWeb_addons)
4213
4214 lambda_action = QAction(mw)
4215 lambda_action.setText(u'Anki’s Tender. This is the home of all support for Anki')
4216 mw.connect(lambda_action, SIGNAL("triggered()"), lambda: go_lambda('https://anki.tenderapp.com/'))
4217
4218 lambda2_action = QAction(mw)
4219 lambda2_action.setText(u'reddit.com r Anki. This is a forum about the Anki flashcard program')
4220 mw.connect(lambda2_action, SIGNAL("triggered()"), lambda: go_lambda('https://www.reddit.com/r/Anki/'))
4221
4222 mw.form.menuHelp.addSeparator()
4223 mw.form.menuHelp.addAction(download_decks_action)
4224 mw.form.menuHelp.addAction(download_addons_action)
4225 mw.form.menuHelp.addAction(lambda_action)
4226 mw.form.menuHelp.addAction(lambda2_action)
4227 mw.form.menuHelp.addSeparator()
4228
4229##############################
4230#
4231get_A_list_action = QAction(mw)
4232get_A_list_action.setText("&УправлÑющие переменные" if lang == 'ru' else "A va&riables")
4233if A['ANKI_MENU_ICONS'][0]:
4234 get_A_list_action.setIcon(QIcon(os.path.join(MUSTHAVE_COLOR_ICONS, 'sheet.png')))
4235get_A_list_action.setShortcut(QKeySequence(HOTKEY['F3_A'][0]))
4236mw.connect(get_A_list_action, SIGNAL("triggered()"), _getA)
4237
4238#mw.addon_cards_menu.addSeparator()
4239mw.addon_cards_menu.addAction(get_A_list_action)
4240mw.addon_cards_menu.addSeparator()
4241
4242##############################
4243#
4244# -- Accept Anki 1.2 shortcuts to list decks, add cards and open browser --
4245# https://ankiweb.net/shared/info/544525276
4246
4247# Copyright: Damien Elmes <anki@ichi2.net>
4248# License: GNU GPL, version 3 or later; http://www.gnu.org/copyleft/gpl.html
4249
4250# Emulate some Anki 1.2 shortcuts.
4251# Allows you to use the old ctrl+d, ctrl+f and ctrl+w shortcuts.
4252
4253if A['ANKI12SHORTCUTS'][0]:
4254
4255 mw.otherDeck = QShortcut(QKeySequence("Ctrl+w"), mw) # old Deck
4256 #mw.otherAdd = QShortcut(QKeySequence("Ctrl+d"), mw) # old Add hinders to new Decks
4257 mw.otherBrowse = QShortcut(QKeySequence("Ctrl+f"), mw) # old Browse
4258
4259 mw.connect(
4260 mw.otherDeck, SIGNAL("activated()"), lambda: mw.moveToState("deckBrowser"))
4261 #mw.connect(
4262 # mw.otherAdd, SIGNAL("activated()"), lambda: mw.onAddCard())
4263 mw.connect(
4264 mw.otherBrowse, SIGNAL("activated()"), lambda: mw.onBrowse())
4265
4266"""
4267Nice, but should have more shortcuts.
4268Posted on 2013-01-30
4269
4270I loved getting my CTRL+F open browser shortcut back,
4271but I'd also like to have the CTRL+E to edit the current card
4272(or to close the editing screen and get back to review screen).
4273Currently it's a shortcut to "export deck",
4274which is totally stupid (who needs a shortcut for that...).
4275
4276Having customizable shortcuts for more actions would be awesome.
4277"""
4278
4279# ----- ******** ############## ******** -----
4280
4281"""
4282 Super helpful, but I have a suggested feature.
4283Posted on 2013-11-04
4284
4285No complaints whatsoever about this plugin, it does just as advertised.
4286
4287One suggested feature though, when I'm doing reviews and a card only has one potential pass interval
4288(i.e., "Good") it'd be great if the "Good" button was larger too.
4289What I picture is the "Again" button being small and taking up a portion of the space
4290previously occupied by the "Show Answer" button, and then the larger "Good" button taking up that remaining space.
4291
4292Likewise when there are only two intervals available, it'd be awesome
4293if the "Good" and "Easy" buttons took up the space that the aforementioned,
4294larger "Good" button would take up, but this is less important than getting a large "Good" button.
4295It'd be a nice additional touch though.
4296
4297This'd be an awesome addition to make those difficult or new cards less frustrating than they already are.
4298"""
4299
4300# Replace _answerButtonList method
4301def answerButtonList(self):
4302 l = ((1, "<style>" + ("button span{font-size:x-large;}" if B['B08_BIG_BUTTONS'][0] else "") + \
4303 " button small { color:#999;font-weight:400;padding-left:.35em;font-size: small; } " + \
4304 "</style>" + "<span style='color:" + btn_Again[0] + ";'>" + btn_Again[1] + "</span>", BEAMS1),)
4305 cnt = self.mw.col.sched.answerButtons(self.card)
4306 if cnt == 2:
4307 return l + ((2, "<span style='color:" + btn_Good[0] + ";font-weight:bold;'>" + btn_Good[1] + "</span>", BEAMS3),)
4308 # the comma at the end is mandatory, a subtle bug occurs without it
4309 elif cnt == 3:
4310 return l + ((2, "<span style='color:" + btn_Good[0] + ";font-weight:bold;'>" + btn_Good[1] + "</span>", BEAMS2),
4311 (3, "<span style='color:" + btn_Easy[0] + ";'>" + btn_Easy[1] + "</span>", BEAMS1))
4312 else:
4313 return l + ((2, "<span style='color:" + btn_Hard[0] + ";'>" + btn_Hard[1] + "</span>", BEAMS1),
4314 (3, "<span style='color:" + btn_Good[0] + ";font-weight:bold;'>" + btn_Good[1] + "</span>", BEAMS1),
4315 (4, "<span style='color:" + btn_Easy[0] + ";'>" + btn_Easy[1] + "</span>", BEAMS1))
4316# all buttons are with coloured text
4317# and have an equal width with buttons in Night Mode
4318
4319# _____ ------ ========== *********** ========== -------- ______
4320
4321if A['CUSTOM_CONGRAT_MSG'][0] and lang == 'ru':
4322 th_Unseen = u"<small style='color:#DA70D6;'>Ðеви-<br>данные</small>" # Ðе про-<br>Ñмотрено</small>"
4323 th_Suspended = u'<small style="color:#c90;">ИÑклю-<br>чённые</small>'
4324 th_Buried = u'<small style="color:#960;">Отло-<br>женные</small>'
4325 _Buried = u'Отложенные' # поÑкольку у Дамьена не предложено Ñто Ñлово Ð´Ð»Ñ Ð¿ÐµÑ€ÐµÐ²Ð¾Ð´Ð°
4326elif A['CUSTOM_CONGRAT_MSG'][0] and lang == 'en':
4327 th_Unseen = u"<small style='color:#DA70D6;'>Unseen</small>"
4328 th_Suspended = u'<small style="color:#c90;">Sus-<br>pen-<br>ded </small>'
4329 th_Buried = u'<small style="color:#960;">Buried</small>'
4330 _Buried = u'Buried'
4331else:
4332 th_Unseen = u"<span style='color:#DA70D6;'>" + _("Unseen") + "</span>"
4333 th_Suspended = u"<span style='color:#c90;'>" + _("Suspended") + "</span>"
4334 th_Buried = u"<span style='color:#960;'>" + _("Buried") + "</span>"
4335 _Buried = _("Buried") # у Дамьена переводÑÑ‚ÑÑ Ñ‚Ð¾Ð»ÑŒÐºÐ¾ Suspended и Suspended+Buried
4336
4337#################################################################################################
4338
4339# Copyright © 2012–2014 Roland Sieker <ospalh@gmail.com>
4340# Show_learn_count.py
4341# License: GNU AGPL, version 3 or later;
4342# http://www.gnu.org/licenses/agpl.html
4343
4344"""
4345Anki-2 add-on to show the learn count in the deck browser proper way
4346"""
4347
4348from aqt.deckbrowser import DeckBrowser
4349
4350def my_studyDeck(self, did):
4351 self.scrollPos = self.web.page().mainFrame().scrollPosition()
4352 self.mw.col.decks.select(did)
4353 self.mw.col.startTimebox()
4354 self.mw.moveToState("review")
4355 if self.mw.state == "overview":
4356 more_tool_bar_off()
4357 tooltip(_("No cards are due yet."))
4358
4359# Event handlers
4360# Monkey patching
4361
4362def my_studyHandler(self, url):
4363 if ":" in url:
4364 (cmd, arg) = url.split(":")
4365 else:
4366 cmd = url
4367 arg = ''
4368 if cmd == "study":
4369 my_studyDeck(self, arg)
4370
4371DeckBrowser._linkHandler = wrap(DeckBrowser._linkHandler, my_studyHandler) # , 'before')
4372
4373################################################################
4374def nonzeroColour(acnt, colour, did):
4375 if not acnt:
4376 colour = "silver"
4377 achk = B['B04_HIDE_BIG_NUMBERS'][0] and acnt > B['B04_HIDE_BIG_NUMBER'][0]
4378 if achk:
4379 cnt = "%s+" % (B['B04_HIDE_BIG_NUMBER'][0])
4380 else:
4381 cnt = str(acnt)
4382 return ("""<a href="study:{}" style="text-decoration:none;cursor:pointer;color:{};" {} onmouseover="this.style.textDecoration='underline';" onmouseout="this.style.textDecoration='none';"> {} </a>""".format(did, colour, \
4383 ((' title=" '+ _('Study') + ' %s "'%(acnt if achk else '')) if B['B11_BUTTON_TITLES'][0] else ''), cnt) \
4384 if did and acnt else \
4385 """<span style="color:{};{}> {} </span>""".format(colour, \
4386 (('%s" title="%s"'%(('cursor:help;' if achk else ''),(acnt if achk else ''))) if B['B11_BUTTON_TITLES'][0] else '"'), \
4387 cnt)) if acnt > 0 else """<span style="color:{};"> {} </span>""".format(colour, cnt)
4388
4389################################################################
4390def deck_browser_render_deck_tree(deck_browser, nodes, depth=0):
4391 if not nodes:
4392 return ""
4393
4394 if depth == 0:
4395 buf = """\n<tr>\n<th colspan=3 style='padding-right:.25em;color:default;'>%s</th>""" \
4396 % ( "<div style = padding-bottom:.25em; "+\
4397 (" title = Профиль " if B['B11_BUTTON_TITLES'][0] else "")+\
4398 ">" + (mw.pm.name if len(mw.pm.profiles()) > 1 and mw.pm.name else "") +\
4399 "</div><div style = font-weight:400;><i>" + _("Decks") + ":</i></div>" +\
4400 "</th>\n<td style='text-align:right;'>%s</td>" %\
4401 ("<i>"+_("Cards")+":</i>" if B['B00_MORE_OVERVIEW_STATS'][0] > 1 else "") )
4402
4403 if B['B00_MORE_OVERVIEW_STATS'][0] > 1:
4404 buf += """\n
4405<td class=count style='padding-right:.25em;color:#33f;width:4em;'>%s</td>\
4406<td class=count style='padding-right:.25em;color:#c33;width:4em;'>%s</td>\
4407<td class=count style='color:#090;padding-right:.25em;width:4em;'>%s</td>\
4408<td class=count style='color:#999;padding-right:1em;width:4em;'>%s</td>\
4409\n""" % ( _("New"), "<small>Из-<br>учить</small>" if A['CUSTOM_CONGRAT_MSG'][0] and lang == 'ru' else _('Learn'), "<small>Про-<br>верить</small>" if A['CUSTOM_CONGRAT_MSG'][0] and lang == 'ru' else _('To Review'), _("Due") )
4410
4411 if B['B00_MORE_OVERVIEW_STATS'][0] > 2:
4412 if not B['B03_GEAR_AT_END_OF_LINE'][0]:
4413 buf += "<td></td>"
4414 buf += """\n
4415<td style="padding-right:.25em;text-align:right;width:4em !important;">%s</td>\
4416<td style="padding-right:.25em;color:#c90;text-align:right;width:3em;">%s</td>\
4417<td style="padding-right:.25em;color:#960;text-align:right;width:3em;">%s</td>\
4418""" % ( th_Unseen, th_Suspended, th_Buried )
4419
4420 buf += "\n</tr>\n" + deck_browser._topLevelDragRow()
4421 else:
4422 buf = ""
4423 for node in nodes:
4424 buf += deck_browser_deck_row(deck_browser, node, depth, len(nodes))
4425 if depth == 0:
4426 buf += deck_browser._topLevelDragRow()
4427
4428 # Get due and new cards
4429 due = 0
4430 new = 0
4431 lrn = 0
4432
4433 for tree in deck_browser.mw.col.sched.deckDueTree():
4434# due += tree[2] + tree[3]
4435 due += tree[2]
4436 lrn += tree[3]
4437 new += tree[4]
4438
4439 if B['B00_MORE_OVERVIEW_STATS'][0] > 1:
4440
4441 buf += """\n
4442<tr style="vertical-align:top;">\
4443<th style="color:gray;text-align:left;">%s</th>\
4444<th align=left>%s</th><th align=left>%s</th>\
4445<th style='color:gray;text-align:right;'>%s:</th>\
4446<th class=count style='width:4em;'>%s</th>\
4447<th class=count style='width:4em;'>%s</th>\
4448<th class=count style='color:gray;width:4em;'> + %s</th>\
4449<th class=count style='color:gray;width:4em;padding-right:1em;'> = %s</th>\
4450\n""" % ( lang, _("Total"), \
4451 nonzeroColour( mw.col.cardCount(), "default", False ), \
4452 nonzeroColour(new+lrn+due, "gray", False), \
4453 nonzeroColour(new, "#33f", False), \
4454 nonzeroColour(lrn, "#c33", False), \
4455 nonzeroColour(due, "#090", False), \
4456 nonzeroColour(lrn+due, "#999", False) )
4457
4458 # options
4459 if not B['B03_GEAR_AT_END_OF_LINE'][0] and B['B00_MORE_OVERVIEW_STATS'][0] > 2:
4460 buf += "<td> </td>\n"
4461
4462 if B['B00_MORE_OVERVIEW_STATS'][0] > 2:
4463 unseen = deck_browser.mw.col.db.scalar("select count(*) from cards where queue=0")
4464 suspended = deck_browser.mw.col.db.scalar("select count(*) from cards where queue = -1")
4465 buried = deck_browser.mw.col.db.scalar("select count(*) from cards where queue = -2")
4466
4467 buf += """\
4468<td style="padding-right:.25em;width:4em !important;" align=right>%s </td>\
4469<td style="padding-right:.25em;width:3em;" align=right>%s </td>\
4470<td style="padding-right:.5em;width:3em;" align=right>%s </td>\
4471""" % (
4472 nonzeroColour(unseen, "#DA70D6", False),
4473 nonzeroColour(suspended, "#c90", False),
4474 nonzeroColour(buried, "#960", False)) # "#555500"))
4475
4476 buf += "\n</tr>\n"
4477
4478 return buf
4479
4480################################################################
4481def deck_browser_deck_row(deck_browser, node, depth, cnt):
4482 name, did, due, lrn, new, children = node
4483 deck = deck_browser.mw.col.decks.get(did)
4484
4485 if B['B00_MORE_OVERVIEW_STATS'][0] > 2:
4486 unseen = deck_browser.mw.col.db.scalar("select count(*) from cards where did = %i and queue=0" % did)
4487 suspended = deck_browser.mw.col.db.scalar("select count(*) from cards where did = %i and queue = -1" % did)
4488 buried = deck_browser.mw.col.db.scalar("select count(*) from cards where did = %i and queue = -2" % did)
4489
4490 if did == 1 and cnt > 1 and not children:
4491 # if the default deck is empty, hide it
4492 if not deck_browser.mw.col.db.scalar(
4493 "select 1 from cards where did = 1"):
4494 return ""
4495
4496 # parent toggled for collapsing
4497 for parent in deck_browser.mw.col.decks.parents(did):
4498 if parent['collapsed']:
4499 buff = ""
4500 return buff
4501 prefix = "<big><b>−</b></big>"
4502 if deck_browser.mw.col.decks.get(did)['collapsed']:
4503 prefix = "<big><b>+</b></big>"
4504
4505# due += lrn
4506
4507 def indent():
4508 return " "*6*depth
4509
4510 if did == deck_browser.mw.col.conf['curDeck']:
4511 klass = 'deck current'
4512 else:
4513 klass = 'deck'
4514
4515 buf = "\n<tr class='%s' id='%d' " % (klass, did)
4516 #buf += """_onmouseover="this.style.backgroundColor='#ddd';this.style.backgroundImage='-webkit-linear-gradient(bottom,#ddd,#eee ,#ddd)';" """
4517 #buf += """_onmouseout="this.style.backgroundColor='';this.style.backgroundImage='';" """
4518 # указание колоды под курÑором ÑбиваетÑÑ Ð¿Ñ€Ð¸ вызове контекÑтного меню под шеÑтерёнкой
4519
4520 if B['B11_BUTTON_TITLES'][0]:
4521 buf += (' title = " ' + _('Today') + ': %s "') % (new+lrn+due)
4522 buf += '>'
4523
4524 # deck link
4525 if children:
4526 collapse = """<a class=collapse href=# onclick='py.link("collapse:%d");return false;' \
4527 style="padding-left:.5em;padding-right:.1em;margin-right:.2em; \
4528 border:solid 1px transparent;border-radius:5px;display:inline-block;" \
4529 onmouseover="this.style.border='solid 1px silver';" \
4530 onmouseout="this.style.border='solid 1px transparent';" \
4531 title="%s">%s</a>""" % (
4532 did, (u' title=" Свернуть/развернуть вложенные колоды "' if lang=='ru' else ' title=" '+_(' Collapse/Downfall ')+' "') if B['B11_BUTTON_TITLES'][0] else '', prefix)
4533 else:
4534 collapse = "<span class=collapse></span>"
4535 if deck['dyn']:
4536 extraclass = "filtered"
4537 else:
4538 extraclass = ""
4539
4540 studydid = ('''onclick="py.link('study:%d');"''' % did) if (new+lrn+due)>0 else ""
4541 cursorPointer = ('''cursor:pointer''') if (new+lrn+due)>0 else ""
4542
4543 buf += """
4544 <td class=decktd colspan=4 %s style="%s;">%s%s<a class="deck %s" href=%s %s>%s</a>\
4545</td>""" % ( studydid, cursorPointer, \
4546 indent(), collapse, extraclass, \
4547 #(''' onclick="py.link('open:%d');return false;"''' % did) if (new+lrn+due)>0 else ("open:%d" % did), \
4548 # In such case KHTML doesn't recognize tag as anchor link, processes it as tag span.
4549 ('''# onclick="py.link('open:%d');return false;"''' % did) if (new+lrn+due)>0 else ("open:%d" % did), \
4550 (" title=' "+_('Study')+" '") if B['B11_BUTTON_TITLES'][0] else '', "<b>"+name+"</b>" if new+lrn+due > 0 else name)
4551
4552 if B['B00_MORE_OVERVIEW_STATS'][0] > 1:
4553 buf += """\n\
4554<td align=right %s style="%s;">%s</td>\
4555<td align=right %s style="%s;">%s</td>\
4556<td align=right %s style="%s;">%s</td>\
4557<td align=right %s style="%s;padding-right:1em;">%s</td>\
4558\n""" % ( studydid, cursorPointer, nonzeroColour(new, "#33f", did),
4559 studydid, cursorPointer, nonzeroColour(lrn, "#c33", did),
4560 studydid, cursorPointer, nonzeroColour(due, "#090", did),
4561 studydid, cursorPointer, nonzeroColour(lrn+due, "#999", did))
4562
4563 # options
4564 if not B['B03_GEAR_AT_END_OF_LINE'][0] and B['B00_MORE_OVERVIEW_STATS'][0] > 2:
4565 buf += "<td align=right class=opts style='width:1.5em!important;' onclick='return false;'> %s</td>" % deck_browser.mw.button(
4566 link="opts:%d" % did,
4567 name="<img src='qrc:/icons/gears.png'>▾")
4568
4569 if B['B00_MORE_OVERVIEW_STATS'][0] > 2:
4570 buf += """\
4571<td %s style="%s;padding-right:.25em;width:3em!important;" align=right>%s </td>\
4572<td %s style="%s;padding-right:.25em;" align=right>%s </td>\
4573<td %s style="%s;padding-right:.5em;" align=right>%s </td>\
4574""" % ( studydid, cursorPointer, nonzeroColour(unseen, "#DA70D6", did),
4575 studydid, cursorPointer, nonzeroColour(suspended, "#c90", did),
4576 studydid, cursorPointer, nonzeroColour(buried, "#960", did)) # "#555500"))
4577
4578 # options
4579 if B['B03_GEAR_AT_END_OF_LINE'][0] or B['B00_MORE_OVERVIEW_STATS'][0] < 3:
4580 buf += "\n<td align=right class=opts style='width:1.5em!important;' onclick='return false;'> %s</td>" % deck_browser.mw.button(
4581 link="opts:%d" % did,
4582 name="<img _valign=bottom src='qrc:/icons/gears.png'>▾")
4583
4584 if B['B05_STUDY_BUTTON'][0]:
4585 buf += """\n<td align=right style="font-size:smaller;"> %s</td>""" % deck_browser.mw.button(
4586 link="study:%d" % did, name="<small style = color:dodgerblue; >►</small>")
4587
4588 buf += "\n</tr>\n"
4589 # children
4590 buf += deck_browser_render_deck_tree(deck_browser, children, depth+1)
4591 return buf
4592
4593# Copyright: Juda Kaleta <juda.kaleta@gmail.com>
4594# License: GNU GPL, version 3 or later; http://www.gnu.org/copyleft/gpl.html
4595# Unseen and buried counts
4596# https://ankiweb.net/shared/info/161964983
4597# Show count of unseen and buried cards in deck browser.
4598
4599org_DeckBrowser_renderDeckTree = DeckBrowser._renderDeckTree
4600org_DeckBrowser_deckRow = DeckBrowser._deckRow
4601
4602def initDeckBro():
4603 if B['B00_MORE_OVERVIEW_STATS'][0]:
4604 DeckBrowser._renderDeckTree = deck_browser_render_deck_tree
4605 DeckBrowser._deckRow = deck_browser_deck_row
4606 else:
4607 DeckBrowser._renderDeckTree = org_DeckBrowser_renderDeckTree
4608 DeckBrowser._deckRow = org_DeckBrowser_deckRow
4609
4610initDeckBro()
4611
4612##############################################################################
4613
4614"""
4615Hierarchical Tags for Anki
4616==========================
4617
4618This addon adds hierarchical tags to the browser in [Anki][]. The addon is
4619[published on Ankiweb](https://ankiweb.net/shared/info/1089921461).
4620
4621To create hierarchies use double-colons in the tag names, for example
4622"learning::anki" or "language::japanese".
4623
4624This addon is licensed under the same license as Anki itself (GNU Affero
4625General Public License 3).
4626
4627## Known Issues
4628
4629When clicking on a tag in the hierarchy, an asterisk is added to the search
4630term. The effect of that is that all notes with that tag and all subtags are
4631searched for.
4632
4633But a side-effect is, that all tags with the same prefix are matched. For
4634example if you have a tag ``it`` and a tag ``italian``, clicking on the tag
4635``it`` would also show content from ``italian``. Let me know if this affects
4636you and I'll try to work around this.
4637
4638## Support
4639
4640The add-on was written by [Patrice Neff][]. I try to monitor threads in the
4641[Anki Support forum][]. To be safe you may also want to open a ticket on the
4642plugin's [GitHub issues][] page.
4643
4644[Anki]: http://ankisrs.net/
4645[Patrice Neff]: http://patrice.ch/
4646[Anki support forum]: https://anki.tenderapp.com/discussions/add-ons
4647[GitHub issues]: https://github.com/pneff/anki-hierarchical-tags/issues
4648"""
4649
4650# -- Just to reduce the load time of the add-ons. -- without any mod. --
4651# https://ankiweb.net/shared/info/1089921461
4652from aqt.browser import Browser
4653
4654# Separator used between hierarchies
4655SEPARATOR = '::'
4656
4657def myTagTree(self, root, _old):
4658 tags = sorted(self.col.tags.all())
4659 tags_tree = {}
4660
4661 for t in tags:
4662 if t.lower() == "marked" or t.lower() == "leech":
4663 continue
4664
4665 components = t.split(SEPARATOR)
4666 for idx, c in enumerate(components):
4667 partial_tag = SEPARATOR.join(components[0:idx + 1])
4668 if not tags_tree.get(partial_tag):
4669 if idx == 0:
4670 parent = root
4671 else:
4672 parent_tag = SEPARATOR.join(components[0:idx])
4673 parent = tags_tree[parent_tag]
4674
4675 item = self.CallbackItem(
4676 parent, c,
4677 lambda partial_tag=partial_tag: self.setFilter("tag", partial_tag + '*'))
4678 item.setIcon(0, QIcon(":/icons/anki-tag.png"))
4679
4680 tags_tree[partial_tag] = item
4681
4682# Simple, nice, and easy.
4683
4684# Very neat! Thanks!
4685# If you like the new collapsible decks, you may want to try this.
4686# The separator can be changed in the file “hierarchical_tags.pyâ€. (I use a hyphen.)
4687
4688# This changed a lot the way I use tags.
4689# Only it cannot change me. ;-)
4690# So it's a pity that there is no way
4691# to change multiple tags at once like we can change cards in the Browser.
4692# Here is a patch to ignore case in the tags (works better with ASCII tags than non-ASCII ones):
4693# https://github.com/pneff/anki-hierarchical-tags/pull/2/files
4694
4695if A['HIERARCHICAL_TAGS'][0]:
4696 Browser._userTagTree = wrap(Browser._userTagTree, myTagTree, 'around')
4697
4698######################################################################
4699# • Swap
4700# https://ankiweb.net/shared/info/1040866511
4701# License: GNU GPL, version 3 or later; http://www.gnu.org/copyleft/gpl.html
4702# Copyright (c) 2016 Dmitry Mikheev, http://finpapa.ucoz.net/
4703
4704if A['SWAP_FRONT_BACK'][0]:
4705 CASE_SENSITIVE = False # True #
4706
4707 fldlst = [
4708 ['En','Ru'],
4709 ['Eng','Rus'],
4710 ['English','Russian'],
4711 ['по-английÑки','по-руÑÑки'],
4712 ['Q','A'],
4713 ['Question','Answer'],
4714 [_('Question'),_('Answer')],
4715 ['Front','Back'],
4716 [_('Front'),_('Back')], # ВопроÑ, Ответ
4717 ]
4718
4719 fld1st = _('Front') # ВопроÑ
4720 fld2nd = _('Back') # Ответ
4721
4722 def JustSwapIt(note):
4723 global fld1st, fld2nd
4724 if not (mw.reviewer.state == 'question' or mw.reviewer.state == 'answer'):
4725 showCritical("Обмен в ÑпиÑке колод или в окне колоды невозможен,<br>только при проÑмотре (заучивании) карточек." if lang=='ru' else 'Swap is available only for cards,<br>not for decks panel nor deck overview as well.')
4726 else:
4727 if not hasattr(mw.reviewer.card,'model'):
4728 showCritical("Извините, конечно, но пока делать проÑто нечего!" if lang=='ru' else 'Oops, <s>I did it again!</s> there is <b>nothing to do</b> yet!')
4729 else:
4730 c = mw.reviewer.card
4731 if c.model()['type'] == MODEL_CLOZE:
4732 showCritical("<center>Обмен полей Ð´Ð»Ñ Ñ‚Ð¸Ð¿Ð° запиÑей <b>Ñ Ð¿Ñ€Ð¾Ð¿ÑƒÑками</b><br> не поддерживаетÑÑ. Только вручную.</center>" if lang=='ru' else """<div style="text-align:center;">It's unable to swap fields of CLOZE note type automatically.<br>Please, do it manually by yourself.</div>""")
4733 # Unfortunately, style="text-align:center;" does not work here. But <center> works.
4734 elif c.model()['type'] == MODEL_STD:
4735 fldn = note.model()['flds']
4736 fldl = len(note.fields)
4737
4738 audioSound = False
4739 for fld in fldn:
4740 if fld['name'].lower()=='audio' or fld['name'].lower()=='sound':
4741 audioSound = True
4742 break
4743
4744 fnd1st = False
4745 for fld in fldn:
4746 for lst in fldlst:
4747 if CASE_SENSITIVE:
4748 found = fld['name']==lst[0]
4749 else:
4750 found = fld['name'].lower()==lst[0].lower()
4751 if found:
4752 fnd1st = True
4753 fld1st = fld['name']
4754 break
4755 else:
4756 continue
4757 break
4758
4759 fnd2nd = False
4760 for fld in fldn:
4761 for lst in fldlst:
4762 if CASE_SENSITIVE:
4763 found = fld['name']==lst[1] and lst[0]==fld1st
4764 else:
4765 found = fld['name'].lower()==lst[1].lower() and lst[0].lower()==fld1st.lower()
4766 if found:
4767 fnd2nd = True
4768 fld2nd = fld['name']
4769 break
4770 else:
4771 continue
4772 break
4773
4774 if fldl<2:
4775 showCritical("У данной запиÑи одно-единÑтвенное поле,<br> его проÑто не Ñ Ñ‡ÐµÐ¼ обменивать." if lang=='ru' else 'It is unable to swap a note with a single field in it.')
4776 return
4777
4778 elif fldl==2: # There are two fields only? Swap it anyway.
4779 fld1st = fldn[0]['name']
4780 fld2nd = fldn[1]['name']
4781 swap_fld = note[fld1st]
4782 note[fld1st] = note[fld2nd]
4783 note[fld2nd] = swap_fld
4784
4785 elif fldl==3 and audioSound: # There are three fields only? With Audio or Sound? Swap other two anyway.
4786 fld1st = ''
4787 fld2nd = ''
4788 for fld in fldn:
4789 if fld['name'].lower()!='audio' and fld['name'].lower()!='sound' and fld1st=='':
4790 fld1st = fld['name']
4791 if fld['name'].lower()!='audio' and fld['name'].lower()!='sound' and fld2nd=='' and fld['name']!=fld1st:
4792 fld2nd = fld['name']
4793 if fld1st!='' and fld2nd!='':
4794 showInfo(unicode(fld1st)+' '+unicode(fld2nd))
4795 swap_fld = note[fld1st]
4796 note[fld1st] = note[fld2nd]
4797 note[fld2nd] = swap_fld
4798 else:
4799 showCritical('3 полÑ, но еÑть и Audio, и Sound. Что Ñ Ñ‡ÐµÐ¼ обменивать-то тогда?')
4800 return
4801
4802 # There are 3 (w/o Audio/Sound) or 4 or more fields?
4803 elif fnd1st and fnd2nd:
4804 # Swap by name if names are found in list.
4805 swap_fld = note[fld1st]
4806 note[fld1st] = note[fld2nd]
4807 note[fld2nd] = swap_fld
4808
4809 else:
4810 # Otherwise swap two first anyway.
4811 fld1st = fldn[0]['name']
4812 fld2nd = fldn[1]['name']
4813 swap_fld = note[fld1st]
4814 note[fld1st] = note[fld2nd]
4815 note[fld2nd] = swap_fld
4816
4817 note.flush() # never forget to flush
4818 tooltip(("Выполнен обмен значений между полÑми <b>%s</b> и <b>%s</b>." if lang=='ru' else '<b>%s</b> and <b>%s</b> swapped.')%(fld1st,fld2nd))
4819
4820 def JustSwapItYourself():
4821 rst = mw.reviewer.state
4822 NB = mw.reviewer.card.note()
4823 JustSwapIt(NB)
4824 mw.reset() # refresh gui
4825 if rst == 'answer':
4826 mw.reviewer._showAnswer() # ._showAnswerHack()
4827
4828 def TryItYourself(edit):
4829 JustSwapIt(edit.note)
4830 mw.reset() # refresh gui
4831 # focus field so it's saved
4832 edit.web.setFocus()
4833 edit.web.eval("focusField(%d);" % edit.currentField)
4834
4835 swap_action = QAction(('О&бмен полей %s и %s' if lang == 'ru' else _('S&wap %s and %s fields'))%(fld1st,fld2nd), mw)
4836 swap_action.setShortcut(QKeySequence(HOTKEY['swap'][0]))
4837 swap_action.setIcon(QIcon(os.path.join(MUSTHAVE_COLOR_ICONS, 'swap.png')))
4838 mw.connect(swap_action, SIGNAL("triggered()"), JustSwapItYourself)
4839
4840 def swap_off():
4841 swap_action.setEnabled(False)
4842
4843 def swap_on():
4844 swap_action.setEnabled(True)
4845
4846 def setup_buttons(editor):
4847 """Add the buttons to the editor."""
4848 editor._addButton("swap_fields", lambda edito=editor: TryItYourself(edito) , HOTKEY['swap'][0],
4849 text="Sw", tip="Swap fields (" + HOTKEY['swap'][0] +")")
4850
4851 mw.deckBrowser.show = wrap(mw.deckBrowser.show, swap_off)
4852 mw.overview.show = wrap(mw.overview.show, swap_off)
4853 mw.reviewer.show = wrap(mw.reviewer.show, swap_on)
4854
4855 # register callback function that gets executed after setupEditorButtons has run.
4856 # See Editor.setupEditorButtons for details
4857 addHook("setupEditorButtons", setup_buttons)
4858
4859
4860#######################################################################################
4861# To make ANKI working correct with images, sounds and videos
4862# start dir must contain only digits, spaces and basic English chars, maybe parentheses
4863
4864check_prog_path = [
4865 ['en',u'The path to the program folder \n\n %s \n\n should contain only ASCII characters (32-127) [0-9A-Za-z \()]'],
4866 ['ru',u'Путь к программе не должен Ñодержать кириллицы \n\n %s \n\n только латиницу из диапазона ASCII (32-127) [0-9A-Za-z \()]'],
4867]
4868#check_prog_path = False
4869
4870def get_prog_path():
4871 """Return either "Anki" org argv[0]"""
4872 argv0 = sys.argv[0]
4873 if A['CHECK_ASCII_PATH'][0] and argv0:
4874 start_dir = os.path.abspath(argv0)
4875 score = len([char for char in start_dir if ord(char)>127])
4876 # show a message box
4877 if score > 0:
4878 showInfo(custom_msg % unicode(start_dir, errors='replace'), type="critical")
4879
4880if check_prog_path:
4881 custom_msg = check_prog_path[0][1]
4882 for msgs in check_prog_path:
4883 if msgs[0] == lang:
4884 custom_msg = msgs[1]
4885 #break
4886 get_prog_path()
4887
4888###################################################################################
4889# Deck name in title
4890
4891# -- Just to reduce the load time of the add-ons. -- without any mod. --
4892# https://ankiweb.net/shared/info/3895972296
4893# v1.2.0 there, v1.3.0 here (from addons-ospalh.zip)
4894
4895# © 2012–2013 Roland Sieker <ospalh@gmail.com>
4896# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
4897
4898## Several separators between the current ‘activity’ (file, directory,
4899## web page) and the program name seem common. Pick one to taste:
4900title_separator = u' — '
4901# title_separator = u' – '
4902# title_separator = u' - ' # —–-
4903# title_separator = u' : '
4904
4905## Show the sub-deck the current card belongs to
4906show_subdeck = True
4907# show_subdeck = False
4908
4909# How to separate the clicked-on deck from the subdeck.
4910subdeck_format = u'{parent} || {child}'
4911#subdeck_format = u'{parent}:–:{child}'
4912# subdeck_format = u'{parent}(::{child})' # Old style
4913
4914## Use either "Anki"
4915#use_argv_0 = False
4916## or the program file name.
4917use_argv_0 = True
4918
4919def get_prog_name():
4920 """Return either "Anki" org argv[0]"""
4921 argv0 = sys.argv[0]
4922 if use_argv_0 and argv0:
4923 return os.path.basename(argv0)
4924 return u'Anki'
4925
4926def str_sys_argv():
4927 retv = ''
4928 for nextel in sys.argv[1:]:
4929 retv += ' ' + unicode(nextel)
4930 return retv
4931
4932class DeckNamer(object):
4933 """Functions to set the title to the deck name in Anki2 """
4934
4935 def __init__(self):
4936 self.prog_name = get_prog_name()
4937 self.profile_string = u''
4938 self.deck_name = u''
4939 self.subdeck_name = u''
4940
4941 def get_deck_name(self):
4942 """Return the deck name"""
4943 try:
4944 self.deck_name = mw.col.decks.current()['name']
4945 except AttributeError:
4946 self.deck_name = u''
4947 self.deck_name = self.deck_name.replace('::',' | ')
4948 self.subdeck_name = self.deck_name
4949 return self.deck_name
4950
4951 def get_profile_string(self):
4952 """
4953 Return the profile name.
4954
4955 When there is more than one profile, return that name,
4956 together with the title separator.
4957 """
4958 if len(mw.pm.profiles()) > 1 and mw.pm.name:
4959 self.profile_string = mw.pm.name
4960 else:
4961 self.profile_string = u''
4962 return self.profile_string
4963
4964 def deck_browser_title(self):
4965 """Set the window title when we are in the deck browser."""
4966 mw.setWindowTitle(unicode(self.get_profile_string()) +
4967 title_separator +
4968 unicode(self.prog_name) +
4969 unicode(str_sys_argv()) +
4970 '')
4971
4972 def overview_title(self):
4973 """Set the window title when we are at the overview."""
4974 mw.setWindowTitle(
4975 unicode(self.profile_string) +
4976 title_separator +
4977 unicode(self.get_deck_name()) +
4978 '')
4979
4980 def card_title(self):
4981 """Set the window title when we are reviewing."""
4982 self.overview_title()
4983 old_subdeck_name = self.subdeck_name
4984 self.subdeck_name = ""
4985 if hasattr(mw.reviewer.card,'did'): # AttributeError: 'NoneType' object has no attribute 'did'
4986 self.subdeck_name = mw.col.decks.get(mw.reviewer.card.did)['name']
4987 # in case there are no new or due cards, queue == 1 or 3 only (learning or day-learning)
4988 self.subdeck_name = self.subdeck_name.replace('::',' | ')
4989 #showInfo(old_subdeck_name + ' $$ ' + self.subdeck_name)
4990 #if old_subdeck_name == self.subdeck_name:
4991 # return
4992
4993 #showWarning(self.deck_name + ' ;; ' + self.subdeck_name)
4994 # в колодах от Ðнки 1.2 Ð¸Ð¼Ñ Ð¿Ð¾Ð´ÐºÐ¾Ð»Ð¾Ð´Ñ‹ не приходит!!!
4995
4996 tags_list = ''
4997 for next_tag in mw.reviewer.card.note().tags:
4998 tags_list += ' %s'%next_tag
4999 tags_list = tags_list.strip()
5000 if len(tags_list):
5001 tags_list = ' { '+tags_list+' } '
5002
5003 #if self.subdeck_name == self.deck_name:
5004 # self.overview_title()
5005 # return
5006 sd = self.subdeck_name[(len(self.deck_name) + 2):]
5007 tmp = unicode(_("Cloze") if mw.reviewer.card.model()['type'] == MODEL_CLOZE else _("Basic"))
5008 temp = unicode(mw.reviewer.card.model()['name'])
5009 tempo = (tmp +' | '+ temp) if tmp!=temp else tmp
5010 tmpl = unicode(mw.reviewer.card.template()['name'])
5011 mess = '' +\
5012 unicode(self.profile_string) +\
5013 unicode(title_separator) +\
5014 unicode( subdeck_format.format(\
5015 parent=self.deck_name,\
5016 child=sd) if len(sd) else unicode(self.get_deck_name()) ) +\
5017 ' ('+ tempo +') ' +\
5018 (tmpl if not (tmpl == tempo or tmpl == temp or tmpl == tmp) else '') +\
5019 tags_list
5020 mw.setWindowTitle(mess)
5021
5022if A['DECK_NAME_IN_TITLE'][0]:
5023 deck_namer = DeckNamer()
5024
5025 mw.deckBrowser.show = wrap(mw.deckBrowser.show, deck_namer.deck_browser_title)
5026 mw.overview.show = wrap(mw.overview.show, deck_namer.overview_title)
5027 #mw.reviewer.show = wrap(mw.reviewer.show, deck_namer.card_title)
5028 addHook("showQuestion", deck_namer.card_title)
5029
5030 #if show_subdeck:
5031 # addHook('showQuestion', deck_namer.card_title)
5032
5033#################################################################################################
5034
5035# See github page to report issues or to contribute:
5036# https://github.com/Arthaey/anki-rebuild-all
5037
5038def rebuildAllDecks():
5039 dynDeckIds = [ d["id"] for d in mw.col.decks.all() if d["dyn"] ]
5040 mw.checkpoint("rebuild {0} decks".format(len(dynDeckIds)))
5041 mw.progress.start()
5042 [ mw.col.sched.rebuildDyn(did) for did in dynDeckIds ]
5043 mw.progress.finish()
5044 #showInfo('<i>Rebuild All Filtered Decks</i> done.')
5045 mw.reset()
5046
5047def _addButton(self):
5048 # There's no clean way to add a button, so hack it in. :(
5049 button = "<button "+(" title=' Rebuild All '" if B['B11_BUTTON_TITLES'][0] else "")+" onclick='py.link(\"rebuild\");'>" + _(u'Rebuild All') + "</button>"
5050 html = self.bottom.web.page().mainFrame().toHtml()
5051 html = re.sub("</button></td>", u"</button>{0}</td>".format(button), html)
5052 self.bottom.draw(html)
5053
5054if A['REBUILD_THEM_ALL'][0]:
5055 rebuild_all_action = QAction(mw)
5056 rebuild_all_action.setText("ПереÑтроить &ВСЕ фильтр-колоды" if lang == 'ru' else "&Rebuild All Filtered Decks")
5057 rebuild_all_action.setShortcut(QKeySequence(HOTKEY['Rebuild_Them_All'][0]))
5058 if A['ANKI_MENU_ICONS'][0]:
5059 rebuild_all_action.setIcon(QIcon(os.path.join(MUSTHAVE_COLOR_ICONS, 'rebuild_all.png')))
5060 mw.connect(rebuild_all_action, SIGNAL("triggered()"), rebuildAllDecks)
5061 #mw.form.menuTools.addAction(rebuild_all_action)
5062 mw.form.menuTools.insertAction(mw.form.actionCreateFiltered, rebuild_all_action)
5063
5064#################################################################################################
5065
5066# Copyright 2013 Thomas TEMPE <thomas.tempe@alysse.org>
5067# strikethrough.py
5068# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
5069
5070from aqt.editor import Editor
5071
5072def toggleStrike(self):
5073 self.web.eval("setFormat('strikethrough');")
5074
5075# Power Create lists ordered unordered and indented
5076# Written by Daniel Mankarios in 2013 <daniel.mankarios@uqconnect.edu.au>
5077# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
5078
5079def toggleOList(self):
5080 self.web.eval("document.execCommand('insertOrderedList', false, %s);"
5081 % json.dumps(self.web.selectedText()))
5082
5083def toggleUList(self):
5084 self.web.eval("document.execCommand('insertUnorderedList', false, %s);"
5085 % json.dumps(self.web.selectedText()))
5086
5087def indentList(self):
5088 self.web.eval("document.execCommand('indent', false, %s);"
5089 % json.dumps(self.web.selectedText()))
5090
5091def myButtons(self):
5092 self._addButton("mybutton", lambda s=self: toggleStrike(self),
5093 text=u"s\u0336", tip="Зачёркивание ("+HOTKEYS['strike'][0]+")" if lang == 'ru' else "Strikethrough ("+HOTKEYS['strike'][0]+")", key=HOTKEYS['strike'][0])
5094
5095 self._addButton(
5096 "OlistButton", lambda s=self: toggleOList(self),
5097 text=u"O", tip="УпорÑдоченный ÑпиÑок ("+HOTKEYS['ordered'][0]+")" if lang == 'ru' else "Ordered List ("+HOTKEYS['ordered'][0]+")", key=HOTKEYS['ordered'][0])
5098
5099 self._addButton(
5100 "UlistButton", lambda s=self: toggleUList(self),
5101 text=u"U", tip="ÐеупорÑдоченный ÑпиÑок ("+HOTKEYS['unordered'][0]+")" if lang == 'ru' else "Unordered List ("+HOTKEYS['unordered'][0]+")", key=HOTKEYS['unordered'][0])
5102
5103 self._addButton(
5104 "indentButton", lambda s=self: indentList(self),
5105 text=u"In", tip="ОтÑтуп ÑпиÑка ("+HOTKEYS['indent'][0]+")" if lang == 'ru' else "Indent List ("+HOTKEYS['indent'][0]+")", key=HOTKEYS['indent'][0])
5106
5107if A['POWER_CREATE_LISTS'][0]:
5108 Editor.toggleStrike = toggleStrike
5109
5110 Editor.toggleOList = toggleOList
5111 Editor.toggleUList = toggleUList
5112 Editor.indentList = indentList
5113
5114 Editor.setupButtons = wrap(Editor.setupButtons, myButtons)
5115
5116# • Alternative hotkeys to cloze selected text in Add or Editor window
5117# https://ankiweb.net/shared/info/2074653746
5118# _Alternative_hotkeys_to_cloze_selected_text_in_Add_or_Editor_window.py
5119
5120akita_CtrlSpace = HOTKEYS["nextCloze"][0] #"Ctrl+Space" # Ctrl+Shift+C
5121akita_CtrlAltSpace = HOTKEYS["sameCloze"][0] #"Ctrl+Alt+Space" # Ctrl+Alt+Shift+C
5122akita_CtrlShiftX = HOTKEYS["showHTML"][0] #"F4" # Ctrl+Shift+X
5123
5124#from aqt.editor import *
5125
5126def akita_setupButtonz(self):
5127 s = QShortcut(
5128 QKeySequence(akita_CtrlSpace), self.parentWindow)
5129 s.connect(s, SIGNAL("activated()"), self.onCloze)
5130
5131 s = QShortcut(
5132 QKeySequence(akita_CtrlAltSpace), self.parentWindow)
5133 s.connect(s, SIGNAL("activated()"), self.onCloze)
5134
5135 s = QShortcut(
5136 QKeySequence(akita_CtrlShiftX), self.widget)
5137 s.connect(s, SIGNAL("activated()"), self.onHtmlEdit)
5138
5139if A['CLOZE_EDITOR_HOTKEYS'][0]:
5140 aqt.editor.Editor.setupButtons = wrap(aqt.editor.Editor.setupButtons, akita_setupButtonz)
5141
5142#################################################################################################
5143# Customizable Congratulations Message
5144# https://ankiweb.net/shared/info/856960187
5145#
5146# This plugin is provided as-is without any additional support.
5147#
5148# To customize, edit the CUSTOM_CONGRAT_MSG line in Custom_Congrats_Message.py with html.
5149# This can be done by Tools->Add-ons->Customizable_Congratulations_Message->Edit
5150# Umlaute
5151# Hey - cool gimmick!
5152# would be nice, if it would support Umlaute äöü etc.
5153# thx. cheers.
5154# How to use Unicode characters
5155# Does what it says.
5156# To the reviewer who said they wanted to use umlauts (or any other non-Latin characters),
5157# you can do this as well, you just have to make two changes:
5158# 1. At the very top of the file, add the line
5159# #-*- coding: utf-8 -*-
5160# 2. In the line with the string to modify, add a letter 'u' in front of the opening quotation mark, like so:
5161# CUSTOM_CONGRAT_MSG = u"This is my ĉöngrátulatiǒns message"
5162from anki.sched import Scheduler
5163
5164def customMsg(self):
5165 return ( custom_msg + "<br><br>" + self._nextDueMsg())
5166
5167if A['CUSTOM_CONGRAT_MSG'][0]:
5168 for lbls in CUSTOM_CONGRAT_MSG:
5169 if lbls[0] == lang:
5170 custom_msg = lbls[1]
5171 Scheduler.finishedMsg = customMsg
5172
5173######################################################################################################
5174# CHK_00_MORE_OVERVIEW_STATS.py
5175# Author: Calumks <calumks@gmail.com>
5176# 2013-01-02 The card count does not include the cards in subdecks. -- 02/01/2013 Fixed card count bug
5177
5178# Get Overview class
5179from aqt.overview import Overview
5180
5181# Replace _table method
5182def table(self):
5183 cardsUnseen = self.mw.col.db.first("""
5184select
5185sum(case when queue=0 then 1 else 0 end) -- new
5186from cards where did in %s""" % self.mw.col.sched._deckLimit())
5187
5188 cardsSuspended = self.mw.col.db.first("""
5189select
5190sum(case when queue = -1 then 1 else 0 end) -- new
5191from cards where did in %s""" % self.mw.col.sched._deckLimit())
5192
5193 cardsBuried = self.mw.col.db.first("""
5194select
5195sum(case when queue = -2 then 1 else 0 end) -- new
5196from cards where did in %s""" % self.mw.col.sched._deckLimit())
5197
5198 cardsTotal = self.mw.col.db.first("""
5199select count(id) from cards
5200where did in %s """ % self.mw.col.sched._deckLimit())
5201
5202 cardsTotalReviews = self.mw.col.db.scalar("""
5203select count() from cards where did in %s and queue > 0
5204and due < ?""" % self.mw.col.sched._deckLimit(), self.mw.col.sched.today+1)
5205
5206 counts = list(self.mw.col.sched.counts())
5207 finished = not sum(counts)
5208 for n in range(len(counts)):
5209 if B['B04_HIDE_BIG_NUMBERS'][0] and counts[n] > B['B04_HIDE_BIG_NUMBER'][0]: # counts[n] == 1000:
5210 counts[n] = "%s+" % (B['B04_HIDE_BIG_NUMBER'][0]) # "1000+"
5211 but = self.mw.button
5212 if finished:
5213 return '<div style="white-space: pre-wrap;">%s</div>' % (
5214 self.mw.col.sched.finishedMsg())
5215 else:
5216 line1 = '''
5217 <table width=300 cellpadding=5>
5218 <tr><td align=center valign=top>
5219 <table cellspacing=5>'''
5220 if B['B00_MORE_OVERVIEW_STATS'][0] == 1:
5221 line2 = "" # "<tr><td></td><td></td></tr>"
5222 #line2 = '''<tr><td>%s:</td><td align=right style="font-weight:bold;color:silver;white-space:nowrap;"><font color=#33f>%s</font> + <font color=#c33>%s</font> + <font color=#090>%s</font></td></tr>''' % ( _('Today'), counts[0], counts[1], counts[2] ) # _("Due today")
5223 else:
5224 line2 = '''
5225 <tr><td style="white-space:nowrap;">%s:</td><td align=right style="color:#33f;font-weight:bold;"> %s</td></tr>
5226 <tr><td style="white-space:nowrap;">%s:</td><td align=right style="color:#c33;font-weight:bold;"> %s</td></tr>
5227 <tr><td style="white-space:nowrap;">%s:</td><td align=right style="color:#090;font-weight:bold;"> %s</td></tr>''' % (
5228 _('New'), counts[0],
5229 _('Learning'), counts[1],
5230 _('To Review'), counts[2] )
5231 if (B['B00_MORE_OVERVIEW_STATS'][0] == 1):
5232 line3 = ""
5233 else:
5234 line3 = '''
5235 <tr><td colspan=2><hr></td></tr>
5236 <tr><td style="white-space:nowrap;">%s:</td><td align=right style="color:#DA70D6;"> %s</td></tr>
5237 <tr><td style="white-space:nowrap;">%s:</td><td align=right style="color:#c90;"> %s</td></tr>
5238 <tr><td style="white-space:nowrap;">%s:</td><td align=right style="color:#960;"> %s</td></tr>
5239 <tr><td colspan=2><hr></td></tr>
5240 <tr><td style="white-space:nowrap;">%s:</td><th align=right> %s</font></th></tr>''' % (
5241 _("Unseen"), cardsUnseen[0] if cardsUnseen[0] <= B['B04_HIDE_BIG_NUMBER'][0] \
5242 or ((cardsUnseen[0] > B['B04_HIDE_BIG_NUMBER'][0]) and not B['B04_HIDE_BIG_NUMBERS'][0]) else "%s+" % (B['B04_HIDE_BIG_NUMBER'][0]),
5243 _("Suspended"), cardsSuspended[0] if cardsSuspended[0] <= B['B04_HIDE_BIG_NUMBER'][0] \
5244 or ((cardsSuspended[0] > B['B04_HIDE_BIG_NUMBER'][0]) and not B['B04_HIDE_BIG_NUMBERS'][0]) else "%s+" % (B['B04_HIDE_BIG_NUMBER'][0]),
5245 _Buried, cardsBuried[0] if cardsBuried[0] <= B['B04_HIDE_BIG_NUMBER'][0] \
5246 or ((cardsBuried[0] > B['B04_HIDE_BIG_NUMBER'][0]) and not B['B04_HIDE_BIG_NUMBERS'][0]) else "%s+" % (B['B04_HIDE_BIG_NUMBER'][0]),
5247 _("Total cards"), cardsTotal[0] if cardsTotal[0] <= B['B04_HIDE_BIG_NUMBER'][0] \
5248 or ((cardsTotal[0] > B['B04_HIDE_BIG_NUMBER'][0]) and not B['B04_HIDE_BIG_NUMBERS'][0]) else "%s+" % (B['B04_HIDE_BIG_NUMBER'][0]) )
5249 line3 += ''' </table>
5250 </td><td align=center>%s</td></tr></table>''' % ( but("study", _("Study Now"), id="study") )
5251 return line1 + line2 + line3
5252
5253# Total reviews == Due
5254
5255original_table = Overview._table
5256
5257if B['B00_MORE_OVERVIEW_STATS'][0] > 0 and B['B00_MORE_OVERVIEW_STATS'][0] < 3:
5258 Overview._table = table
5259
5260#######################################################################
5261# https://ankiweb.net/shared/info/531984586
5262# More Overview Stats 2
5263# This is god-tier.
5264
5265"""
5266
5267Statistics add-on for Anki v1.9.
5268It's based on the nice "More Overview Stats" add-on by Calumks but shows more (and different) values.
5269You can find a description of hidden options and the change log at the beginning of the source code.
5270For bugs or comments, feel free to contact me.
5271
5272Note: this add-on may cause the overview page to display more slowly on large decks and/or slower computers.
5273
5274More Overview Stats 2
5275=====================
5276Statistics add-on for Anki -- based on "More Overview Stats" by
5277Calumks <calumks@gmail.com>
5278
5279Copyright (c) 2014 Martin Zuther (http://www.mzuther.de/)
5280
5281This program is free software: you can redistribute it and/or modify
5282it under the terms of the GNU General Public License as published by
5283the Free Software Foundation, either version 3 of the License, or
5284(at your option) any later version.
5285
5286This program is distributed in the hope that it will be useful,
5287but WITHOUT ANY WARRANTY; without even the implied warranty of
5288MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
5289GNU General Public License for more details.
5290
5291You should have received a copy of the GNU General Public License
5292along with this program. If not, see <http://www.gnu.org/licenses/>.
5293
5294Thank you for using free software!
5295
5296USAGE
5297=====
5298This statistics add-on for Anki is based on the nice "More Overview
5299Stats" add-on by Calumks but shows more (and different) values.
5300
5301To change the behaviour of this add-on, create a JSON file called
5302'More_Overview_Stats_2.json' in your profile folder. When you edit
5303this file, please make sure you save the JSON file using a compatible
5304encoding such as UTF-8.
5305
5306In case you don't like to see the stats on finished decks, copy the
5307following lines to your JSON file:
5308
5309 {
5310 "options": {
5311 "show_table_for_finished_decks": false
5312 }
5313 }
5314
5315There is another (advanced) option: I use Anki to learn vocabulary,
5316and some of my decks have one card per note, while others have two
5317cards per note. To watch my progress, I want to know the (somewhat
5318virtual) number of *notes* instead of the cards.
5319
5320To achieve this, edit the JSON file along these lines:
5321
5322 {
5323 "options": {
5324 "show_table_for_finished_decks": true
5325 },
5326 "note_correction_factors": {
5327 "Spanisch": 2,
5328 "Türkisch": 2,
5329 "Türkisch::Word Pool": 1
5330 }
5331 }
5332
5333If everything works fine, this add-on will compare the current deck
5334name with the entries and use the FACTOR given for the longest match.
5335
5336For the example above, all Spanish and Turkish decks will divide the
5337card numbers by 2, while the subdeck "Türkisch::Word Pool" will show
5338the original card numbers. Please note that the card numbers for new,
5339learning and review will NOT be modified so that you can still monitor
5340your learning progress.
5341
5342Enjoy!
5343
5344HISTORY
5345=======
5346version 1.9:
5347* now works correctly with empty decks (thanks to Jonte!)
5348version 1.8:
5349* changed displayed values (unseen / suspended)
5350* format display using CSS
5351version 1.7:
5352* move hidden variable from source code to JSON file
5353version 1.6:
5354* correct note numbers by deck-specific factors
5355version 1.5:
5356* hide new/learning/review block when deck is finished
5357version 1.4:
5358* changed displayed values (learned)
5359version 1.3:
5360* support Unicode in localisations
5361version 1.2:
5362* align table regardless of font
5363version 1.1:
5364* show table even if deck is finished
5365version 1.0:
5366* initial commit
5367"""
5368
5369# from datetime import date, datetime, time, timedelta
5370
5371def dd4():
5372 if mw.col.crt:
5373 startHour = datetime.fromtimestamp(mw.col.crt).hour
5374 else:
5375 startHour = 4
5376 d0 = datetime.now()
5377 d4 = datetime(d0.year, d0.month, d0.day, startHour, 0, 0)
5378 d4 += timedelta(days=(1 if d0.hour >= startHour else 0))
5379 return (d4 - datetime(1970,1,1)).total_seconds()
5380
5381def overview_table(self):
5382 json_file = os.path.join(self.mw.pm.profileFolder(),
5383 'More_Overview_Stats_2.json')
5384
5385 if os.path.isfile(json_file):
5386 with open(json_file, mode='r') as f:
5387 settings = json.load(f)
5388 else:
5389 settings = {}
5390
5391 current_deck_name = self.mw.col.decks.current()['name']
5392
5393 correction_for_notes = 1
5394 last_match_length = 0
5395
5396 if 'note_correction_factors' in settings:
5397 for fragment, FACTOR in settings['note_correction_factors'].items():
5398 if current_deck_name.startswith(fragment):
5399 if len(fragment) > last_match_length:
5400 correction_for_notes = int(FACTOR)
5401 last_match_length = len(fragment)
5402
5403 # prevent division by zero and negative results
5404 if correction_for_notes <= 0:
5405 correction_for_notes = 1
5406
5407 total, mature, young, unseen, suspended, buried, lrn, lrn1, lrn4, due = self.mw.col.db.first(
5408 '''
5409 select
5410 -- total
5411 count(id),
5412 -- mature
5413 sum(case when queue = 2 and ivl >= 21
5414 then 1 else 0 end),
5415 -- young / learning
5416 sum(case when queue in (1, 3) or (queue = 2 and ivl < 21)
5417 then 1 else 0 end),
5418 -- unseen
5419 sum(case when queue = 0
5420 then 1 else 0 end),
5421 -- suspended
5422 sum(case when queue = -1
5423 then 1 else 0 end),
5424 -- buried
5425 sum(case when queue = -2
5426 then 1 else 0 end),
5427 -- lrn
5428 sum(case when (queue = 1 or queue = 3) and due <= {1}
5429 then round(left / 1000) else 0 end),
5430 -- lrn1 # mw.col.conf['collapseTime'] mins * 60 secs
5431 sum(case when (queue = 1 or queue = 3) and due <= {1}
5432 then 1 else 0 end),
5433 -- lrn4
5434 sum(case when (queue = 1 or queue = 3) and due <= {3}
5435 then 1 else 0 end),
5436 -- due
5437 sum(case when queue = 2 and due < {2}
5438 then 1 else 0 end)
5439 from cards where did in {0:s}
5440 '''.format( self.mw.col.sched._deckLimit(), round(time.time() + mw.col.conf['collapseTime']*60), \
5441 self.mw.col.sched.today+1, round(dd4() + mw.col.conf['collapseTime']*60) ) )
5442
5443 if not total:
5444 return u'<p>No cards found.</p>'
5445
5446 scheduled_counts = list(self.mw.col.sched.counts())
5447 deck_is_finished = not sum(scheduled_counts)
5448
5449 cards = {}
5450
5451 cards['mature'] = mature / int(correction_for_notes)
5452 cards['young'] = young / int(correction_for_notes)
5453 cards['unseen'] = unseen / int(correction_for_notes)
5454 cards['suspended'] = suspended / int(correction_for_notes)
5455 cards['buried'] = buried / int(correction_for_notes)
5456
5457 cards['total'] = total / int(correction_for_notes)
5458 cards['learned'] = cards['mature'] + cards['young']
5459
5460 cards['new'] = scheduled_counts[0]
5461 cards['learn'] = lrn
5462 cards['lrn1'] = lrn1 # str(lrn1)
5463 cards['learning'] = scheduled_counts[1]
5464 cards['lrn4'] = lrn4 # str(lrn4)
5465 cards['review'] = scheduled_counts[2]
5466 cards['due'] = cards['learn'] + cards['review'] # due
5467
5468 cards_percent = {}
5469
5470 cards_percent['mature'] = cards['mature'] * 1.0 / cards['total']
5471 cards_percent['young'] = cards['young'] * 1.0 / cards['total']
5472 cards_percent['unseen'] = cards['unseen'] * 1.0 / cards['total']
5473 cards_percent['suspended'] = cards['suspended'] * 1.0 / cards['total']
5474 cards_percent['buried'] = cards['buried'] * 1.0 / cards['total']
5475
5476 cards_percent['total'] = 1.0
5477 cards_percent['learned'] = cards['learned'] * 1.0 / cards['total']
5478
5479 cards_percent['new'] = cards['new'] * 1.0 / cards['total']
5480 cards_percent['learning'] = cards['learning'] * 1.0 / cards['total']
5481 cards_percent['learn'] = cards['learn'] * 1.0 / cards['total']
5482 cards_percent['review'] = cards['review'] * 1.0 / cards['total']
5483 cards_percent['due'] = cards['due'] * 1.0 / cards['total']
5484
5485 for card in cards:
5486 cards[card] = int(cards[card])
5487 cardz={}
5488
5489 # percents are calculated from correct values
5490 for key, value in cards.iteritems(): # for Python2 only, Python3 does it another way.
5491 #if B['B04_HIDE_BIG_NUMBERS'][0] and value > B['B04_HIDE_BIG_NUMBER'][0]:
5492 # cards[key] = str(B['B04_HIDE_BIG_NUMBER'][0])+"+"
5493 #else:
5494 # cards[key] = str(value)
5495 cardz[key] = nonzeroColour(cards[key],"default",False)
5496
5497 labels = {}
5498
5499 labels['new'] = _('New')
5500 labels['learning'] = _('Learning') # + ' ' + str(round(dd4()))
5501 labels['learn'] = _('Learn') # + ' ' + str(round(time.time()))
5502 labels['review'] = _('To Review')
5503 labels['due'] = _('Due')
5504
5505 labels['unseen'] = _('Unseen')
5506 labels['suspended'] = _('Suspended')
5507 labels['buried'] = _Buried
5508
5509 labels['mature'] = _('Mature')
5510 labels['young'] = _('Young')
5511 labels['learned'] = _('Mature') + ' + ' + _('Young') # _('Learned')
5512
5513 labels['total'] = _('Total')
5514
5515 for key in labels:
5516 labels[key] = u'{:s}:'.format(labels[key])
5517
5518 button = self.mw.button
5519
5520 output_table = '''
5521 <style type="text/css">
5522 hr {
5523 height: 1px;
5524 border: none;
5525 border-top: 1px solid #aaa;
5526 }
5527 td {
5528 vertical-align: top;
5529 }
5530 td.row1, td.hr {
5531 text-align: left;
5532 padding-left: 1.2em;
5533 }
5534 td.row2 {
5535 text-align: right;
5536 padding-left: 1.2em;
5537 padding-right: .2em;
5538 }
5539 td.row3, td.row4 {
5540 text-align: right;
5541 }
5542 td.row4, td.hr {
5543 padding-right:1.2em;
5544 }
5545 td.new {
5546 font-weight: bold;
5547 color: #33f; /* #00a; */
5548 }
5549 td.learning {
5550 color: #c33; /* #a00; */
5551 }
5552 td.learn {
5553 font-weight: bold;
5554 color: #c33; /* #a00; */
5555 }
5556 td.review {
5557 font-weight: bold;
5558 color: #090; /* #080; */
5559 }
5560 td.percent {
5561 font-weight: normal;
5562 color: #888;
5563 }
5564 td.mature {
5565 font-weight: normal;
5566 color: #080;
5567 }
5568 td.young {
5569 font-weight: normal;
5570 color: #3c3;
5571 }
5572 td.learned {
5573 font-weight: normal;
5574 color: #0a0;
5575 }
5576 td.due {
5577 font-weight: normal;
5578 color: #999;
5579 }
5580 td.unseen {
5581 font-weight: normal;
5582 color: #DA70D6; /* #a00; */
5583 }
5584 td.suspended {
5585 font-weight: normal;
5586 color: #c90; /* #a70; */
5587 }
5588 td.buried {
5589 font-weight: normal;
5590 color: #960; /* #a70; */
5591 }
5592 td.total {
5593 font-weight: bold;
5594 color: default; /* #000; */
5595 }
5596 .G { background-color: #e6e6e6; }
5597 .night .G { background-color: #444; }
5598 table {
5599 border-spacing: 0px;
5600 }
5601 </style>
5602 '''
5603
5604 if cards['learning']==cards['learn']:
5605 output_table += ''' <style> .L { display: none; } </style> '''
5606
5607 output_table += '''
5608 <table cellspacing="2">
5609 '''
5610
5611 if not deck_is_finished:
5612 output_table += '''
5613 <tr>
5614 <td class="row1">{label[new]:s}</td>
5615 <td class="row2 new">{cards[new]:s}</td>
5616 <td></td>
5617 <td class="row4 percent">{percent[new]:.0%}</td>
5618 </tr>
5619 <tr class="L">
5620 <td class="row1">{label[learning]:s}</td>
5621 <td class="row2 learning">{cards[learning]:s}</td>
5622 <td class="learning" style="text-align:left;"><sub>{cards[lrn4]}</sub></td>
5623 <td class="row4 percent">{percent[learning]:.0%}</td>
5624 </tr>
5625 <tr class="G">
5626 <td class="row1" style="color:#999;">{label[learn]:s}</td>
5627 <td class="row2 learn">{cards[learn]:s}</td>
5628 <td class="learn" style="text-align:left;"><sub>{cards[lrn1]}</sub></td>
5629 <td class="row4 percent">{percent[learn]:.0%}</td>
5630 </tr>
5631 <tr>
5632 <td class="row1">{label[review]:s}</td>
5633 <td class="row2 review">{cards[review]:s}</td>
5634 <td></td>
5635 <td class="row4 percent">{percent[review]:.0%}</td>
5636 </tr>
5637 <tr>
5638 <td class="row1">{label[due]:s}</td>
5639 <td class="row2 due">{cards[due]:s}</td>
5640 <td></td>
5641 <td class="row4 percent">{percent[due]:.0%}</td>
5642 </tr>
5643 '''.format(label=labels,
5644 cards=cardz,
5645 percent=cards_percent)
5646
5647 if (cards['unseen'] + cards['suspended'] + cards['buried']):
5648 output_table += '''
5649 <tr>
5650 <td colspan="4" class="hr"><hr /></td>
5651 </tr>
5652 <tr>
5653 <td class="row1">{label[unseen]:s}</td>
5654 <td class="row2 unseen">{cards[unseen]:s}</td>
5655 <td></td>
5656 <td class="row4 percent">{percent[unseen]:.0%}</td>
5657 </tr>
5658 <tr>
5659 <td class="row1">{label[suspended]:s}</td>
5660 <td class="row2 suspended">{cards[suspended]:s}</td>
5661 <td></td>
5662 <td class="row4 percent">{percent[suspended]:.0%}</td>
5663 </tr>
5664 <tr>
5665 <td class="row1">{label[buried]:s}</td>
5666 <td class="row2 buried">{cards[buried]:s}</td>
5667 <td></td>
5668 <td class="row4 percent">{percent[buried]:.0%}</td>
5669 </tr>
5670 '''.format(label=labels,
5671 cards=cardz,
5672 percent=cards_percent)
5673
5674 output_table += '''
5675 <tr>
5676 <td colspan="4" class="hr"><hr /></td>
5677 </tr>
5678 <tr>
5679 <td class="row1">{label[mature]:s}</td>
5680 <td class="row2 mature">{cards[mature]:s}</td>
5681 <td></td>
5682 <td class="row4 percent">{percent[mature]:.0%}</td>
5683 </tr>
5684 <tr>
5685 <td class="row1">{label[young]:s}</td>
5686 <td class="row2 young">{cards[young]:s}</td>
5687 <td></td>
5688 <td class="row4 percent">{percent[young]:.0%}</td>
5689 </tr>
5690 <tr>
5691 <td class="row1">{label[learned]:s}</td>
5692 <td class="row2 learned">{cards[learned]:s}</td>
5693 <td></td>
5694 <td class="row4 percent">{percent[learned]:.0%}</td>
5695 </tr>
5696 <tr>
5697 <td colspan="4" class="hr"><hr /></td>
5698 </tr>
5699 <tr>
5700 <td class="row1">{label[total]:s}</td>
5701 <td class="row2 total">{cards[total]:s}</td>
5702 <td></td>
5703 <td class="row4 percent">{percent[total]:.0%}</td>
5704 </tr>
5705 '''.format(label=labels,
5706 cards=cardz,
5707 percent=cards_percent)
5708
5709 output = ''
5710
5711 if deck_is_finished:
5712 if (not 'options' in settings) or (settings['options'].get(
5713 'show_table_for_finished_decks', True)):
5714 output += output_table
5715 output += '''
5716 </table>
5717 <hr style="margin: 1.5em 0; border-top: 1px dotted #888;" />
5718 '''
5719
5720 output += '''
5721 <div style="white-space: pre-wrap;">{:s}</div>
5722 '''.format(self.mw.col.sched.finishedMsg())
5723 else:
5724 suma = cards['new']+cards['learn']+cards['review']
5725 summa = cards['new']+cards['learning']+cards['review']
5726 #showWarning(str(suma)+' '+unicode(summa))
5727 #output += (output_table if suma==0 else "") + '''
5728 if suma>0:
5729 output += '''
5730 <tr>
5731 <td colspan="4" style="text-align: center; padding-top: 0.6em;">{button:s}</td>
5732 </tr>
5733 '''.format(button=(button('study', _('Study Now'), id='study') if summa>0 else button('decks', _('Decks'), id='study')))
5734 # id='study' to get focus
5735 if summa > 0:
5736 output += (output_table if summa > 0 else "") + '''
5737 <tr>
5738 <td colspan="4" style="text-align: center; padding-top: 0.6em;">{button:s}</td>
5739 </tr>
5740 '''.format(button=(button('decks', '<small>'+_('Decks')+'</small>', id='study')))
5741 output += "</table>"
5742
5743 return output
5744
5745# replace _table method
5746if B['B00_MORE_OVERVIEW_STATS'][0] == 3:
5747 Overview._table = overview_table
5748
5749def overview_init():
5750 if B['B00_MORE_OVERVIEW_STATS'][0] > 0 and B['B00_MORE_OVERVIEW_STATS'][0] < 3:
5751 Overview._table = table
5752 elif B['B00_MORE_OVERVIEW_STATS'][0] == 0:
5753 Overview._table = original_table
5754 else:
5755 Overview._table = overview_table
5756
5757# --
5758
5759#self.reviewer._css =
5760'''
5761hr { color: maroon; background-color:#def; margin: .5em 2.5em; }
5762body { margin:1.5em; padding: 2em; border: solid 1px lime; }
5763img { max-width: 95%; max-height: 95%; }
5764.marked { position:fixed; right: 7px; top: 7px; display: none; }
5765#typeans { width: 100%; }
5766.typeGood { background: #0f0; }
5767.typeBad { background: #f00; }
5768.typeMissed { background: #ccc; }'''
5769
5770# --
5771
5772#######################################################################
5773# https://ankiweb.net/shared/info/248074683
5774# Removes Empty Note Types
5775# After some time you pile up a lot of note types that never get deleted?
5776# This plugin takes care of it! After installing,
5777# click on Tools -> Remove Empty Note Types.
5778# This might take a few seconds.
5779
5780##########################################################################
5781# https://ankiweb.net/shared/info/3867500866
5782# Deleting Reduant Configurations -- Redundant, BTW
5783# Delete all configurations inside the collections,
5784# except the one used by decks and the original one.
5785# Written by Sara Jakša sarajaksa@gmail.com
5786
5787if A['REMOVES_EMPTY'][0]:
5788 from aqt.deckconf import DeckConf
5789
5790 class ConfigurationDeletions():
5791
5792 def __init__(self):
5793 self.mw = mw
5794 self.allDecks = mw.col.decks.all()
5795 self.allConf = mw.col.decks.allConf()
5796
5797 def countGroup(self, conf4Del):
5798 counter = 0
5799 for conf in conf4Del:
5800 conf = self.findConfigurationFromId(conf)
5801 if not conf['id'] == 1:
5802 counter += 1
5803 return counter
5804
5805 #Odstrani configuracije
5806 def remGroup(self, conf4Del):
5807 counter = 0
5808 #deck = self.findDeckFromConfiguration(self.findConfigurationFromId(1))
5809 #configuration = DeckConf(mw, deck)
5810 for conf in conf4Del:
5811 conf = self.findConfigurationFromId(conf)
5812 if not conf['id'] == 1:
5813 counter += 1
5814 mw.col.decks.remConf(conf['id'])
5815 #configuration.loadConfs()
5816 return counter
5817
5818 def findDeckFromConfiguration(self, conf):
5819 for deck in self.allDecks:
5820 if deck[u'conf'] == conf:
5821 return deck
5822
5823 #Finds the ID of all configurations
5824 def findIdConfigurations(self):
5825 confId = set()
5826 for conf in self.allConf:
5827 confId.add(conf[u'id'])
5828 return confId
5829
5830 #Find if it is a filter deck
5831 def filterDeck(self, deck):
5832 if deck[u'dyn']:
5833 return True
5834 return False
5835
5836 #Finds all the decks and their's configurations
5837 def findDeckIdConfiguration(self):
5838 deckConfId = set()
5839 for deck in self.allDecks:
5840 if self.filterDeck(deck):
5841 return None
5842 deckConfId.add(deck['conf'])
5843 return deckConfId
5844
5845 #finds all configuration, with no deck
5846 def ophranConfigurations(self, confId, deckId):
5847 conf4Del = []
5848 for ID in confId:
5849 if ID not in deckId:
5850 conf4Del.append(ID)
5851 return conf4Del
5852
5853 #From confId find configuration
5854 def findConfigurationFromId(self, Id):
5855 for conf in self.allConf:
5856 if conf['id'] == Id:
5857 return conf
5858
5859 def remDupes():
5860 counter = 0
5861 mw.window().checkpoint(_("Delete Empty Note Types"))
5862 for model in mw.col.models.all():
5863 if mw.col.models.useCount(model) == 0:
5864 if len(mw.col.models.all())>1:
5865 counter += 1
5866 mw.col.models.rem(model)
5867 #mw.window().requireReset()
5868 return counter
5869
5870 def countDupes():
5871 counter = 0
5872 for model in mw.col.models.all():
5873 if mw.col.models.useCount(model) == 0:
5874 counter += 1
5875 return counter
5876
5877 def confStart():
5878 deletion = ConfigurationDeletions()
5879 cntDel = deletion.countGroup(deletion.ophranConfigurations(deletion.findIdConfigurations(), deletion.findDeckIdConfiguration()))
5880 cntDup = countDupes()
5881 if (cntDel+cntDup):
5882 if askUser((u" Будут удалены: \n пуÑтые типы запиÑей - {} \n пуÑтые группы наÑтроек колод - {} "
5883 if lang == 'ru' else "Would be deleted \n empty note types - {} \n options groups - {} ").format(cntDup,cntDel), defaultno = True): # true - global name not found
5884
5885 cntDel = deletion.remGroup(deletion.ophranConfigurations(deletion.findIdConfigurations(), deletion.findDeckIdConfiguration()))
5886 cntDup = remDupes()
5887 if cntDup + cntDel:
5888 if lang == 'ru':
5889 tooltip(u" Удалены пуÑтые \n типы запиÑей - %d \n группы наÑтроек колод - %d " % (cntDup, cntDel) )
5890 else: # showInfo showWarning showCritical
5891 tooltip(" %d empty note types deleted.\n %d empty options groups deleted." % (cntDup, cntDel) )
5892 else:
5893 tooltip(u'ПуÑтые типы запиÑей или группы наÑтроек колод не найдены.'
5894 if lang == 'ru' else "There are no empty note types or options groups to delete.")
5895
5896 if mw.onCheckDB():
5897 mw.onCheckMediaDB()
5898 mw.onEmptyCards()
5899
5900if A['REMOVES_EMPTY'][0]:
5901 remove_action = QAction(u"&Удалить пуÑтые типы запиÑей, метки и группы наÑтроек колод" if lang == 'ru' else "&Delete Empty Options Groups, Tags and Notes Types", mw)
5902 if A['ANKI_MENU_ICONS'][0]:
5903 remove_action.setIcon(QIcon(os.path.join(MUSTHAVE_COLOR_ICONS, 'warning.png')))
5904 remove_action.setShortcut(HOTKEY['delete_them_all'][0])
5905 mw.connect(remove_action, SIGNAL("triggered()"), confStart)
5906
5907 #mw.form.menuTools.addAction(remove_action)
5908 mw.form.menuTools.insertAction(mw.form.actionFullDatabaseCheck,remove_action)
5909
5910##########################################################################
5911# Clickable Tags on Reviewer
5912# https://ankiweb.net/shared/info/1321188674
5913# Clickable_Tags_on_Reviewer.py
5914# 28.01.2016
5915
5916"""
5917Clickable_Tags add-on 0.1 alpha release- inspired by Dybamic_Tags add-on
5918The styles used here are from Power format pack add-on
5919Copyright: Abdolmahdi Saravi, 2013 <amsaravi@yahoo.com>
5920License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
5921This add-on Displays the Cards Tag in style of keyboard keys that has the
5922ability to click on them witch opens the anki browser and lists the cards that match clicked tag.
5923You can change the style of tags by editing the code.
5924You can also use {{Tags}} in card template to position the tags on the card. by default if you don't use the
5925{{Tags}} in your Cards the add-on puts the tags before any content of the card. you can change this behavior
5926by setting the variable TAG_IN_ALL_CARDS to False
5927Also you can use this add-on as a global css injector. css styles that is defined here are visible on all note types
5928if you Double Click on a tag within viewer the related Tags within current Deck will be showed
5929Good Luck
5930https://bitbucket.org/amsaravi/ankiaddons
5931"""
5932
5933from aqt import DialogManager # we need it to fix the anki behavior not to deal with minimized state of window
5934from anki.template import Template
5935
5936"""
5937 -webkit-user-select: none !important; /* to prevent selecting text on dblclick */
5938 user-select: none !important;
5939 -khtml-user-select: none !important;
5940 -webkit-touch-callout: none !important; /* all these styles doesn't work in Anki */
5941"""
5942
5943kbd_css = """
5944kbd {
5945 box-shadow: inset 0px 1px 0px 0px #ffffff;
5946 background: -webkit-gradient(linear, left top, left bottom, color-stop(0.05, #f9f9f9), color-stop(1, #e9e9e9) );
5947 background-color: #f9f9f9; color: #333;
5948 border-radius: 4px;
5949 border: 1px solid #dcdcdc;
5950 display: inline-block;
5951 font-size:15px;
5952 height: 15px;
5953 line-height: 15px;
5954 padding:4px 4px 6px;
5955 margin:5px;
5956 text-align: center;
5957 text-shadow: 1px 1px 0px #999;
5958 cursor: pointer; cursor: hand;
5959} #Tags {
5960 position: fixed; bottom: .1em; left: 1em; right: 1em; max-height: 3.3em; overflow: auto;
5961} body {
5962 #padding-bottom: 4em !important;
5963}
5964"""
5965
5966java_script = """
5967<script type="text/javascript">
5968var timer = 0;
5969var delay = 200;
5970var prevent = false;
5971function click_func(tags) {
5972 timer = setTimeout(function() {
5973 if (!prevent) {
5974 py.link(tags);
5975 }
5976 prevent = false;
5977 }, delay);
5978}
5979
5980function dblclick_func(tags_deck) {
5981 clearTimeout(timer);
5982 prevent = true;
5983 py.link(tags_deck);
5984}
5985
5986</script>
5987"""
5988
5989TAG_MARK = "{{info:Tags}}"
5990FRNT_SIDE = "{{FrontSide}}"
5991
5992TAG_IN_ALL_CARDS = False #uncomment and put the fields in your cards manually
5993#TAG_IN_ALL_CARDS = True #uncomment to put tags automatically in the beginning of each card
5994
5995def tagClicklinkHandler(self, url):
5996 browser = None
5997
5998 if url.startswith("tagclick_"): # click
5999 tag = url.split("tagclick_")[-1]
6000 browser = aqt.dialogs.open("Browser", self.mw)
6001 browser.setFilter("tag:\"%s\" " % tag)
6002 elif url.startswith("_tagdbl_"): # Ctrl+click
6003 dec, tag = url.split("_tagdbl_")[1:]
6004 browser = aqt.dialogs.open("Browser", self.mw)
6005 browser.setFilter("tag:\"%s\" card:\"%s\" " % (tag, dec))
6006 elif url.startswith("_tag_"): # Shift+click
6007 dec, tag = url.split("_tag_")[1:]
6008 browser = aqt.dialogs.open("Browser", self.mw)
6009 browser.setFilter("tag:\"%s\" note:\"%s\" " % (tag, dec))
6010 elif url.startswith("_tagctrlshift_"): # Ctrl+Shift+click
6011 dec, tag, nop = url.split("_tagctrlshift_")[1:]
6012 browser = aqt.dialogs.open("Browser", self.mw)
6013 browser.setFilter("tag:\"%s\" card:\"%s\" note:\"%s\" " % (tag, dec, nop))
6014
6015 elif url.startswith("_tagdblclick_"): # context
6016 tag = url.split("_tagdblclick_")[-1]
6017 browser = aqt.dialogs.open("Browser", self.mw)
6018 browser.setFilter("tag:\"%s\" deck:current " % (tag))
6019 #dec, tag = url.split("_tagdblclick_")[1:]
6020 #browser = aqt.dialogs.open("Browser", self.mw)
6021 #browser.setFilter("tag:%s \"deck:%s\"" % (tag, dec))
6022 browser.setWindowState(browser.windowState() & ~Qt.WindowMinimized | Qt.WindowActive)
6023 elif url.startswith("_tagctrl_"): # Ctrl+context
6024 dec, tag = url.split("_tagctrl_")[1:]
6025 browser = aqt.dialogs.open("Browser", self.mw)
6026 browser.setFilter("tag:\"%s\" deck:current card:\"%s\" " % (tag, dec))
6027 elif url.startswith("_tagshift_"): # Shift+context
6028 dec, tag = url.split("_tagshift_")[1:]
6029 browser = aqt.dialogs.open("Browser", self.mw)
6030 browser.setFilter("tag:\"%s\" deck:current note:\"%s\" " % (tag, dec))
6031 elif url.startswith("_tagshiftctrl_"): # Ctrl+Shift+context
6032 dec, tag, nop = url.split("_tagshiftctrl_")[1:]
6033 browser = aqt.dialogs.open("Browser", self.mw)
6034 browser.setFilter("tag:\"%s\" deck:current card:\"%s\" note:\"%s\" " % (tag, dec, nop))
6035 #else:
6036 # oldLinkHandler(reviewer, url)
6037
6038 if browser:
6039 browser.setWindowState(browser.windowState() & ~Qt.WindowMinimized | Qt.WindowActive)
6040
6041 # Play the sound or call the original link handler.
6042 if url.startswith("ankiplay"):
6043 play(url[8:])
6044
6045 if url == "rebuild":
6046 rebuildAllDecks()
6047
6048 if ":" in url:
6049 (cmd, arg) = url.split(":", 1)
6050 else:
6051 cmd = url
6052 arg = ''
6053
6054 if cmd == "study":
6055 my_studyDeck(self, arg)
6056 elif url.startswith("typeans:"):
6057 self.typedAnswers.append(unicode(arg))
6058
6059
6060def new_render(self, template=None, context=None, encoding=None):
6061 template = template or self.template
6062 context = context or self.context
6063 if context is not None:
6064 deck = context['Deck']
6065 card = context['Card']
6066 note = context['Type']
6067 tags = context['Tags'].split()
6068 tagStr = " ".join([
6069 ( """<kbd oncontextmenu="if(!event.ctrlKey){if (!event.shiftKey){py.link('_tagdblclick_%s')}else{py.link('_tagshift_%s_tagshift_%s')}}else{if(event.shiftKey){py.link('_tagshiftctrl_%s_tagshiftctrl_%s_tagshiftctrl_%s')}else{py.link('_tagctrl_%s_tagctrl_%s')}};return false;" """
6070 % (tag, note, tag, card, tag, note, card, tag) ) +
6071 ( """ onclick="if (!event.ctrlKey){if (!event.shiftKey){py.link('tagclick_%s');}else{py.link('_tag_%s_tag_%s')}}else{if(event.shiftKey){py.link('_tagctrlshift_%s_tagctrlshift_%s_tagctrlshift_%s')}else{py.link('_tagdbl_%s_tagdbl_%s')}}return false;">%s</kbd>"""
6072 % (tag, note, tag, card, tag, note, card, tag, tag) ) for tag in tags])
6073 tagStr += java_script
6074 template, n = re.subn(TAG_MARK, tagStr, template)
6075 if (not n) and (template.rfind(FRNT_SIDE) == -1) and (TAG_IN_ALL_CARDS):
6076 template = tagStr + template
6077 return old_render(self, template, context, encoding)
6078
6079def kbdTags_new_css(card):
6080 return kbdTags_old_css(card) + "\n<style>\n%s\n</style>\n\n" % kbd_css
6081
6082kbdTags_old_css = Card.css
6083Card.css = kbdTags_new_css
6084
6085old_render = Template.render
6086Template.render = new_render
6087
6088#function to change anki behavior of opening minimized windows that anki doesn't deals with it
6089def unminimize(dlgmngr, name, *args):
6090 instance=old_open(dlgmngr,name, *args)
6091 if instance:
6092 instance.setWindowState(instance.windowState() & ~Qt.WindowMinimized | Qt.WindowActive)
6093 return instance
6094
6095old_open= DialogManager.open
6096DialogManager.open = unminimize
6097
6098def new_renderPreview(self, cardChanged=False,_old=None):
6099 _old(self,cardChanged)
6100 if self._previewWindow:
6101 self._previewWeb._linkHandler=lambda url: tagClicklinkHandler(self,url)
6102 self._previewWeb.setLinkHandler(self._previewWeb._linkHandler)
6103
6104Browser._renderPreview=wrap(Browser._renderPreview, new_renderPreview, "around")
6105
6106# -- click in Preview doesn't work
6107
6108#################################################################################################
6109# Replay buttons on card
6110# https://ankiweb.net/shared/info/498789867
6111#
6112# Small add-on to add replay buttons on the card itself, the way AnkiDroid does it.
6113# This is mostly useful when you have more than one audio file on a card;
6114# with this you can replay individual files. Although there isn’t that much more to say,
6115# there is a manual page. Report further problems at the support site or at github.
6116#
6117# http://ospalh.github.io/anki-addons/Play%20button.html
6118# https://anki.tenderapp.com/discussions/add-ons
6119# https://github.com/ospalh/anki-addons/issues?state=open
6120#
6121# Copyright © 2013–14 Roland Sieker <ospalh@gmail.com>
6122# License: GNU AGPL, version 3 or later;
6123# http://www.gnu.org/copyleft/agpl.html
6124
6125"""Add-on for Anki 2 to add AnkiDroid-style replay buttons."""
6126
6127import shutil
6128
6129from BeautifulSoup import BeautifulSoup
6130
6131from aqt.browser import DataModel
6132from aqt.clayout import CardLayout
6133
6134sound_re = ur"\[sound:(.*?)\]"
6135
6136image_re = ur'\<img src="(.*?)" \/\>'
6137
6138original_arrow_name = 'replay.png'
6139collection_arrow_name = '_inline_replay_button.png'
6140hide_class_name = u'browserhide'
6141
6142# Filter the questions and answers to add play buttons.
6143#
6144def play_button_filter(
6145 qa_html, qa_type, dummy_fields, dummy_model, dummy_data, dummy_col):
6146 #showTextik(qa_html,title=qa_type)
6147
6148 global soundtrack_q_number, soundtrack_q_list
6149 global soundtrack_a_number, soundtrack_a_list
6150 if qa_type == 'q':
6151 soundtrack_q_number = 0
6152 soundtrack_q_list = []
6153 else:
6154 soundtrack_a_number = 0
6155 soundtrack_a_list = []
6156
6157 # no matches = no call
6158 def add_button(sound):
6159 """
6160 Add img link after the match.
6161
6162 Add an img link after the match to replay the audio.
6163 The title is set to "Replay" on the question side
6164 to hide information or to the file name on the answer.
6165 """
6166
6167 if not sound.group(1):
6168 return ""
6169
6170 if not os.path.isfile(os.path.join(mw.pm.profileFolder(), "collection.media", sound.group(1))):
6171 return ""
6172
6173 if 'q' == qa_type:
6174 title = _(u"Replay") + ((' [sound:' + sound.group(1) + ']') if A['REPLAY_BUTTONS_ON_CARD'][0] > 1 else '')
6175 else:
6176 title = sound.group(1)
6177
6178 global soundtrack_q_list
6179 global soundtrack_a_list
6180 if 'q' == qa_type:
6181 soundtrack_q_list.append( sound.group(1) )
6182 else:
6183 soundtrack_a_list.append( sound.group(1) )
6184 #onAudioList()
6185
6186 # -- It's return !!! --
6187 return """{orig}<a href='javascript:py.link("ankiplay{fn}");' \
6188{ttl} class="replaybutton browserhide"><span><img \
6189alt="play" src="{ip}" style="max-width: 32px; max-height: 1em; min-height:8px;" />\
6190</span></a><span style="display: none;">[sound:{fn}]</span>""".format(
6191 orig=sound.group(0), fn=sound.group(1), ip=collection_arrow_name,
6192 ttl=((' title=" ' + title + ' "') if B['B11_BUTTON_TITLES'][0] else '') )
6193 # The [ ] are the square brackets that we want to
6194 # appear as brackets and not trigger the playing of the
6195 # sound. The span inside the a around the img is to bring this
6196 # closer in line with AnkiDroid.
6197
6198 retv = re.sub(sound_re, add_button, qa_html)
6199 # no matches = no call
6200
6201 if qa_type == 'q':
6202 if A['COLORFUL_TOOLBAR'][0]:
6203 replay_action.setEnabled(False)
6204 replay_all_audio_action.setEnabled(False)
6205 replay_audio_action.setEnabled(False)
6206
6207 if A['F6_SOUND_KEY_MENU'][0]:
6208 pause_action.setEnabled(False)
6209 stop_action.setEnabled(False)
6210 forward_action.setEnabled(False)
6211 backward_action.setEnabled(False)
6212 if A['F6_FAST_SLOW'][0]:
6213 fast_action.setEnabled(False)
6214 slow_action.setEnabled(False)
6215 init_action.setEnabled(False)
6216
6217 if qa_type == 'a':
6218 if len(soundtrack_q_list)+len(soundtrack_a_list)>0:
6219 if A['COLORFUL_TOOLBAR'][0]:
6220 replay_action.setEnabled(True)
6221 replay_all_audio_action.setEnabled(True)
6222 replay_audio_action.setEnabled(True)
6223
6224 if A['F6_SOUND_KEY_MENU'][0]:
6225 pause_action.setEnabled(True)
6226 stop_action.setEnabled(True)
6227 forward_action.setEnabled(True)
6228 backward_action.setEnabled(True)
6229 if A['F6_FAST_SLOW'][0]:
6230 fast_action.setEnabled(True)
6231 slow_action.setEnabled(True)
6232 init_action.setEnabled(True)
6233
6234 #onAudioList()
6235 return retv
6236
6237# Filter the questions and answers to remove absentee (missing, not found, unavailable) images.
6238
6239def images_filter(
6240 qa_html, qa_type, dummy_fields, dummy_model, dummy_data, dummy_col):
6241
6242 # no matches = no call
6243 def remove_img(picture):
6244 """
6245 Remove <img tag, if there is no such file in collection.media folder.
6246 """
6247 pic = os.path.join(mw.pm.profileFolder(), "collection.media", picture.group(1))
6248 if not os.path.isfile(pic):
6249 return ""
6250
6251 return picture.group(0)
6252
6253 return re.sub(image_re, remove_img, qa_html)
6254 # no matches = no call
6255
6256
6257def simple_link_handler(url):
6258 # Play the file.
6259 if url.startswith("ankiplay"):
6260 play(url[8:])
6261 else:
6262 QDesktopServices.openUrl(QUrl(url))
6263
6264def add_clayout_link_handler(clayout, dummy_t):
6265 # Make sure we play the files from the card layout window.
6266 clayout.forms[-1]['pform'].frontWeb.setLinkHandler(simple_link_handler)
6267 clayout.forms[-1]['pform'].backWeb.setLinkHandler(simple_link_handler)
6268
6269def add_preview_link_handler(browser):
6270 # Make sure we play the files from the preview window.
6271 browser._previewWeb.setLinkHandler(simple_link_handler)
6272
6273def reduce_format_qa(self, text):
6274 # Remove elements with a given class before displaying.
6275 soup = BeautifulSoup(text)
6276 for hide in soup.findAll(True, {'class': re.compile(
6277 '\\b' + hide_class_name + '\\b')}):
6278 hide.extract()
6279 return original_format_qa(self, unicode(soup))
6280
6281def copy_arrow():
6282 # Copy the image file to the collection.
6283 if not os.path.exists(os.path.join(
6284 mw.col.media.dir(), collection_arrow_name)):
6285 shutil.copy(
6286 os.path.join(mw.pm.addonFolder(), MUSTHAVE_COLOR_ICONS,
6287 original_arrow_name),
6288 collection_arrow_name)
6289
6290if A['REPLAY_BUTTONS_ON_CARD'][0]:
6291 #oldLinkHandler = Reviewer._linkHandler
6292 #Reviewer._linkHandler = tagClicklinkHandler
6293 Reviewer._linkHandler = wrap(Reviewer._linkHandler, tagClicklinkHandler)
6294
6295 original_format_qa = DataModel.formatQA
6296 DataModel.formatQA = reduce_format_qa
6297
6298 addHook("mungeQA", images_filter) # after play_button_filter replay images also had been deleted!
6299 addHook("mungeQA", play_button_filter)
6300 # file exists in collection.media folder
6301 # but not found cause of <img src=... alt=... style=...
6302 # alt and style were interpreted by re as part of src value.
6303 # after changing to <img alt=... src=... style=... it's Ok now,
6304 # images_filter and play_button_filter can be called in any order.
6305
6306 Browser._openPreview = wrap(Browser._openPreview, add_preview_link_handler)
6307 CardLayout.addTab = wrap(CardLayout.addTab, add_clayout_link_handler)
6308 addHook("profileLoaded", copy_arrow)
6309
6310##################################################################
6311# ZOOM
6312# https://ankiweb.net/shared/info/1956318463
6313
6314deck_browser_current_zoom = deck_browser_standard_zoom
6315overview_current_zoom = overview_standard_zoom
6316reviewer_current_zoom = reviewer_standard_zoom
6317
6318# Copyright © 2012–2013 Roland Sieker <ospalh@gmail.com>
6319# Based in part on code by Damien Elmes <anki@ichi2.net>
6320# License: GNU GPL, version 3 or later; http://www.gnu.org/copyleft/gpl.html
6321
6322"""Add-on for Anki 2 to zoom in or out."""
6323
6324def reset_current_zoom():
6325 global deck_browser_current_zoom, overview_current_zoom, reviewer_current_zoom
6326 deck_browser_current_zoom = deck_browser_standard_zoom
6327 overview_current_zoom = overview_standard_zoom
6328 reviewer_current_zoom = reviewer_standard_zoom
6329
6330# How much to increase or decrease the zoom FACTOR with each step. The
6331# a little odd looking number is the fourth root of two. That means
6332# with four clicks you double or half the size, as precisely as
6333# possible.
6334zoom_step = .1 # 1.1 # min 0.5 after 1.9 next 2.1 # 1.5**.15 min 0.8 # 2.0**0.25
6335
6336def zoom_in(step=None):
6337 # Increase the text size.
6338 global deck_browser_current_zoom, overview_current_zoom, reviewer_current_zoom
6339 if not step:
6340 step = zoom_step
6341
6342 if 'deckBrowser' == mw.state:
6343 #deck_browser_current_zoom = round(deck_browser_current_zoom * zoom_step, 1)
6344 deck_browser_current_zoom = round(deck_browser_current_zoom + zoom_step, 1)
6345 current_zoom = deck_browser_current_zoom
6346 if 'overview' == mw.state or 'requestRequired' == mw.state:
6347 overview_current_zoom = round(overview_current_zoom + zoom_step, 1)
6348 current_zoom = overview_current_zoom
6349 if 'review' == mw.state:
6350 reviewer_current_zoom = round(reviewer_current_zoom + zoom_step, 1)
6351 current_zoom = reviewer_current_zoom
6352
6353 if A['ZOOM_IMAGES'][0]:
6354 mw.web.setZoomFactor(current_zoom)
6355 else:
6356 mw.web.setTextSizeMultiplier(current_zoom)
6357
6358def zoom_out(step=None):
6359 # Decrease the text size.
6360 global deck_browser_current_zoom, overview_current_zoom, reviewer_current_zoom
6361 if not step:
6362 step = zoom_step
6363
6364 if 'deckBrowser' == mw.state:
6365 #deck_browser_current_zoom = round(deck_browser_current_zoom / zoom_step, 1)
6366 deck_browser_current_zoom = max(.1,round(deck_browser_current_zoom - zoom_step, 1))
6367 current_zoom = deck_browser_current_zoom
6368 if 'overview' == mw.state or 'requestRequired' == mw.state:
6369 overview_current_zoom = max(.1,round(overview_current_zoom - zoom_step, 1))
6370 current_zoom = overview_current_zoom
6371 if 'review' == mw.state:
6372 reviewer_current_zoom = max(.1,round(reviewer_current_zoom - zoom_step, 1))
6373 current_zoom = reviewer_current_zoom
6374
6375 if A['ZOOM_IMAGES'][0]:
6376 mw.web.setZoomFactor(current_zoom)
6377 else:
6378 mw.web.setTextSizeMultiplier(current_zoom)
6379
6380def zoom_init(state=None, *args):
6381 # Reset the text size.
6382 global deck_browser_current_zoom, overview_current_zoom, reviewer_current_zoom
6383 current_zoom = 1.0
6384
6385 deck_browser_current_zoom = 1.0
6386 overview_current_zoom = 1.0
6387 reviewer_current_zoom = 1.0
6388
6389 if A['ZOOM_IMAGES'][0]:
6390 mw.web.setZoomFactor(current_zoom)
6391 else:
6392 mw.web.setTextSizeMultiplier(current_zoom)
6393
6394def zoom_reset(state=None, *args):
6395 # Reset the text size.
6396 global deck_browser_current_zoom, overview_current_zoom, reviewer_current_zoom
6397 current_zoom = 1.0
6398
6399 if 'deckBrowser' == mw.state:
6400 current_zoom = deck_browser_standard_zoom
6401 if 'overview' == mw.state or 'requestRequired' == mw.state:
6402 current_zoom = overview_standard_zoom
6403 if 'review' == mw.state:
6404 current_zoom = reviewer_standard_zoom
6405 reset_current_zoom()
6406
6407 if A['ZOOM_IMAGES'][0]:
6408 mw.web.setZoomFactor(current_zoom)
6409 else:
6410 mw.web.setTextSizeMultiplier(current_zoom)
6411
6412def current_reset_zoom(state=None, *args):
6413 # Reset the text size.
6414 global deck_browser_current_zoom, overview_current_zoom, reviewer_current_zoom
6415 current_zoom = 1
6416
6417 if 'deckBrowser' == mw.state:
6418 current_zoom = deck_browser_current_zoom
6419 if 'overview' == mw.state or 'requestRequired' == mw.state:
6420 current_zoom = overview_current_zoom
6421 if 'review' == mw.state:
6422 current_zoom = reviewer_current_zoom
6423
6424 if A['ZOOM_IMAGES'][0]:
6425 mw.web.setZoomFactor(current_zoom)
6426 else:
6427 mw.web.setTextSizeMultiplier(current_zoom)
6428
6429def zoom_info():
6430 showTextik("<big>deck_browser_standard_zoom = <b>"+str(deck_browser_current_zoom)+"</b><br>\n"+\
6431 "overview_standard_zoom = <b>"+str(overview_current_zoom)+"</b><br>\n"+\
6432 "reviewer_standard_zoom = <b>"+str(reviewer_current_zoom)+"</b></big>\n",\
6433 minW=222, minH=66, type="HTML", title="Zoom")
6434
6435def zoom_setup_menu():
6436 # Set up the zoom menu.
6437 mw.zoom_submenu = QMenu('МаÑ&штаб' if lang == 'ru' else _(u"&Zoom"), mw)
6438 if A['ANKI_MENU_ICONS'][0]:
6439 mw.zoom_submenu.setIcon(QIcon(os.path.join(MUSTHAVE_COLOR_ICONS, 'zoom.png')))
6440
6441 zoom_info_action = QAction('МаÑштаб &показать' if lang == 'ru' else _('Zoom &Info'), mw)
6442 zoom_info_action.setShortcut(QKeySequence(HOTKEY['zoom_info'][0])) # Ctrl+Shift+0 doesn't work on NumPad
6443 mw.connect(zoom_info_action, SIGNAL("triggered()"), zoom_info)
6444
6445 zoom_in_action = QAction('МаÑштаб у&величить' if lang == 'ru' else _('Zoom &In'), mw)
6446 zoom_in_action.setShortcut(QKeySequence(HOTKEY['zoom_in'][0]))
6447 if A['ANKI_MENU_ICONS'][0]:
6448 zoom_in_action.setIcon(QIcon(os.path.join(MUSTHAVE_COLOR_ICONS, 'zoom_in.png')))
6449 mw.connect(zoom_in_action, SIGNAL("triggered()"), zoom_in)
6450
6451 zoom_out_action = QAction('МаÑштаб у&меньшить' if lang == 'ru' else _('Zoom &Out'), mw)
6452 zoom_out_action.setShortcut(QKeySequence(HOTKEY['zoom_out'][0]))
6453 if A['ANKI_MENU_ICONS'][0]:
6454 zoom_out_action.setIcon(QIcon(os.path.join(MUSTHAVE_COLOR_ICONS, 'zoom_out.png')))
6455 mw.connect(zoom_out_action, SIGNAL("triggered()"), zoom_out)
6456
6457 reset_zoom_action = QAction('МаÑштаб на&чальный ( ='+str(deck_browser_standard_zoom)+' ='+str(overview_standard_zoom)+' ='+str(reviewer_standard_zoom)+' )' if lang == 'ru' else _('&Reset Initial ')+u'( ='+str(deck_browser_standard_zoom)+' ='+str(overview_standard_zoom)+' ='+str(reviewer_standard_zoom)+' )', mw)
6458 reset_zoom_action.setShortcut(QKeySequence(HOTKEY['zoom_reset'][0])) # Shift+0 does not work on NumPad
6459 mw.connect(reset_zoom_action, SIGNAL("triggered()"), zoom_reset)
6460
6461 reset_zoom_init_action = QAction('МаÑ&штаб 1:1 100% ( =1.0 =1.0 =1.0 )' if lang == 'ru' else _('&Reset 1:1 100% ( =1.0 =1.0 =1.0 )'), mw)
6462 reset_zoom_init_action.setShortcut(QKeySequence(HOTKEY['zoom_init'][0]))
6463 mw.connect(reset_zoom_init_action, SIGNAL("triggered()"), zoom_init)
6464
6465 if mw_addon_view_menu_exists:
6466 mw.addon_view_menu.addMenu(mw.zoom_submenu)
6467 mw.zoom_submenu.addAction(zoom_info_action)
6468 mw.zoom_submenu.addSeparator()
6469 mw.zoom_submenu.addAction(zoom_in_action)
6470 mw.zoom_submenu.addAction(zoom_out_action)
6471 mw.zoom_submenu.addSeparator()
6472 if deck_browser_standard_zoom != 1.0 or overview_standard_zoom != 1.0 or reviewer_standard_zoom != 1.0:
6473 mw.zoom_submenu.addAction(reset_zoom_action)
6474 mw.zoom_submenu.addAction(reset_zoom_init_action)
6475
6476################################
6477#
6478toBeerOrNot2Beer = False
6479
6480def handle_wheel_event(event):
6481 """Zoom on mouse wheel events with Ctrl.
6482
6483 Zoom in our out on mouse wheel events when Ctrl is pressed.
6484 A standard mouse wheel click is 120/8 degree.
6485 ZOOM by one step for that amount.
6486 """
6487 global F9_HINT_PEEKED
6488
6489 if event.modifiers() & Qt.ControlModifier:
6490 step = event.delta() / 120 * zoom_step
6491 if step < 0:
6492 zoom_in(-step)
6493 else:
6494 zoom_out(step)
6495
6496 elif bool(event.modifiers() & Qt.AltModifier) and bool(event.modifiers() & Qt.ShiftModifier) or (altMW_action.isChecked() and shiftMW_action.isChecked()):
6497
6498 step = event.delta() / 12000 * zoom_step
6499 if step > 0:
6500 if mw.state == "review":
6501 if mw.reviewer.state == 'answer':
6502 if (not A['ONESIDED_CARDS'][0]) or (not toBeerOrNot2Beer):
6503 anki.sound.stopMplayer()
6504 F9_HINT_PEEKED = False
6505 mw.reviewer._initWeb() # _showQuestion()
6506 elif mw.form.actionUndo.isEnabled():
6507 anki.sound.stopMplayer()
6508 mw.onUndo()
6509 elif mw.reviewer.state == 'question':
6510 if mw.form.actionUndo.isEnabled():
6511 anki.sound.stopMplayer()
6512 mw.onUndo()
6513 else:
6514 if mw.state == "review":
6515 if not _showHint(mw.reviewer):
6516 if mw.reviewer.state == 'question':
6517 mw.reviewer._showAnswer() # ._showAnswerHack()
6518 elif mw.reviewer.state == 'answer':
6519 F9_HINT_PEEKED = False
6520 # self._initWeb() # ._initWeb() # _showQuestion()
6521 #mw.reviewer._answerCard(mw.reviewer._defaultEase())
6522 mw.reviewer.onBuryCard()
6523
6524 elif bool(event.modifiers() & Qt.AltModifier) or altMW_action.isChecked():
6525
6526 step = event.delta() / 12000 * zoom_step
6527 if step > 0:
6528 if mw.state == "review":
6529 if mw.reviewer.state == 'answer':
6530 if (not A['ONESIDED_CARDS'][0]) or (not toBeerOrNot2Beer):
6531 anki.sound.stopMplayer()
6532 F9_HINT_PEEKED = False
6533 mw.reviewer._initWeb() # _showQuestion()
6534 elif mw.form.actionUndo.isEnabled():
6535 anki.sound.stopMplayer()
6536 mw.onUndo()
6537 elif mw.reviewer.state == 'question':
6538 if mw.form.actionUndo.isEnabled():
6539 mw.onUndo()
6540 else:
6541 if mw.state == "review":
6542 if not _showHint(mw.reviewer):
6543 if mw.reviewer.state == 'question':
6544 mw.reviewer._showAnswer() # ._showAnswerHack()
6545 elif mw.reviewer.state == 'answer':
6546 F9_HINT_PEEKED = False
6547 # self._initWeb() # ._initWeb() # _showQuestion()
6548 mw.reviewer._answerCard(mw.reviewer._defaultEase())
6549
6550 elif bool(event.modifiers() & Qt.ShiftModifier) or shiftMW_action.isChecked():
6551
6552 step = event.delta() / 12000 * zoom_step
6553 if step > 0:
6554 if mw.state == "review":
6555 if mw.reviewer.state == 'answer':
6556 if (not A['ONESIDED_CARDS'][0]) or (not toBeerOrNot2Beer):
6557 anki.sound.stopMplayer()
6558 F9_HINT_PEEKED = False
6559 mw.reviewer._initWeb() # _showQuestion()
6560 elif mw.form.actionUndo.isEnabled():
6561 anki.sound.stopMplayer()
6562 mw.onUndo()
6563 elif mw.reviewer.state == 'question':
6564 if mw.form.actionUndo.isEnabled():
6565 anki.sound.stopMplayer()
6566 mw.onUndo()
6567 else:
6568 if mw.state == "review":
6569 if mw.reviewer.state == 'question':
6570 anki.sound.stopMplayer()
6571 mw.reviewer._showAnswer()
6572 elif mw.reviewer.state == 'answer':
6573 anki.sound.stopMplayer()
6574 mw.reviewer._answerCard(mw.reviewer._defaultEase())
6575
6576 else:
6577 original_mw_web_wheelEvent(event)
6578
6579def run_move_to_state_hook(state, *args):
6580 """Run a hook whenever we have changed the state."""
6581 runHook("movedToState", state)
6582
6583if A['ZOOM'][0]:
6584
6585 mw.moveToState = wrap(mw.moveToState, run_move_to_state_hook)
6586 addHook("movedToState", current_reset_zoom)
6587 original_mw_web_wheelEvent = mw.web.wheelEvent
6588 mw.web.wheelEvent = handle_wheel_event
6589 zoom_setup_menu()
6590
6591 shiftMW_action = QAction(mw)
6592 shiftMW_action.setText(u'(Shift+колеÑо мыши): ответы по умолч.' if lang=='ru' else u'Shift + Mouse Wheel')
6593 shiftMW_action.setIcon(QIcon(os.path.join(MUSTHAVE_COLOR_ICONS, 'shift.png')))
6594 shiftMW_action.setCheckable(True)
6595 shiftMW_action.setChecked(False)
6596
6597 altMW_action = QAction(mw)
6598 altMW_action.setText(u'(Alt+колеÑо мыши): ещё и подÑказки' if lang=='ru' else u'Alt + Mouse Wheel')
6599 altMW_action.setIcon(QIcon(os.path.join(MUSTHAVE_COLOR_ICONS, 'alt.png')))
6600 altMW_action.setCheckable(True)
6601 altMW_action.setChecked(False)
6602
6603 if mw_addon_go_menu_exists:
6604 mw.addon_go_menu.addSeparator()
6605 mw.addon_go_menu.addAction(shiftMW_action)
6606 mw.addon_go_menu.addAction(altMW_action)
6607 mw.addon_go_menu.addSeparator()
6608
6609####################################################################################
6610toolbar_gradient_form = """QToolBar:top, QToolBar:bottom {{
6611background: qlineargradient(x1:0, y1:0, x2:0, y2:1, stop:0 {bg}, stop:1 {bgg});
6612}}
6613QToolBar:left, QToolBar:right {{
6614background: qlineargradient(x1:0, y1:0, x2:1, y2:0, stop:0 {bg}, stop:1 {bgg});
6615}}
6616QToolBar:top {{border-bottom: 1px solid {bgl};}}
6617QToolBar:bottom {{border-top: 1px solid {bgl};}}
6618QToolBar:left {{border-right: 1px solid {bgl};}}
6619QToolBar:right {{border-left: 1px solid {bgl};}}""" # """
6620
6621# Change below this at your own risk/only when you know what you are doing.
6622
6623# Make all the actions top level, so we can use them for the menu and
6624# the tool bar.
6625
6626# Most of the icons are part of the standard version, but as they are
6627# not currently used by the standard version, they may disappear when
6628# dae gets around to doing some clean up. So bring them along, anyway.
6629
6630if A['COLORFUL_TOOLBAR'][0]: # or not B['B13_EDIT_MORE'][0]:
6631
6632 edit_current_action = QAction(mw)
6633 edit_current_action.setText(u'Р&едактирование...' if lang=='ru' else _(u"&Edit..."))
6634 edit_current_action.setIcon(QIcon(os.path.join(MUSTHAVE_COLOR_ICONS, 'edit_current.png')))
6635 edit_current_action.setShortcut(QKeySequence(HOTKEY['E'][0]))
6636 mw.connect(edit_current_action, SIGNAL("triggered()"), go_edit_current)
6637
6638 toggle_mark_action = QAction(mw)
6639 toggle_mark_action.setText(_(u"Mark Note"))
6640 toggle_mark_action.setCheckable(True)
6641
6642 toggle_mark_icon = QIcon()
6643 toggle_mark_icon.addFile(os.path.join(MUSTHAVE_COLOR_ICONS, 'mark_off.png'))
6644 toggle_mark_icon.addFile(os.path.join(MUSTHAVE_COLOR_ICONS, 'mark_on.png'),
6645 QSize(), QIcon.Normal, QIcon.On)
6646 if A['COLORFUL_TOOLBAR'][0]:
6647 toggle_mark_action.setIcon(toggle_mark_icon)
6648
6649 toggle_mark_action.setShortcut(QKeySequence(HOTKEY['marked'][0]))
6650 # Alt+Space opens main menu.
6651 # Shift+Space doesn't work inside of type:Field
6652 mw.connect(toggle_mark_action, SIGNAL("triggered()"), mw.reviewer.onMark)
6653
6654 toggle_last_card_action = QAction(mw)
6655 toggle_last_card_action.setText(u"Прекратить проÑмотр поÑле Ñтой карточки" if lang=="ru" else _(u"Last card"))
6656 toggle_last_card_action.setToolTip(u"ПоÑле Ñтой карточки проÑмотр колоды будет прекращён" if lang=="ru" else _(u"Last card"))
6657 toggle_last_card_action.setShortcut(HOTKEY['toggle_last'][0])
6658 toggle_last_card_action.setCheckable(True)
6659 toggle_last_card_action.setChecked(False)
6660
6661 toggle_last_card_icon = QIcon()
6662 toggle_last_card_icon.addFile(os.path.join(MUSTHAVE_COLOR_ICONS, 'last_card_off.png'))
6663 toggle_last_card_icon.addFile(os.path.join(MUSTHAVE_COLOR_ICONS, 'last_card_on.png'),
6664 QSize(), QIcon.Normal, QIcon.On)
6665 if A['COLORFUL_TOOLBAR'][0]:
6666 toggle_last_card_action.setIcon(toggle_last_card_icon)
6667
6668 bury_card_action = QAction(mw)
6669 bury_card_action.setText(_(u"Bury Card"))
6670 #bury_note_action.setShortcut("-") # This hotkey is used somewhere else. But it works as intended.
6671 if A['ANKI_MENU_ICONS'][0]:
6672 bury_card_action.setIcon(QIcon(os.path.join(MUSTHAVE_COLOR_ICONS, 'bury-card.png')))
6673 mw.connect(bury_card_action, SIGNAL("triggered()"), mw.reviewer.onBuryCard)
6674
6675 bury_note_action = QAction(mw)
6676 bury_note_action.setText(_(u"Bury Note"))
6677 bury_note_action.setShortcut(HOTKEY['bury_note'][0])
6678 if A['ANKI_MENU_ICONS'][0]:
6679 bury_note_action.setIcon(QIcon(os.path.join(MUSTHAVE_COLOR_ICONS, 'bury.png')))
6680 mw.connect(bury_note_action, SIGNAL("triggered()"), mw.reviewer.onBuryNote)
6681
6682 suspend_card_action = QAction(mw)
6683 suspend_card_action.setText(_(u"Suspend Card"))
6684 suspend_card_action.setShortcut(HOTKEY['suspend_card'][0])
6685 if A['ANKI_MENU_ICONS'][0]:
6686 suspend_card_action.setIcon(QIcon(os.path.join(MUSTHAVE_COLOR_ICONS, 'suspend_card.png')))
6687 mw.connect(
6688 suspend_card_action, SIGNAL("triggered()"), mw.reviewer.onSuspendCard)
6689
6690 suspend_note_action = QAction(mw)
6691 suspend_note_action.setText(_(u"Suspend Note"))
6692 suspend_note_action.setShortcut(HOTKEY['suspend_note'][0])
6693 if A['ANKI_MENU_ICONS'][0]:
6694 suspend_note_action.setIcon(QIcon(os.path.join(MUSTHAVE_COLOR_ICONS, 'suspend.png')))
6695 mw.connect(suspend_note_action, SIGNAL("triggered()"), mw.reviewer.onSuspend)
6696
6697 delete_action = QAction(mw)
6698 delete_action.setText(_(u"Delete Note"))
6699 if A['ANKI_MENU_ICONS'][0]:
6700 delete_action.setIcon(QIcon(os.path.join(MUSTHAVE_COLOR_ICONS, 'delete.png')))
6701 #delete_action.setShortcut(Qt.META + Qt.Key_Minus)
6702 # on Mac it is converted to Ctrl+- but it is zoom in already
6703 delete_action.setShortcut(HOTKEY['META_Minus'][0])
6704 mw.connect(delete_action, SIGNAL("triggered()"), ask_delete)
6705
6706 options_action = QAction(mw)
6707 options_action.setText(u'&ÐаÑтройки...' if lang=='ru' else _(u"&Options..."))
6708 if A['ANKI_MENU_ICONS'][0]:
6709 options_action.setIcon(QIcon(os.path.join(MUSTHAVE_COLOR_ICONS, 'options.png')))
6710 options_action.setShortcut(HOTKEYZ['options'][0])
6711 mw.connect(options_action, SIGNAL("triggered()"), go_options)
6712
6713mute_action = QAction(mw)
6714mute_action.setText(_(u"Automatically play audio"))
6715mute_action.setShortcut(QKeySequence(HOTKEY['autoplay'][0]))
6716mute_action.setCheckable(True)
6717mute_action.setChecked(True)
6718mute_icon = QIcon()
6719mute_icon.addFile(os.path.join(MUSTHAVE_COLOR_ICONS, 'g-mute.png'))
6720mute_icon.addFile(os.path.join(MUSTHAVE_COLOR_ICONS, 'g-unmute.png'), QSize(), QIcon.Normal, QIcon.On)
6721mute_action.setIcon(mute_icon)
6722
6723if A['COLORFUL_TOOLBAR'][0]:
6724
6725 bury_card_aktion = QAction(mw)
6726 bury_card_aktion.setText(_(u"Bury Card"))
6727 bury_card_aktion.setIcon(QIcon(os.path.join(MUSTHAVE_COLOR_ICONS, 'bury-card.png')))
6728 mw.connect(bury_card_aktion, SIGNAL("triggered()"), mw.reviewer.onBuryCard)
6729
6730 bury_note_aktion = QAction(mw)
6731 bury_note_aktion.setText(_(u"Bury Note"))
6732 bury_note_aktion.setIcon(QIcon(os.path.join(MUSTHAVE_COLOR_ICONS, 'bury.png')))
6733 mw.connect(bury_note_aktion, SIGNAL("triggered()"), mw.reviewer.onBuryNote)
6734
6735 suspend_card_aktion = QAction(mw)
6736 suspend_card_aktion.setText(_(u"Suspend Card"))
6737 suspend_card_aktion.setIcon(QIcon(os.path.join(MUSTHAVE_COLOR_ICONS, 'g-suspend_card.png')))
6738 mw.connect(
6739 suspend_card_aktion, SIGNAL("triggered()"), mw.reviewer.onSuspendCard)
6740
6741 suspend_note_aktion = QAction(mw)
6742 suspend_note_aktion.setText(_(u"Suspend Note"))
6743 suspend_note_aktion.setIcon(QIcon(os.path.join(MUSTHAVE_COLOR_ICONS, 'g-suspend.png')))
6744 mw.connect(suspend_note_aktion, SIGNAL("triggered()"), mw.reviewer.onSuspend)
6745
6746 delete_aktion = QAction(mw)
6747 delete_aktion.setText(_(u"Delete Note"))
6748 delete_aktion.setIcon(QIcon(os.path.join(MUSTHAVE_COLOR_ICONS, 'g-delete.png')))
6749 mw.connect(delete_aktion, SIGNAL("triggered()"), ask_delete)
6750
6751 replay_action = QAction(mw)
6752 replay_action.setText(u"Проиграть вÑе &аудио-файлы (F5)" if lang=='ru' else _(u"&Replay Audio"))
6753 replay_action.setIcon(QIcon(os.path.join(MUSTHAVE_COLOR_ICONS, 'replay.png')))
6754 mw.connect(replay_action, SIGNAL("triggered()"), mw.reviewer.replayAudio)
6755
6756 replay_audio_action = QAction(mw)
6757 replay_audio_action.setText("Прои&грать Ñледующее аудио" if lang == 'ru' else "Replay &Next Audio")
6758 replay_audio_action.setIcon(QIcon(os.path.join(MUSTHAVE_COLOR_ICONS, 'play_next.png')))
6759 replay_audio_action.setShortcut(HOTKEY['replay_next'][0])
6760 mw.connect(replay_audio_action, SIGNAL("triggered()"), onKeyPlus)
6761
6762 list_audio_action = QAction(mw)
6763 list_audio_action.setText("Показать &ÑпиÑок аудио" if lang == 'ru' else "Show Audio &List")
6764 list_audio_action.setIcon(QIcon(os.path.join(MUSTHAVE_COLOR_ICONS, 'list.png')))
6765 list_audio_action.setShortcut(HOTKEY['audio_list'][0])
6766 mw.connect(list_audio_action, SIGNAL("triggered()"), onAudioList)
6767
6768 record_own_action = QAction(mw)
6769 record_own_action.setText(u"&ЗапиÑать Ñвой голоÑ" if lang=='ru' else _(u"Re&cord own voice"))
6770 record_own_action.setIcon(QIcon(os.path.join(MUSTHAVE_COLOR_ICONS, 'blue_mic.png')))
6771 record_own_action.setShortcut(HOTKEYZ['record_own_voice'][0])
6772 mw.connect(record_own_action, SIGNAL("triggered()"), mw.reviewer.onRecordVoice)
6773
6774 replay_own_action = QAction(mw)
6775 replay_own_action.setText(u"&ВоÑпроизвеÑти Ñвой голоÑ" if lang=='ru' else _(u"Re&play own voice"))
6776 replay_own_action.setIcon(QIcon(os.path.join(MUSTHAVE_COLOR_ICONS, 'g-play_green.png')))
6777 replay_own_action.setShortcut(HOTKEYZ['replay_own_voice'][0])
6778 mw.connect(replay_own_action, SIGNAL("triggered()"),
6779 mw.reviewer.onReplayRecorded)
6780
6781 replay_all_audio_action = QAction(mw)
6782 replay_all_audio_action.setText("Про&играть вÑе аудио (F5|R)" if lang == 'ru' else "&Replay All Audio (F5|R)")
6783 replay_all_audio_action.setIcon(QIcon(os.path.join(MUSTHAVE_COLOR_ICONS, 'replay.png')))
6784 mw.connect(replay_all_audio_action, SIGNAL("triggered()"), mw.reviewer.replayAudio)
6785
6786 # Add a Qt tool bar to Anki2.
6787
6788 # This is a more Anki-1-ish Qt tool bar with a number of rather big,
6789 # colorful icons to replace the Anki 2 DSAB toolbar.
6790
6791 mw.qt_tool_bar = QToolBar()
6792 # mw.qt_tool_bar.setAccessibleName('secondary tool bar')
6793 mw.qt_tool_bar.setObjectName('qt tool bar')
6794 if netbook_version:
6795 mw.qt_tool_bar.setIconSize(QSize(A['KING_SIZE'][0], A['KING_SIZE'][0]))
6796 else:
6797 mw.qt_tool_bar.setIconSize(QSize(32, 32))
6798 # Conditional setup
6799 if netbook_version or qt_toolbar_movable:
6800 mw.qt_tool_bar.setFloatable(True)
6801 mw.qt_tool_bar.setMovable(True)
6802 if netbook_version:
6803 mw.addToolBar(Qt.LeftToolBarArea, mw.qt_tool_bar)
6804 else:
6805 mw.addToolBar(Qt.TopToolBarArea, mw.qt_tool_bar)
6806 else:
6807 mw.qt_tool_bar.setFloatable(False)
6808 mw.qt_tool_bar.setMovable(False)
6809 mw.mainLayout.insertWidget(1, mw.qt_tool_bar)
6810 if do_gradient:
6811 palette = mw.qt_tool_bar.palette()
6812 fg = palette.color(QPalette.ButtonText)
6813 bg = palette.color(QPalette.Button)
6814
6815 if bg.lightnessF() > fg.lightnessF():
6816 bgg = bg.lighter(GRADIENT_L)
6817 bgl = bg.darker(GRADIENT_H)
6818 else:
6819 bgl = bg.lighter(GRADIENT_L)
6820 bgg = bg.darker(GRADIENT_H)
6821
6822 mw.qt_tool_bar.setStyleSheet(
6823 toolbar_gradient_form.format(
6824 bg=bg.name(), bgg=bgg.name(), bgl=bgl.name()))
6825
6826 mw.qt_tool_bar.addAction(mw.form.actionUndo)
6827 mw.qt_tool_bar.addSeparator()
6828 mw.qt_tool_bar.addAction(edit_current_action)
6829 mw.qt_tool_bar.addAction(edit_layout_action)
6830 mw.qt_tool_bar.addSeparator()
6831 mw.qt_tool_bar.addAction(decks_aktion)
6832 mw.qt_tool_bar.addAction(add_notes_aktion)
6833 mw.qt_tool_bar.addAction(browse_cards_action)
6834 mw.qt_tool_bar.addSeparator()
6835 mw.qt_tool_bar.addAction(statistics_aktion)
6836 mw.qt_tool_bar.addAction(sync_action)
6837 mw.qt_tool_bar.addSeparator()
6838 mw.qt_tool_bar.addAction(overeview_action)
6839 mw.qt_tool_bar.addAction(study_aktion)
6840 if A['ZOOM'][0]:
6841 mw.qt_tool_bar.addSeparator()
6842 mw.qt_tool_bar.addAction(shiftMW_action)
6843 mw.qt_tool_bar.addAction(altMW_action)
6844 #mw.qt_tool_bar.addSeparator()
6845
6846 def more_tool_bar_off():
6847 #"""Hide the more tool bar."""
6848 global show_more_tool_bar_action, edit_layout_action, bury_note_action, toggle_mark_action, suspend_card_action, suspend_note_action, delete_action, bury_card_action
6849
6850 show_more_tool_bar_action.setEnabled(False)
6851
6852 toggle_mark_action.setEnabled(False)
6853
6854 bury_card_action.setEnabled(False)
6855 bury_note_action.setEnabled(False)
6856 suspend_card_action.setEnabled(False)
6857 suspend_note_action.setEnabled(False)
6858 delete_action.setEnabled(False)
6859
6860 replay_action.setEnabled(False)
6861 replay_audio_action.setEnabled(False)
6862 replay_all_audio_action.setEnabled(False)
6863 list_audio_action.setEnabled(False)
6864 mute_action.setEnabled(False)
6865
6866 if A['F6_SOUND_KEY_MENU'][0]:
6867 pause_action.setEnabled(False)
6868 stop_action.setEnabled(False)
6869 forward_action.setEnabled(False)
6870 backward_action.setEnabled(False)
6871 if A['F6_FAST_SLOW'][0]:
6872 fast_action.setEnabled(False)
6873 slow_action.setEnabled(False)
6874 init_action.setEnabled(False)
6875
6876 try:
6877 mw.reviewer.more_tool_bar.hide()
6878 except AttributeError:
6879 pass
6880
6881 def add_more_tool_bar():
6882 # Add a tool bar at the bottom.
6883
6884 #This provieds colorful command buttons for the functions usually
6885 #hidden in the "More" button at the bottom.
6886
6887 try:
6888 mw.reviewer.more_tool_bar = QToolBar()
6889 except AttributeError:
6890 return
6891 # mw.reviewer.more_tool_bar.setAccessibleName('secondary tool bar')
6892 mw.reviewer.more_tool_bar.setObjectName('more options tool bar')
6893 mw.reviewer.more_tool_bar.setIconSize(QSize(A['KING_SIZE'][0], A['KING_SIZE'][0]))
6894 if netbook_version:
6895 mw.reviewer.more_tool_bar.setFloatable(True)
6896 mw.reviewer.more_tool_bar.setMovable(True)
6897 mw.addToolBar(Qt.RightToolBarArea, mw.reviewer.more_tool_bar)
6898 else:
6899 mw.reviewer.more_tool_bar.setFloatable(False)
6900 mw.reviewer.more_tool_bar.setMovable(False)
6901 mw.mainLayout.insertWidget(2, mw.reviewer.more_tool_bar)
6902 if do_gradient:
6903 palette = mw.reviewer.more_tool_bar.palette()
6904 fg = palette.color(QPalette.ButtonText)
6905 bg = palette.color(QPalette.Button)
6906
6907 if bg.lightnessF() > fg.lightnessF():
6908 bgg = bg.lighter(GRADIENT_L)
6909 bgl = bg.darker(GRADIENT_H)
6910 else:
6911 bgl = bg.lighter(GRADIENT_L)
6912 bgg = bg.darker(GRADIENT_H)
6913
6914 mw.reviewer.more_tool_bar.setStyleSheet(
6915 toolbar_gradient_form.format(
6916 bg=bg.name(), bgg=bgg.name(), bgl=bgl.name()))
6917 # Add the actions here
6918 mw.reviewer.more_tool_bar.addAction(mw.form.actionUndo)
6919 mw.reviewer.more_tool_bar.addSeparator()
6920 mw.reviewer.more_tool_bar.addAction(edit_current_action)
6921 mw.reviewer.more_tool_bar.addAction(edit_layout_action)
6922
6923 mw.reviewer.more_tool_bar.addSeparator()
6924 mw.reviewer.more_tool_bar.addAction(toggle_mark_action)
6925
6926 if show_toggle_last:
6927 mw.reviewer.more_tool_bar.addAction(toggle_last_card_action)
6928
6929 mw.reviewer.more_tool_bar.addSeparator()
6930 mw.reviewer.more_tool_bar.addAction(bury_card_aktion)
6931 mw.reviewer.more_tool_bar.addAction(bury_note_aktion)
6932 if show_suspend_card:
6933 mw.reviewer.more_tool_bar.addAction(suspend_card_aktion)
6934 if show_suspend_note:
6935 mw.reviewer.more_tool_bar.addAction(suspend_note_aktion)
6936 mw.reviewer.more_tool_bar.addAction(delete_aktion)
6937
6938 mw.reviewer.more_tool_bar.addSeparator()
6939 mw.reviewer.more_tool_bar.addAction(options_aktion)
6940
6941 mw.reviewer.more_tool_bar.addSeparator()
6942 if show_mute_button:
6943 mw.reviewer.more_tool_bar.addAction(mute_action)
6944 mw.reviewer.more_tool_bar.addAction(replay_action)
6945 if A['REPLAY_BUTTONS_ON_CARD'][0]:
6946 mw.reviewer.more_tool_bar.addAction(replay_audio_action)
6947
6948 mw.reviewer.more_tool_bar.addSeparator()
6949 mw.reviewer.more_tool_bar.addAction(record_own_action)
6950 mw.reviewer.more_tool_bar.addAction(replay_own_action)
6951
6952 if A['F6_SOUND_KEY_MENU'][0]:
6953 mw.reviewer.more_tool_bar.addSeparator()
6954 mw.reviewer.more_tool_bar.addAction(pause_auction)
6955 mw.reviewer.more_tool_bar.addSeparator()
6956
6957 add_more_tool_bar()
6958 more_tool_bar_off()
6959
6960if A['COLORFUL_TOOLBAR'][0]: # or not B['B13_EDIT_MORE'][0]:
6961 # Add a number of items to menus.
6962
6963 # Put the functions of the DASB old-style tool bar links into
6964 # menus. Sync to the file menu, stats to the tools menu, the DASB,
6965 # together with a study-withouts-overview item to a new go
6966 # menu. Also add items to, d'uh, edit stuff to the edit menu.
6967
6968 # Add sync to the file memu. It was there in Anki 1.
6969 #mw.form.menuCol.insertAction(mw.form.actionImport, sync_action)
6970 # Make a new top level menu and insert it.
6971
6972 # Add DSAB to the new go menu
6973 if mw_addon_view_menu_exists:
6974 mw.addon_view_menu.addSeparator()
6975 # Stats. Maybe this should go to help. Seems somewhat help-ish to me, but not too much.
6976
6977 # Add to the edit menu. The undo looked a bit forlorn.
6978 mw.form.menuEdit.addSeparator()
6979 mw.form.menuEdit.addAction(edit_current_action)
6980 mw.form.menuEdit.addAction(edit_layout_action)
6981 mw.form.menuEdit.addSeparator()
6982 mw.form.menuEdit.addAction(toggle_mark_action)
6983 mw.form.menuEdit.addAction(toggle_last_card_action)
6984 mw.form.menuEdit.addSeparator()
6985 mw.form.menuEdit.addAction(bury_card_action)
6986 mw.form.menuEdit.addAction(bury_note_action)
6987 mw.form.menuEdit.addAction(suspend_card_action)
6988 mw.form.menuEdit.addAction(suspend_note_action)
6989 mw.form.menuEdit.addAction(delete_action)
6990 mw.form.menuEdit.addSeparator()
6991 mw.form.menuEdit.addAction(options_action)
6992 mw.form.menuEdit.addSeparator()
6993
6994if A['FLIP_FLOP'][0] or A['COLORFUL_TOOLBAR'][0]:
6995
6996 PageUp_icon = QIcon(os.path.join(MUSTHAVE_COLOR_ICONS, 'PageUp.png'))
6997 PageDown_icon = QIcon(os.path.join(MUSTHAVE_COLOR_ICONS, 'PageDown.png'))
6998
6999if A['FLIP_FLOP'][0]: #TODO
7000
7001 show_question_auction = QAction(mw)
7002 show_question_auction.setText(u'&Ð›Ð¸Ñ†ÐµÐ²Ð°Ñ Ð¡Ñ‚Ð¾Ñ€Ð¾Ð½Ð°' if lang=='ru' else _(u"Card's &FrontSide"))
7003 show_question_auction.setShortcut(QKeySequence(HOTKEY['F7_FrontSide'][0]))
7004 if A['ANKI_MENU_ICONS'][0]:
7005 show_question_auction.setIcon(PageUp_icon)
7006 mw.connect(show_question_auction, SIGNAL("triggered()"), go_question)
7007
7008 show_answer_auction = QAction(mw)
7009 show_answer_auction.setText(u'&ÐžÐ±Ð¾Ñ€Ð¾Ñ‚Ð½Ð°Ñ Ð¡Ñ‚Ð¾Ñ€Ð¾Ð½Ð°' if lang=='ru' else _(u"Card's &BackSide"))
7010 show_answer_auction.setShortcut(QKeySequence(HOTKEY['F8_BackSide'][0]))
7011 if A['ANKI_MENU_ICONS'][0]:
7012 show_answer_auction.setIcon(PageDown_icon)
7013 mw.connect(show_answer_auction, SIGNAL("triggered()"), go_answer)
7014
7015 mw.form.menuEdit.addSeparator()
7016 mw.form.menuEdit.addAction(show_question_auction)
7017 #edit_menu.addAction(onesided_action)
7018 # U can't do menu twice (or more) with the same action
7019 # -- on additional icons panel it's OK --
7020 mw.form.menuEdit.addAction(show_answer_auction)
7021 mw.form.menuEdit.addSeparator()
7022
7023if A['SWAP_FRONT_BACK'][0]:
7024 mw.form.menuEdit.addAction(swap_action)
7025 mw.form.menuEdit.addSeparator()
7026
7027#########################################################
7028#
7029if mw_addon_view_menu_exists and A['F9_HINT_PEEKING'][0]:
7030
7031 F9_HINT_PEEKING_action = QAction(mw)
7032 F9_HINT_PEEKING_action.setText("Пока&зать Ñледующую подÑказку" if lang == 'ru' else "Show Next Hint")
7033 if A['ANKI_MENU_ICONS'][0]:
7034 F9_HINT_PEEKING_action.setIcon(QIcon(os.path.join(MUSTHAVE_COLOR_ICONS, 'hint.png')))
7035 F9_HINT_PEEKING_action.setShortcut(HOTKEY['next_hint'][0]) # Ctrl+H,F9 press ^H then F9 voila # Control+H OK
7036 mw.connect(F9_HINT_PEEKING_action, SIGNAL("triggered()"), on_showHint)
7037
7038 show_all_hints_action = QAction(mw)
7039 show_all_hints_action.setText("Показать &вÑе подÑказки" if lang == 'ru' else "Show All Hints")
7040 if A['ANKI_MENU_ICONS'][0]:
7041 show_all_hints_action.setIcon(QIcon(os.path.join(MUSTHAVE_COLOR_ICONS, 'envelope.png')))
7042 show_all_hints_action.setShortcut(HOTKEY['all_hints'][0]) # Ctrl+Shift+H does not work anymore
7043 mw.connect(show_all_hints_action, SIGNAL("triggered()"), on_showAllHints)
7044
7045 mw.addon_view_menu.addSeparator()
7046 mw.addon_view_menu.addAction(F9_HINT_PEEKING_action)
7047 mw.addon_view_menu.addAction(show_all_hints_action)
7048 mw.addon_view_menu.addSeparator()
7049
7050######################################################################
7051# Expand and Collapse Decks
7052# https://ankiweb.net/shared/info/2554066128
7053# Creates two options in the Tools menu to expand and collapse all the decks in the main window
7054# Obviously it must be placed in view menu, not tools.
7055
7056def toggleDecksCollapsed(collapsed):
7057 "Toggles all decks to be either expanded or collapsed, based upon the boolean parameter passed in "
7058 # get all the decks and set them collapsed/expanded
7059 deckids = mw.col.decks.allIds()
7060 for deckid in deckids:
7061 deck = mw.col.decks.get(deckid)
7062 deck['collapsed'] = collapsed
7063 mw.col.decks.save(deck)
7064
7065 # Refresh the browser to show the changes
7066 mw.deckBrowser.refresh()
7067
7068def expandAllDecks():
7069 toggleDecksCollapsed(False)
7070
7071def collapseAllDecks():
7072 toggleDecksCollapsed(True)
7073
7074if A['EXPAND_AND_COLLAPSE_DECKS'][0]:
7075
7076 expandAction = QAction("&Развернуть вÑе колоды" if lang == 'ru' else "Expand &All Decks", mw)
7077 expandAction.setShortcut(QKeySequence(HOTKEY['expand_decks'][0]))
7078 if A['ANKI_MENU_ICONS'][0]:
7079 expandAction.setIcon(QIcon(os.path.join(MUSTHAVE_COLOR_ICONS, 'plus.png')))
7080 mw.connect(expandAction, SIGNAL("triggered()"), expandAllDecks)
7081 #mw.form.menuTools.addAction(expandAction)
7082
7083 collapseAction = QAction("&Свернуть вÑе колоды" if lang == 'ru' else "Collapse All &Decks", mw)
7084 collapseAction.setShortcut(QKeySequence(HOTKEY['collapse_decks'][0]))
7085 if A['ANKI_MENU_ICONS'][0]:
7086 collapseAction.setIcon(QIcon(os.path.join(MUSTHAVE_COLOR_ICONS, 'minus.png')))
7087 mw.connect(collapseAction, SIGNAL("triggered()"), collapseAllDecks)
7088 #mw.form.menuTools.addAction(collapseAction)
7089
7090 if mw_addon_view_menu_exists:
7091 mw.addon_view_menu.addSeparator()
7092 mw.addon_view_menu.addAction(expandAction)
7093 mw.addon_view_menu.addAction(collapseAction)
7094 mw.addon_view_menu.addSeparator()
7095
7096#####################################################
7097#
7098if A['ONESIDED_CARDS'][0]:
7099
7100 onesided_action = QAction(mw)
7101 onesided_action.setText(u'Показывать &Только Оборотные Стороны' if lang=='ru' else _(u"Show BackSides &Only"))
7102 onesided_action.setIcon(QIcon(os.path.join(MUSTHAVE_COLOR_ICONS, 'onesided.png')))
7103 onesided_action.setCheckable(True)
7104 onesided_action.setChecked(False)
7105
7106if A['FLIP_FLOP'][0]:
7107
7108 show_question_aukcion = QAction(mw)
7109 show_question_aukcion.setText(u'Перейти на &Лицевую Сторону' if lang=='ru' else _(u"Show &FrontSide"))
7110 show_question_aukcion.setIcon(PageUp_icon)
7111 #show_question_aukcion.setVisible(True)
7112 mw.connect(show_question_aukcion, SIGNAL("triggered()"), go_question)
7113
7114 show_answer_aukcion = QAction(mw)
7115 show_answer_aukcion.setText(u'Перейти на &Оборотную Сторону' if lang=='ru' else _(u"Show &BackSide"))
7116 show_answer_aukcion.setIcon(PageDown_icon)
7117 mw.connect(show_answer_aukcion, SIGNAL("triggered()"), go_answer)
7118
7119 show_question_action = QAction(mw)
7120 show_question_action.setText(u'Перейти на &Лицевую Сторону' if lang=='ru' else _(u"Show &FrontSide"))
7121 show_question_action.setShortcut(HOTKEY['FrontPack'][0])
7122 show_question_action.setIcon(PageUp_icon)
7123 #show_question_action.setVisible(True)
7124 mw.connect(show_question_action, SIGNAL("triggered()"), go_question)
7125
7126 show_answer_action = QAction(mw)
7127 show_answer_action.setText(u'Перейти на &Оборотную Сторону' if lang=='ru' else _(u"Show &BackSide"))
7128 show_answer_action.setShortcut(HOTKEY['BackPack'][0])
7129 show_answer_action.setIcon(PageDown_icon)
7130 mw.connect(show_answer_action, SIGNAL("triggered()"), go_answer)
7131
7132 if mw_addon_view_menu_exists:
7133
7134 if A['NUMERIC_KEYPAD_REMAPPING'][0]:
7135 show_question_aktion = QAction(mw)
7136 show_question_aktion.setText(u'Показать &Лицевую Сторону' if lang=='ru' else _(u"Show &FrontSide"))
7137 show_question_aktion.setShortcut(HOTKEY['FrontPage'][0])
7138 if A['ANKI_MENU_ICONS'][0]:
7139 show_question_aktion.setIcon(PageUp_icon)
7140 mw.connect(show_question_aktion, SIGNAL("triggered()"), go_question)
7141
7142 show_answer_aktion = QAction(mw)
7143 show_answer_aktion.setText(u'Показать &Оборотную Сторону' if lang=='ru' else _(u"Show &BackSide"))
7144 show_answer_aktion.setShortcut(HOTKEY['BackPage'][0])
7145 if A['ANKI_MENU_ICONS'][0]:
7146 show_answer_aktion.setIcon(PageDown_icon)
7147 mw.connect(show_answer_aktion, SIGNAL("triggered()"), go_answer)
7148
7149 mw.addon_view_menu.addSeparator()
7150 mw.addon_view_menu.addAction(show_question_aktion)
7151 mw.addon_view_menu.addAction(show_answer_aktion)
7152 mw.addon_view_menu.addSeparator()
7153
7154if A['FLIP_FLOP'][0]:
7155
7156 show_question_auktion = QAction(mw)
7157 show_question_auktion.setText(u'&Ð›Ð¸Ñ†ÐµÐ²Ð°Ñ Ð¡Ñ‚Ð¾Ñ€Ð¾Ð½Ð° карточки' if lang=='ru' else _(u"Show &FrontSide"))
7158 show_question_auktion.setShortcut(HOTKEY['FrontSide'][0])
7159 if A['ANKI_MENU_ICONS'][0]:
7160 show_question_auktion.setIcon(PageUp_icon)
7161 mw.connect(show_question_auktion, SIGNAL("triggered()"), go_question)
7162
7163 show_answer_auktion = QAction(mw)
7164 show_answer_auktion.setText(u'&ÐžÐ±Ð¾Ñ€Ð¾Ñ‚Ð½Ð°Ñ Ð¡Ñ‚Ð¾Ñ€Ð¾Ð½Ð° карточки' if lang=='ru' else _(u"Show &BackSide"))
7165 show_answer_auktion.setShortcut(HOTKEY['BackSide'][0])
7166 if A['ANKI_MENU_ICONS'][0]:
7167 show_answer_auktion.setIcon(PageDown_icon)
7168 mw.connect(show_answer_auktion, SIGNAL("triggered()"), go_answer)
7169
7170if A['COLORFUL_TOOLBAR'][0]:
7171
7172 show_question_aukcion = QAction(mw)
7173 show_question_aukcion.setText(u'&Ð›Ð¸Ñ†ÐµÐ²Ð°Ñ Ð¡Ñ‚Ð¾Ñ€Ð¾Ð½Ð° карточки' if lang=='ru' else _(u"Show &FrontSide"))
7174 show_question_aukcion.setIcon(PageUp_icon)
7175 mw.connect(show_question_aukcion, SIGNAL("triggered()"), go_question)
7176
7177 show_answer_aukcion = QAction(mw)
7178 show_answer_aukcion.setText(u'&ÐžÐ±Ð¾Ñ€Ð¾Ñ‚Ð½Ð°Ñ Ð¡Ñ‚Ð¾Ñ€Ð¾Ð½Ð° карточки' if lang=='ru' else _(u"Show &BackSide"))
7179 show_answer_aukcion.setIcon(PageDown_icon)
7180 mw.connect(show_answer_aukcion, SIGNAL("triggered()"), go_answer)
7181
7182if A['FLIP_FLOP'][0] and A['COLORFUL_TOOLBAR'][0] and mw_addon_cards_menu_exists:
7183
7184 mw.addon_cards_menu.addSeparator()
7185 mw.addon_cards_menu.addAction(show_question_auktion)
7186 mw.addon_cards_menu.addAction(show_answer_auktion)
7187 mw.addon_cards_menu.addSeparator()
7188
7189if (A['FLIP_FLOP'][0] or A['ONESIDED_CARDS'][0]) and A['COLORFUL_TOOLBAR'][0]:
7190
7191 mw.qt_tool_bar.addSeparator()
7192 if A['FLIP_FLOP'][0]:
7193 mw.qt_tool_bar.addAction(show_question_aukcion)
7194 if A['ONESIDED_CARDS'][0]:
7195 mw.qt_tool_bar.addAction(onesided_action)
7196 if A['FLIP_FLOP'][0]:
7197 mw.qt_tool_bar.addAction(show_answer_aukcion)
7198 mw.qt_tool_bar.addSeparator()
7199
7200if mw_addon_go_menu_exists and (A['FLIP_FLOP'][0] or A['ONESIDED_CARDS'][0]):
7201 mw.addon_go_menu.addSeparator()
7202 if A['FLIP_FLOP'][0]:
7203 mw.addon_go_menu.addAction(show_question_action)
7204 if A['ONESIDED_CARDS'][0]:
7205 mw.addon_go_menu.addAction(onesided_action)
7206 if A['FLIP_FLOP'][0]:
7207 mw.addon_go_menu.addAction(show_answer_action)
7208 mw.addon_go_menu.addSeparator()
7209
7210#################################
7211#
7212mw.form.menuTools.addSeparator()
7213
7214##########################################################################
7215# Card Browser Lookup
7216# https://ankiweb.net/shared/info/869824347
7217
7218"""
7219Simple addon to quickly look up words in Anki's Browser
7220
7221Copyright: Glutanimate 2015 (https://github.com/Glutanimate)
7222Based on 'OSX Dictionary Lookup' by Eddie Blundell <eblundell@gmail.com>:
7223https://gist.github.com/eddie/ff3d820fb267ae26ca0e
7224
7225License: The MIT License (MIT)
7226"""
7227
7228class BrowserLookup:
7229
7230 def get_selected(self, view):
7231 """Copy selected text"""
7232 return view.page().selectedText()
7233
7234 def lookup_action(self, view):
7235 browser = aqt.dialogs.open("Browser", aqt.mw)
7236 browser.form.searchEdit.lineEdit().setText(self.get_selected(view))
7237 browser.onSearch()
7238
7239 def add_action(self, view, menu, action):
7240 """Add 'lookup' action to context menu."""
7241 if self.get_selected(view):
7242 action = menu.addAction(action)
7243 action.connect(action, SIGNAL('triggered()'),
7244 lambda view=view: self.lookup_action(view))
7245
7246 def context_lookup_action(self, view, menu):
7247
7248 edit_current_action = menu.addAction(u'Редактирование' if lang=='ru' else _(u"Edit"))
7249 edit_current_action.connect(edit_current_action, SIGNAL("triggered()"), go_edit_current)
7250
7251 more_menu = QMenu(u'Ещё' if lang=='ru' else _(u"More"),menu)
7252 menu.addMenu(more_menu)
7253
7254 for row in opts:
7255 if not row:
7256 more_menu.addSeparator()
7257 continue
7258 label, scut, func = row
7259 a = more_menu.addAction(label)
7260 a.setShortcut(QKeySequence(scut))
7261 a.connect(a, SIGNAL("triggered()"), func)
7262
7263 """Browser Lookup action"""
7264 if A['SEARCH_BROWSER'][0]:
7265 #menu.addSeparator()
7266 self.add_action(view,menu,(u'ПоиÑк в Обозревателе Anki: %s...' if lang == 'ru' else 'Search Anki Browser for %s...')%self.get_selected(view)[:20])
7267
7268# Add lookup actions to context menu
7269if not B['B13_EDIT_MORE'][0] or A['SEARCH_BROWSER'][0]:
7270 browser_lookup = BrowserLookup()
7271 addHook("AnkiWebView.contextMenuEvent", browser_lookup.context_lookup_action)
7272
7273##################################################################
7274# Search Google Images for selected words
7275# https://ankiweb.net/shared/info/800190862
7276
7277"""
7278Adds Search For Selected Text to the Reviewer Window's context/popup menu
7279
7280Copyright: Steve AW <steveawa@gmail.com>
7281License: GNU GPL, version 3 or later; http://www.gnu.org/copyleft/gpl.html
7282
7283Note: AnkiWebView.contextMenuEvent is replaced, but only to add a hook
7284which should make it easy to maintain.
7285
7286Support: Use at your own risk. If you do find a problem please email me
7287or use one the following forums, however there are certain periods
7288throughout the year when I will not have time to do any work on
7289these addons.
7290
7291Github page: https://github.com/steveaw/anki_addons
7292Anki addons: https://groups.google.com/forum/?hl=en#!forum/anki-addons
7293"""
7294
7295SEARCH_URL = 'https://www.google.com/search?tbm=isch&q=%s'
7296
7297def selected_text_as_query(web_view):
7298 sel = web_view.page().selectedText()
7299 return " ".join(sel.split())
7300
7301def on_search_for_selection_GOOGLE(web_view):
7302 sel_encode = selected_text_as_query(web_view).encode('utf8', 'ignore')
7303 #need to do this the long way around to avoid double % encoding
7304 url = QUrl.fromEncoded(SEARCH_URL % (urllib.quote(sel_encode)))
7305 #openLink(SEARCH_URL + sel_encode)
7306 QDesktopServices.openUrl(url)
7307
7308def contextMenuEvent(self, evt):
7309 # lazy: only run in reviewer
7310 if aqt.mw.state != "review":
7311 return
7312 m = QMenu(self)
7313 a = m.addAction(_("Copy"))
7314 a.connect(a, SIGNAL("triggered()"),
7315 lambda: self.triggerPageAction(QWebPage.Copy))
7316 #Only change is the following statement
7317 runHook("AnkiWebView.contextMenuEvent",self,m)
7318 m.popup(QCursor.pos())
7319
7320def insert_search_menu_action(anki_web_view,m):
7321 selected = selected_text_as_query(anki_web_view)
7322 if len(selected)>0:
7323 truncated = (selected[:40] + '...') if len(selected) > 40 else selected
7324 m.addSeparator()
7325 a = m.addAction(('ИÑкать картинки "%s" в %s ' if lang == 'ru' else 'Search images of "%s" on %s ') \
7326 % (truncated, 'Google Images'))
7327 a.connect(a, SIGNAL("triggered()"),
7328 lambda wv=anki_web_view: on_search_for_selection_GOOGLE(wv))
7329
7330##################################################################
7331# Get context sentence for language learning -Tatoeba lookup on right-click
7332# https://ankiweb.net/shared/info/800190862
7333
7334# Stupid hack by me, to mimic https://ankiweb.net/shared/info/798922495
7335# in order to show German -> English sentences (to provide more context for a given word/phrase).
7336# Defaults are setup to look for German->English sentences.
7337# You could open the SEARCH_TATOEBA_for_selected_text_in_Reviewer python file
7338# in your Anki addons folder and modify FROM_LNG and TO_LNG
7339# in order to get any languages supported for the look up.
7340# FROM_LNG = 'deu'
7341# TO_LNG = 'eng'
7342
7343"""
7344Adds Search For Selected Text to the Reviewer Window's context/popup menu
7345
7346Copyright: Steve AW <steveawa@gmail.com>
7347License: GNU GPL, version 3 or later; http://www.gnu.org/copyleft/gpl.html
7348Changed to work with Tatoeba: <tatoeba_addon@chthonic.co.za>
7349
7350Note: AnkiWebView.contextMenuEvent is replaced, but only to add a hook
7351which should make it easy to maintain.
7352
7353Support: Use at your own risk. If you do find a problem please email me
7354or use one the following forums, however there are certain periods
7355throughout the year when I will not have time to do any work on
7356these addons.
7357
7358Github page: https://github.com/steveaw/anki_addons
7359Anki addons: https://groups.google.com/forum/?hl=en#!forum/anki-addons
7360"""
7361
7362def on_search_for_selection_TATOEBA(web_view, vw):
7363 url = QUrl.fromEncoded(vw)
7364 QDesktopServices.openUrl(url)
7365
7366def search_menu_action(anki_web_view,m,selected,server,search_url):
7367 truncated = (selected[:40] + '...') if len(selected) > 40 else selected
7368 if len(selected)>0:
7369 a = m.addAction(('ИÑкать "%s" на %s ' if lang == 'ru' else 'Search for "%s" on %s ') % (truncated, server))
7370 a.connect(a, SIGNAL("triggered()"),
7371 lambda wv=anki_web_view: on_search_for_selection_TATOEBA(wv, search_url % (urllib.quote(selected_text_as_query(wv).encode('utf8', 'ignore')))))
7372
7373def wiki_menu_action(anki_web_view,m,selected,server,search_url,lng):
7374 truncated = (selected[:40] + '...') if len(selected) > 40 else selected
7375 if len(selected)>0:
7376 a = m.addAction(('Ðайти "%s" на %s (%s) ' if lang == 'ru' else 'Look up for "%s" on %s (%s)') % (truncated, server, lng))
7377 a.connect(a, SIGNAL("triggered()"),
7378 lambda wv=anki_web_view: on_search_for_selection_TATOEBA(wv, search_url % (lng, urllib.quote(selected_text_as_query(wv).encode('utf8', 'ignore')))))
7379
7380# yandex.com
7381def yandex_menu_action(anki_web_view,m,selected,server,search_url,lng,lng2):
7382 truncated = (selected[:40] + '...') if len(selected) > 40 else selected
7383 if len(selected)>0:
7384 a = m.addAction(('ЗаглÑнуть в %s в поиÑках "%s" (%s-%s) ' if lang == 'ru' else 'Look up on %s for "%s" (%s-%s)') % (server, truncated, lng, lng2))
7385 a.connect(a, SIGNAL("triggered()"),
7386 lambda wv=anki_web_view: on_search_for_selection_TATOEBA(wv, search_url % ( urllib.quote(selected_text_as_query(wv).encode('utf8', 'ignore')),lng,lng2)))
7387
7388def translate_menu_action(anki_web_view,m,selected,server,search_url,from_lng,to_lng,dir=""):
7389 truncated = (selected[:40] + '...') if len(selected) > 40 else selected
7390 if len(selected)>0:
7391 if dir=="" or dir=="FROM":
7392 a = m.addAction(('Перевод "%s" на %s (%s->%s) ' if lang == 'ru' else 'Translate for "%s" on %s (%s->%s) ') % (truncated, server, from_lng, to_lng))
7393 a.connect(a, SIGNAL("triggered()"),
7394 lambda wv=anki_web_view: on_search_for_selection_TATOEBA(wv, search_url % (from_lng,to_lng,urllib.quote(selected_text_as_query(wv).encode('utf8', 'ignore')))))
7395 if dir=="" or dir=="TO":
7396 a = m.addAction(('ПеревеÑти "%s" на %s (%s->%s) ' if lang == 'ru' else 'Translation for "%s" on %s (%s->%s) ') % (truncated, server, to_lng, from_lng))
7397 a.connect(a, SIGNAL("triggered()"),
7398 lambda wv=anki_web_view: on_search_for_selection_TATOEBA(wv, search_url % (to_lng,from_lng,urllib.quote(selected_text_as_query(wv).encode('utf8', 'ignore')))))
7399
7400def insert_search_menu_action_TATOEBA(anki_web_view, m):
7401 selected = selected_text_as_query(anki_web_view)
7402 if len(selected)>0:
7403 trans_menu = QMenu(u'Словари на Ñайтах' if lang=='ru' else u'Dictionaries online',m)
7404 m.addMenu(trans_menu)
7405
7406 #translate_menu_action(anki_web_view, trans_menu, selected, 'Lingvo', 'http://www.lingvo.ua/ru/Translate/%s-%s/%s', FROM_LANG, TO_LANG) http://www.lingvo-online.ru/ru/Translate/en-ru/appliance
7407
7408 # 'https://slovari.yandex.ru/%s/%s/'
7409 yandex_menu_action(anki_web_view, trans_menu, selected, 'ЯндекÑ.Словари', 'https://slovari.yandex.ru/%s/%s-%s', TO_LANG, FROM_LANG)
7410 trans_menu.addSeparator()
7411
7412 translate_menu_action(anki_web_view, trans_menu, selected, 'Lingvo', 'http://www.lingvo-online.ru/ru/Translate/%s-%s/%s', FROM_LANG, TO_LANG)
7413 trans_menu.addSeparator()
7414
7415 wiki_menu_action(anki_web_view, trans_menu, selected, 'OLD', 'http://www.oxfordlearnersdictionaries.com/definition/%s/%s', FROM_LANGUAGE)
7416 wiki_menu_action(anki_web_view, trans_menu, selected, 'Oxford', 'http://www.oxfordlearnersdictionaries.com/definition/%s/%s', TO_LANGUAGE)
7417 trans_menu.addSeparator()
7418
7419 search_menu_action(anki_web_view, trans_menu, selected, 'LDOCE', 'http://www.ldoceonline.com/search/?q=%s')
7420 search_menu_action(anki_web_view, trans_menu, selected, 'MW', 'http://www.merriam-webster.com/dictionary/%s')
7421 trans_menu.addSeparator()
7422
7423 wiki_menu_action(anki_web_view, trans_menu, selected, 'CDO', 'http://dictionary.cambridge.org/dictionary/%s/%s', TO_LANGUAGE)
7424 translate_menu_action(anki_web_view, trans_menu, selected, 'CDO', 'http://dictionary.cambridge.org/dictionary/%s-%s/%s', FROM_LANGUAGE, TO_LANGUAGE)
7425
7426 search_menu_action(anki_web_view, m, selected, 'Google', 'https://www.google.com/search?q=%s')
7427
7428 triad_menu = QMenu(u'Триада: ИÑкать Перевод ТатоÑба' if lang=='ru' else _(u"Triad: Search Translation Tatoeba"),m)
7429 m.addMenu(triad_menu)
7430
7431 search_menu_action(anki_web_view, triad_menu, selected, 'Google', 'https://www.google.co.uk/search?q=%s')
7432 translate_menu_action(anki_web_view, triad_menu, selected, 'Google', 'https://translate.google.com/?sl=%s&tl=%s&q=%s', FROM_LANG, TO_LANG)
7433 triad_menu.addSeparator()
7434 search_menu_action(anki_web_view, triad_menu, selected, 'Yandex', 'https://yandex.ru/yandsearch?text=%s')
7435 translate_menu_action(anki_web_view, triad_menu, selected, 'Yandex', 'https://translate.yandex.ru/?lang=%s-%s&text=%s', FROM_LANG, TO_LANG)
7436 triad_menu.addSeparator()
7437 search_menu_action(anki_web_view, triad_menu, selected, 'Bing', 'https://www.bing.com/search?q=%s')
7438 translate_menu_action(anki_web_view, triad_menu, selected, 'Bing', 'https://www.bing.com/translator/?from=%s&to=%s&text=%s', FROM_LANG, TO_LANG)
7439 triad_menu.addSeparator()
7440 translate_menu_action(anki_web_view, triad_menu, selected, 'TATOEBA',
7441 'http://tatoeba.org/eng/sentences/search?from=%s&to=%s&query=%s', FROM_LNG, TO_LNG)
7442 triad_menu.addSeparator()
7443
7444 trans_menu.addSeparator()
7445 wiki_menu_action(anki_web_view, trans_menu, selected, 'The FREE Dictionary', 'http://%s.thefreedictionary.com/%s', FROM_LANG)
7446 wiki_menu_action(anki_web_view, trans_menu, selected, 'The FREE Dictionary', 'http://%s.thefreedictionary.com/%s', TO_LANG)
7447
7448 translate_menu_action(anki_web_view, m, selected, 'Yandex', 'https://translate.yandex.ru/?lang=%s-%s&text=%s', FROM_LANG, TO_LANG, dir="TO")
7449
7450 wiki_main_menu = QMenu(u"ПоиÑк по любой вики..." if lang=="ru" else u"Search any wiki...", m)
7451 m.addMenu(wiki_main_menu)
7452
7453 wiki_menu_action(anki_web_view, wiki_main_menu, selected, 'ВикипедиÑ', 'https://%s.wikipedia.org/wiki/%s', FROM_LANG)
7454 wiki_menu_action(anki_web_view, wiki_main_menu, selected, 'Wikipedia', 'https://%s.wikipedia.org/wiki/%s', TO_LANG)
7455
7456 wiki_menu = QMenu((u'ПоиÑк по оÑтальным вики (%s)' % FROM_LANG) if lang=='ru' else (u"Search wiki sites (%s)" % FROM_LANG), wiki_main_menu)
7457 wiki_main_menu.addMenu(wiki_menu)
7458
7459 wiki_menu_action(anki_web_view, wiki_menu, selected, 'wiktionary', 'https://%s.wiktionary.org/wiki/Special:Search/%s', FROM_LANG)
7460 wiki_menu_action(anki_web_view, wiki_menu, selected, 'wikibooks', 'https://%s.wikibooks.org/wiki/Special:Search/%s', FROM_LANG)
7461 wiki_menu_action(anki_web_view, wiki_menu, selected, 'wikiquote', 'https://%s.wikiquote.org/wiki/Special:Search/%s', FROM_LANG)
7462 wiki_menu_action(anki_web_view, wiki_menu, selected, 'wikisource', 'https://%s.wikisource.org/wiki/Special:Search/%s', FROM_LANG)
7463 wiki_menu_action(anki_web_view, wiki_menu, selected, 'wikiversity', 'https://%s.wikiversity.org/wiki/Special:Search/%s', FROM_LANG)
7464 wiki_menu_action(anki_web_view, wiki_menu, selected, 'wikivoyage', 'https://%s.wikivoyage.org/wiki/Special:Search/%s', FROM_LANG)
7465 wiki_menu_action(anki_web_view, wiki_menu, selected, 'wikinews', 'https://%s.wikinews.org/wiki/Special:Search/%s', FROM_LANG)
7466
7467 viki_menu = QMenu(u"Search wiki sites (%s)" % TO_LANG, wiki_main_menu)
7468 wiki_main_menu.addMenu(viki_menu)
7469
7470 wiki_menu_action(anki_web_view, viki_menu, selected, 'wiktionary', 'https://%s.wiktionary.org/wiki/Special:Search/%s', TO_LANG)
7471 wiki_menu_action(anki_web_view, viki_menu, selected, 'wikibooks', 'https://%s.wikibooks.org/wiki/Special:Search/%s', TO_LANG)
7472 wiki_menu_action(anki_web_view, viki_menu, selected, 'wikiquote', 'https://%s.wikiquote.org/wiki/Special:Search/%s', TO_LANG)
7473 wiki_menu_action(anki_web_view, viki_menu, selected, 'wikisource', 'https://%s.wikisource.org/wiki/Special:Search/%s', TO_LANG)
7474 wiki_menu_action(anki_web_view, viki_menu, selected, 'wikiversity', 'https://%s.wikiversity.org/wiki/Special:Search/%s', TO_LANG)
7475 wiki_menu_action(anki_web_view, viki_menu, selected, 'wikivoyage', 'https://%s.wikivoyage.org/wiki/Special:Search/%s', TO_LANG)
7476 wiki_menu_action(anki_web_view, viki_menu, selected, 'wikinews', 'https://%s.wikinews.org/wiki/Special:Search/%s', TO_LANG)
7477
7478 wiki_menu_action(anki_web_view, wiki_main_menu, selected, 'wikidata', 'https://%s.wikidata.org/wiki/Special:Search/%s', 'www')
7479 wiki_menu_action(anki_web_view, wiki_main_menu, selected, 'wikimedia', 'https://%s.wikimedia.org/wiki/Special:Search/%s', 'commons')
7480
7481 m.addSeparator()
7482
7483if A['SEARCH_AND_TRANSLATE'][0]:
7484 AnkiWebView.contextMenuEvent = contextMenuEvent
7485 addHook("AnkiWebView.contextMenuEvent", insert_search_menu_action)
7486 AnkiWebView.contextMenuEvent = contextMenuEvent
7487 addHook("AnkiWebView.contextMenuEvent", insert_search_menu_action_TATOEBA)
7488
7489#########################################################################
7490# Maximum images height in card editor
7491# https://ankiweb.net/shared/info/229181581
7492
7493# Copyright (C) 2015 by Simone Gaiarin <simgunz@gmail.com> #
7494# #
7495# This program is free software; you can redistribute it and/or modify #
7496# it under the terms of the GNU General Public License as published by #
7497# the Free Software Foundation; either version 3 of the License, or #
7498# (at your option) any later version. #
7499# #
7500# This program is distributed in the hope that it will be useful, #
7501# but WITHOUT ANY WARRANTY; without even the implied warranty of #
7502# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
7503# GNU General Public License for more details. #
7504# #
7505# You should have received a copy of the GNU General Public License #
7506# along with this program; if not, see <http://www.gnu.org/licenses/>. #
7507
7508if A['MAXHEIGHT'][0]: # = '200px'
7509 oldImgStyle = "img { max-width: 90%%; }"
7510 newImgStyle = "img { max-width: 90%%%%; max-height: %s; }" % A['MAXHEIGHT'][0]
7511 editor._html = editor._html.replace(oldImgStyle, newImgStyle)
7512
7513##################################################################
7514# Search from Editor
7515# https://ankiweb.net/shared/info/1559436729
7516
7517# Copyright: itraveller, 2014
7518# License: GNU AGPL v.3 or later
7519
7520'''
7521This script is an add-on for Anki 2.0.28
7522
7523Synopsis:
7524=========
7525This add-on is designed to search directly from the editor area, that is
7526particularly useful to quickly find:
75271) duplicates of the added note
75282) synonyms and homonyms of the added word
7529
7530After installing you will find the 'S' button in the editor tool bar. When you
7531press it the entire contents of the active field (or its highlighted part) will
7532be sent to search box of the Anki browser. The search query looks like:
75331) "<field name:><field content or its highlighted part>"
75342) "<field name:>*<highlighted part>*"
7535
7536Push the button while holding the 'Shift' if you want to search without
7537specifying the field name.
7538'''
7539
7540import aqt.forms
7541
7542def search_FE(self):
7543 '''Send the field content into the search box of the browser'''
7544 # check status of 'shift' key
7545 shift_key = QApplication.keyboardModifiers() == Qt.ShiftModifier
7546 # prepare search query
7547 selection = self.web.selectedText()
7548 fields = self.note.items()
7549 field_name, field_value = fields[self.currentField]
7550 content = '*' + selection + '*' if selection else field_value
7551 tag = '' if shift_key else field_name + ':'
7552 query = '"' + tag + content + '"'
7553 # invoke browser
7554 browser = aqt.dialogs.open("Browser", self.mw)
7555 browser.form.searchEdit.lineEdit().setText(query)
7556 browser.onSearch()
7557
7558def button_FE(self):
7559 '''Create the 'S' button in the editor tool bar.'''
7560 self._addButton("mybutton", lambda s = self: search_FE(self), \
7561 text = u"F", tip = "ИÑкать в Обозревателе Anki ("+HOTKEYS['FE'][0]+")" if lang == 'ru' else "Find in Anki browser ("+HOTKEYS['FE'][0]+")", key = HOTKEYS['FE'][0])
7562
7563if A['SEARCH_FROM_EDITOR'][0]:
7564 Editor.search = search_FE
7565 addHook("setupEditorButtons", button_FE)
7566
7567##################################################################
7568# Reset card scheduling information / progress
7569# https://ankiweb.net/shared/info/1432861881
7570
7571# Copyright: Jeffrey Baitis <jeff@baitis.net>
7572# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
7573
7574# Col is a collection of cards, cids are the ids of the cards to reset.
7575if A['RESET_CARD_SCHEDULING'][0]:
7576
7577 def resetSelectedCardScheduling(self):
7578 """ Resets statistics for selected cards, and removes them from learning queues. """
7579 cids = self.selectedCards()
7580 if not cids:
7581 return
7582 # Allow undo
7583 self.mw.checkpoint(_("Reset scheduling and learning on selected cards"))
7584 self.mw.progress.start(immediate=True)
7585 # Not sure if beginReset is required
7586 self.model.beginReset()
7587
7588 # Removes card from learning queues
7589 self.col.sched.removeLrn(cids) # removeLrn call forgetCards by itself
7590 # Removes card from dynamic deck?
7591 self.col.sched.remFromDyn(cids)
7592 # Resets selected cards in current collection
7593 self.col.sched.resetCards(cids)
7594
7595 self.model.endReset()
7596 self.mw.progress.finish()
7597 # Update the main UI window to reflect changes in card status
7598 self.mw.reset()
7599
7600 def addMenuItem(self):
7601 """ Adds hook to the Edit menu in the note browser """
7602 action = QAction("Ð¡Ð±Ñ€Ð¾Ñ Ñ€Ð°ÑпиÑÐ°Ð½Ð¸Ñ Ð¸ проÑмотров выбранных карточек" if lang == 'ru' else "Reset scheduling and learning on selected cards", self)
7603 self.resetSelectedCardScheduling = resetSelectedCardScheduling
7604 self.connect(action, SIGNAL("triggered()"), lambda s=self: resetSelectedCardScheduling(self))
7605 self.form.menuEdit.addAction(action)
7606
7607 # Add-in hook; called by the AQT Browser object when it is ready for the add-on to modify the menus
7608 addHook('browser.setupMenus', addMenuItem)
7609
7610 # TODO: Eventually, this add-on should be able to be invoked from the deck browser
7611 # addHook('deckbrowser.setupOptions', addOptionsItem)
7612
7613##################################################################
7614# anki-master\aqt\addons.py
7615# Monkey Patching
7616# showInfo -> tooltip
7617
7618from aqt.addons import AddonManager, GetAddons
7619from aqt.downloader import download
7620
7621if A['ADDONS_INSTALL_TOOLTIP'][0]:
7622
7623 def _accept1(self):
7624 #go_AnkiWeb_addons() # This way starts after dialog window were closed, not before.
7625 QDialog.accept(self)
7626 # create downloader thread
7627 ret = download(self.mw, self.form.code.text())
7628 if not ret:
7629 return
7630 data, fname = ret
7631 self.mw.addonManager.install(data, fname)
7632 self.mw.progress.finish()
7633 tooltip(_("Download successful. Please restart Anki."),period=6000)
7634 #aqt.addons.AddonManager.onGetAddons(self.mw.addonManager) # TODO
7635
7636 def _accepts(self):
7637 _accept1(self)
7638 aqt.addons.AddonManager.onGetAddons(self.mw.addonManager)
7639
7640 aqt.addons.GetAddons.accept = _accept1
7641
7642##################################################################
7643# Force custom font
7644# https://ankiweb.net/shared/info/2103013902
7645
7646# Copyright: Damien Elmes <anki@ichi2.net>
7647# License: GNU GPL, version 3 or later; http://www.gnu.org/copyleft/gpl.html
7648
7649def changeFont():
7650 f = QFontInfo(QFont(A['FONT'][0]))
7651 ws = QWebSettings.globalSettings()
7652 mw.fontHeight = A['FONTSIZE'][0] if A['FONTSIZE'][0] else f.pixelSize()
7653 mw.fontFamily = f.family()
7654 mw.fontHeightDelta = max(0, mw.fontHeight - 13)
7655 ws.setFontFamily(QWebSettings.StandardFont, mw.fontFamily)
7656 ws.setFontSize(QWebSettings.DefaultFontSize, mw.fontHeight)
7657 mw.reset()
7658
7659if A['FONT'][0]:
7660 changeFont()
7661
7662##################################################################
7663# ONESIDED_CARDS.py
7664# Copyright © 2012 Roland Sieker, <ospalh@gmail.com>
7665# License: GNU GPL, version 3 or later; http://www.gnu.org/copyleft/gpl.html
7666
7667# monkey patch
7668
7669def maybe_skip_question(self):
7670 model = mw.reviewer.card.model()
7671 #if model['type'] != MODEL_STD:
7672 if model['type'] == MODEL_CLOZE:
7673 morda = 0
7674 else:
7675 morda = mw.reviewer.card.ord
7676 front = model['tmpls'][morda]['qfmt'].strip()
7677 back = model['tmpls'][morda]['afmt'].strip()
7678
7679 global toBeerOrNot2Beer
7680 toBeerOrNot2Beer = onesided_action.isChecked() or \
7681 back in ['','{{FrontSide}}'] or front == back
7682
7683 self._reps += 1
7684 self.state = "question"
7685 self.typedAnswer = None
7686 self.typedAnswers = []
7687 c = self.card
7688 # grab the question and play audio
7689 if c.isEmpty():
7690 q = _("""\
7691The front of this card is empty. Please run Tools>Empty Cards.""")
7692 else:
7693 q = c.q()
7694
7695 if not A['ONESIDED_CARDS'][0] or (A['ONESIDED_CARDS'][0] and not toBeerOrNot2Beer):
7696 if self.autoplay(c):
7697 playFromText(q)
7698
7699 # render & update bottom
7700 q = self._mungeQA(q)
7701 klass = "card card%d" % (c.ord+1)
7702 self.web.eval("_updateQA(%s, false, '%s');" % (json.dumps(q), klass))
7703 self._toggleStar()
7704 if self._bottomReady:
7705 self._showAnswerButton()
7706 # if we have a type answer field, focus main web
7707 if self.typeCorrect:
7708 self.mw.web.setFocus()
7709 # user hook
7710
7711 #if back == '': # permanently forever replace empty BackSide with {{FrontSide}}
7712 # mw.reviewer.card.model()['tmpls'][morda]['afmt'] = '{{FrontSide}}'
7713 # # but for the current card empty answer still will be shown
7714 # # -- avoid side effects! --
7715
7716 runHook('showQuestion')
7717
7718 if toBeerOrNot2Beer:
7719 try:
7720 # Currently, this seems to be the right thing to do.
7721 mw.reviewer.bottom.web.eval("py.link('ansHack');")
7722 except NameError:
7723 #mw.reviewer._showAnswer()
7724 try:
7725 mw.reviewer._showAnswer() #._showAnswerHack()
7726 except NameError:
7727 # Maybe next week we need this again.
7728 mw.reviewer._showAnswer()
7729
7730def maybe_showAnswer(self):
7731 if self.mw.state != "review":
7732 # showing resetRequired screen; ignore space
7733 return
7734 self.state = "answer"
7735 c = self.card
7736 a = c.a()
7737 a = a.strip()
7738
7739 model = mw.reviewer.card.model()
7740 if model['type'] == MODEL_CLOZE:
7741 morda = 0
7742 else:
7743 morda = mw.reviewer.card.ord
7744 if model['tmpls'][morda]['afmt'].strip() in ["",'{{FrontSide}}'] :
7745 a = c.q()
7746 a = a.strip()
7747
7748 # play audio?
7749 if self.autoplay(c):
7750 playFromText(a)
7751
7752 # render and update bottom
7753 a = self._mungeQA(a)
7754 self.web.eval("_updateQA(%s, true);" % json.dumps(a))
7755 self._showEaseButtons()
7756 # user hook
7757 runHook('showAnswer')
7758
7759if A['ONESIDED_CARDS'][0]:
7760 Reviewer._showQuestion = maybe_skip_question
7761 Reviewer._showAnswer = maybe_showAnswer
7762
7763#########################################################################################
7764#
7765# Copyright © 2013–2014 Roland Sieker <ospalh@gmail.com>
7766# -- COLORFUL_TOOLBARS.py
7767# License: GNU GPL, version 3 or later; http://www.gnu.org/copyleft/gpl.html
7768# Images:
7769#
7770# most icons from Anki1
7771#
7772# Exceptions:
7773# study.png,
7774# Found at http://www.vectorarts.net/vector-icons/free-study-book-icons/ ,
7775# "Free for Personal and Commercial Use"
7776#
7777# A few others, notably the 'bury', 'suspend', 'options', 'record' and
7778# 'play recorded' icons were found at openclipart.org:
7779# Free: http://creativecommons.org/publicdomain/zero/1.0/
7780#
7781# Others from other “public domain†images libraries.
7782# __version__ = "1.5.0"
7783
7784"""
7785Add standard tool bars to Anki2.
7786
7787This Anki2 addon adds standard tool bars (QtToolBar) to the Anki
7788main window. By default a few buttons (QActions) are added, more can
7789be added by the user.
7790"""
7791
7792def edit_actions_off():
7793 """Switch off the edit actions."""
7794 global F9_HINT_PEEKED
7795
7796 try:
7797 global edit_current_action, edit_layout_action, bury_note_action, toggle_mark_action, suspend_card_action, suspend_note_action, delete_action, show_more_tool_bar_action, bury_card_action
7798
7799 edit_current_action.setEnabled(False)
7800 edit_layout_action.setEnabled(False)
7801 if A['F4_EDIT'][0]:
7802 F4_edit_current_action.setEnabled(False)
7803 F4_edit_layout_action.setEnabled(False)
7804 if A['FLIP_FLOP'][0]:
7805 show_question_action.setEnabled(False)
7806 show_answer_action.setEnabled(False)
7807 show_question_auktion.setEnabled(False)
7808 show_answer_auktion.setEnabled(False)
7809 toggle_last_card_action.setEnabled(False)
7810 if A['F3_CARD_INFO_DURING_REVIEW'][0]:
7811 F3_CARD_INFO_DURING_REVIEW_action.setEnabled(False)
7812 if A['F3_CARD_HISTORY'][0]:
7813 F3_CARD_HISTORY_action.setEnabled(False)
7814 if A['F9_HINT_PEEKING'][0]:
7815 show_all_hints_action.setEnabled(False)
7816 F9_HINT_PEEKING_action.setEnabled(False)
7817 eddit_current_action.setEnabled(False)
7818 if A['RESET_CARD_SCHEDULING'][0]:
7819 F2_FORGET_ME_NOT_action.setEnabled(False)
7820 F2_FORGET_ME_NOTE_action.setEnabled(False)
7821 if A['SET_INTERVAL'][0]:
7822 set_new_int_action.setEnabled(False)
7823
7824 record_own_action.setEnabled(False)
7825 replay_own_action.setEnabled(False)
7826
7827 toggle_last_card_action.setEnabled(False)
7828
7829 if A['REBUILD_THEM_ALL'][0]:
7830 rebuild_all_action.setEnabled(True)
7831 if A['EXPAND_AND_COLLAPSE_DECKS'][0]:
7832 expandAction.setEnabled(True)
7833 collapseAction.setEnabled(True)
7834
7835 toggle_mark_action.setEnabled(False)
7836
7837 bury_card_action.setEnabled(False)
7838 bury_note_action.setEnabled(False)
7839 suspend_card_action.setEnabled(False)
7840 suspend_note_action.setEnabled(False)
7841 delete_action.setEnabled(False)
7842
7843 replay_action.setEnabled(False)
7844 replay_audio_action.setEnabled(False)
7845 replay_all_audio_action.setEnabled(False)
7846 list_audio_action.setEnabled(False)
7847 mute_action.setEnabled(False)
7848
7849 if A['F6_SOUND_KEY_MENU'][0]:
7850 pause_action.setEnabled(False)
7851 stop_action.setEnabled(False)
7852 forward_action.setEnabled(False)
7853 backward_action.setEnabled(False)
7854 if A['F6_FAST_SLOW'][0]:
7855 fast_action.setEnabled(False)
7856 slow_action.setEnabled(False)
7857 init_action.setEnabled(False)
7858
7859 except AttributeError:
7860 pass
7861
7862def edit_actions_on():
7863 #"""Switch on the edit actions."""
7864 try:
7865 global edit_current_action, edit_layout_action
7866
7867 if A['REBUILD_THEM_ALL'][0]:
7868 rebuild_all_action.setEnabled(False)
7869 if A['EXPAND_AND_COLLAPSE_DECKS'][0]:
7870 expandAction.setEnabled(False)
7871 collapseAction.setEnabled(False)
7872
7873 except AttributeError:
7874 pass
7875
7876def maybe_more_tool_bar_on():
7877 #"""Show the more tool bar when we should."""
7878 global show_more_tool_bar_action, edit_layout_action, bury_note_action, toggle_mark_action, suspend_card_action, suspend_note_action, delete_action, bury_card_action
7879
7880 show_more_tool_bar_action.setEnabled(True)
7881
7882 toggle_mark_action.setEnabled(True)
7883
7884 bury_card_action.setEnabled(True)
7885 bury_note_action.setEnabled(True)
7886 suspend_card_action.setEnabled(True)
7887 suspend_note_action.setEnabled(True)
7888 delete_action.setEnabled(True)
7889
7890 edit_current_action.setEnabled(True)
7891 edit_layout_action.setEnabled(True)
7892 if A['F4_EDIT'][0]:
7893 F4_edit_current_action.setEnabled(True)
7894 F4_edit_layout_action.setEnabled(True)
7895 if A['FLIP_FLOP'][0]:
7896 show_question_action.setEnabled(True)
7897 show_answer_action.setEnabled(True)
7898 show_question_auktion.setEnabled(True)
7899 show_answer_auktion.setEnabled(True)
7900
7901 if A['F3_CARD_INFO_DURING_REVIEW'][0]:
7902 F3_CARD_INFO_DURING_REVIEW_action.setEnabled(True)
7903 if A['F3_CARD_HISTORY'][0]:
7904 F3_CARD_HISTORY_action.setEnabled(True)
7905 if A['F9_HINT_PEEKING'][0]:
7906 show_all_hints_action.setEnabled(True)
7907 F9_HINT_PEEKING_action.setEnabled(True)
7908 eddit_current_action.setEnabled(True)
7909 if A['RESET_CARD_SCHEDULING'][0]:
7910 F2_FORGET_ME_NOT_action.setEnabled(True)
7911 F2_FORGET_ME_NOTE_action.setEnabled(True)
7912 if A['SET_INTERVAL'][0]:
7913 set_new_int_action.setEnabled(True)
7914
7915 record_own_action.setEnabled(True)
7916 replay_own_action.setEnabled(True)
7917
7918 if len(soundtrack_q_list)+len(soundtrack_a_list)>0:
7919 replay_action.setEnabled(True)
7920 replay_all_audio_action.setEnabled(True)
7921 replay_audio_action.setEnabled(True)
7922 list_audio_action.setEnabled(True)
7923
7924 if A['F6_SOUND_KEY_MENU'][0]:
7925 pause_action.setEnabled(True)
7926 stop_action.setEnabled(True)
7927 forward_action.setEnabled(True)
7928 backward_action.setEnabled(True)
7929 if A['F6_FAST_SLOW'][0]:
7930 fast_action.setEnabled(True)
7931 slow_action.setEnabled(True)
7932 init_action.setEnabled(True)
7933
7934 mute_action.setEnabled(True)
7935
7936 toggle_last_card_action.setEnabled(True)
7937
7938 if show_more_tool_bar_action.isChecked():
7939 try:
7940 mw.reviewer.more_tool_bar.show()
7941 except AttributeError:
7942 pass
7943
7944def save_toolbars_visible():
7945 #"""Save if we should show the tool bars in the profile."""
7946 mw.pm.profile['ctb_show_toolbar'] = show_text_tool_bar_action.isChecked()
7947 mw.pm.profile['ctb_show_qt_toolbar'] = show_qt_tool_bar_action.isChecked()
7948 mw.pm.profile['ctb_show_more_toolbar'] = show_more_tool_bar_action.isChecked()
7949
7950def save_toolbarz_visible():
7951 if A['ZOOM'][0]:
7952 mw.pm.profile['ctb_deck_browser_zoom'] = deck_browser_current_zoom
7953 mw.pm.profile['ctb_overview_zoom'] = overview_current_zoom
7954 mw.pm.profile['ctb_reviewer_zoom'] = reviewer_current_zoom
7955
7956 mw.pm.profile['ctb_more_overview_stats'] = B['B00_MORE_OVERVIEW_STATS'][0]
7957 #
7958 mw.pm.profile['ctb_new_and_due'] = (B['B00_MORE_OVERVIEW_STATS'][0] > 1)
7959 mw.pm.profile['ctb_unseen_and_suspended'] = (B['B00_MORE_OVERVIEW_STATS'][0] > 2)
7960 mw.pm.profile['ctb_hide_big_numbers'] = B['B04_HIDE_BIG_NUMBERS'][0]
7961 mw.pm.profile['ctb_gear_at_end_of_line'] = B['B03_GEAR_AT_END_OF_LINE'][0]
7962 #
7963 mw.pm.profile['ctb_musthave_study'] = B['B05_STUDY_BUTTON'][0]
7964 #
7965 mw.pm.profile['ctb_wide_buttons'] = B['B06_WIDE_BUTTONS'][0]
7966 mw.pm.profile['ctb_color_buttons'] = B['B07_COLOR_BUTTONS'][0]
7967 mw.pm.profile['ctb_big_buttons'] = B['B08_BIG_BUTTONS'][0]
7968 mw.pm.profile['ctb_just_smiles'] = (BUTTON_LABELS_SMILES == BUTTON_LABELS)
7969 #
7970 mw.pm.profile['ctb_answer_confirmation'] = B['B10_ANSWER_CONFIRMATION'][0]
7971 mw.pm.profile['ctb_titles'] = B['B11_BUTTON_TITLES'][0]
7972 mw.pm.profile['ctb_hard7'] = B['B12_HARD7'][0]
7973 mw.pm.profile['ctb_edit_more'] = B['B13_EDIT_MORE'][0]
7974
7975def load_toolbars_visible():
7976 # Show the right tool bars.
7977
7978 # Get the state if we should show the tool bars from the profile or
7979 # use default values. Then show or do not show those tool bars.
7980
7981 try:
7982 ttb_on = mw.pm.profile['ctb_show_toolbar']
7983 except KeyError:
7984 ttb_on = False
7985 show_text_tool_bar_action.setChecked(ttb_on)
7986 toggle_text_tool_bar()
7987
7988 try:
7989 qtb_on = mw.pm.profile['ctb_show_qt_toolbar']
7990 except KeyError:
7991 qtb_on = True
7992 show_qt_tool_bar_action.setChecked(qtb_on)
7993 toggle_qt_tool_bar()
7994
7995 try:
7996 mtb_on = mw.pm.profile['ctb_show_more_toolbar']
7997 except KeyError:
7998 mtb_on = True
7999 show_more_tool_bar_action.setChecked(mtb_on)
8000
8001 # Don't toggle the more tool bar yet. It would be shown on the
8002 # deck browser screen
8003 # toggle_more_tool_bar()
8004
8005def load_toolbarz_visible():
8006
8007 global deck_browser_current_zoom, overview_current_zoom, reviewer_current_zoom
8008 global A, B, BUTTON_LABELS
8009
8010 try:
8011 key_value = mw.pm.profile['ctb_deck_browser_zoom']
8012 if A['ZOOM'][0]:
8013 deck_browser_current_zoom = key_value
8014 except KeyError:
8015 pass
8016 # deck_browser_current_zoom = deck_browser_standard_zoom
8017
8018 try:
8019 key_value = mw.pm.profile['ctb_overview_zoom']
8020 if A['ZOOM'][0]:
8021 overview_current_zoom = key_value
8022 except KeyError:
8023 pass
8024 # overview_current_zoom = overview_standard_zoom
8025
8026 try:
8027 key_value = mw.pm.profile['ctb_reviewer_zoom']
8028 if A['ZOOM'][0]:
8029 reviewer_current_zoom = key_value
8030 except KeyError:
8031 pass
8032 # reviewer_current_zoom = reviewer_standard_zoom
8033
8034 current_reset_zoom()
8035
8036 # -- IEWL
8037
8038 try:
8039 next_key1 = mw.pm.profile['ctb_more_overview_stats']
8040 except KeyError:
8041 next_key1 = 0
8042 if next_key1:
8043 B['B00_MORE_OVERVIEW_STATS'][0] = 3
8044 else:
8045 B['B00_MORE_OVERVIEW_STATS'][0] = 0
8046
8047 if B['B00_MORE_OVERVIEW_STATS'][0]:
8048 try:
8049 next_key2 = mw.pm.profile['ctb_unseen_and_suspended']
8050 except KeyError:
8051 next_key2 = True
8052 if next_key2:
8053 B['B00_MORE_OVERVIEW_STATS'][0] = 3
8054 else:
8055 B['B00_MORE_OVERVIEW_STATS'][0] = 2
8056
8057 try:
8058 next_key = mw.pm.profile['ctb_new_and_due']
8059 except KeyError:
8060 next_key = True
8061 if next_key:
8062 if next_key2:
8063 B['B00_MORE_OVERVIEW_STATS'][0] = 3
8064 else:
8065 B['B00_MORE_OVERVIEW_STATS'][0] = 2
8066 else:
8067 B['B00_MORE_OVERVIEW_STATS'][0] = 1
8068
8069 try:
8070 key_value = mw.pm.profile['ctb_hide_big_numbers']
8071 B['B04_HIDE_BIG_NUMBERS'][0] = key_value
8072 except KeyError:
8073 pass
8074
8075 try:
8076 key_value = mw.pm.profile['ctb_gear_at_end_of_line']
8077 B['B03_GEAR_AT_END_OF_LINE'][0] = key_value
8078 except KeyError:
8079 pass
8080
8081 # --
8082
8083 try:
8084 key_value = mw.pm.profile['ctb_musthave_study']
8085 B['B05_STUDY_BUTTON'][0] = key_value
8086 except KeyError:
8087 pass
8088
8089 # --
8090
8091 try:
8092 key_value = mw.pm.profile['ctb_wide_buttons']
8093 B['B06_WIDE_BUTTONS'][0] = key_value
8094 except KeyError:
8095 pass
8096
8097 try:
8098 key_value = mw.pm.profile['ctb_color_buttons']
8099 B['B07_COLOR_BUTTONS'][0] = key_value
8100 except KeyError:
8101 pass
8102
8103 try:
8104 key_value = mw.pm.profile['ctb_big_buttons']
8105 B['B08_BIG_BUTTONS'][0] = key_value
8106 except KeyError:
8107 pass
8108
8109 try:
8110 key_value = mw.pm.profile['ctb_just_smiles']
8111 except KeyError:
8112 key_value = (BUTTON_LABELS_SMILES == BUTTON_LABELS)
8113 #
8114 if key_value:
8115 BUTTON_LABELS = BUTTON_LABELS_SMILES
8116 else:
8117 BUTTON_LABELS = BUTTON_LABELS_LANG
8118
8119 # --
8120 try:
8121 key_value = mw.pm.profile['ctb_answer_confirmation']
8122 B['B10_ANSWER_CONFIRMATION'][0] = key_value
8123 except KeyError:
8124 pass
8125
8126 try:
8127 key_value = mw.pm.profile['ctb_titles']
8128 B['B11_BUTTON_TITLES'][0] = key_value
8129 except KeyError:
8130 pass
8131
8132 try:
8133 key_value = mw.pm.profile['ctb_hard7']
8134 B['B12_HARD7'][0] = key_value
8135 except KeyError:
8136 pass
8137
8138 try:
8139 key_value = mw.pm.profile['ctb_edit_more']
8140 B['B13_EDIT_MORE'][0] = key_value
8141 initEditMore(B['B13_EDIT_MORE'][0])
8142 except KeyError:
8143 pass
8144
8145 #if B['B00_MORE_OVERVIEW_STATS'][0]:
8146 musthave_setup_menu(2)
8147 overview_init()
8148 initDeckBro()
8149
8150 if mw.state == "deckBrowser":
8151 mw.moveToState("deckBrowser")
8152 if mw.state == "overview":
8153 mw.moveToState("overview")
8154 if mw.state == "review":
8155 mw.moveToState("review")
8156
8157# --
8158
8159def update_mark_action():
8160 """Set the state of the mark action to the marked state of the note."""
8161 toggle_mark_action.setChecked(mw.reviewer.card.note().hasTag("marked"))
8162
8163def next_card_toggle_off():
8164 """Switch the next card action off."""
8165 toggle_last_card_action.setChecked(False)
8166
8167def maybe_autoplay(reviewer, card):
8168 """Return whether we should play the sound on card flips."""
8169
8170 # Return False when we have swiched on mute, the standard autoplay state otherwise.
8171 if not mute_action.isChecked():
8172 return False
8173 return reviewer.mw.col.decks.confForDid(card.odid or card.did)['autoplay']
8174
8175if A['ANKI_MENU_ICONS'][0] or A['COLORFUL_TOOLBAR'][0]:
8176 mw.form.actionUndo.setIcon(QIcon(os.path.join(MUSTHAVE_COLOR_ICONS, 'undo.png')))
8177
8178if A['ANKI_MENU_ICONS'][0]:
8179 ## Add images to actions we already have. I skip a few where no icon really fits.
8180 mw.form.actionDocumentation.setIcon(QIcon(os.path.join(MUSTHAVE_COLOR_ICONS, 'help.png')))
8181 mw.form.actionDonate.setIcon(QIcon(os.path.join(MUSTHAVE_COLOR_ICONS, 'donate.png')))
8182 mw.form.actionAbout.setIcon(QIcon(os.path.join(MUSTHAVE_COLOR_ICONS, 'anki.png')))
8183
8184 mw.form.actionSwitchProfile.setIcon(QIcon(os.path.join(MUSTHAVE_COLOR_ICONS, 'switch-profile.png')))
8185 mw.form.actionImport.setIcon(QIcon(os.path.join(MUSTHAVE_COLOR_ICONS, 'import.png')))
8186 mw.form.actionExport.setIcon(QIcon(os.path.join(MUSTHAVE_COLOR_ICONS, 'export.png')))
8187 mw.form.actionExit.setIcon(QIcon(os.path.join(MUSTHAVE_COLOR_ICONS, 'exit.png')))
8188
8189 mw.form.actionDownloadSharedPlugin.setIcon(QIcon(os.path.join(MUSTHAVE_COLOR_ICONS, 'download-addon.png')))
8190 mw.form.actionDownloadSharedPlugin.setShortcut(QKeySequence(HOTKEY['download_addon'][0]))
8191
8192 mw.form.actionFullDatabaseCheck.setIcon(QIcon(os.path.join(MUSTHAVE_COLOR_ICONS, 'check-db.png')))
8193 mw.form.actionPreferences.setIcon(QIcon(os.path.join(MUSTHAVE_COLOR_ICONS, 'preferences.png')))
8194
8195 mw.form.actionStudyDeck.setIcon(QIcon(os.path.join(MUSTHAVE_COLOR_ICONS, 'decks.png')))
8196 mw.form.actionStudyDeck.setShortcut(HOTKEYZ['Study_deck'][0])
8197
8198 mw.form.actionCreateFiltered.setIcon(QIcon(os.path.join(MUSTHAVE_COLOR_ICONS, 'filter.png')))
8199 mw.form.actionCheckMediaDatabase.setIcon(QIcon(os.path.join(MUSTHAVE_COLOR_ICONS, 'media.png')))
8200 mw.form.actionEmptyCards.setIcon(QIcon(os.path.join(MUSTHAVE_COLOR_ICONS, 'empty.png')))
8201 mw.form.actionNoteTypes.setIcon(QIcon(os.path.join(MUSTHAVE_COLOR_ICONS, 'bug.png')))
8202
8203# Wrapper to not show a next card.
8204Reviewer.autoplay = maybe_autoplay
8205
8206#if B['B00_MORE_OVERVIEW_STATS'][0]:
8207if not HIDE_THEM_ALL:
8208 addHook("unloadProfile", save_toolbarz_visible)
8209 addHook("profileLoaded", load_toolbarz_visible)
8210
8211if A['COLORFUL_TOOLBAR'][0]:
8212
8213 mw.deckBrowser.show = wrap(mw.deckBrowser.show, edit_actions_off)
8214 mw.overview.show = wrap(mw.overview.show, edit_actions_on)
8215 mw.reviewer.show = wrap(mw.reviewer.show, edit_actions_on)
8216 mw.reviewer.show = wrap(mw.reviewer.show, maybe_more_tool_bar_on)
8217 mw.overview.show = wrap(mw.overview.show, more_tool_bar_off)
8218 mw.reviewer._toggleStar = wrap(mw.reviewer._toggleStar, update_mark_action)
8219 mw.deckBrowser.show = wrap(mw.deckBrowser.show, more_tool_bar_off)
8220
8221 # Make sure we don't leave a stale last card button switched on
8222 addHook("reviewCleanup", next_card_toggle_off)
8223
8224 addHook("unloadProfile", save_toolbars_visible)
8225 addHook("profileLoaded", load_toolbars_visible)
8226
8227 if mw_addon_audio_menu_exists:
8228 mw.addon_audio_menu.addAction(mute_action)
8229
8230 if A['REPLAY_BUTTONS_ON_CARD'][0]:
8231 mw.addon_audio_menu.addAction(replay_all_audio_action)
8232 mw.addon_audio_menu.addAction(replay_audio_action)
8233 mw.addon_audio_menu.addAction(list_audio_action)
8234
8235 mw.addon_audio_menu.addSeparator()
8236 mw.addon_audio_menu.addAction(record_own_action)
8237 mw.addon_audio_menu.addAction(replay_own_action)
8238 mw.addon_audio_menu.addSeparator()
8239
8240if A['F6_SOUND_KEY_MENU'][0]:
8241 mw.addon_audio_menu.addSeparator()
8242 mw.addon_audio_menu.addAction(pause_action)
8243 mw.addon_audio_menu.addAction(stop_action)
8244 mw.addon_audio_menu.addAction(forward_action)
8245 mw.addon_audio_menu.addAction(backward_action)
8246 mw.addon_audio_menu.addSeparator()
8247 if A['F6_FAST_SLOW'][0]:
8248 mw.addon_audio_menu.addAction(fast_action)
8249 mw.addon_audio_menu.addAction(slow_action)
8250 mw.addon_audio_menu.addAction(init_action)
8251 mw.addon_audio_menu.addSeparator()
8252
8253######################################################
8254#
8255if A['FLIP_FLOP'][0]:
8256 if mw_addon_audio_menu_exists:
8257 if A['NUMERIC_KEYPAD_REMAPPING'][0]:
8258
8259 snow_question_aktion = QAction(mw)
8260 snow_question_aktion.setText(u'Ðа &Лицевую Сторону' if lang=='ru' else _(u"Show &FrontSide"))
8261 snow_question_aktion.setShortcut(HOTKEY['FrontUp'][0])
8262 if A['ANKI_MENU_ICONS'][0]:
8263 snow_question_aktion.setIcon(PageUp_icon)
8264 mw.connect(snow_question_aktion, SIGNAL("triggered()"), go_question)
8265
8266 snow_answer_aktion = QAction(mw)
8267 snow_answer_aktion.setText(u'Ðа &Оборотную Сторону' if lang=='ru' else _(u"Show &BackSide"))
8268 snow_answer_aktion.setShortcut(HOTKEY['BackDown'][0])
8269 if A['ANKI_MENU_ICONS'][0]:
8270 snow_answer_aktion.setIcon(PageDown_icon)
8271 mw.connect(snow_answer_aktion, SIGNAL("triggered()"), go_answer)
8272
8273 mw.addon_audio_menu.addSeparator()
8274 mw.addon_audio_menu.addAction(snow_question_aktion)
8275 mw.addon_audio_menu.addAction(snow_answer_aktion)
8276 mw.addon_audio_menu.addSeparator()
8277
8278#############################################################################################################
8279# Local CSS and DIY night mode
8280# https://ankiweb.net/shared/info/2587372325
8281
8282# © copyright 2012 Roland Sieker <ospalh@gmail.com>
8283# Contains snippets of code from anki proper,
8284# written by Damien Elmes <anki@ichi2.net>
8285# License: GNU AGPL, version 3 or later; http://www.gnu.org/copyleft/agpl.html
8286# Insipired by CSS Modify Style-Sheet input by DAThomas
8287
8288"""
8289Load local CSS and add it to the cards.
8290
8291This is an add-on for Anki 2 SRS.
8292Load the file 'user_style.css' from the user’s profile folder
8293(e.g. "~/Anki/User 1/user_style.css") and add it to the cards, before
8294the style from the template.
8295"""
8296
8297user_css_name = 'user_style.css'
8298"""File name of the user's CSS"""
8299css_encoding = 'utf-8'
8300"""Encoding of the user's CSS file"""
8301local_class = 'loc'
8302"""Class added to all cards"""
8303
8304user_css = u'\n .G { color: gray; }\n .H { visibility: hidden; }\n .B { font-weight: bold; }\n .I { font-style: italic; }\n .reviewerhide, .n { display: none; } .s { font-size: smaller; } \n '
8305extra_class = None
8306
8307def fix_body_class():
8308 # Add classes to body.
8309
8310 # Add classes to the html body, so the local CSS mechanism works
8311 # together with CSS definitions for web review. Use
8312 # ".loc.card"—without space—for the card and ".loc .classNN"—with a
8313 # space—for sub-elements in you user_style.css.
8314
8315 # Gather all the A-Za-z0-9_ characters from the template and model
8316 # names and add those as class.
8317 model = mw.reviewer.card.model()
8318 if model['type'] == MODEL_STD:
8319 template_nr = mw.reviewer.card.ord
8320 else:
8321 template_nr = 0
8322
8323 template_class = re.sub(ur'[\W_]+', u'', model['tmpls'][template_nr]['name']).lower()
8324 model_class = re.sub(ur'[\W_]+', u'', model['name']).lower()
8325
8326 stencil = ur'[~\"\'\`\,\.\@\:\;\#\!\?\$\%\^\&\*\(\)\+\=\/\\\|\{\}\<\>\[\]]+' #
8327 stenc = ur'[\s]+'
8328
8329 template_klass = re.sub(stencil, u'', model['tmpls'][template_nr]['name'])
8330 template_klass = re.sub(stenc, u'_', template_klass)
8331 model_klass = re.sub(stencil, u'', model['name'])
8332 model_klass = re.sub(stenc, u'_', model_klass)
8333
8334 deck_klass = re.sub(SEPARATOR, '_', mw.col.decks.current()['name'])
8335 deck_klass = re.sub(stencil, u'', deck_klass)
8336 deck_klass = re.sub(stenc, u'_', deck_klass)
8337
8338 body_class = ur'{0} card card{1} template_{2} model_{3} card_{4} type_{5} deck_{6}'.format(
8339 local_class, mw.reviewer.card.ord + 1,
8340 template_class, model_class, template_klass, model_klass, deck_klass)
8341
8342 try:
8343 body_class += ' ' + extra_class
8344 except TypeError:
8345 pass
8346
8347 htm_class = ' ' + \
8348 ( " queue_New" if mw.reviewer.card.queue == 0 else "" ) + \
8349 ( " queue_Reps" if mw.reviewer.card.reps > 0 else "" ) + \
8350 ( " queue_Lapses" if mw.reviewer.card.lapses > 0 else "" ) + \
8351 ( " queue_Learn" if mw.reviewer.card.queue == 1 and mw.reviewer.card.type == 1 else "" ) + \
8352 ( " queue_Relearn" if mw.reviewer.card.queue == 1 and mw.reviewer.card.type == 2 else "" ) + \
8353 ( " queue_Filtered" if mw.reviewer.card.queue == 0 and mw.reviewer.card.type == 2 else "" ) + \
8354 ( " queue_NextdayLearn" if mw.reviewer.card.queue == 3 and mw.reviewer.card.type == 1 else "" ) + \
8355 ( " queue_Review" if mw.reviewer.card.queue == 2 else "" ) + \
8356 ( " queue_Young" if mw.reviewer.card.queue == 2 and mw.reviewer.card.ivl < 21 else "" ) + \
8357 ( " queue_Mature" if mw.reviewer.card.queue == 2 and mw.reviewer.card.ivl > 20 else "" )
8358 if lang != 'en':
8359 htm_class += ' ' + \
8360 ( " "+_('queue')+"_"+_("New") if mw.reviewer.card.queue == 0 else "" ) + \
8361 ( " "+_('queue')+"_"+_("Lapses") if mw.reviewer.card.lapses > 0 else "" ) + \
8362 ( " "+_('queue')+"_"+_("Learn") if mw.reviewer.card.queue == 1 and mw.reviewer.card.type == 1 else "" ) + \
8363 ( " "+_('queue')+"_"+_("Relearn") if mw.reviewer.card.queue == 1 and mw.reviewer.card.type == 2 else "" ) + \
8364 ( " "+_('queue')+"_"+_("Filtered") if mw.reviewer.card.queue == 0 and mw.reviewer.card.type == 2 else "" ) + \
8365 ( " "+_('queue')+"_"+_("Review") if mw.reviewer.card.queue == 2 else "" ) + \
8366 ( " "+_('queue')+"_"+_("Young") if mw.reviewer.card.queue == 2 and mw.reviewer.card.ivl < 21 else "" ) + \
8367 ( " "+_('queue')+"_"+_("Mature") if mw.reviewer.card.queue == 2 and mw.reviewer.card.ivl > 20 else "" )
8368 #mw.web.eval(u"(function(){var d=document.documentElement.className;if(d.indexOf('%s')<0){document.documentElement.className+=' %s';}/*alert(document.documentElement.className);*/}())" % (htm_class,htm_class))
8369 # Anki keeps the only <html class=""> between cards.
8370 # It means it's not easy to use card's individual classes on html tag.
8371 # U must delete old classes each time before an assignment of a new one.
8372
8373 deck_klass = ''
8374 decks_klass = 'deck'
8375 deck_klasses = mw.col.decks.current()['name'].split(u'::')
8376 deck_ind = 0
8377 if len(deck_klasses) > 1:
8378 for next_klass in deck_klasses:
8379 deck_ind += 1
8380 next_klass = re.sub(stencil, u'', unicode(next_klass))
8381 next_klass = re.sub(stenc, u'_', next_klass)
8382 if deck_ind < len(deck_klasses):
8383 decks_klass += '_' + next_klass
8384 deck_klass += 'deck_' + next_klass + ' '
8385 if deck_ind > 1 and deck_ind < len(deck_klasses):
8386 deck_klass += decks_klass + ' '
8387 deck_klass = deck_klass.strip()
8388 if len(deck_klass) > 0:
8389 deck_klass = ' ' + deck_klass
8390
8391 tags_klass = ''
8392 for next_klass in mw.reviewer.card.note().tags:
8393 tag_klass = re.sub(SEPARATOR, '_', next_klass)
8394 tag_klass = re.sub(stencil, u'', tag_klass)
8395 tag_klass = re.sub(stenc, u'_', tag_klass)
8396 tag_klass = 'tag_' + tag_klass + ' '
8397 if tags_klass.find(tag_klass) == -1:
8398 tags_klass += tag_klass
8399 while next_klass.rfind(SEPARATOR)>-1:
8400 last_klass = next_klass.split(SEPARATOR)[-1]
8401 tag_klass = re.sub(SEPARATOR, '_', last_klass)
8402 tag_klass = re.sub(stencil, u'', tag_klass)
8403 tag_klass = re.sub(stenc, u'_', tag_klass)
8404 tag_klass = 'tag_' + tag_klass + ' '
8405 if tags_klass.find(tag_klass) == -1:
8406 tags_klass += tag_klass
8407 next_klass = SEPARATOR.join(next_klass.split(SEPARATOR)[:-1])
8408 tag_klass = re.sub(SEPARATOR, '_', next_klass)
8409 tag_klass = re.sub(stencil, u'', tag_klass)
8410 tag_klass = re.sub(stenc, u'_', tag_klass)
8411 tag_klass = 'tag_' + tag_klass + ' '
8412 if tags_klass.find(tag_klass) == -1:
8413 tags_klass += tag_klass
8414 tags_klass = tags_klass[:-1]
8415
8416 frontSide = mw.reviewer.card.model()['tmpls'][0 if mw.reviewer.card.model()['type'] == MODEL_CLOZE else mw.reviewer.card.ord]['qfmt'].strip()
8417 backSide = mw.reviewer.card.model()['tmpls'][0 if mw.reviewer.card.model()['type'] == MODEL_CLOZE else mw.reviewer.card.ord]['afmt'].strip()
8418
8419 mw.web.eval(u"document.body.className = '{0} {1} {2} {3} {4} {5} {6}'.replace(/\s+/g,' ');".format(body_class, htm_class, deck_klass, tags_klass,
8420 (" onesided_card" + (" ОдноÑтороннÑÑ" if lang=='ru' else "")) if backSide == '{{FrontSide}}' or backSide == '' or backSide == frontSide or onesided_action.isChecked() else "",
8421 ( " note_Basic" if mw.reviewer.card.model()['type'] == MODEL_STD else " note_Cloze" ) +
8422 ( "" if lang == 'en' else ( " note_"+_("Cloze") if mw.reviewer.card.model()['type'] == MODEL_CLOZE else " note_"+_("Basic") ) ),
8423 "lang_" + lang
8424 ))
8425
8426# loc card card1
8427# template_oxfordpicturedictionaryinteractivem4r4m
8428# model_oxfordpicturedictionaryinteractivem4r4m
8429# card_OxfordPictureDictionary_Interactive-M4R4M
8430# note_Oxford_PictureDictionaryInteractive-M4R4M
8431# note: Oxford_Picture Dictionary Interactive. -M4R4M
8432# card: @Ox!f#o$r%d "P^ic&t*u(r)e" 'D+i=c:t;i,o|n/a\r?y_I[n]t{e}ra`c<t>ive'. -M4R4M
8433
8434def get_user_css():
8435 """
8436 Load the user's CSS data from disk.
8437 """
8438 global user_css
8439
8440 css_path = os.path.join(mw.pm.profileFolder(), "../addons/" + user_css_name)
8441 try:
8442 with open(css_path, 'r') as f:
8443 user_css += unicode(f.read(), css_encoding)
8444 except IOError:
8445 pass
8446
8447 css_path = os.path.join(mw.pm.profileFolder(), "../" + user_css_name)
8448 try:
8449 with open(css_path, 'r') as f:
8450 user_css += unicode(f.read(), css_encoding)
8451 except IOError:
8452 pass
8453
8454 css_path = os.path.join(mw.pm.profileFolder(), user_css_name)
8455 try:
8456 with open(css_path, 'r') as f:
8457 user_css += unicode(f.read(), css_encoding)
8458 except IOError:
8459 pass
8460
8461def localized_card_css(self):
8462 """Set the css for a card"""
8463 return_css = u''
8464 if user_css:
8465 return_css = '\n<style>\n%s\n</style>' % user_css
8466 a = old_css(self)
8467 a = a.replace('</style>','\n</style>')
8468 return return_css + a.replace('<style>','\n<style>\n\n')
8469
8470def set_extra_class(new_extra_class):
8471 """Set the varible so the extra class is used on the next card."""
8472 global extra_class
8473 extra_class = new_extra_class
8474
8475def setup_menu():
8476 # Add a submenu to a view menu.
8477
8478 # Add a submenu that lists the available extra classes to the view
8479 # menu, creating that menu when neccessary
8480
8481 if extra_classes_list:
8482 mw.extra_class_submenu = QMenu(u'&МеÑтные Ñтили' if lang == 'ru' else '&Local CSS', mw)
8483 # u"Mode (e&xtra class)", mw)
8484
8485 if mw_addon_view_menu_exists:
8486 mw.addon_view_menu.addSeparator()
8487 mw.addon_view_menu.addMenu(mw.extra_class_submenu)
8488
8489 action_group = QActionGroup(mw, exclusive=True)
8490 no_class_action = action_group.addAction(
8491 QAction('н&е иÑпользовать' if lang == 'ru' else '(n&one/standard)', mw, checkable=True))
8492 no_class_action.setChecked(True)
8493 mw.extra_class_submenu.addAction(no_class_action)
8494 mw.connect(no_class_action, SIGNAL("triggered()"),
8495 lambda: set_extra_class(None))
8496 mw.extra_class_submenu.addSeparator()
8497 for ecd in extra_classes_list:
8498 nn_class_action = action_group.addAction(
8499 QAction(ecd['display'], mw, checkable=True))
8500 mw.extra_class_submenu.addAction(nn_class_action)
8501 mw.connect(nn_class_action, SIGNAL("triggered()"),
8502 lambda ec=ecd['class']: set_extra_class(ec))
8503
8504if A['LOCAL_CSS_AND_DIY_NIGHT_MODE'][0]:
8505 old_css = Card.css
8506 Card.css = localized_card_css
8507 hooks.addHook("showQuestion", fix_body_class)
8508 hooks.addHook("profileLoaded", get_user_css)
8509 setup_menu()
8510
8511#####################################################################
8512# Frozen Fields add-on for Anki
8513# Original author: tmbb (https://github.com/tmbb)
8514# with changes by: Glutanimate (https://github.com/Glutanimate)
8515#
8516# Modifications:
8517# 2015-10-25 - added hotkeys for various actions
8518
8519# A more convenient way to mark fields as sticky.
8520# Documentation and further info here:
8521# -- http://tmbb.bitbucket.org/frozen-fields/index.html
8522# Author Contact: tmbb@campus.ul.pt
8523
8524# Snowflake Icon
8525icon_name = "flake"
8526icon_min_width = "24"
8527
8528## Uncomment to use the Kubuntu icon instead of the snowflake icon
8529#icon_name = "frozen_26x28"
8530#icon_min_width = "28"
8531
8532def addons_folder(): return mw.pm.addonFolder()
8533
8534def icon_color(icon, ext="png"):
8535 return "'" + os.path.join(addons_folder(),
8536 MUSTHAVE_COLOR_ICONS,
8537 (icon + "_color." + ext)).replace("\\","/") + "'"
8538
8539def icon_grayscale(icon, ext="png"):
8540 return "'" + os.path.join(addons_folder(),
8541 MUSTHAVE_COLOR_ICONS,
8542 (icon + "_grayscale." + ext)).replace("\\","/") + "'"
8543
8544icon_js_code = """
8545function onFrozen(elem) {
8546 currentField = elem;
8547 py.run("frozen:" + currentField.id.substring(1));
8548}
8549
8550function setFrozenFields(fields, frozen, focusTo) {
8551 var txt = "";
8552 for (var i=0; i<fields.length; i++) {
8553 var n = fields[i][0];
8554 var f = fields[i][1];
8555 if (!f) {
8556 f = "<br>";
8557 }
8558 txt += "<tr><td style='min-width:""" + icon_min_width + """'></td><td class=fname>{0}</td></tr><tr>".format(n);
8559 if (frozen[i]) {
8560 txt += "<td style='min-width:""" + icon_min_width + """'><div id=i{0} onclick='onFrozen(this);'><img src=""" + icon_color(icon_name) + """/></div></td>".format(i);
8561 }
8562 else {
8563 txt += "<td style='min-width:""" + icon_min_width + """'><div id=i{0} onclick='onFrozen(this);'><img src=""" + icon_grayscale(icon_name) + """/></div></td>".format(i);
8564 }
8565 txt += "<td width=100%%>"
8566 txt += "<div id=f{0} onkeydown='onKey();' onmouseup='onKey();'".format(i);
8567 txt += " onfocus='onFocus(this);' onblur='onBlur();' class=field ";
8568 txt += "ondragover='onDragOver(this);' ";
8569 txt += "contentEditable=true class=field>{0}</div>".format(f);
8570 txt += "</td>"
8571 txt += "</td></tr>";
8572 }
8573 $("#fields").html("<table cellpadding=0 width=100%%>"+txt+"</table>");
8574 if (!focusTo) {
8575 focusTo = 0;
8576 }
8577 if (focusTo >= 0) {
8578 $("#f"+focusTo).focus();
8579 }
8580};
8581"""
8582
8583def myLoadNote(self):
8584 self.web.eval(icon_js_code)
8585 if self.stealFocus:
8586 field = self.currentField
8587 else:
8588 field = -1
8589 if not self._loaded:
8590 # will be loaded when page is ready
8591 return
8592 data = []
8593 for fld, val in self.note.items():
8594 data.append((fld, self.mw.col.media.escapeImages(val)))
8595 ###########################################################
8596 sticky = []
8597 model = self.note.model()
8598 for fld in model['flds']:
8599 sticky.append(fld['sticky'])
8600 ###########################################################
8601 self.web.eval("setFrozenFields(%s, %s, %d);" % (
8602 json.dumps(data), json.dumps(sticky), field))
8603 self.web.eval("setFonts(%s);" % (
8604 json.dumps(self.fonts())))
8605 self.checkValid()
8606 self.widget.show()
8607 if self.stealFocus:
8608 self.web.setFocus()
8609
8610def myBridge(self, str):
8611 if str.startswith("frozen"):
8612 (cmd, txt) = str.split(":", 1)
8613 field_nr = int(txt)
8614 model = self.note.model()
8615 is_sticky = model['flds'][field_nr]['sticky']
8616 model['flds'][field_nr]['sticky'] = not is_sticky
8617 self.loadNote()
8618
8619def resetFrozen(editor):
8620 myField = editor.currentField
8621 flds = editor.note.model()['flds']
8622 for n in range(len(editor.note.fields)):
8623 try:
8624 if flds[n]['sticky']:
8625 flds[n]['sticky'] = not flds[n]['sticky']
8626 except IndexError:
8627 break
8628 editor.loadNote()
8629 editor.web.eval("focusField(%d);" % myField)
8630
8631def toggleFrozen(editor):
8632 # myField = editor.currentField
8633 # flds = editor.note.model()['flds']
8634 # flds[myField]['sticky'] = not flds[myField]['sticky']
8635 myField = editor.currentField
8636 editor.web.eval("""py.run("frozen:%d");""" % myField)
8637 editor.loadNote()
8638 editor.web.eval("focusField(%d);" % myField)
8639
8640def onSetupButtons(editor):
8641 # insert custom key sequences here:
8642 # e.g. QKeySequence(Qt.ALT + Qt.SHIFT + Qt.Key_F) for Alt+Shift+F
8643 s = QShortcut(QKeySequence(HOTKEYS['Freeze'][0]), editor.parentWindow)
8644 s.connect(s, SIGNAL("activated()"),
8645 lambda : toggleFrozen(editor))
8646 t = QShortcut(QKeySequence(HOTKEYS['Frozen'][0]), editor.parentWindow)
8647 t.connect(t, SIGNAL("activated()"),
8648 lambda : resetFrozen(editor))
8649
8650if A['FROZEN_FIELDS'][0]:
8651 addHook("setupEditorButtons", onSetupButtons)
8652 editor.Editor.loadNote = myLoadNote
8653 editor.Editor.bridge = wrap(editor.Editor.bridge, myBridge, 'before')
8654
8655import aqt.editor
8656
8657#############################################################
8658# Search cards based on review time
8659# https://ankiweb.net/shared/info/3262774902
8660# by: muflax <mail@muflax.com>, 2014
8661
8662# Search for cards based on their review time.
8663#
8664# Use 'time' to search for cards based on their last review time, 'timeavg' for their average review time and 'timetotal' for their total review time. Time is in seconds, and it accepts comparisons like 'prop'.
8665#
8666# Examples:
8667# 'time:3' finds cards that took up to 3 seconds during the last review, i.e. very easy cards.
8668# 'timeavg:<=4.2' finds cards with less than 4.2 seconds review average.
8669# 'timetotal:>60' finds cards that took more than 1 minute to study over their lifetime.
8670
8671def findTime(command, (interval, args)):
8672 query = interval
8673
8674 # extract argument
8675 m = re.match("^(<=|>=|!=|=|<|>)?(\d+)$", query)
8676 if not m:
8677 # invalid input, ignore it
8678 return
8679 comparison, interval = m.groups()
8680
8681 if not comparison:
8682 comparison = "<="
8683
8684 # is interval a valid millisecond value?
8685 try:
8686 interval = int(float(interval) * 1000)
8687 except ValueError:
8688 return
8689
8690 # query
8691 q = []
8692 # only valid for review/daily learning
8693 q.append("(c.queue in (2,3))")
8694
8695 if command == "last":
8696 # get latest review of each card
8697 group = "select cid, max(id) as maxId from revlog group by cid"
8698 cids = "select r.cid from revlog r inner join (%s) groupedr on r.id = groupedr.maxId" % group
8699 q.append("c.id in (%s where r.time %s %s)" % (cids, comparison, interval))
8700 elif command == "avg":
8701 # get average times
8702 group = "select cid, sum(time)/count() as avg from revlog group by cid"
8703 q.append("c.id in (select cid from (%s) where avg %s %s)" % (group, comparison, interval))
8704 elif command == "total":
8705 # get total times
8706 group = "select cid, sum(time) as total from revlog group by cid"
8707 q.append("c.id in (select cid from (%s) where total %s %s)" % (group, comparison, interval))
8708 else:
8709 # invalid command
8710 return
8711
8712 return " and ".join(q)
8713
8714def addFindTime(search):
8715 search["time"] = lambda x: findTime("last", x)
8716 search["timelast"] = lambda x: findTime("last", x)
8717 search["timeavg"] = lambda x: findTime("avg", x)
8718 search["timetotal"] = lambda x: findTime("total", x)
8719
8720if A['SEARCH_TIME'][0]:
8721 addHook("search", addFindTime)
8722
8723##################################################################
8724# Small add cards dialog
8725# Small add carts dialog
8726# https://ankiweb.net/shared/info/3285086934
8727# Small_add_cards_dialog.py
8728# Copyricht © 2012 Roland Sieker, <ospalh@gmail.com>
8729# Just a quick hack. Remove the minimum size for the add card dialog. (Requested in the Anki Users group.)
8730# License: GNU AGPL, version 3 or later; http://www.gnu.org/copyleft/agpl.html
8731# __version__ = '1.0.0'
8732# See the notes in the progress function
8733
8734if A['SMALL_ADD_EDIT_DIALOGS'][0]:
8735
8736 from aqt.addcards import AddCards
8737
8738 def reset_min_size(self):
8739 """
8740 Undo the setting of the minimun size.
8741 """
8742 self.setMinimumWidth(100) # 400x300 addcards.py
8743 self.setMinimumHeight(50) # 500x400 editcurrent.py
8744
8745 AddCards.setupEditor = wrap(AddCards.setupEditor, reset_min_size)
8746
8747 # Should be default behavior.
8748 # Why a window would require a minimum size is beyond me.
8749 # I should be able to resize anything to whatever size I wish.
8750 # Thanks. -- ¡иɯʎdÊž ин ÊžÉÊž 'ÉнɔÉdÊžÇdu qнεиЖ
8751
8752 from aqt.editcurrent import EditCurrent
8753
8754 EditCurrent.show = wrap(EditCurrent.show, reset_min_size, "before")
8755
8756##################################################################
8757# Update Profile on first run
8758
8759"""
8760if UPDATE_PROFILE and not mw.col.conf.get('musthaveInit',False):
8761 mw.col.conf['musthaveInit'] = True
8762 mw.col.conf['numBackups'] = 10
8763 mw.col.conf['compressBackups'] = False
8764 mw.col.conf['collapseTime'] = 0
8765 mw.col.setMod()
8766"""
8767
8768##################################################################
8769# from AZERTY French keys support
8770
8771AZERTY = [
8772 ['--handbook',"Справочник по Anki 2.0"],
8773 ['--musthave',"Must Have"],
8774 ['--musthave-options',"Must Have "+("— наÑтройки" if lang=="ru" else _("Options"))],
8775 ['--musthave-stats',"Must Have "+("— ÑтатиÑтика" if lang=="ru" else _("Statistics"))],
8776 ['dt', "Пора: ... повторений, ... новых карточек (Decks Total, dt) " if lang=="ru" else "Decks Total (dt)"],
8777]
8778
8779# Prettier menu entry
8780def AZERTYrebuildAddonsMenu(self):
8781 global menu_titles
8782 for menu in self._menus:
8783 for QWERTY in AZERTY:
8784 if QWERTY[0] == menu.title():
8785 menu.setTitle(QWERTY[1])
8786 break
8787 else:
8788 menu.setTitle( menu.title().replace('_',' ') )
8789 menu_titles.append( menu.title().replace('_',' ') )
8790
8791AddonManager.rebuildAddonsMenu = wrap(AddonManager.rebuildAddonsMenu,AZERTYrebuildAddonsMenu)
8792
8793# --------------
8794# no <div style=font...>{{Field}}</div>
8795# in Edit Cards... by Add Field button
8796# only {{Field}}
8797
8798if A['ADD_FIELDS_WITHOUT_DIV'][0]:
8799 def maddField(self, widg, field, font, size):
8800 t = widg.toPlainText()
8801 t +="\n{{%s}}\n" % (field)
8802 widg.setPlainText(t)
8803 self.saveCard()
8804 CardLayout._addField = maddField
8805
8806###########################################################################################
8807# Multiple type fields on card
8808# https://ankiweb.net/shared/info/689574440
8809# This addon allows the user to specify multiple independent {{type:}} fields on one card.
8810
8811# ==== Reviewer ====
8812#we only have to wrap init, since we can overwrite the assignment
8813def myInit(self, mw):
8814 oldInit(self, mw)
8815 self.typeCorrect = []
8816
8817
8818#This is only a constant attached to the reviewer which makes it easy to replace
8819myRevHtml = """
8820<img src="qrc:/icons/rating.png" id=star class=marked>
8821<div id=qa></div>
8822<script>
8823var ankiPlatform = "desktop";
8824var typetxts;
8825function _updateQA (q, answerMode, klass) {
8826 $("#qa").html(q);
8827 typetxts = document.getElementsByName("typetxt");
8828 if (typetxts.length > 0 && typetxts[0]) {
8829 typetxts[0].focus();
8830 }
8831 if (answerMode) {
8832 var e = $("#answer");
8833 if (e[0]) { e[0].scrollIntoView(); }
8834 } else {
8835 window.scrollTo(0, 0);
8836 }
8837 if (klass) {
8838 document.body.className = klass;
8839 }
8840 // don't allow drags of images, which cause them to be deleted
8841 $("img").attr("draggable", false);
8842};
8843function _toggleStar (show) {
8844 if (show) {
8845 $(".marked").show();
8846 } else {
8847 $(".marked").hide();
8848 }
8849};
8850function _getTypedText (num) {
8851 if (typetxts.length > num && typetxts[num]) {
8852 py.link("typeans:"+typetxts[num].value);
8853 }
8854};
8855function _typeAnsPress() {
8856 if (window.event.keyCode === 13) {
8857 py.link("ansHack");
8858 }
8859};
8860function _blurInput() {
8861 for (var i = 0; i < typetxts.length; ++i) {
8862 typetxts[i].blur();
8863 }
8864};
8865</script>
8866"""
8867
8868#Sadly we cannot use the original code in the functions below this
8869def myTypeAnsFilter(self, buf):
8870 if self.state == "question":
8871 self.typeCorrect = []
8872 return self.typeAnsQuestionFilter(buf)
8873 else:
8874 return self.typeAnsAnswerFilter(buf, 0)
8875
8876
8877def myTypeAnsQuestionFilter(self, buf):
8878 clozeIdx = None
8879 m = re.search(self.typeAnsPat, buf)
8880 if not m:
8881 return buf
8882 fld = m.group(1)
8883 # if it's a cloze, extract data
8884 if fld.startswith("cloze:"):
8885 # get field and cloze position
8886 clozeIdx = self.card.ord + 1
8887 fld = fld.split(":")[1]
8888 # loop through fields for a match
8889 for f in self.card.model()['flds']:
8890 if f['name'] == fld:
8891 typeCorrect = self.card.note()[f['name']]
8892 if clozeIdx:
8893 # narrow to cloze
8894 typeCorrect = self._contentForCloze(
8895 typeCorrect, clozeIdx)
8896 self.typeFont = f['font']
8897 self.typeSize = f['size']
8898 break
8899 if not typeCorrect:
8900 #append none, so AnsAnswer indices don't missmatch
8901 self.typeCorrect.append(None)
8902 if typeCorrect is None:
8903 if clozeIdx:
8904 warn = _("""\
8905Please run Tools>Empty Cards""")
8906 else:
8907 warn = _("Type answer: unknown field %s") % fld
8908 return re.sub(self.typeAnsPat, warn, buf)
8909 else:
8910 # empty field, remove type answer pattern
8911 return re.sub(self.typeAnsPat, "", buf)
8912 buf = re.sub(self.typeAnsPat, """
8913<center>
8914<input type=text id=typeans name=typetxt onkeypress="_typeAnsPress();"
8915style="font-family: '%s'; font-size: %spx;">
8916</center>
8917""" % (self.typeFont, self.typeSize), buf, 1)
8918 self.typeCorrect.append(typeCorrect)
8919 return self.typeAnsQuestionFilter(buf)
8920
8921def myTypeAnsAnswerFilter(self, buf, i):
8922 if i >= len(self.typeCorrect):
8923 return re.sub(self.typeAnsPat, "", buf)
8924 # tell webview to call us back with the input content
8925 self.web.eval("_getTypedText(%d);" % i)
8926 origSize = len(buf)
8927 buf = buf.replace("<hr id=answer>", "")
8928 hadHR = len(buf) != origSize
8929 # munge correct value
8930 parser = HTMLParser.HTMLParser()
8931 cor = stripHTML(self.mw.col.media.strip(self.typeCorrect[i]))
8932 # ensure we don't chomp multiple whitespace
8933 cor = cor.replace(" ", " ")
8934 cor = parser.unescape(cor)
8935 cor = cor.replace(u"\xa0", " ")
8936 given = self.typedAnswer
8937 # compare with typed answer
8938 #res = self.correct(given.strip().lower(), cor.strip().lower(), showBad=False)
8939 res = self.correct(given.strip(), cor.strip(), showBad=False)
8940 # and update the type answer area
8941 def repl(match):
8942 # can't pass a string in directly, and can't use re.escape as it
8943 # escapes too much
8944 s = """
8945<span style="font-family: '%s'; font-size: %spx">%s</span>""" % (
8946 self.typeFont, self.typeSize, res)
8947 if hadHR:
8948 # a hack to ensure the q/a separator falls before the answer
8949 # comparison when user is using {{FrontSide}}
8950 s = "<hr id=answer>" + s
8951 return s
8952 buf = re.sub(self.typeAnsPat, repl, buf, 1)
8953 return self.typeAnsAnswerFilter(buf, i + 1)
8954
8955def myCatchEsc(self, evt):
8956 if evt.key() == Qt.Key_Escape:
8957 self.web.eval("_blurInput();")
8958 return True
8959
8960# ==== CLayout ====
8961def myMaybeTextInput(self, txt, type='q'):
8962 ret = oldMaybeTextInput(self, txt, type)
8963 if type == 'q':
8964 ret = re.sub("id='typeans'", "id='typeans' name='typetxt'", ret)
8965 return ret
8966
8967if A['MULTIPLE_TYPING'][0]:
8968
8969 # old style wrap
8970 oldInit = Reviewer.__init__
8971 Reviewer.__init__ = myInit
8972
8973 # monkey patch
8974 Reviewer._revHtml = myRevHtml
8975 Reviewer.typeAnsFilter = myTypeAnsFilter
8976 Reviewer.typeAnsQuestionFilter = myTypeAnsQuestionFilter
8977 Reviewer.typeAnsAnswerFilter = myTypeAnsAnswerFilter
8978 Reviewer._catchEsc = myCatchEsc
8979
8980 oldMaybeTextInput = CardLayout.maybeTextInput
8981 CardLayout.maybeTextInput = myMaybeTextInput
8982
8983
8984##################################################################
8985# Select_Buttons_Automatically_If_Correct_Answer_Wrong_Answer_or_Nothing.py
8986# https://ankiweb.net/shared/info/2074758752
8987# Select Buttons Automatically If Correct Answer, Wrong Answer or Nothing
8988
8989def JustDoIt(parm):
8990 try:
8991 arg = stripHTML(mw.col.media.strip(unicode(parm)))
8992 arg = parm.replace(" ", " ")
8993 except UnicodeDecodeError:
8994 arg = ''
8995 # ensure we don't chomp multiple whitespace
8996 arg = HTMLParser.HTMLParser().unescape(arg)
8997 return arg #unicode(arg.replace(u"\xa0", " ")) #arg
8998
8999def myDefaultEase(self, _old):
9000 #if self.mw.reviewer.state == 'question': # на Ñтороне вопроÑа не делать ничего - там данные от прошлой карточки
9001 # return _old(self)
9002 #tooltip(self.mw.reviewer.state) # it's always called on answer side, but three times
9003
9004 given = ''
9005 if hasattr(self, 'typedAnswer'):
9006 if hasattr(self, 'typeCorrect'):
9007 if self.typeCorrect: # not None
9008 if hasattr(self, 'typedAnswers'):
9009
9010 self.typedAnswer = JustDoIt(unicode(self.typedAnswer))
9011 if not len(self.typedAnswers):
9012 gvn = [self.typedAnswer]
9013 else:
9014 for i in range(len(self.typedAnswers)):
9015 self.typedAnswers[i] = JustDoIt(unicode(self.typedAnswers[i]))
9016 gvn = self.typedAnswers
9017
9018 if not type(self.typeCorrect) is list:
9019 self.typeCorrect = JustDoIt(unicode(self.typeCorrect).encode('utf-8'))
9020 cor = [self.typeCorrect]
9021 # in native Anki it is a string
9022 else:
9023 for i in range(len(self.typeCorrect)):
9024 # <div>Indiana</div> It happens very often after unexpected pushing Enter key.
9025 self.typeCorrect[i] = JustDoIt(stripHTML(unicode(self.typeCorrect[i]).encode('utf-8')))
9026 cor = self.typeCorrect
9027 # with Multiple_type_fields_on_card.py it becomes a list of strings
9028
9029 """
9030 tmp = '' +\
9031 unicode(self.typedAnswers) + '<br>' +\
9032 unicode(self.typeCorrect) + '<br>' +\
9033 unicode(str(self.typedAnswers)) + '<br>' +\
9034 unicode(str(self.typeCorrect)) + '<br>' +\
9035 unicode(unicode(self.typedAnswers)) + '<br>' +\
9036 unicode(unicode(self.typeCorrect)) + '<br>' +\
9037 unicode(gvn) + '<br>' +\
9038 unicode(cor) + '<br>'
9039 if len(gvn)>0:
9040 tmp += '' +\
9041 unicode(gvn[len(gvn)-1]) + '<br>' +\
9042 unicode(cor[len(cor)-1]) + '<br>' +\
9043 unicode(len(gvn[len(gvn)-1])) + '<br>' +\
9044 unicode(len(cor[len(cor)-1])) + '<br>' +\
9045 unicode(str(gvn[len(gvn)-1]==cor[len(cor)-1])) + '<br>'
9046 showInfo(self.typedAnswer + '<br>' + tmp)
9047 """
9048
9049 if (len(gvn)==0):
9050 res = False
9051 else:
9052 if (len(gvn)>1 and len(gvn) != len(cor)): # something went wrong
9053 res = False
9054 else:
9055 if (len(gvn)==1):
9056 #res = gvn[len(gvn)-1].strip().lower()==cor[len(cor)-1].strip().lower()
9057 res = gvn[len(gvn)-1].strip()==cor[len(cor)-1].strip()
9058 else:
9059 res = True
9060 for i in range(0,len(cor)):
9061 gvn[i] = gvn[i].strip() #.lower()
9062 cor[i] = cor[i].strip() #.lower()
9063 #gvn[i] = JustDoIt(gvn[i])
9064 #cor[i] = JustDoIt(cor[i])
9065 if (gvn[i] != "" and gvn[i] != cor[i]):
9066 res = False
9067 if (gvn[i] != ""):
9068 given += gvn[i]
9069
9070 if not F9_HINT_PEEKED and ( res or \
9071 given == '' and ( toBeerOrNot2Beer or altMW_action.isChecked() or shiftMW_action.isChecked() ) ):
9072 #tooltip ('F9_HINT_PEEKED 32...')
9073 if self.mw.col.sched.answerButtons(self.card) == 4:
9074 retv = 3
9075 else:
9076 retv = 2
9077 else:
9078 #showWarning('F9_HINT_PEEKED 21... %s, %s, %s, %s, %s, %s ' % ( F9_HINT_PEEKED,res,given,toBeerOrNot2Beer,altMW_action.isChecked(),shiftMW_action.isChecked() ))
9079 if self.mw.col.sched.answerButtons(self.card) == 4 and B['B12_HARD7'][0]:
9080 retv = 2
9081 else:
9082 retv = 1
9083
9084 else:
9085 #tooltip ('No typedAnswers')
9086 retv = _old(self)
9087 else:
9088 #tooltip ('typeCorrect is None')
9089 retv = _old(self)
9090 else:
9091 #tooltip ('No typeCorrect')
9092 retv = _old(self)
9093 else:
9094 #tooltip ('No typedAnswer')
9095 retv = _old(self)
9096
9097 #showCritical('retv = '+unicode(retv))
9098 return retv
9099
9100if A['MULTIPLE_TYPING'][0]:
9101 Reviewer._defaultEase = wrap(Reviewer._defaultEase, myDefaultEase, "around")
9102
9103##################################################################
9104# Timebox tooltip
9105# https://ankiweb.net/shared/info/2014169675
9106
9107from aqt.reviewer import Reviewer
9108from aqt.utils import tooltip
9109from anki.sound import playFromText, clearAudioQueue, play
9110
9111if A['TIMEBOX_TOOLTIP'][0]:
9112
9113 # anki/collection.py
9114 """
9115 def timeboxReached(self):
9116 "Return (elapsedTime, reps) if timebox reached, or False."
9117 if not self.conf['timeLim']:
9118 # timeboxing disabled
9119 return False
9120 elapsed = time.time() - self._startTime
9121 if elapsed > self.conf['timeLim']:
9122 return (self.conf['timeLim'], self.sched.reps - self._startReps)
9123 """
9124
9125 # aqt/reviewer.py Monkey Patch
9126 def maNextCard(self,_old):
9127 global F9_HINT_PEEKED
9128
9129 if A['COLORFUL_TOOLBAR'][0]:
9130 if toggle_last_card_action.isChecked():
9131 clearAudioQueue()
9132 toggle_last_card_action.setChecked(False)
9133 self.mw.moveToState("overview")
9134 return True
9135
9136 elapsed = self.mw.col.timeboxReached()
9137 if elapsed:
9138 part1 = ngettext("%d card studied in", "%d cards studied in", elapsed[1]) % elapsed[1]
9139 mins = int(round(elapsed[0]/60))
9140 part2 = ngettext("%s minute.", "%s minutes.", mins) % mins
9141 tooltip("<b style=font-size:larger;color:blue;font-weight:bold;> %s <span style=color:red>%s</span> </b><br><br> %s" % ( part1, part2, answerCard_LA if B['B10_ANSWER_CONFIRMATION'][0] else '' ), period=6000)
9142 self.mw.col.startTimebox()
9143 if self.cardQueue:
9144 # undone/edited cards to show
9145 c = self.cardQueue.pop()
9146 c.startTimer()
9147 self.hadCardQueue = True
9148 else:
9149 if self.hadCardQueue:
9150 # the undone/edited cards may be sitting in the regular queue;
9151 # need to reset
9152 self.mw.col.reset()
9153 self.hadCardQueue = False
9154 c = self.mw.col.sched.getCard()
9155
9156 self.card = c
9157 clearAudioQueue()
9158 if not c:
9159 self.mw.moveToState("overview")
9160 return
9161
9162 if self._reps is None or self._reps % 100 == 0:
9163 # we recycle the webview periodically so webkit can free memory
9164 F9_HINT_PEEKED = False
9165 self._initWeb()
9166 else:
9167 F9_HINT_PEEKED = False
9168 self._showQuestion() #._initWeb() #
9169
9170 Reviewer.nextCard = wrap( Reviewer.nextCard, maNextCard, "around" )
9171
9172# Bottom area
9173######################################################################
9174
9175def my_renderBottom(self):
9176 links = [
9177 ["O", "opts", _("Options")],
9178 ]
9179 if self.mw.col.decks.current()['dyn']:
9180 links.append(["R", "refresh", _("Rebuild")])
9181 links.append(["E", "empty", _("Empty")])
9182 else:
9183 links.append(["C", "studymore", _("Custom Study")])
9184 #links.append(["F", "cram", _("Filter/Cram")])
9185 if self.mw.col.sched.haveBuried():
9186 links.append(["U", "unbury", _("Unbury")])
9187 buf = ""
9188 for b in links:
9189 if b[0]:
9190 b[0] = _("Shortcut key: %s") % shortcut(b[0])
9191 buf += """
9192<button title="%s" onclick='py.link(\"%s\");'>%s</button>""" % tuple(b)
9193 self.bottom.draw(buf)
9194 if isMac:
9195 size = 28
9196 else:
9197 size = 36 + self.mw.fontHeightDelta*3
9198 self.bottom.web.setFixedHeight(size)
9199 self.bottom.web.setLinkHandler(self._linkHandler)
9200
9201# Studying more
9202######################################################################
9203
9204def ma_haveBuried(self, did):
9205 sdids = ids2str(did)
9206 cnt = self.mw.col.db.scalar(
9207 "select 1 from cards where queue = -2 and did in %s limit 1" % sdids)
9208 return not not cnt
9209
9210def my_onUnbury(self,did):
9211 import aqt.customstudy
9212 self.mw.col.decks.select(did) #self.col.decks.id(ret.name))
9213 self.mw.moveToState("overview")
9214 self.mw.col.sched.unburyCardsForDeck()
9215 self.mw.reset()
9216
9217def my_onStudyMore(self,did):
9218 import aqt.customstudy
9219 self.mw.col.decks.select(did) #self.col.decks.id(ret.name))
9220 self.mw.moveToState("overview")
9221 aqt.customstudy.CustomStudy(self.mw)
9222 self.mw.reset()
9223
9224def my_onRebuild(self,did):
9225 self.mw.col.decks.select(did)
9226 self.mw.moveToState("overview")
9227 self.mw.col.sched.rebuildDyn()
9228 self.mw.reset()
9229
9230def my_onEmpty(self,did):
9231 self.mw.col.decks.select(did)
9232 #self.mw.moveToState("overview")
9233 self.mw.col.sched.emptyDyn(self.mw.col.decks.selected())
9234 self.mw.reset()
9235
9236def my_onRescheduleDeck(self,did):
9237 if askUser(u"СброÑить раÑпиÑание Ð´Ð»Ñ Ð²Ñех карточек данной колоды?" if lang=="ru" else "Reschedule all cards of current deck?"):
9238 self.mw.checkpoint(_("Forget"))
9239
9240 self.mw.col.decks.select(did)
9241 self.mw.moveToState("overview")
9242
9243 self.mw.col.sched.removeLrn( sorted(self.mw.col.decks.cids(did, children=True)) )
9244 self.mw.col.sched.remFromDyn( sorted(self.mw.col.decks.cids(did, children=True)) )
9245 self.mw.col.sched.resetCards( sorted(self.mw.col.decks.cids(did, children=True)) )
9246
9247 tooltip(u"Карточки колоды поÑтавлены в конец очереди новых карточек." if lang=="ru" else _("Cards of deck are rescheduled as new"))
9248 self.mw.reset()
9249
9250# Options
9251##########################################################################
9252
9253def my_showOptions(self, did):
9254 m = QMenu(self.mw)
9255 a = m.addAction(_("Study"))
9256 a.connect(a, SIGNAL("triggered()"), lambda did=did: my_studyDeck(self, did))
9257 if self.mw.col.decks.get(did)['dyn']:
9258 a = m.addAction(_("Rebuild"))
9259 a.connect(a, SIGNAL("triggered()"), lambda did=did: my_onRebuild(self, did))
9260 a = m.addAction(_("Empty"))
9261 a.connect(a, SIGNAL("triggered()"), lambda did=did: my_onEmpty(self, did))
9262 pass
9263 else:
9264 a = m.addAction(_("Custom Study"))
9265 a.connect(a, SIGNAL("triggered()"), lambda did=did: my_onStudyMore(self, did)) #self._rename(did))
9266 if ma_haveBuried(self, [did]):
9267 a = m.addAction(_("Unbury"))
9268 a.connect(a, SIGNAL("triggered()"), lambda did=did: my_onUnbury(self, did))
9269 a = m.addSeparator()
9270 #
9271 a = m.addAction(_("Rename"))
9272 a.connect(a, SIGNAL("triggered()"), lambda did=did: self._rename(did))
9273 a = m.addAction(_("Options"))
9274 a.connect(a, SIGNAL("triggered()"), lambda did=did: self._options(did))
9275 a = m.addAction(_("Export"))
9276 a.connect(a, SIGNAL("triggered()"), lambda did=did: self._export(did))
9277 a = m.addAction(_("Delete"))
9278 a.connect(a, SIGNAL("triggered()"), lambda did=did: self._delete(did))
9279 #
9280 if A['RESET_CARD_SCHEDULING'][0]:
9281 a = m.addSeparator()
9282 a = m.addAction((u"СброÑить раÑпиÑание" if lang=="ru" else "Reschedule all cards"))
9283 a.connect(a, SIGNAL("triggered()"), lambda did=did: my_onRescheduleDeck(self, did))
9284 #
9285 m.exec_(QCursor.pos())
9286
9287DeckBrowser._showOptions = my_showOptions
9288
9289##################################################################
9290# • day learning cards always before new
9291
9292# By default Anki do so:
9293# learning; new if before; due; day learning; new if after
9294# With this add-on card will be displayed in the following order:
9295# learning; (day learning; new) if before; due; (day learning; new) if after
9296
9297# Normally these cards go after due, but I want them to go before new.
9298
9299# If Tools -> Preferences... -> Basic -> Show new cards before reviews
9300# learning; day learning; new; due
9301# If Tools -> Preferences... -> Basic -> Show new cards after reviews
9302# learning; due; day learning; new
9303
9304# inspired by Anki user rjgoif
9305# https://ankiweb.net/shared/info/1810271825
9306# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
9307# put ALL due "learning" cards first ×
9308# ####################################################################
9309# That is a simple add-on that inserts the daily-learning cards, i.e.
9310# cards in the learning queue with intervals that crossed the day turnover,
9311# before starting other reviews (new cards, review cards). Normally these cards
9312# go last, but I want them to go first.
9313# ####################################################################
9314
9315# This is a simple monkey patch add-on that inserts day learning cards
9316# (learning cards with intervals that crossed the day turnover)
9317# always before new cards without depending due reviews.
9318
9319# by Anki user ankitest
9320# • day learning cards always before new
9321# https://ankiweb.net/shared/info/1331545236
9322
9323if A['DAY_LEARNING'][0]:
9324 import anki.sched
9325
9326 def _getCardReordered(self):
9327 "Return the next due card id, or None."
9328
9329 # learning card due?
9330 c = self._getLrnCard()
9331 if c:
9332 return c
9333
9334 # new first, or time for one?
9335 if self._timeForNewCard():
9336
9337 # day learning card due?
9338 c = self._getLrnDayCard()
9339 if c:
9340 return c
9341
9342 c = self._getNewCard()
9343 if c:
9344 return c
9345
9346 # card due for review?
9347 c = self._getRevCard()
9348 if c:
9349 return c
9350
9351 # day learning card due?
9352 c = self._getLrnDayCard()
9353 if c:
9354 return c
9355
9356 # new cards left?
9357 c = self._getNewCard()
9358 if c:
9359 return c
9360
9361 # collapse or finish
9362 return self._getLrnCard(collapse=True)
9363
9364 anki.sched.Scheduler._getCard = _getCardReordered
9365
9366####################################################
9367# Copyright 2016 Mirco Kraenz <contact@kraenz.eu>
9368# License: GNU GPL, version 3 or later; http://www.gnu.org/copyleft/gpl.html
9369# https://ankiweb.net/shared/info/136533494
9370# Tested on Anki Version 2.0.33, Qt 4.8.4, PyQt 4.10
9371
9372"""
9373Adds new button to Ankis Editor (the window when you click "Add"). By clicking the button or pressing shortcut Alt + D: Clear all the text from the text fields and jump back to topmost text field. example with below image: Click Button. Then the text field with "foo" and the other one with "bar" will be empty. note: Shortcut is customizable by editing the constant atop the code. Repository on GitHub https://github.com/proSingularity/anki2-addons
9374"""
9375
9376SHORTCUT_AltD = "Alt+D"
9377
9378def clear_all_editor_fields(editor):
9379 '''Remove text from fields in editor. '''
9380 note = editor.note
9381 # enumerate all fieldNames of the current note
9382 for c, field_name in enumerate(mw.col.models.fieldNames(note.model())):
9383 note[field_name] = ''
9384 note.flush() # never forget to flush
9385 mw.reset() # refresh gui
9386
9387def setup_clear_buttons(editor):
9388 """Add the buttons to the editor."""
9389 editor._addButton("clear_fields1", lambda edito=editor: clear_all_editor_fields(edito), _(SHORTCUT_AltD),
9390 text=u"C", tip="Clear field entries (" + SHORTCUT_AltD +")")
9391
9392# register callback function that gets executed after setupEditorButtons has run.
9393# See Editor.setupEditorButtons for details
9394addHook("setupEditorButtons", setup_clear_buttons)
9395
9396
9397########################################################
9398# Browser Search Modifiers
9399# inspired by https://ankiweb.net/shared/info/594622823
9400# and https://anki.tenderapp.com/discussions/ankidesktop/17918-add-on-or-anki-feature-suggestion-show-only-front-card-in-browser-checkbox
9401
9402"""
9403Anki Add-on: Browser search modifiers
9404
9405Adds two checkboxes to the browser search form that, when toggled, modify
9406searches in the following way:
9407
9408Deck (Hotkey: Alt+D): Limit results to current deck
9409Card (Hotkey: Alt+C): Limit results to first card of each note
9410
9411Based on the following add-ons:
9412
9413- "Limit searches to current deck" by Damien Elmes
9414 (https://github.com/dae/ankiplugins/blob/master/searchdeck.py)
9415- "Ignore accents in browser search" by Houssam Salem
9416 (https://github.com/hssm/anki-addons)
9417
9418Original idea by Keven on Anki tenderapp
9419
9420Copyright: (c) Glutanimate 2016
9421License: GNU GPL, version 3 or later; http://www.gnu.org/copyleft/gpl.html
9422"""
9423
9424from aqt.forms.browser import Ui_Dialog
9425
9426default_checkbox_conf = {'deck_check_checked': False,
9427 'card_check_checked': False}
9428
9429deckChanged = False
9430cardChanged = False
9431
9432def onSearch(self, reset=True):
9433 """Modify search entry."""
9434
9435 global deckChanged, cardChanged
9436
9437 txt = unicode(self.form.searchEdit.lineEdit().text()).strip()
9438
9439 if self.form.cardToggleButton.isChecked():
9440 if "card:" in txt:
9441 pass
9442 elif _("<type here to search; hit enter to show current deck>") in txt:
9443 pass
9444 elif "is:current" in txt:
9445 pass
9446 else:
9447 txt = "card:1 " + txt
9448 elif cardChanged:
9449 txt = txt.replace("card:1","")
9450 cardChanged = False
9451
9452 txt = txt.strip()
9453
9454 if self.form.deckToggleButton.isChecked():
9455 if "deck:" in txt:
9456 pass
9457 elif _("<type here to search; hit enter to show current deck>") in txt:
9458 pass
9459 elif "is:current" in txt:
9460 pass
9461 elif txt == "" or txt == 'deck:current' or txt == 'card:1' or txt == 'card:1 deck:current':
9462 pass
9463 #elif not txt.strip():
9464 # txt = "deck:*"
9465 else:
9466 txt = "deck:current " + txt
9467 elif deckChanged:
9468 if txt != 'deck:current' and txt != 'card:1 deck:current':
9469 txt = txt.replace("deck:current","")
9470 deckChanged = False
9471
9472 self.form.searchEdit.lineEdit().setText(txt)
9473
9474
9475def onDeckChecked(state):
9476 '''Save the checked state in Anki's configuration.'''
9477 global deckChanged
9478 mw.col.conf['browser_checkbox_conf']['deck_check_checked'] = state
9479 deckChanged = True
9480
9481def onCardChecked(state):
9482 global cardChanged
9483 mw.col.conf['browser_checkbox_conf']['card_check_checked'] = state
9484 cardChanged = True
9485
9486def mySetupUI(self, mw):
9487 """Add new items to the browser UI to allow toggling the add-on."""
9488
9489 # Our UI stuff
9490 self.deckToggleButton = QCheckBox(_("Deck"), self.widget)
9491 self.cardToggleButton = QCheckBox(_("Card"), self.widget)
9492 self.deckToggleButton.setToolTip("Limit results to current deck")
9493 self.cardToggleButton.setToolTip("Limit results to first card of each note")
9494
9495 # Restore checked state
9496 if not 'browser_checkbox_conf' in mw.col.conf:
9497 mw.col.conf['browser_checkbox_conf'] = default_checkbox_conf
9498 self.deckToggleButton.setCheckState(
9499 mw.col.conf['browser_checkbox_conf']['deck_check_checked'])
9500 self.cardToggleButton.setCheckState(
9501 mw.col.conf['browser_checkbox_conf']['card_check_checked'])
9502
9503 # Save state on toggle
9504 mw.connect(self.deckToggleButton, SIGNAL("stateChanged(int)"), onDeckChecked)
9505 mw.connect(self.cardToggleButton, SIGNAL("stateChanged(int)"), onCardChecked)
9506
9507 # Add our items to the right of the search box. We do this by moving
9508 # every widget out of the gridlayout and into a new list. We simply
9509 # add our stuff in the new list in the right place before moving them
9510 # back to gridlayout.
9511 n_items = self.gridLayout.count()
9512 items= []
9513 for i in range(0, n_items):
9514 item = self.gridLayout.itemAt(i).widget()
9515 items.append(item)
9516 if item == self.searchEdit:
9517 items.append(self.deckToggleButton)
9518 items.append(self.cardToggleButton)
9519
9520 for i, item in enumerate(items):
9521 self.gridLayout.addWidget(item, 0, i, 1, 1)
9522
9523def onSetupMenus(self):
9524 '''Toggle state via key bindings.'''
9525 self.a = QShortcut(QKeySequence("Alt+C"), self)
9526 self.connect(self.a, SIGNAL("activated()"), lambda c=self: c.form.cardToggleButton.toggle())
9527 self.a = QShortcut(QKeySequence("Alt+D"), self)
9528 self.connect(self.a, SIGNAL("activated()"), lambda c=self: c.form.deckToggleButton.toggle())
9529
9530 # execute search when toggling checkmarks
9531 self.connect(self.form.deckToggleButton,
9532 SIGNAL("stateChanged(int)"),
9533 self.onReset)
9534 self.connect(self.form.cardToggleButton,
9535 SIGNAL("stateChanged(int)"),
9536 self.onReset)
9537
9538if A['BROWSER_SEARCH_MODIFIERS'][0]:
9539 Ui_Dialog.setupUi = wrap(Ui_Dialog.setupUi, mySetupUI)
9540 Browser.onSearch = wrap(Browser.onSearch, onSearch, "before")
9541 addHook("browser.setupMenus", onSetupMenus)
9542
9543
9544##################################################################
9545# New filename 2016: Must_Have.py --musthave.py
9546
9547if A['CHECK_OLD_ISSUES'][0]:
9548 old_filename = os.path.join(mw.pm.addonFolder(), "Must_Have_Hint_and_Answer_Keys.py")
9549 if os.path.exists(old_filename):
9550 showCritical( u'Ðайдена Ð¿Ñ€ÐµÐ´Ñ‹Ð´ÑƒÑ‰Ð°Ñ Ð²ÐµÑ€ÑÐ¸Ñ Ð´Ð¾Ð¿Ð¾Ð»Ð½ÐµÐ½Ð¸Ñ <b>Must Have</b> <br><br>%s<br><br><b>Must_Have_Hint_and_Answer_Keys <big>▶</big></b><br><br> Она неÑовмеÑтима Ñ Ð½Ð¾Ð²Ð¾Ð¹ верÑией.<br> Ð”Ð»Ñ Ð½Ð¾Ñ€Ð¼Ð°Ð»ÑŒÐ½Ð¾Ð¹ работы:<br> - удалите Ñтарое дополнение<br> - перезапуÑтите Anki.' % (old_filename) if lang == 'ru' else 'The previous version of the <b>Must Have</b> add-on <br><br>%s<br><br> is incompatible with new edition. <br> Please, remove old file and restart Anki.' % (old_filename) )
9551 # отÑюда удалить нельзÑ, Anki Ñлетит из-за неожиданной потери Ð´Ð¾Ð¿Ð¾Ð»Ð½ÐµÐ½Ð¸Ñ Ð² процеÑÑе их загрузки
9552 # It's impossible to remove file right now - Anki will be confused.
9553
9554 old_issues2delete = ''
9555 for old_issue in old_issues:
9556 if len(old_issue[0]) > 0:
9557 old_filename = os.path.join(mw.pm.addonFolder(), old_issue[0])
9558 if os.path.exists(old_filename) and (old_issue[4]<1 or old_issue[4]==4 or old_issue[4]>5):
9559 old_issues2delete += old_issue[0][:-3] + ' \n'
9560
9561 if old_issues2delete != '':
9562 if lang == 'ru':
9563 showText('Ð’ каталоге\n\n '+mw.pm.addonFolder()+'\n\nнайдены дополнениÑ, которые уже включены в дополнение `Must Have`,\nи поÑтому будут конфликтовать Ñ Ð½Ð¸Ð¼.\n\n' + old_issues2delete + '\nУдалите Ñти Ð´Ð¾Ð¿Ð¾Ð»Ð½ÐµÐ½Ð¸Ñ Ð¸ перезапуÑтите Anki.')
9564 else:
9565 showText('There are some add-ons in the folder \n\n '+mw.pm.addonFolder()+'\n\nThey are already part of `Must Have` addon,\n\n' + old_issues2delete + '\nPlease, delete them and restart Anki.')
9566 else:
9567 # mw.addonManager.addonsFolder() is not available here, because of what?
9568 #tmp = os.path.join(mw.addonManager.addonsFolder(), "musthave_addons")
9569 tmp = os.path.join(mw.pm.addonFolder(), "musthave_addons")
9570 if os.path.exists(tmp): # os.path.isdir(tmp)
9571
9572 def openAddons():
9573 mw.reviewer.web.page().mainFrame().evaluateJavaScript(\
9574 'location.assign("https://ankiweb.net/shared/addons/");')
9575
9576 def openAir():
9577 maName = os.path.join(mw.addonManager.addonsFolder(), 'musthave_addons.html') # 'musthave-addons.html')
9578 QDesktopServices.openUrl(QUrl("file:///"+maName))
9579
9580 def filez():
9581 return [f for f in os.listdir(os.path.join(mw.addonManager.addonsFolder(), "musthave_addons"))
9582 if f.endswith(".py")]
9583
9584 def checkAddons():
9585
9586 mustHaveAddons = os.path.join(mw.pm.addonFolder(), "musthave_addons")
9587 if os.path.exists(mustHaveAddons):
9588
9589 old_issues2save = ''
9590 for old_issue in old_issues:
9591 if len(old_issue[0]) > 0:
9592 old_filename = os.path.join(mustHaveAddons, old_issue[0])
9593 if not os.path.exists(old_filename):
9594 old_issues2save += old_issue[0][:-3] + ' \n'
9595
9596 if old_issues2save != '':
9597 showText('Ð’ каталоге\n\n '+mustHaveAddons+'\n\nне найдены дополнениÑ, которые уже включены в дополнение `Must Have`,\nи поÑтому, возможно, Ñледует загрузить их и Ñкопировать туда.\n\n' + old_issues2save + '\nÐайди Ñти Ð´Ð¾Ð¿Ð¾Ð»Ð½ÐµÐ½Ð¸Ñ Ð¸ Ñохрани Ð´Ð»Ñ Ð´Ð°Ð»ÑŒÐ½ÐµÐ¹ÑˆÐ¸Ñ… разборок!')
9598
9599 old_issues2list = ''
9600 for old_filename in filez():
9601 for old_issue in old_issues:
9602 if old_issue[0]==old_filename:
9603 break
9604 else:
9605 old_issues2list += old_filename[:-3] + ' \n'
9606
9607 if old_issues2list != '':
9608 showText('Ð’ каталоге\n\n '+mustHaveAddons+'\n\nнайдены дополнениÑ, которые ещё не включены в ÑпиÑок `old_issues`,\nи поÑтому, возможно, Ñледует занеÑти их туда.\n\n' + old_issues2list + '\nÐайди Ñти Ð´Ð¾Ð¿Ð¾Ð»Ð½ÐµÐ½Ð¸Ñ Ð¸ реши вопроÑ!')
9609
9610 listAddons = mw.reviewer.web.page().mainFrame().evaluateJavaScript(\
9611 '(function(){return JSON.stringify(shared.files)}())')
9612 try:
9613 listAddons = json.loads(listAddons)
9614 except TypeError:
9615 tooltip(' <big> Select <i style=color:red;> <b> Load Addons </b> </i> First! </big> ')
9616 return
9617
9618 nextlines = []
9619 for nextline in listAddons:
9620 nextlines.append(['',nextline[1],str(nextline[0]),time.strftime("%Y-%m-%d", time.localtime(nextline[4]))])
9621
9622 maName = os.path.join(mw.addonManager.addonsFolder(), 'musthave_addons.html')
9623
9624 f = open(maName, 'w')
9625 f.write('<!DOCTYPE html>\n<html>\n<head>\n<title>musthave</title><meta charset=utf-8>\n')
9626 f.write('<style>body{text-align:center;font-family:Calibri;}table{margin:auto;border-spacing:0px;border:solid silver 1px;} table:last-of-type { border-bottom: solid 1px black; } tr:nth-child(2n-1){background-color:rgba(233,233,233,.75);}td,th{line-height:1.5em;padding:.1em 1em;text-align:left;}a{text-decoration:none;}a:active,a:hover{text-decoration:underline;}</style>\n')
9627 f.write('</head>\n<body><h1>musthave <small style = color:green; >'+ unicode(datetime.today().date()) +' <small style = color:blue; >'+ datetime.strftime(datetime.now(),"%H:%M") +'</small></small></h1><p><a href="https://ankiweb.net/shared/addons/">AnkiWeb add-ons list</a></p>\n')
9628
9629 def mayDoIt(old_issues):
9630 oldie = 0
9631 ff = open(os.path.join(mw.addonManager.addonsFolder(), 'musthave-addons.html'), 'w')
9632 ff.write(u'<pre>\n')
9633 for old_issue in old_issues:
9634 if oldie != old_issue[4]:
9635 oldie = old_issue[4]
9636 ff.write('\n')
9637 ff.writelines('')
9638 if not old_issue[2] == '':
9639 ff.write(u'<a href="https://ankiweb.net/shared/info/%s" rel="nofollow">%s</a>\n' % (old_issue[2], old_issue[1]))
9640 ff.write(u'</pre>\n')
9641 ff.close()
9642
9643 mayDoIt( sorted(old_issues, key=lambda issue: issue[4]) )
9644
9645 def canDoIt(old_issues, h2, fl, i4):
9646 f = u'<h4>%s</h4>\n<table style=min-width:50em;><tbody>\n' % (h2)
9647 i = 0
9648 j = 0
9649 for old_issue in old_issues:
9650 if old_issue[4] == i4:
9651 if not old_issue[2] == '':
9652 i += 1
9653 f += u'<tr><td style=padding-right:1em;text-align:right;font-size:smaller;>' +str(i)+ '.</td><td><a href="https://ankiweb.net/shared/info/%s">%s</a></td><td>%s</td><td style="font-size:smaller;">%s</td></tr>\n' \
9654 % (old_issue[2], "<b>%s</b>"%(old_issue[1]) if (old_issue[4]<1) and fl else "<i>%s</i>"%(old_issue[1]) if (old_issue[4]>3) and fl else old_issue[1], old_issue[3], old_issue[2])
9655 else:
9656 if not old_issue[1] == '':
9657 j += 1
9658 f += '<tr><td style=padding-right:1em;text-align:right;color:dodgerblue;>' +str(j)+ '<small>)</small></td>'
9659 else:
9660 f += '<tr><td> </td>'
9661
9662 f += '<td>%s</td><td>%s</td><td> </td></tr>\n' % ( ("<b>%s</b>"%(old_issue[0]) if (old_issue[4]<1) and fl else "<i>%s</i>"%(old_issue[0]) if (old_issue[4]>3) and fl else old_issue[0]), old_issue[3])
9663
9664 f += '</tbody></table>\n'
9665 return f
9666
9667 def doThemAll(old_issues):
9668 for old_issue in old_issues:
9669 not_found = True
9670 for nextline in nextlines:
9671 if old_issue[2]==nextline[2] or old_issue[1].lower() == nextline[1].lower():
9672 #"""
9673 old_issue[3] = old_issue[3].strip()
9674 if old_issue[3][-1:]=='.':
9675 old_issue[3] = old_issue[3][:-1]
9676 try:
9677 mydate = datetime.strptime( old_issue[3], \
9678 "%Y-%m-%d" if '-' in old_issue[3] else "%d.%m.%Y" )
9679 except ValueError:
9680 mydate = ''
9681 try:
9682 madate = datetime.strptime( nextline[3], \
9683 "%d.%m.%Y" if '.' in nextline[3] else "%Y-%m-%d" )
9684 except ValueError:
9685 madate = ''
9686 #"""
9687 if not mydate == madate:
9688 f.write('<tr><th style="text-align:left;"><a href="https://ankiweb.net/shared/info/%s">%s</a></th><th>' \
9689 % (old_issue[2], old_issue[1]) + nextline[3] + '</th><td>' \
9690 + old_issue[3] + '</td></tr>\n')
9691 not_found = False
9692 break
9693 if not_found and not old_issue[2]=='':
9694 f.write('<tr><td><i>' + old_issue[1] + \
9695 '</i></td><td colspan="2" style="text-align:center;">not found in add-ons list.</td></tr>')
9696
9697 f.write('<h2 style="color:red;">ОбновлениÑ</h2>')
9698 f.write('<table><tbody>')
9699 doThemAll(very_interesting)
9700 f.write('</tbody></table>'+canDoIt(very_interesting, 'Very Necessary', False, 0)+' ')
9701
9702 f.write('<h2 style="color:red;">New Issues</h2>')
9703 f.write('<table><tbody>')
9704 doThemAll(old_issues)
9705 f.write('</tbody></table>'+canDoIt(old_issues, 'unknown', True, 0)+\
9706 canDoIt(old_issues, 'disabled if original was found', True, 1)+\
9707 canDoIt(old_issues, 'fully compatible', True, 2)+\
9708 canDoIt(old_issues, 'original works anyway', True, 3)+\
9709 canDoIt(old_issues, 'incompatible', True, 4)+\
9710 canDoIt(old_issues, 'the add-ons are mine', True, 5)+\
9711 ' ')
9712
9713 # 0 - unknown
9714 # 1 - switch it off if original was found
9715 # 2 - fully compatible
9716 # 3 - original works anyway
9717 # 4 - incompatible
9718 # 5 - original does not work
9719
9720 f.write('<p>• </p></body></html>')
9721 f.close()
9722
9723 QDesktopServices.openUrl(QUrl("file:///"+maName))
9724
9725 open_addons_action = QAction(mw)
9726 open_addons_action.setText(u'&Load addons')
9727 mw.connect(open_addons_action, SIGNAL("triggered()"), openAddons)
9728
9729 check_addons_action = QAction(mw)
9730 check_addons_action.setText(u'&Check addons')
9731 mw.connect(check_addons_action, SIGNAL("triggered()"), checkAddons)
9732
9733 view_addons_action = QAction(mw)
9734 view_addons_action.setText(u'&View last Check addons results')
9735 mw.connect(view_addons_action, SIGNAL("triggered()"), openAir)
9736
9737 mw.form.menuHelp.addSeparator()
9738 mw.form.menuHelp.addAction(open_addons_action)
9739 mw.form.menuHelp.addAction(check_addons_action)
9740 mw.form.menuHelp.addAction(view_addons_action)
9741 mw.form.menuHelp.addAction(download_addon_action)
9742 mw.form.menuHelp.addSeparator()
9743
9744 dt = datetime.now() # Текущие дата и времÑ. # Current date and time.
9745 tooltip('<i style="color:blue;">Must Have:</i> it is <b style="color:red;">%s</b> now.' % dt.strftime('%d.%m.%Y %H:%M'))
9746
9747## eof