Update the Project Tree with major improvements (#1079)

This commit is contained in:
Veronica Berglyd Olsen
2022-06-11 14:53:45 +02:00
committed by GitHub
21 changed files with 902 additions and 603 deletions
+1 -2
View File
@@ -48,7 +48,7 @@ The main shorcuts are as follows:
":kbd:`Ctrl`:kbd:`H`", "Open the search and replace bar and search for the selected word, if any is selected. (On Mac, this is :kbd:`Cmd`:kbd:`=`.)"
":kbd:`Ctrl`:kbd:`I`", "Format selected text, or word under cursor, with emphasis (italic)."
":kbd:`Ctrl`:kbd:`K`", "Activate the insert commands. The commands are listed in :ref:`a_kb_ins`."
":kbd:`Ctrl`:kbd:`N`", "Create new document."
":kbd:`Ctrl`:kbd:`N`", "Create new project item."
":kbd:`Ctrl`:kbd:`O`", "Open selected document."
":kbd:`Ctrl`:kbd:`Q`", "Exit novelWriter."
":kbd:`Ctrl`:kbd:`R`", "If in the project tree, open a document for viewing. If the editor has focus, open current document for viewing."
@@ -72,7 +72,6 @@ The main shorcuts are as follows:
":kbd:`Ctrl`:kbd:`Shift`:kbd:`A`", "Select all text in current paragraph."
":kbd:`Ctrl`:kbd:`Shift`:kbd:`G`", "Find previous occurrence of search word in current document."
":kbd:`Ctrl`:kbd:`Shift`:kbd:`I`", "Import text to the current document from a text file."
":kbd:`Ctrl`:kbd:`Shift`:kbd:`N`", "Create new folder."
":kbd:`Ctrl`:kbd:`Shift`:kbd:`O`", "Open a project."
":kbd:`Ctrl`:kbd:`Shift`:kbd:`R`", "Close the document viewer."
":kbd:`Ctrl`:kbd:`Shift`:kbd:`S`", "Save the current project."
-5
View File
@@ -502,11 +502,6 @@ class NWIndex:
"""
return self._itemIndex.mainItemHeader(tHandle)
def getHandleHeaderIntLevel(self, tHandle):
"""Get the integer header level of the first header of a handle.
"""
return H_LEVEL.get(self._itemIndex.mainItemHeader(tHandle), 0)
def getTableOfContents(self, maxDepth, skipExcl=True):
"""Generate a table of contents up to a maximum depth.
"""
+31 -1
View File
@@ -45,7 +45,7 @@ from novelwriter.enum import nwItemType, nwItemClass, nwItemLayout, nwAlert
from novelwriter.error import logException
from novelwriter.common import (
checkString, checkBool, checkInt, isHandle, formatTimeStamp,
makeFileNameSafe, hexToInt, simplified
makeFileNameSafe, hexToInt, minmax, simplified
)
from novelwriter.constants import trConst, nwFiles, nwLabels
@@ -154,6 +154,8 @@ class NWProject():
def newFolder(self, label, pHandle):
"""Add a new folder with a given label and parent item.
"""
if pHandle not in self._projTree:
return None
newItem = NWItem(self)
newItem.setName(label)
newItem.setType(nwItemType.FOLDER)
@@ -164,6 +166,8 @@ class NWProject():
def newFile(self, label, pHandle):
"""Add a new file with a given label and parent item.
"""
if pHandle not in self._projTree:
return None
newItem = NWItem(self)
newItem.setName(label)
newItem.setType(nwItemType.FILE)
@@ -171,6 +175,32 @@ class NWProject():
self._projTree.updateItemData(newItem.itemHandle)
return newItem.itemHandle
def writeNewFile(self, tHandle, hLevel, isDocument):
"""Write content to a new document after it is created. This
will not run if the file exists and is not empty.
"""
tItem = self._projTree[tHandle]
if tItem is None:
return False
if tItem.itemType != nwItemType.FILE:
return False
newDoc = NWDoc(self, tHandle)
if newDoc.readDocument().strip():
return False
hshText = "#"*minmax(hLevel, 1, 4)
newText = f"{hshText} {tItem.itemName}\n\n"
if tItem.isNovelLike() and isDocument:
tItem.setLayout(nwItemLayout.DOCUMENT)
else:
tItem.setLayout(nwItemLayout.NOTE)
newDoc.writeDocument(newText)
self._projIndex.scanText(tHandle, newText)
return True
def trashFolder(self):
"""Add the special trash root folder to the project.
"""
+1 -1
View File
@@ -107,7 +107,7 @@ class GuiPreferences(PagedDialog):
), nwAlert.INFO)
if refreshTree:
self.theParent.treeView.buildTree()
self.theParent.treeView.populateTree()
self._saveWindowSize()
self.accept()
+2 -2
View File
@@ -25,7 +25,7 @@ from novelwriter.gui.itemdetails import GuiItemDetails
from novelwriter.gui.mainmenu import GuiMainMenu
from novelwriter.gui.noveltree import GuiNovelTree
from novelwriter.gui.outline import GuiOutline
from novelwriter.gui.projtree import GuiProjectTree
from novelwriter.gui.projtree import GuiProjectView
from novelwriter.gui.statusbar import GuiMainStatus
from novelwriter.gui.theme import GuiTheme
from novelwriter.gui.viewsbar import GuiViewsBar
@@ -39,7 +39,7 @@ __all__ = [
"GuiMainStatus",
"GuiNovelTree",
"GuiOutline",
"GuiProjectTree",
"GuiProjectView",
"GuiTheme",
"GuiViewsBar",
]
+1 -1
View File
@@ -296,7 +296,7 @@ class GuiItemDetails(QWidget):
return
@pyqtSlot(str, int, int, int)
def doUpdateCounts(self, tHandle, cC, wC, pC):
def updateCounts(self, tHandle, cC, wC, pC):
"""Update the counts if the handle is the same as the one we're
already showing. Otherwise, do nothing.
"""
+1 -54
View File
@@ -33,7 +33,7 @@ from PyQt5.QtCore import QUrl
from PyQt5.QtGui import QDesktopServices
from PyQt5.QtWidgets import QMenuBar, QAction
from novelwriter.enum import nwItemType, nwItemClass, nwDocAction, nwDocInsert, nwWidget
from novelwriter.enum import nwDocAction, nwDocInsert, nwWidget
from novelwriter.constants import trConst, nwKeyWords, nwLabels, nwUnicode
logger = logging.getLogger(__name__)
@@ -62,8 +62,6 @@ class GuiMainMenu(QMenuBar):
# Function Pointers
self._docAction = self.theParent.passDocumentAction
self._moveTreeItem = self.theParent.treeView.moveTreeItem
self._newTreeItem = self.theParent.treeView.newTreeItem
self._docInsert = self.theParent.docEditor.insertText
self._insertKeyWord = self.theParent.docEditor.insertKeyWord
@@ -164,33 +162,6 @@ class GuiMainMenu(QMenuBar):
# Project > Separator
self.projMenu.addSeparator()
# Project > New Root
self.rootMenu = self.projMenu.addMenu(self.tr("Create Root Folder"))
self.rootItems = {}
self.rootItems[nwItemClass.NOVEL] = QAction(self.tr("Novel Root"), self.rootMenu)
self.rootItems[nwItemClass.PLOT] = QAction(self.tr("Plot Root"), self.rootMenu)
self.rootItems[nwItemClass.CHARACTER] = QAction(self.tr("Character Root"), self.rootMenu)
self.rootItems[nwItemClass.WORLD] = QAction(self.tr("Location Root"), self.rootMenu)
self.rootItems[nwItemClass.TIMELINE] = QAction(self.tr("Timeline Root"), self.rootMenu)
self.rootItems[nwItemClass.OBJECT] = QAction(self.tr("Object Root"), self.rootMenu)
self.rootItems[nwItemClass.ENTITY] = QAction(self.tr("Entity Root"), self.rootMenu)
self.rootItems[nwItemClass.CUSTOM] = QAction(self.tr("Custom Root"), self.rootMenu)
self.rootItems[nwItemClass.ARCHIVE] = QAction(self.tr("Archive Root"), self.rootMenu)
for n, itemClass in enumerate(self.rootItems.keys()):
self.rootItems[itemClass].triggered.connect(
lambda n, itemClass=itemClass: self._newTreeItem(nwItemType.ROOT, itemClass)
)
self.rootMenu.addAction(self.rootItems[itemClass])
# Project > New Folder
self.aCreateFolder = QAction(self.tr("Create Folder"), self)
self.aCreateFolder.setShortcut("Ctrl+Shift+N")
self.aCreateFolder.triggered.connect(lambda: self._newTreeItem(nwItemType.FOLDER))
self.projMenu.addAction(self.aCreateFolder)
# Project > Separator
self.projMenu.addSeparator()
# Project > Edit
self.aEditItem = QAction(self.tr("Edit Item"), self)
self.aEditItem.setShortcuts(["Ctrl+E", "F2"])
@@ -203,24 +174,6 @@ class GuiMainMenu(QMenuBar):
self.aDeleteItem.triggered.connect(lambda: self.theParent.treeView.deleteItem(None))
self.projMenu.addAction(self.aDeleteItem)
# Project > Move Up
self.aMoveUp = QAction(self.tr("Move Item Up"), self)
self.aMoveUp.setShortcut("Ctrl+Up")
self.aMoveUp.triggered.connect(lambda: self._moveTreeItem(-1))
self.projMenu.addAction(self.aMoveUp)
# Project > Move Down
self.aMoveDown = QAction(self.tr("Move Item Down"), self)
self.aMoveDown.setShortcut("Ctrl+Down")
self.aMoveDown.triggered.connect(lambda: self._moveTreeItem(1))
self.projMenu.addAction(self.aMoveDown)
# Project > Undo Last Action
self.aMoveUndo = QAction(self.tr("Undo Last Move"), self)
self.aMoveUndo.setShortcut("Ctrl+Shift+Z")
self.aMoveUndo.triggered.connect(lambda: self.theParent.treeView.undoLastMove())
self.projMenu.addAction(self.aMoveUndo)
# Project > Empty Trash
self.aEmptyTrash = QAction(self.tr("Empty Trash"), self)
self.aEmptyTrash.triggered.connect(lambda: self.theParent.treeView.emptyTrash())
@@ -244,12 +197,6 @@ class GuiMainMenu(QMenuBar):
# Document
self.docuMenu = self.addMenu(self.tr("&Document"))
# Document > New
self.aNewDoc = QAction(self.tr("New Document"), self)
self.aNewDoc.setShortcut("Ctrl+N")
self.aNewDoc.triggered.connect(lambda: self._newTreeItem(nwItemType.FILE))
self.docuMenu.addAction(self.aNewDoc)
# Document > Open
self.aOpenDoc = QAction(self.tr("Open Document"), self)
self.aOpenDoc.setShortcut("Ctrl+O")
+3
View File
@@ -62,6 +62,7 @@ class GuiOutline(QWidget):
self.mainConf = novelwriter.CONFIG
self.theParent = theParent
# Build GUI
self.outlineBar = GuiOutlineToolBar(self)
self.outlineView = GuiOutlineView(self)
self.outlineData = GuiOutlineDetails(self)
@@ -225,6 +226,8 @@ class GuiOutlineToolBar(QToolBar):
logger.debug("GuiOutlineToolBar initialisation complete")
return
##
# Methods
##
File diff suppressed because it is too large Load Diff
+43 -57
View File
@@ -40,7 +40,7 @@ from PyQt5.QtWidgets import (
from novelwriter.gui import (
GuiDocEditor, GuiDocViewDetails, GuiDocViewer, GuiItemDetails, GuiMainMenu,
GuiMainStatus, GuiNovelTree, GuiOutline, GuiProjectTree, GuiTheme,
GuiMainStatus, GuiNovelTree, GuiOutline, GuiProjectView, GuiTheme,
GuiViewsBar
)
from novelwriter.dialogs import (
@@ -105,7 +105,7 @@ class GuiMain(QMainWindow):
# Main GUI Elements
self.statusBar = GuiMainStatus(self)
self.treeView = GuiProjectTree(self)
self.treeView = GuiProjectView(self)
self.novelView = GuiNovelTree(self)
self.docEditor = GuiDocEditor(self)
self.viewMeta = GuiDocViewDetails(self)
@@ -115,28 +115,6 @@ class GuiMain(QMainWindow):
self.mainMenu = GuiMainMenu(self)
self.viewsBar = GuiViewsBar(self)
# Connect Signals Between Main Elements
self.viewsBar.viewChangeRequested.connect(self._changeView)
self.treeView.itemSelectionChanged.connect(self._treeSingleClick)
self.treeView.itemDoubleClicked.connect(self._treeDoubleClick)
self.treeView.novelItemChanged.connect(self._treeNovelItemChanged)
self.treeView.wordCountsChanged.connect(self._updateStatusWordCount)
self.treeView.treeItemChanged.connect(self.docEditor.updateDocInfo)
self.treeView.treeItemChanged.connect(self.docViewer.updateDocInfo)
self.treeView.treeItemChanged.connect(self.treeMeta.updateViewBox)
self.treeView.rootFolderChanged.connect(self.projView.updateRootItem)
self.docEditor.spellDictionaryChanged.connect(self.statusBar.setLanguage)
self.docEditor.docEditedStatusChanged.connect(self.statusBar.doUpdateDocumentStatus)
self.docEditor.docCountsChanged.connect(self.treeMeta.doUpdateCounts)
self.docEditor.docCountsChanged.connect(self.treeView.doUpdateCounts)
self.docEditor.loadDocumentTagRequest.connect(self._followTag)
self.docViewer.loadDocumentTagRequest.connect(self._followTag)
self.projView.loadDocumentTagRequest.connect(self._followTag)
# Project Tree Stack
self.projStack = QStackedWidget()
self.projStack.addWidget(self.treeView)
@@ -214,6 +192,30 @@ class GuiMain(QMainWindow):
self.setStatusBar(self.statusBar)
self.addToolBar(Qt.LeftToolBarArea, self.viewsBar)
# Connect Signals
# ===============
self.viewsBar.viewChangeRequested.connect(self._changeView)
self.treeView.selectedItemChanged.connect(self.treeMeta.updateViewBox)
self.treeView.openDocumentRequest.connect(self._openDocument)
self.treeView.novelItemChanged.connect(self._treeNovelItemChanged)
self.treeView.wordCountsChanged.connect(self._updateStatusWordCount)
self.treeView.treeItemChanged.connect(self.docEditor.updateDocInfo)
self.treeView.treeItemChanged.connect(self.docViewer.updateDocInfo)
self.treeView.treeItemChanged.connect(self.treeMeta.updateViewBox)
self.treeView.rootFolderChanged.connect(self.projView.updateRootItem)
self.docEditor.spellDictionaryChanged.connect(self.statusBar.setLanguage)
self.docEditor.docEditedStatusChanged.connect(self.statusBar.doUpdateDocumentStatus)
self.docEditor.docCountsChanged.connect(self.treeMeta.updateCounts)
self.docEditor.docCountsChanged.connect(self.treeView.updateCounts)
self.docEditor.loadDocumentTagRequest.connect(self._followTag)
self.docViewer.loadDocumentTagRequest.connect(self._followTag)
self.projView.loadDocumentTagRequest.connect(self._followTag)
# Finalise Initialisation
# =======================
@@ -289,7 +291,7 @@ class GuiMain(QMainWindow):
"""Wrapper function to clear all sub-elements of the main GUI.
"""
# Project Area
self.treeView.clearTree()
self.treeView.clearProject()
self.novelView.clearTree()
self.treeMeta.clearDetails()
@@ -539,7 +541,7 @@ class GuiMain(QMainWindow):
logger.error("No project open")
return False
self.treeView.saveTreeOrder()
self.treeView.saveProjectTree()
if self.theProject.saveProject(autoSave=autoSave):
self.theProject.index.saveIndex()
@@ -787,7 +789,7 @@ class GuiMain(QMainWindow):
tHandle = None
tLine = None
if self.treeView.hasFocus():
if self.treeView.treeFocus():
tHandle = self.treeView.getSelectedHandle()
elif self.novelView.hasFocus():
tHandle, tLine = self.novelView.getSelectedHandle()
@@ -822,7 +824,7 @@ class GuiMain(QMainWindow):
def rebuildTrees(self):
"""Rebuild the project tree.
"""
self.treeView.buildTree()
self.treeView.populateTree()
self.novelView.refreshTree()
return
@@ -845,7 +847,7 @@ class GuiMain(QMainWindow):
qApp.setOverrideCursor(QCursor(Qt.WaitCursor))
tStart = time()
self.treeView.saveTreeOrder()
self.treeView.saveProjectTree()
self.theProject.index.clearIndex()
for tItem in self.theProject.tree:
@@ -917,7 +919,7 @@ class GuiMain(QMainWindow):
self.saveDocument()
self.docEditor.initEditor()
self.docViewer.initViewer()
self.treeView.initTree()
self.treeView.initSettings()
self.novelView.initTree()
self.projView.initOutline()
self._updateStatusWordCount()
@@ -1159,7 +1161,6 @@ class GuiMain(QMainWindow):
self.mainConf.setViewPanePos(self.splitView.sizes())
self.mainConf.setShowRefPanel(self.viewMeta.isVisible())
self.mainConf.setTreeColWidths(self.treeView.getColumnSizes())
self.mainConf.setNovelColWidths(self.novelView.getColumnSizes())
if not self.mainConf.isFullScreen:
self.mainConf.setWinSize(self.width(), self.height())
@@ -1468,6 +1469,17 @@ class GuiMain(QMainWindow):
self.viewDocument(tHandle=tHandle, tAnchor=f"#{sTitle}")
return
@pyqtSlot(str, Enum)
def _openDocument(self, tHandle, tMode):
"""Handle an open document request.
"""
if tHandle is not None:
if tMode == nwDocMode.EDIT:
self.openDocument(tHandle, changeFocus=False)
elif tMode == nwDocMode.VIEW:
self.viewDocument(tHandle=tHandle)
return
@pyqtSlot(nwView)
def _changeView(self, view):
"""Handle the requested change of view from the GuiViewBar.
@@ -1531,32 +1543,6 @@ class GuiMain(QMainWindow):
return
@pyqtSlot()
def _treeSingleClick(self):
"""Single click on a project tree item just updates the details
panel below the tree.
"""
tHandle = self.treeView.getSelectedHandle()
if tHandle is not None:
self.treeMeta.updateViewBox(tHandle)
return
@pyqtSlot("QTreeWidgetItem*", int)
def _treeDoubleClick(self, tItem, colNo):
"""The user double-clicked an item in the tree. If it is a file,
we open it. Otherwise, we toggle the expanded status.
"""
tHandle = self.treeView.getSelectedHandle()
if tHandle is not None:
tItem = self.theProject.tree[tHandle]
if tItem is None:
return
if tItem.itemType == nwItemType.FILE:
self.openDocument(tHandle, changeFocus=False, doScroll=False)
else:
self.treeView.toggleExpanded(tHandle)
return
@pyqtSlot()
def _treeNovelItemChanged(self):
"""Triggered when there is a change to a novel item in the
+13 -13
View File
@@ -1,13 +1,13 @@
<?xml version='1.0' encoding='utf-8'?>
<novelWriterXML appVersion="1.7-beta1" hexVersion="0x010700b1" fileVersion="1.4" timeStamp="2022-06-05 15:03:00">
<novelWriterXML appVersion="1.7-beta1" hexVersion="0x010700b1" fileVersion="1.4" timeStamp="2022-06-11 13:36:01">
<project>
<name>Sample Project</name>
<title>Sample Project</title>
<author>Jane Smith</author>
<author>Jay Doh</author>
<saveCount>1334</saveCount>
<autoCount>225</autoCount>
<editTime>67746</editTime>
<saveCount>1345</saveCount>
<autoCount>229</autoCount>
<editTime>68286</editTime>
</project>
<settings>
<doBackup>False</doBackup>
@@ -15,7 +15,7 @@
<spellCheck>True</spellCheck>
<spellLang>None</spellLang>
<autoOutline>True</autoOutline>
<lastEdited>a520879ca0b45</lastEdited>
<lastEdited>636b6aa9b697b</lastEdited>
<lastViewed>636b6aa9b697b</lastViewed>
<lastWordCount>1363</lastWordCount>
<novelWordCount>954</novelWordCount>
@@ -33,13 +33,13 @@
<section></section>
</titleFormat>
<status>
<entry key="sf12341" count="7" red="100" green="100" blue="100">New</entry>
<entry key="sf24ce6" count="1" red="200" green="50" blue="0">Notes</entry>
<entry key="sf12341" count="5" red="100" green="100" blue="100">New</entry>
<entry key="sf24ce6" count="2" red="200" green="50" blue="0">Notes</entry>
<entry key="sc24b8f" count="2" red="182" green="60" blue="0">Started</entry>
<entry key="s90e6c9" count="6" red="193" green="129" blue="0">1st Draft</entry>
<entry key="sd51c5b" count="1" red="193" green="129" blue="0">2nd Draft</entry>
<entry key="s90e6c9" count="7" red="193" green="129" blue="0">1st Draft</entry>
<entry key="sd51c5b" count="0" red="193" green="129" blue="0">2nd Draft</entry>
<entry key="s8ae72a" count="0" red="193" green="129" blue="0">3rd Draft</entry>
<entry key="s78ea90" count="0" red="58" green="180" blue="58">Finished</entry>
<entry key="s78ea90" count="1" red="58" green="180" blue="58">Finished</entry>
</status>
<importance>
<entry key="ia857f0" count="5" red="100" green="100" blue="100">None</entry>
@@ -63,7 +63,7 @@
</item>
<item handle="edca4be2fcaf8" parent="7031beac91f75" root="7031beac91f75" order="2" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="False" charCount="26" wordCount="6" paraCount="1" cursorPos="36"/>
<name status="sf12341" import="ia857f0" exported="True">Part One</name>
<name status="s90e6c9" import="ia857f0" exported="True">Part One</name>
</item>
<item handle="6a2d6d5f4f401" parent="7031beac91f75" root="7031beac91f75" order="3" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="True" charCount="95" wordCount="18" paraCount="1" cursorPos="291"/>
@@ -79,11 +79,11 @@
</item>
<item handle="ba8a28a246524" parent="7031beac91f75" root="7031beac91f75" order="4" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="False" charCount="617" wordCount="101" paraCount="3" cursorPos="310"/>
<name status="sf12341" import="ia857f0" exported="True">Interlude</name>
<name status="s78ea90" import="ia857f0" exported="True">Interlude</name>
</item>
<item handle="96b68994dfa3d" parent="7031beac91f75" root="7031beac91f75" order="5" type="FILE" class="NOVEL" layout="NOTE">
<meta expanded="False" charCount="1909" wordCount="346" paraCount="7" cursorPos="0"/>
<name status="sd51c5b" import="ia857f0" exported="False">A Note on Structure</name>
<name status="sf24ce6" import="ia857f0" exported="False">A Note on Structure</name>
</item>
<item handle="88706ddc78b1b" parent="7031beac91f75" root="7031beac91f75" order="6" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="True" charCount="139" wordCount="28" paraCount="1" cursorPos="343"/>
@@ -1,5 +1,5 @@
<?xml version='1.0' encoding='utf-8'?>
<novelWriterXML appVersion="1.7-beta1" hexVersion="0x010700b1" fileVersion="1.4" timeStamp="2022-05-21 17:08:21">
<novelWriterXML appVersion="1.7-beta1" hexVersion="0x010700b1" fileVersion="1.4" timeStamp="2022-06-06 21:17:27">
<project>
<name>New Project</name>
<title>New Novel</title>
@@ -16,9 +16,9 @@
<autoOutline>True</autoOutline>
<lastEdited>None</lastEdited>
<lastViewed>None</lastViewed>
<lastWordCount>0</lastWordCount>
<novelWordCount>0</novelWordCount>
<notesWordCount>0</notesWordCount>
<lastWordCount>2</lastWordCount>
<novelWordCount>1</novelWordCount>
<notesWordCount>1</notesWordCount>
<autoReplace/>
<titleFormat>
<title>%title%</title>
@@ -28,19 +28,19 @@
<section></section>
</titleFormat>
<status>
<entry key="s000008" count="5" red="100" green="100" blue="100">New</entry>
<entry key="s000008" count="7" red="100" green="100" blue="100">New</entry>
<entry key="s000009" count="0" red="200" green="50" blue="0">Note</entry>
<entry key="s00000a" count="0" red="200" green="150" blue="0">Draft</entry>
<entry key="s00000b" count="0" red="50" green="200" blue="0">Finished</entry>
</status>
<importance>
<entry key="i00000c" count="3" red="100" green="100" blue="100">New</entry>
<entry key="i00000c" count="4" red="100" green="100" blue="100">New</entry>
<entry key="i00000d" count="0" red="200" green="50" blue="0">Minor</entry>
<entry key="i00000e" count="0" red="200" green="150" blue="0">Major</entry>
<entry key="i00000f" count="0" red="50" green="200" blue="0">Main</entry>
</importance>
</settings>
<content count="10">
<content count="11">
<item handle="0000000000010" parent="None" root="0000000000010" order="0" type="ROOT" class="NOVEL">
<meta expanded="False"/>
<name status="s000008" import="i00000c">Novel</name>
@@ -73,13 +73,17 @@
<meta expanded="False" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
<name status="s000008" import="i00000c" exported="True">New Scene</name>
</item>
<item handle="0000000000028" parent="31489056e0916" root="None" order="0" type="FILE" class="NO_CLASS" layout="NO_LAYOUT">
<meta expanded="False" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
<name status="None" import="None" exported="True">Hello</name>
<item handle="0000000000028" parent="0000000000015" root="0000000000010" order="0" type="FOLDER" class="NOVEL">
<meta expanded="False"/>
<name status="s000008" import="i00000c">Stuff</name>
</item>
<item handle="0000000000029" parent="71ee45a3c0db9" root="None" order="0" type="FILE" class="NO_CLASS" layout="NO_LAYOUT">
<meta expanded="False" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
<name status="None" import="None" exported="True">Jane</name>
<item handle="0000000000029" parent="0000000000015" root="0000000000010" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="False" charCount="5" wordCount="1" paraCount="0" cursorPos="0"/>
<name status="s000008" import="i00000c" exported="True">Hello</name>
</item>
<item handle="000000000002a" parent="0000000000012" root="0000000000012" order="0" type="FILE" class="CHARACTER" layout="NOTE">
<meta expanded="False" charCount="4" wordCount="1" paraCount="0" cursorPos="0"/>
<name status="s000008" import="i00000c" exported="True">Jane</name>
</item>
</content>
</novelWriterXML>
-3
View File
@@ -232,9 +232,6 @@ def testCoreIndex_CheckThese(mockGUI, fncDir, mockRnd):
assert theIndex.getHandleHeaderLevel(cHandle) == "H1"
assert theIndex.getHandleHeaderLevel(nHandle) == "H1"
assert theIndex.getHandleHeaderIntLevel(cHandle) == 1
assert theIndex.getHandleHeaderIntLevel(nHandle) == 1
assert theIndex.getHandleHeaderIntLevel("stuff") == 0
# Zero Items
assert theIndex.checkThese([], cItem) == []
+48 -10
View File
@@ -29,10 +29,14 @@ from lxml import etree
from tools import cmpFiles, writeFile, readFile, buildTestProject, XML_IGNORE
from mock import causeOSError
from novelwriter.core.project import NWProject
from novelwriter.enum import nwItemClass, nwItemType, nwItemLayout
from novelwriter.common import formatTimeStamp
from novelwriter.constants import nwFiles
from novelwriter.core.tree import NWTree
from novelwriter.core.index import NWIndex
from novelwriter.core.project import NWProject
from novelwriter.core.options import OptionState
from novelwriter.core.document import NWDoc
@pytest.mark.core
@@ -280,12 +284,12 @@ def testCoreProject_NewRoot(fncDir, outDir, refDir, mockGUI, mockRnd):
@pytest.mark.core
def testCoreProject_NewFile(fncDir, outDir, refDir, mockGUI, mockRnd):
def testCoreProject_NewFileFolder(fncDir, outDir, refDir, mockGUI, mockRnd):
"""Check that new files can be added to the project.
"""
projFile = os.path.join(fncDir, "nwProject.nwx")
testFile = os.path.join(outDir, "coreProject_NewFile_nwProject.nwx")
compFile = os.path.join(refDir, "coreProject_NewFile_nwProject.nwx")
testFile = os.path.join(outDir, "coreProject_NewFileFolder_nwProject.nwx")
compFile = os.path.join(refDir, "coreProject_NewFileFolder_nwProject.nwx")
theProject = NWProject(mockGUI)
buildTestProject(theProject, fncDir)
@@ -295,9 +299,33 @@ def testCoreProject_NewFile(fncDir, outDir, refDir, mockGUI, mockRnd):
assert theProject.closeProject() is True
assert theProject.openProject(projFile) is True
assert isinstance(theProject.newFile("Hello", "31489056e0916"), str)
assert isinstance(theProject.newFile("Jane", "71ee45a3c0db9"), str)
assert theProject.projChanged
# Invalid call
assert theProject.newFolder("New Folder", "1234567890abc") is None
assert theProject.newFile("New File", "1234567890abc") is None
# Add files properly
assert theProject.newFolder("Stuff", "0000000000015") == "0000000000028"
assert theProject.newFile("Hello", "0000000000015") == "0000000000029"
assert theProject.newFile("Jane", "0000000000012") == "000000000002a"
assert "0000000000028" in theProject.tree
assert "0000000000029" in theProject.tree
assert "000000000002a" in theProject.tree
# Write to file, failed
assert theProject.writeNewFile("blabla", 1, True) is False # Not a handle
assert theProject.writeNewFile("0000000000028", 1, True) is False # Not a file
assert theProject.writeNewFile("0000000000014", 1, True) is False # Already has content
# Write to file, success
assert theProject.writeNewFile("0000000000029", 2, True) is True
assert NWDoc(theProject, "0000000000029").readDocument() == "## Hello\n\n"
assert theProject.writeNewFile("000000000002a", 1, False) is True
assert NWDoc(theProject, "000000000002a").readDocument() == "# Jane\n\n"
# Save, close and check
assert theProject.projChanged is True
assert theProject.saveProject() is True
assert theProject.closeProject() is True
@@ -305,7 +333,7 @@ def testCoreProject_NewFile(fncDir, outDir, refDir, mockGUI, mockRnd):
assert cmpFiles(testFile, compFile, ignoreStart=XML_IGNORE)
assert theProject.projChanged is False
# END Test testCoreProject_NewFile
# END Test testCoreProject_NewFileFolder
@pytest.mark.core
@@ -613,6 +641,11 @@ def testCoreProject_AccessItems(nwMinimal, mockGUI):
theProject = NWProject(mockGUI)
theProject.openProject(nwMinimal)
# Storage Objects
assert isinstance(theProject.index, NWIndex)
assert isinstance(theProject.tree, NWTree)
assert isinstance(theProject.options, OptionState)
# Move Novel ROOT to after its files
oldOrder = [
"a508bb932959c", # ROOT: Novel
@@ -722,7 +755,7 @@ def testCoreProject_StatusImport(mockGUI, fncDir, mockRnd):
# Change Importance
# =================
fHandle = theProject.newFile("Jane Doe", "8b9d2e465e150")
fHandle = theProject.newFile("Jane Doe", "0000000000012")
theProject.tree[fHandle].setImport("Main")
assert theProject.tree[fHandle].itemImport == importKeys[3]
@@ -894,6 +927,10 @@ def testCoreProject_Methods(monkeypatch, mockGUI, tmpDir, fncDir, mockRnd):
assert theProject.setProjectLang("en_GB") is True
assert theProject.projLang == "en_GB"
# Language Lookup
assert theProject.localLookup(1) == "One"
assert theProject.localLookup(10) == "Ten"
# Automatic outline update
theProject.projChanged = False
assert theProject.setAutoOutline(True)
@@ -1007,7 +1044,8 @@ def testCoreProject_OrphanedFiles(mockGUI, nwLipsum):
# Add a file with non-existent parent
# This file will be renoved from the project on open
assert theProject.newFile("Oops", "0000000000000")
oHandle = theProject.newFile("Oops", "b3643d0f92e32")
theProject.tree[oHandle].setParent("1234567890abc")
# Save and close
assert theProject.saveProject() is True
+16 -15
View File
@@ -25,7 +25,7 @@ import pytest
from mock import causeOSError
from tools import getGuiItem, readFile, writeFile, buildTestProject
from PyQt5.QtWidgets import QAction, QMessageBox, QDialog
from PyQt5.QtWidgets import QAction, QMessageBox, QDialog, QInputDialog
from novelwriter.enum import nwItemType, nwWidget
from novelwriter.dialogs import GuiDocMerge, GuiItemEditor
@@ -39,6 +39,7 @@ def testDlgMerge_Main(qtbot, monkeypatch, nwGUI, fncProj, mockRnd):
# Block message box
monkeypatch.setattr(QMessageBox, "question", lambda *a: QMessageBox.Yes)
monkeypatch.setattr(QMessageBox, "critical", lambda *a: QMessageBox.Ok)
monkeypatch.setattr(QInputDialog, "getText", lambda *a, text: (text, True))
# Create a new project
buildTestProject(nwGUI, fncProj)
@@ -56,11 +57,11 @@ def testDlgMerge_Main(qtbot, monkeypatch, nwGUI, fncProj, mockRnd):
# Add Project Content
monkeypatch.setattr(GuiItemEditor, "exec_", lambda *a: QDialog.Accepted)
nwGUI.switchFocus(nwWidget.TREE)
nwGUI.treeView.clearSelection()
nwGUI.treeView._getTreeItem(hChapterDir).setSelected(True)
nwGUI.treeView.newTreeItem(nwItemType.FILE)
nwGUI.treeView.newTreeItem(nwItemType.FILE)
nwGUI.treeView.newTreeItem(nwItemType.FILE)
nwGUI.treeView.projTree.clearSelection()
nwGUI.treeView.projTree._getTreeItem(hChapterDir).setSelected(True)
nwGUI.treeView.projTree.newTreeItem(nwItemType.FILE)
nwGUI.treeView.projTree.newTreeItem(nwItemType.FILE)
nwGUI.treeView.projTree.newTreeItem(nwItemType.FILE)
assert nwGUI.saveProject() is True
assert nwGUI.closeProject() is True
@@ -82,8 +83,8 @@ def testDlgMerge_Main(qtbot, monkeypatch, nwGUI, fncProj, mockRnd):
# Open the Merge tool
nwGUI.switchFocus(nwWidget.TREE)
nwGUI.treeView.clearSelection()
nwGUI.treeView._getTreeItem(hChapterDir).setSelected(True)
nwGUI.treeView.projTree.clearSelection()
nwGUI.treeView.projTree._getTreeItem(hChapterDir).setSelected(True)
monkeypatch.setattr(GuiDocMerge, "exec_", lambda *a: None)
nwGUI.mainMenu.aMergeDocs.activate(QAction.Trigger)
@@ -101,27 +102,27 @@ def testDlgMerge_Main(qtbot, monkeypatch, nwGUI, fncProj, mockRnd):
assert nwMerge.listBox.count() == 0
# No item selected
nwGUI.treeView.clearSelection()
nwGUI.treeView.projTree.clearSelection()
assert nwMerge._populateList() is False
assert nwMerge.listBox.count() == 0
# Non-existing item
with monkeypatch.context() as mp:
mp.setattr(NWTree, "__getitem__", lambda *a: None)
nwGUI.treeView.clearSelection()
nwGUI.treeView._getTreeItem(hChapterDir).setSelected(True)
nwGUI.treeView.projTree.clearSelection()
nwGUI.treeView.projTree._getTreeItem(hChapterDir).setSelected(True)
assert nwMerge._populateList() is False
assert nwMerge.listBox.count() == 0
# Select a non-folder
nwGUI.treeView.clearSelection()
nwGUI.treeView._getTreeItem(hChapterOne).setSelected(True)
nwGUI.treeView.projTree.clearSelection()
nwGUI.treeView.projTree._getTreeItem(hChapterOne).setSelected(True)
assert nwMerge._populateList() is False
assert nwMerge.listBox.count() == 0
# Select the chapter folder
nwGUI.treeView.clearSelection()
nwGUI.treeView._getTreeItem(hChapterDir).setSelected(True)
nwGUI.treeView.projTree.clearSelection()
nwGUI.treeView.projTree._getTreeItem(hChapterDir).setSelected(True)
assert nwMerge._populateList() is True
assert nwMerge.listBox.count() == 5
+12 -11
View File
@@ -25,7 +25,7 @@ import pytest
from mock import causeOSError
from tools import getGuiItem, readFile, writeFile, buildTestProject
from PyQt5.QtWidgets import QAction, QMessageBox, QDialog
from PyQt5.QtWidgets import QAction, QMessageBox, QDialog, QInputDialog
from novelwriter.enum import nwItemType, nwWidget
from novelwriter.dialogs import GuiDocSplit, GuiItemEditor
@@ -40,6 +40,7 @@ def testDlgSplit_Main(qtbot, monkeypatch, nwGUI, fncProj, mockRnd):
# Block message box
monkeypatch.setattr(QMessageBox, "question", lambda *a: QMessageBox.Yes)
monkeypatch.setattr(QMessageBox, "critical", lambda *a: QMessageBox.Ok)
monkeypatch.setattr(QInputDialog, "getText", lambda *a, text: (text, True))
# Create a new project
buildTestProject(nwGUI, fncProj)
@@ -60,9 +61,9 @@ def testDlgSplit_Main(qtbot, monkeypatch, nwGUI, fncProj, mockRnd):
# Add Project Content
monkeypatch.setattr(GuiItemEditor, "exec_", lambda *a: QDialog.Accepted)
nwGUI.switchFocus(nwWidget.TREE)
nwGUI.treeView.clearSelection()
nwGUI.treeView._getTreeItem(hNovelRoot).setSelected(True)
nwGUI.treeView.newTreeItem(nwItemType.FILE)
nwGUI.treeView.projTree.clearSelection()
nwGUI.treeView.projTree._getTreeItem(hNovelRoot).setSelected(True)
nwGUI.treeView.projTree.newTreeItem(nwItemType.FILE)
assert nwGUI.saveProject() is True
assert nwGUI.closeProject() is True
@@ -89,8 +90,8 @@ def testDlgSplit_Main(qtbot, monkeypatch, nwGUI, fncProj, mockRnd):
# Open the Split tool
nwGUI.switchFocus(nwWidget.TREE)
nwGUI.treeView.clearSelection()
nwGUI.treeView._getTreeItem(hToSplit).setSelected(True)
nwGUI.treeView.projTree.clearSelection()
nwGUI.treeView.projTree._getTreeItem(hToSplit).setSelected(True)
monkeypatch.setattr(GuiDocSplit, "exec_", lambda *a: None)
nwGUI.mainMenu.aSplitDoc.activate(QAction.Trigger)
@@ -109,7 +110,7 @@ def testDlgSplit_Main(qtbot, monkeypatch, nwGUI, fncProj, mockRnd):
# No item selected
nwSplit.sourceItem = None
nwGUI.treeView.clearSelection()
nwGUI.treeView.projTree.clearSelection()
assert nwSplit._populateList() is False
assert nwSplit.listBox.count() == 0
@@ -117,15 +118,15 @@ def testDlgSplit_Main(qtbot, monkeypatch, nwGUI, fncProj, mockRnd):
with monkeypatch.context() as mp:
mp.setattr(NWTree, "__getitem__", lambda *a: None)
nwSplit.sourceItem = None
nwGUI.treeView.clearSelection()
nwGUI.treeView._getTreeItem(hToSplit).setSelected(True)
nwGUI.treeView.projTree.clearSelection()
nwGUI.treeView.projTree._getTreeItem(hToSplit).setSelected(True)
assert nwSplit._populateList() is False
assert nwSplit.listBox.count() == 0
# Select a non-file
nwSplit.sourceItem = None
nwGUI.treeView.clearSelection()
nwGUI.treeView._getTreeItem(hChapterDir).setSelected(True)
nwGUI.treeView.projTree.clearSelection()
nwGUI.treeView.projTree._getTreeItem(hChapterDir).setSelected(True)
assert nwSplit._populateList() is False
assert nwSplit.listBox.count() == 0
+7 -6
View File
@@ -23,12 +23,12 @@ import pytest
from tools import getGuiItem, buildTestProject
from PyQt5.QtWidgets import QAction, QDialog, QMessageBox
from PyQt5.QtWidgets import QAction, QDialog, QMessageBox, QInputDialog
from novelwriter.gui import GuiProjectTree
from novelwriter.enum import nwItemLayout, nwItemType
from novelwriter.dialogs import GuiItemEditor
from novelwriter.core.tree import NWTree
from novelwriter.gui.projtree import GuiProjectTree
statusKeys = ["s000000", "s000001", "s000002", "s000003"]
importKeys = ["i000004", "i000005", "i000006", "i000007"]
@@ -52,7 +52,7 @@ def testDlgItemEditor_Dialog(qtbot, monkeypatch, nwGUI, fncProj, mockRnd):
tHandle = "000000000000f"
# No Selection
nwGUI.treeView.clearSelection()
nwGUI.treeView.projTree.clearSelection()
assert nwGUI.editItem() is False
# Force opening from editor
@@ -154,6 +154,7 @@ def testDlgItemEditor_Note(qtbot, monkeypatch, nwGUI, fncProj, mockRnd):
# Block message box
monkeypatch.setattr(QMessageBox, "question", lambda *a: QMessageBox.Yes)
monkeypatch.setattr(GuiProjectTree, "hasFocus", lambda *a: True)
monkeypatch.setattr(QInputDialog, "getText", lambda *a, text: (text, True))
# Create Project and Open Document
buildTestProject(nwGUI, fncProj)
@@ -163,9 +164,9 @@ def testDlgItemEditor_Note(qtbot, monkeypatch, nwGUI, fncProj, mockRnd):
assert nwGUI.theProject.importItems.name(importKeys[1]) == "Minor"
# Create Note
nwGUI.treeView.clearSelection()
nwGUI.treeView._getTreeItem("000000000000a").setSelected(True)
nwGUI.treeView.newTreeItem(nwItemType.FILE, None)
nwGUI.treeView.projTree.clearSelection()
nwGUI.treeView.projTree._getTreeItem("000000000000a").setSelected(True)
nwGUI.treeView.projTree.newTreeItem(nwItemType.FILE, None, isNote=True)
# Open Note
assert nwGUI.openDocument("0000000000010")
+3 -3
View File
@@ -54,9 +54,9 @@ def testGuiViewer_Main(qtbot, monkeypatch, nwGUI, nwLipsum):
nwGUI.treeView.setSelectedHandle("88243afbe5ed8")
# Middle-click the selected item
theItem = nwGUI.treeView._getTreeItem("88243afbe5ed8")
theRect = nwGUI.treeView.visualItemRect(theItem)
qtbot.mouseClick(nwGUI.treeView.viewport(), Qt.MidButton, pos=theRect.center())
theItem = nwGUI.treeView.projTree._getTreeItem("88243afbe5ed8")
theRect = nwGUI.treeView.projTree.visualItemRect(theItem)
qtbot.mouseClick(nwGUI.treeView.projTree.viewport(), Qt.MidButton, pos=theRect.center())
assert nwGUI.docViewer.docHandle() == "88243afbe5ed8"
# Reload the text
+27 -27
View File
@@ -26,13 +26,12 @@ from shutil import copyfile
from tools import cmpFiles, buildTestProject, XML_IGNORE, writeFile
from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import QMessageBox, QDialog
from PyQt5.QtWidgets import QMessageBox, QDialog, QInputDialog
from novelwriter.gui import (
GuiDocEditor, GuiProjectTree, GuiNovelTree, GuiOutline
)
from novelwriter.gui import GuiDocEditor, GuiNovelTree, GuiOutline
from novelwriter.enum import nwItemType, nwWidget
from novelwriter.tools import GuiProjectWizard
from novelwriter.gui.projtree import GuiProjectTree
from novelwriter.dialogs.itemeditor import GuiItemEditor
keyDelay = 2
@@ -126,7 +125,7 @@ def testGuiMain_ProjectTreeItems(qtbot, monkeypatch, nwGUI, fncProj, mockRnd):
with monkeypatch.context() as mp:
mp.setattr(GuiProjectTree, "hasFocus", lambda *a: True)
assert nwGUI.docEditor.docHandle() is None
nwGUI.treeView._getTreeItem(sHandle).setSelected(True)
nwGUI.treeView.projTree._getTreeItem(sHandle).setSelected(True)
nwGUI._keyPressReturn()
assert nwGUI.docEditor.docHandle() == sHandle
assert nwGUI.closeDocument() is True
@@ -174,6 +173,7 @@ def testGuiMain_Editing(qtbot, monkeypatch, nwGUI, fncProj, refDir, outDir, mock
monkeypatch.setattr(GuiItemEditor, "result", lambda *a: QDialog.Accepted)
monkeypatch.setattr(GuiProjectTree, "hasFocus", lambda *a: True)
monkeypatch.setattr(GuiDocEditor, "hasFocus", lambda *a: True)
monkeypatch.setattr(QInputDialog, "getText", lambda *a, text: (text, True))
# Create new, save, close project
buildTestProject(nwGUI, fncProj)
@@ -220,14 +220,14 @@ def testGuiMain_Editing(qtbot, monkeypatch, nwGUI, fncProj, refDir, outDir, mock
assert nwGUI.theProject.spellCheck is False
# Check that tree items have been created
assert nwGUI.treeView._getTreeItem("0000000000008") is not None
assert nwGUI.treeView._getTreeItem("0000000000009") is not None
assert nwGUI.treeView._getTreeItem("000000000000a") is not None
assert nwGUI.treeView._getTreeItem("000000000000b") is not None
assert nwGUI.treeView._getTreeItem("000000000000c") is not None
assert nwGUI.treeView._getTreeItem("000000000000d") is not None
assert nwGUI.treeView._getTreeItem("000000000000e") is not None
assert nwGUI.treeView._getTreeItem("000000000000f") is not None
assert nwGUI.treeView.projTree._getTreeItem("0000000000008") is not None
assert nwGUI.treeView.projTree._getTreeItem("0000000000009") is not None
assert nwGUI.treeView.projTree._getTreeItem("000000000000a") is not None
assert nwGUI.treeView.projTree._getTreeItem("000000000000b") is not None
assert nwGUI.treeView.projTree._getTreeItem("000000000000c") is not None
assert nwGUI.treeView.projTree._getTreeItem("000000000000d") is not None
assert nwGUI.treeView.projTree._getTreeItem("000000000000e") is not None
assert nwGUI.treeView.projTree._getTreeItem("000000000000f") is not None
nwGUI.mainMenu.aSpellCheck.setChecked(True)
assert nwGUI.mainMenu._toggleSpellCheck()
@@ -240,9 +240,9 @@ def testGuiMain_Editing(qtbot, monkeypatch, nwGUI, fncProj, refDir, outDir, mock
# Add a Character File
nwGUI.switchFocus(nwWidget.TREE)
nwGUI.treeView.clearSelection()
nwGUI.treeView._getTreeItem("000000000000a").setSelected(True)
nwGUI.treeView.newTreeItem(nwItemType.FILE, None)
nwGUI.treeView.projTree.clearSelection()
nwGUI.treeView.projTree._getTreeItem("000000000000a").setSelected(True)
nwGUI.treeView.projTree.newTreeItem(nwItemType.FILE, None, isNote=True)
assert nwGUI.openSelectedItem()
# Type something into the document
@@ -262,9 +262,9 @@ def testGuiMain_Editing(qtbot, monkeypatch, nwGUI, fncProj, refDir, outDir, mock
# Add a Plot File
nwGUI.switchFocus(nwWidget.TREE)
nwGUI.treeView.clearSelection()
nwGUI.treeView._getTreeItem("0000000000009").setSelected(True)
nwGUI.treeView.newTreeItem(nwItemType.FILE, None)
nwGUI.treeView.projTree.clearSelection()
nwGUI.treeView.projTree._getTreeItem("0000000000009").setSelected(True)
nwGUI.treeView.projTree.newTreeItem(nwItemType.FILE, None, isNote=True)
assert nwGUI.openSelectedItem()
# Type something into the document
@@ -284,9 +284,9 @@ def testGuiMain_Editing(qtbot, monkeypatch, nwGUI, fncProj, refDir, outDir, mock
# Add a World File
nwGUI.switchFocus(nwWidget.TREE)
nwGUI.treeView.clearSelection()
nwGUI.treeView._getTreeItem("000000000000b").setSelected(True)
nwGUI.treeView.newTreeItem(nwItemType.FILE, None)
nwGUI.treeView.projTree.clearSelection()
nwGUI.treeView.projTree._getTreeItem("000000000000b").setSelected(True)
nwGUI.treeView.projTree.newTreeItem(nwItemType.FILE, None, isNote=True)
assert nwGUI.openSelectedItem()
# Add Some Text
@@ -315,10 +315,10 @@ def testGuiMain_Editing(qtbot, monkeypatch, nwGUI, fncProj, refDir, outDir, mock
# Select the 'New Scene' file
nwGUI.switchFocus(nwWidget.TREE)
nwGUI.treeView.clearSelection()
nwGUI.treeView._getTreeItem("0000000000008").setExpanded(True)
nwGUI.treeView._getTreeItem("000000000000d").setExpanded(True)
nwGUI.treeView._getTreeItem("000000000000f").setSelected(True)
nwGUI.treeView.projTree.clearSelection()
nwGUI.treeView.projTree._getTreeItem("0000000000008").setExpanded(True)
nwGUI.treeView.projTree._getTreeItem("000000000000d").setExpanded(True)
nwGUI.treeView.projTree._getTreeItem("000000000000f").setSelected(True)
assert nwGUI.openSelectedItem()
# Type something into the document
@@ -461,7 +461,7 @@ def testGuiMain_Editing(qtbot, monkeypatch, nwGUI, fncProj, refDir, outDir, mock
qtbot.wait(stepDelay)
# Check a Quick Create and Delete
assert nwGUI.treeView.newTreeItem(nwItemType.FILE, None)
assert nwGUI.treeView.projTree.newTreeItem(nwItemType.FILE, None)
newHandle = nwGUI.treeView.getSelectedHandle()
assert nwGUI.theProject.tree["0000000000020"] is not None
assert nwGUI.treeView.deleteItem()
+3 -3
View File
@@ -467,7 +467,7 @@ def testGuiMenu_Insert(qtbot, monkeypatch, nwGUI, fncDir, fncProj, mockRnd):
buildTestProject(nwGUI, fncProj)
assert nwGUI.treeView._getTreeItem("000000000000f") is not None
assert nwGUI.treeView.projTree._getTreeItem("000000000000f") is not None
assert nwGUI.openDocument("000000000000f") is True
nwGUI.docEditor.clear()
@@ -476,10 +476,10 @@ def testGuiMenu_Insert(qtbot, monkeypatch, nwGUI, fncDir, fncProj, mockRnd):
assert nwGUI.docEditor.getText() == "hello world"
nwGUI.docEditor.clear()
assert not nwGUI.docEditor.insertText(nwDocInsert.NO_INSERT)
assert nwGUI.docEditor.insertText(nwDocInsert.NO_INSERT) is False
assert nwGUI.docEditor.isEmpty()
assert not nwGUI.docEditor.insertText(None)
assert nwGUI.docEditor.insertText(None) is False
assert nwGUI.docEditor.isEmpty()
# qtbot.stopForInteraction()
+159 -68
View File
@@ -24,11 +24,10 @@ import os
from tools import buildTestProject
from PyQt5.QtWidgets import QAction, QMessageBox
from PyQt5.QtWidgets import QMessageBox, QInputDialog, QMenu
from novelwriter.guimain import GuiMain
from novelwriter.gui.projtree import GuiProjectTree
from novelwriter.enum import nwItemType, nwItemClass
from novelwriter.enum import nwItemLayout, nwItemType, nwItemClass
@pytest.mark.gui
@@ -40,86 +39,101 @@ def testGuiProjTree_NewItems(qtbot, caplog, monkeypatch, nwGUI, fncDir, mockRnd)
monkeypatch.setattr(QMessageBox, "critical", lambda *a: QMessageBox.Yes)
monkeypatch.setattr(QMessageBox, "question", lambda *a: QMessageBox.Yes)
monkeypatch.setattr(QMessageBox, "information", lambda *a: QMessageBox.Yes)
monkeypatch.setattr(GuiMain, "editItem", lambda *a: None)
monkeypatch.setattr(QInputDialog, "getText", lambda *a, text: (text, True))
nwTree = nwGUI.treeView
# Try to add item with no project
assert nwTree.newTreeItem(nwItemType.FILE) is False
assert nwTree.projTree.newTreeItem(nwItemType.FILE) is False
# Create a project
prjDir = os.path.join(fncDir, "project")
buildTestProject(nwGUI, prjDir)
# No itemType set
nwTree.clearSelection()
assert nwTree.newTreeItem(None) is False
nwTree.projTree.clearSelection()
assert nwTree.projTree.newTreeItem(None) is False
# Root Items
# ==========
# No class set
assert nwTree.newTreeItem(nwItemType.ROOT) is False
assert nwTree.projTree.newTreeItem(nwItemType.ROOT) is False
# Create root item
assert nwTree.newTreeItem(nwItemType.ROOT, nwItemClass.WORLD) is True
assert nwTree.projTree.newTreeItem(nwItemType.ROOT, nwItemClass.WORLD) is True
assert "0000000000010" in nwGUI.theProject.tree
# File/Folder Items
# =================
# No location selected for new item
nwTree.clearSelection()
nwTree.projTree.clearSelection()
caplog.clear()
assert nwTree.newTreeItem(nwItemType.FILE) is False
assert nwTree.newTreeItem(nwItemType.FOLDER) is False
assert nwTree.projTree.newTreeItem(nwItemType.FILE) is False
assert nwTree.projTree.newTreeItem(nwItemType.FOLDER) is False
assert "Did not find anywhere" in caplog.text
# Create new folder as child of Novel folder
nwTree.setSelectedHandle("0000000000008")
assert nwTree.newTreeItem(nwItemType.FOLDER) is True
assert nwTree.projTree.newTreeItem(nwItemType.FOLDER) is True
assert nwGUI.theProject.tree["0000000000011"].itemParent == "0000000000008"
assert nwGUI.theProject.tree["0000000000011"].itemRoot == "0000000000008"
assert nwGUI.theProject.tree["0000000000011"].itemClass == nwItemClass.NOVEL
# Add a new file in the new folder
nwTree.setSelectedHandle("0000000000011")
assert nwTree.newTreeItem(nwItemType.FILE) is True
assert nwTree.projTree.newTreeItem(nwItemType.FILE) is True
assert nwGUI.theProject.tree["0000000000012"].itemParent == "0000000000011"
assert nwGUI.theProject.tree["0000000000012"].itemRoot == "0000000000008"
assert nwGUI.theProject.tree["0000000000012"].itemClass == nwItemClass.NOVEL
# Add a new file next to the other new file
# Add a new chapter next to the other new file
nwTree.setSelectedHandle("0000000000012")
assert nwTree.newTreeItem(nwItemType.FILE) is True
assert nwTree.projTree.newTreeItem(nwItemType.FILE, hLevel=2) is True
assert nwGUI.theProject.tree["0000000000013"].itemParent == "0000000000011"
assert nwGUI.theProject.tree["0000000000013"].itemRoot == "0000000000008"
assert nwGUI.theProject.tree["0000000000013"].itemClass == nwItemClass.NOVEL
assert nwGUI.openDocument("0000000000013")
assert nwGUI.docEditor.getText() == "## New Document\n\n"
assert nwGUI.docEditor.getText() == "## New Chapter\n\n"
# Add a new scene next to the other new file
nwTree.setSelectedHandle("0000000000012")
assert nwTree.projTree.newTreeItem(nwItemType.FILE, hLevel=3) is True
assert nwGUI.theProject.tree["0000000000014"].itemParent == "0000000000011"
assert nwGUI.theProject.tree["0000000000014"].itemRoot == "0000000000008"
assert nwGUI.theProject.tree["0000000000014"].itemClass == nwItemClass.NOVEL
assert nwGUI.openDocument("0000000000014")
assert nwGUI.docEditor.getText() == "### New Scene\n\n"
# Add a new file to the characters folder
nwTree.setSelectedHandle("000000000000a")
assert nwTree.newTreeItem(nwItemType.FILE) is True
assert nwGUI.theProject.tree["0000000000014"].itemParent == "000000000000a"
assert nwGUI.theProject.tree["0000000000014"].itemRoot == "000000000000a"
assert nwGUI.theProject.tree["0000000000014"].itemClass == nwItemClass.CHARACTER
assert nwGUI.openDocument("0000000000014")
assert nwTree.projTree.newTreeItem(nwItemType.FILE, hLevel=1, isNote=True) is True
assert nwGUI.theProject.tree["0000000000015"].itemParent == "000000000000a"
assert nwGUI.theProject.tree["0000000000015"].itemRoot == "000000000000a"
assert nwGUI.theProject.tree["0000000000015"].itemClass == nwItemClass.CHARACTER
assert nwGUI.openDocument("0000000000015")
assert nwGUI.docEditor.getText() == "# New Note\n\n"
# Make sure the sibling folder bug trap works
nwTree.setSelectedHandle("0000000000013")
nwGUI.theProject.tree["0000000000013"].setParent(None) # This should not happen
caplog.clear()
assert nwTree.newTreeItem(nwItemType.FILE) is False
assert nwTree.projTree.newTreeItem(nwItemType.FILE) is False
assert "Internal error" in caplog.text
nwGUI.theProject.tree["0000000000013"].setParent("0000000000011")
# Cancel during creation
with monkeypatch.context() as mp:
mp.setattr(QInputDialog, "getText", lambda *a, **k: ("", False))
nwTree.setSelectedHandle("0000000000013")
assert nwTree.projTree.newTreeItem(nwItemType.FILE) is False
# Get the trash folder
nwTree._addTrashRoot()
nwTree.projTree._addTrashRoot()
trashHandle = nwGUI.theProject.trashFolder()
nwTree.setSelectedHandle(trashHandle)
assert nwTree.newTreeItem(nwItemType.FILE) is False
assert nwTree.projTree.newTreeItem(nwItemType.FILE) is False
assert "Cannot add new files or folders to the Trash folder" in caplog.text
# Other Checks
@@ -148,12 +162,12 @@ def testGuiProjTree_MoveItems(qtbot, monkeypatch, nwGUI, fncDir, mockRnd):
monkeypatch.setattr(QMessageBox, "critical", lambda *a: QMessageBox.Yes)
monkeypatch.setattr(QMessageBox, "question", lambda *a: QMessageBox.Yes)
monkeypatch.setattr(QMessageBox, "information", lambda *a: QMessageBox.Yes)
monkeypatch.setattr(GuiMain, "editItem", lambda *a: None)
monkeypatch.setattr(QInputDialog, "getText", lambda *a, text: (text, True))
nwTree = nwGUI.treeView
# Try to move item with no project
assert nwTree.moveTreeItem(1) is False
assert nwTree.projTree.moveTreeItem(1) is False
# Create a project
prjDir = os.path.join(fncDir, "project")
@@ -164,42 +178,33 @@ def testGuiProjTree_MoveItems(qtbot, monkeypatch, nwGUI, fncDir, mockRnd):
# Add some files
nwTree.setSelectedHandle("000000000000d")
assert nwTree.newTreeItem(nwItemType.FILE) is True
assert nwTree.newTreeItem(nwItemType.FILE) is True
assert nwTree.newTreeItem(nwItemType.FILE) is True
assert nwTree.projTree.newTreeItem(nwItemType.FILE) is True
assert nwTree.projTree.newTreeItem(nwItemType.FILE) is True
assert nwTree.projTree.newTreeItem(nwItemType.FILE) is True
assert nwTree.getTreeFromHandle("000000000000d") == [
"000000000000d", "000000000000e", "000000000000f",
"0000000000010", "0000000000011", "0000000000012",
]
# Move item without focus
monkeypatch.setattr(GuiProjectTree, "hasFocus", lambda *a: False)
assert nwTree.moveTreeItem(1) is False
assert nwTree.getTreeFromHandle("000000000000d") == [
"000000000000d", "000000000000e", "000000000000f",
"0000000000010", "0000000000011", "0000000000012",
]
monkeypatch.setattr(GuiProjectTree, "hasFocus", lambda *a: True)
# Move with no selections
nwTree.clearSelection()
assert nwTree.moveTreeItem(1) is False
nwTree.projTree.clearSelection()
assert nwTree.projTree.moveTreeItem(1) is False
# Move second item up twice (should give same result)
nwTree.setSelectedHandle("000000000000f")
assert nwTree.moveTreeItem(-1) is True
assert nwTree.projTree.moveTreeItem(-1) is True
assert nwTree.getTreeFromHandle("000000000000d") == [
"000000000000d", "000000000000f", "000000000000e",
"0000000000010", "0000000000011", "0000000000012",
]
assert nwTree.moveTreeItem(-1) is False
assert nwTree.projTree.moveTreeItem(-1) is False
assert nwTree.getTreeFromHandle("000000000000d") == [
"000000000000d", "000000000000f", "000000000000e",
"0000000000010", "0000000000011", "0000000000012",
]
# Restore via menu entry
nwGUI.mainMenu.aMoveDown.activate(QAction.Trigger)
# Restore
assert nwTree.projTree.moveTreeItem(1) is True
assert nwTree.getTreeFromHandle("000000000000d") == [
"000000000000d", "000000000000e", "000000000000f",
"0000000000010", "0000000000011", "0000000000012",
@@ -207,19 +212,19 @@ def testGuiProjTree_MoveItems(qtbot, monkeypatch, nwGUI, fncDir, mockRnd):
# Move fifth item down twice (should give same result)
nwTree.setSelectedHandle("0000000000011")
assert nwTree.moveTreeItem(1) is True
assert nwTree.projTree.moveTreeItem(1) is True
assert nwTree.getTreeFromHandle("000000000000d") == [
"000000000000d", "000000000000e", "000000000000f",
"0000000000010", "0000000000012", "0000000000011",
]
assert nwTree.moveTreeItem(1) is False
assert nwTree.projTree.moveTreeItem(1) is False
assert nwTree.getTreeFromHandle("000000000000d") == [
"000000000000d", "000000000000e", "000000000000f",
"0000000000010", "0000000000012", "0000000000011",
]
# Restore via menu entry
nwGUI.mainMenu.aMoveUp.activate(QAction.Trigger)
# Restore
assert nwTree.projTree.moveTreeItem(-1) is True
assert nwTree.getTreeFromHandle("000000000000d") == [
"000000000000d", "000000000000e", "000000000000f",
"0000000000010", "0000000000011", "0000000000012",
@@ -227,12 +232,12 @@ def testGuiProjTree_MoveItems(qtbot, monkeypatch, nwGUI, fncDir, mockRnd):
# Move down again, and restore via undo
nwTree.setSelectedHandle("0000000000011")
assert nwTree.moveTreeItem(1) is True
assert nwTree.projTree.moveTreeItem(1) is True
assert nwTree.getTreeFromHandle("000000000000d") == [
"000000000000d", "000000000000e", "000000000000f",
"0000000000010", "0000000000012", "0000000000011",
]
nwGUI.mainMenu.aMoveUndo.activate(QAction.Trigger)
assert nwTree.projTree.undoLastMove() is True
assert nwTree.getTreeFromHandle("000000000000d") == [
"000000000000d", "000000000000e", "000000000000f",
"0000000000010", "0000000000011", "0000000000012",
@@ -245,15 +250,15 @@ def testGuiProjTree_MoveItems(qtbot, monkeypatch, nwGUI, fncDir, mockRnd):
assert nwGUI.theProject.tree._treeOrder.index("0000000000008") == 0
# Move novel folder up
assert nwTree.moveTreeItem(-1) is False
assert nwTree.projTree.moveTreeItem(-1) is False
assert nwGUI.theProject.tree._treeOrder.index("0000000000008") == 0
# Move novel folder down
assert nwTree.moveTreeItem(1) is True
assert nwTree.projTree.moveTreeItem(1) is True
assert nwGUI.theProject.tree._treeOrder.index("0000000000008") == 1
# Move novel folder up again
assert nwTree.moveTreeItem(-1) is True
assert nwTree.projTree.moveTreeItem(-1) is True
assert nwGUI.theProject.tree._treeOrder.index("0000000000008") == 0
# Clean up
@@ -272,7 +277,7 @@ def testGuiProjTree_DeleteItems(qtbot, caplog, monkeypatch, nwGUI, fncDir, mockR
monkeypatch.setattr(QMessageBox, "critical", lambda *a: QMessageBox.Yes)
monkeypatch.setattr(QMessageBox, "question", lambda *a: QMessageBox.Yes)
monkeypatch.setattr(QMessageBox, "information", lambda *a: QMessageBox.Yes)
monkeypatch.setattr(GuiMain, "editItem", lambda *a: None)
monkeypatch.setattr(QInputDialog, "getText", lambda *a, text: (text, True))
nwTree = nwGUI.treeView
@@ -289,9 +294,9 @@ def testGuiProjTree_DeleteItems(qtbot, caplog, monkeypatch, nwGUI, fncDir, mockR
# Add some files
nwTree.setSelectedHandle("000000000000d")
assert nwTree.newTreeItem(nwItemType.FILE) is True
assert nwTree.newTreeItem(nwItemType.FILE) is True
assert nwTree.newTreeItem(nwItemType.FILE) is True
assert nwTree.projTree.newTreeItem(nwItemType.FILE) is True
assert nwTree.projTree.newTreeItem(nwItemType.FILE) is True
assert nwTree.projTree.newTreeItem(nwItemType.FILE) is True
assert nwTree.getTreeFromHandle("000000000000d") == [
"000000000000d", "000000000000e", "000000000000f",
"0000000000010", "0000000000011", "0000000000012",
@@ -304,13 +309,13 @@ def testGuiProjTree_DeleteItems(qtbot, caplog, monkeypatch, nwGUI, fncDir, mockR
monkeypatch.setattr(GuiProjectTree, "hasFocus", lambda *a: True)
# No selection made
nwTree.clearSelection()
nwTree.projTree.clearSelection()
caplog.clear()
assert nwTree.deleteItem() is False
assert "no item to delete" in caplog.text
# Not a valid handle
nwTree.clearSelection()
nwTree.projTree.clearSelection()
caplog.clear()
assert nwTree.deleteItem("0000000000000") is False
assert "Could not find tree item" in caplog.text
@@ -326,10 +331,10 @@ def testGuiProjTree_DeleteItems(qtbot, caplog, monkeypatch, nwGUI, fncDir, mockR
# ===========
# Block adding trash folder
funcPointer = nwTree._addTrashRoot
nwTree._addTrashRoot = lambda *a: None
funcPointer = nwTree.projTree._addTrashRoot
nwTree.projTree._addTrashRoot = lambda *a: None
assert nwTree.deleteItem("0000000000012") is False
nwTree._addTrashRoot = funcPointer
nwTree.projTree._addTrashRoot = funcPointer
# Delete last two documents, which also adds the trash folder
assert nwTree.deleteItem("0000000000012") is True
@@ -373,10 +378,10 @@ def testGuiProjTree_DeleteItems(qtbot, caplog, monkeypatch, nwGUI, fncDir, mockR
# Add a folder with two files
nwTree.setSelectedHandle("0000000000009")
assert nwTree.newTreeItem(nwItemType.FOLDER) is True
assert nwTree.projTree.newTreeItem(nwItemType.FOLDER) is True
nwTree.setSelectedHandle("0000000000014")
assert nwTree.newTreeItem(nwItemType.FILE) is True
assert nwTree.newTreeItem(nwItemType.FILE) is True
assert nwTree.projTree.newTreeItem(nwItemType.FILE) is True
assert nwTree.projTree.newTreeItem(nwItemType.FILE) is True
assert os.path.isfile(os.path.join(fncDir, "project", "content", "0000000000015.nwd"))
assert os.path.isfile(os.path.join(fncDir, "project", "content", "0000000000016.nwd"))
@@ -399,7 +404,7 @@ def testGuiProjTree_DeleteItems(qtbot, caplog, monkeypatch, nwGUI, fncDir, mockR
# Add an empty folder, which can be deleted with no further restrictions
nwTree.setSelectedHandle("0000000000009")
assert nwTree.newTreeItem(nwItemType.FOLDER) is True
assert nwTree.projTree.newTreeItem(nwItemType.FOLDER) is True
assert nwTree.getTreeFromHandle("0000000000009") == ["0000000000009", "0000000000017"]
nwTree.setSelectedHandle("0000000000017")
@@ -441,7 +446,7 @@ def testGuiProjTree_DeleteItems(qtbot, caplog, monkeypatch, nwGUI, fncDir, mockR
assert os.path.isfile(os.path.join(fncDir, "project", "content", "000000000000e.nwd"))
# Delete proper
assert nwTree._deleteTreeItem("000000000000e") is True
assert nwTree.projTree._deleteTreeItem("000000000000e") is True
assert not os.path.isfile(os.path.join(fncDir, "project", "content", "000000000000e.nwd"))
# Clean up
@@ -449,3 +454,89 @@ def testGuiProjTree_DeleteItems(qtbot, caplog, monkeypatch, nwGUI, fncDir, mockR
nwGUI.closeProject()
# END Test testGuiProjTree_DeleteItems
@pytest.mark.gui
def testGuiProjTree_ContextMenu(qtbot, caplog, monkeypatch, nwGUI, fncDir, mockRnd):
"""Test the building of the project tree context menu. All this does
is test that the menu builds. It doesn't open the actual menu,
"""
# Block message box
monkeypatch.setattr(QMessageBox, "warning", lambda *a: QMessageBox.Yes)
monkeypatch.setattr(QMessageBox, "critical", lambda *a: QMessageBox.Yes)
monkeypatch.setattr(QMessageBox, "question", lambda *a: QMessageBox.Yes)
monkeypatch.setattr(QMessageBox, "information", lambda *a: QMessageBox.Yes)
monkeypatch.setattr(QInputDialog, "getText", lambda *a, text: (text, True))
monkeypatch.setattr(QMenu, "exec_", lambda *a: None)
# Create a project
prjDir = os.path.join(fncDir, "project")
buildTestProject(nwGUI, prjDir)
# Handles for new objects
hNovelRoot = "0000000000008"
hTitlePage = "000000000000c"
hChapterDir = "000000000000d"
hChapterFile = "000000000000e"
hCharRoot = "000000000000a"
hCharNote = "0000000000011"
hNovelNote = "0000000000012"
projTree = nwGUI.treeView.projTree
projTree._getTreeItem(hNovelRoot).setExpanded(True)
projTree._getTreeItem(hChapterDir).setExpanded(True)
projTree._addTrashRoot()
hTrashRoot = projTree.theProject.tree.trashRoot()
projTree.setSelectedHandle(hCharRoot)
projTree.newTreeItem(nwItemType.FILE)
projTree.setSelectedHandle(hNovelRoot)
projTree.newTreeItem(nwItemType.FILE, isNote=True)
def itemPos(tHandle):
return projTree.visualItemRect(projTree._getTreeItem(tHandle)).center()
# No item under menu
assert projTree._openContextMenu(projTree.viewport().rect().bottomRight()) is False
# Generate the possible menu combinarions
assert projTree._openContextMenu(itemPos(hTrashRoot)) is True
assert projTree._openContextMenu(itemPos(hNovelRoot)) is True
assert projTree._openContextMenu(itemPos(hNovelNote)) is True
assert projTree._openContextMenu(itemPos(hTitlePage)) is True
assert projTree._openContextMenu(itemPos(hChapterDir)) is True
assert projTree._openContextMenu(itemPos(hChapterFile)) is True
assert projTree._openContextMenu(itemPos(hCharRoot)) is True
assert projTree._openContextMenu(itemPos(hCharNote)) is True
# Direct Edit Functions
# =====================
# Trigger the dedicated functions the menu entries connect to
nwItem = projTree.theProject.tree[hNovelNote]
# Toggle exported flag
assert nwItem.isExported is True
projTree._toggleItemExported(hNovelNote)
assert nwItem.isExported is False
# Change item status
assert nwItem.itemStatus == "s000000"
projTree._changeItemStatus(hNovelNote, "s000001")
assert nwItem.itemStatus == "s000001"
# Change item importance
assert nwItem.itemImport == "i000004"
projTree._changeItemImport(hNovelNote, "i000005")
assert nwItem.itemImport == "i000005"
# Change item layout
assert nwItem.itemLayout == nwItemLayout.NOTE
projTree._changeItemLayout(hNovelNote, nwItemLayout.DOCUMENT)
assert nwItem.itemLayout == nwItemLayout.DOCUMENT
projTree._changeItemLayout(hNovelNote, nwItemLayout.NOTE)
assert nwItem.itemLayout == nwItemLayout.NOTE
# qtbot.stop()
# END Test testGuiProjTree_ContextMenu