Improve placement of new documents (#2302)

This commit is contained in:
Veronica Berglyd Olsen
2025-04-19 15:05:38 +02:00
committed by GitHub
4 changed files with 272 additions and 83 deletions
+26 -1
View File
@@ -33,7 +33,7 @@ from typing import TYPE_CHECKING, Literal, overload
from PyQt6.QtCore import QModelIndex from PyQt6.QtCore import QModelIndex
from novelwriter import SHARED from novelwriter import SHARED
from novelwriter.constants import nwFiles, nwLabels, trConst from novelwriter.constants import nwFiles, nwLabels, nwStyles, trConst
from novelwriter.core.item import NWItem from novelwriter.core.item import NWItem
from novelwriter.core.itemmodel import ProjectModel, ProjectNode from novelwriter.core.itemmodel import ProjectModel, ProjectNode
from novelwriter.enum import nwChange, nwItemClass, nwItemLayout, nwItemType from novelwriter.enum import nwChange, nwItemClass, nwItemLayout, nwItemType
@@ -262,6 +262,31 @@ class NWTree:
return return
def pickParent(self, sNode: ProjectNode, hLevel: int, isNote: bool) -> tuple[str | None, int]:
"""Pick an appropriate parent handle for adding a new item."""
if sNode.item.isFolderType() or sNode.item.isRootType():
# Always add as a direct child of folders
return sNode.item.itemHandle, sNode.childCount()
pNode = sNode.parent()
pLevel = nwStyles.H_LEVEL.get(pNode.item.mainHeading, 0) if pNode else 0
# Notes are treated as H0, and scenes and sections both as H3
sLevel = min(0 if isNote else nwStyles.H_LEVEL.get(sNode.item.mainHeading, 0), 3)
if pNode and pNode.item.isFileType() and pLevel >= hLevel and sLevel > hLevel:
# If the selected item is a smaller heading and the parent heading
# is equal or larger, we make it a sibling of the parent (See #2260)
return pNode.item.itemParent, pNode.row() + 1
if sNode.childCount() > 0 and (0 < sLevel < hLevel or isNote):
# If the selected item already has child nodes and has a larger
# heading or is a note, we make the new item a child
return sNode.item.itemHandle, sNode.childCount()
# The default behaviour is to make the new item a sibling
return sNode.item.itemParent, sNode.row() + 1
def refreshItems(self, items: list[str]) -> None: def refreshItems(self, items: list[str]) -> None:
"""Refresh these items on the GUI. If they are an ordered range, """Refresh these items on the GUI. If they are an ordered range,
also set the isRange flag to True. also set the isRange flag to True.
+55 -51
View File
@@ -275,9 +275,9 @@ class GuiProjectToolBar(QWidget):
# Add Item Menu # Add Item Menu
self.mAdd = QMenu(self) self.mAdd = QMenu(self)
self.aAddEmpty = qtAddAction(self.mAdd, trConst(nwLabels.ITEM_DESCRIPTION["document"])) self.aAddScene = qtAddAction(self.mAdd, trConst(nwLabels.ITEM_DESCRIPTION["doc_h3"]))
self.aAddEmpty.triggered.connect( self.aAddScene.triggered.connect(
qtLambda(self.projTree.newTreeItem, nwItemType.FILE, hLevel=0, isNote=False) qtLambda(self.projTree.newTreeItem, nwItemType.FILE, hLevel=3, isNote=False)
) )
self.aAddChap = qtAddAction(self.mAdd, trConst(nwLabels.ITEM_DESCRIPTION["doc_h2"])) self.aAddChap = qtAddAction(self.mAdd, trConst(nwLabels.ITEM_DESCRIPTION["doc_h2"]))
@@ -285,9 +285,14 @@ class GuiProjectToolBar(QWidget):
qtLambda(self.projTree.newTreeItem, nwItemType.FILE, hLevel=2, isNote=False) qtLambda(self.projTree.newTreeItem, nwItemType.FILE, hLevel=2, isNote=False)
) )
self.aAddScene = qtAddAction(self.mAdd, trConst(nwLabels.ITEM_DESCRIPTION["doc_h3"])) self.aAddPart = qtAddAction(self.mAdd, trConst(nwLabels.ITEM_DESCRIPTION["doc_h1"]))
self.aAddScene.triggered.connect( self.aAddPart.triggered.connect(
qtLambda(self.projTree.newTreeItem, nwItemType.FILE, hLevel=3, isNote=False) qtLambda(self.projTree.newTreeItem, nwItemType.FILE, hLevel=1, isNote=False)
)
self.aAddEmpty = qtAddAction(self.mAdd, trConst(nwLabels.ITEM_DESCRIPTION["document"]))
self.aAddEmpty.triggered.connect(
qtLambda(self.projTree.newTreeItem, nwItemType.FILE, hLevel=0, isNote=False)
) )
self.aAddNote = qtAddAction(self.mAdd, trConst(nwLabels.ITEM_DESCRIPTION["note"])) self.aAddNote = qtAddAction(self.mAdd, trConst(nwLabels.ITEM_DESCRIPTION["note"]))
@@ -366,9 +371,10 @@ class GuiProjectToolBar(QWidget):
self.tbAdd.setThemeIcon("add", "green") self.tbAdd.setThemeIcon("add", "green")
self.tbMore.setThemeIcon("more_vertical") self.tbMore.setThemeIcon("more_vertical")
self.aAddEmpty.setIcon(SHARED.theme.getIcon("prj_document", "file"))
self.aAddChap.setIcon(SHARED.theme.getIcon("prj_chapter", "chapter"))
self.aAddScene.setIcon(SHARED.theme.getIcon("prj_scene", "scene")) self.aAddScene.setIcon(SHARED.theme.getIcon("prj_scene", "scene"))
self.aAddChap.setIcon(SHARED.theme.getIcon("prj_chapter", "chapter"))
self.aAddPart.setIcon(SHARED.theme.getIcon("prj_title", "title"))
self.aAddEmpty.setIcon(SHARED.theme.getIcon("prj_document", "file"))
self.aAddNote.setIcon(SHARED.theme.getIcon("prj_note", "note")) self.aAddNote.setIcon(SHARED.theme.getIcon("prj_note", "note"))
self.aAddFolder.setIcon(SHARED.theme.getIcon("prj_folder", "folder")) self.aAddFolder.setIcon(SHARED.theme.getIcon("prj_folder", "folder"))
@@ -425,9 +431,10 @@ class GuiProjectToolBar(QWidget):
""" """
nwItem = SHARED.project.tree[tHandle] nwItem = SHARED.project.tree[tHandle]
allowDoc = isinstance(nwItem, NWItem) and nwItem.documentAllowed() allowDoc = isinstance(nwItem, NWItem) and nwItem.documentAllowed()
self.aAddEmpty.setVisible(allowDoc)
self.aAddChap.setVisible(allowDoc)
self.aAddScene.setVisible(allowDoc) self.aAddScene.setVisible(allowDoc)
self.aAddChap.setVisible(allowDoc)
self.aAddPart.setVisible(allowDoc)
self.aAddEmpty.setVisible(allowDoc)
return return
## ##
@@ -503,7 +510,6 @@ class GuiProjectTree(QTreeView):
self.customContextMenuRequested.connect(self.openContextMenu) self.customContextMenuRequested.connect(self.openContextMenu)
# Connect signals # Connect signals
self.clicked.connect(self._onSingleClick)
self.doubleClicked.connect(self._onDoubleClick) self.doubleClicked.connect(self._onDoubleClick)
self.collapsed.connect(self._onNodeCollapsed) self.collapsed.connect(self._onNodeCollapsed)
self.expanded.connect(self._onNodeExpanded) self.expanded.connect(self._onNodeExpanded)
@@ -553,6 +559,9 @@ class GuiProjectTree(QTreeView):
def loadModel(self) -> None: def loadModel(self) -> None:
"""Load and prepare a new project model.""" """Load and prepare a new project model."""
if selectModelOld := self.selectionModel():
selectModelOld.disconnect()
self.setModel(SHARED.project.tree.model) self.setModel(SHARED.project.tree.model)
# Lock the column sizes # Lock the column sizes
@@ -568,6 +577,9 @@ class GuiProjectTree(QTreeView):
header.resizeSection(ProjectNode.C_ACTIVE, iPx + 6) header.resizeSection(ProjectNode.C_ACTIVE, iPx + 6)
header.resizeSection(ProjectNode.C_STATUS, iPx + 6) header.resizeSection(ProjectNode.C_STATUS, iPx + 6)
if selectModelNew := self.selectionModel():
selectModelNew.currentChanged.connect(self._onSelectionChange)
self.restoreExpandedState() self.restoreExpandedState()
return return
@@ -606,12 +618,12 @@ class GuiProjectTree(QTreeView):
tHandle = None tHandle = None
if itemType == nwItemType.ROOT and isinstance(itemClass, nwItemClass): if itemType == nwItemType.ROOT and isinstance(itemClass, nwItemClass):
pos = -1 sPos = -1
if (node := self._getNode(self.currentIndex())) and (itemRoot := node.item.itemRoot): if (node := self._getNode(self.currentIndex())) and (itemRoot := node.item.itemRoot):
if root := SHARED.project.tree.nodes.get(itemRoot): if root := SHARED.project.tree.nodes.get(itemRoot):
pos = root.row() + 1 sPos = root.row() + 1
tHandle = SHARED.project.newRoot(itemClass, pos) tHandle = SHARED.project.newRoot(itemClass, sPos)
self.restoreExpandedState() self.restoreExpandedState()
elif itemType in (nwItemType.FILE, nwItemType.FOLDER): elif itemType in (nwItemType.FILE, nwItemType.FOLDER):
@@ -624,52 +636,43 @@ class GuiProjectTree(QTreeView):
SHARED.error(self.tr("Cannot add new files or folders to the Trash folder.")) SHARED.error(self.tr("Cannot add new files or folders to the Trash folder."))
return return
# Collect some information about the selected item # Set default label and determine where to put the new item
sLevel = nwStyles.H_LEVEL.get(node.item.mainHeading, 0) nNote = isNote
sIsParent = node.childCount() > 0 nLevel = hLevel
# Set default label and determine if new item is to be added
# as child or sibling to the selected item
if itemType == nwItemType.FILE: if itemType == nwItemType.FILE:
if copyDoc and (cItem := SHARED.project.tree[copyDoc]): if copyDoc and (cItem := SHARED.project.tree[copyDoc]):
nNote = cItem.isNoteLayout()
nLevel = nwStyles.H_LEVEL.get(cItem.mainHeading, 0)
newLabel = cItem.itemName newLabel = cItem.itemName
asChild = sIsParent and node.item.isDocumentLayout()
elif isNote: elif isNote:
newLabel = self.tr("New Note") newLabel = self.tr("New Note")
asChild = sIsParent elif hLevel == 1:
newLabel = self.tr("New Part")
elif hLevel == 2: elif hLevel == 2:
newLabel = self.tr("New Chapter") newLabel = self.tr("New Chapter")
asChild = sIsParent and node.item.isDocumentLayout() and sLevel < 2
elif hLevel == 3: elif hLevel == 3:
newLabel = self.tr("New Scene") newLabel = self.tr("New Scene")
asChild = sIsParent and node.item.isDocumentLayout() and sLevel < 3
else: else:
newLabel = self.tr("New Document") newLabel = self.tr("New Document")
asChild = sIsParent and node.item.isDocumentLayout()
else: else:
newLabel = self.tr("New Folder") newLabel = self.tr("New Folder")
asChild = False nLevel = 0
pos = -1 sHandle, sPos = SHARED.project.tree.pickParent(node, nLevel, nNote)
sHandle = None if sHandle:
if not (asChild or node.item.isFolderType() or node.item.isRootType()): newLabel, dlgOk = GuiEditLabel.getLabel(self, text=newLabel)
pos = node.row() + 1 if dlgOk:
sHandle = node.item.itemParent # Add the file or folder
if itemType == nwItemType.FILE:
sHandle = sHandle or node.item.itemHandle if tHandle := SHARED.project.newFile(newLabel, sHandle, sPos):
newLabel, dlgOk = GuiEditLabel.getLabel(self, text=newLabel) if copyDoc:
if dlgOk: SHARED.project.copyFileContent(tHandle, copyDoc)
# Add the file or folder elif hLevel > 0:
if itemType == nwItemType.FILE: SHARED.project.writeNewFile(tHandle, hLevel, not nNote)
if tHandle := SHARED.project.newFile(newLabel, sHandle, pos): SHARED.project.index.reIndexHandle(tHandle)
if copyDoc: SHARED.project.tree.refreshItems([tHandle])
SHARED.project.copyFileContent(tHandle, copyDoc) else:
elif hLevel > 0: tHandle = SHARED.project.newFolder(newLabel, sHandle, sPos)
SHARED.project.writeNewFile(tHandle, hLevel, not isNote)
SHARED.project.index.reIndexHandle(tHandle)
SHARED.project.tree.refreshItems([tHandle])
else:
tHandle = SHARED.project.newFolder(newLabel, sHandle, pos)
# Select the new item automatically # Select the new item automatically
if tHandle: if tHandle:
@@ -969,10 +972,10 @@ class GuiProjectTree(QTreeView):
# Private Slots # Private Slots
## ##
@pyqtSlot(QModelIndex) @pyqtSlot(QModelIndex, QModelIndex)
def _onSingleClick(self, index: QModelIndex) -> None: def _onSelectionChange(self, current: QModelIndex, previous: QModelIndex) -> None:
"""The user changed which item is selected.""" """The user changed which item is selected."""
if node := self._getNode(index): if node := self._getNode(current):
self.projView.selectedItemChanged.emit(node.item.itemHandle) self.projView.selectedItemChanged.emit(node.item.itemHandle)
return return
@@ -1198,9 +1201,10 @@ class _TreeContextMenu(QMenu):
def _itemCreation(self) -> None: def _itemCreation(self) -> None:
"""Add create item actions.""" """Add create item actions."""
menu = qtAddMenu(self, self.tr("Create New ...")) menu = qtAddMenu(self, self.tr("Create New ..."))
menu.addAction(self._view.projBar.aAddEmpty)
menu.addAction(self._view.projBar.aAddChap)
menu.addAction(self._view.projBar.aAddScene) menu.addAction(self._view.projBar.aAddScene)
menu.addAction(self._view.projBar.aAddChap)
menu.addAction(self._view.projBar.aAddPart)
menu.addAction(self._view.projBar.aAddEmpty)
menu.addAction(self._view.projBar.aAddNote) menu.addAction(self._view.projBar.aAddNote)
menu.addAction(self._view.projBar.aAddFolder) menu.addAction(self._view.projBar.aAddFolder)
return return
+139
View File
@@ -211,6 +211,145 @@ def testCoreTree_ManipulateTree(mockGUI, mockItems):
] ]
@pytest.mark.core
def testCoreTree_PickParent(mockGUI, mockItems):
"""Check the parent item picker."""
project = NWProject()
tree = NWTree(project)
assert len(tree) == 0
# Case 1: Root and Folder
# =======================
hNovelRoot = tree.create("Novel", None, nwItemType.ROOT, nwItemClass.NOVEL)
nNovelRoot = tree.nodes[hNovelRoot]
# Add a folder
sHandle, sPos = tree.pickParent(nNovelRoot, 0, False)
assert sHandle == hNovelRoot
assert sPos == 0
hNovelFolder = tree.create("Folder", sHandle, nwItemType.FOLDER, pos=sPos)
assert hNovelFolder is not None
nNovelFolder = tree.nodes[hNovelFolder]
assert nNovelFolder.item.itemClass == nwItemClass.NOVEL
# Add a partition
sHandle, sPos = tree.pickParent(nNovelRoot, 1, False)
assert sHandle == hNovelRoot
assert sPos == 1
hPartOne = tree.create("Part One", sHandle, nwItemType.FILE, pos=sPos)
assert hPartOne is not None
nPartOne = tree.nodes[hPartOne]
assert nPartOne.item.itemClass == nwItemClass.NOVEL
nPartOne.item.setMainHeading("H1")
# Folders behave identical to root
sHandle, sPos = tree.pickParent(nNovelFolder, 0, False)
assert sHandle == hNovelFolder
assert sPos == 0
# Case 2: Documents of Same Level
# ===============================
# Add a chapter under Part One
hChapterOne = tree.create("Chapter One", hPartOne, nwItemType.FILE)
assert hChapterOne is not None
nChapterOne = tree.nodes[hChapterOne]
assert nChapterOne.item.itemParent == hPartOne
nChapterOne.item.setMainHeading("H2")
# Add a chapter next to Chapter One -> Sibling (default behaviour)
sHandle, sPos = tree.pickParent(nChapterOne, 2, False)
assert sHandle == hPartOne
assert sPos == 1
hChapterTwo = tree.create("Chapter Two", sHandle, nwItemType.FILE, pos=sPos)
assert hChapterTwo is not None
nChapterTwo = tree.nodes[hChapterTwo]
assert nChapterTwo.item.itemClass == nwItemClass.NOVEL
nChapterTwo.item.setMainHeading("H2")
# Add a new part next to Chapter Two -> Sibling to parent (second if condition)
sHandle, sPos = tree.pickParent(nChapterTwo, 1, False)
assert sHandle == hNovelRoot
assert sPos == 2
# Add a scene under Chapter Two
hSceneOne = tree.create("Scene One", hChapterOne, nwItemType.FILE)
assert hSceneOne is not None
nSceneOne = tree.nodes[hSceneOne]
assert nSceneOne.item.itemParent == hChapterOne
nSceneOne.item.setMainHeading("H3")
# Adding a part next to the scene should also jump a level up
sHandle, sPos = tree.pickParent(nSceneOne, 1, False)
assert sHandle == hPartOne
assert sPos == 1
# Case 3: Documents of Deeper Level
# =================================
# Add chapter directly to Part One with existing chapters -> Added as child (third if)
sHandle, sPos = tree.pickParent(nPartOne, 2, False)
assert sHandle == hPartOne
assert sPos == 2
# But adding a new part becomes a sibling
sHandle, sPos = tree.pickParent(nPartOne, 1, False)
assert sHandle == hNovelRoot
assert sPos == 2
# Add a page without a heading
hPage = tree.create("Page", hNovelRoot, nwItemType.FILE)
assert hPage is not None
nPage = tree.nodes[hPage]
assert nPage.item.itemParent == hNovelRoot
nPage.item.setMainHeading("H0")
# Add a scene below the page
hSceneTwo = tree.create("Scene Two", hPage, nwItemType.FILE)
assert hSceneTwo is not None
nSceneTwo = tree.nodes[hSceneTwo]
assert nSceneTwo.item.itemParent == hPage
nSceneTwo.item.setMainHeading("H3")
# A page without a heading should not add anything as a child
sHandle, sPos = tree.pickParent(nPage, 3, False)
assert sHandle == hNovelRoot
assert sPos == 3
# Case 4: Notes
# =============
hCharRoot = tree.create("Characters", None, nwItemType.ROOT, nwItemClass.CHARACTER)
nCharRoot = tree.nodes[hCharRoot]
# Add a note at root level
hNoteOne = tree.create("Note One", hCharRoot, nwItemType.FILE)
assert hNoteOne is not None
nNoteOne = tree.nodes[hNoteOne]
assert nNoteOne.item.itemClass == nwItemClass.CHARACTER
nNoteOne.item.setMainHeading("H1")
assert nCharRoot.childCount() == 1
# Adding a new note is a sibling
sHandle, sPos = tree.pickParent(nNoteOne, 1, True)
assert sHandle == hCharRoot
assert sPos == 1
# Add a child note to Note One
hNoteTwo = tree.create("Note Two", hNoteOne, nwItemType.FILE)
assert hNoteTwo is not None
nNoteTwo = tree.nodes[hNoteTwo]
assert nNoteTwo.item.itemClass == nwItemClass.CHARACTER
nNoteTwo.item.setMainHeading("H1")
assert nNoteOne.childCount() == 1
# Adding a new note now is a child
sHandle, sPos = tree.pickParent(nNoteOne, 1, True)
assert sHandle == hNoteOne
assert sPos == 1
@pytest.mark.core @pytest.mark.core
def testCoreTree_ItemMethods(monkeypatch, mockGUI, mockItems): def testCoreTree_ItemMethods(monkeypatch, mockGUI, mockItems):
"""Check the item methods of the tree.""" """Check the item methods of the tree."""
+52 -31
View File
@@ -134,7 +134,7 @@ def testGuiProjTree_NewTreeItem(qtbot, caplog, monkeypatch, nwGUI, projPath, moc
# Add a new file in the new folder # Add a new file in the new folder
hNewFile = "0000000000013" hNewFile = "0000000000013"
projView.setSelectedHandle(hNewFolder, doScroll=True) projView.setSelectedHandle(hNewFolder, doScroll=True)
projTree.newTreeItem(nwItemType.FILE) projTree.newTreeItem(nwItemType.FILE, hLevel=0)
assert hNewFile in tree assert hNewFile in tree
item = tree[hNewFile] item = tree[hNewFile]
assert item is not None assert item is not None
@@ -148,9 +148,28 @@ def testGuiProjTree_NewTreeItem(qtbot, caplog, monkeypatch, nwGUI, projPath, moc
"Objects", "Trash", "Objects", "Trash",
] ]
# Add a new chapter next to the other new file # Add a new partition next to the other new file
hNewChapter = "0000000000014" hNewPart = "0000000000014"
projView.setSelectedHandle(hNewFile, doScroll=True) projView.setSelectedHandle(hNewFile, doScroll=True)
projTree.newTreeItem(nwItemType.FILE, hLevel=1)
assert hNewPart in tree
item = tree[hNewPart]
assert item is not None
assert item.itemName == "New Part"
assert item.itemParent == hNewFolder
assert item.itemRoot == C.hNovelRoot
assert item.itemClass == nwItemClass.NOVEL
assert nwGUI.openDocument(hNewPart)
assert nwGUI.docEditor.getText() == "# New Part\n\n"
assert [n.item.itemName for n in tree.model.root.allChildren()] == [
"Novel", "Title Page", "New Folder", "New Chapter", "New Scene",
"New Folder", "New Document", "New Part", "Plot", "Characters",
"Locations", "Objects", "Trash",
]
# Add a new chapter next to the other new file
hNewChapter = "0000000000015"
projView.setSelectedHandle(hNewPart, doScroll=True)
projTree.newTreeItem(nwItemType.FILE, hLevel=2) projTree.newTreeItem(nwItemType.FILE, hLevel=2)
assert hNewChapter in tree assert hNewChapter in tree
item = tree[hNewChapter] item = tree[hNewChapter]
@@ -163,12 +182,12 @@ def testGuiProjTree_NewTreeItem(qtbot, caplog, monkeypatch, nwGUI, projPath, moc
assert nwGUI.docEditor.getText() == "## New Chapter\n\n" assert nwGUI.docEditor.getText() == "## New Chapter\n\n"
assert [n.item.itemName for n in tree.model.root.allChildren()] == [ assert [n.item.itemName for n in tree.model.root.allChildren()] == [
"Novel", "Title Page", "New Folder", "New Chapter", "New Scene", "Novel", "Title Page", "New Folder", "New Chapter", "New Scene",
"New Folder", "New Document", "New Chapter", "Plot", "Characters", "New Folder", "New Document", "New Part", "New Chapter", "Plot",
"Locations", "Objects", "Trash", "Characters", "Locations", "Objects", "Trash",
] ]
# Add a new scene next to the other new file # Add a new scene next to the other new file
hNewScene = "0000000000015" hNewScene = "0000000000016"
projView.setSelectedHandle(hNewChapter, doScroll=True) projView.setSelectedHandle(hNewChapter, doScroll=True)
projTree.newTreeItem(nwItemType.FILE, hLevel=3) projTree.newTreeItem(nwItemType.FILE, hLevel=3)
assert hNewScene in tree assert hNewScene in tree
@@ -182,8 +201,8 @@ def testGuiProjTree_NewTreeItem(qtbot, caplog, monkeypatch, nwGUI, projPath, moc
assert nwGUI.docEditor.getText() == "### New Scene\n\n" assert nwGUI.docEditor.getText() == "### New Scene\n\n"
assert [n.item.itemName for n in tree.model.root.allChildren()] == [ assert [n.item.itemName for n in tree.model.root.allChildren()] == [
"Novel", "Title Page", "New Folder", "New Chapter", "New Scene", "Novel", "Title Page", "New Folder", "New Chapter", "New Scene",
"New Folder", "New Document", "New Chapter", "New Scene", "Plot", "New Folder", "New Document", "New Part", "New Chapter", "New Scene",
"Characters", "Locations", "Objects", "Trash", "Plot", "Characters", "Locations", "Objects", "Trash",
] ]
# Add a new scene with the content copied from the previous # Add a new scene with the content copied from the previous
@@ -191,7 +210,7 @@ def testGuiProjTree_NewTreeItem(qtbot, caplog, monkeypatch, nwGUI, projPath, moc
nwGUI.docEditor.setPlainText("### New Scene\n\nWith Stuff\n\n") nwGUI.docEditor.setPlainText("### New Scene\n\nWith Stuff\n\n")
nwGUI.saveDocument() nwGUI.saveDocument()
hNewSceneCopy = "0000000000016" hNewSceneCopy = "0000000000017"
projView.setSelectedHandle(hNewScene, doScroll=True) projView.setSelectedHandle(hNewScene, doScroll=True)
projTree.newTreeItem(nwItemType.FILE, copyDoc=hNewScene) projTree.newTreeItem(nwItemType.FILE, copyDoc=hNewScene)
assert hNewSceneCopy in tree assert hNewSceneCopy in tree
@@ -205,12 +224,12 @@ def testGuiProjTree_NewTreeItem(qtbot, caplog, monkeypatch, nwGUI, projPath, moc
assert nwGUI.docEditor.getText() == "### New Scene\n\nWith Stuff\n\n" assert nwGUI.docEditor.getText() == "### New Scene\n\nWith Stuff\n\n"
assert [n.item.itemName for n in tree.model.root.allChildren()] == [ assert [n.item.itemName for n in tree.model.root.allChildren()] == [
"Novel", "Title Page", "New Folder", "New Chapter", "New Scene", "Novel", "Title Page", "New Folder", "New Chapter", "New Scene",
"New Folder", "New Document", "New Chapter", "New Scene", "New Scene", "New Folder", "New Document", "New Part", "New Chapter", "New Scene",
"Plot", "Characters", "Locations", "Objects", "Trash", "New Scene", "Plot", "Characters", "Locations", "Objects", "Trash",
] ]
# Add a new file to the characters folder # Add a new file to the characters folder
hNewCharacter = "0000000000017" hNewCharacter = "0000000000018"
projView.setSelectedHandle(C.hCharRoot, doScroll=True) projView.setSelectedHandle(C.hCharRoot, doScroll=True)
projTree.newTreeItem(nwItemType.FILE, hLevel=1, isNote=True) projTree.newTreeItem(nwItemType.FILE, hLevel=1, isNote=True)
assert hNewCharacter in tree assert hNewCharacter in tree
@@ -224,8 +243,9 @@ def testGuiProjTree_NewTreeItem(qtbot, caplog, monkeypatch, nwGUI, projPath, moc
assert nwGUI.docEditor.getText() == "# New Note\n\n" assert nwGUI.docEditor.getText() == "# New Note\n\n"
assert [n.item.itemName for n in tree.model.root.allChildren()] == [ assert [n.item.itemName for n in tree.model.root.allChildren()] == [
"Novel", "Title Page", "New Folder", "New Chapter", "New Scene", "Novel", "Title Page", "New Folder", "New Chapter", "New Scene",
"New Folder", "New Document", "New Chapter", "New Scene", "New Scene", "New Folder", "New Document", "New Part", "New Chapter", "New Scene",
"Plot", "Characters", "New Note", "Locations", "Objects", "Trash", "New Scene", "Plot", "Characters", "New Note", "Locations",
"Objects", "Trash",
] ]
# Cancel during creation # Cancel during creation
@@ -235,15 +255,16 @@ def testGuiProjTree_NewTreeItem(qtbot, caplog, monkeypatch, nwGUI, projPath, moc
projTree.newTreeItem(nwItemType.FILE) projTree.newTreeItem(nwItemType.FILE)
assert [n.item.itemName for n in tree.model.root.allChildren()] == [ assert [n.item.itemName for n in tree.model.root.allChildren()] == [
"Novel", "Title Page", "New Folder", "New Chapter", "New Scene", "Novel", "Title Page", "New Folder", "New Chapter", "New Scene",
"New Folder", "New Document", "New Chapter", "New Scene", "New Scene", "New Folder", "New Document", "New Part", "New Chapter", "New Scene",
"Plot", "Characters", "New Note", "Locations", "Objects", "Trash", "New Scene", "Plot", "Characters", "New Note", "Locations",
"Objects", "Trash",
] ]
# From Template # From Template
# ============= # =============
# Create template folder # Create template folder
hTemplateRoot = "0000000000018" hTemplateRoot = "0000000000019"
projView.setSelectedHandle(hObjectRoot) projView.setSelectedHandle(hObjectRoot)
projTree.newTreeItem(nwItemType.ROOT, nwItemClass.TEMPLATE) projTree.newTreeItem(nwItemType.ROOT, nwItemClass.TEMPLATE)
assert hTemplateRoot in tree assert hTemplateRoot in tree
@@ -255,13 +276,13 @@ def testGuiProjTree_NewTreeItem(qtbot, caplog, monkeypatch, nwGUI, projPath, moc
assert item.itemClass == nwItemClass.TEMPLATE assert item.itemClass == nwItemClass.TEMPLATE
assert [n.item.itemName for n in tree.model.root.allChildren()] == [ assert [n.item.itemName for n in tree.model.root.allChildren()] == [
"Novel", "Title Page", "New Folder", "New Chapter", "New Scene", "Novel", "Title Page", "New Folder", "New Chapter", "New Scene",
"New Folder", "New Document", "New Chapter", "New Scene", "New Scene", "New Folder", "New Document", "New Part", "New Chapter", "New Scene",
"Plot", "Characters", "New Note", "Locations", "Objects", "Templates", "New Scene", "Plot", "Characters", "New Note", "Locations",
"Trash", "Objects", "Templates", "Trash",
] ]
# Create scene template # Create scene template
hSceneTemplate = "0000000000019" hSceneTemplate = "000000000001a"
projView.setSelectedHandle(hTemplateRoot, doScroll=True) projView.setSelectedHandle(hTemplateRoot, doScroll=True)
projTree.newTreeItem(nwItemType.FILE, hLevel=3) projTree.newTreeItem(nwItemType.FILE, hLevel=3)
assert hSceneTemplate in tree assert hSceneTemplate in tree
@@ -278,13 +299,13 @@ def testGuiProjTree_NewTreeItem(qtbot, caplog, monkeypatch, nwGUI, projPath, moc
nwGUI.saveDocument() nwGUI.saveDocument()
assert [n.item.itemName for n in tree.model.root.allChildren()] == [ assert [n.item.itemName for n in tree.model.root.allChildren()] == [
"Novel", "Title Page", "New Folder", "New Chapter", "New Scene", "Novel", "Title Page", "New Folder", "New Chapter", "New Scene",
"New Folder", "New Document", "New Chapter", "New Scene", "New Scene", "New Folder", "New Document", "New Part", "New Chapter", "New Scene",
"Plot", "Characters", "New Note", "Locations", "Objects", "Templates", "New Scene", "Plot", "Characters", "New Note", "Locations",
"New Scene Template", "Trash", "Objects", "Templates", "New Scene Template", "Trash",
] ]
# Create from template # Create from template
hNewFromTemplate = "000000000001a" hNewFromTemplate = "000000000001b"
projView.setSelectedHandle(hNewSceneCopy, doScroll=True) projView.setSelectedHandle(hNewSceneCopy, doScroll=True)
projView.createFileFromTemplate(hSceneTemplate) projView.createFileFromTemplate(hSceneTemplate)
assert hNewFromTemplate in tree assert hNewFromTemplate in tree
@@ -298,9 +319,9 @@ def testGuiProjTree_NewTreeItem(qtbot, caplog, monkeypatch, nwGUI, projPath, moc
assert nwGUI.docEditor.getText() == "### New Scene Template\n\nWith Stuff\n\n" assert nwGUI.docEditor.getText() == "### New Scene Template\n\nWith Stuff\n\n"
assert [n.item.itemName for n in tree.model.root.allChildren()] == [ assert [n.item.itemName for n in tree.model.root.allChildren()] == [
"Novel", "Title Page", "New Folder", "New Chapter", "New Scene", "Novel", "Title Page", "New Folder", "New Chapter", "New Scene",
"New Folder", "New Document", "New Chapter", "New Scene", "New Scene", "New Folder", "New Document", "New Part", "New Chapter", "New Scene",
"New Scene Template", "Plot", "Characters", "New Note", "Locations", "New Scene", "New Scene Template", "Plot", "Characters", "New Note",
"Objects", "Templates", "New Scene Template", "Trash", "Locations", "Objects", "Templates", "New Scene Template", "Trash",
] ]
# Rename Item # Rename Item
@@ -489,7 +510,7 @@ def testGuiProjTree_MouseClicks(qtbot, monkeypatch, nwGUI, projPath, mockRnd):
# Single click emits a signal # Single click emits a signal
with qtbot.waitSignal(projView.selectedItemChanged) as signal: with qtbot.waitSignal(projView.selectedItemChanged) as signal:
projTree._onSingleClick(model.indexFromHandle(C.hNovelRoot)) projTree._onSelectionChange(model.indexFromHandle(C.hNovelRoot), QModelIndex())
assert signal.args[0] == C.hNovelRoot assert signal.args[0] == C.hNovelRoot
# Double click on folder expands/collapses it # Double click on folder expands/collapses it
@@ -1065,7 +1086,7 @@ def testGuiProjTree_ContextMenu(qtbot, monkeypatch, nwGUI, projPath, mockRnd):
# Handles for new objects # Handles for new objects
hCharNote = "0000000000011" hCharNote = "0000000000011"
hNovelNote = "0000000000012" hNovelNote = "0000000000012"
hTrashDoc = "0000000000013" hTrashDoc = "0000000000013"
hSubNote = "0000000000014" hSubNote = "0000000000014"
hNewFolderOne = "0000000000015" hNewFolderOne = "0000000000015"
hNewFolderTwo = "0000000000017" hNewFolderTwo = "0000000000017"
@@ -1090,7 +1111,7 @@ def testGuiProjTree_ContextMenu(qtbot, monkeypatch, nwGUI, projPath, mockRnd):
assert [n.item.itemName for n in tree.model.root.allChildren()] == [ assert [n.item.itemName for n in tree.model.root.allChildren()] == [
"Novel", "Title Page", "New Folder", "New Chapter", "New Scene", "Novel", "Title Page", "New Folder", "New Chapter", "New Scene",
"New Note", "SubNote", "Plot", "Characters", "New Note", "Locations", "New Note", "SubNote", "Plot", "Characters", "New Note", "Locations",
"Trash", "New Document", "Trash", "New Part",
] ]
# Pop the menu in various positions and check for success # Pop the menu in various positions and check for success