diff --git a/novelwriter/constants.py b/novelwriter/constants.py index 130378bd..96e3db99 100644 --- a/novelwriter/constants.py +++ b/novelwriter/constants.py @@ -405,6 +405,7 @@ class nwUnicode: U_THNBSP = "\u202f" # Thin non-breaking space U_ENSP = "\u2002" # Short (en) space U_EMSP = "\u2003" # Long (em) space + U_MMSP = "\u205f" # Medium mathematical space U_LSEP = "\u2028" # Line separator U_PSEP = "\u2029" # Paragraph separator diff --git a/novelwriter/core/buildsettings.py b/novelwriter/core/buildsettings.py index d38f3347..589050c1 100644 --- a/novelwriter/core/buildsettings.py +++ b/novelwriter/core/buildsettings.py @@ -49,50 +49,53 @@ logger = logging.getLogger(__name__) # (type, default, [min value, max value]) SETTINGS_TEMPLATE = { - "filter.includeNovel": (bool, True), - "filter.includeNotes": (bool, False), - "filter.includeInactive": (bool, False), - "headings.fmtTitle": (str, nwHeadFmt.TITLE), - "headings.fmtChapter": (str, nwHeadFmt.TITLE), - "headings.fmtUnnumbered": (str, nwHeadFmt.TITLE), - "headings.fmtScene": (str, "* * *"), - "headings.fmtHardScene": (str, ""), - "headings.fmtSection": (str, ""), - "headings.hideScene": (bool, False), - "headings.hideHardScene": (bool, False), - "headings.hideSection": (bool, True), - "headings.centerTitle": (bool, True), - "headings.centerChapter": (bool, False), - "headings.centerScene": (bool, False), - "headings.breakTitle": (bool, True), - "headings.breakChapter": (bool, True), - "headings.breakScene": (bool, False), - "text.includeSynopsis": (bool, False), - "text.includeComments": (bool, False), - "text.includeKeywords": (bool, False), - "text.includeBodyText": (bool, True), - "text.ignoredKeywords": (str, ""), - "text.addNoteHeadings": (bool, True), - "format.textFont": (str, CONFIG.textFont), - "format.textSize": (int, 12), - "format.lineHeight": (float, 1.15, 0.75, 3.0), - "format.justifyText": (bool, False), - "format.stripUnicode": (bool, False), - "format.replaceTabs": (bool, False), - "format.pageUnit": (str, "cm"), - "format.pageSize": (str, "A4"), - "format.pageWidth": (float, 21.0), - "format.pageHeight": (float, 29.7), - "format.topMargin": (float, 2.0), - "format.bottomMargin": (float, 2.0), - "format.leftMargin": (float, 2.0), - "format.rightMargin": (float, 2.0), - "odt.addColours": (bool, True), - "odt.pageHeader": (str, nwHeadFmt.ODT_AUTO), - "odt.pageCountOffset": (int, 0), - "md.preserveBreaks": (bool, True), - "html.addStyles": (bool, True), - "html.preserveTabs": (bool, False), + "filter.includeNovel": (bool, True), + "filter.includeNotes": (bool, False), + "filter.includeInactive": (bool, False), + "headings.fmtTitle": (str, nwHeadFmt.TITLE), + "headings.fmtChapter": (str, nwHeadFmt.TITLE), + "headings.fmtUnnumbered": (str, nwHeadFmt.TITLE), + "headings.fmtScene": (str, "* * *"), + "headings.fmtHardScene": (str, ""), + "headings.fmtSection": (str, ""), + "headings.hideTitle": (bool, False), + "headings.hideChapter": (bool, False), + "headings.hideUnnumbered": (bool, False), + "headings.hideScene": (bool, False), + "headings.hideHardScene": (bool, False), + "headings.hideSection": (bool, True), + "headings.centerTitle": (bool, True), + "headings.centerChapter": (bool, False), + "headings.centerScene": (bool, False), + "headings.breakTitle": (bool, True), + "headings.breakChapter": (bool, True), + "headings.breakScene": (bool, False), + "text.includeSynopsis": (bool, False), + "text.includeComments": (bool, False), + "text.includeKeywords": (bool, False), + "text.includeBodyText": (bool, True), + "text.ignoredKeywords": (str, ""), + "text.addNoteHeadings": (bool, True), + "format.textFont": (str, CONFIG.textFont), + "format.textSize": (int, 12), + "format.lineHeight": (float, 1.15, 0.75, 3.0), + "format.justifyText": (bool, False), + "format.stripUnicode": (bool, False), + "format.replaceTabs": (bool, False), + "format.pageUnit": (str, "cm"), + "format.pageSize": (str, "A4"), + "format.pageWidth": (float, 21.0), + "format.pageHeight": (float, 29.7), + "format.topMargin": (float, 2.0), + "format.bottomMargin": (float, 2.0), + "format.leftMargin": (float, 2.0), + "format.rightMargin": (float, 2.0), + "odt.addColours": (bool, True), + "odt.pageHeader": (str, nwHeadFmt.ODT_AUTO), + "odt.pageCountOffset": (int, 0), + "md.preserveBreaks": (bool, True), + "html.addStyles": (bool, True), + "html.preserveTabs": (bool, False), } SETTINGS_LABELS = { @@ -108,9 +111,6 @@ SETTINGS_LABELS = { "headings.fmtScene": QT_TRANSLATE_NOOP("Builds", "Scene Format"), "headings.fmtHardScene": QT_TRANSLATE_NOOP("Builds", "Hard Scene Format"), "headings.fmtSection": QT_TRANSLATE_NOOP("Builds", "Section Format"), - "headings.hideScene": QT_TRANSLATE_NOOP("Builds", "Hide Scene Breaks"), - "headings.hideHardScene": QT_TRANSLATE_NOOP("Builds", "Hide Hard Scene Breaks"), - "headings.hideSection": QT_TRANSLATE_NOOP("Builds", "Hide Section Breaks"), "text.grpContent": QT_TRANSLATE_NOOP("Builds", "Text Content"), "text.includeSynopsis": QT_TRANSLATE_NOOP("Builds", "Include Synopsis"), diff --git a/novelwriter/core/docbuild.py b/novelwriter/core/docbuild.py index 23b10791..19457b81 100644 --- a/novelwriter/core/docbuild.py +++ b/novelwriter/core/docbuild.py @@ -275,9 +275,18 @@ class NWBuildDocument: fontInfo = QFontInfo(bldFont) textFixed = fontInfo.fixedPitch() - bldObj.setTitleFormat(self._build.getStr("headings.fmtTitle")) - bldObj.setChapterFormat(self._build.getStr("headings.fmtChapter")) - bldObj.setUnNumberedFormat(self._build.getStr("headings.fmtUnnumbered")) + bldObj.setTitleFormat( + self._build.getStr("headings.fmtTitle"), + self._build.getBool("headings.hideTitle") + ) + bldObj.setChapterFormat( + self._build.getStr("headings.fmtChapter"), + self._build.getBool("headings.hideChapter") + ) + bldObj.setUnNumberedFormat( + self._build.getStr("headings.fmtUnnumbered"), + self._build.getBool("headings.hideUnnumbered") + ) bldObj.setSceneFormat( self._build.getStr("headings.fmtScene"), self._build.getBool("headings.hideScene") diff --git a/novelwriter/core/tohtml.py b/novelwriter/core/tohtml.py index 8e1a67ca..4922e43b 100644 --- a/novelwriter/core/tohtml.py +++ b/novelwriter/core/tohtml.py @@ -256,10 +256,6 @@ class ToHtml(Tokenizer): tHead = tText.replace(nwHeadFmt.BR, "
") lines.append(f"

