diff --git a/novelwriter/constants.py b/novelwriter/constants.py index 8f97ea2f..00b4c88c 100644 --- a/novelwriter/constants.py +++ b/novelwriter/constants.py @@ -93,12 +93,33 @@ class nwShortcode: } -class nwHeaders: +class nwStyles: H_VALID = ("H0", "H1", "H2", "H3", "H4") H_LEVEL = {"H0": 0, "H1": 1, "H2": 2, "H3": 3, "H4": 4} H_SIZES = {0: 2.50, 1: 2.00, 2: 1.75, 3: 1.50, 4: 1.25} + T_LABEL = { + "H0": QT_TRANSLATE_NOOP("Constant", "Title"), + "H1": QT_TRANSLATE_NOOP("Constant", "Heading 1 (Partition)"), + "H2": QT_TRANSLATE_NOOP("Constant", "Heading 2 (Chapter)"), + "H3": QT_TRANSLATE_NOOP("Constant", "Heading 3 (Scene)"), + "H4": QT_TRANSLATE_NOOP("Constant", "Heading 4 (Section)"), + "TT": QT_TRANSLATE_NOOP("Constant", "Text Paragraph"), + "SP": QT_TRANSLATE_NOOP("Constant", "Scene Separator"), + } + T_MARGIN = { + "H0": (1.42, 0.50), # Title margins + "H1": (1.42, 0.50), # Heading 1 margins + "H2": (1.67, 0.50), # Heading 2 margins + "H3": (1.17, 0.50), # Heading 3 margins + "H4": (1.17, 0.50), # Heading 4 margins + "TT": (0.00, 0.58), # Text margins + "SP": (1.17, 1.17), # Separator margins + "MT": (0.00, 0.58), # Meta margins + "FT": (1.42, 0.47), # Footnote margins + } + class nwFiles: diff --git a/novelwriter/core/buildsettings.py b/novelwriter/core/buildsettings.py index 71662db4..80c0cd72 100644 --- a/novelwriter/core/buildsettings.py +++ b/novelwriter/core/buildsettings.py @@ -36,7 +36,7 @@ from PyQt5.QtCore import QT_TRANSLATE_NOOP, QCoreApplication from novelwriter import CONFIG from novelwriter.common import checkUuid, isHandle, jsonEncode -from novelwriter.constants import nwFiles, nwHeadFmt +from novelwriter.constants import nwFiles, nwHeadFmt, nwStyles from novelwriter.core.project import NWProject from novelwriter.enum import nwBuildFmt from novelwriter.error import logException @@ -87,6 +87,20 @@ SETTINGS_TEMPLATE: dict[str, tuple[type, str | int | float | bool]] = { "format.firstLineIndent": (bool, False), "format.firstIndentWidth": (float, 1.4), "format.indentFirstPar": (bool, False), + "format.titleMarginT": (float, nwStyles.T_MARGIN["H0"][0]), + "format.titleMarginB": (float, nwStyles.T_MARGIN["H0"][1]), + "format.h1MarginT": (float, nwStyles.T_MARGIN["H1"][0]), + "format.h1MarginB": (float, nwStyles.T_MARGIN["H1"][1]), + "format.h2MarginT": (float, nwStyles.T_MARGIN["H2"][0]), + "format.h2MarginB": (float, nwStyles.T_MARGIN["H2"][1]), + "format.h3MarginT": (float, nwStyles.T_MARGIN["H3"][0]), + "format.h3MarginB": (float, nwStyles.T_MARGIN["H3"][1]), + "format.h4MarginT": (float, nwStyles.T_MARGIN["H4"][0]), + "format.h4MarginB": (float, nwStyles.T_MARGIN["H4"][1]), + "format.textMarginT": (float, nwStyles.T_MARGIN["TT"][0]), + "format.textMarginB": (float, nwStyles.T_MARGIN["TT"][1]), + "format.sepMarginT": (float, nwStyles.T_MARGIN["SP"][0]), + "format.sepMarginB": (float, nwStyles.T_MARGIN["SP"][1]), "format.pageUnit": (str, "cm"), "format.pageSize": (str, "A4"), "format.pageWidth": (float, 21.0), @@ -144,15 +158,12 @@ SETTINGS_LABELS = { "format.firstIndentWidth": QT_TRANSLATE_NOOP("Builds", "Indent Width"), "format.indentFirstPar": QT_TRANSLATE_NOOP("Builds", "Indent First Paragraph"), + "format.grpMargins": QT_TRANSLATE_NOOP("Builds", "Text Margins"), + "format.grpPage": QT_TRANSLATE_NOOP("Builds", "Page Layout"), "format.pageUnit": QT_TRANSLATE_NOOP("Builds", "Unit"), "format.pageSize": QT_TRANSLATE_NOOP("Builds", "Page Size"), - "format.pageWidth": QT_TRANSLATE_NOOP("Builds", "Page Width"), - "format.pageHeight": QT_TRANSLATE_NOOP("Builds", "Page Height"), - "format.topMargin": QT_TRANSLATE_NOOP("Builds", "Top Margin"), - "format.bottomMargin": QT_TRANSLATE_NOOP("Builds", "Bottom Margin"), - "format.leftMargin": QT_TRANSLATE_NOOP("Builds", "Left Margin"), - "format.rightMargin": QT_TRANSLATE_NOOP("Builds", "Right Margin"), + "format.pageMargins": QT_TRANSLATE_NOOP("Builds", "Page Margins"), "odt": QT_TRANSLATE_NOOP("Builds", "Document Options"), "odt.pageHeader": QT_TRANSLATE_NOOP("Builds", "Page Header"), diff --git a/novelwriter/core/docbuild.py b/novelwriter/core/docbuild.py index cc9d1e50..732b2d76 100644 --- a/novelwriter/core/docbuild.py +++ b/novelwriter/core/docbuild.py @@ -287,6 +287,35 @@ class NWBuildDocument: self._build.getBool("odt.boldHeadings"), ) + bldObj.setTitleMargins( + self._build.getFloat("format.titleMarginT"), + self._build.getFloat("format.titleMarginB"), + ) + bldObj.setHead1Margins( + self._build.getFloat("format.h1MarginT"), + self._build.getFloat("format.h1MarginB"), + ) + bldObj.setHead2Margins( + self._build.getFloat("format.h2MarginT"), + self._build.getFloat("format.h2MarginB"), + ) + bldObj.setHead3Margins( + self._build.getFloat("format.h3MarginT"), + self._build.getFloat("format.h3MarginB"), + ) + bldObj.setHead4Margins( + self._build.getFloat("format.h4MarginT"), + self._build.getFloat("format.h4MarginB"), + ) + bldObj.setTextMargins( + self._build.getFloat("format.textMarginT"), + self._build.getFloat("format.textMarginB"), + ) + bldObj.setSeparatorMargins( + self._build.getFloat("format.sepMarginT"), + self._build.getFloat("format.sepMarginB"), + ) + bldObj.setBodyText(self._build.getBool("text.includeBodyText")) bldObj.setSynopsis(self._build.getBool("text.includeSynopsis")) bldObj.setComments(self._build.getBool("text.includeComments")) diff --git a/novelwriter/core/index.py b/novelwriter/core/index.py index 7c40e120..089d4f27 100644 --- a/novelwriter/core/index.py +++ b/novelwriter/core/index.py @@ -40,7 +40,7 @@ from novelwriter import SHARED from novelwriter.common import ( checkInt, isHandle, isItemClass, isListInstance, isTitleTag, jsonEncode ) -from novelwriter.constants import nwFiles, nwHeaders, nwKeyWords +from novelwriter.constants import nwFiles, nwKeyWords, nwStyles from novelwriter.enum import nwComment, nwItemClass, nwItemLayout, nwItemType from novelwriter.error import logException from novelwriter.text.counting import standardCounter @@ -569,7 +569,7 @@ class NWIndex: for _, _, hItem in self._itemIndex.iterNovelStructure( rHandle=rootHandle, activeOnly=activeOnly ): - iLevel = nwHeaders.H_LEVEL.get(hItem.level, 0) + iLevel = nwStyles.H_LEVEL.get(hItem.level, 0) hCount[iLevel] += 1 return hCount @@ -591,7 +591,7 @@ class NWIndex: rHandle=rHandle, activeOnly=activeOnly ): tKey = f"{tHandle}:{sTitle}" - iLevel = nwHeaders.H_LEVEL.get(hItem.level, 0) + iLevel = nwStyles.H_LEVEL.get(hItem.level, 0) if iLevel > maxDepth: if pKey in tData: tData[pKey]["words"] += hItem.wordCount @@ -1251,7 +1251,7 @@ class IndexHeading: def setLevel(self, level: str) -> None: """Set the level of the heading if it's a valid value.""" - if level in nwHeaders.H_VALID: + if level in nwStyles.H_VALID: self._level = level return diff --git a/novelwriter/core/item.py b/novelwriter/core/item.py index 7d666bd6..c89af5ee 100644 --- a/novelwriter/core/item.py +++ b/novelwriter/core/item.py @@ -33,7 +33,7 @@ from novelwriter.common import ( checkInt, isHandle, isItemClass, isItemLayout, isItemType, simplified, yesNo ) -from novelwriter.constants import nwHeaders, nwLabels, trConst +from novelwriter.constants import nwLabels, nwStyles, trConst from novelwriter.enum import nwItemClass, nwItemLayout, nwItemType if TYPE_CHECKING: # pragma: no cover @@ -518,7 +518,7 @@ class NWItem: def setMainHeading(self, value: str) -> None: """Set the main heading level.""" - if value in nwHeaders.H_LEVEL: + if value in nwStyles.H_LEVEL: self._heading = value return diff --git a/novelwriter/extensions/configlayout.py b/novelwriter/extensions/configlayout.py index 637d90c1..a34b7cde 100644 --- a/novelwriter/extensions/configlayout.py +++ b/novelwriter/extensions/configlayout.py @@ -180,9 +180,16 @@ class NScrollableForm(QScrollArea): self._sections[identifier] = qLabel return - def addRow(self, label: str, widget: QWidget | list[QWidget], helpText: str = "", - unit: str | None = None, button: QWidget | None = None, editable: str | None = None, - stretch: tuple[int, int] = (1, 0)) -> None: + def addRow( + self, + label: str | None, + widget: QWidget | list[QWidget | str | int], + helpText: str = "", + unit: str | None = None, + button: QWidget | None = None, + editable: str | None = None, + stretch: tuple[int, int] = (1, 0), + ) -> None: """Add a label and a widget as a new row of the form.""" row = QHBoxLayout() row.setSpacing(CONFIG.pxInt(12)) @@ -191,13 +198,18 @@ class NScrollableForm(QScrollArea): wBox = QHBoxLayout() wBox.setContentsMargins(0, 0, 0, 0) for item in widget: - wBox.addWidget(item) + if isinstance(item, QWidget): + wBox.addWidget(item) + elif isinstance(item, str): + wBox.addWidget(QLabel(item, self)) + elif isinstance(item, int): + wBox.addSpacing(CONFIG.pxInt(item)) qWidget = QWidget(self) qWidget.setLayout(wBox) else: qWidget = widget - qLabel = QLabel(label, self) + qLabel = QLabel(label or "", self) qLabel.setIndent(self._indent) qLabel.setBuddy(qWidget) @@ -230,7 +242,8 @@ class NScrollableForm(QScrollArea): row.addWidget(qWidget, stretch[1]) self._layout.addLayout(row) - self._index[label.strip()] = qWidget + if label: + self._index[label.strip()] = qWidget self._first = False return diff --git a/novelwriter/extensions/modified.py b/novelwriter/extensions/modified.py index f07e2553..f9062b39 100644 --- a/novelwriter/extensions/modified.py +++ b/novelwriter/extensions/modified.py @@ -136,9 +136,21 @@ class NSpinBox(QSpinBox): class NDoubleSpinBox(QDoubleSpinBox): - def __init__(self, parent: QWidget | None = None) -> None: + def __init__( + self, + parent: QWidget | None = None, + *, + min: float = 0.0, + max: float = 15.0, + step: float = 0.1, + prec: int = 2, + ) -> None: super().__init__(parent=parent) self.setFocusPolicy(Qt.FocusPolicy.StrongFocus) + self.setMinimum(min) + self.setMaximum(max) + self.setSingleStep(step) + self.setDecimals(prec) return def wheelEvent(self, event: QWheelEvent) -> None: diff --git a/novelwriter/formats/tokenizer.py b/novelwriter/formats/tokenizer.py index a2db6cce..e2c5817f 100644 --- a/novelwriter/formats/tokenizer.py +++ b/novelwriter/formats/tokenizer.py @@ -38,7 +38,9 @@ from PyQt5.QtGui import QFont from novelwriter import CONFIG from novelwriter.common import checkInt, formatTimeStamp, numberToRoman -from novelwriter.constants import nwHeadFmt, nwKeyWords, nwLabels, nwShortcode, nwUnicode, trConst +from novelwriter.constants import ( + nwHeadFmt, nwKeyWords, nwLabels, nwShortcode, nwStyles, nwUnicode, trConst +) from novelwriter.core.index import processComment from novelwriter.core.project import NWProject from novelwriter.enum import nwComment, nwItemLayout @@ -167,15 +169,15 @@ class Tokenizer(ABC): self._keepBreaks = True # Keep line breaks in paragraphs # Margins - self._marginTitle = (1.417, 0.500) - self._marginHead1 = (1.417, 0.500) - self._marginHead2 = (1.668, 0.500) - self._marginHead3 = (1.168, 0.500) - self._marginHead4 = (1.168, 0.500) - self._marginText = (0.000, 0.584) - self._marginMeta = (0.000, 0.584) - self._marginFoot = (1.417, 0.467) - self._marginSep = (1.168, 1.168) + self._marginTitle = nwStyles.T_MARGIN["H0"] + self._marginHead1 = nwStyles.T_MARGIN["H1"] + self._marginHead2 = nwStyles.T_MARGIN["H2"] + self._marginHead3 = nwStyles.T_MARGIN["H3"] + self._marginHead4 = nwStyles.T_MARGIN["H4"] + self._marginText = nwStyles.T_MARGIN["TT"] + self._marginMeta = nwStyles.T_MARGIN["MT"] + self._marginFoot = nwStyles.T_MARGIN["FT"] + self._marginSep = nwStyles.T_MARGIN["SP"] # Title Formats self._fmtPart = nwHeadFmt.TITLE # Formatting for partitions diff --git a/novelwriter/formats/toodt.py b/novelwriter/formats/toodt.py index bfb9d80e..960b48aa 100644 --- a/novelwriter/formats/toodt.py +++ b/novelwriter/formats/toodt.py @@ -39,7 +39,7 @@ from PyQt5.QtGui import QFont from novelwriter import __version__ from novelwriter.common import xmlIndent -from novelwriter.constants import nwHeadFmt, nwKeyWords, nwLabels +from novelwriter.constants import nwHeadFmt, nwKeyWords, nwLabels, nwStyles from novelwriter.core.project import NWProject from novelwriter.formats.tokenizer import T_Formats, Tokenizer, stripEscape from novelwriter.types import FONT_STYLE, FONT_WEIGHTS @@ -285,14 +285,14 @@ class ToOdt(Tokenizer): self._headWeight = self._fontBold if self._boldHeads else None hScale = self._scaleHeads - self._fSizeTitle = f"{round((2.50 if hScale else 1.0) * self._fontSize):d}pt" - self._fSizeHead1 = f"{round((2.00 if hScale else 1.0) * self._fontSize):d}pt" - self._fSizeHead2 = f"{round((1.60 if hScale else 1.0) * self._fontSize):d}pt" - self._fSizeHead3 = f"{round((1.30 if hScale else 1.0) * self._fontSize):d}pt" - self._fSizeHead4 = f"{round((1.15 if hScale else 1.0) * self._fontSize):d}pt" - self._fSizeHead = f"{round((1.15 if hScale else 1.0) * self._fontSize):d}pt" - self._fSizeText = f"{self._fontSize:d}pt" - self._fSizeFoot = f"{round(0.8*self._fontSize):d}pt" + self._fSizeTitle = self._emToPt(nwStyles.H_SIZES[0] if hScale else 1.0) # Was 2.50 + self._fSizeHead1 = self._emToPt(nwStyles.H_SIZES[1] if hScale else 1.0) # Was 2.00 + self._fSizeHead2 = self._emToPt(nwStyles.H_SIZES[2] if hScale else 1.0) # Was 1.60 + self._fSizeHead3 = self._emToPt(nwStyles.H_SIZES[3] if hScale else 1.0) # Was 1.30 + self._fSizeHead4 = self._emToPt(nwStyles.H_SIZES[4] if hScale else 1.0) # Was 1.15 + self._fSizeHead = self._emToPt(nwStyles.H_SIZES[4] if hScale else 1.0) # Was 1.15 + self._fSizeText = self._emToPt(1.0) + self._fSizeFoot = self._emToPt(0.8) mScale = self._lineHeight/1.15 @@ -805,6 +805,10 @@ class ToOdt(Tokenizer): """Converts an em value to centimetres.""" return f"{value*2.54/72*self._fontSize:.3f}cm" + def _emToPt(self, scale: float) -> str: + """Compute relative font size in points.""" + return f"{round(scale * self._fontSize):d}pt" + ## # Style Elements ## diff --git a/novelwriter/formats/toqdoc.py b/novelwriter/formats/toqdoc.py index 03dea01b..627b4722 100644 --- a/novelwriter/formats/toqdoc.py +++ b/novelwriter/formats/toqdoc.py @@ -34,7 +34,7 @@ from PyQt5.QtGui import ( ) from PyQt5.QtPrintSupport import QPrinter -from novelwriter.constants import nwHeaders, nwHeadFmt, nwKeyWords, nwLabels, nwUnicode +from novelwriter.constants import nwHeadFmt, nwKeyWords, nwLabels, nwStyles, nwUnicode from novelwriter.core.project import NWProject from novelwriter.formats.tokenizer import T_Formats, Tokenizer from novelwriter.types import ( @@ -150,11 +150,11 @@ class ToQTextDocument(Tokenizer): hScale = self._scaleHeads self._sHead = { - self.T_TITLE: (nwHeaders.H_SIZES.get(0, 1.0) * fPt) if hScale else fPt, - self.T_HEAD1: (nwHeaders.H_SIZES.get(1, 1.0) * fPt) if hScale else fPt, - self.T_HEAD2: (nwHeaders.H_SIZES.get(2, 1.0) * fPt) if hScale else fPt, - self.T_HEAD3: (nwHeaders.H_SIZES.get(3, 1.0) * fPt) if hScale else fPt, - self.T_HEAD4: (nwHeaders.H_SIZES.get(4, 1.0) * fPt) if hScale else fPt, + self.T_TITLE: (nwStyles.H_SIZES.get(0, 1.0) * fPt) if hScale else fPt, + self.T_HEAD1: (nwStyles.H_SIZES.get(1, 1.0) * fPt) if hScale else fPt, + self.T_HEAD2: (nwStyles.H_SIZES.get(2, 1.0) * fPt) if hScale else fPt, + self.T_HEAD3: (nwStyles.H_SIZES.get(3, 1.0) * fPt) if hScale else fPt, + self.T_HEAD4: (nwStyles.H_SIZES.get(4, 1.0) * fPt) if hScale else fPt, } self._mText = (mPx * self._marginText[0], mPx * self._marginText[1]) diff --git a/novelwriter/gui/dochighlight.py b/novelwriter/gui/dochighlight.py index 318d610b..ac222dd4 100644 --- a/novelwriter/gui/dochighlight.py +++ b/novelwriter/gui/dochighlight.py @@ -37,7 +37,7 @@ from PyQt5.QtGui import ( from novelwriter import CONFIG, SHARED from novelwriter.common import checkInt -from novelwriter.constants import nwHeaders, nwUnicode +from novelwriter.constants import nwStyles, nwUnicode from novelwriter.core.index import processComment from novelwriter.enum import nwComment from novelwriter.text.patterns import REGEX_PATTERNS @@ -95,14 +95,14 @@ class GuiDocHighlighter(QSyntaxHighlighter): # Create Character Formats self._addCharFormat("text", SHARED.theme.colText) - self._addCharFormat("header1", SHARED.theme.colHead, "b", nwHeaders.H_SIZES[1]) - self._addCharFormat("header2", SHARED.theme.colHead, "b", nwHeaders.H_SIZES[2]) - self._addCharFormat("header3", SHARED.theme.colHead, "b", nwHeaders.H_SIZES[3]) - self._addCharFormat("header4", SHARED.theme.colHead, "b", nwHeaders.H_SIZES[4]) - self._addCharFormat("head1h", SHARED.theme.colHeadH, "b", nwHeaders.H_SIZES[1]) - self._addCharFormat("head2h", SHARED.theme.colHeadH, "b", nwHeaders.H_SIZES[2]) - self._addCharFormat("head3h", SHARED.theme.colHeadH, "b", nwHeaders.H_SIZES[3]) - self._addCharFormat("head4h", SHARED.theme.colHeadH, "b", nwHeaders.H_SIZES[4]) + self._addCharFormat("header1", SHARED.theme.colHead, "b", nwStyles.H_SIZES[1]) + self._addCharFormat("header2", SHARED.theme.colHead, "b", nwStyles.H_SIZES[2]) + self._addCharFormat("header3", SHARED.theme.colHead, "b", nwStyles.H_SIZES[3]) + self._addCharFormat("header4", SHARED.theme.colHead, "b", nwStyles.H_SIZES[4]) + self._addCharFormat("head1h", SHARED.theme.colHeadH, "b", nwStyles.H_SIZES[1]) + self._addCharFormat("head2h", SHARED.theme.colHeadH, "b", nwStyles.H_SIZES[2]) + self._addCharFormat("head3h", SHARED.theme.colHeadH, "b", nwStyles.H_SIZES[3]) + self._addCharFormat("head4h", SHARED.theme.colHeadH, "b", nwStyles.H_SIZES[4]) self._addCharFormat("bold", colEmph, "b") self._addCharFormat("italic", colEmph, "i") self._addCharFormat("strike", SHARED.theme.colHidden, "s") diff --git a/novelwriter/gui/docviewer.py b/novelwriter/gui/docviewer.py index 70acd21a..c2a75479 100644 --- a/novelwriter/gui/docviewer.py +++ b/novelwriter/gui/docviewer.py @@ -38,7 +38,7 @@ from PyQt5.QtWidgets import ( ) from novelwriter import CONFIG, SHARED -from novelwriter.constants import nwHeaders, nwUnicode +from novelwriter.constants import nwStyles, nwUnicode from novelwriter.enum import nwDocAction, nwDocMode, nwItemType from novelwriter.error import logException from novelwriter.extensions.configlayout import NColourLabel @@ -250,7 +250,7 @@ class GuiDocViewer(QTextBrowser): SHARED.project.data.setLastHandle(tHandle, "viewer") self.docHeader.setHandle(tHandle) self.docHeader.setOutline({ - sTitle: (hItem.title, nwHeaders.H_LEVEL.get(hItem.level, 0)) + sTitle: (hItem.title, nwStyles.H_LEVEL.get(hItem.level, 0)) for sTitle, hItem in SHARED.project.index.iterItemHeadings(tHandle) }) self.updateDocMargins() diff --git a/novelwriter/gui/docviewerpanel.py b/novelwriter/gui/docviewerpanel.py index be9ad287..443ba678 100644 --- a/novelwriter/gui/docviewerpanel.py +++ b/novelwriter/gui/docviewerpanel.py @@ -35,7 +35,7 @@ from PyQt5.QtWidgets import ( from novelwriter import CONFIG, SHARED from novelwriter.common import checkInt -from novelwriter.constants import nwHeaders, nwLabels, nwLists, trConst +from novelwriter.constants import nwLabels, nwLists, nwStyles, trConst from novelwriter.core.index import IndexHeading, IndexItem from novelwriter.enum import nwDocMode, nwItemClass from novelwriter.extensions.modified import NIconToolButton @@ -343,7 +343,7 @@ class _ViewPanelBackRefs(QTreeWidget): nwItem.itemType, nwItem.itemClass, nwItem.itemLayout, nwItem.mainHeading ) - iLevel = nwHeaders.H_LEVEL.get(hItem.level, 0) if nwItem.isDocumentLayout() else 5 + iLevel = nwStyles.H_LEVEL.get(hItem.level, 0) if nwItem.isDocumentLayout() else 5 hDec = SHARED.theme.getHeaderDecorationNarrow(iLevel) tKey = f"{tHandle}:{sTitle}" @@ -453,7 +453,7 @@ class _ViewPanelKeyWords(QTreeWidget): nwItem.itemLayout, nwItem.mainHeading ) impLabel, impIcon = nwItem.getImportStatus() - iLevel = nwHeaders.H_LEVEL.get(hItem.level, 0) if nwItem.isDocumentLayout() else 5 + iLevel = nwStyles.H_LEVEL.get(hItem.level, 0) if nwItem.isDocumentLayout() else 5 hDec = SHARED.theme.getHeaderDecorationNarrow(iLevel) # This can not use a get call to the dictionary as that would create an diff --git a/novelwriter/gui/mainmenu.py b/novelwriter/gui/mainmenu.py index 893129d1..5205b21a 100644 --- a/novelwriter/gui/mainmenu.py +++ b/novelwriter/gui/mainmenu.py @@ -34,7 +34,7 @@ from PyQt5.QtWidgets import QAction, QMenuBar from novelwriter import CONFIG, SHARED from novelwriter.common import openExternalPath -from novelwriter.constants import nwConst, nwKeyWords, nwLabels, nwUnicode, trConst +from novelwriter.constants import nwConst, nwKeyWords, nwLabels, nwStyles, nwUnicode, trConst from novelwriter.enum import nwDocAction, nwDocInsert, nwFocus, nwView from novelwriter.extensions.eventfilters import StatusTipFilter @@ -700,28 +700,28 @@ class GuiMainMenu(QMenuBar): self.fmtMenu.addSeparator() # Format > Heading 1 (Partition) - self.aFmtHead1 = self.fmtMenu.addAction(self.tr("Heading 1 (Partition)")) + self.aFmtHead1 = self.fmtMenu.addAction(trConst(nwStyles.T_LABEL["H1"])) self.aFmtHead1.setShortcut("Ctrl+1") self.aFmtHead1.triggered.connect( lambda: self.requestDocAction.emit(nwDocAction.BLOCK_H1) ) # Format > Heading 2 (Chapter) - self.aFmtHead2 = self.fmtMenu.addAction(self.tr("Heading 2 (Chapter)")) + self.aFmtHead2 = self.fmtMenu.addAction(trConst(nwStyles.T_LABEL["H2"])) self.aFmtHead2.setShortcut("Ctrl+2") self.aFmtHead2.triggered.connect( lambda: self.requestDocAction.emit(nwDocAction.BLOCK_H2) ) # Format > Heading 3 (Scene) - self.aFmtHead3 = self.fmtMenu.addAction(self.tr("Heading 3 (Scene)")) + self.aFmtHead3 = self.fmtMenu.addAction(trConst(nwStyles.T_LABEL["H3"])) self.aFmtHead3.setShortcut("Ctrl+3") self.aFmtHead3.triggered.connect( lambda: self.requestDocAction.emit(nwDocAction.BLOCK_H3) ) # Format > Heading 4 (Section) - self.aFmtHead4 = self.fmtMenu.addAction(self.tr("Heading 4 (Section)")) + self.aFmtHead4 = self.fmtMenu.addAction(trConst(nwStyles.T_LABEL["H4"])) self.aFmtHead4.setShortcut("Ctrl+4") self.aFmtHead4.triggered.connect( lambda: self.requestDocAction.emit(nwDocAction.BLOCK_H4) diff --git a/novelwriter/gui/noveltree.py b/novelwriter/gui/noveltree.py index 98c7886f..5b33a5fb 100644 --- a/novelwriter/gui/noveltree.py +++ b/novelwriter/gui/noveltree.py @@ -40,7 +40,7 @@ from PyQt5.QtWidgets import ( from novelwriter import CONFIG, SHARED from novelwriter.common import minmax -from novelwriter.constants import nwHeaders, nwKeyWords, nwLabels, trConst +from novelwriter.constants import nwKeyWords, nwLabels, nwStyles, trConst from novelwriter.core.index import IndexHeading from novelwriter.enum import nwDocMode, nwItemClass, nwOutline from novelwriter.extensions.modified import NIconToolButton @@ -683,7 +683,7 @@ class GuiNovelTree(QTreeWidget): def _updateTreeItemValues(self, trItem: QTreeWidgetItem, idxItem: IndexHeading, tHandle: str, sTitle: str) -> None: """Set the tree item values from the index entry.""" - iLevel = nwHeaders.H_LEVEL.get(idxItem.level, 0) + iLevel = nwStyles.H_LEVEL.get(idxItem.level, 0) hDec = SHARED.theme.getHeaderDecoration(iLevel) trItem.setData(self.C_TITLE, QtDecoration, hDec) diff --git a/novelwriter/gui/outline.py b/novelwriter/gui/outline.py index 4affb0fd..fc9b9d48 100644 --- a/novelwriter/gui/outline.py +++ b/novelwriter/gui/outline.py @@ -42,7 +42,7 @@ from PyQt5.QtWidgets import ( from novelwriter import CONFIG, SHARED from novelwriter.common import checkInt, formatFileFilter, makeFileNameSafe -from novelwriter.constants import nwHeaders, nwKeyWords, nwLabels, trConst +from novelwriter.constants import nwKeyWords, nwLabels, nwStyles, trConst from novelwriter.enum import nwDocMode, nwItemClass, nwItemLayout, nwItemType, nwOutline from novelwriter.error import logException from novelwriter.extensions.configlayout import NColourLabel @@ -681,7 +681,7 @@ class GuiOutlineTree(QTreeWidget): novStruct = SHARED.project.index.novelStructure(rootHandle=rootHandle, activeOnly=True) for _, tHandle, sTitle, novIdx in novStruct: - iLevel = nwHeaders.H_LEVEL.get(novIdx.level, 0) + iLevel = nwStyles.H_LEVEL.get(novIdx.level, 0) nwItem = SHARED.project.tree[tHandle] if iLevel == 0 or nwItem is None: continue diff --git a/novelwriter/gui/projtree.py b/novelwriter/gui/projtree.py index b2952be4..9863fb83 100644 --- a/novelwriter/gui/projtree.py +++ b/novelwriter/gui/projtree.py @@ -40,7 +40,7 @@ from PyQt5.QtWidgets import ( from novelwriter import CONFIG, SHARED from novelwriter.common import minmax -from novelwriter.constants import nwHeaders, nwLabels, nwUnicode, trConst +from novelwriter.constants import nwLabels, nwStyles, nwUnicode, trConst from novelwriter.core.coretools import DocDuplicator, DocMerger, DocSplitter from novelwriter.core.item import NWItem from novelwriter.dialogs.docmerge import GuiDocMerge @@ -642,7 +642,7 @@ class GuiProjectTree(QTreeWidget): # Collect some information about the selected item qItem = self._getTreeItem(sHandle) - sLevel = nwHeaders.H_LEVEL.get(pItem.mainHeading, 0) + sLevel = nwStyles.H_LEVEL.get(pItem.mainHeading, 0) sIsParent = False if qItem is None else qItem.childCount() > 0 if SHARED.project.tree.isTrash(sHandle): diff --git a/novelwriter/tools/manussettings.py b/novelwriter/tools/manussettings.py index c0168301..284371c4 100644 --- a/novelwriter/tools/manussettings.py +++ b/novelwriter/tools/manussettings.py @@ -38,7 +38,7 @@ from PyQt5.QtWidgets import ( from novelwriter import CONFIG, SHARED from novelwriter.common import describeFont -from novelwriter.constants import nwHeadFmt, nwKeyWords, nwLabels, trConst +from novelwriter.constants import nwHeadFmt, nwKeyWords, nwLabels, nwStyles, trConst from novelwriter.core.buildsettings import BuildSettings, FilterMode from novelwriter.extensions.configlayout import ( NColourLabel, NFixedPage, NScrollableForm, NScrollablePage @@ -964,6 +964,15 @@ class _FormattingTab(NScrollableForm): spW = 6*SHARED.theme.textNWidth dbW = 8*SHARED.theme.textNWidth + # Common Translations + + trW = self.tr("W:") # Width + trH = self.tr("H:") # Height + trT = self.tr("T:") # Top + trB = self.tr("B:") # Bottom + trL = self.tr("L:") # Left + trR = self.tr("R:") # Right + # Text Content # ============ @@ -1067,6 +1076,98 @@ class _FormattingTab(NScrollableForm): self.addRow(self._build.getLabel("format.firstIndentWidth"), self.indentWidth, unit="em") self.addRow(self._build.getLabel("format.indentFirstPar"), self.indentFirstPar) + # Text Margins + # ============ + + title = self._build.getLabel("format.grpMargins") + section += 1 + self._sidebar.addButton(title, section) + self.addGroupLabel(title, section) + + # Title + self.titleMarginT = NDoubleSpinBox(self) + self.titleMarginT.setFixedWidth(dbW) + + self.titleMarginB = NDoubleSpinBox(self) + self.titleMarginB.setFixedWidth(dbW) + + self.addRow( + trConst(nwStyles.T_LABEL["H0"]), + [trT, self.titleMarginT, 6, trB, self.titleMarginB], + ) + + # Heading 1 + self.h1MarginT = NDoubleSpinBox(self) + self.h1MarginT.setFixedWidth(dbW) + + self.h1MarginB = NDoubleSpinBox(self) + self.h1MarginB.setFixedWidth(dbW) + + self.addRow( + trConst(nwStyles.T_LABEL["H1"]), + [trT, self.h1MarginT, 6, trB, self.h1MarginB], + ) + + # Heading 2 + self.h2MarginT = NDoubleSpinBox(self) + self.h2MarginT.setFixedWidth(dbW) + + self.h2MarginB = NDoubleSpinBox(self) + self.h2MarginB.setFixedWidth(dbW) + + self.addRow( + trConst(nwStyles.T_LABEL["H2"]), + [trT, self.h2MarginT, 6, trB, self.h2MarginB], + ) + + # Heading 3 + self.h3MarginT = NDoubleSpinBox(self) + self.h3MarginT.setFixedWidth(dbW) + + self.h3MarginB = NDoubleSpinBox(self) + self.h3MarginB.setFixedWidth(dbW) + + self.addRow( + trConst(nwStyles.T_LABEL["H3"]), + [trT, self.h3MarginT, 6, trB, self.h3MarginB], + ) + + # Heading 4 + self.h4MarginT = NDoubleSpinBox(self) + self.h4MarginT.setFixedWidth(dbW) + + self.h4MarginB = NDoubleSpinBox(self) + self.h4MarginB.setFixedWidth(dbW) + + self.addRow( + trConst(nwStyles.T_LABEL["H4"]), + [trT, self.h4MarginT, 6, trB, self.h4MarginB], + ) + + # Text + self.textMarginT = NDoubleSpinBox(self) + self.textMarginT.setFixedWidth(dbW) + + self.textMarginB = NDoubleSpinBox(self) + self.textMarginB.setFixedWidth(dbW) + + self.addRow( + trConst(nwStyles.T_LABEL["TT"]), + [trT, self.textMarginT, 6, trB, self.textMarginB], + ) + + # Separator + self.sepMarginT = NDoubleSpinBox(self) + self.sepMarginT.setFixedWidth(dbW) + + self.sepMarginB = NDoubleSpinBox(self) + self.sepMarginB.setFixedWidth(dbW) + + self.addRow( + trConst(nwStyles.T_LABEL["SP"]), + [trT, self.sepMarginT, 6, trB, self.sepMarginB], + ) + # Page Layout # =========== @@ -1075,24 +1176,32 @@ class _FormattingTab(NScrollableForm): self._sidebar.addButton(title, section) self.addGroupLabel(title, section) + # Unit self.pageUnit = NComboBox(self) for key, name in nwLabels.UNIT_NAME.items(): self.pageUnit.addItem(trConst(name), key) + self.addRow(self._build.getLabel("format.pageUnit"), self.pageUnit) + + # Page Size self.pageSize = NComboBox(self) for key, name in nwLabels.PAPER_NAME.items(): self.pageSize.addItem(trConst(name), key) - self.pageWidth = NDoubleSpinBox(self) + self.pageWidth = NDoubleSpinBox(self, max=500.0) self.pageWidth.setFixedWidth(dbW) - self.pageWidth.setMaximum(500.0) self.pageWidth.valueChanged.connect(self._pageSizeValueChanged) - self.pageHeight = NDoubleSpinBox(self) + self.pageHeight = NDoubleSpinBox(self, max=500.0) self.pageHeight.setFixedWidth(dbW) - self.pageHeight.setMaximum(500.0) self.pageHeight.valueChanged.connect(self._pageSizeValueChanged) + self.addRow( + self._build.getLabel("format.pageSize"), + [self.pageSize, 6, trW, self.pageWidth, 6, trH, self.pageHeight], + ) + + # Page Margins self.topMargin = NDoubleSpinBox(self) self.topMargin.setFixedWidth(dbW) @@ -1105,14 +1214,14 @@ class _FormattingTab(NScrollableForm): self.rightMargin = NDoubleSpinBox(self) self.rightMargin.setFixedWidth(dbW) - self.addRow(self._build.getLabel("format.pageUnit"), self.pageUnit) - self.addRow(self._build.getLabel("format.pageSize"), self.pageSize) - self.addRow(self._build.getLabel("format.pageWidth"), self.pageWidth) - self.addRow(self._build.getLabel("format.pageHeight"), self.pageHeight) - self.addRow(self._build.getLabel("format.topMargin"), self.topMargin) - self.addRow(self._build.getLabel("format.bottomMargin"), self.bottomMargin) - self.addRow(self._build.getLabel("format.leftMargin"), self.leftMargin) - self.addRow(self._build.getLabel("format.rightMargin"), self.rightMargin) + self.addRow( + self._build.getLabel("format.pageMargins"), + [trT, self.topMargin, 6, trB, self.bottomMargin], + ) + self.addRow( + "", + [trL, self.leftMargin, 6, trR, self.rightMargin], + ) # Open Document # ============= @@ -1203,6 +1312,24 @@ class _FormattingTab(NScrollableForm): self.indentWidth.setValue(self._build.getFloat("format.firstIndentWidth")) self.indentFirstPar.setChecked(self._build.getBool("format.indentFirstPar")) + # Text Margins + # ============ + + self.titleMarginT.setValue(self._build.getFloat("format.titleMarginT")) + self.titleMarginB.setValue(self._build.getFloat("format.titleMarginB")) + self.h1MarginT.setValue(self._build.getFloat("format.h1MarginT")) + self.h1MarginB.setValue(self._build.getFloat("format.h1MarginB")) + self.h2MarginT.setValue(self._build.getFloat("format.h2MarginT")) + self.h2MarginB.setValue(self._build.getFloat("format.h2MarginB")) + self.h3MarginT.setValue(self._build.getFloat("format.h3MarginT")) + self.h3MarginB.setValue(self._build.getFloat("format.h3MarginB")) + self.h4MarginT.setValue(self._build.getFloat("format.h4MarginT")) + self.h4MarginB.setValue(self._build.getFloat("format.h4MarginB")) + self.textMarginT.setValue(self._build.getFloat("format.textMarginT")) + self.textMarginB.setValue(self._build.getFloat("format.textMarginB")) + self.sepMarginT.setValue(self._build.getFloat("format.sepMarginT")) + self.sepMarginB.setValue(self._build.getFloat("format.sepMarginB")) + # Page Layout # =========== @@ -1273,6 +1400,22 @@ class _FormattingTab(NScrollableForm): self._build.setValue("format.firstIndentWidth", self.indentWidth.value()) self._build.setValue("format.indentFirstPar", self.indentFirstPar.isChecked()) + # Text Margins + self._build.setValue("format.titleMarginT", self.titleMarginT.value()) + self._build.setValue("format.titleMarginB", self.titleMarginB.value()) + self._build.setValue("format.h1MarginT", self.h1MarginT.value()) + self._build.setValue("format.h1MarginB", self.h1MarginB.value()) + self._build.setValue("format.h2MarginT", self.h2MarginT.value()) + self._build.setValue("format.h2MarginB", self.h2MarginB.value()) + self._build.setValue("format.h3MarginT", self.h3MarginT.value()) + self._build.setValue("format.h3MarginB", self.h3MarginB.value()) + self._build.setValue("format.h4MarginT", self.h4MarginT.value()) + self._build.setValue("format.h4MarginB", self.h4MarginB.value()) + self._build.setValue("format.textMarginT", self.textMarginT.value()) + self._build.setValue("format.textMarginB", self.textMarginB.value()) + self._build.setValue("format.sepMarginT", self.sepMarginT.value()) + self._build.setValue("format.sepMarginB", self.sepMarginB.value()) + # Page Layout self._build.setValue("format.pageUnit", str(self.pageUnit.currentData())) self._build.setValue("format.pageSize", str(self.pageSize.currentData())) diff --git a/tests/reference/coreToOdt_SaveFlat_document.fodt b/tests/reference/coreToOdt_SaveFlat_document.fodt index 53b1beb6..14c9e9a4 100644 --- a/tests/reference/coreToOdt_SaveFlat_document.fodt +++ b/tests/reference/coreToOdt_SaveFlat_document.fodt @@ -1,13 +1,13 @@ - 2024-05-24T21:31:13 - novelWriter/2.5a4 + 2024-10-17T22:22:08 + novelWriter/2.6a1 Jane Smith 1234 P42DT12H34M56S Test Project - 2024-05-24T21:31:13 + 2024-10-17T22:22:08 Jane Smith @@ -22,50 +22,50 @@ - - + + - + - + - + - + - + - - + + - - + + - - + + - + diff --git a/tests/reference/coreToOdt_SaveFull_styles.xml b/tests/reference/coreToOdt_SaveFull_styles.xml index 8130cbae..708be359 100644 --- a/tests/reference/coreToOdt_SaveFull_styles.xml +++ b/tests/reference/coreToOdt_SaveFull_styles.xml @@ -12,50 +12,50 @@ - - + + - + - + - + - + - + - - + + - - + + - - + + - + diff --git a/tests/reference/mBuildDocBuild_HTML5_Lorem_Ipsum.htm b/tests/reference/mBuildDocBuild_HTML5_Lorem_Ipsum.htm index 992581d7..565158a1 100644 --- a/tests/reference/mBuildDocBuild_HTML5_Lorem_Ipsum.htm +++ b/tests/reference/mBuildDocBuild_HTML5_Lorem_Ipsum.htm @@ -9,8 +9,8 @@ body {font-family: 'Arial'; font-size: 12pt; font-weight: 400; font-style: norma p {text-align: justify; line-height: 150%; margin-top: 0.00em; margin-bottom: 0.76em;} h1 {color: rgb(66, 113, 174); page-break-after: avoid; margin-top: 1.85em; margin-bottom: 0.65em;} h2 {color: rgb(66, 113, 174); page-break-after: avoid; margin-top: 2.18em; margin-bottom: 0.65em;} -h3 {color: rgb(50, 50, 50); page-break-after: avoid; margin-top: 1.52em; margin-bottom: 0.65em;} -h4 {color: rgb(50, 50, 50); page-break-after: avoid; margin-top: 1.52em; margin-bottom: 0.65em;} +h3 {color: rgb(50, 50, 50); page-break-after: avoid; margin-top: 1.53em; margin-bottom: 0.65em;} +h4 {color: rgb(50, 50, 50); page-break-after: avoid; margin-top: 1.53em; margin-bottom: 0.65em;} .title {font-size: 2.5em; margin-top: 1.85em; margin-bottom: 0.65em;} .sep, .skip {text-align: center; margin-top: 1.30em; margin-bottom: 1.30em;} a {color: rgb(66, 113, 174);} diff --git a/tests/reference/mBuildDocBuild_HTML5_Lorem_Ipsum.json b/tests/reference/mBuildDocBuild_HTML5_Lorem_Ipsum.json index d8fcd492..55edb942 100644 --- a/tests/reference/mBuildDocBuild_HTML5_Lorem_Ipsum.json +++ b/tests/reference/mBuildDocBuild_HTML5_Lorem_Ipsum.json @@ -2,8 +2,8 @@ "meta": { "projectName": "Lorem Ipsum", "novelAuthor": "lipsum.com", - "buildTime": 1727777646, - "buildTimeStr": "2024-10-01 12:14:06" + "buildTime": 1729195330, + "buildTimeStr": "2024-10-17 22:02:10" }, "text": { "css": [ @@ -11,8 +11,8 @@ "p {text-align: justify; line-height: 150%; margin-top: 0.00em; margin-bottom: 0.76em;}", "h1 {color: rgb(66, 113, 174); page-break-after: avoid; margin-top: 1.85em; margin-bottom: 0.65em;}", "h2 {color: rgb(66, 113, 174); page-break-after: avoid; margin-top: 2.18em; margin-bottom: 0.65em;}", - "h3 {color: rgb(50, 50, 50); page-break-after: avoid; margin-top: 1.52em; margin-bottom: 0.65em;}", - "h4 {color: rgb(50, 50, 50); page-break-after: avoid; margin-top: 1.52em; margin-bottom: 0.65em;}", + "h3 {color: rgb(50, 50, 50); page-break-after: avoid; margin-top: 1.53em; margin-bottom: 0.65em;}", + "h4 {color: rgb(50, 50, 50); page-break-after: avoid; margin-top: 1.53em; margin-bottom: 0.65em;}", ".title {font-size: 2.5em; margin-top: 1.85em; margin-bottom: 0.65em;}", ".sep, .skip {text-align: center; margin-top: 1.30em; margin-bottom: 1.30em;}", "a {color: rgb(66, 113, 174);}", diff --git a/tests/reference/mBuildDocBuild_OpenDocument_Lorem_Ipsum.fodt b/tests/reference/mBuildDocBuild_OpenDocument_Lorem_Ipsum.fodt index 8608fbbc..11de5303 100644 --- a/tests/reference/mBuildDocBuild_OpenDocument_Lorem_Ipsum.fodt +++ b/tests/reference/mBuildDocBuild_OpenDocument_Lorem_Ipsum.fodt @@ -1,13 +1,13 @@ - 2024-10-01T12:11:50 - novelWriter/2.6a0 + 2024-10-17T22:22:05 + novelWriter/2.6a1 lipsum.com 45 P0DT0H36M8S Lorem Ipsum - 2024-10-01T12:11:50 + 2024-10-17T22:22:05 lipsum.com @@ -22,50 +22,50 @@ - - + + - + - + - + - + - + - - + + - - + + - - + + - + diff --git a/tests/test_formats/test_fmt_tokenizer.py b/tests/test_formats/test_fmt_tokenizer.py index 8cbcdaf0..22491831 100644 --- a/tests/test_formats/test_fmt_tokenizer.py +++ b/tests/test_formats/test_fmt_tokenizer.py @@ -27,7 +27,7 @@ import pytest from PyQt5.QtGui import QFont from novelwriter import CONFIG -from novelwriter.constants import nwHeadFmt +from novelwriter.constants import nwHeadFmt, nwStyles from novelwriter.core.project import NWProject from novelwriter.formats.tokenizer import HeadingFormatter, Tokenizer, stripEscape from novelwriter.formats.tomarkdown import ToMarkdown @@ -74,14 +74,14 @@ def testFmtToken_Setters(mockGUI): assert tokens._lineHeight == 1.15 assert tokens._blockIndent == 4.0 assert tokens._doJustify is False - assert tokens._marginTitle == (1.417, 0.500) - assert tokens._marginHead1 == (1.417, 0.500) - assert tokens._marginHead2 == (1.668, 0.500) - assert tokens._marginHead3 == (1.168, 0.500) - assert tokens._marginHead4 == (1.168, 0.500) - assert tokens._marginText == (0.000, 0.584) - assert tokens._marginMeta == (0.000, 0.584) - assert tokens._marginSep == (1.168, 1.168) + assert tokens._marginTitle == nwStyles.T_MARGIN["H0"] + assert tokens._marginHead1 == nwStyles.T_MARGIN["H1"] + assert tokens._marginHead2 == nwStyles.T_MARGIN["H2"] + assert tokens._marginHead3 == nwStyles.T_MARGIN["H3"] + assert tokens._marginHead4 == nwStyles.T_MARGIN["H4"] + assert tokens._marginSep == nwStyles.T_MARGIN["SP"] + assert tokens._marginText == nwStyles.T_MARGIN["TT"] + assert tokens._marginMeta == nwStyles.T_MARGIN["MT"] assert tokens._hidePart is False assert tokens._hideChapter is False assert tokens._hideUnNum is False