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.includeKeywords": (bool, False),
"text.includeBody": (bool, True),
"text.addNoteHeadings": (bool, True),
"format.buildLang": (str, "en_GB"),
"format.textFont": (str, ""),
"format.textSize": (int, 12),
"format.lineHeight": (float, 1.15, 0.75, 3.0),
"format.justifyText": (bool, False),
"format.stripUnicode": (bool, False),
"format.replaceTabs": (bool, False),
"odt.addColours": (bool, True),
"html.addStyles": (bool, False),
}
@@ -93,6 +95,7 @@ SETTINGS_LABELS = {
"text.includeComments": QT_TRANSLATE_NOOP("Builds", "Comments"),
"text.includeKeywords": QT_TRANSLATE_NOOP("Builds", "Keywords"),
"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.buildLang": QT_TRANSLATE_NOOP("Builds", "Build Language"),
@@ -101,6 +104,7 @@ SETTINGS_LABELS = {
"format.lineHeight": QT_TRANSLATE_NOOP("Builds", "Line Height"),
"format.justifyText": QT_TRANSLATE_NOOP("Builds", "Justify Text Margins"),
"format.stripUnicode": QT_TRANSLATE_NOOP("Builds", "Replace Unicode Characters"),
"format.replaceTabs": QT_TRANSLATE_NOOP("Builds", "Replace Tabs with Spaces"),
"odt": QT_TRANSLATE_NOOP("Builds", "Open Document"),
"odt.addColours": QT_TRANSLATE_NOOP("Builds", "Add Highlight Colours"),
@@ -159,10 +163,33 @@ class BuildSettings:
"""
return SETTINGS_LABELS.get(key, "ERROR")
def getValue(self, key: str) -> str | int | bool | float:
"""Get the value for a specific item, or return the default.
def getStr(self, key: str) -> str:
"""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
@@ -258,9 +285,9 @@ class BuildSettings:
if not isinstance(project, NWProject):
return result
incNovel = bool(self.getValue("filter.includeNovel"))
incNotes = bool(self.getValue("filter.includeNotes"))
incInactive = bool(self.getValue("filter.includeInactive"))
incNovel = bool(self.getBool("filter.includeNovel"))
incNotes = bool(self.getBool("filter.includeNotes"))
incInactive = bool(self.getBool("filter.includeInactive"))
for item in project.tree:
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
along with this program. If not, see <https://www.gnu.org/licenses/>.
"""
from __future__ import annotations
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.core.tokenizer import Tokenizer
from novelwriter.error import formatException
from novelwriter.core.tomd import ToMarkdown
from novelwriter.core.toodt import ToOdt
from novelwriter.core.tohtml import ToHtml
from novelwriter.core.project import NWProject
from novelwriter.core.buildsettings import BuildSettings
logger = logging.getLogger(__name__)
class NWBuildDocument:
def __init__(self, project):
def __init__(self, project: NWProject, build: BuildSettings):
self._project = project
self._build = {}
self._documents = []
self._build = build
self._queue = []
self._error = None
return
@@ -52,41 +58,43 @@ class NWBuildDocument:
##
@property
def error(self):
def error(self) -> str | None:
return self._error
@property
def buildLength(self):
return len(self._documents)
##
# 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
def buildLength(self) -> int:
return len(self._queue)
##
# 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.
"""
makeOdt = ToOdt(self._project, isFlat=isFlat)
self._setupBuild(makeOdt)
makeOdt.initDocument()
for i, tHandle in enumerate(self._documents):
for i, tHandle in enumerate(self._queue):
yield i, self._doBuild(makeOdt, tHandle)
makeOdt.closeDocument()
@@ -102,16 +110,16 @@ class NWBuildDocument:
return
def iterBuildHTML(self, savePath):
def iterBuildHTML(self, savePath: Path) -> Iterable[tuple[int, bool]]:
"""Build an HTML file.
"""
makeHtml = ToHtml(self._project)
self._setupBuild(makeHtml)
if self._build.get("process.replaceTabs", False):
if self._build.getValue("format.replaceTabs"):
makeHtml.replaceTabs()
for i, tHandle in enumerate(self._documents):
for i, tHandle in enumerate(self._queue):
yield i, self._doBuild(makeHtml, tHandle)
self._error = None
@@ -122,7 +130,7 @@ class NWBuildDocument:
return
def iterBuildMarkdown(self, savePath, extendedMd):
def iterBuildMarkdown(self, savePath: Path, extendedMd: bool) -> Iterable[tuple[int, bool]]:
"""Build a Markdown file.
"""
makeMd = ToMarkdown(self._project)
@@ -133,10 +141,10 @@ class NWBuildDocument:
else:
makeMd.setStandardMarkdown()
if self._build.get("process.replaceTabs", False):
if self._build.getValue("format.replaceTabs"):
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)
self._error = None
@@ -151,34 +159,44 @@ class NWBuildDocument:
# Internal Functions
##
def _setupBuild(self, bldObj):
def _setupBuild(self, bldObj: Tokenizer):
"""Configure the build object.
"""
# Get Settings
fmtTitle = self._build.get("format.fmtTitle", "%title%")
fmtChapter = self._build.get("format.fmtChapter", "%title%")
fmtUnnumbered = self._build.get("format.fmtUnnumbered", "%title%")
fmtScene = self._build.get("format.fmtScene", "%title%")
fmtSection = self._build.get("format.fmtSection", "%title%")
buildLang = self._build.get("format.buildLang", "en_GB")
hideScene = self._build.get("format.hideScene", False)
hideSection = self._build.get("format.hideSection", False)
textFont = self._build.get("format.textFont", CONFIG.textFont)
textSize = self._build.get("format.textSize", CONFIG.textSize)
lineHeight = self._build.get("format.lineHeight", 1.15)
justifyText = self._build.get("format.justifyText", False)
noStyling = self._build.get("format.noStyling", False)
replaceUCode = self._build.get("format.replaceUCode", False)
incSynopsis = self._build.get("filter.includeSynopsis", False)
incComments = self._build.get("filter.includeComments", False)
incKeywords = self._build.get("filter.includeKeywords", False)
includeBody = self._build.get("filter.includeBody", True)
fmtTitle = self._build.getStr("headings.fmtTitle")
fmtChapter = self._build.getStr("headings.fmtChapter")
fmtUnnumbered = self._build.getStr("headings.fmtUnnumbered")
fmtScene = self._build.getStr("headings.fmtScene")
fmtSection = self._build.getStr("headings.fmtSection")
hideScene = self._build.getBool("headings.hideScene")
hideSection = self._build.getBool("headings.hideSection")
incSynopsis = self._build.getBool("text.includeSynopsis")
incComments = self._build.getBool("text.includeComments")
incKeywords = self._build.getBool("text.includeKeywords")
includeBody = self._build.getBool("text.includeBody")
buildLang = self._build.getStr("format.buildLang")
textFont = self._build.getStr("format.textFont")
textSize = self._build.getInt("format.textSize")
lineHeight = self._build.getFloat("format.lineHeight")
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
self._project.setProjectLang(buildLang)
# 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()
bldObj.setTitleFormat(fmtTitle)
@@ -197,16 +215,16 @@ class NWBuildDocument:
bldObj.setBodyText(includeBody)
if isinstance(bldObj, ToHtml):
bldObj.setStyles(not noStyling)
bldObj.setStyles(htmlAddStyles)
bldObj.setReplaceUnicode(replaceUCode)
if isinstance(bldObj, ToOdt):
bldObj.setColourHeaders(not noStyling)
bldObj.setColourHeaders(odtAddColours)
bldObj.setLanguage(buildLang)
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.
"""
self._error = None
@@ -226,7 +244,6 @@ class NWBuildDocument:
bldObj.tokenizeText()
bldObj.doHeaders()
bldObj.doConvert()
bldObj.doPostProcessing()
else:
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.QtWidgets import (
QDialog, QHBoxLayout, QListWidget, QListWidgetItem, QPushButton, QSplitter,
QTextBrowser, QVBoxLayout, QWidget, qApp
QDialog, QHBoxLayout, QListWidget, QListWidgetItem, QMenu, QProgressBar,
QPushButton, QSplitter, QTextBrowser, QVBoxLayout, QWidget, qApp
)
from novelwriter import CONFIG
@@ -69,8 +69,8 @@ class GuiBuildManuscript(QDialog):
CONFIG.pxInt(pOptions.getInt("GuiBuildManuscript", "winHeight", hWin))
)
# Controls
# ========
# Build Controls
# ==============
self.buildList = QListWidget()
@@ -80,21 +80,57 @@ class GuiBuildManuscript(QDialog):
self.btnEdit = QPushButton(self.tr("Edit"))
self.btnEdit.clicked.connect(self._editSelectedBuild)
self.btnDel = QPushButton(self.tr("Delete"))
self.btnDelete = QPushButton(self.tr("Delete"))
self.buttonBox = QHBoxLayout()
self.buttonBox.addWidget(self.btnNew)
self.buttonBox.addWidget(self.btnEdit)
self.buttonBox.addWidget(self.btnDel)
# Process Controls
# ================
self.buildProgress = QProgressBar()
self.btnPreview = QPushButton(self.tr("Build Preview"))
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
# ============
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.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.setLayout(self.controlBox)
+10 -10
View File
@@ -457,19 +457,19 @@ class GuiBuildFilterTab(QWidget):
self.mainTheme.getIcon("proj_scene"),
self._build.getLabel("filter.includeNovel"),
"doc:filter.includeNovel",
default=self._build.getValue("filter.includeNovel") or False
default=self._build.getBool("filter.includeNovel")
)
self.filterOpt.addItem(
self.mainTheme.getIcon("proj_note"),
self._build.getLabel("filter.includeNotes"),
"doc:filter.includeNotes",
default=self._build.getValue("filter.includeNotes") or False
default=self._build.getBool("filter.includeNotes")
)
self.filterOpt.addItem(
self.mainTheme.getIcon("unchecked"),
self._build.getLabel("filter.includeInactive"),
"doc:filter.includeInactive",
default=self._build.getValue("filter.includeInactive") or False
default=self._build.getBool("filter.includeInactive")
)
self.filterOpt.addSeparator()
@@ -708,13 +708,13 @@ class GuiBuildHeadingsTab(QWidget):
def loadContent(self):
"""Populate the widgets.
"""
self.fmtTitle.setText(str(self._build.getValue("headings.fmtTitle")))
self.fmtChapter.setText(str(self._build.getValue("headings.fmtChapter")))
self.fmtUnnumbered.setText(str(self._build.getValue("headings.fmtUnnumbered")))
self.fmtScene.setText(str(self._build.getValue("headings.fmtScene")))
self.fmtSection.setText(str(self._build.getValue("headings.fmtSection")))
self.swtScene.setChecked(bool(self._build.getValue("headings.hideScene")))
self.swtSection.setChecked(bool(self._build.getValue("headings.hideSection")))
self.fmtTitle.setText(self._build.getStr("headings.fmtTitle"))
self.fmtChapter.setText(self._build.getStr("headings.fmtChapter"))
self.fmtUnnumbered.setText(self._build.getStr("headings.fmtUnnumbered"))
self.fmtScene.setText(self._build.getStr("headings.fmtScene"))
self.fmtSection.setText(self._build.getStr("headings.fmtSection"))
self.swtScene.setChecked(self._build.getBool("headings.hideScene"))
self.swtSection.setChecked(self._build.getBool("headings.hideSection"))
return
##