From 70033cb0f33e8b4f8e3e3ff932dfa0ddb04de81d Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Sun, 14 Apr 2024 23:33:35 +0200 Subject: [PATCH] Add insert footnote feature in editor --- novelwriter/constants.py | 11 +++++++++-- novelwriter/core/index.py | 12 ++++++++++-- novelwriter/gui/doceditor.py | 32 +++++++++++++++++++++++++++++++- novelwriter/gui/dochighlight.py | 29 ++++++++++++++++++++++++----- sample/content/636b6aa9b697b.nwd | 8 +++++--- sample/nwProject.nwx | 6 +++--- 6 files changed, 82 insertions(+), 16 deletions(-) diff --git a/novelwriter/constants.py b/novelwriter/constants.py index 48487d77..917d0049 100644 --- a/novelwriter/constants.py +++ b/novelwriter/constants.py @@ -25,7 +25,9 @@ from __future__ import annotations from PyQt5.QtCore import QCoreApplication, QT_TRANSLATE_NOOP -from novelwriter.enum import nwBuildFmt, nwItemClass, nwItemLayout, nwOutline, nwStatusShape +from novelwriter.enum import ( + nwBuildFmt, nwComment, nwItemClass, nwItemLayout, nwOutline, nwStatusShape +) def trConst(text: str) -> str: @@ -67,7 +69,7 @@ class nwRegEx: FMT_EB = r"(? str | None: + """Generate a new key for a comment style.""" + if style == nwComment.FOOTNOTE: + return self._textIndex.footnotes.newKey() + elif style == nwComment.COMMENT: + return self._textIndex.comments.newKey() + return None + ## # Extract Data ## @@ -1322,8 +1330,8 @@ class TextIndex: __slots__ = ("_comments", "_footnotes") def __init__(self) -> None: - self._comments = TextRegistry("c_") - self._footnotes = TextRegistry("f_") + self._comments = TextRegistry("c") + self._footnotes = TextRegistry("f") return @property diff --git a/novelwriter/gui/doceditor.py b/novelwriter/gui/doceditor.py index 4d603289..b1e94046 100644 --- a/novelwriter/gui/doceditor.py +++ b/novelwriter/gui/doceditor.py @@ -55,7 +55,7 @@ from novelwriter import CONFIG, SHARED from novelwriter.common import minmax, transferCase from novelwriter.constants import nwConst, nwKeyWords, nwShortcode, nwUnicode from novelwriter.core.document import NWDocument -from novelwriter.enum import nwDocAction, nwDocInsert, nwDocMode, nwItemClass, nwTrinary +from novelwriter.enum import nwComment, nwDocAction, nwDocInsert, nwDocMode, nwItemClass, nwTrinary from novelwriter.extensions.eventfilters import WheelEventFilter from novelwriter.extensions.modified import NIconToggleButton, NIconToolButton from novelwriter.gui.dochighlight import BLOCK_META, BLOCK_TITLE @@ -884,6 +884,9 @@ class GuiDocEditor(QPlainTextEdit): text = GuiLipsum.getLipsum(self) newBlock = True goAfter = False + elif insert == nwDocInsert.FOOTNOTE: + text = "" + self._insertCommentStructure(nwComment.FOOTNOTE) else: return False else: @@ -1850,6 +1853,33 @@ class GuiDocEditor(QPlainTextEdit): return + def _insertCommentStructure(self, style: nwComment) -> None: + """Insert a shortcut/comment combo.""" + if style == nwComment.FOOTNOTE: + key = SHARED.project.index.newCommentKey(style) + code = nwShortcode.COMMENT_STYLES[nwComment.FOOTNOTE] + + cursor = self.textCursor() + block = cursor.block() + text = block.text().rstrip() + if not text or text.startswith(("@", "#", "%")): + SHARED.error(self.tr("Footnotes can only be inserted in text.")) + return + + cursor.beginEditBlock() + cursor.insertText(code.format(key)) + cursor.setPosition(block.position() + block.length()) + cursor.insertBlock() + cursor.insertText(f"%Footnote.{key}: ") + cursor.insertBlock() + cursor.endEditBlock() + + cursor.setPosition(cursor.position() - 1) + + self.setTextCursor(cursor) + + return + ## # Internal Functions ## diff --git a/novelwriter/gui/dochighlight.py b/novelwriter/gui/dochighlight.py index 24bacd58..1debcd7e 100644 --- a/novelwriter/gui/dochighlight.py +++ b/novelwriter/gui/dochighlight.py @@ -44,6 +44,10 @@ logger = logging.getLogger(__name__) SPELLRX = QRegularExpression(r"\b[^\s\-\+\/–—\[\]:]+\b") SPELLRX.setPatternOptions(QRegularExpression.UseUnicodePropertiesOption) +SPELLSC = QRegularExpression(nwRegEx.FMT_SC) +SPELLSC.setPatternOptions(QRegularExpression.UseUnicodePropertiesOption) +SPELLSV = QRegularExpression(nwRegEx.FMT_SV) +SPELLSV.setPatternOptions(QRegularExpression.UseUnicodePropertiesOption) BLOCK_NONE = 0 BLOCK_TEXT = 1 @@ -53,7 +57,10 @@ BLOCK_TITLE = 4 class GuiDocHighlighter(QSyntaxHighlighter): - __slots__ = ("_tItem", "_tHandle", "_spellCheck", "_spellErr", "_hRules", "_hStyles") + __slots__ = ( + "_tHandle", "_isInactive", "_spellCheck", "_spellErr", "_hRules", + "_hStyles", "_rxRules" + ) def __init__(self, document: QTextDocument) -> None: super().__init__(document) @@ -67,6 +74,7 @@ class GuiDocHighlighter(QSyntaxHighlighter): self._hRules: list[tuple[str, dict]] = [] self._hStyles: dict[str, QTextCharFormat] = {} + self._rxRules: list[tuple[QRegularExpression, dict[int, QTextCharFormat]]] = [] self.initHighlighter() @@ -217,12 +225,12 @@ class GuiDocHighlighter(QSyntaxHighlighter): } )) - # Build a QRegExp for each highlight pattern - self.rxRules = [] + # Build a QRegularExpression for each highlight pattern + self._rxRules = [] for regEx, regRules in self._hRules: hReg = QRegularExpression(regEx) hReg.setPatternOptions(QRegularExpression.UseUnicodePropertiesOption) - self.rxRules.append((hReg, regRules)) + self._rxRules.append((hReg, regRules)) return @@ -367,7 +375,7 @@ class GuiDocHighlighter(QSyntaxHighlighter): # Regular Text self.setCurrentBlockState(BLOCK_TEXT) - for rX, xFmt in self.rxRules: + for rX, xFmt in self._rxRules: rxItt = rX.globalMatch(text, 0) while rxItt.hasNext(): rxMatch = rxItt.next() @@ -448,6 +456,17 @@ class TextBlockData(QTextBlockUserData): """Run the spell checker and cache the result, and return the list of spell check errors. """ + if "[" in text: + # Strip shortcodes + for rX in [SPELLSC, SPELLSV]: + rxItt = rX.globalMatch(text, 0) + while rxItt.hasNext(): + rxMatch = rxItt.next() + xPos = rxMatch.capturedStart(0) + xLen = rxMatch.capturedLength(0) + xEnd = rxMatch.capturedEnd(0) + text = text[:xPos] + " "*xLen + text[xEnd:] + self._spellErrors = [] rxSpell = SPELLRX.globalMatch(text[offset:].replace("_", " "), 0) while rxSpell.hasNext(): diff --git a/sample/content/636b6aa9b697b.nwd b/sample/content/636b6aa9b697b.nwd index ed5bacb6..7532c006 100644 --- a/sample/content/636b6aa9b697b.nwd +++ b/sample/content/636b6aa9b697b.nwd @@ -1,8 +1,8 @@ %%~name: Making a Scene %%~path: 6a2d6d5f4f401/636b6aa9b697b %%~kind: NOVEL/DOCUMENT -%%~hash: e1c58699b05b512306a534da70c2952aafc60e8b -%%~date: Unknown/2024-02-25 16:33:40 +%%~hash: 06b80d830f3f4d5c703eff82067d4335b8b98151 +%%~date: Unknown/2024-04-14 23:28:43 ### Making a Scene @pov: Jane @@ -21,7 +21,9 @@ If you have the need for it, you can also add text that can be automatically rep The editor also supports non breaking spaces, and the spell checker accepts long dashes—like this—as valid word separators. Regular dashes are also supported – and can be automatically inserted when typing two hyphens. -Thin spaces and thin non-breaking spaces are also supported from the Insert menu, and can be used to separate numbers from their units, like: 25 kg. +Thin spaces and thin non-breaking spaces are also supported from the Insert menu, and can be used to separate numbers from their units, like: 25 kg.[footnote:fq2ms] + +%Footnote.fq2ms: This is a footnote about non-breaking spaces. #### Some Section Here diff --git a/sample/nwProject.nwx b/sample/nwProject.nwx index 716b21e7..3e5cec69 100644 --- a/sample/nwProject.nwx +++ b/sample/nwProject.nwx @@ -1,6 +1,6 @@ - - + + Sample Project Jane Smith @@ -58,7 +58,7 @@ Chapter One - + Making a Scene