Fix issue with "create note from tag" (#2215)
This commit is contained in:
@@ -173,6 +173,10 @@ class nwKeyWords:
|
|||||||
TAG_KEY, POV_KEY, FOCUS_KEY, CHAR_KEY, PLOT_KEY, TIME_KEY, WORLD_KEY,
|
TAG_KEY, POV_KEY, FOCUS_KEY, CHAR_KEY, PLOT_KEY, TIME_KEY, WORLD_KEY,
|
||||||
OBJECT_KEY, ENTITY_KEY, CUSTOM_KEY, STORY_KEY, MENTION_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
|
# Set of Valid Keys
|
||||||
VALID_KEYS = set(ALL_KEYS)
|
VALID_KEYS = set(ALL_KEYS)
|
||||||
|
|||||||
@@ -68,13 +68,6 @@ class nwComment(Enum):
|
|||||||
STORY = 7
|
STORY = 7
|
||||||
|
|
||||||
|
|
||||||
class nwTrinary(Enum):
|
|
||||||
|
|
||||||
NEGATIVE = -1
|
|
||||||
NEUTRAL = 0
|
|
||||||
POSITIVE = 1
|
|
||||||
|
|
||||||
|
|
||||||
class nwChange(Enum):
|
class nwChange(Enum):
|
||||||
|
|
||||||
CREATE = 0
|
CREATE = 0
|
||||||
|
|||||||
@@ -29,7 +29,6 @@ from PyQt5.QtGui import QColor, QPainter, QPaintEvent
|
|||||||
from PyQt5.QtWidgets import QAbstractButton, QWidget
|
from PyQt5.QtWidgets import QAbstractButton, QWidget
|
||||||
|
|
||||||
from novelwriter import CONFIG
|
from novelwriter import CONFIG
|
||||||
from novelwriter.enum import nwTrinary
|
|
||||||
from novelwriter.types import QtBlack, QtPaintAntiAlias
|
from novelwriter.types import QtBlack, QtPaintAntiAlias
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
@@ -47,14 +46,14 @@ class StatusLED(QAbstractButton):
|
|||||||
self._postitve = QtBlack
|
self._postitve = QtBlack
|
||||||
self._negative = QtBlack
|
self._negative = QtBlack
|
||||||
self._color = QtBlack
|
self._color = QtBlack
|
||||||
self._state = nwTrinary.NEUTRAL
|
self._state = None
|
||||||
self._bPx = CONFIG.pxInt(1)
|
self._bPx = CONFIG.pxInt(1)
|
||||||
self.setFixedWidth(sW)
|
self.setFixedWidth(sW)
|
||||||
self.setFixedHeight(sH)
|
self.setFixedHeight(sH)
|
||||||
return
|
return
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def state(self) -> nwTrinary:
|
def state(self) -> bool | None:
|
||||||
"""The current state of the LED."""
|
"""The current state of the LED."""
|
||||||
return self._state
|
return self._state
|
||||||
|
|
||||||
@@ -66,11 +65,11 @@ class StatusLED(QAbstractButton):
|
|||||||
self.setState(self._state)
|
self.setState(self._state)
|
||||||
return
|
return
|
||||||
|
|
||||||
def setState(self, state: nwTrinary) -> None:
|
def setState(self, state: bool | None) -> None:
|
||||||
"""Set the colour state."""
|
"""Set the colour state."""
|
||||||
if state == nwTrinary.POSITIVE:
|
if state is True:
|
||||||
self._color = self._postitve
|
self._color = self._postitve
|
||||||
elif state == nwTrinary.NEGATIVE:
|
elif state is False:
|
||||||
self._color = self._negative
|
self._color = self._negative
|
||||||
else:
|
else:
|
||||||
self._color = self._neutral
|
self._color = self._neutral
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ from __future__ import annotations
|
|||||||
import bisect
|
import bisect
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from enum import Enum
|
from enum import Enum, IntFlag
|
||||||
from time import time
|
from time import time
|
||||||
|
|
||||||
from PyQt5.QtCore import (
|
from PyQt5.QtCore import (
|
||||||
@@ -57,7 +57,7 @@ from novelwriter.constants import nwConst, nwKeyWords, nwShortcode, nwUnicode
|
|||||||
from novelwriter.core.document import NWDocument
|
from novelwriter.core.document import NWDocument
|
||||||
from novelwriter.enum import (
|
from novelwriter.enum import (
|
||||||
nwChange, nwComment, nwDocAction, nwDocInsert, nwDocMode, nwItemClass,
|
nwChange, nwComment, nwDocAction, nwDocInsert, nwDocMode, nwItemClass,
|
||||||
nwItemType, nwTrinary
|
nwItemType
|
||||||
)
|
)
|
||||||
from novelwriter.extensions.configlayout import NColourLabel
|
from novelwriter.extensions.configlayout import NColourLabel
|
||||||
from novelwriter.extensions.eventfilters import WheelEventFilter
|
from novelwriter.extensions.eventfilters import WheelEventFilter
|
||||||
@@ -84,6 +84,13 @@ class _SelectAction(Enum):
|
|||||||
MOVE_AFTER = 3
|
MOVE_AFTER = 3
|
||||||
|
|
||||||
|
|
||||||
|
class _TagAction(IntFlag):
|
||||||
|
|
||||||
|
NONE = 0b00
|
||||||
|
FOLLOW = 0b01
|
||||||
|
CREATE = 0b10
|
||||||
|
|
||||||
|
|
||||||
class GuiDocEditor(QPlainTextEdit):
|
class GuiDocEditor(QPlainTextEdit):
|
||||||
"""Gui Widget: Main Document Editor"""
|
"""Gui Widget: Main Document Editor"""
|
||||||
|
|
||||||
@@ -1158,11 +1165,11 @@ class GuiDocEditor(QPlainTextEdit):
|
|||||||
|
|
||||||
# Follow
|
# Follow
|
||||||
status = self._processTag(cursor=pCursor, follow=False)
|
status = self._processTag(cursor=pCursor, follow=False)
|
||||||
if status == nwTrinary.POSITIVE:
|
if status & _TagAction.FOLLOW:
|
||||||
action = ctxMenu.addAction(self.tr("Follow Tag"))
|
action = ctxMenu.addAction(self.tr("Follow Tag"))
|
||||||
action.triggered.connect(qtLambda(self._processTag, cursor=pCursor, follow=True))
|
action.triggered.connect(qtLambda(self._processTag, cursor=pCursor, follow=True))
|
||||||
ctxMenu.addSeparator()
|
ctxMenu.addSeparator()
|
||||||
elif status == nwTrinary.NEGATIVE:
|
elif status & _TagAction.CREATE:
|
||||||
action = ctxMenu.addAction(self.tr("Create Note for Tag"))
|
action = ctxMenu.addAction(self.tr("Create Note for Tag"))
|
||||||
action.triggered.connect(qtLambda(self._processTag, cursor=pCursor, create=True))
|
action.triggered.connect(qtLambda(self._processTag, cursor=pCursor, create=True))
|
||||||
ctxMenu.addSeparator()
|
ctxMenu.addSeparator()
|
||||||
@@ -1925,8 +1932,9 @@ class GuiDocEditor(QPlainTextEdit):
|
|||||||
self._qDocument.syntaxHighlighter.rehighlightBlock(block)
|
self._qDocument.syntaxHighlighter.rehighlightBlock(block)
|
||||||
return
|
return
|
||||||
|
|
||||||
def _processTag(self, cursor: QTextCursor | None = None,
|
def _processTag(
|
||||||
follow: bool = True, create: bool = False) -> nwTrinary:
|
self, cursor: QTextCursor | None = None, follow: bool = True, create: bool = False
|
||||||
|
) -> _TagAction:
|
||||||
"""Activated by Ctrl+Enter. Checks that we're in a block
|
"""Activated by Ctrl+Enter. Checks that we're in a block
|
||||||
starting with '@'. We then find the tag under the cursor and
|
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
|
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:
|
if cursor is None:
|
||||||
cursor = self.textCursor()
|
cursor = self.textCursor()
|
||||||
|
|
||||||
|
status = _TagAction.NONE
|
||||||
block = cursor.block()
|
block = cursor.block()
|
||||||
text = block.text()
|
text = block.text()
|
||||||
if len(text) == 0:
|
if len(text) == 0:
|
||||||
return nwTrinary.NEUTRAL
|
return status
|
||||||
|
|
||||||
if text.startswith("@") and self._docHandle:
|
if text.startswith("@") and self._docHandle:
|
||||||
|
|
||||||
isGood, tBits, tPos = SHARED.project.index.scanThis(text)
|
isGood, tBits, tPos = SHARED.project.index.scanThis(text)
|
||||||
if (
|
if (
|
||||||
not isGood or not tBits or tBits[0] == nwKeyWords.TAG_KEY
|
not isGood
|
||||||
or tBits[0] not in nwKeyWords.VALID_KEYS
|
or not tBits
|
||||||
|
or (key := tBits[0]) == nwKeyWords.TAG_KEY
|
||||||
|
or key not in nwKeyWords.VALID_KEYS
|
||||||
):
|
):
|
||||||
return nwTrinary.NEUTRAL
|
return status
|
||||||
|
|
||||||
tag = ""
|
tag = ""
|
||||||
exist = False
|
exist = False
|
||||||
@@ -1965,7 +1976,14 @@ class GuiDocEditor(QPlainTextEdit):
|
|||||||
|
|
||||||
if not tag or tag.startswith("@"):
|
if not tag or tag.startswith("@"):
|
||||||
# The keyword cannot be looked up, so we ignore that
|
# 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:
|
if follow and exist:
|
||||||
logger.debug("Attempting to follow tag '%s'", tag)
|
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)
|
itemClass = nwKeyWords.KEY_CLASS.get(tBits[0], nwItemClass.NO_CLASS)
|
||||||
self.requestNewNoteCreation.emit(tag, itemClass)
|
self.requestNewNoteCreation.emit(tag, itemClass)
|
||||||
|
|
||||||
return nwTrinary.POSITIVE if exist else nwTrinary.NEGATIVE
|
return status
|
||||||
|
|
||||||
return nwTrinary.NEUTRAL
|
|
||||||
|
|
||||||
def _emitRenameItem(self, block: QTextBlock) -> None:
|
def _emitRenameItem(self, block: QTextBlock) -> None:
|
||||||
"""Emit a signal to request an item be renamed."""
|
"""Emit a signal to request an item be renamed."""
|
||||||
|
|||||||
@@ -34,7 +34,6 @@ from PyQt5.QtWidgets import QApplication, QLabel, QStatusBar, QWidget
|
|||||||
from novelwriter import CONFIG, SHARED
|
from novelwriter import CONFIG, SHARED
|
||||||
from novelwriter.common import formatTime
|
from novelwriter.common import formatTime
|
||||||
from novelwriter.constants import nwConst
|
from novelwriter.constants import nwConst
|
||||||
from novelwriter.enum import nwTrinary
|
|
||||||
from novelwriter.extensions.modified import NClickableLabel
|
from novelwriter.extensions.modified import NClickableLabel
|
||||||
from novelwriter.extensions.statusled import StatusLED
|
from novelwriter.extensions.statusled import StatusLED
|
||||||
|
|
||||||
@@ -121,8 +120,8 @@ class GuiMainStatus(QStatusBar):
|
|||||||
self.setRefTime(-1.0)
|
self.setRefTime(-1.0)
|
||||||
self.setLanguage(*SHARED.spelling.describeDict())
|
self.setLanguage(*SHARED.spelling.describeDict())
|
||||||
self.setProjectStats(0, 0)
|
self.setProjectStats(0, 0)
|
||||||
self.setProjectStatus(nwTrinary.NEUTRAL)
|
self.setProjectStatus(None)
|
||||||
self.setDocumentStatus(nwTrinary.NEUTRAL)
|
self.setDocumentStatus(None)
|
||||||
self.updateTime()
|
self.updateTime()
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -152,12 +151,12 @@ class GuiMainStatus(QStatusBar):
|
|||||||
self._refTime = refTime
|
self._refTime = refTime
|
||||||
return
|
return
|
||||||
|
|
||||||
def setProjectStatus(self, state: nwTrinary) -> None:
|
def setProjectStatus(self, state: bool | None) -> None:
|
||||||
"""Set the project status colour icon."""
|
"""Set the project status colour icon."""
|
||||||
self.projIcon.setState(state)
|
self.projIcon.setState(state)
|
||||||
return
|
return
|
||||||
|
|
||||||
def setDocumentStatus(self, state: nwTrinary) -> None:
|
def setDocumentStatus(self, state: bool | None) -> None:
|
||||||
"""Set the document status colour icon."""
|
"""Set the document status colour icon."""
|
||||||
self.docIcon.setState(state)
|
self.docIcon.setState(state)
|
||||||
return
|
return
|
||||||
@@ -220,13 +219,13 @@ class GuiMainStatus(QStatusBar):
|
|||||||
@pyqtSlot(bool)
|
@pyqtSlot(bool)
|
||||||
def updateProjectStatus(self, status: bool) -> None:
|
def updateProjectStatus(self, status: bool) -> None:
|
||||||
"""Update the project status."""
|
"""Update the project status."""
|
||||||
self.setProjectStatus(nwTrinary.NEGATIVE if status else nwTrinary.POSITIVE)
|
self.setProjectStatus(not status)
|
||||||
return
|
return
|
||||||
|
|
||||||
@pyqtSlot(bool)
|
@pyqtSlot(bool)
|
||||||
def updateDocumentStatus(self, status: bool) -> None:
|
def updateDocumentStatus(self, status: bool) -> None:
|
||||||
"""Update the document status."""
|
"""Update the document status."""
|
||||||
self.setDocumentStatus(nwTrinary.NEGATIVE if status else nwTrinary.POSITIVE)
|
self.setDocumentStatus(not status)
|
||||||
return
|
return
|
||||||
|
|
||||||
##
|
##
|
||||||
|
|||||||
@@ -35,8 +35,8 @@ from novelwriter import CONFIG, SHARED
|
|||||||
from novelwriter.common import decodeMimeHandles
|
from novelwriter.common import decodeMimeHandles
|
||||||
from novelwriter.constants import nwKeyWords, nwUnicode
|
from novelwriter.constants import nwKeyWords, nwUnicode
|
||||||
from novelwriter.dialogs.editlabel import GuiEditLabel
|
from novelwriter.dialogs.editlabel import GuiEditLabel
|
||||||
from novelwriter.enum import nwDocAction, nwDocInsert, nwItemClass, nwItemLayout, nwTrinary
|
from novelwriter.enum import nwDocAction, nwDocInsert, nwItemClass, nwItemLayout
|
||||||
from novelwriter.gui.doceditor import GuiDocEditor
|
from novelwriter.gui.doceditor import GuiDocEditor, _TagAction
|
||||||
from novelwriter.text.counting import standardCounter
|
from novelwriter.text.counting import standardCounter
|
||||||
from novelwriter.types import (
|
from novelwriter.types import (
|
||||||
QtAlignJustify, QtAlignLeft, QtKeepAnchor, QtModCtrl, QtModNone,
|
QtAlignJustify, QtAlignLeft, QtKeepAnchor, QtModCtrl, QtModNone,
|
||||||
@@ -1693,21 +1693,22 @@ def testGuiEditor_Tags(qtbot, nwGUI, projPath, ipsumText, mockRnd):
|
|||||||
|
|
||||||
# Empty Block
|
# Empty Block
|
||||||
docEditor.setCursorLine(2)
|
docEditor.setCursorLine(2)
|
||||||
assert docEditor._processTag() is nwTrinary.NEUTRAL
|
assert docEditor._processTag() == _TagAction.NONE
|
||||||
|
|
||||||
# Not On Tag
|
# Not On Tag
|
||||||
docEditor.setCursorLine(1)
|
docEditor.setCursorLine(1)
|
||||||
assert docEditor._processTag() is nwTrinary.NEUTRAL
|
assert docEditor._processTag() == _TagAction.NONE
|
||||||
|
|
||||||
# On Tag Keyword
|
# On Tag Keyword
|
||||||
docEditor.setCursorPosition(15)
|
docEditor.setCursorPosition(15)
|
||||||
assert docEditor._processTag() is nwTrinary.NEUTRAL
|
assert docEditor._processTag() == _TagAction.NONE
|
||||||
|
|
||||||
# On Known Tag, No Follow
|
# On Known Tag, No Follow
|
||||||
docEditor.setCursorPosition(22)
|
docEditor.setCursorPosition(22)
|
||||||
assert docEditor._processTag(follow=False) is nwTrinary.POSITIVE
|
assert docEditor._processTag(follow=False) == _TagAction.FOLLOW
|
||||||
assert nwGUI.docViewer._docHandle is None
|
assert nwGUI.docViewer._docHandle is None
|
||||||
|
|
||||||
|
# qtbot.stop()
|
||||||
# On Known Tag, Follow
|
# On Known Tag, Follow
|
||||||
docEditor.setCursorPosition(22)
|
docEditor.setCursorPosition(22)
|
||||||
position = docEditor.cursorRect().center()
|
position = docEditor.cursorRect().center()
|
||||||
@@ -1723,13 +1724,13 @@ def testGuiEditor_Tags(qtbot, nwGUI, projPath, ipsumText, mockRnd):
|
|||||||
# On Unknown Tag, Create It
|
# On Unknown Tag, Create It
|
||||||
assert "0000000000011" not in SHARED.project.tree
|
assert "0000000000011" not in SHARED.project.tree
|
||||||
docEditor.setCursorPosition(28)
|
docEditor.setCursorPosition(28)
|
||||||
assert docEditor._processTag(create=True) is nwTrinary.NEGATIVE
|
assert docEditor._processTag(create=True) == _TagAction.CREATE
|
||||||
assert "0000000000011" in SHARED.project.tree
|
assert "0000000000011" in SHARED.project.tree
|
||||||
|
|
||||||
# On Unknown Tag, Missing Root
|
# On Unknown Tag, Missing Root
|
||||||
assert "0000000000012" not in SHARED.project.tree
|
assert "0000000000012" not in SHARED.project.tree
|
||||||
docEditor.setCursorPosition(42)
|
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)
|
oHandle = SHARED.project.tree.findRoot(nwItemClass.OBJECT)
|
||||||
assert oHandle == "0000000000012"
|
assert oHandle == "0000000000012"
|
||||||
|
|
||||||
@@ -1738,7 +1739,7 @@ def testGuiEditor_Tags(qtbot, nwGUI, projPath, ipsumText, mockRnd):
|
|||||||
assert oItem.itemParent == "0000000000012"
|
assert oItem.itemParent == "0000000000012"
|
||||||
|
|
||||||
docEditor.setCursorPosition(47)
|
docEditor.setCursorPosition(47)
|
||||||
assert docEditor._processTag() is nwTrinary.NEUTRAL
|
assert docEditor._processTag() == _TagAction.NONE
|
||||||
|
|
||||||
# qtbot.stop()
|
# qtbot.stop()
|
||||||
|
|
||||||
|
|||||||
@@ -25,7 +25,6 @@ import time
|
|||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from novelwriter import CONFIG, SHARED
|
from novelwriter import CONFIG, SHARED
|
||||||
from novelwriter.enum import nwTrinary
|
|
||||||
|
|
||||||
from tests.tools import C, buildTestProject
|
from tests.tools import C, buildTestProject
|
||||||
|
|
||||||
@@ -47,20 +46,20 @@ def testGuiStatusBar_Main(qtbot, monkeypatch, nwGUI, projPath, mockRnd):
|
|||||||
assert status._refTime == refTime
|
assert status._refTime == refTime
|
||||||
|
|
||||||
# Project Status
|
# Project Status
|
||||||
status.setProjectStatus(nwTrinary.NEUTRAL)
|
status.setProjectStatus(None)
|
||||||
assert status.projIcon.state == nwTrinary.NEUTRAL
|
assert status.projIcon.state is None
|
||||||
status.setProjectStatus(nwTrinary.NEGATIVE)
|
status.setProjectStatus(False)
|
||||||
assert status.projIcon.state == nwTrinary.NEGATIVE
|
assert status.projIcon.state is False
|
||||||
status.setProjectStatus(nwTrinary.POSITIVE)
|
status.setProjectStatus(True)
|
||||||
assert status.projIcon.state == nwTrinary.POSITIVE
|
assert status.projIcon.state is True
|
||||||
|
|
||||||
# Document Status
|
# Document Status
|
||||||
status.setDocumentStatus(nwTrinary.NEUTRAL)
|
status.setDocumentStatus(None)
|
||||||
assert status.docIcon.state == nwTrinary.NEUTRAL
|
assert status.docIcon.state is None
|
||||||
status.setDocumentStatus(nwTrinary.NEGATIVE)
|
status.setDocumentStatus(False)
|
||||||
assert status.docIcon.state == nwTrinary.NEGATIVE
|
assert status.docIcon.state is False
|
||||||
status.setDocumentStatus(nwTrinary.POSITIVE)
|
status.setDocumentStatus(True)
|
||||||
assert status.docIcon.state == nwTrinary.POSITIVE
|
assert status.docIcon.state is True
|
||||||
|
|
||||||
# Idle Status
|
# Idle Status
|
||||||
CONFIG.stopWhenIdle = False
|
CONFIG.stopWhenIdle = False
|
||||||
|
|||||||
Reference in New Issue
Block a user