saveNow() now requires a callback

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.
This commit is contained in:
Damien Elmes 2016-07-14 20:23:44 +10:00
parent 37bac3979c
commit 8e71554ac4
5 changed files with 157 additions and 91 deletions

View file

@ -45,6 +45,11 @@ Anki's webviews are now using WebEngine. Of note:
modified to use this. modified to use this.
- Javascript is evaluated asynchronously, so if you need the result of a JS - Javascript is evaluated asynchronously, so if you need the result of a JS
expression you can use ankiwebview's evalWithCallback(). expression you can use ankiwebview's evalWithCallback().
- As a result of this asynchronous behaviour, editor.saveNow() now requires a
callback. If your add-on performs actions in the browser, you likely need to
call editor.saveNow() first and then run the rest of your code in the callback.
Calls to .onSearch() will need to be changed to .search()/.onSearchActivated()
as well. See the browser's .deleteNotes() for an example.
- You can now debug the webviews using an external Chrome instance, by setting - You can now debug the webviews using an external Chrome instance, by setting
the env var QTWEBENGINE_REMOTE_DEBUGGING to 8080 prior to starting Anki, the env var QTWEBENGINE_REMOTE_DEBUGGING to 8080 prior to starting Anki,
then surfing to localhost:8080 in Chrome. If you run into issues, try then surfing to localhost:8080 in Chrome. If you run into issues, try

View file

