Rewrite NWItem to not allow handle to be None
This commit is contained in:
@@ -353,8 +353,6 @@ class BuildSettings:
|
||||
|
||||
for item in project.tree:
|
||||
tHandle = item.itemHandle
|
||||
if tHandle is None:
|
||||
continue
|
||||
if item.isInactiveClass() or (item.itemRoot in self._skipRoot):
|
||||
result[tHandle] = (False, FilterMode.SKIPPED)
|
||||
continue
|
||||
|
||||
@@ -287,7 +287,7 @@ class DocDuplicator:
|
||||
hMap: dict[str, str | None] = {t: None for t in items}
|
||||
for tHandle in items:
|
||||
newItem = self._project.tree.duplicate(tHandle)
|
||||
if newItem is None or newItem.itemHandle is None:
|
||||
if newItem is None:
|
||||
return
|
||||
hMap[tHandle] = newItem.itemHandle
|
||||
if newItem.itemParent in hMap:
|
||||
|
||||
@@ -101,8 +101,6 @@ class NWBuildDocument:
|
||||
self._queue = []
|
||||
filtered = self._build.buildItemFilter(self._project)
|
||||
for item in self._project.tree:
|
||||
if not item.itemHandle:
|
||||
continue
|
||||
if filtered.get(item.itemHandle, False):
|
||||
self._queue.append(item.itemHandle)
|
||||
return
|
||||
|
||||
@@ -243,7 +243,7 @@ class NWDocument:
|
||||
"""Return a pointer to the currently open NWItem."""
|
||||
return self._theItem
|
||||
|
||||
def getMeta(self) -> tuple[str, str | None, str | None, str | None]:
|
||||
def getMeta(self) -> tuple[str, str | None, nwItemClass | None, nwItemLayout | None]:
|
||||
"""Parse the document meta tag and return the name, parent,
|
||||
class and layout meta values.
|
||||
"""
|
||||
|
||||
+43
-40
@@ -1,7 +1,6 @@
|
||||
"""
|
||||
novelWriter – Project Item Class
|
||||
================================
|
||||
Data class for a project tree item
|
||||
|
||||
File History:
|
||||
Created: 2018-10-27 [0.0.1]
|
||||
@@ -43,6 +42,13 @@ logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class NWItem:
|
||||
"""Core: Item Data Class
|
||||
|
||||
This class holds all the project information about a project item.
|
||||
Each item must be associated with a project and have a valid handle.
|
||||
Only the NWTree class should create instances of this class, and
|
||||
must ensure that the handle is valid for all items in the tree.
|
||||
"""
|
||||
|
||||
__slots__ = (
|
||||
"_project", "_name", "_handle", "_parent", "_root", "_order",
|
||||
@@ -51,11 +57,11 @@ class NWItem:
|
||||
"_paraCount", "_cursorPos", "_initCount",
|
||||
)
|
||||
|
||||
def __init__(self, project: NWProject) -> None:
|
||||
def __init__(self, project: NWProject, handle: str) -> None:
|
||||
|
||||
self._project = project
|
||||
self._name = ""
|
||||
self._handle = None
|
||||
self._handle = handle
|
||||
self._parent = None
|
||||
self._root = None
|
||||
self._order = 0
|
||||
@@ -81,31 +87,12 @@ class NWItem:
|
||||
return f"<NWItem handle={self._handle}, parent={self._parent}, name='{self._name}'>"
|
||||
|
||||
def __bool__(self) -> bool:
|
||||
"""Evaluate to False if itemHandle is not set."""
|
||||
return self._handle is not None
|
||||
|
||||
def __copy__(self) -> NWItem:
|
||||
"""Make a shallow copy of the current item."""
|
||||
item = NWItem(self._project)
|
||||
item._name = self._name
|
||||
item._handle = self._handle
|
||||
item._parent = self._parent
|
||||
item._root = self._root
|
||||
item._order = self._order
|
||||
item._type = self._type
|
||||
item._class = self._class
|
||||
item._layout = self._layout
|
||||
item._status = self._status
|
||||
item._import = self._import
|
||||
item._active = self._active
|
||||
item._expanded = self._expanded
|
||||
item._heading = self._heading
|
||||
item._charCount = self._charCount
|
||||
item._wordCount = self._wordCount
|
||||
item._paraCount = self._paraCount
|
||||
item._cursorPos = self._cursorPos
|
||||
item._initCount = self._initCount
|
||||
return item
|
||||
"""The truthiness of the class. The handle used to be initiated
|
||||
to None, but this is no longer the case. It should always
|
||||
evaluate to True since 2.1-beta1, although unpack and the NWTree
|
||||
class can leave it as an empty string.
|
||||
"""
|
||||
return bool(self._handle)
|
||||
|
||||
##
|
||||
# Properties
|
||||
@@ -116,7 +103,7 @@ class NWItem:
|
||||
return self._name
|
||||
|
||||
@property
|
||||
def itemHandle(self) -> str | None:
|
||||
def itemHandle(self) -> str:
|
||||
return self._handle
|
||||
|
||||
@property
|
||||
@@ -184,7 +171,7 @@ class NWItem:
|
||||
return self._cursorPos
|
||||
|
||||
##
|
||||
# Pack/Unpack Data
|
||||
# Pack/Unpack/Duplicate Data
|
||||
##
|
||||
|
||||
def pack(self) -> dict:
|
||||
@@ -227,8 +214,9 @@ class NWItem:
|
||||
meta = data.get("metaAttr", {})
|
||||
name = data.get("nameAttr", {})
|
||||
|
||||
if "handle" in item:
|
||||
self.setHandle(item["handle"])
|
||||
handle = item.get("handle", "")
|
||||
if isHandle(handle):
|
||||
self._handle = handle
|
||||
else:
|
||||
logger.error("Item does not have a handle")
|
||||
return False
|
||||
@@ -269,6 +257,29 @@ class NWItem:
|
||||
|
||||
return True
|
||||
|
||||
@classmethod
|
||||
def duplicate(cls, source: NWItem, handle: str) -> NWItem:
|
||||
"""Make a copy of an item."""
|
||||
cls = NWItem(source._project, handle)
|
||||
cls._name = source._name
|
||||
cls._parent = source._parent
|
||||
cls._root = source._root
|
||||
cls._order = source._order
|
||||
cls._type = source._type
|
||||
cls._class = source._class
|
||||
cls._layout = source._layout
|
||||
cls._status = source._status
|
||||
cls._import = source._import
|
||||
cls._active = source._active
|
||||
cls._expanded = source._expanded
|
||||
cls._heading = source._heading
|
||||
cls._charCount = source._charCount
|
||||
cls._wordCount = source._wordCount
|
||||
cls._paraCount = source._paraCount
|
||||
cls._cursorPos = source._cursorPos
|
||||
cls._initCount = source._initCount
|
||||
return cls
|
||||
|
||||
##
|
||||
# Lookup Methods
|
||||
##
|
||||
@@ -387,14 +398,6 @@ class NWItem:
|
||||
self._name = ""
|
||||
return
|
||||
|
||||
def setHandle(self, handle: Any) -> None:
|
||||
"""Set the item handle, and ensure it is valid."""
|
||||
if isHandle(handle):
|
||||
self._handle = handle
|
||||
else:
|
||||
self._handle = None
|
||||
return
|
||||
|
||||
def setParent(self, handle: Any) -> None:
|
||||
"""Set the parent handle, and ensure it is valid."""
|
||||
if handle is None:
|
||||
|
||||
+19
-56
@@ -38,7 +38,6 @@ from novelwriter.enum import nwItemType, nwItemClass, nwItemLayout, nwAlert
|
||||
from novelwriter.error import logException
|
||||
from novelwriter.constants import trConst, nwLabels
|
||||
from novelwriter.core.tree import NWTree
|
||||
from novelwriter.core.item import NWItem
|
||||
from novelwriter.core.index import NWIndex
|
||||
from novelwriter.core.options import OptionState
|
||||
from novelwriter.core.storage import NWStorage
|
||||
@@ -130,42 +129,20 @@ class NWProject(QObject):
|
||||
# Item Methods
|
||||
##
|
||||
|
||||
def newRoot(self, itemClass, label=None):
|
||||
"""Add a new root item. If label is None, use the class label.
|
||||
def newRoot(self, itemClass: nwItemClass, label: str | None = None) -> str:
|
||||
"""Add a new root folder to the project. If label is not set,
|
||||
use the class label.
|
||||
"""
|
||||
if label is None:
|
||||
label = trConst(nwLabels.CLASS_NAME[itemClass])
|
||||
newItem = NWItem(self)
|
||||
newItem.setName(label)
|
||||
newItem.setType(nwItemType.ROOT)
|
||||
newItem.setClass(itemClass)
|
||||
self._tree.append(None, None, newItem)
|
||||
self._tree.updateItemData(newItem.itemHandle)
|
||||
return newItem.itemHandle
|
||||
label = label or trConst(nwLabels.CLASS_NAME[itemClass])
|
||||
return self._tree.create(label, None, nwItemType.ROOT, itemClass)
|
||||
|
||||
def newFolder(self, label, pHandle):
|
||||
"""Add a new folder with a given label and parent item.
|
||||
"""
|
||||
if pHandle not in self._tree:
|
||||
return None
|
||||
newItem = NWItem(self)
|
||||
newItem.setName(label)
|
||||
newItem.setType(nwItemType.FOLDER)
|
||||
self._tree.append(None, pHandle, newItem)
|
||||
self._tree.updateItemData(newItem.itemHandle)
|
||||
return newItem.itemHandle
|
||||
def newFolder(self, label: str, parent: str) -> str | None:
|
||||
"""Add a new folder with a given label and parent item."""
|
||||
return self._tree.create(label, parent, nwItemType.FOLDER)
|
||||
|
||||
def newFile(self, label, pHandle):
|
||||
"""Add a new file with a given label and parent item.
|
||||
"""
|
||||
if pHandle not in self._tree:
|
||||
return None
|
||||
newItem = NWItem(self)
|
||||
newItem.setName(label)
|
||||
newItem.setType(nwItemType.FILE)
|
||||
self._tree.append(None, pHandle, newItem)
|
||||
self._tree.updateItemData(newItem.itemHandle)
|
||||
return newItem.itemHandle
|
||||
def newFile(self, label: str, parent: str) -> str | None:
|
||||
"""Add a new file with a given label and parent item."""
|
||||
return self._tree.create(label, parent, nwItemType.FILE)
|
||||
|
||||
def writeNewFile(self, tHandle, hLevel, isDocument, addText=""):
|
||||
"""Write content to a new document after it is created. This
|
||||
@@ -211,18 +188,11 @@ class NWProject(QObject):
|
||||
return True
|
||||
|
||||
def trashFolder(self):
|
||||
"""Add the special trash root folder to the project.
|
||||
"""
|
||||
"""Add the special trash root folder to the project."""
|
||||
trashHandle = self._tree.trashRoot()
|
||||
if trashHandle is None:
|
||||
newItem = NWItem(self)
|
||||
newItem.setName(trConst(nwLabels.CLASS_NAME[nwItemClass.TRASH]))
|
||||
newItem.setType(nwItemType.ROOT)
|
||||
newItem.setClass(nwItemClass.TRASH)
|
||||
self._tree.append(None, None, newItem)
|
||||
self._tree.updateItemData(newItem.itemHandle)
|
||||
return newItem.itemHandle
|
||||
|
||||
label = trConst(nwLabels.CLASS_NAME[nwItemClass.TRASH])
|
||||
return self._tree.create(label, None, nwItemType.ROOT, nwItemClass.TRASH)
|
||||
return trashHandle
|
||||
|
||||
##
|
||||
@@ -769,11 +739,8 @@ class NWProject(QObject):
|
||||
oName = self.tr("Recovered File {0}").format(nOrph)
|
||||
|
||||
# Recover file meta data
|
||||
if oClass is None:
|
||||
oClass = nwItemClass.NOVEL
|
||||
|
||||
if oLayout is None:
|
||||
oLayout = nwItemLayout.NOTE
|
||||
oClass = oClass or nwItemClass.NOVEL
|
||||
oLayout = oLayout or nwItemLayout.NOTE
|
||||
|
||||
if oParent is None or oParent not in self._tree:
|
||||
oParent = self._tree.findRoot(oClass)
|
||||
@@ -785,13 +752,9 @@ class NWProject(QObject):
|
||||
noWhere = True
|
||||
continue
|
||||
|
||||
orphItem = NWItem(self)
|
||||
orphItem.setName(oName)
|
||||
orphItem.setType(nwItemType.FILE)
|
||||
orphItem.setClass(oClass)
|
||||
orphItem.setLayout(oLayout)
|
||||
self._tree.append(oHandle, oParent, orphItem)
|
||||
self._tree.updateItemData(orphItem.itemHandle)
|
||||
nHandle = self._tree.create(oName, oParent, nwItemType.FILE, oClass, oLayout)
|
||||
if nHandle is not None:
|
||||
(contentPath / f"{oHandle}.nwd").rename(contentPath / f"{nHandle}.nwd")
|
||||
|
||||
if noWhere:
|
||||
self.mainGui.makeAlert(self.tr(
|
||||
|
||||
+61
-18
@@ -1,7 +1,6 @@
|
||||
"""
|
||||
novelWriter – Project Tree Class
|
||||
================================
|
||||
Data class for the project's tree of project items
|
||||
|
||||
File History:
|
||||
Created: 2020-05-07 [0.4.5]
|
||||
@@ -24,16 +23,15 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import copy
|
||||
import random
|
||||
import logging
|
||||
|
||||
from typing import TYPE_CHECKING, Iterator
|
||||
from typing import TYPE_CHECKING, Iterator, overload
|
||||
from pathlib import Path
|
||||
|
||||
from novelwriter.enum import nwItemClass, nwItemLayout, nwItemType
|
||||
from novelwriter.error import logException
|
||||
from novelwriter.common import checkHandle
|
||||
from novelwriter.common import isHandle
|
||||
from novelwriter.constants import nwFiles
|
||||
from novelwriter.core.item import NWItem
|
||||
|
||||
@@ -44,6 +42,22 @@ logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class NWTree:
|
||||
"""Core: Project Tree Data Class
|
||||
|
||||
Only one instance of this class should exist in the project class.
|
||||
This class holds all the project items of the project as instances
|
||||
of NWItem.
|
||||
|
||||
For historical reasons, the order of the items is saved in a
|
||||
separate list from the items themselves, which are stored in a
|
||||
dictionary. This is somewhat redundant with the newer versions of
|
||||
Python, but is still practical as it's easier to update the item
|
||||
order as a list.
|
||||
|
||||
Each item has a handle, which is a random hex string of length 13.
|
||||
The handle is the name of the item everywhere in novelWriter, and is
|
||||
also used for file names.
|
||||
"""
|
||||
|
||||
MAX_DEPTH = 1000 # Cap of tree traversing for loops
|
||||
|
||||
@@ -52,7 +66,7 @@ class NWTree:
|
||||
self._project = project
|
||||
|
||||
self._projTree: dict[str, NWItem] = {} # Holds all the items of the project
|
||||
self._treeOrder: list[str] = [] # The order of the tree items on the tree view
|
||||
self._treeOrder: list[str] = [] # The order of the tree items in the tree view
|
||||
self._treeRoots: dict[str, NWItem] = {} # The root items of the tree
|
||||
|
||||
self._trashRoot = None # The handle of the trash root folder
|
||||
@@ -79,12 +93,44 @@ class NWTree:
|
||||
"""Returns a copy of the list of all the active handles."""
|
||||
return self._treeOrder.copy()
|
||||
|
||||
def append(self, tHandle: str | None, pHandle: str | None, nwItem: NWItem) -> bool:
|
||||
"""Add a new item to the end of the tree."""
|
||||
tHandle = checkHandle(tHandle, None, True)
|
||||
pHandle = checkHandle(pHandle, None, True)
|
||||
if tHandle is None:
|
||||
@overload
|
||||
def create(self, label: str, parent: None, itemType: nwItemType,
|
||||
itemClass: nwItemClass = nwItemClass.NO_CLASS,
|
||||
itemLayout: nwItemLayout = nwItemLayout.NO_LAYOUT) -> str:
|
||||
...
|
||||
|
||||
@overload
|
||||
def create(self, label: str, parent: str | None, itemType: nwItemType,
|
||||
itemClass: nwItemClass = nwItemClass.NO_CLASS,
|
||||
itemLayout: nwItemLayout = nwItemLayout.NO_LAYOUT) -> str | None:
|
||||
...
|
||||
|
||||
def create(self, label, parent, itemType,
|
||||
itemClass=nwItemClass.NO_CLASS, itemLayout=nwItemLayout.NO_LAYOUT):
|
||||
"""Create a new item in the project tree, and return its handle.
|
||||
If the item cannot be added to the project, None is returned.
|
||||
"""
|
||||
if parent is None or parent in self._treeOrder:
|
||||
tHandle = self._makeHandle()
|
||||
newItem = NWItem(self._project, tHandle)
|
||||
newItem.setName(label)
|
||||
newItem.setParent(parent)
|
||||
newItem.setType(itemType)
|
||||
newItem.setClass(itemClass)
|
||||
newItem.setLayout(itemLayout)
|
||||
self.append(newItem)
|
||||
self.updateItemData(tHandle)
|
||||
return tHandle
|
||||
return None
|
||||
|
||||
def append(self, nwItem: NWItem) -> bool:
|
||||
"""Add a new item to the end of the tree."""
|
||||
tHandle = nwItem.itemHandle
|
||||
pHandle = nwItem.itemParent
|
||||
|
||||
if not isHandle(tHandle):
|
||||
logger.warning("Invalid item handle '%s' detected, skipping", tHandle)
|
||||
return False
|
||||
|
||||
if tHandle in self._projTree:
|
||||
logger.warning("Duplicate handle '%s' detected, skipping", tHandle)
|
||||
@@ -92,9 +138,6 @@ class NWTree:
|
||||
|
||||
logger.debug("Adding item '%s' with parent '%s'", str(tHandle), str(pHandle))
|
||||
|
||||
nwItem.setHandle(tHandle)
|
||||
nwItem.setParent(pHandle)
|
||||
|
||||
if nwItem.isRootType():
|
||||
logger.debug("Item '%s' is a root item", str(tHandle))
|
||||
self._treeRoots[tHandle] = nwItem
|
||||
@@ -119,8 +162,8 @@ class NWTree:
|
||||
"""Duplicate an item and set a new handle."""
|
||||
sItem = self.__getitem__(sHandle)
|
||||
if isinstance(sItem, NWItem):
|
||||
nItem = copy.copy(sItem)
|
||||
if self.append(None, sItem.itemParent, nItem):
|
||||
nItem = NWItem.duplicate(sItem, self._makeHandle())
|
||||
if self.append(nItem):
|
||||
logger.info("Duplicated item '%s' -> '%s'", sHandle, nItem.itemHandle)
|
||||
return nItem
|
||||
return None
|
||||
@@ -142,9 +185,9 @@ class NWTree:
|
||||
"""
|
||||
self.clear()
|
||||
for item in data:
|
||||
nwItem = NWItem(self._project)
|
||||
nwItem = NWItem(self._project, "NOTSET") # Handle is set by unpack()
|
||||
if nwItem.unpack(item):
|
||||
self.append(nwItem.itemHandle, nwItem.itemParent, nwItem)
|
||||
self.append(nwItem)
|
||||
nwItem.saveInitialCount()
|
||||
return
|
||||
|
||||
@@ -314,7 +357,7 @@ class NWTree:
|
||||
return self._trashRoot
|
||||
return None
|
||||
|
||||
def findRoot(self, itemClass: nwItemClass) -> str | None:
|
||||
def findRoot(self, itemClass: nwItemClass | None) -> str | None:
|
||||
"""Find the first root item for a given class."""
|
||||
for aRoot in self._treeRoots:
|
||||
tItem = self.__getitem__(aRoot)
|
||||
|
||||
Reference in New Issue
Block a user