Add insert footnote feature in editor
This commit is contained in:
@@ -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
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
##
|
##
|
||||||
|
|||||||
@@ -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():
|
||||||
|
|||||||
@@ -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: 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
|
#### Some Section Here
|
||||||
|
|
||||||
|
|||||||
@@ -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">
|
||||||
|
|||||||
Reference in New Issue
Block a user