use callback when closing windows

remove the old forceClose hack in favour of a callback when closing, so
all windows have a chance to save properly before the collection is
unloaded

also:

- fix a warning shown when opening about screen
- require a call to editor.cleanup() when closing a window, to make sure
 any pending js callbacks don't try to fire on a deleted object
- make sure we gc webview when closing editcurrent
- main.py still needs refactoring to make use of the change
This commit is contained in:
Damien Elmes 2017-08-16 12:45:33 +10:00
parent ca9d80c014
commit 8ab5a3a176
8 changed files with 97 additions and 64 deletions

View File

@ -38,8 +38,8 @@ except ImportError as e:
from anki.utils import checksum
# Dialog manager - manages modeless windows
##########################################################################emacs
# Dialog manager - manages non-modal windows
##########################################################################
class DialogManager:
@ -65,18 +65,32 @@ class DialogManager:
self._dialogs[name][1] = instance
return instance
def close(self, name):
def markClosed(self, name):
self._dialogs[name] = [self._dialogs[name][0], None]
def closeAll(self):
"True if all closed successfully."
for (n, (creator, instance)) in list(self._dialogs.items()):
if instance:
if not instance.canClose():
return False
instance.forceClose = True
instance.close()
self.close(n)
def allClosed(self):
return not any(x[1] for x in self._dialogs.values())
def closeAll(self, onsuccess):
# can we close immediately?
if self.allClosed():
onsuccess()
return
# ask all windows to close and await a reply
for (name, (creator, instance)) in self._dialogs.items():
if not instance:
continue
def callback():
if self.allClosed():
onsuccess()
else:
# still waiting for others to close
pass
instance.closeWithCallback(callback)
return True
dialogs = DialogManager()

View File

@ -7,17 +7,18 @@ import aqt.forms
from aqt import appVersion
class ClosableQDialog(QDialog):
def canClose(self):
return True
def reject(self):
aqt.dialogs.close("About")
aqt.dialogs.markClosed("About")
QDialog.reject(self)
def accept(self):
aqt.dialogs.close("About")
aqt.dialogs.markClosed("About")
QDialog.accept(self)
def closeWithCallback(self, callback):
self.reject()
callback()
def show(mw):
dialog = ClosableQDialog(mw)
mw.setupDialogGC(dialog)
@ -124,6 +125,8 @@ please get in touch.")
abouttext += '<p>' + _("A big thanks to all the people who have provided \
suggestions, bug reports and donations.")
abt.label.stdHtml(abouttext, js=" ")
dialog.adjustSize()
dialog.show()
def resizeAndShow(arg):
dialog.adjustSize()
dialog.show()
abt.label.evalWithCallback("1", resizeAndShow)
return dialog

View File

