Skip to content

Commit

Permalink
Fix issue with "create note from tag" (#2215)
Browse files Browse the repository at this point in the history
  • Loading branch information
vkbo authored Jan 30, 2025
2 parents 3509374 + 8da56e5 commit f103288
Show file tree
Hide file tree
Showing 7 changed files with 67 additions and 56 deletions.
4 changes: 4 additions & 0 deletions novelwriter/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
7 changes: 0 additions & 7 deletions novelwriter/enum.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,13 +68,6 @@ class nwComment(Enum):
STORY = 7


class nwTrinary(Enum):

NEGATIVE = -1
NEUTRAL = 0
POSITIVE = 1


class nwChange(Enum):

CREATE = 0
Expand Down
11 changes: 5 additions & 6 deletions novelwriter/extensions/statusled.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@
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__)
Expand All @@ -47,14 +46,14 @@ def __init__(self, sW: int, sH: int, parent: QWidget | None = None) -> None:
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

Expand All @@ -66,11 +65,11 @@ def setColors(self, neutral: QColor, positive: QColor, negative: QColor) -> None
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
Expand Down
44 changes: 30 additions & 14 deletions novelwriter/gui/doceditor.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
import bisect
import logging

from enum import Enum
from enum import Enum, IntFlag
from time import time

from PyQt5.QtCore import (
Expand All @@ -57,7 +57,7 @@
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
Expand All @@ -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"""

Expand Down Expand Up @@ -1158,11 +1165,11 @@ def _openContextMenu(self, pos: QPoint) -> None:

# 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()
Expand Down Expand Up @@ -1925,8 +1932,9 @@ def _addWord(self, word: str, block: QTextBlock, save: bool) -> None:
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
Expand All @@ -1936,19 +1944,22 @@ def _processTag(self, cursor: QTextCursor | None = None,
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
Expand All @@ -1965,7 +1976,14 @@ def _processTag(self, cursor: QTextCursor | None = None,

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)
Expand All @@ -1977,9 +1995,7 @@ def _processTag(self, cursor: QTextCursor | None = None,
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."""
Expand Down
13 changes: 6 additions & 7 deletions novelwriter/gui/statusbar.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@
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

Expand Down Expand Up @@ -121,8 +120,8 @@ def clearStatus(self) -> None:
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

Expand Down Expand Up @@ -152,12 +151,12 @@ def setRefTime(self, refTime: float) -> None:
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
Expand Down Expand Up @@ -220,13 +219,13 @@ def setLanguage(self, language: str, provider: str) -> None:
@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

##
Expand Down
19 changes: 10 additions & 9 deletions tests/test_gui/test_gui_doceditor.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,8 @@
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,
Expand Down Expand Up @@ -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()
Expand All @@ -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"

Expand All @@ -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()

Expand Down
25 changes: 12 additions & 13 deletions tests/test_gui/test_gui_statusbar.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@
import pytest

from novelwriter import CONFIG, SHARED
from novelwriter.enum import nwTrinary

from tests.tools import C, buildTestProject

Expand All @@ -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
Expand Down

0 comments on commit f103288

Please sign in to comment.