Restrict dialogue line symbols

This commit is contained in:
Veronica Berglyd Olsen
2025-05-04 22:17:13 +02:00
parent 6f38867972
commit 607001f2dd
5 changed files with 32 additions and 7 deletions
+10 -1
View File
@@ -40,7 +40,7 @@ from PyQt6.QtCore import QCoreApplication, QMimeData, QUrl
from PyQt6.QtGui import QAction, QDesktopServices, QFont, QFontDatabase, QFontInfo from PyQt6.QtGui import QAction, QDesktopServices, QFont, QFontDatabase, QFontInfo
from PyQt6.QtWidgets import QMenu, QMenuBar, QWidget from PyQt6.QtWidgets import QMenu, QMenuBar, QWidget
from novelwriter.constants import nwConst, nwLabels, nwUnicode, trConst from novelwriter.constants import nwConst, nwLabels, nwQuotes, nwUnicode, trConst
from novelwriter.enum import nwItemClass, nwItemLayout, nwItemType from novelwriter.enum import nwItemClass, nwItemLayout, nwItemType
from novelwriter.error import logException from novelwriter.error import logException
@@ -298,6 +298,15 @@ def uniqueCompact(text: str) -> str:
return "".join(sorted(set(compact(text)))) return "".join(sorted(set(compact(text))))
def processDialogSymbols(symbols: str) -> str:
"""Process dialogue line symbols."""
result = ""
for c in uniqueCompact(symbols):
if c in nwQuotes.ALLOWED:
result += c
return result
def elide(text: str, length: int) -> str: def elide(text: str, length: int) -> str:
"""Elide a piece of text to a maximum length.""" """Elide a piece of text to a maximum length."""
if len(text) > (cut := max(4, length)): if len(text) > (cut := max(4, length)):
+3 -2
View File
@@ -42,7 +42,7 @@ from PyQt6.QtWidgets import QApplication
from novelwriter.common import ( from novelwriter.common import (
NWConfigParser, checkInt, checkPath, describeFont, fontMatcher, NWConfigParser, checkInt, checkPath, describeFont, fontMatcher,
formatTimeStamp formatTimeStamp, processDialogSymbols
) )
from novelwriter.constants import nwFiles, nwHtmlUnicode, nwQuotes, nwUnicode from novelwriter.constants import nwFiles, nwHtmlUnicode, nwQuotes, nwUnicode
from novelwriter.error import formatException, logException from novelwriter.error import formatException, logException
@@ -680,7 +680,7 @@ class Config:
self.showFullPath = conf.rdBool(sec, "showfullpath", self.showFullPath) self.showFullPath = conf.rdBool(sec, "showfullpath", self.showFullPath)
self.dialogStyle = conf.rdInt(sec, "dialogstyle", self.dialogStyle) self.dialogStyle = conf.rdInt(sec, "dialogstyle", self.dialogStyle)
self.allowOpenDial = conf.rdBool(sec, "allowopendial", self.allowOpenDial) self.allowOpenDial = conf.rdBool(sec, "allowopendial", self.allowOpenDial)
self.dialogLine = conf.rdStr(sec, "dialogline", self.dialogLine) dialogLine = conf.rdStr(sec, "dialogline", self.dialogLine)
narratorBreak = conf.rdStr(sec, "narratorbreak", self.narratorBreak) narratorBreak = conf.rdStr(sec, "narratorbreak", self.narratorBreak)
narratorDialog = conf.rdStr(sec, "narratordialog", self.narratorDialog) narratorDialog = conf.rdStr(sec, "narratordialog", self.narratorDialog)
self.altDialogOpen = conf.rdStr(sec, "altdialogopen", self.altDialogOpen) self.altDialogOpen = conf.rdStr(sec, "altdialogopen", self.altDialogOpen)
@@ -721,6 +721,7 @@ class Config:
logger.info("Using straight double quotes, so disabling auto-replace") logger.info("Using straight double quotes, so disabling auto-replace")
self.doReplaceDQuote = False self.doReplaceDQuote = False
self.dialogLine = processDialogSymbols(dialogLine)
self.narratorBreak = narratorBreak if narratorBreak in nwQuotes.DASHES else "" self.narratorBreak = narratorBreak if narratorBreak in nwQuotes.DASHES else ""
self.narratorDialog = narratorDialog if narratorDialog in nwQuotes.DASHES else "" self.narratorDialog = narratorDialog if narratorDialog in nwQuotes.DASHES else ""
+6
View File
@@ -512,6 +512,12 @@ class nwQuotes:
"\u2015": QT_TRANSLATE_NOOP("Constant", "Horizontal bar"), "\u2015": QT_TRANSLATE_NOOP("Constant", "Horizontal bar"),
} }
ALLOWED: Final[list[str]] = [
"\u0027", "\u0022", "\u2018", "\u2019", "\u201a", "\u201b", "\u201c", "\u201d", "\u201e",
"\u201f", "\u2e42", "\u2039", "\u203a", "\u00ab", "\u00bb", "\u300c", "\u300d", "\u300e",
"\u300f", "\u2013", "\u2014", "\u2015",
]
class nwUnicode: class nwUnicode:
"""Supported unicode character constants and their HTML equivalents.""" """Supported unicode character constants and their HTML equivalents."""
+2 -2
View File
@@ -34,7 +34,7 @@ from PyQt6.QtWidgets import (
) )
from novelwriter import CONFIG, SHARED from novelwriter import CONFIG, SHARED
from novelwriter.common import compact, describeFont, uniqueCompact from novelwriter.common import compact, describeFont, processDialogSymbols, uniqueCompact
from novelwriter.config import DEF_GUI, DEF_ICONS, DEF_SYNTAX, DEF_TREECOL from novelwriter.config import DEF_GUI, DEF_ICONS, DEF_SYNTAX, DEF_TREECOL
from novelwriter.constants import nwLabels, nwQuotes, nwUnicode, trConst from novelwriter.constants import nwLabels, nwQuotes, nwUnicode, trConst
from novelwriter.dialogs.quotes import GuiQuoteSelect from novelwriter.dialogs.quotes import GuiQuoteSelect
@@ -1033,7 +1033,7 @@ class GuiPreferences(NDialog):
# Text Highlighting # Text Highlighting
dialogueStyle = self.dialogStyle.currentData() dialogueStyle = self.dialogStyle.currentData()
allowOpenDial = self.allowOpenDial.isChecked() allowOpenDial = self.allowOpenDial.isChecked()
dialogueLine = uniqueCompact(self.dialogLine.text()) dialogueLine = processDialogSymbols(self.dialogLine.text())
narratorBreak = self.narratorBreak.currentData() narratorBreak = self.narratorBreak.currentData()
narratorDialog = self.narratorDialog.currentData() narratorDialog = self.narratorDialog.currentData()
altDialogOpen = compact(self.altDialogOpen.text()) altDialogOpen = compact(self.altDialogOpen.text())
+11 -2
View File
@@ -37,8 +37,9 @@ from novelwriter.common import (
formatFileFilter, formatInt, formatTime, formatTimeStamp, formatVersion, formatFileFilter, formatInt, formatTime, formatTimeStamp, formatVersion,
fuzzyTime, getFileSize, hexToInt, isHandle, isItemClass, isItemLayout, fuzzyTime, getFileSize, hexToInt, isHandle, isItemClass, isItemLayout,
isItemType, isListInstance, isTitleTag, jsonEncode, makeFileNameSafe, isItemType, isListInstance, isTitleTag, jsonEncode, makeFileNameSafe,
minmax, numberToRoman, openExternalPath, readTextFile, simplified, minmax, numberToRoman, openExternalPath, processDialogSymbols,
transferCase, uniqueCompact, xmlElement, xmlIndent, xmlSubElem, yesNo readTextFile, simplified, transferCase, uniqueCompact, xmlElement,
xmlIndent, xmlSubElem, yesNo
) )
from tests.mocked import causeOSError from tests.mocked import causeOSError
@@ -377,6 +378,14 @@ def testBaseCommon_uniqueCompact():
assert uniqueCompact("3 2 1") == "123" assert uniqueCompact("3 2 1") == "123"
@pytest.mark.base
def testBaseCommon_processDialogSymbols():
"""Test the processDialogSymbols function."""
assert processDialogSymbols("abc") == ""
assert processDialogSymbols("\u00ab\u00ab\u00bb\u00bb") == "\u00ab\u00bb"
assert processDialogSymbols("-\u2013\u2014\u2015") == "\u2013\u2014\u2015"
@pytest.mark.base @pytest.mark.base
def testBaseCommon_elide(): def testBaseCommon_elide():
"""Test the elide function.""" """Test the elide function."""