Add shape setting to status and importance icons (#1810)

This commit is contained in:
Veronica Berglyd Olsen
2024-04-13 01:07:43 +02:00
committed by GitHub
41 changed files with 1344 additions and 1197 deletions
+29 -1
View File
@@ -25,7 +25,7 @@ from __future__ import annotations
from PyQt5.QtCore import QCoreApplication, QT_TRANSLATE_NOOP 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: def trConst(text: str) -> str:
@@ -268,6 +268,34 @@ class nwLabels:
nwBuildFmt.J_HTML: ".json", nwBuildFmt.J_HTML: ".json",
nwBuildFmt.J_NWD: ".json", nwBuildFmt.J_NWD: ".json",
} }
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 = { FILE_FILTERS = {
"*.txt": QT_TRANSLATE_NOOP("Constant", "Text files"), "*.txt": QT_TRANSLATE_NOOP("Constant", "Text files"),
"*.md": QT_TRANSLATE_NOOP("Constant", "Markdown files"), "*.md": QT_TRANSLATE_NOOP("Constant", "Markdown files"),
+1 -1
View File
@@ -104,7 +104,7 @@ class DocMerger:
docText = self._project.storage.getDocumentText(srcHandle).rstrip("\n") docText = self._project.storage.getDocumentText(srcHandle).rstrip("\n")
if addComment: if addComment:
docInfo = srcItem.describeMe() docInfo = srcItem.describeMe()
docSt, _ = srcItem.getImportStatus(incIcon=False) docSt, _ = srcItem.getImportStatus()
cmtLine = f"% {cmtPrefix} {docInfo}: {srcItem.itemName} [{docSt}]\n\n" cmtLine = f"% {cmtPrefix} {docInfo}: {srcItem.itemName} [{docSt}]\n\n"
docText = cmtLine + docText docText = cmtLine + docText
+5 -15
View File
@@ -25,7 +25,7 @@ from __future__ import annotations
import logging import logging
from typing import TYPE_CHECKING, Any, Literal, overload from typing import TYPE_CHECKING, Any
from PyQt5.QtGui import QIcon from PyQt5.QtGui import QIcon
@@ -308,25 +308,15 @@ class NWItem:
return trConst(nwLabels.ITEM_DESCRIPTION.get(descKey, "")) return trConst(nwLabels.ITEM_DESCRIPTION.get(descKey, ""))
@overload # pragma: no cover def getImportStatus(self) -> tuple[str, QIcon]:
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):
"""Return the relevant importance or status label and icon for """Return the relevant importance or status label and icon for
the current item based on its class. the current item based on its class.
""" """
if self.isNovelLike(): if self.isNovelLike():
stName = self._project.data.itemStatus.name(self._status) entry = self._project.data.itemStatus[self._status]
stIcon = self._project.data.itemStatus.icon(self._status) if incIcon else None
else: else:
stName = self._project.data.itemImport.name(self._import) entry = self._project.data.itemImport[self._import]
stIcon = self._project.data.itemImport.icon(self._import) if incIcon else None return entry.name, entry.icon
return stName, stIcon
## ##
# Checker Methods # Checker Methods
+21 -52
View File
@@ -26,33 +26,32 @@ from __future__ import annotations
import json import json
import logging import logging
from collections.abc import Iterable
from enum import Enum from enum import Enum
from functools import partial
from pathlib import Path
from time import time from time import time
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
from pathlib import Path
from functools import partial
from collections.abc import Iterable
from PyQt5.QtCore import QCoreApplication from PyQt5.QtCore import QCoreApplication
from novelwriter import CONFIG, SHARED, __version__, __hexversion__ from novelwriter import CONFIG, SHARED, __version__, __hexversion__
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 ( from novelwriter.common import (
checkStringNone, formatInt, formatTimeStamp, getFileSize, hexToInt, makeFileNameSafe, minmax 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 if TYPE_CHECKING: # pragma: no cover
from novelwriter.core.item import NWItem from novelwriter.core.item import NWItem
from novelwriter.core.status import NWStatus
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -461,14 +460,14 @@ class NWProject:
def setDefaultStatusImport(self) -> None: def setDefaultStatusImport(self) -> None:
"""Set the default status and importance values.""" """Set the default status and importance values."""
self._data.itemStatus.write(None, self.tr("New"), (100, 100, 100)) self._data.itemStatus.add(None, self.tr("New"), (100, 100, 100), "SQUARE", 0)
self._data.itemStatus.write(None, self.tr("Note"), (200, 50, 0)) self._data.itemStatus.add(None, self.tr("Note"), (200, 50, 0), "SQUARE", 0)
self._data.itemStatus.write(None, self.tr("Draft"), (200, 150, 0)) self._data.itemStatus.add(None, self.tr("Draft"), (200, 150, 0), "SQUARE", 0)
self._data.itemStatus.write(None, self.tr("Finished"), (50, 200, 0)) self._data.itemStatus.add(None, self.tr("Finished"), (50, 200, 0), "SQUARE", 0)
self._data.itemImport.write(None, self.tr("New"), (100, 100, 100)) self._data.itemImport.add(None, self.tr("New"), (100, 100, 100), "SQUARE", 0)
self._data.itemImport.write(None, self.tr("Minor"), (200, 50, 0)) self._data.itemImport.add(None, self.tr("Minor"), (200, 50, 0), "SQUARE", 0)
self._data.itemImport.write(None, self.tr("Major"), (200, 150, 0)) self._data.itemImport.add(None, self.tr("Major"), (200, 150, 0), "SQUARE", 0)
self._data.itemImport.write(None, self.tr("Main"), (50, 200, 0)) self._data.itemImport.add(None, self.tr("Main"), (50, 200, 0), "SQUARE", 0)
return return
def setProjectLang(self, language: str | None) -> None: def setProjectLang(self, language: str | None) -> None:
@@ -491,14 +490,6 @@ class NWProject:
self.setProjectChanged(True) self.setProjectChanged(True)
return return
def setStatusColours(self, new: list[dict], deleted: list[str]) -> bool:
"""Update the list of novel file status flags."""
return self._setStatusImport(new, deleted, self._data.itemStatus)
def setImportColours(self, new: list[dict], deleted: list[str]) -> bool:
"""Update the list of note file importance flags."""
return self._setStatusImport(new, deleted, self._data.itemImport)
def setProjectChanged(self, status: bool) -> bool: def setProjectChanged(self, status: bool) -> bool:
"""Toggle the project changed flag, and propagate the """Toggle the project changed flag, and propagate the
information to the GUI statusbar. information to the GUI statusbar.
@@ -584,28 +575,6 @@ class NWProject:
# Internal Functions # Internal Functions
## ##
def _setStatusImport(self, new: list[dict], delete: list[str], target: NWStatus) -> bool:
"""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))
if name:
order.append(target.write(key, name, cols))
for key in delete:
target.remove(key)
target.reorder(order)
return True
def _loadProjectLocalisation(self) -> bool: def _loadProjectLocalisation(self) -> bool:
"""Load the language data for the current project language.""" """Load the language data for the current project language."""
if self._data.language is None or CONFIG._nwLangPath is None: if self._data.language is None or CONFIG._nwLangPath is None:
+9 -6
View File
@@ -46,7 +46,7 @@ if TYPE_CHECKING: # pragma: no cover
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
FILE_VERSION = "1.5" # The current project file format version 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 HEX_VERSION = 0x0105
NUM_VERSION = { NUM_VERSION = {
@@ -109,6 +109,8 @@ class ProjectXMLReader:
Rev 2: Drops the title node from project and adds the TEMPLATE Rev 2: Drops the title node from project and adds the TEMPLATE
class for items. 2.3 Beta 1. class for items. 2.3 Beta 1.
Rev 3: Added TEMPLATE class. 2.3. 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: def __init__(self, path: str | Path) -> None:
@@ -356,8 +358,8 @@ class ProjectXMLReader:
logger.debug("Parsing <content> section (legacy format)") logger.debug("Parsing <content> section (legacy format)")
# Create maps to look up name -> key for status and importance # Create maps to look up name -> key for status and importance
statusMap = {entry.get("name"): key for key, entry in data.itemStatus.items()} sMap: dict[str | None, str] = {e.name: k for k, e in data.itemStatus.iterItems()}
importMap = {entry.get("name"): key for key, entry in data.itemImport.items()} iMap: dict[str | None, str] = {e.name: k for k, e in data.itemImport.iterItems()}
for xItem in xSection: for xItem in xSection:
if xItem.tag != "item": if xItem.tag != "item":
@@ -404,9 +406,9 @@ class ProjectXMLReader:
# Status was split into separate status/import with a key in 1.4 # Status was split into separate status/import with a key in 1.4
if item.get("class", "") in ("NOVEL", "ARCHIVE"): if item.get("class", "") in ("NOVEL", "ARCHIVE"):
name["status"] = statusMap.get(tmpStatus, None) name["status"] = sMap.get(tmpStatus, None)
else: else:
name["import"] = importMap.get(tmpStatus, None) name["import"] = iMap.get(tmpStatus, None)
# A number of layouts were removed in 1.3 # A number of layouts were removed in 1.3
if item.get("layout", "") in ( if item.get("layout", "") in (
@@ -436,7 +438,8 @@ class ProjectXMLReader:
green = checkInt(xEntry.attrib.get("green", 0), 0) green = checkInt(xEntry.attrib.get("green", 0), 0)
blue = checkInt(xEntry.attrib.get("blue", 0), 0) blue = checkInt(xEntry.attrib.get("blue", 0), 0)
count = checkInt(xEntry.attrib.get("count", 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.add(key, xEntry.text or "", (red, green, blue), shape, count)
return return
def _parseDictKeyText(self, xItem: ET.Element) -> dict: def _parseDictKeyText(self, xItem: ET.Element) -> dict:
+222 -169
View File
@@ -24,17 +24,19 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
""" """
from __future__ import annotations from __future__ import annotations
import random import dataclasses
import logging import logging
import random
from typing import TYPE_CHECKING, Literal from collections.abc import Iterable
from collections.abc import ItemsView, Iterable, Iterator, KeysView, ValuesView from typing import TYPE_CHECKING
from PyQt5.QtGui import QIcon, QPainter, QPainterPath, QPixmap, QColor from PyQt5.QtCore import QPointF, Qt
from PyQt5.QtCore import QRectF from PyQt5.QtGui import QIcon, QPainter, QPainterPath, QPixmap, QColor, QPolygonF
from novelwriter import CONFIG from novelwriter import SHARED
from novelwriter.common import minmax, simplified from novelwriter.common import simplified
from novelwriter.enum import nwStatusShape
from novelwriter.types import QtPaintAnitAlias, QtTransparent from novelwriter.types import QtPaintAnitAlias, QtTransparent
if TYPE_CHECKING: # pragma: no cover if TYPE_CHECKING: # pragma: no cover
@@ -43,83 +45,94 @@ if TYPE_CHECKING: # pragma: no cover
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@dataclasses.dataclass
class StatusEntry:
name: str
color: QColor
shape: nwStatusShape
icon: QIcon
count: int = 0
@classmethod
def duplicate(cls, source: StatusEntry) -> StatusEntry:
"""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
NO_ENTRY = StatusEntry("", QColor(0, 0, 0), nwStatusShape.SQUARE, QIcon(), 0)
class NWStatus: class NWStatus:
STATUS = 1 STATUS = "s"
IMPORT = 2 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 = {} self._store: dict[str, StatusEntry] = {}
self._default = None self._default = None
self._prefix = prefix[:1]
self._iPX = CONFIG.pxInt(24) self._height = SHARED.theme.baseIconHeight
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:
self._prefix = "s"
elif self._type == self.IMPORT:
self._prefix = "i"
else:
raise Exception("This is a bug!")
return return
def write(self, key: str | None, name: str, col: tuple, count: int | None = None) -> str: def __len__(self) -> int:
return len(self._store)
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 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 """Add or update a status entry. If the key is invalid, a new
key is generated. key is generated.
""" """
if not self._isKey(key): if isinstance(color, tuple) and len(color) == 3:
key = self._newKey() qColor = QColor(*color)
if not isinstance(col, tuple): else:
col = (100, 100, 100) qColor = QColor(100, 100, 100)
if len(col) != 3:
col = (100, 100, 100)
cR = minmax(col[0], 0, 255) try:
cG = minmax(col[1], 0, 255) iShape = nwStatusShape[shape]
cB = minmax(col[2], 0, 255) except KeyError:
iShape = nwStatusShape.SQUARE
key = self._checkKey(key)
name = simplified(name) name = simplified(name)
if count is None: icon = self.createIcon(self._height, qColor, iShape)
count = self._store.get(key, {}).get("count", 0) self._store[key] = StatusEntry(name, qColor, iShape, icon, count)
self._store[key] = {
"name": name,
"icon": self._createIcon(cR, cG, cB),
"cols": (cR, cG, cB),
"count": count,
}
if self._default is None: if self._default is None:
self._default = key self._default = key
return key return key
def remove(self, key: str) -> bool: def update(self, update: list[tuple[str | None, StatusEntry]]) -> None:
"""Remove an entry in the list, except if the count > 0.""" """Update the list of statuses, and from removed list."""
if key not in self._store: self._store.clear()
return False for key, entry in update:
if self._store[key]["count"] > 0: self._store[self._checkKey(key)] = entry
return False
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()) return
if key == self._default:
if len(keys) > 0:
self._default = keys[0]
else:
self._default = None
return True
def check(self, value: str) -> str: def check(self, value: str) -> str:
"""Check the key against the stored status names.""" """Check the key against the stored status names."""
@@ -129,93 +142,51 @@ class NWStatus:
return self._default return self._default
return "" 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) -> tuple[int, int, int]:
"""Return the colours associated with a given key."""
if key and key in self._store:
return self._store[key]["cols"]
elif self._default is not None:
return self._store[self._default]["cols"]
return 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):
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: def resetCounts(self) -> None:
"""Clear the counts of references to the status entries.""" """Clear the counts of references to the status entries."""
for key in self._store: for entry in self._store.values():
self._store[key]["count"] = 0 entry.count = 0
return return
def increment(self, key: str | None) -> None: def increment(self, key: str | None) -> None:
"""Increment the counter for a given entry.""" """Increment the counter for a given entry."""
if key and key in self._store: if key and key in self._store:
self._store[key]["count"] += 1 self._store[key].count += 1
return return
def pack(self) -> Iterable[tuple[str, dict]]: def pack(self) -> Iterable[tuple[str, dict]]:
"""Pack the status entries into a dictionary.""" """Pack the status entries into a dictionary."""
for key, data in self._store.items(): for key, entry in self._store.items():
yield (data["name"], { yield (entry.name, {
"key": key, "key": key,
"count": str(data["count"]), "count": str(entry.count),
"red": str(data["cols"][0]), "red": str(entry.color.red()),
"green": str(data["cols"][1]), "green": str(entry.color.green()),
"blue": str(data["cols"][2]), "blue": str(entry.color.blue()),
"shape": entry.shape.name,
}) })
return return
def unpack(self, data: dict) -> None: def iterItems(self) -> Iterable[tuple[str, StatusEntry]]:
"""Unpack a data dictionary and set the class values.""" """Yield entries from the status icons."""
self._store = {} yield from self._store.items()
self._default = None
for key, entry in data.items(): @staticmethod
label = entry.get("label", "") def createIcon(height: int, color: QColor, shape: nwStatusShape) -> QIcon:
colour = entry.get("colour", (100, 100, 100)) """Generate an icon for a status label."""
count = entry.get("count", 0) pixmap = QPixmap(48, 48)
self.write(key, label, colour, count) pixmap.fill(QtTransparent)
return
painter = QPainter(pixmap)
painter.setRenderHint(QtPaintAnitAlias)
painter.fillPath(_SHAPES.getShape(shape), color)
painter.end()
return QIcon(pixmap.scaled(
height, height,
Qt.AspectRatioMode.IgnoreAspectRatio,
Qt.TransformationMode.SmoothTransformation
))
## ##
# Internal Functions # Internal Functions
@@ -246,38 +217,120 @@ class NWStatus:
return False return False
return True return True
def _createIcon(self, red: int, green: int, blue: int) -> QIcon: def _checkKey(self, key: str | None) -> str:
"""Generate an icon for a status label.""" """Check key is valid, and if not, generate one."""
pixmap = QPixmap(self._iPX, self._iPX) return key if self._isKey(key) else self._newKey()
pixmap.fill(QtTransparent)
painter = QPainter(pixmap)
painter.setRenderHint(QtPaintAnitAlias)
painter.fillPath(self._iconPath, QColor(red, green, blue))
painter.end()
return QIcon(pixmap)
##
# 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 # 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]
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)
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.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
return path
# END Class _ShapeCache
# Create Singleton
_SHAPES = _ShapeCache()
+217 -173
View File
@@ -27,20 +27,26 @@ from __future__ import annotations
import logging import logging
from PyQt5.QtCore import Qt, pyqtSignal, pyqtSlot 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 ( from PyQt5.QtWidgets import (
QApplication, QColorDialog, QDialog, QDialogButtonBox, QHBoxLayout, QAbstractItemView, QApplication, QColorDialog, QDialog, QDialogButtonBox, QHBoxLayout,
QLineEdit, QPushButton, QStackedWidget, QTreeWidget, QTreeWidgetItem, QLineEdit, QMenu, QStackedWidget, QToolButton, QTreeWidget,
QVBoxLayout, QWidget QTreeWidgetItem, QVBoxLayout, QWidget
) )
from novelwriter import CONFIG, SHARED from novelwriter import CONFIG, SHARED
from novelwriter.common import simplified 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 from novelwriter.extensions.configlayout import NColourLabel, NFixedPage, NScrollableForm
from novelwriter.extensions.modified import NComboBox, NIconToolButton from novelwriter.extensions.modified import NComboBox, NIconToolButton
from novelwriter.extensions.pagedsidebar import NPagedSideBar from novelwriter.extensions.pagedsidebar import NPagedSideBar
from novelwriter.extensions.switch import NSwitch 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__) logger = logging.getLogger(__name__)
@@ -179,19 +185,19 @@ class GuiProjectSettings(QDialog):
rebuildTrees = False rebuildTrees = False
if self.statusPage.wasChanged: if self.statusPage.changed:
newList, delList = self.statusPage.getNewList() logger.debug("Updating status labels")
project.setStatusColours(newList, delList) project.data.itemStatus.update(self.statusPage.getNewList())
rebuildTrees = True rebuildTrees = True
if self.importPage.wasChanged: if self.importPage.changed:
newList, delList = self.importPage.getNewList() logger.debug("Updating importance labels")
project.setImportColours(newList, delList) project.data.itemImport.update(self.importPage.getNewList())
rebuildTrees = True rebuildTrees = True
if self.replacePage.wasChanged: if self.replacePage.changed:
newList = self.replacePage.getNewList() logger.debug("Updating auto-replace settings")
project.data.setAutoReplace(newList) project.data.setAutoReplace(self.replacePage.getNewList())
self.newProjectSettingsReady.emit(rebuildTrees) self.newProjectSettingsReady.emit(rebuildTrees)
QApplication.processEvents() QApplication.processEvents()
@@ -301,12 +307,12 @@ class _SettingsPage(NScrollableForm):
class _StatusPage(NFixedPage): class _StatusPage(NFixedPage):
COL_LABEL = 0 C_DATA = 0
COL_USAGE = 1 C_LABEL = 0
C_USAGE = 1
KEY_ROLE = QtUserRole D_KEY = QtUserRole
COL_ROLE = QtUserRole + 1 D_ENTRY = QtUserRole + 1
NUM_ROLE = QtUserRole + 2
def __init__(self, parent: QWidget, isStatus: bool) -> None: def __init__(self, parent: QWidget, isStatus: bool) -> None:
super().__init__(parent=parent) super().__init__(parent=parent)
@@ -325,12 +331,21 @@ class _StatusPage(NFixedPage):
) )
self._changed = False self._changed = False
self._colDeleted = [] self._color = QColor(100, 100, 100)
self._selColour = QColor(100, 100, 100) self._shape = nwStatusShape.SQUARE
self._icons = {}
self.iPx = SHARED.theme.baseIconHeight self._iPx = SHARED.theme.baseIconHeight
iSz = SHARED.theme.baseIconSize iSz = SHARED.theme.baseIconSize
bSz = SHARED.theme.buttonIconSize bPd = CONFIG.pxInt(4)
iColor = self.palette().text().color()
# Labels
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 # Title
self.pageTitle = NColourLabel( self.pageTitle = NColourLabel(
@@ -341,12 +356,14 @@ class _StatusPage(NFixedPage):
# List Box # List Box
self.listBox = QTreeWidget(self) self.listBox = QTreeWidget(self)
self.listBox.setHeaderLabels([self.tr("Label"), self.tr("Usage")]) self.listBox.setHeaderLabels([self.tr("Label"), self.tr("Usage")])
self.listBox.itemSelectionChanged.connect(self._selectedItem) self.listBox.setColumnWidth(self.C_LABEL, wCol0)
self.listBox.setColumnWidth(self.COL_LABEL, wCol0)
self.listBox.setIndentation(0) self.listBox.setIndentation(0)
self.listBox.setSelectionBehavior(QAbstractItemView.SelectionBehavior.SelectRows)
self.listBox.setSelectionMode(QAbstractItemView.SelectionMode.SingleSelection)
self.listBox.itemSelectionChanged.connect(self._selectionChanged)
for key, entry in status.items(): for key, entry in status.iterItems():
self._addItem(key, entry["name"], entry["cols"], entry["count"]) self._addItem(key, StatusEntry.duplicate(entry))
# List Controls # List Controls
self.addButton = NIconToolButton(self, iSz, "add") self.addButton = NIconToolButton(self, iSz, "add")
@@ -367,16 +384,43 @@ class _StatusPage(NFixedPage):
self.editName.setPlaceholderText(self.tr("Select item to edit")) self.editName.setPlaceholderText(self.tr("Select item to edit"))
self.editName.setEnabled(False) self.editName.setEnabled(False)
self.colPixmap = QPixmap(self.iPx, self.iPx) buttonStyle = (
self.colPixmap.fill(QColor(100, 100, 100)) f"QToolButton {{padding: 0 {bPd}px;}} "
self.colButton = QPushButton(QIcon(self.colPixmap), self.tr("Colour"), self) "QToolButton::menu-indicator {image: none;}"
self.colButton.setIconSize(bSz) )
self.colButton.setEnabled(False)
self.colButton.clicked.connect(self._selectColour)
self.saveButton = QPushButton(self.tr("Save"), self) self.colorButton = NIconToolButton(self, iSz)
self.saveButton.setEnabled(False) self.colorButton.setToolTip(self.tr("Colour"))
self.saveButton.clicked.connect(self._saveItem) self.colorButton.setSizePolicy(QtSizeMinimum, QtSizeMinimumExpanding)
self.colorButton.setStyleSheet(buttonStyle)
self.colorButton.setEnabled(False)
self.colorButton.clicked.connect(self._selectColour)
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(QtSizeMinimum, QtSizeMinimumExpanding)
self.shapeButton.setStyleSheet(buttonStyle)
self.shapeButton.setEnabled(False)
self.applyButton = QToolButton(self)
self.applyButton.setText(self.tr("Apply"))
self.applyButton.setSizePolicy(QtSizeMinimum, QtSizeMinimumExpanding)
self.applyButton.setEnabled(False)
self.applyButton.clicked.connect(self._applyChanges)
# Assemble # Assemble
self.listControls = QVBoxLayout() self.listControls = QVBoxLayout()
@@ -387,28 +431,30 @@ class _StatusPage(NFixedPage):
self.listControls.addStretch(1) self.listControls.addStretch(1)
self.editBox = QHBoxLayout() self.editBox = QHBoxLayout()
self.editBox.addWidget(self.editName) self.editBox.addWidget(self.editName, 1)
self.editBox.addWidget(self.colButton) self.editBox.addWidget(self.colorButton, 0)
self.editBox.addWidget(self.saveButton) self.editBox.addWidget(self.shapeButton, 0)
self.editBox.addWidget(self.applyButton, 0)
self.mainBox = QVBoxLayout() self.mainBox = QVBoxLayout()
self.mainBox.addWidget(self.listBox) self.mainBox.addWidget(self.listBox, 1)
self.mainBox.addLayout(self.editBox) self.mainBox.addLayout(self.editBox, 0)
self.innerBox = QHBoxLayout() self.innerBox = QHBoxLayout()
self.innerBox.addLayout(self.mainBox) self.innerBox.addLayout(self.mainBox, 1)
self.innerBox.addLayout(self.listControls) self.innerBox.addLayout(self.listControls, 0)
self.outerBox = QVBoxLayout() self.outerBox = QVBoxLayout()
self.outerBox.addWidget(self.pageTitle) self.outerBox.addWidget(self.pageTitle, 0)
self.outerBox.addLayout(self.innerBox) self.outerBox.addLayout(self.innerBox, 1)
self.setCentralLayout(self.outerBox) self.setCentralLayout(self.outerBox)
self._setButtonIcons()
return return
@property @property
def wasChanged(self) -> bool: def changed(self) -> bool:
"""The user changed these settings.""" """The user changed these settings."""
return self._changed return self._changed
@@ -416,20 +462,17 @@ class _StatusPage(NFixedPage):
# Methods # Methods
## ##
def getNewList(self) -> tuple[list, list]: def getNewList(self) -> list[tuple[str | None, StatusEntry]]:
"""Return list of entries.""" """Return list of entries."""
if self._changed: if self._changed:
newList = [] update = []
for n in range(self.listBox.topLevelItemCount()): for n in range(self.listBox.topLevelItemCount()):
item = self.listBox.topLevelItem(n) if item := self.listBox.topLevelItem(n):
if item is not None: key = item.data(self.C_DATA, self.D_KEY)
newList.append({ entry = item.data(self.C_DATA, self.D_ENTRY)
"key": item.data(self.COL_LABEL, self.KEY_ROLE), update.append((key, entry))
"name": item.text(self.COL_LABEL), return update
"cols": item.data(self.COL_LABEL, self.COL_ROLE), return []
})
return newList, self._colDeleted
return [], []
def columnWidth(self) -> int: def columnWidth(self) -> int:
"""Return the size of the header column.""" """Return the size of the header column."""
@@ -442,124 +485,119 @@ class _StatusPage(NFixedPage):
@pyqtSlot() @pyqtSlot()
def _selectColour(self) -> None: def _selectColour(self) -> None:
"""Open a dialog to select the status icon colour.""" """Open a dialog to select the status icon colour."""
if self._selColour is not None: if (color := QColorDialog.getColor(self._color, self, self.trSelColor)).isValid():
newCol = QColorDialog.getColor( self._color = color
self._selColour, self, self.tr("Select Colour") self._setButtonIcons()
)
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())
return return
@pyqtSlot() @pyqtSlot()
def _newItem(self) -> None: def _newItem(self) -> None:
"""Create a new status item.""" """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 self._changed = True
return return
@pyqtSlot() @pyqtSlot()
def _delItem(self) -> None: def _delItem(self) -> None:
"""Delete a status item.""" """Delete a status item."""
selItem = self._getSelectedItem() if item := self._getSelectedItem():
if isinstance(selItem, QTreeWidgetItem): iRow = self.listBox.indexOfTopLevelItem(item)
iRow = self.listBox.indexOfTopLevelItem(selItem) entry: StatusEntry = item.data(self.C_DATA, self.D_ENTRY)
if selItem.data(self.COL_LABEL, self.NUM_ROLE) > 0: if entry.count > 0:
SHARED.error(self.tr("Cannot delete a status item that is in use.")) SHARED.error(self.tr("Cannot delete a status item that is in use."))
else: else:
self.listBox.takeTopLevelItem(iRow) self.listBox.takeTopLevelItem(iRow)
self._colDeleted.append(selItem.data(self.COL_LABEL, self.KEY_ROLE))
self._changed = True self._changed = True
return return
@pyqtSlot() @pyqtSlot()
def _saveItem(self) -> None: def _applyChanges(self) -> None:
"""Save changes made to a status item.""" """Save changes made to a status item."""
selItem = self._getSelectedItem() if item := self._getSelectedItem():
if isinstance(selItem, QTreeWidgetItem): entry: StatusEntry = item.data(self.C_DATA, self.D_ENTRY)
selItem.setText(self.COL_LABEL, simplified(self.editName.text()))
selItem.setIcon(self.COL_LABEL, self.colButton.icon()) name = simplified(self.editName.text())
selItem.setData(self.COL_LABEL, self.COL_ROLE, ( icon = NWStatus.createIcon(self._iPx, self._color, self._shape)
self._selColour.red(), self._selColour.green(), self._selColour.blue()
)) entry.name = name
entry.color = self._color
entry.shape = self._shape
entry.icon = icon
item.setText(self.C_LABEL, name)
item.setIcon(self.C_LABEL, icon)
self._changed = True self._changed = True
return return
@pyqtSlot() @pyqtSlot()
def _selectedItem(self) -> None: def _selectionChanged(self) -> None:
"""Extract the info of a selected item and populate the settings """Extract the info of a selected item and populate the settings
boxes and button. If no item is selected, clear the form. boxes and button. If no item is selected, clear the form.
""" """
selItem = self._getSelectedItem() if item := self._getSelectedItem():
if isinstance(selItem, QTreeWidgetItem): entry: StatusEntry = item.data(self.C_DATA, self.D_ENTRY)
cols = selItem.data(self.COL_LABEL, self.COL_ROLE) self._color = entry.color
name = selItem.text(self.COL_LABEL) self._shape = entry.shape
pixmap = QPixmap(self.iPx, self.iPx) self._setButtonIcons()
pixmap.fill(QColor(*cols))
self._selColour = QColor(*cols) self.editName.setText(entry.name)
self.editName.setText(name)
self.colButton.setIcon(QIcon(pixmap))
self.editName.selectAll() self.editName.selectAll()
self.editName.setFocus() self.editName.setFocus()
self.editName.setEnabled(True) self.editName.setEnabled(True)
self.colButton.setEnabled(True) self.colorButton.setEnabled(True)
self.saveButton.setEnabled(True) self.shapeButton.setEnabled(True)
self.applyButton.setEnabled(True)
else: else:
pixmap = QPixmap(self.iPx, self.iPx) self._color = QColor(100, 100, 100)
pixmap.fill(QColor(100, 100, 100)) self._shape = nwStatusShape.SQUARE
self._selColour = QColor(100, 100, 100) self._setButtonIcons()
self.editName.setText("") self.editName.setText("")
self.colButton.setIcon(QIcon(pixmap))
self.editName.setEnabled(False) self.editName.setEnabled(False)
self.colButton.setEnabled(False) self.colorButton.setEnabled(False)
self.saveButton.setEnabled(False) self.shapeButton.setEnabled(False)
self.applyButton.setEnabled(False)
return return
## ##
# Internal Functions # Internal Functions
## ##
def _addItem(self, key: str | None, name: str, def _selectShape(self, shape: nwStatusShape) -> None:
colour: tuple[int, int, int], count: int) -> 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.""" """Add a status item to the list."""
pixmap = QPixmap(self.iPx, self.iPx)
pixmap.fill(QColor(*colour))
item = QTreeWidgetItem() item = QTreeWidgetItem()
item.setText(self.COL_LABEL, name) item.setText(self.C_LABEL, entry.name)
item.setIcon(self.COL_LABEL, QIcon(pixmap)) item.setIcon(self.C_LABEL, entry.icon)
item.setData(self.COL_LABEL, self.KEY_ROLE, key) item.setText(self.C_USAGE, self._usageString(entry.count))
item.setData(self.COL_LABEL, self.COL_ROLE, colour) item.setData(self.C_DATA, self.D_KEY, key)
item.setData(self.COL_LABEL, self.NUM_ROLE, count) item.setData(self.C_DATA, self.D_ENTRY, entry)
item.setText(self.COL_USAGE, self._usageString(count))
self.listBox.addTopLevelItem(item) self.listBox.addTopLevelItem(item)
return return
def _moveItem(self, step: int) -> None: def _moveItem(self, step: int) -> None:
"""Move and item up or down step.""" """Move and item up or down step."""
selItem = self._getSelectedItem() if item := self._getSelectedItem():
if selItem is None: tIdx = self.listBox.indexOfTopLevelItem(item)
return nItm = self.listBox.topLevelItemCount()
nIdx = tIdx + step
tIndex = self.listBox.indexOfTopLevelItem(selItem) if (0 <= nIdx < nItm) and (cItem := self.listBox.takeTopLevelItem(tIdx)):
nChild = self.listBox.topLevelItemCount() self.listBox.insertTopLevelItem(nIdx, cItem)
nIndex = tIndex + step self.listBox.clearSelection()
if nIndex < 0 or nIndex >= nChild: cItem.setSelected(True)
return self._changed = True
cItem = self.listBox.takeTopLevelItem(tIndex)
self.listBox.insertTopLevelItem(nIndex, cItem)
self.listBox.clearSelection()
if cItem is not None:
cItem.setSelected(True)
self._changed = True
return return
def _getSelectedItem(self) -> QTreeWidgetItem | None: def _getSelectedItem(self) -> QTreeWidgetItem | None:
@@ -571,19 +609,26 @@ class _StatusPage(NFixedPage):
def _usageString(self, count: int) -> str: def _usageString(self, count: int) -> str:
"""Generate usage string.""" """Generate usage string."""
if count == 0: if count == 0:
return self.tr("Not in use") return self.trCountNone
elif count == 1: elif count == 1:
return self.tr("Used once") return self.trCountOne
else: else:
return self.tr("Used by {0} items").format(count) return self.trCountMore.format(count)
def _setButtonIcons(self) -> None:
"""Set the colour of the colour button."""
icon = NWStatus.createIcon(self._iPx, self._color, nwStatusShape.SQUARE)
self.colorButton.setIcon(icon)
self.shapeButton.setIcon(self._icons[self._shape])
return
# END Class _StatusPage # END Class _StatusPage
class _ReplacePage(NFixedPage): class _ReplacePage(NFixedPage):
COL_KEY = 0 C_KEY = 0
COL_REPL = 1 C_REPL = 1
def __init__(self, parent: QWidget) -> None: def __init__(self, parent: QWidget) -> None:
super().__init__(parent=parent) super().__init__(parent=parent)
@@ -605,15 +650,17 @@ class _ReplacePage(NFixedPage):
# List Box # List Box
self.listBox = QTreeWidget(self) self.listBox = QTreeWidget(self)
self.listBox.setHeaderLabels([self.tr("Keyword"), self.tr("Replace With")]) 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.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(): for aKey, aVal in SHARED.project.data.autoReplace.items():
newItem = QTreeWidgetItem(["<%s>" % aKey, aVal]) newItem = QTreeWidgetItem(["<%s>" % aKey, aVal])
self.listBox.addTopLevelItem(newItem) 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) self.listBox.setSortingEnabled(True)
# List Controls # List Controls
@@ -633,8 +680,10 @@ class _ReplacePage(NFixedPage):
self.editValue.setEnabled(False) self.editValue.setEnabled(False)
self.editValue.setMaxLength(80) self.editValue.setMaxLength(80)
self.saveButton = QPushButton(self.tr("Save"), self) self.applyButton = QToolButton(self)
self.saveButton.clicked.connect(self._saveEntry) self.applyButton.setText(self.tr("Apply"))
self.applyButton.setSizePolicy(QtSizeMinimum, QtSizeMinimumExpanding)
self.applyButton.clicked.connect(self._applyChanges)
# Assemble # Assemble
self.listControls = QVBoxLayout() self.listControls = QVBoxLayout()
@@ -645,11 +694,11 @@ class _ReplacePage(NFixedPage):
self.editBox = QHBoxLayout() self.editBox = QHBoxLayout()
self.editBox.addWidget(self.editKey, 4) self.editBox.addWidget(self.editKey, 4)
self.editBox.addWidget(self.editValue, 5) self.editBox.addWidget(self.editValue, 5)
self.editBox.addWidget(self.saveButton, 0) self.editBox.addWidget(self.applyButton, 0)
self.mainBox = QVBoxLayout() self.mainBox = QVBoxLayout()
self.mainBox.addWidget(self.listBox) self.mainBox.addWidget(self.listBox, 1)
self.mainBox.addLayout(self.editBox) self.mainBox.addLayout(self.editBox, 0)
self.innerBox = QHBoxLayout() self.innerBox = QHBoxLayout()
self.innerBox.addLayout(self.mainBox) self.innerBox.addLayout(self.mainBox)
@@ -664,7 +713,7 @@ class _ReplacePage(NFixedPage):
return return
@property @property
def wasChanged(self) -> bool: def changed(self) -> bool:
"""The user changed these settings.""" """The user changed these settings."""
return self._changed return self._changed
@@ -672,15 +721,13 @@ class _ReplacePage(NFixedPage):
# Methods # Methods
## ##
def getNewList(self) -> dict: def getNewList(self) -> dict[str, str]:
"""Extract the list from the widget.""" """Extract the list from the widget."""
new = {} new = {}
for n in range(self.listBox.topLevelItemCount()): for n in range(self.listBox.topLevelItemCount()):
if tItem := self.listBox.topLevelItem(n): if item := self.listBox.topLevelItem(n):
aKey = self._stripNotAllowed(tItem.text(0)) if key := self._stripNotAllowed(item.text(self.C_KEY)):
aVal = tItem.text(1) new[key] = item.text(self.C_REPL)
if len(aKey) > 0:
new[aKey] = aVal
return new return new
def columnWidth(self) -> int: def columnWidth(self) -> int:
@@ -692,51 +739,48 @@ class _ReplacePage(NFixedPage):
## ##
@pyqtSlot() @pyqtSlot()
def _selectedItem(self) -> None: def _selectionChanged(self) -> None:
"""Extract the details from the selected item and populate the """Extract the details from the selected item and populate the
edit form. edit form.
""" """
if selItem := self._getSelectedItem(): if item := self._getSelectedItem():
editKey = self._stripNotAllowed(selItem.text(0)) self.editKey.setText(self._stripNotAllowed(item.text(self.C_KEY)))
editVal = selItem.text(1) self.editValue.setText(item.text(self.C_REPL))
self.editKey.setText(editKey)
self.editValue.setText(editVal)
self.editKey.setEnabled(True) self.editKey.setEnabled(True)
self.editValue.setEnabled(True) self.editValue.setEnabled(True)
self.editKey.selectAll() self.editKey.selectAll()
self.editKey.setFocus() self.editKey.setFocus()
else:
self.editKey.setText("")
self.editValue.setText("")
self.editKey.setEnabled(False)
self.editValue.setEnabled(False)
return return
@pyqtSlot() @pyqtSlot()
def _saveEntry(self) -> None: def _applyChanges(self) -> None:
"""Save the form data into the list widget.""" """Save the form data into the list widget."""
if selItem := self._getSelectedItem(): if item := self._getSelectedItem():
newKey = self.editKey.text() key = self._stripNotAllowed(self.editKey.text())
newVal = self.editValue.text() value = self.editValue.text()
saveKey = self._stripNotAllowed(newKey) if key and value:
if len(saveKey) > 0 and len(newVal) > 0: item.setText(self.C_KEY, f"<{key}>")
selItem.setText(self.COL_KEY, "<%s>" % saveKey) item.setText(self.C_REPL, value)
selItem.setText(self.COL_REPL, newVal)
self.editKey.clear()
self.editValue.clear()
self.editKey.setEnabled(False)
self.editValue.setEnabled(False)
self.listBox.clearSelection()
self._changed = True self._changed = True
return return
@pyqtSlot() @pyqtSlot()
def _addEntry(self) -> None: def _addEntry(self) -> None:
"""Add a new list entry.""" """Add a new list entry."""
saveKey = "<keyword%d>" % (self.listBox.topLevelItemCount() + 1) key = f"<keyword{self.listBox.topLevelItemCount() + 1:d}>"
self.listBox.addTopLevelItem(QTreeWidgetItem([saveKey, ""])) self.listBox.addTopLevelItem(QTreeWidgetItem([key, ""]))
return return
@pyqtSlot() @pyqtSlot()
def _delEntry(self) -> None: def _delEntry(self) -> None:
"""Delete the selected entry.""" """Delete the selected entry."""
if selItem := self._getSelectedItem(): if item := self._getSelectedItem():
self.listBox.takeTopLevelItem(self.listBox.indexOfTopLevelItem(selItem)) self.listBox.takeTopLevelItem(self.listBox.indexOfTopLevelItem(item))
self._changed = True self._changed = True
return return
+26
View File
@@ -205,3 +205,29 @@ class nwBuildFmt(Enum):
J_NWD = 7 J_NWD = 7
# END Enum nwBuildFormat # END Enum nwBuildFormat
class nwStatusShape(Enum):
SQUARE = 0
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
+4 -3
View File
@@ -27,10 +27,11 @@ from math import ceil
from PyQt5.QtCore import QRect from PyQt5.QtCore import QRect
from PyQt5.QtGui import QBrush, QColor, QPaintEvent, QPainter, QPen 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 ( 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(), bar=self.palette().highlight().color(),
text=self.palette().text().color() text=self.palette().text().color()
) )
self.setSizePolicy(QSizePolicy.Policy.Fixed, QSizePolicy.Policy.Fixed) self.setSizePolicy(QtSizeFixed, QtSizeFixed)
self.setFixedWidth(size) self.setFixedWidth(size)
self.setFixedHeight(size) self.setFixedHeight(size)
return return
+3 -1
View File
@@ -25,6 +25,8 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
""" """
from __future__ import annotations from __future__ import annotations
from enum import Enum
from PyQt5.QtCore import QSize, Qt from PyQt5.QtCore import QSize, Qt
from PyQt5.QtGui import QWheelEvent from PyQt5.QtGui import QWheelEvent
from PyQt5.QtWidgets import QComboBox, QDoubleSpinBox, QSpinBox, QToolButton, QWidget from PyQt5.QtWidgets import QComboBox, QDoubleSpinBox, QSpinBox, QToolButton, QWidget
@@ -46,7 +48,7 @@ class NComboBox(QComboBox):
event.ignore() event.ignore()
return 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.""" """Set the current index from data, with a fallback."""
idx = self.findData(data) idx = self.findData(data)
self.setCurrentIndex(self.findData(default) if idx < 0 else idx) self.setCurrentIndex(self.findData(default) if idx < 0 else idx)
+8 -5
View File
@@ -28,11 +28,14 @@ from __future__ import annotations
from PyQt5.QtGui import QColor, QPaintEvent, QPainter, QPolygon from PyQt5.QtGui import QColor, QPaintEvent, QPainter, QPolygon
from PyQt5.QtCore import QPoint, QRectF, QSize, Qt, pyqtSignal, pyqtSlot from PyQt5.QtCore import QPoint, QRectF, QSize, Qt, pyqtSignal, pyqtSlot
from PyQt5.QtWidgets import ( from PyQt5.QtWidgets import (
QAbstractButton, QAction, QButtonGroup, QLabel, QSizePolicy, QStyle, QAbstractButton, QAction, QButtonGroup, QLabel, QStyle,
QStyleOptionToolButton, QToolBar, QToolButton, QWidget 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): class NPagedSideBar(QToolBar):
@@ -59,7 +62,7 @@ class NPagedSideBar(QToolBar):
self.setOrientation(Qt.Orientation.Vertical) self.setOrientation(Qt.Orientation.Vertical)
stretch = QWidget(self) stretch = QWidget(self)
stretch.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding) stretch.setSizePolicy(QtSizeExpanding, QtSizeExpanding)
self._stretchAction = self.addWidget(stretch) self._stretchAction = self.addWidget(stretch)
return return
@@ -119,7 +122,7 @@ class _NPagedToolButton(QToolButton):
def __init__(self, parent: QWidget) -> None: def __init__(self, parent: QWidget) -> None:
super().__init__(parent=parent) super().__init__(parent=parent)
self.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Fixed) self.setSizePolicy(QtSizeExpanding, QtSizeFixed)
self.setCheckable(True) self.setCheckable(True)
fH = self.fontMetrics().height() fH = self.fontMetrics().height()
@@ -197,7 +200,7 @@ class _NPagedToolLabel(QLabel):
def __init__(self, parent: QWidget, textColor: QColor | None = None) -> None: def __init__(self, parent: QWidget, textColor: QColor | None = None) -> None:
super().__init__(parent=parent) super().__init__(parent=parent)
self.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Fixed) self.setSizePolicy(QtSizeExpanding, QtSizeFixed)
fH = self.fontMetrics().height() fH = self.fontMetrics().height()
self._bH = round(fH * 1.7) self._bH = round(fH * 1.7)
+3 -3
View File
@@ -25,10 +25,10 @@ from __future__ import annotations
from PyQt5.QtGui import QMouseEvent, QPainter, QPaintEvent, QResizeEvent from PyQt5.QtGui import QMouseEvent, QPainter, QPaintEvent, QResizeEvent
from PyQt5.QtCore import QEvent, QPropertyAnimation, Qt, pyqtProperty 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 import CONFIG, SHARED
from novelwriter.types import QtPaintAnitAlias, QtMouseLeft, QtNoPen from novelwriter.types import QtPaintAnitAlias, QtMouseLeft, QtNoPen, QtSizeFixed
class NSwitch(QAbstractButton): class NSwitch(QAbstractButton):
@@ -46,7 +46,7 @@ class NSwitch(QAbstractButton):
self._rR = self._xR - self._rB self._rR = self._xR - self._rB
self.setCheckable(True) self.setCheckable(True)
self.setSizePolicy(QSizePolicy.Policy.Fixed, QSizePolicy.Policy.Fixed) self.setSizePolicy(QtSizeFixed, QtSizeFixed)
self.setFixedWidth(self._xW) self.setFixedWidth(self._xW)
self.setFixedHeight(self._xH) self.setFixedHeight(self._xH)
self._offset = self._xR self._offset = self._xR
+6 -3
View File
@@ -25,10 +25,13 @@ from __future__ import annotations
from PyQt5.QtGui import QIcon from PyQt5.QtGui import QIcon
from PyQt5.QtCore import pyqtSignal 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.extensions.switch import NSwitch
from novelwriter.types import QtAlignLeft, QtAlignRight, QtAlignRightMiddle from novelwriter.types import (
QtAlignLeft, QtAlignRight, QtAlignRightMiddle, QtSizeMinimum,
QtSizeMinimumExpanding
)
class NSwitchBox(QScrollArea): class NSwitchBox(QScrollArea):
@@ -59,7 +62,7 @@ class NSwitchBox(QScrollArea):
self._content.setColumnStretch(1, 1) self._content.setColumnStretch(1, 1)
self._widget = QWidget(self) self._widget = QWidget(self)
self._widget.setSizePolicy(QSizePolicy.Policy.MinimumExpanding, QSizePolicy.Policy.Minimum) self._widget.setSizePolicy(QtSizeMinimumExpanding, QtSizeMinimum)
self._widget.setLayout(self._content) self._widget.setLayout(self._content)
self.setWidgetResizable(True) self.setWidgetResizable(True)
+1 -1
View File
@@ -3136,7 +3136,7 @@ class GuiDocEditFooter(QWidget):
sText = "" sText = ""
else: else:
iPx = round(0.9*SHARED.theme.baseIconHeight) iPx = round(0.9*SHARED.theme.baseIconHeight)
status, icon = self._tItem.getImportStatus(incIcon=True) status, icon = self._tItem.getImportStatus()
sIcon = icon.pixmap(iPx, iPx) sIcon = icon.pixmap(iPx, iPx)
sText = f"{status} / {self._tItem.describeMe()}" sText = f"{status} / {self._tItem.describeMe()}"
+1 -1
View File
@@ -450,7 +450,7 @@ class _ViewPanelKeyWords(QTreeWidget):
nwItem.itemType, nwItem.itemClass, nwItem.itemType, nwItem.itemClass,
nwItem.itemLayout, nwItem.mainHeading 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 iLevel = nwHeaders.H_LEVEL.get(hItem.level, 0) if nwItem.isDocumentLayout() else 5
hDec = SHARED.theme.getHeaderDecorationNarrow(iLevel) hDec = SHARED.theme.getHeaderDecorationNarrow(iLevel)
+1 -1
View File
@@ -253,7 +253,7 @@ class GuiItemDetails(QWidget):
# Status # Status
# ====== # ======
status, icon = nwItem.getImportStatus(incIcon=True) status, icon = nwItem.getImportStatus()
self.statusIcon.setPixmap(icon.pixmap(iPx, iPx)) self.statusIcon.setPixmap(icon.pixmap(iPx, iPx))
self.statusData.setText(status) self.statusData.setText(status)
+7 -4
View File
@@ -35,8 +35,8 @@ from PyQt5.QtCore import QModelIndex, QPoint, Qt, pyqtSlot, pyqtSignal
from PyQt5.QtGui import QFocusEvent, QFont, QMouseEvent, QPalette, QResizeEvent from PyQt5.QtGui import QFocusEvent, QFont, QMouseEvent, QPalette, QResizeEvent
from PyQt5.QtWidgets import ( from PyQt5.QtWidgets import (
QAbstractItemView, QActionGroup, QFrame, QHBoxLayout, QHeaderView, QAbstractItemView, QActionGroup, QFrame, QHBoxLayout, QHeaderView,
QInputDialog, QMenu, QSizePolicy, QToolTip, QTreeWidget, QTreeWidgetItem, QInputDialog, QMenu, QToolTip, QTreeWidget, QTreeWidgetItem, QVBoxLayout,
QVBoxLayout, QWidget QWidget
) )
from novelwriter import CONFIG, SHARED 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.modified import NIconToolButton
from novelwriter.extensions.novelselector import NovelSelector from novelwriter.extensions.novelselector import NovelSelector
from novelwriter.gui.theme import STYLES_MIN_TOOLBUTTON 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 if TYPE_CHECKING: # pragma: no cover
from novelwriter.guimain import GuiMain from novelwriter.guimain import GuiMain
@@ -215,7 +218,7 @@ class GuiNovelToolBar(QWidget):
self.novelValue.setFont(selFont) self.novelValue.setFont(selFont)
self.novelValue.setListFormat(self.tr("Outline of {0}")) self.novelValue.setListFormat(self.tr("Outline of {0}"))
self.novelValue.setMinimumWidth(CONFIG.pxInt(150)) 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.novelValue.novelSelectionChanged.connect(self.setCurrentRoot)
self.tbNovel = NIconToolButton(self, iSz) self.tbNovel = NIconToolButton(self, iSz)
+7 -8
View File
@@ -36,20 +36,19 @@ from enum import Enum
from PyQt5.QtCore import Qt, pyqtSignal, pyqtSlot, QT_TRANSLATE_NOOP from PyQt5.QtCore import Qt, pyqtSignal, pyqtSlot, QT_TRANSLATE_NOOP
from PyQt5.QtWidgets import ( from PyQt5.QtWidgets import (
QAbstractItemView, QAction, QFileDialog, QFrame, QGridLayout, QGroupBox, QAbstractItemView, QAction, QFileDialog, QFrame, QGridLayout, QGroupBox,
QHBoxLayout, QLabel, QMenu, QScrollArea, QSizePolicy, QSplitter, QToolBar, QHBoxLayout, QLabel, QMenu, QScrollArea, QSplitter, QToolBar, QToolButton,
QToolButton, QTreeWidget, QTreeWidgetItem, QVBoxLayout, QWidget QTreeWidget, QTreeWidgetItem, QVBoxLayout, QWidget
) )
from novelwriter import CONFIG, SHARED from novelwriter import CONFIG, SHARED
from novelwriter.enum import ( from novelwriter.enum import nwDocMode, nwItemClass, nwItemLayout, nwItemType, nwOutline
nwDocMode, nwItemClass, nwItemLayout, nwItemType, nwOutline
)
from novelwriter.error import logException from novelwriter.error import logException
from novelwriter.common import checkInt, formatFileFilter, makeFileNameSafe from novelwriter.common import checkInt, formatFileFilter, makeFileNameSafe
from novelwriter.constants import nwHeaders, trConst, nwKeyWords, nwLabels from novelwriter.constants import nwHeaders, trConst, nwKeyWords, nwLabels
from novelwriter.extensions.novelselector import NovelSelector from novelwriter.extensions.novelselector import NovelSelector
from novelwriter.types import ( 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) self.setContentsMargins(0, 0, 0, 0)
stretch = QWidget(self) stretch = QWidget(self)
stretch.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding) stretch.setSizePolicy(QtSizeExpanding, QtSizeExpanding)
# Novel Selector # Novel Selector
self.novelLabel = QLabel(self.tr("Outline of"), self) self.novelLabel = QLabel(self.tr("Outline of"), self)
@@ -1048,7 +1047,7 @@ class GuiOutlineDetails(QScrollArea):
self.titleLabel.setText(self.tr(self.LVL_MAP.get(novIdx.level, "H1"))) self.titleLabel.setText(self.tr(self.LVL_MAP.get(novIdx.level, "H1")))
self.titleValue.setText(novIdx.title) self.titleValue.setText(novIdx.title)
itemStatus, _ = nwItem.getImportStatus(incIcon=False) itemStatus, _ = nwItem.getImportStatus()
self.fileValue.setText(nwItem.itemName) self.fileValue.setText(nwItem.itemName)
self.itemValue.setText(itemStatus) self.itemValue.setText(itemStatus)
+14 -11
View File
@@ -38,8 +38,8 @@ from PyQt5.QtGui import (
) )
from PyQt5.QtWidgets import ( from PyQt5.QtWidgets import (
QAbstractItemView, QAction, QDialog, QFrame, QHBoxLayout, QHeaderView, QAbstractItemView, QAction, QDialog, QFrame, QHBoxLayout, QHeaderView,
QLabel, QMenu, QShortcut, QSizePolicy, QTreeWidget, QTreeWidgetItem, QLabel, QMenu, QShortcut, QTreeWidget, QTreeWidgetItem, QVBoxLayout,
QVBoxLayout, QWidget QWidget
) )
from novelwriter import CONFIG, SHARED 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.enum import nwDocMode, nwItemType, nwItemClass, nwItemLayout
from novelwriter.extensions.modified import NIconToolButton from novelwriter.extensions.modified import NIconToolButton
from novelwriter.gui.theme import STYLES_MIN_TOOLBUTTON 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 if TYPE_CHECKING: # pragma: no cover
from novelwriter.guimain import GuiMain from novelwriter.guimain import GuiMain
@@ -274,7 +277,7 @@ class GuiProjectToolBar(QWidget):
self.viewLabel = QLabel(self.tr("Project Content"), self) self.viewLabel = QLabel(self.tr("Project Content"), self)
self.viewLabel.setFont(SHARED.theme.guiFontB) self.viewLabel.setFont(SHARED.theme.guiFontB)
self.viewLabel.setContentsMargins(0, 0, 0, 0) self.viewLabel.setContentsMargins(0, 0, 0, 0)
self.viewLabel.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding) self.viewLabel.setSizePolicy(QtSizeExpanding, QtSizeExpanding)
# Quick Links # Quick Links
self.mQuick = QMenu(self) self.mQuick = QMenu(self)
@@ -1033,7 +1036,7 @@ class GuiProjectTree(QTreeWidget):
if trItem is None or nwItem is None: if trItem is None or nwItem is None:
return return
itemStatus, statusIcon = nwItem.getImportStatus(incIcon=True) itemStatus, statusIcon = nwItem.getImportStatus()
hLevel = nwItem.mainHeading hLevel = nwItem.mainHeading
itemIcon = SHARED.theme.getItemIcon( itemIcon = SHARED.theme.getItemIcon(
nwItem.itemType, nwItem.itemClass, nwItem.itemLayout, hLevel nwItem.itemType, nwItem.itemClass, nwItem.itemLayout, hLevel
@@ -1855,11 +1858,11 @@ class _TreeContextMenu(QMenu):
if self._item.isNovelLike(): if self._item.isNovelLike():
menu = self.addMenu(self.tr("Set Status to ...")) menu = self.addMenu(self.tr("Set Status to ..."))
current = self._item.itemStatus current = self._item.itemStatus
for n, (key, entry) in enumerate(SHARED.project.data.itemStatus.items()): for n, (key, entry) in enumerate(SHARED.project.data.itemStatus.iterItems()):
name = entry["name"] name = entry.name
if not multi and current == key: if not multi and current == key:
name += f" ({nwUnicode.U_CHECK})" name += f" ({nwUnicode.U_CHECK})"
action = menu.addAction(entry["icon"], name) action = menu.addAction(entry.icon, name)
if multi: if multi:
action.triggered.connect(lambda n, key=key: self._iterSetItemStatus(key)) action.triggered.connect(lambda n, key=key: self._iterSetItemStatus(key))
else: else:
@@ -1872,11 +1875,11 @@ class _TreeContextMenu(QMenu):
else: else:
menu = self.addMenu(self.tr("Set Importance to ...")) menu = self.addMenu(self.tr("Set Importance to ..."))
current = self._item.itemImport current = self._item.itemImport
for n, (key, entry) in enumerate(SHARED.project.data.itemImport.items()): for n, (key, entry) in enumerate(SHARED.project.data.itemImport.iterItems()):
name = entry["name"] name = entry.name
if not multi and current == key: if not multi and current == key:
name += f" ({nwUnicode.U_CHECK})" name += f" ({nwUnicode.U_CHECK})"
action = menu.addAction(entry["icon"], name) action = menu.addAction(entry.icon, name)
if multi: if multi:
action.triggered.connect(lambda n, key=key: self._iterSetItemImport(key)) action.triggered.connect(lambda n, key=key: self._iterSetItemImport(key))
else: else:
+7 -9
View File
@@ -36,8 +36,8 @@ from PyQt5.QtPrintSupport import QPrintPreviewDialog, QPrinter
from PyQt5.QtWidgets import ( from PyQt5.QtWidgets import (
QAbstractItemView, QApplication, QDialog, QFormLayout, QGridLayout, QAbstractItemView, QApplication, QDialog, QFormLayout, QGridLayout,
QHBoxLayout, QLabel, QListWidget, QListWidgetItem, QPushButton, QHBoxLayout, QLabel, QListWidget, QListWidgetItem, QPushButton,
QSizePolicy, QSplitter, QStackedWidget, QTabWidget, QTextBrowser, QSplitter, QStackedWidget, QTabWidget, QTextBrowser, QTreeWidget,
QTreeWidget, QTreeWidgetItem, QVBoxLayout, QWidget QTreeWidgetItem, QVBoxLayout, QWidget
) )
from novelwriter import CONFIG, SHARED from novelwriter import CONFIG, SHARED
@@ -54,7 +54,7 @@ from novelwriter.tools.manusbuild import GuiManuscriptBuild
from novelwriter.tools.manussettings import GuiBuildSettings from novelwriter.tools.manussettings import GuiBuildSettings
from novelwriter.types import ( from novelwriter.types import (
QtAlignAbsolute, QtAlignCenter, QtAlignJustify, QtAlignRight, QtAlignTop, QtAlignAbsolute, QtAlignCenter, QtAlignJustify, QtAlignRight, QtAlignTop,
QtUserRole QtSizeExpanding, QtSizeIgnored, QtUserRole
) )
if TYPE_CHECKING: # pragma: no cover if TYPE_CHECKING: # pragma: no cover
@@ -1023,16 +1023,14 @@ class _StatsWidget(QWidget):
@pyqtSlot(bool) @pyqtSlot(bool)
def _toggleView(self, state: bool) -> None: def _toggleView(self, state: bool) -> None:
"""Toggle minimal or maximal view.""" """Toggle minimal or maximal view."""
ignored = QSizePolicy.Policy.Ignored
expanded = QSizePolicy.Policy.Expanding
if state: if state:
self.mainStack.setCurrentWidget(self.maxWidget) self.mainStack.setCurrentWidget(self.maxWidget)
self.maxWidget.setSizePolicy(expanded, expanded) self.maxWidget.setSizePolicy(QtSizeExpanding, QtSizeExpanding)
self.minWidget.setSizePolicy(ignored, ignored) self.minWidget.setSizePolicy(QtSizeIgnored, QtSizeIgnored)
else: else:
self.mainStack.setCurrentWidget(self.minWidget) self.mainStack.setCurrentWidget(self.minWidget)
self.maxWidget.setSizePolicy(ignored, ignored) self.maxWidget.setSizePolicy(QtSizeIgnored, QtSizeIgnored)
self.minWidget.setSizePolicy(expanded, expanded) self.minWidget.setSizePolicy(QtSizeExpanding, QtSizeExpanding)
self.maxWidget.adjustSize() self.maxWidget.adjustSize()
self.minWidget.adjustSize() self.minWidget.adjustSize()
self.mainStack.adjustSize() self.mainStack.adjustSize()
+9 -1
View File
@@ -25,7 +25,7 @@ from __future__ import annotations
from PyQt5.QtCore import Qt from PyQt5.QtCore import Qt
from PyQt5.QtGui import QColor, QPainter, QTextCursor from PyQt5.QtGui import QColor, QPainter, QTextCursor
from PyQt5.QtWidgets import QDialogButtonBox, QStyle from PyQt5.QtWidgets import QDialogButtonBox, QSizePolicy, QStyle
# Qt Alignment Flags # Qt Alignment Flags
@@ -88,3 +88,11 @@ QtKeepAnchor = QTextCursor.MoveMode.KeepAnchor
QtMoveAnchor = QTextCursor.MoveMode.MoveAnchor QtMoveAnchor = QTextCursor.MoveMode.MoveAnchor
QtMoveLeft = QTextCursor.MoveOperation.Left QtMoveLeft = QTextCursor.MoveOperation.Left
QtMoveRight = QTextCursor.MoveOperation.Right 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
+22 -21
View File
@@ -1,6 +1,6 @@
<?xml version='1.0' encoding='utf-8'?> <?xml version='1.0' encoding='utf-8'?>
<novelWriterXML appVersion="2.4rc1" hexVersion="0x020400c1" fileVersion="1.5" fileRevision="3" timeStamp="2024-04-06 15:52:29"> <novelWriterXML appVersion="2.4rc1" hexVersion="0x020400c1" fileVersion="1.5" fileRevision="4" timeStamp="2024-04-11 14:19:47">
<project id="e2be99af-f9bf-4403-857a-c3d1ac25abea" saveCount="1699" autoCount="264" editTime="83349"> <project id="e2be99af-f9bf-4403-857a-c3d1ac25abea" saveCount="1850" autoCount="272" editTime="86438">
<name>Sample Project</name> <name>Sample Project</name>
<author>Jane Smith</author> <author>Jane Smith</author>
</project> </project>
@@ -20,19 +20,20 @@
<entry key="C">D</entry> <entry key="C">D</entry>
</autoReplace> </autoReplace>
<status> <status>
<entry key="sf12341" count="8" red="100" green="100" blue="100">New</entry> <entry key="sf12341" count="8" red="100" green="100" blue="100" shape="SQUARE">New</entry>
<entry key="sf24ce6" count="2" red="200" green="50" blue="0">Notes</entry> <entry key="sf24ce6" count="2" red="200" green="50" blue="0" shape="SQUARE">Notes</entry>
<entry key="sc24b8f" count="3" red="182" green="60" blue="0">Started</entry> <entry key="sc24b8f" count="3" red="182" green="60" blue="0" shape="BARS_1">Started</entry>
<entry key="s90e6c9" count="7" red="193" green="129" blue="0">1st Draft</entry> <entry key="s90e6c9" count="5" red="193" green="129" blue="0" shape="BARS_2">1st Draft</entry>
<entry key="sd51c5b" count="0" red="193" green="129" blue="0">2nd Draft</entry> <entry key="sd51c5b" count="1" red="193" green="129" blue="0" shape="BARS_3">2nd Draft</entry>
<entry key="s8ae72a" count="0" red="193" green="129" blue="0">3rd Draft</entry> <entry key="s8ae72a" count="1" red="193" green="129" blue="0" shape="BARS_4">3rd Draft</entry>
<entry key="s78ea90" count="1" red="58" green="180" blue="58">Finished</entry> <entry key="s78ea90" count="1" red="58" green="180" blue="58" shape="STAR">Finished</entry>
</status> </status>
<importance> <importance>
<entry key="ia857f0" count="5" red="100" green="100" blue="100">None</entry> <entry key="ia857f0" count="5" red="100" green="100" blue="100" shape="SQUARE">None</entry>
<entry key="icfb3a5" count="2" red="0" green="122" blue="188">Minor</entry> <entry key="i4a1d39" count="1" red="220" green="138" blue="221" shape="BLOCK_1">Background</entry>
<entry key="i2d7a54" count="2" red="21" green="0" blue="180">Major</entry> <entry key="icfb3a5" count="1" red="220" green="138" blue="221" shape="BLOCK_2">Minor</entry>
<entry key="i56be10" count="1" red="117" green="0" blue="175">Main</entry> <entry key="i2d7a54" count="2" red="220" green="138" blue="221" shape="BLOCK_3">Major</entry>
<entry key="i56be10" count="1" red="220" green="138" blue="221" shape="BLOCK_4">Main</entry>
</importance> </importance>
</settings> </settings>
<content items="31" novelWords="998" notesWords="416"> <content items="31" novelWords="998" notesWords="416">
@@ -57,7 +58,7 @@
<name status="sf24ce6" import="ia857f0" active="yes">Chapter One</name> <name status="sf24ce6" import="ia857f0" active="yes">Chapter One</name>
</item> </item>
<item handle="636b6aa9b697b" parent="6a2d6d5f4f401" root="7031beac91f75" order="0" type="FILE" class="NOVEL" layout="DOCUMENT"> <item handle="636b6aa9b697b" parent="6a2d6d5f4f401" root="7031beac91f75" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="no" heading="H3" charCount="2937" wordCount="520" paraCount="15" cursorPos="1465" /> <meta expanded="no" heading="H3" charCount="2937" wordCount="520" paraCount="15" cursorPos="0" />
<name status="s90e6c9" import="ia857f0" active="yes">Making a Scene</name> <name status="s90e6c9" import="ia857f0" active="yes">Making a Scene</name>
</item> </item>
<item handle="bc0cbd2a407f3" parent="6a2d6d5f4f401" root="7031beac91f75" order="1" type="FILE" class="NOVEL" layout="DOCUMENT"> <item handle="bc0cbd2a407f3" parent="6a2d6d5f4f401" root="7031beac91f75" order="1" type="FILE" class="NOVEL" layout="DOCUMENT">
@@ -78,7 +79,7 @@
</item> </item>
<item handle="ae7339df26ded" parent="88706ddc78b1b" root="7031beac91f75" order="0" type="FILE" class="NOVEL" layout="DOCUMENT"> <item handle="ae7339df26ded" parent="88706ddc78b1b" root="7031beac91f75" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="no" heading="H3" charCount="189" wordCount="37" paraCount="1" cursorPos="237" /> <meta expanded="no" heading="H3" charCount="189" wordCount="37" paraCount="1" cursorPos="237" />
<name status="s90e6c9" import="ia857f0" active="yes">We Found John!</name> <name status="sd51c5b" import="ia857f0" active="yes">We Found John!</name>
</item> </item>
<item handle="e5e47ebf63b1c" parent="None" root="e5e47ebf63b1c" order="1" type="ROOT" class="NOVEL"> <item handle="e5e47ebf63b1c" parent="None" root="e5e47ebf63b1c" order="1" type="ROOT" class="NOVEL">
<meta expanded="yes" /> <meta expanded="yes" />
@@ -90,7 +91,7 @@
</item> </item>
<item handle="a520879ca0b45" parent="e5e47ebf63b1c" root="e5e47ebf63b1c" order="1" type="FILE" class="NOVEL" layout="DOCUMENT"> <item handle="a520879ca0b45" parent="e5e47ebf63b1c" root="e5e47ebf63b1c" order="1" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="no" heading="H2" charCount="299" wordCount="55" paraCount="2" cursorPos="387" /> <meta expanded="no" heading="H2" charCount="299" wordCount="55" paraCount="2" cursorPos="387" />
<name status="s90e6c9" import="ia857f0" active="yes">Chapter One</name> <name status="s8ae72a" import="ia857f0" active="yes">Chapter One</name>
</item> </item>
<item handle="f6622b4617424" parent="None" root="f6622b4617424" order="2" type="ROOT" class="CHARACTER"> <item handle="f6622b4617424" parent="None" root="f6622b4617424" order="2" type="ROOT" class="CHARACTER">
<meta expanded="yes" /> <meta expanded="yes" />
@@ -102,11 +103,11 @@
</item> </item>
<item handle="14298de4d9524" parent="f7e2d9f330615" root="f6622b4617424" order="0" type="FILE" class="CHARACTER" layout="NOTE"> <item handle="14298de4d9524" parent="f7e2d9f330615" root="f6622b4617424" order="0" type="FILE" class="CHARACTER" layout="NOTE">
<meta expanded="no" heading="H1" charCount="49" wordCount="9" paraCount="1" cursorPos="15" /> <meta expanded="no" heading="H1" charCount="49" wordCount="9" paraCount="1" cursorPos="15" />
<name status="sf12341" import="icfb3a5" active="yes">John Smith</name> <name status="sf12341" import="i2d7a54" active="yes">John Smith</name>
</item> </item>
<item handle="bb2c23b3c42cc" parent="f7e2d9f330615" root="f6622b4617424" order="1" type="FILE" class="CHARACTER" layout="NOTE"> <item handle="bb2c23b3c42cc" parent="f7e2d9f330615" root="f6622b4617424" order="1" type="FILE" class="CHARACTER" layout="NOTE">
<meta expanded="no" heading="H1" charCount="55" wordCount="9" paraCount="1" cursorPos="31" /> <meta expanded="no" heading="H1" charCount="55" wordCount="9" paraCount="1" cursorPos="31" />
<name status="sf12341" import="i2d7a54" active="yes">Jane Smith</name> <name status="sf12341" import="i56be10" active="yes">Jane Smith</name>
</item> </item>
<item handle="15c4492bd5107" parent="None" root="15c4492bd5107" order="3" type="ROOT" class="WORLD"> <item handle="15c4492bd5107" parent="None" root="15c4492bd5107" order="3" type="ROOT" class="WORLD">
<meta expanded="yes" /> <meta expanded="yes" />
@@ -114,15 +115,15 @@
</item> </item>
<item handle="b3e74dbc1f584" parent="15c4492bd5107" root="15c4492bd5107" order="0" type="FILE" class="WORLD" layout="NOTE"> <item handle="b3e74dbc1f584" parent="15c4492bd5107" root="15c4492bd5107" order="0" type="FILE" class="WORLD" layout="NOTE">
<meta expanded="no" heading="H1" charCount="76" wordCount="15" paraCount="1" cursorPos="111" /> <meta expanded="no" heading="H1" charCount="76" wordCount="15" paraCount="1" cursorPos="111" />
<name status="sf12341" import="i56be10" active="yes">Earth</name> <name status="sf12341" import="i2d7a54" active="yes">Earth</name>
</item> </item>
<item handle="f1471bef9f2ae" parent="15c4492bd5107" root="15c4492bd5107" order="1" type="FILE" class="WORLD" layout="NOTE"> <item handle="f1471bef9f2ae" parent="15c4492bd5107" root="15c4492bd5107" order="1" type="FILE" class="WORLD" layout="NOTE">
<meta expanded="no" heading="H1" charCount="115" wordCount="24" paraCount="1" cursorPos="135" /> <meta expanded="no" heading="H1" charCount="115" wordCount="24" paraCount="1" cursorPos="135" />
<name status="sf12341" import="icfb3a5" active="yes">Space</name> <name status="sf12341" import="i4a1d39" active="yes">Space</name>
</item> </item>
<item handle="5eaea4e8cdee8" parent="15c4492bd5107" root="15c4492bd5107" order="2" type="FILE" class="WORLD" layout="NOTE"> <item handle="5eaea4e8cdee8" parent="15c4492bd5107" root="15c4492bd5107" order="2" type="FILE" class="WORLD" layout="NOTE">
<meta expanded="no" heading="H1" charCount="28" wordCount="6" paraCount="1" cursorPos="62" /> <meta expanded="no" heading="H1" charCount="28" wordCount="6" paraCount="1" cursorPos="62" />
<name status="sf12341" import="i2d7a54" active="yes">Mars</name> <name status="sf12341" import="icfb3a5" active="yes">Mars</name>
</item> </item>
<item handle="6827118336ac1" parent="None" root="6827118336ac1" order="4" type="ROOT" class="ARCHIVE"> <item handle="6827118336ac1" parent="None" root="6827118336ac1" order="4" type="ROOT" class="ARCHIVE">
<meta expanded="yes" /> <meta expanded="yes" />
+12 -12
View File
@@ -1,5 +1,5 @@
<?xml version='1.0' encoding='utf-8'?> <?xml version='1.0' encoding='utf-8'?>
<novelWriterXML appVersion="2.0-rc1" hexVersion="0x020000c1" fileVersion="1.5" fileRevision="3" timeStamp="2022-11-07 13:00:48"> <novelWriterXML appVersion="2.0-rc1" hexVersion="0x020000c1" fileVersion="1.5" fileRevision="4" timeStamp="2022-11-07 13:00:48">
<project id="e2be99af-f9bf-4403-857a-c3d1ac25abea" saveCount="5" autoCount="10" editTime="1000"> <project id="e2be99af-f9bf-4403-857a-c3d1ac25abea" saveCount="5" autoCount="10" editTime="1000">
<name>Sample Project</name> <name>Sample Project</name>
<author>Jane Smith</author> <author>Jane Smith</author>
@@ -20,19 +20,19 @@
<entry key="C">D</entry> <entry key="C">D</entry>
</autoReplace> </autoReplace>
<status> <status>
<entry key="sf12341" count="4" red="100" green="100" blue="100">New</entry> <entry key="sf12341" count="4" red="100" green="100" blue="100" shape="SQUARE">New</entry>
<entry key="sf24ce6" count="2" red="200" green="50" blue="0">Notes</entry> <entry key="sf24ce6" count="2" red="200" green="50" blue="0" shape="SQUARE">Notes</entry>
<entry key="sc24b8f" count="3" red="182" green="60" blue="0">Started</entry> <entry key="sc24b8f" count="3" red="182" green="60" blue="0" shape="SQUARE">Started</entry>
<entry key="s90e6c9" count="7" red="193" green="129" blue="0">1st Draft</entry> <entry key="s90e6c9" count="7" red="193" green="129" blue="0" shape="SQUARE">1st Draft</entry>
<entry key="sd51c5b" count="0" red="193" green="129" blue="0">2nd Draft</entry> <entry key="sd51c5b" count="0" red="193" green="129" blue="0" shape="SQUARE">2nd Draft</entry>
<entry key="s8ae72a" count="0" red="193" green="129" blue="0">3rd Draft</entry> <entry key="s8ae72a" count="0" red="193" green="129" blue="0" shape="SQUARE">3rd Draft</entry>
<entry key="s78ea90" count="1" red="58" green="180" blue="58">Finished</entry> <entry key="s78ea90" count="1" red="58" green="180" blue="58" shape="SQUARE">Finished</entry>
</status> </status>
<importance> <importance>
<entry key="ia857f0" count="5" red="100" green="100" blue="100">None</entry> <entry key="ia857f0" count="5" red="100" green="100" blue="100" shape="SQUARE">None</entry>
<entry key="icfb3a5" count="2" red="0" green="122" blue="188">Minor</entry> <entry key="icfb3a5" count="2" red="0" green="122" blue="188" shape="SQUARE">Minor</entry>
<entry key="i2d7a54" count="2" red="21" green="0" blue="180">Major</entry> <entry key="i2d7a54" count="2" red="21" green="0" blue="180" shape="SQUARE">Major</entry>
<entry key="i56be10" count="1" red="117" green="0" blue="175">Main</entry> <entry key="i56be10" count="1" red="117" green="0" blue="175" shape="SQUARE">Main</entry>
</importance> </importance>
</settings> </settings>
<content items="27" novelWords="954" notesWords="409"> <content items="27" novelWords="954" notesWords="409">
+1 -1
View File
@@ -75,7 +75,7 @@ class MockStatusBar:
class MockTheme: class MockTheme:
def __init__(self): def __init__(self):
self.baseIconHeight = 10 self.baseIconHeight = 20
return return
def getPixmap(self, *a): def getPixmap(self, *a):
@@ -1,5 +1,5 @@
<?xml version='1.0' encoding='utf-8'?> <?xml version='1.0' encoding='utf-8'?>
<novelWriterXML appVersion="2.3a4" hexVersion="0x020300a4" fileVersion="1.5" fileRevision="2" timeStamp="2024-02-08 21:34:07"> <novelWriterXML appVersion="2.4rc1" hexVersion="0x020400c1" fileVersion="1.5" fileRevision="4" timeStamp="2024-04-09 22:34:16">
<project id="d0f3fe10-c6e6-4310-8bfd-181eb4224eed" saveCount="1" autoCount="1" editTime="0"> <project id="d0f3fe10-c6e6-4310-8bfd-181eb4224eed" saveCount="1" autoCount="1" editTime="0">
<name>New Project</name> <name>New Project</name>
<author>Jane Doe</author> <author>Jane Doe</author>
@@ -16,16 +16,16 @@
</lastHandle> </lastHandle>
<autoReplace /> <autoReplace />
<status> <status>
<entry key="s000000" count="7" red="100" green="100" blue="100">New</entry> <entry key="s000000" count="7" red="100" green="100" blue="100" shape="SQUARE">New</entry>
<entry key="s000001" count="0" red="200" green="50" blue="0">Note</entry> <entry key="s000001" count="0" red="200" green="50" blue="0" shape="SQUARE">Note</entry>
<entry key="s000002" count="0" red="200" green="150" blue="0">Draft</entry> <entry key="s000002" count="0" red="200" green="150" blue="0" shape="SQUARE">Draft</entry>
<entry key="s000003" count="0" red="50" green="200" blue="0">Finished</entry> <entry key="s000003" count="0" red="50" green="200" blue="0" shape="SQUARE">Finished</entry>
</status> </status>
<importance> <importance>
<entry key="i000004" count="5" red="100" green="100" blue="100">New</entry> <entry key="i000004" count="5" red="100" green="100" blue="100" shape="SQUARE">New</entry>
<entry key="i000005" count="0" red="200" green="50" blue="0">Minor</entry> <entry key="i000005" count="0" red="200" green="50" blue="0" shape="SQUARE">Minor</entry>
<entry key="i000006" count="0" red="200" green="150" blue="0">Major</entry> <entry key="i000006" count="0" red="200" green="150" blue="0" shape="SQUARE">Major</entry>
<entry key="i000007" count="0" red="50" green="200" blue="0">Main</entry> <entry key="i000007" count="0" red="50" green="200" blue="0" shape="SQUARE">Main</entry>
</importance> </importance>
</settings> </settings>
<content items="12" novelWords="10" notesWords="6"> <content items="12" novelWords="10" notesWords="6">
@@ -1,5 +1,5 @@
<?xml version='1.0' encoding='utf-8'?> <?xml version='1.0' encoding='utf-8'?>
<novelWriterXML appVersion="2.3a1" hexVersion="0x020300a1" fileVersion="1.5" fileRevision="2" timeStamp="2024-01-26 22:50:57"> <novelWriterXML appVersion="2.4rc1" hexVersion="0x020400c1" fileVersion="1.5" fileRevision="4" timeStamp="2024-04-09 22:34:16">
<project id="d0f3fe10-c6e6-4310-8bfd-181eb4224eed" saveCount="1" autoCount="1" editTime="0"> <project id="d0f3fe10-c6e6-4310-8bfd-181eb4224eed" saveCount="1" autoCount="1" editTime="0">
<name>New Project</name> <name>New Project</name>
<author>Jane Doe</author> <author>Jane Doe</author>
@@ -16,16 +16,16 @@
</lastHandle> </lastHandle>
<autoReplace /> <autoReplace />
<status> <status>
<entry key="s000000" count="6" red="100" green="100" blue="100">New</entry> <entry key="s000000" count="6" red="100" green="100" blue="100" shape="SQUARE">New</entry>
<entry key="s000001" count="0" red="200" green="50" blue="0">Note</entry> <entry key="s000001" count="0" red="200" green="50" blue="0" shape="SQUARE">Note</entry>
<entry key="s000002" count="0" red="200" green="150" blue="0">Draft</entry> <entry key="s000002" count="0" red="200" green="150" blue="0" shape="SQUARE">Draft</entry>
<entry key="s000003" count="0" red="50" green="200" blue="0">Finished</entry> <entry key="s000003" count="0" red="50" green="200" blue="0" shape="SQUARE">Finished</entry>
</status> </status>
<importance> <importance>
<entry key="i000004" count="10" red="100" green="100" blue="100">New</entry> <entry key="i000004" count="10" red="100" green="100" blue="100" shape="SQUARE">New</entry>
<entry key="i000005" count="0" red="200" green="50" blue="0">Minor</entry> <entry key="i000005" count="0" red="200" green="50" blue="0" shape="SQUARE">Minor</entry>
<entry key="i000006" count="0" red="200" green="150" blue="0">Major</entry> <entry key="i000006" count="0" red="200" green="150" blue="0" shape="SQUARE">Major</entry>
<entry key="i000007" count="0" red="50" green="200" blue="0">Main</entry> <entry key="i000007" count="0" red="50" green="200" blue="0" shape="SQUARE">Main</entry>
</importance> </importance>
</settings> </settings>
<content items="16" novelWords="9" notesWords="0"> <content items="16" novelWords="9" notesWords="0">
@@ -1,5 +1,5 @@
<?xml version='1.0' encoding='utf-8'?> <?xml version='1.0' encoding='utf-8'?>
<novelWriterXML appVersion="2.3a1" hexVersion="0x020300a1" fileVersion="1.5" fileRevision="2" timeStamp="2024-01-26 22:53:40"> <novelWriterXML appVersion="2.4rc1" hexVersion="0x020400c1" fileVersion="1.5" fileRevision="4" timeStamp="2024-04-09 22:34:16">
<project id="d0f3fe10-c6e6-4310-8bfd-181eb4224eed" saveCount="1" autoCount="1" editTime="0"> <project id="d0f3fe10-c6e6-4310-8bfd-181eb4224eed" saveCount="1" autoCount="1" editTime="0">
<name>New Project</name> <name>New Project</name>
<author>Jane Doe</author> <author>Jane Doe</author>
@@ -16,16 +16,16 @@
</lastHandle> </lastHandle>
<autoReplace /> <autoReplace />
<status> <status>
<entry key="s000000" count="15" red="100" green="100" blue="100">New</entry> <entry key="s000000" count="15" red="100" green="100" blue="100" shape="SQUARE">New</entry>
<entry key="s000001" count="0" red="200" green="50" blue="0">Note</entry> <entry key="s000001" count="0" red="200" green="50" blue="0" shape="SQUARE">Note</entry>
<entry key="s000002" count="0" red="200" green="150" blue="0">Draft</entry> <entry key="s000002" count="0" red="200" green="150" blue="0" shape="SQUARE">Draft</entry>
<entry key="s000003" count="0" red="50" green="200" blue="0">Finished</entry> <entry key="s000003" count="0" red="50" green="200" blue="0" shape="SQUARE">Finished</entry>
</status> </status>
<importance> <importance>
<entry key="i000004" count="3" red="100" green="100" blue="100">New</entry> <entry key="i000004" count="3" red="100" green="100" blue="100" shape="SQUARE">New</entry>
<entry key="i000005" count="0" red="200" green="50" blue="0">Minor</entry> <entry key="i000005" count="0" red="200" green="50" blue="0" shape="SQUARE">Minor</entry>
<entry key="i000006" count="0" red="200" green="150" blue="0">Major</entry> <entry key="i000006" count="0" red="200" green="150" blue="0" shape="SQUARE">Major</entry>
<entry key="i000007" count="0" red="50" green="200" blue="0">Main</entry> <entry key="i000007" count="0" red="50" green="200" blue="0" shape="SQUARE">Main</entry>
</importance> </importance>
</settings> </settings>
<content items="18" novelWords="26" notesWords="0"> <content items="18" novelWords="26" notesWords="0">
@@ -1,5 +1,5 @@
<?xml version='1.0' encoding='utf-8'?> <?xml version='1.0' encoding='utf-8'?>
<novelWriterXML appVersion="2.3b1" hexVersion="0x020300b1" fileVersion="1.5" fileRevision="2" timeStamp="2024-02-20 12:52:16"> <novelWriterXML appVersion="2.4rc1" hexVersion="0x020400c1" fileVersion="1.5" fileRevision="4" timeStamp="2024-04-09 22:34:16">
<project id="d0f3fe10-c6e6-4310-8bfd-181eb4224eed" saveCount="1" autoCount="0" editTime="0"> <project id="d0f3fe10-c6e6-4310-8bfd-181eb4224eed" saveCount="1" autoCount="0" editTime="0">
<name>Test Project A</name> <name>Test Project A</name>
<author>Jane Doe</author> <author>Jane Doe</author>
@@ -16,16 +16,16 @@
</lastHandle> </lastHandle>
<autoReplace /> <autoReplace />
<status> <status>
<entry key="s000000" count="15" red="100" green="100" blue="100">New</entry> <entry key="s000000" count="15" red="100" green="100" blue="100" shape="SQUARE">New</entry>
<entry key="s000001" count="0" red="200" green="50" blue="0">Note</entry> <entry key="s000001" count="0" red="200" green="50" blue="0" shape="SQUARE">Note</entry>
<entry key="s000002" count="0" red="200" green="150" blue="0">Draft</entry> <entry key="s000002" count="0" red="200" green="150" blue="0" shape="SQUARE">Draft</entry>
<entry key="s000003" count="0" red="50" green="200" blue="0">Finished</entry> <entry key="s000003" count="0" red="50" green="200" blue="0" shape="SQUARE">Finished</entry>
</status> </status>
<importance> <importance>
<entry key="i000004" count="7" red="100" green="100" blue="100">New</entry> <entry key="i000004" count="7" red="100" green="100" blue="100" shape="SQUARE">New</entry>
<entry key="i000005" count="0" red="200" green="50" blue="0">Minor</entry> <entry key="i000005" count="0" red="200" green="50" blue="0" shape="SQUARE">Minor</entry>
<entry key="i000006" count="0" red="200" green="150" blue="0">Major</entry> <entry key="i000006" count="0" red="200" green="150" blue="0" shape="SQUARE">Major</entry>
<entry key="i000007" count="0" red="50" green="200" blue="0">Main</entry> <entry key="i000007" count="0" red="50" green="200" blue="0" shape="SQUARE">Main</entry>
</importance> </importance>
</settings> </settings>
<content items="22" novelWords="0" notesWords="0"> <content items="22" novelWords="0" notesWords="0">
@@ -1,5 +1,5 @@
<?xml version='1.0' encoding='utf-8'?> <?xml version='1.0' encoding='utf-8'?>
<novelWriterXML appVersion="2.3b1" hexVersion="0x020300b1" fileVersion="1.5" fileRevision="2" timeStamp="2024-02-20 12:52:16"> <novelWriterXML appVersion="2.4rc1" hexVersion="0x020400c1" fileVersion="1.5" fileRevision="4" timeStamp="2024-04-09 22:34:16">
<project id="d0f3fe10-c6e6-4310-8bfd-181eb4224eed" saveCount="1" autoCount="0" editTime="0"> <project id="d0f3fe10-c6e6-4310-8bfd-181eb4224eed" saveCount="1" autoCount="0" editTime="0">
<name>Test Project B</name> <name>Test Project B</name>
<author>Jane Doe</author> <author>Jane Doe</author>
@@ -16,16 +16,16 @@
</lastHandle> </lastHandle>
<autoReplace /> <autoReplace />
<status> <status>
<entry key="s000000" count="9" red="100" green="100" blue="100">New</entry> <entry key="s000000" count="9" red="100" green="100" blue="100" shape="SQUARE">New</entry>
<entry key="s000001" count="0" red="200" green="50" blue="0">Note</entry> <entry key="s000001" count="0" red="200" green="50" blue="0" shape="SQUARE">Note</entry>
<entry key="s000002" count="0" red="200" green="150" blue="0">Draft</entry> <entry key="s000002" count="0" red="200" green="150" blue="0" shape="SQUARE">Draft</entry>
<entry key="s000003" count="0" red="50" green="200" blue="0">Finished</entry> <entry key="s000003" count="0" red="50" green="200" blue="0" shape="SQUARE">Finished</entry>
</status> </status>
<importance> <importance>
<entry key="i000004" count="7" red="100" green="100" blue="100">New</entry> <entry key="i000004" count="7" red="100" green="100" blue="100" shape="SQUARE">New</entry>
<entry key="i000005" count="0" red="200" green="50" blue="0">Minor</entry> <entry key="i000005" count="0" red="200" green="50" blue="0" shape="SQUARE">Minor</entry>
<entry key="i000006" count="0" red="200" green="150" blue="0">Major</entry> <entry key="i000006" count="0" red="200" green="150" blue="0" shape="SQUARE">Major</entry>
<entry key="i000007" count="0" red="50" green="200" blue="0">Main</entry> <entry key="i000007" count="0" red="50" green="200" blue="0" shape="SQUARE">Main</entry>
</importance> </importance>
</settings> </settings>
<content items="16" novelWords="0" notesWords="0"> <content items="16" novelWords="0" notesWords="0">
@@ -1,5 +1,5 @@
<?xml version='1.0' encoding='utf-8'?> <?xml version='1.0' encoding='utf-8'?>
<novelWriterXML appVersion="2.3a1" hexVersion="0x020300a1" fileVersion="1.5" fileRevision="2" timeStamp="2024-01-26 22:48:13"> <novelWriterXML appVersion="2.4rc1" hexVersion="0x020400c1" fileVersion="1.5" fileRevision="4" timeStamp="2024-04-09 22:31:18">
<project id="d0f3fe10-c6e6-4310-8bfd-181eb4224eed" saveCount="3" autoCount="2" editTime="3"> <project id="d0f3fe10-c6e6-4310-8bfd-181eb4224eed" saveCount="3" autoCount="2" editTime="3">
<name>New Project</name> <name>New Project</name>
<author>Jane Doe</author> <author>Jane Doe</author>
@@ -16,16 +16,16 @@
</lastHandle> </lastHandle>
<autoReplace /> <autoReplace />
<status> <status>
<entry key="s000000" count="5" red="100" green="100" blue="100">New</entry> <entry key="s000000" count="5" red="100" green="100" blue="100" shape="SQUARE">New</entry>
<entry key="s000001" count="0" red="200" green="50" blue="0">Note</entry> <entry key="s000001" count="0" red="200" green="50" blue="0" shape="SQUARE">Note</entry>
<entry key="s000002" count="0" red="200" green="150" blue="0">Draft</entry> <entry key="s000002" count="0" red="200" green="150" blue="0" shape="SQUARE">Draft</entry>
<entry key="s000003" count="0" red="50" green="200" blue="0">Finished</entry> <entry key="s000003" count="0" red="50" green="200" blue="0" shape="SQUARE">Finished</entry>
</status> </status>
<importance> <importance>
<entry key="i000004" count="6" red="100" green="100" blue="100">New</entry> <entry key="i000004" count="6" red="100" green="100" blue="100" shape="SQUARE">New</entry>
<entry key="i000005" count="0" red="200" green="50" blue="0">Minor</entry> <entry key="i000005" count="0" red="200" green="50" blue="0" shape="SQUARE">Minor</entry>
<entry key="i000006" count="0" red="200" green="150" blue="0">Major</entry> <entry key="i000006" count="0" red="200" green="150" blue="0" shape="SQUARE">Major</entry>
<entry key="i000007" count="0" red="50" green="200" blue="0">Main</entry> <entry key="i000007" count="0" red="50" green="200" blue="0" shape="SQUARE">Main</entry>
</importance> </importance>
</settings> </settings>
<content items="11" novelWords="142" notesWords="27"> <content items="11" novelWords="142" notesWords="27">
@@ -1,5 +1,5 @@
<?xml version='1.0' encoding='utf-8'?> <?xml version='1.0' encoding='utf-8'?>
<novelWriterXML appVersion="2.3a1" hexVersion="0x020300a1" fileVersion="1.5" fileRevision="2" timeStamp="2024-01-26 22:45:36"> <novelWriterXML appVersion="2.4rc1" hexVersion="0x020400c1" fileVersion="1.5" fileRevision="4" timeStamp="2024-04-09 22:31:14">
<project id="d0f3fe10-c6e6-4310-8bfd-181eb4224eed" saveCount="2" autoCount="1" editTime="0"> <project id="d0f3fe10-c6e6-4310-8bfd-181eb4224eed" saveCount="2" autoCount="1" editTime="0">
<name>New Project</name> <name>New Project</name>
<author>Jane Doe</author> <author>Jane Doe</author>
@@ -16,16 +16,16 @@
</lastHandle> </lastHandle>
<autoReplace /> <autoReplace />
<status> <status>
<entry key="s000000" count="5" red="100" green="100" blue="100">New</entry> <entry key="s000000" count="5" red="100" green="100" blue="100" shape="SQUARE">New</entry>
<entry key="s000001" count="0" red="200" green="50" blue="0">Note</entry> <entry key="s000001" count="0" red="200" green="50" blue="0" shape="SQUARE">Note</entry>
<entry key="s000002" count="0" red="200" green="150" blue="0">Draft</entry> <entry key="s000002" count="0" red="200" green="150" blue="0" shape="SQUARE">Draft</entry>
<entry key="s000003" count="0" red="50" green="200" blue="0">Finished</entry> <entry key="s000003" count="0" red="50" green="200" blue="0" shape="SQUARE">Finished</entry>
</status> </status>
<importance> <importance>
<entry key="i000004" count="3" red="100" green="100" blue="100">New</entry> <entry key="i000004" count="3" red="100" green="100" blue="100" shape="SQUARE">New</entry>
<entry key="i000005" count="0" red="200" green="50" blue="0">Minor</entry> <entry key="i000005" count="0" red="200" green="50" blue="0" shape="SQUARE">Minor</entry>
<entry key="i000006" count="0" red="200" green="150" blue="0">Major</entry> <entry key="i000006" count="0" red="200" green="150" blue="0" shape="SQUARE">Major</entry>
<entry key="i000007" count="0" red="50" green="200" blue="0">Main</entry> <entry key="i000007" count="0" red="50" green="200" blue="0" shape="SQUARE">Main</entry>
</importance> </importance>
</settings> </settings>
<content items="8" novelWords="9" notesWords="0"> <content items="8" novelWords="9" notesWords="0">
+12 -12
View File
@@ -1,5 +1,5 @@
<?xml version='1.0' encoding='utf-8'?> <?xml version='1.0' encoding='utf-8'?>
<novelWriterXML appVersion="2.0-rc1" hexVersion="0x020000c1" fileVersion="1.5" fileRevision="3" timeStamp="2020-05-28 09:59:15"> <novelWriterXML appVersion="2.0-rc1" hexVersion="0x020000c1" fileVersion="1.5" fileRevision="4" timeStamp="2020-05-28 09:59:15">
<project id="d0f3fe10-c6e6-4310-8bfd-181eb4224eed" saveCount="0" autoCount="0" editTime="1000"> <project id="d0f3fe10-c6e6-4310-8bfd-181eb4224eed" saveCount="0" autoCount="0" editTime="1000">
<name>Sample Project</name> <name>Sample Project</name>
<author>Jay Doh</author> <author>Jay Doh</author>
@@ -20,19 +20,19 @@
<entry key="C">D</entry> <entry key="C">D</entry>
</autoReplace> </autoReplace>
<status> <status>
<entry key="s000000" count="0" red="100" green="100" blue="100">New</entry> <entry key="s000000" count="0" red="100" green="100" blue="100" shape="SQUARE">New</entry>
<entry key="s000001" count="0" red="200" green="50" blue="0">Notes</entry> <entry key="s000001" count="0" red="200" green="50" blue="0" shape="SQUARE">Notes</entry>
<entry key="s000002" count="0" red="182" green="60" blue="0">Started</entry> <entry key="s000002" count="0" red="182" green="60" blue="0" shape="SQUARE">Started</entry>
<entry key="s000003" count="0" red="193" green="129" blue="0">1st Draft</entry> <entry key="s000003" count="0" red="193" green="129" blue="0" shape="SQUARE">1st Draft</entry>
<entry key="s000004" count="0" red="193" green="129" blue="0">2nd Draft</entry> <entry key="s000004" count="0" red="193" green="129" blue="0" shape="SQUARE">2nd Draft</entry>
<entry key="s000005" count="0" red="193" green="129" blue="0">3rd Draft</entry> <entry key="s000005" count="0" red="193" green="129" blue="0" shape="SQUARE">3rd Draft</entry>
<entry key="s000006" count="0" red="58" green="180" blue="58">Finished</entry> <entry key="s000006" count="0" red="58" green="180" blue="58" shape="SQUARE">Finished</entry>
</status> </status>
<importance> <importance>
<entry key="i000007" count="0" red="100" green="100" blue="100">None</entry> <entry key="i000007" count="0" red="100" green="100" blue="100" shape="SQUARE">None</entry>
<entry key="i000008" count="0" red="0" green="122" blue="188">Minor</entry> <entry key="i000008" count="0" red="0" green="122" blue="188" shape="SQUARE">Minor</entry>
<entry key="i000009" count="0" red="21" green="0" blue="180">Major</entry> <entry key="i000009" count="0" red="21" green="0" blue="180" shape="SQUARE">Major</entry>
<entry key="i00000a" count="0" red="117" green="0" blue="175">Main</entry> <entry key="i00000a" count="0" red="117" green="0" blue="175" shape="SQUARE">Main</entry>
</importance> </importance>
</settings> </settings>
<content items="22" novelWords="0" notesWords="0"> <content items="22" novelWords="0" notesWords="0">
+12 -12
View File
@@ -1,5 +1,5 @@
<?xml version='1.0' encoding='utf-8'?> <?xml version='1.0' encoding='utf-8'?>
<novelWriterXML appVersion="2.0-rc1" hexVersion="0x020000c1" fileVersion="1.5" fileRevision="3" timeStamp="2020-06-26 21:20:24"> <novelWriterXML appVersion="2.0-rc1" hexVersion="0x020000c1" fileVersion="1.5" fileRevision="4" timeStamp="2020-06-26 21:20:24">
<project id="d0f3fe10-c6e6-4310-8bfd-181eb4224eed" saveCount="5" autoCount="10" editTime="1000"> <project id="d0f3fe10-c6e6-4310-8bfd-181eb4224eed" saveCount="5" autoCount="10" editTime="1000">
<name>Sample Project</name> <name>Sample Project</name>
<author>Jay Doh</author> <author>Jay Doh</author>
@@ -20,19 +20,19 @@
<entry key="C">D</entry> <entry key="C">D</entry>
</autoReplace> </autoReplace>
<status> <status>
<entry key="s000000" count="0" red="100" green="100" blue="100">New</entry> <entry key="s000000" count="0" red="100" green="100" blue="100" shape="SQUARE">New</entry>
<entry key="s000001" count="0" red="200" green="50" blue="0">Notes</entry> <entry key="s000001" count="0" red="200" green="50" blue="0" shape="SQUARE">Notes</entry>
<entry key="s000002" count="0" red="182" green="60" blue="0">Started</entry> <entry key="s000002" count="0" red="182" green="60" blue="0" shape="SQUARE">Started</entry>
<entry key="s000003" count="0" red="193" green="129" blue="0">1st Draft</entry> <entry key="s000003" count="0" red="193" green="129" blue="0" shape="SQUARE">1st Draft</entry>
<entry key="s000004" count="0" red="193" green="129" blue="0">2nd Draft</entry> <entry key="s000004" count="0" red="193" green="129" blue="0" shape="SQUARE">2nd Draft</entry>
<entry key="s000005" count="0" red="193" green="129" blue="0">3rd Draft</entry> <entry key="s000005" count="0" red="193" green="129" blue="0" shape="SQUARE">3rd Draft</entry>
<entry key="s000006" count="0" red="58" green="180" blue="58">Finished</entry> <entry key="s000006" count="0" red="58" green="180" blue="58" shape="SQUARE">Finished</entry>
</status> </status>
<importance> <importance>
<entry key="i000007" count="0" red="100" green="100" blue="100">None</entry> <entry key="i000007" count="0" red="100" green="100" blue="100" shape="SQUARE">None</entry>
<entry key="i000008" count="0" red="0" green="122" blue="188">Minor</entry> <entry key="i000008" count="0" red="0" green="122" blue="188" shape="SQUARE">Minor</entry>
<entry key="i000009" count="0" red="21" green="0" blue="180">Major</entry> <entry key="i000009" count="0" red="21" green="0" blue="180" shape="SQUARE">Major</entry>
<entry key="i00000a" count="0" red="117" green="0" blue="175">Main</entry> <entry key="i00000a" count="0" red="117" green="0" blue="175" shape="SQUARE">Main</entry>
</importance> </importance>
</settings> </settings>
<content items="22" novelWords="0" notesWords="0"> <content items="22" novelWords="0" notesWords="0">
+12 -12
View File
@@ -1,5 +1,5 @@
<?xml version='1.0' encoding='utf-8'?> <?xml version='1.0' encoding='utf-8'?>
<novelWriterXML appVersion="2.0-rc1" hexVersion="0x020000c1" fileVersion="1.5" fileRevision="3" timeStamp="2021-08-30 23:33:44"> <novelWriterXML appVersion="2.0-rc1" hexVersion="0x020000c1" fileVersion="1.5" fileRevision="4" timeStamp="2021-08-30 23:33:44">
<project id="d0f3fe10-c6e6-4310-8bfd-181eb4224eed" saveCount="5" autoCount="10" editTime="1000"> <project id="d0f3fe10-c6e6-4310-8bfd-181eb4224eed" saveCount="5" autoCount="10" editTime="1000">
<name>Sample Project</name> <name>Sample Project</name>
<author>Jay Doh</author> <author>Jay Doh</author>
@@ -20,19 +20,19 @@
<entry key="C">D</entry> <entry key="C">D</entry>
</autoReplace> </autoReplace>
<status> <status>
<entry key="s000000" count="0" red="100" green="100" blue="100">New</entry> <entry key="s000000" count="0" red="100" green="100" blue="100" shape="SQUARE">New</entry>
<entry key="s000001" count="0" red="200" green="50" blue="0">Notes</entry> <entry key="s000001" count="0" red="200" green="50" blue="0" shape="SQUARE">Notes</entry>
<entry key="s000002" count="0" red="182" green="60" blue="0">Started</entry> <entry key="s000002" count="0" red="182" green="60" blue="0" shape="SQUARE">Started</entry>
<entry key="s000003" count="0" red="193" green="129" blue="0">1st Draft</entry> <entry key="s000003" count="0" red="193" green="129" blue="0" shape="SQUARE">1st Draft</entry>
<entry key="s000004" count="0" red="193" green="129" blue="0">2nd Draft</entry> <entry key="s000004" count="0" red="193" green="129" blue="0" shape="SQUARE">2nd Draft</entry>
<entry key="s000005" count="0" red="193" green="129" blue="0">3rd Draft</entry> <entry key="s000005" count="0" red="193" green="129" blue="0" shape="SQUARE">3rd Draft</entry>
<entry key="s000006" count="0" red="58" green="180" blue="58">Finished</entry> <entry key="s000006" count="0" red="58" green="180" blue="58" shape="SQUARE">Finished</entry>
</status> </status>
<importance> <importance>
<entry key="i000007" count="0" red="100" green="100" blue="100">None</entry> <entry key="i000007" count="0" red="100" green="100" blue="100" shape="SQUARE">None</entry>
<entry key="i000008" count="0" red="0" green="122" blue="188">Minor</entry> <entry key="i000008" count="0" red="0" green="122" blue="188" shape="SQUARE">Minor</entry>
<entry key="i000009" count="0" red="21" green="0" blue="180">Major</entry> <entry key="i000009" count="0" red="21" green="0" blue="180" shape="SQUARE">Major</entry>
<entry key="i00000a" count="0" red="117" green="0" blue="175">Main</entry> <entry key="i00000a" count="0" red="117" green="0" blue="175" shape="SQUARE">Main</entry>
</importance> </importance>
</settings> </settings>
<content items="25" novelWords="840" notesWords="376"> <content items="25" novelWords="840" notesWords="376">
+12 -12
View File
@@ -1,5 +1,5 @@
<?xml version='1.0' encoding='utf-8'?> <?xml version='1.0' encoding='utf-8'?>
<novelWriterXML appVersion="2.0-rc1" hexVersion="0x020000c1" fileVersion="1.5" fileRevision="3" timeStamp="2022-10-25 18:26:15"> <novelWriterXML appVersion="2.0-rc1" hexVersion="0x020000c1" fileVersion="1.5" fileRevision="4" timeStamp="2022-10-25 18:26:15">
<project id="d0f3fe10-c6e6-4310-8bfd-181eb4224eed" saveCount="5" autoCount="10" editTime="1000"> <project id="d0f3fe10-c6e6-4310-8bfd-181eb4224eed" saveCount="5" autoCount="10" editTime="1000">
<name>Sample Project</name> <name>Sample Project</name>
<author>Jay Doh</author> <author>Jay Doh</author>
@@ -20,19 +20,19 @@
<entry key="C">D</entry> <entry key="C">D</entry>
</autoReplace> </autoReplace>
<status> <status>
<entry key="s000000" count="0" red="100" green="100" blue="100">New</entry> <entry key="s000000" count="0" red="100" green="100" blue="100" shape="SQUARE">New</entry>
<entry key="s000001" count="0" red="200" green="50" blue="0">Notes</entry> <entry key="s000001" count="0" red="200" green="50" blue="0" shape="SQUARE">Notes</entry>
<entry key="s000002" count="0" red="182" green="60" blue="0">Started</entry> <entry key="s000002" count="0" red="182" green="60" blue="0" shape="SQUARE">Started</entry>
<entry key="s000003" count="0" red="193" green="129" blue="0">1st Draft</entry> <entry key="s000003" count="0" red="193" green="129" blue="0" shape="SQUARE">1st Draft</entry>
<entry key="s000004" count="0" red="193" green="129" blue="0">2nd Draft</entry> <entry key="s000004" count="0" red="193" green="129" blue="0" shape="SQUARE">2nd Draft</entry>
<entry key="s000005" count="0" red="193" green="129" blue="0">3rd Draft</entry> <entry key="s000005" count="0" red="193" green="129" blue="0" shape="SQUARE">3rd Draft</entry>
<entry key="s000006" count="0" red="58" green="180" blue="58">Finished</entry> <entry key="s000006" count="0" red="58" green="180" blue="58" shape="SQUARE">Finished</entry>
</status> </status>
<importance> <importance>
<entry key="i000007" count="0" red="100" green="100" blue="100">None</entry> <entry key="i000007" count="0" red="100" green="100" blue="100" shape="SQUARE">None</entry>
<entry key="i000008" count="0" red="0" green="122" blue="188">Minor</entry> <entry key="i000008" count="0" red="0" green="122" blue="188" shape="SQUARE">Minor</entry>
<entry key="i000009" count="0" red="21" green="0" blue="180">Major</entry> <entry key="i000009" count="0" red="21" green="0" blue="180" shape="SQUARE">Major</entry>
<entry key="i00000a" count="0" red="117" green="0" blue="175">Main</entry> <entry key="i00000a" count="0" red="117" green="0" blue="175" shape="SQUARE">Main</entry>
</importance> </importance>
</settings> </settings>
<content items="25" novelWords="830" notesWords="376"> <content items="25" novelWords="830" notesWords="376">
+12 -12
View File
@@ -1,5 +1,5 @@
<?xml version='1.0' encoding='utf-8'?> <?xml version='1.0' encoding='utf-8'?>
<novelWriterXML appVersion="2.0-rc1" hexVersion="0x020000c1" fileVersion="1.5" fileRevision="3" timeStamp="2022-10-15 12:12:59"> <novelWriterXML appVersion="2.0-rc1" hexVersion="0x020000c1" fileVersion="1.5" fileRevision="4" timeStamp="2022-10-15 12:12:59">
<project id="d0f3fe10-c6e6-4310-8bfd-181eb4224eed" saveCount="5" autoCount="10" editTime="1000"> <project id="d0f3fe10-c6e6-4310-8bfd-181eb4224eed" saveCount="5" autoCount="10" editTime="1000">
<name>Sample Project</name> <name>Sample Project</name>
<author>Jay Doh</author> <author>Jay Doh</author>
@@ -20,19 +20,19 @@
<entry key="C">D</entry> <entry key="C">D</entry>
</autoReplace> </autoReplace>
<status> <status>
<entry key="sf12341" count="4" red="100" green="100" blue="100">New</entry> <entry key="sf12341" count="4" red="100" green="100" blue="100" shape="SQUARE">New</entry>
<entry key="sf24ce6" count="2" red="200" green="50" blue="0">Notes</entry> <entry key="sf24ce6" count="2" red="200" green="50" blue="0" shape="SQUARE">Notes</entry>
<entry key="sc24b8f" count="3" red="182" green="60" blue="0">Started</entry> <entry key="sc24b8f" count="3" red="182" green="60" blue="0" shape="SQUARE">Started</entry>
<entry key="s90e6c9" count="7" red="193" green="129" blue="0">1st Draft</entry> <entry key="s90e6c9" count="7" red="193" green="129" blue="0" shape="SQUARE">1st Draft</entry>
<entry key="sd51c5b" count="0" red="193" green="129" blue="0">2nd Draft</entry> <entry key="sd51c5b" count="0" red="193" green="129" blue="0" shape="SQUARE">2nd Draft</entry>
<entry key="s8ae72a" count="0" red="193" green="129" blue="0">3rd Draft</entry> <entry key="s8ae72a" count="0" red="193" green="129" blue="0" shape="SQUARE">3rd Draft</entry>
<entry key="s78ea90" count="1" red="58" green="180" blue="58">Finished</entry> <entry key="s78ea90" count="1" red="58" green="180" blue="58" shape="SQUARE">Finished</entry>
</status> </status>
<importance> <importance>
<entry key="ia857f0" count="5" red="100" green="100" blue="100">None</entry> <entry key="ia857f0" count="5" red="100" green="100" blue="100" shape="SQUARE">None</entry>
<entry key="icfb3a5" count="2" red="0" green="122" blue="188">Minor</entry> <entry key="icfb3a5" count="2" red="0" green="122" blue="188" shape="SQUARE">Minor</entry>
<entry key="i2d7a54" count="2" red="21" green="0" blue="180">Major</entry> <entry key="i2d7a54" count="2" red="21" green="0" blue="180" shape="SQUARE">Major</entry>
<entry key="i56be10" count="1" red="117" green="0" blue="175">Main</entry> <entry key="i56be10" count="1" red="117" green="0" blue="175" shape="SQUARE">Main</entry>
</importance> </importance>
</settings> </settings>
<content items="27" novelWords="954" notesWords="409"> <content items="27" novelWords="954" notesWords="409">
+2 -2
View File
@@ -553,8 +553,8 @@ def testCoreItem_ClassDefaults(mockGUI):
def testCoreItem_PackUnpack(mockGUI, caplog, mockRnd): def testCoreItem_PackUnpack(mockGUI, caplog, mockRnd):
"""Test packing and unpacking entries for the NWItem class.""" """Test packing and unpacking entries for the NWItem class."""
project = NWProject() project = NWProject()
project.data.itemStatus.write(None, "New", (100, 100, 100)) project.data.itemStatus.add(None, "New", (100, 100, 100), "SQUARE", 0)
project.data.itemImport.write(None, "New", (100, 100, 100)) project.data.itemImport.add(None, "New", (100, 100, 100), "SQUARE", 0)
# Invalid # Invalid
item = NWItem(project, "0000000000000") item = NWItem(project, "0000000000000")
-109
View File
@@ -400,115 +400,6 @@ def testCoreProject_AccessItems(mockGUI, fncPath, mockRnd):
# END Test testCoreProject_AccessItems # 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.keys()):
assert project.data.itemStatus.remove(key) is True
project.data.itemImport.resetCounts()
for key in list(project.data.itemImport.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 @pytest.mark.core
def testCoreProject_Methods(monkeypatch, mockGUI, fncPath, mockRnd): def testCoreProject_Methods(monkeypatch, mockGUI, fncPath, mockRnd):
"""Test other project class methods and functions.""" """Test other project class methods and functions."""
+284 -204
View File
@@ -27,9 +27,12 @@ from shutil import copyfile
from datetime import datetime from datetime import datetime
from novelwriter.constants import nwFiles from novelwriter.constants import nwFiles
from novelwriter.enum import nwStatusShape
from tools import cmpFiles, writeFile from tools import cmpFiles, writeFile
from mocked import causeOSError from mocked import causeOSError
from PyQt5.QtGui import QColor
from novelwriter.core.item import NWItem from novelwriter.core.item import NWItem
from novelwriter.core.projectxml import ProjectXMLReader, ProjectXMLWriter, XMLReadState from novelwriter.core.projectxml import ProjectXMLReader, ProjectXMLWriter, XMLReadState
from novelwriter.core.projectdata import NWProjectData from novelwriter.core.projectdata import NWProjectData
@@ -131,7 +134,7 @@ def testCoreProjectXML_ReadCurrent(monkeypatch, tstPaths, fncPath):
assert xmlReader.state == XMLReadState.PARSED_OK assert xmlReader.state == XMLReadState.PARSED_OK
assert xmlReader.xmlRoot == "novelWriterXML" assert xmlReader.xmlRoot == "novelWriterXML"
assert xmlReader.xmlVersion == 0x0105 assert xmlReader.xmlVersion == 0x0105
assert xmlReader.xmlRevision == 3 assert xmlReader.xmlRevision == 4
assert xmlReader.appVersion == "2.0-rc1" assert xmlReader.appVersion == "2.0-rc1"
assert xmlReader.hexVersion == 0x020000c1 assert xmlReader.hexVersion == 0x020000c1
@@ -154,44 +157,57 @@ def testCoreProjectXML_ReadCurrent(monkeypatch, tstPaths, fncPath):
assert data.getLastHandle("novelTree") == "7031beac91f75" assert data.getLastHandle("novelTree") == "7031beac91f75"
assert data.getLastHandle("outline") == "7031beac91f75" assert data.getLastHandle("outline") == "7031beac91f75"
assert data.itemStatus.name("sf12341") == "New" assert data.itemStatus["sf12341"].name == "New"
assert data.itemStatus.name("sf24ce6") == "Notes" assert data.itemStatus["sf24ce6"].name == "Notes"
assert data.itemStatus.name("sc24b8f") == "Started" assert data.itemStatus["sc24b8f"].name == "Started"
assert data.itemStatus.name("s90e6c9") == "1st Draft" assert data.itemStatus["s90e6c9"].name == "1st Draft"
assert data.itemStatus.name("sd51c5b") == "2nd Draft" assert data.itemStatus["sd51c5b"].name == "2nd Draft"
assert data.itemStatus.name("s8ae72a") == "3rd Draft" assert data.itemStatus["s8ae72a"].name == "3rd Draft"
assert data.itemStatus.name("s78ea90") == "Finished" assert data.itemStatus["s78ea90"].name == "Finished"
assert data.itemImport.name("ia857f0") == "None" assert data.itemImport["ia857f0"].name == "None"
assert data.itemImport.name("icfb3a5") == "Minor" assert data.itemImport["icfb3a5"].name == "Minor"
assert data.itemImport.name("i2d7a54") == "Major" assert data.itemImport["i2d7a54"].name == "Major"
assert data.itemImport.name("i56be10") == "Main" assert data.itemImport["i56be10"].name == "Main"
assert data.itemStatus.cols("sf12341") == (100, 100, 100) assert data.itemStatus["sf12341"].color == QColor(100, 100, 100)
assert data.itemStatus.cols("sf24ce6") == (200, 50, 0) assert data.itemStatus["sf24ce6"].color == QColor(200, 50, 0)
assert data.itemStatus.cols("sc24b8f") == (182, 60, 0) assert data.itemStatus["sc24b8f"].color == QColor(182, 60, 0)
assert data.itemStatus.cols("s90e6c9") == (193, 129, 0) assert data.itemStatus["s90e6c9"].color == QColor(193, 129, 0)
assert data.itemStatus.cols("sd51c5b") == (193, 129, 0) assert data.itemStatus["sd51c5b"].color == QColor(193, 129, 0)
assert data.itemStatus.cols("s8ae72a") == (193, 129, 0) assert data.itemStatus["s8ae72a"].color == QColor(193, 129, 0)
assert data.itemStatus.cols("s78ea90") == (58, 180, 58) assert data.itemStatus["s78ea90"].color == QColor(58, 180, 58)
assert data.itemImport.cols("ia857f0") == (100, 100, 100) assert data.itemImport["ia857f0"].color == QColor(100, 100, 100)
assert data.itemImport.cols("icfb3a5") == (0, 122, 188) assert data.itemImport["icfb3a5"].color == QColor(0, 122, 188)
assert data.itemImport.cols("i2d7a54") == (21, 0, 180) assert data.itemImport["i2d7a54"].color == QColor(21, 0, 180)
assert data.itemImport.cols("i56be10") == (117, 0, 175) assert data.itemImport["i56be10"].color == QColor(117, 0, 175)
assert data.itemStatus.count("sf12341") == 4 assert data.itemStatus["sf12341"].shape == nwStatusShape.SQUARE
assert data.itemStatus.count("sf24ce6") == 2 assert data.itemStatus["sf24ce6"].shape == nwStatusShape.SQUARE
assert data.itemStatus.count("sc24b8f") == 3 assert data.itemStatus["sc24b8f"].shape == nwStatusShape.SQUARE
assert data.itemStatus.count("s90e6c9") == 7 assert data.itemStatus["s90e6c9"].shape == nwStatusShape.SQUARE
assert data.itemStatus.count("sd51c5b") == 0 assert data.itemStatus["sd51c5b"].shape == nwStatusShape.SQUARE
assert data.itemStatus.count("s8ae72a") == 0 assert data.itemStatus["s8ae72a"].shape == nwStatusShape.SQUARE
assert data.itemStatus.count("s78ea90") == 1 assert data.itemStatus["s78ea90"].shape == nwStatusShape.SQUARE
assert data.itemImport.count("ia857f0") == 5 assert data.itemImport["ia857f0"].shape == nwStatusShape.SQUARE
assert data.itemImport.count("icfb3a5") == 2 assert data.itemImport["icfb3a5"].shape == nwStatusShape.SQUARE
assert data.itemImport.count("i2d7a54") == 2 assert data.itemImport["i2d7a54"].shape == nwStatusShape.SQUARE
assert data.itemImport.count("i56be10") == 1 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 # Compare content
dumpFile = tstPaths.outDir / "projectXML_ReadCurrent.json" dumpFile = tstPaths.outDir / "projectXML_ReadCurrent.json"
@@ -272,44 +288,57 @@ def testCoreProjectXML_ReadLegacy10(tstPaths, fncPath, mockRnd):
assert data.getLastHandle("novelTree") is None # Doesn't exist in 1.0 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.getLastHandle("outline") is None # Doesn't exist in 1.0
assert data.itemStatus.name("s000000") == "New" assert data.itemStatus["s000000"].name == "New"
assert data.itemStatus.name("s000001") == "Notes" assert data.itemStatus["s000001"].name == "Notes"
assert data.itemStatus.name("s000002") == "Started" assert data.itemStatus["s000002"].name == "Started"
assert data.itemStatus.name("s000003") == "1st Draft" assert data.itemStatus["s000003"].name == "1st Draft"
assert data.itemStatus.name("s000004") == "2nd Draft" assert data.itemStatus["s000004"].name == "2nd Draft"
assert data.itemStatus.name("s000005") == "3rd Draft" assert data.itemStatus["s000005"].name == "3rd Draft"
assert data.itemStatus.name("s000006") == "Finished" assert data.itemStatus["s000006"].name == "Finished"
assert data.itemImport.name("i000007") == "None" assert data.itemImport["i000007"].name == "None"
assert data.itemImport.name("i000008") == "Minor" assert data.itemImport["i000008"].name == "Minor"
assert data.itemImport.name("i000009") == "Major" assert data.itemImport["i000009"].name == "Major"
assert data.itemImport.name("i00000a") == "Main" assert data.itemImport["i00000a"].name == "Main"
assert data.itemStatus.cols("s000000") == (100, 100, 100) assert data.itemStatus["s000000"].color == QColor(100, 100, 100)
assert data.itemStatus.cols("s000001") == (200, 50, 0) assert data.itemStatus["s000001"].color == QColor(200, 50, 0)
assert data.itemStatus.cols("s000002") == (182, 60, 0) assert data.itemStatus["s000002"].color == QColor(182, 60, 0)
assert data.itemStatus.cols("s000003") == (193, 129, 0) assert data.itemStatus["s000003"].color == QColor(193, 129, 0)
assert data.itemStatus.cols("s000004") == (193, 129, 0) assert data.itemStatus["s000004"].color == QColor(193, 129, 0)
assert data.itemStatus.cols("s000005") == (193, 129, 0) assert data.itemStatus["s000005"].color == QColor(193, 129, 0)
assert data.itemStatus.cols("s000006") == (58, 180, 58) assert data.itemStatus["s000006"].color == QColor(58, 180, 58)
assert data.itemImport.cols("i000007") == (100, 100, 100) assert data.itemImport["i000007"].color == QColor(100, 100, 100)
assert data.itemImport.cols("i000008") == (0, 122, 188) assert data.itemImport["i000008"].color == QColor(0, 122, 188)
assert data.itemImport.cols("i000009") == (21, 0, 180) assert data.itemImport["i000009"].color == QColor(21, 0, 180)
assert data.itemImport.cols("i00000a") == (117, 0, 175) assert data.itemImport["i00000a"].color == QColor(117, 0, 175)
assert data.itemStatus.count("s000000") == 0 assert data.itemStatus["s000000"].shape == nwStatusShape.SQUARE
assert data.itemStatus.count("s000001") == 0 assert data.itemStatus["s000001"].shape == nwStatusShape.SQUARE
assert data.itemStatus.count("s000002") == 0 assert data.itemStatus["s000002"].shape == nwStatusShape.SQUARE
assert data.itemStatus.count("s000003") == 0 assert data.itemStatus["s000003"].shape == nwStatusShape.SQUARE
assert data.itemStatus.count("s000004") == 0 assert data.itemStatus["s000004"].shape == nwStatusShape.SQUARE
assert data.itemStatus.count("s000005") == 0 assert data.itemStatus["s000005"].shape == nwStatusShape.SQUARE
assert data.itemStatus.count("s000006") == 0 assert data.itemStatus["s000006"].shape == nwStatusShape.SQUARE
assert data.itemImport.count("i000007") == 0 assert data.itemImport["i000007"].shape == nwStatusShape.SQUARE
assert data.itemImport.count("i000008") == 0 assert data.itemImport["i000008"].shape == nwStatusShape.SQUARE
assert data.itemImport.count("i000009") == 0 assert data.itemImport["i000009"].shape == nwStatusShape.SQUARE
assert data.itemImport.count("i00000a") == 0 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 # Compare content
dumpFile = tstPaths.outDir / "projectXML_ReadLegacy10.json" dumpFile = tstPaths.outDir / "projectXML_ReadLegacy10.json"
@@ -325,7 +354,7 @@ def testCoreProjectXML_ReadLegacy10(tstPaths, fncPath, mockRnd):
for entry in content: for entry in content:
item = NWItem(mockProject, "0000000000000") # type: ignore item = NWItem(mockProject, "0000000000000") # type: ignore
item.unpack(entry) item.unpack(entry)
status[item.itemHandle] = item.getImportStatus(incIcon=False)[0] status[item.itemHandle] = item.getImportStatus()[0]
packedContent.append(item.pack()) packedContent.append(item.pack())
assert status == { assert status == {
@@ -406,44 +435,57 @@ def testCoreProjectXML_ReadLegacy11(tstPaths, fncPath, mockRnd):
assert data.getLastHandle("novelTree") is None # Doesn't exist in 1.1 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.getLastHandle("outline") is None # Doesn't exist in 1.1
assert data.itemStatus.name("s000000") == "New" assert data.itemStatus["s000000"].name == "New"
assert data.itemStatus.name("s000001") == "Notes" assert data.itemStatus["s000001"].name == "Notes"
assert data.itemStatus.name("s000002") == "Started" assert data.itemStatus["s000002"].name == "Started"
assert data.itemStatus.name("s000003") == "1st Draft" assert data.itemStatus["s000003"].name == "1st Draft"
assert data.itemStatus.name("s000004") == "2nd Draft" assert data.itemStatus["s000004"].name == "2nd Draft"
assert data.itemStatus.name("s000005") == "3rd Draft" assert data.itemStatus["s000005"].name == "3rd Draft"
assert data.itemStatus.name("s000006") == "Finished" assert data.itemStatus["s000006"].name == "Finished"
assert data.itemImport.name("i000007") == "None" assert data.itemImport["i000007"].name == "None"
assert data.itemImport.name("i000008") == "Minor" assert data.itemImport["i000008"].name == "Minor"
assert data.itemImport.name("i000009") == "Major" assert data.itemImport["i000009"].name == "Major"
assert data.itemImport.name("i00000a") == "Main" assert data.itemImport["i00000a"].name == "Main"
assert data.itemStatus.cols("s000000") == (100, 100, 100) assert data.itemStatus["s000000"].color == QColor(100, 100, 100)
assert data.itemStatus.cols("s000001") == (200, 50, 0) assert data.itemStatus["s000001"].color == QColor(200, 50, 0)
assert data.itemStatus.cols("s000002") == (182, 60, 0) assert data.itemStatus["s000002"].color == QColor(182, 60, 0)
assert data.itemStatus.cols("s000003") == (193, 129, 0) assert data.itemStatus["s000003"].color == QColor(193, 129, 0)
assert data.itemStatus.cols("s000004") == (193, 129, 0) assert data.itemStatus["s000004"].color == QColor(193, 129, 0)
assert data.itemStatus.cols("s000005") == (193, 129, 0) assert data.itemStatus["s000005"].color == QColor(193, 129, 0)
assert data.itemStatus.cols("s000006") == (58, 180, 58) assert data.itemStatus["s000006"].color == QColor(58, 180, 58)
assert data.itemImport.cols("i000007") == (100, 100, 100) assert data.itemImport["i000007"].color == QColor(100, 100, 100)
assert data.itemImport.cols("i000008") == (0, 122, 188) assert data.itemImport["i000008"].color == QColor(0, 122, 188)
assert data.itemImport.cols("i000009") == (21, 0, 180) assert data.itemImport["i000009"].color == QColor(21, 0, 180)
assert data.itemImport.cols("i00000a") == (117, 0, 175) assert data.itemImport["i00000a"].color == QColor(117, 0, 175)
assert data.itemStatus.count("s000000") == 0 assert data.itemStatus["s000000"].shape == nwStatusShape.SQUARE
assert data.itemStatus.count("s000001") == 0 assert data.itemStatus["s000001"].shape == nwStatusShape.SQUARE
assert data.itemStatus.count("s000002") == 0 assert data.itemStatus["s000002"].shape == nwStatusShape.SQUARE
assert data.itemStatus.count("s000003") == 0 assert data.itemStatus["s000003"].shape == nwStatusShape.SQUARE
assert data.itemStatus.count("s000004") == 0 assert data.itemStatus["s000004"].shape == nwStatusShape.SQUARE
assert data.itemStatus.count("s000005") == 0 assert data.itemStatus["s000005"].shape == nwStatusShape.SQUARE
assert data.itemStatus.count("s000006") == 0 assert data.itemStatus["s000006"].shape == nwStatusShape.SQUARE
assert data.itemImport.count("i000007") == 0 assert data.itemImport["i000007"].shape == nwStatusShape.SQUARE
assert data.itemImport.count("i000008") == 0 assert data.itemImport["i000008"].shape == nwStatusShape.SQUARE
assert data.itemImport.count("i000009") == 0 assert data.itemImport["i000009"].shape == nwStatusShape.SQUARE
assert data.itemImport.count("i00000a") == 0 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 # Compare content
dumpFile = tstPaths.outDir / "projectXML_ReadLegacy11.json" dumpFile = tstPaths.outDir / "projectXML_ReadLegacy11.json"
@@ -459,7 +501,7 @@ def testCoreProjectXML_ReadLegacy11(tstPaths, fncPath, mockRnd):
for entry in content: for entry in content:
item = NWItem(mockProject, "0000000000000") # type: ignore item = NWItem(mockProject, "0000000000000") # type: ignore
item.unpack(entry) item.unpack(entry)
status[item.itemHandle] = item.getImportStatus(incIcon=False)[0] status[item.itemHandle] = item.getImportStatus()[0]
packedContent.append(item.pack()) packedContent.append(item.pack())
assert status == { assert status == {
@@ -540,44 +582,57 @@ def testCoreProjectXML_ReadLegacy12(tstPaths, fncPath, mockRnd):
assert data.getLastHandle("novelTree") is None # Doesn't exist in 1.2 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.getLastHandle("outline") is None # Doesn't exist in 1.2
assert data.itemStatus.name("s000000") == "New" assert data.itemStatus["s000000"].name == "New"
assert data.itemStatus.name("s000001") == "Notes" assert data.itemStatus["s000001"].name == "Notes"
assert data.itemStatus.name("s000002") == "Started" assert data.itemStatus["s000002"].name == "Started"
assert data.itemStatus.name("s000003") == "1st Draft" assert data.itemStatus["s000003"].name == "1st Draft"
assert data.itemStatus.name("s000004") == "2nd Draft" assert data.itemStatus["s000004"].name == "2nd Draft"
assert data.itemStatus.name("s000005") == "3rd Draft" assert data.itemStatus["s000005"].name == "3rd Draft"
assert data.itemStatus.name("s000006") == "Finished" assert data.itemStatus["s000006"].name == "Finished"
assert data.itemImport.name("i000007") == "None" assert data.itemImport["i000007"].name == "None"
assert data.itemImport.name("i000008") == "Minor" assert data.itemImport["i000008"].name == "Minor"
assert data.itemImport.name("i000009") == "Major" assert data.itemImport["i000009"].name == "Major"
assert data.itemImport.name("i00000a") == "Main" assert data.itemImport["i00000a"].name == "Main"
assert data.itemStatus.cols("s000000") == (100, 100, 100) assert data.itemStatus["s000000"].color == QColor(100, 100, 100)
assert data.itemStatus.cols("s000001") == (200, 50, 0) assert data.itemStatus["s000001"].color == QColor(200, 50, 0)
assert data.itemStatus.cols("s000002") == (182, 60, 0) assert data.itemStatus["s000002"].color == QColor(182, 60, 0)
assert data.itemStatus.cols("s000003") == (193, 129, 0) assert data.itemStatus["s000003"].color == QColor(193, 129, 0)
assert data.itemStatus.cols("s000004") == (193, 129, 0) assert data.itemStatus["s000004"].color == QColor(193, 129, 0)
assert data.itemStatus.cols("s000005") == (193, 129, 0) assert data.itemStatus["s000005"].color == QColor(193, 129, 0)
assert data.itemStatus.cols("s000006") == (58, 180, 58) assert data.itemStatus["s000006"].color == QColor(58, 180, 58)
assert data.itemImport.cols("i000007") == (100, 100, 100) assert data.itemImport["i000007"].color == QColor(100, 100, 100)
assert data.itemImport.cols("i000008") == (0, 122, 188) assert data.itemImport["i000008"].color == QColor(0, 122, 188)
assert data.itemImport.cols("i000009") == (21, 0, 180) assert data.itemImport["i000009"].color == QColor(21, 0, 180)
assert data.itemImport.cols("i00000a") == (117, 0, 175) assert data.itemImport["i00000a"].color == QColor(117, 0, 175)
assert data.itemStatus.count("s000000") == 0 assert data.itemStatus["s000000"].shape == nwStatusShape.SQUARE
assert data.itemStatus.count("s000001") == 0 assert data.itemStatus["s000001"].shape == nwStatusShape.SQUARE
assert data.itemStatus.count("s000002") == 0 assert data.itemStatus["s000002"].shape == nwStatusShape.SQUARE
assert data.itemStatus.count("s000003") == 0 assert data.itemStatus["s000003"].shape == nwStatusShape.SQUARE
assert data.itemStatus.count("s000004") == 0 assert data.itemStatus["s000004"].shape == nwStatusShape.SQUARE
assert data.itemStatus.count("s000005") == 0 assert data.itemStatus["s000005"].shape == nwStatusShape.SQUARE
assert data.itemStatus.count("s000006") == 0 assert data.itemStatus["s000006"].shape == nwStatusShape.SQUARE
assert data.itemImport.count("i000007") == 0 assert data.itemImport["i000007"].shape == nwStatusShape.SQUARE
assert data.itemImport.count("i000008") == 0 assert data.itemImport["i000008"].shape == nwStatusShape.SQUARE
assert data.itemImport.count("i000009") == 0 assert data.itemImport["i000009"].shape == nwStatusShape.SQUARE
assert data.itemImport.count("i00000a") == 0 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 # Compare content
dumpFile = tstPaths.outDir / "projectXML_ReadLegacy12.json" dumpFile = tstPaths.outDir / "projectXML_ReadLegacy12.json"
@@ -593,7 +648,7 @@ def testCoreProjectXML_ReadLegacy12(tstPaths, fncPath, mockRnd):
for entry in content: for entry in content:
item = NWItem(mockProject, "0000000000000") # type: ignore item = NWItem(mockProject, "0000000000000") # type: ignore
item.unpack(entry) item.unpack(entry)
status[item.itemHandle] = item.getImportStatus(incIcon=False)[0] status[item.itemHandle] = item.getImportStatus()[0]
packedContent.append(item.pack()) packedContent.append(item.pack())
assert status == { assert status == {
@@ -677,44 +732,57 @@ def testCoreProjectXML_ReadLegacy13(tstPaths, fncPath, mockRnd):
assert data.getLastHandle("novelTree") is None # Doesn't exist in 1.3 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.getLastHandle("outline") is None # Doesn't exist in 1.3
assert data.itemStatus.name("s000000") == "New" assert data.itemStatus["s000000"].name == "New"
assert data.itemStatus.name("s000001") == "Notes" assert data.itemStatus["s000001"].name == "Notes"
assert data.itemStatus.name("s000002") == "Started" assert data.itemStatus["s000002"].name == "Started"
assert data.itemStatus.name("s000003") == "1st Draft" assert data.itemStatus["s000003"].name == "1st Draft"
assert data.itemStatus.name("s000004") == "2nd Draft" assert data.itemStatus["s000004"].name == "2nd Draft"
assert data.itemStatus.name("s000005") == "3rd Draft" assert data.itemStatus["s000005"].name == "3rd Draft"
assert data.itemStatus.name("s000006") == "Finished" assert data.itemStatus["s000006"].name == "Finished"
assert data.itemImport.name("i000007") == "None" assert data.itemImport["i000007"].name == "None"
assert data.itemImport.name("i000008") == "Minor" assert data.itemImport["i000008"].name == "Minor"
assert data.itemImport.name("i000009") == "Major" assert data.itemImport["i000009"].name == "Major"
assert data.itemImport.name("i00000a") == "Main" assert data.itemImport["i00000a"].name == "Main"
assert data.itemStatus.cols("s000000") == (100, 100, 100) assert data.itemStatus["s000000"].color == QColor(100, 100, 100)
assert data.itemStatus.cols("s000001") == (200, 50, 0) assert data.itemStatus["s000001"].color == QColor(200, 50, 0)
assert data.itemStatus.cols("s000002") == (182, 60, 0) assert data.itemStatus["s000002"].color == QColor(182, 60, 0)
assert data.itemStatus.cols("s000003") == (193, 129, 0) assert data.itemStatus["s000003"].color == QColor(193, 129, 0)
assert data.itemStatus.cols("s000004") == (193, 129, 0) assert data.itemStatus["s000004"].color == QColor(193, 129, 0)
assert data.itemStatus.cols("s000005") == (193, 129, 0) assert data.itemStatus["s000005"].color == QColor(193, 129, 0)
assert data.itemStatus.cols("s000006") == (58, 180, 58) assert data.itemStatus["s000006"].color == QColor(58, 180, 58)
assert data.itemImport.cols("i000007") == (100, 100, 100) assert data.itemImport["i000007"].color == QColor(100, 100, 100)
assert data.itemImport.cols("i000008") == (0, 122, 188) assert data.itemImport["i000008"].color == QColor(0, 122, 188)
assert data.itemImport.cols("i000009") == (21, 0, 180) assert data.itemImport["i000009"].color == QColor(21, 0, 180)
assert data.itemImport.cols("i00000a") == (117, 0, 175) assert data.itemImport["i00000a"].color == QColor(117, 0, 175)
assert data.itemStatus.count("s000000") == 0 assert data.itemStatus["s000000"].shape == nwStatusShape.SQUARE
assert data.itemStatus.count("s000001") == 0 assert data.itemStatus["s000001"].shape == nwStatusShape.SQUARE
assert data.itemStatus.count("s000002") == 0 assert data.itemStatus["s000002"].shape == nwStatusShape.SQUARE
assert data.itemStatus.count("s000003") == 0 assert data.itemStatus["s000003"].shape == nwStatusShape.SQUARE
assert data.itemStatus.count("s000004") == 0 assert data.itemStatus["s000004"].shape == nwStatusShape.SQUARE
assert data.itemStatus.count("s000005") == 0 assert data.itemStatus["s000005"].shape == nwStatusShape.SQUARE
assert data.itemStatus.count("s000006") == 0 assert data.itemStatus["s000006"].shape == nwStatusShape.SQUARE
assert data.itemImport.count("i000007") == 0 assert data.itemImport["i000007"].shape == nwStatusShape.SQUARE
assert data.itemImport.count("i000008") == 0 assert data.itemImport["i000008"].shape == nwStatusShape.SQUARE
assert data.itemImport.count("i000009") == 0 assert data.itemImport["i000009"].shape == nwStatusShape.SQUARE
assert data.itemImport.count("i00000a") == 0 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 # Compare content
dumpFile = tstPaths.outDir / "projectXML_ReadLegacy13.json" dumpFile = tstPaths.outDir / "projectXML_ReadLegacy13.json"
@@ -730,7 +798,7 @@ def testCoreProjectXML_ReadLegacy13(tstPaths, fncPath, mockRnd):
for entry in content: for entry in content:
item = NWItem(mockProject, "0000000000000") # type: ignore item = NWItem(mockProject, "0000000000000") # type: ignore
item.unpack(entry) item.unpack(entry)
status[item.itemHandle] = item.getImportStatus(incIcon=False)[0] status[item.itemHandle] = item.getImportStatus()[0]
packedContent.append(item.pack()) packedContent.append(item.pack())
assert status == { assert status == {
@@ -814,44 +882,56 @@ def testCoreProjectXML_ReadLegacy14(tstPaths, fncPath, mockRnd):
assert data.getLastHandle("novelTree") is None # Doesn't exist in 1.3 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.getLastHandle("outline") is None # Doesn't exist in 1.3
assert data.itemStatus.name("sf12341") == "New" assert data.itemStatus["sf12341"].name == "New"
assert data.itemStatus.name("sf24ce6") == "Notes" assert data.itemStatus["sf24ce6"].name == "Notes"
assert data.itemStatus.name("sc24b8f") == "Started" assert data.itemStatus["sc24b8f"].name == "Started"
assert data.itemStatus.name("s90e6c9") == "1st Draft" assert data.itemStatus["s90e6c9"].name == "1st Draft"
assert data.itemStatus.name("sd51c5b") == "2nd Draft" assert data.itemStatus["sd51c5b"].name == "2nd Draft"
assert data.itemStatus.name("s8ae72a") == "3rd Draft" assert data.itemStatus["s8ae72a"].name == "3rd Draft"
assert data.itemStatus.name("s78ea90") == "Finished" assert data.itemStatus["s78ea90"].name == "Finished"
assert data.itemImport.name("ia857f0") == "None" assert data.itemImport["ia857f0"].name == "None"
assert data.itemImport.name("icfb3a5") == "Minor" assert data.itemImport["icfb3a5"].name == "Minor"
assert data.itemImport.name("i2d7a54") == "Major" assert data.itemImport["i2d7a54"].name == "Major"
assert data.itemImport.name("i56be10") == "Main" assert data.itemImport["i56be10"].name == "Main"
assert data.itemStatus.cols("sf12341") == (100, 100, 100) assert data.itemStatus["sf12341"].color == QColor(100, 100, 100)
assert data.itemStatus.cols("sf24ce6") == (200, 50, 0) assert data.itemStatus["sf24ce6"].color == QColor(200, 50, 0)
assert data.itemStatus.cols("sc24b8f") == (182, 60, 0) assert data.itemStatus["sc24b8f"].color == QColor(182, 60, 0)
assert data.itemStatus.cols("s90e6c9") == (193, 129, 0) assert data.itemStatus["s90e6c9"].color == QColor(193, 129, 0)
assert data.itemStatus.cols("sd51c5b") == (193, 129, 0) assert data.itemStatus["sd51c5b"].color == QColor(193, 129, 0)
assert data.itemStatus.cols("s8ae72a") == (193, 129, 0) assert data.itemStatus["s8ae72a"].color == QColor(193, 129, 0)
assert data.itemStatus.cols("s78ea90") == (58, 180, 58) assert data.itemStatus["s78ea90"].color == QColor(58, 180, 58)
assert data.itemImport.cols("ia857f0") == (100, 100, 100) assert data.itemImport["ia857f0"].color == QColor(100, 100, 100)
assert data.itemImport.cols("icfb3a5") == (0, 122, 188) assert data.itemImport["icfb3a5"].color == QColor(0, 122, 188)
assert data.itemImport.cols("i2d7a54") == (21, 0, 180) assert data.itemImport["i2d7a54"].color == QColor(21, 0, 180)
assert data.itemImport.cols("i56be10") == (117, 0, 175) assert data.itemImport["i56be10"].color == QColor(117, 0, 175)
assert data.itemStatus.count("sf12341") == 4 assert data.itemStatus["sf12341"].shape == nwStatusShape.SQUARE
assert data.itemStatus.count("sf24ce6") == 2 assert data.itemStatus["sf24ce6"].shape == nwStatusShape.SQUARE
assert data.itemStatus.count("sc24b8f") == 3 assert data.itemStatus["sc24b8f"].shape == nwStatusShape.SQUARE
assert data.itemStatus.count("s90e6c9") == 7 assert data.itemStatus["s90e6c9"].shape == nwStatusShape.SQUARE
assert data.itemStatus.count("sd51c5b") == 0 assert data.itemStatus["sd51c5b"].shape == nwStatusShape.SQUARE
assert data.itemStatus.count("s8ae72a") == 0 assert data.itemStatus["s8ae72a"].shape == nwStatusShape.SQUARE
assert data.itemStatus.count("s78ea90") == 1 assert data.itemStatus["s78ea90"].shape == nwStatusShape.SQUARE
assert data.itemImport.count("ia857f0") == 5 assert data.itemImport["ia857f0"].shape == nwStatusShape.SQUARE
assert data.itemImport.count("icfb3a5") == 2 assert data.itemImport["icfb3a5"].shape == nwStatusShape.SQUARE
assert data.itemImport.count("i2d7a54") == 2 assert data.itemImport["i2d7a54"].shape == nwStatusShape.SQUARE
assert data.itemImport.count("i56be10") == 1 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 # Compare content
dumpFile = tstPaths.outDir / "projectXML_ReadLegacy14.json" dumpFile = tstPaths.outDir / "projectXML_ReadLegacy14.json"
@@ -867,7 +947,7 @@ def testCoreProjectXML_ReadLegacy14(tstPaths, fncPath, mockRnd):
for entry in content: for entry in content:
item = NWItem(mockProject, "0000000000000") # type: ignore item = NWItem(mockProject, "0000000000000") # type: ignore
item.unpack(entry) item.unpack(entry)
status[item.itemHandle] = item.getImportStatus(incIcon=False)[0] status[item.itemHandle] = item.getImportStatus()[0]
packedContent.append(item.pack()) packedContent.append(item.pack())
assert status == { assert status == {
+223 -175
View File
@@ -24,24 +24,50 @@ import pytest
from tools import C from tools import C
from PyQt5.QtGui import QIcon 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] statusKeys = [C.sNew, C.sNote, C.sDraft, C.sFinished]
importKeys = [C.iNew, C.iMinor, C.iMajor, C.iMain] importKeys = [C.iNew, C.iMinor, C.iMajor, C.iMain]
@pytest.mark.core @pytest.mark.core
def testCoreStatus_Internal(mockRnd): def testCoreStatus_StatusEntry():
"""Test all the internal functions of the NWStatus class. """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) nStatus = NWStatus(NWStatus.STATUS)
nImport = NWStatus(NWStatus.IMPORT) nImport = NWStatus(NWStatus.IMPORT)
with pytest.raises(Exception):
NWStatus(999)
# Generate Key # Generate Key
# ============ # ============
@@ -49,14 +75,14 @@ def testCoreStatus_Internal(mockRnd):
assert nStatus._newKey() == statusKeys[1] assert nStatus._newKey() == statusKeys[1]
# Key collision, should move to key 3 # Key collision, should move to key 3
nStatus.write(statusKeys[2], "Crash", (0, 0, 0)) nStatus.add(statusKeys[2], "Crash", (0, 0, 0), "SQUARE", 0)
assert nStatus._newKey() == statusKeys[3] assert nStatus._newKey() == statusKeys[3]
assert nImport._newKey() == importKeys[0] assert nImport._newKey() == importKeys[0]
assert nImport._newKey() == importKeys[1] assert nImport._newKey() == importKeys[1]
# Key collision, should move to key 3 # Key collision, should move to key 3
nImport.write(importKeys[2], "Crash", (0, 0, 0)) nImport.add(importKeys[2], "Crash", (0, 0, 0), "SQUARE", 0)
assert nImport._newKey() == importKeys[3] assert nImport._newKey() == importKeys[3]
# Check Key # Check Key
@@ -82,81 +108,94 @@ def testCoreStatus_Internal(mockRnd):
assert nImport._isKey("i12345F") is False # Not a lower case hex value assert nImport._isKey("i12345F") is False # Not a lower case hex value
assert nImport._isKey("i12345f") is True # Valid 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 # END Test testCoreStatus_Internal
@pytest.mark.core @pytest.mark.core
def testCoreStatus_Iterator(mockRnd): def testCoreStatus_Iterator(mockGUI, mockRnd):
"""Test the iterator functions of the NWStatus class. """Test the iterator functions of the NWStatus class."""
"""
nStatus = NWStatus(NWStatus.STATUS) nStatus = NWStatus(NWStatus.STATUS)
nStatus.add(None, "New", (100, 100, 100), "SQUARE", 0)
nStatus.write(None, "New", (100, 100, 100)) nStatus.add(None, "Note", (200, 50, 0), "CIRCLE", 1)
nStatus.write(None, "Note", (200, 50, 0)) nStatus.add(None, "Draft", (200, 150, 0), "SQUARE", 2)
nStatus.write(None, "Draft", (200, 150, 0)) nStatus.add(None, "Finished", (50, 200, 0), "CIRCLE", 3)
nStatus.write(None, "Finished", (50, 200, 0))
# Direct access # Direct access
entry = nStatus[statusKeys[0]] entry = nStatus[statusKeys[0]]
assert entry["cols"] == (100, 100, 100) assert entry.color == QColor(100, 100, 100)
assert entry["name"] == "New" assert entry.name == "New"
assert entry["count"] == 0 assert entry.count == 0
assert isinstance(entry["icon"], QIcon) assert isinstance(entry.icon, QIcon)
# Iterate # Length
entries = list(nStatus) assert len(nStatus._store) == 4
assert len(entries) == 4
assert len(nStatus) == 4 assert len(nStatus) == 4
# Keys # Content : Keys
assert list(nStatus.keys()) == statusKeys assert [k for k, _ in nStatus.iterItems()] == [
"s000000", "s000001", "s000002", "s000003"
]
# Items # Content : Names
for index, (key, entry) in enumerate(nStatus.items()): assert [e.name for _, e in nStatus.iterItems()] == [
assert key == statusKeys[index] "New", "Note", "Draft", "Finished"
assert "cols" in entry ]
assert "name" in entry
assert "count" in entry
assert "icon" in entry
# Valuse # Content : Colours
for entry in nStatus.values(): assert [e.color for _, e in nStatus.iterItems()] == [
assert "cols" in entry QColor(100, 100, 100), QColor(200, 50, 0), QColor(200, 150, 0), QColor(50, 200, 0)
assert "name" in entry ]
assert "count" in entry
assert "icon" in entry # 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 # END Test testCoreStatus_Iterator
@pytest.mark.core @pytest.mark.core
def testCoreStatus_Entries(mockRnd): def testCoreStatus_Entries(mockGUI, mockRnd):
"""Test all the simple setters for the NWStatus class. """Test all the simple setters for the NWStatus class."""
"""
nStatus = NWStatus(NWStatus.STATUS) nStatus = NWStatus(NWStatus.STATUS)
# Write # Add
# ===== # ===
# Have a key # Has a key
nStatus.write(statusKeys[0], "Entry 1", (200, 100, 50)) nStatus.add(statusKeys[0], "Entry 1", (200, 100, 50), "SQUARE", 0)
assert nStatus[statusKeys[0]]["name"] == "Entry 1" assert nStatus[statusKeys[0]].name == "Entry 1"
assert nStatus[statusKeys[0]]["cols"] == (200, 100, 50) 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.write(None, "Entry 2", (210, 110, 60)) nStatus.add(None, "Entry 2", (210, 110, 60), "SQUARE", 0)
assert nStatus[statusKeys[1]]["name"] == "Entry 2" assert nStatus[statusKeys[1]].name == "Entry 2"
assert nStatus[statusKeys[1]]["cols"] == (210, 110, 60) assert nStatus[statusKeys[1]].color == QColor(210, 110, 60)
assert nStatus[statusKeys[1]].shape == nwStatusShape.SQUARE
# Wrong colour spec # Wrong colour spec, unknown shape
nStatus.write(None, "Entry 3", "what?") nStatus.add(None, "Entry 3", "what?", "", 0) # type: ignore
assert nStatus[statusKeys[2]]["name"] == "Entry 3" assert nStatus[statusKeys[2]].name == "Entry 3"
assert nStatus[statusKeys[2]]["cols"] == (100, 100, 100) assert nStatus[statusKeys[2]].color == QColor(100, 100, 100)
assert nStatus[statusKeys[2]].shape == nwStatusShape.SQUARE
# Wrong colour count # Wrong colour count
nStatus.write(None, "Entry 4", (10, 20)) nStatus.add(None, "Entry 4", (10, 20), "CIRCLE", 0) # type: ignore
assert nStatus[statusKeys[3]]["name"] == "Entry 4" assert nStatus[statusKeys[3]].name == "Entry 4"
assert nStatus[statusKeys[3]]["cols"] == (100, 100, 100) assert nStatus[statusKeys[3]].color == QColor(100, 100, 100)
assert nStatus[statusKeys[3]].shape == nwStatusShape.CIRCLE
# Check # Check
# ===== # =====
@@ -171,29 +210,38 @@ def testCoreStatus_Entries(mockRnd):
# Name Access # Name Access
# =========== # ===========
assert nStatus.name(statusKeys[0]) == "Entry 1" assert nStatus[statusKeys[0]].name == "Entry 1"
assert nStatus.name(statusKeys[1]) == "Entry 2" assert nStatus[statusKeys[1]].name == "Entry 2"
assert nStatus.name(statusKeys[2]) == "Entry 3" assert nStatus[statusKeys[2]].name == "Entry 3"
assert nStatus.name(statusKeys[3]) == "Entry 4" assert nStatus[statusKeys[3]].name == "Entry 4"
assert nStatus.name("blablabla") == "Entry 1" assert nStatus["blablabla"].name == "Entry 1"
# Colour Access # Colour Access
# ============= # =============
assert nStatus.cols(statusKeys[0]) == (200, 100, 50) assert nStatus[statusKeys[0]].color == QColor(200, 100, 50)
assert nStatus.cols(statusKeys[1]) == (210, 110, 60) assert nStatus[statusKeys[1]].color == QColor(210, 110, 60)
assert nStatus.cols(statusKeys[2]) == (100, 100, 100) assert nStatus[statusKeys[2]].color == QColor(100, 100, 100)
assert nStatus.cols(statusKeys[3]) == (100, 100, 100) assert nStatus[statusKeys[3]].color == QColor(100, 100, 100)
assert nStatus.cols("blablabla") == (200, 100, 50) assert nStatus["blablabla"].color == QColor(200, 100, 50)
# Icon Access # Icon Access
# =========== # ===========
assert isinstance(nStatus.icon(statusKeys[0]), QIcon) assert isinstance(nStatus[statusKeys[0]].icon, QIcon)
assert isinstance(nStatus.icon(statusKeys[1]), QIcon) assert isinstance(nStatus[statusKeys[1]].icon, QIcon)
assert isinstance(nStatus.icon(statusKeys[2]), QIcon) assert isinstance(nStatus[statusKeys[2]].icon, QIcon)
assert isinstance(nStatus.icon(statusKeys[3]), QIcon) assert isinstance(nStatus[statusKeys[3]].icon, QIcon)
assert isinstance(nStatus.icon("blablabla"), 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 # Increment and Count Access
# ========================== # ==========================
@@ -203,50 +251,32 @@ def testCoreStatus_Entries(mockRnd):
for _ in range(n): for _ in range(n):
nStatus.increment(statusKeys[i]) nStatus.increment(statusKeys[i])
assert nStatus.count(statusKeys[0]) == countTo[0] assert nStatus[statusKeys[0]].count == countTo[0]
assert nStatus.count(statusKeys[1]) == countTo[1] assert nStatus[statusKeys[1]].count == countTo[1]
assert nStatus.count(statusKeys[2]) == countTo[2] assert nStatus[statusKeys[2]].count == countTo[2]
assert nStatus.count(statusKeys[3]) == countTo[3] assert nStatus[statusKeys[3]].count == countTo[3]
assert nStatus.count("blablabla") == countTo[0] assert nStatus["blablabla"].count == countTo[0]
nStatus.resetCounts() nStatus.resetCounts()
assert nStatus.count(statusKeys[0]) == 0 assert nStatus[statusKeys[0]].count == 0
assert nStatus.count(statusKeys[1]) == 0 assert nStatus[statusKeys[1]].count == 0
assert nStatus.count(statusKeys[2]) == 0 assert nStatus[statusKeys[2]].count == 0
assert nStatus.count(statusKeys[3]) == 0 assert nStatus[statusKeys[3]].count == 0
# Reorder # Update
# ======= # ======
cOrder = list(nStatus.keys()) assert list(nStatus._store.keys()) == statusKeys
assert cOrder == statusKeys
# Wrong length # Reverse
assert nStatus.reorder([]) is False order: list[tuple[str | None, StatusEntry]] = list(nStatus.iterItems())
nStatus.update(list(reversed(order)))
assert list(nStatus._store.keys()) == list(reversed(statusKeys))
# No change # Restore
assert nStatus.reorder(cOrder) is False nStatus.update(order)
assert list(nStatus._store.keys()) == statusKeys
# Actual reaorder
nOrder = [
statusKeys[0],
statusKeys[2],
statusKeys[1],
statusKeys[3],
]
assert nStatus.reorder(nOrder) is True
assert list(nStatus.keys()) == nOrder
# Add an unknown key
wOrder = nOrder.copy()
wOrder[3] = nStatus._newKey()
assert nStatus.reorder(wOrder) is False
assert list(nStatus.keys()) == nOrder
# Put it back
assert nStatus.reorder(cOrder) is True
assert list(nStatus.keys()) == cOrder
# Default # Default
# ======= # =======
@@ -255,56 +285,41 @@ def testCoreStatus_Entries(mockRnd):
nStatus._default = None nStatus._default = None
assert nStatus.check("Entry 5") == "" assert nStatus.check("Entry 5") == ""
assert nStatus.name("blablabla") == "" assert nStatus["blablabla"].name == ""
assert nStatus.cols("blablabla") == (100, 100, 100) assert nStatus["blablabla"].color == QColor(0, 0, 0)
assert nStatus.count("blablabla") == 0 assert nStatus["blablabla"].shape == nwStatusShape.SQUARE
assert isinstance(nStatus.icon("blablabla"), QIcon) assert nStatus["blablabla"].icon.isNull()
assert nStatus["blablabla"].count == 0
nStatus._default = default nStatus._default = default
# Remove # Remove
# ====== # ======
# This uses update with deleted items
# Non-existing entry order: list[tuple[str | None, StatusEntry]] = list(nStatus.iterItems())
assert nStatus.remove("blablabla") is False
# Non-zero entry # Remove Entry 0
nStatus.increment(statusKeys[3]) nStatus.update([order[1], order[3], order[2]])
assert nStatus.remove(statusKeys[3]) is False assert list(nStatus._store.keys()) == [statusKeys[1], statusKeys[3], statusKeys[2]]
assert nStatus._default == statusKeys[1]
# Delete last entry # Remove Entry 1
nStatus.resetCounts() nStatus.update([order[3], order[2]])
lastName = nStatus.name(statusKeys[3]) assert list(nStatus._store.keys()) == [statusKeys[3], statusKeys[2]]
assert lastName == "Entry 4" assert nStatus._default == statusKeys[3]
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
assert nStatus.name(firstName) == "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
# END Test testCoreStatus_Entries # END Test testCoreStatus_Entries
@pytest.mark.core @pytest.mark.core
def testCoreStatus_PackUnpack(mockRnd): def testCoreStatus_Pack(mockGUI, mockRnd):
"""Test all the pack/unpack of the NWStatus class. """Test data packing of the NWStatus class."""
"""
nStatus = NWStatus(NWStatus.STATUS) nStatus = NWStatus(NWStatus.STATUS)
nStatus.write(None, "New", (100, 100, 100)) nStatus.add(None, "New", (100, 100, 100), "SQUARE", 0)
nStatus.write(None, "Note", (200, 50, 0)) nStatus.add(None, "Note", (200, 50, 0), "CIRCLE", 0)
nStatus.write(None, "Draft", (200, 150, 0)) nStatus.add(None, "Draft", (200, 150, 0), "SQUARE", 0)
nStatus.write(None, "Finished", (50, 200, 0)) nStatus.add(None, "Finished", (50, 200, 0), "SQUARE", 0)
countTo = [3, 5, 7, 9] countTo = [3, 5, 7, 9]
for i, n in enumerate(countTo): for i, n in enumerate(countTo):
@@ -318,52 +333,85 @@ def testCoreStatus_PackUnpack(mockRnd):
"count": "3", "count": "3",
"red": "100", "red": "100",
"green": "100", "green": "100",
"blue": "100" "blue": "100",
"shape": "SQUARE",
}), }),
("Note", { ("Note", {
"key": statusKeys[1], "key": statusKeys[1],
"count": "5", "count": "5",
"red": "200", "red": "200",
"green": "50", "green": "50",
"blue": "0" "blue": "0",
"shape": "CIRCLE",
}), }),
("Draft", { ("Draft", {
"key": statusKeys[2], "key": statusKeys[2],
"count": "7", "count": "7",
"red": "200", "red": "200",
"green": "150", "green": "150",
"blue": "0" "blue": "0",
"shape": "SQUARE",
}), }),
("Finished", { ("Finished", {
"key": statusKeys[3], "key": statusKeys[3],
"count": "9", "count": "9",
"red": "50", "red": "50",
"green": "200", "green": "200",
"blue": "0" "blue": "0",
"shape": "SQUARE",
}), }),
] ]
# Unpack # END Test testCoreStatus_Pack
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
@pytest.mark.core
def testCoreStatus_ShapeCache():
"""Test the _ShapeCache class."""
shapes = _ShapeCache()
# Generate all shapes
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)
hexagon = shapes.getShape(nwStatusShape.HEXAGON)
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.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
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
+76 -82
View File
@@ -24,14 +24,13 @@ import pytest
from tools import C, buildTestProject from tools import C, buildTestProject
from PyQt5.QtCore import Qt
from PyQt5.QtGui import QColor from PyQt5.QtGui import QColor
from PyQt5.QtWidgets import QDialog, QAction, QColorDialog from PyQt5.QtWidgets import QDialog, QAction, QColorDialog
from novelwriter import CONFIG, SHARED from novelwriter import CONFIG, SHARED
from novelwriter.dialogs.editlabel import GuiEditLabel from novelwriter.dialogs.editlabel import GuiEditLabel
from novelwriter.dialogs.projectsettings import GuiProjectSettings from novelwriter.dialogs.projectsettings import GuiProjectSettings
from novelwriter.enum import nwItemType from novelwriter.enum import nwItemType, nwStatusShape
from novelwriter.types import QtMouseLeft from novelwriter.types import QtMouseLeft
KEY_DELAY = 1 KEY_DELAY = 1
@@ -169,8 +168,8 @@ def testDlgProjSettings_StatusImport(qtbot, monkeypatch, nwGUI, projPath, mockRn
nwGUI.rebuildTrees() nwGUI.rebuildTrees()
project.countStatus() project.countStatus()
assert [e["count"] for _, e in project.data.itemStatus.items()] == [2, 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.items()] == [3, 0, 2, 1] assert [e.count for _, e in project.data.itemImport.iterItems()] == [3, 0, 2, 1]
# Create Dialog # Create Dialog
projSettings = GuiProjectSettings(nwGUI, GuiProjectSettings.PAGE_STATUS) projSettings = GuiProjectSettings(nwGUI, GuiProjectSettings.PAGE_STATUS)
@@ -182,20 +181,20 @@ def testDlgProjSettings_StatusImport(qtbot, monkeypatch, nwGUI, projPath, mockRn
status = projSettings.statusPage status = projSettings.statusPage
assert status.wasChanged is False assert status.changed is False
assert status.getNewList() == ([], []) assert status.getNewList() == []
assert status.listBox.topLevelItemCount() == 4 assert status.listBox.topLevelItemCount() == 4
# Can't delete the first item (it's in use) # Can't delete the first item (it's in use)
status.listBox.clearSelection() status.listBox.clearSelection()
status.listBox.setCurrentItem(status.listBox.topLevelItem(0)) status.listBox.setCurrentItem(status.listBox.topLevelItem(0))
qtbot.mouseClick(status.delButton, QtMouseLeft) status.delButton.click()
assert status.listBox.topLevelItemCount() == 4 assert status.listBox.topLevelItemCount() == 4
# Can delete the second item # Can delete the second item
status.listBox.clearSelection() status.listBox.clearSelection()
status.listBox.setCurrentItem(status.listBox.topLevelItem(1)) status.listBox.setCurrentItem(status.listBox.topLevelItem(1))
qtbot.mouseClick(status.delButton, QtMouseLeft) status.delButton.click()
assert status.listBox.topLevelItemCount() == 3 assert status.listBox.topLevelItemCount() == 3
# Add a new item # Add a new item
@@ -204,39 +203,38 @@ def testDlgProjSettings_StatusImport(qtbot, monkeypatch, nwGUI, projPath, mockRn
status.addButton.click() status.addButton.click()
status.listBox.setCurrentItem(status.listBox.topLevelItem(3)) status.listBox.setCurrentItem(status.listBox.topLevelItem(3))
status.editName.setText("Final") status.editName.setText("Final")
status.colButton.click() status.colorButton.click()
status.saveButton.click() status._selectShape(nwStatusShape.CIRCLE)
status.applyButton.click()
assert status.listBox.topLevelItemCount() == 4 assert status.listBox.topLevelItemCount() == 4
assert status.wasChanged is True assert status.changed is True
assert status.getNewList() == ( update = status.getNewList()
[
{ assert update[0][0] == C.sNew
"key": C.sNew, assert update[0][1].name == "New"
"name": "New", assert update[0][1].color == QColor(100, 100, 100)
"cols": (100, 100, 100) assert update[0][1].shape == nwStatusShape.SQUARE
}, {
"key": C.sDraft, assert update[1][0] == C.sDraft
"name": "Draft", assert update[1][1].name == "Draft"
"cols": (200, 150, 0) assert update[1][1].color == QColor(200, 150, 0)
}, { assert update[1][1].shape == nwStatusShape.SQUARE
"key": C.sFinished,
"name": "Finished", assert update[2][0] == C.sFinished
"cols": (50, 200, 0) assert update[2][1].name == "Finished"
}, { assert update[2][1].color == QColor(50, 200, 0)
"key": None, assert update[2][1].shape == nwStatusShape.SQUARE
"name": "Final",
"cols": (20, 30, 40) assert update[3][0] is None
} assert update[3][1].name == "Final"
], [ assert update[3][1].color == QColor(20, 30, 40)
C.sNote # Deleted item assert update[3][1].shape == nwStatusShape.CIRCLE
]
)
# Move items, none selected -> no change # Move items, none selected -> no change
status.listBox.clearSelection() status.listBox.clearSelection()
status._moveItem(1) 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 C.sNew, C.sDraft, C.sFinished, None
] ]
@@ -244,7 +242,7 @@ def testDlgProjSettings_StatusImport(qtbot, monkeypatch, nwGUI, projPath, mockRn
status.listBox.clearSelection() status.listBox.clearSelection()
status.listBox.setCurrentItem(status.listBox.topLevelItem(0)) status.listBox.setCurrentItem(status.listBox.topLevelItem(0))
status._moveItem(-1) 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 C.sNew, C.sDraft, C.sFinished, None
] ]
@@ -252,13 +250,13 @@ def testDlgProjSettings_StatusImport(qtbot, monkeypatch, nwGUI, projPath, mockRn
status.listBox.clearSelection() status.listBox.clearSelection()
status.listBox.setCurrentItem(status.listBox.topLevelItem(3)) status.listBox.setCurrentItem(status.listBox.topLevelItem(3))
status._moveItem(-1) 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 C.sNew, C.sDraft, None, C.sFinished
] ]
# Move items, same selected, move down -> allowed # Move items, same selected, move down -> allowed
status._moveItem(1) 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 C.sNew, C.sDraft, C.sFinished, None
] ]
@@ -271,62 +269,58 @@ def testDlgProjSettings_StatusImport(qtbot, monkeypatch, nwGUI, projPath, mockRn
# Delete unused entry # Delete unused entry
importance.listBox.clearSelection() importance.listBox.clearSelection()
importance.listBox.setCurrentItem(importance.listBox.topLevelItem(1)) importance.listBox.setCurrentItem(importance.listBox.topLevelItem(1))
qtbot.mouseClick(importance.delButton, QtMouseLeft) importance.delButton.click()
assert importance.listBox.topLevelItemCount() == 3 assert importance.listBox.topLevelItemCount() == 3
# Add a new entry # Add a new entry
with monkeypatch.context() as mp: with monkeypatch.context() as mp:
mp.setattr(QColorDialog, "getColor", lambda *a: QColor(20, 30, 40)) mp.setattr(QColorDialog, "getColor", lambda *a: QColor(20, 30, 40))
qtbot.mouseClick(importance.addButton, QtMouseLeft) importance.addButton.click()
importance.listBox.clearSelection() importance.listBox.clearSelection()
importance.listBox.setCurrentItem(importance.listBox.topLevelItem(3)) importance.listBox.setCurrentItem(importance.listBox.topLevelItem(3))
for _ in range(8): importance.editName.setText("Final")
qtbot.keyClick(importance.editName, Qt.Key.Key_Backspace, delay=KEY_DELAY) importance.colorButton.click()
for c in "Final": importance._selectShape(nwStatusShape.TRIANGLE)
qtbot.keyClick(importance.editName, c, delay=KEY_DELAY) importance.applyButton.click()
qtbot.mouseClick(importance.colButton, QtMouseLeft)
qtbot.mouseClick(importance.saveButton, QtMouseLeft)
assert importance.listBox.topLevelItemCount() == 4 assert importance.listBox.topLevelItemCount() == 4
assert importance.wasChanged is True assert importance.changed is True
assert importance.getNewList() == ( update = importance.getNewList()
[
{ assert update[0][0] == C.iNew
"key": C.iNew, assert update[0][1].name == "New"
"name": "New", assert update[0][1].color == QColor(100, 100, 100)
"cols": (100, 100, 100) assert update[0][1].shape == nwStatusShape.SQUARE
}, {
"key": C.iMajor, assert update[1][0] == C.iMajor
"name": "Major", assert update[1][1].name == "Major"
"cols": (200, 150, 0) assert update[1][1].color == QColor(200, 150, 0)
}, { assert update[1][1].shape == nwStatusShape.SQUARE
"key": C.iMain,
"name": "Main", assert update[2][0] == C.iMain
"cols": (50, 200, 0) assert update[2][1].name == "Main"
}, { assert update[2][1].color == QColor(50, 200, 0)
"key": None, assert update[2][1].shape == nwStatusShape.SQUARE
"name": "Final",
"cols": (20, 30, 40) assert update[3][0] is None
} assert update[3][1].name == "Final"
], [ assert update[3][1].color == QColor(20, 30, 40)
C.iMinor # Deleted item assert update[3][1].shape == nwStatusShape.TRIANGLE
]
)
# Check Project # Check Project
projSettings._doSave() projSettings._doSave()
statusItems = dict(project.data.itemStatus.items()) statusItems = dict(project.data.itemStatus.iterItems())
assert statusItems[C.sNew]["name"] == "New" assert statusItems[C.sNew].name == "New"
assert statusItems[C.sDraft]["name"] == "Draft" assert statusItems[C.sDraft].name == "Draft"
assert statusItems[C.sFinished]["name"] == "Finished" assert statusItems[C.sFinished].name == "Finished"
assert statusItems["s000013"]["name"] == "Final" assert statusItems["s000013"].name == "Final"
importItems = dict(project.data.itemImport.items()) importItems = dict(project.data.itemImport.iterItems())
assert importItems[C.iNew]["name"] == "New" assert importItems[C.iNew].name == "New"
assert importItems[C.iMajor]["name"] == "Major" assert importItems[C.iMajor].name == "Major"
assert importItems[C.iMain]["name"] == "Main" assert importItems[C.iMain].name == "Main"
assert importItems["i000014"]["name"] == "Final" assert importItems["i000014"].name == "Final"
# qtbot.stop() # qtbot.stop()
@@ -363,7 +357,7 @@ def testDlgProjSettings_Replace(qtbot, monkeypatch, nwGUI, projPath, mockRnd):
# Nothing to save or delete # Nothing to save or delete
replace.listBox.clearSelection() replace.listBox.clearSelection()
replace._saveEntry() replace._applyChanges()
replace._delEntry() replace._delEntry()
assert replace.listBox.topLevelItemCount() == 2 assert replace.listBox.topLevelItemCount() == 2
@@ -381,7 +375,7 @@ def testDlgProjSettings_Replace(qtbot, monkeypatch, nwGUI, projPath, mockRnd):
replace.editValue.setText("") replace.editValue.setText("")
for c in "With This Stuff ": for c in "With This Stuff ":
qtbot.keyClick(replace.editValue, c, delay=KEY_DELAY) 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) == "<This>" # type: ignore assert replace.listBox.topLevelItem(2).text(0) == "<This>" # type: ignore
assert replace.listBox.topLevelItem(2).text(1) == "With This Stuff " # type: ignore assert replace.listBox.topLevelItem(2).text(1) == "With This Stuff " # type: ignore