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/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/doceditor.py b/novelwriter/gui/doceditor.py index 52e0180a..d02f01d9 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,13 @@ class _SelectAction(Enum): MOVE_AFTER = 3 +class _TagAction(IntFlag): + + NONE = 0b00 + FOLLOW = 0b01 + CREATE = 0b10 + + class GuiDocEditor(QPlainTextEdit): """Gui Widget: Main Document Editor""" @@ -1158,11 +1165,11 @@ class GuiDocEditor(QPlainTextEdit): # Follow status = self._processTag(cursor=pCursor, follow=False) - if status == nwTrinary.POSITIVE: + 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 +1932,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 +1944,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 +1976,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 +1995,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/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_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() 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