@ -151,7 +151,7 @@ class AddCards(QDialog):
def editHistory(self, nid): def editHistory(self, nid):
browser = aqt.dialogs.open("Browser", self.mw) browser = aqt.dialogs.open("Browser", self.mw)
browser.form.searchEdit.lineEdit().setText("nid:%d" % nid) browser.form.searchEdit.lineEdit().setText("nid:%d" % nid)
browser.onSearch() browser.onSearchActivated()
def addNote(self, note): def addNote(self, note):
note.model()['did'] = self.deckChooser.selectedId() note.model()['did'] = self.deckChooser.selectedId()
@ -178,7 +178,9 @@ question on all cards."""), help="AddItems")
return note return note
def addCards(self): def addCards(self):
self.editor.saveNow() self.editor.saveNow(self._addCards)
def _addCards(self):
self.editor.saveAddModeVars() self.editor.saveAddModeVars()
note = self.editor.note note = self.editor.note
note = self.addNote(note) note = self.addNote(note)

View file

@ -115,8 +115,7 @@ class DataModel(QAbstractTableModel):
# Filtering # Filtering
###################################################################### ######################################################################
def search(self, txt, reset=True): def search(self, txt):
if reset:
self.beginReset() self.beginReset()
t = time.time() t = time.time()
# the db progress handler may cause a refresh, so we need to zero out # the db progress handler may cause a refresh, so we need to zero out
@ -125,15 +124,14 @@ class DataModel(QAbstractTableModel):
self.cards = self.col.findCards(txt, order=True) self.cards = self.col.findCards(txt, order=True)
#self.browser.mw.pm.profile['fullSearch']) #self.browser.mw.pm.profile['fullSearch'])
#print "fetch cards in %dms" % ((time.time() - t)*1000) #print "fetch cards in %dms" % ((time.time() - t)*1000)
if reset:
self.endReset() self.endReset()
def reset(self): def reset(self):
self.beginReset() self.beginReset()
self.endReset() self.endReset()
# caller must have called editor.saveNow() before calling this or .reset()
def beginReset(self): def beginReset(self):
self.browser.editor.saveNow()
self.browser.editor.setNote(None, hide=False) self.browser.editor.setNote(None, hide=False)
self.browser.mw.progress.start() self.browser.mw.progress.start()
self.saveSelection() self.saveSelection()
@ -147,6 +145,9 @@ class DataModel(QAbstractTableModel):
self.browser.mw.progress.finish() self.browser.mw.progress.finish()
def reverse(self): def reverse(self):
self.browser.editor.saveNow(self._reverse)
def _reverse(self):
self.beginReset() self.beginReset()
self.cards.reverse() self.cards.reverse()
self.endReset() self.endReset()
@ -351,6 +352,7 @@ class Browser(QMainWindow):
self.col = self.mw.col self.col = self.mw.col
self.lastFilter = "" self.lastFilter = ""
self._previewWindow = None self._previewWindow = None
self._closeEventHasCleanedUp = False
self.form = aqt.forms.browser.Ui_Dialog() self.form = aqt.forms.browser.Ui_Dialog()
self.form.setupUi(self) self.form.setupUi(self)
restoreGeom(self, "editor", 0) restoreGeom(self, "editor", 0)
@ -364,17 +366,13 @@ class Browser(QMainWindow):
self.setupColumns() self.setupColumns()
self.setupTable() self.setupTable()
self.setupMenus() self.setupMenus()
self.setupSearch()
self.setupTree() self.setupTree()
self.setupHeaders() self.setupHeaders()
self.setupHooks() self.setupHooks()
self.setupEditor() self.setupEditor()
self.updateFont() self.updateFont()
self.onUndoState(self.mw.form.actionUndo.isEnabled()) self.onUndoState(self.mw.form.actionUndo.isEnabled())
self.form.searchEdit.setFocus() self.setupSearch()
self.form.searchEdit.lineEdit().setText("is:current")
self.form.searchEdit.lineEdit().selectAll()
self.onSearch()
self.show() self.show()
def setupToolbar(self): def setupToolbar(self):
@ -391,7 +389,6 @@ class Browser(QMainWindow):
f.actionClose.setVisible(False) f.actionClose.setVisible(False)
f.actionReposition.triggered.connect(self.reposition) f.actionReposition.triggered.connect(self.reposition)
f.actionReschedule.triggered.connect(self.reschedule) f.actionReschedule.triggered.connect(self.reschedule)
f.actionCram.triggered.connect(self.cram)
f.actionChangeModel.triggered.connect(self.onChangeModel) f.actionChangeModel.triggered.connect(self.onChangeModel)
# edit # edit
f.actionUndo.triggered.connect(self.mw.onUndo) f.actionUndo.triggered.connect(self.mw.onUndo)
@ -458,21 +455,36 @@ class Browser(QMainWindow):
curmax + 6) curmax + 6)
def closeEvent(self, evt): def closeEvent(self, evt):
if not self._closeEventHasCleanedUp:
if self.editor.note:
# ignore event for now to allow us to save
self.editor.saveNow(self._closeEventAfterSave)
evt.ignore()
else:
self._closeEventCleanup()
evt.accept()
self.mw.gcWindow(self)
else:
evt.accept()
self.mw.gcWindow(self)
def _closeEventAfterSave(self):
self._closeEventCleanup()
self.close()
def _closeEventCleanup(self):
self.editor.setNote(None)
saveSplitter(self.form.splitter_2, "editor2") saveSplitter(self.form.splitter_2, "editor2")
saveSplitter(self.form.splitter, "editor3") saveSplitter(self.form.splitter, "editor3")
self.editor.saveNow()
self.editor.setNote(None)
saveGeom(self, "editor") saveGeom(self, "editor")
saveState(self, "editor") saveState(self, "editor")
saveHeader(self.form.tableView.horizontalHeader(), "editor") saveHeader(self.form.tableView.horizontalHeader(), "editor")
self.col.conf['activeCols'] = self.model.activeCols self.col.conf['activeCols'] = self.model.activeCols
self.col.setMod() self.col.setMod()
self.hide()
aqt.dialogs.close("Browser")
self.teardownHooks() self.teardownHooks()
self.mw.maybeReset() self.mw.maybeReset()
self.mw.gcWindow(self) aqt.dialogs.close("Browser")
evt.accept() self._closeEventHasCleanedUp = True
def canClose(self): def canClose(self):
return True return True
@ -510,19 +522,31 @@ class Browser(QMainWindow):
###################################################################### ######################################################################
def setupSearch(self): def setupSearch(self):
self.filterTimer = None
self.form.searchEdit.setLineEdit(FavouritesLineEdit(self.mw, self)) self.form.searchEdit.setLineEdit(FavouritesLineEdit(self.mw, self))
self.form.searchButton.clicked.connect(self.onSearch) self.form.searchButton.clicked.connect(self.onSearchActivated)
self.form.searchEdit.lineEdit().returnPressed.connect(self.onSearch) self.form.searchEdit.lineEdit().returnPressed.connect(self.onSearchActivated)
self.form.searchEdit.setCompleter(None) self.form.searchEdit.setCompleter(None)
self.form.searchEdit.addItems(self.mw.pm.profile['searchHistory']) self.form.searchEdit.addItems(self.mw.pm.profile['searchHistory'])
self._searchPrompt = _("<type here to search; hit enter to show current deck>")
self._lastSearchTxt = "is:current"
self.search()
# then replace text for easily showing the deck
self.form.searchEdit.lineEdit().setText(self._searchPrompt)
self.form.searchEdit.lineEdit().selectAll()
self.form.searchEdit.setFocus()
def onSearch(self, reset=True): # search triggered by user
"Careful: if reset is true, the current note is saved." def onSearchActivated(self):
self.editor.saveNow(self._onSearchActivated)
def _onSearchActivated(self):
# convert guide text before we save history
if self.form.searchEdit.lineEdit().text() == self._searchPrompt:
self.form.searchEdit.lineEdit().setText("deck:current ")
# update history
txt = str(self.form.searchEdit.lineEdit().text()).strip() txt = str(self.form.searchEdit.lineEdit().text()).strip()
prompt = _("<type here to search; hit enter to show current deck>")
sh = self.mw.pm.profile['searchHistory'] sh = self.mw.pm.profile['searchHistory']
# update search history
if txt in sh: if txt in sh:
sh.remove(txt) sh.remove(txt)
sh.insert(0, txt) sh.insert(0, txt)
@ -530,30 +554,25 @@ class Browser(QMainWindow):
self.form.searchEdit.clear() self.form.searchEdit.clear()
self.form.searchEdit.addItems(sh) self.form.searchEdit.addItems(sh)
self.mw.pm.profile['searchHistory'] = sh self.mw.pm.profile['searchHistory'] = sh
if self.mw.state == "review" and "is:current" in txt:
# search for current card, but set search to easily display whole # keep track of search string so that we reuse identical search when
# deck # refreshing, rather than whatever is currently in the search field
if reset: self._lastSearchTxt = txt
self.model.beginReset() self.search()
self.model.focusedCard = self.mw.reviewer.card.id
self.model.search("nid:%d"%self.mw.reviewer.card.nid, False) # search triggered programmatically. caller must have saved note first.
if reset: def search(self):
self.model.endReset() if "is:current" in self._lastSearchTxt:
self.form.searchEdit.lineEdit().setText(prompt) # show current card if there is one
self.form.searchEdit.lineEdit().selectAll() c = self.mw.reviewer.card
return nid = c and c.nid or 0
elif "is:current" in txt: self.model.search("nid:%d"%nid)
self.form.searchEdit.lineEdit().setText(prompt) else:
self.form.searchEdit.lineEdit().selectAll() self.model.search(self._lastSearchTxt)
elif txt == prompt:
self.form.searchEdit.lineEdit().setText("deck:current ")
txt = "deck:current "
self.model.search(txt, reset)
if not self.model.cards: if not self.model.cards:
# no row change will fire # no row change will fire
self.onRowChanged(None, None) self._onRowChanged(None, None)
elif self.mw.state == "review":
self.focusCid(self.mw.reviewer.card.id)
def updateTitle(self): def updateTitle(self):
selected = len(self.form.tableView.selectionModel().selectedRows()) selected = len(self.form.tableView.selectionModel().selectedRows())
@ -568,7 +587,7 @@ class Browser(QMainWindow):
def onReset(self): def onReset(self):
self.editor.setNote(None) self.editor.setNote(None)
self.onSearch() self.search()
# Table view & editor # Table view & editor
###################################################################### ######################################################################
@ -588,6 +607,9 @@ class Browser(QMainWindow):
def onRowChanged(self, current, previous): def onRowChanged(self, current, previous):
"Update current note and hide/show editor." "Update current note and hide/show editor."
self.editor.saveNow(lambda: self._onRowChanged(current, previous))
def _onRowChanged(self, current, previous):
update = self.updateTitle() update = self.updateTitle()
show = self.model.cards and update == 1 show = self.model.cards and update == 1
self.form.splitter.widget(1).setVisible(not not show) self.form.splitter.widget(1).setVisible(not not show)
@ -636,14 +658,16 @@ class Browser(QMainWindow):
hh.sectionMoved.connect(self.onColumnMoved) hh.sectionMoved.connect(self.onColumnMoved)
def onSortChanged(self, idx, ord): def onSortChanged(self, idx, ord):
self.editor.saveNow(lambda: self._onSortChanged(idx, ord))
def _onSortChanged(self, idx, ord):
type = self.model.activeCols[idx] type = self.model.activeCols[idx]
noSort = ("question", "answer", "template", "deck", "note", "noteTags") noSort = ("question", "answer", "template", "deck", "note", "noteTags")
if type in noSort: if type in noSort:
if type == "template": if type == "template":
# fixme: change to 'card:1' to be clearer in future dev round
showInfo(_("""\ showInfo(_("""\
This column can't be sorted on, but you can search for individual card types, \ This column can't be sorted on, but you can search for individual card types, \
such as 'card:Card 1'.""")) such as 'card:1'."""))
elif type == "deck": elif type == "deck":
showInfo(_("""\ showInfo(_("""\
This column can't be sorted on, but you can search for specific decks \ This column can't be sorted on, but you can search for specific decks \
@ -658,7 +682,7 @@ by clicking on one on the left."""))
if type == "noteFld": if type == "noteFld":
ord = not ord ord = not ord
self.col.conf['sortBackwards'] = ord self.col.conf['sortBackwards'] = ord
self.onSearch() self.search()
else: else:
if self.col.conf['sortBackwards'] != ord: if self.col.conf['sortBackwards'] != ord:
self.col.conf['sortBackwards'] = ord self.col.conf['sortBackwards'] = ord
@ -692,6 +716,9 @@ by clicking on one on the left."""))
m.exec_(gpos) m.exec_(gpos)
def toggleField(self, type): def toggleField(self, type):
self.editor.saveNow(lambda: self._toggleField(type))
def _toggleField(self, type):
self.model.beginReset() self.model.beginReset()
if type in self.model.activeCols: if type in self.model.activeCols:
if len(self.model.activeCols) < 2: if len(self.model.activeCols) < 2:
@ -778,15 +805,14 @@ by clicking on one on the left."""))
txt = "-"+txt txt = "-"+txt
if self.mw.app.keyboardModifiers() & Qt.ControlModifier: if self.mw.app.keyboardModifiers() & Qt.ControlModifier:
cur = str(self.form.searchEdit.lineEdit().text()) cur = str(self.form.searchEdit.lineEdit().text())
if cur and cur != \ if cur and cur != self._searchPrompt:
_("<type here to search; hit enter to show current deck>"):
txt = cur + " " + txt txt = cur + " " + txt
elif self.mw.app.keyboardModifiers() & Qt.ShiftModifier: elif self.mw.app.keyboardModifiers() & Qt.ShiftModifier:
cur = str(self.form.searchEdit.lineEdit().text()) cur = str(self.form.searchEdit.lineEdit().text())
if cur: if cur:
txt = cur + " or " + txt txt = cur + " or " + txt
self.form.searchEdit.lineEdit().setText(txt) self.form.searchEdit.lineEdit().setText(txt)
self.onSearch() self.onSearchActivated()
def _systemTagTree(self, root): def _systemTagTree(self, root):
tags = ( tags = (
@ -975,15 +1001,13 @@ where id in %s""" % ids2str(sf))
###################################################################### ######################################################################
def onChangeModel(self): def onChangeModel(self):
self.editor.saveNow(self._onChangeModel)
def _onChangeModel(self):
nids = self.oneModelNotes() nids = self.oneModelNotes()
if nids: if nids:
ChangeModel(self, nids) ChangeModel(self, nids)
def cram(self):
return showInfo("not yet implemented")
self.close()
self.mw.onCram(self.selectedCards())
# Preview # Preview
###################################################################### ######################################################################
@ -1102,6 +1126,9 @@ where id in %s""" % ids2str(sf))
###################################################################### ######################################################################
def deleteNotes(self): def deleteNotes(self):
self.editor.saveNow(self._deleteNotes)
def _deleteNotes(self):
nids = self.selectedNotes() nids = self.selectedNotes()
if not nids: if not nids:
return return
@ -1122,7 +1149,7 @@ where id in %s""" % ids2str(sf))
# last selection at top; place one above topmost selection # last selection at top; place one above topmost selection
newRow = min(selectedRows) - 1 newRow = min(selectedRows) - 1
self.col.remNotes(nids) self.col.remNotes(nids)
self.onSearch(reset=False) self.search()
if len(self.model.cards): if len(self.model.cards):
newRow = min(newRow, len(self.model.cards) - 1) newRow = min(newRow, len(self.model.cards) - 1)
newRow = max(newRow, 0) newRow = max(newRow, 0)
@ -1135,6 +1162,9 @@ where id in %s""" % ids2str(sf))
###################################################################### ######################################################################
def setDeck(self): def setDeck(self):
self.editor.saveNow(self._setDeck)
def _setDeck(self):
from aqt.studydeck import StudyDeck from aqt.studydeck import StudyDeck
cids = self.selectedCards() cids = self.selectedCards()
if not cids: if not cids:
@ -1171,6 +1201,9 @@ update cards set usn=?, mod=?, did=? where id in """ + scids,
###################################################################### ######################################################################
def addTags(self, tags=None, label=None, prompt=None, func=None): def addTags(self, tags=None, label=None, prompt=None, func=None):
self.editor.saveNow(lambda: self._addTags(tags, label, prompt, func))
def _addTags(self, tags, label, prompt, func):
if prompt is None: if prompt is None:
prompt = _("Enter tags to add:") prompt = _("Enter tags to add:")
if tags is None: if tags is None:
@ -1202,11 +1235,11 @@ update cards set usn=?, mod=?, did=? where id in """ + scids,
def isSuspended(self): def isSuspended(self):
return not not (self.card and self.card.queue == -1) return not not (self.card and self.card.queue == -1)
def onSuspend(self, sus=None): def onSuspend(self):
if sus is None: self.editor.saveNow(self._onSuspend)
def _onSuspend(self):
sus = not self.isSuspended() sus = not self.isSuspended()
# focus lost hook may not have chance to fire
self.editor.saveNow()
c = self.selectedCards() c = self.selectedCards()
if sus: if sus:
self.col.sched.suspendCards(c) self.col.sched.suspendCards(c)
@ -1230,6 +1263,9 @@ update cards set usn=?, mod=?, did=? where id in """ + scids,
###################################################################### ######################################################################
def reposition(self): def reposition(self):
self.editor.saveNow(self._reposition)
def _reposition(self):
cids = self.selectedCards() cids = self.selectedCards()
cids2 = self.col.db.list( cids2 = self.col.db.list(
"select id from cards where type = 0 and id in " + ids2str(cids)) "select id from cards where type = 0 and id in " + ids2str(cids))
@ -1253,7 +1289,7 @@ update cards set usn=?, mod=?, did=? where id in """ + scids,
self.col.sched.sortCards( self.col.sched.sortCards(
cids, start=frm.start.value(), step=frm.step.value(), cids, start=frm.start.value(), step=frm.step.value(),
shuffle=frm.randomize.isChecked(), shift=frm.shift.isChecked()) shuffle=frm.randomize.isChecked(), shift=frm.shift.isChecked())
self.onSearch(reset=False) self.search()
self.mw.requireReset() self.mw.requireReset()
self.model.endReset() self.model.endReset()
@ -1261,6 +1297,9 @@ update cards set usn=?, mod=?, did=? where id in """ + scids,
###################################################################### ######################################################################
def reschedule(self): def reschedule(self):
self.editor.saveNow(self._reschedule)
def _reschedule(self):
d = QDialog(self) d = QDialog(self)
d.setWindowModality(Qt.WindowModal) d.setWindowModality(Qt.WindowModal)
frm = aqt.forms.reschedule.Ui_Dialog() frm = aqt.forms.reschedule.Ui_Dialog()
@ -1277,7 +1316,7 @@ update cards set usn=?, mod=?, did=? where id in """ + scids,
fmax = max(fmin, fmax) fmax = max(fmin, fmax)
self.col.sched.reschedCards( self.col.sched.reschedCards(
self.selectedCards(), fmin, fmax) self.selectedCards(), fmin, fmax)
self.onSearch(reset=False) self.search()
self.mw.requireReset() self.mw.requireReset()
self.model.endReset() self.model.endReset()
@ -1285,12 +1324,17 @@ update cards set usn=?, mod=?, did=? where id in """ + scids,
###################################################################### ######################################################################
def selectNotes(self): def selectNotes(self):
self.editor.saveNow(self._selectNotes)
def _selectNotes(self):
nids = self.selectedNotes() nids = self.selectedNotes()
self.form.searchEdit.lineEdit().setText("nid:"+",".join([str(x) for x in nids])) # bypass search history
self._lastSearchTxt = "nid:"+",".join([str(x) for x in nids])
self.form.searchEdit.lineEdit().setText(self._lastSearchTxt)
# clear the selection so we don't waste energy preserving it # clear the selection so we don't waste energy preserving it
tv = self.form.tableView tv = self.form.tableView
tv.selectionModel().clear() tv.selectionModel().clear()
self.onSearch() self.search()
tv.selectAll() tv.selectAll()
def invertSelection(self): def invertSelection(self):
@ -1327,6 +1371,9 @@ update cards set usn=?, mod=?, did=? where id in """ + scids,
###################################################################### ######################################################################
def onFindReplace(self): def onFindReplace(self):
self.editor.saveNow(self._onFindReplace)
def _onFindReplace(self):
sf = self.selectedNotes() sf = self.selectedNotes()
if not sf: if not sf:
return return
@ -1361,7 +1408,7 @@ update cards set usn=?, mod=?, did=? where id in """ + scids,
showInfo(_("Invalid regular expression."), parent=self) showInfo(_("Invalid regular expression."), parent=self)
return return
else: else:
self.onSearch() self.search()
self.mw.requireReset() self.mw.requireReset()
finally: finally:
self.model.endReset() self.model.endReset()
@ -1380,6 +1427,9 @@ update cards set usn=?, mod=?, did=? where id in """ + scids,
###################################################################### ######################################################################
def onFindDupes(self): def onFindDupes(self):
self.editor.saveNow(self._onFindDupes)
def _onFindDupes(self):
d = QDialog(self) d = QDialog(self)
self.mw.setupDialogGC(d) self.mw.setupDialogGC(d)
frm = aqt.forms.finddupes.Ui_Dialog() frm = aqt.forms.finddupes.Ui_Dialog()
@ -1441,7 +1491,9 @@ update cards set usn=?, mod=?, did=? where id in """ + scids,
def dupeLinkClicked(self, link): def dupeLinkClicked(self, link):
self.form.searchEdit.lineEdit().setText(link) self.form.searchEdit.lineEdit().setText(link)
self.onSearch() # manually, because we've already saved
self._lastSearchTxt = link
self.search()
self.onNote() self.onNote()
# Jumping # Jumping
@ -1450,7 +1502,6 @@ update cards set usn=?, mod=?, did=? where id in """ + scids,
def _moveCur(self, dir=None, idx=None): def _moveCur(self, dir=None, idx=None):
if not self.model.cards: if not self.model.cards:
return return
self.editor.saveNow()
tv = self.form.tableView tv = self.form.tableView
if idx is None: if idx is None:
idx = tv.moveCursor(dir, self.mw.app.keyboardModifiers()) idx = tv.moveCursor(dir, self.mw.app.keyboardModifiers())
@ -1458,12 +1509,18 @@ update cards set usn=?, mod=?, did=? where id in """ + scids,
tv.setCurrentIndex(idx) tv.setCurrentIndex(idx)
def onPreviousCard(self): def onPreviousCard(self):
self.editor.saveNow(self._onPreviousCard)
def _onPreviousCard(self):
f = self.editor.currentField f = self.editor.currentField
self._moveCur(QAbstractItemView.MoveUp) self._moveCur(QAbstractItemView.MoveUp)
self.editor.web.setFocus() self.editor.web.setFocus()
self.editor.web.eval("focusField(%d)" % f) self.editor.web.eval("focusField(%d)" % f)
def onNextCard(self): def onNextCard(self):
self.editor.saveNow(self._onNextCard)
def _onNextCard(self):
f = self.editor.currentField f = self.editor.currentField
self._moveCur(QAbstractItemView.MoveDown) self._moveCur(QAbstractItemView.MoveDown)
self.editor.web.setFocus() self.editor.web.setFocus()
@ -1675,7 +1732,7 @@ Are you sure you want to continue?""")):
b.model.beginReset() b.model.beginReset()
mm = b.mw.col.models mm = b.mw.col.models
mm.change(self.oldModel, self.nids, self.targetModel, fmap, cmap) mm.change(self.oldModel, self.nids, self.targetModel, fmap, cmap)
b.onSearch(reset=False) b.search()
b.model.endReset() b.model.endReset()
b.mw.progress.finish() b.mw.progress.finish()
b.mw.reset() b.mw.reset()

