Merge pull request #221 from vkbo/build_updates

Build Project Updates
This commit is contained in:
Veronica K. Berglyd Olsen
2020-05-23 21:21:35 +02:00
committed by GitHub
6 changed files with 293 additions and 168 deletions
+4
View File
@@ -2,6 +2,10 @@
## Version 0.6 [2020-xx-xx]
**Bugfixes**
* Fixed a bug in validation of `@tag:` meta tags where one or more spaces before the `:` would still pass as valid tags, but the keyword index array would be missing those spaces in its counter. This mainly affected the highlighting of keywords, which would be misaligned. PR #206
**User Interface**
* The Export Tool has been removed and replaced by a new tool called "Build Novel Project". The new tool has the same filtering options as the Export Tool, but with more formatting options for titles. It also has a preview window to display the generated document. A Save As button provides exports to HTML, novelWriter Markdown. plain text, PDF and Open Document format. LaTeX export has not been ported over, and interfacing with Pandoc is no longer supported either. Although, as before, the HTML export can be converted with Pandoc to other formats outside of novelWriter. The new tool also supports printing. PR #204
+95 -30
View File
@@ -48,12 +48,16 @@ class ToHtml(Tokenizer):
"<" : "&lt;",
">" : "&gt;",
"&" : "&amp;",
"\t" : "&emsp;",
"\t" : "&emsp;"*2,
nwUnicode.U_ENDASH : nwUnicode.H_ENDASH,
nwUnicode.U_EMDASH : nwUnicode.H_EMDASH,
nwUnicode.U_HELLIP : nwUnicode.H_HELLIP,
nwUnicode.U_NBSP : nwUnicode.H_NBSP,
}
self.revDict = {}
self.reReplace = []
self.reReverse = []
self._buildRegEx()
return
@@ -71,6 +75,7 @@ class ToHtml(Tokenizer):
self.doKeywords = True
self.doComments = doComments
self.repDict["\t"] = "&nbsp;"*8
self._buildRegEx()
return
##
@@ -82,10 +87,9 @@ class ToHtml(Tokenizer):
characters into their respective HTML entities.
"""
Tokenizer.doAutoReplace(self)
xRep = re.compile("|".join([re.escape(k) for k in self.repDict.keys()]), flags=re.DOTALL)
self.theText = xRep.sub(lambda x: self.repDict[x.group(0)], self.theText)
self.theText = self.reReplace.sub(
lambda x: self.repDict[x.group(0)], self.theText
)
return
def doPostProcessing(self):
@@ -95,18 +99,15 @@ class ToHtml(Tokenizer):
if self.genMode == self.M_PREVIEW:
# Doesn't matter for preview as we don't use the markdown
return
revDict = dict(map(reversed, self.repDict.items()))
xRep = re.compile("|".join([re.escape(k) for k in revDict.keys()]), flags=re.DOTALL)
self.theMarkdown = xRep.sub(lambda x: revDict[x.group(0)], self.theMarkdown)
self.theMarkdown = self.reReverse.sub(
lambda x: self.revDict[x.group(0)], self.theMarkdown
)
return
def doConvert(self):
"""Convert the list of text tokens into a HTML document saved
to theResult.
"""
htmlTags = {
self.FMT_B_B : "<strong>",
self.FMT_B_E : "</strong>",
@@ -116,11 +117,32 @@ class ToHtml(Tokenizer):
self.FMT_U_E : "</u>",
}
if self.isNovel and self.genMode != self.M_PREVIEW:
# For novel files for export, we bump the titles one level
# up as this is more useful for printing and word processor
# imports.
h1 = "h1 class='title'"
h2 = "h1"
h3 = "h2"
h4 = "h3"
else:
h1 = "h1"
h2 = "h2"
h3 = "h3"
h4 = "h4"
alignHead = self.A_LEFT
if self.doJustify:
alignPar = self.A_JUSTIFY
else:
alignPar = self.A_LEFT
self.theResult = ""
thisPar = []
parStyle = ""
parStyle = None
tmpResult = []
hasHardBreak = False
for tType, tText, tFormat, tStyle in self.theTokens:
# Styles
@@ -136,20 +158,16 @@ class ToHtml(Tokenizer):
aStyle.append("text-align: justify;")
if tStyle & self.A_PBB:
aStyle.append("page-break-before: always;")
if tStyle & self.A_PBB_L:
aStyle.append("page-break-before: left;")
if tStyle & self.A_PBB_R:
aStyle.append("page-break-before: right;")
if tStyle & self.A_PBB_AV:
aStyle.append("page-break-before: avoid;")
if tStyle & self.A_PBB_NO:
aStyle.append("page-break-before: never;")
if tStyle & self.A_PBA:
aStyle.append("page-break-after: always;")
if tStyle & self.A_PBA_L:
aStyle.append("page-break-after: left;")
if tStyle & self.A_PBA_R:
aStyle.append("page-break-after: right;")
if tStyle & self.A_PBA_AV:
aStyle.append("page-break-after: avoid;")
if tStyle & self.A_PBA_NO:
aStyle.append("page-break-after: never;")
if len(aStyle) > 0:
hStyle = " style='%s'" % (" ".join(aStyle))
@@ -158,41 +176,54 @@ class ToHtml(Tokenizer):
# Process TextType
if tType == self.T_EMPTY:
if parStyle is None:
parStyle = ""
if hasHardBreak:
parClass = " class='break'"
else:
parClass = ""
if len(thisPar) > 0:
tTemp = "".join(thisPar)
tmpResult.append("<p%s>%s</p>\n" % (parStyle, tTemp.rstrip()))
tmpResult.append("<p%s%s>%s</p>\n" % (parStyle, parClass, tTemp.rstrip()))
thisPar = []
parStyle = ""
parStyle = None
hasHardBreak = False
elif tType == self.T_TITLE:
tHead = tText.replace(r"\\", "<br/>")
tmpResult.append("<h1 class='title'%s>%s</h1>\n" % (hStyle, tHead))
elif tType == self.T_HEAD1:
tHead = tText.replace(r"\\", "<br/>")
tmpResult.append("<h1%s>%s</h1>\n" % (hStyle, tHead))
tmpResult.append("<%s%s>%s</%s>\n" % (h1, hStyle, tHead, h1))
elif tType == self.T_HEAD2:
tHead = tText.replace(r"\\", "<br/>")
tmpResult.append("<h2%s>%s</h2>\n" % (hStyle, tHead))
tmpResult.append("<%s%s>%s</%s>\n" % (h2, hStyle, tHead, h2))
elif tType == self.T_HEAD3:
tHead = tText.replace(r"\\", "<br/>")
tmpResult.append("<h3%s>%s</h3>\n" % (hStyle, tHead))
tmpResult.append("<%s%s>%s</%s>\n" % (h3, hStyle, tHead, h3))
elif tType == self.T_HEAD4:
tHead = tText.replace(r"\\", "<br/>")
tmpResult.append("<h4%s>%s</h4>\n" % (hStyle, tHead))
tmpResult.append("<%s%s>%s</%s>\n" % (h4, hStyle, tHead, h4))
elif tType == self.T_SEP:
tmpResult.append("<p%s>%s</p>\n" % (hStyle, tText))
tmpResult.append("<p class='sep'>%s</p>\n" % tText)
elif tType == self.T_SKIP:
tmpResult.append("<p%s>&nbsp;</p>\n" % hStyle)
tmpResult.append("<p class='skip'>&nbsp;</p>\n")
elif tType == self.T_TEXT:
tTemp = tText
parStyle = hStyle
if parStyle is None:
parStyle = hStyle
for xPos, xLen, xFmt in reversed(tFormat):
tTemp = tTemp[:xPos]+htmlTags[xFmt]+tTemp[xPos+xLen:]
if tText.endswith(" "):
thisPar.append(tTemp.rstrip()+"<br/>")
hasHardBreak = True
else:
thisPar.append(tTemp.rstrip()+" ")
@@ -210,6 +241,29 @@ class ToHtml(Tokenizer):
return
def getStylesheet(self):
"""Generate a stylesheet appropriate for the current settings.
"""
theStyles = []
if self.doJustify:
theStyles.append(r"p {text-align: justify;}")
else:
theStyles.append(r"p {text-align: left;}")
theStyles.append(r"h1, h2 {color: rgb(66, 113, 174);}")
theStyles.append(r"h3, h4 {color: rgb(50, 50, 50);}")
theStyles.append(r"h1, h2, h3, h4 {page-break-after: avoid;}")
theStyles.append(r".title {font-size: 2.5em;}")
theStyles.append(r".tags {color: rgb(245, 135, 31); font-weight: bold;}")
theStyles.append(r".break {text-align: left;}")
theStyles.append(r".sep {text-align: center; margin-top: 1em; margin-bottom: 1em;}")
theStyles.append(r".skip {margin-top: 1em; margin-bottom: 1em;}")
theStyles.append(r".synopsis {font-style: italic;}")
theStyles.append(r".comment {font-style: italic; color: rgb(100, 100, 100);}")
return theStyles
##
# Internal Functions
##
@@ -233,7 +287,6 @@ class ToHtml(Tokenizer):
def _formatKeywords(self, tText):
"""Apply HTML formatting to keywords.
"""
tText = "@"+tText
isValid, theBits, thePos = self.theParent.theIndex.scanThis(tText)
if not isValid or not theBits:
@@ -263,4 +316,16 @@ class ToHtml(Tokenizer):
return "<div>%s</div>" % retText
def _buildRegEx(self):
"""Build the regular expressions
"""
self.revDict = dict(map(reversed, self.repDict.items()))
self.reReplace = re.compile(
"|".join([re.escape(k) for k in self.repDict.keys()]), flags=re.DOTALL
)
self.reReverse = re.compile(
"|".join([re.escape(k) for k in self.revDict.keys()]), flags=re.DOTALL
)
return
# END Class ToHtml
+74 -48
View File
@@ -34,7 +34,7 @@ from PyQt5.QtCore import QRegularExpression
from nw.core.document import NWDoc
from nw.core.tools import numberToWord
from nw.constants import nwItemLayout
from nw.constants import nwItemLayout, nwItemType
logger = logging.getLogger(__name__)
@@ -51,26 +51,26 @@ class Tokenizer():
T_SYNOPSIS = 2 # Synopsis comment
T_COMMENT = 3 # Comment line
T_KEYWORD = 4 # Command line
T_HEAD1 = 5 # Header 1 (title)
T_HEAD2 = 6 # Header 2 (chapter)
T_HEAD3 = 7 # Header 3 (scene)
T_HEAD4 = 8 # Header 4
T_TEXT = 9 # Text line
T_SEP = 10 # Scene separator
T_SKIP = 11 # Paragraph break
T_TITLE = 5 # Title
T_HEAD1 = 6 # Header 1
T_HEAD2 = 7 # Header 2
T_HEAD3 = 8 # Header 3
T_HEAD4 = 9 # Header 4
T_TEXT = 10 # Text line
T_SEP = 11 # Scene separator
T_SKIP = 12 # Paragraph break
A_LEFT = 1 # Left aligned
A_RIGHT = 2 # Right aligned
A_CENTRE = 4 # Centred
A_JUSTIFY = 8 # Justified
A_PBB = 16 # Page break before
A_PBB_L = 32 # Page break before, left
A_PBB_R = 64 # Page break before, right
A_PBB_AV = 128 # Page break, avoid
A_PBA = 256 # Page break after
A_PBA_L = 512 # Page break after, left
A_PBA_R = 1024 # Page break after, right
A_PBA_AV = 2048 # Page break, avoid
A_NONE = 0 # No special style
A_LEFT = 1 # Left aligned
A_RIGHT = 2 # Right aligned
A_CENTRE = 4 # Centred
A_JUSTIFY = 8 # Justified
A_PBB = 16 # Page break before always
A_PBB_AV = 32 # Page break before avoid
A_PBB_NO = 64 # Page break before never
A_PBA = 128 # Page break after always
A_PBA_AV = 256 # Page break after avoid
A_PBA_NO = 512 # Page break after avoid
def __init__(self, theProject, theParent):
@@ -198,6 +198,25 @@ class Tokenizer():
# Class Methods
##
def addRootHeading(self, theHandle):
"""Add a heading at the start if a new root folder.
"""
theItem = self.theProject.projTree[theHandle]
if theItem is None:
return False
if theItem.itemType != nwItemType.ROOT:
return False
theTitle = "Notes: %s" % theItem.itemName
self.theTokens = []
self.theTokens.append((
self.T_TITLE, theTitle, None, self.A_PBB | self.A_CENTRE
))
self.theMarkdown = "# %s\n\n" % theTitle
return True
def setText(self, theHandle, theText=None):
"""Set the text for the tokenizer from a handle. If theText is
not set, load it from the file.
@@ -205,6 +224,8 @@ class Tokenizer():
self.theHandle = theHandle
self.theItem = self.theProject.projTree[theHandle]
if self.theItem is None:
return
if theText is not None:
# If the text is set, just use that
@@ -283,11 +304,6 @@ class Tokenizer():
[None, self.FMT_U_B, None, self.FMT_U_E]
)]
if self.doJustify:
defAlign = self.A_JUSTIFY
else:
defAlign = self.A_LEFT
self.theTokens = []
self.theMarkdown = ""
tmpMarkdown = []
@@ -296,7 +312,7 @@ class Tokenizer():
# Tag lines starting with specific characters
if len(aLine.strip()) == 0:
self.theTokens.append((
self.T_EMPTY, "", None, None
self.T_EMPTY, "", None, self.A_NONE
))
tmpMarkdown.append("\n")
@@ -304,45 +320,45 @@ class Tokenizer():
cLine = aLine[1:].strip()
if cLine.lower().startswith("synopsis:"):
self.theTokens.append((
self.T_SYNOPSIS, cLine[9:].strip(), None, defAlign
self.T_SYNOPSIS, cLine[9:].strip(), None, self.A_NONE
))
if self.doSynopsis:
tmpMarkdown.append("%s\n" % aLine)
else:
self.theTokens.append((
self.T_COMMENT, aLine[1:].strip(), None, defAlign
self.T_COMMENT, aLine[1:].strip(), None, self.A_NONE
))
if self.doComments:
tmpMarkdown.append("%s\n" % aLine)
elif aLine[0] == "@":
self.theTokens.append((
self.T_KEYWORD, aLine[1:].strip(), None, self.A_LEFT
self.T_KEYWORD, aLine[1:].strip(), None, self.A_NONE
))
if self.doKeywords:
tmpMarkdown.append("%s\n" % aLine)
elif aLine[:2] == "# ":
self.theTokens.append((
self.T_HEAD1, aLine[2:].strip(), None, self.A_LEFT | self.A_PBB
self.T_HEAD1, aLine[2:].strip(), None, self.A_NONE
))
tmpMarkdown.append("%s\n" % aLine)
elif aLine[:3] == "## ":
self.theTokens.append((
self.T_HEAD2, aLine[3:].strip(), None, self.A_LEFT | self.A_PBA_AV
self.T_HEAD2, aLine[3:].strip(), None, self.A_NONE
))
tmpMarkdown.append("%s\n" % aLine)
elif aLine[:4] == "### ":
self.theTokens.append((
self.T_HEAD3, aLine[4:].strip(), None, self.A_LEFT | self.A_PBA_AV
self.T_HEAD3, aLine[4:].strip(), None, self.A_NONE
))
tmpMarkdown.append("%s\n" % aLine)
elif aLine[:5] == "#### ":
self.theTokens.append((
self.T_HEAD4, aLine[5:].strip(), None, self.A_LEFT | self.A_PBA_AV
self.T_HEAD4, aLine[5:].strip(), None, self.A_NONE
))
tmpMarkdown.append("%s\n" % aLine)
@@ -367,13 +383,13 @@ class Tokenizer():
# sorted by position
fmtPos = sorted(fmtPos, key=itemgetter(0))
self.theTokens.append((
self.T_TEXT, aLine, fmtPos, defAlign
self.T_TEXT, aLine, fmtPos, self.A_NONE
))
tmpMarkdown.append("%s\n" % aLine)
# Always add an empty line at the end
self.theTokens.append((
self.T_EMPTY, "", None, None
self.T_EMPTY, "", None, self.A_NONE
))
tmpMarkdown.append("\n")
@@ -391,8 +407,8 @@ class Tokenizer():
if self.isNone or self.isNote:
return
# For novel files, we need to handle chapter numbering and scene
# breaks
# For novel files, we need to handle chapter numbering, scene
# numbering, and scene breaks
if self.isNovel:
for n in range(len(self.theTokens)):
@@ -410,7 +426,7 @@ class Tokenizer():
tText = self._formatHeading(self.fmtTitle, tText)
self.theTokens[n] = (
tType, tText, None, self.A_LEFT | self.A_PBB_R
tType, tText, None, self.A_NONE
)
elif tType == self.T_HEAD2:
@@ -426,7 +442,7 @@ class Tokenizer():
# Format the chapter header
self.theTokens[n] = (
tType, tText, None, self.A_LEFT | self.A_PBB_R
tType, tText, None, self.A_PBB
)
# Set scene variables
@@ -443,21 +459,21 @@ class Tokenizer():
tTemp = self._formatHeading(self.fmtScene, tText)
if tTemp == "" and self.hideScene:
self.theTokens[n] = (
self.T_EMPTY, "", None, None
self.T_EMPTY, "", None, self.A_NONE
)
elif tTemp == "" and not self.hideScene:
if self.firstScene:
self.theTokens[n] = (
self.T_EMPTY, "", None, None
self.T_EMPTY, "", None, self.A_NONE
)
else:
self.theTokens[n] = (
self.T_SKIP, "", None, None
self.T_SKIP, "", None, self.A_NONE
)
elif tTemp == self.fmtScene:
if self.firstScene:
self.theTokens[n] = (
self.T_EMPTY, "", None, None
self.T_EMPTY, "", None, self.A_NONE
)
else:
self.theTokens[n] = (
@@ -465,7 +481,7 @@ class Tokenizer():
)
else:
self.theTokens[n] = (
tType, tTemp, None, self.A_LEFT | self.A_PBA_AV
tType, tTemp, None, self.A_NONE
)
# Definitely no longer the first scene
@@ -478,11 +494,11 @@ class Tokenizer():
tTemp = self._formatHeading(self.fmtSection, tText)
if tTemp == "" and self.hideSection:
self.theTokens[n] = (
self.T_EMPTY, "", None, None
self.T_EMPTY, "", None, self.A_NONE
)
elif tTemp == "" and not self.hideSection:
self.theTokens[n] = (
self.T_SKIP, "", None, None
self.T_SKIP, "", None, self.A_NONE
)
elif tTemp == self.fmtSection:
self.theTokens[n] = (
@@ -490,18 +506,28 @@ class Tokenizer():
)
else:
self.theTokens[n] = (
tType, tTemp, None, self.A_LEFT | self.A_PBA_AV
tType, tTemp, None, self.A_NONE
)
# For title page and partitions, we need to centre all text.
# For partition, we also add a page break before, and for
# both types we always add a page break after the content.
# We also swap header level 1 with a title type instead.
if self.isTitle or self.isPart:
for n, tToken in enumerate(self.theTokens):
tType = tToken[0]
tText = tToken[1]
tFormat = tToken[2]
if self.isTitle:
if tType == self.T_HEAD1:
if self.isTitle:
self.theTokens[n] = (
self.T_TITLE, tText, tFormat, self.A_PBB_NO | self.A_CENTRE
)
else:
self.theTokens[n] = (
tType, tText, tFormat, self.A_PBB | self.A_CENTRE
)
else:
self.theTokens[n] = (
tType, tText, tFormat, self.A_CENTRE
)
+96 -83
View File
@@ -34,7 +34,7 @@ from time import time
from PyQt5.QtCore import Qt, QByteArray
from PyQt5.QtPrintSupport import QPrinter, QPrintPreviewDialog
from PyQt5.QtGui import (
QTextOption, QPalette, QColor, QTextDocumentWriter
QTextOption, QPalette, QColor, QTextDocumentWriter, QFont
)
from PyQt5.QtWidgets import (
QDialog, QVBoxLayout, QHBoxLayout, QTextBrowser, QPushButton, QLabel,
@@ -44,7 +44,7 @@ from PyQt5.QtWidgets import (
from nw.gui.additions import QSwitch
from nw.core import ToHtml
from nw.constants import (
nwConst, nwFiles, nwAlert, nwItemType, nwItemLayout, nwItemClass
nwAlert, nwItemType, nwItemLayout, nwItemClass
)
logger = logging.getLogger(__name__)
@@ -69,9 +69,9 @@ class GuiBuildNovel(QDialog):
self.theTheme = theParent.theTheme
self.optState = self.theProject.optState
self.htmlText = [] # List of html document
self.nwdText = [] # List of markdown documents
self.textLayout = [] # List of nwItemLayout entries
self.htmlText = [] # List of html document
self.htmlStyle = [] # List of html styles
self.nwdText = [] # List of markdown documents
self.setWindowTitle("Build Novel Project")
self.setMinimumWidth(800)
@@ -320,11 +320,23 @@ class GuiBuildNovel(QDialog):
tStart = time()
self.htmlText = []
self.htmlStyle = []
self.nwdText = []
self.textLayout = []
for nItt, tItem in enumerate(self.theProject.projTree):
if self._checkInclude(tItem, noteFiles, novelFiles, ignoreFlag):
noteRoot = noteFiles
noteRoot &= tItem.itemType == nwItemType.ROOT
noteRoot &= tItem.itemClass != nwItemClass.NOVEL
if noteRoot:
# Add headers for root folders of notes
makeHtml.addRootHeading(tItem.itemHandle)
makeHtml.doConvert()
self.htmlText.append(makeHtml.getResult())
self.nwdText.append(makeHtml.getFilteredMarkdown())
elif self._checkInclude(tItem, noteFiles, novelFiles, ignoreFlag):
makeHtml.setText(tItem.itemHandle)
makeHtml.doAutoReplace()
makeHtml.tokenizeText()
@@ -333,16 +345,17 @@ class GuiBuildNovel(QDialog):
makeHtml.doPostProcessing()
self.htmlText.append(makeHtml.getResult())
self.nwdText.append(makeHtml.getFilteredMarkdown())
self.textLayout.append(tItem.itemLayout)
# Update progress bar, also for skipped items
self.buildProgress.setValue(nItt+1)
tEnd = time()
logger.debug("Built project in %.3f ms" % (1000*(tEnd-tStart)))
self.htmlStyle = makeHtml.getStylesheet()
# Load the preview document with the html data
self.docView.setHtml("".join(self.htmlText))
self.docView.setStyleSheet(self.htmlStyle)
self.docView.setContent(self.htmlText)
return
@@ -457,58 +470,53 @@ class GuiBuildNovel(QDialog):
return False
# Do the actual writing
wSuccess = False
errMsg = ""
if outTool == "Qt":
docWriter = QTextDocumentWriter()
docWriter.setFileName(savePath)
docWriter.setFormat(byteFmt)
if docWriter.write(self.docView.qDocument):
self.theParent.makeAlert(
"Document successfully written in %s format to file: %s" % (
textFmt, savePath
), nwAlert.INFO
)
else:
self.theParent.makeAlert(
"Failed to write document in %s format to file: %s" % (
textFmt, savePath
), nwAlert.ERROR
)
wSuccess = docWriter.write(self.docView.qDocument)
elif outTool == "NW":
try:
with open(savePath, mode="w", encoding="utf8") as outFile:
if theFormat == self.FMT_HTM:
# Write novelWriter HTML data
outFile.write("<!DOCTYPE html>\n")
outFile.write("<html>\n")
outFile.write("<head>\n")
outFile.write("<meta charset='utf-8'>\n")
outFile.write("</head>\n")
outFile.write("<body>\n")
outFile.write("<article style='width: 800px; margin: 40px auto'>\n")
for aLine in self.htmlText:
outFile.write(aLine)
outFile.write("</article>\n")
outFile.write("</body>\n")
outFile.write("</html>\n")
theStyle = self.htmlStyle.copy()
theStyle.append(r"article {width: 800px; margin: 40px auto;}")
theHtml = (
"<!DOCTYPE html>\n"
"<html>\n"
"<head>\n"
"<meta charset='utf-8'>\n"
"<title>{projTitle:s}</title>\n"
"</head>\n"
"<style>\n"
"{htmlStyle:s}\n"
"</style>\n"
"<body>\n"
"<article>\n"
"{bodyText:s}\n"
"</article>\n"
"</body>\n"
"</html>\n"
).format(
projTitle = self.theProject.projName,
htmlStyle = "\n".join(theStyle),
bodyText = "".join(self.htmlText),
)
outFile.write(theHtml)
elif theFormat == self.FMT_NWD:
# Write novelWriter markdown data
for aLine in self.nwdText:
outFile.write(aLine)
self.theParent.makeAlert(
"Document successfully written in %s format to file: %s" % (
textFmt, savePath
), nwAlert.INFO
)
wSuccess = True
except Exception as e:
self.theParent.makeAlert(
"Failed to write document in %s format to file: %s" % (
textFmt, str(e)
), nwAlert.ERROR
)
errMsg = str(e)
elif outTool == "QtPrint" and theFormat == self.FMT_PDF:
try:
@@ -520,23 +528,29 @@ class GuiBuildNovel(QDialog):
thePrinter.setColorMode(QPrinter.Color)
thePrinter.setOutputFileName(savePath)
self.docView.qDocument.print(thePrinter)
self.theParent.makeAlert(
"Document successfully written in %s format to file: %s" % (
textFmt, savePath
), nwAlert.INFO
)
wSuccess = True
except Exception as e:
self.theParent.makeAlert(
"Failed to write document in %s format to file: %s" % (
textFmt, str(e)
), nwAlert.ERROR
)
errMsg - str(e)
else:
return False
errMsg = "Unknown format"
return True
# Report to user
if wSuccess:
self.theParent.makeAlert(
"%s file successfully written to:<br> %s" % (
textFmt, savePath
), nwAlert.INFO
)
else:
self.theParent.makeAlert(
"Failed to write %s file. %s" % (
textFmt, errMsg
), nwAlert.ERROR
)
return wSuccess
def _printDocument(self):
"""Open the print preview dialog.
@@ -550,7 +564,6 @@ class GuiBuildNovel(QDialog):
"""Connect the print preview painter to the document viewer.
"""
thePrinter.setOrientation(QPrinter.Portrait)
thePrinter.setOutputFormat(QPrinter.NativeFormat | QPrinter.PdfFormat)
self.docView.qDocument.print(thePrinter)
return
@@ -612,7 +625,8 @@ class GuiBuildNovel(QDialog):
if path.isfile(docPath):
with open(docPath, mode="r", encoding="utf8") as inFile:
helpText = inFile.read()
self.docView.setText(helpText)
self.docView.setStyleSheet()
self.docView.setContent(helpText)
else:
self.theParent.makeAlert(
"Could not open help text file for Build Project.", nwAlert.ERROR
@@ -638,6 +652,14 @@ class GuiBuildNovelDocView(QTextBrowser):
self.qDocument = self.document()
self.qDocument.setDocumentMargin(self.mainConf.textMargin)
theFont = QFont()
if self.mainConf.textFont is None:
# If none is defined, set the default back to config
self.mainConf.textFont = self.qDocument.defaultFont().family()
theFont.setFamily(self.mainConf.textFont)
theFont.setPointSize(self.mainConf.textSize)
self.setFont(theFont)
theOpt = QTextOption()
if self.mainConf.doJustify:
theOpt.setAlignment(Qt.AlignJustify)
@@ -648,7 +670,7 @@ class GuiBuildNovelDocView(QTextBrowser):
docPalette.setColor(QPalette.Text, QColor( 0, 0, 0))
self.setPalette(docPalette)
self._makeStyleSheet()
self.setStyleSheet()
self.show()
@@ -656,35 +678,26 @@ class GuiBuildNovelDocView(QTextBrowser):
return
def setText(self, theText):
def setContent(self, theText):
"""Set the content, either from text or list of text.
"""
if isinstance(theText, list):
theText = "".join(theText)
theText = theText.replace("&emsp;","&nbsp;"*4)
self.setHtml(theText)
return
##
# Internal Functions
##
def setStyleSheet(self, theStyles=[]):
"""Set the stylesheet for the preview document.
"""
if not theStyles:
theStyles.append(r"h1, h2 {color: rgb(66, 113, 174);}")
theStyles.append(r"h3, h4 {color: rgb(50, 50, 50);}")
theStyles.append(r"a {color: rgb(137, 89, 168);}")
theStyles.append(r"mark {background-color: rgb(240, 198, 116);}")
theStyles.append(r".tags {color: rgb(245, 135, 31); font-weight: bold;}")
def _makeStyleSheet(self):
styleSheet = (
"h1, h2 {"
" color: rgb(66, 113, 174);"
"}\n"
"h3, h4 {"
" color: rgb(50, 50, 50);"
"}\n"
"a {"
" color: rgb(137, 89, 168);"
"}\n"
"mark {"
" background-color: rgb(240, 198, 116);"
"}\n"
".tags {"
" color: rgb(245, 135, 31);"
" font-wright: bold;"
"}\n"
)
self.qDocument.setDefaultStyleSheet(styleSheet)
self.qDocument.setDefaultStyleSheet("\n".join(theStyles))
return
@@ -0,0 +1,4 @@
%%~ edca4be2fcaf8:7031beac91f75:Part 1
# Part One
The first part.
+20 -7
View File
@@ -1,5 +1,5 @@
<?xml version='1.0' encoding='utf-8'?>
<novelWriterXML appVersion="0.5" hexVersion="0x000500f0" fileVersion="1.0" saveCount="130" autoCount="17" timeStamp="2020-05-11 19:23:21">
<novelWriterXML appVersion="0.5.2" hexVersion="0x000502f0" fileVersion="1.0" saveCount="158" autoCount="21" timeStamp="2020-05-23 21:12:53">
<project>
<name>Sample Project</name>
<title>Sample Project</title>
@@ -10,7 +10,7 @@
<settings>
<spellCheck>True</spellCheck>
<autoOutline>True</autoOutline>
<lastEdited>96b68994dfa3d</lastEdited>
<lastEdited>636b6aa9b697b</lastEdited>
<lastViewed>6a2d6d5f4f401</lastViewed>
<lastWordCount>875</lastWordCount>
<autoReplace>
@@ -20,12 +20,12 @@
</autoReplace>
<titleFormat>
<title>%title%</title>
<chapter>Chapter %chnum%.\\%title%</chapter>
<chapter>Chapter %chnum%: %title%</chapter>
<unnumbered>%title%</unnumbered>
<scene>Scene %chnum%.%scnum%: %title%</scene>
<scene>* * *</scene>
<section></section>
<withSynopsis>True</withSynopsis>
<withComments>False</withComments>
<withComments>True</withComments>
<withKeywords>False</withKeywords>
</titleFormat>
<status>
@@ -44,7 +44,7 @@
<entry blue="175" green="0" red="117">Main</entry>
</importance>
</settings>
<content count="20">
<content count="21">
<item handle="7031beac91f75" order="0" parent="None">
<name>Novel</name>
<type>ROOT</type>
@@ -65,7 +65,20 @@
<paraCount>2</paraCount>
<cursorPos>78</cursorPos>
</item>
<item handle="e7ded148d6e4a" order="1" parent="7031beac91f75">
<item handle="edca4be2fcaf8" order="1" parent="7031beac91f75">
<name>Part 1</name>
<type>FILE</type>
<class>NOVEL</class>
<status>New</status>
<expanded>False</expanded>
<exported>True</exported>
<layout>PARTITION</layout>
<charCount>0</charCount>
<wordCount>0</wordCount>
<paraCount>0</paraCount>
<cursorPos>0</cursorPos>
</item>
<item handle="e7ded148d6e4a" order="2" parent="7031beac91f75">
<name>A Folder</name>
<type>FOLDER</type>
<class>NOVEL</class>