diff --git a/novelwriter/core/itemmodel.py b/novelwriter/core/itemmodel.py index 34579b950..948b6146e 100644 --- a/novelwriter/core/itemmodel.py +++ b/novelwriter/core/itemmodel.py @@ -91,6 +91,10 @@ def __repr__(self) -> str: f"children={len(self._children)}>" ) + def __bool__(self) -> bool: + """A node should always evaluate to True.""" + return True + ## # Properties ## @@ -190,10 +194,10 @@ def addChild(self, child: ProjectNode, pos: int = -1) -> None: 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 @@ def _refreshChildrenPos(self) -> None: """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 28fad3075..ced91fd10 100644 --- a/novelwriter/core/project.py +++ b/novelwriter/core/project.py @@ -44,6 +44,7 @@ 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 @@ def countStatus(self) -> None: 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 a5e2e4d66..7935e5aa2 100644 --- a/novelwriter/core/status.py +++ b/novelwriter/core/status.py @@ -65,6 +65,9 @@ def duplicate(cls, source: StatusEntry) -> 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 @@ def add(self, key: str | None, name: str, color: tuple[int, int, int], 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 @@ def update(self, update: list[tuple[str | None, StatusEntry]]) -> None: 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 e97b89312..1f919542f 100644 --- a/novelwriter/core/tree.py +++ b/novelwriter/core/tree.py @@ -128,7 +128,7 @@ def add(self, item: NWItem, pos: int = -1) -> bool: 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 @@ def add(self, item: NWItem, pos: int = -1) -> bool: 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 @@ def remove(self, tHandle: str) -> bool: 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 @@ def refreshItems(self, items: list[str]) -> None: 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 @@ def __iter__(self) -> Iterator[NWItem]: # 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 6cbff7d22..5eecdd0a8 100644 --- a/novelwriter/dialogs/projectsettings.py +++ b/novelwriter/dialogs/projectsettings.py @@ -185,11 +185,11 @@ def _doSave(self) -> None: 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 7a3b114a3..3358738fd 100644 --- a/novelwriter/gui/noveltree.py +++ b/novelwriter/gui/noveltree.py @@ -41,7 +41,7 @@ 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 @@ def refreshTree(self) -> None: 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 718caee04..e07f3c841 100644 --- a/novelwriter/gui/outline.py +++ b/novelwriter/gui/outline.py @@ -43,7 +43,7 @@ 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 @@ def treeHasFocus(self) -> bool: # 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 @@ def __init__(self, outlineView: GuiOutlineView) -> None: 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 @@ def exportOutline(self) -> None: ## @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 @@ def _treeDoubleClick(self, tItem: QTreeWidgetItem, tCol: int) -> None: 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 4f996c1b8..aa3ea021e 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 @@ def createFileFromTemplate(self, tHandle: str) -> None: 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 @@ def newTreeItem( 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 @@ def newTreeItem( 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 ee8c7b321..8d83bd926 100644 --- a/novelwriter/guimain.py +++ b/novelwriter/guimain.py @@ -221,6 +221,9 @@ def __init__(self) -> None: 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 @@ def __init__(self) -> None: 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 @@ def _processConfigChanges(self, restart: bool, tree: bool, theme: bool, syntax: 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 f5fa135e4..b3a1b04f3 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 @@ def projectSignalProxy(self, data: dict) -> None: 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 f5df63bf8..dc6633319 100644 --- a/tests/mocked.py +++ b/tests/mocked.py @@ -20,6 +20,8 @@ """ 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 @@ def close(self): 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 fbfa9e9c2..40dd54eeb 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 1a6938aaf..e284a2db8 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 431cc119f..1157b376c 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 01d3a0ef8..387cf8dae 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 ed43d5cd2..cb0a48433 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 eb6ce7743..000000000 --- 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 bd73dfd81..12cb6647d 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 2c624eca0..7e9f61cab 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 000000000..4b7499701 --- /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 4a06631ab..e90a04781 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 f5119a551..af5358516 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 b27ab593a..8ebfd7872 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 8eeceba00..151a7e8aa 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 afcad9a26..c312f5ac5 100644 --- a/tests/test_gui/test_gui_docviewer.py +++ b/tests/test_gui/test_gui_docviewer.py @@ -29,7 +29,7 @@ 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 mockExec(*a): 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 729c56901..d5a609c8d 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 596183f66..7ca32b8b7 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 e92b5a013..bf2974c76 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 48649ba99..624f1f845 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)"