@ -28,7 +28,6 @@ class AddCards(QDialog):
self.setupButtons()
self.onReset()
self.history = []
self.forceClose = False
restoreGeom(self, "add")
addHook('reset', self.onReset)
addHook('currentModelChanged', self.onModelChange)
@ -205,22 +204,32 @@ question on all cards."""), help="AddItems")
return QDialog.keyPressEvent(self, evt)
def reject(self):
if not self.canClose():
return
self.ifCanClose(self._reject)
def _reject(self):
remHook('reset', self.onReset)
remHook('currentModelChanged', self.onModelChange)
clearAudioQueue()
self.removeTempNote(self.editor.note)
self.editor.setNote(None)
self.editor.cleanup()
self.modelChooser.cleanup()
self.deckChooser.cleanup()
self.mw.maybeReset()
saveGeom(self, "add")
aqt.dialogs.close("AddCards")
aqt.dialogs.markClosed("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
def ifCanClose(self, onOk):
def afterSave():
ok = (self.editor.fieldsAreBlank() or
askUser(_("Close and lose current input?")))
if ok:
onOk()
self.editor.saveNow(afterSave)
def closeWithCallback(self, cb):
def doClose():
self._reject()
cb()
self.ifCanClose(doClose)

View File

@ -366,7 +366,6 @@ class Browser(QMainWindow):
applyStyles(self)
self.mw = mw
self.col = self.mw.col
self.forceClose = False
self.lastFilter = ""
self.focusTo = None
self._previewWindow = None
@ -459,26 +458,15 @@ class Browser(QMainWindow):
curmax + 6)
def closeEvent(self, evt):
if not self._closeEventHasCleanedUp:
if self.editor.note and not self.forceClose:
# 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:
if self._closeEventHasCleanedUp:
evt.accept()
self.mw.gcWindow(self)
return
self.editor.saveNow(self._closeWindow)
evt.ignore()
def _closeEventAfterSave(self):
self._closeEventCleanup()
self.close()
def _closeEventCleanup(self):
def _closeWindow(self):
self._cancelPreviewTimer()
self.editor.setNote(None)
self.editor.cleanup()
saveSplitter(self.form.splitter, "editor3")
saveGeom(self, "editor")
saveState(self, "editor")
@ -487,14 +475,18 @@ class Browser(QMainWindow):
self.col.setMod()
self.teardownHooks()
self.mw.maybeReset()
aqt.dialogs.close("Browser")
aqt.dialogs.markClosed("Browser")
self._closeEventHasCleanedUp = True
self.mw.gcWindow(self)
self.close()
def canClose(self):
return True
def closeWithCallback(self, onsuccess):
def callback():
self._closeWindow()
onsuccess()
self.editor.saveNow(callback)
def keyPressEvent(self, evt):
"Show answer on RET or register answer."
if evt.key() == Qt.Key_Escape:
self.close()
else:

View File

@ -12,13 +12,13 @@ class EditCurrent(QDialog):
def __init__(self, mw):
QDialog.__init__(self, None, Qt.Window)
mw.setupDialogGC(self)
self.mw = mw
self.form = aqt.forms.editcurrent.Ui_Dialog()
self.form.setupUi(self)
self.setWindowTitle(_("Edit Current"))
self.setMinimumHeight(400)
self.setMinimumWidth(500)
self.rejected.connect(self.onSave)
self.form.buttonBox.button(QDialogButtonBox.Close).setShortcut(
QKeySequence("Ctrl+Return"))
self.editor = aqt.editor.Editor(self.mw, self.form.fieldsArea, self)
@ -40,15 +40,18 @@ class EditCurrent(QDialog):
remHook("reset", self.onReset)
self.editor.setNote(None)
self.mw.reset()
aqt.dialogs.close("EditCurrent")
aqt.dialogs.markClosed("EditCurrent")
self.close()
return
self.editor.setNote(n)
def onSave(self):
self.editor.saveNow(self._onSave)
def reject(self):
self.saveAndClose()
def _onSave(self):
def saveAndClose(self):
self.editor.saveNow(self._saveAndClose)
def _saveAndClose(self):
remHook("reset", self.onReset)
r = self.mw.reviewer
try:
@ -58,9 +61,14 @@ class EditCurrent(QDialog):
pass
else:
self.mw.reviewer.cardQueue.append(self.mw.reviewer.card)
self.editor.cleanup()
self.mw.moveToState("review")
saveGeom(self, "editcurrent")
aqt.dialogs.close("EditCurrent")
aqt.dialogs.markClosed("EditCurrent")
QDialog.reject(self)
def canClose(self):
return True
def closeWithCallback(self, onsuccess):
def callback():
self._saveAndClose()
onsuccess()
self.editor.saveNow(callback)

View File

@ -319,6 +319,11 @@ class Editor:
return False
return True
def cleanup(self):
self.setNote(None)
# prevent any remaining evalWithCallback() events from firing after C++ object deleted
self.web = None
# HTML editing
######################################################################

View File

@ -530,7 +530,8 @@ title="%s" %s>%s</button>''' % (
self.form.centralwidget.setLayout(self.mainLayout)
def closeAllCollectionWindows(self):
return aqt.dialogs.closeAll()
aqt.dialogs.closeAll(lambda: 1)
return True
# Components
##########################################################################

View File

@ -42,14 +42,15 @@ class DeckStats(QDialog):
self.show()
self.activateWindow()
def canClose(self):
return True
def reject(self):
saveGeom(self, self.name)
aqt.dialogs.close("DeckStats")
aqt.dialogs.markClosed("DeckStats")
QDialog.reject(self)
def closeWithCallback(self, callback):
self.reject()
callback()
def _imagePath(self):
name = time.strftime("-%Y-%m-%d@%H-%M-%S.pdf",
time.localtime(time.time()))