@@ -82,6 +82,18 @@ def checkBool(checkValue, defaultValue, allowNone=False):
|
||||
return defaultValue
|
||||
return defaultValue
|
||||
|
||||
def checkHandle(checkValue, defaultValue, allowNone=False):
|
||||
"""Check if a value is a handle.
|
||||
"""
|
||||
if allowNone:
|
||||
if checkValue is None:
|
||||
return None
|
||||
if checkValue == "None":
|
||||
return None
|
||||
if isHandle(checkValue):
|
||||
return str(checkValue)
|
||||
return defaultValue
|
||||
|
||||
def isHandle(theString):
|
||||
"""Check if a string is a valid novelWriter handle.
|
||||
Note: This is case sensitive. Must be lower case!
|
||||
|
||||
@@ -4,7 +4,6 @@ from nw.core.document import NWDoc
|
||||
from nw.core.index import NWIndex
|
||||
from nw.core.project import NWProject
|
||||
from nw.core.spellcheck import NWSpellCheck, NWSpellEnchant, NWSpellSimple
|
||||
from nw.core.tokenizer import Tokenizer
|
||||
from nw.core.tohtml import ToHtml
|
||||
from nw.core.tools import countWords, numberToRoman, numberToWord
|
||||
|
||||
@@ -15,7 +14,6 @@ __all__ = [
|
||||
"NWSpellCheck",
|
||||
"NWSpellEnchant",
|
||||
"NWSpellSimple",
|
||||
"Tokenizer",
|
||||
"ToHtml",
|
||||
"countWords",
|
||||
"numberToRoman",
|
||||
|
||||
+41
-33
@@ -26,11 +26,9 @@
|
||||
"""
|
||||
|
||||
import logging
|
||||
import nw
|
||||
|
||||
from os import path, rename, unlink
|
||||
|
||||
from nw.core.item import NWItem
|
||||
from nw.constants import nwAlert
|
||||
from nw.common import isHandle
|
||||
from nw.constants import nwItemLayout, nwItemClass, nwConst
|
||||
@@ -41,14 +39,14 @@ class NWDoc():
|
||||
|
||||
def __init__(self, theProject, theParent):
|
||||
|
||||
self.mainConf = nw.CONFIG
|
||||
self.theProject = theProject
|
||||
self.theParent = theParent
|
||||
|
||||
self.theItem = None
|
||||
self.docHandle = None
|
||||
self.fileLoc = None
|
||||
self.docMeta = ""
|
||||
# Internal Variables
|
||||
self._theItem = None # The currently open item
|
||||
self._docHandle = None # The handle of the currently open item
|
||||
self._fileLoc = None # The file location of the currently open item
|
||||
self._docMeta = "" # The meta string of the currently open item
|
||||
|
||||
# Internal Mapping
|
||||
self.makeAlert = self.theParent.makeAlert
|
||||
@@ -62,10 +60,10 @@ class NWDoc():
|
||||
def clearDocument(self):
|
||||
"""Clear the document contents.
|
||||
"""
|
||||
self.theItem = None
|
||||
self.docHandle = None
|
||||
self.fileLoc = None
|
||||
self.docMeta = ""
|
||||
self._theItem = None
|
||||
self._docHandle = None
|
||||
self._fileLoc = None
|
||||
self._docMeta = ""
|
||||
return
|
||||
|
||||
def openDocument(self, tHandle, showStatus=True, isOrphan=False):
|
||||
@@ -78,31 +76,31 @@ class NWDoc():
|
||||
# Always clear first, since the object will often be reused.
|
||||
self.clearDocument()
|
||||
|
||||
self.docHandle = tHandle
|
||||
self._docHandle = tHandle
|
||||
if not isOrphan:
|
||||
self.theItem = self.theProject.projTree[tHandle]
|
||||
self._theItem = self.theProject.projTree[tHandle]
|
||||
else:
|
||||
self.theItem = None
|
||||
self._theItem = None
|
||||
|
||||
if self.theItem is None and not isOrphan:
|
||||
if self._theItem is None and not isOrphan:
|
||||
self.clearDocument()
|
||||
return None
|
||||
|
||||
docFile = self.docHandle+".nwd"
|
||||
docFile = self._docHandle+".nwd"
|
||||
logger.debug("Opening document %s" % docFile)
|
||||
|
||||
docPath = path.join(self.theProject.projContent, docFile)
|
||||
self.fileLoc = docPath
|
||||
self._fileLoc = docPath
|
||||
|
||||
theText = ""
|
||||
self.docMeta = ""
|
||||
self._docMeta = ""
|
||||
if path.isfile(docPath):
|
||||
try:
|
||||
with open(docPath, mode="r", encoding="utf8") as inFile:
|
||||
fstLine = inFile.readline()
|
||||
if fstLine.startswith("%%~ "):
|
||||
# This is the meta line
|
||||
self.docMeta = fstLine[4:].strip()
|
||||
self._docMeta = fstLine[4:].strip()
|
||||
else:
|
||||
theText = fstLine
|
||||
theText += inFile.read()
|
||||
@@ -120,10 +118,10 @@ class NWDoc():
|
||||
logger.debug("The requested document does not exist.")
|
||||
return ""
|
||||
|
||||
logger.verbose("DocMeta: '%s'" % self.docMeta)
|
||||
logger.verbose("DocMeta: '%s'" % self._docMeta)
|
||||
|
||||
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
|
||||
|
||||
@@ -131,29 +129,29 @@ class NWDoc():
|
||||
"""Save the document via temp file in case of save failure, and
|
||||
in any case keep a backup of the file.
|
||||
"""
|
||||
if self.docHandle is None:
|
||||
if self._docHandle is None:
|
||||
return False
|
||||
|
||||
self.theProject.ensureFolderStructure()
|
||||
|
||||
docFile = self.docHandle+".nwd"
|
||||
docFile = self._docHandle+".nwd"
|
||||
logger.debug("Saving document %s" % docFile)
|
||||
|
||||
docPath = path.join(self.theProject.projContent, docFile)
|
||||
docTemp = path.join(self.theProject.projContent, docFile+"~")
|
||||
|
||||
if isinstance(self.theItem, NWItem):
|
||||
itemPath = self.theProject.projTree.getItemPath(self.docHandle)
|
||||
if self._theItem is None:
|
||||
docMeta = ""
|
||||
else:
|
||||
itemPath = self.theProject.projTree.getItemPath(self._docHandle)
|
||||
docMeta = (
|
||||
"%%~ {handlepath:s}:{itemclass:s}:{itemlayout:s}:{itemname:s}\n"
|
||||
).format(
|
||||
handlepath = ":".join(itemPath),
|
||||
itemclass = self.theItem.itemClass.name,
|
||||
itemlayout = self.theItem.itemLayout.name,
|
||||
itemname = self.theItem.itemName,
|
||||
itemclass = self._theItem.itemClass.name,
|
||||
itemlayout = self._theItem.itemLayout.name,
|
||||
itemname = self._theItem.itemName,
|
||||
)
|
||||
else:
|
||||
docMeta = ""
|
||||
|
||||
try:
|
||||
with open(docTemp, mode="w", encoding="utf8") as outFile:
|
||||
@@ -169,7 +167,7 @@ class NWDoc():
|
||||
unlink(docPath)
|
||||
rename(docTemp, docPath)
|
||||
|
||||
self.theParent.statusBar.setStatus("Saved Document: %s" % self.theItem.itemName)
|
||||
self.theParent.statusBar.setStatus("Saved Document: %s" % self._theItem.itemName)
|
||||
|
||||
return True
|
||||
|
||||
@@ -201,15 +199,25 @@ class NWDoc():
|
||||
# Getters
|
||||
##
|
||||
|
||||
def getFileLocation(self):
|
||||
"""Return the file location of the current file.
|
||||
"""
|
||||
return self._fileLoc
|
||||
|
||||
def getCurrentItem(self):
|
||||
"""Return a pointer to the currently open item.
|
||||
"""
|
||||
return self._theItem
|
||||
|
||||
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:
|
||||
if len(self._docMeta) < 14:
|
||||
# Not enough information
|
||||
return "", [], None, None
|
||||
|
||||
theMeta = self.docMeta
|
||||
theMeta = self._docMeta
|
||||
|
||||
# Scan for handles
|
||||
thePath = []
|
||||
|
||||
+2
-1
@@ -27,6 +27,7 @@
|
||||
|
||||
import logging
|
||||
import json
|
||||
import nw
|
||||
|
||||
from os import path
|
||||
from time import time
|
||||
@@ -66,9 +67,9 @@ class NWIndex():
|
||||
def __init__(self, theProject, theParent):
|
||||
|
||||
# Internal
|
||||
self.mainConf = nw.CONFIG
|
||||
self.theProject = theProject
|
||||
self.theParent = theParent
|
||||
self.mainConf = self.theParent.mainConf
|
||||
self.indexBroken = False
|
||||
|
||||
# Indices
|
||||
|
||||
@@ -28,7 +28,6 @@
|
||||
|
||||
import logging
|
||||
import json
|
||||
import nw
|
||||
|
||||
from os import path
|
||||
|
||||
@@ -40,9 +39,7 @@ class OptionState():
|
||||
|
||||
def __init__(self, theProject):
|
||||
|
||||
self.mainConf = nw.CONFIG
|
||||
self.theProject = theProject
|
||||
|
||||
self.theState = {}
|
||||
self.validMap = {
|
||||
"GuiWritingStats": {
|
||||
|
||||
+27
-25
@@ -481,7 +481,7 @@ class NWProject():
|
||||
|
||||
# Changes:
|
||||
# 1.0 : Original file format.
|
||||
# 1.1 : Changes the way documents are structure in the project
|
||||
# 1.1 : Changes the way documents are structured in the project
|
||||
# folder from data_X, where X is the first hex value of
|
||||
# the handle, to a single content folder.
|
||||
# 1.2 : Changes the way autoReplace entries are stored. The 1.1
|
||||
@@ -518,15 +518,16 @@ class NWProject():
|
||||
msgRes = msgBox.question(self.theParent, "Version Conflict", (
|
||||
"This project was saved by a newer version of novelWriter, version %s. "
|
||||
"This is version %s. If you continue to open the project, some attributes "
|
||||
"and settings may not be preserved. Continue opening the project?"
|
||||
"and settings may not be preserved, but the overall project should be fine. "
|
||||
"Continue opening the project?"
|
||||
) % (
|
||||
appVersion, nw.__version__
|
||||
))
|
||||
if msgRes != QMessageBox.Yes:
|
||||
return False
|
||||
|
||||
# Start Parsing XML
|
||||
# =================
|
||||
# Start Parsing the XML
|
||||
# =====================
|
||||
|
||||
for xChild in xRoot:
|
||||
if xChild.tag == "project":
|
||||
@@ -578,9 +579,9 @@ class NWProject():
|
||||
elif xItem.tag == "notesWordCount":
|
||||
self.notesWCount = checkInt(xItem.text, 0, False)
|
||||
elif xItem.tag == "status":
|
||||
self.statusItems.unpackEntries(xItem)
|
||||
self.statusItems.unpackXML(xItem)
|
||||
elif xItem.tag == "importance":
|
||||
self.importItems.unpackEntries(xItem)
|
||||
self.importItems.unpackXML(xItem)
|
||||
elif xItem.tag == "autoReplace":
|
||||
for xEntry in xItem:
|
||||
if xEntry.tag == "entry":
|
||||
@@ -684,9 +685,9 @@ class NWProject():
|
||||
self._packProjectValue(xTitleFmt, aKey, aValue)
|
||||
|
||||
xStatus = etree.SubElement(xSettings, "status")
|
||||
self.statusItems.packEntries(xStatus)
|
||||
self.statusItems.packXML(xStatus)
|
||||
xStatus = etree.SubElement(xSettings, "importance")
|
||||
self.importItems.packEntries(xStatus)
|
||||
self.importItems.packXML(xStatus)
|
||||
|
||||
# Save Tree Content
|
||||
logger.debug("Writing project content")
|
||||
@@ -1440,23 +1441,24 @@ class NWProject():
|
||||
def _deprecatedFiles(self):
|
||||
"""Delete files that are no longer used by novelWriter.
|
||||
"""
|
||||
rmList = []
|
||||
rmList.append(path.join(self.projCache, "nwProject.nwx.0"))
|
||||
rmList.append(path.join(self.projCache, "nwProject.nwx.1"))
|
||||
rmList.append(path.join(self.projCache, "nwProject.nwx.2"))
|
||||
rmList.append(path.join(self.projCache, "nwProject.nwx.3"))
|
||||
rmList.append(path.join(self.projCache, "nwProject.nwx.4"))
|
||||
rmList.append(path.join(self.projCache, "nwProject.nwx.5"))
|
||||
rmList.append(path.join(self.projCache, "nwProject.nwx.6"))
|
||||
rmList.append(path.join(self.projCache, "nwProject.nwx.7"))
|
||||
rmList.append(path.join(self.projCache, "nwProject.nwx.8"))
|
||||
rmList.append(path.join(self.projCache, "nwProject.nwx.9"))
|
||||
rmList.append(path.join(self.projMeta, "mainOptions.json"))
|
||||
rmList.append(path.join(self.projMeta, "exportOptions.json"))
|
||||
rmList.append(path.join(self.projMeta, "outlineOptions.json"))
|
||||
rmList.append(path.join(self.projMeta, "timelineOptions.json"))
|
||||
rmList.append(path.join(self.projMeta, "docMergeOptions.json"))
|
||||
rmList.append(path.join(self.projMeta, "sessionLogOptions.json"))
|
||||
rmList = [
|
||||
path.join(self.projCache, "nwProject.nwx.0"),
|
||||
path.join(self.projCache, "nwProject.nwx.1"),
|
||||
path.join(self.projCache, "nwProject.nwx.2"),
|
||||
path.join(self.projCache, "nwProject.nwx.3"),
|
||||
path.join(self.projCache, "nwProject.nwx.4"),
|
||||
path.join(self.projCache, "nwProject.nwx.5"),
|
||||
path.join(self.projCache, "nwProject.nwx.6"),
|
||||
path.join(self.projCache, "nwProject.nwx.7"),
|
||||
path.join(self.projCache, "nwProject.nwx.8"),
|
||||
path.join(self.projCache, "nwProject.nwx.9"),
|
||||
path.join(self.projMeta, "mainOptions.json"),
|
||||
path.join(self.projMeta, "exportOptions.json"),
|
||||
path.join(self.projMeta, "outlineOptions.json"),
|
||||
path.join(self.projMeta, "timelineOptions.json"),
|
||||
path.join(self.projMeta, "docMergeOptions.json"),
|
||||
path.join(self.projMeta, "sessionLogOptions.json"),
|
||||
]
|
||||
|
||||
for rmFile in rmList:
|
||||
if path.isfile(rmFile):
|
||||
|
||||
@@ -89,13 +89,10 @@ class NWSpellCheck():
|
||||
|
||||
@staticmethod
|
||||
def expandLanguage(spTag):
|
||||
"""Translate a language tag to something more suer friendly.
|
||||
"""Translate a language tag to something more user friendly.
|
||||
"""
|
||||
spBits = spTag.split("_")
|
||||
if spBits[0] in isoLanguage.ISO_639_1:
|
||||
spLang = isoLanguage.ISO_639_1[spBits[0]]
|
||||
else:
|
||||
spLang = spBits[0]
|
||||
spLang = isoLanguage.ISO_639_1.get(spBits[0], spBits[0])
|
||||
if len(spBits) > 1:
|
||||
spLang += " (%s)" % spBits[1]
|
||||
return spLang
|
||||
|
||||
+42
-42
@@ -36,12 +36,12 @@ logger = logging.getLogger(__name__)
|
||||
class NWStatus():
|
||||
|
||||
def __init__(self):
|
||||
self.theLabels = []
|
||||
self.theColours = []
|
||||
self.theCounts = []
|
||||
self.theMap = {}
|
||||
self.theLength = 0
|
||||
self.theIndex = 0
|
||||
self._theLabels = []
|
||||
self._theColours = []
|
||||
self._theCounts = []
|
||||
self._theMap = {}
|
||||
self._theLength = 0
|
||||
self._theIndex = 0
|
||||
return
|
||||
|
||||
def addEntry(self, theLabel, theColours):
|
||||
@@ -50,11 +50,11 @@ class NWStatus():
|
||||
"""
|
||||
theLabel = theLabel.strip()
|
||||
if self.lookupEntry(theLabel) is None:
|
||||
self.theLabels.append(theLabel)
|
||||
self.theColours.append(theColours)
|
||||
self.theCounts.append(0)
|
||||
self.theMap[theLabel] = self.theLength
|
||||
self.theLength += 1
|
||||
self._theLabels.append(theLabel)
|
||||
self._theColours.append(theColours)
|
||||
self._theCounts.append(0)
|
||||
self._theMap[theLabel] = self._theLength
|
||||
self._theLength += 1
|
||||
return True
|
||||
|
||||
def lookupEntry(self, theLabel):
|
||||
@@ -64,8 +64,8 @@ class NWStatus():
|
||||
if theLabel is None:
|
||||
return None
|
||||
theLabel = theLabel.strip()
|
||||
if theLabel in self.theMap.keys():
|
||||
return self.theMap[theLabel]
|
||||
if theLabel in self._theMap.keys():
|
||||
return self._theMap[theLabel]
|
||||
return None
|
||||
|
||||
def checkEntry(self, theStatus):
|
||||
@@ -77,8 +77,8 @@ class NWStatus():
|
||||
if self.lookupEntry(theStatus) is not None:
|
||||
return theStatus
|
||||
theStatus = checkInt(theStatus, 0, False)
|
||||
if theStatus >= 0 and theStatus < self.theLength:
|
||||
return self.theLabels[theStatus]
|
||||
if theStatus >= 0 and theStatus < self._theLength:
|
||||
return self._theLabels[theStatus]
|
||||
|
||||
def setNewEntries(self, newList):
|
||||
"""Update the list of entries after they have been modified by
|
||||
@@ -87,12 +87,12 @@ class NWStatus():
|
||||
replaceMap = {}
|
||||
|
||||
if newList is not None:
|
||||
self.theLabels = []
|
||||
self.theColours = []
|
||||
self.theCounts = []
|
||||
self.theMap = {}
|
||||
self.theLength = 0
|
||||
self.theIndex = 0
|
||||
self._theLabels = []
|
||||
self._theColours = []
|
||||
self._theCounts = []
|
||||
self._theMap = {}
|
||||
self._theLength = 0
|
||||
self._theIndex = 0
|
||||
|
||||
for nName, nR, nG, nB, oName in newList:
|
||||
self.addEntry(nName, (nR, nG, nB))
|
||||
@@ -104,7 +104,7 @@ class NWStatus():
|
||||
def resetCounts(self):
|
||||
"""Clear the counts of references to the status entries.
|
||||
"""
|
||||
self.theCounts = [0]*self.theLength
|
||||
self._theCounts = [0]*self._theLength
|
||||
return
|
||||
|
||||
def countEntry(self, theLabel):
|
||||
@@ -112,23 +112,23 @@ class NWStatus():
|
||||
"""
|
||||
theIndex = self.lookupEntry(theLabel)
|
||||
if theIndex is not None:
|
||||
self.theCounts[theIndex] += 1
|
||||
self._theCounts[theIndex] += 1
|
||||
return
|
||||
|
||||
def packEntries(self, xParent):
|
||||
def packXML(self, xParent):
|
||||
"""Pack the status entries into an XML object for saving to the
|
||||
main project file.
|
||||
"""
|
||||
for n in range(self.theLength):
|
||||
for n in range(self._theLength):
|
||||
xSub = etree.SubElement(xParent, "entry", attrib={
|
||||
"blue" : str(self.theColours[n][2]),
|
||||
"green" : str(self.theColours[n][1]),
|
||||
"red" : str(self.theColours[n][0]),
|
||||
"blue" : str(self._theColours[n][2]),
|
||||
"green" : str(self._theColours[n][1]),
|
||||
"red" : str(self._theColours[n][0]),
|
||||
})
|
||||
xSub.text = self.theLabels[n]
|
||||
xSub.text = self._theLabels[n]
|
||||
return True
|
||||
|
||||
def unpackEntries(self, xParent):
|
||||
def unpackXML(self, xParent):
|
||||
"""Unpack an XML tree and set the class values.
|
||||
"""
|
||||
theLabels = []
|
||||
@@ -151,12 +151,12 @@ class NWStatus():
|
||||
theColours.append((cR, cG, cB))
|
||||
|
||||
if len(theLabels) > 0:
|
||||
self.theLabels = []
|
||||
self.theColours = []
|
||||
self.theCounts = []
|
||||
self.theMap = {}
|
||||
self.theLength = 0
|
||||
self.theIndex = 0
|
||||
self._theLabels = []
|
||||
self._theColours = []
|
||||
self._theCounts = []
|
||||
self._theMap = {}
|
||||
self._theLength = 0
|
||||
self._theIndex = 0
|
||||
|
||||
for n in range(len(theLabels)):
|
||||
self.addEntry(theLabels[n], theColours[n])
|
||||
@@ -170,22 +170,22 @@ class NWStatus():
|
||||
def __getitem__(self, n):
|
||||
"""Return an entry by its index.
|
||||
"""
|
||||
if n >= 0 and n < self.theLength:
|
||||
return self.theLabels[n], self.theColours[n], self.theCounts[n]
|
||||
if n >= 0 and n < self._theLength:
|
||||
return self._theLabels[n], self._theColours[n], self._theCounts[n]
|
||||
return None, None, None
|
||||
|
||||
def __iter__(self):
|
||||
"""Initialise the iterator.
|
||||
"""
|
||||
self.theIndex = 0
|
||||
self._theIndex = 0
|
||||
return self
|
||||
|
||||
def __next__(self):
|
||||
"""Return the next entry for the iterator.
|
||||
"""
|
||||
if self.theIndex < self.theLength:
|
||||
theLabel, theColour, theCount = self.__getitem__(self.theIndex)
|
||||
self.theIndex += 1
|
||||
if self._theIndex < self._theLength:
|
||||
theLabel, theColour, theCount = self.__getitem__(self._theIndex)
|
||||
self._theIndex += 1
|
||||
return theLabel, theColour, theCount
|
||||
else:
|
||||
raise StopIteration
|
||||
|
||||
+1
-2
@@ -309,8 +309,7 @@ class ToHtml(Tokenizer):
|
||||
def _formatKeywords(self, tText):
|
||||
"""Apply HTML formatting to keywords.
|
||||
"""
|
||||
tText = "@"+tText
|
||||
isValid, theBits, thePos = self.theParent.theIndex.scanThis(tText)
|
||||
isValid, theBits, thePos = self.theParent.theIndex.scanThis("@"+tText)
|
||||
if not isValid or not theBits:
|
||||
return ""
|
||||
|
||||
|
||||
@@ -27,7 +27,6 @@
|
||||
|
||||
import logging
|
||||
import re
|
||||
import nw
|
||||
|
||||
from operator import itemgetter
|
||||
from PyQt5.QtCore import QRegularExpression
|
||||
@@ -74,9 +73,8 @@ class Tokenizer():
|
||||
|
||||
def __init__(self, theProject, theParent):
|
||||
|
||||
self.mainConf = nw.CONFIG
|
||||
self.theProject = theProject
|
||||
self.theParent = theParent
|
||||
self.theProject = theProject
|
||||
self.theParent = theParent
|
||||
|
||||
# Data Variables
|
||||
self.theText = None # The raw text to be tokenized
|
||||
|
||||
+24
-25
@@ -34,7 +34,7 @@ from hashlib import sha256
|
||||
from time import time
|
||||
|
||||
from nw.core.item import NWItem
|
||||
from nw.common import checkString
|
||||
from nw.common import checkHandle
|
||||
from nw.constants import nwFiles, nwItemType, nwItemClass, nwItemLayout, nwConst
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -49,7 +49,6 @@ class NWTree():
|
||||
self._treeOrder = [] # The order of the tree items on the tree view
|
||||
self._treeRoots = [] # The root items of the tree
|
||||
self._trashRoot = None # The handle of the trash root folder
|
||||
self._theLength = 0 # Always the length of _treeOrder
|
||||
self._theIndex = 0 # The current iterator index
|
||||
self._treeChanged = False # True if tree structure has changed
|
||||
self._handleSeed = None # Used for generating handles for testing
|
||||
@@ -68,7 +67,6 @@ class NWTree():
|
||||
self._treeRoots = []
|
||||
self._trashRoot = None
|
||||
self._archRoot = None
|
||||
self._theLength = 0
|
||||
self._theIndex = 0
|
||||
self._treeChanged = False
|
||||
return
|
||||
@@ -81,12 +79,12 @@ class NWTree():
|
||||
def append(self, tHandle, pHandle, nwItem):
|
||||
"""Add a new item to the end of the tree.
|
||||
"""
|
||||
tHandle = checkString(tHandle, None, True)
|
||||
pHandle = checkString(pHandle, None, True)
|
||||
tHandle = checkHandle(tHandle, None, True)
|
||||
pHandle = checkHandle(pHandle, None, True)
|
||||
if tHandle is None:
|
||||
tHandle = self._makeHandle()
|
||||
|
||||
logger.verbose("Adding entry %s with parent %s" % (str(tHandle), str(pHandle)))
|
||||
logger.verbose("Adding item %s with parent %s" % (str(tHandle), str(pHandle)))
|
||||
|
||||
nwItem.setHandle(tHandle)
|
||||
nwItem.setParent(pHandle)
|
||||
@@ -95,29 +93,29 @@ class NWTree():
|
||||
self._treeOrder.append(tHandle)
|
||||
|
||||
if nwItem.itemType == nwItemType.ROOT:
|
||||
logger.verbose("Entry %s is a root item" % str(tHandle))
|
||||
logger.verbose("Item %s is a root item" % str(tHandle))
|
||||
self._treeRoots.append(tHandle)
|
||||
if nwItem.itemClass == nwItemClass.ARCHIVE:
|
||||
logger.verbose("Entry %s is the archive folder" % str(tHandle))
|
||||
logger.verbose("Item %s is the archive folder" % str(tHandle))
|
||||
self._archRoot = tHandle
|
||||
|
||||
if nwItem.itemType == nwItemType.TRASH:
|
||||
if self._trashRoot is None:
|
||||
logger.verbose("Entry %s is the trash folder" % str(tHandle))
|
||||
logger.verbose("Item %s is the trash folder" % str(tHandle))
|
||||
self._trashRoot = tHandle
|
||||
else:
|
||||
logger.error("Only one trash folder allowed")
|
||||
|
||||
self._theLength = len(self._treeOrder)
|
||||
self._setTreeChanged(True)
|
||||
|
||||
return
|
||||
|
||||
def packXML(self, xParent):
|
||||
"""Pack the content of the tree into an XML object.
|
||||
"""Pack the content of the tree into the provided XML object. In
|
||||
the order defined by the _treeOrder list.
|
||||
"""
|
||||
xContent = etree.SubElement(xParent, "content", attrib={
|
||||
"count": str(self._theLength)}
|
||||
"count": str(len(self._treeOrder))}
|
||||
)
|
||||
for tHandle in self._treeOrder:
|
||||
tItem = self.__getitem__(tHandle)
|
||||
@@ -309,7 +307,6 @@ class NWTree():
|
||||
|
||||
# Save the temp list
|
||||
self._treeOrder = tmpOrder
|
||||
self._theLength = len(self._treeOrder)
|
||||
self._setTreeChanged(True)
|
||||
logger.verbose("Project tree order updated")
|
||||
|
||||
@@ -330,7 +327,7 @@ class NWTree():
|
||||
if tItem is None:
|
||||
return False
|
||||
if tItem.itemType != nwItemType.FILE:
|
||||
logger.error("Item '%s' is not a file" % tHandle)
|
||||
logger.error("Item %s is not a file" % tHandle)
|
||||
return False
|
||||
if not isinstance(itemLayout, nwItemLayout):
|
||||
return False
|
||||
@@ -370,12 +367,12 @@ class NWTree():
|
||||
def __len__(self):
|
||||
"""Return the length counter. Does not check that it is correct!
|
||||
"""
|
||||
return self._theLength
|
||||
return len(self._treeOrder)
|
||||
|
||||
def __bool__(self):
|
||||
"""Returns True if the tree has any entries.
|
||||
"""
|
||||
return self._theLength > 0
|
||||
return len(self._treeOrder) > 0
|
||||
|
||||
##
|
||||
# Item Access Methods
|
||||
@@ -391,19 +388,21 @@ class NWTree():
|
||||
return None
|
||||
|
||||
def __delitem__(self, tHandle):
|
||||
"""This only removes the item from the order list, but not from
|
||||
the project tree.
|
||||
"""Remove an item from the internal lists and dictionaries.
|
||||
"""
|
||||
if tHandle not in self._treeOrder:
|
||||
logger.warning(
|
||||
"Could not remove item %s from project tree as it does not exist" % tHandle
|
||||
)
|
||||
if tHandle in self._treeOrder and tHandle in self._projTree:
|
||||
self._treeOrder.remove(tHandle)
|
||||
del self._projTree[tHandle]
|
||||
else:
|
||||
logger.warning("Failed to delete item %s: item not found" % tHandle)
|
||||
return False
|
||||
self._treeOrder.remove(tHandle)
|
||||
self._theLength = len(self._treeOrder)
|
||||
|
||||
if tHandle in self._treeRoots:
|
||||
self._treeRoots.remove(tHandle)
|
||||
if tHandle == self._trashRoot:
|
||||
self._trashRoot = None
|
||||
if tHandle == self._archRoot:
|
||||
self._archRoot = None
|
||||
|
||||
self._setTreeChanged(True)
|
||||
|
||||
@@ -427,7 +426,7 @@ class NWTree():
|
||||
def __next__(self):
|
||||
"""Returns the item from the next entry in the _treeOrder list.
|
||||
"""
|
||||
if self._theIndex < self._theLength:
|
||||
if self._theIndex < len(self._treeOrder):
|
||||
theItem = self.__getitem__(self._treeOrder[self._theIndex])
|
||||
self._theIndex += 1
|
||||
return theItem
|
||||
|
||||
+17
-15
@@ -270,10 +270,12 @@ class GuiDocEditor(QTextEdit):
|
||||
afTime = time()
|
||||
logger.debug("Document highlighted in %.3f milliseconds" % (1000*(afTime-bfTime)))
|
||||
|
||||
if tLine is None:
|
||||
self.setCursorPosition(self.nwDocument.theItem.cursorPos)
|
||||
theItem = self.nwDocument.getCurrentItem()
|
||||
if tLine is None and theItem is not None:
|
||||
self.setCursorPosition(theItem.cursorPos)
|
||||
else:
|
||||
self.setCursorLine(tLine)
|
||||
|
||||
self.lastEdit = time()
|
||||
self._runCounter()
|
||||
self.wcTimer.start()
|
||||
@@ -308,21 +310,20 @@ class GuiDocEditor(QTextEdit):
|
||||
"""Save the text currently in the editor to the NWDoc object,
|
||||
and update the NWItem meta data.
|
||||
"""
|
||||
if self.nwDocument.theItem is None:
|
||||
theItem = self.nwDocument.getCurrentItem()
|
||||
if theItem is None:
|
||||
return False
|
||||
|
||||
docText = self.getText()
|
||||
cursPos = self.getCursorPosition()
|
||||
self.nwDocument.theItem.setCharCount(self.charCount)
|
||||
self.nwDocument.theItem.setWordCount(self.wordCount)
|
||||
self.nwDocument.theItem.setParaCount(self.paraCount)
|
||||
self.nwDocument.theItem.setCursorPos(cursPos)
|
||||
theItem.setCharCount(self.charCount)
|
||||
theItem.setWordCount(self.wordCount)
|
||||
theItem.setParaCount(self.paraCount)
|
||||
theItem.setCursorPos(cursPos)
|
||||
self.nwDocument.saveDocument(docText)
|
||||
self.setDocumentChanged(False)
|
||||
|
||||
self.theParent.theIndex.scanText(
|
||||
self.nwDocument.theItem.itemHandle, docText
|
||||
)
|
||||
self.theParent.theIndex.scanText(theItem.itemHandle, docText)
|
||||
|
||||
return True
|
||||
|
||||
@@ -598,7 +599,7 @@ class GuiDocEditor(QTextEdit):
|
||||
"Location: {fileLoc:s}"
|
||||
).format(
|
||||
handle = self.theHandle,
|
||||
fileLoc = str(self.nwDocument.fileLoc)
|
||||
fileLoc = str(self.nwDocument.getFileLocation())
|
||||
))
|
||||
return
|
||||
|
||||
@@ -873,7 +874,8 @@ class GuiDocEditor(QTextEdit):
|
||||
def _updateCounts(self):
|
||||
"""Slot for the word counter's finished signal
|
||||
"""
|
||||
if self.theHandle is None or self.nwDocument.theItem is None:
|
||||
theItem = self.nwDocument.getCurrentItem()
|
||||
if self.theHandle is None or theItem is None:
|
||||
return
|
||||
|
||||
logger.verbose("Updating word count")
|
||||
@@ -881,9 +883,9 @@ class GuiDocEditor(QTextEdit):
|
||||
self.charCount = self.wCounter.charCount
|
||||
self.wordCount = self.wCounter.wordCount
|
||||
self.paraCount = self.wCounter.paraCount
|
||||
self.nwDocument.theItem.setCharCount(self.charCount)
|
||||
self.nwDocument.theItem.setWordCount(self.wordCount)
|
||||
self.nwDocument.theItem.setParaCount(self.paraCount)
|
||||
theItem.setCharCount(self.charCount)
|
||||
theItem.setWordCount(self.wordCount)
|
||||
theItem.setParaCount(self.paraCount)
|
||||
|
||||
self.theParent.treeView.propagateCount(self.theHandle, self.wordCount)
|
||||
self.theParent.treeView.projectWordCount()
|
||||
|
||||
+7
-4
@@ -639,7 +639,8 @@ class GuiDocViewHistory():
|
||||
return
|
||||
|
||||
def _truncateHistory(self, atPos):
|
||||
"""Truncate the navigation history to the given position.
|
||||
"""Truncate the navigation history to the given position. Also
|
||||
enforces a maximum length of the navigation history to 20.
|
||||
"""
|
||||
nSkip = 1 if atPos > 19 else 0
|
||||
|
||||
@@ -652,13 +653,15 @@ class GuiDocViewHistory():
|
||||
return
|
||||
|
||||
def _dumpHistory(self):
|
||||
"""Debug function to dump history. Since it is a for loop, it is
|
||||
skipped entirely if log level isn't VERBOSE.
|
||||
"""Debug function to dump history to the logger. Since it is a
|
||||
for loop, it is skipped entirely if log level isn't VERBOSE.
|
||||
"""
|
||||
if logger.getEffectiveLevel() < logging.DEBUG:
|
||||
for i, (h, p) in enumerate(zip(self._navHistory, self._posHistory)):
|
||||
logger.verbose(
|
||||
"History: %s %2d %13s %5d" % (">" if i == self._currPos else " ", i, h, p)
|
||||
"History %02d: %s %13s [x:%d]" % (
|
||||
i + 1, ">" if i == self._currPos else " ", h, p
|
||||
)
|
||||
)
|
||||
return
|
||||
|
||||
|
||||
+7
-7
@@ -378,11 +378,11 @@ class GuiProjectTree(QTreeWidget):
|
||||
return True
|
||||
|
||||
def deleteItem(self, tHandle=None, alreadyAsked=False, askForTrash=False):
|
||||
"""Delete items from the tree. Note that this does not delete
|
||||
the item from the item tree in the project object. However,
|
||||
since this is only meta data, there isn't really a need to do
|
||||
that to save memory. Items not in the tree are not saved to the
|
||||
project file, so a loaded project will be clean anyway.
|
||||
"""Delete an item from the project tree. As a first step, files are
|
||||
moved to the Trash folder. Permanent deletion is a second step. This
|
||||
second step also deletes the item from the project object as well as
|
||||
delete the files on disk. Folders are deleted if they're empty only,
|
||||
and the deletion is always permanent.
|
||||
"""
|
||||
if tHandle is None:
|
||||
tHandle = self.getSelectedHandle()
|
||||
@@ -584,7 +584,7 @@ class GuiProjectTree(QTreeWidget):
|
||||
always make sure items with a parent have had their parent item
|
||||
sent first.
|
||||
"""
|
||||
logger.debug("Building project tree ...")
|
||||
logger.debug("Building the project tree ...")
|
||||
self.clear()
|
||||
iCount = 0
|
||||
|
||||
@@ -592,7 +592,7 @@ class GuiProjectTree(QTreeWidget):
|
||||
iCount += 1
|
||||
self._addTreeItem(nwItem)
|
||||
|
||||
logger.debug("%d items added to project tree" % iCount)
|
||||
logger.debug("%d items added to the project tree" % iCount)
|
||||
return True
|
||||
|
||||
def getSelectedHandle(self):
|
||||
|
||||
+10
-1
@@ -5,7 +5,7 @@
|
||||
import pytest
|
||||
from nw.common import (
|
||||
checkString, checkBool, checkInt, colRange, formatInt, transferCase,
|
||||
fuzzyTime
|
||||
fuzzyTime, checkHandle
|
||||
)
|
||||
from nwtools import cmpList
|
||||
|
||||
@@ -42,6 +42,15 @@ def testCheckBool():
|
||||
assert checkBool(1.0, None, False) is None
|
||||
assert checkBool(2.0, None, False) is None
|
||||
|
||||
@pytest.mark.core
|
||||
def testCheckHandle():
|
||||
assert checkHandle("None", 1, True) is None
|
||||
assert checkHandle("None", 1, False) == 1
|
||||
assert checkHandle(None, 1, True) is None
|
||||
assert checkHandle(None, 1, False) == 1
|
||||
assert checkHandle("47666c91c7ccf", None, False) == "47666c91c7ccf"
|
||||
assert checkHandle("h7666c91c7ccf", None, False) is None
|
||||
|
||||
@pytest.mark.core
|
||||
def testColRange():
|
||||
assert colRange([0, 0], [0, 0], 0) is None
|
||||
|
||||
+61
-2
@@ -12,7 +12,7 @@ from nw.core.project import NWProject
|
||||
from nw.core.document import NWDoc
|
||||
from nw.core.index import NWIndex
|
||||
from nw.core.spellcheck import NWSpellEnchant, NWSpellSimple
|
||||
from nw.constants import nwItemClass, nwItemLayout
|
||||
from nw.constants import nwItemClass, nwItemLayout, nwFiles
|
||||
|
||||
@pytest.mark.project
|
||||
def testProjectNewOpenSave(nwFuncTemp, nwTempProj, nwRef, nwTemp, nwDummy):
|
||||
@@ -316,7 +316,7 @@ def testDocMeta(nwDummy, nwLipsum):
|
||||
assert theClass == nwItemClass.NOVEL
|
||||
assert theLayout == nwItemLayout.SCENE
|
||||
|
||||
aDoc.docMeta = "too_short"
|
||||
aDoc._docMeta = "too_short"
|
||||
theMeta, thePath, theClass, theLayout = aDoc.getMeta()
|
||||
assert theMeta == ""
|
||||
assert thePath == []
|
||||
@@ -372,3 +372,62 @@ def testSpellSimple(nwTemp, nwConf):
|
||||
|
||||
dList = spChk.listDictionaries()
|
||||
assert len(dList) > 0
|
||||
|
||||
@pytest.mark.project
|
||||
def testProjectOptions(nwDummy, nwLipsum):
|
||||
theProject = NWProject(nwDummy)
|
||||
assert theProject.projMeta is None
|
||||
|
||||
theOpts = theProject.optState
|
||||
assert not theOpts.loadSettings()
|
||||
assert not theOpts.saveSettings()
|
||||
|
||||
# No Settings
|
||||
assert theProject.openProject(nwLipsum)
|
||||
assert theOpts.loadSettings()
|
||||
assert theOpts.saveSettings()
|
||||
assert str(theOpts.theState) == r"{}"
|
||||
|
||||
# Read Invalid Settings and Filter
|
||||
stateFile = path.join(theProject.projMeta, nwFiles.OPTS_FILE)
|
||||
with open(stateFile, mode="w", encoding="utf8") as outFile:
|
||||
outFile.write(
|
||||
r'{"GuiProjectSettings": {"winWidth": 100, "winHeight": 50}, "NoGroup": {"NoName": 0}}'
|
||||
)
|
||||
assert theOpts.loadSettings()
|
||||
assert str(theOpts.theState) == r"{'GuiProjectSettings': {'winWidth': 100, 'winHeight': 50}}"
|
||||
|
||||
# Set New Settings
|
||||
assert not theOpts.setValue("NoGroup", "NoName", None)
|
||||
assert not theOpts.setValue("GuiProjectSettings", "NoName", None)
|
||||
assert theOpts.setValue("GuiProjectSettings", "winWidth", 200)
|
||||
assert theOpts.setValue("GuiProjectSettings", "winHeight", 80)
|
||||
assert str(theOpts.theState) == r"{'GuiProjectSettings': {'winWidth': 200, 'winHeight': 80}}"
|
||||
|
||||
# Check Read/Write Types
|
||||
|
||||
## String
|
||||
assert theOpts.setValue("GuiWritingStats", "winWidth", "123")
|
||||
assert isinstance(theOpts.getString("GuiWritingStats", "winWidth", "456"), str)
|
||||
assert theOpts.getString("GuiWritingStats", "NoName", "456") == "456"
|
||||
|
||||
## Int
|
||||
assert theOpts.setValue("GuiWritingStats", "winWidth", "123")
|
||||
assert isinstance(theOpts.getInt("GuiWritingStats", "winWidth", 456), int)
|
||||
assert theOpts.getInt("GuiWritingStats", "NoName", 456) == 456
|
||||
assert theOpts.setValue("GuiWritingStats", "winWidth", "True")
|
||||
assert theOpts.getInt("GuiWritingStats", "NoName", 456) == 456
|
||||
|
||||
## Float
|
||||
assert theOpts.setValue("GuiWritingStats", "winWidth", "123")
|
||||
assert isinstance(theOpts.getFloat("GuiWritingStats", "winWidth", 456.0), float)
|
||||
assert theOpts.getFloat("GuiWritingStats", "NoName", 456.0) == 456.0
|
||||
assert theOpts.setValue("GuiWritingStats", "winWidth", "True")
|
||||
assert theOpts.getFloat("GuiWritingStats", "winWidth", 456.0) == 456.0
|
||||
|
||||
## Bool
|
||||
assert theOpts.setValue("GuiWritingStats", "winWidth", True)
|
||||
assert isinstance(theOpts.getBool("GuiWritingStats", "winWidth", False), bool)
|
||||
assert theOpts.getFloat("GuiWritingStats", "NoName", False) is False
|
||||
assert theOpts.setValue("GuiWritingStats", "winWidth", "True")
|
||||
assert theOpts.getFloat("GuiWritingStats", "winWidth", False) is False
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""novelWriter Tools Tester
|
||||
"""
|
||||
|
||||
import pytest
|
||||
|
||||
from nw.core.tools import countWords, numberToRoman, numberToWord
|
||||
|
||||
@pytest.mark.core
|
||||
def testCountWords():
|
||||
testText = (
|
||||
"# Heading One\n"
|
||||
"## Heading Two\n"
|
||||
"### Heading Three\n"
|
||||
"#### Heading Four\n"
|
||||
"\n"
|
||||
"@tag: value\n"
|
||||
"\n"
|
||||
"% A comment that should n ot be counted.\n"
|
||||
"\n"
|
||||
"The first paragraph.\n"
|
||||
"\n"
|
||||
"The second paragraph.\n"
|
||||
"\n"
|
||||
"\n"
|
||||
"The third paragraph.\n"
|
||||
)
|
||||
cC, wC, pC = countWords(testText)
|
||||
|
||||
assert cC == 108
|
||||
assert wC == 17
|
||||
assert pC == 3
|
||||
|
||||
@pytest.mark.core
|
||||
def testNumberWords():
|
||||
assert numberToWord(0, "en") == "Zero"
|
||||
assert numberToWord(1, "en") == "One"
|
||||
assert numberToWord(2, "en") == "Two"
|
||||
assert numberToWord(3, "en") == "Three"
|
||||
assert numberToWord(4, "en") == "Four"
|
||||
assert numberToWord(5, "en") == "Five"
|
||||
assert numberToWord(6, "en") == "Six"
|
||||
assert numberToWord(7, "en") == "Seven"
|
||||
assert numberToWord(8, "en") == "Eight"
|
||||
assert numberToWord(9, "en") == "Nine"
|
||||
assert numberToWord(10, "en") == "Ten"
|
||||
assert numberToWord(11, "en") == "Eleven"
|
||||
assert numberToWord(12, "en") == "Twelve"
|
||||
assert numberToWord(13, "en") == "Thirteen"
|
||||
assert numberToWord(14, "en") == "Fourteen"
|
||||
assert numberToWord(15, "en") == "Fifteen"
|
||||
assert numberToWord(16, "en") == "Sixteen"
|
||||
assert numberToWord(17, "en") == "Seventeen"
|
||||
assert numberToWord(18, "en") == "Eighteen"
|
||||
assert numberToWord(19, "en") == "Nineteen"
|
||||
assert numberToWord(20, "en") == "Twenty"
|
||||
assert numberToWord(21, "en") == "Twenty-One"
|
||||
assert numberToWord(29, "en") == "Twenty-Nine"
|
||||
assert numberToWord(42, "en") == "Forty-Two"
|
||||
assert numberToWord(142, "en") == "One Hundred Forty-Two"
|
||||
assert numberToWord(999, "en") == "Nine Hundred Ninety-Nine"
|
||||
|
||||
@pytest.mark.core
|
||||
def testRomanNumbers():
|
||||
assert numberToRoman(None, False) == "NAN"
|
||||
assert numberToRoman(0, False) == "OOR"
|
||||
assert numberToRoman(1, False) == "I"
|
||||
assert numberToRoman(2, False) == "II"
|
||||
assert numberToRoman(3, False) == "III"
|
||||
assert numberToRoman(4, False) == "IV"
|
||||
assert numberToRoman(5, False) == "V"
|
||||
assert numberToRoman(6, False) == "VI"
|
||||
assert numberToRoman(7, False) == "VII"
|
||||
assert numberToRoman(8, False) == "VIII"
|
||||
assert numberToRoman(9, False) == "IX"
|
||||
assert numberToRoman(10, False) == "X"
|
||||
assert numberToRoman(14, False) == "XIV"
|
||||
assert numberToRoman(42, False) == "XLII"
|
||||
assert numberToRoman(99, False) == "XCIX"
|
||||
assert numberToRoman(142, False) == "CXLII"
|
||||
assert numberToRoman(542, False) == "DXLII"
|
||||
assert numberToRoman(999, False) == "CMXCIX"
|
||||
assert numberToRoman(2010, False) == "MMX"
|
||||
assert numberToRoman(999, True) == "cmxcix"
|
||||
Reference in New Issue
Block a user