From 9565b14d8b3766f53ab3b5116f28ecd64eb464e0 Mon Sep 17 00:00:00 2001 From: "Veronica K. B. Olsen" Date: Sun, 5 May 2019 00:11:46 +0200 Subject: [PATCH] Autosaving works, and document and project tracks whether they have been changed or not. --- nw/config.py | 24 ++++++++-- nw/gui/doceditor.py | 4 +- nw/gui/dochighlight.py | 4 +- nw/gui/doctree.py | 8 +++- nw/gui/winmain.py | 47 ++++++++++++++++++- nw/project/project.py | 22 ++++++--- .../sampleNovel/data_6/36b6aa9b697b_main.nwd | 2 +- sample/sampleNovel/nwProject.nwx | 6 +-- 8 files changed, 97 insertions(+), 20 deletions(-) diff --git a/nw/config.py b/nw/config.py index cde16913..68b76411 100644 --- a/nw/config.py +++ b/nw/config.py @@ -57,6 +57,10 @@ class Config: self.treeColWidth = [120, 30, 50] self.mainPanePos = [300, 800] + ## Project + self.autoSaveProj = 60 + self.autoSaveDoc = 30 + ## Text Editor self.textFixedW = True self.textWidth = 600 @@ -115,6 +119,14 @@ class Config: confParser.get(cnfSec,"mainpane"), 2, self.mainPanePos ) + ## Project + cnfSec = "Project" + if confParser.has_section(cnfSec): + if confParser.has_option(cnfSec,"autosaveproject"): + self.autoSaveProj = confParser.getint(cnfSec,"autosaveproject") + if confParser.has_option(cnfSec,"autosavedoc"): + self.autoSaveDoc = confParser.getint(cnfSec,"autosavedoc") + ## Editor cnfSec = "Editor" if confParser.has_section(cnfSec): @@ -169,9 +181,15 @@ class Config: ## Sizes cnfSec = "Sizes" confParser.add_section(cnfSec) - confParser.set(cnfSec,"geometry", self.packList(self.winGeometry)) - confParser.set(cnfSec,"treecols", self.packList(self.treeColWidth)) - confParser.set(cnfSec,"mainpane", self.packList(self.mainPanePos)) + confParser.set(cnfSec,"geometry", self.packList(self.winGeometry)) + confParser.set(cnfSec,"treecols", self.packList(self.treeColWidth)) + confParser.set(cnfSec,"mainpane", self.packList(self.mainPanePos)) + + ## Project + cnfSec = "Project" + confParser.add_section(cnfSec) + confParser.set(cnfSec,"autosaveproject", str(self.autoSaveProj)) + confParser.set(cnfSec,"autosavedoc", str(self.autoSaveDoc)) ## Editor cnfSec = "Editor" diff --git a/nw/gui/doceditor.py b/nw/gui/doceditor.py index 0e2c2dd1..175be69b 100644 --- a/nw/gui/doceditor.py +++ b/nw/gui/doceditor.py @@ -204,7 +204,7 @@ class GuiDocEditor(QTextEdit): self.wcTimer.start() if self.mainConf.doReplace and not self.hasSelection: self._docAutoReplace(self.theDoc.findBlock(thePos)) - logger.verbose("Doc change signal took %.3f µs" % ((time()-self.lastEdit)*1e6)) + # logger.verbose("Doc change signal took %.3f µs" % ((time()-self.lastEdit)*1e6)) return def _docAutoReplace(self, theBlock): @@ -267,7 +267,7 @@ class GuiDocEditor(QTextEdit): """ sinceActive = time()-self.lastEdit if sinceActive > 5*self.wcInterval: - logger.verbose("Stopping word count timer due to no activity over the last %.3f seconds" % sinceActive) + logger.debug("Stopping word count timer due to no activity over the last %.3f seconds" % sinceActive) self.wcTimer.stop() elif self.wCounter.isRunning(): logger.verbose("Word counter thread is busy") diff --git a/nw/gui/dochighlight.py b/nw/gui/dochighlight.py index eb9ddd43..b1c35831 100644 --- a/nw/gui/dochighlight.py +++ b/nw/gui/dochighlight.py @@ -143,9 +143,9 @@ class GuiDocHighlighter(QSyntaxHighlighter): } )) - # Build a QRegExp for each pattern + # Build a QRegExp for each pattern and for the spell checker self.rules = [(QRegularExpression(a),b) for (a,b) in self.hRules] - self.spellRx = QRegularExpression(r"[\w\'{:s}]+".format(self.mainConf.fmtSingleQuotes[1])) + self.spellRx = QRegularExpression(r"[\w\'{:s}]+".format(self.mainConf.fmtApostrophe)) logger.debug("DocHighlighter initialisation complete") diff --git a/nw/gui/doctree.py b/nw/gui/doctree.py index deec592b..1c60349c 100644 --- a/nw/gui/doctree.py +++ b/nw/gui/doctree.py @@ -137,7 +137,7 @@ class GuiDocTree(QTreeWidget): trItem.setSelected(True) self.theParent.editItem() - return + return True def moveTreeItem(self, nStep): """Move an item up or down in the tree, but only if the treeView has focus. This also @@ -163,6 +163,7 @@ class GuiDocTree(QTreeWidget): pItem.insertChild(nIndex, cItem) self.clearSelection() cItem.setSelected(True) + self.theProject.projChanged = True return def saveTreeOrder(self): @@ -208,6 +209,7 @@ class GuiDocTree(QTreeWidget): nwItemS.setParent(self.theProject.trashRoot) self.clearSelection() trItemP.setSelected(True) + self.theProject.projChanged = True elif nwItemS.itemType == nwItemType.FOLDER: logger.debug("User requested folder %s deleted" % tHandle) @@ -220,6 +222,7 @@ class GuiDocTree(QTreeWidget): trItemP.takeChild(tIndex) self.clearSelection() trItemP.setSelected(True) + self.theProject.projChanged = True else: self.theParent.makeAlert(["Cannot delete folder.","It is not empty."],2) return @@ -230,6 +233,7 @@ class GuiDocTree(QTreeWidget): if trItemS.childCount() == 0: self.takeTopLevelItem(tIndex) self.theParent.mainMenu.setAvailableRoot() + self.theProject.projChanged = True else: self.theParent.makeAlert(["Cannot delete root folder.","It is not empty."],2) return @@ -387,6 +391,7 @@ class GuiDocTree(QTreeWidget): pHandle = trItemP.text(self.C_HANDLE) nwItemS.setParent(pHandle) self.setTreeItemValues(tHandle) + self.theProject.projChanged = True return def _moveOrphanedItem(self, tHandle, dHandle): @@ -401,6 +406,7 @@ class GuiDocTree(QTreeWidget): pHandle = trItemP.text(self.C_HANDLE) nwItemS.setParent(pHandle) self.setTreeItemValues(tHandle) + self.theProject.projChanged = True return ## diff --git a/nw/gui/winmain.py b/nw/gui/winmain.py index 712f08c8..4b67339e 100644 --- a/nw/gui/winmain.py +++ b/nw/gui/winmain.py @@ -16,7 +16,7 @@ import nw from os import path from PyQt5.QtWidgets import QWidget, QMainWindow, QVBoxLayout, QFrame, QSplitter, QFileDialog, QStackedWidget, QShortcut, QMessageBox from PyQt5.QtGui import QIcon -from PyQt5.QtCore import Qt +from PyQt5.QtCore import Qt, QTimer from nw.gui.doctree import GuiDocTree from nw.gui.doceditor import GuiDocEditor @@ -91,6 +91,16 @@ class GuiMain(QMainWindow): theCss = inFile.read() self.setStyleSheet(theCss) + self.asProjTimer = QTimer() + self.asProjTimer.setInterval(int(self.mainConf.autoSaveProj*1000)) + self.asProjTimer.timeout.connect(self._autoSaveProject) + self.asProjTimer.start() + + self.asDocTimer = QTimer() + self.asDocTimer.setInterval(int(self.mainConf.autoSaveDoc*1000)) + self.asDocTimer.timeout.connect(self._autoSaveDocument) + self.asDocTimer.start() + self.show() logger.debug("GUI initialisation complete") @@ -162,6 +172,8 @@ class GuiMain(QMainWindow): ## def openDocument(self, tHandle): + if self._takeDocumentAction(): + self.saveDocument() self.stackPane.setCurrentIndex(self.stackDoc) self.docEditor.setText(self.theDocument.openDocument(tHandle)) self.docEditor.changeWidth() @@ -174,6 +186,7 @@ class GuiMain(QMainWindow): self.theDocument.theItem.setWordCount(self.docEditor.wordCount) self.theDocument.theItem.setParaCount(self.docEditor.paraCount) self.theDocument.saveDocument(docHtml) + self.docEditor.theDoc.setModified(False) return ## @@ -245,6 +258,10 @@ class GuiMain(QMainWindow): def closeMain(self): logger.info("Exiting %s" % nw.__package__) + if self._takeDocumentAction(): + self.saveDocument() + if self._takeProjectAction(): + self.saveProject() self.mainConf.setWinSize(self.width(), self.height()) self.mainConf.setTreeColWidths(self.treeView.getColumnSizes()) self.mainConf.setMainPanePos(self.splitMain.sizes()) @@ -270,6 +287,34 @@ class GuiMain(QMainWindow): self.setWindowTitle(winTitle) return True + def _autoSaveProject(self): + if self._takeProjectAction(): + logger.debug("Autosaving project") + self.saveProject() + return + + def _autoSaveDocument(self): + if self._takeDocumentAction(): + logger.debug("Autosaving document") + self.saveDocument() + return + + def _takeProjectAction(self): + if self.theProject.projPath is None: + return False + if not self.theProject.projChanged: + return False + return True + + def _takeDocumentAction(self): + if self.stackPane.currentIndex() != self.stackDoc: + return False + if self.theDocument.theItem is None: + return False + if not self.docEditor.theDoc.isModified(): + return False + return True + ## # Events ## diff --git a/nw/project/project.py b/nw/project/project.py index 83b3cc08..79b22a05 100644 --- a/nw/project/project.py +++ b/nw/project/project.py @@ -33,6 +33,7 @@ class NWProject(): # Internal self.mainConf = nw.CONFIG self.theParent = theParent + self.projChanged = False # Project Settings self.projTree = None @@ -111,6 +112,8 @@ class NWProject(): def clearProject(self): + self.projChanged = False + # Project Settings self.projTree = {} self.treeOrder = [] @@ -197,6 +200,7 @@ class NWProject(): self.theParent.statusBar.setStatus("Opened Project: %s" % self.projName) self._scanProjectFolder() + self.projChanged = False return True @@ -255,6 +259,7 @@ class NWProject(): self.mainConf.setRecent(self.projPath) self.theParent.statusBar.setStatus("Saved Project: %s" % self.projName) + self.projChanged = False return True @@ -264,14 +269,17 @@ class NWProject(): def setProjectPath(self, projPath): self.projPath = projPath + self.projChanged = True return True def setProjectName(self, projName): self.projName = projName.strip() + self.projChanged = True return True def setBookTitle(self, bookTitle): self.bookTitle = bookTitle.strip() + self.projChanged = True return True def setBookAuthors(self, bookAuthors): @@ -281,18 +289,16 @@ class NWProject(): if bookAuthor == "": continue self.bookAuthors.append(bookAuthor) + self.projChanged = True return True def setTreeOrder(self, newOrder): if len(self.treeOrder) != len(newOrder): logger.warning("Size of new and old tree order does not match") self.treeOrder = newOrder + self.projChanged = True return True - def setItemName(self, tHandle, theName): - self.projTree[tHandle].setName(theName) - return - ## # Get Functions ## @@ -349,16 +355,16 @@ class NWProject(): continue fHandle = fileItem[5]+fileItem[7:19] if fHandle in self.treeOrder: - logger.verbose("Checking file %s, handle %s: OK" % (fileItem,fHandle)) + logger.debug("Checking file %s, handle %s: OK" % (fileItem,fHandle)) else: - logger.verbose("Checking file %s, handle %s: Orphaned" % (fileItem,fHandle)) + logger.debug("Checking file %s, handle %s: Orphaned" % (fileItem,fHandle)) orphanFiles.append(fHandle) # Report status if len(orphanFiles) > 0: self.theParent.makeAlert("Found %d orphaned file(s) in project folder!" % len(orphanFiles),1) else: - logger.verbose("File check OK") + logger.debug("File check OK") return # Handle orphans @@ -396,6 +402,8 @@ class NWProject(): else: logger.error("Only one trash folder allowed") + self.projChanged = True + return def _makeStatusIcons(self): diff --git a/sample/sampleNovel/data_6/36b6aa9b697b_main.nwd b/sample/sampleNovel/data_6/36b6aa9b697b_main.nwd index 30579a92..0f9c32d2 100644 --- a/sample/sampleNovel/data_6/36b6aa9b697b_main.nwd +++ b/sample/sampleNovel/data_6/36b6aa9b697b_main.nwd @@ -13,6 +13,6 @@ Some text here would look good as well, and maybe some "dialogue"? So, this is some text that we’ve been adding to this document. It is utterly meaningless text, but since this is just dummy text, that doesn’t really matter. The text is perfectly happy to live in this document regardless. -This paragraph is also meaningless. +This paragraph is also meaningless. At least a bit. diff --git a/sample/sampleNovel/nwProject.nwx b/sample/sampleNovel/nwProject.nwx index e0cc03e6..f82147c2 100644 --- a/sample/sampleNovel/nwProject.nwx +++ b/sample/sampleNovel/nwProject.nwx @@ -1,5 +1,5 @@ - + Sample Project Sample Project @@ -50,8 +50,8 @@ 1 False SCENE - 376 - 66 + 392 + 70 4