Add simple build preview
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -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 2018–2023, 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
|
||||
@@ -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 2018–2023, 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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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
|
||||
# =============================
|
||||
|
||||
Reference in New Issue
Block a user