From 9532be585bcef6820345f3a2a9626bfbe34e71b8 Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Thu, 30 Jan 2025 17:28:18 +0100 Subject: [PATCH 1/3] Don't show create note for tag option for story and mention keywords (#2212) --- novelwriter/constants.py | 4 +++ novelwriter/gui/doceditor.py | 47 +++++++++++++++++++--------- tests/test_gui/test_gui_doceditor.py | 19 +++++------ 3 files changed, 47 insertions(+), 23 deletions(-) diff --git a/novelwriter/constants.py b/novelwriter/constants.py index ee2f3347..6ed952bf 100644 --- a/novelwriter/constants.py +++ b/novelwriter/constants.py @@ -173,6 +173,10 @@ class nwKeyWords: TAG_KEY, POV_KEY, FOCUS_KEY, CHAR_KEY, PLOT_KEY, TIME_KEY, WORLD_KEY, OBJECT_KEY, ENTITY_KEY, CUSTOM_KEY, STORY_KEY, MENTION_KEY, ] + CAN_CREATE = [ + POV_KEY, FOCUS_KEY, CHAR_KEY, PLOT_KEY, TIME_KEY, WORLD_KEY, + OBJECT_KEY, ENTITY_KEY, CUSTOM_KEY, + ] # Set of Valid Keys VALID_KEYS = set(ALL_KEYS) diff --git a/novelwriter/gui/doceditor.py b/novelwriter/gui/doceditor.py index 52e0180a..4e2c5029 100644 --- a/novelwriter/gui/doceditor.py +++ b/novelwriter/gui/doceditor.py @@ -34,7 +34,7 @@ from __future__ import annotations import bisect import logging -from enum import Enum +from enum import Enum, IntFlag from time import time from PyQt5.QtCore import ( @@ -57,7 +57,7 @@ from novelwriter.constants import nwConst, nwKeyWords, nwShortcode, nwUnicode from novelwriter.core.document import NWDocument from novelwriter.enum import ( nwChange, nwComment, nwDocAction, nwDocInsert, nwDocMode, nwItemClass, - nwItemType, nwTrinary + nwItemType ) from novelwriter.extensions.configlayout import NColourLabel from novelwriter.extensions.eventfilters import WheelEventFilter @@ -84,6 +84,15 @@ class _SelectAction(Enum): MOVE_AFTER = 3 +class _TagAction(IntFlag): + + NONE = 0b0000 + GOOD = 0b0001 + BAD = 0b0010 + FOLLOW = 0b0100 + CREATE = 0b1000 + + class GuiDocEditor(QPlainTextEdit): """Gui Widget: Main Document Editor""" @@ -1158,11 +1167,12 @@ class GuiDocEditor(QPlainTextEdit): # Follow status = self._processTag(cursor=pCursor, follow=False) - if status == nwTrinary.POSITIVE: + print(status) + if status & _TagAction.FOLLOW: action = ctxMenu.addAction(self.tr("Follow Tag")) action.triggered.connect(qtLambda(self._processTag, cursor=pCursor, follow=True)) ctxMenu.addSeparator() - elif status == nwTrinary.NEGATIVE: + elif status & _TagAction.CREATE: action = ctxMenu.addAction(self.tr("Create Note for Tag")) action.triggered.connect(qtLambda(self._processTag, cursor=pCursor, create=True)) ctxMenu.addSeparator() @@ -1925,8 +1935,9 @@ class GuiDocEditor(QPlainTextEdit): self._qDocument.syntaxHighlighter.rehighlightBlock(block) return - def _processTag(self, cursor: QTextCursor | None = None, - follow: bool = True, create: bool = False) -> nwTrinary: + def _processTag( + self, cursor: QTextCursor | None = None, follow: bool = True, create: bool = False + ) -> _TagAction: """Activated by Ctrl+Enter. Checks that we're in a block starting with '@'. We then find the tag under the cursor and check that it is not the tag itself. If all this is fine, we @@ -1936,19 +1947,22 @@ class GuiDocEditor(QPlainTextEdit): if cursor is None: cursor = self.textCursor() + status = _TagAction.NONE block = cursor.block() text = block.text() if len(text) == 0: - return nwTrinary.NEUTRAL + return status if text.startswith("@") and self._docHandle: isGood, tBits, tPos = SHARED.project.index.scanThis(text) if ( - not isGood or not tBits or tBits[0] == nwKeyWords.TAG_KEY - or tBits[0] not in nwKeyWords.VALID_KEYS + not isGood + or not tBits + or (key := tBits[0]) == nwKeyWords.TAG_KEY + or key not in nwKeyWords.VALID_KEYS ): - return nwTrinary.NEUTRAL + return status tag = "" exist = False @@ -1965,7 +1979,14 @@ class GuiDocEditor(QPlainTextEdit): if not tag or tag.startswith("@"): # The keyword cannot be looked up, so we ignore that - return nwTrinary.NEUTRAL + return status + + if not exist and key in nwKeyWords.CAN_CREATE: + # Must only be set if we have a tag selected + status |= _TagAction.CREATE + + if exist: + status |= _TagAction.FOLLOW if follow and exist: logger.debug("Attempting to follow tag '%s'", tag) @@ -1977,9 +1998,7 @@ class GuiDocEditor(QPlainTextEdit): itemClass = nwKeyWords.KEY_CLASS.get(tBits[0], nwItemClass.NO_CLASS) self.requestNewNoteCreation.emit(tag, itemClass) - return nwTrinary.POSITIVE if exist else nwTrinary.NEGATIVE - - return nwTrinary.NEUTRAL + return status def _emitRenameItem(self, block: QTextBlock) -> None: """Emit a signal to request an item be renamed.""" diff --git a/tests/test_gui/test_gui_doceditor.py b/tests/test_gui/test_gui_doceditor.py index efd4345e..e71b93ab 100644 --- a/tests/test_gui/test_gui_doceditor.py +++ b/tests/test_gui/test_gui_doceditor.py @@ -35,8 +35,8 @@ from novelwriter import CONFIG, SHARED from novelwriter.common import decodeMimeHandles from novelwriter.constants import nwKeyWords, nwUnicode from novelwriter.dialogs.editlabel import GuiEditLabel -from novelwriter.enum import nwDocAction, nwDocInsert, nwItemClass, nwItemLayout, nwTrinary -from novelwriter.gui.doceditor import GuiDocEditor +from novelwriter.enum import nwDocAction, nwDocInsert, nwItemClass, nwItemLayout +from novelwriter.gui.doceditor import GuiDocEditor, _TagAction from novelwriter.text.counting import standardCounter from novelwriter.types import ( QtAlignJustify, QtAlignLeft, QtKeepAnchor, QtModCtrl, QtModNone, @@ -1693,21 +1693,22 @@ def testGuiEditor_Tags(qtbot, nwGUI, projPath, ipsumText, mockRnd): # Empty Block docEditor.setCursorLine(2) - assert docEditor._processTag() is nwTrinary.NEUTRAL + assert docEditor._processTag() == _TagAction.NONE # Not On Tag docEditor.setCursorLine(1) - assert docEditor._processTag() is nwTrinary.NEUTRAL + assert docEditor._processTag() == _TagAction.NONE # On Tag Keyword docEditor.setCursorPosition(15) - assert docEditor._processTag() is nwTrinary.NEUTRAL + assert docEditor._processTag() == _TagAction.NONE # On Known Tag, No Follow docEditor.setCursorPosition(22) - assert docEditor._processTag(follow=False) is nwTrinary.POSITIVE + assert docEditor._processTag(follow=False) == _TagAction.FOLLOW assert nwGUI.docViewer._docHandle is None + # qtbot.stop() # On Known Tag, Follow docEditor.setCursorPosition(22) position = docEditor.cursorRect().center() @@ -1723,13 +1724,13 @@ def testGuiEditor_Tags(qtbot, nwGUI, projPath, ipsumText, mockRnd): # On Unknown Tag, Create It assert "0000000000011" not in SHARED.project.tree docEditor.setCursorPosition(28) - assert docEditor._processTag(create=True) is nwTrinary.NEGATIVE + assert docEditor._processTag(create=True) == _TagAction.CREATE assert "0000000000011" in SHARED.project.tree # On Unknown Tag, Missing Root assert "0000000000012" not in SHARED.project.tree docEditor.setCursorPosition(42) - assert docEditor._processTag(create=True) is nwTrinary.NEGATIVE + assert docEditor._processTag(create=True) == _TagAction.CREATE oHandle = SHARED.project.tree.findRoot(nwItemClass.OBJECT) assert oHandle == "0000000000012" @@ -1738,7 +1739,7 @@ def testGuiEditor_Tags(qtbot, nwGUI, projPath, ipsumText, mockRnd): assert oItem.itemParent == "0000000000012" docEditor.setCursorPosition(47) - assert docEditor._processTag() is nwTrinary.NEUTRAL + assert docEditor._processTag() == _TagAction.NONE # qtbot.stop() From f83e9d8c87ee2536e3d5d7cd6be716c7f4705b58 Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Thu, 30 Jan 2025 17:28:39 +0100 Subject: [PATCH 2/3] Drop the trinary enum --- novelwriter/enum.py | 7 ------- novelwriter/extensions/statusled.py | 11 +++++------ novelwriter/gui/statusbar.py | 13 ++++++------- tests/test_gui/test_gui_statusbar.py | 25 ++++++++++++------------- 4 files changed, 23 insertions(+), 33 deletions(-) diff --git a/novelwriter/enum.py b/novelwriter/enum.py index d738a616..c9d871b3 100644 --- a/novelwriter/enum.py +++ b/novelwriter/enum.py @@ -68,13 +68,6 @@ class nwComment(Enum): STORY = 7 -class nwTrinary(Enum): - - NEGATIVE = -1 - NEUTRAL = 0 - POSITIVE = 1 - - class nwChange(Enum): CREATE = 0 diff --git a/novelwriter/extensions/statusled.py b/novelwriter/extensions/statusled.py index e9a88104..e3806cc0 100644 --- a/novelwriter/extensions/statusled.py +++ b/novelwriter/extensions/statusled.py @@ -29,7 +29,6 @@ from PyQt5.QtGui import QColor, QPainter, QPaintEvent from PyQt5.QtWidgets import QAbstractButton, QWidget from novelwriter import CONFIG -from novelwriter.enum import nwTrinary from novelwriter.types import QtBlack, QtPaintAntiAlias logger = logging.getLogger(__name__) @@ -47,14 +46,14 @@ class StatusLED(QAbstractButton): self._postitve = QtBlack self._negative = QtBlack self._color = QtBlack - self._state = nwTrinary.NEUTRAL + self._state = None self._bPx = CONFIG.pxInt(1) self.setFixedWidth(sW) self.setFixedHeight(sH) return @property - def state(self) -> nwTrinary: + def state(self) -> bool | None: """The current state of the LED.""" return self._state @@ -66,11 +65,11 @@ class StatusLED(QAbstractButton): self.setState(self._state) return - def setState(self, state: nwTrinary) -> None: + def setState(self, state: bool | None) -> None: """Set the colour state.""" - if state == nwTrinary.POSITIVE: + if state is True: self._color = self._postitve - elif state == nwTrinary.NEGATIVE: + elif state is False: self._color = self._negative else: self._color = self._neutral diff --git a/novelwriter/gui/statusbar.py b/novelwriter/gui/statusbar.py index 14e3f420..ad810589 100644 --- a/novelwriter/gui/statusbar.py +++ b/novelwriter/gui/statusbar.py @@ -34,7 +34,6 @@ from PyQt5.QtWidgets import QApplication, QLabel, QStatusBar, QWidget from novelwriter import CONFIG, SHARED from novelwriter.common import formatTime from novelwriter.constants import nwConst -from novelwriter.enum import nwTrinary from novelwriter.extensions.modified import NClickableLabel from novelwriter.extensions.statusled import StatusLED @@ -121,8 +120,8 @@ class GuiMainStatus(QStatusBar): self.setRefTime(-1.0) self.setLanguage(*SHARED.spelling.describeDict()) self.setProjectStats(0, 0) - self.setProjectStatus(nwTrinary.NEUTRAL) - self.setDocumentStatus(nwTrinary.NEUTRAL) + self.setProjectStatus(None) + self.setDocumentStatus(None) self.updateTime() return @@ -152,12 +151,12 @@ class GuiMainStatus(QStatusBar): self._refTime = refTime return - def setProjectStatus(self, state: nwTrinary) -> None: + def setProjectStatus(self, state: bool | None) -> None: """Set the project status colour icon.""" self.projIcon.setState(state) return - def setDocumentStatus(self, state: nwTrinary) -> None: + def setDocumentStatus(self, state: bool | None) -> None: """Set the document status colour icon.""" self.docIcon.setState(state) return @@ -220,13 +219,13 @@ class GuiMainStatus(QStatusBar): @pyqtSlot(bool) def updateProjectStatus(self, status: bool) -> None: """Update the project status.""" - self.setProjectStatus(nwTrinary.NEGATIVE if status else nwTrinary.POSITIVE) + self.setProjectStatus(not status) return @pyqtSlot(bool) def updateDocumentStatus(self, status: bool) -> None: """Update the document status.""" - self.setDocumentStatus(nwTrinary.NEGATIVE if status else nwTrinary.POSITIVE) + self.setDocumentStatus(not status) return ## diff --git a/tests/test_gui/test_gui_statusbar.py b/tests/test_gui/test_gui_statusbar.py index 1a279a48..7090f6f7 100644 --- a/tests/test_gui/test_gui_statusbar.py +++ b/tests/test_gui/test_gui_statusbar.py @@ -25,7 +25,6 @@ import time import pytest from novelwriter import CONFIG, SHARED -from novelwriter.enum import nwTrinary from tests.tools import C, buildTestProject @@ -47,20 +46,20 @@ def testGuiStatusBar_Main(qtbot, monkeypatch, nwGUI, projPath, mockRnd): assert status._refTime == refTime # Project Status - status.setProjectStatus(nwTrinary.NEUTRAL) - assert status.projIcon.state == nwTrinary.NEUTRAL - status.setProjectStatus(nwTrinary.NEGATIVE) - assert status.projIcon.state == nwTrinary.NEGATIVE - status.setProjectStatus(nwTrinary.POSITIVE) - assert status.projIcon.state == nwTrinary.POSITIVE + status.setProjectStatus(None) + assert status.projIcon.state is None + status.setProjectStatus(False) + assert status.projIcon.state is False + status.setProjectStatus(True) + assert status.projIcon.state is True # Document Status - status.setDocumentStatus(nwTrinary.NEUTRAL) - assert status.docIcon.state == nwTrinary.NEUTRAL - status.setDocumentStatus(nwTrinary.NEGATIVE) - assert status.docIcon.state == nwTrinary.NEGATIVE - status.setDocumentStatus(nwTrinary.POSITIVE) - assert status.docIcon.state == nwTrinary.POSITIVE + status.setDocumentStatus(None) + assert status.docIcon.state is None + status.setDocumentStatus(False) + assert status.docIcon.state is False + status.setDocumentStatus(True) + assert status.docIcon.state is True # Idle Status CONFIG.stopWhenIdle = False From 8da56e56c11350da15408fc76c5bdd5487506667 Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Thu, 30 Jan 2025 17:33:43 +0100 Subject: [PATCH 3/3] Remove unused flags and debug print --- novelwriter/gui/doceditor.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/novelwriter/gui/doceditor.py b/novelwriter/gui/doceditor.py index 4e2c5029..d02f01d9 100644 --- a/novelwriter/gui/doceditor.py +++ b/novelwriter/gui/doceditor.py @@ -86,11 +86,9 @@ class _SelectAction(Enum): class _TagAction(IntFlag): - NONE = 0b0000 - GOOD = 0b0001 - BAD = 0b0010 - FOLLOW = 0b0100 - CREATE = 0b1000 + NONE = 0b00 + FOLLOW = 0b01 + CREATE = 0b10 class GuiDocEditor(QPlainTextEdit): @@ -1167,7 +1165,6 @@ class GuiDocEditor(QPlainTextEdit): # Follow status = self._processTag(cursor=pCursor, follow=False) - print(status) if status & _TagAction.FOLLOW: action = ctxMenu.addAction(self.tr("Follow Tag")) action.triggered.connect(qtLambda(self._processTag, cursor=pCursor, follow=True))