mirror of
https://github.com/olivierkes/manuskript.git
synced 2024-05-14 09:52:27 +12:00
Track dirty state and have the UI respect it
Intending to learn more about the way Manuskript goes about saving the project in order to figure out how to tackle some recent saving-related issues, I stumbled into learning that Manuskript likes to save data a whole lot. Too much, in fact. When I close the project with unsaved changes, I expected those changes to not be saved... but they were. This completely subverts my expectations of a program using typical file-based operations involving opening, saving and closing files. There are three more settings that influence when the program saves, and I personally consider them a bit overkill or even detrimental to the stated purpose. What if Manuskript forces a save when nothing was changed and something goes wrong? Saving too much can in fact be dangerous! For now, I have left existing functionality as-is, but I would prefer to respect the dirty flag I have introduced in this commit for at least the 'save-on-quit' and 'save every X minutes' features. (The third is smarter and only triggers after noticing changes, so it is less important.) Making sure the dirty flag works as expected is the first step in making such changes in the future. UI-wise, this commit now offers the user the opportunity to save their changes, discard them, or outright cancel their action entirely when performing a destructive action on a dirty project. As of this commit, I have identified two of such scenarios: 1) closing the project, 2) closing the window with save-on-quit turned off. If I missed any, do let me know. But for now, maybe now I can finally start digging into those issues that sent me down this rabbit hole...
This commit is contained in:
parent
60b6f98c21
commit
97cf0e1373
|
@ -7,7 +7,7 @@ from PyQt5.QtCore import (pyqtSignal, QSignalMapper, QTimer, QSettings, Qt, QPoi
|
|||
QRegExp, QUrl, QSize, QModelIndex)
|
||||
from PyQt5.QtGui import QStandardItemModel, QIcon, QColor
|
||||
from PyQt5.QtWidgets import QMainWindow, QHeaderView, qApp, QMenu, QActionGroup, QAction, QStyle, QListWidgetItem, \
|
||||
QLabel, QDockWidget, QWidget
|
||||
QLabel, QDockWidget, QWidget, QMessageBox
|
||||
|
||||
from manuskript import settings
|
||||
from manuskript.enums import Character, PlotStep, Plot, World, Outline
|
||||
|
@ -57,6 +57,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
|||
|
||||
# Var
|
||||
self.currentProject = None
|
||||
self.projectDirty = None # has the user made any unsaved changes ?
|
||||
self._lastFocus = None
|
||||
self._lastMDEditView = None
|
||||
self._defaultCursorFlashTime = 1000 # Overriden at startup with system
|
||||
|
@ -273,6 +274,15 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
|||
break
|
||||
new = new.parent()
|
||||
|
||||
def projectName(self):
|
||||
"""
|
||||
Returns a user-friendly name for the loaded project.
|
||||
"""
|
||||
pName = os.path.split(self.currentProject)[1]
|
||||
if pName.endswith('.msk'):
|
||||
pName=pName[:-4]
|
||||
return pName
|
||||
|
||||
###############################################################################
|
||||
# SUMMARY
|
||||
###############################################################################
|
||||
|
@ -618,33 +628,68 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
|||
# We force to emit even if it opens on the current tab
|
||||
self.tabMain.currentChanged.emit(settings.lastTab)
|
||||
|
||||
# Make sure we can update the window title later.
|
||||
self.currentProject = project
|
||||
self.projectDirty = False
|
||||
QSettings().setValue("lastProject", project)
|
||||
|
||||
# Add project name to Window's name
|
||||
pName = os.path.split(project)[1]
|
||||
if pName.endswith('.msk'):
|
||||
pName=pName[:-4]
|
||||
self.setWindowTitle(pName + " - " + self.tr("Manuskript"))
|
||||
self.setWindowTitle(self.projectName() + " - " + self.tr("Manuskript"))
|
||||
|
||||
# Stuff
|
||||
# self.checkPersosID() # Shouldn't be necessary any longer
|
||||
|
||||
self.currentProject = project
|
||||
QSettings().setValue("lastProject", project)
|
||||
|
||||
# Show main Window
|
||||
self.switchToProject()
|
||||
|
||||
def handleUnsavedChanges(self):
|
||||
"""
|
||||
There may be some currently unsaved changes, but the action the user triggered
|
||||
will result in the project or application being closed. To save, or not to save?
|
||||
|
||||
Or just bail out entirely?
|
||||
|
||||
Sometimes it is best to just ask.
|
||||
"""
|
||||
|
||||
if not self.projectDirty:
|
||||
return True # no unsaved changes, all is good
|
||||
|
||||
msg = QMessageBox(QMessageBox.Question,
|
||||
self.tr("Save project?"),
|
||||
"<p><b>" +
|
||||
self.tr("Save changes to project \"{}\" before closing?").format(self.projectName()) +
|
||||
"</b></p>" +
|
||||
"<p>" +
|
||||
self.tr("Your changes will be lost if you don't save them.") +
|
||||
"</p>",
|
||||
QMessageBox.Save | QMessageBox.Discard | QMessageBox.Cancel)
|
||||
|
||||
ret = msg.exec()
|
||||
|
||||
if ret == QMessageBox.Cancel:
|
||||
return False # the situation has not been handled, cancel action
|
||||
|
||||
if ret == QMessageBox.Save:
|
||||
self.saveDatas()
|
||||
|
||||
return True # the situation has been handled
|
||||
|
||||
|
||||
def closeProject(self):
|
||||
|
||||
if not self.currentProject:
|
||||
return
|
||||
|
||||
# Make sure data is saved.
|
||||
if not self.handleUnsavedChanges():
|
||||
return # user
|
||||
|
||||
# Close open tabs in editor
|
||||
self.mainEditor.closeAllTabs()
|
||||
|
||||
# Save datas
|
||||
self.saveDatas()
|
||||
|
||||
self.currentProject = None
|
||||
self.projectDirty = None
|
||||
QSettings().setValue("lastProject", "")
|
||||
|
||||
# Clear datas
|
||||
|
@ -711,23 +756,6 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
|||
self._toolbarState = ""
|
||||
|
||||
def closeEvent(self, event):
|
||||
# Save State and geometry and other things
|
||||
sttgns = QSettings(qApp.organizationName(), qApp.applicationName())
|
||||
sttgns.setValue("geometry", self.saveGeometry())
|
||||
sttgns.setValue("windowState", self.saveState())
|
||||
sttgns.setValue("metadataState", self.redacMetadata.saveState())
|
||||
sttgns.setValue("revisionsState", self.redacMetadata.revisions.saveState())
|
||||
sttgns.setValue("splitterRedacH", self.splitterRedacH.saveState())
|
||||
sttgns.setValue("splitterRedacV", self.splitterRedacV.saveState())
|
||||
sttgns.setValue("toolbar", self.toolbar.saveState())
|
||||
|
||||
# If we are not in the welcome window, we update the visibility
|
||||
# of the docks widgets
|
||||
if self.stack.currentIndex() == 1:
|
||||
self.updateDockVisibility()
|
||||
# Storing the visibility of docks to restore it on restart
|
||||
sttgns.setValue("docks", self._dckVisibility)
|
||||
|
||||
# Specific settings to save before quitting
|
||||
settings.lastTab = self.tabMain.currentIndex()
|
||||
|
||||
|
@ -735,14 +763,42 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
|||
# Remembering the current items (stores outlineItem's ID)
|
||||
settings.openIndexes = self.mainEditor.tabSplitter.openIndexes()
|
||||
|
||||
# Save data from models
|
||||
if self.currentProject and settings.saveOnQuit:
|
||||
self.saveDatas()
|
||||
# Save data from models
|
||||
if settings.saveOnQuit:
|
||||
self.saveDatas()
|
||||
elif not self.handleUnsavedChanges():
|
||||
event.ignore() # user opted to cancel the close action
|
||||
|
||||
# closeEvent
|
||||
# QMainWindow.closeEvent(self, event) # Causing segfaults?
|
||||
|
||||
# User may have canceled close event, so make sure we indeed want to close.
|
||||
# This is necessary because self.updateDockVisibility() hides UI elements.
|
||||
if event.isAccepted():
|
||||
# Save State and geometry and other things
|
||||
appSettings = QSettings(qApp.organizationName(), qApp.applicationName())
|
||||
appSettings.setValue("geometry", self.saveGeometry())
|
||||
appSettings.setValue("windowState", self.saveState())
|
||||
appSettings.setValue("metadataState", self.redacMetadata.saveState())
|
||||
appSettings.setValue("revisionsState", self.redacMetadata.revisions.saveState())
|
||||
appSettings.setValue("splitterRedacH", self.splitterRedacH.saveState())
|
||||
appSettings.setValue("splitterRedacV", self.splitterRedacV.saveState())
|
||||
appSettings.setValue("toolbar", self.toolbar.saveState())
|
||||
|
||||
# If we are not in the welcome window, we update the visibility
|
||||
# of the docks widgets
|
||||
if self.stack.currentIndex() == 1:
|
||||
self.updateDockVisibility()
|
||||
|
||||
# Storing the visibility of docks to restore it on restart
|
||||
appSettings.setValue("docks", self._dckVisibility)
|
||||
|
||||
def startTimerNoChanges(self):
|
||||
"""
|
||||
Something changed in the project that requires auto-saving.
|
||||
"""
|
||||
self.projectDirty = True
|
||||
|
||||
if settings.autoSaveNoChanges:
|
||||
self.saveTimerNoChanges.start()
|
||||
|
||||
|
@ -757,11 +813,17 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
|||
self.currentProject = projectName
|
||||
QSettings().setValue("lastProject", projectName)
|
||||
|
||||
r = loadSave.saveProject() # version=0
|
||||
# Stop the timer before saving: if auto-saving fails (bugs out?) we don't want it
|
||||
# to keep trying and continuously hitting the failure condition. Nor do we want to
|
||||
# risk a scenario where the timer somehow triggers a new save while saving.
|
||||
self.saveTimerNoChanges.stop()
|
||||
|
||||
r = loadSave.saveProject() # version=0
|
||||
|
||||
projectName = os.path.basename(self.currentProject)
|
||||
if r:
|
||||
self.projectDirty = False # successful save, clear dirty flag
|
||||
|
||||
feedback = self.tr("Project {} saved.").format(projectName)
|
||||
F.statusMessage(feedback, importance=0)
|
||||
else:
|
||||
|
|
Loading…
Reference in a new issue