From fd2248de171532cc5c84233135ca1497806600ec Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Sat, 16 Apr 2022 17:44:54 +0200 Subject: [PATCH 1/3] Add up and down button icons --- .../assets/icons/typicons_dark/icons.conf | 2 ++ .../icons/typicons_dark/typ_chevron-down.svg | 31 +++++++++++++++++++ .../icons/typicons_dark/typ_chevron-up.svg | 31 +++++++++++++++++++ .../assets/icons/typicons_light/icons.conf | 2 ++ .../icons/typicons_light/typ_chevron-down.svg | 31 +++++++++++++++++++ .../icons/typicons_light/typ_chevron-up.svg | 31 +++++++++++++++++++ novelwriter/gui/theme.py | 2 +- 7 files changed, 129 insertions(+), 1 deletion(-) create mode 100644 novelwriter/assets/icons/typicons_dark/typ_chevron-down.svg create mode 100644 novelwriter/assets/icons/typicons_dark/typ_chevron-up.svg create mode 100644 novelwriter/assets/icons/typicons_light/typ_chevron-down.svg create mode 100644 novelwriter/assets/icons/typicons_light/typ_chevron-up.svg 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 @@ + + + + + + image/svg+xml + + + + + + + + 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 @@ + + + + + + image/svg+xml + + + + + + + + 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 @@ + + + + + + image/svg+xml + + + + + + + + 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 @@ + + + + + + image/svg+xml + + + + + + + + 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", From 6aa13e318bc82af5e62a942b6fcc7468210557b6 Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Sat, 16 Apr 2022 18:17:01 +0200 Subject: [PATCH 2/3] Add sorting capability to status and importance labels --- novelwriter/core/project.py | 62 ++++++++++++---------------- novelwriter/core/status.py | 22 ++++++++++ novelwriter/dialogs/projsettings.py | 64 ++++++++++++++++++++--------- 3 files changed, 93 insertions(+), 55 deletions(-) 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..9fcfbb88 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 True + + 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..a0987418 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 False + + 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. """ From 3ed74f0b02b94aee34846a881ad39974fca0af70 Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Sat, 16 Apr 2022 18:35:26 +0200 Subject: [PATCH 3/3] Add test coverage of status and importance reordering --- novelwriter/core/status.py | 2 +- novelwriter/dialogs/projsettings.py | 2 +- .../guiProjSettings_Dialog_nwProject.nwx | 4 +- tests/test_core/test_core_status.py | 32 +++++++++++++++ tests/test_dialogs/test_dlg_projsettings.py | 39 ++++++++++++------- 5 files changed, 62 insertions(+), 17 deletions(-) diff --git a/novelwriter/core/status.py b/novelwriter/core/status.py index 9fcfbb88..4bade5e7 100644 --- a/novelwriter/core/status.py +++ b/novelwriter/core/status.py @@ -175,7 +175,7 @@ class NWStatus(): return False if order == list(self._store.keys()): - return True + return False store = {} for key in order: diff --git a/novelwriter/dialogs/projsettings.py b/novelwriter/dialogs/projsettings.py index a0987418..2a2bb969 100644 --- a/novelwriter/dialogs/projsettings.py +++ b/novelwriter/dialogs/projsettings.py @@ -464,7 +464,7 @@ class GuiProjectEditStatus(QWidget): nChild = self.listBox.topLevelItemCount() nIndex = tIndex + step if nIndex < 0 or nIndex >= nChild: - return False + return cItem = self.listBox.takeTopLevelItem(tIndex) self.listBox.insertTopLevelItem(nIndex, cItem) 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 # ==============