From 67d49a8f2b78442d2f3562c4445287707591c942 Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Thu, 12 Jun 2025 16:52:24 +0200 Subject: [PATCH 1/6] Remove theme key from theme colours list --- novelwriter/constants.py | 1 - novelwriter/dialogs/preferences.py | 1 + tests/test_dialogs/test_dlg_preferences.py | 4 ++-- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/novelwriter/constants.py b/novelwriter/constants.py index cdb05546..701ac5a1 100644 --- a/novelwriter/constants.py +++ b/novelwriter/constants.py @@ -447,7 +447,6 @@ class nwLabels: "Custom": (-1.0, -1.0), } THEME_COLORS: Final[dict[str, str]] = { - "theme": QT_TRANSLATE_NOOP("Constant", "Theme Colours"), "default": QT_TRANSLATE_NOOP("Constant", "Foreground Colour"), "base": QT_TRANSLATE_NOOP("Constant", "Background Colour"), "faded": QT_TRANSLATE_NOOP("Constant", "Faded Colour"), diff --git a/novelwriter/dialogs/preferences.py b/novelwriter/dialogs/preferences.py index 3d284c2e..1a8645b3 100644 --- a/novelwriter/dialogs/preferences.py +++ b/novelwriter/dialogs/preferences.py @@ -294,6 +294,7 @@ class GuiPreferences(NDialog): # Tree Icon Colours self.iconColTree = NComboBox(self) self.iconColTree.setMinimumWidth(200) + self.iconColTree.addItem(self.tr("Theme Colours"), DEF_TREECOL) for key, label in nwLabels.THEME_COLORS.items(): self.iconColTree.addItem(trConst(label), key) self.iconColTree.setCurrentData(CONFIG.iconColTree, DEF_TREECOL) diff --git a/tests/test_dialogs/test_dlg_preferences.py b/tests/test_dialogs/test_dlg_preferences.py index 9add0573..a017585f 100644 --- a/tests/test_dialogs/test_dlg_preferences.py +++ b/tests/test_dialogs/test_dlg_preferences.py @@ -27,7 +27,7 @@ from PyQt6.QtGui import QAction, QFont, QFontDatabase, QKeyEvent from PyQt6.QtWidgets import QFileDialog, QFontDialog from novelwriter import CONFIG, SHARED -from novelwriter.config import DEF_GUI_DARK, DEF_GUI_LIGHT +from novelwriter.config import DEF_GUI_DARK, DEF_GUI_LIGHT, DEF_TREECOL from novelwriter.constants import nwUnicode from novelwriter.dialogs.preferences import GuiPreferences from novelwriter.dialogs.quotes import GuiQuoteSelect @@ -198,7 +198,7 @@ def testDlgPreferences_Settings(qtbot, monkeypatch, nwGUI, fncPath, tstPaths): prefs.iconColDocs.setChecked(True) prefs.emphLabels.setChecked(True) - assert CONFIG.iconColTree == "theme" + assert CONFIG.iconColTree == DEF_TREECOL assert CONFIG.iconColDocs is False assert CONFIG.emphLabels is False From fe46a82e63dd065b5ee0ce58bbcea6da9c7ef283 Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Thu, 12 Jun 2025 16:55:12 +0200 Subject: [PATCH 2/6] Change how colours are stored for status labels to use theme colour or hex format --- novelwriter/core/projectxml.py | 15 ++++++++---- novelwriter/core/status.py | 28 ++++++++++++--------- novelwriter/gui/theme.py | 16 ++++++------ tests/test_gui/test_gui_theme.py | 42 ++++++++++++++++---------------- 4 files changed, 55 insertions(+), 46 deletions(-) diff --git a/novelwriter/core/projectxml.py b/novelwriter/core/projectxml.py index 1e0e146b..a20649d2 100644 --- a/novelwriter/core/projectxml.py +++ b/novelwriter/core/projectxml.py @@ -46,7 +46,7 @@ if TYPE_CHECKING: logger = logging.getLogger(__name__) FILE_VERSION = "1.5" # The current project file format version -FILE_REVISION = "5" # The current project file format revision +FILE_REVISION = "6" # The current project file format revision HEX_VERSION = 0x0105 NUM_VERSION = { @@ -111,6 +111,8 @@ class ProjectXMLReader: nodes. 2.5. Rev 5: Added novelChars and notesChars attributes to content node. 2.7 RC 1. + Rev 6: Replaced red, green and blue attributes with a single + color attribute. 2.8 Beta 1. """ def __init__(self, path: str | Path) -> None: @@ -439,12 +441,15 @@ class ProjectXMLReader: for xEntry in xItem: if xEntry.tag == "entry": key = xEntry.attrib.get("key", None) - red = checkInt(xEntry.attrib.get("red", 0), 0) - green = checkInt(xEntry.attrib.get("green", 0), 0) - blue = checkInt(xEntry.attrib.get("blue", 0), 0) + red = checkInt(xEntry.attrib.get("red", 0), 0) # Deprecated in 1.5 Rev 6 + green = checkInt(xEntry.attrib.get("green", 0), 0) # Deprecated in 1.5 Rev 6 + blue = checkInt(xEntry.attrib.get("blue", 0), 0) # Deprecated in 1.5 Rev 6 + color = xEntry.attrib.get("color") # Added in 1.5 Rev 6 count = checkInt(xEntry.attrib.get("count", 0), 0) shape = xEntry.attrib.get("shape", "") - sObject.add(key, xEntry.text or "", (red, green, blue), shape, count) + if color is None: + color = f"{red}, {green}, {blue}" + sObject.add(key, xEntry.text or "", color, shape, count) return def _parseDictKeyText(self, xItem: ET.Element) -> dict: diff --git a/novelwriter/core/status.py b/novelwriter/core/status.py index f7d9b584..1fdf5396 100644 --- a/novelwriter/core/status.py +++ b/novelwriter/core/status.py @@ -35,6 +35,7 @@ from PyQt6.QtGui import QColor, QIcon, QPainter, QPainterPath, QPixmap, QPolygon from novelwriter import SHARED from novelwriter.common import simplified +from novelwriter.constants import nwLabels from novelwriter.enum import nwStatusShape from novelwriter.types import QtPaintAntiAlias, QtTransparent @@ -43,12 +44,15 @@ if TYPE_CHECKING: logger = logging.getLogger(__name__) +CUSTOM_COL = "custom" + @dataclasses.dataclass class StatusEntry: name: str color: QColor + theme: str shape: nwStatusShape icon: QIcon count: int = 0 @@ -62,7 +66,7 @@ class StatusEntry: return status -NO_ENTRY = StatusEntry("", QColor(0, 0, 0), nwStatusShape.SQUARE, QIcon(), 0) +NO_ENTRY = StatusEntry("", QColor(0, 0, 0), CUSTOM_COL, nwStatusShape.SQUARE, QIcon(), 0) T_UpdateEntry = list[tuple[str | None, StatusEntry]] T_StatusKind = Literal["s", "i"] @@ -97,15 +101,12 @@ class NWStatus: # Methods ## - def add(self, key: str | None, name: str, color: tuple[int, int, int], - shape: str, count: int) -> str: + def add(self, key: str | None, name: str, color: str, shape: str, count: int) -> str: """Add or update a status entry. If the key is invalid, a new key is generated. """ - if isinstance(color, tuple) and len(color) == 3: - qColor = QColor(*color) - else: - qColor = QColor(100, 100, 100) + qColor = SHARED.theme.parseColor(color) + theme = color if color in nwLabels.THEME_COLORS else CUSTOM_COL try: iShape = nwStatusShape[shape] @@ -115,7 +116,7 @@ class NWStatus: key = self._checkKey(key) name = simplified(name) icon = self.createIcon(self._height, qColor, iShape) - self._store[key] = StatusEntry(name, qColor, iShape, icon, count) + self._store[key] = StatusEntry(name, qColor, theme, iShape, icon, count) if self._default is None: self._default = key @@ -157,12 +158,14 @@ class NWStatus: def pack(self) -> Iterable[tuple[str, dict]]: """Pack the status entries into a dictionary.""" for key, entry in self._store.items(): + if entry.theme == CUSTOM_COL: + color = entry.color.name(QColor.NameFormat.HexRgb) + else: + color = entry.theme yield (entry.name, { "key": key, "count": str(entry.count), - "red": str(entry.color.red()), - "green": str(entry.color.green()), - "blue": str(entry.color.blue()), + "color": color, "shape": entry.shape.name, }) return @@ -179,8 +182,9 @@ class NWStatus: try: shape = nwStatusShape[str(data[0])] color = QColor(str(data[1])) + theme = CUSTOM_COL if data[1].startswith("#") else data[1] icon = NWStatus.createIcon(self._height, color, shape) - return StatusEntry(simplified(data[2]), color, shape, icon) + return StatusEntry(simplified(data[2]), color, theme, shape, icon) except Exception: logger.error("Could not parse entry %s", str(data)) return None diff --git a/novelwriter/gui/theme.py b/novelwriter/gui/theme.py index ee0e559f..fbde3028 100644 --- a/novelwriter/gui/theme.py +++ b/novelwriter/gui/theme.py @@ -40,7 +40,7 @@ from PyQt6.QtWidgets import QApplication from novelwriter import CONFIG from novelwriter.common import checkInt, minmax -from novelwriter.config import DEF_GUI_DARK, DEF_GUI_LIGHT, DEF_ICONS +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.error import logException @@ -438,7 +438,7 @@ class GuiTheme: self._guiPalette.setBrush(QtColDisabled, QPalette.ColorRole.Accent, grey) # Set project override colours - if (override := CONFIG.iconColTree) != "theme": + if (override := CONFIG.iconColTree) != DEF_TREECOL: color = self._qColors.get(override, QtBlack) self._setBaseColor("root", color) self._setBaseColor("folder", color) @@ -465,11 +465,7 @@ class GuiTheme: """Load a standard style sheet.""" return self._styleSheets.get(name, "") - ## - # Internal Functions - ## - - def _parseColor(self, value: str, default: QColor = QtBlack) -> QColor: + def parseColor(self, value: str, default: QColor = QtBlack) -> QColor: """Parse a string as a colour value.""" if value in self._qColors: # Named colour @@ -500,6 +496,10 @@ class GuiTheme: return QColor(*result) return default + ## + # Internal Functions + ## + def _setBaseColor(self, key: str, color: QColor) -> None: """Set the colour for a named colour.""" self._qColors[key] = QColor(color) @@ -560,7 +560,7 @@ class GuiTheme: def _readColor(self, parser: ConfigParser, section: str, name: str) -> QColor: """Parse a colour value from a config string.""" - return self._parseColor(parser.get(section, name, fallback="default")) + return self.parseColor(parser.get(section, name, fallback="default")) def _setPalette( self, parser: ConfigParser, section: str, name: str, value: QPalette.ColorRole diff --git a/tests/test_gui/test_gui_theme.py b/tests/test_gui/test_gui_theme.py index 27e5915d..04e4c62b 100644 --- a/tests/test_gui/test_gui_theme.py +++ b/tests/test_gui/test_gui_theme.py @@ -55,37 +55,37 @@ def testGuiTheme_ParseColor(): theme._qColors["grey"] = QColor(127, 127, 127) # By Name - assert theme._parseColor("red").getRgb() == (255, 0, 0, 255) - assert theme._parseColor("green").getRgb() == (0, 255, 0, 255) - assert theme._parseColor("blue").getRgb() == (0, 0, 255, 255) - assert theme._parseColor("bob").getRgb() == (0, 0, 0, 255) + assert theme.parseColor("red").getRgb() == (255, 0, 0, 255) + assert theme.parseColor("green").getRgb() == (0, 255, 0, 255) + assert theme.parseColor("blue").getRgb() == (0, 0, 255, 255) + assert theme.parseColor("bob").getRgb() == (0, 0, 0, 255) # CSS Format - assert theme._parseColor("#ff0000").getRgb() == (255, 0, 0, 255) - assert theme._parseColor("#ff00007f").getRgb() == (255, 0, 0, 127) - assert theme._parseColor("#ff00").getRgb() == (0, 0, 0, 255) # Too short -> ignored - assert theme._parseColor("#ff00007f15").getRgb() == (0, 0, 0, 255) # Too long -> ignored + assert theme.parseColor("#ff0000").getRgb() == (255, 0, 0, 255) + assert theme.parseColor("#ff00007f").getRgb() == (255, 0, 0, 127) + assert theme.parseColor("#ff00").getRgb() == (0, 0, 0, 255) # Too short -> ignored + assert theme.parseColor("#ff00007f15").getRgb() == (0, 0, 0, 255) # Too long -> ignored # Name + Alpha - assert theme._parseColor("red:255").getRgb() == (255, 0, 0, 255) - assert theme._parseColor("red:127").getRgb() == (255, 0, 0, 127) - assert theme._parseColor("red:512").getRgb() == (255, 0, 0, 255) # Value truncated + assert theme.parseColor("red:255").getRgb() == (255, 0, 0, 255) + assert theme.parseColor("red:127").getRgb() == (255, 0, 0, 127) + assert theme.parseColor("red:512").getRgb() == (255, 0, 0, 255) # Value truncated # Name + Lighter - assert theme._parseColor("grey:L100").getRgb() == (127, 127, 127, 255) - assert theme._parseColor("grey:L150").getRgb() == (190, 190, 190, 255) - assert theme._parseColor("grey:L50").getRgb() == (63, 63, 63, 255) + assert theme.parseColor("grey:L100").getRgb() == (127, 127, 127, 255) + assert theme.parseColor("grey:L150").getRgb() == (190, 190, 190, 255) + assert theme.parseColor("grey:L50").getRgb() == (63, 63, 63, 255) # Name + Darker - assert theme._parseColor("grey:D100").getRgb() == (127, 127, 127, 255) - assert theme._parseColor("grey:D150").getRgb() == (85, 85, 85, 255) - assert theme._parseColor("grey:D50").getRgb() == (254, 254, 254, 255) + assert theme.parseColor("grey:D100").getRgb() == (127, 127, 127, 255) + assert theme.parseColor("grey:D150").getRgb() == (85, 85, 85, 255) + assert theme.parseColor("grey:D50").getRgb() == (254, 254, 254, 255) # Values - assert theme._parseColor("255, 0, 0").getRgb() == (255, 0, 0, 255) - assert theme._parseColor("255, 0, 0, 255").getRgb() == (255, 0, 0, 255) - assert theme._parseColor("255, 0, 0, 127").getRgb() == (255, 0, 0, 127) - assert theme._parseColor("255, 0, 0, 127, 42").getRgb() == (255, 0, 0, 127) # Truncated + assert theme.parseColor("255, 0, 0").getRgb() == (255, 0, 0, 255) + assert theme.parseColor("255, 0, 0, 255").getRgb() == (255, 0, 0, 255) + assert theme.parseColor("255, 0, 0, 127").getRgb() == (255, 0, 0, 127) + assert theme.parseColor("255, 0, 0, 127, 42").getRgb() == (255, 0, 0, 127) # Truncated @pytest.mark.gui From 2e0ee9b75ff3a3670d98c602ed401ad7e78cba98 Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Thu, 12 Jun 2025 16:55:28 +0200 Subject: [PATCH 3/6] Update project settings for for status and importance colours --- novelwriter/dialogs/projectsettings.py | 88 ++++++++++++++++++++------ 1 file changed, 67 insertions(+), 21 deletions(-) diff --git a/novelwriter/dialogs/projectsettings.py b/novelwriter/dialogs/projectsettings.py index 623be345..071664fc 100644 --- a/novelwriter/dialogs/projectsettings.py +++ b/novelwriter/dialogs/projectsettings.py @@ -33,14 +33,14 @@ from PyQt6.QtCore import Qt, pyqtSignal, pyqtSlot from PyQt6.QtGui import QCloseEvent, QColor from PyQt6.QtWidgets import ( QAbstractItemView, QApplication, QColorDialog, QDialogButtonBox, - QFileDialog, QGridLayout, QHBoxLayout, QLineEdit, QMenu, QStackedWidget, - QTreeWidget, QTreeWidgetItem, QVBoxLayout, QWidget + QFileDialog, QGridLayout, QHBoxLayout, QLabel, QLineEdit, QMenu, + QStackedWidget, QTreeWidget, QTreeWidgetItem, QVBoxLayout, QWidget ) from novelwriter import CONFIG, SHARED from novelwriter.common import formatFileFilter, qtAddAction, qtLambda, simplified from novelwriter.constants import nwLabels, trConst -from novelwriter.core.status import NWStatus, StatusEntry +from novelwriter.core.status import CUSTOM_COL, NWStatus, StatusEntry from novelwriter.enum import nwStatusShape from novelwriter.extensions.configlayout import NColorLabel, NFixedPage, NScrollableForm from novelwriter.extensions.modified import NComboBox, NDialog, NIconToolButton @@ -326,6 +326,7 @@ class _StatusPage(NFixedPage): self._changed = False self._color = QColor(100, 100, 100) self._shape = nwStatusShape.SQUARE + self._theme = CUSTOM_COL self._icons = {} self._iPx = SHARED.theme.baseIconHeight @@ -384,11 +385,26 @@ class _StatusPage(NFixedPage): self.exportButton.clicked.connect(self._exportLabels) # Edit Form - self.labelText = QLineEdit(self) - self.labelText.setMaxLength(40) - self.labelText.setPlaceholderText(self.tr("Select item to edit")) - self.labelText.setEnabled(False) - self.labelText.textEdited.connect(self._onNameEdit) + self.editName = QLineEdit(self) + self.editName.setMaxLength(40) + self.editName.setPlaceholderText(self.tr("Select item to edit")) + self.editName.setEnabled(False) + self.editName.textEdited.connect(self._onNameEdit) + + self.labelName = QLabel(self.tr("Label"), self) + self.labelName.setBuddy(self.editName) + + # Icon Colours + self.iconColor = NComboBox(self) + self.iconColor.setMinimumWidth(200) + self.iconColor.setEnabled(False) + self.iconColor.addItem(self.tr("Custom"), CUSTOM_COL) + for key, label in nwLabels.THEME_COLORS.items(): + self.iconColor.addItem(trConst(label), key) + self.iconColor.currentIndexChanged.connect(self._onThemeSelect) + + self.labelColor = QLabel(self.tr("Colour"), self) + self.labelColor.setBuddy(self.iconColor) buttonStyle = ( "QToolButton {padding: 0 4px;} " @@ -425,6 +441,9 @@ class _StatusPage(NFixedPage): self.shapeButton.setStyleSheet(buttonStyle) self.shapeButton.setEnabled(False) + self.labelShape = QLabel(self.tr("Shape"), self) + self.labelShape.setBuddy(self.iconColor) + # Assemble self.listControls = QVBoxLayout() self.listControls.addWidget(self.addButton) @@ -435,10 +454,15 @@ class _StatusPage(NFixedPage): self.listControls.addWidget(self.importButton) self.listControls.addWidget(self.exportButton) - self.editBox = QHBoxLayout() - self.editBox.addWidget(self.labelText, 1) - self.editBox.addWidget(self.colorButton, 0) - self.editBox.addWidget(self.shapeButton, 0) + self.editBox = QGridLayout() + self.editBox.addWidget(self.labelName, 0, 0) + self.editBox.addWidget(self.editName, 0, 1, 1, 5) + self.editBox.addWidget(self.labelColor, 1, 0) + self.editBox.addWidget(self.iconColor, 1, 1) + self.editBox.addWidget(self.colorButton, 1, 2) + self.editBox.addWidget(self.labelShape, 1, 3) + self.editBox.addWidget(self.shapeButton, 1, 4) + self.editBox.setColumnStretch(5, 1) self.innerBox = QGridLayout() self.innerBox.addWidget(self.listBox, 0, 0) @@ -496,11 +520,20 @@ class _StatusPage(NFixedPage): self._changed = True return + @pyqtSlot(int) + def _onThemeSelect(self, index: int) -> None: + """Update the colour handling on theme selection change.""" + self._theme = str(self.iconColor.currentData()) + self._setButtonIcons() + self._updateIcon() + return + @pyqtSlot() def _onColorSelect(self) -> None: """Open a dialog to select the status icon colour.""" if (color := QColorDialog.getColor(self._color, self, self.trSelColor)).isValid(): self._color = color + self._theme = CUSTOM_COL self._setButtonIcons() self._updateIcon() return @@ -511,7 +544,8 @@ class _StatusPage(NFixedPage): color = QColor(100, 100, 100) shape = nwStatusShape.SQUARE icon = NWStatus.createIcon(self._iPx, color, shape) - self._addItem(None, StatusEntry(self.tr("New Item"), color, shape, icon, 0)) + theme = str(self.iconColor.currentData()) + self._addItem(None, StatusEntry(self.tr("New Item"), color, theme, shape, icon, 0)) self._changed = True return @@ -537,22 +571,26 @@ class _StatusPage(NFixedPage): entry: StatusEntry = item.data(self.C_DATA, self.D_ENTRY) self._color = entry.color self._shape = entry.shape + self._theme = entry.theme self._setButtonIcons() - self.labelText.setText(entry.name) - self.labelText.selectAll() - self.labelText.setFocus() + self.editName.setText(entry.name) + self.editName.selectAll() + self.editName.setFocus() - self.labelText.setEnabled(True) + self.editName.setEnabled(True) + self.iconColor.setEnabled(True) self.colorButton.setEnabled(True) self.shapeButton.setEnabled(True) else: self._color = QColor(100, 100, 100) self._shape = nwStatusShape.SQUARE + self._theme = CUSTOM_COL self._setButtonIcons() - self.labelText.setText("") + self.editName.setText("") - self.labelText.setEnabled(False) + self.editName.setEnabled(False) + self.iconColor.setEnabled(False) self.colorButton.setEnabled(False) self.shapeButton.setEnabled(False) return @@ -608,10 +646,11 @@ class _StatusPage(NFixedPage): def _updateIcon(self) -> None: """Apply changes made to a status icon.""" if item := self._getSelectedItem(): - icon = NWStatus.createIcon(self._iPx, self._color, self._shape) + icon = NWStatus.createIcon(self._iPx, self._pickColor(), self._shape) entry: StatusEntry = item.data(self.C_DATA, self.D_ENTRY) entry.color = self._color entry.shape = self._shape + entry.theme = self._theme entry.icon = icon item.setIcon(self.C_LABEL, icon) self._changed = True @@ -658,11 +697,18 @@ class _StatusPage(NFixedPage): def _setButtonIcons(self) -> None: """Set the colour of the colour button.""" - icon = NWStatus.createIcon(self._iPx, self._color, nwStatusShape.SQUARE) + icon = NWStatus.createIcon(self._iPx, self._pickColor(), nwStatusShape.SQUARE) + self.iconColor.setCurrentData(self._theme, CUSTOM_COL) self.colorButton.setIcon(icon) self.shapeButton.setIcon(self._icons[self._shape]) return + def _pickColor(self) -> QColor: + """Get the correct colour value based on selections.""" + if self._theme == CUSTOM_COL: + return self._color + return SHARED.theme.getBaseColor(self._theme) + class _ReplacePage(NFixedPage): From ff85c6688f1183a4f67c97de52a54db1a11ade18 Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Thu, 12 Jun 2025 17:59:38 +0200 Subject: [PATCH 4/6] Add methods to refresh status icons on theme change --- novelwriter/core/project.py | 22 ++++++++++++++-------- novelwriter/core/status.py | 10 ++++++++++ novelwriter/guimain.py | 1 + 3 files changed, 25 insertions(+), 8 deletions(-) diff --git a/novelwriter/core/project.py b/novelwriter/core/project.py index ed5a8284..e86b744b 100644 --- a/novelwriter/core/project.py +++ b/novelwriter/core/project.py @@ -483,14 +483,14 @@ class NWProject: def setDefaultStatusImport(self) -> None: """Set the default status and importance values.""" - self._data.itemStatus.add(None, self.tr("New"), (120, 120, 120), "STAR", 0) - self._data.itemStatus.add(None, self.tr("Note"), (205, 171, 143), "TRIANGLE", 0) - self._data.itemStatus.add(None, self.tr("Draft"), (143, 240, 164), "CIRCLE_T", 0) - self._data.itemStatus.add(None, self.tr("Finished"), (249, 240, 107), "STAR", 0) - self._data.itemImport.add(None, self.tr("New"), (120, 120, 120), "SQUARE", 0) - self._data.itemImport.add(None, self.tr("Minor"), (220, 138, 221), "BLOCK_2", 0) - self._data.itemImport.add(None, self.tr("Major"), (220, 138, 221), "BLOCK_3", 0) - self._data.itemImport.add(None, self.tr("Main"), (220, 138, 221), "BLOCK_4", 0) + self._data.itemStatus.add(None, self.tr("New"), "faded", "STAR", 0) + self._data.itemStatus.add(None, self.tr("Note"), "red", "TRIANGLE", 0) + self._data.itemStatus.add(None, self.tr("Draft"), "yellow", "CIRCLE_T", 0) + self._data.itemStatus.add(None, self.tr("Finished"), "green", "STAR", 0) + self._data.itemImport.add(None, self.tr("New"), "purple", "SQUARE", 0) + self._data.itemImport.add(None, self.tr("Minor"), "purple", "BLOCK_2", 0) + self._data.itemImport.add(None, self.tr("Major"), "purple", "BLOCK_3", 0) + self._data.itemImport.add(None, self.tr("Main"), "purple", "BLOCK_4", 0) return def setProjectLang(self, language: str | None) -> None: @@ -547,6 +547,12 @@ class NWProject: self._tree.refreshAllItems() return + def updateTheme(self) -> None: + """Update theme elements.""" + self._data.itemStatus.refreshIcons() + self._data.itemImport.refreshIcons() + return + def localLookup(self, word: str | int) -> str: """Look up a word or number in the translation map for the project and return it. The variable is cast to a string before diff --git a/novelwriter/core/status.py b/novelwriter/core/status.py index 1fdf5396..bcee828c 100644 --- a/novelwriter/core/status.py +++ b/novelwriter/core/status.py @@ -189,6 +189,16 @@ class NWStatus: logger.error("Could not parse entry %s", str(data)) return None + def refreshIcons(self) -> None: + """Refresh all icons.""" + for entry in self._store.values(): + if entry.theme != CUSTOM_COL: + print("<", entry.color.name(QColor.NameFormat.HexRgb)) + entry.color = SHARED.theme.parseColor(entry.theme) + print(">", entry.color.name(QColor.NameFormat.HexRgb)) + entry.icon = NWStatus.createIcon(self._height, entry.color, entry.shape) + return + @staticmethod def createIcon(height: int, color: QColor, shape: nwStatusShape) -> QIcon: """Generate an icon for a status label.""" diff --git a/novelwriter/guimain.py b/novelwriter/guimain.py index 25f997f6..680f35d5 100644 --- a/novelwriter/guimain.py +++ b/novelwriter/guimain.py @@ -913,6 +913,7 @@ class GuiMain(QMainWindow): def refreshThemeColors(self, syntax: bool = False, force: bool = False) -> None: """Refresh the GUI theme.""" SHARED.theme.loadTheme(force=force) + SHARED.project.updateTheme() self.setPalette(QApplication.palette()) self.docEditor.updateTheme() self.docViewer.updateTheme() From e3eea2628a4b377931c92f79ba64b28a592895e0 Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Thu, 12 Jun 2025 19:22:11 +0200 Subject: [PATCH 5/6] Fix current tests --- novelwriter/core/projectxml.py | 8 +- sample/nwProject.nwx | 30 +-- tests/conftest.py | 8 +- tests/files/nwProject-1.5.nwx | 26 +-- tests/lipsum/nwProject.nwx | 20 +- .../coreProject_NewFileFolder_nwProject.nwx | 18 +- .../coreProject_NewRoot_nwProject.nwx | 18 +- .../coreTools_DocDuplicator_nwProject.nwx | 18 +- .../coreTools_ProjectBuilderA_nwProject.nwx | 18 +- .../coreTools_ProjectBuilderB_nwProject.nwx | 18 +- .../reference/fmtToDocX_SaveDocument_core.xml | 6 +- .../guiEditor_Main_Final_nwProject.nwx | 18 +- .../guiEditor_Main_Initial_nwProject.nwx | 18 +- tests/reference/projectXML_ReadLegacy10.nwx | 24 +- tests/reference/projectXML_ReadLegacy11.nwx | 24 +- tests/reference/projectXML_ReadLegacy12.nwx | 24 +- tests/reference/projectXML_ReadLegacy13.nwx | 24 +- tests/reference/projectXML_ReadLegacy14.nwx | 24 +- tests/test_core/test_core_item.py | 4 +- tests/test_core/test_core_projectxml.py | 216 ++++++++++++------ tests/test_core/test_core_status.py | 93 ++++---- .../test_dialogs/test_dlg_projectsettings.py | 24 +- tests/test_gui/test_gui_docviewerpanel.py | 2 +- 23 files changed, 380 insertions(+), 303 deletions(-) diff --git a/novelwriter/core/projectxml.py b/novelwriter/core/projectxml.py index a20649d2..47ea748e 100644 --- a/novelwriter/core/projectxml.py +++ b/novelwriter/core/projectxml.py @@ -441,10 +441,10 @@ class ProjectXMLReader: for xEntry in xItem: if xEntry.tag == "entry": key = xEntry.attrib.get("key", None) - red = checkInt(xEntry.attrib.get("red", 0), 0) # Deprecated in 1.5 Rev 6 - green = checkInt(xEntry.attrib.get("green", 0), 0) # Deprecated in 1.5 Rev 6 - blue = checkInt(xEntry.attrib.get("blue", 0), 0) # Deprecated in 1.5 Rev 6 - color = xEntry.attrib.get("color") # Added in 1.5 Rev 6 + red = checkInt(xEntry.attrib.get("red", 0), 0) # Deprecated in 1.5 R6 + green = checkInt(xEntry.attrib.get("green", 0), 0) # Deprecated in 1.5 R6 + blue = checkInt(xEntry.attrib.get("blue", 0), 0) # Deprecated in 1.5 R6 + color = xEntry.attrib.get("color") # Added in 1.5 R6 count = checkInt(xEntry.attrib.get("count", 0), 0) shape = xEntry.attrib.get("shape", "") if color is None: diff --git a/sample/nwProject.nwx b/sample/nwProject.nwx index ae0fee4d..32ed2662 100644 --- a/sample/nwProject.nwx +++ b/sample/nwProject.nwx @@ -1,6 +1,6 @@ - - + + Sample Project Jane Smith @@ -20,20 +20,20 @@ D - New - Notes - Started - 1st Draft - 2nd Draft - 3rd Draft - Finished + New + Notes + Started + 1st Draft + 2nd Draft + 3rd Draft + Finished - None - Background - Minor - Major - Main + None + Background + Minor + Major + Main @@ -58,7 +58,7 @@ Chapter One - + Making a Scene diff --git a/tests/conftest.py b/tests/conftest.py index a2c3ad03..bd66447b 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -35,7 +35,7 @@ sys.path.insert(1, str(Path(__file__).parent.parent.absolute())) from novelwriter import CONFIG, SHARED -from tests.mocked import MockGuiMain, MockTheme +from tests.mocked import MockGuiMain from tests.tools import cleanProject _TST_ROOT = Path(__file__).parent @@ -151,12 +151,16 @@ def projPath(fncPath): @pytest.fixture(scope="function") def mockGUI(qtbot, monkeypatch): """Create a mock instance of novelWriter's main GUI class.""" + from novelwriter.gui.theme import GuiTheme + monkeypatch.setattr(QMessageBox, "exec", lambda *a: None) monkeypatch.setattr(QMessageBox, "result", lambda *a: QMessageBox.StandardButton.Yes) gui = MockGuiMain() - theme = MockTheme() + theme = GuiTheme() + theme.loadTheme() monkeypatch.setattr(SHARED, "_gui", gui) monkeypatch.setattr(SHARED, "_theme", theme) + return gui diff --git a/tests/files/nwProject-1.5.nwx b/tests/files/nwProject-1.5.nwx index cd103d11..839b0876 100644 --- a/tests/files/nwProject-1.5.nwx +++ b/tests/files/nwProject-1.5.nwx @@ -1,5 +1,5 @@ - + Sample Project Jane Smith @@ -20,20 +20,20 @@ D - New - Notes - Started - 1st Draft - 2nd Draft - 3rd Draft - Finished + New + Notes + Started + 1st Draft + 2nd Draft + 3rd Draft + Finished - None - Background - Minor - Major - Main + None + Background + Minor + Major + Main diff --git a/tests/lipsum/nwProject.nwx b/tests/lipsum/nwProject.nwx index de32f1f6..f2c7e31c 100644 --- a/tests/lipsum/nwProject.nwx +++ b/tests/lipsum/nwProject.nwx @@ -1,6 +1,6 @@ - - + + Lorem Ipsum lipsum.com @@ -19,16 +19,16 @@ Replace Text 2 - New - Note - Draft - Finished + New + Note + Draft + Finished - New - Minor - Major - Main + New + Minor + Major + Main diff --git a/tests/reference/coreProject_NewFileFolder_nwProject.nwx b/tests/reference/coreProject_NewFileFolder_nwProject.nwx index b244eeb6..d3eec6c6 100644 --- a/tests/reference/coreProject_NewFileFolder_nwProject.nwx +++ b/tests/reference/coreProject_NewFileFolder_nwProject.nwx @@ -1,5 +1,5 @@ - + New Project Jane Doe @@ -16,16 +16,16 @@ - New - Note - Draft - Finished + New + Note + Draft + Finished - New - Minor - Major - Main + New + Minor + Major + Main diff --git a/tests/reference/coreProject_NewRoot_nwProject.nwx b/tests/reference/coreProject_NewRoot_nwProject.nwx index abcfdfb7..4c253097 100644 --- a/tests/reference/coreProject_NewRoot_nwProject.nwx +++ b/tests/reference/coreProject_NewRoot_nwProject.nwx @@ -1,5 +1,5 @@ - + New Project Jane Doe @@ -16,16 +16,16 @@ - New - Note - Draft - Finished + New + Note + Draft + Finished - New - Minor - Major - Main + New + Minor + Major + Main diff --git a/tests/reference/coreTools_DocDuplicator_nwProject.nwx b/tests/reference/coreTools_DocDuplicator_nwProject.nwx index 9b0d2071..2fb52c05 100644 --- a/tests/reference/coreTools_DocDuplicator_nwProject.nwx +++ b/tests/reference/coreTools_DocDuplicator_nwProject.nwx @@ -1,5 +1,5 @@ - + New Project Jane Doe @@ -16,16 +16,16 @@ - New - Note - Draft - Finished + New + Note + Draft + Finished - New - Minor - Major - Main + New + Minor + Major + Main diff --git a/tests/reference/coreTools_ProjectBuilderA_nwProject.nwx b/tests/reference/coreTools_ProjectBuilderA_nwProject.nwx index 296b8172..5b7cbbab 100644 --- a/tests/reference/coreTools_ProjectBuilderA_nwProject.nwx +++ b/tests/reference/coreTools_ProjectBuilderA_nwProject.nwx @@ -1,5 +1,5 @@ - + Test Project A Jane Doe @@ -16,16 +16,16 @@ - New - Note - Draft - Finished + New + Note + Draft + Finished - New - Minor - Major - Main + New + Minor + Major + Main diff --git a/tests/reference/coreTools_ProjectBuilderB_nwProject.nwx b/tests/reference/coreTools_ProjectBuilderB_nwProject.nwx index 531aea22..83362eb3 100644 --- a/tests/reference/coreTools_ProjectBuilderB_nwProject.nwx +++ b/tests/reference/coreTools_ProjectBuilderB_nwProject.nwx @@ -1,5 +1,5 @@ - + Test Project B Jane Doe @@ -16,16 +16,16 @@ - New - Note - Draft - Finished + New + Note + Draft + Finished - New - Minor - Major - Main + New + Minor + Major + Main diff --git a/tests/reference/fmtToDocX_SaveDocument_core.xml b/tests/reference/fmtToDocX_SaveDocument_core.xml index 3a0d34b6..25a58621 100644 --- a/tests/reference/fmtToDocX_SaveDocument_core.xml +++ b/tests/reference/fmtToDocX_SaveDocument_core.xml @@ -1,10 +1,10 @@ - 2025-04-29T22:46:36 - 2025-04-29T22:46:36 + 2025-06-12T19:20:06 + 2025-06-12T19:20:06 lipsum.com Lorem Ipsum en_GB - 52 + 54 lipsum.com diff --git a/tests/reference/guiEditor_Main_Final_nwProject.nwx b/tests/reference/guiEditor_Main_Final_nwProject.nwx index 21078178..d415b195 100644 --- a/tests/reference/guiEditor_Main_Final_nwProject.nwx +++ b/tests/reference/guiEditor_Main_Final_nwProject.nwx @@ -1,5 +1,5 @@ - + New Project Jane Doe @@ -16,16 +16,16 @@ - New - Note - Draft - Finished + New + Note + Draft + Finished - New - Minor - Major - Main + New + Minor + Major + Main diff --git a/tests/reference/guiEditor_Main_Initial_nwProject.nwx b/tests/reference/guiEditor_Main_Initial_nwProject.nwx index 1f3260da..bb69bd02 100644 --- a/tests/reference/guiEditor_Main_Initial_nwProject.nwx +++ b/tests/reference/guiEditor_Main_Initial_nwProject.nwx @@ -1,5 +1,5 @@ - + New Project Jane Doe @@ -16,16 +16,16 @@ - New - Note - Draft - Finished + New + Note + Draft + Finished - New - Minor - Major - Main + New + Minor + Major + Main diff --git a/tests/reference/projectXML_ReadLegacy10.nwx b/tests/reference/projectXML_ReadLegacy10.nwx index cc0c7d88..3a59d7e7 100644 --- a/tests/reference/projectXML_ReadLegacy10.nwx +++ b/tests/reference/projectXML_ReadLegacy10.nwx @@ -1,5 +1,5 @@ - + Sample Project Jay Doh @@ -20,19 +20,19 @@ D - New - Notes - Started - 1st Draft - 2nd Draft - 3rd Draft - Finished + New + Notes + Started + 1st Draft + 2nd Draft + 3rd Draft + Finished - None - Minor - Major - Main + None + Minor + Major + Main diff --git a/tests/reference/projectXML_ReadLegacy11.nwx b/tests/reference/projectXML_ReadLegacy11.nwx index 0ea370fb..bd4bc6f5 100644 --- a/tests/reference/projectXML_ReadLegacy11.nwx +++ b/tests/reference/projectXML_ReadLegacy11.nwx @@ -1,5 +1,5 @@ - + Sample Project Jay Doh @@ -20,19 +20,19 @@ D - New - Notes - Started - 1st Draft - 2nd Draft - 3rd Draft - Finished + New + Notes + Started + 1st Draft + 2nd Draft + 3rd Draft + Finished - None - Minor - Major - Main + None + Minor + Major + Main diff --git a/tests/reference/projectXML_ReadLegacy12.nwx b/tests/reference/projectXML_ReadLegacy12.nwx index bac7fb3a..7a949b06 100644 --- a/tests/reference/projectXML_ReadLegacy12.nwx +++ b/tests/reference/projectXML_ReadLegacy12.nwx @@ -1,5 +1,5 @@ - + Sample Project Jay Doh @@ -20,19 +20,19 @@ D - New - Notes - Started - 1st Draft - 2nd Draft - 3rd Draft - Finished + New + Notes + Started + 1st Draft + 2nd Draft + 3rd Draft + Finished - None - Minor - Major - Main + None + Minor + Major + Main diff --git a/tests/reference/projectXML_ReadLegacy13.nwx b/tests/reference/projectXML_ReadLegacy13.nwx index 1fb96f5e..c9fc13e6 100644 --- a/tests/reference/projectXML_ReadLegacy13.nwx +++ b/tests/reference/projectXML_ReadLegacy13.nwx @@ -1,5 +1,5 @@ - + Sample Project Jay Doh @@ -20,19 +20,19 @@ D - New - Notes - Started - 1st Draft - 2nd Draft - 3rd Draft - Finished + New + Notes + Started + 1st Draft + 2nd Draft + 3rd Draft + Finished - None - Minor - Major - Main + None + Minor + Major + Main diff --git a/tests/reference/projectXML_ReadLegacy14.nwx b/tests/reference/projectXML_ReadLegacy14.nwx index 3c763d27..b123a490 100644 --- a/tests/reference/projectXML_ReadLegacy14.nwx +++ b/tests/reference/projectXML_ReadLegacy14.nwx @@ -1,5 +1,5 @@ - + Sample Project Jay Doh @@ -20,19 +20,19 @@ D - New - Notes - Started - 1st Draft - 2nd Draft - 3rd Draft - Finished + New + Notes + Started + 1st Draft + 2nd Draft + 3rd Draft + Finished - None - Minor - Major - Main + None + Minor + Major + Main diff --git a/tests/test_core/test_core_item.py b/tests/test_core/test_core_item.py index ad93f7db..f6cd4278 100644 --- a/tests/test_core/test_core_item.py +++ b/tests/test_core/test_core_item.py @@ -547,8 +547,8 @@ def testCoreItem_ClassDefaults(mockGUI): def testCoreItem_PackUnpack(mockGUI, caplog, mockRnd): """Test packing and unpacking entries for the NWItem class.""" project = NWProject() - project.data.itemStatus.add(None, "New", (100, 100, 100), "SQUARE", 0) - project.data.itemImport.add(None, "New", (100, 100, 100), "SQUARE", 0) + project.data.itemStatus.add(None, "New", "#646464", "SQUARE", 0) + project.data.itemImport.add(None, "New", "#646464", "SQUARE", 0) # Invalid item = NWItem(project, "0000000000000") diff --git a/tests/test_core/test_core_projectxml.py b/tests/test_core/test_core_projectxml.py index 3021c7c4..ebccbbfa 100644 --- a/tests/test_core/test_core_projectxml.py +++ b/tests/test_core/test_core_projectxml.py @@ -27,12 +27,11 @@ from shutil import copyfile import pytest -from PyQt6.QtGui import QColor - from novelwriter.constants import nwFiles from novelwriter.core.item import NWItem from novelwriter.core.projectdata import NWProjectData from novelwriter.core.projectxml import ProjectXMLReader, ProjectXMLWriter, XMLReadState +from novelwriter.core.status import CUSTOM_COL from novelwriter.enum import nwStatusShape from tests.mocked import causeOSError @@ -137,7 +136,7 @@ def testCoreProjectXML_ReadCurrent(monkeypatch, mockGUI, tstPaths, fncPath): assert xmlReader.state == XMLReadState.PARSED_OK assert xmlReader.xmlRoot == "novelWriterXML" assert xmlReader.xmlVersion == 0x0105 - assert xmlReader.xmlRevision == 5 + assert xmlReader.xmlRevision == 6 assert xmlReader.appVersion == "2.7b1" assert xmlReader.hexVersion == 0x020700b1 @@ -173,19 +172,31 @@ def testCoreProjectXML_ReadCurrent(monkeypatch, mockGUI, tstPaths, fncPath): assert data.itemImport["i2d7a54"].name == "Major" assert data.itemImport["i56be10"].name == "Main" - assert data.itemStatus["sf12341"].color == QColor(100, 100, 100) - assert data.itemStatus["sf24ce6"].color == QColor(200, 50, 0) - assert data.itemStatus["sc24b8f"].color == QColor(182, 60, 0) - assert data.itemStatus["s90e6c9"].color == QColor(193, 129, 0) - assert data.itemStatus["sd51c5b"].color == QColor(193, 129, 0) - assert data.itemStatus["s8ae72a"].color == QColor(193, 129, 0) - assert data.itemStatus["s78ea90"].color == QColor(58, 180, 58) + assert data.itemStatus["sf12341"].color.getRgb() == (100, 100, 100, 255) + assert data.itemStatus["sf24ce6"].color.getRgb() == (200, 50, 0, 255) + assert data.itemStatus["sc24b8f"].color.getRgb() == (182, 60, 0, 255) + assert data.itemStatus["s90e6c9"].color.getRgb() == (193, 129, 0, 255) + assert data.itemStatus["sd51c5b"].color.getRgb() == (193, 129, 0, 255) + assert data.itemStatus["s8ae72a"].color.getRgb() == (193, 129, 0, 255) + assert data.itemStatus["s78ea90"].color.getRgb() == (58, 180, 58, 255) - assert data.itemImport["ia857f0"].color == QColor(100, 100, 100) - assert data.itemImport["i4a1d39"].color == QColor(220, 138, 221) - assert data.itemImport["icfb3a5"].color == QColor(220, 138, 221) - assert data.itemImport["i2d7a54"].color == QColor(220, 138, 221) - assert data.itemImport["i56be10"].color == QColor(220, 138, 221) + assert data.itemImport["ia857f0"].color.getRgb() == (100, 100, 100, 255) + assert data.itemImport["icfb3a5"].color.getRgb() == (220, 138, 221, 255) + assert data.itemImport["i2d7a54"].color.getRgb() == (220, 138, 221, 255) + assert data.itemImport["i56be10"].color.getRgb() == (220, 138, 221, 255) + + assert data.itemStatus["sf12341"].theme == CUSTOM_COL + assert data.itemStatus["sf24ce6"].theme == CUSTOM_COL + assert data.itemStatus["sc24b8f"].theme == CUSTOM_COL + assert data.itemStatus["s90e6c9"].theme == CUSTOM_COL + assert data.itemStatus["sd51c5b"].theme == CUSTOM_COL + assert data.itemStatus["s8ae72a"].theme == CUSTOM_COL + assert data.itemStatus["s78ea90"].theme == CUSTOM_COL + + assert data.itemImport["ia857f0"].theme == CUSTOM_COL + assert data.itemImport["icfb3a5"].theme == CUSTOM_COL + assert data.itemImport["i2d7a54"].theme == CUSTOM_COL + assert data.itemImport["i56be10"].theme == CUSTOM_COL assert data.itemStatus["sf12341"].shape == nwStatusShape.SQUARE assert data.itemStatus["sf24ce6"].shape == nwStatusShape.SQUARE @@ -305,18 +316,31 @@ def testCoreProjectXML_ReadLegacy10(tstPaths, fncPath, mockGUI, mockRnd): assert data.itemImport["i000009"].name == "Major" assert data.itemImport["i00000a"].name == "Main" - assert data.itemStatus["s000000"].color == QColor(100, 100, 100) - assert data.itemStatus["s000001"].color == QColor(200, 50, 0) - assert data.itemStatus["s000002"].color == QColor(182, 60, 0) - assert data.itemStatus["s000003"].color == QColor(193, 129, 0) - assert data.itemStatus["s000004"].color == QColor(193, 129, 0) - assert data.itemStatus["s000005"].color == QColor(193, 129, 0) - assert data.itemStatus["s000006"].color == QColor(58, 180, 58) + assert data.itemStatus["s000000"].color.getRgb() == (100, 100, 100, 255) + assert data.itemStatus["s000001"].color.getRgb() == (200, 50, 0, 255) + assert data.itemStatus["s000002"].color.getRgb() == (182, 60, 0, 255) + assert data.itemStatus["s000003"].color.getRgb() == (193, 129, 0, 255) + assert data.itemStatus["s000004"].color.getRgb() == (193, 129, 0, 255) + assert data.itemStatus["s000005"].color.getRgb() == (193, 129, 0, 255) + assert data.itemStatus["s000006"].color.getRgb() == (58, 180, 58, 255) - assert data.itemImport["i000007"].color == QColor(100, 100, 100) - assert data.itemImport["i000008"].color == QColor(0, 122, 188) - assert data.itemImport["i000009"].color == QColor(21, 0, 180) - assert data.itemImport["i00000a"].color == QColor(117, 0, 175) + assert data.itemImport["i000007"].color.getRgb() == (100, 100, 100, 255) + assert data.itemImport["i000008"].color.getRgb() == (0, 122, 188, 255) + assert data.itemImport["i000009"].color.getRgb() == (21, 0, 180, 255) + assert data.itemImport["i00000a"].color.getRgb() == (117, 0, 175, 255) + + assert data.itemStatus["s000000"].theme == CUSTOM_COL + assert data.itemStatus["s000001"].theme == CUSTOM_COL + assert data.itemStatus["s000002"].theme == CUSTOM_COL + assert data.itemStatus["s000003"].theme == CUSTOM_COL + assert data.itemStatus["s000004"].theme == CUSTOM_COL + assert data.itemStatus["s000005"].theme == CUSTOM_COL + assert data.itemStatus["s000006"].theme == CUSTOM_COL + + assert data.itemImport["i000007"].theme == CUSTOM_COL + assert data.itemImport["i000008"].theme == CUSTOM_COL + assert data.itemImport["i000009"].theme == CUSTOM_COL + assert data.itemImport["i00000a"].theme == CUSTOM_COL assert data.itemStatus["s000000"].shape == nwStatusShape.SQUARE assert data.itemStatus["s000001"].shape == nwStatusShape.SQUARE @@ -450,18 +474,31 @@ def testCoreProjectXML_ReadLegacy11(tstPaths, fncPath, mockGUI, mockRnd): assert data.itemImport["i000009"].name == "Major" assert data.itemImport["i00000a"].name == "Main" - assert data.itemStatus["s000000"].color == QColor(100, 100, 100) - assert data.itemStatus["s000001"].color == QColor(200, 50, 0) - assert data.itemStatus["s000002"].color == QColor(182, 60, 0) - assert data.itemStatus["s000003"].color == QColor(193, 129, 0) - assert data.itemStatus["s000004"].color == QColor(193, 129, 0) - assert data.itemStatus["s000005"].color == QColor(193, 129, 0) - assert data.itemStatus["s000006"].color == QColor(58, 180, 58) + assert data.itemStatus["s000000"].color.getRgb() == (100, 100, 100, 255) + assert data.itemStatus["s000001"].color.getRgb() == (200, 50, 0, 255) + assert data.itemStatus["s000002"].color.getRgb() == (182, 60, 0, 255) + assert data.itemStatus["s000003"].color.getRgb() == (193, 129, 0, 255) + assert data.itemStatus["s000004"].color.getRgb() == (193, 129, 0, 255) + assert data.itemStatus["s000005"].color.getRgb() == (193, 129, 0, 255) + assert data.itemStatus["s000006"].color.getRgb() == (58, 180, 58, 255) - assert data.itemImport["i000007"].color == QColor(100, 100, 100) - assert data.itemImport["i000008"].color == QColor(0, 122, 188) - assert data.itemImport["i000009"].color == QColor(21, 0, 180) - assert data.itemImport["i00000a"].color == QColor(117, 0, 175) + assert data.itemImport["i000007"].color.getRgb() == (100, 100, 100, 255) + assert data.itemImport["i000008"].color.getRgb() == (0, 122, 188, 255) + assert data.itemImport["i000009"].color.getRgb() == (21, 0, 180, 255) + assert data.itemImport["i00000a"].color.getRgb() == (117, 0, 175, 255) + + assert data.itemStatus["s000000"].theme == CUSTOM_COL + assert data.itemStatus["s000001"].theme == CUSTOM_COL + assert data.itemStatus["s000002"].theme == CUSTOM_COL + assert data.itemStatus["s000003"].theme == CUSTOM_COL + assert data.itemStatus["s000004"].theme == CUSTOM_COL + assert data.itemStatus["s000005"].theme == CUSTOM_COL + assert data.itemStatus["s000006"].theme == CUSTOM_COL + + assert data.itemImport["i000007"].theme == CUSTOM_COL + assert data.itemImport["i000008"].theme == CUSTOM_COL + assert data.itemImport["i000009"].theme == CUSTOM_COL + assert data.itemImport["i00000a"].theme == CUSTOM_COL assert data.itemStatus["s000000"].shape == nwStatusShape.SQUARE assert data.itemStatus["s000001"].shape == nwStatusShape.SQUARE @@ -595,18 +632,31 @@ def testCoreProjectXML_ReadLegacy12(tstPaths, fncPath, mockGUI, mockRnd): assert data.itemImport["i000009"].name == "Major" assert data.itemImport["i00000a"].name == "Main" - assert data.itemStatus["s000000"].color == QColor(100, 100, 100) - assert data.itemStatus["s000001"].color == QColor(200, 50, 0) - assert data.itemStatus["s000002"].color == QColor(182, 60, 0) - assert data.itemStatus["s000003"].color == QColor(193, 129, 0) - assert data.itemStatus["s000004"].color == QColor(193, 129, 0) - assert data.itemStatus["s000005"].color == QColor(193, 129, 0) - assert data.itemStatus["s000006"].color == QColor(58, 180, 58) + assert data.itemStatus["s000000"].color.getRgb() == (100, 100, 100, 255) + assert data.itemStatus["s000001"].color.getRgb() == (200, 50, 0, 255) + assert data.itemStatus["s000002"].color.getRgb() == (182, 60, 0, 255) + assert data.itemStatus["s000003"].color.getRgb() == (193, 129, 0, 255) + assert data.itemStatus["s000004"].color.getRgb() == (193, 129, 0, 255) + assert data.itemStatus["s000005"].color.getRgb() == (193, 129, 0, 255) + assert data.itemStatus["s000006"].color.getRgb() == (58, 180, 58, 255) - assert data.itemImport["i000007"].color == QColor(100, 100, 100) - assert data.itemImport["i000008"].color == QColor(0, 122, 188) - assert data.itemImport["i000009"].color == QColor(21, 0, 180) - assert data.itemImport["i00000a"].color == QColor(117, 0, 175) + assert data.itemImport["i000007"].color.getRgb() == (100, 100, 100, 255) + assert data.itemImport["i000008"].color.getRgb() == (0, 122, 188, 255) + assert data.itemImport["i000009"].color.getRgb() == (21, 0, 180, 255) + assert data.itemImport["i00000a"].color.getRgb() == (117, 0, 175, 255) + + assert data.itemStatus["s000000"].theme == CUSTOM_COL + assert data.itemStatus["s000001"].theme == CUSTOM_COL + assert data.itemStatus["s000002"].theme == CUSTOM_COL + assert data.itemStatus["s000003"].theme == CUSTOM_COL + assert data.itemStatus["s000004"].theme == CUSTOM_COL + assert data.itemStatus["s000005"].theme == CUSTOM_COL + assert data.itemStatus["s000006"].theme == CUSTOM_COL + + assert data.itemImport["i000007"].theme == CUSTOM_COL + assert data.itemImport["i000008"].theme == CUSTOM_COL + assert data.itemImport["i000009"].theme == CUSTOM_COL + assert data.itemImport["i00000a"].theme == CUSTOM_COL assert data.itemStatus["s000000"].shape == nwStatusShape.SQUARE assert data.itemStatus["s000001"].shape == nwStatusShape.SQUARE @@ -743,18 +793,31 @@ def testCoreProjectXML_ReadLegacy13(tstPaths, fncPath, mockGUI, mockRnd): assert data.itemImport["i000009"].name == "Major" assert data.itemImport["i00000a"].name == "Main" - assert data.itemStatus["s000000"].color == QColor(100, 100, 100) - assert data.itemStatus["s000001"].color == QColor(200, 50, 0) - assert data.itemStatus["s000002"].color == QColor(182, 60, 0) - assert data.itemStatus["s000003"].color == QColor(193, 129, 0) - assert data.itemStatus["s000004"].color == QColor(193, 129, 0) - assert data.itemStatus["s000005"].color == QColor(193, 129, 0) - assert data.itemStatus["s000006"].color == QColor(58, 180, 58) + assert data.itemStatus["s000000"].color.getRgb() == (100, 100, 100, 255) + assert data.itemStatus["s000001"].color.getRgb() == (200, 50, 0, 255) + assert data.itemStatus["s000002"].color.getRgb() == (182, 60, 0, 255) + assert data.itemStatus["s000003"].color.getRgb() == (193, 129, 0, 255) + assert data.itemStatus["s000004"].color.getRgb() == (193, 129, 0, 255) + assert data.itemStatus["s000005"].color.getRgb() == (193, 129, 0, 255) + assert data.itemStatus["s000006"].color.getRgb() == (58, 180, 58, 255) - assert data.itemImport["i000007"].color == QColor(100, 100, 100) - assert data.itemImport["i000008"].color == QColor(0, 122, 188) - assert data.itemImport["i000009"].color == QColor(21, 0, 180) - assert data.itemImport["i00000a"].color == QColor(117, 0, 175) + assert data.itemImport["i000007"].color.getRgb() == (100, 100, 100, 255) + assert data.itemImport["i000008"].color.getRgb() == (0, 122, 188, 255) + assert data.itemImport["i000009"].color.getRgb() == (21, 0, 180, 255) + assert data.itemImport["i00000a"].color.getRgb() == (117, 0, 175, 255) + + assert data.itemStatus["s000000"].theme == CUSTOM_COL + assert data.itemStatus["s000001"].theme == CUSTOM_COL + assert data.itemStatus["s000002"].theme == CUSTOM_COL + assert data.itemStatus["s000003"].theme == CUSTOM_COL + assert data.itemStatus["s000004"].theme == CUSTOM_COL + assert data.itemStatus["s000005"].theme == CUSTOM_COL + assert data.itemStatus["s000006"].theme == CUSTOM_COL + + assert data.itemImport["i000007"].theme == CUSTOM_COL + assert data.itemImport["i000008"].theme == CUSTOM_COL + assert data.itemImport["i000009"].theme == CUSTOM_COL + assert data.itemImport["i00000a"].theme == CUSTOM_COL assert data.itemStatus["s000000"].shape == nwStatusShape.SQUARE assert data.itemStatus["s000001"].shape == nwStatusShape.SQUARE @@ -891,18 +954,31 @@ def testCoreProjectXML_ReadLegacy14(tstPaths, fncPath, mockGUI, mockRnd): assert data.itemImport["i2d7a54"].name == "Major" assert data.itemImport["i56be10"].name == "Main" - assert data.itemStatus["sf12341"].color == QColor(100, 100, 100) - assert data.itemStatus["sf24ce6"].color == QColor(200, 50, 0) - assert data.itemStatus["sc24b8f"].color == QColor(182, 60, 0) - assert data.itemStatus["s90e6c9"].color == QColor(193, 129, 0) - assert data.itemStatus["sd51c5b"].color == QColor(193, 129, 0) - assert data.itemStatus["s8ae72a"].color == QColor(193, 129, 0) - assert data.itemStatus["s78ea90"].color == QColor(58, 180, 58) + assert data.itemStatus["sf12341"].color.getRgb() == (100, 100, 100, 255) + assert data.itemStatus["sf24ce6"].color.getRgb() == (200, 50, 0, 255) + assert data.itemStatus["sc24b8f"].color.getRgb() == (182, 60, 0, 255) + assert data.itemStatus["s90e6c9"].color.getRgb() == (193, 129, 0, 255) + assert data.itemStatus["sd51c5b"].color.getRgb() == (193, 129, 0, 255) + assert data.itemStatus["s8ae72a"].color.getRgb() == (193, 129, 0, 255) + assert data.itemStatus["s78ea90"].color.getRgb() == (58, 180, 58, 255) - assert data.itemImport["ia857f0"].color == QColor(100, 100, 100) - assert data.itemImport["icfb3a5"].color == QColor(0, 122, 188) - assert data.itemImport["i2d7a54"].color == QColor(21, 0, 180) - assert data.itemImport["i56be10"].color == QColor(117, 0, 175) + assert data.itemImport["ia857f0"].color.getRgb() == (100, 100, 100, 255) + assert data.itemImport["icfb3a5"].color.getRgb() == (0, 122, 188, 255) + assert data.itemImport["i2d7a54"].color.getRgb() == (21, 0, 180, 255) + assert data.itemImport["i56be10"].color.getRgb() == (117, 0, 175, 255) + + assert data.itemStatus["sf12341"].theme == CUSTOM_COL + assert data.itemStatus["sf24ce6"].theme == CUSTOM_COL + assert data.itemStatus["sc24b8f"].theme == CUSTOM_COL + assert data.itemStatus["s90e6c9"].theme == CUSTOM_COL + assert data.itemStatus["sd51c5b"].theme == CUSTOM_COL + assert data.itemStatus["s8ae72a"].theme == CUSTOM_COL + assert data.itemStatus["s78ea90"].theme == CUSTOM_COL + + assert data.itemImport["ia857f0"].theme == CUSTOM_COL + assert data.itemImport["icfb3a5"].theme == CUSTOM_COL + assert data.itemImport["i2d7a54"].theme == CUSTOM_COL + assert data.itemImport["i56be10"].theme == CUSTOM_COL assert data.itemStatus["sf12341"].shape == nwStatusShape.SQUARE assert data.itemStatus["sf24ce6"].shape == nwStatusShape.SQUARE diff --git a/tests/test_core/test_core_status.py b/tests/test_core/test_core_status.py index dd6b18a5..3b5e80b8 100644 --- a/tests/test_core/test_core_status.py +++ b/tests/test_core/test_core_status.py @@ -24,7 +24,7 @@ import pytest from PyQt6.QtGui import QColor, QIcon -from novelwriter.core.status import NWStatus, StatusEntry, _ShapeCache +from novelwriter.core.status import CUSTOM_COL, NWStatus, StatusEntry, _ShapeCache from novelwriter.enum import nwStatusShape from tests.tools import C @@ -38,11 +38,12 @@ def testCoreStatus_StatusEntry(): """Test the StatusEntry class.""" color = QColor(255, 0, 0) icon = NWStatus.createIcon(24, color, nwStatusShape.CIRCLE) - entry = StatusEntry("Test", color, nwStatusShape.CIRCLE, icon, 42) + entry = StatusEntry("Test", color, CUSTOM_COL, nwStatusShape.CIRCLE, icon, 42) # Check values assert entry.name == "Test" assert entry.color is color + assert entry.theme == CUSTOM_COL assert entry.shape == nwStatusShape.CIRCLE assert entry.icon is icon assert entry.count == 42 @@ -55,6 +56,7 @@ def testCoreStatus_StatusEntry(): assert other.name == "Test" assert other.color is not color # Not the same object assert other.color == color # But same colours + assert entry.theme == CUSTOM_COL assert other.shape == nwStatusShape.CIRCLE assert other.icon is not icon # Not the same icon, but a copy assert other.count == 42 @@ -73,14 +75,14 @@ def testCoreStatus_Internal(mockGUI, mockRnd): assert nStatus._newKey() == statusKeys[1] # Key collision, should move to key 3 - nStatus.add(statusKeys[2], "Crash", (0, 0, 0), "SQUARE", 0) + nStatus.add(statusKeys[2], "Crash", "#000000", "SQUARE", 0) assert nStatus._newKey() == statusKeys[3] assert nImport._newKey() == importKeys[0] assert nImport._newKey() == importKeys[1] # Key collision, should move to key 3 - nImport.add(importKeys[2], "Crash", (0, 0, 0), "SQUARE", 0) + nImport.add(importKeys[2], "Crash", "#000000", "SQUARE", 0) assert nImport._newKey() == importKeys[3] # Check Key @@ -119,14 +121,14 @@ def testCoreStatus_Internal(mockGUI, mockRnd): def testCoreStatus_Iterator(mockGUI, mockRnd): """Test the iterator functions of the NWStatus class.""" nStatus = NWStatus(NWStatus.STATUS) - nStatus.add(None, "New", (100, 100, 100), "SQUARE", 0) - nStatus.add(None, "Note", (200, 50, 0), "CIRCLE", 1) - nStatus.add(None, "Draft", (200, 150, 0), "SQUARE", 2) - nStatus.add(None, "Finished", (50, 200, 0), "CIRCLE", 3) + nStatus.add(None, "New", "#646464", "SQUARE", 0) + nStatus.add(None, "Note", "#ff3f00", "CIRCLE", 1) + nStatus.add(None, "Draft", "#ffaf00", "SQUARE", 2) + nStatus.add(None, "Finished", "#3fff00", "CIRCLE", 3) # Direct access entry = nStatus[statusKeys[0]] - assert entry.color == QColor(100, 100, 100) + assert entry.color.getRgb() == (100, 100, 100, 255) assert entry.name == "New" assert entry.count == 0 assert isinstance(entry.icon, QIcon) @@ -146,8 +148,8 @@ def testCoreStatus_Iterator(mockGUI, mockRnd): ] # Content : Colours - assert [e.color for _, e in nStatus.iterItems()] == [ - QColor(100, 100, 100), QColor(200, 50, 0), QColor(200, 150, 0), QColor(50, 200, 0) + assert [e.color.getRgb() for _, e in nStatus.iterItems()] == [ + (100, 100, 100, 255), (255, 63, 0, 255), (255, 175, 0, 255), (63, 255, 0, 255) ] # Content : Shape @@ -168,27 +170,31 @@ def testCoreStatus_Entries(mockGUI, mockRnd): # === # Has a key - nStatus.add(statusKeys[0], "Entry 1", (200, 100, 50), "SQUARE", 0) + nStatus.add(statusKeys[0], "Entry 1", "200, 100, 50", "SQUARE", 0) assert nStatus[statusKeys[0]].name == "Entry 1" - assert nStatus[statusKeys[0]].color == QColor(200, 100, 50) + assert nStatus[statusKeys[0]].color.getRgb() == (200, 100, 50, 255) + assert nStatus[statusKeys[0]].theme == CUSTOM_COL assert nStatus[statusKeys[0]].shape == nwStatusShape.SQUARE # Doesn't have a key - nStatus.add(None, "Entry 2", (210, 110, 60), "SQUARE", 0) + nStatus.add(None, "Entry 2", "210, 110, 60", "SQUARE", 0) assert nStatus[statusKeys[1]].name == "Entry 2" - assert nStatus[statusKeys[1]].color == QColor(210, 110, 60) + assert nStatus[statusKeys[1]].color.getRgb() == (210, 110, 60, 255) + assert nStatus[statusKeys[1]].theme == CUSTOM_COL assert nStatus[statusKeys[1]].shape == nwStatusShape.SQUARE # Wrong colour spec, unknown shape - nStatus.add(None, "Entry 3", "what?", "", 0) # type: ignore + nStatus.add(None, "Entry 3", "what?", "", 0) assert nStatus[statusKeys[2]].name == "Entry 3" - assert nStatus[statusKeys[2]].color == QColor(100, 100, 100) + assert nStatus[statusKeys[2]].color.getRgb() == (0, 0, 0, 255) + assert nStatus[statusKeys[2]].theme == CUSTOM_COL assert nStatus[statusKeys[2]].shape == nwStatusShape.SQUARE - # Wrong colour count - nStatus.add(None, "Entry 4", (10, 20), "CIRCLE", 0) # type: ignore + # Wrong colour definition + nStatus.add(None, "Entry 4", "#stuff#", "CIRCLE", 0) assert nStatus[statusKeys[3]].name == "Entry 4" - assert nStatus[statusKeys[3]].color == QColor(100, 100, 100) + assert nStatus[statusKeys[3]].color.getRgb() == (0, 0, 0, 255) + assert nStatus[statusKeys[3]].theme == CUSTOM_COL assert nStatus[statusKeys[3]].shape == nwStatusShape.CIRCLE # Check @@ -202,8 +208,6 @@ def testCoreStatus_Entries(mockGUI, mockRnd): assert nStatus.check("s987654") == statusKeys[0] # Name Access - # =========== - assert nStatus[statusKeys[0]].name == "Entry 1" assert nStatus[statusKeys[1]].name == "Entry 2" assert nStatus[statusKeys[2]].name == "Entry 3" @@ -211,17 +215,20 @@ def testCoreStatus_Entries(mockGUI, mockRnd): assert nStatus["blablabla"].name == "Entry 1" # Colour Access - # ============= + assert nStatus[statusKeys[0]].color.getRgb() == (200, 100, 50, 255) + assert nStatus[statusKeys[1]].color.getRgb() == (210, 110, 60, 255) + assert nStatus[statusKeys[2]].color.getRgb() == (0, 0, 0, 255) + assert nStatus[statusKeys[3]].color.getRgb() == (0, 0, 0, 255) + assert nStatus["blablabla"].color.getRgb() == (200, 100, 50, 255) - assert nStatus[statusKeys[0]].color == QColor(200, 100, 50) - assert nStatus[statusKeys[1]].color == QColor(210, 110, 60) - assert nStatus[statusKeys[2]].color == QColor(100, 100, 100) - assert nStatus[statusKeys[3]].color == QColor(100, 100, 100) - assert nStatus["blablabla"].color == QColor(200, 100, 50) + # Theme Access + assert nStatus[statusKeys[0]].theme == CUSTOM_COL + assert nStatus[statusKeys[1]].theme == CUSTOM_COL + assert nStatus[statusKeys[2]].theme == CUSTOM_COL + assert nStatus[statusKeys[3]].theme == CUSTOM_COL + assert nStatus["blablabla"].theme == CUSTOM_COL # Icon Access - # =========== - assert isinstance(nStatus[statusKeys[0]].icon, QIcon) assert isinstance(nStatus[statusKeys[1]].icon, QIcon) assert isinstance(nStatus[statusKeys[2]].icon, QIcon) @@ -229,8 +236,6 @@ def testCoreStatus_Entries(mockGUI, mockRnd): assert isinstance(nStatus["blablabla"].icon, QIcon) # Shape Access - # ============ - assert nStatus[statusKeys[0]].shape == nwStatusShape.SQUARE assert nStatus[statusKeys[1]].shape == nwStatusShape.SQUARE assert nStatus[statusKeys[2]].shape == nwStatusShape.SQUARE @@ -322,10 +327,10 @@ def testCoreStatus_Entries(mockGUI, mockRnd): def testCoreStatus_Pack(mockGUI, mockRnd): """Test data packing of the NWStatus class.""" nStatus = NWStatus(NWStatus.STATUS) - nStatus.add(None, "New", (100, 100, 100), "SQUARE", 0) - nStatus.add(None, "Note", (200, 50, 0), "CIRCLE", 0) - nStatus.add(None, "Draft", (200, 150, 0), "SQUARE", 0) - nStatus.add(None, "Finished", (50, 200, 0), "SQUARE", 0) + nStatus.add(None, "New", "#646464", "SQUARE", 0) + nStatus.add(None, "Note", "#c83200", "CIRCLE", 0) + nStatus.add(None, "Draft", "#c89600", "SQUARE", 0) + nStatus.add(None, "Finished", "#32c800", "SQUARE", 0) countTo = [3, 5, 7, 9] for i, n in enumerate(countTo): @@ -337,33 +342,25 @@ def testCoreStatus_Pack(mockGUI, mockRnd): ("New", { "key": statusKeys[0], "count": "3", - "red": "100", - "green": "100", - "blue": "100", + "color": "#646464", "shape": "SQUARE", }), ("Note", { "key": statusKeys[1], "count": "5", - "red": "200", - "green": "50", - "blue": "0", + "color": "#c83200", "shape": "CIRCLE", }), ("Draft", { "key": statusKeys[2], "count": "7", - "red": "200", - "green": "150", - "blue": "0", + "color": "#c89600", "shape": "SQUARE", }), ("Finished", { "key": statusKeys[3], "count": "9", - "red": "50", - "green": "200", - "blue": "0", + "color": "#32c800", "shape": "SQUARE", }), ] diff --git a/tests/test_dialogs/test_dlg_projectsettings.py b/tests/test_dialogs/test_dlg_projectsettings.py index edd0cebe..c20382d1 100644 --- a/tests/test_dialogs/test_dlg_projectsettings.py +++ b/tests/test_dialogs/test_dlg_projectsettings.py @@ -214,22 +214,22 @@ def testDlgProjSettings_StatusImport(qtbot, monkeypatch, nwGUI, projPath, mockRn assert update[0][0] == C.sNew assert update[0][1].name == "New" - assert update[0][1].color == QColor(120, 120, 120) + assert update[0][1].color.getRgb() == (108, 108, 108, 255) assert update[0][1].shape == nwStatusShape.STAR assert update[1][0] == C.sDraft assert update[1][1].name == "Draft" - assert update[1][1].color == QColor(143, 240, 164) + assert update[1][1].color.getRgb() == (163, 156, 52, 255) assert update[1][1].shape == nwStatusShape.CIRCLE_T assert update[2][0] == C.sFinished assert update[2][1].name == "Finished" - assert update[2][1].color == QColor(249, 240, 107) + assert update[2][1].color.getRgb() == (41, 102, 41, 255) assert update[2][1].shape == nwStatusShape.STAR assert update[3][0] is None assert update[3][1].name == "Final" - assert update[3][1].color == QColor(20, 30, 40) + assert update[3][1].color.getRgb() == (20, 30, 40, 255) assert update[3][1].shape == nwStatusShape.CIRCLE # Move items, none selected -> no change @@ -289,22 +289,22 @@ def testDlgProjSettings_StatusImport(qtbot, monkeypatch, nwGUI, projPath, mockRn assert update[0][0] == C.iNew assert update[0][1].name == "New" - assert update[0][1].color == QColor(120, 120, 120) + assert update[0][1].color.getRgb() == (179, 90, 179, 255) assert update[0][1].shape == nwStatusShape.SQUARE assert update[1][0] == C.iMajor assert update[1][1].name == "Major" - assert update[1][1].color == QColor(220, 138, 221) + assert update[1][1].color.getRgb() == (179, 90, 179, 255) assert update[1][1].shape == nwStatusShape.BLOCK_3 assert update[2][0] == C.iMain assert update[2][1].name == "Main" - assert update[2][1].color == QColor(220, 138, 221) + assert update[2][1].color.getRgb() == (179, 90, 179, 255) assert update[2][1].shape == nwStatusShape.BLOCK_4 assert update[3][0] is None assert update[3][1].name == "Final" - assert update[3][1].color == QColor(20, 30, 40) + assert update[3][1].color.getRgb() == (20, 30, 40, 255) assert update[3][1].shape == nwStatusShape.TRIANGLE # Check Project @@ -354,10 +354,10 @@ def testDlgProjSettings_StatusImportExport(qtbot, monkeypatch, nwGUI, projPath, assert expFile.is_file() is True assert expFile.read_text().split() == [ - "STAR,#787878,New", - "TRIANGLE,#cdab8f,Note", - "CIRCLE_T,#8ff0a4,Draft", - "STAR,#f9f06b,Finished", + "STAR,#6c6c6c,New", + "TRIANGLE,#a62a2d,Note", + "CIRCLE_T,#a39c34,Draft", + "STAR,#296629,Finished", ] # Import Error diff --git a/tests/test_gui/test_gui_docviewerpanel.py b/tests/test_gui/test_gui_docviewerpanel.py index b0d02493..f51d9a81 100644 --- a/tests/test_gui/test_gui_docviewerpanel.py +++ b/tests/test_gui/test_gui_docviewerpanel.py @@ -224,7 +224,7 @@ def testGuiViewerPanel_Tags(qtbot, monkeypatch, caplog, nwGUI, projPath, mockRnd # Update Labels assert charTab.topLevelItem(0).text(charTab.C_IMPORT) == "New" - SHARED.project.data.itemImport.add(C.iNew, "Stuff", (100, 100, 100), "SQUARE", 0) + SHARED.project.data.itemImport.add(C.iNew, "Stuff", "#646464", "SQUARE", 0) viewPanel.updateStatusLabels("i") assert charTab.topLevelItem(0).text(charTab.C_IMPORT) == "Stuff" From 9384b8a6b9b3c1108de62c83962464a71f3aab6f Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Fri, 13 Jun 2025 21:37:31 +0200 Subject: [PATCH 6/6] Update test coverage --- novelwriter/core/status.py | 5 +---- tests/conftest.py | 16 +++++++++++++- tests/test_core/test_core_status.py | 25 +++++++++++++++++++--- tests/test_dialogs/test_dlg_preferences.py | 6 +++--- tests/test_gui/test_gui_guimain.py | 3 +-- 5 files changed, 42 insertions(+), 13 deletions(-) diff --git a/novelwriter/core/status.py b/novelwriter/core/status.py index bcee828c..c3e58b76 100644 --- a/novelwriter/core/status.py +++ b/novelwriter/core/status.py @@ -192,10 +192,7 @@ class NWStatus: def refreshIcons(self) -> None: """Refresh all icons.""" for entry in self._store.values(): - if entry.theme != CUSTOM_COL: - print("<", entry.color.name(QColor.NameFormat.HexRgb)) - entry.color = SHARED.theme.parseColor(entry.theme) - print(">", entry.color.name(QColor.NameFormat.HexRgb)) + entry.color = SHARED.theme.parseColor(entry.theme) entry.icon = NWStatus.createIcon(self._height, entry.color, entry.shape) return diff --git a/tests/conftest.py b/tests/conftest.py index bd66447b..4017bb9f 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -34,6 +34,8 @@ from PyQt6.QtWidgets import QMessageBox sys.path.insert(1, str(Path(__file__).parent.parent.absolute())) from novelwriter import CONFIG, SHARED +from novelwriter.config import DEF_GUI_DARK, DEF_GUI_LIGHT +from novelwriter.enum import nwTheme from tests.mocked import MockGuiMain from tests.tools import cleanProject @@ -60,6 +62,10 @@ def resetConfigVars(): CONFIG._dLocale = QLocale("en_GB") CONFIG._manuals = {"manual": _TMP_ROOT / "manual.pdf"} CONFIG.guiLocale = "en_GB" + CONFIG.darkTheme = DEF_GUI_DARK + CONFIG.lightTheme = DEF_GUI_LIGHT + CONFIG.themeMode = nwTheme.LIGHT + CONFIG.emphLabels = True # Ensures better coverage, off by default return @@ -157,13 +163,21 @@ def mockGUI(qtbot, monkeypatch): monkeypatch.setattr(QMessageBox, "result", lambda *a: QMessageBox.StandardButton.Yes) gui = MockGuiMain() theme = GuiTheme() - theme.loadTheme() monkeypatch.setattr(SHARED, "_gui", gui) monkeypatch.setattr(SHARED, "_theme", theme) return gui +@pytest.fixture(scope="function") +def mockGUIwithTheme(mockGUI): + """Create a mock instance of novelWriter's main GUI class with the + theme instance initialised. + """ + SHARED.theme.initThemes() + return mockGUI + + @pytest.fixture(scope="function") def nwGUI(qtbot, monkeypatch, functionFixture): """Create an instance of the novelWriter GUI.""" diff --git a/tests/test_core/test_core_status.py b/tests/test_core/test_core_status.py index 3b5e80b8..1ea0c09e 100644 --- a/tests/test_core/test_core_status.py +++ b/tests/test_core/test_core_status.py @@ -324,13 +324,32 @@ def testCoreStatus_Entries(mockGUI, mockRnd): @pytest.mark.core -def testCoreStatus_Pack(mockGUI, mockRnd): +def testCoreStatus_RefreshIcons(mockGUIwithTheme, mockRnd): + """Test refreshing the icons of the NWStatus class.""" + nStatus = NWStatus(NWStatus.STATUS) + nStatus.add(None, "New", "default", "SQUARE", 0) + nStatus.add(None, "Note", "red", "CIRCLE", 0) + nStatus.add(None, "Draft", "yellow", "SQUARE", 0) + nStatus.add(None, "Finished", "green", "SQUARE", 0) + + beforeIcons = [nStatus[statusKeys[i]].icon for i in range(4)] + + # Refreshing the icons should generate new ones + nStatus.refreshIcons() + afterIcons = [nStatus[statusKeys[i]].icon for i in range(4)] + + for before, after in zip(beforeIcons, afterIcons, strict=False): + assert before is not after + + +@pytest.mark.core +def testCoreStatus_Pack(mockGUIwithTheme, mockRnd): """Test data packing of the NWStatus class.""" nStatus = NWStatus(NWStatus.STATUS) nStatus.add(None, "New", "#646464", "SQUARE", 0) nStatus.add(None, "Note", "#c83200", "CIRCLE", 0) nStatus.add(None, "Draft", "#c89600", "SQUARE", 0) - nStatus.add(None, "Finished", "#32c800", "SQUARE", 0) + nStatus.add(None, "Finished", "default", "SQUARE", 0) countTo = [3, 5, 7, 9] for i, n in enumerate(countTo): @@ -360,7 +379,7 @@ def testCoreStatus_Pack(mockGUI, mockRnd): ("Finished", { "key": statusKeys[3], "count": "9", - "color": "#32c800", + "color": "default", "shape": "SQUARE", }), ] diff --git a/tests/test_dialogs/test_dlg_preferences.py b/tests/test_dialogs/test_dlg_preferences.py index a017585f..66d6183e 100644 --- a/tests/test_dialogs/test_dlg_preferences.py +++ b/tests/test_dialogs/test_dlg_preferences.py @@ -196,11 +196,11 @@ def testDlgPreferences_Settings(qtbot, monkeypatch, nwGUI, fncPath, tstPaths): # Project View prefs.iconColTree.setCurrentData("faded", "default") prefs.iconColDocs.setChecked(True) - prefs.emphLabels.setChecked(True) + prefs.emphLabels.setChecked(False) assert CONFIG.iconColTree == DEF_TREECOL assert CONFIG.iconColDocs is False - assert CONFIG.emphLabels is False + assert CONFIG.emphLabels is True # Behaviour prefs.autoSaveDoc.stepUp() @@ -362,7 +362,7 @@ def testDlgPreferences_Settings(qtbot, monkeypatch, nwGUI, fncPath, tstPaths): # Project View assert CONFIG.iconColTree == "faded" assert CONFIG.iconColDocs is True - assert CONFIG.emphLabels is True + assert CONFIG.emphLabels is False # Behaviour assert CONFIG.autoSaveDoc == 31 diff --git a/tests/test_gui/test_gui_guimain.py b/tests/test_gui/test_gui_guimain.py index 5010094d..3940cbba 100644 --- a/tests/test_gui/test_gui_guimain.py +++ b/tests/test_gui/test_gui_guimain.py @@ -267,8 +267,7 @@ def testGuiMain_Editing(qtbot, monkeypatch, nwGUI, projPath, tstPaths, mockRnd): # Change some settings CONFIG.hideHScroll = True CONFIG.hideVScroll = True - CONFIG.autoScrollPos = 80 - CONFIG.autoScroll = True + CONFIG.autoScroll = False # Add a Character File nwGUI._changeView(nwView.PROJECT)