Merge branch 'main' into merge_1.6.6
This commit is contained in:
@@ -421,6 +421,12 @@ class PagedDialog(QDialog):
|
||||
self._buttonBox.addWidget(buttonBar)
|
||||
return
|
||||
|
||||
def setCurrentWidget(self, widget):
|
||||
"""Forward the changing of tab to the QTabWidget.
|
||||
"""
|
||||
self._tabBox.setCurrentWidget(widget)
|
||||
return
|
||||
|
||||
# END Class PagedDialog
|
||||
|
||||
|
||||
|
||||
@@ -43,7 +43,12 @@ logger = logging.getLogger(__name__)
|
||||
|
||||
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)
|
||||
|
||||
logger.debug("Initialising GuiProjectSettings ...")
|
||||
@@ -83,12 +88,19 @@ class GuiProjectSettings(PagedDialog):
|
||||
self.addControls(self.buttonBox)
|
||||
|
||||
# Flags
|
||||
self.spellChanged = False
|
||||
self._spellChanged = False
|
||||
|
||||
# Focus Tab
|
||||
self._focusTab(focusTab)
|
||||
|
||||
logger.debug("GuiProjectSettings initialisation complete")
|
||||
|
||||
return
|
||||
|
||||
@property
|
||||
def spellChanged(self):
|
||||
return self._spellChanged
|
||||
|
||||
##
|
||||
# Slots
|
||||
##
|
||||
@@ -108,7 +120,7 @@ class GuiProjectSettings(PagedDialog):
|
||||
self.theProject.setProjBackup(doBackup)
|
||||
|
||||
# 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:
|
||||
newList, delList = self.tabStatus.getNewList()
|
||||
@@ -141,6 +153,19 @@ class GuiProjectSettings(PagedDialog):
|
||||
# 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):
|
||||
"""Save GUI settings.
|
||||
"""
|
||||
|
||||
+62
-55
@@ -41,8 +41,8 @@ from PyQt5.QtWidgets import (
|
||||
|
||||
from novelwriter.core import DocMerger, DocSplitter
|
||||
from novelwriter.enum import nwDocMode, nwItemType, nwItemClass, nwItemLayout, nwAlert
|
||||
from novelwriter.dialogs import GuiDocMerge, GuiDocSplit, GuiEditLabel
|
||||
from novelwriter.constants import nwHeaders, trConst, nwLabels
|
||||
from novelwriter.dialogs import GuiDocMerge, GuiDocSplit, GuiEditLabel, GuiProjectSettings
|
||||
from novelwriter.constants import nwHeaders, nwUnicode, trConst, nwLabels
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -62,6 +62,9 @@ class GuiProjectView(QWidget):
|
||||
selectedItemChanged = pyqtSignal(str)
|
||||
openDocumentRequest = pyqtSignal(str, Enum, int, str)
|
||||
|
||||
# Requests for the main GUI
|
||||
projectSettingsRequest = pyqtSignal(int)
|
||||
|
||||
def __init__(self, mainGui):
|
||||
super().__init__(parent=mainGui)
|
||||
|
||||
@@ -102,9 +105,6 @@ class GuiProjectView(QWidget):
|
||||
self.keyContext.activated.connect(lambda: self.projTree.openContextOnSelected())
|
||||
|
||||
# Function Mappings
|
||||
self.revealNewTreeItem = self.projTree.revealNewTreeItem
|
||||
self.renameTreeItem = self.projTree.renameTreeItem
|
||||
self.getTreeFromHandle = self.projTree.getTreeFromHandle
|
||||
self.emptyTrash = self.projTree.emptyTrash
|
||||
self.requestDeleteItem = self.projTree.requestDeleteItem
|
||||
self.setTreeItemValues = self.projTree.setTreeItemValues
|
||||
@@ -161,6 +161,16 @@ class GuiProjectView(QWidget):
|
||||
"""
|
||||
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
|
||||
##
|
||||
@@ -1111,10 +1121,12 @@ class GuiProjectTree(QTreeWidget):
|
||||
open a context menu in-place.
|
||||
"""
|
||||
tItem = None
|
||||
hasChild = False
|
||||
selItem = self.itemAt(clickPos)
|
||||
if isinstance(selItem, QTreeWidgetItem):
|
||||
tHandle = selItem.data(self.C_NAME, Qt.UserRole)
|
||||
tItem = self.theProject.tree[tHandle]
|
||||
hasChild = selItem.childCount() > 0
|
||||
|
||||
if tItem is None:
|
||||
logger.debug("No item found")
|
||||
@@ -1128,9 +1140,8 @@ class GuiProjectTree(QTreeWidget):
|
||||
trashHandle = self.theProject.tree.trashRoot()
|
||||
if tItem.itemHandle == trashHandle and trashHandle is not None:
|
||||
# The trash folder only has one option
|
||||
ctxMenu.addAction(
|
||||
self.tr("Empty Trash"), lambda: self.emptyTrash()
|
||||
)
|
||||
aEmptyTrash = ctxMenu.addAction(self.tr("Empty Trash"))
|
||||
aEmptyTrash.triggered.connect(lambda: self.emptyTrash())
|
||||
ctxMenu.exec_(self.viewport().mapToGlobal(clickPos))
|
||||
return True
|
||||
|
||||
@@ -1140,15 +1151,14 @@ class GuiProjectTree(QTreeWidget):
|
||||
isRoot = tItem.isRootType()
|
||||
isFolder = tItem.isFolderType()
|
||||
isFile = tItem.isFileType()
|
||||
hasChild = selItem.childCount() > 0
|
||||
|
||||
if isFile:
|
||||
ctxMenu.addAction(
|
||||
self.tr("Open Document"),
|
||||
aOpenDoc = ctxMenu.addAction(self.tr("Open Document"))
|
||||
aOpenDoc.triggered.connect(
|
||||
lambda: self.projView.openDocumentRequest.emit(tHandle, nwDocMode.EDIT, -1, "")
|
||||
)
|
||||
ctxMenu.addAction(
|
||||
self.tr("View Document"),
|
||||
aViewDoc = ctxMenu.addAction(self.tr("View Document"))
|
||||
aViewDoc.triggered.connect(
|
||||
lambda: self.projView.openDocumentRequest.emit(tHandle, nwDocMode.VIEW, -1, "")
|
||||
)
|
||||
ctxMenu.addSeparator()
|
||||
@@ -1156,29 +1166,40 @@ class GuiProjectTree(QTreeWidget):
|
||||
# Edit Item Settings
|
||||
# ==================
|
||||
|
||||
ctxMenu.addAction(
|
||||
self.tr("Change Label"), lambda: self.renameTreeItem(tHandle)
|
||||
)
|
||||
aLabel = ctxMenu.addAction(self.tr("Change Label"))
|
||||
aLabel.triggered.connect(lambda: self.renameTreeItem(tHandle))
|
||||
|
||||
if isFile:
|
||||
ctxMenu.addAction(
|
||||
self.tr("Toggle Active"), lambda: self._toggleItemActive(tHandle)
|
||||
)
|
||||
aActive = ctxMenu.addAction(self.tr("Toggle Active"))
|
||||
aActive.triggered.connect(lambda: self._toggleItemActive(tHandle))
|
||||
|
||||
checkMark = f" ({nwUnicode.U_CHECK})"
|
||||
if tItem.isNovelLike():
|
||||
mStatus = ctxMenu.addMenu(self.tr("Set Status to ..."))
|
||||
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(
|
||||
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:
|
||||
mImport = ctxMenu.addMenu(self.tr("Set Importance to ..."))
|
||||
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(
|
||||
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
|
||||
# ==============
|
||||
@@ -1193,38 +1214,30 @@ class GuiProjectTree(QTreeWidget):
|
||||
isNoteFile = isFile and tItem.isNoteLayout()
|
||||
|
||||
if (isNoteFile or isFolder) and tItem.documentAllowed():
|
||||
mTrans.addAction(
|
||||
self.tr("Convert to {0}").format(trDoc),
|
||||
aConvert1 = mTrans.addAction(self.tr("Convert to {0}").format(trDoc))
|
||||
aConvert1.triggered.connect(
|
||||
lambda: self._changeItemLayout(tHandle, nwItemLayout.DOCUMENT)
|
||||
)
|
||||
|
||||
if isDocFile or isFolder:
|
||||
mTrans.addAction(
|
||||
self.tr("Convert to {0}").format(trNote),
|
||||
aConvert2 = mTrans.addAction(self.tr("Convert to {0}").format(trNote))
|
||||
aConvert2.triggered.connect(
|
||||
lambda: self._changeItemLayout(tHandle, nwItemLayout.NOTE)
|
||||
)
|
||||
|
||||
if hasChild and isFile:
|
||||
mTrans.addAction(
|
||||
self.tr("Merge Child Items into Self"),
|
||||
lambda: self._mergeDocuments(tHandle, False)
|
||||
)
|
||||
mTrans.addAction(
|
||||
self.tr("Merge Child Items into New"),
|
||||
lambda: self._mergeDocuments(tHandle, True)
|
||||
)
|
||||
aMerge1 = mTrans.addAction(self.tr("Merge Child Items into Self"))
|
||||
aMerge1.triggered.connect(lambda: self._mergeDocuments(tHandle, False))
|
||||
aMerge2 = mTrans.addAction(self.tr("Merge Child Items into New"))
|
||||
aMerge2.triggered.connect(lambda: self._mergeDocuments(tHandle, True))
|
||||
|
||||
if hasChild and isFolder:
|
||||
mTrans.addAction(
|
||||
self.tr("Merge Documents in Folder"),
|
||||
lambda: self._mergeDocuments(tHandle, True)
|
||||
)
|
||||
aMerge3 = mTrans.addAction(self.tr("Merge Documents in Folder"))
|
||||
aMerge3.triggered.connect(lambda: self._mergeDocuments(tHandle, True))
|
||||
|
||||
if isFile:
|
||||
mTrans.addAction(
|
||||
self.tr("Split Document by Headers"),
|
||||
lambda: self._splitDocument(tHandle)
|
||||
)
|
||||
aSplit1 = mTrans.addAction(self.tr("Split Document by Headers"))
|
||||
aSplit1.triggered.connect(lambda: self._splitDocument(tHandle))
|
||||
|
||||
# Expand/Collapse/Delete
|
||||
# ======================
|
||||
@@ -1232,23 +1245,17 @@ class GuiProjectTree(QTreeWidget):
|
||||
ctxMenu.addSeparator()
|
||||
|
||||
if hasChild:
|
||||
ctxMenu.addAction(
|
||||
self.tr("Expand All"),
|
||||
lambda: self.setExpandedFromHandle(tHandle, True)
|
||||
)
|
||||
ctxMenu.addAction(
|
||||
self.tr("Collapse All"),
|
||||
lambda: self.setExpandedFromHandle(tHandle, False)
|
||||
)
|
||||
aExpand = ctxMenu.addAction(self.tr("Expand All"))
|
||||
aExpand.triggered.connect(lambda: self.setExpandedFromHandle(tHandle, True))
|
||||
aCollapse = ctxMenu.addAction(self.tr("Collapse All"))
|
||||
aCollapse.triggered.connect(lambda: self.setExpandedFromHandle(tHandle, False))
|
||||
|
||||
if tItem.itemClass == nwItemClass.TRASH or isRoot or (isFolder and not hasChild):
|
||||
ctxMenu.addAction(
|
||||
self.tr("Delete Permanently"), lambda: self.permanentlyDeleteItem(tHandle)
|
||||
)
|
||||
aDelete = ctxMenu.addAction(self.tr("Delete Permanently"))
|
||||
aDelete.triggered.connect(lambda: self.permanentlyDeleteItem(tHandle))
|
||||
else:
|
||||
ctxMenu.addAction(
|
||||
self.tr("Move to Trash"), lambda: self.moveItemToTrash(tHandle)
|
||||
)
|
||||
aMoveTrash = ctxMenu.addAction(self.tr("Move to Trash"))
|
||||
aMoveTrash.triggered.connect(lambda: self.moveItemToTrash(tHandle))
|
||||
|
||||
# Show Context Menu
|
||||
ctxMenu.exec_(self.viewport().mapToGlobal(clickPos))
|
||||
|
||||
+8
-10
@@ -208,6 +208,7 @@ class GuiMain(QMainWindow):
|
||||
self.projView.rootFolderChanged.connect(self.outlineView.updateRootItem)
|
||||
self.projView.rootFolderChanged.connect(self.novelView.updateRootItem)
|
||||
self.projView.rootFolderChanged.connect(self.projView.updateRootItem)
|
||||
self.projView.projectSettingsRequest.connect(self.showProjectSettingsDialog)
|
||||
|
||||
self.novelView.selectedItemChanged.connect(self.itemDetails.updateViewBox)
|
||||
self.novelView.openDocumentRequest.connect(self._openDocument)
|
||||
@@ -806,15 +807,11 @@ class GuiMain(QMainWindow):
|
||||
logger.error("No project open")
|
||||
return False
|
||||
|
||||
if tHandle is None:
|
||||
if self.docEditor.anyFocus() or self.isFocusMode:
|
||||
tHandle = self.docEditor.docHandle()
|
||||
else:
|
||||
tHandle = self.projView.getSelectedHandle()
|
||||
if tHandle:
|
||||
return self.projView.renameTreeItem(tHandle)
|
||||
if tHandle is None and (self.docEditor.anyFocus() or self.isFocusMode):
|
||||
tHandle = self.docEditor.docHandle()
|
||||
self.projView.renameTreeItem(tHandle)
|
||||
|
||||
return False
|
||||
return True
|
||||
|
||||
def rebuildTrees(self):
|
||||
"""Rebuild the project tree.
|
||||
@@ -921,14 +918,15 @@ class GuiMain(QMainWindow):
|
||||
|
||||
return
|
||||
|
||||
def showProjectSettingsDialog(self):
|
||||
@pyqtSlot(int)
|
||||
def showProjectSettingsDialog(self, focusTab=GuiProjectSettings.TAB_MAIN):
|
||||
"""Open the project settings dialog.
|
||||
"""
|
||||
if not self.hasProject:
|
||||
logger.error("No project open")
|
||||
return False
|
||||
|
||||
dlgProj = GuiProjectSettings(self)
|
||||
dlgProj = GuiProjectSettings(self, focusTab=focusTab)
|
||||
dlgProj.exec_()
|
||||
|
||||
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>
|
||||
@@ -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/>.
|
||||
"""
|
||||
|
||||
import os
|
||||
import pytest
|
||||
|
||||
from shutil import copyfile
|
||||
from tools import cmpFiles, getGuiItem, buildTestProject
|
||||
from novelwriter.enum import nwItemType
|
||||
from tools import C, getGuiItem, buildTestProject
|
||||
|
||||
from PyQt5.QtGui import QColor
|
||||
from PyQt5.QtCore import Qt
|
||||
from PyQt5.QtWidgets import QDialog, QAction, QMessageBox, QColorDialog
|
||||
|
||||
from novelwriter.dialogs.editlabel import GuiEditLabel
|
||||
from novelwriter.dialogs.projsettings import GuiProjectSettings
|
||||
|
||||
keyDelay = 2
|
||||
typeDelay = 1
|
||||
stepDelay = 20
|
||||
statusKeys = ["s000000", "s000001", "s000002", "s000003"]
|
||||
importKeys = ["i000004", "i000005", "i000006", "i000007"]
|
||||
|
||||
|
||||
@pytest.mark.gui
|
||||
def testDlgProjSettings_Dialog(
|
||||
qtbot, monkeypatch, nwGUI, fncDir, fncProj, outDir, refDir, mockRnd
|
||||
):
|
||||
"""Test the full project settings dialog.
|
||||
def testDlgProjSettings_Dialog(qtbot, monkeypatch, nwGUI):
|
||||
"""Test the main dialog class. Saving settings is not tested in this
|
||||
test, but are instead tested in the individual tab tests.
|
||||
"""
|
||||
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
|
||||
monkeypatch.setattr(QMessageBox, "question", 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
|
||||
nwGUI.mainMenu.aProjectSettings.activate(QAction.Trigger)
|
||||
assert getGuiItem("GuiProjectSettings") is None
|
||||
|
||||
# Create new project
|
||||
buildTestProject(nwGUI, fncProj)
|
||||
nwGUI.mainConf.backupPath = fncDir
|
||||
|
||||
# Pretend we have a project
|
||||
nwGUI.hasProject = True
|
||||
nwGUI.theProject.setSpellLang("en")
|
||||
nwGUI.theProject.setBookAuthors("Jane Smith\nJohn Smith")
|
||||
nwGUI.theProject.setAutoReplace({"A": "B", "C": "D"})
|
||||
|
||||
# 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)
|
||||
qtbot.waitUntil(lambda: getGuiItem("GuiProjectSettings") is not None, timeout=1000)
|
||||
|
||||
@@ -77,83 +67,189 @@ def testDlgProjSettings_Dialog(
|
||||
projEdit.show()
|
||||
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
|
||||
# ============
|
||||
|
||||
assert projEdit.tabMain.editName.text() == "New Project"
|
||||
assert projEdit.tabMain.editTitle.text() == "New Novel"
|
||||
assert projEdit.tabMain.editAuthors.toPlainText() == "Jane Smith\nJohn Smith"
|
||||
assert projEdit.tabMain.spellLang.currentData() == "en"
|
||||
assert projEdit.tabMain.doBackup.isChecked() is False
|
||||
tabMain = projSettings.tabMain
|
||||
|
||||
assert tabMain.editName.text() == "New Project"
|
||||
assert tabMain.editTitle.text() == "New Novel"
|
||||
assert tabMain.editAuthors.toPlainText() == "Jane Smith\nJohn Smith"
|
||||
assert tabMain.spellLang.currentData() == "en"
|
||||
assert tabMain.doBackup.isChecked() is False
|
||||
|
||||
qtbot.wait(stepDelay)
|
||||
projEdit.tabMain.editName.setText("")
|
||||
tabMain.editName.setText("")
|
||||
for c in "Project Name":
|
||||
qtbot.keyClick(projEdit.tabMain.editName, c, delay=typeDelay)
|
||||
projEdit.tabMain.editTitle.setText("")
|
||||
qtbot.keyClick(tabMain.editName, c, delay=typeDelay)
|
||||
tabMain.editTitle.setText("")
|
||||
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":
|
||||
qtbot.keyClick(projEdit.tabMain.editAuthors, c, delay=typeDelay)
|
||||
qtbot.keyClick(projEdit.tabMain.editAuthors, Qt.Key_Return, delay=keyDelay)
|
||||
qtbot.keyClick(tabMain.editAuthors, c, delay=typeDelay)
|
||||
qtbot.keyClick(tabMain.editAuthors, Qt.Key_Return, delay=keyDelay)
|
||||
for c in "John Doh":
|
||||
qtbot.keyClick(projEdit.tabMain.editAuthors, c, delay=typeDelay)
|
||||
qtbot.keyClick(projEdit.tabMain.editAuthors, Qt.Key_Return, delay=keyDelay)
|
||||
qtbot.keyClick(tabMain.editAuthors, c, delay=typeDelay)
|
||||
qtbot.keyClick(tabMain.editAuthors, Qt.Key_Return, delay=keyDelay)
|
||||
|
||||
qtbot.wait(stepDelay)
|
||||
assert projEdit.tabMain.editName.text() == "Project Name"
|
||||
assert projEdit.tabMain.editTitle.text() == "Project Title"
|
||||
assert projEdit.tabMain.editAuthors.toPlainText() == "Jane Doe\nJohn Doh\n"
|
||||
assert tabMain.editName.text() == "Project Name"
|
||||
assert tabMain.editTitle.text() == "Project Title"
|
||||
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
|
||||
# ==========
|
||||
|
||||
projEdit._tabBox.setCurrentWidget(projEdit.tabStatus)
|
||||
tabStatus = projSettings.tabStatus
|
||||
|
||||
assert projEdit.tabStatus.colChanged is False
|
||||
assert projEdit.tabStatus.getNewList() == ([], [])
|
||||
assert projEdit.tabStatus.listBox.topLevelItemCount() == 4
|
||||
assert tabStatus.colChanged is False
|
||||
assert tabStatus.getNewList() == ([], [])
|
||||
assert tabStatus.listBox.topLevelItemCount() == 4
|
||||
|
||||
# Can't delete the first item (it's in use)
|
||||
projEdit.tabStatus.listBox.clearSelection()
|
||||
projEdit.tabStatus.listBox.topLevelItem(0).setSelected(True)
|
||||
qtbot.mouseClick(projEdit.tabStatus.delButton, Qt.LeftButton)
|
||||
assert projEdit.tabStatus.listBox.topLevelItemCount() == 4
|
||||
tabStatus.listBox.clearSelection()
|
||||
tabStatus.listBox.setCurrentItem(tabStatus.listBox.topLevelItem(0))
|
||||
qtbot.mouseClick(tabStatus.delButton, Qt.LeftButton)
|
||||
assert tabStatus.listBox.topLevelItemCount() == 4
|
||||
|
||||
# Can delete the third item
|
||||
projEdit.tabStatus.listBox.clearSelection()
|
||||
projEdit.tabStatus.listBox.topLevelItem(2).setSelected(True)
|
||||
qtbot.mouseClick(projEdit.tabStatus.delButton, Qt.LeftButton)
|
||||
assert projEdit.tabStatus.listBox.topLevelItemCount() == 3
|
||||
# Can delete the second item
|
||||
tabStatus.listBox.clearSelection()
|
||||
tabStatus.listBox.setCurrentItem(tabStatus.listBox.topLevelItem(1))
|
||||
qtbot.mouseClick(tabStatus.delButton, Qt.LeftButton)
|
||||
assert tabStatus.listBox.topLevelItemCount() == 3
|
||||
|
||||
# Add a new item
|
||||
monkeypatch.setattr(QColorDialog, "getColor", lambda *a: QColor(20, 30, 40))
|
||||
qtbot.mouseClick(projEdit.tabStatus.addButton, Qt.LeftButton)
|
||||
projEdit.tabStatus.listBox.topLevelItem(3).setSelected(True)
|
||||
for n in range(8):
|
||||
qtbot.keyClick(projEdit.tabStatus.editName, Qt.Key_Backspace, delay=typeDelay)
|
||||
for c in "Final":
|
||||
qtbot.keyClick(projEdit.tabStatus.editName, c, delay=typeDelay)
|
||||
qtbot.mouseClick(projEdit.tabStatus.colButton, Qt.LeftButton)
|
||||
qtbot.mouseClick(projEdit.tabStatus.saveButton, Qt.LeftButton)
|
||||
assert projEdit.tabStatus.listBox.topLevelItemCount() == 4
|
||||
qtbot.wait(stepDelay)
|
||||
with monkeypatch.context() as mp:
|
||||
mp.setattr(QColorDialog, "getColor", lambda *a: QColor(20, 30, 40))
|
||||
qtbot.mouseClick(tabStatus.addButton, Qt.LeftButton)
|
||||
tabStatus.listBox.setCurrentItem(tabStatus.listBox.topLevelItem(3))
|
||||
for _ in range(8):
|
||||
qtbot.keyClick(tabStatus.editName, Qt.Key_Backspace, delay=typeDelay)
|
||||
for c in "Final":
|
||||
qtbot.keyClick(tabStatus.editName, c, delay=typeDelay)
|
||||
qtbot.mouseClick(tabStatus.colButton, Qt.LeftButton)
|
||||
qtbot.mouseClick(tabStatus.saveButton, Qt.LeftButton)
|
||||
assert tabStatus.listBox.topLevelItemCount() == 4
|
||||
|
||||
assert projEdit.tabStatus.colChanged is True
|
||||
assert projEdit.tabStatus.getNewList() == (
|
||||
assert tabStatus.colChanged is True
|
||||
assert tabStatus.getNewList() == (
|
||||
[
|
||||
{
|
||||
"key": statusKeys[0],
|
||||
"key": C.sNew,
|
||||
"name": "New",
|
||||
"cols": (100, 100, 100)
|
||||
}, {
|
||||
"key": statusKeys[1],
|
||||
"name": "Note",
|
||||
"cols": (200, 50, 0)
|
||||
"key": C.sDraft,
|
||||
"name": "Draft",
|
||||
"cols": (200, 150, 0)
|
||||
}, {
|
||||
"key": statusKeys[3],
|
||||
"key": C.sFinished,
|
||||
"name": "Finished",
|
||||
"cols": (50, 200, 0)
|
||||
}, {
|
||||
@@ -162,121 +258,201 @@ def testDlgProjSettings_Dialog(
|
||||
"cols": (20, 30, 40)
|
||||
}
|
||||
], [
|
||||
statusKeys[2] # Deleted item
|
||||
C.sNote # Deleted item
|
||||
]
|
||||
)
|
||||
|
||||
# Move items
|
||||
projEdit.tabStatus.listBox.clearSelection()
|
||||
projEdit.tabStatus._moveItem(1)
|
||||
assert [x["key"] for x in projEdit.tabStatus.getNewList()[0]] == [
|
||||
statusKeys[0], statusKeys[1], statusKeys[3], None
|
||||
# Move items, none selected -> no change
|
||||
tabStatus.listBox.clearSelection()
|
||||
tabStatus._moveItem(1)
|
||||
assert [x["key"] for x in tabStatus.getNewList()[0]] == [
|
||||
C.sNew, C.sDraft, C.sFinished, None
|
||||
]
|
||||
|
||||
projEdit.tabStatus.listBox.clearSelection()
|
||||
projEdit.tabStatus.listBox.topLevelItem(0).setSelected(True)
|
||||
projEdit.tabStatus._moveItem(-1)
|
||||
assert [x["key"] for x in projEdit.tabStatus.getNewList()[0]] == [
|
||||
statusKeys[0], statusKeys[1], statusKeys[3], None
|
||||
# Move items, first selected, move up -> no change
|
||||
tabStatus.listBox.clearSelection()
|
||||
tabStatus.listBox.setCurrentItem(tabStatus.listBox.topLevelItem(0))
|
||||
tabStatus._moveItem(-1)
|
||||
assert [x["key"] for x in tabStatus.getNewList()[0]] == [
|
||||
C.sNew, C.sDraft, C.sFinished, None
|
||||
]
|
||||
|
||||
projEdit.tabStatus.listBox.clearSelection()
|
||||
projEdit.tabStatus.listBox.topLevelItem(3).setSelected(True)
|
||||
projEdit.tabStatus._moveItem(-1)
|
||||
assert [x["key"] for x in projEdit.tabStatus.getNewList()[0]] == [
|
||||
statusKeys[0], statusKeys[1], None, statusKeys[3]
|
||||
# Move items, last selected, move up -> allowed
|
||||
tabStatus.listBox.clearSelection()
|
||||
tabStatus.listBox.setCurrentItem(tabStatus.listBox.topLevelItem(3))
|
||||
tabStatus._moveItem(-1)
|
||||
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]] == [
|
||||
statusKeys[0], statusKeys[1], statusKeys[3], None
|
||||
|
||||
# Move items, same selected, move down -> allowed
|
||||
tabStatus._moveItem(1)
|
||||
assert [x["key"] for x in tabStatus.getNewList()[0]] == [
|
||||
C.sNew, C.sDraft, C.sFinished, None
|
||||
]
|
||||
|
||||
# Importance Tab
|
||||
# ==============
|
||||
|
||||
projEdit._tabBox.setCurrentWidget(projEdit.tabImport)
|
||||
projEdit.tabStatus.listBox.clearSelection()
|
||||
projEdit.tabImport.listBox.topLevelItem(3).setSelected(True)
|
||||
qtbot.mouseClick(projEdit.tabImport.delButton, Qt.LeftButton)
|
||||
qtbot.mouseClick(projEdit.tabImport.addButton, Qt.LeftButton)
|
||||
projEdit.tabStatus.listBox.clearSelection()
|
||||
projEdit.tabImport.listBox.topLevelItem(3).setSelected(True)
|
||||
for n in range(8):
|
||||
qtbot.keyClick(projEdit.tabImport.editName, Qt.Key_Backspace, delay=typeDelay)
|
||||
for c in "Final":
|
||||
qtbot.keyClick(projEdit.tabImport.editName, c, delay=typeDelay)
|
||||
qtbot.mouseClick(projEdit.tabImport.saveButton, Qt.LeftButton)
|
||||
qtbot.wait(stepDelay)
|
||||
tabImport = projSettings.tabImport
|
||||
projSettings._focusTab(GuiProjectSettings.TAB_IMPORT)
|
||||
|
||||
# Delete unused entry
|
||||
tabImport.listBox.clearSelection()
|
||||
tabImport.listBox.setCurrentItem(tabImport.listBox.topLevelItem(1))
|
||||
qtbot.mouseClick(tabImport.delButton, Qt.LeftButton)
|
||||
assert tabImport.listBox.topLevelItemCount() == 3
|
||||
|
||||
# Add a new entry
|
||||
with monkeypatch.context() as mp:
|
||||
mp.setattr(QColorDialog, "getColor", lambda *a: QColor(20, 30, 40))
|
||||
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
|
||||
# ================
|
||||
|
||||
qtbot.wait(stepDelay)
|
||||
projEdit._tabBox.setCurrentWidget(projEdit.tabReplace)
|
||||
tabReplace = projSettings.tabReplace
|
||||
|
||||
assert projEdit.tabReplace.listBox.topLevelItem(0).text(0) == "<A>"
|
||||
assert projEdit.tabReplace.listBox.topLevelItem(0).text(1) == "B"
|
||||
assert projEdit.tabReplace.listBox.topLevelItem(1).text(0) == "<C>"
|
||||
assert projEdit.tabReplace.listBox.topLevelItem(1).text(1) == "D"
|
||||
assert tabReplace.listBox.topLevelItem(0).text(0) == "<A>"
|
||||
assert tabReplace.listBox.topLevelItem(0).text(1) == "B"
|
||||
assert tabReplace.listBox.topLevelItem(1).text(0) == "<C>"
|
||||
assert tabReplace.listBox.topLevelItem(1).text(1) == "D"
|
||||
assert tabReplace.listBox.topLevelItemCount() == 2
|
||||
|
||||
qtbot.mouseClick(projEdit.tabReplace.addButton, Qt.LeftButton)
|
||||
projEdit.tabReplace.listBox.topLevelItem(2).setSelected(True)
|
||||
projEdit.tabReplace.editKey.setText("")
|
||||
# Nothing to save or delete
|
||||
tabReplace.listBox.clearSelection()
|
||||
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 ":
|
||||
qtbot.keyClick(projEdit.tabReplace.editKey, c, delay=typeDelay)
|
||||
projEdit.tabReplace.editValue.setText("")
|
||||
qtbot.keyClick(tabReplace.editKey, c, delay=typeDelay)
|
||||
tabReplace.editValue.setText("")
|
||||
for c in "With This Stuff ":
|
||||
qtbot.keyClick(projEdit.tabReplace.editValue, c, delay=typeDelay)
|
||||
qtbot.mouseClick(projEdit.tabReplace.saveButton, Qt.LeftButton)
|
||||
qtbot.keyClick(tabReplace.editValue, c, delay=typeDelay)
|
||||
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)
|
||||
projEdit.tabReplace.listBox.clearSelection()
|
||||
assert not projEdit.tabReplace._saveEntry()
|
||||
assert not projEdit.tabReplace._delEntry()
|
||||
qtbot.mouseClick(projEdit.tabReplace.addButton, Qt.LeftButton)
|
||||
# Create a new entry again
|
||||
tabReplace.listBox.clearSelection()
|
||||
qtbot.mouseClick(tabReplace.addButton, Qt.LeftButton)
|
||||
assert tabReplace.listBox.topLevelItemCount() == 4
|
||||
|
||||
# The list is sorted, so we must find it
|
||||
newIdx = -1
|
||||
for i in range(projEdit.tabReplace.listBox.topLevelItemCount()):
|
||||
if projEdit.tabReplace.listBox.topLevelItem(i).text(0) == "<keyword4>":
|
||||
for i in range(tabReplace.listBox.topLevelItemCount()):
|
||||
if tabReplace.listBox.topLevelItem(i).text(0) == "<keyword4>":
|
||||
newIdx = i
|
||||
break
|
||||
|
||||
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
|
||||
nwGUI.mainMenu.aProjectSettings.activate(QAction.Trigger)
|
||||
qtbot.waitUntil(lambda: getGuiItem("GuiProjectSettings") is not None, timeout=1000)
|
||||
# Clean up
|
||||
# qtbot.stop()
|
||||
projSettings._doClose()
|
||||
|
||||
projEdit = getGuiItem("GuiProjectSettings")
|
||||
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
|
||||
# END Test testDlgProjSettings_Replace
|
||||
|
||||
@@ -1144,7 +1144,7 @@ def testGuiEditor_Tags(qtbot, monkeypatch, nwGUI, nwMinimal, ipsumText):
|
||||
assert nwGUI.openDocument(cHandle) is True
|
||||
assert nwGUI.docEditor.replaceText(theText) is True
|
||||
assert nwGUI.saveDocument() is True
|
||||
assert nwGUI.projView.revealNewTreeItem(cHandle)
|
||||
assert nwGUI.projView.projTree.revealNewTreeItem(cHandle)
|
||||
nwGUI.docEditor.updateTagHighLighting()
|
||||
|
||||
# Follow Tag
|
||||
|
||||
@@ -183,7 +183,7 @@ def testGuiOutline_Content(qtbot, monkeypatch, nwGUI, nwLipsum):
|
||||
|
||||
# Add a second novel folder
|
||||
newHandle = nwGUI.theProject.newRoot(nwItemClass.NOVEL)
|
||||
nwGUI.projView.revealNewTreeItem(newHandle)
|
||||
nwGUI.projView.projTree.revealNewTreeItem(newHandle)
|
||||
|
||||
# Check new values in dropdown list
|
||||
assert outlineBar.novelValue.itemData(0) == lipHandle
|
||||
@@ -202,7 +202,7 @@ def testGuiOutline_Content(qtbot, monkeypatch, nwGUI, nwLipsum):
|
||||
aHandle = nwGUI.theProject.newFile(dTitle, newHandle)
|
||||
hHash = "#"*hLevel
|
||||
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()
|
||||
|
||||
|
||||
@@ -160,11 +160,11 @@ def testGuiProjTree_NewItems(qtbot, caplog, monkeypatch, nwGUI, fncDir, mockRnd)
|
||||
# ============
|
||||
|
||||
# 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
|
||||
nHandle = theProject.newFile("Test", None)
|
||||
assert projView.revealNewTreeItem(nHandle) is False
|
||||
assert projView.projTree.revealNewTreeItem(nHandle) is False
|
||||
|
||||
# Clean up
|
||||
# qtbot.stop()
|
||||
@@ -184,10 +184,11 @@ def testGuiProjTree_MoveItems(qtbot, monkeypatch, nwGUI, fncDir, mockRnd):
|
||||
monkeypatch.setattr(QMessageBox, "information", lambda *a: QMessageBox.Yes)
|
||||
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
|
||||
assert nwTree.projTree.moveTreeItem(1) is False
|
||||
assert projView.projTree.moveTreeItem(1) is False
|
||||
|
||||
# Create a project
|
||||
prjDir = os.path.join(fncDir, "project")
|
||||
@@ -197,68 +198,68 @@ def testGuiProjTree_MoveItems(qtbot, monkeypatch, nwGUI, fncDir, mockRnd):
|
||||
# ==============
|
||||
|
||||
# Add some files
|
||||
nwTree.setSelectedHandle(C.hChapterDir)
|
||||
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(C.hChapterDir) == [
|
||||
projView.setSelectedHandle(C.hChapterDir)
|
||||
assert projTree.newTreeItem(nwItemType.FILE) is True
|
||||
assert projTree.newTreeItem(nwItemType.FILE) is True
|
||||
assert projTree.newTreeItem(nwItemType.FILE) is True
|
||||
assert projTree.getTreeFromHandle(C.hChapterDir) == [
|
||||
C.hChapterDir, C.hChapterDoc, C.hSceneDoc,
|
||||
"0000000000010", "0000000000011", "0000000000012",
|
||||
]
|
||||
|
||||
# Move with no selections
|
||||
nwTree.projTree.clearSelection()
|
||||
assert nwTree.projTree.moveTreeItem(1) is False
|
||||
projTree.clearSelection()
|
||||
assert projTree.moveTreeItem(1) is False
|
||||
|
||||
# Move second item up twice (should give same result)
|
||||
nwTree.setSelectedHandle(C.hSceneDoc)
|
||||
assert nwTree.projTree.moveTreeItem(-1) is True
|
||||
assert nwTree.getTreeFromHandle(C.hChapterDir) == [
|
||||
projView.setSelectedHandle(C.hSceneDoc)
|
||||
assert projTree.moveTreeItem(-1) is True
|
||||
assert projTree.getTreeFromHandle(C.hChapterDir) == [
|
||||
C.hChapterDir, C.hSceneDoc, C.hChapterDoc,
|
||||
"0000000000010", "0000000000011", "0000000000012",
|
||||
]
|
||||
assert nwTree.projTree.moveTreeItem(-1) is False
|
||||
assert nwTree.getTreeFromHandle(C.hChapterDir) == [
|
||||
assert projTree.moveTreeItem(-1) is False
|
||||
assert projTree.getTreeFromHandle(C.hChapterDir) == [
|
||||
C.hChapterDir, C.hSceneDoc, C.hChapterDoc,
|
||||
"0000000000010", "0000000000011", "0000000000012",
|
||||
]
|
||||
|
||||
# Restore
|
||||
assert nwTree.projTree.moveTreeItem(1) is True
|
||||
assert nwTree.getTreeFromHandle(C.hChapterDir) == [
|
||||
assert projTree.moveTreeItem(1) is True
|
||||
assert projTree.getTreeFromHandle(C.hChapterDir) == [
|
||||
C.hChapterDir, C.hChapterDoc, C.hSceneDoc,
|
||||
"0000000000010", "0000000000011", "0000000000012",
|
||||
]
|
||||
|
||||
# Move fifth item down twice (should give same result)
|
||||
nwTree.setSelectedHandle("0000000000011")
|
||||
assert nwTree.projTree.moveTreeItem(1) is True
|
||||
assert nwTree.getTreeFromHandle(C.hChapterDir) == [
|
||||
projView.setSelectedHandle("0000000000011")
|
||||
assert projTree.moveTreeItem(1) is True
|
||||
assert projTree.getTreeFromHandle(C.hChapterDir) == [
|
||||
C.hChapterDir, C.hChapterDoc, C.hSceneDoc,
|
||||
"0000000000010", "0000000000012", "0000000000011",
|
||||
]
|
||||
assert nwTree.projTree.moveTreeItem(1) is False
|
||||
assert nwTree.getTreeFromHandle(C.hChapterDir) == [
|
||||
assert projTree.moveTreeItem(1) is False
|
||||
assert projTree.getTreeFromHandle(C.hChapterDir) == [
|
||||
C.hChapterDir, C.hChapterDoc, C.hSceneDoc,
|
||||
"0000000000010", "0000000000012", "0000000000011",
|
||||
]
|
||||
|
||||
# Restore
|
||||
assert nwTree.projTree.moveTreeItem(-1) is True
|
||||
assert nwTree.getTreeFromHandle(C.hChapterDir) == [
|
||||
assert projTree.moveTreeItem(-1) is True
|
||||
assert projTree.getTreeFromHandle(C.hChapterDir) == [
|
||||
C.hChapterDir, C.hChapterDoc, C.hSceneDoc,
|
||||
"0000000000010", "0000000000011", "0000000000012",
|
||||
]
|
||||
|
||||
# Move down again, and restore via undo
|
||||
nwTree.setSelectedHandle("0000000000011")
|
||||
assert nwTree.projTree.moveTreeItem(1) is True
|
||||
assert nwTree.getTreeFromHandle(C.hChapterDir) == [
|
||||
projView.setSelectedHandle("0000000000011")
|
||||
assert projTree.moveTreeItem(1) is True
|
||||
assert projTree.getTreeFromHandle(C.hChapterDir) == [
|
||||
C.hChapterDir, C.hChapterDoc, C.hSceneDoc,
|
||||
"0000000000010", "0000000000012", "0000000000011",
|
||||
]
|
||||
assert nwTree.projTree.undoLastMove() is True
|
||||
assert nwTree.getTreeFromHandle(C.hChapterDir) == [
|
||||
assert projTree.undoLastMove() is True
|
||||
assert projTree.getTreeFromHandle(C.hChapterDir) == [
|
||||
C.hChapterDir, C.hChapterDoc, C.hSceneDoc,
|
||||
"0000000000010", "0000000000011", "0000000000012",
|
||||
]
|
||||
@@ -266,19 +267,19 @@ def testGuiProjTree_MoveItems(qtbot, monkeypatch, nwGUI, fncDir, mockRnd):
|
||||
# Root Folder
|
||||
# ===========
|
||||
|
||||
nwTree.setSelectedHandle(C.hNovelRoot)
|
||||
projView.setSelectedHandle(C.hNovelRoot)
|
||||
assert nwGUI.theProject.tree._treeOrder.index(C.hNovelRoot) == 0
|
||||
|
||||
# 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
|
||||
|
||||
# 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
|
||||
|
||||
# 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
|
||||
|
||||
# Clean up
|
||||
@@ -299,76 +300,77 @@ def testGuiProjTree_RequestDeleteItem(qtbot, caplog, monkeypatch, nwGUI, fncDir,
|
||||
monkeypatch.setattr(QMessageBox, "information", lambda *a: QMessageBox.Yes)
|
||||
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
|
||||
assert nwView.requestDeleteItem() is False
|
||||
assert projView.requestDeleteItem() is False
|
||||
|
||||
# Create a project
|
||||
prjDir = os.path.join(fncDir, "project")
|
||||
buildTestProject(nwGUI, prjDir)
|
||||
|
||||
# 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
|
||||
nwView.setSelectedHandle(C.hChapterDir)
|
||||
assert nwView.projTree.newTreeItem(nwItemType.FILE) is True
|
||||
assert nwView.projTree.newTreeItem(nwItemType.FILE) is True
|
||||
assert nwView.projTree.newTreeItem(nwItemType.FILE) is True
|
||||
assert nwView.getTreeFromHandle(C.hChapterDir) == [
|
||||
projView.setSelectedHandle(C.hChapterDir)
|
||||
assert projTree.newTreeItem(nwItemType.FILE) is True
|
||||
assert projTree.newTreeItem(nwItemType.FILE) is True
|
||||
assert projTree.newTreeItem(nwItemType.FILE) is True
|
||||
assert projTree.getTreeFromHandle(C.hChapterDir) == [
|
||||
C.hChapterDir, C.hChapterDoc, C.hSceneDoc,
|
||||
"0000000000010", "0000000000011", "0000000000012",
|
||||
]
|
||||
|
||||
# Delete item without focus -> blocked
|
||||
monkeypatch.setattr(GuiProjectTree, "hasFocus", lambda *a: False)
|
||||
nwView.setSelectedHandle("0000000000012")
|
||||
assert nwView.requestDeleteItem() is False
|
||||
projView.setSelectedHandle("0000000000012")
|
||||
assert projView.requestDeleteItem() is False
|
||||
monkeypatch.setattr(GuiProjectTree, "hasFocus", lambda *a: True)
|
||||
|
||||
# No selection made
|
||||
nwView.projTree.clearSelection()
|
||||
projTree.clearSelection()
|
||||
caplog.clear()
|
||||
assert nwView.requestDeleteItem() is False
|
||||
assert projView.requestDeleteItem() is False
|
||||
assert "no item to delete" in caplog.text
|
||||
|
||||
# Not a valid handle
|
||||
nwView.projTree.clearSelection()
|
||||
projTree.clearSelection()
|
||||
caplog.clear()
|
||||
assert nwView.requestDeleteItem("0000000000000") is False
|
||||
assert projView.requestDeleteItem("0000000000000") is False
|
||||
assert "No tree item with handle '0000000000000'" in caplog.text
|
||||
|
||||
# Delete Root Folders
|
||||
# ===================
|
||||
|
||||
assert nwView.requestDeleteItem(C.hNovelRoot) is False # Novel Root is blocked
|
||||
assert nwView.requestDeleteItem(C.hCharRoot) is True # Character Root
|
||||
assert projView.requestDeleteItem(C.hNovelRoot) is False # Novel Root is blocked
|
||||
assert projView.requestDeleteItem(C.hCharRoot) is True # Character Root
|
||||
|
||||
# Delete File
|
||||
# ===========
|
||||
|
||||
# Block adding trash folder
|
||||
funcPointer = nwView.projTree._addTrashRoot
|
||||
nwView.projTree._addTrashRoot = lambda *a: None
|
||||
assert nwView.requestDeleteItem("0000000000012") is False
|
||||
nwView.projTree._addTrashRoot = funcPointer
|
||||
funcPointer = projTree._addTrashRoot
|
||||
projTree._addTrashRoot = lambda *a: None
|
||||
assert projView.requestDeleteItem("0000000000012") is False
|
||||
projTree._addTrashRoot = funcPointer
|
||||
|
||||
# Delete last two documents, which also adds the trash folder
|
||||
assert nwView.requestDeleteItem("0000000000012") is True
|
||||
assert nwView.requestDeleteItem("0000000000011") is True
|
||||
assert nwView.getTreeFromHandle(C.hChapterDir) == [
|
||||
assert projView.requestDeleteItem("0000000000012") is True
|
||||
assert projView.requestDeleteItem("0000000000011") is True
|
||||
assert projTree.getTreeFromHandle(C.hChapterDir) == [
|
||||
C.hChapterDir, C.hChapterDoc, C.hSceneDoc,
|
||||
"0000000000010"
|
||||
]
|
||||
trashHandle = nwGUI.theProject.tree.trashRoot()
|
||||
assert nwView.getTreeFromHandle(trashHandle) == [
|
||||
assert projTree.getTreeFromHandle(trashHandle) == [
|
||||
trashHandle, "0000000000012", "0000000000011"
|
||||
]
|
||||
|
||||
# Try to delete the trash folder
|
||||
caplog.clear()
|
||||
assert nwView.requestDeleteItem("0000000000013") is False
|
||||
assert projView.requestDeleteItem("0000000000013") is False
|
||||
assert "Cannot delete the Trash folder" in caplog.text
|
||||
|
||||
nwGUI.closeProject()
|
||||
|
||||
@@ -40,7 +40,7 @@ def testGuiStatusBar_Main(qtbot, monkeypatch, nwGUI, fncProj, mockRnd):
|
||||
cHandle = nwGUI.theProject.newFile("A Note", C.hCharRoot)
|
||||
newDoc = NWDoc(nwGUI.theProject, cHandle)
|
||||
newDoc.writeDocument("# A Note\n\n")
|
||||
nwGUI.projView.revealNewTreeItem(cHandle)
|
||||
nwGUI.projView.projTree.revealNewTreeItem(cHandle)
|
||||
nwGUI.rebuildIndex(beQuiet=True)
|
||||
|
||||
# Reference Time
|
||||
|
||||
@@ -30,6 +30,17 @@ XML_IGNORE = ("<novelWriterXML", "<saveCount", "<autoCount", "<editTime")
|
||||
|
||||
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
|
||||
hInvalid = "0000000000000"
|
||||
hNovelRoot = "0000000000008"
|
||||
|
||||
Reference in New Issue
Block a user