From 3f842c2fbee24e1ca212abd48faccb5f227a40e1 Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Thu, 23 Nov 2023 19:40:17 +0100 Subject: [PATCH] Add a context menu option to rename a document from any title within it --- novelwriter/gui/doceditor.py | 14 +++++++++++- novelwriter/gui/projtree.py | 34 ++++++++++++++--------------- novelwriter/guimain.py | 1 + tests/test_gui/test_gui_projtree.py | 9 +++----- 4 files changed, 34 insertions(+), 24 deletions(-) diff --git a/novelwriter/gui/doceditor.py b/novelwriter/gui/doceditor.py index f1ae127b..28225d8b 100644 --- a/novelwriter/gui/doceditor.py +++ b/novelwriter/gui/doceditor.py @@ -98,6 +98,7 @@ class GuiDocEditor(QPlainTextEdit): closeDocumentRequest = pyqtSignal() toggleFocusModeRequest = pyqtSignal() requestProjectItemSelected = pyqtSignal(str, bool) + requestProjectItemRenamed = pyqtSignal(str, str) def __init__(self, mainGui: GuiMain) -> None: super().__init__(parent=mainGui) @@ -371,7 +372,7 @@ class GuiDocEditor(QPlainTextEdit): return - def loadText(self, tHandle, tLine=None) -> bool: + def loadText(self, tHandle: str, tLine=None) -> bool: """Load text from a document into the editor. If we have an I/O error, we must handle this and clear the editor so that we don't risk overwriting the file if it exists. This can for instance @@ -1071,8 +1072,12 @@ class GuiDocEditor(QPlainTextEdit): """ uCursor = self.textCursor() pCursor = self.cursorForPosition(pos) + pBlock = pCursor.block() ctxMenu = QMenu(self) + if pBlock.userState() == GuiDocHighlighter.BLOCK_TITLE: + aLabel = ctxMenu.addAction(self.tr("Set as Document Name")) + aLabel.triggered.connect(lambda: self._emitRenameItem(pBlock)) # Follow status = self._processTag(cursor=pCursor, follow=False) @@ -1867,6 +1872,13 @@ class GuiDocEditor(QPlainTextEdit): return nwTrinary.NEUTRAL + def _emitRenameItem(self, block: QTextBlock) -> None: + """Emit a signal to request an item be renamed.""" + if self._docHandle: + text = block.text().lstrip("#").lstrip("!").strip() + self.requestProjectItemRenamed.emit(self._docHandle, text) + return + def _openContextFromCursor(self) -> None: """Open the spell check context menu at the cursor.""" self._openContextMenu(self.cursorRect().center()) diff --git a/novelwriter/gui/projtree.py b/novelwriter/gui/projtree.py index d55a0602..607e694a 100644 --- a/novelwriter/gui/projtree.py +++ b/novelwriter/gui/projtree.py @@ -190,17 +190,20 @@ class GuiProjectView(QWidget): """Check if the project tree has focus.""" return self.projTree.hasFocus() - def renameTreeItem(self, tHandle: str | None = None) -> bool: + ## + # Public Slots + ## + + @pyqtSlot(str, str) + def renameTreeItem(self, tHandle: str | None = None, name: str = "") -> None: """External request to rename an item or the currently selected item. This is triggered by the global menu or keyboard shortcut. """ if tHandle is None: tHandle = self.projTree.getSelectedHandle() - return self.projTree.renameTreeItem(tHandle) if tHandle else False - - ## - # Public Slots - ## + if tHandle: + self.projTree.renameTreeItem(tHandle, name=name) + return @pyqtSlot(str, bool) def setSelectedHandle(self, tHandle: str, doScroll: bool = False) -> None: @@ -766,19 +769,16 @@ class GuiProjectTree(QTreeWidget): self.setCurrentItem(tItem.child(0)) return - def renameTreeItem(self, tHandle: str) -> bool: + def renameTreeItem(self, tHandle: str, name: str = "") -> None: """Open a dialog to edit the label of an item.""" tItem = SHARED.project.tree[tHandle] - if tItem is None: - return False - - newLabel, dlgOk = GuiEditLabel.getLabel(self, text=tItem.itemName) - if dlgOk: - tItem.setName(newLabel) - self.setTreeItemValues(tHandle) - self._alertTreeChange(tHandle, flush=False) - - return True + if tItem: + newLabel, dlgOk = GuiEditLabel.getLabel(self, text=name or tItem.itemName) + if dlgOk: + tItem.setName(newLabel) + self.setTreeItemValues(tHandle) + self._alertTreeChange(tHandle, flush=False) + return def saveTreeOrder(self) -> None: """Build a list of the items in the project tree and send them diff --git a/novelwriter/guimain.py b/novelwriter/guimain.py index 4e885b24..defb0fe5 100644 --- a/novelwriter/guimain.py +++ b/novelwriter/guimain.py @@ -285,6 +285,7 @@ class GuiMain(QMainWindow): self.docEditor.closeDocumentRequest.connect(self.closeDocEditor) self.docEditor.toggleFocusModeRequest.connect(self.toggleFocusMode) self.docEditor.requestProjectItemSelected.connect(self.projView.setSelectedHandle) + self.docEditor.requestProjectItemRenamed.connect(self.projView.renameTreeItem) self.docViewer.documentLoaded.connect(self.docViewerPanel.updateHandle) self.docViewer.loadDocumentTagRequest.connect(self._followTag) diff --git a/tests/test_gui/test_gui_projtree.py b/tests/test_gui/test_gui_projtree.py index 76495311..679f4826 100644 --- a/tests/test_gui/test_gui_projtree.py +++ b/tests/test_gui/test_gui_projtree.py @@ -156,12 +156,9 @@ def testGuiProjTree_NewItems(qtbot, caplog, monkeypatch, nwGUI, projPath, mockRn # Rename plot folder with monkeypatch.context() as mp: mp.setattr(GuiEditLabel, "getLabel", lambda *a, **k: ("Stuff", True)) - projTree.renameTreeItem(C.hPlotRoot) is True + projTree.renameTreeItem(C.hPlotRoot) assert project.tree[C.hPlotRoot].itemName == "Stuff" # type: ignore - # Rename invalid folder - projTree.renameTreeItem("0000000000000") is False - # Other Checks # ============ @@ -1095,12 +1092,12 @@ def testGuiProjTree_Other(qtbot, monkeypatch, nwGUI: GuiMain, projPath, mockRnd) mp.setattr(GuiEditLabel, "getLabel", lambda *a, **k: ("FooBar", True)) projTree.clearSelection() assert SHARED.project.tree[C.hChapterDoc].itemName == "New Chapter" # type: ignore - assert projView.renameTreeItem(C.hChapterDoc) is True + projView.renameTreeItem(C.hChapterDoc) assert SHARED.project.tree[C.hChapterDoc].itemName == "FooBar" # type: ignore projTree.setSelectedHandle(C.hSceneDoc) assert SHARED.project.tree[C.hSceneDoc].itemName == "New Scene" # type: ignore - assert projView.renameTreeItem() is True + projView.renameTreeItem() assert SHARED.project.tree[C.hSceneDoc].itemName == "FooBar" # type: ignore # Check Crash Resistance