diff --git a/novelwriter/__init__.py b/novelwriter/__init__.py
index 8d1c68f7..bd8fa337 100644
--- a/novelwriter/__init__.py
+++ b/novelwriter/__init__.py
@@ -58,9 +58,9 @@ __license__ = "GPLv3"
__author__ = "Veronica Berglyd Olsen"
__maintainer__ = "Veronica Berglyd Olsen"
__email__ = "code@vkbo.net"
-__version__ = "2.0.7"
-__hexversion__ = "0x020007f0"
-__date__ = "2023-04-16"
+__version__ = "2.1-beta1"
+__hexversion__ = "0x020100b1"
+__date__ = "2023-06-19"
__status__ = "Stable"
__domain__ = "novelwriter.io"
diff --git a/novelwriter/constants.py b/novelwriter/constants.py
index 2875a72b..60ed8af3 100644
--- a/novelwriter/constants.py
+++ b/novelwriter/constants.py
@@ -22,16 +22,16 @@ General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
"""
+from __future__ import annotations
from PyQt5.QtCore import QCoreApplication, QT_TRANSLATE_NOOP
from novelwriter.enum import nwBuildFmt, nwItemClass, nwItemLayout, nwOutline
-def trConst(tString):
- """Wrapper function for locally translating constants.
- """
- return QCoreApplication.translate("Constant", tString)
+def trConst(text: str) -> str:
+ """Wrapper function for locally translating constants."""
+ return QCoreApplication.translate("Constant", text)
class nwConst:
@@ -225,6 +225,32 @@ class nwLabels:
nwBuildFmt.J_HTML: ".json",
nwBuildFmt.J_NWD: ".json",
}
+ UNIT_NAME = {
+ "mm": QT_TRANSLATE_NOOP("Constant", "Millimetres"),
+ "cm": QT_TRANSLATE_NOOP("Constant", "Centimetres"),
+ "in": QT_TRANSLATE_NOOP("Constant", "Inches"),
+ }
+ UNIT_SCALE = {
+ "mm": 1.0,
+ "cm": 10.0,
+ "in": 25.4,
+ }
+ PAPER_NAME = {
+ "A4": QT_TRANSLATE_NOOP("Constant", "A4"),
+ "A5": QT_TRANSLATE_NOOP("Constant", "A5"),
+ "A6": QT_TRANSLATE_NOOP("Constant", "A6"),
+ "Legal": QT_TRANSLATE_NOOP("Constant", "US Legal"),
+ "Letter": QT_TRANSLATE_NOOP("Constant", "US Letter"),
+ "Custom": QT_TRANSLATE_NOOP("Constant", "Custom"),
+ }
+ PAPER_SIZE = {
+ "A4": (210.0, 297.0),
+ "A5": (148.0, 210.0),
+ "A6": (105.0, 148.0),
+ "Legal": (215.9, 355.6),
+ "Letter": (215.9, 279.4),
+ "Custom": (-1.0, -1.0),
+ }
# END Class nwLabels
diff --git a/novelwriter/core/buildsettings.py b/novelwriter/core/buildsettings.py
index cafa637d..cba442e8 100644
--- a/novelwriter/core/buildsettings.py
+++ b/novelwriter/core/buildsettings.py
@@ -71,6 +71,14 @@ SETTINGS_TEMPLATE = {
"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),
"html.addStyles": (bool, True),
}
@@ -99,7 +107,7 @@ SETTINGS_LABELS = {
"text.addNoteHeadings": QT_TRANSLATE_NOOP("Builds", "Add Titles for Notes"),
"format.grpFormat": QT_TRANSLATE_NOOP("Builds", "Text Format"),
- "format.buildLang": QT_TRANSLATE_NOOP("Builds", "Document Language"),
+ "format.buildLang": QT_TRANSLATE_NOOP("Builds", "Language"),
"format.textFont": QT_TRANSLATE_NOOP("Builds", "Font Family"),
"format.textSize": QT_TRANSLATE_NOOP("Builds", "Font Size"),
"format.lineHeight": QT_TRANSLATE_NOOP("Builds", "Line Height"),
@@ -107,6 +115,15 @@ SETTINGS_LABELS = {
"format.justifyText": QT_TRANSLATE_NOOP("Builds", "Justify Text Margins"),
"format.stripUnicode": QT_TRANSLATE_NOOP("Builds", "Replace Unicode Characters"),
"format.replaceTabs": QT_TRANSLATE_NOOP("Builds", "Replace Tabs with Spaces"),
+ "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"),
"odt": QT_TRANSLATE_NOOP("Builds", "Open Document"),
"odt.addColours": QT_TRANSLATE_NOOP("Builds", "Add Highlight Colours"),
@@ -160,7 +177,7 @@ class BuildSettings:
@property
def buildID(self) -> str:
- """The build ID as an UUID."""
+ """The build ID as a UUID."""
return self._uuid
@property
@@ -196,24 +213,24 @@ class BuildSettings:
def getStr(self, key: str) -> str:
"""Type safe value access for strings."""
- value = self._settings.get(key, SETTINGS_TEMPLATE.get(key, (None, None)[1]))
+ value = self._settings.get(key, SETTINGS_TEMPLATE.get(key, (None, None))[1])
return str(value)
def getBool(self, key: str) -> bool:
"""Type safe value access for bools."""
- value = self._settings.get(key, SETTINGS_TEMPLATE.get(key, (None, None)[1]))
+ value = self._settings.get(key, SETTINGS_TEMPLATE.get(key, (None, None))[1])
return bool(value)
def getInt(self, key: str) -> int:
"""Type safe value access for integers."""
- value = self._settings.get(key, SETTINGS_TEMPLATE.get(key, (None, None)[1]))
+ value = self._settings.get(key, SETTINGS_TEMPLATE.get(key, (None, None))[1])
if isinstance(value, (int, float)):
return int(value)
return 0
def getFloat(self, key: str) -> float:
"""Type safe value access for floats."""
- value = self._settings.get(key, SETTINGS_TEMPLATE.get(key, (None, None)[1]))
+ value = self._settings.get(key, SETTINGS_TEMPLATE.get(key, (None, None))[1])
if isinstance(value, (int, float)):
return float(value)
return 0.0
diff --git a/novelwriter/core/docbuild.py b/novelwriter/core/docbuild.py
index 82753306..e960debc 100644
--- a/novelwriter/core/docbuild.py
+++ b/novelwriter/core/docbuild.py
@@ -33,6 +33,7 @@ from PyQt5.QtGui import QFont, QFontInfo
from novelwriter import CONFIG
from novelwriter.enum import nwBuildFmt
from novelwriter.error import formatException, logException
+from novelwriter.constants import nwLabels
from novelwriter.core.item import NWItem
from novelwriter.core.tomd import ToMarkdown
from novelwriter.core.toodt import ToOdt
@@ -290,6 +291,17 @@ class NWBuildDocument:
bldObj.setColourHeaders(self._build.getBool("odt.addColours"))
bldObj.setLanguage(buildLang)
+ scale = nwLabels.UNIT_SCALE.get(self._build.getStr("format.pageUnit"), 1.0)
+ pW, pH = nwLabels.PAPER_SIZE.get(self._build.getStr("format.pageSize"), (-1.0, -1.0))
+ bldObj.setPageLayout(
+ pW if pW > 0.0 else scale*self._build.getFloat("format.pageWidth"),
+ pH if pH > 0.0 else scale*self._build.getFloat("format.pageHeight"),
+ scale*self._build.getFloat("format.topMargin"),
+ scale*self._build.getFloat("format.bottomMargin"),
+ scale*self._build.getFloat("format.leftMargin"),
+ scale*self._build.getFloat("format.rightMargin"),
+ )
+
filtered = self._build.buildItemFilter(
self._project, withRoots=self._build.getBool("text.addNoteHeadings")
)
diff --git a/novelwriter/core/toodt.py b/novelwriter/core/toodt.py
index 3bdb66b5..34d5a7d8 100644
--- a/novelwriter/core/toodt.py
+++ b/novelwriter/core/toodt.py
@@ -147,7 +147,7 @@ class ToOdt(Tokenizer):
self._dLanguage = "en"
self._dCountry = "GB"
- # Text Margings in Units of em
+ # Text Margings
self._mTopTitle = "0.423cm"
self._mTopHead1 = "0.423cm"
self._mTopHead2 = "0.353cm"
@@ -166,11 +166,13 @@ class ToOdt(Tokenizer):
self._mBotText = "0.247cm"
self._mBotMeta = "0.106cm"
- # Document Margins
- self._mDocTop = "2.000cm"
- self._mDocBtm = "2.000cm"
- self._mDocLeft = "2.000cm"
- self._mDocRight = "2.000cm"
+ # Document Size and Margins
+ self._mDocWidth = "21.0cm"
+ self._mDocHeight = "29.7cm"
+ self._mDocTop = "2.000cm"
+ self._mDocBtm = "2.000cm"
+ self._mDocLeft = "2.000cm"
+ self._mDocRight = "2.000cm"
# Colour
self._colHead12 = None
@@ -200,6 +202,19 @@ class ToOdt(Tokenizer):
self._colourHead = state
return
+ def setPageLayout(
+ self, width: int | float, height: int | float,
+ top: int | float, bottom: int | float, left: int | float, right: int | float
+ ):
+ """Set the document page size and margins in millimetres."""
+ self._mDocWidth = f"{width/10.0:.3f}cm"
+ self._mDocHeight = f"{height/10.0:.3f}cm"
+ self._mDocTop = f"{top/10.0:.3f}cm"
+ self._mDocBtm = f"{bottom/10.0:.3f}cm"
+ self._mDocLeft = f"{left/10.0:.3f}cm"
+ self._mDocRight = f"{right/10.0:.3f}cm"
+ return
+
##
# Class Methods
##
@@ -454,10 +469,10 @@ class ToOdt(Tokenizer):
self._addTextPar("Heading_20_4", oStyle, tHead, isHead=True, oLevel="4")
elif tType == self.T_SEP:
- self._addTextPar("Text_20_body", oStyle, tText)
+ self._addTextPar("Separator", oStyle, tText)
elif tType == self.T_SKIP:
- self._addTextPar("Text_20_body", oStyle, "")
+ self._addTextPar("Separator", oStyle, "")
elif tType == self.T_TEXT:
if parStyle is None:
@@ -721,10 +736,13 @@ class ToOdt(Tokenizer):
xPage = ET.SubElement(self._xAut2, _mkTag("style", "page-layout"), attrib=tAttr)
tAttr = {}
+ tAttr[_mkTag("fo", "page-width")] = self._mDocWidth
+ tAttr[_mkTag("fo", "page-height")] = self._mDocHeight
tAttr[_mkTag("fo", "margin-top")] = self._mDocTop
tAttr[_mkTag("fo", "margin-bottom")] = self._mDocBtm
tAttr[_mkTag("fo", "margin-left")] = self._mDocLeft
tAttr[_mkTag("fo", "margin-right")] = self._mDocRight
+ tAttr[_mkTag("fo", "print-orientation")] = "portrait"
ET.SubElement(xPage, _mkTag("style", "page-layout-properties"), attrib=tAttr)
xHead = ET.SubElement(xPage, _mkTag("style", "header-style"))
@@ -869,6 +887,25 @@ class ToOdt(Tokenizer):
self._mainPara["Title"] = oStyle
+ # Add Separator Style
+ # ===================
+
+ oStyle = ODTParagraphStyle()
+ oStyle.setDisplayName("Separator")
+ oStyle.setParentStyleName("Standard")
+ oStyle.setNextStyleName("Text_20_body")
+ oStyle.setClass("text")
+ oStyle.setTextAlign("center")
+ oStyle.setMarginTop(self._mTopText)
+ oStyle.setMarginBottom(self._mBotText)
+ oStyle.setLineHeight(self._fLineHeight)
+ oStyle.setFontName(self._textFont)
+ oStyle.setFontFamily(self._fontFamily)
+ oStyle.setFontSize(self._fSizeText)
+ oStyle.packXML(self._xStyl, "Separator")
+
+ self._mainPara["Separator"] = oStyle
+
# Add Heading 1 Style
# ===================
diff --git a/novelwriter/extensions/configlayout.py b/novelwriter/extensions/configlayout.py
index cada2efa..93d348be 100644
--- a/novelwriter/extensions/configlayout.py
+++ b/novelwriter/extensions/configlayout.py
@@ -58,8 +58,7 @@ class NConfigLayout(QGridLayout):
##
def setHelpTextStyle(self, color: QColor | list | tuple, fontScale: float = FONT_SCALE):
- """Set the text color for the help text.
- """
+ """Set the text color for the help text."""
if isinstance(color, QColor):
self._helpCol = color
else:
@@ -68,8 +67,7 @@ class NConfigLayout(QGridLayout):
return
def setHelpText(self, row: int, text: str):
- """Set the text for the help label.
- """
+ """Set the text for the help label."""
if row in self._itemMap:
qHelp = self._itemMap[row][1]
if isinstance(qHelp, NHelpLabel):
@@ -77,8 +75,7 @@ class NConfigLayout(QGridLayout):
return
def setLabelText(self, row: int, text: str):
- """Set the text for the main label.
- """
+ """Set the text for the main label."""
if row in self._itemMap:
self._itemMap[row](0).setText(text)
return
@@ -88,8 +85,7 @@ class NConfigLayout(QGridLayout):
##
def addGroupLabel(self, label: str):
- """Adds a text label to separate groups of settings.
- """
+ """Add a text label to separate groups of settings."""
hM = CONFIG.pxInt(4)
qLabel = QLabel("%s" % label)
qLabel.setContentsMargins(0, hM, 0, hM)
@@ -103,44 +99,35 @@ class NConfigLayout(QGridLayout):
self, label: str, widget: QWidget, helpText: str | None = None,
unit: str | None = None, button: QWidget | None = None
) -> int:
- """Add a label and a widget as a new row of the grid.
- """
- if isinstance(widget, QWidget):
- qWidget = widget
- else:
- qWidget = None
- raise ValueError("The widget must be a QWidget")
-
+ """Add a label and a widget as a new row of the grid."""
wSp = CONFIG.pxInt(8)
qLabel = QLabel(label)
qLabel.setIndent(wSp)
+ qLabel.setBuddy(widget)
qHelp = None
if helpText is not None:
qHelp = NHelpLabel(str(helpText), self._helpCol, self._fontScale)
qHelp.setIndent(wSp)
-
labelBox = QVBoxLayout()
labelBox.addWidget(qLabel)
labelBox.addWidget(qHelp)
labelBox.setSpacing(0)
labelBox.addStretch(1)
-
self.addLayout(labelBox, self._nextRow, 0, 1, 1, Qt.AlignLeft | Qt.AlignTop)
-
else:
self.addWidget(qLabel, self._nextRow, 0, 1, 1, Qt.AlignLeft | Qt.AlignTop)
if isinstance(unit, str):
controlBox = QHBoxLayout()
- controlBox.addWidget(qWidget, 0, Qt.AlignVCenter)
+ controlBox.addWidget(widget, 0, Qt.AlignVCenter)
controlBox.addWidget(QLabel(unit), 0, Qt.AlignVCenter)
controlBox.setSpacing(wSp)
self.addLayout(controlBox, self._nextRow, 1, 1, 1, Qt.AlignRight | Qt.AlignTop)
elif isinstance(button, QAbstractButton):
controlBox = QHBoxLayout()
- controlBox.addWidget(qWidget, 0, Qt.AlignVCenter)
+ controlBox.addWidget(widget, 0, Qt.AlignVCenter)
controlBox.addWidget(button, 0, Qt.AlignVCenter)
controlBox.setSpacing(wSp)
self.addLayout(controlBox, self._nextRow, 1, 1, 1, Qt.AlignRight | Qt.AlignTop)
@@ -151,14 +138,12 @@ class NConfigLayout(QGridLayout):
qLayout.addWidget(widget)
self.addLayout(qLayout, self._nextRow, 1, 1, 1, Qt.AlignRight | Qt.AlignTop)
else:
- self.addWidget(qWidget, self._nextRow, 1, 1, 1, Qt.AlignRight | Qt.AlignTop)
-
- qLabel.setBuddy(qWidget)
+ self.addWidget(widget, self._nextRow, 1, 1, 1, Qt.AlignRight | Qt.AlignTop)
self.setRowStretch(self._nextRow, 0)
self.setRowStretch(self._nextRow+1, 1)
- self._itemMap[self._nextRow] = (qLabel, qHelp, qWidget)
+ self._itemMap[self._nextRow] = (qLabel, qHelp, widget)
self._nextRow += 1
return self._nextRow - 1
@@ -187,8 +172,7 @@ class NSimpleLayout(QGridLayout):
##
def addGroupLabel(self, label: str):
- """Adds a text label to separate groups of settings.
- """
+ """Add a text label to separate groups of settings."""
hM = CONFIG.pxInt(4)
qLabel = QLabel("%s" % label)
qLabel.setContentsMargins(0, hM, 0, hM)
@@ -199,14 +183,7 @@ class NSimpleLayout(QGridLayout):
return
def addRow(self, label: str, widget: QWidget):
- """Add a label and a widget as a new row of the grid.
- """
- if isinstance(widget, QWidget):
- qWidget = widget
- else:
- qWidget = None
- raise ValueError("The widget must be a QWidget")
-
+ """Add a label and a widget as a new row of the grid."""
wSp = CONFIG.pxInt(8)
qLabel = QLabel(label)
qLabel.setIndent(wSp)
@@ -217,9 +194,9 @@ class NSimpleLayout(QGridLayout):
qLayout.addWidget(widget)
self.addLayout(qLayout, self._nextRow, 1, 1, 1, Qt.AlignRight | Qt.AlignTop)
else:
- self.addWidget(qWidget, self._nextRow, 1, 1, 1, Qt.AlignRight | Qt.AlignTop)
+ self.addWidget(widget, self._nextRow, 1, 1, 1, Qt.AlignRight | Qt.AlignTop)
- qLabel.setBuddy(qWidget)
+ qLabel.setBuddy(widget)
self.setRowStretch(self._nextRow, 0)
self.setRowStretch(self._nextRow+1, 1)
diff --git a/novelwriter/tools/manussettings.py b/novelwriter/tools/manussettings.py
index 3e390c2c..6a031bf0 100644
--- a/novelwriter/tools/manussettings.py
+++ b/novelwriter/tools/manussettings.py
@@ -40,7 +40,7 @@ from PyQt5.QtWidgets import (
)
from novelwriter import CONFIG
-from novelwriter.constants import nwHeadFmt
+from novelwriter.constants import nwHeadFmt, nwLabels, trConst
from novelwriter.core.buildsettings import BuildSettings, FilterMode
from novelwriter.extensions.switch import NSwitch
from novelwriter.extensions.switchbox import NSwitchBox
@@ -644,7 +644,7 @@ class _HeadingsTab(QWidget):
self.btnScene.clicked.connect(lambda: self._editHeading(self.EDIT_SCENE))
self.hdeScene = QLabel(self.tr("Hide"))
self.hdeScene.setToolTip(sceneHideTip)
- self.swtScene = NSwitch(width=2*iPx, height=iPx)
+ self.swtScene = NSwitch(self, width=2*iPx, height=iPx)
self.swtScene.setToolTip(sceneHideTip)
wrapScene = QHBoxLayout()
@@ -671,7 +671,7 @@ class _HeadingsTab(QWidget):
self.btnSection.clicked.connect(lambda: self._editHeading(self.EDIT_SECTION))
self.hdeSection = QLabel(self.tr("Hide"))
self.hdeSection.setToolTip(sectionHideTip)
- self.swtSection = NSwitch(width=2*iPx, height=iPx)
+ self.swtSection = NSwitch(self, width=2*iPx, height=iPx)
self.swtSection.setToolTip(sectionHideTip)
wrapSection = QHBoxLayout()
@@ -882,10 +882,10 @@ class _ContentTab(QWidget):
self.formLeft = NSimpleLayout()
self.formLeft.addGroupLabel(self._build.getLabel("text.grpContent"))
- self.incSynopsis = NSwitch(width=2*iPx, height=iPx)
- self.incComments = NSwitch(width=2*iPx, height=iPx)
- self.incKeywords = NSwitch(width=2*iPx, height=iPx)
- self.incBodyText = NSwitch(width=2*iPx, height=iPx)
+ self.incSynopsis = NSwitch(self, width=2*iPx, height=iPx)
+ self.incComments = NSwitch(self, width=2*iPx, height=iPx)
+ self.incKeywords = NSwitch(self, width=2*iPx, height=iPx)
+ self.incBodyText = NSwitch(self, width=2*iPx, height=iPx)
self.formLeft.addRow(self._build.getLabel("text.includeSynopsis"), self.incSynopsis)
self.formLeft.addRow(self._build.getLabel("text.includeComments"), self.incComments)
@@ -898,7 +898,7 @@ class _ContentTab(QWidget):
self.formRight = NSimpleLayout()
self.formRight.addGroupLabel(self._build.getLabel("text.grpInsert"))
- self.addNoteHead = NSwitch(width=2*iPx, height=iPx)
+ self.addNoteHead = NSwitch(self, width=2*iPx, height=iPx)
self.formRight.addRow(self._build.getLabel("text.addNoteHeadings"), self.addNoteHead)
@@ -945,33 +945,34 @@ class _FormatTab(QWidget):
self.mainTheme = buildMain.mainGui.mainTheme
self._build = build
+ self._unitScale = 1.0
iPx = self.mainTheme.baseIconSize
+ spW = 6*self.mainTheme.textNWidth
+ dbW = 8*self.mainTheme.textNWidth
- # Form
- # ====
+ # Text Format Form
+ # ================
- self.mainForm = NConfigLayout()
- self.mainForm.addGroupLabel(self._build.getLabel("format.grpFormat"))
+ self.formFormat = NConfigLayout()
+ self.formFormat.addGroupLabel(self._build.getLabel("format.grpFormat"))
# Build Language
self.buildLang = QComboBox()
- self.buildLang.setMinimumWidth(CONFIG.pxInt(250))
langauges = CONFIG.listLanguages(CONFIG.LANG_PROJ)
self.buildLang.addItem("[%s]" % self.tr("Not Set"), "None")
for langID, langName in langauges:
self.buildLang.addItem(langName, langID)
- self.mainForm.addRow(self._build.getLabel("format.buildLang"), self.buildLang)
+ self.formFormat.addRow(self._build.getLabel("format.buildLang"), self.buildLang)
# Font Family
self.textFont = QLineEdit()
self.textFont.setReadOnly(True)
- self.textFont.setMinimumWidth(CONFIG.pxInt(200))
self.btnTextFont = QPushButton("...")
self.btnTextFont.setMaximumWidth(int(2.5*self.mainTheme.getTextWidth("...")))
self.btnTextFont.clicked.connect(self._selectFont)
- self.mainForm.addRow(
+ self.formFormat.addRow(
self._build.getLabel("format.textFont"), self.textFont, button=self.btnTextFont
)
@@ -980,39 +981,104 @@ class _FormatTab(QWidget):
self.textSize.setMinimum(8)
self.textSize.setMaximum(60)
self.textSize.setSingleStep(1)
- self.textSize.setMinimumWidth(CONFIG.pxInt(60))
- self.mainForm.addRow(
+ self.textSize.setMinimumWidth(spW)
+ self.formFormat.addRow(
self._build.getLabel("format.textSize"), self.textSize, unit="pt"
)
# Line Height
self.lineHeight = QDoubleSpinBox(self)
- self.lineHeight.setFixedWidth(6*self.mainTheme.textNWidth)
+ self.lineHeight.setFixedWidth(spW)
self.lineHeight.setMinimum(0.75)
self.lineHeight.setMaximum(3.0)
self.lineHeight.setSingleStep(0.05)
self.lineHeight.setDecimals(2)
- self.lineHeight.setMinimumWidth(CONFIG.pxInt(60))
- self.mainForm.addRow(
+ self.formFormat.addRow(
self._build.getLabel("format.lineHeight"), self.lineHeight, unit="em"
)
- # Switches
- self.mainForm.addGroupLabel(self._build.getLabel("format.grpOptions"))
- self.mainForm.setContentsMargins(0, 0, 0, 0)
+ # Text Options Form
+ # =================
- self.justifyText = NSwitch(width=2*iPx, height=iPx)
- self.stripUnicode = NSwitch(width=2*iPx, height=iPx)
- self.replaceTabs = NSwitch(width=2*iPx, height=iPx)
+ self.formOptions = NSimpleLayout()
+ self.formOptions.addGroupLabel(self._build.getLabel("format.grpOptions"))
+ self.formOptions.setContentsMargins(0, 0, 0, 0)
- self.mainForm.addRow(self._build.getLabel("format.justifyText"), self.justifyText)
- self.mainForm.addRow(self._build.getLabel("format.stripUnicode"), self.stripUnicode)
- self.mainForm.addRow(self._build.getLabel("format.replaceTabs"), self.replaceTabs)
+ self.justifyText = NSwitch(self, width=2*iPx, height=iPx)
+ self.stripUnicode = NSwitch(self, width=2*iPx, height=iPx)
+ self.replaceTabs = NSwitch(self, width=2*iPx, height=iPx)
+
+ self.formOptions.addRow(self._build.getLabel("format.justifyText"), self.justifyText)
+ self.formOptions.addRow(self._build.getLabel("format.stripUnicode"), self.stripUnicode)
+ self.formOptions.addRow(self._build.getLabel("format.replaceTabs"), self.replaceTabs)
+
+ # Page Layout Form
+ # ================
+
+ self.formLayout = NSimpleLayout()
+ self.formLayout.addGroupLabel(self._build.getLabel("format.grpPage"))
+ self.formLayout.setContentsMargins(0, 0, 0, 0)
+
+ self.pageUnit = QComboBox(self)
+ for key, name in nwLabels.UNIT_NAME.items():
+ self.pageUnit.addItem(trConst(name), key)
+
+ self.pageSize = QComboBox(self)
+ for key, name in nwLabels.PAPER_NAME.items():
+ self.pageSize.addItem(trConst(name), key)
+
+ self.pageWidth = QDoubleSpinBox(self)
+ self.pageWidth.setFixedWidth(dbW)
+ self.pageWidth.setMaximum(500.0)
+
+ self.pageHeight = QDoubleSpinBox(self)
+ self.pageHeight.setFixedWidth(dbW)
+ self.pageHeight.setMaximum(500.0)
+
+ self.topMargin = QDoubleSpinBox(self)
+ self.topMargin.setFixedWidth(dbW)
+
+ self.bottomMargin = QDoubleSpinBox(self)
+ self.bottomMargin.setFixedWidth(dbW)
+
+ self.leftMargin = QDoubleSpinBox(self)
+ self.leftMargin.setFixedWidth(dbW)
+
+ self.rightMargin = QDoubleSpinBox(self)
+ self.rightMargin.setFixedWidth(dbW)
+
+ self.formLayout.addRow(self._build.getLabel("format.pageUnit"), self.pageUnit)
+ self.formLayout.addRow(self._build.getLabel("format.pageSize"), self.pageSize)
+ self.formLayout.addRow(self._build.getLabel("format.pageWidth"), self.pageWidth)
+ self.formLayout.addRow(self._build.getLabel("format.pageHeight"), self.pageHeight)
+ self.formLayout.addRow(self._build.getLabel("format.topMargin"), self.topMargin)
+ self.formLayout.addRow(self._build.getLabel("format.bottomMargin"), self.bottomMargin)
+ self.formLayout.addRow(self._build.getLabel("format.leftMargin"), self.leftMargin)
+ self.formLayout.addRow(self._build.getLabel("format.rightMargin"), self.rightMargin)
# Assemble GUI
# ============
- self.setLayout(self.mainForm)
+ self.formLeft = QVBoxLayout()
+ self.formLeft.addLayout(self.formFormat)
+ self.formLeft.addLayout(self.formOptions)
+ self.formLeft.addStretch(1)
+ self.formLeft.setContentsMargins(0, 0, 0, 0)
+ self.formLeft.setSpacing(CONFIG.pxInt(8))
+
+ self.formRight = QVBoxLayout()
+ self.formRight.addLayout(self.formLayout)
+ self.formRight.addStretch(1)
+ self.formRight.setContentsMargins(0, 0, 0, 0)
+ self.formRight.setSpacing(CONFIG.pxInt(8))
+
+ self.outerBox = QHBoxLayout()
+ self.outerBox.addLayout(self.formLeft, 1)
+ self.outerBox.addLayout(self.formRight, 1)
+ self.outerBox.setContentsMargins(0, 0, 0, 0)
+ self.outerBox.setSpacing(CONFIG.pxInt(16))
+
+ self.setLayout(self.outerBox)
return
@@ -1034,6 +1100,29 @@ class _FormatTab(QWidget):
self.stripUnicode.setChecked(self._build.getBool("format.stripUnicode"))
self.replaceTabs.setChecked(self._build.getBool("format.replaceTabs"))
+ pageUnit = self._build.getStr("format.pageUnit")
+ index = self.pageUnit.findData(pageUnit)
+ if index >= 0:
+ self.pageUnit.setCurrentIndex(index)
+ self._unitScale = nwLabels.UNIT_SCALE.get(pageUnit, 1.0)
+ self._changeUnit(index)
+
+ self.pageWidth.setValue(self._build.getFloat("format.pageWidth"))
+ self.pageHeight.setValue(self._build.getFloat("format.pageHeight"))
+ self.topMargin.setValue(self._build.getFloat("format.topMargin"))
+ self.bottomMargin.setValue(self._build.getFloat("format.bottomMargin"))
+ self.leftMargin.setValue(self._build.getFloat("format.leftMargin"))
+ self.rightMargin.setValue(self._build.getFloat("format.rightMargin"))
+
+ pageSize = self._build.getStr("format.pageSize")
+ index = self.pageSize.findData(pageSize)
+ if index >= 0:
+ self.pageSize.setCurrentIndex(index)
+ self._changePageSize(index)
+
+ self.pageUnit.currentIndexChanged.connect(self._changeUnit)
+ self.pageSize.currentIndexChanged.connect(self._changePageSize)
+
return
def saveContent(self):
@@ -1042,9 +1131,19 @@ class _FormatTab(QWidget):
self._build.setValue("format.textFont", self.textFont.text())
self._build.setValue("format.textSize", self.textSize.value())
self._build.setValue("format.lineHeight", self.lineHeight.value())
+
self._build.setValue("format.justifyText", self.justifyText.isChecked())
self._build.setValue("format.stripUnicode", self.stripUnicode.isChecked())
self._build.setValue("format.replaceTabs", self.replaceTabs.isChecked())
+
+ self._build.setValue("format.pageUnit", str(self.pageUnit.currentData()))
+ self._build.setValue("format.pageSize", str(self.pageSize.currentData()))
+ self._build.setValue("format.pageWidth", self.pageWidth.value())
+ self._build.setValue("format.pageHeight", self.pageHeight.value())
+ self._build.setValue("format.topMargin", self.topMargin.value())
+ self._build.setValue("format.bottomMargin", self.bottomMargin.value())
+ self._build.setValue("format.leftMargin", self.leftMargin.value())
+ self._build.setValue("format.rightMargin", self.rightMargin.value())
return
##
@@ -1063,6 +1162,75 @@ class _FormatTab(QWidget):
self.textSize.setValue(theFont.pointSize())
return
+ @pyqtSlot(int)
+ def _changeUnit(self, index: int):
+ """The current unit change, so recalculate sizes."""
+ newUnit = self.pageUnit.itemData(index)
+ newScale = nwLabels.UNIT_SCALE.get(newUnit, 1.0)
+ reScale = self._unitScale/newScale
+
+ pageWidth = self.pageWidth.value() * reScale
+ pageHeight = self.pageHeight.value() * reScale
+ topMargin = self.topMargin.value() * reScale
+ bottomMargin = self.bottomMargin.value() * reScale
+ leftMargin = self.leftMargin.value() * reScale
+ rightMargin = self.rightMargin.value() * reScale
+
+ isMM = newUnit == "mm"
+ nDec = 1 if isMM else 2
+ nStep = 1.0 if isMM else 0.1
+ pMax = 500.0 if isMM else 50.0
+ mMax = 150.0 if isMM else 15.0
+
+ self.pageWidth.setDecimals(nDec)
+ self.pageWidth.setSingleStep(nStep)
+ self.pageWidth.setMaximum(pMax)
+ self.pageWidth.setValue(pageWidth)
+
+ self.pageHeight.setDecimals(nDec)
+ self.pageHeight.setSingleStep(nStep)
+ self.pageHeight.setMaximum(pMax)
+ self.pageHeight.setValue(pageHeight)
+
+ self.topMargin.setDecimals(nDec)
+ self.topMargin.setSingleStep(nStep)
+ self.topMargin.setMaximum(mMax)
+ self.topMargin.setValue(topMargin)
+
+ self.bottomMargin.setDecimals(nDec)
+ self.bottomMargin.setSingleStep(nStep)
+ self.bottomMargin.setMaximum(mMax)
+ self.bottomMargin.setValue(bottomMargin)
+
+ self.leftMargin.setDecimals(nDec)
+ self.leftMargin.setSingleStep(nStep)
+ self.leftMargin.setMaximum(mMax)
+ self.leftMargin.setValue(leftMargin)
+
+ self.rightMargin.setDecimals(nDec)
+ self.rightMargin.setSingleStep(nStep)
+ self.rightMargin.setMaximum(mMax)
+ self.rightMargin.setValue(rightMargin)
+
+ self._unitScale = newScale
+
+ return
+
+ @pyqtSlot(int)
+ def _changePageSize(self, index: int):
+ """The page size has changed."""
+ self.pageWidth.setEnabled(True)
+ self.pageHeight.setEnabled(True)
+
+ w, h = nwLabels.PAPER_SIZE[self.pageSize.itemData(index)] if index >= 0 else (-1.0, -1.0)
+ if w > 0.0 and h > 0.0:
+ self.pageWidth.setEnabled(False)
+ self.pageHeight.setEnabled(False)
+ self.pageWidth.setValue(w/self._unitScale)
+ self.pageHeight.setValue(h/self._unitScale)
+
+ return
+
# END Class _FormatTab
@@ -1084,7 +1252,7 @@ class _OutputTab(QWidget):
self.formLeft = NSimpleLayout()
self.formLeft.addGroupLabel(self._build.getLabel("odt"))
- self.odtAddColours = NSwitch(width=2*iPx, height=iPx)
+ self.odtAddColours = NSwitch(self, width=2*iPx, height=iPx)
self.formLeft.addRow(self._build.getLabel("odt.addColours"), self.odtAddColours)
@@ -1094,7 +1262,7 @@ class _OutputTab(QWidget):
self.formRight = NSimpleLayout()
self.formRight.addGroupLabel(self._build.getLabel("html"))
- self.htmlAddStyles = NSwitch(width=2*iPx, height=iPx)
+ self.htmlAddStyles = NSwitch(self, width=2*iPx, height=iPx)
self.formRight.addRow(self._build.getLabel("html.addStyles"), self.htmlAddStyles)
diff --git a/sample/nwProject.nwx b/sample/nwProject.nwx
index ee8d43ca..18efb9d6 100644
--- a/sample/nwProject.nwx
+++ b/sample/nwProject.nwx
@@ -1,6 +1,6 @@
-
-
+
+
Sample Project
Sample Project
Jane Smith
diff --git a/tests/reference/coreToOdt_SaveFlat_document.fodt b/tests/reference/coreToOdt_SaveFlat_document.fodt
index 0c2b658e..70198aaa 100644
--- a/tests/reference/coreToOdt_SaveFlat_document.fodt
+++ b/tests/reference/coreToOdt_SaveFlat_document.fodt
@@ -1,13 +1,13 @@
- 2023-05-29T22:10:15
- novelWriter/2.0.7
+ 2023-07-19T23:55:29
+ novelWriter/2.1-beta1
Jane Smith
1234
P42DT12H34M56S
Test Project
- 2023-05-29T22:10:15
+ 2023-07-19T23:55:29
Jane Smith
@@ -38,6 +38,10 @@
+
+
+
+
@@ -60,7 +64,7 @@
-
+
diff --git a/tests/reference/coreToOdt_SaveFull_styles.xml b/tests/reference/coreToOdt_SaveFull_styles.xml
index 5f2d1db6..c52c6938 100644
--- a/tests/reference/coreToOdt_SaveFull_styles.xml
+++ b/tests/reference/coreToOdt_SaveFull_styles.xml
@@ -28,6 +28,10 @@
+
+
+
+
@@ -50,7 +54,7 @@
-
+
diff --git a/tests/reference/mBuildDocBuild_OpenDocument_Lorem_Ipsum.fodt b/tests/reference/mBuildDocBuild_OpenDocument_Lorem_Ipsum.fodt
index aba1a301..1c5ccd36 100644
--- a/tests/reference/mBuildDocBuild_OpenDocument_Lorem_Ipsum.fodt
+++ b/tests/reference/mBuildDocBuild_OpenDocument_Lorem_Ipsum.fodt
@@ -1,13 +1,13 @@
- 2023-05-29T22:20:22
- novelWriter/2.0.7
+ 2023-07-19T23:55:28
+ novelWriter/2.1-beta1
lipsum.com
- 40
- P0DT0H31M45S
+ 42
+ P0DT0H31M53S
Lorem Ipsum
- 2023-05-29T22:20:22
+ 2023-07-19T23:55:28
lipsum.com
@@ -38,6 +38,10 @@
+
+
+
+
@@ -60,7 +64,7 @@
-
+
diff --git a/tests/test_core/test_core_toodt.py b/tests/test_core/test_core_toodt.py
index 5ab6f276..359b0935 100644
--- a/tests/test_core/test_core_toodt.py
+++ b/tests/test_core/test_core_toodt.py
@@ -129,8 +129,8 @@ def testCoreToOdt_TextFormatting(mockGUI):
assert theDoc._paraStyle("Text_20_body", oStyle) == "P1"
assert list(theDoc._mainPara.keys()) == [
- "Text_20_body", "Text_20_Meta", "Title", "Heading_20_1",
- "Heading_20_2", "Heading_20_3", "Heading_20_4", "Header"
+ "Text_20_body", "Text_20_Meta", "Title", "Separator",
+ "Heading_20_1", "Heading_20_2", "Heading_20_3", "Heading_20_4", "Header",
]
theKey = "071d6b2e4764749f8c78d3c1ab9099fa04c07d2d53fd3de61eb1bdf1cb4845c3"
@@ -145,9 +145,9 @@ def testCoreToOdt_TextFormatting(mockGUI):
theDoc.initDocument()
theDoc._addTextPar("Standard", oStyle, "")
assert xmlToText(theDoc._xText) == (
- ""
- ""
- ""
+ ''
+ ''
+ ''
)
# No Format
@@ -155,9 +155,9 @@ def testCoreToOdt_TextFormatting(mockGUI):
theDoc._addTextPar("Standard", oStyle, "Hello World")
assert theDoc.errData == []
assert xmlToText(theDoc._xText) == (
- ""
- "Hello World"
- ""
+ ''
+ 'Hello World'
+ ''
)
# Heading Level None
@@ -165,9 +165,9 @@ def testCoreToOdt_TextFormatting(mockGUI):
theDoc._addTextPar("Standard", oStyle, "Hello World", isHead=True)
assert theDoc.errData == []
assert xmlToText(theDoc._xText) == (
- ""
- "Hello World"
- ""
+ ''
+ 'Hello World'
+ ''
)
# Heading Level 1
@@ -175,9 +175,9 @@ def testCoreToOdt_TextFormatting(mockGUI):
theDoc._addTextPar("Standard", oStyle, "Hello World", isHead=True, oLevel="1")
assert theDoc.errData == []
assert xmlToText(theDoc._xText) == (
- ""
- "Hello World"
- ""
+ ''
+ 'Hello World'
+ ''
)
# Formatted Text
@@ -187,11 +187,11 @@ def testCoreToOdt_TextFormatting(mockGUI):
theDoc._addTextPar("Standard", oStyle, theTxt, tFmt=theFmt)
assert theDoc.errData == []
assert xmlToText(theDoc._xText) == (
- ""
- "A few "
- "words from our sponsor"
- ""
+ ''
+ 'A few '
+ 'words from '
+ 'our sponsor'
+ ''
)
# Incorrectly Formatted Text
@@ -201,11 +201,11 @@ def testCoreToOdt_TextFormatting(mockGUI):
theDoc._addTextPar("Standard", oStyle, theTxt, tFmt=theFmt)
assert theDoc.errData == ["Unknown format tag encountered"]
assert xmlToText(theDoc._xText) == (
- ""
- ""
- "A few words"
- ""
- ""
+ ''
+ ''
+ 'A few words'
+ ''
+ ''
)
# Formatted Text
@@ -215,9 +215,9 @@ def testCoreToOdt_TextFormatting(mockGUI):
theDoc._addTextPar("Standard", oStyle, theTxt, tFmt=theFmt)
assert theDoc.errData == []
assert xmlToText(theDoc._xText) == (
- ""
- "HelloWorld"
- ""
+ ''
+ 'HelloWorld'
+ ''
)
# Test for issue #1412
@@ -230,10 +230,10 @@ def testCoreToOdt_TextFormatting(mockGUI):
theDoc._addTextPar("Standard", oStyle, theTxt, tFmt=theFmt)
assert theDoc.errData == []
assert xmlToText(theDoc._xText) == (
- ""
- "Test text **"
- "bold** and more."
- ""
+ ''
+ 'Test text **'
+ 'bold** and more.'
+ ''
)
# END Test testCoreToOdt_TextFormatting
@@ -454,9 +454,9 @@ def testCoreToOdt_Convert(mockGUI):
assert theDoc.errData == []
assert xmlToText(theDoc._xText) == (
''
- '* * *'
+ '* * *'
'Text'
- '* * *'
+ '* * *'
'Text'
''
)
@@ -472,9 +472,9 @@ def testCoreToOdt_Convert(mockGUI):
assert theDoc.errData == []
assert xmlToText(theDoc._xText) == (
''
- ''
+ ''
'Text'
- ''
+ ''
'Text'
''
)
@@ -500,24 +500,24 @@ def testCoreToOdt_Convert(mockGUI):
assert xmlToText(theDoc._xText) == (
''
'Scene'
- ''
+ ''
'Point of View: Jane'
- ''
+ ''
'Characters: John'
''
'Plot: Main'
- 'Right align'
+ 'Right align'
'Left Align'
- 'Centered'
+ 'Centered'
'Left indent'
'Right indent'
''
)
+ assert getStyle("P3")._pAttr["margin-bottom"] == ["fo", "0.000cm"]
assert getStyle("P4")._pAttr["margin-bottom"] == ["fo", "0.000cm"]
- assert getStyle("P5")._pAttr["margin-bottom"] == ["fo", "0.000cm"]
- assert getStyle("P5")._pAttr["margin-top"] == ["fo", "0.000cm"]
- assert getStyle("P6")._pAttr["text-align"] == ["fo", "right"]
- assert getStyle("P3")._pAttr["text-align"] == ["fo", "center"]
+ assert getStyle("P4")._pAttr["margin-top"] == ["fo", "0.000cm"]
+ assert getStyle("P5")._pAttr["text-align"] == ["fo", "right"]
+ assert getStyle("P6")._pAttr["text-align"] == ["fo", "center"]
assert getStyle("P7")._pAttr["margin-left"] == ["fo", "1.693cm"]
assert getStyle("P8")._pAttr["margin-right"] == ["fo", "1.693cm"]
diff --git a/tests/test_tools/test_tools_manussettings.py b/tests/test_tools/test_tools_manussettings.py
index b4ae8e12..c455b623 100644
--- a/tests/test_tools/test_tools_manussettings.py
+++ b/tests/test_tools/test_tools_manussettings.py
@@ -519,10 +519,20 @@ def testBuildSettings_Format(monkeypatch, qtbot: QtBot, nwGUI: GuiMain):
build.setValue("format.textFont", "") # Will fall back to config value
build.setValue("format.textSize", 12)
build.setValue("format.lineHeight", 1.2)
+
build.setValue("format.justifyText", False)
build.setValue("format.stripUnicode", False)
build.setValue("format.replaceTabs", False)
+ build.setValue("format.pageUnit", "mm")
+ build.setValue("format.pageSize", "Custom")
+ build.setValue("format.pageWidth", 180.0)
+ build.setValue("format.pageHeight", 250.0)
+ build.setValue("format.topMargin", 25.0)
+ build.setValue("format.bottomMargin", 25.0)
+ build.setValue("format.leftMargin", 15.0)
+ build.setValue("format.rightMargin", 15.0)
+
# Create the dialog and populate it
bSettings = GuiBuildSettings(nwGUI, nwGUI, build)
bSettings.show()
@@ -537,19 +547,33 @@ def testBuildSettings_Format(monkeypatch, qtbot: QtBot, nwGUI: GuiMain):
assert fmtTab.textFont.text() == textFont
assert fmtTab.textSize.value() == 12
assert fmtTab.lineHeight.value() == 1.2
+
assert fmtTab.justifyText.isChecked() is False
assert fmtTab.stripUnicode.isChecked() is False
assert fmtTab.replaceTabs.isChecked() is False
+ assert fmtTab.pageUnit.currentData() == "mm"
+ assert fmtTab.pageSize.currentData() == "Custom"
+ assert fmtTab.pageWidth.value() == 180.0
+ assert fmtTab.pageHeight.value() == 250.0
+ assert fmtTab.topMargin.value() == 25.0
+ assert fmtTab.bottomMargin.value() == 25.0
+ assert fmtTab.leftMargin.value() == 15.0
+ assert fmtTab.rightMargin.value() == 15.0
+
# Change values
fmtTab.buildLang.setCurrentIndex(fmtTab.buildLang.findData("en_GB"))
fmtTab.textFont.setText("Arial")
fmtTab.textSize.setValue(11)
fmtTab.lineHeight.setValue(1.15)
+
fmtTab.justifyText.setChecked(True)
fmtTab.stripUnicode.setChecked(True)
fmtTab.replaceTabs.setChecked(True)
+ fmtTab.pageUnit.setCurrentIndex(fmtTab.pageUnit.findData("cm"))
+ fmtTab.pageSize.setCurrentIndex(fmtTab.pageSize.findData("A4"))
+
# Save values
fmtTab.saveContent()
@@ -557,10 +581,20 @@ def testBuildSettings_Format(monkeypatch, qtbot: QtBot, nwGUI: GuiMain):
assert build.getStr("format.textFont") == "Arial"
assert build.getInt("format.textSize") == 11
assert build.getFloat("format.lineHeight") == 1.15
+
assert build.getBool("format.justifyText") is True
assert build.getBool("format.stripUnicode") is True
assert build.getBool("format.replaceTabs") is True
+ assert fmtTab.pageUnit.currentData() == "cm"
+ assert fmtTab.pageSize.currentData() == "A4"
+ assert fmtTab.pageWidth.value() == 21.0
+ assert fmtTab.pageHeight.value() == 29.7
+ assert fmtTab.topMargin.value() == 2.5
+ assert fmtTab.bottomMargin.value() == 2.5
+ assert fmtTab.leftMargin.value() == 1.5
+ assert fmtTab.rightMargin.value() == 1.5
+
# Check that the font dialog doesn't fail
with monkeypatch.context() as mp:
font = QFont()