diff --git a/novelwriter/constants.py b/novelwriter/constants.py index d4a944cc..9d874110 100644 --- a/novelwriter/constants.py +++ b/novelwriter/constants.py @@ -300,16 +300,21 @@ class nwLabels: class nwHeadFmt: - BR = "{BR}" - TITLE = "{Title}" - CH_NUM = "{Chapter}" - CH_WORD = "{Chapter:Word}" - CH_ROMU = "{Chapter:URoman}" - CH_ROML = "{Chapter:LRoman}" - SC_NUM = "{Scene}" - SC_ABS = "{Scene:Abs}" + BR = "{BR}" + TITLE = "{Title}" + CH_NUM = "{Chapter}" + CH_WORD = "{Chapter:Word}" + CH_ROMU = "{Chapter:URoman}" + CH_ROML = "{Chapter:LRoman}" + SC_NUM = "{Scene}" + SC_ABS = "{Scene:Abs}" + CHAR_POV = "{Char:POV}" + CHAR_FOCUS = "{Char:Focus}" - PAGE_HEADERS = [TITLE, CH_NUM, CH_WORD, CH_ROMU, CH_ROML, SC_NUM, SC_ABS] + PAGE_HEADERS = [ + TITLE, CH_NUM, CH_WORD, CH_ROMU, CH_ROML, SC_NUM, SC_ABS, + CHAR_POV, CHAR_FOCUS + ] # ODT Document Page Header ODT_PROJECT = "{Project}" diff --git a/novelwriter/core/tokenizer.py b/novelwriter/core/tokenizer.py index ca21250d..267fb0e3 100644 --- a/novelwriter/core/tokenizer.py +++ b/novelwriter/core/tokenizer.py @@ -37,7 +37,9 @@ from PyQt5.QtCore import QCoreApplication, QRegularExpression from novelwriter.enum import nwComment, nwItemLayout from novelwriter.common import formatTimeStamp, numberToRoman, checkInt -from novelwriter.constants import nwHeadFmt, nwRegEx, nwShortcode, nwUnicode +from novelwriter.constants import ( + nwHeadFmt, nwKeyWords, nwLabels, nwRegEx, nwShortcode, nwUnicode, trConst +) from novelwriter.core.index import processComment from novelwriter.core.project import NWProject @@ -112,13 +114,15 @@ class Tokenizer(ABC): # Data Variables self._text = "" # The raw text to be tokenized - self._nwItem = None # The NWItem associated with the handle - self._tokens = [] # The list of the processed tokens + self._nwItem = None # The NWItem currently being processed self._result = "" # The result of the last document self._keepMarkdown = False # Whether to keep the markdown text self._allMarkdown = [] # The result novelWriter markdown of all documents + # Processed Tokens + self._tokens: list[tuple[int, int, str, list[tuple[int, int]] | None, int]] = [] + # User Settings self._textFont = "Serif" # Output text font self._textSize = 11 # Output text size @@ -655,6 +659,8 @@ class Tokenizer(ABC): if not self._isNovel: return False + self._hFormatter.setHandle(self._nwItem.itemHandle if self._nwItem else None) + for n, tToken in enumerate(self._tokens): # In case we see text before a scene, we reset the flag @@ -664,7 +670,7 @@ class Tokenizer(ABC): elif tToken[0] == self.T_HEAD1: # Partition - tTemp = self._hFormatter.apply(self._fmtTitle, tToken[2]) + tTemp = self._hFormatter.apply(self._fmtTitle, tToken[2], tToken[1]) self._tokens[n] = ( tToken[0], tToken[1], tTemp, None, tToken[4] ) @@ -674,10 +680,10 @@ class Tokenizer(ABC): # Numbered or Unnumbered if tToken[0] == self.T_UNNUM: - tTemp = self._hFormatter.apply(self._fmtUnNum, tToken[2]) + tTemp = self._hFormatter.apply(self._fmtUnNum, tToken[2], tToken[1]) else: self._hFormatter.incChapter() - tTemp = self._hFormatter.apply(self._fmtChapter, tToken[2]) + tTemp = self._hFormatter.apply(self._fmtChapter, tToken[2], tToken[1]) # Format the chapter header self._tokens[n] = ( @@ -693,7 +699,7 @@ class Tokenizer(ABC): self._hFormatter.incScene() - tTemp = self._hFormatter.apply(self._fmtScene, tToken[2]) + tTemp = self._hFormatter.apply(self._fmtScene, tToken[2], tToken[1]) if tTemp == "" and self._hideScene: self._tokens[n] = ( self.T_EMPTY, tToken[1], "", None, self.A_NONE @@ -726,7 +732,7 @@ class Tokenizer(ABC): elif tToken[0] == self.T_HEAD4: # Section - tTemp = self._hFormatter.apply(self._fmtSection, tToken[2]) + tTemp = self._hFormatter.apply(self._fmtSection, tToken[2], tToken[1]) if tTemp == "" and self._hideSection: self._tokens[n] = ( self.T_EMPTY, tToken[1], "", None, self.A_NONE @@ -817,11 +823,17 @@ class HeadingFormatter: def __init__(self, project: NWProject) -> None: self._project = project + self._handle = None self._chCount = 0 self._scChCount = 0 self._scAbsCount = 0 return + def setHandle(self, tHandle: str | None) -> None: + """Set the handle currently being processed.""" + self._handle = tHandle + return + def incChapter(self) -> None: """Increment the chapter counter.""" self._chCount += 1 @@ -838,7 +850,7 @@ class HeadingFormatter: self._scChCount = 0 return - def apply(self, hFormat: str, text: str) -> str: + def apply(self, hFormat: str, text: str, nHead: int) -> str: """Apply formatting to a specific heading.""" hFormat = hFormat.replace(nwHeadFmt.TITLE, text) hFormat = hFormat.replace(nwHeadFmt.CH_NUM, str(self._chCount)) @@ -854,6 +866,19 @@ class HeadingFormatter: chRom = numberToRoman(self._chCount, toLower=False) hFormat = hFormat.replace(nwHeadFmt.CH_ROMU, chRom) + if nwHeadFmt.CHAR_POV in hFormat or nwHeadFmt.CHAR_FOCUS in hFormat: + if self._handle and nHead > 0: + refs = self._project.index.getReferences(self._handle, f"T{nHead:04d}") + povData = refs[nwKeyWords.POV_KEY] + focData = refs[nwKeyWords.FOCUS_KEY] + povText = povData[0] if povData else nwUnicode.U_ENDASH + focText = focData[0] if focData else nwUnicode.U_ENDASH + else: + povText = trConst(nwLabels.KEY_NAME[nwKeyWords.POV_KEY]) + focText = trConst(nwLabels.KEY_NAME[nwKeyWords.FOCUS_KEY]) + hFormat = hFormat.replace(nwHeadFmt.CHAR_POV, povText) + hFormat = hFormat.replace(nwHeadFmt.CHAR_FOCUS, focText) + return hFormat # END Class HeadingFormatter diff --git a/novelwriter/tools/manuscript.py b/novelwriter/tools/manuscript.py index e295b8d4..aaf09141 100644 --- a/novelwriter/tools/manuscript.py +++ b/novelwriter/tools/manuscript.py @@ -604,7 +604,7 @@ class _DetailsWidget(QWidget): for key in entries: sub = QTreeWidgetItem() sub.setText(0, build.getLabel(key)) - sub.setText(1, hFmt.apply(build.getStr(key), title)) + sub.setText(1, hFmt.apply(build.getStr(key), title, 0)) item.addChild(sub) for key in ["headings.hideScene", "headings.hideSection"]: sub = QTreeWidgetItem() diff --git a/novelwriter/tools/manussettings.py b/novelwriter/tools/manussettings.py index b62d0491..76709dbf 100644 --- a/novelwriter/tools/manussettings.py +++ b/novelwriter/tools/manussettings.py @@ -722,6 +722,8 @@ class _HeadingsTab(NScrollablePage): self.aInsChRomL = self.menuInsert.addAction(self.tr("Chapter Number (Lower Case Roman)")) self.aInsScNum = self.menuInsert.addAction(self.tr("Scene Number (In Chapter)")) self.aInsScAbs = self.menuInsert.addAction(self.tr("Scene Number (Absolute)")) + self.aInsCharPOV = self.menuInsert.addAction(self.tr("Point of View Character")) + self.aInsCharFocus = self.menuInsert.addAction(self.tr("Focus Character")) self.aInsTitle.triggered.connect(lambda: self._insertIntoForm(nwHeadFmt.TITLE)) self.aInsChNum.triggered.connect(lambda: self._insertIntoForm(nwHeadFmt.CH_NUM)) @@ -730,6 +732,8 @@ class _HeadingsTab(NScrollablePage): self.aInsChRomL.triggered.connect(lambda: self._insertIntoForm(nwHeadFmt.CH_ROML)) self.aInsScNum.triggered.connect(lambda: self._insertIntoForm(nwHeadFmt.SC_NUM)) self.aInsScAbs.triggered.connect(lambda: self._insertIntoForm(nwHeadFmt.SC_ABS)) + self.aInsCharPOV.triggered.connect(lambda: self._insertIntoForm(nwHeadFmt.CHAR_POV)) + self.aInsCharFocus.triggered.connect(lambda: self._insertIntoForm(nwHeadFmt.CHAR_FOCUS)) self.btnInsert = QPushButton(self.tr("Insert")) self.btnInsert.setMenu(self.menuInsert)