From 624787994aa503b1f3eb3eec672fa99d741c1a11 Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Tue, 25 Oct 2022 08:17:18 +0200 Subject: [PATCH 01/36] Refactor the projhect tree context menu code --- novelwriter/gui/projtree.py | 77 +++++++++++++++---------------------- 1 file changed, 30 insertions(+), 47 deletions(-) diff --git a/novelwriter/gui/projtree.py b/novelwriter/gui/projtree.py index 96dc98f1..202efee0 100644 --- a/novelwriter/gui/projtree.py +++ b/novelwriter/gui/projtree.py @@ -1128,9 +1128,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 @@ -1143,12 +1142,12 @@ class GuiProjectTree(QTreeWidget): 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,14 +1155,12 @@ 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)) if tItem.isNovelLike(): mStatus = ctxMenu.addMenu(self.tr("Set Status to ...")) @@ -1193,38 +1190,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 +1221,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)) From e89f00453294a9ff16cc044a6553d5ed3713dba4 Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Tue, 25 Oct 2022 12:20:18 +0200 Subject: [PATCH 02/36] Add a manage labels option to project tree context menu (#1203) --- novelwriter/custom.py | 6 ++++++ novelwriter/dialogs/projsettings.py | 23 ++++++++++++++++++++++- novelwriter/gui/projtree.py | 15 ++++++++++++++- novelwriter/guimain.py | 6 ++++-- 4 files changed, 46 insertions(+), 4 deletions(-) diff --git a/novelwriter/custom.py b/novelwriter/custom.py index d516e2dd..5d9d8122 100644 --- a/novelwriter/custom.py +++ b/novelwriter/custom.py @@ -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 diff --git a/novelwriter/dialogs/projsettings.py b/novelwriter/dialogs/projsettings.py index 86c268d6..5760d982 100644 --- a/novelwriter/dialogs/projsettings.py +++ b/novelwriter/dialogs/projsettings.py @@ -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 ...") @@ -85,6 +90,9 @@ class GuiProjectSettings(PagedDialog): # Flags self.spellChanged = False + # Focus Tab + self._focusTab(focusTab) + logger.debug("GuiProjectSettings initialisation complete") return @@ -141,6 +149,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. """ diff --git a/novelwriter/gui/projtree.py b/novelwriter/gui/projtree.py index 202efee0..f4b60161 100644 --- a/novelwriter/gui/projtree.py +++ b/novelwriter/gui/projtree.py @@ -41,7 +41,7 @@ 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.dialogs import GuiDocMerge, GuiDocSplit, GuiEditLabel, GuiProjectSettings from novelwriter.constants import nwHeaders, 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) @@ -1169,6 +1172,11 @@ class GuiProjectTree(QTreeWidget): 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()): @@ -1176,6 +1184,11 @@ class GuiProjectTree(QTreeWidget): 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 # ============== diff --git a/novelwriter/guimain.py b/novelwriter/guimain.py index fee7c7d6..4025dd05 100644 --- a/novelwriter/guimain.py +++ b/novelwriter/guimain.py @@ -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) @@ -921,14 +922,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: From 2ee6adcf352de52113806753a767a9189a2f6c54 Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Tue, 25 Oct 2022 15:47:40 +0200 Subject: [PATCH 03/36] Clean up function mappings in project tree class --- novelwriter/gui/projtree.py | 13 ++- novelwriter/guimain.py | 12 +-- tests/test_gui/test_gui_doceditor.py | 2 +- tests/test_gui/test_gui_outline.py | 4 +- tests/test_gui/test_gui_projtree.py | 120 ++++++++++++++------------- tests/test_gui/test_gui_statusbar.py | 2 +- 6 files changed, 79 insertions(+), 74 deletions(-) diff --git a/novelwriter/gui/projtree.py b/novelwriter/gui/projtree.py index f4b60161..bd77a57a 100644 --- a/novelwriter/gui/projtree.py +++ b/novelwriter/gui/projtree.py @@ -105,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 @@ -164,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 ## diff --git a/novelwriter/guimain.py b/novelwriter/guimain.py index 4025dd05..cf0a9dff 100644 --- a/novelwriter/guimain.py +++ b/novelwriter/guimain.py @@ -807,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. diff --git a/tests/test_gui/test_gui_doceditor.py b/tests/test_gui/test_gui_doceditor.py index b3bc49b1..68f02c71 100644 --- a/tests/test_gui/test_gui_doceditor.py +++ b/tests/test_gui/test_gui_doceditor.py @@ -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 diff --git a/tests/test_gui/test_gui_outline.py b/tests/test_gui/test_gui_outline.py index 2f33cf7a..457be313 100644 --- a/tests/test_gui/test_gui_outline.py +++ b/tests/test_gui/test_gui_outline.py @@ -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() diff --git a/tests/test_gui/test_gui_projtree.py b/tests/test_gui/test_gui_projtree.py index b5201846..ecf78341 100644 --- a/tests/test_gui/test_gui_projtree.py +++ b/tests/test_gui/test_gui_projtree.py @@ -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() diff --git a/tests/test_gui/test_gui_statusbar.py b/tests/test_gui/test_gui_statusbar.py index 6c99642f..e2e9a5bb 100644 --- a/tests/test_gui/test_gui_statusbar.py +++ b/tests/test_gui/test_gui_statusbar.py @@ -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 From 7b04d576056469f004a7c04362c493d70f51bbf4 Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Tue, 25 Oct 2022 15:48:29 +0200 Subject: [PATCH 04/36] Refactor project settings test --- novelwriter/dialogs/projsettings.py | 8 +- .../guiProjSettings_Dialog_nwProject.nwx | 83 --- tests/test_dialogs/test_dlg_projsettings.py | 492 ++++++++++++------ tests/tools.py | 11 + 4 files changed, 351 insertions(+), 243 deletions(-) delete mode 100644 tests/reference/guiProjSettings_Dialog_nwProject.nwx diff --git a/novelwriter/dialogs/projsettings.py b/novelwriter/dialogs/projsettings.py index 5760d982..eb96ffb2 100644 --- a/novelwriter/dialogs/projsettings.py +++ b/novelwriter/dialogs/projsettings.py @@ -88,7 +88,7 @@ class GuiProjectSettings(PagedDialog): self.addControls(self.buttonBox) # Flags - self.spellChanged = False + self._spellChanged = False # Focus Tab self._focusTab(focusTab) @@ -97,6 +97,10 @@ class GuiProjectSettings(PagedDialog): return + @property + def spellChanged(self): + return self._spellChanged + ## # Slots ## @@ -116,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() diff --git a/tests/reference/guiProjSettings_Dialog_nwProject.nwx b/tests/reference/guiProjSettings_Dialog_nwProject.nwx deleted file mode 100644 index 307aae55..00000000 --- a/tests/reference/guiProjSettings_Dialog_nwProject.nwx +++ /dev/null @@ -1,83 +0,0 @@ - - - - Project Name - Project Title - Jane Doe - John Doh - 1 - 1 - 0 - - - True - None - False - en - None - None - None - None - 9 - 9 - 0 - - B - D - With This Stuff - - - %title% - %title% - %title% - * * * -
-
- - New - Note - Finished - Final - - - New - Minor - Major - Final - -
- - - - Novel - - - - Title Page - - - - New Chapter - - - - New Chapter - - - - New Scene - - - - Plot - - - - Characters - - - - World - - -
diff --git a/tests/test_dialogs/test_dlg_projsettings.py b/tests/test_dialogs/test_dlg_projsettings.py index d194229c..48e896bf 100644 --- a/tests/test_dialogs/test_dlg_projsettings.py +++ b/tests/test_dialogs/test_dlg_projsettings.py @@ -19,56 +19,46 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . """ -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) == "" - assert projEdit.tabReplace.listBox.topLevelItem(0).text(1) == "B" - assert projEdit.tabReplace.listBox.topLevelItem(1).text(0) == "" - assert projEdit.tabReplace.listBox.topLevelItem(1).text(1) == "D" + assert tabReplace.listBox.topLevelItem(0).text(0) == "" + assert tabReplace.listBox.topLevelItem(0).text(1) == "B" + assert tabReplace.listBox.topLevelItem(1).text(0) == "" + 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) == "" + 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) == "" + 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) == "": + for i in range(tabReplace.listBox.topLevelItemCount()): + if tabReplace.listBox.topLevelItem(i).text(0) == "": 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 diff --git a/tests/tools.py b/tests/tools.py index 4744d472..4a97c930 100644 --- a/tests/tools.py +++ b/tests/tools.py @@ -30,6 +30,17 @@ XML_IGNORE = (" Date: Tue, 25 Oct 2022 16:19:25 +0200 Subject: [PATCH 05/36] Add checkmark for current active status or iumportance value (#1202) --- novelwriter/gui/projtree.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/novelwriter/gui/projtree.py b/novelwriter/gui/projtree.py index bd77a57a..e34419d1 100644 --- a/novelwriter/gui/projtree.py +++ b/novelwriter/gui/projtree.py @@ -42,7 +42,7 @@ 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, GuiProjectSettings -from novelwriter.constants import nwHeaders, trConst, nwLabels +from novelwriter.constants import nwHeaders, nwUnicode, trConst, nwLabels logger = logging.getLogger(__name__) @@ -1121,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") @@ -1149,7 +1151,6 @@ class GuiProjectTree(QTreeWidget): isRoot = tItem.isRootType() isFolder = tItem.isFolderType() isFile = tItem.isFileType() - hasChild = selItem.childCount() > 0 if isFile: aOpenDoc = ctxMenu.addAction(self.tr("Open Document")) @@ -1172,10 +1173,12 @@ class GuiProjectTree(QTreeWidget): 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) ) @@ -1187,7 +1190,8 @@ class GuiProjectTree(QTreeWidget): 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) ) From a5cbaa7f8cd136f28a7ad1bab42324a9c42f3db1 Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Tue, 25 Oct 2022 16:58:46 +0200 Subject: [PATCH 06/36] Update requirements and add Python 3.11 to CI --- .github/workflows/test_linux.yml | 2 +- requirements.txt | 2 +- setup.cfg | 3 ++- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/.github/workflows/test_linux.yml b/.github/workflows/test_linux.yml index 5cac8e23..6dcf160f 100644 --- a/.github/workflows/test_linux.yml +++ b/.github/workflows/test_linux.yml @@ -14,7 +14,7 @@ jobs: testLinux: strategy: matrix: - python-version: ["3.7", "3.8", "3.9", "3.10"] + python-version: ["3.7", "3.8", "3.9", "3.10", "3.11"] runs-on: ubuntu-latest steps: - name: Python Setup diff --git a/requirements.txt b/requirements.txt index 4c10636b..b47e1e91 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,3 @@ -pyqt5>=5.3 +pyqt5>=5.10 lxml>=4.2.0 pyenchant>=3.0.0 diff --git a/setup.cfg b/setup.cfg index b88b656b..fc808a1e 100644 --- a/setup.cfg +++ b/setup.cfg @@ -15,6 +15,7 @@ classifiers = Programming Language :: Python :: 3.8 Programming Language :: Python :: 3.9 Programming Language :: Python :: 3.10 + Programming Language :: Python :: 3.11 Programming Language :: Python :: Implementation :: CPython License :: OSI Approved :: GNU General Public License v3 (GPLv3) Development Status :: 5 - Production/Stable @@ -32,7 +33,7 @@ python_requires = >=3.7 include_package_data = True packages = find: install_requires = - pyqt5>=5.3 + pyqt5>=5.10 lxml>=4.2.0 pyenchant>=3.0.0 From dc0f9abdbc19cb4c9293c171b12ed466ca0dbf2d Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Tue, 25 Oct 2022 18:12:29 +0200 Subject: [PATCH 07/36] Fix lookup of tags with spaces (#1195) --- novelwriter/gui/doceditor.py | 35 +++++++++++++++++++++++------------ 1 file changed, 23 insertions(+), 12 deletions(-) diff --git a/novelwriter/gui/doceditor.py b/novelwriter/gui/doceditor.py index 83d971d5..e1ed9816 100644 --- a/novelwriter/gui/doceditor.py +++ b/novelwriter/gui/doceditor.py @@ -1871,10 +1871,10 @@ class GuiDocEditor(QTextEdit): def _followTag(self, theCursor=None, loadTag=True): """Activated by Ctrl+Enter. Checks that we're in a block - starting with '@'. We then find the word under the cursor and - check that it is after the ':'. If all this is fine, we have a - tag and can tell the document viewer to try and find and load - the file where the tag is defined. + starting with '@'. We then find the tag under the cursor and + check that it is not the tag itself. If all this is fine, we + have a tag and can tell the document viewer to try and find and + load the file where the tag is defined. """ if theCursor is None: theCursor = self.textCursor() @@ -1887,18 +1887,29 @@ class GuiDocEditor(QTextEdit): if theText.startswith("@"): - theCursor.select(QTextCursor.WordUnderCursor) - theWord = theCursor.selectedText() - cPos = theText.find(":") - wPos = theCursor.selectionStart() - theBlock.position() - if wPos <= cPos: + isGood, tBits, tPos = self.theParent.theIndex.scanThis(theText) + if not isGood: + return False + + theTag = "" + cPos = theCursor.selectionStart() - theBlock.position() + for sTag, sPos in zip(reversed(tBits), reversed(tPos)): + if cPos >= sPos: + # The cursor is between the start of two tags + if cPos <= sPos + len(sTag): + # The cursor is inside or at the edge of the tag + theTag = sTag + break + + if not theTag or theTag.startswith("@"): + # The keyword cannot be looked up, so we ignore that return False if loadTag: - logger.verbose("Attempting to follow tag '%s'", theWord) - self.theParent.docViewer.loadFromTag(theWord) + logger.verbose("Attempting to follow tag '%s'", theTag) + self.theParent.docViewer.loadFromTag(theTag) else: - logger.verbose("Potential tag '%s'", theWord) + logger.verbose("Potential tag '%s'", theTag) return True From 4862ac385761bf84c6532751284d00bd4810977a Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Tue, 25 Oct 2022 18:26:37 +0200 Subject: [PATCH 08/36] Bump the version number and update the changelog --- CHANGELOG.md | 19 ++++++++++++++++++- novelwriter/__init__.py | 6 +++--- novelwriter/assets/text/release_notes.htm | 7 ++++++- sample/nwProject.nwx | 8 ++++---- 4 files changed, 31 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 300f82fd..e27e65af 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,10 +1,27 @@ # novelWriter Changelog +## Version 1.6.6 [2022-10-25] + +### Release Notes + +This is a bugfix release that fixes a minor issues with following tags in the editor. It is now +possible to also follow tags that contain spaces. + +### Detailed Changelog + +**Bugfixes** + +* Fix a bug where only the word under the cursor would be looked up when the user tried to follow a + tag in the editor. The lookup function now uses the same parser for the `@`-line as the syntax + highlighter does, so they should behave consistently. Issue #1195. PR #1209. + +---- + ## Version 1.6.5 [2022-10-13] ### Release Notes -This is a bugfix release that fixes a a few minor issues. The idle time for new projects would be +This is a bugfix release that fixes a few minor issues. The idle time for new projects would be artificially inflated as the clock was not reset when the project was first created. This only affects the first entry in the writing statistics. A scaling issue for the Preferences dialog has also been fixed. It only affected screens with UI scaling enabled. Lastly, typing `Shift+Enter` in diff --git a/novelwriter/__init__.py b/novelwriter/__init__.py index 2b40a55f..b76e94ec 100644 --- a/novelwriter/__init__.py +++ b/novelwriter/__init__.py @@ -60,9 +60,9 @@ __license__ = "GPLv3" __author__ = "Veronica Berglyd Olsen" __maintainer__ = "Veronica Berglyd Olsen" __email__ = "code@vkbo.net" -__version__ = "1.6.5" -__hexversion__ = "0x010605f0" -__date__ = "2022-10-13" +__version__ = "1.6.6" +__hexversion__ = "0x010606f0" +__date__ = "2022-10-25" __status__ = "Stable" __domain__ = "novelwriter.io" __url__ = "https://novelwriter.io" diff --git a/novelwriter/assets/text/release_notes.htm b/novelwriter/assets/text/release_notes.htm index 34c423a8..1db03b01 100644 --- a/novelwriter/assets/text/release_notes.htm +++ b/novelwriter/assets/text/release_notes.htm @@ -72,7 +72,7 @@ text cursor sometimes disappears when reaching the right-hand edge of the text e

Patch 1.6.5 – 13 October 2022

-

This is a bugfix release that fixes a a few minor issues. The idle time for new projects would +

This is a bugfix release that fixes a few minor issues. The idle time for new projects would be artificially inflated as the clock was not reset when the project was first created. This only affects the first entry in the writing statistics. A scaling issue for the Preferences dialog has also been fixed. It only affected screens with UI scaling enabled. Lastly, typing Shift+Enter in @@ -80,5 +80,10 @@ the text editor now creates a regular line break instead of a special line separ separator serves no purpose in plain text, and was producing inconsistencies in how text is processed and displayed.

+

Patch 1.6.6 – 25 October 2022

+ +

This is a bugfix release that fixes a minor issues with following tags in the editor. It is now +possible to also follow tags that contain spaces.

+ diff --git a/sample/nwProject.nwx b/sample/nwProject.nwx index 1a9abc5a..74b42a6c 100644 --- a/sample/nwProject.nwx +++ b/sample/nwProject.nwx @@ -1,13 +1,13 @@ - + Sample Project Sample Project Jane Smith Jay Doh - 1306 + 1312 199 - 65126 + 65207 False @@ -121,7 +121,7 @@ 2429 432 14 - 219 + 62 Another Scene From 25c4330fd0c7bb2a9b4bfd9ad6de1ab21396156a Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Tue, 25 Oct 2022 21:27:55 +0200 Subject: [PATCH 09/36] Make icon theme a part of the GUI theme (solves #1172) --- novelwriter/assets/themes/default.conf | 3 +- novelwriter/assets/themes/default_dark.conf | 1 + novelwriter/assets/themes/solarized_dark.conf | 1 + .../assets/themes/solarized_light.conf | 1 + novelwriter/config.py | 6 ---- novelwriter/dialogs/preferences.py | 19 ------------- novelwriter/gui/theme.py | 28 ++++--------------- tests/test_base/test_base_config.py | 26 ----------------- tests/test_gui/test_gui_theme.py | 2 +- 9 files changed, 11 insertions(+), 76 deletions(-) diff --git a/novelwriter/assets/themes/default.conf b/novelwriter/assets/themes/default.conf index 004b811d..db780c75 100644 --- a/novelwriter/assets/themes/default.conf +++ b/novelwriter/assets/themes/default.conf @@ -1,2 +1,3 @@ [Main] -name = Default System Theme +name = Default Theme +icontheme = typicons_light diff --git a/novelwriter/assets/themes/default_dark.conf b/novelwriter/assets/themes/default_dark.conf index 44317ac1..97039825 100644 --- a/novelwriter/assets/themes/default_dark.conf +++ b/novelwriter/assets/themes/default_dark.conf @@ -5,6 +5,7 @@ credit = Veronica Berglyd Olsen url = https://github.com/vkbo/novelWriter license = CC BY-SA 4.0 licenseurl = https://creativecommons.org/licenses/by-sa/4.0/ +icontheme = typicons_dark [Palette] window = 54, 54, 54 diff --git a/novelwriter/assets/themes/solarized_dark.conf b/novelwriter/assets/themes/solarized_dark.conf index 812d968d..cf46d519 100644 --- a/novelwriter/assets/themes/solarized_dark.conf +++ b/novelwriter/assets/themes/solarized_dark.conf @@ -5,6 +5,7 @@ credit = Ethan Schoonover url = https://ethanschoonover.com/solarized/ license = MIT licenseurl = https://github.com/altercation/solarized/blob/master/LICENSE +icontheme = typicons_dark [Palette] window = 0, 43, 54 diff --git a/novelwriter/assets/themes/solarized_light.conf b/novelwriter/assets/themes/solarized_light.conf index 855e4e76..c9fb50c0 100644 --- a/novelwriter/assets/themes/solarized_light.conf +++ b/novelwriter/assets/themes/solarized_light.conf @@ -5,6 +5,7 @@ credit = Ethan Schoonover url = https://ethanschoonover.com/solarized/ license = MIT licenseurl = https://github.com/altercation/solarized/blob/master/LICENSE +icontheme = typicons_light [Palette] window = 238, 232, 213 diff --git a/novelwriter/config.py b/novelwriter/config.py index 73a8c3c6..ecc5f4db 100644 --- a/novelwriter/config.py +++ b/novelwriter/config.py @@ -508,12 +508,6 @@ class Config: logger.info("Using straight double quotes, so disabling auto-replace") self.doReplaceDQuote = False - # Check deprecated settings - if self.guiIcons in ("typicons_colour_dark", "typicons_grey_dark"): - self.guiIcons = "typicons_dark" - elif self.guiIcons in ("typicons_colour_light", "typicons_grey_light"): - self.guiIcons = "typicons_light" - return True def saveConfig(self): diff --git a/novelwriter/dialogs/preferences.py b/novelwriter/dialogs/preferences.py index ae2df4c5..4551d3e6 100644 --- a/novelwriter/dialogs/preferences.py +++ b/novelwriter/dialogs/preferences.py @@ -185,22 +185,6 @@ class GuiPreferencesGeneral(QWidget): self.tr("Requires restart.") ) - # Select Icon Theme - self.guiIcons = QComboBox() - self.guiIcons.setMinimumWidth(minWidth) - self.iconCache = self.mainTheme.iconCache.listThemes() - for iconDir, iconName in self.iconCache: - self.guiIcons.addItem(iconName, iconDir) - iconIdx = self.guiIcons.findData(self.mainConf.guiIcons) - if iconIdx != -1: - self.guiIcons.setCurrentIndex(iconIdx) - - self.mainForm.addRow( - self.tr("Main icon theme"), - self.guiIcons, - self.tr("Requires restart.") - ) - # Editor Theme self.guiSyntax = QComboBox() self.guiSyntax.setMinimumWidth(self.mainConf.pxInt(200)) @@ -288,7 +272,6 @@ class GuiPreferencesGeneral(QWidget): """ guiLang = self.guiLang.currentData() guiTheme = self.guiTheme.currentData() - guiIcons = self.guiIcons.currentData() guiSyntax = self.guiSyntax.currentData() guiFont = self.guiFont.text() guiFontSize = self.guiFontSize.value() @@ -298,7 +281,6 @@ class GuiPreferencesGeneral(QWidget): needsRestart = False needsRestart |= self.mainConf.guiLang != guiLang needsRestart |= self.mainConf.guiTheme != guiTheme - needsRestart |= self.mainConf.guiIcons != guiIcons needsRestart |= self.mainConf.guiFont != guiFont needsRestart |= self.mainConf.guiFontSize != guiFontSize @@ -308,7 +290,6 @@ class GuiPreferencesGeneral(QWidget): self.mainConf.guiLang = guiLang self.mainConf.guiTheme = guiTheme - self.mainConf.guiIcons = guiIcons self.mainConf.guiSyntax = guiSyntax self.mainConf.guiFont = guiFont self.mainConf.guiFontSize = guiFontSize diff --git a/novelwriter/gui/theme.py b/novelwriter/gui/theme.py index 4c76179d..b13e43ca 100644 --- a/novelwriter/gui/theme.py +++ b/novelwriter/gui/theme.py @@ -67,6 +67,7 @@ class GuiTheme: self.themeUrl = "" self.themeLicense = "" self.themeLicenseUrl = "" + self.themeIcons = "" # GUI self.statNone = [120, 120, 120] @@ -267,6 +268,7 @@ class GuiTheme: self.themeUrl = confParser.rdStr(cnfSec, "url", "") self.themeLicense = confParser.rdStr(cnfSec, "license", "N/A") self.themeLicenseUrl = confParser.rdStr(cnfSec, "licenseurl", "") + self.themeIcons = confParser.rdStr(cnfSec, "icontheme", "") # Palette cnfSec = "Palette" @@ -293,6 +295,9 @@ class GuiTheme: self.statUnsaved = self._loadColour(confParser, cnfSec, "statusunsaved") self.statSaved = self._loadColour(confParser, cnfSec, "statussaved") + # Set Icon Theme + self.mainConf.guiIcons = self.themeIcons + # CSS File cssData = readTextFile(self.cssFile) if cssData: @@ -492,7 +497,6 @@ class GuiIcons: # Storage self._qIcons = {} self._themeMap = {} - self._themeList = [] self._headerDec = [] self._confName = "icons.conf" @@ -668,28 +672,6 @@ class GuiIcons: ] return self._headerDec[minmax(hLevel, 0, 4)] - def listThemes(self): - """Scan the icons themes folder and list all themes. - """ - if self._themeList: - return self._themeList - - confParser = NWConfigParser() - for themeDir in os.listdir(self._iconPath): - themePath = os.path.join(self._iconPath, themeDir) - if not os.path.isdir(themePath): - continue - - logger.debug("Checking icon theme config for '%s'", themeDir) - themeConf = os.path.join(themePath, self._confName) - themeName = _loadInternalName(confParser, themeConf) - if themeName: - self._themeList.append((themeDir, themeName)) - - self._themeList = sorted(self._themeList, key=lambda x: x[1]) - - return self._themeList - ## # Internal Functions ## diff --git a/tests/test_base/test_base_config.py b/tests/test_base/test_base_config.py index f6721493..61994f04 100644 --- a/tests/test_base/test_base_config.py +++ b/tests/test_base/test_base_config.py @@ -192,32 +192,6 @@ def testBaseConfig_Init(monkeypatch, tmpDir, fncDir, outDir, refDir, filesDir): tstConf.doReplaceSQuote = orDoSng assert tstConf.saveConfig() is True - # Test Correcting icon theme - origIcons = tstConf.guiIcons - - tstConf.guiIcons = "typicons_colour_dark" - assert tstConf.saveConfig() is True - assert tstConf.loadConfig() is True - assert tstConf.guiIcons == "typicons_dark" - - tstConf.guiIcons = "typicons_grey_dark" - assert tstConf.saveConfig() is True - assert tstConf.loadConfig() is True - assert tstConf.guiIcons == "typicons_dark" - - tstConf.guiIcons = "typicons_colour_light" - assert tstConf.saveConfig() is True - assert tstConf.loadConfig() is True - assert tstConf.guiIcons == "typicons_light" - - tstConf.guiIcons = "typicons_grey_light" - assert tstConf.saveConfig() is True - assert tstConf.loadConfig() is True - assert tstConf.guiIcons == "typicons_light" - - tstConf.guiIcons = origIcons - assert tstConf.saveConfig() - # Localisation # ============ diff --git a/tests/test_gui/test_gui_theme.py b/tests/test_gui/test_gui_theme.py index 0014a4da..3c93ad60 100644 --- a/tests/test_gui/test_gui_theme.py +++ b/tests/test_gui/test_gui_theme.py @@ -50,7 +50,7 @@ def testGuiTheme_Main(qtbot, monkeypatch, nwMinimal, tmpDir): assert novelwriter.CONFIG.confPath == nwMinimal novelwriter.CONFIG.guiTheme = "default_dark" novelwriter.CONFIG.guiSyntax = "tomorrow_night_eighties" - novelwriter.CONFIG.guiIcons = "typicons_colour_dark" + novelwriter.CONFIG.guiIcons = "typicons_dark" novelwriter.CONFIG.guiFont = "Cantarell" novelwriter.CONFIG.guiFontSize = 11 novelwriter.CONFIG.confChanged = True From e1ecd3cd27b5b7ede94bae09940edd75f5796cde Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Tue, 25 Oct 2022 22:01:44 +0200 Subject: [PATCH 10/36] Fix some code linter complaints in doc editor --- novelwriter/gui/doceditor.py | 34 ++++++++++++++++------------------ 1 file changed, 16 insertions(+), 18 deletions(-) diff --git a/novelwriter/gui/doceditor.py b/novelwriter/gui/doceditor.py index 564e23c3..d401a9dd 100644 --- a/novelwriter/gui/doceditor.py +++ b/novelwriter/gui/doceditor.py @@ -138,24 +138,20 @@ class GuiDocEditor(QTextEdit): self.setFrameStyle(QFrame.NoFrame) # Custom Shortcuts - QShortcut( - QKeySequence("Ctrl+."), - self, - context=Qt.WidgetShortcut, - activated=self._openSpellContext - ) - QShortcut( - Qt.Key_Return | Qt.ControlModifier, - self, - context=Qt.WidgetShortcut, - activated=self._followTag - ) - QShortcut( - Qt.Key_Enter | Qt.ControlModifier, - self, - context=Qt.WidgetShortcut, - activated=self._followTag - ) + self.keyContext = QShortcut(self) + self.keyContext.setKey("Ctrl+.") + self.keyContext.setContext(Qt.WidgetShortcut) + self.keyContext.activated.connect(self._openSpellContext) + + self.followTag1 = QShortcut(self) + self.followTag1.setKey(Qt.Key_Return | Qt.ControlModifier) + self.followTag1.setContext(Qt.WidgetShortcut) + self.followTag1.activated.connect(self._followTag) + + self.followTag2 = QShortcut(self) + self.followTag2.setKey(Qt.Key_Enter | Qt.ControlModifier) + self.followTag2.setContext(Qt.WidgetShortcut) + self.followTag2.activated.connect(self._followTag) # Set Up Document Word Counter self.wcTimerDoc = QTimer() @@ -1162,6 +1158,7 @@ class GuiDocEditor(QTextEdit): posCursor = self.cursorForPosition(thePos) spellCheck = self._spellCheck + theWord = "" if posCursor.block().text().startswith("@"): spellCheck = False @@ -1430,6 +1427,7 @@ class GuiDocEditor(QTextEdit): origB = theCursor.selectionEnd() else: origA = theCursor.position() + origB = theCursor.position() findOpt = QTextDocument.FindFlag(0) if self.docSearch.isCaseSense: From 52ede26f3df6a689ce50bbdc6c8968d0cb2218c5 Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Tue, 25 Oct 2022 23:12:21 +0200 Subject: [PATCH 11/36] Add updateTheme functions to most GUI components --- novelwriter/gui/doceditor.py | 80 +++++++++++++++++++------- novelwriter/gui/docviewer.py | 86 ++++++++++++++++++---------- novelwriter/gui/noveltree.py | 52 +++++++++++------ novelwriter/gui/projtree.py | 107 +++++++++++++++++++++-------------- novelwriter/gui/theme.py | 10 +++- novelwriter/gui/viewsbar.py | 25 +++++--- novelwriter/guimain.py | 9 ++- 7 files changed, 251 insertions(+), 118 deletions(-) diff --git a/novelwriter/gui/doceditor.py b/novelwriter/gui/doceditor.py index d401a9dd..6b098959 100644 --- a/novelwriter/gui/doceditor.py +++ b/novelwriter/gui/doceditor.py @@ -206,6 +206,14 @@ class GuiDocEditor(QTextEdit): return True + def updateTheme(self): + """Update theme elements + """ + self.docSearch.updateTheme() + self.docHeader.updateTheme() + self.docFooter.updateTheme() + return + def initEditor(self): """Initialise or re-initialise the editor with the user's settings. This function is both called when the editor is @@ -2251,7 +2259,6 @@ class GuiDocEditSearch(QFrame): self.searchOpt.setToolButtonStyle(Qt.ToolButtonIconOnly) self.searchOpt.setIconSize(QSize(tPx, tPx)) self.searchOpt.setContentsMargins(0, 0, 0, 0) - self.searchOpt.setStyleSheet("QToolBar {padding: 0;}") self.searchLabel = QLabel(self.tr("Search")) self.searchLabel.setFont(self.boxFont) @@ -2262,35 +2269,30 @@ class GuiDocEditSearch(QFrame): self.resultLabel.setMinimumWidth(self.mainTheme.getTextWidth("?/?", self.boxFont)) self.toggleCase = QAction(self.tr("Case Sensitive"), self) - self.toggleCase.setIcon(self.mainTheme.getIcon("search_case")) self.toggleCase.setCheckable(True) self.toggleCase.setChecked(self.isCaseSense) self.toggleCase.toggled.connect(self._doToggleCase) self.searchOpt.addAction(self.toggleCase) self.toggleWord = QAction(self.tr("Whole Words Only"), self) - self.toggleWord.setIcon(self.mainTheme.getIcon("search_word")) self.toggleWord.setCheckable(True) self.toggleWord.setChecked(self.isWholeWord) self.toggleWord.toggled.connect(self._doToggleWord) self.searchOpt.addAction(self.toggleWord) self.toggleRegEx = QAction(self.tr("RegEx Mode"), self) - self.toggleRegEx.setIcon(self.mainTheme.getIcon("search_regex")) self.toggleRegEx.setCheckable(True) self.toggleRegEx.setChecked(self.isRegEx) self.toggleRegEx.toggled.connect(self._doToggleRegEx) self.searchOpt.addAction(self.toggleRegEx) self.toggleLoop = QAction(self.tr("Loop Search"), self) - self.toggleLoop.setIcon(self.mainTheme.getIcon("search_loop")) self.toggleLoop.setCheckable(True) self.toggleLoop.setChecked(self.doLoop) self.toggleLoop.toggled.connect(self._doToggleLoop) self.searchOpt.addAction(self.toggleLoop) self.toggleProject = QAction(self.tr("Search Next File"), self) - self.toggleProject.setIcon(self.mainTheme.getIcon("search_project")) self.toggleProject.setCheckable(True) self.toggleProject.setChecked(self.doNextFile) self.toggleProject.toggled.connect(self._doToggleProject) @@ -2299,7 +2301,6 @@ class GuiDocEditSearch(QFrame): self.searchOpt.addSeparator() self.toggleMatchCap = QAction(self.tr("Preserve Case"), self) - self.toggleMatchCap.setIcon(self.mainTheme.getIcon("search_preserve")) self.toggleMatchCap.setCheckable(True) self.toggleMatchCap.setChecked(self.doMatchCap) self.toggleMatchCap.toggled.connect(self._doToggleMatchCap) @@ -2308,7 +2309,6 @@ class GuiDocEditSearch(QFrame): self.searchOpt.addSeparator() self.cancelSearch = QAction(self.tr("Close Search"), self) - self.cancelSearch.setIcon(self.mainTheme.getIcon("search_cancel")) self.cancelSearch.triggered.connect(self._doClose) self.searchOpt.addAction(self.cancelSearch) @@ -2320,15 +2320,14 @@ class GuiDocEditSearch(QFrame): self.showReplace = QToolButton(self) self.showReplace.setArrowType(Qt.RightArrow) self.showReplace.setCheckable(True) - self.showReplace.setStyleSheet("QToolButton {border: none; background: transparent;}") self.showReplace.toggled.connect(self._doToggleReplace) - self.searchButton = QPushButton(self.mainTheme.getIcon("search"), "") + self.searchButton = QPushButton("") self.searchButton.setFixedSize(QSize(bPx, bPx)) self.searchButton.setToolTip(self.tr("Find in current document")) self.searchButton.clicked.connect(self._doSearch) - self.replaceButton = QPushButton(self.mainTheme.getIcon("search_replace"), "") + self.replaceButton = QPushButton("") self.replaceButton.setFixedSize(QSize(bPx, bPx)) self.replaceButton.setToolTip(self.tr("Find and replace in current document")) self.replaceButton.clicked.connect(self._doReplace) @@ -2358,6 +2357,35 @@ class GuiDocEditSearch(QFrame): self.replaceButton.setVisible(False) self.adjustSize() + self.updateTheme() + + logger.debug("GuiDocEditSearch initialisation complete") + + return + + def updateTheme(self): + """Update theme elements. + """ + qPalette = qApp.palette() + self.setPalette(qPalette) + self.searchBox.setPalette(qPalette) + self.replaceBox.setPalette(qPalette) + + # Set icons + self.toggleCase.setIcon(self.mainTheme.getIcon("search_case")) + self.toggleWord.setIcon(self.mainTheme.getIcon("search_word")) + self.toggleRegEx.setIcon(self.mainTheme.getIcon("search_regex")) + self.toggleLoop.setIcon(self.mainTheme.getIcon("search_loop")) + self.toggleProject.setIcon(self.mainTheme.getIcon("search_project")) + self.toggleMatchCap.setIcon(self.mainTheme.getIcon("search_preserve")) + self.cancelSearch.setIcon(self.mainTheme.getIcon("search_cancel")) + self.searchButton.setIcon(self.mainTheme.getIcon("search")) + self.replaceButton.setIcon(self.mainTheme.getIcon("search_replace")) + + # Set stylesheets + self.searchOpt.setStyleSheet("QToolBar {padding: 0;}") + self.showReplace.setStyleSheet("QToolButton {border: none; background: transparent;}") + # Construct Box Colours qPalette = self.searchBox.palette() baseCol = qPalette.base().color() @@ -2376,8 +2404,6 @@ class GuiDocEditSearch(QFrame): False: errCol } - logger.debug("GuiDocEditSearch initialisation complete") - return def closeSearch(self): @@ -2403,10 +2429,10 @@ class GuiDocEditSearch(QFrame): """ if self.replaceBox.isVisible(): if self.searchBox.hasFocus(): - self.replaceBox.setFocus(True) + self.replaceBox.setFocus() return True elif self.replaceBox.hasFocus(): - self.searchBox.setFocus(True) + self.searchBox.setFocus() return True return False @@ -2631,7 +2657,6 @@ class GuiDocEditHeader(QWidget): # Buttons self.editButton = QToolButton(self) - self.editButton.setIcon(self.mainTheme.getIcon("edit")) self.editButton.setContentsMargins(0, 0, 0, 0) self.editButton.setIconSize(QSize(fPx, fPx)) self.editButton.setFixedSize(fPx, fPx) @@ -2642,7 +2667,6 @@ class GuiDocEditHeader(QWidget): self.editButton.clicked.connect(self._editDocument) self.searchButton = QToolButton(self) - self.searchButton.setIcon(self.mainTheme.getIcon("search")) self.searchButton.setContentsMargins(0, 0, 0, 0) self.searchButton.setIconSize(QSize(fPx, fPx)) self.searchButton.setFixedSize(fPx, fPx) @@ -2653,7 +2677,6 @@ class GuiDocEditHeader(QWidget): self.searchButton.clicked.connect(self._searchDocument) self.minmaxButton = QToolButton(self) - self.minmaxButton.setIcon(self.mainTheme.getIcon("maximise")) self.minmaxButton.setContentsMargins(0, 0, 0, 0) self.minmaxButton.setIconSize(QSize(fPx, fPx)) self.minmaxButton.setFixedSize(fPx, fPx) @@ -2664,7 +2687,6 @@ class GuiDocEditHeader(QWidget): self.minmaxButton.clicked.connect(self._minmaxDocument) self.closeButton = QToolButton(self) - self.closeButton.setIcon(self.mainTheme.getIcon("close")) self.closeButton.setContentsMargins(0, 0, 0, 0) self.closeButton.setIconSize(QSize(fPx, fPx)) self.closeButton.setFixedSize(fPx, fPx) @@ -2692,6 +2714,7 @@ class GuiDocEditHeader(QWidget): self.setMinimumHeight(fPx + 2*cM) # Fix the Colours + self.updateTheme() self.matchColours() logger.debug("GuiDocEditHeader initialisation complete") @@ -2702,6 +2725,15 @@ class GuiDocEditHeader(QWidget): # Methods ## + def updateTheme(self): + """Update theme elements. + """ + self.editButton.setIcon(self.mainTheme.getIcon("edit")) + self.searchButton.setIcon(self.mainTheme.getIcon("search")) + self.minmaxButton.setIcon(self.mainTheme.getIcon("maximise")) + self.closeButton.setIcon(self.mainTheme.getIcon("close")) + return + def matchColours(self): """Update the colours of the widget to match those of the syntax theme rather than the main GUI. @@ -2860,7 +2892,6 @@ class GuiDocEditFooter(QWidget): # Lines self.linesIcon = QLabel("") - self.linesIcon.setPixmap(self.mainTheme.getPixmap("status_lines", (self.sPx, self.sPx))) self.linesIcon.setContentsMargins(0, 0, 0, 0) self.linesIcon.setFixedHeight(self.sPx) self.linesIcon.setAlignment(Qt.AlignLeft | Qt.AlignTop) @@ -2876,7 +2907,6 @@ class GuiDocEditFooter(QWidget): # Words self.wordsIcon = QLabel("") - self.wordsIcon.setPixmap(self.mainTheme.getPixmap("status_stats", (self.sPx, self.sPx))) self.wordsIcon.setContentsMargins(0, 0, 0, 0) self.wordsIcon.setFixedHeight(self.sPx) self.wordsIcon.setAlignment(Qt.AlignLeft | Qt.AlignTop) @@ -2911,6 +2941,7 @@ class GuiDocEditFooter(QWidget): self.setMinimumHeight(fPx + 2*cM) # Fix the Colours + self.updateTheme() self.matchColours() self.updateLineCount() self.updateCounts() @@ -2923,6 +2954,13 @@ class GuiDocEditFooter(QWidget): # Methods ## + def updateTheme(self): + """Update theme elements. + """ + self.linesIcon.setPixmap(self.mainTheme.getPixmap("status_lines", (self.sPx, self.sPx))) + self.wordsIcon.setPixmap(self.mainTheme.getPixmap("status_stats", (self.sPx, self.sPx))) + return + def matchColours(self): """Update the colours of the widget to match those of the syntax theme rather than the main GUI. diff --git a/novelwriter/gui/docviewer.py b/novelwriter/gui/docviewer.py index 644c3b6d..d3cac0b0 100644 --- a/novelwriter/gui/docviewer.py +++ b/novelwriter/gui/docviewer.py @@ -102,6 +102,13 @@ class GuiDocViewer(QTextBrowser): self.docHeader.setTitleFromHandle(self._docHandle) return True + def updateTheme(self): + """Update theme elements. + """ + self.docHeader.updateTheme() + self.docFooter.updateTheme() + return + def initViewer(self): """Set editor settings from main config. """ @@ -742,7 +749,6 @@ class GuiDocViewHeader(QWidget): # Buttons self.backButton = QToolButton(self) - self.backButton.setIcon(self.mainTheme.getIcon("backward")) self.backButton.setContentsMargins(0, 0, 0, 0) self.backButton.setIconSize(QSize(fPx, fPx)) self.backButton.setFixedSize(fPx, fPx) @@ -753,7 +759,6 @@ class GuiDocViewHeader(QWidget): self.backButton.clicked.connect(self.docViewer.navBackward) self.forwardButton = QToolButton(self) - self.forwardButton.setIcon(self.mainTheme.getIcon("forward")) self.forwardButton.setContentsMargins(0, 0, 0, 0) self.forwardButton.setIconSize(QSize(fPx, fPx)) self.forwardButton.setFixedSize(fPx, fPx) @@ -764,7 +769,6 @@ class GuiDocViewHeader(QWidget): self.forwardButton.clicked.connect(self.docViewer.navForward) self.refreshButton = QToolButton(self) - self.refreshButton.setIcon(self.mainTheme.getIcon("refresh")) self.refreshButton.setContentsMargins(0, 0, 0, 0) self.refreshButton.setIconSize(QSize(fPx, fPx)) self.refreshButton.setFixedSize(fPx, fPx) @@ -775,7 +779,6 @@ class GuiDocViewHeader(QWidget): self.refreshButton.clicked.connect(self._refreshDocument) self.closeButton = QToolButton(self) - self.closeButton.setIcon(self.mainTheme.getIcon("close")) self.closeButton.setContentsMargins(0, 0, 0, 0) self.closeButton.setIconSize(QSize(fPx, fPx)) self.closeButton.setFixedSize(fPx, fPx) @@ -803,6 +806,7 @@ class GuiDocViewHeader(QWidget): self.setMinimumHeight(fPx + 2*cM) # Fix the Colours + self.updateTheme() self.matchColours() logger.debug("GuiDocViewHeader initialisation complete") @@ -813,6 +817,15 @@ class GuiDocViewHeader(QWidget): # Methods ## + def updateTheme(self): + """Update theme elements. + """ + self.backButton.setIcon(self.mainTheme.getIcon("backward")) + self.forwardButton.setIcon(self.mainTheme.getIcon("forward")) + self.refreshButton.setIcon(self.mainTheme.getIcon("refresh")) + self.closeButton.setIcon(self.mainTheme.getIcon("close")) + return + def matchColours(self): """Update the colours of the widget to match those of the syntax theme rather than the main GUI. @@ -926,33 +939,13 @@ class GuiDocViewFooter(QWidget): bSp = self.mainConf.pxInt(2) hSp = self.mainConf.pxInt(8) - # Icons - stickyOn = self.mainTheme.getPixmap("sticky-on", (fPx, fPx)) - stickyOff = self.mainTheme.getPixmap("sticky-off", (fPx, fPx)) - stickyIcon = QIcon() - stickyIcon.addPixmap(stickyOn, QIcon.Normal, QIcon.On) - stickyIcon.addPixmap(stickyOff, QIcon.Normal, QIcon.Off) - - bulletOn = self.mainTheme.getPixmap("bullet-on", (fPx, fPx)) - bulletOff = self.mainTheme.getPixmap("bullet-off", (fPx, fPx)) - bulletIcon = QIcon() - bulletIcon.addPixmap(bulletOn, QIcon.Normal, QIcon.On) - bulletIcon.addPixmap(bulletOff, QIcon.Normal, QIcon.Off) - # Main Widget Settings self.setContentsMargins(0, 0, 0, 0) self.setAutoFillBackground(True) - buttonStyle = ( - "QToolButton {{border: none; background: transparent;}} " - "QToolButton:hover {{border: none; background: rgba({0},{1},{2},0.2);}}" - ).format(*self.mainTheme.colText) - # Show/Hide Details self.showHide = QToolButton(self) self.showHide.setToolButtonStyle(Qt.ToolButtonIconOnly) - self.showHide.setStyleSheet(buttonStyle) - self.showHide.setIcon(self.mainTheme.getIcon("reference")) self.showHide.setIconSize(QSize(fPx, fPx)) self.showHide.setFixedSize(QSize(fPx, fPx)) self.showHide.clicked.connect(self._doShowHide) @@ -962,8 +955,6 @@ class GuiDocViewFooter(QWidget): self.stickyRefs = QToolButton(self) self.stickyRefs.setCheckable(True) self.stickyRefs.setToolButtonStyle(Qt.ToolButtonIconOnly) - self.stickyRefs.setStyleSheet(buttonStyle) - self.stickyRefs.setIcon(stickyIcon) self.stickyRefs.setIconSize(QSize(fPx, fPx)) self.stickyRefs.setFixedSize(QSize(fPx, fPx)) self.stickyRefs.toggled.connect(self._doToggleSticky) @@ -976,8 +967,6 @@ class GuiDocViewFooter(QWidget): self.showComments.setCheckable(True) self.showComments.setChecked(self.mainConf.viewComments) self.showComments.setToolButtonStyle(Qt.ToolButtonIconOnly) - self.showComments.setStyleSheet(buttonStyle) - self.showComments.setIcon(bulletIcon) self.showComments.setIconSize(QSize(fPx, fPx)) self.showComments.setFixedSize(QSize(fPx, fPx)) self.showComments.toggled.connect(self._doToggleComments) @@ -988,8 +977,6 @@ class GuiDocViewFooter(QWidget): self.showSynopsis.setCheckable(True) self.showSynopsis.setChecked(self.mainConf.viewSynopsis) self.showSynopsis.setToolButtonStyle(Qt.ToolButtonIconOnly) - self.showSynopsis.setStyleSheet(buttonStyle) - self.showSynopsis.setIcon(bulletIcon) self.showSynopsis.setIconSize(QSize(fPx, fPx)) self.showSynopsis.setFixedSize(QSize(fPx, fPx)) self.showSynopsis.toggled.connect(self._doToggleSynopsis) @@ -1063,6 +1050,7 @@ class GuiDocViewFooter(QWidget): self.setMinimumHeight(fPx + 2*cM) # Fix the Colours + self.updateTheme() self.matchColours() logger.debug("GuiDocViewFooter initialisation complete") @@ -1073,6 +1061,44 @@ class GuiDocViewFooter(QWidget): # Methods ## + def updateTheme(self): + """Update theme elements. + """ + # Icons + + fPx = int(0.9*self.mainTheme.fontPixelSize) + + stickyOn = self.mainTheme.getPixmap("sticky-on", (fPx, fPx)) + stickyOff = self.mainTheme.getPixmap("sticky-off", (fPx, fPx)) + stickyIcon = QIcon() + stickyIcon.addPixmap(stickyOn, QIcon.Normal, QIcon.On) + stickyIcon.addPixmap(stickyOff, QIcon.Normal, QIcon.Off) + + bulletOn = self.mainTheme.getPixmap("bullet-on", (fPx, fPx)) + bulletOff = self.mainTheme.getPixmap("bullet-off", (fPx, fPx)) + bulletIcon = QIcon() + bulletIcon.addPixmap(bulletOn, QIcon.Normal, QIcon.On) + bulletIcon.addPixmap(bulletOff, QIcon.Normal, QIcon.Off) + + self.showHide.setIcon(self.mainTheme.getIcon("reference")) + self.stickyRefs.setIcon(stickyIcon) + self.showComments.setIcon(bulletIcon) + self.showSynopsis.setIcon(bulletIcon) + + # StyleSheets + + buttonStyle = ( + "QToolButton {{border: none; background: transparent;}} " + "QToolButton:hover {{border: none; background: rgba({0},{1},{2},0.2);}}" + ).format(*self.mainTheme.colText) + + self.showHide.setStyleSheet(buttonStyle) + self.stickyRefs.setStyleSheet(buttonStyle) + self.showComments.setStyleSheet(buttonStyle) + self.showSynopsis.setStyleSheet(buttonStyle) + + return + def matchColours(self): """Update the colours of the widget to match those of the syntax theme rather than the main GUI. diff --git a/novelwriter/gui/noveltree.py b/novelwriter/gui/noveltree.py index acadd29f..489b7ab6 100644 --- a/novelwriter/gui/noveltree.py +++ b/novelwriter/gui/noveltree.py @@ -92,6 +92,13 @@ class GuiNovelView(QWidget): # Methods ## + def updateTheme(self): + """Update theme elements. + """ + self.novelBar.updateTheme() + self.refreshTree() + return + def initSettings(self): """Initialise GUI elements that depend on specific settings. """ @@ -181,16 +188,6 @@ class GuiNovelToolBar(QWidget): self.setContentsMargins(0, 0, 0, 0) self.setAutoFillBackground(True) - qPalette = self.palette() - qPalette.setBrush(QPalette.Window, qPalette.base()) - self.setPalette(qPalette) - - fadeCol = qPalette.text().color() - buttonStyle = ( - "QToolButton {{padding: {0}px; border: none; background: transparent;}} " - "QToolButton:hover {{border: none; background: rgba({1},{2},{3},0.2);}}" - ).format(mPx, fadeCol.red(), fadeCol.green(), fadeCol.blue()) - # Widget Label self.viewLabel = QLabel("%s" % self.tr("Novel Outline")) self.viewLabel.setContentsMargins(0, 0, 0, 0) @@ -199,9 +196,7 @@ class GuiNovelToolBar(QWidget): # Refresh Button self.tbRefresh = QToolButton(self) self.tbRefresh.setToolTip(self.tr("Refresh")) - self.tbRefresh.setIcon(self.mainTheme.getIcon("refresh")) self.tbRefresh.setIconSize(QSize(iPx, iPx)) - self.tbRefresh.setStyleSheet(buttonStyle) self.tbRefresh.clicked.connect(self._refreshNovelTree) # Novel Root Menu @@ -211,9 +206,7 @@ class GuiNovelToolBar(QWidget): self.tbRoot = QToolButton(self) self.tbRoot.setToolTip(self.tr("Novel Root")) - self.tbRoot.setIcon(self.mainTheme.getIcon(nwLabels.CLASS_ICON[nwItemClass.NOVEL])) self.tbRoot.setIconSize(QSize(iPx, iPx)) - self.tbRoot.setStyleSheet(buttonStyle) self.tbRoot.setMenu(self.mRoot) self.tbRoot.setPopupMode(QToolButton.InstantPopup) @@ -230,9 +223,7 @@ class GuiNovelToolBar(QWidget): self.tbMore = QToolButton(self) self.tbMore.setToolTip(self.tr("More Options")) - self.tbMore.setIcon(self.mainTheme.getIcon("menu")) self.tbMore.setIconSize(QSize(iPx, iPx)) - self.tbMore.setStyleSheet(buttonStyle) self.tbMore.setMenu(self.mMore) self.tbMore.setPopupMode(QToolButton.InstantPopup) @@ -247,6 +238,8 @@ class GuiNovelToolBar(QWidget): self.setLayout(self.outerBox) + self.updateTheme() + logger.debug("GuiNovelToolBar initialisation complete") return @@ -255,6 +248,33 @@ class GuiNovelToolBar(QWidget): # Methods ## + def updateTheme(self): + """Update theme elements. + """ + # Icons + + self.tbRefresh.setIcon(self.mainTheme.getIcon("refresh")) + self.tbRoot.setIcon(self.mainTheme.getIcon(nwLabels.CLASS_ICON[nwItemClass.NOVEL])) + self.tbMore.setIcon(self.mainTheme.getIcon("menu")) + + qPalette = self.palette() + qPalette.setBrush(QPalette.Window, qPalette.base()) + self.setPalette(qPalette) + + # StyleSheets + + fadeCol = qPalette.text().color() + buttonStyle = ( + "QToolButton {{padding: {0}px; border: none; background: transparent;}} " + "QToolButton:hover {{border: none; background: rgba({1},{2},{3},0.2);}}" + ).format(self.mainConf.pxInt(2), fadeCol.red(), fadeCol.green(), fadeCol.blue()) + + self.tbRefresh.setStyleSheet(buttonStyle) + self.tbRoot.setStyleSheet(buttonStyle) + self.tbMore.setStyleSheet(buttonStyle) + + return + def clearContent(self): """Run clearing project tasks. """ diff --git a/novelwriter/gui/projtree.py b/novelwriter/gui/projtree.py index e34419d1..19d8af38 100644 --- a/novelwriter/gui/projtree.py +++ b/novelwriter/gui/projtree.py @@ -119,6 +119,13 @@ class GuiProjectView(QWidget): # Methods ## + def updateTheme(self): + """Update theme elements. + """ + self.projBar.updateTheme() + self.populateTree() + return + def initSettings(self): """Initialise GUI elements that depend on specific settings. """ @@ -213,16 +220,6 @@ class GuiProjectToolBar(QWidget): self.setContentsMargins(0, 0, 0, 0) self.setAutoFillBackground(True) - qPalette = self.palette() - qPalette.setBrush(QPalette.Window, qPalette.base()) - self.setPalette(qPalette) - - fadeCol = qPalette.text().color() - buttonStyle = ( - "QToolButton {{padding: {0}px; border: none; background: transparent;}} " - "QToolButton:hover {{border: none; background: rgba({1},{2},{3},0.2);}}" - ).format(mPx, fadeCol.red(), fadeCol.green(), fadeCol.blue()) - # Widget Label self.viewLabel = QLabel("%s" % self.tr("Project Content")) self.viewLabel.setContentsMargins(0, 0, 0, 0) @@ -234,78 +231,56 @@ class GuiProjectToolBar(QWidget): self.tbQuick = QToolButton(self) self.tbQuick.setToolTip("%s [Ctrl+L]" % self.tr("Quick Links")) self.tbQuick.setShortcut("Ctrl+L") - self.tbQuick.setIcon(self.mainTheme.getIcon("bookmark")) self.tbQuick.setIconSize(QSize(iPx, iPx)) - self.tbQuick.setStyleSheet(buttonStyle) self.tbQuick.setMenu(self.mQuick) self.tbQuick.setPopupMode(QToolButton.InstantPopup) # Move Buttons self.tbMoveU = QToolButton(self) self.tbMoveU.setToolTip("%s [Ctrl+Up]" % self.tr("Move Up")) - self.tbMoveU.setIcon(self.mainTheme.getIcon("up")) self.tbMoveU.setIconSize(QSize(iPx, iPx)) - self.tbMoveU.setStyleSheet(buttonStyle) self.tbMoveU.clicked.connect(lambda: self.projTree.moveTreeItem(-1)) self.tbMoveD = QToolButton(self) self.tbMoveD.setToolTip("%s [Ctrl+Down]" % self.tr("Move Down")) - self.tbMoveD.setIcon(self.mainTheme.getIcon("down")) self.tbMoveD.setIconSize(QSize(iPx, iPx)) - self.tbMoveD.setStyleSheet(buttonStyle) self.tbMoveD.clicked.connect(lambda: self.projTree.moveTreeItem(1)) # Add Item Menu self.mAdd = QMenu() self.aAddEmpty = self.mAdd.addAction(trConst(nwLabels.ITEM_DESCRIPTION["document"])) - self.aAddEmpty.setIcon(self.mainTheme.getIcon("proj_document")) self.aAddEmpty.triggered.connect( lambda: self.projTree.newTreeItem(nwItemType.FILE, hLevel=0, isNote=False) ) self.aAddChap = self.mAdd.addAction(trConst(nwLabels.ITEM_DESCRIPTION["doc_h2"])) - self.aAddChap.setIcon(self.mainTheme.getIcon("proj_chapter")) self.aAddChap.triggered.connect( lambda: self.projTree.newTreeItem(nwItemType.FILE, hLevel=2, isNote=False) ) self.aAddScene = self.mAdd.addAction(trConst(nwLabels.ITEM_DESCRIPTION["doc_h3"])) - self.aAddScene.setIcon(self.mainTheme.getIcon("proj_scene")) self.aAddScene.triggered.connect( lambda: self.projTree.newTreeItem(nwItemType.FILE, hLevel=3, isNote=False) ) self.aAddNote = self.mAdd.addAction(trConst(nwLabels.ITEM_DESCRIPTION["note"])) - self.aAddNote.setIcon(self.mainTheme.getIcon("proj_note")) self.aAddNote.triggered.connect( lambda: self.projTree.newTreeItem(nwItemType.FILE, hLevel=1, isNote=True) ) self.aAddFolder = self.mAdd.addAction(trConst(nwLabels.ITEM_DESCRIPTION["folder"])) - self.aAddFolder.setIcon(self.mainTheme.getIcon("proj_folder")) self.aAddFolder.triggered.connect( lambda: self.projTree.newTreeItem(nwItemType.FOLDER) ) self.mAddRoot = self.mAdd.addMenu(trConst(nwLabels.ITEM_DESCRIPTION["root"])) - self._addRootFolderEntry(nwItemClass.NOVEL) - self._addRootFolderEntry(nwItemClass.ARCHIVE) - self.mAddRoot.addSeparator() - self._addRootFolderEntry(nwItemClass.PLOT) - self._addRootFolderEntry(nwItemClass.CHARACTER) - self._addRootFolderEntry(nwItemClass.WORLD) - self._addRootFolderEntry(nwItemClass.TIMELINE) - self._addRootFolderEntry(nwItemClass.OBJECT) - self._addRootFolderEntry(nwItemClass.ENTITY) - self._addRootFolderEntry(nwItemClass.CUSTOM) + self._buildRootMenu() self.tbAdd = QToolButton(self) self.tbAdd.setToolTip("%s [Ctrl+N]" % self.tr("Add Item")) self.tbAdd.setShortcut("Ctrl+N") - self.tbAdd.setIcon(self.mainTheme.getIcon("add")) self.tbAdd.setIconSize(QSize(iPx, iPx)) - self.tbAdd.setStyleSheet(buttonStyle) self.tbAdd.setMenu(self.mAdd) self.tbAdd.setPopupMode(QToolButton.InstantPopup) @@ -326,9 +301,7 @@ class GuiProjectToolBar(QWidget): self.tbMore = QToolButton(self) self.tbMore.setToolTip(self.tr("More Options")) - self.tbMore.setIcon(self.mainTheme.getIcon("menu")) self.tbMore.setIconSize(QSize(iPx, iPx)) - self.tbMore.setStyleSheet(buttonStyle) self.tbMore.setMenu(self.mMore) self.tbMore.setPopupMode(QToolButton.InstantPopup) @@ -344,6 +317,7 @@ class GuiProjectToolBar(QWidget): self.outerBox.setSpacing(0) self.setLayout(self.outerBox) + self.updateTheme() logger.debug("GuiProjectToolBar initialisation complete") @@ -353,6 +327,41 @@ class GuiProjectToolBar(QWidget): # Methods ## + def updateTheme(self): + """Update theme elements. + """ + qPalette = self.palette() + qPalette.setBrush(QPalette.Window, qPalette.base()) + self.setPalette(qPalette) + + fadeCol = qPalette.text().color() + buttonStyle = ( + "QToolButton {{padding: {0}px; border: none; background: transparent;}} " + "QToolButton:hover {{border: none; background: rgba({1},{2},{3},0.2);}}" + ).format(self.mainConf.pxInt(2), fadeCol.red(), fadeCol.green(), fadeCol.blue()) + + self.tbQuick.setStyleSheet(buttonStyle) + self.tbMoveU.setStyleSheet(buttonStyle) + self.tbMoveD.setStyleSheet(buttonStyle) + self.tbAdd.setStyleSheet(buttonStyle) + self.tbMore.setStyleSheet(buttonStyle) + + self.tbQuick.setIcon(self.mainTheme.getIcon("bookmark")) + self.tbMoveU.setIcon(self.mainTheme.getIcon("up")) + self.tbMoveD.setIcon(self.mainTheme.getIcon("down")) + self.aAddEmpty.setIcon(self.mainTheme.getIcon("proj_document")) + self.aAddChap.setIcon(self.mainTheme.getIcon("proj_chapter")) + self.aAddScene.setIcon(self.mainTheme.getIcon("proj_scene")) + self.aAddNote.setIcon(self.mainTheme.getIcon("proj_note")) + self.aAddFolder.setIcon(self.mainTheme.getIcon("proj_folder")) + self.tbAdd.setIcon(self.mainTheme.getIcon("add")) + self.tbMore.setIcon(self.mainTheme.getIcon("menu")) + + self.buildQuickLinkMenu() + self._buildRootMenu() + + return + def clearContent(self): """Clear dynamic content on the tool bar. """ @@ -379,13 +388,29 @@ class GuiProjectToolBar(QWidget): # Internal Functions ## - def _addRootFolderEntry(self, itemClass): - """Add a menu entry for a root folder of a given class. + def _buildRootMenu(self): + """Build the rood folder menu. """ - aNew = self.mAddRoot.addAction(trConst(nwLabels.CLASS_NAME[itemClass])) - aNew.setIcon(self.mainTheme.getIcon(nwLabels.CLASS_ICON[itemClass])) - aNew.triggered.connect(lambda: self.projTree.newTreeItem(nwItemType.ROOT, itemClass)) - self.mAddRoot.addAction(aNew) + def addClass(itemClass): + aNew = self.mAddRoot.addAction(trConst(nwLabels.CLASS_NAME[itemClass])) + aNew.setIcon(self.mainTheme.getIcon(nwLabels.CLASS_ICON[itemClass])) + aNew.triggered.connect(lambda: self.projTree.newTreeItem(nwItemType.ROOT, itemClass)) + self.mAddRoot.addAction(aNew) + return + + self.mAddRoot.clear() + addClass(nwItemClass.NOVEL) + addClass(nwItemClass.ARCHIVE) + self.mAddRoot.addSeparator() + addClass(nwItemClass.PLOT) + addClass(nwItemClass.CHARACTER) + addClass(nwItemClass.WORLD) + addClass(nwItemClass.TIMELINE) + addClass(nwItemClass.OBJECT) + addClass(nwItemClass.ENTITY) + addClass(nwItemClass.CUSTOM) + + return # END Class GuiProjectToolBar diff --git a/novelwriter/gui/theme.py b/novelwriter/gui/theme.py index b13e43ca..1c52f7dd 100644 --- a/novelwriter/gui/theme.py +++ b/novelwriter/gui/theme.py @@ -130,7 +130,6 @@ class GuiTheme: self.updateFont() self.updateTheme() - self.iconCache.updateTheme() # Icon Functions self.getIcon = self.iconCache.getIcon @@ -220,6 +219,7 @@ class GuiTheme: else: self.cssFile = self.themeFile[:-5]+".css" self.loadTheme() + self.iconCache.updateTheme() self.syntaxFile = self._availSyntax.get(self.guiSyntax, None) if self.syntaxFile is None: @@ -287,6 +287,8 @@ class GuiTheme: self._setPalette(confParser, cnfSec, "highlightedtext", QPalette.HighlightedText) self._setPalette(confParser, cnfSec, "link", QPalette.Link) self._setPalette(confParser, cnfSec, "linkvisited", QPalette.LinkVisited) + else: + self._guiPalette = qApp.style().standardPalette() # GUI cnfSec = "GUI" @@ -578,6 +580,12 @@ class GuiIcons: if iconKey not in self._themeMap: logger.error("No icon file specified for '%s'", iconKey) + # Refresh icons + for iconKey in self._qIcons: + logger.debug("Reloading icon: '%s'", iconKey) + qIcon = self._loadIcon(iconKey) + self._qIcons[iconKey] = qIcon + return True ## diff --git a/novelwriter/gui/viewsbar.py b/novelwriter/gui/viewsbar.py index dcd697db..da85275c 100644 --- a/novelwriter/gui/viewsbar.py +++ b/novelwriter/gui/viewsbar.py @@ -61,7 +61,6 @@ class GuiViewsBar(QToolBar): self.setIconSize(QSize(iPx, iPx)) self.setMaximumWidth(mPx) self.setContentsMargins(0, 0, 0, 0) - self.setStyleSheet("QToolBar {border: 0px;}") stretch = QWidget(self) stretch.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) @@ -70,37 +69,31 @@ class GuiViewsBar(QToolBar): self.aProject = QAction(self.tr("Project"), self) self.aProject.setFont(lblFont) self.aProject.setToolTip(self.tr("Project Tree View")) - self.aProject.setIcon(self.mainTheme.getIcon("view_editor")) self.aProject.triggered.connect(lambda: self.viewChangeRequested.emit(nwView.PROJECT)) self.aNovel = QAction(self.tr("Novel"), self) self.aNovel.setFont(lblFont) self.aNovel.setToolTip(self.tr("Novel Tree View")) - self.aNovel.setIcon(self.mainTheme.getIcon("view_novel")) self.aNovel.triggered.connect(lambda: self.viewChangeRequested.emit(nwView.NOVEL)) self.aOutline = QAction(self.tr("Outline"), self) self.aOutline.setFont(lblFont) self.aOutline.setToolTip(self.tr("Novel Outline View")) - self.aOutline.setIcon(self.mainTheme.getIcon("view_outline")) self.aOutline.triggered.connect(lambda: self.viewChangeRequested.emit(nwView.OUTLINE)) self.aBuild = QAction(self.tr("Build"), self) self.aBuild.setFont(lblFont) self.aBuild.setToolTip(self.tr("Build Novel Project")) - self.aBuild.setIcon(self.mainTheme.getIcon("view_build")) self.aBuild.triggered.connect(lambda: self.mainGui.showBuildProjectDialog()) self.aDetails = QAction(self.tr("Details"), self) self.aDetails.setFont(lblFont) self.aDetails.setToolTip(self.tr("Project Details")) - self.aDetails.setIcon(self.mainTheme.getIcon("proj_details")) self.aDetails.triggered.connect(lambda: self.mainGui.showProjectDetailsDialog()) self.aStats = QAction(self.tr("Stats"), self) self.aStats.setFont(lblFont) self.aStats.setToolTip(self.tr("Writing Statistics")) - self.aStats.setIcon(self.mainTheme.getIcon("proj_stats")) self.aStats.triggered.connect(lambda: self.mainGui.showWritingStatsDialog()) # Settings Menu @@ -114,7 +107,6 @@ class GuiViewsBar(QToolBar): self.tbSettings = QToolButton(self) self.tbSettings.setFont(lblFont) self.tbSettings.setText(self.tr("Settings")) - self.tbSettings.setIcon(self.mainTheme.getIcon("settings")) self.tbSettings.setMenu(self.mSettings) self.tbSettings.setToolButtonStyle(Qt.ToolButtonTextUnderIcon) self.tbSettings.setPopupMode(QToolButton.InstantPopup) @@ -129,8 +121,25 @@ class GuiViewsBar(QToolBar): self.addAction(self.aStats) self.addWidget(self.tbSettings) + self.updateTheme() + logger.debug("GuiViewsBar initialisation complete") return + def updateTheme(self): + """Initialise GUI elements that depend on specific settings. + """ + self.setStyleSheet("QToolBar {border: 0px;}") + + self.aProject.setIcon(self.mainTheme.getIcon("view_editor")) + self.aNovel.setIcon(self.mainTheme.getIcon("view_novel")) + self.aOutline.setIcon(self.mainTheme.getIcon("view_outline")) + self.aBuild.setIcon(self.mainTheme.getIcon("view_build")) + self.aDetails.setIcon(self.mainTheme.getIcon("proj_details")) + self.aStats.setIcon(self.mainTheme.getIcon("proj_stats")) + self.tbSettings.setIcon(self.mainTheme.getIcon("settings")) + + return + # END Class GuiViewsBar diff --git a/novelwriter/guimain.py b/novelwriter/guimain.py index cf0a9dff..9b18220a 100644 --- a/novelwriter/guimain.py +++ b/novelwriter/guimain.py @@ -907,8 +907,15 @@ class GuiMain(QMainWindow): if dlgConf.result() == QDialog.Accepted: logger.debug("Applying new preferences") self.initMain() - self.mainTheme.updateTheme() self.saveDocument() + + self.mainTheme.updateTheme() + self.viewsBar.updateTheme() + self.projView.updateTheme() + self.novelView.updateTheme() + self.docEditor.updateTheme() + self.docViewer.updateTheme() + self.docEditor.initEditor() self.docViewer.initViewer() self.projView.initSettings() From 88183cdc39b9911692a5f72d92b0958cba8947c0 Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Tue, 25 Oct 2022 23:42:43 +0200 Subject: [PATCH 12/36] Add updateTheme functions to remaining GUI components --- novelwriter/dialogs/preferences.py | 38 +++++++++++++----------------- novelwriter/gui/doceditor.py | 28 ++++++++++++---------- novelwriter/gui/docviewer.py | 26 +++++++++++--------- novelwriter/gui/itemdetails.py | 15 ++++++++---- novelwriter/gui/noveltree.py | 2 -- novelwriter/gui/outline.py | 20 +++++++++++++--- novelwriter/gui/statusbar.py | 22 ++++++++++++----- novelwriter/guimain.py | 28 +++++++++++++++++----- 8 files changed, 113 insertions(+), 66 deletions(-) diff --git a/novelwriter/dialogs/preferences.py b/novelwriter/dialogs/preferences.py index 4551d3e6..6236a2a5 100644 --- a/novelwriter/dialogs/preferences.py +++ b/novelwriter/dialogs/preferences.py @@ -34,7 +34,6 @@ from PyQt5.QtWidgets import ( QLineEdit, QFileDialog, QFontDialog, QDoubleSpinBox ) -from novelwriter.enum import nwAlert from novelwriter.custom import QSwitch, QConfigLayout, PagedDialog from novelwriter.dialogs.quotes import GuiQuoteSelect @@ -78,6 +77,12 @@ class GuiPreferences(PagedDialog): self.resize(*self.mainConf.getPreferencesSize()) + # Settings + self.updateTheme = False + self.updateSyntax = False + self.needsRestart = False + self.refreshTree = False + logger.debug("GuiPreferences initialisation complete") return @@ -92,8 +97,7 @@ class GuiPreferences(PagedDialog): """ logger.debug("Saving new preferences") - needsRestart, refreshTree = self.tabGeneral.saveValues() - + self.tabGeneral.saveValues() self.tabProjects.saveValues() self.tabDocs.saveValues() self.tabEditor.saveValues() @@ -101,14 +105,6 @@ class GuiPreferences(PagedDialog): self.tabAuto.saveValues() self.tabQuote.saveValues() - if needsRestart: - self.mainGui.makeAlert(self.tr( - "Some changes will not be applied until novelWriter has been restarted." - ), nwAlert.INFO) - - if refreshTree: - self.mainGui.projView.populateTree() - self._saveWindowSize() self.accept() @@ -140,6 +136,7 @@ class GuiPreferencesGeneral(QWidget): super().__init__(parent=prefsGui) self.mainConf = novelwriter.CONFIG + self.prefsGui = prefsGui self.mainGui = prefsGui.mainGui self.mainTheme = prefsGui.mainGui.mainTheme @@ -277,16 +274,13 @@ class GuiPreferencesGeneral(QWidget): guiFontSize = self.guiFontSize.value() emphLabels = self.emphLabels.isChecked() - # Check if restart is needed - needsRestart = False - needsRestart |= self.mainConf.guiLang != guiLang - needsRestart |= self.mainConf.guiTheme != guiTheme - needsRestart |= self.mainConf.guiFont != guiFont - needsRestart |= self.mainConf.guiFontSize != guiFontSize - - # Check if refreshing project tree is needed - refreshTree = False - refreshTree |= self.mainConf.emphLabels != emphLabels + # Update Flags + self.prefsGui.updateTheme |= self.mainConf.guiTheme != guiTheme + self.prefsGui.updateSyntax |= self.mainConf.guiSyntax != guiSyntax + self.prefsGui.needsRestart |= self.mainConf.guiLang != guiLang + self.prefsGui.needsRestart |= self.mainConf.guiFont != guiFont + self.prefsGui.needsRestart |= self.mainConf.guiFontSize != guiFontSize + self.prefsGui.refreshTree |= self.mainConf.emphLabels != emphLabels self.mainConf.guiLang = guiLang self.mainConf.guiTheme = guiTheme @@ -300,7 +294,7 @@ class GuiPreferencesGeneral(QWidget): self.mainConf.confChanged = True - return needsRestart, refreshTree + return ## # Slots diff --git a/novelwriter/gui/doceditor.py b/novelwriter/gui/doceditor.py index 6b098959..8db3734a 100644 --- a/novelwriter/gui/doceditor.py +++ b/novelwriter/gui/doceditor.py @@ -2650,17 +2650,11 @@ class GuiDocEditHeader(QWidget): lblFont.setPointSizeF(0.9*self.mainTheme.fontPointSize) self.theTitle.setFont(lblFont) - buttonStyle = ( - "QToolButton {{border: none; background: transparent;}} " - "QToolButton:hover {{border: none; background: rgba({0},{1},{2},0.2);}}" - ).format(*self.mainTheme.colText) - # Buttons self.editButton = QToolButton(self) self.editButton.setContentsMargins(0, 0, 0, 0) self.editButton.setIconSize(QSize(fPx, fPx)) self.editButton.setFixedSize(fPx, fPx) - self.editButton.setStyleSheet(buttonStyle) self.editButton.setToolButtonStyle(Qt.ToolButtonIconOnly) self.editButton.setVisible(False) self.editButton.setToolTip(self.tr("Edit document label")) @@ -2670,7 +2664,6 @@ class GuiDocEditHeader(QWidget): self.searchButton.setContentsMargins(0, 0, 0, 0) self.searchButton.setIconSize(QSize(fPx, fPx)) self.searchButton.setFixedSize(fPx, fPx) - self.searchButton.setStyleSheet(buttonStyle) self.searchButton.setToolButtonStyle(Qt.ToolButtonIconOnly) self.searchButton.setVisible(False) self.searchButton.setToolTip(self.tr("Search document")) @@ -2680,7 +2673,6 @@ class GuiDocEditHeader(QWidget): self.minmaxButton.setContentsMargins(0, 0, 0, 0) self.minmaxButton.setIconSize(QSize(fPx, fPx)) self.minmaxButton.setFixedSize(fPx, fPx) - self.minmaxButton.setStyleSheet(buttonStyle) self.minmaxButton.setToolButtonStyle(Qt.ToolButtonIconOnly) self.minmaxButton.setVisible(False) self.minmaxButton.setToolTip(self.tr("Toggle Focus Mode")) @@ -2690,7 +2682,6 @@ class GuiDocEditHeader(QWidget): self.closeButton.setContentsMargins(0, 0, 0, 0) self.closeButton.setIconSize(QSize(fPx, fPx)) self.closeButton.setFixedSize(fPx, fPx) - self.closeButton.setStyleSheet(buttonStyle) self.closeButton.setToolButtonStyle(Qt.ToolButtonIconOnly) self.closeButton.setVisible(False) self.closeButton.setToolTip(self.tr("Close the document")) @@ -2713,9 +2704,7 @@ class GuiDocEditHeader(QWidget): self.outerBox.setContentsMargins(cM, cM, cM, cM) self.setMinimumHeight(fPx + 2*cM) - # Fix the Colours self.updateTheme() - self.matchColours() logger.debug("GuiDocEditHeader initialisation complete") @@ -2732,6 +2721,19 @@ class GuiDocEditHeader(QWidget): self.searchButton.setIcon(self.mainTheme.getIcon("search")) self.minmaxButton.setIcon(self.mainTheme.getIcon("maximise")) self.closeButton.setIcon(self.mainTheme.getIcon("close")) + + buttonStyle = ( + "QToolButton {{border: none; background: transparent;}} " + "QToolButton:hover {{border: none; background: rgba({0},{1},{2},0.2);}}" + ).format(*self.mainTheme.colText) + + self.editButton.setStyleSheet(buttonStyle) + self.searchButton.setStyleSheet(buttonStyle) + self.minmaxButton.setStyleSheet(buttonStyle) + self.closeButton.setStyleSheet(buttonStyle) + + self.matchColours() + return def matchColours(self): @@ -2942,7 +2944,6 @@ class GuiDocEditFooter(QWidget): # Fix the Colours self.updateTheme() - self.matchColours() self.updateLineCount() self.updateCounts() @@ -2959,6 +2960,9 @@ class GuiDocEditFooter(QWidget): """ self.linesIcon.setPixmap(self.mainTheme.getPixmap("status_lines", (self.sPx, self.sPx))) self.wordsIcon.setPixmap(self.mainTheme.getPixmap("status_stats", (self.sPx, self.sPx))) + + self.matchColours() + return def matchColours(self): diff --git a/novelwriter/gui/docviewer.py b/novelwriter/gui/docviewer.py index d3cac0b0..1b1a5b53 100644 --- a/novelwriter/gui/docviewer.py +++ b/novelwriter/gui/docviewer.py @@ -742,17 +742,11 @@ class GuiDocViewHeader(QWidget): lblFont.setPointSizeF(0.9*self.mainTheme.fontPointSize) self.theTitle.setFont(lblFont) - buttonStyle = ( - "QToolButton {{border: none; background: transparent;}} " - "QToolButton:hover {{border: none; background: rgba({0},{1},{2},0.2);}}" - ).format(*self.mainTheme.colText) - # Buttons self.backButton = QToolButton(self) self.backButton.setContentsMargins(0, 0, 0, 0) self.backButton.setIconSize(QSize(fPx, fPx)) self.backButton.setFixedSize(fPx, fPx) - self.backButton.setStyleSheet(buttonStyle) self.backButton.setToolButtonStyle(Qt.ToolButtonIconOnly) self.backButton.setVisible(False) self.backButton.setToolTip(self.tr("Go backward")) @@ -762,7 +756,6 @@ class GuiDocViewHeader(QWidget): self.forwardButton.setContentsMargins(0, 0, 0, 0) self.forwardButton.setIconSize(QSize(fPx, fPx)) self.forwardButton.setFixedSize(fPx, fPx) - self.forwardButton.setStyleSheet(buttonStyle) self.forwardButton.setToolButtonStyle(Qt.ToolButtonIconOnly) self.forwardButton.setVisible(False) self.forwardButton.setToolTip(self.tr("Go forward")) @@ -772,7 +765,6 @@ class GuiDocViewHeader(QWidget): self.refreshButton.setContentsMargins(0, 0, 0, 0) self.refreshButton.setIconSize(QSize(fPx, fPx)) self.refreshButton.setFixedSize(fPx, fPx) - self.refreshButton.setStyleSheet(buttonStyle) self.refreshButton.setToolButtonStyle(Qt.ToolButtonIconOnly) self.refreshButton.setVisible(False) self.refreshButton.setToolTip(self.tr("Reload the document")) @@ -782,7 +774,6 @@ class GuiDocViewHeader(QWidget): self.closeButton.setContentsMargins(0, 0, 0, 0) self.closeButton.setIconSize(QSize(fPx, fPx)) self.closeButton.setFixedSize(fPx, fPx) - self.closeButton.setStyleSheet(buttonStyle) self.closeButton.setToolButtonStyle(Qt.ToolButtonIconOnly) self.closeButton.setVisible(False) self.closeButton.setToolTip(self.tr("Close the document")) @@ -807,7 +798,6 @@ class GuiDocViewHeader(QWidget): # Fix the Colours self.updateTheme() - self.matchColours() logger.debug("GuiDocViewHeader initialisation complete") @@ -824,6 +814,19 @@ class GuiDocViewHeader(QWidget): self.forwardButton.setIcon(self.mainTheme.getIcon("forward")) self.refreshButton.setIcon(self.mainTheme.getIcon("refresh")) self.closeButton.setIcon(self.mainTheme.getIcon("close")) + + buttonStyle = ( + "QToolButton {{border: none; background: transparent;}} " + "QToolButton:hover {{border: none; background: rgba({0},{1},{2},0.2);}}" + ).format(*self.mainTheme.colText) + + self.backButton.setStyleSheet(buttonStyle) + self.forwardButton.setStyleSheet(buttonStyle) + self.refreshButton.setStyleSheet(buttonStyle) + self.closeButton.setStyleSheet(buttonStyle) + + self.matchColours() + return def matchColours(self): @@ -1051,7 +1054,6 @@ class GuiDocViewFooter(QWidget): # Fix the Colours self.updateTheme() - self.matchColours() logger.debug("GuiDocViewFooter initialisation complete") @@ -1097,6 +1099,8 @@ class GuiDocViewFooter(QWidget): self.showComments.setStyleSheet(buttonStyle) self.showSynopsis.setStyleSheet(buttonStyle) + self.matchColours() + return def matchColours(self): diff --git a/novelwriter/gui/itemdetails.py b/novelwriter/gui/itemdetails.py index a99650c2..e8eae31d 100644 --- a/novelwriter/gui/itemdetails.py +++ b/novelwriter/gui/itemdetails.py @@ -53,12 +53,8 @@ class GuiItemDetails(QWidget): hSp = self.mainConf.pxInt(6) vSp = self.mainConf.pxInt(1) mPx = self.mainConf.pxInt(6) - iPx = self.mainTheme.baseIconSize fPt = self.mainTheme.fontPointSize - self._expCheck = self.mainTheme.getPixmap("check", (iPx, iPx)) - self._expCross = self.mainTheme.getPixmap("cross", (iPx, iPx)) - fntLabel = QFont() fntLabel.setBold(True) fntLabel.setPointSizeF(0.9*fPt) @@ -179,6 +175,8 @@ class GuiItemDetails(QWidget): self.setLayout(self.mainBox) + self.updateTheme() + # Make sure the columns for flags and counts don't resize too often flagWidth = self.mainTheme.getTextWidth("Mm", fntValue) countWidth = self.mainTheme.getTextWidth("99,999", fntValue) @@ -219,6 +217,15 @@ class GuiItemDetails(QWidget): """ self.updateViewBox(self._itemHandle) + def updateTheme(self): + """Update theme elements. + """ + iPx = self.mainTheme.baseIconSize + self._expCheck = self.mainTheme.getPixmap("check", (iPx, iPx)) + self._expCross = self.mainTheme.getPixmap("cross", (iPx, iPx)) + self.updateViewBox(self._itemHandle) + return + ## # Public Slots ## diff --git a/novelwriter/gui/noveltree.py b/novelwriter/gui/noveltree.py index 489b7ab6..54bf884d 100644 --- a/novelwriter/gui/noveltree.py +++ b/novelwriter/gui/noveltree.py @@ -252,7 +252,6 @@ class GuiNovelToolBar(QWidget): """Update theme elements. """ # Icons - self.tbRefresh.setIcon(self.mainTheme.getIcon("refresh")) self.tbRoot.setIcon(self.mainTheme.getIcon(nwLabels.CLASS_ICON[nwItemClass.NOVEL])) self.tbMore.setIcon(self.mainTheme.getIcon("menu")) @@ -262,7 +261,6 @@ class GuiNovelToolBar(QWidget): self.setPalette(qPalette) # StyleSheets - fadeCol = qPalette.text().color() buttonStyle = ( "QToolButton {{padding: {0}px; border: none; background: transparent;}} " diff --git a/novelwriter/gui/outline.py b/novelwriter/gui/outline.py index bb900d9f..dc0bf6d6 100644 --- a/novelwriter/gui/outline.py +++ b/novelwriter/gui/outline.py @@ -97,6 +97,13 @@ class GuiOutlineView(QWidget): # Methods ## + def updateTheme(self): + """Update theme elements. + """ + self.outlineBar.updateTheme() + self.refreshTree() + return + def initSettings(self): """Initialise GUI elements that depend on specific settings. """ @@ -213,7 +220,6 @@ class GuiOutlineToolBar(QToolBar): self.setMovable(False) self.setIconSize(QSize(iPx, iPx)) self.setContentsMargins(0, 0, 0, 0) - self.setStyleSheet("QToolBar {border: 0px;}") stretch = QWidget(self) stretch.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) @@ -228,7 +234,6 @@ class GuiOutlineToolBar(QToolBar): # Actions self.aRefresh = QAction(self.tr("Refresh"), self) - self.aRefresh.setIcon(self.mainTheme.getIcon("refresh")) self.aRefresh.triggered.connect(self._refreshRequested) # Column Menu @@ -238,7 +243,6 @@ class GuiOutlineToolBar(QToolBar): ) self.tbColumns = QToolButton(self) - self.tbColumns.setIcon(self.mainTheme.getIcon("menu")) self.tbColumns.setMenu(self.mColumns) self.tbColumns.setPopupMode(QToolButton.InstantPopup) @@ -258,6 +262,16 @@ class GuiOutlineToolBar(QToolBar): # Methods ## + def updateTheme(self): + """Update theme elements. + """ + self.setStyleSheet("QToolBar {border: 0px;}") + + self.aRefresh.setIcon(self.mainTheme.getIcon("refresh")) + self.tbColumns.setIcon(self.mainTheme.getIcon("menu")) + + return + def populateNovelList(self): """Fill the novel combo box with a list of all novel folders. """ diff --git a/novelwriter/gui/statusbar.py b/novelwriter/gui/statusbar.py index c6191b8d..26830e2d 100644 --- a/novelwriter/gui/statusbar.py +++ b/novelwriter/gui/statusbar.py @@ -66,7 +66,6 @@ class GuiMainStatus(QStatusBar): # The Spell Checker Language self.langIcon = QLabel("") self.langText = QLabel(self.tr("None")) - self.langIcon.setPixmap(self.mainTheme.getPixmap("status_lang", (iPx, iPx))) self.langIcon.setContentsMargins(0, 0, 0, 0) self.langText.setContentsMargins(0, 0, xM, 0) self.addPermanentWidget(self.langIcon) @@ -91,7 +90,6 @@ class GuiMainStatus(QStatusBar): # The Project and Session Stats self.statsIcon = QLabel() self.statsText = QLabel("") - self.statsIcon.setPixmap(self.mainTheme.getPixmap("status_stats", (iPx, iPx))) self.statsIcon.setContentsMargins(0, 0, 0, 0) self.statsText.setContentsMargins(0, 0, xM, 0) self.addPermanentWidget(self.statsIcon) @@ -99,12 +97,8 @@ class GuiMainStatus(QStatusBar): # The Session Clock # Set the mimimum width so the label doesn't rescale every second - self.timePixmap = self.mainTheme.getPixmap("status_time", (iPx, iPx)) - self.idlePixmap = self.mainTheme.getPixmap("status_idle", (iPx, iPx)) - self.timeIcon = QLabel() self.timeText = QLabel("") - self.timeIcon.setPixmap(self.timePixmap) self.timeText.setToolTip(self.tr("Session Time")) self.timeText.setMinimumWidth(self.mainTheme.getTextWidth("00:00:00:")) self.timeIcon.setContentsMargins(0, 0, 0, 0) @@ -117,6 +111,7 @@ class GuiMainStatus(QStatusBar): logger.debug("GuiMainStatus initialisation complete") + self.updateTheme() self.clearStatus() return @@ -132,6 +127,21 @@ class GuiMainStatus(QStatusBar): self.updateTime() return True + def updateTheme(self): + """Update theme elements. + """ + iPx = self.mainTheme.baseIconSize + + self.langIcon.setPixmap(self.mainTheme.getPixmap("status_lang", (iPx, iPx))) + self.statsIcon.setPixmap(self.mainTheme.getPixmap("status_stats", (iPx, iPx))) + + self.timePixmap = self.mainTheme.getPixmap("status_time", (iPx, iPx)) + self.idlePixmap = self.mainTheme.getPixmap("status_idle", (iPx, iPx)) + + self.timeIcon.setPixmap(self.timePixmap) + + return + ## # Setters ## diff --git a/novelwriter/guimain.py b/novelwriter/guimain.py index 9b18220a..a6ac3407 100644 --- a/novelwriter/guimain.py +++ b/novelwriter/guimain.py @@ -909,18 +909,34 @@ class GuiMain(QMainWindow): self.initMain() self.saveDocument() - self.mainTheme.updateTheme() - self.viewsBar.updateTheme() - self.projView.updateTheme() - self.novelView.updateTheme() - self.docEditor.updateTheme() - self.docViewer.updateTheme() + if dlgConf.needsRestart: + self.makeAlert(self.tr( + "Some changes will not be applied until novelWriter has been restarted." + ), nwAlert.INFO) + + if dlgConf.refreshTree: + self.projView.populateTree() + + if dlgConf.updateTheme: + self.mainTheme.updateTheme() + self.docEditor.updateTheme() + self.docViewer.updateTheme() + self.viewsBar.updateTheme() + self.projView.updateTheme() + self.novelView.updateTheme() + self.outlineView.updateTheme() + self.itemDetails.updateTheme() + self.mainStatus.updateTheme() + + if dlgConf.updateSyntax: + pass self.docEditor.initEditor() self.docViewer.initViewer() self.projView.initSettings() self.novelView.initSettings() self.outlineView.initSettings() + self._updateStatusWordCount() return From 8d7ebefa65fc353c40591eac003d8d318d87e56e Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Tue, 25 Oct 2022 23:56:07 +0200 Subject: [PATCH 13/36] Fix updating syntax colours (#1171) --- novelwriter/gui/doceditor.py | 40 ++++++++++++++++++++---------------- novelwriter/gui/theme.py | 20 ++++++++++++------ novelwriter/guimain.py | 3 ++- 3 files changed, 38 insertions(+), 25 deletions(-) diff --git a/novelwriter/gui/doceditor.py b/novelwriter/gui/doceditor.py index 8db3734a..179cab17 100644 --- a/novelwriter/gui/doceditor.py +++ b/novelwriter/gui/doceditor.py @@ -173,6 +173,7 @@ class GuiDocEditor(QTextEdit): self.wCounterSel.signals.countsReady.connect(self._updateSelCounts) # Finalise + self.updateSyntaxColours() self.initEditor() logger.debug("GuiDocEditor initialisation complete") @@ -214,6 +215,27 @@ class GuiDocEditor(QTextEdit): self.docFooter.updateTheme() return + def updateSyntaxColours(self): + """Update the syntax highlighting theme. + """ + mainPalette = self.palette() + mainPalette.setColor(QPalette.Window, QColor(*self.mainTheme.colBack)) + mainPalette.setColor(QPalette.Base, QColor(*self.mainTheme.colBack)) + mainPalette.setColor(QPalette.Text, QColor(*self.mainTheme.colText)) + self.setPalette(mainPalette) + + docPalette = self.viewport().palette() + docPalette.setColor(QPalette.Base, QColor(*self.mainTheme.colBack)) + docPalette.setColor(QPalette.Text, QColor(*self.mainTheme.colText)) + self.viewport().setPalette(docPalette) + + self.docHeader.matchColours() + self.docFooter.matchColours() + + self.highLight.initHighlighter() + + return + def initEditor(self): """Initialise or re-initialise the editor with the user's settings. This function is both called when the editor is @@ -260,21 +282,6 @@ class GuiDocEditor(QTextEdit): theFont.setPointSize(self.mainConf.textSize) self.setFont(theFont) - # Set the widget colours to match syntax theme - mainPalette = self.palette() - mainPalette.setColor(QPalette.Window, QColor(*self.mainTheme.colBack)) - mainPalette.setColor(QPalette.Base, QColor(*self.mainTheme.colBack)) - mainPalette.setColor(QPalette.Text, QColor(*self.mainTheme.colText)) - self.setPalette(mainPalette) - - docPalette = self.viewport().palette() - docPalette.setColor(QPalette.Base, QColor(*self.mainTheme.colBack)) - docPalette.setColor(QPalette.Text, QColor(*self.mainTheme.colText)) - self.viewport().setPalette(docPalette) - - self.docHeader.matchColours() - self.docFooter.matchColours() - # Set default text margins # Due to cursor visibility, a part of the margin must be # allocated to the document itself. See issue #1112. @@ -309,9 +316,6 @@ class GuiDocEditor(QTextEdit): # Refresh the tab stops self.setTabStopDistance(self.mainConf.getTabWidth()) - # Initialise the syntax highlighter - self.highLight.initHighlighter() - # Configure word count timer self.wcInterval = self.mainConf.wordCountTimer self.wcTimerDoc.setInterval(int(self.wcInterval*1000)) diff --git a/novelwriter/gui/theme.py b/novelwriter/gui/theme.py index 1c52f7dd..5761f28f 100644 --- a/novelwriter/gui/theme.py +++ b/novelwriter/gui/theme.py @@ -130,6 +130,7 @@ class GuiTheme: self.updateFont() self.updateTheme() + self.updateSyntax() # Icon Functions self.getIcon = self.iconCache.getIcon @@ -221,12 +222,6 @@ class GuiTheme: self.loadTheme() self.iconCache.updateTheme() - self.syntaxFile = self._availSyntax.get(self.guiSyntax, None) - if self.syntaxFile is None: - logger.error("Could not find syntax theme '%s'", self.guiSyntax) - else: - self.loadSyntax() - # Update dependant colours backCol = qApp.palette().window().color() textCol = qApp.palette().windowText().color() @@ -243,6 +238,19 @@ class GuiTheme: return True + def updateSyntax(self): + """Update the syntac theme from theme files. + """ + self.guiSyntax = self.mainConf.guiSyntax + + self.syntaxFile = self._availSyntax.get(self.guiSyntax, None) + if self.syntaxFile is None: + logger.error("Could not find syntax theme '%s'", self.guiSyntax) + else: + self.loadSyntax() + + return True + def loadTheme(self): """Load the currently specified GUI theme. """ diff --git a/novelwriter/guimain.py b/novelwriter/guimain.py index a6ac3407..93cdbcc2 100644 --- a/novelwriter/guimain.py +++ b/novelwriter/guimain.py @@ -929,7 +929,8 @@ class GuiMain(QMainWindow): self.mainStatus.updateTheme() if dlgConf.updateSyntax: - pass + self.mainTheme.updateSyntax() + self.docEditor.updateSyntaxColours() self.docEditor.initEditor() self.docViewer.initViewer() From c7999831c9631acbc861061b6e8a0f8c9ec2fb83 Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Wed, 26 Oct 2022 22:01:04 +0200 Subject: [PATCH 14/36] Remove icon setting from config, and let the theme control it --- novelwriter/config.py | 9 ----- novelwriter/gui/noveltree.py | 10 ++++- novelwriter/gui/theme.py | 40 +++++-------------- tests/reference/baseConfig_novelwriter.conf | 3 +- .../reference/guiPreferences_novelwriter.conf | 7 ++-- tests/test_gui/test_gui_theme.py | 6 +-- 6 files changed, 24 insertions(+), 51 deletions(-) diff --git a/novelwriter/config.py b/novelwriter/config.py index ecc5f4db..d939faea 100644 --- a/novelwriter/config.py +++ b/novelwriter/config.py @@ -73,7 +73,6 @@ class Config: # General self.guiTheme = "" # GUI theme self.guiSyntax = "" # Syntax theme - self.guiIcons = "" # Icon theme self.guiFont = "" # Defaults to system default font self.guiFontSize = 11 # Is overridden if system default is loaded self.guiScale = 1.0 # Set automatically by Theme class @@ -81,7 +80,6 @@ class Config: self.setDefaultGuiTheme() self.setDefaultSyntaxTheme() - self.setDefaultIconTheme() # Localisation self.qLocal = QLocale.system() @@ -410,7 +408,6 @@ class Config: cnfSec = "Main" self.guiTheme = theConf.rdStr(cnfSec, "theme", self.guiTheme) self.guiSyntax = theConf.rdStr(cnfSec, "syntax", self.guiSyntax) - self.guiIcons = theConf.rdStr(cnfSec, "icons", self.guiIcons) self.guiFont = theConf.rdStr(cnfSec, "guifont", self.guiFont) self.guiFontSize = theConf.rdInt(cnfSec, "guifontsize", self.guiFontSize) self.lastNotes = theConf.rdStr(cnfSec, "lastnotes", self.lastNotes) @@ -523,7 +520,6 @@ class Config: "timestamp": formatTimeStamp(time()), "theme": str(self.guiTheme), "syntax": str(self.guiSyntax), - "icons": str(self.guiIcons), "guifont": str(self.guiFont), "guifontsize": str(self.guiFontSize), "lastnotes": str(self.lastNotes), @@ -810,11 +806,6 @@ class Config: """ self.guiSyntax = "default_light" - def setDefaultIconTheme(self): - """Reset the icon theme to default value. - """ - self.guiIcons = "typicons_light" - ## # Getters ## diff --git a/novelwriter/gui/noveltree.py b/novelwriter/gui/noveltree.py index 54bf884d..210626b5 100644 --- a/novelwriter/gui/noveltree.py +++ b/novelwriter/gui/noveltree.py @@ -96,6 +96,7 @@ class GuiNovelView(QWidget): """Update theme elements. """ self.novelBar.updateTheme() + self.novelTree.updateTheme() self.refreshTree() return @@ -408,7 +409,6 @@ class GuiNovelTree(QTreeWidget): fH2.setBold(True) self._hFonts = [self.font(), fH1, fH2, self.font(), self.font()] - self._pMore = self.mainTheme.loadDecoration("deco_doc_more", pxH=iPx) # Connect signals self.clicked.connect(self._treeItemClicked) @@ -417,6 +417,7 @@ class GuiNovelTree(QTreeWidget): # Set custom settings self.initSettings() + self.updateTheme() logger.debug("GuiNovelTree initialisation complete") @@ -438,6 +439,13 @@ class GuiNovelTree(QTreeWidget): return + def updateTheme(self): + """Update theme elements. + """ + iPx = self.mainTheme.baseIconSize + self._pMore = self.mainTheme.loadDecoration("deco_doc_more", pxH=iPx) + return + ## # Properties ## diff --git a/novelwriter/gui/theme.py b/novelwriter/gui/theme.py index 5761f28f..bbb7c177 100644 --- a/novelwriter/gui/theme.py +++ b/novelwriter/gui/theme.py @@ -220,7 +220,7 @@ class GuiTheme: else: self.cssFile = self.themeFile[:-5]+".css" self.loadTheme() - self.iconCache.updateTheme() + self.iconCache.updateTheme(self.themeIcons) # Update dependant colours backCol = qApp.palette().window().color() @@ -305,9 +305,6 @@ class GuiTheme: self.statUnsaved = self._loadColour(confParser, cnfSec, "statusunsaved") self.statSaved = self._loadColour(confParser, cnfSec, "statussaved") - # Set Icon Theme - self.mainConf.guiIcons = self.themeIcons - # CSS File cssData = readTextFile(self.cssFile) if cssData: @@ -511,8 +508,7 @@ class GuiIcons: self._confName = "icons.conf" # Icon Theme Path - self._iconPath = os.path.join(self.mainConf.assetPath, "icons") - self._themePath = os.path.join(self._iconPath, "system") + self._iconPath = os.path.join(self.mainConf.assetPath, "icons") # Icon Theme Meta self.themeName = "" @@ -529,20 +525,19 @@ class GuiIcons: # Actions ## - def updateTheme(self): + def updateTheme(self, iconTheme): """Update the theme map. This is more of an init, since many of the GUI icons cannot really be replaced without writing specific update functions for the classes where they're used. """ self._themeMap = {} - themePath = self._getThemePath() - if themePath is None: - logger.warning("No icons loaded") + themePath = os.path.join(self.mainConf.assetPath, "icons", iconTheme) + if not os.path.isdir(themePath): + logger.warning("No icons loaded for '%s'", iconTheme) return False - self._themePath = themePath themeConf = os.path.join(themePath, self._confName) - logger.info("Loading icon theme '%s'", self.mainConf.guiIcons) + logger.info("Loading icon theme '%s'", iconTheme) # Config File confParser = NWConfigParser() @@ -572,7 +567,7 @@ class GuiIcons: if iconName not in self.ICON_KEYS: logger.error("Unknown icon name '%s' in config file", iconName) else: - iconPath = os.path.join(self._themePath, iconFile) + iconPath = os.path.join(themePath, iconFile) if os.path.isfile(iconPath): self._themeMap[iconName] = iconPath logger.debug("Icon slot '%s' using file '%s'", iconName, iconFile) @@ -594,6 +589,8 @@ class GuiIcons: qIcon = self._loadIcon(iconKey) self._qIcons[iconKey] = qIcon + self._headerDec = [] + return True ## @@ -692,23 +689,6 @@ class GuiIcons: # Internal Functions ## - def _getThemePath(self): - """Get a valid theme path. Returns None if it fails. - """ - themePath = os.path.join(self.mainConf.assetPath, "icons", self.mainConf.guiIcons) - if not os.path.isdir(themePath): - logger.warning( - "Icon theme '%s' not found, resetting to default", self.mainConf.guiIcons - ) - self.mainConf.setDefaultIconTheme() - - themePath = os.path.join(self.mainConf.assetPath, "icons", self.mainConf.guiIcons) - if not os.path.isdir(themePath): - logger.error("Default icon theme not found") - return None - - return themePath - def _loadIcon(self, iconKey): """Load an icon from the assets themes folder. Is guaranteed to return a QIcon. diff --git a/tests/reference/baseConfig_novelwriter.conf b/tests/reference/baseConfig_novelwriter.conf index 56b5a807..87c65cba 100644 --- a/tests/reference/baseConfig_novelwriter.conf +++ b/tests/reference/baseConfig_novelwriter.conf @@ -1,8 +1,7 @@ [Main] -timestamp = 2021-12-31 16:45:32 +timestamp = 2022-10-26 11:19:49 theme = default syntax = default_light -icons = typicons_light guifont = guifontsize = 11 lastnotes = 0x0 diff --git a/tests/reference/guiPreferences_novelwriter.conf b/tests/reference/guiPreferences_novelwriter.conf index 9d21830d..d42234e8 100644 --- a/tests/reference/guiPreferences_novelwriter.conf +++ b/tests/reference/guiPreferences_novelwriter.conf @@ -1,9 +1,8 @@ [Main] -timestamp = 2021-12-31 16:45:34 +timestamp = 2022-10-26 11:19:51 theme = default syntax = default_light -icons = typicons_light -guifont = Sans +guifont = Cantarell guifontsize = 12 lastnotes = 0x0 guilang = en_GB @@ -12,7 +11,7 @@ hidehscroll = True [Sizes] geometry = 1200, 650 -preferences = 670, 589 +preferences = 699, 614 projcols = 200, 60, 140 mainpane = 300, 800 docpane = 400, 400 diff --git a/tests/test_gui/test_gui_theme.py b/tests/test_gui/test_gui_theme.py index 3c93ad60..062c1b87 100644 --- a/tests/test_gui/test_gui_theme.py +++ b/tests/test_gui/test_gui_theme.py @@ -50,7 +50,6 @@ def testGuiTheme_Main(qtbot, monkeypatch, nwMinimal, tmpDir): assert novelwriter.CONFIG.confPath == nwMinimal novelwriter.CONFIG.guiTheme = "default_dark" novelwriter.CONFIG.guiSyntax = "tomorrow_night_eighties" - novelwriter.CONFIG.guiIcons = "typicons_dark" novelwriter.CONFIG.guiFont = "Cantarell" novelwriter.CONFIG.guiFontSize = 11 novelwriter.CONFIG.confChanged = True @@ -72,7 +71,6 @@ def testGuiTheme_Main(qtbot, monkeypatch, nwMinimal, tmpDir): assert novelwriter.CONFIG.guiTheme == "default_dark" assert novelwriter.CONFIG.guiSyntax == "tomorrow_night_eighties" - assert novelwriter.CONFIG.guiIcons == "typicons_dark" assert novelwriter.CONFIG.guiFont != "" assert novelwriter.CONFIG.guiFontSize > 0 @@ -117,9 +115,7 @@ def testGuiTheme_Main(qtbot, monkeypatch, nwMinimal, tmpDir): # Test Icon class iconCache = nwGUI.mainTheme.iconCache - novelwriter.CONFIG.guiIcons = "invalid" - assert iconCache.updateTheme() is True - assert novelwriter.CONFIG.guiIcons == "typicons_light" + assert iconCache.updateTheme("invalid") is False # Ask for a non-existent key anImg = iconCache.loadDecoration("nonsense", 20, 20) From 34193021e84269028226dc43903c36a70f081d55 Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Thu, 27 Oct 2022 23:47:29 +0200 Subject: [PATCH 15/36] Simplify the themes class a bit --- novelwriter/gui/theme.py | 233 +++++++++++++++++---------------------- novelwriter/guimain.py | 4 +- 2 files changed, 105 insertions(+), 132 deletions(-) diff --git a/novelwriter/gui/theme.py b/novelwriter/gui/theme.py index bbb7c177..68e2f716 100644 --- a/novelwriter/gui/theme.py +++ b/novelwriter/gui/theme.py @@ -105,16 +105,14 @@ class GuiTheme: self.colRepTag = [0, 0, 0] self.colMod = [0, 0, 0] - # Changeable Settings - self.guiTheme = None - self.guiSyntax = None - self.syntaxFile = None - self.cssFile = None - self.guiFontDB = QFontDatabase() - # Class Setup # =========== + # Init GUI Font + self.guiFontDB = QFontDatabase() + self._setGuiFont() + + # Load Themes self._guiPalette = QPalette() self._themeList = [] self._syntaxList = [] @@ -128,9 +126,8 @@ class GuiTheme: self._listConf(self._availSyntax, os.path.join(self.mainConf.dataPath, "syntax")) self._listConf(self._availThemes, os.path.join(self.mainConf.dataPath, "themes")) - self.updateFont() - self.updateTheme() - self.updateSyntax() + self.loadTheme() + self.loadSyntax() # Icon Functions self.getIcon = self.iconCache.getIcon @@ -184,85 +181,26 @@ class GuiTheme: return int(ceil(qMetrics.boundingRect(theText).width())) ## - # Actions + # Theme Methods ## - def updateFont(self): - """Update the GUI's font style from settings. - """ - theFont = QFont() - if self.mainConf.guiFont not in self.guiFontDB.families(): - if self.mainConf.osWindows and "Arial" in self.guiFontDB.families(): - # On Windows we default to Arial if possible - theFont.setFamily("Arial") - theFont.setPointSize(10) - else: - theFont = self.guiFontDB.systemFont(QFontDatabase.GeneralFont) - self.mainConf.guiFont = theFont.family() - self.mainConf.guiFontSize = theFont.pointSize() - else: - theFont.setFamily(self.mainConf.guiFont) - theFont.setPointSize(self.mainConf.guiFontSize) - - qApp.setFont(theFont) - - return - - def updateTheme(self): - """Update the GUI theme from theme files. - """ - self.guiTheme = self.mainConf.guiTheme - self.guiSyntax = self.mainConf.guiSyntax - - self.themeFile = self._availThemes.get(self.guiTheme, None) - if self.themeFile is None: - logger.error("Could not find GUI theme '%s'", self.guiTheme) - else: - self.cssFile = self.themeFile[:-5]+".css" - self.loadTheme() - self.iconCache.updateTheme(self.themeIcons) - - # Update dependant colours - backCol = qApp.palette().window().color() - textCol = qApp.palette().windowText().color() - - backLCol = backCol.lightnessF() - textLCol = textCol.lightnessF() - - if backLCol > textLCol: - helpLCol = textLCol + 0.65*(backLCol - textLCol) - else: - helpLCol = backLCol + 0.65*(textLCol - backLCol) - - self.helpText = [int(255*helpLCol)]*3 - - return True - - def updateSyntax(self): - """Update the syntac theme from theme files. - """ - self.guiSyntax = self.mainConf.guiSyntax - - self.syntaxFile = self._availSyntax.get(self.guiSyntax, None) - if self.syntaxFile is None: - logger.error("Could not find syntax theme '%s'", self.guiSyntax) - else: - self.loadSyntax() - - return True - def loadTheme(self): """Load the currently specified GUI theme. """ - logger.info("Loading GUI theme '%s'", self.guiTheme) + guiTheme = self.mainConf.guiTheme + themeFile = self._availThemes.get(guiTheme, None) + if themeFile is None: + logger.error("Could not find GUI theme '%s'", guiTheme) + return False # Config File + logger.info("Loading GUI theme '%s'", guiTheme) confParser = NWConfigParser() try: - with open(self.themeFile, mode="r", encoding="utf-8") as inFile: + with open(themeFile, mode="r", encoding="utf-8") as inFile: confParser.read_file(inFile) except Exception: - logger.error("Could not load theme settings from: %s", self.themeFile) + logger.error("Could not load theme settings from: %s", themeFile) logException() return False @@ -301,31 +239,54 @@ class GuiTheme: # GUI cnfSec = "GUI" if confParser.has_section(cnfSec): - self.statNone = self._loadColour(confParser, cnfSec, "statusnone") - self.statUnsaved = self._loadColour(confParser, cnfSec, "statusunsaved") - self.statSaved = self._loadColour(confParser, cnfSec, "statussaved") + self.statNone = self._parseColour(confParser, cnfSec, "statusnone") + self.statUnsaved = self._parseColour(confParser, cnfSec, "statusunsaved") + self.statSaved = self._parseColour(confParser, cnfSec, "statussaved") + + # Icons + self.iconCache.updateTheme(self.themeIcons) # CSS File - cssData = readTextFile(self.cssFile) + cssData = readTextFile(themeFile[:-5]+".css") if cssData: qApp.setStyleSheet(cssData) # Apply Styles qApp.setPalette(self._guiPalette) + # Update Dependant Colours + backCol = qApp.palette().window().color() + textCol = qApp.palette().windowText().color() + + backLCol = backCol.lightnessF() + textLCol = textCol.lightnessF() + + if backLCol > textLCol: + helpLCol = textLCol + 0.65*(backLCol - textLCol) + else: + helpLCol = backLCol + 0.65*(textLCol - backLCol) + + self.helpText = [int(255*helpLCol)]*3 + return True def loadSyntax(self): """Load the currently specified syntax highlighter theme. """ - logger.info("Loading syntax theme '%s'", self.guiSyntax) + guiSyntax = self.mainConf.guiSyntax + syntaxFile = self._availSyntax.get(guiSyntax, None) + if syntaxFile is None: + logger.error("Could not find syntax theme '%s'", guiSyntax) + return False + + logger.info("Loading syntax theme '%s'", guiSyntax) confParser = NWConfigParser() try: - with open(self.syntaxFile, mode="r", encoding="utf-8") as inFile: + with open(syntaxFile, mode="r", encoding="utf-8") as inFile: confParser.read_file(inFile) except Exception: - logger.error("Could not load syntax colours from: %s", self.syntaxFile) + logger.error("Could not load syntax colours from: %s", syntaxFile) logException() return False @@ -333,32 +294,32 @@ class GuiTheme: cnfSec = "Main" if confParser.has_section(cnfSec): self.syntaxName = confParser.rdStr(cnfSec, "name", "") - self.syntaxDescription = confParser.rdStr(cnfSec, "description", "") - self.syntaxAuthor = confParser.rdStr(cnfSec, "author", "") - self.syntaxCredit = confParser.rdStr(cnfSec, "credit", "") + self.syntaxDescription = confParser.rdStr(cnfSec, "description", "N/A") + self.syntaxAuthor = confParser.rdStr(cnfSec, "author", "N/A") + self.syntaxCredit = confParser.rdStr(cnfSec, "credit", "N/A") self.syntaxUrl = confParser.rdStr(cnfSec, "url", "") - self.syntaxLicense = confParser.rdStr(cnfSec, "license", "") + self.syntaxLicense = confParser.rdStr(cnfSec, "license", "N/A") self.syntaxLicenseUrl = confParser.rdStr(cnfSec, "licenseurl", "") # Syntax cnfSec = "Syntax" if confParser.has_section(cnfSec): - self.colBack = self._loadColour(confParser, cnfSec, "background") - self.colText = self._loadColour(confParser, cnfSec, "text") - self.colLink = self._loadColour(confParser, cnfSec, "link") - self.colHead = self._loadColour(confParser, cnfSec, "headertext") - self.colHeadH = self._loadColour(confParser, cnfSec, "headertag") - self.colEmph = self._loadColour(confParser, cnfSec, "emphasis") - self.colDialN = self._loadColour(confParser, cnfSec, "straightquotes") - self.colDialD = self._loadColour(confParser, cnfSec, "doublequotes") - self.colDialS = self._loadColour(confParser, cnfSec, "singlequotes") - self.colHidden = self._loadColour(confParser, cnfSec, "hidden") - self.colKey = self._loadColour(confParser, cnfSec, "keyword") - self.colVal = self._loadColour(confParser, cnfSec, "value") - self.colSpell = self._loadColour(confParser, cnfSec, "spellcheckline") - self.colError = self._loadColour(confParser, cnfSec, "errorline") - self.colRepTag = self._loadColour(confParser, cnfSec, "replacetag") - self.colMod = self._loadColour(confParser, cnfSec, "modifier") + self.colBack = self._parseColour(confParser, cnfSec, "background") + self.colText = self._parseColour(confParser, cnfSec, "text") + self.colLink = self._parseColour(confParser, cnfSec, "link") + self.colHead = self._parseColour(confParser, cnfSec, "headertext") + self.colHeadH = self._parseColour(confParser, cnfSec, "headertag") + self.colEmph = self._parseColour(confParser, cnfSec, "emphasis") + self.colDialN = self._parseColour(confParser, cnfSec, "straightquotes") + self.colDialD = self._parseColour(confParser, cnfSec, "doublequotes") + self.colDialS = self._parseColour(confParser, cnfSec, "singlequotes") + self.colHidden = self._parseColour(confParser, cnfSec, "hidden") + self.colKey = self._parseColour(confParser, cnfSec, "keyword") + self.colVal = self._parseColour(confParser, cnfSec, "value") + self.colSpell = self._parseColour(confParser, cnfSec, "spellcheckline") + self.colError = self._parseColour(confParser, cnfSec, "errorline") + self.colRepTag = self._parseColour(confParser, cnfSec, "replacetag") + self.colMod = self._parseColour(confParser, cnfSec, "modifier") return True @@ -400,52 +361,64 @@ class GuiTheme: # Internal Functions ## + def _setGuiFont(self): + """Update the GUI's font style from settings. + """ + theFont = QFont() + if self.mainConf.guiFont not in self.guiFontDB.families(): + if self.mainConf.osWindows and "Arial" in self.guiFontDB.families(): + # On Windows we default to Arial if possible + theFont.setFamily("Arial") + theFont.setPointSize(10) + else: + theFont = self.guiFontDB.systemFont(QFontDatabase.GeneralFont) + self.mainConf.guiFont = theFont.family() + self.mainConf.guiFontSize = theFont.pointSize() + else: + theFont.setFamily(self.mainConf.guiFont) + theFont.setPointSize(self.mainConf.guiFontSize) + + qApp.setFont(theFont) + + return + def _listConf(self, targetDict, checkDir): - """Scan for syntax and gui themes and populate the dictionary. + """Scan for theme config files and populate the dictionary. """ if not os.path.isdir(checkDir): - return + return False for checkFile in os.listdir(checkDir): confPath = os.path.join(checkDir, checkFile) if os.path.isfile(confPath) and confPath.endswith(".conf"): targetDict[checkFile[:-5]] = confPath - return + return True - def _loadColour(self, confParser, cnfSec, cnfName): - """Load a colour value from a config string. + def _parseColour(self, confParser, cnfSec, cnfName): + """Parse a colour value from a config string. """ if confParser.has_option(cnfSec, cnfName): - inData = confParser.get(cnfSec, cnfName).split(",") - outData = [] + values = confParser.get(cnfSec, cnfName).split(",") + result = [] try: - outData.append(int(inData[0])) - outData.append(int(inData[1])) - outData.append(int(inData[2])) + result.append(minmax(int(values[0]), 0, 255)) + result.append(minmax(int(values[1]), 0, 255)) + result.append(minmax(int(values[2]), 0, 255)) except Exception: logger.error("Could not load theme colours for '%s' from config file", cnfName) - outData = [0, 0, 0] + result = [0, 0, 0] else: logger.warning("Could not find theme colours for '%s' in config file", cnfName) - outData = [0, 0, 0] - return outData + result = [0, 0, 0] + return result def _setPalette(self, confParser, cnfSec, cnfName, paletteVal): """Set a palette colour value from a config string. """ - readCol = [] - if confParser.has_option(cnfSec, cnfName): - inData = confParser.get(cnfSec, cnfName).split(",") - try: - readCol.append(int(inData[0])) - readCol.append(int(inData[1])) - readCol.append(int(inData[2])) - except Exception: - logger.error("Could not load theme colours for '%s' from config file", cnfName) - return - if len(readCol) == 3: - self._guiPalette.setColor(paletteVal, QColor(*readCol)) + self._guiPalette.setColor( + paletteVal, QColor(*self._parseColour(confParser, cnfSec, cnfName)) + ) return # End Class GuiTheme diff --git a/novelwriter/guimain.py b/novelwriter/guimain.py index 93cdbcc2..b1467695 100644 --- a/novelwriter/guimain.py +++ b/novelwriter/guimain.py @@ -918,7 +918,7 @@ class GuiMain(QMainWindow): self.projView.populateTree() if dlgConf.updateTheme: - self.mainTheme.updateTheme() + self.mainTheme.loadTheme() self.docEditor.updateTheme() self.docViewer.updateTheme() self.viewsBar.updateTheme() @@ -929,7 +929,7 @@ class GuiMain(QMainWindow): self.mainStatus.updateTheme() if dlgConf.updateSyntax: - self.mainTheme.updateSyntax() + self.mainTheme.loadSyntax() self.docEditor.updateSyntaxColours() self.docEditor.initEditor() From 28916afccec876e87d352101c459c9c28414860e Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Fri, 28 Oct 2022 00:00:57 +0200 Subject: [PATCH 16/36] Add test coverage of GUI theme --- novelwriter/gui/theme.py | 2 +- tests/test_gui/test_gui_theme.py | 272 +++++++++++++++++-------------- 2 files changed, 148 insertions(+), 126 deletions(-) diff --git a/novelwriter/gui/theme.py b/novelwriter/gui/theme.py index 68e2f716..da03b291 100644 --- a/novelwriter/gui/theme.py +++ b/novelwriter/gui/theme.py @@ -247,7 +247,7 @@ class GuiTheme: self.iconCache.updateTheme(self.themeIcons) # CSS File - cssData = readTextFile(themeFile[:-5]+".css") + cssData = readTextFile(themeFile[:-5]+".qss") if cssData: qApp.setStyleSheet(cssData) diff --git a/tests/test_gui/test_gui_theme.py b/tests/test_gui/test_gui_theme.py index 062c1b87..6cd82e64 100644 --- a/tests/test_gui/test_gui_theme.py +++ b/tests/test_gui/test_gui_theme.py @@ -19,157 +19,179 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . """ +import os +import shutil import pytest -import novelwriter -from PyQt5.QtGui import QColor, QPixmap, QIcon -from PyQt5.QtWidgets import QMessageBox +from configparser import ConfigParser -keyDelay = 2 -typeDelay = 1 -stepDelay = 20 +from mock import causeOSError +from tools import writeFile + +from PyQt5.QtGui import QPalette +from PyQt5.QtWidgets import QApplication, QMessageBox + +from novelwriter.config import Config +from novelwriter.gui.theme import GuiTheme @pytest.mark.gui -def testGuiTheme_Main(qtbot, monkeypatch, nwMinimal, tmpDir): - """Test the theme and icon classes. +def testGuiTheme_Main(qtbot, monkeypatch, nwGUI, fncDir): + """Test the theme class init. """ - # Block message box monkeypatch.setattr(QMessageBox, "information", lambda *a: QMessageBox.Yes) monkeypatch.setattr(QMessageBox, "question", lambda *a: QMessageBox.Yes) monkeypatch.setattr(QMessageBox, "warning", lambda *a: QMessageBox.Yes) - nwGUI = novelwriter.main( - ["--testmode", "--config=%s" % nwMinimal, "--data=%s" % tmpDir, nwMinimal] - ) - qtbot.addWidget(nwGUI) - nwGUI.show() - qtbot.wait(stepDelay) + mainTheme: GuiTheme = nwGUI.mainTheme + mainConf: Config = nwGUI.mainConf - # Change Settings - assert novelwriter.CONFIG.confPath == nwMinimal - novelwriter.CONFIG.guiTheme = "default_dark" - novelwriter.CONFIG.guiSyntax = "tomorrow_night_eighties" - novelwriter.CONFIG.guiFont = "Cantarell" - novelwriter.CONFIG.guiFontSize = 11 - novelwriter.CONFIG.confChanged = True - assert novelwriter.CONFIG.saveConfig() + # Methods + # ======= - nwGUI.closeMain() - nwGUI.close() - del nwGUI + mSize = mainTheme.getTextWidth("m") + assert mSize > 0 + assert mainTheme.getTextWidth("m", mainTheme.guiFont) == mSize - # Re-open - assert novelwriter.CONFIG.confPath == nwMinimal - nwGUI = novelwriter.main( - ["--testmode", "--config=%s" % nwMinimal, "--data=%s" % tmpDir, nwMinimal] - ) - assert nwGUI.mainConf.confPath == nwMinimal - qtbot.addWidget(nwGUI) - nwGUI.show() - qtbot.wait(stepDelay) + # Init Fonts + # ========== - assert novelwriter.CONFIG.guiTheme == "default_dark" - assert novelwriter.CONFIG.guiSyntax == "tomorrow_night_eighties" - assert novelwriter.CONFIG.guiFont != "" - assert novelwriter.CONFIG.guiFontSize > 0 + # The defaults should be set + defaultFont = mainConf.guiFont + defaultSize = mainConf.guiFontSize - # Check GUI Colours - thePalette = nwGUI.palette() - assert thePalette.window().color() == QColor(54, 54, 54) - assert thePalette.windowText().color() == QColor(174, 174, 174) - assert thePalette.base().color() == QColor(62, 62, 62) - assert thePalette.alternateBase().color() == QColor(78, 78, 78) - assert thePalette.text().color() == QColor(174, 174, 174) - assert thePalette.toolTipBase().color() == QColor(255, 255, 192) - assert thePalette.toolTipText().color() == QColor(21, 21, 13) - assert thePalette.button().color() == QColor(62, 62, 62) - assert thePalette.buttonText().color() == QColor(174, 174, 174) - assert thePalette.brightText().color() == QColor(174, 174, 174) - assert thePalette.highlight().color() == QColor(44, 152, 247) - assert thePalette.highlightedText().color() == QColor(255, 255, 255) - assert thePalette.link().color() == QColor(44, 152, 247) - assert thePalette.linkVisited().color() == QColor(44, 152, 247) + # CHange them to nonsense values + mainConf.guiFont = "notafont" + mainConf.guiFontSize = 99 - assert nwGUI.mainTheme.statNone == [150, 152, 150] - assert nwGUI.mainTheme.statSaved == [39, 135, 78] - assert nwGUI.mainTheme.statUnsaved == [138, 32, 32] + # Let the theme class set them back to default + mainTheme._setGuiFont() + assert mainConf.guiFont == defaultFont + assert mainConf.guiFontSize == defaultSize - # Check Syntax Colours - assert nwGUI.mainTheme.colBack == [45, 45, 45] - assert nwGUI.mainTheme.colText == [204, 204, 204] - assert nwGUI.mainTheme.colLink == [102, 153, 204] - assert nwGUI.mainTheme.colHead == [102, 153, 204] - assert nwGUI.mainTheme.colHeadH == [102, 153, 204] - assert nwGUI.mainTheme.colEmph == [249, 145, 57] - assert nwGUI.mainTheme.colDialN == [242, 119, 122] - assert nwGUI.mainTheme.colDialD == [153, 204, 153] - assert nwGUI.mainTheme.colDialS == [255, 204, 102] - assert nwGUI.mainTheme.colHidden == [153, 153, 153] - assert nwGUI.mainTheme.colKey == [242, 119, 122] - assert nwGUI.mainTheme.colVal == [204, 153, 204] - assert nwGUI.mainTheme.colSpell == [242, 119, 122] - assert nwGUI.mainTheme.colError == [153, 204, 153] - assert nwGUI.mainTheme.colRepTag == [102, 204, 204] - assert nwGUI.mainTheme.colMod == [249, 145, 57] + # A second call should just restore the defaults again + mainTheme._setGuiFont() + assert mainConf.guiFont == defaultFont + assert mainConf.guiFontSize == defaultSize - # Test Icon class - iconCache = nwGUI.mainTheme.iconCache - assert iconCache.updateTheme("invalid") is False + # Scan for Themes + # =============== - # Ask for a non-existent key - anImg = iconCache.loadDecoration("nonsense", 20, 20) - assert isinstance(anImg, QPixmap) - assert anImg.isNull() + assert mainTheme._listConf({}, "not_a_path") is False - # Add a non-existent file and request it - iconCache.IMAGE_MAP["nonsense"] = "nofile.jpg" - anImg = iconCache.loadDecoration("nonsense", 20, 20) - assert isinstance(anImg, QPixmap) - assert anImg.isNull() + themeOne = os.path.join(fncDir, "themes", "themeone.conf") + themeTwo = os.path.join(fncDir, "themes", "themetwo.conf") + writeFile(themeOne, "# Stuff") + writeFile(themeTwo, "# Stuff") - # Get a real image, with different size parameters - anImg = iconCache.loadDecoration("wiz-back", 20, None) - assert isinstance(anImg, QPixmap) - assert not anImg.isNull() - assert anImg.width() == 20 - assert anImg.height() >= 56 + result = {} + assert mainTheme._listConf(result, os.path.join(fncDir, "themes")) is True + assert result["themeone"] == themeOne + assert result["themetwo"] == themeTwo - anImg = iconCache.loadDecoration("wiz-back", None, 70) - assert isinstance(anImg, QPixmap) - assert not anImg.isNull() - assert anImg.height() == 70 - assert anImg.width() >= 24 + # Parse Colours + # ============= - anImg = iconCache.loadDecoration("wiz-back", 30, 70) - assert isinstance(anImg, QPixmap) - assert not anImg.isNull() - assert anImg.height() == 70 - assert anImg.width() == 30 + parser = ConfigParser() + parser["Palette"] = { + "colour1": "100, 150, 200", + "colour2": "100, 150, 200, 250", + "colour3": "250, 250", + "colour4": "-10, 127, 300", + } - anImg = iconCache.loadDecoration("wiz-back", None, None) - assert isinstance(anImg, QPixmap) - assert not anImg.isNull() - assert anImg.height() >= 1500 - assert anImg.width() >= 500 + # Test the parser for several valid and invalid values + assert mainTheme._parseColour(parser, "Palette", "colour1") == [100, 150, 200] + assert mainTheme._parseColour(parser, "Palette", "colour2") == [100, 150, 200] + assert mainTheme._parseColour(parser, "Palette", "colour3") == [0, 0, 0] + assert mainTheme._parseColour(parser, "Palette", "colour4") == [0, 127, 255] + assert mainTheme._parseColour(parser, "Palette", "colour5") == [0, 0, 0] - # Load icons - anIcon = iconCache.getIcon("nonsense") - assert isinstance(anIcon, QIcon) - assert anIcon.isNull() + # The palette should load with the parsed values + mainTheme._setPalette(parser, "Palette", "colour1", QPalette.Window) + assert mainTheme._guiPalette.color(QPalette.Window).getRgb() == (100, 150, 200, 255) + mainTheme._setPalette(parser, "Palette", "colour2", QPalette.Window) + assert mainTheme._guiPalette.color(QPalette.Window).getRgb() == (100, 150, 200, 255) + mainTheme._setPalette(parser, "Palette", "colour3", QPalette.Window) + assert mainTheme._guiPalette.color(QPalette.Window).getRgb() == (0, 0, 0, 255) + mainTheme._setPalette(parser, "Palette", "colour4", QPalette.Window) + assert mainTheme._guiPalette.color(QPalette.Window).getRgb() == (0, 127, 255, 255) + mainTheme._setPalette(parser, "Palette", "colour5", QPalette.Window) + assert mainTheme._guiPalette.color(QPalette.Window).getRgb() == (0, 0, 0, 255) - anIcon = iconCache.getIcon("novelwriter") - assert isinstance(anIcon, QIcon) - assert not anIcon.isNull() - - # Check return empty icon if file not found - iconCache.ICON_KEYS.add("testicon3") - anIcon = iconCache.getIcon("testicon3") - assert isinstance(anIcon, QIcon) - assert anIcon.isNull() - - # qtbot.stopForInteraction() - nwGUI.closeMain() - nwGUI.close() + # qtbot.stop() # END Test testGuiTheme_Main + + +@pytest.mark.gui +def testGuiTheme_Themes(qtbot, monkeypatch, nwGUI, fncDir): + """Test the theme class init. + """ + monkeypatch.setattr(QMessageBox, "information", lambda *a: QMessageBox.Yes) + monkeypatch.setattr(QMessageBox, "question", lambda *a: QMessageBox.Yes) + monkeypatch.setattr(QMessageBox, "warning", lambda *a: QMessageBox.Yes) + + mainTheme: GuiTheme = nwGUI.mainTheme + mainConf: Config = nwGUI.mainConf + + # List Themes + # =========== + + shutil.copy( + os.path.join(mainConf.assetPath, "themes", "default_dark.conf"), + os.path.join(fncDir, "themes") + ) + shutil.copy( + os.path.join(mainConf.assetPath, "themes", "default.conf"), + os.path.join(fncDir, "themes") + ) + writeFile(os.path.join(fncDir, "themes", "default.qss"), "/* Stuff */") + + # Load the theme info + themesList = mainTheme.listThemes() + assert themesList[0] == ("default_dark", "Default Dark Theme") + assert themesList[1] == ("default", "Default Theme") + + # A second call should returned the cached list + assert mainTheme.listThemes() == mainTheme._themeList + + # Check handling of broken theme settings + mainConf.guiTheme = "not_a_theme" + assert mainTheme.loadTheme() is False + + # Check handling of unreadable file + mainConf.guiTheme = "default" + with monkeypatch.context() as mp: + mp.setattr("builtins.open", causeOSError) + assert mainTheme.loadTheme() is False + + # Load Default Theme + # ================== + + # Set a mock colour for the window background + mainTheme._guiPalette.color(QPalette.Window).setRgb(0, 0, 0, 0) + + # Load the default theme + mainConf.guiTheme = "default" + assert mainTheme.loadTheme() is True + + # This should load a standard palette + wCol = QApplication.style().standardPalette().color(QPalette.Window).getRgb() + assert mainTheme._guiPalette.color(QPalette.Window).getRgb() == wCol + + # Load Default Dark Theme + # ======================= + + mainConf.guiTheme = "default_dark" + assert mainTheme.loadTheme() is True + + # Check a few values + assert mainTheme._guiPalette.color(QPalette.Window).getRgb() == (54, 54, 54, 255) + assert mainTheme._guiPalette.color(QPalette.WindowText).getRgb() == (174, 174, 174, 255) + assert mainTheme._guiPalette.color(QPalette.Base).getRgb() == (62, 62, 62, 255) + assert mainTheme._guiPalette.color(QPalette.AlternateBase).getRgb() == (78, 78, 78, 255) + + # qtbot.stop() + +# END Test testGuiTheme_Themes From 42f5ea205f43ee996c13dc15002414359159a638 Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Fri, 28 Oct 2022 22:15:54 +0200 Subject: [PATCH 17/36] Clean up GUI tests --- tests/conftest.py | 6 +- tests/test_dialogs/test_dlg_about.py | 7 +- tests/test_dialogs/test_dlg_dialogs.py | 14 +- tests/test_dialogs/test_dlg_docmerge.py | 7 +- tests/test_dialogs/test_dlg_docsplit.py | 5 - tests/test_dialogs/test_dlg_preferences.py | 63 +++-- tests/test_dialogs/test_dlg_projdetails.py | 17 +- tests/test_dialogs/test_dlg_projload.py | 23 +- tests/test_dialogs/test_dlg_projsettings.py | 46 ++-- tests/test_dialogs/test_dlg_wordlist.py | 14 +- tests/test_gui/test_gui_doceditor.py | 106 ++------ tests/test_gui/test_gui_docviewer.py | 12 +- tests/test_gui/test_gui_guimain.py | 262 +++++++++----------- tests/test_gui/test_gui_mainmenu.py | 64 +---- tests/test_gui/test_gui_noveltree.py | 5 +- tests/test_gui/test_gui_outline.py | 12 +- tests/test_gui/test_gui_projtree.py | 55 +--- tests/test_gui/test_gui_statusbar.py | 6 +- tests/test_gui/test_gui_theme.py | 12 +- tests/test_tools/test_tools_build.py | 28 +-- tests/test_tools/test_tools_lipsum.py | 9 +- tests/test_tools/test_tools_projwizard.py | 34 +-- tests/test_tools/test_tools_writingstats.py | 28 +-- 23 files changed, 240 insertions(+), 595 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index 40725923..12842add 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -162,7 +162,11 @@ def mockGUI(monkeypatch, tmpConf): def nwGUI(qtbot, monkeypatch, fncDir, fncConf): """Create an instance of the novelWriter GUI. """ - monkeypatch.setattr(QMessageBox, "warning", lambda *a: QMessageBox.Yes) + monkeypatch.setattr(QMessageBox, "warning", lambda *a: QMessageBox.Ok) + monkeypatch.setattr(QMessageBox, "critical", lambda *a: QMessageBox.Ok) + monkeypatch.setattr(QMessageBox, "information", lambda *a: QMessageBox.Ok) + monkeypatch.setattr(QMessageBox, "question", lambda *a: QMessageBox.Yes) + monkeypatch.setattr("novelwriter.CONFIG", fncConf) nwGUI = novelwriter.main(["--testmode", "--config=%s" % fncDir, "--data=%s" % fncDir]) qtbot.addWidget(nwGUI) diff --git a/tests/test_dialogs/test_dlg_about.py b/tests/test_dialogs/test_dlg_about.py index 80a6ca06..63de9039 100644 --- a/tests/test_dialogs/test_dlg_about.py +++ b/tests/test_dialogs/test_dlg_about.py @@ -29,12 +29,9 @@ from novelwriter.dialogs.about import GuiAbout @pytest.mark.gui -def testDlgAbout_NWDialog(qtbot, monkeypatch, nwGUI): +def testDlgAbout_NWDialog(qtbot, nwGUI): """Test the novelWriter about dialogs. """ - # Block message box - monkeypatch.setattr(QMessageBox, "question", lambda *a: QMessageBox.Yes) - # NW About nwGUI.mainTheme.themeName = "A Theme" nwGUI.mainTheme.themeAuthor = "An Author" @@ -74,8 +71,6 @@ def testDlgAbout_NWDialog(qtbot, monkeypatch, nwGUI): def testDlgAbout_QtDialog(monkeypatch, nwGUI): """Test the Qt about dialogs. """ - # Block message box - monkeypatch.setattr(QMessageBox, "question", lambda *a: QMessageBox.Yes) monkeypatch.setattr(QMessageBox, "aboutQt", lambda *a, **k: None) # Open About diff --git a/tests/test_dialogs/test_dlg_dialogs.py b/tests/test_dialogs/test_dlg_dialogs.py index 9afcf410..772554ea 100644 --- a/tests/test_dialogs/test_dlg_dialogs.py +++ b/tests/test_dialogs/test_dlg_dialogs.py @@ -22,7 +22,7 @@ along with this program. If not, see . import pytest from PyQt5.QtCore import QItemSelectionModel -from PyQt5.QtWidgets import QAction, QListWidgetItem, QDialog, QMessageBox +from PyQt5.QtWidgets import QAction, QListWidgetItem, QDialog from novelwriter.dialogs.quotes import GuiQuoteSelect from novelwriter.dialogs.updates import GuiUpdates @@ -30,12 +30,9 @@ from novelwriter.dialogs.editlabel import GuiEditLabel @pytest.mark.gui -def testDlgOther_QuoteSelect(qtbot, monkeypatch, nwGUI): +def testDlgOther_QuoteSelect(qtbot, nwGUI): """Test the quote symbols dialog. """ - # Block message box - monkeypatch.setattr(QMessageBox, "question", lambda *a: QMessageBox.Yes) - nwQuot = GuiQuoteSelect(nwGUI) nwQuot.show() @@ -52,7 +49,7 @@ def testDlgOther_QuoteSelect(qtbot, monkeypatch, nwGUI): assert nwQuot.result() == QDialog.Accepted assert nwQuot.selectedQuote == lastItem - # qtbot.stopForInteraction() + # qtbot.stop() nwQuot._doReject() nwQuot.close() @@ -63,9 +60,6 @@ def testDlgOther_QuoteSelect(qtbot, monkeypatch, nwGUI): def testDlgOther_Updates(qtbot, monkeypatch, nwGUI): """Test the check for updates dialog. """ - # Block message box - monkeypatch.setattr(QMessageBox, "question", lambda *a: QMessageBox.Yes) - nwUpdate = GuiUpdates(nwGUI) nwUpdate.show() @@ -95,7 +89,7 @@ def testDlgOther_Updates(qtbot, monkeypatch, nwGUI): # Trigger from Menu nwGUI.mainMenu.aUpdates.activate(QAction.Trigger) - # qtbot.stopForInteraction() + # qtbot.stop() nwUpdate._doClose() # END Test testDlgOther_Updates diff --git a/tests/test_dialogs/test_dlg_docmerge.py b/tests/test_dialogs/test_dlg_docmerge.py index 4b86fa78..1cf4768c 100644 --- a/tests/test_dialogs/test_dlg_docmerge.py +++ b/tests/test_dialogs/test_dlg_docmerge.py @@ -24,19 +24,14 @@ import pytest from tools import buildTestProject, C from PyQt5.QtCore import Qt -from PyQt5.QtWidgets import QMessageBox from novelwriter.dialogs.docmerge import GuiDocMerge @pytest.mark.gui -def testDlgMerge_Main(qtbot, monkeypatch, nwGUI, fncProj, mockRnd): +def testDlgMerge_Main(qtbot, nwGUI, fncProj, mockRnd): """Test the merge documents tool. """ - # Block message box - monkeypatch.setattr(QMessageBox, "question", lambda *a: QMessageBox.Yes) - monkeypatch.setattr(QMessageBox, "critical", lambda *a: QMessageBox.Ok) - # Create a new project buildTestProject(nwGUI, fncProj) diff --git a/tests/test_dialogs/test_dlg_docsplit.py b/tests/test_dialogs/test_dlg_docsplit.py index 4689f76d..05092c67 100644 --- a/tests/test_dialogs/test_dlg_docsplit.py +++ b/tests/test_dialogs/test_dlg_docsplit.py @@ -23,8 +23,6 @@ import pytest from tools import C, buildTestProject -from PyQt5.QtWidgets import QMessageBox - from novelwriter.dialogs.docsplit import GuiDocSplit from novelwriter.dialogs.editlabel import GuiEditLabel @@ -33,9 +31,6 @@ from novelwriter.dialogs.editlabel import GuiEditLabel def testDlgSplit_Main(qtbot, monkeypatch, nwGUI, fncProj, mockRnd): """Test the split document tool. """ - # Block message box - monkeypatch.setattr(QMessageBox, "question", lambda *a: QMessageBox.Yes) - monkeypatch.setattr(QMessageBox, "critical", lambda *a: QMessageBox.Ok) monkeypatch.setattr(GuiEditLabel, "getLabel", lambda *a, text: (text, True)) # Create a new project diff --git a/tests/test_dialogs/test_dlg_preferences.py b/tests/test_dialogs/test_dlg_preferences.py index 22461751..4b077699 100644 --- a/tests/test_dialogs/test_dlg_preferences.py +++ b/tests/test_dialogs/test_dlg_preferences.py @@ -35,9 +35,7 @@ from novelwriter.config import Config from novelwriter.dialogs.quotes import GuiQuoteSelect from novelwriter.dialogs.preferences import GuiPreferences -keyDelay = 2 -typeDelay = 1 -stepDelay = 20 +KEY_DELAY = 1 @pytest.mark.gui @@ -63,7 +61,6 @@ def testDlgPreferences_Main(qtbot, monkeypatch, fncDir, outDir, refDir): nwGUI = novelwriter.main(["--testmode", "--config=%s" % fncDir, "--data=%s" % fncDir]) qtbot.addWidget(nwGUI) nwGUI.show() - qtbot.wait(stepDelay) theConf = nwGUI.mainConf assert theConf.confPath == fncDir @@ -81,21 +78,21 @@ def testDlgPreferences_Main(qtbot, monkeypatch, fncDir, outDir, refDir): assert nwPrefs.mainConf.confPath == fncDir # General Settings - qtbot.wait(keyDelay) + qtbot.wait(KEY_DELAY) tabGeneral = nwPrefs.tabGeneral nwPrefs._tabBox.setCurrentWidget(tabGeneral) - qtbot.wait(keyDelay) + qtbot.wait(KEY_DELAY) assert tabGeneral.showFullPath.isChecked() qtbot.mouseClick(tabGeneral.showFullPath, Qt.LeftButton) assert not tabGeneral.showFullPath.isChecked() - qtbot.wait(keyDelay) + qtbot.wait(KEY_DELAY) assert not tabGeneral.hideVScroll.isChecked() qtbot.mouseClick(tabGeneral.hideVScroll, Qt.LeftButton) assert tabGeneral.hideVScroll.isChecked() - qtbot.wait(keyDelay) + qtbot.wait(KEY_DELAY) assert not tabGeneral.hideHScroll.isChecked() qtbot.mouseClick(tabGeneral.hideHScroll, Qt.LeftButton) assert tabGeneral.hideHScroll.isChecked() @@ -104,21 +101,21 @@ def testDlgPreferences_Main(qtbot, monkeypatch, fncDir, outDir, refDir): monkeypatch.setattr(QFontDialog, "getFont", lambda font, obj: (font, True)) qtbot.mouseClick(tabGeneral.fontButton, Qt.LeftButton) - qtbot.wait(keyDelay) + qtbot.wait(KEY_DELAY) tabGeneral.guiFontSize.setValue(12) # Projects Settings - qtbot.wait(keyDelay) + qtbot.wait(KEY_DELAY) tabProjects = nwPrefs.tabProjects nwPrefs._tabBox.setCurrentWidget(tabProjects) tabProjects.backupPath = "no/where" - qtbot.wait(keyDelay) + qtbot.wait(KEY_DELAY) assert not tabProjects.backupOnClose.isChecked() qtbot.mouseClick(tabProjects.backupOnClose, Qt.LeftButton) assert tabProjects.backupOnClose.isChecked() - # qtbot.stopForInteraction() + # qtbot.stop() # Check Browse button monkeypatch.setattr(QFileDialog, "getExistingDirectory", lambda *a, **k: "") @@ -126,99 +123,99 @@ def testDlgPreferences_Main(qtbot, monkeypatch, fncDir, outDir, refDir): monkeypatch.setattr(QFileDialog, "getExistingDirectory", lambda *a, **k: "some/dir") qtbot.mouseClick(tabProjects.backupGetPath, Qt.LeftButton) - qtbot.wait(keyDelay) + qtbot.wait(KEY_DELAY) tabProjects.autoSaveDoc.setValue(20) tabProjects.autoSaveProj.setValue(40) # Document Settings - qtbot.wait(keyDelay) + qtbot.wait(KEY_DELAY) tabDocs = nwPrefs.tabDocs nwPrefs._tabBox.setCurrentWidget(tabDocs) - qtbot.wait(keyDelay) + qtbot.wait(KEY_DELAY) qtbot.mouseClick(tabDocs.fontButton, Qt.LeftButton) - qtbot.wait(keyDelay) + qtbot.wait(KEY_DELAY) tabDocs.textSize.setValue(13) tabDocs.textWidth.setValue(700) tabDocs.focusWidth.setValue(900) tabDocs.textMargin.setValue(45) tabDocs.tabWidth.setValue(45) - qtbot.wait(keyDelay) + qtbot.wait(KEY_DELAY) assert not tabDocs.hideFocusFooter.isChecked() qtbot.mouseClick(tabDocs.hideFocusFooter, Qt.LeftButton) assert tabDocs.hideFocusFooter.isChecked() - qtbot.wait(keyDelay) + qtbot.wait(KEY_DELAY) assert not tabDocs.doJustify.isChecked() qtbot.mouseClick(tabDocs.doJustify, Qt.LeftButton) assert tabDocs.doJustify.isChecked() # Editor Settings - qtbot.wait(keyDelay) + qtbot.wait(KEY_DELAY) tabEditor = nwPrefs.tabEditor nwPrefs._tabBox.setCurrentWidget(tabEditor) - qtbot.wait(keyDelay) + qtbot.wait(KEY_DELAY) assert not tabEditor.showTabsNSpaces.isChecked() qtbot.mouseClick(tabEditor.showTabsNSpaces, Qt.LeftButton) assert tabEditor.showTabsNSpaces.isChecked() - qtbot.wait(keyDelay) + qtbot.wait(KEY_DELAY) assert not tabEditor.showLineEndings.isChecked() qtbot.mouseClick(tabEditor.showLineEndings, Qt.LeftButton) assert tabEditor.showLineEndings.isChecked() - qtbot.wait(keyDelay) + qtbot.wait(KEY_DELAY) assert not tabEditor.autoScroll.isChecked() qtbot.mouseClick(tabEditor.autoScroll, Qt.LeftButton) assert tabEditor.autoScroll.isChecked() - qtbot.wait(keyDelay) + qtbot.wait(KEY_DELAY) tabEditor.scrollPastEnd.setValue(0) - qtbot.wait(keyDelay) + qtbot.wait(KEY_DELAY) tabEditor.bigDocLimit.setValue(500) # Syntax Settings - qtbot.wait(keyDelay) + qtbot.wait(KEY_DELAY) tabSyntax = nwPrefs.tabSyntax nwPrefs._tabBox.setCurrentWidget(tabSyntax) - qtbot.wait(keyDelay) + qtbot.wait(KEY_DELAY) assert tabSyntax.highlightQuotes.isChecked() qtbot.mouseClick(tabSyntax.highlightQuotes, Qt.LeftButton) assert not tabSyntax.highlightQuotes.isChecked() - qtbot.wait(keyDelay) + qtbot.wait(KEY_DELAY) assert tabSyntax.highlightEmph.isChecked() qtbot.mouseClick(tabSyntax.highlightEmph, Qt.LeftButton) assert not tabSyntax.highlightEmph.isChecked() # Automation Settings - qtbot.wait(keyDelay) + qtbot.wait(KEY_DELAY) tabAuto = nwPrefs.tabAuto nwPrefs._tabBox.setCurrentWidget(tabAuto) - qtbot.wait(keyDelay) + qtbot.wait(KEY_DELAY) assert tabAuto.autoSelect.isChecked() qtbot.mouseClick(tabAuto.autoSelect, Qt.LeftButton) assert not tabAuto.autoSelect.isChecked() - qtbot.wait(keyDelay) + qtbot.wait(KEY_DELAY) assert tabAuto.doReplace.isChecked() qtbot.mouseClick(tabAuto.doReplace, Qt.LeftButton) assert not tabAuto.doReplace.isChecked() - qtbot.wait(keyDelay) + qtbot.wait(KEY_DELAY) assert not tabAuto.doReplaceSQuote.isEnabled() assert not tabAuto.doReplaceDQuote.isEnabled() assert not tabAuto.doReplaceDash.isEnabled() assert not tabAuto.doReplaceDots.isEnabled() # Quotation Style - qtbot.wait(keyDelay) + qtbot.wait(KEY_DELAY) tabQuote = nwPrefs.tabQuote nwPrefs._tabBox.setCurrentWidget(tabQuote) @@ -249,6 +246,6 @@ def testDlgPreferences_Main(qtbot, monkeypatch, fncDir, outDir, refDir): novelwriter.CONFIG = origConf nwGUI.closeMain() - # qtbot.stopForInteraction() + # qtbot.stop() # END Test testDlgPreferences_Main diff --git a/tests/test_dialogs/test_dlg_projdetails.py b/tests/test_dialogs/test_dlg_projdetails.py index b2952ad6..84360d11 100644 --- a/tests/test_dialogs/test_dlg_projdetails.py +++ b/tests/test_dialogs/test_dlg_projdetails.py @@ -23,25 +23,15 @@ import pytest from tools import getGuiItem -from PyQt5.QtWidgets import QAction, QMessageBox +from PyQt5.QtWidgets import QAction from novelwriter.dialogs.projdetails import GuiProjectDetails -keyDelay = 2 -typeDelay = 1 -stepDelay = 20 - @pytest.mark.gui -def testDlgProjDetails_Dialog(qtbot, monkeypatch, nwGUI, nwLipsum): +def testDlgProjDetails_Dialog(qtbot, nwGUI, nwLipsum): """Test the project details dialog. """ - # Block message box - monkeypatch.setattr(QMessageBox, "question", lambda *a: QMessageBox.Yes) - monkeypatch.setattr(QMessageBox, "information", lambda *a: QMessageBox.Yes) - monkeypatch.setattr(QMessageBox, "warning", lambda *a: QMessageBox.Yes) - monkeypatch.setattr(QMessageBox, "critical", lambda *a: QMessageBox.Yes) - # Create a project to work on assert nwGUI.openProject(nwLipsum) assert nwGUI.rebuildIndex(beQuiet=True) @@ -54,7 +44,6 @@ def testDlgProjDetails_Dialog(qtbot, monkeypatch, nwGUI, nwLipsum): projDet = getGuiItem("GuiProjectDetails") assert isinstance(projDet, GuiProjectDetails) - qtbot.wait(stepDelay) # Overview Page # ============= @@ -105,7 +94,7 @@ def testDlgProjDetails_Dialog(qtbot, monkeypatch, nwGUI, nwLipsum): assert tocTree.topLevelItem(i).text(tocTab.C_PAGES) == thePages[i] assert tocTree.topLevelItem(i).text(tocTab.C_PAGE) == thePage[i] - # qtbot.stopForInteraction() + # qtbot.stop() # Clean Up projDet._doClose() diff --git a/tests/test_dialogs/test_dlg_projload.py b/tests/test_dialogs/test_dlg_projload.py index 5916d185..6e81ffe2 100644 --- a/tests/test_dialogs/test_dlg_projload.py +++ b/tests/test_dialogs/test_dlg_projload.py @@ -26,29 +26,19 @@ from tools import getGuiItem from PyQt5.QtCore import Qt from PyQt5.QtWidgets import ( - QDialogButtonBox, QTreeWidgetItem, QDialog, QAction, QFileDialog, - QMessageBox + QDialogButtonBox, QTreeWidgetItem, QDialog, QAction, QFileDialog ) from novelwriter.dialogs.projload import GuiProjectLoad -keyDelay = 2 -typeDelay = 1 -stepDelay = 20 - @pytest.mark.gui def testDlgLoadProject_Main(qtbot, monkeypatch, nwGUI, nwMinimal): """Test the load project wizard. """ - # Block message box - monkeypatch.setattr(QMessageBox, "question", lambda *a: QMessageBox.Yes) - monkeypatch.setattr(QMessageBox, "information", lambda *a: QMessageBox.Yes) - assert nwGUI.openProject(nwMinimal) assert nwGUI.closeProject() - qtbot.wait(stepDelay) monkeypatch.setattr(GuiProjectLoad, "exec_", lambda *a: None) monkeypatch.setattr(GuiProjectLoad, "result", lambda *a: QDialog.Accepted) nwGUI.mainMenu.aOpenProject.activate(QAction.Trigger) @@ -58,22 +48,18 @@ def testDlgLoadProject_Main(qtbot, monkeypatch, nwGUI, nwMinimal): assert isinstance(nwLoad, GuiProjectLoad) nwLoad.show() - qtbot.wait(stepDelay) recentCount = nwLoad.listBox.topLevelItemCount() assert recentCount > 0 - qtbot.wait(stepDelay) selItem = nwLoad.listBox.topLevelItem(0) selPath = selItem.data(nwLoad.C_NAME, Qt.UserRole) assert isinstance(selItem, QTreeWidgetItem) - qtbot.wait(stepDelay) nwLoad.selPath.setText("") nwLoad.listBox.setCurrentItem(selItem) nwLoad._doSelectRecent() assert nwLoad.selPath.text() == selPath - qtbot.wait(stepDelay) qtbot.mouseClick(nwLoad.buttonBox.button(QDialogButtonBox.Open), Qt.LeftButton) assert nwLoad.openPath == selPath assert nwLoad.openState == nwLoad.OPEN_STATE @@ -81,27 +67,22 @@ def testDlgLoadProject_Main(qtbot, monkeypatch, nwGUI, nwMinimal): # Just create a new project load from scratch for the rest of the test del nwLoad - qtbot.wait(stepDelay) nwGUI.mainMenu.aOpenProject.activate(QAction.Trigger) qtbot.waitUntil(lambda: getGuiItem("GuiProjectLoad") is not None, timeout=1000) - qtbot.wait(stepDelay) nwLoad = getGuiItem("GuiProjectLoad") assert isinstance(nwLoad, GuiProjectLoad) nwLoad.show() - qtbot.wait(stepDelay) qtbot.mouseClick(nwLoad.buttonBox.button(QDialogButtonBox.Cancel), Qt.LeftButton) assert nwLoad.openPath is None assert nwLoad.openState == nwLoad.NONE_STATE - qtbot.wait(stepDelay) nwLoad.show() qtbot.mouseClick(nwLoad.newButton, Qt.LeftButton) assert nwLoad.openPath is None assert nwLoad.openState == nwLoad.NEW_STATE - qtbot.wait(stepDelay) nwLoad.show() nwLoad._doDeleteRecent() assert nwLoad.listBox.topLevelItemCount() == recentCount - 1 @@ -113,6 +94,6 @@ def testDlgLoadProject_Main(qtbot, monkeypatch, nwGUI, nwMinimal): assert nwLoad.openState == nwLoad.OPEN_STATE nwLoad.close() - # qtbot.stopForInteraction() + # qtbot.stop() # END Test testDlgLoadProject_Main diff --git a/tests/test_dialogs/test_dlg_projsettings.py b/tests/test_dialogs/test_dlg_projsettings.py index 48e896bf..bb38f317 100644 --- a/tests/test_dialogs/test_dlg_projsettings.py +++ b/tests/test_dialogs/test_dlg_projsettings.py @@ -26,14 +26,12 @@ 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 PyQt5.QtWidgets import QDialog, QAction, QColorDialog from novelwriter.dialogs.editlabel import GuiEditLabel from novelwriter.dialogs.projsettings import GuiProjectSettings -keyDelay = 2 -typeDelay = 1 -stepDelay = 20 +KEY_DELAY = 1 @pytest.mark.gui @@ -41,10 +39,6 @@ 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. """ - # 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) @@ -91,10 +85,6 @@ def testDlgProjSettings_Dialog(qtbot, monkeypatch, nwGUI): 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")]) @@ -125,23 +115,21 @@ def testDlgProjSettings_Main(qtbot, monkeypatch, nwGUI, fncDir, fncProj, mockRnd assert tabMain.spellLang.currentData() == "en" assert tabMain.doBackup.isChecked() is False - qtbot.wait(stepDelay) tabMain.editName.setText("") for c in "Project Name": - qtbot.keyClick(tabMain.editName, c, delay=typeDelay) + qtbot.keyClick(tabMain.editName, c, delay=KEY_DELAY) tabMain.editTitle.setText("") for c in "Project Title": - qtbot.keyClick(tabMain.editTitle, c, delay=typeDelay) + qtbot.keyClick(tabMain.editTitle, c, delay=KEY_DELAY) tabMain.editAuthors.clear() for c in "Jane Doe": - qtbot.keyClick(tabMain.editAuthors, c, delay=typeDelay) - qtbot.keyClick(tabMain.editAuthors, Qt.Key_Return, delay=keyDelay) + qtbot.keyClick(tabMain.editAuthors, c, delay=KEY_DELAY) + qtbot.keyClick(tabMain.editAuthors, Qt.Key_Return, delay=KEY_DELAY) for c in "John Doh": - qtbot.keyClick(tabMain.editAuthors, c, delay=typeDelay) - qtbot.keyClick(tabMain.editAuthors, Qt.Key_Return, delay=keyDelay) + qtbot.keyClick(tabMain.editAuthors, c, delay=KEY_DELAY) + qtbot.keyClick(tabMain.editAuthors, Qt.Key_Return, delay=KEY_DELAY) - qtbot.wait(stepDelay) assert tabMain.editName.text() == "Project Name" assert tabMain.editTitle.text() == "Project Title" assert tabMain.editAuthors.toPlainText() == "Jane Doe\nJohn Doh\n" @@ -164,9 +152,6 @@ def testDlgProjSettings_StatusImport(qtbot, monkeypatch, nwGUI, fncDir, fncProj, """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 @@ -230,9 +215,9 @@ def testDlgProjSettings_StatusImport(qtbot, monkeypatch, nwGUI, fncDir, fncProj, 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) + qtbot.keyClick(tabStatus.editName, Qt.Key_Backspace, delay=KEY_DELAY) for c in "Final": - qtbot.keyClick(tabStatus.editName, c, delay=typeDelay) + qtbot.keyClick(tabStatus.editName, c, delay=KEY_DELAY) qtbot.mouseClick(tabStatus.colButton, Qt.LeftButton) qtbot.mouseClick(tabStatus.saveButton, Qt.LeftButton) assert tabStatus.listBox.topLevelItemCount() == 4 @@ -310,9 +295,9 @@ def testDlgProjSettings_StatusImport(qtbot, monkeypatch, nwGUI, fncDir, fncProj, tabImport.listBox.clearSelection() tabImport.listBox.setCurrentItem(tabImport.listBox.topLevelItem(3)) for _ in range(8): - qtbot.keyClick(tabImport.editName, Qt.Key_Backspace, delay=typeDelay) + qtbot.keyClick(tabImport.editName, Qt.Key_Backspace, delay=KEY_DELAY) for c in "Final": - qtbot.keyClick(tabImport.editName, c, delay=typeDelay) + qtbot.keyClick(tabImport.editName, c, delay=KEY_DELAY) qtbot.mouseClick(tabImport.colButton, Qt.LeftButton) qtbot.mouseClick(tabImport.saveButton, Qt.LeftButton) assert tabImport.listBox.topLevelItemCount() == 4 @@ -368,9 +353,6 @@ def testDlgProjSettings_StatusImport(qtbot, monkeypatch, nwGUI, fncDir, fncProj, 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 @@ -419,10 +401,10 @@ def testDlgProjSettings_Replace(qtbot, monkeypatch, nwGUI, fncDir, fncProj, mock tabReplace.listBox.setCurrentItem(tabReplace.listBox.topLevelItem(2)) tabReplace.editKey.setText("") for c in "Th is ": - qtbot.keyClick(tabReplace.editKey, c, delay=typeDelay) + qtbot.keyClick(tabReplace.editKey, c, delay=KEY_DELAY) tabReplace.editValue.setText("") for c in "With This Stuff ": - qtbot.keyClick(tabReplace.editValue, c, delay=typeDelay) + qtbot.keyClick(tabReplace.editValue, c, delay=KEY_DELAY) qtbot.mouseClick(tabReplace.saveButton, Qt.LeftButton) assert tabReplace.listBox.topLevelItem(2).text(0) == "" assert tabReplace.listBox.topLevelItem(2).text(1) == "With This Stuff " diff --git a/tests/test_dialogs/test_dlg_wordlist.py b/tests/test_dialogs/test_dlg_wordlist.py index 994fb407..24d61157 100644 --- a/tests/test_dialogs/test_dlg_wordlist.py +++ b/tests/test_dialogs/test_dlg_wordlist.py @@ -23,7 +23,7 @@ import os import pytest from PyQt5.QtCore import Qt -from PyQt5.QtWidgets import QDialog, QMessageBox, QAction +from PyQt5.QtWidgets import QDialog, QAction from tools import writeFile, readFile, getGuiItem from mock import causeOSError @@ -31,25 +31,17 @@ from mock import causeOSError from novelwriter.constants import nwFiles from novelwriter.dialogs.wordlist import GuiWordList -keyDelay = 2 -typeDelay = 1 -stepDelay = 20 - @pytest.mark.gui def testDlgWordList_Dialog(qtbot, monkeypatch, nwGUI, nwMinimal): """test the word list editor. """ - monkeypatch.setattr(QMessageBox, "information", lambda *a: QMessageBox.Yes) - monkeypatch.setattr(QMessageBox, "question", lambda *a: QMessageBox.Yes) - monkeypatch.setattr(QMessageBox, "critical", lambda *a: QMessageBox.Yes) monkeypatch.setattr(GuiWordList, "exec_", lambda *a: None) monkeypatch.setattr(GuiWordList, "result", lambda *a: QDialog.Accepted) monkeypatch.setattr(GuiWordList, "accept", lambda *a: None) # Open project nwGUI.openProject(nwMinimal) - qtbot.wait(stepDelay) dictFile = os.path.join(nwMinimal, "meta", nwFiles.PROJ_DICT) # Load the dialog @@ -59,7 +51,6 @@ def testDlgWordList_Dialog(qtbot, monkeypatch, nwGUI, nwMinimal): wList = getGuiItem("GuiWordList") assert isinstance(wList, GuiWordList) wList.show() - qtbot.wait(stepDelay) # List should be blank assert wList.listBox.count() == 0 @@ -73,7 +64,6 @@ def testDlgWordList_Dialog(qtbot, monkeypatch, nwGUI, nwMinimal): "word_f\n" "word_b\n" )) - qtbot.wait(stepDelay) assert wList._loadWordList() # Check that the content was loaded @@ -130,7 +120,7 @@ def testDlgWordList_Dialog(qtbot, monkeypatch, nwGUI, nwMinimal): monkeypatch.setattr("builtins.open", causeOSError) assert not wList._doSave() - # qtbot.stopForInteraction() + # qtbot.stop() wList._doClose() # END Test testDlgWordList_Dialog diff --git a/tests/test_gui/test_gui_doceditor.py b/tests/test_gui/test_gui_doceditor.py index 68f02c71..e2ba77ae 100644 --- a/tests/test_gui/test_gui_doceditor.py +++ b/tests/test_gui/test_gui_doceditor.py @@ -25,33 +25,26 @@ from mock import causeOSError from PyQt5.QtCore import Qt from PyQt5.QtGui import QTextBlock, QTextCursor, QTextOption -from PyQt5.QtWidgets import QAction, QMessageBox, qApp +from PyQt5.QtWidgets import QAction, qApp from novelwriter.enum import nwDocAction, nwDocInsert, nwItemLayout from novelwriter.constants import nwKeyWords, nwUnicode from novelwriter.core.index import countWords from novelwriter.gui.doceditor import GuiDocEditor -keyDelay = 2 -typeDelay = 1 -stepDelay = 20 +KEY_DELAY = 1 @pytest.mark.gui -def testGuiEditor_Init(qtbot, monkeypatch, nwGUI, nwMinimal, ipsumText): +def testGuiEditor_Init(qtbot, nwGUI, nwMinimal, ipsumText): """Test initialising the editor. """ - # Block message box - monkeypatch.setattr(QMessageBox, "question", lambda *a: QMessageBox.Yes) - monkeypatch.setattr(QMessageBox, "information", lambda *a: QMessageBox.Yes) - # Open project assert nwGUI.openProject(nwMinimal) assert nwGUI.openDocument("8c659a11cd429") nwGUI.docEditor.setText("### Lorem Ipsum\n\n%s" % ipsumText[0]) assert nwGUI.saveDocument() - qtbot.wait(stepDelay) # Check Defaults qDoc = nwGUI.docEditor.document() @@ -80,7 +73,7 @@ def testGuiEditor_Init(qtbot, monkeypatch, nwGUI, nwMinimal, ipsumText): assert nwGUI.docEditor.horizontalScrollBarPolicy() == Qt.ScrollBarAlwaysOff assert nwGUI.docEditor._typPadChar == nwUnicode.U_THNBSP - # qtbot.stopForInteraction() + # qtbot.stop() # END Test testGuiEditor_Init @@ -89,10 +82,6 @@ def testGuiEditor_Init(qtbot, monkeypatch, nwGUI, nwMinimal, ipsumText): def testGuiEditor_LoadText(qtbot, monkeypatch, caplog, nwGUI, nwMinimal, ipsumText): """Test loading text into the editor. """ - # Block message box - monkeypatch.setattr(QMessageBox, "question", lambda *a: QMessageBox.Yes) - monkeypatch.setattr(QMessageBox, "critical", lambda *a: QMessageBox.Yes) - # Open project sHandle = "8c659a11cd429" assert nwGUI.openProject(nwMinimal) is True @@ -102,7 +91,6 @@ def testGuiEditor_LoadText(qtbot, monkeypatch, caplog, nwGUI, nwMinimal, ipsumTe nwGUI.docEditor.replaceText(longText) assert nwGUI.saveDocument() is True assert nwGUI.closeDocument() is True - qtbot.wait(stepDelay) # Load Text # ========= @@ -142,7 +130,7 @@ def testGuiEditor_LoadText(qtbot, monkeypatch, caplog, nwGUI, nwMinimal, ipsumTe assert nwGUI.docEditor.loadText(sHandle) is True assert nwGUI.docEditor.toPlainText() == "" - # qtbot.stopForInteraction() + # qtbot.stop() # END Test testGuiEditor_LoadText @@ -151,15 +139,10 @@ def testGuiEditor_LoadText(qtbot, monkeypatch, caplog, nwGUI, nwMinimal, ipsumTe def testGuiEditor_SaveText(qtbot, monkeypatch, caplog, nwGUI, nwMinimal, ipsumText): """Test saving text from the editor. """ - # Block message box - monkeypatch.setattr(QMessageBox, "question", lambda *a: QMessageBox.Yes) - monkeypatch.setattr(QMessageBox, "critical", lambda *a: QMessageBox.Yes) - # Open project sHandle = "8c659a11cd429" assert nwGUI.openProject(nwMinimal) is True assert nwGUI.openDocument(sHandle) is True - qtbot.wait(stepDelay) # Save Text # ========= @@ -193,24 +176,19 @@ def testGuiEditor_SaveText(qtbot, monkeypatch, caplog, nwGUI, nwMinimal, ipsumTe # Regular save assert nwGUI.docEditor.saveText() is True - # qtbot.stopForInteraction() + # qtbot.stop() # END Test testGuiEditor_SaveText @pytest.mark.gui -def testGuiEditor_MetaData(qtbot, monkeypatch, nwGUI, nwMinimal): +def testGuiEditor_MetaData(qtbot, nwGUI, nwMinimal): """Test extracting various meta data and other values. """ - # Block message box - monkeypatch.setattr(QMessageBox, "question", lambda *a: QMessageBox.Yes) - monkeypatch.setattr(QMessageBox, "critical", lambda *a: QMessageBox.Yes) - # Open project sHandle = "8c659a11cd429" assert nwGUI.openProject(nwMinimal) is True assert nwGUI.openDocument(sHandle) is True - qtbot.wait(stepDelay) # Get Text # This should replace line and paragraph separators, but preserve @@ -253,21 +231,16 @@ def testGuiEditor_MetaData(qtbot, monkeypatch, nwGUI, nwMinimal): @pytest.mark.gui -def testGuiEditor_Actions(qtbot, monkeypatch, nwGUI, nwMinimal, ipsumText): +def testGuiEditor_Actions(qtbot, nwGUI, nwMinimal, ipsumText): """Test the document actions. This is not an extensive test of the action features, just that the actions are actually called. The various action features are tested when their respective functions are tested. """ - # Block message box - monkeypatch.setattr(QMessageBox, "question", lambda *a: QMessageBox.Yes) - monkeypatch.setattr(QMessageBox, "critical", lambda *a: QMessageBox.Yes) - # Open project sHandle = "8c659a11cd429" assert nwGUI.openProject(nwMinimal) is True assert nwGUI.openDocument(sHandle) is True - qtbot.wait(stepDelay) theText = "### A Scene\n\n%s" % "\n\n".join(ipsumText) assert nwGUI.docEditor.replaceText(theText) is True @@ -487,7 +460,7 @@ def testGuiEditor_Actions(qtbot, monkeypatch, nwGUI, nwMinimal, ipsumText): # Unknown Action assert nwGUI.docEditor.docAction(nwDocAction.NO_ACTION) is False - # qtbot.stopForInteraction() + # qtbot.stop() # END Test testGuiEditor_Actions @@ -496,15 +469,10 @@ def testGuiEditor_Actions(qtbot, monkeypatch, nwGUI, nwMinimal, ipsumText): def testGuiEditor_Insert(qtbot, monkeypatch, nwGUI, nwMinimal, ipsumText): """Test the document insert functions. """ - # Block message box - monkeypatch.setattr(QMessageBox, "question", lambda *a: QMessageBox.Yes) - monkeypatch.setattr(QMessageBox, "critical", lambda *a: QMessageBox.Yes) - # Open project sHandle = "8c659a11cd429" assert nwGUI.openProject(nwMinimal) is True assert nwGUI.openDocument(sHandle) is True - qtbot.wait(stepDelay) theText = "### A Scene\n\n%s" % "\n\n".join(ipsumText) assert nwGUI.docEditor.replaceText(theText) is True @@ -577,7 +545,7 @@ def testGuiEditor_Insert(qtbot, monkeypatch, nwGUI, nwMinimal, ipsumText): "\n\n\n", "\n\n@pov: Jane\n@char: John\n\n", 1 ) - # qtbot.stopForInteraction() + # qtbot.stop() # END Test testGuiEditor_Insert @@ -586,15 +554,10 @@ def testGuiEditor_Insert(qtbot, monkeypatch, nwGUI, nwMinimal, ipsumText): def testGuiEditor_TextManipulation(qtbot, monkeypatch, nwGUI, nwMinimal, ipsumText): """Test the text manipulation functions. """ - # Block message box - monkeypatch.setattr(QMessageBox, "question", lambda *a: QMessageBox.Yes) - monkeypatch.setattr(QMessageBox, "critical", lambda *a: QMessageBox.Yes) - # Open project sHandle = "8c659a11cd429" assert nwGUI.openProject(nwMinimal) is True assert nwGUI.openDocument(sHandle) is True - qtbot.wait(stepDelay) theText = "### A Scene\n\n%s" % "\n\n".join(ipsumText) assert nwGUI.docEditor.replaceText(theText) is True @@ -791,7 +754,7 @@ def testGuiEditor_TextManipulation(qtbot, monkeypatch, nwGUI, nwMinimal, ipsumTe assert newPara[6] == twoBits[4] assert newPara[7] == " ".join(twoBits[5:]) - # qtbot.stopForInteraction() + # qtbot.stop() # END Test testGuiEditor_TextManipulation @@ -800,15 +763,10 @@ def testGuiEditor_TextManipulation(qtbot, monkeypatch, nwGUI, nwMinimal, ipsumTe def testGuiEditor_BlockFormatting(qtbot, monkeypatch, nwGUI, nwMinimal, ipsumText): """Test the block formatting function. """ - # Block message box - monkeypatch.setattr(QMessageBox, "question", lambda *a: QMessageBox.Yes) - monkeypatch.setattr(QMessageBox, "critical", lambda *a: QMessageBox.Yes) - # Open project sHandle = "8c659a11cd429" assert nwGUI.openProject(nwMinimal) is True assert nwGUI.openDocument(sHandle) is True - qtbot.wait(stepDelay) theText = "### A Scene\n\n%s" % "\n\n".join(ipsumText) assert nwGUI.docEditor.replaceText(theText) is True @@ -1115,24 +1073,19 @@ def testGuiEditor_BlockFormatting(qtbot, monkeypatch, nwGUI, nwMinimal, ipsumTex assert nwGUI.docEditor._formatBlock(nwDocAction.BLOCK_COM) is True assert nwGUI.docEditor.getText() == "#### Title\n\n% The Text\n\n" - # qtbot.stopForInteraction() + # qtbot.stop() # END Test testGuiEditor_BlockFormatting @pytest.mark.gui -def testGuiEditor_Tags(qtbot, monkeypatch, nwGUI, nwMinimal, ipsumText): +def testGuiEditor_Tags(qtbot, nwGUI, nwMinimal, ipsumText): """Test the document editor tags functionality. """ - # Block message box - monkeypatch.setattr(QMessageBox, "question", lambda *a: QMessageBox.Yes) - monkeypatch.setattr(QMessageBox, "critical", lambda *a: QMessageBox.Yes) - # Open project sHandle = "8c659a11cd429" assert nwGUI.openProject(nwMinimal) is True assert nwGUI.openDocument(sHandle) is True - qtbot.wait(stepDelay) # Create Scene theText = "### A Scene\n\n@char: Jane, John\n\n" + ipsumText[0] + "\n\n" @@ -1181,7 +1134,7 @@ def testGuiEditor_Tags(qtbot, monkeypatch, nwGUI, nwMinimal, ipsumText): assert nwGUI.closeDocViewer() is True assert nwGUI.docViewer._docHandle is None - # qtbot.stopForInteraction() + # qtbot.stop() # END Test testGuiEditor_Tags @@ -1190,10 +1143,6 @@ def testGuiEditor_Tags(qtbot, monkeypatch, nwGUI, nwMinimal, ipsumText): def testGuiEditor_WordCounters(qtbot, monkeypatch, nwGUI, nwMinimal, ipsumText): """Test saving text from the editor. """ - # Block message box - monkeypatch.setattr(QMessageBox, "question", lambda *a: QMessageBox.Yes) - monkeypatch.setattr(QMessageBox, "critical", lambda *a: QMessageBox.Yes) - class MockThreadPool: def __init__(self): @@ -1226,7 +1175,6 @@ def testGuiEditor_WordCounters(qtbot, monkeypatch, nwGUI, nwMinimal, ipsumText): nwGUI.theProject.tree[sHandle]._initCount = 0 # Clear item's count nwGUI.theProject.tree[sHandle]._wordCount = 0 # Clear item's count assert nwGUI.openDocument(sHandle) is True - qtbot.wait(stepDelay) theText = "\n\n".join(ipsumText) cC, wC, pC = countWords(theText) @@ -1249,7 +1197,6 @@ def testGuiEditor_WordCounters(qtbot, monkeypatch, nwGUI, nwMinimal, ipsumText): nwGUI.docEditor.wCounterDoc.run() # nwGUI.docEditor._updateDocCounts(cC, wC, pC) - qtbot.wait(stepDelay) assert nwGUI.theProject.tree[sHandle]._charCount == cC assert nwGUI.theProject.tree[sHandle]._wordCount == wC assert nwGUI.theProject.tree[sHandle]._paraCount == pC @@ -1258,7 +1205,6 @@ def testGuiEditor_WordCounters(qtbot, monkeypatch, nwGUI, nwMinimal, ipsumText): # Select all text assert nwGUI.docEditor.docFooter._docSelection is False nwGUI.docEditor.docAction(nwDocAction.SEL_ALL) - qtbot.wait(stepDelay) assert nwGUI.docEditor.docFooter._docSelection is True # Run the selection word counter @@ -1267,10 +1213,9 @@ def testGuiEditor_WordCounters(qtbot, monkeypatch, nwGUI, nwMinimal, ipsumText): nwGUI.docEditor.wCounterSel.run() # nwGUI.docEditor._updateSelCounts(cC, wC, pC) - qtbot.wait(stepDelay) assert nwGUI.docEditor.docFooter.wordsText.text() == f"Words: {wC} selected" - # qtbot.stopForInteraction() + # qtbot.stop() # END Test testGuiEditor_WordCounters @@ -1279,14 +1224,11 @@ def testGuiEditor_WordCounters(qtbot, monkeypatch, nwGUI, nwMinimal, ipsumText): def testGuiEditor_Search(qtbot, monkeypatch, nwGUI, nwLipsum): """Test the document editor search functionality. """ - # Block message box - monkeypatch.setattr(QMessageBox, "question", lambda *a: QMessageBox.Yes) monkeypatch.setattr(GuiDocEditor, "hasFocus", lambda *a: True) assert nwGUI.openProject(nwLipsum) is True assert nwGUI.openDocument("4c4f28287af27") is True origText = nwGUI.docEditor.getText() - qtbot.wait(stepDelay) # Select the Word "est" nwGUI.docEditor.setCursorPosition(630) @@ -1301,11 +1243,11 @@ def testGuiEditor_Search(qtbot, monkeypatch, nwGUI, nwLipsum): # Find next by enter key monkeypatch.setattr(nwGUI.docEditor.docSearch.searchBox, "hasFocus", lambda: True) - qtbot.keyClick(nwGUI.docEditor.docSearch.searchBox, Qt.Key_Return, delay=keyDelay) + qtbot.keyClick(nwGUI.docEditor.docSearch.searchBox, Qt.Key_Return, delay=KEY_DELAY) assert abs(nwGUI.docEditor.getCursorPosition() - 1284) < 3 # Find next by button - qtbot.mouseClick(nwGUI.docEditor.docSearch.searchButton, Qt.LeftButton, delay=keyDelay) + qtbot.mouseClick(nwGUI.docEditor.docSearch.searchButton, Qt.LeftButton, delay=KEY_DELAY) assert abs(nwGUI.docEditor.getCursorPosition() - 1498) < 3 # Activate loop search @@ -1323,14 +1265,14 @@ def testGuiEditor_Search(qtbot, monkeypatch, nwGUI, nwLipsum): assert nwGUI.docEditor.setCursorPosition(15) # Toggle search again with header button - qtbot.mouseClick(nwGUI.docEditor.docHeader.searchButton, Qt.LeftButton, delay=keyDelay) + qtbot.mouseClick(nwGUI.docEditor.docHeader.searchButton, Qt.LeftButton, delay=KEY_DELAY) assert nwGUI.docEditor.docSearch.setSearchText("") assert nwGUI.docEditor.docSearch.isVisible() is True # Search for non-existing nwGUI.docEditor.setCursorPosition(0) assert nwGUI.docEditor.docSearch.setSearchText("abcdef") - qtbot.mouseClick(nwGUI.docEditor.docSearch.searchButton, Qt.LeftButton, delay=keyDelay) + qtbot.mouseClick(nwGUI.docEditor.docSearch.searchButton, Qt.LeftButton, delay=KEY_DELAY) assert nwGUI.docEditor.getCursorPosition() < 3 # No result # Enable RegEx search @@ -1341,19 +1283,19 @@ def testGuiEditor_Search(qtbot, monkeypatch, nwGUI, nwLipsum): # Set invalid RegEx nwGUI.docEditor.setCursorPosition(0) assert nwGUI.docEditor.docSearch.setSearchText(r"\bSus[") - qtbot.mouseClick(nwGUI.docEditor.docSearch.searchButton, Qt.LeftButton, delay=keyDelay) + qtbot.mouseClick(nwGUI.docEditor.docSearch.searchButton, Qt.LeftButton, delay=KEY_DELAY) assert nwGUI.docEditor.getCursorPosition() < 3 # No result # Set dangerous RegEx (issue #1015) # If this doesn't get caught, the app will hang nwGUI.docEditor.setCursorPosition(0) assert nwGUI.docEditor.docSearch.setSearchText(r".*") - qtbot.mouseClick(nwGUI.docEditor.docSearch.searchButton, Qt.LeftButton, delay=keyDelay) + qtbot.mouseClick(nwGUI.docEditor.docSearch.searchButton, Qt.LeftButton, delay=KEY_DELAY) assert abs(nwGUI.docEditor.getCursorPosition() - 14) < 3 # Set valid RegEx assert nwGUI.docEditor.docSearch.setSearchText(r"\bSus") - qtbot.mouseClick(nwGUI.docEditor.docSearch.searchButton, Qt.LeftButton, delay=keyDelay) + qtbot.mouseClick(nwGUI.docEditor.docSearch.searchButton, Qt.LeftButton, delay=KEY_DELAY) assert abs(nwGUI.docEditor.getCursorPosition() - 208) < 3 # Find next and then prev @@ -1404,7 +1346,7 @@ def testGuiEditor_Search(qtbot, monkeypatch, nwGUI, nwLipsum): assert abs(nwGUI.docEditor.getCursorPosition() - 208) < 3 # Replace "sus" with "foo" via replace button - qtbot.mouseClick(nwGUI.docEditor.docSearch.replaceButton, Qt.LeftButton, delay=keyDelay) + qtbot.mouseClick(nwGUI.docEditor.docSearch.replaceButton, Qt.LeftButton, delay=KEY_DELAY) assert nwGUI.docEditor.getText()[205:213] == "foocipit" # Revert last two replaces @@ -1482,7 +1424,7 @@ def testGuiEditor_Search(qtbot, monkeypatch, nwGUI, nwLipsum): monkeypatch.setattr(nwGUI.docEditor.docSearch.replaceBox, "hasFocus", lambda: True) assert nwGUI.docEditor.focusNextPrevChild(True) is True - # qtbot.stopForInteraction() + # qtbot.stop() # END Test testGuiEditor_Search diff --git a/tests/test_gui/test_gui_docviewer.py b/tests/test_gui/test_gui_docviewer.py index ef76d51c..22057e15 100644 --- a/tests/test_gui/test_gui_docviewer.py +++ b/tests/test_gui/test_gui_docviewer.py @@ -23,25 +23,17 @@ import pytest from PyQt5.QtCore import Qt, QUrl from PyQt5.QtGui import QTextCursor -from PyQt5.QtWidgets import qApp, QAction, QMessageBox +from PyQt5.QtWidgets import qApp, QAction from mock import causeException from novelwriter.enum import nwDocAction from novelwriter.core.tohtml import ToHtml -keyDelay = 2 -typeDelay = 1 -stepDelay = 20 - @pytest.mark.gui def testGuiViewer_Main(qtbot, monkeypatch, nwGUI, nwLipsum): """Test the document viewer. """ - # Block message box - monkeypatch.setattr(QMessageBox, "question", lambda *a: QMessageBox.Yes) - monkeypatch.setattr(QMessageBox, "information", lambda *a: QMessageBox.Yes) - # Open project assert nwGUI.openProject(nwLipsum) @@ -184,6 +176,6 @@ def testGuiViewer_Main(qtbot, monkeypatch, nwGUI, nwLipsum): assert nwGUI.docViewer.loadText("846352075de7d") is False assert nwGUI.docViewer.toPlainText() == "An error occurred while generating the preview." - # qtbot.stopForInteraction() + # qtbot.stop() # END Test testGuiViewer_Main diff --git a/tests/test_gui/test_gui_guimain.py b/tests/test_gui/test_gui_guimain.py index 5d434601..79fcbe52 100644 --- a/tests/test_gui/test_gui_guimain.py +++ b/tests/test_gui/test_gui_guimain.py @@ -37,17 +37,13 @@ from novelwriter.gui.projtree import GuiProjectTree from novelwriter.gui.doceditor import GuiDocEditor from novelwriter.gui.noveltree import GuiNovelView -keyDelay = 2 -typeDelay = 1 -stepDelay = 20 +KEY_DELAY = 1 @pytest.mark.gui -def testGuiMain_ProjectBlocker(monkeypatch, nwGUI): +def testGuiMain_ProjectBlocker(nwGUI): """Test the blocking of features when there's no project open. """ - monkeypatch.setattr(QMessageBox, "question", lambda *a: QMessageBox.Yes) - # Test no-project blocking assert nwGUI.closeProject() is True assert nwGUI.saveProject() is False @@ -74,9 +70,6 @@ def testGuiMain_ProjectBlocker(monkeypatch, nwGUI): def testGuiMain_NewProject(monkeypatch, nwGUI, fncProj): """Test creating a new project. """ - monkeypatch.setattr(QMessageBox, "question", lambda *a: QMessageBox.Yes) - monkeypatch.setattr(QMessageBox, "critical", lambda *a: QMessageBox.Yes) - # No data with monkeypatch.context() as mp: mp.setattr(GuiProjectWizard, "exec_", lambda *a: None) @@ -113,8 +106,6 @@ def testGuiMain_NewProject(monkeypatch, nwGUI, fncProj): def testGuiMain_ProjectTreeItems(qtbot, monkeypatch, nwGUI, fncProj, mockRnd): """Test handling of project tree items based on GUI focus states. """ - monkeypatch.setattr(QMessageBox, "question", lambda *a: QMessageBox.Yes) - buildTestProject(nwGUI, fncProj) sHandle = "000000000000f" @@ -165,9 +156,6 @@ def testGuiMain_ProjectTreeItems(qtbot, monkeypatch, nwGUI, fncProj, mockRnd): def testGuiMain_Editing(qtbot, monkeypatch, nwGUI, fncProj, refDir, outDir, mockRnd): """Test the document editor. """ - # Block message box - monkeypatch.setattr(QMessageBox, "question", lambda *a: QMessageBox.Yes) - monkeypatch.setattr(QMessageBox, "information", lambda *a: QMessageBox.Yes) monkeypatch.setattr(GuiProjectTree, "hasFocus", lambda *a: True) monkeypatch.setattr(GuiDocEditor, "hasFocus", lambda *a: True) monkeypatch.setattr(QInputDialog, "getText", lambda *a, text: (text, True)) @@ -195,11 +183,9 @@ def testGuiMain_Editing(qtbot, monkeypatch, nwGUI, fncProj, refDir, outDir, mock compFile = os.path.join(refDir, "guiEditor_Main_Initial_nwProject.nwx") copyfile(projFile, testFile) assert cmpFiles(testFile, compFile, ignoreStart=XML_IGNORE) - qtbot.wait(stepDelay) # Re-open project assert nwGUI.openProject(fncProj) - qtbot.wait(stepDelay) # Check that we loaded the data assert len(nwGUI.theProject.tree) == 8 @@ -241,18 +227,18 @@ def testGuiMain_Editing(qtbot, monkeypatch, nwGUI, fncProj, refDir, outDir, mock # Type something into the document nwGUI.switchFocus(nwWidget.EDITOR) - qtbot.keyClick(nwGUI.docEditor, "a", modifier=Qt.ControlModifier, delay=keyDelay) + qtbot.keyClick(nwGUI.docEditor, "a", modifier=Qt.ControlModifier, delay=KEY_DELAY) for c in "# Jane Doe": - qtbot.keyClick(nwGUI.docEditor, c, delay=typeDelay) - qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay) - qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay) + qtbot.keyClick(nwGUI.docEditor, c, delay=KEY_DELAY) + qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=KEY_DELAY) + qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=KEY_DELAY) for c in "@tag: Jane": - qtbot.keyClick(nwGUI.docEditor, c, delay=typeDelay) - qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay) - qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay) + qtbot.keyClick(nwGUI.docEditor, c, delay=KEY_DELAY) + qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=KEY_DELAY) + qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=KEY_DELAY) for c in "This is a file about Jane.": - qtbot.keyClick(nwGUI.docEditor, c, delay=typeDelay) - qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay) + qtbot.keyClick(nwGUI.docEditor, c, delay=KEY_DELAY) + qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=KEY_DELAY) # Add a Plot File nwGUI.switchFocus(nwWidget.TREE) @@ -263,18 +249,18 @@ def testGuiMain_Editing(qtbot, monkeypatch, nwGUI, fncProj, refDir, outDir, mock # Type something into the document nwGUI.switchFocus(nwWidget.EDITOR) - qtbot.keyClick(nwGUI.docEditor, "a", modifier=Qt.ControlModifier, delay=keyDelay) + qtbot.keyClick(nwGUI.docEditor, "a", modifier=Qt.ControlModifier, delay=KEY_DELAY) for c in "# Main Plot": - qtbot.keyClick(nwGUI.docEditor, c, delay=typeDelay) - qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay) - qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay) + qtbot.keyClick(nwGUI.docEditor, c, delay=KEY_DELAY) + qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=KEY_DELAY) + qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=KEY_DELAY) for c in "@tag: MainPlot": - qtbot.keyClick(nwGUI.docEditor, c, delay=typeDelay) - qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay) - qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay) + qtbot.keyClick(nwGUI.docEditor, c, delay=KEY_DELAY) + qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=KEY_DELAY) + qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=KEY_DELAY) for c in "This is a file detailing the main plot.": - qtbot.keyClick(nwGUI.docEditor, c, delay=typeDelay) - qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay) + qtbot.keyClick(nwGUI.docEditor, c, delay=KEY_DELAY) + qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=KEY_DELAY) # Add a World File nwGUI.switchFocus(nwWidget.TREE) @@ -290,18 +276,18 @@ def testGuiMain_Editing(qtbot, monkeypatch, nwGUI, fncProj, refDir, outDir, mock # Type something into the document nwGUI.switchFocus(nwWidget.EDITOR) - qtbot.keyClick(nwGUI.docEditor, "a", modifier=Qt.ControlModifier, delay=keyDelay) + qtbot.keyClick(nwGUI.docEditor, "a", modifier=Qt.ControlModifier, delay=KEY_DELAY) for c in "# Main Location": - qtbot.keyClick(nwGUI.docEditor, c, delay=typeDelay) - qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay) - qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay) + qtbot.keyClick(nwGUI.docEditor, c, delay=KEY_DELAY) + qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=KEY_DELAY) + qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=KEY_DELAY) for c in "@tag: Home": - qtbot.keyClick(nwGUI.docEditor, c, delay=typeDelay) - qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay) - qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay) + qtbot.keyClick(nwGUI.docEditor, c, delay=KEY_DELAY) + qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=KEY_DELAY) + qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=KEY_DELAY) for c in "This is a file describing Jane's home.": - qtbot.keyClick(nwGUI.docEditor, c, delay=typeDelay) - qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay) + qtbot.keyClick(nwGUI.docEditor, c, delay=KEY_DELAY) + qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=KEY_DELAY) # Trigger autosaves before making more changes nwGUI._autoSaveDocument() @@ -317,67 +303,67 @@ def testGuiMain_Editing(qtbot, monkeypatch, nwGUI, fncProj, refDir, outDir, mock # Type something into the document nwGUI.switchFocus(nwWidget.EDITOR) - qtbot.keyClick(nwGUI.docEditor, "a", modifier=Qt.ControlModifier, delay=keyDelay) + qtbot.keyClick(nwGUI.docEditor, "a", modifier=Qt.ControlModifier, delay=KEY_DELAY) for c in "# Novel": - qtbot.keyClick(nwGUI.docEditor, c, delay=typeDelay) - qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay) - qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay) + qtbot.keyClick(nwGUI.docEditor, c, delay=KEY_DELAY) + qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=KEY_DELAY) + qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=KEY_DELAY) for c in "## Chapter": - qtbot.keyClick(nwGUI.docEditor, c, delay=typeDelay) - qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay) - qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay) + qtbot.keyClick(nwGUI.docEditor, c, delay=KEY_DELAY) + qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=KEY_DELAY) + qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=KEY_DELAY) for c in "@pov: Jane": - qtbot.keyClick(nwGUI.docEditor, c, delay=typeDelay) - qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay) + qtbot.keyClick(nwGUI.docEditor, c, delay=KEY_DELAY) + qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=KEY_DELAY) for c in "@plot: MainPlot": - qtbot.keyClick(nwGUI.docEditor, c, delay=typeDelay) - qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay) - qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay) + qtbot.keyClick(nwGUI.docEditor, c, delay=KEY_DELAY) + qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=KEY_DELAY) + qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=KEY_DELAY) for c in "### Scene": - qtbot.keyClick(nwGUI.docEditor, c, delay=typeDelay) - qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay) - qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay) + qtbot.keyClick(nwGUI.docEditor, c, delay=KEY_DELAY) + qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=KEY_DELAY) + qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=KEY_DELAY) for c in "% How about a comment?": - qtbot.keyClick(nwGUI.docEditor, c, delay=typeDelay) - qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay) + qtbot.keyClick(nwGUI.docEditor, c, delay=KEY_DELAY) + qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=KEY_DELAY) for c in "@pov: Jane": - qtbot.keyClick(nwGUI.docEditor, c, delay=typeDelay) - qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay) + qtbot.keyClick(nwGUI.docEditor, c, delay=KEY_DELAY) + qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=KEY_DELAY) for c in "@plot: MainPlot": - qtbot.keyClick(nwGUI.docEditor, c, delay=typeDelay) - qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay) + qtbot.keyClick(nwGUI.docEditor, c, delay=KEY_DELAY) + qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=KEY_DELAY) for c in "@location: Home": - qtbot.keyClick(nwGUI.docEditor, c, delay=typeDelay) - qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay) - qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay) + qtbot.keyClick(nwGUI.docEditor, c, delay=KEY_DELAY) + qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=KEY_DELAY) + qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=KEY_DELAY) for c in "#### Some Section": - qtbot.keyClick(nwGUI.docEditor, c, delay=typeDelay) - qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay) - qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay) + qtbot.keyClick(nwGUI.docEditor, c, delay=KEY_DELAY) + qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=KEY_DELAY) + qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=KEY_DELAY) for c in "@char: Jane": - qtbot.keyClick(nwGUI.docEditor, c, delay=typeDelay) - qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay) - qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay) + qtbot.keyClick(nwGUI.docEditor, c, delay=KEY_DELAY) + qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=KEY_DELAY) + qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=KEY_DELAY) for c in "This is a paragraph of nonsense text.": - qtbot.keyClick(nwGUI.docEditor, c, delay=typeDelay) - qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay) - qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay) + qtbot.keyClick(nwGUI.docEditor, c, delay=KEY_DELAY) + qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=KEY_DELAY) + qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=KEY_DELAY) # Don't allow Shift+Enter to insert a line separator (issue #1150) for c in "This is another paragraph": - qtbot.keyClick(nwGUI.docEditor, c, delay=typeDelay) - qtbot.keyClick(nwGUI.docEditor, Qt.Key_Enter, modifier=Qt.ShiftModifier, delay=typeDelay) + qtbot.keyClick(nwGUI.docEditor, c, delay=KEY_DELAY) + qtbot.keyClick(nwGUI.docEditor, Qt.Key_Enter, modifier=Qt.ShiftModifier, delay=KEY_DELAY) for c in "with a line separator in it.": - qtbot.keyClick(nwGUI.docEditor, c, delay=typeDelay) - qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay) - qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay) + qtbot.keyClick(nwGUI.docEditor, c, delay=KEY_DELAY) + qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=KEY_DELAY) + qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=KEY_DELAY) # Auto-Replace # ============ @@ -386,43 +372,43 @@ def testGuiMain_Editing(qtbot, monkeypatch, nwGUI, fncProj, refDir, outDir, mock "This is another paragraph of much longer nonsense text. " "It is in fact 1 very very NONSENSICAL nonsense text! " ): - qtbot.keyClick(nwGUI.docEditor, c, delay=typeDelay) + qtbot.keyClick(nwGUI.docEditor, c, delay=KEY_DELAY) for c in "We can also try replacing \"quotes\", even single 'quotes' are replaced. ": - qtbot.keyClick(nwGUI.docEditor, c, delay=typeDelay) + qtbot.keyClick(nwGUI.docEditor, c, delay=KEY_DELAY) for c in "Isn't that nice? ": - qtbot.keyClick(nwGUI.docEditor, c, delay=typeDelay) + qtbot.keyClick(nwGUI.docEditor, c, delay=KEY_DELAY) for c in "We can hyphen-ate, make dashes -- and even longer dashes --- if we want. ": - qtbot.keyClick(nwGUI.docEditor, c, delay=typeDelay) + qtbot.keyClick(nwGUI.docEditor, c, delay=KEY_DELAY) for c in "Ellipsis? Not a problem either ... ": - qtbot.keyClick(nwGUI.docEditor, c, delay=typeDelay) + qtbot.keyClick(nwGUI.docEditor, c, delay=KEY_DELAY) for c in "How about three hyphens - -": - qtbot.keyClick(nwGUI.docEditor, c, delay=typeDelay) - qtbot.keyClick(nwGUI.docEditor, Qt.Key_Left, delay=keyDelay) - qtbot.keyClick(nwGUI.docEditor, Qt.Key_Backspace, delay=keyDelay) - qtbot.keyClick(nwGUI.docEditor, Qt.Key_Right, delay=keyDelay) + qtbot.keyClick(nwGUI.docEditor, c, delay=KEY_DELAY) + qtbot.keyClick(nwGUI.docEditor, Qt.Key_Left, delay=KEY_DELAY) + qtbot.keyClick(nwGUI.docEditor, Qt.Key_Backspace, delay=KEY_DELAY) + qtbot.keyClick(nwGUI.docEditor, Qt.Key_Right, delay=KEY_DELAY) for c in "- for long dash? It works too.": - qtbot.keyClick(nwGUI.docEditor, c, delay=typeDelay) - qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay) - qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay) + qtbot.keyClick(nwGUI.docEditor, c, delay=KEY_DELAY) + qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=KEY_DELAY) + qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=KEY_DELAY) for c in "\"Full line double quoted text.\"": - qtbot.keyClick(nwGUI.docEditor, c, delay=typeDelay) - qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay) - qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay) + qtbot.keyClick(nwGUI.docEditor, c, delay=KEY_DELAY) + qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=KEY_DELAY) + qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=KEY_DELAY) for c in "'Full line single quoted text.'": - qtbot.keyClick(nwGUI.docEditor, c, delay=typeDelay) - qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay) - qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay) + qtbot.keyClick(nwGUI.docEditor, c, delay=KEY_DELAY) + qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=KEY_DELAY) + qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=KEY_DELAY) # Insert spaces before and after quotes nwGUI.mainConf.fmtPadBefore = "\u201d" nwGUI.mainConf.fmtPadAfter = "\u201c" for c in "Some \"double quoted text with spaces padded\".": - qtbot.keyClick(nwGUI.docEditor, c, delay=typeDelay) - qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay) - qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay) + qtbot.keyClick(nwGUI.docEditor, c, delay=KEY_DELAY) + qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=KEY_DELAY) + qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=KEY_DELAY) nwGUI.mainConf.fmtPadBefore = "" nwGUI.mainConf.fmtPadAfter = "" @@ -431,24 +417,24 @@ def testGuiMain_Editing(qtbot, monkeypatch, nwGUI, fncProj, refDir, outDir, mock nwGUI.mainConf.fmtPadBefore = ":" for c in "@object: NoSpaceAdded": - qtbot.keyClick(nwGUI.docEditor, c, delay=typeDelay) - qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay) - qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay) + qtbot.keyClick(nwGUI.docEditor, c, delay=KEY_DELAY) + qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=KEY_DELAY) + qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=KEY_DELAY) for c in "% synopsis: No space before this colon.": - qtbot.keyClick(nwGUI.docEditor, c, delay=typeDelay) - qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay) - qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay) + qtbot.keyClick(nwGUI.docEditor, c, delay=KEY_DELAY) + qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=KEY_DELAY) + qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=KEY_DELAY) for c in "Add space before this colon: See?": - qtbot.keyClick(nwGUI.docEditor, c, delay=typeDelay) - qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay) - qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay) + qtbot.keyClick(nwGUI.docEditor, c, delay=KEY_DELAY) + qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=KEY_DELAY) + qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=KEY_DELAY) for c in "But don't add a double space : See?": - qtbot.keyClick(nwGUI.docEditor, c, delay=typeDelay) - qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay) - qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay) + qtbot.keyClick(nwGUI.docEditor, c, delay=KEY_DELAY) + qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=KEY_DELAY) + qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=KEY_DELAY) nwGUI.mainConf.fmtPadBefore = "" @@ -456,55 +442,49 @@ def testGuiMain_Editing(qtbot, monkeypatch, nwGUI, fncProj, refDir, outDir, mock # ================ for c in "\t\"Tab-indented text\"": - qtbot.keyClick(nwGUI.docEditor, c, delay=typeDelay) - qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay) - qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay) + qtbot.keyClick(nwGUI.docEditor, c, delay=KEY_DELAY) + qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=KEY_DELAY) + qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=KEY_DELAY) for c in ">\"Paragraph-indented text\"": - qtbot.keyClick(nwGUI.docEditor, c, delay=typeDelay) - qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay) - qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay) + qtbot.keyClick(nwGUI.docEditor, c, delay=KEY_DELAY) + qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=KEY_DELAY) + qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=KEY_DELAY) for c in ">>\"Right-aligned text\"": - qtbot.keyClick(nwGUI.docEditor, c, delay=typeDelay) - qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay) - qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay) + qtbot.keyClick(nwGUI.docEditor, c, delay=KEY_DELAY) + qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=KEY_DELAY) + qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=KEY_DELAY) for c in "\t'Tab-indented text'": - qtbot.keyClick(nwGUI.docEditor, c, delay=typeDelay) - qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay) - qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay) + qtbot.keyClick(nwGUI.docEditor, c, delay=KEY_DELAY) + qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=KEY_DELAY) + qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=KEY_DELAY) for c in ">'Paragraph-indented text'": - qtbot.keyClick(nwGUI.docEditor, c, delay=typeDelay) - qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay) - qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay) + qtbot.keyClick(nwGUI.docEditor, c, delay=KEY_DELAY) + qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=KEY_DELAY) + qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=KEY_DELAY) for c in ">>'Right-aligned text'": - qtbot.keyClick(nwGUI.docEditor, c, delay=typeDelay) - qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay) - qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay) + qtbot.keyClick(nwGUI.docEditor, c, delay=KEY_DELAY) + qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=KEY_DELAY) + qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=KEY_DELAY) - qtbot.wait(stepDelay) nwGUI.docEditor.wCounterDoc.run() - qtbot.wait(stepDelay) # Save the document assert nwGUI.docEditor.docChanged() assert nwGUI.saveDocument() assert not nwGUI.docEditor.docChanged() - qtbot.wait(stepDelay) nwGUI.rebuildIndex() - qtbot.wait(stepDelay) # Open and view the edited document nwGUI.switchFocus(nwWidget.VIEWER) assert nwGUI.openDocument(C.hSceneDoc) assert nwGUI.viewDocument(C.hSceneDoc) - qtbot.wait(stepDelay) assert nwGUI.saveProject() assert nwGUI.closeDocViewer() - qtbot.wait(stepDelay) # Check a Quick Create and Delete assert nwGUI.projView.projTree.newTreeItem(nwItemType.FILE, None) @@ -553,11 +533,9 @@ def testGuiMain_Editing(qtbot, monkeypatch, nwGUI, fncProj, refDir, outDir, mock @pytest.mark.gui -def testGuiMain_FocusFullMode(qtbot, monkeypatch, nwGUI, fncProj, mockRnd): +def testGuiMain_FocusFullMode(qtbot, nwGUI, fncProj, mockRnd): """Test toggling focus mode in main window. """ - monkeypatch.setattr(QMessageBox, "question", lambda *a: QMessageBox.Yes) - buildTestProject(nwGUI, fncProj) assert nwGUI.isFocusMode is False @@ -596,6 +574,6 @@ def testGuiMain_FocusFullMode(qtbot, monkeypatch, nwGUI, fncProj, mockRnd): nwGUI.toggleFullScreenMode() assert nwGUI.mainConf.isFullScreen is False - # qtbot.stopForInteraction() + # qtbot.stop() # END Test testGuiMain_FocusFullMode diff --git a/tests/test_gui/test_gui_mainmenu.py b/tests/test_gui/test_gui_mainmenu.py index c4bf2dd3..e55e1039 100644 --- a/tests/test_gui/test_gui_mainmenu.py +++ b/tests/test_gui/test_gui_mainmenu.py @@ -32,24 +32,17 @@ from novelwriter.enum import nwDocAction, nwDocInsert from novelwriter.constants import nwKeyWords, nwUnicode from novelwriter.gui.doceditor import GuiDocEditor -keyDelay = 2 -typeDelay = 1 -stepDelay = 20 - @pytest.mark.gui def testGuiMenu_EditFormat(qtbot, monkeypatch, nwGUI, nwLipsum): """Test the main menu Edit and Format entries. """ - # Block message box - monkeypatch.setattr(QMessageBox, "question", lambda *a: QMessageBox.Yes) monkeypatch.setattr(GuiDocEditor, "hasFocus", lambda *a: True) # Test Document Action with No Project assert nwGUI.docEditor.docAction(nwDocAction.COPY) is False assert nwGUI.openProject(nwLipsum) is True - qtbot.wait(stepDelay) # Split By Chapter assert nwGUI.openDocument("4c4f28287af27") is True @@ -61,57 +54,43 @@ def testGuiMenu_EditFormat(qtbot, monkeypatch, nwGUI, nwLipsum): nwGUI.mainMenu.aFmtStrong.activate(QAction.Trigger) fmtStr = "**Pellentesque** nec erat ut nulla posuere commodo." assert nwGUI.docEditor.getText()[39:90] == fmtStr - qtbot.wait(stepDelay) nwGUI.mainMenu.aFmtStrong.activate(QAction.Trigger) assert nwGUI.docEditor.getText()[39:86] == cleanText - qtbot.wait(stepDelay) # Italic nwGUI.mainMenu.aFmtEmph.activate(QAction.Trigger) fmtStr = "_Pellentesque_ nec erat ut nulla posuere commodo." assert nwGUI.docEditor.getText()[39:88] == fmtStr - qtbot.wait(stepDelay) nwGUI.mainMenu.aFmtEmph.activate(QAction.Trigger) assert nwGUI.docEditor.getText()[39:86] == cleanText - qtbot.wait(stepDelay) # Strikethrough nwGUI.mainMenu.aFmtStrike.activate(QAction.Trigger) fmtStr = "~~Pellentesque~~ nec erat ut nulla posuere commodo." assert nwGUI.docEditor.getText()[39:90] == fmtStr - qtbot.wait(stepDelay) nwGUI.mainMenu.aFmtStrike.activate(QAction.Trigger) assert nwGUI.docEditor.getText()[39:86] == cleanText - qtbot.wait(stepDelay) # Should get us back to plain nwGUI.mainMenu.aFmtStrong.activate(QAction.Trigger) - qtbot.wait(stepDelay) nwGUI.mainMenu.aFmtEmph.activate(QAction.Trigger) - qtbot.wait(stepDelay) nwGUI.mainMenu.aFmtEmph.activate(QAction.Trigger) - qtbot.wait(stepDelay) nwGUI.mainMenu.aFmtStrong.activate(QAction.Trigger) assert nwGUI.docEditor.getText()[39:86] == cleanText - qtbot.wait(stepDelay) # Double Quotes nwGUI.mainMenu.aFmtDQuote.activate(QAction.Trigger) fmtStr = "“Pellentesque” nec erat ut nulla posuere commodo." assert nwGUI.docEditor.getText()[39:88] == fmtStr - qtbot.wait(stepDelay) nwGUI.mainMenu.aEditUndo.activate(QAction.Trigger) assert nwGUI.docEditor.getText()[39:86] == cleanText - qtbot.wait(stepDelay) # Single Quotes nwGUI.mainMenu.aFmtSQuote.activate(QAction.Trigger) fmtStr = "‘Pellentesque’ nec erat ut nulla posuere commodo." assert nwGUI.docEditor.getText()[39:88] == fmtStr - qtbot.wait(stepDelay) nwGUI.mainMenu.aEditUndo.activate(QAction.Trigger) assert nwGUI.docEditor.getText()[39:86] == cleanText - qtbot.wait(stepDelay) # Block Formats # ============= @@ -121,61 +100,50 @@ def testGuiMenu_EditFormat(qtbot, monkeypatch, nwGUI, nwLipsum): nwGUI.mainMenu.aFmtHead1.activate(QAction.Trigger) fmtStr = "# Pellentesque nec erat ut nulla posuere commodo." assert nwGUI.docEditor.getText()[39:88] == fmtStr - qtbot.wait(stepDelay) # Header 2 nwGUI.mainMenu.aFmtHead2.activate(QAction.Trigger) fmtStr = "## Pellentesque nec erat ut nulla posuere commodo." assert nwGUI.docEditor.getText()[39:89] == fmtStr - qtbot.wait(stepDelay) # Header 3 nwGUI.mainMenu.aFmtHead3.activate(QAction.Trigger) fmtStr = "### Pellentesque nec erat ut nulla posuere commodo." assert nwGUI.docEditor.getText()[39:90] == fmtStr - qtbot.wait(stepDelay) # Header 4 nwGUI.mainMenu.aFmtHead4.activate(QAction.Trigger) fmtStr = "#### Pellentesque nec erat ut nulla posuere commodo." assert nwGUI.docEditor.getText()[39:91] == fmtStr - qtbot.wait(stepDelay) # Clear Format nwGUI.mainMenu.aFmtNoFormat.activate(QAction.Trigger) assert nwGUI.docEditor.getText()[39:86] == cleanText - qtbot.wait(stepDelay) # Comment On nwGUI.mainMenu.aFmtComment.activate(QAction.Trigger) fmtStr = "% Pellentesque nec erat ut nulla posuere commodo." assert nwGUI.docEditor.getText()[39:88] == fmtStr - qtbot.wait(stepDelay) # Comment Off nwGUI.mainMenu.aFmtComment.activate(QAction.Trigger) assert nwGUI.docEditor.getText()[39:86] == cleanText - qtbot.wait(stepDelay) # Check comment with no space before text assert nwGUI.docEditor.setCursorPosition(39) assert nwGUI.docEditor.insertText("%") fmtStr = "%Pellentesque nec erat ut nulla posuere commodo." assert nwGUI.docEditor.getText()[39:87] == fmtStr - qtbot.wait(stepDelay) nwGUI.mainMenu.aFmtNoFormat.activate(QAction.Trigger) assert nwGUI.docEditor.getText()[39:86] == cleanText - qtbot.wait(stepDelay) # Undo/Redo nwGUI.mainMenu.aEditUndo.activate(QAction.Trigger) fmtStr = "%Pellentesque nec erat ut nulla posuere commodo." assert nwGUI.docEditor.getText()[39:87] == fmtStr - qtbot.wait(stepDelay) nwGUI.mainMenu.aEditRedo.activate(QAction.Trigger) assert nwGUI.docEditor.getText()[39:86] == cleanText - qtbot.wait(stepDelay) # Cut, Copy and Paste assert nwGUI.docEditor.setCursorPosition(39) @@ -240,36 +208,30 @@ def testGuiMenu_EditFormat(qtbot, monkeypatch, nwGUI, nwLipsum): nwGUI.mainMenu.aFmtAlignLeft.activate(QAction.Trigger) fmtStr = "A single, short paragraph. <<" assert nwGUI.docEditor.getText()[:29] == fmtStr - qtbot.wait(stepDelay) # Right Align nwGUI.mainMenu.aFmtAlignRight.activate(QAction.Trigger) fmtStr = ">> A single, short paragraph." assert nwGUI.docEditor.getText()[:29] == fmtStr - qtbot.wait(stepDelay) # Centre Align nwGUI.mainMenu.aFmtAlignCentre.activate(QAction.Trigger) fmtStr = ">> A single, short paragraph. <<" assert nwGUI.docEditor.getText()[:32] == fmtStr - qtbot.wait(stepDelay) # Left Indent nwGUI.mainMenu.aFmtIndentLeft.activate(QAction.Trigger) fmtStr = "> A single, short paragraph." assert nwGUI.docEditor.getText()[:28] == fmtStr - qtbot.wait(stepDelay) # Right Indent nwGUI.mainMenu.aFmtIndentRight.activate(QAction.Trigger) fmtStr = "> A single, short paragraph. <" assert nwGUI.docEditor.getText()[:30] == fmtStr - qtbot.wait(stepDelay) # No Format nwGUI.mainMenu.aFmtNoFormat.activate(QAction.Trigger) assert nwGUI.docEditor.getText()[:30] == cleanText - qtbot.wait(stepDelay) # Other Checks @@ -368,21 +330,17 @@ def testGuiMenu_EditFormat(qtbot, monkeypatch, nwGUI, nwLipsum): "Also text with \"double\" quotes which are \"less tricky\".\n\n" ) - # qtbot.stopForInteraction() + # qtbot.stop() # END Test testGuiMenu_EditFormat @pytest.mark.gui -def testGuiMenu_ContextMenus(qtbot, monkeypatch, nwGUI, nwLipsum): +def testGuiMenu_ContextMenus(qtbot, nwGUI, nwLipsum): """Test the context menus. """ - # Block message box - monkeypatch.setattr(QMessageBox, "question", lambda *a: QMessageBox.Yes) - assert nwGUI.openProject(nwLipsum) assert nwGUI.openDocument("4c4f28287af27") - qtbot.wait(stepDelay) # Editor Context Menu theCursor = nwGUI.docEditor.textCursor() @@ -452,7 +410,7 @@ def testGuiMenu_ContextMenus(qtbot, monkeypatch, nwGUI, nwLipsum): assert nwGUI.docViewer.docHeader.backButton.isEnabled() assert not nwGUI.docViewer.docHeader.forwardButton.isEnabled() - # qtbot.stopForInteraction() + # qtbot.stop() # END Test testGuiMenu_ContextMenus @@ -461,10 +419,6 @@ def testGuiMenu_ContextMenus(qtbot, monkeypatch, nwGUI, nwLipsum): def testGuiMenu_Insert(qtbot, monkeypatch, nwGUI, fncDir, fncProj, mockRnd): """Test the Insert menu. """ - # Block message box - monkeypatch.setattr(QMessageBox, "question", lambda *a: QMessageBox.Yes) - monkeypatch.setattr(QMessageBox, "critical", lambda *a: QMessageBox.Yes) - buildTestProject(nwGUI, fncProj) assert nwGUI.projView.projTree._getTreeItem(C.hSceneDoc) is not None @@ -482,7 +436,7 @@ def testGuiMenu_Insert(qtbot, monkeypatch, nwGUI, fncDir, fncProj, mockRnd): assert nwGUI.docEditor.insertText(None) is False assert nwGUI.docEditor.isEmpty() - # qtbot.stopForInteraction() + # qtbot.stop() # Check Menu Entries nwGUI.mainMenu.aInsENDash.activate(QAction.Trigger) @@ -676,12 +630,12 @@ def testGuiMenu_Insert(qtbot, monkeypatch, nwGUI, fncDir, fncProj, mockRnd): assert nwGUI.docEditor.getText() == "Bar" # The document isn't empty, so the message box should pop - monkeypatch.setattr(QMessageBox, "question", lambda *a, **k: QMessageBox.No) - assert not nwGUI.importDocument() - assert nwGUI.docEditor.getText() == "Bar" + with monkeypatch.context() as mp: + mp.setattr(QMessageBox, "question", lambda *a, **k: QMessageBox.No) + assert not nwGUI.importDocument() + assert nwGUI.docEditor.getText() == "Bar" # Finally, accept the replaced text, this time we use the menu entry to trigger it - monkeypatch.setattr(QMessageBox, "question", lambda *a, **k: QMessageBox.Yes) nwGUI.mainMenu.aImportFile.activate(QAction.Trigger) assert nwGUI.docEditor.getText() == "Foo" @@ -705,6 +659,6 @@ def testGuiMenu_Insert(qtbot, monkeypatch, nwGUI, fncDir, fncProj, mockRnd): assert theBits[0] == "The currently open file is saved in:" assert theBits[1] == os.path.join(fncProj, "content", "000000000000f.nwd") - # qtbot.stopForInteraction() + # qtbot.stop() # END Test testGuiMenu_Insert diff --git a/tests/test_gui/test_gui_noveltree.py b/tests/test_gui/test_gui_noveltree.py index e38d5555..4ec79000 100644 --- a/tests/test_gui/test_gui_noveltree.py +++ b/tests/test_gui/test_gui_noveltree.py @@ -26,7 +26,7 @@ from tools import C, buildTestProject, writeFile from PyQt5.QtGui import QFocusEvent from PyQt5.QtCore import Qt, QEvent -from PyQt5.QtWidgets import QMessageBox, QToolTip +from PyQt5.QtWidgets import QToolTip from novelwriter.enum import nwWidget, nwItemType from novelwriter.gui.noveltree import NovelTreeColumn @@ -37,9 +37,6 @@ from novelwriter.dialogs.editlabel import GuiEditLabel def testGuiNovelTree_TreeItems(qtbot, monkeypatch, nwGUI, fncProj, mockRnd): """Test navigating the novel tree. """ - # Block message box - monkeypatch.setattr(QMessageBox, "question", lambda *a: QMessageBox.Yes) - monkeypatch.setattr(QMessageBox, "information", lambda *a: QMessageBox.Yes) monkeypatch.setattr(GuiEditLabel, "getLabel", lambda *a, text: (text, True)) buildTestProject(nwGUI, fncProj) diff --git a/tests/test_gui/test_gui_outline.py b/tests/test_gui/test_gui_outline.py index 457be313..91467683 100644 --- a/tests/test_gui/test_gui_outline.py +++ b/tests/test_gui/test_gui_outline.py @@ -26,7 +26,7 @@ import pytest from tools import buildTestProject, writeFile from PyQt5.QtCore import Qt -from PyQt5.QtWidgets import QWidget, QMessageBox, QAction +from PyQt5.QtWidgets import QWidget, QAction from novelwriter.enum import nwItemClass, nwOutline, nwView @@ -35,10 +35,6 @@ from novelwriter.enum import nwItemClass, nwOutline, nwView def testGuiOutline_Main(qtbot, monkeypatch, nwGUI, fncDir): """Test the outline view. """ - # Block message box - monkeypatch.setattr(QMessageBox, "question", lambda *a: QMessageBox.Yes) - monkeypatch.setattr(QMessageBox, "information", lambda *a: QMessageBox.Yes) - # Create a project prjDir = os.path.join(fncDir, "project") buildTestProject(nwGUI, prjDir) @@ -156,13 +152,9 @@ def testGuiOutline_Main(qtbot, monkeypatch, nwGUI, fncDir): @pytest.mark.gui -def testGuiOutline_Content(qtbot, monkeypatch, nwGUI, nwLipsum): +def testGuiOutline_Content(qtbot, nwGUI, nwLipsum): """Test the outline view. """ - # Block message box - monkeypatch.setattr(QMessageBox, "question", lambda *a: QMessageBox.Yes) - monkeypatch.setattr(QMessageBox, "information", lambda *a: QMessageBox.Yes) - assert nwGUI.openProject(nwLipsum) nwGUI.mainConf.lastPath = nwLipsum diff --git a/tests/test_gui/test_gui_projtree.py b/tests/test_gui/test_gui_projtree.py index ecf78341..02efbb9a 100644 --- a/tests/test_gui/test_gui_projtree.py +++ b/tests/test_gui/test_gui_projtree.py @@ -40,11 +40,6 @@ from novelwriter.dialogs.editlabel import GuiEditLabel def testGuiProjTree_NewItems(qtbot, caplog, monkeypatch, nwGUI, fncDir, mockRnd): """Test adding and removing items from the project tree. """ - # Block message box - monkeypatch.setattr(QMessageBox, "warning", lambda *a: QMessageBox.Yes) - monkeypatch.setattr(QMessageBox, "critical", lambda *a: QMessageBox.Yes) - monkeypatch.setattr(QMessageBox, "question", lambda *a: QMessageBox.Yes) - monkeypatch.setattr(QMessageBox, "information", lambda *a: QMessageBox.Yes) monkeypatch.setattr(GuiEditLabel, "getLabel", lambda *a, text: (text, True)) projView = nwGUI.projView @@ -177,11 +172,6 @@ def testGuiProjTree_NewItems(qtbot, caplog, monkeypatch, nwGUI, fncDir, mockRnd) def testGuiProjTree_MoveItems(qtbot, monkeypatch, nwGUI, fncDir, mockRnd): """Test adding and removing items from the project tree. """ - # Block message box - monkeypatch.setattr(QMessageBox, "warning", lambda *a: QMessageBox.Yes) - monkeypatch.setattr(QMessageBox, "critical", lambda *a: QMessageBox.Yes) - monkeypatch.setattr(QMessageBox, "question", lambda *a: QMessageBox.Yes) - monkeypatch.setattr(QMessageBox, "information", lambda *a: QMessageBox.Yes) monkeypatch.setattr(GuiEditLabel, "getLabel", lambda *a, text: (text, True)) projView = nwGUI.projView @@ -283,7 +273,7 @@ def testGuiProjTree_MoveItems(qtbot, monkeypatch, nwGUI, fncDir, mockRnd): assert nwGUI.theProject.tree._treeOrder.index(C.hNovelRoot) == 0 # Clean up - # qtbot.stopForInteraction() + # qtbot.stop() nwGUI.closeProject() # END Test testGuiProjTree_MoveItems @@ -293,11 +283,6 @@ def testGuiProjTree_MoveItems(qtbot, monkeypatch, nwGUI, fncDir, mockRnd): def testGuiProjTree_RequestDeleteItem(qtbot, caplog, monkeypatch, nwGUI, fncDir, mockRnd): """Test external requests for removing items from project tree. """ - # Block message box - monkeypatch.setattr(QMessageBox, "warning", lambda *a: QMessageBox.Yes) - monkeypatch.setattr(QMessageBox, "critical", lambda *a: QMessageBox.Yes) - monkeypatch.setattr(QMessageBox, "question", lambda *a: QMessageBox.Yes) - monkeypatch.setattr(QMessageBox, "information", lambda *a: QMessageBox.Yes) monkeypatch.setattr(GuiEditLabel, "getLabel", lambda *a, text: (text, True)) projView = nwGUI.projView @@ -382,11 +367,6 @@ def testGuiProjTree_RequestDeleteItem(qtbot, caplog, monkeypatch, nwGUI, fncDir, def testGuiProjTree_MoveItemToTrash(qtbot, caplog, monkeypatch, nwGUI, fncDir, mockRnd): """Test moving items to Trash. """ - # Block message box - monkeypatch.setattr(QMessageBox, "warning", lambda *a: QMessageBox.Yes) - monkeypatch.setattr(QMessageBox, "critical", lambda *a: QMessageBox.Yes) - monkeypatch.setattr(QMessageBox, "question", lambda *a: QMessageBox.Yes) - monkeypatch.setattr(QMessageBox, "information", lambda *a: QMessageBox.Yes) monkeypatch.setattr(GuiEditLabel, "getLabel", lambda *a, text: (text, True)) theProject = nwGUI.theProject @@ -441,11 +421,6 @@ def testGuiProjTree_MoveItemToTrash(qtbot, caplog, monkeypatch, nwGUI, fncDir, m def testGuiProjTree_PermanentlyDeleteItem(qtbot, caplog, monkeypatch, nwGUI, fncDir, mockRnd): """Test permanently deleting items. """ - # Block message box - monkeypatch.setattr(QMessageBox, "warning", lambda *a: QMessageBox.Yes) - monkeypatch.setattr(QMessageBox, "critical", lambda *a: QMessageBox.Yes) - monkeypatch.setattr(QMessageBox, "question", lambda *a: QMessageBox.Yes) - monkeypatch.setattr(QMessageBox, "information", lambda *a: QMessageBox.Yes) monkeypatch.setattr(GuiEditLabel, "getLabel", lambda *a, text: (text, True)) theProject = nwGUI.theProject @@ -499,11 +474,6 @@ def testGuiProjTree_PermanentlyDeleteItem(qtbot, caplog, monkeypatch, nwGUI, fnc def testGuiProjTree_EmptyTrash(qtbot, caplog, monkeypatch, nwGUI, fncDir, mockRnd): """Test emptying Trash. """ - # Block message box - monkeypatch.setattr(QMessageBox, "warning", lambda *a: QMessageBox.Yes) - monkeypatch.setattr(QMessageBox, "critical", lambda *a: QMessageBox.Yes) - monkeypatch.setattr(QMessageBox, "question", lambda *a: QMessageBox.Yes) - monkeypatch.setattr(QMessageBox, "information", lambda *a: QMessageBox.Yes) monkeypatch.setattr(GuiEditLabel, "getLabel", lambda *a, text: (text, True)) theProject = nwGUI.theProject @@ -559,11 +529,6 @@ def testGuiProjTree_ContextMenu(qtbot, monkeypatch, nwGUI, fncDir, mockRnd): """Test the building of the project tree context menu. All this does is test that the menu builds. It doesn't open the actual menu, """ - # Block message box - monkeypatch.setattr(QMessageBox, "warning", lambda *a: QMessageBox.Yes) - monkeypatch.setattr(QMessageBox, "critical", lambda *a: QMessageBox.Yes) - monkeypatch.setattr(QMessageBox, "question", lambda *a: QMessageBox.Yes) - monkeypatch.setattr(QMessageBox, "information", lambda *a: QMessageBox.Yes) monkeypatch.setattr(GuiEditLabel, "getLabel", lambda *a, text: (text, True)) monkeypatch.setattr(QMenu, "exec_", lambda *a: None) @@ -684,12 +649,6 @@ def testGuiProjTree_MergeDocuments(qtbot, monkeypatch, nwGUI, fncDir, mockRnd, i """ mergeData = {} - # Block message box - monkeypatch.setattr(QMessageBox, "warning", lambda *a: QMessageBox.Yes) - monkeypatch.setattr(QMessageBox, "critical", lambda *a: QMessageBox.Yes) - monkeypatch.setattr(QMessageBox, "question", lambda *a: QMessageBox.Yes) - monkeypatch.setattr(QMessageBox, "information", lambda *a: QMessageBox.Yes) - monkeypatch.setattr(GuiDocMerge, "__init__", lambda *a: None) monkeypatch.setattr(GuiDocMerge, "exec_", lambda *a: None) monkeypatch.setattr(GuiDocMerge, "result", lambda *a: QDialog.Accepted) @@ -794,12 +753,6 @@ def testGuiProjTree_SplitDocument(qtbot, monkeypatch, nwGUI, fncDir, mockRnd, ip splitData = {} splitText = [] - # Block message box - monkeypatch.setattr(QMessageBox, "warning", lambda *a: QMessageBox.Yes) - monkeypatch.setattr(QMessageBox, "critical", lambda *a: QMessageBox.Yes) - monkeypatch.setattr(QMessageBox, "question", lambda *a: QMessageBox.Yes) - monkeypatch.setattr(QMessageBox, "information", lambda *a: QMessageBox.Yes) - monkeypatch.setattr(GuiDocSplit, "__init__", lambda *a: None) monkeypatch.setattr(GuiDocSplit, "exec_", lambda *a: None) monkeypatch.setattr(GuiDocSplit, "result", lambda *a: QDialog.Accepted) @@ -907,12 +860,6 @@ def testGuiProjTree_Other(qtbot, monkeypatch, nwGUI, fncDir, mockRnd): """Test various parts of the project tree class not covered by other tests. """ - # Block message box - monkeypatch.setattr(QMessageBox, "warning", lambda *a: QMessageBox.Yes) - monkeypatch.setattr(QMessageBox, "critical", lambda *a: QMessageBox.Yes) - monkeypatch.setattr(QMessageBox, "question", lambda *a: QMessageBox.Yes) - monkeypatch.setattr(QMessageBox, "information", lambda *a: QMessageBox.Yes) - # Create a project prjDir = os.path.join(fncDir, "project") buildTestProject(nwGUI, prjDir) diff --git a/tests/test_gui/test_gui_statusbar.py b/tests/test_gui/test_gui_statusbar.py index e2e9a5bb..30d8d861 100644 --- a/tests/test_gui/test_gui_statusbar.py +++ b/tests/test_gui/test_gui_statusbar.py @@ -24,18 +24,14 @@ import pytest from tools import C, buildTestProject -from PyQt5.QtWidgets import QMessageBox - from novelwriter.enum import nwState from novelwriter.core.document import NWDoc @pytest.mark.gui -def testGuiStatusBar_Main(qtbot, monkeypatch, nwGUI, fncProj, mockRnd): +def testGuiStatusBar_Main(qtbot, nwGUI, fncProj, mockRnd): """Test the the various features of the status bar. """ - monkeypatch.setattr(QMessageBox, "question", lambda *a: QMessageBox.Yes) - buildTestProject(nwGUI, fncProj) cHandle = nwGUI.theProject.newFile("A Note", C.hCharRoot) newDoc = NWDoc(nwGUI.theProject, cHandle) diff --git a/tests/test_gui/test_gui_theme.py b/tests/test_gui/test_gui_theme.py index 6cd82e64..3c6503cb 100644 --- a/tests/test_gui/test_gui_theme.py +++ b/tests/test_gui/test_gui_theme.py @@ -29,20 +29,16 @@ from mock import causeOSError from tools import writeFile from PyQt5.QtGui import QPalette -from PyQt5.QtWidgets import QApplication, QMessageBox +from PyQt5.QtWidgets import QApplication from novelwriter.config import Config from novelwriter.gui.theme import GuiTheme @pytest.mark.gui -def testGuiTheme_Main(qtbot, monkeypatch, nwGUI, fncDir): +def testGuiTheme_Main(qtbot, nwGUI, fncDir): """Test the theme class init. """ - monkeypatch.setattr(QMessageBox, "information", lambda *a: QMessageBox.Yes) - monkeypatch.setattr(QMessageBox, "question", lambda *a: QMessageBox.Yes) - monkeypatch.setattr(QMessageBox, "warning", lambda *a: QMessageBox.Yes) - mainTheme: GuiTheme = nwGUI.mainTheme mainConf: Config = nwGUI.mainConf @@ -128,10 +124,6 @@ def testGuiTheme_Main(qtbot, monkeypatch, nwGUI, fncDir): def testGuiTheme_Themes(qtbot, monkeypatch, nwGUI, fncDir): """Test the theme class init. """ - monkeypatch.setattr(QMessageBox, "information", lambda *a: QMessageBox.Yes) - monkeypatch.setattr(QMessageBox, "question", lambda *a: QMessageBox.Yes) - monkeypatch.setattr(QMessageBox, "warning", lambda *a: QMessageBox.Yes) - mainTheme: GuiTheme = nwGUI.mainTheme mainConf: Config = nwGUI.mainConf diff --git a/tests/test_tools/test_tools_build.py b/tests/test_tools/test_tools_build.py index 40e30dea..e0088a1c 100644 --- a/tests/test_tools/test_tools_build.py +++ b/tests/test_tools/test_tools_build.py @@ -26,22 +26,16 @@ from shutil import copyfile from tools import cmpFiles, getGuiItem from PyQt5.QtCore import Qt -from PyQt5.QtWidgets import QAction, QMessageBox, QFileDialog +from PyQt5.QtWidgets import QAction, QFileDialog from novelwriter.tools import GuiBuildNovel -keyDelay = 2 -typeDelay = 1 -stepDelay = 20 - @pytest.mark.gui def testToolBuild_Main(qtbot, monkeypatch, nwGUI, nwLipsum, refDir, outDir): """Test the build tool. """ # Block message box - monkeypatch.setattr(QMessageBox, "question", lambda *a: QMessageBox.Yes) - monkeypatch.setattr(QMessageBox, "information", lambda *a: QMessageBox.Yes) monkeypatch.setattr(QFileDialog, "getSaveFileName", lambda a, b, c, **k: (c, None)) # Check that we cannot open when there is no project @@ -121,27 +115,17 @@ def testToolBuild_Main(qtbot, monkeypatch, nwGUI, nwLipsum, refDir, outDir): # Change Title Formats and Flip Switches nwBuild.fmtChapter.setText(r"Chapter %chw%: %title%") - qtbot.wait(stepDelay) nwBuild.fmtScene.setText(r"Scene %ch%.%sc%: %title%") - qtbot.wait(stepDelay) nwBuild.fmtSection.setText(r"%ch%.%sc%.1: %title%") - qtbot.wait(stepDelay) qtbot.mouseClick(nwBuild.justifyText, Qt.LeftButton) - qtbot.wait(stepDelay) qtbot.mouseClick(nwBuild.includeSynopsis, Qt.LeftButton) - qtbot.wait(stepDelay) qtbot.mouseClick(nwBuild.includeComments, Qt.LeftButton) - qtbot.wait(stepDelay) qtbot.mouseClick(nwBuild.includeKeywords, Qt.LeftButton) - qtbot.wait(stepDelay) qtbot.mouseClick(nwBuild.replaceUCode, Qt.LeftButton) - qtbot.wait(stepDelay) qtbot.mouseClick(nwBuild.noteFiles, Qt.LeftButton) - qtbot.wait(stepDelay) qtbot.mouseClick(nwBuild.ignoreFlag, Qt.LeftButton) - qtbot.wait(stepDelay) qtbot.mouseClick(nwBuild.buildNovel, Qt.LeftButton) @@ -175,7 +159,6 @@ def testToolBuild_Main(qtbot, monkeypatch, nwGUI, nwLipsum, refDir, outDir): # Replace Tabs with Spaces qtbot.mouseClick(nwBuild.replaceTabs, Qt.LeftButton) - qtbot.wait(stepDelay) qtbot.mouseClick(nwBuild.buildNovel, Qt.LeftButton) @@ -210,20 +193,13 @@ def testToolBuild_Main(qtbot, monkeypatch, nwGUI, nwLipsum, refDir, outDir): # Putline Mode nwBuild.fmtChapter.setText(r"Chapter %chw%: %title%") - qtbot.wait(stepDelay) nwBuild.fmtScene.setText(r"Scene %sca%: %title%") - qtbot.wait(stepDelay) nwBuild.fmtSection.setText(r"Section: %title%") - qtbot.wait(stepDelay) qtbot.mouseClick(nwBuild.includeComments, Qt.LeftButton) - qtbot.wait(stepDelay) qtbot.mouseClick(nwBuild.noteFiles, Qt.LeftButton) - qtbot.wait(stepDelay) qtbot.mouseClick(nwBuild.ignoreFlag, Qt.LeftButton) - qtbot.wait(stepDelay) qtbot.mouseClick(nwBuild.includeBody, Qt.LeftButton) - qtbot.wait(stepDelay) qtbot.mouseClick(nwBuild.buildNovel, Qt.LeftButton) @@ -287,6 +263,6 @@ def testToolBuild_Main(qtbot, monkeypatch, nwGUI, nwLipsum, refDir, outDir): nwBuild._doClose() - # qtbot.stopForInteraction() + # qtbot.stop() # END Test testToolBuild_Main diff --git a/tests/test_tools/test_tools_lipsum.py b/tests/test_tools/test_tools_lipsum.py index de70a428..0bd07f97 100644 --- a/tests/test_tools/test_tools_lipsum.py +++ b/tests/test_tools/test_tools_lipsum.py @@ -23,18 +23,15 @@ import pytest from tools import C, getGuiItem, buildTestProject -from PyQt5.QtWidgets import QAction, QMessageBox +from PyQt5.QtWidgets import QAction from novelwriter.tools import GuiLipsum @pytest.mark.gui -def testToolLipsum_Main(qtbot, monkeypatch, nwGUI, fncProj, mockRnd): +def testToolLipsum_Main(qtbot, nwGUI, fncProj, mockRnd): """Test the Lorem Ipsum tool. """ - # Block message box - monkeypatch.setattr(QMessageBox, "question", lambda *a: QMessageBox.Yes) - # Check that we cannot open when there is no project nwGUI.mainMenu.aLipsumText.activate(QAction.Trigger) assert getGuiItem("GuiLipsum") is None @@ -70,6 +67,6 @@ def testToolLipsum_Main(qtbot, monkeypatch, nwGUI, fncProj, mockRnd): # Close nwLipsum._doClose() - # qtbot.stopForInteraction() + # qtbot.stop() # END Test testToolLipsum_Main diff --git a/tests/test_tools/test_tools_projwizard.py b/tests/test_tools/test_tools_projwizard.py index afe720ff..a2facf6e 100644 --- a/tests/test_tools/test_tools_projwizard.py +++ b/tests/test_tools/test_tools_projwizard.py @@ -26,7 +26,7 @@ import pytest from tools import getGuiItem from PyQt5.QtCore import Qt -from PyQt5.QtWidgets import QFileDialog, QWizard, QMessageBox, QDialog +from PyQt5.QtWidgets import QFileDialog, QWizard, QDialog from novelwriter.enum import nwItemClass from novelwriter.tools.projwizard import ( @@ -34,10 +34,6 @@ from novelwriter.tools.projwizard import ( ProjWizardPopulatePage, ProjWizardCustomPage, ProjWizardFinalPage ) -keyDelay = 2 -typeDelay = 1 -stepDelay = 20 - @pytest.mark.gui @pytest.mark.skipif(sys.platform.startswith("darwin"), reason="Not running on Darwin") @@ -45,13 +41,8 @@ def testToolProjectWizard_Handling(qtbot, monkeypatch, nwGUI, nwMinimal): """Test the launch of the project wizard. Disabled for macOS because the test segfaults on QWizard.show() """ - # Block message box - monkeypatch.setattr(QMessageBox, "question", lambda *a: QMessageBox.Yes) - monkeypatch.setattr(QMessageBox, "critical", lambda *a: QMessageBox.Yes) - - ## - # Test New Project Function - ## + # Test New Project Function + # ======================== # New with a project open should cause an error assert nwGUI.openProject(nwMinimal) @@ -73,9 +64,8 @@ def testToolProjectWizard_Handling(qtbot, monkeypatch, nwGUI, nwMinimal): mp.setattr(nwGUI, "showNewProjectDialog", lambda *a: {"projPath": nwMinimal}) assert nwGUI.newProject() is False - ## - # Test the Wizard Launching - ## + # Test the Wizard Launching + # ========================= nwGUI.mainConf.lastPath = " " monkeypatch.setattr(GuiProjectWizard, "exec_", lambda *a: None) @@ -87,7 +77,6 @@ def testToolProjectWizard_Handling(qtbot, monkeypatch, nwGUI, nwMinimal): assert isinstance(nwWiz, GuiProjectWizard) nwWiz.show() - qtbot.wait(stepDelay) qtbot.mouseClick(nwWiz.button(QWizard.CancelButton), Qt.LeftButton) assert result is None @@ -100,7 +89,7 @@ def testToolProjectWizard_Handling(qtbot, monkeypatch, nwGUI, nwMinimal): nwWiz.reject() nwWiz.close() - # qtbot.stopForInteraction() + # qtbot.stop() # END Test testToolProjectWizard_Handling @@ -111,14 +100,12 @@ def testToolProjectWizard_Handling(qtbot, monkeypatch, nwGUI, nwMinimal): def testToolProjectWizard_Run(qtbot, monkeypatch, nwGUI, fncDir, prjType): """Test the new project wizard with a set of selection scenarios. """ - monkeypatch.setattr(QMessageBox, "question", lambda *a: QMessageBox.Yes) - monkeypatch.setattr(QMessageBox, "critical", lambda *a: QMessageBox.Yes) monkeypatch.setattr(GuiProjectWizard, "exec_", lambda *a: None) nwGUI.mainConf.lastPath = " " nwWiz = GuiProjectWizard(nwGUI) nwWiz.show() - qtbot.wait(stepDelay) + qtbot.addWidget(nwWiz) # Intro Page # ========== @@ -134,7 +121,6 @@ def testToolProjectWizard_Run(qtbot, monkeypatch, nwGUI, fncDir, prjType): # Setting projName should activate the button assert nwWiz.button(QWizard.NextButton).isEnabled() - qtbot.wait(stepDelay) qtbot.mouseClick(nwWiz.button(QWizard.NextButton), Qt.LeftButton) # Folder Page @@ -173,7 +159,6 @@ def testToolProjectWizard_Run(qtbot, monkeypatch, nwGUI, fncDir, prjType): # Setting projPath should activate the button assert nwWiz.button(QWizard.NextButton).isEnabled() - qtbot.wait(stepDelay) qtbot.mouseClick(nwWiz.button(QWizard.NextButton), Qt.LeftButton) # Populate Page @@ -183,7 +168,6 @@ def testToolProjectWizard_Run(qtbot, monkeypatch, nwGUI, fncDir, prjType): assert isinstance(popPage, ProjWizardPopulatePage) assert nwWiz.button(QWizard.NextButton).isEnabled() - qtbot.wait(stepDelay) if prjType.startswith("minimal"): popPage.popMinimal.setChecked(True) elif prjType.startswith("custom"): @@ -191,7 +175,6 @@ def testToolProjectWizard_Run(qtbot, monkeypatch, nwGUI, fncDir, prjType): elif prjType.startswith("sample"): popPage.popSample.setChecked(True) - qtbot.wait(stepDelay) qtbot.mouseClick(nwWiz.button(QWizard.NextButton), Qt.LeftButton) # Custom Page @@ -219,7 +202,6 @@ def testToolProjectWizard_Run(qtbot, monkeypatch, nwGUI, fncDir, prjType): customPage.numChapters.setValue(0) customPage.numScenes.setValue(10) - qtbot.wait(stepDelay) qtbot.mouseClick(nwWiz.button(QWizard.NextButton), Qt.LeftButton) # Final Page @@ -264,6 +246,6 @@ def testToolProjectWizard_Run(qtbot, monkeypatch, nwGUI, fncDir, prjType): nwWiz.reject() nwWiz.close() - # qtbot.stopForInteraction() + # qtbot.stop() # END Test testToolProjectWizard_Run diff --git a/tests/test_tools/test_tools_writingstats.py b/tests/test_tools/test_tools_writingstats.py index b7358b20..a4ba1963 100644 --- a/tests/test_tools/test_tools_writingstats.py +++ b/tests/test_tools/test_tools_writingstats.py @@ -27,26 +27,16 @@ from mock import causeOSError from tools import getGuiItem, writeFile, buildTestProject from PyQt5.QtCore import Qt -from PyQt5.QtWidgets import QAction, QFileDialog, QMessageBox +from PyQt5.QtWidgets import QAction, QFileDialog from novelwriter.tools import GuiWritingStats from novelwriter.constants import nwFiles -keyDelay = 2 -typeDelay = 1 -stepDelay = 20 - @pytest.mark.gui def testToolWritingStats_Main(qtbot, monkeypatch, nwGUI, fncDir, fncProj): """Test the full writing stats tool. """ - # Block message box - monkeypatch.setattr(QMessageBox, "question", lambda *a: QMessageBox.Yes) - monkeypatch.setattr(QMessageBox, "information", lambda *a: QMessageBox.Yes) - monkeypatch.setattr(QMessageBox, "warning", lambda *a: QMessageBox.Yes) - monkeypatch.setattr(QMessageBox, "critical", lambda *a: QMessageBox.Yes) - # Create a project to work on buildTestProject(nwGUI, fncProj) qtbot.wait(100) @@ -60,7 +50,6 @@ def testToolWritingStats_Main(qtbot, monkeypatch, nwGUI, fncDir, fncProj): sessLog = getGuiItem("GuiWritingStats") assert isinstance(sessLog, GuiWritingStats) - qtbot.wait(stepDelay) # Test Loading # ============ @@ -186,9 +175,7 @@ def testToolWritingStats_Main(qtbot, monkeypatch, nwGUI, fncDir, fncProj): # No Novel Files qtbot.mouseClick(sessLog.incNovel, Qt.LeftButton) - qtbot.wait(stepDelay) assert sessLog._saveData(sessLog.FMT_JSON) - qtbot.wait(stepDelay) jsonStats = os.path.join(fncDir, "sessionStats.json") with open(jsonStats, mode="r", encoding="utf-8") as inFile: @@ -234,9 +221,7 @@ def testToolWritingStats_Main(qtbot, monkeypatch, nwGUI, fncDir, fncProj): # No Note Files qtbot.mouseClick(sessLog.incNovel, Qt.LeftButton) qtbot.mouseClick(sessLog.incNotes, Qt.LeftButton) - qtbot.wait(stepDelay) assert sessLog._saveData(sessLog.FMT_JSON) - qtbot.wait(stepDelay) jsonStats = os.path.join(fncDir, "sessionStats.json") with open(jsonStats, mode="r", encoding="utf-8") as inFile: @@ -282,11 +267,9 @@ def testToolWritingStats_Main(qtbot, monkeypatch, nwGUI, fncDir, fncProj): # No Negative Entries qtbot.mouseClick(sessLog.incNotes, Qt.LeftButton) qtbot.mouseClick(sessLog.hideNegative, Qt.LeftButton) - qtbot.wait(stepDelay) assert sessLog._saveData(sessLog.FMT_JSON) - qtbot.wait(stepDelay) - # qtbot.stopForInteraction() + # qtbot.stop() jsonStats = os.path.join(fncDir, "sessionStats.json") with open(jsonStats, mode="r", encoding="utf-8") as inFile: @@ -316,9 +299,7 @@ def testToolWritingStats_Main(qtbot, monkeypatch, nwGUI, fncDir, fncProj): # Un-hide Zero Entries qtbot.mouseClick(sessLog.hideNegative, Qt.LeftButton) qtbot.mouseClick(sessLog.hideZeros, Qt.LeftButton) - qtbot.wait(stepDelay) assert sessLog._saveData(sessLog.FMT_JSON) - qtbot.wait(stepDelay) jsonStats = os.path.join(fncDir, "sessionStats.json") with open(jsonStats, mode="r", encoding="utf-8") as inFile: @@ -371,9 +352,7 @@ def testToolWritingStats_Main(qtbot, monkeypatch, nwGUI, fncDir, fncProj): # Group by Day qtbot.mouseClick(sessLog.groupByDay, Qt.LeftButton) - qtbot.wait(stepDelay) assert sessLog._saveData(sessLog.FMT_JSON) - qtbot.wait(stepDelay) jsonStats = os.path.join(fncDir, "sessionStats.json") with open(jsonStats, mode="r", encoding="utf-8") as inFile: @@ -422,10 +401,9 @@ def testToolWritingStats_Main(qtbot, monkeypatch, nwGUI, fncDir, fncProj): assert not sessLog._loadLogFile() assert not sessLog._saveData(sessLog.FMT_CSV) - # qtbot.stopForInteraction() + # qtbot.stop() sessLog._doClose() assert nwGUI.closeProject() - qtbot.wait(stepDelay) # END Test testToolWritingStats_Main From f76fc9549440702792ddbbc887a78b9a728a861a Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Fri, 28 Oct 2022 23:26:25 +0200 Subject: [PATCH 18/36] Complete coverage of themes classes --- novelwriter/gui/theme.py | 8 +- tests/test_gui/test_gui_theme.py | 267 ++++++++++++++++++++++++++++++- 2 files changed, 266 insertions(+), 9 deletions(-) diff --git a/novelwriter/gui/theme.py b/novelwriter/gui/theme.py index da03b291..cb0e4bab 100644 --- a/novelwriter/gui/theme.py +++ b/novelwriter/gui/theme.py @@ -244,7 +244,7 @@ class GuiTheme: self.statSaved = self._parseColour(confParser, cnfSec, "statussaved") # Icons - self.iconCache.updateTheme(self.themeIcons) + self.iconCache.loadTheme(self.themeIcons) # CSS File cssData = readTextFile(themeFile[:-5]+".qss") @@ -498,7 +498,7 @@ class GuiIcons: # Actions ## - def updateTheme(self, iconTheme): + def loadTheme(self, iconTheme): """Update the theme map. This is more of an init, since many of the GUI icons cannot really be replaced without writing specific update functions for the classes where they're used. @@ -585,7 +585,7 @@ class GuiIcons: return QPixmap() if not os.path.isfile(imgPath): - logger.error("Asset '%s' not found", self.IMAGE_MAP[decoKey]) + logger.error("Asset not found: %s", imgPath) return QPixmap() theDeco = QPixmap(imgPath) @@ -598,7 +598,7 @@ class GuiIcons: return theDeco - def getIcon(self, iconKey, iconSize=None): + def getIcon(self, iconKey): """Return an icon from the icon buffer. If it doesn't exist, return, load it, and if it still doesn't exist, return an empty icon. diff --git a/tests/test_gui/test_gui_theme.py b/tests/test_gui/test_gui_theme.py index 3c6503cb..cd16ff86 100644 --- a/tests/test_gui/test_gui_theme.py +++ b/tests/test_gui/test_gui_theme.py @@ -26,13 +26,15 @@ import pytest from configparser import ConfigParser from mock import causeOSError +from novelwriter.constants import nwLabels +from novelwriter.enum import nwItemClass, nwItemLayout, nwItemType from tools import writeFile -from PyQt5.QtGui import QPalette +from PyQt5.QtGui import QIcon, QPalette, QPixmap from PyQt5.QtWidgets import QApplication from novelwriter.config import Config -from novelwriter.gui.theme import GuiTheme +from novelwriter.gui.theme import GuiIcons, GuiTheme @pytest.mark.gui @@ -121,8 +123,8 @@ def testGuiTheme_Main(qtbot, nwGUI, fncDir): @pytest.mark.gui -def testGuiTheme_Themes(qtbot, monkeypatch, nwGUI, fncDir): - """Test the theme class init. +def testGuiTheme_Theme(qtbot, monkeypatch, nwGUI, fncDir): + """Test the theme part of the class. """ mainTheme: GuiTheme = nwGUI.mainTheme mainConf: Config = nwGUI.mainConf @@ -140,6 +142,11 @@ def testGuiTheme_Themes(qtbot, monkeypatch, nwGUI, fncDir): ) writeFile(os.path.join(fncDir, "themes", "default.qss"), "/* Stuff */") + # Block the reading of the files + with monkeypatch.context() as mp: + mp.setattr("builtins.open", causeOSError) + assert mainTheme.listThemes() == [] + # Load the theme info themesList = mainTheme.listThemes() assert themesList[0] == ("default_dark", "Default Dark Theme") @@ -186,4 +193,254 @@ def testGuiTheme_Themes(qtbot, monkeypatch, nwGUI, fncDir): # qtbot.stop() -# END Test testGuiTheme_Themes +# END Test testGuiTheme_Theme + + +@pytest.mark.gui +def testGuiTheme_Syntax(qtbot, monkeypatch, nwGUI, fncDir): + """Test the syntax part of the class. + """ + mainTheme: GuiTheme = nwGUI.mainTheme + mainConf: Config = nwGUI.mainConf + + # List Themes + # =========== + + shutil.copy( + os.path.join(mainConf.assetPath, "syntax", "default_dark.conf"), + os.path.join(fncDir, "syntax") + ) + shutil.copy( + os.path.join(mainConf.assetPath, "syntax", "default_light.conf"), + os.path.join(fncDir, "syntax") + ) + + # Block the reading of the files + with monkeypatch.context() as mp: + mp.setattr("builtins.open", causeOSError) + assert mainTheme.listThemes() == [] + + # Load the syntax info + syntaxList = mainTheme.listSyntax() + assert syntaxList[0] == ("default_dark", "Default Dark") + assert syntaxList[1] == ("default_light", "Default Light") + + # A second call should returned the cached list + assert mainTheme.listSyntax() == mainTheme._syntaxList + + # Check handling of broken theme settings + mainConf.guiSyntax = "not_a_syntax" + assert mainTheme.loadSyntax() is False + + # Check handling of unreadable file + mainConf.guiSyntax = "default_light" + with monkeypatch.context() as mp: + mp.setattr("builtins.open", causeOSError) + assert mainTheme.loadSyntax() is False + + # Load Default Light Syntax + # ========================= + + # Load the default syntax + mainConf.guiSyntax = "default_light" + assert mainTheme.loadSyntax() is True + + # Check some values + assert mainTheme.syntaxName == "Default Light" + assert mainTheme.colBack == [255, 255, 255] + assert mainTheme.colText == [0, 0, 0] + assert mainTheme.colLink == [0, 0, 200] + + # Load Default Dark Theme + # ======================= + + # Load the default syntax + mainConf.guiSyntax = "default_dark" + assert mainTheme.loadSyntax() is True + + # Check some values + assert mainTheme.syntaxName == "Default Dark" + assert mainTheme.colBack == [54, 54, 54] + assert mainTheme.colText == [199, 207, 208] + assert mainTheme.colLink == [184, 200, 0] + + # qtbot.stop() + +# END Test testGuiTheme_Syntax + + +@pytest.mark.gui +def testGuiTheme_Icons(qtbot, caplog, monkeypatch, nwGUI, fncDir): + """Test the icon cache class. + """ + iconCache: GuiIcons = nwGUI.mainTheme.iconCache + mainConf: Config = nwGUI.mainConf + + # Load Theme + # ========== + + # Invalid theme name + assert iconCache.loadTheme("not_a_theme") is False + + # Check handling of unreadable file + with monkeypatch.context() as mp: + mp.setattr("builtins.open", causeOSError) + assert iconCache.loadTheme("typicons_dark") is False + + # Load a broken theme file + iconsDir = os.path.join(fncDir, "icons") + os.mkdir(iconsDir) + os.mkdir(os.path.join(iconsDir, "testicons")) + writeFile(os.path.join(iconsDir, "testicons", "icons.conf"), ( + "[Main]\n" + "name = Test Icons\n" + "\n" + "[Map]\n" + "add = add.svg\n" + "stuff = stuff.svg\n" + )) + + assetPath = mainConf.assetPath + mainConf.assetPath = fncDir + + caplog.clear() + assert iconCache.loadTheme("testicons") is True + assert "Unknown icon name 'stuff' in config file" in caplog.text + assert "Icon file 'add.svg' not in theme folder" in caplog.text + + mainConf.assetPath = assetPath + + # Load working theme file + assert iconCache.loadTheme("typicons_dark") is True + assert "add" in iconCache._themeMap + + # Load Decorations + # ================ + + # Invalid name should return empty pixmap + qPix = iconCache.loadDecoration("stuff") + assert qPix.isNull() is True + + # Load an image + qPix = iconCache.loadDecoration("wiz-back") + assert qPix.isNull() is False + + # Fail finding the file + with monkeypatch.context() as mp: + mp.setattr("os.path.isfile", lambda *a: False) + qPix = iconCache.loadDecoration("wiz-back") + assert qPix.isNull() is True + + # Test image sizes + qPix = iconCache.loadDecoration("wiz-back", pxW=100, pxH=None) + assert qPix.isNull() is False + assert qPix.width() == 100 + assert qPix.height() > 100 + + qPix = iconCache.loadDecoration("wiz-back", pxW=None, pxH=100) + assert qPix.isNull() is False + assert qPix.width() < 100 + assert qPix.height() == 100 + + qPix = iconCache.loadDecoration("wiz-back", pxW=100, pxH=100) + assert qPix.isNull() is False + assert qPix.width() == 100 + assert qPix.height() == 100 + + # Load Icons + # ========== + + # Load an unknown icon + qIcon = iconCache.getIcon("stuff") + assert isinstance(qIcon, QIcon) + assert qIcon.isNull() is True + + # Load an icon, it is likelyu already cached + qIcon = iconCache.getIcon("add") + assert isinstance(qIcon, QIcon) + assert qIcon.isNull() is False + + # Load it as a pixmap with a size + qPix = iconCache.getPixmap("add", (50, 50)) + assert isinstance(qPix, QPixmap) + assert qPix.isNull() is False + assert qPix.width() == 50 + assert qPix.height() == 50 + + # Load app icon + qIcon = iconCache.getIcon("novelwriter") + assert isinstance(qIcon, QIcon) + assert qIcon.isNull() is False + + # Load mime icon + qIcon = iconCache.getIcon("proj_nwx") + assert isinstance(qIcon, QIcon) + assert qIcon.isNull() is False + + # Load Item Icons + # =============== + + # Root -> Not Null + assert iconCache.getItemIcon( + nwItemType.ROOT, nwItemClass.NOVEL, nwItemLayout.NO_LAYOUT, hLevel="H0" + ) == iconCache.getIcon(nwLabels.CLASS_ICON[nwItemClass.NOVEL]) + + # Folder -> Not Null + assert iconCache.getItemIcon( + nwItemType.FOLDER, nwItemClass.NOVEL, nwItemLayout.NO_LAYOUT, hLevel="H0" + ) == iconCache.getIcon("proj_folder") + + # Document H0 -> Not Null + assert iconCache.getItemIcon( + nwItemType.FILE, nwItemClass.NOVEL, nwItemLayout.NO_LAYOUT, hLevel="H0" + ) == iconCache.getIcon("proj_document") + + # Document H1 -> Not Null + assert iconCache.getItemIcon( + nwItemType.FILE, nwItemClass.NOVEL, nwItemLayout.DOCUMENT, hLevel="H1" + ) == iconCache.getIcon("proj_title") + + # Document H2 -> Not Null + assert iconCache.getItemIcon( + nwItemType.FILE, nwItemClass.NOVEL, nwItemLayout.DOCUMENT, hLevel="H2" + ) == iconCache.getIcon("proj_chapter") + + # Document H3 -> Not Null + assert iconCache.getItemIcon( + nwItemType.FILE, nwItemClass.NOVEL, nwItemLayout.DOCUMENT, hLevel="H3" + ) == iconCache.getIcon("proj_scene") + + # Document H4 -> Not Null + assert iconCache.getItemIcon( + nwItemType.FILE, nwItemClass.NOVEL, nwItemLayout.DOCUMENT, hLevel="H4" + ) == iconCache.getIcon("proj_section") + + # Document H5 -> Not Null + assert iconCache.getItemIcon( + nwItemType.FILE, nwItemClass.NOVEL, nwItemLayout.NO_LAYOUT, hLevel="H4" + ) == iconCache.getIcon("proj_document") + + # Note -> Not Null + assert iconCache.getItemIcon( + nwItemType.FILE, nwItemClass.NOVEL, nwItemLayout.NOTE, hLevel="H5" + ) == iconCache.getIcon("proj_note") + + # No Type -> Null + assert iconCache.getItemIcon( + nwItemType.NO_TYPE, nwItemClass.NOVEL, nwItemLayout.DOCUMENT, hLevel="H0" + ).isNull() is True + + # Header Decorations + # ================== + + assert iconCache.getHeaderDecoration(-1) == iconCache._headerDec[0] + assert iconCache.getHeaderDecoration(0) == iconCache._headerDec[0] + assert iconCache.getHeaderDecoration(1) == iconCache._headerDec[1] + assert iconCache.getHeaderDecoration(2) == iconCache._headerDec[2] + assert iconCache.getHeaderDecoration(3) == iconCache._headerDec[3] + assert iconCache.getHeaderDecoration(4) == iconCache._headerDec[4] + assert iconCache.getHeaderDecoration(5) == iconCache._headerDec[4] + + # qtbot.stop() + +# END Test testGuiTheme_Icons From 9b1c3fece2ab05c291849f827adbaaa7a36b17a9 Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Fri, 28 Oct 2022 23:50:52 +0200 Subject: [PATCH 19/36] Update default themes and remove custom style sheet feature --- novelwriter/assets/themes/default.conf | 5 +++-- novelwriter/assets/themes/default_dark.conf | 15 ++++++++------- novelwriter/assets/themes/default_dark.qss | 5 ----- novelwriter/gui/theme.py | 7 +------ 4 files changed, 12 insertions(+), 20 deletions(-) delete mode 100644 novelwriter/assets/themes/default_dark.qss diff --git a/novelwriter/assets/themes/default.conf b/novelwriter/assets/themes/default.conf index db780c75..aa3691e1 100644 --- a/novelwriter/assets/themes/default.conf +++ b/novelwriter/assets/themes/default.conf @@ -1,3 +1,4 @@ [Main] -name = Default Theme -icontheme = typicons_light +name = Default Theme +description = Qt standard colours +icontheme = typicons_light \ No newline at end of file diff --git a/novelwriter/assets/themes/default_dark.conf b/novelwriter/assets/themes/default_dark.conf index 97039825..05a9b4ce 100644 --- a/novelwriter/assets/themes/default_dark.conf +++ b/novelwriter/assets/themes/default_dark.conf @@ -1,11 +1,12 @@ [Main] -name = Default Dark Theme -author = Veronica Berglyd Olsen -credit = Veronica Berglyd Olsen -url = https://github.com/vkbo/novelWriter -license = CC BY-SA 4.0 -licenseurl = https://creativecommons.org/licenses/by-sa/4.0/ -icontheme = typicons_dark +name = Default Dark Theme +description = The novelWriter standard dark theme +author = Veronica Berglyd Olsen +credit = Veronica Berglyd Olsen +url = https://github.com/vkbo/novelWriter +license = CC BY-SA 4.0 +licenseurl = https://creativecommons.org/licenses/by-sa/4.0/ +icontheme = typicons_dark [Palette] window = 54, 54, 54 diff --git a/novelwriter/assets/themes/default_dark.qss b/novelwriter/assets/themes/default_dark.qss deleted file mode 100644 index 44f94ec6..00000000 --- a/novelwriter/assets/themes/default_dark.qss +++ /dev/null @@ -1,5 +0,0 @@ -/** - * Default Theme: Dark - * This theme doesn't use any custom styles, so the file is only here as - * an example. There doesn't have to be a styles.qss file in the folder. - */ diff --git a/novelwriter/gui/theme.py b/novelwriter/gui/theme.py index cb0e4bab..a352636e 100644 --- a/novelwriter/gui/theme.py +++ b/novelwriter/gui/theme.py @@ -38,7 +38,7 @@ from PyQt5.QtGui import ( from novelwriter.enum import nwItemLayout, nwItemType from novelwriter.error import logException -from novelwriter.common import NWConfigParser, minmax, readTextFile +from novelwriter.common import NWConfigParser, minmax from novelwriter.constants import nwLabels logger = logging.getLogger(__name__) @@ -246,11 +246,6 @@ class GuiTheme: # Icons self.iconCache.loadTheme(self.themeIcons) - # CSS File - cssData = readTextFile(themeFile[:-5]+".qss") - if cssData: - qApp.setStyleSheet(cssData) - # Apply Styles qApp.setPalette(self._guiPalette) From 4a9ea459faac11e09d6ad78fc167c16aff110a6f Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Sat, 29 Oct 2022 00:38:32 +0200 Subject: [PATCH 20/36] Make some minor changes and update test coverage --- novelwriter/dialogs/preferences.py | 40 ++++++++++++++++------ novelwriter/gui/doceditor.py | 14 ++++++++ novelwriter/gui/docviewer.py | 6 ++++ novelwriter/gui/theme.py | 10 +++--- novelwriter/guimain.py | 2 ++ tests/test_dialogs/test_dlg_preferences.py | 16 +++++++-- 6 files changed, 70 insertions(+), 18 deletions(-) diff --git a/novelwriter/dialogs/preferences.py b/novelwriter/dialogs/preferences.py index 6236a2a5..6b9dbd9f 100644 --- a/novelwriter/dialogs/preferences.py +++ b/novelwriter/dialogs/preferences.py @@ -78,15 +78,35 @@ class GuiPreferences(PagedDialog): self.resize(*self.mainConf.getPreferencesSize()) # Settings - self.updateTheme = False - self.updateSyntax = False - self.needsRestart = False - self.refreshTree = False + self._updateTheme = False + self._updateSyntax = False + self._needsRestart = False + self._refreshTree = False logger.debug("GuiPreferences initialisation complete") return + ## + # Properties + ## + + @property + def updateTheme(self): + return self._updateTheme + + @property + def updateSyntax(self): + return self._updateSyntax + + @property + def needsRestart(self): + return self._needsRestart + + @property + def refreshTree(self): + return self._refreshTree + ## # Slots ## @@ -275,12 +295,12 @@ class GuiPreferencesGeneral(QWidget): emphLabels = self.emphLabels.isChecked() # Update Flags - self.prefsGui.updateTheme |= self.mainConf.guiTheme != guiTheme - self.prefsGui.updateSyntax |= self.mainConf.guiSyntax != guiSyntax - self.prefsGui.needsRestart |= self.mainConf.guiLang != guiLang - self.prefsGui.needsRestart |= self.mainConf.guiFont != guiFont - self.prefsGui.needsRestart |= self.mainConf.guiFontSize != guiFontSize - self.prefsGui.refreshTree |= self.mainConf.emphLabels != emphLabels + self.prefsGui._updateTheme |= self.mainConf.guiTheme != guiTheme + self.prefsGui._updateSyntax |= self.mainConf.guiSyntax != guiSyntax + self.prefsGui._needsRestart |= self.mainConf.guiLang != guiLang + self.prefsGui._needsRestart |= self.mainConf.guiFont != guiFont + self.prefsGui._needsRestart |= self.mainConf.guiFontSize != guiFontSize + self.prefsGui._refreshTree |= self.mainConf.emphLabels != emphLabels self.mainConf.guiLang = guiLang self.mainConf.guiTheme = guiTheme diff --git a/novelwriter/gui/doceditor.py b/novelwriter/gui/doceditor.py index 179cab17..3e4a965f 100644 --- a/novelwriter/gui/doceditor.py +++ b/novelwriter/gui/doceditor.py @@ -2526,12 +2526,14 @@ class GuiDocEditSearch(QFrame): # Slots ## + @pyqtSlot() def _doClose(self): """Hide the search/replace bar. """ self.closeSearch() return + @pyqtSlot() def _doSearch(self): """Call the search action function for the document editor. """ @@ -2542,12 +2544,14 @@ class GuiDocEditSearch(QFrame): self.docEditor.findNext() return + @pyqtSlot() def _doReplace(self): """Call the replace action function for the document editor. """ self.docEditor.replaceNext() return + @pyqtSlot(bool) def _doToggleReplace(self, theState): """Toggle the show/hide of the replace box. """ @@ -2562,36 +2566,42 @@ class GuiDocEditSearch(QFrame): self.docEditor.updateDocMargins() return + @pyqtSlot(bool) def _doToggleCase(self, theState): """Enable/disable case sensitive mode. """ self.isCaseSense = theState return + @pyqtSlot(bool) def _doToggleWord(self, theState): """Enable/disable whole word search mode. """ self.isWholeWord = theState return + @pyqtSlot(bool) def _doToggleRegEx(self, theState): """Enable/disable regular expression search mode. """ self.isRegEx = theState return + @pyqtSlot(bool) def _doToggleLoop(self, theState): """Enable/disable looping the search. """ self.doLoop = theState return + @pyqtSlot(bool) def _doToggleProject(self, theState): """Enable/disable continuing search in next project file. """ self.doNextFile = theState return + @pyqtSlot(bool) def _doToggleMatchCap(self, theState): """Enable/disable preserving capitalisation when replacing. """ @@ -2804,18 +2814,21 @@ class GuiDocEditHeader(QWidget): # Slots ## + @pyqtSlot() def _editDocument(self): """Open the edit item dialog from the main GUI. """ self.mainGui.editItemLabel(self._docHandle) return + @pyqtSlot() def _searchDocument(self): """Toggle the visibility of the search box. """ self.docEditor.toggleSearch() return + @pyqtSlot() def _closeDocument(self): """Trigger the close editor on the main window. """ @@ -2826,6 +2839,7 @@ class GuiDocEditHeader(QWidget): self.minmaxButton.setVisible(False) return + @pyqtSlot() def _minmaxDocument(self): """Switch on or off Focus Mode. """ diff --git a/novelwriter/gui/docviewer.py b/novelwriter/gui/docviewer.py index 1b1a5b53..0437363e 100644 --- a/novelwriter/gui/docviewer.py +++ b/novelwriter/gui/docviewer.py @@ -889,12 +889,14 @@ class GuiDocViewHeader(QWidget): # Slots ## + @pyqtSlot() def _closeDocument(self): """Trigger the close editor/viewer on the main window. """ self.mainGui.closeDocViewer() return + @pyqtSlot() def _refreshDocument(self): """Reload the content of the document. """ @@ -1124,6 +1126,7 @@ class GuiDocViewFooter(QWidget): # Slots ## + @pyqtSlot() def _doShowHide(self): """Toggle the expand/collapse of the panel. """ @@ -1131,6 +1134,7 @@ class GuiDocViewFooter(QWidget): self.viewMeta.setVisible(not isVisible) return + @pyqtSlot(bool) def _doToggleSticky(self, theState): """Toggle the sticky flag for the reference panel. """ @@ -1140,6 +1144,7 @@ class GuiDocViewFooter(QWidget): self.viewMeta.refreshReferences(self.docViewer.docHandle()) return + @pyqtSlot(bool) def _doToggleComments(self, theState): """Toggle the view comment button and reload the document. """ @@ -1147,6 +1152,7 @@ class GuiDocViewFooter(QWidget): self.docViewer.reloadText() return + @pyqtSlot(bool) def _doToggleSynopsis(self, theState): """Toggle the view synopsis button and reload the document. """ diff --git a/novelwriter/gui/theme.py b/novelwriter/gui/theme.py index a352636e..93c26440 100644 --- a/novelwriter/gui/theme.py +++ b/novelwriter/gui/theme.py @@ -246,12 +246,9 @@ class GuiTheme: # Icons self.iconCache.loadTheme(self.themeIcons) - # Apply Styles - qApp.setPalette(self._guiPalette) - # Update Dependant Colours - backCol = qApp.palette().window().color() - textCol = qApp.palette().windowText().color() + backCol = self._guiPalette.window().color() + textCol = self._guiPalette.windowText().color() backLCol = backCol.lightnessF() textLCol = textCol.lightnessF() @@ -263,6 +260,9 @@ class GuiTheme: self.helpText = [int(255*helpLCol)]*3 + # Apply Styles + qApp.setPalette(self._guiPalette) + return True def loadSyntax(self): diff --git a/novelwriter/guimain.py b/novelwriter/guimain.py index b1467695..58c1e72a 100644 --- a/novelwriter/guimain.py +++ b/novelwriter/guimain.py @@ -918,6 +918,8 @@ class GuiMain(QMainWindow): self.projView.populateTree() if dlgConf.updateTheme: + # We are doing this manually instead of connecting to + # qApp.paletteChanged since the processing order matters self.mainTheme.loadTheme() self.docEditor.updateTheme() self.docViewer.updateTheme() diff --git a/tests/test_dialogs/test_dlg_preferences.py b/tests/test_dialogs/test_dlg_preferences.py index 4b077699..17dd6246 100644 --- a/tests/test_dialogs/test_dlg_preferences.py +++ b/tests/test_dialogs/test_dlg_preferences.py @@ -69,14 +69,24 @@ def testDlgPreferences_Main(qtbot, monkeypatch, fncDir, outDir, refDir): monkeypatch.setattr(GuiPreferences, "result", lambda *a: QDialog.Accepted) monkeypatch.setattr(nwGUI.docEditor.spEnchant, "listDictionaries", lambda: [("en", "none")]) - nwGUI.mainMenu.aPreferences.activate(QAction.Trigger) - qtbot.waitUntil(lambda: getGuiItem("GuiPreferences") is not None, timeout=1000) + with monkeypatch.context() as mp: + mp.setattr(GuiPreferences, "updateTheme", lambda *a: True) + mp.setattr(GuiPreferences, "updateSyntax", lambda *a: True) + mp.setattr(GuiPreferences, "needsRestart", lambda *a: True) + mp.setattr(GuiPreferences, "refreshTree", lambda *a: True) + nwGUI.mainMenu.aPreferences.activate(QAction.Trigger) + qtbot.waitUntil(lambda: getGuiItem("GuiPreferences") is not None, timeout=1000) nwPrefs = getGuiItem("GuiPreferences") assert isinstance(nwPrefs, GuiPreferences) nwPrefs.show() assert nwPrefs.mainConf.confPath == fncDir + assert nwPrefs.updateTheme is False + assert nwPrefs.updateSyntax is False + assert nwPrefs.needsRestart is False + assert nwPrefs.refreshTree is False + # General Settings qtbot.wait(KEY_DELAY) tabGeneral = nwPrefs.tabGeneral @@ -220,7 +230,7 @@ def testDlgPreferences_Main(qtbot, monkeypatch, fncDir, outDir, refDir): nwPrefs._tabBox.setCurrentWidget(tabQuote) monkeypatch.setattr(GuiQuoteSelect, "selectedQuote", "'") - monkeypatch.setattr(GuiQuoteSelect, "exec_", lambda *args: QDialog.Accepted) + monkeypatch.setattr(GuiQuoteSelect, "exec_", lambda *a: QDialog.Accepted) qtbot.mouseClick(tabQuote.btnDoubleStyleC, Qt.LeftButton) # Save and Check Config From 6d15bc5f145d4542e69b140d99edc8eb68a5426b Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Sat, 29 Oct 2022 00:56:39 +0200 Subject: [PATCH 21/36] Fix help text in Preferences --- novelwriter/dialogs/preferences.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/novelwriter/dialogs/preferences.py b/novelwriter/dialogs/preferences.py index 6b9dbd9f..72498421 100644 --- a/novelwriter/dialogs/preferences.py +++ b/novelwriter/dialogs/preferences.py @@ -183,7 +183,7 @@ class GuiPreferencesGeneral(QWidget): self.mainForm.addRow( self.tr("Main GUI language"), self.guiLang, - self.tr("Requires restart.") + self.tr("Requires restart to take effect.") ) # Select Theme @@ -199,7 +199,7 @@ class GuiPreferencesGeneral(QWidget): self.mainForm.addRow( self.tr("Main GUI theme"), self.guiTheme, - self.tr("Requires restart.") + self.tr("General colour theme and icons.") ) # Editor Theme @@ -229,7 +229,7 @@ class GuiPreferencesGeneral(QWidget): self.mainForm.addRow( self.tr("Font family"), self.guiFont, - self.tr("Requires restart."), + self.tr("Requires restart to take effect."), theButton=self.fontButton ) @@ -242,7 +242,7 @@ class GuiPreferencesGeneral(QWidget): self.mainForm.addRow( self.tr("Font size"), self.guiFontSize, - self.tr("Requires restart."), + self.tr("Requires restart to take effect."), theUnit=self.tr("pt") ) From 33eb3efb11ec0482a05d9853258b813eab24d93d Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Sat, 29 Oct 2022 01:05:33 +0200 Subject: [PATCH 22/36] Remove restriction on formatting empty block (#1178) --- novelwriter/gui/doceditor.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/novelwriter/gui/doceditor.py b/novelwriter/gui/doceditor.py index 3e4a965f..9126872b 100644 --- a/novelwriter/gui/doceditor.py +++ b/novelwriter/gui/doceditor.py @@ -1709,12 +1709,8 @@ class GuiDocEditor(QTextEdit): logger.debug("Invalid block selected for action '%s'", str(docAction)) return False - theText = theBlock.text() - if len(theText.strip()) == 0: - logger.debug("Empty block selected for action '%s'", str(docAction)) - return False - # Remove existing format first, if any + theText = theBlock.text() if theText.startswith("@"): logger.error("Cannot apply block format to keyword/value line") return False From dc401278fb93711e518f918b3e757f88b19796f4 Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Sat, 29 Oct 2022 01:05:41 +0200 Subject: [PATCH 23/36] Update tests --- tests/test_gui/test_gui_doceditor.py | 4 ---- tests/test_gui/test_gui_mainmenu.py | 14 ++++++++++---- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/tests/test_gui/test_gui_doceditor.py b/tests/test_gui/test_gui_doceditor.py index e2ba77ae..22a960c2 100644 --- a/tests/test_gui/test_gui_doceditor.py +++ b/tests/test_gui/test_gui_doceditor.py @@ -783,10 +783,6 @@ def testGuiEditor_BlockFormatting(qtbot, monkeypatch, nwGUI, nwMinimal, ipsumTex mp.setattr(QTextBlock, "isValid", lambda *a, **k: False) assert nwGUI.docEditor._formatBlock(nwDocAction.BLOCK_TXT) is False - # Empty Block - assert nwGUI.docEditor.setCursorLine(1) is True - assert nwGUI.docEditor._formatBlock(nwDocAction.BLOCK_TXT) is False - # Keyword assert nwGUI.docEditor.replaceText("@pov: Jane\n\n") is True assert nwGUI.docEditor.setCursorPosition(5) is True diff --git a/tests/test_gui/test_gui_mainmenu.py b/tests/test_gui/test_gui_mainmenu.py index e55e1039..f10514ad 100644 --- a/tests/test_gui/test_gui_mainmenu.py +++ b/tests/test_gui/test_gui_mainmenu.py @@ -116,6 +116,16 @@ def testGuiMenu_EditFormat(qtbot, monkeypatch, nwGUI, nwLipsum): fmtStr = "#### Pellentesque nec erat ut nulla posuere commodo." assert nwGUI.docEditor.getText()[39:91] == fmtStr + # Title Format + nwGUI.mainMenu.aFmtTitle.activate(QAction.Trigger) + fmtStr = "#! Pellentesque nec erat ut nulla posuere commodo." + assert nwGUI.docEditor.getText()[39:89] == fmtStr + + # Unnumbered Chapter + nwGUI.mainMenu.aFmtUnNum.activate(QAction.Trigger) + fmtStr = "##! Pellentesque nec erat ut nulla posuere commodo." + assert nwGUI.docEditor.getText()[39:90] == fmtStr + # Clear Format nwGUI.mainMenu.aFmtNoFormat.activate(QAction.Trigger) assert nwGUI.docEditor.getText()[39:86] == cleanText @@ -314,10 +324,6 @@ def testGuiMenu_EditFormat(qtbot, monkeypatch, nwGUI, nwLipsum): assert nwGUI.docEditor.setCursorPosition(17) assert not nwGUI.docEditor._formatBlock(nwDocAction.BLOCK_TXT) - # Cannot Format Empty Line - assert nwGUI.docEditor.setCursorPosition(13) - assert not nwGUI.docEditor._formatBlock(nwDocAction.BLOCK_TXT) - # Invalid Action assert nwGUI.docEditor.setCursorPosition(30) assert not nwGUI.docEditor._formatBlock(nwDocAction.NO_ACTION) From 3a90ee2b0d5aa0eb1ce927f3b4fc03a947c08612 Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Sat, 29 Oct 2022 01:26:26 +0200 Subject: [PATCH 24/36] Add insert menu entry for synopsis comment (#1177) --- novelwriter/enum.py | 7 ++++--- novelwriter/gui/doceditor.py | 10 +++++++++- novelwriter/gui/mainmenu.py | 9 +++++++++ 3 files changed, 22 insertions(+), 4 deletions(-) diff --git a/novelwriter/enum.py b/novelwriter/enum.py index 48b894ba..ecb4ea4a 100644 --- a/novelwriter/enum.py +++ b/novelwriter/enum.py @@ -112,9 +112,10 @@ class nwDocInsert(Enum): QUOTE_RS = 2 QUOTE_LD = 3 QUOTE_RD = 4 - NEW_PAGE = 5 - VSPACE_S = 6 - VSPACE_M = 7 + SYNOPSIS = 5 + NEW_PAGE = 6 + VSPACE_S = 7 + VSPACE_M = 8 # END Enum nwDocInsert diff --git a/novelwriter/gui/doceditor.py b/novelwriter/gui/doceditor.py index 9126872b..0bde6d8c 100644 --- a/novelwriter/gui/doceditor.py +++ b/novelwriter/gui/doceditor.py @@ -877,6 +877,7 @@ class GuiDocEditor(QTextEdit): return False newBlock = False + goAfter = False if isinstance(theInsert, str): theText = theInsert @@ -889,22 +890,29 @@ class GuiDocEditor(QTextEdit): theText = self._typDQOpen elif theInsert == nwDocInsert.QUOTE_RD: theText = self._typDQClose + elif theInsert == nwDocInsert.SYNOPSIS: + theText = "% Synopsis: " + newBlock = True + goAfter = True elif theInsert == nwDocInsert.NEW_PAGE: theText = "[NEW PAGE]" newBlock = True + goAfter = False elif theInsert == nwDocInsert.VSPACE_S: theText = "[VSPACE]" newBlock = True + goAfter = False elif theInsert == nwDocInsert.VSPACE_M: theText = "[VSPACE:2]" newBlock = True + goAfter = False else: return False else: return False if newBlock: - self.insertNewBlock(theText, defaultAfter=False) + self.insertNewBlock(theText, defaultAfter=goAfter) else: theCursor = self.textCursor() theCursor.beginEditBlock() diff --git a/novelwriter/gui/mainmenu.py b/novelwriter/gui/mainmenu.py index a110cb8a..8a74035e 100644 --- a/novelwriter/gui/mainmenu.py +++ b/novelwriter/gui/mainmenu.py @@ -554,6 +554,15 @@ class GuiMainMenu(QMenuBar): ) self.mInsKeywords.addAction(self.mInsKWItems[keyWord][0]) + # Insert > Special Comments + self.mInsComments = self.insertMenu.addMenu(self.tr("Special Comments")) + + # Insert > Synopsis Comment + self.aInsSynopsis = QAction(self.tr("Synopsis Comment"), self) + self.aInsSynopsis.setShortcut("Ctrl+K, S") + self.aInsSynopsis.triggered.connect(lambda: self._docInsert(nwDocInsert.SYNOPSIS)) + self.mInsComments.addAction(self.aInsSynopsis) + # Insert > Symbols self.mInsBreaks = self.insertMenu.addMenu(self.tr("Page Break and Space")) From dcb473137b4bf61976bc83de09afb11dc4b3a625 Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Sat, 29 Oct 2022 01:26:58 +0200 Subject: [PATCH 25/36] Update documentation and test with synopsis insert feature --- docs/source/usage_shortcuts.rst | 1 + tests/test_gui/test_gui_mainmenu.py | 27 +++++++++++++++------------ 2 files changed, 16 insertions(+), 12 deletions(-) diff --git a/docs/source/usage_shortcuts.rst b/docs/source/usage_shortcuts.rst index 7a321bcc..8254dfe0 100644 --- a/docs/source/usage_shortcuts.rst +++ b/docs/source/usage_shortcuts.rst @@ -137,6 +137,7 @@ a key or key combination for the inserted content. ":kbd:`Ctrl`:kbd:`K`, :kbd:`F`", "Insert a ``@focus`` keyword." ":kbd:`Ctrl`:kbd:`K`, :kbd:`C`", "Insert a ``@char`` keyword." ":kbd:`Ctrl`:kbd:`K`, :kbd:`P`", "Insert a ``@plot`` keyword." + ":kbd:`Ctrl`:kbd:`K`, :kbd:`S`", "Insert a synopsis comment." ":kbd:`Ctrl`:kbd:`K`, :kbd:`T`", "Insert a ``@time`` keyword." ":kbd:`Ctrl`:kbd:`K`, :kbd:`L`", "Insert a ``@location`` keyword." ":kbd:`Ctrl`:kbd:`K`, :kbd:`O`", "Insert an ``@object`` keyword." diff --git a/tests/test_gui/test_gui_mainmenu.py b/tests/test_gui/test_gui_mainmenu.py index f10514ad..7d7caaf7 100644 --- a/tests/test_gui/test_gui_mainmenu.py +++ b/tests/test_gui/test_gui_mainmenu.py @@ -537,9 +537,8 @@ def testGuiMenu_Insert(qtbot, monkeypatch, nwGUI, fncDir, fncProj, mockRnd): assert nwGUI.docEditor.getText() == nwUnicode.U_THNBSP nwGUI.docEditor.clear() - ## - # Insert Keywords - ## + # Insert Keywords + # =============== nwGUI.docEditor.setText("Stuff") nwGUI.mainMenu.mInsKWItems[nwKeyWords.TAG_KEY][0].activate(QAction.Trigger) @@ -589,9 +588,15 @@ def testGuiMenu_Insert(qtbot, monkeypatch, nwGUI, fncDir, fncProj, mockRnd): nwGUI.docEditor.clear() - ## - # Insert Break or Space - ## + # Insert Special Comments + # ======================= + + nwGUI.docEditor.setText("Stuff\n") + nwGUI.mainMenu.aInsSynopsis.activate(QAction.Trigger) + assert nwGUI.docEditor.getText() == "Stuff\n% Synopsis: \n" + + # Insert Break or Space + # ===================== nwGUI.docEditor.setText("### Stuff\n") nwGUI.mainMenu.aInsNewPage.activate(QAction.Trigger) @@ -607,9 +612,8 @@ def testGuiMenu_Insert(qtbot, monkeypatch, nwGUI, fncDir, fncProj, mockRnd): nwGUI.docEditor.clear() - ## - # Insert text from file - ## + # Insert Text from File + # ===================== nwGUI.closeDocument() @@ -645,9 +649,8 @@ def testGuiMenu_Insert(qtbot, monkeypatch, nwGUI, fncDir, fncProj, mockRnd): nwGUI.mainMenu.aImportFile.activate(QAction.Trigger) assert nwGUI.docEditor.getText() == "Foo" - ## - # Reveal file location - ## + # Reveal File Location + # ==================== theMessage = "" From 309a58cb1847596f38123e7e4d63e60dd3b550e4 Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Sat, 29 Oct 2022 01:36:31 +0200 Subject: [PATCH 26/36] Fix missing synopsis action in main window --- novelwriter/guimain.py | 1 + 1 file changed, 1 insertion(+) diff --git a/novelwriter/guimain.py b/novelwriter/guimain.py index 58c1e72a..89e4ca44 100644 --- a/novelwriter/guimain.py +++ b/novelwriter/guimain.py @@ -1340,6 +1340,7 @@ class GuiMain(QMainWindow): self.addAction(self.mainMenu.aInsMinus) self.addAction(self.mainMenu.aInsTimes) self.addAction(self.mainMenu.aInsDivide) + self.addAction(self.mainMenu.aInsSynopsis) for mAction, _ in self.mainMenu.mInsKWItems.values(): self.addAction(mAction) From 18878dd19b9a019e79a3ed37c8643dc2fba47753 Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Sat, 29 Oct 2022 17:55:45 +0200 Subject: [PATCH 27/36] Change how item data is stored in the project tree --- novelwriter/gui/projtree.py | 32 ++++++++++++++++++-------------- 1 file changed, 18 insertions(+), 14 deletions(-) diff --git a/novelwriter/gui/projtree.py b/novelwriter/gui/projtree.py index 19d8af38..b80a6352 100644 --- a/novelwriter/gui/projtree.py +++ b/novelwriter/gui/projtree.py @@ -417,11 +417,15 @@ class GuiProjectToolBar(QWidget): class GuiProjectTree(QTreeWidget): + C_DATA = 0 C_NAME = 0 C_COUNT = 1 C_ACTIVE = 2 C_STATUS = 3 + D_HANDLE = Qt.UserRole + D_WORDS = Qt.UserRole + 1 + def __init__(self, projView): super().__init__(parent=projView) @@ -974,10 +978,10 @@ class GuiProjectTree(QTreeWidget): if countChildren: for i in range(tItem.childCount()): - newCount += int(tItem.child(i).data(self.C_COUNT, Qt.UserRole)) + newCount += int(tItem.child(i).data(self.C_DATA, self.D_WORDS)) tItem.setText(self.C_COUNT, f"{newCount:n}") - tItem.setData(self.C_COUNT, Qt.UserRole, int(newCount)) + tItem.setData(self.C_DATA, self.D_WORDS, int(newCount)) pItem = tItem.parent() if pItem is None: @@ -986,8 +990,8 @@ class GuiProjectTree(QTreeWidget): pCount = 0 pHandle = None for i in range(pItem.childCount()): - pCount += int(pItem.child(i).data(self.C_COUNT, Qt.UserRole)) - pHandle = pItem.data(self.C_NAME, Qt.UserRole) + pCount += int(pItem.child(i).data(self.C_DATA, self.D_WORDS)) + pHandle = pItem.data(self.C_DATA, self.D_HANDLE) if pHandle: if self.theProject.tree.checkType(pHandle, nwItemType.FILE): @@ -1038,8 +1042,8 @@ class GuiProjectTree(QTreeWidget): return False dstIndex = min(max(0, dstIndex), dstItem.childCount()) - sHandle = srcItem.data(self.C_NAME, Qt.UserRole) - dHandle = dstItem.data(self.C_NAME, Qt.UserRole) + sHandle = srcItem.data(self.C_DATA, self.D_HANDLE) + dHandle = dstItem.data(self.C_DATA, self.D_HANDLE) logger.debug("Moving item '%s' back to '%s', index %d", sHandle, dHandle, dstIndex) wCount = self._getItemWordCount(sHandle) @@ -1063,7 +1067,7 @@ class GuiProjectTree(QTreeWidget): """ selItem = self.selectedItems() if selItem: - return selItem[0].data(self.C_NAME, Qt.UserRole) + return selItem[0].data(self.C_DATA, self.D_HANDLE) return None @@ -1149,7 +1153,7 @@ class GuiProjectTree(QTreeWidget): hasChild = False selItem = self.itemAt(clickPos) if isinstance(selItem, QTreeWidgetItem): - tHandle = selItem.data(self.C_NAME, Qt.UserRole) + tHandle = selItem.data(self.C_DATA, self.D_HANDLE) tItem = self.theProject.tree[tHandle] hasChild = selItem.childCount() > 0 @@ -1308,7 +1312,7 @@ class GuiProjectTree(QTreeWidget): if not isinstance(selItem, QTreeWidgetItem): return - tHandle = selItem.data(self.C_NAME, Qt.UserRole) + tHandle = selItem.data(self.C_DATA, self.D_HANDLE) tItem = self.theProject.tree[tHandle] if tItem is None: return @@ -1367,7 +1371,7 @@ class GuiProjectTree(QTreeWidget): # Update item parent handle in the project, make sure meta data # is updated accordingly, and update word count - pHandle = trItemP.data(self.C_NAME, Qt.UserRole) + pHandle = trItemP.data(self.C_DATA, self.D_HANDLE) nwItemS.setParent(pHandle) trItemP.setExpanded(True) logger.debug("The parent of item '%s' has been changed to '%s'", tHandle, pHandle) @@ -1397,7 +1401,7 @@ class GuiProjectTree(QTreeWidget): tItem = self._getTreeItem(tHandle) if tItem is None: return 0 - return int(tItem.data(self.C_COUNT, Qt.UserRole)) + return int(tItem.data(self.C_DATA, self.D_WORDS)) def _getTreeItem(self, tHandle): """Return the QTreeWidgetItem of a given item handle. @@ -1617,7 +1621,7 @@ class GuiProjectTree(QTreeWidget): """This is a recursive function returning all items in a tree starting at a given QTreeWidgetItem. """ - tHandle = tItem.data(self.C_NAME, Qt.UserRole) + tHandle = tItem.data(self.C_DATA, self.D_HANDLE) cCount = tItem.childCount() # Update tree-related meta data @@ -1650,8 +1654,8 @@ class GuiProjectTree(QTreeWidget): newItem.setTextAlignment(self.C_ACTIVE, Qt.AlignLeft) newItem.setTextAlignment(self.C_STATUS, Qt.AlignLeft) - newItem.setData(self.C_NAME, Qt.UserRole, tHandle) - newItem.setData(self.C_COUNT, Qt.UserRole, 0) + newItem.setData(self.C_DATA, self.D_HANDLE, tHandle) + newItem.setData(self.C_DATA, self.D_WORDS, 0) self._treeMap[tHandle] = newItem if pHandle is None: From ea59379479f4f21d7727edd007f5862e6df5708c Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Sun, 30 Oct 2022 01:20:31 +0200 Subject: [PATCH 28/36] Add new checkbox icons --- .../assets/icons/typicons_dark/icons.conf | 6 ++-- .../typicons_dark/mixed_input-checked.svg | 35 +++++++++++++++++++ .../icons/typicons_dark/mixed_input-none.svg | 31 ++++++++++++++++ .../typicons_dark/mixed_input-unchecked.svg | 35 +++++++++++++++++++ .../assets/icons/typicons_light/icons.conf | 6 ++-- .../typicons_light/mixed_input-checked.svg | 35 +++++++++++++++++++ .../icons/typicons_light/mixed_input-none.svg | 31 ++++++++++++++++ .../typicons_light/mixed_input-unchecked.svg | 35 +++++++++++++++++++ 8 files changed, 210 insertions(+), 4 deletions(-) create mode 100644 novelwriter/assets/icons/typicons_dark/mixed_input-checked.svg create mode 100644 novelwriter/assets/icons/typicons_dark/mixed_input-none.svg create mode 100644 novelwriter/assets/icons/typicons_dark/mixed_input-unchecked.svg create mode 100644 novelwriter/assets/icons/typicons_light/mixed_input-checked.svg create mode 100644 novelwriter/assets/icons/typicons_light/mixed_input-none.svg create mode 100644 novelwriter/assets/icons/typicons_light/mixed_input-unchecked.svg diff --git a/novelwriter/assets/icons/typicons_dark/icons.conf b/novelwriter/assets/icons/typicons_dark/icons.conf index 521e9ee8..fa8b8619 100644 --- a/novelwriter/assets/icons/typicons_dark/icons.conf +++ b/novelwriter/assets/icons/typicons_dark/icons.conf @@ -21,7 +21,7 @@ backward = typ_chevron-left.svg bookmark = typ_bookmark.svg bullet-off = typ_media-record-outline.svg bullet-on = typ_media-record.svg -check = typ_tick.svg +checked = mixed_input-checked.svg close = typ_times.svg cls_archive = typ_delete.svg cls_character = typ_user.svg @@ -41,13 +41,14 @@ forward = typ_chevron-right.svg maximise = typ_arrow-maximise.svg menu = typ_th-menu.svg minimise = typ_arrow-minimise.svg +noncheckable = mixed_input-none.svg proj_chapter = mixed_document-chapter.svg proj_details = typ_th-list-grey.svg proj_document = typ_document-text.svg proj_folder = typ_folder.svg proj_note = mixed_document-note.svg -proj_section = mixed_document-section.svg proj_scene = mixed_document-scene.svg +proj_section = mixed_document-section.svg proj_stats = typ_chart-bar-grey.svg proj_title = mixed_document-title.svg reference = typ_at.svg @@ -70,6 +71,7 @@ status_stats = typ_chart-bar-grey.svg status_time = typ_stopwatch-grey.svg sticky-off = typ_pin-outline.svg sticky-on = typ_pin.svg +unchecked = mixed_input-unchecked.svg up = typ_chevron-up.svg view_build = typ_export.svg view_editor = mixed_edit.svg diff --git a/novelwriter/assets/icons/typicons_dark/mixed_input-checked.svg b/novelwriter/assets/icons/typicons_dark/mixed_input-checked.svg new file mode 100644 index 00000000..e6236d3c --- /dev/null +++ b/novelwriter/assets/icons/typicons_dark/mixed_input-checked.svg @@ -0,0 +1,35 @@ + + + + + + image/svg+xml + + + + + + + + + diff --git a/novelwriter/assets/icons/typicons_dark/mixed_input-none.svg b/novelwriter/assets/icons/typicons_dark/mixed_input-none.svg new file mode 100644 index 00000000..a34897ee --- /dev/null +++ b/novelwriter/assets/icons/typicons_dark/mixed_input-none.svg @@ -0,0 +1,31 @@ + + + + + + image/svg+xml + + + + + + + + diff --git a/novelwriter/assets/icons/typicons_dark/mixed_input-unchecked.svg b/novelwriter/assets/icons/typicons_dark/mixed_input-unchecked.svg new file mode 100644 index 00000000..65ec7feb --- /dev/null +++ b/novelwriter/assets/icons/typicons_dark/mixed_input-unchecked.svg @@ -0,0 +1,35 @@ + + + + + + image/svg+xml + + + + + + + + + diff --git a/novelwriter/assets/icons/typicons_light/icons.conf b/novelwriter/assets/icons/typicons_light/icons.conf index 1b6b1348..5212b685 100644 --- a/novelwriter/assets/icons/typicons_light/icons.conf +++ b/novelwriter/assets/icons/typicons_light/icons.conf @@ -21,7 +21,7 @@ backward = typ_chevron-left.svg bookmark = typ_bookmark.svg bullet-off = typ_media-record-outline.svg bullet-on = typ_media-record.svg -check = typ_tick.svg +checked = mixed_input-checked.svg close = typ_times.svg cls_archive = typ_delete.svg cls_character = typ_user.svg @@ -41,13 +41,14 @@ forward = typ_chevron-right.svg maximise = typ_arrow-maximise.svg menu = typ_th-menu.svg minimise = typ_arrow-minimise.svg +noncheckable = mixed_input-none.svg proj_chapter = mixed_document-chapter.svg proj_details = typ_th-list-grey.svg proj_document = typ_document-text.svg proj_folder = typ_folder.svg proj_note = mixed_document-note.svg -proj_section = mixed_document-section.svg proj_scene = mixed_document-scene.svg +proj_section = mixed_document-section.svg proj_stats = typ_chart-bar-grey.svg proj_title = mixed_document-title.svg reference = typ_at.svg @@ -70,6 +71,7 @@ status_stats = typ_chart-bar-grey.svg status_time = typ_stopwatch-grey.svg sticky-off = typ_pin-outline.svg sticky-on = typ_pin.svg +unchecked = mixed_input-unchecked.svg up = typ_chevron-up.svg view_build = typ_export.svg view_editor = mixed_edit.svg diff --git a/novelwriter/assets/icons/typicons_light/mixed_input-checked.svg b/novelwriter/assets/icons/typicons_light/mixed_input-checked.svg new file mode 100644 index 00000000..d41ac684 --- /dev/null +++ b/novelwriter/assets/icons/typicons_light/mixed_input-checked.svg @@ -0,0 +1,35 @@ + + + + + + image/svg+xml + + + + + + + + + diff --git a/novelwriter/assets/icons/typicons_light/mixed_input-none.svg b/novelwriter/assets/icons/typicons_light/mixed_input-none.svg new file mode 100644 index 00000000..dde0c2cd --- /dev/null +++ b/novelwriter/assets/icons/typicons_light/mixed_input-none.svg @@ -0,0 +1,31 @@ + + + + + + image/svg+xml + + + + + + + + diff --git a/novelwriter/assets/icons/typicons_light/mixed_input-unchecked.svg b/novelwriter/assets/icons/typicons_light/mixed_input-unchecked.svg new file mode 100644 index 00000000..e3bb7a58 --- /dev/null +++ b/novelwriter/assets/icons/typicons_light/mixed_input-unchecked.svg @@ -0,0 +1,35 @@ + + + + + + image/svg+xml + + + + + + + + + From e80536405e59a00b17921250e06cf96a9bd6815e Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Sun, 30 Oct 2022 01:21:07 +0200 Subject: [PATCH 29/36] Replace active status icons in project tree --- .../assets/icons/typicons_dark/typ_tick.svg | 31 ------------------- .../assets/icons/typicons_light/typ_tick.svg | 31 ------------------- novelwriter/gui/projtree.py | 7 +++-- novelwriter/gui/theme.py | 6 ++-- 4 files changed, 8 insertions(+), 67 deletions(-) delete mode 100644 novelwriter/assets/icons/typicons_dark/typ_tick.svg delete mode 100644 novelwriter/assets/icons/typicons_light/typ_tick.svg diff --git a/novelwriter/assets/icons/typicons_dark/typ_tick.svg b/novelwriter/assets/icons/typicons_dark/typ_tick.svg deleted file mode 100644 index 84383114..00000000 --- a/novelwriter/assets/icons/typicons_dark/typ_tick.svg +++ /dev/null @@ -1,31 +0,0 @@ - - - - - - image/svg+xml - - - - - - - - diff --git a/novelwriter/assets/icons/typicons_light/typ_tick.svg b/novelwriter/assets/icons/typicons_light/typ_tick.svg deleted file mode 100644 index 1c24677a..00000000 --- a/novelwriter/assets/icons/typicons_light/typ_tick.svg +++ /dev/null @@ -1,31 +0,0 @@ - - - - - - image/svg+xml - - - - - - - - diff --git a/novelwriter/gui/projtree.py b/novelwriter/gui/projtree.py index b80a6352..0055340a 100644 --- a/novelwriter/gui/projtree.py +++ b/novelwriter/gui/projtree.py @@ -951,10 +951,13 @@ class GuiProjectTree(QTreeWidget): trItem.setToolTip(self.C_STATUS, itemStatus) if nwItem.isFileType(): - iconName = "check" if nwItem.isActive else "cross" + iconName = "checked" if nwItem.isActive else "unchecked" toolTip = self._lblActive if nwItem.isActive else self._lblInactive - trItem.setIcon(self.C_ACTIVE, self.mainTheme.getIcon(iconName)) trItem.setToolTip(self.C_ACTIVE, toolTip) + else: + iconName = "noncheckable" + + trItem.setIcon(self.C_ACTIVE, self.mainTheme.getIcon(iconName)) if self.mainConf.emphLabels and nwItem.isDocumentLayout(): trFont = trItem.font(self.C_NAME) diff --git a/novelwriter/gui/theme.py b/novelwriter/gui/theme.py index 93c26440..570b06b2 100644 --- a/novelwriter/gui/theme.py +++ b/novelwriter/gui/theme.py @@ -448,9 +448,9 @@ class GuiIcons: "view_editor", "view_novel", "view_outline", # General Button Icons - "add", "backward", "bookmark", "check", "close", "cross", "down", "edit", "forward", - "maximise", "menu", "minimise", "reference", "refresh", "remove", "search_replace", - "search", "settings", "up", + "add", "backward", "bookmark", "checked", "close", "cross", "down", "edit", "forward", + "maximise", "menu", "minimise", "noncheckable", "reference", "refresh", "remove", + "search_replace", "search", "settings", "unchecked", "up", # Switches "sticky-on", "sticky-off", From dbade05e46c54c6cb3e241fdefa5fab7be74607a Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Sun, 30 Oct 2022 02:33:00 +0200 Subject: [PATCH 30/36] Generate nicer status icons --- novelwriter/core/status.py | 47 ++++++++++++++++++++++++++------------ 1 file changed, 32 insertions(+), 15 deletions(-) diff --git a/novelwriter/core/status.py b/novelwriter/core/status.py index a379f911..b84a3690 100644 --- a/novelwriter/core/status.py +++ b/novelwriter/core/status.py @@ -26,11 +26,13 @@ along with this program. If not, see . import random import logging + import novelwriter from lxml import etree -from PyQt5.QtGui import QIcon, QPixmap, QColor +from PyQt5.QtGui import QIcon, QPainter, QPainterPath, QPixmap, QColor +from PyQt5.QtCore import QRectF, Qt from novelwriter.common import checkInt, minmax, simplified @@ -49,10 +51,15 @@ class NWStatus: self._reverse = {} self._default = None - self._iconSize = novelwriter.CONFIG.pxInt(32) - pixmap = QPixmap(self._iconSize, self._iconSize) - pixmap.fill(QColor(100, 100, 100)) - self._defaultIcon = QIcon(pixmap) + self._iPX = novelwriter.CONFIG.pxInt(24) + + pA = novelwriter.CONFIG.pxInt(2) + pB = novelwriter.CONFIG.pxInt(20) + pR = float(novelwriter.CONFIG.pxInt(4)) + self._iconPath = QPainterPath() + self._iconPath.addRoundedRect(QRectF(pA, pA, pB, pB), pR, pR) + + self._defaultIcon = self._createIcon([100, 100, 100]) if self._type == self.STATUS: self._prefix = "s" @@ -63,19 +70,16 @@ class NWStatus: return - def write(self, key, name, cols, count=None): + def write(self, key, name, col, count=None): """Add or update a status entry. If the key is invalid, a new key is generated. """ if not self._isKey(key): key = self._newKey() - if not isinstance(cols, tuple): - cols = (100, 100, 100) - if len(cols) != 3: - cols = (100, 100, 100) - - pixmap = QPixmap(self._iconSize, self._iconSize) - pixmap.fill(QColor(*cols)) + if not isinstance(col, tuple): + col = (100, 100, 100) + if len(col) != 3: + col = (100, 100, 100) name = simplified(name) if count is None: @@ -83,8 +87,8 @@ class NWStatus: self._store[key] = { "name": name, - "icon": QIcon(pixmap), - "cols": cols, + "icon": self._createIcon(col), + "cols": col, "count": count, } self._reverse[name] = key @@ -267,6 +271,19 @@ class NWStatus: return False return True + def _createIcon(self, col): + """Generate an icon for a status label. + """ + pixmap = QPixmap(self._iPX, self._iPX) + pixmap.fill(Qt.transparent) + + painter = QPainter(pixmap) + painter.setRenderHint(QPainter.Antialiasing) + painter.fillPath(self._iconPath, QColor(*col)) + painter.end() + + return QIcon(pixmap) + ## # Iterator Bits ## From 80496ab1ed6113576ee6fa0a38a23d60bc8bab8b Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Sun, 30 Oct 2022 15:16:39 +0100 Subject: [PATCH 31/36] Improve active/inactive icons again --- .../typicons_dark/mixed_input-checked.svg | 26 ++++++++++------- .../icons/typicons_dark/mixed_input-none.svg | 29 ++++++++++++++----- .../typicons_dark/mixed_input-unchecked.svg | 26 ++++++++++------- .../typicons_light/mixed_input-checked.svg | 26 ++++++++++------- .../icons/typicons_light/mixed_input-none.svg | 29 ++++++++++++++----- .../typicons_light/mixed_input-unchecked.svg | 26 ++++++++++------- novelwriter/core/status.py | 1 - setup.py | 1 + 8 files changed, 103 insertions(+), 61 deletions(-) diff --git a/novelwriter/assets/icons/typicons_dark/mixed_input-checked.svg b/novelwriter/assets/icons/typicons_dark/mixed_input-checked.svg index e6236d3c..a044fbaa 100644 --- a/novelwriter/assets/icons/typicons_dark/mixed_input-checked.svg +++ b/novelwriter/assets/icons/typicons_dark/mixed_input-checked.svg @@ -5,13 +5,15 @@ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" - version="1.2" width="24" height="24" + version="1.2" viewBox="0 0 24 24" - id="svg2803"> + id="svg2287"> + + id="metadata2281"> @@ -22,14 +24,16 @@ - + d="m17.714 22h-11.429c-2.3629 0-4.2857-1.9229-4.2857-4.2857v-11.429c0-2.3629 1.9229-4.2857 4.2857-4.2857h7.1429c0.79 0 1.4286 0.64 1.4286 1.4286 0 0.78857-0.63857 1.4286-1.4286 1.4286h-7.1429c-0.78857 0-1.4286 0.64143-1.4286 1.4286v11.429c0 0.78714 0.64 1.4286 1.4286 1.4286h11.429c0.78857 0 1.4286-0.64143 1.4286-1.4286v-4.2857c0-0.78857 0.63857-1.4286 1.4286-1.4286s1.4286 0.64 1.4286 1.4286v4.2857c0 2.3629-1.9229 4.2857-4.2857 4.2857z" + fill="#848484" + stroke-width="1.4286" + id="path2283" /> + d="m19.02 3.3503c-0.60933 0.010623-1.2003 0.31545-1.5586 0.86719l-5.0977 7.8477-2.3672-3.6445c-0.57329-0.88278-1.7442-1.1319-2.627-0.55859-0.88278 0.57329-1.1319 1.7461-0.55859 2.6289l3.9199 6.0391c0.38514 0.59307 1.0409 0.88965 1.6973 0.85352 0.03023-2.78e-4 0.05972-0.0041 0.08984-0.0059 0.0556-0.0057 0.11074-0.0088 0.16602-0.01953 0.52104-0.07621 1.0077-0.36147 1.3184-0.83984l6.6445-10.23c0.57329-0.88278 0.32419-2.0556-0.55859-2.6289-0.33104-0.21498-0.70276-0.31497-1.0684-0.30859z" + fill="#9c9" + stroke-linecap="round" + stroke-linejoin="round" + stroke-width="1.111" + id="path2285" /> diff --git a/novelwriter/assets/icons/typicons_dark/mixed_input-none.svg b/novelwriter/assets/icons/typicons_dark/mixed_input-none.svg index a34897ee..105e1d8f 100644 --- a/novelwriter/assets/icons/typicons_dark/mixed_input-none.svg +++ b/novelwriter/assets/icons/typicons_dark/mixed_input-none.svg @@ -5,13 +5,15 @@ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" - version="1.2" width="24" height="24" + version="1.2" viewBox="0 0 24 24" - id="svg2803"> + id="svg2931"> + + id="metadata2925"> @@ -22,10 +24,21 @@ - + d="m2 17.714v-11.429c0-2.3629 1.9229-4.2857 4.2857-4.2857h11.429c2.3629 0 4.2857 1.9229 4.2857 4.2857v7.1429c0 0.79-0.64 1.4286-1.4286 1.4286-0.78857 0-1.4286-0.63857-1.4286-1.4286v-7.1429c0-0.78857-0.64143-1.4286-1.4286-1.4286h-11.429c-0.78714 0-1.4286 0.64-1.4286 1.4286v11.429c0 0.78857 0.64143 1.4286 1.4286 1.4286h4.2857c0.78857 0 1.4286 0.63857 1.4286 1.4286s-0.64 1.4286-1.4286 1.4286h-4.2857c-2.3629 0-4.2857-1.9229-4.2857-4.2857zm15.714 4.2857h-11.429c-2.3629 0-4.2857-1.9229-4.2857-4.2857v-11.429c0-2.3629 1.9229-4.2857 4.2857-4.2857h7.1429c0.79 0 1.4286 0.64 1.4286 1.4286 0 0.78857-0.63857 1.4286-1.4286 1.4286h-7.1429c-0.78857 0-1.4286 0.64143-1.4286 1.4286v11.429c0 0.78714 0.64 1.4286 1.4286 1.4286h11.429c0.78857 0 1.4286-0.64143 1.4286-1.4286v-4.2857c0-0.78857 0.63857-1.4286 1.4286-1.4286s1.4286 0.64 1.4286 1.4286v4.2857c0 2.3629-1.9229 4.2857-4.2857 4.2857z" + fill="#848484" + stroke-width="1.4286" + id="path2927" /> + diff --git a/novelwriter/assets/icons/typicons_dark/mixed_input-unchecked.svg b/novelwriter/assets/icons/typicons_dark/mixed_input-unchecked.svg index 65ec7feb..b5ee8b37 100644 --- a/novelwriter/assets/icons/typicons_dark/mixed_input-unchecked.svg +++ b/novelwriter/assets/icons/typicons_dark/mixed_input-unchecked.svg @@ -5,13 +5,15 @@ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" - version="1.2" width="24" height="24" + version="1.2" viewBox="0 0 24 24" - id="svg2803"> + id="svg3574"> + + id="metadata3568"> @@ -22,14 +24,16 @@ - + d="m17.714 22h-11.429c-2.3629 0-4.2857-1.9229-4.2857-4.2857v-11.429c0-2.3629 1.9229-4.2857 4.2857-4.2857h7.1429c0.79 0 1.4286 0.64 1.4286 1.4286 0 0.78857-0.63857 1.4286-1.4286 1.4286h-7.1429c-0.78857 0-1.4286 0.64143-1.4286 1.4286v11.429c0 0.78714 0.64 1.4286 1.4286 1.4286h11.429c0.78857 0 1.4286-0.64143 1.4286-1.4286v-4.2857c0-0.78857 0.63857-1.4286 1.4286-1.4286s1.4286 0.64 1.4286 1.4286v4.2857c0 2.3629-1.9229 4.2857-4.2857 4.2857z" + fill="#848484" + stroke-width="1.4286" + id="path3570" /> + d="m17.99 3.3379c-0.48501 0.025418-0.96034 0.23584-1.3125 0.62695l-4.6777 5.1953-1.332-1.4785c-0.70433-0.78223-1.9014-0.84495-2.6836-0.14062-0.78223 0.70433-0.84495 1.9014-0.14062 2.6836l1.5996 1.7754-1.5977 1.7754c-0.70433 0.78223-0.64161 1.9793 0.14062 2.6836 0.78223 0.70433 1.9773 0.64161 2.6816-0.14062l1.332-1.4785 1.332 1.4785c0.70433 0.78224 1.9014 0.84495 2.6836 0.14062 0.78223-0.70433 0.84495-1.9014 0.14062-2.6836l-1.5996-1.7754 4.9453-5.4922c0.70433-0.78223 0.64161-1.9793-0.14062-2.6836-0.39112-0.35216-0.88608-0.51175-1.3711-0.48633z" + fill="#d64848" + stroke-linecap="round" + stroke-linejoin="round" + stroke-width="1.111" + id="path3572" /> diff --git a/novelwriter/assets/icons/typicons_light/mixed_input-checked.svg b/novelwriter/assets/icons/typicons_light/mixed_input-checked.svg index d41ac684..8e42a8b6 100644 --- a/novelwriter/assets/icons/typicons_light/mixed_input-checked.svg +++ b/novelwriter/assets/icons/typicons_light/mixed_input-checked.svg @@ -5,13 +5,15 @@ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" - version="1.2" width="24" height="24" + version="1.2" viewBox="0 0 24 24" - id="svg2803"> + id="svg879"> + + id="metadata873"> @@ -22,14 +24,16 @@ - + d="m17.714 22h-11.429c-2.3629 0-4.2857-1.9229-4.2857-4.2857v-11.429c0-2.3629 1.9229-4.2857 4.2857-4.2857h7.1429c0.79 0 1.4286 0.64 1.4286 1.4286 0 0.78857-0.63857 1.4286-1.4286 1.4286h-7.1429c-0.78857 0-1.4286 0.64143-1.4286 1.4286v11.429c0 0.78714 0.64 1.4286 1.4286 1.4286h11.429c0.78857 0 1.4286-0.64143 1.4286-1.4286v-4.2857c0-0.78857 0.63857-1.4286 1.4286-1.4286s1.4286 0.64 1.4286 1.4286v4.2857c0 2.3629-1.9229 4.2857-4.2857 4.2857z" + fill="#333" + stroke-width="1.4286" + id="path875" /> + d="m19.02 3.3503c-0.60933 0.010623-1.2003 0.31545-1.5586 0.86719l-5.0977 7.8477-2.3672-3.6445c-0.57329-0.88278-1.7442-1.1319-2.627-0.55859-0.88278 0.57329-1.1319 1.7461-0.55859 2.6289l3.9199 6.0391c0.38514 0.59307 1.0409 0.88965 1.6973 0.85352 0.03023-2.78e-4 0.05972-0.0041 0.08984-0.0059 0.0556-0.0057 0.11074-0.0088 0.16602-0.01953 0.52104-0.07621 1.0077-0.36147 1.3184-0.83984l6.6445-10.23c0.57329-0.88278 0.32419-2.0556-0.55859-2.6289-0.33104-0.21498-0.70276-0.31497-1.0684-0.30859z" + fill="#718c00" + stroke-linecap="round" + stroke-linejoin="round" + stroke-width="1.111" + id="path877" /> diff --git a/novelwriter/assets/icons/typicons_light/mixed_input-none.svg b/novelwriter/assets/icons/typicons_light/mixed_input-none.svg index dde0c2cd..f80dd7ff 100644 --- a/novelwriter/assets/icons/typicons_light/mixed_input-none.svg +++ b/novelwriter/assets/icons/typicons_light/mixed_input-none.svg @@ -5,13 +5,15 @@ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" - version="1.2" width="24" height="24" + version="1.2" viewBox="0 0 24 24" - id="svg2803"> + id="svg973"> + + id="metadata967"> @@ -22,10 +24,21 @@ - + d="m2 17.714v-11.429c0-2.3629 1.9229-4.2857 4.2857-4.2857h11.429c2.3629 0 4.2857 1.9229 4.2857 4.2857v7.1429c0 0.79-0.64 1.4286-1.4286 1.4286-0.78857 0-1.4286-0.63857-1.4286-1.4286v-7.1429c0-0.78857-0.64143-1.4286-1.4286-1.4286h-11.429c-0.78714 0-1.4286 0.64-1.4286 1.4286v11.429c0 0.78857 0.64143 1.4286 1.4286 1.4286h4.2857c0.78857 0 1.4286 0.63857 1.4286 1.4286s-0.64 1.4286-1.4286 1.4286h-4.2857c-2.3629 0-4.2857-1.9229-4.2857-4.2857zm15.714 4.2857h-11.429c-2.3629 0-4.2857-1.9229-4.2857-4.2857v-11.429c0-2.3629 1.9229-4.2857 4.2857-4.2857h7.1429c0.79 0 1.4286 0.64 1.4286 1.4286 0 0.78857-0.63857 1.4286-1.4286 1.4286h-7.1429c-0.78857 0-1.4286 0.64143-1.4286 1.4286v11.429c0 0.78714 0.64 1.4286 1.4286 1.4286h11.429c0.78857 0 1.4286-0.64143 1.4286-1.4286v-4.2857c0-0.78857 0.63857-1.4286 1.4286-1.4286s1.4286 0.64 1.4286 1.4286v4.2857c0 2.3629-1.9229 4.2857-4.2857 4.2857z" + fill="#333" + stroke-width="1.4286" + id="path969" /> + diff --git a/novelwriter/assets/icons/typicons_light/mixed_input-unchecked.svg b/novelwriter/assets/icons/typicons_light/mixed_input-unchecked.svg index e3bb7a58..0bd5f6dc 100644 --- a/novelwriter/assets/icons/typicons_light/mixed_input-unchecked.svg +++ b/novelwriter/assets/icons/typicons_light/mixed_input-unchecked.svg @@ -5,13 +5,15 @@ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" - version="1.2" width="24" height="24" + version="1.2" viewBox="0 0 24 24" - id="svg2803"> + id="svg1616"> + + id="metadata1610"> @@ -22,14 +24,16 @@ - + d="m17.714 22h-11.429c-2.3629 0-4.2857-1.9229-4.2857-4.2857v-11.429c0-2.3629 1.9229-4.2857 4.2857-4.2857h7.1429c0.79 0 1.4286 0.64 1.4286 1.4286 0 0.78857-0.63857 1.4286-1.4286 1.4286h-7.1429c-0.78857 0-1.4286 0.64143-1.4286 1.4286v11.429c0 0.78714 0.64 1.4286 1.4286 1.4286h11.429c0.78857 0 1.4286-0.64143 1.4286-1.4286v-4.2857c0-0.78857 0.63857-1.4286 1.4286-1.4286s1.4286 0.64 1.4286 1.4286v4.2857c0 2.3629-1.9229 4.2857-4.2857 4.2857z" + fill="#333" + stroke-width="1.4286" + id="path1612" /> + d="m17.99 3.3379c-0.48501 0.025418-0.96034 0.23584-1.3125 0.62695l-4.6777 5.1953-1.332-1.4785c-0.70433-0.78223-1.9014-0.84495-2.6836-0.14062-0.78223 0.70433-0.84495 1.9014-0.14062 2.6836l1.5996 1.7754-1.5977 1.7754c-0.70433 0.78223-0.64161 1.9793 0.14062 2.6836 0.78223 0.70433 1.9773 0.64161 2.6816-0.14062l1.332-1.4785 1.332 1.4785c0.70433 0.78224 1.9014 0.84495 2.6836 0.14062 0.78223-0.70433 0.84495-1.9014 0.14062-2.6836l-1.5996-1.7754 4.9453-5.4922c0.70433-0.78223 0.64161-1.9793-0.14062-2.6836-0.39112-0.35216-0.88608-0.51175-1.3711-0.48633z" + fill="#c82829" + stroke-linecap="round" + stroke-linejoin="round" + stroke-width="1.111" + id="path1614" /> diff --git a/novelwriter/core/status.py b/novelwriter/core/status.py index b84a3690..c466461e 100644 --- a/novelwriter/core/status.py +++ b/novelwriter/core/status.py @@ -26,7 +26,6 @@ along with this program. If not, see . import random import logging - import novelwriter from lxml import etree diff --git a/setup.py b/setup.py index a0bb9b03..675a29c8 100755 --- a/setup.py +++ b/setup.py @@ -131,6 +131,7 @@ def makeCheckSum(sumFile, cwd=None): except Exception as exc: print("Could not generate sha256 file") print(str(exc)) + return "" return shaFile From 15fd4c51df98483380e45158212d8df34c917b95 Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Sun, 30 Oct 2022 16:58:10 +0100 Subject: [PATCH 32/36] Fix bug with missing icons in outline view --- novelwriter/gui/outline.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/novelwriter/gui/outline.py b/novelwriter/gui/outline.py index dc0bf6d6..b5a1060a 100644 --- a/novelwriter/gui/outline.py +++ b/novelwriter/gui/outline.py @@ -254,6 +254,8 @@ class GuiOutlineToolBar(QToolBar): self.addWidget(self.tbColumns) self.addWidget(stretch) + self.updateTheme() + logger.debug("GuiOutlineToolBar initialisation complete") return From 1784d9ce281fd28bcf669258773c6e53de095ca6 Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Sun, 30 Oct 2022 17:03:52 +0100 Subject: [PATCH 33/36] Add move to trash switch on split dialog and processing (#1179) --- novelwriter/dialogs/docsplit.py | 9 ++++++++- novelwriter/gui/projtree.py | 3 +++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/novelwriter/dialogs/docsplit.py b/novelwriter/dialogs/docsplit.py index 1b6a5f70..a8904b46 100644 --- a/novelwriter/dialogs/docsplit.py +++ b/novelwriter/dialogs/docsplit.py @@ -103,14 +103,19 @@ class GuiDocSplit(QDialog): self.hierarchySwitch = QSwitch(width=2*iPx, height=iPx) self.hierarchySwitch.setChecked(docHierarchy) + self.trashLabel = QLabel(self.tr("Move split document to Trash")) + self.trashSwitch = QSwitch(width=2*iPx, height=iPx) + self.optBox = QGridLayout() self.optBox.addWidget(self.folderLabel, 0, 0) self.optBox.addWidget(self.folderSwitch, 0, 1) self.optBox.addWidget(self.hierarchyLabel, 1, 0) self.optBox.addWidget(self.hierarchySwitch, 1, 1) + self.optBox.addWidget(self.trashLabel, 2, 0) + self.optBox.addWidget(self.trashSwitch, 2, 1) self.optBox.setVerticalSpacing(vSp) self.optBox.setHorizontalSpacing(hSp) - self.optBox.setColumnStretch(2, 1) + self.optBox.setColumnStretch(3, 1) # Buttons self.buttonBox = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel) @@ -155,11 +160,13 @@ class GuiDocSplit(QDialog): spLevel = self.splitLevel.currentData() intoFolder = self.folderSwitch.isChecked() docHierarchy = self.hierarchySwitch.isChecked() + moveToTrash = self.trashSwitch.isChecked() self._data["spLevel"] = spLevel self._data["headerList"] = headerList self._data["intoFolder"] = intoFolder self._data["docHierarchy"] = docHierarchy + self._data["moveToTrash"] = moveToTrash pOptions = self.theProject.options pOptions.setValue("GuiDocSplit", "spLevel", spLevel) diff --git a/novelwriter/gui/projtree.py b/novelwriter/gui/projtree.py index 0055340a..02e1352a 100644 --- a/novelwriter/gui/projtree.py +++ b/novelwriter/gui/projtree.py @@ -1612,6 +1612,9 @@ class GuiProjectTree(QTreeWidget): self.tr("Could not write document content."), docSplit.getError() ], nwAlert.ERROR) + if splitData.get("moveToTrash", False): + self.moveItemToTrash(tHandle, askFirst=False, flush=True) + self.saveTreeOrder() else: From c1988bcc17f6f5340fdf5f1758c1a48cb11545eb Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Sun, 30 Oct 2022 17:07:24 +0100 Subject: [PATCH 34/36] Fix bug where split document loses status or importance --- novelwriter/core/doctools.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/novelwriter/core/doctools.py b/novelwriter/core/doctools.py index 54cb9d25..95fec897 100644 --- a/novelwriter/core/doctools.py +++ b/novelwriter/core/doctools.py @@ -192,7 +192,7 @@ class DocSplitter: """An iterator that will write each document in the buffer, and return its new handle, parent handle, and sibling handle. """ - if self._srcHandle is None: + if self._srcHandle is None or self._srcItem is None: return pHandle = self._parHandle @@ -224,6 +224,10 @@ class DocSplitter: dHandle = self.theProject.newFile(docLabel, pHandle) hHandle[hLevel] = dHandle + newItem = self.theProject.tree[dHandle] + newItem.setStatus(self._srcItem.itemStatus) + newItem.setImport(self._srcItem.itemImport) + outDoc = NWDoc(self.theProject, dHandle) status = outDoc.writeDocument("\n".join(docText)) if not status: From 4d5c1b24555f7b35be337eed5f29e11a22d53e14 Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Sun, 30 Oct 2022 17:14:28 +0100 Subject: [PATCH 35/36] Update split document tests --- tests/test_core/test_core_doctools.py | 7 +++++++ tests/test_gui/test_gui_projtree.py | 5 ++++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/tests/test_core/test_core_doctools.py b/tests/test_core/test_core_doctools.py index bcff60ce..02d87866 100644 --- a/tests/test_core/test_core_doctools.py +++ b/tests/test_core/test_core_doctools.py @@ -162,6 +162,8 @@ def testCoreDocTools_DocSplitter(monkeypatch, mockGUI, fncDir, outDir, refDir, m docText = "\n\n".join(docData) docRaw = docText.splitlines() assert NWDoc(theProject, hSplitDoc).writeDocument(docText) is True + theProject.tree[hSplitDoc].setStatus(C.sFinished) + theProject.tree[hSplitDoc].setImport(C.iMain) docSplitter = DocSplitter(theProject, hSplitDoc) assert docSplitter._srcItem.isFileType() @@ -242,6 +244,11 @@ def testCoreDocTools_DocSplitter(monkeypatch, mockGUI, fncDir, outDir, refDir, m "000000000002e", # Scene Five is after Scene Four ] + # Check that status and importance has been preserved + for rHandle in resDocHandle: + assert theProject.tree[rHandle].itemStatus == C.sFinished + assert theProject.tree[rHandle].itemImport == C.iMain + # Check handling of improper initialisation docSplitter = DocSplitter(theProject, C.hInvalid) assert docSplitter._srcHandle is None diff --git a/tests/test_gui/test_gui_projtree.py b/tests/test_gui/test_gui_projtree.py index 02efbb9a..31228e1e 100644 --- a/tests/test_gui/test_gui_projtree.py +++ b/tests/test_gui/test_gui_projtree.py @@ -837,14 +837,17 @@ def testGuiProjTree_SplitDocument(qtbot, monkeypatch, nwGUI, fncDir, mockRnd, ip assert tHandle in theProject.tree assert os.path.isfile(os.path.join(prjDir, "content", f"{tHandle}.nwd")) - # Add to a folder + # Add to a folder and move source to trash splitData["intoFolder"] = True + splitData["moveToTrash"] = True assert projTree._splitDocument(hSplitDoc) is True assert "0000000000029" in theProject.tree # The folder for tHandle in trdSet: assert tHandle in theProject.tree assert os.path.isfile(os.path.join(prjDir, "content", f"{tHandle}.nwd")) + assert theProject.tree.isTrash(hSplitDoc) is True + # Cancelled by user with monkeypatch.context() as mp: mp.setattr(GuiDocSplit, "result", lambda *a: QDialog.Rejected) From aa963dea3dbd9b2fd131686202cbfede2a67b3a4 Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Sun, 30 Oct 2022 17:55:38 +0100 Subject: [PATCH 36/36] Add version and build info to AppImage build (#1182) --- setup.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/setup.py b/setup.py index 675a29c8..facb5713 100755 --- a/setup.py +++ b/setup.py @@ -906,7 +906,6 @@ def makeForLaunchpad(doSign=False, isFirst=False, isSnapshot=False): def makeAppImage(sysArgs): """Build an Appimage """ - import glob import argparse import platform @@ -1089,8 +1088,9 @@ def makeAppImage(sysArgs): shutil.copyfile("setup/icons/novelwriter.svg", f"{imageDir}/novelwriter.svg") print("Copied: setup/icons/novelwriter.svg") - shutil.copyfile("setup/data/hicolor/256x256/apps/novelwriter.png", - f"{imageDir}/novelwriter.png") + shutil.copyfile( + "setup/data/hicolor/256x256/apps/novelwriter.png", f"{imageDir}/novelwriter.png" + ) print("Copied: setup/data/hicolor/256x256/apps/novelwriter.png") # Build Appimage @@ -1111,7 +1111,9 @@ def makeAppImage(sysArgs): print("") sys.exit(1) - outFile = glob.glob(f"{bldDir}/*.AppImage")[0] + bldFile = glob.glob(f"{bldDir}/*.AppImage")[0] + outFile = f"{bldDir}/novelWriter-{pkgVers}-py{pythonVer}-{linuxTag}.AppImage" + os.rename(bldFile, outFile) shaFile = makeCheckSum(os.path.basename(outFile), cwd=bldDir) toUpload(outFile) @@ -1119,11 +1121,11 @@ def makeAppImage(sysArgs): return unparsedArgs + ## # Make Windows Setup EXE (build-win-exe) ## - def makeWindowsEmbedded(sysArgs): """Set up a package with embedded Python and dependencies for Windows installation.