diff options
Diffstat (limited to '.local/share/Anki2/addons21/keybindings')
-rw-r--r-- | .local/share/Anki2/addons21/keybindings/__init__.py | 2 | ||||
-rw-r--r-- | .local/share/Anki2/addons21/keybindings/config.json | 152 | ||||
-rw-r--r-- | .local/share/Anki2/addons21/keybindings/config.md | 309 | ||||
-rw-r--r-- | .local/share/Anki2/addons21/keybindings/cs_functions.py | 231 | ||||
-rw-r--r-- | .local/share/Anki2/addons21/keybindings/custom_shortcuts.py | 882 | ||||
-rw-r--r-- | .local/share/Anki2/addons21/keybindings/meta.json | 1 |
6 files changed, 1577 insertions, 0 deletions
diff --git a/.local/share/Anki2/addons21/keybindings/__init__.py b/.local/share/Anki2/addons21/keybindings/__init__.py new file mode 100644 index 0000000..05b9932 --- /dev/null +++ b/.local/share/Anki2/addons21/keybindings/__init__.py @@ -0,0 +1,2 @@ +from . import custom_shortcuts +from . import cs_functions diff --git a/.local/share/Anki2/addons21/keybindings/config.json b/.local/share/Anki2/addons21/keybindings/config.json new file mode 100644 index 0000000..892e614 --- /dev/null +++ b/.local/share/Anki2/addons21/keybindings/config.json @@ -0,0 +1,152 @@ +{ + "main debug":"Ctrl+:", + "main deckbrowser":"D", + "main study":"S", + "main add":"A", + "main browse":"B", + "main stats":"T", + "main sync":"Y", + "editor card layout":"Ctrl+L", + "editor bold":"Ctrl+B", + "editor italic":"Ctrl+I", + "editor underline":"Ctrl+U", + "editor superscript":"Ctrl++", + "editor subscript":"Ctrl+=", + "editor remove format":"Ctrl+R", + "editor foreground":"F7", + "editor change col":"F8", + "editor change deck":"<nop>", + "editor cloze":"Ctrl+Shift+C", + "editor cloze alt":"Ctrl+Shift+Alt+C", + "editor cloze forced increment":"<nop>", + "editor cloze no increment":"<nop>", + "editor add media":"F3", + "editor record sound":"F5", + "editor insert latex":"Ctrl+T, T", + "editor insert latex equation":"Ctrl+T, E", + "editor insert latex math environment":"Ctrl+T, M", + "editor insert mathjax inline":"Ctrl+M, M", + "editor insert mathjax block":"Ctrl+M, E", + "editor insert mathjax chemistry":"Ctrl+M, C", + "editor html edit":"Ctrl+Shift+X", + "editor focus tags":"Ctrl+Shift+T", + "editor confirm add card":"Ctrl+Return", + "editor add card close window":"<default>", + "editor change note type":"Ctrl+N", + "editor toggle sticky current":"F9", + "editor toggle sticky all":"Shift+F9", + "editor block indent": "<nop>", + "editor block outdent": "<nop>", + "editor list insert ordered": "<nop>", + "editor list insert unordered": "<nop>", + "editor _extras": { + "paste custom text":"<nop>" + }, + "editor _pastes": { + }, + "editor _duplicates": { + }, + "reviewer edit current":"E", + "reviewer flip card 1":" ", + "reviewer flip card 2":"Qt.Key_Return", + "reviewer flip card 3":"Qt.Key_Enter", + "reviewer replay audio 1":"r", + "reviewer replay audio 2":"F5", + "reviewer set flag 1":"Ctrl+1", + "reviewer set flag 2":"Ctrl+2", + "reviewer set flag 3":"Ctrl+3", + "reviewer set flag 4":"Ctrl+4", + "reviewer set flag 5":"Ctrl+5", + "reviewer set flag 6":"Ctrl+6", + "reviewer set flag 7":"Ctrl+7", + "reviewer set flag 0":"Ctrl+0", + "reviewer mark card":"*", + "reviewer bury note":"=", + "reviewer bury card":"-", + "reviewer suspend note":"!", + "reviewer suspend card":"@", + "reviewer delete note":"Ctrl+Delete", + "reviewer play recorded voice":"v", + "reviewer record voice":"Shift+v", + "reviewer set due date":"Ctrl+Shift+D", + "reviewer options menu":"o", + "reviewer choice 1":"1", + "reviewer choice 2":"2", + "reviewer choice 3":"3", + "reviewer choice 4":"4", + "reviewer pause audio":"5", + "reviewer seek backward":"6", + "reviewer seek forward":"7", + "reviewer card info":"I", + "reviewer previous card info":"Alt+I", + "reviewer _duplicates":{ + }, + "reviewer more options":"M", + "m_toolbox quit":"Ctrl+Q", + "m_toolbox preferences":"Ctrl+P", + "m_toolbox undo":"Ctrl+Z", + "m_toolbox see documentation":"F1", + "m_toolbox switch profile":"Ctrl+Shift+P", + "m_toolbox export":"Ctrl+E", + "m_toolbox import":"Ctrl+Shift+I", + "m_toolbox study":"/", + "m_toolbox create filtered deck":"F", + "m_toolbox addons":"Ctrl+Shift+A", + "m_toolbox _duplicates":{ + }, + "window_browser preview": "Ctrl+Shift+P", + "window_browser reschedule":"Ctrl+Shift+D", + "window_browser select all":"Ctrl+Alt+A", + "window_browser undo":"Ctrl+Alt+Z", + "window_browser invert selection":"Ctrl+Alt+S", + "window_browser find":"Ctrl+F", + "window_browser goto note":"Ctrl+Shift+N", + "window_browser goto next note":"Ctrl+N", + "window_browser goto previous note":"Ctrl+P", + "window_browser guide":"F1", + "window_browser change note type":"Ctrl+Shift+M", + "window_browser find and replace":"Ctrl+Alt+F", + "window_browser filter":"Ctrl+Shift+F", + "window_browser goto card list":"Ctrl+Shift+L", + "window_browser reposition":"Ctrl+Shift+S", + "window_browser first card":"Home", + "window_browser last card":"End", + "window_browser close":"Ctrl+W", + "window_browser info":"Ctrl+Shift+I", + "window_browser add tag":"Ctrl+Shift+A", + "window_browser remove tag":"Ctrl+Alt+Shift+A", + "window_browser suspend":"Ctrl+J", + "window_browser delete":"Ctrl+Del", + "window_browser add note":"Ctrl+E", + "window_browser change deck":"Ctrl+D", + "window_browser flag_red":"Ctrl+1", + "window_browser flag_orange":"Ctrl+2", + "window_browser flag_green":"Ctrl+3", + "window_browser flag_blue":"Ctrl+4", + "window_browser goto sidebar":"Ctrl+Shift+R", + "window_browser toggle mark":"Ctrl+K", + "window_browser clear unused tags":"<nop>", + "window_browser find duplicates":"<nop>", + "window_browser select notes":"<nop>", + "window_browser manage note types":"<nop>", + "window_browser save current filter":"<nop>", + "window_browser remove current filter":"<nop>", + "window_browser sidebar search": "Alt+1", + "window_browser sidebar select": "Alt+2", + "window_browser forget card": "Ctrl+Alt+N", + "window_browser _filters":{ + "_concat":{}, + "_orConcat":{} + }, + "Ω enable main": "y", + "Ω enable editor": "y", + "Ω enable reviewer": "y", + "Ω enable m_toolbox": "y", + "Ω enable window_browser": "y", + "Ω enable conflict warning": "y", + "Ω custom paste text": "", + "Ω custom paste extra texts": { + }, + "Ω custom paste end style": "n" +} + diff --git a/.local/share/Anki2/addons21/keybindings/config.md b/.local/share/Anki2/addons21/keybindings/config.md new file mode 100644 index 0000000..eca22df --- /dev/null +++ b/.local/share/Anki2/addons21/keybindings/config.md @@ -0,0 +1,309 @@ +### To disable a shortcut, set that shortcut to `<nop>`. + +### For macOS users, `Ctrl` is `⌘`, `Alt` is `⌥`, and `Meta` is `^` (control key). + + +## Editor Options + +"editor \_duplicates": Takes functions and binds them to new shortcuts. + +This object takes inputs of the form "(function keyword)":"(shortcut)", separated by commas. (e.g. {"editor bold":"Ctrl+Shift+9", "editor cloze":"Alt+Shift+2"}) + +If you want to set more than one duplicate per shortcut for the reviewer, you can do so by adding the suffix "+++" *immediately* after the shortcut name followed by any distinct string. (e.g. {"editor bold+++ first": "Ctrl+Shift+0", "editor bold+++ 2nd": "Ctrl+Shift+8"}) + +All the keywords are exactly the same as the keywords used in the json file, making the lines copy-pastable. + +"editor add media": Add external media + +"editor add card close window": In the Add Card dialog, closes the given window. For some reason, Macs have a default shortcut for this while all other OSes don't. Since the default is OS dependent, it is given to be `<default>` rather than any specified value. + +"editor block indent": Indents the active field. Useful for indenting lists. + +"editor block outdent": Outdents (unindents) the active field. Useful for unindenting lists. + +"editor bold": Toggle bold + +"editor card layout": Change Card Layout + +"editor change col": Change text color + +*Note that this shortcut currently exclusively uses the legacy version of color editing. This is incompatible with the new color editing as of Anki 2.1.49. If you don't want to use the old version, disable this shortcut by setting it to <nop>.* + +"editor cloze": Insert cloze (increments cloze ID if `Alt` is *not* part of your keybind, so `Ctrl+Shift+C` does increment ID, while `Ctrl+Shift+Alt+C` does not) + +"editor cloze alt": Insert cloze (behaves identically to "editor cloze") + +"editor cloze forced increment": Insert cloze, **always increments the cloze ID number**, *does not activate cloze add-ons* + +"editor cloze no increment": Insert cloze, **never increments the cloze ID number**, *does not activate cloze add-ons* + +The reason for the seemingly weird editor cloze behavior is Anki's internal implementation of the cloze insertion shortcuts. Anki's implementation is used in "editor cloze" and "editor cloze alt" and should play well with other addons, while a different implementation is used for "forced increment" and "no increment". + +"editor change note type": Change the type of the given note + +"editor confirm add card": In the add card editing window, adds the card + +"editor focus tags": Switch focus to the Tags field + +"editor foreground": Set foreground color + +*Note that this shortcut currently exclusively uses the legacy version of color editing. This is incompatible with the new color editing as of Anki 2.1.49. If you don't want to use the old version, disable this shortcut by setting it to <nop>.* + +"editor html edit": Edit the card's HTML + +"editor insert latex": Insert LaTeX formatted text + +"editor insert latex equation": Insert a LaTeX equation + +"editor insert latex math environment": Insert a LaTeX math environment + +"editor insert mathjax block": Insert a MathJax block + +"editor insert mathjax chemistry": Insert a chemistry thing in MathJax + +"editor insert mathjax inline": Insert an inline MathJax expresesion + +"editor list insert ordered": Put an ordered list into the active field. +k +"editor list insert unordered": Put an unordered list into the active field. + +"editor italic": Toggle italics + +"editor record sound": Record sound + +"editor remove format": Remove card formatting + +"editor subscript": Toggle subscript + +"editor superscript": Toggle superscript + +"editor toggle sticky all": *Anki 2.1.45+* Toggle the stickiness of all fields. "Toggle all" is interpreted the same was as in vanilla Anki: If any field is sticky, turn all stickies off. Otherwise, turn all stickies on. + +"editor toggle sticky current": *Anki 2.1.45+* Toggle the stickiness of the current field. + +*In the current implementation for toggling stickies, setting this to something other than the default will lose you the ability to click to toggle the sticky pins. If you want to retain this ability, using a duplicate `"editor toggle sticky current"` will still allow you to click pins.* + +"editor underline": Toggle underline + +**In the future, these shortcuts may be removed and put into a new add-on, as they are not part of Anki's default functionality.** + +Within "editor \_extras": + +"paste custom text": Pastes a custom piece of html text into a card field (defined in "Ω custom paste text") + +"editor \_pastes": Same functionality as paste custom text, but allows any number of texts with any label (texts are defined in "Ω custom paste extra texts", labels must match) + +e.g. `"editor _pastes": {"dashes":"Ctrl+Shift+5", "dots":"Ctrl+Shift+6"}` matched with `"Ω custom paste extra texts": {"dashes":"<b>--</b>","dots":". . ."}` pastes the corresponding text with the corresponding label + +## Main Toolbox Options + +"m\_toolbox \_duplicates": Takes functions and binds them to new shortcuts. + +This object takes inputs of the form "(function keyword)":"(shortcut)", separated by commas. (e.g. {"m\_toolbox undo":"u","m\_toolbox study":"9"}) + +All the keywords are exactly the same as the keywords used in the json file, making the lines copy-pastable. + +If you want to set more than one duplicate per shortcut for the reviewer, you can do so by adding the suffix "+++" *immediately* after the shortcut name followed by any distinct string. (e.g. {"m\_toolbox study+++ first": "6", "m\_toolbox study+++ 2nd": "V"}) + +"m_toolbox addons": Go to the addons window + +"m_toolbox create filtered deck": Create a filtered deck + +"m_toolbox export": Export the user's decks + +"m_toolbox import": Import a deck file (.apkg, etc.) + +"m_toolbox preferences": Go to the user preferences window + +"m_toolbox quit": Quit Anki + +"m_toolbox see documentation": Go to the Anki manual + +"m_toolbox study": Start studying the selected deck + +"m_toolbox switch profile": Switch user profiles + +"m_toolbox undo": Undo the **last main window (reviewer)** action + +## Home Options + +**NOTE: Setting these to "Ctrl+:", "d", "s", "a", "b", "t", or "y" will not work if they are not the default setting for that function.** + +"main add": Add new card + +"main browse": Enter card browser + +"main debug": Open debug screen + +"main deckbrowser": Go back to home screen + +"main stats": Enter stats screen + +"main study": Study current deck + +"main sync": Synchronize with AnkiWeb + +## Reviewer Options + +"reviewer \_duplicates": Takes functions and binds them to new shortcuts. + +This object takes inputs of the form "(function keyword)":"(shortcut)", separated by commas. (e.g. {"reviewer mark card":"]","reviewer flip card 1":"-"}) + +All the keywords are exactly the same as the keywords used in the json file, making the lines copy-pastable. (Those who want to remove the numbers from stuff like "reviewer flip card" can do so as well) + +If you want to set more than one duplicate per shortcut for the reviewer, you can do so by adding the suffix "+++" *immediately* after the shortcut name followed by any distinct string. (e.g. {"reviewer flip card 3+++ first": "l", "reviewer flip card 3+++ 2nd": "t"}) + +**Make sure to remap keys to empty keyspace.** + +"reviewer bury card": Bury this card + +"reviewer bury note": Bury this note (card and associated cards) + +"reviewer card info": Display info about this card + +"reviewer choice [1234]": Select 1/2/3/4 (Again/Hard/Good/Easy or Again/Good/Easy) + +"reviewer delete note": Delete this note + +"reviewer edit current": Edit the card currently being reviewed + +"reviewer flip card [123]": Flip the card to see the answer + +"reviewer mark card": Mark this card with a star + +"reviewer more options": Display the menu showing other review options for this card (e.g. flagging the card, suspend/delete/bury, playing audio, etc.) + +"reviewer options menu": Go to the review options menu + +"reviewer pause audio": Pause the audio being played + +"reviewer play recorded voice": If there is a recorded voice, play it + +"reviewer previous card info": Display info about the previous card + +"reviewer record voice": Record your voice + +"reviewer replay audio [12]": Replay audio attached to the card + +"reviewer set due date": Reschedules a card in the review schedule in Anki 2.1.41+ + +"reviewer set flag [12345670]": Set a flag on this card (or none for 0), changing colors depending on the number (1/2/3/4, and for 2.1.45+, 5/6/7 as well) + +"reviewer seek backward": Rewind the audio 5 seconds + +"reviewer seek forward": More the audio forward 5 seconds + +"reviewer suspend card": Suspend this card + +"reviewer suspend note": Suspend this note + +## Browser Window Options + +"window\_browser \_filters": Auto-fills the search bar of the browser with the given text. Can be used for filters such as current deck (`deck:current`) or cards due for review (`is:due`) + +The syntax for this is: `"(filter name): (shortcut)"`, though one may need to escape quotes with `\` e.g. `"deck: Something something"` becomes `\"deck: Something something\"` + +Sub-objects within `_filters`: + +"\_concat": Instead of replacing text in the search bar, adds the text to the end of the search bar + +"\_orConcat": Adds the "or" + the text to the end of the search bar ("or" acts like logical or for searches) + +"window_browser add card": Adds a new card (goes to the add window) + +"window_browser add tag": Adds a tag to the selected card + +"window_browser change deck": Changes the deck of the selected card + +"window_browser change note type": Changes the note type of the selected card + +"window_browser clear flags": Removes all flags from a card + +"window_browser clear unused tags": *Not in vanilla Anki*, Removes all unused tags + +"window_browser close": Closes the browser + +"window_browser delete": Deletes the selected card + +"window_browser filter": Adds filters to a search + +"window_browser find": Finds a pattern + +"window_browser find duplicates": *Not in vanilla Anki*, Finds cards with the same fields + +"window_browser find and replace": Finds a pattern and replaces it with another pattern + +"window_browser first card": Selects only the first card in the list + +"window_browser flag_blue": Toggles the blue flag + +"window_browser flag_green": Toggles the green flag + +"window_browser flag_purple": Toggles the purple flag + +"window_browser flag_red": Toggles the red flag + +"window_browser forget card": Forgets the selected card (sets the card as new) + +"window_browser goto card list": Switches focus to the card list + +"window_browser goto next note": Selects the note after the selected note in the list + +"window_browser goto note": Switches focus to the note fields + +"window_browser goto previous note": Selects the note before the selected note + +"window_browser goto sidebar": Goes to the sidebar of decks/note types + +"window_browser guide": Opens the browser guide in the default browser + +"window_browser info": Shows info of the selected card + +"window_browser invert selection": Inverts the selection of cards + +"window_browser last card": Goes to the last card on the list + +"window_browser manage note types": *Not in vanilla Anki*, Goes to the note type management window + +"window_browser preview": Emulates what the card will look like during review + +"window_browser remove current filter": *Not in vanilla Anki*, Removes the most recently used filter previously saved to the sidebar + +"window_browser remove tag": Removes tags from a card + +"window_browser reposition": Repositions a new card in the new card queue + +"window_browser reschedule": Reschedules a card in the review schedule, named "set due date" in Anki 2.1.41+ + +"window_browser save current filter": *Not in vanilla Anki*, Saves the current filter to the sidebar (and lets you name it) + +"window_browser select all": Selects all cards + +"window_browser select notes": *Not in vanilla Anki*, Selects only the current notes + +"window_browser sidebar search": *Useful only in Anki 2.1.45+* Sets the sidebar to initialize a search of the selected item in the sidebar + +"window_browser sidebar select": *Useful only in Anki 2.1.45+* Sets the sidebar to just select the item in the sidebar + +"window_browser suspend": Suspends the selected cards + +"window_browser toggle mark": Toggles the mark on the selected cards + +"window_browser undo": Undoes the latest **browser** action (**m\_toolbox undo** undoes **reviewer** actions) + +## Add-on Preferences + +**These options are for changing a few settings on the add-on itself.** + +"Ω custom paste end style": For the exceptionally lazy (like me). If set to "y", inserts a `</span>` and a zero-width space at the end of the custom text to stop the custom style from bleeding into new text. + +Otherwise, the custom paste will behave exactly like regular paste. + +"Ω custom paste text": Controls what html will be pasted by "custom paste" in "editor \_extras" +e.g. `"Ω custom paste text": "<span style=\"font-size: 20px; color:#3399ff\">◆</span>"` + +"Ω custom paste extra texts": Controls what html will be pasted by the correspondingly labeled paste in "editor \_pastes" (See "editor \_pastes" for an example) + +"Ω enable main/window_browser/editor/etc.": If set to "n", doesn't enable the corresponding set of shortcuts for the respective functions (useful for addon compatability in a pinch) + +"Ω enable conflict warning": If set to "y", shows a warning window whenever two shortcuts of the same type are set to the same key. diff --git a/.local/share/Anki2/addons21/keybindings/cs_functions.py b/.local/share/Anki2/addons21/keybindings/cs_functions.py new file mode 100644 index 0000000..2232393 --- /dev/null +++ b/.local/share/Anki2/addons21/keybindings/cs_functions.py @@ -0,0 +1,231 @@ +import re +import anki +from aqt.utils import tooltip, showInfo +try: + from aqt.utils import ( + HelpPage, + TR, + tr, + ) +except: + pass +from aqt.qt import * +from aqt import mw +try: + from aqt.operations.card import set_card_deck +except: + pass + + +def get_version(): + """Return the integer subversion of Anki on which the addon is run ("2.1.11" -> 11)""" + return int(anki.version.split('.')[2]) + +def cs_editor_on_alt_cloze(self): + self.saveNow(self.cs_u_onAltCloze, keepFocus=True) + +def cs_editor_on_std_cloze(self): + self.saveNow(self.cs_u_onStdCloze, keepFocus=True) + +def cs_editor_generate_cloze(self, altModifier = False): + # check that the model is set up for cloze deletion + if not re.search('{{(.*:)*cloze:',self.note.model()['tmpls'][0]['qfmt']): + if self.addMode: + tooltip(_("Warning, cloze deletions will not work until " + "you switch the type at the top to Cloze.")) + else: + showInfo(_("""\ +To make a cloze deletion on an existing note, you need to change it \ +to a cloze type first, via Edit>Change Note Type.""")) + return + # find the highest existing cloze + highest = 0 + for name, val in list(self.note.items()): + m = re.findall(r"\{\{c(\d+)::", val) + if m: + highest = max(highest, sorted([int(x) for x in m])[-1]) + # reuse last? + if not altModifier: + highest += 1 + highest = max(1, highest) + self.web.eval("wrap('{{c%d::', '}}');" % highest) + +#If the shortcut has "+++" in it for multiple duplications, +#Truncate the shortcut from that point to get the original name +def normalizeShortcutName(scut): + prefix_idx = scut.find('+++') + if scut.find('+++') != -1: + # If the multiple duplicates "+++" is found, + # truncate the shortcut to the proper name + scut = scut[:prefix_idx] + return scut + +#Converts json shortcuts into functions for the reviewer +#sToF: shortcutToFunction +def review_sToF(self,scut): + + #"reviewer" is retained for copy-pastability, may be removed later + #"self.mw.onEditCurrent" is exactly how it was in reviewer.py, DO NOT CHANGE + sdict = { + "reviewer edit current": self.mw.onEditCurrent, + "reviewer flip card": self.onEnterKey, + "reviewer flip card 1": self.onEnterKey, + "reviewer flip card 2": self.onEnterKey, + "reviewer flip card 3": self.onEnterKey, + "reviewer options menu": self.onOptions, + "reviewer record voice": self.onRecordVoice, + "reviewer play recorded voice": self.onReplayRecorded, + "reviewer play recorded voice 1": self.onReplayRecorded, + "reviewer play recorded voice 2": self.onReplayRecorded, + "reviewer delete note": self.onDelete, + "reviewer suspend card": self.onSuspendCard, + "reviewer suspend note": self.onSuspend, + "reviewer bury card": self.onBuryCard, + "reviewer bury note": self.onBuryNote, + "reviewer mark card": self.onMark, + "reviewer set flag 1": lambda: self.setFlag(1), + "reviewer set flag 2": lambda: self.setFlag(2), + "reviewer set flag 3": lambda: self.setFlag(3), + "reviewer set flag 4": lambda: self.setFlag(4), + "reviewer set flag 0": lambda: self.setFlag(0), + "reviewer replay audio": self.replayAudio, + "reviewer replay audio 1": self.replayAudio, + "reviewer replay audio 2": self.replayAudio, + "reviewer choice 1": lambda: self._answerCard(1), + "reviewer choice 2": lambda: self._answerCard(2), + "reviewer choice 3": lambda: self._answerCard(3), + "reviewer choice 4": lambda: self._answerCard(4), + } + if get_version() >= 20: + sdict["reviewer pause audio"] = self.on_pause_audio + sdict["reviewer seek backward"] = self.on_seek_backward + sdict["reviewer seek forward"] = self.on_seek_forward + if get_version() >= 33: + sdict["reviewer more options"] = self.showContextMenu + if get_version() >= 41: + sdict["reviewer set due date"] = self.on_set_due + if get_version() >= 45: + sdict["reviewer card info"] = self.on_card_info + sdict["reviewer set flag 5"] = lambda: self.setFlag(5) + sdict["reviewer set flag 6"] = lambda: self.setFlag(6) + sdict["reviewer set flag 7"] = lambda: self.setFlag(7) + if get_version() >= 48: + sdict["reviewer previous card info"] = self.on_previous_card_info + + scut = normalizeShortcutName(scut) + if scut in sdict: + return sdict[scut] + return None + +#Converts json shortcuts into functions for the reviewer +#sToF: shortcutToFunction +def editor_sToF(self,scut): + sdict = { + "editor card layout": (self.onCardLayout, True), + "editor bold": (self.toggleBold,), + "editor italic": (self.toggleItalic,), + "editor underline": (self.toggleUnderline,), + "editor superscript": (self.toggleSuper,), + "editor subscript": (self.toggleSub,), + "editor remove format": (self.removeFormat,), + "editor foreground": (self.onForeground,), + "editor change col": (self.onChangeCol,), + "editor cloze": (self.cs_onStdCloze,), + "editor cloze alt": (self.cs_onAltCloze,), + "editor add media": (self.onAddMedia,), + "editor record sound": (self.onRecSound,), + "editor insert latex": (self.insertLatex,), + "editor insert latex equation": (self.insertLatexEqn,), + "editor insert latex math environment": (self.insertLatexMathEnv,), + "editor insert mathjax inline": (self.insertMathjaxInline,), + "editor insert mathjax block": (self.insertMathjaxBlock,), + "editor html edit": (self.onHtmlEdit,), + "editor focus tags": (self.onFocusTags, True), + "editor toggle sticky current": (self.csToggleStickyCurrent,), + "editor toggle sticky all": (self.csToggleStickyAll,), + + + } + if get_version() >= 45: + sdict.update({ + "editor html edit": (lambda: + self.web.eval( + """{const currentField = getCurrentField(); if (currentField) { currentField.toggleHtmlEdit(); }}""" + ), ), + "editor block indent": (lambda: + self.web.eval( + """ document.execCommand("indent"); """ + ), ), + "editor block outdent": (lambda: + self.web.eval( + """ document.execCommand("outdent") """ + ), ), + "editor list insert unordered": (lambda: + self.web.eval( + """ document.execCommand("insertUnorderedList"); """ + ), ), + "editor list insert ordered": (lambda: + self.web.eval( + """ document.execCommand("insertOrderedList"); """ + ), ), + }) + + scut = normalizeShortcutName(scut) + if scut in sdict: + return sdict[scut] + return None + +def editor_changeDeck(self): + if not self.card: + return + from aqt.studydeck import StudyDeck + cid = self.card.id + did = self.card.did + current = self.mw.col.decks.get(did)["name"] + ret = StudyDeck( + self.mw, + current=current, + accept=tr(TR.BROWSING_MOVE_CARDS), + title=tr(TR.BROWSING_CHANGE_DECK), + help=HelpPage.BROWSING, + parent=self.mw, + ) + if not ret.name: + return + did = self.mw.col.decks.id(ret.name) + try: + set_card_deck(parent=self.widget, card_ids=[cid], deck_id=did).run_in_background() + except: + self.mw.col.set_deck([cid], did) + + +#Performs a preliminary check for if any filter is saved before removing it +def remove_filter(self): + name = self._currentFilterIsSaved() + if name: + self._onRemoveFilter() + +#For reviewer shortcut assignments: +#Takes as input ls (the list of shortcuts, of the form (shortcut, function pointer)) +#and replacements (a dict mapping function pointers to new shortcuts) +#Then, for every tuple in the list, if its function pointer has a replacement shortcut, replace it +#Changes the list in-place +def reviewer_find_and_replace_functions(ls, replacements): + for i, val in enumerate(ls): + func = val[1] + if func in replacements: + ls[i] = (replacements[func].pop(), func) + if not replacements[func]: + replacements.pop(func) + +#For reviewer shortcut assignments: +#Takes as input ls (the list of shortcuts, of the form (shortcut, function pointer)) +#and replacements (a dict mapping old shortcuts to new shortcuts) +#Then, for every tuple in the list, if its shortcut is in the list, replace it +#Changes the list in-place +#Prefer reviewer_find_and_replace_functions to this because functions are less fragile +def reviewer_find_and_replace_scuts(ls, replacements): + for i, val in enumerate(ls): + scut = val[0] + if scut in replacements: + ls[i] = (replacements.pop(scut), val[1]) 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() diff --git a/.local/share/Anki2/addons21/keybindings/meta.json b/.local/share/Anki2/addons21/keybindings/meta.json new file mode 100644 index 0000000..fe824c0 --- /dev/null +++ b/.local/share/Anki2/addons21/keybindings/meta.json @@ -0,0 +1 @@ +{"name": "Customize Keyboard Shortcuts", "mod": 1656483227, "min_point_version": 0, "max_point_version": 45, "branch_index": 0, "disabled": false, "config": {"editor _duplicates": {}, "editor _extras": {"paste custom text": "<nop>"}, "editor _pastes": {}, "editor add card close window": "<default>", "editor add media": "F3", "editor block indent": "<nop>", "editor block outdent": "<nop>", "editor bold": "Ctrl+B", "editor card layout": "Ctrl+L", "editor change col": "F8", "editor change deck": "<nop>", "editor change note type": "Ctrl+N", "editor cloze": "Ctrl+Shift+C", "editor cloze alt": "Ctrl+Shift+Alt+C", "editor cloze forced increment": "<nop>", "editor cloze no increment": "<nop>", "editor confirm add card": "Ctrl+Return", "editor focus tags": "Ctrl+Shift+T", "editor foreground": "F7", "editor html edit": "Ctrl+Shift+X", "editor insert latex": "Ctrl+T, T", "editor insert latex equation": "Ctrl+T, E", "editor insert latex math environment": "Ctrl+T, M", "editor insert mathjax block": "Ctrl+M, E", "editor insert mathjax chemistry": "Ctrl+M, C", "editor insert mathjax inline": "Ctrl+M, M", "editor italic": "Ctrl+I", "editor list insert ordered": "<nop>", "editor list insert unordered": "<nop>", "editor record sound": "F5", "editor remove format": "Ctrl+R", "editor subscript": "Ctrl+=", "editor superscript": "Ctrl++", "editor toggle sticky all": "Shift+F9", "editor toggle sticky current": "F9", "editor underline": "Ctrl+U", "m_toolbox _duplicates": {}, "m_toolbox addons": "Ctrl+Shift+A", "m_toolbox create filtered deck": "F", "m_toolbox export": "Ctrl+E", "m_toolbox import": "Ctrl+Shift+I", "m_toolbox preferences": "Ctrl+P", "m_toolbox quit": "Ctrl+Q", "m_toolbox see documentation": "F1", "m_toolbox study": "/", "m_toolbox switch profile": "Ctrl+Shift+P", "m_toolbox undo": "Ctrl+Z", "main add": "A", "main browse": "B", "main debug": "Ctrl+:", "main deckbrowser": "D", "main stats": "T", "main study": "S", "main sync": "Y", "reviewer _duplicates": {}, "reviewer bury card": "-", "reviewer bury note": "=", "reviewer card info": "I", "reviewer choice 1": "q", "reviewer choice 2": "w", "reviewer choice 3": "3", "reviewer choice 4": "4", "reviewer delete note": "Ctrl+Delete", "reviewer edit current": "E", "reviewer flip card 1": " ", "reviewer flip card 2": "Qt.Key_Return", "reviewer flip card 3": "Qt.Key_Enter", "reviewer mark card": "*", "reviewer more options": "M", "reviewer options menu": "o", "reviewer pause audio": "5", "reviewer play recorded voice": "v", "reviewer previous card info": "Alt+I", "reviewer record voice": "Shift+v", "reviewer replay audio 1": "r", "reviewer replay audio 2": "F5", "reviewer seek backward": "6", "reviewer seek forward": "7", "reviewer set due date": "Ctrl+Shift+D", "reviewer set flag 0": "Ctrl+0", "reviewer set flag 1": "Ctrl+1", "reviewer set flag 2": "Ctrl+2", "reviewer set flag 3": "Ctrl+3", "reviewer set flag 4": "Ctrl+4", "reviewer set flag 5": "Ctrl+5", "reviewer set flag 6": "Ctrl+6", "reviewer set flag 7": "Ctrl+7", "reviewer suspend card": "@", "reviewer suspend note": "!", "window_browser _filters": {"_concat": {}, "_orConcat": {}}, "window_browser add note": "Ctrl+E", "window_browser add tag": "Ctrl+Shift+A", "window_browser change deck": "Ctrl+D", "window_browser change note type": "Ctrl+Shift+M", "window_browser clear unused tags": "<nop>", "window_browser close": "Ctrl+W", "window_browser delete": "Ctrl+Del", "window_browser filter": "Ctrl+Shift+F", "window_browser find": "Ctrl+F", "window_browser find and replace": "Ctrl+Alt+F", "window_browser find duplicates": "<nop>", "window_browser first card": "Home", "window_browser flag_blue": "Ctrl+4", "window_browser flag_green": "Ctrl+3", "window_browser flag_orange": "Ctrl+2", "window_browser flag_red": "Ctrl+1", "window_browser forget card": "Ctrl+Alt+N", "window_browser goto card list": "Ctrl+Shift+L", "window_browser goto next note": "Ctrl+N", "window_browser goto note": "Ctrl+Shift+N", "window_browser goto previous note": "Ctrl+P", "window_browser goto sidebar": "Ctrl+Shift+R", "window_browser guide": "F1", "window_browser info": "Ctrl+Shift+I", "window_browser invert selection": "Ctrl+Alt+S", "window_browser last card": "End", "window_browser manage note types": "<nop>", "window_browser preview": "Ctrl+Shift+P", "window_browser remove current filter": "<nop>", "window_browser remove tag": "Ctrl+Alt+Shift+A", "window_browser reposition": "Ctrl+Shift+S", "window_browser reschedule": "Ctrl+Shift+D", "window_browser save current filter": "<nop>", "window_browser select all": "Ctrl+Alt+A", "window_browser select notes": "<nop>", "window_browser sidebar search": "Alt+1", "window_browser sidebar select": "Alt+2", "window_browser suspend": "Ctrl+J", "window_browser toggle mark": "Ctrl+K", "window_browser undo": "Ctrl+Alt+Z", "\u03a9 custom paste end style": "n", "\u03a9 custom paste extra texts": {}, "\u03a9 custom paste text": "", "\u03a9 enable conflict warning": "y", "\u03a9 enable editor": "y", "\u03a9 enable m_toolbox": "y", "\u03a9 enable main": "y", "\u03a9 enable reviewer": "y", "\u03a9 enable window_browser": "y"}}
\ No newline at end of file |