Autosaving works, and document and project tracks whether they have been changed or not.
This commit is contained in:
+21
-3
@@ -57,6 +57,10 @@ class Config:
|
|||||||
self.treeColWidth = [120, 30, 50]
|
self.treeColWidth = [120, 30, 50]
|
||||||
self.mainPanePos = [300, 800]
|
self.mainPanePos = [300, 800]
|
||||||
|
|
||||||
|
## Project
|
||||||
|
self.autoSaveProj = 60
|
||||||
|
self.autoSaveDoc = 30
|
||||||
|
|
||||||
## Text Editor
|
## Text Editor
|
||||||
self.textFixedW = True
|
self.textFixedW = True
|
||||||
self.textWidth = 600
|
self.textWidth = 600
|
||||||
@@ -115,6 +119,14 @@ class Config:
|
|||||||
confParser.get(cnfSec,"mainpane"), 2, self.mainPanePos
|
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
|
## Editor
|
||||||
cnfSec = "Editor"
|
cnfSec = "Editor"
|
||||||
if confParser.has_section(cnfSec):
|
if confParser.has_section(cnfSec):
|
||||||
@@ -169,9 +181,15 @@ class Config:
|
|||||||
## Sizes
|
## Sizes
|
||||||
cnfSec = "Sizes"
|
cnfSec = "Sizes"
|
||||||
confParser.add_section(cnfSec)
|
confParser.add_section(cnfSec)
|
||||||
confParser.set(cnfSec,"geometry", self.packList(self.winGeometry))
|
confParser.set(cnfSec,"geometry", self.packList(self.winGeometry))
|
||||||
confParser.set(cnfSec,"treecols", self.packList(self.treeColWidth))
|
confParser.set(cnfSec,"treecols", self.packList(self.treeColWidth))
|
||||||
confParser.set(cnfSec,"mainpane", self.packList(self.mainPanePos))
|
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
|
## Editor
|
||||||
cnfSec = "Editor"
|
cnfSec = "Editor"
|
||||||
|
|||||||
+2
-2
@@ -204,7 +204,7 @@ class GuiDocEditor(QTextEdit):
|
|||||||
self.wcTimer.start()
|
self.wcTimer.start()
|
||||||
if self.mainConf.doReplace and not self.hasSelection:
|
if self.mainConf.doReplace and not self.hasSelection:
|
||||||
self._docAutoReplace(self.theDoc.findBlock(thePos))
|
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
|
return
|
||||||
|
|
||||||
def _docAutoReplace(self, theBlock):
|
def _docAutoReplace(self, theBlock):
|
||||||
@@ -267,7 +267,7 @@ class GuiDocEditor(QTextEdit):
|
|||||||
"""
|
"""
|
||||||
sinceActive = time()-self.lastEdit
|
sinceActive = time()-self.lastEdit
|
||||||
if sinceActive > 5*self.wcInterval:
|
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()
|
self.wcTimer.stop()
|
||||||
elif self.wCounter.isRunning():
|
elif self.wCounter.isRunning():
|
||||||
logger.verbose("Word counter thread is busy")
|
logger.verbose("Word counter thread is busy")
|
||||||
|
|||||||
@@ -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.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")
|
logger.debug("DocHighlighter initialisation complete")
|
||||||
|
|
||||||
|
|||||||
+7
-1
@@ -137,7 +137,7 @@ class GuiDocTree(QTreeWidget):
|
|||||||
trItem.setSelected(True)
|
trItem.setSelected(True)
|
||||||
self.theParent.editItem()
|
self.theParent.editItem()
|
||||||
|
|
||||||
return
|
return True
|
||||||
|
|
||||||
def moveTreeItem(self, nStep):
|
def moveTreeItem(self, nStep):
|
||||||
"""Move an item up or down in the tree, but only if the treeView has focus. This also
|
"""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)
|
pItem.insertChild(nIndex, cItem)
|
||||||
self.clearSelection()
|
self.clearSelection()
|
||||||
cItem.setSelected(True)
|
cItem.setSelected(True)
|
||||||
|
self.theProject.projChanged = True
|
||||||
return
|
return
|
||||||
|
|
||||||
def saveTreeOrder(self):
|
def saveTreeOrder(self):
|
||||||
@@ -208,6 +209,7 @@ class GuiDocTree(QTreeWidget):
|
|||||||
nwItemS.setParent(self.theProject.trashRoot)
|
nwItemS.setParent(self.theProject.trashRoot)
|
||||||
self.clearSelection()
|
self.clearSelection()
|
||||||
trItemP.setSelected(True)
|
trItemP.setSelected(True)
|
||||||
|
self.theProject.projChanged = True
|
||||||
|
|
||||||
elif nwItemS.itemType == nwItemType.FOLDER:
|
elif nwItemS.itemType == nwItemType.FOLDER:
|
||||||
logger.debug("User requested folder %s deleted" % tHandle)
|
logger.debug("User requested folder %s deleted" % tHandle)
|
||||||
@@ -220,6 +222,7 @@ class GuiDocTree(QTreeWidget):
|
|||||||
trItemP.takeChild(tIndex)
|
trItemP.takeChild(tIndex)
|
||||||
self.clearSelection()
|
self.clearSelection()
|
||||||
trItemP.setSelected(True)
|
trItemP.setSelected(True)
|
||||||
|
self.theProject.projChanged = True
|
||||||
else:
|
else:
|
||||||
self.theParent.makeAlert(["Cannot delete folder.","It is not empty."],2)
|
self.theParent.makeAlert(["Cannot delete folder.","It is not empty."],2)
|
||||||
return
|
return
|
||||||
@@ -230,6 +233,7 @@ class GuiDocTree(QTreeWidget):
|
|||||||
if trItemS.childCount() == 0:
|
if trItemS.childCount() == 0:
|
||||||
self.takeTopLevelItem(tIndex)
|
self.takeTopLevelItem(tIndex)
|
||||||
self.theParent.mainMenu.setAvailableRoot()
|
self.theParent.mainMenu.setAvailableRoot()
|
||||||
|
self.theProject.projChanged = True
|
||||||
else:
|
else:
|
||||||
self.theParent.makeAlert(["Cannot delete root folder.","It is not empty."],2)
|
self.theParent.makeAlert(["Cannot delete root folder.","It is not empty."],2)
|
||||||
return
|
return
|
||||||
@@ -387,6 +391,7 @@ class GuiDocTree(QTreeWidget):
|
|||||||
pHandle = trItemP.text(self.C_HANDLE)
|
pHandle = trItemP.text(self.C_HANDLE)
|
||||||
nwItemS.setParent(pHandle)
|
nwItemS.setParent(pHandle)
|
||||||
self.setTreeItemValues(tHandle)
|
self.setTreeItemValues(tHandle)
|
||||||
|
self.theProject.projChanged = True
|
||||||
return
|
return
|
||||||
|
|
||||||
def _moveOrphanedItem(self, tHandle, dHandle):
|
def _moveOrphanedItem(self, tHandle, dHandle):
|
||||||
@@ -401,6 +406,7 @@ class GuiDocTree(QTreeWidget):
|
|||||||
pHandle = trItemP.text(self.C_HANDLE)
|
pHandle = trItemP.text(self.C_HANDLE)
|
||||||
nwItemS.setParent(pHandle)
|
nwItemS.setParent(pHandle)
|
||||||
self.setTreeItemValues(tHandle)
|
self.setTreeItemValues(tHandle)
|
||||||
|
self.theProject.projChanged = True
|
||||||
return
|
return
|
||||||
|
|
||||||
##
|
##
|
||||||
|
|||||||
+46
-1
@@ -16,7 +16,7 @@ import nw
|
|||||||
from os import path
|
from os import path
|
||||||
from PyQt5.QtWidgets import QWidget, QMainWindow, QVBoxLayout, QFrame, QSplitter, QFileDialog, QStackedWidget, QShortcut, QMessageBox
|
from PyQt5.QtWidgets import QWidget, QMainWindow, QVBoxLayout, QFrame, QSplitter, QFileDialog, QStackedWidget, QShortcut, QMessageBox
|
||||||
from PyQt5.QtGui import QIcon
|
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.doctree import GuiDocTree
|
||||||
from nw.gui.doceditor import GuiDocEditor
|
from nw.gui.doceditor import GuiDocEditor
|
||||||
@@ -91,6 +91,16 @@ class GuiMain(QMainWindow):
|
|||||||
theCss = inFile.read()
|
theCss = inFile.read()
|
||||||
self.setStyleSheet(theCss)
|
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()
|
self.show()
|
||||||
|
|
||||||
logger.debug("GUI initialisation complete")
|
logger.debug("GUI initialisation complete")
|
||||||
@@ -162,6 +172,8 @@ class GuiMain(QMainWindow):
|
|||||||
##
|
##
|
||||||
|
|
||||||
def openDocument(self, tHandle):
|
def openDocument(self, tHandle):
|
||||||
|
if self._takeDocumentAction():
|
||||||
|
self.saveDocument()
|
||||||
self.stackPane.setCurrentIndex(self.stackDoc)
|
self.stackPane.setCurrentIndex(self.stackDoc)
|
||||||
self.docEditor.setText(self.theDocument.openDocument(tHandle))
|
self.docEditor.setText(self.theDocument.openDocument(tHandle))
|
||||||
self.docEditor.changeWidth()
|
self.docEditor.changeWidth()
|
||||||
@@ -174,6 +186,7 @@ class GuiMain(QMainWindow):
|
|||||||
self.theDocument.theItem.setWordCount(self.docEditor.wordCount)
|
self.theDocument.theItem.setWordCount(self.docEditor.wordCount)
|
||||||
self.theDocument.theItem.setParaCount(self.docEditor.paraCount)
|
self.theDocument.theItem.setParaCount(self.docEditor.paraCount)
|
||||||
self.theDocument.saveDocument(docHtml)
|
self.theDocument.saveDocument(docHtml)
|
||||||
|
self.docEditor.theDoc.setModified(False)
|
||||||
return
|
return
|
||||||
|
|
||||||
##
|
##
|
||||||
@@ -245,6 +258,10 @@ class GuiMain(QMainWindow):
|
|||||||
|
|
||||||
def closeMain(self):
|
def closeMain(self):
|
||||||
logger.info("Exiting %s" % nw.__package__)
|
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.setWinSize(self.width(), self.height())
|
||||||
self.mainConf.setTreeColWidths(self.treeView.getColumnSizes())
|
self.mainConf.setTreeColWidths(self.treeView.getColumnSizes())
|
||||||
self.mainConf.setMainPanePos(self.splitMain.sizes())
|
self.mainConf.setMainPanePos(self.splitMain.sizes())
|
||||||
@@ -270,6 +287,34 @@ class GuiMain(QMainWindow):
|
|||||||
self.setWindowTitle(winTitle)
|
self.setWindowTitle(winTitle)
|
||||||
return True
|
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
|
# Events
|
||||||
##
|
##
|
||||||
|
|||||||
+15
-7
@@ -33,6 +33,7 @@ class NWProject():
|
|||||||
# Internal
|
# Internal
|
||||||
self.mainConf = nw.CONFIG
|
self.mainConf = nw.CONFIG
|
||||||
self.theParent = theParent
|
self.theParent = theParent
|
||||||
|
self.projChanged = False
|
||||||
|
|
||||||
# Project Settings
|
# Project Settings
|
||||||
self.projTree = None
|
self.projTree = None
|
||||||
@@ -111,6 +112,8 @@ class NWProject():
|
|||||||
|
|
||||||
def clearProject(self):
|
def clearProject(self):
|
||||||
|
|
||||||
|
self.projChanged = False
|
||||||
|
|
||||||
# Project Settings
|
# Project Settings
|
||||||
self.projTree = {}
|
self.projTree = {}
|
||||||
self.treeOrder = []
|
self.treeOrder = []
|
||||||
@@ -197,6 +200,7 @@ class NWProject():
|
|||||||
self.theParent.statusBar.setStatus("Opened Project: %s" % self.projName)
|
self.theParent.statusBar.setStatus("Opened Project: %s" % self.projName)
|
||||||
|
|
||||||
self._scanProjectFolder()
|
self._scanProjectFolder()
|
||||||
|
self.projChanged = False
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
@@ -255,6 +259,7 @@ class NWProject():
|
|||||||
|
|
||||||
self.mainConf.setRecent(self.projPath)
|
self.mainConf.setRecent(self.projPath)
|
||||||
self.theParent.statusBar.setStatus("Saved Project: %s" % self.projName)
|
self.theParent.statusBar.setStatus("Saved Project: %s" % self.projName)
|
||||||
|
self.projChanged = False
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
@@ -264,14 +269,17 @@ class NWProject():
|
|||||||
|
|
||||||
def setProjectPath(self, projPath):
|
def setProjectPath(self, projPath):
|
||||||
self.projPath = projPath
|
self.projPath = projPath
|
||||||
|
self.projChanged = True
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def setProjectName(self, projName):
|
def setProjectName(self, projName):
|
||||||
self.projName = projName.strip()
|
self.projName = projName.strip()
|
||||||
|
self.projChanged = True
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def setBookTitle(self, bookTitle):
|
def setBookTitle(self, bookTitle):
|
||||||
self.bookTitle = bookTitle.strip()
|
self.bookTitle = bookTitle.strip()
|
||||||
|
self.projChanged = True
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def setBookAuthors(self, bookAuthors):
|
def setBookAuthors(self, bookAuthors):
|
||||||
@@ -281,18 +289,16 @@ class NWProject():
|
|||||||
if bookAuthor == "":
|
if bookAuthor == "":
|
||||||
continue
|
continue
|
||||||
self.bookAuthors.append(bookAuthor)
|
self.bookAuthors.append(bookAuthor)
|
||||||
|
self.projChanged = True
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def setTreeOrder(self, newOrder):
|
def setTreeOrder(self, newOrder):
|
||||||
if len(self.treeOrder) != len(newOrder):
|
if len(self.treeOrder) != len(newOrder):
|
||||||
logger.warning("Size of new and old tree order does not match")
|
logger.warning("Size of new and old tree order does not match")
|
||||||
self.treeOrder = newOrder
|
self.treeOrder = newOrder
|
||||||
|
self.projChanged = True
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def setItemName(self, tHandle, theName):
|
|
||||||
self.projTree[tHandle].setName(theName)
|
|
||||||
return
|
|
||||||
|
|
||||||
##
|
##
|
||||||
# Get Functions
|
# Get Functions
|
||||||
##
|
##
|
||||||
@@ -349,16 +355,16 @@ class NWProject():
|
|||||||
continue
|
continue
|
||||||
fHandle = fileItem[5]+fileItem[7:19]
|
fHandle = fileItem[5]+fileItem[7:19]
|
||||||
if fHandle in self.treeOrder:
|
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:
|
else:
|
||||||
logger.verbose("Checking file %s, handle %s: Orphaned" % (fileItem,fHandle))
|
logger.debug("Checking file %s, handle %s: Orphaned" % (fileItem,fHandle))
|
||||||
orphanFiles.append(fHandle)
|
orphanFiles.append(fHandle)
|
||||||
|
|
||||||
# Report status
|
# Report status
|
||||||
if len(orphanFiles) > 0:
|
if len(orphanFiles) > 0:
|
||||||
self.theParent.makeAlert("Found %d orphaned file(s) in project folder!" % len(orphanFiles),1)
|
self.theParent.makeAlert("Found %d orphaned file(s) in project folder!" % len(orphanFiles),1)
|
||||||
else:
|
else:
|
||||||
logger.verbose("File check OK")
|
logger.debug("File check OK")
|
||||||
return
|
return
|
||||||
|
|
||||||
# Handle orphans
|
# Handle orphans
|
||||||
@@ -396,6 +402,8 @@ class NWProject():
|
|||||||
else:
|
else:
|
||||||
logger.error("Only one trash folder allowed")
|
logger.error("Only one trash folder allowed")
|
||||||
|
|
||||||
|
self.projChanged = True
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
def _makeStatusIcons(self):
|
def _makeStatusIcons(self):
|
||||||
|
|||||||
@@ -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.
|
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.
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<?xml version='1.0' encoding='utf-8'?>
|
<?xml version='1.0' encoding='utf-8'?>
|
||||||
<novelWriterXML appVersion="0.0.1" fileVersion="1.0" timeStamp="2019-05-04 22:24:32">
|
<novelWriterXML appVersion="0.0.1" fileVersion="1.0" timeStamp="2019-05-04 23:57:20">
|
||||||
<project>
|
<project>
|
||||||
<name>Sample Project</name>
|
<name>Sample Project</name>
|
||||||
<title>Sample Project</title>
|
<title>Sample Project</title>
|
||||||
@@ -50,8 +50,8 @@
|
|||||||
<status>1</status>
|
<status>1</status>
|
||||||
<expanded>False</expanded>
|
<expanded>False</expanded>
|
||||||
<layout>SCENE</layout>
|
<layout>SCENE</layout>
|
||||||
<charCount>376</charCount>
|
<charCount>392</charCount>
|
||||||
<wordCount>66</wordCount>
|
<wordCount>70</wordCount>
|
||||||
<paraCount>4</paraCount>
|
<paraCount>4</paraCount>
|
||||||
</item>
|
</item>
|
||||||
<item handle="bc0cbd2a407f3" order="2" parent="e7ded148d6e4a">
|
<item handle="bc0cbd2a407f3" order="2" parent="e7ded148d6e4a">
|
||||||
|
|||||||
Reference in New Issue
Block a user