From df6ca2e17b181e24a0bd6a1460bd1dbe3076e2dd Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Mon, 30 Dec 2024 16:34:54 +0100 Subject: [PATCH 1/4] Drop the status/importance Apply button (#2150) --- novelwriter/dialogs/projectsettings.py | 75 ++++++++++++-------------- 1 file changed, 35 insertions(+), 40 deletions(-) diff --git a/novelwriter/dialogs/projectsettings.py b/novelwriter/dialogs/projectsettings.py index 5eecdd0a8..b2b1ee549 100644 --- a/novelwriter/dialogs/projectsettings.py +++ b/novelwriter/dialogs/projectsettings.py @@ -352,17 +352,17 @@ def __init__(self, parent: QWidget, isStatus: bool) -> None: self.listBox.setIndentation(0) self.listBox.setSelectionBehavior(QAbstractItemView.SelectionBehavior.SelectRows) self.listBox.setSelectionMode(QAbstractItemView.SelectionMode.SingleSelection) - self.listBox.itemSelectionChanged.connect(self._selectionChanged) + self.listBox.itemSelectionChanged.connect(self._onSelectionChanged) for key, entry in status.iterItems(): self._addItem(key, StatusEntry.duplicate(entry)) # List Controls self.addButton = NIconToolButton(self, iSz, "add") - self.addButton.clicked.connect(self._newItem) + self.addButton.clicked.connect(self._onItemCreate) self.delButton = NIconToolButton(self, iSz, "remove") - self.delButton.clicked.connect(self._delItem) + self.delButton.clicked.connect(self._onItemDelete) self.upButton = NIconToolButton(self, iSz, "up") self.upButton.clicked.connect(qtLambda(self._moveItem, -1)) @@ -375,6 +375,7 @@ def __init__(self, parent: QWidget, isStatus: bool) -> None: self.editName.setMaxLength(40) self.editName.setPlaceholderText(self.tr("Select item to edit")) self.editName.setEnabled(False) + self.editName.textEdited.connect(self._onNameEdit) buttonStyle = ( f"QToolButton {{padding: 0 {bPd}px;}} " @@ -386,13 +387,13 @@ def __init__(self, parent: QWidget, isStatus: bool) -> None: self.colorButton.setSizePolicy(QtSizeMinimum, QtSizeMinimumExpanding) self.colorButton.setStyleSheet(buttonStyle) self.colorButton.setEnabled(False) - self.colorButton.clicked.connect(self._selectColour) + self.colorButton.clicked.connect(self._onColourSelect) 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, trConst(label)) - action.triggered.connect(lambda _, shape=shape: self._selectShape(shape)) + action.triggered.connect(qtLambda(self._selectShape, shape)) self._icons[shape] = icon self.shapeMenu = QMenu(self) @@ -408,12 +409,6 @@ def buildMenu(menu: QMenu, items: dict[nwStatusShape, str]) -> None: 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() self.listControls.addWidget(self.addButton) @@ -426,7 +421,6 @@ def buildMenu(menu: QMenu, items: dict[nwStatusShape, str]) -> None: 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, 1) @@ -474,16 +468,28 @@ def columnWidth(self) -> int: # Private Slots ## + @pyqtSlot(str) + def _onNameEdit(self, text: str) -> None: + """Update the status label text.""" + if item := self._getSelectedItem(): + name = simplified(text) + entry: StatusEntry = item.data(self.C_DATA, self.D_ENTRY) + entry.name = name + item.setText(self.C_LABEL, name) + self._changed = True + return + @pyqtSlot() - def _selectColour(self) -> None: + def _onColourSelect(self) -> None: """Open a dialog to select the status icon colour.""" if (color := QColorDialog.getColor(self._color, self, self.trSelColor)).isValid(): self._color = color self._setButtonIcons() + self._updateIcon() return @pyqtSlot() - def _newItem(self) -> None: + def _onItemCreate(self) -> None: """Create a new status item.""" color = QColor(100, 100, 100) shape = nwStatusShape.SQUARE @@ -493,7 +499,7 @@ def _newItem(self) -> None: return @pyqtSlot() - def _delItem(self) -> None: + def _onItemDelete(self) -> None: """Delete a status item.""" if item := self._getSelectedItem(): iRow = self.listBox.indexOfTopLevelItem(item) @@ -506,28 +512,7 @@ def _delItem(self) -> None: return @pyqtSlot() - def _applyChanges(self) -> None: - """Save changes made to a status item.""" - 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 _selectionChanged(self) -> None: + def _onSelectionChanged(self) -> None: """Extract the info of a selected item and populate the settings boxes and button. If no item is selected, clear the form. """ @@ -544,8 +529,6 @@ def _selectionChanged(self) -> None: self.editName.setEnabled(True) self.colorButton.setEnabled(True) self.shapeButton.setEnabled(True) - self.applyButton.setEnabled(True) - else: self._color = QColor(100, 100, 100) self._shape = nwStatusShape.SQUARE @@ -555,7 +538,6 @@ def _selectionChanged(self) -> None: self.editName.setEnabled(False) self.colorButton.setEnabled(False) self.shapeButton.setEnabled(False) - self.applyButton.setEnabled(False) return ## @@ -566,6 +548,19 @@ def _selectShape(self, shape: nwStatusShape) -> None: """Set the current shape.""" self._shape = shape self._setButtonIcons() + self._updateIcon() + return + + def _updateIcon(self) -> None: + """Apply changes made to a status icon.""" + if item := self._getSelectedItem(): + icon = NWStatus.createIcon(self._iPx, self._color, self._shape) + entry: StatusEntry = item.data(self.C_DATA, self.D_ENTRY) + entry.color = self._color + entry.shape = self._shape + entry.icon = icon + item.setIcon(self.C_LABEL, icon) + self._changed = True return def _addItem(self, key: str | None, entry: StatusEntry) -> None: From c08eca7c3b2b1f9832d7384045928fa11772f3a2 Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Mon, 30 Dec 2024 16:39:44 +0100 Subject: [PATCH 2/4] Update test --- tests/test_dialogs/test_dlg_projectsettings.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/tests/test_dialogs/test_dlg_projectsettings.py b/tests/test_dialogs/test_dlg_projectsettings.py index 151a7e8aa..7f9c8e258 100644 --- a/tests/test_dialogs/test_dlg_projectsettings.py +++ b/tests/test_dialogs/test_dlg_projectsettings.py @@ -198,10 +198,9 @@ def testDlgProjSettings_StatusImport(qtbot, monkeypatch, nwGUI, projPath, mockRn mp.setattr(QColorDialog, "getColor", lambda *a: QColor(20, 30, 40)) status.addButton.click() status.listBox.setCurrentItem(status.listBox.topLevelItem(3)) - status.editName.setText("Final") + status._onNameEdit("Final") status.colorButton.click() status._selectShape(nwStatusShape.CIRCLE) - status.applyButton.click() assert status.listBox.topLevelItemCount() == 4 assert status.changed is True @@ -274,10 +273,9 @@ def testDlgProjSettings_StatusImport(qtbot, monkeypatch, nwGUI, projPath, mockRn importance.addButton.click() importance.listBox.clearSelection() importance.listBox.setCurrentItem(importance.listBox.topLevelItem(3)) - importance.editName.setText("Final") + importance._onNameEdit("Final") importance.colorButton.click() importance._selectShape(nwStatusShape.TRIANGLE) - importance.applyButton.click() assert importance.listBox.topLevelItemCount() == 4 assert importance.changed is True From 6448f05557658ba37a55cf2f4a24038ea8a78df9 Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Mon, 30 Dec 2024 16:50:34 +0100 Subject: [PATCH 3/4] Also remove apply button from auto-replace settings --- novelwriter/dialogs/projectsettings.py | 46 +++++++++---------- .../test_dialogs/test_dlg_projectsettings.py | 10 +--- 2 files changed, 25 insertions(+), 31 deletions(-) diff --git a/novelwriter/dialogs/projectsettings.py b/novelwriter/dialogs/projectsettings.py index b2b1ee549..21f2476fa 100644 --- a/novelwriter/dialogs/projectsettings.py +++ b/novelwriter/dialogs/projectsettings.py @@ -30,7 +30,7 @@ from PyQt5.QtGui import QCloseEvent, QColor from PyQt5.QtWidgets import ( QAbstractItemView, QApplication, QColorDialog, QDialogButtonBox, - QHBoxLayout, QLineEdit, QMenu, QStackedWidget, QToolButton, QTreeWidget, + QHBoxLayout, QLineEdit, QMenu, QStackedWidget, QTreeWidget, QTreeWidgetItem, QVBoxLayout, QWidget ) @@ -660,15 +660,12 @@ def __init__(self, parent: QWidget) -> None: self.editKey.setPlaceholderText(self.tr("Select item to edit")) self.editKey.setEnabled(False) self.editKey.setMaxLength(40) + self.editKey.textEdited.connect(self._onKeyEdit) self.editValue = QLineEdit(self) self.editValue.setEnabled(False) - self.editValue.setMaxLength(80) - - self.applyButton = QToolButton(self) - self.applyButton.setText(self.tr("Apply")) - self.applyButton.setSizePolicy(QtSizeMinimum, QtSizeMinimumExpanding) - self.applyButton.clicked.connect(self._applyChanges) + self.editValue.setMaxLength(250) + self.editValue.textEdited.connect(self._onValueEdit) # Assemble self.listControls = QVBoxLayout() @@ -679,7 +676,6 @@ def __init__(self, parent: QWidget) -> None: self.editBox = QHBoxLayout() self.editBox.addWidget(self.editKey, 4) self.editBox.addWidget(self.editValue, 5) - self.editBox.addWidget(self.applyButton, 0) self.mainBox = QVBoxLayout() self.mainBox.addWidget(self.listBox, 1) @@ -711,7 +707,7 @@ def getNewList(self) -> dict[str, str]: new = {} for n in range(self.listBox.topLevelItemCount()): if item := self.listBox.topLevelItem(n): - if key := self._stripNotAllowed(item.text(self.C_KEY)): + if key := self._stripKey(item.text(self.C_KEY)): new[key] = item.text(self.C_REPL) return new @@ -723,13 +719,29 @@ def columnWidth(self) -> int: # Private Slots ## + @pyqtSlot(str) + def _onKeyEdit(self, text: str) -> None: + """Update the key text.""" + if (item := self._getSelectedItem()) and (key := self._stripKey(text)): + item.setText(self.C_KEY, f"<{key}>") + self._changed = True + return + + @pyqtSlot(str) + def _onValueEdit(self, text: str) -> None: + """Update the value text.""" + if item := self._getSelectedItem(): + item.setText(self.C_REPL, text) + self._changed = True + return + @pyqtSlot() def _selectionChanged(self) -> None: """Extract the details from the selected item and populate the edit form. """ if item := self._getSelectedItem(): - self.editKey.setText(self._stripNotAllowed(item.text(self.C_KEY))) + self.editKey.setText(self._stripKey(item.text(self.C_KEY))) self.editValue.setText(item.text(self.C_REPL)) self.editKey.setEnabled(True) self.editValue.setEnabled(True) @@ -742,18 +754,6 @@ def _selectionChanged(self) -> None: self.editValue.setEnabled(False) return - @pyqtSlot() - def _applyChanges(self) -> None: - """Save the form data into the list widget.""" - 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.""" @@ -779,6 +779,6 @@ def _getSelectedItem(self) -> QTreeWidgetItem | None: return items[0] return None - def _stripNotAllowed(self, key: str) -> str: + def _stripKey(self, key: str) -> str: """Clean up the replace key string.""" return "".join(c for c in key if c.isalnum()) diff --git a/tests/test_dialogs/test_dlg_projectsettings.py b/tests/test_dialogs/test_dlg_projectsettings.py index 7f9c8e258..c440e60c0 100644 --- a/tests/test_dialogs/test_dlg_projectsettings.py +++ b/tests/test_dialogs/test_dlg_projectsettings.py @@ -349,7 +349,6 @@ def testDlgProjSettings_Replace(qtbot, monkeypatch, nwGUI, projPath, mockRnd): # Nothing to save or delete replace.listBox.clearSelection() - replace._applyChanges() replace._delEntry() assert replace.listBox.topLevelItemCount() == 2 @@ -361,13 +360,8 @@ def testDlgProjSettings_Replace(qtbot, monkeypatch, nwGUI, projPath, mockRnd): # Edit the entry replace.listBox.setCurrentItem(replace.listBox.topLevelItem(2)) - replace.editKey.setText("") - for c in "Th is ": - qtbot.keyClick(replace.editKey, c, delay=KEY_DELAY) - replace.editValue.setText("") - for c in "With This Stuff ": - qtbot.keyClick(replace.editValue, c, delay=KEY_DELAY) - qtbot.mouseClick(replace.applyButton, QtMouseLeft) + replace._onKeyEdit("Th is ") + replace._onValueEdit("With This Stuff ") assert replace.listBox.topLevelItem(2).text(0) == "" # type: ignore assert replace.listBox.topLevelItem(2).text(1) == "With This Stuff " # type: ignore From 4a6511b39f595709402c68abe20fad6dd4e1161b Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Mon, 30 Dec 2024 16:55:54 +0100 Subject: [PATCH 4/4] Add some minor code cleanup --- novelwriter/dialogs/projectsettings.py | 36 +++++++++---------- novelwriter/guimain.py | 4 --- .../test_dialogs/test_dlg_projectsettings.py | 2 +- 3 files changed, 19 insertions(+), 23 deletions(-) diff --git a/novelwriter/dialogs/projectsettings.py b/novelwriter/dialogs/projectsettings.py index 21f2476fa..e31a8f63b 100644 --- a/novelwriter/dialogs/projectsettings.py +++ b/novelwriter/dialogs/projectsettings.py @@ -371,11 +371,11 @@ def __init__(self, parent: QWidget, isStatus: bool) -> None: self.dnButton.clicked.connect(qtLambda(self._moveItem, 1)) # Edit Form - self.editName = QLineEdit(self) - self.editName.setMaxLength(40) - self.editName.setPlaceholderText(self.tr("Select item to edit")) - self.editName.setEnabled(False) - self.editName.textEdited.connect(self._onNameEdit) + self.labelText = QLineEdit(self) + self.labelText.setMaxLength(40) + self.labelText.setPlaceholderText(self.tr("Select item to edit")) + self.labelText.setEnabled(False) + self.labelText.textEdited.connect(self._onNameEdit) buttonStyle = ( f"QToolButton {{padding: 0 {bPd}px;}} " @@ -418,7 +418,7 @@ def buildMenu(menu: QMenu, items: dict[nwStatusShape, str]) -> None: self.listControls.addStretch(1) self.editBox = QHBoxLayout() - self.editBox.addWidget(self.editName, 1) + self.editBox.addWidget(self.labelText, 1) self.editBox.addWidget(self.colorButton, 0) self.editBox.addWidget(self.shapeButton, 0) @@ -522,20 +522,20 @@ def _onSelectionChanged(self) -> None: self._shape = entry.shape self._setButtonIcons() - self.editName.setText(entry.name) - self.editName.selectAll() - self.editName.setFocus() + self.labelText.setText(entry.name) + self.labelText.selectAll() + self.labelText.setFocus() - self.editName.setEnabled(True) + self.labelText.setEnabled(True) self.colorButton.setEnabled(True) self.shapeButton.setEnabled(True) else: self._color = QColor(100, 100, 100) self._shape = nwStatusShape.SQUARE self._setButtonIcons() - self.editName.setText("") + self.labelText.setText("") - self.editName.setEnabled(False) + self.labelText.setEnabled(False) self.colorButton.setEnabled(False) self.shapeButton.setEnabled(False) return @@ -639,7 +639,7 @@ def __init__(self, parent: QWidget) -> None: self.listBox.setIndentation(0) self.listBox.setSelectionBehavior(QAbstractItemView.SelectionBehavior.SelectRows) self.listBox.setSelectionMode(QAbstractItemView.SelectionMode.SingleSelection) - self.listBox.itemSelectionChanged.connect(self._selectionChanged) + self.listBox.itemSelectionChanged.connect(self._onSelectionChanged) for aKey, aVal in SHARED.project.data.autoReplace.items(): newItem = QTreeWidgetItem(["<%s>" % aKey, aVal]) @@ -650,10 +650,10 @@ def __init__(self, parent: QWidget) -> None: # List Controls self.addButton = NIconToolButton(self, iSz, "add") - self.addButton.clicked.connect(self._addEntry) + self.addButton.clicked.connect(self._onEntryCreated) self.delButton = NIconToolButton(self, iSz, "remove") - self.delButton.clicked.connect(self._delEntry) + self.delButton.clicked.connect(self._onEntryDeleted) # Edit Form self.editKey = QLineEdit(self) @@ -736,7 +736,7 @@ def _onValueEdit(self, text: str) -> None: return @pyqtSlot() - def _selectionChanged(self) -> None: + def _onSelectionChanged(self) -> None: """Extract the details from the selected item and populate the edit form. """ @@ -755,14 +755,14 @@ def _selectionChanged(self) -> None: return @pyqtSlot() - def _addEntry(self) -> None: + def _onEntryCreated(self) -> None: """Add a new list entry.""" key = f"" self.listBox.addTopLevelItem(QTreeWidgetItem([key, ""])) return @pyqtSlot() - def _delEntry(self) -> None: + def _onEntryDeleted(self) -> None: """Delete the selected entry.""" if item := self._getSelectedItem(): self.listBox.takeTopLevelItem(self.listBox.indexOfTopLevelItem(item)) diff --git a/novelwriter/guimain.py b/novelwriter/guimain.py index d0f74bdce..1a3a56947 100644 --- a/novelwriter/guimain.py +++ b/novelwriter/guimain.py @@ -957,12 +957,8 @@ def _appFocusChanged(self, old: QWidget, new: QWidget) -> None: docEditor = True elif self.docViewer.isAncestorOf(new): docViewer = True - self.docEditor.changeFocusState(docEditor) self.docViewer.changeFocusState(docViewer) - - logger.debug("Main focus switched to: %s", type(new).__name__) - return @pyqtSlot(bool) diff --git a/tests/test_dialogs/test_dlg_projectsettings.py b/tests/test_dialogs/test_dlg_projectsettings.py index c440e60c0..1d009a70a 100644 --- a/tests/test_dialogs/test_dlg_projectsettings.py +++ b/tests/test_dialogs/test_dlg_projectsettings.py @@ -349,7 +349,7 @@ def testDlgProjSettings_Replace(qtbot, monkeypatch, nwGUI, projPath, mockRnd): # Nothing to save or delete replace.listBox.clearSelection() - replace._delEntry() + replace._onEntryDeleted() assert replace.listBox.topLevelItemCount() == 2 # Create a new entry