diff --git a/novelwriter/core/itemmodel.py b/novelwriter/core/itemmodel.py
index 34579b95..948b6146 100644
--- a/novelwriter/core/itemmodel.py
+++ b/novelwriter/core/itemmodel.py
@@ -91,6 +91,10 @@ class ProjectNode:
f"children={len(self._children)}>"
)
+ def __bool__(self) -> bool:
+ """A node should always evaluate to True."""
+ return True
+
##
# Properties
##
@@ -190,10 +194,10 @@ class ProjectNode:
self._updateRelationships(child)
if 0 <= pos < len(self._children):
self._children.insert(pos, child)
- self._refreshChildrenPos()
else:
child._row = len(self._children)
self._children.append(child)
+ self._refreshChildrenPos()
return
def takeChild(self, pos: int) -> ProjectNode | None:
@@ -229,6 +233,7 @@ class ProjectNode:
"""Update the row value on all children."""
for n, child in enumerate(self._children):
child._row = n
+ child.item.setOrder(n)
return
def _updateRelationships(self, child: ProjectNode) -> None:
diff --git a/novelwriter/core/project.py b/novelwriter/core/project.py
index 28fad307..ced91fd1 100644
--- a/novelwriter/core/project.py
+++ b/novelwriter/core/project.py
@@ -44,6 +44,7 @@ from novelwriter.core.options import OptionState
from novelwriter.core.projectdata import NWProjectData
from novelwriter.core.projectxml import ProjectXMLReader, ProjectXMLWriter, XMLReadState
from novelwriter.core.sessions import NWSessionLog
+from novelwriter.core.status import T_StatusKinds, T_UpdateEntry
from novelwriter.core.storage import NWStorage, NWStorageOpen
from novelwriter.core.tree import NWTree
from novelwriter.enum import nwItemClass, nwItemLayout, nwItemType
@@ -531,6 +532,18 @@ class NWProject:
self._data.itemImport.increment(nwItem.itemImport)
return
+ def updateStatus(self, kind: T_StatusKinds, update: T_UpdateEntry) -> None:
+ """Update status or import entries."""
+ if kind == "s":
+ self._data.itemStatus.update(update)
+ SHARED.projectSignalProxy({"event": "statusLabels", "kind": kind})
+ self._tree.refreshAllItems()
+ elif kind == "i":
+ self._data.itemImport.update(update)
+ SHARED.projectSignalProxy({"event": "statusLabels", "kind": kind})
+ self._tree.refreshAllItems()
+ return
+
def localLookup(self, word: str | int) -> str:
"""Look up a word or number in the translation map for the
project and return it. The variable is cast to a string before
diff --git a/novelwriter/core/status.py b/novelwriter/core/status.py
index a5e2e4d6..7935e5aa 100644
--- a/novelwriter/core/status.py
+++ b/novelwriter/core/status.py
@@ -65,6 +65,9 @@ class StatusEntry:
NO_ENTRY = StatusEntry("", QColor(0, 0, 0), nwStatusShape.SQUARE, QIcon(), 0)
+T_UpdateEntry = list[tuple[str | None, StatusEntry]]
+T_StatusKinds = Literal["s", "i"]
+
class NWStatus:
@@ -73,7 +76,7 @@ class NWStatus:
__slots__ = ("_store", "_default", "_prefix", "_height")
- def __init__(self, prefix: Literal["s", "i"]) -> None:
+ def __init__(self, prefix: T_StatusKinds) -> None:
self._store: dict[str, StatusEntry] = {}
self._default = None
self._prefix = prefix[:1]
@@ -120,7 +123,7 @@ class NWStatus:
return key
- def update(self, update: list[tuple[str | None, StatusEntry]]) -> None:
+ def update(self, update: T_UpdateEntry) -> None:
"""Update the list of statuses."""
self._store.clear()
for key, entry in update:
@@ -130,10 +133,6 @@ class NWStatus:
if self._default not in self._store:
self._default = next(iter(self._store)) if self._store else None
- # Emit the change signal and refresh tree
- SHARED.projectSignalProxy({"event": "statusLabels", "kind": self._prefix})
- SHARED.project.tree.refreshAllItems()
-
return
def check(self, value: str) -> str:
diff --git a/novelwriter/core/tree.py b/novelwriter/core/tree.py
index e97b8931..1f919542 100644
--- a/novelwriter/core/tree.py
+++ b/novelwriter/core/tree.py
@@ -128,7 +128,7 @@ class NWTree:
self._model.insertChild(node, index, pos)
self._nodes[item.itemHandle] = node
self._items[item.itemHandle] = item
- self._itemChange(item.itemHandle, nwChange.CREATE)
+ self._itemChange(item, nwChange.CREATE)
else:
logger.error("Could not locate parent of '%s'", item.itemHandle)
return False
@@ -137,7 +137,7 @@ class NWTree:
self._model.insertChild(node, QModelIndex(), pos)
self._nodes[item.itemHandle] = node
self._items[item.itemHandle] = item
- self._itemChange(item.itemHandle, nwChange.CREATE)
+ self._itemChange(item, nwChange.CREATE)
else:
logger.error("Invalid project item '%s'", item.itemHandle)
return False
@@ -148,7 +148,7 @@ class NWTree:
if (node := self._nodes.get(tHandle)) and tHandle in self._items:
index = self._model.indexFromNode(node)
if index.isValid() and self._model.removeChild(index.parent(), index.row()):
- self._itemChange(tHandle, nwChange.DELETE)
+ self._itemChange(node.item, nwChange.DELETE)
del self._nodes[tHandle]
del self._items[tHandle]
return True
@@ -246,7 +246,7 @@ class NWTree:
indexS = self._model.indexFromNode(node, 0)
indexE = self._model.indexFromNode(node, 3)
self._model.dataChanged.emit(indexS, indexE)
- self._itemChange(tHandle, nwChange.UPDATE)
+ self._itemChange(node.item, nwChange.UPDATE)
return
def refreshAllItems(self) -> None:
@@ -464,11 +464,18 @@ class NWTree:
# Internal Functions
##
- def _itemChange(self, tHandle: str, change: nwChange) -> None:
+ def _itemChange(self, item: NWItem, change: nwChange) -> None:
"""Signal item change and notify project."""
+ tHandle = item.itemHandle
logger.debug("Item change: %s -> %s", tHandle, change.name)
self._project.setProjectChanged(True)
- SHARED.projectSignalProxy({"event": "itemChanged", "handle": tHandle, "change": change})
+ SHARED.projectSignalProxy(
+ {"event": "itemChanged", "handle": tHandle, "change": change}
+ )
+ if item.isRootType():
+ SHARED.projectSignalProxy(
+ {"event": "rootChanged", "handle": tHandle, "change": change}
+ )
return
def _getTrashNode(self) -> ProjectNode | None:
diff --git a/novelwriter/dialogs/projectsettings.py b/novelwriter/dialogs/projectsettings.py
index 6cbff7d2..5eecdd0a 100644
--- a/novelwriter/dialogs/projectsettings.py
+++ b/novelwriter/dialogs/projectsettings.py
@@ -185,11 +185,11 @@ class GuiProjectSettings(NDialog):
if self.statusPage.changed:
logger.debug("Updating status labels")
- project.data.itemStatus.update(self.statusPage.getNewList())
+ project.updateStatus("s", self.statusPage.getNewList())
if self.importPage.changed:
logger.debug("Updating importance labels")
- project.data.itemImport.update(self.importPage.getNewList())
+ project.updateStatus("i", self.importPage.getNewList())
if self.replacePage.changed:
logger.debug("Updating auto-replace settings")
diff --git a/novelwriter/gui/noveltree.py b/novelwriter/gui/noveltree.py
index 7a3b114a..3358738f 100644
--- a/novelwriter/gui/noveltree.py
+++ b/novelwriter/gui/noveltree.py
@@ -41,7 +41,7 @@ from novelwriter import CONFIG, SHARED
from novelwriter.common import minmax, qtLambda
from novelwriter.constants import nwKeyWords, nwLabels, nwStyles, trConst
from novelwriter.core.index import IndexHeading
-from novelwriter.enum import nwDocMode, nwItemClass, nwOutline
+from novelwriter.enum import nwChange, nwDocMode, nwItemClass, nwOutline
from novelwriter.extensions.modified import NIconToolButton
from novelwriter.extensions.novelselector import NovelSelector
from novelwriter.gui.theme import STYLES_MIN_TOOLBUTTON
@@ -174,8 +174,8 @@ class GuiNovelView(QWidget):
self.novelTree.refreshTree(rootHandle=SHARED.project.data.getLastHandle("novelTree"))
return
- @pyqtSlot(str)
- def updateRootItem(self, tHandle: str) -> None:
+ @pyqtSlot(str, Enum)
+ def updateRootItem(self, tHandle: str, change: nwChange) -> None:
"""If any root item changes, rebuild the novel root menu."""
self.novelBar.buildNovelRootMenu()
return
diff --git a/novelwriter/gui/outline.py b/novelwriter/gui/outline.py
index 718caee0..e07f3c84 100644
--- a/novelwriter/gui/outline.py
+++ b/novelwriter/gui/outline.py
@@ -43,7 +43,7 @@ from PyQt5.QtWidgets import (
from novelwriter import CONFIG, SHARED
from novelwriter.common import checkInt, formatFileFilter, makeFileNameSafe
from novelwriter.constants import nwKeyWords, nwLabels, nwStats, nwStyles, trConst
-from novelwriter.enum import nwDocMode, nwItemClass, nwItemLayout, nwItemType, nwOutline
+from novelwriter.enum import nwChange, nwDocMode, nwItemClass, nwItemLayout, nwItemType, nwOutline
from novelwriter.error import logException
from novelwriter.extensions.configlayout import NColourLabel
from novelwriter.extensions.novelselector import NovelSelector
@@ -165,8 +165,8 @@ class GuiOutlineView(QWidget):
# Public Slots
##
- @pyqtSlot(str)
- def updateRootItem(self, tHandle: str) -> None:
+ @pyqtSlot(str, Enum)
+ def updateRootItem(self, tHandle: str, change: nwChange) -> None:
"""Handle tasks whenever a root folders changes."""
self.outlineBar.populateNovelList()
self.outlineData.updateClasses()
@@ -380,8 +380,8 @@ class GuiOutlineTree(QTreeWidget):
self.setSelectionMode(QAbstractItemView.SelectionMode.SingleSelection)
self.setExpandsOnDoubleClick(False)
self.setDragEnabled(False)
- self.itemDoubleClicked.connect(self._treeDoubleClick)
- self.itemSelectionChanged.connect(self._itemSelected)
+ self.itemDoubleClicked.connect(self._onItemDoubleClicked)
+ self.itemSelectionChanged.connect(self._onItemSelectionChanged)
self.setIconSize(SHARED.theme.baseIconSize)
self.setIndentation(0)
@@ -563,7 +563,7 @@ class GuiOutlineTree(QTreeWidget):
##
@pyqtSlot("QTreeWidgetItem*", int)
- def _treeDoubleClick(self, tItem: QTreeWidgetItem, tCol: int) -> None:
+ def _onItemDoubleClicked(self, tItem: QTreeWidgetItem, tCol: int) -> None:
"""Extract the handle and line number of the title double-
clicked, and send it to the main gui class for opening in the
document editor.
@@ -574,14 +574,13 @@ class GuiOutlineTree(QTreeWidget):
return
@pyqtSlot()
- def _itemSelected(self) -> None:
+ def _onItemSelectionChanged(self) -> None:
"""Extract the handle and line number of the currently selected
title, and send it to the details panel.
"""
- selItems = self.selectedItems()
- if selItems:
- tHandle = selItems[0].data(self._colIdx[nwOutline.TITLE], self.D_HANDLE)
- sTitle = selItems[0].data(self._colIdx[nwOutline.TITLE], self.D_TITLE)
+ if items := self.selectedItems():
+ tHandle = items[0].data(self._colIdx[nwOutline.TITLE], self.D_HANDLE)
+ sTitle = items[0].data(self._colIdx[nwOutline.TITLE], self.D_TITLE)
self.activeItemChanged.emit(tHandle, sTitle)
return
diff --git a/novelwriter/gui/projtree.py b/novelwriter/gui/projtree.py
index 4f996c1b..aa3ea021 100644
--- a/novelwriter/gui/projtree.py
+++ b/novelwriter/gui/projtree.py
@@ -66,15 +66,9 @@ class GuiProjectView(QWidget):
available are mapped through to the project tree class.
"""
- # Signals triggered when the meta data values of items change
- rootFolderChanged = pyqtSignal(str)
-
- # Signals for user interaction with the project tree
- selectedItemChanged = pyqtSignal(str)
openDocumentRequest = pyqtSignal(str, Enum, str, bool)
-
- # Requests for the main GUI
projectSettingsRequest = pyqtSignal(int)
+ selectedItemChanged = pyqtSignal(str)
def __init__(self, parent: QWidget) -> None:
super().__init__(parent=parent)
@@ -229,8 +223,8 @@ class GuiProjectView(QWidget):
self.projTree.newTreeItem(nwItemType.FILE, copyDoc=tHandle)
return
- @pyqtSlot(str)
- def updateRootItem(self, tHandle: str) -> None:
+ @pyqtSlot(str, Enum)
+ def updateRootItem(self, tHandle: str, change: nwChange) -> None:
"""Process root item changes."""
self.projBar.buildQuickLinksMenu()
return
@@ -625,9 +619,8 @@ class GuiProjectTree(QTreeView):
if root := SHARED.project.tree.nodes.get(itemRoot):
pos = root.row() + 1
- SHARED.project.newRoot(itemClass, pos)
+ tHandle = SHARED.project.newRoot(itemClass, pos)
self.restoreExpandedState()
- self.projView.rootFolderChanged.emit(tHandle)
elif itemType in (nwItemType.FILE, nwItemType.FOLDER):
@@ -684,7 +677,10 @@ class GuiProjectTree(QTreeView):
SHARED.project.index.reIndexHandle(tHandle)
SHARED.project.tree.refreshItems([tHandle])
else:
- SHARED.project.newFolder(newLabel, sHandle, pos)
+ tHandle = SHARED.project.newFolder(newLabel, sHandle, pos)
+
+ # Select the new item automatically
+ self.setSelectedHandle(tHandle)
return
diff --git a/novelwriter/guimain.py b/novelwriter/guimain.py
index ee8c7b32..8d83bd92 100644
--- a/novelwriter/guimain.py
+++ b/novelwriter/guimain.py
@@ -221,6 +221,9 @@ class GuiMain(QMainWindow):
SHARED.projectItemChanged.connect(self.projView.onProjectItemChanged)
SHARED.projectStatusChanged.connect(self.mainStatus.updateProjectStatus)
SHARED.projectStatusMessage.connect(self.mainStatus.setStatusMessage)
+ SHARED.rootFolderChanged.connect(self.novelView.updateRootItem)
+ SHARED.rootFolderChanged.connect(self.outlineView.updateRootItem)
+ SHARED.rootFolderChanged.connect(self.projView.updateRootItem)
SHARED.spellLanguageChanged.connect(self.mainStatus.setLanguage)
SHARED.statusLabelsChanged.connect(self.docViewerPanel.updateStatusLabels)
@@ -235,9 +238,6 @@ class GuiMain(QMainWindow):
self.projView.openDocumentRequest.connect(self._openDocument)
self.projView.projectSettingsRequest.connect(self.showProjectSettingsDialog)
- self.projView.rootFolderChanged.connect(self.novelView.updateRootItem)
- self.projView.rootFolderChanged.connect(self.outlineView.updateRootItem)
- self.projView.rootFolderChanged.connect(self.projView.updateRootItem)
self.projView.selectedItemChanged.connect(self.itemDetails.updateViewBox)
self.novelView.openDocumentRequest.connect(self._openDocument)
@@ -1083,6 +1083,9 @@ class GuiMain(QMainWindow):
self.projView.initSettings()
self.novelView.initSettings()
self.outlineView.initSettings()
+
+ # Force update of word count
+ self._lastTotalCount = 0
self._updateStatusWordCount()
return
diff --git a/novelwriter/shared.py b/novelwriter/shared.py
index f5fa135e..b3a1b04f 100644
--- a/novelwriter/shared.py
+++ b/novelwriter/shared.py
@@ -63,6 +63,7 @@ class SharedData(QObject):
indexCleared = pyqtSignal()
mainClockTick = pyqtSignal()
projectItemChanged = pyqtSignal(str, Enum)
+ rootFolderChanged = pyqtSignal(str, Enum)
projectStatusChanged = pyqtSignal(bool)
projectStatusMessage = pyqtSignal(str)
spellLanguageChanged = pyqtSignal(str, str)
@@ -338,6 +339,10 @@ class SharedData(QObject):
self.projectItemChanged.emit(
data.get("handle", ""), data.get("change", nwChange.UPDATE)
)
+ elif event == "rootChanged":
+ self.rootFolderChanged.emit(
+ data.get("handle", ""), data.get("change", nwChange.UPDATE)
+ )
return
##
diff --git a/tests/mocked.py b/tests/mocked.py
index f5df63bf..dc663331 100644
--- a/tests/mocked.py
+++ b/tests/mocked.py
@@ -20,6 +20,8 @@ along with this program. If not, see .
"""
from __future__ import annotations
+from unittest.mock import MagicMock
+
from PyQt5.QtGui import QFont, QIcon, QPixmap
from PyQt5.QtWidgets import QWidget
@@ -28,7 +30,9 @@ class MockGuiMain(QWidget):
def __init__(self):
super().__init__()
- self.mainStatus = MockStatusBar()
+ self.mainStatus = MagicMock()
+ self.docEditor = MagicMock()
+ self.docViewer = MagicMock()
self.projPath = ""
return
@@ -52,18 +56,6 @@ class MockGuiMain(QWidget):
return "close"
-class MockStatusBar:
-
- def __init__(self):
- return
-
- def setStatus(self, text):
- return
-
- def updateProjectStatus(self, status):
- return
-
-
class MockTheme:
def __init__(self):
diff --git a/tests/reference/coreProject_NewFileFolder_nwProject.nwx b/tests/reference/coreProject_NewFileFolder_nwProject.nwx
index fbfa9e9c..40dd54ee 100644
--- a/tests/reference/coreProject_NewFileFolder_nwProject.nwx
+++ b/tests/reference/coreProject_NewFileFolder_nwProject.nwx
@@ -1,5 +1,5 @@
-
+
New Project
Jane Doe
@@ -37,7 +37,7 @@
Title Page
- -
+
-
New Chapter
@@ -45,11 +45,11 @@
New Chapter
- -
+
-
New Scene
- -
+
-
Stuff
@@ -57,11 +57,11 @@
Hello
- -
+
-
Plot
- -
+
-
Characters
@@ -69,11 +69,11 @@
Jane
- -
+
-
John
- -
+
-
Locations
diff --git a/tests/reference/coreProject_NewRoot_nwProject.nwx b/tests/reference/coreProject_NewRoot_nwProject.nwx
index 1a6938aa..e284a2db 100644
--- a/tests/reference/coreProject_NewRoot_nwProject.nwx
+++ b/tests/reference/coreProject_NewRoot_nwProject.nwx
@@ -1,5 +1,5 @@
-
+
New Project
Jane Doe
@@ -37,7 +37,7 @@
Title Page
- -
+
-
New Chapter
@@ -45,51 +45,51 @@
New Chapter
- -
+
-
New Scene
- -
+
-
Plot
- -
+
-
Characters
- -
+
-
Locations
- -
+
-
Novel
- -
+
-
Plot
- -
+
-
Characters
- -
+
-
Locations
- -
+
-
Timeline
- -
+
-
Objects
- -
+
-
Custom
- -
+
-
Custom
diff --git a/tests/reference/coreTools_DocDuplicator_nwProject.nwx b/tests/reference/coreTools_DocDuplicator_nwProject.nwx
index 431cc119..1157b376 100644
--- a/tests/reference/coreTools_DocDuplicator_nwProject.nwx
+++ b/tests/reference/coreTools_DocDuplicator_nwProject.nwx
@@ -1,5 +1,5 @@
-
+
New Project
Jane Doe
@@ -37,7 +37,7 @@
Title Page
- -
+
-
New Chapter
@@ -45,23 +45,23 @@
New Chapter
- -
+
-
New Chapter
- -
+
-
New Scene
- -
+
-
New Scene
- -
+
-
New Scene
- -
+
-
New Chapter
@@ -69,11 +69,11 @@
New Chapter
- -
+
-
New Scene
- -
+
-
Novel
@@ -81,7 +81,7 @@
Title Page
- -
+
-
New Chapter
@@ -89,19 +89,19 @@
New Chapter
- -
+
-
New Scene
- -
+
-
Plot
- -
+
-
Characters
- -
+
-
Locations
diff --git a/tests/reference/coreTools_ProjectBuilderA_nwProject.nwx b/tests/reference/coreTools_ProjectBuilderA_nwProject.nwx
index 01d3a0ef..387cf8da 100644
--- a/tests/reference/coreTools_ProjectBuilderA_nwProject.nwx
+++ b/tests/reference/coreTools_ProjectBuilderA_nwProject.nwx
@@ -1,5 +1,5 @@
-
+
Test Project A
Jane Doe
@@ -37,7 +37,7 @@
Title Page
- -
+
-
Chapter 1
@@ -45,15 +45,15 @@
Scene 1.1
- -
+
-
Scene 1.2
- -
+
-
Scene 1.3
- -
+
-
Chapter 2
@@ -61,15 +61,15 @@
Scene 2.1
- -
+
-
Scene 2.2
- -
+
-
Scene 2.3
- -
+
-
Chapter 3
@@ -77,15 +77,15 @@
Scene 3.1
- -
+
-
Scene 3.2
- -
+
-
Scene 3.3
- -
+
-
Plot
@@ -93,7 +93,7 @@
Main Plot
- -
+
-
Characters
@@ -101,7 +101,7 @@
Protagonist
- -
+
-
Locations
@@ -109,11 +109,11 @@
Main Location
- -
+
-
Archive
- -
+
-
Trash
diff --git a/tests/reference/coreTools_ProjectBuilderB_nwProject.nwx b/tests/reference/coreTools_ProjectBuilderB_nwProject.nwx
index ed43d5cd..cb0a4843 100644
--- a/tests/reference/coreTools_ProjectBuilderB_nwProject.nwx
+++ b/tests/reference/coreTools_ProjectBuilderB_nwProject.nwx
@@ -1,5 +1,5 @@
-
+
Test Project B
Jane Doe
@@ -37,31 +37,31 @@
Title Page
- -
+
-
Scene 1
- -
+
-
Scene 2
- -
+
-
Scene 3
- -
+
-
Scene 4
- -
+
-
Scene 5
- -
+
-
Scene 6
- -
+
-
Plot
@@ -69,7 +69,7 @@
Main Plot
- -
+
-
Characters
@@ -77,7 +77,7 @@
Protagonist
- -
+
-
Locations
@@ -85,11 +85,11 @@
Main Location
- -
+
-
Archive
- -
+
-
Trash
diff --git a/tests/reference/guiEditor_Main_Final_0000000000010.nwd b/tests/reference/guiEditor_Main_Final_0000000000010.nwd
deleted file mode 100644
index eb6ce774..00000000
--- a/tests/reference/guiEditor_Main_Final_0000000000010.nwd
+++ /dev/null
@@ -1,10 +0,0 @@
-%%~name: New Note
-%%~path: 000000000000a/0000000000010
-%%~kind: CHARACTER/NOTE
-%%~hash: 9fae6dfdd3d1c0822d3a3cf90c0142e65ad8e557
-%%~date: 2023-08-25 18:14:24/2023-08-25 18:14:24
-# Jane Doe
-
-@tag: Jane
-
-This is a file about Jane.
diff --git a/tests/reference/guiEditor_Main_Final_0000000000011.nwd b/tests/reference/guiEditor_Main_Final_0000000000011.nwd
index bd73dfd8..12cb6647 100644
--- a/tests/reference/guiEditor_Main_Final_0000000000011.nwd
+++ b/tests/reference/guiEditor_Main_Final_0000000000011.nwd
@@ -1,10 +1,10 @@
%%~name: New Note
-%%~path: 0000000000009/0000000000011
-%%~kind: PLOT/NOTE
-%%~hash: 3d3697638a70fc86cc023df6d42202a262d93905
-%%~date: 2024-04-14 23:46:49/2024-04-14 23:46:49
-# Main Plot
+%%~path: 000000000000a/0000000000011
+%%~kind: CHARACTER/NOTE
+%%~hash: 9fae6dfdd3d1c0822d3a3cf90c0142e65ad8e557
+%%~date: 2024-11-23 18:26:00/2024-11-23 18:26:00
+# Jane Doe
-@tag: MainPlot
+@tag: Jane
-This is a file [i]detailing[/i] the main plot.
+This is a file about Jane.
diff --git a/tests/reference/guiEditor_Main_Final_0000000000012.nwd b/tests/reference/guiEditor_Main_Final_0000000000012.nwd
index 2c624eca..7e9f61ca 100644
--- a/tests/reference/guiEditor_Main_Final_0000000000012.nwd
+++ b/tests/reference/guiEditor_Main_Final_0000000000012.nwd
@@ -1,10 +1,10 @@
%%~name: New Note
-%%~path: 000000000000b/0000000000012
-%%~kind: WORLD/NOTE
-%%~hash: 3f5c3c6c3ba1c27c30b8ac9e59c222fb9a1bd775
-%%~date: 2023-08-25 18:17:45/2023-08-25 18:17:45
-# Main Location
+%%~path: 0000000000009/0000000000012
+%%~kind: PLOT/NOTE
+%%~hash: 3d3697638a70fc86cc023df6d42202a262d93905
+%%~date: 2024-11-23 18:26:31/2024-11-23 18:26:31
+# Main Plot
-@tag: Home
+@tag: MainPlot
-This is a file describing Jane’s home.
+This is a file [i]detailing[/i] the main plot.
diff --git a/tests/reference/guiEditor_Main_Final_0000000000013.nwd b/tests/reference/guiEditor_Main_Final_0000000000013.nwd
new file mode 100644
index 00000000..4b749970
--- /dev/null
+++ b/tests/reference/guiEditor_Main_Final_0000000000013.nwd
@@ -0,0 +1,10 @@
+%%~name: New Note
+%%~path: 000000000000b/0000000000013
+%%~kind: WORLD/NOTE
+%%~hash: 3f5c3c6c3ba1c27c30b8ac9e59c222fb9a1bd775
+%%~date: 2024-11-23 18:28:00/2024-11-23 18:28:00
+# Main Location
+
+@tag: Home
+
+This is a file describing Jane’s home.
diff --git a/tests/reference/guiEditor_Main_Final_nwProject.nwx b/tests/reference/guiEditor_Main_Final_nwProject.nwx
index 4a06631a..e90a0478 100644
--- a/tests/reference/guiEditor_Main_Final_nwProject.nwx
+++ b/tests/reference/guiEditor_Main_Final_nwProject.nwx
@@ -1,5 +1,5 @@
-
+
New Project
Jane Doe
@@ -22,19 +22,19 @@
Finished
- New
+ New
Minor
Major
Main
-
+
-
Novel
-
-
+
Title Page
-
@@ -42,36 +42,40 @@
New Chapter
-
-
+
New Chapter
-
-
+
New Scene
-
Plot
- -
-
+
-
+
New Note
-
Characters
- -
-
+
-
+
New Note
-
- World
+ Locations
- -
-
+
-
+
New Note
+ -
+
+ Trash
+
diff --git a/tests/reference/guiEditor_Main_Initial_nwProject.nwx b/tests/reference/guiEditor_Main_Initial_nwProject.nwx
index f5119a55..af535851 100644
--- a/tests/reference/guiEditor_Main_Initial_nwProject.nwx
+++ b/tests/reference/guiEditor_Main_Initial_nwProject.nwx
@@ -1,5 +1,5 @@
-
+
New Project
Jane Doe
@@ -59,7 +59,7 @@
-
- World
+ Locations
diff --git a/tests/test_core/test_core_item.py b/tests/test_core/test_core_item.py
index b27ab593..8ebfd787 100644
--- a/tests/test_core/test_core_item.py
+++ b/tests/test_core/test_core_item.py
@@ -275,7 +275,7 @@ def testCoreItem_Methods(mockGUI, mockRnd, fncPath):
"handle": "000000000000f",
"parent": "000000000000d",
"root": "0000000000008",
- "order": "0",
+ "order": "1",
"type": "FILE",
"class": "NOVEL",
"layout": "DOCUMENT"
diff --git a/tests/test_dialogs/test_dlg_projectsettings.py b/tests/test_dialogs/test_dlg_projectsettings.py
index 8eeceba0..151a7e8a 100644
--- a/tests/test_dialogs/test_dlg_projectsettings.py
+++ b/tests/test_dialogs/test_dlg_projectsettings.py
@@ -161,7 +161,7 @@ def testDlgProjSettings_StatusImport(qtbot, monkeypatch, nwGUI, projPath, mockRn
project.tree[hCharNote].setImport(C.iMajor) # type: ignore
project.tree[hWorldNote].setImport(C.iMain) # type: ignore
- nwGUI.projView.populateTree()
+ project.tree.refreshAllItems()
project.countStatus()
assert [e.count for _, e in project.data.itemStatus.iterItems()] == [2, 0, 2, 1]
diff --git a/tests/test_gui/test_gui_docviewer.py b/tests/test_gui/test_gui_docviewer.py
index afcad9a2..c312f5ac 100644
--- a/tests/test_gui/test_gui_docviewer.py
+++ b/tests/test_gui/test_gui_docviewer.py
@@ -29,7 +29,7 @@ from PyQt5.QtGui import QDesktopServices, QMouseEvent, QTextCursor
from PyQt5.QtWidgets import QAction, QApplication, QMenu
from novelwriter import CONFIG, SHARED
-from novelwriter.enum import nwDocAction
+from novelwriter.enum import nwChange, nwDocAction
from novelwriter.formats.toqdoc import ToQTextDocument
from novelwriter.types import QtModNone, QtMouseLeft
@@ -206,12 +206,12 @@ def testGuiViewer_Main(qtbot, monkeypatch, nwGUI, prjLipsum):
nwItem = SHARED.project.tree["4c4f28287af27"]
nwItem.setName("Test Title") # type: ignore
assert nwItem.itemName == "Test Title" # type: ignore
- docViewer.updateDocInfo("4c4f28287af27")
+ docViewer.onProjectItemChanged("4c4f28287af27", nwChange.UPDATE)
assert docViewer.docHeader.itemTitle.text() == "Characters \u203a Test Title"
# Title without full path
CONFIG.showFullPath = False
- docViewer.updateDocInfo("4c4f28287af27")
+ docViewer.onProjectItemChanged("4c4f28287af27", nwChange.UPDATE)
assert docViewer.docHeader.itemTitle.text() == "Test Title"
CONFIG.showFullPath = True
diff --git a/tests/test_gui/test_gui_docviewerpanel.py b/tests/test_gui/test_gui_docviewerpanel.py
index 729c5690..d5a609c8 100644
--- a/tests/test_gui/test_gui_docviewerpanel.py
+++ b/tests/test_gui/test_gui_docviewerpanel.py
@@ -38,8 +38,7 @@ def testGuiViewerPanel_BackRefs(qtbot, monkeypatch, nwGUI, projPath, mockRnd):
monkeypatch.setattr(GuiEditLabel, "getLabel", lambda *a, text: (text, True))
buildTestProject(nwGUI, projPath)
- projTree = nwGUI.projView.projTree
- projTree.expandAll()
+ nwGUI.projView.projTree.expandAll()
viewPanel = nwGUI.docViewerPanel
tabBackRefs = viewPanel.tabBackRefs
@@ -81,7 +80,7 @@ def testGuiViewerPanel_BackRefs(qtbot, monkeypatch, nwGUI, projPath, mockRnd):
# Update Label
SHARED.project.tree[C.hSceneDoc].setName("First Scene") # type: ignore
- projTree.renameTreeItem(C.hSceneDoc)
+ nwGUI.projView.renameTreeItem(C.hSceneDoc)
item = tabBackRefs.topLevelItem(0)
assert item.text(tabBackRefs.C_DOC) == "First Scene"
assert item.text(tabBackRefs.C_TITLE) == "Scene One"
@@ -127,8 +126,7 @@ def testGuiViewerPanel_Tags(qtbot, monkeypatch, caplog, nwGUI, projPath, mockRnd
monkeypatch.setattr(GuiEditLabel, "getLabel", lambda *a, text: (text, True))
buildTestProject(nwGUI, projPath)
- projTree = nwGUI.projView.projTree
- projTree.expandAll()
+ nwGUI.projView.projTree.expandAll()
viewPanel = nwGUI.docViewerPanel
nwGUI.openDocument(C.hSceneDoc)
@@ -175,7 +173,7 @@ def testGuiViewerPanel_Tags(qtbot, monkeypatch, caplog, nwGUI, projPath, mockRnd
nwGUI.docEditor.setPlainText("# Jane Smith\n\n@tag: Janey\n\n")
nwGUI.saveDocument()
SHARED.project.tree[hJane].setName("Awesome Jane") # type: ignore
- projTree.renameTreeItem(hJane)
+ nwGUI.projView.renameTreeItem(hJane)
item = charTab.topLevelItem(0)
assert item.text(charTab.C_NAME) == "Janey"
assert item.text(charTab.C_DOC) == "Awesome Jane"
@@ -221,8 +219,7 @@ def testGuiViewerPanel_Tags(qtbot, monkeypatch, caplog, nwGUI, projPath, mockRnd
nwJohn = SHARED.project.tree[hJohn]
assert isinstance(nwJohn, NWItem)
nwJohn.setActive(False)
- projTree.setTreeItemValues(nwJohn)
- projTree._alertTreeChange(hJohn, flush=False)
+ nwJohn.notifyToRefresh()
assert charTab.topLevelItemCount() == 1
# Update Labels
diff --git a/tests/test_gui/test_gui_guimain.py b/tests/test_gui/test_gui_guimain.py
index 596183f6..7ca32b8b 100644
--- a/tests/test_gui/test_gui_guimain.py
+++ b/tests/test_gui/test_gui_guimain.py
@@ -210,9 +210,8 @@ def testGuiMain_Editing(qtbot, monkeypatch, nwGUI, projPath, tstPaths, mockRnd):
assert nwGUI.closeProject()
assert len(SHARED.project.tree) == 0
- assert len(SHARED.project.tree._order) == 0
- assert len(SHARED.project.tree._roots) == 0
- assert SHARED.project.tree.trashRoot is None
+ assert len(SHARED.project.tree._items) == 0
+ assert len(SHARED.project.tree._nodes) == 0
assert SHARED.project.data.name == ""
assert SHARED.project.data.author == ""
assert SHARED.project.data.spellCheck is False
@@ -228,23 +227,22 @@ def testGuiMain_Editing(qtbot, monkeypatch, nwGUI, projPath, tstPaths, mockRnd):
assert nwGUI.openProject(projPath)
# Check that we loaded the data
- assert len(SHARED.project.tree) == 8
- assert len(SHARED.project.tree._order) == 8
- assert len(SHARED.project.tree._roots) == 4
- assert SHARED.project.tree.trashRoot is None
+ assert len(SHARED.project.tree) == 9
+ assert SHARED.project.tree.model.root.childCount() == 5
+ assert SHARED.project.tree.trash is not None # Created automatically
assert SHARED.project.data.name == "New Project"
assert SHARED.project.data.author == "Jane Doe"
assert SHARED.project.data.spellCheck is False
# Check that tree items have been created
- assert nwGUI.projView.projTree._getTreeItem(C.hNovelRoot) is not None
- assert nwGUI.projView.projTree._getTreeItem(C.hPlotRoot) is not None
- assert nwGUI.projView.projTree._getTreeItem(C.hCharRoot) is not None
- assert nwGUI.projView.projTree._getTreeItem(C.hWorldRoot) is not None
- assert nwGUI.projView.projTree._getTreeItem(C.hTitlePage) is not None
- assert nwGUI.projView.projTree._getTreeItem(C.hChapterDir) is not None
- assert nwGUI.projView.projTree._getTreeItem(C.hChapterDoc) is not None
- assert nwGUI.projView.projTree._getTreeItem(C.hSceneDoc) is not None
+ assert SHARED.project.tree[C.hNovelRoot] is not None
+ assert SHARED.project.tree[C.hPlotRoot] is not None
+ assert SHARED.project.tree[C.hCharRoot] is not None
+ assert SHARED.project.tree[C.hWorldRoot] is not None
+ assert SHARED.project.tree[C.hTitlePage] is not None
+ assert SHARED.project.tree[C.hChapterDir] is not None
+ assert SHARED.project.tree[C.hChapterDoc] is not None
+ assert SHARED.project.tree[C.hSceneDoc] is not None
nwGUI.mainMenu.aSpellCheck.setChecked(True)
nwGUI.mainMenu._toggleSpellCheck()
@@ -256,10 +254,12 @@ def testGuiMain_Editing(qtbot, monkeypatch, nwGUI, projPath, tstPaths, mockRnd):
CONFIG.autoScroll = True
# Add a Character File
- nwGUI._switchFocus(nwFocus.TREE)
+ nwGUI._changeView(nwView.PROJECT)
+ nwGUI.projView.projTree.expandAll()
nwGUI.projView.projTree.clearSelection()
- nwGUI.projView.projTree._getTreeItem(C.hCharRoot).setSelected(True)
+ nwGUI.projView.projTree.setSelectedHandle(C.hCharRoot)
nwGUI.projView.projTree.newTreeItem(nwItemType.FILE, None, isNote=True)
+ nwGUI.projView.projTree.expandAll()
nwGUI.openSelectedItem()
# Text Editor
@@ -290,10 +290,11 @@ def testGuiMain_Editing(qtbot, monkeypatch, nwGUI, projPath, tstPaths, mockRnd):
qtbot.keyClick(docEditor, Qt.Key.Key_Return, delay=KEY_DELAY)
# Add a Plot File
- nwGUI._switchFocus(nwFocus.TREE)
+ nwGUI.projView.projTree.expandAll()
nwGUI.projView.projTree.clearSelection()
- nwGUI.projView.projTree._getTreeItem(C.hPlotRoot).setSelected(True)
+ nwGUI.projView.projTree.setSelectedHandle(C.hPlotRoot)
nwGUI.projView.projTree.newTreeItem(nwItemType.FILE, None, isNote=True)
+ nwGUI.projView.projTree.expandAll()
nwGUI.openSelectedItem()
# Type something into the document
@@ -312,10 +313,11 @@ def testGuiMain_Editing(qtbot, monkeypatch, nwGUI, projPath, tstPaths, mockRnd):
qtbot.keyClick(docEditor, Qt.Key.Key_Return, delay=KEY_DELAY)
# Add a World File
- nwGUI._switchFocus(nwFocus.TREE)
+ nwGUI.projView.projTree.expandAll()
nwGUI.projView.projTree.clearSelection()
- nwGUI.projView.projTree._getTreeItem(C.hWorldRoot).setSelected(True)
+ nwGUI.projView.projTree.setSelectedHandle(C.hWorldRoot)
nwGUI.projView.projTree.newTreeItem(nwItemType.FILE, None, isNote=True)
+ nwGUI.projView.projTree.expandAll()
nwGUI.openSelectedItem()
# Add Some Text
@@ -343,11 +345,9 @@ def testGuiMain_Editing(qtbot, monkeypatch, nwGUI, projPath, tstPaths, mockRnd):
nwGUI._autoSaveProject()
# Select the 'New Scene' file
- nwGUI._switchFocus(nwFocus.TREE)
+ nwGUI.projView.projTree.expandAll()
nwGUI.projView.projTree.clearSelection()
- nwGUI.projView.projTree._getTreeItem(C.hNovelRoot).setExpanded(True)
- nwGUI.projView.projTree._getTreeItem(C.hChapterDir).setExpanded(True)
- nwGUI.projView.projTree._getTreeItem(C.hSceneDoc).setSelected(True)
+ nwGUI.projView.projTree.setSelectedHandle(C.hSceneDoc)
nwGUI.openSelectedItem()
# Type something into the document
@@ -533,6 +533,7 @@ def testGuiMain_Editing(qtbot, monkeypatch, nwGUI, projPath, tstPaths, mockRnd):
# Indent and Align
# ================
+ nwGUI._switchFocus(nwView.EDITOR)
for c in "\t\"Tab-indented text\"":
qtbot.keyClick(docEditor, c, delay=KEY_DELAY)
qtbot.keyClick(docEditor, Qt.Key.Key_Return, delay=KEY_DELAY)
@@ -622,12 +623,6 @@ def testGuiMain_Editing(qtbot, monkeypatch, nwGUI, projPath, tstPaths, mockRnd):
copyfile(projFile, testFile)
assert cmpFiles(testFile, compFile, ignoreStart=NWD_IGNORE)
- projFile = projPath / "content" / "0000000000010.nwd"
- testFile = tstPaths.outDir / "guiEditor_Main_Final_0000000000010.nwd"
- compFile = tstPaths.refDir / "guiEditor_Main_Final_0000000000010.nwd"
- copyfile(projFile, testFile)
- assert cmpFiles(testFile, compFile, ignoreStart=NWD_IGNORE)
-
projFile = projPath / "content" / "0000000000011.nwd"
testFile = tstPaths.outDir / "guiEditor_Main_Final_0000000000011.nwd"
compFile = tstPaths.refDir / "guiEditor_Main_Final_0000000000011.nwd"
@@ -640,6 +635,12 @@ def testGuiMain_Editing(qtbot, monkeypatch, nwGUI, projPath, tstPaths, mockRnd):
copyfile(projFile, testFile)
assert cmpFiles(testFile, compFile, ignoreStart=NWD_IGNORE)
+ projFile = projPath / "content" / "0000000000013.nwd"
+ testFile = tstPaths.outDir / "guiEditor_Main_Final_0000000000013.nwd"
+ compFile = tstPaths.refDir / "guiEditor_Main_Final_0000000000013.nwd"
+ copyfile(projFile, testFile)
+ assert cmpFiles(testFile, compFile, ignoreStart=NWD_IGNORE)
+
# qtbot.stop()
diff --git a/tests/test_gui/test_gui_outline.py b/tests/test_gui/test_gui_outline.py
index e92b5a01..bf2974c7 100644
--- a/tests/test_gui/test_gui_outline.py
+++ b/tests/test_gui/test_gui_outline.py
@@ -197,7 +197,8 @@ def testGuiOutline_Content(qtbot, monkeypatch, nwGUI, prjLipsum, fncPath, tstPat
assert outlineBar.novelValue.itemData(2) == "" # All novels
# Add a second novel folder
- newHandle = SHARED.project.newRoot(nwItemClass.NOVEL)
+ with qtbot.waitSignal(SHARED.rootFolderChanged):
+ newHandle = SHARED.project.newRoot(nwItemClass.NOVEL)
# Check new values in dropdown list
assert outlineBar.novelValue.itemData(0) == lipHandle
@@ -277,7 +278,7 @@ def testGuiOutline_Content(qtbot, monkeypatch, nwGUI, prjLipsum, fncPath, tstPat
assert outlineData.fileValue.text() == "Scene One"
assert outlineData.itemValue.text() == "Finished"
- outlineTree._treeDoubleClick(selItem, 0)
+ outlineTree._onItemDoubleClicked(selItem, 0)
assert nwGUI.docEditor.docHandle == "88243afbe5ed8"
# Dump to CSV
diff --git a/tests/test_gui/test_gui_statusbar.py b/tests/test_gui/test_gui_statusbar.py
index 48649ba9..624f1f84 100644
--- a/tests/test_gui/test_gui_statusbar.py
+++ b/tests/test_gui/test_gui_statusbar.py
@@ -86,9 +86,11 @@ def testGuiStatusBar_Main(qtbot, nwGUI, projPath, mockRnd):
# Project Stats
CONFIG.incNotesWCount = False
+ nwGUI._lastTotalCount = 0
nwGUI._updateStatusWordCount()
assert nwGUI.mainStatus.statsText.text() == "Words: 9 (+9)"
CONFIG.incNotesWCount = True
+ nwGUI._lastTotalCount = 0
nwGUI._updateStatusWordCount()
assert nwGUI.mainStatus.statsText.text() == "Words: 11 (+11)"