From 31fac7bf4513ec70a0d9ac4ebbec334ae1cc609c Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Sat, 23 Apr 2022 15:28:26 +0200 Subject: [PATCH 01/10] Make the test suite less spammy again --- tests/conftest.py | 4 +--- tests/test_gui/test_gui_projtree.py | 4 ---- 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index c6634f7cf..769f855c8 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -164,9 +164,7 @@ def nwGUI(qtbot, monkeypatch, fncDir, fncConf): """ monkeypatch.setattr(QMessageBox, "warning", lambda *a: QMessageBox.Yes) monkeypatch.setattr("novelwriter.CONFIG", fncConf) - nwGUI = novelwriter.main( - ["--testmode", "--info", "--config=%s" % fncDir, "--data=%s" % fncDir] - ) + nwGUI = novelwriter.main(["--testmode", "--config=%s" % fncDir, "--data=%s" % fncDir]) qtbot.addWidget(nwGUI) nwGUI.show() qtbot.wait(20) diff --git a/tests/test_gui/test_gui_projtree.py b/tests/test_gui/test_gui_projtree.py index d7d44a78c..845630868 100644 --- a/tests/test_gui/test_gui_projtree.py +++ b/tests/test_gui/test_gui_projtree.py @@ -304,9 +304,7 @@ def testGuiProjTree_DeleteItems(qtbot, caplog, monkeypatch, nwGUI, fncDir, mockR # Delete item without focus -> blocked monkeypatch.setattr(GuiProjectTree, "hasFocus", lambda *a: False) nwTree.setSelectedHandle("0000000000012") - caplog.clear() assert nwTree.deleteItem() is False - assert "blocked" in caplog.text monkeypatch.setattr(GuiProjectTree, "hasFocus", lambda *a: True) # No selection made @@ -399,10 +397,8 @@ def testGuiProjTree_DeleteItems(qtbot, caplog, monkeypatch, nwGUI, fncDir, mockR # =========== # Try to empty trash that is already empty - caplog.clear() assert nwTree.getTreeFromHandle(trashHandle) == [trashHandle] assert nwTree.emptyTrash() is False - assert "already empty" in caplog.text # Move the two remaining scene documents to trash assert nwTree.deleteItem("000000000000f") is True From 64da2793ac061e278319b3f6f17f7f0049909b96 Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Sat, 23 Apr 2022 15:38:53 +0200 Subject: [PATCH 02/10] Allow file items to save expanded status --- novelwriter/core/item.py | 3 +- tests/lipsum/nwProject.nwx | 36 +++++++++---------- tests/minimal/nwProject.nwx | 12 +++---- .../coreProject_NewCustomA_nwProject.nwx | 28 +++++++-------- .../coreProject_NewCustomB_nwProject.nwx | 16 ++++----- .../coreProject_NewFile_nwProject.nwx | 12 +++---- .../coreProject_NewMinimal_nwProject.nwx | 8 ++--- .../coreProject_NewRoot_nwProject.nwx | 8 ++--- .../guiEditor_Main_Final_nwProject.nwx | 16 ++++----- .../guiEditor_Main_Initial_nwProject.nwx | 8 ++--- .../guiProjSettings_Dialog_nwProject.nwx | 8 ++--- tests/test_core/test_core_item.py | 5 +-- tests/test_core/test_core_tree.py | 12 +++---- 13 files changed, 86 insertions(+), 86 deletions(-) diff --git a/novelwriter/core/item.py b/novelwriter/core/item.py index f6d93b36e..0aed89914 100644 --- a/novelwriter/core/item.py +++ b/novelwriter/core/item.py @@ -160,13 +160,12 @@ def packXML(self, xParent): itemAttrib["layout"] = str(self._layout.name) metaAttrib = {} + metaAttrib["expanded"] = str(self._expanded) if self._type == nwItemType.FILE: metaAttrib["charCount"] = str(self._charCount) metaAttrib["wordCount"] = str(self._wordCount) metaAttrib["paraCount"] = str(self._paraCount) metaAttrib["cursorPos"] = str(self._cursorPos) - else: - metaAttrib["expanded"] = str(self._expanded) nameAttrib = {} nameAttrib["status"] = str(self._status) diff --git a/tests/lipsum/nwProject.nwx b/tests/lipsum/nwProject.nwx index d8d7a49ed..02e3df498 100644 --- a/tests/lipsum/nwProject.nwx +++ b/tests/lipsum/nwProject.nwx @@ -1,12 +1,12 @@ - + Lorem Ipsum Lorem Ipsum lipsum.com - 24 + 26 24 - 1856 + 1863 False @@ -49,19 +49,19 @@ Novel - + Lorem Ipsum - + Front Matter - + Prologue - + Act One @@ -69,19 +69,19 @@ Chapter One - + Chapter One - + Scene One - + Scene Two - + Interlude @@ -89,19 +89,19 @@ Chapter Two - + Chapter Two - + Scene Three - + Scene Four - + Scene Five @@ -109,7 +109,7 @@ Characters - + Mr. Nobody @@ -117,7 +117,7 @@ Plot - + Main @@ -125,7 +125,7 @@ World - + Ancient Europe diff --git a/tests/minimal/nwProject.nwx b/tests/minimal/nwProject.nwx index 6945dc087..af7595a4e 100644 --- a/tests/minimal/nwProject.nwx +++ b/tests/minimal/nwProject.nwx @@ -1,13 +1,13 @@ - + Test Minimal Minimal Jane Doe John Doh - 15 + 17 2 - 146 + 150 True @@ -47,7 +47,7 @@ Novel - + Title Page @@ -55,11 +55,11 @@ New Chapter - + New Chapter - + New Scene diff --git a/tests/reference/coreProject_NewCustomA_nwProject.nwx b/tests/reference/coreProject_NewCustomA_nwProject.nwx index 8747363b9..14abf9e2b 100644 --- a/tests/reference/coreProject_NewCustomA_nwProject.nwx +++ b/tests/reference/coreProject_NewCustomA_nwProject.nwx @@ -1,5 +1,5 @@ - + Test Custom Test Novel @@ -71,7 +71,7 @@ Entities - + Title Page @@ -79,19 +79,19 @@ Chapter 1 - + Chapter 1 - + Scene 1.1 - + Scene 1.2 - + Scene 1.3 @@ -99,19 +99,19 @@ Chapter 2 - + Chapter 2 - + Scene 2.1 - + Scene 2.2 - + Scene 2.3 @@ -119,19 +119,19 @@ Chapter 3 - + Chapter 3 - + Scene 3.1 - + Scene 3.2 - + Scene 3.3 diff --git a/tests/reference/coreProject_NewCustomB_nwProject.nwx b/tests/reference/coreProject_NewCustomB_nwProject.nwx index 7f397e856..bca6ea80f 100644 --- a/tests/reference/coreProject_NewCustomB_nwProject.nwx +++ b/tests/reference/coreProject_NewCustomB_nwProject.nwx @@ -1,5 +1,5 @@ - + Test Custom Test Novel @@ -71,31 +71,31 @@ Entities - + Title Page - + Scene 1 - + Scene 2 - + Scene 3 - + Scene 4 - + Scene 5 - + Scene 6 diff --git a/tests/reference/coreProject_NewFile_nwProject.nwx b/tests/reference/coreProject_NewFile_nwProject.nwx index 86f9cf119..d253ebccb 100644 --- a/tests/reference/coreProject_NewFile_nwProject.nwx +++ b/tests/reference/coreProject_NewFile_nwProject.nwx @@ -1,5 +1,5 @@ - + New Project @@ -57,7 +57,7 @@ World - + Title Page @@ -65,19 +65,19 @@ New Chapter - + New Chapter - + New Scene - + Hello - + Jane diff --git a/tests/reference/coreProject_NewMinimal_nwProject.nwx b/tests/reference/coreProject_NewMinimal_nwProject.nwx index 37a5428bc..a6711a847 100644 --- a/tests/reference/coreProject_NewMinimal_nwProject.nwx +++ b/tests/reference/coreProject_NewMinimal_nwProject.nwx @@ -1,5 +1,5 @@ - + New Project @@ -57,7 +57,7 @@ World - + Title Page @@ -65,11 +65,11 @@ New Chapter - + New Chapter - + New Scene diff --git a/tests/reference/coreProject_NewRoot_nwProject.nwx b/tests/reference/coreProject_NewRoot_nwProject.nwx index 67c49f052..2ab623010 100644 --- a/tests/reference/coreProject_NewRoot_nwProject.nwx +++ b/tests/reference/coreProject_NewRoot_nwProject.nwx @@ -1,5 +1,5 @@ - + New Project @@ -57,7 +57,7 @@ World - + Title Page @@ -65,11 +65,11 @@ New Chapter - + New Chapter - + New Scene diff --git a/tests/reference/guiEditor_Main_Final_nwProject.nwx b/tests/reference/guiEditor_Main_Final_nwProject.nwx index 3900c6d23..61abe4f3d 100644 --- a/tests/reference/guiEditor_Main_Final_nwProject.nwx +++ b/tests/reference/guiEditor_Main_Final_nwProject.nwx @@ -1,11 +1,11 @@ - + New Project 5 2 - 4 + 3 True @@ -45,7 +45,7 @@ Novel - + Title Page @@ -53,11 +53,11 @@ New Chapter - + New Chapter - + New Scene @@ -65,7 +65,7 @@ Plot - + New Note @@ -73,7 +73,7 @@ Characters - + New Note @@ -81,7 +81,7 @@ World - + New Note diff --git a/tests/reference/guiEditor_Main_Initial_nwProject.nwx b/tests/reference/guiEditor_Main_Initial_nwProject.nwx index 66a4c94e5..1a5fd5be1 100644 --- a/tests/reference/guiEditor_Main_Initial_nwProject.nwx +++ b/tests/reference/guiEditor_Main_Initial_nwProject.nwx @@ -1,5 +1,5 @@ - + New Project @@ -45,7 +45,7 @@ Novel - + Title Page @@ -53,11 +53,11 @@ New Chapter - + New Chapter - + New Scene diff --git a/tests/reference/guiProjSettings_Dialog_nwProject.nwx b/tests/reference/guiProjSettings_Dialog_nwProject.nwx index 77d01d456..326c63e5e 100644 --- a/tests/reference/guiProjSettings_Dialog_nwProject.nwx +++ b/tests/reference/guiProjSettings_Dialog_nwProject.nwx @@ -1,5 +1,5 @@ - + Project Name Project Title @@ -51,7 +51,7 @@ Novel - + Title Page @@ -59,11 +59,11 @@ New Chapter - + New Chapter - + New Scene diff --git a/tests/test_core/test_core_item.py b/tests/test_core/test_core_item.py index 572b31d7b..dec95cd76 100644 --- a/tests/test_core/test_core_item.py +++ b/tests/test_core/test_core_item.py @@ -500,8 +500,9 @@ def testCoreItem_XMLPackUnpack(mockGUI, caplog, mockRnd): assert etree.tostring(xContent, pretty_print=False, encoding="utf-8") == ( b'' b'A Name' + b'type="FILE" class="NOVEL" layout="NOTE">A Name' b'' ) % bytes(importKeys[3], encoding="utf8") diff --git a/tests/test_core/test_core_tree.py b/tests/test_core/test_core_tree.py index c70cc4bb1..e8734936e 100644 --- a/tests/test_core/test_core_tree.py +++ b/tests/test_core/test_core_tree.py @@ -419,12 +419,12 @@ def testCoreTree_XMLPackUnpack(mockGUI, mockItems): b'type="FOLDER" class="NOVEL">Act One' b'Chapter One' b'Scene One' b'Characters' b'Jane Doe' b'' b'' From 1a47bbaf8c1f1bd95979e3cc109842c1ccc69fe0 Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Sat, 23 Apr 2022 15:53:14 +0200 Subject: [PATCH 03/10] Allow files to have child items --- novelwriter/gui/projtree.py | 9 ++---- sample/nwProject.nwx | 60 ++++++++++++++++++------------------- 2 files changed, 32 insertions(+), 37 deletions(-) diff --git a/novelwriter/gui/projtree.py b/novelwriter/gui/projtree.py index 23238b84d..c912bb187 100644 --- a/novelwriter/gui/projtree.py +++ b/novelwriter/gui/projtree.py @@ -793,17 +793,12 @@ def dropEvent(self, theEvent): # - Files can be moved anywhere # - Folders can only be moved within the same root folder # - Root folders cannot be moved at all - # - Items cannot be dropped on top of a file (moved inside) isFile = snItem.itemType == nwItemType.FILE isRoot = snItem.itemType == nwItemType.ROOT - onFile = dnItem.itemType == nwItemType.FILE inSame = snItem.itemRoot == dnItem.itemRoot - allowDrop = inSame or isFile - allowDrop &= not (self.dropIndicatorPosition() == QAbstractItemView.OnItem and onFile) - - if allowDrop and not isRoot: + if (inSame or isFile) and not isRoot: logger.debug("Drag'n'drop of item '%s' accepted", sHandle) wCount = int(sItem.data(self.C_COUNT, Qt.UserRole)) @@ -1057,7 +1052,7 @@ def filterActions(self, theItem): trashHandle = self.theTree.theProject.projTree.trashRoot() - inTrash = theItem.itemParent == trashHandle and trashHandle is not None + inTrash = self.theTree.theProject.projTree.isTrash(theItem.itemHandle) isTrash = theItem.itemHandle == trashHandle and trashHandle is not None isFile = theItem.itemType == nwItemType.FILE diff --git a/sample/nwProject.nwx b/sample/nwProject.nwx index 236608705..6bb6fdd4d 100644 --- a/sample/nwProject.nwx +++ b/sample/nwProject.nwx @@ -1,13 +1,13 @@ - + Sample Project Sample Project Jane Smith Jay Doh - 1306 - 199 - 65149 + 1308 + 201 + 65350 False @@ -54,47 +54,47 @@ Novel - + Title Page - + Page - + Part One A Folder - - + + + Interlude + + + Chapter One - - + + Making a Scene - - + + Another Scene - - - Interlude - - - + + A Note on Structure - - + + Chapter Two - - + + We Found John! @@ -106,11 +106,11 @@ Main Characters - + John Smith - + Jane Smith @@ -118,15 +118,15 @@ Locations - + Earth - + Space - + Mars @@ -138,7 +138,7 @@ Scenes - + Old File @@ -146,7 +146,7 @@ Trash - + Delete Me! From 5cdcda66a5b98fadb5b78f5fd568dd37eada8dc3 Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Sat, 23 Apr 2022 16:09:50 +0200 Subject: [PATCH 04/10] Update data recursively when moving an item with child items --- novelwriter/gui/projtree.py | 48 +++++++++++++++++++++---------------- 1 file changed, 28 insertions(+), 20 deletions(-) diff --git a/novelwriter/gui/projtree.py b/novelwriter/gui/projtree.py index c912bb187..5358b0826 100644 --- a/novelwriter/gui/projtree.py +++ b/novelwriter/gui/projtree.py @@ -435,7 +435,6 @@ def deleteItem(self, tHandle=None, alreadyAsked=False, bulkAction=False): logger.error("Could not find tree item for deletion") return False - wCount = int(trItemS.data(self.C_COUNT, Qt.UserRole)) if nwItemS.itemType == nwItemType.FILE: logger.debug("User requested file '%s' deleted", tHandle) trItemP = trItemS.parent() @@ -494,7 +493,7 @@ def deleteItem(self, tHandle=None, alreadyAsked=False, bulkAction=False): tIndex = trItemP.indexOfChild(trItemS) trItemC = trItemP.takeChild(tIndex) trItemT.addChild(trItemC) - self._postItemMove(tHandle, wCount) + self._postItemMove(tHandle) self._recordLastMove(trItemS, trItemP, tIndex) self._setTreeChanged(True) @@ -646,7 +645,6 @@ def undoLastMove(self): return False dstIndex = min(max(0, dstIndex), dstItem.childCount()) - wCount = int(srcItem.data(self.C_COUNT, Qt.UserRole)) sHandle = srcItem.data(self.C_NAME, Qt.UserRole) dHandle = dstItem.data(self.C_NAME, Qt.UserRole) logger.debug("Moving item '%s' back to '%s', index %d", sHandle, dHandle, dstIndex) @@ -657,7 +655,7 @@ def undoLastMove(self): movItem = parItem.takeChild(srcIndex) dstItem.insertChild(dstIndex, movItem) - self._postItemMove(sHandle, wCount) + self._postItemMove(sHandle) self.clearSelection() movItem.setSelected(True) @@ -801,11 +799,9 @@ def dropEvent(self, theEvent): if (inSame or isFile) and not isRoot: logger.debug("Drag'n'drop of item '%s' accepted", sHandle) - wCount = int(sItem.data(self.C_COUNT, Qt.UserRole)) self.propagateCount(sHandle, 0) - QTreeWidget.dropEvent(self, theEvent) - self._postItemMove(sHandle, wCount) + self._postItemMove(sHandle) self._recordLastMove(sItem, pItem, pIndex) else: @@ -822,7 +818,7 @@ def dropEvent(self, theEvent): # Internal Functions ## - def _postItemMove(self, tHandle, wCount): + def _postItemMove(self, tHandle): """Run various maintenance tasks for a moved item. """ trItemS = self._getTreeItem(tHandle) @@ -836,19 +832,23 @@ def _postItemMove(self, tHandle, wCount): # is updated accordingly, and update word count pHandle = trItemP.data(self.C_NAME, Qt.UserRole) nwItemS.setParent(pHandle) - self.theProject.projTree.updateItemData(tHandle) - self.setTreeItemValues(tHandle) - self.propagateCount(tHandle, wCount) - logger.debug("The parent of item '%s' has been changed to '%s'", tHandle, pHandle) - # The items dropped into archive or trash should be removed - # from the project index, for all other items, we rescan the - # file to ensure the index is up to date. - if nwItemS.isInactive(): - self.theIndex.deleteHandle(tHandle) - else: - self.theIndex.reIndexHandle(tHandle) + mHandles = self.getTreeFromHandle(tHandle) + logger.debug("A total of %d item(s) were moved", len(mHandles)) + for mHandle in mHandles: + logger.debug("Updating item '%s'", mHandle) + wCount = self._getItemWordCount(mHandle) + self.theProject.projTree.updateItemData(mHandle) + + # Update the index + if nwItemS.isInactive(): + self.theIndex.deleteHandle(tHandle) + else: + self.theIndex.reIndexHandle(tHandle) + + self.setTreeItemValues(mHandle) + self.propagateCount(mHandle, wCount) # Trigger dependent updates self._setTreeChanged(True) @@ -856,8 +856,16 @@ def _postItemMove(self, tHandle, wCount): return True + def _getItemWordCount(self, tHandle): + """Retrun the word count of a given item handle. + """ + tItem = self._getTreeItem(tHandle) + if tItem is None: + return 0 + return int(tItem.data(self.C_COUNT, Qt.UserRole)) + def _getTreeItem(self, tHandle): - """Returns the QTreeWidgetItem of a given item handle. + """Return the QTreeWidgetItem of a given item handle. """ return self._treeMap.get(tHandle, None) From d96c8ddf8509d60c14f236abde9560694a6b6a3b Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Sat, 23 Apr 2022 16:21:20 +0200 Subject: [PATCH 05/10] Remove all restrictions on drag and drop except in ROOT items --- novelwriter/gui/projtree.py | 44 +++++++++++-------------------------- 1 file changed, 13 insertions(+), 31 deletions(-) diff --git a/novelwriter/gui/projtree.py b/novelwriter/gui/projtree.py index 5358b0826..39445dee9 100644 --- a/novelwriter/gui/projtree.py +++ b/novelwriter/gui/projtree.py @@ -762,13 +762,9 @@ def dropEvent(self, theEvent): drop is allowed or not. Disallowed drops are cancelled. """ sHandle = self.getSelectedHandle() - if sHandle is None: - logger.error("No handle selected") - return - dIndex = self.indexAt(theEvent.pos()) - if not dIndex.isValid(): - logger.error("Invalid drop index") + if sHandle is None or not dIndex.isValid(): + logger.error("Invalid drag and drop event") return sItem = self._getTreeItem(sHandle) @@ -776,41 +772,27 @@ def dropEvent(self, theEvent): dHandle = dItem.data(self.C_NAME, Qt.UserRole) snItem = self.theProject.projTree[sHandle] dnItem = self.theProject.projTree[dHandle] - if dnItem is None: + + if snItem.itemType == nwItemType.ROOT or dnItem is None: + logger.debug("Drag'n'drop of item '%s' not accepted", sHandle) + theEvent.ignore() self.theParent.makeAlert(self.tr( "The item cannot be moved to that location." ), nwAlert.ERROR) return + logger.debug("Drag'n'drop of item '%s' accepted", sHandle) + + self.propagateCount(sHandle, 0) + QTreeWidget.dropEvent(self, theEvent) + self._postItemMove(sHandle) + pItem = sItem.parent() pIndex = 0 if pItem is not None: pIndex = pItem.indexOfChild(sItem) - # Determine if the drag and drop is allowed: - # - Files can be moved anywhere - # - Folders can only be moved within the same root folder - # - Root folders cannot be moved at all - - isFile = snItem.itemType == nwItemType.FILE - isRoot = snItem.itemType == nwItemType.ROOT - inSame = snItem.itemRoot == dnItem.itemRoot - - if (inSame or isFile) and not isRoot: - logger.debug("Drag'n'drop of item '%s' accepted", sHandle) - - self.propagateCount(sHandle, 0) - QTreeWidget.dropEvent(self, theEvent) - self._postItemMove(sHandle) - self._recordLastMove(sItem, pItem, pIndex) - - else: - logger.debug("Drag'n'drop of item '%s' not accepted", sHandle) - - theEvent.ignore() - self.theParent.makeAlert(self.tr( - "The item cannot be moved to that location." - ), nwAlert.ERROR) + self._recordLastMove(sItem, pItem, pIndex) return From bd5a14b18ecca3c21f7e20d18b08fc25535bf251 Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Sat, 23 Apr 2022 16:57:13 +0200 Subject: [PATCH 06/10] Block drag and drop for ROOT items on the widget level, and drop the TRASH item type --- novelwriter/core/item.py | 2 ++ novelwriter/core/project.py | 2 +- novelwriter/core/tree.py | 15 +++++++-------- novelwriter/enum.py | 1 - novelwriter/gui/projtree.py | 22 ++++------------------ novelwriter/gui/theme.py | 3 --- sample/nwProject.nwx | 8 ++++---- 7 files changed, 18 insertions(+), 35 deletions(-) diff --git a/novelwriter/core/item.py b/novelwriter/core/item.py index 0aed89914..e23b4fb9c 100644 --- a/novelwriter/core/item.py +++ b/novelwriter/core/item.py @@ -408,6 +408,8 @@ def setType(self, value): self._type = value elif isItemType(value): self._type = nwItemType[value] + elif value == "TRASH": + self._type = nwItemType.ROOT else: logger.error("Unrecognised item type '%s'", value) self._type = nwItemType.NO_TYPE diff --git a/novelwriter/core/project.py b/novelwriter/core/project.py index 0d24d74c6..26efb323d 100644 --- a/novelwriter/core/project.py +++ b/novelwriter/core/project.py @@ -158,7 +158,7 @@ def trashFolder(self): if trashHandle is None: newItem = NWItem(self) newItem.setName(trConst(nwLabels.CLASS_NAME[nwItemClass.TRASH])) - newItem.setType(nwItemType.TRASH) + newItem.setType(nwItemType.ROOT) newItem.setClass(nwItemClass.TRASH) self.projTree.append(None, None, newItem) self.projTree.updateItemData(newItem.itemHandle) diff --git a/novelwriter/core/tree.py b/novelwriter/core/tree.py index a158c37a1..eb349bf8d 100644 --- a/novelwriter/core/tree.py +++ b/novelwriter/core/tree.py @@ -100,14 +100,13 @@ def append(self, tHandle, pHandle, nwItem): if nwItem.itemClass == nwItemClass.ARCHIVE: logger.verbose("Item '%s' is the archive folder", str(tHandle)) self._archRoot = tHandle - - if nwItem.itemType == nwItemType.TRASH: - if self._trashRoot is None: - logger.verbose("Item '%s' is the trash folder", str(tHandle)) - self._trashRoot = tHandle - else: - logger.error("Only one trash folder allowed") - return False + elif nwItem.itemClass == nwItemClass.TRASH: + if self._trashRoot is None: + logger.verbose("Item '%s' is the trash folder", str(tHandle)) + self._trashRoot = tHandle + else: + logger.error("Only one trash folder allowed") + return False self._projTree[tHandle] = nwItem self._treeOrder.append(tHandle) diff --git a/novelwriter/enum.py b/novelwriter/enum.py index 3360d8c5f..56340541a 100644 --- a/novelwriter/enum.py +++ b/novelwriter/enum.py @@ -32,7 +32,6 @@ class nwItemType(Enum): ROOT = 1 FOLDER = 2 FILE = 3 - TRASH = 4 # END Enum nwItemType diff --git a/novelwriter/gui/projtree.py b/novelwriter/gui/projtree.py index 39445dee9..7f5dc6f37 100644 --- a/novelwriter/gui/projtree.py +++ b/novelwriter/gui/projtree.py @@ -762,31 +762,18 @@ def dropEvent(self, theEvent): drop is allowed or not. Disallowed drops are cancelled. """ sHandle = self.getSelectedHandle() - dIndex = self.indexAt(theEvent.pos()) - if sHandle is None or not dIndex.isValid(): + if sHandle is None: logger.error("Invalid drag and drop event") return - sItem = self._getTreeItem(sHandle) - dItem = self.itemFromIndex(dIndex) - dHandle = dItem.data(self.C_NAME, Qt.UserRole) - snItem = self.theProject.projTree[sHandle] - dnItem = self.theProject.projTree[dHandle] - - if snItem.itemType == nwItemType.ROOT or dnItem is None: - logger.debug("Drag'n'drop of item '%s' not accepted", sHandle) - theEvent.ignore() - self.theParent.makeAlert(self.tr( - "The item cannot be moved to that location." - ), nwAlert.ERROR) - return - logger.debug("Drag'n'drop of item '%s' accepted", sHandle) self.propagateCount(sHandle, 0) QTreeWidget.dropEvent(self, theEvent) self._postItemMove(sHandle) + # Record undo information + sItem = self._getTreeItem(sHandle) pItem = sItem.parent() pIndex = 0 if pItem is not None: @@ -895,8 +882,7 @@ def _addTreeItem(self, nwItem, nHandle=None): self._treeMap[tHandle] = newItem if pHandle is None: if nwItem.itemType == nwItemType.ROOT: - self.addTopLevelItem(newItem) - elif nwItem.itemType == nwItemType.TRASH: + newItem.setFlags(newItem.flags() ^ Qt.ItemIsDragEnabled) self.addTopLevelItem(newItem) else: self.theParent.makeAlert(self.tr( diff --git a/novelwriter/gui/theme.py b/novelwriter/gui/theme.py index 3c180b503..19fe823ad 100644 --- a/novelwriter/gui/theme.py +++ b/novelwriter/gui/theme.py @@ -639,9 +639,6 @@ def getItemIcon(self, tType, tClass, tLayout, hLevel="H0"): iconName = "proj_scene" elif tLayout == nwItemLayout.NOTE: iconName = "proj_note" - elif tType == nwItemType.TRASH: - iconName = nwLabels.CLASS_ICON[tClass] - if iconName is None: return QIcon() diff --git a/sample/nwProject.nwx b/sample/nwProject.nwx index 6bb6fdd4d..b2ac1c0ec 100644 --- a/sample/nwProject.nwx +++ b/sample/nwProject.nwx @@ -1,13 +1,13 @@ - + Sample Project Sample Project Jane Smith Jay Doh - 1308 + 1309 201 - 65350 + 65353 False @@ -141,7 +141,7 @@ Old File - + Trash From 821833df8ec17c4d638cf6dbfdfe26f76ee21e5b Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Sat, 23 Apr 2022 17:05:29 +0200 Subject: [PATCH 07/10] Fix tests and drop unused function from NWTree class --- novelwriter/core/tree.py | 24 ------------------- .../guiEditor_Main_Final_nwProject.nwx | 4 ++-- tests/test_base/test_base_common.py | 16 ++++++++++++- tests/test_core/test_core_item.py | 6 +++-- tests/test_core/test_core_tree.py | 12 +++------- 5 files changed, 24 insertions(+), 38 deletions(-) diff --git a/novelwriter/core/tree.py b/novelwriter/core/tree.py index eb349bf8d..ee57e0db6 100644 --- a/novelwriter/core/tree.py +++ b/novelwriter/core/tree.py @@ -351,30 +351,6 @@ def setFileItemLayout(self, tHandle, itemLayout): return True - ## - # Getters - ## - - def countTypes(self): - """Count the number of files, folders and roots in the project. - """ - nRoot = 0 - nFolder = 0 - nFile = 0 - - for tHandle in self._treeOrder: - tItem = self.__getitem__(tHandle) - if tItem is None: - continue - elif tItem.itemType == nwItemType.ROOT: - nRoot += 1 - elif tItem.itemType == nwItemType.FOLDER: - nFolder += 1 - elif tItem.itemType == nwItemType.FILE: - nFile += 1 - - return nRoot, nFolder, nFile - ## # Meta Methods ## diff --git a/tests/reference/guiEditor_Main_Final_nwProject.nwx b/tests/reference/guiEditor_Main_Final_nwProject.nwx index 61abe4f3d..e29290cac 100644 --- a/tests/reference/guiEditor_Main_Final_nwProject.nwx +++ b/tests/reference/guiEditor_Main_Final_nwProject.nwx @@ -1,5 +1,5 @@ - + New Project @@ -84,7 +84,7 @@ New Note - + Trash diff --git a/tests/test_base/test_base_common.py b/tests/test_base/test_base_common.py index d7ad41194..0ce153b46 100644 --- a/tests/test_base/test_base_common.py +++ b/tests/test_base/test_base_common.py @@ -161,6 +161,7 @@ def testBaseCommon_IsItemClass(): assert isItemClass("ARCHIVE") is True assert isItemClass("TRASH") is True + # Invalid assert isItemClass("None") is False assert isItemClass(None) is False assert isItemClass("STUFF") is False @@ -176,8 +177,11 @@ def testBaseCommon_IsItemType(): assert isItemType("ROOT") is True assert isItemType("FOLDER") is True assert isItemType("FILE") is True - assert isItemType("TRASH") is True + # Deprecated Type + assert isItemType("TRASH") is False + + # Invalid assert isItemType("None") is False assert isItemType(None) is False assert isItemType("STUFF") is False @@ -193,6 +197,16 @@ def testBaseCommon_IsItemLayout(): assert isItemLayout("DOCUMENT") is True assert isItemLayout("NOTE") is True + # Deprecated Layouts + assert isItemLayout("TITLE") is False + assert isItemLayout("PAGE") is False + assert isItemLayout("BOOK") is False + assert isItemLayout("PARTITION") is False + assert isItemLayout("UNNUMBERED") is False + assert isItemLayout("CHAPTER") is False + assert isItemLayout("SCENE") is False + + # Invalid assert isItemLayout("None") is False assert isItemLayout(None) is False assert isItemLayout("STUFF") is False diff --git a/tests/test_core/test_core_item.py b/tests/test_core/test_core_item.py index dec95cd76..fbc0ded30 100644 --- a/tests/test_core/test_core_item.py +++ b/tests/test_core/test_core_item.py @@ -283,8 +283,6 @@ def testCoreItem_TypeSetter(mockGUI): assert theItem.itemType == nwItemType.FOLDER theItem.setType("FILE") assert theItem.itemType == nwItemType.FILE - theItem.setType("TRASH") - assert theItem.itemType == nwItemType.TRASH # Alternative theItem.setType(nwItemType.ROOT) @@ -712,4 +710,8 @@ def testCoreItem_ConvertFromFmt13(mockGUI): assert theItem.itemType == nwItemType.FILE assert theItem.itemLayout == nwItemLayout.DOCUMENT + # Deprecated Type + theItem.setType("TRASH") + assert theItem.itemType == nwItemType.ROOT + # END Test testCoreItem_ConvertFromFmt13 diff --git a/tests/test_core/test_core_tree.py b/tests/test_core/test_core_tree.py index e8734936e..5c05f679d 100644 --- a/tests/test_core/test_core_tree.py +++ b/tests/test_core/test_core_tree.py @@ -76,7 +76,7 @@ def mockItems(mockGUI, mockRnd): itemF = NWItem(theProject) itemF._name = "Trash" - itemF._type = nwItemType.TRASH + itemF._type = nwItemType.ROOT itemF._class = nwItemClass.TRASH itemF._expanded = False @@ -172,7 +172,7 @@ def testCoreTree_BuildTree(mockGUI, mockItems): # Try to add another trash folder itemT = NWItem(theProject) itemT._name = "Trash" - itemT._type = nwItemType.TRASH + itemT._type = nwItemType.ROOT itemT._class = nwItemClass.TRASH itemT._expanded = False @@ -353,12 +353,6 @@ def testCoreTree_Stats(mockGUI, mockItems): assert novelWords == 550 assert noteWords == 400 - # Count types - nRoot, nFolder, nFile = theTree.countTypes() - assert nRoot == 3 - assert nFolder == 1 - assert nFile == 3 - # END Test testCoreTree_Stats @@ -429,7 +423,7 @@ def testCoreTree_XMLPackUnpack(mockGUI, mockItems): b'Outtakes' - b'Trash' b' Date: Sat, 23 Apr 2022 18:03:03 +0200 Subject: [PATCH 08/10] Fix drag and drop word propagation --- novelwriter/gui/projtree.py | 30 ++++++++++++++++++------------ 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/novelwriter/gui/projtree.py b/novelwriter/gui/projtree.py index 7f5dc6f37..44672a897 100644 --- a/novelwriter/gui/projtree.py +++ b/novelwriter/gui/projtree.py @@ -435,6 +435,7 @@ def deleteItem(self, tHandle=None, alreadyAsked=False, bulkAction=False): logger.error("Could not find tree item for deletion") return False + wCount = self._getItemWordCount(tHandle) if nwItemS.itemType == nwItemType.FILE: logger.debug("User requested file '%s' deleted", tHandle) trItemP = trItemS.parent() @@ -493,7 +494,7 @@ def deleteItem(self, tHandle=None, alreadyAsked=False, bulkAction=False): tIndex = trItemP.indexOfChild(trItemS) trItemC = trItemP.takeChild(tIndex) trItemT.addChild(trItemC) - self._postItemMove(tHandle) + self._postItemMove(tHandle, wCount) self._recordLastMove(trItemS, trItemP, tIndex) self._setTreeChanged(True) @@ -649,13 +650,14 @@ def undoLastMove(self): dHandle = dstItem.data(self.C_NAME, Qt.UserRole) logger.debug("Moving item '%s' back to '%s', index %d", sHandle, dHandle, dstIndex) + wCount = self._getItemWordCount(sHandle) self.propagateCount(sHandle, 0) parItem = srcItem.parent() srcIndex = parItem.indexOfChild(srcItem) movItem = parItem.takeChild(srcIndex) dstItem.insertChild(dstIndex, movItem) - self._postItemMove(sHandle) + self._postItemMove(sHandle, wCount) self.clearSelection() movItem.setSelected(True) @@ -768,18 +770,23 @@ def dropEvent(self, theEvent): logger.debug("Drag'n'drop of item '%s' accepted", sHandle) - self.propagateCount(sHandle, 0) - QTreeWidget.dropEvent(self, theEvent) - self._postItemMove(sHandle) - - # Record undo information sItem = self._getTreeItem(sHandle) + isExpanded = False + if sItem is not None: + isExpanded = sItem.isExpanded() + pItem = sItem.parent() pIndex = 0 if pItem is not None: pIndex = pItem.indexOfChild(sItem) + wCount = self._getItemWordCount(sHandle) + self.propagateCount(sHandle, 0) + + QTreeWidget.dropEvent(self, theEvent) + self._postItemMove(sHandle, wCount) self._recordLastMove(sItem, pItem, pIndex) + sItem.setExpanded(isExpanded) return @@ -787,7 +794,7 @@ def dropEvent(self, theEvent): # Internal Functions ## - def _postItemMove(self, tHandle): + def _postItemMove(self, tHandle, wCount): """Run various maintenance tasks for a moved item. """ trItemS = self._getTreeItem(tHandle) @@ -807,19 +814,18 @@ def _postItemMove(self, tHandle): logger.debug("A total of %d item(s) were moved", len(mHandles)) for mHandle in mHandles: logger.debug("Updating item '%s'", mHandle) - wCount = self._getItemWordCount(mHandle) self.theProject.projTree.updateItemData(mHandle) # Update the index if nwItemS.isInactive(): - self.theIndex.deleteHandle(tHandle) + self.theIndex.deleteHandle(mHandle) else: - self.theIndex.reIndexHandle(tHandle) + self.theIndex.reIndexHandle(mHandle) self.setTreeItemValues(mHandle) - self.propagateCount(mHandle, wCount) # Trigger dependent updates + self.propagateCount(tHandle, wCount) self._setTreeChanged(True) self._emitItemChange(tHandle) From 732f20a0f13bbef5ff12f7bcc81f33883f6ee5b0 Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Sat, 23 Apr 2022 19:00:11 +0200 Subject: [PATCH 09/10] Fix word count accounting and double click tree expansion --- novelwriter/gui/projtree.py | 31 ++++++++++++++++++++++++------- novelwriter/guimain.py | 12 +++++++++--- sample/content/636b6aa9b697b.nwd | 2 +- sample/content/ae7339df26ded.nwd | 2 +- sample/content/bc0cbd2a407f3.nwd | 2 +- sample/nwProject.nwx | 22 +++++++++++----------- 6 files changed, 47 insertions(+), 24 deletions(-) diff --git a/novelwriter/gui/projtree.py b/novelwriter/gui/projtree.py index 44672a897..eadbe5b85 100644 --- a/novelwriter/gui/projtree.py +++ b/novelwriter/gui/projtree.py @@ -82,7 +82,7 @@ def __init__(self, theParent): # Tree Settings iPx = self.theTheme.baseIconSize self.setIconSize(QSize(iPx, iPx)) - self.setExpandsOnDoubleClick(True) + self.setExpandsOnDoubleClick(False) self.setIndentation(iPx) self.setColumnCount(4) self.setHeaderLabels([ @@ -349,6 +349,14 @@ def getTreeFromHandle(self, tHandle): theList = self._scanChildren(theList, theItem, 0) return theList + def toggleExpanded(self, tHandle): + """Expand an item based on its handle. + """ + trItem = self._getTreeItem(tHandle) + if trItem is not None: + trItem.setExpanded(not trItem.isExpanded()) + return + def getColumnSizes(self): """Return the column widths for the tree columns. """ @@ -576,7 +584,7 @@ def setTreeItemValues(self, tHandle): return - def propagateCount(self, tHandle, theCount): + def propagateCount(self, tHandle, newCount, countChildren=False): """Recursive function setting the word count for a given item, and propagating that count upwards in the tree until reaching a root item. This function is more efficient than recalculating @@ -588,8 +596,12 @@ def propagateCount(self, tHandle, theCount): if tItem is None: return - tItem.setText(self.C_COUNT, f"{theCount:n}") - tItem.setData(self.C_COUNT, Qt.UserRole, int(theCount)) + if countChildren: + for i in range(tItem.childCount()): + newCount += int(tItem.child(i).data(self.C_COUNT, Qt.UserRole)) + + tItem.setText(self.C_COUNT, f"{newCount:n}") + tItem.setData(self.C_COUNT, Qt.UserRole, int(newCount)) pItem = tItem.parent() if pItem is None: @@ -602,7 +614,12 @@ def propagateCount(self, tHandle, theCount): pHandle = pItem.data(self.C_NAME, Qt.UserRole) if pHandle: - self.propagateCount(pHandle, pCount) + if self.theProject.projTree.checkType(pHandle, nwItemType.FILE): + # A file has an internal word count we need to account + # for, but a folder always has 0 words on its own. + pCount += self.theIndex.getCounts(pHandle)[1] + + self.propagateCount(pHandle, pCount, countChildren=False) return @@ -724,7 +741,7 @@ def _rightClickMenu(self, clickPos): def doUpdateCounts(self, tHandle, cCount, wCount, pCount): """Slot for updating the word count of a specific item. """ - self.propagateCount(tHandle, wCount) + self.propagateCount(tHandle, wCount, countChildren=True) self.wordCountsChanged.emit() return @@ -908,7 +925,7 @@ def _addTreeItem(self, nwItem, nHandle=None): self._treeMap[pHandle].insertChild(byIndex+1, newItem) else: self._treeMap[pHandle].addChild(newItem) - self.propagateCount(tHandle, nwItem.wordCount) + self.propagateCount(tHandle, nwItem.wordCount, countChildren=True) self.setTreeItemValues(tHandle) newItem.setExpanded(nwItem.isExpanded) diff --git a/novelwriter/guimain.py b/novelwriter/guimain.py index fabd3867b..60fe0d232 100644 --- a/novelwriter/guimain.py +++ b/novelwriter/guimain.py @@ -906,7 +906,7 @@ def rebuildIndex(self, beQuiet=False): tItem.setCharCount(cC) tItem.setWordCount(wC) tItem.setParaCount(pC) - self.treeView.propagateCount(tItem.itemHandle, wC) + self.treeView.propagateCount(tItem.itemHandle, wC, countChildren=True) self.treeView.setTreeItemValues(tItem.itemHandle) tEnd = time() @@ -1568,11 +1568,17 @@ def _treeSingleClick(self): @pyqtSlot("QTreeWidgetItem*", int) def _treeDoubleClick(self, tItem, colNo): """The user double-clicked an item in the tree. If it is a file, - we open it. Otherwise, we do nothing. + we open it. Otherwise, we toggle the expanded status. """ tHandle = self.treeView.getSelectedHandle() if tHandle is not None: - self.openDocument(tHandle, changeFocus=False, doScroll=False) + tItem = self.theProject.projTree[tHandle] + if tItem is None: + return + if tItem.itemType == nwItemType.FILE: + self.openDocument(tHandle, changeFocus=False, doScroll=False) + else: + self.treeView.toggleExpanded(tHandle) return @pyqtSlot() diff --git a/sample/content/636b6aa9b697b.nwd b/sample/content/636b6aa9b697b.nwd index 8fe960421..2927b7d01 100644 --- a/sample/content/636b6aa9b697b.nwd +++ b/sample/content/636b6aa9b697b.nwd @@ -1,5 +1,5 @@ %%~name: Making a Scene -%%~path: e7ded148d6e4a/636b6aa9b697b +%%~path: 6a2d6d5f4f401/636b6aa9b697b %%~kind: NOVEL/DOCUMENT ### Making a Scene diff --git a/sample/content/ae7339df26ded.nwd b/sample/content/ae7339df26ded.nwd index 9b135713c..1eb7a65d3 100644 --- a/sample/content/ae7339df26ded.nwd +++ b/sample/content/ae7339df26ded.nwd @@ -1,5 +1,5 @@ %%~name: We Found John! -%%~path: e7ded148d6e4a/ae7339df26ded +%%~path: 88706ddc78b1b/ae7339df26ded %%~kind: NOVEL/DOCUMENT ### We Found John! diff --git a/sample/content/bc0cbd2a407f3.nwd b/sample/content/bc0cbd2a407f3.nwd index 3c95e8ffb..4ebbca1db 100644 --- a/sample/content/bc0cbd2a407f3.nwd +++ b/sample/content/bc0cbd2a407f3.nwd @@ -1,5 +1,5 @@ %%~name: Another Scene -%%~path: e7ded148d6e4a/bc0cbd2a407f3 +%%~path: 6a2d6d5f4f401/bc0cbd2a407f3 %%~kind: NOVEL/DOCUMENT ### Another Scene diff --git a/sample/nwProject.nwx b/sample/nwProject.nwx index b2ac1c0ec..3a0124065 100644 --- a/sample/nwProject.nwx +++ b/sample/nwProject.nwx @@ -1,13 +1,13 @@ - + Sample Project Sample Project Jane Smith Jay Doh - 1309 - 201 - 65353 + 1323 + 207 + 66237 False @@ -69,22 +69,22 @@ A Folder - - - Interlude - - + Chapter One - + Making a Scene Another Scene + + + Interlude + A Note on Structure @@ -138,7 +138,7 @@ Scenes - + Old File From d9e3fd7a01441c7ac083fe0f0a8f1cc35057ab88 Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Sat, 23 Apr 2022 19:34:40 +0200 Subject: [PATCH 10/10] Make some minor improvements to how expanded status is saved for items --- novelwriter/gui/projtree.py | 10 ++++++++-- sample/nwProject.nwx | 8 ++++---- tests/reference/guiEditor_Main_Final_nwProject.nwx | 2 +- 3 files changed, 13 insertions(+), 7 deletions(-) diff --git a/novelwriter/gui/projtree.py b/novelwriter/gui/projtree.py index eadbe5b85..cb59c9ee2 100644 --- a/novelwriter/gui/projtree.py +++ b/novelwriter/gui/projtree.py @@ -825,6 +825,7 @@ def _postItemMove(self, tHandle, wCount): # is updated accordingly, and update word count pHandle = trItemP.data(self.C_NAME, Qt.UserRole) nwItemS.setParent(pHandle) + trItemP.setExpanded(True) logger.debug("The parent of item '%s' has been changed to '%s'", tHandle, pHandle) mHandles = self.getTreeFromHandle(tHandle) @@ -873,12 +874,17 @@ def _scanChildren(self, theList, tItem, tIndex): starting at a given QTreeWidgetItem. """ tHandle = tItem.data(self.C_NAME, Qt.UserRole) + cCount = tItem.childCount() + + # Update tree-related meta data nwItem = self.theProject.projTree[tHandle] - nwItem.setExpanded(tItem.isExpanded()) + nwItem.setExpanded(tItem.isExpanded() and cCount > 0) nwItem.setOrder(tIndex) + theList.append(tHandle) - for i in range(tItem.childCount()): + for i in range(cCount): self._scanChildren(theList, tItem.child(i), i) + return theList def _addTreeItem(self, nwItem, nHandle=None): diff --git a/sample/nwProject.nwx b/sample/nwProject.nwx index 3a0124065..406fa4232 100644 --- a/sample/nwProject.nwx +++ b/sample/nwProject.nwx @@ -1,13 +1,13 @@ - + Sample Project Sample Project Jane Smith Jay Doh - 1323 + 1327 207 - 66237 + 66285 False @@ -74,7 +74,7 @@ Chapter One - + Making a Scene diff --git a/tests/reference/guiEditor_Main_Final_nwProject.nwx b/tests/reference/guiEditor_Main_Final_nwProject.nwx index e29290cac..c4ba847c9 100644 --- a/tests/reference/guiEditor_Main_Final_nwProject.nwx +++ b/tests/reference/guiEditor_Main_Final_nwProject.nwx @@ -85,7 +85,7 @@ New Note - + Trash