{aNm}{tHead}

\n") - elif tType == self.T_UNNUM: - tHead = tText.replace(nwHeadFmt.BR, "
") - lines.append(f"<{h2}{hStyle}>{aNm}{tHead}\n") - elif tType == self.T_HEAD1: tHead = tText.replace(nwHeadFmt.BR, "
") lines.append(f"<{h1}{h1Cl}{hStyle}>{aNm}{tHead}\n") diff --git a/novelwriter/core/tokenizer.py b/novelwriter/core/tokenizer.py index 3d52e19a..ac44471c 100644 --- a/novelwriter/core/tokenizer.py +++ b/novelwriter/core/tokenizer.py @@ -88,14 +88,13 @@ class Tokenizer(ABC): T_COMMENT = 4 # Comment line T_KEYWORD = 5 # Command line T_TITLE = 6 # Title - T_UNNUM = 7 # Unnumbered - T_HEAD1 = 8 # Heading 1 - T_HEAD2 = 9 # Heading 2 - T_HEAD3 = 10 # Heading 3 - T_HEAD4 = 11 # Heading 4 - T_TEXT = 12 # Text line - T_SEP = 13 # Scene separator - T_SKIP = 14 # Paragraph break + T_HEAD1 = 7 # Heading 1 + T_HEAD2 = 8 # Heading 2 + T_HEAD3 = 9 # Heading 3 + T_HEAD4 = 10 # Heading 4 + T_TEXT = 11 # Text line + T_SEP = 12 # Scene separator + T_SKIP = 13 # Paragraph break # Block Style A_NONE = 0x0000 # No special style @@ -111,7 +110,7 @@ class Tokenizer(ABC): A_IND_R = 0x0200 # Right indentation # Lookups - L_HEADINGS = [T_TITLE, T_UNNUM, T_HEAD1, T_HEAD2, T_HEAD3, T_HEAD4] + L_HEADINGS = [T_TITLE, T_HEAD1, T_HEAD2, T_HEAD3, T_HEAD4] def __init__(self, project: NWProject) -> None: @@ -159,6 +158,9 @@ class Tokenizer(ABC): self._fmtHScene = nwHeadFmt.TITLE # Formatting for hard scenes self._fmtSection = nwHeadFmt.TITLE # Formatting for sections + self._hideTitle = False # Do not include title headings + self._hideChapter = False # Do not include chapter headings + self._hideUnNum = False # Do not include unnumbered headings self._hideScene = False # Do not include scene headings self._hideHScene = False # Do not include hard scene headings self._hideSection = False # Do not include section headings @@ -171,7 +173,7 @@ class Tokenizer(ABC): # Instance Variables self._hFormatter = HeadingFormatter(self._project) - self._noSep = False # Flag to indicate that we don't want a scene separator + self._noSep = True # Flag to indicate that we don't want a scene separator # This File self._isNone = False # Document has unknown layout @@ -217,7 +219,7 @@ class Tokenizer(ABC): return self._result @property - def allMarkdown(self) -> list: + def allMarkdown(self) -> list[str]: """The combined novelWriter Markdown text.""" return self._allMarkdown @@ -227,7 +229,7 @@ class Tokenizer(ABC): return self._counts @property - def errData(self) -> list: + def errData(self) -> list[str]: """The error data.""" return self._errData @@ -235,34 +237,37 @@ class Tokenizer(ABC): # Setters ## - def setTitleFormat(self, hFormat: str) -> None: + def setTitleFormat(self, hFormat: str, hide: bool = False) -> None: """Set the title format pattern.""" self._fmtTitle = hFormat.strip() + self._hideTitle = hide return - def setChapterFormat(self, hFormat: str) -> None: + def setChapterFormat(self, hFormat: str, hide: bool = False) -> None: """Set the chapter format pattern.""" self._fmtChapter = hFormat.strip() + self._hideChapter = hide return - def setUnNumberedFormat(self, hFormat: str) -> None: + def setUnNumberedFormat(self, hFormat: str, hide: bool = False) -> None: """Set the unnumbered format pattern.""" self._fmtUnNum = hFormat.strip() + self._hideUnNum = hide return - def setSceneFormat(self, hFormat: str, hide: bool) -> None: + def setSceneFormat(self, hFormat: str, hide: bool = False) -> None: """Set the scene format pattern and hidden status.""" self._fmtScene = hFormat.strip() self._hideScene = hide return - def setHardSceneFormat(self, hFormat: str, hide: bool) -> None: + def setHardSceneFormat(self, hFormat: str, hide: bool = False) -> None: """Set the hard scene format pattern and hidden status.""" self._fmtHScene = hFormat.strip() self._hideHScene = hide return - def setSectionFormat(self, hFormat: str, hide: bool) -> None: + def setSectionFormat(self, hFormat: str, hide: bool = False) -> None: """Set the section format pattern and hidden status.""" self._fmtSection = hFormat.strip() self._hideSection = hide @@ -579,10 +584,16 @@ class Tokenizer(ABC): tText = aLine[2:].strip() tType = self.T_HEAD1 if isPlain else self.T_TITLE tStyle = self.A_NONE if isPlain else (self.A_PBB | self.A_CENTRE) + sHide = self._hideTitle if isPlain else False if self._isNovel: - if isPlain: + if sHide: + tText = "" + tType = self.T_EMPTY + tStyle = self.A_NONE + elif isPlain: tText = self._hFormatter.apply(self._fmtTitle, tText, nHead) tStyle = self._titleStyle + if isPlain: self._hFormatter.resetScene() else: self._hFormatter.resetAll() @@ -609,15 +620,19 @@ class Tokenizer(ABC): tText = aLine[3:].strip() tType = self.T_HEAD2 tStyle = self.A_NONE + sHide = self._hideChapter if isPlain else self._hideUnNum tFormat = self._fmtChapter if isPlain else self._fmtUnNum if self._isNovel: if isPlain: self._hFormatter.incChapter() - tText = self._hFormatter.apply(tFormat, tText, nHead) - tType = self.T_HEAD2 if isPlain else self.T_UNNUM - tStyle = self._chapterStyle - self._noSep = True + if sHide: + tText = "" + tType = self.T_EMPTY + else: + tText = self._hFormatter.apply(tFormat, tText, nHead) + tStyle = self._chapterStyle self._hFormatter.resetScene() + self._noSep = True self._tokens.append(( tType, nHead, tText, [], tStyle @@ -646,15 +661,18 @@ class Tokenizer(ABC): tFormat = self._fmtScene if isPlain else self._fmtHScene if self._isNovel: self._hFormatter.incScene() - tText = self._hFormatter.apply(tFormat, tText, nHead) - tStyle = self._sceneStyle - if tText == "": - tType = self.T_EMPTY if self._noSep or sHide else self.T_SKIP - tStyle = self.A_NONE - elif tText == tFormat: - tText = "" if self._noSep else tText - tType = self.T_EMPTY if self._noSep else self.T_SEP - tStyle = self.A_NONE if self._noSep else self.A_CENTRE + if sHide: + tText = "" + tType = self.T_EMPTY + else: + tText = self._hFormatter.apply(tFormat, tText, nHead) + tStyle = self._sceneStyle + if tText == "": # Empty Format + tType = self.T_EMPTY if self._noSep else self.T_SKIP + elif tText == tFormat: # Static Format + tText = "" if self._noSep else tText + tType = self.T_EMPTY if self._noSep else self.T_SEP + tStyle = self.A_NONE if self._noSep else self.A_CENTRE self._noSep = False self._tokens.append(( @@ -676,12 +694,16 @@ class Tokenizer(ABC): tType = self.T_HEAD4 tStyle = self.A_NONE if self._isNovel: - tText = self._hFormatter.apply(self._fmtSection, tText, nHead) - if tText == "": - tType = self.T_EMPTY if self._hideSection else self.T_SKIP - elif tText == self._fmtSection: - tType = self.T_SEP - tStyle = self.A_CENTRE + if self._hideSection: + tText = "" + tType = self.T_EMPTY + else: + tText = self._hFormatter.apply(self._fmtSection, tText, nHead) + if tText == "": # Empty Format + tType = self.T_SKIP + elif tText == self._fmtSection: # Static Format + tType = self.T_SEP + tStyle = self.A_CENTRE self._tokens.append(( tType, nHead, tText, [], tStyle diff --git a/novelwriter/core/tomd.py b/novelwriter/core/tomd.py index a57ba18f..82fdb78a 100644 --- a/novelwriter/core/tomd.py +++ b/novelwriter/core/tomd.py @@ -27,7 +27,7 @@ import logging from pathlib import Path -from novelwriter.constants import nwHeadFmt, nwLabels +from novelwriter.constants import nwHeadFmt, nwLabels, nwUnicode from novelwriter.core.project import NWProject from novelwriter.core.tokenizer import Tokenizer @@ -108,6 +108,7 @@ class ToMarkdown(Tokenizer): self.FMT_SUB_B: "", self.FMT_SUB_E: "", } + cSkip = "" else: # Extended Markdown mdTags = { @@ -126,6 +127,7 @@ class ToMarkdown(Tokenizer): self.FMT_SUB_B: "~", self.FMT_SUB_E: "~", } + cSkip = nwUnicode.U_MMSP self._result = "" @@ -136,7 +138,7 @@ class ToMarkdown(Tokenizer): for tType, _, tText, tFormat, tStyle in self._tokens: if tType == self.T_EMPTY: - if len(para) > 0: + if para: tTemp = (lineSep.join(para)).rstrip(" ") lines.append(f"{tTemp}\n\n") para = [] @@ -145,10 +147,6 @@ class ToMarkdown(Tokenizer): tHead = tText.replace(nwHeadFmt.BR, "\n") lines.append(f"# {tHead}\n\n") - elif tType == self.T_UNNUM: - tHead = tText.replace(nwHeadFmt.BR, "\n") - lines.append(f"## {tHead}\n\n") - elif tType == self.T_HEAD1: tHead = tText.replace(nwHeadFmt.BR, "\n") lines.append(f"# {tHead}\n\n") @@ -169,7 +167,7 @@ class ToMarkdown(Tokenizer): lines.append(f"{tText}\n\n") elif tType == self.T_SKIP: - lines.append("\n\n\n") + lines.append(f"{cSkip}\n\n") elif tType == self.T_TEXT: tTemp = tText diff --git a/novelwriter/core/toodt.py b/novelwriter/core/toodt.py index 494b12c4..e3ddebdf 100644 --- a/novelwriter/core/toodt.py +++ b/novelwriter/core/toodt.py @@ -450,10 +450,6 @@ class ToOdt(Tokenizer): tHead = tText.replace(nwHeadFmt.BR, "\n") self._addTextPar("Title", oStyle, tHead, isHead=False) # Title must be text:p - elif tType == self.T_UNNUM: - tHead = tText.replace(nwHeadFmt.BR, "\n") - self._addTextPar("Heading_20_2", oStyle, tHead, isHead=True, oLevel="2") - elif tType == self.T_HEAD1: tHead = tText.replace(nwHeadFmt.BR, "\n") self._addTextPar("Heading_20_1", oStyle, tHead, isHead=True, oLevel="1") diff --git a/novelwriter/tools/manuscript.py b/novelwriter/tools/manuscript.py index 3f52e9c8..0e89b3c6 100644 --- a/novelwriter/tools/manuscript.py +++ b/novelwriter/tools/manuscript.py @@ -605,23 +605,26 @@ class _DetailsWidget(QWidget): hFmt.resetScene() hFmt.incScene() title = self.tr("Title") + hidden = self.tr("Hidden") item = QTreeWidgetItem() item.setText(0, build.getLabel("headings")) item.setText(1, "") self.listView.addTopLevelItem(item) - for key in [ - "headings.fmtTitle", "headings.fmtChapter", "headings.fmtUnnumbered", - "headings.fmtScene", "headings.fmtHardScene", "headings.fmtSection" + for hFormat, hHide in [ + ("headings.fmtTitle", "headings.hideTitle"), + ("headings.fmtChapter", "headings.hideChapter"), + ("headings.fmtUnnumbered", "headings.hideUnnumbered"), + ("headings.fmtScene", "headings.hideScene"), + ("headings.fmtHardScene", "headings.hideHardScene"), + ("headings.fmtSection", "headings.hideSection"), ]: sub = QTreeWidgetItem() - sub.setText(0, build.getLabel(key)) - sub.setText(1, hFmt.apply(build.getStr(key), title, 0)) - item.addChild(sub) - for key in ["headings.hideScene", "headings.hideHardScene", "headings.hideSection"]: - sub = QTreeWidgetItem() - sub.setText(0, build.getLabel(key)) - sub.setIcon(1, on if build.getBool(key) else off) + sub.setText(0, build.getLabel(hFormat)) + if build.getBool(hHide): + sub.setText(1, f"[{hidden}]") + else: + sub.setText(1, hFmt.apply(build.getStr(hFormat), title, 0)) item.addChild(sub) # Text Content diff --git a/novelwriter/tools/manussettings.py b/novelwriter/tools/manussettings.py index b298adbd..d072c5fb 100644 --- a/novelwriter/tools/manussettings.py +++ b/novelwriter/tools/manussettings.py @@ -601,7 +601,7 @@ class _HeadingsTab(NScrollablePage): # Format Boxes # ============ self.formatBox = QGridLayout() - self.formatBox.setHorizontalSpacing(vSp) + self.formatBox.setHorizontalSpacing(bSp) # Title Heading self.lblTitle = QLabel(self._build.getLabel("headings.fmtTitle"), self) @@ -610,14 +610,15 @@ class _HeadingsTab(NScrollablePage): self.btnTitle = QToolButton(self) self.btnTitle.setIcon(SHARED.theme.getIcon("edit")) self.btnTitle.clicked.connect(lambda: self._editHeading(self.EDIT_TITLE)) - - wrapTitle = QHBoxLayout() - wrapTitle.addWidget(self.fmtTitle) - wrapTitle.addWidget(self.btnTitle) - wrapTitle.setSpacing(bSp) + self.hdeTitle = QLabel(self.tr("Hide"), self) + self.hdeTitle.setIndent(bSp) + self.swtTitle = NSwitch(self, height=iPx) self.formatBox.addWidget(self.lblTitle, 0, 0) - self.formatBox.addLayout(wrapTitle, 0, 1) + self.formatBox.addWidget(self.fmtTitle, 0, 1) + self.formatBox.addWidget(self.btnTitle, 0, 2) + self.formatBox.addWidget(self.hdeTitle, 0, 3) + self.formatBox.addWidget(self.swtTitle, 0, 4) # Chapter Heading self.lblChapter = QLabel(self._build.getLabel("headings.fmtChapter"), self) @@ -626,14 +627,15 @@ class _HeadingsTab(NScrollablePage): self.btnChapter = QToolButton(self) self.btnChapter.setIcon(SHARED.theme.getIcon("edit")) self.btnChapter.clicked.connect(lambda: self._editHeading(self.EDIT_CHAPTER)) - - wrapChapter = QHBoxLayout() - wrapChapter.addWidget(self.fmtChapter) - wrapChapter.addWidget(self.btnChapter) - wrapChapter.setSpacing(bSp) + self.hdeChapter = QLabel(self.tr("Hide"), self) + self.hdeChapter.setIndent(bSp) + self.swtChapter = NSwitch(self, height=iPx) self.formatBox.addWidget(self.lblChapter, 1, 0) - self.formatBox.addLayout(wrapChapter, 1, 1) + self.formatBox.addWidget(self.fmtChapter, 1, 1) + self.formatBox.addWidget(self.btnChapter, 1, 2) + self.formatBox.addWidget(self.hdeChapter, 1, 3) + self.formatBox.addWidget(self.swtChapter, 1, 4) # Unnumbered Chapter Heading self.lblUnnumbered = QLabel(self._build.getLabel("headings.fmtUnnumbered"), self) @@ -642,95 +644,66 @@ class _HeadingsTab(NScrollablePage): self.btnUnnumbered = QToolButton(self) self.btnUnnumbered.setIcon(SHARED.theme.getIcon("edit")) self.btnUnnumbered.clicked.connect(lambda: self._editHeading(self.EDIT_UNNUM)) - - wrapUnnumbered = QHBoxLayout() - wrapUnnumbered.addWidget(self.fmtUnnumbered) - wrapUnnumbered.addWidget(self.btnUnnumbered) - wrapUnnumbered.setSpacing(bSp) + self.hdeUnnumbered = QLabel(self.tr("Hide"), self) + self.hdeUnnumbered.setIndent(bSp) + self.swtUnnumbered = NSwitch(self, height=iPx) self.formatBox.addWidget(self.lblUnnumbered, 2, 0) - self.formatBox.addLayout(wrapUnnumbered, 2, 1) + self.formatBox.addWidget(self.fmtUnnumbered, 2, 1) + self.formatBox.addWidget(self.btnUnnumbered, 2, 2) + self.formatBox.addWidget(self.hdeUnnumbered, 2, 3) + self.formatBox.addWidget(self.swtUnnumbered, 2, 4) # Scene Heading - sceneHideTip = self._build.getLabel("headings.hideScene") self.lblScene = QLabel(self._build.getLabel("headings.fmtScene"), self) self.fmtScene = QLineEdit("", self) self.fmtScene.setReadOnly(True) self.btnScene = QToolButton(self) self.btnScene.setIcon(SHARED.theme.getIcon("edit")) self.btnScene.clicked.connect(lambda: self._editHeading(self.EDIT_SCENE)) - self.hdeScene = QLabel(self.tr("Hide")) - self.hdeScene.setToolTip(sceneHideTip) + self.hdeScene = QLabel(self.tr("Hide"), self) + self.hdeScene.setIndent(bSp) self.swtScene = NSwitch(self, height=iPx) - self.swtScene.setToolTip(sceneHideTip) - - wrapScene = QHBoxLayout() - wrapScene.addWidget(self.fmtScene) - wrapScene.addWidget(self.btnScene) - wrapScene.setSpacing(bSp) - - wrapSceneHide = QHBoxLayout() - wrapSceneHide.addWidget(self.hdeScene) - wrapSceneHide.addWidget(self.swtScene) - wrapSceneHide.setSpacing(bSp) self.formatBox.addWidget(self.lblScene, 3, 0) - self.formatBox.addLayout(wrapScene, 3, 1) - self.formatBox.addLayout(wrapSceneHide, 3, 2) + self.formatBox.addWidget(self.fmtScene, 3, 1) + self.formatBox.addWidget(self.btnScene, 3, 2) + self.formatBox.addWidget(self.hdeScene, 3, 3) + self.formatBox.addWidget(self.swtScene, 3, 4) # Alt Scene Heading - hardSceneHideTip = self._build.getLabel("headings.hideHardScene") self.lblHScene = QLabel(self._build.getLabel("headings.fmtHardScene"), self) self.fmtHScene = QLineEdit("", self) self.fmtHScene.setReadOnly(True) self.btnHScene = QToolButton(self) self.btnHScene.setIcon(SHARED.theme.getIcon("edit")) self.btnHScene.clicked.connect(lambda: self._editHeading(self.EDIT_HSCENE)) - self.hdeHScene = QLabel(self.tr("Hide")) - self.hdeHScene.setToolTip(hardSceneHideTip) + self.hdeHScene = QLabel(self.tr("Hide"), self) + self.hdeHScene.setIndent(bSp) self.swtHScene = NSwitch(self, height=iPx) - self.swtHScene.setToolTip(hardSceneHideTip) - - wrapHScene = QHBoxLayout() - wrapHScene.addWidget(self.fmtHScene) - wrapHScene.addWidget(self.btnHScene) - wrapHScene.setSpacing(bSp) - - wrapHSceneHide = QHBoxLayout() - wrapHSceneHide.addWidget(self.hdeHScene) - wrapHSceneHide.addWidget(self.swtHScene) - wrapHSceneHide.setSpacing(bSp) self.formatBox.addWidget(self.lblHScene, 4, 0) - self.formatBox.addLayout(wrapHScene, 4, 1) - self.formatBox.addLayout(wrapHSceneHide, 4, 2) + self.formatBox.addWidget(self.fmtHScene, 4, 1) + self.formatBox.addWidget(self.btnHScene, 4, 2) + self.formatBox.addWidget(self.hdeHScene, 4, 3) + self.formatBox.addWidget(self.swtHScene, 4, 4) # Section Heading - sectionHideTip = self._build.getLabel("headings.hideSection") self.lblSection = QLabel(self._build.getLabel("headings.fmtSection"), self) self.fmtSection = QLineEdit("", self) self.fmtSection.setReadOnly(True) self.btnSection = QToolButton(self) self.btnSection.setIcon(SHARED.theme.getIcon("edit")) self.btnSection.clicked.connect(lambda: self._editHeading(self.EDIT_SECTION)) - self.hdeSection = QLabel(self.tr("Hide")) - self.hdeSection.setToolTip(sectionHideTip) + self.hdeSection = QLabel(self.tr("Hide"), self) + self.hdeSection.setIndent(bSp) self.swtSection = NSwitch(self, height=iPx) - self.swtSection.setToolTip(sectionHideTip) - - wrapSection = QHBoxLayout() - wrapSection.addWidget(self.fmtSection) - wrapSection.addWidget(self.btnSection) - wrapSection.setSpacing(bSp) - - wrapSectionHide = QHBoxLayout() - wrapSectionHide.addWidget(self.hdeSection) - wrapSectionHide.addWidget(self.swtSection) - wrapSectionHide.setSpacing(bSp) self.formatBox.addWidget(self.lblSection, 5, 0) - self.formatBox.addLayout(wrapSection, 5, 1) - self.formatBox.addLayout(wrapSectionHide, 5, 2) + self.formatBox.addWidget(self.fmtSection, 5, 1) + self.formatBox.addWidget(self.btnSection, 5, 2) + self.formatBox.addWidget(self.hdeSection, 5, 3) + self.formatBox.addWidget(self.swtSection, 5, 4) # Edit Form # ========= @@ -860,9 +833,14 @@ class _HeadingsTab(NScrollablePage): self.fmtScene.setText(self._build.getStr("headings.fmtScene")) self.fmtHScene.setText(self._build.getStr("headings.fmtHardScene")) self.fmtSection.setText(self._build.getStr("headings.fmtSection")) + + self.swtTitle.setChecked(self._build.getBool("headings.hideTitle")) + self.swtChapter.setChecked(self._build.getBool("headings.hideChapter")) + self.swtUnnumbered.setChecked(self._build.getBool("headings.hideUnnumbered")) self.swtScene.setChecked(self._build.getBool("headings.hideScene")) self.swtHScene.setChecked(self._build.getBool("headings.hideHardScene")) self.swtSection.setChecked(self._build.getBool("headings.hideSection")) + self.centerTitle.setChecked(self._build.getBool("headings.centerTitle")) self.centerChapter.setChecked(self._build.getBool("headings.centerChapter")) self.centerScene.setChecked(self._build.getBool("headings.centerScene")) @@ -873,9 +851,13 @@ class _HeadingsTab(NScrollablePage): def saveContent(self) -> None: """Save choices back into build object.""" + self._build.setValue("headings.hideTitle", self.swtTitle.isChecked()) + self._build.setValue("headings.hideChapter", self.swtChapter.isChecked()) + self._build.setValue("headings.hideUnnumbered", self.swtUnnumbered.isChecked()) self._build.setValue("headings.hideScene", self.swtScene.isChecked()) self._build.setValue("headings.hideHardScene", self.swtHScene.isChecked()) self._build.setValue("headings.hideSection", self.swtSection.isChecked()) + self._build.setValue("headings.centerTitle", self.centerTitle.isChecked()) self._build.setValue("headings.centerChapter", self.centerChapter.isChecked()) self._build.setValue("headings.centerScene", self.centerScene.isChecked()) diff --git a/tests/test_core/test_core_tohtml.py b/tests/test_core/test_core_tohtml.py index ef6d9b3e..da80e880 100644 --- a/tests/test_core/test_core_tohtml.py +++ b/tests/test_core/test_core_tohtml.py @@ -288,7 +288,7 @@ def testCoreToHtml_ConvertDirect(mockGUI): # Unnumbered html._tokens = [ - (html.T_UNNUM, 1, "Prologue", None, html.A_PBB), + (html.T_HEAD2, 1, "Prologue", None, html.A_PBB), (html.T_EMPTY, 1, "", None, html.A_NONE), ] html.doConvert() diff --git a/tests/test_core/test_core_tokenizer.py b/tests/test_core/test_core_tokenizer.py index 03343d0c..b3b69774 100644 --- a/tests/test_core/test_core_tokenizer.py +++ b/tests/test_core/test_core_tokenizer.py @@ -62,7 +62,11 @@ def testCoreToken_Setters(mockGUI): assert tokens._marginHead4 == (0.584, 0.500) assert tokens._marginText == (0.000, 0.584) assert tokens._marginMeta == (0.000, 0.584) + assert tokens._hideTitle is False + assert tokens._hideChapter is False + assert tokens._hideUnNum is False assert tokens._hideScene is False + assert tokens._hideHScene is False assert tokens._hideSection is False assert tokens._linkHeaders is False assert tokens._doBodyText is True @@ -71,9 +75,9 @@ def testCoreToken_Setters(mockGUI): assert tokens._doKeywords is False # Set new values - tokens.setTitleFormat(f"T: {nwHeadFmt.TITLE}") - tokens.setChapterFormat(f"C: {nwHeadFmt.TITLE}") - tokens.setUnNumberedFormat(f"U: {nwHeadFmt.TITLE}") + tokens.setTitleFormat(f"T: {nwHeadFmt.TITLE}", True) + tokens.setChapterFormat(f"C: {nwHeadFmt.TITLE}", True) + tokens.setUnNumberedFormat(f"U: {nwHeadFmt.TITLE}", True) tokens.setSceneFormat(f"S: {nwHeadFmt.TITLE}", True) tokens.setHardSceneFormat(f"H: {nwHeadFmt.TITLE}", True) tokens.setSectionFormat(f"X: {nwHeadFmt.TITLE}", True) @@ -114,7 +118,11 @@ def testCoreToken_Setters(mockGUI): assert tokens._marginHead4 == (2.0, 2.0) assert tokens._marginText == (2.0, 2.0) assert tokens._marginMeta == (2.0, 2.0) + assert tokens._hideTitle is True + assert tokens._hideChapter is True + assert tokens._hideUnNum is True assert tokens._hideScene is True + assert tokens._hideHScene is True assert tokens._hideSection is True assert tokens._linkHeaders is True assert tokens._doBodyText is False @@ -122,6 +130,12 @@ def testCoreToken_Setters(mockGUI): assert tokens._doComments is True assert tokens._doKeywords is True + # Properties + assert tokens.result == "" + assert tokens.allMarkdown == [] + assert tokens.textStats == {} + assert tokens.errData == [] + # Check Limits tokens.setLineHeight(0.0) assert tokens._lineHeight == 0.5 @@ -419,7 +433,7 @@ def testCoreToken_HeaderFormat(mockGUI): tokens.tokenizeText() assert tokens._tokens == [ - (Tokenizer.T_UNNUM, 1, "Prologue", [], Tokenizer.A_PBB), + (Tokenizer.T_HEAD2, 1, "Prologue", [], Tokenizer.A_PBB), (Tokenizer.T_EMPTY, 1, "", [], Tokenizer.A_NONE), ] assert tokens.allMarkdown[-1] == "##! Prologue\n\n" @@ -1340,7 +1354,7 @@ def testCoreToken_ProcessHeaders(mockGUI): tokens.setUnNumberedFormat(f"U: {nwHeadFmt.TITLE}") tokens.tokenizeText() assert tokens._tokens == [ - (Tokenizer.T_UNNUM, 1, "U: Prologue", [], Tokenizer.A_PBB), + (Tokenizer.T_HEAD2, 1, "U: Prologue", [], Tokenizer.A_PBB), (Tokenizer.T_EMPTY, 1, "", [], Tokenizer.A_NONE), ] @@ -1720,10 +1734,8 @@ def testCoreToken_CountStats(mockGUI, ipsumText): @pytest.mark.core -def testCoreToken_HeaderCounterAndVisibility(mockGUI): - """Test the header counter and visibility of the Tokenizer class. - This is a special test to cover issue #1704. - """ +def testCoreToken_SceneSeparators(mockGUI): + """Test the section and scene separators of the Tokenizer class.""" project = NWProject() project.data.setLanguage("en") project._loadProjectLocalisation() @@ -1841,6 +1853,172 @@ def testCoreToken_HeaderCounterAndVisibility(mockGUI): "Text\n\n" ) + # Separators with Scenes Only + # =========================== + # Requires a fresh builder class + md = ToMarkdown(project) + md.setExtendedMarkdown() + md._isNone = False + md._isNote = False + md._isNovel = True + + md._text = ( + "### Scene One\n\n" + "Text\n\n" + "### Scene Two\n\n" + "Text\n\n" + "### Scene Three\n\n" + "Text\n\n" + "###! Scene Four\n\n" + "Text\n\n" + ) + + md.setSceneFormat("", False) + md.setHardSceneFormat("* * *", False) + md.tokenizeText() + md.doConvert() + assert md.result == ( + "Text\n\n" + "\u205f\n\n" + "Text\n\n" + "\u205f\n\n" + "Text\n\n" + "* * *\n\n" + "Text\n\n" + ) + +# END Test testCoreToken_SceneSeparators + + +@pytest.mark.core +def testCoreToken_HeaderVisibility(mockGUI): + """Test the heading visibility settings of the Tokenizer class.""" + project = NWProject() + project.data.setLanguage("en") + project._loadProjectLocalisation() + md = ToMarkdown(project) + + md._text = ( + "#! Novel\n\n" + "# Title One\n\n" + "##! Prologue\n\n" + "Text\n\n" + "## Chapter One\n\n" + "### Scene One\n\n" + "Text\n\n" + "### Scene Two\n\n" + "#### Section Two\n\n" + "Text\n\n" + "## Chapter Two\n\n" + "### Scene Three\n\n" + "Text\n\n" + "###! Scene Four\n\n" + "Text\n\n" + ) + + # Novel Files + # =========== + + md._isNone = False + md._isNote = False + md._isNovel = True + + # Show All + md.setTitleFormat(nwHeadFmt.TITLE, False) + md.setChapterFormat(nwHeadFmt.TITLE, False) + md.setUnNumberedFormat(nwHeadFmt.TITLE, False) + md.setSceneFormat(nwHeadFmt.TITLE, False) + md.setHardSceneFormat(nwHeadFmt.TITLE, False) + md.setSectionFormat(nwHeadFmt.TITLE, False) + + md.tokenizeText() + md.doConvert() + assert md.result == ( + "# Novel\n\n" + "# Title One\n\n" + "## Prologue\n\n" + "Text\n\n" + "## Chapter One\n\n" + "### Scene One\n\n" + "Text\n\n" + "### Scene Two\n\n" + "#### Section Two\n\n" + "Text\n\n" + "## Chapter Two\n\n" + "### Scene Three\n\n" + "Text\n\n" + "### Scene Four\n\n" + "Text\n\n" + ) + + # Hide All + md.setTitleFormat(nwHeadFmt.TITLE, True) + md.setChapterFormat(nwHeadFmt.TITLE, True) + md.setUnNumberedFormat(nwHeadFmt.TITLE, True) + md.setSceneFormat(nwHeadFmt.TITLE, True) + md.setHardSceneFormat(nwHeadFmt.TITLE, True) + md.setSectionFormat(nwHeadFmt.TITLE, True) + + md.tokenizeText() + md.doConvert() + assert md.result == ( + "# Novel\n\n" + "Text\n\n" + "Text\n\n" + "Text\n\n" + "Text\n\n" + "Text\n\n" + ) + + # Note Files + # ========== + + md._isNone = False + md._isNote = True + md._isNovel = False + + # Hide All + md.setTitleFormat(nwHeadFmt.TITLE, True) + md.setChapterFormat(nwHeadFmt.TITLE, True) + md.setUnNumberedFormat(nwHeadFmt.TITLE, True) + md.setSceneFormat(nwHeadFmt.TITLE, True) + md.setHardSceneFormat(nwHeadFmt.TITLE, True) + md.setSectionFormat(nwHeadFmt.TITLE, True) + + md.tokenizeText() + md.doConvert() + assert md.result == ( + "# Novel\n\n" + "# Title One\n\n" + "## Prologue\n\n" + "Text\n\n" + "## Chapter One\n\n" + "### Scene One\n\n" + "Text\n\n" + "### Scene Two\n\n" + "#### Section Two\n\n" + "Text\n\n" + "## Chapter Two\n\n" + "### Scene Three\n\n" + "Text\n\n" + "### Scene Four\n\n" + "Text\n\n" + ) + +# END Test testCoreToken_HeaderVisibility + + +@pytest.mark.core +def testCoreToken_CounterHandling(mockGUI): + """Test the heading counter of the Tokenizer class.""" + project = NWProject() + project.data.setLanguage("en") + project._loadProjectLocalisation() + md = ToMarkdown(project) + md._isNone = False + md._isNote = False + md._isNovel = True + # Counter Handling, Novel Titles # ============================== # This also checks that only numbered chapters bump the counter @@ -1875,6 +2053,7 @@ def testCoreToken_HeaderCounterAndVisibility(mockGUI): ) md.setTitleFormat(f"T: {nwHeadFmt.TITLE}") md.setChapterFormat(f"C {nwHeadFmt.CH_NUM}: {nwHeadFmt.TITLE}") + md.setUnNumberedFormat(f"U: {nwHeadFmt.TITLE}") md.setSceneFormat( f"S {nwHeadFmt.CH_NUM}.{nwHeadFmt.SC_NUM} ({nwHeadFmt.SC_ABS}): {nwHeadFmt.TITLE}", False ) @@ -1888,7 +2067,7 @@ def testCoreToken_HeaderCounterAndVisibility(mockGUI): md.doConvert() assert md.result == ( "# Novel One\n\n" - "## Prologue\n\n" + "## U: Prologue\n\n" "Text\n\n" "## C 1: Chapter One\n\n" "### S 1.1 (1): Scene One\n\n" @@ -1901,7 +2080,7 @@ def testCoreToken_HeaderCounterAndVisibility(mockGUI): "### H 2.2 (4): Scene Four\n\n" "Text\n\n" "# Novel Two\n\n" - "## Prologue\n\n" + "## U: Prologue\n\n" "Text\n\n" "## C 1: Chapter One\n\n" "### S 1.1 (1): Scene One\n\n" @@ -1915,11 +2094,11 @@ def testCoreToken_HeaderCounterAndVisibility(mockGUI): "Text\n\n" ) -# END Test testCoreToken_HeaderCounterAndVisibility +# END Test testCoreToken_CounterHandling @pytest.mark.core -def testCoreIndex_HeadingFormatter(fncPath, mockRnd): +def testCoreToken_HeadingFormatter(fncPath, mockRnd): """Check the HeadingFormatter class.""" project = NWProject() project.setProjectLang("en_GB") @@ -2023,4 +2202,4 @@ def testCoreIndex_HeadingFormatter(fncPath, mockRnd): cFormat = f"Chapter {nwHeadFmt.CH_NUM}, Scene {nwHeadFmt.SC_NUM} - {nwHeadFmt.CHAR_FOCUS}" assert formatter.apply(cFormat, "Hi Bob", 0) == "Chapter 2, Scene 3 - Focus" -# END Test testCoreIndex_HeadingFormatter +# END Test testCoreToken_HeadingFormatter diff --git a/tests/test_core/test_core_tomd.py b/tests/test_core/test_core_tomd.py index b368ad68..c67caf63 100644 --- a/tests/test_core/test_core_tomd.py +++ b/tests/test_core/test_core_tomd.py @@ -213,14 +213,6 @@ def testCoreToMarkdown_ConvertDirect(mockGUI): toMD.doConvert() assert toMD.result == "# A Title\n\n" - # Unnumbered - toMD._tokens = [ - (toMD.T_UNNUM, 1, "Prologue", None, toMD.A_PBB), - (toMD.T_EMPTY, 1, "", None, toMD.A_NONE), - ] - toMD.doConvert() - assert toMD.result == "## Prologue\n\n" - # Separators # ========== @@ -238,7 +230,7 @@ def testCoreToMarkdown_ConvertDirect(mockGUI): (toMD.T_EMPTY, 1, "", None, toMD.A_NONE), ] toMD.doConvert() - assert toMD.result == "\n\n\n" + assert toMD.result == "\n\n" # END Test testCoreToMarkdown_ConvertDirect