Merge pull request #201 from vkbo/minor_things

Minor Refactoring and Restore Orphan File Label
This commit is contained in:
Veronica K. Berglyd Olsen
2020-05-09 14:07:10 +02:00
committed by GitHub
7 changed files with 236 additions and 98 deletions
+27
View File
@@ -28,6 +28,10 @@
import logging import logging
import nw import nw
from datetime import datetime
from nw.constants import nwConst
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
def checkString(checkValue, defaultValue, allowNone=False): def checkString(checkValue, defaultValue, allowNone=False):
@@ -73,6 +77,20 @@ def checkBool(checkValue, defaultValue, allowNone=False):
return defaultValue return defaultValue
return defaultValue return defaultValue
def isHandle(theString):
"""Check if a string is a valid novelWriter handle.
Note: This is case sensitive. Must be lower case!
"""
if not isinstance(theString, str):
return False
if len(theString) != 13:
return False
invalidChar = False
for c in theString:
if c not in "0123456789abcdef":
invalidChar = True
return not invalidChar
def colRange(rgbStart, rgbEnd, nStep): def colRange(rgbStart, rgbEnd, nStep):
if len(rgbStart) != 3 and len(rgbEnd) != 3 and nStep < 1: if len(rgbStart) != 3 and len(rgbEnd) != 3 and nStep < 1:
@@ -122,6 +140,15 @@ def formatInt(theInt):
return "%d" % theInt return "%d" % theInt
def formatTimeStamp(theTime, fileSafe=False):
"""Take a number (on the format returned by time.time()) and convert
it to a timestamp string.
"""
if fileSafe:
return datetime.fromtimestamp(theTime).strftime(nwConst.fStampFmt)
else:
return datetime.fromtimestamp(theTime).strftime(nwConst.tStampFmt)
def splitVersionNumber(vString): def splitVersionNumber(vString):
""" Splits a version string on the form aa.bb.cc into major, minor """ Splits a version string on the form aa.bb.cc into major, minor
and patch, and computes an integer value aabbcc. and patch, and computes an integer value aabbcc.
+3 -3
View File
@@ -32,13 +32,13 @@ import sys
import nw import nw
from os import path, mkdir, unlink, rename from os import path, mkdir, unlink, rename
from datetime import datetime from time import time
from PyQt5.Qt import PYQT_VERSION_STR from PyQt5.Qt import PYQT_VERSION_STR
from PyQt5.QtCore import QT_VERSION_STR, QStandardPaths, QSysInfo from PyQt5.QtCore import QT_VERSION_STR, QStandardPaths, QSysInfo
from nw.constants import nwFiles, nwUnicode from nw.constants import nwFiles, nwUnicode
from nw.common import splitVersionNumber from nw.common import splitVersionNumber, formatTimeStamp
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -445,7 +445,7 @@ class Config:
## Main ## Main
cnfSec = "Main" cnfSec = "Main"
cnfParse.add_section(cnfSec) cnfParse.add_section(cnfSec)
cnfParse.set(cnfSec,"timestamp", datetime.now().strftime("%Y-%m-%d %H:%M:%S")) cnfParse.set(cnfSec,"timestamp", formatTimeStamp(time()))
cnfParse.set(cnfSec,"theme", str(self.guiTheme)) cnfParse.set(cnfSec,"theme", str(self.guiTheme))
cnfParse.set(cnfSec,"syntax", str(self.guiSyntax)) cnfParse.set(cnfSec,"syntax", str(self.guiSyntax))
cnfParse.set(cnfSec,"guidark", str(self.guiDark)) cnfParse.set(cnfSec,"guidark", str(self.guiDark))
+2 -1
View File
@@ -29,7 +29,8 @@ from nw.constants.enum import nwItemClass, nwItemLayout, nwOutline
class nwConst(): class nwConst():
tStampFmt = "%Y-%m-%d %H:%M:%S" tStampFmt = "%Y-%m-%d %H:%M:%S" # Default format
fStampFmt = "%Y-%m-%d %H.%M.%S" # FileName safe format
# END Class nwConst # END Class nwConst
+52 -11
View File
@@ -31,6 +31,7 @@ import nw
from os import path, mkdir, rename, unlink from os import path, mkdir, rename, unlink
from nw.constants import nwAlert from nw.constants import nwAlert
from nw.common import isHandle
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -68,25 +69,29 @@ class NWDoc():
self.docMeta = "" self.docMeta = ""
return return
def openDocument(self, tHandle, showStatus=True): def openDocument(self, tHandle, showStatus=True, isOrphan=False):
"""Open a document from handle, capturing potential file system """Open a document from handle, capturing potential file system
errors and parse meta data. errors and parse meta data.
""" """
self.docHandle = tHandle self.docHandle = tHandle
self.theItem = self.theProject.projTree[tHandle] if not isOrphan:
self.theItem = self.theProject.projTree[tHandle]
else:
self.theItem = None
if self.theItem is None: if self.theItem is None and not isOrphan:
self.clearDocument() self.clearDocument()
return None return None
# By default, the document is editable. # By default, the document is editable.
# Except for files in the trash folder. # Except for files in the trash folder.
self.docEditable = True self.docEditable = True
if self.theItem.parHandle == self.theProject.projTree.trashRoot(): if self.theItem is not None:
self.docEditable = False if self.theItem.parHandle == self.theProject.projTree.trashRoot():
self.docEditable = False
docDir, docFile = self.assemblePath(self.docHandle, self.FILE_MN) docDir, docFile = self._assemblePath(self.docHandle, self.FILE_MN)
self.fileLoc = path.join(docDir,docFile) self.fileLoc = path.join(docDir,docFile)
logger.debug("Opening document %s" % self.fileLoc) logger.debug("Opening document %s" % self.fileLoc)
dataDir = path.join(self.theProject.projPath, docDir) dataDir = path.join(self.theProject.projPath, docDir)
@@ -100,7 +105,7 @@ class NWDoc():
fstLine = inFile.readline() fstLine = inFile.readline()
if fstLine.startswith("%%~ "): if fstLine.startswith("%%~ "):
# This is the meta line # This is the meta line
self.docMeta = fstLine.strip() self.docMeta = fstLine[4:].strip()
else: else:
theText = fstLine theText = fstLine
theText += inFile.read() theText += inFile.read()
@@ -120,7 +125,7 @@ class NWDoc():
logger.verbose("DocMeta: '%s'" % self.docMeta) logger.verbose("DocMeta: '%s'" % self.docMeta)
if showStatus: if showStatus and not isOrphan:
self.theParent.statusBar.setStatus("Opened Document: %s" % self.theItem.itemName) self.theParent.statusBar.setStatus("Opened Document: %s" % self.theItem.itemName)
return theText return theText
@@ -133,7 +138,7 @@ class NWDoc():
if self.docHandle is None or not self.docEditable: if self.docHandle is None or not self.docEditable:
return False return False
docDir, docFile = self.assemblePath(self.docHandle, self.FILE_MN) docDir, docFile = self._assemblePath(self.docHandle, self.FILE_MN)
logger.debug("Saving document %s" % path.join(docDir,docFile)) logger.debug("Saving document %s" % path.join(docDir,docFile))
dataPath = path.join(self.theProject.projPath, docDir) dataPath = path.join(self.theProject.projPath, docDir)
docPath = path.join(dataPath, docFile) docPath = path.join(dataPath, docFile)
@@ -171,7 +176,7 @@ class NWDoc():
"""Permanently delete a document source file and its backups """Permanently delete a document source file and its backups
from the project data folder. from the project data folder.
""" """
docDir, docFile = self.assemblePath(tHandle, self.FILE_MN) docDir, docFile = self._assemblePath(tHandle, self.FILE_MN)
dataPath = path.join(self.theProject.projPath, docDir) dataPath = path.join(self.theProject.projPath, docDir)
chkList = [] chkList = []
chkList.append(path.join(dataPath, docFile)) chkList.append(path.join(dataPath, docFile))
@@ -187,8 +192,44 @@ class NWDoc():
return False return False
return True return True
##
# Getters
##
def getMeta(self):
"""Parses the document meta tag and returns the path and name as
a list and a string.
"""
if len(self.docMeta) < 14:
# Not enough information
return "", []
theMeta = self.docMeta
# Scan for handles
thePath = []
for n in range(200):
if len(theMeta) < 14:
break
if theMeta[13] == ":":
theHandle = theMeta[:13]
if isHandle(theHandle):
thePath.append(theHandle)
theMeta = theMeta[14:]
else:
break
else:
break
return theMeta, thePath
##
# Internal Functions
##
@staticmethod @staticmethod
def assemblePath(tHandle, docExt): def _assemblePath(tHandle, docExt):
if tHandle is None: if tHandle is None:
return None, None return None, None
docDir = "data_"+tHandle[0] docDir = "data_"+tHandle[0]
+149 -80
View File
@@ -3,12 +3,12 @@
novelWriter Project Wrapper novelWriter Project Wrapper
=============================== ===============================
Class holding a project Class wrapping the data if a novelWriter project
File History: File History:
Created: 2018-09-29 [0.0.1] NWProject Created: 2018-09-29 [0.0.1] NWProject
Added: 2018-10-27 [0.0.1] NWItem Created: 2018-10-27 [0.0.1] NWItem
Added: 2019-05-19 [0.1.3] NWStatus Created: 2019-05-19 [0.1.3] NWStatus
Merged: 2020-05-07 [0.4.5] Moved NWItem class to this file Merged: 2020-05-07 [0.4.5] Moved NWItem class to this file
Merged: 2020-05-07 [0.4.5] Moved NWStatus class to this file Merged: 2020-05-07 [0.4.5] Moved NWStatus class to this file
Added: 2020-05-07 [0.4.5] NWTree Added: 2020-05-07 [0.4.5] NWTree
@@ -36,13 +36,13 @@ import nw
from os import path, mkdir, listdir, unlink, rename from os import path, mkdir, listdir, unlink, rename
from lxml import etree from lxml import etree
from hashlib import sha256 from hashlib import sha256
from datetime import datetime
from time import time from time import time
from shutil import make_archive from shutil import make_archive
from nw.gui.tools import OptionState from nw.gui.tools import OptionState
from nw.core.tools import projectMaintenance from nw.core.tools import projectMaintenance
from nw.common import checkString, checkBool, checkInt from nw.core.document import NWDoc
from nw.common import checkString, checkBool, checkInt, formatTimeStamp
from nw.constants import ( from nw.constants import (
nwFiles, nwConst, nwItemType, nwItemClass, nwItemLayout, nwAlert nwFiles, nwConst, nwItemType, nwItemClass, nwItemLayout, nwAlert
) )
@@ -62,12 +62,12 @@ class NWProject():
self.projTree = NWTree(self) # The project tree self.projTree = NWTree(self) # The project tree
# Project Status # Project Status
self.projOpened = None # The time stamp of when the project file was opened self.projOpened = 0 # The time stamp of when the project file was opened
self.projChanged = None # The project has unsaved changes self.projChanged = False # The project has unsaved changes
self.projAltered = None # The project has been altered this session self.projAltered = False # The project has been altered this session
self.lockedBy = None # Data on which computer has the project open self.lockedBy = None # Data on which computer has the project open
self.saveCount = None # Meta data: number of saves self.saveCount = None # Meta data: number of saves
self.autoCount = None # Meta data: number of automatic saves self.autoCount = None # Meta data: number of automatic saves
# Class Settings # Class Settings
self.projPath = None # The full path to where the currently open project is saved self.projPath = None # The full path to where the currently open project is saved
@@ -76,23 +76,23 @@ class NWProject():
self.projFile = None # The file name of the project main XML file self.projFile = None # The file name of the project main XML file
# Project Meta # Project Meta
self.projName = None self.projName = "" # Project name (working title)
self.bookTitle = None self.bookTitle = "" # The final title; should only be used for exports
self.bookAuthors = None self.bookAuthors = [] # A list of book authors
# Various # Various
self.autoReplace = None self.autoReplace = {} # Text to auto-replace on exports
# Project Settings # Project Settings
self.spellCheck = False self.spellCheck = False # Controls the spellcheck-as-you-type feature
self.autoOutline = True self.autoOutline = True # If true, the Project Outline is updated automatically
self.statusItems = None self.statusItems = None # Novel file progress status values
self.importItems = None self.importItems = None # Note file importance values
self.lastEdited = None self.lastEdited = None # The handle of the last file to be edited
self.lastViewed = None self.lastViewed = None # The handle of the last file to be viewed
self.lastWCount = 0 self.lastWCount = 0 # The project word count from last session
self.currWCount = 0 self.currWCount = 0 # The project word count in current session
self.doBackup = True self.doBackup = True # Run project backup on exit
# Set Defaults # Set Defaults
self.clearProject() self.clearProject()
@@ -182,8 +182,8 @@ class NWProject():
""" """
# Project Status # Project Status
self.projOpened = None self.projOpened = 0
self.projChanged = None self.projChanged = False
self.projAltered = False self.projAltered = False
self.saveCount = 0 self.saveCount = 0
self.autoCount = 0 self.autoCount = 0
@@ -240,7 +240,7 @@ class NWProject():
self.projDict = path.join(self.projMeta, nwFiles.PROJ_DICT) self.projDict = path.join(self.projMeta, nwFiles.PROJ_DICT)
if not self._checkFolder(self.projMeta): if not self._checkFolder(self.projMeta):
return return False
if overrideLock: if overrideLock:
self._clearLockFile() self._clearLockFile()
@@ -290,7 +290,7 @@ class NWProject():
self.autoCount = 0 self.autoCount = 0
if "appVersion" in xRoot.attrib: if "appVersion" in xRoot.attrib:
appVersion = xRoot.attrib["appVersion"] appVersion = xRoot.attrib["appVersion"]
if "fileVersion" in xRoot.attrib: if "fileVersion" in xRoot.attrib:
fileVersion = xRoot.attrib["fileVersion"] fileVersion = xRoot.attrib["fileVersion"]
if "saveCount" in xRoot.attrib: if "saveCount" in xRoot.attrib:
@@ -324,35 +324,32 @@ class NWProject():
logger.verbose("Author: '%s'" % xItem.text) logger.verbose("Author: '%s'" % xItem.text)
self.bookAuthors.append(xItem.text) self.bookAuthors.append(xItem.text)
elif xItem.tag == "backup": elif xItem.tag == "backup":
self.doBackup = checkBool(xItem.text,False) self.doBackup = checkBool(xItem.text, False)
elif xChild.tag == "settings": elif xChild.tag == "settings":
logger.debug("Found project settings") logger.debug("Found project settings")
for xItem in xChild: for xItem in xChild:
if xItem.text is None: if xItem.text is None:
continue continue
if xItem.tag == "spellCheck": if xItem.tag == "spellCheck":
self.spellCheck = checkBool(xItem.text,False) self.spellCheck = checkBool(xItem.text, False)
elif xItem.tag == "autoOutline": elif xItem.tag == "autoOutline":
self.autoOutline = checkBool(xItem.text,True) self.autoOutline = checkBool(xItem.text, True)
elif xItem.tag == "lastEdited": elif xItem.tag == "lastEdited":
self.lastEdited = checkString(xItem.text,None,True) self.lastEdited = checkString(xItem.text, None, True)
elif xItem.tag == "lastViewed": elif xItem.tag == "lastViewed":
self.lastViewed = checkString(xItem.text,None,True) self.lastViewed = checkString(xItem.text, None, True)
elif xItem.tag == "lastWordCount": elif xItem.tag == "lastWordCount":
self.lastWCount = checkInt(xItem.text,0,False) self.lastWCount = checkInt(xItem.text, 0, False)
elif xItem.tag == "status": elif xItem.tag == "status":
self.statusItems.unpackEntries(xItem) self.statusItems.unpackEntries(xItem)
elif xItem.tag == "importance": elif xItem.tag == "importance":
self.importItems.unpackEntries(xItem) self.importItems.unpackEntries(xItem)
elif xItem.tag == "autoReplace": elif xItem.tag == "autoReplace":
for xEntry in xItem: for xEntry in xItem:
self.autoReplace[xEntry.tag] = checkString(xEntry.text,None,False) self.autoReplace[xEntry.tag] = checkString(xEntry.text, None, False)
elif xChild.tag == "content": elif xChild.tag == "content":
logger.debug("Found project content") logger.debug("Found project content")
for xItem in xChild: self.projTree.unpackXML(xChild)
nwItem = NWItem(self)
if nwItem.unpackXML(xItem):
self.projTree.append(nwItem.itemHandle, nwItem.parHandle, nwItem)
self.optState.loadSettings() self.optState.loadSettings()
@@ -384,8 +381,10 @@ class NWProject():
self.projMeta = path.join(self.projPath,"meta") self.projMeta = path.join(self.projPath,"meta")
saveTime = time() saveTime = time()
if not self._checkFolder(self.projPath): return if not self._checkFolder(self.projPath):
if not self._checkFolder(self.projMeta): return return False
if not self._checkFolder(self.projMeta):
return False
logger.debug("Saving project: %s" % self.projPath) logger.debug("Saving project: %s" % self.projPath)
@@ -401,27 +400,27 @@ class NWProject():
"fileVersion" : "1.0", "fileVersion" : "1.0",
"saveCount" : str(self.saveCount), "saveCount" : str(self.saveCount),
"autoCount" : str(self.autoCount), "autoCount" : str(self.autoCount),
"timeStamp" : datetime.fromtimestamp(saveTime).strftime("%Y-%m-%d %H:%M:%S"), "timeStamp" : formatTimeStamp(saveTime),
}) })
# Save Project Meta # Save Project Meta
xProject = etree.SubElement(nwXML, "project") xProject = etree.SubElement(nwXML, "project")
self._saveProjectValue(xProject, "name", self.projName, True) self._packProjectValue(xProject, "name", self.projName, True)
self._saveProjectValue(xProject, "title", self.bookTitle, True) self._packProjectValue(xProject, "title", self.bookTitle, True)
self._saveProjectValue(xProject, "author", self.bookAuthors) self._packProjectValue(xProject, "author", self.bookAuthors)
self._saveProjectValue(xProject, "backup", self.doBackup) self._packProjectValue(xProject, "backup", self.doBackup)
# Save Project Settings # Save Project Settings
xSettings = etree.SubElement(nwXML, "settings") xSettings = etree.SubElement(nwXML, "settings")
self._saveProjectValue(xSettings, "spellCheck", self.spellCheck) self._packProjectValue(xSettings, "spellCheck", self.spellCheck)
self._saveProjectValue(xSettings, "autoOutline", self.autoOutline) self._packProjectValue(xSettings, "autoOutline", self.autoOutline)
self._saveProjectValue(xSettings, "lastEdited", self.lastEdited) self._packProjectValue(xSettings, "lastEdited", self.lastEdited)
self._saveProjectValue(xSettings, "lastViewed", self.lastViewed) self._packProjectValue(xSettings, "lastViewed", self.lastViewed)
self._saveProjectValue(xSettings, "lastWordCount", self.currWCount) self._packProjectValue(xSettings, "lastWordCount", self.currWCount)
xAutoRep = etree.SubElement(xSettings, "autoReplace") xAutoRep = etree.SubElement(xSettings, "autoReplace")
for aKey, aValue in self.autoReplace.items(): for aKey, aValue in self.autoReplace.items():
if len(aKey) > 0: if len(aKey) > 0:
self._saveProjectValue(xAutoRep,aKey,aValue) self._packProjectValue(xAutoRep,aKey,aValue)
xStatus = etree.SubElement(xSettings,"status") xStatus = etree.SubElement(xSettings,"status")
self.statusItems.packEntries(xStatus) self.statusItems.packEntries(xStatus)
@@ -430,9 +429,7 @@ class NWProject():
# Save Tree Content # Save Tree Content
logger.debug("Writing project content") logger.debug("Writing project content")
xContent = etree.SubElement(nwXML, "content", attrib={"count":str(len(self.projTree))}) self.projTree.packXML(nwXML)
for tItem in self.projTree:
tItem.packXML(xContent)
# Write the xml tree to file # Write the xml tree to file
tempFile = path.join(self.projPath, self.projFile+"~") tempFile = path.join(self.projPath, self.projFile+"~")
@@ -529,7 +526,7 @@ class NWProject():
) )
return False return False
archName = "Backup on %s" % datetime.now().strftime("%Y-%m-%d at %H.%M.%S") archName = "Backup from %s" % formatTimeStamp(time(), fileSafe=True)
baseName = path.join(baseDir, archName) baseName = path.join(baseDir, archName)
try: try:
@@ -559,6 +556,9 @@ class NWProject():
## ##
def setProjectPath(self, projPath): def setProjectPath(self, projPath):
"""Set the project storage path, and also expand ~ to the user
directory using the path library.
"""
if projPath is None or projPath == "": if projPath is None or projPath == "":
self.projPath = None self.projPath = None
else: else:
@@ -569,16 +569,23 @@ class NWProject():
return True return True
def setProjectName(self, projName): def setProjectName(self, projName):
"""Set the project name (working title), This is the the title
used for backup files etc.
"""
self.projName = projName.strip() self.projName = projName.strip()
self.setProjectChanged(True) self.setProjectChanged(True)
return True return True
def setBookTitle(self, bookTitle): def setBookTitle(self, bookTitle):
"""Set the boom title, that is, the title to include in exports.
"""
self.bookTitle = bookTitle.strip() self.bookTitle = bookTitle.strip()
self.setProjectChanged(True) self.setProjectChanged(True)
return True return True
def setBookAuthors(self, bookAuthors): def setBookAuthors(self, bookAuthors):
"""A line separated list of book authors, parsed into an array.
"""
self.bookAuthors = [] self.bookAuthors = []
for bookAuthor in bookAuthors.split("\n"): for bookAuthor in bookAuthors.split("\n"):
bookAuthor = bookAuthor.strip() bookAuthor = bookAuthor.strip()
@@ -589,36 +596,44 @@ class NWProject():
return True return True
def setProjBackup(self, doBackup): def setProjBackup(self, doBackup):
self.doBackup = False """Set whether projects should be backed up or not. The user
will notified in case dependant settings are missing.
"""
self.doBackup = doBackup
if doBackup: if doBackup:
if not path.isdir(self.mainConf.backupPath): if not path.isdir(self.mainConf.backupPath):
self.theParent.makeAlert(( self.theParent.makeAlert((
"You must set a valid backup path in preferences to use " "You must set a valid backup path in preferences to use "
"the automatic project backup feature." "the automatic project backup feature."
), nwAlert.ERROR) ), nwAlert.WARN)
return False
if self.projName == "": if self.projName == "":
self.theParent.makeAlert(( self.theParent.makeAlert((
"You must set a valid project name in project settings to " "You must set a valid project name in project settings to "
"use the automatic project backup feature." "use the automatic project backup feature."
), nwAlert.ERROR) ), nwAlert.WARN)
return False
self.doBackup = True
return True return True
def setSpellCheck(self, theMode): def setSpellCheck(self, theMode):
"""Enable/disable spell checking.
"""
if self.spellCheck != theMode: if self.spellCheck != theMode:
self.spellCheck = theMode self.spellCheck = theMode
self.setProjectChanged(True) self.setProjectChanged(True)
return True return True
def setAutoOutline(self, theMode): def setAutoOutline(self, theMode):
"""Enable/disable automatic update of project outline.
"""
if self.autoOutline != theMode: if self.autoOutline != theMode:
self.autoOutline = theMode self.autoOutline = theMode
self.setProjectChanged(True) self.setProjectChanged(True)
return True return True
def setTreeOrder(self, newOrder): def setTreeOrder(self, newOrder):
"""A list representing the liner/flattened order of project
items in the GUI project tree. The user can rearrange the order
by drag-and-drop. Forwarded to the NWTree class.
"""
if len(self.projTree) != len(newOrder): if len(self.projTree) != 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.projTree.setOrder(newOrder) self.projTree.setOrder(newOrder)
@@ -626,48 +641,64 @@ class NWProject():
return True return True
def setLastEdited(self, tHandle): def setLastEdited(self, tHandle):
"""Set last edited project item.
"""
if self.lastEdited != tHandle: if self.lastEdited != tHandle:
self.lastEdited = tHandle self.lastEdited = tHandle
self.setProjectChanged(True) self.setProjectChanged(True)
return True return True
def setLastViewed(self, tHandle): def setLastViewed(self, tHandle):
"""Set last viewed project item.
"""
if self.lastViewed != tHandle: if self.lastViewed != tHandle:
self.lastViewed = tHandle self.lastViewed = tHandle
self.setProjectChanged(True) self.setProjectChanged(True)
return True return True
def setProjectWordCount(self, theCount): def setProjectWordCount(self, theCount):
"""Set the current project word count.
"""
if self.currWCount != theCount: if self.currWCount != theCount:
self.currWCount = theCount self.currWCount = theCount
self.setProjectChanged(True) self.setProjectChanged(True)
return True return True
def setStatusColours(self, newCols): def setStatusColours(self, newCols):
"""Update the list of novel file status flags. Also iterate
through the project and replace keys that have been renamed.
"""
replaceMap = self.statusItems.setNewEntries(newCols) replaceMap = self.statusItems.setNewEntries(newCols)
if self.projTree: for nwItem in self.projTree:
for nwItem in self.projTree: if nwItem.itemClass == nwItemClass.NOVEL:
if nwItem.itemClass == nwItemClass.NOVEL: if nwItem.itemStatus in replaceMap.keys():
if nwItem.itemStatus in replaceMap.keys(): nwItem.setStatus(replaceMap[nwItem.itemStatus])
nwItem.setStatus(replaceMap[nwItem.itemStatus])
self.setProjectChanged(True) self.setProjectChanged(True)
return return
def setImportColours(self, newCols): def setImportColours(self, newCols):
"""Update the list of note file importance flags. Also iterate
through the project and replace keys that have been renamed.
"""
replaceMap = self.importItems.setNewEntries(newCols) replaceMap = self.importItems.setNewEntries(newCols)
if self.projTree: for nwItem in self.projTree:
for nwItem in self.projTree: if nwItem.itemClass != nwItemClass.NOVEL:
if nwItem.itemClass != nwItemClass.NOVEL: if nwItem.itemStatus in replaceMap.keys():
if nwItem.itemStatus in replaceMap.keys(): nwItem.setStatus(replaceMap[nwItem.itemStatus])
nwItem.setStatus(replaceMap[nwItem.itemStatus])
self.setProjectChanged(True) self.setProjectChanged(True)
return return
def setAutoReplace(self, autoReplace): def setAutoReplace(self, autoReplace):
"""Update the auto-replace dictionary. This replaces the entire
dictionary, so alterations have to be made in a copy.
"""
self.autoReplace = autoReplace self.autoReplace = autoReplace
return return
def setProjectChanged(self, bValue): def setProjectChanged(self, bValue):
"""Toggle the project changed flag, and propagate the
information to the GUI statusbar.
"""
self.projChanged = bValue self.projChanged = bValue
self.theParent.setProjectStatus(self.projChanged) self.theParent.setProjectStatus(self.projChanged)
if bValue: if bValue:
@@ -733,7 +764,8 @@ class NWProject():
def countStatus(self): def countStatus(self):
"""Count how many times the various status flags are used in the """Count how many times the various status flags are used in the
project tree. project tree. The counts themselves are kept in the NWStatus
objects. This is essentially a refresh.
""" """
self.statusItems.resetCounts() self.statusItems.resetCounts()
self.importItems.resetCounts() self.importItems.resetCounts()
@@ -825,7 +857,7 @@ class NWProject():
return False return False
return True return True
def _saveProjectValue(self, xParent, theName, theValue, allowNone=True): def _packProjectValue(self, xParent, theName, theValue, allowNone=True):
if not isinstance(theValue, list): if not isinstance(theValue, list):
theValue = [theValue] theValue = [theValue]
for aValue in theValue: for aValue in theValue:
@@ -838,7 +870,7 @@ class NWProject():
def _scanProjectFolder(self): def _scanProjectFolder(self):
"""Scan the project folder and check that the files in it are """Scan the project folder and check that the files in it are
also in the project CML file. If they aren't, import them as also in the project XML file. If they aren't, import them as
orphaned files so the user can either delete them, or put them orphaned files so the user can either delete them, or put them
back into the project tree. back into the project tree.
""" """
@@ -882,11 +914,20 @@ class NWProject():
return return
# Handle orphans # Handle orphans
aDoc = NWDoc(self, self.theParent)
nOrph = 0 nOrph = 0
for oHandle in orphanFiles: for oHandle in orphanFiles:
nOrph += 1
# Look for meta data
if aDoc.openDocument(oHandle, showStatus=False, isOrphan=True):
oName, oPath = aDoc.getMeta()
aDoc.clearDocument()
else:
nOrph += 1
oName = "Orphaned File %d" % nOrph
orphItem = NWItem(self) orphItem = NWItem(self)
orphItem.setName("Orphaned File %d" % nOrph) orphItem.setName(oName)
orphItem.setType(nwItemType.FILE) orphItem.setType(nwItemType.FILE)
orphItem.setClass(nwItemClass.NO_CLASS) orphItem.setClass(nwItemClass.NO_CLASS)
orphItem.setLayout(nwItemLayout.NO_LAYOUT) orphItem.setLayout(nwItemLayout.NO_LAYOUT)
@@ -909,8 +950,8 @@ class NWProject():
"End: {closed:s} " "End: {closed:s} "
"Words: {words:8d}" "Words: {words:8d}"
).format( ).format(
opened = datetime.fromtimestamp(self.projOpened).strftime(nwConst.tStampFmt), opened = formatTimeStamp(self.projOpened),
closed = datetime.now().strftime(nwConst.tStampFmt), closed = formatTimeStamp(time()),
words = self.getSessionWordCount(), words = self.getSessionWordCount(),
), file=outFile) ), file=outFile)
@@ -993,6 +1034,33 @@ class NWTree():
return return
def packXML(self, xParent):
"""Pack the content of the tree into an XML object.
"""
xContent = etree.SubElement(xParent, "content", attrib={
"count":str(self._theLength)}
)
for tHandle in self._treeOrder:
tItem = self.__getitem__(tHandle)
tItem.packXML(xContent)
return
def unpackXML(self, xContent):
"""Iterate through all items of a content XML object and add
them to the project tree.
"""
if xContent.tag != "content":
logger.error("XML entry is not a NWTree")
return False
self.clear()
for xItem in xContent:
nwItem = NWItem(self.theProject)
if nwItem.unpackXML(xItem):
self.append(nwItem.itemHandle, nwItem.parHandle, nwItem)
return True
## ##
# Tree Structure Methods # Tree Structure Methods
## ##
@@ -1019,7 +1087,8 @@ class NWTree():
def checkRootUnique(self, theClass): def checkRootUnique(self, theClass):
"""Checks if there already is a root entry of class 'theClass' """Checks if there already is a root entry of class 'theClass'
in the root of the project tree. in the root of the project tree. CUSTOM class is skipped as it
is not required to be unique.
""" """
if theClass == nwItemClass.CUSTOM: if theClass == nwItemClass.CUSTOM:
return True return True
+2 -2
View File
@@ -180,8 +180,8 @@ class GuiSessionLogView(QDialog):
inData = inLine.split() inData = inLine.split()
if len(inData) != 8: if len(inData) != 8:
continue continue
dStart = datetime.strptime("%s %s" % (inData[1],inData[2]),nwConst.tStampFmt) dStart = datetime.strptime("%s %s" % (inData[1],inData[2]), nwConst.tStampFmt)
dEnd = datetime.strptime("%s %s" % (inData[4],inData[5]),nwConst.tStampFmt) dEnd = datetime.strptime("%s %s" % (inData[4],inData[5]), nwConst.tStampFmt)
nWords = int(inData[7]) nWords = int(inData[7])
tDiff = dEnd - dStart tDiff = dEnd - dStart
sDiff = tDiff.total_seconds() sDiff = tDiff.total_seconds()
+1 -1
View File
@@ -404,7 +404,7 @@ class GuiMain(QMainWindow):
else: else:
return False return False
# project is loaded # Project is loaded
self.hasProject = True self.hasProject = True
# Load the tag index # Load the tag index