Update build dialog and make doc build class compatible with build settings

This commit is contained in:
Veronica Berglyd Olsen
2023-05-22 21:58:05 +02:00
parent 854cc77220
commit 2592423637
4 changed files with 161 additions and 81 deletions
+33 -6
View File
@@ -63,12 +63,14 @@ SETTINGS_TEMPLATE = {
"text.includeComments": (bool, False), "text.includeComments": (bool, False),
"text.includeKeywords": (bool, False), "text.includeKeywords": (bool, False),
"text.includeBody": (bool, True), "text.includeBody": (bool, True),
"text.addNoteHeadings": (bool, True),
"format.buildLang": (str, "en_GB"), "format.buildLang": (str, "en_GB"),
"format.textFont": (str, ""), "format.textFont": (str, ""),
"format.textSize": (int, 12), "format.textSize": (int, 12),
"format.lineHeight": (float, 1.15, 0.75, 3.0), "format.lineHeight": (float, 1.15, 0.75, 3.0),
"format.justifyText": (bool, False), "format.justifyText": (bool, False),
"format.stripUnicode": (bool, False), "format.stripUnicode": (bool, False),
"format.replaceTabs": (bool, False),
"odt.addColours": (bool, True), "odt.addColours": (bool, True),
"html.addStyles": (bool, False), "html.addStyles": (bool, False),
} }
@@ -93,6 +95,7 @@ SETTINGS_LABELS = {
"text.includeComments": QT_TRANSLATE_NOOP("Builds", "Comments"), "text.includeComments": QT_TRANSLATE_NOOP("Builds", "Comments"),
"text.includeKeywords": QT_TRANSLATE_NOOP("Builds", "Keywords"), "text.includeKeywords": QT_TRANSLATE_NOOP("Builds", "Keywords"),
"text.includeBody": QT_TRANSLATE_NOOP("Builds", "Body Text"), "text.includeBody": QT_TRANSLATE_NOOP("Builds", "Body Text"),
"text.addNoteHeadings": QT_TRANSLATE_NOOP("Builds", "Add Titles for Notes"),
"format": QT_TRANSLATE_NOOP("Builds", "Text Format"), "format": QT_TRANSLATE_NOOP("Builds", "Text Format"),
"format.buildLang": QT_TRANSLATE_NOOP("Builds", "Build Language"), "format.buildLang": QT_TRANSLATE_NOOP("Builds", "Build Language"),
@@ -101,6 +104,7 @@ SETTINGS_LABELS = {
"format.lineHeight": QT_TRANSLATE_NOOP("Builds", "Line Height"), "format.lineHeight": QT_TRANSLATE_NOOP("Builds", "Line Height"),
"format.justifyText": QT_TRANSLATE_NOOP("Builds", "Justify Text Margins"), "format.justifyText": QT_TRANSLATE_NOOP("Builds", "Justify Text Margins"),
"format.stripUnicode": QT_TRANSLATE_NOOP("Builds", "Replace Unicode Characters"), "format.stripUnicode": QT_TRANSLATE_NOOP("Builds", "Replace Unicode Characters"),
"format.replaceTabs": QT_TRANSLATE_NOOP("Builds", "Replace Tabs with Spaces"),
"odt": QT_TRANSLATE_NOOP("Builds", "Open Document"), "odt": QT_TRANSLATE_NOOP("Builds", "Open Document"),
"odt.addColours": QT_TRANSLATE_NOOP("Builds", "Add Highlight Colours"), "odt.addColours": QT_TRANSLATE_NOOP("Builds", "Add Highlight Colours"),
@@ -159,10 +163,33 @@ class BuildSettings:
""" """
return SETTINGS_LABELS.get(key, "ERROR") return SETTINGS_LABELS.get(key, "ERROR")
def getValue(self, key: str) -> str | int | bool | float: def getStr(self, key: str) -> str:
"""Get the value for a specific item, or return the default. """Type safe value access for strings.
""" """
return self._settings.get(key, SETTINGS_TEMPLATE.get(key, (None, None)[1])) value = self._settings.get(key, SETTINGS_TEMPLATE.get(key, (None, None)[1]))
return str(value)
def getBool(self, key: str) -> bool:
"""Type safe value access for bools.
"""
value = self._settings.get(key, SETTINGS_TEMPLATE.get(key, (None, None)[1]))
return bool(value)
def getInt(self, key: str) -> int:
"""Type safe value access for integers.
"""
value = self._settings.get(key, SETTINGS_TEMPLATE.get(key, (None, None)[1]))
if isinstance(value, int):
return value
return 0
def getFloat(self, key: str) -> float:
"""Type safe value access for float.
"""
value = self._settings.get(key, SETTINGS_TEMPLATE.get(key, (None, None)[1]))
if isinstance(value, float):
return value
return 0.0
## ##
# Setters # Setters
@@ -258,9 +285,9 @@ class BuildSettings:
if not isinstance(project, NWProject): if not isinstance(project, NWProject):
return result return result
incNovel = bool(self.getValue("filter.includeNovel")) incNovel = bool(self.getBool("filter.includeNovel"))
incNotes = bool(self.getValue("filter.includeNotes")) incNotes = bool(self.getBool("filter.includeNotes"))
incInactive = bool(self.getValue("filter.includeInactive")) incInactive = bool(self.getBool("filter.includeInactive"))
for item in project.tree: for item in project.tree:
tHandle = item.itemHandle tHandle = item.itemHandle
+72 -55
View File
@@ -22,27 +22,33 @@ General Public License for more details.
You should have received a copy of the GNU General Public License You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>. along with this program. If not, see <https://www.gnu.org/licenses/>.
""" """
from __future__ import annotations
import logging import logging
from pathlib import Path
from typing import Iterable
from PyQt5.QtGui import QFont, QFontInfo from PyQt5.QtGui import QFont, QFontDatabase, QFontInfo
from novelwriter import CONFIG from novelwriter import CONFIG
from novelwriter.core.tokenizer import Tokenizer
from novelwriter.error import formatException from novelwriter.error import formatException
from novelwriter.core.tomd import ToMarkdown from novelwriter.core.tomd import ToMarkdown
from novelwriter.core.toodt import ToOdt from novelwriter.core.toodt import ToOdt
from novelwriter.core.tohtml import ToHtml from novelwriter.core.tohtml import ToHtml
from novelwriter.core.project import NWProject
from novelwriter.core.buildsettings import BuildSettings
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
class NWBuildDocument: class NWBuildDocument:
def __init__(self, project): def __init__(self, project: NWProject, build: BuildSettings):
self._project = project self._project = project
self._build = {} self._build = build
self._documents = [] self._queue = []
self._error = None self._error = None
return return
@@ -52,41 +58,43 @@ class NWBuildDocument:
## ##
@property @property
def error(self): def error(self) -> str | None:
return self._error return self._error
@property @property
def buildLength(self): def buildLength(self) -> int:
return len(self._documents) return len(self._queue)
##
# Setters
##
def setBuildConfig(self, config):
"""Set the build config dictionary.
"""
self._build = config
return
def addDocument(self, tHandle):
"""Add a document to the build queue.
"""
self._documents.append(tHandle)
return
## ##
# Methods # Methods
## ##
def iterBuildOpenDocument(self, savePath, isFlat): def addDocument(self, tHandle: str):
"""Add a document to the build queue manually.
"""
self._queue.append(tHandle)
return
def queueAll(self):
"""Queue all document as defined by the build setup.
"""
filtered = self._build.buildItemFilter(self._project)
noteTitles = self._build.getValue("text.addNoteHeadings")
for item in self._project.tree:
if filtered.get(item.itemHandle, False):
self._queue.append(item.itemHandle)
elif item.isRootType() and noteTitles:
self._queue.append(item.itemHandle)
return
def iterBuildOpenDocument(self, savePath: Path, isFlat: bool) -> Iterable[tuple[int, bool]]:
"""Build an Open Document file. """Build an Open Document file.
""" """
makeOdt = ToOdt(self._project, isFlat=isFlat) makeOdt = ToOdt(self._project, isFlat=isFlat)
self._setupBuild(makeOdt) self._setupBuild(makeOdt)
makeOdt.initDocument() makeOdt.initDocument()
for i, tHandle in enumerate(self._documents): for i, tHandle in enumerate(self._queue):
yield i, self._doBuild(makeOdt, tHandle) yield i, self._doBuild(makeOdt, tHandle)
makeOdt.closeDocument() makeOdt.closeDocument()
@@ -102,16 +110,16 @@ class NWBuildDocument:
return return
def iterBuildHTML(self, savePath): def iterBuildHTML(self, savePath: Path) -> Iterable[tuple[int, bool]]:
"""Build an HTML file. """Build an HTML file.
""" """
makeHtml = ToHtml(self._project) makeHtml = ToHtml(self._project)
self._setupBuild(makeHtml) self._setupBuild(makeHtml)
if self._build.get("process.replaceTabs", False): if self._build.getValue("format.replaceTabs"):
makeHtml.replaceTabs() makeHtml.replaceTabs()
for i, tHandle in enumerate(self._documents): for i, tHandle in enumerate(self._queue):
yield i, self._doBuild(makeHtml, tHandle) yield i, self._doBuild(makeHtml, tHandle)
self._error = None self._error = None
@@ -122,7 +130,7 @@ class NWBuildDocument:
return return
def iterBuildMarkdown(self, savePath, extendedMd): def iterBuildMarkdown(self, savePath: Path, extendedMd: bool) -> Iterable[tuple[int, bool]]:
"""Build a Markdown file. """Build a Markdown file.
""" """
makeMd = ToMarkdown(self._project) makeMd = ToMarkdown(self._project)
@@ -133,10 +141,10 @@ class NWBuildDocument:
else: else:
makeMd.setStandardMarkdown() makeMd.setStandardMarkdown()
if self._build.get("process.replaceTabs", False): if self._build.getValue("format.replaceTabs"):
makeMd.replaceTabs(nSpaces=4, spaceChar=" ") makeMd.replaceTabs(nSpaces=4, spaceChar=" ")
for i, tHandle in enumerate(self._documents): for i, tHandle in enumerate(self._queue):
yield i, self._doBuild(makeMd, tHandle) yield i, self._doBuild(makeMd, tHandle)
self._error = None self._error = None
@@ -151,34 +159,44 @@ class NWBuildDocument:
# Internal Functions # Internal Functions
## ##
def _setupBuild(self, bldObj): def _setupBuild(self, bldObj: Tokenizer):
"""Configure the build object. """Configure the build object.
""" """
# Get Settings # Get Settings
fmtTitle = self._build.get("format.fmtTitle", "%title%") fmtTitle = self._build.getStr("headings.fmtTitle")
fmtChapter = self._build.get("format.fmtChapter", "%title%") fmtChapter = self._build.getStr("headings.fmtChapter")
fmtUnnumbered = self._build.get("format.fmtUnnumbered", "%title%") fmtUnnumbered = self._build.getStr("headings.fmtUnnumbered")
fmtScene = self._build.get("format.fmtScene", "%title%") fmtScene = self._build.getStr("headings.fmtScene")
fmtSection = self._build.get("format.fmtSection", "%title%") fmtSection = self._build.getStr("headings.fmtSection")
buildLang = self._build.get("format.buildLang", "en_GB") hideScene = self._build.getBool("headings.hideScene")
hideScene = self._build.get("format.hideScene", False) hideSection = self._build.getBool("headings.hideSection")
hideSection = self._build.get("format.hideSection", False)
textFont = self._build.get("format.textFont", CONFIG.textFont) incSynopsis = self._build.getBool("text.includeSynopsis")
textSize = self._build.get("format.textSize", CONFIG.textSize) incComments = self._build.getBool("text.includeComments")
lineHeight = self._build.get("format.lineHeight", 1.15) incKeywords = self._build.getBool("text.includeKeywords")
justifyText = self._build.get("format.justifyText", False) includeBody = self._build.getBool("text.includeBody")
noStyling = self._build.get("format.noStyling", False)
replaceUCode = self._build.get("format.replaceUCode", False) buildLang = self._build.getStr("format.buildLang")
incSynopsis = self._build.get("filter.includeSynopsis", False) textFont = self._build.getStr("format.textFont")
incComments = self._build.get("filter.includeComments", False) textSize = self._build.getInt("format.textSize")
incKeywords = self._build.get("filter.includeKeywords", False) lineHeight = self._build.getFloat("format.lineHeight")
includeBody = self._build.get("filter.includeBody", True) justifyText = self._build.getBool("format.justifyText")
replaceUCode = self._build.getBool("format.stripUnicode")
odtAddColours = self._build.getBool("odt.addColours")
htmlAddStyles = self._build.getBool("html.addStyles")
# The language lookup dict is reloaded if needed # The language lookup dict is reloaded if needed
self._project.setProjectLang(buildLang) self._project.setProjectLang(buildLang)
# Get font information # Get font information
fontInfo = QFontInfo(QFont(textFont, textSize)) if not textFont:
textFont = str(CONFIG.textFont)
if not textFont:
textFont = QFontDatabase.systemFont(QFontDatabase.GeneralFont).family()
bldFont = QFont(family=textFont, pointSize=textSize)
fontInfo = QFontInfo(bldFont)
textFixed = fontInfo.fixedPitch() textFixed = fontInfo.fixedPitch()
bldObj.setTitleFormat(fmtTitle) bldObj.setTitleFormat(fmtTitle)
@@ -197,16 +215,16 @@ class NWBuildDocument:
bldObj.setBodyText(includeBody) bldObj.setBodyText(includeBody)
if isinstance(bldObj, ToHtml): if isinstance(bldObj, ToHtml):
bldObj.setStyles(not noStyling) bldObj.setStyles(htmlAddStyles)
bldObj.setReplaceUnicode(replaceUCode) bldObj.setReplaceUnicode(replaceUCode)
if isinstance(bldObj, ToOdt): if isinstance(bldObj, ToOdt):
bldObj.setColourHeaders(not noStyling) bldObj.setColourHeaders(odtAddColours)
bldObj.setLanguage(buildLang) bldObj.setLanguage(buildLang)
return return
def _doBuild(self, bldObj, tHandle): def _doBuild(self, bldObj: Tokenizer, tHandle: str) -> bool:
"""Build a single document and add it to the build object. """Build a single document and add it to the build object.
""" """
self._error = None self._error = None
@@ -226,7 +244,6 @@ class NWBuildDocument:
bldObj.tokenizeText() bldObj.tokenizeText()
bldObj.doHeaders() bldObj.doHeaders()
bldObj.doConvert() bldObj.doConvert()
bldObj.doPostProcessing()
else: else:
logger.info(f"Build: Skipping '{tHandle}'") logger.info(f"Build: Skipping '{tHandle}'")
+46 -10
View File
@@ -30,8 +30,8 @@ from typing import TYPE_CHECKING
from PyQt5.QtCore import Qt, pyqtSlot from PyQt5.QtCore import Qt, pyqtSlot
from PyQt5.QtWidgets import ( from PyQt5.QtWidgets import (
QDialog, QHBoxLayout, QListWidget, QListWidgetItem, QPushButton, QSplitter, QDialog, QHBoxLayout, QListWidget, QListWidgetItem, QMenu, QProgressBar,
QTextBrowser, QVBoxLayout, QWidget, qApp QPushButton, QSplitter, QTextBrowser, QVBoxLayout, QWidget, qApp
) )
from novelwriter import CONFIG from novelwriter import CONFIG
@@ -69,8 +69,8 @@ class GuiBuildManuscript(QDialog):
CONFIG.pxInt(pOptions.getInt("GuiBuildManuscript", "winHeight", hWin)) CONFIG.pxInt(pOptions.getInt("GuiBuildManuscript", "winHeight", hWin))
) )
# Controls # Build Controls
# ======== # ==============
self.buildList = QListWidget() self.buildList = QListWidget()
@@ -80,21 +80,57 @@ class GuiBuildManuscript(QDialog):
self.btnEdit = QPushButton(self.tr("Edit")) self.btnEdit = QPushButton(self.tr("Edit"))
self.btnEdit.clicked.connect(self._editSelectedBuild) self.btnEdit.clicked.connect(self._editSelectedBuild)
self.btnDel = QPushButton(self.tr("Delete")) self.btnDelete = QPushButton(self.tr("Delete"))
self.buttonBox = QHBoxLayout() # Process Controls
self.buttonBox.addWidget(self.btnNew) # ================
self.buttonBox.addWidget(self.btnEdit)
self.buttonBox.addWidget(self.btnDel)
self.buildProgress = QProgressBar()
self.btnPreview = QPushButton(self.tr("Build Preview"))
self.manPreview = GuiManuscriptPreview(self) self.manPreview = GuiManuscriptPreview(self)
self.menuPrint = QMenu(self)
self.aPrintSend = self.menuPrint.addAction(self.tr("Print Preview"))
self.aPrintFile = self.menuPrint.addAction(self.tr("Print to PDF"))
self.menuSave = QMenu(self)
self.aSaveODT = self.menuSave.addAction(self.tr("Open Document (.odt)"))
self.aSaveFODT = self.menuSave.addAction(self.tr("Flat Open Document (.fodt)"))
self.aSaveHTM = self.menuSave.addAction(self.tr("novelWriter HTML (.htm)"))
self.aSaveNWD = self.menuSave.addAction(self.tr("novelWriter Markdown (.nwd)"))
self.aSaveMD = self.menuSave.addAction(self.tr("Standard Markdown (.md)"))
self.aSaveGH = self.menuSave.addAction(self.tr("GitHub Markdown (.md)"))
self.aSaveJsonH = self.menuSave.addAction(self.tr("JSON + novelWriter HTML (.json)"))
self.aSaveJsonM = self.menuSave.addAction(self.tr("JSON + novelWriter Markdown (.json)"))
self.btnPrint = QPushButton(self.tr("Print"))
self.btnPrint.setMenu(self.menuPrint)
self.btnSave = QPushButton(self.tr("Save As"))
self.btnSave.setMenu(self.menuSave)
self.btnClose = QPushButton(self.tr("Close"))
# Assemble GUI # Assemble GUI
# ============ # ============
self.buildBox = QHBoxLayout()
self.buildBox.addWidget(self.btnNew)
self.buildBox.addWidget(self.btnEdit)
self.buildBox.addWidget(self.btnDelete)
self.processBox = QHBoxLayout()
self.processBox.addWidget(self.btnSave)
self.processBox.addWidget(self.btnPrint)
self.processBox.addWidget(self.btnClose)
self.controlBox = QVBoxLayout() self.controlBox = QVBoxLayout()
self.controlBox.addWidget(self.buildList) self.controlBox.addWidget(self.buildList)
self.controlBox.addLayout(self.buttonBox) self.controlBox.addLayout(self.buildBox)
self.controlBox.addWidget(self.buildProgress)
self.controlBox.addWidget(self.btnPreview)
self.controlBox.addLayout(self.processBox)
self.controlBox.setContentsMargins(0, 0, 0, 0)
self.optsWidget = QWidget() self.optsWidget = QWidget()
self.optsWidget.setLayout(self.controlBox) self.optsWidget.setLayout(self.controlBox)
+10 -10
View File
@@ -457,19 +457,19 @@ class GuiBuildFilterTab(QWidget):
self.mainTheme.getIcon("proj_scene"), self.mainTheme.getIcon("proj_scene"),
self._build.getLabel("filter.includeNovel"), self._build.getLabel("filter.includeNovel"),
"doc:filter.includeNovel", "doc:filter.includeNovel",
default=self._build.getValue("filter.includeNovel") or False default=self._build.getBool("filter.includeNovel")
) )
self.filterOpt.addItem( self.filterOpt.addItem(
self.mainTheme.getIcon("proj_note"), self.mainTheme.getIcon("proj_note"),
self._build.getLabel("filter.includeNotes"), self._build.getLabel("filter.includeNotes"),
"doc:filter.includeNotes", "doc:filter.includeNotes",
default=self._build.getValue("filter.includeNotes") or False default=self._build.getBool("filter.includeNotes")
) )
self.filterOpt.addItem( self.filterOpt.addItem(
self.mainTheme.getIcon("unchecked"), self.mainTheme.getIcon("unchecked"),
self._build.getLabel("filter.includeInactive"), self._build.getLabel("filter.includeInactive"),
"doc:filter.includeInactive", "doc:filter.includeInactive",
default=self._build.getValue("filter.includeInactive") or False default=self._build.getBool("filter.includeInactive")
) )
self.filterOpt.addSeparator() self.filterOpt.addSeparator()
@@ -708,13 +708,13 @@ class GuiBuildHeadingsTab(QWidget):
def loadContent(self): def loadContent(self):
"""Populate the widgets. """Populate the widgets.
""" """
self.fmtTitle.setText(str(self._build.getValue("headings.fmtTitle"))) self.fmtTitle.setText(self._build.getStr("headings.fmtTitle"))
self.fmtChapter.setText(str(self._build.getValue("headings.fmtChapter"))) self.fmtChapter.setText(self._build.getStr("headings.fmtChapter"))
self.fmtUnnumbered.setText(str(self._build.getValue("headings.fmtUnnumbered"))) self.fmtUnnumbered.setText(self._build.getStr("headings.fmtUnnumbered"))
self.fmtScene.setText(str(self._build.getValue("headings.fmtScene"))) self.fmtScene.setText(self._build.getStr("headings.fmtScene"))
self.fmtSection.setText(str(self._build.getValue("headings.fmtSection"))) self.fmtSection.setText(self._build.getStr("headings.fmtSection"))
self.swtScene.setChecked(bool(self._build.getValue("headings.hideScene"))) self.swtScene.setChecked(self._build.getBool("headings.hideScene"))
self.swtSection.setChecked(bool(self._build.getValue("headings.hideSection"))) self.swtSection.setChecked(self._build.getBool("headings.hideSection"))
return return
## ##