From e4ad4f38c8137367b16c2a14ea590dc30cae12ac Mon Sep 17 00:00:00 2001 From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com> Date: Sun, 16 Aug 2020 18:39:34 +0200 Subject: [PATCH 1/7] Indexer should still count words for trash and archived files --- nw/core/index.py | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/nw/core/index.py b/nw/core/index.py index 6962dbb7..ca69a2f6 100644 --- a/nw/core/index.py +++ b/nw/core/index.py @@ -268,21 +268,28 @@ class NWIndex(): """ theItem = self.theProject.projTree[tHandle] theRoot = self.theProject.projTree.getRootItem(tHandle) + if theItem is None: logger.error("Not indexing unknown item %s" % tHandle) return False if theItem.itemType != nwItemType.FILE: logger.error("Not indexing non-file item %s" % tHandle) return False - if theItem.parHandle == self.theProject.projTree.trashRoot(): - logger.error("Not indexing trash item %s" % tHandle) - return False if theItem.itemLayout == nwItemLayout.NO_LAYOUT: logger.error("Not indexing no-layout item %s" % tHandle) return False if theRoot is None: logger.error("Not indexing homeless item %s" % tHandle) return False + + # Run word counter for whole text + cC, wC, pC = countWords(theText) + self.textCounts[tHandle] = [cC, wC, pC] + + # If the file is archived or trashed, we don't index the file itself + if theItem.parHandle == self.theProject.projTree.trashRoot(): + logger.error("Not indexing trash item %s" % tHandle) + return False if theRoot.itemClass == nwItemClass.ARCHIVE: logger.error("Not indexing archived item %s" % tHandle) return False @@ -351,10 +358,6 @@ class NWIndex(): lastText = "\n".join(theLines[nTitle-1:nLine-1]) self._indexWordCounts(tHandle, isNovel, lastText, nTitle) - # Run word counter for whole text - cC, wC, pC = countWords(theText) - self.textCounts[tHandle] = [cC, wC, pC] - # Update timestamps for index changes nowTime = time() self.timeIndex = nowTime From 8cd4a6cc2745c47d3820474e36269cd58dbaa2aa Mon Sep 17 00:00:00 2001 From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com> Date: Sun, 16 Aug 2020 18:57:59 +0200 Subject: [PATCH 2/7] Use integer time for the index --- nw/core/index.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/nw/core/index.py b/nw/core/index.py index ca69a2f6..d50533a1 100644 --- a/nw/core/index.py +++ b/nw/core/index.py @@ -176,7 +176,7 @@ class NWIndex(): if "textCounts" in theData.keys(): self.textCounts = theData["textCounts"] - nowTime = time() + nowTime = round(time()) self.timeNovel = nowTime self.timeNote = nowTime self.timeIndex = nowTime @@ -282,7 +282,7 @@ class NWIndex(): logger.error("Not indexing homeless item %s" % tHandle) return False - # Run word counter for whole text + # Run word counter for the whole text cC, wC, pC = countWords(theText) self.textCounts[tHandle] = [cC, wC, pC] @@ -304,7 +304,7 @@ class NWIndex(): self.refIndex[tHandle] = {} self.refIndex[tHandle]["T000000"] = { "tags" : [], - "updated" : time(), + "updated" : round(time()), } if itemLayout == nwItemLayout.NOTE: self.noteIndex[tHandle] = {} @@ -359,7 +359,7 @@ class NWIndex(): self._indexWordCounts(tHandle, isNovel, lastText, nTitle) # Update timestamps for index changes - nowTime = time() + nowTime = round(time()) self.timeIndex = nowTime if isNovel: self.timeNovel = nowTime @@ -394,7 +394,7 @@ class NWIndex(): sTitle = "T%06d" % nLine self.refIndex[tHandle][sTitle] = { "tags" : [], - "updated" : time(), + "updated" : round(time()), } theData = { "level" : hDepth, @@ -404,7 +404,7 @@ class NWIndex(): "cCount" : 0, "wCount" : 0, "pCount" : 0, - "updated" : time(), + "updated" : round(time()), } if hText != "": @@ -428,14 +428,14 @@ class NWIndex(): self.novelIndex[tHandle][sTitle]["cCount"] = cC self.novelIndex[tHandle][sTitle]["wCount"] = wC self.novelIndex[tHandle][sTitle]["pCount"] = pC - self.novelIndex[tHandle][sTitle]["updated"] = time() + self.novelIndex[tHandle][sTitle]["updated"] = round(time()) else: if tHandle in self.noteIndex: if sTitle in self.noteIndex[tHandle]: self.noteIndex[tHandle][sTitle]["cCount"] = cC self.noteIndex[tHandle][sTitle]["wCount"] = wC self.noteIndex[tHandle][sTitle]["pCount"] = pC - self.noteIndex[tHandle][sTitle]["updated"] = time() + self.noteIndex[tHandle][sTitle]["updated"] = round(time()) return def _indexSynopsis(self, tHandle, isNovel, theText, nTitle): @@ -446,12 +446,12 @@ class NWIndex(): if tHandle in self.novelIndex: if sTitle in self.novelIndex[tHandle]: self.novelIndex[tHandle][sTitle]["synopsis"] = theText - self.novelIndex[tHandle][sTitle]["updated"] = time() + self.novelIndex[tHandle][sTitle]["updated"] = round(time()) else: if tHandle in self.noteIndex: if sTitle in self.noteIndex[tHandle]: self.noteIndex[tHandle][sTitle]["synopsis"] = theText - self.noteIndex[tHandle][sTitle]["updated"] = time() + self.noteIndex[tHandle][sTitle]["updated"] = round(time()) return def _indexNoteRef(self, tHandle, aLine, nLine, nTitle): From c19c6246529a22d8937032981efe0e9ff32f6038 Mon Sep 17 00:00:00 2001 From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com> Date: Sun, 16 Aug 2020 21:17:58 +0200 Subject: [PATCH 3/7] Allow folders in Outtakes root folder as it seems to be working as intended with the current logic --- nw/gui/projtree.py | 51 +++++++++++++++++++++++++++------------------- 1 file changed, 30 insertions(+), 21 deletions(-) diff --git a/nw/gui/projtree.py b/nw/gui/projtree.py index ab2fe52b..6fadb199 100644 --- a/nw/gui/projtree.py +++ b/nw/gui/projtree.py @@ -59,6 +59,7 @@ class GuiProjectTree(QTreeWidget): self.theParent = theParent self.theTheme = theParent.theTheme self.theProject = theParent.theProject + self.theIndex = theParent.theIndex # Tree Settings self.theMap = None @@ -145,6 +146,9 @@ class GuiProjectTree(QTreeWidget): if not self.theParent.hasProject: return False + # The item needs to be assigned an item class, so one must be + # provided, or it must be possible to extract it from the parent + # item of the new item. if itemClass is None and pHandle is not None: pItem = self.theProject.projTree[pHandle] if pItem is not None: @@ -167,6 +171,7 @@ class GuiProjectTree(QTreeWidget): self.makeAlert("Failed to add new item.", nwAlert.BUG) return False + # Everything is fine, we have what we need, so we proceed logger.verbose("Adding new item of type %s and class %s to handle %s" % ( itemType.name, itemClass.name, str(pHandle)) ) @@ -182,7 +187,9 @@ class GuiProjectTree(QTreeWidget): # If still nothing, give up if pHandle is None: - logger.error("Did not find anywhere to add the item!") + self.makeAlert( + "Did not find anywhere to add the file or folder!", nwAlert.ERROR + ) return False # Now check if the selected item is a file, in which case @@ -207,14 +214,6 @@ class GuiProjectTree(QTreeWidget): ) return False - if pItem.itemClass == nwItemClass.ARCHIVE: - self.makeAlert( - "Cannot add new files or folders to the %s folder." % ( - nwLabels.CLASS_NAME[nwItemClass.ARCHIVE] - ), nwAlert.ERROR - ) - return False - # If we're still here, add the file or folder if itemType == nwItemType.FILE: tHandle = self.theProject.newFile("New File", itemClass, pHandle) @@ -226,7 +225,6 @@ class GuiProjectTree(QTreeWidget): # Add the new item to the tree self.revealTreeItem(tHandle, nHandle) - self.theParent.editItem() return True @@ -332,7 +330,9 @@ class GuiProjectTree(QTreeWidget): logger.debug("Emptying Trash folder") if trashHandle is None: - self.makeAlert("There is no Trash folder.", nwAlert.INFO) + self.makeAlert( + "There is currently no Trash folder in this project.", nwAlert.INFO + ) return False theTrash = self.getTreeFromHandle(trashHandle) @@ -341,13 +341,13 @@ class GuiProjectTree(QTreeWidget): nTrash = len(theTrash) if nTrash == 0: - self.makeAlert("The Trash folder is empty.", nwAlert.INFO) + self.makeAlert("The Trash folder is already empty.", nwAlert.INFO) return False msgBox = QMessageBox() msgRes = msgBox.question( self, "Empty Trash", "Permanently delete %d file%s from Trash?" % ( - nTrash, "s"*int(nTrash > 1) + nTrash, "s" if nTrash > 1 else "" ) ) if msgRes != QMessageBox.Yes: @@ -420,7 +420,7 @@ class GuiProjectTree(QTreeWidget): theDoc = NWDoc(self.theProject, self.theParent) theDoc.deleteDocument(tHandle) del self.theProject.projTree[tHandle] - self.theParent.theIndex.deleteHandle(tHandle) + self.theIndex.deleteHandle(tHandle) else: # The file is not already in the trash folder, so we @@ -448,7 +448,7 @@ class GuiProjectTree(QTreeWidget): self.propagateCount(tHandle, wCount) self._setTreeChanged(True) - self.theParent.theIndex.deleteHandle(tHandle) + self.theIndex.deleteHandle(tHandle) elif nwItemS.itemType == nwItemType.FOLDER: logger.debug("User requested folder %s deleted" % tHandle) @@ -461,7 +461,11 @@ class GuiProjectTree(QTreeWidget): trItemP.takeChild(tIndex) del self.theProject.projTree[tHandle] else: - self.makeAlert("Cannot delete folder. It is not empty.", nwAlert.ERROR) + self.makeAlert(( + "Cannot delete folder. It is not empty. " + "Recursive deletion is not supported. " + "Please delete the content first." + ), nwAlert.ERROR) return False elif nwItemS.itemType == nwItemType.ROOT: @@ -473,7 +477,11 @@ class GuiProjectTree(QTreeWidget): self.theParent.mainMenu.setAvailableRoot() self._setTreeChanged(True) else: - self.makeAlert("Cannot delete root folder. It is not empty.", nwAlert.ERROR) + self.makeAlert(( + "Cannot delete root folder. It is not empty. " + "Recursive deletion is not supported. " + "Please delete the content first." + ), nwAlert.ERROR) return False return True @@ -710,11 +718,12 @@ class GuiProjectTree(QTreeWidget): self.propagateCount(sHandle, wCount) # The items dropped into archive or trash should be removed - # from the project index + # from the project index, for all other items, we rescan the + # file to ensure the index is up to date. if onFree: - self.theParent.theIndex.deleteHandle(sHandle) + self.theIndex.deleteHandle(sHandle) else: - self.theParent.theIndex.reIndexHandle(sHandle) + self.theIndex.reIndexHandle(sHandle) else: logger.debug("Drag'n'drop of item %s not accepted" % sHandle) @@ -964,7 +973,7 @@ class GuiProjectTreeMenu(QMenu): showEdit = not isTrash and not isOrph showExport = isFile and not inTrash and not isOrph showNewFile = not isTrash and not inTrash and not isOrph and not isArch - showNewFolder = not isTrash and not inTrash and not isOrph and not isArch + showNewFolder = not isTrash and not inTrash and not isOrph showDelete = not isTrash showEmpty = isTrash From efc7078a22c50c77769e01ff0230e7a61e8aedd6 Mon Sep 17 00:00:00 2001 From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com> Date: Sun, 16 Aug 2020 21:24:04 +0200 Subject: [PATCH 4/7] Updated documentation --- docs/source/interface.rst | 13 +++++++------ docs/source/projects.rst | 10 +++++----- 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/docs/source/interface.rst b/docs/source/interface.rst index d0435abf..48b27691 100644 --- a/docs/source/interface.rst +++ b/docs/source/interface.rst @@ -48,9 +48,10 @@ addition to the word count. Project Tree Drag and Drop -------------------------- -The project tree allows dragging and drop to a certain extent, but it does not allow bulk actions. -This is deliberate to avoid accidentally messing up your project. The project tree has no undo -function. +The project tree allows dragging and drop to a certain extent. This feature is primarily intended +for rearranging the order of your files within each root folder, and has only limited support for +moving files elsewhere in the project tree. In general, bulk actions are not allowed. This is +deliberate to avoid accidentally messing up your project. The project tree has no undo function. Document files and their folders can be rearranged freely within their root folders. Novel files cannot be moved out of the :guilabel:`Novel` folder, except to :guilabel:`Trash` and the @@ -59,9 +60,9 @@ cannot be moved out of the :guilabel:`Novel` folder, except to :guilabel:`Trash` Folders cannot be moved at all outside their root tree. Neither can a folder containing files be deleted. You must first delete the files. -Root folders in the project tree cannot be dragged and dropped. However, if you want to reorder -them, you can move them up or down with respect to eachother from the :guilabel:`Tools` menu, or by -pressing :kbd:`Ctrl`:kbd:`Shift` and the :kbd:`Up` or :kbd:`Down` key. +Root folders in the project tree cannot be dragged and dropped at all. However, if you want to +reorder them, you can move them up or down with respect to eachother from the :guilabel:`Tools` +menu, or by pressing :kbd:`Ctrl`:kbd:`Shift` and the :kbd:`Up` or :kbd:`Down` key. .. _a_ui_edit: diff --git a/docs/source/projects.rst b/docs/source/projects.rst index 199ed904..76f0fe2c 100644 --- a/docs/source/projects.rst +++ b/docs/source/projects.rst @@ -103,13 +103,13 @@ Archived Documents (Outtakes) If you don't want to delete a file, or put it in the :guilabel:`Trash` folder where it may be deleted, but still want it out of your main project tree, you can create an :guilabel:`Outtakes` -root folder from the :guilabel:`project` menu. This folder is similar to the :guilabel:`Trash` -folder in that it doesn't allow subfolders and creating new files. +root folder from the :guilabel:`Project` menu. You are not allowed to move folders to this root +folder, only files. If you need folders in it to organise your files, you can of course create new +ones there. You can drag any file to this folder and preserve its settings. The file will always be excluded -from the :guilabel:`Build Novel Project` builds, even with the setting to ignore the excluded files -option. The file is also removed from the project index, so the tags and references defined in it -will not show up anywhere else. +from the :guilabel:`Build Novel Project` builds. The file is also removed from the project index, so +the tags and references defined in it will not show up anywhere else. .. _a_proj_roots_orph: From f21036d1f02c7407334bb975fc05ae0f8bd607df Mon Sep 17 00:00:00 2001 From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com> Date: Sun, 16 Aug 2020 21:26:31 +0200 Subject: [PATCH 5/7] Minor tweaks in docs --- docs/source/projects.rst | 6 ++---- sample/nwProject.nwx | 29 ++++++++++++++++++----------- 2 files changed, 20 insertions(+), 15 deletions(-) diff --git a/docs/source/projects.rst b/docs/source/projects.rst index 76f0fe2c..c25aa13b 100644 --- a/docs/source/projects.rst +++ b/docs/source/projects.rst @@ -90,10 +90,8 @@ trash folder can then be deleted permanently, either individually, or by emptyin menu. Files in this folder are removed from the project index and cannot be referenced. Folders and root folders can only be deleted when they are empty. Recursive deletion is not -supported. - -A document file or a folder can be deleted from the :guilabel:`project` menu, or by pressing -:kbd:`Ctrl`:kbd:`Del`. +supported. A document file or a folder can be deleted from the :guilabel:`project` menu, or by +pressing :kbd:`Ctrl`:kbd:`Del`. .. _a_proj_roots_out: diff --git a/sample/nwProject.nwx b/sample/nwProject.nwx index 05c459a5..01631b07 100644 --- a/sample/nwProject.nwx +++ b/sample/nwProject.nwx @@ -1,13 +1,13 @@ - + Sample Project Sample Project Jane Smith Jay Doh - 713 - 138 - 35134 + 723 + 140 + 35485 False @@ -15,8 +15,8 @@ True 636b6aa9b697b 636b6aa9b697b - 1071 - 695 + 1077 + 701 376 B @@ -46,7 +46,7 @@ Main - + Novel ROOT @@ -269,7 +269,14 @@ None True - + + Scenes + FOLDER + ARCHIVE + None + True + + Old File FILE NOVEL @@ -295,9 +302,9 @@ New True SCENE - 0 - 0 - 0 + 30 + 6 + 1 36 From 850ac2b2f3cdd87534c17dfca668a5837a06a365 Mon Sep 17 00:00:00 2001 From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com> Date: Sun, 16 Aug 2020 21:35:22 +0200 Subject: [PATCH 6/7] Put back the edit item popup as it blocks runaway file or folder creation --- nw/gui/projtree.py | 1 + 1 file changed, 1 insertion(+) diff --git a/nw/gui/projtree.py b/nw/gui/projtree.py index 6fadb199..a84e7176 100644 --- a/nw/gui/projtree.py +++ b/nw/gui/projtree.py @@ -119,6 +119,7 @@ class GuiProjectTree(QTreeWidget): # Internal Mapping self.makeAlert = self.theParent.makeAlert + self.theParent.editItem() return From 619810544aa6a44b7df527e016a310b73d08e327 Mon Sep 17 00:00:00 2001 From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com> Date: Sun, 16 Aug 2020 21:55:36 +0200 Subject: [PATCH 7/7] Reduce the defacto maximum folder depth from ~200 to a hard 30 and enforce it --- nw/constants/constants.py | 2 ++ nw/core/document.py | 4 ++-- nw/core/tree.py | 10 +++++----- nw/gui/projtree.py | 18 +++++++++++++++--- 4 files changed, 24 insertions(+), 10 deletions(-) diff --git a/nw/constants/constants.py b/nw/constants/constants.py index a8859873..7c25fa46 100644 --- a/nw/constants/constants.py +++ b/nw/constants/constants.py @@ -33,6 +33,8 @@ class nwConst(): fStampFmt = "%Y-%m-%d %H.%M.%S" # FileName safe format dStampFmt = "%Y-%m-%d" # Date only format + maxDepth = 30 # Maximum folder depth of a project + # END Class nwConst class nwRegEx(): diff --git a/nw/core/document.py b/nw/core/document.py index 022e608e..9aec16f6 100644 --- a/nw/core/document.py +++ b/nw/core/document.py @@ -33,7 +33,7 @@ from os import path, rename, unlink from nw.core.item import NWItem from nw.constants import nwAlert from nw.common import isHandle -from nw.constants import nwItemLayout, nwItemClass +from nw.constants import nwItemLayout, nwItemClass, nwConst logger = logging.getLogger(__name__) @@ -213,7 +213,7 @@ class NWDoc(): # Scan for handles thePath = [] - for n in range(200): + for n in range(nwConst.maxDepth + 5): if len(theMeta) < 14: break if theMeta[13] == ":": diff --git a/nw/core/tree.py b/nw/core/tree.py index 7d1bef46..0fcf2061 100644 --- a/nw/core/tree.py +++ b/nw/core/tree.py @@ -35,7 +35,7 @@ from time import time from nw.core.item import NWItem from nw.common import checkString -from nw.constants import nwFiles, nwItemType, nwItemClass, nwItemLayout +from nw.constants import nwFiles, nwItemType, nwItemClass, nwItemLayout, nwConst logger = logging.getLogger(__name__) @@ -251,11 +251,11 @@ class NWTree(): 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. + maximum depth to make infinite loops impossible. """ tItem = self.__getitem__(tHandle) if tItem is not None: - for i in range(200): + for i in range(nwConst.maxDepth + 1): if tItem.parHandle is None: return tItem else: @@ -266,14 +266,14 @@ class NWTree(): 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 + We do this with a for loop with a maximum depth to make infinite loops impossible. """ tTree = [] tItem = self.__getitem__(tHandle) if tItem is not None: tTree.append(tHandle) - for i in range(200): + for i in range(nwConst.maxDepth + 1): if tItem.parHandle is None: return tTree else: diff --git a/nw/gui/projtree.py b/nw/gui/projtree.py index a84e7176..2eaf832c 100644 --- a/nw/gui/projtree.py +++ b/nw/gui/projtree.py @@ -38,7 +38,7 @@ from PyQt5.QtWidgets import ( from nw.core import NWDoc from nw.constants import ( - nwLabels, nwItemType, nwItemClass, nwItemLayout, nwAlert + nwLabels, nwItemType, nwItemClass, nwItemLayout, nwAlert, nwConst ) logger = logging.getLogger(__name__) @@ -119,7 +119,6 @@ class GuiProjectTree(QTreeWidget): # Internal Mapping self.makeAlert = self.theParent.makeAlert - self.theParent.editItem() return @@ -215,17 +214,30 @@ class GuiProjectTree(QTreeWidget): ) return False + parTree = self.theProject.projTree.getItemPath(pHandle) + # If we're still here, add the file or folder if itemType == nwItemType.FILE: tHandle = self.theProject.newFile("New File", itemClass, pHandle) + elif itemType == nwItemType.FOLDER: + if len(parTree) >= nwConst.maxDepth - 1: + # Folders cannot be deeper than maxDepth - 1, leaving room + # for one more level of files. + self.makeAlert(( + "Cannot add new folder to this item. " + "Maximum folder depth has been reached." + ), nwAlert.ERROR) + return False tHandle = self.theProject.newFolder("New Folder", itemClass, pHandle) + else: logger.error("Failed to add new item") return False # Add the new item to the tree self.revealTreeItem(tHandle, nHandle) + self.theParent.editItem() return True @@ -541,7 +553,7 @@ class GuiProjectTree(QTreeWidget): pCount += int(pItem.child(i).text(self.C_COUNT)) pHandle = pItem.data(self.C_NAME, Qt.UserRole) - if not nDepth > 200 and pHandle != "": + if not nDepth > nwConst.maxDepth + 1 and pHandle != "": self.propagateCount(pHandle, pCount, nDepth+1) return