Simplify the status class

This commit is contained in:
Veronica Berglyd Olsen
2024-04-10 17:57:39 +02:00
parent 478db63b30
commit 63d2f5c57d
9 changed files with 123 additions and 174 deletions
+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
+22 -33
View File
@@ -36,7 +36,8 @@ from collections.abc import Iterable
from PyQt5.QtCore import QCoreApplication
from novelwriter import CONFIG, SHARED, __version__, __hexversion__
from novelwriter.enum import nwItemType, nwItemClass, nwItemLayout, nwStatusShape
from novelwriter.core.status import StatusEntry
from novelwriter.enum import nwItemType, nwItemClass, nwItemLayout
from novelwriter.error import logException
from novelwriter.constants import trConst, nwLabels
from novelwriter.core.tree import NWTree
@@ -461,15 +462,14 @@ class NWProject:
def setDefaultStatusImport(self) -> None:
"""Set the default status and importance values."""
square = nwStatusShape.SQUARE
self._data.itemStatus.write(None, self.tr("New"), (100, 100, 100), square)
self._data.itemStatus.write(None, self.tr("Note"), (200, 50, 0), square)
self._data.itemStatus.write(None, self.tr("Draft"), (200, 150, 0), square)
self._data.itemStatus.write(None, self.tr("Finished"), (50, 200, 0), square)
self._data.itemImport.write(None, self.tr("New"), (100, 100, 100), square)
self._data.itemImport.write(None, self.tr("Minor"), (200, 50, 0), square)
self._data.itemImport.write(None, self.tr("Major"), (200, 150, 0), square)
self._data.itemImport.write(None, self.tr("Main"), (50, 200, 0), square)
self._data.itemStatus.write(None, self.tr("New"), (100, 100, 100), "SQUARE")
self._data.itemStatus.write(None, self.tr("Note"), (200, 50, 0), "SQUARE")
self._data.itemStatus.write(None, self.tr("Draft"), (200, 150, 0), "SQUARE")
self._data.itemStatus.write(None, self.tr("Finished"), (50, 200, 0), "SQUARE")
self._data.itemImport.write(None, self.tr("New"), (100, 100, 100), "SQUARE")
self._data.itemImport.write(None, self.tr("Minor"), (200, 50, 0), "SQUARE")
self._data.itemImport.write(None, self.tr("Major"), (200, 150, 0), "SQUARE")
self._data.itemImport.write(None, self.tr("Main"), (50, 200, 0), "SQUARE")
return
def setProjectLang(self, language: str | None) -> None:
@@ -492,13 +492,13 @@ class NWProject:
self.setProjectChanged(True)
return
def setStatusColours(self, new: list[dict], deleted: list[str]) -> bool:
def setStatus(self, update: list[tuple[str | None, StatusEntry]], remove: list[str]) -> None:
"""Update the list of novel file status flags."""
return self._setStatusImport(new, deleted, self._data.itemStatus)
return self._setStatusImport(update, remove, self._data.itemStatus)
def setImportColours(self, new: list[dict], deleted: list[str]) -> bool:
def setImport(self, update: list[tuple[str | None, StatusEntry]], remove: list[str]) -> None:
"""Update the list of note file importance flags."""
return self._setStatusImport(new, deleted, self._data.itemImport)
return self._setStatusImport(update, remove, self._data.itemImport)
def setProjectChanged(self, status: bool) -> bool:
"""Toggle the project changed flag, and propagate the
@@ -585,28 +585,17 @@ class NWProject:
# Internal Functions
##
def _setStatusImport(self, new: list[dict], delete: list[str], target: NWStatus) -> bool:
def _setStatusImport(self, update: list[tuple[str | None, StatusEntry]],
remove: list[str], target: NWStatus) -> None:
"""Update the list of novel file status or importance flags, and
delete those that have been requested deleted.
"""
if not (new or delete):
return False
order = []
for entry in new:
key = entry.get("key", None)
name = entry.get("name", "")
cols = entry.get("cols", (100, 100, 100))
shape = entry.get("shape", nwStatusShape.SQUARE)
if name:
order.append(target.write(key, name, cols, shape))
for key in delete:
target.remove(key)
target.reorder(order)
return True
if update or remove:
order = [target.write(k, e.name, e.color, e.shape) for k, e in update]
for key in remove:
target.remove(key)
target.reorder(order)
return
def _loadProjectLocalisation(self) -> bool:
"""Load the language data for the current project language."""
+37 -57
View File
@@ -24,11 +24,11 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
"""
from __future__ import annotations
import dataclasses
import logging
import random
from collections.abc import Iterable
from dataclasses import dataclass
from math import cos, pi, sin
from typing import TYPE_CHECKING, Literal
@@ -46,18 +46,26 @@ if TYPE_CHECKING: # pragma: no cover
logger = logging.getLogger(__name__)
@dataclass
@dataclasses.dataclass
class StatusEntry:
name: str
colour: QColor
color: QColor
shape: nwStatusShape
icon: QIcon
count: int = 0
@classmethod
def duplicate(cls, source: StatusEntry) -> StatusEntry:
"""Create a shallow copy of the source object."""
return dataclasses.replace(source)
# END Class StatusEntry
NO_ENTRY = StatusEntry("", QColor(0, 0, 0), nwStatusShape.SQUARE, QIcon(), 0)
class NWStatus:
STATUS = 1
@@ -70,9 +78,6 @@ class NWStatus:
self._default = None
self._iPX = CONFIG.pxInt(24)
self._defaultIcon = self.createIcon(
self._iPX, QColor(100, 100, 100), nwStatusShape.SQUARE
)
if self._type == self.STATUS:
self._prefix = "s"
@@ -86,44 +91,51 @@ class NWStatus:
def __len__(self) -> int:
return len(self._store)
def __getitem__(self, key: str) -> StatusEntry:
return self._store[key]
def __getitem__(self, key: str | None) -> StatusEntry:
"""Return the entry associated with a given key."""
if key and key in self._store:
return self._store[key]
elif self._default is not None:
return self._store[self._default]
return NO_ENTRY
##
# Methods
##
def write(self, key: str | None, name: str, col: tuple[int, int, int],
shape: nwStatusShape | str, count: int | None = None) -> str:
def write(self, key: str | None, name: str, color: tuple[int, int, int] | QColor,
shape: nwStatusShape | str, count: int = 0) -> str:
"""Add or update a status entry. If the key is invalid, a new
key is generated.
"""
if not self._isKey(key):
key = self._newKey()
if not isinstance(col, tuple):
col = (100, 100, 100)
if len(col) != 3:
col = (100, 100, 100)
name = simplified(name)
colour = QColor(*col)
if isinstance(color, QColor):
qColor = color
elif isinstance(color, tuple) and len(color) == 3:
qColor = QColor(*color)
else:
qColor = QColor(100, 100, 100)
if not isinstance(shape, nwStatusShape):
if shape in nwStatusShape.__members__:
try:
shape = nwStatusShape[shape]
else:
except KeyError:
shape = nwStatusShape.SQUARE
icon = self.createIcon(self._iPX, colour, shape)
name = simplified(name)
icon = self.createIcon(self._iPX, qColor, shape)
if key and key in self._store:
entry = self._store[key]
entry.name = name
entry.colour = colour
entry.color = qColor
entry.shape = shape
entry.icon = icon
entry.count = count or 0
entry.count = count
else:
self._store[key] = StatusEntry(name, colour, shape, icon, count or 0)
self._store[key] = StatusEntry(name, qColor, shape, icon, count)
if self._default is None:
self._default = key
@@ -156,38 +168,6 @@ class NWStatus:
return self._default
return ""
def name(self, key: str | None) -> str:
"""Return the name associated with a given key."""
if key and key in self._store:
return self._store[key].name
elif self._default is not None:
return self._store[self._default].name
return ""
def cols(self, key: str | None) -> QColor:
"""Return the colours associated with a given key."""
if key and key in self._store:
return self._store[key].colour
elif self._default is not None:
return self._store[self._default].colour
return QColor(100, 100, 100)
def count(self, key: str | None) -> int:
"""Return the count associated with a given key."""
if key and key in self._store:
return self._store[key].count
elif self._default is not None:
return self._store[self._default].count
return 0
def icon(self, key: str | None) -> QIcon:
"""Return the icon associated with a given key."""
if key and key in self._store:
return self._store[key].icon
elif self._default is not None:
return self._store[self._default].icon
return self._defaultIcon
def reorder(self, order: list[str]) -> bool:
"""Reorder the items according to list."""
if len(order) != len(self._store):
@@ -227,9 +207,9 @@ class NWStatus:
yield (entry.name, {
"key": key,
"count": str(entry.count),
"red": str(entry.colour.red()),
"green": str(entry.colour.green()),
"blue": str(entry.colour.blue()),
"red": str(entry.color.red()),
"green": str(entry.color.green()),
"blue": str(entry.color.blue()),
"shape": entry.shape.name,
})
return
+54 -64
View File
@@ -36,7 +36,7 @@ from PyQt5.QtWidgets import (
from novelwriter import CONFIG, SHARED
from novelwriter.common import simplified
from novelwriter.core.status import NWStatus
from novelwriter.core.status import NWStatus, StatusEntry
from novelwriter.enum import nwStatusShape
from novelwriter.extensions.configlayout import NColourLabel, NFixedPage, NScrollableForm
from novelwriter.extensions.modified import NComboBox, NIconToolButton
@@ -182,18 +182,18 @@ class GuiProjectSettings(QDialog):
rebuildTrees = False
if self.statusPage.wasChanged:
newList, delList = self.statusPage.getNewList()
project.setStatusColours(newList, delList)
update, remove = self.statusPage.getNewList()
project.setStatus(update, remove)
rebuildTrees = True
if self.importPage.wasChanged:
newList, delList = self.importPage.getNewList()
project.setImportColours(newList, delList)
update, remove = self.importPage.getNewList()
project.setImport(update, remove)
rebuildTrees = True
if self.replacePage.wasChanged:
newList = self.replacePage.getNewList()
project.data.setAutoReplace(newList)
update = self.replacePage.getNewList()
project.data.setAutoReplace(update)
self.newProjectSettingsReady.emit(rebuildTrees)
QApplication.processEvents()
@@ -308,9 +308,7 @@ class _StatusPage(NFixedPage):
C_USAGE = 1
D_KEY = QtUserRole
D_COLOR = QtUserRole + 1
D_SHAPE = QtUserRole + 2
D_COUNT = QtUserRole + 3
D_ENTRY = QtUserRole + 1
def __init__(self, parent: QWidget, isStatus: bool) -> None:
super().__init__(parent=parent)
@@ -329,10 +327,10 @@ class _StatusPage(NFixedPage):
)
self._changed = False
self._colDeleted = []
self._colDeleted: list[str] = []
self._selColour = QColor(100, 100, 100)
self.iPx = SHARED.theme.baseIconHeight
self._iPx = SHARED.theme.baseIconHeight
iSz = SHARED.theme.baseIconSize
bSz = SHARED.theme.buttonIconSize
@@ -355,7 +353,7 @@ class _StatusPage(NFixedPage):
self.listBox.setIndentation(0)
for key, entry in status.iterItems():
self._addItem(key, entry.name, entry.colour, entry.shape, entry.icon, entry.count)
self._addItem(key, StatusEntry.duplicate(entry))
# List Controls
self.addButton = NIconToolButton(self, iSz, "add")
@@ -376,7 +374,7 @@ class _StatusPage(NFixedPage):
self.editName.setPlaceholderText(self.tr("Select item to edit"))
self.editName.setEnabled(False)
self.colPixmap = QPixmap(self.iPx, self.iPx)
self.colPixmap = QPixmap(self._iPx, self._iPx)
self.colPixmap.fill(QColor(100, 100, 100))
self.colButton = QPushButton(QIcon(self.colPixmap), self.tr("Colour"), self)
self.colButton.setIconSize(bSz)
@@ -425,20 +423,16 @@ class _StatusPage(NFixedPage):
# Methods
##
def getNewList(self) -> tuple[list, list]:
def getNewList(self) -> tuple[list[tuple[str | None, StatusEntry]], list[str]]:
"""Return list of entries."""
if self._changed:
newList = []
update = []
for n in range(self.listBox.topLevelItemCount()):
item = self.listBox.topLevelItem(n)
if item is not None:
newList.append({
"key": item.data(self.C_DATA, self.D_KEY),
"name": item.text(self.C_DATA),
"cols": item.data(self.C_DATA, self.D_COLOR),
"shape": item.data(self.C_DATA, self.D_SHAPE),
})
return newList, self._colDeleted
if item := self.listBox.topLevelItem(n):
key = item.data(self.C_DATA, self.D_KEY)
entry = item.data(self.C_DATA, self.D_ENTRY)
update.append((key, entry))
return update, self._colDeleted
return [], []
def columnWidth(self) -> int:
@@ -458,7 +452,7 @@ class _StatusPage(NFixedPage):
)
if newCol.isValid():
self._selColour = newCol
pixmap = QPixmap(self.iPx, self.iPx)
pixmap = QPixmap(self._iPx, self._iPx)
pixmap.fill(newCol)
self.colButton.setIcon(QIcon(pixmap))
self.colButton.setIconSize(pixmap.rect().size())
@@ -467,34 +461,43 @@ class _StatusPage(NFixedPage):
@pyqtSlot()
def _newItem(self) -> None:
"""Create a new status item."""
# self._addItem(None, self.tr("New Item"), (100, 100, 100), 0)
color = QColor(100, 100, 100)
shape = nwStatusShape.SQUARE
icon = NWStatus.createIcon(self._iPx, color, shape)
self._addItem(None, StatusEntry(self.tr("New Item"), color, shape, icon, 0))
self._changed = True
return
@pyqtSlot()
def _delItem(self) -> None:
"""Delete a status item."""
selItem = self._getSelectedItem()
if isinstance(selItem, QTreeWidgetItem):
iRow = self.listBox.indexOfTopLevelItem(selItem)
if selItem.data(self.C_LABEL, self.D_COUNT) > 0:
if item := self._getSelectedItem():
iRow = self.listBox.indexOfTopLevelItem(item)
entry: StatusEntry = item.data(self.C_DATA, self.D_ENTRY)
if entry.count > 0:
SHARED.error(self.tr("Cannot delete a status item that is in use."))
else:
self.listBox.takeTopLevelItem(iRow)
self._colDeleted.append(selItem.data(self.C_DATA, self.D_KEY))
self._colDeleted.append(item.data(self.C_DATA, self.D_KEY))
self._changed = True
return
@pyqtSlot()
def _saveItem(self) -> None:
"""Save changes made to a status item."""
selItem = self._getSelectedItem()
if isinstance(selItem, QTreeWidgetItem):
selItem.setText(self.C_LABEL, simplified(self.editName.text()))
selItem.setIcon(self.C_LABEL, self.colButton.icon())
selItem.setData(self.C_DATA, self.D_COLOR, (
self._selColour.red(), self._selColour.green(), self._selColour.blue()
))
if item := self._getSelectedItem():
entry: StatusEntry = item.data(self.C_DATA, self.D_ENTRY)
name = simplified(self.editName.text())
shape = nwStatusShape.SQUARE
icon = NWStatus.createIcon(self._iPx, self._selColour, shape)
entry.name = name
entry.shape = shape
entry.color = self._selColour
entry.icon = icon
item.setText(self.C_LABEL, name)
item.setIcon(self.C_LABEL, icon)
self._changed = True
return
@@ -503,26 +506,21 @@ class _StatusPage(NFixedPage):
"""Extract the info of a selected item and populate the settings
boxes and button. If no item is selected, clear the form.
"""
selItem = self._getSelectedItem()
if isinstance(selItem, QTreeWidgetItem):
cols = selItem.data(self.C_DATA, self.D_COLOR)
name = selItem.text(self.C_LABEL)
pixmap = QPixmap(self.iPx, self.iPx)
pixmap.fill(cols)
self._selColour = cols
self.editName.setText(name)
self.colButton.setIcon(QIcon(pixmap))
if item := self._getSelectedItem():
entry: StatusEntry = item.data(self.C_DATA, self.D_ENTRY)
self._selColour = entry.color
self.editName.setText(entry.name)
self.colButton.setIcon(entry.icon)
self.editName.selectAll()
self.editName.setFocus()
self.editName.setEnabled(True)
self.colButton.setEnabled(True)
self.saveButton.setEnabled(True)
else:
pixmap = QPixmap(self.iPx, self.iPx)
pixmap.fill(QColor(100, 100, 100))
self._selColour = QColor(100, 100, 100)
icon = NWStatus.createIcon(self._iPx, self._selColour, nwStatusShape.SQUARE)
self.editName.setText("")
self.colButton.setIcon(QIcon(pixmap))
self.colButton.setIcon(icon)
self.editName.setEnabled(False)
self.colButton.setEnabled(False)
self.saveButton.setEnabled(False)
@@ -532,23 +530,15 @@ class _StatusPage(NFixedPage):
# Internal Functions
##
def _addItem(self, key: str | None, name: str, colour: QColor,
shape: nwStatusShape, icon: QIcon | None, count: int) -> None:
def _addItem(self, key: str | None, entry: StatusEntry) -> None:
"""Add a status item to the list."""
if icon is None:
icon = NWStatus.createIcon(SHARED.theme.baseIconHeight, colour, shape)
item = QTreeWidgetItem()
item.setText(self.C_LABEL, name)
item.setIcon(self.C_LABEL, icon)
item.setText(self.C_USAGE, self._usageString(count))
item.setText(self.C_LABEL, entry.name)
item.setIcon(self.C_LABEL, entry.icon)
item.setText(self.C_USAGE, self._usageString(entry.count))
item.setData(self.C_DATA, self.D_KEY, key)
item.setData(self.C_DATA, self.D_COLOR, colour)
item.setData(self.C_DATA, self.D_SHAPE, shape)
item.setData(self.C_DATA, self.D_COUNT, count)
item.setData(self.C_DATA, self.D_ENTRY, entry)
self.listBox.addTopLevelItem(item)
return
def _moveItem(self, step: int) -> None:
+1 -1
View File
@@ -3135,7 +3135,7 @@ class GuiDocEditFooter(QWidget):
sText = ""
else:
iPx = round(0.9*SHARED.theme.baseIconHeight)
status, icon = self._tItem.getImportStatus(incIcon=True)
status, icon = self._tItem.getImportStatus()
sIcon = icon.pixmap(iPx, iPx)
sText = f"{status} / {self._tItem.describeMe()}"
+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)
+1 -1
View File
@@ -1048,7 +1048,7 @@ class GuiOutlineDetails(QScrollArea):
self.titleLabel.setText(self.tr(self.LVL_MAP.get(novIdx.level, "H1")))
self.titleValue.setText(novIdx.title)
itemStatus, _ = nwItem.getImportStatus(incIcon=False)
itemStatus, _ = nwItem.getImportStatus()
self.fileValue.setText(nwItem.itemName)
self.itemValue.setText(itemStatus)
+1 -1
View File
@@ -1033,7 +1033,7 @@ class GuiProjectTree(QTreeWidget):
if trItem is None or nwItem is None:
return
itemStatus, statusIcon = nwItem.getImportStatus(incIcon=True)
itemStatus, statusIcon = nwItem.getImportStatus()
hLevel = nwItem.mainHeading
itemIcon = SHARED.theme.getItemIcon(
nwItem.itemType, nwItem.itemClass, nwItem.itemLayout, hLevel