From af44646a779b996434c30ba7cb5436c2f0fd55bf Mon Sep 17 00:00:00 2001
From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com>
Date: Tue, 9 Apr 2024 21:42:59 +0200
Subject: [PATCH 01/20] Add a shape generator to the status class
---
novelwriter/core/status.py | 94 ++++++++++++++++++++++++++++++++------
novelwriter/enum.py | 13 ++++++
2 files changed, 93 insertions(+), 14 deletions(-)
diff --git a/novelwriter/core/status.py b/novelwriter/core/status.py
index 554bdeed..ec45a6c3 100644
--- a/novelwriter/core/status.py
+++ b/novelwriter/core/status.py
@@ -24,17 +24,19 @@ along with this program. If not, see .
"""
from __future__ import annotations
-import random
import logging
+import random
-from typing import TYPE_CHECKING, Literal
from collections.abc import ItemsView, Iterable, Iterator, KeysView, ValuesView
+from math import cos, pi, sin
+from typing import TYPE_CHECKING, Literal
-from PyQt5.QtGui import QIcon, QPainter, QPainterPath, QPixmap, QColor
-from PyQt5.QtCore import QRectF
+from PyQt5.QtCore import QPointF, Qt
+from PyQt5.QtGui import QIcon, QPainter, QPainterPath, QPixmap, QColor, QPolygonF
from novelwriter import CONFIG
from novelwriter.common import minmax, simplified
+from novelwriter.enum import nwStatusShape
from novelwriter.types import QtPaintAnitAlias, QtTransparent
if TYPE_CHECKING: # pragma: no cover
@@ -55,13 +57,6 @@ class NWStatus:
self._default = None
self._iPX = CONFIG.pxInt(24)
-
- pA = CONFIG.pxInt(2)
- pB = CONFIG.pxInt(20)
- pR = float(CONFIG.pxInt(4))
- self._iconPath = QPainterPath()
- self._iconPath.addRoundedRect(QRectF(pA, pA, pB, pB), pR, pR)
-
self._defaultIcon = self._createIcon(100, 100, 100)
if self._type == self.STATUS:
@@ -248,15 +243,21 @@ class NWStatus:
def _createIcon(self, red: int, green: int, blue: int) -> QIcon:
"""Generate an icon for a status label."""
- pixmap = QPixmap(self._iPX, self._iPX)
+ pixmap = QPixmap(48, 48)
pixmap.fill(QtTransparent)
+ path = _SHAPES.getShape(nwStatusShape.DIAMOND)
+
painter = QPainter(pixmap)
painter.setRenderHint(QtPaintAnitAlias)
- painter.fillPath(self._iconPath, QColor(red, green, blue))
+ painter.fillPath(path, QColor(red, green, blue))
painter.end()
- return QIcon(pixmap)
+ return QIcon(pixmap.scaled(
+ self._iPX, self._iPX,
+ Qt.AspectRatioMode.IgnoreAspectRatio,
+ Qt.TransformationMode.SmoothTransformation
+ ))
##
# Iterator Bits
@@ -281,3 +282,68 @@ class NWStatus:
return self._store.values()
# END Class NWStatus
+
+
+class _ShapeCache:
+
+ def __init__(self) -> None:
+ self._cache: dict[nwStatusShape, QPainterPath] = {}
+ return
+
+ def getShape(self, shape: nwStatusShape) -> QPainterPath:
+ """Return a painter shape for an icon."""
+ if shape in self._cache:
+ return self._cache[shape]
+
+ def circ(r: float, a: float, x: float, y: float) -> QPointF:
+ print(round(x+r*sin(pi*a/180), 2), round(y-r*cos(pi*a/180), 2))
+ return QPointF(round(x+r*sin(pi*a/180), 2), round(y-r*cos(pi*a/180), 2))
+
+ path = QPainterPath()
+ if shape == nwStatusShape.SQUARE:
+ path.addRoundedRect(2.0, 2.0, 44.0, 44.0, 4.0, 4.0)
+ elif shape == nwStatusShape.CIRCLE:
+ path.addEllipse(2.0, 2.0, 44.0, 44.0)
+ elif shape == nwStatusShape.TRIANGLE:
+ path.addPolygon(QPolygonF([
+ circ(23.0, 0.0, 24.0, 26.0),
+ circ(23.0, 120.0, 24.0, 26.0),
+ circ(23.0, 240.0, 24.0, 26.0),
+ ]))
+ elif shape == nwStatusShape.DIAMOND:
+ path.addPolygon(QPolygonF([
+ circ(22.0, 0.0, 24.0, 24.0),
+ circ(20.0, 90.0, 24.0, 24.0),
+ circ(22.0, 180.0, 24.0, 24.0),
+ circ(20.0, 270.0, 24.0, 24.0),
+ ]))
+ elif shape == nwStatusShape.PENTAGON:
+ path.addPolygon(QPolygonF([
+ circ(23.0, 0.0, 24.0, 24.5),
+ circ(23.0, 72.0, 24.0, 24.5),
+ circ(23.0, 144.0, 24.0, 24.5),
+ circ(23.0, 216.0, 24.0, 24.5),
+ circ(23.0, 288.0, 24.0, 24.5),
+ ]))
+ elif shape == nwStatusShape.STAR:
+ path.addPolygon(QPolygonF([
+ circ(24.0, 0.0, 24.0, 24.5),
+ circ(24.0, 144.0, 24.0, 24.5),
+ circ(24.0, 288.0, 24.0, 24.5),
+ circ(24.0, 72.0, 24.0, 24.5),
+ circ(24.0, 216.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)
+
+ self._cache[shape] = path
+
+ return path
+
+# END Class _ShapeCache
+
+
+# Create Singleton
+_SHAPES = _ShapeCache()
diff --git a/novelwriter/enum.py b/novelwriter/enum.py
index d905e587..c0f88ba7 100644
--- a/novelwriter/enum.py
+++ b/novelwriter/enum.py
@@ -205,3 +205,16 @@ class nwBuildFmt(Enum):
J_NWD = 7
# END Enum nwBuildFormat
+
+
+class nwStatusShape(Enum):
+
+ SQUARE = 0
+ CIRCLE = 1
+ TRIANGLE = 2
+ DIAMOND = 3
+ PENTAGON = 4
+ STAR = 5
+ PACMAN = 6
+
+# END Enum nwStatusShape
From ff3cae9db4ce40bdcc148a176f1ea01a2c1f2da7 Mon Sep 17 00:00:00 2001
From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com>
Date: Tue, 9 Apr 2024 22:39:46 +0200
Subject: [PATCH 02/20] Add status icon shape to project XML
---
novelwriter/core/project.py | 22 ++++++++++++----------
novelwriter/core/projectxml.py | 7 +++++--
novelwriter/core/status.py | 32 ++++++++++++++------------------
3 files changed, 31 insertions(+), 30 deletions(-)
diff --git a/novelwriter/core/project.py b/novelwriter/core/project.py
index 4a399c24..3ce2160a 100644
--- a/novelwriter/core/project.py
+++ b/novelwriter/core/project.py
@@ -36,7 +36,7 @@ from collections.abc import Iterable
from PyQt5.QtCore import QCoreApplication
from novelwriter import CONFIG, SHARED, __version__, __hexversion__
-from novelwriter.enum import nwItemType, nwItemClass, nwItemLayout
+from novelwriter.enum import nwItemType, nwItemClass, nwItemLayout, nwStatusShape
from novelwriter.error import logException
from novelwriter.constants import trConst, nwLabels
from novelwriter.core.tree import NWTree
@@ -461,14 +461,15 @@ class NWProject:
def setDefaultStatusImport(self) -> None:
"""Set the default status and importance values."""
- self._data.itemStatus.write(None, self.tr("New"), (100, 100, 100))
- self._data.itemStatus.write(None, self.tr("Note"), (200, 50, 0))
- self._data.itemStatus.write(None, self.tr("Draft"), (200, 150, 0))
- self._data.itemStatus.write(None, self.tr("Finished"), (50, 200, 0))
- self._data.itemImport.write(None, self.tr("New"), (100, 100, 100))
- self._data.itemImport.write(None, self.tr("Minor"), (200, 50, 0))
- self._data.itemImport.write(None, self.tr("Major"), (200, 150, 0))
- self._data.itemImport.write(None, self.tr("Main"), (50, 200, 0))
+ square = nwStatusShape.SQUARE
+ self._data.itemStatus.write(None, self.tr("New"), (100, 100, 100), square)
+ self._data.itemStatus.write(None, self.tr("Note"), (200, 50, 0), square)
+ self._data.itemStatus.write(None, self.tr("Draft"), (200, 150, 0), square)
+ self._data.itemStatus.write(None, self.tr("Finished"), (50, 200, 0), square)
+ self._data.itemImport.write(None, self.tr("New"), (100, 100, 100), square)
+ self._data.itemImport.write(None, self.tr("Minor"), (200, 50, 0), square)
+ self._data.itemImport.write(None, self.tr("Major"), (200, 150, 0), square)
+ self._data.itemImport.write(None, self.tr("Main"), (50, 200, 0), square)
return
def setProjectLang(self, language: str | None) -> None:
@@ -596,8 +597,9 @@ class NWProject:
key = entry.get("key", None)
name = entry.get("name", "")
cols = entry.get("cols", (100, 100, 100))
+ shape = entry.get("shape", nwStatusShape.SQUARE)
if name:
- order.append(target.write(key, name, cols))
+ order.append(target.write(key, name, cols, shape))
for key in delete:
target.remove(key)
diff --git a/novelwriter/core/projectxml.py b/novelwriter/core/projectxml.py
index 52db58f6..bb2ade31 100644
--- a/novelwriter/core/projectxml.py
+++ b/novelwriter/core/projectxml.py
@@ -46,7 +46,7 @@ if TYPE_CHECKING: # pragma: no cover
logger = logging.getLogger(__name__)
FILE_VERSION = "1.5" # The current project file format version
-FILE_REVISION = "3" # The current project file format revision
+FILE_REVISION = "4" # The current project file format revision
HEX_VERSION = 0x0105
NUM_VERSION = {
@@ -109,6 +109,8 @@ class ProjectXMLReader:
Rev 2: Drops the title node from project and adds the TEMPLATE
class for items. 2.3 Beta 1.
Rev 3: Added TEMPLATE class. 2.3.
+ Rev 4: Added shape attribute to status and importance entry
+ nodes. 2.5.
"""
def __init__(self, path: str | Path) -> None:
@@ -436,7 +438,8 @@ class ProjectXMLReader:
green = checkInt(xEntry.attrib.get("green", 0), 0)
blue = checkInt(xEntry.attrib.get("blue", 0), 0)
count = checkInt(xEntry.attrib.get("count", 0), 0)
- sObject.write(key, xEntry.text or "", (red, green, blue), count)
+ shape = xEntry.attrib.get("shape", "")
+ sObject.write(key, xEntry.text or "", (red, green, blue), shape, count)
return
def _parseDictKeyText(self, xItem: ET.Element) -> dict:
diff --git a/novelwriter/core/status.py b/novelwriter/core/status.py
index ec45a6c3..0470bac0 100644
--- a/novelwriter/core/status.py
+++ b/novelwriter/core/status.py
@@ -57,7 +57,7 @@ class NWStatus:
self._default = None
self._iPX = CONFIG.pxInt(24)
- self._defaultIcon = self._createIcon(100, 100, 100)
+ self._defaultIcon = self._createIcon(100, 100, 100, nwStatusShape.SQUARE)
if self._type == self.STATUS:
self._prefix = "s"
@@ -68,7 +68,8 @@ class NWStatus:
return
- def write(self, key: str | None, name: str, col: tuple, count: int | None = None) -> str:
+ def write(self, key: str | None, name: str, col: tuple, shape: nwStatusShape | str,
+ count: int | None = None) -> str:
"""Add or update a status entry. If the key is invalid, a new
key is generated.
"""
@@ -83,14 +84,21 @@ class NWStatus:
cG = minmax(col[1], 0, 255)
cB = minmax(col[2], 0, 255)
name = simplified(name)
+ if not isinstance(shape, nwStatusShape):
+ if shape in nwStatusShape.__members__:
+ shape = nwStatusShape[shape]
+ else:
+ shape = nwStatusShape.SQUARE
+
if count is None:
count = self._store.get(key, {}).get("count", 0)
self._store[key] = {
"name": name,
- "icon": self._createIcon(cR, cG, cB),
+ "icon": self._createIcon(cR, cG, cB, shape),
"cols": (cR, cG, cB),
"count": count,
+ "shape": shape,
}
if self._default is None:
@@ -198,20 +206,10 @@ class NWStatus:
"red": str(data["cols"][0]),
"green": str(data["cols"][1]),
"blue": str(data["cols"][2]),
+ "shape": data["shape"].name,
})
return
- def unpack(self, data: dict) -> None:
- """Unpack a data dictionary and set the class values."""
- self._store = {}
- self._default = None
- for key, entry in data.items():
- label = entry.get("label", "")
- colour = entry.get("colour", (100, 100, 100))
- count = entry.get("count", 0)
- self.write(key, label, colour, count)
- return
-
##
# Internal Functions
##
@@ -241,16 +239,14 @@ class NWStatus:
return False
return True
- def _createIcon(self, red: int, green: int, blue: int) -> QIcon:
+ def _createIcon(self, red: int, green: int, blue: int, shape: nwStatusShape) -> QIcon:
"""Generate an icon for a status label."""
pixmap = QPixmap(48, 48)
pixmap.fill(QtTransparent)
- path = _SHAPES.getShape(nwStatusShape.DIAMOND)
-
painter = QPainter(pixmap)
painter.setRenderHint(QtPaintAnitAlias)
- painter.fillPath(path, QColor(red, green, blue))
+ painter.fillPath(_SHAPES.getShape(shape), QColor(red, green, blue))
painter.end()
return QIcon(pixmap.scaled(
From 4003ececb2dd2b2cb0ef7b8c68ac0559ed1ea31c Mon Sep 17 00:00:00 2001
From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com>
Date: Tue, 9 Apr 2024 22:40:14 +0200
Subject: [PATCH 03/20] Update tests
---
tests/files/nwProject-1.5.nwx | 24 +++---
.../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 ++---
.../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 | 2 +-
tests/test_core/test_core_status.py | 80 +++++++------------
16 files changed, 167 insertions(+), 189 deletions(-)
diff --git a/tests/files/nwProject-1.5.nwx b/tests/files/nwProject-1.5.nwx
index 7823c057..35eab583 100644
--- a/tests/files/nwProject-1.5.nwx
+++ b/tests/files/nwProject-1.5.nwx
@@ -1,5 +1,5 @@
-
+
Sample Project
Jane Smith
@@ -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/coreProject_NewFileFolder_nwProject.nwx b/tests/reference/coreProject_NewFileFolder_nwProject.nwx
index 2422fc02..4aba7481 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 028da14c..4bfbb568 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 bab9a82d..5f6685a6 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 60c0fce3..01d3a0ef 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 86980406..ed43d5cd 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/guiEditor_Main_Final_nwProject.nwx b/tests/reference/guiEditor_Main_Final_nwProject.nwx
index c3ee941f..2c6603eb 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 aa9c3a74..f5119a55 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 a8f9e945..70885cfd 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 c216f88a..1d93bd02 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 b3cad791..d344f378 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 4bd25a9a..05a4dfa5 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 1ca92061..2a7e24d0 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 151d89da..784b86ac 100644
--- a/tests/test_core/test_core_item.py
+++ b/tests/test_core/test_core_item.py
@@ -553,8 +553,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.write(None, "New", (100, 100, 100))
- project.data.itemImport.write(None, "New", (100, 100, 100))
+ project.data.itemStatus.write(None, "New", (100, 100, 100), "SQUARE")
+ project.data.itemImport.write(None, "New", (100, 100, 100), "SQUARE")
# Invalid
item = NWItem(project, "0000000000000")
diff --git a/tests/test_core/test_core_projectxml.py b/tests/test_core/test_core_projectxml.py
index 534aaed2..3971e136 100644
--- a/tests/test_core/test_core_projectxml.py
+++ b/tests/test_core/test_core_projectxml.py
@@ -131,7 +131,7 @@ def testCoreProjectXML_ReadCurrent(monkeypatch, tstPaths, fncPath):
assert xmlReader.state == XMLReadState.PARSED_OK
assert xmlReader.xmlRoot == "novelWriterXML"
assert xmlReader.xmlVersion == 0x0105
- assert xmlReader.xmlRevision == 3
+ assert xmlReader.xmlRevision == 4
assert xmlReader.appVersion == "2.0-rc1"
assert xmlReader.hexVersion == 0x020000c1
diff --git a/tests/test_core/test_core_status.py b/tests/test_core/test_core_status.py
index 9e30d9c4..11a8ab7b 100644
--- a/tests/test_core/test_core_status.py
+++ b/tests/test_core/test_core_status.py
@@ -27,6 +27,7 @@ from tools import C
from PyQt5.QtGui import QIcon
from novelwriter.core.status import NWStatus
+from novelwriter.enum import nwStatusShape
statusKeys = [C.sNew, C.sNote, C.sDraft, C.sFinished]
importKeys = [C.iNew, C.iMinor, C.iMajor, C.iMain]
@@ -34,13 +35,12 @@ importKeys = [C.iNew, C.iMinor, C.iMajor, C.iMain]
@pytest.mark.core
def testCoreStatus_Internal(mockRnd):
- """Test all the internal functions of the NWStatus class.
- """
+ """Test all the internal functions of the NWStatus class."""
nStatus = NWStatus(NWStatus.STATUS)
nImport = NWStatus(NWStatus.IMPORT)
with pytest.raises(Exception):
- NWStatus(999)
+ NWStatus(999) # type: ignore
# Generate Key
# ============
@@ -49,14 +49,14 @@ def testCoreStatus_Internal(mockRnd):
assert nStatus._newKey() == statusKeys[1]
# Key collision, should move to key 3
- nStatus.write(statusKeys[2], "Crash", (0, 0, 0))
+ nStatus.write(statusKeys[2], "Crash", (0, 0, 0), nwStatusShape.SQUARE)
assert nStatus._newKey() == statusKeys[3]
assert nImport._newKey() == importKeys[0]
assert nImport._newKey() == importKeys[1]
# Key collision, should move to key 3
- nImport.write(importKeys[2], "Crash", (0, 0, 0))
+ nImport.write(importKeys[2], "Crash", (0, 0, 0), nwStatusShape.SQUARE)
assert nImport._newKey() == importKeys[3]
# Check Key
@@ -87,14 +87,13 @@ def testCoreStatus_Internal(mockRnd):
@pytest.mark.core
def testCoreStatus_Iterator(mockRnd):
- """Test the iterator functions of the NWStatus class.
- """
+ """Test the iterator functions of the NWStatus class."""
nStatus = NWStatus(NWStatus.STATUS)
- nStatus.write(None, "New", (100, 100, 100))
- nStatus.write(None, "Note", (200, 50, 0))
- nStatus.write(None, "Draft", (200, 150, 0))
- nStatus.write(None, "Finished", (50, 200, 0))
+ nStatus.write(None, "New", (100, 100, 100), nwStatusShape.SQUARE)
+ nStatus.write(None, "Note", (200, 50, 0), nwStatusShape.SQUARE)
+ nStatus.write(None, "Draft", (200, 150, 0), nwStatusShape.SQUARE)
+ nStatus.write(None, "Finished", (50, 200, 0), nwStatusShape.SQUARE)
# Direct access
entry = nStatus[statusKeys[0]]
@@ -131,30 +130,29 @@ def testCoreStatus_Iterator(mockRnd):
@pytest.mark.core
def testCoreStatus_Entries(mockRnd):
- """Test all the simple setters for the NWStatus class.
- """
+ """Test all the simple setters for the NWStatus class."""
nStatus = NWStatus(NWStatus.STATUS)
# Write
# =====
# Have a key
- nStatus.write(statusKeys[0], "Entry 1", (200, 100, 50))
+ nStatus.write(statusKeys[0], "Entry 1", (200, 100, 50), nwStatusShape.SQUARE)
assert nStatus[statusKeys[0]]["name"] == "Entry 1"
assert nStatus[statusKeys[0]]["cols"] == (200, 100, 50)
# Don't have a key
- nStatus.write(None, "Entry 2", (210, 110, 60))
+ nStatus.write(None, "Entry 2", (210, 110, 60), nwStatusShape.SQUARE)
assert nStatus[statusKeys[1]]["name"] == "Entry 2"
assert nStatus[statusKeys[1]]["cols"] == (210, 110, 60)
# Wrong colour spec
- nStatus.write(None, "Entry 3", "what?")
+ nStatus.write(None, "Entry 3", "what?", nwStatusShape.SQUARE) # type: ignore
assert nStatus[statusKeys[2]]["name"] == "Entry 3"
assert nStatus[statusKeys[2]]["cols"] == (100, 100, 100)
# Wrong colour count
- nStatus.write(None, "Entry 4", (10, 20))
+ nStatus.write(None, "Entry 4", (10, 20), nwStatusShape.SQUARE)
assert nStatus[statusKeys[3]]["name"] == "Entry 4"
assert nStatus[statusKeys[3]]["cols"] == (100, 100, 100)
@@ -283,7 +281,7 @@ def testCoreStatus_Entries(mockRnd):
# Delete default entry, Entry 2 is new default
firstName = nStatus.name(nStatus._default)
assert firstName == "Entry 1"
- assert nStatus.remove(nStatus._default) is True
+ assert nStatus.remove(nStatus._default) is True # type: ignore
assert nStatus.name(firstName) == "Entry 2"
# Remove remaining entries
@@ -298,13 +296,12 @@ def testCoreStatus_Entries(mockRnd):
@pytest.mark.core
def testCoreStatus_PackUnpack(mockRnd):
- """Test all the pack/unpack of the NWStatus class.
- """
+ """Test all the pack/unpack of the NWStatus class."""
nStatus = NWStatus(NWStatus.STATUS)
- nStatus.write(None, "New", (100, 100, 100))
- nStatus.write(None, "Note", (200, 50, 0))
- nStatus.write(None, "Draft", (200, 150, 0))
- nStatus.write(None, "Finished", (50, 200, 0))
+ nStatus.write(None, "New", (100, 100, 100), nwStatusShape.SQUARE)
+ nStatus.write(None, "Note", (200, 50, 0), nwStatusShape.SQUARE)
+ nStatus.write(None, "Draft", (200, 150, 0), nwStatusShape.SQUARE)
+ nStatus.write(None, "Finished", (50, 200, 0), nwStatusShape.SQUARE)
countTo = [3, 5, 7, 9]
for i, n in enumerate(countTo):
@@ -318,52 +315,33 @@ def testCoreStatus_PackUnpack(mockRnd):
"count": "3",
"red": "100",
"green": "100",
- "blue": "100"
+ "blue": "100",
+ "shape": "SQUARE",
}),
("Note", {
"key": statusKeys[1],
"count": "5",
"red": "200",
"green": "50",
- "blue": "0"
+ "blue": "0",
+ "shape": "SQUARE",
}),
("Draft", {
"key": statusKeys[2],
"count": "7",
"red": "200",
"green": "150",
- "blue": "0"
+ "blue": "0",
+ "shape": "SQUARE",
}),
("Finished", {
"key": statusKeys[3],
"count": "9",
"red": "50",
"green": "200",
- "blue": "0"
+ "blue": "0",
+ "shape": "SQUARE",
}),
]
- # Unpack
- nStatus = NWStatus(NWStatus.STATUS)
- nStatus.unpack({
- statusKeys[0]: {"label": "New0", "colour": (100, 100, 100), "count": countTo[0]},
- statusKeys[1]: {"label": "New1", "colour": (150, 150, 150), "count": countTo[1]},
- statusKeys[2]: {"label": "New2", "colour": (200, 200, 200), "count": countTo[2]},
- statusKeys[3]: {"label": "New3", "colour": (250, 250, 250), "count": countTo[3]},
- })
- assert len(nStatus._store) == 4
- assert list(nStatus._store.keys()) == statusKeys
- assert nStatus._store[statusKeys[0]]["name"] == "New0"
- assert nStatus._store[statusKeys[1]]["name"] == "New1"
- assert nStatus._store[statusKeys[2]]["name"] == "New2"
- assert nStatus._store[statusKeys[3]]["name"] == "New3"
- assert nStatus._store[statusKeys[0]]["cols"] == (100, 100, 100)
- assert nStatus._store[statusKeys[1]]["cols"] == (150, 150, 150)
- assert nStatus._store[statusKeys[2]]["cols"] == (200, 200, 200)
- assert nStatus._store[statusKeys[3]]["cols"] == (250, 250, 250)
- assert nStatus._store[statusKeys[0]]["count"] == countTo[0]
- assert nStatus._store[statusKeys[1]]["count"] == countTo[1]
- assert nStatus._store[statusKeys[2]]["count"] == countTo[2]
- assert nStatus._store[statusKeys[3]]["count"] == countTo[3]
-
# END Test testCoreStatus_PackUnpack
From dd15c93f51519d35e804eaf8bb36a62b2ccecec9 Mon Sep 17 00:00:00 2001
From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com>
Date: Tue, 9 Apr 2024 22:41:37 +0200
Subject: [PATCH 04/20] Comment out debug print
---
novelwriter/core/status.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/novelwriter/core/status.py b/novelwriter/core/status.py
index 0470bac0..a8c0c0ba 100644
--- a/novelwriter/core/status.py
+++ b/novelwriter/core/status.py
@@ -292,7 +292,7 @@ class _ShapeCache:
return self._cache[shape]
def circ(r: float, a: float, x: float, y: float) -> QPointF:
- print(round(x+r*sin(pi*a/180), 2), round(y-r*cos(pi*a/180), 2))
+ # print(round(x+r*sin(pi*a/180), 2), round(y-r*cos(pi*a/180), 2))
return QPointF(round(x+r*sin(pi*a/180), 2), round(y-r*cos(pi*a/180), 2))
path = QPainterPath()
From 5c9fd471a7d117802c08f2b93201c7f012c40ed4 Mon Sep 17 00:00:00 2001
From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com>
Date: Tue, 9 Apr 2024 23:00:17 +0200
Subject: [PATCH 05/20] Update codecov token
---
.github/workflows/test_linux.yml | 4 +++-
.github/workflows/test_mac.yml | 4 +++-
.github/workflows/test_win.yml | 4 +++-
3 files changed, 9 insertions(+), 3 deletions(-)
diff --git a/.github/workflows/test_linux.yml b/.github/workflows/test_linux.yml
index d1453e17..2ca682ca 100644
--- a/.github/workflows/test_linux.yml
+++ b/.github/workflows/test_linux.yml
@@ -38,4 +38,6 @@ jobs:
export QT_QPA_PLATFORM=offscreen
pytest -v --cov=novelwriter --timeout=60
- name: Upload to Codecov
- uses: codecov/codecov-action@v3
+ uses: codecov/codecov-action@v4
+ env:
+ CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
diff --git a/.github/workflows/test_mac.yml b/.github/workflows/test_mac.yml
index fe83ff74..de4aebc5 100644
--- a/.github/workflows/test_mac.yml
+++ b/.github/workflows/test_mac.yml
@@ -32,4 +32,6 @@ jobs:
export QT_QPA_PLATFORM=offscreen
pytest -v --cov=novelwriter --timeout=60
- name: Upload to Codecov
- uses: codecov/codecov-action@v3
+ uses: codecov/codecov-action@v4
+ env:
+ CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
diff --git a/.github/workflows/test_win.yml b/.github/workflows/test_win.yml
index 108670ee..9cb28ba0 100644
--- a/.github/workflows/test_win.yml
+++ b/.github/workflows/test_win.yml
@@ -28,4 +28,6 @@ jobs:
run: |
pytest -v --cov=novelwriter --timeout=60
- name: Upload to Codecov
- uses: codecov/codecov-action@v3
+ uses: codecov/codecov-action@v4
+ env:
+ CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
From 127588699ed17f6b9f60614a6771b22b08b3b033 Mon Sep 17 00:00:00 2001
From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com>
Date: Wed, 10 Apr 2024 01:08:57 +0200
Subject: [PATCH 06/20] Switch status entries from dict to dataclass
---
novelwriter/core/projectxml.py | 8 +-
novelwriter/core/status.py | 159 +++++++++++++------------
novelwriter/dialogs/projectsettings.py | 79 ++++++------
novelwriter/gui/projtree.py | 12 +-
4 files changed, 138 insertions(+), 120 deletions(-)
diff --git a/novelwriter/core/projectxml.py b/novelwriter/core/projectxml.py
index bb2ade31..eedcde10 100644
--- a/novelwriter/core/projectxml.py
+++ b/novelwriter/core/projectxml.py
@@ -358,8 +358,8 @@ class ProjectXMLReader:
logger.debug("Parsing section (legacy format)")
# Create maps to look up name -> key for status and importance
- statusMap = {entry.get("name"): key for key, entry in data.itemStatus.items()}
- importMap = {entry.get("name"): key for key, entry in data.itemImport.items()}
+ sMap: dict[str | None, str] = {e.name: k for k, e in data.itemStatus.iterItems()}
+ iMap: dict[str | None, str] = {e.name: k for k, e in data.itemImport.iterItems()}
for xItem in xSection:
if xItem.tag != "item":
@@ -406,9 +406,9 @@ class ProjectXMLReader:
# Status was split into separate status/import with a key in 1.4
if item.get("class", "") in ("NOVEL", "ARCHIVE"):
- name["status"] = statusMap.get(tmpStatus, None)
+ name["status"] = sMap.get(tmpStatus, None)
else:
- name["import"] = importMap.get(tmpStatus, None)
+ name["import"] = iMap.get(tmpStatus, None)
# A number of layouts were removed in 1.3
if item.get("layout", "") in (
diff --git a/novelwriter/core/status.py b/novelwriter/core/status.py
index a8c0c0ba..01c25067 100644
--- a/novelwriter/core/status.py
+++ b/novelwriter/core/status.py
@@ -27,7 +27,8 @@ from __future__ import annotations
import logging
import random
-from collections.abc import ItemsView, Iterable, Iterator, KeysView, ValuesView
+from collections.abc import Iterable
+from dataclasses import dataclass
from math import cos, pi, sin
from typing import TYPE_CHECKING, Literal
@@ -35,7 +36,7 @@ from PyQt5.QtCore import QPointF, Qt
from PyQt5.QtGui import QIcon, QPainter, QPainterPath, QPixmap, QColor, QPolygonF
from novelwriter import CONFIG
-from novelwriter.common import minmax, simplified
+from novelwriter.common import simplified
from novelwriter.enum import nwStatusShape
from novelwriter.types import QtPaintAnitAlias, QtTransparent
@@ -45,6 +46,18 @@ if TYPE_CHECKING: # pragma: no cover
logger = logging.getLogger(__name__)
+@dataclass
+class StatusEntry:
+
+ name: str
+ colour: QColor
+ shape: nwStatusShape
+ icon: QIcon
+ count: int = 0
+
+# END Class StatusEntry
+
+
class NWStatus:
STATUS = 1
@@ -53,11 +66,13 @@ class NWStatus:
def __init__(self, kind: Literal[1, 2]) -> None:
self._type = kind
- self._store = {}
+ self._store: dict[str, StatusEntry] = {}
self._default = None
self._iPX = CONFIG.pxInt(24)
- self._defaultIcon = self._createIcon(100, 100, 100, nwStatusShape.SQUARE)
+ self._defaultIcon = self.createIcon(
+ self._iPX, QColor(100, 100, 100), nwStatusShape.SQUARE
+ )
if self._type == self.STATUS:
self._prefix = "s"
@@ -68,8 +83,18 @@ class NWStatus:
return
- def write(self, key: str | None, name: str, col: tuple, shape: nwStatusShape | str,
- count: int | None = None) -> str:
+ def __len__(self) -> int:
+ return len(self._store)
+
+ def __getitem__(self, key: str) -> StatusEntry:
+ return self._store[key]
+
+ ##
+ # Methods
+ ##
+
+ def write(self, key: str | None, name: str, col: tuple[int, int, int],
+ shape: nwStatusShape | str, count: int | None = None) -> str:
"""Add or update a status entry. If the key is invalid, a new
key is generated.
"""
@@ -80,26 +105,25 @@ class NWStatus:
if len(col) != 3:
col = (100, 100, 100)
- cR = minmax(col[0], 0, 255)
- cG = minmax(col[1], 0, 255)
- cB = minmax(col[2], 0, 255)
name = simplified(name)
+ colour = QColor(*col)
if not isinstance(shape, nwStatusShape):
if shape in nwStatusShape.__members__:
shape = nwStatusShape[shape]
else:
shape = nwStatusShape.SQUARE
- if count is None:
- count = self._store.get(key, {}).get("count", 0)
+ icon = self.createIcon(self._iPX, colour, shape)
- self._store[key] = {
- "name": name,
- "icon": self._createIcon(cR, cG, cB, shape),
- "cols": (cR, cG, cB),
- "count": count,
- "shape": shape,
- }
+ if key and key in self._store:
+ entry = self._store[key]
+ entry.name = name
+ entry.colour = colour
+ entry.shape = shape
+ entry.icon = icon
+ entry.count = count or 0
+ else:
+ self._store[key] = StatusEntry(name, colour, shape, icon, count or 0)
if self._default is None:
self._default = key
@@ -110,7 +134,7 @@ class NWStatus:
"""Remove an entry in the list, except if the count > 0."""
if key not in self._store:
return False
- if self._store[key]["count"] > 0:
+ if self._store[key].count > 0:
return False
del self._store[key]
@@ -135,33 +159,33 @@ class NWStatus:
def name(self, key: str | None) -> str:
"""Return the name associated with a given key."""
if key and key in self._store:
- return self._store[key]["name"]
+ return self._store[key].name
elif self._default is not None:
- return self._store[self._default]["name"]
+ return self._store[self._default].name
return ""
- def cols(self, key: str | None) -> tuple[int, int, int]:
+ def cols(self, key: str | None) -> QColor:
"""Return the colours associated with a given key."""
if key and key in self._store:
- return self._store[key]["cols"]
+ return self._store[key].colour
elif self._default is not None:
- return self._store[self._default]["cols"]
- return 100, 100, 100
+ return self._store[self._default].colour
+ return QColor(100, 100, 100)
def count(self, key: str | None) -> int:
"""Return the count associated with a given key."""
if key and key in self._store:
- return self._store[key]["count"]
+ return self._store[key].count
elif self._default is not None:
- return self._store[self._default]["count"]
+ return self._store[self._default].count
return 0
def icon(self, key: str | None) -> QIcon:
"""Return the icon associated with a given key."""
if key and key in self._store:
- return self._store[key]["icon"]
+ return self._store[key].icon
elif self._default is not None:
- return self._store[self._default]["icon"]
+ return self._store[self._default].icon
return self._defaultIcon
def reorder(self, order: list[str]) -> bool:
@@ -188,28 +212,49 @@ class NWStatus:
def resetCounts(self) -> None:
"""Clear the counts of references to the status entries."""
for key in self._store:
- self._store[key]["count"] = 0
+ self._store[key].count = 0
return
def increment(self, key: str | None) -> None:
"""Increment the counter for a given entry."""
if key and key in self._store:
- self._store[key]["count"] += 1
+ self._store[key].count += 1
return
def pack(self) -> Iterable[tuple[str, dict]]:
"""Pack the status entries into a dictionary."""
- for key, data in self._store.items():
- yield (data["name"], {
+ for key, entry in self._store.items():
+ yield (entry.name, {
"key": key,
- "count": str(data["count"]),
- "red": str(data["cols"][0]),
- "green": str(data["cols"][1]),
- "blue": str(data["cols"][2]),
- "shape": data["shape"].name,
+ "count": str(entry.count),
+ "red": str(entry.colour.red()),
+ "green": str(entry.colour.green()),
+ "blue": str(entry.colour.blue()),
+ "shape": entry.shape.name,
})
return
+ def iterItems(self) -> Iterable[tuple[str, StatusEntry]]:
+ """Yield entries from the status icons."""
+ yield from self._store.items()
+
+ @staticmethod
+ def createIcon(height: int, colour: 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.end()
+
+ return QIcon(pixmap.scaled(
+ height, height,
+ Qt.AspectRatioMode.IgnoreAspectRatio,
+ Qt.TransformationMode.SmoothTransformation
+ ))
+
##
# Internal Functions
##
@@ -239,44 +284,6 @@ class NWStatus:
return False
return True
- def _createIcon(self, red: int, green: int, blue: int, 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), QColor(red, green, blue))
- painter.end()
-
- return QIcon(pixmap.scaled(
- self._iPX, self._iPX,
- Qt.AspectRatioMode.IgnoreAspectRatio,
- Qt.TransformationMode.SmoothTransformation
- ))
-
- ##
- # Iterator Bits
- ##
-
- def __len__(self) -> int:
- return len(self._store)
-
- def __getitem__(self, key: str) -> dict:
- return self._store[key]
-
- def __iter__(self) -> Iterator[dict]:
- return iter(self._store)
-
- def keys(self) -> KeysView[str]:
- return self._store.keys()
-
- def items(self) -> ItemsView[str, dict]:
- return self._store.items()
-
- def values(self) -> ValuesView[dict]:
- return self._store.values()
-
# END Class NWStatus
diff --git a/novelwriter/dialogs/projectsettings.py b/novelwriter/dialogs/projectsettings.py
index 4e234f76..8d554a56 100644
--- a/novelwriter/dialogs/projectsettings.py
+++ b/novelwriter/dialogs/projectsettings.py
@@ -36,6 +36,8 @@ from PyQt5.QtWidgets import (
from novelwriter import CONFIG, SHARED
from novelwriter.common import simplified
+from novelwriter.core.status import NWStatus
+from novelwriter.enum import nwStatusShape
from novelwriter.extensions.configlayout import NColourLabel, NFixedPage, NScrollableForm
from novelwriter.extensions.modified import NComboBox, NIconToolButton
from novelwriter.extensions.pagedsidebar import NPagedSideBar
@@ -301,12 +303,14 @@ class _SettingsPage(NScrollableForm):
class _StatusPage(NFixedPage):
- COL_LABEL = 0
- COL_USAGE = 1
+ C_DATA = 0
+ C_LABEL = 0
+ C_USAGE = 1
- KEY_ROLE = QtUserRole
- COL_ROLE = QtUserRole + 1
- NUM_ROLE = QtUserRole + 2
+ D_KEY = QtUserRole
+ D_COLOR = QtUserRole + 1
+ D_SHAPE = QtUserRole + 2
+ D_COUNT = QtUserRole + 3
def __init__(self, parent: QWidget, isStatus: bool) -> None:
super().__init__(parent=parent)
@@ -332,6 +336,11 @@ class _StatusPage(NFixedPage):
iSz = SHARED.theme.baseIconSize
bSz = SHARED.theme.buttonIconSize
+ # Labels
+ self.trCountNone = self.tr("Not in use")
+ self.trCountOne = self.tr("Used once")
+ self.trCountMore = self.tr("Used by {0} items")
+
# Title
self.pageTitle = NColourLabel(
pageLabel, SHARED.theme.helpText, parent=self,
@@ -342,11 +351,11 @@ class _StatusPage(NFixedPage):
self.listBox = QTreeWidget(self)
self.listBox.setHeaderLabels([self.tr("Label"), self.tr("Usage")])
self.listBox.itemSelectionChanged.connect(self._selectedItem)
- self.listBox.setColumnWidth(self.COL_LABEL, wCol0)
+ self.listBox.setColumnWidth(self.C_LABEL, wCol0)
self.listBox.setIndentation(0)
- for key, entry in status.items():
- self._addItem(key, entry["name"], entry["cols"], entry["count"])
+ for key, entry in status.iterItems():
+ self._addItem(key, entry.name, entry.colour, entry.shape, entry.icon, entry.count)
# List Controls
self.addButton = NIconToolButton(self, iSz, "add")
@@ -424,9 +433,10 @@ class _StatusPage(NFixedPage):
item = self.listBox.topLevelItem(n)
if item is not None:
newList.append({
- "key": item.data(self.COL_LABEL, self.KEY_ROLE),
- "name": item.text(self.COL_LABEL),
- "cols": item.data(self.COL_LABEL, self.COL_ROLE),
+ "key": item.data(self.C_DATA, self.D_KEY),
+ "name": item.text(self.C_DATA),
+ "cols": item.data(self.C_DATA, self.D_COLOR),
+ "shape": item.data(self.C_DATA, self.D_SHAPE),
})
return newList, self._colDeleted
return [], []
@@ -457,7 +467,7 @@ class _StatusPage(NFixedPage):
@pyqtSlot()
def _newItem(self) -> None:
"""Create a new status item."""
- self._addItem(None, self.tr("New Item"), (100, 100, 100), 0)
+ # self._addItem(None, self.tr("New Item"), (100, 100, 100), 0)
self._changed = True
return
@@ -467,11 +477,11 @@ class _StatusPage(NFixedPage):
selItem = self._getSelectedItem()
if isinstance(selItem, QTreeWidgetItem):
iRow = self.listBox.indexOfTopLevelItem(selItem)
- if selItem.data(self.COL_LABEL, self.NUM_ROLE) > 0:
+ if selItem.data(self.C_LABEL, self.D_COUNT) > 0:
SHARED.error(self.tr("Cannot delete a status item that is in use."))
else:
self.listBox.takeTopLevelItem(iRow)
- self._colDeleted.append(selItem.data(self.COL_LABEL, self.KEY_ROLE))
+ self._colDeleted.append(selItem.data(self.C_DATA, self.D_KEY))
self._changed = True
return
@@ -480,9 +490,9 @@ class _StatusPage(NFixedPage):
"""Save changes made to a status item."""
selItem = self._getSelectedItem()
if isinstance(selItem, QTreeWidgetItem):
- selItem.setText(self.COL_LABEL, simplified(self.editName.text()))
- selItem.setIcon(self.COL_LABEL, self.colButton.icon())
- selItem.setData(self.COL_LABEL, self.COL_ROLE, (
+ selItem.setText(self.C_LABEL, simplified(self.editName.text()))
+ selItem.setIcon(self.C_LABEL, self.colButton.icon())
+ selItem.setData(self.C_DATA, self.D_COLOR, (
self._selColour.red(), self._selColour.green(), self._selColour.blue()
))
self._changed = True
@@ -495,11 +505,11 @@ class _StatusPage(NFixedPage):
"""
selItem = self._getSelectedItem()
if isinstance(selItem, QTreeWidgetItem):
- cols = selItem.data(self.COL_LABEL, self.COL_ROLE)
- name = selItem.text(self.COL_LABEL)
+ cols = selItem.data(self.C_DATA, self.D_COLOR)
+ name = selItem.text(self.C_LABEL)
pixmap = QPixmap(self.iPx, self.iPx)
- pixmap.fill(QColor(*cols))
- self._selColour = QColor(*cols)
+ pixmap.fill(cols)
+ self._selColour = cols
self.editName.setText(name)
self.colButton.setIcon(QIcon(pixmap))
self.editName.selectAll()
@@ -522,19 +532,20 @@ class _StatusPage(NFixedPage):
# Internal Functions
##
- def _addItem(self, key: str | None, name: str,
- colour: tuple[int, int, int], count: int) -> None:
+ def _addItem(self, key: str | None, name: str, colour: QColor,
+ shape: nwStatusShape, icon: QIcon | None, count: int) -> None:
"""Add a status item to the list."""
- pixmap = QPixmap(self.iPx, self.iPx)
- pixmap.fill(QColor(*colour))
+ if icon is None:
+ icon = NWStatus.createIcon(SHARED.theme.baseIconHeight, colour, shape)
item = QTreeWidgetItem()
- item.setText(self.COL_LABEL, name)
- item.setIcon(self.COL_LABEL, QIcon(pixmap))
- item.setData(self.COL_LABEL, self.KEY_ROLE, key)
- item.setData(self.COL_LABEL, self.COL_ROLE, colour)
- item.setData(self.COL_LABEL, self.NUM_ROLE, count)
- item.setText(self.COL_USAGE, self._usageString(count))
+ item.setText(self.C_LABEL, name)
+ item.setIcon(self.C_LABEL, icon)
+ item.setText(self.C_USAGE, self._usageString(count))
+ item.setData(self.C_DATA, self.D_KEY, key)
+ item.setData(self.C_DATA, self.D_COLOR, colour)
+ item.setData(self.C_DATA, self.D_SHAPE, shape)
+ item.setData(self.C_DATA, self.D_COUNT, count)
self.listBox.addTopLevelItem(item)
@@ -571,11 +582,11 @@ class _StatusPage(NFixedPage):
def _usageString(self, count: int) -> str:
"""Generate usage string."""
if count == 0:
- return self.tr("Not in use")
+ return self.trCountNone
elif count == 1:
- return self.tr("Used once")
+ return self.trCountOne
else:
- return self.tr("Used by {0} items").format(count)
+ return self.trCountMore.format(count)
# END Class _StatusPage
diff --git a/novelwriter/gui/projtree.py b/novelwriter/gui/projtree.py
index 5ba158af..7743c3f9 100644
--- a/novelwriter/gui/projtree.py
+++ b/novelwriter/gui/projtree.py
@@ -1855,11 +1855,11 @@ class _TreeContextMenu(QMenu):
if self._item.isNovelLike():
menu = self.addMenu(self.tr("Set Status to ..."))
current = self._item.itemStatus
- for n, (key, entry) in enumerate(SHARED.project.data.itemStatus.items()):
- name = entry["name"]
+ for n, (key, entry) in enumerate(SHARED.project.data.itemStatus.iterItems()):
+ name = entry.name
if not multi and current == key:
name += f" ({nwUnicode.U_CHECK})"
- action = menu.addAction(entry["icon"], name)
+ action = menu.addAction(entry.icon, name)
if multi:
action.triggered.connect(lambda n, key=key: self._iterSetItemStatus(key))
else:
@@ -1872,11 +1872,11 @@ class _TreeContextMenu(QMenu):
else:
menu = self.addMenu(self.tr("Set Importance to ..."))
current = self._item.itemImport
- for n, (key, entry) in enumerate(SHARED.project.data.itemImport.items()):
- name = entry["name"]
+ for n, (key, entry) in enumerate(SHARED.project.data.itemImport.iterItems()):
+ name = entry.name
if not multi and current == key:
name += f" ({nwUnicode.U_CHECK})"
- action = menu.addAction(entry["icon"], name)
+ action = menu.addAction(entry.icon, name)
if multi:
action.triggered.connect(lambda n, key=key: self._iterSetItemImport(key))
else:
From 478db63b30b8f175e39df97e8aa0eea1a675ff9f Mon Sep 17 00:00:00 2001
From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com>
Date: Wed, 10 Apr 2024 01:09:11 +0200
Subject: [PATCH 07/20] Update tests
---
tests/test_core/test_core_project.py | 4 +-
tests/test_core/test_core_projectxml.py | 134 ++++++++++++------------
tests/test_core/test_core_status.py | 73 +++++--------
3 files changed, 97 insertions(+), 114 deletions(-)
diff --git a/tests/test_core/test_core_project.py b/tests/test_core/test_core_project.py
index 8a4c0b8f..bfc896a3 100644
--- a/tests/test_core/test_core_project.py
+++ b/tests/test_core/test_core_project.py
@@ -494,11 +494,11 @@ def testCoreProject_StatusImport(mockGUI, fncPath, mockRnd):
# ====================
project.data.itemStatus.resetCounts()
- for key in list(project.data.itemStatus.keys()):
+ for key in list(project.data.itemStatus._store.keys()):
assert project.data.itemStatus.remove(key) is True
project.data.itemImport.resetCounts()
- for key in list(project.data.itemImport.keys()):
+ for key in list(project.data.itemImport._store.keys()):
assert project.data.itemImport.remove(key) is True
assert len(project.data.itemStatus) == 0
diff --git a/tests/test_core/test_core_projectxml.py b/tests/test_core/test_core_projectxml.py
index 3971e136..20138bf5 100644
--- a/tests/test_core/test_core_projectxml.py
+++ b/tests/test_core/test_core_projectxml.py
@@ -30,6 +30,8 @@ from novelwriter.constants import nwFiles
from tools import cmpFiles, writeFile
from mocked import causeOSError
+from PyQt5.QtGui import QColor
+
from novelwriter.core.item import NWItem
from novelwriter.core.projectxml import ProjectXMLReader, ProjectXMLWriter, XMLReadState
from novelwriter.core.projectdata import NWProjectData
@@ -167,18 +169,18 @@ def testCoreProjectXML_ReadCurrent(monkeypatch, tstPaths, fncPath):
assert data.itemImport.name("i2d7a54") == "Major"
assert data.itemImport.name("i56be10") == "Main"
- assert data.itemStatus.cols("sf12341") == (100, 100, 100)
- assert data.itemStatus.cols("sf24ce6") == (200, 50, 0)
- assert data.itemStatus.cols("sc24b8f") == (182, 60, 0)
- assert data.itemStatus.cols("s90e6c9") == (193, 129, 0)
- assert data.itemStatus.cols("sd51c5b") == (193, 129, 0)
- assert data.itemStatus.cols("s8ae72a") == (193, 129, 0)
- assert data.itemStatus.cols("s78ea90") == (58, 180, 58)
+ assert data.itemStatus.cols("sf12341") == QColor(100, 100, 100)
+ assert data.itemStatus.cols("sf24ce6") == QColor(200, 50, 0)
+ assert data.itemStatus.cols("sc24b8f") == QColor(182, 60, 0)
+ assert data.itemStatus.cols("s90e6c9") == QColor(193, 129, 0)
+ assert data.itemStatus.cols("sd51c5b") == QColor(193, 129, 0)
+ assert data.itemStatus.cols("s8ae72a") == QColor(193, 129, 0)
+ assert data.itemStatus.cols("s78ea90") == QColor(58, 180, 58)
- assert data.itemImport.cols("ia857f0") == (100, 100, 100)
- assert data.itemImport.cols("icfb3a5") == (0, 122, 188)
- assert data.itemImport.cols("i2d7a54") == (21, 0, 180)
- assert data.itemImport.cols("i56be10") == (117, 0, 175)
+ assert data.itemImport.cols("ia857f0") == QColor(100, 100, 100)
+ assert data.itemImport.cols("icfb3a5") == QColor(0, 122, 188)
+ assert data.itemImport.cols("i2d7a54") == QColor(21, 0, 180)
+ assert data.itemImport.cols("i56be10") == QColor(117, 0, 175)
assert data.itemStatus.count("sf12341") == 4
assert data.itemStatus.count("sf24ce6") == 2
@@ -285,18 +287,18 @@ def testCoreProjectXML_ReadLegacy10(tstPaths, fncPath, mockRnd):
assert data.itemImport.name("i000009") == "Major"
assert data.itemImport.name("i00000a") == "Main"
- assert data.itemStatus.cols("s000000") == (100, 100, 100)
- assert data.itemStatus.cols("s000001") == (200, 50, 0)
- assert data.itemStatus.cols("s000002") == (182, 60, 0)
- assert data.itemStatus.cols("s000003") == (193, 129, 0)
- assert data.itemStatus.cols("s000004") == (193, 129, 0)
- assert data.itemStatus.cols("s000005") == (193, 129, 0)
- assert data.itemStatus.cols("s000006") == (58, 180, 58)
+ assert data.itemStatus.cols("s000000") == QColor(100, 100, 100)
+ assert data.itemStatus.cols("s000001") == QColor(200, 50, 0)
+ assert data.itemStatus.cols("s000002") == QColor(182, 60, 0)
+ assert data.itemStatus.cols("s000003") == QColor(193, 129, 0)
+ assert data.itemStatus.cols("s000004") == QColor(193, 129, 0)
+ assert data.itemStatus.cols("s000005") == QColor(193, 129, 0)
+ assert data.itemStatus.cols("s000006") == QColor(58, 180, 58)
- assert data.itemImport.cols("i000007") == (100, 100, 100)
- assert data.itemImport.cols("i000008") == (0, 122, 188)
- assert data.itemImport.cols("i000009") == (21, 0, 180)
- assert data.itemImport.cols("i00000a") == (117, 0, 175)
+ assert data.itemImport.cols("i000007") == QColor(100, 100, 100)
+ assert data.itemImport.cols("i000008") == QColor(0, 122, 188)
+ assert data.itemImport.cols("i000009") == QColor(21, 0, 180)
+ assert data.itemImport.cols("i00000a") == QColor(117, 0, 175)
assert data.itemStatus.count("s000000") == 0
assert data.itemStatus.count("s000001") == 0
@@ -419,18 +421,18 @@ def testCoreProjectXML_ReadLegacy11(tstPaths, fncPath, mockRnd):
assert data.itemImport.name("i000009") == "Major"
assert data.itemImport.name("i00000a") == "Main"
- assert data.itemStatus.cols("s000000") == (100, 100, 100)
- assert data.itemStatus.cols("s000001") == (200, 50, 0)
- assert data.itemStatus.cols("s000002") == (182, 60, 0)
- assert data.itemStatus.cols("s000003") == (193, 129, 0)
- assert data.itemStatus.cols("s000004") == (193, 129, 0)
- assert data.itemStatus.cols("s000005") == (193, 129, 0)
- assert data.itemStatus.cols("s000006") == (58, 180, 58)
+ assert data.itemStatus.cols("s000000") == QColor(100, 100, 100)
+ assert data.itemStatus.cols("s000001") == QColor(200, 50, 0)
+ assert data.itemStatus.cols("s000002") == QColor(182, 60, 0)
+ assert data.itemStatus.cols("s000003") == QColor(193, 129, 0)
+ assert data.itemStatus.cols("s000004") == QColor(193, 129, 0)
+ assert data.itemStatus.cols("s000005") == QColor(193, 129, 0)
+ assert data.itemStatus.cols("s000006") == QColor(58, 180, 58)
- assert data.itemImport.cols("i000007") == (100, 100, 100)
- assert data.itemImport.cols("i000008") == (0, 122, 188)
- assert data.itemImport.cols("i000009") == (21, 0, 180)
- assert data.itemImport.cols("i00000a") == (117, 0, 175)
+ assert data.itemImport.cols("i000007") == QColor(100, 100, 100)
+ assert data.itemImport.cols("i000008") == QColor(0, 122, 188)
+ assert data.itemImport.cols("i000009") == QColor(21, 0, 180)
+ assert data.itemImport.cols("i00000a") == QColor(117, 0, 175)
assert data.itemStatus.count("s000000") == 0
assert data.itemStatus.count("s000001") == 0
@@ -553,18 +555,18 @@ def testCoreProjectXML_ReadLegacy12(tstPaths, fncPath, mockRnd):
assert data.itemImport.name("i000009") == "Major"
assert data.itemImport.name("i00000a") == "Main"
- assert data.itemStatus.cols("s000000") == (100, 100, 100)
- assert data.itemStatus.cols("s000001") == (200, 50, 0)
- assert data.itemStatus.cols("s000002") == (182, 60, 0)
- assert data.itemStatus.cols("s000003") == (193, 129, 0)
- assert data.itemStatus.cols("s000004") == (193, 129, 0)
- assert data.itemStatus.cols("s000005") == (193, 129, 0)
- assert data.itemStatus.cols("s000006") == (58, 180, 58)
+ assert data.itemStatus.cols("s000000") == QColor(100, 100, 100)
+ assert data.itemStatus.cols("s000001") == QColor(200, 50, 0)
+ assert data.itemStatus.cols("s000002") == QColor(182, 60, 0)
+ assert data.itemStatus.cols("s000003") == QColor(193, 129, 0)
+ assert data.itemStatus.cols("s000004") == QColor(193, 129, 0)
+ assert data.itemStatus.cols("s000005") == QColor(193, 129, 0)
+ assert data.itemStatus.cols("s000006") == QColor(58, 180, 58)
- assert data.itemImport.cols("i000007") == (100, 100, 100)
- assert data.itemImport.cols("i000008") == (0, 122, 188)
- assert data.itemImport.cols("i000009") == (21, 0, 180)
- assert data.itemImport.cols("i00000a") == (117, 0, 175)
+ assert data.itemImport.cols("i000007") == QColor(100, 100, 100)
+ assert data.itemImport.cols("i000008") == QColor(0, 122, 188)
+ assert data.itemImport.cols("i000009") == QColor(21, 0, 180)
+ assert data.itemImport.cols("i00000a") == QColor(117, 0, 175)
assert data.itemStatus.count("s000000") == 0
assert data.itemStatus.count("s000001") == 0
@@ -690,18 +692,18 @@ def testCoreProjectXML_ReadLegacy13(tstPaths, fncPath, mockRnd):
assert data.itemImport.name("i000009") == "Major"
assert data.itemImport.name("i00000a") == "Main"
- assert data.itemStatus.cols("s000000") == (100, 100, 100)
- assert data.itemStatus.cols("s000001") == (200, 50, 0)
- assert data.itemStatus.cols("s000002") == (182, 60, 0)
- assert data.itemStatus.cols("s000003") == (193, 129, 0)
- assert data.itemStatus.cols("s000004") == (193, 129, 0)
- assert data.itemStatus.cols("s000005") == (193, 129, 0)
- assert data.itemStatus.cols("s000006") == (58, 180, 58)
+ assert data.itemStatus.cols("s000000") == QColor(100, 100, 100)
+ assert data.itemStatus.cols("s000001") == QColor(200, 50, 0)
+ assert data.itemStatus.cols("s000002") == QColor(182, 60, 0)
+ assert data.itemStatus.cols("s000003") == QColor(193, 129, 0)
+ assert data.itemStatus.cols("s000004") == QColor(193, 129, 0)
+ assert data.itemStatus.cols("s000005") == QColor(193, 129, 0)
+ assert data.itemStatus.cols("s000006") == QColor(58, 180, 58)
- assert data.itemImport.cols("i000007") == (100, 100, 100)
- assert data.itemImport.cols("i000008") == (0, 122, 188)
- assert data.itemImport.cols("i000009") == (21, 0, 180)
- assert data.itemImport.cols("i00000a") == (117, 0, 175)
+ assert data.itemImport.cols("i000007") == QColor(100, 100, 100)
+ assert data.itemImport.cols("i000008") == QColor(0, 122, 188)
+ assert data.itemImport.cols("i000009") == QColor(21, 0, 180)
+ assert data.itemImport.cols("i00000a") == QColor(117, 0, 175)
assert data.itemStatus.count("s000000") == 0
assert data.itemStatus.count("s000001") == 0
@@ -827,18 +829,18 @@ def testCoreProjectXML_ReadLegacy14(tstPaths, fncPath, mockRnd):
assert data.itemImport.name("i2d7a54") == "Major"
assert data.itemImport.name("i56be10") == "Main"
- assert data.itemStatus.cols("sf12341") == (100, 100, 100)
- assert data.itemStatus.cols("sf24ce6") == (200, 50, 0)
- assert data.itemStatus.cols("sc24b8f") == (182, 60, 0)
- assert data.itemStatus.cols("s90e6c9") == (193, 129, 0)
- assert data.itemStatus.cols("sd51c5b") == (193, 129, 0)
- assert data.itemStatus.cols("s8ae72a") == (193, 129, 0)
- assert data.itemStatus.cols("s78ea90") == (58, 180, 58)
+ assert data.itemStatus.cols("sf12341") == QColor(100, 100, 100)
+ assert data.itemStatus.cols("sf24ce6") == QColor(200, 50, 0)
+ assert data.itemStatus.cols("sc24b8f") == QColor(182, 60, 0)
+ assert data.itemStatus.cols("s90e6c9") == QColor(193, 129, 0)
+ assert data.itemStatus.cols("sd51c5b") == QColor(193, 129, 0)
+ assert data.itemStatus.cols("s8ae72a") == QColor(193, 129, 0)
+ assert data.itemStatus.cols("s78ea90") == QColor(58, 180, 58)
- assert data.itemImport.cols("ia857f0") == (100, 100, 100)
- assert data.itemImport.cols("icfb3a5") == (0, 122, 188)
- assert data.itemImport.cols("i2d7a54") == (21, 0, 180)
- assert data.itemImport.cols("i56be10") == (117, 0, 175)
+ assert data.itemImport.cols("ia857f0") == QColor(100, 100, 100)
+ assert data.itemImport.cols("icfb3a5") == QColor(0, 122, 188)
+ assert data.itemImport.cols("i2d7a54") == QColor(21, 0, 180)
+ assert data.itemImport.cols("i56be10") == QColor(117, 0, 175)
assert data.itemStatus.count("sf12341") == 4
assert data.itemStatus.count("sf24ce6") == 2
diff --git a/tests/test_core/test_core_status.py b/tests/test_core/test_core_status.py
index 11a8ab7b..748663ea 100644
--- a/tests/test_core/test_core_status.py
+++ b/tests/test_core/test_core_status.py
@@ -24,7 +24,7 @@ import pytest
from tools import C
-from PyQt5.QtGui import QIcon
+from PyQt5.QtGui import QColor, QIcon
from novelwriter.core.status import NWStatus
from novelwriter.enum import nwStatusShape
@@ -97,34 +97,15 @@ def testCoreStatus_Iterator(mockRnd):
# Direct access
entry = nStatus[statusKeys[0]]
- assert entry["cols"] == (100, 100, 100)
- assert entry["name"] == "New"
- assert entry["count"] == 0
- assert isinstance(entry["icon"], QIcon)
+ assert entry.colour == QColor(100, 100, 100)
+ assert entry.name == "New"
+ assert entry.count == 0
+ assert isinstance(entry.icon, QIcon)
- # Iterate
- entries = list(nStatus)
- assert len(entries) == 4
+ # Length
+ assert len(nStatus._store) == 4
assert len(nStatus) == 4
- # Keys
- assert list(nStatus.keys()) == statusKeys
-
- # Items
- for index, (key, entry) in enumerate(nStatus.items()):
- assert key == statusKeys[index]
- assert "cols" in entry
- assert "name" in entry
- assert "count" in entry
- assert "icon" in entry
-
- # Valuse
- for entry in nStatus.values():
- assert "cols" in entry
- assert "name" in entry
- assert "count" in entry
- assert "icon" in entry
-
# END Test testCoreStatus_Iterator
@@ -138,23 +119,23 @@ def testCoreStatus_Entries(mockRnd):
# Have a key
nStatus.write(statusKeys[0], "Entry 1", (200, 100, 50), nwStatusShape.SQUARE)
- assert nStatus[statusKeys[0]]["name"] == "Entry 1"
- assert nStatus[statusKeys[0]]["cols"] == (200, 100, 50)
+ assert nStatus[statusKeys[0]].name == "Entry 1"
+ assert nStatus[statusKeys[0]].colour == QColor(200, 100, 50)
# Don't have a key
nStatus.write(None, "Entry 2", (210, 110, 60), nwStatusShape.SQUARE)
- assert nStatus[statusKeys[1]]["name"] == "Entry 2"
- assert nStatus[statusKeys[1]]["cols"] == (210, 110, 60)
+ assert nStatus[statusKeys[1]].name == "Entry 2"
+ assert nStatus[statusKeys[1]].colour == QColor(210, 110, 60)
# Wrong colour spec
nStatus.write(None, "Entry 3", "what?", nwStatusShape.SQUARE) # type: ignore
- assert nStatus[statusKeys[2]]["name"] == "Entry 3"
- assert nStatus[statusKeys[2]]["cols"] == (100, 100, 100)
+ assert nStatus[statusKeys[2]].name == "Entry 3"
+ assert nStatus[statusKeys[2]].colour == QColor(100, 100, 100)
# Wrong colour count
- nStatus.write(None, "Entry 4", (10, 20), nwStatusShape.SQUARE)
- assert nStatus[statusKeys[3]]["name"] == "Entry 4"
- assert nStatus[statusKeys[3]]["cols"] == (100, 100, 100)
+ nStatus.write(None, "Entry 4", (10, 20), nwStatusShape.SQUARE) # type: ignore
+ assert nStatus[statusKeys[3]].name == "Entry 4"
+ assert nStatus[statusKeys[3]].colour == QColor(100, 100, 100)
# Check
# =====
@@ -178,11 +159,11 @@ def testCoreStatus_Entries(mockRnd):
# Colour Access
# =============
- assert nStatus.cols(statusKeys[0]) == (200, 100, 50)
- assert nStatus.cols(statusKeys[1]) == (210, 110, 60)
- assert nStatus.cols(statusKeys[2]) == (100, 100, 100)
- assert nStatus.cols(statusKeys[3]) == (100, 100, 100)
- assert nStatus.cols("blablabla") == (200, 100, 50)
+ assert nStatus.cols(statusKeys[0]) == QColor(200, 100, 50)
+ assert nStatus.cols(statusKeys[1]) == QColor(210, 110, 60)
+ assert nStatus.cols(statusKeys[2]) == QColor(100, 100, 100)
+ assert nStatus.cols(statusKeys[3]) == QColor(100, 100, 100)
+ assert nStatus.cols("blablabla") == QColor(200, 100, 50)
# Icon Access
# ===========
@@ -217,7 +198,7 @@ def testCoreStatus_Entries(mockRnd):
# Reorder
# =======
- cOrder = list(nStatus.keys())
+ cOrder = list(nStatus._store.keys())
assert cOrder == statusKeys
# Wrong length
@@ -226,7 +207,7 @@ def testCoreStatus_Entries(mockRnd):
# No change
assert nStatus.reorder(cOrder) is False
- # Actual reaorder
+ # Actual re-order
nOrder = [
statusKeys[0],
statusKeys[2],
@@ -234,17 +215,17 @@ def testCoreStatus_Entries(mockRnd):
statusKeys[3],
]
assert nStatus.reorder(nOrder) is True
- assert list(nStatus.keys()) == nOrder
+ assert list(nStatus._store.keys()) == nOrder
# Add an unknown key
wOrder = nOrder.copy()
wOrder[3] = nStatus._newKey()
assert nStatus.reorder(wOrder) is False
- assert list(nStatus.keys()) == nOrder
+ assert list(nStatus._store.keys()) == nOrder
# Put it back
assert nStatus.reorder(cOrder) is True
- assert list(nStatus.keys()) == cOrder
+ assert list(nStatus._store.keys()) == cOrder
# Default
# =======
@@ -254,7 +235,7 @@ def testCoreStatus_Entries(mockRnd):
assert nStatus.check("Entry 5") == ""
assert nStatus.name("blablabla") == ""
- assert nStatus.cols("blablabla") == (100, 100, 100)
+ assert nStatus.cols("blablabla") == QColor(100, 100, 100)
assert nStatus.count("blablabla") == 0
assert isinstance(nStatus.icon("blablabla"), QIcon)
From 63d2f5c57db9d22ce3b57d799a03659ca117c091 Mon Sep 17 00:00:00 2001
From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com>
Date: Wed, 10 Apr 2024 17:57:39 +0200
Subject: [PATCH 08/20] Simplify the status class
---
novelwriter/core/item.py | 20 ++---
novelwriter/core/project.py | 55 +++++-------
novelwriter/core/status.py | 94 ++++++++------------
novelwriter/dialogs/projectsettings.py | 118 +++++++++++--------------
novelwriter/gui/doceditor.py | 2 +-
novelwriter/gui/docviewerpanel.py | 2 +-
novelwriter/gui/itemdetails.py | 2 +-
novelwriter/gui/outline.py | 2 +-
novelwriter/gui/projtree.py | 2 +-
9 files changed, 123 insertions(+), 174 deletions(-)
diff --git a/novelwriter/core/item.py b/novelwriter/core/item.py
index a52b8a36..35dba0fe 100644
--- a/novelwriter/core/item.py
+++ b/novelwriter/core/item.py
@@ -25,7 +25,7 @@ from __future__ import annotations
import logging
-from typing import TYPE_CHECKING, Any, Literal, overload
+from typing import TYPE_CHECKING, Any
from PyQt5.QtGui import QIcon
@@ -308,25 +308,15 @@ class NWItem:
return trConst(nwLabels.ITEM_DESCRIPTION.get(descKey, ""))
- @overload # pragma: no cover
- def getImportStatus(self, incIcon: Literal[True] = True) -> tuple[str, QIcon]:
- pass
-
- @overload # pragma: no cover
- def getImportStatus(self, incIcon: Literal[False]) -> tuple[str, None]:
- pass
-
- def getImportStatus(self, incIcon=True):
+ def getImportStatus(self) -> tuple[str, QIcon]:
"""Return the relevant importance or status label and icon for
the current item based on its class.
"""
if self.isNovelLike():
- stName = self._project.data.itemStatus.name(self._status)
- stIcon = self._project.data.itemStatus.icon(self._status) if incIcon else None
+ entry = self._project.data.itemStatus[self._status]
else:
- stName = self._project.data.itemImport.name(self._import)
- stIcon = self._project.data.itemImport.icon(self._import) if incIcon else None
- return stName, stIcon
+ entry = self._project.data.itemImport[self._import]
+ return entry.name, entry.icon
##
# Checker Methods
diff --git a/novelwriter/core/project.py b/novelwriter/core/project.py
index 3ce2160a..2a9e156a 100644
--- a/novelwriter/core/project.py
+++ b/novelwriter/core/project.py
@@ -36,7 +36,8 @@ from collections.abc import Iterable
from PyQt5.QtCore import QCoreApplication
from novelwriter import CONFIG, SHARED, __version__, __hexversion__
-from novelwriter.enum import nwItemType, nwItemClass, nwItemLayout, nwStatusShape
+from novelwriter.core.status import StatusEntry
+from novelwriter.enum import nwItemType, nwItemClass, nwItemLayout
from novelwriter.error import logException
from novelwriter.constants import trConst, nwLabels
from novelwriter.core.tree import NWTree
@@ -461,15 +462,14 @@ class NWProject:
def setDefaultStatusImport(self) -> None:
"""Set the default status and importance values."""
- square = nwStatusShape.SQUARE
- self._data.itemStatus.write(None, self.tr("New"), (100, 100, 100), square)
- self._data.itemStatus.write(None, self.tr("Note"), (200, 50, 0), square)
- self._data.itemStatus.write(None, self.tr("Draft"), (200, 150, 0), square)
- self._data.itemStatus.write(None, self.tr("Finished"), (50, 200, 0), square)
- self._data.itemImport.write(None, self.tr("New"), (100, 100, 100), square)
- self._data.itemImport.write(None, self.tr("Minor"), (200, 50, 0), square)
- self._data.itemImport.write(None, self.tr("Major"), (200, 150, 0), square)
- self._data.itemImport.write(None, self.tr("Main"), (50, 200, 0), square)
+ self._data.itemStatus.write(None, self.tr("New"), (100, 100, 100), "SQUARE")
+ self._data.itemStatus.write(None, self.tr("Note"), (200, 50, 0), "SQUARE")
+ self._data.itemStatus.write(None, self.tr("Draft"), (200, 150, 0), "SQUARE")
+ self._data.itemStatus.write(None, self.tr("Finished"), (50, 200, 0), "SQUARE")
+ self._data.itemImport.write(None, self.tr("New"), (100, 100, 100), "SQUARE")
+ self._data.itemImport.write(None, self.tr("Minor"), (200, 50, 0), "SQUARE")
+ self._data.itemImport.write(None, self.tr("Major"), (200, 150, 0), "SQUARE")
+ self._data.itemImport.write(None, self.tr("Main"), (50, 200, 0), "SQUARE")
return
def setProjectLang(self, language: str | None) -> None:
@@ -492,13 +492,13 @@ class NWProject:
self.setProjectChanged(True)
return
- def setStatusColours(self, new: list[dict], deleted: list[str]) -> bool:
+ def setStatus(self, update: list[tuple[str | None, StatusEntry]], remove: list[str]) -> None:
"""Update the list of novel file status flags."""
- return self._setStatusImport(new, deleted, self._data.itemStatus)
+ return self._setStatusImport(update, remove, self._data.itemStatus)
- def setImportColours(self, new: list[dict], deleted: list[str]) -> bool:
+ def setImport(self, update: list[tuple[str | None, StatusEntry]], remove: list[str]) -> None:
"""Update the list of note file importance flags."""
- return self._setStatusImport(new, deleted, self._data.itemImport)
+ return self._setStatusImport(update, remove, self._data.itemImport)
def setProjectChanged(self, status: bool) -> bool:
"""Toggle the project changed flag, and propagate the
@@ -585,28 +585,17 @@ class NWProject:
# Internal Functions
##
- def _setStatusImport(self, new: list[dict], delete: list[str], target: NWStatus) -> bool:
+ def _setStatusImport(self, update: list[tuple[str | None, StatusEntry]],
+ remove: list[str], target: NWStatus) -> None:
"""Update the list of novel file status or importance flags, and
delete those that have been requested deleted.
"""
- if not (new or delete):
- return False
-
- order = []
- for entry in new:
- key = entry.get("key", None)
- name = entry.get("name", "")
- cols = entry.get("cols", (100, 100, 100))
- shape = entry.get("shape", nwStatusShape.SQUARE)
- if name:
- order.append(target.write(key, name, cols, shape))
-
- for key in delete:
- target.remove(key)
-
- target.reorder(order)
-
- return True
+ if update or remove:
+ order = [target.write(k, e.name, e.color, e.shape) for k, e in update]
+ for key in remove:
+ target.remove(key)
+ target.reorder(order)
+ return
def _loadProjectLocalisation(self) -> bool:
"""Load the language data for the current project language."""
diff --git a/novelwriter/core/status.py b/novelwriter/core/status.py
index 01c25067..ebf3d662 100644
--- a/novelwriter/core/status.py
+++ b/novelwriter/core/status.py
@@ -24,11 +24,11 @@ along with this program. If not, see .
"""
from __future__ import annotations
+import dataclasses
import logging
import random
from collections.abc import Iterable
-from dataclasses import dataclass
from math import cos, pi, sin
from typing import TYPE_CHECKING, Literal
@@ -46,18 +46,26 @@ if TYPE_CHECKING: # pragma: no cover
logger = logging.getLogger(__name__)
-@dataclass
+@dataclasses.dataclass
class StatusEntry:
name: str
- colour: QColor
+ color: QColor
shape: nwStatusShape
icon: QIcon
count: int = 0
+ @classmethod
+ def duplicate(cls, source: StatusEntry) -> StatusEntry:
+ """Create a shallow copy of the source object."""
+ return dataclasses.replace(source)
+
# END Class StatusEntry
+NO_ENTRY = StatusEntry("", QColor(0, 0, 0), nwStatusShape.SQUARE, QIcon(), 0)
+
+
class NWStatus:
STATUS = 1
@@ -70,9 +78,6 @@ class NWStatus:
self._default = None
self._iPX = CONFIG.pxInt(24)
- self._defaultIcon = self.createIcon(
- self._iPX, QColor(100, 100, 100), nwStatusShape.SQUARE
- )
if self._type == self.STATUS:
self._prefix = "s"
@@ -86,44 +91,51 @@ class NWStatus:
def __len__(self) -> int:
return len(self._store)
- def __getitem__(self, key: str) -> StatusEntry:
- return self._store[key]
+ def __getitem__(self, key: str | None) -> StatusEntry:
+ """Return the entry associated with a given key."""
+ if key and key in self._store:
+ return self._store[key]
+ elif self._default is not None:
+ return self._store[self._default]
+ return NO_ENTRY
##
# Methods
##
- def write(self, key: str | None, name: str, col: tuple[int, int, int],
- shape: nwStatusShape | str, count: int | None = None) -> str:
+ def write(self, key: str | None, name: str, color: tuple[int, int, int] | QColor,
+ shape: nwStatusShape | str, count: int = 0) -> str:
"""Add or update a status entry. If the key is invalid, a new
key is generated.
"""
if not self._isKey(key):
key = self._newKey()
- if not isinstance(col, tuple):
- col = (100, 100, 100)
- if len(col) != 3:
- col = (100, 100, 100)
- name = simplified(name)
- colour = QColor(*col)
+ if isinstance(color, QColor):
+ qColor = color
+ elif isinstance(color, tuple) and len(color) == 3:
+ qColor = QColor(*color)
+ else:
+ qColor = QColor(100, 100, 100)
+
if not isinstance(shape, nwStatusShape):
- if shape in nwStatusShape.__members__:
+ try:
shape = nwStatusShape[shape]
- else:
+ except KeyError:
shape = nwStatusShape.SQUARE
- icon = self.createIcon(self._iPX, colour, shape)
+ name = simplified(name)
+ icon = self.createIcon(self._iPX, qColor, shape)
if key and key in self._store:
entry = self._store[key]
entry.name = name
- entry.colour = colour
+ entry.color = qColor
entry.shape = shape
entry.icon = icon
- entry.count = count or 0
+ entry.count = count
else:
- self._store[key] = StatusEntry(name, colour, shape, icon, count or 0)
+ self._store[key] = StatusEntry(name, qColor, shape, icon, count)
if self._default is None:
self._default = key
@@ -156,38 +168,6 @@ class NWStatus:
return self._default
return ""
- def name(self, key: str | None) -> str:
- """Return the name associated with a given key."""
- if key and key in self._store:
- return self._store[key].name
- elif self._default is not None:
- return self._store[self._default].name
- return ""
-
- def cols(self, key: str | None) -> QColor:
- """Return the colours associated with a given key."""
- if key and key in self._store:
- return self._store[key].colour
- elif self._default is not None:
- return self._store[self._default].colour
- return QColor(100, 100, 100)
-
- def count(self, key: str | None) -> int:
- """Return the count associated with a given key."""
- if key and key in self._store:
- return self._store[key].count
- elif self._default is not None:
- return self._store[self._default].count
- return 0
-
- def icon(self, key: str | None) -> QIcon:
- """Return the icon associated with a given key."""
- if key and key in self._store:
- return self._store[key].icon
- elif self._default is not None:
- return self._store[self._default].icon
- return self._defaultIcon
-
def reorder(self, order: list[str]) -> bool:
"""Reorder the items according to list."""
if len(order) != len(self._store):
@@ -227,9 +207,9 @@ class NWStatus:
yield (entry.name, {
"key": key,
"count": str(entry.count),
- "red": str(entry.colour.red()),
- "green": str(entry.colour.green()),
- "blue": str(entry.colour.blue()),
+ "red": str(entry.color.red()),
+ "green": str(entry.color.green()),
+ "blue": str(entry.color.blue()),
"shape": entry.shape.name,
})
return
diff --git a/novelwriter/dialogs/projectsettings.py b/novelwriter/dialogs/projectsettings.py
index 8d554a56..ff3663fe 100644
--- a/novelwriter/dialogs/projectsettings.py
+++ b/novelwriter/dialogs/projectsettings.py
@@ -36,7 +36,7 @@ from PyQt5.QtWidgets import (
from novelwriter import CONFIG, SHARED
from novelwriter.common import simplified
-from novelwriter.core.status import NWStatus
+from novelwriter.core.status import NWStatus, StatusEntry
from novelwriter.enum import nwStatusShape
from novelwriter.extensions.configlayout import NColourLabel, NFixedPage, NScrollableForm
from novelwriter.extensions.modified import NComboBox, NIconToolButton
@@ -182,18 +182,18 @@ class GuiProjectSettings(QDialog):
rebuildTrees = False
if self.statusPage.wasChanged:
- newList, delList = self.statusPage.getNewList()
- project.setStatusColours(newList, delList)
+ update, remove = self.statusPage.getNewList()
+ project.setStatus(update, remove)
rebuildTrees = True
if self.importPage.wasChanged:
- newList, delList = self.importPage.getNewList()
- project.setImportColours(newList, delList)
+ update, remove = self.importPage.getNewList()
+ project.setImport(update, remove)
rebuildTrees = True
if self.replacePage.wasChanged:
- newList = self.replacePage.getNewList()
- project.data.setAutoReplace(newList)
+ update = self.replacePage.getNewList()
+ project.data.setAutoReplace(update)
self.newProjectSettingsReady.emit(rebuildTrees)
QApplication.processEvents()
@@ -308,9 +308,7 @@ class _StatusPage(NFixedPage):
C_USAGE = 1
D_KEY = QtUserRole
- D_COLOR = QtUserRole + 1
- D_SHAPE = QtUserRole + 2
- D_COUNT = QtUserRole + 3
+ D_ENTRY = QtUserRole + 1
def __init__(self, parent: QWidget, isStatus: bool) -> None:
super().__init__(parent=parent)
@@ -329,10 +327,10 @@ class _StatusPage(NFixedPage):
)
self._changed = False
- self._colDeleted = []
+ self._colDeleted: list[str] = []
self._selColour = QColor(100, 100, 100)
- self.iPx = SHARED.theme.baseIconHeight
+ self._iPx = SHARED.theme.baseIconHeight
iSz = SHARED.theme.baseIconSize
bSz = SHARED.theme.buttonIconSize
@@ -355,7 +353,7 @@ class _StatusPage(NFixedPage):
self.listBox.setIndentation(0)
for key, entry in status.iterItems():
- self._addItem(key, entry.name, entry.colour, entry.shape, entry.icon, entry.count)
+ self._addItem(key, StatusEntry.duplicate(entry))
# List Controls
self.addButton = NIconToolButton(self, iSz, "add")
@@ -376,7 +374,7 @@ class _StatusPage(NFixedPage):
self.editName.setPlaceholderText(self.tr("Select item to edit"))
self.editName.setEnabled(False)
- self.colPixmap = QPixmap(self.iPx, self.iPx)
+ self.colPixmap = QPixmap(self._iPx, self._iPx)
self.colPixmap.fill(QColor(100, 100, 100))
self.colButton = QPushButton(QIcon(self.colPixmap), self.tr("Colour"), self)
self.colButton.setIconSize(bSz)
@@ -425,20 +423,16 @@ class _StatusPage(NFixedPage):
# Methods
##
- def getNewList(self) -> tuple[list, list]:
+ def getNewList(self) -> tuple[list[tuple[str | None, StatusEntry]], list[str]]:
"""Return list of entries."""
if self._changed:
- newList = []
+ update = []
for n in range(self.listBox.topLevelItemCount()):
- item = self.listBox.topLevelItem(n)
- if item is not None:
- newList.append({
- "key": item.data(self.C_DATA, self.D_KEY),
- "name": item.text(self.C_DATA),
- "cols": item.data(self.C_DATA, self.D_COLOR),
- "shape": item.data(self.C_DATA, self.D_SHAPE),
- })
- return newList, self._colDeleted
+ if item := self.listBox.topLevelItem(n):
+ key = item.data(self.C_DATA, self.D_KEY)
+ entry = item.data(self.C_DATA, self.D_ENTRY)
+ update.append((key, entry))
+ return update, self._colDeleted
return [], []
def columnWidth(self) -> int:
@@ -458,7 +452,7 @@ class _StatusPage(NFixedPage):
)
if newCol.isValid():
self._selColour = newCol
- pixmap = QPixmap(self.iPx, self.iPx)
+ pixmap = QPixmap(self._iPx, self._iPx)
pixmap.fill(newCol)
self.colButton.setIcon(QIcon(pixmap))
self.colButton.setIconSize(pixmap.rect().size())
@@ -467,34 +461,43 @@ class _StatusPage(NFixedPage):
@pyqtSlot()
def _newItem(self) -> None:
"""Create a new status item."""
- # self._addItem(None, self.tr("New Item"), (100, 100, 100), 0)
+ 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))
self._changed = True
return
@pyqtSlot()
def _delItem(self) -> None:
"""Delete a status item."""
- selItem = self._getSelectedItem()
- if isinstance(selItem, QTreeWidgetItem):
- iRow = self.listBox.indexOfTopLevelItem(selItem)
- if selItem.data(self.C_LABEL, self.D_COUNT) > 0:
+ if item := self._getSelectedItem():
+ iRow = self.listBox.indexOfTopLevelItem(item)
+ entry: StatusEntry = item.data(self.C_DATA, self.D_ENTRY)
+ if entry.count > 0:
SHARED.error(self.tr("Cannot delete a status item that is in use."))
else:
self.listBox.takeTopLevelItem(iRow)
- self._colDeleted.append(selItem.data(self.C_DATA, self.D_KEY))
+ self._colDeleted.append(item.data(self.C_DATA, self.D_KEY))
self._changed = True
return
@pyqtSlot()
def _saveItem(self) -> None:
"""Save changes made to a status item."""
- selItem = self._getSelectedItem()
- if isinstance(selItem, QTreeWidgetItem):
- selItem.setText(self.C_LABEL, simplified(self.editName.text()))
- selItem.setIcon(self.C_LABEL, self.colButton.icon())
- selItem.setData(self.C_DATA, self.D_COLOR, (
- self._selColour.red(), self._selColour.green(), self._selColour.blue()
- ))
+ if item := self._getSelectedItem():
+ entry: StatusEntry = item.data(self.C_DATA, self.D_ENTRY)
+
+ name = simplified(self.editName.text())
+ shape = nwStatusShape.SQUARE
+ icon = NWStatus.createIcon(self._iPx, self._selColour, shape)
+ entry.name = name
+ entry.shape = shape
+ entry.color = self._selColour
+ entry.icon = icon
+
+ item.setText(self.C_LABEL, name)
+ item.setIcon(self.C_LABEL, icon)
self._changed = True
return
@@ -503,26 +506,21 @@ class _StatusPage(NFixedPage):
"""Extract the info of a selected item and populate the settings
boxes and button. If no item is selected, clear the form.
"""
- selItem = self._getSelectedItem()
- if isinstance(selItem, QTreeWidgetItem):
- cols = selItem.data(self.C_DATA, self.D_COLOR)
- name = selItem.text(self.C_LABEL)
- pixmap = QPixmap(self.iPx, self.iPx)
- pixmap.fill(cols)
- self._selColour = cols
- self.editName.setText(name)
- self.colButton.setIcon(QIcon(pixmap))
+ if item := self._getSelectedItem():
+ entry: StatusEntry = item.data(self.C_DATA, self.D_ENTRY)
+ self._selColour = entry.color
+ self.editName.setText(entry.name)
+ self.colButton.setIcon(entry.icon)
self.editName.selectAll()
self.editName.setFocus()
self.editName.setEnabled(True)
self.colButton.setEnabled(True)
self.saveButton.setEnabled(True)
else:
- pixmap = QPixmap(self.iPx, self.iPx)
- pixmap.fill(QColor(100, 100, 100))
self._selColour = QColor(100, 100, 100)
+ icon = NWStatus.createIcon(self._iPx, self._selColour, nwStatusShape.SQUARE)
self.editName.setText("")
- self.colButton.setIcon(QIcon(pixmap))
+ self.colButton.setIcon(icon)
self.editName.setEnabled(False)
self.colButton.setEnabled(False)
self.saveButton.setEnabled(False)
@@ -532,23 +530,15 @@ class _StatusPage(NFixedPage):
# Internal Functions
##
- def _addItem(self, key: str | None, name: str, colour: QColor,
- shape: nwStatusShape, icon: QIcon | None, count: int) -> None:
+ def _addItem(self, key: str | None, entry: StatusEntry) -> None:
"""Add a status item to the list."""
- if icon is None:
- icon = NWStatus.createIcon(SHARED.theme.baseIconHeight, colour, shape)
-
item = QTreeWidgetItem()
- item.setText(self.C_LABEL, name)
- item.setIcon(self.C_LABEL, icon)
- item.setText(self.C_USAGE, self._usageString(count))
+ item.setText(self.C_LABEL, entry.name)
+ item.setIcon(self.C_LABEL, entry.icon)
+ item.setText(self.C_USAGE, self._usageString(entry.count))
item.setData(self.C_DATA, self.D_KEY, key)
- item.setData(self.C_DATA, self.D_COLOR, colour)
- item.setData(self.C_DATA, self.D_SHAPE, shape)
- item.setData(self.C_DATA, self.D_COUNT, count)
-
+ item.setData(self.C_DATA, self.D_ENTRY, entry)
self.listBox.addTopLevelItem(item)
-
return
def _moveItem(self, step: int) -> None:
diff --git a/novelwriter/gui/doceditor.py b/novelwriter/gui/doceditor.py
index 1f56a2b9..e4fdffd5 100644
--- a/novelwriter/gui/doceditor.py
+++ b/novelwriter/gui/doceditor.py
@@ -3135,7 +3135,7 @@ class GuiDocEditFooter(QWidget):
sText = ""
else:
iPx = round(0.9*SHARED.theme.baseIconHeight)
- status, icon = self._tItem.getImportStatus(incIcon=True)
+ status, icon = self._tItem.getImportStatus()
sIcon = icon.pixmap(iPx, iPx)
sText = f"{status} / {self._tItem.describeMe()}"
diff --git a/novelwriter/gui/docviewerpanel.py b/novelwriter/gui/docviewerpanel.py
index f96e44e6..ed4d9ba6 100644
--- a/novelwriter/gui/docviewerpanel.py
+++ b/novelwriter/gui/docviewerpanel.py
@@ -450,7 +450,7 @@ class _ViewPanelKeyWords(QTreeWidget):
nwItem.itemType, nwItem.itemClass,
nwItem.itemLayout, nwItem.mainHeading
)
- impLabel, impIcon = nwItem.getImportStatus(incIcon=True)
+ impLabel, impIcon = nwItem.getImportStatus()
iLevel = nwHeaders.H_LEVEL.get(hItem.level, 0) if nwItem.isDocumentLayout() else 5
hDec = SHARED.theme.getHeaderDecorationNarrow(iLevel)
diff --git a/novelwriter/gui/itemdetails.py b/novelwriter/gui/itemdetails.py
index 5e9ed84d..1a90d2a3 100644
--- a/novelwriter/gui/itemdetails.py
+++ b/novelwriter/gui/itemdetails.py
@@ -253,7 +253,7 @@ class GuiItemDetails(QWidget):
# Status
# ======
- status, icon = nwItem.getImportStatus(incIcon=True)
+ status, icon = nwItem.getImportStatus()
self.statusIcon.setPixmap(icon.pixmap(iPx, iPx))
self.statusData.setText(status)
diff --git a/novelwriter/gui/outline.py b/novelwriter/gui/outline.py
index 4ed2679f..05f80e3b 100644
--- a/novelwriter/gui/outline.py
+++ b/novelwriter/gui/outline.py
@@ -1048,7 +1048,7 @@ class GuiOutlineDetails(QScrollArea):
self.titleLabel.setText(self.tr(self.LVL_MAP.get(novIdx.level, "H1")))
self.titleValue.setText(novIdx.title)
- itemStatus, _ = nwItem.getImportStatus(incIcon=False)
+ itemStatus, _ = nwItem.getImportStatus()
self.fileValue.setText(nwItem.itemName)
self.itemValue.setText(itemStatus)
diff --git a/novelwriter/gui/projtree.py b/novelwriter/gui/projtree.py
index 7743c3f9..286b192d 100644
--- a/novelwriter/gui/projtree.py
+++ b/novelwriter/gui/projtree.py
@@ -1033,7 +1033,7 @@ class GuiProjectTree(QTreeWidget):
if trItem is None or nwItem is None:
return
- itemStatus, statusIcon = nwItem.getImportStatus(incIcon=True)
+ itemStatus, statusIcon = nwItem.getImportStatus()
hLevel = nwItem.mainHeading
itemIcon = SHARED.theme.getItemIcon(
nwItem.itemType, nwItem.itemClass, nwItem.itemLayout, hLevel
From f17302439e4143e534cdc2b96d97aa9c2619e97d Mon Sep 17 00:00:00 2001
From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com>
Date: Wed, 10 Apr 2024 21:38:43 +0200
Subject: [PATCH 09/20] Simplify further how status labels are added and
updated
---
novelwriter/core/coretools.py | 2 +-
novelwriter/core/project.py | 64 +++++++------------
novelwriter/core/projectxml.py | 2 +-
novelwriter/core/status.py | 87 ++++++++------------------
novelwriter/dialogs/projectsettings.py | 56 +++++++----------
5 files changed, 69 insertions(+), 142 deletions(-)
diff --git a/novelwriter/core/coretools.py b/novelwriter/core/coretools.py
index b8fc5ba1..925db901 100644
--- a/novelwriter/core/coretools.py
+++ b/novelwriter/core/coretools.py
@@ -104,7 +104,7 @@ class DocMerger:
docText = self._project.storage.getDocumentText(srcHandle).rstrip("\n")
if addComment:
docInfo = srcItem.describeMe()
- docSt, _ = srcItem.getImportStatus(incIcon=False)
+ docSt, _ = srcItem.getImportStatus()
cmtLine = f"% {cmtPrefix} {docInfo}: {srcItem.itemName} [{docSt}]\n\n"
docText = cmtLine + docText
diff --git a/novelwriter/core/project.py b/novelwriter/core/project.py
index 2a9e156a..08a3dd11 100644
--- a/novelwriter/core/project.py
+++ b/novelwriter/core/project.py
@@ -26,34 +26,32 @@ from __future__ import annotations
import json
import logging
+from collections.abc import Iterable
from enum import Enum
+from functools import partial
+from pathlib import Path
from time import time
from typing import TYPE_CHECKING
-from pathlib import Path
-from functools import partial
-from collections.abc import Iterable
from PyQt5.QtCore import QCoreApplication
from novelwriter import CONFIG, SHARED, __version__, __hexversion__
-from novelwriter.core.status import StatusEntry
-from novelwriter.enum import nwItemType, nwItemClass, nwItemLayout
-from novelwriter.error import logException
-from novelwriter.constants import trConst, nwLabels
-from novelwriter.core.tree import NWTree
-from novelwriter.core.index import NWIndex
-from novelwriter.core.options import OptionState
-from novelwriter.core.storage import NWStorage, NWStorageOpen
-from novelwriter.core.sessions import NWSessionLog
-from novelwriter.core.projectxml import ProjectXMLReader, ProjectXMLWriter, XMLReadState
-from novelwriter.core.projectdata import NWProjectData
from novelwriter.common import (
checkStringNone, formatInt, formatTimeStamp, getFileSize, hexToInt, makeFileNameSafe, minmax
)
+from novelwriter.constants import trConst, nwLabels
+from novelwriter.core.index import NWIndex
+from novelwriter.core.options import OptionState
+from novelwriter.core.projectdata import NWProjectData
+from novelwriter.core.projectxml import ProjectXMLReader, ProjectXMLWriter, XMLReadState
+from novelwriter.core.sessions import NWSessionLog
+from novelwriter.core.storage import NWStorage, NWStorageOpen
+from novelwriter.core.tree import NWTree
+from novelwriter.enum import nwItemType, nwItemClass, nwItemLayout
+from novelwriter.error import logException
if TYPE_CHECKING: # pragma: no cover
from novelwriter.core.item import NWItem
- from novelwriter.core.status import NWStatus
logger = logging.getLogger(__name__)
@@ -462,14 +460,14 @@ class NWProject:
def setDefaultStatusImport(self) -> None:
"""Set the default status and importance values."""
- self._data.itemStatus.write(None, self.tr("New"), (100, 100, 100), "SQUARE")
- self._data.itemStatus.write(None, self.tr("Note"), (200, 50, 0), "SQUARE")
- self._data.itemStatus.write(None, self.tr("Draft"), (200, 150, 0), "SQUARE")
- self._data.itemStatus.write(None, self.tr("Finished"), (50, 200, 0), "SQUARE")
- self._data.itemImport.write(None, self.tr("New"), (100, 100, 100), "SQUARE")
- self._data.itemImport.write(None, self.tr("Minor"), (200, 50, 0), "SQUARE")
- self._data.itemImport.write(None, self.tr("Major"), (200, 150, 0), "SQUARE")
- self._data.itemImport.write(None, self.tr("Main"), (50, 200, 0), "SQUARE")
+ self._data.itemStatus.add(None, self.tr("New"), (100, 100, 100), "SQUARE", 0)
+ self._data.itemStatus.add(None, self.tr("Note"), (200, 50, 0), "SQUARE", 0)
+ self._data.itemStatus.add(None, self.tr("Draft"), (200, 150, 0), "SQUARE", 0)
+ self._data.itemStatus.add(None, self.tr("Finished"), (50, 200, 0), "SQUARE", 0)
+ self._data.itemImport.add(None, self.tr("New"), (100, 100, 100), "SQUARE", 0)
+ self._data.itemImport.add(None, self.tr("Minor"), (200, 50, 0), "SQUARE", 0)
+ self._data.itemImport.add(None, self.tr("Major"), (200, 150, 0), "SQUARE", 0)
+ self._data.itemImport.add(None, self.tr("Main"), (50, 200, 0), "SQUARE", 0)
return
def setProjectLang(self, language: str | None) -> None:
@@ -492,14 +490,6 @@ class NWProject:
self.setProjectChanged(True)
return
- def setStatus(self, update: list[tuple[str | None, StatusEntry]], remove: list[str]) -> None:
- """Update the list of novel file status flags."""
- return self._setStatusImport(update, remove, self._data.itemStatus)
-
- def setImport(self, update: list[tuple[str | None, StatusEntry]], remove: list[str]) -> None:
- """Update the list of note file importance flags."""
- return self._setStatusImport(update, remove, self._data.itemImport)
-
def setProjectChanged(self, status: bool) -> bool:
"""Toggle the project changed flag, and propagate the
information to the GUI statusbar.
@@ -585,18 +575,6 @@ class NWProject:
# Internal Functions
##
- def _setStatusImport(self, update: list[tuple[str | None, StatusEntry]],
- remove: list[str], target: NWStatus) -> None:
- """Update the list of novel file status or importance flags, and
- delete those that have been requested deleted.
- """
- if update or remove:
- order = [target.write(k, e.name, e.color, e.shape) for k, e in update]
- for key in remove:
- target.remove(key)
- target.reorder(order)
- return
-
def _loadProjectLocalisation(self) -> bool:
"""Load the language data for the current project language."""
if self._data.language is None or CONFIG._nwLangPath is None:
diff --git a/novelwriter/core/projectxml.py b/novelwriter/core/projectxml.py
index eedcde10..ac28ed4a 100644
--- a/novelwriter/core/projectxml.py
+++ b/novelwriter/core/projectxml.py
@@ -439,7 +439,7 @@ class ProjectXMLReader:
blue = checkInt(xEntry.attrib.get("blue", 0), 0)
count = checkInt(xEntry.attrib.get("count", 0), 0)
shape = xEntry.attrib.get("shape", "")
- sObject.write(key, xEntry.text or "", (red, green, blue), shape, count)
+ sObject.add(key, xEntry.text or "", (red, green, blue), shape, count)
return
def _parseDictKeyText(self, xItem: ET.Element) -> dict:
diff --git a/novelwriter/core/status.py b/novelwriter/core/status.py
index ebf3d662..df5414d9 100644
--- a/novelwriter/core/status.py
+++ b/novelwriter/core/status.py
@@ -35,7 +35,7 @@ from typing import TYPE_CHECKING, Literal
from PyQt5.QtCore import QPointF, Qt
from PyQt5.QtGui import QIcon, QPainter, QPainterPath, QPixmap, QColor, QPolygonF
-from novelwriter import CONFIG
+from novelwriter import SHARED
from novelwriter.common import simplified
from novelwriter.enum import nwStatusShape
from novelwriter.types import QtPaintAnitAlias, QtTransparent
@@ -77,7 +77,7 @@ class NWStatus:
self._store: dict[str, StatusEntry] = {}
self._default = None
- self._iPX = CONFIG.pxInt(24)
+ self._iPx = SHARED.theme.baseIconHeight
if self._type == self.STATUS:
self._prefix = "s"
@@ -103,62 +103,42 @@ class NWStatus:
# Methods
##
- def write(self, key: str | None, name: str, color: tuple[int, int, int] | QColor,
- shape: nwStatusShape | str, count: int = 0) -> str:
+ def add(self, key: str | None, name: str, color: tuple[int, int, int],
+ shape: str, count: int) -> str:
"""Add or update a status entry. If the key is invalid, a new
key is generated.
"""
- if not self._isKey(key):
- key = self._newKey()
-
- if isinstance(color, QColor):
- qColor = color
- elif isinstance(color, tuple) and len(color) == 3:
+ if isinstance(color, tuple) and len(color) == 3:
qColor = QColor(*color)
else:
qColor = QColor(100, 100, 100)
- if not isinstance(shape, nwStatusShape):
- try:
- shape = nwStatusShape[shape]
- except KeyError:
- shape = nwStatusShape.SQUARE
+ try:
+ iShape = nwStatusShape[shape]
+ except KeyError:
+ iShape = nwStatusShape.SQUARE
+ key = self._checkKey(key)
name = simplified(name)
- icon = self.createIcon(self._iPX, qColor, shape)
-
- if key and key in self._store:
- entry = self._store[key]
- entry.name = name
- entry.color = qColor
- entry.shape = shape
- entry.icon = icon
- entry.count = count
- else:
- self._store[key] = StatusEntry(name, qColor, shape, icon, count)
+ icon = self.createIcon(self._iPx, qColor, iShape)
+ self._store[key] = StatusEntry(name, qColor, iShape, icon, count)
if self._default is None:
self._default = key
return key
- def remove(self, key: str) -> bool:
- """Remove an entry in the list, except if the count > 0."""
- if key not in self._store:
- return False
- if self._store[key].count > 0:
- return False
+ def update(self, update: list[tuple[str | None, StatusEntry]]) -> None:
+ """Update the list of statuses, and from removed list."""
+ self._store.clear()
+ for key, entry in update:
+ self._store[self._checkKey(key)] = entry
- del self._store[key]
+ # Check if we need a new default
+ if self._default not in self._store:
+ self._default = next(iter(self._store)) if self._store else None
- keys = list(self._store.keys())
- if key == self._default:
- if len(keys) > 0:
- self._default = keys[0]
- else:
- self._default = None
-
- return True
+ return
def check(self, value: str) -> str:
"""Check the key against the stored status names."""
@@ -168,27 +148,6 @@ class NWStatus:
return self._default
return ""
- def reorder(self, order: list[str]) -> bool:
- """Reorder the items according to list."""
- if len(order) != len(self._store):
- logger.error("Length mismatch between new and old order")
- return False
-
- if order == list(self._store.keys()):
- return False
-
- store = {}
- for key in order:
- if key in self._store:
- store[key] = self._store[key]
- else:
- logger.error("Unknown key '%s' in order", key)
- return False
-
- self._store = store
-
- return True
-
def resetCounts(self) -> None:
"""Clear the counts of references to the status entries."""
for key in self._store:
@@ -264,6 +223,10 @@ class NWStatus:
return False
return True
+ def _checkKey(self, key: str | None) -> str:
+ """Check key is valid, and if not, generate one."""
+ return key if self._isKey(key) else self._newKey()
+
# END Class NWStatus
diff --git a/novelwriter/dialogs/projectsettings.py b/novelwriter/dialogs/projectsettings.py
index ff3663fe..e4543864 100644
--- a/novelwriter/dialogs/projectsettings.py
+++ b/novelwriter/dialogs/projectsettings.py
@@ -181,19 +181,16 @@ class GuiProjectSettings(QDialog):
rebuildTrees = False
- if self.statusPage.wasChanged:
- update, remove = self.statusPage.getNewList()
- project.setStatus(update, remove)
+ if self.statusPage.changed:
+ project.data.itemStatus.update(self.statusPage.getNewList())
rebuildTrees = True
- if self.importPage.wasChanged:
- update, remove = self.importPage.getNewList()
- project.setImport(update, remove)
+ if self.importPage.changed:
+ project.data.itemImport.update(self.importPage.getNewList())
rebuildTrees = True
- if self.replacePage.wasChanged:
- update = self.replacePage.getNewList()
- project.data.setAutoReplace(update)
+ if self.replacePage.changed:
+ project.data.setAutoReplace(self.replacePage.getNewList())
self.newProjectSettingsReady.emit(rebuildTrees)
QApplication.processEvents()
@@ -327,7 +324,6 @@ class _StatusPage(NFixedPage):
)
self._changed = False
- self._colDeleted: list[str] = []
self._selColour = QColor(100, 100, 100)
self._iPx = SHARED.theme.baseIconHeight
@@ -415,7 +411,7 @@ class _StatusPage(NFixedPage):
return
@property
- def wasChanged(self) -> bool:
+ def changed(self) -> bool:
"""The user changed these settings."""
return self._changed
@@ -423,7 +419,7 @@ class _StatusPage(NFixedPage):
# Methods
##
- def getNewList(self) -> tuple[list[tuple[str | None, StatusEntry]], list[str]]:
+ def getNewList(self) -> list[tuple[str | None, StatusEntry]]:
"""Return list of entries."""
if self._changed:
update = []
@@ -432,8 +428,8 @@ class _StatusPage(NFixedPage):
key = item.data(self.C_DATA, self.D_KEY)
entry = item.data(self.C_DATA, self.D_ENTRY)
update.append((key, entry))
- return update, self._colDeleted
- return [], []
+ return update
+ return []
def columnWidth(self) -> int:
"""Return the size of the header column."""
@@ -478,7 +474,6 @@ class _StatusPage(NFixedPage):
SHARED.error(self.tr("Cannot delete a status item that is in use."))
else:
self.listBox.takeTopLevelItem(iRow)
- self._colDeleted.append(item.data(self.C_DATA, self.D_KEY))
self._changed = True
return
@@ -543,24 +538,15 @@ class _StatusPage(NFixedPage):
def _moveItem(self, step: int) -> None:
"""Move and item up or down step."""
- selItem = self._getSelectedItem()
- if selItem is None:
- return
-
- tIndex = self.listBox.indexOfTopLevelItem(selItem)
- nChild = self.listBox.topLevelItemCount()
- nIndex = tIndex + step
- if nIndex < 0 or nIndex >= nChild:
- return
-
- cItem = self.listBox.takeTopLevelItem(tIndex)
- self.listBox.insertTopLevelItem(nIndex, cItem)
- self.listBox.clearSelection()
-
- if cItem is not None:
- cItem.setSelected(True)
- self._changed = True
-
+ if item := self._getSelectedItem():
+ tIdx = self.listBox.indexOfTopLevelItem(item)
+ nItm = self.listBox.topLevelItemCount()
+ nIdx = tIdx + step
+ if (0 <= nIdx < nItm) and (cItem := self.listBox.takeTopLevelItem(tIdx)):
+ self.listBox.insertTopLevelItem(nIdx, cItem)
+ self.listBox.clearSelection()
+ cItem.setSelected(True)
+ self._changed = True
return
def _getSelectedItem(self) -> QTreeWidgetItem | None:
@@ -665,7 +651,7 @@ class _ReplacePage(NFixedPage):
return
@property
- def wasChanged(self) -> bool:
+ def changed(self) -> bool:
"""The user changed these settings."""
return self._changed
@@ -673,7 +659,7 @@ class _ReplacePage(NFixedPage):
# Methods
##
- def getNewList(self) -> dict:
+ def getNewList(self) -> dict[str, str]:
"""Extract the list from the widget."""
new = {}
for n in range(self.listBox.topLevelItemCount()):
From adfd003532152c11897c2e805dcdad66c5a301a9 Mon Sep 17 00:00:00 2001
From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com>
Date: Wed, 10 Apr 2024 21:39:23 +0200
Subject: [PATCH 10/20] Update core tests
---
tests/test_core/test_core_item.py | 4 +-
tests/test_core/test_core_project.py | 109 ------
tests/test_core/test_core_projectxml.py | 484 ++++++++++++++----------
tests/test_core/test_core_status.py | 189 ++++-----
4 files changed, 378 insertions(+), 408 deletions(-)
diff --git a/tests/test_core/test_core_item.py b/tests/test_core/test_core_item.py
index 784b86ac..65cf7b5f 100644
--- a/tests/test_core/test_core_item.py
+++ b/tests/test_core/test_core_item.py
@@ -553,8 +553,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.write(None, "New", (100, 100, 100), "SQUARE")
- project.data.itemImport.write(None, "New", (100, 100, 100), "SQUARE")
+ project.data.itemStatus.add(None, "New", (100, 100, 100), "SQUARE", 0)
+ project.data.itemImport.add(None, "New", (100, 100, 100), "SQUARE", 0)
# Invalid
item = NWItem(project, "0000000000000")
diff --git a/tests/test_core/test_core_project.py b/tests/test_core/test_core_project.py
index bfc896a3..3304fc14 100644
--- a/tests/test_core/test_core_project.py
+++ b/tests/test_core/test_core_project.py
@@ -400,115 +400,6 @@ def testCoreProject_AccessItems(mockGUI, fncPath, mockRnd):
# END Test testCoreProject_AccessItems
-@pytest.mark.core
-def testCoreProject_StatusImport(mockGUI, fncPath, mockRnd):
- """Test the status and importance flag handling."""
- project = NWProject()
- mockRnd.reset()
- buildTestProject(project, fncPath)
-
- statusKeys = [C.sNew, C.sNote, C.sDraft, C.sFinished]
- importKeys = [C.iNew, C.iMinor, C.iMajor, C.iMain]
-
- # Change Status
- # =============
-
- project.tree[C.hNovelRoot].setStatus(statusKeys[3]) # type: ignore
- project.tree[C.hPlotRoot].setStatus(statusKeys[2]) # type: ignore
- project.tree[C.hCharRoot].setStatus(statusKeys[1]) # type: ignore
- project.tree[C.hWorldRoot].setStatus(statusKeys[3]) # type: ignore
-
- assert project.tree[C.hNovelRoot].itemStatus == statusKeys[3] # type: ignore
- assert project.tree[C.hPlotRoot].itemStatus == statusKeys[2] # type: ignore
- assert project.tree[C.hCharRoot].itemStatus == statusKeys[1] # type: ignore
- assert project.tree[C.hWorldRoot].itemStatus == statusKeys[3] # type: ignore
-
- newList = [
- {"key": statusKeys[0], "name": "New", "cols": (1, 1, 1)},
- {"key": statusKeys[1], "name": "Draft", "cols": (2, 2, 2)}, # These are swapped
- {"key": statusKeys[2], "name": "Note", "cols": (3, 3, 3)}, # These are swapped
- {"key": statusKeys[3], "name": "Edited", "cols": (4, 4, 4)}, # Renamed
- {"key": None, "name": "Finished", "cols": (5, 5, 5)}, # New, reused name
- ]
- assert project.setStatusColours(None, None) is False # type: ignore
- assert project.setStatusColours([], []) is False
- assert project.setStatusColours(newList, []) is True
-
- assert project.data.itemStatus.name(statusKeys[0]) == "New"
- assert project.data.itemStatus.name(statusKeys[1]) == "Draft"
- assert project.data.itemStatus.name(statusKeys[2]) == "Note"
- assert project.data.itemStatus.name(statusKeys[3]) == "Edited"
- assert project.data.itemStatus.cols(statusKeys[0]) == (1, 1, 1)
- assert project.data.itemStatus.cols(statusKeys[1]) == (2, 2, 2)
- assert project.data.itemStatus.cols(statusKeys[2]) == (3, 3, 3)
- assert project.data.itemStatus.cols(statusKeys[3]) == (4, 4, 4)
-
- # Check the new entry
- lastKey = project.data.itemStatus.check("s000010")
- assert lastKey == "s000010"
- assert project.data.itemStatus.name(lastKey) == "Finished"
- assert project.data.itemStatus.cols(lastKey) == (5, 5, 5)
-
- # Delete last entry
- assert project.setStatusColours([], [lastKey]) is True
- assert project.data.itemStatus.name(lastKey) == "New"
-
- # Change Importance
- # =================
-
- fHandle = project.newFile("Jane Doe", C.hCharRoot)
- project.tree[fHandle].setImport(importKeys[3]) # type: ignore
-
- assert project.tree[fHandle].itemImport == importKeys[3] # type: ignore
- newList = [
- {"key": importKeys[0], "name": "New", "cols": (1, 1, 1)},
- {"key": importKeys[1], "name": "Minor", "cols": (2, 2, 2)},
- {"key": importKeys[2], "name": "Major", "cols": (3, 3, 3)},
- {"key": importKeys[3], "name": "Min", "cols": (4, 4, 4)},
- {"key": None, "name": "Max", "cols": (5, 5, 5)},
- ]
- assert project.setImportColours(None, None) is False # type: ignore
- assert project.setImportColours([], []) is False
- assert project.setImportColours(newList, []) is True
-
- assert project.data.itemImport.name(importKeys[0]) == "New"
- assert project.data.itemImport.name(importKeys[1]) == "Minor"
- assert project.data.itemImport.name(importKeys[2]) == "Major"
- assert project.data.itemImport.name(importKeys[3]) == "Min"
- assert project.data.itemImport.cols(importKeys[0]) == (1, 1, 1)
- assert project.data.itemImport.cols(importKeys[1]) == (2, 2, 2)
- assert project.data.itemImport.cols(importKeys[2]) == (3, 3, 3)
- assert project.data.itemImport.cols(importKeys[3]) == (4, 4, 4)
-
- # Check the new entry
- lastKey = project.data.itemImport.check("i000012")
- assert lastKey == "i000012"
- assert project.data.itemImport.name(lastKey) == "Max"
- assert project.data.itemImport.cols(lastKey) == (5, 5, 5)
-
- # Delete last entry
- assert project.setImportColours([], [lastKey]) is True
- assert project.data.itemImport.name(lastKey) == "New"
-
- # Delete Status/Import
- # ====================
-
- project.data.itemStatus.resetCounts()
- for key in list(project.data.itemStatus._store.keys()):
- assert project.data.itemStatus.remove(key) is True
-
- project.data.itemImport.resetCounts()
- for key in list(project.data.itemImport._store.keys()):
- assert project.data.itemImport.remove(key) is True
-
- assert len(project.data.itemStatus) == 0
- assert len(project.data.itemImport) == 0
- assert project.saveProject() is True
- project.closeProject()
-
-# END Test testCoreProject_StatusImport
-
-
@pytest.mark.core
def testCoreProject_Methods(monkeypatch, mockGUI, fncPath, mockRnd):
"""Test other project class methods and functions."""
diff --git a/tests/test_core/test_core_projectxml.py b/tests/test_core/test_core_projectxml.py
index 20138bf5..394e29cf 100644
--- a/tests/test_core/test_core_projectxml.py
+++ b/tests/test_core/test_core_projectxml.py
@@ -27,6 +27,7 @@ from shutil import copyfile
from datetime import datetime
from novelwriter.constants import nwFiles
+from novelwriter.enum import nwStatusShape
from tools import cmpFiles, writeFile
from mocked import causeOSError
@@ -156,44 +157,57 @@ def testCoreProjectXML_ReadCurrent(monkeypatch, tstPaths, fncPath):
assert data.getLastHandle("novelTree") == "7031beac91f75"
assert data.getLastHandle("outline") == "7031beac91f75"
- assert data.itemStatus.name("sf12341") == "New"
- assert data.itemStatus.name("sf24ce6") == "Notes"
- assert data.itemStatus.name("sc24b8f") == "Started"
- assert data.itemStatus.name("s90e6c9") == "1st Draft"
- assert data.itemStatus.name("sd51c5b") == "2nd Draft"
- assert data.itemStatus.name("s8ae72a") == "3rd Draft"
- assert data.itemStatus.name("s78ea90") == "Finished"
+ assert data.itemStatus["sf12341"].name == "New"
+ assert data.itemStatus["sf24ce6"].name == "Notes"
+ assert data.itemStatus["sc24b8f"].name == "Started"
+ assert data.itemStatus["s90e6c9"].name == "1st Draft"
+ assert data.itemStatus["sd51c5b"].name == "2nd Draft"
+ assert data.itemStatus["s8ae72a"].name == "3rd Draft"
+ assert data.itemStatus["s78ea90"].name == "Finished"
- assert data.itemImport.name("ia857f0") == "None"
- assert data.itemImport.name("icfb3a5") == "Minor"
- assert data.itemImport.name("i2d7a54") == "Major"
- assert data.itemImport.name("i56be10") == "Main"
+ assert data.itemImport["ia857f0"].name == "None"
+ assert data.itemImport["icfb3a5"].name == "Minor"
+ assert data.itemImport["i2d7a54"].name == "Major"
+ assert data.itemImport["i56be10"].name == "Main"
- assert data.itemStatus.cols("sf12341") == QColor(100, 100, 100)
- assert data.itemStatus.cols("sf24ce6") == QColor(200, 50, 0)
- assert data.itemStatus.cols("sc24b8f") == QColor(182, 60, 0)
- assert data.itemStatus.cols("s90e6c9") == QColor(193, 129, 0)
- assert data.itemStatus.cols("sd51c5b") == QColor(193, 129, 0)
- assert data.itemStatus.cols("s8ae72a") == QColor(193, 129, 0)
- assert data.itemStatus.cols("s78ea90") == QColor(58, 180, 58)
+ 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.itemImport.cols("ia857f0") == QColor(100, 100, 100)
- assert data.itemImport.cols("icfb3a5") == QColor(0, 122, 188)
- assert data.itemImport.cols("i2d7a54") == QColor(21, 0, 180)
- assert data.itemImport.cols("i56be10") == QColor(117, 0, 175)
+ 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.itemStatus.count("sf12341") == 4
- assert data.itemStatus.count("sf24ce6") == 2
- assert data.itemStatus.count("sc24b8f") == 3
- assert data.itemStatus.count("s90e6c9") == 7
- assert data.itemStatus.count("sd51c5b") == 0
- assert data.itemStatus.count("s8ae72a") == 0
- assert data.itemStatus.count("s78ea90") == 1
+ assert data.itemStatus["sf12341"].shape == nwStatusShape.SQUARE
+ assert data.itemStatus["sf24ce6"].shape == nwStatusShape.SQUARE
+ assert data.itemStatus["sc24b8f"].shape == nwStatusShape.SQUARE
+ assert data.itemStatus["s90e6c9"].shape == nwStatusShape.SQUARE
+ assert data.itemStatus["sd51c5b"].shape == nwStatusShape.SQUARE
+ assert data.itemStatus["s8ae72a"].shape == nwStatusShape.SQUARE
+ assert data.itemStatus["s78ea90"].shape == nwStatusShape.SQUARE
- assert data.itemImport.count("ia857f0") == 5
- assert data.itemImport.count("icfb3a5") == 2
- assert data.itemImport.count("i2d7a54") == 2
- assert data.itemImport.count("i56be10") == 1
+ assert data.itemImport["ia857f0"].shape == nwStatusShape.SQUARE
+ assert data.itemImport["icfb3a5"].shape == nwStatusShape.SQUARE
+ assert data.itemImport["i2d7a54"].shape == nwStatusShape.SQUARE
+ assert data.itemImport["i56be10"].shape == nwStatusShape.SQUARE
+
+ assert data.itemStatus["sf12341"].count == 4
+ assert data.itemStatus["sf24ce6"].count == 2
+ assert data.itemStatus["sc24b8f"].count == 3
+ assert data.itemStatus["s90e6c9"].count == 7
+ assert data.itemStatus["sd51c5b"].count == 0
+ assert data.itemStatus["s8ae72a"].count == 0
+ assert data.itemStatus["s78ea90"].count == 1
+
+ assert data.itemImport["ia857f0"].count == 5
+ assert data.itemImport["icfb3a5"].count == 2
+ assert data.itemImport["i2d7a54"].count == 2
+ assert data.itemImport["i56be10"].count == 1
# Compare content
dumpFile = tstPaths.outDir / "projectXML_ReadCurrent.json"
@@ -274,44 +288,57 @@ def testCoreProjectXML_ReadLegacy10(tstPaths, fncPath, mockRnd):
assert data.getLastHandle("novelTree") is None # Doesn't exist in 1.0
assert data.getLastHandle("outline") is None # Doesn't exist in 1.0
- assert data.itemStatus.name("s000000") == "New"
- assert data.itemStatus.name("s000001") == "Notes"
- assert data.itemStatus.name("s000002") == "Started"
- assert data.itemStatus.name("s000003") == "1st Draft"
- assert data.itemStatus.name("s000004") == "2nd Draft"
- assert data.itemStatus.name("s000005") == "3rd Draft"
- assert data.itemStatus.name("s000006") == "Finished"
+ assert data.itemStatus["s000000"].name == "New"
+ assert data.itemStatus["s000001"].name == "Notes"
+ assert data.itemStatus["s000002"].name == "Started"
+ assert data.itemStatus["s000003"].name == "1st Draft"
+ assert data.itemStatus["s000004"].name == "2nd Draft"
+ assert data.itemStatus["s000005"].name == "3rd Draft"
+ assert data.itemStatus["s000006"].name == "Finished"
- assert data.itemImport.name("i000007") == "None"
- assert data.itemImport.name("i000008") == "Minor"
- assert data.itemImport.name("i000009") == "Major"
- assert data.itemImport.name("i00000a") == "Main"
+ assert data.itemImport["i000007"].name == "None"
+ assert data.itemImport["i000008"].name == "Minor"
+ assert data.itemImport["i000009"].name == "Major"
+ assert data.itemImport["i00000a"].name == "Main"
- assert data.itemStatus.cols("s000000") == QColor(100, 100, 100)
- assert data.itemStatus.cols("s000001") == QColor(200, 50, 0)
- assert data.itemStatus.cols("s000002") == QColor(182, 60, 0)
- assert data.itemStatus.cols("s000003") == QColor(193, 129, 0)
- assert data.itemStatus.cols("s000004") == QColor(193, 129, 0)
- assert data.itemStatus.cols("s000005") == QColor(193, 129, 0)
- assert data.itemStatus.cols("s000006") == QColor(58, 180, 58)
+ 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.itemImport.cols("i000007") == QColor(100, 100, 100)
- assert data.itemImport.cols("i000008") == QColor(0, 122, 188)
- assert data.itemImport.cols("i000009") == QColor(21, 0, 180)
- assert data.itemImport.cols("i00000a") == QColor(117, 0, 175)
+ 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.itemStatus.count("s000000") == 0
- assert data.itemStatus.count("s000001") == 0
- assert data.itemStatus.count("s000002") == 0
- assert data.itemStatus.count("s000003") == 0
- assert data.itemStatus.count("s000004") == 0
- assert data.itemStatus.count("s000005") == 0
- assert data.itemStatus.count("s000006") == 0
+ assert data.itemStatus["s000000"].shape == nwStatusShape.SQUARE
+ assert data.itemStatus["s000001"].shape == nwStatusShape.SQUARE
+ assert data.itemStatus["s000002"].shape == nwStatusShape.SQUARE
+ assert data.itemStatus["s000003"].shape == nwStatusShape.SQUARE
+ assert data.itemStatus["s000004"].shape == nwStatusShape.SQUARE
+ assert data.itemStatus["s000005"].shape == nwStatusShape.SQUARE
+ assert data.itemStatus["s000006"].shape == nwStatusShape.SQUARE
- assert data.itemImport.count("i000007") == 0
- assert data.itemImport.count("i000008") == 0
- assert data.itemImport.count("i000009") == 0
- assert data.itemImport.count("i00000a") == 0
+ assert data.itemImport["i000007"].shape == nwStatusShape.SQUARE
+ assert data.itemImport["i000008"].shape == nwStatusShape.SQUARE
+ assert data.itemImport["i000009"].shape == nwStatusShape.SQUARE
+ assert data.itemImport["i00000a"].shape == nwStatusShape.SQUARE
+
+ assert data.itemStatus["s000000"].count == 0
+ assert data.itemStatus["s000001"].count == 0
+ assert data.itemStatus["s000002"].count == 0
+ assert data.itemStatus["s000003"].count == 0
+ assert data.itemStatus["s000004"].count == 0
+ assert data.itemStatus["s000005"].count == 0
+ assert data.itemStatus["s000006"].count == 0
+
+ assert data.itemImport["i000007"].count == 0
+ assert data.itemImport["i000008"].count == 0
+ assert data.itemImport["i000009"].count == 0
+ assert data.itemImport["i00000a"].count == 0
# Compare content
dumpFile = tstPaths.outDir / "projectXML_ReadLegacy10.json"
@@ -327,7 +354,7 @@ def testCoreProjectXML_ReadLegacy10(tstPaths, fncPath, mockRnd):
for entry in content:
item = NWItem(mockProject, "0000000000000") # type: ignore
item.unpack(entry)
- status[item.itemHandle] = item.getImportStatus(incIcon=False)[0]
+ status[item.itemHandle] = item.getImportStatus()[0]
packedContent.append(item.pack())
assert status == {
@@ -408,44 +435,57 @@ def testCoreProjectXML_ReadLegacy11(tstPaths, fncPath, mockRnd):
assert data.getLastHandle("novelTree") is None # Doesn't exist in 1.1
assert data.getLastHandle("outline") is None # Doesn't exist in 1.1
- assert data.itemStatus.name("s000000") == "New"
- assert data.itemStatus.name("s000001") == "Notes"
- assert data.itemStatus.name("s000002") == "Started"
- assert data.itemStatus.name("s000003") == "1st Draft"
- assert data.itemStatus.name("s000004") == "2nd Draft"
- assert data.itemStatus.name("s000005") == "3rd Draft"
- assert data.itemStatus.name("s000006") == "Finished"
+ assert data.itemStatus["s000000"].name == "New"
+ assert data.itemStatus["s000001"].name == "Notes"
+ assert data.itemStatus["s000002"].name == "Started"
+ assert data.itemStatus["s000003"].name == "1st Draft"
+ assert data.itemStatus["s000004"].name == "2nd Draft"
+ assert data.itemStatus["s000005"].name == "3rd Draft"
+ assert data.itemStatus["s000006"].name == "Finished"
- assert data.itemImport.name("i000007") == "None"
- assert data.itemImport.name("i000008") == "Minor"
- assert data.itemImport.name("i000009") == "Major"
- assert data.itemImport.name("i00000a") == "Main"
+ assert data.itemImport["i000007"].name == "None"
+ assert data.itemImport["i000008"].name == "Minor"
+ assert data.itemImport["i000009"].name == "Major"
+ assert data.itemImport["i00000a"].name == "Main"
- assert data.itemStatus.cols("s000000") == QColor(100, 100, 100)
- assert data.itemStatus.cols("s000001") == QColor(200, 50, 0)
- assert data.itemStatus.cols("s000002") == QColor(182, 60, 0)
- assert data.itemStatus.cols("s000003") == QColor(193, 129, 0)
- assert data.itemStatus.cols("s000004") == QColor(193, 129, 0)
- assert data.itemStatus.cols("s000005") == QColor(193, 129, 0)
- assert data.itemStatus.cols("s000006") == QColor(58, 180, 58)
+ 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.itemImport.cols("i000007") == QColor(100, 100, 100)
- assert data.itemImport.cols("i000008") == QColor(0, 122, 188)
- assert data.itemImport.cols("i000009") == QColor(21, 0, 180)
- assert data.itemImport.cols("i00000a") == QColor(117, 0, 175)
+ 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.itemStatus.count("s000000") == 0
- assert data.itemStatus.count("s000001") == 0
- assert data.itemStatus.count("s000002") == 0
- assert data.itemStatus.count("s000003") == 0
- assert data.itemStatus.count("s000004") == 0
- assert data.itemStatus.count("s000005") == 0
- assert data.itemStatus.count("s000006") == 0
+ assert data.itemStatus["s000000"].shape == nwStatusShape.SQUARE
+ assert data.itemStatus["s000001"].shape == nwStatusShape.SQUARE
+ assert data.itemStatus["s000002"].shape == nwStatusShape.SQUARE
+ assert data.itemStatus["s000003"].shape == nwStatusShape.SQUARE
+ assert data.itemStatus["s000004"].shape == nwStatusShape.SQUARE
+ assert data.itemStatus["s000005"].shape == nwStatusShape.SQUARE
+ assert data.itemStatus["s000006"].shape == nwStatusShape.SQUARE
- assert data.itemImport.count("i000007") == 0
- assert data.itemImport.count("i000008") == 0
- assert data.itemImport.count("i000009") == 0
- assert data.itemImport.count("i00000a") == 0
+ assert data.itemImport["i000007"].shape == nwStatusShape.SQUARE
+ assert data.itemImport["i000008"].shape == nwStatusShape.SQUARE
+ assert data.itemImport["i000009"].shape == nwStatusShape.SQUARE
+ assert data.itemImport["i00000a"].shape == nwStatusShape.SQUARE
+
+ assert data.itemStatus["s000000"].count == 0
+ assert data.itemStatus["s000001"].count == 0
+ assert data.itemStatus["s000002"].count == 0
+ assert data.itemStatus["s000003"].count == 0
+ assert data.itemStatus["s000004"].count == 0
+ assert data.itemStatus["s000005"].count == 0
+ assert data.itemStatus["s000006"].count == 0
+
+ assert data.itemImport["i000007"].count == 0
+ assert data.itemImport["i000008"].count == 0
+ assert data.itemImport["i000009"].count == 0
+ assert data.itemImport["i00000a"].count == 0
# Compare content
dumpFile = tstPaths.outDir / "projectXML_ReadLegacy11.json"
@@ -461,7 +501,7 @@ def testCoreProjectXML_ReadLegacy11(tstPaths, fncPath, mockRnd):
for entry in content:
item = NWItem(mockProject, "0000000000000") # type: ignore
item.unpack(entry)
- status[item.itemHandle] = item.getImportStatus(incIcon=False)[0]
+ status[item.itemHandle] = item.getImportStatus()[0]
packedContent.append(item.pack())
assert status == {
@@ -542,44 +582,57 @@ def testCoreProjectXML_ReadLegacy12(tstPaths, fncPath, mockRnd):
assert data.getLastHandle("novelTree") is None # Doesn't exist in 1.2
assert data.getLastHandle("outline") is None # Doesn't exist in 1.2
- assert data.itemStatus.name("s000000") == "New"
- assert data.itemStatus.name("s000001") == "Notes"
- assert data.itemStatus.name("s000002") == "Started"
- assert data.itemStatus.name("s000003") == "1st Draft"
- assert data.itemStatus.name("s000004") == "2nd Draft"
- assert data.itemStatus.name("s000005") == "3rd Draft"
- assert data.itemStatus.name("s000006") == "Finished"
+ assert data.itemStatus["s000000"].name == "New"
+ assert data.itemStatus["s000001"].name == "Notes"
+ assert data.itemStatus["s000002"].name == "Started"
+ assert data.itemStatus["s000003"].name == "1st Draft"
+ assert data.itemStatus["s000004"].name == "2nd Draft"
+ assert data.itemStatus["s000005"].name == "3rd Draft"
+ assert data.itemStatus["s000006"].name == "Finished"
- assert data.itemImport.name("i000007") == "None"
- assert data.itemImport.name("i000008") == "Minor"
- assert data.itemImport.name("i000009") == "Major"
- assert data.itemImport.name("i00000a") == "Main"
+ assert data.itemImport["i000007"].name == "None"
+ assert data.itemImport["i000008"].name == "Minor"
+ assert data.itemImport["i000009"].name == "Major"
+ assert data.itemImport["i00000a"].name == "Main"
- assert data.itemStatus.cols("s000000") == QColor(100, 100, 100)
- assert data.itemStatus.cols("s000001") == QColor(200, 50, 0)
- assert data.itemStatus.cols("s000002") == QColor(182, 60, 0)
- assert data.itemStatus.cols("s000003") == QColor(193, 129, 0)
- assert data.itemStatus.cols("s000004") == QColor(193, 129, 0)
- assert data.itemStatus.cols("s000005") == QColor(193, 129, 0)
- assert data.itemStatus.cols("s000006") == QColor(58, 180, 58)
+ 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.itemImport.cols("i000007") == QColor(100, 100, 100)
- assert data.itemImport.cols("i000008") == QColor(0, 122, 188)
- assert data.itemImport.cols("i000009") == QColor(21, 0, 180)
- assert data.itemImport.cols("i00000a") == QColor(117, 0, 175)
+ 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.itemStatus.count("s000000") == 0
- assert data.itemStatus.count("s000001") == 0
- assert data.itemStatus.count("s000002") == 0
- assert data.itemStatus.count("s000003") == 0
- assert data.itemStatus.count("s000004") == 0
- assert data.itemStatus.count("s000005") == 0
- assert data.itemStatus.count("s000006") == 0
+ assert data.itemStatus["s000000"].shape == nwStatusShape.SQUARE
+ assert data.itemStatus["s000001"].shape == nwStatusShape.SQUARE
+ assert data.itemStatus["s000002"].shape == nwStatusShape.SQUARE
+ assert data.itemStatus["s000003"].shape == nwStatusShape.SQUARE
+ assert data.itemStatus["s000004"].shape == nwStatusShape.SQUARE
+ assert data.itemStatus["s000005"].shape == nwStatusShape.SQUARE
+ assert data.itemStatus["s000006"].shape == nwStatusShape.SQUARE
- assert data.itemImport.count("i000007") == 0
- assert data.itemImport.count("i000008") == 0
- assert data.itemImport.count("i000009") == 0
- assert data.itemImport.count("i00000a") == 0
+ assert data.itemImport["i000007"].shape == nwStatusShape.SQUARE
+ assert data.itemImport["i000008"].shape == nwStatusShape.SQUARE
+ assert data.itemImport["i000009"].shape == nwStatusShape.SQUARE
+ assert data.itemImport["i00000a"].shape == nwStatusShape.SQUARE
+
+ assert data.itemStatus["s000000"].count == 0
+ assert data.itemStatus["s000001"].count == 0
+ assert data.itemStatus["s000002"].count == 0
+ assert data.itemStatus["s000003"].count == 0
+ assert data.itemStatus["s000004"].count == 0
+ assert data.itemStatus["s000005"].count == 0
+ assert data.itemStatus["s000006"].count == 0
+
+ assert data.itemImport["i000007"].count == 0
+ assert data.itemImport["i000008"].count == 0
+ assert data.itemImport["i000009"].count == 0
+ assert data.itemImport["i00000a"].count == 0
# Compare content
dumpFile = tstPaths.outDir / "projectXML_ReadLegacy12.json"
@@ -595,7 +648,7 @@ def testCoreProjectXML_ReadLegacy12(tstPaths, fncPath, mockRnd):
for entry in content:
item = NWItem(mockProject, "0000000000000") # type: ignore
item.unpack(entry)
- status[item.itemHandle] = item.getImportStatus(incIcon=False)[0]
+ status[item.itemHandle] = item.getImportStatus()[0]
packedContent.append(item.pack())
assert status == {
@@ -679,44 +732,57 @@ def testCoreProjectXML_ReadLegacy13(tstPaths, fncPath, mockRnd):
assert data.getLastHandle("novelTree") is None # Doesn't exist in 1.3
assert data.getLastHandle("outline") is None # Doesn't exist in 1.3
- assert data.itemStatus.name("s000000") == "New"
- assert data.itemStatus.name("s000001") == "Notes"
- assert data.itemStatus.name("s000002") == "Started"
- assert data.itemStatus.name("s000003") == "1st Draft"
- assert data.itemStatus.name("s000004") == "2nd Draft"
- assert data.itemStatus.name("s000005") == "3rd Draft"
- assert data.itemStatus.name("s000006") == "Finished"
+ assert data.itemStatus["s000000"].name == "New"
+ assert data.itemStatus["s000001"].name == "Notes"
+ assert data.itemStatus["s000002"].name == "Started"
+ assert data.itemStatus["s000003"].name == "1st Draft"
+ assert data.itemStatus["s000004"].name == "2nd Draft"
+ assert data.itemStatus["s000005"].name == "3rd Draft"
+ assert data.itemStatus["s000006"].name == "Finished"
- assert data.itemImport.name("i000007") == "None"
- assert data.itemImport.name("i000008") == "Minor"
- assert data.itemImport.name("i000009") == "Major"
- assert data.itemImport.name("i00000a") == "Main"
+ assert data.itemImport["i000007"].name == "None"
+ assert data.itemImport["i000008"].name == "Minor"
+ assert data.itemImport["i000009"].name == "Major"
+ assert data.itemImport["i00000a"].name == "Main"
- assert data.itemStatus.cols("s000000") == QColor(100, 100, 100)
- assert data.itemStatus.cols("s000001") == QColor(200, 50, 0)
- assert data.itemStatus.cols("s000002") == QColor(182, 60, 0)
- assert data.itemStatus.cols("s000003") == QColor(193, 129, 0)
- assert data.itemStatus.cols("s000004") == QColor(193, 129, 0)
- assert data.itemStatus.cols("s000005") == QColor(193, 129, 0)
- assert data.itemStatus.cols("s000006") == QColor(58, 180, 58)
+ 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.itemImport.cols("i000007") == QColor(100, 100, 100)
- assert data.itemImport.cols("i000008") == QColor(0, 122, 188)
- assert data.itemImport.cols("i000009") == QColor(21, 0, 180)
- assert data.itemImport.cols("i00000a") == QColor(117, 0, 175)
+ 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.itemStatus.count("s000000") == 0
- assert data.itemStatus.count("s000001") == 0
- assert data.itemStatus.count("s000002") == 0
- assert data.itemStatus.count("s000003") == 0
- assert data.itemStatus.count("s000004") == 0
- assert data.itemStatus.count("s000005") == 0
- assert data.itemStatus.count("s000006") == 0
+ assert data.itemStatus["s000000"].shape == nwStatusShape.SQUARE
+ assert data.itemStatus["s000001"].shape == nwStatusShape.SQUARE
+ assert data.itemStatus["s000002"].shape == nwStatusShape.SQUARE
+ assert data.itemStatus["s000003"].shape == nwStatusShape.SQUARE
+ assert data.itemStatus["s000004"].shape == nwStatusShape.SQUARE
+ assert data.itemStatus["s000005"].shape == nwStatusShape.SQUARE
+ assert data.itemStatus["s000006"].shape == nwStatusShape.SQUARE
- assert data.itemImport.count("i000007") == 0
- assert data.itemImport.count("i000008") == 0
- assert data.itemImport.count("i000009") == 0
- assert data.itemImport.count("i00000a") == 0
+ assert data.itemImport["i000007"].shape == nwStatusShape.SQUARE
+ assert data.itemImport["i000008"].shape == nwStatusShape.SQUARE
+ assert data.itemImport["i000009"].shape == nwStatusShape.SQUARE
+ assert data.itemImport["i00000a"].shape == nwStatusShape.SQUARE
+
+ assert data.itemStatus["s000000"].count == 0
+ assert data.itemStatus["s000001"].count == 0
+ assert data.itemStatus["s000002"].count == 0
+ assert data.itemStatus["s000003"].count == 0
+ assert data.itemStatus["s000004"].count == 0
+ assert data.itemStatus["s000005"].count == 0
+ assert data.itemStatus["s000006"].count == 0
+
+ assert data.itemImport["i000007"].count == 0
+ assert data.itemImport["i000008"].count == 0
+ assert data.itemImport["i000009"].count == 0
+ assert data.itemImport["i00000a"].count == 0
# Compare content
dumpFile = tstPaths.outDir / "projectXML_ReadLegacy13.json"
@@ -732,7 +798,7 @@ def testCoreProjectXML_ReadLegacy13(tstPaths, fncPath, mockRnd):
for entry in content:
item = NWItem(mockProject, "0000000000000") # type: ignore
item.unpack(entry)
- status[item.itemHandle] = item.getImportStatus(incIcon=False)[0]
+ status[item.itemHandle] = item.getImportStatus()[0]
packedContent.append(item.pack())
assert status == {
@@ -816,44 +882,56 @@ def testCoreProjectXML_ReadLegacy14(tstPaths, fncPath, mockRnd):
assert data.getLastHandle("novelTree") is None # Doesn't exist in 1.3
assert data.getLastHandle("outline") is None # Doesn't exist in 1.3
- assert data.itemStatus.name("sf12341") == "New"
- assert data.itemStatus.name("sf24ce6") == "Notes"
- assert data.itemStatus.name("sc24b8f") == "Started"
- assert data.itemStatus.name("s90e6c9") == "1st Draft"
- assert data.itemStatus.name("sd51c5b") == "2nd Draft"
- assert data.itemStatus.name("s8ae72a") == "3rd Draft"
- assert data.itemStatus.name("s78ea90") == "Finished"
+ assert data.itemStatus["sf12341"].name == "New"
+ assert data.itemStatus["sf24ce6"].name == "Notes"
+ assert data.itemStatus["sc24b8f"].name == "Started"
+ assert data.itemStatus["s90e6c9"].name == "1st Draft"
+ assert data.itemStatus["sd51c5b"].name == "2nd Draft"
+ assert data.itemStatus["s8ae72a"].name == "3rd Draft"
+ assert data.itemStatus["s78ea90"].name == "Finished"
- assert data.itemImport.name("ia857f0") == "None"
- assert data.itemImport.name("icfb3a5") == "Minor"
- assert data.itemImport.name("i2d7a54") == "Major"
- assert data.itemImport.name("i56be10") == "Main"
+ assert data.itemImport["ia857f0"].name == "None"
+ assert data.itemImport["icfb3a5"].name == "Minor"
+ assert data.itemImport["i2d7a54"].name == "Major"
+ assert data.itemImport["i56be10"].name == "Main"
- assert data.itemStatus.cols("sf12341") == QColor(100, 100, 100)
- assert data.itemStatus.cols("sf24ce6") == QColor(200, 50, 0)
- assert data.itemStatus.cols("sc24b8f") == QColor(182, 60, 0)
- assert data.itemStatus.cols("s90e6c9") == QColor(193, 129, 0)
- assert data.itemStatus.cols("sd51c5b") == QColor(193, 129, 0)
- assert data.itemStatus.cols("s8ae72a") == QColor(193, 129, 0)
- assert data.itemStatus.cols("s78ea90") == QColor(58, 180, 58)
+ 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.itemImport.cols("ia857f0") == QColor(100, 100, 100)
- assert data.itemImport.cols("icfb3a5") == QColor(0, 122, 188)
- assert data.itemImport.cols("i2d7a54") == QColor(21, 0, 180)
- assert data.itemImport.cols("i56be10") == QColor(117, 0, 175)
+ 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.itemStatus.count("sf12341") == 4
- assert data.itemStatus.count("sf24ce6") == 2
- assert data.itemStatus.count("sc24b8f") == 3
- assert data.itemStatus.count("s90e6c9") == 7
- assert data.itemStatus.count("sd51c5b") == 0
- assert data.itemStatus.count("s8ae72a") == 0
- assert data.itemStatus.count("s78ea90") == 1
+ assert data.itemStatus["sf12341"].shape == nwStatusShape.SQUARE
+ assert data.itemStatus["sf24ce6"].shape == nwStatusShape.SQUARE
+ assert data.itemStatus["sc24b8f"].shape == nwStatusShape.SQUARE
+ assert data.itemStatus["s90e6c9"].shape == nwStatusShape.SQUARE
+ assert data.itemStatus["sd51c5b"].shape == nwStatusShape.SQUARE
+ assert data.itemStatus["s8ae72a"].shape == nwStatusShape.SQUARE
+ assert data.itemStatus["s78ea90"].shape == nwStatusShape.SQUARE
- assert data.itemImport.count("ia857f0") == 5
- assert data.itemImport.count("icfb3a5") == 2
- assert data.itemImport.count("i2d7a54") == 2
- assert data.itemImport.count("i56be10") == 1
+ assert data.itemImport["ia857f0"].shape == nwStatusShape.SQUARE
+ assert data.itemImport["icfb3a5"].shape == nwStatusShape.SQUARE
+ assert data.itemImport["i2d7a54"].shape == nwStatusShape.SQUARE
+ assert data.itemImport["i56be10"].shape == nwStatusShape.SQUARE
+
+ assert data.itemStatus["sf12341"].count == 4
+ assert data.itemStatus["sf24ce6"].count == 2
+ assert data.itemStatus["sc24b8f"].count == 3
+ assert data.itemStatus["s90e6c9"].count == 7
+ assert data.itemStatus["sd51c5b"].count == 0
+ assert data.itemStatus["s8ae72a"].count == 0
+ assert data.itemStatus["s78ea90"].count == 1
+ assert data.itemImport["ia857f0"].count == 5
+ assert data.itemImport["icfb3a5"].count == 2
+ assert data.itemImport["i2d7a54"].count == 2
+ assert data.itemImport["i56be10"].count == 1
# Compare content
dumpFile = tstPaths.outDir / "projectXML_ReadLegacy14.json"
@@ -869,7 +947,7 @@ def testCoreProjectXML_ReadLegacy14(tstPaths, fncPath, mockRnd):
for entry in content:
item = NWItem(mockProject, "0000000000000") # type: ignore
item.unpack(entry)
- status[item.itemHandle] = item.getImportStatus(incIcon=False)[0]
+ status[item.itemHandle] = item.getImportStatus()[0]
packedContent.append(item.pack())
assert status == {
diff --git a/tests/test_core/test_core_status.py b/tests/test_core/test_core_status.py
index 748663ea..9dd440e5 100644
--- a/tests/test_core/test_core_status.py
+++ b/tests/test_core/test_core_status.py
@@ -49,14 +49,14 @@ def testCoreStatus_Internal(mockRnd):
assert nStatus._newKey() == statusKeys[1]
# Key collision, should move to key 3
- nStatus.write(statusKeys[2], "Crash", (0, 0, 0), nwStatusShape.SQUARE)
+ nStatus.add(statusKeys[2], "Crash", (0, 0, 0), "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.write(importKeys[2], "Crash", (0, 0, 0), nwStatusShape.SQUARE)
+ nImport.add(importKeys[2], "Crash", (0, 0, 0), "SQUARE", 0)
assert nImport._newKey() == importKeys[3]
# Check Key
@@ -90,14 +90,14 @@ def testCoreStatus_Iterator(mockRnd):
"""Test the iterator functions of the NWStatus class."""
nStatus = NWStatus(NWStatus.STATUS)
- nStatus.write(None, "New", (100, 100, 100), nwStatusShape.SQUARE)
- nStatus.write(None, "Note", (200, 50, 0), nwStatusShape.SQUARE)
- nStatus.write(None, "Draft", (200, 150, 0), nwStatusShape.SQUARE)
- nStatus.write(None, "Finished", (50, 200, 0), nwStatusShape.SQUARE)
+ nStatus.add(None, "New", (100, 100, 100), "SQUARE", 0)
+ nStatus.add(None, "Note", (200, 50, 0), "SQUARE", 0)
+ nStatus.add(None, "Draft", (200, 150, 0), "SQUARE", 0)
+ nStatus.add(None, "Finished", (50, 200, 0), "SQUARE", 0)
# Direct access
entry = nStatus[statusKeys[0]]
- assert entry.colour == QColor(100, 100, 100)
+ assert entry.color == QColor(100, 100, 100)
assert entry.name == "New"
assert entry.count == 0
assert isinstance(entry.icon, QIcon)
@@ -118,24 +118,24 @@ def testCoreStatus_Entries(mockRnd):
# =====
# Have a key
- nStatus.write(statusKeys[0], "Entry 1", (200, 100, 50), nwStatusShape.SQUARE)
+ nStatus.add(statusKeys[0], "Entry 1", (200, 100, 50), "SQUARE", 0)
assert nStatus[statusKeys[0]].name == "Entry 1"
- assert nStatus[statusKeys[0]].colour == QColor(200, 100, 50)
+ assert nStatus[statusKeys[0]].color == QColor(200, 100, 50)
# Don't have a key
- nStatus.write(None, "Entry 2", (210, 110, 60), nwStatusShape.SQUARE)
+ nStatus.add(None, "Entry 2", (210, 110, 60), "SQUARE", 0)
assert nStatus[statusKeys[1]].name == "Entry 2"
- assert nStatus[statusKeys[1]].colour == QColor(210, 110, 60)
+ assert nStatus[statusKeys[1]].color == QColor(210, 110, 60)
# Wrong colour spec
- nStatus.write(None, "Entry 3", "what?", nwStatusShape.SQUARE) # type: ignore
+ nStatus.add(None, "Entry 3", "what?", "SQUARE", 0) # type: ignore
assert nStatus[statusKeys[2]].name == "Entry 3"
- assert nStatus[statusKeys[2]].colour == QColor(100, 100, 100)
+ assert nStatus[statusKeys[2]].color == QColor(100, 100, 100)
# Wrong colour count
- nStatus.write(None, "Entry 4", (10, 20), nwStatusShape.SQUARE) # type: ignore
+ nStatus.add(None, "Entry 4", (10, 20), "SQUARE", 0) # type: ignore
assert nStatus[statusKeys[3]].name == "Entry 4"
- assert nStatus[statusKeys[3]].colour == QColor(100, 100, 100)
+ assert nStatus[statusKeys[3]].color == QColor(100, 100, 100)
# Check
# =====
@@ -150,29 +150,29 @@ def testCoreStatus_Entries(mockRnd):
# Name Access
# ===========
- assert nStatus.name(statusKeys[0]) == "Entry 1"
- assert nStatus.name(statusKeys[1]) == "Entry 2"
- assert nStatus.name(statusKeys[2]) == "Entry 3"
- assert nStatus.name(statusKeys[3]) == "Entry 4"
- assert nStatus.name("blablabla") == "Entry 1"
+ assert nStatus[statusKeys[0]].name == "Entry 1"
+ assert nStatus[statusKeys[1]].name == "Entry 2"
+ assert nStatus[statusKeys[2]].name == "Entry 3"
+ assert nStatus[statusKeys[3]].name == "Entry 4"
+ assert nStatus["blablabla"].name == "Entry 1"
# Colour Access
# =============
- assert nStatus.cols(statusKeys[0]) == QColor(200, 100, 50)
- assert nStatus.cols(statusKeys[1]) == QColor(210, 110, 60)
- assert nStatus.cols(statusKeys[2]) == QColor(100, 100, 100)
- assert nStatus.cols(statusKeys[3]) == QColor(100, 100, 100)
- assert nStatus.cols("blablabla") == QColor(200, 100, 50)
+ 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)
# Icon Access
# ===========
- assert isinstance(nStatus.icon(statusKeys[0]), QIcon)
- assert isinstance(nStatus.icon(statusKeys[1]), QIcon)
- assert isinstance(nStatus.icon(statusKeys[2]), QIcon)
- assert isinstance(nStatus.icon(statusKeys[3]), QIcon)
- assert isinstance(nStatus.icon("blablabla"), QIcon)
+ assert isinstance(nStatus[statusKeys[0]].icon, QIcon)
+ assert isinstance(nStatus[statusKeys[1]].icon, QIcon)
+ assert isinstance(nStatus[statusKeys[2]].icon, QIcon)
+ assert isinstance(nStatus[statusKeys[3]].icon, QIcon)
+ assert isinstance(nStatus["blablabla"].icon, QIcon)
# Increment and Count Access
# ==========================
@@ -182,50 +182,50 @@ def testCoreStatus_Entries(mockRnd):
for _ in range(n):
nStatus.increment(statusKeys[i])
- assert nStatus.count(statusKeys[0]) == countTo[0]
- assert nStatus.count(statusKeys[1]) == countTo[1]
- assert nStatus.count(statusKeys[2]) == countTo[2]
- assert nStatus.count(statusKeys[3]) == countTo[3]
- assert nStatus.count("blablabla") == countTo[0]
+ assert nStatus[statusKeys[0]].count == countTo[0]
+ assert nStatus[statusKeys[1]].count == countTo[1]
+ assert nStatus[statusKeys[2]].count == countTo[2]
+ assert nStatus[statusKeys[3]].count == countTo[3]
+ assert nStatus["blablabla"].count == countTo[0]
nStatus.resetCounts()
- assert nStatus.count(statusKeys[0]) == 0
- assert nStatus.count(statusKeys[1]) == 0
- assert nStatus.count(statusKeys[2]) == 0
- assert nStatus.count(statusKeys[3]) == 0
+ assert nStatus[statusKeys[0]].count == 0
+ assert nStatus[statusKeys[1]].count == 0
+ assert nStatus[statusKeys[2]].count == 0
+ assert nStatus[statusKeys[3]].count == 0
# Reorder
# =======
- cOrder = list(nStatus._store.keys())
- assert cOrder == statusKeys
+ # cOrder = list(nStatus._store.keys())
+ # assert cOrder == statusKeys
- # Wrong length
- assert nStatus.reorder([]) is False
+ # # Wrong length
+ # assert nStatus.reorder([]) is False
- # No change
- assert nStatus.reorder(cOrder) is False
+ # # No change
+ # assert nStatus.reorder(cOrder) is False
- # Actual re-order
- nOrder = [
- statusKeys[0],
- statusKeys[2],
- statusKeys[1],
- statusKeys[3],
- ]
- assert nStatus.reorder(nOrder) is True
- assert list(nStatus._store.keys()) == nOrder
+ # # Actual re-order
+ # nOrder = [
+ # statusKeys[0],
+ # statusKeys[2],
+ # statusKeys[1],
+ # statusKeys[3],
+ # ]
+ # assert nStatus.reorder(nOrder) is True
+ # assert list(nStatus._store.keys()) == nOrder
- # Add an unknown key
- wOrder = nOrder.copy()
- wOrder[3] = nStatus._newKey()
- assert nStatus.reorder(wOrder) is False
- assert list(nStatus._store.keys()) == nOrder
+ # # Add an unknown key
+ # wOrder = nOrder.copy()
+ # wOrder[3] = nStatus._newKey()
+ # assert nStatus.reorder(wOrder) is False
+ # assert list(nStatus._store.keys()) == nOrder
- # Put it back
- assert nStatus.reorder(cOrder) is True
- assert list(nStatus._store.keys()) == cOrder
+ # # Put it back
+ # assert nStatus.reorder(cOrder) is True
+ # assert list(nStatus._store.keys()) == cOrder
# Default
# =======
@@ -234,43 +234,44 @@ def testCoreStatus_Entries(mockRnd):
nStatus._default = None
assert nStatus.check("Entry 5") == ""
- assert nStatus.name("blablabla") == ""
- assert nStatus.cols("blablabla") == QColor(100, 100, 100)
- assert nStatus.count("blablabla") == 0
- assert isinstance(nStatus.icon("blablabla"), QIcon)
+ assert nStatus["blablabla"].name == ""
+ assert nStatus["blablabla"].color == QColor(0, 0, 0)
+ assert nStatus["blablabla"].shape == nwStatusShape.SQUARE
+ assert nStatus["blablabla"].icon.isNull()
+ assert nStatus["blablabla"].count == 0
nStatus._default = default
- # Remove
- # ======
+ # # Remove
+ # # ======
- # Non-existing entry
- assert nStatus.remove("blablabla") is False
+ # # Non-existing entry
+ # assert nStatus.remove("blablabla") is False
- # Non-zero entry
- nStatus.increment(statusKeys[3])
- assert nStatus.remove(statusKeys[3]) is False
+ # # Non-zero entry
+ # nStatus.increment(statusKeys[3])
+ # assert nStatus.remove(statusKeys[3]) is False
- # Delete last entry
- nStatus.resetCounts()
- lastName = nStatus.name(statusKeys[3])
- assert lastName == "Entry 4"
- assert nStatus.remove(statusKeys[3]) is True
- assert nStatus.check(statusKeys[3]) == nStatus._default
- assert nStatus.check(lastName) == nStatus._default
+ # # Delete last entry
+ # nStatus.resetCounts()
+ # lastName = nStatus[statusKeys[3]].name
+ # assert lastName == "Entry 4"
+ # assert nStatus.remove(statusKeys[3]) is True
+ # assert nStatus.check(statusKeys[3]) == nStatus._default
+ # assert nStatus.check(lastName) == nStatus._default
- # Delete default entry, Entry 2 is new default
- firstName = nStatus.name(nStatus._default)
- assert firstName == "Entry 1"
- assert nStatus.remove(nStatus._default) is True # type: ignore
- assert nStatus.name(firstName) == "Entry 2"
+ # # Delete default entry, Entry 2 is new default
+ # firstName = nStatus[nStatus._default].name
+ # assert firstName == "Entry 1"
+ # assert nStatus.remove(nStatus._default) is True # type: ignore
+ # assert nStatus[firstName].name == "Entry 2"
- # Remove remaining entries
- assert nStatus.remove(statusKeys[1]) is True
- assert nStatus.remove(statusKeys[2]) is True
+ # # Remove remaining entries
+ # assert nStatus.remove(statusKeys[1]) is True
+ # assert nStatus.remove(statusKeys[2]) is True
- assert len(nStatus) == 0
- assert nStatus._default is None
+ # assert len(nStatus) == 0
+ # assert nStatus._default is None
# END Test testCoreStatus_Entries
@@ -279,10 +280,10 @@ def testCoreStatus_Entries(mockRnd):
def testCoreStatus_PackUnpack(mockRnd):
"""Test all the pack/unpack of the NWStatus class."""
nStatus = NWStatus(NWStatus.STATUS)
- nStatus.write(None, "New", (100, 100, 100), nwStatusShape.SQUARE)
- nStatus.write(None, "Note", (200, 50, 0), nwStatusShape.SQUARE)
- nStatus.write(None, "Draft", (200, 150, 0), nwStatusShape.SQUARE)
- nStatus.write(None, "Finished", (50, 200, 0), nwStatusShape.SQUARE)
+ nStatus.add(None, "New", (100, 100, 100), "SQUARE", 0)
+ nStatus.add(None, "Note", (200, 50, 0), "SQUARE", 0)
+ nStatus.add(None, "Draft", (200, 150, 0), "SQUARE", 0)
+ nStatus.add(None, "Finished", (50, 200, 0), "SQUARE", 0)
countTo = [3, 5, 7, 9]
for i, n in enumerate(countTo):
From d1dbb0b378d59ea47de52c398344f3255240c396 Mon Sep 17 00:00:00 2001
From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com>
Date: Wed, 10 Apr 2024 22:19:15 +0200
Subject: [PATCH 11/20] Add test coverage of status class
---
novelwriter/core/status.py | 7 +-
tests/mocked.py | 2 +-
tests/test_core/test_core_status.py | 215 ++++++++++++++++++----------
3 files changed, 146 insertions(+), 78 deletions(-)
diff --git a/novelwriter/core/status.py b/novelwriter/core/status.py
index df5414d9..8909cc98 100644
--- a/novelwriter/core/status.py
+++ b/novelwriter/core/status.py
@@ -57,8 +57,11 @@ class StatusEntry:
@classmethod
def duplicate(cls, source: StatusEntry) -> StatusEntry:
- """Create a shallow copy of the source object."""
- return dataclasses.replace(source)
+ """Create a deep copy of the source object."""
+ cls = dataclasses.replace(source)
+ cls.color = QColor(source.color)
+ cls.icon = QIcon(source.icon)
+ return cls
# END Class StatusEntry
diff --git a/tests/mocked.py b/tests/mocked.py
index 65b65d93..5f9fc6a3 100644
--- a/tests/mocked.py
+++ b/tests/mocked.py
@@ -75,7 +75,7 @@ class MockStatusBar:
class MockTheme:
def __init__(self):
- self.baseIconHeight = 10
+ self.baseIconHeight = 20
return
def getPixmap(self, *a):
diff --git a/tests/test_core/test_core_status.py b/tests/test_core/test_core_status.py
index 9dd440e5..3c2b8edd 100644
--- a/tests/test_core/test_core_status.py
+++ b/tests/test_core/test_core_status.py
@@ -26,7 +26,7 @@ from tools import C
from PyQt5.QtGui import QColor, QIcon
-from novelwriter.core.status import NWStatus
+from novelwriter.core.status import NWStatus, StatusEntry, _ShapeCache
from novelwriter.enum import nwStatusShape
statusKeys = [C.sNew, C.sNote, C.sDraft, C.sFinished]
@@ -34,7 +34,36 @@ importKeys = [C.iNew, C.iMinor, C.iMajor, C.iMain]
@pytest.mark.core
-def testCoreStatus_Internal(mockRnd):
+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)
+
+ # Check values
+ assert entry.name == "Test"
+ assert entry.color is color
+ assert entry.shape == nwStatusShape.CIRCLE
+ assert entry.icon is icon
+ assert entry.count == 42
+
+ # Make a copy
+ other = StatusEntry.duplicate(entry)
+ assert other is not entry
+
+ # Check copy is not the same
+ assert other.name == "Test"
+ assert other.color is not color # Not the same object
+ assert other.color == color # But same colours
+ assert other.shape == nwStatusShape.CIRCLE
+ assert other.icon is not icon # Not the same icon, but a copy
+ assert other.count == 42
+
+# END Test testCoreStatus_StatusEntry
+
+
+@pytest.mark.core
+def testCoreStatus_Internal(mockGUI, mockRnd):
"""Test all the internal functions of the NWStatus class."""
nStatus = NWStatus(NWStatus.STATUS)
nImport = NWStatus(NWStatus.IMPORT)
@@ -82,18 +111,25 @@ def testCoreStatus_Internal(mockRnd):
assert nImport._isKey("i12345F") is False # Not a lower case hex value
assert nImport._isKey("i12345f") is True # Valid hex value
+ assert nStatus._checkKey(None) == "s000008" # Creates next key
+ assert nStatus._checkKey("s654321") == "s654321" # Status key accepted
+ assert nStatus._checkKey("i123456") == "s000009" # Import key not accepted
+
+ assert nImport._checkKey(None) == "i00000a" # Creates next key
+ assert nImport._checkKey("s654321") == "i00000b" # Status key not accepted
+ assert nImport._checkKey("i123456") == "i123456" # Import key accepted
+
# END Test testCoreStatus_Internal
@pytest.mark.core
-def testCoreStatus_Iterator(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), "SQUARE", 0)
- nStatus.add(None, "Draft", (200, 150, 0), "SQUARE", 0)
- nStatus.add(None, "Finished", (50, 200, 0), "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)
# Direct access
entry = nStatus[statusKeys[0]]
@@ -106,36 +142,63 @@ def testCoreStatus_Iterator(mockRnd):
assert len(nStatus._store) == 4
assert len(nStatus) == 4
+ # Content : Keys
+ assert [k for k, _ in nStatus.iterItems()] == [
+ "s000000", "s000001", "s000002", "s000003"
+ ]
+
+ # Content : Names
+ assert [e.name for _, e in nStatus.iterItems()] == [
+ "New", "Note", "Draft", "Finished"
+ ]
+
+ # 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)
+ ]
+
+ # Content : Shape
+ assert [e.shape for _, e in nStatus.iterItems()] == [
+ nwStatusShape.SQUARE, nwStatusShape.CIRCLE, nwStatusShape.SQUARE, nwStatusShape.CIRCLE
+ ]
+
+ # Content : Count
+ assert [e.count for _, e in nStatus.iterItems()] == [0, 1, 2, 3]
+
# END Test testCoreStatus_Iterator
@pytest.mark.core
-def testCoreStatus_Entries(mockRnd):
+def testCoreStatus_Entries(mockGUI, mockRnd):
"""Test all the simple setters for the NWStatus class."""
nStatus = NWStatus(NWStatus.STATUS)
- # Write
- # =====
+ # Add
+ # ===
- # Have a key
+ # Has a key
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]].shape == nwStatusShape.SQUARE
- # Don't have a key
+ # Doesn't have a key
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]].shape == nwStatusShape.SQUARE
- # Wrong colour spec
- nStatus.add(None, "Entry 3", "what?", "SQUARE", 0) # type: ignore
+ # Wrong colour spec, unknown shape
+ nStatus.add(None, "Entry 3", "what?", "", 0) # type: ignore
assert nStatus[statusKeys[2]].name == "Entry 3"
assert nStatus[statusKeys[2]].color == QColor(100, 100, 100)
+ assert nStatus[statusKeys[2]].shape == nwStatusShape.SQUARE
# Wrong colour count
- nStatus.add(None, "Entry 4", (10, 20), "SQUARE", 0) # type: ignore
+ nStatus.add(None, "Entry 4", (10, 20), "CIRCLE", 0) # type: ignore
assert nStatus[statusKeys[3]].name == "Entry 4"
assert nStatus[statusKeys[3]].color == QColor(100, 100, 100)
+ assert nStatus[statusKeys[3]].shape == nwStatusShape.CIRCLE
# Check
# =====
@@ -174,6 +237,15 @@ def testCoreStatus_Entries(mockRnd):
assert isinstance(nStatus[statusKeys[3]].icon, QIcon)
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
+ assert nStatus[statusKeys[3]].shape == nwStatusShape.CIRCLE
+ assert nStatus["blablabla"].shape == nwStatusShape.SQUARE
+
# Increment and Count Access
# ==========================
@@ -195,37 +267,19 @@ def testCoreStatus_Entries(mockRnd):
assert nStatus[statusKeys[2]].count == 0
assert nStatus[statusKeys[3]].count == 0
- # Reorder
- # =======
+ # Update
+ # ======
- # cOrder = list(nStatus._store.keys())
- # assert cOrder == statusKeys
+ assert list(nStatus._store.keys()) == statusKeys
- # # Wrong length
- # assert nStatus.reorder([]) is False
+ # Reverse
+ order: list[tuple[str | None, StatusEntry]] = list(nStatus.iterItems())
+ nStatus.update(list(reversed(order)))
+ assert list(nStatus._store.keys()) == list(reversed(statusKeys))
- # # No change
- # assert nStatus.reorder(cOrder) is False
-
- # # Actual re-order
- # nOrder = [
- # statusKeys[0],
- # statusKeys[2],
- # statusKeys[1],
- # statusKeys[3],
- # ]
- # assert nStatus.reorder(nOrder) is True
- # assert list(nStatus._store.keys()) == nOrder
-
- # # Add an unknown key
- # wOrder = nOrder.copy()
- # wOrder[3] = nStatus._newKey()
- # assert nStatus.reorder(wOrder) is False
- # assert list(nStatus._store.keys()) == nOrder
-
- # # Put it back
- # assert nStatus.reorder(cOrder) is True
- # assert list(nStatus._store.keys()) == cOrder
+ # Restore
+ nStatus.update(order)
+ assert list(nStatus._store.keys()) == statusKeys
# Default
# =======
@@ -242,46 +296,31 @@ def testCoreStatus_Entries(mockRnd):
nStatus._default = default
- # # Remove
- # # ======
+ # Remove
+ # ======
+ # This uses update with deleted items
- # # Non-existing entry
- # assert nStatus.remove("blablabla") is False
+ order: list[tuple[str | None, StatusEntry]] = list(nStatus.iterItems())
- # # Non-zero entry
- # nStatus.increment(statusKeys[3])
- # assert nStatus.remove(statusKeys[3]) is False
+ # Remove Entry 0
+ nStatus.update([order[1], order[3], order[2]])
+ assert list(nStatus._store.keys()) == [statusKeys[1], statusKeys[3], statusKeys[2]]
+ assert nStatus._default == statusKeys[1]
- # # Delete last entry
- # nStatus.resetCounts()
- # lastName = nStatus[statusKeys[3]].name
- # assert lastName == "Entry 4"
- # assert nStatus.remove(statusKeys[3]) is True
- # assert nStatus.check(statusKeys[3]) == nStatus._default
- # assert nStatus.check(lastName) == nStatus._default
-
- # # Delete default entry, Entry 2 is new default
- # firstName = nStatus[nStatus._default].name
- # assert firstName == "Entry 1"
- # assert nStatus.remove(nStatus._default) is True # type: ignore
- # assert nStatus[firstName].name == "Entry 2"
-
- # # Remove remaining entries
- # assert nStatus.remove(statusKeys[1]) is True
- # assert nStatus.remove(statusKeys[2]) is True
-
- # assert len(nStatus) == 0
- # assert nStatus._default is None
+ # Remove Entry 1
+ nStatus.update([order[3], order[2]])
+ assert list(nStatus._store.keys()) == [statusKeys[3], statusKeys[2]]
+ assert nStatus._default == statusKeys[3]
# END Test testCoreStatus_Entries
@pytest.mark.core
-def testCoreStatus_PackUnpack(mockRnd):
- """Test all the pack/unpack of the NWStatus class."""
+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), "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)
@@ -306,7 +345,7 @@ def testCoreStatus_PackUnpack(mockRnd):
"red": "200",
"green": "50",
"blue": "0",
- "shape": "SQUARE",
+ "shape": "CIRCLE",
}),
("Draft", {
"key": statusKeys[2],
@@ -326,4 +365,30 @@ def testCoreStatus_PackUnpack(mockRnd):
}),
]
-# END Test testCoreStatus_PackUnpack
+# END Test testCoreStatus_Pack
+
+
+@pytest.mark.core
+def testCoreStatus_ShapeCache():
+ """Test the _ShapeCache class."""
+ shapes = _ShapeCache()
+
+ # Generate all shapes
+ square = shapes.getShape(nwStatusShape.SQUARE)
+ circle = shapes.getShape(nwStatusShape.CIRCLE)
+ triangle = shapes.getShape(nwStatusShape.TRIANGLE)
+ diamond = shapes.getShape(nwStatusShape.DIAMOND)
+ pentagon = shapes.getShape(nwStatusShape.PENTAGON)
+ star = shapes.getShape(nwStatusShape.STAR)
+ pacman = shapes.getShape(nwStatusShape.PACMAN)
+
+ # Request again should return from cache
+ assert shapes.getShape(nwStatusShape.SQUARE) is square
+ assert shapes.getShape(nwStatusShape.CIRCLE) is circle
+ assert shapes.getShape(nwStatusShape.TRIANGLE) is triangle
+ assert shapes.getShape(nwStatusShape.DIAMOND) is diamond
+ assert shapes.getShape(nwStatusShape.PENTAGON) is pentagon
+ assert shapes.getShape(nwStatusShape.STAR) is star
+ assert shapes.getShape(nwStatusShape.PACMAN) is pacman
+
+# END Test testCoreStatus_ShapeCache
From 778b65b48c30d73e4379d9f3b8e5f941300fa451 Mon Sep 17 00:00:00 2001
From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com>
Date: Wed, 10 Apr 2024 23:24:00 +0200
Subject: [PATCH 12/20] Add shape selector to project settings
---
novelwriter/constants.py | 11 +++-
novelwriter/dialogs/projectsettings.py | 85 +++++++++++++++++---------
novelwriter/extensions/modified.py | 4 +-
sample/nwProject.nwx | 26 ++++----
4 files changed, 81 insertions(+), 45 deletions(-)
diff --git a/novelwriter/constants.py b/novelwriter/constants.py
index a1e2b84a..04eea148 100644
--- a/novelwriter/constants.py
+++ b/novelwriter/constants.py
@@ -25,7 +25,7 @@ from __future__ import annotations
from PyQt5.QtCore import QCoreApplication, QT_TRANSLATE_NOOP
-from novelwriter.enum import nwBuildFmt, nwItemClass, nwItemLayout, nwOutline
+from novelwriter.enum import nwBuildFmt, nwItemClass, nwItemLayout, nwOutline, nwStatusShape
def trConst(text: str) -> str:
@@ -268,6 +268,15 @@ class nwLabels:
nwBuildFmt.J_HTML: ".json",
nwBuildFmt.J_NWD: ".json",
}
+ STATUS_SHAPES = {
+ nwStatusShape.SQUARE: QT_TRANSLATE_NOOP("Constant", "Square"),
+ nwStatusShape.CIRCLE: QT_TRANSLATE_NOOP("Constant", "Circle"),
+ nwStatusShape.TRIANGLE: QT_TRANSLATE_NOOP("Constant", "Triangle"),
+ nwStatusShape.DIAMOND: QT_TRANSLATE_NOOP("Constant", "Diamond"),
+ nwStatusShape.PENTAGON: QT_TRANSLATE_NOOP("Constant", "Pentagon"),
+ nwStatusShape.STAR: QT_TRANSLATE_NOOP("Constant", "Star"),
+ nwStatusShape.PACMAN: QT_TRANSLATE_NOOP("Constant", "Pacman"),
+ }
FILE_FILTERS = {
"*.txt": QT_TRANSLATE_NOOP("Constant", "Text files"),
"*.md": QT_TRANSLATE_NOOP("Constant", "Markdown files"),
diff --git a/novelwriter/dialogs/projectsettings.py b/novelwriter/dialogs/projectsettings.py
index e4543864..c6b5281a 100644
--- a/novelwriter/dialogs/projectsettings.py
+++ b/novelwriter/dialogs/projectsettings.py
@@ -30,12 +30,13 @@ from PyQt5.QtCore import Qt, pyqtSignal, pyqtSlot
from PyQt5.QtGui import QCloseEvent, QColor, QIcon, QPixmap
from PyQt5.QtWidgets import (
QApplication, QColorDialog, QDialog, QDialogButtonBox, QHBoxLayout,
- QLineEdit, QPushButton, QStackedWidget, QTreeWidget, QTreeWidgetItem,
- QVBoxLayout, QWidget
+ QLineEdit, QPushButton, QSizePolicy, QStackedWidget, QTreeWidget,
+ QTreeWidgetItem, QVBoxLayout, QWidget
)
from novelwriter import CONFIG, SHARED
from novelwriter.common import simplified
+from novelwriter.constants import nwLabels
from novelwriter.core.status import NWStatus, StatusEntry
from novelwriter.enum import nwStatusShape
from novelwriter.extensions.configlayout import NColourLabel, NFixedPage, NScrollableForm
@@ -182,14 +183,17 @@ class GuiProjectSettings(QDialog):
rebuildTrees = False
if self.statusPage.changed:
+ logger.debug("Updating status labels")
project.data.itemStatus.update(self.statusPage.getNewList())
rebuildTrees = True
if self.importPage.changed:
+ logger.debug("Updating importance labels")
project.data.itemImport.update(self.importPage.getNewList())
rebuildTrees = True
if self.replacePage.changed:
+ logger.debug("Updating auto-replace settings")
project.data.setAutoReplace(self.replacePage.getNewList())
self.newProjectSettingsReady.emit(rebuildTrees)
@@ -324,7 +328,7 @@ class _StatusPage(NFixedPage):
)
self._changed = False
- self._selColour = QColor(100, 100, 100)
+ self._color = QColor(100, 100, 100)
self._iPx = SHARED.theme.baseIconHeight
iSz = SHARED.theme.baseIconSize
@@ -334,6 +338,7 @@ class _StatusPage(NFixedPage):
self.trCountNone = self.tr("Not in use")
self.trCountOne = self.tr("Used once")
self.trCountMore = self.tr("Used by {0} items")
+ self.trSelColor = self.tr("Select Colour")
# Title
self.pageTitle = NColourLabel(
@@ -370,15 +375,22 @@ class _StatusPage(NFixedPage):
self.editName.setPlaceholderText(self.tr("Select item to edit"))
self.editName.setEnabled(False)
- self.colPixmap = QPixmap(self._iPx, self._iPx)
- self.colPixmap.fill(QColor(100, 100, 100))
- self.colButton = QPushButton(QIcon(self.colPixmap), self.tr("Colour"), self)
- self.colButton.setIconSize(bSz)
+ self.colButton = QPushButton("", self)
self.colButton.setEnabled(False)
+ self.colButton.setIconSize(bSz)
+ self.colButton.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.MinimumExpanding)
self.colButton.clicked.connect(self._selectColour)
+ self._setColButton(self._color)
+
+ self.shapeList = NComboBox(self)
+ self.shapeList.setEnabled(False)
+ self.shapeList.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.MinimumExpanding)
+ for shape, label in nwLabels.STATUS_SHAPES.items():
+ self.shapeList.addItem(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)
# Assemble
@@ -390,13 +402,14 @@ class _StatusPage(NFixedPage):
self.listControls.addStretch(1)
self.editBox = QHBoxLayout()
- self.editBox.addWidget(self.editName)
- self.editBox.addWidget(self.colButton)
- self.editBox.addWidget(self.saveButton)
+ 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.mainBox = QVBoxLayout()
- self.mainBox.addWidget(self.listBox)
- self.mainBox.addLayout(self.editBox)
+ self.mainBox.addWidget(self.listBox, 1)
+ self.mainBox.addLayout(self.editBox, 0)
self.innerBox = QHBoxLayout()
self.innerBox.addLayout(self.mainBox)
@@ -442,16 +455,9 @@ class _StatusPage(NFixedPage):
@pyqtSlot()
def _selectColour(self) -> None:
"""Open a dialog to select the status icon colour."""
- if self._selColour is not None:
- newCol = QColorDialog.getColor(
- self._selColour, self, self.tr("Select Colour")
- )
- if newCol.isValid():
- self._selColour = newCol
- pixmap = QPixmap(self._iPx, self._iPx)
- pixmap.fill(newCol)
- self.colButton.setIcon(QIcon(pixmap))
- self.colButton.setIconSize(pixmap.rect().size())
+ if (color := QColorDialog.getColor(self._color, self, self.trSelColor)).isValid():
+ self._color = color
+ self._setColButton(color)
return
@pyqtSlot()
@@ -484,16 +490,20 @@ class _StatusPage(NFixedPage):
entry: StatusEntry = item.data(self.C_DATA, self.D_ENTRY)
name = simplified(self.editName.text())
- shape = nwStatusShape.SQUARE
- icon = NWStatus.createIcon(self._iPx, self._selColour, shape)
+ selected = self.shapeList.currentData()
+ shape = selected if isinstance(selected, nwStatusShape) else nwStatusShape.SQUARE
+ icon = NWStatus.createIcon(self._iPx, self._color, shape)
+
entry.name = name
+ entry.color = self._color
entry.shape = shape
- entry.color = self._selColour
entry.icon = icon
item.setText(self.C_LABEL, name)
item.setIcon(self.C_LABEL, icon)
+
self._changed = True
+
return
@pyqtSlot()
@@ -503,21 +513,28 @@ class _StatusPage(NFixedPage):
"""
if item := self._getSelectedItem():
entry: StatusEntry = item.data(self.C_DATA, self.D_ENTRY)
- self._selColour = entry.color
+ self._color = entry.color
+ self._setColButton(entry.color)
+
self.editName.setText(entry.name)
- self.colButton.setIcon(entry.icon)
self.editName.selectAll()
self.editName.setFocus()
+ self.shapeList.setCurrentData(entry.shape, nwStatusShape.SQUARE)
+
self.editName.setEnabled(True)
self.colButton.setEnabled(True)
+ self.shapeList.setEnabled(True)
self.saveButton.setEnabled(True)
+
else:
- self._selColour = QColor(100, 100, 100)
- icon = NWStatus.createIcon(self._iPx, self._selColour, nwStatusShape.SQUARE)
+ self._color = QColor(100, 100, 100)
+ self._setColButton(self._color)
self.editName.setText("")
- self.colButton.setIcon(icon)
+ self.shapeList.setCurrentIndex(0)
+
self.editName.setEnabled(False)
self.colButton.setEnabled(False)
+ self.shapeList.setEnabled(False)
self.saveButton.setEnabled(False)
return
@@ -564,6 +581,14 @@ class _StatusPage(NFixedPage):
else:
return self.trCountMore.format(count)
+ def _setColButton(self, color: QColor) -> None:
+ """Set the colour of the colour button."""
+ pixmap = QPixmap(self._iPx, self._iPx)
+ pixmap.fill(color)
+ self.colButton.setIcon(QIcon(pixmap))
+ self.colButton.setIconSize(pixmap.rect().size())
+ return
+
# END Class _StatusPage
diff --git a/novelwriter/extensions/modified.py b/novelwriter/extensions/modified.py
index 4eb50cff..b83a2662 100644
--- a/novelwriter/extensions/modified.py
+++ b/novelwriter/extensions/modified.py
@@ -25,6 +25,8 @@ along with this program. If not, see .
"""
from __future__ import annotations
+from enum import Enum
+
from PyQt5.QtCore import QSize, Qt
from PyQt5.QtGui import QWheelEvent
from PyQt5.QtWidgets import QComboBox, QDoubleSpinBox, QSpinBox, QToolButton, QWidget
@@ -46,7 +48,7 @@ class NComboBox(QComboBox):
event.ignore()
return
- def setCurrentData(self, data: str, default: str) -> None:
+ def setCurrentData(self, data: str | Enum, default: str | Enum) -> None:
"""Set the current index from data, with a fallback."""
idx = self.findData(data)
self.setCurrentIndex(self.findData(default) if idx < 0 else idx)
diff --git a/sample/nwProject.nwx b/sample/nwProject.nwx
index 24170a34..3f64c4c7 100644
--- a/sample/nwProject.nwx
+++ b/sample/nwProject.nwx
@@ -1,6 +1,6 @@
-
-
+
+
Sample Project
Jane Smith
@@ -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
From e47f0c5166785308083bc2a11631935d72739224 Mon Sep 17 00:00:00 2001
From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com>
Date: Wed, 10 Apr 2024 23:35:14 +0200
Subject: [PATCH 13/20] Update GUI test
---
.../test_dialogs/test_dlg_projectsettings.py | 138 +++++++++---------
1 file changed, 66 insertions(+), 72 deletions(-)
diff --git a/tests/test_dialogs/test_dlg_projectsettings.py b/tests/test_dialogs/test_dlg_projectsettings.py
index 9831ea1f..1ccdfab6 100644
--- a/tests/test_dialogs/test_dlg_projectsettings.py
+++ b/tests/test_dialogs/test_dlg_projectsettings.py
@@ -24,14 +24,13 @@ import pytest
from tools import C, buildTestProject
-from PyQt5.QtCore import Qt
from PyQt5.QtGui import QColor
from PyQt5.QtWidgets import QDialog, QAction, QColorDialog
from novelwriter import CONFIG, SHARED
from novelwriter.dialogs.editlabel import GuiEditLabel
from novelwriter.dialogs.projectsettings import GuiProjectSettings
-from novelwriter.enum import nwItemType
+from novelwriter.enum import nwItemType, nwStatusShape
from novelwriter.types import QtMouseLeft
KEY_DELAY = 1
@@ -169,8 +168,8 @@ def testDlgProjSettings_StatusImport(qtbot, monkeypatch, nwGUI, projPath, mockRn
nwGUI.rebuildTrees()
project.countStatus()
- assert [e["count"] for _, e in project.data.itemStatus.items()] == [2, 0, 2, 1]
- assert [e["count"] for _, e in project.data.itemImport.items()] == [3, 0, 2, 1]
+ assert [e.count for _, e in project.data.itemStatus.iterItems()] == [2, 0, 2, 1]
+ assert [e.count for _, e in project.data.itemImport.iterItems()] == [3, 0, 2, 1]
# Create Dialog
projSettings = GuiProjectSettings(nwGUI, GuiProjectSettings.PAGE_STATUS)
@@ -182,8 +181,8 @@ def testDlgProjSettings_StatusImport(qtbot, monkeypatch, nwGUI, projPath, mockRn
status = projSettings.statusPage
- assert status.wasChanged is False
- assert status.getNewList() == ([], [])
+ assert status.changed is False
+ assert status.getNewList() == []
assert status.listBox.topLevelItemCount() == 4
# Can't delete the first item (it's in use)
@@ -204,39 +203,38 @@ def testDlgProjSettings_StatusImport(qtbot, monkeypatch, nwGUI, projPath, mockRn
status.addButton.click()
status.listBox.setCurrentItem(status.listBox.topLevelItem(3))
status.editName.setText("Final")
+ status.shapeList.setCurrentData(nwStatusShape.CIRCLE, nwStatusShape.SQUARE)
status.colButton.click()
status.saveButton.click()
assert status.listBox.topLevelItemCount() == 4
- assert status.wasChanged is True
- assert status.getNewList() == (
- [
- {
- "key": C.sNew,
- "name": "New",
- "cols": (100, 100, 100)
- }, {
- "key": C.sDraft,
- "name": "Draft",
- "cols": (200, 150, 0)
- }, {
- "key": C.sFinished,
- "name": "Finished",
- "cols": (50, 200, 0)
- }, {
- "key": None,
- "name": "Final",
- "cols": (20, 30, 40)
- }
- ], [
- C.sNote # Deleted item
- ]
- )
+ assert status.changed is True
+ update = status.getNewList()
+
+ assert update[0][0] == C.sNew
+ assert update[0][1].name == "New"
+ assert update[0][1].color == QColor(100, 100, 100)
+ assert update[0][1].shape == nwStatusShape.SQUARE
+
+ assert update[1][0] == C.sDraft
+ assert update[1][1].name == "Draft"
+ assert update[1][1].color == QColor(200, 150, 0)
+ assert update[1][1].shape == nwStatusShape.SQUARE
+
+ assert update[2][0] == C.sFinished
+ assert update[2][1].name == "Finished"
+ assert update[2][1].color == QColor(50, 200, 0)
+ assert update[2][1].shape == nwStatusShape.SQUARE
+
+ 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].shape == nwStatusShape.CIRCLE
# Move items, none selected -> no change
status.listBox.clearSelection()
status._moveItem(1)
- assert [x["key"] for x in status.getNewList()[0]] == [
+ assert [x[0] for x in status.getNewList()] == [
C.sNew, C.sDraft, C.sFinished, None
]
@@ -244,7 +242,7 @@ def testDlgProjSettings_StatusImport(qtbot, monkeypatch, nwGUI, projPath, mockRn
status.listBox.clearSelection()
status.listBox.setCurrentItem(status.listBox.topLevelItem(0))
status._moveItem(-1)
- assert [x["key"] for x in status.getNewList()[0]] == [
+ assert [x[0] for x in status.getNewList()] == [
C.sNew, C.sDraft, C.sFinished, None
]
@@ -252,13 +250,13 @@ def testDlgProjSettings_StatusImport(qtbot, monkeypatch, nwGUI, projPath, mockRn
status.listBox.clearSelection()
status.listBox.setCurrentItem(status.listBox.topLevelItem(3))
status._moveItem(-1)
- assert [x["key"] for x in status.getNewList()[0]] == [
+ assert [x[0] for x in status.getNewList()] == [
C.sNew, C.sDraft, None, C.sFinished
]
# Move items, same selected, move down -> allowed
status._moveItem(1)
- assert [x["key"] for x in status.getNewList()[0]] == [
+ assert [x[0] for x in status.getNewList()] == [
C.sNew, C.sDraft, C.sFinished, None
]
@@ -280,53 +278,49 @@ def testDlgProjSettings_StatusImport(qtbot, monkeypatch, nwGUI, projPath, mockRn
qtbot.mouseClick(importance.addButton, QtMouseLeft)
importance.listBox.clearSelection()
importance.listBox.setCurrentItem(importance.listBox.topLevelItem(3))
- for _ in range(8):
- qtbot.keyClick(importance.editName, Qt.Key.Key_Backspace, delay=KEY_DELAY)
- for c in "Final":
- qtbot.keyClick(importance.editName, c, delay=KEY_DELAY)
+ importance.editName.setText("Final")
+ importance.shapeList.setCurrentData(nwStatusShape.TRIANGLE, nwStatusShape.SQUARE)
qtbot.mouseClick(importance.colButton, QtMouseLeft)
qtbot.mouseClick(importance.saveButton, QtMouseLeft)
assert importance.listBox.topLevelItemCount() == 4
- assert importance.wasChanged is True
- assert importance.getNewList() == (
- [
- {
- "key": C.iNew,
- "name": "New",
- "cols": (100, 100, 100)
- }, {
- "key": C.iMajor,
- "name": "Major",
- "cols": (200, 150, 0)
- }, {
- "key": C.iMain,
- "name": "Main",
- "cols": (50, 200, 0)
- }, {
- "key": None,
- "name": "Final",
- "cols": (20, 30, 40)
- }
- ], [
- C.iMinor # Deleted item
- ]
- )
+ assert importance.changed is True
+ update = importance.getNewList()
+
+ assert update[0][0] == C.iNew
+ assert update[0][1].name == "New"
+ assert update[0][1].color == QColor(100, 100, 100)
+ 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(200, 150, 0)
+ assert update[1][1].shape == nwStatusShape.SQUARE
+
+ assert update[2][0] == C.iMain
+ assert update[2][1].name == "Main"
+ assert update[2][1].color == QColor(50, 200, 0)
+ assert update[2][1].shape == nwStatusShape.SQUARE
+
+ 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].shape == nwStatusShape.TRIANGLE
# Check Project
projSettings._doSave()
- statusItems = dict(project.data.itemStatus.items())
- assert statusItems[C.sNew]["name"] == "New"
- assert statusItems[C.sDraft]["name"] == "Draft"
- assert statusItems[C.sFinished]["name"] == "Finished"
- assert statusItems["s000013"]["name"] == "Final"
+ statusItems = dict(project.data.itemStatus.iterItems())
+ assert statusItems[C.sNew].name == "New"
+ assert statusItems[C.sDraft].name == "Draft"
+ assert statusItems[C.sFinished].name == "Finished"
+ assert statusItems["s000013"].name == "Final"
- importItems = dict(project.data.itemImport.items())
- assert importItems[C.iNew]["name"] == "New"
- assert importItems[C.iMajor]["name"] == "Major"
- assert importItems[C.iMain]["name"] == "Main"
- assert importItems["i000014"]["name"] == "Final"
+ importItems = dict(project.data.itemImport.iterItems())
+ assert importItems[C.iNew].name == "New"
+ assert importItems[C.iMajor].name == "Major"
+ assert importItems[C.iMain].name == "Main"
+ assert importItems["i000014"].name == "Final"
# qtbot.stop()
From f4decae77f0971d9b4cc73def5a6c6c18d81154c Mon Sep 17 00:00:00 2001
From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com>
Date: Wed, 10 Apr 2024 23:44:43 +0200
Subject: [PATCH 14/20] Simplify status class counter reset
---
novelwriter/core/status.py | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/novelwriter/core/status.py b/novelwriter/core/status.py
index 8909cc98..cde8bf02 100644
--- a/novelwriter/core/status.py
+++ b/novelwriter/core/status.py
@@ -153,8 +153,8 @@ class NWStatus:
def resetCounts(self) -> None:
"""Clear the counts of references to the status entries."""
- for key in self._store:
- self._store[key].count = 0
+ for entry in self._store.values():
+ entry.count = 0
return
def increment(self, key: str | None) -> None:
From 6cf83bb3814f18bc00daa869646ab5641d428846 Mon Sep 17 00:00:00 2001
From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com>
Date: Thu, 11 Apr 2024 11:17:03 +0200
Subject: [PATCH 15/20] Add more status icon shapes
---
novelwriter/constants.py | 26 ++++++---
novelwriter/core/status.py | 82 ++++++++++++++++++++++-------
novelwriter/enum.py | 26 ++++++---
tests/test_core/test_core_status.py | 38 ++++++++++---
4 files changed, 132 insertions(+), 40 deletions(-)
diff --git a/novelwriter/constants.py b/novelwriter/constants.py
index 04eea148..3c66f087 100644
--- a/novelwriter/constants.py
+++ b/novelwriter/constants.py
@@ -269,13 +269,25 @@ class nwLabels:
nwBuildFmt.J_NWD: ".json",
}
STATUS_SHAPES = {
- nwStatusShape.SQUARE: QT_TRANSLATE_NOOP("Constant", "Square"),
- nwStatusShape.CIRCLE: QT_TRANSLATE_NOOP("Constant", "Circle"),
- nwStatusShape.TRIANGLE: QT_TRANSLATE_NOOP("Constant", "Triangle"),
- nwStatusShape.DIAMOND: QT_TRANSLATE_NOOP("Constant", "Diamond"),
- nwStatusShape.PENTAGON: QT_TRANSLATE_NOOP("Constant", "Pentagon"),
- nwStatusShape.STAR: QT_TRANSLATE_NOOP("Constant", "Star"),
- nwStatusShape.PACMAN: QT_TRANSLATE_NOOP("Constant", "Pacman"),
+ 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.STAR: QT_TRANSLATE_NOOP("Constant", "Star"),
+ nwStatusShape.PACMAN: QT_TRANSLATE_NOOP("Constant", "Pacman"),
+ 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"),
+ nwStatusShape.BARS_4: QT_TRANSLATE_NOOP("Constant", "4 Bars"),
+ nwStatusShape.BLOCK_1: QT_TRANSLATE_NOOP("Constant", "1 Block"),
+ nwStatusShape.BLOCK_2: QT_TRANSLATE_NOOP("Constant", "2 Blocks"),
+ nwStatusShape.BLOCK_3: QT_TRANSLATE_NOOP("Constant", "3 Blocks"),
+ nwStatusShape.BLOCK_4: QT_TRANSLATE_NOOP("Constant", "4 Blocks"),
}
FILE_FILTERS = {
"*.txt": QT_TRANSLATE_NOOP("Constant", "Text files"),
diff --git a/novelwriter/core/status.py b/novelwriter/core/status.py
index cde8bf02..c2899ad7 100644
--- a/novelwriter/core/status.py
+++ b/novelwriter/core/status.py
@@ -244,48 +244,92 @@ class _ShapeCache:
if shape in self._cache:
return self._cache[shape]
- def circ(r: float, a: float, x: float, y: float) -> QPointF:
+ 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(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.CIRCLE_Q:
+ path.moveTo(24.0, 24.0)
+ path.arcTo(2.0, 2.0, 44.0, 44.0, 0.0, 90.0)
+ elif shape == nwStatusShape.CIRCLE_H:
+ path.moveTo(24.0, 24.0)
+ path.arcTo(2.0, 2.0, 44.0, 44.0, -90.0, 180.0)
+ elif shape == nwStatusShape.CIRCLE_T:
+ path.moveTo(24.0, 24.0)
+ 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([
- circ(23.0, 0.0, 24.0, 26.0),
- circ(23.0, 120.0, 24.0, 26.0),
- circ(23.0, 240.0, 24.0, 26.0),
+ 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([
- circ(22.0, 0.0, 24.0, 24.0),
- circ(20.0, 90.0, 24.0, 24.0),
- circ(22.0, 180.0, 24.0, 24.0),
- circ(20.0, 270.0, 24.0, 24.0),
+ 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([
- circ(23.0, 0.0, 24.0, 24.5),
- circ(23.0, 72.0, 24.0, 24.5),
- circ(23.0, 144.0, 24.0, 24.5),
- circ(23.0, 216.0, 24.0, 24.5),
- circ(23.0, 288.0, 24.0, 24.5),
+ 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([
- circ(24.0, 0.0, 24.0, 24.5),
- circ(24.0, 144.0, 24.0, 24.5),
- circ(24.0, 288.0, 24.0, 24.5),
- circ(24.0, 72.0, 24.0, 24.5),
- circ(24.0, 216.0, 24.0, 24.5),
+ 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:
+ path.addRoundedRect(2.0, 2.0, 8.0, 44.0, 4.0, 4.0)
+ path.addRoundedRect(14.0, 2.0, 8.0, 44.0, 4.0, 4.0)
+ elif shape == nwStatusShape.BARS_3:
+ path.addRoundedRect(2.0, 2.0, 8.0, 44.0, 4.0, 4.0)
+ path.addRoundedRect(14.0, 2.0, 8.0, 44.0, 4.0, 4.0)
+ path.addRoundedRect(26.0, 2.0, 8.0, 44.0, 4.0, 4.0)
+ elif shape == nwStatusShape.BARS_4:
+ path.addRoundedRect(2.0, 2.0, 8.0, 44.0, 4.0, 4.0)
+ path.addRoundedRect(14.0, 2.0, 8.0, 44.0, 4.0, 4.0)
+ path.addRoundedRect(26.0, 2.0, 8.0, 44.0, 4.0, 4.0)
+ path.addRoundedRect(38.0, 2.0, 8.0, 44.0, 4.0, 4.0)
+ elif shape == nwStatusShape.BLOCK_1:
+ path.addRoundedRect(2.0, 2.0, 20.0, 20.0, 4.0, 4.0)
+ elif shape == nwStatusShape.BLOCK_2:
+ path.addRoundedRect(2.0, 2.0, 20.0, 20.0, 4.0, 4.0)
+ path.addRoundedRect(24.0, 2.0, 20.0, 20.0, 4.0, 4.0)
+ elif shape == nwStatusShape.BLOCK_3:
+ path.addRoundedRect(2.0, 2.0, 20.0, 20.0, 4.0, 4.0)
+ path.addRoundedRect(2.0, 24.0, 20.0, 20.0, 4.0, 4.0)
+ path.addRoundedRect(24.0, 2.0, 20.0, 20.0, 4.0, 4.0)
+ elif shape == nwStatusShape.BLOCK_4:
+ path.addRoundedRect(2.0, 2.0, 20.0, 20.0, 4.0, 4.0)
+ path.addRoundedRect(2.0, 24.0, 20.0, 20.0, 4.0, 4.0)
+ path.addRoundedRect(24.0, 2.0, 20.0, 20.0, 4.0, 4.0)
+ path.addRoundedRect(24.0, 24.0, 20.0, 20.0, 4.0, 4.0)
self._cache[shape] = path
diff --git a/novelwriter/enum.py b/novelwriter/enum.py
index c0f88ba7..e5ee3dd5 100644
--- a/novelwriter/enum.py
+++ b/novelwriter/enum.py
@@ -209,12 +209,24 @@ class nwBuildFmt(Enum):
class nwStatusShape(Enum):
- SQUARE = 0
- CIRCLE = 1
- TRIANGLE = 2
- DIAMOND = 3
- PENTAGON = 4
- STAR = 5
- PACMAN = 6
+ 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
# END Enum nwStatusShape
diff --git a/tests/test_core/test_core_status.py b/tests/test_core/test_core_status.py
index 3c2b8edd..4ca9e29b 100644
--- a/tests/test_core/test_core_status.py
+++ b/tests/test_core/test_core_status.py
@@ -374,21 +374,45 @@ def testCoreStatus_ShapeCache():
shapes = _ShapeCache()
# Generate all shapes
- square = shapes.getShape(nwStatusShape.SQUARE)
- circle = shapes.getShape(nwStatusShape.CIRCLE)
- triangle = shapes.getShape(nwStatusShape.TRIANGLE)
- diamond = shapes.getShape(nwStatusShape.DIAMOND)
- pentagon = shapes.getShape(nwStatusShape.PENTAGON)
- star = shapes.getShape(nwStatusShape.STAR)
- pacman = shapes.getShape(nwStatusShape.PACMAN)
+ square = shapes.getShape(nwStatusShape.SQUARE)
+ circleQ = shapes.getShape(nwStatusShape.CIRCLE_Q)
+ circleH = shapes.getShape(nwStatusShape.CIRCLE_H)
+ circleT = shapes.getShape(nwStatusShape.CIRCLE_T)
+ circle = shapes.getShape(nwStatusShape.CIRCLE)
+ triangle = shapes.getShape(nwStatusShape.TRIANGLE)
+ nabla = shapes.getShape(nwStatusShape.NABLA)
+ diamond = shapes.getShape(nwStatusShape.DIAMOND)
+ pentagon = shapes.getShape(nwStatusShape.PENTAGON)
+ star = shapes.getShape(nwStatusShape.STAR)
+ pacman = shapes.getShape(nwStatusShape.PACMAN)
+ bars1 = shapes.getShape(nwStatusShape.BARS_1)
+ bars2 = shapes.getShape(nwStatusShape.BARS_2)
+ bars3 = shapes.getShape(nwStatusShape.BARS_3)
+ bars4 = shapes.getShape(nwStatusShape.BARS_4)
+ block1 = shapes.getShape(nwStatusShape.BLOCK_1)
+ block2 = shapes.getShape(nwStatusShape.BLOCK_2)
+ block3 = shapes.getShape(nwStatusShape.BLOCK_3)
+ block4 = shapes.getShape(nwStatusShape.BLOCK_4)
# Request again should return from cache
assert shapes.getShape(nwStatusShape.SQUARE) is square
+ assert shapes.getShape(nwStatusShape.CIRCLE_Q) is circleQ
+ assert shapes.getShape(nwStatusShape.CIRCLE_H) is circleH
+ assert shapes.getShape(nwStatusShape.CIRCLE_T) is circleT
assert shapes.getShape(nwStatusShape.CIRCLE) is circle
assert shapes.getShape(nwStatusShape.TRIANGLE) is triangle
+ 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.STAR) is star
assert shapes.getShape(nwStatusShape.PACMAN) is pacman
+ assert shapes.getShape(nwStatusShape.BARS_1) is bars1
+ assert shapes.getShape(nwStatusShape.BARS_2) is bars2
+ assert shapes.getShape(nwStatusShape.BARS_3) is bars3
+ assert shapes.getShape(nwStatusShape.BARS_4) is bars4
+ assert shapes.getShape(nwStatusShape.BLOCK_1) is block1
+ assert shapes.getShape(nwStatusShape.BLOCK_2) is block2
+ assert shapes.getShape(nwStatusShape.BLOCK_3) is block3
+ assert shapes.getShape(nwStatusShape.BLOCK_4) is block4
# END Test testCoreStatus_ShapeCache
From f67d2c5a4c1360cee2c5e4caec05049662ce8b09 Mon Sep 17 00:00:00 2001
From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com>
Date: Thu, 11 Apr 2024 19:52:54 +0200
Subject: [PATCH 16/20] Add pre-calculation of status icons
---
novelwriter/constants.py | 9 +-
novelwriter/core/status.py | 120 +++++++++---------
novelwriter/dialogs/projectsettings.py | 26 ++--
novelwriter/enum.py | 37 +++---
sample/nwProject.nwx | 37 +++---
tests/test_core/test_core_status.py | 5 +-
.../test_dialogs/test_dlg_projectsettings.py | 6 +-
7 files changed, 120 insertions(+), 120 deletions(-)
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
From 21a5dbbb52cb3ec2f4b7f4a26566226c5235580f Mon Sep 17 00:00:00 2001
From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com>
Date: Thu, 11 Apr 2024 21:35:25 +0200
Subject: [PATCH 17/20] Redesign status item controls, and clean up
auto-replace methods
---
novelwriter/constants.py | 48 +++----
novelwriter/dialogs/projectsettings.py | 168 ++++++++++++++-----------
2 files changed, 119 insertions(+), 97 deletions(-)
diff --git a/novelwriter/constants.py b/novelwriter/constants.py
index 6ed6080f..48487d77 100644
--- a/novelwriter/constants.py
+++ b/novelwriter/constants.py
@@ -268,27 +268,33 @@ class nwLabels:
nwBuildFmt.J_HTML: ".json",
nwBuildFmt.J_NWD: ".json",
}
- STATUS_SHAPES = {
- nwStatusShape.SQUARE: QT_TRANSLATE_NOOP("Constant", "Square"),
- 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"),
- nwStatusShape.BARS_4: QT_TRANSLATE_NOOP("Constant", "4 Bars"),
- nwStatusShape.BLOCK_1: QT_TRANSLATE_NOOP("Constant", "1 Block"),
- nwStatusShape.BLOCK_2: QT_TRANSLATE_NOOP("Constant", "2 Blocks"),
- nwStatusShape.BLOCK_3: QT_TRANSLATE_NOOP("Constant", "3 Blocks"),
- nwStatusShape.BLOCK_4: QT_TRANSLATE_NOOP("Constant", "4 Blocks"),
+ SHAPES_PLAIN = {
+ nwStatusShape.SQUARE: QT_TRANSLATE_NOOP("Constant", "Square"),
+ 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"),
+ }
+ SHAPES_CIRCLE = {
+ nwStatusShape.CIRCLE_Q: QT_TRANSLATE_NOOP("Constant", "1/4 Circle"),
+ nwStatusShape.CIRCLE_H: QT_TRANSLATE_NOOP("Constant", "Half Circle"),
+ nwStatusShape.CIRCLE_T: QT_TRANSLATE_NOOP("Constant", "3/4 Circle"),
+ nwStatusShape.CIRCLE: QT_TRANSLATE_NOOP("Constant", "Full Circle"),
+ }
+ SHAPES_BARS = {
+ 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"),
+ nwStatusShape.BARS_4: QT_TRANSLATE_NOOP("Constant", "4 Bars"),
+ }
+ SHAPES_BLOCKS = {
+ nwStatusShape.BLOCK_1: QT_TRANSLATE_NOOP("Constant", "1 Block"),
+ nwStatusShape.BLOCK_2: QT_TRANSLATE_NOOP("Constant", "2 Blocks"),
+ nwStatusShape.BLOCK_3: QT_TRANSLATE_NOOP("Constant", "3 Blocks"),
+ nwStatusShape.BLOCK_4: QT_TRANSLATE_NOOP("Constant", "4 Blocks"),
}
FILE_FILTERS = {
"*.txt": QT_TRANSLATE_NOOP("Constant", "Text files"),
diff --git a/novelwriter/dialogs/projectsettings.py b/novelwriter/dialogs/projectsettings.py
index 67f05968..d6ab8b65 100644
--- a/novelwriter/dialogs/projectsettings.py
+++ b/novelwriter/dialogs/projectsettings.py
@@ -27,10 +27,10 @@ from __future__ import annotations
import logging
from PyQt5.QtCore import Qt, pyqtSignal, pyqtSlot
-from PyQt5.QtGui import QCloseEvent, QColor, QIcon, QPixmap
+from PyQt5.QtGui import QCloseEvent, QColor
from PyQt5.QtWidgets import (
QApplication, QColorDialog, QDialog, QDialogButtonBox, QHBoxLayout,
- QLineEdit, QPushButton, QSizePolicy, QStackedWidget, QTreeWidget,
+ QLineEdit, QMenu, QSizePolicy, QStackedWidget, QToolButton, QTreeWidget,
QTreeWidgetItem, QVBoxLayout, QWidget
)
@@ -329,10 +329,12 @@ class _StatusPage(NFixedPage):
self._changed = False
self._color = QColor(100, 100, 100)
+ self._shape = nwStatusShape.SQUARE
+ self._icons = {}
self._iPx = SHARED.theme.baseIconHeight
iSz = SHARED.theme.baseIconSize
- bSz = SHARED.theme.buttonIconSize
+ bPd = CONFIG.pxInt(4)
iColor = self.palette().text().color()
@@ -351,9 +353,9 @@ class _StatusPage(NFixedPage):
# List Box
self.listBox = QTreeWidget(self)
self.listBox.setHeaderLabels([self.tr("Label"), self.tr("Usage")])
- self.listBox.itemSelectionChanged.connect(self._selectedItem)
self.listBox.setColumnWidth(self.C_LABEL, wCol0)
self.listBox.setIndentation(0)
+ self.listBox.itemSelectionChanged.connect(self._selectedItem)
for key, entry in status.iterItems():
self._addItem(key, StatusEntry.duplicate(entry))
@@ -377,24 +379,42 @@ class _StatusPage(NFixedPage):
self.editName.setPlaceholderText(self.tr("Select item to edit"))
self.editName.setEnabled(False)
- self.colButton = QPushButton("", self)
- self.colButton.setEnabled(False)
- self.colButton.setIconSize(bSz)
- self.colButton.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.MinimumExpanding)
- self.colButton.clicked.connect(self._selectColour)
- self._setColButton(self._color)
+ buttonStyle = (
+ f"QToolButton {{padding: 0 {bPd}px;}} "
+ "QToolButton::menu-indicator {image: none;}"
+ )
- 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():
- icon = NWStatus.createIcon(self._iPx, iColor, shape)
- self.shapeList.addItem(icon, label, shape)
+ self.colorButton = NIconToolButton(self, iSz)
+ self.colorButton.setToolTip(self.tr("Colour"))
+ self.colorButton.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.MinimumExpanding)
+ self.colorButton.setStyleSheet(buttonStyle)
+ self.colorButton.setEnabled(False)
+ self.colorButton.clicked.connect(self._selectColour)
- self.applyButton = QPushButton(self.tr("Apply"), self)
- self.applyButton.setEnabled(False)
+ def buildMenu(menu: QMenu, items: dict[nwStatusShape, str]) -> None:
+ for shape, label in items.items():
+ icon = NWStatus.createIcon(self._iPx, iColor, shape)
+ action = menu.addAction(icon, label)
+ action.triggered.connect(lambda _, shape=shape: self._selectShape(shape))
+ self._icons[shape] = icon
+
+ self.shapeMenu = QMenu(self)
+ buildMenu(self.shapeMenu, nwLabels.SHAPES_PLAIN)
+ buildMenu(self.shapeMenu.addMenu(self.tr("Circles ...")), nwLabels.SHAPES_CIRCLE)
+ buildMenu(self.shapeMenu.addMenu(self.tr("Bars ...")), nwLabels.SHAPES_BARS)
+ buildMenu(self.shapeMenu.addMenu(self.tr("Blocks ...")), nwLabels.SHAPES_BLOCKS)
+
+ self.shapeButton = NIconToolButton(self, iSz)
+ self.shapeButton.setMenu(self.shapeMenu)
+ self.shapeButton.setToolTip(self.tr("Shape"))
+ self.shapeButton.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.MinimumExpanding)
+ self.shapeButton.setStyleSheet(buttonStyle)
+ self.shapeButton.setEnabled(False)
+
+ self.applyButton = QToolButton(self)
+ self.applyButton.setText(self.tr("Apply"))
self.applyButton.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.MinimumExpanding)
+ self.applyButton.setEnabled(False)
self.applyButton.clicked.connect(self._saveItem)
# Assemble
@@ -407,8 +427,8 @@ class _StatusPage(NFixedPage):
self.editBox = QHBoxLayout()
self.editBox.addWidget(self.editName, 1)
- self.editBox.addWidget(self.colButton, 0)
- self.editBox.addWidget(self.shapeList, 0)
+ self.editBox.addWidget(self.colorButton, 0)
+ self.editBox.addWidget(self.shapeButton, 0)
self.editBox.addWidget(self.applyButton, 0)
self.mainBox = QVBoxLayout()
@@ -416,14 +436,15 @@ class _StatusPage(NFixedPage):
self.mainBox.addLayout(self.editBox, 0)
self.innerBox = QHBoxLayout()
- self.innerBox.addLayout(self.mainBox)
- self.innerBox.addLayout(self.listControls)
+ self.innerBox.addLayout(self.mainBox, 1)
+ self.innerBox.addLayout(self.listControls, 0)
self.outerBox = QVBoxLayout()
- self.outerBox.addWidget(self.pageTitle)
- self.outerBox.addLayout(self.innerBox)
+ self.outerBox.addWidget(self.pageTitle, 0)
+ self.outerBox.addLayout(self.innerBox, 1)
self.setCentralLayout(self.outerBox)
+ self._setButtonIcons()
return
@@ -461,7 +482,7 @@ class _StatusPage(NFixedPage):
"""Open a dialog to select the status icon colour."""
if (color := QColorDialog.getColor(self._color, self, self.trSelColor)).isValid():
self._color = color
- self._setColButton(color)
+ self._setButtonIcons()
return
@pyqtSlot()
@@ -494,13 +515,11 @@ class _StatusPage(NFixedPage):
entry: StatusEntry = item.data(self.C_DATA, self.D_ENTRY)
name = simplified(self.editName.text())
- selected = self.shapeList.currentData()
- shape = selected if isinstance(selected, nwStatusShape) else nwStatusShape.SQUARE
- icon = NWStatus.createIcon(self._iPx, self._color, shape)
+ icon = NWStatus.createIcon(self._iPx, self._color, self._shape)
entry.name = name
entry.color = self._color
- entry.shape = shape
+ entry.shape = self._shape
entry.icon = icon
item.setText(self.C_LABEL, name)
@@ -518,27 +537,27 @@ class _StatusPage(NFixedPage):
if item := self._getSelectedItem():
entry: StatusEntry = item.data(self.C_DATA, self.D_ENTRY)
self._color = entry.color
- self._setColButton(entry.color)
+ self._shape = entry.shape
+ self._setButtonIcons()
self.editName.setText(entry.name)
self.editName.selectAll()
self.editName.setFocus()
- self.shapeList.setCurrentData(entry.shape, nwStatusShape.SQUARE)
self.editName.setEnabled(True)
- self.colButton.setEnabled(True)
- self.shapeList.setEnabled(True)
+ self.colorButton.setEnabled(True)
+ self.shapeButton.setEnabled(True)
self.applyButton.setEnabled(True)
else:
self._color = QColor(100, 100, 100)
- self._setColButton(self._color)
+ self._shape = nwStatusShape.SQUARE
+ self._setButtonIcons()
self.editName.setText("")
- self.shapeList.setCurrentIndex(0)
self.editName.setEnabled(False)
- self.colButton.setEnabled(False)
- self.shapeList.setEnabled(False)
+ self.colorButton.setEnabled(False)
+ self.shapeButton.setEnabled(False)
self.applyButton.setEnabled(False)
return
@@ -546,6 +565,12 @@ class _StatusPage(NFixedPage):
# Internal Functions
##
+ def _selectShape(self, shape: nwStatusShape) -> None:
+ """Set the current shape."""
+ self._shape = shape
+ self._setButtonIcons()
+ return
+
def _addItem(self, key: str | None, entry: StatusEntry) -> None:
"""Add a status item to the list."""
item = QTreeWidgetItem()
@@ -585,12 +610,11 @@ class _StatusPage(NFixedPage):
else:
return self.trCountMore.format(count)
- def _setColButton(self, color: QColor) -> None:
+ def _setButtonIcons(self) -> None:
"""Set the colour of the colour button."""
- pixmap = QPixmap(self._iPx, self._iPx)
- pixmap.fill(color)
- self.colButton.setIcon(QIcon(pixmap))
- self.colButton.setIconSize(pixmap.rect().size())
+ icon = NWStatus.createIcon(self._iPx, self._color, nwStatusShape.SQUARE)
+ self.colorButton.setIcon(icon)
+ self.shapeButton.setIcon(self._icons[self._shape])
return
# END Class _StatusPage
@@ -598,8 +622,8 @@ class _StatusPage(NFixedPage):
class _ReplacePage(NFixedPage):
- COL_KEY = 0
- COL_REPL = 1
+ C_KEY = 0
+ C_REPL = 1
def __init__(self, parent: QWidget) -> None:
super().__init__(parent=parent)
@@ -621,7 +645,7 @@ class _ReplacePage(NFixedPage):
# List Box
self.listBox = QTreeWidget(self)
self.listBox.setHeaderLabels([self.tr("Keyword"), self.tr("Replace With")])
- self.listBox.setColumnWidth(self.COL_KEY, wCol0)
+ self.listBox.setColumnWidth(self.C_KEY, wCol0)
self.listBox.setIndentation(0)
self.listBox.itemSelectionChanged.connect(self._selectedItem)
@@ -629,7 +653,7 @@ class _ReplacePage(NFixedPage):
newItem = QTreeWidgetItem(["<%s>" % aKey, aVal])
self.listBox.addTopLevelItem(newItem)
- self.listBox.sortByColumn(self.COL_KEY, Qt.SortOrder.AscendingOrder)
+ self.listBox.sortByColumn(self.C_KEY, Qt.SortOrder.AscendingOrder)
self.listBox.setSortingEnabled(True)
# List Controls
@@ -649,7 +673,9 @@ class _ReplacePage(NFixedPage):
self.editValue.setEnabled(False)
self.editValue.setMaxLength(80)
- self.applyButton = QPushButton(self.tr("Apply"), self)
+ self.applyButton = QToolButton(self)
+ self.applyButton.setText(self.tr("Apply"))
+ self.applyButton.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.MinimumExpanding)
self.applyButton.clicked.connect(self._saveEntry)
# Assemble
@@ -664,8 +690,8 @@ class _ReplacePage(NFixedPage):
self.editBox.addWidget(self.applyButton, 0)
self.mainBox = QVBoxLayout()
- self.mainBox.addWidget(self.listBox)
- self.mainBox.addLayout(self.editBox)
+ self.mainBox.addWidget(self.listBox, 1)
+ self.mainBox.addLayout(self.editBox, 0)
self.innerBox = QHBoxLayout()
self.innerBox.addLayout(self.mainBox)
@@ -692,11 +718,9 @@ class _ReplacePage(NFixedPage):
"""Extract the list from the widget."""
new = {}
for n in range(self.listBox.topLevelItemCount()):
- if tItem := self.listBox.topLevelItem(n):
- aKey = self._stripNotAllowed(tItem.text(0))
- aVal = tItem.text(1)
- if len(aKey) > 0:
- new[aKey] = aVal
+ if item := self.listBox.topLevelItem(n):
+ if key := self._stripNotAllowed(item.text(self.C_KEY)):
+ new[key] = item.text(self.C_REPL)
return new
def columnWidth(self) -> int:
@@ -712,11 +736,9 @@ class _ReplacePage(NFixedPage):
"""Extract the details from the selected item and populate the
edit form.
"""
- if selItem := self._getSelectedItem():
- editKey = self._stripNotAllowed(selItem.text(0))
- editVal = selItem.text(1)
- self.editKey.setText(editKey)
- self.editValue.setText(editVal)
+ if item := self._getSelectedItem():
+ self.editKey.setText(self._stripNotAllowed(item.text(self.C_KEY)))
+ self.editValue.setText(item.text(self.C_REPL))
self.editKey.setEnabled(True)
self.editValue.setEnabled(True)
self.editKey.selectAll()
@@ -726,33 +748,27 @@ class _ReplacePage(NFixedPage):
@pyqtSlot()
def _saveEntry(self) -> None:
"""Save the form data into the list widget."""
- if selItem := self._getSelectedItem():
- newKey = self.editKey.text()
- newVal = self.editValue.text()
- saveKey = self._stripNotAllowed(newKey)
- if len(saveKey) > 0 and len(newVal) > 0:
- selItem.setText(self.COL_KEY, "<%s>" % saveKey)
- selItem.setText(self.COL_REPL, newVal)
- self.editKey.clear()
- self.editValue.clear()
- self.editKey.setEnabled(False)
- self.editValue.setEnabled(False)
- self.listBox.clearSelection()
+ if item := self._getSelectedItem():
+ key = self._stripNotAllowed(self.editKey.text())
+ value = self.editValue.text()
+ if key and value:
+ item.setText(self.C_KEY, f"<{key}>")
+ item.setText(self.C_REPL, value)
self._changed = True
return
@pyqtSlot()
def _addEntry(self) -> None:
"""Add a new list entry."""
- saveKey = "" % (self.listBox.topLevelItemCount() + 1)
- self.listBox.addTopLevelItem(QTreeWidgetItem([saveKey, ""]))
+ key = f""
+ self.listBox.addTopLevelItem(QTreeWidgetItem([key, ""]))
return
@pyqtSlot()
def _delEntry(self) -> None:
"""Delete the selected entry."""
- if selItem := self._getSelectedItem():
- self.listBox.takeTopLevelItem(self.listBox.indexOfTopLevelItem(selItem))
+ if item := self._getSelectedItem():
+ self.listBox.takeTopLevelItem(self.listBox.indexOfTopLevelItem(item))
self._changed = True
return
From e10f3a19d3a6af6d1144260f66ff1ebc690c0fcd Mon Sep 17 00:00:00 2001
From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com>
Date: Thu, 11 Apr 2024 21:36:48 +0200
Subject: [PATCH 18/20] Add size policy types
---
novelwriter/dialogs/projectsettings.py | 15 +++++++++------
novelwriter/extensions/circularprogress.py | 7 ++++---
novelwriter/extensions/pagedsidebar.py | 13 ++++++++-----
novelwriter/extensions/switch.py | 6 +++---
novelwriter/extensions/switchbox.py | 9 ++++++---
novelwriter/gui/noveltree.py | 11 +++++++----
novelwriter/gui/outline.py | 13 ++++++-------
novelwriter/gui/projtree.py | 11 +++++++----
novelwriter/tools/manuscript.py | 16 +++++++---------
novelwriter/types.py | 10 +++++++++-
10 files changed, 66 insertions(+), 45 deletions(-)
diff --git a/novelwriter/dialogs/projectsettings.py b/novelwriter/dialogs/projectsettings.py
index d6ab8b65..78ebd143 100644
--- a/novelwriter/dialogs/projectsettings.py
+++ b/novelwriter/dialogs/projectsettings.py
@@ -30,7 +30,7 @@ from PyQt5.QtCore import Qt, pyqtSignal, pyqtSlot
from PyQt5.QtGui import QCloseEvent, QColor
from PyQt5.QtWidgets import (
QApplication, QColorDialog, QDialog, QDialogButtonBox, QHBoxLayout,
- QLineEdit, QMenu, QSizePolicy, QStackedWidget, QToolButton, QTreeWidget,
+ QLineEdit, QMenu, QStackedWidget, QToolButton, QTreeWidget,
QTreeWidgetItem, QVBoxLayout, QWidget
)
@@ -43,7 +43,10 @@ from novelwriter.extensions.configlayout import NColourLabel, NFixedPage, NScrol
from novelwriter.extensions.modified import NComboBox, NIconToolButton
from novelwriter.extensions.pagedsidebar import NPagedSideBar
from novelwriter.extensions.switch import NSwitch
-from novelwriter.types import QtDialogCancel, QtDialogSave, QtUserRole
+from novelwriter.types import (
+ QtDialogCancel, QtDialogSave, QtSizeMinimum, QtSizeMinimumExpanding,
+ QtUserRole
+)
logger = logging.getLogger(__name__)
@@ -386,7 +389,7 @@ class _StatusPage(NFixedPage):
self.colorButton = NIconToolButton(self, iSz)
self.colorButton.setToolTip(self.tr("Colour"))
- self.colorButton.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.MinimumExpanding)
+ self.colorButton.setSizePolicy(QtSizeMinimum, QtSizeMinimumExpanding)
self.colorButton.setStyleSheet(buttonStyle)
self.colorButton.setEnabled(False)
self.colorButton.clicked.connect(self._selectColour)
@@ -407,13 +410,13 @@ class _StatusPage(NFixedPage):
self.shapeButton = NIconToolButton(self, iSz)
self.shapeButton.setMenu(self.shapeMenu)
self.shapeButton.setToolTip(self.tr("Shape"))
- self.shapeButton.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.MinimumExpanding)
+ self.shapeButton.setSizePolicy(QtSizeMinimum, QtSizeMinimumExpanding)
self.shapeButton.setStyleSheet(buttonStyle)
self.shapeButton.setEnabled(False)
self.applyButton = QToolButton(self)
self.applyButton.setText(self.tr("Apply"))
- self.applyButton.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.MinimumExpanding)
+ self.applyButton.setSizePolicy(QtSizeMinimum, QtSizeMinimumExpanding)
self.applyButton.setEnabled(False)
self.applyButton.clicked.connect(self._saveItem)
@@ -675,7 +678,7 @@ class _ReplacePage(NFixedPage):
self.applyButton = QToolButton(self)
self.applyButton.setText(self.tr("Apply"))
- self.applyButton.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.MinimumExpanding)
+ self.applyButton.setSizePolicy(QtSizeMinimum, QtSizeMinimumExpanding)
self.applyButton.clicked.connect(self._saveEntry)
# Assemble
diff --git a/novelwriter/extensions/circularprogress.py b/novelwriter/extensions/circularprogress.py
index bbc2f64a..23ca7d3d 100644
--- a/novelwriter/extensions/circularprogress.py
+++ b/novelwriter/extensions/circularprogress.py
@@ -27,10 +27,11 @@ from math import ceil
from PyQt5.QtCore import QRect
from PyQt5.QtGui import QBrush, QColor, QPaintEvent, QPainter, QPen
-from PyQt5.QtWidgets import QProgressBar, QSizePolicy, QWidget
+from PyQt5.QtWidgets import QProgressBar, QWidget
from novelwriter.types import (
- QtPaintAnitAlias, QtAlignCenter, QtRoundCap, QtSolidLine, QtTransparent
+ QtPaintAnitAlias, QtAlignCenter, QtRoundCap, QtSizeFixed, QtSolidLine,
+ QtTransparent
)
@@ -59,7 +60,7 @@ class NProgressCircle(QProgressBar):
bar=self.palette().highlight().color(),
text=self.palette().text().color()
)
- self.setSizePolicy(QSizePolicy.Policy.Fixed, QSizePolicy.Policy.Fixed)
+ self.setSizePolicy(QtSizeFixed, QtSizeFixed)
self.setFixedWidth(size)
self.setFixedHeight(size)
return
diff --git a/novelwriter/extensions/pagedsidebar.py b/novelwriter/extensions/pagedsidebar.py
index a3c53455..7709509f 100644
--- a/novelwriter/extensions/pagedsidebar.py
+++ b/novelwriter/extensions/pagedsidebar.py
@@ -28,11 +28,14 @@ from __future__ import annotations
from PyQt5.QtGui import QColor, QPaintEvent, QPainter, QPolygon
from PyQt5.QtCore import QPoint, QRectF, QSize, Qt, pyqtSignal, pyqtSlot
from PyQt5.QtWidgets import (
- QAbstractButton, QAction, QButtonGroup, QLabel, QSizePolicy, QStyle,
+ QAbstractButton, QAction, QButtonGroup, QLabel, QStyle,
QStyleOptionToolButton, QToolBar, QToolButton, QWidget
)
-from novelwriter.types import QtPaintAnitAlias, QtAlignLeft, QtMouseOver, QtNoBrush, QtNoPen
+from novelwriter.types import (
+ QtPaintAnitAlias, QtAlignLeft, QtMouseOver, QtNoBrush, QtNoPen,
+ QtSizeExpanding, QtSizeFixed
+)
class NPagedSideBar(QToolBar):
@@ -59,7 +62,7 @@ class NPagedSideBar(QToolBar):
self.setOrientation(Qt.Orientation.Vertical)
stretch = QWidget(self)
- stretch.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding)
+ stretch.setSizePolicy(QtSizeExpanding, QtSizeExpanding)
self._stretchAction = self.addWidget(stretch)
return
@@ -119,7 +122,7 @@ class _NPagedToolButton(QToolButton):
def __init__(self, parent: QWidget) -> None:
super().__init__(parent=parent)
- self.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Fixed)
+ self.setSizePolicy(QtSizeExpanding, QtSizeFixed)
self.setCheckable(True)
fH = self.fontMetrics().height()
@@ -197,7 +200,7 @@ class _NPagedToolLabel(QLabel):
def __init__(self, parent: QWidget, textColor: QColor | None = None) -> None:
super().__init__(parent=parent)
- self.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Fixed)
+ self.setSizePolicy(QtSizeExpanding, QtSizeFixed)
fH = self.fontMetrics().height()
self._bH = round(fH * 1.7)
diff --git a/novelwriter/extensions/switch.py b/novelwriter/extensions/switch.py
index e35bd79a..1c04a158 100644
--- a/novelwriter/extensions/switch.py
+++ b/novelwriter/extensions/switch.py
@@ -25,10 +25,10 @@ from __future__ import annotations
from PyQt5.QtGui import QMouseEvent, QPainter, QPaintEvent, QResizeEvent
from PyQt5.QtCore import QEvent, QPropertyAnimation, Qt, pyqtProperty
-from PyQt5.QtWidgets import QAbstractButton, QSizePolicy, QWidget
+from PyQt5.QtWidgets import QAbstractButton, QWidget
from novelwriter import CONFIG, SHARED
-from novelwriter.types import QtPaintAnitAlias, QtMouseLeft, QtNoPen
+from novelwriter.types import QtPaintAnitAlias, QtMouseLeft, QtNoPen, QtSizeFixed
class NSwitch(QAbstractButton):
@@ -46,7 +46,7 @@ class NSwitch(QAbstractButton):
self._rR = self._xR - self._rB
self.setCheckable(True)
- self.setSizePolicy(QSizePolicy.Policy.Fixed, QSizePolicy.Policy.Fixed)
+ self.setSizePolicy(QtSizeFixed, QtSizeFixed)
self.setFixedWidth(self._xW)
self.setFixedHeight(self._xH)
self._offset = self._xR
diff --git a/novelwriter/extensions/switchbox.py b/novelwriter/extensions/switchbox.py
index d41587ba..1cff6a9c 100644
--- a/novelwriter/extensions/switchbox.py
+++ b/novelwriter/extensions/switchbox.py
@@ -25,10 +25,13 @@ from __future__ import annotations
from PyQt5.QtGui import QIcon
from PyQt5.QtCore import pyqtSignal
-from PyQt5.QtWidgets import QGridLayout, QLabel, QScrollArea, QSizePolicy, QWidget
+from PyQt5.QtWidgets import QGridLayout, QLabel, QScrollArea, QWidget
from novelwriter.extensions.switch import NSwitch
-from novelwriter.types import QtAlignLeft, QtAlignRight, QtAlignRightMiddle
+from novelwriter.types import (
+ QtAlignLeft, QtAlignRight, QtAlignRightMiddle, QtSizeMinimum,
+ QtSizeMinimumExpanding
+)
class NSwitchBox(QScrollArea):
@@ -59,7 +62,7 @@ class NSwitchBox(QScrollArea):
self._content.setColumnStretch(1, 1)
self._widget = QWidget(self)
- self._widget.setSizePolicy(QSizePolicy.Policy.MinimumExpanding, QSizePolicy.Policy.Minimum)
+ self._widget.setSizePolicy(QtSizeMinimumExpanding, QtSizeMinimum)
self._widget.setLayout(self._content)
self.setWidgetResizable(True)
diff --git a/novelwriter/gui/noveltree.py b/novelwriter/gui/noveltree.py
index 7eea96d8..6643df47 100644
--- a/novelwriter/gui/noveltree.py
+++ b/novelwriter/gui/noveltree.py
@@ -35,8 +35,8 @@ from PyQt5.QtCore import QModelIndex, QPoint, Qt, pyqtSlot, pyqtSignal
from PyQt5.QtGui import QFocusEvent, QFont, QMouseEvent, QPalette, QResizeEvent
from PyQt5.QtWidgets import (
QAbstractItemView, QActionGroup, QFrame, QHBoxLayout, QHeaderView,
- QInputDialog, QMenu, QSizePolicy, QToolTip, QTreeWidget, QTreeWidgetItem,
- QVBoxLayout, QWidget
+ QInputDialog, QMenu, QToolTip, QTreeWidget, QTreeWidgetItem, QVBoxLayout,
+ QWidget
)
from novelwriter import CONFIG, SHARED
@@ -47,7 +47,10 @@ from novelwriter.enum import nwDocMode, nwItemClass, nwOutline
from novelwriter.extensions.modified import NIconToolButton
from novelwriter.extensions.novelselector import NovelSelector
from novelwriter.gui.theme import STYLES_MIN_TOOLBUTTON
-from novelwriter.types import QtAlignRight, QtDecoration, QtMouseLeft, QtMouseMiddle, QtUserRole
+from novelwriter.types import (
+ QtAlignRight, QtDecoration, QtMouseLeft, QtMouseMiddle, QtSizeExpanding,
+ QtUserRole
+)
if TYPE_CHECKING: # pragma: no cover
from novelwriter.guimain import GuiMain
@@ -215,7 +218,7 @@ class GuiNovelToolBar(QWidget):
self.novelValue.setFont(selFont)
self.novelValue.setListFormat(self.tr("Outline of {0}"))
self.novelValue.setMinimumWidth(CONFIG.pxInt(150))
- self.novelValue.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding)
+ self.novelValue.setSizePolicy(QtSizeExpanding, QtSizeExpanding)
self.novelValue.novelSelectionChanged.connect(self.setCurrentRoot)
self.tbNovel = NIconToolButton(self, iSz)
diff --git a/novelwriter/gui/outline.py b/novelwriter/gui/outline.py
index 05f80e3b..380455ce 100644
--- a/novelwriter/gui/outline.py
+++ b/novelwriter/gui/outline.py
@@ -36,20 +36,19 @@ from enum import Enum
from PyQt5.QtCore import Qt, pyqtSignal, pyqtSlot, QT_TRANSLATE_NOOP
from PyQt5.QtWidgets import (
QAbstractItemView, QAction, QFileDialog, QFrame, QGridLayout, QGroupBox,
- QHBoxLayout, QLabel, QMenu, QScrollArea, QSizePolicy, QSplitter, QToolBar,
- QToolButton, QTreeWidget, QTreeWidgetItem, QVBoxLayout, QWidget
+ QHBoxLayout, QLabel, QMenu, QScrollArea, QSplitter, QToolBar, QToolButton,
+ QTreeWidget, QTreeWidgetItem, QVBoxLayout, QWidget
)
from novelwriter import CONFIG, SHARED
-from novelwriter.enum import (
- nwDocMode, nwItemClass, nwItemLayout, nwItemType, nwOutline
-)
+from novelwriter.enum import nwDocMode, nwItemClass, nwItemLayout, nwItemType, nwOutline
from novelwriter.error import logException
from novelwriter.common import checkInt, formatFileFilter, makeFileNameSafe
from novelwriter.constants import nwHeaders, trConst, nwKeyWords, nwLabels
from novelwriter.extensions.novelselector import NovelSelector
from novelwriter.types import (
- QtAlignLeftTop, QtAlignRight, QtAlignRightTop, QtDecoration, QtUserRole
+ QtAlignLeftTop, QtAlignRight, QtAlignRightTop, QtDecoration,
+ QtSizeExpanding, QtUserRole
)
@@ -217,7 +216,7 @@ class GuiOutlineToolBar(QToolBar):
self.setContentsMargins(0, 0, 0, 0)
stretch = QWidget(self)
- stretch.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding)
+ stretch.setSizePolicy(QtSizeExpanding, QtSizeExpanding)
# Novel Selector
self.novelLabel = QLabel(self.tr("Outline of"), self)
diff --git a/novelwriter/gui/projtree.py b/novelwriter/gui/projtree.py
index 286b192d..8074325b 100644
--- a/novelwriter/gui/projtree.py
+++ b/novelwriter/gui/projtree.py
@@ -38,8 +38,8 @@ from PyQt5.QtGui import (
)
from PyQt5.QtWidgets import (
QAbstractItemView, QAction, QDialog, QFrame, QHBoxLayout, QHeaderView,
- QLabel, QMenu, QShortcut, QSizePolicy, QTreeWidget, QTreeWidgetItem,
- QVBoxLayout, QWidget
+ QLabel, QMenu, QShortcut, QTreeWidget, QTreeWidgetItem, QVBoxLayout,
+ QWidget
)
from novelwriter import CONFIG, SHARED
@@ -54,7 +54,10 @@ from novelwriter.dialogs.projectsettings import GuiProjectSettings
from novelwriter.enum import nwDocMode, nwItemType, nwItemClass, nwItemLayout
from novelwriter.extensions.modified import NIconToolButton
from novelwriter.gui.theme import STYLES_MIN_TOOLBUTTON
-from novelwriter.types import QtAlignLeft, QtAlignRight, QtMouseLeft, QtMouseMiddle, QtUserRole
+from novelwriter.types import (
+ QtAlignLeft, QtAlignRight, QtMouseLeft, QtMouseMiddle, QtSizeExpanding,
+ QtUserRole,
+)
if TYPE_CHECKING: # pragma: no cover
from novelwriter.guimain import GuiMain
@@ -274,7 +277,7 @@ class GuiProjectToolBar(QWidget):
self.viewLabel = QLabel(self.tr("Project Content"), self)
self.viewLabel.setFont(SHARED.theme.guiFontB)
self.viewLabel.setContentsMargins(0, 0, 0, 0)
- self.viewLabel.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding)
+ self.viewLabel.setSizePolicy(QtSizeExpanding, QtSizeExpanding)
# Quick Links
self.mQuick = QMenu(self)
diff --git a/novelwriter/tools/manuscript.py b/novelwriter/tools/manuscript.py
index 8f1df562..149e1a88 100644
--- a/novelwriter/tools/manuscript.py
+++ b/novelwriter/tools/manuscript.py
@@ -36,8 +36,8 @@ from PyQt5.QtPrintSupport import QPrintPreviewDialog, QPrinter
from PyQt5.QtWidgets import (
QAbstractItemView, QApplication, QDialog, QFormLayout, QGridLayout,
QHBoxLayout, QLabel, QListWidget, QListWidgetItem, QPushButton,
- QSizePolicy, QSplitter, QStackedWidget, QTabWidget, QTextBrowser,
- QTreeWidget, QTreeWidgetItem, QVBoxLayout, QWidget
+ QSplitter, QStackedWidget, QTabWidget, QTextBrowser, QTreeWidget,
+ QTreeWidgetItem, QVBoxLayout, QWidget
)
from novelwriter import CONFIG, SHARED
@@ -54,7 +54,7 @@ from novelwriter.tools.manusbuild import GuiManuscriptBuild
from novelwriter.tools.manussettings import GuiBuildSettings
from novelwriter.types import (
QtAlignAbsolute, QtAlignCenter, QtAlignJustify, QtAlignRight, QtAlignTop,
- QtUserRole
+ QtSizeExpanding, QtSizeIgnored, QtUserRole
)
if TYPE_CHECKING: # pragma: no cover
@@ -1030,16 +1030,14 @@ class _StatsWidget(QWidget):
@pyqtSlot(bool)
def _toggleView(self, state: bool) -> None:
"""Toggle minimal or maximal view."""
- ignored = QSizePolicy.Policy.Ignored
- expanded = QSizePolicy.Policy.Expanding
if state:
self.mainStack.setCurrentWidget(self.maxWidget)
- self.maxWidget.setSizePolicy(expanded, expanded)
- self.minWidget.setSizePolicy(ignored, ignored)
+ self.maxWidget.setSizePolicy(QtSizeExpanding, QtSizeExpanding)
+ self.minWidget.setSizePolicy(QtSizeIgnored, QtSizeIgnored)
else:
self.mainStack.setCurrentWidget(self.minWidget)
- self.maxWidget.setSizePolicy(ignored, ignored)
- self.minWidget.setSizePolicy(expanded, expanded)
+ self.maxWidget.setSizePolicy(QtSizeIgnored, QtSizeIgnored)
+ self.minWidget.setSizePolicy(QtSizeExpanding, QtSizeExpanding)
self.maxWidget.adjustSize()
self.minWidget.adjustSize()
self.mainStack.adjustSize()
diff --git a/novelwriter/types.py b/novelwriter/types.py
index 56e59604..49e670ce 100644
--- a/novelwriter/types.py
+++ b/novelwriter/types.py
@@ -25,7 +25,7 @@ from __future__ import annotations
from PyQt5.QtCore import Qt
from PyQt5.QtGui import QColor, QPainter, QTextCursor
-from PyQt5.QtWidgets import QDialogButtonBox, QStyle
+from PyQt5.QtWidgets import QDialogButtonBox, QSizePolicy, QStyle
# Qt Alignment Flags
@@ -88,3 +88,11 @@ QtKeepAnchor = QTextCursor.MoveMode.KeepAnchor
QtMoveAnchor = QTextCursor.MoveMode.MoveAnchor
QtMoveLeft = QTextCursor.MoveOperation.Left
QtMoveRight = QTextCursor.MoveOperation.Right
+
+# Size Policy
+
+QtSizeExpanding = QSizePolicy.Policy.Expanding
+QtSizeFixed = QSizePolicy.Policy.Fixed
+QtSizeIgnored = QSizePolicy.Policy.Ignored
+QtSizeMinimum = QSizePolicy.Policy.Minimum
+QtSizeMinimumExpanding = QSizePolicy.Policy.MinimumExpanding
From 54f26ea06ea090f5d3bcd206fb386a95dc39c6c3 Mon Sep 17 00:00:00 2001
From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com>
Date: Thu, 11 Apr 2024 21:42:33 +0200
Subject: [PATCH 19/20] Update test
---
tests/test_dialogs/test_dlg_projectsettings.py | 18 +++++++++---------
1 file changed, 9 insertions(+), 9 deletions(-)
diff --git a/tests/test_dialogs/test_dlg_projectsettings.py b/tests/test_dialogs/test_dlg_projectsettings.py
index 921aafd3..3432fd5e 100644
--- a/tests/test_dialogs/test_dlg_projectsettings.py
+++ b/tests/test_dialogs/test_dlg_projectsettings.py
@@ -188,13 +188,13 @@ def testDlgProjSettings_StatusImport(qtbot, monkeypatch, nwGUI, projPath, mockRn
# Can't delete the first item (it's in use)
status.listBox.clearSelection()
status.listBox.setCurrentItem(status.listBox.topLevelItem(0))
- qtbot.mouseClick(status.delButton, QtMouseLeft)
+ status.delButton.click()
assert status.listBox.topLevelItemCount() == 4
# Can delete the second item
status.listBox.clearSelection()
status.listBox.setCurrentItem(status.listBox.topLevelItem(1))
- qtbot.mouseClick(status.delButton, QtMouseLeft)
+ status.delButton.click()
assert status.listBox.topLevelItemCount() == 3
# Add a new item
@@ -203,8 +203,8 @@ def testDlgProjSettings_StatusImport(qtbot, monkeypatch, nwGUI, projPath, mockRn
status.addButton.click()
status.listBox.setCurrentItem(status.listBox.topLevelItem(3))
status.editName.setText("Final")
- status.shapeList.setCurrentData(nwStatusShape.CIRCLE, nwStatusShape.SQUARE)
- status.colButton.click()
+ status.colorButton.click()
+ status._selectShape(nwStatusShape.CIRCLE)
status.applyButton.click()
assert status.listBox.topLevelItemCount() == 4
@@ -269,19 +269,19 @@ def testDlgProjSettings_StatusImport(qtbot, monkeypatch, nwGUI, projPath, mockRn
# Delete unused entry
importance.listBox.clearSelection()
importance.listBox.setCurrentItem(importance.listBox.topLevelItem(1))
- qtbot.mouseClick(importance.delButton, QtMouseLeft)
+ importance.delButton.click()
assert importance.listBox.topLevelItemCount() == 3
# Add a new entry
with monkeypatch.context() as mp:
mp.setattr(QColorDialog, "getColor", lambda *a: QColor(20, 30, 40))
- qtbot.mouseClick(importance.addButton, QtMouseLeft)
+ importance.addButton.click()
importance.listBox.clearSelection()
importance.listBox.setCurrentItem(importance.listBox.topLevelItem(3))
importance.editName.setText("Final")
- importance.shapeList.setCurrentData(nwStatusShape.TRIANGLE, nwStatusShape.SQUARE)
- qtbot.mouseClick(importance.colButton, QtMouseLeft)
- qtbot.mouseClick(importance.applyButton, QtMouseLeft)
+ importance.colorButton.click()
+ importance._selectShape(nwStatusShape.TRIANGLE)
+ importance.applyButton.click()
assert importance.listBox.topLevelItemCount() == 4
assert importance.changed is True
From 492bc044fce9e811be7e53b0bdd73177ef620a69 Mon Sep 17 00:00:00 2001
From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com>
Date: Thu, 11 Apr 2024 21:53:37 +0200
Subject: [PATCH 20/20] Make project setting lists behave the same way
---
novelwriter/dialogs/projectsettings.py | 27 ++++++++++++-------
.../test_dialogs/test_dlg_projectsettings.py | 2 +-
2 files changed, 19 insertions(+), 10 deletions(-)
diff --git a/novelwriter/dialogs/projectsettings.py b/novelwriter/dialogs/projectsettings.py
index 78ebd143..bd9123e5 100644
--- a/novelwriter/dialogs/projectsettings.py
+++ b/novelwriter/dialogs/projectsettings.py
@@ -29,7 +29,7 @@ import logging
from PyQt5.QtCore import Qt, pyqtSignal, pyqtSlot
from PyQt5.QtGui import QCloseEvent, QColor
from PyQt5.QtWidgets import (
- QApplication, QColorDialog, QDialog, QDialogButtonBox, QHBoxLayout,
+ QAbstractItemView, QApplication, QColorDialog, QDialog, QDialogButtonBox, QHBoxLayout,
QLineEdit, QMenu, QStackedWidget, QToolButton, QTreeWidget,
QTreeWidgetItem, QVBoxLayout, QWidget
)
@@ -358,7 +358,9 @@ class _StatusPage(NFixedPage):
self.listBox.setHeaderLabels([self.tr("Label"), self.tr("Usage")])
self.listBox.setColumnWidth(self.C_LABEL, wCol0)
self.listBox.setIndentation(0)
- self.listBox.itemSelectionChanged.connect(self._selectedItem)
+ self.listBox.setSelectionBehavior(QAbstractItemView.SelectionBehavior.SelectRows)
+ self.listBox.setSelectionMode(QAbstractItemView.SelectionMode.SingleSelection)
+ self.listBox.itemSelectionChanged.connect(self._selectionChanged)
for key, entry in status.iterItems():
self._addItem(key, StatusEntry.duplicate(entry))
@@ -418,7 +420,7 @@ class _StatusPage(NFixedPage):
self.applyButton.setText(self.tr("Apply"))
self.applyButton.setSizePolicy(QtSizeMinimum, QtSizeMinimumExpanding)
self.applyButton.setEnabled(False)
- self.applyButton.clicked.connect(self._saveItem)
+ self.applyButton.clicked.connect(self._applyChanges)
# Assemble
self.listControls = QVBoxLayout()
@@ -512,7 +514,7 @@ class _StatusPage(NFixedPage):
return
@pyqtSlot()
- def _saveItem(self) -> None:
+ def _applyChanges(self) -> None:
"""Save changes made to a status item."""
if item := self._getSelectedItem():
entry: StatusEntry = item.data(self.C_DATA, self.D_ENTRY)
@@ -533,7 +535,7 @@ class _StatusPage(NFixedPage):
return
@pyqtSlot()
- def _selectedItem(self) -> None:
+ def _selectionChanged(self) -> None:
"""Extract the info of a selected item and populate the settings
boxes and button. If no item is selected, clear the form.
"""
@@ -650,7 +652,9 @@ class _ReplacePage(NFixedPage):
self.listBox.setHeaderLabels([self.tr("Keyword"), self.tr("Replace With")])
self.listBox.setColumnWidth(self.C_KEY, wCol0)
self.listBox.setIndentation(0)
- self.listBox.itemSelectionChanged.connect(self._selectedItem)
+ self.listBox.setSelectionBehavior(QAbstractItemView.SelectionBehavior.SelectRows)
+ self.listBox.setSelectionMode(QAbstractItemView.SelectionMode.SingleSelection)
+ self.listBox.itemSelectionChanged.connect(self._selectionChanged)
for aKey, aVal in SHARED.project.data.autoReplace.items():
newItem = QTreeWidgetItem(["<%s>" % aKey, aVal])
@@ -679,7 +683,7 @@ class _ReplacePage(NFixedPage):
self.applyButton = QToolButton(self)
self.applyButton.setText(self.tr("Apply"))
self.applyButton.setSizePolicy(QtSizeMinimum, QtSizeMinimumExpanding)
- self.applyButton.clicked.connect(self._saveEntry)
+ self.applyButton.clicked.connect(self._applyChanges)
# Assemble
self.listControls = QVBoxLayout()
@@ -735,7 +739,7 @@ class _ReplacePage(NFixedPage):
##
@pyqtSlot()
- def _selectedItem(self) -> None:
+ def _selectionChanged(self) -> None:
"""Extract the details from the selected item and populate the
edit form.
"""
@@ -746,10 +750,15 @@ class _ReplacePage(NFixedPage):
self.editValue.setEnabled(True)
self.editKey.selectAll()
self.editKey.setFocus()
+ else:
+ self.editKey.setText("")
+ self.editValue.setText("")
+ self.editKey.setEnabled(False)
+ self.editValue.setEnabled(False)
return
@pyqtSlot()
- def _saveEntry(self) -> None:
+ def _applyChanges(self) -> None:
"""Save the form data into the list widget."""
if item := self._getSelectedItem():
key = self._stripNotAllowed(self.editKey.text())
diff --git a/tests/test_dialogs/test_dlg_projectsettings.py b/tests/test_dialogs/test_dlg_projectsettings.py
index 3432fd5e..498dfda5 100644
--- a/tests/test_dialogs/test_dlg_projectsettings.py
+++ b/tests/test_dialogs/test_dlg_projectsettings.py
@@ -357,7 +357,7 @@ def testDlgProjSettings_Replace(qtbot, monkeypatch, nwGUI, projPath, mockRnd):
# Nothing to save or delete
replace.listBox.clearSelection()
- replace._saveEntry()
+ replace._applyChanges()
replace._delEntry()
assert replace.listBox.topLevelItemCount() == 2