diff --git a/novelwriter/assets/icons/typicons_dark/icons.conf b/novelwriter/assets/icons/typicons_dark/icons.conf
index e06aba68..bcaef71a 100644
--- a/novelwriter/assets/icons/typicons_dark/icons.conf
+++ b/novelwriter/assets/icons/typicons_dark/icons.conf
@@ -42,6 +42,7 @@ doc_h2 = mixed_heading2.svg
doc_h3 = mixed_heading3.svg
doc_h4 = mixed_heading4.svg
done = typ_input-checked.svg
+down = typ_chevron-down.svg
edit = typ_pencil.svg
forward = typ_chevron-right.svg
hash = typ_hash.svg
@@ -74,3 +75,4 @@ status_stats = typ_chart-bar-grey.svg
status_time = typ_stopwatch-grey.svg
sticky-off = typ_pin-outline.svg
sticky-on = typ_pin.svg
+up = typ_chevron-up.svg
diff --git a/novelwriter/assets/icons/typicons_dark/typ_chevron-down.svg b/novelwriter/assets/icons/typicons_dark/typ_chevron-down.svg
new file mode 100644
index 00000000..53389084
--- /dev/null
+++ b/novelwriter/assets/icons/typicons_dark/typ_chevron-down.svg
@@ -0,0 +1,31 @@
+
+
diff --git a/novelwriter/assets/icons/typicons_dark/typ_chevron-up.svg b/novelwriter/assets/icons/typicons_dark/typ_chevron-up.svg
new file mode 100644
index 00000000..9ac7e927
--- /dev/null
+++ b/novelwriter/assets/icons/typicons_dark/typ_chevron-up.svg
@@ -0,0 +1,31 @@
+
+
diff --git a/novelwriter/assets/icons/typicons_light/icons.conf b/novelwriter/assets/icons/typicons_light/icons.conf
index 56e18b2a..8639908f 100644
--- a/novelwriter/assets/icons/typicons_light/icons.conf
+++ b/novelwriter/assets/icons/typicons_light/icons.conf
@@ -42,6 +42,7 @@ doc_h2 = mixed_heading2.svg
doc_h3 = mixed_heading3.svg
doc_h4 = mixed_heading4.svg
done = typ_input-checked.svg
+down = typ_chevron-down.svg
edit = typ_pencil.svg
forward = typ_chevron-right.svg
hash = typ_hash.svg
@@ -74,3 +75,4 @@ status_stats = typ_chart-bar-grey.svg
status_time = typ_stopwatch-grey.svg
sticky-off = typ_pin-outline.svg
sticky-on = typ_pin.svg
+up = typ_chevron-up.svg
diff --git a/novelwriter/assets/icons/typicons_light/typ_chevron-down.svg b/novelwriter/assets/icons/typicons_light/typ_chevron-down.svg
new file mode 100644
index 00000000..6ba80643
--- /dev/null
+++ b/novelwriter/assets/icons/typicons_light/typ_chevron-down.svg
@@ -0,0 +1,31 @@
+
+
diff --git a/novelwriter/assets/icons/typicons_light/typ_chevron-up.svg b/novelwriter/assets/icons/typicons_light/typ_chevron-up.svg
new file mode 100644
index 00000000..1b9eb901
--- /dev/null
+++ b/novelwriter/assets/icons/typicons_light/typ_chevron-up.svg
@@ -0,0 +1,31 @@
+
+
diff --git a/novelwriter/core/project.py b/novelwriter/core/project.py
index a8a9cce3..9623520e 100644
--- a/novelwriter/core/project.py
+++ b/novelwriter/core/project.py
@@ -1072,46 +1072,14 @@ class NWProject():
return True
def setStatusColours(self, newCols, delCols):
- """Update the list of novel file status flags. Also iterate
- through the project and replace keys that have been renamed.
+ """Update the list of novel file status flags.
"""
- if not (newCols or delCols):
- return False
-
- for entry in newCols:
- key = entry.get("key", None)
- name = entry.get("name", "")
- cols = entry.get("cols", (100, 100, 100))
- if name:
- self.statusItems.write(key, name, cols)
-
- for key in delCols:
- self.statusItems.remove(key)
-
- self.setProjectChanged(True)
-
- return True
+ return self._setStatusImport(newCols, delCols, self.statusItems)
def setImportColours(self, newCols, delCols):
- """Update the list of note file importance flags. Also iterate
- through the project and replace keys that have been renamed.
+ """Update the list of note file importance flags.
"""
- if not (newCols or delCols):
- return False
-
- for entry in newCols:
- key = entry.get("key", None)
- name = entry.get("name", "")
- cols = entry.get("cols", (100, 100, 100))
- if name:
- self.importItems.write(key, name, cols)
-
- for key in delCols:
- self.importItems.remove(key)
-
- self.setProjectChanged(True)
-
- return True
+ return self._setStatusImport(newCols, delCols, self.importItems)
def setAutoReplace(self, autoReplace):
"""Update the auto-replace dictionary.
@@ -1251,6 +1219,28 @@ class NWProject():
# Internal Functions
##
+ def _setStatusImport(self, new, delete, target):
+ """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):
"""Load the language data for the current project language.
"""
diff --git a/novelwriter/core/status.py b/novelwriter/core/status.py
index 4e9317f2..4bade5e7 100644
--- a/novelwriter/core/status.py
+++ b/novelwriter/core/status.py
@@ -167,6 +167,28 @@ class NWStatus():
else:
return self._defaultIcon
+ def reorder(self, order):
+ """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):
"""Clear the counts of references to the status entries.
"""
diff --git a/novelwriter/dialogs/projsettings.py b/novelwriter/dialogs/projsettings.py
index 9599cc92..2a2bb969 100644
--- a/novelwriter/dialogs/projsettings.py
+++ b/novelwriter/dialogs/projsettings.py
@@ -306,6 +306,12 @@ class GuiProjectEditStatus(QWidget):
self.delButton = QPushButton(self.theTheme.getIcon("remove"), "")
self.delButton.clicked.connect(self._delItem)
+ self.upButton = QPushButton(self.theTheme.getIcon("up"), "")
+ self.upButton.clicked.connect(lambda: self._moveItem(-1))
+
+ self.dnButton = QPushButton(self.theTheme.getIcon("down"), "")
+ self.dnButton.clicked.connect(lambda: self._moveItem(1))
+
# Edit Form
# =========
@@ -315,7 +321,7 @@ class GuiProjectEditStatus(QWidget):
self.editName.setPlaceholderText(self.tr("Select item to edit"))
self.colPixmap = QPixmap(self.iPx, self.iPx)
- self.colPixmap.fill(QColor(120, 120, 120))
+ self.colPixmap.fill(QColor(100, 100, 100))
self.colButton = QPushButton(QIcon(self.colPixmap), self.tr("Colour"))
self.colButton.setIconSize(self.colPixmap.rect().size())
self.colButton.clicked.connect(self._selectColour)
@@ -329,6 +335,8 @@ class GuiProjectEditStatus(QWidget):
self.listControls = QVBoxLayout()
self.listControls.addWidget(self.addButton)
self.listControls.addWidget(self.delButton)
+ self.listControls.addWidget(self.upButton)
+ self.listControls.addWidget(self.dnButton)
self.listControls.addStretch(1)
self.editBox = QHBoxLayout()
@@ -390,7 +398,7 @@ class GuiProjectEditStatus(QWidget):
def _newItem(self):
"""Create a new status item.
"""
- newItem = self._addItem(None, self.tr("New Item"), (0, 0, 0), 0)
+ newItem = self._addItem(None, self.tr("New Item"), (100, 100, 100), 0)
newItem.setBackground(self.COL_LABEL, QBrush(QColor(0, 255, 0, 70)))
newItem.setBackground(self.COL_USAGE, QBrush(QColor(0, 255, 0, 70)))
self.colChanged = True
@@ -445,23 +453,47 @@ class GuiProjectEditStatus(QWidget):
return item
+ def _moveItem(self, step):
+ """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()
+
+ cItem.setSelected(True)
+ self.colChanged = True
+
+ return
+
def _selectedItem(self):
"""Extract the info of a selected item and populate the settings
boxes and button.
"""
selItem = self._getSelectedItem()
- if selItem is not None:
- cols = selItem.data(self.COL_LABEL, self.COL_ROLE)
- name = selItem.text(self.COL_LABEL)
+ if selItem is None:
+ return
- pixmap = QPixmap(self.iPx, self.iPx)
- pixmap.fill(QColor(*cols))
- self.selColour = QColor(*cols)
- self.editName.setText(name)
- self.colButton.setIcon(QIcon(pixmap))
- self.editName.setEnabled(True)
- self.editName.selectAll()
- self.editName.setFocus()
+ 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))
+ self.editName.setEnabled(True)
+ self.editName.selectAll()
+ self.editName.setFocus()
return
@@ -477,12 +509,6 @@ class GuiProjectEditStatus(QWidget):
return selItem[0]
return None
- def _rowsMoved(self):
- """A row has been moved, so set the changed flag.
- """
- self.colChanged = True
- return
-
def _usageString(self, nUse):
"""Generate usage string.
"""
diff --git a/novelwriter/gui/theme.py b/novelwriter/gui/theme.py
index 6cdf72ef..3c180b50 100644
--- a/novelwriter/gui/theme.py
+++ b/novelwriter/gui/theme.py
@@ -469,7 +469,7 @@ class GuiIcons:
"delete", "close", "done", "clear", "save", "add", "remove",
"search", "search_replace", "edit", "check", "cross", "hash",
"maximise", "minimise", "refresh", "reference", "backward",
- "forward", "settings",
+ "forward", "settings", "up", "down",
# Switches
"sticky-on", "sticky-off",
diff --git a/tests/reference/guiProjSettings_Dialog_nwProject.nwx b/tests/reference/guiProjSettings_Dialog_nwProject.nwx
index e894d5bf..1b3c818a 100644
--- a/tests/reference/guiProjSettings_Dialog_nwProject.nwx
+++ b/tests/reference/guiProjSettings_Dialog_nwProject.nwx
@@ -1,5 +1,5 @@
-
+
Project Name
Project Title
@@ -42,7 +42,7 @@
New
Minor
Major
- Final
+ Final
diff --git a/tests/test_core/test_core_status.py b/tests/test_core/test_core_status.py
index 611f8327..e1d224d8 100644
--- a/tests/test_core/test_core_status.py
+++ b/tests/test_core/test_core_status.py
@@ -229,6 +229,38 @@ def testCoreStatus_Entries(constData):
assert theStatus.count(constData.statusKeys[2]) == 0
assert theStatus.count(constData.statusKeys[3]) == 0
+ # Reorder
+ # =======
+
+ cOrder = list(theStatus.keys())
+ assert cOrder == constData.statusKeys
+
+ # Wrong length
+ assert theStatus.reorder([]) is False
+
+ # No change
+ assert theStatus.reorder(cOrder) is False
+
+ # Actual reaorder
+ nOrder = [
+ constData.statusKeys[0],
+ constData.statusKeys[2],
+ constData.statusKeys[1],
+ constData.statusKeys[3],
+ ]
+ assert theStatus.reorder(nOrder) is True
+ assert list(theStatus.keys()) == nOrder
+
+ # Add an unknown key
+ wOrder = nOrder.copy()
+ wOrder[3] = theStatus._newKey()
+ assert theStatus.reorder(wOrder) is False
+ assert list(theStatus.keys()) == nOrder
+
+ # Put it back
+ assert theStatus.reorder(cOrder) is True
+ assert list(theStatus.keys()) == cOrder
+
# Default
# =======
diff --git a/tests/test_dialogs/test_dlg_projsettings.py b/tests/test_dialogs/test_dlg_projsettings.py
index f2aa8319..5a047522 100644
--- a/tests/test_dialogs/test_dlg_projsettings.py
+++ b/tests/test_dialogs/test_dlg_projsettings.py
@@ -28,9 +28,7 @@ from tools import cmpFiles, getGuiItem
from PyQt5.QtGui import QColor
from PyQt5.QtCore import Qt
-from PyQt5.QtWidgets import (
- QDialog, QAction, QMessageBox, QColorDialog, QTreeWidgetItem
-)
+from PyQt5.QtWidgets import QDialog, QAction, QMessageBox, QColorDialog
from novelwriter.dialogs import GuiProjectSettings
@@ -118,16 +116,6 @@ def testDlgProjSettings_Dialog(
assert projEdit.tabStatus.getNewList() == ([], [])
assert projEdit.tabStatus.listBox.topLevelItemCount() == 4
- # Fake drag'n'drop should change changed status
- projEdit.tabStatus._rowsMoved()
- assert projEdit.tabStatus.colChanged is True
- projEdit.tabStatus.colChanged = False
-
- projEdit.tabStatus.listBox.clearSelection()
- assert projEdit.tabStatus._getSelectedItem() is None
- projEdit.tabStatus.listBox.topLevelItem(0).setSelected(True)
- assert isinstance(projEdit.tabStatus._getSelectedItem(), QTreeWidgetItem)
-
# Can't delete the first item (it's in use)
projEdit.tabStatus.listBox.clearSelection()
projEdit.tabStatus.listBox.topLevelItem(0).setSelected(True)
@@ -178,6 +166,31 @@ def testDlgProjSettings_Dialog(
]
)
+ # Move items
+ projEdit.tabStatus.listBox.clearSelection()
+ projEdit.tabStatus._moveItem(1)
+ assert [x["key"] for x in projEdit.tabStatus.getNewList()[0]] == [
+ constData.statusKeys[0], constData.statusKeys[1], constData.statusKeys[3], None
+ ]
+
+ projEdit.tabStatus.listBox.clearSelection()
+ projEdit.tabStatus.listBox.topLevelItem(0).setSelected(True)
+ projEdit.tabStatus._moveItem(-1)
+ assert [x["key"] for x in projEdit.tabStatus.getNewList()[0]] == [
+ constData.statusKeys[0], constData.statusKeys[1], constData.statusKeys[3], None
+ ]
+
+ projEdit.tabStatus.listBox.clearSelection()
+ projEdit.tabStatus.listBox.topLevelItem(3).setSelected(True)
+ projEdit.tabStatus._moveItem(-1)
+ assert [x["key"] for x in projEdit.tabStatus.getNewList()[0]] == [
+ constData.statusKeys[0], constData.statusKeys[1], None, constData.statusKeys[3]
+ ]
+ projEdit.tabStatus._moveItem(1)
+ assert [x["key"] for x in projEdit.tabStatus.getNewList()[0]] == [
+ constData.statusKeys[0], constData.statusKeys[1], constData.statusKeys[3], None
+ ]
+
# Importance Tab
# ==============