Complete the manuscript build dialog, and the doc builder class
This commit is contained in:
@@ -32,7 +32,7 @@ from PyQt5.QtGui import QFont, QFontInfo
|
|||||||
|
|
||||||
from novelwriter import CONFIG
|
from novelwriter import CONFIG
|
||||||
from novelwriter.enum import nwBuildFmt
|
from novelwriter.enum import nwBuildFmt
|
||||||
from novelwriter.error import formatException
|
from novelwriter.error import formatException, logException
|
||||||
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
|
||||||
@@ -110,13 +110,13 @@ class NWBuildDocument:
|
|||||||
def iterBuild(self, path: Path, bFormat: nwBuildFmt) -> Iterable[tuple[int, bool]]:
|
def iterBuild(self, path: Path, bFormat: nwBuildFmt) -> Iterable[tuple[int, bool]]:
|
||||||
"""Wrapper for builders based on format."""
|
"""Wrapper for builders based on format."""
|
||||||
if bFormat in (nwBuildFmt.ODT, nwBuildFmt.FODT):
|
if bFormat in (nwBuildFmt.ODT, nwBuildFmt.FODT):
|
||||||
yield from self.iterBuildOpenDocument(path, bFormat == "fodt")
|
yield from self.iterBuildOpenDocument(path, bFormat == nwBuildFmt.FODT)
|
||||||
elif bFormat in (nwBuildFmt.HTML, nwBuildFmt.J_HTML):
|
elif bFormat in (nwBuildFmt.HTML, nwBuildFmt.J_HTML):
|
||||||
yield from self.iterBuildHTML(path if bFormat == "html" else None)
|
yield from self.iterBuildHTML(path, asJson=bFormat == nwBuildFmt.J_HTML)
|
||||||
elif bFormat in (nwBuildFmt.STD_MD, nwBuildFmt.EXT_MD):
|
elif bFormat in (nwBuildFmt.STD_MD, nwBuildFmt.EXT_MD):
|
||||||
yield from self.iterBuildMarkdown(path, bFormat == "md+")
|
yield from self.iterBuildMarkdown(path, bFormat == nwBuildFmt.EXT_MD)
|
||||||
elif bFormat in (nwBuildFmt.NWD, nwBuildFmt.J_NWD):
|
elif bFormat in (nwBuildFmt.NWD, nwBuildFmt.J_NWD):
|
||||||
yield from self.iterBuildNovelWriter(path if bFormat == "nwd" else None)
|
yield from self.iterBuildNWD(path, asJson=bFormat == nwBuildFmt.J_NWD)
|
||||||
return
|
return
|
||||||
|
|
||||||
def iterBuildOpenDocument(self, path: Path, isFlat: bool) -> Iterable[tuple[int, bool]]:
|
def iterBuildOpenDocument(self, path: Path, isFlat: bool) -> Iterable[tuple[int, bool]]:
|
||||||
@@ -143,11 +143,12 @@ class NWBuildDocument:
|
|||||||
else:
|
else:
|
||||||
makeObj.saveOpenDocText(path)
|
makeObj.saveOpenDocText(path)
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
|
logException()
|
||||||
self._error = formatException(exc)
|
self._error = formatException(exc)
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
def iterBuildHTML(self, path: Path | None) -> Iterable[tuple[int, bool]]:
|
def iterBuildHTML(self, path: Path | None, asJson: bool = False) -> Iterable[tuple[int, bool]]:
|
||||||
"""Build an HTML file. If path is None, no file is saved. This
|
"""Build an HTML file. If path is None, no file is saved. This
|
||||||
is used for generating build previews.
|
is used for generating build previews.
|
||||||
"""
|
"""
|
||||||
@@ -169,8 +170,12 @@ class NWBuildDocument:
|
|||||||
|
|
||||||
if isinstance(path, Path):
|
if isinstance(path, Path):
|
||||||
try:
|
try:
|
||||||
makeObj.saveHTML5(path)
|
if asJson:
|
||||||
|
makeObj.saveHtmlJson(path)
|
||||||
|
else:
|
||||||
|
makeObj.saveHtml5(path)
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
|
logException()
|
||||||
self._error = formatException(exc)
|
self._error = formatException(exc)
|
||||||
|
|
||||||
return
|
return
|
||||||
@@ -201,11 +206,12 @@ class NWBuildDocument:
|
|||||||
try:
|
try:
|
||||||
makeObj.saveMarkdown(path)
|
makeObj.saveMarkdown(path)
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
|
logException()
|
||||||
self._error = formatException(exc)
|
self._error = formatException(exc)
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
def iterBuildNovelWriter(self, path: Path | None) -> Iterable[tuple[int, bool]]:
|
def iterBuildNWD(self, path: Path | None, asJson: bool = False) -> Iterable[tuple[int, bool]]:
|
||||||
"""Build a novelWriter Markdown file."""
|
"""Build a novelWriter Markdown file."""
|
||||||
makeObj = ToMarkdown(self._project)
|
makeObj = ToMarkdown(self._project)
|
||||||
filtered = self._setupBuild(makeObj)
|
filtered = self._setupBuild(makeObj)
|
||||||
@@ -226,8 +232,12 @@ class NWBuildDocument:
|
|||||||
|
|
||||||
if isinstance(path, Path):
|
if isinstance(path, Path):
|
||||||
try:
|
try:
|
||||||
makeObj.saveRawMarkdown(path)
|
if asJson:
|
||||||
|
makeObj.saveRawMarkdownJSON(path)
|
||||||
|
else:
|
||||||
|
makeObj.saveRawMarkdown(path)
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
|
logException()
|
||||||
self._error = formatException(exc)
|
self._error = formatException(exc)
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|||||||
+35
-18
@@ -23,11 +23,14 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|||||||
"""
|
"""
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import json
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
|
from time import time
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
from novelwriter import CONFIG
|
from novelwriter import CONFIG
|
||||||
|
from novelwriter.common import formatTimeStamp
|
||||||
from novelwriter.constants import nwKeyWords, nwLabels, nwHtmlUnicode
|
from novelwriter.constants import nwKeyWords, nwLabels, nwHtmlUnicode
|
||||||
from novelwriter.core.project import NWProject
|
from novelwriter.core.project import NWProject
|
||||||
from novelwriter.core.tokenizer import Tokenizer, stripEscape
|
from novelwriter.core.tokenizer import Tokenizer, stripEscape
|
||||||
@@ -296,38 +299,52 @@ class ToHtml(Tokenizer):
|
|||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
def saveHTML5(self, savePath: str | Path):
|
def saveHtml5(self, path: str | Path):
|
||||||
"""Save the data to an .html file.
|
"""Save the data to an HTML file."""
|
||||||
"""
|
with open(path, mode="w", encoding="utf-8") as fObj:
|
||||||
with open(savePath, mode="w", encoding="utf-8") as outFile:
|
fObj.write((
|
||||||
theStyle = self.getStyleSheet()
|
|
||||||
theStyle.append("article {width: 800px; margin: 40px auto;}")
|
|
||||||
bodyText = "".join(self._fullHTML)
|
|
||||||
bodyText = bodyText.replace("\t", "	").rstrip()
|
|
||||||
|
|
||||||
theHtml = (
|
|
||||||
"<!DOCTYPE html>\n"
|
"<!DOCTYPE html>\n"
|
||||||
"<html>\n"
|
"<html>\n"
|
||||||
"<head>\n"
|
"<head>\n"
|
||||||
"<meta charset='utf-8'>\n"
|
"<meta charset='utf-8'>\n"
|
||||||
"<title>{projTitle:s}</title>\n"
|
"<title>{title:s}</title>\n"
|
||||||
"</head>\n"
|
"</head>\n"
|
||||||
"<style>\n"
|
"<style>\n"
|
||||||
"{htmlStyle:s}\n"
|
"{style:s}\n"
|
||||||
"</style>\n"
|
"</style>\n"
|
||||||
"<body>\n"
|
"<body>\n"
|
||||||
"<article>\n"
|
"<article>\n"
|
||||||
"{bodyText:s}\n"
|
"{body:s}\n"
|
||||||
"</article>\n"
|
"</article>\n"
|
||||||
"</body>\n"
|
"</body>\n"
|
||||||
"</html>\n"
|
"</html>\n"
|
||||||
).format(
|
).format(
|
||||||
projTitle=self._project.data.name,
|
title=self._project.data.name,
|
||||||
htmlStyle="\n".join(theStyle),
|
style="\n".join(self.getStyleSheet()),
|
||||||
bodyText=bodyText,
|
body=("".join(self._fullHTML)).replace("\t", "	").rstrip(),
|
||||||
)
|
))
|
||||||
outFile.write(theHtml)
|
logger.info("Wrote file: %s", path)
|
||||||
|
return
|
||||||
|
|
||||||
|
def saveHtmlJson(self, path: str | Path):
|
||||||
|
"""Save the data to a JSON file."""
|
||||||
|
timeStamp = time()
|
||||||
|
data = {
|
||||||
|
"meta": {
|
||||||
|
"projectName": self._project.data.name,
|
||||||
|
"novelTitle": self._project.data.title,
|
||||||
|
"novelAuthor": self._project.data.author,
|
||||||
|
"buildTime": int(timeStamp),
|
||||||
|
"buildTimeStr": formatTimeStamp(timeStamp),
|
||||||
|
},
|
||||||
|
"text": {
|
||||||
|
"css": self.getStyleSheet(),
|
||||||
|
"html": [page.rstrip("\n").split("\n") for page in self.fullHTML],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
with open(path, mode="w", encoding="utf-8") as fObj:
|
||||||
|
json.dump(data, fObj, indent=2)
|
||||||
|
logger.info("Wrote file: %s", path)
|
||||||
return
|
return
|
||||||
|
|
||||||
def replaceTabs(self, nSpaces: int = 8, spaceChar: str = " "):
|
def replaceTabs(self, nSpaces: int = 8, spaceChar: str = " "):
|
||||||
|
|||||||
@@ -25,9 +25,11 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import re
|
import re
|
||||||
|
import json
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from abc import ABC, abstractmethod
|
from abc import ABC, abstractmethod
|
||||||
|
from time import time
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from operator import itemgetter
|
from operator import itemgetter
|
||||||
from functools import partial
|
from functools import partial
|
||||||
@@ -35,7 +37,7 @@ from functools import partial
|
|||||||
from PyQt5.QtCore import QCoreApplication, QRegularExpression
|
from PyQt5.QtCore import QCoreApplication, QRegularExpression
|
||||||
|
|
||||||
from novelwriter.enum import nwItemLayout, nwItemType
|
from novelwriter.enum import nwItemLayout, nwItemType
|
||||||
from novelwriter.common import numberToRoman, checkInt
|
from novelwriter.common import formatTimeStamp, numberToRoman, checkInt
|
||||||
from novelwriter.constants import nwConst, nwHeadFmt, nwRegEx, nwUnicode
|
from novelwriter.constants import nwConst, nwHeadFmt, nwRegEx, nwUnicode
|
||||||
from novelwriter.core.project import NWProject
|
from novelwriter.core.project import NWProject
|
||||||
|
|
||||||
@@ -740,13 +742,32 @@ class Tokenizer(ABC):
|
|||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def saveRawMarkdown(self, savePath: str | Path):
|
def saveRawMarkdown(self, path: str | Path):
|
||||||
"""Save the data to a plain text file."""
|
"""Save the raw text to a plain text file."""
|
||||||
with open(savePath, mode="w", encoding="utf-8") as outFile:
|
with open(path, mode="w", encoding="utf-8") as outFile:
|
||||||
for nwdPage in self._allMarkdown:
|
for nwdPage in self._allMarkdown:
|
||||||
outFile.write(nwdPage)
|
outFile.write(nwdPage)
|
||||||
return
|
return
|
||||||
|
|
||||||
|
def saveRawMarkdownJSON(self, path: str | Path):
|
||||||
|
"""Save the raw text to a JSON file."""
|
||||||
|
timeStamp = time()
|
||||||
|
data = {
|
||||||
|
"meta": {
|
||||||
|
"projectName": self._project.data.name,
|
||||||
|
"novelTitle": self._project.data.title,
|
||||||
|
"novelAuthor": self._project.data.author,
|
||||||
|
"buildTime": int(timeStamp),
|
||||||
|
"buildTimeStr": formatTimeStamp(timeStamp),
|
||||||
|
},
|
||||||
|
"text": {
|
||||||
|
"nwd": [page.rstrip("\n").split("\n") for page in self._allMarkdown],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
with open(path, mode="w", encoding="utf-8") as fObj:
|
||||||
|
json.dump(data, fObj, indent=2)
|
||||||
|
return
|
||||||
|
|
||||||
# END Class Tokenizer
|
# END Class Tokenizer
|
||||||
|
|
||||||
|
|
||||||
@@ -760,27 +781,23 @@ class HeadingFormatter:
|
|||||||
return
|
return
|
||||||
|
|
||||||
def incChapter(self):
|
def incChapter(self):
|
||||||
"""Increment the chapter counter.
|
"""Increment the chapter counter."""
|
||||||
"""
|
|
||||||
self._chCount += 1
|
self._chCount += 1
|
||||||
return
|
return
|
||||||
|
|
||||||
def incScene(self):
|
def incScene(self):
|
||||||
"""Increment the scene counters.
|
"""Increment the scene counters."""
|
||||||
"""
|
|
||||||
self._scChCount += 1
|
self._scChCount += 1
|
||||||
self._scAbsCount += 1
|
self._scAbsCount += 1
|
||||||
return
|
return
|
||||||
|
|
||||||
def resetScene(self):
|
def resetScene(self):
|
||||||
"""Reset the chapter scene counter.
|
"""Reset the chapter scene counter."""
|
||||||
"""
|
|
||||||
self._scChCount = 0
|
self._scChCount = 0
|
||||||
return
|
return
|
||||||
|
|
||||||
def apply(self, hFormat: str, text: str):
|
def apply(self, hFormat: str, text: str):
|
||||||
"""Apply formatting to a specific heading.
|
"""Apply formatting to a specific heading."""
|
||||||
"""
|
|
||||||
hFormat = hFormat.replace(nwHeadFmt.TITLE, text)
|
hFormat = hFormat.replace(nwHeadFmt.TITLE, text)
|
||||||
hFormat = hFormat.replace(nwHeadFmt.CH_NUM, str(self._chCount))
|
hFormat = hFormat.replace(nwHeadFmt.CH_NUM, str(self._chCount))
|
||||||
hFormat = hFormat.replace(nwHeadFmt.SC_NUM, str(self._scChCount))
|
hFormat = hFormat.replace(nwHeadFmt.SC_NUM, str(self._scChCount))
|
||||||
|
|||||||
@@ -179,6 +179,7 @@ class ToMarkdown(Tokenizer):
|
|||||||
"""Save the data to a plain text file."""
|
"""Save the data to a plain text file."""
|
||||||
with open(path, mode="w", encoding="utf-8") as outFile:
|
with open(path, mode="w", encoding="utf-8") as outFile:
|
||||||
outFile.write("".join(self._fullMD))
|
outFile.write("".join(self._fullMD))
|
||||||
|
logger.info("Wrote file: %s", path)
|
||||||
return
|
return
|
||||||
|
|
||||||
def replaceTabs(self, nSpaces: int = 8, spaceChar: str = " "):
|
def replaceTabs(self, nSpaces: int = 8, spaceChar: str = " "):
|
||||||
|
|||||||
@@ -501,6 +501,7 @@ class ToOdt(Tokenizer):
|
|||||||
xml = ET.ElementTree(self._dFlat)
|
xml = ET.ElementTree(self._dFlat)
|
||||||
xmlIndent(xml)
|
xmlIndent(xml)
|
||||||
xml.write(fObj, encoding="utf-8", xml_declaration=True)
|
xml.write(fObj, encoding="utf-8", xml_declaration=True)
|
||||||
|
logger.info("Wrote file: %s", path)
|
||||||
return
|
return
|
||||||
|
|
||||||
def saveOpenDocText(self, path: str | Path):
|
def saveOpenDocText(self, path: str | Path):
|
||||||
@@ -535,6 +536,8 @@ class ToOdt(Tokenizer):
|
|||||||
putInZip("meta.xml", self._dMeta, outZip)
|
putInZip("meta.xml", self._dMeta, outZip)
|
||||||
putInZip("styles.xml", self._dStyl, outZip)
|
putInZip("styles.xml", self._dStyl, outZip)
|
||||||
|
|
||||||
|
logger.info("Wrote file: %s", path)
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
##
|
##
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ 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
|
||||||
|
|
||||||
from math import ceil
|
from math import ceil
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,54 @@
|
|||||||
|
"""
|
||||||
|
novelWriter – Custom Widget: Progress Simple
|
||||||
|
============================================
|
||||||
|
|
||||||
|
File History:
|
||||||
|
Created: 2023-06-09 [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
|
||||||
|
|
||||||
|
from math import ceil
|
||||||
|
|
||||||
|
from PyQt5.QtGui import QPaintEvent, QPainter
|
||||||
|
from PyQt5.QtWidgets import QProgressBar, QWidget
|
||||||
|
|
||||||
|
|
||||||
|
class NProgressSimple(QProgressBar):
|
||||||
|
"""Extension: Simple Progress Widget
|
||||||
|
|
||||||
|
A custom widget that paints a plain bar with no other styling.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, parent: QWidget):
|
||||||
|
super().__init__(parent=parent)
|
||||||
|
return
|
||||||
|
|
||||||
|
def paintEvent(self, event: QPaintEvent):
|
||||||
|
"""Custom painter for the progress bar."""
|
||||||
|
if self.value() == 0:
|
||||||
|
return
|
||||||
|
progress = ceil(self.width()*float(self.value())/self.maximum())
|
||||||
|
qPaint = QPainter(self)
|
||||||
|
qPaint.setRenderHint(QPainter.Antialiasing, True)
|
||||||
|
qPaint.setPen(self.palette().highlight().color())
|
||||||
|
qPaint.setBrush(self.palette().highlight())
|
||||||
|
qPaint.drawRect(0, 0, progress, self.height())
|
||||||
|
return
|
||||||
|
|
||||||
|
# END Class NProgressSimple
|
||||||
@@ -24,24 +24,25 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
from pathlib import Path
|
|
||||||
|
|
||||||
from typing import TYPE_CHECKING
|
from typing import TYPE_CHECKING
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
from PyQt5.QtCore import QSize, Qt, pyqtSlot
|
from PyQt5.QtCore import QSize, QTimer, Qt, pyqtSlot
|
||||||
from PyQt5.QtWidgets import (
|
from PyQt5.QtWidgets import (
|
||||||
QAbstractButton, QAbstractItemView, QDialog, QDialogButtonBox, QFileDialog,
|
QAbstractButton, QAbstractItemView, QDialog, QDialogButtonBox, QFileDialog,
|
||||||
QGridLayout, QHBoxLayout, QLabel, QLineEdit, QListWidget, QListWidgetItem,
|
QGridLayout, QHBoxLayout, QLabel, QLineEdit, QListWidget, QListWidgetItem,
|
||||||
QProgressBar, QPushButton, QSplitter, QVBoxLayout, QWidget
|
QPushButton, QSplitter, QVBoxLayout, QWidget
|
||||||
)
|
)
|
||||||
|
|
||||||
from novelwriter import CONFIG
|
from novelwriter import CONFIG
|
||||||
from novelwriter.enum import nwBuildFmt
|
from novelwriter.enum import nwAlert, nwBuildFmt
|
||||||
from novelwriter.common import makeFileNameSafe
|
from novelwriter.common import makeFileNameSafe
|
||||||
from novelwriter.constants import nwLabels
|
from novelwriter.constants import nwLabels
|
||||||
from novelwriter.core.item import NWItem
|
from novelwriter.core.item import NWItem
|
||||||
from novelwriter.core.docbuild import NWBuildDocument
|
from novelwriter.core.docbuild import NWBuildDocument
|
||||||
from novelwriter.core.buildsettings import BuildSettings
|
from novelwriter.core.buildsettings import BuildSettings
|
||||||
|
from novelwriter.extensions.simpleprogress import NProgressSimple
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from novelwriter.guimain import GuiMain
|
from novelwriter.guimain import GuiMain
|
||||||
@@ -50,7 +51,7 @@ logger = logging.getLogger(__name__)
|
|||||||
|
|
||||||
|
|
||||||
class GuiManuscriptBuild(QDialog):
|
class GuiManuscriptBuild(QDialog):
|
||||||
"""GUI Tools: Manucript Builder Dialog
|
"""GUI Tools: Manuscript Build Dialog
|
||||||
|
|
||||||
This is the tool for running the build itself. It can be accessed
|
This is the tool for running the build itself. It can be accessed
|
||||||
independently of the Manuscript Build Tool.
|
independently of the Manuscript Build Tool.
|
||||||
@@ -73,10 +74,13 @@ class GuiManuscriptBuild(QDialog):
|
|||||||
|
|
||||||
self.setWindowTitle(self.tr("Build Manuscript"))
|
self.setWindowTitle(self.tr("Build Manuscript"))
|
||||||
self.setMinimumWidth(CONFIG.pxInt(500))
|
self.setMinimumWidth(CONFIG.pxInt(500))
|
||||||
self.setMinimumHeight(CONFIG.pxInt(250))
|
self.setMinimumHeight(CONFIG.pxInt(300))
|
||||||
|
|
||||||
iPx = self.mainTheme.baseIconSize
|
iPx = self.mainTheme.baseIconSize
|
||||||
wWin = CONFIG.pxInt(660)
|
sp4 = CONFIG.pxInt(4)
|
||||||
|
sp8 = CONFIG.pxInt(8)
|
||||||
|
sp16 = CONFIG.pxInt(16)
|
||||||
|
wWin = CONFIG.pxInt(620)
|
||||||
hWin = CONFIG.pxInt(360)
|
hWin = CONFIG.pxInt(360)
|
||||||
|
|
||||||
pOptions = self.theProject.options
|
pOptions = self.theProject.options
|
||||||
@@ -141,35 +145,42 @@ class GuiManuscriptBuild(QDialog):
|
|||||||
self.lblMain.setWordWrap(True)
|
self.lblMain.setWordWrap(True)
|
||||||
self.lblMain.setFont(font)
|
self.lblMain.setFont(font)
|
||||||
|
|
||||||
|
# Build Path
|
||||||
|
self.lblPath = QLabel(self.tr("Path"))
|
||||||
|
self.buildPath = QLineEdit(self)
|
||||||
|
self.btnBrowse = QPushButton(self.mainTheme.getIcon("browse"), "")
|
||||||
|
|
||||||
|
self.pathBox = QHBoxLayout()
|
||||||
|
self.pathBox.addWidget(self.buildPath)
|
||||||
|
self.pathBox.addWidget(self.btnBrowse)
|
||||||
|
self.pathBox.setSpacing(sp8)
|
||||||
|
|
||||||
# Build Name
|
# Build Name
|
||||||
self.lblName = QLabel(self.tr("File Name"))
|
self.lblName = QLabel(self.tr("File Name"))
|
||||||
self.buildName = QLineEdit()
|
self.buildName = QLineEdit(self)
|
||||||
self.btnReset = QPushButton(self.mainTheme.getIcon("revert"), "")
|
self.btnReset = QPushButton(self.mainTheme.getIcon("revert"), "")
|
||||||
self.btnReset.setToolTip(self.tr("Reset file name to default"))
|
self.btnReset.setToolTip(self.tr("Reset file name to default"))
|
||||||
|
|
||||||
self.nameBox = QHBoxLayout()
|
self.nameBox = QHBoxLayout()
|
||||||
self.nameBox.addWidget(self.buildName)
|
self.nameBox.addWidget(self.buildName)
|
||||||
self.nameBox.addWidget(self.btnReset)
|
self.nameBox.addWidget(self.btnReset)
|
||||||
|
self.nameBox.setSpacing(sp8)
|
||||||
|
|
||||||
# Build Progress
|
# Build Progress
|
||||||
self.lblProgress = QLabel(self.tr("Progress"))
|
self.buildProgress = NProgressSimple(self)
|
||||||
|
|
||||||
self.buildProgress = QProgressBar()
|
|
||||||
self.buildProgress.setMinimum(0)
|
self.buildProgress.setMinimum(0)
|
||||||
self.buildProgress.setValue(0)
|
self.buildProgress.setValue(0)
|
||||||
|
self.buildProgress.setTextVisible(False)
|
||||||
self.progressBox = QVBoxLayout()
|
self.buildProgress.setFixedHeight(sp8)
|
||||||
self.progressBox.addWidget(self.lblProgress)
|
|
||||||
self.progressBox.addWidget(self.buildProgress)
|
|
||||||
self.progressBox.setSpacing(CONFIG.pxInt(4))
|
|
||||||
|
|
||||||
# Build Box
|
# Build Box
|
||||||
self.buildBox = QGridLayout()
|
self.buildBox = QGridLayout()
|
||||||
self.buildBox.addWidget(self.lblName, 1, 0)
|
self.buildBox.addWidget(self.lblPath, 0, 0)
|
||||||
self.buildBox.addLayout(self.nameBox, 1, 1)
|
self.buildBox.addLayout(self.pathBox, 0, 1)
|
||||||
self.buildBox.addWidget(self.lblProgress, 2, 0)
|
self.buildBox.addWidget(self.lblName, 1, 0)
|
||||||
self.buildBox.addWidget(self.buildProgress, 2, 1)
|
self.buildBox.addLayout(self.nameBox, 1, 1)
|
||||||
self.buildBox.setVerticalSpacing(CONFIG.pxInt(4))
|
self.buildBox.setHorizontalSpacing(sp8)
|
||||||
|
self.buildBox.setVerticalSpacing(sp4)
|
||||||
|
|
||||||
# Dialog Buttons
|
# Dialog Buttons
|
||||||
self.btnBuild = QPushButton(self.mainTheme.getIcon("export"), self.tr("&Build"))
|
self.btnBuild = QPushButton(self.mainTheme.getIcon("export"), self.tr("&Build"))
|
||||||
@@ -182,7 +193,7 @@ class GuiManuscriptBuild(QDialog):
|
|||||||
self.mainSplit = QSplitter()
|
self.mainSplit = QSplitter()
|
||||||
self.mainSplit.addWidget(self.formatWidget)
|
self.mainSplit.addWidget(self.formatWidget)
|
||||||
self.mainSplit.addWidget(self.contentWidget)
|
self.mainSplit.addWidget(self.contentWidget)
|
||||||
self.mainSplit.setHandleWidth(CONFIG.pxInt(16))
|
self.mainSplit.setHandleWidth(sp16)
|
||||||
self.mainSplit.setCollapsible(0, False)
|
self.mainSplit.setCollapsible(0, False)
|
||||||
self.mainSplit.setCollapsible(1, False)
|
self.mainSplit.setCollapsible(1, False)
|
||||||
self.mainSplit.setSizes([
|
self.mainSplit.setSizes([
|
||||||
@@ -192,15 +203,21 @@ class GuiManuscriptBuild(QDialog):
|
|||||||
|
|
||||||
self.outerBox = QVBoxLayout()
|
self.outerBox = QVBoxLayout()
|
||||||
self.outerBox.addWidget(self.lblMain, 0, Qt.AlignCenter)
|
self.outerBox.addWidget(self.lblMain, 0, Qt.AlignCenter)
|
||||||
|
self.outerBox.addSpacing(sp16)
|
||||||
self.outerBox.addWidget(self.mainSplit, 1)
|
self.outerBox.addWidget(self.mainSplit, 1)
|
||||||
|
self.outerBox.addSpacing(sp4)
|
||||||
|
self.outerBox.addWidget(self.buildProgress, 0)
|
||||||
|
self.outerBox.addSpacing(sp4)
|
||||||
self.outerBox.addLayout(self.buildBox, 0)
|
self.outerBox.addLayout(self.buildBox, 0)
|
||||||
|
self.outerBox.addSpacing(sp16)
|
||||||
self.outerBox.addWidget(self.dlgButtons, 0)
|
self.outerBox.addWidget(self.dlgButtons, 0)
|
||||||
self.outerBox.setSpacing(CONFIG.pxInt(12))
|
self.outerBox.setSpacing(0)
|
||||||
|
|
||||||
self.setLayout(self.outerBox)
|
self.setLayout(self.outerBox)
|
||||||
|
|
||||||
self.btnBuild.setFocus()
|
self.btnBuild.setFocus()
|
||||||
self._populateContentList()
|
self._populateContentList()
|
||||||
|
self.buildPath.setText(str(self._build.lastPath))
|
||||||
if self._build.lastBuildName:
|
if self._build.lastBuildName:
|
||||||
self.buildName.setText(self._build.lastBuildName)
|
self.buildName.setText(self._build.lastBuildName)
|
||||||
else:
|
else:
|
||||||
@@ -208,6 +225,7 @@ class GuiManuscriptBuild(QDialog):
|
|||||||
|
|
||||||
# Signals
|
# Signals
|
||||||
self.btnReset.clicked.connect(self._doResetBuildName)
|
self.btnReset.clicked.connect(self._doResetBuildName)
|
||||||
|
self.btnBrowse.clicked.connect(self._doSelectPath)
|
||||||
self.dlgButtons.clicked.connect(self._dialogButtonClicked)
|
self.dlgButtons.clicked.connect(self._dialogButtonClicked)
|
||||||
self.listFormats.itemSelectionChanged.connect(self._formatSelectionChanged)
|
self.listFormats.itemSelectionChanged.connect(self._formatSelectionChanged)
|
||||||
|
|
||||||
@@ -253,6 +271,18 @@ class GuiManuscriptBuild(QDialog):
|
|||||||
self.close()
|
self.close()
|
||||||
return
|
return
|
||||||
|
|
||||||
|
@pyqtSlot()
|
||||||
|
def _doSelectPath(self):
|
||||||
|
"""Select a folder for output."""
|
||||||
|
bPath = Path(self.buildPath.text())
|
||||||
|
bPath = bPath if bPath.is_dir() else self._build.lastPath
|
||||||
|
savePath = QFileDialog.getExistingDirectory(
|
||||||
|
self, self.tr("Select Folder"), str(bPath)
|
||||||
|
)
|
||||||
|
if savePath:
|
||||||
|
self.buildPath.setText(savePath)
|
||||||
|
return
|
||||||
|
|
||||||
@pyqtSlot()
|
@pyqtSlot()
|
||||||
def _doResetBuildName(self):
|
def _doResetBuildName(self):
|
||||||
"""Generate a default build name."""
|
"""Generate a default build name."""
|
||||||
@@ -269,43 +299,55 @@ class GuiManuscriptBuild(QDialog):
|
|||||||
self.buildProgress.setValue(0)
|
self.buildProgress.setValue(0)
|
||||||
return
|
return
|
||||||
|
|
||||||
|
@pyqtSlot()
|
||||||
|
def _resetProgress(self):
|
||||||
|
"""Set the progress bar back to 0."""
|
||||||
|
self.buildProgress.setValue(0)
|
||||||
|
return
|
||||||
|
|
||||||
##
|
##
|
||||||
# Internal Functions
|
# Internal Functions
|
||||||
##
|
##
|
||||||
|
|
||||||
def _runBuild(self) -> bool:
|
def _runBuild(self) -> bool:
|
||||||
"""Run the currently selected build."""
|
"""Run the currently selected build."""
|
||||||
selFormat = self._getSelectedFormat()
|
bFormat = self._getSelectedFormat()
|
||||||
if not isinstance(selFormat, nwBuildFmt):
|
if not isinstance(bFormat, nwBuildFmt):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
lastName = self.buildName.text().strip()
|
bName = self.buildName.text().strip()
|
||||||
if not lastName:
|
if not bName:
|
||||||
self._doResetBuildName()
|
self._doResetBuildName()
|
||||||
|
bName = self.buildName.text().strip()
|
||||||
lastPath = self._build.lastPath
|
|
||||||
selExt = nwLabels.BUILD_EXT[selFormat]
|
|
||||||
selName = Path(makeFileNameSafe(lastName)).with_suffix(selExt)
|
|
||||||
|
|
||||||
self.buildProgress.setValue(0)
|
self.buildProgress.setValue(0)
|
||||||
savePath, _ = QFileDialog.getSaveFileName(
|
bPath = Path(self.buildPath.text())
|
||||||
self, self.tr("Save Manuscript As"), str(lastPath / selName)
|
if not bPath.is_dir():
|
||||||
)
|
self.mainGui.makeAlert(self.tr("Output folder does not exist."), nwAlert.ERROR)
|
||||||
if not savePath:
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
buildPath = Path(savePath)
|
bExt = nwLabels.BUILD_EXT[bFormat]
|
||||||
|
buildPath = (bPath / makeFileNameSafe(bName)).with_suffix(bExt)
|
||||||
|
|
||||||
|
if buildPath.exists():
|
||||||
|
if not self.mainGui.askQuestion(
|
||||||
|
self.tr("File Exists"),
|
||||||
|
self.tr("The file already exists. Do you want to overwrite it?")
|
||||||
|
):
|
||||||
|
return False
|
||||||
|
|
||||||
docBuild = NWBuildDocument(self.theProject, self._build)
|
docBuild = NWBuildDocument(self.theProject, self._build)
|
||||||
docBuild.queueAll()
|
docBuild.queueAll()
|
||||||
|
|
||||||
self.buildProgress.setMaximum(len(docBuild))
|
self.buildProgress.setMaximum(len(docBuild))
|
||||||
for i, _ in docBuild.iterBuild(buildPath, selFormat):
|
for i, _ in docBuild.iterBuild(buildPath, bFormat):
|
||||||
self.buildProgress.setValue(i+1)
|
self.buildProgress.setValue(i+1)
|
||||||
|
|
||||||
self._build.setLastFormat(selFormat)
|
self._build.setLastPath(bPath)
|
||||||
self._build.setLastPath(buildPath.parent)
|
self._build.setLastBuildName(bName)
|
||||||
self._build.setLastBuildName(lastName)
|
self._build.setLastFormat(bFormat)
|
||||||
|
|
||||||
|
QTimer.singleShot(1000, self._resetProgress)
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|||||||
@@ -18,7 +18,6 @@ a {color: rgb(66, 113, 174);}
|
|||||||
.break {text-align: left;}
|
.break {text-align: left;}
|
||||||
.synopsis {font-style: italic;}
|
.synopsis {font-style: italic;}
|
||||||
.comment {font-style: italic; color: rgb(100, 100, 100);}
|
.comment {font-style: italic; color: rgb(100, 100, 100);}
|
||||||
article {width: 800px; margin: 40px auto;}
|
|
||||||
</style>
|
</style>
|
||||||
<body>
|
<body>
|
||||||
<article>
|
<article>
|
||||||
|
|||||||
@@ -517,7 +517,6 @@ def testCoreToHtml_Complex(mockGUI, fncPath):
|
|||||||
# ==========
|
# ==========
|
||||||
|
|
||||||
theStyle = theHtml.getStyleSheet()
|
theStyle = theHtml.getStyleSheet()
|
||||||
theStyle.append("article {width: 800px; margin: 40px auto;}")
|
|
||||||
htmlDoc = (
|
htmlDoc = (
|
||||||
"<!DOCTYPE html>\n"
|
"<!DOCTYPE html>\n"
|
||||||
"<html>\n"
|
"<html>\n"
|
||||||
@@ -540,7 +539,7 @@ def testCoreToHtml_Complex(mockGUI, fncPath):
|
|||||||
)
|
)
|
||||||
|
|
||||||
saveFile = fncPath / "outFile.htm"
|
saveFile = fncPath / "outFile.htm"
|
||||||
theHtml.saveHTML5(saveFile)
|
theHtml.saveHtml5(saveFile)
|
||||||
assert readFile(saveFile) == htmlDoc
|
assert readFile(saveFile) == htmlDoc
|
||||||
|
|
||||||
# END Test testCoreToHtml_Complex
|
# END Test testCoreToHtml_Complex
|
||||||
|
|||||||
Reference in New Issue
Block a user