summaryrefslogtreecommitdiff
path: root/.local/share/Anki2/addons21/keybindings/custom_shortcuts.py
diff options
context:
space:
mode:
authorThanos Apollo <[email protected]>2022-08-04 09:50:48 +0300
committerThanos Apollo <[email protected]>2022-08-04 09:50:48 +0300
commit4ddb7273098bee179bb77e0937e560fc0100960c (patch)
treecebc2f9412e45910408a7885ca78d7dedb77de78 /.local/share/Anki2/addons21/keybindings/custom_shortcuts.py
parente83759ae9d0513024e390810ddcb18ffdd84675e (diff)
Add anki addons
Diffstat (limited to '.local/share/Anki2/addons21/keybindings/custom_shortcuts.py')
-rw-r--r--.local/share/Anki2/addons21/keybindings/custom_shortcuts.py882
1 files changed, 882 insertions, 0 deletions
diff --git a/.local/share/Anki2/addons21/keybindings/custom_shortcuts.py b/.local/share/Anki2/addons21/keybindings/custom_shortcuts.py
new file mode 100644
index 0000000..c0209a6
--- /dev/null
+++ b/.local/share/Anki2/addons21/keybindings/custom_shortcuts.py
@@ -0,0 +1,882 @@
+# Last updated to be useful for: Anki 2.1.45
+import warnings
+from anki.lang import _
+from aqt import mw
+from aqt.qt import *
+from anki.hooks import runHook,addHook,wrap
+try:
+ from aqt.utils import (
+ TR,
+ shortcut,
+ showWarning,
+ tr,
+ )
+ tr_import = True
+except:
+ from aqt.utils import showWarning
+ tr_import = False
+from aqt.toolbar import Toolbar
+from aqt.editor import Editor, EditorWebView
+try:
+ from aqt.editor import EditorMode
+ editor_mode_import = True
+except:
+ editor_mode_import = False
+from aqt.reviewer import Reviewer
+from aqt.browser import Browser
+from aqt.modelchooser import ModelChooser
+
+try:
+ from aqt.notetypechooser import NotetypeChooser
+ notetypechooser_import = True
+except:
+ notetypechooser_import = False
+from aqt.addcards import AddCards
+from anki.utils import json
+from bs4 import BeautifulSoup
+from . import cs_functions as functions
+try:
+ from aqt.operations.notetype import update_notetype_legacy
+except:
+ pass
+
+# Anki before version 2.1.20 does not use aqt.gui_hooks
+try:
+ from aqt import gui_hooks
+ new_hooks = True
+except:
+ new_hooks = False
+
+# Gets config.json as config
+config = mw.addonManager.getConfig(__name__)
+CS_CONFLICTSTR = "Custom Shortcut Conflicts: \n\n"
+# config_scuts initialized after cs_traverseKeys
+Qt_functions = {"Qt.Key_Enter": Qt.Key_Enter,
+ "Qt.Key_Return": Qt.Key_Return,
+ "Qt.Key_Escape": Qt.Key_Escape,
+ "Qt.Key_Space": Qt.Key_Space,
+ "Qt.Key_Tab": Qt.Key_Tab,
+ "Qt.Key_Backspace": Qt.Key_Backspace,
+ "Qt.Key_Delete": Qt.Key_Delete,
+ "Qt.Key_Left": Qt.Key_Left,
+ "Qt.Key_Down": Qt.Key_Down,
+ "Qt.Key_Right": Qt.Key_Right,
+ "Qt.Key_Up": Qt.Key_Up,
+ "Qt.Key_PageUp": Qt.Key_PageUp,
+ "Qt.Key_PageDown": Qt.Key_PageDown,
+ "<nop>": ""
+ }
+
+# There is a weird interaction with QShortcuts wherein if there are 2 (or more)
+# QShortcuts mapped to the same key and function and both are enabled,
+# the shortcut doesn't work
+
+# There isn't an obvious way to get the original QShortcut objects, as
+# The addons executes after the setup phase (which creates QShortcut objects)
+
+def cs_traverseKeys(Rep, D):
+ ret = {}
+ for key in D:
+ if isinstance(D[key],dict):
+ ret[key] = cs_traverseKeys(Rep,D[key])
+ elif D[key] in Rep:
+ ret[key] = Rep[D[key]]
+ else:
+ ret[key] = D[key]
+ return ret
+
+
+# Since QShortcuts cannot reveal their action (to the best of my knowledge),
+# This map reconstructs what each QShortcut is supposed to do from its id
+# The ids were found manually but so far have remained constant
+mainShortcutIds = {-1: "main debug",
+ -2: "main deckbrowser",
+ -3: "main study",
+ -4: "main add",
+ -5: "main browse",
+ -6: "main stats",
+ -7: "main sync"
+ }
+
+# This contains the processed shortcuts used for the rest of the functions
+config_scuts = cs_traverseKeys(Qt_functions,config)
+
+# The main shortcuts are now found by manually going through all QT shortcuts
+# and replacing them with their custom shortcut equivalent
+
+mainShortcutPairs = {
+ "Ctrl+:": config_scuts["main debug"],
+ "D": config_scuts["main deckbrowser"],
+ "S": config_scuts["main study"],
+ "A": config_scuts["main add"],
+ "B": config_scuts["main browse"],
+ "T": config_scuts["main stats"],
+ "Y": config_scuts["main sync"],
+ }
+
+# Finds all the shortcuts, figures out relevant ones from hardcoded shortcut check,
+# and sets it to the right one
+# This function has a side effect of changing the shortcut's id
+def cs_main_setupShortcuts():
+ mwShortcuts = mw.findChildren(QShortcut)
+ if functions.get_version() >= 50:
+ for child in mwShortcuts:
+ if child.key().toString() in mainShortcutPairs:
+ oldScut = child.key().toString()
+ newScut = mainShortcutPairs[oldScut]
+ child.setKey(newScut)
+ mainShortcutPairs.pop(oldScut) # Only replace shortcuts once (the first time would be the main shortcut)
+ else: # If possible, use the old shortcut remapping method
+ # This may be removed if the new method is fount to be more stable
+ for scut in mwShortcuts:
+ if scut.id() in mainShortcutIds:
+ scut.setKey(config_scuts[mainShortcutIds[scut.id()]])
+
+
+# Governs the shortcuts on the main toolbar
+def cs_mt_setupShortcuts():
+ m = mw.form
+ # Goes through and includes anything on the duplicates list
+ scuts_list = {
+ "m_toolbox quit": [config_scuts["m_toolbox quit"]],
+ "m_toolbox preferences": [config_scuts["m_toolbox preferences"]],
+ "m_toolbox undo": [config_scuts["m_toolbox undo"]],
+ "m_toolbox see documentation": [config_scuts["m_toolbox see documentation"]],
+ "m_toolbox switch profile": [config_scuts["m_toolbox switch profile"]],
+ "m_toolbox export": [config_scuts["m_toolbox export"]],
+ "m_toolbox import": [config_scuts["m_toolbox import"]],
+ "m_toolbox study": [config_scuts["m_toolbox study"]],
+ "m_toolbox create filtered deck": [config_scuts["m_toolbox create filtered deck"]],
+ "m_toolbox addons": [config_scuts["m_toolbox addons"]]
+ }
+ for act, key in config_scuts["m_toolbox _duplicates"].items():
+ scuts_list[functions.normalizeShortcutName(act)].append(key)
+ m.actionExit.setShortcuts(scuts_list["m_toolbox quit"])
+ m.actionPreferences.setShortcuts(scuts_list["m_toolbox preferences"])
+ m.actionUndo.setShortcuts(scuts_list["m_toolbox undo"])
+ m.actionDocumentation.setShortcuts(scuts_list["m_toolbox see documentation"])
+ m.actionSwitchProfile.setShortcuts(scuts_list["m_toolbox switch profile"])
+ m.actionExport.setShortcuts(scuts_list["m_toolbox export"])
+ m.actionImport.setShortcuts(scuts_list["m_toolbox import"])
+ m.actionStudyDeck.setShortcuts(scuts_list["m_toolbox study"])
+ m.actionCreateFiltered.setShortcuts(scuts_list["m_toolbox create filtered deck"])
+ m.actionAdd_ons.setShortcuts(scuts_list["m_toolbox addons"])
+
+# Governs the shortcuts on the review window
+# This replacement method is pretty blind but tries to minimize disruption
+# Replaces shortcuts at the start first
+# If other addons append shortcuts, this shouldn't bother those addons
+def cs_review_setupShortcuts(self, _old):
+ # More fragile replacement: For these shortcuts,
+ # Their functions are lambdas, so we can't directly address them
+ # I'm not completely satisfied by this option
+ new_scut_replacements = {
+ "Ctrl+1" : config_scuts["reviewer set flag 1"],
+ "Ctrl+2" : config_scuts["reviewer set flag 2"],
+ "Ctrl+3" : config_scuts["reviewer set flag 3"],
+ "Ctrl+4" : config_scuts["reviewer set flag 4"],
+ "1" : config_scuts["reviewer choice 1"],
+ "2" : config_scuts["reviewer choice 2"],
+ "3" : config_scuts["reviewer choice 3"],
+ "4" : config_scuts["reviewer choice 4"],
+ }
+ if functions.get_version() >= 45:
+ new_scut_replacements["Ctrl+5"] = config_scuts["reviewer set flag 5"]
+ new_scut_replacements["Ctrl+6"] = config_scuts["reviewer set flag 6"]
+ new_scut_replacements["Ctrl+7"] = config_scuts["reviewer set flag 7"]
+
+ # Less fragile replacement: For these shortcuts, address them by pointer and replace shortcut
+ # The keys are dicts because we will want to replace multiply shortcut keys
+ new_function_replacements = {
+ self.mw.onEditCurrent : [config_scuts["reviewer edit current"]],
+ self.onEnterKey : [
+ config_scuts["reviewer flip card 1"],
+ config_scuts["reviewer flip card 2"],
+ config_scuts["reviewer flip card 3"]],
+ self.replayAudio : [
+ config_scuts["reviewer replay audio 1"],
+ config_scuts["reviewer replay audio 2"],
+ ],
+ self.onMark : [config_scuts["reviewer mark card"]],
+ self.onBuryNote : [config_scuts["reviewer bury note"]],
+ self.onBuryCard : [config_scuts["reviewer bury card"]],
+ self.onSuspend : [config_scuts["reviewer suspend note"]],
+ self.onSuspendCard : [config_scuts["reviewer suspend card"]],
+ self.onDelete : [config_scuts["reviewer delete note"]],
+ self.onReplayRecorded : [config_scuts["reviewer play recorded voice"]],
+ self.onRecordVoice : [config_scuts["reviewer record voice"]],
+ self.onOptions : [config_scuts["reviewer options menu"]],
+ }
+ cuts = _old(self)
+ # Order is important: shortcut-based replacement should come first
+ functions.reviewer_find_and_replace_scuts(cuts,new_scut_replacements)
+
+ if functions.get_version() >= 20:
+ new_function_replacements[self.on_pause_audio] = [config_scuts["reviewer pause audio"]]
+ new_function_replacements[self.on_seek_backward] = [config_scuts["reviewer seek backward"]]
+ new_function_replacements[self.on_seek_forward] = [config_scuts["reviewer seek forward"]]
+ if functions.get_version() >= 33:
+ new_function_replacements[self.showContextMenu] = [config_scuts["reviewer more options"]]
+ if functions.get_version() >= 41:
+ new_function_replacements[self.on_set_due] = [config_scuts["reviewer set due date"]]
+ if functions.get_version() >= 45:
+ new_function_replacements[self.on_card_info] = [config_scuts["reviewer card info"]]
+ if functions.get_version() >= 48:
+ new_function_replacements[self.on_previous_card_info] = [config_scuts["reviewer previous card info"]]
+ functions.reviewer_find_and_replace_functions(cuts,new_function_replacements)
+ for scut in config_scuts["reviewer _duplicates"]:
+ cuts.append((config_scuts["reviewer _duplicates"][scut], self.sToF(scut)))
+ return cuts
+
+# The function to setup shortcuts on the Editor
+# Something funky is going on with the default MathJax and LaTeX shortcuts
+# It does not affect the function (as I currently know of)
+def cs_editor_setupShortcuts(self):
+ dupes = []
+ # if a third element is provided, enable shortcut even when no field is selected
+ cuts = [
+ (config_scuts["editor card layout"], self.onCardLayout, True),
+ (config_scuts["editor bold"], self.toggleBold),
+ (config_scuts["editor italic"], self.toggleItalic),
+ (config_scuts["editor underline"], self.toggleUnderline),
+ (config_scuts["editor superscript"], self.toggleSuper),
+ (config_scuts["editor subscript"], self.toggleSub),
+ (config_scuts["editor remove format"], self.removeFormat),
+ (config_scuts["editor cloze"], self.onCloze),
+ (config_scuts["editor cloze alt"], self.onCloze),
+ (config_scuts["editor cloze forced increment"], self.cs_onStdCloze),
+ (config_scuts["editor cloze no increment"], self.cs_onAltCloze),
+ (config_scuts["editor add media"], self.onAddMedia),
+ (config_scuts["editor record sound"], self.onRecSound),
+ (config_scuts["editor insert latex"], self.insertLatex),
+ (config_scuts["editor insert latex equation"], self.insertLatexEqn),
+ (config_scuts["editor insert latex math environment"], self.insertLatexMathEnv),
+ (config_scuts["editor insert mathjax inline"], self.insertMathjaxInline),
+ (config_scuts["editor insert mathjax block"], self.insertMathjaxBlock),
+ (config_scuts["editor focus tags"], self.onFocusTags, True),
+ (config_scuts["editor _extras"]["paste custom text"],
+ lambda text=config_scuts["Ω custom paste text"]: self.customPaste(text)),
+ ]
+ # Due to the svelte changes, these shortcuts "break"
+ # in the sense that they no longer correspond to the most recent shortcuts
+ cuts += [(config_scuts["editor foreground"], self.onForeground),
+ (config_scuts["editor change col"], self.onChangeCol),
+ ]
+ if functions.get_version() >= 45:
+ if functions.get_version() >= 50:
+ pass
+ else:
+ cuts += [
+ (config_scuts["editor html edit"], lambda:
+ self.web.eval(
+ """{const currentField = getCurrentField(); if (currentField) { currentField.toggleHtmlEdit(); }}"""
+ )),
+ ]
+ cuts += [
+ (config_scuts["editor toggle sticky current"], self.csToggleStickyCurrent),
+ (config_scuts["editor toggle sticky all"], self.csToggleStickyAll),
+ (config_scuts["editor block indent"], lambda:
+ self.web.eval(
+ """
+ {
+ document.execCommand("indent");
+ }
+ """
+ )
+ ),
+ (config_scuts["editor block outdent"], lambda:
+ self.web.eval(
+ """
+ {
+ document.execCommand("outdent")
+ }
+ """
+ )
+ ),
+ (config_scuts["editor list insert unordered"], lambda:
+ self.web.eval(
+ """
+ document.execCommand("insertUnorderedList");
+ """
+ )
+ ),
+ (config_scuts["editor list insert ordered"], lambda:
+ self.web.eval(
+ """
+ document.execCommand("insertOrderedList");
+ """
+ )
+ ),
+ ]
+ else:
+ cuts += [
+ (config_scuts["editor html edit"], self.onHtmlEdit),]
+
+ for scut in config_scuts["editor _duplicates"]:
+ if self.sToF(scut):
+ dupes.append((config_scuts["editor _duplicates"][scut],)+self.sToF(scut))
+ cuts += dupes
+ for label in config_scuts["editor _pastes"]:
+ if label in config_scuts["Ω custom paste extra texts"]:
+ scut = config_scuts["editor _pastes"][label]
+ temp = config_scuts["Ω custom paste extra texts"][label]
+ cuts.append((scut, lambda text=temp: self.customPaste(text)))
+ # There is a try-except clause to handle 2.1.0 version, which does not have this shortcut
+ try:
+ cuts.append((config_scuts["editor insert mathjax chemistry"], self.insertMathjaxChemistry))
+ except AttributeError:
+ pass
+ if new_hooks:
+ gui_hooks.editor_did_init_shortcuts(cuts, self)
+ else:
+ runHook("setupEditorShortcuts", cuts, self)
+ for row in cuts:
+ if len(row) == 2:
+ keys, fn = row
+ fn = self._addFocusCheck(fn)
+ else:
+ keys, fn, _ = row
+ scut = QShortcut(QKeySequence(keys), self.widget, activated=fn)
+
+# Wrapper function to add another shortcut to change note type
+# Not with the other custom shortcut editor functions because
+# the Anki functionality handling card type is not
+# in the Editor itself
+def cs_editorChangeNoteType(self):
+ NOTE_TYPE_STR = "editor change note type"
+ new_scuts = {config_scuts[NOTE_TYPE_STR]}
+ for act, key in config_scuts["editor _duplicates"].items():
+ if functions.normalizeShortcutName(act) == NOTE_TYPE_STR:
+ new_scuts.add(key)
+ for scut in new_scuts:
+ if functions.get_version() >= 41:
+ QShortcut(QKeySequence(scut), self._widget, activated=self.on_activated)
+ elif functions.get_version() >= 36:
+ QShortcut(QKeySequence(scut), self.widget, activated=self.on_activated)
+ else:
+ QShortcut(QKeySequence(scut), self.widget, activated=self.onModelChange)
+
+def cs_editorNotetypeChooser(self, show_label: bool):
+ NOTE_TYPE_STR = "editor change note type"
+ new_scuts = {config_scuts[NOTE_TYPE_STR]}
+ for act, key in config_scuts["editor _duplicates"].items():
+ if functions.normalizeShortcutName(act) == NOTE_TYPE_STR:
+ new_scuts.add(key)
+ for scut in new_scuts:
+ # Since the hard-coded shortcut for this is Ctrl+N,
+ # Don't destroy the existing shortcut
+ # (has the side effect of leaving Ctrl+N assigned at all times)
+ if scut == "Ctrl+N":
+ continue
+ qconnect(QShortcut(QKeySequence(scut), self._widget).activated,
+ self.on_button_activated
+ )
+
+def cs_editorUpdateStickyPins(self, stickies, model):
+ stickiesStr = ""
+
+ # Hack: the svelte interface exposes a function "setSticky" which manually sets the sticky displays of all the pins
+ # Manually create a set of stickies in order to set the sticky pins to the right display value
+ stickiesStr += "["
+ firstInput = True
+ for sticky in stickies:
+ if not firstInput:
+ stickiesStr += ","
+ firstInput = False
+ stickiesStr += ("true" if sticky else "false")
+ stickiesStr += "]"
+
+ try:
+ update_notetype_legacy(parent=self.mw, notetype=model).run_in_background(
+ initiator=self
+ )
+ self.web.eval("setSticky({})".format(stickiesStr))
+ except:
+ pass
+
+def cs_editorToggleSticky(self, index: int):
+ model = self.note.note_type()
+ flds = model["flds"]
+ stickies = []
+ flds[index]["sticky"] = not flds[index]["sticky"]
+ for fld in flds:
+ stickies.append(fld["sticky"])
+ cs_editorUpdateStickyPins(self, stickies, model)
+
+
+# Toggle sticky on all fields
+# "Toggle" is interpreted as it is in Anki:
+# If any sticky is on, turn everything off
+# If all stickies are off, turn everything on
+def cs_editorToggleStickyAll(self):
+ model = self.note.note_type()
+ flds = model["flds"]
+
+ any_sticky = any([fld["sticky"] for fld in flds])
+ stickies = []
+ for fld in flds:
+ if not any_sticky or fld["sticky"]:
+ fld["sticky"] = not fld["sticky"]
+ stickies.append(fld["sticky"])
+ cs_editorUpdateStickyPins(self, stickies, model)
+
+# Toggle sticky on the current field
+def cs_editorToggleStickyCurrent(self):
+ if self.currentField is not None:
+ cs_editorToggleSticky(self,self.currentField)
+
+# Intercepts the bridge functions normally used to toggle stickiness
+def cs_captureBridgeToggleSticky(self, cmd, _old):
+ # If we intercept a "toggle sticky all" command, then
+ if cmd.startswith("toggleStickyAll") and config_scuts["editor toggle sticky all"] != "Shift+F9":
+ model = self.note.note_type()
+ return model["flds"]["sticky"]
+ elif cmd.startswith("toggleSticky") and config_scuts["editor toggle sticky current"] != "F9":
+ model = self.note.note_type()
+ (_, num) = cmd.split(":",1)
+ idx = int(num)
+
+ return model["flds"][idx]["sticky"]
+ return _old(self,cmd)
+
+
+# Wrapper function to change the shortcut to add a card
+# Not with the other custom shortcut editor functions because
+# the add card button is not within the Editor class
+def cs_editorAddCard(self):
+ ADD_CARD_STR = "editor confirm add card"
+ self.addButton.setShortcut(QKeySequence(config_scuts[ADD_CARD_STR]))
+ for act, key in config_scuts["editor _duplicates"].items():
+ if functions.normalizeShortcutName(act) == ADD_CARD_STR:
+ QShortcut(QKeySequence(key), self, activated=self.addCards)
+
+def cs_editorChangeDeck(self):
+ CHANGE_DECK_STR = "editor change deck"
+ new_scuts = {config_scuts[CHANGE_DECK_STR]}
+ for act, key in config_scuts["editor _duplicates"].items():
+ if functions.normalizeShortcutName(act) == CHANGE_DECK_STR:
+ new_scuts.add(key)
+ for scut in new_scuts:
+ QShortcut(QKeySequence(scut), self.widget, activated=self.cs_changeDeck)
+
+# IMPLEMENTS Browser shortcuts
+def cs_browser_setupShortcuts(self):
+ f = self.form
+ try:
+ f.previewButton.setShortcut(config_scuts["window_browser preview"])
+ except:
+ pass
+ try:
+ f.action_set_due_date.setShortcut(config_scuts["window_browser reschedule"])
+ except:
+ f.actionReschedule.setShortcut(config_scuts["window_browser reschedule"])
+ f.actionSelectAll.setShortcut(config_scuts["window_browser select all"])
+ f.actionUndo.setShortcut(config_scuts["window_browser undo"])
+ f.actionInvertSelection.setShortcut(config_scuts["window_browser invert selection"])
+ f.actionFind.setShortcut(config_scuts["window_browser find"])
+ f.actionNote.setShortcut(config_scuts["window_browser goto note"])
+ f.actionNextCard.setShortcut(config_scuts["window_browser goto next note"])
+ f.actionPreviousCard.setShortcut(config_scuts["window_browser goto previous note"])
+ f.actionChangeModel.setShortcut(config_scuts["window_browser change note type"])
+ f.actionGuide.setShortcut(config_scuts["window_browser guide"])
+ f.actionFindReplace.setShortcut(config_scuts["window_browser find and replace"])
+ try:
+ f.actionTags.setShortcut(config_scuts["window_browser filter"])
+ except AttributeError:
+ f.actionSidebarFilter.setShortcut(config_scuts["window_browser filter"])
+ f.actionCardList.setShortcut(config_scuts["window_browser goto card list"])
+ f.actionReposition.setShortcut(config_scuts["window_browser reposition"])
+ f.actionFirstCard.setShortcut(config_scuts["window_browser first card"])
+ f.actionLastCard.setShortcut(config_scuts["window_browser last card"])
+ f.actionClose.setShortcut(config_scuts["window_browser close"])
+ f.action_Info.setShortcut(config_scuts["window_browser info"])
+ f.actionAdd_Tags.setShortcut(config_scuts["window_browser add tag"])
+ f.actionRemove_Tags.setShortcut(config_scuts["window_browser remove tag"])
+ f.actionToggle_Suspend.setShortcut(config_scuts["window_browser suspend"])
+ f.actionDelete.setShortcut(config_scuts["window_browser delete"])
+ f.actionAdd.setShortcut(config_scuts["window_browser add note"])
+ f.actionChange_Deck.setShortcut(config_scuts["window_browser change deck"])
+ f.actionRed_Flag.setShortcut(config_scuts["window_browser flag_red"])
+ try:
+ f.actionOrange_Flag.setShortcut(config_scuts["window_browser flag_orange"])
+ except AttributeError:
+ f.actionPurple_Flag.setShortcut(config_scuts["window_browser flag_orange"])
+ f.actionGreen_Flag.setShortcut(config_scuts["window_browser flag_green"])
+ f.actionBlue_Flag.setShortcut(config_scuts["window_browser flag_blue"])
+ f.actionSidebar.setShortcut(config_scuts["window_browser goto sidebar"])
+ f.actionToggle_Mark.setShortcut(config_scuts["window_browser toggle mark"])
+ f.actionClear_Unused_Tags.setShortcut(config_scuts["window_browser clear unused tags"])
+ f.actionFindDuplicates.setShortcut(config_scuts["window_browser find duplicates"])
+ f.actionSelectNotes.setShortcut(config_scuts["window_browser select notes"])
+ f.actionManage_Note_Types.setShortcut(config_scuts["window_browser manage note types"])
+ try:
+ f.action_forget.setShortcut(config_scuts["window_browser forget card"])
+ except AttributeError:
+ pass
+
+# Mimics the style of other Anki functions, analogue of customPaste
+# Note that the saveNow function used earler takes the cursor to the end of the line,
+# as it is meant to save work before entering a new window
+def cs_editor_custom_paste(self, text):
+ self._customPaste(text)
+
+# Mimics the style of other Anki functions, analogue of _customPaste
+def cs_uEditor_custom_paste(self, text):
+ html = text
+ if config_scuts["Ω custom paste end style"].upper() == "Y":
+ html += "</span>\u200b"
+ with warnings.catch_warnings() as w:
+ warnings.simplefilter('ignore', UserWarning)
+ html = str(BeautifulSoup(html, "html.parser"))
+ self.doPaste(html,True,True)
+
+# detects shortcut conflicts
+# Gets all the shortcuts in a given object of the form {name: scut, ...} and names them
+# Returns a dictionary of the form {scut: [labels of objects with that scut], ...}
+def cs_getAllScuts(obj, strCont):
+ res = {}
+ for key in obj:
+ if isinstance(obj[key], dict):
+ rec = cs_getAllScuts(obj[key], key + " in " + strCont)
+ for term in rec:
+ if term in res:
+ res[term] += rec[term]
+ else:
+ res[term] = rec[term]
+ else:
+ text_scut = obj[key].upper()
+ if text_scut in res:
+ res[text_scut].append(key + " in " + strCont)
+ else:
+ res[text_scut] = [key + " in " + strCont]
+ return res
+
+# Ignores the Add-on (Ω) options
+def cs_conflictDetect():
+ if config["Ω enable conflict warning"].upper() != "Y":
+ return
+ ext_list = {}
+ for e in config:
+ sub = e[0:(e.find(" "))]
+ val = config[e]
+ if sub == "Ω":
+ continue
+ if sub not in ext_list:
+ ext_list[sub] = {}
+ if isinstance(val, dict):
+ scuts = cs_getAllScuts(val, e)
+ for scut in scuts:
+ if scut in ext_list[sub]:
+ ext_list[sub][scut] += scuts[scut]
+ else:
+ ext_list[sub][scut] = scuts[scut]
+ else:
+ text_val = val.upper()
+ if text_val in ext_list[sub]:
+ ext_list[sub][text_val].append(e)
+ else:
+ ext_list[sub][text_val] = [e]
+ conflictStr = CS_CONFLICTSTR
+ conflict = False
+ for key in ext_list:
+ for k in ext_list[key]:
+ if len(ext_list[key][k]) == 1:
+ continue
+ if k == "<NOP>":
+ continue
+ if not k:
+ continue
+ conflict = True
+ conflictStr += ", ".join(ext_list[key][k])
+ conflictStr += "\nshare '" + k + "' as a shortcut\n\n"
+ if conflict:
+ conflictStr += "\nThese shortcuts will not work.\n"
+ conflictStr += "Please change them in the config.json."
+ showWarning(conflictStr)
+
+def cs_toolbarCenterLinks(self):
+ try:
+ links = [
+ self.create_link(
+ "decks",
+ tr(TR.ACTIONS_DECKS),
+ self._deckLinkHandler,
+ tip=tr(TR.ACTIONS_SHORTCUT_KEY, val=config_scuts["main deckbrowser"]),
+ id="decks",
+ ),
+ self.create_link(
+ "add",
+ tr(TR.ACTIONS_ADD),
+ self._addLinkHandler,
+ tip=tr(TR.ACTIONS_SHORTCUT_KEY, val=config_scuts["main add"]),
+ id="add",
+ ),
+ self.create_link(
+ "browse",
+ tr(TR.QT_MISC_BROWSE),
+ self._browseLinkHandler,
+ tip=tr(TR.ACTIONS_SHORTCUT_KEY, val=config_scuts["main browse"]),
+ id="browse",
+ ),
+ self.create_link(
+ "stats",
+ tr(TR.QT_MISC_STATS),
+ self._statsLinkHandler,
+ tip=tr(TR.ACTIONS_SHORTCUT_KEY, val=config_scuts["main stats"]),
+ id="stats",
+ ),
+ ]
+
+ links.append(self._create_sync_link())
+
+ gui_hooks.top_toolbar_did_init_links(links, self)
+
+ return "\n".join(links)
+ except:
+ pass
+ try:
+ links = [
+ self.create_link(
+ "decks",
+ _("Decks"),
+ self._deckLinkHandler,
+ tip=_("Shortcut key: %s") % config_scuts["main deckbrowser"],
+ id="decks",
+ ),
+ self.create_link(
+ "add",
+ _("Add"),
+ self._addLinkHandler,
+ tip=_("Shortcut key: %s") % config_scuts["main add"],
+ id="add",
+ ),
+ self.create_link(
+ "browse",
+ _("Browse"),
+ self._browseLinkHandler,
+ tip=_("Shortcut key: %s") % config_scuts["main browse"],
+ id="browse",
+ ),
+ self.create_link(
+ "stats",
+ _("Stats"),
+ self._statsLinkHandler,
+ tip=_("Shortcut key: %s") % config_scuts["main stats"],
+ id="stats",
+ ),
+ ]
+
+ links.append(self._create_sync_link())
+
+ gui_hooks.top_toolbar_did_init_links(links, self)
+
+ return "\n".join(links)
+ except:
+ links = [
+ ["decks", _("Decks"), _("Shortcut key: %s") % config_scuts["main deckbrowser"]],
+ ["add", _("Add"), _("Shortcut key: %s") % config_scuts["main add"]],
+ ["browse", _("Browse"), _("Shortcut key: %s") % config_scuts["main browse"]],
+ ["stats", _("Stats"), _("Shortcut key: %s") % config_scuts["main stats"]],
+ ["sync", _("Sync"), _("Shortcut key: %s") % config_scuts["main sync"]],
+ ]
+ return self._linkHTML(links)
+
+def cs_browser_basicFilter(self, txt):
+ self.form.searchEdit.lineEdit().setText(txt)
+ self.onSearchActivated()
+
+def cs_browser_concatFilter(self, txt):
+ cur = str(self.form.searchEdit.lineEdit().text())
+ if cur and cur != self._searchPrompt:
+ txt = cur + " " + txt
+ self.form.searchEdit.lineEdit().setText(txt)
+ self.onSearchActivated()
+
+def cs_browser_orConcatFilter(self, txt):
+ cur = str(self.form.searchEdit.lineEdit().text())
+ if cur:
+ txt = cur + " or " + txt
+ self.form.searchEdit.lineEdit().setText(txt)
+ self.onSearchActivated()
+
+# Inserts the custom filter shortcuts upon browser startup
+def cs_browser_setupEditor(self):
+ if functions.get_version() >= 50 and editor_mode_import:
+ QShortcut(QKeySequence(config_scuts["window_browser preview"]), self, self.onTogglePreview)
+ def add_preview_button(editor):
+ editor._links["preview"] = lambda _editor: self.onTogglePreview()
+ gui_hooks.editor_did_init.append(add_preview_button)
+ self.editor = Editor(self.mw, self.form.fieldsArea, self, editor_mode=EditorMode.BROWSER,)
+ gui_hooks.editor_did_init.remove(add_preview_button)
+ elif functions.get_version() >= 45:
+ QShortcut(QKeySequence(config_scuts["window_browser preview"]), self, self.onTogglePreview)
+ def add_preview_button(editor):
+ preview_shortcut = config_scuts["window_browser preview"]
+
+ editor._links["preview"] = lambda _editor: self.onTogglePreview()
+ editor.web.eval(
+ "$editorToolbar.then(({ notetypeButtons }) => notetypeButtons.appendButton({ component: editorToolbar.PreviewButton, id: 'preview' }));"
+ )
+ gui_hooks.editor_did_init.append(add_preview_button)
+ self.editor = Editor(self.mw, self.form.fieldsArea, self)
+ gui_hooks.editor_did_init.remove(add_preview_button)
+ elif functions.get_version() >= 39:
+ def add_preview_button(leftbuttons, editor):
+ preview_shortcut = config_scuts["window_browser preview"]
+ leftbuttons.insert(
+ 0,
+ editor.addButton(
+ None,
+ "preview",
+ lambda _editor: self.onTogglePreview(),
+ tr(
+ TR.BROWSING_PREVIEW_SELECTED_CARD,
+ val=shortcut(preview_shortcut),
+ ),
+ tr(TR.ACTIONS_PREVIEW),
+ id="previewButton",
+ keys=preview_shortcut,
+ disables=False,
+ rightside=False,
+ toggleable=True,
+ ),
+ )
+ gui_hooks.editor_did_init_left_buttons.append(add_preview_button)
+ self.editor = Editor(self.mw, self.form.fieldsArea, self)
+ gui_hooks.editor_did_init_left_buttons.remove(add_preview_button)
+ else:
+ self.editor = Editor(self.mw, self.form.fieldsArea, self)
+ self.csFilterScuts = {}
+ self.csFilterFuncs = {}
+ self.csCatFilterScuts = {}
+ self.csCatFilterFuncs = {}
+ self.csOCatFilterScuts = {}
+ self.csOCatFilterFuncs = {}
+ for filt in config_scuts["window_browser _filters"]:
+ scut = config_scuts["window_browser _filters"][filt]
+ if isinstance(scut, dict):
+ continue
+ self.csFilterFuncs[filt] = lambda txt=filt: cs_browser_basicFilter(self, txt)
+ self.csFilterScuts[filt] = QShortcut(QKeySequence(scut), self)
+ self.csFilterScuts[filt].activated.connect(self.csFilterFuncs[filt])
+ if "_concat" in config_scuts["window_browser _filters"]:
+ for filt in config_scuts["window_browser _filters"]["_concat"]:
+ scut = config_scuts["window_browser _filters"]["_concat"][filt]
+ self.csCatFilterFuncs[filt] = lambda txt=filt: cs_browser_concatFilter(self, txt)
+ self.csCatFilterScuts[filt] = QShortcut(QKeySequence(scut), self)
+ self.csCatFilterScuts[filt].activated.connect(self.csCatFilterFuncs[filt])
+ if "_orConcat" in config_scuts["window_browser _filters"]:
+ for filt in config_scuts["window_browser _filters"]["_orConcat"]:
+ scut = config_scuts["window_browser _filters"]["_orConcat"][filt]
+ self.csOCatFilterFuncs[filt] = lambda txt=filt: cs_browser_orConcatFilter(self, txt)
+ self.csOCatFilterScuts[filt] = QShortcut(QKeySequence(scut), self)
+ self.csOCatFilterScuts[filt].activated.connect(self.csOCatFilterFuncs[filt])
+ if config_scuts["window_browser save current filter"]:
+ self.csSaveFilterScut = QShortcut(QKeySequence(config_scuts["window_browser save current filter"]), self)
+ self.csSaveFilterScut.activated.connect(self._onSaveFilter)
+ if config_scuts["window_browser remove current filter"]:
+ self.csRemoveFilterScut = QShortcut(QKeySequence(config_scuts["window_browser remove current filter"]), self)
+ self.csRemoveFilterScut.activated.connect(self.csRemoveFilterFunc)
+
+# Corresponds to _setup_tools in the SidebarToolbar class in Anki 2.1.45
+sidebar_tool_names = [
+ "window_browser sidebar search",
+ "window_browser sidebar select"
+ ]
+
+
+def cs_sidebar_setup_tools(self):
+ from aqt.theme import theme_manager
+ for row, tool in enumerate(self._tools):
+ action = self.addAction(
+ theme_manager.icon_from_resources(tool[1]), tool[2]()
+ )
+ action.setCheckable(True)
+ # If we are aware of the row, set it in the tools
+ # otherwise, use the default
+ action.setShortcut(
+ config_scuts[sidebar_tool_names[row]]
+ if row < len(sidebar_tool_names) else
+ f"Alt+{row + 1}"
+ )
+ self._action_group.addAction(action)
+ # always start with first tool
+ active = 0
+ self._action_group.actions()[active].setChecked(True)
+ self.sidebar.tool = self._tools[active][0]
+
+def cs_injectCloseShortcut(scuts):
+ def inject_shortcut(self):
+ try:
+ from aqt.utils import is_mac
+ isMac = is_mac
+ except:
+ from aqt.utils import isMac
+ cutExistingShortcut = False
+ for scut in scuts:
+ if scut == "<default>":
+ continue
+ addedShortcut = False
+ if isMac and not cutExistingShortcut:
+ for child in self.findChildren(QShortcut):
+ if child.key().toString() == 'Ctrl+W':
+ child.setKey(scut)
+ addedShortcut = cutExistingShortcut = True
+ if not addedShortcut:
+ shortcut = QShortcut(QKeySequence(scut), self)
+ qconnect(shortcut.activated, self.reject)
+ setattr(self, "_closeShortcut", shortcut)
+ return inject_shortcut
+
+# Functions that execute on startup
+if config_scuts["Ω enable main"].upper() == 'Y':
+ Toolbar._centerLinks = cs_toolbarCenterLinks
+ cs_main_setupShortcuts()
+if config_scuts["Ω enable editor"].upper() == 'Y':
+ Editor.cs_changeDeck = functions.editor_changeDeck
+ Editor.sToF = functions.editor_sToF
+ Editor.cs_u_onAltCloze = lambda self: functions.cs_editor_generate_cloze(self, altModifier=True)
+ Editor.cs_u_onStdCloze = lambda self: functions.cs_editor_generate_cloze(self, altModifier=False)
+ Editor.cs_onAltCloze = functions.cs_editor_on_alt_cloze
+ Editor.cs_onStdCloze = functions.cs_editor_on_std_cloze
+ Editor.customPaste = cs_editor_custom_paste
+ Editor._customPaste = cs_uEditor_custom_paste
+ Editor.setupShortcuts = cs_editor_setupShortcuts
+ Editor.setupShortcuts = wrap(Editor.setupShortcuts, cs_editorChangeDeck)
+ if functions.get_version() >= 45:
+ Editor.csToggleStickyCurrent = cs_editorToggleStickyCurrent
+ Editor.csToggleStickyAll = cs_editorToggleStickyAll
+ Editor.onBridgeCmd = wrap(Editor.onBridgeCmd, cs_captureBridgeToggleSticky, "around")
+ if notetypechooser_import:
+ NotetypeChooser._setup_ui = wrap(NotetypeChooser._setup_ui, cs_editorNotetypeChooser)
+ ModelChooser.setupModels = wrap(ModelChooser.setupModels, cs_editorChangeNoteType)
+ AddCards.setupButtons = wrap(AddCards.setupButtons, cs_editorAddCard)
+ try:
+ gui_hooks.add_cards_did_init.append(cs_injectCloseShortcut([config_scuts["editor add card close window"]]))
+ except:
+ pass
+if config_scuts["Ω enable reviewer"].upper() == 'Y':
+ Reviewer._shortcutKeys = wrap(Reviewer._shortcutKeys, cs_review_setupShortcuts, "around")
+ Reviewer.sToF = functions.review_sToF
+if config_scuts["Ω enable m_toolbox"].upper() == 'Y':
+ cs_mt_setupShortcuts()
+# Hooks to setup shortcuts at the right time
+if config_scuts["Ω enable window_browser"].upper() == 'Y':
+ Browser.csRemoveFilterFunc = functions.remove_filter
+ Browser.setupEditor = cs_browser_setupEditor
+ addHook('browser.setupMenus', cs_browser_setupShortcuts)
+ if functions.get_version() >= 45:
+ from aqt.browser import SidebarToolbar
+ SidebarToolbar._setup_tools = cs_sidebar_setup_tools
+
+# Fun fact: the stats window shortcut can also be customized (very slightly)
+# Due to the added complexity of handling this relative to what is probably zero demand, this will remain unimplemented for the time being
+# gui_hooks.stats_dialog_will_show.append(cs_injectCloseShortcut([config_scuts["stats close window"]]))
+# The deck options window is another feature that probably won't be implemented unless requested
+# gui_hooks.deck_options_did_load.append(cs_injectCloseShortcut([config_scuts["editor deck options close window"]]))
+
+# Detects all conflicts, regardless of enable status
+cs_conflictDetect()
+
+# Redraws the toolbar with the new shortcuts
+mw.toolbar.draw()