mirror of
https://github.com/ankitects/anki.git
synced 2025-09-19 14:32:22 -04:00
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:
parent
ca9d80c014
commit
8ab5a3a176
8 changed files with 97 additions and 64 deletions
|
@ -38,8 +38,8 @@ except ImportError as e:
|
||||||
|
|
||||||
from anki.utils import checksum
|
from anki.utils import checksum
|
||||||
|
|
||||||
# Dialog manager - manages modeless windows
|
# Dialog manager - manages non-modal windows
|
||||||
##########################################################################emacs
|
##########################################################################
|
||||||
|
|
||||||
class DialogManager:
|
class DialogManager:
|
||||||
|
|
||||||
|
@ -65,18 +65,32 @@ class DialogManager:
|
||||||
self._dialogs[name][1] = instance
|
self._dialogs[name][1] = instance
|
||||||
return instance
|
return instance
|
||||||
|
|
||||||
def close(self, name):
|
def markClosed(self, name):
|
||||||
self._dialogs[name] = [self._dialogs[name][0], None]
|
self._dialogs[name] = [self._dialogs[name][0], None]
|
||||||
|
|
||||||
def closeAll(self):
|
def allClosed(self):
|
||||||
"True if all closed successfully."
|
return not any(x[1] for x in self._dialogs.values())
|
||||||
for (n, (creator, instance)) in list(self._dialogs.items()):
|
|
||||||
if instance:
|
def closeAll(self, onsuccess):
|
||||||
if not instance.canClose():
|
# can we close immediately?
|
||||||
return False
|
if self.allClosed():
|
||||||
instance.forceClose = True
|
onsuccess()
|
||||||
instance.close()
|
return
|
||||||
self.close(n)
|
|
||||||
|
# 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
|
return True
|
||||||
|
|
||||||
dialogs = DialogManager()
|
dialogs = DialogManager()
|
||||||
|
|
13
aqt/about.py
13
aqt/about.py
|
@ -7,17 +7,18 @@ import aqt.forms
|
||||||
from aqt import appVersion
|
from aqt import appVersion
|
||||||
|
|
||||||
class ClosableQDialog(QDialog):
|
class ClosableQDialog(QDialog):
|
||||||
def canClose(self):
|
|
||||||
return True
|
|
||||||
|
|
||||||
def reject(self):
|
def reject(self):
|
||||||
aqt.dialogs.close("About")
|
aqt.dialogs.markClosed("About")
|
||||||
QDialog.reject(self)
|
QDialog.reject(self)
|
||||||
|
|
||||||
def accept(self):
|
def accept(self):
|
||||||
aqt.dialogs.close("About")
|
aqt.dialogs.markClosed("About")
|
||||||
QDialog.accept(self)
|
QDialog.accept(self)
|
||||||
|
|
||||||
|
def closeWithCallback(self, callback):
|
||||||
|
self.reject()
|
||||||
|
callback()
|
||||||
|
|
||||||
def show(mw):
|
def show(mw):
|
||||||
dialog = ClosableQDialog(mw)
|
dialog = ClosableQDialog(mw)
|
||||||
mw.setupDialogGC(dialog)
|
mw.setupDialogGC(dialog)
|
||||||
|
@ -124,6 +125,8 @@ please get in touch.")
|
||||||
abouttext += '<p>' + _("A big thanks to all the people who have provided \
|
abouttext += '<p>' + _("A big thanks to all the people who have provided \
|
||||||
suggestions, bug reports and donations.")
|
suggestions, bug reports and donations.")
|
||||||
abt.label.stdHtml(abouttext, js=" ")
|
abt.label.stdHtml(abouttext, js=" ")
|
||||||
|
def resizeAndShow(arg):
|
||||||
dialog.adjustSize()
|
dialog.adjustSize()
|
||||||
dialog.show()
|
dialog.show()
|
||||||
|
abt.label.evalWithCallback("1", resizeAndShow)
|
||||||
return dialog
|
return dialog
|
||||||
|
|
|
@ -28,7 +28,6 @@ class AddCards(QDialog):
|
||||||
self.setupButtons()
|
self.setupButtons()
|
||||||
self.onReset()
|
self.onReset()
|
||||||
self.history = []
|
self.history = []
|
||||||
self.forceClose = False
|
|
||||||
restoreGeom(self, "add")
|
restoreGeom(self, "add")
|
||||||
addHook('reset', self.onReset)
|
addHook('reset', self.onReset)
|
||||||
addHook('currentModelChanged', self.onModelChange)
|
addHook('currentModelChanged', self.onModelChange)
|
||||||
|
@ -205,22 +204,32 @@ question on all cards."""), help="AddItems")
|
||||||
return QDialog.keyPressEvent(self, evt)
|
return QDialog.keyPressEvent(self, evt)
|
||||||
|
|
||||||
def reject(self):
|
def reject(self):
|
||||||
if not self.canClose():
|
self.ifCanClose(self._reject)
|
||||||
return
|
|
||||||
|
def _reject(self):
|
||||||
remHook('reset', self.onReset)
|
remHook('reset', self.onReset)
|
||||||
remHook('currentModelChanged', self.onModelChange)
|
remHook('currentModelChanged', self.onModelChange)
|
||||||
clearAudioQueue()
|
clearAudioQueue()
|
||||||
self.removeTempNote(self.editor.note)
|
self.removeTempNote(self.editor.note)
|
||||||
self.editor.setNote(None)
|
self.editor.cleanup()
|
||||||
self.modelChooser.cleanup()
|
self.modelChooser.cleanup()
|
||||||
self.deckChooser.cleanup()
|
self.deckChooser.cleanup()
|
||||||
self.mw.maybeReset()
|
self.mw.maybeReset()
|
||||||
saveGeom(self, "add")
|
saveGeom(self, "add")
|
||||||
aqt.dialogs.close("AddCards")
|
aqt.dialogs.markClosed("AddCards")
|
||||||
QDialog.reject(self)
|
QDialog.reject(self)
|
||||||
|
|
||||||
def canClose(self):
|
def ifCanClose(self, onOk):
|
||||||
if (self.forceClose or self.editor.fieldsAreBlank() or
|
def afterSave():
|
||||||
askUser(_("Close and lose current input?"))):
|
ok = (self.editor.fieldsAreBlank() or
|
||||||
return True
|
askUser(_("Close and lose current input?")))
|
||||||
return False
|
if ok:
|
||||||
|
onOk()
|
||||||
|
|
||||||
|
self.editor.saveNow(afterSave)
|
||||||
|
|
||||||
|
def closeWithCallback(self, cb):
|
||||||
|
def doClose():
|
||||||
|
self._reject()
|
||||||
|
cb()
|
||||||
|
self.ifCanClose(doClose)
|
||||||
|
|
|
@ -366,7 +366,6 @@ class Browser(QMainWindow):
|
||||||
applyStyles(self)
|
applyStyles(self)
|
||||||
self.mw = mw
|
self.mw = mw
|
||||||
self.col = self.mw.col
|
self.col = self.mw.col
|
||||||
self.forceClose = False
|
|
||||||
self.lastFilter = ""
|
self.lastFilter = ""
|
||||||
self.focusTo = None
|
self.focusTo = None
|
||||||
self._previewWindow = None
|
self._previewWindow = None
|
||||||
|
@ -459,26 +458,15 @@ class Browser(QMainWindow):
|
||||||
curmax + 6)
|
curmax + 6)
|
||||||
|
|
||||||
def closeEvent(self, evt):
|
def closeEvent(self, evt):
|
||||||
if not self._closeEventHasCleanedUp:
|
if self._closeEventHasCleanedUp:
|
||||||
if self.editor.note and not self.forceClose:
|
evt.accept()
|
||||||
# ignore event for now to allow us to save
|
return
|
||||||
self.editor.saveNow(self._closeEventAfterSave)
|
self.editor.saveNow(self._closeWindow)
|
||||||
evt.ignore()
|
evt.ignore()
|
||||||
else:
|
|
||||||
self._closeEventCleanup()
|
|
||||||
evt.accept()
|
|
||||||
self.mw.gcWindow(self)
|
|
||||||
else:
|
|
||||||
evt.accept()
|
|
||||||
self.mw.gcWindow(self)
|
|
||||||
|
|
||||||
def _closeEventAfterSave(self):
|
def _closeWindow(self):
|
||||||
self._closeEventCleanup()
|
|
||||||
self.close()
|
|
||||||
|
|
||||||
def _closeEventCleanup(self):
|
|
||||||
self._cancelPreviewTimer()
|
self._cancelPreviewTimer()
|
||||||
self.editor.setNote(None)
|
self.editor.cleanup()
|
||||||
saveSplitter(self.form.splitter, "editor3")
|
saveSplitter(self.form.splitter, "editor3")
|
||||||
saveGeom(self, "editor")
|
saveGeom(self, "editor")
|
||||||
saveState(self, "editor")
|
saveState(self, "editor")
|
||||||
|
@ -487,14 +475,18 @@ class Browser(QMainWindow):
|
||||||
self.col.setMod()
|
self.col.setMod()
|
||||||
self.teardownHooks()
|
self.teardownHooks()
|
||||||
self.mw.maybeReset()
|
self.mw.maybeReset()
|
||||||
aqt.dialogs.close("Browser")
|
aqt.dialogs.markClosed("Browser")
|
||||||
self._closeEventHasCleanedUp = True
|
self._closeEventHasCleanedUp = True
|
||||||
|
self.mw.gcWindow(self)
|
||||||
|
self.close()
|
||||||
|
|
||||||
def canClose(self):
|
def closeWithCallback(self, onsuccess):
|
||||||
return True
|
def callback():
|
||||||
|
self._closeWindow()
|
||||||
|
onsuccess()
|
||||||
|
self.editor.saveNow(callback)
|
||||||
|
|
||||||
def keyPressEvent(self, evt):
|
def keyPressEvent(self, evt):
|
||||||
"Show answer on RET or register answer."
|
|
||||||
if evt.key() == Qt.Key_Escape:
|
if evt.key() == Qt.Key_Escape:
|
||||||
self.close()
|
self.close()
|
||||||
else:
|
else:
|
||||||
|
|
|
@ -12,13 +12,13 @@ class EditCurrent(QDialog):
|
||||||
|
|
||||||
def __init__(self, mw):
|
def __init__(self, mw):
|
||||||
QDialog.__init__(self, None, Qt.Window)
|
QDialog.__init__(self, None, Qt.Window)
|
||||||
|
mw.setupDialogGC(self)
|
||||||
self.mw = mw
|
self.mw = mw
|
||||||
self.form = aqt.forms.editcurrent.Ui_Dialog()
|
self.form = aqt.forms.editcurrent.Ui_Dialog()
|
||||||
self.form.setupUi(self)
|
self.form.setupUi(self)
|
||||||
self.setWindowTitle(_("Edit Current"))
|
self.setWindowTitle(_("Edit Current"))
|
||||||
self.setMinimumHeight(400)
|
self.setMinimumHeight(400)
|
||||||
self.setMinimumWidth(500)
|
self.setMinimumWidth(500)
|
||||||
self.rejected.connect(self.onSave)
|
|
||||||
self.form.buttonBox.button(QDialogButtonBox.Close).setShortcut(
|
self.form.buttonBox.button(QDialogButtonBox.Close).setShortcut(
|
||||||
QKeySequence("Ctrl+Return"))
|
QKeySequence("Ctrl+Return"))
|
||||||
self.editor = aqt.editor.Editor(self.mw, self.form.fieldsArea, self)
|
self.editor = aqt.editor.Editor(self.mw, self.form.fieldsArea, self)
|
||||||
|
@ -40,15 +40,18 @@ class EditCurrent(QDialog):
|
||||||
remHook("reset", self.onReset)
|
remHook("reset", self.onReset)
|
||||||
self.editor.setNote(None)
|
self.editor.setNote(None)
|
||||||
self.mw.reset()
|
self.mw.reset()
|
||||||
aqt.dialogs.close("EditCurrent")
|
aqt.dialogs.markClosed("EditCurrent")
|
||||||
self.close()
|
self.close()
|
||||||
return
|
return
|
||||||
self.editor.setNote(n)
|
self.editor.setNote(n)
|
||||||
|
|
||||||
def onSave(self):
|
def reject(self):
|
||||||
self.editor.saveNow(self._onSave)
|
self.saveAndClose()
|
||||||
|
|
||||||
def _onSave(self):
|
def saveAndClose(self):
|
||||||
|
self.editor.saveNow(self._saveAndClose)
|
||||||
|
|
||||||
|
def _saveAndClose(self):
|
||||||
remHook("reset", self.onReset)
|
remHook("reset", self.onReset)
|
||||||
r = self.mw.reviewer
|
r = self.mw.reviewer
|
||||||
try:
|
try:
|
||||||
|
@ -58,9 +61,14 @@ class EditCurrent(QDialog):
|
||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
self.mw.reviewer.cardQueue.append(self.mw.reviewer.card)
|
self.mw.reviewer.cardQueue.append(self.mw.reviewer.card)
|
||||||
|
self.editor.cleanup()
|
||||||
self.mw.moveToState("review")
|
self.mw.moveToState("review")
|
||||||
saveGeom(self, "editcurrent")
|
saveGeom(self, "editcurrent")
|
||||||
aqt.dialogs.close("EditCurrent")
|
aqt.dialogs.markClosed("EditCurrent")
|
||||||
|
QDialog.reject(self)
|
||||||
|
|
||||||
def canClose(self):
|
def closeWithCallback(self, onsuccess):
|
||||||
return True
|
def callback():
|
||||||
|
self._saveAndClose()
|
||||||
|
onsuccess()
|
||||||
|
self.editor.saveNow(callback)
|
||||||
|
|
|
@ -319,6 +319,11 @@ class Editor:
|
||||||
return False
|
return False
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
def cleanup(self):
|
||||||
|
self.setNote(None)
|
||||||
|
# prevent any remaining evalWithCallback() events from firing after C++ object deleted
|
||||||
|
self.web = None
|
||||||
|
|
||||||
# HTML editing
|
# HTML editing
|
||||||
######################################################################
|
######################################################################
|
||||||
|
|
||||||
|
|
|
@ -530,7 +530,8 @@ title="%s" %s>%s</button>''' % (
|
||||||
self.form.centralwidget.setLayout(self.mainLayout)
|
self.form.centralwidget.setLayout(self.mainLayout)
|
||||||
|
|
||||||
def closeAllCollectionWindows(self):
|
def closeAllCollectionWindows(self):
|
||||||
return aqt.dialogs.closeAll()
|
aqt.dialogs.closeAll(lambda: 1)
|
||||||
|
return True
|
||||||
|
|
||||||
# Components
|
# Components
|
||||||
##########################################################################
|
##########################################################################
|
||||||
|
|
|
@ -42,14 +42,15 @@ class DeckStats(QDialog):
|
||||||
self.show()
|
self.show()
|
||||||
self.activateWindow()
|
self.activateWindow()
|
||||||
|
|
||||||
def canClose(self):
|
|
||||||
return True
|
|
||||||
|
|
||||||
def reject(self):
|
def reject(self):
|
||||||
saveGeom(self, self.name)
|
saveGeom(self, self.name)
|
||||||
aqt.dialogs.close("DeckStats")
|
aqt.dialogs.markClosed("DeckStats")
|
||||||
QDialog.reject(self)
|
QDialog.reject(self)
|
||||||
|
|
||||||
|
def closeWithCallback(self, callback):
|
||||||
|
self.reject()
|
||||||
|
callback()
|
||||||
|
|
||||||
def _imagePath(self):
|
def _imagePath(self):
|
||||||
name = time.strftime("-%Y-%m-%d@%H-%M-%S.pdf",
|
name = time.strftime("-%Y-%m-%d@%H-%M-%S.pdf",
|
||||||
time.localtime(time.time()))
|
time.localtime(time.time()))
|
||||||
|
|
Loading…
Reference in a new issue