diff --git a/novelwriter/constants.py b/novelwriter/constants.py index 3c66f087..6ed6080f 100644 --- a/novelwriter/constants.py +++ b/novelwriter/constants.py @@ -270,16 +270,17 @@ class nwLabels: } STATUS_SHAPES = { nwStatusShape.SQUARE: QT_TRANSLATE_NOOP("Constant", "Square"), - nwStatusShape.CIRCLE_Q: QT_TRANSLATE_NOOP("Constant", "Circle, 1/4"), - nwStatusShape.CIRCLE_H: QT_TRANSLATE_NOOP("Constant", "Circle, Half"), - nwStatusShape.CIRCLE_T: QT_TRANSLATE_NOOP("Constant", "Circle, 3/4"), - nwStatusShape.CIRCLE: QT_TRANSLATE_NOOP("Constant", "Circle, Full"), nwStatusShape.TRIANGLE: QT_TRANSLATE_NOOP("Constant", "Triangle"), nwStatusShape.NABLA: QT_TRANSLATE_NOOP("Constant", "Nabla"), nwStatusShape.DIAMOND: QT_TRANSLATE_NOOP("Constant", "Diamond"), nwStatusShape.PENTAGON: QT_TRANSLATE_NOOP("Constant", "Pentagon"), + nwStatusShape.HEXAGON: QT_TRANSLATE_NOOP("Constant", "Hexagon"), nwStatusShape.STAR: QT_TRANSLATE_NOOP("Constant", "Star"), nwStatusShape.PACMAN: QT_TRANSLATE_NOOP("Constant", "Pacman"), + nwStatusShape.CIRCLE_Q: QT_TRANSLATE_NOOP("Constant", "Circle, 1/4"), + nwStatusShape.CIRCLE_H: QT_TRANSLATE_NOOP("Constant", "Circle, Half"), + nwStatusShape.CIRCLE_T: QT_TRANSLATE_NOOP("Constant", "Circle, 3/4"), + nwStatusShape.CIRCLE: QT_TRANSLATE_NOOP("Constant", "Circle, Full"), nwStatusShape.BARS_1: QT_TRANSLATE_NOOP("Constant", "1 Bar"), nwStatusShape.BARS_2: QT_TRANSLATE_NOOP("Constant", "2 Bars"), nwStatusShape.BARS_3: QT_TRANSLATE_NOOP("Constant", "3 Bars"), diff --git a/novelwriter/core/status.py b/novelwriter/core/status.py index c2899ad7..6b85ccec 100644 --- a/novelwriter/core/status.py +++ b/novelwriter/core/status.py @@ -29,8 +29,7 @@ import logging import random from collections.abc import Iterable -from math import cos, pi, sin -from typing import TYPE_CHECKING, Literal +from typing import TYPE_CHECKING from PyQt5.QtCore import QPointF, Qt from PyQt5.QtGui import QIcon, QPainter, QPainterPath, QPixmap, QColor, QPolygonF @@ -71,24 +70,16 @@ NO_ENTRY = StatusEntry("", QColor(0, 0, 0), nwStatusShape.SQUARE, QIcon(), 0) class NWStatus: - STATUS = 1 - IMPORT = 2 + STATUS = "s" + IMPORT = "i" - def __init__(self, kind: Literal[1, 2]) -> None: + __slots__ = ("_store", "_default", "_prefix", "_height") - self._type = kind + def __init__(self, prefix: str) -> None: self._store: dict[str, StatusEntry] = {} self._default = None - - self._iPx = SHARED.theme.baseIconHeight - - if self._type == self.STATUS: - self._prefix = "s" - elif self._type == self.IMPORT: - self._prefix = "i" - else: - raise Exception("This is a bug!") - + self._prefix = prefix[:1] + self._height = SHARED.theme.baseIconHeight return def __len__(self) -> int: @@ -123,7 +114,7 @@ class NWStatus: key = self._checkKey(key) name = simplified(name) - icon = self.createIcon(self._iPx, qColor, iShape) + icon = self.createIcon(self._height, qColor, iShape) self._store[key] = StatusEntry(name, qColor, iShape, icon, count) if self._default is None: @@ -181,14 +172,14 @@ class NWStatus: yield from self._store.items() @staticmethod - def createIcon(height: int, colour: QColor, shape: nwStatusShape) -> QIcon: + def createIcon(height: int, color: QColor, shape: nwStatusShape) -> QIcon: """Generate an icon for a status label.""" pixmap = QPixmap(48, 48) pixmap.fill(QtTransparent) painter = QPainter(pixmap) painter.setRenderHint(QtPaintAnitAlias) - painter.fillPath(_SHAPES.getShape(shape), colour) + painter.fillPath(_SHAPES.getShape(shape), color) painter.end() return QIcon(pixmap.scaled( @@ -244,14 +235,56 @@ class _ShapeCache: if shape in self._cache: return self._cache[shape] - def polar(r: float, a: float, x: float, y: float) -> QPointF: - # Converts polar coordinates to cartesian - # print(round(x+r*sin(pi*a/180), 2), round(y-r*cos(pi*a/180), 2)) - return QPointF(x+r*sin(pi*a/180), y-r*cos(pi*a/180)) - path = QPainterPath() if shape == nwStatusShape.SQUARE: path.addRoundedRect(2.0, 2.0, 44.0, 44.0, 4.0, 4.0) + elif shape == nwStatusShape.TRIANGLE: + path.addPolygon(QPolygonF([ + QPointF(24.00, 3.00), + QPointF(43.92, 37.50), + QPointF(4.08, 37.50), + ])) + elif shape == nwStatusShape.NABLA: + path.addPolygon(QPolygonF([ + QPointF(24.00, 48.00), + QPointF(4.08, 14.50), + QPointF(43.92, 14.50), + ])) + elif shape == nwStatusShape.DIAMOND: + path.addPolygon(QPolygonF([ + QPointF(24.00, 2.00), + QPointF(44.00, 24.00), + QPointF(24.00, 46.00), + QPointF(4.00, 24.00), + ])) + elif shape == nwStatusShape.PENTAGON: + path.addPolygon(QPolygonF([ + QPointF(24.00, 1.50), + QPointF(45.87, 17.39), + QPointF(37.52, 43.11), + QPointF(10.48, 43.11), + QPointF(2.13, 17.39), + ])) + elif shape == nwStatusShape.HEXAGON: + path.addPolygon(QPolygonF([ + QPointF(24.00, 1.50), + QPointF(43.92, 13.00), + QPointF(43.92, 36.00), + QPointF(24.00, 47.50), + QPointF(4.08, 36.00), + QPointF(4.08, 13.00), + ])) + elif shape == nwStatusShape.STAR: + path.addPolygon(QPolygonF([ + QPointF(24.00, 0.50), QPointF(31.05, 14.79), + QPointF(46.83, 17.08), QPointF(35.41, 28.21), + QPointF(38.11, 43.92), QPointF(24.00, 36.50), + QPointF(9.89, 43.92), QPointF(12.59, 28.21), + QPointF(1.17, 17.08), QPointF(15.37, 16.16), + ])) + elif shape == nwStatusShape.PACMAN: + path.moveTo(24.0, 24.0) + path.arcTo(2.0, 2.0, 44.0, 44.0, 40.0, 280.0) elif shape == nwStatusShape.CIRCLE_Q: path.moveTo(24.0, 24.0) path.arcTo(2.0, 2.0, 44.0, 44.0, 0.0, 90.0) @@ -263,45 +296,6 @@ class _ShapeCache: path.arcTo(2.0, 2.0, 44.0, 44.0, -180.0, 270.0) elif shape == nwStatusShape.CIRCLE: path.addEllipse(2.0, 2.0, 44.0, 44.0) - elif shape == nwStatusShape.TRIANGLE: - path.addPolygon(QPolygonF([ - polar(23.0, 0.0, 24.0, 26.0), - polar(23.0, 120.0, 24.0, 26.0), - polar(23.0, 240.0, 24.0, 26.0), - ])) - elif shape == nwStatusShape.NABLA: - path.addPolygon(QPolygonF([ - polar(23.0, 180.0, 24.0, 26.0), - polar(23.0, 300.0, 24.0, 26.0), - polar(23.0, 60.0, 24.0, 26.0), - ])) - elif shape == nwStatusShape.DIAMOND: - path.addPolygon(QPolygonF([ - polar(22.0, 0.0, 24.0, 24.0), - polar(20.0, 90.0, 24.0, 24.0), - polar(22.0, 180.0, 24.0, 24.0), - polar(20.0, 270.0, 24.0, 24.0), - ])) - elif shape == nwStatusShape.PENTAGON: - path.addPolygon(QPolygonF([ - polar(23.0, 0.0, 24.0, 24.5), - polar(23.0, 72.0, 24.0, 24.5), - polar(23.0, 144.0, 24.0, 24.5), - polar(23.0, 216.0, 24.0, 24.5), - polar(23.0, 288.0, 24.0, 24.5), - ])) - elif shape == nwStatusShape.STAR: - path.addPolygon(QPolygonF([ - polar(24.0, 0.0, 24.0, 24.5), polar(12.0, 36.0, 24.0, 24.5), - polar(24.0, 72.0, 24.0, 24.5), polar(12.0, 108.0, 24.0, 24.5), - polar(24.0, 144.0, 24.0, 24.5), polar(12.0, 180.0, 24.0, 24.5), - polar(24.0, 216.0, 24.0, 24.5), polar(12.0, 252.0, 24.0, 24.5), - polar(24.0, 288.0, 24.0, 24.5), polar(12.0, 314.0, 24.0, 24.5), - ])) - path.setFillRule(Qt.FillRule.WindingFill) - elif shape == nwStatusShape.PACMAN: - path.moveTo(24.0, 24.0) - path.arcTo(2.0, 2.0, 44.0, 44.0, 40.0, 280.0) elif shape == nwStatusShape.BARS_1: path.addRoundedRect(2.0, 2.0, 8.0, 44.0, 4.0, 4.0) elif shape == nwStatusShape.BARS_2: diff --git a/novelwriter/dialogs/projectsettings.py b/novelwriter/dialogs/projectsettings.py index c6b5281a..67f05968 100644 --- a/novelwriter/dialogs/projectsettings.py +++ b/novelwriter/dialogs/projectsettings.py @@ -334,6 +334,8 @@ class _StatusPage(NFixedPage): iSz = SHARED.theme.baseIconSize bSz = SHARED.theme.buttonIconSize + iColor = self.palette().text().color() + # Labels self.trCountNone = self.tr("Not in use") self.trCountOne = self.tr("Used once") @@ -385,13 +387,15 @@ class _StatusPage(NFixedPage): self.shapeList = NComboBox(self) self.shapeList.setEnabled(False) self.shapeList.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.MinimumExpanding) + self.shapeList.setMaxVisibleItems(5) for shape, label in nwLabels.STATUS_SHAPES.items(): - self.shapeList.addItem(label, shape) + icon = NWStatus.createIcon(self._iPx, iColor, shape) + self.shapeList.addItem(icon, label, shape) - self.saveButton = QPushButton(self.tr("Save"), self) - self.saveButton.setEnabled(False) - self.saveButton.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.MinimumExpanding) - self.saveButton.clicked.connect(self._saveItem) + self.applyButton = QPushButton(self.tr("Apply"), self) + self.applyButton.setEnabled(False) + self.applyButton.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.MinimumExpanding) + self.applyButton.clicked.connect(self._saveItem) # Assemble self.listControls = QVBoxLayout() @@ -405,7 +409,7 @@ class _StatusPage(NFixedPage): self.editBox.addWidget(self.editName, 1) self.editBox.addWidget(self.colButton, 0) self.editBox.addWidget(self.shapeList, 0) - self.editBox.addWidget(self.saveButton, 0) + self.editBox.addWidget(self.applyButton, 0) self.mainBox = QVBoxLayout() self.mainBox.addWidget(self.listBox, 1) @@ -524,7 +528,7 @@ class _StatusPage(NFixedPage): self.editName.setEnabled(True) self.colButton.setEnabled(True) self.shapeList.setEnabled(True) - self.saveButton.setEnabled(True) + self.applyButton.setEnabled(True) else: self._color = QColor(100, 100, 100) @@ -535,7 +539,7 @@ class _StatusPage(NFixedPage): self.editName.setEnabled(False) self.colButton.setEnabled(False) self.shapeList.setEnabled(False) - self.saveButton.setEnabled(False) + self.applyButton.setEnabled(False) return ## @@ -645,8 +649,8 @@ class _ReplacePage(NFixedPage): self.editValue.setEnabled(False) self.editValue.setMaxLength(80) - self.saveButton = QPushButton(self.tr("Save"), self) - self.saveButton.clicked.connect(self._saveEntry) + self.applyButton = QPushButton(self.tr("Apply"), self) + self.applyButton.clicked.connect(self._saveEntry) # Assemble self.listControls = QVBoxLayout() @@ -657,7 +661,7 @@ class _ReplacePage(NFixedPage): self.editBox = QHBoxLayout() self.editBox.addWidget(self.editKey, 4) self.editBox.addWidget(self.editValue, 5) - self.editBox.addWidget(self.saveButton, 0) + self.editBox.addWidget(self.applyButton, 0) self.mainBox = QVBoxLayout() self.mainBox.addWidget(self.listBox) diff --git a/novelwriter/enum.py b/novelwriter/enum.py index e5ee3dd5..96d97f15 100644 --- a/novelwriter/enum.py +++ b/novelwriter/enum.py @@ -210,23 +210,24 @@ class nwBuildFmt(Enum): class nwStatusShape(Enum): SQUARE = 0 - CIRCLE_Q = 1 - CIRCLE_H = 2 - CIRCLE_T = 3 - CIRCLE = 4 - TRIANGLE = 5 - NABLA = 6 - DIAMOND = 7 - PENTAGON = 8 - STAR = 9 - PACMAN = 10 - BARS_1 = 11 - BARS_2 = 12 - BARS_3 = 13 - BARS_4 = 14 - BLOCK_1 = 15 - BLOCK_2 = 16 - BLOCK_3 = 17 - BLOCK_4 = 18 + TRIANGLE = 1 + NABLA = 2 + DIAMOND = 3 + PENTAGON = 4 + HEXAGON = 5 + STAR = 6 + PACMAN = 7 + CIRCLE_Q = 8 + CIRCLE_H = 9 + CIRCLE_T = 10 + CIRCLE = 11 + BARS_1 = 12 + BARS_2 = 13 + BARS_3 = 14 + BARS_4 = 15 + BLOCK_1 = 16 + BLOCK_2 = 17 + BLOCK_3 = 18 + BLOCK_4 = 19 # END Enum nwStatusShape diff --git a/sample/nwProject.nwx b/sample/nwProject.nwx index 3f64c4c7..716b21e7 100644 --- a/sample/nwProject.nwx +++ b/sample/nwProject.nwx @@ -1,6 +1,6 @@ - - + + Sample Project Jane Smith @@ -22,17 +22,18 @@ New Notes - Started - 1st Draft - 2nd Draft - 3rd Draft + Started + 1st Draft + 2nd Draft + 3rd Draft Finished - None - Minor - Major - Main + None + Background + Minor + Major + Main @@ -57,7 +58,7 @@ Chapter One - + Making a Scene @@ -78,7 +79,7 @@ - We Found John! + We Found John! @@ -90,7 +91,7 @@ - Chapter One + Chapter One @@ -102,11 +103,11 @@ - John Smith + John Smith - Jane Smith + Jane Smith @@ -114,15 +115,15 @@ - Earth + Earth - Space + Space - Mars + Mars diff --git a/tests/test_core/test_core_status.py b/tests/test_core/test_core_status.py index 4ca9e29b..7c6b2fc4 100644 --- a/tests/test_core/test_core_status.py +++ b/tests/test_core/test_core_status.py @@ -68,9 +68,6 @@ def testCoreStatus_Internal(mockGUI, mockRnd): nStatus = NWStatus(NWStatus.STATUS) nImport = NWStatus(NWStatus.IMPORT) - with pytest.raises(Exception): - NWStatus(999) # type: ignore - # Generate Key # ============ @@ -383,6 +380,7 @@ def testCoreStatus_ShapeCache(): nabla = shapes.getShape(nwStatusShape.NABLA) diamond = shapes.getShape(nwStatusShape.DIAMOND) pentagon = shapes.getShape(nwStatusShape.PENTAGON) + hexagon = shapes.getShape(nwStatusShape.HEXAGON) star = shapes.getShape(nwStatusShape.STAR) pacman = shapes.getShape(nwStatusShape.PACMAN) bars1 = shapes.getShape(nwStatusShape.BARS_1) @@ -404,6 +402,7 @@ def testCoreStatus_ShapeCache(): assert shapes.getShape(nwStatusShape.NABLA) is nabla assert shapes.getShape(nwStatusShape.DIAMOND) is diamond assert shapes.getShape(nwStatusShape.PENTAGON) is pentagon + assert shapes.getShape(nwStatusShape.HEXAGON) is hexagon assert shapes.getShape(nwStatusShape.STAR) is star assert shapes.getShape(nwStatusShape.PACMAN) is pacman assert shapes.getShape(nwStatusShape.BARS_1) is bars1 diff --git a/tests/test_dialogs/test_dlg_projectsettings.py b/tests/test_dialogs/test_dlg_projectsettings.py index 1ccdfab6..921aafd3 100644 --- a/tests/test_dialogs/test_dlg_projectsettings.py +++ b/tests/test_dialogs/test_dlg_projectsettings.py @@ -205,7 +205,7 @@ def testDlgProjSettings_StatusImport(qtbot, monkeypatch, nwGUI, projPath, mockRn status.editName.setText("Final") status.shapeList.setCurrentData(nwStatusShape.CIRCLE, nwStatusShape.SQUARE) status.colButton.click() - status.saveButton.click() + status.applyButton.click() assert status.listBox.topLevelItemCount() == 4 assert status.changed is True @@ -281,7 +281,7 @@ def testDlgProjSettings_StatusImport(qtbot, monkeypatch, nwGUI, projPath, mockRn importance.editName.setText("Final") importance.shapeList.setCurrentData(nwStatusShape.TRIANGLE, nwStatusShape.SQUARE) qtbot.mouseClick(importance.colButton, QtMouseLeft) - qtbot.mouseClick(importance.saveButton, QtMouseLeft) + qtbot.mouseClick(importance.applyButton, QtMouseLeft) assert importance.listBox.topLevelItemCount() == 4 assert importance.changed is True @@ -375,7 +375,7 @@ def testDlgProjSettings_Replace(qtbot, monkeypatch, nwGUI, projPath, mockRnd): replace.editValue.setText("") for c in "With This Stuff ": qtbot.keyClick(replace.editValue, c, delay=KEY_DELAY) - qtbot.mouseClick(replace.saveButton, QtMouseLeft) + qtbot.mouseClick(replace.applyButton, QtMouseLeft) assert replace.listBox.topLevelItem(2).text(0) == "" # type: ignore assert replace.listBox.topLevelItem(2).text(1) == "With This Stuff " # type: ignore