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