diff --git a/novelwriter/core/tree.py b/novelwriter/core/tree.py index a9643fc9..fef2759d 100644 --- a/novelwriter/core/tree.py +++ b/novelwriter/core/tree.py @@ -65,13 +65,12 @@ 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 in the tree view - self._treeRoots: dict[str, NWItem] = {} # The root items of the tree + self._tree: dict[str, NWItem] = {} # Holds all the items of the project + self._order: list[str] = [] # The order of the tree items in the tree view + self._roots: dict[str, NWItem] = {} # The root items of the tree - self._trashRoot = None # The handle of the trash root folder - self._archRoot = None # The handle of the archive root folder - self._treeChanged = False # True if tree structure has changed + self._trash = None # The handle of the trash root folder + self._changed = False # True if tree structure has changed return @@ -81,17 +80,16 @@ class NWTree: def clear(self) -> None: """Clear the item tree entirely.""" - self._projTree = {} - self._treeOrder = [] - self._treeRoots = {} - self._trashRoot = None - self._archRoot = None - self._treeChanged = False + self._tree = {} + self._order = [] + self._roots = {} + self._trash = None + self._changed = False return def handles(self) -> list[str]: """Returns a copy of the list of all the active handles.""" - return self._treeOrder.copy() + return self._order.copy() @overload # pragma: no cover def create(self, label: str, parent: None, itemType: Literal[nwItemType.ROOT], @@ -109,7 +107,7 @@ class NWTree: parent, None is returned. For root elements, this cannot occur. """ parent = None if itemType == nwItemType.ROOT else parent - if parent is None or parent in self._treeOrder: + if parent is None or parent in self._order: tHandle = self._makeHandle() newItem = NWItem(self._project, tHandle) newItem.setName(label) @@ -130,7 +128,7 @@ class NWTree: logger.warning("Invalid item handle '%s' detected, skipping", tHandle) return False - if tHandle in self._projTree: + if tHandle in self._tree: logger.warning("Duplicate handle '%s' detected, skipping", tHandle) return False @@ -138,20 +136,17 @@ class NWTree: if nwItem.isRootType(): logger.debug("Item '%s' is a root item", str(tHandle)) - self._treeRoots[tHandle] = nwItem - if nwItem.itemClass == nwItemClass.ARCHIVE: - logger.debug("Item '%s' is the archive folder", str(tHandle)) - self._archRoot = tHandle - elif nwItem.itemClass == nwItemClass.TRASH: - if self._trashRoot is None: + self._roots[tHandle] = nwItem + if nwItem.itemClass == nwItemClass.TRASH: + if self._trash is None: logger.debug("Item '%s' is the trash folder", str(tHandle)) - self._trashRoot = tHandle + self._trash = tHandle else: logger.error("Only one trash folder allowed") return False - self._projTree[tHandle] = nwItem - self._treeOrder.append(tHandle) + self._tree[tHandle] = nwItem + self._order.append(tHandle) self._setTreeChanged(True) return True @@ -171,7 +166,7 @@ class NWTree: items. In the order defined by the _treeOrder list. """ tree = [] - for tHandle in self._treeOrder: + for tHandle in self._order: tItem = self.__getitem__(tHandle) if tItem: tree.append(tItem.pack()) @@ -199,7 +194,7 @@ class NWTree: """ storage = self._project.storage files = set(storage.scanContent()) - for tHandle in self._treeOrder: + for tHandle in self._order: if self.updateItemData(tHandle): logger.debug("Checking item '%s' ... OK", tHandle) files.discard(tHandle) # Remove it from the record @@ -220,7 +215,7 @@ class NWTree: oName, oParent, oClass, oLayout = aDoc.getMeta() oName = oName or cHandle - oParent = oParent if oParent in self._treeOrder else None + oParent = oParent if oParent in self._order else None oClass = oClass or nwItemClass.NOVEL oLayout = oLayout or nwItemLayout.NOTE @@ -258,7 +253,7 @@ class NWTree: tocList = [] tocLen = 0 - for tHandle in self._treeOrder: + for tHandle in self._order: tItem = self.__getitem__(tHandle) if tItem is None: continue @@ -300,7 +295,7 @@ class NWTree: """Loop over all entries and add up the word counts.""" noteWords = 0 novelWords = 0 - for tHandle in self._treeOrder: + for tHandle in self._order: tItem = self.__getitem__(tHandle) if tItem is None: continue @@ -376,13 +371,13 @@ class NWTree: def rootClasses(self) -> set[nwItemClass]: """Return a set of all root classes in use by the project.""" rootClasses = set() - for nwItem in self._treeRoots.values(): + for nwItem in self._roots.values(): rootClasses.add(nwItem.itemClass) return rootClasses def iterRoots(self, itemClass: nwItemClass | None) -> Iterator[tuple[str, NWItem]]: """Iterate over all root items of a given class in order.""" - for tHandle in self._treeOrder: + for tHandle in self._order: nwItem = self.__getitem__(tHandle) if isinstance(nwItem, NWItem) and nwItem.isRootType(): if itemClass is None or nwItem.itemClass == itemClass: @@ -396,12 +391,12 @@ class NWTree: return True if tItem.itemClass == nwItemClass.TRASH: return True - if self._trashRoot is not None: - if tHandle == self._trashRoot: + if self._trash is not None: + if tHandle == self._trash: return True - elif tItem.itemParent == self._trashRoot: + elif tItem.itemParent == self._trash: return True - elif tItem.itemRoot == self._trashRoot: + elif tItem.itemRoot == self._trash: return True return False @@ -409,13 +404,13 @@ class NWTree: """Returns the handle of the trash folder, or None if there isn't one. """ - if self._trashRoot: - return self._trashRoot + if self._trash: + return self._trash return None def findRoot(self, itemClass: nwItemClass | None) -> str | None: """Find the first root item for a given class.""" - for aRoot in self._treeRoots: + for aRoot in self._roots: tItem = self.__getitem__(aRoot) if tItem is None: continue @@ -429,18 +424,18 @@ class NWTree: def setOrder(self, newOrder: list[str]) -> None: """Reorders the tree based on a list of items.""" - tmpOrder = [tHandle for tHandle in newOrder if tHandle in self._projTree] - if not (len(tmpOrder) == len(newOrder) == len(self._treeOrder)): + tmpOrder = [tHandle for tHandle in newOrder if tHandle in self._tree] + if not (len(tmpOrder) == len(newOrder) == len(self._order)): # Something is wrong, so let's debug it for tHandle in newOrder: - if tHandle not in self._projTree: + if tHandle not in self._tree: logger.error("Handle '%s' in new tree order is not in old order", tHandle) - for tHandle in self._treeOrder: + for tHandle in self._order: if tHandle not in tmpOrder: logger.warning("Handle '%s' in old tree order is not in new order", tHandle) # Save the temp list - self._treeOrder = tmpOrder + self._order = tmpOrder self._setTreeChanged(True) logger.debug("Project tree order updated") @@ -452,36 +447,34 @@ class NWTree: def __len__(self) -> int: """The number of items in the project.""" - return len(self._treeOrder) + return len(self._order) def __bool__(self) -> bool: """True if there are any items in the project.""" - return bool(self._treeOrder) + return bool(self._order) def __getitem__(self, tHandle: str | None) -> NWItem | None: """Return a project item based on its handle. Returns None if the handle doesn't exist in the project. """ - if tHandle and tHandle in self._projTree: - return self._projTree[tHandle] + if tHandle and tHandle in self._tree: + return self._tree[tHandle] logger.error("No tree item with handle '%s'", str(tHandle)) return None def __delitem__(self, tHandle: str) -> None: """Remove an item from the internal lists and dictionaries.""" - if tHandle in self._treeOrder and tHandle in self._projTree: - self._treeOrder.remove(tHandle) - del self._projTree[tHandle] + if tHandle in self._order and tHandle in self._tree: + self._order.remove(tHandle) + del self._tree[tHandle] else: logger.warning("Failed to delete item '%s': item not found", tHandle) return - if tHandle in self._treeRoots: - del self._treeRoots[tHandle] - if tHandle == self._trashRoot: - self._trashRoot = None - if tHandle == self._archRoot: - self._archRoot = None + if tHandle in self._roots: + del self._roots[tHandle] + if tHandle == self._trash: + self._trash = None self._setTreeChanged(True) @@ -489,12 +482,12 @@ class NWTree: def __contains__(self, tHandle: str) -> bool: """Checks if a handle exists in the tree.""" - return tHandle in self._treeOrder + return tHandle in self._order def __iter__(self) -> Iterator[NWItem]: """Iterate through project items.""" - for tHandle in self._treeOrder: - tItem = self._projTree.get(tHandle) + for tHandle in self._order: + tItem = self._tree.get(tHandle) if isinstance(tItem, NWItem): yield tItem return @@ -507,7 +500,7 @@ class NWTree: """Set the changed flag to theState, and if being set to True, propagate that state change to the parent NWProject class. """ - self._treeChanged = state + self._changed = state if state: self._project.setProjectChanged(True) return @@ -518,7 +511,7 @@ class NWTree: """ logger.debug("Generating new handle") handle = f"{random.getrandbits(52):013x}" - if handle in self._projTree: + if handle in self._tree: logger.warning("Duplicate handle encountered! Retrying ...") handle = self._makeHandle() diff --git a/tests/test_core/test_core_coretools.py b/tests/test_core/test_core_coretools.py index 6a39d96b..c5c90023 100644 --- a/tests/test_core/test_core_coretools.py +++ b/tests/test_core/test_core_coretools.py @@ -290,7 +290,7 @@ def testCoreTools_DocDuplicator(mockGUI, fncPath, tstPaths, mockRnd): assert list(dup.duplicate([C.hSceneDoc])) == [ ("0000000000010", C.hSceneDoc), # The Scene ] - assert theProject.tree._treeOrder == [ + assert theProject.tree._order == [ C.hNovelRoot, C.hPlotRoot, C.hCharRoot, C.hWorldRoot, C.hTitlePage, C.hChapterDir, C.hChapterDoc, C.hSceneDoc, "0000000000010", @@ -311,7 +311,7 @@ def testCoreTools_DocDuplicator(mockGUI, fncPath, tstPaths, mockRnd): ("0000000000012", None), # The Chapter ("0000000000013", None), # The Scene ] - assert theProject.tree._treeOrder == [ + assert theProject.tree._order == [ C.hNovelRoot, C.hPlotRoot, C.hCharRoot, C.hWorldRoot, C.hTitlePage, C.hChapterDir, C.hChapterDoc, C.hSceneDoc, "0000000000010", @@ -342,7 +342,7 @@ def testCoreTools_DocDuplicator(mockGUI, fncPath, tstPaths, mockRnd): ("0000000000017", None), # The Chapter ("0000000000018", None), # The Scene ] - assert theProject.tree._treeOrder == [ + assert theProject.tree._order == [ C.hNovelRoot, C.hPlotRoot, C.hCharRoot, C.hWorldRoot, C.hTitlePage, C.hChapterDir, C.hChapterDoc, C.hSceneDoc, "0000000000010", diff --git a/tests/test_core/test_core_docbuild.py b/tests/test_core/test_core_docbuild.py index 148b1651..4c5e924a 100644 --- a/tests/test_core/test_core_docbuild.py +++ b/tests/test_core/test_core_docbuild.py @@ -421,8 +421,8 @@ def testCoreDocBuild_Custom(mockGUI, fncPath: Path): # Add an invalid item to the project nHandle = "0123456789def" - project.tree._treeOrder.append(nHandle) - project.tree._projTree[nHandle] = None # type: ignore + project.tree._order.append(nHandle) + project.tree._tree[nHandle] = None # type: ignore docBuild.queueAll() assert len(docBuild) == 8 diff --git a/tests/test_core/test_core_index.py b/tests/test_core/test_core_index.py index 409491bf..b9e107e5 100644 --- a/tests/test_core/test_core_index.py +++ b/tests/test_core/test_core_index.py @@ -727,7 +727,7 @@ def testCoreIndex_ExtractData(mockGUI, fncPath, mockRnd): ] # Add a fake handle to the tree and check that it's ignored - theProject.tree._treeOrder.append("0000000000000") + theProject.tree._order.append("0000000000000") assert [(h, t) for h, t, _ in theIndex._itemIndex.iterNovelStructure(skipExcl=False)] == [ (C.hTitlePage, "T0001"), (C.hChapterDoc, "T0001"), @@ -738,7 +738,7 @@ def testCoreIndex_ExtractData(mockGUI, fncPath, mockRnd): (sHandle, "T0001"), (tHandle, "T0001"), ] - theProject.tree._treeOrder.remove("0000000000000") + theProject.tree._order.remove("0000000000000") # Extract stats assert theIndex.getNovelWordCount(skipExcl=False) == 43 @@ -1077,7 +1077,7 @@ def testCoreIndex_ItemIndex(mockGUI, fncPath, mockRnd): assert nStruct[0][0] == uHandle # Inject garbage into tree - theProject.tree._treeOrder.append("stuff") + theProject.tree._order.append("stuff") nStruct = list(itemIndex.iterNovelStructure()) assert len(nStruct) == 4 assert nStruct[0][0] == nHandle diff --git a/tests/test_core/test_core_project.py b/tests/test_core/test_core_project.py index fd20714d..a750e3ba 100644 --- a/tests/test_core/test_core_project.py +++ b/tests/test_core/test_core_project.py @@ -328,7 +328,7 @@ def testCoreProject_AccessItems(mockGUI, fncPath, mockRnd): assert theProject.tree.handles() == newOrder # Add a non-existing item - theProject.tree._treeOrder.append(C.hInvalid) + theProject.tree._order.append(C.hInvalid) # Add an item with a non-existent parent nHandle = theProject.newFile("Test File", C.hChapterDir) diff --git a/tests/test_core/test_core_tree.py b/tests/test_core/test_core_tree.py index cc5a6724..83abd8cb 100644 --- a/tests/test_core/test_core_tree.py +++ b/tests/test_core/test_core_tree.py @@ -127,7 +127,7 @@ def testCoreTree_BuildTree(mockGUI, mockItems): assert theTree.append(nwItem) is True assert theTree.updateItemData(nwItem.itemHandle) is True - assert theTree._treeChanged is True + assert theTree._changed is True # Check that tree is not empty (calls __bool__) assert bool(theTree) is True @@ -413,9 +413,9 @@ def testCoreTree_Methods(mockGUI, mockItems): assert roots[3][0] == "a000000000004" # Add a fake item to root and check that it can handle it - theTree._treeRoots["0000000000000"] = NWItem(theProject, "0000000000000") + theTree._roots["0000000000000"] = NWItem(theProject, "0000000000000") assert theTree.findRoot(nwItemClass.WORLD) is None - del theTree._treeRoots["0000000000000"] + del theTree._roots["0000000000000"] # Get item path assert theTree.getItemPath("stuff") == [] @@ -456,13 +456,13 @@ def testCoreTree_MakeHandles(mockGUI): random.seed(42) tHandle = theTree._makeHandle() assert tHandle == handles[0] - theTree._projTree[handles[0]] = None # type: ignore + theTree._tree[handles[0]] = None # type: ignore # Add the next in line to the project to force duplicate - theTree._projTree[handles[1]] = None # type: ignore + theTree._tree[handles[1]] = None # type: ignore tHandle = theTree._makeHandle() assert tHandle == handles[2] - theTree._projTree[handles[2]] = None # type: ignore + theTree._tree[handles[2]] = None # type: ignore # Reset the seed to force collissions, which should still end up # returning the next handle in the sequence @@ -483,7 +483,7 @@ def testCoreTree_Stats(mockGUI, mockItems): theTree.append(nwItem) assert len(theTree) == len(mockItems) - theTree._treeOrder.append("stuff") + theTree._order.append("stuff") # Count Words novelWords, noteWords = theTree.sumWords() @@ -520,7 +520,7 @@ def testCoreTree_Reorder(caplog, mockGUI, mockItems): assert "Handle 'stuff' in new tree order is not in old order" in caplog.text caplog.clear() - theTree._treeOrder.append("stuff") + theTree._order.append("stuff") theTree.setOrder(bHandle) assert theTree.handles() == bHandle assert "Handle 'stuff' in old tree order is not in new order" in caplog.text @@ -539,7 +539,7 @@ def testCoreTree_ToCFile(monkeypatch, fncPath, mockGUI, mockItems): theTree.updateItemData(nwItem.itemHandle) assert len(theTree) == len(mockItems) - theTree._treeOrder.append("stuff") + theTree._order.append("stuff") def mockIsFile(fileName): """Return True for items that are files in novelWriter and diff --git a/tests/test_gui/test_gui_guimain.py b/tests/test_gui/test_gui_guimain.py index 4f45367c..fd906971 100644 --- a/tests/test_gui/test_gui_guimain.py +++ b/tests/test_gui/test_gui_guimain.py @@ -203,8 +203,8 @@ def testGuiMain_Editing(qtbot, monkeypatch, nwGUI, projPath, tstPaths, mockRnd): assert nwGUI.closeProject() assert len(SHARED.project.tree) == 0 - assert len(SHARED.project.tree._treeOrder) == 0 - assert len(SHARED.project.tree._treeRoots) == 0 + assert len(SHARED.project.tree._order) == 0 + assert len(SHARED.project.tree._roots) == 0 assert SHARED.project.tree.trashRoot() is None assert SHARED.project.data.name == "" assert SHARED.project.data.title == "" @@ -223,8 +223,8 @@ def testGuiMain_Editing(qtbot, monkeypatch, nwGUI, projPath, tstPaths, mockRnd): # Check that we loaded the data assert len(SHARED.project.tree) == 8 - assert len(SHARED.project.tree._treeOrder) == 8 - assert len(SHARED.project.tree._treeRoots) == 4 + assert len(SHARED.project.tree._order) == 8 + assert len(SHARED.project.tree._roots) == 4 assert SHARED.project.tree.trashRoot() is None assert SHARED.project.data.name == "New Project" assert SHARED.project.data.title == "New Novel" diff --git a/tests/test_gui/test_gui_projtree.py b/tests/test_gui/test_gui_projtree.py index f96ad4e3..bfb57a39 100644 --- a/tests/test_gui/test_gui_projtree.py +++ b/tests/test_gui/test_gui_projtree.py @@ -260,19 +260,19 @@ def testGuiProjTree_MoveItems(qtbot, monkeypatch, nwGUI, projPath, mockRnd): # =========== projView.setSelectedHandle(C.hNovelRoot) - assert SHARED.project.tree._treeOrder.index(C.hNovelRoot) == 0 + assert SHARED.project.tree._order.index(C.hNovelRoot) == 0 # Move novel folder up assert projTree.moveTreeItem(-1) is False - assert SHARED.project.tree._treeOrder.index(C.hNovelRoot) == 0 + assert SHARED.project.tree._order.index(C.hNovelRoot) == 0 # Move novel folder down assert projTree.moveTreeItem(1) is True - assert SHARED.project.tree._treeOrder.index(C.hNovelRoot) == 1 + assert SHARED.project.tree._order.index(C.hNovelRoot) == 1 # Move novel folder up again assert projTree.moveTreeItem(-1) is True - assert SHARED.project.tree._treeOrder.index(C.hNovelRoot) == 0 + assert SHARED.project.tree._order.index(C.hNovelRoot) == 0 # Clean up # qtbot.stop() @@ -881,7 +881,7 @@ def testGuiProjTree_Duplicate(qtbot, monkeypatch, nwGUI: GuiMain, projPath, mock assert len(SHARED.project.tree) == 21 # Check tree order that all items are next to eachother - assert SHARED.project.tree._treeOrder == [ + assert SHARED.project.tree._order == [ C.hNovelRoot, C.hTitlePage, "0000000000010", C.hChapterDir, C.hChapterDoc, C.hSceneDoc, "0000000000011", "0000000000012", "0000000000013", "0000000000014", "0000000000015", "0000000000016", "0000000000017", "0000000000018", "0000000000019", "000000000001a",