Make page breaks visible in manuscript preview (#2086)

This commit is contained in:
Veronica Berglyd Olsen
2024-11-07 18:39:10 +01:00
committed by GitHub
16 changed files with 319 additions and 151 deletions
@@ -14,6 +14,7 @@
"Objects": "Objects",
"Entities": "Entities",
"Custom": "Custom",
"New Page": "New Page",
"0": "Zero",
"1": "One",
"2": "Two",
+22 -8
View File
@@ -39,7 +39,7 @@ from PyQt5.QtCore import QCoreApplication
from novelwriter import CONFIG, SHARED
from novelwriter.common import isHandle, minmax, simplified
from novelwriter.constants import nwConst, nwFiles, nwItemClass
from novelwriter.constants import nwConst, nwFiles, nwItemClass, nwStats
from novelwriter.core.item import NWItem
from novelwriter.core.project import NWProject
from novelwriter.core.storage import NWStorageCreate
@@ -428,7 +428,6 @@ class ProjectBuilder:
lblNewProject = self.tr("New Project")
lblTitlePage = self.tr("Title Page")
lblByAuthors = self.tr("By")
# Settings
project.data.setUuid(None)
@@ -441,14 +440,29 @@ class ProjectBuilder:
# Add Root Folders
hNovelRoot = project.newRoot(nwItemClass.NOVEL)
hTitlePage = project.newFile(lblTitlePage, hNovelRoot)
novelTitle = project.data.name
titlePage = f"#! {novelTitle}\n\n"
if project.data.author:
titlePage += f">> {lblByAuthors} {project.data.author} <<\n\n"
# Generate Title Page
aDoc = project.storage.getDocument(hTitlePage)
aDoc.writeDocument(titlePage)
aDoc.writeDocument((
"{author}[br]\n"
"{address} 1[br]\n"
"{address} 2 <<\n"
"\n"
"[vspace:5]\n"
"\n"
"#! {title}\n"
"\n"
">> **{by} {author}** <<\n"
"\n"
">> {count}: [field:{field}] <<\n"
).format(
author=project.data.author or "None",
address=self.tr("Address"),
title=project.data.name or "None",
by=self.tr("By"),
count=self.tr("Word Count"),
field=nwStats.WORDS_TEXT,
))
# Create a project structure based on selected root folders
# and a number of chapters and scenes selected in the
+13 -20
View File
@@ -87,15 +87,6 @@ class NWBuildDocument:
"""
return self._cache
##
# Setters
##
def setBuildOutline(self, state: bool) -> None:
"""Turn on/off outline for builds."""
self._outline = state
return
##
# Special Methods
##
@@ -122,11 +113,12 @@ class NWBuildDocument:
self._queue.append(item.itemHandle)
return
def iterBuildPreview(self) -> Iterable[tuple[int, bool]]:
def iterBuildPreview(self, newPage: bool) -> Iterable[tuple[int, bool]]:
"""Build a preview QTextDocument."""
makeObj = ToQTextDocument(self._project)
filtered = self._setupBuild(makeObj)
makeObj.initDocument()
makeObj.setShowNewPage(newPage)
self._outline = True
yield from self._iterBuild(makeObj, filtered)
makeObj.closeDocument()
@@ -352,14 +344,17 @@ class NWBuildDocument:
tItem = self._project.tree[tHandle]
if isinstance(tItem, NWItem):
try:
if tItem.isRootType() and not tItem.isNovelLike():
bldObj.addRootHeading(tHandle)
if convert:
bldObj.doConvert()
if self._count:
bldObj.countStats()
if self._outline:
bldObj.buildOutline()
if tItem.isRootType():
if tItem.isNovelLike():
bldObj.setBreakNext()
else:
bldObj.addRootHeading(tHandle)
if convert:
bldObj.doConvert()
if self._count:
bldObj.countStats()
if self._outline:
bldObj.buildOutline()
elif tItem.isFileType():
bldObj.setText(tHandle)
bldObj.doPreProcessing()
@@ -370,8 +365,6 @@ class NWBuildDocument:
bldObj.buildOutline()
if convert:
bldObj.doConvert()
else:
logger.info(f"Build: Skipping '{tHandle}'")
except Exception:
self._error = f"Build: Failed to build '{tHandle}'"
+1 -1
View File
@@ -60,7 +60,7 @@ VALID_MAP: dict[str, set[str]] = {
},
"GuiManuscript": {
"winWidth", "winHeight", "optsWidth", "viewWidth", "listHeight",
"detailsHeight", "detailsWidth", "detailsExpanded",
"detailsHeight", "detailsWidth", "detailsExpanded", "showNewPage",
},
"GuiManuscriptBuild": {
"winWidth", "winHeight", "fmtWidth", "sumWidth",
+22 -23
View File
@@ -236,32 +236,31 @@ class ToDocX(Tokenizer):
self._pars.append(par)
# Styles
if tStyle is not None:
if tStyle & BlockFmt.LEFT:
par.setAlignment("left")
elif tStyle & BlockFmt.RIGHT:
par.setAlignment("right")
elif tStyle & BlockFmt.CENTRE:
par.setAlignment("center")
elif tStyle & BlockFmt.JUSTIFY:
par.setAlignment("both")
if tStyle & BlockFmt.LEFT:
par.setAlignment("left")
elif tStyle & BlockFmt.RIGHT:
par.setAlignment("right")
elif tStyle & BlockFmt.CENTRE:
par.setAlignment("center")
elif tStyle & BlockFmt.JUSTIFY:
par.setAlignment("both")
if tStyle & BlockFmt.PBB:
par.setPageBreakBefore(True)
if tStyle & BlockFmt.PBA:
par.setPageBreakAfter(True)
if tStyle & BlockFmt.PBB:
par.setPageBreakBefore(True)
if tStyle & BlockFmt.PBA:
par.setPageBreakAfter(True)
if tStyle & BlockFmt.Z_BTM:
par.setMarginBottom(0.0)
if tStyle & BlockFmt.Z_TOP:
par.setMarginTop(0.0)
if tStyle & BlockFmt.Z_BTM:
par.setMarginBottom(0.0)
if tStyle & BlockFmt.Z_TOP:
par.setMarginTop(0.0)
if tStyle & BlockFmt.IND_T:
par.setIndentFirst(True)
if tStyle & BlockFmt.IND_L:
par.setMarginLeft(bIndent)
if tStyle & BlockFmt.IND_R:
par.setMarginRight(bIndent)
if tStyle & BlockFmt.IND_T:
par.setIndentFirst(True)
if tStyle & BlockFmt.IND_L:
par.setMarginLeft(bIndent)
if tStyle & BlockFmt.IND_R:
par.setMarginRight(bIndent)
# Process Text Types
if tType == BlockTyp.TEXT:
+1 -1
View File
@@ -171,7 +171,7 @@ class ToHtml(Tokenizer):
# Styles
aStyle = []
if tStyle is not None and self._cssStyles:
if self._cssStyles:
if tStyle & BlockFmt.LEFT:
aStyle.append("text-align: left;")
elif tStyle & BlockFmt.RIGHT:
+39 -34
View File
@@ -166,6 +166,7 @@ class Tokenizer(ABC):
self._hFormatter = HeadingFormatter(self._project)
self._noSep = True # Flag to indicate that we don't want a scene separator
self._noIndent = False # Flag to disable text indent on next paragraph
self._breakNext = False # Add a page break on next token
# This File
self._isNovel = False # Document is a novel document
@@ -444,6 +445,11 @@ class Tokenizer(ABC):
self._classes["optional"] = self._theme.optional
return
def setBreakNext(self) -> None:
"""Set a page break for next block."""
self._breakNext = True
return
def addRootHeading(self, tHandle: str) -> None:
"""Add a heading at the start of a new root folder."""
self._text = ""
@@ -531,7 +537,6 @@ class Tokenizer(ABC):
text = REGEX_PATTERNS.lineBreak.sub("\uffff", self._text)
nHead = 0
breakNext = False
rawText = []
tHandle = self._handle or ""
tBlocks: list[T_Block] = [B_EMPTY]
@@ -546,11 +551,11 @@ class Tokenizer(ABC):
rawText.append("\n")
continue
if breakNext:
sAlign = BlockFmt.PBB
breakNext = False
if self._breakNext:
tStyle = BlockFmt.PBB
self._breakNext = False
else:
sAlign = BlockFmt.NONE
tStyle = BlockFmt.NONE
# Check Line Format
# =================
@@ -563,12 +568,12 @@ class Tokenizer(ABC):
# therefore proceed to check other formats.
if sLine in ("[newpage]", "[new page]"):
breakNext = True
self._breakNext = True
continue
elif sLine == "[vspace]":
tBlocks.append(
(BlockTyp.SKIP, "", "", [], sAlign)
(BlockTyp.SKIP, "", "", [], tStyle)
)
continue
@@ -576,7 +581,7 @@ class Tokenizer(ABC):
nSkip = checkInt(sLine[8:-1], 0)
if nSkip >= 1:
tBlocks.append(
(BlockTyp.SKIP, "", "", [], sAlign)
(BlockTyp.SKIP, "", "", [], tStyle)
)
if nSkip > 1:
tBlocks += (nSkip - 1) * [
@@ -599,14 +604,14 @@ class Tokenizer(ABC):
if cStyle == nwComment.PLAIN and not self._doComments:
continue
if doJustify and not sAlign & BlockFmt.ALIGNED:
sAlign |= BlockFmt.JUSTIFY
if doJustify and not tStyle & BlockFmt.ALIGNED:
tStyle |= BlockFmt.JUSTIFY
if cStyle in (nwComment.SYNOPSIS, nwComment.SHORT, nwComment.PLAIN):
bStyle = COMMENT_STYLE[cStyle]
tLine, tFmt = self._formatComment(bStyle, cKey, cText)
tBlocks.append((
BlockTyp.COMMENT, "", tLine, tFmt, sAlign
BlockTyp.COMMENT, "", tLine, tFmt, tStyle
))
if keepRaw:
rawText.append(f"{aLine}\n")
@@ -627,7 +632,7 @@ class Tokenizer(ABC):
tTag, tLine, tFmt = self._formatMeta(aLine)
if tLine:
tBlocks.append((
BlockTyp.KEYWORD, tTag[1:], tLine, tFmt, sAlign
BlockTyp.KEYWORD, tTag[1:], tLine, tFmt, tStyle
))
if keepRaw:
rawText.append(f"{aLine}\n")
@@ -646,16 +651,16 @@ class Tokenizer(ABC):
nHead += 1
tText = aLine[2:].strip()
tType = BlockTyp.HEAD1 if isPlain else BlockTyp.TITLE
tStyle = BlockFmt.NONE if isPlain else self._titleStyle
sHide = self._hidePart if isPlain else False
if not (isPlain or isNovel and sHide):
tStyle |= self._titleStyle
if isNovel:
if sHide:
tText = ""
tType = BlockTyp.EMPTY
tStyle = BlockFmt.NONE
elif isPlain:
tText = self._hFormatter.apply(self._fmtPart, tText, nHead)
tStyle = self._partStyle
tStyle |= self._partStyle
if isPlain:
self._hFormatter.resetScene()
else:
@@ -682,7 +687,6 @@ class Tokenizer(ABC):
nHead += 1
tText = aLine[3:].strip()
tType = BlockTyp.HEAD2
tStyle = BlockFmt.NONE
sHide = self._hideChapter if isPlain else self._hideUnNum
tFormat = self._fmtChapter if isPlain else self._fmtUnNum
if isNovel:
@@ -693,7 +697,7 @@ class Tokenizer(ABC):
tType = BlockTyp.EMPTY
else:
tText = self._hFormatter.apply(tFormat, tText, nHead)
tStyle = self._chapterStyle
tStyle |= self._chapterStyle
self._hFormatter.resetScene()
self._noSep = True
@@ -719,7 +723,6 @@ class Tokenizer(ABC):
nHead += 1
tText = aLine[4:].strip()
tType = BlockTyp.HEAD3
tStyle = BlockFmt.NONE
sHide = self._hideScene if isPlain else self._hideHScene
tFormat = self._fmtScene if isPlain else self._fmtHScene
if isNovel:
@@ -729,13 +732,13 @@ class Tokenizer(ABC):
tType = BlockTyp.EMPTY
else:
tText = self._hFormatter.apply(tFormat, tText, nHead)
tStyle = self._sceneStyle
tStyle |= self._sceneStyle
if tText == "": # Empty Format
tType = BlockTyp.EMPTY if self._noSep else BlockTyp.SKIP
elif tText == tFormat: # Static Format
tText = "" if self._noSep else tText
tType = BlockTyp.EMPTY if self._noSep else BlockTyp.SEP
tStyle = BlockFmt.NONE if self._noSep else BlockFmt.CENTRE
tStyle |= BlockFmt.NONE if self._noSep else BlockFmt.CENTRE
self._noSep = False
tBlocks.append((
@@ -755,7 +758,6 @@ class Tokenizer(ABC):
nHead += 1
tText = aLine[5:].strip()
tType = BlockTyp.HEAD4
tStyle = BlockFmt.NONE
if isNovel:
if self._hideSection:
tText = ""
@@ -766,7 +768,7 @@ class Tokenizer(ABC):
tType = BlockTyp.SKIP
elif tText == self._fmtSection: # Static Format
tType = BlockTyp.SEP
tStyle = BlockFmt.CENTRE
tStyle |= BlockFmt.CENTRE
tBlocks.append((
tType, f"{tHandle}:T{nHead:04d}", tText, [], tStyle
@@ -803,35 +805,38 @@ class Tokenizer(ABC):
bLine = bLine[:-1].rstrip(" ")
if alnLeft and alnRight:
sAlign |= BlockFmt.CENTRE
tStyle |= BlockFmt.CENTRE
elif alnLeft:
sAlign |= BlockFmt.LEFT
tStyle |= BlockFmt.LEFT
elif alnRight:
sAlign |= BlockFmt.RIGHT
tStyle |= BlockFmt.RIGHT
if indLeft:
sAlign |= BlockFmt.IND_L
tStyle |= BlockFmt.IND_L
if indRight:
sAlign |= BlockFmt.IND_R
tStyle |= BlockFmt.IND_R
# Process formats
tLine, tFmt = self._extractFormats(bLine, hDialog=isNovel)
tBlocks.append((
BlockTyp.TEXT, "", tLine, tFmt, sAlign
BlockTyp.TEXT, "", tLine, tFmt, tStyle
))
if keepRaw:
rawText.append(f"{aLine}\n")
# If we have content, turn off the first page flag
if self._isFirst and len(tBlocks) > 1:
if self._isFirst and tBlocks:
self._isFirst = False # First document has been processed
# Make sure the blocks array doesn't start with a page break
# on the very first page, adding a blank first page.
if (cBlock := tBlocks[1])[4] & BlockFmt.PBB:
tBlocks[1] = (
cBlock[0], cBlock[1], cBlock[2], cBlock[3], cBlock[4] & ~BlockFmt.PBB
)
# on the very first block, adding a blank first page.
for n, cBlock in enumerate(tBlocks):
if cBlock[0] != BlockTyp.EMPTY:
if cBlock[4] & BlockFmt.PBB:
tBlocks[n] = (
cBlock[0], cBlock[1], cBlock[2], cBlock[3], cBlock[4] & ~BlockFmt.PBB
)
break
# Always add an empty line at the end of the file
tBlocks.append(B_EMPTY)
+20 -21
View File
@@ -408,30 +408,29 @@ class ToOdt(Tokenizer):
# Styles
oStyle = ODTParagraphStyle("New")
if tStyle is not None:
if tStyle & BlockFmt.LEFT:
oStyle.setTextAlign("left")
elif tStyle & BlockFmt.RIGHT:
oStyle.setTextAlign("right")
elif tStyle & BlockFmt.CENTRE:
oStyle.setTextAlign("center")
elif tStyle & BlockFmt.JUSTIFY:
oStyle.setTextAlign("justify")
if tStyle & BlockFmt.LEFT:
oStyle.setTextAlign("left")
elif tStyle & BlockFmt.RIGHT:
oStyle.setTextAlign("right")
elif tStyle & BlockFmt.CENTRE:
oStyle.setTextAlign("center")
elif tStyle & BlockFmt.JUSTIFY:
oStyle.setTextAlign("justify")
if tStyle & BlockFmt.PBB:
oStyle.setBreakBefore("page")
if tStyle & BlockFmt.PBA:
oStyle.setBreakAfter("page")
if tStyle & BlockFmt.PBB:
oStyle.setBreakBefore("page")
if tStyle & BlockFmt.PBA:
oStyle.setBreakAfter("page")
if tStyle & BlockFmt.Z_BTM:
oStyle.setMarginBottom("0.000cm")
if tStyle & BlockFmt.Z_TOP:
oStyle.setMarginTop("0.000cm")
if tStyle & BlockFmt.Z_BTM:
oStyle.setMarginBottom("0.000cm")
if tStyle & BlockFmt.Z_TOP:
oStyle.setMarginTop("0.000cm")
if tStyle & BlockFmt.IND_L:
oStyle.setMarginLeft(self._fBlockIndent)
if tStyle & BlockFmt.IND_R:
oStyle.setMarginRight(self._fBlockIndent)
if tStyle & BlockFmt.IND_L:
oStyle.setMarginLeft(self._fBlockIndent)
if tStyle & BlockFmt.IND_R:
oStyle.setMarginRight(self._fBlockIndent)
# Process Text Types
if tType == BlockTyp.TEXT:
+56 -26
View File
@@ -41,7 +41,7 @@ from novelwriter.formats.tokenizer import HEADINGS, Tokenizer
from novelwriter.types import (
QtAlignAbsolute, QtAlignCenter, QtAlignJustify, QtAlignLeft, QtAlignRight,
QtKeepAnchor, QtMoveAnchor, QtPageBreakAfter, QtPageBreakBefore,
QtTransparent, QtVAlignNormal, QtVAlignSub, QtVAlignSuper
QtPropLineHeight, QtTransparent, QtVAlignNormal, QtVAlignSub, QtVAlignSuper
)
logger = logging.getLogger(__name__)
@@ -75,6 +75,7 @@ class ToQTextDocument(Tokenizer):
self._init = False
self._bold = QFont.Weight.Bold
self._normal = QFont.Weight.Normal
self._newPage = False
self._pageSize = QPageSize(QPageSize.PageSizeId.A4)
self._pageMargins = QMarginsF(20.0, 20.0, 20.0, 20.0)
@@ -102,6 +103,11 @@ class ToQTextDocument(Tokenizer):
self._pageMargins = QMarginsF(left, top, right, bottom)
return
def setShowNewPage(self, state: bool) -> None:
"""Add markers for page breaks."""
self._newPage = state
return
##
# Class Methods
##
@@ -149,8 +155,6 @@ class ToQTextDocument(Tokenizer):
# Text Formats
# ============
QtPropLineHeight = QTextBlockFormat.LineHeightTypes.ProportionalHeight
self._blockFmt = QTextBlockFormat()
self._blockFmt.setTopMargin(self._mText[0])
self._blockFmt.setBottomMargin(self._mText[1])
@@ -184,32 +188,32 @@ class ToQTextDocument(Tokenizer):
bFmt.setTopMargin(self._mSep[0])
bFmt.setBottomMargin(self._mSep[1])
if tStyle is not None:
if tStyle & BlockFmt.LEFT:
bFmt.setAlignment(QtAlignLeft)
elif tStyle & BlockFmt.RIGHT:
bFmt.setAlignment(QtAlignRight)
elif tStyle & BlockFmt.CENTRE:
bFmt.setAlignment(QtAlignCenter)
elif tStyle & BlockFmt.JUSTIFY:
bFmt.setAlignment(QtAlignJustify)
if tStyle & BlockFmt.LEFT:
bFmt.setAlignment(QtAlignLeft)
elif tStyle & BlockFmt.RIGHT:
bFmt.setAlignment(QtAlignRight)
elif tStyle & BlockFmt.CENTRE:
bFmt.setAlignment(QtAlignCenter)
elif tStyle & BlockFmt.JUSTIFY:
bFmt.setAlignment(QtAlignJustify)
if tStyle & BlockFmt.PBB:
bFmt.setPageBreakPolicy(QtPageBreakBefore)
if tStyle & BlockFmt.PBA:
bFmt.setPageBreakPolicy(QtPageBreakAfter)
if tStyle & BlockFmt.PBB:
self._insertNewPageMarker(cursor)
bFmt.setPageBreakPolicy(QtPageBreakBefore)
if tStyle & BlockFmt.PBA:
bFmt.setPageBreakPolicy(QtPageBreakAfter)
if tStyle & BlockFmt.Z_BTM:
bFmt.setBottomMargin(0.0)
if tStyle & BlockFmt.Z_TOP:
bFmt.setTopMargin(0.0)
if tStyle & BlockFmt.Z_BTM:
bFmt.setBottomMargin(0.0)
if tStyle & BlockFmt.Z_TOP:
bFmt.setTopMargin(0.0)
if tStyle & BlockFmt.IND_L:
bFmt.setLeftMargin(self._mIndent)
if tStyle & BlockFmt.IND_R:
bFmt.setRightMargin(self._mIndent)
if tStyle & BlockFmt.IND_T:
bFmt.setTextIndent(self._tIndent)
if tStyle & BlockFmt.IND_L:
bFmt.setLeftMargin(self._mIndent)
if tStyle & BlockFmt.IND_R:
bFmt.setRightMargin(self._mIndent)
if tStyle & BlockFmt.IND_T:
bFmt.setTextIndent(self._tIndent)
if tType in (BlockTyp.TEXT, BlockTyp.COMMENT, BlockTyp.KEYWORD):
newBlock(cursor, bFmt)
@@ -228,6 +232,9 @@ class ToQTextDocument(Tokenizer):
newBlock(cursor, bFmt)
cursor.insertText(nwUnicode.U_NBSP, self._charFmt)
if tStyle & BlockFmt.PBA:
self._insertNewPageMarker(cursor)
self._document.blockSignals(False)
return
@@ -386,6 +393,29 @@ class ToQTextDocument(Tokenizer):
return
def _insertNewPageMarker(self, cursor: QTextCursor) -> None:
"""Insert a new page marker."""
if self._newPage:
cursor.insertHtml("<hr width='100%'>")
hFmt = cursor.blockFormat()
hFmt.setBottomMargin(0.0)
hFmt.setLineHeight(75.0, QtPropLineHeight)
cursor.setBlockFormat(hFmt)
bFmt = QTextBlockFormat(self._blockFmt)
bFmt.setAlignment(QtAlignCenter)
bFmt.setTopMargin(0.0)
bFmt.setLineHeight(75.0, QtPropLineHeight)
cFmt = QTextCharFormat(self._charFmt)
cFmt.setFontPointSize(0.75*self._textFont.pointSizeF())
cFmt.setForeground(self._theme.comment)
newBlock(cursor, bFmt)
cursor.insertText(self._project.localLookup("New Page"), cFmt)
return
def _genHeadStyle(self, hType: BlockTyp, hKey: str, rFmt: QTextBlockFormat) -> T_TextStyle:
"""Generate a heading style set."""
mTop, mBottom = self._mHead.get(hType, (0.0, 0.0))
+26 -5
View File
@@ -48,6 +48,7 @@ from novelwriter.core.buildsettings import BuildCollection, BuildSettings
from novelwriter.core.docbuild import NWBuildDocument
from novelwriter.extensions.modified import NIconToggleButton, NIconToolButton, NToolDialog
from novelwriter.extensions.progressbars import NProgressCircle
from novelwriter.extensions.switch import NSwitch
from novelwriter.formats.tokenizer import HeadingFormatter
from novelwriter.formats.toqdoc import ToQTextDocument
from novelwriter.gui.theme import STYLES_FLAT_TABS, STYLES_MIN_TOOLBUTTON
@@ -87,6 +88,7 @@ class GuiManuscript(NToolDialog):
self.setMinimumWidth(CONFIG.pxInt(600))
self.setMinimumHeight(CONFIG.pxInt(500))
iPx = SHARED.theme.baseIconHeight
iSz = SHARED.theme.baseIconSize
wWin = CONFIG.pxInt(900)
hWin = CONFIG.pxInt(600)
@@ -191,15 +193,31 @@ class GuiManuscript(NToolDialog):
self.processBox.addWidget(self.btnBuild, 1, 0)
self.processBox.addWidget(self.btnClose, 1, 1)
# Preview Options
# ===============
self.swtNewPage = NSwitch(self, height=iPx)
self.swtNewPage.setChecked(pOptions.getBool("GuiManuscript", "showNewPage", True))
self.swtNewPage.clicked.connect(self._generatePreview)
self.lblNewPage = QLabel(self.tr("Show Page Breaks"), self)
self.lblNewPage.setBuddy(self.swtNewPage)
# Assemble GUI
# ============
self.docPreview = _PreviewWidget(self)
self.docStats = _StatsWidget(self)
self.docBar = QHBoxLayout()
self.docBar.addWidget(self.docStats, 1, QtAlignTop)
self.docBar.addWidget(self.lblNewPage, 0, QtAlignTop)
self.docBar.addWidget(self.swtNewPage, 0, QtAlignTop)
self.docBar.setContentsMargins(0, 0, 0, 0)
self.docBox = QVBoxLayout()
self.docBox.addWidget(self.docPreview, 1)
self.docBox.addWidget(self.docStats, 0)
self.docBox.addLayout(self.docBar, 0)
self.docBox.setContentsMargins(0, 0, 0, 0)
self.docWdiget = QWidget(self)
@@ -344,6 +362,7 @@ class GuiManuscript(NToolDialog):
return
start = time()
showNewPage = self.swtNewPage.isChecked()
# Make sure editor content is saved before we start
SHARED.saveEditor()
@@ -352,7 +371,7 @@ class GuiManuscript(NToolDialog):
docBuild.queueAll()
self.docPreview.beginNewBuild(len(docBuild))
for step, _ in docBuild.iterBuildPreview():
for step, _ in docBuild.iterBuildPreview(showNewPage):
self.docPreview.buildStep(step + 1)
QApplication.processEvents()
@@ -434,6 +453,7 @@ class GuiManuscript(NToolDialog):
detailsHeight = CONFIG.rpxInt(buildSplit[1])
detailsWidth = CONFIG.rpxInt(self.buildDetails.getColumnWidth())
detailsExpanded = self.buildDetails.getExpandedState()
showNewPage = self.swtNewPage.isChecked()
logger.debug("Saving State: GuiManuscript")
pOptions = SHARED.project.options
@@ -445,6 +465,7 @@ class GuiManuscript(NToolDialog):
pOptions.setValue("GuiManuscript", "detailsHeight", detailsHeight)
pOptions.setValue("GuiManuscript", "detailsWidth", detailsWidth)
pOptions.setValue("GuiManuscript", "detailsExpanded", detailsExpanded)
pOptions.setValue("GuiManuscript", "showNewPage", showNewPage)
pOptions.saveSettings()
return
@@ -945,7 +966,7 @@ class _StatsWidget(QWidget):
self.toggleButton = NIconToggleButton(self, SHARED.theme.baseIconSize, "unfold")
self.toggleButton.toggled.connect(self._toggleView)
self._buildStatsPanel()
self._buildBottomPanel()
self.mainStack = QStackedWidget(self)
self.mainStack.addWidget(self.minWidget)
@@ -1010,8 +1031,8 @@ class _StatsWidget(QWidget):
# Internal Functions
##
def _buildStatsPanel(self) -> None:
"""Build the minimal stats page."""
def _buildBottomPanel(self) -> None:
"""Build the bottom page."""
mPx = CONFIG.pxInt(8)
hPx = CONFIG.pxInt(12)
vPx = CONFIG.pxInt(4)
+7 -2
View File
@@ -24,7 +24,10 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
from __future__ import annotations
from PyQt5.QtCore import Qt
from PyQt5.QtGui import QColor, QFont, QPainter, QTextCharFormat, QTextCursor, QTextFormat
from PyQt5.QtGui import (
QColor, QFont, QPainter, QTextBlockFormat, QTextCharFormat, QTextCursor,
QTextFormat
)
from PyQt5.QtWidgets import QDialog, QDialogButtonBox, QSizePolicy, QStyle
# Qt Alignment Flags
@@ -48,11 +51,13 @@ QtVAlignNormal = QTextCharFormat.VerticalAlignment.AlignNormal
QtVAlignSub = QTextCharFormat.VerticalAlignment.AlignSubScript
QtVAlignSuper = QTextCharFormat.VerticalAlignment.AlignSuperScript
# Qt Page Break
# Qt Text Formats
QtPageBreakBefore = QTextFormat.PageBreakFlag.PageBreak_AlwaysBefore
QtPageBreakAfter = QTextFormat.PageBreakFlag.PageBreak_AlwaysAfter
QtPropLineHeight = QTextBlockFormat.LineHeightTypes.ProportionalHeight
# Qt Painter Types
QtTransparent = QColor(0, 0, 0, 0)
@@ -0,0 +1,38 @@
Jane Doe
Address 1
Address 2
# Test Project A
**By Jane Doe**
Word Count: 11
## Chapter 1
* * *
* * *
## Chapter 2
* * *
* * *
## Chapter 3
* * *
* * *
@@ -0,0 +1,30 @@
Jane Doe
Address 1
Address 2
# Test Project B
**By Jane Doe**
Word Count: 11
* * *
* * *
* * *
* * *
* * *
+41 -8
View File
@@ -31,10 +31,13 @@ import pytest
from novelwriter import CONFIG
from novelwriter.constants import nwConst, nwFiles, nwItemClass
from novelwriter.core.buildsettings import BuildSettings
from novelwriter.core.coretools import (
DocDuplicator, DocMerger, DocSearch, DocSplitter, ProjectBuilder
)
from novelwriter.core.docbuild import NWBuildDocument
from novelwriter.core.project import NWProject
from novelwriter.enum import nwBuildFmt
from tests.mocked import causeOSError
from tests.tools import NWD_IGNORE, XML_IGNORE, C, buildTestProject, cmpFiles
@@ -507,10 +510,6 @@ def testCoreTools_ProjectBuilderA(monkeypatch, fncPath, tstPaths, mockGUI, mockR
"""Create a new project from a project dictionary, with chapters."""
monkeypatch.setattr("uuid.uuid4", lambda *a: uuid.UUID("d0f3fe10-c6e6-4310-8bfd-181eb4224eed"))
projFile = fncPath / "nwProject.nwx"
testFile = tstPaths.outDir / "coreTools_ProjectBuilderA_nwProject.nwx"
compFile = tstPaths.refDir / "coreTools_ProjectBuilderA_nwProject.nwx"
data = {
"name": "Test Project A",
"author": "Jane Doe",
@@ -531,19 +530,34 @@ def testCoreTools_ProjectBuilderA(monkeypatch, fncPath, tstPaths, mockGUI, mockR
builder = ProjectBuilder()
assert builder.buildProject(data) is True
projFile = fncPath / "nwProject.nwx"
testFile = tstPaths.outDir / "coreTools_ProjectBuilderA_nwProject.nwx"
compFile = tstPaths.refDir / "coreTools_ProjectBuilderA_nwProject.nwx"
copyfile(projFile, testFile)
assert cmpFiles(testFile, compFile, ignoreStart=XML_IGNORE)
# Check Content
project = NWProject()
project.openProject(fncPath)
build = BuildSettings()
docBuild = NWBuildDocument(project, build)
docBuild.queueAll()
testFile = tstPaths.outDir / "coreTools_ProjectBuilderA_Project.md"
compFile = tstPaths.refDir / "coreTools_ProjectBuilderA_Project.md"
assert list(docBuild.iterBuildDocument(testFile, nwBuildFmt.EXT_MD))
assert cmpFiles(testFile, compFile, ignoreStart=XML_IGNORE)
@pytest.mark.core
def testCoreTools_ProjectBuilderB(monkeypatch, fncPath, tstPaths, mockGUI, mockRnd):
"""Create a new project from a project dictionary, without chapters."""
monkeypatch.setattr("uuid.uuid4", lambda *a: uuid.UUID("d0f3fe10-c6e6-4310-8bfd-181eb4224eed"))
projFile = fncPath / "nwProject.nwx"
testFile = tstPaths.outDir / "coreTools_ProjectBuilderB_nwProject.nwx"
compFile = tstPaths.refDir / "coreTools_ProjectBuilderB_nwProject.nwx"
data = {
"name": "Test Project B",
"author": "Jane Doe",
@@ -564,9 +578,28 @@ def testCoreTools_ProjectBuilderB(monkeypatch, fncPath, tstPaths, mockGUI, mockR
builder = ProjectBuilder()
assert builder.buildProject(data) is True
projFile = fncPath / "nwProject.nwx"
testFile = tstPaths.outDir / "coreTools_ProjectBuilderB_nwProject.nwx"
compFile = tstPaths.refDir / "coreTools_ProjectBuilderB_nwProject.nwx"
copyfile(projFile, testFile)
assert cmpFiles(testFile, compFile, ignoreStart=XML_IGNORE)
# Check Content
project = NWProject()
project.openProject(fncPath)
build = BuildSettings()
docBuild = NWBuildDocument(project, build)
docBuild.queueAll()
testFile = tstPaths.outDir / "coreTools_ProjectBuilderB_Project.md"
compFile = tstPaths.refDir / "coreTools_ProjectBuilderB_Project.md"
assert list(docBuild.iterBuildDocument(testFile, nwBuildFmt.EXT_MD))
assert cmpFiles(testFile, compFile, ignoreStart=XML_IGNORE)
@pytest.mark.core
def testCoreTools_ProjectBuilderCopyPlain(monkeypatch, caplog, mockGUI, prjLipsum, fncPath):
+1 -1
View File
@@ -88,7 +88,7 @@ def testCoreDocBuild_OpenDocument(monkeypatch, mockGUI, prjLipsum, fncPath, tstP
build.unpack(BUILD_CONF)
docBuild = NWBuildDocument(project, build)
docBuild.setBuildOutline(True)
docBuild._outline = True
docBuild.queueAll()
assert docBuild._outline is True
+1 -1
View File
@@ -48,7 +48,7 @@ def testToolManuscript_Init(monkeypatch, qtbot, nwGUI, projPath, mockRnd):
buildTestProject(nwGUI, projPath)
nwGUI.openProject(projPath)
SHARED.project.storage.getDocument(C.hChapterDoc).writeDocument("## A Chapter\n\n\t\tHi")
allText = "New Novel\nBy Jane Doe\nA Chapter\n\t\tHi"
allText = "New Novel\nBy Jane Doe\n\nNew Page\nA Chapter\n\t\tHi"
nwGUI.mainMenu.aBuildManuscript.activate(QAction.ActionEvent.Trigger)
qtbot.waitUntil(lambda: SHARED.findTopLevelWidget(GuiManuscript) is not None, timeout=1000)