View file

@ -51,8 +51,10 @@ class EditCurrent(QDialog):
self.editor.setNote(n) self.editor.setNote(n)
def onSave(self): def onSave(self):
self.editor.saveNow(self._onSave)
def _onSave(self):
remHook("reset", self.onReset) remHook("reset", self.onReset)
self.editor.saveNow()
r = self.mw.reviewer r = self.mw.reviewer
try: try:
r.card.load() r.card.load()

View file

@ -80,6 +80,7 @@ function setFGButton(col) {
}; };
function saveNow() { function saveNow() {
clearChangeTimer();
if (currentField) { if (currentField) {
currentField.blur(); currentField.blur();
} }
@ -453,13 +454,17 @@ class Editor(object):
# _("Record audio (F5)") # _("Record audio (F5)")
def onFields(self): def onFields(self):
self.saveNow(self._onFields)
def _onFields(self):
from aqt.fields import FieldDialog from aqt.fields import FieldDialog
self.saveNow()
FieldDialog(self.mw, self.note, parent=self.parentWindow) FieldDialog(self.mw, self.note, parent=self.parentWindow)
def onCardLayout(self): def onCardLayout(self):
self.saveNow(self._onCardLayout)
def _onCardLayout(self):
from aqt.clayout import CardLayout from aqt.clayout import CardLayout
self.saveNow()
if self.card: if self.card:
ord = self.card.ord ord = self.card.ord
else: else:
@ -581,20 +586,13 @@ class Editor(object):
return [(f['font'], f['size'], f['rtl']) return [(f['font'], f['size'], f['rtl'])
for f in self.note.model()['flds']] for f in self.note.model()['flds']]
def saveNow(self): def saveNow(self, callback):
"Must call this before adding cards, closing dialog, etc." "Save unsaved edits then call callback()."
if not self.note: if not self.note:
callback()
return return
self.saveTags() self.saveTags()
# move focus out of fields and save tags self.web.evalWithCallback("saveNow()", lambda res: callback())
self._saveNowWaiting = True
self.web.evalWithCallback("saveNow()", self._saveNowDone)
while self._saveNowWaiting:
# and process events so any focus-lost hooks fire
self.mw.app.processEvents()
def _saveNowDone(self, res):
self._saveNowWaiting = False
def checkValid(self): def checkValid(self):
cols = [] cols = []
@ -615,7 +613,7 @@ class Editor(object):
browser.form.searchEdit.lineEdit().setText( browser.form.searchEdit.lineEdit().setText(
'"dupe:%s,%s"' % (self.note.model()['id'], '"dupe:%s,%s"' % (self.note.model()['id'],
contents)) contents))
browser.onSearch() browser.onSearchActivated()
def fieldsAreBlank(self): def fieldsAreBlank(self):
if not self.note: if not self.note:
@ -630,7 +628,9 @@ class Editor(object):
###################################################################### ######################################################################
def onHtmlEdit(self): def onHtmlEdit(self):
self.saveNow() self.saveNow(self._onHtmlEdit)
def _onHtmlEdit(self):
d = QDialog(self.widget) d = QDialog(self.widget)
form = aqt.forms.edithtml.Ui_Dialog() form = aqt.forms.edithtml.Ui_Dialog()
form.setupUi(d) form.setupUi(d)