Add simple build preview

This commit is contained in:
Veronica Berglyd Olsen
2023-05-31 18:10:41 +02:00
parent 704fffada3
commit 510b6ff6d2
6 changed files with 159 additions and 41 deletions
+30 -17
View File
@@ -33,7 +33,6 @@ from PyQt5.QtGui import QFont, QFontInfo
from novelwriter import CONFIG
from novelwriter.error import formatException
from novelwriter.constants import nwConst
from novelwriter.core.tomd import ToMarkdown
from novelwriter.core.toodt import ToOdt
from novelwriter.core.tohtml import ToHtml
@@ -46,13 +45,14 @@ logger = logging.getLogger(__name__)
class NWBuildDocument:
def __init__(self, project: NWProject, build: BuildSettings):
__slots__ = ("_project", "_build", "_queue", "_error", "_cache")
def __init__(self, project: NWProject, build: BuildSettings):
self._project = project
self._build = build
self._queue = []
self._error = None
self._cache = None
return
##
@@ -64,7 +64,14 @@ class NWBuildDocument:
return self._error
@property
def buildLength(self) -> int:
def lastBuild(self) -> Tokenizer | None:
return self._cache
##
# Special Methods
##
def __len__(self) -> int:
return len(self._queue)
##
@@ -89,7 +96,7 @@ class NWBuildDocument:
self._queue.append(item.itemHandle)
return
def iterBuildOpenDocument(self, savePath: Path, isFlat: bool) -> Iterable[tuple[int, bool]]:
def iterBuildOpenDocument(self, path: Path, isFlat: bool) -> Iterable[tuple[int, bool]]:
"""Build an Open Document file.
"""
makeOdt = ToOdt(self._project, isFlat=isFlat)
@@ -102,18 +109,21 @@ class NWBuildDocument:
makeOdt.closeDocument()
self._error = None
self._cache = makeOdt
try:
if isFlat:
makeOdt.saveFlatXML(savePath)
makeOdt.saveFlatXML(path)
else:
makeOdt.saveOpenDocText(savePath)
makeOdt.saveOpenDocText(path)
except Exception as exc:
self._error = formatException(exc)
return
def iterBuildHTML(self, savePath: Path) -> Iterable[tuple[int, bool]]:
"""Build an HTML file.
def iterBuildHTML(self, path: Path | None) -> Iterable[tuple[int, bool]]:
"""Build an HTML file. If path is None, no file is saved. This
is used for generating build previews.
"""
makeHtml = ToHtml(self._project)
self._setupBuild(makeHtml)
@@ -125,14 +135,17 @@ class NWBuildDocument:
yield i, self._doBuild(makeHtml, tHandle)
self._error = None
try:
makeHtml.saveHTML5(savePath)
except Exception as exc:
self._error = formatException(exc)
self._cache = makeHtml
if isinstance(path, Path):
try:
makeHtml.saveHTML5(path)
except Exception as exc:
self._error = formatException(exc)
return
def iterBuildMarkdown(self, savePath: Path, extendedMd: bool) -> Iterable[tuple[int, bool]]:
def iterBuildMarkdown(self, path: Path, extendedMd: bool) -> Iterable[tuple[int, bool]]:
"""Build a Markdown file.
"""
makeMd = ToMarkdown(self._project)
@@ -150,8 +163,10 @@ class NWBuildDocument:
yield i, self._doBuild(makeMd, tHandle)
self._error = None
self._cache = makeMd
try:
makeMd.saveMarkdown(savePath)
makeMd.saveMarkdown(path)
except Exception as exc:
self._error = formatException(exc)
@@ -194,8 +209,6 @@ class NWBuildDocument:
# Get font information
if not textFont:
textFont = str(CONFIG.textFont)
if not textFont:
textFont = nwConst.SYSTEM_FONT
bldFont = QFont(textFont, textSize)
fontInfo = QFontInfo(bldFont)
+42
View File
@@ -0,0 +1,42 @@
"""
novelWriter GUI Build Manuscript
==================================
GUI classes for the Manuscript Build Tool
File History:
Created: 2023-05-24 [2.1b1]
This file is a part of novelWriter
Copyright 20182023, Veronica Berglyd Olsen
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
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 PyQt5.QtWidgets import QDialog, QWidget
logger = logging.getLogger(__name__)
class GuiManuscriptBuild(QDialog):
def __init__(self, parent: QWidget):
super().__init__(parent=parent)
self._parent = parent
return
# END Class GuiManuscriptBuild
+79 -16
View File
@@ -6,7 +6,6 @@ GUI classes for the Manuscript Build Tool
File History:
Created: 2023-05-13 [2.1b1] GuiManuscript
Created: 2023-05-13 [2.1b1] GuiManuscriptPreview
Created: 2023-05-24 [2.1b1] GuiManuscriptBuild
This file is a part of novelWriter
Copyright 20182023, Veronica Berglyd Olsen
@@ -26,10 +25,13 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
"""
from __future__ import annotations
import json
import logging
from time import time
from typing import TYPE_CHECKING
from PyQt5.QtGui import QCursor
from PyQt5.QtCore import Qt, pyqtSlot
from PyQt5.QtWidgets import (
QDialog, QHBoxLayout, QListWidget, QListWidgetItem, QMenu, QProgressBar,
@@ -37,6 +39,9 @@ from PyQt5.QtWidgets import (
)
from novelwriter import CONFIG
from novelwriter.error import logException
from novelwriter.core.tohtml import ToHtml
from novelwriter.core.docbuild import NWBuildDocument
from novelwriter.core.buildsettings import BuildCollection, BuildSettings
from novelwriter.tools.manussettings import GuiBuildSettings
@@ -92,7 +97,8 @@ class GuiManuscript(QDialog):
self.buildProgress = QProgressBar()
self.btnPreview = QPushButton(self.tr("Build Preview"))
self.manPreview = GuiManuscriptPreview(self)
self.btnPreview.clicked.connect(self._generatePreview)
self.manPreview = GuiManuscriptPreview(self.mainGui)
self.menuPrint = QMenu(self)
self.aPrintSend = self.menuPrint.addAction(self.tr("Print Preview"))
@@ -153,6 +159,7 @@ class GuiManuscript(QDialog):
self.outerBox.addWidget(self.mainSplit)
self.setLayout(self.outerBox)
self.setSizeGripEnabled(True)
logger.debug("GuiManuscript initialisation complete")
@@ -193,11 +200,9 @@ class GuiManuscript(QDialog):
def _editSelectedBuild(self):
"""Edit the currently selected build settings entry.
"""
bItems = self.buildList.selectedItems()
if bItems:
build = self._builds.getBuild(bItems[0].data(Qt.UserRole))
if isinstance(build, BuildSettings):
self._openSettingsDialog(build)
build = self._getSelectedBuild()
if build is not None:
self._openSettingsDialog(build)
return
@pyqtSlot(BuildSettings)
@@ -209,6 +214,43 @@ class GuiManuscript(QDialog):
self._updateBuildItem(build)
return
@pyqtSlot()
def _generatePreview(self):
"""
"""
build = self._getSelectedBuild()
if build is None:
return
docBuild = NWBuildDocument(self.theProject, build)
docBuild.queueAll()
self.buildProgress.setMaximum(len(docBuild))
for step, status in docBuild.iterBuildHTML(None):
self.buildProgress.setValue(step + 1)
buildObj = docBuild.lastBuild
assert isinstance(buildObj, ToHtml)
result = {
"time": int(time()),
"style": buildObj.getStyleSheet(),
"html": buildObj.fullHTML,
}
logger.debug("Saving build cache")
cache = CONFIG.dataPath("cache") / f"build_{build.buildID}.json"
try:
with open(cache, mode="w+", encoding="utf-8") as outFile:
outFile.write(json.dumps(result, indent=2))
except Exception:
logger.error("Failed to save build cache")
logException()
return
self.manPreview.setContent(result)
return
@pyqtSlot()
def _doClose(self):
"""The close button has been clicked.
@@ -221,6 +263,16 @@ class GuiManuscript(QDialog):
# Internal Functions
##
def _getSelectedBuild(self) -> BuildSettings | None:
"""Get the currently selected build.
"""
bItems = self.buildList.selectedItems()
if bItems:
build = self._builds.getBuild(bItems[0].data(Qt.UserRole))
if isinstance(build, BuildSettings):
return build
return None
def _saveManuscript(self, outFormat: int):
"""Save the manuscript file or files.
"""
@@ -244,8 +296,8 @@ class GuiManuscript(QDialog):
winHeight = CONFIG.rpxInt(self.height())
mainSplit = self.mainSplit.sizes()
optsWidth = mainSplit[0]
viewWidth = mainSplit[1]
optsWidth = CONFIG.rpxInt(mainSplit[0])
viewWidth = CONFIG.rpxInt(mainSplit[1])
pOptions = self.theProject.options
pOptions.setValue("GuiManuscript", "winWidth", winWidth)
@@ -266,6 +318,7 @@ class GuiManuscript(QDialog):
qApp.processEvents()
dlgSettings.loadContent()
dlgSettings.newSettingsReady.connect(self._processNewSettings)
return
def _updateBuildsList(self):
@@ -295,7 +348,7 @@ class GuiManuscript(QDialog):
class GuiManuscriptPreview(QTextBrowser):
def __init__(self, mainGui):
def __init__(self, mainGui: GuiMain):
super().__init__(parent=mainGui)
self.mainGui = mainGui
@@ -304,15 +357,25 @@ class GuiManuscriptPreview(QTextBrowser):
return
# END Class GuiManuscriptPreview
def setContent(self, data: dict):
"""
"""
sPos = self.verticalScrollBar().value()
qApp.setOverrideCursor(QCursor(Qt.WaitCursor))
html = "".join(data.get("html", []))
html = html.replace("\t", "!!tab!!")
html = html.replace("<del>", "<span style='text-decoration: line-through;'>")
html = html.replace("</del>", "</span>")
self.setHtml(html)
qApp.processEvents()
class GuiManuscriptBuild(QDialog):
while self.find("!!tab!!"):
theCursor = self.textCursor()
theCursor.insertText("\t")
def __init__(self, parent):
super().__init__(parent=parent)
self._manusGui = parent
self.verticalScrollBar().setValue(sPos)
return
# END Class GuiManuscriptBuild
# END Class GuiManuscriptPreview
+1 -1
View File
@@ -398,7 +398,7 @@ class GuiBuildFilterTab(QWidget):
sizes = self.mainSplit.sizes()
if len(sizes) < 2:
return 0, 0
return sizes[0], sizes[1]
return CONFIG.rpxInt(sizes[0]), CONFIG.rpxInt(sizes[1])
##
# Slots
+3 -3
View File
@@ -1,6 +1,6 @@
<?xml version='1.0' encoding='utf-8'?>
<novelWriterXML appVersion="2.0.7" hexVersion="0x020007f0" fileVersion="1.5" timeStamp="2023-05-29 21:29:35">
<project id="e2be99af-f9bf-4403-857a-c3d1ac25abea" saveCount="1505" autoCount="237" editTime="74902">
<novelWriterXML appVersion="2.0.7" hexVersion="0x020007f0" fileVersion="1.5" timeStamp="2023-05-31 17:53:27">
<project id="e2be99af-f9bf-4403-857a-c3d1ac25abea" saveCount="1506" autoCount="237" editTime="75085">
<name>Sample Project</name>
<title>Sample Project</title>
<author>Jane Smith</author>
@@ -65,7 +65,7 @@
<name status="sf24ce6" import="ia857f0" active="yes">Chapter One</name>
</item>
<item handle="636b6aa9b697b" parent="6a2d6d5f4f401" root="7031beac91f75" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="no" heading="H3" charCount="2687" wordCount="479" paraCount="14" cursorPos="1369" />
<meta expanded="no" heading="H3" charCount="2687" wordCount="479" paraCount="14" cursorPos="18" />
<name status="s90e6c9" import="ia857f0" active="yes">Making a Scene</name>
</item>
<item handle="bc0cbd2a407f3" parent="6a2d6d5f4f401" root="7031beac91f75" order="1" type="FILE" class="NOVEL" layout="DOCUMENT">
+4 -4
View File
@@ -80,7 +80,7 @@ def testCoreDocBuild_OpenDocument(monkeypatch, mockGUI, prjLipsum, fncPath, tstP
docBuild = NWBuildDocument(project, build)
docBuild.queueAll()
assert docBuild.buildLength == 21
assert len(docBuild) == 21
# Check FODT Build
# ================
@@ -139,7 +139,7 @@ def testCoreDocBuild_OpenDocument(monkeypatch, mockGUI, prjLipsum, fncPath, tstP
mp.setattr("novelwriter.core.toodt.ToOdt.doConvert", causeException)
docBuild.addDocument("0000000000000")
assert docBuild.buildLength == 22
assert len(docBuild) == 22
count = 0
error = []
@@ -188,7 +188,7 @@ def testCoreDocBuild_HTML(monkeypatch, mockGUI, prjLipsum, fncPath, tstPaths):
docBuild = NWBuildDocument(project, build)
docBuild.queueAll()
assert docBuild.buildLength == 21
assert len(docBuild) == 21
# Check HTML5 Build
# =================
@@ -239,7 +239,7 @@ def testCoreDocBuild_Markdown(monkeypatch, mockGUI, prjLipsum, fncPath, tstPaths
docBuild = NWBuildDocument(project, build)
docBuild.queueAll()
assert docBuild.buildLength == 21
assert len(docBuild) == 21
# Check Standard Markdown Build
# =============================