Add insert footnote feature in editor

This commit is contained in:
Veronica Berglyd Olsen
2024-04-14 23:33:35 +02:00
parent da102d3d5a
commit 70033cb0f3
6 changed files with 82 additions and 16 deletions
+9 -2
View File
@@ -25,7 +25,9 @@ from __future__ import annotations
from PyQt5.QtCore import QCoreApplication, QT_TRANSLATE_NOOP 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: def trConst(text: str) -> str:
@@ -67,7 +69,7 @@ class nwRegEx:
FMT_EB = r"(?<![\w\\])([\*]{2})(?![\s\*])(.+?)(?<![\s\\])(\1)(?!\w)" FMT_EB = r"(?<![\w\\])([\*]{2})(?![\s\*])(.+?)(?<![\s\\])(\1)(?!\w)"
FMT_ST = r"(?<![\w\\])([~]{2})(?![\s~])(.+?)(?<![\s\\])(\1)(?!\w)" FMT_ST = r"(?<![\w\\])([~]{2})(?![\s~])(.+?)(?<![\s\\])(\1)(?!\w)"
FMT_SC = r"(?i)(?<!\\)(\[[\/\!]?(?:i|b|s|u|m|sup|sub)\])" FMT_SC = r"(?i)(?<!\\)(\[[\/\!]?(?:i|b|s|u|m|sup|sub)\])"
FMT_SV = r"(?<!\\)(\[(?i)(?:fn|footnote):)(.+?)(?<!\\)(\])" FMT_SV = r"(?<!\\)(\[(?i)(?:footnote):)(.+?)(?<!\\)(\])"
# END Class nwRegEx # END Class nwRegEx
@@ -89,6 +91,11 @@ class nwShortcode:
SUB_O = "[sub]" SUB_O = "[sub]"
SUB_C = "[/sub]" SUB_C = "[/sub]"
COMMENT_STYLES = {
nwComment.FOOTNOTE: "[footnote:{0}]",
nwComment.COMMENT: "[comment:{0}]",
}
# END Class nwShortcode # END Class nwShortcode
+10 -2
View File
@@ -514,6 +514,14 @@ class NWIndex:
name, _, display = text.partition("|") name, _, display = text.partition("|")
return name.rstrip(), display.lstrip() return name.rstrip(), display.lstrip()
def newCommentKey(self, style: nwComment) -> 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 # Extract Data
## ##
@@ -1322,8 +1330,8 @@ class TextIndex:
__slots__ = ("_comments", "_footnotes") __slots__ = ("_comments", "_footnotes")
def __init__(self) -> None: def __init__(self) -> None:
self._comments = TextRegistry("c_") self._comments = TextRegistry("c")
self._footnotes = TextRegistry("f_") self._footnotes = TextRegistry("f")
return return
@property @property
+31 -1
View File
@@ -55,7 +55,7 @@ from novelwriter import CONFIG, SHARED
from novelwriter.common import minmax, transferCase from novelwriter.common import minmax, transferCase
from novelwriter.constants import nwConst, nwKeyWords, nwShortcode, nwUnicode from novelwriter.constants import nwConst, nwKeyWords, nwShortcode, nwUnicode
from novelwriter.core.document import NWDocument 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.eventfilters import WheelEventFilter
from novelwriter.extensions.modified import NIconToggleButton, NIconToolButton from novelwriter.extensions.modified import NIconToggleButton, NIconToolButton
from novelwriter.gui.dochighlight import BLOCK_META, BLOCK_TITLE from novelwriter.gui.dochighlight import BLOCK_META, BLOCK_TITLE
@@ -884,6 +884,9 @@ class GuiDocEditor(QPlainTextEdit):
text = GuiLipsum.getLipsum(self) text = GuiLipsum.getLipsum(self)
newBlock = True newBlock = True
goAfter = False goAfter = False
elif insert == nwDocInsert.FOOTNOTE:
text = ""
self._insertCommentStructure(nwComment.FOOTNOTE)
else: else:
return False return False
else: else:
@@ -1850,6 +1853,33 @@ class GuiDocEditor(QPlainTextEdit):
return 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 # Internal Functions
## ##
+24 -5
View File
@@ -44,6 +44,10 @@ logger = logging.getLogger(__name__)
SPELLRX = QRegularExpression(r"\b[^\s\-\+\/–—\[\]:]+\b") SPELLRX = QRegularExpression(r"\b[^\s\-\+\/–—\[\]:]+\b")
SPELLRX.setPatternOptions(QRegularExpression.UseUnicodePropertiesOption) 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_NONE = 0
BLOCK_TEXT = 1 BLOCK_TEXT = 1
@@ -53,7 +57,10 @@ BLOCK_TITLE = 4
class GuiDocHighlighter(QSyntaxHighlighter): class GuiDocHighlighter(QSyntaxHighlighter):
__slots__ = ("_tItem", "_tHandle", "_spellCheck", "_spellErr", "_hRules", "_hStyles") __slots__ = (
"_tHandle", "_isInactive", "_spellCheck", "_spellErr", "_hRules",
"_hStyles", "_rxRules"
)
def __init__(self, document: QTextDocument) -> None: def __init__(self, document: QTextDocument) -> None:
super().__init__(document) super().__init__(document)
@@ -67,6 +74,7 @@ class GuiDocHighlighter(QSyntaxHighlighter):
self._hRules: list[tuple[str, dict]] = [] self._hRules: list[tuple[str, dict]] = []
self._hStyles: dict[str, QTextCharFormat] = {} self._hStyles: dict[str, QTextCharFormat] = {}
self._rxRules: list[tuple[QRegularExpression, dict[int, QTextCharFormat]]] = []
self.initHighlighter() self.initHighlighter()
@@ -217,12 +225,12 @@ class GuiDocHighlighter(QSyntaxHighlighter):
} }
)) ))
# Build a QRegExp for each highlight pattern # Build a QRegularExpression for each highlight pattern
self.rxRules = [] self._rxRules = []
for regEx, regRules in self._hRules: for regEx, regRules in self._hRules:
hReg = QRegularExpression(regEx) hReg = QRegularExpression(regEx)
hReg.setPatternOptions(QRegularExpression.UseUnicodePropertiesOption) hReg.setPatternOptions(QRegularExpression.UseUnicodePropertiesOption)
self.rxRules.append((hReg, regRules)) self._rxRules.append((hReg, regRules))
return return
@@ -367,7 +375,7 @@ class GuiDocHighlighter(QSyntaxHighlighter):
# Regular Text # Regular Text
self.setCurrentBlockState(BLOCK_TEXT) self.setCurrentBlockState(BLOCK_TEXT)
for rX, xFmt in self.rxRules: for rX, xFmt in self._rxRules:
rxItt = rX.globalMatch(text, 0) rxItt = rX.globalMatch(text, 0)
while rxItt.hasNext(): while rxItt.hasNext():
rxMatch = rxItt.next() rxMatch = rxItt.next()
@@ -448,6 +456,17 @@ class TextBlockData(QTextBlockUserData):
"""Run the spell checker and cache the result, and return the """Run the spell checker and cache the result, and return the
list of spell check errors. 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 = [] self._spellErrors = []
rxSpell = SPELLRX.globalMatch(text[offset:].replace("_", " "), 0) rxSpell = SPELLRX.globalMatch(text[offset:].replace("_", " "), 0)
while rxSpell.hasNext(): while rxSpell.hasNext():
+5 -3
View File
@@ -1,8 +1,8 @@
%%~name: Making a Scene %%~name: Making a Scene
%%~path: 6a2d6d5f4f401/636b6aa9b697b %%~path: 6a2d6d5f4f401/636b6aa9b697b
%%~kind: NOVEL/DOCUMENT %%~kind: NOVEL/DOCUMENT
%%~hash: e1c58699b05b512306a534da70c2952aafc60e8b %%~hash: 06b80d830f3f4d5c703eff82067d4335b8b98151
%%~date: Unknown/2024-02-25 16:33:40 %%~date: Unknown/2024-04-14 23:28:43
### Making a Scene ### Making a Scene
@pov: Jane @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. 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: 25kg. 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: 25kg.[footnote:fq2ms]
%Footnote.fq2ms: This is a footnote about non-breaking spaces.
#### Some Section Here #### Some Section Here
+3 -3
View File
@@ -1,6 +1,6 @@
<?xml version='1.0' encoding='utf-8'?> <?xml version='1.0' encoding='utf-8'?>
<novelWriterXML appVersion="2.4rc1" hexVersion="0x020400c1" fileVersion="1.5" fileRevision="4" timeStamp="2024-04-11 14:19:47"> <novelWriterXML appVersion="2.5a1" hexVersion="0x020500a1" fileVersion="1.5" fileRevision="4" timeStamp="2024-04-14 23:31:44">
<project id="e2be99af-f9bf-4403-857a-c3d1ac25abea" saveCount="1850" autoCount="272" editTime="86438"> <project id="e2be99af-f9bf-4403-857a-c3d1ac25abea" saveCount="1879" autoCount="272" editTime="86979">
<name>Sample Project</name> <name>Sample Project</name>
<author>Jane Smith</author> <author>Jane Smith</author>
</project> </project>
@@ -58,7 +58,7 @@
<name status="sf24ce6" import="ia857f0" active="yes">Chapter One</name> <name status="sf24ce6" import="ia857f0" active="yes">Chapter One</name>
</item> </item>
<item handle="636b6aa9b697b" parent="6a2d6d5f4f401" root="7031beac91f75" order="0" type="FILE" class="NOVEL" layout="DOCUMENT"> <item handle="636b6aa9b697b" parent="6a2d6d5f4f401" root="7031beac91f75" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="no" heading="H3" charCount="2937" wordCount="520" paraCount="15" cursorPos="0" /> <meta expanded="no" heading="H3" charCount="2953" wordCount="520" paraCount="15" cursorPos="2049" />
<name status="s90e6c9" import="ia857f0" active="yes">Making a Scene</name> <name status="s90e6c9" import="ia857f0" active="yes">Making a Scene</name>
</item> </item>
<item handle="bc0cbd2a407f3" parent="6a2d6d5f4f401" root="7031beac91f75" order="1" type="FILE" class="NOVEL" layout="DOCUMENT"> <item handle="bc0cbd2a407f3" parent="6a2d6d5f4f401" root="7031beac91f75" order="1" type="FILE" class="NOVEL" layout="DOCUMENT">