-
Notifications
You must be signed in to change notification settings - Fork 58
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Field history more features #102
base: master
Are you sure you want to change the base?
Changes from all commits
f2f7589
dd61788
6f34d6e
98a1ed5
3b18384
3b23bc6
016feac
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -4,7 +4,6 @@ | |
__pycache__/ | ||
# Anki | ||
src/*/meta.json | ||
src/*/config.json | ||
# Build files | ||
build | ||
src/*/forms* | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -7,14 +7,12 @@ | |
(e.g. to show newly added cards since last search) | ||
|
||
Copyright: (c) Glutanimate 2016-2017 <https://glutanimate.com/> | ||
2018 Arthur Milchior <[email protected]> (porting to 2.1) | ||
License: GNU AGPLv3 or later <https://www.gnu.org/licenses/agpl.html> | ||
""" | ||
|
||
# Do not modify the following line | ||
from __future__ import unicode_literals | ||
from anki import version as anki_version | ||
anki21 = anki_version.startswith("2.1.") | ||
from .config import getConfig | ||
|
||
######## USER CONFIGURATION START ######## | ||
|
||
|
@@ -41,15 +39,12 @@ | |
from aqt.browser import Browser | ||
from anki.hooks import addHook | ||
def debug(t): | ||
#print(t) | ||
print(t) | ||
pass | ||
|
||
def refreshView(self): | ||
debug("Calling refreshView()") | ||
if anki21: | ||
self.onSearchActivated() | ||
else: | ||
self.onSearch(reset=True) | ||
self.onSearchActivated() | ||
if SORTING_COLUMN: | ||
try: | ||
col_index = self.model.activeCols.index(SORTING_COLUMN) | ||
|
@@ -62,7 +57,7 @@ def setupMenu(self): | |
menu = self.form.menuEdit | ||
menu.addSeparator() | ||
a = menu.addAction('Refresh View') | ||
a.setShortcut(QKeySequence("CTRL+F5" if anki21 else "F5")) | ||
a.setShortcut(QKeySequence(getConfig("ShortCut","CTRL+F5"))) | ||
a.triggered.connect(self.refreshView) | ||
|
||
Browser.refreshView = refreshView | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
{"ShortCut": "CTRL+F5"} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
from aqt import mw | ||
|
||
options = None | ||
def readIfRequired(): | ||
global options | ||
if options is None: | ||
options = mw.addonManager.getConfig(__name__) or dict() | ||
|
||
def newConf(config): | ||
global options | ||
options = None | ||
|
||
def getConfig(s = None, default = None): | ||
"""Get the dictionnary of objects. If a name is given, return the | ||
object with this name if it exists. | ||
|
||
reads if required.""" | ||
|
||
readIfRequired() | ||
if s is None: | ||
return options | ||
else: | ||
return options.get(s, default) | ||
|
||
mw.addonManager.setConfigUpdatedAction(__name__,newConf) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
{ | ||
"limit search to deck": true, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Although I can see the advantage of including spaces in config keys for readability, I still prefer going old school json and using camelCase config keys (whitespace in keys always makes me nervous. might just be because I started programming with bash...) |
||
"number of note to consider in history": 100, | ||
"historyWindowShortcut": "Ctrl+Alt+H", | ||
"predefinedWindowShortcut": "Ctrl+Alt+P", | ||
"fieldRestoreShortcut": "Alt+Z", | ||
"partialRestoreShortcut": "Alt+Shift+Z", | ||
"fullRestoreShortcut": "Ctrl+Alt+Shift+Z", | ||
"partialRestoreFields": [], | ||
"predefinedFields":{ | ||
"Example of name of field": [ | ||
"Example of default value", | ||
"other example of default value" | ||
]} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
from aqt import mw | ||
import sys | ||
|
||
userOption = None | ||
|
||
def getUserOption(key = None, default = None): | ||
#print(f"getUserOption(key = {key}, default = {default})") | ||
global userOption | ||
if userOption is None: | ||
userOption = mw.addonManager.getConfig(__name__) | ||
#debug("userOption read from the file and is {userOption}") | ||
if key is None: | ||
#debug("return {userOption}") | ||
return userOption | ||
if key in userOption: | ||
#debug("key in userOption. Returning {userOption[key]}") | ||
return userOption[key] | ||
else: | ||
#debug("key not in userOption. Returning default.") | ||
return default | ||
|
||
def writeConfig(): | ||
mw.addonManager.writeConfig(__name__,userOption) | ||
|
||
def update(_): | ||
global userOption, fromName | ||
userOption = None | ||
fromName = None | ||
|
||
mw.addonManager.setConfigUpdatedAction(__name__,update) |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -16,6 +16,7 @@ | |
# Anki's built-in add-on configuration menu | ||
|
||
history_window_shortcut = "Ctrl+Alt+H" | ||
predefined_window_shortcut = "Ctrl+Alt+P" | ||
field_restore_shortcut = "Alt+Z" | ||
partial_restore_shortcut = "Alt+Shift+Z" | ||
full_restore_shortcut = "Ctrl+Alt+Shift+Z" | ||
|
@@ -37,15 +38,16 @@ | |
|
||
from anki import version | ||
ANKI21 = version.startswith("2.1") | ||
from .config import getConfig | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This currently throws an error because config.py is using getUserOption |
||
|
||
|
||
if ANKI21: | ||
config = mw.addonManager.getConfig(__name__) | ||
history_window_shortcut = config["historyWindowShortcut"] | ||
field_restore_shortcut = config["fieldRestoreShortcut"] | ||
partial_restore_shortcut = config["partialRestoreShortcut"] | ||
full_restore_shortcut = config["fullRestoreShortcut"] | ||
partial_restore_fields = config["partialRestoreFields"] | ||
history_window_shortcut = getConfig("historyWindowShortcut", "Ctrl+Alt+H") | ||
predefined_window_shortcut = getConfig("predefinedWindowShortcut", "Ctrl+Alt+P") | ||
field_restore_shortcut = getConfig("fieldRestoreShortcut", "Alt+Z") | ||
partial_restore_shortcut = getConfig("partialRestoreShortcut", "Alt+Shift+Z") | ||
full_restore_shortcut = getConfig("fullRestoreShortcut", "Ctrl+Alt+Shift+Z") | ||
partial_restore_fields = getConfig("partialRestoreFields", []) | ||
|
||
# Ctrl+Alt+H is a global hotkey on macOS | ||
# Hacky solution for anki21. A platform-specific config.json would be | ||
|
@@ -76,17 +78,38 @@ def showCompleter(self): | |
self.completer.complete() | ||
|
||
|
||
def myGetField(parent, question, last_val, **kwargs): | ||
edit = CustomTextEdit(parent, last_val) | ||
ret = getText(question, parent, edit=edit, **kwargs) | ||
return ret | ||
def myGetField(parent, question, potential_vals, **kwargs): | ||
striped_vals = {} | ||
keys = [] | ||
for val in potential_vals: | ||
if not val.strip(): | ||
continue | ||
striped_val = stripHTML(val) | ||
striped_vals[striped_val] = val | ||
keys.append(striped_val) | ||
edit = CustomTextEdit(parent, potential_vals) | ||
(text, ret) = getText(question, parent, edit=edit, **kwargs) | ||
return striped_vals.get(text, False) | ||
|
||
|
||
def predefinedRestore(self, model, fld): | ||
field = model['flds'][fld]['name'] | ||
keys = getConfig("predefinedFields", dict()).get(field, []) | ||
if not keys: | ||
tooltip("No predefined values for this field. Edit the configuration.") | ||
return False | ||
|
||
txt = "Set field to:" | ||
text = myGetField(self.parentWindow, | ||
txt, keys, title="Predefined fields") | ||
if text: | ||
self.note[field] = text | ||
|
||
def historyRestore(self, mode, results, model, fld): | ||
field = model['flds'][fld]['name'] | ||
last_val = {} | ||
last_vals = {} | ||
keys = [] | ||
for nid in results[:100]: | ||
for nid in results[:getConfig("number of note to consider in history", 100)]: | ||
oldNote = self.note.col.getNote(nid) | ||
if field in oldNote: | ||
html = oldNote[field] | ||
|
@@ -99,18 +122,17 @@ def historyRestore(self, mode, results, model, fld): | |
text = stripHTML(html) | ||
else: | ||
text = None | ||
if text and text not in last_val: | ||
if text and text not in last_vals: | ||
keys.append(text) | ||
last_val[text] = html | ||
if not last_val: | ||
last_vals[text] = html | ||
if not last_vals: | ||
tooltip("No prior entries for this field found.") | ||
return False | ||
txt = "Set field to:" | ||
(text, ret) = myGetField(self.parentWindow, | ||
text = myGetField(self.parentWindow, | ||
txt, keys, title="Field History") | ||
if not ret or not text.strip() or text not in last_val: | ||
return False | ||
self.note[field] = last_val[text] | ||
if text: | ||
self.note[field] = text | ||
|
||
|
||
def quickRestore(self, mode, results, model, fld): | ||
|
@@ -149,29 +171,40 @@ def restoreEditorFields(self, mode): | |
|
||
# Gather note info | ||
fld = self.currentField | ||
if fld is None and mode in ("history", "field"): | ||
if fld is None and mode in ("history", "field", "predefined"): | ||
# only necessary on anki20 | ||
tooltip("Please select a field whose last entry you want to restore.") | ||
return False | ||
did = self.parentWindow.deckChooser.selectedId() | ||
deck = self.mw.col.decks.nameOrNone(did) | ||
model = self.note.model() | ||
|
||
# Perform search | ||
if deck: | ||
query = "deck:'%s'" % (deck) | ||
results = self.note.col.findNotes(query) | ||
if not results: | ||
tooltip("Could not find any past notes in current deck.<br>" | ||
"If you just imported a deck you might have to restart Anki.") | ||
return False | ||
results.sort(reverse=True) | ||
|
||
# Get user selection | ||
if mode == "history": | ||
ret = historyRestore(self, mode, results, model, fld) | ||
# for predefined, don't compute deck info | ||
if mode == "predefined": | ||
ret = predefinedRestore(self, model, fld) | ||
if ret is False: | ||
return False | ||
else: | ||
ret = quickRestore(self, mode, results, model, fld) | ||
# Perform search | ||
if getConfig("limit search to deck", True) and hasattr(self.parentWindow, "deckChooser"): | ||
did = self.parentWindow.deckChooser.selectedId() | ||
deck = self.mw.col.decks.nameOrNone(did) | ||
where = "deck" | ||
if deck: | ||
query = "deck:'%s'" % (deck) | ||
else: | ||
query = "" | ||
where = "collection" | ||
results = self.note.col.findNotes(query) | ||
if not results: | ||
tooltip(f"Could not find any past notes in current {where}.<br>" | ||
"If you just imported a deck you might have to restart Anki.") | ||
return False | ||
results.sort(reverse=True) | ||
|
||
# Get user selection | ||
if mode == "history": | ||
ret = historyRestore(self, mode, results, model, fld) | ||
else: | ||
ret = quickRestore(self, mode, results, model, fld) | ||
if ret is False: | ||
return False | ||
|
||
|
@@ -186,6 +219,10 @@ def restoreEditorFields(self, mode): | |
# Assign hotkeys | ||
|
||
def onSetupButtons20(editor): | ||
t = QShortcut(QKeySequence(history_window_shortcut), editor.parentWindow) | ||
t.activated.connect(lambda: editor.restoreEditorFields("history")) | ||
t = QShortcut(QKeySequence(predefined_window_shortcut), editor.parentWindow) | ||
t.activated.connect(lambda: editor.restoreEditorFields("predefined")) | ||
if not isinstance(editor.parentWindow, AddCards): | ||
return # only enable in add cards dialog | ||
t = QShortcut(QKeySequence(full_restore_shortcut), editor.parentWindow) | ||
|
@@ -194,23 +231,24 @@ def onSetupButtons20(editor): | |
t.activated.connect(lambda: editor.restoreEditorFields("partial")) | ||
t = QShortcut(QKeySequence(field_restore_shortcut), editor.parentWindow) | ||
t.activated.connect(lambda: editor.restoreEditorFields("field")) | ||
t = QShortcut(QKeySequence(history_window_shortcut), editor.parentWindow) | ||
t.activated.connect(lambda: editor.restoreEditorFields("history")) | ||
|
||
|
||
def onSetupShortcuts21(cuts, editor): | ||
if not isinstance(editor.parentWindow, AddCards): | ||
return # only enable in AddCards dialog | ||
added_shortcuts = [ | ||
(full_restore_shortcut, | ||
lambda: editor.restoreEditorFields("full"), True), | ||
(partial_restore_shortcut, | ||
lambda: editor.restoreEditorFields("partial"), True), | ||
(field_restore_shortcut, | ||
lambda: editor.restoreEditorFields("field")), | ||
(history_window_shortcut, | ||
lambda: editor.restoreEditorFields("history")), | ||
(predefined_window_shortcut, | ||
lambda: editor.restoreEditorFields("predefined")), | ||
] | ||
if isinstance(editor.parentWindow, AddCards): | ||
added_shortcuts += [ | ||
(full_restore_shortcut, | ||
lambda: editor.restoreEditorFields("full"), True), | ||
(partial_restore_shortcut, | ||
lambda: editor.restoreEditorFields("partial"), True), | ||
(field_restore_shortcut, | ||
lambda: editor.restoreEditorFields("field")), | ||
] | ||
cuts.extend(added_shortcuts) | ||
|
||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think you meant to file the changes for browser refresh as a separate PR?