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