diff --git a/novelwriter/core/buildsettings.py b/novelwriter/core/buildsettings.py index 1037c572..44f186cb 100644 --- a/novelwriter/core/buildsettings.py +++ b/novelwriter/core/buildsettings.py @@ -59,6 +59,12 @@ SETTINGS_TEMPLATE = { "headings.fmtSection": (str, ""), "headings.hideScene": (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), diff --git a/novelwriter/core/docbuild.py b/novelwriter/core/docbuild.py index d3e10987..a7966074 100644 --- a/novelwriter/core/docbuild.py +++ b/novelwriter/core/docbuild.py @@ -286,6 +286,18 @@ class NWBuildDocument: self._build.getStr("headings.fmtSection"), self._build.getBool("headings.hideSection") ) + bldObj.setTitleStyle( + self._build.getBool("headings.centerTitle"), + self._build.getBool("headings.breakTitle") + ) + bldObj.setChapterStyle( + self._build.getBool("headings.centerChapter"), + self._build.getBool("headings.breakChapter") + ) + bldObj.setSceneStyle( + self._build.getBool("headings.centerScene"), + self._build.getBool("headings.breakScene") + ) bldObj.setFont(fontFamily, textSize, textFixed) bldObj.setJustify(self._build.getBool("format.justifyText")) diff --git a/novelwriter/core/tokenizer.py b/novelwriter/core/tokenizer.py index 1bd3d794..f148236f 100644 --- a/novelwriter/core/tokenizer.py +++ b/novelwriter/core/tokenizer.py @@ -162,6 +162,10 @@ class Tokenizer(ABC): self._linkHeaders = False # Add an anchor before headers + self._titleStyle = self.A_CENTRE | self.A_PBB + self._chapterStyle = self.A_PBB + self._sceneStyle = self.A_NONE + # Instance Variables self._hFormatter = HeadingFormatter(self._project) self._skipSeparator = False # Flag to indicate that we skip the scene separator @@ -171,6 +175,7 @@ class Tokenizer(ABC): self._isNovel = False # Document is a novel document self._isNote = False # Document is a project note self._isFirst = True # Document is the first in a set + self._noBreak = False # Don't allow an initial break when formatting headers # Error Handling self._errData = [] @@ -255,6 +260,27 @@ class Tokenizer(ABC): self._hideSection = hide return + def setTitleStyle(self, center: bool, pageBreak: bool) -> None: + """Set the title heading style.""" + self._titleStyle = ( + (self.A_CENTRE if center else self.A_NONE) | (self.A_PBB if pageBreak else self.A_NONE) + ) + return + + def setChapterStyle(self, center: bool, pageBreak: bool) -> None: + """Set the chapter heading style.""" + self._chapterStyle = ( + (self.A_CENTRE if center else self.A_NONE) | (self.A_PBB if pageBreak else self.A_NONE) + ) + return + + def setSceneStyle(self, center: bool, pageBreak: bool) -> None: + """Set the scene heading style.""" + self._sceneStyle = ( + (self.A_CENTRE if center else self.A_NONE) | (self.A_PBB if pageBreak else self.A_NONE) + ) + return + def setFont(self, family: str, size: int, isFixed: bool = False) -> None: """Set the build font.""" self._textFont = family @@ -511,24 +537,17 @@ class Tokenizer(ABC): tmpMarkdown.append("%s\n" % aLine) elif aLine[:2] == "# ": - if self._isNovel: - sAlign |= self.A_CENTRE - sAlign |= self.A_PBB - nHead += 1 self._tokens.append(( - self.T_HEAD1, nHead, aLine[2:].strip(), [], sAlign + self.T_HEAD1, nHead, aLine[2:].strip(), [], self.A_NONE )) if self._keepMarkdown: tmpMarkdown.append("%s\n" % aLine) elif aLine[:3] == "## ": - if self._isNovel: - sAlign |= self.A_PBB - nHead += 1 self._tokens.append(( - self.T_HEAD2, nHead, aLine[3:].strip(), [], sAlign + self.T_HEAD2, nHead, aLine[3:].strip(), [], self.A_NONE )) if self._keepMarkdown: tmpMarkdown.append("%s\n" % aLine) @@ -536,7 +555,7 @@ class Tokenizer(ABC): elif aLine[:4] == "### ": nHead += 1 self._tokens.append(( - self.T_HEAD3, nHead, aLine[4:].strip(), [], sAlign + self.T_HEAD3, nHead, aLine[4:].strip(), [], self.A_NONE )) if self._keepMarkdown: tmpMarkdown.append("%s\n" % aLine) @@ -544,7 +563,7 @@ class Tokenizer(ABC): elif aLine[:5] == "#### ": nHead += 1 self._tokens.append(( - self.T_HEAD4, nHead, aLine[5:].strip(), [], sAlign + self.T_HEAD4, nHead, aLine[5:].strip(), [], self.A_NONE )) if self._keepMarkdown: tmpMarkdown.append("%s\n" % aLine) @@ -557,7 +576,7 @@ class Tokenizer(ABC): tStyle = self.T_HEAD1 self._tokens.append(( - tStyle, nHead, aLine[3:].strip(), [], sAlign | self.A_CENTRE + tStyle, nHead, aLine[3:].strip(), [], self.A_PBB | self.A_CENTRE )) if self._keepMarkdown: tmpMarkdown.append("%s\n" % aLine) @@ -566,12 +585,11 @@ class Tokenizer(ABC): nHead += 1 if self._isNovel: tStyle = self.T_UNNUM - sAlign |= self.A_PBB else: tStyle = self.T_HEAD2 self._tokens.append(( - tStyle, nHead, aLine[4:].strip(), [], sAlign + tStyle, nHead, aLine[4:].strip(), [], self.A_NONE )) if self._keepMarkdown: tmpMarkdown.append("%s\n" % aLine) @@ -622,7 +640,8 @@ class Tokenizer(ABC): # If we have content, turn off the first page flag if self._isFirst and self._tokens: - self._isFirst = False + self._isFirst = False # First document has been processed + self._noBreak = True # Checks again after headers are processed # Make sure the token array doesn't start with a page break # on the very first page, adding a blank first page. @@ -686,7 +705,7 @@ class Tokenizer(ABC): tTemp = self._hFormatter.apply(self._fmtTitle, token[2], token[1]) self._tokens[n] = ( - token[0], token[1], tTemp, [], token[4] + token[0], token[1], tTemp, [], self._titleStyle ) # Set scene variables @@ -704,7 +723,7 @@ class Tokenizer(ABC): # Format the chapter header self._tokens[n] = ( - token[0], token[1], tTemp, [], token[4] + token[0], token[1], tTemp, [], self._chapterStyle ) # Set scene variables @@ -729,11 +748,11 @@ class Tokenizer(ABC): self._tokens[n] = ( self.T_EMPTY if self._skipSeparator else self.T_SEP, token[1], "" if self._skipSeparator else tTemp, [], - self.A_NONE if self._skipSeparator else (token[4] | self.A_CENTRE) + self.A_NONE if self._skipSeparator else self.A_CENTRE ) else: self._tokens[n] = ( - token[0], token[1], tTemp, [], token[4] + token[0], token[1], tTemp, [], self._sceneStyle ) self._skipSeparator = False @@ -758,6 +777,15 @@ class Tokenizer(ABC): token[0], token[1], tTemp, [], token[4] ) + if n == 0 and self._noBreak: + # Make sure again that the token array doesn't start with a page break + self._noBreak = False + if self._tokens[0][4] & self.A_PBB: + token = self._tokens[0] + self._tokens[0] = ( + token[0], token[1], token[2], token[3], token[4] & ~self.A_PBB + ) + return True def countStats(self) -> dict[str, int]: diff --git a/novelwriter/tools/manussettings.py b/novelwriter/tools/manussettings.py index 3ccf79e1..d27b8acf 100644 --- a/novelwriter/tools/manussettings.py +++ b/novelwriter/tools/manussettings.py @@ -593,6 +593,7 @@ class _HeadingsTab(NScrollablePage): self._editing = 0 iPx = SHARED.theme.baseIconSize + sSp = CONFIG.pxInt(16) vSp = CONFIG.pxInt(12) bSp = CONFIG.pxInt(6) @@ -614,8 +615,8 @@ class _HeadingsTab(NScrollablePage): wrapTitle.addWidget(self.btnTitle) wrapTitle.setSpacing(bSp) - self.formatBox.addWidget(self.lblTitle, 0, 0, Qt.AlignLeft) - self.formatBox.addLayout(wrapTitle, 0, 1, Qt.AlignLeft) + self.formatBox.addWidget(self.lblTitle, 0, 0) + self.formatBox.addLayout(wrapTitle, 0, 1) # Chapter Heading self.lblChapter = QLabel(self._build.getLabel("headings.fmtChapter")) @@ -630,8 +631,8 @@ class _HeadingsTab(NScrollablePage): wrapChapter.addWidget(self.btnChapter) wrapChapter.setSpacing(bSp) - self.formatBox.addWidget(self.lblChapter, 1, 0, Qt.AlignLeft) - self.formatBox.addLayout(wrapChapter, 1, 1, Qt.AlignLeft) + self.formatBox.addWidget(self.lblChapter, 1, 0) + self.formatBox.addLayout(wrapChapter, 1, 1) # Unnumbered Chapter Heading self.lblUnnumbered = QLabel(self._build.getLabel("headings.fmtUnnumbered")) @@ -646,8 +647,8 @@ class _HeadingsTab(NScrollablePage): wrapUnnumbered.addWidget(self.btnUnnumbered) wrapUnnumbered.setSpacing(bSp) - self.formatBox.addWidget(self.lblUnnumbered, 2, 0, Qt.AlignLeft) - self.formatBox.addLayout(wrapUnnumbered, 2, 1, Qt.AlignLeft) + self.formatBox.addWidget(self.lblUnnumbered, 2, 0) + self.formatBox.addLayout(wrapUnnumbered, 2, 1) # Scene Heading sceneHideTip = self._build.getLabel("headings.hideScene") @@ -672,9 +673,9 @@ class _HeadingsTab(NScrollablePage): wrapSceneHide.addWidget(self.swtScene) wrapSceneHide.setSpacing(bSp) - self.formatBox.addWidget(self.lblScene, 3, 0, Qt.AlignLeft) - self.formatBox.addLayout(wrapScene, 3, 1, Qt.AlignLeft) - self.formatBox.addLayout(wrapSceneHide, 3, 2, Qt.AlignLeft) + self.formatBox.addWidget(self.lblScene, 3, 0) + self.formatBox.addLayout(wrapScene, 3, 1) + self.formatBox.addLayout(wrapSceneHide, 3, 2) # Section Heading sectionHideTip = self._build.getLabel("headings.hideSection") @@ -699,16 +700,16 @@ class _HeadingsTab(NScrollablePage): wrapSectionHide.addWidget(self.swtSection) wrapSectionHide.setSpacing(bSp) - self.formatBox.addWidget(self.lblSection, 4, 0, Qt.AlignLeft) - self.formatBox.addLayout(wrapSection, 4, 1, Qt.AlignLeft) - self.formatBox.addLayout(wrapSectionHide, 4, 2, Qt.AlignLeft) + self.formatBox.addWidget(self.lblSection, 4, 0) + self.formatBox.addLayout(wrapSection, 4, 1) + self.formatBox.addLayout(wrapSectionHide, 4, 2) # Edit Form # ========= self.lblEditForm = QLabel(self.tr("Editing: {0}").format(self.tr("None"))) - self.editTextBox = QPlainTextEdit() + self.editTextBox = QPlainTextEdit(self) self.editTextBox.setFixedHeight(5*iPx) self.editTextBox.setEnabled(False) @@ -735,10 +736,10 @@ class _HeadingsTab(NScrollablePage): 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 = QPushButton(self.tr("Insert"), self) self.btnInsert.setMenu(self.menuInsert) - self.btnApply = QPushButton(self.tr("Apply")) + self.btnApply = QPushButton(self.tr("Apply"), self) self.btnApply.clicked.connect(self._saveFormat) self.formButtonBox = QHBoxLayout() @@ -751,13 +752,72 @@ class _HeadingsTab(NScrollablePage): self.editFormBox.addWidget(self.editTextBox) self.editFormBox.addLayout(self.formButtonBox) + # Layout Matrix + # ============= + self.layoutMatrix = QGridLayout() + self.layoutMatrix.setVerticalSpacing(vSp) + self.layoutMatrix.setHorizontalSpacing(vSp) + + # Heading + self.layoutHeading = QLabel("{0}".format(self.tr("Additional Styling")), self) + self.layoutMatrix.addWidget(self.layoutHeading, 0, 0, 1, 5) + + # Title Layout + self.mtxTitle = QLabel(self._build.getLabel("headings.fmtTitle")) + self.centerTitle = NSwitch(self, height=iPx) + self.breakTitle = NSwitch(self, height=iPx) + lblCenterT = QLabel(self.tr("Centre")) + lblCenterT.setIndent(sSp) + lblBreakT = QLabel(self.tr("Page Break")) + lblBreakT.setIndent(sSp) + + self.layoutMatrix.addWidget(self.mtxTitle, 1, 0) + self.layoutMatrix.addWidget(lblCenterT, 1, 1) + self.layoutMatrix.addWidget(self.centerTitle, 1, 2) + self.layoutMatrix.addWidget(lblBreakT, 1, 3) + self.layoutMatrix.addWidget(self.breakTitle, 1, 4) + + # Chapter Layout + self.mtxChapter = QLabel(self._build.getLabel("headings.fmtChapter")) + self.centerChapter = NSwitch(self, height=iPx) + self.breakChapter = NSwitch(self, height=iPx) + lblCenterC = QLabel(self.tr("Centre")) + lblCenterC.setIndent(sSp) + lblBreakC = QLabel(self.tr("Page Break")) + lblBreakC.setIndent(sSp) + + self.layoutMatrix.addWidget(self.mtxChapter, 2, 0) + self.layoutMatrix.addWidget(lblCenterC, 2, 1) + self.layoutMatrix.addWidget(self.centerChapter, 2, 2) + self.layoutMatrix.addWidget(lblBreakC, 2, 3) + self.layoutMatrix.addWidget(self.breakChapter, 2, 4) + + # Scene Layout + self.mtxScene = QLabel(self._build.getLabel("headings.fmtScene")) + self.centerScene = NSwitch(self, height=iPx) + self.breakScene = NSwitch(self, height=iPx) + lblCenterS = QLabel(self.tr("Centre")) + lblCenterS.setIndent(sSp) + lblBreakS = QLabel(self.tr("Page Break")) + lblBreakS.setIndent(sSp) + + self.layoutMatrix.addWidget(self.mtxScene, 3, 0) + self.layoutMatrix.addWidget(lblCenterS, 3, 1) + self.layoutMatrix.addWidget(self.centerScene, 3, 2) + self.layoutMatrix.addWidget(lblBreakS, 3, 3) + self.layoutMatrix.addWidget(self.breakScene, 3, 4) + + self.layoutMatrix.setColumnStretch(5, 1) + # Assemble # ======== self.outerBox = QVBoxLayout() self.outerBox.addLayout(self.formatBox) - self.outerBox.addSpacing(CONFIG.pxInt(16)) + self.outerBox.addSpacing(sSp) self.outerBox.addLayout(self.editFormBox) + self.outerBox.addSpacing(sSp) + self.outerBox.addLayout(self.layoutMatrix) self.outerBox.addStretch(1) self.setCentralLayout(self.outerBox) @@ -773,12 +833,24 @@ class _HeadingsTab(NScrollablePage): self.fmtSection.setText(self._build.getStr("headings.fmtSection")) self.swtScene.setChecked(self._build.getBool("headings.hideScene")) 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")) + self.breakTitle.setChecked(self._build.getBool("headings.breakTitle")) + self.breakChapter.setChecked(self._build.getBool("headings.breakChapter")) + self.breakScene.setChecked(self._build.getBool("headings.breakScene")) return def saveContent(self) -> None: """Save choices back into build object.""" self._build.setValue("headings.hideScene", self.swtScene.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()) + self._build.setValue("headings.breakTitle", self.breakTitle.isChecked()) + self._build.setValue("headings.breakChapter", self.breakChapter.isChecked()) + self._build.setValue("headings.breakScene", self.breakScene.isChecked()) return ##