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