8e71554ac4
the current code was freezing when clicking on 'cards' in the browser - it looks like like the javascript callback was never being called despite calling processEvents(). so we need to refactor the code to call saveNow() with a callback that does the subsequent processing. a lot of the browser code was implicitly calling saveNow() via beginReset(), so we've had to change all that code to save immediately before it begins any processing. found a probable bug in the process - it doesn't look like onRowChange() was saving before overwriting the note, so theoretically edits could be lost if the user switched to another card very quickly after typing something. onSearch() has been split into a GUI-activated onSearchActivated() that takes care of saving, and a lower level search() that refreshes the current search. it keeps track of the last search via an instance variable so that it refreshes properly if a user accidentally adds some characters to their search without activating the search, then does something like reverse the sort order.
223 lines
7.6 KiB
Python
223 lines
7.6 KiB
Python
# Copyright: Damien Elmes <anki@ichi2.net>
|
|
# -*- coding: utf-8 -*-
|
|
# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
|
from anki.lang import _
|
|
|
|
from aqt.qt import *
|
|
import aqt.forms
|
|
from aqt.utils import saveGeom, restoreGeom, showWarning, askUser, shortcut, \
|
|
tooltip, openHelp, addCloseShortcut, downArrow
|
|
from anki.sound import clearAudioQueue
|
|
from anki.hooks import addHook, remHook, runHook
|
|
from anki.utils import stripHTMLMedia, isMac
|
|
import aqt.editor, aqt.modelchooser, aqt.deckchooser
|
|
|
|
class AddCards(QDialog):
|
|
|
|
def __init__(self, mw):
|
|
QDialog.__init__(self, None, Qt.Window)
|
|
mw.setupDialogGC(self)
|
|
self.mw = mw
|
|
self.form = aqt.forms.addcards.Ui_Dialog()
|
|
self.form.setupUi(self)
|
|
self.setWindowTitle(_("Add"))
|
|
self.setMinimumHeight(300)
|
|
self.setMinimumWidth(400)
|
|
self.setupChoosers()
|
|
self.setupEditor()
|
|
self.setupButtons()
|
|
self.onReset()
|
|
self.history = []
|
|
self.forceClose = False
|
|
restoreGeom(self, "add")
|
|
addHook('reset', self.onReset)
|
|
addHook('currentModelChanged', self.onModelChange)
|
|
addCloseShortcut(self)
|
|
self.show()
|
|
self.setupNewNote()
|
|
|
|
def setupEditor(self):
|
|
self.editor = aqt.editor.Editor(
|
|
self.mw, self.form.fieldsArea, self, True)
|
|
|
|
def setupChoosers(self):
|
|
self.modelChooser = aqt.modelchooser.ModelChooser(
|
|
self.mw, self.form.modelArea)
|
|
self.deckChooser = aqt.deckchooser.DeckChooser(
|
|
self.mw, self.form.deckArea)
|
|
|
|
def helpRequested(self):
|
|
openHelp("addingnotes")
|
|
|
|
def setupButtons(self):
|
|
bb = self.form.buttonBox
|
|
ar = QDialogButtonBox.ActionRole
|
|
# add
|
|
self.addButton = bb.addButton(_("Add"), ar)
|
|
self.addButton.clicked.connect(self.addCards)
|
|
self.addButton.setShortcut(QKeySequence("Ctrl+Return"))
|
|
self.addButton.setToolTip(shortcut(_("Add (shortcut: ctrl+enter)")))
|
|
# close
|
|
self.closeButton = QPushButton(_("Close"))
|
|
self.closeButton.setAutoDefault(False)
|
|
bb.addButton(self.closeButton, QDialogButtonBox.RejectRole)
|
|
# help
|
|
self.helpButton = QPushButton(_("Help"), clicked=self.helpRequested)
|
|
self.helpButton.setAutoDefault(False)
|
|
bb.addButton(self.helpButton,
|
|
QDialogButtonBox.HelpRole)
|
|
# history
|
|
b = bb.addButton(
|
|
_("History")+ " "+downArrow(), ar)
|
|
if isMac:
|
|
sc = "Ctrl+Shift+H"
|
|
else:
|
|
sc = "Ctrl+H"
|
|
b.setShortcut(QKeySequence(sc))
|
|
b.setToolTip(_("Shortcut: %s") % shortcut(sc))
|
|
b.clicked.connect(self.onHistory)
|
|
b.setEnabled(False)
|
|
self.historyButton = b
|
|
|
|
def setupNewNote(self, set=True):
|
|
f = self.mw.col.newNote()
|
|
if set:
|
|
self.editor.setNote(f, focus=True)
|
|
return f
|
|
|
|
def onModelChange(self):
|
|
oldNote = self.editor.note
|
|
note = self.setupNewNote(set=False)
|
|
if oldNote:
|
|
oldFields = list(oldNote.keys())
|
|
newFields = list(note.keys())
|
|
for n, f in enumerate(note.model()['flds']):
|
|
fieldName = f['name']
|
|
try:
|
|
oldFieldName = oldNote.model()['flds'][n]['name']
|
|
except IndexError:
|
|
oldFieldName = None
|
|
# copy identical fields
|
|
if fieldName in oldFields:
|
|
note[fieldName] = oldNote[fieldName]
|
|
# set non-identical fields by field index
|
|
elif oldFieldName and oldFieldName not in newFields:
|
|
try:
|
|
note.fields[n] = oldNote.fields[n]
|
|
except IndexError:
|
|
pass
|
|
self.editor.currentField = 0
|
|
self.editor.setNote(note, focus=True)
|
|
|
|
def onReset(self, model=None, keep=False):
|
|
oldNote = self.editor.note
|
|
note = self.setupNewNote(set=False)
|
|
flds = note.model()['flds']
|
|
# copy fields from old note
|
|
if oldNote:
|
|
if not keep:
|
|
self.removeTempNote(oldNote)
|
|
for n in range(len(note.fields)):
|
|
try:
|
|
if not keep or flds[n]['sticky']:
|
|
note.fields[n] = oldNote.fields[n]
|
|
else:
|
|
note.fields[n] = ""
|
|
except IndexError:
|
|
break
|
|
self.editor.currentField = 0
|
|
self.editor.setNote(note, focus=True)
|
|
|
|
def removeTempNote(self, note):
|
|
if not note or not note.id:
|
|
return
|
|
# we don't have to worry about cards; just the note
|
|
self.mw.col._remNotes([note.id])
|
|
|
|
def addHistory(self, note):
|
|
txt = stripHTMLMedia(",".join(note.fields))[:30]
|
|
self.history.insert(0, (note.id, txt))
|
|
self.history = self.history[:15]
|
|
self.historyButton.setEnabled(True)
|
|
|
|
def onHistory(self):
|
|
m = QMenu(self)
|
|
for nid, txt in self.history:
|
|
a = m.addAction(_("Edit %s") % txt)
|
|
a.triggered.connect(lambda b, nid=nid: self.editHistory(nid))
|
|
runHook("AddCards.onHistory", self, m)
|
|
m.exec_(self.historyButton.mapToGlobal(QPoint(0,0)))
|
|
|
|
def editHistory(self, nid):
|
|
browser = aqt.dialogs.open("Browser", self.mw)
|
|
browser.form.searchEdit.lineEdit().setText("nid:%d" % nid)
|
|
browser.onSearchActivated()
|
|
|
|
def addNote(self, note):
|
|
note.model()['did'] = self.deckChooser.selectedId()
|
|
ret = note.dupeOrEmpty()
|
|
if ret == 1:
|
|
showWarning(_(
|
|
"The first field is empty."),
|
|
help="AddItems#AddError")
|
|
return
|
|
if '{{cloze:' in note.model()['tmpls'][0]['qfmt']:
|
|
if not self.mw.col.models._availClozeOrds(
|
|
note.model(), note.joinedFields(), False):
|
|
if not askUser(_("You have a cloze deletion note type "
|
|
"but have not made any cloze deletions. Proceed?")):
|
|
return
|
|
cards = self.mw.col.addNote(note)
|
|
if not cards:
|
|
showWarning(_("""\
|
|
The input you have provided would make an empty \
|
|
question on all cards."""), help="AddItems")
|
|
return
|
|
self.addHistory(note)
|
|
self.mw.requireReset()
|
|
return note
|
|
|
|
def addCards(self):
|
|
self.editor.saveNow(self._addCards)
|
|
|
|
def _addCards(self):
|
|
self.editor.saveAddModeVars()
|
|
note = self.editor.note
|
|
note = self.addNote(note)
|
|
if not note:
|
|
return
|
|
tooltip(_("Added"), period=500)
|
|
# stop anything playing
|
|
clearAudioQueue()
|
|
self.onReset(keep=True)
|
|
self.mw.col.autosave()
|
|
|
|
def keyPressEvent(self, evt):
|
|
"Show answer on RET or register answer."
|
|
if (evt.key() in (Qt.Key_Enter, Qt.Key_Return)
|
|
and self.editor.tags.hasFocus()):
|
|
evt.accept()
|
|
return
|
|
return QDialog.keyPressEvent(self, evt)
|
|
|
|
def reject(self):
|
|
if not self.canClose():
|
|
return
|
|
remHook('reset', self.onReset)
|
|
remHook('currentModelChanged', self.onModelChange)
|
|
clearAudioQueue()
|
|
self.removeTempNote(self.editor.note)
|
|
self.editor.setNote(None)
|
|
self.modelChooser.cleanup()
|
|
self.deckChooser.cleanup()
|
|
self.mw.maybeReset()
|
|
saveGeom(self, "add")
|
|
aqt.dialogs.close("AddCards")
|
|
QDialog.reject(self)
|
|
|
|
def canClose(self):
|
|
if (self.forceClose or self.editor.fieldsAreBlank() or
|
|
askUser(_("Close and lose current input?"))):
|
|
return True
|
|
return False
|