From 8037b3a31125c697dfc8a41fea06d53f2950e724 Mon Sep 17 00:00:00 2001
From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com>
Date: Thu, 7 May 2020 00:38:25 +0200
Subject: [PATCH 1/9] Some improvements to the NWItem class, and removal of
unused imports of it in the code
---
nw/gui/elements/doctree.py | 2 +-
nw/guimain.py | 2 +-
nw/project/item.py | 47 +++++++++++++++++++-------------------
3 files changed, 26 insertions(+), 25 deletions(-)
diff --git a/nw/gui/elements/doctree.py b/nw/gui/elements/doctree.py
index b6858905..4fd08da8 100644
--- a/nw/gui/elements/doctree.py
+++ b/nw/gui/elements/doctree.py
@@ -34,7 +34,7 @@ from PyQt5.QtWidgets import (
QTreeWidget, QTreeWidgetItem, QAbstractItemView, QApplication, QMessageBox
)
-from nw.project import NWItem, NWDoc
+from nw.project import NWDoc
from nw.constants import (
nwLabels, nwItemType, nwItemClass, nwItemLayout, nwAlert
)
diff --git a/nw/guimain.py b/nw/guimain.py
index fb221ba2..47b533d6 100644
--- a/nw/guimain.py
+++ b/nw/guimain.py
@@ -45,7 +45,7 @@ from nw.gui import (
GuiConfigEditor, GuiProjectEditor, GuiItemEditor, GuiProjectOutline,
GuiSessionLogView, GuiDocMerge, GuiDocSplit, GuiProjectLoad
)
-from nw.project import NWProject, NWDoc, NWItem, NWIndex, NWBackup
+from nw.project import NWProject, NWDoc, NWIndex, NWBackup
from nw.tools import countWords
from nw.constants import nwFiles, nwItemType, nwAlert
diff --git a/nw/project/item.py b/nw/project/item.py
index 7843270a..3bd358af 100644
--- a/nw/project/item.py
+++ b/nw/project/item.py
@@ -57,6 +57,21 @@ class NWItem():
self.paraCount = 0
self.cursorPos = 0
+ # Map of Setters
+ self._setMap = {
+ "name" : self.setName,
+ "order" : self.setOrder,
+ "type" : self.setType,
+ "class" : self.setClass,
+ "layout" : self.setLayout,
+ "status" : self.setStatus,
+ "expanded" : self.setExpanded,
+ "charCount" : self.setCharCount,
+ "wordCount" : self.setWordCount,
+ "paraCount" : self.setParaCount,
+ "cursorPos" : self.setCursorPos,
+ }
+
return
##
@@ -64,6 +79,8 @@ class NWItem():
##
def packXML(self, xParent):
+ """Packs all the data in the class instance into an XML object.
+ """
xPack = etree.SubElement(xParent,"item",attrib={
"handle" : str(self.itemHandle),
"order" : str(self.itemOrder),
@@ -82,7 +99,8 @@ class NWItem():
xSub = self._subPack(xPack,"cursorPos", text=str(self.cursorPos), none=False)
return xPack
- def _subPack(self, xParent, name, attrib=None, text=None, none=True):
+ @staticmethod
+ def _subPack(xParent, name, attrib=None, text=None, none=True):
if not none and (text == None or text == "None"):
return None
xSub = etree.SubElement(xParent,name,attrib=attrib)
@@ -95,29 +113,12 @@ class NWItem():
##
def setFromTag(self, tagName, tagValue):
+ """Set a value from a given tag name rather than call the set
+ function directly. Useful when setting data read in from XML.
+ """
logger.verbose("Setting tag '%s' to value '%s'" % (tagName, str(tagValue)))
- if tagName == "name":
- self.setName(tagValue)
- elif tagName == "order":
- self.setOrder(tagValue)
- elif tagName == "type":
- self.setType(tagValue)
- elif tagName == "class":
- self.setClass(tagValue)
- elif tagName == "layout":
- self.setLayout(tagValue)
- elif tagName == "status":
- self.setStatus(tagValue)
- elif tagName == "expanded":
- self.setExpanded(tagValue)
- elif tagName == "charCount":
- self.setCharCount(tagValue)
- elif tagName == "wordCount":
- self.setWordCount(tagValue)
- elif tagName == "paraCount":
- self.setParaCount(tagValue)
- elif tagName == "cursorPos":
- self.setCursorPos(tagValue)
+ if tagName in self._setMap:
+ self._setMap[tagName](tagValue)
else:
logger.error("Unknown tag '%s'" % tagName)
return
From d5775a7838508ccb32a4bd5051a9847c54747ab0 Mon Sep 17 00:00:00 2001
From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com>
Date: Thu, 7 May 2020 00:59:05 +0200
Subject: [PATCH 2/9] Improved lookup of enums
---
nw/project/item.py | 33 ++++++++++++---------------------
1 file changed, 12 insertions(+), 21 deletions(-)
diff --git a/nw/project/item.py b/nw/project/item.py
index 3bd358af..f404eda7 100644
--- a/nw/project/item.py
+++ b/nw/project/item.py
@@ -146,40 +146,31 @@ class NWItem():
def setType(self, theType):
if isinstance(theType, nwItemType):
self.itemType = theType
- return
+ elif theType in nwItemType.__members__:
+ self.itemType = nwItemType[theType]
else:
- for itemType in nwItemType:
- if theType == itemType.name:
- self.itemType = itemType
- return
- logger.error("Unrecognised item type '%s'" % theType)
- self.itemType = nwItemType.NO_TYPE
+ logger.error("Unrecognised item type '%s'" % theType)
+ self.itemType = nwItemType.NO_TYPE
return
def setClass(self, theClass):
if isinstance(theClass, nwItemClass):
self.itemClass = theClass
- return
+ elif theClass in nwItemClass.__members__:
+ self.itemClass = nwItemClass[theClass]
else:
- for itemClass in nwItemClass:
- if theClass == itemClass.name:
- self.itemClass = itemClass
- return
- logger.error("Unrecognised item class '%s'" % theClass)
- self.itemClass = nwItemClass.NO_CLASS
+ logger.error("Unrecognised item class '%s'" % theClass)
+ self.itemClass = nwItemClass.NO_CLASS
return
def setLayout(self, theLayout):
if isinstance(theLayout, nwItemLayout):
self.itemLayout = theLayout
- return
+ elif theLayout in nwItemLayout.__members__:
+ self.itemLayout = nwItemLayout[theLayout]
else:
- for itemLayout in nwItemLayout:
- if theLayout == itemLayout.name:
- self.itemLayout = itemLayout
- return
- logger.error("Unrecognised item layout '%s'" % theLayout)
- self.itemLayout = nwItemLayout.NO_LAYOUT
+ logger.error("Unrecognised item layout '%s'" % theLayout)
+ self.itemLayout = nwItemLayout.NO_LAYOUT
return
def setStatus(self, theStatus):
From 1a521758d14e2f9febb9e194b7b5742f4ab18017 Mon Sep 17 00:00:00 2001
From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com>
Date: Thu, 7 May 2020 01:25:09 +0200
Subject: [PATCH 3/9] Moved the unpacking of XML data into the NWItem class
---
nw/project/item.py | 22 ++++++++++++++++++++++
nw/project/project.py | 15 ++-------------
2 files changed, 24 insertions(+), 13 deletions(-)
diff --git a/nw/project/item.py b/nw/project/item.py
index f404eda7..5295092e 100644
--- a/nw/project/item.py
+++ b/nw/project/item.py
@@ -99,6 +99,28 @@ class NWItem():
xSub = self._subPack(xPack,"cursorPos", text=str(self.cursorPos), none=False)
return xPack
+ def unpackXML(self, xItem):
+ """Sets the values from an XML entry of type 'item'.
+ """
+
+ if xItem.tag != "item":
+ logger.error("XML entry is not an NWItem")
+ return False
+
+ if "handle" in xItem.attrib:
+ self.itemHandle = xItem.attrib["handle"]
+ else:
+ logger.error("XML item entry does not have a handle")
+ return False
+
+ if "parent" in xItem.attrib:
+ self.parHandle = xItem.attrib["parent"]
+
+ for xValue in xItem:
+ self.setFromTag(xValue.tag, xValue.text)
+
+ return True
+
@staticmethod
def _subPack(xParent, name, attrib=None, text=None, none=True):
if not none and (text == None or text == "None"):
diff --git a/nw/project/project.py b/nw/project/project.py
index 45294564..674cba92 100644
--- a/nw/project/project.py
+++ b/nw/project/project.py
@@ -334,20 +334,9 @@ class NWProject():
elif xChild.tag == "content":
logger.debug("Found project content")
for xItem in xChild:
- itemAttrib = xItem.attrib
- if "handle" in xItem.attrib:
- tHandle = itemAttrib["handle"]
- else:
- logger.error("Skipping entry missing handle")
- continue
- if "parent" in xItem.attrib:
- pHandle = itemAttrib["parent"]
- else:
- pHandle = None
nwItem = NWItem(self)
- for xValue in xItem:
- nwItem.setFromTag(xValue.tag,xValue.text)
- self._appendItem(tHandle,pHandle,nwItem)
+ if nwItem.unpackXML(xItem):
+ self._appendItem(nwItem.itemHandle, nwItem.parHandle, nwItem)
self.optState.loadSettings()
From 3cb45d0dc375c99758b9e53dcf025e705c6e2e4b Mon Sep 17 00:00:00 2001
From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com>
Date: Thu, 7 May 2020 19:43:22 +0200
Subject: [PATCH 4/9] Added some more checks on item setters
---
nw/project/item.py | 84 ++++++++++++++++++++++------------------------
1 file changed, 40 insertions(+), 44 deletions(-)
diff --git a/nw/project/item.py b/nw/project/item.py
index 5295092e..f0462dbe 100644
--- a/nw/project/item.py
+++ b/nw/project/item.py
@@ -57,21 +57,6 @@ class NWItem():
self.paraCount = 0
self.cursorPos = 0
- # Map of Setters
- self._setMap = {
- "name" : self.setName,
- "order" : self.setOrder,
- "type" : self.setType,
- "class" : self.setClass,
- "layout" : self.setLayout,
- "status" : self.setStatus,
- "expanded" : self.setExpanded,
- "charCount" : self.setCharCount,
- "wordCount" : self.setWordCount,
- "paraCount" : self.setParaCount,
- "cursorPos" : self.setCursorPos,
- }
-
return
##
@@ -97,7 +82,7 @@ class NWItem():
xSub = self._subPack(xPack,"wordCount", text=str(self.wordCount), none=False)
xSub = self._subPack(xPack,"paraCount", text=str(self.paraCount), none=False)
xSub = self._subPack(xPack,"cursorPos", text=str(self.cursorPos), none=False)
- return xPack
+ return
def unpackXML(self, xItem):
"""Sets the values from an XML entry of type 'item'.
@@ -116,8 +101,24 @@ class NWItem():
if "parent" in xItem.attrib:
self.parHandle = xItem.attrib["parent"]
+ setMap = {
+ "name" : self.setName,
+ "order" : self.setOrder,
+ "type" : self.setType,
+ "class" : self.setClass,
+ "layout" : self.setLayout,
+ "status" : self.setStatus,
+ "expanded" : self.setExpanded,
+ "charCount" : self.setCharCount,
+ "wordCount" : self.setWordCount,
+ "paraCount" : self.setParaCount,
+ "cursorPos" : self.setCursorPos,
+ }
for xValue in xItem:
- self.setFromTag(xValue.tag, xValue.text)
+ if xValue.tag in setMap:
+ setMap[xValue.tag](xValue.text)
+ else:
+ logger.error("Unknown tag '%s'" % xValue.tag)
return True
@@ -130,21 +131,6 @@ class NWItem():
xSub.text = text
return xSub
- ##
- # Settings Wrapper
- ##
-
- def setFromTag(self, tagName, tagValue):
- """Set a value from a given tag name rather than call the set
- function directly. Useful when setting data read in from XML.
- """
- logger.verbose("Setting tag '%s' to value '%s'" % (tagName, str(tagValue)))
- if tagName in self._setMap:
- self._setMap[tagName](tagValue)
- else:
- logger.error("Unknown tag '%s'" % tagName)
- return
-
##
# Set Item Values
##
@@ -154,15 +140,29 @@ class NWItem():
return
def setHandle(self, theHandle):
- self.itemHandle = theHandle
+ if isinstance(theHandle, str):
+ if len(theHandle) == 13:
+ self.itemHandle = theHandle
+ else:
+ self.itemHandle = None
+ else:
+ self.itemHandle = None
return
def setParent(self, theParent):
- self.parHandle = theParent
+ if theParent is None:
+ self.parHandle = None
+ elif isinstance(theParent, str):
+ if len(theParent) == 13:
+ self.parHandle = theParent
+ else:
+ self.parHandle = None
+ else:
+ self.parHandle = None
return
def setOrder(self, theOrder):
- self.itemOrder = theOrder
+ self.itemOrder = checkInt(theOrder, 0)
return
def setType(self, theType):
@@ -206,7 +206,7 @@ class NWItem():
if isinstance(expState, str):
self.isExpanded = expState == str(True)
else:
- self.isExpanded = expState
+ self.isExpanded = expState == True
return
##
@@ -214,23 +214,19 @@ class NWItem():
##
def setCharCount(self, theCount):
- theCount = checkInt(theCount,0)
- self.charCount = theCount
+ self.charCount = checkInt(theCount,0)
return
def setWordCount(self, theCount):
- theCount = checkInt(theCount,0)
- self.wordCount = theCount
+ self.wordCount = checkInt(theCount,0)
return
def setParaCount(self, theCount):
- theCount = checkInt(theCount,0)
- self.paraCount = theCount
+ self.paraCount = checkInt(theCount,0)
return
def setCursorPos(self, thePosition):
- thePosition = checkInt(thePosition,0)
- self.cursorPos = thePosition
+ self.cursorPos = checkInt(thePosition,0)
return
# END Class NWItem
From 323b46a5590692db89ea66eaf067bf608757e3bf Mon Sep 17 00:00:00 2001
From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com>
Date: Thu, 7 May 2020 19:43:55 +0200
Subject: [PATCH 5/9] Added direct testing of the NWItem class
---
tests/test_item.py | 218 ++++++++++++++++++++++++++++++++++++++++++
tests/test_project.py | 10 +-
2 files changed, 223 insertions(+), 5 deletions(-)
create mode 100644 tests/test_item.py
diff --git a/tests/test_item.py b/tests/test_item.py
new file mode 100644
index 00000000..101eda79
--- /dev/null
+++ b/tests/test_item.py
@@ -0,0 +1,218 @@
+# -*- coding: utf-8 -*-
+"""novelWriter NWItem Class Tester
+"""
+
+import nw
+import pytest
+
+from lxml import etree
+from nwdummy import DummyMain
+
+from nw.config import Config
+from nw.project.project import NWProject
+from nw.project.item import NWItem
+from nw.constants import nwItemClass, nwItemType, nwItemLayout
+
+theConf = Config()
+theMain = DummyMain()
+theMain.mainConf = theConf
+
+theProject = NWProject(theMain)
+theItem = NWItem(theProject)
+nwXML = etree.Element("novelWriterXML")
+
+@pytest.mark.project
+def testItemSettersSimple():
+
+ # Name
+ theItem.setName("A Name")
+ assert theItem.itemName == "A Name"
+ theItem.setName("\t A Name ")
+ assert theItem.itemName == "A Name"
+
+ # Handle
+ theItem.setHandle(123)
+ assert theItem.itemHandle is None
+ theItem.setHandle("0123456789abcdef")
+ assert theItem.itemHandle is None
+ theItem.setHandle("0123456789abc")
+ assert theItem.itemHandle == "0123456789abc"
+
+ # Parent
+ theItem.setParent(None)
+ assert theItem.parHandle is None
+ theItem.setParent(123)
+ assert theItem.parHandle is None
+ theItem.setParent("0123456789abcdef")
+ assert theItem.parHandle is None
+ theItem.setParent("0123456789abc")
+ assert theItem.parHandle == "0123456789abc"
+
+ # Order
+ theItem.setOrder(None)
+ assert theItem.itemOrder == 0
+ theItem.setOrder("1")
+ assert theItem.itemOrder == 1
+ theItem.setOrder(1)
+ assert theItem.itemOrder == 1
+
+ # Status
+ theItem.setStatus("Nonsense")
+ assert theItem.itemStatus == "New"
+ theItem.setStatus("New")
+ assert theItem.itemStatus == "New"
+ theItem.setStatus("Minor")
+ assert theItem.itemStatus == "Minor"
+ theItem.setStatus("Major")
+ assert theItem.itemStatus == "Major"
+ theItem.setStatus("Main")
+ assert theItem.itemStatus == "Main"
+
+ # Expanded
+ theItem.setExpanded(8)
+ assert not theItem.isExpanded
+ theItem.setExpanded(None)
+ assert not theItem.isExpanded
+ theItem.setExpanded("None")
+ assert not theItem.isExpanded
+ theItem.setExpanded("What?")
+ assert not theItem.isExpanded
+ theItem.setExpanded("True")
+ assert theItem.isExpanded
+ theItem.setExpanded(True)
+ assert theItem.isExpanded
+
+ # CharCount
+ theItem.setCharCount(None)
+ assert theItem.charCount == 0
+ theItem.setCharCount("1")
+ assert theItem.charCount == 1
+ theItem.setCharCount(1)
+ assert theItem.charCount == 1
+
+ # WordCount
+ theItem.setWordCount(None)
+ assert theItem.wordCount == 0
+ theItem.setWordCount("1")
+ assert theItem.wordCount == 1
+ theItem.setWordCount(1)
+ assert theItem.wordCount == 1
+
+ # ParaCount
+ theItem.setParaCount(None)
+ assert theItem.paraCount == 0
+ theItem.setParaCount("1")
+ assert theItem.paraCount == 1
+ theItem.setParaCount(1)
+ assert theItem.paraCount == 1
+
+ # CursorPos
+ theItem.setCursorPos(None)
+ assert theItem.cursorPos == 0
+ theItem.setCursorPos("1")
+ assert theItem.cursorPos == 1
+ theItem.setCursorPos(1)
+ assert theItem.cursorPos == 1
+
+@pytest.mark.project
+def testItemClassSetter():
+
+ # Class
+ theItem.setClass(None)
+ assert theItem.itemClass == nwItemClass.NO_CLASS
+ theItem.setClass("NONSENSE")
+ assert theItem.itemClass == nwItemClass.NO_CLASS
+ theItem.setClass("NO_CLASS")
+ assert theItem.itemClass == nwItemClass.NO_CLASS
+ theItem.setClass("NOVEL")
+ assert theItem.itemClass == nwItemClass.NOVEL
+ theItem.setClass("PLOT")
+ assert theItem.itemClass == nwItemClass.PLOT
+ theItem.setClass("CHARACTER")
+ assert theItem.itemClass == nwItemClass.CHARACTER
+ theItem.setClass("WORLD")
+ assert theItem.itemClass == nwItemClass.WORLD
+ theItem.setClass("TIMELINE")
+ assert theItem.itemClass == nwItemClass.TIMELINE
+ theItem.setClass("OBJECT")
+ assert theItem.itemClass == nwItemClass.OBJECT
+ theItem.setClass("ENTITY")
+ assert theItem.itemClass == nwItemClass.ENTITY
+ theItem.setClass("CUSTOM")
+ assert theItem.itemClass == nwItemClass.CUSTOM
+ theItem.setClass("TRASH")
+ assert theItem.itemClass == nwItemClass.TRASH
+
+@pytest.mark.project
+def testItemTypeSetter():
+
+ # Class
+ theItem.setType(None)
+ assert theItem.itemType == nwItemType.NO_TYPE
+ theItem.setType("NONSENSE")
+ assert theItem.itemType == nwItemType.NO_TYPE
+ theItem.setType("NO_TYPE")
+ assert theItem.itemType == nwItemType.NO_TYPE
+ theItem.setType("ROOT")
+ assert theItem.itemType == nwItemType.ROOT
+ theItem.setType("FOLDER")
+ assert theItem.itemType == nwItemType.FOLDER
+ theItem.setType("FILE")
+ assert theItem.itemType == nwItemType.FILE
+ theItem.setType("TRASH")
+ assert theItem.itemType == nwItemType.TRASH
+
+@pytest.mark.project
+def testItemLayoutSetter():
+
+ # Class
+ theItem.setLayout(None)
+ assert theItem.itemLayout == nwItemLayout.NO_LAYOUT
+ theItem.setLayout("NONSENSE")
+ assert theItem.itemLayout == nwItemLayout.NO_LAYOUT
+ theItem.setLayout("NO_LAYOUT")
+ assert theItem.itemLayout == nwItemLayout.NO_LAYOUT
+ theItem.setLayout("TITLE")
+ assert theItem.itemLayout == nwItemLayout.TITLE
+ theItem.setLayout("BOOK")
+ assert theItem.itemLayout == nwItemLayout.BOOK
+ theItem.setLayout("PAGE")
+ assert theItem.itemLayout == nwItemLayout.PAGE
+ theItem.setLayout("PARTITION")
+ assert theItem.itemLayout == nwItemLayout.PARTITION
+ theItem.setLayout("UNNUMBERED")
+ assert theItem.itemLayout == nwItemLayout.UNNUMBERED
+ theItem.setLayout("CHAPTER")
+ assert theItem.itemLayout == nwItemLayout.CHAPTER
+ theItem.setLayout("SCENE")
+ assert theItem.itemLayout == nwItemLayout.SCENE
+ theItem.setLayout("NOTE")
+ assert theItem.itemLayout == nwItemLayout.NOTE
+
+@pytest.mark.project
+def testItemXMLPackUnpack():
+
+ # Pack
+ xContent = etree.SubElement(nwXML, "content")
+ theItem.packXML(xContent)
+ assert etree.tostring(xContent, pretty_print=False, encoding="utf-8") == (
+ b""
+ b"- "
+ b"A NameTRASHTRASHMainTrue"
+ b"
"
+ b""
+ )
+
+ # Unpack
+ assert theItem.unpackXML(xContent[0])
+ assert theItem.itemHandle == "0123456789abc"
+ assert theItem.parHandle == "0123456789abc"
+ assert theItem.itemOrder == 1
+ assert theItem.isExpanded
+ assert theItem.charCount == 1
+ assert theItem.wordCount == 1
+ assert theItem.paraCount == 1
+ assert theItem.cursorPos == 1
+ assert theItem.itemClass == nwItemClass.TRASH
+ assert theItem.itemType == nwItemType.TRASH
+ assert theItem.itemLayout == nwItemLayout.NOTE
diff --git a/tests/test_project.py b/tests/test_project.py
index a38d6d58..81301c4d 100644
--- a/tests/test_project.py
+++ b/tests/test_project.py
@@ -2,17 +2,17 @@
"""novelWriter Project Class Tester
"""
-import nw, pytest, types
+import nw
+import pytest
from os import path
from nwtools import *
from nwdummy import DummyMain
-from nw.config import Config
+from nw.config import Config
from nw.project.project import NWProject
-from nw.project.item import NWItem
-from nw.project.index import NWIndex
-from nw.constants import nwItemClass
+from nw.project.index import NWIndex
+from nw.constants import nwItemClass
theConf = Config()
theMain = DummyMain()
From 64ae52e7aa48a742c01810d33aacc1919e94d2d3 Mon Sep 17 00:00:00 2001
From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com>
Date: Thu, 7 May 2020 20:02:49 +0200
Subject: [PATCH 6/9] Moved NWItem and NWStatus into the project file as the
classes are no longer called from elsewhere
---
nw/project/__init__.py | 4 -
nw/project/item.py | 232 ---------------------------
nw/project/project.py | 345 ++++++++++++++++++++++++++++++++++++++++-
nw/project/status.py | 170 --------------------
tests/test_item.py | 3 +-
5 files changed, 344 insertions(+), 410 deletions(-)
delete mode 100644 nw/project/item.py
delete mode 100644 nw/project/status.py
diff --git a/nw/project/__init__.py b/nw/project/__init__.py
index d7065c78..b36f5a5e 100644
--- a/nw/project/__init__.py
+++ b/nw/project/__init__.py
@@ -3,15 +3,11 @@
from nw.project.backup import NWBackup
from nw.project.document import NWDoc
from nw.project.index import NWIndex
-from nw.project.item import NWItem
from nw.project.project import NWProject
-from nw.project.status import NWStatus
__all__ = [
"NWBackup",
"NWDoc",
"NWIndex",
- "NWItem",
"NWProject",
- "NWStatus",
]
diff --git a/nw/project/item.py b/nw/project/item.py
deleted file mode 100644
index f0462dbe..00000000
--- a/nw/project/item.py
+++ /dev/null
@@ -1,232 +0,0 @@
-# -*- coding: utf-8 -*-
-"""novelWriter Project Item
-
- novelWriter – Project Item
-============================
- Class holding a project item
-
- File History:
- Created: 2018-10-27 [0.0.1]
-
- This file is a part of novelWriter
- Copyright 2020, Veronica Berglyd Olsen
-
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation, either version 3 of the License, or
- (at your option) any later version.
-
- This program is distributed in the hope that it will be useful, but
- WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with this program. If not, see .
-"""
-
-import logging
-import nw
-
-from lxml import etree
-
-from nw.common import checkInt
-from nw.constants import nwItemType, nwItemClass, nwItemLayout
-
-logger = logging.getLogger(__name__)
-
-class NWItem():
-
- def __init__(self, theProject):
-
- self.theProject = theProject
-
- self.itemName = ""
- self.itemHandle = None
- self.parHandle = None
- self.itemOrder = None
- self.itemType = nwItemType.NO_TYPE
- self.itemClass = nwItemClass.NO_CLASS
- self.itemLayout = nwItemLayout.NO_LAYOUT
- self.itemStatus = None
- self.isExpanded = False
-
- # Document Meta Data
- self.charCount = 0
- self.wordCount = 0
- self.paraCount = 0
- self.cursorPos = 0
-
- return
-
- ##
- # XML Pack
- ##
-
- def packXML(self, xParent):
- """Packs all the data in the class instance into an XML object.
- """
- xPack = etree.SubElement(xParent,"item",attrib={
- "handle" : str(self.itemHandle),
- "order" : str(self.itemOrder),
- "parent" : str(self.parHandle),
- })
- xSub = self._subPack(xPack,"name", text=str(self.itemName))
- xSub = self._subPack(xPack,"type", text=str(self.itemType.name))
- xSub = self._subPack(xPack,"class", text=str(self.itemClass.name))
- xSub = self._subPack(xPack,"status", text=str(self.itemStatus))
- xSub = self._subPack(xPack,"expanded", text=str(self.isExpanded))
- if self.itemType == nwItemType.FILE:
- xSub = self._subPack(xPack,"layout", text=str(self.itemLayout.name))
- xSub = self._subPack(xPack,"charCount", text=str(self.charCount), none=False)
- xSub = self._subPack(xPack,"wordCount", text=str(self.wordCount), none=False)
- xSub = self._subPack(xPack,"paraCount", text=str(self.paraCount), none=False)
- xSub = self._subPack(xPack,"cursorPos", text=str(self.cursorPos), none=False)
- return
-
- def unpackXML(self, xItem):
- """Sets the values from an XML entry of type 'item'.
- """
-
- if xItem.tag != "item":
- logger.error("XML entry is not an NWItem")
- return False
-
- if "handle" in xItem.attrib:
- self.itemHandle = xItem.attrib["handle"]
- else:
- logger.error("XML item entry does not have a handle")
- return False
-
- if "parent" in xItem.attrib:
- self.parHandle = xItem.attrib["parent"]
-
- setMap = {
- "name" : self.setName,
- "order" : self.setOrder,
- "type" : self.setType,
- "class" : self.setClass,
- "layout" : self.setLayout,
- "status" : self.setStatus,
- "expanded" : self.setExpanded,
- "charCount" : self.setCharCount,
- "wordCount" : self.setWordCount,
- "paraCount" : self.setParaCount,
- "cursorPos" : self.setCursorPos,
- }
- for xValue in xItem:
- if xValue.tag in setMap:
- setMap[xValue.tag](xValue.text)
- else:
- logger.error("Unknown tag '%s'" % xValue.tag)
-
- return True
-
- @staticmethod
- def _subPack(xParent, name, attrib=None, text=None, none=True):
- if not none and (text == None or text == "None"):
- return None
- xSub = etree.SubElement(xParent,name,attrib=attrib)
- if text is not None:
- xSub.text = text
- return xSub
-
- ##
- # Set Item Values
- ##
-
- def setName(self, theName):
- self.itemName = theName.strip()
- return
-
- def setHandle(self, theHandle):
- if isinstance(theHandle, str):
- if len(theHandle) == 13:
- self.itemHandle = theHandle
- else:
- self.itemHandle = None
- else:
- self.itemHandle = None
- return
-
- def setParent(self, theParent):
- if theParent is None:
- self.parHandle = None
- elif isinstance(theParent, str):
- if len(theParent) == 13:
- self.parHandle = theParent
- else:
- self.parHandle = None
- else:
- self.parHandle = None
- return
-
- def setOrder(self, theOrder):
- self.itemOrder = checkInt(theOrder, 0)
- return
-
- def setType(self, theType):
- if isinstance(theType, nwItemType):
- self.itemType = theType
- elif theType in nwItemType.__members__:
- self.itemType = nwItemType[theType]
- else:
- logger.error("Unrecognised item type '%s'" % theType)
- self.itemType = nwItemType.NO_TYPE
- return
-
- def setClass(self, theClass):
- if isinstance(theClass, nwItemClass):
- self.itemClass = theClass
- elif theClass in nwItemClass.__members__:
- self.itemClass = nwItemClass[theClass]
- else:
- logger.error("Unrecognised item class '%s'" % theClass)
- self.itemClass = nwItemClass.NO_CLASS
- return
-
- def setLayout(self, theLayout):
- if isinstance(theLayout, nwItemLayout):
- self.itemLayout = theLayout
- elif theLayout in nwItemLayout.__members__:
- self.itemLayout = nwItemLayout[theLayout]
- else:
- logger.error("Unrecognised item layout '%s'" % theLayout)
- self.itemLayout = nwItemLayout.NO_LAYOUT
- return
-
- def setStatus(self, theStatus):
- if self.itemClass == nwItemClass.NOVEL:
- self.itemStatus = self.theProject.statusItems.checkEntry(theStatus)
- else:
- self.itemStatus = self.theProject.importItems.checkEntry(theStatus)
- return
-
- def setExpanded(self, expState):
- if isinstance(expState, str):
- self.isExpanded = expState == str(True)
- else:
- self.isExpanded = expState == True
- return
-
- ##
- # Set Document Meta Data
- ##
-
- def setCharCount(self, theCount):
- self.charCount = checkInt(theCount,0)
- return
-
- def setWordCount(self, theCount):
- self.wordCount = checkInt(theCount,0)
- return
-
- def setParaCount(self, theCount):
- self.paraCount = checkInt(theCount,0)
- return
-
- def setCursorPos(self, thePosition):
- self.cursorPos = checkInt(thePosition,0)
- return
-
-# END Class NWItem
diff --git a/nw/project/project.py b/nw/project/project.py
index 674cba92..9ee3844f 100644
--- a/nw/project/project.py
+++ b/nw/project/project.py
@@ -7,6 +7,8 @@
File History:
Created: 2018-09-29 [0.0.1]
+ Merged: 2020-05-07 [0.4.5] Merged NWItem class into file
+ Merged: 2020-05-07 [0.4.5] Merged NWStatus class into file
This file is a part of novelWriter
Copyright 2020, Veronica Berglyd Olsen
@@ -34,8 +36,6 @@ from hashlib import sha256
from datetime import datetime
from time import time
-from nw.project.status import NWStatus
-from nw.project.item import NWItem
from nw.tools import projectMaintenance, OptionState
from nw.common import checkString, checkBool, checkInt
from nw.constants import (
@@ -934,3 +934,344 @@ class NWProject():
return itemHandle
# END Class NWProject
+
+# ================================================================================================ #
+# NWItem
+# Class holding the project items making up the NWProject
+# ================================================================================================ #
+
+class NWItem():
+
+ def __init__(self, theProject):
+
+ self.theProject = theProject
+
+ self.itemName = ""
+ self.itemHandle = None
+ self.parHandle = None
+ self.itemOrder = None
+ self.itemType = nwItemType.NO_TYPE
+ self.itemClass = nwItemClass.NO_CLASS
+ self.itemLayout = nwItemLayout.NO_LAYOUT
+ self.itemStatus = None
+ self.isExpanded = False
+
+ # Document Meta Data
+ self.charCount = 0
+ self.wordCount = 0
+ self.paraCount = 0
+ self.cursorPos = 0
+
+ return
+
+ ##
+ # XML Pack
+ ##
+
+ def packXML(self, xParent):
+ """Packs all the data in the class instance into an XML object.
+ """
+ xPack = etree.SubElement(xParent,"item",attrib={
+ "handle" : str(self.itemHandle),
+ "order" : str(self.itemOrder),
+ "parent" : str(self.parHandle),
+ })
+ xSub = self._subPack(xPack,"name", text=str(self.itemName))
+ xSub = self._subPack(xPack,"type", text=str(self.itemType.name))
+ xSub = self._subPack(xPack,"class", text=str(self.itemClass.name))
+ xSub = self._subPack(xPack,"status", text=str(self.itemStatus))
+ xSub = self._subPack(xPack,"expanded", text=str(self.isExpanded))
+ if self.itemType == nwItemType.FILE:
+ xSub = self._subPack(xPack,"layout", text=str(self.itemLayout.name))
+ xSub = self._subPack(xPack,"charCount", text=str(self.charCount), none=False)
+ xSub = self._subPack(xPack,"wordCount", text=str(self.wordCount), none=False)
+ xSub = self._subPack(xPack,"paraCount", text=str(self.paraCount), none=False)
+ xSub = self._subPack(xPack,"cursorPos", text=str(self.cursorPos), none=False)
+ return
+
+ def unpackXML(self, xItem):
+ """Sets the values from an XML entry of type 'item'.
+ """
+
+ if xItem.tag != "item":
+ logger.error("XML entry is not an NWItem")
+ return False
+
+ if "handle" in xItem.attrib:
+ self.itemHandle = xItem.attrib["handle"]
+ else:
+ logger.error("XML item entry does not have a handle")
+ return False
+
+ if "parent" in xItem.attrib:
+ self.parHandle = xItem.attrib["parent"]
+
+ setMap = {
+ "name" : self.setName,
+ "order" : self.setOrder,
+ "type" : self.setType,
+ "class" : self.setClass,
+ "layout" : self.setLayout,
+ "status" : self.setStatus,
+ "expanded" : self.setExpanded,
+ "charCount" : self.setCharCount,
+ "wordCount" : self.setWordCount,
+ "paraCount" : self.setParaCount,
+ "cursorPos" : self.setCursorPos,
+ }
+ for xValue in xItem:
+ if xValue.tag in setMap:
+ setMap[xValue.tag](xValue.text)
+ else:
+ logger.error("Unknown tag '%s'" % xValue.tag)
+
+ return True
+
+ @staticmethod
+ def _subPack(xParent, name, attrib=None, text=None, none=True):
+ if not none and (text == None or text == "None"):
+ return None
+ xSub = etree.SubElement(xParent,name,attrib=attrib)
+ if text is not None:
+ xSub.text = text
+ return xSub
+
+ ##
+ # Set Item Values
+ ##
+
+ def setName(self, theName):
+ self.itemName = theName.strip()
+ return
+
+ def setHandle(self, theHandle):
+ if isinstance(theHandle, str):
+ if len(theHandle) == 13:
+ self.itemHandle = theHandle
+ else:
+ self.itemHandle = None
+ else:
+ self.itemHandle = None
+ return
+
+ def setParent(self, theParent):
+ if theParent is None:
+ self.parHandle = None
+ elif isinstance(theParent, str):
+ if len(theParent) == 13:
+ self.parHandle = theParent
+ else:
+ self.parHandle = None
+ else:
+ self.parHandle = None
+ return
+
+ def setOrder(self, theOrder):
+ self.itemOrder = checkInt(theOrder, 0)
+ return
+
+ def setType(self, theType):
+ if isinstance(theType, nwItemType):
+ self.itemType = theType
+ elif theType in nwItemType.__members__:
+ self.itemType = nwItemType[theType]
+ else:
+ logger.error("Unrecognised item type '%s'" % theType)
+ self.itemType = nwItemType.NO_TYPE
+ return
+
+ def setClass(self, theClass):
+ if isinstance(theClass, nwItemClass):
+ self.itemClass = theClass
+ elif theClass in nwItemClass.__members__:
+ self.itemClass = nwItemClass[theClass]
+ else:
+ logger.error("Unrecognised item class '%s'" % theClass)
+ self.itemClass = nwItemClass.NO_CLASS
+ return
+
+ def setLayout(self, theLayout):
+ if isinstance(theLayout, nwItemLayout):
+ self.itemLayout = theLayout
+ elif theLayout in nwItemLayout.__members__:
+ self.itemLayout = nwItemLayout[theLayout]
+ else:
+ logger.error("Unrecognised item layout '%s'" % theLayout)
+ self.itemLayout = nwItemLayout.NO_LAYOUT
+ return
+
+ def setStatus(self, theStatus):
+ if self.itemClass == nwItemClass.NOVEL:
+ self.itemStatus = self.theProject.statusItems.checkEntry(theStatus)
+ else:
+ self.itemStatus = self.theProject.importItems.checkEntry(theStatus)
+ return
+
+ def setExpanded(self, expState):
+ if isinstance(expState, str):
+ self.isExpanded = expState == str(True)
+ else:
+ self.isExpanded = expState == True
+ return
+
+ ##
+ # Set Document Meta Data
+ ##
+
+ def setCharCount(self, theCount):
+ self.charCount = checkInt(theCount,0)
+ return
+
+ def setWordCount(self, theCount):
+ self.wordCount = checkInt(theCount,0)
+ return
+
+ def setParaCount(self, theCount):
+ self.paraCount = checkInt(theCount,0)
+ return
+
+ def setCursorPos(self, thePosition):
+ self.cursorPos = checkInt(thePosition,0)
+ return
+
+# END Class NWItem
+
+# ================================================================================================ #
+# NWStatus
+# Class holding the item status values stored in the NWProject
+# ================================================================================================ #
+
+class NWStatus():
+
+ def __init__(self):
+ self.theLabels = []
+ self.theColours = []
+ self.theCounts = []
+ self.theMap = {}
+ self.theLength = 0
+ self.theIndex = 0
+ return
+
+ def addEntry(self, theLabel, theColours):
+ 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
+ return True
+
+ def lookupEntry(self, theLabel):
+ if theLabel is None:
+ return None
+ theLabel = theLabel.strip()
+ if theLabel in self.theMap.keys():
+ return self.theMap[theLabel]
+ return None
+
+ def checkEntry(self, theStatus):
+ if isinstance(theStatus, str):
+ theStatus = theStatus.strip()
+ 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]
+
+ def setNewEntries(self, newList):
+
+ replaceMap = {}
+
+ if newList is not None:
+
+ 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))
+ if nName != oName and oName is not None:
+ replaceMap[oName] = nName
+
+ return replaceMap
+
+ def resetCounts(self):
+ self.theCounts = [0]*self.theLength
+ return
+
+ def countEntry(self, theLabel):
+ theIndex = self.lookupEntry(theLabel)
+ if theIndex is not None:
+ self.theCounts[theIndex] += 1
+ return
+
+ def packEntries(self, xParent):
+ 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]),
+ })
+ xSub.text = self.theLabels[n]
+ return True
+
+ def unpackEntries(self, xParent):
+
+ theLabels = []
+ theColours = []
+
+ for xChild in xParent:
+ theLabels.append(xChild.text)
+ if "red" in xChild.attrib:
+ cR = checkInt(xChild.attrib["red"],0,False)
+ else:
+ cR = 0
+ if "green" in xChild.attrib:
+ cG = checkInt(xChild.attrib["green"],0,False)
+ else:
+ cG = 0
+ if "blue" in xChild.attrib:
+ cB = checkInt(xChild.attrib["blue"],0,False)
+ else:
+ cB = 0
+ theColours.append((cR,cG,cB))
+
+ if len(theLabels) > 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])
+
+ return True
+
+ ##
+ # Iterator Bits
+ ##
+
+ def __getitem__(self, 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):
+ self.theIndex = 0
+ return self
+
+ def __next__(self):
+ if self.theIndex < self.theLength:
+ theLabel, theColour, theCount = self.__getitem__(self.theIndex)
+ self.theIndex += 1
+ return theLabel, theColour, theCount
+ else:
+ raise StopIteration
+
+# END Class NWStatus
diff --git a/nw/project/status.py b/nw/project/status.py
deleted file mode 100644
index c7771f3d..00000000
--- a/nw/project/status.py
+++ /dev/null
@@ -1,170 +0,0 @@
-# -*- coding: utf-8 -*-
-"""novelWriter Item Status
-
- novelWriter – Item Status
-===========================
- Class holding the project's item statuses
-
- File History:
- Created: 2019-05-19 [0.1.3]
-
- This file is a part of novelWriter
- Copyright 2020, Veronica Berglyd Olsen
-
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation, either version 3 of the License, or
- (at your option) any later version.
-
- This program is distributed in the hope that it will be useful, but
- WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with this program. If not, see .
-"""
-
-import logging
-import nw
-
-from lxml import etree
-
-from nw.common import checkInt
-
-logger = logging.getLogger(__name__)
-
-class NWStatus():
-
- def __init__(self):
- self.theLabels = []
- self.theColours = []
- self.theCounts = []
- self.theMap = {}
- self.theLength = 0
- self.theIndex = 0
- return
-
- def addEntry(self, theLabel, theColours):
- 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
- return True
-
- def lookupEntry(self, theLabel):
- if theLabel is None:
- return None
- theLabel = theLabel.strip()
- if theLabel in self.theMap.keys():
- return self.theMap[theLabel]
- return None
-
- def checkEntry(self, theStatus):
- if isinstance(theStatus, str):
- theStatus = theStatus.strip()
- 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]
-
- def setNewEntries(self, newList):
-
- replaceMap = {}
-
- if newList is not None:
-
- 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))
- if nName != oName and oName is not None:
- replaceMap[oName] = nName
-
- return replaceMap
-
- def resetCounts(self):
- self.theCounts = [0]*self.theLength
- return
-
- def countEntry(self, theLabel):
- theIndex = self.lookupEntry(theLabel)
- if theIndex is not None:
- self.theCounts[theIndex] += 1
- return
-
- def packEntries(self, xParent):
- 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]),
- })
- xSub.text = self.theLabels[n]
- return True
-
- def unpackEntries(self, xParent):
-
- theLabels = []
- theColours = []
-
- for xChild in xParent:
- theLabels.append(xChild.text)
- if "red" in xChild.attrib:
- cR = checkInt(xChild.attrib["red"],0,False)
- else:
- cR = 0
- if "green" in xChild.attrib:
- cG = checkInt(xChild.attrib["green"],0,False)
- else:
- cG = 0
- if "blue" in xChild.attrib:
- cB = checkInt(xChild.attrib["blue"],0,False)
- else:
- cB = 0
- theColours.append((cR,cG,cB))
-
- if len(theLabels) > 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])
-
- return True
-
- ##
- # Iterator Bits
- ##
-
- def __getitem__(self, 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):
- self.theIndex = 0
- return self
-
- def __next__(self):
- if self.theIndex < self.theLength:
- theLabel, theColour, theCount = self.__getitem__(self.theIndex)
- self.theIndex += 1
- return theLabel, theColour, theCount
- else:
- raise StopIteration
-
-# END Class NWStatus
diff --git a/tests/test_item.py b/tests/test_item.py
index 101eda79..b58f1b7e 100644
--- a/tests/test_item.py
+++ b/tests/test_item.py
@@ -9,8 +9,7 @@ from lxml import etree
from nwdummy import DummyMain
from nw.config import Config
-from nw.project.project import NWProject
-from nw.project.item import NWItem
+from nw.project.project import NWProject, NWItem
from nw.constants import nwItemClass, nwItemType, nwItemLayout
theConf = Config()
From 58eb8e78ba967cb54eb093dec936d53145f6b8c0 Mon Sep 17 00:00:00 2001
From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com>
Date: Thu, 7 May 2020 23:08:15 +0200
Subject: [PATCH 7/9] Moved the project tree into its own iterable class NWTree
---
nw/convert/file/text.py | 4 +-
nw/convert/tokenizer.py | 2 +-
nw/gui/dialogs/docmerge.py | 6 +-
nw/gui/dialogs/docsplit.py | 6 +-
nw/gui/dialogs/export.py | 8 +-
nw/gui/dialogs/itemeditor.py | 2 +-
nw/gui/elements/docdetails.py | 2 +-
nw/gui/elements/doctitlebar.py | 4 +-
nw/gui/elements/doctree.py | 53 ++---
nw/gui/elements/docviewer.py | 2 +-
nw/gui/elements/outline.py | 2 +-
nw/gui/elements/viewdetails.py | 2 +-
nw/gui/mainmenu.py | 2 +-
nw/gui/tools/dochighlight.py | 2 +-
nw/guimain.py | 26 +--
nw/project/document.py | 4 +-
nw/project/index.py | 8 +-
nw/project/project.py | 383 +++++++++++++++++++++++----------
18 files changed, 342 insertions(+), 176 deletions(-)
diff --git a/nw/convert/file/text.py b/nw/convert/file/text.py
index f5119781..ceab8d55 100644
--- a/nw/convert/file/text.py
+++ b/nw/convert/file/text.py
@@ -164,12 +164,12 @@ class TextFile():
* Items that appear in the TRASH folder
"""
- theItem = self.theProject.getItem(tHandle)
+ theItem = self.theProject.projTree[tHandle]
isNone = theItem.itemType != nwItemType.FILE
isNone |= theItem.itemLayout == nwItemLayout.NO_LAYOUT
isNone |= theItem.itemClass == nwItemClass.NO_CLASS
isNone |= theItem.itemClass == nwItemClass.TRASH
- isNone |= theItem.parHandle == self.theProject.trashRoot
+ isNone |= theItem.parHandle == self.theProject.projTree.trashRoot()
isNote = theItem.itemLayout == nwItemLayout.NOTE
isNovel = not isNone and not isNote
diff --git a/nw/convert/tokenizer.py b/nw/convert/tokenizer.py
index 53f44958..f891a9f7 100644
--- a/nw/convert/tokenizer.py
+++ b/nw/convert/tokenizer.py
@@ -142,7 +142,7 @@ class Tokenizer():
def setText(self, theHandle, theText=None):
self.theHandle = theHandle
- self.theItem = self.theProject.getItem(theHandle)
+ self.theItem = self.theProject.projTree[theHandle]
if theText is not None:
# If the text is set, just use that
diff --git a/nw/gui/dialogs/docmerge.py b/nw/gui/dialogs/docmerge.py
index 02bcbf5b..887e8b72 100644
--- a/nw/gui/dialogs/docmerge.py
+++ b/nw/gui/dialogs/docmerge.py
@@ -115,7 +115,7 @@ class GuiDocMerge(QDialog):
), nwAlert.ERROR)
return
- srcItem = self.theProject.getItem(self.sourceItem)
+ srcItem = self.theProject.projTree[self.sourceItem]
nHandle = self.theProject.newFile(srcItem.itemName, srcItem.itemClass, srcItem.parHandle)
self.theParent.treeView.revealTreeItem(nHandle)
theDoc.openDocument(nHandle, False)
@@ -149,7 +149,7 @@ class GuiDocMerge(QDialog):
if tHandle is None:
return
- nwItem = self.theProject.getItem(tHandle)
+ nwItem = self.theProject.projTree[tHandle]
if nwItem is None:
return
if nwItem.itemType is not nwItemType.FOLDER:
@@ -160,7 +160,7 @@ class GuiDocMerge(QDialog):
for sHandle in self.theParent.treeView.getTreeFromHandle(tHandle):
newItem = QListWidgetItem()
- nwItem = self.theProject.getItem(sHandle)
+ nwItem = self.theProject.projTree[sHandle]
if nwItem.itemType is not nwItemType.FILE:
continue
newItem.setText(nwItem.itemName)
diff --git a/nw/gui/dialogs/docsplit.py b/nw/gui/dialogs/docsplit.py
index 3a2b332e..55be4ee4 100644
--- a/nw/gui/dialogs/docsplit.py
+++ b/nw/gui/dialogs/docsplit.py
@@ -120,7 +120,7 @@ class GuiDocSplit(QDialog):
), nwAlert.ERROR)
return
- srcItem = self.theProject.getItem(self.sourceItem)
+ srcItem = self.theProject.projTree[self.sourceItem]
if srcItem is None:
self.theParent.makeAlert((
"Could not parse source document."
@@ -172,7 +172,7 @@ class GuiDocSplit(QDialog):
wTitle = wTitle.strip()
nHandle = self.theProject.newFile(wTitle, srcItem.itemClass, fHandle)
- newItem = self.theProject.getItem(nHandle)
+ newItem = self.theProject.projTree[nHandle]
newItem.setLayout(itemLayout)
logger.verbose(
"Creating new document %s with text from line %d to %d" % (nHandle, iStart, iEnd-1)
@@ -213,7 +213,7 @@ class GuiDocSplit(QDialog):
if self.sourceItem is None:
return
- nwItem = self.theProject.getItem(self.sourceItem)
+ nwItem = self.theProject.projTree[self.sourceItem]
if nwItem is None:
return
if nwItem.itemType is not nwItemType.FILE:
diff --git a/nw/gui/dialogs/export.py b/nw/gui/dialogs/export.py
index eb8f3925..2d67e482 100644
--- a/nw/gui/dialogs/export.py
+++ b/nw/gui/dialogs/export.py
@@ -134,7 +134,7 @@ class GuiExport(QDialog):
self.exportStatus.setText("Export failed ...")
return False
- nItems = len(self.theProject.treeOrder)
+ nItems = len(self.theProject.projTree)
if eFormat == GuiExportMain.FMT_PDOC:
nItems += int(0.2*nItems)
self.exportProgress.setMinimum(0)
@@ -182,16 +182,14 @@ class GuiExport(QDialog):
time.sleep(0.5)
nDone = 0
- for tHandle in self.theProject.treeOrder:
+ for tItem in self.theProject.projTree:
self.exportProgress.setValue(nDone)
- tItem = self.theProject.getItem(tHandle)
-
self.exportStatus.setText("Exporting: %s" % tItem.itemName)
logger.verbose("Exporting: %s" % tItem.itemName)
if tItem is not None and tItem.itemType == nwItemType.FILE:
- outFile.addText(tHandle)
+ outFile.addText(tItem.itemHandle)
nDone += 1
diff --git a/nw/gui/dialogs/itemeditor.py b/nw/gui/dialogs/itemeditor.py
index f58ee020..30c4721c 100644
--- a/nw/gui/dialogs/itemeditor.py
+++ b/nw/gui/dialogs/itemeditor.py
@@ -48,7 +48,7 @@ class GuiItemEditor(QDialog):
self.mainConf = nw.CONFIG
self.theProject = theProject
self.theParent = theParent
- self.theItem = self.theProject.getItem(tHandle)
+ self.theItem = self.theProject.projTree[tHandle]
self.outerBox = QHBoxLayout()
self.innerBox = QVBoxLayout()
diff --git a/nw/gui/elements/docdetails.py b/nw/gui/elements/docdetails.py
index b638ea79..a55e44c2 100644
--- a/nw/gui/elements/docdetails.py
+++ b/nw/gui/elements/docdetails.py
@@ -83,7 +83,7 @@ class GuiDocDetails(QFrame):
def buildViewBox(self, tHandle):
- nwItem = self.theProject.getItem(tHandle)
+ nwItem = self.theProject.projTree[tHandle]
if nwItem is None:
colTwo = [""]*4
diff --git a/nw/gui/elements/doctitlebar.py b/nw/gui/elements/doctitlebar.py
index 8547ad06..08248a93 100644
--- a/nw/gui/elements/doctitlebar.py
+++ b/nw/gui/elements/doctitlebar.py
@@ -89,13 +89,13 @@ class GuiDocTitleBar(QLabel):
tTitle = []
tTree = self.theProject.getItemPath(tHandle)
for aHandle in reversed(tTree):
- nwItem = self.theProject.getItem(aHandle)
+ nwItem = self.theProject.projTree[aHandle]
if nwItem is not None:
tTitle.append(nwItem.itemName)
sSep = " %s " % nwUnicode.U_RSAQUO
self.setText(sSep.join(tTitle))
else:
- nwItem = self.theProject.getItem(tHandle)
+ nwItem = self.theProject.projTree[tHandle]
if nwItem is None:
return False
diff --git a/nw/gui/elements/doctree.py b/nw/gui/elements/doctree.py
index 4fd08da8..09330346 100644
--- a/nw/gui/elements/doctree.py
+++ b/nw/gui/elements/doctree.py
@@ -118,7 +118,7 @@ class GuiDocTree(QTreeWidget):
return False
if itemClass is None and pHandle is not None:
- pItem = self.theProject.getItem(pHandle)
+ pItem = self.theProject.projTree[pHandle]
if pItem is not None:
itemClass = pItem.itemClass
@@ -150,7 +150,7 @@ class GuiDocTree(QTreeWidget):
# If no parent has been selected, make the new file under
# the root NOVEL item.
if pHandle is None:
- pHandle = self.theProject.findRootItem(nwItemClass.NOVEL)
+ pHandle = self.theProject.projTree.findRoot(nwItemClass.NOVEL)
# If still nothing, give up
if pHandle is None:
@@ -159,7 +159,7 @@ class GuiDocTree(QTreeWidget):
# Now check if the selected item is a file, in which case
# the new file will be a sibling
- pItem = self.theProject.getItem(pHandle)
+ pItem = self.theProject.projTree[pHandle]
if pItem.itemType == nwItemType.FILE:
pHandle = pItem.parHandle
@@ -170,7 +170,7 @@ class GuiDocTree(QTreeWidget):
)
return False
- if pHandle == self.theProject.trashRoot:
+ if pHandle == self.theProject.projTree.trashRoot():
self.makeAlert(
"Cannot add new files or folders to the trash folder.", nwAlert.ERROR
)
@@ -194,7 +194,7 @@ class GuiDocTree(QTreeWidget):
def revealTreeItem(self, tHandle):
"""Reveal a newly added project item in the project tree.
"""
- nwItem = self.theProject.getItem(tHandle)
+ nwItem = self.theProject.projTree[tHandle]
trItem = self._addTreeItem(nwItem)
pHandle = nwItem.parHandle
if pHandle is not None and pHandle in self.theMap.keys():
@@ -267,14 +267,16 @@ class GuiDocTree(QTreeWidget):
deleteItem function for each document in the Trash folder.
"""
+ trashHandle = self.theProject.projTree.trashRoot()
+
logger.debug("Emptying Trash folder")
- if self.theProject.trashRoot is None:
+ if trashHandle is None:
self.makeAlert("There is no Trash folder.", nwAlert.INFO)
return False
- theTrash = self.getTreeFromHandle(self.theProject.trashRoot)
- if self.theProject.trashRoot in theTrash:
- theTrash.remove(self.theProject.trashRoot)
+ theTrash = self.getTreeFromHandle(trashHandle)
+ if trashHandle in theTrash:
+ theTrash.remove(trashHandle)
nTrash = len(theTrash)
if nTrash == 0:
@@ -291,8 +293,8 @@ class GuiDocTree(QTreeWidget):
return False
logger.verbose("Deleting %d files from Trash" % nTrash)
- for tHandle in self.getTreeFromHandle(self.theProject.trashRoot):
- if tHandle == self.theProject.trashRoot:
+ for tHandle in self.getTreeFromHandle(trashHandle):
+ if tHandle == trashHandle:
continue
self.deleteItem(tHandle, True)
@@ -313,7 +315,7 @@ class GuiDocTree(QTreeWidget):
return False
trItemS = self._getTreeItem(tHandle)
- nwItemS = self.theProject.getItem(tHandle)
+ nwItemS = self.theProject.projTree[tHandle]
if nwItemS is None:
return False
@@ -327,7 +329,7 @@ class GuiDocTree(QTreeWidget):
return False
pHandle = nwItemS.parHandle
- if pHandle is not None and pHandle == self.theProject.trashRoot:
+ if pHandle is not None and pHandle == self.theProject.projTree.trashRoot():
# If the file is in the trash folder already, as the
# user if they want to permanently delete the file.
@@ -353,7 +355,7 @@ class GuiDocTree(QTreeWidget):
theDoc = NWDoc(self.theProject, self.theParent)
theDoc.deleteDocument(tHandle)
- self.theProject.deleteItem(tHandle)
+ del self.theProject.projTree[tHandle]
self.theParent.theIndex.deleteHandle(tHandle)
else:
@@ -366,7 +368,7 @@ class GuiDocTree(QTreeWidget):
tIndex = trItemP.indexOfChild(trItemS)
trItemC = trItemP.takeChild(tIndex)
trItemT.addChild(trItemC)
- nwItemS.setParent(self.theProject.trashRoot)
+ nwItemS.setParent(self.theProject.projTree.trashRoot())
self.theProject.setProjectChanged(True)
self.theParent.theIndex.deleteHandle(tHandle)
@@ -380,7 +382,7 @@ class GuiDocTree(QTreeWidget):
tIndex = trItemP.indexOfChild(trItemS)
if trItemS.childCount() == 0:
trItemP.takeChild(tIndex)
- self.theProject.deleteItem(tHandle)
+ del self.theProject.projTree[tHandle]
else:
self.makeAlert(["Cannot delete folder.","It is not empty."], nwAlert.ERROR)
return False
@@ -401,7 +403,7 @@ class GuiDocTree(QTreeWidget):
def setTreeItemValues(self, tHandle):
trItem = self._getTreeItem(tHandle)
- nwItem = self.theProject.getItem(tHandle)
+ nwItem = self.theProject.projTree[tHandle]
tName = nwItem.itemName
tClass = nwItem.itemClass
tHandle = nwItem.itemHandle
@@ -552,14 +554,15 @@ class GuiDocTree(QTreeWidget):
"""Adds the trash root folder if it doesn't already exist in the
project tree.
"""
- if self.theProject.trashRoot is None:
+ trashHandle = self.theProject.projTree.trashRoot()
+ if trashHandle is None:
self.theProject.addTrash()
trItem = self._addTreeItem(
- self.theProject.getItem(self.theProject.trashRoot)
+ self.theProject.projTree[trashHandle]
)
trItem.setExpanded(True)
else:
- trItem = self._getTreeItem(self.theProject.trashRoot)
+ trItem = self._getTreeItem(trashHandle)
return trItem
def _addOrphanedRoot(self):
@@ -589,7 +592,7 @@ class GuiDocTree(QTreeWidget):
"""
trItemS = self._getTreeItem(tHandle)
- nwItemS = self.theProject.getItem(tHandle)
+ nwItemS = self.theProject.projTree[tHandle]
trItemP = trItemS.parent()
if trItemP is None:
logger.error("Failed to find new parent item of %s" % tHandle)
@@ -609,8 +612,8 @@ class GuiDocTree(QTreeWidget):
def _moveOrphanedItem(self, tHandle, dHandle):
trItemS = self._getTreeItem(tHandle)
- nwItemS = self.theProject.getItem(tHandle)
- nwItemD = self.theProject.getItem(dHandle)
+ nwItemS = self.theProject.projTree[tHandle]
+ nwItemD = self.theProject.projTree[dHandle]
trItemP = trItemS.parent()
nwItemS.setClass(nwItemD.itemClass)
if trItemP is None:
@@ -652,8 +655,8 @@ class GuiDocTree(QTreeWidget):
dItem = self.itemFromIndex(dIndex)
dHandle = dItem.text(self.C_HANDLE)
- snItem = self.theProject.getItem(sHandle)
- dnItem = self.theProject.getItem(dHandle)
+ snItem = self.theProject.projTree[sHandle]
+ dnItem = self.theProject.projTree[dHandle]
if dnItem is None:
self.makeAlert("The item cannot be moved to that location.", nwAlert.ERROR)
return
diff --git a/nw/gui/elements/docviewer.py b/nw/gui/elements/docviewer.py
index d04aa152..af51e908 100644
--- a/nw/gui/elements/docviewer.py
+++ b/nw/gui/elements/docviewer.py
@@ -123,7 +123,7 @@ class GuiDocViewer(QTextBrowser):
"""Load text into the viewer from an item handle.
"""
- tItem = self.theProject.getItem(tHandle)
+ tItem = self.theProject.projTree[tHandle]
if tItem is None:
logger.warning("Item not found")
return False
diff --git a/nw/gui/elements/outline.py b/nw/gui/elements/outline.py
index 660af8b3..54cfbf5a 100644
--- a/nw/gui/elements/outline.py
+++ b/nw/gui/elements/outline.py
@@ -405,7 +405,7 @@ class GuiProjectOutline(QTreeWidget):
"""Populate a tree item with all the column values.
"""
- nwItem = self.theProject.getItem(tHandle)
+ nwItem = self.theProject.projTree[tHandle]
novIdx = self.theIndex.novelIndex[tHandle][sTitle]
newItem = QTreeWidgetItem()
diff --git a/nw/gui/elements/viewdetails.py b/nw/gui/elements/viewdetails.py
index 9a12293f..cb0d6af3 100644
--- a/nw/gui/elements/viewdetails.py
+++ b/nw/gui/elements/viewdetails.py
@@ -103,7 +103,7 @@ class GuiDocViewDetails(QWidget):
theRefs = self.theParent.theIndex.getBackReferenceList(tHandle)
theList = []
for tHandle in theRefs:
- tItem = self.theProject.getItem(tHandle)
+ tItem = self.theProject.projTree[tHandle]
if tItem is not None:
theList.append("%s" % (tHandle,tItem.itemName))
diff --git a/nw/gui/mainmenu.py b/nw/gui/mainmenu.py
index e6553191..f1021fc3 100644
--- a/nw/gui/mainmenu.py
+++ b/nw/gui/mainmenu.py
@@ -68,7 +68,7 @@ class GuiMainMenu(QMenuBar):
if itemClass == nwItemClass.NO_CLASS: continue
if itemClass == nwItemClass.TRASH: continue
self.rootItems[itemClass].setEnabled(
- self.theProject.checkRootUnique(itemClass)
+ self.theProject.projTree.checkRootUnique(itemClass)
)
return
diff --git a/nw/gui/tools/dochighlight.py b/nw/gui/tools/dochighlight.py
index cfdad370..fbe3a02e 100644
--- a/nw/gui/tools/dochighlight.py
+++ b/nw/gui/tools/dochighlight.py
@@ -228,7 +228,7 @@ class GuiDocHighlighter(QSyntaxHighlighter):
return
if theText.startswith("@"): # Keywords and commands
- tItem = self.theParent.theProject.getItem(self.theHandle)
+ tItem = self.theParent.theProject.projTree[self.theHandle]
isValid, theBits, thePos = self.theIndex.scanThis(theText)
isGood = self.theIndex.checkThese(theBits, tItem)
if isValid:
diff --git a/nw/guimain.py b/nw/guimain.py
index 47b533d6..14e0816b 100644
--- a/nw/guimain.py
+++ b/nw/guimain.py
@@ -617,7 +617,7 @@ class GuiMain(QMainWindow):
return False
logger.verbose("Opening item %s" % tHandle)
- nwItem = self.theProject.getItem(tHandle)
+ nwItem = self.theProject.projTree[tHandle]
if nwItem.itemType == nwItemType.FILE:
logger.verbose("Requested item %s is a file" % tHandle)
self.openDocument(tHandle)
@@ -656,7 +656,7 @@ class GuiMain(QMainWindow):
self.treeView.saveTreeOrder()
self.theIndex.clearIndex()
- nItems = len(self.theProject.treeOrder)
+ nItems = len(self.theProject.projTree)
dlgProg = QProgressDialog("Scanning files ...", "Cancel", 0, nItems, self)
dlgProg.setWindowModality(Qt.WindowModal)
@@ -668,27 +668,27 @@ class GuiMain(QMainWindow):
time.sleep(0.5)
nDone = 0
- for tHandle in self.theProject.treeOrder:
-
- tItem = self.theProject.getItem(tHandle)
+ for tItem in self.theProject.projTree:
dlgProg.setValue(nDone)
- dlgProg.setLabelText("Scanning: %s" % tItem.itemName)
- logger.verbose("Scanning: %s" % tItem.itemName)
if tItem is not None and tItem.itemType == nwItemType.FILE:
+
+ dlgProg.setLabelText("Scanning: %s" % tItem.itemName)
+ logger.verbose("Scanning: %s" % tItem.itemName)
+
theDoc = NWDoc(self.theProject, self)
- theText = theDoc.openDocument(tHandle, False)
+ theText = theDoc.openDocument(tItem.itemHandle, False)
# Build tag index
- self.theIndex.scanText(tHandle, theText)
+ self.theIndex.scanText(tItem.itemHandle, theText)
# Get Word Counts
- cC, wC, pC = self.theIndex.getCounts(tHandle)
+ cC, wC, pC = self.theIndex.getCounts(tItem.itemHandle)
tItem.setCharCount(cC)
tItem.setWordCount(wC)
tItem.setParaCount(pC)
- self.treeView.propagateCount(tHandle, wC)
+ self.treeView.propagateCount(tItem.itemHandle, wC)
self.treeView.projectWordCount()
nDone += 1
@@ -1020,7 +1020,7 @@ class GuiMain(QMainWindow):
def _treeDoubleClick(self, tItem, colNo):
tHandle = tItem.text(3)
logger.verbose("User double clicked tree item with handle %s" % tHandle)
- nwItem = self.theProject.getItem(tHandle)
+ nwItem = self.theProject.projTree[tHandle]
if nwItem.itemType == nwItemType.FILE:
logger.verbose("Requested item %s is a file" % tHandle)
self.openDocument(tHandle)
@@ -1031,7 +1031,7 @@ class GuiMain(QMainWindow):
def _treeKeyPressReturn(self):
tHandle = self.treeView.getSelectedHandle()
logger.verbose("User pressed return on tree item with handle %s" % tHandle)
- nwItem = self.theProject.getItem(tHandle)
+ nwItem = self.theProject.projTree[tHandle]
if nwItem.itemType == nwItemType.FILE:
logger.verbose("Requested item %s is a file" % tHandle)
self.openDocument(tHandle)
diff --git a/nw/project/document.py b/nw/project/document.py
index b456043c..1e200d33 100644
--- a/nw/project/document.py
+++ b/nw/project/document.py
@@ -62,7 +62,7 @@ class NWDoc():
def openDocument(self, tHandle, showStatus=True):
self.docHandle = tHandle
- self.theItem = self.theProject.getItem(tHandle)
+ self.theItem = self.theProject.projTree[tHandle]
if self.theItem is None:
self.clearDocument()
@@ -71,7 +71,7 @@ class NWDoc():
# By default, the document is editable.
# Except for files in the trash folder.
self.docEditable = True
- if self.theItem.parHandle == self.theProject.trashRoot:
+ if self.theItem.parHandle == self.theProject.projTree.trashRoot():
self.docEditable = False
docDir, docFile = self.assemblePath(self.docHandle, self.FILE_MN)
diff --git a/nw/project/index.py b/nw/project/index.py
index 7ff301a7..9cc1098b 100644
--- a/nw/project/index.py
+++ b/nw/project/index.py
@@ -249,12 +249,12 @@ class NWIndex():
files before we save them, unless we're rebuilding the index.
"""
- theItem = self.theProject.getItem(tHandle)
+ theItem = self.theProject.projTree[tHandle]
if theItem is None:
return False
if theItem.itemType != nwItemType.FILE:
return False
- if theItem.parHandle == self.theProject.trashRoot:
+ if theItem.parHandle == self.theProject.projTree.trashRoot():
return False
if theItem.itemLayout == nwItemLayout.NO_LAYOUT:
return False
@@ -539,7 +539,7 @@ class NWIndex():
"""
theStructure = []
- for tHandle in self.theProject.treeOrder:
+ for tHandle in self.theProject.projTree.handles():
if tHandle not in self.novelIndex:
continue
for sTitle in sorted(self.novelIndex[tHandle].keys()):
@@ -605,7 +605,7 @@ class NWIndex():
theRefs = {}
- tItem = self.theProject.getItem(tHandle)
+ tItem = self.theProject.projTree[tHandle]
if tHandle is None:
return theRefs
diff --git a/nw/project/project.py b/nw/project/project.py
index 9ee3844f..039e63ec 100644
--- a/nw/project/project.py
+++ b/nw/project/project.py
@@ -59,18 +59,12 @@ class NWProject():
self.saveCount = None # Meta data: number of saves
self.autoCount = None # Meta data: number of automatic saves
- # Debug
- self.handleSeed = None
-
# Class Settings
- self.projTree = None # Holds all the items of the project
- self.treeOrder = None # The order of the tree items on the tree view
- self.treeRoots = None # The root items of the tree
- self.trashRoot = None # The handle of the trash root folder
- self.projPath = None # The full path to where the currently open project is saved
- self.projMeta = None # The full path to the project's meta data folder
- self.projDict = None # The spell check dictionary
- self.projFile = None # The file name of the project main xml file
+ self.projTree = NWTree(self)
+ self.projPath = None # The full path to where the currently open project is saved
+ self.projMeta = None # The full path to the project's meta data folder
+ self.projDict = None # The spell check dictionary
+ self.projFile = None # The file name of the project main xml file
# Project Meta
self.projName = None
@@ -104,7 +98,10 @@ class NWProject():
##
def newRoot(self, rootName, rootClass):
- if not self.checkRootUnique(rootClass):
+ """Add a new root item. These items are unique, except for item class
+ CUSTOM, and always have parent handle set to None.
+ """
+ if not self.projTree.checkRootUnique(rootClass):
self.makeAlert("Duplicate root item detected!", nwAlert.ERROR)
return None
newItem = NWItem(self)
@@ -112,19 +109,24 @@ class NWProject():
newItem.setType(nwItemType.ROOT)
newItem.setClass(rootClass)
newItem.setStatus(0)
- self._appendItem(None,None,newItem)
+ self.projTree.append(None, None, newItem)
return newItem.itemHandle
def newFolder(self, folderName, folderClass, pHandle):
+ """Add a new folder with a given name and class and parent item.
+ """
newItem = NWItem(self)
newItem.setName(folderName)
newItem.setType(nwItemType.FOLDER)
newItem.setClass(folderClass)
newItem.setStatus(0)
- self._appendItem(None,pHandle,newItem)
+ self.projTree.append(None, pHandle, newItem)
return newItem.itemHandle
def newFile(self, fileName, fileClass, pHandle):
+ """Add a new file with a given name and class, and set a default
+ layout based on the class. SCENE for NOVEL, and otherwise NOTE.
+ """
newItem = NWItem(self)
newItem.setName(fileName)
newItem.setType(nwItemType.FILE)
@@ -134,15 +136,17 @@ class NWProject():
newItem.setLayout(nwItemLayout.NOTE)
newItem.setClass(fileClass)
newItem.setStatus(0)
- self._appendItem(None,pHandle,newItem)
+ self.projTree.append(None, pHandle, newItem)
return newItem.itemHandle
def addTrash(self):
+ """Add the special trash root folder to the project.
+ """
newItem = NWItem(self)
newItem.setName("Trash")
newItem.setType(nwItemType.TRASH)
newItem.setClass(nwItemClass.TRASH)
- self._appendItem(None,None,newItem)
+ self.projTree.append(None, None, newItem)
return newItem.itemHandle
##
@@ -165,17 +169,17 @@ class NWProject():
default values.
"""
+ # Project Status
self.projOpened = None
self.projChanged = None
self.projAltered = False
self.saveCount = 0
self.autoCount = 0
+ # Project Tree
+ self.projTree.clear()
+
# Project Settings
- self.projTree = {}
- self.treeOrder = []
- self.treeRoots = []
- self.trashRoot = None
self.projPath = None
self.projMeta = None
self.projDict = None
@@ -336,7 +340,7 @@ class NWProject():
for xItem in xChild:
nwItem = NWItem(self)
if nwItem.unpackXML(xItem):
- self._appendItem(nwItem.itemHandle, nwItem.parHandle, nwItem)
+ self.projTree.append(nwItem.itemHandle, nwItem.parHandle, nwItem)
self.optState.loadSettings()
@@ -414,9 +418,9 @@ class NWProject():
# Save Tree Content
logger.debug("Writing project content")
- xContent = etree.SubElement(nwXML, "content", attrib={"count":str(len(self.treeOrder))})
- for tHandle in self.treeOrder:
- self.projTree[tHandle].packXML(xContent)
+ xContent = etree.SubElement(nwXML, "content", attrib={"count":str(len(self.projTree))})
+ for tItem in self.projTree:
+ tItem.packXML(xContent)
# Write the xml tree to file
tempFile = path.join(self.projPath, self.projFile+"~")
@@ -529,9 +533,9 @@ class NWProject():
return True
def setTreeOrder(self, newOrder):
- if len(self.treeOrder) != len(newOrder):
+ if len(self.projTree) != len(newOrder):
logger.warning("Size of new and old tree order does not match")
- self.treeOrder = newOrder
+ self.projTree.setOrder(newOrder)
self.setProjectChanged(True)
return True
@@ -555,8 +559,8 @@ class NWProject():
def setStatusColours(self, newCols):
replaceMap = self.statusItems.setNewEntries(newCols)
- if self.projTree is not None:
- for nwItem in self.projTree.values():
+ if self.projTree:
+ for nwItem in self.projTree:
if nwItem.itemClass == nwItemClass.NOVEL:
if nwItem.itemStatus in replaceMap.keys():
nwItem.setStatus(replaceMap[nwItem.itemStatus])
@@ -565,8 +569,8 @@ class NWProject():
def setImportColours(self, newCols):
replaceMap = self.importItems.setNewEntries(newCols)
- if self.projTree is not None:
- for nwItem in self.projTree.values():
+ if self.projTree:
+ for nwItem in self.projTree:
if nwItem.itemClass != nwItemClass.NOVEL:
if nwItem.itemStatus in replaceMap.keys():
nwItem.setStatus(replaceMap[nwItem.itemStatus])
@@ -589,15 +593,6 @@ class NWProject():
# Get Functions
##
- def getItem(self, tHandle):
- """Return a project item based on its handle. Returns None if
- the handle doesn't exist in the project.
- """
- if tHandle in self.projTree:
- return self.projTree[tHandle]
- logger.error("No tree item with handle %s" % str(tHandle))
- return None
-
def getSessionWordCount(self):
return self.currWCount - self.lastWCount
@@ -606,14 +601,14 @@ class NWProject():
parent None, the root item. We do this with a for loop with a
maximum depth of 200 to make infinite loops impossible.
"""
- tItem = self.getItem(tHandle)
+ tItem = self.projTree[tHandle]
if tItem is not None:
for i in range(200):
if tItem.parHandle is None:
return tHandle
else:
tHandle = tItem.parHandle
- tItem = self.getItem(tHandle)
+ tItem = self.projTree[tHandle]
if tItem is None:
return tHandle
return None
@@ -625,7 +620,7 @@ class NWProject():
infinite loops impossible.
"""
tTree = []
- tItem = self.getItem(tHandle)
+ tItem = self.projTree[tHandle]
if tItem is not None:
tTree.append(tHandle)
for i in range(200):
@@ -633,7 +628,7 @@ class NWProject():
return tTree
else:
tHandle = tItem.parHandle
- tItem = self.getItem(tHandle)
+ tItem = self.projTree[tHandle]
if tItem is None:
return tTree
else:
@@ -647,12 +642,12 @@ class NWProject():
already sent to the tree.
"""
sentItems = []
- iterItems = self.treeOrder.copy()
+ iterItems = self.projTree.handles()
n = 0
nMax = len(iterItems)
while n < nMax:
tHandle = iterItems[n]
- tItem = self.getItem(tHandle)
+ tItem = self.projTree[tHandle]
n += 1
if n > 10000:
return # Just in case
@@ -685,43 +680,13 @@ class NWProject():
# Class Methods
##
- def deleteItem(self, tHandle):
- """This only removes the item from the order list, but not from
- the project tree.
- """
- if tHandle not in self.treeOrder:
- logger.warning(
- "Could not remove item %s from treeOrder as it does not exist" % tHandle
- )
- return False
- self.treeOrder.remove(tHandle)
- self.setProjectChanged(True)
- return True
-
- def findRootItem(self, theClass):
- for aRoot in self.treeRoots:
- if theClass == self.projTree[aRoot].itemClass:
- return self.projTree[aRoot].itemHandle
- return None
-
- def checkRootUnique(self, theClass):
- """Checks if there already is a root entry of class 'theClass'
- in the root of the project tree.
- """
- if theClass == nwItemClass.CUSTOM:
- return True
- for aRoot in self.treeRoots:
- if theClass == self.projTree[aRoot].itemClass:
- return False
- return True
-
def countStatus(self):
"""Count how many times the various status flags are used in the
project tree.
"""
self.statusItems.resetCounts()
self.importItems.resetCounts()
- for nwItem in self.projTree.values():
+ for nwItem in self.projTree:
if nwItem.itemClass == nwItemClass.NOVEL:
self.statusItems.countEntry(nwItem.itemStatus)
else:
@@ -844,7 +809,7 @@ class NWProject():
logger.warning("Skipping file %s" % fileItem)
continue
fHandle = fileItem[5]+fileItem[7:19]
- if fHandle in self.treeOrder:
+ if fHandle in self.projTree:
logger.debug("Checking file %s, handle %s: OK" % (fileItem,fHandle))
else:
logger.debug("Checking file %s, handle %s: Orphaned" % (fileItem,fHandle))
@@ -869,33 +834,7 @@ class NWProject():
orItem.setType(nwItemType.FILE)
orItem.setClass(nwItemClass.NO_CLASS)
orItem.setLayout(nwItemLayout.NO_LAYOUT)
- self._appendItem(oHandle,None,orItem)
-
- return
-
- def _appendItem(self, tHandle, pHandle, nwItem):
- tHandle = checkString(tHandle,self._makeHandle(),False)
- pHandle = checkString(pHandle,None,True)
- logger.verbose("Adding entry %s with parent %s" % (str(tHandle),str(pHandle)))
-
- nwItem.setHandle(tHandle)
- nwItem.setParent(pHandle)
-
- self.projTree[tHandle] = nwItem
- self.treeOrder.append(tHandle)
-
- if nwItem.itemType == nwItemType.ROOT:
- logger.verbose("Entry %s is a root item" % str(tHandle))
- self.treeRoots.append(tHandle)
-
- if nwItem.itemType == nwItemType.TRASH:
- if self.trashRoot is None:
- logger.verbose("Entry %s is the trash folder" % str(tHandle))
- self.trashRoot = tHandle
- else:
- logger.error("Only one trash folder allowed")
-
- self.setProjectChanged(True)
+ self.projTree.append(oHandle, None, orItem)
return
@@ -919,21 +858,247 @@ class NWProject():
return True
+# END Class NWProject
+
+# ================================================================================================ #
+# NWTree
+# Class holding the project tree for the NWProject
+# ================================================================================================ #
+
+class NWTree():
+
+ def __init__(self, theProject):
+
+ self.theProject = theProject
+
+ self._projTree = {} # Holds all the items of the project
+ self._treeOrder = [] # The order of the tree items on the tree view
+ self._treeRoots = [] # The root items of the tree
+ self._trashRoot = "" # 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
+
+ return
+
+ ##
+ # Class Methods
+ ##
+
+ def clear(self):
+ """Clear the item tree entirely.
+ """
+ self._projTree = {}
+ self._treeOrder = []
+ self._treeRoots = []
+ self._trashRoot = ""
+ self._theLength = 0
+ self._theIndex = 0
+ self._treeChanged = False
+ return
+
+ def handles(self):
+ """Returns a copy of the list of all the active handles.
+ """
+ return self._treeOrder.copy()
+
+ 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)
+ if tHandle is None:
+ tHandle = self._makeHandle()
+
+ logger.verbose("Adding entry %s with parent %s" % (str(tHandle), str(pHandle)))
+
+ nwItem.setHandle(tHandle)
+ nwItem.setParent(pHandle)
+
+ self._projTree[tHandle] = nwItem
+ self._treeOrder.append(tHandle)
+
+ if nwItem.itemType == nwItemType.ROOT:
+ logger.verbose("Entry %s is a root item" % str(tHandle))
+ self._treeRoots.append(tHandle)
+
+ if nwItem.itemType == nwItemType.TRASH:
+ if self._trashRoot is None:
+ logger.verbose("Entry %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 trashRoot(self):
+ """Returns the handle of the trash folder, or None if there
+ isn't one.
+ """
+ if self._trashRoot:
+ return self._trashRoot
+ return None
+
+ def findRoot(self, theClass):
+ """Find the root item for a given class.
+ Note: This returns the first item for class CUSTOM.
+ """
+ for aRoot in self._treeRoots:
+ tItem = self.__getitem__(aRoot)
+ if tItem is None:
+ continue
+ if theClass == tItem.itemClass:
+ return tItem.itemHandle
+ return None
+
+ def checkRootUnique(self, theClass):
+ """Checks if there already is a root entry of class 'theClass'
+ in the root of the project tree.
+ """
+ if theClass == nwItemClass.CUSTOM:
+ return True
+ for aRoot in self._treeRoots:
+ tItem = self.__getitem__(aRoot)
+ if theClass == tItem.itemClass:
+ return False
+ return True
+
+ ##
+ # Setters
+ ##
+
+ def setOrder(self, newOrder):
+ """Reorders the tree based on a list of items.
+ """
+ tmpOrder = []
+
+ # Add all known elements to a new temp list
+ for tHandle in newOrder:
+ if tHandle in self._projTree:
+ tmpOrder.append(tHandle)
+ else:
+ logger.error("Handle %s in new tree order is not in project tree" % tHandle)
+
+ # Do a reverse lookup to check for items that will be lost
+ # This is mainly for debugging purposes
+ for tHandle in self._treeOrder:
+ if tHandle not in tmpOrder:
+ logger.warning("Handle %s in old tree order is not in new tree order" % tHandle)
+
+ # Save the temp list
+ self._treeOrder = tmpOrder
+ self._theLength = len(self._treeOrder)
+ self._setTreeChanged(True)
+
+ return
+
+ def setSeed(self, theSeed):
+ """Used for debugging!
+ Sets a seed for generating handles so that they always come out
+ in a predictable order.
+ """
+ self._handleSeed = theSeed
+ return
+
+ ##
+ # Meta Methods
+ ##
+
+ def __len__(self):
+ return self._theLength
+
+ def __bool__(self):
+ return self._theLength > 0
+
+ ##
+ # Item Access Methods
+ ##
+
+ def __getitem__(self, tHandle):
+ """Return a project item based on its handle. Returns None if
+ the handle doesn't exist in the project.
+ """
+ if tHandle in self._projTree:
+ return self._projTree[tHandle]
+ logger.error("No tree item with handle %s" % str(tHandle))
+ return None
+
+ def __delitem__(self, tHandle):
+ """This only removes the item from the order list, but not from
+ the project tree.
+ """
+ if tHandle not in self._treeOrder:
+ logger.warning(
+ "Could not remove item %s from project tree as it does not exist" % tHandle
+ )
+ return False
+ self._treeOrder.remove(tHandle)
+ self._theLength = len(self._treeOrder)
+ self._setTreeChanged(True)
+ return True
+
+ def __contains__(self, tHandle):
+ """Checks if a handle exists in the tree.
+ """
+ return tHandle in self._treeOrder
+
+ ##
+ # Iterator Methods
+ ##
+
+ def __iter__(self):
+ """Initiates the iterator,
+ """
+ self._theIndex = 0
+ return self
+
+ def __next__(self):
+ """Returns the item from the next entry in the _treeOrder list.
+ """
+ if self._theIndex < self._theLength:
+ theItem = self.__getitem__(self._treeOrder[self._theIndex])
+ self._theIndex += 1
+ return theItem
+ else:
+ raise StopIteration
+
+ ##
+ # Internal Functions
+ ##
+
+ def _setTreeChanged(self, theState):
+ """Set the changed flag to theState, and if being set to True,
+ propagate that change to the parent NWProject class.
+ """
+ self._treeChanged = theState
+ if theState:
+ self.theProject.setProjectChanged(True)
+ return
+
def _makeHandle(self, addSeed=""):
- if self.handleSeed is None:
+ """Generate a unique item handle. In the unlikely event that the
+ key already exists, salt the seed and generate a new handle.
+ """
+ if self._handleSeed is None:
newSeed = str(time()) + addSeed
else:
# This is used for debugging
- newSeed = str(self.handleSeed)
- self.handleSeed += 1
+ newSeed = str(self._handleSeed)
+ self._handleSeed += 1
logger.verbose("Generating handle with seed '%s'" % newSeed)
itemHandle = sha256(newSeed.encode()).hexdigest()[0:13]
- if itemHandle in self.projTree.keys():
+ if itemHandle in self._projTree:
logger.warning("Duplicate handle encountered! Retrying ...")
itemHandle = self._makeHandle(addSeed+"!")
return itemHandle
-# END Class NWProject
+# END Class NWTree
# ================================================================================================ #
# NWItem
From 5b3291e4eeae611ba49824985f095859afb706ed Mon Sep 17 00:00:00 2001
From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com>
Date: Thu, 7 May 2020 23:08:43 +0200
Subject: [PATCH 8/9] Updated tests to work with new project tree class
---
...b6561_main.nwd => 1_8010bd9270f9_main.nwd} | 0
...52555_main.nwd => 1_a6562590ef19_main.nwd} | 0
...7e394_main.nwd => 1_e17daca5f3e1_main.nwd} | 0
tests/reference/gui/1_nwProject.nwx | 8 ++---
tests/reference/proj/2_nwProject.nwx | 10 +++----
tests/test_gui.py | 30 +++++++++----------
tests/test_project.py | 2 +-
7 files changed, 25 insertions(+), 25 deletions(-)
rename tests/reference/gui/{1_fca346db6561_main.nwd => 1_8010bd9270f9_main.nwd} (100%)
rename tests/reference/gui/{1_688b6ef52555_main.nwd => 1_a6562590ef19_main.nwd} (100%)
rename tests/reference/gui/{1_2d20bbd7e394_main.nwd => 1_e17daca5f3e1_main.nwd} (100%)
diff --git a/tests/reference/gui/1_fca346db6561_main.nwd b/tests/reference/gui/1_8010bd9270f9_main.nwd
similarity index 100%
rename from tests/reference/gui/1_fca346db6561_main.nwd
rename to tests/reference/gui/1_8010bd9270f9_main.nwd
diff --git a/tests/reference/gui/1_688b6ef52555_main.nwd b/tests/reference/gui/1_a6562590ef19_main.nwd
similarity index 100%
rename from tests/reference/gui/1_688b6ef52555_main.nwd
rename to tests/reference/gui/1_a6562590ef19_main.nwd
diff --git a/tests/reference/gui/1_2d20bbd7e394_main.nwd b/tests/reference/gui/1_e17daca5f3e1_main.nwd
similarity index 100%
rename from tests/reference/gui/1_2d20bbd7e394_main.nwd
rename to tests/reference/gui/1_e17daca5f3e1_main.nwd
diff --git a/tests/reference/gui/1_nwProject.nwx b/tests/reference/gui/1_nwProject.nwx
index 02119a65..c55ff681 100644
--- a/tests/reference/gui/1_nwProject.nwx
+++ b/tests/reference/gui/1_nwProject.nwx
@@ -1,5 +1,5 @@
-
+
@@ -59,7 +59,7 @@
New
True
- -
+
-
New File
FILE
CHARACTER
@@ -78,7 +78,7 @@
New
True
- -
+
-
New File
FILE
PLOT
@@ -97,7 +97,7 @@
New
True
- -
+
-
New File
FILE
WORLD
diff --git a/tests/reference/proj/2_nwProject.nwx b/tests/reference/proj/2_nwProject.nwx
index a40a2e8d..92bd9df4 100644
--- a/tests/reference/proj/2_nwProject.nwx
+++ b/tests/reference/proj/2_nwProject.nwx
@@ -1,5 +1,5 @@
-
+
@@ -73,28 +73,28 @@
0
0
- -
+
-
Timeline
ROOT
TIMELINE
New
False
- -
+
-
Object
ROOT
OBJECT
New
False
- -
+
-
Custom1
ROOT
CUSTOM
New
False
- -
+
-
Custom2
ROOT
CUSTOM
diff --git a/tests/test_gui.py b/tests/test_gui.py
index c8f8d013..e2291fff 100644
--- a/tests/test_gui.py
+++ b/tests/test_gui.py
@@ -25,15 +25,15 @@ def testMainWindows(qtbot, nwTempGUI, nwRef, nwTemp):
qtbot.wait(stepDelay)
# Create new, save, close project
- nwGUI.theProject.handleSeed = 42
+ nwGUI.theProject.projTree.setSeed(42)
assert nwGUI.newProject(nwTempGUI, True)
assert nwGUI.saveProject()
assert nwGUI.closeProject()
assert len(nwGUI.theProject.projTree) == 0
- assert len(nwGUI.theProject.treeOrder) == 0
- assert len(nwGUI.theProject.treeRoots) == 0
- assert nwGUI.theProject.trashRoot is None
+ assert len(nwGUI.theProject.projTree._treeOrder) == 0
+ assert len(nwGUI.theProject.projTree._treeRoots) == 0
+ assert nwGUI.theProject.projTree.trashRoot() is None
assert nwGUI.theProject.projPath is None
assert nwGUI.theProject.projMeta is None
assert nwGUI.theProject.projFile == "nwProject.nwx"
@@ -55,9 +55,9 @@ def testMainWindows(qtbot, nwTempGUI, nwRef, nwTemp):
# Check that we loaded the data
assert len(nwGUI.theProject.projTree) == 6
- assert len(nwGUI.theProject.treeOrder) == 6
- assert len(nwGUI.theProject.treeRoots) == 4
- assert nwGUI.theProject.trashRoot is None
+ assert len(nwGUI.theProject.projTree._treeOrder) == 6
+ assert len(nwGUI.theProject.projTree._treeRoots) == 4
+ assert nwGUI.theProject.projTree.trashRoot() is None
assert nwGUI.theProject.projPath == nwTempGUI
assert nwGUI.theProject.projMeta == path.join(nwTempGUI,"meta")
assert nwGUI.theProject.projFile == "nwProject.nwx"
@@ -237,14 +237,14 @@ def testMainWindows(qtbot, nwTempGUI, nwRef, nwTemp):
# Check the files
refFile = path.join(nwTempGUI,"nwProject.nwx")
assert cmpFiles(refFile, path.join(nwRef,"gui","1_nwProject.nwx"), [2])
- refFile = path.join(nwTempGUI,"data_0","2d20bbd7e394_main.nwd")
- assert cmpFiles(refFile, path.join(nwRef,"gui","1_2d20bbd7e394_main.nwd"))
- refFile = path.join(nwTempGUI,"data_2","fca346db6561_main.nwd")
- assert cmpFiles(refFile, path.join(nwRef,"gui","1_fca346db6561_main.nwd"))
+ refFile = path.join(nwTempGUI,"data_0","e17daca5f3e1_main.nwd")
+ assert cmpFiles(refFile, path.join(nwRef,"gui","1_e17daca5f3e1_main.nwd"))
+ refFile = path.join(nwTempGUI,"data_9","8010bd9270f9_main.nwd")
+ assert cmpFiles(refFile, path.join(nwRef,"gui","1_8010bd9270f9_main.nwd"))
refFile = path.join(nwTempGUI,"data_3","1489056e0916_main.nwd")
assert cmpFiles(refFile, path.join(nwRef,"gui","1_1489056e0916_main.nwd"))
- refFile = path.join(nwTempGUI,"data_7","688b6ef52555_main.nwd")
- assert cmpFiles(refFile, path.join(nwRef,"gui","1_688b6ef52555_main.nwd"))
+ refFile = path.join(nwTempGUI,"data_1","a6562590ef19_main.nwd")
+ assert cmpFiles(refFile, path.join(nwRef,"gui","1_a6562590ef19_main.nwd"))
nwGUI.closeMain()
# qtbot.stopForInteraction()
@@ -258,7 +258,7 @@ def testProjectEditor(qtbot, nwTempGUI, nwRef, nwTemp):
qtbot.wait(stepDelay)
# Create new, save, open project
- nwGUI.theProject.handleSeed = 42
+ nwGUI.theProject.projTree.setSeed(42)
assert nwGUI.newProject(nwTempGUI, True)
nwGUI.mainConf.backupPath = nwTempGUI
@@ -345,7 +345,7 @@ def testItemEditor(qtbot, nwTempGUI, nwRef, nwTemp):
qtbot.wait(stepDelay)
# Create new, save, open project
- nwGUI.theProject.handleSeed = 42
+ nwGUI.theProject.projTree.setSeed(42)
assert nwGUI.newProject(nwTempGUI, True)
itemEdit = GuiItemEditor(nwGUI, nwGUI.theProject, "31489056e0916")
diff --git a/tests/test_project.py b/tests/test_project.py
index 81301c4d..c029fbba 100644
--- a/tests/test_project.py
+++ b/tests/test_project.py
@@ -19,7 +19,7 @@ theMain = DummyMain()
theMain.mainConf = theConf
theProject = NWProject(theMain)
-theProject.handleSeed = 42
+theProject.projTree.setSeed(42)
@pytest.mark.project
def testProjectNew(nwTempProj,nwRef,nwTemp):
From d75243418f538bbc66c7ec5e2bc99812a105f3ae Mon Sep 17 00:00:00 2001
From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com>
Date: Thu, 7 May 2020 23:59:32 +0200
Subject: [PATCH 9/9] Moved a couple more functions to the NWTree class, and
cleaned up the source file a bit
---
nw/gui/elements/doctitlebar.py | 2 +-
nw/project/project.py | 160 +++++++++++++++++++--------------
2 files changed, 96 insertions(+), 66 deletions(-)
diff --git a/nw/gui/elements/doctitlebar.py b/nw/gui/elements/doctitlebar.py
index 08248a93..9e05f479 100644
--- a/nw/gui/elements/doctitlebar.py
+++ b/nw/gui/elements/doctitlebar.py
@@ -87,7 +87,7 @@ class GuiDocTitleBar(QLabel):
if self.mainConf.showFullPath:
tTitle = []
- tTree = self.theProject.getItemPath(tHandle)
+ tTree = self.theProject.projTree.getItemPath(tHandle)
for aHandle in reversed(tTree):
nwItem = self.theProject.projTree[aHandle]
if nwItem is not None:
diff --git a/nw/project/project.py b/nw/project/project.py
index 039e63ec..cd3fdc55 100644
--- a/nw/project/project.py
+++ b/nw/project/project.py
@@ -6,9 +6,12 @@
Class holding a project
File History:
- Created: 2018-09-29 [0.0.1]
- Merged: 2020-05-07 [0.4.5] Merged NWItem class into file
- Merged: 2020-05-07 [0.4.5] Merged NWStatus class into file
+ Created: 2018-09-29 [0.0.1] NWProject
+ Added: 2018-10-27 [0.0.1] NWItem
+ Added: 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 NWStatus class to this file
+ Added: 2020-05-07 [0.4.5] NWTree
This file is a part of novelWriter
Copyright 2020, Veronica Berglyd Olsen
@@ -49,9 +52,14 @@ class NWProject():
def __init__(self, theParent):
# Internal
- self.theParent = theParent
- self.mainConf = self.theParent.mainConf
- self.optState = OptionState(self)
+ self.theParent = theParent
+ self.mainConf = nw.CONFIG
+
+ # Core Elements
+ self.optState = OptionState(self) # Project-specific GUI options
+ self.projTree = NWTree(self) # The project tree
+
+ # Project Status
self.projOpened = None # The time stamp of when the project file was opened
self.projChanged = None # The project has unsaved changes
self.projAltered = None # The project has been altered this session
@@ -60,11 +68,10 @@ class NWProject():
self.autoCount = None # Meta data: number of automatic saves
# Class Settings
- self.projTree = NWTree(self)
self.projPath = None # The full path to where the currently open project is saved
self.projMeta = None # The full path to the project's meta data folder
self.projDict = None # The spell check dictionary
- 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
self.projName = None
@@ -154,6 +161,9 @@ class NWProject():
##
def newProject(self):
+ """Create a new project by populating the project tree with a
+ few starter items.
+ """
hNovel = self.newRoot("Novel", nwItemClass.NOVEL)
hChars = self.newRoot("Characters", nwItemClass.CHARACTER)
hWorld = self.newRoot("Plot", nwItemClass.PLOT)
@@ -469,7 +479,7 @@ class NWProject():
return True
##
- # Set Functions
+ # Setters
##
def setProjectPath(self, projPath):
@@ -590,60 +600,25 @@ class NWProject():
return self.projChanged
##
- # Get Functions
+ # Getters
##
def getSessionWordCount(self):
+ """Returns the number of words added or removed this session.
+ """
return self.currWCount - self.lastWCount
- def getRootItem(self, tHandle):
- """Iterate upwards in the tree until we find the item with
- parent None, the root item. We do this with a for loop with a
- maximum depth of 200 to make infinite loops impossible.
- """
- tItem = self.projTree[tHandle]
- if tItem is not None:
- for i in range(200):
- if tItem.parHandle is None:
- return tHandle
- else:
- tHandle = tItem.parHandle
- tItem = self.projTree[tHandle]
- if tItem is None:
- return tHandle
- return None
-
- def getItemPath(self, tHandle):
- """Iterate upwards in the tree until we find the item with
- parent None, the root item, and return the list of handles.
- We do this with a for loop with a maximum depth of 200 to make
- infinite loops impossible.
- """
- tTree = []
- tItem = self.projTree[tHandle]
- if tItem is not None:
- tTree.append(tHandle)
- for i in range(200):
- if tItem.parHandle is None:
- return tTree
- else:
- tHandle = tItem.parHandle
- tItem = self.projTree[tHandle]
- if tItem is None:
- return tTree
- else:
- tTree.append(tHandle)
- return tTree
-
def getProjectItems(self):
- """This function is called from the tree view when building the
- tree. Each item in the project is returned in the order saved in
- the project file, but first it checks that it has a parent item
- already sent to the tree.
+ """This function ensures that the item tree loaded is sent to
+ the GUI tree view in such a way that the tree can be built. That
+ is, the parent item must be sent before its child. In principle,
+ a proper XML file will already ensure that, but in the event the
+ order has been altered, or a file is orphaned, this function is
+ capable of handling it.
"""
sentItems = []
iterItems = self.projTree.handles()
- n = 0
+ n = 0
nMax = len(iterItems)
while n < nMax:
tHandle = iterItems[n]
@@ -786,6 +761,11 @@ class NWProject():
return
def _scanProjectFolder(self):
+ """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
+ orphaned files so the user can either delete them, or put them
+ back into the project tree.
+ """
if self.projPath is None:
return
@@ -829,16 +809,18 @@ class NWProject():
nOrph = 0
for oHandle in orphanFiles:
nOrph += 1
- orItem = NWItem(self)
- orItem.setName("Orphaned File %d" % nOrph)
- orItem.setType(nwItemType.FILE)
- orItem.setClass(nwItemClass.NO_CLASS)
- orItem.setLayout(nwItemLayout.NO_LAYOUT)
- self.projTree.append(oHandle, None, orItem)
+ orphItem = NWItem(self)
+ orphItem.setName("Orphaned File %d" % nOrph)
+ orphItem.setType(nwItemType.FILE)
+ orphItem.setClass(nwItemClass.NO_CLASS)
+ orphItem.setLayout(nwItemLayout.NO_LAYOUT)
+ self.projTree.append(oHandle, None, orphItem)
return
def _appendSessionStats(self):
+ """Append session statistics to the sessions log file.
+ """
if self.projMeta is None:
return False
@@ -895,8 +877,8 @@ class NWTree():
self._treeOrder = []
self._treeRoots = []
self._trashRoot = ""
- self._theLength = 0
- self._theIndex = 0
+ self._theLength = 0
+ self._theIndex = 0
self._treeChanged = False
return
@@ -937,6 +919,10 @@ class NWTree():
return
+ ##
+ # Tree Structure Methods
+ ##
+
def trashRoot(self):
"""Returns the handle of the trash folder, or None if there
isn't one.
@@ -969,6 +955,45 @@ class NWTree():
return False
return True
+ def getRootItem(self, tHandle):
+ """Iterate upwards in the tree until we find the item with
+ parent None, the root item. We do this with a for loop with a
+ maximum depth of 200 to make infinite loops impossible.
+ """
+ tItem = self.__getitem__(tHandle)
+ if tItem is not None:
+ for i in range(200):
+ if tItem.parHandle is None:
+ return tHandle
+ else:
+ tHandle = tItem.parHandle
+ tItem = self.__getitem__(tHandle)
+ if tItem is None:
+ return tHandle
+ return None
+
+ def getItemPath(self, tHandle):
+ """Iterate upwards in the tree until we find the item with
+ parent None, the root item, and return the list of handles.
+ We do this with a for loop with a maximum depth of 200 to make
+ infinite loops impossible.
+ """
+ tTree = []
+ tItem = self.__getitem__(tHandle)
+ if tItem is not None:
+ tTree.append(tHandle)
+ for i in range(200):
+ if tItem.parHandle is None:
+ return tTree
+ else:
+ tHandle = tItem.parHandle
+ tItem = self.__getitem__(tHandle)
+ if tItem is None:
+ return tTree
+ else:
+ tTree.append(tHandle)
+ return tTree
+
##
# Setters
##
@@ -1053,7 +1078,7 @@ class NWTree():
##
def __iter__(self):
- """Initiates the iterator,
+ """Initiates the iterator.
"""
self._theIndex = 0
return self
@@ -1074,7 +1099,7 @@ class NWTree():
def _setTreeChanged(self, theState):
"""Set the changed flag to theState, and if being set to True,
- propagate that change to the parent NWProject class.
+ propagate that state change to the parent NWProject class.
"""
self._treeChanged = theState
if theState:
@@ -1130,7 +1155,7 @@ class NWItem():
return
##
- # XML Pack
+ # XML Pack/Unpack
##
def packXML(self, xParent):
@@ -1375,6 +1400,9 @@ class NWStatus():
return
def packEntries(self, xParent):
+ """Pack the status entries into an XML object for saving to the
+ main project file.
+ """
for n in range(self.theLength):
xSub = etree.SubElement(xParent,"entry",attrib={
"blue" : str(self.theColours[n][2]),
@@ -1385,6 +1413,8 @@ class NWStatus():
return True
def unpackEntries(self, xParent):
+ """Unpack an XML tree and set the class values.
+ """
theLabels = []
theColours = []