From b093446020a38d5eb423cbeff0aa47ede2cdab5b Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Sun, 31 Aug 2025 15:08:42 +0200 Subject: [PATCH 01/79] Fix Qt side crash when forwarding editor auto-complete keypresses (#2510) --- novelwriter/gui/doceditor.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/novelwriter/gui/doceditor.py b/novelwriter/gui/doceditor.py index 846b6317..83ff8fe7 100644 --- a/novelwriter/gui/doceditor.py +++ b/novelwriter/gui/doceditor.py @@ -1080,6 +1080,7 @@ class GuiDocEditor(QPlainTextEdit): if (block := self._qDocument.findBlock(pos)).isValid(): text = block.text() + if text and text[0] in "@%" and added + removed == 1: # Only run on single character changes, or it will trigger # at unwanted times when other changes are made to the document @@ -1094,10 +1095,6 @@ class GuiDocEditor(QPlainTextEdit): point = self.cursorRect().bottomRight() self._completer.move(viewport.mapToGlobal(point)) self._completer.show() - else: - self._completer.close() - else: - self._completer.close() if self._doReplace and added == 1: cursor = self.textCursor() @@ -1121,7 +1118,7 @@ class GuiDocEditor(QPlainTextEdit): cursor.setPosition(check, QtMoveAnchor) cursor.setPosition(check + length, QtKeepAnchor) cursor.insertText(text) - self._completer.hide() + self._completer.close() return @pyqtSlot() @@ -2186,6 +2183,7 @@ class CommandCompleter(QMenu): ): super().keyPressEvent(event) elif isinstance(parent, GuiDocEditor): + self.close() # Close to release the event lock before forwarding the key press (#2510) parent.keyPressEvent(event) return From 33b3a71ecdc832bef47d5e168aeecd6e5a6aaf9a Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Sun, 31 Aug 2025 15:11:51 +0200 Subject: [PATCH 02/79] Make a minor performance improvement in the editor completer --- novelwriter/gui/doceditor.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/novelwriter/gui/doceditor.py b/novelwriter/gui/doceditor.py index 83ff8fe7..daf99c4c 100644 --- a/novelwriter/gui/doceditor.py +++ b/novelwriter/gui/doceditor.py @@ -2086,10 +2086,13 @@ class CommandCompleter(QMenu): called on every keystroke on a line starting with @ or %. """ + __slots__ = ("_parent",) + complete = pyqtSignal(int, int, str) def __init__(self, parent: QWidget) -> None: super().__init__(parent=parent) + self._parent = parent return def updateMetaText(self, text: str, pos: int) -> bool: @@ -2176,15 +2179,14 @@ class CommandCompleter(QMenu): def keyPressEvent(self, event: QKeyEvent) -> None: """Capture keypresses and forward most of them to the editor.""" - parent = self.parent() if event.key() in ( Qt.Key.Key_Up, Qt.Key.Key_Down, Qt.Key.Key_Return, Qt.Key.Key_Enter, Qt.Key.Key_Escape ): super().keyPressEvent(event) - elif isinstance(parent, GuiDocEditor): + else: self.close() # Close to release the event lock before forwarding the key press (#2510) - parent.keyPressEvent(event) + self._parent.keyPressEvent(event) return ## From d69d2234c4dc91abab9cadbca17ae82a98976fdf Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Sun, 31 Aug 2025 15:23:33 +0200 Subject: [PATCH 03/79] Rename completer signal --- novelwriter/gui/doceditor.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/novelwriter/gui/doceditor.py b/novelwriter/gui/doceditor.py index daf99c4c..0ac4a992 100644 --- a/novelwriter/gui/doceditor.py +++ b/novelwriter/gui/doceditor.py @@ -151,7 +151,7 @@ class GuiDocEditor(QPlainTextEdit): # Completer self._completer = CommandCompleter(self) - self._completer.complete.connect(self._insertCompletion) + self._completer.insertText.connect(self._insertCompletion) # Create Custom Document self._qDocument = GuiTextDocument(self) @@ -2088,7 +2088,7 @@ class CommandCompleter(QMenu): __slots__ = ("_parent",) - complete = pyqtSignal(int, int, str) + insertText = pyqtSignal(int, int, str) def __init__(self, parent: QWidget) -> None: super().__init__(parent=parent) @@ -2195,7 +2195,7 @@ class CommandCompleter(QMenu): def _emitComplete(self, pos: int, length: int, value: str) -> None: """Emit the signal to indicate a selection has been made.""" - self.complete.emit(pos, length, value) + self.insertText.emit(pos, length, value) return From 08d2a5fa958af433e1fbb069c5166f262b614e98 Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Sun, 31 Aug 2025 15:27:55 +0200 Subject: [PATCH 04/79] Fix linting errors from updated Ruff version --- tests/test_gui/test_gui_guimain.py | 8 ++++---- tests/test_gui/test_gui_theme.py | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/test_gui/test_gui_guimain.py b/tests/test_gui/test_gui_guimain.py index 6fda9b6a..69c4da26 100644 --- a/tests/test_gui/test_gui_guimain.py +++ b/tests/test_gui/test_gui_guimain.py @@ -812,13 +812,13 @@ def testGuiMain_OpenClose(qtbot, monkeypatch, nwGUI, projPath, fncPath, mockRnd) # Handle broken index on project open nwGUI.closeProject() idxPath: Path = projPath / "meta" / nwFiles.INDEX_FILE - assert idxPath.read_text() != "{}" - idxPath.write_text("{}") - assert idxPath.read_text() == "{}" + assert idxPath.read_text(encoding="utf-8") != "{}" + idxPath.write_text("{}", encoding="utf-8") + assert idxPath.read_text(encoding="utf-8") == "{}" nwGUI.openProject(projPath) nwGUI.saveProject() - assert idxPath.read_text() != "{}" + assert idxPath.read_text(encoding="utf-8") != "{}" assert nwGUI.docEditor.docHandle == C.hSceneDoc assert nwGUI.docViewer.docHandle == C.hTitlePage diff --git a/tests/test_gui/test_gui_theme.py b/tests/test_gui/test_gui_theme.py index 7b0f518d..2deb3464 100644 --- a/tests/test_gui/test_gui_theme.py +++ b/tests/test_gui/test_gui_theme.py @@ -164,14 +164,14 @@ def testGuiTheme_Theme(qtbot, monkeypatch, nwGUI, tstPaths): # =============== mockTheme: Path = tstPaths.cnfDir / "themes" / "test.conf" - mockTheme.write_text( + mockTheme.write_text(( "[Main]\n" "name = Test\n" "\n" "[Palette]\n" "window = 0, 0, 0\n" "text = 255, 255, 255\n" - ) + ), encoding="utf-8") mainTheme._availThemes["test"] = mockTheme CONFIG.guiTheme = "test" From bb876b55fc6b8af889378406ede80be281544e15 Mon Sep 17 00:00:00 2001 From: Amber Flina Date: Thu, 11 Sep 2025 09:47:32 -0300 Subject: [PATCH 05/79] Handle IMEs --- novelwriter/gui/doceditor.py | 27 +++++++++++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/novelwriter/gui/doceditor.py b/novelwriter/gui/doceditor.py index 0ac4a992..7a0f1c3f 100644 --- a/novelwriter/gui/doceditor.py +++ b/novelwriter/gui/doceditor.py @@ -44,8 +44,8 @@ from PyQt6.QtCore import ( ) from PyQt6.QtGui import ( QAction, QCursor, QDragEnterEvent, QDragMoveEvent, QDropEvent, QKeyEvent, - QKeySequence, QMouseEvent, QPalette, QPixmap, QResizeEvent, QShortcut, - QTextBlock, QTextCursor, QTextDocument, QTextOption + QKeySequence, QInputMethodEvent, QMouseEvent, QPalette, QPixmap, QResizeEvent, + QShortcut, QTextBlock, QTextCursor, QTextDocument, QTextOption, ) from PyQt6.QtWidgets import ( QApplication, QFrame, QGridLayout, QHBoxLayout, QLabel, QLineEdit, QMenu, @@ -1017,6 +1017,29 @@ class GuiDocEditor(QPlainTextEdit): super().resizeEvent(event) return + def inputMethodEvent(self, event: QInputMethodEvent) -> None: + """Handle text being input from CJK input methods""" + super().inputMethodEvent(event) + if event.commitString(): + self.ensureCursorVisible() + if self._completer.isVisible(): + rect = self.cursorRect() + pos = self.mapToGlobal(rect.bottomLeft()) + self._completer.move(pos) + + def inputMethodQuery(self, query: Qt.InputMethodQuery): + """Adjust completion windows for CJK input methods to consider + the viewport margins. + """ + if query == Qt.InputMethodQuery.ImCursorRectangle: + rect = self.cursorRect() + vM = self.viewportMargins() + rect.translate(vM.left(), vM.top()) + + return rect + + return super().inputMethodQuery(query) + ## # Public Slots ## From bad03c45a0fc0f7a7f02d5b6275d7825f6369c9c Mon Sep 17 00:00:00 2001 From: Amber Flina Date: Thu, 11 Sep 2025 09:53:04 -0300 Subject: [PATCH 06/79] Linting --- novelwriter/gui/doceditor.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/novelwriter/gui/doceditor.py b/novelwriter/gui/doceditor.py index 7a0f1c3f..13fc4798 100644 --- a/novelwriter/gui/doceditor.py +++ b/novelwriter/gui/doceditor.py @@ -39,8 +39,8 @@ from enum import Enum, IntFlag from time import time from PyQt6.QtCore import ( - QObject, QPoint, QRegularExpression, QRunnable, Qt, QTimer, pyqtSignal, - pyqtSlot + QObject, QPoint, QRegularExpression, QRunnable, Qt, QTimer, QVariant, + pyqtSignal, pyqtSlot ) from PyQt6.QtGui import ( QAction, QCursor, QDragEnterEvent, QDragMoveEvent, QDropEvent, QKeyEvent, @@ -1018,7 +1018,7 @@ class GuiDocEditor(QPlainTextEdit): return def inputMethodEvent(self, event: QInputMethodEvent) -> None: - """Handle text being input from CJK input methods""" + """Handle text being input from CJK input methods.""" super().inputMethodEvent(event) if event.commitString(): self.ensureCursorVisible() @@ -1027,7 +1027,7 @@ class GuiDocEditor(QPlainTextEdit): pos = self.mapToGlobal(rect.bottomLeft()) self._completer.move(pos) - def inputMethodQuery(self, query: Qt.InputMethodQuery): + def inputMethodQuery(self, query: Qt.InputMethodQuery) -> QVariant: """Adjust completion windows for CJK input methods to consider the viewport margins. """ From 8a01ffa832886b5eb6f53490605ab6ab4bda475f Mon Sep 17 00:00:00 2001 From: Amber Flina Date: Thu, 11 Sep 2025 16:32:10 -0300 Subject: [PATCH 07/79] Other two linters --- novelwriter/gui/doceditor.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/novelwriter/gui/doceditor.py b/novelwriter/gui/doceditor.py index 13fc4798..8d4f0060 100644 --- a/novelwriter/gui/doceditor.py +++ b/novelwriter/gui/doceditor.py @@ -39,13 +39,14 @@ from enum import Enum, IntFlag from time import time from PyQt6.QtCore import ( - QObject, QPoint, QRegularExpression, QRunnable, Qt, QTimer, QVariant, - pyqtSignal, pyqtSlot + QObject, QPoint, QRegularExpression, QRunnable, Qt, QTimer, pyqtSignal, + pyqtSlot ) from PyQt6.QtGui import ( - QAction, QCursor, QDragEnterEvent, QDragMoveEvent, QDropEvent, QKeyEvent, - QKeySequence, QInputMethodEvent, QMouseEvent, QPalette, QPixmap, QResizeEvent, - QShortcut, QTextBlock, QTextCursor, QTextDocument, QTextOption, + QAction, QCursor, QDragEnterEvent, QDragMoveEvent, QDropEvent, + QInputMethodEvent, QKeyEvent, QKeySequence, QMouseEvent, QPalette, QPixmap, + QResizeEvent, QShortcut, QTextBlock, QTextCursor, QTextDocument, + QTextOption ) from PyQt6.QtWidgets import ( QApplication, QFrame, QGridLayout, QHBoxLayout, QLabel, QLineEdit, QMenu, @@ -1027,7 +1028,7 @@ class GuiDocEditor(QPlainTextEdit): pos = self.mapToGlobal(rect.bottomLeft()) self._completer.move(pos) - def inputMethodQuery(self, query: Qt.InputMethodQuery) -> QVariant: + def inputMethodQuery(self, query: Qt.InputMethodQuery) -> object: """Adjust completion windows for CJK input methods to consider the viewport margins. """ From 42f0d71ecfd5bc25df2a86abab382e23105dfea1 Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Fri, 12 Sep 2025 22:01:17 +0200 Subject: [PATCH 08/79] Clean up types --- novelwriter/gui/doceditor.py | 15 +++++++-------- novelwriter/types.py | 2 ++ 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/novelwriter/gui/doceditor.py b/novelwriter/gui/doceditor.py index 8d4f0060..e4dba082 100644 --- a/novelwriter/gui/doceditor.py +++ b/novelwriter/gui/doceditor.py @@ -39,8 +39,8 @@ from enum import Enum, IntFlag from time import time from PyQt6.QtCore import ( - QObject, QPoint, QRegularExpression, QRunnable, Qt, QTimer, pyqtSignal, - pyqtSlot + QObject, QPoint, QRect, QRegularExpression, QRunnable, Qt, QTimer, + QVariant, pyqtSignal, pyqtSlot ) from PyQt6.QtGui import ( QAction, QCursor, QDragEnterEvent, QDragMoveEvent, QDropEvent, @@ -75,8 +75,9 @@ from novelwriter.text.counting import standardCounter from novelwriter.tools.lipsum import GuiLipsum from novelwriter.types import ( QtAlignCenterTop, QtAlignJustify, QtAlignLeft, QtAlignLeftTop, - QtAlignRight, QtKeepAnchor, QtModCtrl, QtModNone, QtModShift, QtMouseLeft, - QtMoveAnchor, QtMoveLeft, QtMoveRight, QtScrollAlwaysOff, QtScrollAsNeeded + QtAlignRight, QtImCursorRectangle, QtKeepAnchor, QtModCtrl, QtModNone, + QtModShift, QtMouseLeft, QtMoveAnchor, QtMoveLeft, QtMoveRight, + QtScrollAlwaysOff, QtScrollAsNeeded ) logger = logging.getLogger(__name__) @@ -1028,17 +1029,15 @@ class GuiDocEditor(QPlainTextEdit): pos = self.mapToGlobal(rect.bottomLeft()) self._completer.move(pos) - def inputMethodQuery(self, query: Qt.InputMethodQuery) -> object: + def inputMethodQuery(self, query: Qt.InputMethodQuery) -> QRect | QVariant: """Adjust completion windows for CJK input methods to consider the viewport margins. """ - if query == Qt.InputMethodQuery.ImCursorRectangle: + if query == QtImCursorRectangle: rect = self.cursorRect() vM = self.viewportMargins() rect.translate(vM.left(), vM.top()) - return rect - return super().inputMethodQuery(query) ## diff --git a/novelwriter/types.py b/novelwriter/types.py index fb0faebd..f7f7a36d 100644 --- a/novelwriter/types.py +++ b/novelwriter/types.py @@ -110,6 +110,8 @@ QtMoveAnchor = QTextCursor.MoveMode.MoveAnchor QtMoveLeft = QTextCursor.MoveOperation.Left QtMoveRight = QTextCursor.MoveOperation.Right +QtImCursorRectangle = Qt.InputMethodQuery.ImCursorRectangle + # Size Policy QtSizeExpanding = QSizePolicy.Policy.Expanding From 410d8bfe1302fd2a09315248dc15340f465052db Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Sat, 13 Sep 2025 15:27:38 +0200 Subject: [PATCH 09/79] Add test coverage --- novelwriter/gui/doceditor.py | 22 +++++++++++++--------- tests/test_gui/test_gui_doceditor.py | 16 +++++++++++++++- 2 files changed, 28 insertions(+), 10 deletions(-) diff --git a/novelwriter/gui/doceditor.py b/novelwriter/gui/doceditor.py index e4dba082..d93b9927 100644 --- a/novelwriter/gui/doceditor.py +++ b/novelwriter/gui/doceditor.py @@ -916,7 +916,7 @@ class GuiDocEditor(QPlainTextEdit): return True ## - # Document Events and Maintenance + # Events and Overloads ## def keyPressEvent(self, event: QKeyEvent) -> None: @@ -1023,19 +1023,18 @@ class GuiDocEditor(QPlainTextEdit): """Handle text being input from CJK input methods.""" super().inputMethodEvent(event) if event.commitString(): + # See issues #2267 and #2517 self.ensureCursorVisible() - if self._completer.isVisible(): - rect = self.cursorRect() - pos = self.mapToGlobal(rect.bottomLeft()) - self._completer.move(pos) + self._completerToCursor() def inputMethodQuery(self, query: Qt.InputMethodQuery) -> QRect | QVariant: """Adjust completion windows for CJK input methods to consider the viewport margins. """ if query == QtImCursorRectangle: - rect = self.cursorRect() + # See issues #2267 and #2517 vM = self.viewportMargins() + rect = self.cursorRect() rect.translate(vM.left(), vM.top()) return rect return super().inputMethodQuery(query) @@ -1109,15 +1108,14 @@ class GuiDocEditor(QPlainTextEdit): # at unwanted times when other changes are made to the document cursor = self.textCursor() bPos = cursor.positionInBlock() - if bPos > 0 and (viewport := self.viewport()): + if bPos > 0: if text[0] == "@": show = self._completer.updateMetaText(text, bPos) else: show = self._completer.updateCommentText(text, bPos) if show: - point = self.cursorRect().bottomRight() - self._completer.move(viewport.mapToGlobal(point)) self._completer.show() + self._completerToCursor() if self._doReplace and added == 1: cursor = self.textCursor() @@ -1918,6 +1916,12 @@ class GuiDocEditor(QPlainTextEdit): # Internal Functions ## + def _completerToCursor(self) -> None: + """Make sure the completer menu is positioned by the cursor.""" + if self._completer.isVisible() and (viewport := self.viewport()): + point = self.cursorRect().bottomLeft() + self._completer.move(viewport.mapToGlobal(point)) + def _correctWord(self, cursor: QTextCursor, word: str) -> None: """Slot for the spell check context menu triggering the replacement of a word with the word from the dictionary. diff --git a/tests/test_gui/test_gui_doceditor.py b/tests/test_gui/test_gui_doceditor.py index 5bda2d82..08fbef0b 100644 --- a/tests/test_gui/test_gui_doceditor.py +++ b/tests/test_gui/test_gui_doceditor.py @@ -27,7 +27,8 @@ import pytest from PyQt6.QtCore import QEvent, QMimeData, QPointF, Qt, QThreadPool, QUrl from PyQt6.QtGui import ( QAction, QClipboard, QDesktopServices, QDragEnterEvent, QDragMoveEvent, - QDropEvent, QFont, QMouseEvent, QTextBlock, QTextCursor, QTextOption + QDropEvent, QFont, QInputMethodEvent, QMouseEvent, QTextBlock, QTextCursor, + QTextOption ) from PyQt6.QtWidgets import QApplication, QMenu, QPlainTextEdit @@ -1941,6 +1942,19 @@ def testGuiEditor_Completer(qtbot, nwGUI, projPath, mockRnd): "%Note.Consistency: \n" ) + # CJK completer reposition (#2267) + qtbot.keyClick(docEditor, "%", delay=KEY_DELAY) + assert completer.isVisible() is True + completer.move(0, 0) + assert completer.pos().x() == 0 + assert completer.pos().y() == 0 + + event = QInputMethodEvent() + event.setCommitString("Ping") + docEditor.inputMethodEvent(event) + assert completer.pos().x() > 0 # Should have moved + assert completer.pos().y() > 0 # Should have moved + # qtbot.stop() From da6b9b098046c0d3c841d6f5e900fdfccff4d6e2 Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Sat, 13 Sep 2025 15:48:33 +0200 Subject: [PATCH 10/79] Add more comments --- tests/test_gui/test_gui_doceditor.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/test_gui/test_gui_doceditor.py b/tests/test_gui/test_gui_doceditor.py index 08fbef0b..4e240295 100644 --- a/tests/test_gui/test_gui_doceditor.py +++ b/tests/test_gui/test_gui_doceditor.py @@ -1942,18 +1942,18 @@ def testGuiEditor_Completer(qtbot, nwGUI, projPath, mockRnd): "%Note.Consistency: \n" ) - # CJK completer reposition (#2267) + # CJK completer reposition (#2267 and #2517) qtbot.keyClick(docEditor, "%", delay=KEY_DELAY) assert completer.isVisible() is True completer.move(0, 0) - assert completer.pos().x() == 0 - assert completer.pos().y() == 0 + assert completer.pos().x() == 0 # Completer menu at 0 + assert completer.pos().y() == 0 # Completer menu at 0 event = QInputMethodEvent() - event.setCommitString("Ping") + event.setCommitString("Text") docEditor.inputMethodEvent(event) - assert completer.pos().x() > 0 # Should have moved - assert completer.pos().y() > 0 # Should have moved + assert completer.pos().x() > 0 # Completer should have moved + assert completer.pos().y() > 0 # Completer should have moved # qtbot.stop() From c1d133035091e48845e20fa4128555f5e2c13236 Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Sun, 14 Sep 2025 17:37:58 +0200 Subject: [PATCH 11/79] Bump version and update changelog --- CHANGELOG.md | 23 +++++++++++++++++++++++ novelwriter/__init__.py | 6 +++--- sample/nwProject.nwx | 4 ++-- 3 files changed, 28 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1dc17163..d68344bb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,28 @@ # novelWriter Changelog +## Version 2.7.5 [2025-09-14] + +### Release Notes + +This is a patch release that fixes an issue related to crashes when using the completer menu under +certain conditions, and improves positioning of the input box for CJK languages. + +### Detailed Changelog + +**Bugfixes** + +* Fixes an issue where the app would crash of deleting the `@` character with the completer menu + visible and the text margins of the editor set to "justified". This is likely crashing due to + some unhandled corner case in the Qt library, but the implementation of the completer menu in + novelWriter uses a small hack to bypass some intended behaviour of the menu. Extra steps have + been added to the implementation that seems to avoid the crash. Issue #2510. PR #2511. +* Fixes an issue where the input box that shows up when typing CJK languages were covering the text + due to an incorrect offset of the box location. The incorrect offset is caused by the text + margins not being taken into account. Fix by @Euophrys based on solution by @Jack-name. + Issues #2267 and #2517. PR #2518. + +---- + ## Version 2.7.4 [2025-07-15] ### Release Notes diff --git a/novelwriter/__init__.py b/novelwriter/__init__.py index fb4432e8..65a5c286 100644 --- a/novelwriter/__init__.py +++ b/novelwriter/__init__.py @@ -49,9 +49,9 @@ __license__ = "GPLv3" __author__ = "Veronica Berglyd Olsen" __maintainer__ = "Veronica Berglyd Olsen" __email__ = "code@vkbo.net" -__version__ = "2.7.4" -__hexversion__ = "0x020704f0" -__date__ = "2025-07-15" +__version__ = "2.7.5" +__hexversion__ = "0x020705f0" +__date__ = "2025-09-14" __status__ = "Stable" __domain__ = "novelwriter.io" diff --git a/sample/nwProject.nwx b/sample/nwProject.nwx index 3c7296dc..a4871a50 100644 --- a/sample/nwProject.nwx +++ b/sample/nwProject.nwx @@ -1,6 +1,6 @@ - - + + Sample Project Jane Smith From bb01d0e5aed2ff927626c07269ff84d188e8dd4d Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Sun, 14 Sep 2025 18:37:23 +0200 Subject: [PATCH 12/79] Fix debian build --- ...E-Apache-2.0.txt => LICENSE-Apache-2.0.txt | 0 pkgutils.py | 3 +-- pyproject.toml | 5 +---- utils/build_debian.py | 15 +++++++------- utils/common.py | 20 +++++++++++-------- 5 files changed, 22 insertions(+), 21 deletions(-) rename setup/LICENSE-Apache-2.0.txt => LICENSE-Apache-2.0.txt (100%) diff --git a/setup/LICENSE-Apache-2.0.txt b/LICENSE-Apache-2.0.txt similarity index 100% rename from setup/LICENSE-Apache-2.0.txt rename to LICENSE-Apache-2.0.txt diff --git a/pkgutils.py b/pkgutils.py index fd9ecf67..f71ea2f2 100755 --- a/pkgutils.py +++ b/pkgutils.py @@ -267,8 +267,7 @@ if __name__ == "__main__": cmdBuildUbuntu = parsers.add_parser( "build-ubuntu", help=( "Build a .deb package for Debian and Ubuntu. " - "Add --sign to sign package. " - "Add --first to set build number to 0." + "Add --sign to sign package." ) ) cmdBuildUbuntu.add_argument("--sign", action="store_true", help="Sign the package.") diff --git a/pyproject.toml b/pyproject.toml index 3610e012..9e4cf021 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -10,10 +10,7 @@ authors = [ description = "A plain text editor for planning and writing novels" readme = {file = "setup/description_pypi.md", content-type = "text/markdown"} license = "GPL-3.0-or-later AND Apache-2.0 AND CC-BY-4.0" -license-files = [ - "LICENSE.md", - "setup/LICENSE-Apache-2.0.txt", -] +license-files = ["LICENSE.md", "LICENSE-Apache-2.0.txt"] classifiers = [ "Programming Language :: Python :: 3 :: Only", "Programming Language :: Python :: 3.10", diff --git a/utils/build_debian.py b/utils/build_debian.py index e555ff95..a8cf9a05 100644 --- a/utils/build_debian.py +++ b/utils/build_debian.py @@ -36,7 +36,7 @@ SIGN_KEY = "D6A9F6B8F227CF7C6F6D1EE84DBBE4B734B0BD08" def makeDebianPackage( signKey: str | None = None, sourceBuild: bool = False, distName: str = "unstable", - buildName: str = "", forLaunchpad: bool = False + buildName: str = "", forLaunchpad: bool = False, oldLicense: bool = False, ) -> str: """Build a Debian package.""" print("") @@ -96,7 +96,7 @@ def makeDebianPackage( print("Copying or generating additional files ...") print("") - copyPackageFiles(outDir, setupPy=True) + copyPackageFiles(outDir, oldLicense=oldLicense) # Copy/Write Debian Files # ======================= @@ -181,14 +181,14 @@ def launchpad(args: argparse.Namespace) -> None: bldNum = "0" distLoop = [ - ("24.04", "noble"), - ("25.04", "plucky"), - ("25.10", "questing"), + ("24.04", "noble", True), + ("25.04", "plucky", True), + ("25.10", "questing", False), ] print("Building Ubuntu packages for:") print("") - for distNum, codeName in distLoop: + for distNum, codeName, _ in distLoop: print(f" * Ubuntu {distNum} {codeName.title()}") print("") @@ -198,7 +198,7 @@ def launchpad(args: argparse.Namespace) -> None: print("") dputCmd = [] - for distNum, codeName in distLoop: + for distNum, codeName, oldLicense in distLoop: buildName = f"ubuntu{distNum}.{bldNum}" dCmd = makeDebianPackage( signKey=signKey, @@ -206,6 +206,7 @@ def launchpad(args: argparse.Namespace) -> None: distName=codeName, buildName=buildName, forLaunchpad=True, + oldLicense=oldLicense, ) dputCmd.append(dCmd) diff --git a/utils/common.py b/utils/common.py index 1784ed01..95dcac28 100644 --- a/utils/common.py +++ b/utils/common.py @@ -91,27 +91,31 @@ def copySourceCode(dst: Path) -> None: return -def copyPackageFiles(dst: Path, setupPy: bool = False) -> None: +def copyPackageFiles(dst: Path, oldLicense: bool = False) -> None: """Copy files needed for packaging.""" - copyFiles = ["LICENSE.md", "CREDITS.md", "pyproject.toml"] + copyFiles = ["LICENSE.md", "LICENSE-Apache-2.0.txt", "CREDITS.md", "pyproject.toml"] for copyFile in copyFiles: shutil.copyfile(copyFile, dst / copyFile) print("Copied:", copyFile, flush=True) writeFile(dst / "MANIFEST.in", ( "include LICENSE.md\n" + "include LICENSE-Apache-2.0.txt\n" "include CREDITS.md\n" "recursive-include novelwriter/assets *\n" )) - if setupPy: - writeFile(dst / "setup.py", ( - "import setuptools\n" - "setuptools.setup()\n" - )) - text = readFile(ROOT_DIR / "pyproject.toml") text = text.replace("setup/description_pypi.md", "data/description_short.txt") + if oldLicense: + new = [] + for line in text.splitlines(): + if line.startswith("license = "): + line = 'license = {text = "GPL-3.0-or-later AND Apache-2.0 AND CC-BY-4.0"}' + if line.startswith("license-files = "): + continue + new.append(line) + text = "\n".join(new) writeFile(dst / "pyproject.toml", text) return From a37482aaad009ff95e4b91f48f91bde50c188203 Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Sun, 14 Sep 2025 18:54:42 +0200 Subject: [PATCH 13/79] Pin windows build to 2022 image --- .github/workflows/build_win.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build_win.yml b/.github/workflows/build_win.yml index b9e96e9d..bee832c7 100644 --- a/.github/workflows/build_win.yml +++ b/.github/workflows/build_win.yml @@ -8,7 +8,7 @@ jobs: buildWin64: needs: buildAssets - runs-on: windows-latest + runs-on: windows-2022 steps: - name: Python Setup uses: actions/setup-python@v5 From f019a3cd0c42cef017ae5099effee5e68040c9b4 Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Sun, 14 Sep 2025 19:24:32 +0200 Subject: [PATCH 14/79] Move the Apache license file back to setup --- pyproject.toml | 2 +- LICENSE-Apache-2.0.txt => setup/LICENSE-Apache-2.0.txt | 0 utils/common.py | 4 ++-- 3 files changed, 3 insertions(+), 3 deletions(-) rename LICENSE-Apache-2.0.txt => setup/LICENSE-Apache-2.0.txt (100%) diff --git a/pyproject.toml b/pyproject.toml index 9e4cf021..15736ca4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -10,7 +10,7 @@ authors = [ description = "A plain text editor for planning and writing novels" readme = {file = "setup/description_pypi.md", content-type = "text/markdown"} license = "GPL-3.0-or-later AND Apache-2.0 AND CC-BY-4.0" -license-files = ["LICENSE.md", "LICENSE-Apache-2.0.txt"] +license-files = ["LICENSE.md", "setup/LICENSE-Apache-2.0.txt"] classifiers = [ "Programming Language :: Python :: 3 :: Only", "Programming Language :: Python :: 3.10", diff --git a/LICENSE-Apache-2.0.txt b/setup/LICENSE-Apache-2.0.txt similarity index 100% rename from LICENSE-Apache-2.0.txt rename to setup/LICENSE-Apache-2.0.txt diff --git a/utils/common.py b/utils/common.py index 95dcac28..09d937b1 100644 --- a/utils/common.py +++ b/utils/common.py @@ -93,14 +93,14 @@ def copySourceCode(dst: Path) -> None: def copyPackageFiles(dst: Path, oldLicense: bool = False) -> None: """Copy files needed for packaging.""" - copyFiles = ["LICENSE.md", "LICENSE-Apache-2.0.txt", "CREDITS.md", "pyproject.toml"] + copyFiles = ["LICENSE.md", "setup/LICENSE-Apache-2.0.txt", "CREDITS.md", "pyproject.toml"] for copyFile in copyFiles: shutil.copyfile(copyFile, dst / copyFile) print("Copied:", copyFile, flush=True) writeFile(dst / "MANIFEST.in", ( "include LICENSE.md\n" - "include LICENSE-Apache-2.0.txt\n" + "include setup/LICENSE-Apache-2.0.txt\n" "include CREDITS.md\n" "recursive-include novelwriter/assets *\n" )) From 4d398f170de3ed85301b412b03c004e4fb49196c Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Sun, 14 Sep 2025 19:42:36 +0200 Subject: [PATCH 15/79] Add back attribute lost when merging --- novelwriter/gui/doceditor.py | 1 + 1 file changed, 1 insertion(+) diff --git a/novelwriter/gui/doceditor.py b/novelwriter/gui/doceditor.py index d35cb6b4..c6773f24 100644 --- a/novelwriter/gui/doceditor.py +++ b/novelwriter/gui/doceditor.py @@ -2079,6 +2079,7 @@ class CommandCompleter(QMenu): def __init__(self, parent: QWidget) -> None: super().__init__(parent=parent) + self._parent = parent def updateMetaText(self, text: str, pos: int) -> bool: """Update the menu options based on the line of text.""" From 100aad1256146083cee38f68551370f6edc72790 Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Sat, 4 Oct 2025 18:36:44 +0200 Subject: [PATCH 16/79] Update linting due to new Ruff rules --- novelWriter.py | 4 ++-- novelwriter/core/status.py | 2 +- novelwriter/core/tree.py | 2 +- novelwriter/error.py | 2 +- novelwriter/gui/doceditor.py | 8 ++++---- novelwriter/gui/docviewer.py | 2 +- novelwriter/gui/outline.py | 2 +- 7 files changed, 11 insertions(+), 11 deletions(-) diff --git a/novelWriter.py b/novelWriter.py index 797dfe23..78c8db27 100755 --- a/novelWriter.py +++ b/novelWriter.py @@ -7,8 +7,8 @@ import os import sys try: - import PyQt6.QtCore - import PyQt6.QtGui + import PyQt6.QtCore # noqa: F401 + import PyQt6.QtGui # noqa: F401 import PyQt6.QtWidgets # noqa: F401 except Exception: print("ERROR: Failed to load dependency PyQt6") diff --git a/novelwriter/core/status.py b/novelwriter/core/status.py index fc8bf85c..9a3d3194 100644 --- a/novelwriter/core/status.py +++ b/novelwriter/core/status.py @@ -183,7 +183,7 @@ class NWStatus: icon = NWStatus.createIcon(self._height, color, shape) return StatusEntry(simplified(data[2]), color, theme, shape, icon) except Exception: - logger.error("Could not parse entry %s", str(data)) + logger.error("Could not parse entry %s", data) return None def refreshIcons(self) -> None: diff --git a/novelwriter/core/tree.py b/novelwriter/core/tree.py index 4ee4b626..2f151cc4 100644 --- a/novelwriter/core/tree.py +++ b/novelwriter/core/tree.py @@ -89,7 +89,7 @@ class NWTree: """ if tHandle and tHandle in self._items: return self._items[tHandle] - logger.error("No tree item with handle '%s'", str(tHandle)) + logger.error("No tree item with handle '%s'", tHandle) return None def __contains__(self, tHandle: str) -> bool: diff --git a/novelwriter/error.py b/novelwriter/error.py index 59fafdff..0f1ff8fd 100644 --- a/novelwriter/error.py +++ b/novelwriter/error.py @@ -171,7 +171,7 @@ def exceptionHandler(exType: type, exValue: BaseException, exTrace: TracebackTyp from PyQt6.QtWidgets import QApplication - logger.critical("%s: %s", exType.__name__, str(exValue)) + logger.critical("%s: %s", exType.__name__, exValue) print_tb(exTrace) try: diff --git a/novelwriter/gui/doceditor.py b/novelwriter/gui/doceditor.py index c6773f24..339503d5 100644 --- a/novelwriter/gui/doceditor.py +++ b/novelwriter/gui/doceditor.py @@ -672,7 +672,7 @@ class GuiDocEditor(QPlainTextEdit): self.spellCheckStateChanged.emit(state) self.spellCheckDocument() - logger.debug("Spell check is set to '%s'", str(state)) + logger.debug("Spell check is set to '%s'", state) def spellCheckDocument(self) -> None: """Rerun the highlighter to update spell checking status of the @@ -785,7 +785,7 @@ class GuiDocEditor(QPlainTextEdit): elif action == nwDocAction.SC_SUB: self._wrapSelection(nwShortcode.SUB_O, nwShortcode.SUB_C) else: - logger.debug("Unknown or unsupported document action '%s'", str(action)) + logger.debug("Unknown or unsupported document action '%s'", action) self._allowAutoReplace(True) return False @@ -1750,7 +1750,7 @@ class GuiDocEditor(QPlainTextEdit): elif action == nwDocAction.BLOCK_TXT: text = temp else: - logger.error("Unknown or unsupported block format requested: '%s'", str(action)) + logger.error("Unknown or unsupported block format requested: '%s'", action) return nwDocAction.NO_ACTION, "", 0 return action, text, offset @@ -1760,7 +1760,7 @@ class GuiDocEditor(QPlainTextEdit): cursor = self.textCursor() block = cursor.block() if not block.isValid(): - logger.debug("Invalid block selected for action '%s'", str(action)) + logger.debug("Invalid block selected for action '%s'", action) return False action, text, offset = self._processBlockFormat(action, block.text()) diff --git a/novelwriter/gui/docviewer.py b/novelwriter/gui/docviewer.py index 10ef9c46..a043e947 100644 --- a/novelwriter/gui/docviewer.py +++ b/novelwriter/gui/docviewer.py @@ -291,7 +291,7 @@ class GuiDocViewer(QTextBrowser): elif action == nwDocAction.SEL_PARA: self._makeSelection(QTextCursor.SelectionType.BlockUnderCursor) else: - logger.debug("Unknown or unsupported document action '%s'", str(action)) + logger.debug("Unknown or unsupported document action '%s'", action) return False return True diff --git a/novelwriter/gui/outline.py b/novelwriter/gui/outline.py index 3df01d3f..4a76712f 100644 --- a/novelwriter/gui/outline.py +++ b/novelwriter/gui/outline.py @@ -586,7 +586,7 @@ class GuiOutlineTree(QTreeWidget): try: for name, (hidden, width) in colState.items(): if name not in nwOutline.__members__: - logger.warning("Ignored unknown outline column '%s'", str(name)) + logger.warning("Ignored unknown outline column '%s'", name) continue tmpOrder.append(nwOutline[name]) tmpHidden[nwOutline[name]] = hidden From 42352e95003a9b72a9ce905832415f01f662b514 Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Sat, 4 Oct 2025 18:42:20 +0200 Subject: [PATCH 17/79] Add some more type constants --- novelwriter/gui/doceditor.py | 25 +++++++++++-------------- novelwriter/gui/docviewer.py | 17 +++++++---------- novelwriter/types.py | 5 +++++ tests/test_gui/test_gui_doceditor.py | 11 ++++++----- tests/test_gui/test_gui_docviewer.py | 14 ++++++-------- tests/test_gui/test_gui_mainmenu.py | 8 ++++---- 6 files changed, 39 insertions(+), 41 deletions(-) diff --git a/novelwriter/gui/doceditor.py b/novelwriter/gui/doceditor.py index 339503d5..1496743f 100644 --- a/novelwriter/gui/doceditor.py +++ b/novelwriter/gui/doceditor.py @@ -77,7 +77,8 @@ from novelwriter.types import ( QtAlignCenterTop, QtAlignJustify, QtAlignLeft, QtAlignLeftTop, QtAlignRight, QtImCursorRectangle, QtKeepAnchor, QtModCtrl, QtModNone, QtModShift, QtMouseLeft, QtMoveAnchor, QtMoveLeft, QtMoveRight, - QtScrollAlwaysOff, QtScrollAsNeeded, QtTransparent + QtScrollAlwaysOff, QtScrollAsNeeded, QtSelectBlock, QtSelectDocument, + QtSelectWord, QtTransparent ) logger = logging.getLogger(__name__) @@ -731,9 +732,9 @@ class GuiDocEditor(QPlainTextEdit): elif action == nwDocAction.D_QUOTE: self._wrapSelection(CONFIG.fmtDQuoteOpen, CONFIG.fmtDQuoteClose) elif action == nwDocAction.SEL_ALL: - self._makeSelection(QTextCursor.SelectionType.Document) + self._makeSelection(QtSelectDocument) elif action == nwDocAction.SEL_PARA: - self._makeSelection(QTextCursor.SelectionType.BlockUnderCursor) + self._makeSelection(QtSelectBlock) elif action == nwDocAction.BLOCK_H1: self._formatBlock(nwDocAction.BLOCK_H1) elif action == nwDocAction.BLOCK_H2: @@ -1174,13 +1175,9 @@ class GuiDocEditor(QPlainTextEdit): action = qtAddAction(ctxMenu, self.tr("Select All")) action.triggered.connect(qtLambda(self.docAction, nwDocAction.SEL_ALL)) action = qtAddAction(ctxMenu, self.tr("Select Word")) - action.triggered.connect(qtLambda( - self._makePosSelection, QTextCursor.SelectionType.WordUnderCursor, pos, - )) + action.triggered.connect(qtLambda(self._makePosSelection, QtSelectWord, pos)) action = qtAddAction(ctxMenu, self.tr("Select Paragraph")) - action.triggered.connect(qtLambda( - self._makePosSelection, QTextCursor.SelectionType.BlockUnderCursor, pos - )) + action.triggered.connect(qtLambda(self._makePosSelection, QtSelectBlock, pos)) # Spell Checking if SHARED.project.data.spellCheck: @@ -1770,7 +1767,7 @@ class GuiDocEditor(QPlainTextEdit): pos = cursor.position() cursor.beginEditBlock() - self._makeSelection(QTextCursor.SelectionType.BlockUnderCursor, cursor) + self._makeSelection(QtSelectBlock, cursor) cursor.insertText(text) cursor.endEditBlock() @@ -1798,7 +1795,7 @@ class GuiDocEditor(QPlainTextEdit): if pAction != nwDocAction.NO_ACTION and blockText.strip(): action = pAction # First block decides further actions cursor.setPosition(block.position()) - self._makeSelection(QTextCursor.SelectionType.BlockUnderCursor, cursor) + self._makeSelection(QtSelectBlock, cursor) cursor.insertText(text) toggle = False @@ -1818,7 +1815,7 @@ class GuiDocEditor(QPlainTextEdit): """Strip line breaks within paragraphs in the selected text.""" cursor = self.textCursor() if not cursor.hasSelection(): - cursor.select(QTextCursor.SelectionType.Document) + cursor.select(QtSelectDocument) rS = 0 rE = self._qDocument.characterCount() @@ -2035,10 +2032,10 @@ class GuiDocEditor(QPlainTextEdit): cursor.clearSelection() cursor.select(mode) - if mode == QTextCursor.SelectionType.WordUnderCursor: + if mode == QtSelectWord: cursor = self._autoSelect() - elif mode == QTextCursor.SelectionType.BlockUnderCursor: + elif mode == QtSelectBlock: # This selection mode also selects the preceding paragraph # separator, which we want to avoid. posS = cursor.selectionStart() diff --git a/novelwriter/gui/docviewer.py b/novelwriter/gui/docviewer.py index a043e947..fc7bef81 100644 --- a/novelwriter/gui/docviewer.py +++ b/novelwriter/gui/docviewer.py @@ -53,7 +53,8 @@ from novelwriter.formats.toqdoc import ToQTextDocument from novelwriter.gui.theme import STYLES_MIN_TOOLBUTTON from novelwriter.types import ( QtAlignCenterTop, QtKeepAnchor, QtMouseLeft, QtMoveAnchor, - QtScrollAlwaysOff, QtScrollAsNeeded + QtScrollAlwaysOff, QtScrollAsNeeded, QtSelectBlock, QtSelectDocument, + QtSelectWord ) logger = logging.getLogger(__name__) @@ -287,9 +288,9 @@ class GuiDocViewer(QTextBrowser): elif action == nwDocAction.COPY: self.copy() elif action == nwDocAction.SEL_ALL: - self._makeSelection(QTextCursor.SelectionType.Document) + self._makeSelection(QtSelectDocument) elif action == nwDocAction.SEL_PARA: - self._makeSelection(QTextCursor.SelectionType.BlockUnderCursor) + self._makeSelection(QtSelectBlock) else: logger.debug("Unknown or unsupported document action '%s'", action) return False @@ -400,14 +401,10 @@ class GuiDocViewer(QTextBrowser): action.triggered.connect(qtLambda(self.docAction, nwDocAction.SEL_ALL)) action = qtAddAction(ctxMenu, self.tr("Select Word")) - action.triggered.connect(qtLambda( - self._makePosSelection, QTextCursor.SelectionType.WordUnderCursor, point - )) + action.triggered.connect(qtLambda(self._makePosSelection, QtSelectWord, point)) action = qtAddAction(ctxMenu, self.tr("Select Paragraph")) - action.triggered.connect(qtLambda( - self._makePosSelection, QTextCursor.SelectionType.BlockUnderCursor, point - )) + action.triggered.connect(qtLambda(self._makePosSelection, QtSelectBlock, point)) # Open the context menu if viewport := self.viewport(): @@ -466,7 +463,7 @@ class GuiDocViewer(QTextBrowser): cursor.clearSelection() cursor.select(selType) - if selType == QTextCursor.SelectionType.BlockUnderCursor: + if selType == QtSelectBlock: # This selection mode also selects the preceding paragraph # separator, which we want to avoid. posS = cursor.selectionStart() diff --git a/novelwriter/types.py b/novelwriter/types.py index 0d5e64d8..f972e04f 100644 --- a/novelwriter/types.py +++ b/novelwriter/types.py @@ -109,9 +109,14 @@ QtRoleReject = QDialogButtonBox.ButtonRole.RejectRole QtKeepAnchor = QTextCursor.MoveMode.KeepAnchor QtMoveAnchor = QTextCursor.MoveMode.MoveAnchor + QtMoveLeft = QTextCursor.MoveOperation.Left QtMoveRight = QTextCursor.MoveOperation.Right +QtSelectWord = QTextCursor.SelectionType.WordUnderCursor +QtSelectBlock = QTextCursor.SelectionType.BlockUnderCursor +QtSelectDocument = QTextCursor.SelectionType.Document + QtImCursorRectangle = Qt.InputMethodQuery.ImCursorRectangle # Size Policy diff --git a/tests/test_gui/test_gui_doceditor.py b/tests/test_gui/test_gui_doceditor.py index ba03b103..4a3c97d9 100644 --- a/tests/test_gui/test_gui_doceditor.py +++ b/tests/test_gui/test_gui_doceditor.py @@ -42,7 +42,8 @@ from novelwriter.gui.dochighlight import TextBlockData from novelwriter.text.counting import standardCounter from novelwriter.types import ( QtAlignJustify, QtAlignLeft, QtKeepAnchor, QtModCtrl, QtModNone, - QtMouseLeft, QtMoveAnchor, QtMoveRight, QtScrollAlwaysOff, QtScrollAsNeeded + QtMouseLeft, QtMoveAnchor, QtMoveRight, QtScrollAlwaysOff, + QtScrollAsNeeded, QtSelectDocument, QtSelectWord ) from tests.mocked import causeOSError @@ -58,7 +59,7 @@ def getMenuForPos(editor: GuiDocEditor, pos: int, select: bool = False) -> QMenu cursor = editor.textCursor() cursor.setPosition(pos) if select: - cursor.select(QTextCursor.SelectionType.WordUnderCursor) + cursor.select(QtSelectWord) editor.setTextCursor(cursor) editor._openContextFromCursor() for obj in editor.children(): @@ -1239,7 +1240,7 @@ def testGuiEditor_TextManipulation(qtbot, nwGUI, projPath, ipsumText, mockRnd): docEditor.setCursorPosition(45) assert len(docEditor._selectedBlocks(cursor)) == 0 - cursor.select(QTextCursor.SelectionType.Document) + cursor.select(QtSelectDocument) assert len(docEditor._selectedBlocks(cursor)) == 15 # Remove All @@ -2093,7 +2094,7 @@ def testGuiEditor_Search(qtbot, monkeypatch, nwGUI, prjLipsum): # Select the Word "est" docEditor.setCursorPosition(663) - docEditor._makeSelection(QTextCursor.SelectionType.WordUnderCursor) + docEditor._makeSelection(QtSelectWord) cursor = docEditor.textCursor() assert cursor.selectedText() == "est" @@ -2223,7 +2224,7 @@ def testGuiEditor_Search(qtbot, monkeypatch, nwGUI, prjLipsum): # Close search and select "est" again docSearch.cancelSearch.activate(QAction.ActionEvent.Trigger) docEditor.setCursorPosition(663) - docEditor._makeSelection(QTextCursor.SelectionType.WordUnderCursor) + docEditor._makeSelection(QtSelectWord) cursor = docEditor.textCursor() assert cursor.selectedText() == "est" diff --git a/tests/test_gui/test_gui_docviewer.py b/tests/test_gui/test_gui_docviewer.py index ac16173b..06d97a80 100644 --- a/tests/test_gui/test_gui_docviewer.py +++ b/tests/test_gui/test_gui_docviewer.py @@ -27,7 +27,7 @@ import pytest from PyQt6.QtCore import QEvent, QMimeData, QPointF, Qt, QUrl from PyQt6.QtGui import ( QAction, QDesktopServices, QDragEnterEvent, QDragMoveEvent, QDropEvent, - QMouseEvent, QTextCursor + QMouseEvent ) from PyQt6.QtWidgets import QApplication, QMenu, QTextBrowser @@ -35,7 +35,7 @@ from novelwriter import CONFIG, SHARED from novelwriter.common import decodeMimeHandles from novelwriter.enum import nwChange, nwDocAction from novelwriter.formats.toqdoc import ToQTextDocument -from novelwriter.types import QtModNone, QtMouseLeft, QtMouseMiddle +from novelwriter.types import QtModNone, QtMouseLeft, QtMouseMiddle, QtSelectBlock, QtSelectWord from tests.mocked import causeException from tests.tools import C, buildTestProject @@ -89,7 +89,7 @@ def testGuiViewer_Main(qtbot, monkeypatch, nwGUI, prjLipsum): cursor = docViewer.textCursor() cursor.setPosition(100) docViewer.setTextCursor(cursor) - docViewer._makeSelection(QTextCursor.SelectionType.WordUnderCursor) + docViewer._makeSelection(QtSelectWord) clipboard = QApplication.clipboard() assert clipboard is not None @@ -117,9 +117,7 @@ def testGuiViewer_Main(qtbot, monkeypatch, nwGUI, prjLipsum): cursor.clearSelection() docViewer.setTextCursor(cursor) - docViewer._makePosSelection( - QTextCursor.SelectionType.BlockUnderCursor, docViewer.cursorRect().center() - ) + docViewer._makePosSelection(QtSelectBlock, docViewer.cursorRect().center()) cursor = docViewer.textCursor() assert cursor.selectedText() == ( "Synopsis: Aenean ut placerat velit. Etiam laoreet ullamcorper risus, " @@ -159,7 +157,7 @@ def testGuiViewer_Main(qtbot, monkeypatch, nwGUI, prjLipsum): cursor = docViewer.textCursor() cursor.setPosition(27) docViewer.setTextCursor(cursor) - docViewer._makeSelection(QTextCursor.SelectionType.WordUnderCursor) + docViewer._makeSelection(QtSelectWord) with monkeypatch.context() as mp: mp.setattr(QMenu, "exec", mockExec) docViewer._openContextMenu(docViewer.cursorRect().center()) @@ -169,7 +167,7 @@ def testGuiViewer_Main(qtbot, monkeypatch, nwGUI, prjLipsum): cursor = docViewer.textCursor() cursor.setPosition(27) docViewer.setTextCursor(cursor) - docViewer._makeSelection(QTextCursor.SelectionType.WordUnderCursor) + docViewer._makeSelection(QtSelectWord) rect = docViewer.cursorRect() docViewer._linkClicked(QUrl("#tag_bod")) assert docViewer.docHandle == "4c4f28287af27" diff --git a/tests/test_gui/test_gui_mainmenu.py b/tests/test_gui/test_gui_mainmenu.py index 9c327cda..0d6344bc 100644 --- a/tests/test_gui/test_gui_mainmenu.py +++ b/tests/test_gui/test_gui_mainmenu.py @@ -24,14 +24,14 @@ from unittest.mock import MagicMock import pytest -from PyQt6.QtGui import QAction, QDesktopServices, QTextBlock, QTextCursor +from PyQt6.QtGui import QAction, QDesktopServices, QTextBlock from PyQt6.QtWidgets import QFileDialog, QMessageBox from novelwriter import CONFIG, SHARED from novelwriter.constants import nwKeyWords, nwShortcode, nwStats, nwUnicode from novelwriter.enum import nwDocAction, nwDocInsert from novelwriter.gui.doceditor import GuiDocEditor -from novelwriter.types import QtKeepAnchor, QtMoveRight +from novelwriter.types import QtKeepAnchor, QtMoveRight, QtSelectWord from tests.tools import C, buildTestProject, writeFile @@ -188,7 +188,7 @@ def testGuiMainMenu_EditFormat(qtbot, monkeypatch, nwGUI, prjLipsum): # Cut, Copy and Paste docEditor.setCursorPosition(x) - docEditor._makeSelection(QTextCursor.SelectionType.WordUnderCursor) + docEditor._makeSelection(QtSelectWord) mainMenu.aEditCut.activate(QAction.ActionEvent.Trigger) assert docEditor.getText()[x:x+50] == ( @@ -201,7 +201,7 @@ def testGuiMainMenu_EditFormat(qtbot, monkeypatch, nwGUI, prjLipsum): ) docEditor.setCursorPosition(x) - docEditor._makeSelection(QTextCursor.SelectionType.WordUnderCursor) + docEditor._makeSelection(QtSelectWord) mainMenu.aEditCopy.activate(QAction.ActionEvent.Trigger) assert docEditor.getText()[x:x+50] == ( From 3d55b49ee65c30300d457bc11d4b28ff214621b1 Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Sat, 4 Oct 2025 18:48:51 +0200 Subject: [PATCH 18/79] Update MacOS tests to run on latest --- .github/workflows/test_mac.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test_mac.yml b/.github/workflows/test_mac.yml index 8465f75b..2070f157 100644 --- a/.github/workflows/test_mac.yml +++ b/.github/workflows/test_mac.yml @@ -12,7 +12,7 @@ on: jobs: testMac: - runs-on: macos-13 + runs-on: macos-latest steps: - name: Python Setup uses: actions/setup-python@v5 From 04198f3ede4449f69f4464efdd549a26aab4f078 Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Sun, 5 Oct 2025 17:13:08 +0200 Subject: [PATCH 19/79] Remove quiet flag from index rebuild --- novelwriter/guimain.py | 8 ++++---- tests/test_gui/test_gui_guimain.py | 2 +- tests/test_gui/test_gui_statusbar.py | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/novelwriter/guimain.py b/novelwriter/guimain.py index 91d7ff59..ac065b03 100644 --- a/novelwriter/guimain.py +++ b/novelwriter/guimain.py @@ -497,7 +497,8 @@ class GuiMain(QMainWindow): # Check if we need to rebuild the index if SHARED.project.index.indexBroken: - SHARED.info(self.tr("The project index is outdated or broken. Rebuilding index.")) + if not SHARED.project.index.indexUpgrade: + SHARED.info(self.tr("The project index is broken. Rebuilding index.")) self.rebuildIndex() # Make sure the changed status is set to false on things opened @@ -729,7 +730,7 @@ class GuiMain(QMainWindow): return - def rebuildIndex(self, beQuiet: bool = False) -> None: + def rebuildIndex(self) -> None: """Rebuild the entire index.""" if SHARED.hasProject: logger.info("Rebuilding index ...") @@ -746,8 +747,7 @@ class GuiMain(QMainWindow): self._updateStatusWordCount() QApplication.restoreOverrideCursor() - if not beQuiet: - SHARED.info(self.tr("The project index has been successfully rebuilt.")) + SHARED.info(self.tr("The project index has been successfully rebuilt.")) ## # Main Dialogs diff --git a/tests/test_gui/test_gui_guimain.py b/tests/test_gui/test_gui_guimain.py index 258d5e0a..092a0486 100644 --- a/tests/test_gui/test_gui_guimain.py +++ b/tests/test_gui/test_gui_guimain.py @@ -713,7 +713,7 @@ def testGuiMain_Features(qtbot, monkeypatch, nwGUI, projPath, mockRnd): cHandle = SHARED.project.newFile("Jane", C.hCharRoot) newDoc = SHARED.project.storage.getDocument(cHandle) newDoc.writeDocument("# Jane\n\n@tag: Jane\n\n") - nwGUI.rebuildIndex(beQuiet=True) + nwGUI.rebuildIndex() assert SHARED.focusMode is False diff --git a/tests/test_gui/test_gui_statusbar.py b/tests/test_gui/test_gui_statusbar.py index 19c3f277..8bbb6fbe 100644 --- a/tests/test_gui/test_gui_statusbar.py +++ b/tests/test_gui/test_gui_statusbar.py @@ -36,7 +36,7 @@ def testGuiStatusBar_Main(qtbot, monkeypatch, nwGUI, projPath, mockRnd): cHandle = SHARED.project.newFile("A Note", C.hCharRoot) newDoc = SHARED.project.storage.getDocument(cHandle) newDoc.writeDocument("# A Note\n\n") - nwGUI.rebuildIndex(beQuiet=True) + nwGUI.rebuildIndex() status = nwGUI.mainStatus From 0344701f1b3c99eb1b43e448fe8089c735dc2bd8 Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Sun, 5 Oct 2025 17:13:40 +0200 Subject: [PATCH 20/79] Add helper function to combine JSON strings as larger JSON file --- novelwriter/common.py | 6 ++++++ tests/test_base/test_base_common.py | 19 +++++++++++++++---- tests/tools.py | 4 ++-- 3 files changed, 23 insertions(+), 6 deletions(-) diff --git a/novelwriter/common.py b/novelwriter/common.py index 8fd9386a..5bd35d50 100644 --- a/novelwriter/common.py +++ b/novelwriter/common.py @@ -560,6 +560,12 @@ def jsonEncode(data: dict | list | tuple, n: int = 0, nmax: int = 0) -> str: return "".join(buffer) +def jsonCombine(data: dict[str, str]) -> str: + """Combine multiple already packed JSON strings.""" + payload = ",\n".join(f' "{k}": {v}' for k, v in data.items()) + return f"{{\n{payload}\n}}\n" + + ## # XML Helpers ## diff --git a/tests/test_base/test_base_common.py b/tests/test_base/test_base_common.py index 95d339df..7ba2ddb0 100644 --- a/tests/test_base/test_base_common.py +++ b/tests/test_base/test_base_common.py @@ -36,10 +36,10 @@ from novelwriter.common import ( describeFont, elide, encodeMimeHandles, firstFloat, fontMatcher, formatFileFilter, formatInt, formatTime, formatTimeStamp, formatVersion, fuzzyTime, getFileSize, hexToInt, isHandle, isItemClass, isItemLayout, - isItemType, isListInstance, isTitleTag, jsonEncode, makeFileNameSafe, - minmax, numberToRoman, openExternalPath, processDialogSymbols, - readTextFile, simplified, transferCase, uniqueCompact, utf16CharMap, - xmlElement, xmlIndent, xmlSubElem, yesNo + isItemType, isListInstance, isTitleTag, jsonCombine, jsonEncode, + makeFileNameSafe, minmax, numberToRoman, openExternalPath, + processDialogSymbols, readTextFile, simplified, transferCase, + uniqueCompact, utf16CharMap, xmlElement, xmlIndent, xmlSubElem, yesNo ) from novelwriter.enum import nwItemClass @@ -651,6 +651,17 @@ def testBaseCommon_jsonEncode(): ) +@pytest.mark.base +def testBaseCommon_jsonCombine(): + """Test the jsonCombine function.""" + assert jsonCombine({"a": "[1, 2]", "b": "[3, 4]"}) == ( + '{\n' + ' "a": [1, 2],\n' + ' "b": [3, 4]\n' + '}\n' + ) + + @pytest.mark.base def testBaseCommon_xmlIndent(): """Test the xmlIndent function.""" diff --git a/tests/tools.py b/tests/tools.py index 9eec20d0..5d0b7c84 100644 --- a/tests/tools.py +++ b/tests/tools.py @@ -66,8 +66,8 @@ class C: def cmpFiles( fileOne: str | Path, fileTwo: str | Path, - ignoreLines: list | None = None, - ignoreStart: tuple | None = None + ignoreLines: list[int] | None = None, + ignoreStart: tuple[str] | None = None ) -> bool: """Compare two files, with optional line ignore.""" if ignoreLines is None: From 850976335c4d8bd58285f309c80f0768936ec09c Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Sun, 5 Oct 2025 17:18:42 +0200 Subject: [PATCH 21/79] Don't show index rebuild dialog when just upgrading index --- novelwriter/core/index.py | 35 +++++++++++++------ .../coreIndex_LoadSave_tagsIndex.json | 4 +++ tests/test_core/test_core_index.py | 2 +- 3 files changed, 29 insertions(+), 12 deletions(-) diff --git a/novelwriter/core/index.py b/novelwriter/core/index.py index 2a9aba3a..d1955c45 100644 --- a/novelwriter/core/index.py +++ b/novelwriter/core/index.py @@ -33,8 +33,10 @@ from pathlib import Path from time import time from typing import TYPE_CHECKING -from novelwriter import SHARED -from novelwriter.common import isHandle, isItemClass, isTitleTag, jsonEncode +from novelwriter import SHARED, __hexversion__ +from novelwriter.common import ( + formatTimeStamp, isHandle, isItemClass, isTitleTag, jsonCombine, jsonEncode +) from novelwriter.constants import nwFiles, nwKeyWords, nwStyles from novelwriter.core.indexdata import NOTE_TYPES, TT_NONE, IndexHeading, IndexNode, T_NoteTypes from novelwriter.core.novelmodel import NovelModel @@ -82,6 +84,11 @@ class Index: a rebuild of the index data. """ + __slots__ = ( + "_indexBroken", "_indexChange", "_indexUpgrade", "_itemIndex", "_novelExtra", + "_novelModels", "_project", "_rootChange", "_tagsIndex", + ) + def __init__(self, project: NWProject) -> None: self._project = project @@ -90,6 +97,7 @@ class Index: self._tagsIndex = TagsIndex() self._itemIndex = ItemIndex(project, self._tagsIndex) self._indexBroken = False + self._indexUpgrade = False # Models self._novelModels: dict[str, NovelModel] = {} @@ -110,6 +118,10 @@ class Index: def indexBroken(self) -> bool: return self._indexBroken + @property + def indexUpgrade(self) -> bool: + return self._indexUpgrade + ## # Getters ## @@ -241,6 +253,8 @@ class Index: return False try: + meta = data.get("novelWriter.meta", {}) + self._indexUpgrade = meta.get("version") != __hexversion__ self._tagsIndex.unpackData(data["novelWriter.tagsIndex"]) self._itemIndex.unpackData(data["novelWriter.itemIndex"]) except Exception: @@ -273,23 +287,22 @@ class Index: return False logger.debug("Saving index file") - tStart = time() + start = time() try: - tagsIndex = jsonEncode(self._tagsIndex.packData(), n=1, nmax=2) - itemIndex = jsonEncode(self._itemIndex.packData(), n=1, nmax=4) + meta = {"version": __hexversion__, "timestamp": formatTimeStamp(start)} with open(indexFile, mode="w+", encoding="utf-8") as outFile: - outFile.write("{\n") - outFile.write(f' "novelWriter.tagsIndex": {tagsIndex},\n') - outFile.write(f' "novelWriter.itemIndex": {itemIndex}\n') - outFile.write("}\n") - + outFile.write(jsonCombine({ + "novelWriter.meta": jsonEncode(meta, n=1), + "novelWriter.tagsIndex": jsonEncode(self._tagsIndex.packData(), n=1, nmax=2), + "novelWriter.itemIndex": jsonEncode(self._itemIndex.packData(), n=1, nmax=4), + })) except Exception: logger.error("Failed to save index file") logException() return False - logger.debug("Index saved in %.3f ms", (time() - tStart)*1000) + logger.debug("Index saved in %.3f ms", (time() - start)*1000) return True diff --git a/tests/reference/coreIndex_LoadSave_tagsIndex.json b/tests/reference/coreIndex_LoadSave_tagsIndex.json index c8365601..af9474f0 100644 --- a/tests/reference/coreIndex_LoadSave_tagsIndex.json +++ b/tests/reference/coreIndex_LoadSave_tagsIndex.json @@ -1,4 +1,8 @@ { + "novelWriter.meta": { + "version": "0x020800a2", + "timestamp": "2025-10-05 17:06:58" + }, "novelWriter.tagsIndex": { "bod": {"name": "Bod", "display": "Nobody Owens", "handle": "4c4f28287af27", "heading": "T0001", "class": "CHARACTER"}, "main": {"name": "Main", "display": "Main", "handle": "2426c6f0ca922", "heading": "T0001", "class": "PLOT"}, diff --git a/tests/test_core/test_core_index.py b/tests/test_core/test_core_index.py index 332c27c3..9032aed8 100644 --- a/tests/test_core/test_core_index.py +++ b/tests/test_core/test_core_index.py @@ -157,7 +157,7 @@ def testCoreIndex_LoadSave(qtbot, monkeypatch, prjLipsum, nwGUI, tstPaths): # Check File copyfile(projFile, testFile) - assert cmpFiles(testFile, compFile) + assert cmpFiles(testFile, compFile, ignoreLines=[3, 4]) # Write an empty index file and load it projFile.write_text("{}", encoding="utf-8") From 050e3b09ad46c16703420e53363e1d6cdfac9f8c Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Sun, 5 Oct 2025 17:39:48 +0200 Subject: [PATCH 22/79] Fix test coverage --- novelwriter/guimain.py | 2 +- tests/test_gui/test_gui_guimain.py | 7 ++++--- tests/tools.py | 2 +- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/novelwriter/guimain.py b/novelwriter/guimain.py index ac065b03..acf6114b 100644 --- a/novelwriter/guimain.py +++ b/novelwriter/guimain.py @@ -498,7 +498,7 @@ class GuiMain(QMainWindow): # Check if we need to rebuild the index if SHARED.project.index.indexBroken: if not SHARED.project.index.indexUpgrade: - SHARED.info(self.tr("The project index is broken. Rebuilding index.")) + SHARED.warn(self.tr("The project index is broken. Rebuilding index.")) self.rebuildIndex() # Make sure the changed status is set to false on things opened diff --git a/tests/test_gui/test_gui_guimain.py b/tests/test_gui/test_gui_guimain.py index 092a0486..0e15b71e 100644 --- a/tests/test_gui/test_gui_guimain.py +++ b/tests/test_gui/test_gui_guimain.py @@ -32,7 +32,8 @@ from PyQt6.QtCore import Qt from PyQt6.QtGui import QPalette from PyQt6.QtWidgets import QInputDialog, QMessageBox -from novelwriter import CONFIG, SHARED +from novelwriter import CONFIG, SHARED, __hexversion__ +from novelwriter.common import jsonEncode from novelwriter.config import DEF_GUI_DARK, DEF_GUI_LIGHT from novelwriter.constants import nwFiles from novelwriter.dialogs.editlabel import GuiEditLabel @@ -825,11 +826,11 @@ def testGuiMain_OpenClose(qtbot, monkeypatch, nwGUI, projPath, fncPath, mockRnd) nwGUI.viewDocument(C.hTitlePage) # Handle broken index on project open + idxData = jsonEncode({"novelWriter.meta": {"version": __hexversion__}}) nwGUI.closeProject() idxPath: Path = projPath / "meta" / nwFiles.INDEX_FILE assert idxPath.read_text(encoding="utf-8") != "{}" - idxPath.write_text("{}", encoding="utf-8") - assert idxPath.read_text(encoding="utf-8") == "{}" + idxPath.write_text(idxData, encoding="utf-8") nwGUI.openProject(projPath) nwGUI.saveProject() diff --git a/tests/tools.py b/tests/tools.py index 5d0b7c84..bca68899 100644 --- a/tests/tools.py +++ b/tests/tools.py @@ -67,7 +67,7 @@ def cmpFiles( fileOne: str | Path, fileTwo: str | Path, ignoreLines: list[int] | None = None, - ignoreStart: tuple[str] | None = None + ignoreStart: tuple | None = None ) -> bool: """Compare two files, with optional line ignore.""" if ignoreLines is None: From a5513cc50462b47cbe4792a3400471a316920cd5 Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Mon, 13 Oct 2025 19:23:56 +0200 Subject: [PATCH 23/79] Add Python 3.14 and update actions --- .github/workflows/build_assets.yml | 4 ++-- .github/workflows/build_linux.yml | 4 ++-- .github/workflows/build_mac.yml | 4 ++-- .github/workflows/build_win.yml | 4 ++-- .github/workflows/build_win_launcher.yml | 2 +- .github/workflows/i18n.yml | 7 +++---- .github/workflows/syntax.yml | 4 ++-- .github/workflows/test_linux.yml | 6 +++--- .github/workflows/test_mac.yml | 4 ++-- .github/workflows/test_win.yml | 4 ++-- 10 files changed, 21 insertions(+), 22 deletions(-) diff --git a/.github/workflows/build_assets.yml b/.github/workflows/build_assets.yml index d9959e37..291da081 100644 --- a/.github/workflows/build_assets.yml +++ b/.github/workflows/build_assets.yml @@ -7,7 +7,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Python Setup - uses: actions/setup-python@v5 + uses: actions/setup-python@v6 with: python-version: "3.13" architecture: x64 @@ -18,7 +18,7 @@ jobs: sudo apt install qttools5-dev-tools latexmk texlive texlive-latex-extra - name: Checkout Source - uses: actions/checkout@v4 + uses: actions/checkout@v5 - name: Install Packages (pip) run: pip install -U -r requirements.txt -r docs/requirements.txt diff --git a/.github/workflows/build_linux.yml b/.github/workflows/build_linux.yml index 64f13d21..b1e62800 100644 --- a/.github/workflows/build_linux.yml +++ b/.github/workflows/build_linux.yml @@ -17,7 +17,7 @@ jobs: LINUX_ARCH: "x86_64" steps: - name: Python Setup - uses: actions/setup-python@v5 + uses: actions/setup-python@v6 with: python-version: "3.13" architecture: x64 @@ -31,7 +31,7 @@ jobs: run: pip install python-appimage setuptools - name: Checkout Source - uses: actions/checkout@v4 + uses: actions/checkout@v5 - name: Download Artifacts uses: actions/download-artifact@v4 diff --git a/.github/workflows/build_mac.yml b/.github/workflows/build_mac.yml index 0b08977c..1d708561 100644 --- a/.github/workflows/build_mac.yml +++ b/.github/workflows/build_mac.yml @@ -15,7 +15,7 @@ jobs: MINICONDA_ARCH: x86_64 steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v5 - name: Download Artifacts uses: actions/download-artifact@v4 @@ -46,7 +46,7 @@ jobs: MINICONDA_ARCH: arm64 steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v5 - name: Download Artifacts uses: actions/download-artifact@v4 diff --git a/.github/workflows/build_win.yml b/.github/workflows/build_win.yml index bee832c7..5c7bc616 100644 --- a/.github/workflows/build_win.yml +++ b/.github/workflows/build_win.yml @@ -11,13 +11,13 @@ jobs: runs-on: windows-2022 steps: - name: Python Setup - uses: actions/setup-python@v5 + uses: actions/setup-python@v6 with: python-version: "3.13" architecture: x64 - name: Checkout Source - uses: actions/checkout@v4 + uses: actions/checkout@v5 - name: Download Assets uses: actions/download-artifact@v4 diff --git a/.github/workflows/build_win_launcher.yml b/.github/workflows/build_win_launcher.yml index 901f8a0d..59314e25 100644 --- a/.github/workflows/build_win_launcher.yml +++ b/.github/workflows/build_win_launcher.yml @@ -7,7 +7,7 @@ jobs: runs-on: windows-latest steps: - name: Checkout Source - uses: actions/checkout@v4 + uses: actions/checkout@v5 - name: Build Launcher id: build diff --git a/.github/workflows/i18n.yml b/.github/workflows/i18n.yml index 45d91b07..82a22f10 100644 --- a/.github/workflows/i18n.yml +++ b/.github/workflows/i18n.yml @@ -10,7 +10,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Python Setup - uses: actions/setup-python@v5 + uses: actions/setup-python@v6 with: python-version: "3.13" architecture: x64 @@ -19,14 +19,13 @@ jobs: sudo apt update sudo apt install qttools5-dev-tools - name: Checkout Source - uses: actions/checkout@v4 + uses: actions/checkout@v5 - name: Build Assets run: python pkgutils.py qtlrelease - name: Upload Artifacts uses: actions/upload-artifact@v4 with: name: nw-i18n - path: | - novelwriter/assets/i18n/*.qm + path: novelwriter/assets/i18n/*.qm if-no-files-found: error retention-days: 7 diff --git a/.github/workflows/syntax.yml b/.github/workflows/syntax.yml index e66f3104..d91c58fd 100644 --- a/.github/workflows/syntax.yml +++ b/.github/workflows/syntax.yml @@ -15,12 +15,12 @@ jobs: runs-on: ubuntu-latest steps: - name: Python Setup - uses: actions/setup-python@v5 + uses: actions/setup-python@v6 with: python-version: 3 architecture: x64 - name: Checkout Source - uses: actions/checkout@v4 + uses: actions/checkout@v5 - name: Install Dependencies run: pip install -r requirements.txt -r requirements-dev.txt - name: Ruff Check diff --git a/.github/workflows/test_linux.yml b/.github/workflows/test_linux.yml index b13e19c1..46f9b437 100644 --- a/.github/workflows/test_linux.yml +++ b/.github/workflows/test_linux.yml @@ -14,12 +14,12 @@ jobs: testLinux: strategy: matrix: - python-version: ["3.10", "3.11", "3.12", "3.13"] + python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"] fail-fast: false runs-on: ubuntu-latest steps: - name: Python Setup - uses: actions/setup-python@v5 + uses: actions/setup-python@v6 with: python-version: ${{ matrix.python-version }} architecture: x64 @@ -28,7 +28,7 @@ jobs: sudo apt update sudo apt install libenchant-2-dev qttools5-dev-tools - name: Checkout Source - uses: actions/checkout@v4 + uses: actions/checkout@v5 - name: Install Dependencies (pip) run: | pip install -U -r requirements.txt -r tests/requirements.txt diff --git a/.github/workflows/test_mac.yml b/.github/workflows/test_mac.yml index 2070f157..7ecb5116 100644 --- a/.github/workflows/test_mac.yml +++ b/.github/workflows/test_mac.yml @@ -15,7 +15,7 @@ jobs: runs-on: macos-latest steps: - name: Python Setup - uses: actions/setup-python@v5 + uses: actions/setup-python@v6 with: python-version: "3.13" architecture: x64 @@ -23,7 +23,7 @@ jobs: run: | brew install enchant - name: Checkout Source - uses: actions/checkout@v4 + uses: actions/checkout@v5 - name: Install Dependencies (pip) run: | pip install -U pyobjc -r requirements.txt -r tests/requirements.txt diff --git a/.github/workflows/test_win.yml b/.github/workflows/test_win.yml index f2887216..1cdca205 100644 --- a/.github/workflows/test_win.yml +++ b/.github/workflows/test_win.yml @@ -15,12 +15,12 @@ jobs: runs-on: windows-latest steps: - name: Python Setup - uses: actions/setup-python@v5 + uses: actions/setup-python@v6 with: python-version: "3.13" architecture: x64 - name: Checkout Source - uses: actions/checkout@v4 + uses: actions/checkout@v5 - name: Install Dependencies (pip) run: | pip install -U -r requirements.txt -r tests/requirements.txt From b04dadaa4566a29aa61aff525b17fd4bbceec27a Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Mon, 13 Oct 2025 19:33:26 +0200 Subject: [PATCH 24/79] Add Python 3.14 to classifiers --- pyproject.toml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index ce9fe191..f10c3529 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,5 +1,5 @@ [build-system] -requires = ["setuptools >= 77.0.3"] +requires = ["setuptools>=77.0.3"] build-backend = "setuptools.build_meta" [project] @@ -17,6 +17,7 @@ classifiers = [ "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", + "Programming Language :: Python :: 3.14", "Programming Language :: Python :: Implementation :: CPython", "Development Status :: 5 - Production/Stable", "Operating System :: OS Independent", From 47ecf2e8861d9197c54ac1bdddfb00b98b3660ef Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Wed, 15 Oct 2025 09:03:51 +0200 Subject: [PATCH 25/79] Fix typos --- docs/source/usage/introduction.rst | 6 +++--- docs/source/usage/organising_project.rst | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/source/usage/introduction.rst b/docs/source/usage/introduction.rst index 47dceb98..0dc59f96 100644 --- a/docs/source/usage/introduction.rst +++ b/docs/source/usage/introduction.rst @@ -6,8 +6,8 @@ Introduction .. _Markdown: https://en.wikipedia.org/wiki/Markdown -In a nutshell, novelWriter is a plain text editor that lets you organise one or more novels and -associated notes as many smaller documents. You can at any time generate standard document formats +In a nutshell, novelWriter is a plain text editor that lets you organise one or more novels, and +associated notes, as many smaller documents. You can at any time generate standard document formats from these plain text documents. Whether it is an outline of your story, a draft, a complete manuscript, or even a collection of your character notes or other notes. @@ -51,7 +51,7 @@ comments, and an auto-complete menu can help you here too. More about this later writing. It is also *not* a full-featured Markdown editor. In addition, novelWriter is not intended as a tool for organising research for writing, and - therefore lacks formatting features you may need for this purpose. The notes feature in is + therefore lacks formatting features you may need for this purpose. The notes feature is mainly intended for character profiles and plot outlines. It is recommended to use a proper note-taking tool for research. This is anyway more practical as you may use the same research for multiple projects. diff --git a/docs/source/usage/organising_project.rst b/docs/source/usage/organising_project.rst index 4f4e3c9b..a3809d7a 100644 --- a/docs/source/usage/organising_project.rst +++ b/docs/source/usage/organising_project.rst @@ -15,7 +15,7 @@ side of the main window. Each line in the project tree shows the name of each item, its word count (or alternatively character count), an icon for :ref:`docs_usage_project_active`, and a custom icon for -:ref:`docs_usage_project_status` of each item. These latter two are covered alter in this section. +:ref:`docs_usage_project_status` of each item. These latter two are covered later in this section. You can add, view and edit documents in the project tree by right-clicking on them. Some features are also located in the buttons along the top, next to the **Project Content** label. @@ -47,8 +47,8 @@ Root Folder Types **Novel** (Story) This is where you put the documents that are part of your story. You can create multiple Novel - folders if you wish, but various parts of the application assumes each Novel folder belong to - one novel. + folders if you wish, but various parts of the application assumes each Novel folder belongs to + only one novel. The Novel folder is somewhat special in that it can contain documents for chapters, scenes and story partitions. How this is indicated is covered in the section :ref:`docs_usage_headings`. From c1968ba4e34596e4b01b688ec51f6916dfe05032 Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Sat, 18 Oct 2025 17:41:25 +0200 Subject: [PATCH 26/79] Add dependency groups to pyproject and drop pytest-cov dependency --- pyproject.toml | 78 +++++++++++++++++++++++++++++--------------------- run_tests.py | 29 ++++++++++++------- 2 files changed, 64 insertions(+), 43 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index f10c3529..1299f261 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,11 +4,9 @@ build-backend = "setuptools.build_meta" [project] name = "novelWriter" -authors = [ - {name = "Veronica Berglyd Olsen", email = "code@vkbo.net"}, -] +authors = [{ name = "Veronica Berglyd Olsen", email = "code@vkbo.net" }] description = "A plain text editor for planning and writing novels" -readme = {file = "setup/description_pypi.md", content-type = "text/markdown"} +readme = { file = "setup/description_pypi.md", content-type = "text/markdown" } license = "GPL-3.0-or-later AND Apache-2.0 AND CC-BY-4.0" license-files = ["LICENSE.md", "setup/LICENSE-Apache-2.0.txt"] classifiers = [ @@ -26,12 +24,28 @@ classifiers = [ "Topic :: Text Editors", ] requires-python = ">=3.10" -dependencies = [ - "pyqt6>=6.4", - "pyenchant>=3.0.0", -] +dependencies = ["pyqt6>=6.4", "pyenchant>=3.0.0"] dynamic = ["version"] +[dependency-groups] +dev = [ + { include-group = "docs" }, + { include-group = "test" }, + { include-group = "lint" }, +] +docs = [ + "docutils>=0.17.1", + "pygments>=2.7", + "sphinx-book-theme", + "sphinx-copybutton", + "sphinx-design", + "sphinx-favicon", + "sphinx-intl", + "sphinx>=5.0", +] +test = ["coverage>=7.2.0", "pytest-qt", "pytest-timeout", "pytest>=6.0.0"] +lint = ["isort", "pyright", "ruff"] + [project.urls] Homepage = "https://novelwriter.io" Documentation = "https://docs.novelwriter.io" @@ -42,13 +56,13 @@ Issues = "https://github.com/vkbo/novelWriter/issues" novelwriter = "novelwriter:main" [tool.setuptools.dynamic] -version = {attr = "novelwriter.__version__"} +version = { attr = "novelwriter.__version__" } [tool.setuptools.packages.find] include = ["novelwriter*"] [tool.isort] -py_version="310" +py_version = "310" line_length = 99 wrap_length = 79 multi_line_output = 5 @@ -64,26 +78,26 @@ preview = true # Rules: https://docs.astral.sh/ruff/rules select = [ - "A", # flake8-builtins (A) - "ANN", # flake8-annotations (ANN) - "B", # flake8-bugbear (B) - "D", # pydocstyle (D) - "E", # pycodestyle (E) - "F", # Pyflakes (F) - "FA", # flake8-future-annotations (FA) - "PERF", # Perflint (PERF) - "PLC", # Pylint Convention (PLC) - "PLE", # Pylint Error (PLE) - "PLR17", # Refactor (PLR) - Only PLR17xx - "PLW", # Pylint Warning (PLW) - "Q", # flake8-quotes (Q) - "RET", # flake8-return (RET) - "RUF", # Ruff-specific rules (RUF) - "SLF", # flake8-self (SLF) - "SLOT", # flake8-slots (SLOT) - "TC", # flake8-type-checking (TC) - "UP", # pyupgrade (UP) - "W", # pycodestyle (W) + "A", # flake8-builtins (A) + "ANN", # flake8-annotations (ANN) + "B", # flake8-bugbear (B) + "D", # pydocstyle (D) + "E", # pycodestyle (E) + "F", # Pyflakes (F) + "FA", # flake8-future-annotations (FA) + "PERF", # Perflint (PERF) + "PLC", # Pylint Convention (PLC) + "PLE", # Pylint Error (PLE) + "PLR17", # Refactor (PLR) - Only PLR17xx + "PLW", # Pylint Warning (PLW) + "Q", # flake8-quotes (Q) + "RET", # flake8-return (RET) + "RUF", # Ruff-specific rules (RUF) + "SLF", # flake8-self (SLF) + "SLOT", # flake8-slots (SLOT) + "TC", # flake8-type-checking (TC) + "UP", # pyupgrade (UP) + "W", # pycodestyle (W) ] ignore = [ "ANN401", # any-type @@ -149,6 +163,4 @@ branch = false [tool.coverage.report] precision = 2 -exclude_also = [ - "if TYPE_CHECKING:" -] +exclude_also = ["if TYPE_CHECKING:"] diff --git a/run_tests.py b/run_tests.py index b9687aa9..f2e91ec7 100755 --- a/run_tests.py +++ b/run_tests.py @@ -3,6 +3,7 @@ import argparse import os +import shlex import subprocess import sys @@ -12,27 +13,35 @@ if __name__ == "__main__": parser.add_argument("-o", action="store_true", help="Run off screen") parser.add_argument("-r", action="store_true", help="Generate reports") parser.add_argument("-t", action="store_true", help="Generate terminal report") - parser.add_argument("-m", help="Test modules") - parser.add_argument("-k", help="Test filters") + parser.add_argument("-u", action="store_true", help="Generate uncovered terminal report") + parser.add_argument("-m", help="Test modules", metavar="MARKEXPR") + parser.add_argument("-k", help="Test filters", metavar="EXPRESSION") args = parser.parse_args() env = os.environ.copy() env["QT_SCALE_FACTOR"] = "1.0" - cmd = [sys.executable, "-m", "pytest", "-vv"] + if args.r or args.t or args.u: + cmd = [sys.executable, "-m"] + else: + cmd = ["coverage", "-m"] + + cmd += ["pytest", "-vv"] if args.o: env["QT_QPA_PLATFORM"] = "offscreen" - if args.r or args.t: - cmd += ["--cov=novelwriter"] - if args.r: - cmd += ["--cov-report=xml", "--cov-report=html"] - if args.t: - cmd += ["--cov-report=term"] if args.m: cmd += ["-m", args.m] if args.k: cmd += ["-k", args.k] - print("Calling:", " ".join(cmd)) + print("Calling:", shlex.join(cmd)) subprocess.call(cmd, env=env) + + if args.r: + subprocess.call(["coverage", "xml"]) + subprocess.call(["coverage", "html"]) + if args.t and not args.u: + subprocess.call(["coverage", "report"]) + if args.u: + subprocess.call(["coverage", "report", "--skip-covered "]) From 10193520a4b83f18e72654fe34987640e121a365 Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Sat, 18 Oct 2025 17:46:47 +0200 Subject: [PATCH 27/79] Update Linux test workflows --- .github/workflows/test_linux.yml | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/.github/workflows/test_linux.yml b/.github/workflows/test_linux.yml index 46f9b437..ec4ec56c 100644 --- a/.github/workflows/test_linux.yml +++ b/.github/workflows/test_linux.yml @@ -23,23 +23,28 @@ jobs: with: python-version: ${{ matrix.python-version }} architecture: x64 + - name: Install Packages (apt) run: | sudo apt update sudo apt install libenchant-2-dev qttools5-dev-tools + - name: Checkout Source uses: actions/checkout@v5 - - name: Install Dependencies (pip) - run: | - pip install -U -r requirements.txt -r tests/requirements.txt + + - name: Install UV + uses: astral-sh/setup-uv@v6 + - name: Run Build Commands run: | - python pkgutils.py qtlrelease - python pkgutils.py sample + uv run --group test pkgutils.py qtlrelease + uv run --group test pkgutils.py sample + - name: Run Tests run: | export QT_QPA_PLATFORM=offscreen - python -m pytest -v --cov=novelwriter --timeout=60 + uv run --group test coverage -m pytest -v --timeout=60 + - name: Upload to Codecov uses: codecov/codecov-action@v5 env: From 08d37dd9e8534fb94fe5df639ce629c02daf69d3 Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Sat, 18 Oct 2025 17:53:01 +0200 Subject: [PATCH 28/79] Fix bug in Linux test workflow --- .github/workflows/test_linux.yml | 6 +++--- run_tests.py | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/test_linux.yml b/.github/workflows/test_linux.yml index ec4ec56c..68b44078 100644 --- a/.github/workflows/test_linux.yml +++ b/.github/workflows/test_linux.yml @@ -37,13 +37,13 @@ jobs: - name: Run Build Commands run: | - uv run --group test pkgutils.py qtlrelease - uv run --group test pkgutils.py sample + uv run --no-dev pkgutils.py qtlrelease + uv run --no-dev pkgutils.py sample - name: Run Tests run: | export QT_QPA_PLATFORM=offscreen - uv run --group test coverage -m pytest -v --timeout=60 + uv run --no-dev --group test coverage run -m pytest -v --timeout=60 - name: Upload to Codecov uses: codecov/codecov-action@v5 diff --git a/run_tests.py b/run_tests.py index f2e91ec7..a2b8e937 100755 --- a/run_tests.py +++ b/run_tests.py @@ -23,9 +23,9 @@ if __name__ == "__main__": env["QT_SCALE_FACTOR"] = "1.0" if args.r or args.t or args.u: - cmd = [sys.executable, "-m"] + cmd = ["coverage", "run", "-m"] else: - cmd = ["coverage", "-m"] + cmd = [sys.executable, "-m"] cmd += ["pytest", "-vv"] if args.o: From b4c9d7c76cb132d0d2f873e19c4d8c990f25f4c9 Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Sat, 18 Oct 2025 17:58:59 +0200 Subject: [PATCH 29/79] Fix coverage reporting --- .github/workflows/test_linux.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test_linux.yml b/.github/workflows/test_linux.yml index 68b44078..0a4c7963 100644 --- a/.github/workflows/test_linux.yml +++ b/.github/workflows/test_linux.yml @@ -44,8 +44,10 @@ jobs: run: | export QT_QPA_PLATFORM=offscreen uv run --no-dev --group test coverage run -m pytest -v --timeout=60 + uv run --no-dev --group test coverage xml - name: Upload to Codecov uses: codecov/codecov-action@v5 - env: - CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} + with: + token: ${{ secrets.CODECOV_TOKEN }} + fail_ci_if_error: true From 4aef01ef323527bc74e910c9965f698b059de588 Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Sat, 18 Oct 2025 18:08:40 +0200 Subject: [PATCH 30/79] Make sure only novelwriter source files are included in coverage --- .github/workflows/test_linux.yml | 2 +- pyproject.toml | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/test_linux.yml b/.github/workflows/test_linux.yml index 0a4c7963..0b6f9d28 100644 --- a/.github/workflows/test_linux.yml +++ b/.github/workflows/test_linux.yml @@ -27,7 +27,7 @@ jobs: - name: Install Packages (apt) run: | sudo apt update - sudo apt install libenchant-2-dev qttools5-dev-tools + sudo apt install qttools5-dev-tools - name: Checkout Source uses: actions/checkout@v5 diff --git a/pyproject.toml b/pyproject.toml index 1299f261..d0e66ec4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -160,6 +160,7 @@ markers = [ [tool.coverage.run] branch = false +source = ["novelwriter"] [tool.coverage.report] precision = 2 From 7a9464c592be3320d4b54406ddbf4b5d73c56e8c Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Sat, 18 Oct 2025 18:20:13 +0200 Subject: [PATCH 31/79] Update MacOS and Linux workflows --- .github/workflows/test_linux.yml | 33 ++++++++++++++++++------------- .github/workflows/test_mac.yml | 34 +++++++++++++++++++++----------- pyproject.toml | 1 + 3 files changed, 43 insertions(+), 25 deletions(-) diff --git a/.github/workflows/test_linux.yml b/.github/workflows/test_linux.yml index 0b6f9d28..efc5bce8 100644 --- a/.github/workflows/test_linux.yml +++ b/.github/workflows/test_linux.yml @@ -14,37 +14,42 @@ jobs: testLinux: strategy: matrix: - python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"] + python-version: + - "3.10" + - "3.11" + - "3.12" + - "3.13" + - "3.14" fail-fast: false runs-on: ubuntu-latest steps: - - name: Python Setup - uses: actions/setup-python@v6 - with: - python-version: ${{ matrix.python-version }} - architecture: x64 - - - name: Install Packages (apt) + - name: Install System Packages run: | sudo apt update sudo apt install qttools5-dev-tools + - name: Install UV and Python + uses: astral-sh/setup-uv@v6 + with: + python-version: ${{ matrix.python-version }} + - name: Checkout Source uses: actions/checkout@v5 - - name: Install UV - uses: astral-sh/setup-uv@v6 + - name: Sync UV + run: | + uv sync --no-dev --group test - name: Run Build Commands run: | - uv run --no-dev pkgutils.py qtlrelease - uv run --no-dev pkgutils.py sample + uv run --no-sync pkgutils.py qtlrelease + uv run --no-sync pkgutils.py sample - name: Run Tests run: | export QT_QPA_PLATFORM=offscreen - uv run --no-dev --group test coverage run -m pytest -v --timeout=60 - uv run --no-dev --group test coverage xml + uv run --no-sync coverage run -m pytest -v --timeout=60 + uv run --no-sync coverage xml - name: Upload to Codecov uses: codecov/codecov-action@v5 diff --git a/.github/workflows/test_mac.yml b/.github/workflows/test_mac.yml index 7ecb5116..21a5e6e6 100644 --- a/.github/workflows/test_mac.yml +++ b/.github/workflows/test_mac.yml @@ -14,24 +14,36 @@ jobs: testMac: runs-on: macos-latest steps: - - name: Python Setup - uses: actions/setup-python@v6 + # - name: Python Setup + # uses: actions/setup-python@v6 + # with: + # python-version: "3.13" + # architecture: x64 + + # - name: Install Packages (brew) + # run: | + # brew install enchant + + - name: Install UV and Python + uses: astral-sh/setup-uv@v6 with: python-version: "3.13" - architecture: x64 - - name: Install Packages (brew) - run: | - brew install enchant + - name: Checkout Source uses: actions/checkout@v5 - - name: Install Dependencies (pip) + + - name: Sync UV run: | - pip install -U pyobjc -r requirements.txt -r tests/requirements.txt + uv sync --no-dev --group test --group macos + - name: Run Tests run: | export QT_QPA_PLATFORM=offscreen - python -m pytest -v --cov=novelwriter --timeout=60 + uv run --no-sync coverage run -m pytest -v --timeout=60 + uv run --no-sync coverage xml + - name: Upload to Codecov uses: codecov/codecov-action@v5 - env: - CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} + with: + token: ${{ secrets.CODECOV_TOKEN }} + fail_ci_if_error: true diff --git a/pyproject.toml b/pyproject.toml index d0e66ec4..945e8028 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -45,6 +45,7 @@ docs = [ ] test = ["coverage>=7.2.0", "pytest-qt", "pytest-timeout", "pytest>=6.0.0"] lint = ["isort", "pyright", "ruff"] +macos = ["pyobjc"] [project.urls] Homepage = "https://novelwriter.io" From 177d39c2a3113be0009d2e33de33c437f4702815 Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Sat, 18 Oct 2025 18:25:01 +0200 Subject: [PATCH 32/79] Move checkout source further up --- .github/workflows/test_linux.yml | 6 +++--- .github/workflows/test_mac.yml | 14 ++------------ 2 files changed, 5 insertions(+), 15 deletions(-) diff --git a/.github/workflows/test_linux.yml b/.github/workflows/test_linux.yml index efc5bce8..6a0d18d0 100644 --- a/.github/workflows/test_linux.yml +++ b/.github/workflows/test_linux.yml @@ -23,6 +23,9 @@ jobs: fail-fast: false runs-on: ubuntu-latest steps: + - name: Checkout Source + uses: actions/checkout@v5 + - name: Install System Packages run: | sudo apt update @@ -33,9 +36,6 @@ jobs: with: python-version: ${{ matrix.python-version }} - - name: Checkout Source - uses: actions/checkout@v5 - - name: Sync UV run: | uv sync --no-dev --group test diff --git a/.github/workflows/test_mac.yml b/.github/workflows/test_mac.yml index 21a5e6e6..5d24b3c9 100644 --- a/.github/workflows/test_mac.yml +++ b/.github/workflows/test_mac.yml @@ -14,24 +14,14 @@ jobs: testMac: runs-on: macos-latest steps: - # - name: Python Setup - # uses: actions/setup-python@v6 - # with: - # python-version: "3.13" - # architecture: x64 - - # - name: Install Packages (brew) - # run: | - # brew install enchant + - name: Checkout Source + uses: actions/checkout@v5 - name: Install UV and Python uses: astral-sh/setup-uv@v6 with: python-version: "3.13" - - name: Checkout Source - uses: actions/checkout@v5 - - name: Sync UV run: | uv sync --no-dev --group test --group macos From af8644838560a0d7fad42d6136be07c6f1aaf1b9 Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Sat, 18 Oct 2025 18:30:58 +0200 Subject: [PATCH 33/79] Add setuptools to MacOS workflow --- pyproject.toml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 945e8028..2160eefe 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -29,10 +29,12 @@ dynamic = ["version"] [dependency-groups] dev = [ + { include-group = "build" }, { include-group = "docs" }, { include-group = "test" }, { include-group = "lint" }, ] +build = ["setuptools>=77.0.3"] docs = [ "docutils>=0.17.1", "pygments>=2.7", @@ -45,7 +47,7 @@ docs = [ ] test = ["coverage>=7.2.0", "pytest-qt", "pytest-timeout", "pytest>=6.0.0"] lint = ["isort", "pyright", "ruff"] -macos = ["pyobjc"] +macos = ["pyobjc", { include-group = "build" }] [project.urls] Homepage = "https://novelwriter.io" From 40363068e378759d1b00c5df54ec541c31daa80c Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Sat, 18 Oct 2025 18:51:55 +0200 Subject: [PATCH 34/79] Update Windows workflow and try to fix MacOS --- .github/workflows/test_mac.yml | 2 +- .github/workflows/test_win.yml | 26 ++++++++++++++++---------- pyproject.toml | 4 +--- 3 files changed, 18 insertions(+), 14 deletions(-) diff --git a/.github/workflows/test_mac.yml b/.github/workflows/test_mac.yml index 5d24b3c9..c88348db 100644 --- a/.github/workflows/test_mac.yml +++ b/.github/workflows/test_mac.yml @@ -24,7 +24,7 @@ jobs: - name: Sync UV run: | - uv sync --no-dev --group test --group macos + uv sync --no-dev --group test --group macos --python-platform x86_64-apple-darwin - name: Run Tests run: | diff --git a/.github/workflows/test_win.yml b/.github/workflows/test_win.yml index 1cdca205..4f6ad739 100644 --- a/.github/workflows/test_win.yml +++ b/.github/workflows/test_win.yml @@ -14,20 +14,26 @@ jobs: testWin: runs-on: windows-latest steps: - - name: Python Setup - uses: actions/setup-python@v6 - with: - python-version: "3.13" - architecture: x64 - name: Checkout Source uses: actions/checkout@v5 - - name: Install Dependencies (pip) + + - name: Install UV and Python + uses: astral-sh/setup-uv@v6 + with: + python-version: "3.13" + + - name: Sync UV run: | - pip install -U -r requirements.txt -r tests/requirements.txt + uv sync --no-dev --group test + - name: Run Tests run: | - python -m pytest -v --cov=novelwriter --timeout=60 + $env:QT_QPA_PLATFORM=offscreen + uv run --no-sync coverage run -m pytest -v --timeout=60 + uv run --no-sync coverage xml + - name: Upload to Codecov uses: codecov/codecov-action@v5 - env: - CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} + with: + token: ${{ secrets.CODECOV_TOKEN }} + fail_ci_if_error: true diff --git a/pyproject.toml b/pyproject.toml index 2160eefe..945e8028 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -29,12 +29,10 @@ dynamic = ["version"] [dependency-groups] dev = [ - { include-group = "build" }, { include-group = "docs" }, { include-group = "test" }, { include-group = "lint" }, ] -build = ["setuptools>=77.0.3"] docs = [ "docutils>=0.17.1", "pygments>=2.7", @@ -47,7 +45,7 @@ docs = [ ] test = ["coverage>=7.2.0", "pytest-qt", "pytest-timeout", "pytest>=6.0.0"] lint = ["isort", "pyright", "ruff"] -macos = ["pyobjc", { include-group = "build" }] +macos = ["pyobjc"] [project.urls] Homepage = "https://novelwriter.io" From a6e29f0ded37e4527cf7062e19d6c2749c2c1b00 Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Sat, 18 Oct 2025 19:11:01 +0200 Subject: [PATCH 35/79] Add more debug output --- .github/workflows/test_mac.yml | 2 +- .github/workflows/test_win.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test_mac.yml b/.github/workflows/test_mac.yml index c88348db..e6c99383 100644 --- a/.github/workflows/test_mac.yml +++ b/.github/workflows/test_mac.yml @@ -24,7 +24,7 @@ jobs: - name: Sync UV run: | - uv sync --no-dev --group test --group macos --python-platform x86_64-apple-darwin + uv sync --verbose --no-dev --group test --group macos --python-platform x86_64-apple-darwin - name: Run Tests run: | diff --git a/.github/workflows/test_win.yml b/.github/workflows/test_win.yml index 4f6ad739..e64bef4f 100644 --- a/.github/workflows/test_win.yml +++ b/.github/workflows/test_win.yml @@ -24,7 +24,7 @@ jobs: - name: Sync UV run: | - uv sync --no-dev --group test + uv sync --verbose --no-dev --group test - name: Run Tests run: | From e66f19914ea6d8d82e79389f8f09276408df4de0 Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Sat, 18 Oct 2025 23:14:33 +0200 Subject: [PATCH 36/79] Fix case conflict on MacOS and Windows --- novelWriter.py | 8 -------- pyproject.toml | 6 +++--- 2 files changed, 3 insertions(+), 11 deletions(-) diff --git a/novelWriter.py b/novelWriter.py index 78c8db27..9758edee 100755 --- a/novelWriter.py +++ b/novelWriter.py @@ -6,14 +6,6 @@ novelWriter – Start Script import os import sys -try: - import PyQt6.QtCore # noqa: F401 - import PyQt6.QtGui # noqa: F401 - import PyQt6.QtWidgets # noqa: F401 -except Exception: - print("ERROR: Failed to load dependency PyQt6") - sys.exit(1) - os.curdir = os.path.abspath(os.path.dirname(__file__)) if __name__ == "__main__": diff --git a/pyproject.toml b/pyproject.toml index 945e8028..d5a0b673 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -57,10 +57,10 @@ Issues = "https://github.com/vkbo/novelWriter/issues" novelwriter = "novelwriter:main" [tool.setuptools.dynamic] -version = { attr = "novelwriter.__version__" } +version = { attr = "novelwriter.__init__.__version__" } -[tool.setuptools.packages.find] -include = ["novelwriter*"] +[tool.setuptools] +packages = ["novelwriter"] [tool.isort] py_version = "310" From 359d5f36b5b986a9157e4021f74fb161145b477a Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Sat, 18 Oct 2025 23:17:53 +0200 Subject: [PATCH 37/79] Remove debug settings from workflows --- .github/workflows/test_mac.yml | 2 +- .github/workflows/test_win.yml | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/.github/workflows/test_mac.yml b/.github/workflows/test_mac.yml index e6c99383..5d24b3c9 100644 --- a/.github/workflows/test_mac.yml +++ b/.github/workflows/test_mac.yml @@ -24,7 +24,7 @@ jobs: - name: Sync UV run: | - uv sync --verbose --no-dev --group test --group macos --python-platform x86_64-apple-darwin + uv sync --no-dev --group test --group macos - name: Run Tests run: | diff --git a/.github/workflows/test_win.yml b/.github/workflows/test_win.yml index e64bef4f..1cb2dbdd 100644 --- a/.github/workflows/test_win.yml +++ b/.github/workflows/test_win.yml @@ -24,11 +24,10 @@ jobs: - name: Sync UV run: | - uv sync --verbose --no-dev --group test + uv sync --no-dev --group test - name: Run Tests run: | - $env:QT_QPA_PLATFORM=offscreen uv run --no-sync coverage run -m pytest -v --timeout=60 uv run --no-sync coverage xml From e21d94446111d4f946483d8c815d750349e39d00 Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Sat, 18 Oct 2025 23:23:46 +0200 Subject: [PATCH 38/79] Fix mac workflow and update linting job --- .github/workflows/syntax.yml | 31 ++++++++++++++++++------------- .github/workflows/test_mac.yml | 4 ++++ 2 files changed, 22 insertions(+), 13 deletions(-) diff --git a/.github/workflows/syntax.yml b/.github/workflows/syntax.yml index d91c58fd..f3d723f9 100644 --- a/.github/workflows/syntax.yml +++ b/.github/workflows/syntax.yml @@ -14,24 +14,29 @@ jobs: checkSyntax: runs-on: ubuntu-latest steps: - - name: Python Setup - uses: actions/setup-python@v6 - with: - python-version: 3 - architecture: x64 - name: Checkout Source uses: actions/checkout@v5 - - name: Install Dependencies - run: pip install -r requirements.txt -r requirements-dev.txt + + - name: Install UV and Python + uses: astral-sh/setup-uv@v6 + with: + enable-cache: true + + - name: Sync UV + run: | + uv sync --no-dev --group lint + - name: Ruff Check run: | - ruff --version - ruff check + uv run --no-sync ruff --version + uv run --no-sync ruff check + - name: Pyright Check run: | - pyright --version - pyright + uv run --no-sync pyright --version + uv run --no-sync pyright + - name: Isort Check run: | - isort --version - isort --check . + uv run --no-sync isort --version + uv run --no-sync isort --check . diff --git a/.github/workflows/test_mac.yml b/.github/workflows/test_mac.yml index 5d24b3c9..d191ba4b 100644 --- a/.github/workflows/test_mac.yml +++ b/.github/workflows/test_mac.yml @@ -17,6 +17,10 @@ jobs: - name: Checkout Source uses: actions/checkout@v5 + - name: Install System Packages + run: | + brew install enchant + - name: Install UV and Python uses: astral-sh/setup-uv@v6 with: From 41c58a52e3faf0f9437fdf9cfc522b6261d51017 Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Sun, 19 Oct 2025 00:19:47 +0200 Subject: [PATCH 39/79] Update Debian build --- novelwriter/__init__.py | 6 +++--- pyproject.toml | 6 ++++-- setup/debian/control | 2 ++ setup/make_release.sh | 16 ++++------------ utils/common.py | 11 ++++++++--- 5 files changed, 21 insertions(+), 20 deletions(-) diff --git a/novelwriter/__init__.py b/novelwriter/__init__.py index fa3dd30e..82200f17 100644 --- a/novelwriter/__init__.py +++ b/novelwriter/__init__.py @@ -49,9 +49,9 @@ __license__ = "GPLv3" __author__ = "Veronica Berglyd Olsen" __maintainer__ = "Veronica Berglyd Olsen" __email__ = "code@vkbo.net" -__version__ = "2.8a2" -__hexversion__ = "0x020800a2" -__date__ = "2025-07-16" +__version__ = "2.8a3" +__hexversion__ = "0x020800a3" +__date__ = "2025-10-18" __status__ = "Stable" __domain__ = "novelwriter.io" diff --git a/pyproject.toml b/pyproject.toml index d5a0b673..5b5c3a9e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -29,10 +29,12 @@ dynamic = ["version"] [dependency-groups] dev = [ + { include-group = "build" }, { include-group = "docs" }, { include-group = "test" }, { include-group = "lint" }, ] +build = ["build", "setuptools>=77.0.3"] docs = [ "docutils>=0.17.1", "pygments>=2.7", @@ -59,8 +61,8 @@ novelwriter = "novelwriter:main" [tool.setuptools.dynamic] version = { attr = "novelwriter.__init__.__version__" } -[tool.setuptools] -packages = ["novelwriter"] +[tool.setuptools.packages.find] +include = ["novelwriter*"] [tool.isort] py_version = "310" diff --git a/setup/debian/control b/setup/debian/control index 24f0cd25..30c7921c 100644 --- a/setup/debian/control +++ b/setup/debian/control @@ -4,6 +4,8 @@ Section: text Priority: optional Build-Depends: dh-python, + pybuild-plugin-pyproject, + python3-build, python3-setuptools, python3-all, debhelper (>= 9), diff --git a/setup/make_release.sh b/setup/make_release.sh index 97438bd9..8c59a933 100755 --- a/setup/make_release.sh +++ b/setup/make_release.sh @@ -1,8 +1,6 @@ #!/bin/bash set -e -ENVPATH=/tmp/nwBuild - if [ ! -f pkgutils.py ]; then echo "Must be called from the root folder of the source" exit 1 @@ -12,18 +10,12 @@ echo "" echo " Building Dependencies" echo "================================================================================" echo "" -if [ ! -d $ENVPATH ]; then - python3 -m venv $ENVPATH -fi -source $ENVPATH/bin/activate -pip3 install -r requirements.txt -r docs/requirements.txt -python3 pkgutils.py build-assets -python3 pkgutils.py icons optional -deactivate +uv run pkgutils.py build-assets +uv run pkgutils.py icons optional echo "" echo " Building Linux Packages" echo "================================================================================" echo "" -python3 pkgutils.py build-deb --sign -python3 pkgutils.py build-ubuntu --sign +uv run pkgutils.py build-deb --sign +uv run pkgutils.py build-ubuntu --sign diff --git a/utils/common.py b/utils/common.py index 54528ba2..723da500 100644 --- a/utils/common.py +++ b/utils/common.py @@ -92,14 +92,19 @@ def copySourceCode(dst: Path) -> None: def copyPackageFiles(dst: Path, oldLicense: bool = False) -> None: """Copy files needed for packaging.""" - copyFiles = ["LICENSE.md", "setup/LICENSE-Apache-2.0.txt", "CREDITS.md", "pyproject.toml"] + copyFiles = [ + ROOT_DIR / "LICENSE.md", + SETUP_DIR / "LICENSE-Apache-2.0.txt", + ROOT_DIR / "CREDITS.md", + ROOT_DIR / "pyproject.toml", + ] for copyFile in copyFiles: - shutil.copyfile(copyFile, dst / copyFile) + shutil.copyfile(copyFile, dst / copyFile.name) print("Copied:", copyFile, flush=True) writeFile(dst / "MANIFEST.in", ( "include LICENSE.md\n" - "include setup/LICENSE-Apache-2.0.txt\n" + "include LICENSE-Apache-2.0.txt\n" "include CREDITS.md\n" "recursive-include novelwriter/assets *\n" )) From 687bcc2779ef9623719d6b5070305da2850b601f Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Wed, 22 Oct 2025 19:30:36 +0200 Subject: [PATCH 40/79] Update MacOS build --- .github/workflows/build_assets.yml | 24 ++++++++++++------------ .github/workflows/i18n.yml | 10 +++++++--- .github/workflows/test_linux.yml | 2 +- pyproject.toml | 9 ++++++--- setup/macos/build.sh | 1 - 5 files changed, 26 insertions(+), 20 deletions(-) diff --git a/.github/workflows/build_assets.yml b/.github/workflows/build_assets.yml index 291da081..bb90b5b5 100644 --- a/.github/workflows/build_assets.yml +++ b/.github/workflows/build_assets.yml @@ -6,27 +6,27 @@ jobs: buildAssets: runs-on: ubuntu-latest steps: - - name: Python Setup - uses: actions/setup-python@v6 - with: - python-version: "3.13" - architecture: x64 + - name: Checkout Source + uses: actions/checkout@v5 - - name: Install Packages (apt) + - name: Install System Packages run: | sudo apt update sudo apt install qttools5-dev-tools latexmk texlive texlive-latex-extra - - name: Checkout Source - uses: actions/checkout@v5 + - name: Install UV and Python + uses: astral-sh/setup-uv@v6 + with: + python-version: "3.13" - - name: Install Packages (pip) - run: pip install -U -r requirements.txt -r docs/requirements.txt + - name: Sync UV + run: | + uv sync --no-dev --group docs - name: Build Assets run: | - python pkgutils.py build-assets - python pkgutils.py icons optional + uv run --no-sync pkgutils.py qtlrelease + uv run --no-sync pkgutils.py sample - name: Upload Artifacts uses: actions/upload-artifact@v4 diff --git a/.github/workflows/i18n.yml b/.github/workflows/i18n.yml index 82a22f10..238ae27a 100644 --- a/.github/workflows/i18n.yml +++ b/.github/workflows/i18n.yml @@ -14,14 +14,18 @@ jobs: with: python-version: "3.13" architecture: x64 - - name: Install Packages (apt) + + - name: Checkout Source + uses: actions/checkout@v5 + + - name: Install System Packages run: | sudo apt update sudo apt install qttools5-dev-tools - - name: Checkout Source - uses: actions/checkout@v5 + - name: Build Assets run: python pkgutils.py qtlrelease + - name: Upload Artifacts uses: actions/upload-artifact@v4 with: diff --git a/.github/workflows/test_linux.yml b/.github/workflows/test_linux.yml index 6a0d18d0..b900128f 100644 --- a/.github/workflows/test_linux.yml +++ b/.github/workflows/test_linux.yml @@ -40,7 +40,7 @@ jobs: run: | uv sync --no-dev --group test - - name: Run Build Commands + - name: Build Assets run: | uv run --no-sync pkgutils.py qtlrelease uv run --no-sync pkgutils.py sample diff --git a/pyproject.toml b/pyproject.toml index 5b5c3a9e..d8ebd34f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -9,6 +9,8 @@ description = "A plain text editor for planning and writing novels" readme = { file = "setup/description_pypi.md", content-type = "text/markdown" } license = "GPL-3.0-or-later AND Apache-2.0 AND CC-BY-4.0" license-files = ["LICENSE.md", "setup/LICENSE-Apache-2.0.txt"] +dynamic = ["version"] +requires-python = ">=3.10" classifiers = [ "Programming Language :: Python :: 3 :: Only", "Programming Language :: Python :: 3.10", @@ -23,9 +25,10 @@ classifiers = [ "Natural Language :: English", "Topic :: Text Editors", ] -requires-python = ">=3.10" -dependencies = ["pyqt6>=6.4", "pyenchant>=3.0.0"] -dynamic = ["version"] +dependencies = [ + "pyqt6>=6.4", + "pyenchant>=3.3.0", # 3.3 is needed for MacOS AARCH64 builds +] [dependency-groups] dev = [ diff --git a/setup/macos/build.sh b/setup/macos/build.sh index ca475fa1..d9e4f601 100755 --- a/setup/macos/build.sh +++ b/setup/macos/build.sh @@ -109,7 +109,6 @@ conda install -c conda-forge enchant hunspell-en --yes # Install dependencies echo "Installing Python dependencies ..." pip install -r "$SRC_DIR/requirements.txt" -pip install pyenchant==3.3.0rc1 # Leave conda env conda deactivate From 0b79bcd4b4f772f3bc5e2e3c6014003beaa87da5 Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Wed, 22 Oct 2025 19:35:35 +0200 Subject: [PATCH 41/79] Fix assets build job --- .github/workflows/build_assets.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build_assets.yml b/.github/workflows/build_assets.yml index bb90b5b5..945b0634 100644 --- a/.github/workflows/build_assets.yml +++ b/.github/workflows/build_assets.yml @@ -18,6 +18,7 @@ jobs: uses: astral-sh/setup-uv@v6 with: python-version: "3.13" + enable-cache: true - name: Sync UV run: | @@ -25,8 +26,8 @@ jobs: - name: Build Assets run: | - uv run --no-sync pkgutils.py qtlrelease - uv run --no-sync pkgutils.py sample + uv run --no-sync pkgutils.py build-assets + uv run --no-sync pkgutils.py icons optional - name: Upload Artifacts uses: actions/upload-artifact@v4 From 06cb12887692cf3b2b5f1cad5a48a4e67534418e Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Wed, 22 Oct 2025 21:11:20 +0200 Subject: [PATCH 42/79] Remove dependencies on requirements files and generate when needed --- docs/source/technical/source.rst | 29 +++++++++++++++++++++----- docs/source/technical/tests.rst | 35 ++++++++++++++++++-------------- pkgutils.py | 35 +++++++++++++++++++++++++++----- setup/macos/build.sh | 3 ++- setup/make_pip.sh | 22 +------------------- utils/build_windows.py | 5 ++--- utils/common.py | 14 +++++++++++++ 7 files changed, 93 insertions(+), 50 deletions(-) diff --git a/docs/source/technical/source.rst b/docs/source/technical/source.rst index 6b69b491..44750bf2 100644 --- a/docs/source/technical/source.rst +++ b/docs/source/technical/source.rst @@ -7,6 +7,7 @@ Running from Source .. _GitHub: https://github.com/vkbo/novelWriter/releases .. _PyPi: https://pypi.org/project/novelWriter/ .. _Sphinx Docs: https://www.sphinx-doc.org/ +.. _uv: https://docs.astral.sh/uv/ This chapter describes various ways of running novelWriter directly from the source code, and how to build the various components like the translation files and documentation. @@ -28,6 +29,7 @@ by running: .. _docs_technical_source_depend: + Dependencies ============ @@ -43,13 +45,22 @@ The following Python packages are needed to run all features of novelWriter: If you want spell checking, you must install the ``PyEnchant`` package. The spell check library must be at least 3.0 to work with Windows. On Linux, 2.0 also works fine. -If you install from PyPi, these dependencies should be installed automatically. If you install from -source, dependencies can still be installed from PyPi with: +If you install novelWriter from PyPi, these dependencies should be installed automatically. + +If you run it from source, and want to install dependencies using ``pip``, you must first generate +the ``requirements.txt`` file: .. code-block:: bash + python pkgutils.py gen-req pip install -r requirements.txt +Otherwise you can run novelWriter with uv_: + +.. code-block:: bash + + uv run novelwriter + .. note:: On Linux distros, the Qt library is usually split up into multiple packages. In some cases, @@ -136,12 +147,14 @@ running: Building the Documentation ========================== -A local copy of this documentation can be generated as HTML. This requires installing some Python -packages from PyPi: +A local copy of this documentation can be generated as HTML. + +If you're using ``pip``, you must first generate the ``requirements.txt`` file: .. code-block:: bash - pip install -r docs/requirements.txt + python pkgutils.py gen-req docs + pip install -r requirements.txt The documentation can then be built from the root folder in the source code by running: @@ -149,6 +162,12 @@ The documentation can then be built from the root folder in the source code by r make -C docs html +Or you can run directly with uv_: + +.. code-block:: bash + + uv run make -C docs html + If successful, the documentation should be available in the ``docs/build/html`` folder and you can open the ``index.html`` file in your browser. diff --git a/docs/source/technical/tests.rst b/docs/source/technical/tests.rst index 56623ff4..4eb8c244 100644 --- a/docs/source/technical/tests.rst +++ b/docs/source/technical/tests.rst @@ -4,23 +4,12 @@ Running Tests ************* +.. _uv: https://docs.astral.sh/uv/ + The novelWriter source code is well covered by tests. The test framework used for the development is ``pytest`` with the use of an extension for Qt. -Dependencies -============ - -The dependencies for running the tests can be installed with: - -.. code-block:: bash - - pip install -r tests/requirements.txt - -This will install a couple of extra packages for coverage and test management. The minimum -requirement is ``pytest`` and ``pytest-qt``. - - Simple Test Run =============== @@ -28,19 +17,35 @@ To run the tests, you simply need to execute the following from the root of the .. code-block:: bash - pytest + uv run pytest + +This uses uv_. See below for manually installing dependencies using ``pip``. Since several of the tests involve opening up the novelWriter GUI, you may want to disable the GUI for the duration of the test run. Moving your mouse while the tests are running may otherwise interfere with the execution of some tests. -You can disable the renderring of the GUI by setting the flag ``QT_QPA_PLATFORM=offscreen``: +You can disable the rendering of the GUI by setting the flag ``QT_QPA_PLATFORM=offscreen``: .. code-block:: bash export QT_QPA_PLATFORM=offscreen pytest +Dependencies +------------ + +To run generate the requirements file and install using ``pip``, run: + +.. code-block:: bash + + python pkgutils.py gen-req app test + pip install -r tests/requirements.txt + +This will install a couple of extra packages for coverage and test management. The minimum +requirement is ``pytest`` and ``pytest-qt``. + + Advanced Options ================ diff --git a/pkgutils.py b/pkgutils.py index 29d6983f..fc49f94d 100755 --- a/pkgutils.py +++ b/pkgutils.py @@ -40,7 +40,10 @@ import utils.build_windows import utils.docs import utils.icon_themes -from utils.common import ROOT_DIR, SETUP_DIR, extractVersion, readFile, stripVersion, writeFile +from utils.common import ( + ROOT_DIR, SETUP_DIR, extractReqs, extractVersion, readFile, stripVersion, + writeFile +) OS_LINUX = sys.platform.startswith("linux") OS_DARWIN = sys.platform.startswith("darwin") @@ -61,7 +64,7 @@ def installPackages(args: argparse.Namespace) -> None: print("=======================") print("") - installQueue = ["pip", "-r requirements.txt"] + installQueue = ["pip", *extractReqs(["app"])] if args.mac: installQueue.append("pyobjc") elif args.win: @@ -130,6 +133,15 @@ def genMacOSPlist(args: argparse.Namespace) -> None: writeFile(outDir / "Info.plist", plistXML) +def genReqFiles(args: argparse.Namespace) -> None: + """Generate requirements.txt file from pyproject.toml.""" + select = [s.strip().lower() for s in args.groups] if args.groups else ["app"] + (ROOT_DIR / "requirements.txt").write_text( + "\n".join(extractReqs(select)), + encoding="utf-8" + ) + + if __name__ == "__main__": """Parse command line options and run the commands.""" parser = argparse.ArgumentParser( @@ -222,7 +234,7 @@ if __name__ == "__main__": cmdBuildHtmlDocs = parsers.add_parser( "docs-html", help="Build the HTML docs." ) - cmdBuildHtmlDocs.add_argument("lang", nargs="+") + cmdBuildHtmlDocs.add_argument("lang", nargs="+", help="Language codes to generate docs for.") cmdBuildHtmlDocs.set_defaults(func=utils.docs.buildHtmlDocs) # Build Sample @@ -295,10 +307,23 @@ if __name__ == "__main__": cmdBuildClean.set_defaults(func=cleanBuildDirs) # Generate MacOS PList File - cmdBuildMacOSPlist = parsers.add_parser( + cmdGenMacOSPlist = parsers.add_parser( "gen-plist", help="Generate an Info.plist for use in a MacOS Bundle." ) - cmdBuildMacOSPlist.set_defaults(func=genMacOSPlist) + cmdGenMacOSPlist.set_defaults(func=genMacOSPlist) + + # Generate Requirement File + cmdGenReq = parsers.add_parser( + "gen-req", help="Generate a requirements.txt file for pip." + ) + cmdGenReq.add_argument( + "groups", nargs="*", help=( + "Groups to generate for, or 'all' to generate for all groups. " + "Use 'app' to generate for just the core application. " + "Defaults to app dependencies." + ) + ) + cmdGenReq.set_defaults(func=genReqFiles) args = parser.parse_args() args.func(args) diff --git a/setup/macos/build.sh b/setup/macos/build.sh index d9e4f601..cafa196a 100755 --- a/setup/macos/build.sh +++ b/setup/macos/build.sh @@ -1,7 +1,7 @@ #! /bin/bash if [[ -z "$1" || -z "$2" || -z "$3" ]]; then - echo "Not enouch input arguments" + echo "Not enough input arguments" exit 1 fi @@ -108,6 +108,7 @@ conda install -c conda-forge enchant hunspell-en --yes # Install dependencies echo "Installing Python dependencies ..." +python3 pkgutils.py gen-req pip install -r "$SRC_DIR/requirements.txt" # Leave conda env diff --git a/setup/make_pip.sh b/setup/make_pip.sh index 681a3cb5..f7e82c65 100755 --- a/setup/make_pip.sh +++ b/setup/make_pip.sh @@ -1,24 +1,11 @@ #!/bin/bash set -e -ENVPATH=/tmp/nwBuild - if [ ! -f pkgutils.py ]; then echo "Must be called from the root folder of the source" exit 1 fi -echo "" -echo " Create Python Env" -echo "================================================================================" -echo "" - -if [ ! -d $ENVPATH ]; then - python3 -m venv $ENVPATH -fi -source $ENVPATH/bin/activate -pip3 install -U build twine -r requirements.txt -r docs/requirements.txt - echo "" echo " Building Dependencies" echo "================================================================================" @@ -30,7 +17,7 @@ echo "" echo " Building Packages" echo "================================================================================" echo "" -python3 -m build +uv build mkdir -pv dist_upload cp -v dist/novelwriter-*.whl dist_upload/ cd dist_upload @@ -38,13 +25,6 @@ FILE=$(ls -t | head -1) shasum -a 256 $FILE | tee $FILE.sha256 cd .. -echo "" -echo " Checking Packages" -echo "================================================================================" -echo "" -twine check dist/* -deactivate - echo "" echo " Done!" echo "================================================================================" diff --git a/utils/build_windows.py b/utils/build_windows.py index 523797da..debae0ef 100644 --- a/utils/build_windows.py +++ b/utils/build_windows.py @@ -30,7 +30,7 @@ import zipfile from pathlib import Path from utils.common import ( - ROOT_DIR, SETUP_DIR, copySourceCode, extractVersion, readFile, + ROOT_DIR, SETUP_DIR, copySourceCode, extractReqs, extractVersion, readFile, removeRedundantQt, systemCall, writeFile ) @@ -45,7 +45,6 @@ def prepareCode(outDir: Path) -> None: files = [ ROOT_DIR / "CREDITS.md", ROOT_DIR / "LICENSE.md", - ROOT_DIR / "requirements.txt", SETUP_DIR / "iss_license.txt", SETUP_DIR / "windows" / "novelWriter.ico", SETUP_DIR / "windows" / "novelWriter.exe", @@ -85,7 +84,7 @@ def installRequirements(libDir: Path) -> None: """Install dependencies.""" print("Install dependencies ...") systemCall([ - sys.executable, "-m", "pip", "install", "-r", "requirements.txt", "--target", libDir + sys.executable, "-m", "pip", "install", *extractReqs(["app"]), "--target", libDir ]) print("Done") print("") diff --git a/utils/common.py b/utils/common.py index 723da500..0136cf47 100644 --- a/utils/common.py +++ b/utils/common.py @@ -26,10 +26,24 @@ import sys from pathlib import Path +import tomllib + ROOT_DIR = Path(__file__).parent.parent SETUP_DIR = ROOT_DIR / "setup" +def extractReqs(groups: list[str]) -> list[str]: + """Generate requirements.txt file from pyproject.toml.""" + data = tomllib.loads((ROOT_DIR / "pyproject.toml").read_text(encoding="utf-8")) + reqs = [] + if "app" in groups or "all" in groups: + reqs += data["project"]["dependencies"] + for group in data["dependency-groups"]: + if group in groups or "all" in groups: + reqs += [d for d in data["dependency-groups"][group] if isinstance(d, str)] + return reqs + + def extractVersion(beQuiet: bool = False) -> tuple[str, str, str]: """Extract the novelWriter version number without having to import anything else from the main package. From 2d7a42d918a9792f562b4294e7598c1dce4b98d1 Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Wed, 22 Oct 2025 21:17:18 +0200 Subject: [PATCH 43/79] Remove requirements files --- .gitignore | 1 + docs/requirements.txt | 8 -------- requirements-all.txt | 5 ----- requirements-dev.txt | 4 ---- requirements.txt | 2 -- setup/make_pip.sh | 4 ++-- setup/requirements.txt | 2 -- tests/requirements.txt | 5 ----- 8 files changed, 3 insertions(+), 28 deletions(-) delete mode 100644 docs/requirements.txt delete mode 100644 requirements-all.txt delete mode 100644 requirements-dev.txt delete mode 100644 requirements.txt delete mode 100644 setup/requirements.txt delete mode 100644 tests/requirements.txt diff --git a/.gitignore b/.gitignore index 833d4182..5b91bed7 100644 --- a/.gitignore +++ b/.gitignore @@ -13,6 +13,7 @@ setup.iss /setup/macos/Info.plist /setup/windows/build .venv +/uv.lock # Translations /novelwriter/assets/i18n/*.qm diff --git a/docs/requirements.txt b/docs/requirements.txt deleted file mode 100644 index 767647e7..00000000 --- a/docs/requirements.txt +++ /dev/null @@ -1,8 +0,0 @@ -docutils>=0.17.1 -pygments>=2.7 -sphinx-book-theme -sphinx-copybutton -sphinx-design -sphinx-favicon -sphinx-intl -sphinx>=5.0 diff --git a/requirements-all.txt b/requirements-all.txt deleted file mode 100644 index d140ead6..00000000 --- a/requirements-all.txt +++ /dev/null @@ -1,5 +0,0 @@ --r requirements.txt --r requirements-dev.txt --r docs/requirements.txt --r setup/requirements.txt --r tests/requirements.txt diff --git a/requirements-dev.txt b/requirements-dev.txt deleted file mode 100644 index 794644e0..00000000 --- a/requirements-dev.txt +++ /dev/null @@ -1,4 +0,0 @@ -build -isort -pyright -ruff diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index fa1cf957..00000000 --- a/requirements.txt +++ /dev/null @@ -1,2 +0,0 @@ -pyqt6>=6.4 -pyenchant>=3.0.0 diff --git a/setup/make_pip.sh b/setup/make_pip.sh index f7e82c65..9d3c61b2 100755 --- a/setup/make_pip.sh +++ b/setup/make_pip.sh @@ -10,8 +10,8 @@ echo "" echo " Building Dependencies" echo "================================================================================" echo "" -python3 pkgutils.py build-assets -python3 pkgutils.py icons optional +uv run pkgutils.py build-assets +uv run pkgutils.py icons optional echo "" echo " Building Packages" diff --git a/setup/requirements.txt b/setup/requirements.txt deleted file mode 100644 index 120a3f4f..00000000 --- a/setup/requirements.txt +++ /dev/null @@ -1,2 +0,0 @@ -setuptools>=77.0.3 -twine diff --git a/tests/requirements.txt b/tests/requirements.txt deleted file mode 100644 index 39f96e3c..00000000 --- a/tests/requirements.txt +++ /dev/null @@ -1,5 +0,0 @@ -coverage>=7.2.0 -pytest-cov -pytest-qt -pytest-timeout -pytest>=6.0.0 From e676bdc3222045a47e3e06ed702dd7f1c7ba15a8 Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Wed, 22 Oct 2025 21:23:57 +0200 Subject: [PATCH 44/79] Drop support for Python 3.10 --- .github/workflows/test_linux.yml | 1 - novelwriter/__init__.py | 4 ++-- pyproject.toml | 5 ++--- setup/debian/control | 6 +++--- 4 files changed, 7 insertions(+), 9 deletions(-) diff --git a/.github/workflows/test_linux.yml b/.github/workflows/test_linux.yml index b900128f..1f84fa95 100644 --- a/.github/workflows/test_linux.yml +++ b/.github/workflows/test_linux.yml @@ -15,7 +15,6 @@ jobs: strategy: matrix: python-version: - - "3.10" - "3.11" - "3.12" - "3.13" diff --git a/novelwriter/__init__.py b/novelwriter/__init__.py index 82200f17..17c0298e 100644 --- a/novelwriter/__init__.py +++ b/novelwriter/__init__.py @@ -206,9 +206,9 @@ def main(sysArgs: list | None = None) -> GuiMain | None: # Check Packages and Versions errorData = [] errorCode = 0 - if sys.hexversion < 0x030a00f0: + if sys.hexversion < 0x030b00f0: errorData.append( - f"At least Python 3.10 is required, found {CONFIG.verPyString}" + f"At least Python 3.11 is required, found {CONFIG.verPyString}" ) errorCode |= 0x04 if CONFIG.verQtValue < 0x060400: diff --git a/pyproject.toml b/pyproject.toml index d8ebd34f..fe42db85 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -10,10 +10,9 @@ readme = { file = "setup/description_pypi.md", content-type = "text/markdown" } license = "GPL-3.0-or-later AND Apache-2.0 AND CC-BY-4.0" license-files = ["LICENSE.md", "setup/LICENSE-Apache-2.0.txt"] dynamic = ["version"] -requires-python = ">=3.10" +requires-python = ">=3.11" classifiers = [ "Programming Language :: Python :: 3 :: Only", - "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", @@ -153,7 +152,7 @@ include = ["novelwriter"] exclude = ["**/__pycache__"] reportIncompatibleMethodOverride = false -pythonVersion = "3.10" +pythonVersion = "3.11" [tool.pytest.ini_options] log_level = "DEBUG" diff --git a/setup/debian/control b/setup/debian/control index 30c7921c..06d63fa8 100644 --- a/setup/debian/control +++ b/setup/debian/control @@ -9,21 +9,21 @@ Build-Depends: python3-setuptools, python3-all, debhelper (>= 9), - python3 (>=3.10), + python3 (>=3.11), python3-pyqt6 (>= 6.4), python3-pyqt6.qtsvg (>= 6.4), python3-enchant (>= 2.0), qt6-image-formats-plugins (>= 6.4) Standards-Version: 4.5.1 Homepage: https://novelwriter.io -X-Python3-Version: >= 3.10 +X-Python3-Version: >= 3.11 Package: novelwriter Architecture: all Depends: ${misc:Depends}, ${python3:Depends}, - python3 (>=3.10), + python3 (>=3.11), python3-pyqt6 (>= 6.4), python3-pyqt6.qtsvg (>= 6.4), python3-enchant (>= 2.0), From df2766d80e65e04695dac33367dd5a80e9388902 Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Wed, 22 Oct 2025 21:26:38 +0200 Subject: [PATCH 45/79] Update linting --- novelwriter/gui/theme.py | 2 +- pyproject.toml | 2 +- utils/common.py | 3 +-- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/novelwriter/gui/theme.py b/novelwriter/gui/theme.py index b83de9b7..f3a026f1 100644 --- a/novelwriter/gui/theme.py +++ b/novelwriter/gui/theme.py @@ -615,7 +615,7 @@ class GuiTheme: lookup = f"{prefix}{name} {key}" keys.append(lookup) data[lookup] = (file.stem, name, mode == "dark", file) - except Exception: # noqa: PERF203 + except Exception: logger.error("Could not read file: %s", file) logException() diff --git a/pyproject.toml b/pyproject.toml index fe42db85..17599847 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -67,7 +67,7 @@ version = { attr = "novelwriter.__init__.__version__" } include = ["novelwriter*"] [tool.isort] -py_version = "310" +py_version = "311" line_length = 99 wrap_length = 79 multi_line_output = 5 diff --git a/utils/common.py b/utils/common.py index 0136cf47..a718971e 100644 --- a/utils/common.py +++ b/utils/common.py @@ -23,11 +23,10 @@ from __future__ import annotations import shutil import subprocess import sys +import tomllib from pathlib import Path -import tomllib - ROOT_DIR = Path(__file__).parent.parent SETUP_DIR = ROOT_DIR / "setup" From b3c18001e29f313c56719a6fa313d439108c5a24 Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Wed, 22 Oct 2025 21:57:20 +0200 Subject: [PATCH 46/79] Add build for Ubuntu 26.04 --- utils/build_debian.py | 1 + 1 file changed, 1 insertion(+) diff --git a/utils/build_debian.py b/utils/build_debian.py index 9ac0bd2e..892ee718 100644 --- a/utils/build_debian.py +++ b/utils/build_debian.py @@ -183,6 +183,7 @@ def launchpad(args: argparse.Namespace) -> None: ("24.04", "noble", True), ("25.04", "plucky", True), ("25.10", "questing", False), + ("26.04", "resolute", False), ] print("Building Ubuntu packages for:") From 9640d95cef4725bf9e3f3d40e7bee2815e9a76d7 Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Wed, 22 Oct 2025 22:02:40 +0200 Subject: [PATCH 47/79] Fix docs a little --- docs/source/technical/source.rst | 16 ++++++++-------- docs/source/technical/tests.rst | 2 +- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/docs/source/technical/source.rst b/docs/source/technical/source.rst index 44750bf2..c5bce486 100644 --- a/docs/source/technical/source.rst +++ b/docs/source/technical/source.rst @@ -47,20 +47,20 @@ must be at least 3.0 to work with Windows. On Linux, 2.0 also works fine. If you install novelWriter from PyPi, these dependencies should be installed automatically. -If you run it from source, and want to install dependencies using ``pip``, you must first generate -the ``requirements.txt`` file: +You can run novelWriter directly from source with uv_: + +.. code-block:: bash + + uv run novelwriter + +If you prefer to install dependencies using ``pip``, you must first generate the +``requirements.txt`` file: .. code-block:: bash python pkgutils.py gen-req pip install -r requirements.txt -Otherwise you can run novelWriter with uv_: - -.. code-block:: bash - - uv run novelwriter - .. note:: On Linux distros, the Qt library is usually split up into multiple packages. In some cases, diff --git a/docs/source/technical/tests.rst b/docs/source/technical/tests.rst index 4eb8c244..532bd750 100644 --- a/docs/source/technical/tests.rst +++ b/docs/source/technical/tests.rst @@ -35,7 +35,7 @@ You can disable the rendering of the GUI by setting the flag ``QT_QPA_PLATFORM=o Dependencies ------------ -To run generate the requirements file and install using ``pip``, run: +To generate the requirements file and install dependencies using ``pip``, run: .. code-block:: bash From 3ceaadd0c014b4c1c1dbbd5c807bf4b7e1404de0 Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Wed, 22 Oct 2025 22:07:50 +0200 Subject: [PATCH 48/79] Fix outdated docstring --- utils/common.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utils/common.py b/utils/common.py index a718971e..f5637285 100644 --- a/utils/common.py +++ b/utils/common.py @@ -32,7 +32,7 @@ SETUP_DIR = ROOT_DIR / "setup" def extractReqs(groups: list[str]) -> list[str]: - """Generate requirements.txt file from pyproject.toml.""" + """Extract dependency groups from pyproject.toml.""" data = tomllib.loads((ROOT_DIR / "pyproject.toml").read_text(encoding="utf-8")) reqs = [] if "app" in groups or "all" in groups: From ede6a94665a9445779ae7942c8da27018ba3cc28 Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Wed, 22 Oct 2025 22:32:45 +0200 Subject: [PATCH 49/79] Drop the pip command from pkgutils.py --- pkgutils.py | 42 +----------------------------------------- 1 file changed, 1 insertion(+), 41 deletions(-) diff --git a/pkgutils.py b/pkgutils.py index fc49f94d..1c17e960 100755 --- a/pkgutils.py +++ b/pkgutils.py @@ -29,7 +29,6 @@ from __future__ import annotations import argparse import datetime import shutil -import subprocess import sys import utils.assets @@ -55,33 +54,6 @@ def printVersion(args: argparse.Namespace) -> None: print(extractVersion(beQuiet=True)[0], end=None) -def installPackages(args: argparse.Namespace) -> None: - """Install package dependencies both for this script and for running - novelWriter itself. - """ - print("") - print("Installing Dependencies") - print("=======================") - print("") - - installQueue = ["pip", *extractReqs(["app"])] - if args.mac: - installQueue.append("pyobjc") - elif args.win: - installQueue.append("pywin32") - - pyCmd = [sys.executable, "-m"] - pipCmd = ["pip", "install", "--user", "--upgrade"] - for stepCmd in installQueue: - pkgCmd = stepCmd.split(" ") - try: - subprocess.call(pyCmd + pipCmd + pkgCmd) - except Exception as exc: - print("Failed with error:") - print(str(exc)) - sys.exit(1) - - def cleanBuildDirs(args: argparse.Namespace) -> None: """Recursively delete the 'build' and 'dist' folders.""" print("") @@ -160,18 +132,6 @@ if __name__ == "__main__": ) cmdVersion.set_defaults(func=printVersion) - # General - # ======= - - # Pip Install - cmdPipInstall = parsers.add_parser( - "pip", help="Install all package dependencies for novelWriter using pip." - ) - cmdPipInstall.add_argument("--linux", action="store_true", help="For Linux.", default=OS_LINUX) - cmdPipInstall.add_argument("--mac", action="store_true", help="For MacOS.", default=OS_DARWIN) - cmdPipInstall.add_argument("--win", action="store_true", help="For Windows.", default=OS_WIN) - cmdPipInstall.set_defaults(func=installPackages) - # Additional Builds # ================= @@ -320,7 +280,7 @@ if __name__ == "__main__": "groups", nargs="*", help=( "Groups to generate for, or 'all' to generate for all groups. " "Use 'app' to generate for just the core application. " - "Defaults to app dependencies." + "Defaults to 'app' if none are specified." ) ) cmdGenReq.set_defaults(func=genReqFiles) From 20e1126a625e3e199ab6ceef4febdf9f6c86b2cc Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Wed, 22 Oct 2025 23:11:56 +0200 Subject: [PATCH 50/79] Allow named comments to be repeated (#2483) --- novelwriter/core/indexdata.py | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/novelwriter/core/indexdata.py b/novelwriter/core/indexdata.py index b7867bd3..78477312 100644 --- a/novelwriter/core/indexdata.py +++ b/novelwriter/core/indexdata.py @@ -300,13 +300,13 @@ class IndexHeading: """Set the text for a comment and make sure it is a string.""" match comment.lower(): case "short" | "synopsis" | "summary": - self._comments["summary"] = str(text) + self._appendCommentText("summary", text) case "story" if key: self._cache.story.add(key) - self._comments[f"story.{key}"] = str(text) + self._appendCommentText(f"story.{key}", text) case "note" if key: self._cache.note.add(key) - self._comments[f"note.{key}"] = str(text) + self._appendCommentText(f"note.{key}", text) def setTag(self, tag: str) -> None: """Set the tag for references, and make sure it is a string.""" @@ -371,6 +371,7 @@ class IndexHeading: def unpackData(self, data: dict) -> None: """Unpack a heading entry from a dictionary.""" + self._comments = {} # These are accumulative and should be reset here for key, entry in data.items(): if key == "meta": self.setLevel(entry.get("level", "H0")) @@ -394,3 +395,13 @@ class IndexHeading: self.setComment(comment, compact(kind), str(entry)) else: raise KeyError("Unknown key in heading entry") + + ## + # Internal Functions + ## + + def _appendCommentText(self, key: str, text: str) -> None: + """Append text to a comment.""" + if current := self._comments.get(key): + text = f"{current:s}\n\n{text:s}" + self._comments[key] = str(text) From 10f0f6440bbcfb0550dadb02a565c833b75e75bc Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Wed, 22 Oct 2025 23:12:11 +0200 Subject: [PATCH 51/79] Extend tests --- tests/test_core/test_core_indexdata.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/tests/test_core/test_core_indexdata.py b/tests/test_core/test_core_indexdata.py index faef5d51..cb749be7 100644 --- a/tests/test_core/test_core_indexdata.py +++ b/tests/test_core/test_core_indexdata.py @@ -251,13 +251,17 @@ def testCoreIndexData_IndexHeading(): "note.consitency": "Only explode once", } + # Append Synopsis + head.setComment(nwComment.SYNOPSIS.name, "", "How it started ...") + assert head.synopsis == "In the beginning ...\n\nHow it started ..." + # Unpack KeyError with pytest.raises(KeyError, match="Unknown key in heading entry"): head.unpackData({"stuff": "more stuff"}) # Unpack Comments head.unpackData({"summary": "How it started ..."}) - assert head.synopsis == "How it started ..." + assert head.synopsis == "How it started ..." # This resets the comments dictionary @pytest.mark.core From f23ce71620a8cd7901d5b137c097d80da08a9945 Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Wed, 22 Oct 2025 23:12:27 +0200 Subject: [PATCH 52/79] Add stepwise and rerun commands to test runner script --- run_tests.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/run_tests.py b/run_tests.py index a2b8e937..18e1eeae 100755 --- a/run_tests.py +++ b/run_tests.py @@ -14,6 +14,8 @@ if __name__ == "__main__": parser.add_argument("-r", action="store_true", help="Generate reports") parser.add_argument("-t", action="store_true", help="Generate terminal report") parser.add_argument("-u", action="store_true", help="Generate uncovered terminal report") + parser.add_argument("-lf", action="store_true", help="Re-run failed tests") + parser.add_argument("-sw", action="store_true", help="Run tests stepwise") parser.add_argument("-m", help="Test modules", metavar="MARKEXPR") parser.add_argument("-k", help="Test filters", metavar="EXPRESSION") @@ -30,6 +32,10 @@ if __name__ == "__main__": cmd += ["pytest", "-vv"] if args.o: env["QT_QPA_PLATFORM"] = "offscreen" + if args.lf: + cmd += ["--last-failed"] + if args.sw: + cmd += ["--stepwise"] if args.m: cmd += ["-m", args.m] if args.k: From 096686457d1a171ef4b05b95ba7ae196a4f9205c Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Sat, 25 Oct 2025 20:47:32 +0200 Subject: [PATCH 53/79] Add a standard button generator --- novelwriter/enum.py | 15 +++++++++++++ novelwriter/extensions/modified.py | 34 +++++++++++++++++++++++++++-- novelwriter/gui/theme.py | 35 +++++++++++++++++++++++++----- novelwriter/tools/welcome.py | 32 ++++++++------------------- 4 files changed, 85 insertions(+), 31 deletions(-) diff --git a/novelwriter/enum.py b/novelwriter/enum.py index 7ea60294..1bb85120 100644 --- a/novelwriter/enum.py +++ b/novelwriter/enum.py @@ -247,3 +247,18 @@ class nwStatusShape(Enum): BLOCK_2 = 17 BLOCK_3 = 18 BLOCK_4 = 19 + + +class nwStandardButton(Enum): + """Enum: Standard Dialog Buttons.""" + + OK = 0 + CANCEL = 1 + YES = 2 + NO = 3 + OPEN = 4 + CLOSE = 5 + BROWSE = 6 + LIST = 7 + NEW = 8 + CREATE = 9 diff --git a/novelwriter/extensions/modified.py b/novelwriter/extensions/modified.py index bb256ff1..7e245d21 100644 --- a/novelwriter/extensions/modified.py +++ b/novelwriter/extensions/modified.py @@ -31,8 +31,8 @@ from typing import TYPE_CHECKING from PyQt6.QtCore import QModelIndex, QSize, Qt, pyqtSignal, pyqtSlot from PyQt6.QtWidgets import ( - QApplication, QComboBox, QDialog, QDoubleSpinBox, QLabel, QSpinBox, - QToolButton, QTreeView, QWidget + QApplication, QComboBox, QDialog, QDoubleSpinBox, QLabel, QPushButton, + QSpinBox, QToolButton, QTreeView, QWidget ) from novelwriter import CONFIG, SHARED @@ -199,6 +199,36 @@ class NDoubleSpinBox(QDoubleSpinBox): event.ignore() +class NPushButton(QPushButton): + """Custom: Modified QPushButton. + + A quicker way to create a push button using the app theme. + """ + + def __init__( + self, parent: QWidget, text: str, iconSize: QSize, + icon: str | None = None, color: str | None = None + ) -> None: + super().__init__(parent=parent) + self.setText(text) + self.setIconSize(iconSize) + self._icon = icon + self._color = color + if icon: + self.refreshIcon() + + def setThemeIcon(self, icon: str, color: str | None = None) -> None: + """Set an icon from the current theme.""" + self._icon = icon + self._color = color + self.refreshIcon() + + def refreshIcon(self) -> None: + """Reload the theme icon.""" + if self._icon: + self.setIcon(SHARED.theme.getIcon(self._icon, self._color)) + + class NIconToolButton(QToolButton): """Custom: Modified QToolButton. diff --git a/novelwriter/gui/theme.py b/novelwriter/gui/theme.py index f3a026f1..9b3c10d4 100644 --- a/novelwriter/gui/theme.py +++ b/novelwriter/gui/theme.py @@ -31,19 +31,20 @@ from dataclasses import dataclass from math import ceil from typing import TYPE_CHECKING, Final -from PyQt6.QtCore import QSize, Qt +from PyQt6.QtCore import QT_TRANSLATE_NOOP, QCoreApplication, QSize, Qt from PyQt6.QtGui import ( QColor, QFont, QFontDatabase, QFontMetrics, QGuiApplication, QIcon, QPainter, QPainterPath, QPalette, QPixmap ) -from PyQt6.QtWidgets import QApplication +from PyQt6.QtWidgets import QApplication, QWidget from novelwriter import CONFIG from novelwriter.common import checkInt, minmax from novelwriter.config import DEF_GUI_DARK, DEF_GUI_LIGHT, DEF_ICONS, DEF_TREECOL from novelwriter.constants import nwLabels -from novelwriter.enum import nwItemClass, nwItemLayout, nwItemType, nwTheme +from novelwriter.enum import nwItemClass, nwItemLayout, nwItemType, nwStandardButton, nwTheme from novelwriter.error import logException +from novelwriter.extensions.modified import NPushButton from novelwriter.types import QtBlack, QtHexArgb, QtPaintAntiAlias, QtTransparent if TYPE_CHECKING: @@ -55,6 +56,19 @@ STYLES_FLAT_TABS = "flatTabWidget" STYLES_MIN_TOOLBUTTON = "minimalToolButton" STYLES_BIG_TOOLBUTTON = "bigToolButton" +STANDARD_BUTTONS = { + nwStandardButton.OK: (QT_TRANSLATE_NOOP("Button", "OK"), "bullet-on", "blue"), + nwStandardButton.CANCEL: (QT_TRANSLATE_NOOP("Button", "Cancel"), "cancel", "red"), + nwStandardButton.YES: (QT_TRANSLATE_NOOP("Button", "Yes"), "bullet-on", "green"), + nwStandardButton.NO: (QT_TRANSLATE_NOOP("Button", "No"), "bullet-on", "red"), + nwStandardButton.OPEN: (QT_TRANSLATE_NOOP("Button", "Open"), "open", "blue"), + nwStandardButton.CLOSE: (QT_TRANSLATE_NOOP("Button", "Close"), "close", "default"), + nwStandardButton.BROWSE: (QT_TRANSLATE_NOOP("Button", "Browse"), "browse", "yellow"), + nwStandardButton.LIST: (QT_TRANSLATE_NOOP("Button", "List"), "list", "blue"), + nwStandardButton.NEW: (QT_TRANSLATE_NOOP("Button", "New"), "add", "green"), + nwStandardButton.CREATE: (QT_TRANSLATE_NOOP("Button", "Create"), "star", "yellow"), +} + @dataclass class ThemeEntry: @@ -120,9 +134,9 @@ class GuiTheme: "_qColors", "_styleSheets", "_svgColors", "_syntaxList", "accentCol", "baseButtonHeight", "baseIconHeight", "baseIconSize", "buttonIconSize", "errorText", "fadedText", "fontPixelSize", "fontPointSize", "getDecoration", "getHeaderDecoration", - "getHeaderDecorationNarrow", "getIcon", "getItemIcon", "getPixmap", "getToggleIcon", - "guiFont", "guiFontB", "guiFontBU", "guiFontFixed", "guiFontSmall", "helpText", - "iconCache", "isDarkTheme", "syntaxTheme", "textNHeight", "textNWidth", + "getHeaderDecorationNarrow", "getIcon", "getItemIcon", "getPixmap", "getStandardButton", + "getToggleIcon", "guiFont", "guiFontB", "guiFontBU", "guiFontFixed", "guiFontSmall", + "helpText", "iconCache", "isDarkTheme", "syntaxTheme", "textNHeight", "textNWidth", ) def __init__(self) -> None: @@ -153,6 +167,7 @@ class GuiTheme: self.getItemIcon = self.iconCache.getItemIcon self.getToggleIcon = self.iconCache.getToggleIcon self.getDecoration = self.iconCache.getDecoration + self.getStandardButton = self.iconCache.getStandardButton self.getHeaderDecoration = self.iconCache.getHeaderDecoration self.getHeaderDecorationNarrow = self.iconCache.getHeaderDecorationNarrow @@ -808,6 +823,14 @@ class GuiIcons: w, h = size return self.getIcon(name, color, w, h).pixmap(w, h, QIcon.Mode.Normal) + def getStandardButton(self, button: nwStandardButton, parent: QWidget) -> NPushButton: + """Return a standard button with icon and text.""" + text, icon, color = STANDARD_BUTTONS.get(button, ("", "", "")) + return NPushButton( + parent, QCoreApplication.translate("Button", text), + self._theme.buttonIconSize, icon, color + ) + def getDecoration(self, name: str, w: int | None = None, h: int | None = None) -> QPixmap: """Load graphical decoration element based on the decoration map or the icon map. This function always returns a QPixmap. diff --git a/novelwriter/tools/welcome.py b/novelwriter/tools/welcome.py index 69d2e090..041a0031 100644 --- a/novelwriter/tools/welcome.py +++ b/novelwriter/tools/welcome.py @@ -35,15 +35,15 @@ from PyQt6.QtCore import ( from PyQt6.QtGui import QAction, QCloseEvent, QFont, QPainter, QPaintEvent, QPen, QShortcut from PyQt6.QtWidgets import ( QApplication, QFileDialog, QFormLayout, QHBoxLayout, QLabel, QLineEdit, - QListView, QMenu, QPushButton, QScrollArea, QStackedWidget, - QStyledItemDelegate, QStyleOptionViewItem, QVBoxLayout, QWidget + QListView, QMenu, QScrollArea, QStackedWidget, QStyledItemDelegate, + QStyleOptionViewItem, QVBoxLayout, QWidget ) from novelwriter import CONFIG, SHARED from novelwriter.common import formatInt, makeFileNameSafe, qtAddAction, qtLambda from novelwriter.constants import nwFiles from novelwriter.core.coretools import ProjectBuilder -from novelwriter.enum import nwItemClass +from novelwriter.enum import nwItemClass, nwStandardButton from novelwriter.extensions.configlayout import NWrappedWidgetBox from novelwriter.extensions.modified import NDialog, NIconToolButton, NSpinBox from novelwriter.extensions.switch import NSwitch @@ -75,8 +75,6 @@ class GuiWelcome(NDialog): self.setMinimumHeight(450) self.resize(*CONFIG.welcomeWinSize) - btnIconSize = SHARED.theme.buttonIconSize - # Elements # ======== @@ -104,34 +102,22 @@ class GuiWelcome(NDialog): # Buttons # ======= - self.btnList = QPushButton(self.tr("List"), self) - self.btnList.setIcon(SHARED.theme.getIcon("list", "blue")) - self.btnList.setIconSize(btnIconSize) + self.btnList = SHARED.theme.getStandardButton(nwStandardButton.LIST, self) self.btnList.clicked.connect(self._showOpenProjectPage) - self.btnNew = QPushButton(self.tr("New"), self) - self.btnNew.setIcon(SHARED.theme.getIcon("add", "green")) - self.btnNew.setIconSize(btnIconSize) + self.btnNew = SHARED.theme.getStandardButton(nwStandardButton.NEW, self) self.btnNew.clicked.connect(self._showNewProjectPage) - self.btnBrowse = QPushButton(self.tr("Browse"), self) - self.btnBrowse.setIcon(SHARED.theme.getIcon("browse", "yellow")) - self.btnBrowse.setIconSize(btnIconSize) + self.btnBrowse = SHARED.theme.getStandardButton(nwStandardButton.BROWSE, self) self.btnBrowse.clicked.connect(self._browseForProject) - self.btnCancel = QPushButton(self.tr("Cancel"), self) - self.btnCancel.setIcon(SHARED.theme.getIcon("cancel", "red")) - self.btnCancel.setIconSize(btnIconSize) + self.btnCancel = SHARED.theme.getStandardButton(nwStandardButton.CANCEL, self) self.btnCancel.clicked.connect(qtLambda(self.close)) - self.btnCreate = QPushButton(self.tr("Create"), self) - self.btnCreate.setIcon(SHARED.theme.getIcon("star", "yellow")) - self.btnCreate.setIconSize(btnIconSize) + self.btnCreate = SHARED.theme.getStandardButton(nwStandardButton.CREATE, self) self.btnCreate.clicked.connect(self.tabNew.createNewProject) - self.btnOpen = QPushButton(self.tr("Open"), self) - self.btnOpen.setIcon(SHARED.theme.getIcon("open", "blue")) - self.btnOpen.setIconSize(btnIconSize) + self.btnOpen = SHARED.theme.getStandardButton(nwStandardButton.OPEN, self) self.btnOpen.clicked.connect(self._openSelectedItem) self.btnBox = QHBoxLayout() From bb1d90213b400526a5760fcc7a3e5b8954189c71 Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Sat, 25 Oct 2025 21:38:30 +0200 Subject: [PATCH 54/79] Update standard dialog boxes --- novelwriter/enum.py | 1 + novelwriter/gui/theme.py | 21 +++++++++++---------- novelwriter/shared.py | 35 ++++++++++++++++++++++++++++++----- 3 files changed, 42 insertions(+), 15 deletions(-) diff --git a/novelwriter/enum.py b/novelwriter/enum.py index 1bb85120..4db5cdce 100644 --- a/novelwriter/enum.py +++ b/novelwriter/enum.py @@ -262,3 +262,4 @@ class nwStandardButton(Enum): LIST = 7 NEW = 8 CREATE = 9 + RESET = 10 diff --git a/novelwriter/gui/theme.py b/novelwriter/gui/theme.py index 9b3c10d4..441c9cd1 100644 --- a/novelwriter/gui/theme.py +++ b/novelwriter/gui/theme.py @@ -57,16 +57,17 @@ STYLES_MIN_TOOLBUTTON = "minimalToolButton" STYLES_BIG_TOOLBUTTON = "bigToolButton" STANDARD_BUTTONS = { - nwStandardButton.OK: (QT_TRANSLATE_NOOP("Button", "OK"), "bullet-on", "blue"), - nwStandardButton.CANCEL: (QT_TRANSLATE_NOOP("Button", "Cancel"), "cancel", "red"), - nwStandardButton.YES: (QT_TRANSLATE_NOOP("Button", "Yes"), "bullet-on", "green"), - nwStandardButton.NO: (QT_TRANSLATE_NOOP("Button", "No"), "bullet-on", "red"), - nwStandardButton.OPEN: (QT_TRANSLATE_NOOP("Button", "Open"), "open", "blue"), - nwStandardButton.CLOSE: (QT_TRANSLATE_NOOP("Button", "Close"), "close", "default"), - nwStandardButton.BROWSE: (QT_TRANSLATE_NOOP("Button", "Browse"), "browse", "yellow"), - nwStandardButton.LIST: (QT_TRANSLATE_NOOP("Button", "List"), "list", "blue"), - nwStandardButton.NEW: (QT_TRANSLATE_NOOP("Button", "New"), "add", "green"), - nwStandardButton.CREATE: (QT_TRANSLATE_NOOP("Button", "Create"), "star", "yellow"), + nwStandardButton.OK: (QT_TRANSLATE_NOOP("Button", "OK"), "btn_ok", "blue"), + nwStandardButton.CANCEL: (QT_TRANSLATE_NOOP("Button", "Cancel"), "btn_cancel", "red"), + nwStandardButton.YES: (QT_TRANSLATE_NOOP("Button", "Yes"), "btn_yes", "green"), + nwStandardButton.NO: (QT_TRANSLATE_NOOP("Button", "No"), "btn_no", "red"), + nwStandardButton.OPEN: (QT_TRANSLATE_NOOP("Button", "Open"), "btn_open", "blue"), + nwStandardButton.CLOSE: (QT_TRANSLATE_NOOP("Button", "Close"), "btn_close", "red"), + nwStandardButton.BROWSE: (QT_TRANSLATE_NOOP("Button", "Browse"), "btn_browse", "yellow"), + nwStandardButton.LIST: (QT_TRANSLATE_NOOP("Button", "List"), "btn_list", "blue"), + nwStandardButton.NEW: (QT_TRANSLATE_NOOP("Button", "New"), "btn_new", "green"), + nwStandardButton.CREATE: (QT_TRANSLATE_NOOP("Button", "Create"), "btn_create", "yellow"), + nwStandardButton.RESET: (QT_TRANSLATE_NOOP("Button", "Reset"), "btn_reset", "green"), } diff --git a/novelwriter/shared.py b/novelwriter/shared.py index 7c971341..be439b48 100644 --- a/novelwriter/shared.py +++ b/novelwriter/shared.py @@ -34,12 +34,12 @@ from typing import TYPE_CHECKING, TypeVar from PyQt6.QtCore import QObject, QRunnable, QThreadPool, QTimer, QUrl, pyqtSignal, pyqtSlot from PyQt6.QtGui import QDesktopServices, QFont, QScreen -from PyQt6.QtWidgets import QApplication, QFileDialog, QFontDialog, QMessageBox, QWidget +from PyQt6.QtWidgets import QApplication, QDialog, QFileDialog, QFontDialog, QMessageBox, QWidget from novelwriter.common import formatFileFilter from novelwriter.constants import nwFiles from novelwriter.core.spellcheck import NWSpellEnchant -from novelwriter.enum import nwChange, nwItemClass +from novelwriter.enum import nwChange, nwItemClass, nwStandardButton if TYPE_CHECKING: from collections.abc import Callable @@ -422,7 +422,7 @@ class SharedData(QObject): alert.setAlertType(_GuiAlert.WARN if warn else _GuiAlert.ASK, True) self._lastAlert = alert.logMessage alert.exec() - return alert.result() == QMessageBox.StandardButton.Yes + return alert.finalState ## # Internal Functions @@ -469,6 +469,7 @@ class _GuiAlert(QMessageBox): super().__init__(parent=parent) self._theme = theme self._message = "" + self._state = False logger.debug("Ready: _GuiAlert") def __del__(self) -> None: # pragma: no cover @@ -478,6 +479,10 @@ class _GuiAlert(QMessageBox): def logMessage(self) -> str: return self._message + @property + def finalState(self) -> bool: + return self._state + def setMessage(self, text: str, info: str, details: str) -> None: """Set the alert box message.""" self._message = " ".join(filter(None, [text, info, details])) @@ -496,9 +501,17 @@ class _GuiAlert(QMessageBox): Yes/No buttons or just an Ok button. """ if isYesNo: - self.setStandardButtons(QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No) + self._btnYes = self._theme.getStandardButton(nwStandardButton.YES, self) + self._btnYes.clicked.connect(self._onAccept) + self._btnNo = self._theme.getStandardButton(nwStandardButton.NO, self) + self._btnNo.clicked.connect(self._onReject) + self.addButton(self._btnYes, QMessageBox.ButtonRole.YesRole) + self.addButton(self._btnNo, QMessageBox.ButtonRole.NoRole) else: - self.setStandardButtons(QMessageBox.StandardButton.Ok) + self._btnOk = self._theme.getStandardButton(nwStandardButton.OK, self) + self._btnOk.clicked.connect(self._onAccept) + self.addButton(self._btnOk, QMessageBox.ButtonRole.AcceptRole) + pSz = 2*self._theme.baseIconHeight if level == self.INFO: self.setIconPixmap(self._theme.getPixmap("alert_info", (pSz, pSz), "blue")) @@ -512,3 +525,15 @@ class _GuiAlert(QMessageBox): elif level == self.ASK: self.setIconPixmap(self._theme.getPixmap("alert_question", (pSz, pSz), "blue")) self.setWindowTitle(self.tr("Question")) + + @pyqtSlot() + def _onAccept(self) -> None: + """Process accepted state.""" + self._state = True + self.close() + + @pyqtSlot() + def _onReject(self) -> None: + """Process rejected state.""" + self._state = False + self.setResult(QDialog.DialogCode.Rejected) From 1938969a63be1afdd28cc81ae8f174bd927b9248 Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Sat, 25 Oct 2025 21:42:39 +0200 Subject: [PATCH 55/79] Update icon themes --- .../assets/icons/material_filled_normal.icons | 13 +++++++++++-- .../assets/icons/material_filled_thin.icons | 13 +++++++++++-- .../assets/icons/material_rounded_normal.icons | 13 +++++++++++-- .../assets/icons/material_rounded_thin.icons | 13 +++++++++++-- .../assets/icons/material_sharp_normal.icons | 13 +++++++++++-- .../assets/icons/material_sharp_thin.icons | 13 +++++++++++-- tests/files/all_icons.json | 14 ++++++++++++-- utils/icon_themes.py | 16 ++++++++++++++-- utils/icon_themes/font_awesome.json | 18 ++++++++++++++---- utils/icon_themes/material_symbols.json | 14 ++++++++++++-- utils/icon_themes/remix.json | 14 ++++++++++++-- 11 files changed, 130 insertions(+), 24 deletions(-) diff --git a/novelwriter/assets/icons/material_filled_normal.icons b/novelwriter/assets/icons/material_filled_normal.icons index 36b8536f..1cd8cfb1 100644 --- a/novelwriter/assets/icons/material_filled_normal.icons +++ b/novelwriter/assets/icons/material_filled_normal.icons @@ -59,6 +59,17 @@ icon:sb_stats = icon:theme_dark = icon:theme_auto = +icon:btn_ok = +icon:btn_cancel = +icon:btn_yes = +icon:btn_no = +icon:btn_open = +icon:btn_close = +icon:btn_browse = +icon:btn_list = +icon:btn_new = +icon:btn_create = +icon:btn_reset = icon:add = icon:bookmarks = icon:browse = @@ -94,7 +105,6 @@ icon:minimise = icon:more_vertical = icon:noncheckable = -icon:open = icon:panel = icon:pin = icon:project_copy = @@ -103,7 +113,6 @@ icon:refresh = icon:revert = icon:settings = -icon:star = icon:stats = icon:text = icon:timer_off = diff --git a/novelwriter/assets/icons/material_filled_thin.icons b/novelwriter/assets/icons/material_filled_thin.icons index a405ea3e..2d399f22 100644 --- a/novelwriter/assets/icons/material_filled_thin.icons +++ b/novelwriter/assets/icons/material_filled_thin.icons @@ -59,6 +59,17 @@ icon:sb_stats = icon:theme_dark = icon:theme_auto = +icon:btn_ok = +icon:btn_cancel = +icon:btn_yes = +icon:btn_no = +icon:btn_open = +icon:btn_close = +icon:btn_browse = +icon:btn_list = +icon:btn_new = +icon:btn_create = +icon:btn_reset = icon:add = icon:bookmarks = icon:browse = @@ -94,7 +105,6 @@ icon:minimise = icon:more_vertical = icon:noncheckable = -icon:open = icon:panel = icon:pin = icon:project_copy = @@ -103,7 +113,6 @@ icon:refresh = icon:revert = icon:settings = -icon:star = icon:stats = icon:text = icon:timer_off = diff --git a/novelwriter/assets/icons/material_rounded_normal.icons b/novelwriter/assets/icons/material_rounded_normal.icons index 9c22a001..044bdd5d 100644 --- a/novelwriter/assets/icons/material_rounded_normal.icons +++ b/novelwriter/assets/icons/material_rounded_normal.icons @@ -59,6 +59,17 @@ icon:sb_stats = icon:theme_dark = icon:theme_auto = +icon:btn_ok = +icon:btn_cancel = +icon:btn_yes = +icon:btn_no = +icon:btn_open = +icon:btn_close = +icon:btn_browse = +icon:btn_list = +icon:btn_new = +icon:btn_create = +icon:btn_reset = icon:add = icon:bookmarks = icon:browse = @@ -94,7 +105,6 @@ icon:minimise = icon:more_vertical = icon:noncheckable = -icon:open = icon:panel = icon:pin = icon:project_copy = @@ -103,7 +113,6 @@ icon:refresh = icon:revert = icon:settings = -icon:star = icon:stats = icon:text = icon:timer_off = diff --git a/novelwriter/assets/icons/material_rounded_thin.icons b/novelwriter/assets/icons/material_rounded_thin.icons index 1002fcbf..22834e45 100644 --- a/novelwriter/assets/icons/material_rounded_thin.icons +++ b/novelwriter/assets/icons/material_rounded_thin.icons @@ -59,6 +59,17 @@ icon:sb_stats = icon:theme_dark = icon:theme_auto = +icon:btn_ok = +icon:btn_cancel = +icon:btn_yes = +icon:btn_no = +icon:btn_open = +icon:btn_close = +icon:btn_browse = +icon:btn_list = +icon:btn_new = +icon:btn_create = +icon:btn_reset = icon:add = icon:bookmarks = icon:browse = @@ -94,7 +105,6 @@ icon:minimise = icon:more_vertical = icon:noncheckable = -icon:open = icon:panel = icon:pin = icon:project_copy = @@ -103,7 +113,6 @@ icon:refresh = icon:revert = icon:settings = -icon:star = icon:stats = icon:text = icon:timer_off = diff --git a/novelwriter/assets/icons/material_sharp_normal.icons b/novelwriter/assets/icons/material_sharp_normal.icons index b8b8c0e6..13c68127 100644 --- a/novelwriter/assets/icons/material_sharp_normal.icons +++ b/novelwriter/assets/icons/material_sharp_normal.icons @@ -59,6 +59,17 @@ icon:sb_stats = icon:theme_dark = icon:theme_auto = +icon:btn_ok = +icon:btn_cancel = +icon:btn_yes = +icon:btn_no = +icon:btn_open = +icon:btn_close = +icon:btn_browse = +icon:btn_list = +icon:btn_new = +icon:btn_create = +icon:btn_reset = icon:add = icon:bookmarks = icon:browse = @@ -94,7 +105,6 @@ icon:minimise = icon:more_vertical = icon:noncheckable = -icon:open = icon:panel = icon:pin = icon:project_copy = @@ -103,7 +113,6 @@ icon:refresh = icon:revert = icon:settings = -icon:star = icon:stats = icon:text = icon:timer_off = diff --git a/novelwriter/assets/icons/material_sharp_thin.icons b/novelwriter/assets/icons/material_sharp_thin.icons index afe8528d..c8bb13a8 100644 --- a/novelwriter/assets/icons/material_sharp_thin.icons +++ b/novelwriter/assets/icons/material_sharp_thin.icons @@ -59,6 +59,17 @@ icon:sb_stats = icon:theme_dark = icon:theme_auto = +icon:btn_ok = +icon:btn_cancel = +icon:btn_yes = +icon:btn_no = +icon:btn_open = +icon:btn_close = +icon:btn_browse = +icon:btn_list = +icon:btn_new = +icon:btn_create = +icon:btn_reset = icon:add = icon:bookmarks = icon:browse = @@ -94,7 +105,6 @@ icon:minimise = icon:more_vertical = icon:noncheckable = -icon:open = icon:panel = icon:pin = icon:project_copy = @@ -103,7 +113,6 @@ icon:refresh = icon:revert = icon:settings = -icon:star = icon:stats = icon:text = icon:timer_off = diff --git a/tests/files/all_icons.json b/tests/files/all_icons.json index 3d85f818..41797f20 100644 --- a/tests/files/all_icons.json +++ b/tests/files/all_icons.json @@ -60,6 +60,18 @@ "theme_dark", "theme_auto", + "btn_ok", + "btn_cancel", + "btn_yes", + "btn_no", + "btn_open", + "btn_close", + "btn_browse", + "btn_list", + "btn_new", + "btn_create", + "btn_reset", + "add", "bookmarks", "browse", @@ -95,7 +107,6 @@ "more_arrow", "more_vertical", "noncheckable", - "open", "panel", "pin", "project_copy", @@ -104,7 +115,6 @@ "remove", "revert", "settings", - "star", "stats", "text", "timer_off", diff --git a/utils/icon_themes.py b/utils/icon_themes.py index cb906b01..e5c2268f 100644 --- a/utils/icon_themes.py +++ b/utils/icon_themes.py @@ -107,6 +107,18 @@ ICONS = [ "theme_dark", "theme_auto", + "btn_ok", + "btn_cancel", + "btn_yes", + "btn_no", + "btn_open", + "btn_close", + "btn_browse", + "btn_list", + "btn_new", + "btn_create", + "btn_reset", + "add", "bookmarks", "browse", @@ -142,7 +154,6 @@ ICONS = [ "more_arrow", "more_vertical", "noncheckable", - "open", "panel", "pin", "project_copy", @@ -151,7 +162,6 @@ ICONS = [ "remove", "revert", "settings", - "star", "stats", "text", "timer_off", @@ -288,6 +298,8 @@ def processFontAwesome(workDir: Path, iconsDir: Path, jobs: dict) -> None: viewbox = [int(x) for x in svg.get("viewBox", "").split()] viewbox = [viewbox[2]//2 - 256, 0, 512, 512] svg.set("viewBox", " ".join(str(x) for x in viewbox)) + for elem in svg.iter(): + elem.attrib.pop("fill", None) icons[key] = svg else: print(f"Not Found: {icon}.svg") diff --git a/utils/icon_themes/font_awesome.json b/utils/icon_themes/font_awesome.json index d98dc9e2..1769409e 100644 --- a/utils/icon_themes/font_awesome.json +++ b/utils/icon_themes/font_awesome.json @@ -60,6 +60,18 @@ "theme_dark": "moon", "theme_auto": "circle-half-stroke", + "btn_ok": "circle-check", + "btn_cancel": "ban", + "btn_yes": "circle-check", + "btn_no": "circle-xmark", + "btn_open": "file-arrow-up", + "btn_close": "circle-xmark", + "btn_browse": "folder-open", + "btn_list": "list", + "btn_new": "plus", + "btn_create": "star", + "btn_reset": "rotate-left", + "add": "plus", "bookmarks": "bookmark", "browse": "folder-open", @@ -95,16 +107,14 @@ "more_arrow": "caret-right", "more_vertical": "ellipsis-vertical", "noncheckable": "square-minus", - "open": "file-arrow-up", "panel": "table-list", "pin": "thumbtack", "project_copy": "copy", "quote": "quote-right", - "refresh": "arrow-rotate-right", + "refresh": "rotate-right", "remove": "minus", - "revert": "arrow-rotate-left", + "revert": "rotate-left", "settings": "gear", - "star": "star", "stats": "chart-line", "text": "file-lines", "timer_off": "pause", diff --git a/utils/icon_themes/material_symbols.json b/utils/icon_themes/material_symbols.json index 61c1975a..3d1adc47 100644 --- a/utils/icon_themes/material_symbols.json +++ b/utils/icon_themes/material_symbols.json @@ -60,6 +60,18 @@ "theme_dark": "dark_mode", "theme_auto": "contrast", + "btn_ok": "check_circle", + "btn_cancel": "cancel", + "btn_yes": "check_circle", + "btn_no": "do_not_disturb_on", + "btn_open": "open_in_new", + "btn_close": "close", + "btn_browse": "folder_open", + "btn_list": "format_list_bulleted", + "btn_new": "new_window", + "btn_create": "star", + "btn_reset": "undo", + "add": "add", "bookmarks": "bookmarks", "browse": "folder_open", @@ -95,7 +107,6 @@ "more_arrow": "arrow_right", "more_vertical": "more_vert", "noncheckable": "indeterminate_check_box", - "open": "open_in_new", "panel": "dock_to_bottom", "pin": "keep", "project_copy": "folder_copy", @@ -104,7 +115,6 @@ "remove": "remove", "revert": "settings_backup_restore", "settings": "settings", - "star": "star", "stats": "stacked_line_chart", "text": "subject", "timer_off": "timer_off", diff --git a/utils/icon_themes/remix.json b/utils/icon_themes/remix.json index 0ac173ec..da78a84f 100644 --- a/utils/icon_themes/remix.json +++ b/utils/icon_themes/remix.json @@ -60,6 +60,18 @@ "theme_dark": "moon", "theme_auto": "contrast", + "btn_ok": "checkbox-circle", + "btn_cancel": "indeterminate-circle", + "btn_yes": "checkbox-circle", + "btn_no": "close-circle", + "btn_open": "file-upload", + "btn_close": "close-circle", + "btn_browse": "folder-2", + "btn_list": "list-unordered", + "btn_new": "add", + "btn_create": "star-fill", + "btn_reset": "reset-left", + "add": "add", "bookmarks": "bookmark", "browse": "folder-2", @@ -95,7 +107,6 @@ "more_arrow": "arrow-right-s-fill", "more_vertical": "more-2-fill", "noncheckable": "checkbox-indeterminate", - "open": "file-upload", "panel": "layout-bottom", "pin": "pushpin", "project_copy": "file-copy-2", @@ -104,7 +115,6 @@ "remove": "subtract", "revert": "reset-left", "settings": "settings-2", - "star": "star-fill", "stats": "line-chart", "text": "file-text", "timer_off": "zzz", From 2d67cb7dcc7df98ee2856dabce186bb8da4b79b4 Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Sat, 25 Oct 2025 22:37:19 +0200 Subject: [PATCH 56/79] Update tests and test coverage --- novelwriter/extensions/modified.py | 6 -- novelwriter/shared.py | 4 +- run_tests.py | 5 +- tests/conftest.py | 8 ++- tests/mocked.py | 6 ++ tests/test_base/test_base_shared.py | 70 +++++++++++++++++++++-- tests/test_core/test_core_project.py | 7 +-- tests/test_gui/test_gui_guimain.py | 9 +-- tests/test_gui/test_gui_i18n.py | 4 +- tests/test_gui/test_gui_mainmenu.py | 5 +- tests/test_gui/test_gui_projtree.py | 13 +++-- tests/test_tools/test_tools_manusbuild.py | 5 +- 12 files changed, 105 insertions(+), 37 deletions(-) diff --git a/novelwriter/extensions/modified.py b/novelwriter/extensions/modified.py index 7e245d21..b802a4d9 100644 --- a/novelwriter/extensions/modified.py +++ b/novelwriter/extensions/modified.py @@ -217,12 +217,6 @@ class NPushButton(QPushButton): if icon: self.refreshIcon() - def setThemeIcon(self, icon: str, color: str | None = None) -> None: - """Set an icon from the current theme.""" - self._icon = icon - self._color = color - self.refreshIcon() - def refreshIcon(self) -> None: """Reload the theme icon.""" if self._icon: diff --git a/novelwriter/shared.py b/novelwriter/shared.py index be439b48..b76ad165 100644 --- a/novelwriter/shared.py +++ b/novelwriter/shared.py @@ -34,7 +34,7 @@ from typing import TYPE_CHECKING, TypeVar from PyQt6.QtCore import QObject, QRunnable, QThreadPool, QTimer, QUrl, pyqtSignal, pyqtSlot from PyQt6.QtGui import QDesktopServices, QFont, QScreen -from PyQt6.QtWidgets import QApplication, QDialog, QFileDialog, QFontDialog, QMessageBox, QWidget +from PyQt6.QtWidgets import QApplication, QFileDialog, QFontDialog, QMessageBox, QWidget from novelwriter.common import formatFileFilter from novelwriter.constants import nwFiles @@ -536,4 +536,4 @@ class _GuiAlert(QMessageBox): def _onReject(self) -> None: """Process rejected state.""" self._state = False - self.setResult(QDialog.DialogCode.Rejected) + self.close() diff --git a/run_tests.py b/run_tests.py index 18e1eeae..6f593127 100755 --- a/run_tests.py +++ b/run_tests.py @@ -25,7 +25,10 @@ if __name__ == "__main__": env["QT_SCALE_FACTOR"] = "1.0" if args.r or args.t or args.u: - cmd = ["coverage", "run", "-m"] + cmd = ["coverage", "run"] + if args.lf or args.sw: + cmd += ["--append"] + cmd += ["-m"] else: cmd = [sys.executable, "-m"] diff --git a/tests/conftest.py b/tests/conftest.py index 765755bf..f8e3a5eb 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -157,9 +157,11 @@ def projPath(fncPath): def mockGUI(qtbot, monkeypatch): """Create a mock instance of novelWriter's main GUI class.""" from novelwriter.gui.theme import GuiTheme + from novelwriter.shared import _GuiAlert monkeypatch.setattr(QMessageBox, "exec", lambda *a: None) - monkeypatch.setattr(QMessageBox, "result", lambda *a: QMessageBox.StandardButton.Yes) + monkeypatch.setattr(_GuiAlert, "exec", lambda *a: None) + monkeypatch.setattr(_GuiAlert, "finalState", True) gui = MockGuiMain() theme = GuiTheme() monkeypatch.setattr(SHARED, "_gui", gui) @@ -182,9 +184,11 @@ def nwGUI(qtbot, monkeypatch, functionFixture): """Create an instance of the novelWriter GUI.""" from novelwriter.gui.theme import GuiTheme from novelwriter.guimain import GuiMain + from novelwriter.shared import _GuiAlert monkeypatch.setattr(QMessageBox, "exec", lambda *a: None) - monkeypatch.setattr(QMessageBox, "result", lambda *a: QMessageBox.StandardButton.Yes) + monkeypatch.setattr(_GuiAlert, "exec", lambda *a: None) + monkeypatch.setattr(_GuiAlert, "finalState", True) CONFIG.loadConfig() SHARED.initTheme(GuiTheme()) diff --git a/tests/mocked.py b/tests/mocked.py index f86d5be0..92b56abe 100644 --- a/tests/mocked.py +++ b/tests/mocked.py @@ -22,9 +22,12 @@ from __future__ import annotations from unittest.mock import MagicMock +from PyQt6.QtCore import QSize from PyQt6.QtGui import QFont, QIcon, QPixmap from PyQt6.QtWidgets import QWidget +from novelwriter.extensions.modified import NPushButton + class MockGuiMain(QWidget): @@ -72,6 +75,9 @@ class MockTheme: def getHeaderDecoration(self, *a) -> QPixmap: return QPixmap() + def getStandardButton(self, *a) -> NPushButton: + return NPushButton(None, "", QSize(1, 1)) # type: ignore + def getIcon(self, *a) -> QIcon: return QIcon() diff --git a/tests/test_base/test_base_shared.py b/tests/test_base/test_base_shared.py index 64cc306b..a020a694 100644 --- a/tests/test_base/test_base_shared.py +++ b/tests/test_base/test_base_shared.py @@ -26,10 +26,10 @@ import pytest from PyQt6.QtCore import QUrl from PyQt6.QtGui import QDesktopServices -from PyQt6.QtWidgets import QFileDialog, QMessageBox, QWidget +from PyQt6.QtWidgets import QFileDialog, QWidget from novelwriter.core.project import NWProject -from novelwriter.shared import SharedData +from novelwriter.shared import SharedData, _GuiAlert from tests.mocked import MockGuiMain, MockTheme from tests.tools import buildTestProject @@ -143,10 +143,10 @@ def testBaseSharedData_Projects(monkeypatch, caplog, fncPath): @pytest.mark.base -def testBaseSharedData_Alerts(qtbot, monkeypatch, caplog): +def testBaseSharedData_Alerts(qtbot, monkeypatch, caplog, mockGUI): """Test SharedData class alert helper functions.""" - monkeypatch.setattr(QMessageBox, "exec", lambda *a: None) - monkeypatch.setattr(QMessageBox, "result", lambda *a: QMessageBox.StandardButton.Yes) + monkeypatch.setattr(_GuiAlert, "exec", lambda *a: None) + monkeypatch.setattr(_GuiAlert, "finalState", True) shared = SharedData() @@ -188,3 +188,63 @@ def testBaseSharedData_Alerts(qtbot, monkeypatch, caplog): # Question box assert shared.question("Why?") is True assert shared.lastAlert == "Why?" + + +@pytest.mark.base +def testBaseSharedData_GuiAlert(): + """Test the _GuiAlert class.""" + alert = _GuiAlert(None, MockTheme()) # type: ignore + + # Default states + assert alert.logMessage == "" + assert alert.finalState is False + + # Populate message + text = "one" + info = "two" + details = "three" + alert.setMessage(text, info, details) + assert alert.logMessage == f"{text} {info} {details}" + assert alert.text() == text + assert alert.informativeText() == info + assert alert.detailedText() == details + + # Populate exception + exc = Exception("oops") + alert.setException(exc) + assert alert.logMessage == f"{text} {info} {details}" + assert alert.informativeText() == f"{info}
Exception: {exc!s}" + + # Alert: Info + alert.setAlertType(_GuiAlert.INFO, False) + assert hasattr(alert, "_btnOk") + assert alert.windowTitle() == "Information" + alert._btnOk.click() + assert alert.finalState is True + alert._state = False + + # Alert: Warning + alert.setAlertType(_GuiAlert.WARN, False) + assert hasattr(alert, "_btnOk") + assert alert.windowTitle() == "Warning" + alert._btnOk.click() + assert alert.finalState is True + alert._state = False + + # Alert: Error + alert.setAlertType(_GuiAlert.ERROR, False) + assert hasattr(alert, "_btnOk") + assert alert.windowTitle() == "Error" + alert._btnOk.click() + assert alert.finalState is True + alert._state = False + + # Alert: Question + alert.setAlertType(_GuiAlert.ASK, True) + assert hasattr(alert, "_btnYes") + assert hasattr(alert, "_btnNo") + assert alert.windowTitle() == "Question" + alert._btnYes.click() + assert alert.finalState is True + alert._btnNo.click() + assert alert.finalState is False diff --git a/tests/test_core/test_core_project.py b/tests/test_core/test_core_project.py index cdb202c1..799cd423 100644 --- a/tests/test_core/test_core_project.py +++ b/tests/test_core/test_core_project.py @@ -25,13 +25,12 @@ from zipfile import ZipFile import pytest -from PyQt6.QtWidgets import QMessageBox - from novelwriter import CONFIG, SHARED from novelwriter.constants import nwFiles from novelwriter.core.project import NWProject, NWProjectState from novelwriter.core.projectxml import ProjectXMLReader, ProjectXMLWriter, XMLReadState from novelwriter.enum import nwItemClass +from novelwriter.shared import _GuiAlert from tests.mocked import causeOSError from tests.tools import XML_IGNORE, C, buildTestProject, cmpFiles @@ -274,14 +273,14 @@ def testCoreProject_Open(monkeypatch, caplog, mockGUI, fncPath, mockRnd): # Won't convert legacy file with monkeypatch.context() as mp: mp.setattr(ProjectXMLReader, "state", property(lambda *a: XMLReadState.WAS_LEGACY)) - mp.setattr(QMessageBox, "result", lambda *a: QMessageBox.StandardButton.No) + mp.setattr(_GuiAlert, "finalState", False) assert project.openProject(fncPath, clearLock=True) is False assert "The file format of your project is about to be" in SHARED.lastAlert # Won't open project from newer version with monkeypatch.context() as mp: mp.setattr(ProjectXMLReader, "hexVersion", property(lambda *a: 0x99999999)) - mp.setattr(QMessageBox, "result", lambda *a: QMessageBox.StandardButton.No) + mp.setattr(_GuiAlert, "finalState", False) assert project.openProject(fncPath, clearLock=True) is False assert "This project was saved by a newer version" in SHARED.lastAlert diff --git a/tests/test_gui/test_gui_guimain.py b/tests/test_gui/test_gui_guimain.py index 0e15b71e..4cfa3a91 100644 --- a/tests/test_gui/test_gui_guimain.py +++ b/tests/test_gui/test_gui_guimain.py @@ -30,7 +30,7 @@ import pytest from PyQt6.QtCore import Qt from PyQt6.QtGui import QPalette -from PyQt6.QtWidgets import QInputDialog, QMessageBox +from PyQt6.QtWidgets import QInputDialog from novelwriter import CONFIG, SHARED, __hexversion__ from novelwriter.common import jsonEncode @@ -42,6 +42,7 @@ from novelwriter.gui.doceditor import GuiDocEditor from novelwriter.gui.noveltree import GuiNovelView from novelwriter.gui.outline import GuiOutlineView from novelwriter.gui.projtree import GuiProjectTree +from novelwriter.shared import _GuiAlert from novelwriter.tools.welcome import GuiWelcome from novelwriter.types import QtModCtrl, QtModShift @@ -104,7 +105,7 @@ def testGuiMain_Launch(qtbot, monkeypatch, nwGUI, projPath): # Check that closes can be blocked with monkeypatch.context() as mp: - mp.setattr(QMessageBox, "result", lambda *a: QMessageBox.StandardButton.No) + mp.setattr(_GuiAlert, "finalState", False) assert nwGUI.openProject(projPath) is True assert nwGUI.closeMain() is False nwGUI.closeProject() @@ -841,7 +842,7 @@ def testGuiMain_OpenClose(qtbot, monkeypatch, nwGUI, projPath, fncPath, mockRnd) # Block closing assert SHARED.hasProject is True with monkeypatch.context() as mp: - mp.setattr(QMessageBox, "result", lambda *a: QMessageBox.StandardButton.No) + mp.setattr(_GuiAlert, "finalState", False) assert nwGUI.openProject(projPath) is False assert SHARED.hasProject is True @@ -854,7 +855,7 @@ def testGuiMain_OpenClose(qtbot, monkeypatch, nwGUI, projPath, fncPath, mockRnd) shutil.copyfile(lockBack, lockPath) with monkeypatch.context() as mp: - mp.setattr(QMessageBox, "result", lambda *a: QMessageBox.StandardButton.No) + mp.setattr(_GuiAlert, "finalState", False) assert nwGUI.openProject(projPath) is False assert nwGUI.openProject(projPath) is True diff --git a/tests/test_gui/test_gui_i18n.py b/tests/test_gui/test_gui_i18n.py index 915d83a4..e482d85f 100644 --- a/tests/test_gui/test_gui_i18n.py +++ b/tests/test_gui/test_gui_i18n.py @@ -24,7 +24,7 @@ import sys import pytest -from PyQt6.QtWidgets import QApplication, QDialog, QMessageBox +from PyQt6.QtWidgets import QApplication, QDialog from novelwriter import CONFIG, SHARED from novelwriter.dialogs.about import GuiAbout @@ -49,8 +49,6 @@ LANG_DATA = CONFIG.listLanguages(CONFIG.LANG_NW) def testGuiI18n_Localisation(qtbot, monkeypatch, language, nwGUI, projPath): """Test loading the gui with a specific language.""" monkeypatch.setattr(QDialog, "exec", lambda *a: None) - monkeypatch.setattr(QMessageBox, "exec", lambda *a: None) - monkeypatch.setattr(QMessageBox, "result", lambda *a: QMessageBox.StandardButton.Yes) # Set the test language CONFIG.guiLocale = language diff --git a/tests/test_gui/test_gui_mainmenu.py b/tests/test_gui/test_gui_mainmenu.py index 0d6344bc..e599e9f9 100644 --- a/tests/test_gui/test_gui_mainmenu.py +++ b/tests/test_gui/test_gui_mainmenu.py @@ -25,12 +25,13 @@ from unittest.mock import MagicMock import pytest from PyQt6.QtGui import QAction, QDesktopServices, QTextBlock -from PyQt6.QtWidgets import QFileDialog, QMessageBox +from PyQt6.QtWidgets import QFileDialog from novelwriter import CONFIG, SHARED from novelwriter.constants import nwKeyWords, nwShortcode, nwStats, nwUnicode from novelwriter.enum import nwDocAction, nwDocInsert from novelwriter.gui.doceditor import GuiDocEditor +from novelwriter.shared import _GuiAlert from novelwriter.types import QtKeepAnchor, QtMoveRight, QtSelectWord from tests.tools import C, buildTestProject, writeFile @@ -575,7 +576,7 @@ def testGuiMainMenu_Insert(qtbot, monkeypatch, nwGUI, fncPath, projPath, mockRnd # The document isn't empty, so the message box should pop with monkeypatch.context() as mp: - mp.setattr(QMessageBox, "result", lambda *a, **k: QMessageBox.StandardButton.No) + mp.setattr(_GuiAlert, "finalState", False) assert not nwGUI.importDocument() assert docEditor.getText() == "Bar" diff --git a/tests/test_gui/test_gui_projtree.py b/tests/test_gui/test_gui_projtree.py index f6430f55..bdd50ad0 100644 --- a/tests/test_gui/test_gui_projtree.py +++ b/tests/test_gui/test_gui_projtree.py @@ -26,7 +26,7 @@ import pytest from PyQt6.QtCore import QEvent, QItemSelectionModel, QModelIndex, QPointF from PyQt6.QtGui import QMouseEvent -from PyQt6.QtWidgets import QMenu, QMessageBox +from PyQt6.QtWidgets import QMenu from novelwriter import CONFIG, SHARED from novelwriter.dialogs.docmerge import GuiDocMerge @@ -34,6 +34,7 @@ from novelwriter.dialogs.docsplit import GuiDocSplit from novelwriter.dialogs.editlabel import GuiEditLabel from novelwriter.enum import nwDocMode, nwItemClass, nwItemLayout, nwItemType from novelwriter.gui.projtree import _TreeContextMenu +from novelwriter.shared import _GuiAlert from novelwriter.types import ( QtAccepted, QtModNone, QtMouseLeft, QtMouseMiddle, QtRejected, QtScrollAlwaysOff, QtScrollAsNeeded @@ -627,7 +628,7 @@ def testGuiProjTree_DeleteRequest(qtbot, caplog, monkeypatch, nwGUI, projPath, m # User can cancel move to trash with monkeypatch.context() as mp: - mp.setattr(QMessageBox, "result", lambda *a: QMessageBox.StandardButton.No) + mp.setattr(_GuiAlert, "finalState", False) projTree.processDeleteRequest(hScenes, askFirst=True) assert [n.item.itemName for n in tree.model.root.allChildren()] == [ "Novel", "Title Page", "New Folder", "New Chapter", "New Scene", @@ -645,7 +646,7 @@ def testGuiProjTree_DeleteRequest(qtbot, caplog, monkeypatch, nwGUI, projPath, m # User can block permanent deletion with monkeypatch.context() as mp: - mp.setattr(QMessageBox, "result", lambda *a: QMessageBox.StandardButton.No) + mp.setattr(_GuiAlert, "finalState", False) projTree.processDeleteRequest(hScenes[0:2], askFirst=True) assert [n.item.itemName for n in tree.model.root.allChildren()] == [ "Novel", "Title Page", "New Folder", "New Chapter", "New Scene", @@ -677,7 +678,7 @@ def testGuiProjTree_DeleteRequest(qtbot, caplog, monkeypatch, nwGUI, projPath, m # Trash can be completely emptied, but user can block it with monkeypatch.context() as mp: - mp.setattr(QMessageBox, "result", lambda *a: QMessageBox.StandardButton.No) + mp.setattr(_GuiAlert, "finalState", False) projTree.emptyTrash() assert [n.item.itemName for n in tree.model.root.allChildren()] == [ "Novel", "Title Page", "Chapter Folder", "Plot", "Characters", "Trash", @@ -995,7 +996,7 @@ def testGuiProjTree_Duplicate(qtbot, monkeypatch, nwGUI, projPath, mockRnd): # Duplicate title page, but select no with monkeypatch.context() as mp: - mp.setattr(QMessageBox, "result", lambda *a: QtRejected) + mp.setattr(_GuiAlert, "finalState", False) projTree.duplicateFromHandle(C.hTitlePage) assert [n.item.itemName for n in tree.model.root.allChildren()] == [ "Novel", "Title Page", "New Folder", "New Chapter", "New Scene", @@ -1302,7 +1303,7 @@ def testGuiProjTree_ContextMenu(qtbot, monkeypatch, nwGUI, projPath, mockRnd): # Click no on the dialog with monkeypatch.context() as mp: - mp.setattr(QMessageBox, "result", lambda *a: QtRejected) + mp.setattr(_GuiAlert, "finalState", False) ctxMenu._convertFolderToFile(nwItemLayout.DOCUMENT) assert nodeOne.item.isFolderType() diff --git a/tests/test_tools/test_tools_manusbuild.py b/tests/test_tools/test_tools_manusbuild.py index 91924120..35ddb7fa 100644 --- a/tests/test_tools/test_tools_manusbuild.py +++ b/tests/test_tools/test_tools_manusbuild.py @@ -26,13 +26,14 @@ import pytest from PyQt6.QtCore import QUrl from PyQt6.QtGui import QDesktopServices -from PyQt6.QtWidgets import QFileDialog, QListWidgetItem, QMessageBox +from PyQt6.QtWidgets import QFileDialog, QListWidgetItem from pytestqt.qtbot import QtBot from novelwriter.constants import nwLabels from novelwriter.core.buildsettings import BuildSettings from novelwriter.enum import nwBuildFmt from novelwriter.guimain import GuiMain +from novelwriter.shared import _GuiAlert from novelwriter.tools.manusbuild import GuiManuscriptBuild from novelwriter.types import QtDialogClose @@ -134,7 +135,7 @@ def testToolManuscriptBuild_Main( manus.buildPath.setText(str(fncPath)) manus.buildName.setText("TestBuild") with monkeypatch.context() as mp: - mp.setattr(QMessageBox, "result", lambda *a: QMessageBox.StandardButton.No) + mp.setattr(_GuiAlert, "finalState", False) assert manus._runBuild() is False # Test that the open button works From 1b0c33984585640982e17c0837452b71c8677082 Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Sat, 25 Oct 2025 23:31:47 +0200 Subject: [PATCH 57/79] Add more icons --- .../assets/icons/material_filled_normal.icons | 8 +++++- .../assets/icons/material_filled_thin.icons | 8 +++++- .../icons/material_rounded_normal.icons | 8 +++++- .../assets/icons/material_rounded_thin.icons | 8 +++++- .../assets/icons/material_sharp_normal.icons | 8 +++++- .../assets/icons/material_sharp_thin.icons | 8 +++++- novelwriter/enum.py | 28 +++++++++++-------- tests/files/all_icons.json | 6 ++++ utils/icon_themes.py | 6 ++++ utils/icon_themes/font_awesome.json | 6 ++++ utils/icon_themes/material_symbols.json | 8 +++++- utils/icon_themes/remix.json | 6 ++++ 12 files changed, 90 insertions(+), 18 deletions(-) diff --git a/novelwriter/assets/icons/material_filled_normal.icons b/novelwriter/assets/icons/material_filled_normal.icons index 1cd8cfb1..7b58f1a4 100644 --- a/novelwriter/assets/icons/material_filled_normal.icons +++ b/novelwriter/assets/icons/material_filled_normal.icons @@ -64,12 +64,18 @@ icon:btn_cancel = icon:btn_no = icon:btn_open = -icon:btn_close = +icon:btn_close = +icon:btn_save = icon:btn_browse = icon:btn_list = icon:btn_new = icon:btn_create = icon:btn_reset = +icon:btn_insert = +icon:btn_apply = +icon:btn_build = +icon:btn_print = +icon:btn_preview = icon:add = icon:bookmarks = icon:browse = diff --git a/novelwriter/assets/icons/material_filled_thin.icons b/novelwriter/assets/icons/material_filled_thin.icons index 2d399f22..0bf9758b 100644 --- a/novelwriter/assets/icons/material_filled_thin.icons +++ b/novelwriter/assets/icons/material_filled_thin.icons @@ -64,12 +64,18 @@ icon:btn_cancel = icon:btn_no = icon:btn_open = -icon:btn_close = +icon:btn_close = +icon:btn_save = icon:btn_browse = icon:btn_list = icon:btn_new = icon:btn_create = icon:btn_reset = +icon:btn_insert = +icon:btn_apply = +icon:btn_build = +icon:btn_print = +icon:btn_preview = icon:add = icon:bookmarks = icon:browse = diff --git a/novelwriter/assets/icons/material_rounded_normal.icons b/novelwriter/assets/icons/material_rounded_normal.icons index 044bdd5d..0ff44cbd 100644 --- a/novelwriter/assets/icons/material_rounded_normal.icons +++ b/novelwriter/assets/icons/material_rounded_normal.icons @@ -64,12 +64,18 @@ icon:btn_cancel = icon:btn_no = icon:btn_open = -icon:btn_close = +icon:btn_close = +icon:btn_save = icon:btn_browse = icon:btn_list = icon:btn_new = icon:btn_create = icon:btn_reset = +icon:btn_insert = +icon:btn_apply = +icon:btn_build = +icon:btn_print = +icon:btn_preview = icon:add = icon:bookmarks = icon:browse = diff --git a/novelwriter/assets/icons/material_rounded_thin.icons b/novelwriter/assets/icons/material_rounded_thin.icons index 22834e45..b5606512 100644 --- a/novelwriter/assets/icons/material_rounded_thin.icons +++ b/novelwriter/assets/icons/material_rounded_thin.icons @@ -64,12 +64,18 @@ icon:btn_cancel = icon:btn_no = icon:btn_open = -icon:btn_close = +icon:btn_close = +icon:btn_save = icon:btn_browse = icon:btn_list = icon:btn_new = icon:btn_create = icon:btn_reset = +icon:btn_insert = +icon:btn_apply = +icon:btn_build = +icon:btn_print = +icon:btn_preview = icon:add = icon:bookmarks = icon:browse = diff --git a/novelwriter/assets/icons/material_sharp_normal.icons b/novelwriter/assets/icons/material_sharp_normal.icons index 13c68127..a9b6ff25 100644 --- a/novelwriter/assets/icons/material_sharp_normal.icons +++ b/novelwriter/assets/icons/material_sharp_normal.icons @@ -64,12 +64,18 @@ icon:btn_cancel = icon:btn_no = icon:btn_open = -icon:btn_close = +icon:btn_close = +icon:btn_save = icon:btn_browse = icon:btn_list = icon:btn_new = icon:btn_create = icon:btn_reset = +icon:btn_insert = +icon:btn_apply = +icon:btn_build = +icon:btn_print = +icon:btn_preview = icon:add = icon:bookmarks = icon:browse = diff --git a/novelwriter/assets/icons/material_sharp_thin.icons b/novelwriter/assets/icons/material_sharp_thin.icons index c8bb13a8..11e04deb 100644 --- a/novelwriter/assets/icons/material_sharp_thin.icons +++ b/novelwriter/assets/icons/material_sharp_thin.icons @@ -64,12 +64,18 @@ icon:btn_cancel = icon:btn_no = icon:btn_open = -icon:btn_close = +icon:btn_close = +icon:btn_save = icon:btn_browse = icon:btn_list = icon:btn_new = icon:btn_create = icon:btn_reset = +icon:btn_insert = +icon:btn_apply = +icon:btn_build = +icon:btn_print = +icon:btn_preview = icon:add = icon:bookmarks = icon:browse = diff --git a/novelwriter/enum.py b/novelwriter/enum.py index 4db5cdce..0b88f8ab 100644 --- a/novelwriter/enum.py +++ b/novelwriter/enum.py @@ -252,14 +252,20 @@ class nwStatusShape(Enum): class nwStandardButton(Enum): """Enum: Standard Dialog Buttons.""" - OK = 0 - CANCEL = 1 - YES = 2 - NO = 3 - OPEN = 4 - CLOSE = 5 - BROWSE = 6 - LIST = 7 - NEW = 8 - CREATE = 9 - RESET = 10 + OK = 0 + CANCEL = 1 + YES = 2 + NO = 3 + OPEN = 4 + CLOSE = 5 + SAVE = 6 + BROWSE = 7 + LIST = 8 + NEW = 9 + CREATE = 10 + RESET = 11 + INSERT = 12 + APPLY = 13 + BUILD = 14 + PRINT = 15 + PREVIEW = 16 diff --git a/tests/files/all_icons.json b/tests/files/all_icons.json index 41797f20..0579a3df 100644 --- a/tests/files/all_icons.json +++ b/tests/files/all_icons.json @@ -66,11 +66,17 @@ "btn_no", "btn_open", "btn_close", + "btn_save", "btn_browse", "btn_list", "btn_new", "btn_create", "btn_reset", + "btn_insert", + "btn_apply", + "btn_build", + "btn_print", + "btn_preview", "add", "bookmarks", diff --git a/utils/icon_themes.py b/utils/icon_themes.py index e5c2268f..cd133dbc 100644 --- a/utils/icon_themes.py +++ b/utils/icon_themes.py @@ -113,11 +113,17 @@ ICONS = [ "btn_no", "btn_open", "btn_close", + "btn_save", "btn_browse", "btn_list", "btn_new", "btn_create", "btn_reset", + "btn_insert", + "btn_apply", + "btn_build", + "btn_print", + "btn_preview", "add", "bookmarks", diff --git a/utils/icon_themes/font_awesome.json b/utils/icon_themes/font_awesome.json index 1769409e..82f295fc 100644 --- a/utils/icon_themes/font_awesome.json +++ b/utils/icon_themes/font_awesome.json @@ -66,11 +66,17 @@ "btn_no": "circle-xmark", "btn_open": "file-arrow-up", "btn_close": "circle-xmark", + "btn_save": "floppy-disk", "btn_browse": "folder-open", "btn_list": "list", "btn_new": "plus", "btn_create": "star", "btn_reset": "rotate-left", + "btn_insert": "i-cursor", + "btn_apply": "square-check", + "btn_build": "up-right-from-square", + "btn_print": "print", + "btn_preview": "eye", "add": "plus", "bookmarks": "bookmark", diff --git a/utils/icon_themes/material_symbols.json b/utils/icon_themes/material_symbols.json index 3d1adc47..0727a21c 100644 --- a/utils/icon_themes/material_symbols.json +++ b/utils/icon_themes/material_symbols.json @@ -65,12 +65,18 @@ "btn_yes": "check_circle", "btn_no": "do_not_disturb_on", "btn_open": "open_in_new", - "btn_close": "close", + "btn_close": "cancel", + "btn_save": "file_save", "btn_browse": "folder_open", "btn_list": "format_list_bulleted", "btn_new": "new_window", "btn_create": "star", "btn_reset": "undo", + "btn_insert": "insert_text", + "btn_apply": "check_box", + "btn_build": "export_notes", + "btn_print": "print", + "btn_preview": "preview", "add": "add", "bookmarks": "bookmarks", diff --git a/utils/icon_themes/remix.json b/utils/icon_themes/remix.json index da78a84f..9e4bd207 100644 --- a/utils/icon_themes/remix.json +++ b/utils/icon_themes/remix.json @@ -66,11 +66,17 @@ "btn_no": "close-circle", "btn_open": "file-upload", "btn_close": "close-circle", + "btn_save": "save-3", "btn_browse": "folder-2", "btn_list": "list-unordered", "btn_new": "add", "btn_create": "star-fill", "btn_reset": "reset-left", + "btn_insert": "add-box", + "btn_apply": "checkbox", + "btn_build": "stack", + "btn_print": "printer", + "btn_preview": "eye", "add": "add", "bookmarks": "bookmark", From c9c2b354554dd1265e30222a57ca4f757cc6d544 Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Sat, 25 Oct 2025 23:38:19 +0200 Subject: [PATCH 58/79] Update dialog buttons on all dialogs and tools --- novelwriter/dialogs/about.py | 10 +++++-- novelwriter/dialogs/docmerge.py | 23 ++++++++++------ novelwriter/dialogs/docsplit.py | 17 ++++++++---- novelwriter/dialogs/editlabel.py | 18 ++++++++---- novelwriter/dialogs/preferences.py | 17 ++++++++---- novelwriter/dialogs/projectsettings.py | 21 ++++++++------ novelwriter/dialogs/quotes.py | 21 ++++++++------ novelwriter/dialogs/wordlist.py | 16 +++++++---- novelwriter/gui/theme.py | 28 +++++++++++-------- novelwriter/tools/dictionaries.py | 12 +++++--- novelwriter/tools/lipsum.py | 23 ++++++++-------- novelwriter/tools/manusbuild.py | 38 +++++++++++--------------- novelwriter/tools/manuscript.py | 15 +++++----- novelwriter/tools/manussettings.py | 21 +++++++++----- novelwriter/tools/noveldetails.py | 12 +++++--- novelwriter/tools/writingstats.py | 31 ++++++++++----------- 16 files changed, 193 insertions(+), 130 deletions(-) diff --git a/novelwriter/dialogs/about.py b/novelwriter/dialogs/about.py index 59d4251f..429930cd 100644 --- a/novelwriter/dialogs/about.py +++ b/novelwriter/dialogs/about.py @@ -33,10 +33,11 @@ from PyQt6.QtWidgets import ( from novelwriter import CONFIG, SHARED from novelwriter.common import readTextFile +from novelwriter.enum import nwStandardButton from novelwriter.extensions.configlayout import NColorLabel from novelwriter.extensions.modified import NDialog from novelwriter.extensions.versioninfo import VersionInfoWidget -from novelwriter.types import QtAlignRightTop, QtDialogClose, QtHexArgb +from novelwriter.types import QtAlignRightTop, QtHexArgb if TYPE_CHECKING: from PyQt6.QtGui import QCloseEvent @@ -82,8 +83,11 @@ class GuiAbout(NDialog): self.txtCredits.setViewportMargins(0, 8, 8, 0) # Buttons - self.btnBox = QDialogButtonBox(QtDialogClose, self) - self.btnBox.rejected.connect(self.reject) + self.btnClose = SHARED.theme.getStandardButton(nwStandardButton.CLOSE, self) + self.btnClose.clicked.connect(self.reject) + + self.btnBox = QDialogButtonBox(self) + self.btnBox.addButton(self.btnClose, QDialogButtonBox.ButtonRole.RejectRole) # Assemble self.innerBox = QVBoxLayout() diff --git a/novelwriter/dialogs/docmerge.py b/novelwriter/dialogs/docmerge.py index 2c392374..0c8bdfa2 100644 --- a/novelwriter/dialogs/docmerge.py +++ b/novelwriter/dialogs/docmerge.py @@ -33,10 +33,11 @@ from PyQt6.QtWidgets import ( ) from novelwriter import SHARED +from novelwriter.enum import nwStandardButton from novelwriter.extensions.configlayout import NColorLabel from novelwriter.extensions.modified import NDialog from novelwriter.extensions.switch import NSwitch -from novelwriter.types import QtAccepted, QtDialogCancel, QtDialogOk, QtDialogReset, QtUserRole +from novelwriter.types import QtAccepted, QtUserRole logger = logging.getLogger(__name__) @@ -85,13 +86,19 @@ class GuiDocMerge(NDialog): self.optBox.setColumnStretch(2, 1) # Buttons - self.buttonBox = QDialogButtonBox(QtDialogOk | QtDialogCancel, self) - self.buttonBox.accepted.connect(self.accept) - self.buttonBox.rejected.connect(self.reject) + self.btnOk = SHARED.theme.getStandardButton(nwStandardButton.OK, self) + self.btnOk.clicked.connect(self.accept) - self.resetButton = self.buttonBox.addButton(QtDialogReset) - if self.resetButton: - self.resetButton.clicked.connect(self._resetList) + self.btnCancel = SHARED.theme.getStandardButton(nwStandardButton.CANCEL, self) + self.btnCancel.clicked.connect(self.reject) + + self.btnReset = SHARED.theme.getStandardButton(nwStandardButton.RESET, self) + self.btnReset.clicked.connect(self._resetList) + + self.btnBox = QDialogButtonBox(self) + self.btnBox.addButton(self.btnOk, QDialogButtonBox.ButtonRole.AcceptRole) + self.btnBox.addButton(self.btnCancel, QDialogButtonBox.ButtonRole.RejectRole) + self.btnBox.addButton(self.btnReset, QDialogButtonBox.ButtonRole.ResetRole) # Assemble self.outerBox = QVBoxLayout() @@ -103,7 +110,7 @@ class GuiDocMerge(NDialog): self.outerBox.addSpacing(8) self.outerBox.addLayout(self.optBox) self.outerBox.addSpacing(12) - self.outerBox.addWidget(self.buttonBox) + self.outerBox.addWidget(self.btnBox) self.setLayout(self.outerBox) # Load Content diff --git a/novelwriter/dialogs/docsplit.py b/novelwriter/dialogs/docsplit.py index 5850aff3..7355409e 100644 --- a/novelwriter/dialogs/docsplit.py +++ b/novelwriter/dialogs/docsplit.py @@ -33,10 +33,11 @@ from PyQt6.QtWidgets import ( ) from novelwriter import SHARED +from novelwriter.enum import nwStandardButton from novelwriter.extensions.configlayout import NColorLabel from novelwriter.extensions.modified import NComboBox, NDialog from novelwriter.extensions.switch import NSwitch -from novelwriter.types import QtAccepted, QtDialogCancel, QtDialogOk, QtUserRole +from novelwriter.types import QtAccepted, QtUserRole logger = logging.getLogger(__name__) @@ -117,9 +118,15 @@ class GuiDocSplit(NDialog): self.optBox.setColumnStretch(3, 1) # Buttons - self.buttonBox = QDialogButtonBox(QtDialogOk | QtDialogCancel, self) - self.buttonBox.accepted.connect(self.accept) - self.buttonBox.rejected.connect(self.reject) + self.btnOk = SHARED.theme.getStandardButton(nwStandardButton.OK, self) + self.btnOk.clicked.connect(self.accept) + + self.btnCancel = SHARED.theme.getStandardButton(nwStandardButton.CANCEL, self) + self.btnCancel.clicked.connect(self.reject) + + self.btnBox = QDialogButtonBox(self) + self.btnBox.addButton(self.btnOk, QDialogButtonBox.ButtonRole.AcceptRole) + self.btnBox.addButton(self.btnCancel, QDialogButtonBox.ButtonRole.RejectRole) # Assemble self.outerBox = QVBoxLayout() @@ -132,7 +139,7 @@ class GuiDocSplit(NDialog): self.outerBox.addSpacing(8) self.outerBox.addLayout(self.optBox) self.outerBox.addSpacing(12) - self.outerBox.addWidget(self.buttonBox) + self.outerBox.addWidget(self.btnBox) self.setLayout(self.outerBox) # Load Content diff --git a/novelwriter/dialogs/editlabel.py b/novelwriter/dialogs/editlabel.py index b8b4bae4..4d56e07e 100644 --- a/novelwriter/dialogs/editlabel.py +++ b/novelwriter/dialogs/editlabel.py @@ -27,8 +27,10 @@ import logging from PyQt6.QtWidgets import QDialogButtonBox, QHBoxLayout, QLabel, QLineEdit, QVBoxLayout, QWidget +from novelwriter import SHARED +from novelwriter.enum import nwStandardButton from novelwriter.extensions.modified import NDialog -from novelwriter.types import QtAccepted, QtDialogCancel, QtDialogOk +from novelwriter.types import QtAccepted logger = logging.getLogger(__name__) @@ -54,9 +56,15 @@ class GuiEditLabel(NDialog): self.lblValue.setBuddy(self.lblValue) # Buttons - self.buttonBox = QDialogButtonBox(QtDialogOk | QtDialogCancel, self) - self.buttonBox.accepted.connect(self.accept) - self.buttonBox.rejected.connect(self.reject) + self.btnOk = SHARED.theme.getStandardButton(nwStandardButton.OK, self) + self.btnOk.clicked.connect(self.accept) + + self.btnCancel = SHARED.theme.getStandardButton(nwStandardButton.CANCEL, self) + self.btnCancel.clicked.connect(self.reject) + + self.btnBox = QDialogButtonBox(self) + self.btnBox.addButton(self.btnOk, QDialogButtonBox.ButtonRole.AcceptRole) + self.btnBox.addButton(self.btnCancel, QDialogButtonBox.ButtonRole.RejectRole) # Assemble self.innerBox = QHBoxLayout() @@ -67,7 +75,7 @@ class GuiEditLabel(NDialog): self.outerBox = QVBoxLayout() self.outerBox.setSpacing(12) self.outerBox.addLayout(self.innerBox, 1) - self.outerBox.addWidget(self.buttonBox, 0) + self.outerBox.addWidget(self.btnBox, 0) self.setLayout(self.outerBox) diff --git a/novelwriter/dialogs/preferences.py b/novelwriter/dialogs/preferences.py index 758f7908..7062f4ad 100644 --- a/novelwriter/dialogs/preferences.py +++ b/novelwriter/dialogs/preferences.py @@ -38,13 +38,14 @@ from novelwriter.common import compact, describeFont, processDialogSymbols, uniq from novelwriter.config import DEF_GUI_DARK, DEF_GUI_LIGHT, DEF_ICONS, DEF_TREECOL from novelwriter.constants import nwLabels, nwQuotes, nwUnicode, trConst from novelwriter.dialogs.quotes import GuiQuoteSelect +from novelwriter.enum import nwStandardButton from novelwriter.extensions.configlayout import NColorLabel, NScrollableForm from novelwriter.extensions.modified import ( NComboBox, NDialog, NDoubleSpinBox, NIconToolButton, NSpinBox ) from novelwriter.extensions.pagedsidebar import NPagedSideBar from novelwriter.extensions.switch import NSwitch -from novelwriter.types import QtAlignCenter, QtDialogCancel, QtDialogSave +from novelwriter.types import QtAlignCenter logger = logging.getLogger(__name__) @@ -89,9 +90,15 @@ class GuiPreferences(NDialog): self.mainForm.setHelpTextStyle(SHARED.theme.helpText) # Buttons - self.buttonBox = QDialogButtonBox(QtDialogSave | QtDialogCancel, self) - self.buttonBox.accepted.connect(self._doSave) - self.buttonBox.rejected.connect(self.reject) + self.btnSave = SHARED.theme.getStandardButton(nwStandardButton.SAVE, self) + self.btnSave.clicked.connect(self._doSave) + + self.btnCancel = SHARED.theme.getStandardButton(nwStandardButton.CANCEL, self) + self.btnCancel.clicked.connect(self.reject) + + self.btnBox = QDialogButtonBox(self) + self.btnBox.addButton(self.btnSave, QDialogButtonBox.ButtonRole.AcceptRole) + self.btnBox.addButton(self.btnCancel, QDialogButtonBox.ButtonRole.RejectRole) # Assemble self.searchBox = QHBoxLayout() @@ -107,7 +114,7 @@ class GuiPreferences(NDialog): self.outerBox = QVBoxLayout() self.outerBox.addLayout(self.searchBox) self.outerBox.addLayout(self.mainBox) - self.outerBox.addWidget(self.buttonBox) + self.outerBox.addWidget(self.btnBox) self.outerBox.setSpacing(8) self.setLayout(self.outerBox) diff --git a/novelwriter/dialogs/projectsettings.py b/novelwriter/dialogs/projectsettings.py index 5e16d52e..a6f090cf 100644 --- a/novelwriter/dialogs/projectsettings.py +++ b/novelwriter/dialogs/projectsettings.py @@ -41,15 +41,12 @@ from novelwriter import CONFIG, SHARED from novelwriter.common import formatFileFilter, qtAddAction, qtLambda, simplified from novelwriter.constants import nwLabels, trConst from novelwriter.core.status import CUSTOM_COL, NWStatus, StatusEntry -from novelwriter.enum import nwStatusShape +from novelwriter.enum import nwStandardButton, nwStatusShape from novelwriter.extensions.configlayout import NColorLabel, NFixedPage, NScrollableForm from novelwriter.extensions.modified import NComboBox, NDialog, NIconToolButton from novelwriter.extensions.pagedsidebar import NPagedSideBar from novelwriter.extensions.switch import NSwitch -from novelwriter.types import ( - QtDialogCancel, QtDialogSave, QtSizeMinimum, QtSizeMinimumExpanding, - QtUserRole -) +from novelwriter.types import QtSizeMinimum, QtSizeMinimumExpanding, QtUserRole logger = logging.getLogger(__name__) @@ -95,9 +92,15 @@ class GuiProjectSettings(NDialog): self.sidebar.buttonClicked.connect(self._sidebarClicked) # Buttons - self.buttonBox = QDialogButtonBox(QtDialogSave | QtDialogCancel, self) - self.buttonBox.accepted.connect(self._doSave) - self.buttonBox.rejected.connect(self.reject) + self.btnSave = SHARED.theme.getStandardButton(nwStandardButton.SAVE, self) + self.btnSave.clicked.connect(self._doSave) + + self.btnCancel = SHARED.theme.getStandardButton(nwStandardButton.CANCEL, self) + self.btnCancel.clicked.connect(self.reject) + + self.btnBox = QDialogButtonBox(self) + self.btnBox.addButton(self.btnSave, QDialogButtonBox.ButtonRole.AcceptRole) + self.btnBox.addButton(self.btnCancel, QDialogButtonBox.ButtonRole.RejectRole) # Content SHARED.project.countStatus() @@ -126,7 +129,7 @@ class GuiProjectSettings(NDialog): self.outerBox = QVBoxLayout() self.outerBox.addLayout(self.topBox) self.outerBox.addLayout(self.mainBox) - self.outerBox.addWidget(self.buttonBox) + self.outerBox.addWidget(self.btnBox) self.outerBox.setSpacing(8) self.setLayout(self.outerBox) diff --git a/novelwriter/dialogs/quotes.py b/novelwriter/dialogs/quotes.py index 6030ecfe..2a813d39 100644 --- a/novelwriter/dialogs/quotes.py +++ b/novelwriter/dialogs/quotes.py @@ -32,12 +32,11 @@ from PyQt6.QtWidgets import ( QListWidgetItem, QVBoxLayout, QWidget ) +from novelwriter import SHARED from novelwriter.constants import nwQuotes, trConst +from novelwriter.enum import nwStandardButton from novelwriter.extensions.modified import NDialog -from novelwriter.types import ( - QtAccepted, QtAlignCenter, QtAlignTop, QtDialogCancel, QtDialogOk, - QtUserRole -) +from novelwriter.types import QtAccepted, QtAlignCenter, QtAlignTop, QtUserRole logger = logging.getLogger(__name__) @@ -91,9 +90,15 @@ class GuiQuoteSelect(NDialog): self.listBox.setMinimumHeight(150) # Buttons - self.buttonBox = QDialogButtonBox(QtDialogOk | QtDialogCancel, self) - self.buttonBox.accepted.connect(self.accept) - self.buttonBox.rejected.connect(self.reject) + self.btnOk = SHARED.theme.getStandardButton(nwStandardButton.OK, self) + self.btnOk.clicked.connect(self.accept) + + self.btnCancel = SHARED.theme.getStandardButton(nwStandardButton.CANCEL, self) + self.btnCancel.clicked.connect(self.reject) + + self.btnBox = QDialogButtonBox(self) + self.btnBox.addButton(self.btnOk, QDialogButtonBox.ButtonRole.AcceptRole) + self.btnBox.addButton(self.btnCancel, QDialogButtonBox.ButtonRole.RejectRole) # Assemble self.labelBox.addWidget(self.previewLabel, 0, QtAlignTop) @@ -103,7 +108,7 @@ class GuiQuoteSelect(NDialog): self.innerBox.addWidget(self.listBox) self.outerBox.addLayout(self.innerBox) - self.outerBox.addWidget(self.buttonBox) + self.outerBox.addWidget(self.btnBox) self.setLayout(self.outerBox) diff --git a/novelwriter/dialogs/wordlist.py b/novelwriter/dialogs/wordlist.py index 19e2da9d..c1db00ee 100644 --- a/novelwriter/dialogs/wordlist.py +++ b/novelwriter/dialogs/wordlist.py @@ -37,9 +37,9 @@ from PyQt6.QtWidgets import ( from novelwriter import CONFIG, SHARED from novelwriter.common import formatFileFilter from novelwriter.core.spellcheck import UserDictionary +from novelwriter.enum import nwStandardButton from novelwriter.extensions.configlayout import NColorLabel from novelwriter.extensions.modified import NDialog, NIconToolButton -from novelwriter.types import QtDialogClose, QtDialogSave if TYPE_CHECKING: from PyQt6.QtGui import QCloseEvent @@ -110,9 +110,15 @@ class GuiWordList(NDialog): self.editBox.addWidget(self.delButton, 0) # Buttons - self.buttonBox = QDialogButtonBox(QtDialogSave | QtDialogClose, self) - self.buttonBox.accepted.connect(self._doSave) - self.buttonBox.rejected.connect(self.reject) + self.btnSave = SHARED.theme.getStandardButton(nwStandardButton.SAVE, self) + self.btnSave.clicked.connect(self._doSave) + + self.btnClose = SHARED.theme.getStandardButton(nwStandardButton.CLOSE, self) + self.btnClose.clicked.connect(self.reject) + + self.btnBox = QDialogButtonBox(self) + self.btnBox.addButton(self.btnSave, QDialogButtonBox.ButtonRole.AcceptRole) + self.btnBox.addButton(self.btnClose, QDialogButtonBox.ButtonRole.RejectRole) # Assemble self.outerBox = QVBoxLayout() @@ -120,7 +126,7 @@ class GuiWordList(NDialog): self.outerBox.addWidget(self.listBox, 1) self.outerBox.addLayout(self.editBox, 0) self.outerBox.addSpacing(12) - self.outerBox.addWidget(self.buttonBox, 0) + self.outerBox.addWidget(self.btnBox, 0) self.outerBox.setSpacing(4) self.setLayout(self.outerBox) diff --git a/novelwriter/gui/theme.py b/novelwriter/gui/theme.py index 441c9cd1..6293a9f6 100644 --- a/novelwriter/gui/theme.py +++ b/novelwriter/gui/theme.py @@ -57,17 +57,23 @@ STYLES_MIN_TOOLBUTTON = "minimalToolButton" STYLES_BIG_TOOLBUTTON = "bigToolButton" STANDARD_BUTTONS = { - nwStandardButton.OK: (QT_TRANSLATE_NOOP("Button", "OK"), "btn_ok", "blue"), - nwStandardButton.CANCEL: (QT_TRANSLATE_NOOP("Button", "Cancel"), "btn_cancel", "red"), - nwStandardButton.YES: (QT_TRANSLATE_NOOP("Button", "Yes"), "btn_yes", "green"), - nwStandardButton.NO: (QT_TRANSLATE_NOOP("Button", "No"), "btn_no", "red"), - nwStandardButton.OPEN: (QT_TRANSLATE_NOOP("Button", "Open"), "btn_open", "blue"), - nwStandardButton.CLOSE: (QT_TRANSLATE_NOOP("Button", "Close"), "btn_close", "red"), - nwStandardButton.BROWSE: (QT_TRANSLATE_NOOP("Button", "Browse"), "btn_browse", "yellow"), - nwStandardButton.LIST: (QT_TRANSLATE_NOOP("Button", "List"), "btn_list", "blue"), - nwStandardButton.NEW: (QT_TRANSLATE_NOOP("Button", "New"), "btn_new", "green"), - nwStandardButton.CREATE: (QT_TRANSLATE_NOOP("Button", "Create"), "btn_create", "yellow"), - nwStandardButton.RESET: (QT_TRANSLATE_NOOP("Button", "Reset"), "btn_reset", "green"), + nwStandardButton.OK: (QT_TRANSLATE_NOOP("Button", "OK"), "btn_ok", "blue"), + nwStandardButton.CANCEL: (QT_TRANSLATE_NOOP("Button", "Cancel"), "btn_cancel", "red"), + nwStandardButton.YES: (QT_TRANSLATE_NOOP("Button", "&Yes"), "btn_yes", "green"), + nwStandardButton.NO: (QT_TRANSLATE_NOOP("Button", "&No"), "btn_no", "red"), + nwStandardButton.OPEN: (QT_TRANSLATE_NOOP("Button", "Open"), "btn_open", "blue"), + nwStandardButton.CLOSE: (QT_TRANSLATE_NOOP("Button", "Close"), "btn_close", "faded"), + nwStandardButton.SAVE: (QT_TRANSLATE_NOOP("Button", "Save"), "btn_save", "blue"), + nwStandardButton.BROWSE: (QT_TRANSLATE_NOOP("Button", "Browse"), "btn_browse", "yellow"), + nwStandardButton.LIST: (QT_TRANSLATE_NOOP("Button", "List"), "btn_list", "blue"), + nwStandardButton.NEW: (QT_TRANSLATE_NOOP("Button", "New"), "btn_new", "green"), + nwStandardButton.CREATE: (QT_TRANSLATE_NOOP("Button", "Create"), "btn_create", "yellow"), + nwStandardButton.RESET: (QT_TRANSLATE_NOOP("Button", "Reset"), "btn_reset", "green"), + nwStandardButton.INSERT: (QT_TRANSLATE_NOOP("Button", "Insert"), "btn_insert", "blue"), + nwStandardButton.APPLY: (QT_TRANSLATE_NOOP("Button", "Apply"), "btn_apply", "blue"), + nwStandardButton.BUILD: (QT_TRANSLATE_NOOP("Button", "Build"), "btn_build", "blue"), + nwStandardButton.PRINT: (QT_TRANSLATE_NOOP("Button", "Print"), "btn_print", "blue"), + nwStandardButton.PREVIEW: (QT_TRANSLATE_NOOP("Button", "Preview"), "btn_preview", "blue"), } diff --git a/novelwriter/tools/dictionaries.py b/novelwriter/tools/dictionaries.py index 5cc97021..06f26a95 100644 --- a/novelwriter/tools/dictionaries.py +++ b/novelwriter/tools/dictionaries.py @@ -37,9 +37,10 @@ from PyQt6.QtWidgets import ( from novelwriter import CONFIG, SHARED from novelwriter.common import formatFileFilter, formatInt, getFileSize, openExternalPath +from novelwriter.enum import nwStandardButton from novelwriter.error import formatException from novelwriter.extensions.modified import NIconToolButton, NNonBlockingDialog -from novelwriter.types import QtDialogClose, QtHexArgb +from novelwriter.types import QtHexArgb logger = logging.getLogger(__name__) @@ -110,8 +111,11 @@ class GuiDictionaries(NNonBlockingDialog): self.infoBox.setFrameStyle(QFrame.Shape.NoFrame) # Buttons - self.buttonBox = QDialogButtonBox(QtDialogClose, self) - self.buttonBox.rejected.connect(self.reject) + self.btnClose = SHARED.theme.getStandardButton(nwStandardButton.CLOSE, self) + self.btnClose.clicked.connect(self.reject) + + self.btnBox = QDialogButtonBox(self) + self.btnBox.addButton(self.btnClose, QDialogButtonBox.ButtonRole.AcceptRole) # Assemble self.outerBox = QVBoxLayout() @@ -123,7 +127,7 @@ class GuiDictionaries(NNonBlockingDialog): self.outerBox.addLayout(self.inBox, 0) self.outerBox.addWidget(self.infoBox, 1) self.outerBox.addSpacing(8) - self.outerBox.addWidget(self.buttonBox, 0) + self.outerBox.addWidget(self.btnBox, 0) self.setLayout(self.outerBox) diff --git a/novelwriter/tools/lipsum.py b/novelwriter/tools/lipsum.py index d28fb845..49b793a5 100644 --- a/novelwriter/tools/lipsum.py +++ b/novelwriter/tools/lipsum.py @@ -34,9 +34,10 @@ from PyQt6.QtWidgets import ( from novelwriter import CONFIG, SHARED from novelwriter.common import readTextFile +from novelwriter.enum import nwStandardButton from novelwriter.extensions.modified import NDialog from novelwriter.extensions.switch import NSwitch -from novelwriter.types import QtAlignLeft, QtAlignRight, QtDialogClose, QtRoleAction +from novelwriter.types import QtAlignLeft, QtAlignRight logger = logging.getLogger(__name__) @@ -91,22 +92,22 @@ class GuiLipsum(NDialog): self.innerBox.addLayout(self.formBox) # Buttons - self.buttonBox = QDialogButtonBox(self) - self.buttonBox.rejected.connect(self.reject) + self.btnInsert = SHARED.theme.getStandardButton(nwStandardButton.INSERT, self) + self.btnInsert.clicked.connect(self._doInsert) + self.btnInsert.setAutoDefault(False) - self.btnClose = self.buttonBox.addButton(QtDialogClose) - if self.btnClose: - self.btnClose.setAutoDefault(False) + self.btnClose = SHARED.theme.getStandardButton(nwStandardButton.CLOSE, self) + self.btnClose.clicked.connect(self.reject) + self.btnClose.setAutoDefault(False) - self.btnInsert = self.buttonBox.addButton(self.tr("Insert"), QtRoleAction) - if self.btnInsert: - self.btnInsert.clicked.connect(self._doInsert) - self.btnInsert.setAutoDefault(False) + self.btnBox = QDialogButtonBox(self) + self.btnBox.addButton(self.btnInsert, QDialogButtonBox.ButtonRole.ApplyRole) + self.btnBox.addButton(self.btnClose, QDialogButtonBox.ButtonRole.RejectRole) # Assemble self.outerBox = QVBoxLayout() self.outerBox.addLayout(self.innerBox) - self.outerBox.addWidget(self.buttonBox) + self.outerBox.addWidget(self.btnBox) self.outerBox.setSpacing(16) self.setLayout(self.outerBox) diff --git a/novelwriter/tools/manusbuild.py b/novelwriter/tools/manusbuild.py index 19070b14..c77898d7 100644 --- a/novelwriter/tools/manusbuild.py +++ b/novelwriter/tools/manusbuild.py @@ -32,7 +32,7 @@ from PyQt6.QtCore import QTimer, pyqtSlot from PyQt6.QtWidgets import ( QAbstractButton, QAbstractItemView, QDialogButtonBox, QFileDialog, QGridLayout, QHBoxLayout, QLabel, QLineEdit, QListWidget, QListWidgetItem, - QPushButton, QSplitter, QVBoxLayout, QWidget + QSplitter, QVBoxLayout, QWidget ) from novelwriter import SHARED @@ -40,10 +40,10 @@ from novelwriter.common import makeFileNameSafe, openExternalPath from novelwriter.constants import nwLabels from novelwriter.core.docbuild import NWBuildDocument from novelwriter.core.item import NWItem -from novelwriter.enum import nwBuildFmt -from novelwriter.extensions.modified import NDialog, NIconToolButton +from novelwriter.enum import nwBuildFmt, nwStandardButton +from novelwriter.extensions.modified import NDialog, NIconToolButton, NPushButton from novelwriter.extensions.progressbars import NProgressSimple -from novelwriter.types import QtAlignCenter, QtDialogClose, QtRoleAction, QtRoleReject, QtUserRole +from novelwriter.types import QtAlignCenter, QtRoleAction, QtRoleReject, QtUserRole if TYPE_CHECKING: from PyQt6.QtGui import QCloseEvent @@ -178,25 +178,19 @@ class GuiManuscriptBuild(NDialog): self.buildBox.setVerticalSpacing(4) # Dialog Buttons - self.buttonBox = QDialogButtonBox(self) - - self.btnOpen = QPushButton( - SHARED.theme.getIcon("browse", "yellow"), self.tr("Open Folder"), self - ) - self.btnOpen.setIconSize(bSz) + self.btnOpen = NPushButton(self, self.tr("Open Folder"), bSz, "browse", "yellow") self.btnOpen.setAutoDefault(False) - self.buttonBox.addButton(self.btnOpen, QtRoleAction) - self.btnBuild = QPushButton( - SHARED.theme.getIcon("sb_build", "blue"), self.tr("&Build"), self - ) - self.btnBuild.setIconSize(bSz) + self.btnBuild = SHARED.theme.getStandardButton(nwStandardButton.BUILD, self) self.btnBuild.setAutoDefault(True) - self.buttonBox.addButton(self.btnBuild, QtRoleAction) - self.btnClose = self.buttonBox.addButton(QtDialogClose) - if self.btnClose: - self.btnClose.setAutoDefault(False) + self.btnClose = SHARED.theme.getStandardButton(nwStandardButton.CLOSE, self) + self.btnClose.setAutoDefault(False) + + self.btnBox = QDialogButtonBox(self) + self.btnBox.addButton(self.btnOpen, QtRoleAction) + self.btnBox.addButton(self.btnBuild, QtRoleAction) + self.btnBox.addButton(self.btnClose, QtRoleReject) # Assemble GUI # ============ @@ -223,7 +217,7 @@ class GuiManuscriptBuild(NDialog): self.outerBox.addSpacing(4) self.outerBox.addLayout(self.buildBox, 0) self.outerBox.addSpacing(16) - self.outerBox.addWidget(self.buttonBox, 0) + self.outerBox.addWidget(self.btnBox, 0) self.outerBox.setSpacing(0) self.setLayout(self.outerBox) @@ -239,7 +233,7 @@ class GuiManuscriptBuild(NDialog): # Signals self.btnReset.clicked.connect(self._doResetBuildName) self.btnBrowse.clicked.connect(self._doSelectPath) - self.buttonBox.clicked.connect(self._dialogButtonClicked) + self.btnBox.clicked.connect(self._dialogButtonClicked) self.listFormats.itemSelectionChanged.connect(self._resetProgress) logger.debug("Ready: GuiManuscriptBuild") @@ -266,7 +260,7 @@ class GuiManuscriptBuild(NDialog): @pyqtSlot("QAbstractButton*") def _dialogButtonClicked(self, button: QAbstractButton) -> None: """Handle button clicks from the dialog button box.""" - role = self.buttonBox.buttonRole(button) + role = self.btnBox.buttonRole(button) if role == QtRoleAction: if button == self.btnBuild: self._runBuild() diff --git a/novelwriter/tools/manuscript.py b/novelwriter/tools/manuscript.py index 8c458db3..79a93787 100644 --- a/novelwriter/tools/manuscript.py +++ b/novelwriter/tools/manuscript.py @@ -36,9 +36,9 @@ from PyQt6.QtGui import ( from PyQt6.QtPrintSupport import QPrinter, QPrintPreviewDialog from PyQt6.QtWidgets import ( QAbstractItemView, QApplication, QFormLayout, QGridLayout, QHBoxLayout, - QLabel, QListWidget, QListWidgetItem, QPushButton, QSplitter, - QStackedWidget, QTabWidget, QTextBrowser, QTreeWidget, QTreeWidgetItem, - QVBoxLayout, QWidget + QLabel, QListWidget, QListWidgetItem, QSplitter, QStackedWidget, + QTabWidget, QTextBrowser, QTreeWidget, QTreeWidgetItem, QVBoxLayout, + QWidget ) from novelwriter import CONFIG, SHARED @@ -46,6 +46,7 @@ from novelwriter.common import fuzzyTime, qtLambda from novelwriter.constants import nwHeadFmt, nwLabels, nwStats, nwUnicode, trStats from novelwriter.core.buildsettings import BuildCollection, BuildSettings from novelwriter.core.docbuild import NWBuildDocument +from novelwriter.enum import nwStandardButton from novelwriter.extensions.modified import NIconToggleButton, NIconToolButton, NToolDialog from novelwriter.extensions.progressbars import NProgressCircle from novelwriter.extensions.switch import NSwitch @@ -171,16 +172,16 @@ class GuiManuscript(NToolDialog): # Process Controls # ================ - self.btnPreview = QPushButton(self.tr("Preview"), self) + self.btnPreview = SHARED.theme.getStandardButton(nwStandardButton.PREVIEW, self) self.btnPreview.clicked.connect(self._generatePreview) - self.btnPrint = QPushButton(self.tr("Print"), self) + self.btnPrint = SHARED.theme.getStandardButton(nwStandardButton.PRINT, self) self.btnPrint.clicked.connect(self._printDocument) - self.btnBuild = QPushButton(self.tr("Build"), self) + self.btnBuild = SHARED.theme.getStandardButton(nwStandardButton.BUILD, self) self.btnBuild.clicked.connect(self._buildManuscript) - self.btnClose = QPushButton(self.tr("Close"), self) + self.btnClose = SHARED.theme.getStandardButton(nwStandardButton.CLOSE, self) self.btnClose.clicked.connect(qtLambda(self.close)) self.processBox = QGridLayout() diff --git a/novelwriter/tools/manussettings.py b/novelwriter/tools/manussettings.py index e8bf94b6..795c7eed 100644 --- a/novelwriter/tools/manussettings.py +++ b/novelwriter/tools/manussettings.py @@ -40,6 +40,7 @@ from novelwriter import CONFIG, SHARED from novelwriter.common import describeFont, fontMatcher, qtAddAction, qtLambda from novelwriter.constants import nwHeadFmt, nwKeyWords, nwLabels, nwUnicode, trConst from novelwriter.core.buildsettings import BuildSettings, FilterMode +from novelwriter.enum import nwStandardButton from novelwriter.extensions.configlayout import ( NColorLabel, NFixedPage, NScrollableForm, NScrollablePage ) @@ -50,9 +51,8 @@ from novelwriter.extensions.pagedsidebar import NPagedSideBar from novelwriter.extensions.switch import NSwitch from novelwriter.extensions.switchbox import NSwitchBox from novelwriter.types import ( - QtAlignCenter, QtAlignLeft, QtDialogApply, QtDialogClose, QtDialogSave, - QtHeaderFixed, QtHeaderStretch, QtRoleAccept, QtRoleApply, QtRoleReject, - QtUserRole + QtAlignCenter, QtAlignLeft, QtHeaderFixed, QtHeaderStretch, QtRoleAccept, + QtRoleApply, QtRoleReject, QtUserRole ) if TYPE_CHECKING: @@ -125,8 +125,15 @@ class GuiBuildSettings(NToolDialog): self.toolStack.addWidget(self.optTabFormatting) # Buttons - self.buttonBox = QDialogButtonBox(QtDialogApply | QtDialogSave | QtDialogClose, self) - self.buttonBox.clicked.connect(self._dialogButtonClicked) + self.btnApply = SHARED.theme.getStandardButton(nwStandardButton.APPLY, self) + self.btnSave = SHARED.theme.getStandardButton(nwStandardButton.SAVE, self) + self.btnCancel = SHARED.theme.getStandardButton(nwStandardButton.CANCEL, self) + + self.btnBox = QDialogButtonBox(self) + self.btnBox.addButton(self.btnApply, QDialogButtonBox.ButtonRole.ApplyRole) + self.btnBox.addButton(self.btnSave, QDialogButtonBox.ButtonRole.AcceptRole) + self.btnBox.addButton(self.btnCancel, QDialogButtonBox.ButtonRole.RejectRole) + self.btnBox.clicked.connect(self._dialogButtonClicked) # Assemble self.topBox = QHBoxLayout() @@ -143,7 +150,7 @@ class GuiBuildSettings(NToolDialog): self.outerBox = QVBoxLayout() self.outerBox.addLayout(self.topBox) self.outerBox.addLayout(self.mainBox) - self.outerBox.addWidget(self.buttonBox) + self.outerBox.addWidget(self.btnBox) self.outerBox.setSpacing(12) self.setLayout(self.outerBox) @@ -205,7 +212,7 @@ class GuiBuildSettings(NToolDialog): @pyqtSlot("QAbstractButton*") def _dialogButtonClicked(self, button: QAbstractButton) -> None: """Handle button clicks from the dialog button box.""" - role = self.buttonBox.buttonRole(button) + role = self.btnBox.buttonRole(button) if role == QtRoleApply: self._applyChanges() self._emitBuildData() diff --git a/novelwriter/tools/noveldetails.py b/novelwriter/tools/noveldetails.py index 7447b553..bfec8938 100644 --- a/novelwriter/tools/noveldetails.py +++ b/novelwriter/tools/noveldetails.py @@ -38,12 +38,13 @@ from PyQt6.QtWidgets import ( from novelwriter import SHARED from novelwriter.common import formatTime, numberToRoman from novelwriter.constants import nwUnicode +from novelwriter.enum import nwStandardButton from novelwriter.extensions.configlayout import NColorLabel, NFixedPage, NScrollablePage from novelwriter.extensions.modified import NNonBlockingDialog from novelwriter.extensions.novelselector import NovelSelector from novelwriter.extensions.pagedsidebar import NPagedSideBar from novelwriter.extensions.switch import NSwitch -from novelwriter.types import QtAlignRight, QtDecoration, QtDialogClose +from novelwriter.types import QtAlignRight, QtDecoration if TYPE_CHECKING: from PyQt6.QtGui import QCloseEvent @@ -102,8 +103,11 @@ class GuiNovelDetails(NNonBlockingDialog): self.mainStack.addWidget(self.contentsPage) # Buttons - self.buttonBox = QDialogButtonBox(QtDialogClose, self) - self.buttonBox.rejected.connect(self.reject) + self.btnClose = SHARED.theme.getStandardButton(nwStandardButton.CLOSE, self) + self.btnClose.clicked.connect(self.reject) + + self.btnBox = QDialogButtonBox(self) + self.btnBox.addButton(self.btnClose, QDialogButtonBox.ButtonRole.RejectRole) # Assemble self.topBox = QHBoxLayout() @@ -119,7 +123,7 @@ class GuiNovelDetails(NNonBlockingDialog): self.outerBox = QVBoxLayout() self.outerBox.addLayout(self.topBox) self.outerBox.addLayout(self.mainBox) - self.outerBox.addWidget(self.buttonBox) + self.outerBox.addWidget(self.btnBox) self.outerBox.setSpacing(8) self.setLayout(self.outerBox) diff --git a/novelwriter/tools/writingstats.py b/novelwriter/tools/writingstats.py index 4494da8f..b6440e5c 100644 --- a/novelwriter/tools/writingstats.py +++ b/novelwriter/tools/writingstats.py @@ -39,13 +39,11 @@ from PyQt6.QtWidgets import ( from novelwriter import CONFIG, SHARED from novelwriter.common import checkInt, checkIntTuple, formatTime, minmax, qtLambda from novelwriter.constants import nwConst +from novelwriter.enum import nwStandardButton from novelwriter.error import formatException -from novelwriter.extensions.modified import NToolDialog +from novelwriter.extensions.modified import NPushButton, NToolDialog from novelwriter.extensions.switch import NSwitch -from novelwriter.types import ( - QtAlignLeftMiddle, QtAlignRight, QtAlignRightMiddle, QtDecoration, - QtDialogClose, QtRoleAction -) +from novelwriter.types import QtAlignLeftMiddle, QtAlignRight, QtAlignRightMiddle, QtDecoration if TYPE_CHECKING: from novelwriter.guimain import GuiMain @@ -182,6 +180,7 @@ class GuiWritingStats(NToolDialog): # Filter Options iPx = SHARED.theme.baseIconHeight + bSz = SHARED.theme.buttonIconSize self.filterForm = QGridLayout(self) self.filterForm.setRowStretch(6, 1) @@ -276,6 +275,10 @@ class GuiWritingStats(NToolDialog): self.optsBox.addWidget(self.histMax, 0) # Buttons + self.btnClose = SHARED.theme.getStandardButton(nwStandardButton.CLOSE, self) + self.btnClose.clicked.connect(self._doClose) + self.btnClose.setAutoDefault(False) + self.saveJSON = QAction(self.tr("JSON Data File (.json)"), self) self.saveJSON.triggered.connect(qtLambda(self._saveData, self.FMT_JSON)) @@ -286,17 +289,13 @@ class GuiWritingStats(NToolDialog): self.saveMenu.addAction(self.saveJSON) self.saveMenu.addAction(self.saveCSV) - self.buttonBox = QDialogButtonBox(self) - self.buttonBox.rejected.connect(self._doClose) + self.btnSave = NPushButton(self, self.tr("Save As"), bSz, "btn_save", "blue") + self.btnSave.setAutoDefault(False) + self.btnSave.setMenu(self.saveMenu) - self.btnClose = self.buttonBox.addButton(QtDialogClose) - if self.btnClose: - self.btnClose.setAutoDefault(False) - - self.btnSave = self.buttonBox.addButton(self.tr("Save As"), QtRoleAction) - if self.btnSave: - self.btnSave.setAutoDefault(False) - self.btnSave.setMenu(self.saveMenu) + self.btnBox = QDialogButtonBox(self) + self.btnBox.addButton(self.btnClose, QDialogButtonBox.ButtonRole.RejectRole) + self.btnBox.addButton(self.btnSave, QDialogButtonBox.ButtonRole.ActionRole) # Assemble self.outerBox = QGridLayout() @@ -304,7 +303,7 @@ class GuiWritingStats(NToolDialog): self.outerBox.addLayout(self.optsBox, 1, 0, 1, 2) self.outerBox.addWidget(self.infoBox, 2, 0) self.outerBox.addWidget(self.filterBox, 2, 1) - self.outerBox.addWidget(self.buttonBox, 3, 0, 1, 2) + self.outerBox.addWidget(self.btnBox, 3, 0, 1, 2) self.outerBox.setRowStretch(0, 1) self.setLayout(self.outerBox) From ce939a40534b72819628e2063d8bcf8c7810f4c6 Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Sat, 25 Oct 2025 23:52:56 +0200 Subject: [PATCH 59/79] Clean up button types --- novelwriter/dialogs/about.py | 4 ++-- novelwriter/dialogs/docmerge.py | 8 ++++---- novelwriter/dialogs/docsplit.py | 6 +++--- novelwriter/dialogs/editlabel.py | 6 +++--- novelwriter/dialogs/preferences.py | 6 +++--- novelwriter/dialogs/projectsettings.py | 9 ++++++--- novelwriter/dialogs/quotes.py | 9 ++++++--- novelwriter/dialogs/wordlist.py | 5 +++-- novelwriter/tools/dictionaries.py | 4 ++-- novelwriter/tools/lipsum.py | 6 +++--- novelwriter/tools/manussettings.py | 8 ++++---- novelwriter/tools/noveldetails.py | 4 ++-- novelwriter/tools/writingstats.py | 9 ++++++--- novelwriter/types.py | 8 +------- 14 files changed, 48 insertions(+), 44 deletions(-) diff --git a/novelwriter/dialogs/about.py b/novelwriter/dialogs/about.py index 429930cd..080b3b21 100644 --- a/novelwriter/dialogs/about.py +++ b/novelwriter/dialogs/about.py @@ -37,7 +37,7 @@ from novelwriter.enum import nwStandardButton from novelwriter.extensions.configlayout import NColorLabel from novelwriter.extensions.modified import NDialog from novelwriter.extensions.versioninfo import VersionInfoWidget -from novelwriter.types import QtAlignRightTop, QtHexArgb +from novelwriter.types import QtAlignRightTop, QtHexArgb, QtRoleReject if TYPE_CHECKING: from PyQt6.QtGui import QCloseEvent @@ -87,7 +87,7 @@ class GuiAbout(NDialog): self.btnClose.clicked.connect(self.reject) self.btnBox = QDialogButtonBox(self) - self.btnBox.addButton(self.btnClose, QDialogButtonBox.ButtonRole.RejectRole) + self.btnBox.addButton(self.btnClose, QtRoleReject) # Assemble self.innerBox = QVBoxLayout() diff --git a/novelwriter/dialogs/docmerge.py b/novelwriter/dialogs/docmerge.py index 0c8bdfa2..9f3d6184 100644 --- a/novelwriter/dialogs/docmerge.py +++ b/novelwriter/dialogs/docmerge.py @@ -37,7 +37,7 @@ from novelwriter.enum import nwStandardButton from novelwriter.extensions.configlayout import NColorLabel from novelwriter.extensions.modified import NDialog from novelwriter.extensions.switch import NSwitch -from novelwriter.types import QtAccepted, QtUserRole +from novelwriter.types import QtAccepted, QtRoleAccept, QtRoleReject, QtRoleReset, QtUserRole logger = logging.getLogger(__name__) @@ -96,9 +96,9 @@ class GuiDocMerge(NDialog): self.btnReset.clicked.connect(self._resetList) self.btnBox = QDialogButtonBox(self) - self.btnBox.addButton(self.btnOk, QDialogButtonBox.ButtonRole.AcceptRole) - self.btnBox.addButton(self.btnCancel, QDialogButtonBox.ButtonRole.RejectRole) - self.btnBox.addButton(self.btnReset, QDialogButtonBox.ButtonRole.ResetRole) + self.btnBox.addButton(self.btnOk, QtRoleAccept) + self.btnBox.addButton(self.btnCancel, QtRoleReject) + self.btnBox.addButton(self.btnReset, QtRoleReset) # Assemble self.outerBox = QVBoxLayout() diff --git a/novelwriter/dialogs/docsplit.py b/novelwriter/dialogs/docsplit.py index 7355409e..059a1678 100644 --- a/novelwriter/dialogs/docsplit.py +++ b/novelwriter/dialogs/docsplit.py @@ -37,7 +37,7 @@ from novelwriter.enum import nwStandardButton from novelwriter.extensions.configlayout import NColorLabel from novelwriter.extensions.modified import NComboBox, NDialog from novelwriter.extensions.switch import NSwitch -from novelwriter.types import QtAccepted, QtUserRole +from novelwriter.types import QtAccepted, QtRoleAccept, QtRoleReject, QtUserRole logger = logging.getLogger(__name__) @@ -125,8 +125,8 @@ class GuiDocSplit(NDialog): self.btnCancel.clicked.connect(self.reject) self.btnBox = QDialogButtonBox(self) - self.btnBox.addButton(self.btnOk, QDialogButtonBox.ButtonRole.AcceptRole) - self.btnBox.addButton(self.btnCancel, QDialogButtonBox.ButtonRole.RejectRole) + self.btnBox.addButton(self.btnOk, QtRoleAccept) + self.btnBox.addButton(self.btnCancel, QtRoleReject) # Assemble self.outerBox = QVBoxLayout() diff --git a/novelwriter/dialogs/editlabel.py b/novelwriter/dialogs/editlabel.py index 4d56e07e..4e998aa8 100644 --- a/novelwriter/dialogs/editlabel.py +++ b/novelwriter/dialogs/editlabel.py @@ -30,7 +30,7 @@ from PyQt6.QtWidgets import QDialogButtonBox, QHBoxLayout, QLabel, QLineEdit, QV from novelwriter import SHARED from novelwriter.enum import nwStandardButton from novelwriter.extensions.modified import NDialog -from novelwriter.types import QtAccepted +from novelwriter.types import QtAccepted, QtRoleAccept, QtRoleReject logger = logging.getLogger(__name__) @@ -63,8 +63,8 @@ class GuiEditLabel(NDialog): self.btnCancel.clicked.connect(self.reject) self.btnBox = QDialogButtonBox(self) - self.btnBox.addButton(self.btnOk, QDialogButtonBox.ButtonRole.AcceptRole) - self.btnBox.addButton(self.btnCancel, QDialogButtonBox.ButtonRole.RejectRole) + self.btnBox.addButton(self.btnOk, QtRoleAccept) + self.btnBox.addButton(self.btnCancel, QtRoleReject) # Assemble self.innerBox = QHBoxLayout() diff --git a/novelwriter/dialogs/preferences.py b/novelwriter/dialogs/preferences.py index 7062f4ad..71cfca38 100644 --- a/novelwriter/dialogs/preferences.py +++ b/novelwriter/dialogs/preferences.py @@ -45,7 +45,7 @@ from novelwriter.extensions.modified import ( ) from novelwriter.extensions.pagedsidebar import NPagedSideBar from novelwriter.extensions.switch import NSwitch -from novelwriter.types import QtAlignCenter +from novelwriter.types import QtAlignCenter, QtRoleAccept, QtRoleReject logger = logging.getLogger(__name__) @@ -97,8 +97,8 @@ class GuiPreferences(NDialog): self.btnCancel.clicked.connect(self.reject) self.btnBox = QDialogButtonBox(self) - self.btnBox.addButton(self.btnSave, QDialogButtonBox.ButtonRole.AcceptRole) - self.btnBox.addButton(self.btnCancel, QDialogButtonBox.ButtonRole.RejectRole) + self.btnBox.addButton(self.btnSave, QtRoleAccept) + self.btnBox.addButton(self.btnCancel, QtRoleReject) # Assemble self.searchBox = QHBoxLayout() diff --git a/novelwriter/dialogs/projectsettings.py b/novelwriter/dialogs/projectsettings.py index a6f090cf..dcb7f8de 100644 --- a/novelwriter/dialogs/projectsettings.py +++ b/novelwriter/dialogs/projectsettings.py @@ -46,7 +46,10 @@ from novelwriter.extensions.configlayout import NColorLabel, NFixedPage, NScroll from novelwriter.extensions.modified import NComboBox, NDialog, NIconToolButton from novelwriter.extensions.pagedsidebar import NPagedSideBar from novelwriter.extensions.switch import NSwitch -from novelwriter.types import QtSizeMinimum, QtSizeMinimumExpanding, QtUserRole +from novelwriter.types import ( + QtRoleAccept, QtRoleReject, QtSizeMinimum, QtSizeMinimumExpanding, + QtUserRole +) logger = logging.getLogger(__name__) @@ -99,8 +102,8 @@ class GuiProjectSettings(NDialog): self.btnCancel.clicked.connect(self.reject) self.btnBox = QDialogButtonBox(self) - self.btnBox.addButton(self.btnSave, QDialogButtonBox.ButtonRole.AcceptRole) - self.btnBox.addButton(self.btnCancel, QDialogButtonBox.ButtonRole.RejectRole) + self.btnBox.addButton(self.btnSave, QtRoleAccept) + self.btnBox.addButton(self.btnCancel, QtRoleReject) # Content SHARED.project.countStatus() diff --git a/novelwriter/dialogs/quotes.py b/novelwriter/dialogs/quotes.py index 2a813d39..2e625ba0 100644 --- a/novelwriter/dialogs/quotes.py +++ b/novelwriter/dialogs/quotes.py @@ -36,7 +36,10 @@ from novelwriter import SHARED from novelwriter.constants import nwQuotes, trConst from novelwriter.enum import nwStandardButton from novelwriter.extensions.modified import NDialog -from novelwriter.types import QtAccepted, QtAlignCenter, QtAlignTop, QtUserRole +from novelwriter.types import ( + QtAccepted, QtAlignCenter, QtAlignTop, QtRoleAccept, QtRoleReject, + QtUserRole +) logger = logging.getLogger(__name__) @@ -97,8 +100,8 @@ class GuiQuoteSelect(NDialog): self.btnCancel.clicked.connect(self.reject) self.btnBox = QDialogButtonBox(self) - self.btnBox.addButton(self.btnOk, QDialogButtonBox.ButtonRole.AcceptRole) - self.btnBox.addButton(self.btnCancel, QDialogButtonBox.ButtonRole.RejectRole) + self.btnBox.addButton(self.btnOk, QtRoleAccept) + self.btnBox.addButton(self.btnCancel, QtRoleReject) # Assemble self.labelBox.addWidget(self.previewLabel, 0, QtAlignTop) diff --git a/novelwriter/dialogs/wordlist.py b/novelwriter/dialogs/wordlist.py index c1db00ee..1a05e187 100644 --- a/novelwriter/dialogs/wordlist.py +++ b/novelwriter/dialogs/wordlist.py @@ -40,6 +40,7 @@ from novelwriter.core.spellcheck import UserDictionary from novelwriter.enum import nwStandardButton from novelwriter.extensions.configlayout import NColorLabel from novelwriter.extensions.modified import NDialog, NIconToolButton +from novelwriter.types import QtRoleAccept, QtRoleReject if TYPE_CHECKING: from PyQt6.QtGui import QCloseEvent @@ -117,8 +118,8 @@ class GuiWordList(NDialog): self.btnClose.clicked.connect(self.reject) self.btnBox = QDialogButtonBox(self) - self.btnBox.addButton(self.btnSave, QDialogButtonBox.ButtonRole.AcceptRole) - self.btnBox.addButton(self.btnClose, QDialogButtonBox.ButtonRole.RejectRole) + self.btnBox.addButton(self.btnSave, QtRoleAccept) + self.btnBox.addButton(self.btnClose, QtRoleReject) # Assemble self.outerBox = QVBoxLayout() diff --git a/novelwriter/tools/dictionaries.py b/novelwriter/tools/dictionaries.py index 06f26a95..cf90d65a 100644 --- a/novelwriter/tools/dictionaries.py +++ b/novelwriter/tools/dictionaries.py @@ -40,7 +40,7 @@ from novelwriter.common import formatFileFilter, formatInt, getFileSize, openExt from novelwriter.enum import nwStandardButton from novelwriter.error import formatException from novelwriter.extensions.modified import NIconToolButton, NNonBlockingDialog -from novelwriter.types import QtHexArgb +from novelwriter.types import QtHexArgb, QtRoleReject logger = logging.getLogger(__name__) @@ -115,7 +115,7 @@ class GuiDictionaries(NNonBlockingDialog): self.btnClose.clicked.connect(self.reject) self.btnBox = QDialogButtonBox(self) - self.btnBox.addButton(self.btnClose, QDialogButtonBox.ButtonRole.AcceptRole) + self.btnBox.addButton(self.btnClose, QtRoleReject) # Assemble self.outerBox = QVBoxLayout() diff --git a/novelwriter/tools/lipsum.py b/novelwriter/tools/lipsum.py index 49b793a5..a970fe93 100644 --- a/novelwriter/tools/lipsum.py +++ b/novelwriter/tools/lipsum.py @@ -37,7 +37,7 @@ from novelwriter.common import readTextFile from novelwriter.enum import nwStandardButton from novelwriter.extensions.modified import NDialog from novelwriter.extensions.switch import NSwitch -from novelwriter.types import QtAlignLeft, QtAlignRight +from novelwriter.types import QtAlignLeft, QtAlignRight, QtRoleApply, QtRoleReject logger = logging.getLogger(__name__) @@ -101,8 +101,8 @@ class GuiLipsum(NDialog): self.btnClose.setAutoDefault(False) self.btnBox = QDialogButtonBox(self) - self.btnBox.addButton(self.btnInsert, QDialogButtonBox.ButtonRole.ApplyRole) - self.btnBox.addButton(self.btnClose, QDialogButtonBox.ButtonRole.RejectRole) + self.btnBox.addButton(self.btnInsert, QtRoleApply) + self.btnBox.addButton(self.btnClose, QtRoleReject) # Assemble self.outerBox = QVBoxLayout() diff --git a/novelwriter/tools/manussettings.py b/novelwriter/tools/manussettings.py index 795c7eed..407eff5e 100644 --- a/novelwriter/tools/manussettings.py +++ b/novelwriter/tools/manussettings.py @@ -127,12 +127,12 @@ class GuiBuildSettings(NToolDialog): # Buttons self.btnApply = SHARED.theme.getStandardButton(nwStandardButton.APPLY, self) self.btnSave = SHARED.theme.getStandardButton(nwStandardButton.SAVE, self) - self.btnCancel = SHARED.theme.getStandardButton(nwStandardButton.CANCEL, self) + self.btnClose = SHARED.theme.getStandardButton(nwStandardButton.CLOSE, self) self.btnBox = QDialogButtonBox(self) - self.btnBox.addButton(self.btnApply, QDialogButtonBox.ButtonRole.ApplyRole) - self.btnBox.addButton(self.btnSave, QDialogButtonBox.ButtonRole.AcceptRole) - self.btnBox.addButton(self.btnCancel, QDialogButtonBox.ButtonRole.RejectRole) + self.btnBox.addButton(self.btnApply, QtRoleApply) + self.btnBox.addButton(self.btnSave, QtRoleAccept) + self.btnBox.addButton(self.btnClose, QtRoleReject) self.btnBox.clicked.connect(self._dialogButtonClicked) # Assemble diff --git a/novelwriter/tools/noveldetails.py b/novelwriter/tools/noveldetails.py index bfec8938..c0314e9b 100644 --- a/novelwriter/tools/noveldetails.py +++ b/novelwriter/tools/noveldetails.py @@ -44,7 +44,7 @@ from novelwriter.extensions.modified import NNonBlockingDialog from novelwriter.extensions.novelselector import NovelSelector from novelwriter.extensions.pagedsidebar import NPagedSideBar from novelwriter.extensions.switch import NSwitch -from novelwriter.types import QtAlignRight, QtDecoration +from novelwriter.types import QtAlignRight, QtDecoration, QtRoleReject if TYPE_CHECKING: from PyQt6.QtGui import QCloseEvent @@ -107,7 +107,7 @@ class GuiNovelDetails(NNonBlockingDialog): self.btnClose.clicked.connect(self.reject) self.btnBox = QDialogButtonBox(self) - self.btnBox.addButton(self.btnClose, QDialogButtonBox.ButtonRole.RejectRole) + self.btnBox.addButton(self.btnClose, QtRoleReject) # Assemble self.topBox = QHBoxLayout() diff --git a/novelwriter/tools/writingstats.py b/novelwriter/tools/writingstats.py index b6440e5c..dcf3ce40 100644 --- a/novelwriter/tools/writingstats.py +++ b/novelwriter/tools/writingstats.py @@ -43,7 +43,10 @@ from novelwriter.enum import nwStandardButton from novelwriter.error import formatException from novelwriter.extensions.modified import NPushButton, NToolDialog from novelwriter.extensions.switch import NSwitch -from novelwriter.types import QtAlignLeftMiddle, QtAlignRight, QtAlignRightMiddle, QtDecoration +from novelwriter.types import ( + QtAlignLeftMiddle, QtAlignRight, QtAlignRightMiddle, QtDecoration, + QtRoleAction, QtRoleReject +) if TYPE_CHECKING: from novelwriter.guimain import GuiMain @@ -294,8 +297,8 @@ class GuiWritingStats(NToolDialog): self.btnSave.setMenu(self.saveMenu) self.btnBox = QDialogButtonBox(self) - self.btnBox.addButton(self.btnClose, QDialogButtonBox.ButtonRole.RejectRole) - self.btnBox.addButton(self.btnSave, QDialogButtonBox.ButtonRole.ActionRole) + self.btnBox.addButton(self.btnSave, QtRoleAction) + self.btnBox.addButton(self.btnClose, QtRoleReject) # Assemble self.outerBox = QGridLayout() diff --git a/novelwriter/types.py b/novelwriter/types.py index f972e04f..52a18351 100644 --- a/novelwriter/types.py +++ b/novelwriter/types.py @@ -93,17 +93,11 @@ QtMouseMiddle = Qt.MouseButton.MiddleButton QtAccepted = QDialog.DialogCode.Accepted QtRejected = QDialog.DialogCode.Rejected -QtDialogApply = QDialogButtonBox.StandardButton.Apply -QtDialogCancel = QDialogButtonBox.StandardButton.Cancel -QtDialogClose = QDialogButtonBox.StandardButton.Close -QtDialogOk = QDialogButtonBox.StandardButton.Ok -QtDialogReset = QDialogButtonBox.StandardButton.Reset -QtDialogSave = QDialogButtonBox.StandardButton.Save - QtRoleAccept = QDialogButtonBox.ButtonRole.AcceptRole QtRoleAction = QDialogButtonBox.ButtonRole.ActionRole QtRoleApply = QDialogButtonBox.ButtonRole.ApplyRole QtRoleReject = QDialogButtonBox.ButtonRole.RejectRole +QtRoleReset = QDialogButtonBox.ButtonRole.ResetRole # Cursor Types From 5f34e924d185b4f670fdb3963d9033d35a4c9cab Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Sat, 25 Oct 2025 23:53:18 +0200 Subject: [PATCH 60/79] Fix broken tests --- tests/test_dialogs/test_dlg_preferences.py | 14 ++----- tests/test_tools/test_tools_manusbuild.py | 9 +---- tests/test_tools/test_tools_manuscript.py | 9 +---- tests/test_tools/test_tools_manussettings.py | 41 +++++--------------- 4 files changed, 18 insertions(+), 55 deletions(-) diff --git a/tests/test_dialogs/test_dlg_preferences.py b/tests/test_dialogs/test_dlg_preferences.py index dd749aa5..f9509528 100644 --- a/tests/test_dialogs/test_dlg_preferences.py +++ b/tests/test_dialogs/test_dlg_preferences.py @@ -32,7 +32,7 @@ from novelwriter.constants import nwUnicode from novelwriter.dialogs.preferences import GuiPreferences from novelwriter.dialogs.quotes import GuiQuoteSelect from novelwriter.gui.theme import ThemeEntry -from novelwriter.types import QtDialogCancel, QtDialogSave, QtModNone +from novelwriter.types import QtModNone KEY_DELAY = 1 @@ -118,16 +118,12 @@ def testDlgPreferences_Actions(qtbot, monkeypatch, nwGUI): # Check Save Button prefs.show() with qtbot.waitSignal(prefs.newPreferencesReady) as signal: - button = prefs.buttonBox.button(QtDialogSave) - assert button is not None - button.click() + prefs.btnSave.click() assert len(signal.args) == 4 # Check Close Button prefs.show() - button = prefs.buttonBox.button(QtDialogCancel) - assert button is not None - button.click() + prefs.btnCancel.click() assert prefs.isHidden() is True # Close Using Escape Key @@ -342,9 +338,7 @@ def testDlgPreferences_Settings(qtbot, monkeypatch, nwGUI, fncPath, tstPaths): with monkeypatch.context() as mp: mp.setattr(QFontDatabase, "families", lambda *a: ["TestFont"]) with qtbot.waitSignal(prefs.newPreferencesReady) as signal: - button = prefs.buttonBox.button(QtDialogSave) - assert button is not None - button.click() + prefs.btnSave.click() assert signal.args == [True, True, True, True] # Check Settings diff --git a/tests/test_tools/test_tools_manusbuild.py b/tests/test_tools/test_tools_manusbuild.py index 35ddb7fa..39875483 100644 --- a/tests/test_tools/test_tools_manusbuild.py +++ b/tests/test_tools/test_tools_manusbuild.py @@ -35,7 +35,6 @@ from novelwriter.enum import nwBuildFmt from novelwriter.guimain import GuiMain from novelwriter.shared import _GuiAlert from novelwriter.tools.manusbuild import GuiManuscriptBuild -from novelwriter.types import QtDialogClose from tests.tools import buildTestProject @@ -95,9 +94,7 @@ def testToolManuscriptBuild_Main( assert (fncPath / "TestBuild").with_suffix(nwLabels.BUILD_EXT[fmt]).exists() lastFmt = fmt - button = manus.buttonBox.button(QtDialogClose) - assert button is not None - manus._dialogButtonClicked(button) + manus._dialogButtonClicked(manus.btnClose) manus.deleteLater() assert build.lastBuildName == "TestBuild" @@ -151,7 +148,5 @@ def testToolManuscriptBuild_Main( assert lastUrl.startswith("file://") # Finish - button = manus.buttonBox.button(QtDialogClose) - assert button is not None - manus._dialogButtonClicked(button) + manus._dialogButtonClicked(manus.btnClose) # qtbot.stop() diff --git a/tests/test_tools/test_tools_manuscript.py b/tests/test_tools/test_tools_manuscript.py index 0e498ea1..0d74dbff 100644 --- a/tests/test_tools/test_tools_manuscript.py +++ b/tests/test_tools/test_tools_manuscript.py @@ -37,7 +37,6 @@ from novelwriter.core.buildsettings import BuildSettings from novelwriter.tools.manusbuild import GuiManuscriptBuild from novelwriter.tools.manuscript import GuiManuscript from novelwriter.tools.manussettings import GuiBuildSettings -from novelwriter.types import QtDialogApply, QtDialogSave from tests.tools import C, buildTestProject @@ -115,9 +114,7 @@ def testToolManuscript_Builds(qtbot, nwGUI, projPath): with qtbot.waitSignal(bSettings.newSettingsReady, timeout=5000): bSettings.newSettingsReady.connect(_testNewSettingsReady) - button = bSettings.buttonBox.button(QtDialogSave) - assert button is not None - button.click() + bSettings.btnSave.click() assert isinstance(build, BuildSettings) assert build.name == "Test Build" @@ -136,9 +133,7 @@ def testToolManuscript_Builds(qtbot, nwGUI, projPath): with qtbot.waitSignal(bSettings.newSettingsReady, timeout=5000): bSettings.newSettingsReady.connect(_testNewSettingsReady) - button = bSettings.buttonBox.button(QtDialogApply) - assert button is not None - button.click() # Should leave the dialog open + bSettings.btnApply.click() # Should leave the dialog open assert isinstance(build, BuildSettings) assert build.name == "Test Build" diff --git a/tests/test_tools/test_tools_manussettings.py b/tests/test_tools/test_tools_manussettings.py index b5ce7880..853fe6cb 100644 --- a/tests/test_tools/test_tools_manussettings.py +++ b/tests/test_tools/test_tools_manussettings.py @@ -33,7 +33,6 @@ from novelwriter.core.buildsettings import BuildSettings, FilterMode from novelwriter.tools.manussettings import ( GuiBuildSettings, _FilterTab, _FormattingTab, _HeadingsTab ) -from novelwriter.types import QtDialogApply, QtDialogClose, QtDialogSave from tests.tools import C, buildTestProject @@ -78,9 +77,7 @@ def testToolBuildSettings_Init(qtbot, nwGUI, projPath, mockRnd): # Capture Apply button with qtbot.waitSignal(bSettings.newSettingsReady, timeout=5000): bSettings.newSettingsReady.connect(_testNewSettingsReady) - button = bSettings.buttonBox.button(QtDialogApply) - assert button is not None - bSettings._dialogButtonClicked(button) + bSettings._dialogButtonClicked(bSettings.btnApply) assert triggered @@ -89,9 +86,7 @@ def testToolBuildSettings_Init(qtbot, nwGUI, projPath, mockRnd): with qtbot.waitSignal(bSettings.newSettingsReady, timeout=5000): bSettings.newSettingsReady.connect(_testNewSettingsReady) - button = bSettings.buttonBox.button(QtDialogSave) - assert button is not None - bSettings._dialogButtonClicked(button) + bSettings._dialogButtonClicked(bSettings.btnSave) assert triggered @@ -109,9 +104,7 @@ def testToolBuildSettings_Init(qtbot, nwGUI, projPath, mockRnd): assert triggered # Finish - button = bSettings.buttonBox.button(QtDialogClose) - assert button is not None - bSettings._dialogButtonClicked(button) + bSettings._dialogButtonClicked(bSettings.btnClose) # qtbot.stop() @@ -326,9 +319,7 @@ def testToolBuildSettings_Filter(qtbot, nwGUI, projPath, mockRnd): ] # Finish - button = bSettings.buttonBox.button(QtDialogClose) - assert button is not None - bSettings._dialogButtonClicked(button) + bSettings._dialogButtonClicked(bSettings.btnClose) # qtbot.stop() @@ -505,9 +496,7 @@ def testToolBuildSettings_Headings(qtbot, nwGUI): assert sBuild.getBool("headings.hideSection") is True # Finish - button = bSettings.buttonBox.button(QtDialogClose) - assert button is not None - bSettings._dialogButtonClicked(button) + bSettings._dialogButtonClicked(bSettings.btnClose) # qtbot.stop() @@ -579,9 +568,7 @@ def testToolBuildSettings_FormatTextContent(qtbot, nwGUI): assert sBuild.getBool("text.addNoteHeadings") is True # Finish - button = bSettings.buttonBox.button(QtDialogClose) - assert button is not None - bSettings._dialogButtonClicked(button) + bSettings._dialogButtonClicked(bSettings.btnClose) # qtbot.stop() @@ -657,9 +644,7 @@ def testToolBuildSettings_FormatTextFormat(monkeypatch, qtbot, nwGUI): assert fmtTab._textFont == font # Finish - button = bSettings.buttonBox.button(QtDialogClose) - assert button is not None - bSettings._dialogButtonClicked(button) + bSettings._dialogButtonClicked(bSettings.btnClose) # qtbot.stop() @@ -703,9 +688,7 @@ def testToolBuildSettings_FormatFirstLineIndent(monkeypatch, qtbot, nwGUI): assert sBuild.getBool("format.indentFirstPar") is True # Finish - button = bSettings.buttonBox.button(QtDialogClose) - assert button is not None - bSettings._dialogButtonClicked(button) + bSettings._dialogButtonClicked(bSettings.btnClose) # qtbot.stop() @@ -761,9 +744,7 @@ def testToolBuildSettings_FormatPageLayout(monkeypatch, qtbot, nwGUI): assert fmtTab.rightMargin.value() == 1.5 # Finish - button = bSettings.buttonBox.button(QtDialogClose) - assert button is not None - bSettings._dialogButtonClicked(button) + bSettings._dialogButtonClicked(bSettings.btnClose) # qtbot.stop() @@ -832,7 +813,5 @@ def testToolBuildSettings_FormatOutput(qtbot, nwGUI): assert fmtTab.odtPageHeader.text() == nwHeadFmt.DOC_AUTO # Finish - button = bSettings.buttonBox.button(QtDialogClose) - assert button is not None - bSettings._dialogButtonClicked(button) + bSettings._dialogButtonClicked(bSettings.btnClose) # qtbot.stop() From af36a3b1bf51a3bd809ae3c72dad433523397802 Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Sat, 25 Oct 2025 23:55:29 +0200 Subject: [PATCH 61/79] Remove extra qtbase translations for buttons --- i18n/README.md | 17 ----------------- i18n/qtbase.py | 49 ------------------------------------------------- utils/assets.py | 1 - 3 files changed, 67 deletions(-) delete mode 100644 i18n/qtbase.py diff --git a/i18n/README.md b/i18n/README.md index b2df46f0..6b8ce27f 100644 --- a/i18n/README.md +++ b/i18n/README.md @@ -114,23 +114,6 @@ You can now test the translation in novelWriter. The Preferences dialog should l language, so go ahead and select it. -### Missing QtBase Translations - -The default Qt dialogs also have translations, for instance for standard buttons like "Yes", "No", -"Ok", "Cancel", etc. Generally, these translation files are installed with the Qt libraries on your -system, and novelWriter will collect those translations from there. However, these translations are -missing for many languages. - -As a starting point, there is no need to translate any entries in the `.ts` files that are under -elements starting with the letter "Q", like "QPlatformTheme", "QWizard", etc. If these turn up in -English in novelWriter after activating a translation, it means they are probably missing in the Qt -library, and you may also need to translate these. - -These additional translation entries are generated from a file named `i18n/qtbase.py`, which is not -a file that novelWriter uses. It is there only to generate these additional entries for the `.ts` -files. - - ## Project Localisation Projects can have a different language setting than the GUI itself. The files with format diff --git a/i18n/qtbase.py b/i18n/qtbase.py deleted file mode 100644 index e40279cd..00000000 --- a/i18n/qtbase.py +++ /dev/null @@ -1,49 +0,0 @@ -""" -Qt Base Translation File -======================== - -This file causes Qt Linguist to generate translation entries for the Qt -elements that need translation in novelWriter for those languages who do -not yet have a qtbase_xx.qm file shipped with Qt. - -If a qtbase_xx.qm file already exists, do not add a translation for the -entries generated from this file. -""" # noqa - -from PyQt6.QtCore import QT_TRANSLATE_NOOP - -# QDialogButtonBox -# ================ - -QT_TRANSLATE_NOOP("QDialogButtonBox", "OK") - -# QGnomeTheme -# =========== - -QT_TRANSLATE_NOOP("QGnomeTheme", "&OK") -QT_TRANSLATE_NOOP("QGnomeTheme", "&Save") -QT_TRANSLATE_NOOP("QGnomeTheme", "&Cancel") -QT_TRANSLATE_NOOP("QGnomeTheme", "&Close") -QT_TRANSLATE_NOOP("QGnomeTheme", "Close without Saving") - -# QPlatformTheme -# ============== - -QT_TRANSLATE_NOOP("QPlatformTheme", "OK") -QT_TRANSLATE_NOOP("QPlatformTheme", "Save") -QT_TRANSLATE_NOOP("QPlatformTheme", "Save All") -QT_TRANSLATE_NOOP("QPlatformTheme", "Open") -QT_TRANSLATE_NOOP("QPlatformTheme", "&Yes") -QT_TRANSLATE_NOOP("QPlatformTheme", "Yes to &All") -QT_TRANSLATE_NOOP("QPlatformTheme", "&No") -QT_TRANSLATE_NOOP("QPlatformTheme", "N&o to All") -QT_TRANSLATE_NOOP("QPlatformTheme", "Abort") -QT_TRANSLATE_NOOP("QPlatformTheme", "Retry") -QT_TRANSLATE_NOOP("QPlatformTheme", "Ignore") -QT_TRANSLATE_NOOP("QPlatformTheme", "Close") -QT_TRANSLATE_NOOP("QPlatformTheme", "Cancel") -QT_TRANSLATE_NOOP("QPlatformTheme", "Discard") -QT_TRANSLATE_NOOP("QPlatformTheme", "Help") -QT_TRANSLATE_NOOP("QPlatformTheme", "Apply") -QT_TRANSLATE_NOOP("QPlatformTheme", "Reset") -QT_TRANSLATE_NOOP("QPlatformTheme", "Restore Defaults") diff --git a/utils/assets.py b/utils/assets.py index 7f0222cd..5822cc96 100644 --- a/utils/assets.py +++ b/utils/assets.py @@ -111,7 +111,6 @@ def updateTranslationSources(args: argparse.Namespace) -> None: print("") sources = list((ROOT_DIR / "novelwriter").glob("**/*.py")) - sources.insert(0, ROOT_DIR / "i18n" / "qtbase.py") for source in sources: print(source.relative_to(ROOT_DIR)) From a02254f5d5cdd8e67bae1167df13282074652198 Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Sun, 26 Oct 2025 00:12:37 +0200 Subject: [PATCH 62/79] Make minor improvements to the code and fix test on MacOS --- novelwriter/extensions/modified.py | 7 +++---- tests/test_base/test_base_shared.py | 14 ++++++++++---- 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/novelwriter/extensions/modified.py b/novelwriter/extensions/modified.py index b802a4d9..ee1be5e7 100644 --- a/novelwriter/extensions/modified.py +++ b/novelwriter/extensions/modified.py @@ -210,12 +210,11 @@ class NPushButton(QPushButton): icon: str | None = None, color: str | None = None ) -> None: super().__init__(parent=parent) - self.setText(text) - self.setIconSize(iconSize) self._icon = icon self._color = color - if icon: - self.refreshIcon() + self.setText(text) + self.setIconSize(iconSize) + self.refreshIcon() def refreshIcon(self) -> None: """Reload the theme icon.""" diff --git a/tests/test_base/test_base_shared.py b/tests/test_base/test_base_shared.py index a020a694..0a37b624 100644 --- a/tests/test_base/test_base_shared.py +++ b/tests/test_base/test_base_shared.py @@ -20,6 +20,8 @@ along with this program. If not, see . """ # noqa from __future__ import annotations +import sys + from unittest.mock import MagicMock import pytest @@ -218,7 +220,8 @@ def testBaseSharedData_GuiAlert(): # Alert: Info alert.setAlertType(_GuiAlert.INFO, False) assert hasattr(alert, "_btnOk") - assert alert.windowTitle() == "Information" + if sys.platform != "darwin": # Not set on MacOS + assert alert.windowTitle() == "Information" alert._btnOk.click() assert alert.finalState is True alert._state = False @@ -226,7 +229,8 @@ def testBaseSharedData_GuiAlert(): # Alert: Warning alert.setAlertType(_GuiAlert.WARN, False) assert hasattr(alert, "_btnOk") - assert alert.windowTitle() == "Warning" + if sys.platform != "darwin": # Not set on MacOS + assert alert.windowTitle() == "Warning" alert._btnOk.click() assert alert.finalState is True alert._state = False @@ -234,7 +238,8 @@ def testBaseSharedData_GuiAlert(): # Alert: Error alert.setAlertType(_GuiAlert.ERROR, False) assert hasattr(alert, "_btnOk") - assert alert.windowTitle() == "Error" + if sys.platform != "darwin": # Not set on MacOS + assert alert.windowTitle() == "Error" alert._btnOk.click() assert alert.finalState is True alert._state = False @@ -243,7 +248,8 @@ def testBaseSharedData_GuiAlert(): alert.setAlertType(_GuiAlert.ASK, True) assert hasattr(alert, "_btnYes") assert hasattr(alert, "_btnNo") - assert alert.windowTitle() == "Question" + if sys.platform != "darwin": # Not set on MacOS + assert alert.windowTitle() == "Question" alert._btnYes.click() assert alert.finalState is True alert._btnNo.click() From e492c6575d22f6547ef2e78b16ad2d51aa56aa78 Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Sun, 26 Oct 2025 02:00:04 +0100 Subject: [PATCH 63/79] Add theme update handling to manuscript build tool --- novelwriter/extensions/modified.py | 10 ++-- novelwriter/extensions/switch.py | 12 +++-- novelwriter/gui/doceditor.py | 3 +- novelwriter/guimain.py | 3 ++ novelwriter/tools/manuscript.py | 82 +++++++++++++++++++++--------- 5 files changed, 76 insertions(+), 34 deletions(-) diff --git a/novelwriter/extensions/modified.py b/novelwriter/extensions/modified.py index ee1be5e7..abc12da1 100644 --- a/novelwriter/extensions/modified.py +++ b/novelwriter/extensions/modified.py @@ -214,10 +214,10 @@ class NPushButton(QPushButton): self._color = color self.setText(text) self.setIconSize(iconSize) - self.refreshIcon() + self.updateIcon() - def refreshIcon(self) -> None: - """Reload the theme icon.""" + def updateIcon(self) -> None: + """Update the theme icon.""" if self._icon: self.setIcon(SHARED.theme.getIcon(self._icon, self._color)) @@ -262,8 +262,8 @@ class NIconToggleButton(QToolButton): def setThemeIcon(self, iconKey: str) -> None: """Set an icon from the current theme.""" - iconSize = self.iconSize() - self.setIcon(SHARED.theme.getToggleIcon(iconKey, (iconSize.width(), iconSize.height()))) + size = self.iconSize() + self.setIcon(SHARED.theme.getToggleIcon(iconKey, (size.width(), size.height()))) class NClickableLabel(QLabel): diff --git a/novelwriter/extensions/switch.py b/novelwriter/extensions/switch.py index cc0d43a9..983d6294 100644 --- a/novelwriter/extensions/switch.py +++ b/novelwriter/extensions/switch.py @@ -34,7 +34,7 @@ from novelwriter.types import QtNoPen, QtPaintAntiAlias, QtSizeFixed class NSwitch(QAbstractButton): """Custom: Toggle Switch.""" - __slots__ = ("_cOff", "_cOn", "_offset", "_rH", "_rR", "_xH", "_xR", "_xW") + __slots__ = ("_offset", "_rH", "_rR", "_xH", "_xR", "_xW") def __init__(self, parent: QWidget, height: int = 0) -> None: super().__init__(parent=parent) @@ -45,13 +45,11 @@ class NSwitch(QAbstractButton): self._rH = self._xH - 4 self._rR = self._xR - 2 - self._cOn = SHARED.theme.accentCol - self._cOff = self.palette().alternateBase() - self.setCheckable(True) self.setSizePolicy(QtSizeFixed, QtSizeFixed) self.setFixedWidth(self._xW) self.setFixedHeight(self._xH) + self.setUpdatesEnabled(True) self._offset = self._xR self.clicked.connect(self._onClick) @@ -96,7 +94,7 @@ class NSwitch(QAbstractButton): painter.setOpacity(1.0 if self.isEnabled() else 0.5) painter.setPen(palette.highlight().color() if self.hasFocus() else palette.mid().color()) - painter.setBrush(self._cOn if self.isChecked() else self._cOff) + painter.setBrush(SHARED.theme.accentCol if self.isChecked() else palette.alternateBase()) painter.drawRoundedRect(0, 0, self._xW, self._xH, self._xR, self._xR) painter.setPen(QtNoPen) @@ -110,6 +108,10 @@ class NSwitch(QAbstractButton): self.setCursor(Qt.CursorShape.PointingHandCursor) super().enterEvent(event) + ## + # Internal Functions + ## + @pyqtSlot(bool) def _onClick(self, checked: bool) -> None: """Animate the toggle action.""" diff --git a/novelwriter/gui/doceditor.py b/novelwriter/gui/doceditor.py index 1496743f..72e7577f 100644 --- a/novelwriter/gui/doceditor.py +++ b/novelwriter/gui/doceditor.py @@ -2590,7 +2590,7 @@ class GuiDocEditSearch(QFrame): # Buttons # ======= - self.showReplace = NIconToggleButton(self, iSz, "unfold") + self.showReplace = NIconToggleButton(self, iSz) self.showReplace.toggled.connect(self._doToggleReplace) self.searchButton = NIconToolButton(self, iSz) @@ -2732,6 +2732,7 @@ class GuiDocEditSearch(QFrame): self.cancelSearch.setIcon(SHARED.theme.getIcon("search_cancel")) self.searchButton.setThemeIcon("search", "green") self.replaceButton.setThemeIcon("search_replace", "green") + self.showReplace.setThemeIcon("unfold") # Set stylesheets self.searchOpt.setStyleSheet("QToolBar {padding: 0;}") diff --git a/novelwriter/guimain.py b/novelwriter/guimain.py index acf6114b..20e86ad8 100644 --- a/novelwriter/guimain.py +++ b/novelwriter/guimain.py @@ -910,6 +910,9 @@ class GuiMain(QMainWindow): self.mainStatus.updateTheme() SHARED.project.tree.refreshAllItems() + if dialog := SHARED.findTopLevelWidget(GuiManuscript): + dialog.updateTheme() + if syntax: self.docEditor.updateSyntaxColors() diff --git a/novelwriter/tools/manuscript.py b/novelwriter/tools/manuscript.py index 79a93787..9ad1557e 100644 --- a/novelwriter/tools/manuscript.py +++ b/novelwriter/tools/manuscript.py @@ -101,30 +101,20 @@ class GuiManuscript(NToolDialog): # Build Controls # ============== - qPalette = self.palette() - qPalette.setBrush(QPalette.ColorRole.Window, qPalette.base()) - self.setPalette(qPalette) - - buttonStyle = SHARED.theme.getStyleSheet(STYLES_MIN_TOOLBUTTON) - - self.tbAdd = NIconToolButton(self, iSz, "add", "green") + self.tbAdd = NIconToolButton(self, iSz) self.tbAdd.setToolTip(self.tr("Add New Build")) - self.tbAdd.setStyleSheet(buttonStyle) self.tbAdd.clicked.connect(self._createNewBuild) - self.tbDel = NIconToolButton(self, iSz, "remove", "red") + self.tbDel = NIconToolButton(self, iSz) self.tbDel.setToolTip(self.tr("Delete Selected Build")) - self.tbDel.setStyleSheet(buttonStyle) self.tbDel.clicked.connect(self._deleteSelectedBuild) - self.tbCopy = NIconToolButton(self, iSz, "copy", "blue") + self.tbCopy = NIconToolButton(self, iSz) self.tbCopy.setToolTip(self.tr("Duplicate Selected Build")) - self.tbCopy.setStyleSheet(buttonStyle) self.tbCopy.clicked.connect(self._copySelectedBuild) - self.tbEdit = NIconToolButton(self, iSz, "edit", "green") + self.tbEdit = NIconToolButton(self, iSz) self.tbEdit.setToolTip(self.tr("Edit Selected Build")) - self.tbEdit.setStyleSheet(buttonStyle) self.tbEdit.clicked.connect(self._editSelectedBuild) self.lblBuilds = QLabel("{0}".format(self.tr("Builds")), self) @@ -159,7 +149,6 @@ class GuiManuscript(NToolDialog): self.detailsTabs = QTabWidget(self) self.detailsTabs.addTab(self.buildDetails, self.tr("Details")) self.detailsTabs.addTab(self.buildOutline, self.tr("Outline")) - self.detailsTabs.setStyleSheet(SHARED.theme.getStyleSheet(STYLES_FLAT_TABS)) self.buildSplit = QSplitter(Qt.Orientation.Vertical, self) self.buildSplit.addWidget(self.buildList) @@ -247,6 +236,8 @@ class GuiManuscript(NToolDialog): self.setLayout(self.outerBox) self.setSizeGripEnabled(True) + self.updateTheme(init=True) + # Signals self.buildOutline.outlineEntryClicked.connect(self.docPreview.navigateTo) @@ -270,6 +261,31 @@ class GuiManuscript(NToolDialog): self.buildList.setCurrentItem(self._buildMap[selected]) QTimer.singleShot(200, self._generatePreview) + def updateTheme(self, *, init: bool = False) -> None: + """Update theme elements.""" + self.tbAdd.setThemeIcon("add", "green") + self.tbDel.setThemeIcon("remove", "red") + self.tbCopy.setThemeIcon("copy", "blue") + self.tbEdit.setThemeIcon("edit", "green") + + if not init: + self.btnPreview.updateIcon() + self.btnPrint.updateIcon() + self.btnBuild.updateIcon() + self.btnClose.updateIcon() + + buttonStyle = SHARED.theme.getStyleSheet(STYLES_MIN_TOOLBUTTON) + self.tbAdd.setStyleSheet(buttonStyle) + self.tbDel.setStyleSheet(buttonStyle) + self.tbCopy.setStyleSheet(buttonStyle) + self.tbEdit.setStyleSheet(buttonStyle) + + self.detailsTabs.setStyleSheet(SHARED.theme.getStyleSheet(STYLES_FLAT_TABS)) + + self.buildDetails.updateTheme() + self.buildOutline.updateTheme() + self.docPreview.updateTheme() + ## # Events ## @@ -490,6 +506,7 @@ class _DetailsWidget(QWidget): super().__init__(parent=parent) self._initExpanded = True + self._build = None # Tree Widget self.listView = QTreeWidget(self) @@ -612,9 +629,16 @@ class _DetailsWidget(QWidget): sub.setIcon(1, on if build.getBool(key) else off) item.addChild(sub) + self._build = build + # Restore expanded state self.setExpandedState(expanded) + def updateTheme(self) -> None: + """Update theme elements.""" + if self._build: + self.updateInfo(self._build) + class _OutlineWidget(QWidget): @@ -639,9 +663,9 @@ class _OutlineWidget(QWidget): self.outerBox.setContentsMargins(0, 0, 0, 0) self.setLayout(self.outerBox) - def updateOutline(self, data: dict[str, str]) -> None: + def updateOutline(self, data: dict[str, str], *, force: bool = False) -> None: """Update the outline.""" - if isinstance(data, dict) and data != self._outline: + if isinstance(data, dict) and (data != self._outline or force): self.listView.clear() tFont = self.font() @@ -679,6 +703,10 @@ class _OutlineWidget(QWidget): self.listView.setIndentation(SHARED.theme.baseIconHeight if indent else 4) self._outline = data + def updateTheme(self) -> None: + """Update theme elements.""" + self.updateOutline(self._outline, force=True) + ## # Private Slots ## @@ -721,17 +749,12 @@ class _PreviewWidget(QTextBrowser): self.anchorClicked.connect(self._linkClicked) # Document Age - aPalette = self.palette() - aPalette.setColor(QPalette.ColorRole.Window, aPalette.toolTipBase().color()) - aPalette.setColor(QPalette.ColorRole.WindowText, aPalette.toolTipText().color()) - aFont = self.font() aFont.setPointSizeF(0.9*SHARED.theme.fontPointSize) self.ageLabel = QLabel("", self) self.ageLabel.setIndent(0) self.ageLabel.setFont(aFont) - self.ageLabel.setPalette(aPalette) self.ageLabel.setAutoFillBackground(True) self.ageLabel.setAlignment(QtAlignCenter) self.ageLabel.setFixedHeight(int(2.1*SHARED.theme.fontPixelSize)) @@ -750,6 +773,7 @@ class _PreviewWidget(QTextBrowser): self._updateDocMargins() self._updateBuildAge() + self.updateTheme() self.setTextFont(CONFIG.textFont) # Age Timer @@ -815,6 +839,13 @@ class _PreviewWidget(QTextBrowser): QApplication.processEvents() QTimer.singleShot(300, self._postUpdate) + def updateTheme(self) -> None: + """Update theme elements.""" + palette = QApplication.palette() + palette.setColor(QPalette.ColorRole.Window, palette.toolTipBase().color()) + palette.setColor(QPalette.ColorRole.WindowText, palette.toolTipText().color()) + self.ageLabel.setPalette(palette) + ## # Events ## @@ -907,7 +938,7 @@ class _StatsWidget(QWidget): self.minWidget = QWidget(self) self.maxWidget = QWidget(self) - self.toggleButton = NIconToggleButton(self, SHARED.theme.baseIconSize, "unfold") + self.toggleButton = NIconToggleButton(self, SHARED.theme.baseIconSize) self.toggleButton.toggled.connect(self._toggleView) self._buildBottomPanel() @@ -922,6 +953,7 @@ class _StatsWidget(QWidget): self.outerBox.setContentsMargins(0, 0, 0, 0) self.setLayout(self.outerBox) + self.updateTheme() self._toggleView(False) @@ -946,6 +978,10 @@ class _StatsWidget(QWidget): self.maxHeadWordChars.setText(f"{data.get(nwStats.WCHARS_TITLE, 0):n}") self.maxTextWordChars.setText(f"{data.get(nwStats.WCHARS_TEXT, 0):n}") + def updateTheme(self) -> None: + """Update theme elements.""" + self.toggleButton.setThemeIcon("unfold") + ## # Private Slots ## From 8b02c95f51fedc8f3b5313dd876441e9f833e179 Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Sun, 26 Oct 2025 02:04:03 +0100 Subject: [PATCH 64/79] Add theme update handling to manuscript build settings --- novelwriter/dialogs/preferences.py | 10 +-- novelwriter/dialogs/projectsettings.py | 4 +- novelwriter/extensions/modified.py | 12 +-- novelwriter/extensions/pagedsidebar.py | 7 ++ novelwriter/tools/manuscript.py | 4 + novelwriter/tools/manussettings.py | 119 ++++++++++++++++++------- 6 files changed, 114 insertions(+), 42 deletions(-) diff --git a/novelwriter/dialogs/preferences.py b/novelwriter/dialogs/preferences.py index 71cfca38..1c96b7e1 100644 --- a/novelwriter/dialogs/preferences.py +++ b/novelwriter/dialogs/preferences.py @@ -158,7 +158,7 @@ class GuiPreferences(NDialog): self.mainForm.addGroupLabel(title, section) # Display Language - self.guiLocale = NComboBox(self) + self.guiLocale = NComboBox(self, scrollable=False) self.guiLocale.setMinimumWidth(200) for lang, name in CONFIG.listLanguages(CONFIG.LANG_NW): self.guiLocale.addItem(name, lang) @@ -170,9 +170,9 @@ class GuiPreferences(NDialog): ) # Colour Theme - self.lightTheme = NComboBox(self) + self.lightTheme = NComboBox(self, scrollable=False) self.lightTheme.setMinimumWidth(200) - self.darkTheme = NComboBox(self) + self.darkTheme = NComboBox(self, scrollable=False) self.darkTheme.setMinimumWidth(200) for key, theme in SHARED.theme.colourThemes.items(): if theme.dark: @@ -193,7 +193,7 @@ class GuiPreferences(NDialog): ) # Icon Theme - self.iconTheme = NComboBox(self) + self.iconTheme = NComboBox(self, scrollable=False) self.iconTheme.setMinimumWidth(200) for key, theme in SHARED.theme.iconCache.iconThemes.items(): self.iconTheme.addItem(theme.name, key) @@ -511,7 +511,7 @@ class GuiPreferences(NDialog): self.mainForm.addGroupLabel(title, section) # Spell Checking - self.spellLanguage = NComboBox(self) + self.spellLanguage = NComboBox(self, scrollable=False) self.spellLanguage.setMinimumWidth(200) if CONFIG.hasEnchant: diff --git a/novelwriter/dialogs/projectsettings.py b/novelwriter/dialogs/projectsettings.py index dcb7f8de..37704f70 100644 --- a/novelwriter/dialogs/projectsettings.py +++ b/novelwriter/dialogs/projectsettings.py @@ -257,7 +257,7 @@ class _SettingsPage(NScrollableForm): # Project Language projLang = data.language or CONFIG.guiLocale - self.projLang = NComboBox(self) + self.projLang = NComboBox(self, scrollable=False) self.projLang.setMinimumWidth(200) for tag, language in CONFIG.listLanguages(CONFIG.LANG_PROJ): self.projLang.addItem(language, tag) @@ -269,7 +269,7 @@ class _SettingsPage(NScrollableForm): ) # Spell Check Language - self.spellLang = NComboBox(self) + self.spellLang = NComboBox(self, scrollable=False) self.spellLang.setMinimumWidth(200) self.spellLang.addItem(self.tr("Default"), "None") if CONFIG.hasEnchant: diff --git a/novelwriter/extensions/modified.py b/novelwriter/extensions/modified.py index abc12da1..90d0ffbb 100644 --- a/novelwriter/extensions/modified.py +++ b/novelwriter/extensions/modified.py @@ -125,14 +125,16 @@ class NComboBox(QComboBox): window of many widgets. """ - def __init__(self, parent: QWidget | None = None, maxItems: int = 15) -> None: + def __init__( + self, parent: QWidget | None = None, maxItems: int = 15, scrollable: bool = True + ) -> None: super().__init__(parent=parent) self.setFocusPolicy(Qt.FocusPolicy.StrongFocus) self.setMaxVisibleItems(maxItems) - - # The style sheet disables Fusion style pop-up mode on some platforms - # and allows for scrolling of long lists of items - self.setStyleSheet("QComboBox {combobox-popup: 0;}") + if not scrollable: + # The style sheet disables Fusion style pop-up mode on some + # platforms and allows for scrolling of long lists of items + self.setStyleSheet("QComboBox {combobox-popup: 0;}") def wheelEvent(self, event: QWheelEvent) -> None: """Only capture the mouse wheel if the widget has focus.""" diff --git a/novelwriter/extensions/pagedsidebar.py b/novelwriter/extensions/pagedsidebar.py index fd332495..04c284ed 100644 --- a/novelwriter/extensions/pagedsidebar.py +++ b/novelwriter/extensions/pagedsidebar.py @@ -72,6 +72,9 @@ class NPagedSideBar(QToolBar): def setLabelColor(self, color: QColor) -> None: """Set the text color for the labels.""" self._labelCol = color + for widget in self.children(): + if isinstance(widget, _NPagedToolLabel): + widget.setTextColor(color) def addLabel(self, text: str) -> None: """Add a new label to the toolbar.""" @@ -188,6 +191,10 @@ class _NPagedToolLabel(QLabel): self._textCol = textColor or self.palette().text().color() + def setTextColor(self, textColor: QColor | None = None) -> None: + """Set a new text colour.""" + self._textCol = textColor or self.palette().text().color() + def paintEvent(self, event: QPaintEvent) -> None: """Overload the paint event to draw a simple, left aligned text label that matches the button style. diff --git a/novelwriter/tools/manuscript.py b/novelwriter/tools/manuscript.py index 9ad1557e..9646140e 100644 --- a/novelwriter/tools/manuscript.py +++ b/novelwriter/tools/manuscript.py @@ -286,6 +286,10 @@ class GuiManuscript(NToolDialog): self.buildOutline.updateTheme() self.docPreview.updateTheme() + for obj in SHARED.mainGui.children(): + if isinstance(obj, GuiBuildSettings): + obj.updateTheme() + ## # Events ## diff --git a/novelwriter/tools/manussettings.py b/novelwriter/tools/manussettings.py index 407eff5e..318c21f1 100644 --- a/novelwriter/tools/manussettings.py +++ b/novelwriter/tools/manussettings.py @@ -154,6 +154,7 @@ class GuiBuildSettings(NToolDialog): self.outerBox.setSpacing(12) self.setLayout(self.outerBox) + self.updateTheme(init=True) # Set Default Tab self.sidebar.setSelected(self.OPT_FILTERS) @@ -170,6 +171,20 @@ class GuiBuildSettings(NToolDialog): self.optTabHeadings.loadContent() self.optTabFormatting.loadContent() + def updateTheme(self, *, init: bool = False) -> None: + """Update theme elements.""" + if not init: + self.btnApply.updateIcon() + self.btnSave.updateIcon() + self.btnClose.updateIcon() + + self.optTabSelect.updateTheme() + self.optTabHeadings.updateTheme() + self.optTabFormatting.updateTheme() + + self.titleLabel.setTextColors(color=SHARED.theme.helpText) + self.sidebar.setLabelColor(SHARED.theme.helpText) + ## # Properties ## @@ -326,15 +341,13 @@ class _FilterTab(NFixedPage): self.includedButton = NIconToolButton(self, iSz) self.includedButton.setToolTip(self.tr("Always included")) - self.includedButton.setIcon(self._statusFlags[self.F_INCLUDED]) self.includedButton.clicked.connect(qtLambda(self._setSelectedMode, self.F_INCLUDED)) self.excludedButton = NIconToolButton(self, iSz) self.excludedButton.setToolTip(self.tr("Always excluded")) - self.excludedButton.setIcon(self._statusFlags[self.F_EXCLUDED]) self.excludedButton.clicked.connect(qtLambda(self._setSelectedMode, self.F_EXCLUDED)) - self.resetButton = NIconToolButton(self, iSz, "revert", "green") + self.resetButton = NIconToolButton(self, iSz) self.resetButton.setToolTip(self.tr("Reset to default")) self.resetButton.clicked.connect(qtLambda(self._setSelectedMode, self.F_FILTERED)) @@ -376,6 +389,7 @@ class _FilterTab(NFixedPage): pOptions.getInt("GuiBuildSettings", "filterWidth", 300), ]) + self.updateTheme(init=True) self.setCentralWidget(self.mainSplit) def loadContent(self) -> None: @@ -389,6 +403,18 @@ class _FilterTab(NFixedPage): m, n = (sizes[0], sizes[1]) if len(sizes) >= 2 else (0, 0) return m, n + def updateTheme(self, *, init: bool = False) -> None: + """Update theme elements.""" + if not init: + self._statusFlags[self.F_FILTERED] = SHARED.theme.getIcon("filter", "orange") + self._statusFlags[self.F_INCLUDED] = SHARED.theme.getIcon("pin", "blue") + self._statusFlags[self.F_EXCLUDED] = SHARED.theme.getIcon("exclude", "red") + self.loadContent() + + self.includedButton.setIcon(self._statusFlags[self.F_INCLUDED]) + self.excludedButton.setIcon(self._statusFlags[self.F_EXCLUDED]) + self.resetButton.setThemeIcon("revert", "green") + ## # Slots ## @@ -555,7 +581,7 @@ class _HeadingsTab(NScrollablePage): self.lblPart = QLabel(self._build.getLabel("headings.fmtPart"), self) self.fmtPart = QLineEdit("", self) self.fmtPart.setReadOnly(True) - self.btnPart = NIconToolButton(self, iSz, "edit", "green") + self.btnPart = NIconToolButton(self, iSz) self.btnPart.clicked.connect(qtLambda(self._editHeading, self.EDIT_TITLE)) self.swtPart = NSwitch(self, height=iPx) self.hdePart = QLabel(trHide, self) @@ -572,7 +598,7 @@ class _HeadingsTab(NScrollablePage): self.lblChapter = QLabel(self._build.getLabel("headings.fmtChapter"), self) self.fmtChapter = QLineEdit("", self) self.fmtChapter.setReadOnly(True) - self.btnChapter = NIconToolButton(self, iSz, "edit", "green") + self.btnChapter = NIconToolButton(self, iSz) self.btnChapter.clicked.connect(qtLambda(self._editHeading, self.EDIT_CHAPTER)) self.swtChapter = NSwitch(self, height=iPx) self.hdeChapter = QLabel(trHide, self) @@ -589,7 +615,7 @@ class _HeadingsTab(NScrollablePage): self.lblUnnumbered = QLabel(self._build.getLabel("headings.fmtUnnumbered"), self) self.fmtUnnumbered = QLineEdit("", self) self.fmtUnnumbered.setReadOnly(True) - self.btnUnnumbered = NIconToolButton(self, iSz, "edit", "green") + self.btnUnnumbered = NIconToolButton(self, iSz) self.btnUnnumbered.clicked.connect(qtLambda(self._editHeading, self.EDIT_UNNUM)) self.swtUnnumbered = NSwitch(self, height=iPx) self.hdeUnnumbered = QLabel(trHide, self) @@ -606,7 +632,7 @@ class _HeadingsTab(NScrollablePage): self.lblScene = QLabel(self._build.getLabel("headings.fmtScene"), self) self.fmtScene = QLineEdit("", self) self.fmtScene.setReadOnly(True) - self.btnScene = NIconToolButton(self, iSz, "edit", "green") + self.btnScene = NIconToolButton(self, iSz) self.btnScene.clicked.connect(qtLambda(self._editHeading, self.EDIT_SCENE)) self.swtScene = NSwitch(self, height=iPx) self.hdeScene = QLabel(trHide, self) @@ -623,7 +649,7 @@ class _HeadingsTab(NScrollablePage): self.lblAScene = QLabel(self._build.getLabel("headings.fmtAltScene"), self) self.fmtAScene = QLineEdit("", self) self.fmtAScene.setReadOnly(True) - self.btnAScene = NIconToolButton(self, iSz, "edit", "green") + self.btnAScene = NIconToolButton(self, iSz) self.btnAScene.clicked.connect(qtLambda(self._editHeading, self.EDIT_HSCENE)) self.swtAScene = NSwitch(self, height=iPx) self.hdeAScene = QLabel(trHide, self) @@ -640,7 +666,7 @@ class _HeadingsTab(NScrollablePage): self.lblSection = QLabel(self._build.getLabel("headings.fmtSection"), self) self.fmtSection = QLineEdit("", self) self.fmtSection.setReadOnly(True) - self.btnSection = NIconToolButton(self, iSz, "edit", "green") + self.btnSection = NIconToolButton(self, iSz) self.btnSection.clicked.connect(qtLambda(self._editHeading, self.EDIT_SECTION)) self.swtSection = NSwitch(self, height=iPx) self.hdeSection = QLabel(trHide, self) @@ -774,8 +800,21 @@ class _HeadingsTab(NScrollablePage): self.outerBox.addLayout(self.layoutMatrix) self.outerBox.addStretch(1) + self.updateTheme() self.setCentralLayout(self.outerBox) + def updateTheme(self) -> None: + """Update theme elements.""" + self.btnPart.setThemeIcon("edit", "green") + self.btnChapter.setThemeIcon("edit", "green") + self.btnUnnumbered.setThemeIcon("edit", "green") + self.btnScene.setThemeIcon("edit", "green") + self.btnAScene.setThemeIcon("edit", "green") + self.btnSection.setThemeIcon("edit", "green") + + self.formSyntax.initHighlighter() + self.formSyntax.rehighlight() + def loadContent(self) -> None: """Populate the widgets.""" def fmtBreak(text: str) -> str: @@ -907,10 +946,14 @@ class _HeadingSyntaxHighlighter(QSyntaxHighlighter): def __init__(self, document: QTextDocument | None) -> None: super().__init__(document) - syntax = SHARED.theme.syntaxTheme self._fmtSymbol = QTextCharFormat() - self._fmtSymbol.setForeground(syntax.head) self._fmtFormat = QTextCharFormat() + self.initHighlighter() + + def initHighlighter(self) -> None: + """Update theme elements.""" + syntax = SHARED.theme.syntaxTheme + self._fmtSymbol.setForeground(syntax.head) self._fmtFormat.setForeground(syntax.emph) def highlightBlock(self, text: str) -> None: @@ -936,6 +979,7 @@ class _FormattingTab(NScrollableForm): self.setHelpTextStyle(SHARED.theme.helpText) self.buildForm() + self.updateTheme() def buildForm(self) -> None: """Build the formatting form.""" @@ -979,7 +1023,7 @@ class _FormattingTab(NScrollableForm): lambda keyword=keyword: self._updateIgnoredKeywords(keyword) ) - self.ignoredKeywordsButton = NIconToolButton(self, iSz, "add", "green") + self.ignoredKeywordsButton = NIconToolButton(self, iSz) self.ignoredKeywordsButton.setMenu(self.mnKeywords) self.addRow( self._build.getLabel("text.ignoredKeywords"), self.ignoredKeywords, @@ -1001,7 +1045,7 @@ class _FormattingTab(NScrollableForm): # Text Font self.textFont = QLineEdit(self) self.textFont.setReadOnly(True) - self.btnTextFont = NIconToolButton(self, iSz, "font") + self.btnTextFont = NIconToolButton(self, iSz) self.btnTextFont.clicked.connect(self._selectFont) self.addRow( self._build.getLabel("format.textFont"), self.textFont, @@ -1060,12 +1104,12 @@ class _FormattingTab(NScrollableForm): self._sidebar.addButton(title, section) self.addGroupLabel(title, section) - pixT = SHARED.theme.getPixmap("margin_top", (iPx, iPx)) - pixB = SHARED.theme.getPixmap("margin_bottom", (iPx, iPx)) - pixL = SHARED.theme.getPixmap("margin_left", (iPx, iPx)) - pixR = SHARED.theme.getPixmap("margin_right", (iPx, iPx)) - pixH = SHARED.theme.getPixmap("fit_height", (iPx, iPx)) - pixW = SHARED.theme.getPixmap("fit_width", (iPx, iPx)) + self.pixT = QLabel(self) + self.pixB = QLabel(self) + self.pixL = QLabel(self) + self.pixR = QLabel(self) + self.pixH = QLabel(self) + self.pixW = QLabel(self) # Title self.titleMarginT = NDoubleSpinBox(self) @@ -1076,7 +1120,7 @@ class _FormattingTab(NScrollableForm): self.addRow( self._build.getLabel("format.titleMargin"), - [pixT, self.titleMarginT, 6, pixB, self.titleMarginB], + [self.pixT, self.titleMarginT, 6, self.pixB, self.titleMarginB], unit="em", ) @@ -1089,7 +1133,7 @@ class _FormattingTab(NScrollableForm): self.addRow( self._build.getLabel("format.h1Margin"), - [pixT, self.h1MarginT, 6, pixB, self.h1MarginB], + [self.pixT, self.h1MarginT, 6, self.pixB, self.h1MarginB], unit="em", ) @@ -1102,7 +1146,7 @@ class _FormattingTab(NScrollableForm): self.addRow( self._build.getLabel("format.h2Margin"), - [pixT, self.h2MarginT, 6, pixB, self.h2MarginB], + [self.pixT, self.h2MarginT, 6, self.pixB, self.h2MarginB], unit="em", ) @@ -1115,7 +1159,7 @@ class _FormattingTab(NScrollableForm): self.addRow( self._build.getLabel("format.h3Margin"), - [pixT, self.h3MarginT, 6, pixB, self.h3MarginB], + [self.pixT, self.h3MarginT, 6, self.pixB, self.h3MarginB], unit="em", ) @@ -1128,7 +1172,7 @@ class _FormattingTab(NScrollableForm): self.addRow( self._build.getLabel("format.h4Margin"), - [pixT, self.h4MarginT, 6, pixB, self.h4MarginB], + [self.pixT, self.h4MarginT, 6, self.pixB, self.h4MarginB], unit="em", ) @@ -1141,7 +1185,7 @@ class _FormattingTab(NScrollableForm): self.addRow( self._build.getLabel("format.textMargin"), - [pixT, self.textMarginT, 6, pixB, self.textMarginB], + [self.pixT, self.textMarginT, 6, self.pixB, self.textMarginB], unit="em", ) @@ -1154,7 +1198,7 @@ class _FormattingTab(NScrollableForm): self.addRow( self._build.getLabel("format.sepMargin"), - [pixT, self.sepMarginT, 6, pixB, self.sepMarginB], + [self.pixT, self.sepMarginT, 6, self.pixB, self.sepMarginB], unit="em", ) @@ -1188,7 +1232,7 @@ class _FormattingTab(NScrollableForm): self.addRow( self._build.getLabel("format.pageSize"), - [self.pageSize, 6, pixW, self.pageWidth, 6, pixH, self.pageHeight], + [self.pageSize, 6, self.pixW, self.pageWidth, 6, self.pixH, self.pageHeight], ) # Page Margins @@ -1206,11 +1250,11 @@ class _FormattingTab(NScrollableForm): self.addRow( self._build.getLabel("format.pageMargins"), - [pixT, self.topMargin, 6, pixB, self.bottomMargin], + [self.pixT, self.topMargin, 6, self.pixB, self.bottomMargin], ) self.addRow( "", - [pixL, self.leftMargin, 6, pixR, self.rightMargin], + [self.pixL, self.leftMargin, 6, self.pixR, self.rightMargin], ) # Open Document @@ -1224,7 +1268,7 @@ class _FormattingTab(NScrollableForm): # Header self.odtPageHeader = QLineEdit(self) self.odtPageHeader.setMinimumWidth(200) - self.btnPageHeader = NIconToolButton(self, iSz, "revert", "green") + self.btnPageHeader = NIconToolButton(self, iSz) self.btnPageHeader.clicked.connect(self._resetPageHeader) self.addRow( self._build.getLabel("doc.pageHeader"), self.odtPageHeader, @@ -1264,6 +1308,21 @@ class _FormattingTab(NScrollableForm): # Finalise self.finalise() + def updateTheme(self) -> None: + """Update theme elements.""" + iPx = SHARED.theme.baseIconHeight + + self.ignoredKeywordsButton.setThemeIcon("add", "green") + self.btnTextFont.setThemeIcon("font") + self.btnPageHeader.setThemeIcon("revert", "green") + + self.pixT.setPixmap(SHARED.theme.getPixmap("margin_top", (iPx, iPx))) + self.pixB.setPixmap(SHARED.theme.getPixmap("margin_bottom", (iPx, iPx))) + self.pixL.setPixmap(SHARED.theme.getPixmap("margin_left", (iPx, iPx))) + self.pixR.setPixmap(SHARED.theme.getPixmap("margin_right", (iPx, iPx))) + self.pixH.setPixmap(SHARED.theme.getPixmap("fit_height", (iPx, iPx))) + self.pixW.setPixmap(SHARED.theme.getPixmap("fit_width", (iPx, iPx))) + def loadContent(self) -> None: """Populate the widgets.""" # Text Content From e0bd7d82fb838b933193f8815c4a3661a79f02cb Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Sun, 26 Oct 2025 02:07:55 +0100 Subject: [PATCH 65/79] Flip the boolean for scrollable combo boxes --- novelwriter/dialogs/preferences.py | 10 +++++----- novelwriter/dialogs/projectsettings.py | 4 ++-- novelwriter/extensions/modified.py | 4 ++-- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/novelwriter/dialogs/preferences.py b/novelwriter/dialogs/preferences.py index 1c96b7e1..d36ca9ba 100644 --- a/novelwriter/dialogs/preferences.py +++ b/novelwriter/dialogs/preferences.py @@ -158,7 +158,7 @@ class GuiPreferences(NDialog): self.mainForm.addGroupLabel(title, section) # Display Language - self.guiLocale = NComboBox(self, scrollable=False) + self.guiLocale = NComboBox(self, scrollable=True) self.guiLocale.setMinimumWidth(200) for lang, name in CONFIG.listLanguages(CONFIG.LANG_NW): self.guiLocale.addItem(name, lang) @@ -170,9 +170,9 @@ class GuiPreferences(NDialog): ) # Colour Theme - self.lightTheme = NComboBox(self, scrollable=False) + self.lightTheme = NComboBox(self, scrollable=True) self.lightTheme.setMinimumWidth(200) - self.darkTheme = NComboBox(self, scrollable=False) + self.darkTheme = NComboBox(self, scrollable=True) self.darkTheme.setMinimumWidth(200) for key, theme in SHARED.theme.colourThemes.items(): if theme.dark: @@ -193,7 +193,7 @@ class GuiPreferences(NDialog): ) # Icon Theme - self.iconTheme = NComboBox(self, scrollable=False) + self.iconTheme = NComboBox(self, scrollable=True) self.iconTheme.setMinimumWidth(200) for key, theme in SHARED.theme.iconCache.iconThemes.items(): self.iconTheme.addItem(theme.name, key) @@ -511,7 +511,7 @@ class GuiPreferences(NDialog): self.mainForm.addGroupLabel(title, section) # Spell Checking - self.spellLanguage = NComboBox(self, scrollable=False) + self.spellLanguage = NComboBox(self, scrollable=True) self.spellLanguage.setMinimumWidth(200) if CONFIG.hasEnchant: diff --git a/novelwriter/dialogs/projectsettings.py b/novelwriter/dialogs/projectsettings.py index 37704f70..31af45a5 100644 --- a/novelwriter/dialogs/projectsettings.py +++ b/novelwriter/dialogs/projectsettings.py @@ -257,7 +257,7 @@ class _SettingsPage(NScrollableForm): # Project Language projLang = data.language or CONFIG.guiLocale - self.projLang = NComboBox(self, scrollable=False) + self.projLang = NComboBox(self, scrollable=True) self.projLang.setMinimumWidth(200) for tag, language in CONFIG.listLanguages(CONFIG.LANG_PROJ): self.projLang.addItem(language, tag) @@ -269,7 +269,7 @@ class _SettingsPage(NScrollableForm): ) # Spell Check Language - self.spellLang = NComboBox(self, scrollable=False) + self.spellLang = NComboBox(self, scrollable=True) self.spellLang.setMinimumWidth(200) self.spellLang.addItem(self.tr("Default"), "None") if CONFIG.hasEnchant: diff --git a/novelwriter/extensions/modified.py b/novelwriter/extensions/modified.py index 90d0ffbb..837b3c80 100644 --- a/novelwriter/extensions/modified.py +++ b/novelwriter/extensions/modified.py @@ -126,12 +126,12 @@ class NComboBox(QComboBox): """ def __init__( - self, parent: QWidget | None = None, maxItems: int = 15, scrollable: bool = True + self, parent: QWidget | None = None, maxItems: int = 15, scrollable: bool = False ) -> None: super().__init__(parent=parent) self.setFocusPolicy(Qt.FocusPolicy.StrongFocus) self.setMaxVisibleItems(maxItems) - if not scrollable: + if scrollable: # The style sheet disables Fusion style pop-up mode on some # platforms and allows for scrolling of long lists of items self.setStyleSheet("QComboBox {combobox-popup: 0;}") From a703e0bb7d6aa148dda4e8b57bf59822b466407c Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Sun, 26 Oct 2025 02:40:50 +0100 Subject: [PATCH 66/79] Update tests and add more debug output --- novelwriter/core/project.py | 2 ++ novelwriter/gui/doceditor.py | 12 ++++++++++-- novelwriter/gui/docviewer.py | 6 +++++- novelwriter/gui/docviewerpanel.py | 6 ++++++ novelwriter/gui/itemdetails.py | 1 + novelwriter/gui/noveltree.py | 5 +++-- novelwriter/gui/outline.py | 6 ++++++ novelwriter/gui/projtree.py | 3 +++ novelwriter/gui/search.py | 2 ++ novelwriter/gui/sidebar.py | 3 ++- novelwriter/gui/statusbar.py | 2 ++ novelwriter/tools/manuscript.py | 15 +++++++++++---- novelwriter/tools/manussettings.py | 9 ++++++++- tests/test_tools/test_tools_manuscript.py | 6 ++++++ 14 files changed, 67 insertions(+), 11 deletions(-) diff --git a/novelwriter/core/project.py b/novelwriter/core/project.py index 2a10fd92..44bf3590 100644 --- a/novelwriter/core/project.py +++ b/novelwriter/core/project.py @@ -552,6 +552,8 @@ class NWProject: def updateTheme(self) -> None: """Update theme elements.""" + logger.debug("Theme Update: NWProject") + self._data.itemStatus.refreshIcons() self._data.itemImport.refreshIcons() diff --git a/novelwriter/gui/doceditor.py b/novelwriter/gui/doceditor.py index 72e7577f..d7a96765 100644 --- a/novelwriter/gui/doceditor.py +++ b/novelwriter/gui/doceditor.py @@ -292,6 +292,8 @@ class GuiDocEditor(QPlainTextEdit): def updateTheme(self) -> None: """Update theme elements.""" + logger.debug("Theme Update: GuiDocEditor") + self.docSearch.updateTheme() self.docHeader.updateTheme() self.docFooter.updateTheme() @@ -2478,8 +2480,9 @@ class GuiDocToolBar(QWidget): def updateTheme(self) -> None: """Initialise GUI elements that depend on specific settings.""" - syntax = SHARED.theme.syntaxTheme + logger.debug("Theme Update: GuiDocToolBar") + syntax = SHARED.theme.syntaxTheme palette = self.palette() palette.setColor(QPalette.ColorRole.Window, syntax.back) palette.setColor(QPalette.ColorRole.WindowText, syntax.text) @@ -2716,8 +2719,9 @@ class GuiDocEditSearch(QFrame): def updateTheme(self) -> None: """Update theme elements.""" - palette = QApplication.palette() + logger.debug("Theme Update: GuiDocEditSearch") + palette = QApplication.palette() self.setPalette(palette) self.searchBox.setPalette(palette) self.replaceBox.setPalette(palette) @@ -2961,6 +2965,8 @@ class GuiDocEditHeader(QWidget): def updateTheme(self) -> None: """Update theme elements.""" + logger.debug("Theme Update: GuiDocEditHeader") + self.tbButton.setThemeIcon("fmt_toolbar", "blue") self.outlineButton.setThemeIcon("list", "blue") self.searchButton.setThemeIcon("search", "blue") @@ -3150,6 +3156,8 @@ class GuiDocEditFooter(QWidget): def updateTheme(self) -> None: """Update theme elements.""" + logger.debug("Theme Update: GuiDocEditFooter") + iPx = round(0.9*SHARED.theme.baseIconHeight) self.linesIcon.setPixmap(SHARED.theme.getPixmap("lines", (iPx, iPx))) self.wordsIcon.setPixmap(SHARED.theme.getPixmap("stats", (iPx, iPx))) diff --git a/novelwriter/gui/docviewer.py b/novelwriter/gui/docviewer.py index fc7bef81..5cd44e27 100644 --- a/novelwriter/gui/docviewer.py +++ b/novelwriter/gui/docviewer.py @@ -141,6 +141,7 @@ class GuiDocViewer(QTextBrowser): def updateTheme(self) -> None: """Update theme elements.""" + logger.debug("Theme Update: GuiDocViewer") self.docHeader.updateTheme() self.docFooter.updateTheme() @@ -724,6 +725,8 @@ class GuiDocViewHeader(QWidget): def updateTheme(self) -> None: """Update theme elements.""" + logger.debug("Theme Update: GuiDocViewHeader") + self.outlineButton.setThemeIcon("list", "blue") self.backButton.setThemeIcon("chevron_left", "blue") self.forwardButton.setThemeIcon("chevron_right", "blue") @@ -910,7 +913,8 @@ class GuiDocViewFooter(QWidget): def updateTheme(self) -> None: """Update theme elements.""" - # Icons + logger.debug("Theme Update: GuiDocViewFooter") + fPx = int(0.9*SHARED.theme.fontPixelSize) bulletIcon = SHARED.theme.getToggleIcon("bullet", (fPx, fPx), "blue") diff --git a/novelwriter/gui/docviewerpanel.py b/novelwriter/gui/docviewerpanel.py index 8ac9816a..9f3fd209 100644 --- a/novelwriter/gui/docviewerpanel.py +++ b/novelwriter/gui/docviewerpanel.py @@ -106,6 +106,8 @@ class GuiDocViewerPanel(QWidget): def updateTheme(self, updateTabs: bool = True) -> None: """Update theme elements.""" + logger.debug("Theme Update: GuiDocViewerPanel") + self.optsButton.setThemeIcon("more_vertical") self.optsButton.setStyleSheet(SHARED.theme.getStyleSheet(STYLES_MIN_TOOLBUTTON)) self.mainTabs.setStyleSheet(SHARED.theme.getStyleSheet(STYLES_FLAT_TABS)) @@ -270,6 +272,8 @@ class _ViewPanelBackRefs(QTreeWidget): def updateTheme(self) -> None: """Update theme elements.""" + logger.debug("Theme Update: _ViewPanelBackRefs") + self._editIcon = SHARED.theme.getIcon("edit", "green") self._viewIcon = SHARED.theme.getIcon("view", "blue") for i in range(self.topLevelItemCount()): @@ -400,6 +404,8 @@ class _ViewPanelKeyWords(QTreeWidget): def updateTheme(self) -> None: """Update theme elements.""" + logger.debug("Theme Update: _ViewPanelKeyWords") + self._classIcon = SHARED.theme.getIcon(nwLabels.CLASS_ICON[self._class], "root") self._editIcon = SHARED.theme.getIcon("edit", "green") self._viewIcon = SHARED.theme.getIcon("view", "blue") diff --git a/novelwriter/gui/itemdetails.py b/novelwriter/gui/itemdetails.py index d087f742..697d8b8c 100644 --- a/novelwriter/gui/itemdetails.py +++ b/novelwriter/gui/itemdetails.py @@ -216,6 +216,7 @@ class GuiItemDetails(QWidget): def updateTheme(self) -> None: """Update theme elements.""" + logger.debug("Theme Update: GuiItemDetails") self.updateViewBox(self._handle) def updateViewBox(self, tHandle: str | None) -> None: diff --git a/novelwriter/gui/noveltree.py b/novelwriter/gui/noveltree.py index a5521ef8..feb5e703 100644 --- a/novelwriter/gui/noveltree.py +++ b/novelwriter/gui/noveltree.py @@ -89,6 +89,7 @@ class GuiNovelView(QWidget): def updateTheme(self) -> None: """Update theme elements.""" + logger.debug("Theme Update: GuiNovelView") self.novelBar.updateTheme() def initSettings(self) -> None: @@ -244,12 +245,12 @@ class GuiNovelToolBar(QWidget): def updateTheme(self) -> None: """Update theme elements.""" - # Icons + logger.debug("Theme Update: GuiNovelToolBar") + self.tbNovel.setThemeIcon("cls_novel", "red") self.tbRefresh.setThemeIcon("refresh", "green") self.tbMore.setThemeIcon("more_vertical") - # StyleSheets buttonStyle = SHARED.theme.getStyleSheet(STYLES_MIN_TOOLBUTTON) self.tbNovel.setStyleSheet(buttonStyle) self.tbRefresh.setStyleSheet(buttonStyle) diff --git a/novelwriter/gui/outline.py b/novelwriter/gui/outline.py index 4a76712f..3a97ec35 100644 --- a/novelwriter/gui/outline.py +++ b/novelwriter/gui/outline.py @@ -103,6 +103,8 @@ class GuiOutlineView(QWidget): def updateTheme(self) -> None: """Update theme elements.""" + logger.debug("Theme Update: GuiOutlineView") + self.outlineBar.updateTheme() self.outlineTree.updateTheme() self.outlineTree.refreshTree( @@ -258,6 +260,8 @@ class GuiOutlineToolBar(QToolBar): def updateTheme(self) -> None: """Update theme elements.""" + logger.debug("Theme Update: GuiOutlineToolBar") + self.setStyleSheet("QToolBar {border: 0px;}") self.novelValue.refreshNovelList() self.aRefresh.setIcon(SHARED.theme.getIcon("refresh", "green")) @@ -454,6 +458,8 @@ class GuiOutlineTree(QTreeWidget): def updateTheme(self) -> None: """Update theme elements.""" + logger.debug("Theme Update: GuiOutlineTree") + iType = nwItemType.FILE iClass = nwItemClass.NO_CLASS iLayout = nwItemLayout.DOCUMENT diff --git a/novelwriter/gui/projtree.py b/novelwriter/gui/projtree.py index ab79cfb6..758b15cd 100644 --- a/novelwriter/gui/projtree.py +++ b/novelwriter/gui/projtree.py @@ -138,6 +138,7 @@ class GuiProjectView(QWidget): def updateTheme(self) -> None: """Update theme elements.""" + logger.debug("Theme Update: GuiProjectView") self.projBar.updateTheme() def initSettings(self) -> None: @@ -346,6 +347,8 @@ class GuiProjectToolBar(QWidget): def updateTheme(self) -> None: """Update theme elements.""" + logger.debug("Theme Update: GuiProjectToolBar") + buttonStyle = SHARED.theme.getStyleSheet(STYLES_MIN_TOOLBUTTON) self.tbQuick.setStyleSheet(buttonStyle) self.tbMoveU.setStyleSheet(buttonStyle) diff --git a/novelwriter/gui/search.py b/novelwriter/gui/search.py index 5313d676..e49770b3 100644 --- a/novelwriter/gui/search.py +++ b/novelwriter/gui/search.py @@ -158,6 +158,8 @@ class GuiProjectSearch(QWidget): def updateTheme(self) -> None: """Update theme elements.""" + logger.debug("Theme Update: GuiProjectSearch") + palette = QApplication.palette() colBase = palette.base().color().name(QtHexArgb) colFocus = palette.highlight().color().name(QtHexArgb) diff --git a/novelwriter/gui/sidebar.py b/novelwriter/gui/sidebar.py index 6440fc15..8e6acfd3 100644 --- a/novelwriter/gui/sidebar.py +++ b/novelwriter/gui/sidebar.py @@ -129,8 +129,9 @@ class GuiSideBar(QWidget): def updateTheme(self) -> None: """Initialise GUI elements that depend on specific settings.""" - buttonStyle = SHARED.theme.getStyleSheet(STYLES_BIG_TOOLBUTTON) + logger.debug("Theme Update: GuiSideBar") + buttonStyle = SHARED.theme.getStyleSheet(STYLES_BIG_TOOLBUTTON) self.tbProject.setStyleSheet(buttonStyle) self.tbNovel.setStyleSheet(buttonStyle) self.tbSearch.setStyleSheet(buttonStyle) diff --git a/novelwriter/gui/statusbar.py b/novelwriter/gui/statusbar.py index 0c8288b5..3578e3e0 100644 --- a/novelwriter/gui/statusbar.py +++ b/novelwriter/gui/statusbar.py @@ -133,6 +133,8 @@ class GuiMainStatus(QStatusBar): def updateTheme(self) -> None: """Update theme elements.""" + logger.debug("Theme Update: GuiMainStatus") + iPx = SHARED.theme.baseIconHeight self.langIcon.setPixmap(SHARED.theme.getPixmap("language", (iPx, iPx))) self.statsIcon.setPixmap(SHARED.theme.getPixmap("stats", (iPx, iPx))) diff --git a/novelwriter/tools/manuscript.py b/novelwriter/tools/manuscript.py index 9646140e..e3c5679b 100644 --- a/novelwriter/tools/manuscript.py +++ b/novelwriter/tools/manuscript.py @@ -263,10 +263,7 @@ class GuiManuscript(NToolDialog): def updateTheme(self, *, init: bool = False) -> None: """Update theme elements.""" - self.tbAdd.setThemeIcon("add", "green") - self.tbDel.setThemeIcon("remove", "red") - self.tbCopy.setThemeIcon("copy", "blue") - self.tbEdit.setThemeIcon("edit", "green") + logger.debug("Theme Update: GuiManuscript, init=%s", init) if not init: self.btnPreview.updateIcon() @@ -274,6 +271,11 @@ class GuiManuscript(NToolDialog): self.btnBuild.updateIcon() self.btnClose.updateIcon() + self.tbAdd.setThemeIcon("add", "green") + self.tbDel.setThemeIcon("remove", "red") + self.tbCopy.setThemeIcon("copy", "blue") + self.tbEdit.setThemeIcon("edit", "green") + buttonStyle = SHARED.theme.getStyleSheet(STYLES_MIN_TOOLBUTTON) self.tbAdd.setStyleSheet(buttonStyle) self.tbDel.setStyleSheet(buttonStyle) @@ -641,6 +643,7 @@ class _DetailsWidget(QWidget): def updateTheme(self) -> None: """Update theme elements.""" if self._build: + logger.debug("Theme Update: _DetailsWidget") self.updateInfo(self._build) @@ -709,6 +712,7 @@ class _OutlineWidget(QWidget): def updateTheme(self) -> None: """Update theme elements.""" + logger.debug("Theme Update: _OutlineWidget") self.updateOutline(self._outline, force=True) ## @@ -845,6 +849,8 @@ class _PreviewWidget(QTextBrowser): def updateTheme(self) -> None: """Update theme elements.""" + logger.debug("Theme Update: _PreviewWidget") + palette = QApplication.palette() palette.setColor(QPalette.ColorRole.Window, palette.toolTipBase().color()) palette.setColor(QPalette.ColorRole.WindowText, palette.toolTipText().color()) @@ -984,6 +990,7 @@ class _StatsWidget(QWidget): def updateTheme(self) -> None: """Update theme elements.""" + logger.debug("Theme Update: _StatsWidget") self.toggleButton.setThemeIcon("unfold") ## diff --git a/novelwriter/tools/manussettings.py b/novelwriter/tools/manussettings.py index 318c21f1..dd5bc50c 100644 --- a/novelwriter/tools/manussettings.py +++ b/novelwriter/tools/manussettings.py @@ -173,6 +173,8 @@ class GuiBuildSettings(NToolDialog): def updateTheme(self, *, init: bool = False) -> None: """Update theme elements.""" + logger.debug("Theme Update: GuiBuildSettings, init=%s", init) + if not init: self.btnApply.updateIcon() self.btnSave.updateIcon() @@ -405,6 +407,8 @@ class _FilterTab(NFixedPage): def updateTheme(self, *, init: bool = False) -> None: """Update theme elements.""" + logger.debug("Theme Update: _FilterTab, init=%s", init) + if not init: self._statusFlags[self.F_FILTERED] = SHARED.theme.getIcon("filter", "orange") self._statusFlags[self.F_INCLUDED] = SHARED.theme.getIcon("pin", "blue") @@ -805,6 +809,8 @@ class _HeadingsTab(NScrollablePage): def updateTheme(self) -> None: """Update theme elements.""" + logger.debug("Theme Update: _HeadingsTab") + self.btnPart.setThemeIcon("edit", "green") self.btnChapter.setThemeIcon("edit", "green") self.btnUnnumbered.setThemeIcon("edit", "green") @@ -1310,12 +1316,13 @@ class _FormattingTab(NScrollableForm): def updateTheme(self) -> None: """Update theme elements.""" - iPx = SHARED.theme.baseIconHeight + logger.debug("Theme Update: _FormattingTab") self.ignoredKeywordsButton.setThemeIcon("add", "green") self.btnTextFont.setThemeIcon("font") self.btnPageHeader.setThemeIcon("revert", "green") + iPx = SHARED.theme.baseIconHeight self.pixT.setPixmap(SHARED.theme.getPixmap("margin_top", (iPx, iPx))) self.pixB.setPixmap(SHARED.theme.getPixmap("margin_bottom", (iPx, iPx))) self.pixL.setPixmap(SHARED.theme.getPixmap("margin_left", (iPx, iPx))) diff --git a/tests/test_tools/test_tools_manuscript.py b/tests/test_tools/test_tools_manuscript.py index 0d74dbff..ac70c626 100644 --- a/tests/test_tools/test_tools_manuscript.py +++ b/tests/test_tools/test_tools_manuscript.py @@ -72,6 +72,9 @@ def testToolManuscript_Init(monkeypatch, qtbot, nwGUI, projPath, mockRnd): manus.btnPreview.click() assert manus.docPreview.toPlainText().strip() == allText + # Trigger a theme update, which is only a visual refresh, but it shouldn't crash + manus.updateTheme() + nwGUI.closeProject() # This should auto-close the manuscript tool # qtbot.stop() @@ -148,6 +151,9 @@ def testToolManuscript_Builds(qtbot, nwGUI, projPath): assert new is not None assert new.name == "Test Build 2" + # Trigger a theme update, which should propagate to settings + manus.updateTheme() + # Close the dialog should also close the child dialogs manus.btnClose.click() if isinstance(bSettings, GuiBuildSettings): From f2c28339859afb25469f64cbceab771763c97d2b Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Sun, 26 Oct 2025 03:16:22 +0100 Subject: [PATCH 67/79] Update test coverage --- novelwriter/extensions/configlayout.py | 8 ++----- novelwriter/extensions/modified.py | 8 +++---- tests/test_ext/test_ext_modified.py | 27 ++++++++++++++++++++--- tests/test_tools/test_tools_manuscript.py | 2 +- 4 files changed, 31 insertions(+), 14 deletions(-) diff --git a/novelwriter/extensions/configlayout.py b/novelwriter/extensions/configlayout.py index 14800a5c..2c11ad8e 100644 --- a/novelwriter/extensions/configlayout.py +++ b/novelwriter/extensions/configlayout.py @@ -27,7 +27,7 @@ along with this program. If not, see . """ # noqa from __future__ import annotations -from PyQt6.QtGui import QColor, QFont, QPalette, QPixmap +from PyQt6.QtGui import QColor, QFont, QPalette from PyQt6.QtWidgets import ( QAbstractButton, QFrame, QHBoxLayout, QLabel, QLayout, QScrollArea, QVBoxLayout, QWidget @@ -170,7 +170,7 @@ class NScrollableForm(QScrollArea): def addRow( self, label: str | None, - widget: QWidget | list[QWidget | QPixmap | int], + widget: QWidget | list[QWidget | int], helpText: str = "", unit: str | None = None, button: QWidget | None = None, @@ -187,10 +187,6 @@ class NScrollableForm(QScrollArea): for item in widget: if isinstance(item, QWidget): wBox.addWidget(item) - elif isinstance(item, QPixmap): - icon = QLabel(self) - icon.setPixmap(item) - wBox.addWidget(icon) elif isinstance(item, int): wBox.addSpacing(item) qWidget = QWidget(self) diff --git a/novelwriter/extensions/modified.py b/novelwriter/extensions/modified.py index 837b3c80..054665ce 100644 --- a/novelwriter/extensions/modified.py +++ b/novelwriter/extensions/modified.py @@ -241,9 +241,9 @@ class NIconToolButton(QToolButton): if icon: self.setThemeIcon(icon, color) - def setThemeIcon(self, iconKey: str, color: str | None = None) -> None: + def setThemeIcon(self, icon: str, color: str | None = None) -> None: """Set an icon from the current theme.""" - self.setIcon(SHARED.theme.getIcon(iconKey, color)) + self.setIcon(SHARED.theme.getIcon(icon, color)) class NIconToggleButton(QToolButton): @@ -262,10 +262,10 @@ class NIconToggleButton(QToolButton): if icon: self.setThemeIcon(icon) - def setThemeIcon(self, iconKey: str) -> None: + def setThemeIcon(self, icon: str) -> None: """Set an icon from the current theme.""" size = self.iconSize() - self.setIcon(SHARED.theme.getToggleIcon(iconKey, (size.width(), size.height()))) + self.setIcon(SHARED.theme.getToggleIcon(icon, (size.width(), size.height()))) class NClickableLabel(QLabel): diff --git a/tests/test_ext/test_ext_modified.py b/tests/test_ext/test_ext_modified.py index 7e65de1a..fa66a528 100644 --- a/tests/test_ext/test_ext_modified.py +++ b/tests/test_ext/test_ext_modified.py @@ -22,12 +22,13 @@ from __future__ import annotations import pytest -from PyQt6.QtCore import QEvent, QPoint, QPointF, Qt +from PyQt6.QtCore import QEvent, QPoint, QPointF, QSize, Qt from PyQt6.QtGui import QKeyEvent, QMouseEvent, QStandardItem, QStandardItemModel, QWheelEvent from PyQt6.QtWidgets import QWidget from novelwriter.extensions.modified import ( - NClickableLabel, NComboBox, NDialog, NDoubleSpinBox, NSpinBox, NTreeView + NClickableLabel, NComboBox, NDialog, NDoubleSpinBox, NIconToggleButton, + NIconToolButton, NSpinBox, NTreeView ) from novelwriter.types import QtModNone, QtMouseLeft, QtMouseMiddle, QtRejected @@ -168,7 +169,7 @@ def testExtModified_NDoubleSpinBox(qtbot, monkeypatch): @pytest.mark.gui -def testExtModified_NClickableLabel(qtbot, monkeypatch): +def testExtModified_NClickableLabel(qtbot): """Test the NClickableLabel class.""" widget = NClickableLabel() dialog = SimpleDialog(widget) @@ -181,3 +182,23 @@ def testExtModified_NClickableLabel(qtbot, monkeypatch): with qtbot.waitSignal(widget.mouseClicked): widget.mousePressEvent(event) + + +@pytest.mark.gui +def testExtModified_ToolButtons(qtbot): + """Test the NIconToolButton and NIconToggleButton classes.""" + dialog = SimpleDialog(None) + + size = QSize(16, 16) + button1 = NIconToolButton(dialog, size, "add", "green") + button2 = NIconToggleButton(dialog, size, "bullet") + + assert button1.iconSize() == size + assert button2.iconSize() == size + + assert button1.icon().isNull() is False + assert button2.icon().isNull() is False + + dialog.addWidget(button1) + dialog.addWidget(button2) + dialog.show() diff --git a/tests/test_tools/test_tools_manuscript.py b/tests/test_tools/test_tools_manuscript.py index ac70c626..3d8b86cb 100644 --- a/tests/test_tools/test_tools_manuscript.py +++ b/tests/test_tools/test_tools_manuscript.py @@ -152,7 +152,7 @@ def testToolManuscript_Builds(qtbot, nwGUI, projPath): assert new.name == "Test Build 2" # Trigger a theme update, which should propagate to settings - manus.updateTheme() + nwGUI.refreshThemeColors() # Close the dialog should also close the child dialogs manus.btnClose.click() From 5a4930f86002d07be2446f03fc133ad1cec6329c Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Sun, 26 Oct 2025 12:06:45 +0100 Subject: [PATCH 68/79] Fix role for close buttons --- novelwriter/dialogs/about.py | 4 ++-- novelwriter/dialogs/wordlist.py | 4 ++-- novelwriter/tools/dictionaries.py | 4 ++-- novelwriter/tools/lipsum.py | 4 ++-- novelwriter/tools/manusbuild.py | 4 ++-- novelwriter/tools/manussettings.py | 4 ++-- novelwriter/tools/noveldetails.py | 4 ++-- novelwriter/tools/writingstats.py | 4 ++-- novelwriter/types.py | 1 + 9 files changed, 17 insertions(+), 16 deletions(-) diff --git a/novelwriter/dialogs/about.py b/novelwriter/dialogs/about.py index 080b3b21..a8898fa0 100644 --- a/novelwriter/dialogs/about.py +++ b/novelwriter/dialogs/about.py @@ -37,7 +37,7 @@ from novelwriter.enum import nwStandardButton from novelwriter.extensions.configlayout import NColorLabel from novelwriter.extensions.modified import NDialog from novelwriter.extensions.versioninfo import VersionInfoWidget -from novelwriter.types import QtAlignRightTop, QtHexArgb, QtRoleReject +from novelwriter.types import QtAlignRightTop, QtHexArgb, QtRoleDestruct if TYPE_CHECKING: from PyQt6.QtGui import QCloseEvent @@ -87,7 +87,7 @@ class GuiAbout(NDialog): self.btnClose.clicked.connect(self.reject) self.btnBox = QDialogButtonBox(self) - self.btnBox.addButton(self.btnClose, QtRoleReject) + self.btnBox.addButton(self.btnClose, QtRoleDestruct) # Assemble self.innerBox = QVBoxLayout() diff --git a/novelwriter/dialogs/wordlist.py b/novelwriter/dialogs/wordlist.py index 1a05e187..a2f60b91 100644 --- a/novelwriter/dialogs/wordlist.py +++ b/novelwriter/dialogs/wordlist.py @@ -40,7 +40,7 @@ from novelwriter.core.spellcheck import UserDictionary from novelwriter.enum import nwStandardButton from novelwriter.extensions.configlayout import NColorLabel from novelwriter.extensions.modified import NDialog, NIconToolButton -from novelwriter.types import QtRoleAccept, QtRoleReject +from novelwriter.types import QtRoleAccept, QtRoleDestruct if TYPE_CHECKING: from PyQt6.QtGui import QCloseEvent @@ -119,7 +119,7 @@ class GuiWordList(NDialog): self.btnBox = QDialogButtonBox(self) self.btnBox.addButton(self.btnSave, QtRoleAccept) - self.btnBox.addButton(self.btnClose, QtRoleReject) + self.btnBox.addButton(self.btnClose, QtRoleDestruct) # Assemble self.outerBox = QVBoxLayout() diff --git a/novelwriter/tools/dictionaries.py b/novelwriter/tools/dictionaries.py index cf90d65a..bd19b78d 100644 --- a/novelwriter/tools/dictionaries.py +++ b/novelwriter/tools/dictionaries.py @@ -40,7 +40,7 @@ from novelwriter.common import formatFileFilter, formatInt, getFileSize, openExt from novelwriter.enum import nwStandardButton from novelwriter.error import formatException from novelwriter.extensions.modified import NIconToolButton, NNonBlockingDialog -from novelwriter.types import QtHexArgb, QtRoleReject +from novelwriter.types import QtHexArgb, QtRoleDestruct logger = logging.getLogger(__name__) @@ -115,7 +115,7 @@ class GuiDictionaries(NNonBlockingDialog): self.btnClose.clicked.connect(self.reject) self.btnBox = QDialogButtonBox(self) - self.btnBox.addButton(self.btnClose, QtRoleReject) + self.btnBox.addButton(self.btnClose, QtRoleDestruct) # Assemble self.outerBox = QVBoxLayout() diff --git a/novelwriter/tools/lipsum.py b/novelwriter/tools/lipsum.py index a970fe93..3cd2fea7 100644 --- a/novelwriter/tools/lipsum.py +++ b/novelwriter/tools/lipsum.py @@ -37,7 +37,7 @@ from novelwriter.common import readTextFile from novelwriter.enum import nwStandardButton from novelwriter.extensions.modified import NDialog from novelwriter.extensions.switch import NSwitch -from novelwriter.types import QtAlignLeft, QtAlignRight, QtRoleApply, QtRoleReject +from novelwriter.types import QtAlignLeft, QtAlignRight, QtRoleApply, QtRoleDestruct logger = logging.getLogger(__name__) @@ -102,7 +102,7 @@ class GuiLipsum(NDialog): self.btnBox = QDialogButtonBox(self) self.btnBox.addButton(self.btnInsert, QtRoleApply) - self.btnBox.addButton(self.btnClose, QtRoleReject) + self.btnBox.addButton(self.btnClose, QtRoleDestruct) # Assemble self.outerBox = QVBoxLayout() diff --git a/novelwriter/tools/manusbuild.py b/novelwriter/tools/manusbuild.py index c77898d7..ffbef09d 100644 --- a/novelwriter/tools/manusbuild.py +++ b/novelwriter/tools/manusbuild.py @@ -43,7 +43,7 @@ from novelwriter.core.item import NWItem from novelwriter.enum import nwBuildFmt, nwStandardButton from novelwriter.extensions.modified import NDialog, NIconToolButton, NPushButton from novelwriter.extensions.progressbars import NProgressSimple -from novelwriter.types import QtAlignCenter, QtRoleAction, QtRoleReject, QtUserRole +from novelwriter.types import QtAlignCenter, QtRoleAction, QtRoleDestruct, QtRoleReject, QtUserRole if TYPE_CHECKING: from PyQt6.QtGui import QCloseEvent @@ -190,7 +190,7 @@ class GuiManuscriptBuild(NDialog): self.btnBox = QDialogButtonBox(self) self.btnBox.addButton(self.btnOpen, QtRoleAction) self.btnBox.addButton(self.btnBuild, QtRoleAction) - self.btnBox.addButton(self.btnClose, QtRoleReject) + self.btnBox.addButton(self.btnClose, QtRoleDestruct) # Assemble GUI # ============ diff --git a/novelwriter/tools/manussettings.py b/novelwriter/tools/manussettings.py index dd5bc50c..7f00f6ff 100644 --- a/novelwriter/tools/manussettings.py +++ b/novelwriter/tools/manussettings.py @@ -52,7 +52,7 @@ from novelwriter.extensions.switch import NSwitch from novelwriter.extensions.switchbox import NSwitchBox from novelwriter.types import ( QtAlignCenter, QtAlignLeft, QtHeaderFixed, QtHeaderStretch, QtRoleAccept, - QtRoleApply, QtRoleReject, QtUserRole + QtRoleApply, QtRoleDestruct, QtRoleReject, QtUserRole ) if TYPE_CHECKING: @@ -132,7 +132,7 @@ class GuiBuildSettings(NToolDialog): self.btnBox = QDialogButtonBox(self) self.btnBox.addButton(self.btnApply, QtRoleApply) self.btnBox.addButton(self.btnSave, QtRoleAccept) - self.btnBox.addButton(self.btnClose, QtRoleReject) + self.btnBox.addButton(self.btnClose, QtRoleDestruct) self.btnBox.clicked.connect(self._dialogButtonClicked) # Assemble diff --git a/novelwriter/tools/noveldetails.py b/novelwriter/tools/noveldetails.py index c0314e9b..24b7a9d4 100644 --- a/novelwriter/tools/noveldetails.py +++ b/novelwriter/tools/noveldetails.py @@ -44,7 +44,7 @@ from novelwriter.extensions.modified import NNonBlockingDialog from novelwriter.extensions.novelselector import NovelSelector from novelwriter.extensions.pagedsidebar import NPagedSideBar from novelwriter.extensions.switch import NSwitch -from novelwriter.types import QtAlignRight, QtDecoration, QtRoleReject +from novelwriter.types import QtAlignRight, QtDecoration, QtRoleDestruct if TYPE_CHECKING: from PyQt6.QtGui import QCloseEvent @@ -107,7 +107,7 @@ class GuiNovelDetails(NNonBlockingDialog): self.btnClose.clicked.connect(self.reject) self.btnBox = QDialogButtonBox(self) - self.btnBox.addButton(self.btnClose, QtRoleReject) + self.btnBox.addButton(self.btnClose, QtRoleDestruct) # Assemble self.topBox = QHBoxLayout() diff --git a/novelwriter/tools/writingstats.py b/novelwriter/tools/writingstats.py index dcf3ce40..fea44864 100644 --- a/novelwriter/tools/writingstats.py +++ b/novelwriter/tools/writingstats.py @@ -45,7 +45,7 @@ from novelwriter.extensions.modified import NPushButton, NToolDialog from novelwriter.extensions.switch import NSwitch from novelwriter.types import ( QtAlignLeftMiddle, QtAlignRight, QtAlignRightMiddle, QtDecoration, - QtRoleAction, QtRoleReject + QtRoleAction, QtRoleDestruct ) if TYPE_CHECKING: @@ -298,7 +298,7 @@ class GuiWritingStats(NToolDialog): self.btnBox = QDialogButtonBox(self) self.btnBox.addButton(self.btnSave, QtRoleAction) - self.btnBox.addButton(self.btnClose, QtRoleReject) + self.btnBox.addButton(self.btnClose, QtRoleDestruct) # Assemble self.outerBox = QGridLayout() diff --git a/novelwriter/types.py b/novelwriter/types.py index 52a18351..658e4ed0 100644 --- a/novelwriter/types.py +++ b/novelwriter/types.py @@ -96,6 +96,7 @@ QtRejected = QDialog.DialogCode.Rejected QtRoleAccept = QDialogButtonBox.ButtonRole.AcceptRole QtRoleAction = QDialogButtonBox.ButtonRole.ActionRole QtRoleApply = QDialogButtonBox.ButtonRole.ApplyRole +QtRoleDestruct = QDialogButtonBox.ButtonRole.DestructiveRole QtRoleReject = QDialogButtonBox.ButtonRole.RejectRole QtRoleReset = QDialogButtonBox.ButtonRole.ResetRole From c4e9774ffa9b00bbccb9ed8bec6c87093ba9f73d Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Sun, 26 Oct 2025 12:06:56 +0100 Subject: [PATCH 69/79] Update readmes --- CONTRIBUTING.md | 73 ++++++++++++++++++++++++++++++++++++++++++------- README.md | 19 ++++++++++++- 2 files changed, 81 insertions(+), 11 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index abd8b319..1ddeaed1 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -12,6 +12,8 @@ just make a pull request directly. * Bugfixes for new or existing bugs. Please also report new bugs in the issue tracker even if you also provide a fix. It makes it easier to keep track of what has been fixed and when. * Translations made via the [Crowdin project page](https://crowdin.com/project/novelwriter). +* Translations of the documentation. These need to use Sphinx i18n tooling. Please start a + discussion before beginning such work as it requires some coordination. * Improvements to the documentation. Particularly if the documentation is unclear. Please don't make any larger changes to the documentation without discussing them with the maintainer first. * Adaptations, installation or packaging features targeting specific operating systems. @@ -21,6 +23,34 @@ just make a pull request directly. * Make a pull request that restructures or reformats existing code. If you think some part of the code could be improved, please make an issue thread or start a discussion. The same applies to any text document in the repository. +* Make pull requests with AI generated code. This is not a project suitable for vibe coding. + Outright slop will result in the account being blocked. + +This project uses [uv](https://docs.astral.sh/uv/) as its main developer tool. In order to run +novelWriter directly from checked out source, simply call from the root folder: + +```bash +uv run novelwriter +``` + +Many tasks like building assets from source are handled by the `pkgutils.py` helper tool. + +```bash +uv run pkgutils.py --help +``` + +The translation files needed at runtime can be built with: + +```bash +uv run pkgutils.py qtlrelease +``` + +Material design icons are included with the source. Optional icon themes can be built with: + +```bash +uv run pkgutils.py icons optional +``` + ## Picking the Correct Branch for a Pull Request @@ -33,6 +63,11 @@ New features are only accepted on full releases, so a feature pull request must `main` branch. However, if the `main` branch is very close to a new full release, pull requests may not be merged until the release is completed. +This project uses GitHub milestones to plan releases, and only pull requests included in the +current release cycle will be merged to `main`. Milestone tickets are not set in stone and are +often moved between them. + + ## Pull Request Check List Make sure the pull request follows these rules: @@ -41,32 +76,45 @@ Make sure the pull request follows these rules: own fork from the current `main` branch. Do not make pull requests from your copy of the `main` branch. * Please provide a description of the changes in the pull request under the summary section of the - pull request template, and reference any related issues by providing the issue number. + pull request template, and reference any related issues by providing the issue number. Do not + post links to issue numbers as that breaks the integration. Stating the issue number is enough. * Do not change the version number. * Do not submit files that were not actively changed but have otherwise been modified. This is - mostly an issue with translation files. The language tool may update all files in the `i18n` - folder. + particularly an issue with autoformatting. + ## General Rules These are the guidelines for the project. The source code of novelWriter broadly follows the [PEP8](https://www.python.org/dev/peps/pep-0008) style guide, but with a few exceptions. +The project uses [ruff](https://docs.astral.sh/ruff/) for linting, but the auto-formatter should +not be used at this point. It also uses [isort](https://pycqa.github.io/isort) for import sorting. +The latter can be auto-formatted and the settings are defined in ``pyproject.toml`. + + ### Tests * New code must not break any existing tests. * New code must come with tests that cover the code in full. If the code has branches that only - runs on some OSes, they must be covered when test are run on that OS. The test suite runs on - Linux, Windows and MacOS. + runs on some OSes, they only need to be covered when test are run on that OS. The test suite runs + on Linux, Windows and MacOS. + +A helper script is provided for running tests. It simplifies coverage reporting and a few other +things. Run the following to see all details: + +```bash +uv run run_tests.py --help +``` + ### Code Formatting -* Do not run automatic formatting tools like `black` or `ruff` on the code. Auto-formatting using - `ruff` is planned, but there are a couple of features missing in it, so it is currently only used - for linting. Auto-formatting with `isort` is configured in `pyproject.toml` and can be used. -* The pull request code *must* pass the `ruff` linting rules specified in `pyproject.toml`. +* The pull request code *must* pass the `ruff` and `isort` linting rules specified in + `pyproject.toml`. * In general, do not make large scale formatting changes to the code. + ### Type Annotations * All functions and parameters must be type annotated, and so must variables and attributes if the @@ -77,6 +125,7 @@ These are the guidelines for the project. The source code of novelWriter broadly * Do not use deprecated capitalised annotations like `Dict`, `List`, `Tuple`, etc. * Type annotated code must be runnable on all supported Python versions. + ### Internationalisation * All comments and docstrings in the code must be in English. @@ -84,6 +133,7 @@ These are the guidelines for the project. The source code of novelWriter broadly spelling of this text *must* be UK English. US English spelling is not allowed for these strings. * Commit descriptions and pull requests must also be in English. + ### Line Length * Source code lines can extend to the upper limit of 99 characters. Generally, if a code statement @@ -92,6 +142,7 @@ These are the guidelines for the project. The source code of novelWriter broadly * For text files, the text should be wrapped at 99 character. The exception is Markdown image tags and URLs which can run past that limit. + ### Spaces, Indentation and Alignment * Only indentation by multiples of 4 spaces is allowed. @@ -101,9 +152,11 @@ These are the guidelines for the project. The source code of novelWriter broadly rule is relaxed a bit here. Alignment is allowed when populating large dictionaries or setting many class attributes. It does improve readability in such cases, but should not be overused. + ### General Code Rules * Use f-string style for string formatting as the first choice, and `.format` functions if there is a good reason for it. Do not use `%` style formatting except for logging output. For logging, `%` must be used (it's a limitation in the logging library unfortunately). -* Functions should be on camelCase form for consistency with the Qt library code. +* Functions should be on camelCase form for consistency with the Qt library code. This also goes + for variable names for the sake of internal consistency. diff --git a/README.md b/README.md index bb2cd3e9..fd83fdc1 100644 --- a/README.md +++ b/README.md @@ -29,6 +29,7 @@ documentation. * PyPi Project: [pypi.org/project/novelWriter](https://pypi.org/project/novelWriter) * Social Media: [fosstodon.org/@novelwriter](https://fosstodon.org/@novelwriter) + ## Sponsors @@ -38,6 +39,7 @@ documentation.
+ ## Implementation novelWriter is written with Python and Qt6 with PyQt6 Python binding. It is released on Linux, @@ -48,12 +50,25 @@ Python.

+ +## Working With the Source + +This project uses [uv](https://docs.astral.sh/uv/) as its main developer tool. That means the +`pyproject.toml` file handles almost everything aside from a few OS-specific packaging tasks. + +In order to run novelWriter directly from checked out source, simply call from the root folder: + +```bash +uv run novelwriter +``` + ## Project Contributions Please don't make feature pull requests without first having discussed them with the maintainer. You can make a feature request in the [issues tracker](https://github.com/vkbo/novelWriter/issues), or if the idea isn't fully formed, start a [discussion](https://github.com/vkbo/novelWriter/discussions). -Please also don't make pull requests to reformat or rewrite existing code unless there is a very good reason for doing so. +Please also don't make pull requests to reformat or rewrite existing code unless there is a very +good reason for doing so. Fixes and patches are welcome. Contributions related to packaging and installing novelWriter will also be appreciated, but please make an issue or a discussion topic first. Before contributing any @@ -66,6 +81,7 @@ Project credits are available in [CREDITS.md](https://github.com/vkbo/novelWrite the `release` branch. So if you're submitting a fix to a current release, **including changes to documentation**, they must be made to the `release` branch. + ### Translations New translations are always welcome. This project uses Crowdin to maintain translations, and you @@ -73,6 +89,7 @@ can contribute translations at the [Crowdin project page](https://crowdin.com/pr If you have any questions, feel free to post them to the [Translations of novelWriter](https://github.com/vkbo/novelWriter/issues/93) issue thread. + ## Licence This is Open Source software, and novelWriter is licenced under GPLv3. See the From 61d591e86e8cd4d9b28f61b28ad7b8381a6e0ca3 Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Sun, 26 Oct 2025 12:38:32 +0100 Subject: [PATCH 70/79] Add theme field for white space --- novelwriter/gui/dochighlight.py | 4 +--- novelwriter/gui/theme.py | 2 ++ tests/test_gui/test_gui_theme.py | 7 ++++--- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/novelwriter/gui/dochighlight.py b/novelwriter/gui/dochighlight.py index 3cd1cb1d..f20f5f5e 100644 --- a/novelwriter/gui/dochighlight.py +++ b/novelwriter/gui/dochighlight.py @@ -94,8 +94,6 @@ class GuiDocHighlighter(QSyntaxHighlighter): syntax = SHARED.theme.syntaxTheme colEmph = syntax.emph if CONFIG.highlightEmph else None - colBreak = QColor(syntax.emph) - colBreak.setAlpha(64) # Create Character Formats self._addCharFormat("text", syntax.text) @@ -112,7 +110,7 @@ class GuiDocHighlighter(QSyntaxHighlighter): self._addCharFormat("strike", syntax.hidden, "s") self._addCharFormat("mark", syntax.mark, "bg") self._addCharFormat("mspaces", syntax.error, "err") - self._addCharFormat("nobreak", colBreak, "bg") + self._addCharFormat("nobreak", syntax.space, "bg") self._addCharFormat("altdialog", syntax.dialA) self._addCharFormat("dialog", syntax.dialN) self._addCharFormat("replace", syntax.repTag) diff --git a/novelwriter/gui/theme.py b/novelwriter/gui/theme.py index 6293a9f6..407e87a9 100644 --- a/novelwriter/gui/theme.py +++ b/novelwriter/gui/theme.py @@ -114,6 +114,7 @@ class SyntaxColors: head: QColor = QColor(0, 0, 0) headH: QColor = QColor(0, 0, 0) emph: QColor = QColor(0, 0, 0) + space: QColor = QColor(0, 0, 0) dialN: QColor = QColor(0, 0, 0) dialA: QColor = QColor(0, 0, 0) hidden: QColor = QColor(0, 0, 0) @@ -393,6 +394,7 @@ class GuiTheme: self.syntaxTheme.head = self._readColor(parser, sec, "headertext") self.syntaxTheme.headH = self._readColor(parser, sec, "headertag") self.syntaxTheme.emph = self._readColor(parser, sec, "emphasis") + self.syntaxTheme.space = self._readColor(parser, sec, "whitespace") self.syntaxTheme.dialN = self._readColor(parser, sec, "dialog") self.syntaxTheme.dialA = self._readColor(parser, sec, "altdialog") self.syntaxTheme.hidden = self._readColor(parser, sec, "hidden") diff --git a/tests/test_gui/test_gui_theme.py b/tests/test_gui/test_gui_theme.py index aa613bd5..7f4d976a 100644 --- a/tests/test_gui/test_gui_theme.py +++ b/tests/test_gui/test_gui_theme.py @@ -635,9 +635,10 @@ def testGuiTheme_CheckTheme(theme): ], "Syntax": [ "background", "text", "line", "link", "headertext", "headertag", - "emphasis", "dialog", "altdialog", "hidden", "note", "shortcode", - "keyword", "tag", "value", "optional", "spellcheckline", - "errorline", "replacetag", "modifier", "texthighlight", + "emphasis", "whitespace", "dialog", "altdialog", "hidden", "note", + "shortcode", "keyword", "tag", "value", "optional", + "spellcheckline", "errorline", "replacetag", "modifier", + "texthighlight", ], } optional = ["credit", "url"] From 0a5261a68c351204cd14953e6b3889e5a1b7449f Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Sun, 26 Oct 2025 12:44:23 +0100 Subject: [PATCH 71/79] Update all themes --- novelwriter/assets/themes/aura.conf | 1 + novelwriter/assets/themes/aura_bright.conf | 1 + novelwriter/assets/themes/aura_soft.conf | 1 + novelwriter/assets/themes/b2t_garden_dark.conf | 1 + novelwriter/assets/themes/b2t_garden_light.conf | 1 + novelwriter/assets/themes/b2t_suburb_dark.conf | 1 + novelwriter/assets/themes/b2t_suburb_light.conf | 1 + novelwriter/assets/themes/b4t_classic_o_dark.conf | 1 + novelwriter/assets/themes/b4t_classic_o_light.conf | 1 + novelwriter/assets/themes/b4t_modern_c_dark.conf | 1 + novelwriter/assets/themes/b4t_modern_c_light.conf | 1 + novelwriter/assets/themes/blue_streak_dark.conf | 1 + novelwriter/assets/themes/blue_streak_light.conf | 1 + novelwriter/assets/themes/castle_day.conf | 1 + novelwriter/assets/themes/castle_night.conf | 1 + novelwriter/assets/themes/chalky_soil.conf | 1 + novelwriter/assets/themes/chernozem.conf | 1 + novelwriter/assets/themes/cyberpunk_night.conf | 1 + novelwriter/assets/themes/default_dark.conf | 1 + novelwriter/assets/themes/default_light.conf | 1 + novelwriter/assets/themes/dracula.conf | 1 + novelwriter/assets/themes/espresso.conf | 1 + novelwriter/assets/themes/everforest_dark.conf | 1 + novelwriter/assets/themes/everforest_light.conf | 1 + novelwriter/assets/themes/floral_daydream.conf | 1 + novelwriter/assets/themes/floral_midnight.conf | 1 + novelwriter/assets/themes/full_moon.conf | 1 + novelwriter/assets/themes/grey_dark.conf | 1 + novelwriter/assets/themes/grey_light.conf | 1 + novelwriter/assets/themes/horizon_dark.conf | 1 + novelwriter/assets/themes/horizon_light.conf | 1 + novelwriter/assets/themes/lcars.conf | 1 + novelwriter/assets/themes/light_owl.conf | 1 + novelwriter/assets/themes/new_moon.conf | 1 + novelwriter/assets/themes/night_owl.conf | 1 + novelwriter/assets/themes/noctis.conf | 1 + novelwriter/assets/themes/noctis_lux.conf | 1 + novelwriter/assets/themes/nord.conf | 1 + novelwriter/assets/themes/nordlicht.conf | 1 + novelwriter/assets/themes/otium_dark.conf | 1 + novelwriter/assets/themes/otium_light.conf | 1 + novelwriter/assets/themes/paragon.conf | 1 + novelwriter/assets/themes/primer_light.conf | 1 + novelwriter/assets/themes/primer_night.conf | 1 + novelwriter/assets/themes/ruby_day.conf | 1 + novelwriter/assets/themes/ruby_night.conf | 3 ++- novelwriter/assets/themes/selenium_dark.conf | 1 + novelwriter/assets/themes/selenium_light.conf | 1 + novelwriter/assets/themes/sepia_dark.conf | 1 + novelwriter/assets/themes/sepia_light.conf | 1 + novelwriter/assets/themes/snazzy.conf | 1 + novelwriter/assets/themes/solarized_dark.conf | 1 + novelwriter/assets/themes/solarized_light.conf | 1 + novelwriter/assets/themes/sultana_light.conf | 1 + novelwriter/assets/themes/sultana_night.conf | 1 + novelwriter/assets/themes/tango_dark.conf | 1 + novelwriter/assets/themes/tango_light.conf | 1 + novelwriter/assets/themes/tomorrow.conf | 1 + novelwriter/assets/themes/tomorrow_night.conf | 1 + novelwriter/assets/themes/tomorrow_night_blue.conf | 1 + novelwriter/assets/themes/tomorrow_night_bright.conf | 1 + novelwriter/assets/themes/tomorrow_night_eighties.conf | 1 + novelwriter/assets/themes/vivid_black_green.conf | 1 + novelwriter/assets/themes/vivid_black_red.conf | 3 ++- novelwriter/assets/themes/vivid_white_green.conf | 1 + novelwriter/assets/themes/vivid_white_red.conf | 1 + novelwriter/assets/themes/warpgate.conf | 1 + novelwriter/assets/themes/waterlily_dark.conf | 1 + novelwriter/assets/themes/waterlily_light.conf | 1 + 69 files changed, 71 insertions(+), 2 deletions(-) diff --git a/novelwriter/assets/themes/aura.conf b/novelwriter/assets/themes/aura.conf index a31ac7ba..97ba7908 100644 --- a/novelwriter/assets/themes/aura.conf +++ b/novelwriter/assets/themes/aura.conf @@ -59,6 +59,7 @@ link = blue headertext = cyan headertag = cyan:128 emphasis = purple:L105 +whitespace = purple:64 dialog = cyan altdialog = blue note = yellow diff --git a/novelwriter/assets/themes/aura_bright.conf b/novelwriter/assets/themes/aura_bright.conf index 651e452c..47bc7854 100644 --- a/novelwriter/assets/themes/aura_bright.conf +++ b/novelwriter/assets/themes/aura_bright.conf @@ -57,6 +57,7 @@ link = blue headertext = cyan headertag = cyan:128 emphasis = purple +whitespace = purple:64 dialog = cyan:D105 altdialog = blue note = yellow diff --git a/novelwriter/assets/themes/aura_soft.conf b/novelwriter/assets/themes/aura_soft.conf index f8d58f70..1dafbc49 100644 --- a/novelwriter/assets/themes/aura_soft.conf +++ b/novelwriter/assets/themes/aura_soft.conf @@ -59,6 +59,7 @@ link = blue headertext = cyan headertag = cyan:128 emphasis = #a581ec +whitespace = #a581ec64 dialog = cyan altdialog = blue note = yellow diff --git a/novelwriter/assets/themes/b2t_garden_dark.conf b/novelwriter/assets/themes/b2t_garden_dark.conf index f5ecceba..cfaaa37e 100644 --- a/novelwriter/assets/themes/b2t_garden_dark.conf +++ b/novelwriter/assets/themes/b2t_garden_dark.conf @@ -59,6 +59,7 @@ link = blue headertext = #fbfaf8 headertag = #828782 emphasis = #3fac39 +whitespace = #3fac3964 dialog = #90d98c altdialog = #4cb946 note = #dd843c diff --git a/novelwriter/assets/themes/b2t_garden_light.conf b/novelwriter/assets/themes/b2t_garden_light.conf index ac2287b7..d15c2e35 100644 --- a/novelwriter/assets/themes/b2t_garden_light.conf +++ b/novelwriter/assets/themes/b2t_garden_light.conf @@ -59,6 +59,7 @@ link = blue headertext = #2b2c2a headertag = #828782 emphasis = #4cb946 +whitespace = #4cb94664 dialog = #1c8217 altdialog = #3fac39 note = #d97726 diff --git a/novelwriter/assets/themes/b2t_suburb_dark.conf b/novelwriter/assets/themes/b2t_suburb_dark.conf index 854808ca..56c3768d 100644 --- a/novelwriter/assets/themes/b2t_suburb_dark.conf +++ b/novelwriter/assets/themes/b2t_suburb_dark.conf @@ -59,6 +59,7 @@ link = blue headertext = red headertag = red:128 emphasis = #ffffff +whitespace = #ffffff64 dialog = #fe81b5 altdialog = #ffb3d2 note = #a0acfe diff --git a/novelwriter/assets/themes/b2t_suburb_light.conf b/novelwriter/assets/themes/b2t_suburb_light.conf index f31c8c29..8b728e6a 100644 --- a/novelwriter/assets/themes/b2t_suburb_light.conf +++ b/novelwriter/assets/themes/b2t_suburb_light.conf @@ -59,6 +59,7 @@ link = blue headertext = red headertag = red:128 emphasis = #1e202f +whitespace = #1e202f64 dialog = red altdialog = #fb6fa9 note = blue diff --git a/novelwriter/assets/themes/b4t_classic_o_dark.conf b/novelwriter/assets/themes/b4t_classic_o_dark.conf index ce775630..ca8a2b8b 100644 --- a/novelwriter/assets/themes/b4t_classic_o_dark.conf +++ b/novelwriter/assets/themes/b4t_classic_o_dark.conf @@ -59,6 +59,7 @@ link = blue headertext = cyan headertag = cyan:128 emphasis = #5fe2d1 +whitespace = #5fe2d164 dialog = #87B4FC altdialog = #5A96F6 note = #d19af4 diff --git a/novelwriter/assets/themes/b4t_classic_o_light.conf b/novelwriter/assets/themes/b4t_classic_o_light.conf index 0f9c94a1..829e3a4d 100644 --- a/novelwriter/assets/themes/b4t_classic_o_light.conf +++ b/novelwriter/assets/themes/b4t_classic_o_light.conf @@ -59,6 +59,7 @@ link = blue headertext = cyan headertag = cyan:128 emphasis = #008976 +whitespace = #00897664 dialog = #1a68e5 altdialog = #1249a0 note = #c17eed diff --git a/novelwriter/assets/themes/b4t_modern_c_dark.conf b/novelwriter/assets/themes/b4t_modern_c_dark.conf index e348fbef..080a859f 100644 --- a/novelwriter/assets/themes/b4t_modern_c_dark.conf +++ b/novelwriter/assets/themes/b4t_modern_c_dark.conf @@ -59,6 +59,7 @@ link = blue headertext = red headertag = red:128 emphasis = #f391b6 +whitespace = #f391b664 dialog = yellow altdialog = #e86296 note = #929ff7 diff --git a/novelwriter/assets/themes/b4t_modern_c_light.conf b/novelwriter/assets/themes/b4t_modern_c_light.conf index b881167f..cc6468fe 100644 --- a/novelwriter/assets/themes/b4t_modern_c_light.conf +++ b/novelwriter/assets/themes/b4t_modern_c_light.conf @@ -59,6 +59,7 @@ link = blue headertext = red headertag = red:128 emphasis = #d53874 +whitespace = #d5387464 dialog = #9f6303 altdialog = #e86296 note = blue diff --git a/novelwriter/assets/themes/blue_streak_dark.conf b/novelwriter/assets/themes/blue_streak_dark.conf index 49d19c42..aad8acc0 100644 --- a/novelwriter/assets/themes/blue_streak_dark.conf +++ b/novelwriter/assets/themes/blue_streak_dark.conf @@ -59,6 +59,7 @@ link = blue headertext = blue headertag = blue:D150 emphasis = blue:L150 +whitespace = blue:64 dialog = blue altdialog = blue:L150 note = blue:L150 diff --git a/novelwriter/assets/themes/blue_streak_light.conf b/novelwriter/assets/themes/blue_streak_light.conf index 9f52d1a6..3d9ff76b 100644 --- a/novelwriter/assets/themes/blue_streak_light.conf +++ b/novelwriter/assets/themes/blue_streak_light.conf @@ -59,6 +59,7 @@ link = blue headertext = blue headertag = blue:D150 emphasis = blue:L125 +whitespace = blue:64 dialog = blue altdialog = blue:L125 note = blue:L125 diff --git a/novelwriter/assets/themes/castle_day.conf b/novelwriter/assets/themes/castle_day.conf index ab57dcd8..bd5dbdd0 100644 --- a/novelwriter/assets/themes/castle_day.conf +++ b/novelwriter/assets/themes/castle_day.conf @@ -57,6 +57,7 @@ link = blue headertext = yellow headertag = yellow:160 emphasis = #aa791e +whitespace = #aa791e64 dialog = green:D110 altdialog = #378b8b note = faded diff --git a/novelwriter/assets/themes/castle_night.conf b/novelwriter/assets/themes/castle_night.conf index c4245236..c7eb49c1 100644 --- a/novelwriter/assets/themes/castle_night.conf +++ b/novelwriter/assets/themes/castle_night.conf @@ -57,6 +57,7 @@ link = blue headertext = yellow headertag = yellow:160 emphasis = yellow +whitespace = yellow:64 dialog = green altdialog = cyan note = faded diff --git a/novelwriter/assets/themes/chalky_soil.conf b/novelwriter/assets/themes/chalky_soil.conf index 5e90b992..7f52150b 100644 --- a/novelwriter/assets/themes/chalky_soil.conf +++ b/novelwriter/assets/themes/chalky_soil.conf @@ -57,6 +57,7 @@ link = blue headertext = red headertag = red:128 emphasis = #488843 +whitespace = #48884364 dialog = #b95a00 altdialog = #b18010 note = faded diff --git a/novelwriter/assets/themes/chernozem.conf b/novelwriter/assets/themes/chernozem.conf index 56f80f66..93b1b258 100644 --- a/novelwriter/assets/themes/chernozem.conf +++ b/novelwriter/assets/themes/chernozem.conf @@ -57,6 +57,7 @@ link = blue headertext = red headertag = red:128 emphasis = #85c47f +whitespace = #85c47f64 dialog = orange altdialog = yellow note = faded diff --git a/novelwriter/assets/themes/cyberpunk_night.conf b/novelwriter/assets/themes/cyberpunk_night.conf index d433dabe..55641eae 100644 --- a/novelwriter/assets/themes/cyberpunk_night.conf +++ b/novelwriter/assets/themes/cyberpunk_night.conf @@ -58,6 +58,7 @@ link = blue headertext = #ffffff headertag = purple emphasis = cyan +whitespace = cyan:64 dialog = green altdialog = #008cff note = #969696 diff --git a/novelwriter/assets/themes/default_dark.conf b/novelwriter/assets/themes/default_dark.conf index 1f7359bb..985e7595 100644 --- a/novelwriter/assets/themes/default_dark.conf +++ b/novelwriter/assets/themes/default_dark.conf @@ -59,6 +59,7 @@ link = blue headertext = green headertag = green:D150 emphasis = orange +whitespace = orange:64 dialog = blue altdialog = red note = yellow diff --git a/novelwriter/assets/themes/default_light.conf b/novelwriter/assets/themes/default_light.conf index da7784f5..f141bd71 100644 --- a/novelwriter/assets/themes/default_light.conf +++ b/novelwriter/assets/themes/default_light.conf @@ -59,6 +59,7 @@ link = blue headertext = green headertag = green:L135 emphasis = orange +whitespace = orange:64 dialog = blue altdialog = red note = yellow:D125 diff --git a/novelwriter/assets/themes/dracula.conf b/novelwriter/assets/themes/dracula.conf index 04fe9949..f10c9484 100644 --- a/novelwriter/assets/themes/dracula.conf +++ b/novelwriter/assets/themes/dracula.conf @@ -75,6 +75,7 @@ link = #ff79c6 headertext = purple headertag = purple:D150 emphasis = orange +whitespace = orange:64 dialog = green altdialog = yellow note = #ffcce9 diff --git a/novelwriter/assets/themes/espresso.conf b/novelwriter/assets/themes/espresso.conf index 3d222896..85c4d168 100644 --- a/novelwriter/assets/themes/espresso.conf +++ b/novelwriter/assets/themes/espresso.conf @@ -59,6 +59,7 @@ link = blue headertext = yellow:L125 headertag = faded emphasis = orange +whitespace = orange:64 dialog = yellow altdialog = orange note = yellow:L125 diff --git a/novelwriter/assets/themes/everforest_dark.conf b/novelwriter/assets/themes/everforest_dark.conf index ff10cf93..b6f7d962 100644 --- a/novelwriter/assets/themes/everforest_dark.conf +++ b/novelwriter/assets/themes/everforest_dark.conf @@ -59,6 +59,7 @@ link = blue headertext = cyan headertag = cyan:128 emphasis = blue +whitespace = blue:64 dialog = green altdialog = cyan note = purple diff --git a/novelwriter/assets/themes/everforest_light.conf b/novelwriter/assets/themes/everforest_light.conf index 901379ed..988e2f93 100644 --- a/novelwriter/assets/themes/everforest_light.conf +++ b/novelwriter/assets/themes/everforest_light.conf @@ -59,6 +59,7 @@ link = blue headertext = cyan headertag = cyan:128 emphasis = blue +whitespace = blue:64 dialog = green altdialog = cyan note = purple diff --git a/novelwriter/assets/themes/floral_daydream.conf b/novelwriter/assets/themes/floral_daydream.conf index ecee703f..5c0b7bb7 100644 --- a/novelwriter/assets/themes/floral_daydream.conf +++ b/novelwriter/assets/themes/floral_daydream.conf @@ -57,6 +57,7 @@ link = #4781d8 headertext = #4781d8 headertag = #4781d880 emphasis = #8152b8 +whitespace = #8152b864 dialog = #eb4073 altdialog = #4781d8 note = #31924c diff --git a/novelwriter/assets/themes/floral_midnight.conf b/novelwriter/assets/themes/floral_midnight.conf index c05bb27d..eaa88fde 100644 --- a/novelwriter/assets/themes/floral_midnight.conf +++ b/novelwriter/assets/themes/floral_midnight.conf @@ -57,6 +57,7 @@ link = blue headertext = blue headertag = blue:128 emphasis = #ff9dd9 +whitespace = #ff9dd964 dialog = #d19df3 altdialog = blue note = #65ca80 diff --git a/novelwriter/assets/themes/full_moon.conf b/novelwriter/assets/themes/full_moon.conf index c6e2c864..6bb73de9 100644 --- a/novelwriter/assets/themes/full_moon.conf +++ b/novelwriter/assets/themes/full_moon.conf @@ -57,6 +57,7 @@ link = blue headertext = purple headertag = purple:128 emphasis = blue +whitespace = blue:64 dialog = #0e2a35 altdialog = cyan note = green diff --git a/novelwriter/assets/themes/grey_dark.conf b/novelwriter/assets/themes/grey_dark.conf index 665c7a1f..8bb0d1f2 100644 --- a/novelwriter/assets/themes/grey_dark.conf +++ b/novelwriter/assets/themes/grey_dark.conf @@ -59,6 +59,7 @@ link = default headertext = default:L115 headertag = default:D125 emphasis = default +whitespace = default:64 dialog = default altdialog = default note = default diff --git a/novelwriter/assets/themes/grey_light.conf b/novelwriter/assets/themes/grey_light.conf index 2106e5bf..35e7be54 100644 --- a/novelwriter/assets/themes/grey_light.conf +++ b/novelwriter/assets/themes/grey_light.conf @@ -59,6 +59,7 @@ link = default headertext = default:D200 headertag = default:L400 emphasis = default +whitespace = default:64 dialog = default altdialog = default note = default diff --git a/novelwriter/assets/themes/horizon_dark.conf b/novelwriter/assets/themes/horizon_dark.conf index 085e8c6d..6e446db2 100644 --- a/novelwriter/assets/themes/horizon_dark.conf +++ b/novelwriter/assets/themes/horizon_dark.conf @@ -59,6 +59,7 @@ link = blue headertext = red headertag = red:128 emphasis = yellow +whitespace = yellow:64 dialog = orange altdialog = red note = blue diff --git a/novelwriter/assets/themes/horizon_light.conf b/novelwriter/assets/themes/horizon_light.conf index cee0a7f1..50bd40bd 100644 --- a/novelwriter/assets/themes/horizon_light.conf +++ b/novelwriter/assets/themes/horizon_light.conf @@ -59,6 +59,7 @@ link = blue headertext = red headertag = red:128 emphasis = #f6661e +whitespace = #f6661e64 dialog = orange altdialog = red note = blue diff --git a/novelwriter/assets/themes/lcars.conf b/novelwriter/assets/themes/lcars.conf index ee553602..6d5956f6 100644 --- a/novelwriter/assets/themes/lcars.conf +++ b/novelwriter/assets/themes/lcars.conf @@ -59,6 +59,7 @@ link = purple headertext = orange headertag = red emphasis = orange +whitespace = orange:64 dialog = yellow altdialog = green note = purple diff --git a/novelwriter/assets/themes/light_owl.conf b/novelwriter/assets/themes/light_owl.conf index 6a43afc0..c3e338a1 100644 --- a/novelwriter/assets/themes/light_owl.conf +++ b/novelwriter/assets/themes/light_owl.conf @@ -79,6 +79,7 @@ link = blue headertext = blue headertag = blue:160 emphasis = orange +whitespace = orange:64 dialog = green altdialog = yellow note = yellow:D175 diff --git a/novelwriter/assets/themes/new_moon.conf b/novelwriter/assets/themes/new_moon.conf index 4bbb2e58..79018122 100644 --- a/novelwriter/assets/themes/new_moon.conf +++ b/novelwriter/assets/themes/new_moon.conf @@ -59,6 +59,7 @@ link = blue headertext = purple headertag = purple:128 emphasis = blue +whitespace = blue:64 dialog = #ffffff altdialog = cyan note = green diff --git a/novelwriter/assets/themes/night_owl.conf b/novelwriter/assets/themes/night_owl.conf index 716f7eb8..93e3c105 100644 --- a/novelwriter/assets/themes/night_owl.conf +++ b/novelwriter/assets/themes/night_owl.conf @@ -79,6 +79,7 @@ link = blue headertext = blue headertag = blue:160 emphasis = orange +whitespace = orange:64 dialog = green altdialog = yellow note = yellow:L115 diff --git a/novelwriter/assets/themes/noctis.conf b/novelwriter/assets/themes/noctis.conf index 235bb5dc..e1489328 100644 --- a/novelwriter/assets/themes/noctis.conf +++ b/novelwriter/assets/themes/noctis.conf @@ -91,6 +91,7 @@ link = #40d4e7 headertext = #49e9a6 headertag = green:D125 emphasis = #d67e5c +whitespace = #d67e5c64 dialog = green altdialog = blue note = #d67e5c diff --git a/novelwriter/assets/themes/noctis_lux.conf b/novelwriter/assets/themes/noctis_lux.conf index fa37b293..c6e22bf5 100644 --- a/novelwriter/assets/themes/noctis_lux.conf +++ b/novelwriter/assets/themes/noctis_lux.conf @@ -91,6 +91,7 @@ link = #00c6e0 headertext = #00b368 headertag = green:D125 emphasis = #b3694d +whitespace = #b3694d64 dialog = green altdialog = blue note = #b3694d diff --git a/novelwriter/assets/themes/nord.conf b/novelwriter/assets/themes/nord.conf index e2a2faf9..aeaeb30e 100644 --- a/novelwriter/assets/themes/nord.conf +++ b/novelwriter/assets/themes/nord.conf @@ -59,6 +59,7 @@ link = purple headertext = cyan headertag = cyan:128 emphasis = #8fbcbb +whitespace = #8fbcbb64 dialog = blue altdialog = green note = orange diff --git a/novelwriter/assets/themes/nordlicht.conf b/novelwriter/assets/themes/nordlicht.conf index f8d9a846..ccfde60a 100644 --- a/novelwriter/assets/themes/nordlicht.conf +++ b/novelwriter/assets/themes/nordlicht.conf @@ -57,6 +57,7 @@ link = purple headertext = #559db1 headertag = #559db188 emphasis = #549b86 +whitespace = #549b8664 dialog = #5579a5 altdialog = #619b5b note = orange diff --git a/novelwriter/assets/themes/otium_dark.conf b/novelwriter/assets/themes/otium_dark.conf index 7319180a..5d9401e3 100644 --- a/novelwriter/assets/themes/otium_dark.conf +++ b/novelwriter/assets/themes/otium_dark.conf @@ -57,6 +57,7 @@ link = #a6d0ed headertext = yellow headertag = yellow:128 emphasis = yellow +whitespace = yellow:64 dialog = green altdialog = orange note = #a6d0ed diff --git a/novelwriter/assets/themes/otium_light.conf b/novelwriter/assets/themes/otium_light.conf index 062c7879..c3084ed7 100644 --- a/novelwriter/assets/themes/otium_light.conf +++ b/novelwriter/assets/themes/otium_light.conf @@ -57,6 +57,7 @@ link = blue headertext = yellow headertag = yellow:128 emphasis = #ad802a +whitespace = #ad802a64 dialog = green altdialog = #c0652d note = #668bbd diff --git a/novelwriter/assets/themes/paragon.conf b/novelwriter/assets/themes/paragon.conf index 7b05bee4..f6a0cc6f 100644 --- a/novelwriter/assets/themes/paragon.conf +++ b/novelwriter/assets/themes/paragon.conf @@ -58,6 +58,7 @@ link = green headertext = green headertag = green:128 emphasis = #99bbff +whitespace = #99bbff64 dialog = cyan altdialog = green note = #c7b377 diff --git a/novelwriter/assets/themes/primer_light.conf b/novelwriter/assets/themes/primer_light.conf index 49850873..97262d06 100644 --- a/novelwriter/assets/themes/primer_light.conf +++ b/novelwriter/assets/themes/primer_light.conf @@ -59,6 +59,7 @@ link = blue headertext = default headertag = #9ea7b0 emphasis = #1a7f37 +whitespace = #1a7f3764 dialog = #0d1117 altdialog = cyan note = #bf8700 diff --git a/novelwriter/assets/themes/primer_night.conf b/novelwriter/assets/themes/primer_night.conf index 2b4e4156..e25f06c0 100644 --- a/novelwriter/assets/themes/primer_night.conf +++ b/novelwriter/assets/themes/primer_night.conf @@ -59,6 +59,7 @@ link = blue headertext = default headertag = #b1bac4 emphasis = #7ee787 +whitespace = #7ee78764 dialog = #ffffff altdialog = #57ccc5 note = #f8e3a1 diff --git a/novelwriter/assets/themes/ruby_day.conf b/novelwriter/assets/themes/ruby_day.conf index 5a56e472..d01aa090 100644 --- a/novelwriter/assets/themes/ruby_day.conf +++ b/novelwriter/assets/themes/ruby_day.conf @@ -57,6 +57,7 @@ link = #5159cc headertext = #343242 headertag = #34324280 emphasis = #c05858 +whitespace = #c0585864 dialog = #ce1d1d altdialog = #c45522 note = #9f58ad diff --git a/novelwriter/assets/themes/ruby_night.conf b/novelwriter/assets/themes/ruby_night.conf index 37f60ac6..9c6d988c 100644 --- a/novelwriter/assets/themes/ruby_night.conf +++ b/novelwriter/assets/themes/ruby_night.conf @@ -57,6 +57,7 @@ link = blue headertext = default headertag = default:128 emphasis = #ff9d9d +whitespace = #ff9d9d64 dialog = red altdialog = orange note = purple @@ -70,4 +71,4 @@ spellcheckline = #ff2727 errorline = cyan replacetag = blue modifier = green -texthighlight = red:80 \ No newline at end of file +texthighlight = red:80 diff --git a/novelwriter/assets/themes/selenium_dark.conf b/novelwriter/assets/themes/selenium_dark.conf index a5c5cc1b..e19e525e 100644 --- a/novelwriter/assets/themes/selenium_dark.conf +++ b/novelwriter/assets/themes/selenium_dark.conf @@ -57,6 +57,7 @@ link = blue headertext = #e4e0ec headertag = faded emphasis = #9267d3 +whitespace = #9267d364 dialog = #e4e0ec altdialog = #c458ac note = red diff --git a/novelwriter/assets/themes/selenium_light.conf b/novelwriter/assets/themes/selenium_light.conf index d1eefa1f..7b6fe9b5 100644 --- a/novelwriter/assets/themes/selenium_light.conf +++ b/novelwriter/assets/themes/selenium_light.conf @@ -57,6 +57,7 @@ link = blue headertext = #29252f headertag = faded emphasis = purple +whitespace = purple:64 dialog = #29252f altdialog = #c458ac note = red diff --git a/novelwriter/assets/themes/sepia_dark.conf b/novelwriter/assets/themes/sepia_dark.conf index 49deeedd..358728f5 100644 --- a/novelwriter/assets/themes/sepia_dark.conf +++ b/novelwriter/assets/themes/sepia_dark.conf @@ -57,6 +57,7 @@ link = cyan headertext = default headertag = default:160 emphasis = #f1daca +whitespace = #f1daca64 dialog = orange altdialog = red note = green diff --git a/novelwriter/assets/themes/sepia_light.conf b/novelwriter/assets/themes/sepia_light.conf index a7c1d306..8c85e2e0 100644 --- a/novelwriter/assets/themes/sepia_light.conf +++ b/novelwriter/assets/themes/sepia_light.conf @@ -57,6 +57,7 @@ link = #458871 headertext = default headertag = default:160 emphasis = #6e2920 +whitespace = #6e292064 dialog = #ac5828 altdialog = #b44b4b note = #6e8021 diff --git a/novelwriter/assets/themes/snazzy.conf b/novelwriter/assets/themes/snazzy.conf index 0c36d884..68c270fb 100644 --- a/novelwriter/assets/themes/snazzy.conf +++ b/novelwriter/assets/themes/snazzy.conf @@ -72,6 +72,7 @@ link = blue headertext = green headertag = green:D125 emphasis = purple +whitespace = purple:64 dialog = blue altdialog = yellow note = cyan diff --git a/novelwriter/assets/themes/solarized_dark.conf b/novelwriter/assets/themes/solarized_dark.conf index 4c73412f..e9292dbd 100644 --- a/novelwriter/assets/themes/solarized_dark.conf +++ b/novelwriter/assets/themes/solarized_dark.conf @@ -78,6 +78,7 @@ link = blue headertext = blue headertag = #657b83 emphasis = blue +whitespace = blue:64 dialog = cyan altdialog = red note = cyan:D125 diff --git a/novelwriter/assets/themes/solarized_light.conf b/novelwriter/assets/themes/solarized_light.conf index 61d701b7..bc8abcfd 100644 --- a/novelwriter/assets/themes/solarized_light.conf +++ b/novelwriter/assets/themes/solarized_light.conf @@ -78,6 +78,7 @@ link = blue headertext = blue headertag = #657b83 emphasis = blue +whitespace = blue:64 dialog = cyan altdialog = red note = cyan:D125 diff --git a/novelwriter/assets/themes/sultana_light.conf b/novelwriter/assets/themes/sultana_light.conf index 1f4e5195..1a29f079 100644 --- a/novelwriter/assets/themes/sultana_light.conf +++ b/novelwriter/assets/themes/sultana_light.conf @@ -57,6 +57,7 @@ link = #5577bb headertext = #66515e headertag = #66515e80 emphasis = #5577bb +whitespace = #5577bb64 dialog = #c54a5f altdialog = #4d968a note = #699128 diff --git a/novelwriter/assets/themes/sultana_night.conf b/novelwriter/assets/themes/sultana_night.conf index bd55dd84..a952c87e 100644 --- a/novelwriter/assets/themes/sultana_night.conf +++ b/novelwriter/assets/themes/sultana_night.conf @@ -57,6 +57,7 @@ link = blue headertext = #f3eae0 headertag = #f3eae080 emphasis = blue +whitespace = blue:64 dialog = red altdialog = cyan note = green diff --git a/novelwriter/assets/themes/tango_dark.conf b/novelwriter/assets/themes/tango_dark.conf index 8661ce27..0e5707cb 100644 --- a/novelwriter/assets/themes/tango_dark.conf +++ b/novelwriter/assets/themes/tango_dark.conf @@ -73,6 +73,7 @@ link = blue headertext = blue headertag = blue:160 emphasis = orange +whitespace = orange:64 dialog = green altdialog = yellow note = yellow:L115 diff --git a/novelwriter/assets/themes/tango_light.conf b/novelwriter/assets/themes/tango_light.conf index 379b0a72..efbcaf20 100644 --- a/novelwriter/assets/themes/tango_light.conf +++ b/novelwriter/assets/themes/tango_light.conf @@ -73,6 +73,7 @@ link = blue headertext = blue headertag = blue:160 emphasis = orange +whitespace = orange:64 dialog = green altdialog = yellow note = yellow:D125 diff --git a/novelwriter/assets/themes/tomorrow.conf b/novelwriter/assets/themes/tomorrow.conf index 15abcd32..70b123ee 100644 --- a/novelwriter/assets/themes/tomorrow.conf +++ b/novelwriter/assets/themes/tomorrow.conf @@ -79,6 +79,7 @@ link = blue headertext = blue headertag = blue:L135 emphasis = orange +whitespace = orange:64 dialog = green altdialog = yellow note = #b38c00 diff --git a/novelwriter/assets/themes/tomorrow_night.conf b/novelwriter/assets/themes/tomorrow_night.conf index ac213a76..9bd4fbea 100644 --- a/novelwriter/assets/themes/tomorrow_night.conf +++ b/novelwriter/assets/themes/tomorrow_night.conf @@ -79,6 +79,7 @@ link = blue headertext = blue headertag = blue:D150 emphasis = orange +whitespace = orange:64 dialog = green altdialog = yellow note = #f0dbb2 diff --git a/novelwriter/assets/themes/tomorrow_night_blue.conf b/novelwriter/assets/themes/tomorrow_night_blue.conf index 05193e6f..409cfd25 100644 --- a/novelwriter/assets/themes/tomorrow_night_blue.conf +++ b/novelwriter/assets/themes/tomorrow_night_blue.conf @@ -79,6 +79,7 @@ link = blue headertext = blue headertag = blue:D150 emphasis = orange +whitespace = orange:64 dialog = green altdialog = yellow note = #fff4cc diff --git a/novelwriter/assets/themes/tomorrow_night_bright.conf b/novelwriter/assets/themes/tomorrow_night_bright.conf index 514940fa..14a1a4d6 100644 --- a/novelwriter/assets/themes/tomorrow_night_bright.conf +++ b/novelwriter/assets/themes/tomorrow_night_bright.conf @@ -79,6 +79,7 @@ link = blue headertext = blue headertag = blue:D150 emphasis = orange +whitespace = orange:64 dialog = green altdialog = yellow note = #e7d696 diff --git a/novelwriter/assets/themes/tomorrow_night_eighties.conf b/novelwriter/assets/themes/tomorrow_night_eighties.conf index 154b6ef4..af7190fc 100644 --- a/novelwriter/assets/themes/tomorrow_night_eighties.conf +++ b/novelwriter/assets/themes/tomorrow_night_eighties.conf @@ -79,6 +79,7 @@ link = blue headertext = blue headertag = blue:D150 emphasis = orange +whitespace = orange:64 dialog = green altdialog = yellow note = #ffe6b3 diff --git a/novelwriter/assets/themes/vivid_black_green.conf b/novelwriter/assets/themes/vivid_black_green.conf index f2d17043..2c0101e3 100644 --- a/novelwriter/assets/themes/vivid_black_green.conf +++ b/novelwriter/assets/themes/vivid_black_green.conf @@ -59,6 +59,7 @@ link = blue headertext = cyan headertag = cyan:128 emphasis = blue +whitespace = blue:64 dialog = green altdialog = cyan note = red diff --git a/novelwriter/assets/themes/vivid_black_red.conf b/novelwriter/assets/themes/vivid_black_red.conf index fd7d99f2..c7f3f6b8 100644 --- a/novelwriter/assets/themes/vivid_black_red.conf +++ b/novelwriter/assets/themes/vivid_black_red.conf @@ -59,6 +59,7 @@ link = blue headertext = yellow headertag = yellow:128 emphasis = orange +whitespace = orange:64 dialog = red altdialog = yellow note = green @@ -72,4 +73,4 @@ spellcheckline = #ff3737 errorline = cyan replacetag = #ff61e5 modifier = cyan -texthighlight = red:80 \ No newline at end of file +texthighlight = red:80 diff --git a/novelwriter/assets/themes/vivid_white_green.conf b/novelwriter/assets/themes/vivid_white_green.conf index 7c299bdc..5d421a2a 100644 --- a/novelwriter/assets/themes/vivid_white_green.conf +++ b/novelwriter/assets/themes/vivid_white_green.conf @@ -59,6 +59,7 @@ link = blue headertext = cyan headertag = cyan:128 emphasis = blue +whitespace = blue:64 dialog = green altdialog = cyan note = red diff --git a/novelwriter/assets/themes/vivid_white_red.conf b/novelwriter/assets/themes/vivid_white_red.conf index e77b42b3..dc40bfbe 100644 --- a/novelwriter/assets/themes/vivid_white_red.conf +++ b/novelwriter/assets/themes/vivid_white_red.conf @@ -59,6 +59,7 @@ link = blue headertext = yellow headertag = yellow:128 emphasis = orange +whitespace = orange:64 dialog = red altdialog = #a37e03 note = green diff --git a/novelwriter/assets/themes/warpgate.conf b/novelwriter/assets/themes/warpgate.conf index 1398732f..2cb0108f 100644 --- a/novelwriter/assets/themes/warpgate.conf +++ b/novelwriter/assets/themes/warpgate.conf @@ -58,6 +58,7 @@ link = green headertext = green headertag = green:128 emphasis = #fff6aa +whitespace = #fff6aa64 dialog = cyan altdialog = #11bdc6 note = green diff --git a/novelwriter/assets/themes/waterlily_dark.conf b/novelwriter/assets/themes/waterlily_dark.conf index 92395b9c..46b356bf 100644 --- a/novelwriter/assets/themes/waterlily_dark.conf +++ b/novelwriter/assets/themes/waterlily_dark.conf @@ -57,6 +57,7 @@ link = blue headertext = default headertag = faded emphasis = green +whitespace = green:64 dialog = #ff9fbd altdialog = orange note = blue diff --git a/novelwriter/assets/themes/waterlily_light.conf b/novelwriter/assets/themes/waterlily_light.conf index 960baa67..b86d4f2c 100644 --- a/novelwriter/assets/themes/waterlily_light.conf +++ b/novelwriter/assets/themes/waterlily_light.conf @@ -57,6 +57,7 @@ link = #0b9caf headertext = #526b5d headertag = #526b5d80 emphasis = #1e920e +whitespace = #1e920e64 dialog = #d15574 altdialog = #d86f3e note = #0b9caf From ba01a2a2192bf6d060682083c06c047575c5ff46 Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Sun, 26 Oct 2025 12:46:01 +0100 Subject: [PATCH 72/79] Update documentation --- docs/source/more/customise.rst | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/docs/source/more/customise.rst b/docs/source/more/customise.rst index 89a74ba0..ba016d30 100644 --- a/docs/source/more/customise.rst +++ b/docs/source/more/customise.rst @@ -109,6 +109,7 @@ A colour theme ``.conf`` file consists of the following settings: headertext = green headertag = green:L135 emphasis = orange + whitespace = orange:64 dialog = blue altdialog = red note = yellow:D125 @@ -173,9 +174,10 @@ There are several ways to enter colour values: .. versionadded:: 2.8 The ``[Syntax]`` section was moved into the main theme file. Previously, these settings were in - their own file. The ``[Icons]`` section was renamed to ``[Base]``. Added the ``line`` setting. - Dropped the ``license``, ``licenseurl``, and ``description`` settings. The ``author`` field - is now required if the theme is included in the app, but not for user themes. + their own file. The ``[Icons]`` section was renamed to ``[Base]``. Added the ``line`` and + ``whitespace`` settings. Dropped the ``license``, ``licenseurl``, and ``description`` settings. + The ``author`` field is now required if the theme is included in the app, but not for user + themes. Icon Themes From 6db67e909d66e93d2710f00dd737ab549e0022d5 Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Sun, 26 Oct 2025 14:02:18 +0100 Subject: [PATCH 73/79] Add icon colour settings to themes and enforce them on all GUI icons --- novelwriter/core/novelmodel.py | 2 +- novelwriter/dialogs/preferences.py | 20 ++++---- novelwriter/dialogs/projectsettings.py | 16 +++---- novelwriter/dialogs/wordlist.py | 8 ++-- novelwriter/extensions/modified.py | 19 ++++---- novelwriter/extensions/novelselector.py | 2 +- novelwriter/gui/doceditor.py | 54 ++++++++++----------- novelwriter/gui/docviewer.py | 16 +++---- novelwriter/gui/docviewerpanel.py | 14 +++--- novelwriter/gui/noveltree.py | 6 +-- novelwriter/gui/outline.py | 6 +-- novelwriter/gui/projtree.py | 14 +++--- novelwriter/gui/search.py | 10 ++-- novelwriter/gui/sidebar.py | 18 +++---- novelwriter/gui/theme.py | 62 +++++++++++++++++-------- novelwriter/shared.py | 8 ++-- novelwriter/tools/dictionaries.py | 6 +-- novelwriter/tools/lipsum.py | 2 +- novelwriter/tools/manusbuild.py | 20 ++++---- novelwriter/tools/manuscript.py | 16 +++---- novelwriter/tools/manussettings.py | 43 +++++++++-------- novelwriter/tools/welcome.py | 12 ++--- novelwriter/tools/writingstats.py | 2 +- tests/test_ext/test_ext_modified.py | 6 +-- tests/test_gui/test_gui_theme.py | 21 +++++---- 25 files changed, 216 insertions(+), 187 deletions(-) diff --git a/novelwriter/core/novelmodel.py b/novelwriter/core/novelmodel.py index 7b1cd426..6418b1bc 100644 --- a/novelwriter/core/novelmodel.py +++ b/novelwriter/core/novelmodel.py @@ -61,7 +61,7 @@ class NovelModel(QAbstractTableModel): def __init__(self) -> None: super().__init__() self._rows: list[dict[int, T_NodeData]] = [] - self._more = SHARED.theme.getIcon("more_arrow") + self._more = SHARED.theme.getIcon("more_arrow", "tool") self._columns = 3 self._extraKey = "" self._extraLabel = "" diff --git a/novelwriter/dialogs/preferences.py b/novelwriter/dialogs/preferences.py index d36ca9ba..df976d21 100644 --- a/novelwriter/dialogs/preferences.py +++ b/novelwriter/dialogs/preferences.py @@ -71,7 +71,7 @@ class GuiPreferences(NDialog): ) # Search Box - self.searchAction = QAction(SHARED.theme.getIcon("search"), "") + self.searchAction = QAction(SHARED.theme.getIcon("search", "apply"), "") self.searchAction.triggered.connect(self._gotoSearch) self.searchText = QLineEdit(self) @@ -211,7 +211,7 @@ class GuiPreferences(NDialog): self.guiFont.setMinimumWidth(162) self.guiFont.setText(describeFont(self._guiFont)) self.guiFont.setCursorPosition(0) - self.guiFontButton = NIconToolButton(self, iSz, "font") + self.guiFontButton = NIconToolButton(self, iSz, "font", "tool") self.guiFontButton.clicked.connect(self._selectGuiFont) self.mainForm.addRow( self.tr("Application font"), self.guiFont, @@ -265,7 +265,7 @@ class GuiPreferences(NDialog): self.textFont.setMinimumWidth(162) self.textFont.setText(describeFont(CONFIG.textFont)) self.textFont.setCursorPosition(0) - self.textFontButton = NIconToolButton(self, iSz, "font") + self.textFontButton = NIconToolButton(self, iSz, "font", "tool") self.textFontButton.clicked.connect(self._selectTextFont) self.mainForm.addRow( self.tr("Document font"), self.textFont, @@ -373,7 +373,9 @@ class GuiPreferences(NDialog): # Backup Path self.backupPath = CONFIG.backupPath() - self.backupGetPath = QPushButton(SHARED.theme.getIcon("browse"), self.tr("Browse"), self) + self.backupGetPath = QPushButton( + SHARED.theme.getIcon("browse", "systemio"), self.tr("Browse"), self + ) self.backupGetPath.setIconSize(iSz) self.backupGetPath.clicked.connect(self._backupFolder) self.mainForm.addRow( @@ -665,7 +667,7 @@ class GuiPreferences(NDialog): self.dialogLine.setAlignment(QtAlignCenter) self.dialogLine.setText(" ".join(CONFIG.dialogLine)) - self.dialogLineButton = NIconToolButton(self, iSz, "add", "green") + self.dialogLineButton = NIconToolButton(self, iSz, "add", "add") self.dialogLineButton.setMenu(self.mnLineSymbols) self.mainForm.addRow( @@ -807,7 +809,7 @@ class GuiPreferences(NDialog): self.fmtSQuoteOpen.setFixedWidth(boxFixed) self.fmtSQuoteOpen.setAlignment(QtAlignCenter) self.fmtSQuoteOpen.setText(CONFIG.fmtSQuoteOpen) - self.btnSQuoteOpen = NIconToolButton(self, iSz, "quote") + self.btnSQuoteOpen = NIconToolButton(self, iSz, "quote", "tool") self.btnSQuoteOpen.clicked.connect(self._changeSingleQuoteOpen) self.mainForm.addRow( self.tr("Single quote open style"), self.fmtSQuoteOpen, @@ -821,7 +823,7 @@ class GuiPreferences(NDialog): self.fmtSQuoteClose.setFixedWidth(boxFixed) self.fmtSQuoteClose.setAlignment(QtAlignCenter) self.fmtSQuoteClose.setText(CONFIG.fmtSQuoteClose) - self.btnSQuoteClose = NIconToolButton(self, iSz, "quote") + self.btnSQuoteClose = NIconToolButton(self, iSz, "quote", "tool") self.btnSQuoteClose.clicked.connect(self._changeSingleQuoteClose) self.mainForm.addRow( self.tr("Single quote close style"), self.fmtSQuoteClose, @@ -836,7 +838,7 @@ class GuiPreferences(NDialog): self.fmtDQuoteOpen.setFixedWidth(boxFixed) self.fmtDQuoteOpen.setAlignment(QtAlignCenter) self.fmtDQuoteOpen.setText(CONFIG.fmtDQuoteOpen) - self.btnDQuoteOpen = NIconToolButton(self, iSz, "quote") + self.btnDQuoteOpen = NIconToolButton(self, iSz, "quote", "tool") self.btnDQuoteOpen.clicked.connect(self._changeDoubleQuoteOpen) self.mainForm.addRow( self.tr("Double quote open style"), self.fmtDQuoteOpen, @@ -850,7 +852,7 @@ class GuiPreferences(NDialog): self.fmtDQuoteClose.setFixedWidth(boxFixed) self.fmtDQuoteClose.setAlignment(QtAlignCenter) self.fmtDQuoteClose.setText(CONFIG.fmtDQuoteClose) - self.btnDQuoteClose = NIconToolButton(self, iSz, "quote") + self.btnDQuoteClose = NIconToolButton(self, iSz, "quote", "tool") self.btnDQuoteClose.clicked.connect(self._changeDoubleQuoteClose) self.mainForm.addRow( self.tr("Double quote close style"), self.fmtDQuoteClose, diff --git a/novelwriter/dialogs/projectsettings.py b/novelwriter/dialogs/projectsettings.py index 31af45a5..16a4a865 100644 --- a/novelwriter/dialogs/projectsettings.py +++ b/novelwriter/dialogs/projectsettings.py @@ -356,27 +356,27 @@ class _StatusPage(NFixedPage): self._addItem(key, StatusEntry.duplicate(entry)) # List Controls - self.addButton = NIconToolButton(self, iSz, "add", "green") + self.addButton = NIconToolButton(self, iSz, "add", "add") self.addButton.setToolTip(self.tr("Add Label")) self.addButton.clicked.connect(self._onItemCreate) - self.delButton = NIconToolButton(self, iSz, "remove", "red") + self.delButton = NIconToolButton(self, iSz, "remove", "remove") self.delButton.setToolTip(self.tr("Delete Label")) self.delButton.clicked.connect(self._onItemDelete) - self.upButton = NIconToolButton(self, iSz, "chevron_up", "blue") + self.upButton = NIconToolButton(self, iSz, "chevron_up", "action") self.upButton.setToolTip(self.tr("Move Up")) self.upButton.clicked.connect(qtLambda(self._moveItem, -1)) - self.downButton = NIconToolButton(self, iSz, "chevron_down", "blue") + self.downButton = NIconToolButton(self, iSz, "chevron_down", "action") self.downButton.setToolTip(self.tr("Move Down")) self.downButton.clicked.connect(qtLambda(self._moveItem, 1)) - self.importButton = NIconToolButton(self, iSz, "import", "green") + self.importButton = NIconToolButton(self, iSz, "import", "apply") self.importButton.setToolTip(self.tr("Import Labels")) self.importButton.clicked.connect(self._importLabels) - self.exportButton = NIconToolButton(self, iSz, "export", "blue") + self.exportButton = NIconToolButton(self, iSz, "export", "action") self.exportButton.setToolTip(self.tr("Export Labels")) self.exportButton.clicked.connect(self._exportLabels) @@ -729,10 +729,10 @@ class _ReplacePage(NFixedPage): self.listBox.setSortingEnabled(True) # List Controls - self.addButton = NIconToolButton(self, iSz, "add", "green") + self.addButton = NIconToolButton(self, iSz, "add", "add") self.addButton.clicked.connect(self._onEntryCreated) - self.delButton = NIconToolButton(self, iSz, "remove", "red") + self.delButton = NIconToolButton(self, iSz, "remove", "remove") self.delButton.clicked.connect(self._onEntryDeleted) # Edit Form diff --git a/novelwriter/dialogs/wordlist.py b/novelwriter/dialogs/wordlist.py index a2f60b91..927d6f21 100644 --- a/novelwriter/dialogs/wordlist.py +++ b/novelwriter/dialogs/wordlist.py @@ -75,11 +75,11 @@ class GuiWordList(NDialog): scale=NColorLabel.HEADER_SCALE ) - self.importButton = NIconToolButton(self, iSz, "import", "green") + self.importButton = NIconToolButton(self, iSz, "import", "apply") self.importButton.setToolTip(self.tr("Import words from text file")) self.importButton.clicked.connect(self._importWords) - self.exportButton = NIconToolButton(self, iSz, "export", "blue") + self.exportButton = NIconToolButton(self, iSz, "export", "action") self.exportButton.setToolTip(self.tr("Export words to text file")) self.exportButton.clicked.connect(self._exportWords) @@ -97,11 +97,11 @@ class GuiWordList(NDialog): # Add/Remove Form self.newEntry = QLineEdit(self) - self.addButton = NIconToolButton(self, iSz, "add", "green") + self.addButton = NIconToolButton(self, iSz, "add", "add") self.addButton.setToolTip(self.tr("Add Word")) self.addButton.clicked.connect(self._doAdd) - self.delButton = NIconToolButton(self, iSz, "remove", "red") + self.delButton = NIconToolButton(self, iSz, "remove", "remove") self.delButton.setToolTip(self.tr("Remove Word")) self.delButton.clicked.connect(self._doDelete) diff --git a/novelwriter/extensions/modified.py b/novelwriter/extensions/modified.py index 054665ce..d6a01b44 100644 --- a/novelwriter/extensions/modified.py +++ b/novelwriter/extensions/modified.py @@ -220,7 +220,7 @@ class NPushButton(QPushButton): def updateIcon(self) -> None: """Update the theme icon.""" - if self._icon: + if self._icon and self._color: self.setIcon(SHARED.theme.getIcon(self._icon, self._color)) @@ -238,10 +238,10 @@ class NIconToolButton(QToolButton): self.setToolButtonStyle(Qt.ToolButtonStyle.ToolButtonIconOnly) self.setIconSize(iconSize) self.setPopupMode(QToolButton.ToolButtonPopupMode.InstantPopup) - if icon: + if icon and color: self.setThemeIcon(icon, color) - def setThemeIcon(self, icon: str, color: str | None = None) -> None: + def setThemeIcon(self, icon: str, color: str) -> None: """Set an icon from the current theme.""" self.setIcon(SHARED.theme.getIcon(icon, color)) @@ -252,20 +252,23 @@ class NIconToggleButton(QToolButton): A quicker way to create a toggle button using the app theme. """ - def __init__(self, parent: QWidget, iconSize: QSize, icon: str | None = None) -> None: + def __init__( + self, parent: QWidget, iconSize: QSize, + icon: str | None = None, color: str | None = None + ) -> None: super().__init__(parent=parent) self.setToolButtonStyle(Qt.ToolButtonStyle.ToolButtonIconOnly) self.setIconSize(iconSize) self.setPopupMode(QToolButton.ToolButtonPopupMode.InstantPopup) self.setCheckable(True) self.setStyleSheet("border: none; background: transparent;") - if icon: - self.setThemeIcon(icon) + if icon and color: + self.setThemeIcon(icon, color) - def setThemeIcon(self, icon: str) -> None: + def setThemeIcon(self, icon: str, color: str) -> None: """Set an icon from the current theme.""" size = self.iconSize() - self.setIcon(SHARED.theme.getToggleIcon(icon, (size.width(), size.height()))) + self.setIcon(SHARED.theme.getToggleIcon(icon, (size.width(), size.height()), color)) class NClickableLabel(QLabel): diff --git a/novelwriter/extensions/novelselector.py b/novelwriter/extensions/novelselector.py index 27d217c6..64513378 100644 --- a/novelwriter/extensions/novelselector.py +++ b/novelwriter/extensions/novelselector.py @@ -110,7 +110,7 @@ class NovelSelector(QComboBox): self._firstHandle = None self.clear() - icon = SHARED.theme.getIcon(nwLabels.CLASS_ICON[nwItemClass.NOVEL], "blue") + icon = SHARED.theme.getIcon(nwLabels.CLASS_ICON[nwItemClass.NOVEL], "root") for tHandle, nwItem in SHARED.project.tree.iterRoots(nwItemClass.NOVEL): if self._listFormat: name = self._listFormat.format(nwItem.itemName) diff --git a/novelwriter/gui/doceditor.py b/novelwriter/gui/doceditor.py index d7a96765..fc05f063 100644 --- a/novelwriter/gui/doceditor.py +++ b/novelwriter/gui/doceditor.py @@ -2489,17 +2489,17 @@ class GuiDocToolBar(QWidget): palette.setColor(QPalette.ColorRole.Text, syntax.text) self.setPalette(palette) - self.tbBoldMD.setThemeIcon("fmt_bold", "orange") - self.tbItalicMD.setThemeIcon("fmt_italic", "orange") - self.tbStrikeMD.setThemeIcon("fmt_strike", "orange") - self.tbMarkMD.setThemeIcon("fmt_mark", "orange") - self.tbBold.setThemeIcon("fmt_bold") - self.tbItalic.setThemeIcon("fmt_italic") - self.tbStrike.setThemeIcon("fmt_strike") - self.tbUnderline.setThemeIcon("fmt_underline") - self.tbMark.setThemeIcon("fmt_mark") - self.tbSuperscript.setThemeIcon("fmt_superscript") - self.tbSubscript.setThemeIcon("fmt_subscript") + self.tbBoldMD.setThemeIcon("fmt_bold", "mdformat") + self.tbItalicMD.setThemeIcon("fmt_italic", "mdformat") + self.tbStrikeMD.setThemeIcon("fmt_strike", "mdformat") + self.tbMarkMD.setThemeIcon("fmt_mark", "mdformat") + self.tbBold.setThemeIcon("fmt_bold", "scformat") + self.tbItalic.setThemeIcon("fmt_italic", "scformat") + self.tbStrike.setThemeIcon("fmt_strike", "scformat") + self.tbUnderline.setThemeIcon("fmt_underline", "scformat") + self.tbMark.setThemeIcon("fmt_mark", "scformat") + self.tbSuperscript.setThemeIcon("fmt_superscript", "scformat") + self.tbSubscript.setThemeIcon("fmt_subscript", "scformat") class GuiDocEditSearch(QFrame): @@ -2727,16 +2727,16 @@ class GuiDocEditSearch(QFrame): self.replaceBox.setPalette(palette) # Set icons - self.toggleCase.setIcon(SHARED.theme.getIcon("search_case")) - self.toggleWord.setIcon(SHARED.theme.getIcon("search_word")) - self.toggleRegEx.setIcon(SHARED.theme.getIcon("search_regex")) - self.toggleLoop.setIcon(SHARED.theme.getIcon("search_loop")) - self.toggleProject.setIcon(SHARED.theme.getIcon("search_project")) - self.toggleMatchCap.setIcon(SHARED.theme.getIcon("search_preserve")) - self.cancelSearch.setIcon(SHARED.theme.getIcon("search_cancel")) - self.searchButton.setThemeIcon("search", "green") - self.replaceButton.setThemeIcon("search_replace", "green") - self.showReplace.setThemeIcon("unfold") + self.toggleCase.setIcon(SHARED.theme.getIcon("search_case", "tool")) + self.toggleWord.setIcon(SHARED.theme.getIcon("search_word", "tool")) + self.toggleRegEx.setIcon(SHARED.theme.getIcon("search_regex", "tool")) + self.toggleLoop.setIcon(SHARED.theme.getIcon("search_loop", "tool")) + self.toggleProject.setIcon(SHARED.theme.getIcon("search_project", "tool")) + self.toggleMatchCap.setIcon(SHARED.theme.getIcon("search_preserve", "tool")) + self.cancelSearch.setIcon(SHARED.theme.getIcon("search_cancel", "tool")) + self.searchButton.setThemeIcon("search", "action") + self.replaceButton.setThemeIcon("search_replace", "apply") + self.showReplace.setThemeIcon("unfold", "default") # Set stylesheets self.searchOpt.setStyleSheet("QToolBar {padding: 0;}") @@ -2967,11 +2967,11 @@ class GuiDocEditHeader(QWidget): """Update theme elements.""" logger.debug("Theme Update: GuiDocEditHeader") - self.tbButton.setThemeIcon("fmt_toolbar", "blue") - self.outlineButton.setThemeIcon("list", "blue") - self.searchButton.setThemeIcon("search", "blue") - self.minmaxButton.setThemeIcon("maximise", "blue") - self.closeButton.setThemeIcon("close", "red") + self.tbButton.setThemeIcon("fmt_toolbar", "action") + self.outlineButton.setThemeIcon("list", "action") + self.searchButton.setThemeIcon("search", "action") + self.minmaxButton.setThemeIcon("maximise", "action") + self.closeButton.setThemeIcon("close", "reject") buttonStyle = SHARED.theme.getStyleSheet(STYLES_MIN_TOOLBUTTON) self.tbButton.setStyleSheet(buttonStyle) @@ -3037,7 +3037,7 @@ class GuiDocEditHeader(QWidget): @pyqtSlot(bool) def _focusModeChanged(self, focusMode: bool) -> None: """Update minimise/maximise icon of the Focus Mode button.""" - self.minmaxButton.setThemeIcon("minimise" if focusMode else "maximise", "blue") + self.minmaxButton.setThemeIcon("minimise" if focusMode else "maximise", "action") ## # Events diff --git a/novelwriter/gui/docviewer.py b/novelwriter/gui/docviewer.py index 5cd44e27..b9dcb3d6 100644 --- a/novelwriter/gui/docviewer.py +++ b/novelwriter/gui/docviewer.py @@ -727,12 +727,12 @@ class GuiDocViewHeader(QWidget): """Update theme elements.""" logger.debug("Theme Update: GuiDocViewHeader") - self.outlineButton.setThemeIcon("list", "blue") - self.backButton.setThemeIcon("chevron_left", "blue") - self.forwardButton.setThemeIcon("chevron_right", "blue") - self.editButton.setThemeIcon("edit", "green") - self.refreshButton.setThemeIcon("refresh", "green") - self.closeButton.setThemeIcon("close", "red") + self.outlineButton.setThemeIcon("list", "action") + self.backButton.setThemeIcon("chevron_left", "action") + self.forwardButton.setThemeIcon("chevron_right", "action") + self.editButton.setThemeIcon("edit", "change") + self.refreshButton.setThemeIcon("refresh", "change") + self.closeButton.setThemeIcon("close", "reject") buttonStyle = SHARED.theme.getStyleSheet(STYLES_MIN_TOOLBUTTON) self.outlineButton.setStyleSheet(buttonStyle) @@ -916,9 +916,9 @@ class GuiDocViewFooter(QWidget): logger.debug("Theme Update: GuiDocViewFooter") fPx = int(0.9*SHARED.theme.fontPixelSize) - bulletIcon = SHARED.theme.getToggleIcon("bullet", (fPx, fPx), "blue") + bulletIcon = SHARED.theme.getToggleIcon("bullet", (fPx, fPx), "action") - self.showHide.setThemeIcon("panel") + self.showHide.setThemeIcon("panel", "default") self.showComments.setIcon(bulletIcon) self.showSynopsis.setIcon(bulletIcon) self.showNotes.setIcon(bulletIcon) diff --git a/novelwriter/gui/docviewerpanel.py b/novelwriter/gui/docviewerpanel.py index 9f3fd209..fed0e4a3 100644 --- a/novelwriter/gui/docviewerpanel.py +++ b/novelwriter/gui/docviewerpanel.py @@ -108,7 +108,7 @@ class GuiDocViewerPanel(QWidget): """Update theme elements.""" logger.debug("Theme Update: GuiDocViewerPanel") - self.optsButton.setThemeIcon("more_vertical") + self.optsButton.setThemeIcon("more_vertical", "default") self.optsButton.setStyleSheet(SHARED.theme.getStyleSheet(STYLES_MIN_TOOLBUTTON)) self.mainTabs.setStyleSheet(SHARED.theme.getStyleSheet(STYLES_FLAT_TABS)) if updateTabs: @@ -263,8 +263,8 @@ class _ViewPanelBackRefs(QTreeWidget): header.setSectionsMovable(False) # Cache Icons Locally - self._editIcon = SHARED.theme.getIcon("edit", "green") - self._viewIcon = SHARED.theme.getIcon("view", "blue") + self._editIcon = SHARED.theme.getIcon("edit", "change") + self._viewIcon = SHARED.theme.getIcon("view", "action") # Signals self.clicked.connect(self._treeItemClicked) @@ -274,8 +274,8 @@ class _ViewPanelBackRefs(QTreeWidget): """Update theme elements.""" logger.debug("Theme Update: _ViewPanelBackRefs") - self._editIcon = SHARED.theme.getIcon("edit", "green") - self._viewIcon = SHARED.theme.getIcon("view", "blue") + self._editIcon = SHARED.theme.getIcon("edit", "change") + self._viewIcon = SHARED.theme.getIcon("view", "action") for i in range(self.topLevelItemCount()): if item := self.topLevelItem(i): item.setIcon(self.C_EDIT, self._editIcon) @@ -407,8 +407,8 @@ class _ViewPanelKeyWords(QTreeWidget): logger.debug("Theme Update: _ViewPanelKeyWords") self._classIcon = SHARED.theme.getIcon(nwLabels.CLASS_ICON[self._class], "root") - self._editIcon = SHARED.theme.getIcon("edit", "green") - self._viewIcon = SHARED.theme.getIcon("view", "blue") + self._editIcon = SHARED.theme.getIcon("edit", "change") + self._viewIcon = SHARED.theme.getIcon("view", "action") def countEntries(self) -> int: """Return the number of items in the list.""" diff --git a/novelwriter/gui/noveltree.py b/novelwriter/gui/noveltree.py index feb5e703..25b9cb9e 100644 --- a/novelwriter/gui/noveltree.py +++ b/novelwriter/gui/noveltree.py @@ -247,9 +247,9 @@ class GuiNovelToolBar(QWidget): """Update theme elements.""" logger.debug("Theme Update: GuiNovelToolBar") - self.tbNovel.setThemeIcon("cls_novel", "red") - self.tbRefresh.setThemeIcon("refresh", "green") - self.tbMore.setThemeIcon("more_vertical") + self.tbNovel.setThemeIcon("cls_novel", "root") + self.tbRefresh.setThemeIcon("refresh", "change") + self.tbMore.setThemeIcon("more_vertical", "default") buttonStyle = SHARED.theme.getStyleSheet(STYLES_MIN_TOOLBUTTON) self.tbNovel.setStyleSheet(buttonStyle) diff --git a/novelwriter/gui/outline.py b/novelwriter/gui/outline.py index 3a97ec35..1bc185a7 100644 --- a/novelwriter/gui/outline.py +++ b/novelwriter/gui/outline.py @@ -264,9 +264,9 @@ class GuiOutlineToolBar(QToolBar): self.setStyleSheet("QToolBar {border: 0px;}") self.novelValue.refreshNovelList() - self.aRefresh.setIcon(SHARED.theme.getIcon("refresh", "green")) - self.aExport.setIcon(SHARED.theme.getIcon("export", "blue")) - self.tbColumns.setIcon(SHARED.theme.getIcon("more_vertical")) + self.aRefresh.setIcon(SHARED.theme.getIcon("refresh", "change")) + self.aExport.setIcon(SHARED.theme.getIcon("export", "action")) + self.tbColumns.setIcon(SHARED.theme.getIcon("more_vertical", "default")) self.tbColumns.setStyleSheet("QToolButton::menu-indicator {image: none;}") self.novelLabel.setTextColors(color=self.palette().windowText().color()) diff --git a/novelwriter/gui/projtree.py b/novelwriter/gui/projtree.py index 758b15cd..de427a1d 100644 --- a/novelwriter/gui/projtree.py +++ b/novelwriter/gui/projtree.py @@ -356,11 +356,11 @@ class GuiProjectToolBar(QWidget): self.tbAdd.setStyleSheet(buttonStyle) self.tbMore.setStyleSheet(buttonStyle) - self.tbQuick.setThemeIcon("bookmarks", "blue") - self.tbMoveU.setThemeIcon("chevron_up", "blue") - self.tbMoveD.setThemeIcon("chevron_down", "blue") - self.tbAdd.setThemeIcon("add", "green") - self.tbMore.setThemeIcon("more_vertical") + self.tbQuick.setThemeIcon("bookmarks", "action") + self.tbMoveU.setThemeIcon("chevron_up", "action") + self.tbMoveD.setThemeIcon("chevron_down", "action") + self.tbAdd.setThemeIcon("add", "add") + self.tbMore.setThemeIcon("more_vertical", "default") self.aAddScene.setIcon(SHARED.theme.getIcon("prj_scene", "scene")) self.aAddChap.setIcon(SHARED.theme.getIcon("prj_chapter", "chapter")) @@ -1183,10 +1183,10 @@ class _TreeContextMenu(QMenu): if len(self._indices) > 1: mSub = qtAddMenu(self, self.tr("Set Active to ...")) aOne = qtAddAction(mSub, self._tree.trActive) - aOne.setIcon(SHARED.theme.getIcon("checked", "green")) + aOne.setIcon(SHARED.theme.getIcon("checked", "accept")) aOne.triggered.connect(qtLambda(self._iterItemActive, True)) aTwo = qtAddAction(mSub, self._tree.trInactive) - aTwo.setIcon(SHARED.theme.getIcon("unchecked", "red")) + aTwo.setIcon(SHARED.theme.getIcon("unchecked", "reject")) aTwo.triggered.connect(qtLambda(self._iterItemActive, False)) else: action = qtAddAction(self, self.tr("Toggle Active")) diff --git a/novelwriter/gui/search.py b/novelwriter/gui/search.py index e49770b3..1571591b 100644 --- a/novelwriter/gui/search.py +++ b/novelwriter/gui/search.py @@ -107,7 +107,7 @@ class GuiProjectSearch(QWidget): # Search Box self.searchAction = QAction("", self) - self.searchAction.setIcon(SHARED.theme.getIcon("search", "blue")) + self.searchAction.setIcon(SHARED.theme.getIcon("search", "apply")) self.searchAction.triggered.connect(self._processSearch) self.searchText = QLineEdit(self) @@ -170,10 +170,10 @@ class GuiProjectSearch(QWidget): f"QLineEdit:focus {{border: 1px solid {colFocus};}} " ) - self.searchAction.setIcon(SHARED.theme.getIcon("search", "blue")) - self.toggleCase.setIcon(SHARED.theme.getIcon("search_case")) - self.toggleWord.setIcon(SHARED.theme.getIcon("search_word")) - self.toggleRegEx.setIcon(SHARED.theme.getIcon("search_regex")) + self.searchAction.setIcon(SHARED.theme.getIcon("search", "apply")) + self.toggleCase.setIcon(SHARED.theme.getIcon("search_case", "tool")) + self.toggleWord.setIcon(SHARED.theme.getIcon("search_word", "tool")) + self.toggleRegEx.setIcon(SHARED.theme.getIcon("search_regex", "tool")) def processReturn(self) -> None: """Process a return keypress forwarded from the main GUI.""" diff --git a/novelwriter/gui/sidebar.py b/novelwriter/gui/sidebar.py index 8e6acfd3..5aceb0d5 100644 --- a/novelwriter/gui/sidebar.py +++ b/novelwriter/gui/sidebar.py @@ -142,14 +142,14 @@ class GuiSideBar(QWidget): self.tbTheme.setStyleSheet(buttonStyle) self.tbSettings.setStyleSheet(buttonStyle) - self.tbProject.setThemeIcon("sb_project") - self.tbNovel.setThemeIcon("sb_novel") - self.tbSearch.setThemeIcon("sb_search") - self.tbOutline.setThemeIcon("sb_outline") - self.tbBuild.setThemeIcon("sb_build") - self.tbDetails.setThemeIcon("sb_details") - self.tbStats.setThemeIcon("sb_stats") - self.tbSettings.setThemeIcon("settings") + self.tbProject.setThemeIcon("sb_project", "default") + self.tbNovel.setThemeIcon("sb_novel", "default") + self.tbSearch.setThemeIcon("sb_search", "default") + self.tbOutline.setThemeIcon("sb_outline", "default") + self.tbBuild.setThemeIcon("sb_build", "default") + self.tbDetails.setThemeIcon("sb_details", "default") + self.tbStats.setThemeIcon("sb_stats", "default") + self.tbSettings.setThemeIcon("settings", "default") self._setThemeModeIcon() @@ -176,7 +176,7 @@ class GuiSideBar(QWidget): def _setThemeModeIcon(self) -> None: """Set the theme button icon.""" - self.tbTheme.setThemeIcon(nwLabels.THEME_MODE_ICON[CONFIG.themeMode]) + self.tbTheme.setThemeIcon(nwLabels.THEME_MODE_ICON[CONFIG.themeMode], "default") self.tbTheme.setToolTip(trConst(nwLabels.THEME_MODE_LABEL[CONFIG.themeMode])) diff --git a/novelwriter/gui/theme.py b/novelwriter/gui/theme.py index 407e87a9..8f92304d 100644 --- a/novelwriter/gui/theme.py +++ b/novelwriter/gui/theme.py @@ -57,23 +57,23 @@ STYLES_MIN_TOOLBUTTON = "minimalToolButton" STYLES_BIG_TOOLBUTTON = "bigToolButton" STANDARD_BUTTONS = { - nwStandardButton.OK: (QT_TRANSLATE_NOOP("Button", "OK"), "btn_ok", "blue"), - nwStandardButton.CANCEL: (QT_TRANSLATE_NOOP("Button", "Cancel"), "btn_cancel", "red"), - nwStandardButton.YES: (QT_TRANSLATE_NOOP("Button", "&Yes"), "btn_yes", "green"), - nwStandardButton.NO: (QT_TRANSLATE_NOOP("Button", "&No"), "btn_no", "red"), - nwStandardButton.OPEN: (QT_TRANSLATE_NOOP("Button", "Open"), "btn_open", "blue"), - nwStandardButton.CLOSE: (QT_TRANSLATE_NOOP("Button", "Close"), "btn_close", "faded"), - nwStandardButton.SAVE: (QT_TRANSLATE_NOOP("Button", "Save"), "btn_save", "blue"), - nwStandardButton.BROWSE: (QT_TRANSLATE_NOOP("Button", "Browse"), "btn_browse", "yellow"), - nwStandardButton.LIST: (QT_TRANSLATE_NOOP("Button", "List"), "btn_list", "blue"), - nwStandardButton.NEW: (QT_TRANSLATE_NOOP("Button", "New"), "btn_new", "green"), - nwStandardButton.CREATE: (QT_TRANSLATE_NOOP("Button", "Create"), "btn_create", "yellow"), - nwStandardButton.RESET: (QT_TRANSLATE_NOOP("Button", "Reset"), "btn_reset", "green"), - nwStandardButton.INSERT: (QT_TRANSLATE_NOOP("Button", "Insert"), "btn_insert", "blue"), - nwStandardButton.APPLY: (QT_TRANSLATE_NOOP("Button", "Apply"), "btn_apply", "blue"), - nwStandardButton.BUILD: (QT_TRANSLATE_NOOP("Button", "Build"), "btn_build", "blue"), - nwStandardButton.PRINT: (QT_TRANSLATE_NOOP("Button", "Print"), "btn_print", "blue"), - nwStandardButton.PREVIEW: (QT_TRANSLATE_NOOP("Button", "Preview"), "btn_preview", "blue"), + nwStandardButton.OK: (QT_TRANSLATE_NOOP("Button", "OK"), "btn_ok", "action"), + nwStandardButton.CANCEL: (QT_TRANSLATE_NOOP("Button", "Cancel"), "btn_cancel", "reject"), + nwStandardButton.YES: (QT_TRANSLATE_NOOP("Button", "&Yes"), "btn_yes", "accept"), + nwStandardButton.NO: (QT_TRANSLATE_NOOP("Button", "&No"), "btn_no", "reject"), + nwStandardButton.OPEN: (QT_TRANSLATE_NOOP("Button", "Open"), "btn_open", "action"), + nwStandardButton.CLOSE: (QT_TRANSLATE_NOOP("Button", "Close"), "btn_close", "destroy"), + nwStandardButton.SAVE: (QT_TRANSLATE_NOOP("Button", "Save"), "btn_save", "action"), + nwStandardButton.BROWSE: (QT_TRANSLATE_NOOP("Button", "Browse"), "btn_browse", "systemio"), + nwStandardButton.LIST: (QT_TRANSLATE_NOOP("Button", "List"), "btn_list", "action"), + nwStandardButton.NEW: (QT_TRANSLATE_NOOP("Button", "New"), "btn_new", "apply"), + nwStandardButton.CREATE: (QT_TRANSLATE_NOOP("Button", "Create"), "btn_create", "create"), + nwStandardButton.RESET: (QT_TRANSLATE_NOOP("Button", "Reset"), "btn_reset", "reset"), + nwStandardButton.INSERT: (QT_TRANSLATE_NOOP("Button", "Insert"), "btn_insert", "action"), + nwStandardButton.APPLY: (QT_TRANSLATE_NOOP("Button", "Apply"), "btn_apply", "apply"), + nwStandardButton.BUILD: (QT_TRANSLATE_NOOP("Button", "Build"), "btn_build", "action"), + nwStandardButton.PRINT: (QT_TRANSLATE_NOOP("Button", "Print"), "btn_print", "action"), + nwStandardButton.PREVIEW: (QT_TRANSLATE_NOOP("Button", "Preview"), "btn_preview", "action"), } @@ -357,6 +357,28 @@ class GuiTheme: self._setBaseColor("inactive", self._readColor(parser, sec, "inactive")) self._setBaseColor("disabled", self._readColor(parser, sec, "disabled")) + # Icon + sec = "Icon" + if parser.has_section(sec): + self._setBaseColor("tool", self._readColor(parser, sec, "tool")) + self._setBaseColor("accept", self._readColor(parser, sec, "accept")) + self._setBaseColor("reject", self._readColor(parser, sec, "reject")) + self._setBaseColor("action", self._readColor(parser, sec, "action")) + self._setBaseColor("altaction", self._readColor(parser, sec, "altaction")) + self._setBaseColor("apply", self._readColor(parser, sec, "apply")) + self._setBaseColor("create", self._readColor(parser, sec, "create")) + self._setBaseColor("destroy", self._readColor(parser, sec, "destroy")) + self._setBaseColor("reset", self._readColor(parser, sec, "reset")) + self._setBaseColor("add", self._readColor(parser, sec, "add")) + self._setBaseColor("change", self._readColor(parser, sec, "change")) + self._setBaseColor("remove", self._readColor(parser, sec, "remove")) + self._setBaseColor("scformat", self._readColor(parser, sec, "scformat")) + self._setBaseColor("mdformat", self._readColor(parser, sec, "mdformat")) + self._setBaseColor("systemio", self._readColor(parser, sec, "systemio")) + self._setBaseColor("info", self._readColor(parser, sec, "info")) + self._setBaseColor("warning", self._readColor(parser, sec, "warning")) + self._setBaseColor("error", self._readColor(parser, sec, "error")) + # Palette sec = "Palette" if parser.has_section(sec): @@ -767,7 +789,7 @@ class GuiIcons: # Access Functions ## - def getIcon(self, name: str, color: str | None = None, w: int = 24, h: int = 24) -> QIcon: + def getIcon(self, name: str, color: str, w: int = 24, h: int = 24) -> QIcon: """Return an icon from the icon buffer, or load it.""" variant = f"{name}-{color}" if color else name if (key := f"{variant}-{w}x{h}") in self._qIcons: @@ -778,7 +800,7 @@ class GuiIcons: logger.debug("Icon: %s", key) return icon - def getToggleIcon(self, name: str, size: tuple[int, int], color: str | None = None) -> QIcon: + def getToggleIcon(self, name: str, size: tuple[int, int], color: str) -> QIcon: """Return a toggle icon from the icon buffer, or load it.""" if name in self.TOGGLE_ICON_KEYS: pOne = self.getPixmap(self.TOGGLE_ICON_KEYS[name][0], size, color) @@ -830,7 +852,7 @@ class GuiIcons: doesn't exist, return an empty QPixmap. """ w, h = size - return self.getIcon(name, color, w, h).pixmap(w, h, QIcon.Mode.Normal) + return self.getIcon(name, color or "default", w, h).pixmap(w, h, QIcon.Mode.Normal) def getStandardButton(self, button: nwStandardButton, parent: QWidget) -> NPushButton: """Return a standard button with icon and text.""" diff --git a/novelwriter/shared.py b/novelwriter/shared.py index b76ad165..eacf2298 100644 --- a/novelwriter/shared.py +++ b/novelwriter/shared.py @@ -514,16 +514,16 @@ class _GuiAlert(QMessageBox): pSz = 2*self._theme.baseIconHeight if level == self.INFO: - self.setIconPixmap(self._theme.getPixmap("alert_info", (pSz, pSz), "blue")) + self.setIconPixmap(self._theme.getPixmap("alert_info", (pSz, pSz), "info")) self.setWindowTitle(self.tr("Information")) elif level == self.WARN: - self.setIconPixmap(self._theme.getPixmap("alert_warn", (pSz, pSz), "orange")) + self.setIconPixmap(self._theme.getPixmap("alert_warn", (pSz, pSz), "warning")) self.setWindowTitle(self.tr("Warning")) elif level == self.ERROR: - self.setIconPixmap(self._theme.getPixmap("alert_error", (pSz, pSz), "red")) + self.setIconPixmap(self._theme.getPixmap("alert_error", (pSz, pSz), "error")) self.setWindowTitle(self.tr("Error")) elif level == self.ASK: - self.setIconPixmap(self._theme.getPixmap("alert_question", (pSz, pSz), "blue")) + self.setIconPixmap(self._theme.getPixmap("alert_question", (pSz, pSz), "info")) self.setWindowTitle(self.tr("Question")) @pyqtSlot() diff --git a/novelwriter/tools/dictionaries.py b/novelwriter/tools/dictionaries.py index bd19b78d..9195b49a 100644 --- a/novelwriter/tools/dictionaries.py +++ b/novelwriter/tools/dictionaries.py @@ -79,10 +79,10 @@ class GuiDictionaries(NNonBlockingDialog): self.huInfo.setOpenExternalLinks(True) self.huInfo.setWordWrap(True) self.huInput = QLineEdit(self) - self.huBrowse = NIconToolButton(self, iSz, "browse") + self.huBrowse = NIconToolButton(self, iSz, "browse", "systemio") self.huBrowse.clicked.connect(self._doBrowseHunspell) self.huImport = QPushButton(self.tr("Add Dictionary"), self) - self.huImport.setIcon(SHARED.theme.getIcon("add", "green")) + self.huImport.setIcon(SHARED.theme.getIcon("add", "add")) self.huImport.clicked.connect(self._doImportHunspell) self.huPathBox = QHBoxLayout() @@ -97,7 +97,7 @@ class GuiDictionaries(NNonBlockingDialog): self.inInfo = QLabel(self.tr("Dictionary install location"), self) self.inPath = QLineEdit(self) self.inPath.setReadOnly(True) - self.inBrowse = NIconToolButton(self, iSz, "browse") + self.inBrowse = NIconToolButton(self, iSz, "browse", "systemio") self.inBrowse.clicked.connect(self._doOpenInstallLocation) self.inBox = QHBoxLayout() diff --git a/novelwriter/tools/lipsum.py b/novelwriter/tools/lipsum.py index 3cd2fea7..55119317 100644 --- a/novelwriter/tools/lipsum.py +++ b/novelwriter/tools/lipsum.py @@ -59,7 +59,7 @@ class GuiLipsum(NDialog): # Icon self.docIcon = QLabel(self) - self.docIcon.setPixmap(SHARED.theme.getPixmap("text", (64, 64), "blue")) + self.docIcon.setPixmap(SHARED.theme.getPixmap("text", (64, 64), "info")) self.leftBox = QVBoxLayout() self.leftBox.setSpacing(4) diff --git a/novelwriter/tools/manusbuild.py b/novelwriter/tools/manusbuild.py index ffbef09d..5520982d 100644 --- a/novelwriter/tools/manusbuild.py +++ b/novelwriter/tools/manusbuild.py @@ -43,7 +43,7 @@ from novelwriter.core.item import NWItem from novelwriter.enum import nwBuildFmt, nwStandardButton from novelwriter.extensions.modified import NDialog, NIconToolButton, NPushButton from novelwriter.extensions.progressbars import NProgressSimple -from novelwriter.types import QtAlignCenter, QtRoleAction, QtRoleDestruct, QtRoleReject, QtUserRole +from novelwriter.types import QtAlignCenter, QtRoleAction, QtRoleDestruct, QtUserRole if TYPE_CHECKING: from PyQt6.QtGui import QCloseEvent @@ -143,7 +143,7 @@ class GuiManuscriptBuild(NDialog): # Build Path self.lblPath = QLabel(self.tr("Path"), self) self.buildPath = QLineEdit(self) - self.btnBrowse = NIconToolButton(self, iSz, "browse") + self.btnBrowse = NIconToolButton(self, iSz, "browse", "systemio") self.pathBox = QHBoxLayout() self.pathBox.addWidget(self.buildPath) @@ -153,7 +153,7 @@ class GuiManuscriptBuild(NDialog): # Build Name self.lblName = QLabel(self.tr("File Name"), self) self.buildName = QLineEdit(self) - self.btnReset = NIconToolButton(self, iSz, "revert", "green") + self.btnReset = NIconToolButton(self, iSz, "revert", "reset") self.btnReset.setToolTip(self.tr("Reset file name to default")) self.nameBox = QHBoxLayout() @@ -178,7 +178,7 @@ class GuiManuscriptBuild(NDialog): self.buildBox.setVerticalSpacing(4) # Dialog Buttons - self.btnOpen = NPushButton(self, self.tr("Open Folder"), bSz, "browse", "yellow") + self.btnOpen = NPushButton(self, self.tr("Open Folder"), bSz, "browse", "systemio") self.btnOpen.setAutoDefault(False) self.btnBuild = SHARED.theme.getStandardButton(nwStandardButton.BUILD, self) @@ -260,13 +260,11 @@ class GuiManuscriptBuild(NDialog): @pyqtSlot("QAbstractButton*") def _dialogButtonClicked(self, button: QAbstractButton) -> None: """Handle button clicks from the dialog button box.""" - role = self.btnBox.buttonRole(button) - if role == QtRoleAction: - if button == self.btnBuild: - self._runBuild() - elif button == self.btnOpen: - self._openOutputFolder() - elif role == QtRoleReject: + if button == self.btnBuild: + self._runBuild() + elif button == self.btnOpen: + self._openOutputFolder() + elif button == self.btnClose: self.close() @pyqtSlot() diff --git a/novelwriter/tools/manuscript.py b/novelwriter/tools/manuscript.py index e3c5679b..5bf30983 100644 --- a/novelwriter/tools/manuscript.py +++ b/novelwriter/tools/manuscript.py @@ -271,10 +271,10 @@ class GuiManuscript(NToolDialog): self.btnBuild.updateIcon() self.btnClose.updateIcon() - self.tbAdd.setThemeIcon("add", "green") - self.tbDel.setThemeIcon("remove", "red") - self.tbCopy.setThemeIcon("copy", "blue") - self.tbEdit.setThemeIcon("edit", "green") + self.tbAdd.setThemeIcon("add", "add") + self.tbDel.setThemeIcon("remove", "remove") + self.tbCopy.setThemeIcon("copy", "accept") + self.tbEdit.setThemeIcon("edit", "change") buttonStyle = SHARED.theme.getStyleSheet(STYLES_MIN_TOOLBUTTON) self.tbAdd.setStyleSheet(buttonStyle) @@ -484,7 +484,7 @@ class GuiManuscript(NToolDialog): for key, name in self._builds.builds(): bItem = QListWidgetItem() bItem.setText(name) - bItem.setIcon(SHARED.theme.getIcon("build_settings", "blue")) + bItem.setIcon(SHARED.theme.getIcon("build_settings", "action")) bItem.setData(self.D_KEY, key) self.buildList.addItem(bItem) self._buildMap[key] = bItem @@ -574,8 +574,8 @@ class _DetailsWidget(QWidget): self.listView.clear() - on = SHARED.theme.getIcon("bullet-on", "blue") - off = SHARED.theme.getIcon("bullet-off", "blue") + on = SHARED.theme.getIcon("bullet-on", "action") + off = SHARED.theme.getIcon("bullet-off", "action") # Name item = QTreeWidgetItem() @@ -991,7 +991,7 @@ class _StatsWidget(QWidget): def updateTheme(self) -> None: """Update theme elements.""" logger.debug("Theme Update: _StatsWidget") - self.toggleButton.setThemeIcon("unfold") + self.toggleButton.setThemeIcon("unfold", "default") ## # Private Slots diff --git a/novelwriter/tools/manussettings.py b/novelwriter/tools/manussettings.py index 7f00f6ff..35fe49f6 100644 --- a/novelwriter/tools/manussettings.py +++ b/novelwriter/tools/manussettings.py @@ -52,7 +52,7 @@ from novelwriter.extensions.switch import NSwitch from novelwriter.extensions.switchbox import NSwitchBox from novelwriter.types import ( QtAlignCenter, QtAlignLeft, QtHeaderFixed, QtHeaderStretch, QtRoleAccept, - QtRoleApply, QtRoleDestruct, QtRoleReject, QtUserRole + QtRoleApply, QtRoleDestruct, QtUserRole ) if TYPE_CHECKING: @@ -229,15 +229,14 @@ class GuiBuildSettings(NToolDialog): @pyqtSlot("QAbstractButton*") def _dialogButtonClicked(self, button: QAbstractButton) -> None: """Handle button clicks from the dialog button box.""" - role = self.btnBox.buttonRole(button) - if role == QtRoleApply: + if button == self.btnApply: self._applyChanges() self._emitBuildData() - elif role == QtRoleAccept: + elif button == self.btnSave: self._applyChanges() self._emitBuildData() self.close() - elif role == QtRoleReject: + elif button == self.btnClose: self._build.resetChangedState() self.close() @@ -302,9 +301,9 @@ class _FilterTab(NFixedPage): self._statusFlags: dict[int, QIcon] = { self.F_NONE: QIcon(), - self.F_FILTERED: SHARED.theme.getIcon("filter", "orange"), - self.F_INCLUDED: SHARED.theme.getIcon("pin", "blue"), - self.F_EXCLUDED: SHARED.theme.getIcon("exclude", "red"), + self.F_FILTERED: SHARED.theme.getIcon("filter", "altaction"), + self.F_INCLUDED: SHARED.theme.getIcon("pin", "action"), + self.F_EXCLUDED: SHARED.theme.getIcon("exclude", "reject"), } self._trIncluded = self.tr("Included in manuscript") @@ -410,14 +409,14 @@ class _FilterTab(NFixedPage): logger.debug("Theme Update: _FilterTab, init=%s", init) if not init: - self._statusFlags[self.F_FILTERED] = SHARED.theme.getIcon("filter", "orange") - self._statusFlags[self.F_INCLUDED] = SHARED.theme.getIcon("pin", "blue") - self._statusFlags[self.F_EXCLUDED] = SHARED.theme.getIcon("exclude", "red") + self._statusFlags[self.F_FILTERED] = SHARED.theme.getIcon("filter", "altaction") + self._statusFlags[self.F_INCLUDED] = SHARED.theme.getIcon("pin", "action") + self._statusFlags[self.F_EXCLUDED] = SHARED.theme.getIcon("exclude", "reject") self.loadContent() self.includedButton.setIcon(self._statusFlags[self.F_INCLUDED]) self.excludedButton.setIcon(self._statusFlags[self.F_EXCLUDED]) - self.resetButton.setThemeIcon("revert", "green") + self.resetButton.setThemeIcon("revert", "reset") ## # Slots @@ -492,7 +491,7 @@ class _FilterTab(NFixedPage): default=self._build.getBool("filter.includeNotes") ) self.filterOpt.addItem( - SHARED.theme.getIcon("unchecked", "red"), + SHARED.theme.getIcon("unchecked", "reject"), self._build.getLabel("filter.includeInactive"), "doc:filter.includeInactive", default=self._build.getBool("filter.includeInactive") @@ -811,12 +810,12 @@ class _HeadingsTab(NScrollablePage): """Update theme elements.""" logger.debug("Theme Update: _HeadingsTab") - self.btnPart.setThemeIcon("edit", "green") - self.btnChapter.setThemeIcon("edit", "green") - self.btnUnnumbered.setThemeIcon("edit", "green") - self.btnScene.setThemeIcon("edit", "green") - self.btnAScene.setThemeIcon("edit", "green") - self.btnSection.setThemeIcon("edit", "green") + self.btnPart.setThemeIcon("edit", "change") + self.btnChapter.setThemeIcon("edit", "change") + self.btnUnnumbered.setThemeIcon("edit", "change") + self.btnScene.setThemeIcon("edit", "change") + self.btnAScene.setThemeIcon("edit", "change") + self.btnSection.setThemeIcon("edit", "change") self.formSyntax.initHighlighter() self.formSyntax.rehighlight() @@ -1318,9 +1317,9 @@ class _FormattingTab(NScrollableForm): """Update theme elements.""" logger.debug("Theme Update: _FormattingTab") - self.ignoredKeywordsButton.setThemeIcon("add", "green") - self.btnTextFont.setThemeIcon("font") - self.btnPageHeader.setThemeIcon("revert", "green") + self.ignoredKeywordsButton.setThemeIcon("add", "add") + self.btnTextFont.setThemeIcon("font", "tool") + self.btnPageHeader.setThemeIcon("revert", "reset") iPx = SHARED.theme.baseIconHeight self.pixT.setPixmap(SHARED.theme.getPixmap("margin_top", (iPx, iPx))) diff --git a/novelwriter/tools/welcome.py b/novelwriter/tools/welcome.py index 041a0031..933ecec1 100644 --- a/novelwriter/tools/welcome.py +++ b/novelwriter/tools/welcome.py @@ -258,7 +258,7 @@ class _OpenProjectPage(QWidget): # Info / Tool self.aMissing = QAction(self) - self.aMissing.setIcon(SHARED.theme.getIcon("alert_warn", "orange")) + self.aMissing.setIcon(SHARED.theme.getIcon("alert_warn", "warning")) self.aMissing.setToolTip(self.tr("The project path is not reachable.")) self.selectedPath = QLineEdit(self) @@ -534,7 +534,7 @@ class _NewProjectForm(QWidget): self.projPath = QLineEdit(self) self.projPath.setReadOnly(True) - self.browsePath = NIconToolButton(self, iSz, "browse") + self.browsePath = NIconToolButton(self, iSz, "browse", "systemio") self.browsePath.clicked.connect(self._doBrowse) self.pathBox = QHBoxLayout() @@ -545,20 +545,20 @@ class _NewProjectForm(QWidget): self.projFill = QLineEdit(self) self.projFill.setReadOnly(True) - self.browseFill = NIconToolButton(self, iSz, "document_add", "blue") + self.browseFill = NIconToolButton(self, iSz, "document_add", "add") self.fillMenu = QMenu(self.browseFill) self.fillBlank = qtAddAction(self.fillMenu, self.tr("Create a fresh project")) - self.fillBlank.setIcon(SHARED.theme.getIcon("document")) + self.fillBlank.setIcon(SHARED.theme.getIcon("document", "file")) self.fillBlank.triggered.connect(self._setFillBlank) self.fillSample = qtAddAction(self.fillMenu, self.tr("Create an example project")) - self.fillSample.setIcon(SHARED.theme.getIcon("document_add", "blue")) + self.fillSample.setIcon(SHARED.theme.getIcon("document_add", "add")) self.fillSample.triggered.connect(self._setFillSample) self.fillCopy = qtAddAction(self.fillMenu, self.tr("Copy an existing project")) - self.fillCopy.setIcon(SHARED.theme.getIcon("project_copy", "green")) + self.fillCopy.setIcon(SHARED.theme.getIcon("project_copy", "action")) self.fillCopy.triggered.connect(self._setFillCopy) self.browseFill.setMenu(self.fillMenu) diff --git a/novelwriter/tools/writingstats.py b/novelwriter/tools/writingstats.py index fea44864..c8034487 100644 --- a/novelwriter/tools/writingstats.py +++ b/novelwriter/tools/writingstats.py @@ -292,7 +292,7 @@ class GuiWritingStats(NToolDialog): self.saveMenu.addAction(self.saveJSON) self.saveMenu.addAction(self.saveCSV) - self.btnSave = NPushButton(self, self.tr("Save As"), bSz, "btn_save", "blue") + self.btnSave = NPushButton(self, self.tr("Save As"), bSz, "btn_save", "action") self.btnSave.setAutoDefault(False) self.btnSave.setMenu(self.saveMenu) diff --git a/tests/test_ext/test_ext_modified.py b/tests/test_ext/test_ext_modified.py index fa66a528..d7c2ae09 100644 --- a/tests/test_ext/test_ext_modified.py +++ b/tests/test_ext/test_ext_modified.py @@ -185,13 +185,13 @@ def testExtModified_NClickableLabel(qtbot): @pytest.mark.gui -def testExtModified_ToolButtons(qtbot): +def testExtModified_ToolButtons(qtbot, mockGUI): """Test the NIconToolButton and NIconToggleButton classes.""" dialog = SimpleDialog(None) size = QSize(16, 16) - button1 = NIconToolButton(dialog, size, "add", "green") - button2 = NIconToggleButton(dialog, size, "bullet") + button1 = NIconToolButton(dialog, size, "add", "add") + button2 = NIconToggleButton(dialog, size, "bullet", "action") assert button1.iconSize() == size assert button2.iconSize() == size diff --git a/tests/test_gui/test_gui_theme.py b/tests/test_gui/test_gui_theme.py index 7f4d976a..beff6b21 100644 --- a/tests/test_gui/test_gui_theme.py +++ b/tests/test_gui/test_gui_theme.py @@ -421,36 +421,36 @@ def testGuiTheme_LoadIcons(): # ========== # Load an unknown icon - qIcon = iconCache.getIcon("stuff") + qIcon = iconCache.getIcon("stuff", "tool") assert isinstance(qIcon, QIcon) assert qIcon == iconCache._noIcon # Load an icon, it is likely already cached - qIcon = iconCache.getIcon("add") + qIcon = iconCache.getIcon("add", "tool") assert isinstance(qIcon, QIcon) assert qIcon.isNull() is False # Load it as a pixmap with a size # If this part of the test fails, you may need to set the # environment variable: QT_SCALE_FACTOR=1 - qPix = iconCache.getPixmap("add", (50, 50)) + qPix = iconCache.getPixmap("add", (50, 50), "tool") assert isinstance(qPix, QPixmap) assert qPix.isNull() is False assert qPix.width() == 50, "If this fails, make sure QT_SCALE_FACTOR=1" assert qPix.height() == 50, "If this fails, make sure QT_SCALE_FACTOR=1" # Load app icon - qIcon = iconCache.getIcon("novelwriter") + qIcon = iconCache.getIcon("novelwriter", "tool") assert isinstance(qIcon, QIcon) assert qIcon != iconCache._noIcon # Load mime icon - qIcon = iconCache.getIcon("proj_nwx") + qIcon = iconCache.getIcon("proj_nwx", "tool") assert isinstance(qIcon, QIcon) assert qIcon != iconCache._noIcon # Toggle icon - qIcon = iconCache.getToggleIcon("bullet", (24, 24)) + qIcon = iconCache.getToggleIcon("bullet", (24, 24), "tool") assert isinstance(qIcon, QIcon) assert qIcon != iconCache._noIcon pOn = qIcon.pixmap(24, 24, QIcon.Mode.Normal, QIcon.State.On) @@ -458,7 +458,7 @@ def testGuiTheme_LoadIcons(): assert pOn != pOff # Unknown toggle icon - qIcon = iconCache.getToggleIcon("stuff", (24, 24)) + qIcon = iconCache.getToggleIcon("stuff", (24, 24), "tool") assert isinstance(qIcon, QIcon) assert qIcon == iconCache._noIcon @@ -610,7 +610,7 @@ def testGuiTheme_CheckTheme(theme): parser = ConfigParser() parser.read(current.path, encoding="utf-8") - sections = ["Main", "Base", "Project", "Palette", "GUI", "Syntax"] + sections = ["Main", "Base", "Project", "Icon", "Palette", "GUI", "Syntax"] assert sorted(parser.sections()) == sorted(sections) structure = { @@ -625,6 +625,11 @@ def testGuiTheme_CheckTheme(theme): "root", "folder", "file", "title", "chapter", "scene", "note", "active", "inactive", "disabled", ], + "Icon": [ + "tool", "accept", "reject", "action", "altaction", "apply", + "create", "destroy", "reset", "add", "change", "remove", + "scformat", "mdformat", "systemio", "info", "warning", "error", + ], "Palette": [ "window", "windowtext", "base", "alternatebase", "text", "tooltipbase", "tooltiptext", "button", "buttontext", "brighttext", From f74b151c5346efc2605320b3a51aa99ea7bd7d7d Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Sun, 26 Oct 2025 14:02:55 +0100 Subject: [PATCH 74/79] Add default icon colours to all themes --- novelwriter/assets/themes/aura.conf | 20 +++++++++++++++++++ novelwriter/assets/themes/aura_bright.conf | 20 +++++++++++++++++++ novelwriter/assets/themes/aura_soft.conf | 20 +++++++++++++++++++ .../assets/themes/b2t_garden_dark.conf | 20 +++++++++++++++++++ .../assets/themes/b2t_garden_light.conf | 20 +++++++++++++++++++ .../assets/themes/b2t_suburb_dark.conf | 20 +++++++++++++++++++ .../assets/themes/b2t_suburb_light.conf | 20 +++++++++++++++++++ .../assets/themes/b4t_classic_o_dark.conf | 20 +++++++++++++++++++ .../assets/themes/b4t_classic_o_light.conf | 20 +++++++++++++++++++ .../assets/themes/b4t_modern_c_dark.conf | 20 +++++++++++++++++++ .../assets/themes/b4t_modern_c_light.conf | 20 +++++++++++++++++++ .../assets/themes/blue_streak_dark.conf | 20 +++++++++++++++++++ .../assets/themes/blue_streak_light.conf | 20 +++++++++++++++++++ novelwriter/assets/themes/castle_day.conf | 20 +++++++++++++++++++ novelwriter/assets/themes/castle_night.conf | 20 +++++++++++++++++++ novelwriter/assets/themes/chalky_soil.conf | 20 +++++++++++++++++++ novelwriter/assets/themes/chernozem.conf | 20 +++++++++++++++++++ .../assets/themes/cyberpunk_night.conf | 20 +++++++++++++++++++ novelwriter/assets/themes/default_dark.conf | 20 +++++++++++++++++++ novelwriter/assets/themes/default_light.conf | 20 +++++++++++++++++++ novelwriter/assets/themes/dracula.conf | 20 +++++++++++++++++++ novelwriter/assets/themes/espresso.conf | 20 +++++++++++++++++++ .../assets/themes/everforest_dark.conf | 20 +++++++++++++++++++ .../assets/themes/everforest_light.conf | 20 +++++++++++++++++++ .../assets/themes/floral_daydream.conf | 20 +++++++++++++++++++ .../assets/themes/floral_midnight.conf | 20 +++++++++++++++++++ novelwriter/assets/themes/full_moon.conf | 20 +++++++++++++++++++ novelwriter/assets/themes/grey_dark.conf | 20 +++++++++++++++++++ novelwriter/assets/themes/grey_light.conf | 20 +++++++++++++++++++ novelwriter/assets/themes/horizon_dark.conf | 20 +++++++++++++++++++ novelwriter/assets/themes/horizon_light.conf | 20 +++++++++++++++++++ novelwriter/assets/themes/lcars.conf | 20 +++++++++++++++++++ novelwriter/assets/themes/light_owl.conf | 20 +++++++++++++++++++ novelwriter/assets/themes/new_moon.conf | 20 +++++++++++++++++++ novelwriter/assets/themes/night_owl.conf | 20 +++++++++++++++++++ novelwriter/assets/themes/noctis.conf | 20 +++++++++++++++++++ novelwriter/assets/themes/noctis_lux.conf | 20 +++++++++++++++++++ novelwriter/assets/themes/nord.conf | 20 +++++++++++++++++++ novelwriter/assets/themes/nordlicht.conf | 20 +++++++++++++++++++ novelwriter/assets/themes/otium_dark.conf | 20 +++++++++++++++++++ novelwriter/assets/themes/otium_light.conf | 20 +++++++++++++++++++ novelwriter/assets/themes/paragon.conf | 20 +++++++++++++++++++ novelwriter/assets/themes/primer_light.conf | 20 +++++++++++++++++++ novelwriter/assets/themes/primer_night.conf | 20 +++++++++++++++++++ novelwriter/assets/themes/ruby_day.conf | 20 +++++++++++++++++++ novelwriter/assets/themes/ruby_night.conf | 20 +++++++++++++++++++ novelwriter/assets/themes/selenium_dark.conf | 20 +++++++++++++++++++ novelwriter/assets/themes/selenium_light.conf | 20 +++++++++++++++++++ novelwriter/assets/themes/sepia_dark.conf | 20 +++++++++++++++++++ novelwriter/assets/themes/sepia_light.conf | 20 +++++++++++++++++++ novelwriter/assets/themes/snazzy.conf | 20 +++++++++++++++++++ novelwriter/assets/themes/solarized_dark.conf | 20 +++++++++++++++++++ .../assets/themes/solarized_light.conf | 20 +++++++++++++++++++ novelwriter/assets/themes/sultana_light.conf | 20 +++++++++++++++++++ novelwriter/assets/themes/sultana_night.conf | 20 +++++++++++++++++++ novelwriter/assets/themes/tango_dark.conf | 20 +++++++++++++++++++ novelwriter/assets/themes/tango_light.conf | 20 +++++++++++++++++++ novelwriter/assets/themes/tomorrow.conf | 20 +++++++++++++++++++ novelwriter/assets/themes/tomorrow_night.conf | 20 +++++++++++++++++++ .../assets/themes/tomorrow_night_blue.conf | 20 +++++++++++++++++++ .../assets/themes/tomorrow_night_bright.conf | 20 +++++++++++++++++++ .../themes/tomorrow_night_eighties.conf | 20 +++++++++++++++++++ .../assets/themes/vivid_black_green.conf | 20 +++++++++++++++++++ .../assets/themes/vivid_black_red.conf | 20 +++++++++++++++++++ .../assets/themes/vivid_white_green.conf | 20 +++++++++++++++++++ .../assets/themes/vivid_white_red.conf | 20 +++++++++++++++++++ novelwriter/assets/themes/warpgate.conf | 20 +++++++++++++++++++ novelwriter/assets/themes/waterlily_dark.conf | 20 +++++++++++++++++++ .../assets/themes/waterlily_light.conf | 20 +++++++++++++++++++ 69 files changed, 1380 insertions(+) diff --git a/novelwriter/assets/themes/aura.conf b/novelwriter/assets/themes/aura.conf index 97ba7908..ece05583 100644 --- a/novelwriter/assets/themes/aura.conf +++ b/novelwriter/assets/themes/aura.conf @@ -29,6 +29,26 @@ active = cyan inactive = red disabled = faded +[Icon] +tool = default +accept = green +reject = red +action = blue +altaction = orange +apply = green +create = yellow +destroy = faded +reset = green +add = green +change = green +remove = red +scformat = default +mdformat = orange +systemio = yellow +info = blue +warning = orange +error = red + [Palette] window = #0c0c11 windowtext = default diff --git a/novelwriter/assets/themes/aura_bright.conf b/novelwriter/assets/themes/aura_bright.conf index 47bc7854..d2813a88 100644 --- a/novelwriter/assets/themes/aura_bright.conf +++ b/novelwriter/assets/themes/aura_bright.conf @@ -27,6 +27,26 @@ active = cyan inactive = red disabled = faded +[Icon] +tool = default +accept = green +reject = red +action = blue +altaction = orange +apply = green +create = yellow +destroy = faded +reset = green +add = green +change = green +remove = red +scformat = default +mdformat = orange +systemio = yellow +info = blue +warning = orange +error = red + [Palette] window = #e1dae2 windowtext = default diff --git a/novelwriter/assets/themes/aura_soft.conf b/novelwriter/assets/themes/aura_soft.conf index 1dafbc49..d975ed7e 100644 --- a/novelwriter/assets/themes/aura_soft.conf +++ b/novelwriter/assets/themes/aura_soft.conf @@ -29,6 +29,26 @@ active = cyan inactive = red disabled = faded +[Icon] +tool = default +accept = green +reject = red +action = blue +altaction = orange +apply = green +create = yellow +destroy = faded +reset = green +add = green +change = green +remove = red +scformat = default +mdformat = orange +systemio = yellow +info = blue +warning = orange +error = red + [Palette] window = #191924 windowtext = default diff --git a/novelwriter/assets/themes/b2t_garden_dark.conf b/novelwriter/assets/themes/b2t_garden_dark.conf index cfaaa37e..b10c6862 100644 --- a/novelwriter/assets/themes/b2t_garden_dark.conf +++ b/novelwriter/assets/themes/b2t_garden_dark.conf @@ -29,6 +29,26 @@ active = green inactive = orange disabled = #696d69 +[Icon] +tool = default +accept = green +reject = red +action = blue +altaction = orange +apply = green +create = yellow +destroy = faded +reset = green +add = green +change = green +remove = red +scformat = default +mdformat = orange +systemio = yellow +info = blue +warning = orange +error = red + [Palette] window = #1e1f1e windowtext = default diff --git a/novelwriter/assets/themes/b2t_garden_light.conf b/novelwriter/assets/themes/b2t_garden_light.conf index d15c2e35..25a2dd3f 100644 --- a/novelwriter/assets/themes/b2t_garden_light.conf +++ b/novelwriter/assets/themes/b2t_garden_light.conf @@ -29,6 +29,26 @@ active = green inactive = orange disabled = #aab1aa +[Icon] +tool = default +accept = green +reject = red +action = blue +altaction = orange +apply = green +create = yellow +destroy = faded +reset = green +add = green +change = green +remove = red +scformat = default +mdformat = orange +systemio = yellow +info = blue +warning = orange +error = red + [Palette] window = #ece5df windowtext = default diff --git a/novelwriter/assets/themes/b2t_suburb_dark.conf b/novelwriter/assets/themes/b2t_suburb_dark.conf index 56c3768d..3f1b6937 100644 --- a/novelwriter/assets/themes/b2t_suburb_dark.conf +++ b/novelwriter/assets/themes/b2t_suburb_dark.conf @@ -29,6 +29,26 @@ active = red inactive = faded disabled = #575c79 +[Icon] +tool = default +accept = green +reject = red +action = blue +altaction = orange +apply = green +create = yellow +destroy = faded +reset = green +add = green +change = green +remove = red +scformat = default +mdformat = orange +systemio = yellow +info = blue +warning = orange +error = red + [Palette] window = #1e212f windowtext = default diff --git a/novelwriter/assets/themes/b2t_suburb_light.conf b/novelwriter/assets/themes/b2t_suburb_light.conf index 8b728e6a..d494a21e 100644 --- a/novelwriter/assets/themes/b2t_suburb_light.conf +++ b/novelwriter/assets/themes/b2t_suburb_light.conf @@ -29,6 +29,26 @@ active = red inactive = faded disabled = #b6bad1 +[Icon] +tool = default +accept = green +reject = red +action = blue +altaction = orange +apply = green +create = yellow +destroy = faded +reset = green +add = green +change = green +remove = red +scformat = default +mdformat = orange +systemio = yellow +info = blue +warning = orange +error = red + [Palette] window = #e9e5e7 windowtext = default diff --git a/novelwriter/assets/themes/b4t_classic_o_dark.conf b/novelwriter/assets/themes/b4t_classic_o_dark.conf index ca8a2b8b..795794d6 100644 --- a/novelwriter/assets/themes/b4t_classic_o_dark.conf +++ b/novelwriter/assets/themes/b4t_classic_o_dark.conf @@ -29,6 +29,26 @@ active = default inactive = faded disabled = #454f5f +[Icon] +tool = default +accept = green +reject = red +action = blue +altaction = orange +apply = green +create = yellow +destroy = faded +reset = green +add = green +change = green +remove = red +scformat = default +mdformat = orange +systemio = yellow +info = blue +warning = orange +error = red + [Palette] window = #191d23 windowtext = default diff --git a/novelwriter/assets/themes/b4t_classic_o_light.conf b/novelwriter/assets/themes/b4t_classic_o_light.conf index 829e3a4d..781d772f 100644 --- a/novelwriter/assets/themes/b4t_classic_o_light.conf +++ b/novelwriter/assets/themes/b4t_classic_o_light.conf @@ -29,6 +29,26 @@ active = default inactive = faded disabled = #acb5c3 +[Icon] +tool = default +accept = green +reject = red +action = blue +altaction = orange +apply = green +create = yellow +destroy = faded +reset = green +add = green +change = green +remove = red +scformat = default +mdformat = orange +systemio = yellow +info = blue +warning = orange +error = red + [Palette] window = #e7eaee windowtext = default diff --git a/novelwriter/assets/themes/b4t_modern_c_dark.conf b/novelwriter/assets/themes/b4t_modern_c_dark.conf index 080a859f..2af49bae 100644 --- a/novelwriter/assets/themes/b4t_modern_c_dark.conf +++ b/novelwriter/assets/themes/b4t_modern_c_dark.conf @@ -29,6 +29,26 @@ active = default inactive = faded disabled = #5d5f6f +[Icon] +tool = default +accept = green +reject = red +action = blue +altaction = orange +apply = green +create = yellow +destroy = faded +reset = green +add = green +change = green +remove = red +scformat = default +mdformat = orange +systemio = yellow +info = blue +warning = orange +error = red + [Palette] window = #1b1c20 windowtext = default diff --git a/novelwriter/assets/themes/b4t_modern_c_light.conf b/novelwriter/assets/themes/b4t_modern_c_light.conf index cc6468fe..b9b02cfe 100644 --- a/novelwriter/assets/themes/b4t_modern_c_light.conf +++ b/novelwriter/assets/themes/b4t_modern_c_light.conf @@ -29,6 +29,26 @@ active = default inactive = faded disabled = #BBBDC9 +[Icon] +tool = default +accept = green +reject = red +action = blue +altaction = orange +apply = green +create = yellow +destroy = faded +reset = green +add = green +change = green +remove = red +scformat = default +mdformat = orange +systemio = yellow +info = blue +warning = orange +error = red + [Palette] window = #e7e7ed windowtext = default diff --git a/novelwriter/assets/themes/blue_streak_dark.conf b/novelwriter/assets/themes/blue_streak_dark.conf index aad8acc0..e42ec1b3 100644 --- a/novelwriter/assets/themes/blue_streak_dark.conf +++ b/novelwriter/assets/themes/blue_streak_dark.conf @@ -29,6 +29,26 @@ active = blue:L150 inactive = blue:D150 disabled = faded +[Icon] +tool = default +accept = green +reject = red +action = blue +altaction = orange +apply = green +create = yellow +destroy = faded +reset = green +add = green +change = green +remove = red +scformat = default +mdformat = orange +systemio = yellow +info = blue +warning = orange +error = red + [Palette] window = base:L125 windowtext = default diff --git a/novelwriter/assets/themes/blue_streak_light.conf b/novelwriter/assets/themes/blue_streak_light.conf index 3d9ff76b..48937537 100644 --- a/novelwriter/assets/themes/blue_streak_light.conf +++ b/novelwriter/assets/themes/blue_streak_light.conf @@ -29,6 +29,26 @@ active = blue:L115 inactive = blue:D150 disabled = faded +[Icon] +tool = default +accept = green +reject = red +action = blue +altaction = orange +apply = green +create = yellow +destroy = faded +reset = green +add = green +change = green +remove = red +scformat = default +mdformat = orange +systemio = yellow +info = blue +warning = orange +error = red + [Palette] window = base:D105 windowtext = default diff --git a/novelwriter/assets/themes/castle_day.conf b/novelwriter/assets/themes/castle_day.conf index bd5dbdd0..00c25c46 100644 --- a/novelwriter/assets/themes/castle_day.conf +++ b/novelwriter/assets/themes/castle_day.conf @@ -27,6 +27,26 @@ active = default inactive = faded disabled = #b4aca5 +[Icon] +tool = default +accept = green +reject = red +action = blue +altaction = orange +apply = green +create = yellow +destroy = faded +reset = green +add = green +change = green +remove = red +scformat = default +mdformat = orange +systemio = yellow +info = blue +warning = orange +error = red + [Palette] window = base:D110 windowtext = default diff --git a/novelwriter/assets/themes/castle_night.conf b/novelwriter/assets/themes/castle_night.conf index c7eb49c1..46978375 100644 --- a/novelwriter/assets/themes/castle_night.conf +++ b/novelwriter/assets/themes/castle_night.conf @@ -27,6 +27,26 @@ active = default inactive = faded disabled = #4b4f5c +[Icon] +tool = default +accept = green +reject = red +action = blue +altaction = orange +apply = green +create = yellow +destroy = faded +reset = green +add = green +change = green +remove = red +scformat = default +mdformat = orange +systemio = yellow +info = blue +warning = orange +error = red + [Palette] window = base:D130 windowtext = default diff --git a/novelwriter/assets/themes/chalky_soil.conf b/novelwriter/assets/themes/chalky_soil.conf index 7f52150b..23aaefa1 100644 --- a/novelwriter/assets/themes/chalky_soil.conf +++ b/novelwriter/assets/themes/chalky_soil.conf @@ -27,6 +27,26 @@ active = green inactive = faded disabled = faded:L135 +[Icon] +tool = default +accept = green +reject = red +action = blue +altaction = orange +apply = green +create = yellow +destroy = faded +reset = green +add = green +change = green +remove = red +scformat = default +mdformat = orange +systemio = yellow +info = blue +warning = orange +error = red + [Palette] window = base:D108 windowtext = default diff --git a/novelwriter/assets/themes/chernozem.conf b/novelwriter/assets/themes/chernozem.conf index 93b1b258..fcb2caeb 100644 --- a/novelwriter/assets/themes/chernozem.conf +++ b/novelwriter/assets/themes/chernozem.conf @@ -27,6 +27,26 @@ active = green inactive = faded disabled = #504742 +[Icon] +tool = default +accept = green +reject = red +action = blue +altaction = orange +apply = green +create = yellow +destroy = faded +reset = green +add = green +change = green +remove = red +scformat = default +mdformat = orange +systemio = yellow +info = blue +warning = orange +error = red + [Palette] window = #241d1d windowtext = default diff --git a/novelwriter/assets/themes/cyberpunk_night.conf b/novelwriter/assets/themes/cyberpunk_night.conf index 55641eae..ac67d376 100644 --- a/novelwriter/assets/themes/cyberpunk_night.conf +++ b/novelwriter/assets/themes/cyberpunk_night.conf @@ -28,6 +28,26 @@ active = green inactive = red disabled = faded +[Icon] +tool = default +accept = green +reject = red +action = blue +altaction = orange +apply = green +create = yellow +destroy = faded +reset = green +add = green +change = green +remove = red +scformat = default +mdformat = orange +systemio = yellow +info = blue +warning = orange +error = red + [Palette] window = base windowtext = #969696 diff --git a/novelwriter/assets/themes/default_dark.conf b/novelwriter/assets/themes/default_dark.conf index 985e7595..69bdbdee 100644 --- a/novelwriter/assets/themes/default_dark.conf +++ b/novelwriter/assets/themes/default_dark.conf @@ -29,6 +29,26 @@ active = green inactive = red disabled = faded +[Icon] +tool = default +accept = green +reject = red +action = blue +altaction = orange +apply = green +create = yellow +destroy = faded +reset = green +add = green +change = green +remove = red +scformat = default +mdformat = orange +systemio = yellow +info = blue +warning = orange +error = red + [Palette] window = base:L125 windowtext = default diff --git a/novelwriter/assets/themes/default_light.conf b/novelwriter/assets/themes/default_light.conf index f141bd71..63ee995c 100644 --- a/novelwriter/assets/themes/default_light.conf +++ b/novelwriter/assets/themes/default_light.conf @@ -29,6 +29,26 @@ active = green inactive = red disabled = faded +[Icon] +tool = default +accept = green +reject = red +action = blue +altaction = orange +apply = green +create = yellow +destroy = faded +reset = green +add = green +change = green +remove = red +scformat = default +mdformat = orange +systemio = yellow +info = blue +warning = orange +error = red + [Palette] window = base:D105 windowtext = default diff --git a/novelwriter/assets/themes/dracula.conf b/novelwriter/assets/themes/dracula.conf index f10c9484..6bcc3945 100644 --- a/novelwriter/assets/themes/dracula.conf +++ b/novelwriter/assets/themes/dracula.conf @@ -45,6 +45,26 @@ active = green inactive = red disabled = faded +[Icon] +tool = default +accept = green +reject = red +action = blue +altaction = orange +apply = green +create = yellow +destroy = faded +reset = green +add = green +change = green +remove = red +scformat = default +mdformat = orange +systemio = yellow +info = blue +warning = orange +error = red + [Palette] window = #44475a windowtext = #f8f8f2 diff --git a/novelwriter/assets/themes/espresso.conf b/novelwriter/assets/themes/espresso.conf index 85c4d168..f1295bcd 100644 --- a/novelwriter/assets/themes/espresso.conf +++ b/novelwriter/assets/themes/espresso.conf @@ -29,6 +29,26 @@ active = green inactive = red disabled = faded +[Icon] +tool = default +accept = green +reject = red +action = blue +altaction = orange +apply = green +create = yellow +destroy = faded +reset = green +add = green +change = green +remove = red +scformat = default +mdformat = orange +systemio = yellow +info = blue +warning = orange +error = red + [Palette] window = base:L125 windowtext = default diff --git a/novelwriter/assets/themes/everforest_dark.conf b/novelwriter/assets/themes/everforest_dark.conf index b6f7d962..eadee969 100644 --- a/novelwriter/assets/themes/everforest_dark.conf +++ b/novelwriter/assets/themes/everforest_dark.conf @@ -29,6 +29,26 @@ active = cyan inactive = orange disabled = faded +[Icon] +tool = default +accept = green +reject = red +action = blue +altaction = orange +apply = green +create = yellow +destroy = faded +reset = green +add = green +change = green +remove = red +scformat = default +mdformat = orange +systemio = yellow +info = blue +warning = orange +error = red + [Palette] window = #1e2326 windowtext = default diff --git a/novelwriter/assets/themes/everforest_light.conf b/novelwriter/assets/themes/everforest_light.conf index 988e2f93..d26b8399 100644 --- a/novelwriter/assets/themes/everforest_light.conf +++ b/novelwriter/assets/themes/everforest_light.conf @@ -29,6 +29,26 @@ active = cyan inactive = orange disabled = faded +[Icon] +tool = default +accept = green +reject = red +action = blue +altaction = orange +apply = green +create = yellow +destroy = faded +reset = green +add = green +change = green +remove = red +scformat = default +mdformat = orange +systemio = yellow +info = blue +warning = orange +error = red + [Palette] window = #f2efdf windowtext = default diff --git a/novelwriter/assets/themes/floral_daydream.conf b/novelwriter/assets/themes/floral_daydream.conf index 5c0b7bb7..33033d83 100644 --- a/novelwriter/assets/themes/floral_daydream.conf +++ b/novelwriter/assets/themes/floral_daydream.conf @@ -27,6 +27,26 @@ active = green inactive = faded disabled = #e2bcce +[Icon] +tool = default +accept = green +reject = red +action = blue +altaction = orange +apply = green +create = yellow +destroy = faded +reset = green +add = green +change = green +remove = red +scformat = default +mdformat = orange +systemio = yellow +info = blue +warning = orange +error = red + [Palette] window = #ffe3ea windowtext = default diff --git a/novelwriter/assets/themes/floral_midnight.conf b/novelwriter/assets/themes/floral_midnight.conf index eaa88fde..77518f6b 100644 --- a/novelwriter/assets/themes/floral_midnight.conf +++ b/novelwriter/assets/themes/floral_midnight.conf @@ -27,6 +27,26 @@ active = green inactive = faded disabled = #55516d +[Icon] +tool = default +accept = green +reject = red +action = blue +altaction = orange +apply = green +create = yellow +destroy = faded +reset = green +add = green +change = green +remove = red +scformat = default +mdformat = orange +systemio = yellow +info = blue +warning = orange +error = red + [Palette] window = #181825 windowtext = default diff --git a/novelwriter/assets/themes/full_moon.conf b/novelwriter/assets/themes/full_moon.conf index 6bb73de9..32f770a0 100644 --- a/novelwriter/assets/themes/full_moon.conf +++ b/novelwriter/assets/themes/full_moon.conf @@ -27,6 +27,26 @@ active = green inactive = red disabled = faded +[Icon] +tool = default +accept = green +reject = red +action = blue +altaction = orange +apply = green +create = yellow +destroy = faded +reset = green +add = green +change = green +remove = red +scformat = default +mdformat = orange +systemio = yellow +info = blue +warning = orange +error = red + [Palette] window = #e7ebee windowtext = default diff --git a/novelwriter/assets/themes/grey_dark.conf b/novelwriter/assets/themes/grey_dark.conf index 8bb0d1f2..92326bc0 100644 --- a/novelwriter/assets/themes/grey_dark.conf +++ b/novelwriter/assets/themes/grey_dark.conf @@ -29,6 +29,26 @@ active = green inactive = red disabled = faded +[Icon] +tool = default +accept = green +reject = red +action = blue +altaction = orange +apply = green +create = yellow +destroy = faded +reset = green +add = green +change = green +remove = red +scformat = default +mdformat = orange +systemio = yellow +info = blue +warning = orange +error = red + [Palette] window = #363636 windowtext = default diff --git a/novelwriter/assets/themes/grey_light.conf b/novelwriter/assets/themes/grey_light.conf index 35e7be54..ac7d9d90 100644 --- a/novelwriter/assets/themes/grey_light.conf +++ b/novelwriter/assets/themes/grey_light.conf @@ -29,6 +29,26 @@ active = green inactive = red disabled = faded +[Icon] +tool = default +accept = green +reject = red +action = blue +altaction = orange +apply = green +create = yellow +destroy = faded +reset = green +add = green +change = green +remove = red +scformat = default +mdformat = orange +systemio = yellow +info = blue +warning = orange +error = red + [Palette] window = #efefef windowtext = default diff --git a/novelwriter/assets/themes/horizon_dark.conf b/novelwriter/assets/themes/horizon_dark.conf index 6e446db2..caa1b0b1 100644 --- a/novelwriter/assets/themes/horizon_dark.conf +++ b/novelwriter/assets/themes/horizon_dark.conf @@ -29,6 +29,26 @@ active = yellow inactive = faded disabled = #404263 +[Icon] +tool = default +accept = green +reject = red +action = blue +altaction = orange +apply = green +create = yellow +destroy = faded +reset = green +add = green +change = green +remove = red +scformat = default +mdformat = orange +systemio = yellow +info = blue +warning = orange +error = red + [Palette] window = base windowtext = default diff --git a/novelwriter/assets/themes/horizon_light.conf b/novelwriter/assets/themes/horizon_light.conf index 50bd40bd..e7d948b0 100644 --- a/novelwriter/assets/themes/horizon_light.conf +++ b/novelwriter/assets/themes/horizon_light.conf @@ -29,6 +29,26 @@ active = #eb834f inactive = faded disabled = faded:L125 +[Icon] +tool = default +accept = green +reject = red +action = blue +altaction = orange +apply = green +create = yellow +destroy = faded +reset = green +add = green +change = green +remove = red +scformat = default +mdformat = orange +systemio = yellow +info = blue +warning = orange +error = red + [Palette] window = base windowtext = default diff --git a/novelwriter/assets/themes/lcars.conf b/novelwriter/assets/themes/lcars.conf index 6d5956f6..cd55aea9 100644 --- a/novelwriter/assets/themes/lcars.conf +++ b/novelwriter/assets/themes/lcars.conf @@ -29,6 +29,26 @@ active = blue inactive = red disabled = faded +[Icon] +tool = default +accept = green +reject = red +action = blue +altaction = orange +apply = green +create = yellow +destroy = faded +reset = green +add = green +change = green +remove = red +scformat = default +mdformat = orange +systemio = yellow +info = blue +warning = orange +error = red + [Palette] window = base:L180 windowtext = default diff --git a/novelwriter/assets/themes/light_owl.conf b/novelwriter/assets/themes/light_owl.conf index c3e338a1..891cead4 100644 --- a/novelwriter/assets/themes/light_owl.conf +++ b/novelwriter/assets/themes/light_owl.conf @@ -49,6 +49,26 @@ active = green inactive = red disabled = faded +[Icon] +tool = default +accept = green +reject = red +action = blue +altaction = orange +apply = green +create = yellow +destroy = faded +reset = green +add = green +change = green +remove = red +scformat = default +mdformat = orange +systemio = yellow +info = blue +warning = orange +error = red + [Palette] window = #eaeaea windowtext = default diff --git a/novelwriter/assets/themes/new_moon.conf b/novelwriter/assets/themes/new_moon.conf index 79018122..45d8d442 100644 --- a/novelwriter/assets/themes/new_moon.conf +++ b/novelwriter/assets/themes/new_moon.conf @@ -29,6 +29,26 @@ active = green inactive = red disabled = faded +[Icon] +tool = default +accept = green +reject = red +action = blue +altaction = orange +apply = green +create = yellow +destroy = faded +reset = green +add = green +change = green +remove = red +scformat = default +mdformat = orange +systemio = yellow +info = blue +warning = orange +error = red + [Palette] window = #252525 windowtext = default diff --git a/novelwriter/assets/themes/night_owl.conf b/novelwriter/assets/themes/night_owl.conf index 93e3c105..72c307ce 100644 --- a/novelwriter/assets/themes/night_owl.conf +++ b/novelwriter/assets/themes/night_owl.conf @@ -37,6 +37,26 @@ cyan = #7fdbca blue = #82aaff purple = #c792ea +[Icon] +tool = default +accept = green +reject = red +action = blue +altaction = orange +apply = green +create = yellow +destroy = faded +reset = green +add = green +change = green +remove = red +scformat = default +mdformat = orange +systemio = yellow +info = blue +warning = orange +error = red + [Project] root = blue folder = yellow diff --git a/novelwriter/assets/themes/noctis.conf b/novelwriter/assets/themes/noctis.conf index e1489328..8a25d31c 100644 --- a/novelwriter/assets/themes/noctis.conf +++ b/novelwriter/assets/themes/noctis.conf @@ -61,6 +61,26 @@ active = green inactive = red disabled = faded +[Icon] +tool = default +accept = green +reject = red +action = blue +altaction = orange +apply = green +create = yellow +destroy = faded +reset = green +add = green +change = green +remove = red +scformat = default +mdformat = orange +systemio = yellow +info = blue +warning = orange +error = red + [Palette] window = #041d20 windowtext = default diff --git a/novelwriter/assets/themes/noctis_lux.conf b/novelwriter/assets/themes/noctis_lux.conf index c6e22bf5..03835ea9 100644 --- a/novelwriter/assets/themes/noctis_lux.conf +++ b/novelwriter/assets/themes/noctis_lux.conf @@ -61,6 +61,26 @@ active = green inactive = red disabled = faded +[Icon] +tool = default +accept = green +reject = red +action = blue +altaction = orange +apply = green +create = yellow +destroy = faded +reset = green +add = green +change = green +remove = red +scformat = default +mdformat = orange +systemio = yellow +info = blue +warning = orange +error = red + [Palette] window = #f9f1e1 windowtext = default diff --git a/novelwriter/assets/themes/nord.conf b/novelwriter/assets/themes/nord.conf index aeaeb30e..308695ab 100644 --- a/novelwriter/assets/themes/nord.conf +++ b/novelwriter/assets/themes/nord.conf @@ -29,6 +29,26 @@ active = green inactive = faded disabled = #576279 +[Icon] +tool = default +accept = green +reject = red +action = blue +altaction = orange +apply = green +create = yellow +destroy = faded +reset = green +add = green +change = green +remove = red +scformat = default +mdformat = orange +systemio = yellow +info = blue +warning = orange +error = red + [Palette] window = #242933 windowtext = default diff --git a/novelwriter/assets/themes/nordlicht.conf b/novelwriter/assets/themes/nordlicht.conf index ccfde60a..7725627a 100644 --- a/novelwriter/assets/themes/nordlicht.conf +++ b/novelwriter/assets/themes/nordlicht.conf @@ -27,6 +27,26 @@ active = green inactive = faded disabled = #b2c0d6 +[Icon] +tool = default +accept = green +reject = red +action = blue +altaction = orange +apply = green +create = yellow +destroy = faded +reset = green +add = green +change = green +remove = red +scformat = default +mdformat = orange +systemio = yellow +info = blue +warning = orange +error = red + [Palette] window = #e5e9f0 windowtext = default diff --git a/novelwriter/assets/themes/otium_dark.conf b/novelwriter/assets/themes/otium_dark.conf index 5d9401e3..7ff09686 100644 --- a/novelwriter/assets/themes/otium_dark.conf +++ b/novelwriter/assets/themes/otium_dark.conf @@ -27,6 +27,26 @@ active = default inactive = faded disabled = #4e545e +[Icon] +tool = default +accept = green +reject = red +action = blue +altaction = orange +apply = green +create = yellow +destroy = faded +reset = green +add = green +change = green +remove = red +scformat = default +mdformat = orange +systemio = yellow +info = blue +warning = orange +error = red + [Palette] window = base windowtext = default diff --git a/novelwriter/assets/themes/otium_light.conf b/novelwriter/assets/themes/otium_light.conf index c3084ed7..a524051e 100644 --- a/novelwriter/assets/themes/otium_light.conf +++ b/novelwriter/assets/themes/otium_light.conf @@ -27,6 +27,26 @@ active = default:L165 inactive = faded:L110 disabled = #adadad +[Icon] +tool = default +accept = green +reject = red +action = blue +altaction = orange +apply = green +create = yellow +destroy = faded +reset = green +add = green +change = green +remove = red +scformat = default +mdformat = orange +systemio = yellow +info = blue +warning = orange +error = red + [Palette] window = base windowtext = default diff --git a/novelwriter/assets/themes/paragon.conf b/novelwriter/assets/themes/paragon.conf index f6a0cc6f..d8ef6919 100644 --- a/novelwriter/assets/themes/paragon.conf +++ b/novelwriter/assets/themes/paragon.conf @@ -28,6 +28,26 @@ active = #00ffe1 inactive = faded disabled = faded:D150 +[Icon] +tool = default +accept = green +reject = red +action = blue +altaction = orange +apply = green +create = yellow +destroy = faded +reset = green +add = green +change = green +remove = red +scformat = default +mdformat = orange +systemio = yellow +info = blue +warning = orange +error = red + [Palette] window = #10151b windowtext = default diff --git a/novelwriter/assets/themes/primer_light.conf b/novelwriter/assets/themes/primer_light.conf index 97262d06..9eb865f2 100644 --- a/novelwriter/assets/themes/primer_light.conf +++ b/novelwriter/assets/themes/primer_light.conf @@ -29,6 +29,26 @@ active = #24292f inactive = faded disabled = #afb8c1 +[Icon] +tool = default +accept = green +reject = red +action = blue +altaction = orange +apply = green +create = yellow +destroy = faded +reset = green +add = green +change = green +remove = red +scformat = default +mdformat = orange +systemio = yellow +info = blue +warning = orange +error = red + [Palette] window = #eaeef2 windowtext = default diff --git a/novelwriter/assets/themes/primer_night.conf b/novelwriter/assets/themes/primer_night.conf index e25f06c0..73ae6270 100644 --- a/novelwriter/assets/themes/primer_night.conf +++ b/novelwriter/assets/themes/primer_night.conf @@ -29,6 +29,26 @@ active = default inactive = faded disabled = #484f58 +[Icon] +tool = default +accept = green +reject = red +action = blue +altaction = orange +apply = green +create = yellow +destroy = faded +reset = green +add = green +change = green +remove = red +scformat = default +mdformat = orange +systemio = yellow +info = blue +warning = orange +error = red + [Palette] window = #161b22 windowtext = default diff --git a/novelwriter/assets/themes/ruby_day.conf b/novelwriter/assets/themes/ruby_day.conf index d01aa090..d7b3e033 100644 --- a/novelwriter/assets/themes/ruby_day.conf +++ b/novelwriter/assets/themes/ruby_day.conf @@ -27,6 +27,26 @@ active = default inactive = red disabled = faded +[Icon] +tool = default +accept = green +reject = red +action = blue +altaction = orange +apply = green +create = yellow +destroy = faded +reset = green +add = green +change = green +remove = red +scformat = default +mdformat = orange +systemio = yellow +info = blue +warning = orange +error = red + [Palette] window = #f1d9d9 windowtext = default diff --git a/novelwriter/assets/themes/ruby_night.conf b/novelwriter/assets/themes/ruby_night.conf index 9c6d988c..c73b6579 100644 --- a/novelwriter/assets/themes/ruby_night.conf +++ b/novelwriter/assets/themes/ruby_night.conf @@ -27,6 +27,26 @@ active = default inactive = red disabled = faded +[Icon] +tool = default +accept = green +reject = red +action = blue +altaction = orange +apply = green +create = yellow +destroy = faded +reset = green +add = green +change = green +remove = red +scformat = default +mdformat = orange +systemio = yellow +info = blue +warning = orange +error = red + [Palette] window = #0e0e11 windowtext = default diff --git a/novelwriter/assets/themes/selenium_dark.conf b/novelwriter/assets/themes/selenium_dark.conf index e19e525e..406205ab 100644 --- a/novelwriter/assets/themes/selenium_dark.conf +++ b/novelwriter/assets/themes/selenium_dark.conf @@ -27,6 +27,26 @@ active = #c7c0d2 inactive = red disabled = #625b70 +[Icon] +tool = default +accept = green +reject = red +action = blue +altaction = orange +apply = green +create = yellow +destroy = faded +reset = green +add = green +change = green +remove = red +scformat = default +mdformat = orange +systemio = yellow +info = blue +warning = orange +error = red + [Palette] window = #0f0e13 windowtext = #c7c0d2 diff --git a/novelwriter/assets/themes/selenium_light.conf b/novelwriter/assets/themes/selenium_light.conf index 7b6fe9b5..28dbfe7f 100644 --- a/novelwriter/assets/themes/selenium_light.conf +++ b/novelwriter/assets/themes/selenium_light.conf @@ -27,6 +27,26 @@ active = default inactive = red disabled = faded +[Icon] +tool = default +accept = green +reject = red +action = blue +altaction = orange +apply = green +create = yellow +destroy = faded +reset = green +add = green +change = green +remove = red +scformat = default +mdformat = orange +systemio = yellow +info = blue +warning = orange +error = red + [Palette] window = #d2cfda windowtext = #29252f diff --git a/novelwriter/assets/themes/sepia_dark.conf b/novelwriter/assets/themes/sepia_dark.conf index 358728f5..a4980608 100644 --- a/novelwriter/assets/themes/sepia_dark.conf +++ b/novelwriter/assets/themes/sepia_dark.conf @@ -27,6 +27,26 @@ active = default inactive = red disabled = faded +[Icon] +tool = default +accept = green +reject = red +action = blue +altaction = orange +apply = green +create = yellow +destroy = faded +reset = green +add = green +change = green +remove = red +scformat = default +mdformat = orange +systemio = yellow +info = blue +warning = orange +error = red + [Palette] window = #251b18 windowtext = #ceb3a2 diff --git a/novelwriter/assets/themes/sepia_light.conf b/novelwriter/assets/themes/sepia_light.conf index 8c85e2e0..06dfa52b 100644 --- a/novelwriter/assets/themes/sepia_light.conf +++ b/novelwriter/assets/themes/sepia_light.conf @@ -27,6 +27,26 @@ active = #86685b inactive = red disabled = #b6a096 +[Icon] +tool = default +accept = green +reject = red +action = blue +altaction = orange +apply = green +create = yellow +destroy = faded +reset = green +add = green +change = green +remove = red +scformat = default +mdformat = orange +systemio = yellow +info = blue +warning = orange +error = red + [Palette] window = #e2d3c1 windowtext = default diff --git a/novelwriter/assets/themes/snazzy.conf b/novelwriter/assets/themes/snazzy.conf index 68c270fb..b0dd2dc5 100644 --- a/novelwriter/assets/themes/snazzy.conf +++ b/novelwriter/assets/themes/snazzy.conf @@ -42,6 +42,26 @@ active = green inactive = red disabled = faded +[Icon] +tool = default +accept = green +reject = red +action = blue +altaction = orange +apply = green +create = yellow +destroy = faded +reset = green +add = green +change = green +remove = red +scformat = default +mdformat = orange +systemio = yellow +info = blue +warning = orange +error = red + [Palette] window = #f3f4f5 windowtext = default diff --git a/novelwriter/assets/themes/solarized_dark.conf b/novelwriter/assets/themes/solarized_dark.conf index e9292dbd..acfb1bf5 100644 --- a/novelwriter/assets/themes/solarized_dark.conf +++ b/novelwriter/assets/themes/solarized_dark.conf @@ -48,6 +48,26 @@ active = green inactive = red disabled = faded +[Icon] +tool = default +accept = green +reject = red +action = blue +altaction = orange +apply = green +create = yellow +destroy = faded +reset = green +add = green +change = green +remove = red +scformat = default +mdformat = orange +systemio = yellow +info = blue +warning = orange +error = red + [Palette] window = #073642 windowtext = default diff --git a/novelwriter/assets/themes/solarized_light.conf b/novelwriter/assets/themes/solarized_light.conf index bc8abcfd..7f487603 100644 --- a/novelwriter/assets/themes/solarized_light.conf +++ b/novelwriter/assets/themes/solarized_light.conf @@ -48,6 +48,26 @@ active = green inactive = red disabled = faded +[Icon] +tool = default +accept = green +reject = red +action = blue +altaction = orange +apply = green +create = yellow +destroy = faded +reset = green +add = green +change = green +remove = red +scformat = default +mdformat = orange +systemio = yellow +info = blue +warning = orange +error = red + [Palette] window = #eee8d5 windowtext = default diff --git a/novelwriter/assets/themes/sultana_light.conf b/novelwriter/assets/themes/sultana_light.conf index 1a29f079..b24d3557 100644 --- a/novelwriter/assets/themes/sultana_light.conf +++ b/novelwriter/assets/themes/sultana_light.conf @@ -27,6 +27,26 @@ active = #77606e inactive = red disabled = faded +[Icon] +tool = default +accept = green +reject = red +action = blue +altaction = orange +apply = green +create = yellow +destroy = faded +reset = green +add = green +change = green +remove = red +scformat = default +mdformat = orange +systemio = yellow +info = blue +warning = orange +error = red + [Palette] window = #f0e4e4 windowtext = default diff --git a/novelwriter/assets/themes/sultana_night.conf b/novelwriter/assets/themes/sultana_night.conf index a952c87e..887ad2e4 100644 --- a/novelwriter/assets/themes/sultana_night.conf +++ b/novelwriter/assets/themes/sultana_night.conf @@ -27,6 +27,26 @@ active = default inactive = red disabled = #85748a +[Icon] +tool = default +accept = green +reject = red +action = blue +altaction = orange +apply = green +create = yellow +destroy = faded +reset = green +add = green +change = green +remove = red +scformat = default +mdformat = orange +systemio = yellow +info = blue +warning = orange +error = red + [Palette] window = #221a23 windowtext = default diff --git a/novelwriter/assets/themes/tango_dark.conf b/novelwriter/assets/themes/tango_dark.conf index 0e5707cb..7c3b6a1a 100644 --- a/novelwriter/assets/themes/tango_dark.conf +++ b/novelwriter/assets/themes/tango_dark.conf @@ -43,6 +43,26 @@ active = green inactive = red disabled = faded +[Icon] +tool = default +accept = green +reject = red +action = blue +altaction = orange +apply = green +create = yellow +destroy = faded +reset = green +add = green +change = green +remove = red +scformat = default +mdformat = orange +systemio = yellow +info = blue +warning = orange +error = red + [Palette] window = base:L140 windowtext = default diff --git a/novelwriter/assets/themes/tango_light.conf b/novelwriter/assets/themes/tango_light.conf index efbcaf20..1ff2b78a 100644 --- a/novelwriter/assets/themes/tango_light.conf +++ b/novelwriter/assets/themes/tango_light.conf @@ -43,6 +43,26 @@ active = green inactive = red disabled = faded +[Icon] +tool = default +accept = green +reject = red +action = blue +altaction = orange +apply = green +create = yellow +destroy = faded +reset = green +add = green +change = green +remove = red +scformat = default +mdformat = orange +systemio = yellow +info = blue +warning = orange +error = red + [Palette] window = base:D110 windowtext = default diff --git a/novelwriter/assets/themes/tomorrow.conf b/novelwriter/assets/themes/tomorrow.conf index 70b123ee..beca3841 100644 --- a/novelwriter/assets/themes/tomorrow.conf +++ b/novelwriter/assets/themes/tomorrow.conf @@ -49,6 +49,26 @@ active = green inactive = red disabled = faded +[Icon] +tool = default +accept = green +reject = red +action = blue +altaction = orange +apply = green +create = yellow +destroy = faded +reset = green +add = green +change = green +remove = red +scformat = default +mdformat = orange +systemio = yellow +info = blue +warning = orange +error = red + [Palette] window = #efefef windowtext = #000000 diff --git a/novelwriter/assets/themes/tomorrow_night.conf b/novelwriter/assets/themes/tomorrow_night.conf index 9bd4fbea..3531e63d 100644 --- a/novelwriter/assets/themes/tomorrow_night.conf +++ b/novelwriter/assets/themes/tomorrow_night.conf @@ -49,6 +49,26 @@ active = green inactive = red disabled = faded +[Icon] +tool = default +accept = green +reject = red +action = blue +altaction = orange +apply = green +create = yellow +destroy = faded +reset = green +add = green +change = green +remove = red +scformat = default +mdformat = orange +systemio = yellow +info = blue +warning = orange +error = red + [Palette] window = #282a2e windowtext = default diff --git a/novelwriter/assets/themes/tomorrow_night_blue.conf b/novelwriter/assets/themes/tomorrow_night_blue.conf index 409cfd25..df9e0715 100644 --- a/novelwriter/assets/themes/tomorrow_night_blue.conf +++ b/novelwriter/assets/themes/tomorrow_night_blue.conf @@ -49,6 +49,26 @@ active = green inactive = red disabled = faded +[Icon] +tool = default +accept = green +reject = red +action = blue +altaction = orange +apply = green +create = yellow +destroy = faded +reset = green +add = green +change = green +remove = red +scformat = default +mdformat = orange +systemio = yellow +info = blue +warning = orange +error = red + [Palette] window = base:L125 windowtext = default diff --git a/novelwriter/assets/themes/tomorrow_night_bright.conf b/novelwriter/assets/themes/tomorrow_night_bright.conf index 14a1a4d6..056f7f94 100644 --- a/novelwriter/assets/themes/tomorrow_night_bright.conf +++ b/novelwriter/assets/themes/tomorrow_night_bright.conf @@ -49,6 +49,26 @@ active = green inactive = red disabled = faded +[Icon] +tool = default +accept = green +reject = red +action = blue +altaction = orange +apply = green +create = yellow +destroy = faded +reset = green +add = green +change = green +remove = red +scformat = default +mdformat = orange +systemio = yellow +info = blue +warning = orange +error = red + [Palette] window = #181818 windowtext = default diff --git a/novelwriter/assets/themes/tomorrow_night_eighties.conf b/novelwriter/assets/themes/tomorrow_night_eighties.conf index af7190fc..e3b71444 100644 --- a/novelwriter/assets/themes/tomorrow_night_eighties.conf +++ b/novelwriter/assets/themes/tomorrow_night_eighties.conf @@ -49,6 +49,26 @@ active = green inactive = red disabled = faded +[Icon] +tool = default +accept = green +reject = red +action = blue +altaction = orange +apply = green +create = yellow +destroy = faded +reset = green +add = green +change = green +remove = red +scformat = default +mdformat = orange +systemio = yellow +info = blue +warning = orange +error = red + [Palette] window = #393939 windowtext = default diff --git a/novelwriter/assets/themes/vivid_black_green.conf b/novelwriter/assets/themes/vivid_black_green.conf index 2c0101e3..e6a2f0cd 100644 --- a/novelwriter/assets/themes/vivid_black_green.conf +++ b/novelwriter/assets/themes/vivid_black_green.conf @@ -29,6 +29,26 @@ active = green inactive = faded disabled = faded:D175 +[Icon] +tool = default +accept = green +reject = red +action = blue +altaction = orange +apply = green +create = yellow +destroy = faded +reset = green +add = green +change = green +remove = red +scformat = default +mdformat = orange +systemio = yellow +info = blue +warning = orange +error = red + [Palette] window = base windowtext = default diff --git a/novelwriter/assets/themes/vivid_black_red.conf b/novelwriter/assets/themes/vivid_black_red.conf index c7f3f6b8..cb615301 100644 --- a/novelwriter/assets/themes/vivid_black_red.conf +++ b/novelwriter/assets/themes/vivid_black_red.conf @@ -29,6 +29,26 @@ active = red inactive = faded disabled = faded:D175 +[Icon] +tool = default +accept = green +reject = red +action = blue +altaction = orange +apply = green +create = yellow +destroy = faded +reset = green +add = green +change = green +remove = red +scformat = default +mdformat = orange +systemio = yellow +info = blue +warning = orange +error = red + [Palette] window = base windowtext = default diff --git a/novelwriter/assets/themes/vivid_white_green.conf b/novelwriter/assets/themes/vivid_white_green.conf index 5d421a2a..f30474b4 100644 --- a/novelwriter/assets/themes/vivid_white_green.conf +++ b/novelwriter/assets/themes/vivid_white_green.conf @@ -29,6 +29,26 @@ active = green inactive = faded disabled = faded:L135 +[Icon] +tool = default +accept = green +reject = red +action = blue +altaction = orange +apply = green +create = yellow +destroy = faded +reset = green +add = green +change = green +remove = red +scformat = default +mdformat = orange +systemio = yellow +info = blue +warning = orange +error = red + [Palette] window = base windowtext = default diff --git a/novelwriter/assets/themes/vivid_white_red.conf b/novelwriter/assets/themes/vivid_white_red.conf index dc40bfbe..08a52c02 100644 --- a/novelwriter/assets/themes/vivid_white_red.conf +++ b/novelwriter/assets/themes/vivid_white_red.conf @@ -29,6 +29,26 @@ active = red inactive = faded disabled = faded:L135 +[Icon] +tool = default +accept = green +reject = red +action = blue +altaction = orange +apply = green +create = yellow +destroy = faded +reset = green +add = green +change = green +remove = red +scformat = default +mdformat = orange +systemio = yellow +info = blue +warning = orange +error = red + [Palette] window = base windowtext = default diff --git a/novelwriter/assets/themes/warpgate.conf b/novelwriter/assets/themes/warpgate.conf index 2cb0108f..2b1852ed 100644 --- a/novelwriter/assets/themes/warpgate.conf +++ b/novelwriter/assets/themes/warpgate.conf @@ -28,6 +28,26 @@ active = green inactive = red disabled = faded +[Icon] +tool = default +accept = green +reject = red +action = blue +altaction = orange +apply = green +create = yellow +destroy = faded +reset = green +add = green +change = green +remove = red +scformat = default +mdformat = orange +systemio = yellow +info = blue +warning = orange +error = red + [Palette] window = base:D150 windowtext = default diff --git a/novelwriter/assets/themes/waterlily_dark.conf b/novelwriter/assets/themes/waterlily_dark.conf index 46b356bf..246f06d3 100644 --- a/novelwriter/assets/themes/waterlily_dark.conf +++ b/novelwriter/assets/themes/waterlily_dark.conf @@ -27,6 +27,26 @@ active = #ff9fbd inactive = faded disabled = #374a5c +[Icon] +tool = default +accept = green +reject = red +action = blue +altaction = orange +apply = green +create = yellow +destroy = faded +reset = green +add = green +change = green +remove = red +scformat = default +mdformat = orange +systemio = yellow +info = blue +warning = orange +error = red + [Palette] window = #151f25 windowtext = default diff --git a/novelwriter/assets/themes/waterlily_light.conf b/novelwriter/assets/themes/waterlily_light.conf index b86d4f2c..4acf2979 100644 --- a/novelwriter/assets/themes/waterlily_light.conf +++ b/novelwriter/assets/themes/waterlily_light.conf @@ -27,6 +27,26 @@ active = #da7b8f inactive = faded disabled = #b6c9ba +[Icon] +tool = default +accept = green +reject = red +action = blue +altaction = orange +apply = green +create = yellow +destroy = faded +reset = green +add = green +change = green +remove = red +scformat = default +mdformat = orange +systemio = yellow +info = blue +warning = orange +error = red + [Palette] window = #e2eee7 windowtext = default From 4a94ab86c963fa5aee9ffcb2ba02c2968201c1c5 Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Sun, 26 Oct 2025 14:16:05 +0100 Subject: [PATCH 75/79] Add sidebar icon colour setting and rename format icon settings --- novelwriter/gui/sidebar.py | 18 +++++++++--------- novelwriter/gui/theme.py | 5 +++-- 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/novelwriter/gui/sidebar.py b/novelwriter/gui/sidebar.py index 5aceb0d5..b03302ac 100644 --- a/novelwriter/gui/sidebar.py +++ b/novelwriter/gui/sidebar.py @@ -142,14 +142,14 @@ class GuiSideBar(QWidget): self.tbTheme.setStyleSheet(buttonStyle) self.tbSettings.setStyleSheet(buttonStyle) - self.tbProject.setThemeIcon("sb_project", "default") - self.tbNovel.setThemeIcon("sb_novel", "default") - self.tbSearch.setThemeIcon("sb_search", "default") - self.tbOutline.setThemeIcon("sb_outline", "default") - self.tbBuild.setThemeIcon("sb_build", "default") - self.tbDetails.setThemeIcon("sb_details", "default") - self.tbStats.setThemeIcon("sb_stats", "default") - self.tbSettings.setThemeIcon("settings", "default") + self.tbProject.setThemeIcon("sb_project", "sidebar") + self.tbNovel.setThemeIcon("sb_novel", "sidebar") + self.tbSearch.setThemeIcon("sb_search", "sidebar") + self.tbOutline.setThemeIcon("sb_outline", "sidebar") + self.tbBuild.setThemeIcon("sb_build", "sidebar") + self.tbDetails.setThemeIcon("sb_details", "sidebar") + self.tbStats.setThemeIcon("sb_stats", "sidebar") + self.tbSettings.setThemeIcon("settings", "sidebar") self._setThemeModeIcon() @@ -176,7 +176,7 @@ class GuiSideBar(QWidget): def _setThemeModeIcon(self) -> None: """Set the theme button icon.""" - self.tbTheme.setThemeIcon(nwLabels.THEME_MODE_ICON[CONFIG.themeMode], "default") + self.tbTheme.setThemeIcon(nwLabels.THEME_MODE_ICON[CONFIG.themeMode], "sidebar") self.tbTheme.setToolTip(trConst(nwLabels.THEME_MODE_LABEL[CONFIG.themeMode])) diff --git a/novelwriter/gui/theme.py b/novelwriter/gui/theme.py index 8f92304d..fb0935ab 100644 --- a/novelwriter/gui/theme.py +++ b/novelwriter/gui/theme.py @@ -361,6 +361,7 @@ class GuiTheme: sec = "Icon" if parser.has_section(sec): self._setBaseColor("tool", self._readColor(parser, sec, "tool")) + self._setBaseColor("sidebar", self._readColor(parser, sec, "sidebar")) self._setBaseColor("accept", self._readColor(parser, sec, "accept")) self._setBaseColor("reject", self._readColor(parser, sec, "reject")) self._setBaseColor("action", self._readColor(parser, sec, "action")) @@ -372,8 +373,8 @@ class GuiTheme: self._setBaseColor("add", self._readColor(parser, sec, "add")) self._setBaseColor("change", self._readColor(parser, sec, "change")) self._setBaseColor("remove", self._readColor(parser, sec, "remove")) - self._setBaseColor("scformat", self._readColor(parser, sec, "scformat")) - self._setBaseColor("mdformat", self._readColor(parser, sec, "mdformat")) + self._setBaseColor("shortcode", self._readColor(parser, sec, "shortcode")) + self._setBaseColor("markdown", self._readColor(parser, sec, "markdown")) self._setBaseColor("systemio", self._readColor(parser, sec, "systemio")) self._setBaseColor("info", self._readColor(parser, sec, "info")) self._setBaseColor("warning", self._readColor(parser, sec, "warning")) From 073838af6ea78d9de2263573d8f75ff6a7c4ee04 Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Sun, 26 Oct 2025 14:16:20 +0100 Subject: [PATCH 76/79] Update theme files --- novelwriter/assets/themes/aura.conf | 5 +++-- novelwriter/assets/themes/aura_bright.conf | 5 +++-- novelwriter/assets/themes/aura_soft.conf | 5 +++-- novelwriter/assets/themes/b2t_garden_dark.conf | 5 +++-- novelwriter/assets/themes/b2t_garden_light.conf | 5 +++-- novelwriter/assets/themes/b2t_suburb_dark.conf | 5 +++-- novelwriter/assets/themes/b2t_suburb_light.conf | 5 +++-- novelwriter/assets/themes/b4t_classic_o_dark.conf | 5 +++-- novelwriter/assets/themes/b4t_classic_o_light.conf | 5 +++-- novelwriter/assets/themes/b4t_modern_c_dark.conf | 5 +++-- novelwriter/assets/themes/b4t_modern_c_light.conf | 5 +++-- novelwriter/assets/themes/blue_streak_dark.conf | 5 +++-- novelwriter/assets/themes/blue_streak_light.conf | 5 +++-- novelwriter/assets/themes/castle_day.conf | 5 +++-- novelwriter/assets/themes/castle_night.conf | 5 +++-- novelwriter/assets/themes/chalky_soil.conf | 5 +++-- novelwriter/assets/themes/chernozem.conf | 5 +++-- novelwriter/assets/themes/cyberpunk_night.conf | 5 +++-- novelwriter/assets/themes/default_dark.conf | 5 +++-- novelwriter/assets/themes/default_light.conf | 5 +++-- novelwriter/assets/themes/dracula.conf | 5 +++-- novelwriter/assets/themes/espresso.conf | 5 +++-- novelwriter/assets/themes/everforest_dark.conf | 5 +++-- novelwriter/assets/themes/everforest_light.conf | 5 +++-- novelwriter/assets/themes/floral_daydream.conf | 5 +++-- novelwriter/assets/themes/floral_midnight.conf | 5 +++-- novelwriter/assets/themes/full_moon.conf | 5 +++-- novelwriter/assets/themes/grey_dark.conf | 5 +++-- novelwriter/assets/themes/grey_light.conf | 5 +++-- novelwriter/assets/themes/horizon_dark.conf | 5 +++-- novelwriter/assets/themes/horizon_light.conf | 5 +++-- novelwriter/assets/themes/lcars.conf | 5 +++-- novelwriter/assets/themes/light_owl.conf | 5 +++-- novelwriter/assets/themes/new_moon.conf | 5 +++-- novelwriter/assets/themes/night_owl.conf | 5 +++-- novelwriter/assets/themes/noctis.conf | 5 +++-- novelwriter/assets/themes/noctis_lux.conf | 5 +++-- novelwriter/assets/themes/nord.conf | 5 +++-- novelwriter/assets/themes/nordlicht.conf | 5 +++-- novelwriter/assets/themes/otium_dark.conf | 5 +++-- novelwriter/assets/themes/otium_light.conf | 5 +++-- novelwriter/assets/themes/paragon.conf | 5 +++-- novelwriter/assets/themes/primer_light.conf | 5 +++-- novelwriter/assets/themes/primer_night.conf | 5 +++-- novelwriter/assets/themes/ruby_day.conf | 5 +++-- novelwriter/assets/themes/ruby_night.conf | 5 +++-- novelwriter/assets/themes/selenium_dark.conf | 5 +++-- novelwriter/assets/themes/selenium_light.conf | 5 +++-- novelwriter/assets/themes/sepia_dark.conf | 5 +++-- novelwriter/assets/themes/sepia_light.conf | 5 +++-- novelwriter/assets/themes/snazzy.conf | 5 +++-- novelwriter/assets/themes/solarized_dark.conf | 5 +++-- novelwriter/assets/themes/solarized_light.conf | 5 +++-- novelwriter/assets/themes/sultana_light.conf | 5 +++-- novelwriter/assets/themes/sultana_night.conf | 5 +++-- novelwriter/assets/themes/tango_dark.conf | 5 +++-- novelwriter/assets/themes/tango_light.conf | 5 +++-- novelwriter/assets/themes/tomorrow.conf | 5 +++-- novelwriter/assets/themes/tomorrow_night.conf | 5 +++-- novelwriter/assets/themes/tomorrow_night_blue.conf | 5 +++-- novelwriter/assets/themes/tomorrow_night_bright.conf | 5 +++-- novelwriter/assets/themes/tomorrow_night_eighties.conf | 5 +++-- novelwriter/assets/themes/vivid_black_green.conf | 5 +++-- novelwriter/assets/themes/vivid_black_red.conf | 5 +++-- novelwriter/assets/themes/vivid_white_green.conf | 5 +++-- novelwriter/assets/themes/vivid_white_red.conf | 5 +++-- novelwriter/assets/themes/warpgate.conf | 5 +++-- novelwriter/assets/themes/waterlily_dark.conf | 5 +++-- novelwriter/assets/themes/waterlily_light.conf | 5 +++-- 69 files changed, 207 insertions(+), 138 deletions(-) diff --git a/novelwriter/assets/themes/aura.conf b/novelwriter/assets/themes/aura.conf index ece05583..5a62ffb8 100644 --- a/novelwriter/assets/themes/aura.conf +++ b/novelwriter/assets/themes/aura.conf @@ -31,6 +31,7 @@ disabled = faded [Icon] tool = default +sidebar = default accept = green reject = red action = blue @@ -42,8 +43,8 @@ reset = green add = green change = green remove = red -scformat = default -mdformat = orange +shortcode = default +markdown = orange systemio = yellow info = blue warning = orange diff --git a/novelwriter/assets/themes/aura_bright.conf b/novelwriter/assets/themes/aura_bright.conf index d2813a88..0a4881b3 100644 --- a/novelwriter/assets/themes/aura_bright.conf +++ b/novelwriter/assets/themes/aura_bright.conf @@ -29,6 +29,7 @@ disabled = faded [Icon] tool = default +sidebar = default accept = green reject = red action = blue @@ -40,8 +41,8 @@ reset = green add = green change = green remove = red -scformat = default -mdformat = orange +shortcode = default +markdown = orange systemio = yellow info = blue warning = orange diff --git a/novelwriter/assets/themes/aura_soft.conf b/novelwriter/assets/themes/aura_soft.conf index d975ed7e..a3c3ed00 100644 --- a/novelwriter/assets/themes/aura_soft.conf +++ b/novelwriter/assets/themes/aura_soft.conf @@ -31,6 +31,7 @@ disabled = faded [Icon] tool = default +sidebar = default accept = green reject = red action = blue @@ -42,8 +43,8 @@ reset = green add = green change = green remove = red -scformat = default -mdformat = orange +shortcode = default +markdown = orange systemio = yellow info = blue warning = orange diff --git a/novelwriter/assets/themes/b2t_garden_dark.conf b/novelwriter/assets/themes/b2t_garden_dark.conf index b10c6862..c55a0987 100644 --- a/novelwriter/assets/themes/b2t_garden_dark.conf +++ b/novelwriter/assets/themes/b2t_garden_dark.conf @@ -31,6 +31,7 @@ disabled = #696d69 [Icon] tool = default +sidebar = default accept = green reject = red action = blue @@ -42,8 +43,8 @@ reset = green add = green change = green remove = red -scformat = default -mdformat = orange +shortcode = default +markdown = orange systemio = yellow info = blue warning = orange diff --git a/novelwriter/assets/themes/b2t_garden_light.conf b/novelwriter/assets/themes/b2t_garden_light.conf index 25a2dd3f..c5a33fed 100644 --- a/novelwriter/assets/themes/b2t_garden_light.conf +++ b/novelwriter/assets/themes/b2t_garden_light.conf @@ -31,6 +31,7 @@ disabled = #aab1aa [Icon] tool = default +sidebar = default accept = green reject = red action = blue @@ -42,8 +43,8 @@ reset = green add = green change = green remove = red -scformat = default -mdformat = orange +shortcode = default +markdown = orange systemio = yellow info = blue warning = orange diff --git a/novelwriter/assets/themes/b2t_suburb_dark.conf b/novelwriter/assets/themes/b2t_suburb_dark.conf index 3f1b6937..a3050b8d 100644 --- a/novelwriter/assets/themes/b2t_suburb_dark.conf +++ b/novelwriter/assets/themes/b2t_suburb_dark.conf @@ -31,6 +31,7 @@ disabled = #575c79 [Icon] tool = default +sidebar = default accept = green reject = red action = blue @@ -42,8 +43,8 @@ reset = green add = green change = green remove = red -scformat = default -mdformat = orange +shortcode = default +markdown = orange systemio = yellow info = blue warning = orange diff --git a/novelwriter/assets/themes/b2t_suburb_light.conf b/novelwriter/assets/themes/b2t_suburb_light.conf index d494a21e..8c1f7de3 100644 --- a/novelwriter/assets/themes/b2t_suburb_light.conf +++ b/novelwriter/assets/themes/b2t_suburb_light.conf @@ -31,6 +31,7 @@ disabled = #b6bad1 [Icon] tool = default +sidebar = default accept = green reject = red action = blue @@ -42,8 +43,8 @@ reset = green add = green change = green remove = red -scformat = default -mdformat = orange +shortcode = default +markdown = orange systemio = yellow info = blue warning = orange diff --git a/novelwriter/assets/themes/b4t_classic_o_dark.conf b/novelwriter/assets/themes/b4t_classic_o_dark.conf index 795794d6..1b7e4e6f 100644 --- a/novelwriter/assets/themes/b4t_classic_o_dark.conf +++ b/novelwriter/assets/themes/b4t_classic_o_dark.conf @@ -31,6 +31,7 @@ disabled = #454f5f [Icon] tool = default +sidebar = default accept = green reject = red action = blue @@ -42,8 +43,8 @@ reset = green add = green change = green remove = red -scformat = default -mdformat = orange +shortcode = default +markdown = orange systemio = yellow info = blue warning = orange diff --git a/novelwriter/assets/themes/b4t_classic_o_light.conf b/novelwriter/assets/themes/b4t_classic_o_light.conf index 781d772f..60a06c42 100644 --- a/novelwriter/assets/themes/b4t_classic_o_light.conf +++ b/novelwriter/assets/themes/b4t_classic_o_light.conf @@ -31,6 +31,7 @@ disabled = #acb5c3 [Icon] tool = default +sidebar = default accept = green reject = red action = blue @@ -42,8 +43,8 @@ reset = green add = green change = green remove = red -scformat = default -mdformat = orange +shortcode = default +markdown = orange systemio = yellow info = blue warning = orange diff --git a/novelwriter/assets/themes/b4t_modern_c_dark.conf b/novelwriter/assets/themes/b4t_modern_c_dark.conf index 2af49bae..844bdf83 100644 --- a/novelwriter/assets/themes/b4t_modern_c_dark.conf +++ b/novelwriter/assets/themes/b4t_modern_c_dark.conf @@ -31,6 +31,7 @@ disabled = #5d5f6f [Icon] tool = default +sidebar = default accept = green reject = red action = blue @@ -42,8 +43,8 @@ reset = green add = green change = green remove = red -scformat = default -mdformat = orange +shortcode = default +markdown = orange systemio = yellow info = blue warning = orange diff --git a/novelwriter/assets/themes/b4t_modern_c_light.conf b/novelwriter/assets/themes/b4t_modern_c_light.conf index b9b02cfe..c5358a2c 100644 --- a/novelwriter/assets/themes/b4t_modern_c_light.conf +++ b/novelwriter/assets/themes/b4t_modern_c_light.conf @@ -31,6 +31,7 @@ disabled = #BBBDC9 [Icon] tool = default +sidebar = default accept = green reject = red action = blue @@ -42,8 +43,8 @@ reset = green add = green change = green remove = red -scformat = default -mdformat = orange +shortcode = default +markdown = orange systemio = yellow info = blue warning = orange diff --git a/novelwriter/assets/themes/blue_streak_dark.conf b/novelwriter/assets/themes/blue_streak_dark.conf index e42ec1b3..fc14f4be 100644 --- a/novelwriter/assets/themes/blue_streak_dark.conf +++ b/novelwriter/assets/themes/blue_streak_dark.conf @@ -31,6 +31,7 @@ disabled = faded [Icon] tool = default +sidebar = default accept = green reject = red action = blue @@ -42,8 +43,8 @@ reset = green add = green change = green remove = red -scformat = default -mdformat = orange +shortcode = default +markdown = orange systemio = yellow info = blue warning = orange diff --git a/novelwriter/assets/themes/blue_streak_light.conf b/novelwriter/assets/themes/blue_streak_light.conf index 48937537..52e8ce73 100644 --- a/novelwriter/assets/themes/blue_streak_light.conf +++ b/novelwriter/assets/themes/blue_streak_light.conf @@ -31,6 +31,7 @@ disabled = faded [Icon] tool = default +sidebar = default accept = green reject = red action = blue @@ -42,8 +43,8 @@ reset = green add = green change = green remove = red -scformat = default -mdformat = orange +shortcode = default +markdown = orange systemio = yellow info = blue warning = orange diff --git a/novelwriter/assets/themes/castle_day.conf b/novelwriter/assets/themes/castle_day.conf index 00c25c46..a860caa5 100644 --- a/novelwriter/assets/themes/castle_day.conf +++ b/novelwriter/assets/themes/castle_day.conf @@ -29,6 +29,7 @@ disabled = #b4aca5 [Icon] tool = default +sidebar = default accept = green reject = red action = blue @@ -40,8 +41,8 @@ reset = green add = green change = green remove = red -scformat = default -mdformat = orange +shortcode = default +markdown = orange systemio = yellow info = blue warning = orange diff --git a/novelwriter/assets/themes/castle_night.conf b/novelwriter/assets/themes/castle_night.conf index 46978375..90532b3b 100644 --- a/novelwriter/assets/themes/castle_night.conf +++ b/novelwriter/assets/themes/castle_night.conf @@ -29,6 +29,7 @@ disabled = #4b4f5c [Icon] tool = default +sidebar = default accept = green reject = red action = blue @@ -40,8 +41,8 @@ reset = green add = green change = green remove = red -scformat = default -mdformat = orange +shortcode = default +markdown = orange systemio = yellow info = blue warning = orange diff --git a/novelwriter/assets/themes/chalky_soil.conf b/novelwriter/assets/themes/chalky_soil.conf index 23aaefa1..16e89018 100644 --- a/novelwriter/assets/themes/chalky_soil.conf +++ b/novelwriter/assets/themes/chalky_soil.conf @@ -29,6 +29,7 @@ disabled = faded:L135 [Icon] tool = default +sidebar = default accept = green reject = red action = blue @@ -40,8 +41,8 @@ reset = green add = green change = green remove = red -scformat = default -mdformat = orange +shortcode = default +markdown = orange systemio = yellow info = blue warning = orange diff --git a/novelwriter/assets/themes/chernozem.conf b/novelwriter/assets/themes/chernozem.conf index fcb2caeb..ea6a74a8 100644 --- a/novelwriter/assets/themes/chernozem.conf +++ b/novelwriter/assets/themes/chernozem.conf @@ -29,6 +29,7 @@ disabled = #504742 [Icon] tool = default +sidebar = default accept = green reject = red action = blue @@ -40,8 +41,8 @@ reset = green add = green change = green remove = red -scformat = default -mdformat = orange +shortcode = default +markdown = orange systemio = yellow info = blue warning = orange diff --git a/novelwriter/assets/themes/cyberpunk_night.conf b/novelwriter/assets/themes/cyberpunk_night.conf index ac67d376..1d5c4ce0 100644 --- a/novelwriter/assets/themes/cyberpunk_night.conf +++ b/novelwriter/assets/themes/cyberpunk_night.conf @@ -30,6 +30,7 @@ disabled = faded [Icon] tool = default +sidebar = default accept = green reject = red action = blue @@ -41,8 +42,8 @@ reset = green add = green change = green remove = red -scformat = default -mdformat = orange +shortcode = default +markdown = orange systemio = yellow info = blue warning = orange diff --git a/novelwriter/assets/themes/default_dark.conf b/novelwriter/assets/themes/default_dark.conf index 69bdbdee..6db770f1 100644 --- a/novelwriter/assets/themes/default_dark.conf +++ b/novelwriter/assets/themes/default_dark.conf @@ -31,6 +31,7 @@ disabled = faded [Icon] tool = default +sidebar = default accept = green reject = red action = blue @@ -42,8 +43,8 @@ reset = green add = green change = green remove = red -scformat = default -mdformat = orange +shortcode = default +markdown = orange systemio = yellow info = blue warning = orange diff --git a/novelwriter/assets/themes/default_light.conf b/novelwriter/assets/themes/default_light.conf index 63ee995c..c9ef8dc7 100644 --- a/novelwriter/assets/themes/default_light.conf +++ b/novelwriter/assets/themes/default_light.conf @@ -31,6 +31,7 @@ disabled = faded [Icon] tool = default +sidebar = default accept = green reject = red action = blue @@ -42,8 +43,8 @@ reset = green add = green change = green remove = red -scformat = default -mdformat = orange +shortcode = default +markdown = orange systemio = yellow info = blue warning = orange diff --git a/novelwriter/assets/themes/dracula.conf b/novelwriter/assets/themes/dracula.conf index 6bcc3945..080e6f96 100644 --- a/novelwriter/assets/themes/dracula.conf +++ b/novelwriter/assets/themes/dracula.conf @@ -47,6 +47,7 @@ disabled = faded [Icon] tool = default +sidebar = default accept = green reject = red action = blue @@ -58,8 +59,8 @@ reset = green add = green change = green remove = red -scformat = default -mdformat = orange +shortcode = default +markdown = orange systemio = yellow info = blue warning = orange diff --git a/novelwriter/assets/themes/espresso.conf b/novelwriter/assets/themes/espresso.conf index f1295bcd..54a166f7 100644 --- a/novelwriter/assets/themes/espresso.conf +++ b/novelwriter/assets/themes/espresso.conf @@ -31,6 +31,7 @@ disabled = faded [Icon] tool = default +sidebar = default accept = green reject = red action = blue @@ -42,8 +43,8 @@ reset = green add = green change = green remove = red -scformat = default -mdformat = orange +shortcode = default +markdown = orange systemio = yellow info = blue warning = orange diff --git a/novelwriter/assets/themes/everforest_dark.conf b/novelwriter/assets/themes/everforest_dark.conf index eadee969..36204052 100644 --- a/novelwriter/assets/themes/everforest_dark.conf +++ b/novelwriter/assets/themes/everforest_dark.conf @@ -31,6 +31,7 @@ disabled = faded [Icon] tool = default +sidebar = default accept = green reject = red action = blue @@ -42,8 +43,8 @@ reset = green add = green change = green remove = red -scformat = default -mdformat = orange +shortcode = default +markdown = orange systemio = yellow info = blue warning = orange diff --git a/novelwriter/assets/themes/everforest_light.conf b/novelwriter/assets/themes/everforest_light.conf index d26b8399..f4b4b5e1 100644 --- a/novelwriter/assets/themes/everforest_light.conf +++ b/novelwriter/assets/themes/everforest_light.conf @@ -31,6 +31,7 @@ disabled = faded [Icon] tool = default +sidebar = default accept = green reject = red action = blue @@ -42,8 +43,8 @@ reset = green add = green change = green remove = red -scformat = default -mdformat = orange +shortcode = default +markdown = orange systemio = yellow info = blue warning = orange diff --git a/novelwriter/assets/themes/floral_daydream.conf b/novelwriter/assets/themes/floral_daydream.conf index 33033d83..ed74dfd2 100644 --- a/novelwriter/assets/themes/floral_daydream.conf +++ b/novelwriter/assets/themes/floral_daydream.conf @@ -29,6 +29,7 @@ disabled = #e2bcce [Icon] tool = default +sidebar = default accept = green reject = red action = blue @@ -40,8 +41,8 @@ reset = green add = green change = green remove = red -scformat = default -mdformat = orange +shortcode = default +markdown = orange systemio = yellow info = blue warning = orange diff --git a/novelwriter/assets/themes/floral_midnight.conf b/novelwriter/assets/themes/floral_midnight.conf index 77518f6b..c0180642 100644 --- a/novelwriter/assets/themes/floral_midnight.conf +++ b/novelwriter/assets/themes/floral_midnight.conf @@ -29,6 +29,7 @@ disabled = #55516d [Icon] tool = default +sidebar = default accept = green reject = red action = blue @@ -40,8 +41,8 @@ reset = green add = green change = green remove = red -scformat = default -mdformat = orange +shortcode = default +markdown = orange systemio = yellow info = blue warning = orange diff --git a/novelwriter/assets/themes/full_moon.conf b/novelwriter/assets/themes/full_moon.conf index 32f770a0..67a19c96 100644 --- a/novelwriter/assets/themes/full_moon.conf +++ b/novelwriter/assets/themes/full_moon.conf @@ -29,6 +29,7 @@ disabled = faded [Icon] tool = default +sidebar = default accept = green reject = red action = blue @@ -40,8 +41,8 @@ reset = green add = green change = green remove = red -scformat = default -mdformat = orange +shortcode = default +markdown = orange systemio = yellow info = blue warning = orange diff --git a/novelwriter/assets/themes/grey_dark.conf b/novelwriter/assets/themes/grey_dark.conf index 92326bc0..45cd4137 100644 --- a/novelwriter/assets/themes/grey_dark.conf +++ b/novelwriter/assets/themes/grey_dark.conf @@ -31,6 +31,7 @@ disabled = faded [Icon] tool = default +sidebar = default accept = green reject = red action = blue @@ -42,8 +43,8 @@ reset = green add = green change = green remove = red -scformat = default -mdformat = orange +shortcode = default +markdown = orange systemio = yellow info = blue warning = orange diff --git a/novelwriter/assets/themes/grey_light.conf b/novelwriter/assets/themes/grey_light.conf index ac7d9d90..bd341eff 100644 --- a/novelwriter/assets/themes/grey_light.conf +++ b/novelwriter/assets/themes/grey_light.conf @@ -31,6 +31,7 @@ disabled = faded [Icon] tool = default +sidebar = default accept = green reject = red action = blue @@ -42,8 +43,8 @@ reset = green add = green change = green remove = red -scformat = default -mdformat = orange +shortcode = default +markdown = orange systemio = yellow info = blue warning = orange diff --git a/novelwriter/assets/themes/horizon_dark.conf b/novelwriter/assets/themes/horizon_dark.conf index caa1b0b1..6e2077fa 100644 --- a/novelwriter/assets/themes/horizon_dark.conf +++ b/novelwriter/assets/themes/horizon_dark.conf @@ -31,6 +31,7 @@ disabled = #404263 [Icon] tool = default +sidebar = default accept = green reject = red action = blue @@ -42,8 +43,8 @@ reset = green add = green change = green remove = red -scformat = default -mdformat = orange +shortcode = default +markdown = orange systemio = yellow info = blue warning = orange diff --git a/novelwriter/assets/themes/horizon_light.conf b/novelwriter/assets/themes/horizon_light.conf index e7d948b0..128f90d6 100644 --- a/novelwriter/assets/themes/horizon_light.conf +++ b/novelwriter/assets/themes/horizon_light.conf @@ -31,6 +31,7 @@ disabled = faded:L125 [Icon] tool = default +sidebar = default accept = green reject = red action = blue @@ -42,8 +43,8 @@ reset = green add = green change = green remove = red -scformat = default -mdformat = orange +shortcode = default +markdown = orange systemio = yellow info = blue warning = orange diff --git a/novelwriter/assets/themes/lcars.conf b/novelwriter/assets/themes/lcars.conf index cd55aea9..2587c4cd 100644 --- a/novelwriter/assets/themes/lcars.conf +++ b/novelwriter/assets/themes/lcars.conf @@ -31,6 +31,7 @@ disabled = faded [Icon] tool = default +sidebar = default accept = green reject = red action = blue @@ -42,8 +43,8 @@ reset = green add = green change = green remove = red -scformat = default -mdformat = orange +shortcode = default +markdown = orange systemio = yellow info = blue warning = orange diff --git a/novelwriter/assets/themes/light_owl.conf b/novelwriter/assets/themes/light_owl.conf index 891cead4..c1e48f6d 100644 --- a/novelwriter/assets/themes/light_owl.conf +++ b/novelwriter/assets/themes/light_owl.conf @@ -51,6 +51,7 @@ disabled = faded [Icon] tool = default +sidebar = default accept = green reject = red action = blue @@ -62,8 +63,8 @@ reset = green add = green change = green remove = red -scformat = default -mdformat = orange +shortcode = default +markdown = orange systemio = yellow info = blue warning = orange diff --git a/novelwriter/assets/themes/new_moon.conf b/novelwriter/assets/themes/new_moon.conf index 45d8d442..a69da8ad 100644 --- a/novelwriter/assets/themes/new_moon.conf +++ b/novelwriter/assets/themes/new_moon.conf @@ -31,6 +31,7 @@ disabled = faded [Icon] tool = default +sidebar = default accept = green reject = red action = blue @@ -42,8 +43,8 @@ reset = green add = green change = green remove = red -scformat = default -mdformat = orange +shortcode = default +markdown = orange systemio = yellow info = blue warning = orange diff --git a/novelwriter/assets/themes/night_owl.conf b/novelwriter/assets/themes/night_owl.conf index 72c307ce..d49de77b 100644 --- a/novelwriter/assets/themes/night_owl.conf +++ b/novelwriter/assets/themes/night_owl.conf @@ -39,6 +39,7 @@ purple = #c792ea [Icon] tool = default +sidebar = default accept = green reject = red action = blue @@ -50,8 +51,8 @@ reset = green add = green change = green remove = red -scformat = default -mdformat = orange +shortcode = default +markdown = orange systemio = yellow info = blue warning = orange diff --git a/novelwriter/assets/themes/noctis.conf b/novelwriter/assets/themes/noctis.conf index 8a25d31c..09bf4080 100644 --- a/novelwriter/assets/themes/noctis.conf +++ b/novelwriter/assets/themes/noctis.conf @@ -63,6 +63,7 @@ disabled = faded [Icon] tool = default +sidebar = default accept = green reject = red action = blue @@ -74,8 +75,8 @@ reset = green add = green change = green remove = red -scformat = default -mdformat = orange +shortcode = default +markdown = orange systemio = yellow info = blue warning = orange diff --git a/novelwriter/assets/themes/noctis_lux.conf b/novelwriter/assets/themes/noctis_lux.conf index 03835ea9..cc08f4e9 100644 --- a/novelwriter/assets/themes/noctis_lux.conf +++ b/novelwriter/assets/themes/noctis_lux.conf @@ -63,6 +63,7 @@ disabled = faded [Icon] tool = default +sidebar = default accept = green reject = red action = blue @@ -74,8 +75,8 @@ reset = green add = green change = green remove = red -scformat = default -mdformat = orange +shortcode = default +markdown = orange systemio = yellow info = blue warning = orange diff --git a/novelwriter/assets/themes/nord.conf b/novelwriter/assets/themes/nord.conf index 308695ab..ac29ff76 100644 --- a/novelwriter/assets/themes/nord.conf +++ b/novelwriter/assets/themes/nord.conf @@ -31,6 +31,7 @@ disabled = #576279 [Icon] tool = default +sidebar = default accept = green reject = red action = blue @@ -42,8 +43,8 @@ reset = green add = green change = green remove = red -scformat = default -mdformat = orange +shortcode = default +markdown = orange systemio = yellow info = blue warning = orange diff --git a/novelwriter/assets/themes/nordlicht.conf b/novelwriter/assets/themes/nordlicht.conf index 7725627a..31235ce0 100644 --- a/novelwriter/assets/themes/nordlicht.conf +++ b/novelwriter/assets/themes/nordlicht.conf @@ -29,6 +29,7 @@ disabled = #b2c0d6 [Icon] tool = default +sidebar = default accept = green reject = red action = blue @@ -40,8 +41,8 @@ reset = green add = green change = green remove = red -scformat = default -mdformat = orange +shortcode = default +markdown = orange systemio = yellow info = blue warning = orange diff --git a/novelwriter/assets/themes/otium_dark.conf b/novelwriter/assets/themes/otium_dark.conf index 7ff09686..d34fdd9d 100644 --- a/novelwriter/assets/themes/otium_dark.conf +++ b/novelwriter/assets/themes/otium_dark.conf @@ -29,6 +29,7 @@ disabled = #4e545e [Icon] tool = default +sidebar = default accept = green reject = red action = blue @@ -40,8 +41,8 @@ reset = green add = green change = green remove = red -scformat = default -mdformat = orange +shortcode = default +markdown = orange systemio = yellow info = blue warning = orange diff --git a/novelwriter/assets/themes/otium_light.conf b/novelwriter/assets/themes/otium_light.conf index a524051e..40493c3b 100644 --- a/novelwriter/assets/themes/otium_light.conf +++ b/novelwriter/assets/themes/otium_light.conf @@ -29,6 +29,7 @@ disabled = #adadad [Icon] tool = default +sidebar = default accept = green reject = red action = blue @@ -40,8 +41,8 @@ reset = green add = green change = green remove = red -scformat = default -mdformat = orange +shortcode = default +markdown = orange systemio = yellow info = blue warning = orange diff --git a/novelwriter/assets/themes/paragon.conf b/novelwriter/assets/themes/paragon.conf index d8ef6919..d1691c57 100644 --- a/novelwriter/assets/themes/paragon.conf +++ b/novelwriter/assets/themes/paragon.conf @@ -30,6 +30,7 @@ disabled = faded:D150 [Icon] tool = default +sidebar = default accept = green reject = red action = blue @@ -41,8 +42,8 @@ reset = green add = green change = green remove = red -scformat = default -mdformat = orange +shortcode = default +markdown = orange systemio = yellow info = blue warning = orange diff --git a/novelwriter/assets/themes/primer_light.conf b/novelwriter/assets/themes/primer_light.conf index 9eb865f2..cddfa23d 100644 --- a/novelwriter/assets/themes/primer_light.conf +++ b/novelwriter/assets/themes/primer_light.conf @@ -31,6 +31,7 @@ disabled = #afb8c1 [Icon] tool = default +sidebar = default accept = green reject = red action = blue @@ -42,8 +43,8 @@ reset = green add = green change = green remove = red -scformat = default -mdformat = orange +shortcode = default +markdown = orange systemio = yellow info = blue warning = orange diff --git a/novelwriter/assets/themes/primer_night.conf b/novelwriter/assets/themes/primer_night.conf index 73ae6270..fc8c83a3 100644 --- a/novelwriter/assets/themes/primer_night.conf +++ b/novelwriter/assets/themes/primer_night.conf @@ -31,6 +31,7 @@ disabled = #484f58 [Icon] tool = default +sidebar = default accept = green reject = red action = blue @@ -42,8 +43,8 @@ reset = green add = green change = green remove = red -scformat = default -mdformat = orange +shortcode = default +markdown = orange systemio = yellow info = blue warning = orange diff --git a/novelwriter/assets/themes/ruby_day.conf b/novelwriter/assets/themes/ruby_day.conf index d7b3e033..411302c8 100644 --- a/novelwriter/assets/themes/ruby_day.conf +++ b/novelwriter/assets/themes/ruby_day.conf @@ -29,6 +29,7 @@ disabled = faded [Icon] tool = default +sidebar = default accept = green reject = red action = blue @@ -40,8 +41,8 @@ reset = green add = green change = green remove = red -scformat = default -mdformat = orange +shortcode = default +markdown = orange systemio = yellow info = blue warning = orange diff --git a/novelwriter/assets/themes/ruby_night.conf b/novelwriter/assets/themes/ruby_night.conf index c73b6579..2ae79c59 100644 --- a/novelwriter/assets/themes/ruby_night.conf +++ b/novelwriter/assets/themes/ruby_night.conf @@ -29,6 +29,7 @@ disabled = faded [Icon] tool = default +sidebar = default accept = green reject = red action = blue @@ -40,8 +41,8 @@ reset = green add = green change = green remove = red -scformat = default -mdformat = orange +shortcode = default +markdown = orange systemio = yellow info = blue warning = orange diff --git a/novelwriter/assets/themes/selenium_dark.conf b/novelwriter/assets/themes/selenium_dark.conf index 406205ab..95b23535 100644 --- a/novelwriter/assets/themes/selenium_dark.conf +++ b/novelwriter/assets/themes/selenium_dark.conf @@ -29,6 +29,7 @@ disabled = #625b70 [Icon] tool = default +sidebar = default accept = green reject = red action = blue @@ -40,8 +41,8 @@ reset = green add = green change = green remove = red -scformat = default -mdformat = orange +shortcode = default +markdown = orange systemio = yellow info = blue warning = orange diff --git a/novelwriter/assets/themes/selenium_light.conf b/novelwriter/assets/themes/selenium_light.conf index 28dbfe7f..64f448e6 100644 --- a/novelwriter/assets/themes/selenium_light.conf +++ b/novelwriter/assets/themes/selenium_light.conf @@ -29,6 +29,7 @@ disabled = faded [Icon] tool = default +sidebar = default accept = green reject = red action = blue @@ -40,8 +41,8 @@ reset = green add = green change = green remove = red -scformat = default -mdformat = orange +shortcode = default +markdown = orange systemio = yellow info = blue warning = orange diff --git a/novelwriter/assets/themes/sepia_dark.conf b/novelwriter/assets/themes/sepia_dark.conf index a4980608..fd530359 100644 --- a/novelwriter/assets/themes/sepia_dark.conf +++ b/novelwriter/assets/themes/sepia_dark.conf @@ -29,6 +29,7 @@ disabled = faded [Icon] tool = default +sidebar = default accept = green reject = red action = blue @@ -40,8 +41,8 @@ reset = green add = green change = green remove = red -scformat = default -mdformat = orange +shortcode = default +markdown = orange systemio = yellow info = blue warning = orange diff --git a/novelwriter/assets/themes/sepia_light.conf b/novelwriter/assets/themes/sepia_light.conf index 06dfa52b..35452652 100644 --- a/novelwriter/assets/themes/sepia_light.conf +++ b/novelwriter/assets/themes/sepia_light.conf @@ -29,6 +29,7 @@ disabled = #b6a096 [Icon] tool = default +sidebar = default accept = green reject = red action = blue @@ -40,8 +41,8 @@ reset = green add = green change = green remove = red -scformat = default -mdformat = orange +shortcode = default +markdown = orange systemio = yellow info = blue warning = orange diff --git a/novelwriter/assets/themes/snazzy.conf b/novelwriter/assets/themes/snazzy.conf index b0dd2dc5..a4e5935d 100644 --- a/novelwriter/assets/themes/snazzy.conf +++ b/novelwriter/assets/themes/snazzy.conf @@ -44,6 +44,7 @@ disabled = faded [Icon] tool = default +sidebar = default accept = green reject = red action = blue @@ -55,8 +56,8 @@ reset = green add = green change = green remove = red -scformat = default -mdformat = orange +shortcode = default +markdown = orange systemio = yellow info = blue warning = orange diff --git a/novelwriter/assets/themes/solarized_dark.conf b/novelwriter/assets/themes/solarized_dark.conf index acfb1bf5..26963c0f 100644 --- a/novelwriter/assets/themes/solarized_dark.conf +++ b/novelwriter/assets/themes/solarized_dark.conf @@ -50,6 +50,7 @@ disabled = faded [Icon] tool = default +sidebar = default accept = green reject = red action = blue @@ -61,8 +62,8 @@ reset = green add = green change = green remove = red -scformat = default -mdformat = orange +shortcode = default +markdown = orange systemio = yellow info = blue warning = orange diff --git a/novelwriter/assets/themes/solarized_light.conf b/novelwriter/assets/themes/solarized_light.conf index 7f487603..a05c5beb 100644 --- a/novelwriter/assets/themes/solarized_light.conf +++ b/novelwriter/assets/themes/solarized_light.conf @@ -50,6 +50,7 @@ disabled = faded [Icon] tool = default +sidebar = default accept = green reject = red action = blue @@ -61,8 +62,8 @@ reset = green add = green change = green remove = red -scformat = default -mdformat = orange +shortcode = default +markdown = orange systemio = yellow info = blue warning = orange diff --git a/novelwriter/assets/themes/sultana_light.conf b/novelwriter/assets/themes/sultana_light.conf index b24d3557..3f09904a 100644 --- a/novelwriter/assets/themes/sultana_light.conf +++ b/novelwriter/assets/themes/sultana_light.conf @@ -29,6 +29,7 @@ disabled = faded [Icon] tool = default +sidebar = default accept = green reject = red action = blue @@ -40,8 +41,8 @@ reset = green add = green change = green remove = red -scformat = default -mdformat = orange +shortcode = default +markdown = orange systemio = yellow info = blue warning = orange diff --git a/novelwriter/assets/themes/sultana_night.conf b/novelwriter/assets/themes/sultana_night.conf index 887ad2e4..c27b2ac3 100644 --- a/novelwriter/assets/themes/sultana_night.conf +++ b/novelwriter/assets/themes/sultana_night.conf @@ -29,6 +29,7 @@ disabled = #85748a [Icon] tool = default +sidebar = default accept = green reject = red action = blue @@ -40,8 +41,8 @@ reset = green add = green change = green remove = red -scformat = default -mdformat = orange +shortcode = default +markdown = orange systemio = yellow info = blue warning = orange diff --git a/novelwriter/assets/themes/tango_dark.conf b/novelwriter/assets/themes/tango_dark.conf index 7c3b6a1a..0b1884bc 100644 --- a/novelwriter/assets/themes/tango_dark.conf +++ b/novelwriter/assets/themes/tango_dark.conf @@ -45,6 +45,7 @@ disabled = faded [Icon] tool = default +sidebar = default accept = green reject = red action = blue @@ -56,8 +57,8 @@ reset = green add = green change = green remove = red -scformat = default -mdformat = orange +shortcode = default +markdown = orange systemio = yellow info = blue warning = orange diff --git a/novelwriter/assets/themes/tango_light.conf b/novelwriter/assets/themes/tango_light.conf index 1ff2b78a..01486203 100644 --- a/novelwriter/assets/themes/tango_light.conf +++ b/novelwriter/assets/themes/tango_light.conf @@ -45,6 +45,7 @@ disabled = faded [Icon] tool = default +sidebar = default accept = green reject = red action = blue @@ -56,8 +57,8 @@ reset = green add = green change = green remove = red -scformat = default -mdformat = orange +shortcode = default +markdown = orange systemio = yellow info = blue warning = orange diff --git a/novelwriter/assets/themes/tomorrow.conf b/novelwriter/assets/themes/tomorrow.conf index beca3841..80823828 100644 --- a/novelwriter/assets/themes/tomorrow.conf +++ b/novelwriter/assets/themes/tomorrow.conf @@ -51,6 +51,7 @@ disabled = faded [Icon] tool = default +sidebar = default accept = green reject = red action = blue @@ -62,8 +63,8 @@ reset = green add = green change = green remove = red -scformat = default -mdformat = orange +shortcode = default +markdown = orange systemio = yellow info = blue warning = orange diff --git a/novelwriter/assets/themes/tomorrow_night.conf b/novelwriter/assets/themes/tomorrow_night.conf index 3531e63d..b0346d0f 100644 --- a/novelwriter/assets/themes/tomorrow_night.conf +++ b/novelwriter/assets/themes/tomorrow_night.conf @@ -51,6 +51,7 @@ disabled = faded [Icon] tool = default +sidebar = default accept = green reject = red action = blue @@ -62,8 +63,8 @@ reset = green add = green change = green remove = red -scformat = default -mdformat = orange +shortcode = default +markdown = orange systemio = yellow info = blue warning = orange diff --git a/novelwriter/assets/themes/tomorrow_night_blue.conf b/novelwriter/assets/themes/tomorrow_night_blue.conf index df9e0715..678185a3 100644 --- a/novelwriter/assets/themes/tomorrow_night_blue.conf +++ b/novelwriter/assets/themes/tomorrow_night_blue.conf @@ -51,6 +51,7 @@ disabled = faded [Icon] tool = default +sidebar = default accept = green reject = red action = blue @@ -62,8 +63,8 @@ reset = green add = green change = green remove = red -scformat = default -mdformat = orange +shortcode = default +markdown = orange systemio = yellow info = blue warning = orange diff --git a/novelwriter/assets/themes/tomorrow_night_bright.conf b/novelwriter/assets/themes/tomorrow_night_bright.conf index 056f7f94..a28657e7 100644 --- a/novelwriter/assets/themes/tomorrow_night_bright.conf +++ b/novelwriter/assets/themes/tomorrow_night_bright.conf @@ -51,6 +51,7 @@ disabled = faded [Icon] tool = default +sidebar = default accept = green reject = red action = blue @@ -62,8 +63,8 @@ reset = green add = green change = green remove = red -scformat = default -mdformat = orange +shortcode = default +markdown = orange systemio = yellow info = blue warning = orange diff --git a/novelwriter/assets/themes/tomorrow_night_eighties.conf b/novelwriter/assets/themes/tomorrow_night_eighties.conf index e3b71444..651c764d 100644 --- a/novelwriter/assets/themes/tomorrow_night_eighties.conf +++ b/novelwriter/assets/themes/tomorrow_night_eighties.conf @@ -51,6 +51,7 @@ disabled = faded [Icon] tool = default +sidebar = default accept = green reject = red action = blue @@ -62,8 +63,8 @@ reset = green add = green change = green remove = red -scformat = default -mdformat = orange +shortcode = default +markdown = orange systemio = yellow info = blue warning = orange diff --git a/novelwriter/assets/themes/vivid_black_green.conf b/novelwriter/assets/themes/vivid_black_green.conf index e6a2f0cd..c9e3af02 100644 --- a/novelwriter/assets/themes/vivid_black_green.conf +++ b/novelwriter/assets/themes/vivid_black_green.conf @@ -31,6 +31,7 @@ disabled = faded:D175 [Icon] tool = default +sidebar = default accept = green reject = red action = blue @@ -42,8 +43,8 @@ reset = green add = green change = green remove = red -scformat = default -mdformat = orange +shortcode = default +markdown = orange systemio = yellow info = blue warning = orange diff --git a/novelwriter/assets/themes/vivid_black_red.conf b/novelwriter/assets/themes/vivid_black_red.conf index cb615301..45fdf500 100644 --- a/novelwriter/assets/themes/vivid_black_red.conf +++ b/novelwriter/assets/themes/vivid_black_red.conf @@ -31,6 +31,7 @@ disabled = faded:D175 [Icon] tool = default +sidebar = default accept = green reject = red action = blue @@ -42,8 +43,8 @@ reset = green add = green change = green remove = red -scformat = default -mdformat = orange +shortcode = default +markdown = orange systemio = yellow info = blue warning = orange diff --git a/novelwriter/assets/themes/vivid_white_green.conf b/novelwriter/assets/themes/vivid_white_green.conf index f30474b4..7d10036d 100644 --- a/novelwriter/assets/themes/vivid_white_green.conf +++ b/novelwriter/assets/themes/vivid_white_green.conf @@ -31,6 +31,7 @@ disabled = faded:L135 [Icon] tool = default +sidebar = default accept = green reject = red action = blue @@ -42,8 +43,8 @@ reset = green add = green change = green remove = red -scformat = default -mdformat = orange +shortcode = default +markdown = orange systemio = yellow info = blue warning = orange diff --git a/novelwriter/assets/themes/vivid_white_red.conf b/novelwriter/assets/themes/vivid_white_red.conf index 08a52c02..e8f6956c 100644 --- a/novelwriter/assets/themes/vivid_white_red.conf +++ b/novelwriter/assets/themes/vivid_white_red.conf @@ -31,6 +31,7 @@ disabled = faded:L135 [Icon] tool = default +sidebar = default accept = green reject = red action = blue @@ -42,8 +43,8 @@ reset = green add = green change = green remove = red -scformat = default -mdformat = orange +shortcode = default +markdown = orange systemio = yellow info = blue warning = orange diff --git a/novelwriter/assets/themes/warpgate.conf b/novelwriter/assets/themes/warpgate.conf index 2b1852ed..f4f016cf 100644 --- a/novelwriter/assets/themes/warpgate.conf +++ b/novelwriter/assets/themes/warpgate.conf @@ -30,6 +30,7 @@ disabled = faded [Icon] tool = default +sidebar = default accept = green reject = red action = blue @@ -41,8 +42,8 @@ reset = green add = green change = green remove = red -scformat = default -mdformat = orange +shortcode = default +markdown = orange systemio = yellow info = blue warning = orange diff --git a/novelwriter/assets/themes/waterlily_dark.conf b/novelwriter/assets/themes/waterlily_dark.conf index 246f06d3..fd19a045 100644 --- a/novelwriter/assets/themes/waterlily_dark.conf +++ b/novelwriter/assets/themes/waterlily_dark.conf @@ -29,6 +29,7 @@ disabled = #374a5c [Icon] tool = default +sidebar = default accept = green reject = red action = blue @@ -40,8 +41,8 @@ reset = green add = green change = green remove = red -scformat = default -mdformat = orange +shortcode = default +markdown = orange systemio = yellow info = blue warning = orange diff --git a/novelwriter/assets/themes/waterlily_light.conf b/novelwriter/assets/themes/waterlily_light.conf index 4acf2979..7d1ad410 100644 --- a/novelwriter/assets/themes/waterlily_light.conf +++ b/novelwriter/assets/themes/waterlily_light.conf @@ -29,6 +29,7 @@ disabled = #b6c9ba [Icon] tool = default +sidebar = default accept = green reject = red action = blue @@ -40,8 +41,8 @@ reset = green add = green change = green remove = red -scformat = default -mdformat = orange +shortcode = default +markdown = orange systemio = yellow info = blue warning = orange From d6911ed39b02e35b4b31e39095263a947ded3cb4 Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Sun, 26 Oct 2025 14:24:00 +0100 Subject: [PATCH 77/79] Fix tests --- tests/test_gui/test_gui_theme.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/test_gui/test_gui_theme.py b/tests/test_gui/test_gui_theme.py index beff6b21..cd25dfe9 100644 --- a/tests/test_gui/test_gui_theme.py +++ b/tests/test_gui/test_gui_theme.py @@ -626,9 +626,9 @@ def testGuiTheme_CheckTheme(theme): "active", "inactive", "disabled", ], "Icon": [ - "tool", "accept", "reject", "action", "altaction", "apply", - "create", "destroy", "reset", "add", "change", "remove", - "scformat", "mdformat", "systemio", "info", "warning", "error", + "tool", "sidebar", "accept", "reject", "action", "altaction", + "apply", "create", "destroy", "reset", "add", "change", "remove", + "shortcode", "markdown", "systemio", "info", "warning", "error", ], "Palette": [ "window", "windowtext", "base", "alternatebase", "text", From 34ecfba2304ce8f24ca33109f1f55d10cac03f90 Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Sun, 26 Oct 2025 20:18:36 +0100 Subject: [PATCH 78/79] Remove the source section of the readme as it's also in teh contributing guide --- README.md | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/README.md b/README.md index fd83fdc1..f8f05025 100644 --- a/README.md +++ b/README.md @@ -51,17 +51,6 @@ Python.

-## Working With the Source - -This project uses [uv](https://docs.astral.sh/uv/) as its main developer tool. That means the -`pyproject.toml` file handles almost everything aside from a few OS-specific packaging tasks. - -In order to run novelWriter directly from checked out source, simply call from the root folder: - -```bash -uv run novelwriter -``` - ## Project Contributions Please don't make feature pull requests without first having discussed them with the maintainer. From 31437f3d3e58c5ecb4c7c87c07cd19fca9df2589 Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Sun, 26 Oct 2025 20:25:03 +0100 Subject: [PATCH 79/79] Tune the readme a little --- README.md | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index f8f05025..ada066c1 100644 --- a/README.md +++ b/README.md @@ -12,11 +12,7 @@ novelWriter is a plain text editor designed for writing novels assembled from ma documents. It uses a minimal formatting syntax inspired by Markdown, and adds a meta data syntax for comments, synopsis, and cross-referencing. It's designed to be a simple text editor that allows for easy organisation of text and notes, using human readable text files as storage for robustness. - -The project storage is suitable for version control software, and also well suited for file -synchronisation tools. All text is saved as plain text files with a meta data header. The core -project structure is stored in a single project XML file. Other meta data is primarily saved as -JSON files. +The project format is well suited both for version control software and file synchronisation tools. For more details, and how to install and use novelWriter, please see the main website and documentation. @@ -42,9 +38,9 @@ documentation. ## Implementation -novelWriter is written with Python and Qt6 with PyQt6 Python binding. It is released on Linux, -Windows and MacOS. It can in principle run on any Operating System that also supports Qt, PyQt and -Python. +novelWriter is written in Python and uses Qt6 with PyQt6 Python binding as the UI framework. It is +released on Linux, Windows and MacOS. It can in principle run on any Operating System that also +supports Qt, PyQt and Python.

@@ -57,7 +53,7 @@ Please don't make feature pull requests without first having discussed them with You can make a feature request in the [issues tracker](https://github.com/vkbo/novelWriter/issues), or if the idea isn't fully formed, start a [discussion](https://github.com/vkbo/novelWriter/discussions). Please also don't make pull requests to reformat or rewrite existing code unless there is a very -good reason for doing so. +good reason for doing so. Please do not submit AI generated content. Fixes and patches are welcome. Contributions related to packaging and installing novelWriter will also be appreciated, but please make an issue or a discussion topic first. Before contributing any