Merge branch 'main' into merge_1.6.6

This commit is contained in:
Veronica Berglyd Olsen
2022-10-25 18:49:28 +02:00
committed by GitHub
11 changed files with 514 additions and 372 deletions
+6
View File
@@ -421,6 +421,12 @@ class PagedDialog(QDialog):
self._buttonBox.addWidget(buttonBar) self._buttonBox.addWidget(buttonBar)
return return
def setCurrentWidget(self, widget):
"""Forward the changing of tab to the QTabWidget.
"""
self._tabBox.setCurrentWidget(widget)
return
# END Class PagedDialog # END Class PagedDialog
+28 -3
View File
@@ -43,7 +43,12 @@ logger = logging.getLogger(__name__)
class GuiProjectSettings(PagedDialog): class GuiProjectSettings(PagedDialog):
def __init__(self, mainGui): TAB_MAIN = 0
TAB_STATUS = 1
TAB_IMPORT = 2
TAB_REPLACE = 3
def __init__(self, mainGui, focusTab=TAB_MAIN):
super().__init__(parent=mainGui) super().__init__(parent=mainGui)
logger.debug("Initialising GuiProjectSettings ...") logger.debug("Initialising GuiProjectSettings ...")
@@ -83,12 +88,19 @@ class GuiProjectSettings(PagedDialog):
self.addControls(self.buttonBox) self.addControls(self.buttonBox)
# Flags # Flags
self.spellChanged = False self._spellChanged = False
# Focus Tab
self._focusTab(focusTab)
logger.debug("GuiProjectSettings initialisation complete") logger.debug("GuiProjectSettings initialisation complete")
return return
@property
def spellChanged(self):
return self._spellChanged
## ##
# Slots # Slots
## ##
@@ -108,7 +120,7 @@ class GuiProjectSettings(PagedDialog):
self.theProject.setProjBackup(doBackup) self.theProject.setProjBackup(doBackup)
# Remember this as updating spell dictionary can be expensive # Remember this as updating spell dictionary can be expensive
self.spellChanged = self.theProject.setSpellLang(spellLang) self._spellChanged = self.theProject.setSpellLang(spellLang)
if self.tabStatus.colChanged: if self.tabStatus.colChanged:
newList, delList = self.tabStatus.getNewList() newList, delList = self.tabStatus.getNewList()
@@ -141,6 +153,19 @@ class GuiProjectSettings(PagedDialog):
# Internal Functions # Internal Functions
## ##
def _focusTab(self, tab):
"""Change which is the focused tab.
"""
if tab == self.TAB_MAIN:
self.setCurrentWidget(self.tabMain)
elif tab == self.TAB_STATUS:
self.setCurrentWidget(self.tabStatus)
elif tab == self.TAB_IMPORT:
self.setCurrentWidget(self.tabImport)
elif tab == self.TAB_REPLACE:
self.setCurrentWidget(self.tabReplace)
return
def _saveGuiSettings(self): def _saveGuiSettings(self):
"""Save GUI settings. """Save GUI settings.
""" """
+62 -55
View File
@@ -41,8 +41,8 @@ from PyQt5.QtWidgets import (
from novelwriter.core import DocMerger, DocSplitter from novelwriter.core import DocMerger, DocSplitter
from novelwriter.enum import nwDocMode, nwItemType, nwItemClass, nwItemLayout, nwAlert from novelwriter.enum import nwDocMode, nwItemType, nwItemClass, nwItemLayout, nwAlert
from novelwriter.dialogs import GuiDocMerge, GuiDocSplit, GuiEditLabel from novelwriter.dialogs import GuiDocMerge, GuiDocSplit, GuiEditLabel, GuiProjectSettings
from novelwriter.constants import nwHeaders, trConst, nwLabels from novelwriter.constants import nwHeaders, nwUnicode, trConst, nwLabels
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -62,6 +62,9 @@ class GuiProjectView(QWidget):
selectedItemChanged = pyqtSignal(str) selectedItemChanged = pyqtSignal(str)
openDocumentRequest = pyqtSignal(str, Enum, int, str) openDocumentRequest = pyqtSignal(str, Enum, int, str)
# Requests for the main GUI
projectSettingsRequest = pyqtSignal(int)
def __init__(self, mainGui): def __init__(self, mainGui):
super().__init__(parent=mainGui) super().__init__(parent=mainGui)
@@ -102,9 +105,6 @@ class GuiProjectView(QWidget):
self.keyContext.activated.connect(lambda: self.projTree.openContextOnSelected()) self.keyContext.activated.connect(lambda: self.projTree.openContextOnSelected())
# Function Mappings # Function Mappings
self.revealNewTreeItem = self.projTree.revealNewTreeItem
self.renameTreeItem = self.projTree.renameTreeItem
self.getTreeFromHandle = self.projTree.getTreeFromHandle
self.emptyTrash = self.projTree.emptyTrash self.emptyTrash = self.projTree.emptyTrash
self.requestDeleteItem = self.projTree.requestDeleteItem self.requestDeleteItem = self.projTree.requestDeleteItem
self.setTreeItemValues = self.projTree.setTreeItemValues self.setTreeItemValues = self.projTree.setTreeItemValues
@@ -161,6 +161,16 @@ class GuiProjectView(QWidget):
""" """
return self.projTree.hasFocus() return self.projTree.hasFocus()
def renameTreeItem(self, tHandle=None):
"""External request to rename an item or the currently selected
item. This is triggered by the global menu or keyboard shortcut.
"""
if tHandle is None:
tHandle = self.projTree.getSelectedHandle()
if tHandle:
return self.projTree.renameTreeItem(tHandle)
return
## ##
# Public Slots # Public Slots
## ##
@@ -1111,10 +1121,12 @@ class GuiProjectTree(QTreeWidget):
open a context menu in-place. open a context menu in-place.
""" """
tItem = None tItem = None
hasChild = False
selItem = self.itemAt(clickPos) selItem = self.itemAt(clickPos)
if isinstance(selItem, QTreeWidgetItem): if isinstance(selItem, QTreeWidgetItem):
tHandle = selItem.data(self.C_NAME, Qt.UserRole) tHandle = selItem.data(self.C_NAME, Qt.UserRole)
tItem = self.theProject.tree[tHandle] tItem = self.theProject.tree[tHandle]
hasChild = selItem.childCount() > 0
if tItem is None: if tItem is None:
logger.debug("No item found") logger.debug("No item found")
@@ -1128,9 +1140,8 @@ class GuiProjectTree(QTreeWidget):
trashHandle = self.theProject.tree.trashRoot() trashHandle = self.theProject.tree.trashRoot()
if tItem.itemHandle == trashHandle and trashHandle is not None: if tItem.itemHandle == trashHandle and trashHandle is not None:
# The trash folder only has one option # The trash folder only has one option
ctxMenu.addAction( aEmptyTrash = ctxMenu.addAction(self.tr("Empty Trash"))
self.tr("Empty Trash"), lambda: self.emptyTrash() aEmptyTrash.triggered.connect(lambda: self.emptyTrash())
)
ctxMenu.exec_(self.viewport().mapToGlobal(clickPos)) ctxMenu.exec_(self.viewport().mapToGlobal(clickPos))
return True return True
@@ -1140,15 +1151,14 @@ class GuiProjectTree(QTreeWidget):
isRoot = tItem.isRootType() isRoot = tItem.isRootType()
isFolder = tItem.isFolderType() isFolder = tItem.isFolderType()
isFile = tItem.isFileType() isFile = tItem.isFileType()
hasChild = selItem.childCount() > 0
if isFile: if isFile:
ctxMenu.addAction( aOpenDoc = ctxMenu.addAction(self.tr("Open Document"))
self.tr("Open Document"), aOpenDoc.triggered.connect(
lambda: self.projView.openDocumentRequest.emit(tHandle, nwDocMode.EDIT, -1, "") lambda: self.projView.openDocumentRequest.emit(tHandle, nwDocMode.EDIT, -1, "")
) )
ctxMenu.addAction( aViewDoc = ctxMenu.addAction(self.tr("View Document"))
self.tr("View Document"), aViewDoc.triggered.connect(
lambda: self.projView.openDocumentRequest.emit(tHandle, nwDocMode.VIEW, -1, "") lambda: self.projView.openDocumentRequest.emit(tHandle, nwDocMode.VIEW, -1, "")
) )
ctxMenu.addSeparator() ctxMenu.addSeparator()
@@ -1156,29 +1166,40 @@ class GuiProjectTree(QTreeWidget):
# Edit Item Settings # Edit Item Settings
# ================== # ==================
ctxMenu.addAction( aLabel = ctxMenu.addAction(self.tr("Change Label"))
self.tr("Change Label"), lambda: self.renameTreeItem(tHandle) aLabel.triggered.connect(lambda: self.renameTreeItem(tHandle))
)
if isFile: if isFile:
ctxMenu.addAction( aActive = ctxMenu.addAction(self.tr("Toggle Active"))
self.tr("Toggle Active"), lambda: self._toggleItemActive(tHandle) aActive.triggered.connect(lambda: self._toggleItemActive(tHandle))
)
checkMark = f" ({nwUnicode.U_CHECK})"
if tItem.isNovelLike(): if tItem.isNovelLike():
mStatus = ctxMenu.addMenu(self.tr("Set Status to ...")) mStatus = ctxMenu.addMenu(self.tr("Set Status to ..."))
for n, (key, entry) in enumerate(self.theProject.statusItems.items()): for n, (key, entry) in enumerate(self.theProject.statusItems.items()):
aStatus = mStatus.addAction(entry["icon"], entry["name"]) entryName = entry["name"] + (checkMark if tItem.itemStatus == key else "")
aStatus = mStatus.addAction(entry["icon"], entryName)
aStatus.triggered.connect( aStatus.triggered.connect(
lambda n, key=key: self._changeItemStatus(tHandle, key) lambda n, key=key: self._changeItemStatus(tHandle, key)
) )
mStatus.addSeparator()
aManage1 = mStatus.addAction("Manage Labels ...")
aManage1.triggered.connect(
lambda: self.projView.projectSettingsRequest.emit(GuiProjectSettings.TAB_STATUS)
)
else: else:
mImport = ctxMenu.addMenu(self.tr("Set Importance to ...")) mImport = ctxMenu.addMenu(self.tr("Set Importance to ..."))
for n, (key, entry) in enumerate(self.theProject.importItems.items()): for n, (key, entry) in enumerate(self.theProject.importItems.items()):
aImport = mImport.addAction(entry["icon"], entry["name"]) entryName = entry["name"] + (checkMark if tItem.itemImport == key else "")
aImport = mImport.addAction(entry["icon"], entryName)
aImport.triggered.connect( aImport.triggered.connect(
lambda n, key=key: self._changeItemImport(tHandle, key) lambda n, key=key: self._changeItemImport(tHandle, key)
) )
mImport.addSeparator()
aManage2 = mImport.addAction("Manage Labels ...")
aManage2.triggered.connect(
lambda: self.projView.projectSettingsRequest.emit(GuiProjectSettings.TAB_IMPORT)
)
# Transform Item # Transform Item
# ============== # ==============
@@ -1193,38 +1214,30 @@ class GuiProjectTree(QTreeWidget):
isNoteFile = isFile and tItem.isNoteLayout() isNoteFile = isFile and tItem.isNoteLayout()
if (isNoteFile or isFolder) and tItem.documentAllowed(): if (isNoteFile or isFolder) and tItem.documentAllowed():
mTrans.addAction( aConvert1 = mTrans.addAction(self.tr("Convert to {0}").format(trDoc))
self.tr("Convert to {0}").format(trDoc), aConvert1.triggered.connect(
lambda: self._changeItemLayout(tHandle, nwItemLayout.DOCUMENT) lambda: self._changeItemLayout(tHandle, nwItemLayout.DOCUMENT)
) )
if isDocFile or isFolder: if isDocFile or isFolder:
mTrans.addAction( aConvert2 = mTrans.addAction(self.tr("Convert to {0}").format(trNote))
self.tr("Convert to {0}").format(trNote), aConvert2.triggered.connect(
lambda: self._changeItemLayout(tHandle, nwItemLayout.NOTE) lambda: self._changeItemLayout(tHandle, nwItemLayout.NOTE)
) )
if hasChild and isFile: if hasChild and isFile:
mTrans.addAction( aMerge1 = mTrans.addAction(self.tr("Merge Child Items into Self"))
self.tr("Merge Child Items into Self"), aMerge1.triggered.connect(lambda: self._mergeDocuments(tHandle, False))
lambda: self._mergeDocuments(tHandle, False) aMerge2 = mTrans.addAction(self.tr("Merge Child Items into New"))
) aMerge2.triggered.connect(lambda: self._mergeDocuments(tHandle, True))
mTrans.addAction(
self.tr("Merge Child Items into New"),
lambda: self._mergeDocuments(tHandle, True)
)
if hasChild and isFolder: if hasChild and isFolder:
mTrans.addAction( aMerge3 = mTrans.addAction(self.tr("Merge Documents in Folder"))
self.tr("Merge Documents in Folder"), aMerge3.triggered.connect(lambda: self._mergeDocuments(tHandle, True))
lambda: self._mergeDocuments(tHandle, True)
)
if isFile: if isFile:
mTrans.addAction( aSplit1 = mTrans.addAction(self.tr("Split Document by Headers"))
self.tr("Split Document by Headers"), aSplit1.triggered.connect(lambda: self._splitDocument(tHandle))
lambda: self._splitDocument(tHandle)
)
# Expand/Collapse/Delete # Expand/Collapse/Delete
# ====================== # ======================
@@ -1232,23 +1245,17 @@ class GuiProjectTree(QTreeWidget):
ctxMenu.addSeparator() ctxMenu.addSeparator()
if hasChild: if hasChild:
ctxMenu.addAction( aExpand = ctxMenu.addAction(self.tr("Expand All"))
self.tr("Expand All"), aExpand.triggered.connect(lambda: self.setExpandedFromHandle(tHandle, True))
lambda: self.setExpandedFromHandle(tHandle, True) aCollapse = ctxMenu.addAction(self.tr("Collapse All"))
) aCollapse.triggered.connect(lambda: self.setExpandedFromHandle(tHandle, False))
ctxMenu.addAction(
self.tr("Collapse All"),
lambda: self.setExpandedFromHandle(tHandle, False)
)
if tItem.itemClass == nwItemClass.TRASH or isRoot or (isFolder and not hasChild): if tItem.itemClass == nwItemClass.TRASH or isRoot or (isFolder and not hasChild):
ctxMenu.addAction( aDelete = ctxMenu.addAction(self.tr("Delete Permanently"))
self.tr("Delete Permanently"), lambda: self.permanentlyDeleteItem(tHandle) aDelete.triggered.connect(lambda: self.permanentlyDeleteItem(tHandle))
)
else: else:
ctxMenu.addAction( aMoveTrash = ctxMenu.addAction(self.tr("Move to Trash"))
self.tr("Move to Trash"), lambda: self.moveItemToTrash(tHandle) aMoveTrash.triggered.connect(lambda: self.moveItemToTrash(tHandle))
)
# Show Context Menu # Show Context Menu
ctxMenu.exec_(self.viewport().mapToGlobal(clickPos)) ctxMenu.exec_(self.viewport().mapToGlobal(clickPos))
+8 -10
View File
@@ -208,6 +208,7 @@ class GuiMain(QMainWindow):
self.projView.rootFolderChanged.connect(self.outlineView.updateRootItem) self.projView.rootFolderChanged.connect(self.outlineView.updateRootItem)
self.projView.rootFolderChanged.connect(self.novelView.updateRootItem) self.projView.rootFolderChanged.connect(self.novelView.updateRootItem)
self.projView.rootFolderChanged.connect(self.projView.updateRootItem) self.projView.rootFolderChanged.connect(self.projView.updateRootItem)
self.projView.projectSettingsRequest.connect(self.showProjectSettingsDialog)
self.novelView.selectedItemChanged.connect(self.itemDetails.updateViewBox) self.novelView.selectedItemChanged.connect(self.itemDetails.updateViewBox)
self.novelView.openDocumentRequest.connect(self._openDocument) self.novelView.openDocumentRequest.connect(self._openDocument)
@@ -806,15 +807,11 @@ class GuiMain(QMainWindow):
logger.error("No project open") logger.error("No project open")
return False return False
if tHandle is None: if tHandle is None and (self.docEditor.anyFocus() or self.isFocusMode):
if self.docEditor.anyFocus() or self.isFocusMode: tHandle = self.docEditor.docHandle()
tHandle = self.docEditor.docHandle() self.projView.renameTreeItem(tHandle)
else:
tHandle = self.projView.getSelectedHandle()
if tHandle:
return self.projView.renameTreeItem(tHandle)
return False return True
def rebuildTrees(self): def rebuildTrees(self):
"""Rebuild the project tree. """Rebuild the project tree.
@@ -921,14 +918,15 @@ class GuiMain(QMainWindow):
return return
def showProjectSettingsDialog(self): @pyqtSlot(int)
def showProjectSettingsDialog(self, focusTab=GuiProjectSettings.TAB_MAIN):
"""Open the project settings dialog. """Open the project settings dialog.
""" """
if not self.hasProject: if not self.hasProject:
logger.error("No project open") logger.error("No project open")
return False return False
dlgProj = GuiProjectSettings(self) dlgProj = GuiProjectSettings(self, focusTab=focusTab)
dlgProj.exec_() dlgProj.exec_()
if dlgProj.result() == QDialog.Accepted: if dlgProj.result() == QDialog.Accepted:
@@ -1,83 +0,0 @@
<?xml version='1.0' encoding='utf-8'?>
<novelWriterXML appVersion="2.0-rc1" hexVersion="0x020000c1" fileVersion="1.4" timeStamp="2022-10-20 18:21:59">
<project>
<name>Project Name</name>
<title>Project Title</title>
<author>Jane Doe</author>
<author>John Doh</author>
<saveCount>1</saveCount>
<autoCount>1</autoCount>
<editTime>0</editTime>
</project>
<settings>
<doBackup>True</doBackup>
<language>None</language>
<spellCheck>False</spellCheck>
<spellLang>en</spellLang>
<lastEdited>None</lastEdited>
<lastViewed>None</lastViewed>
<lastNovel>None</lastNovel>
<lastOutline>None</lastOutline>
<lastWordCount>9</lastWordCount>
<novelWordCount>9</novelWordCount>
<notesWordCount>0</notesWordCount>
<autoReplace>
<entry key="A">B</entry>
<entry key="C">D</entry>
<entry key="This">With This Stuff</entry>
</autoReplace>
<titleFormat>
<title>%title%</title>
<chapter>%title%</chapter>
<unnumbered>%title%</unnumbered>
<scene>* * *</scene>
<section></section>
</titleFormat>
<status>
<entry key="s000000" count="5" red="100" green="100" blue="100">New</entry>
<entry key="s000001" count="0" red="200" green="50" blue="0">Note</entry>
<entry key="s000003" count="0" red="50" green="200" blue="0">Finished</entry>
<entry key="s000010" count="0" red="20" green="30" blue="40">Final</entry>
</status>
<importance>
<entry key="i000004" count="3" red="100" green="100" blue="100">New</entry>
<entry key="i000005" count="0" red="200" green="50" blue="0">Minor</entry>
<entry key="i000006" count="0" red="200" green="150" blue="0">Major</entry>
<entry key="i000011" count="0" red="100" green="100" blue="100">Final</entry>
</importance>
</settings>
<content count="8">
<item handle="0000000000008" parent="None" root="0000000000008" order="0" type="ROOT" class="NOVEL">
<meta expanded="False"/>
<name status="s000000" import="i000004">Novel</name>
</item>
<item handle="000000000000c" parent="0000000000008" root="0000000000008" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="False" mainHeading="H1" charCount="20" wordCount="5" paraCount="1" cursorPos="0"/>
<name status="s000000" import="i000004" active="True">Title Page</name>
</item>
<item handle="000000000000d" parent="0000000000008" root="0000000000008" order="1" type="FOLDER" class="NOVEL">
<meta expanded="False"/>
<name status="s000000" import="i000004">New Chapter</name>
</item>
<item handle="000000000000e" parent="000000000000d" root="0000000000008" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="False" mainHeading="H2" charCount="11" wordCount="2" paraCount="0" cursorPos="0"/>
<name status="s000000" import="i000004" active="True">New Chapter</name>
</item>
<item handle="000000000000f" parent="000000000000d" root="0000000000008" order="1" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="False" mainHeading="H3" charCount="9" wordCount="2" paraCount="0" cursorPos="0"/>
<name status="s000000" import="i000004" active="True">New Scene</name>
</item>
<item handle="0000000000009" parent="None" root="0000000000009" order="1" type="ROOT" class="PLOT">
<meta expanded="False"/>
<name status="s000000" import="i000004">Plot</name>
</item>
<item handle="000000000000a" parent="None" root="000000000000a" order="2" type="ROOT" class="CHARACTER">
<meta expanded="False"/>
<name status="s000000" import="i000004">Characters</name>
</item>
<item handle="000000000000b" parent="None" root="000000000000b" order="3" type="ROOT" class="WORLD">
<meta expanded="False"/>
<name status="s000000" import="i000004">World</name>
</item>
</content>
</novelWriterXML>
+334 -158
View File
@@ -19,56 +19,46 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>. along with this program. If not, see <https://www.gnu.org/licenses/>.
""" """
import os
import pytest import pytest
from shutil import copyfile from novelwriter.enum import nwItemType
from tools import cmpFiles, getGuiItem, buildTestProject from tools import C, getGuiItem, buildTestProject
from PyQt5.QtGui import QColor from PyQt5.QtGui import QColor
from PyQt5.QtCore import Qt from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import QDialog, QAction, QMessageBox, QColorDialog from PyQt5.QtWidgets import QDialog, QAction, QMessageBox, QColorDialog
from novelwriter.dialogs.editlabel import GuiEditLabel
from novelwriter.dialogs.projsettings import GuiProjectSettings from novelwriter.dialogs.projsettings import GuiProjectSettings
keyDelay = 2 keyDelay = 2
typeDelay = 1 typeDelay = 1
stepDelay = 20 stepDelay = 20
statusKeys = ["s000000", "s000001", "s000002", "s000003"]
importKeys = ["i000004", "i000005", "i000006", "i000007"]
@pytest.mark.gui @pytest.mark.gui
def testDlgProjSettings_Dialog( def testDlgProjSettings_Dialog(qtbot, monkeypatch, nwGUI):
qtbot, monkeypatch, nwGUI, fncDir, fncProj, outDir, refDir, mockRnd """Test the main dialog class. Saving settings is not tested in this
): test, but are instead tested in the individual tab tests.
"""Test the full project settings dialog.
""" """
projFile = os.path.join(fncProj, "nwProject.nwx")
testFile = os.path.join(outDir, "guiProjSettings_Dialog_nwProject.nwx")
compFile = os.path.join(refDir, "guiProjSettings_Dialog_nwProject.nwx")
# Block message box # Block message box
monkeypatch.setattr(QMessageBox, "question", lambda *a: QMessageBox.Yes) monkeypatch.setattr(QMessageBox, "question", lambda *a: QMessageBox.Yes)
monkeypatch.setattr(QMessageBox, "critical", lambda *a: QMessageBox.Yes) monkeypatch.setattr(QMessageBox, "critical", lambda *a: QMessageBox.Yes)
# Block the GUI blocking thread
monkeypatch.setattr(GuiProjectSettings, "exec_", lambda *a: None)
monkeypatch.setattr(GuiProjectSettings, "result", lambda *a: QDialog.Accepted)
monkeypatch.setattr(GuiProjectSettings, "spellChanged", lambda *a: True)
# Check that we cannot open when there is no project # Check that we cannot open when there is no project
nwGUI.mainMenu.aProjectSettings.activate(QAction.Trigger) nwGUI.mainMenu.aProjectSettings.activate(QAction.Trigger)
assert getGuiItem("GuiProjectSettings") is None assert getGuiItem("GuiProjectSettings") is None
# Create new project # Pretend we have a project
buildTestProject(nwGUI, fncProj) nwGUI.hasProject = True
nwGUI.mainConf.backupPath = fncDir
nwGUI.theProject.setSpellLang("en") nwGUI.theProject.setSpellLang("en")
nwGUI.theProject.setBookAuthors("Jane Smith\nJohn Smith")
nwGUI.theProject.setAutoReplace({"A": "B", "C": "D"})
# Get the dialog object # Get the dialog object
monkeypatch.setattr(GuiProjectSettings, "exec_", lambda *a: None)
monkeypatch.setattr(GuiProjectSettings, "result", lambda *a: QDialog.Accepted)
monkeypatch.setattr(nwGUI.docEditor.spEnchant, "listDictionaries", lambda: [("en", "none")])
nwGUI.mainMenu.aProjectSettings.activate(QAction.Trigger) nwGUI.mainMenu.aProjectSettings.activate(QAction.Trigger)
qtbot.waitUntil(lambda: getGuiItem("GuiProjectSettings") is not None, timeout=1000) qtbot.waitUntil(lambda: getGuiItem("GuiProjectSettings") is not None, timeout=1000)
@@ -77,83 +67,189 @@ def testDlgProjSettings_Dialog(
projEdit.show() projEdit.show()
qtbot.addWidget(projEdit) qtbot.addWidget(projEdit)
# Switch Tabs
projEdit._focusTab(GuiProjectSettings.TAB_REPLACE)
assert projEdit._tabBox.currentWidget() == projEdit.tabReplace
projEdit._focusTab(GuiProjectSettings.TAB_IMPORT)
assert projEdit._tabBox.currentWidget() == projEdit.tabImport
projEdit._focusTab(GuiProjectSettings.TAB_STATUS)
assert projEdit._tabBox.currentWidget() == projEdit.tabStatus
projEdit._focusTab(GuiProjectSettings.TAB_MAIN)
assert projEdit._tabBox.currentWidget() == projEdit.tabMain
# Clean Up
projEdit._doClose()
# qtbot.stop()
# END Test testDlgProjSettings_Dialog
@pytest.mark.gui
def testDlgProjSettings_Main(qtbot, monkeypatch, nwGUI, fncDir, fncProj, mockRnd):
"""Test the main tab of the project settings dialog.
"""
# Block message box
monkeypatch.setattr(QMessageBox, "question", lambda *a: QMessageBox.Yes)
monkeypatch.setattr(QMessageBox, "critical", lambda *a: QMessageBox.Yes)
# Mock components
monkeypatch.setattr(nwGUI.docEditor.spEnchant, "listDictionaries", lambda: [("en", "none")])
# Create new project
buildTestProject(nwGUI, fncProj)
mockRnd.reset()
nwGUI.mainConf.backupPath = fncDir
# Set some values
theProject = nwGUI.theProject
theProject.setSpellLang("en")
theProject.setBookAuthors("Jane Smith\nJohn Smith")
theProject.setAutoReplace({"A": "B", "C": "D"})
# Create Dialog
projSettings = GuiProjectSettings(nwGUI, GuiProjectSettings.TAB_MAIN)
projSettings.show()
qtbot.addWidget(projSettings)
# Settings Tab # Settings Tab
# ============ # ============
assert projEdit.tabMain.editName.text() == "New Project" tabMain = projSettings.tabMain
assert projEdit.tabMain.editTitle.text() == "New Novel"
assert projEdit.tabMain.editAuthors.toPlainText() == "Jane Smith\nJohn Smith" assert tabMain.editName.text() == "New Project"
assert projEdit.tabMain.spellLang.currentData() == "en" assert tabMain.editTitle.text() == "New Novel"
assert projEdit.tabMain.doBackup.isChecked() is False assert tabMain.editAuthors.toPlainText() == "Jane Smith\nJohn Smith"
assert tabMain.spellLang.currentData() == "en"
assert tabMain.doBackup.isChecked() is False
qtbot.wait(stepDelay) qtbot.wait(stepDelay)
projEdit.tabMain.editName.setText("") tabMain.editName.setText("")
for c in "Project Name": for c in "Project Name":
qtbot.keyClick(projEdit.tabMain.editName, c, delay=typeDelay) qtbot.keyClick(tabMain.editName, c, delay=typeDelay)
projEdit.tabMain.editTitle.setText("") tabMain.editTitle.setText("")
for c in "Project Title": for c in "Project Title":
qtbot.keyClick(projEdit.tabMain.editTitle, c, delay=typeDelay) qtbot.keyClick(tabMain.editTitle, c, delay=typeDelay)
projEdit.tabMain.editAuthors.clear() tabMain.editAuthors.clear()
for c in "Jane Doe": for c in "Jane Doe":
qtbot.keyClick(projEdit.tabMain.editAuthors, c, delay=typeDelay) qtbot.keyClick(tabMain.editAuthors, c, delay=typeDelay)
qtbot.keyClick(projEdit.tabMain.editAuthors, Qt.Key_Return, delay=keyDelay) qtbot.keyClick(tabMain.editAuthors, Qt.Key_Return, delay=keyDelay)
for c in "John Doh": for c in "John Doh":
qtbot.keyClick(projEdit.tabMain.editAuthors, c, delay=typeDelay) qtbot.keyClick(tabMain.editAuthors, c, delay=typeDelay)
qtbot.keyClick(projEdit.tabMain.editAuthors, Qt.Key_Return, delay=keyDelay) qtbot.keyClick(tabMain.editAuthors, Qt.Key_Return, delay=keyDelay)
qtbot.wait(stepDelay) qtbot.wait(stepDelay)
assert projEdit.tabMain.editName.text() == "Project Name" assert tabMain.editName.text() == "Project Name"
assert projEdit.tabMain.editTitle.text() == "Project Title" assert tabMain.editTitle.text() == "Project Title"
assert projEdit.tabMain.editAuthors.toPlainText() == "Jane Doe\nJohn Doh\n" assert tabMain.editAuthors.toPlainText() == "Jane Doe\nJohn Doh\n"
assert projSettings.spellChanged is False
projSettings._doSave()
assert theProject.projName == "Project Name"
assert theProject.bookTitle == "Project Title"
assert theProject.bookAuthors == ["Jane Doe", "John Doh"]
# Clean up
projSettings._doClose()
# qtbot.stop()
# END Test testDlgProjSettings_Main
@pytest.mark.gui
def testDlgProjSettings_StatusImport(qtbot, monkeypatch, nwGUI, fncDir, fncProj, mockRnd):
"""Test the status and importance tabs of the project settings
dialog.
"""
# Block message box
monkeypatch.setattr(QMessageBox, "question", lambda *a: QMessageBox.Yes)
monkeypatch.setattr(QMessageBox, "critical", lambda *a: QMessageBox.Yes)
monkeypatch.setattr(GuiEditLabel, "getLabel", lambda *a, text: (text, True))
# Mock components
monkeypatch.setattr(nwGUI.docEditor.spEnchant, "listDictionaries", lambda: [("en", "none")])
# Create new project
mockRnd.reset()
buildTestProject(nwGUI, fncProj)
nwGUI.mainConf.backupPath = fncDir
# Set some values
theProject = nwGUI.theProject
theProject.tree[C.hTitlePage].setStatus(C.sFinished)
theProject.tree[C.hChapterDoc].setStatus(C.sDraft)
theProject.tree[C.hSceneDoc].setStatus(C.sDraft)
nwGUI.projView.projTree.setSelectedHandle(C.hPlotRoot)
nwGUI.projView.projTree.newTreeItem(nwItemType.FILE, hLevel=1, isNote=True)
nwGUI.projView.projTree.setSelectedHandle(C.hCharRoot)
nwGUI.projView.projTree.newTreeItem(nwItemType.FILE, hLevel=1, isNote=True)
nwGUI.projView.projTree.setSelectedHandle(C.hWorldRoot)
nwGUI.projView.projTree.newTreeItem(nwItemType.FILE, hLevel=1, isNote=True)
hPlotNote = "0000000000010"
hCharNote = "0000000000011"
hWorldNote = "0000000000012"
theProject.tree[hPlotNote].setImport(C.iMajor)
theProject.tree[hCharNote].setImport(C.iMajor)
theProject.tree[hWorldNote].setImport(C.iMain)
# Create Dialog
projSettings = GuiProjectSettings(nwGUI, GuiProjectSettings.TAB_STATUS)
projSettings.show()
qtbot.addWidget(projSettings)
# Status Tab # Status Tab
# ========== # ==========
projEdit._tabBox.setCurrentWidget(projEdit.tabStatus) tabStatus = projSettings.tabStatus
assert projEdit.tabStatus.colChanged is False assert tabStatus.colChanged is False
assert projEdit.tabStatus.getNewList() == ([], []) assert tabStatus.getNewList() == ([], [])
assert projEdit.tabStatus.listBox.topLevelItemCount() == 4 assert tabStatus.listBox.topLevelItemCount() == 4
# Can't delete the first item (it's in use) # Can't delete the first item (it's in use)
projEdit.tabStatus.listBox.clearSelection() tabStatus.listBox.clearSelection()
projEdit.tabStatus.listBox.topLevelItem(0).setSelected(True) tabStatus.listBox.setCurrentItem(tabStatus.listBox.topLevelItem(0))
qtbot.mouseClick(projEdit.tabStatus.delButton, Qt.LeftButton) qtbot.mouseClick(tabStatus.delButton, Qt.LeftButton)
assert projEdit.tabStatus.listBox.topLevelItemCount() == 4 assert tabStatus.listBox.topLevelItemCount() == 4
# Can delete the third item # Can delete the second item
projEdit.tabStatus.listBox.clearSelection() tabStatus.listBox.clearSelection()
projEdit.tabStatus.listBox.topLevelItem(2).setSelected(True) tabStatus.listBox.setCurrentItem(tabStatus.listBox.topLevelItem(1))
qtbot.mouseClick(projEdit.tabStatus.delButton, Qt.LeftButton) qtbot.mouseClick(tabStatus.delButton, Qt.LeftButton)
assert projEdit.tabStatus.listBox.topLevelItemCount() == 3 assert tabStatus.listBox.topLevelItemCount() == 3
# Add a new item # Add a new item
monkeypatch.setattr(QColorDialog, "getColor", lambda *a: QColor(20, 30, 40)) with monkeypatch.context() as mp:
qtbot.mouseClick(projEdit.tabStatus.addButton, Qt.LeftButton) mp.setattr(QColorDialog, "getColor", lambda *a: QColor(20, 30, 40))
projEdit.tabStatus.listBox.topLevelItem(3).setSelected(True) qtbot.mouseClick(tabStatus.addButton, Qt.LeftButton)
for n in range(8): tabStatus.listBox.setCurrentItem(tabStatus.listBox.topLevelItem(3))
qtbot.keyClick(projEdit.tabStatus.editName, Qt.Key_Backspace, delay=typeDelay) for _ in range(8):
for c in "Final": qtbot.keyClick(tabStatus.editName, Qt.Key_Backspace, delay=typeDelay)
qtbot.keyClick(projEdit.tabStatus.editName, c, delay=typeDelay) for c in "Final":
qtbot.mouseClick(projEdit.tabStatus.colButton, Qt.LeftButton) qtbot.keyClick(tabStatus.editName, c, delay=typeDelay)
qtbot.mouseClick(projEdit.tabStatus.saveButton, Qt.LeftButton) qtbot.mouseClick(tabStatus.colButton, Qt.LeftButton)
assert projEdit.tabStatus.listBox.topLevelItemCount() == 4 qtbot.mouseClick(tabStatus.saveButton, Qt.LeftButton)
qtbot.wait(stepDelay) assert tabStatus.listBox.topLevelItemCount() == 4
assert projEdit.tabStatus.colChanged is True assert tabStatus.colChanged is True
assert projEdit.tabStatus.getNewList() == ( assert tabStatus.getNewList() == (
[ [
{ {
"key": statusKeys[0], "key": C.sNew,
"name": "New", "name": "New",
"cols": (100, 100, 100) "cols": (100, 100, 100)
}, { }, {
"key": statusKeys[1], "key": C.sDraft,
"name": "Note", "name": "Draft",
"cols": (200, 50, 0) "cols": (200, 150, 0)
}, { }, {
"key": statusKeys[3], "key": C.sFinished,
"name": "Finished", "name": "Finished",
"cols": (50, 200, 0) "cols": (50, 200, 0)
}, { }, {
@@ -162,121 +258,201 @@ def testDlgProjSettings_Dialog(
"cols": (20, 30, 40) "cols": (20, 30, 40)
} }
], [ ], [
statusKeys[2] # Deleted item C.sNote # Deleted item
] ]
) )
# Move items # Move items, none selected -> no change
projEdit.tabStatus.listBox.clearSelection() tabStatus.listBox.clearSelection()
projEdit.tabStatus._moveItem(1) tabStatus._moveItem(1)
assert [x["key"] for x in projEdit.tabStatus.getNewList()[0]] == [ assert [x["key"] for x in tabStatus.getNewList()[0]] == [
statusKeys[0], statusKeys[1], statusKeys[3], None C.sNew, C.sDraft, C.sFinished, None
] ]
projEdit.tabStatus.listBox.clearSelection() # Move items, first selected, move up -> no change
projEdit.tabStatus.listBox.topLevelItem(0).setSelected(True) tabStatus.listBox.clearSelection()
projEdit.tabStatus._moveItem(-1) tabStatus.listBox.setCurrentItem(tabStatus.listBox.topLevelItem(0))
assert [x["key"] for x in projEdit.tabStatus.getNewList()[0]] == [ tabStatus._moveItem(-1)
statusKeys[0], statusKeys[1], statusKeys[3], None assert [x["key"] for x in tabStatus.getNewList()[0]] == [
C.sNew, C.sDraft, C.sFinished, None
] ]
projEdit.tabStatus.listBox.clearSelection() # Move items, last selected, move up -> allowed
projEdit.tabStatus.listBox.topLevelItem(3).setSelected(True) tabStatus.listBox.clearSelection()
projEdit.tabStatus._moveItem(-1) tabStatus.listBox.setCurrentItem(tabStatus.listBox.topLevelItem(3))
assert [x["key"] for x in projEdit.tabStatus.getNewList()[0]] == [ tabStatus._moveItem(-1)
statusKeys[0], statusKeys[1], None, statusKeys[3] assert [x["key"] for x in tabStatus.getNewList()[0]] == [
C.sNew, C.sDraft, None, C.sFinished
] ]
projEdit.tabStatus._moveItem(1)
assert [x["key"] for x in projEdit.tabStatus.getNewList()[0]] == [ # Move items, same selected, move down -> allowed
statusKeys[0], statusKeys[1], statusKeys[3], None tabStatus._moveItem(1)
assert [x["key"] for x in tabStatus.getNewList()[0]] == [
C.sNew, C.sDraft, C.sFinished, None
] ]
# Importance Tab # Importance Tab
# ============== # ==============
projEdit._tabBox.setCurrentWidget(projEdit.tabImport) tabImport = projSettings.tabImport
projEdit.tabStatus.listBox.clearSelection() projSettings._focusTab(GuiProjectSettings.TAB_IMPORT)
projEdit.tabImport.listBox.topLevelItem(3).setSelected(True)
qtbot.mouseClick(projEdit.tabImport.delButton, Qt.LeftButton) # Delete unused entry
qtbot.mouseClick(projEdit.tabImport.addButton, Qt.LeftButton) tabImport.listBox.clearSelection()
projEdit.tabStatus.listBox.clearSelection() tabImport.listBox.setCurrentItem(tabImport.listBox.topLevelItem(1))
projEdit.tabImport.listBox.topLevelItem(3).setSelected(True) qtbot.mouseClick(tabImport.delButton, Qt.LeftButton)
for n in range(8): assert tabImport.listBox.topLevelItemCount() == 3
qtbot.keyClick(projEdit.tabImport.editName, Qt.Key_Backspace, delay=typeDelay)
for c in "Final": # Add a new entry
qtbot.keyClick(projEdit.tabImport.editName, c, delay=typeDelay) with monkeypatch.context() as mp:
qtbot.mouseClick(projEdit.tabImport.saveButton, Qt.LeftButton) mp.setattr(QColorDialog, "getColor", lambda *a: QColor(20, 30, 40))
qtbot.wait(stepDelay) qtbot.mouseClick(tabImport.addButton, Qt.LeftButton)
tabImport.listBox.clearSelection()
tabImport.listBox.setCurrentItem(tabImport.listBox.topLevelItem(3))
for _ in range(8):
qtbot.keyClick(tabImport.editName, Qt.Key_Backspace, delay=typeDelay)
for c in "Final":
qtbot.keyClick(tabImport.editName, c, delay=typeDelay)
qtbot.mouseClick(tabImport.colButton, Qt.LeftButton)
qtbot.mouseClick(tabImport.saveButton, Qt.LeftButton)
assert tabImport.listBox.topLevelItemCount() == 4
assert tabImport.colChanged is True
assert tabImport.getNewList() == (
[
{
"key": C.iNew,
"name": "New",
"cols": (100, 100, 100)
}, {
"key": C.iMajor,
"name": "Major",
"cols": (200, 150, 0)
}, {
"key": C.iMain,
"name": "Main",
"cols": (50, 200, 0)
}, {
"key": None,
"name": "Final",
"cols": (20, 30, 40)
}
], [
C.iMinor # Deleted item
]
)
# Check Project
projSettings._doSave()
statusItems = dict(theProject.statusItems.items())
assert statusItems[C.sNew]["name"] == "New"
assert statusItems[C.sDraft]["name"] == "Draft"
assert statusItems[C.sFinished]["name"] == "Finished"
assert statusItems["s000013"]["name"] == "Final"
importItems = dict(theProject.importItems.items())
assert importItems[C.iNew]["name"] == "New"
assert importItems[C.iMajor]["name"] == "Major"
assert importItems[C.iMain]["name"] == "Main"
assert importItems["i000014"]["name"] == "Final"
# Clean up
# qtbot.stop()
projSettings._doClose()
# END Test testDlgProjSettings_StatusImport
@pytest.mark.gui
def testDlgProjSettings_Replace(qtbot, monkeypatch, nwGUI, fncDir, fncProj, mockRnd):
"""Test the auto-replace tab of the project settings dialog.
"""
# Block message box
monkeypatch.setattr(QMessageBox, "question", lambda *a: QMessageBox.Yes)
monkeypatch.setattr(QMessageBox, "critical", lambda *a: QMessageBox.Yes)
monkeypatch.setattr(GuiEditLabel, "getLabel", lambda *a, text: (text, True))
# Mock components
monkeypatch.setattr(nwGUI.docEditor.spEnchant, "listDictionaries", lambda: [("en", "none")])
# Create new project
mockRnd.reset()
buildTestProject(nwGUI, fncProj)
nwGUI.mainConf.backupPath = fncDir
# Set some values
theProject = nwGUI.theProject
theProject.autoReplace = {
"A": "B", "C": "D"
}
# Create Dialog
projSettings = GuiProjectSettings(nwGUI, GuiProjectSettings.TAB_REPLACE)
projSettings.show()
qtbot.addWidget(projSettings)
# Auto-Replace Tab # Auto-Replace Tab
# ================ # ================
qtbot.wait(stepDelay) tabReplace = projSettings.tabReplace
projEdit._tabBox.setCurrentWidget(projEdit.tabReplace)
assert projEdit.tabReplace.listBox.topLevelItem(0).text(0) == "<A>" assert tabReplace.listBox.topLevelItem(0).text(0) == "<A>"
assert projEdit.tabReplace.listBox.topLevelItem(0).text(1) == "B" assert tabReplace.listBox.topLevelItem(0).text(1) == "B"
assert projEdit.tabReplace.listBox.topLevelItem(1).text(0) == "<C>" assert tabReplace.listBox.topLevelItem(1).text(0) == "<C>"
assert projEdit.tabReplace.listBox.topLevelItem(1).text(1) == "D" assert tabReplace.listBox.topLevelItem(1).text(1) == "D"
assert tabReplace.listBox.topLevelItemCount() == 2
qtbot.mouseClick(projEdit.tabReplace.addButton, Qt.LeftButton) # Nothing to save or delete
projEdit.tabReplace.listBox.topLevelItem(2).setSelected(True) tabReplace.listBox.clearSelection()
projEdit.tabReplace.editKey.setText("") assert tabReplace._saveEntry() is False
assert tabReplace._delEntry() is False
assert tabReplace.listBox.topLevelItemCount() == 2
# Create a new entry
qtbot.mouseClick(tabReplace.addButton, Qt.LeftButton)
assert tabReplace.listBox.topLevelItemCount() == 3
assert tabReplace.listBox.topLevelItem(2).text(0) == "<keyword3>"
assert tabReplace.listBox.topLevelItem(2).text(1) == ""
# Edit the entry
tabReplace.listBox.setCurrentItem(tabReplace.listBox.topLevelItem(2))
tabReplace.editKey.setText("")
for c in "Th is ": for c in "Th is ":
qtbot.keyClick(projEdit.tabReplace.editKey, c, delay=typeDelay) qtbot.keyClick(tabReplace.editKey, c, delay=typeDelay)
projEdit.tabReplace.editValue.setText("") tabReplace.editValue.setText("")
for c in "With This Stuff ": for c in "With This Stuff ":
qtbot.keyClick(projEdit.tabReplace.editValue, c, delay=typeDelay) qtbot.keyClick(tabReplace.editValue, c, delay=typeDelay)
qtbot.mouseClick(projEdit.tabReplace.saveButton, Qt.LeftButton) qtbot.mouseClick(tabReplace.saveButton, Qt.LeftButton)
assert tabReplace.listBox.topLevelItem(2).text(0) == "<This>"
assert tabReplace.listBox.topLevelItem(2).text(1) == "With This Stuff "
qtbot.wait(stepDelay) # Create a new entry again
projEdit.tabReplace.listBox.clearSelection() tabReplace.listBox.clearSelection()
assert not projEdit.tabReplace._saveEntry() qtbot.mouseClick(tabReplace.addButton, Qt.LeftButton)
assert not projEdit.tabReplace._delEntry() assert tabReplace.listBox.topLevelItemCount() == 4
qtbot.mouseClick(projEdit.tabReplace.addButton, Qt.LeftButton)
# The list is sorted, so we must find it
newIdx = -1 newIdx = -1
for i in range(projEdit.tabReplace.listBox.topLevelItemCount()): for i in range(tabReplace.listBox.topLevelItemCount()):
if projEdit.tabReplace.listBox.topLevelItem(i).text(0) == "<keyword4>": if tabReplace.listBox.topLevelItem(i).text(0) == "<keyword4>":
newIdx = i newIdx = i
break break
assert newIdx >= 0 assert newIdx >= 0
newItem = projEdit.tabReplace.listBox.topLevelItem(newIdx)
projEdit.tabReplace.listBox.setCurrentItem(newItem)
qtbot.mouseClick(projEdit.tabReplace.delButton, Qt.LeftButton)
qtbot.wait(stepDelay)
# Save & Check # Then delete the new item
# ============ tabReplace.listBox.setCurrentItem(tabReplace.listBox.topLevelItem(newIdx))
qtbot.mouseClick(tabReplace.delButton, Qt.LeftButton)
assert tabReplace.listBox.topLevelItemCount() == 3
projEdit._doSave() # Check Project
projSettings._doSave()
assert theProject.autoReplace == {
"A": "B", "C": "D", "This": "With This Stuff"
}
# Open again, and check project settings # Clean up
nwGUI.mainMenu.aProjectSettings.activate(QAction.Trigger) # qtbot.stop()
qtbot.waitUntil(lambda: getGuiItem("GuiProjectSettings") is not None, timeout=1000) projSettings._doClose()
projEdit = getGuiItem("GuiProjectSettings") # END Test testDlgProjSettings_Replace
assert isinstance(projEdit, GuiProjectSettings)
qtbot.addWidget(projEdit)
assert projEdit.tabMain.editName.text() == "Project Name"
assert projEdit.tabMain.editTitle.text() == "Project Title"
theAuth = projEdit.tabMain.editAuthors.toPlainText().strip().splitlines()
assert len(theAuth) == 2
assert theAuth[0] == "Jane Doe"
assert theAuth[1] == "John Doh"
projEdit._doClose()
qtbot.wait(stepDelay)
assert nwGUI.saveProject()
qtbot.wait(stepDelay)
# Check the files
copyfile(projFile, testFile)
assert cmpFiles(testFile, compFile, [2, 8, 9, 10])
# qtbot.stopForInteraction()
# END Test testDlgProjSettings_Dialog
+1 -1
View File
@@ -1144,7 +1144,7 @@ def testGuiEditor_Tags(qtbot, monkeypatch, nwGUI, nwMinimal, ipsumText):
assert nwGUI.openDocument(cHandle) is True assert nwGUI.openDocument(cHandle) is True
assert nwGUI.docEditor.replaceText(theText) is True assert nwGUI.docEditor.replaceText(theText) is True
assert nwGUI.saveDocument() is True assert nwGUI.saveDocument() is True
assert nwGUI.projView.revealNewTreeItem(cHandle) assert nwGUI.projView.projTree.revealNewTreeItem(cHandle)
nwGUI.docEditor.updateTagHighLighting() nwGUI.docEditor.updateTagHighLighting()
# Follow Tag # Follow Tag
+2 -2
View File
@@ -183,7 +183,7 @@ def testGuiOutline_Content(qtbot, monkeypatch, nwGUI, nwLipsum):
# Add a second novel folder # Add a second novel folder
newHandle = nwGUI.theProject.newRoot(nwItemClass.NOVEL) newHandle = nwGUI.theProject.newRoot(nwItemClass.NOVEL)
nwGUI.projView.revealNewTreeItem(newHandle) nwGUI.projView.projTree.revealNewTreeItem(newHandle)
# Check new values in dropdown list # Check new values in dropdown list
assert outlineBar.novelValue.itemData(0) == lipHandle assert outlineBar.novelValue.itemData(0) == lipHandle
@@ -202,7 +202,7 @@ def testGuiOutline_Content(qtbot, monkeypatch, nwGUI, nwLipsum):
aHandle = nwGUI.theProject.newFile(dTitle, newHandle) aHandle = nwGUI.theProject.newFile(dTitle, newHandle)
hHash = "#"*hLevel hHash = "#"*hLevel
writeFile(os.path.join(nwLipsum, "content", f"{aHandle}.nwd"), f"{hHash} {dTitle}\n\n") writeFile(os.path.join(nwLipsum, "content", f"{aHandle}.nwd"), f"{hHash} {dTitle}\n\n")
nwGUI.projView.revealNewTreeItem(aHandle) nwGUI.projView.projTree.revealNewTreeItem(aHandle)
nwGUI.rebuildIndex() nwGUI.rebuildIndex()
+61 -59
View File
@@ -160,11 +160,11 @@ def testGuiProjTree_NewItems(qtbot, caplog, monkeypatch, nwGUI, fncDir, mockRnd)
# ============ # ============
# Also check error handling in reveal function # Also check error handling in reveal function
assert projView.revealNewTreeItem("abc") is False assert projView.projTree.revealNewTreeItem("abc") is False
# Add an item that cannot be displayed in the tree # Add an item that cannot be displayed in the tree
nHandle = theProject.newFile("Test", None) nHandle = theProject.newFile("Test", None)
assert projView.revealNewTreeItem(nHandle) is False assert projView.projTree.revealNewTreeItem(nHandle) is False
# Clean up # Clean up
# qtbot.stop() # qtbot.stop()
@@ -184,10 +184,11 @@ def testGuiProjTree_MoveItems(qtbot, monkeypatch, nwGUI, fncDir, mockRnd):
monkeypatch.setattr(QMessageBox, "information", lambda *a: QMessageBox.Yes) monkeypatch.setattr(QMessageBox, "information", lambda *a: QMessageBox.Yes)
monkeypatch.setattr(GuiEditLabel, "getLabel", lambda *a, text: (text, True)) monkeypatch.setattr(GuiEditLabel, "getLabel", lambda *a, text: (text, True))
nwTree = nwGUI.projView projView = nwGUI.projView
projTree = nwGUI.projView.projTree
# Try to move item with no project # Try to move item with no project
assert nwTree.projTree.moveTreeItem(1) is False assert projView.projTree.moveTreeItem(1) is False
# Create a project # Create a project
prjDir = os.path.join(fncDir, "project") prjDir = os.path.join(fncDir, "project")
@@ -197,68 +198,68 @@ def testGuiProjTree_MoveItems(qtbot, monkeypatch, nwGUI, fncDir, mockRnd):
# ============== # ==============
# Add some files # Add some files
nwTree.setSelectedHandle(C.hChapterDir) projView.setSelectedHandle(C.hChapterDir)
assert nwTree.projTree.newTreeItem(nwItemType.FILE) is True assert projTree.newTreeItem(nwItemType.FILE) is True
assert nwTree.projTree.newTreeItem(nwItemType.FILE) is True assert projTree.newTreeItem(nwItemType.FILE) is True
assert nwTree.projTree.newTreeItem(nwItemType.FILE) is True assert projTree.newTreeItem(nwItemType.FILE) is True
assert nwTree.getTreeFromHandle(C.hChapterDir) == [ assert projTree.getTreeFromHandle(C.hChapterDir) == [
C.hChapterDir, C.hChapterDoc, C.hSceneDoc, C.hChapterDir, C.hChapterDoc, C.hSceneDoc,
"0000000000010", "0000000000011", "0000000000012", "0000000000010", "0000000000011", "0000000000012",
] ]
# Move with no selections # Move with no selections
nwTree.projTree.clearSelection() projTree.clearSelection()
assert nwTree.projTree.moveTreeItem(1) is False assert projTree.moveTreeItem(1) is False
# Move second item up twice (should give same result) # Move second item up twice (should give same result)
nwTree.setSelectedHandle(C.hSceneDoc) projView.setSelectedHandle(C.hSceneDoc)
assert nwTree.projTree.moveTreeItem(-1) is True assert projTree.moveTreeItem(-1) is True
assert nwTree.getTreeFromHandle(C.hChapterDir) == [ assert projTree.getTreeFromHandle(C.hChapterDir) == [
C.hChapterDir, C.hSceneDoc, C.hChapterDoc, C.hChapterDir, C.hSceneDoc, C.hChapterDoc,
"0000000000010", "0000000000011", "0000000000012", "0000000000010", "0000000000011", "0000000000012",
] ]
assert nwTree.projTree.moveTreeItem(-1) is False assert projTree.moveTreeItem(-1) is False
assert nwTree.getTreeFromHandle(C.hChapterDir) == [ assert projTree.getTreeFromHandle(C.hChapterDir) == [
C.hChapterDir, C.hSceneDoc, C.hChapterDoc, C.hChapterDir, C.hSceneDoc, C.hChapterDoc,
"0000000000010", "0000000000011", "0000000000012", "0000000000010", "0000000000011", "0000000000012",
] ]
# Restore # Restore
assert nwTree.projTree.moveTreeItem(1) is True assert projTree.moveTreeItem(1) is True
assert nwTree.getTreeFromHandle(C.hChapterDir) == [ assert projTree.getTreeFromHandle(C.hChapterDir) == [
C.hChapterDir, C.hChapterDoc, C.hSceneDoc, C.hChapterDir, C.hChapterDoc, C.hSceneDoc,
"0000000000010", "0000000000011", "0000000000012", "0000000000010", "0000000000011", "0000000000012",
] ]
# Move fifth item down twice (should give same result) # Move fifth item down twice (should give same result)
nwTree.setSelectedHandle("0000000000011") projView.setSelectedHandle("0000000000011")
assert nwTree.projTree.moveTreeItem(1) is True assert projTree.moveTreeItem(1) is True
assert nwTree.getTreeFromHandle(C.hChapterDir) == [ assert projTree.getTreeFromHandle(C.hChapterDir) == [
C.hChapterDir, C.hChapterDoc, C.hSceneDoc, C.hChapterDir, C.hChapterDoc, C.hSceneDoc,
"0000000000010", "0000000000012", "0000000000011", "0000000000010", "0000000000012", "0000000000011",
] ]
assert nwTree.projTree.moveTreeItem(1) is False assert projTree.moveTreeItem(1) is False
assert nwTree.getTreeFromHandle(C.hChapterDir) == [ assert projTree.getTreeFromHandle(C.hChapterDir) == [
C.hChapterDir, C.hChapterDoc, C.hSceneDoc, C.hChapterDir, C.hChapterDoc, C.hSceneDoc,
"0000000000010", "0000000000012", "0000000000011", "0000000000010", "0000000000012", "0000000000011",
] ]
# Restore # Restore
assert nwTree.projTree.moveTreeItem(-1) is True assert projTree.moveTreeItem(-1) is True
assert nwTree.getTreeFromHandle(C.hChapterDir) == [ assert projTree.getTreeFromHandle(C.hChapterDir) == [
C.hChapterDir, C.hChapterDoc, C.hSceneDoc, C.hChapterDir, C.hChapterDoc, C.hSceneDoc,
"0000000000010", "0000000000011", "0000000000012", "0000000000010", "0000000000011", "0000000000012",
] ]
# Move down again, and restore via undo # Move down again, and restore via undo
nwTree.setSelectedHandle("0000000000011") projView.setSelectedHandle("0000000000011")
assert nwTree.projTree.moveTreeItem(1) is True assert projTree.moveTreeItem(1) is True
assert nwTree.getTreeFromHandle(C.hChapterDir) == [ assert projTree.getTreeFromHandle(C.hChapterDir) == [
C.hChapterDir, C.hChapterDoc, C.hSceneDoc, C.hChapterDir, C.hChapterDoc, C.hSceneDoc,
"0000000000010", "0000000000012", "0000000000011", "0000000000010", "0000000000012", "0000000000011",
] ]
assert nwTree.projTree.undoLastMove() is True assert projTree.undoLastMove() is True
assert nwTree.getTreeFromHandle(C.hChapterDir) == [ assert projTree.getTreeFromHandle(C.hChapterDir) == [
C.hChapterDir, C.hChapterDoc, C.hSceneDoc, C.hChapterDir, C.hChapterDoc, C.hSceneDoc,
"0000000000010", "0000000000011", "0000000000012", "0000000000010", "0000000000011", "0000000000012",
] ]
@@ -266,19 +267,19 @@ def testGuiProjTree_MoveItems(qtbot, monkeypatch, nwGUI, fncDir, mockRnd):
# Root Folder # Root Folder
# =========== # ===========
nwTree.setSelectedHandle(C.hNovelRoot) projView.setSelectedHandle(C.hNovelRoot)
assert nwGUI.theProject.tree._treeOrder.index(C.hNovelRoot) == 0 assert nwGUI.theProject.tree._treeOrder.index(C.hNovelRoot) == 0
# Move novel folder up # Move novel folder up
assert nwTree.projTree.moveTreeItem(-1) is False assert projTree.moveTreeItem(-1) is False
assert nwGUI.theProject.tree._treeOrder.index(C.hNovelRoot) == 0 assert nwGUI.theProject.tree._treeOrder.index(C.hNovelRoot) == 0
# Move novel folder down # Move novel folder down
assert nwTree.projTree.moveTreeItem(1) is True assert projTree.moveTreeItem(1) is True
assert nwGUI.theProject.tree._treeOrder.index(C.hNovelRoot) == 1 assert nwGUI.theProject.tree._treeOrder.index(C.hNovelRoot) == 1
# Move novel folder up again # Move novel folder up again
assert nwTree.projTree.moveTreeItem(-1) is True assert projTree.moveTreeItem(-1) is True
assert nwGUI.theProject.tree._treeOrder.index(C.hNovelRoot) == 0 assert nwGUI.theProject.tree._treeOrder.index(C.hNovelRoot) == 0
# Clean up # Clean up
@@ -299,76 +300,77 @@ def testGuiProjTree_RequestDeleteItem(qtbot, caplog, monkeypatch, nwGUI, fncDir,
monkeypatch.setattr(QMessageBox, "information", lambda *a: QMessageBox.Yes) monkeypatch.setattr(QMessageBox, "information", lambda *a: QMessageBox.Yes)
monkeypatch.setattr(GuiEditLabel, "getLabel", lambda *a, text: (text, True)) monkeypatch.setattr(GuiEditLabel, "getLabel", lambda *a, text: (text, True))
nwView = nwGUI.projView projView = nwGUI.projView
projTree = nwGUI.projView.projTree
# Try to run with no project # Try to run with no project
assert nwView.requestDeleteItem() is False assert projView.requestDeleteItem() is False
# Create a project # Create a project
prjDir = os.path.join(fncDir, "project") prjDir = os.path.join(fncDir, "project")
buildTestProject(nwGUI, prjDir) buildTestProject(nwGUI, prjDir)
# Try emptying the trash already now, when there is no trash folder # Try emptying the trash already now, when there is no trash folder
assert nwView.emptyTrash() is False assert projView.emptyTrash() is False
# Add some files # Add some files
nwView.setSelectedHandle(C.hChapterDir) projView.setSelectedHandle(C.hChapterDir)
assert nwView.projTree.newTreeItem(nwItemType.FILE) is True assert projTree.newTreeItem(nwItemType.FILE) is True
assert nwView.projTree.newTreeItem(nwItemType.FILE) is True assert projTree.newTreeItem(nwItemType.FILE) is True
assert nwView.projTree.newTreeItem(nwItemType.FILE) is True assert projTree.newTreeItem(nwItemType.FILE) is True
assert nwView.getTreeFromHandle(C.hChapterDir) == [ assert projTree.getTreeFromHandle(C.hChapterDir) == [
C.hChapterDir, C.hChapterDoc, C.hSceneDoc, C.hChapterDir, C.hChapterDoc, C.hSceneDoc,
"0000000000010", "0000000000011", "0000000000012", "0000000000010", "0000000000011", "0000000000012",
] ]
# Delete item without focus -> blocked # Delete item without focus -> blocked
monkeypatch.setattr(GuiProjectTree, "hasFocus", lambda *a: False) monkeypatch.setattr(GuiProjectTree, "hasFocus", lambda *a: False)
nwView.setSelectedHandle("0000000000012") projView.setSelectedHandle("0000000000012")
assert nwView.requestDeleteItem() is False assert projView.requestDeleteItem() is False
monkeypatch.setattr(GuiProjectTree, "hasFocus", lambda *a: True) monkeypatch.setattr(GuiProjectTree, "hasFocus", lambda *a: True)
# No selection made # No selection made
nwView.projTree.clearSelection() projTree.clearSelection()
caplog.clear() caplog.clear()
assert nwView.requestDeleteItem() is False assert projView.requestDeleteItem() is False
assert "no item to delete" in caplog.text assert "no item to delete" in caplog.text
# Not a valid handle # Not a valid handle
nwView.projTree.clearSelection() projTree.clearSelection()
caplog.clear() caplog.clear()
assert nwView.requestDeleteItem("0000000000000") is False assert projView.requestDeleteItem("0000000000000") is False
assert "No tree item with handle '0000000000000'" in caplog.text assert "No tree item with handle '0000000000000'" in caplog.text
# Delete Root Folders # Delete Root Folders
# =================== # ===================
assert nwView.requestDeleteItem(C.hNovelRoot) is False # Novel Root is blocked assert projView.requestDeleteItem(C.hNovelRoot) is False # Novel Root is blocked
assert nwView.requestDeleteItem(C.hCharRoot) is True # Character Root assert projView.requestDeleteItem(C.hCharRoot) is True # Character Root
# Delete File # Delete File
# =========== # ===========
# Block adding trash folder # Block adding trash folder
funcPointer = nwView.projTree._addTrashRoot funcPointer = projTree._addTrashRoot
nwView.projTree._addTrashRoot = lambda *a: None projTree._addTrashRoot = lambda *a: None
assert nwView.requestDeleteItem("0000000000012") is False assert projView.requestDeleteItem("0000000000012") is False
nwView.projTree._addTrashRoot = funcPointer projTree._addTrashRoot = funcPointer
# Delete last two documents, which also adds the trash folder # Delete last two documents, which also adds the trash folder
assert nwView.requestDeleteItem("0000000000012") is True assert projView.requestDeleteItem("0000000000012") is True
assert nwView.requestDeleteItem("0000000000011") is True assert projView.requestDeleteItem("0000000000011") is True
assert nwView.getTreeFromHandle(C.hChapterDir) == [ assert projTree.getTreeFromHandle(C.hChapterDir) == [
C.hChapterDir, C.hChapterDoc, C.hSceneDoc, C.hChapterDir, C.hChapterDoc, C.hSceneDoc,
"0000000000010" "0000000000010"
] ]
trashHandle = nwGUI.theProject.tree.trashRoot() trashHandle = nwGUI.theProject.tree.trashRoot()
assert nwView.getTreeFromHandle(trashHandle) == [ assert projTree.getTreeFromHandle(trashHandle) == [
trashHandle, "0000000000012", "0000000000011" trashHandle, "0000000000012", "0000000000011"
] ]
# Try to delete the trash folder # Try to delete the trash folder
caplog.clear() caplog.clear()
assert nwView.requestDeleteItem("0000000000013") is False assert projView.requestDeleteItem("0000000000013") is False
assert "Cannot delete the Trash folder" in caplog.text assert "Cannot delete the Trash folder" in caplog.text
nwGUI.closeProject() nwGUI.closeProject()
+1 -1
View File
@@ -40,7 +40,7 @@ def testGuiStatusBar_Main(qtbot, monkeypatch, nwGUI, fncProj, mockRnd):
cHandle = nwGUI.theProject.newFile("A Note", C.hCharRoot) cHandle = nwGUI.theProject.newFile("A Note", C.hCharRoot)
newDoc = NWDoc(nwGUI.theProject, cHandle) newDoc = NWDoc(nwGUI.theProject, cHandle)
newDoc.writeDocument("# A Note\n\n") newDoc.writeDocument("# A Note\n\n")
nwGUI.projView.revealNewTreeItem(cHandle) nwGUI.projView.projTree.revealNewTreeItem(cHandle)
nwGUI.rebuildIndex(beQuiet=True) nwGUI.rebuildIndex(beQuiet=True)
# Reference Time # Reference Time
+11
View File
@@ -30,6 +30,17 @@ XML_IGNORE = ("<novelWriterXML", "<saveCount", "<autoCount", "<editTime")
class C: class C:
# Import and status items from test project when random generator is mocked
sNew = "s000000"
sNote = "s000001"
sDraft = "s000002"
sFinished = "s000003"
iNew = "i000004"
iMinor = "i000005"
iMajor = "i000006"
iMain = "i000007"
# Handles from test project when random generator is mocked # Handles from test project when random generator is mocked
hInvalid = "0000000000000" hInvalid = "0000000000000"
hNovelRoot = "0000000000008" hNovelRoot = "0000000000008"