diff --git a/novelwriter/core/options.py b/novelwriter/core/options.py index e0ce2a2c..bad425b1 100644 --- a/novelwriter/core/options.py +++ b/novelwriter/core/options.py @@ -43,13 +43,6 @@ VALID_MAP = { "hideZeros", "hideNegative", "groupByDay", "showIdleTime", "histMax", }, "GuiDocSplit": {"spLevel", "intoFolder", "docHierarchy"}, - "GuiBuildNovel": { - "winWidth", "winHeight", "boxWidth", "docWidth", "hideScene", - "hideSection", "addNovel", "addNotes", "ignoreFlag", "justifyText", - "excludeBody", "textFont", "textSize", "lineHeight", "noStyling", - "incSynopsis", "incComments", "incKeywords", "incBodyText", - "replaceTabs", "replaceUCode", "rootFilter", - }, "GuiOutline": {"headerOrder", "columnWidth", "columnHidden"}, "GuiProjectSettings": { "winWidth", "winHeight", "replaceColW", "statusColW", "importColW", diff --git a/novelwriter/gui/mainmenu.py b/novelwriter/gui/mainmenu.py index 8870fa37..80b1f8ae 100644 --- a/novelwriter/gui/mainmenu.py +++ b/novelwriter/gui/mainmenu.py @@ -829,11 +829,6 @@ class GuiMainMenu(QMenuBar): self.aBackupProject.triggered.connect(lambda: self.theProject.backupProject(True)) self.toolsMenu.addAction(self.aBackupProject) - # Tools > Build Novel Project - self.aBuildProject = QAction(self.tr("Build Novel Project"), self) - self.aBuildProject.triggered.connect(self.mainGui.showBuildProjectDialog) - self.toolsMenu.addAction(self.aBuildProject) - # Tools > Build Manuscript self.aBuildManuscript = QAction(self.tr("Build Manuscript"), self) self.aBuildManuscript.setShortcut("F5") diff --git a/novelwriter/gui/sidebar.py b/novelwriter/gui/sidebar.py index 66370b36..637e11a5 100644 --- a/novelwriter/gui/sidebar.py +++ b/novelwriter/gui/sidebar.py @@ -82,8 +82,8 @@ class GuiSideBar(QToolBar): self.aBuild = QAction(self.tr("Build"), self) self.aBuild.setFont(lblFont) - self.aBuild.setToolTip(self.tr("Build Novel Project")) - self.aBuild.triggered.connect(lambda: self.mainGui.showBuildProjectDialog()) + self.aBuild.setToolTip(self.tr("Build Manuscript")) + self.aBuild.triggered.connect(lambda: self.mainGui.showBuildManuscriptDialog()) self.aDetails = QAction(self.tr("Details"), self) self.aDetails.setFont(lblFont) diff --git a/novelwriter/guimain.py b/novelwriter/guimain.py index f2922b24..2b536b8a 100644 --- a/novelwriter/guimain.py +++ b/novelwriter/guimain.py @@ -55,7 +55,6 @@ from novelwriter.dialogs.wordlist import GuiWordList from novelwriter.dialogs.preferences import GuiPreferences from novelwriter.dialogs.projdetails import GuiProjectDetails from novelwriter.dialogs.projsettings import GuiProjectSettings -from novelwriter.tools.build import GuiBuildNovel from novelwriter.tools.lipsum import GuiLipsum from novelwriter.tools.manuscript import GuiManuscript from novelwriter.tools.projwizard import GuiProjectWizard @@ -1010,26 +1009,6 @@ class GuiMain(QMainWindow): return True - def showBuildProjectDialog(self): - """Open the build project dialog. - """ - if not self.hasProject: - logger.error("No project open") - return False - - dlgBuild = getGuiItem("GuiBuildNovel") - if dlgBuild is None: - dlgBuild = GuiBuildNovel(self) - assert isinstance(dlgBuild, GuiBuildNovel) - - dlgBuild.setModal(False) - dlgBuild.show() - dlgBuild.raise_() - qApp.processEvents() - dlgBuild.viewCachedDoc() - - return True - @pyqtSlot() def showBuildManuscriptDialog(self): """Open the build manuscript dialog. diff --git a/novelwriter/tools/build.py b/novelwriter/tools/build.py deleted file mode 100644 index c013c7b8..00000000 --- a/novelwriter/tools/build.py +++ /dev/null @@ -1,1425 +0,0 @@ -""" -novelWriter – GUI Build Novel Project -===================================== -GUI classes for the build novel project dialog - -File History: -Created: 2020-05-09 [0.5] - -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 . -""" - -import json -import logging - -from time import time -from pathlib import Path -from datetime import datetime - -from PyQt5.QtGui import ( - QPalette, QColor, QFont, QCursor, QFontInfo -) -from PyQt5.QtCore import Qt, QTimer -from PyQt5.QtWidgets import ( - qApp, QDialog, QVBoxLayout, QHBoxLayout, QTextBrowser, QPushButton, QLabel, - QLineEdit, QGroupBox, QGridLayout, QProgressBar, QMenu, QAction, - QFileDialog, QFontDialog, QSpinBox, QScrollArea, QSplitter, QWidget, - QSizePolicy, QDoubleSpinBox, QComboBox -) -from PyQt5.QtPrintSupport import QPrinter, QPrintPreviewDialog - -from novelwriter import CONFIG -from novelwriter.enum import nwAlert, nwItemType, nwItemLayout, nwItemClass -from novelwriter.error import formatException, logException -from novelwriter.common import fuzzyTime, makeFileNameSafe -from novelwriter.constants import nwConst, nwFiles -from novelwriter.core.tomd import ToMarkdown -from novelwriter.core.toodt import ToOdt -from novelwriter.core.tohtml import ToHtml -from novelwriter.extensions.switch import NSwitch - -logger = logging.getLogger(__name__) - - -class GuiBuildNovel(QDialog): - - FMT_PDF = 1 # Print to PDF - FMT_ODT = 2 # Open Document file - FMT_FODT = 3 # Flat Open Document file - FMT_HTM = 4 # HTML5 - FMT_NWD = 5 # nW Markdown - FMT_MD = 6 # Standard Markdown - FMT_GH = 7 # GitHub Markdown - FMT_JSON_H = 8 # HTML5 wrapped in JSON - FMT_JSON_M = 9 # nW Markdown wrapped in JSON - - def __init__(self, mainGui): - super().__init__(parent=mainGui) - - logger.debug("Initialising GuiBuildNovel ...") - self.setObjectName("GuiBuildNovel") - - self.mainGui = mainGui - self.mainTheme = mainGui.mainTheme - self.theProject = mainGui.theProject - - self.htmlText = [] # List of html documents - self.htmlStyle = [] # List of html styles - self.htmlSize = 0 # Size of the html document - self.buildTime = 0 # The timestamp of the last build - - self.setWindowTitle(self.tr("Build Novel Project")) - self.setMinimumWidth(CONFIG.pxInt(700)) - self.setMinimumHeight(CONFIG.pxInt(600)) - - pOptions = self.theProject.options - self.resize( - CONFIG.pxInt(pOptions.getInt("GuiBuildNovel", "winWidth", 900)), - CONFIG.pxInt(pOptions.getInt("GuiBuildNovel", "winHeight", 800)) - ) - - self.docView = GuiBuildNovelDocView(self, self.theProject) - - hS = self.mainTheme.fontPixelSize - wS = 2*hS - - # Title Formats - # ============= - - self.titleGroup = QGroupBox(self.tr("Title Formats for Novel Files"), self) - self.titleForm = QGridLayout(self) - self.titleGroup.setLayout(self.titleForm) - - fmtHelp = "
".join([ - "%s" % self.tr("Formatting Codes:"), - self.tr("{0} for the title as set in the document").format(r"%title%"), - self.tr("{0} for chapter number (1, 2, 3)").format(r"%ch%"), - self.tr("{0} for chapter number as a word (one, two)").format(r"%chw%"), - self.tr("{0} for chapter number in upper case Roman").format(r"%chI%"), - self.tr("{0} for chapter number in lower case Roman").format(r"%chi%"), - self.tr("{0} for scene number within chapter").format(r"%sc%"), - self.tr("{0} for scene number within novel").format(r"%sca%"), - ]) - fmtScHelp = "

%s" % self.tr( - "Leave blank to skip this heading, or set to a static text, like " - "for instance '{0}', to make a separator. The separator will " - "be centred automatically and only appear between sections of " - "the same type." - ).format("* * *") - xFmt = CONFIG.pxInt(100) - - self.fmtTitle = QLineEdit() - self.fmtTitle.setMaxLength(200) - self.fmtTitle.setMinimumWidth(xFmt) - self.fmtTitle.setToolTip(fmtHelp) - self.fmtTitle.setText( - self._reFmtCodes(self.theProject.data.getTitleFormat("title")) - ) - - self.fmtChapter = QLineEdit() - self.fmtChapter.setMaxLength(200) - self.fmtChapter.setMinimumWidth(xFmt) - self.fmtChapter.setToolTip(fmtHelp) - self.fmtChapter.setText( - self._reFmtCodes(self.theProject.data.getTitleFormat("chapter")) - ) - - self.fmtUnnumbered = QLineEdit() - self.fmtUnnumbered.setMaxLength(200) - self.fmtUnnumbered.setMinimumWidth(xFmt) - self.fmtUnnumbered.setToolTip(fmtHelp) - self.fmtUnnumbered.setText( - self._reFmtCodes(self.theProject.data.getTitleFormat("unnumbered")) - ) - - self.fmtScene = QLineEdit() - self.fmtScene.setMaxLength(200) - self.fmtScene.setMinimumWidth(xFmt) - self.fmtScene.setToolTip(fmtHelp + fmtScHelp) - self.fmtScene.setText( - self._reFmtCodes(self.theProject.data.getTitleFormat("scene")) - ) - - self.fmtSection = QLineEdit() - self.fmtSection.setMaxLength(200) - self.fmtSection.setMinimumWidth(xFmt) - self.fmtSection.setToolTip(fmtHelp + fmtScHelp) - self.fmtSection.setText( - self._reFmtCodes(self.theProject.data.getTitleFormat("section")) - ) - - self.buildLang = QComboBox() - self.buildLang.setMinimumWidth(xFmt) - theLangs = CONFIG.listLanguages(CONFIG.LANG_PROJ) - self.buildLang.addItem("[%s]" % self.tr("Not Set"), "None") - for langID, langName in theLangs: - self.buildLang.addItem(langName, langID) - - langIdx = self.buildLang.findData(self.theProject.data.language) - if langIdx != -1: - self.buildLang.setCurrentIndex(langIdx) - - self.hideScene = NSwitch(width=wS, height=hS) - self.hideScene.setChecked( - pOptions.getBool("GuiBuildNovel", "hideScene", False) - ) - - self.hideSection = NSwitch(width=wS, height=hS) - self.hideSection.setChecked( - pOptions.getBool("GuiBuildNovel", "hideSection", True) - ) - - # Wrapper boxes due to QGridView and QLineEdit expand bug - self.boxTitle = QHBoxLayout() - self.boxTitle.addWidget(self.fmtTitle) - self.boxChapter = QHBoxLayout() - self.boxChapter.addWidget(self.fmtChapter) - self.boxUnnumb = QHBoxLayout() - self.boxUnnumb.addWidget(self.fmtUnnumbered) - self.boxScene = QHBoxLayout() - self.boxScene.addWidget(self.fmtScene) - self.boxSection = QHBoxLayout() - self.boxSection.addWidget(self.fmtSection) - - titleLabel = QLabel(self.tr("Title")) - chapterLabel = QLabel(self.tr("Chapter")) - unnumbLabel = QLabel(self.tr("Unnumbered")) - sceneLabel = QLabel(self.tr("Scene")) - sectionLabel = QLabel(self.tr("Section")) - langLabel = QLabel(self.tr("Language")) - hSceneLabel = QLabel(self.tr("Hide scene")) - hSectionLabel = QLabel(self.tr("Hide section")) - - self.titleForm.addWidget(titleLabel, 0, 0, 1, 1, Qt.AlignLeft) - self.titleForm.addLayout(self.boxTitle, 0, 1, 1, 1, Qt.AlignRight) - self.titleForm.addWidget(chapterLabel, 1, 0, 1, 1, Qt.AlignLeft) - self.titleForm.addLayout(self.boxChapter, 1, 1, 1, 1, Qt.AlignRight) - self.titleForm.addWidget(unnumbLabel, 2, 0, 1, 1, Qt.AlignLeft) - self.titleForm.addLayout(self.boxUnnumb, 2, 1, 1, 1, Qt.AlignRight) - self.titleForm.addWidget(sceneLabel, 3, 0, 1, 1, Qt.AlignLeft) - self.titleForm.addLayout(self.boxScene, 3, 1, 1, 1, Qt.AlignRight) - self.titleForm.addWidget(sectionLabel, 4, 0, 1, 1, Qt.AlignLeft) - self.titleForm.addLayout(self.boxSection, 4, 1, 1, 1, Qt.AlignRight) - self.titleForm.addWidget(langLabel, 5, 0, 1, 1, Qt.AlignLeft) - self.titleForm.addWidget(self.buildLang, 5, 1, 1, 1, Qt.AlignRight) - self.titleForm.addWidget(hSceneLabel, 6, 0, 1, 1, Qt.AlignLeft) - self.titleForm.addWidget(self.hideScene, 6, 1, 1, 1, Qt.AlignRight) - self.titleForm.addWidget(hSectionLabel, 7, 0, 1, 1, Qt.AlignLeft) - self.titleForm.addWidget(self.hideSection, 7, 1, 1, 1, Qt.AlignRight) - - self.titleForm.setColumnStretch(0, 0) - self.titleForm.setColumnStretch(1, 1) - - # Font Options - # ============ - - self.fontGroup = QGroupBox(self.tr("Font Options"), self) - self.fontForm = QGridLayout(self) - self.fontGroup.setLayout(self.fontForm) - - # Font Family - self.textFont = QLineEdit() - self.textFont.setReadOnly(True) - self.textFont.setMinimumWidth(xFmt) - self.textFont.setText( - pOptions.getString("GuiBuildNovel", "textFont", CONFIG.textFont) - ) - self.fontButton = QPushButton("...") - self.fontButton.setMaximumWidth(int(2.5*self.mainTheme.getTextWidth("..."))) - self.fontButton.clicked.connect(self._selectFont) - - self.textSize = QSpinBox(self) - self.textSize.setFixedWidth(6*self.mainTheme.textNWidth) - self.textSize.setMinimum(6) - self.textSize.setMaximum(72) - self.textSize.setSingleStep(1) - self.textSize.setValue( - pOptions.getInt("GuiBuildNovel", "textSize", CONFIG.textSize) - ) - - self.lineHeight = QDoubleSpinBox(self) - self.lineHeight.setFixedWidth(6*self.mainTheme.textNWidth) - self.lineHeight.setMinimum(0.8) - self.lineHeight.setMaximum(3.0) - self.lineHeight.setSingleStep(0.05) - self.lineHeight.setDecimals(2) - self.lineHeight.setValue( - pOptions.getFloat("GuiBuildNovel", "lineHeight", 1.15) - ) - - # Wrapper box due to QGridView and QLineEdit expand bug - self.boxFont = QHBoxLayout() - self.boxFont.addWidget(self.textFont) - - fontFamilyLabel = QLabel(self.tr("Font family")) - fontSizeLabel = QLabel(self.tr("Font size")) - lineHeightLabel = QLabel(self.tr("Line height")) - justifyLabel = QLabel(self.tr("Justify text")) - stylingLabel = QLabel(self.tr("Disable styling")) - - self.fontForm.addWidget(fontFamilyLabel, 0, 0, 1, 1, Qt.AlignLeft) - self.fontForm.addLayout(self.boxFont, 0, 1, 1, 1, Qt.AlignRight) - self.fontForm.addWidget(self.fontButton, 0, 2, 1, 1, Qt.AlignRight) - self.fontForm.addWidget(fontSizeLabel, 1, 0, 1, 1, Qt.AlignLeft) - self.fontForm.addWidget(self.textSize, 1, 1, 1, 2, Qt.AlignRight) - self.fontForm.addWidget(lineHeightLabel, 2, 0, 1, 1, Qt.AlignLeft) - self.fontForm.addWidget(self.lineHeight, 2, 1, 1, 2, Qt.AlignRight) - - self.fontForm.setColumnStretch(0, 0) - self.fontForm.setColumnStretch(1, 1) - self.fontForm.setColumnStretch(2, 0) - - # Styling Options - # =============== - - self.styleGroup = QGroupBox(self.tr("Styling Options"), self) - self.styleForm = QGridLayout(self) - self.styleGroup.setLayout(self.styleForm) - - self.justifyText = NSwitch(width=wS, height=hS) - self.justifyText.setChecked( - pOptions.getBool("GuiBuildNovel", "justifyText", False) - ) - - self.noStyling = NSwitch(width=wS, height=hS) - self.noStyling.setChecked( - pOptions.getBool("GuiBuildNovel", "noStyling", False) - ) - - self.styleForm.addWidget(justifyLabel, 1, 0, 1, 1, Qt.AlignLeft) - self.styleForm.addWidget(self.justifyText, 1, 1, 1, 2, Qt.AlignRight) - self.styleForm.addWidget(stylingLabel, 2, 0, 1, 1, Qt.AlignLeft) - self.styleForm.addWidget(self.noStyling, 2, 1, 1, 2, Qt.AlignRight) - - self.styleForm.setColumnStretch(0, 0) - self.styleForm.setColumnStretch(1, 1) - - # Include Options - # =============== - - self.textGroup = QGroupBox(self.tr("Include Options"), self) - self.textForm = QGridLayout(self) - self.textGroup.setLayout(self.textForm) - - self.includeSynopsis = NSwitch(width=wS, height=hS) - self.includeSynopsis.setChecked( - pOptions.getBool("GuiBuildNovel", "incSynopsis", False) - ) - - self.includeComments = NSwitch(width=wS, height=hS) - self.includeComments.setChecked( - pOptions.getBool("GuiBuildNovel", "incComments", False) - ) - - self.includeKeywords = NSwitch(width=wS, height=hS) - self.includeKeywords.setChecked( - pOptions.getBool("GuiBuildNovel", "incKeywords", False) - ) - - self.includeBody = NSwitch(width=wS, height=hS) - self.includeBody.setChecked( - pOptions.getBool("GuiBuildNovel", "incBodyText", True) - ) - - synopsisLabel = QLabel(self.tr("Include synopsis")) - commentsLabel = QLabel(self.tr("Include comments")) - keywordsLabel = QLabel(self.tr("Include keywords")) - bodyLabel = QLabel(self.tr("Include body text")) - - self.textForm.addWidget(synopsisLabel, 0, 0, 1, 1, Qt.AlignLeft) - self.textForm.addWidget(self.includeSynopsis, 0, 1, 1, 1, Qt.AlignRight) - self.textForm.addWidget(commentsLabel, 1, 0, 1, 1, Qt.AlignLeft) - self.textForm.addWidget(self.includeComments, 1, 1, 1, 1, Qt.AlignRight) - self.textForm.addWidget(keywordsLabel, 2, 0, 1, 1, Qt.AlignLeft) - self.textForm.addWidget(self.includeKeywords, 2, 1, 1, 1, Qt.AlignRight) - self.textForm.addWidget(bodyLabel, 3, 0, 1, 1, Qt.AlignLeft) - self.textForm.addWidget(self.includeBody, 3, 1, 1, 1, Qt.AlignRight) - - self.textForm.setColumnStretch(0, 1) - self.textForm.setColumnStretch(1, 0) - - # Root Filter Options - # =================== - - self.rootGroup = QGroupBox(self.tr("Root Filter Options"), self) - self.rootForm = QGridLayout(self) - self.rootGroup.setLayout(self.rootForm) - - rootFilter = pOptions.getValue("GuiBuildNovel", "rootFilter", []) - if not isinstance(rootFilter, list): - rootFilter = [] - - iRow = 0 - self.rootSelection = {} - for tHandle, nwItem in self.theProject.tree.iterRoots(None): - if not nwItem.isInactiveClass(): - rootLabel = QLabel(nwItem.itemName) - rootLabel.setWordWrap(True) - - rootValue = NSwitch(width=wS, height=hS) - rootValue.setChecked(tHandle not in rootFilter) - - self.rootSelection[tHandle] = rootValue - self.rootForm.addWidget(rootLabel, iRow, 0, 1, 1, Qt.AlignLeft) - self.rootForm.addWidget(rootValue, iRow, 1, 1, 1, Qt.AlignRight) - - iRow += 1 - - self.rootForm.setColumnStretch(0, 1) - self.rootForm.setColumnStretch(1, 0) - - # File Filter Options - # =================== - - self.fileGroup = QGroupBox(self.tr("File Filter Options"), self) - self.fileForm = QGridLayout(self) - self.fileGroup.setLayout(self.fileForm) - - self.novelFiles = NSwitch(width=wS, height=hS) - self.novelFiles.setChecked( - pOptions.getBool("GuiBuildNovel", "addNovel", True) - ) - - self.noteFiles = NSwitch(width=wS, height=hS) - self.noteFiles.setChecked( - pOptions.getBool("GuiBuildNovel", "addNotes", False) - ) - - self.ignoreFlag = NSwitch(width=wS, height=hS) - self.ignoreFlag.setChecked( - pOptions.getBool("GuiBuildNovel", "ignoreFlag", False) - ) - - novelLabel = QLabel(self.tr("Include novel files")) - notesLabel = QLabel(self.tr("Include note files")) - activeLabel = QLabel(self.tr("Include inactive files")) - - self.fileForm.addWidget(novelLabel, 0, 0, 1, 1, Qt.AlignLeft) - self.fileForm.addWidget(self.novelFiles, 0, 1, 1, 1, Qt.AlignRight) - self.fileForm.addWidget(notesLabel, 1, 0, 1, 1, Qt.AlignLeft) - self.fileForm.addWidget(self.noteFiles, 1, 1, 1, 1, Qt.AlignRight) - self.fileForm.addWidget(activeLabel, 2, 0, 1, 1, Qt.AlignLeft) - self.fileForm.addWidget(self.ignoreFlag, 2, 1, 1, 1, Qt.AlignRight) - - self.fileForm.setColumnStretch(0, 1) - self.fileForm.setColumnStretch(1, 0) - - # Export Options - # ============== - - self.exportGroup = QGroupBox(self.tr("Export Options"), self) - self.exportForm = QGridLayout(self) - self.exportGroup.setLayout(self.exportForm) - - self.replaceTabs = NSwitch(width=wS, height=hS) - self.replaceTabs.setChecked( - pOptions.getBool("GuiBuildNovel", "replaceTabs", False) - ) - - self.replaceUCode = NSwitch(width=wS, height=hS) - self.replaceUCode.setChecked( - pOptions.getBool("GuiBuildNovel", "replaceUCode", False) - ) - - tabsLabel = QLabel(self.tr("Replace tabs with spaces")) - uCodeLabel = QLabel(self.tr("Replace Unicode in HTML")) - - self.exportForm.addWidget(tabsLabel, 0, 0, 1, 1, Qt.AlignLeft) - self.exportForm.addWidget(self.replaceTabs, 0, 1, 1, 1, Qt.AlignRight) - self.exportForm.addWidget(uCodeLabel, 1, 0, 1, 1, Qt.AlignLeft) - self.exportForm.addWidget(self.replaceUCode, 1, 1, 1, 1, Qt.AlignRight) - - self.exportForm.setColumnStretch(0, 1) - self.exportForm.setColumnStretch(1, 0) - - # Build Button - # ============ - - self.buildProgress = QProgressBar() - - self.buildNovel = QPushButton(self.tr("Build Preview")) - self.buildNovel.clicked.connect(self._buildPreview) - - # Action Buttons - # ============== - - self.buttonBox = QHBoxLayout() - - # Printing - - self.printMenu = QMenu(self) - self.btnPrint = QPushButton(self.tr("Print")) - self.btnPrint.setMenu(self.printMenu) - - self.printSend = QAction(self.tr("Print Preview"), self) - self.printSend.triggered.connect(self._printDocument) - self.printMenu.addAction(self.printSend) - - self.printFile = QAction(self.tr("Print to PDF"), self) - self.printFile.triggered.connect(lambda: self._saveDocument(self.FMT_PDF)) - self.printMenu.addAction(self.printFile) - - # Saving to File - - self.saveMenu = QMenu(self) - self.btnSave = QPushButton(self.tr("Save As")) - self.btnSave.setMenu(self.saveMenu) - - self.saveODT = QAction(self.tr("Open Document (.odt)"), self) - self.saveODT.triggered.connect(lambda: self._saveDocument(self.FMT_ODT)) - self.saveMenu.addAction(self.saveODT) - - self.saveFODT = QAction(self.tr("Flat Open Document (.fodt)"), self) - self.saveFODT.triggered.connect(lambda: self._saveDocument(self.FMT_FODT)) - self.saveMenu.addAction(self.saveFODT) - - self.saveHTM = QAction(self.tr("novelWriter HTML (.htm)"), self) - self.saveHTM.triggered.connect(lambda: self._saveDocument(self.FMT_HTM)) - self.saveMenu.addAction(self.saveHTM) - - self.saveNWD = QAction(self.tr("novelWriter Markdown (.nwd)"), self) - self.saveNWD.triggered.connect(lambda: self._saveDocument(self.FMT_NWD)) - self.saveMenu.addAction(self.saveNWD) - - self.saveMD = QAction(self.tr("Standard Markdown (.md)"), self) - self.saveMD.triggered.connect(lambda: self._saveDocument(self.FMT_MD)) - self.saveMenu.addAction(self.saveMD) - - self.saveGH = QAction(self.tr("GitHub Markdown (.md)"), self) - self.saveGH.triggered.connect(lambda: self._saveDocument(self.FMT_GH)) - self.saveMenu.addAction(self.saveGH) - - self.saveJsonH = QAction(self.tr("JSON + novelWriter HTML (.json)"), self) - self.saveJsonH.triggered.connect(lambda: self._saveDocument(self.FMT_JSON_H)) - self.saveMenu.addAction(self.saveJsonH) - - self.saveJsonM = QAction(self.tr("JSON + novelWriter Markdown (.json)"), self) - self.saveJsonM.triggered.connect(lambda: self._saveDocument(self.FMT_JSON_M)) - self.saveMenu.addAction(self.saveJsonM) - - self.btnClose = QPushButton(self.tr("Close")) - self.btnClose.clicked.connect(self._doClose) - - self.buttonBox.addWidget(self.btnSave) - self.buttonBox.addWidget(self.btnPrint) - self.buttonBox.addWidget(self.btnClose) - self.buttonBox.setSpacing(CONFIG.pxInt(4)) - - # Assemble GUI - # ============ - - # Splitter Position - boxWidth = CONFIG.pxInt(350) - boxWidth = pOptions.getInt("GuiBuildNovel", "boxWidth", boxWidth) - docWidth = max(self.width() - boxWidth, 100) - docWidth = pOptions.getInt("GuiBuildNovel", "docWidth", docWidth) - - # The Tool Box - self.toolsBox = QVBoxLayout() - self.toolsBox.addWidget(self.titleGroup) - self.toolsBox.addWidget(self.fontGroup) - self.toolsBox.addWidget(self.styleGroup) - self.toolsBox.addWidget(self.textGroup) - self.toolsBox.addWidget(self.rootGroup) - self.toolsBox.addWidget(self.fileGroup) - self.toolsBox.addWidget(self.exportGroup) - self.toolsBox.addStretch(1) - - # Tool Box Wrapper Widget - self.toolsWidget = QWidget() - self.toolsWidget.setSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.Minimum) - self.toolsWidget.setLayout(self.toolsBox) - - # Tool Box Scroll Area - self.toolsArea = QScrollArea() - self.toolsArea.setMinimumWidth(CONFIG.pxInt(250)) - self.toolsArea.setWidgetResizable(True) - self.toolsArea.setWidget(self.toolsWidget) - - if CONFIG.hideVScroll: - self.toolsArea.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff) - else: - self.toolsArea.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded) - - if CONFIG.hideHScroll: - self.toolsArea.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) - else: - self.toolsArea.setHorizontalScrollBarPolicy(Qt.ScrollBarAsNeeded) - - # Tools and Buttons Layout - tSp = CONFIG.pxInt(8) - self.innerBox = QVBoxLayout() - self.innerBox.addWidget(self.toolsArea) - self.innerBox.addSpacing(tSp) - self.innerBox.addWidget(self.buildProgress) - self.innerBox.addWidget(self.buildNovel) - self.innerBox.addSpacing(tSp) - self.innerBox.addLayout(self.buttonBox) - - # Tools and Buttons Wrapper Widget - self.innerWidget = QWidget() - self.innerWidget.setLayout(self.innerBox) - - # Main Dialog Splitter - self.mainSplit = QSplitter(Qt.Horizontal) - self.mainSplit.addWidget(self.innerWidget) - self.mainSplit.addWidget(self.docView) - self.mainSplit.setSizes([boxWidth, docWidth]) - - self.idxSettings = self.mainSplit.indexOf(self.innerWidget) - self.idxDocument = self.mainSplit.indexOf(self.docView) - - self.mainSplit.setCollapsible(self.idxSettings, False) - self.mainSplit.setCollapsible(self.idxDocument, False) - - # Outer Layout - self.outerBox = QHBoxLayout() - self.outerBox.addWidget(self.mainSplit) - - self.setLayout(self.outerBox) - self.buildNovel.setFocus() - - logger.debug("GuiBuildNovel initialisation complete") - - return - - def viewCachedDoc(self): - """Load the previously generated document from cache. - """ - if self._loadCache(): - textFont = self.textFont.text() - textSize = self.textSize.value() - justifyText = self.justifyText.isChecked() - self.docView.setTextFont(textFont, textSize) - self.docView.setJustify(justifyText) - if self.noStyling.isChecked(): - self.docView.clearStyleSheet() - else: - self.docView.setStyleSheet(self.htmlStyle) - - htmlSize = sum([len(x) for x in self.htmlText]) - if htmlSize < nwConst.MAX_BUILDSIZE: - qApp.processEvents() - self.docView.setContent(self.htmlText, self.buildTime) - else: - self.docView.setText( - self.tr("Failed to generate preview. The result is too big.") - ) - - else: - self.htmlText = [] - self.htmlStyle = [] - self.buildTime = 0 - return False - - return True - - ## - # Slots and Related - ## - - def _buildPreview(self): - """Build a preview of the project in the document viewer. - """ - # Get Settings - justifyText = self.justifyText.isChecked() - noStyling = self.noStyling.isChecked() - textFont = self.textFont.text() - textSize = self.textSize.value() - replaceTabs = self.replaceTabs.isChecked() - - self.htmlText = [] - self.htmlStyle = [] - self.htmlSize = 0 - - # Build Preview - # ============= - - makeHtml = ToHtml(self.theProject) - self._doBuild(makeHtml, isPreview=True) - if replaceTabs: - makeHtml.replaceTabs() - - self.htmlText = makeHtml.fullHTML - self.htmlStyle = makeHtml.getStyleSheet() - self.htmlSize = makeHtml.getFullResultSize() - self.buildTime = int(time()) - - # Load Preview - # ============ - - self.docView.setTextFont(textFont, textSize) - self.docView.setJustify(justifyText) - if noStyling: - self.docView.clearStyleSheet() - else: - self.docView.setStyleSheet(self.htmlStyle) - - if self.htmlSize < nwConst.MAX_BUILDSIZE: - self.docView.setContent(self.htmlText, self.buildTime) - else: - self.docView.setText( - "Failed to generate preview. The result is too big." - ) - - self._saveCache() - - return - - def _doBuild(self, bldObj, isPreview=False, doConvert=True): - """Run the build with a specific build object. - """ - tStart = int(time()) - - # Get Settings - fmtTitle = self.fmtTitle.text() - fmtChapter = self.fmtChapter.text() - fmtUnnumbered = self.fmtUnnumbered.text() - fmtScene = self.fmtScene.text() - fmtSection = self.fmtSection.text() - buildLang = self.buildLang.currentData() - hideScene = self.hideScene.isChecked() - hideSection = self.hideSection.isChecked() - textFont = self.textFont.text() - textSize = self.textSize.value() - lineHeight = self.lineHeight.value() - justifyText = self.justifyText.isChecked() - noStyling = self.noStyling.isChecked() - incSynopsis = self.includeSynopsis.isChecked() - incComments = self.includeComments.isChecked() - incKeywords = self.includeKeywords.isChecked() - novelFiles = self.novelFiles.isChecked() - noteFiles = self.noteFiles.isChecked() - ignoreFlag = self.ignoreFlag.isChecked() - includeBody = self.includeBody.isChecked() - replaceUCode = self.replaceUCode.isChecked() - - # The language lookup dict is reloaded if needed - self.theProject.setProjectLang(buildLang) - - # Get font information - fontInfo = QFontInfo(QFont(textFont, textSize)) - textFixed = fontInfo.fixedPitch() - - isHtml = isinstance(bldObj, ToHtml) - isOdt = isinstance(bldObj, ToOdt) - - bldObj.setTitleFormat(fmtTitle) - bldObj.setChapterFormat(fmtChapter) - bldObj.setUnNumberedFormat(fmtUnnumbered) - bldObj.setSceneFormat(fmtScene, hideScene) - bldObj.setSectionFormat(fmtSection, hideSection) - - bldObj.setFont(textFont, textSize, textFixed) - bldObj.setJustify(justifyText) - bldObj.setLineHeight(lineHeight) - - bldObj.setSynopsis(incSynopsis) - bldObj.setComments(incComments) - bldObj.setKeywords(incKeywords) - bldObj.setBodyText(includeBody) - - if isHtml: - bldObj.setStyles(not noStyling) - bldObj.setReplaceUnicode(replaceUCode) - - if isOdt: - bldObj.setColourHeaders(not noStyling) - bldObj.setLanguage(buildLang) - bldObj.initDocument() - - # Make sure the project and document is up to date - self.mainGui.saveDocument() - - self.buildProgress.setMaximum(len(self.theProject.tree)) - self.buildProgress.setValue(0) - - rootFilter = set(self._generateRootFilter()) - for nItt, tItem in enumerate(self.theProject.tree): - - noteRoot = noteFiles - noteRoot &= tItem.itemType == nwItemType.ROOT - noteRoot &= tItem.itemClass != nwItemClass.NOVEL - noteRoot &= tItem.itemClass != nwItemClass.ARCHIVE - - try: - if noteRoot: - # Add headers for root folders of notes - bldObj.addRootHeading(tItem.itemHandle) - if doConvert: - bldObj.doConvert() - - elif self._checkInclude(tItem, noteFiles, novelFiles, ignoreFlag, rootFilter): - bldObj.setText(tItem.itemHandle) - bldObj.doPreProcessing() - bldObj.tokenizeText() - bldObj.doHeaders() - if doConvert: - bldObj.doConvert() - - except Exception: - logger.error("Failed to build document '%s'", tItem.itemHandle) - logException() - if isPreview: - self.docView.setText(( - "Failed to generate preview. " - "Document with title '%s' could not be parsed." - ) % tItem.itemName) - - return False - - # Update progress bar, also for skipped items - self.buildProgress.setValue(nItt+1) - - if isOdt: - bldObj.closeDocument() - - tEnd = int(time()) - logger.debug("Built project in %.3f ms", 1000*(tEnd - tStart)) - - if bldObj.errData: - self.mainGui.makeAlert([ - self.tr("There were problems when building the project:") - ] + bldObj.errData, nwAlert.ERROR) - - return - - def _checkInclude(self, theItem, noteFiles, novelFiles, ignoreFlag, rootFilter): - """This function checks whether a file should be included in the - export or not. For standard note and novel files, this is - controlled by the options selected by the user. For other files - classified as non-exportable, a few checks must be made, and the - following are not: - * Items that are not actual files. - * Items that have been orphaned which are tagged as NO_LAYOUT - and NO_CLASS. - * Items that appear in the TRASH folder or have parent set to - None (orphaned files). - """ - if theItem is None: - return False - - if not (theItem.isActive or ignoreFlag): - return False - - if theItem.itemRoot in rootFilter: - return False - - isNone = not theItem.isFileType() - isNone |= theItem.itemLayout == nwItemLayout.NO_LAYOUT - isNone |= theItem.isInactiveClass() - isNone |= theItem.itemParent is None - isNote = theItem.isNoteLayout() - isNovel = not isNone and not isNote - - if isNone: - return False - if isNote and not noteFiles: - return False - if isNovel and not novelFiles: - return False - - return True - - def _saveDocument(self, theFmt): - """Save the document to various formats. - """ - replaceTabs = self.replaceTabs.isChecked() - - fileExt = "" - textFmt = "" - - # Settings - # ======== - - if theFmt == self.FMT_ODT: - fileExt = "odt" - textFmt = self.tr("Open Document") - - elif theFmt == self.FMT_FODT: - fileExt = "fodt" - textFmt = self.tr("Flat Open Document") - - elif theFmt == self.FMT_HTM: - fileExt = "htm" - textFmt = self.tr("Plain HTML") - - elif theFmt == self.FMT_NWD: - fileExt = "nwd" - textFmt = self.tr("novelWriter Markdown") - - elif theFmt == self.FMT_MD: - fileExt = "md" - textFmt = self.tr("Standard Markdown") - - elif theFmt == self.FMT_GH: - fileExt = "md" - textFmt = self.tr("GitHub Markdown") - - elif theFmt == self.FMT_JSON_H: - fileExt = "json" - textFmt = self.tr("JSON + novelWriter HTML") - - elif theFmt == self.FMT_JSON_M: - fileExt = "json" - textFmt = self.tr("JSON + novelWriter Markdown") - - elif theFmt == self.FMT_PDF: - fileExt = "pdf" - textFmt = self.tr("PDF") - - else: - return False - - # Generate File Name - # ================== - - cleanName = makeFileNameSafe(self.theProject.data.name) - fileName = "%s.%s" % (cleanName, fileExt) - savePath = CONFIG.lastPath() / fileName - savePath, _ = QFileDialog.getSaveFileName( - self, self.tr("Save Document As"), str(savePath) - ) - if not savePath: - return False - - CONFIG.setLastPath(savePath) - - # Build and Write - # =============== - - errMsg = "" - wSuccess = False - - if theFmt == self.FMT_ODT: - makeOdt = ToOdt(self.theProject, isFlat=False) - self._doBuild(makeOdt) - try: - makeOdt.saveOpenDocText(savePath) - wSuccess = True - except Exception as exc: - errMsg = formatException(exc) - - elif theFmt == self.FMT_FODT: - makeOdt = ToOdt(self.theProject, isFlat=True) - self._doBuild(makeOdt) - try: - makeOdt.saveFlatXML(savePath) - wSuccess = True - except Exception as exc: - errMsg = formatException(exc) - - elif theFmt == self.FMT_HTM: - makeHtml = ToHtml(self.theProject) - self._doBuild(makeHtml) - if replaceTabs: - makeHtml.replaceTabs() - - try: - makeHtml.saveHTML5(savePath) - wSuccess = True - except Exception as exc: - errMsg = formatException(exc) - - elif theFmt == self.FMT_NWD: - makeNwd = ToMarkdown(self.theProject) - makeNwd.setKeepMarkdown(True) - self._doBuild(makeNwd, doConvert=False) - if replaceTabs: - makeNwd.replaceTabs(spaceChar=" ") - - try: - makeNwd.saveRawMarkdown(savePath) - wSuccess = True - except Exception as exc: - errMsg = formatException(exc) - - elif theFmt in (self.FMT_MD, self.FMT_GH): - makeMd = ToMarkdown(self.theProject) - if theFmt == self.FMT_GH: - makeMd.setGitHubMarkdown() - else: - makeMd.setStandardMarkdown() - - self._doBuild(makeMd) - if replaceTabs: - makeMd.replaceTabs(nSpaces=4, spaceChar=" ") - - try: - makeMd.saveMarkdown(savePath) - wSuccess = True - except Exception as exc: - errMsg = formatException(exc) - - elif theFmt == self.FMT_JSON_H or theFmt == self.FMT_JSON_M: - jsonData = { - "meta": { - "projectName": self.theProject.data.name, - "novelTitle": self.theProject.data.title, - "novelAuthor": self.theProject.data.author, - "buildTime": self.buildTime, - } - } - - if theFmt == self.FMT_JSON_H: - makeHtml = ToHtml(self.theProject) - self._doBuild(makeHtml) - if replaceTabs: - makeHtml.replaceTabs() - - theBody = [] - for htmlPage in makeHtml.fullHTML: - theBody.append(htmlPage.rstrip("\n").split("\n")) - jsonData["text"] = { - "css": self.htmlStyle, - "html": theBody, - } - - elif theFmt == self.FMT_JSON_M: - makeMd = ToHtml(self.theProject) - makeMd.setKeepMarkdown(True) - self._doBuild(makeMd, doConvert=False) - if replaceTabs: - makeMd.replaceTabs(spaceChar=" ") - - theBody = [] - for nwdPage in makeMd.theMarkdown: - theBody.append(nwdPage.split("\n")) - jsonData["text"] = { - "nwd": theBody, - } - - try: - with open(savePath, mode="w", encoding="utf-8") as outFile: - outFile.write(json.dumps(jsonData, indent=2)) - wSuccess = True - except Exception as exc: - errMsg = formatException(exc) - - elif theFmt == self.FMT_PDF: - try: - thePrinter = QPrinter() - thePrinter.setOutputFormat(QPrinter.PdfFormat) - thePrinter.setOrientation(QPrinter.Portrait) - thePrinter.setDuplex(QPrinter.DuplexLongSide) - thePrinter.setFontEmbeddingEnabled(True) - thePrinter.setColorMode(QPrinter.Color) - thePrinter.setOutputFileName(savePath) - self.docView.document().print(thePrinter) - wSuccess = True - - except Exception as exc: - errMsg = formatException(exc) - - else: - # If the if statements above and here match, it should not - # be possible to reach this else statement. - return False # pragma: no cover - - # Report to User - # ============== - - if wSuccess: - self.mainGui.makeAlert([ - self.tr("{0} file successfully written to:").format(textFmt), savePath - ], nwAlert.INFO) - else: - self.mainGui.makeAlert(self.tr( - "Failed to write {0} file. {1}" - ).format(textFmt, errMsg), nwAlert.ERROR) - - return wSuccess - - def _printDocument(self): - """Open the print preview dialog. - """ - thePreview = QPrintPreviewDialog(self) - thePreview.paintRequested.connect(self._doPrintPreview) - thePreview.exec_() - return - - def _doPrintPreview(self, thePrinter): - """Connect the print preview painter to the document viewer. - """ - qApp.setOverrideCursor(QCursor(Qt.WaitCursor)) - thePrinter.setOrientation(QPrinter.Portrait) - self.docView.document().print(thePrinter) - qApp.restoreOverrideCursor() - return - - def _selectFont(self): - """Open the QFontDialog and set a font for the font style. - """ - currFont = QFont() - currFont.setFamily(self.textFont.text()) - currFont.setPointSize(self.textSize.value()) - theFont, theStatus = QFontDialog.getFont(currFont, self) - if theStatus: - self.textFont.setText(theFont.family()) - self.textSize.setValue(theFont.pointSize()) - - self.raise_() # Move the dialog to front (fixes a bug on macOS) - - return - - def _generateRootFilter(self): - """Return a list of all root folders that are filtered out. - """ - return [h for h, s in self.rootSelection.items() if not s.isChecked()] - - def _loadCache(self): - """Save the current data to cache. - """ - buildCache = self.theProject.storage.getCacheFile(nwFiles.BUILD_CACHE) - if not isinstance(buildCache, Path): - return False - - dataCount = 0 - if buildCache.exists(): - logger.debug("Loading build cache") - try: - with open(buildCache, mode="r", encoding="utf-8") as inFile: - theJson = inFile.read() - theData = json.loads(theJson) - except Exception: - logger.error("Failed to load build cache") - logException() - return False - - if "buildTime" in theData.keys(): - self.buildTime = theData["buildTime"] - if "htmlStyle" in theData.keys(): - self.htmlStyle = theData["htmlStyle"] - dataCount += 1 - if "htmlText" in theData.keys(): - self.htmlText = theData["htmlText"] - dataCount += 1 - - return dataCount == 2 - - def _saveCache(self): - """Save the current data to cache. - """ - buildCache = self.theProject.storage.getCacheFile(nwFiles.BUILD_CACHE) - if not isinstance(buildCache, Path): - return False - - logger.debug("Saving build cache") - try: - with open(buildCache, mode="w+", encoding="utf-8") as outFile: - outFile.write(json.dumps({ - "buildTime": self.buildTime, - "htmlStyle": self.htmlStyle, - "htmlText": self.htmlText, - }, indent=2)) - except Exception: - logger.error("Failed to save build cache") - logException() - return False - - return True - - def _doClose(self): - """Close button was clicked. - """ - self.close() - return - - ## - # Events - ## - - def closeEvent(self, theEvent): - """Capture the user closing the window so we can save settings. - """ - self._saveSettings() - self.docView.clear() - theEvent.accept() - return - - ## - # Internal Functions - ## - - def _saveSettings(self): - """Save the various user settings. - """ - logger.debug("Saving GuiBuildNovel settings") - - # Formatting - self.theProject.data.setTitleFormat({ - "title": self.fmtTitle.text().strip(), - "chapter": self.fmtChapter.text().strip(), - "unnumbered": self.fmtUnnumbered.text().strip(), - "scene": self.fmtScene.text().strip(), - "section": self.fmtSection.text().strip(), - }) - - buildLang = self.buildLang.currentData() - hideScene = self.hideScene.isChecked() - hideSection = self.hideSection.isChecked() - winWidth = CONFIG.rpxInt(self.width()) - winHeight = CONFIG.rpxInt(self.height()) - justifyText = self.justifyText.isChecked() - noStyling = self.noStyling.isChecked() - textFont = self.textFont.text() - textSize = self.textSize.value() - lineHeight = self.lineHeight.value() - novelFiles = self.novelFiles.isChecked() - noteFiles = self.noteFiles.isChecked() - ignoreFlag = self.ignoreFlag.isChecked() - incSynopsis = self.includeSynopsis.isChecked() - incComments = self.includeComments.isChecked() - incKeywords = self.includeKeywords.isChecked() - incBodyText = self.includeBody.isChecked() - replaceTabs = self.replaceTabs.isChecked() - replaceUCode = self.replaceUCode.isChecked() - rootFilter = self._generateRootFilter() - - mainSplit = self.mainSplit.sizes() - boxWidth = CONFIG.rpxInt(mainSplit[0]) - docWidth = CONFIG.rpxInt(mainSplit[1]) - - self.theProject.setProjectLang(buildLang) - - # GUI Settings - pOptions = self.theProject.options - pOptions.setValue("GuiBuildNovel", "hideScene", hideScene) - pOptions.setValue("GuiBuildNovel", "hideSection", hideSection) - pOptions.setValue("GuiBuildNovel", "winWidth", winWidth) - pOptions.setValue("GuiBuildNovel", "winHeight", winHeight) - pOptions.setValue("GuiBuildNovel", "boxWidth", boxWidth) - pOptions.setValue("GuiBuildNovel", "docWidth", docWidth) - pOptions.setValue("GuiBuildNovel", "justifyText", justifyText) - pOptions.setValue("GuiBuildNovel", "noStyling", noStyling) - pOptions.setValue("GuiBuildNovel", "textFont", textFont) - pOptions.setValue("GuiBuildNovel", "textSize", textSize) - pOptions.setValue("GuiBuildNovel", "lineHeight", lineHeight) - pOptions.setValue("GuiBuildNovel", "addNovel", novelFiles) - pOptions.setValue("GuiBuildNovel", "addNotes", noteFiles) - pOptions.setValue("GuiBuildNovel", "ignoreFlag", ignoreFlag) - pOptions.setValue("GuiBuildNovel", "incSynopsis", incSynopsis) - pOptions.setValue("GuiBuildNovel", "incComments", incComments) - pOptions.setValue("GuiBuildNovel", "incKeywords", incKeywords) - pOptions.setValue("GuiBuildNovel", "incBodyText", incBodyText) - pOptions.setValue("GuiBuildNovel", "replaceTabs", replaceTabs) - pOptions.setValue("GuiBuildNovel", "replaceUCode", replaceUCode) - pOptions.setValue("GuiBuildNovel", "rootFilter", rootFilter) - pOptions.saveSettings() - - return - - def _reFmtCodes(self, theFormat): - """Translates old formatting codes to new ones. - """ - theFormat = theFormat.replace(r"%chnum%", r"%ch%") - theFormat = theFormat.replace(r"%scnum%", r"%sc%") - theFormat = theFormat.replace(r"%scabsnum%", r"%sca%") - theFormat = theFormat.replace(r"%chnumword%", r"%chw%") - return theFormat - -# END Class GuiBuildNovel - - -class GuiBuildNovelDocView(QTextBrowser): - - def __init__(self, mainGui, theProject): - super().__init__(parent=mainGui) - - logger.debug("Initialising GuiBuildNovelDocView ...") - - self.theProject = theProject - self.mainGui = mainGui - self.mainTheme = mainGui.mainTheme - self.buildTime = 0 - - self.setMinimumWidth(40*self.mainGui.mainTheme.textNWidth) - self.setOpenExternalLinks(False) - - self.document().setDocumentMargin(CONFIG.getTextMargin()) - self.setPlaceholderText(self.tr( - "This area will show the content of the document to be " - "exported or printed. Press the \"Build Preview\" button " - "to generate content." - )) - - theFont = QFont() - theFont.setFamily(CONFIG.textFont) - theFont.setPointSize(CONFIG.textSize) - self.setFont(theFont) - - # Set the tab stops - self.setTabStopDistance(CONFIG.getTabWidth()) - - docPalette = self.palette() - docPalette.setColor(QPalette.Base, QColor(255, 255, 255)) - docPalette.setColor(QPalette.Text, QColor(0, 0, 0)) - self.setPalette(docPalette) - - lblPalette = self.palette() - lblPalette.setColor(QPalette.Background, lblPalette.toolTipBase().color()) - lblPalette.setColor(QPalette.Foreground, lblPalette.toolTipText().color()) - - lblFont = self.font() - lblFont.setPointSizeF(0.9*self.mainTheme.fontPointSize) - - fPx = int(1.1*self.mainTheme.fontPixelSize) - - self.theTitle = QLabel("", self) - self.theTitle.setIndent(0) - self.theTitle.setAutoFillBackground(True) - self.theTitle.setAlignment(Qt.AlignCenter) - self.theTitle.setFixedHeight(fPx) - self.theTitle.setPalette(lblPalette) - self.theTitle.setFont(lblFont) - - self._updateDocMargins() - self._updateBuildAge() - self.setStyleSheet() - - # Age Timer - self.ageTimer = QTimer() - self.ageTimer.setInterval(10000) - self.ageTimer.timeout.connect(self._updateBuildAge) - self.ageTimer.start() - - logger.debug("GuiBuildNovelDocView initialisation complete") - - return - - def setJustify(self, doJustify): - """Set the justify text option. - """ - theOpt = self.document().defaultTextOption() - if doJustify: - theOpt.setAlignment(Qt.AlignJustify) - else: - theOpt.setAlignment(Qt.AlignAbsolute) - self.document().setDefaultTextOption(theOpt) - return - - def setTextFont(self, textFont, textSize): - """Set the text font properties. - """ - theFont = QFont() - theFont.setFamily(textFont) - theFont.setPointSize(textSize) - self.setFont(theFont) - return - - def setContent(self, theText, timeStamp): - """Set the content, either from text or list of text. - """ - if isinstance(theText, list): - theText = "".join(theText) - - self.buildTime = timeStamp - sPos = self.verticalScrollBar().value() - qApp.setOverrideCursor(QCursor(Qt.WaitCursor)) - - theText = theText.replace("\t", "!!tab!!") - theText = theText.replace("", "") - theText = theText.replace("", "") - self.setHtml(theText) - qApp.processEvents() - - while self.find("!!tab!!"): - theCursor = self.textCursor() - theCursor.insertText("\t") - - self.verticalScrollBar().setValue(sPos) - self._updateBuildAge() - - # Since we change the content while it may still be rendering, we mark - # the document dirty again to make sure it's re-rendered properly. - self.document().markContentsDirty(0, self.document().characterCount()) - qApp.restoreOverrideCursor() - - return - - def setStyleSheet(self, theStyles=None): - """Set the stylesheet for the preview document. - """ - if theStyles is None: - theStyles = [] - - if not theStyles: - theStyles.append("h1, h2 {color: rgb(66, 113, 174);}") - theStyles.append("h3, h4 {color: rgb(50, 50, 50);}") - theStyles.append("a {color: rgb(66, 113, 174);}") - theStyles.append(".tags {color: rgb(245, 135, 31); font-weight: bold;}") - - self.document().setDefaultStyleSheet("\n".join(theStyles)) - - return - - def clearStyleSheet(self): - """Clears the document stylesheet. - """ - self.document().setDefaultStyleSheet("") - return - - ## - # Events - ## - - def resizeEvent(self, theEvent): - """Make sure the document title is the same width as the window. - """ - super().resizeEvent(theEvent) - self._updateDocMargins() - return - - ## - # Internal Functions - ## - - def _updateBuildAge(self): - """Update the build time and the fuzzy age. - """ - if self.buildTime > 0: - strBuildTime = "%s (%s)" % ( - datetime.fromtimestamp(self.buildTime).strftime("%x %X"), - fuzzyTime(time() - self.buildTime) - ) - else: - strBuildTime = self.tr("Unknown") - - self.theTitle.setText("%s %s" % (self.tr("Build Time:"), strBuildTime)) - - return - - def _updateDocMargins(self): - """Automatically adjust the header to fill the top of the - document within the viewport. - """ - vBar = self.verticalScrollBar() - if vBar.isVisible(): - sW = vBar.width() - else: - sW = 0 - - tB = self.frameWidth() - tW = self.width() - 2*tB - sW - tH = self.theTitle.height() - - self.theTitle.setGeometry(tB, tB, tW, tH) - self.setViewportMargins(0, tH, 0, 0) - - return - -# END Class GuiBuildNovelDocView diff --git a/tests/test_core/test_core_options.py b/tests/test_core/test_core_options.py index 1a9cf381..ce26dbe1 100644 --- a/tests/test_core/test_core_options.py +++ b/tests/test_core/test_core_options.py @@ -32,8 +32,7 @@ from novelwriter.gui.noveltree import NovelTreeColumn @pytest.mark.core def testCoreOptions_LoadSave(monkeypatch, mockGUI, fncPath): - """Test loading and saving from the OptionState class. - """ + """Test loading and saving from the OptionState class.""" theProject = NWProject(mockGUI) theOpts = OptionState(theProject) @@ -43,13 +42,12 @@ def testCoreOptions_LoadSave(monkeypatch, mockGUI, fncPath): # Write a test file optFile = metaDir / nwFiles.OPTS_FILE optFile.write_text(json.dumps({ - "GuiBuildNovel": { - "winWidth": 1000, - "winHeight": 700, - "addNovel": True, - "addNotes": False, - "textFont": "Cantarell", - "mockItem": None, + "GuiProjectSettings": { + "winWidth": 570, + "winHeight": 375, + "replaceColW": 130, + "statusColW": 130, + "importColW": 130 }, "MockGroup": { "mockItem": None, @@ -76,12 +74,12 @@ def testCoreOptions_LoadSave(monkeypatch, mockGUI, fncPath): # Check that unwanted items have been removed assert theOpts._theState == { - "GuiBuildNovel": { - "winWidth": 1000, - "winHeight": 700, - "addNovel": True, - "addNotes": False, - "textFont": "Cantarell", + "GuiProjectSettings": { + "winWidth": 570, + "winHeight": 375, + "replaceColW": 130, + "statusColW": 130, + "importColW": 130 }, } @@ -91,12 +89,12 @@ def testCoreOptions_LoadSave(monkeypatch, mockGUI, fncPath): # Load again to check we get the values back assert theOpts.loadSettings() assert theOpts._theState == { - "GuiBuildNovel": { - "winWidth": 1000, - "winHeight": 700, - "addNovel": True, - "addNotes": False, - "textFont": "Cantarell", + "GuiProjectSettings": { + "winWidth": 570, + "winHeight": 375, + "replaceColW": 130, + "statusColW": 130, + "importColW": 130 }, } @@ -105,8 +103,7 @@ def testCoreOptions_LoadSave(monkeypatch, mockGUI, fncPath): @pytest.mark.core def testCoreOptions_SetGet(mockGUI): - """Test setting and getting values from the OptionState class. - """ + """Test setting and getting values from the OptionState class.""" theProject = NWProject(mockGUI) theOpts = OptionState(theProject) @@ -114,36 +111,33 @@ def testCoreOptions_SetGet(mockGUI): # Set invalid values assert theOpts.setValue("MockGroup", "mockItem", None) is False - assert theOpts.setValue("GuiBuildNovel", "mockItem", None) is False + assert theOpts.setValue("GuiProjectSettings", "mockItem", None) is False # Set valid value - assert theOpts.setValue("GuiBuildNovel", "winWidth", 100) + assert theOpts.setValue("GuiProjectSettings", "winWidth", 100) is True # Set some values of different types - assert theOpts.setValue("GuiBuildNovel", "winWidth", 100) - assert theOpts.setValue("GuiBuildNovel", "winHeight", 12.34) - assert theOpts.setValue("GuiBuildNovel", "addNovel", True) - assert theOpts.setValue("GuiBuildNovel", "textFont", "Cantarell") - assert theOpts.setValue("GuiNovelView", "lastCol", nwColHidden) + assert theOpts.setValue("GuiProjectDetails", "winWidth", 100) is True + assert theOpts.setValue("GuiProjectDetails", "winHeight", 12.34) is True + assert theOpts.setValue("GuiProjectDetails", "clearDouble", True) is True + assert theOpts.setValue("GuiNovelView", "lastCol", nwColHidden) is True # Generic get, doesn't check type - assert theOpts.getValue("GuiBuildNovel", "winWidth", None) == 100 - assert theOpts.getValue("GuiBuildNovel", "winHeight", None) == 12.34 - assert theOpts.getValue("GuiBuildNovel", "addNovel", None) is True - assert theOpts.getValue("GuiBuildNovel", "textFont", None) == "Cantarell" - assert theOpts.getValue("GuiBuildNovel", "mockItem", None) is None + assert theOpts.getValue("GuiProjectDetails", "winWidth", None) == 100 + assert theOpts.getValue("GuiProjectDetails", "winHeight", None) == 12.34 + assert theOpts.getValue("GuiProjectDetails", "clearDouble", None) is True + assert theOpts.getValue("GuiProjectDetails", "mockItem", None) is None # Get type-specific - assert theOpts.getString("GuiBuildNovel", "winWidth", None) is None - assert theOpts.getString("GuiBuildNovel", "mockItem", None) is None - assert theOpts.getInt("GuiBuildNovel", "winWidth", None) == 100 - assert theOpts.getInt("GuiBuildNovel", "textFont", None) is None - assert theOpts.getInt("GuiBuildNovel", "mockItem", None) is None - assert theOpts.getFloat("GuiBuildNovel", "winWidth", None) == 100.0 - assert theOpts.getFloat("GuiBuildNovel", "textFont", None) is None - assert theOpts.getFloat("GuiBuildNovel", "mockItem", None) is None - assert theOpts.getBool("GuiBuildNovel", "addNovel", None) is True - assert theOpts.getBool("GuiBuildNovel", "mockItem", None) is None + assert theOpts.getString("GuiProjectDetails", "winWidth", None) is None + assert theOpts.getString("GuiProjectDetails", "mockItem", None) is None + assert theOpts.getInt("GuiProjectDetails", "winWidth", None) == 100 + assert theOpts.getInt("GuiProjectDetails", "textFont", None) is None + assert theOpts.getInt("GuiProjectDetails", "mockItem", None) is None + assert theOpts.getFloat("GuiProjectDetails", "winWidth", None) == 100.0 + assert theOpts.getFloat("GuiProjectDetails", "mockItem", None) is None + assert theOpts.getBool("GuiProjectDetails", "clearDouble", None) is True + assert theOpts.getBool("GuiProjectDetails", "mockItem", None) is None assert theOpts.getEnum("GuiNovelView", "lastCol", NovelTreeColumn, None) == nwColHidden # Get from non-existent groups diff --git a/tests/test_gui/test_gui_guimain.py b/tests/test_gui/test_gui_guimain.py index f5b9ecc6..7c49585d 100644 --- a/tests/test_gui/test_gui_guimain.py +++ b/tests/test_gui/test_gui_guimain.py @@ -63,7 +63,7 @@ def testGuiMain_ProjectBlocker(nwGUI): assert nwGUI.rebuildIndex() is False assert nwGUI.showProjectSettingsDialog() is False assert nwGUI.showProjectDetailsDialog() is False - assert nwGUI.showBuildProjectDialog() is False + assert nwGUI.showBuildManuscriptDialog() is False assert nwGUI.showProjectWordListDialog() is False assert nwGUI.showWritingStatsDialog() is False diff --git a/tests/test_tools/test_tools_build.py b/tests/test_tools/test_tools_build.py deleted file mode 100644 index 798c3dfa..00000000 --- a/tests/test_tools/test_tools_build.py +++ /dev/null @@ -1,262 +0,0 @@ -""" -novelWriter – Build Dialog Class Tester -======================================= - -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 . -""" - -import pytest - -from shutil import copyfile - -from tools import ODT_IGNORE, cmpFiles, getGuiItem - -from PyQt5.QtCore import Qt -from PyQt5.QtWidgets import QAction, QFileDialog - -from novelwriter import CONFIG -from novelwriter.tools.build import GuiBuildNovel - - -@pytest.mark.gui -@pytest.mark.skip -def testToolBuild_Main(qtbot, monkeypatch, nwGUI, prjLipsum, tstPaths): - """Test the build tool. - """ - # Block message box - monkeypatch.setattr(QFileDialog, "getSaveFileName", lambda a, b, c, **k: (c, None)) - - # Check that we cannot open when there is no project - nwGUI.mainMenu.aBuildProject.activate(QAction.Trigger) - assert getGuiItem("GuiBuildNovel") is None - - # Open a project - assert nwGUI.openProject(prjLipsum) - - # Open the tool - nwGUI.mainMenu.aBuildProject.activate(QAction.Trigger) - qtbot.waitUntil(lambda: getGuiItem("GuiBuildNovel") is not None, timeout=1000) - - nwBuild = getGuiItem("GuiBuildNovel") - assert isinstance(nwBuild, GuiBuildNovel) - - nwBuild.textFont.setText("DejaVu Sans") - nwBuild.textSize.setValue(11) - - # Test Save - # ========= - - # Invalid file format - assert not nwBuild._saveDocument(-1) - - # No path selected - with monkeypatch.context() as mp: - mp.setattr(QFileDialog, "getSaveFileName", lambda *a, **k: ("", "")) - assert not nwBuild._saveDocument(nwBuild.FMT_NWD) - - # Default Settings - CONFIG._lastPath = prjLipsum - qtbot.mouseClick(nwBuild.buildNovel, Qt.LeftButton) - - assert nwBuild._saveDocument(nwBuild.FMT_NWD) - projFile = prjLipsum / "Lorem Ipsum.nwd" - testFile = tstPaths.outDir / "guiBuild_Tool_Step1_Lorem_Ipsum.nwd" - compFile = tstPaths.refDir / "guiBuild_Tool_Step1_Lorem_Ipsum.nwd" - copyfile(projFile, testFile) - assert cmpFiles(testFile, compFile) - - assert nwBuild._saveDocument(nwBuild.FMT_HTM) - projFile = prjLipsum / "Lorem Ipsum.htm" - testFile = tstPaths.outDir / "guiBuild_Tool_Step1_Lorem_Ipsum.htm" - compFile = tstPaths.refDir / "guiBuild_Tool_Step1_Lorem_Ipsum.htm" - copyfile(projFile, testFile) - assert cmpFiles(testFile, compFile) - - assert nwBuild._saveDocument(nwBuild.FMT_MD) - projFile = prjLipsum / "Lorem Ipsum.md" - testFile = tstPaths.outDir / "guiBuild_Tool_Step1_Lorem_Ipsum.md" - compFile = tstPaths.refDir / "guiBuild_Tool_Step1_Lorem_Ipsum.md" - copyfile(projFile, testFile) - assert cmpFiles(testFile, compFile) - - assert nwBuild._saveDocument(nwBuild.FMT_GH) - projFile = prjLipsum / "Lorem Ipsum.md" - testFile = tstPaths.outDir / "guiBuild_Tool_Step1G_Lorem_Ipsum.md" - compFile = tstPaths.refDir / "guiBuild_Tool_Step1G_Lorem_Ipsum.md" - copyfile(projFile, testFile) - assert cmpFiles(testFile, compFile) - - assert nwBuild._saveDocument(nwBuild.FMT_FODT) - projFile = prjLipsum / "Lorem Ipsum.fodt" - testFile = tstPaths.outDir / "guiBuild_Tool_Step1_Lorem_Ipsum.fodt" - compFile = tstPaths.refDir / "guiBuild_Tool_Step1_Lorem_Ipsum.fodt" - copyfile(projFile, testFile) - assert cmpFiles(testFile, compFile, ignoreStart=ODT_IGNORE) - - # Change Title Formats and Flip Switches - nwBuild.fmtChapter.setText(r"Chapter %chw%: %title%") - nwBuild.fmtScene.setText(r"Scene %ch%.%sc%: %title%") - nwBuild.fmtSection.setText(r"%ch%.%sc%.1: %title%") - - qtbot.mouseClick(nwBuild.justifyText, Qt.LeftButton) - qtbot.mouseClick(nwBuild.includeSynopsis, Qt.LeftButton) - qtbot.mouseClick(nwBuild.includeComments, Qt.LeftButton) - qtbot.mouseClick(nwBuild.includeKeywords, Qt.LeftButton) - qtbot.mouseClick(nwBuild.replaceUCode, Qt.LeftButton) - - qtbot.mouseClick(nwBuild.noteFiles, Qt.LeftButton) - qtbot.mouseClick(nwBuild.ignoreFlag, Qt.LeftButton) - - qtbot.mouseClick(nwBuild.buildNovel, Qt.LeftButton) - - assert nwBuild._saveDocument(nwBuild.FMT_NWD) - projFile = prjLipsum / "Lorem Ipsum.nwd" - testFile = tstPaths.outDir / "guiBuild_Tool_Step2_Lorem_Ipsum.nwd" - compFile = tstPaths.refDir / "guiBuild_Tool_Step2_Lorem_Ipsum.nwd" - copyfile(projFile, testFile) - assert cmpFiles(testFile, compFile) - - assert nwBuild._saveDocument(nwBuild.FMT_HTM) - projFile = prjLipsum / "Lorem Ipsum.htm" - testFile = tstPaths.outDir / "guiBuild_Tool_Step2_Lorem_Ipsum.htm" - compFile = tstPaths.refDir / "guiBuild_Tool_Step2_Lorem_Ipsum.htm" - copyfile(projFile, testFile) - assert cmpFiles(testFile, compFile) - - assert nwBuild._saveDocument(nwBuild.FMT_MD) - projFile = prjLipsum / "Lorem Ipsum.md" - testFile = tstPaths.outDir / "guiBuild_Tool_Step2_Lorem_Ipsum.md" - compFile = tstPaths.refDir / "guiBuild_Tool_Step2_Lorem_Ipsum.md" - copyfile(projFile, testFile) - assert cmpFiles(testFile, compFile) - - assert nwBuild._saveDocument(nwBuild.FMT_FODT) - projFile = prjLipsum / "Lorem Ipsum.fodt" - testFile = tstPaths.outDir / "guiBuild_Tool_Step2_Lorem_Ipsum.fodt" - compFile = tstPaths.refDir / "guiBuild_Tool_Step2_Lorem_Ipsum.fodt" - copyfile(projFile, testFile) - assert cmpFiles(testFile, compFile, ignoreStart=ODT_IGNORE) - - # Replace Tabs with Spaces - qtbot.mouseClick(nwBuild.replaceTabs, Qt.LeftButton) - - qtbot.mouseClick(nwBuild.buildNovel, Qt.LeftButton) - - # Save files that can be compared - assert nwBuild._saveDocument(nwBuild.FMT_NWD) - projFile = prjLipsum / "Lorem Ipsum.nwd" - testFile = tstPaths.outDir / "guiBuild_Tool_Step3_Lorem_Ipsum.nwd" - compFile = tstPaths.refDir / "guiBuild_Tool_Step3_Lorem_Ipsum.nwd" - copyfile(projFile, testFile) - assert cmpFiles(testFile, compFile) - - assert nwBuild._saveDocument(nwBuild.FMT_HTM) - projFile = prjLipsum / "Lorem Ipsum.htm" - testFile = tstPaths.outDir / "guiBuild_Tool_Step3_Lorem_Ipsum.htm" - compFile = tstPaths.refDir / "guiBuild_Tool_Step3_Lorem_Ipsum.htm" - copyfile(projFile, testFile) - assert cmpFiles(testFile, compFile) - - assert nwBuild._saveDocument(nwBuild.FMT_MD) - projFile = prjLipsum / "Lorem Ipsum.md" - testFile = tstPaths.outDir / "guiBuild_Tool_Step3_Lorem_Ipsum.md" - compFile = tstPaths.refDir / "guiBuild_Tool_Step3_Lorem_Ipsum.md" - copyfile(projFile, testFile) - assert cmpFiles(testFile, compFile) - - assert nwBuild._saveDocument(nwBuild.FMT_FODT) - projFile = prjLipsum / "Lorem Ipsum.fodt" - testFile = tstPaths.outDir / "guiBuild_Tool_Step3_Lorem_Ipsum.fodt" - compFile = tstPaths.refDir / "guiBuild_Tool_Step3_Lorem_Ipsum.fodt" - copyfile(projFile, testFile) - assert cmpFiles(testFile, compFile, ignoreStart=ODT_IGNORE) - - # Putline Mode - nwBuild.fmtChapter.setText(r"Chapter %chw%: %title%") - nwBuild.fmtScene.setText(r"Scene %sca%: %title%") - nwBuild.fmtSection.setText(r"Section: %title%") - - qtbot.mouseClick(nwBuild.includeComments, Qt.LeftButton) - qtbot.mouseClick(nwBuild.noteFiles, Qt.LeftButton) - qtbot.mouseClick(nwBuild.ignoreFlag, Qt.LeftButton) - qtbot.mouseClick(nwBuild.includeBody, Qt.LeftButton) - - qtbot.mouseClick(nwBuild.buildNovel, Qt.LeftButton) - - # Save files that can be compared - assert nwBuild._saveDocument(nwBuild.FMT_NWD) - projFile = prjLipsum / "Lorem Ipsum.nwd" - testFile = tstPaths.outDir / "guiBuild_Tool_Step4_Lorem_Ipsum.nwd" - compFile = tstPaths.refDir / "guiBuild_Tool_Step4_Lorem_Ipsum.nwd" - copyfile(projFile, testFile) - assert cmpFiles(testFile, compFile) - - assert nwBuild._saveDocument(nwBuild.FMT_HTM) - projFile = prjLipsum / "Lorem Ipsum.htm" - testFile = tstPaths.outDir / "guiBuild_Tool_Step4_Lorem_Ipsum.htm" - compFile = tstPaths.refDir / "guiBuild_Tool_Step4_Lorem_Ipsum.htm" - copyfile(projFile, testFile) - assert cmpFiles(testFile, compFile) - - # Check the JSON files too at this stage - assert nwBuild._saveDocument(nwBuild.FMT_JSON_H) - projFile = prjLipsum / "Lorem Ipsum.json" - testFile = tstPaths.outDir / "guiBuild_Tool_Step4H_Lorem_Ipsum.json" - compFile = tstPaths.refDir / "guiBuild_Tool_Step4H_Lorem_Ipsum.json" - copyfile(projFile, testFile) - assert cmpFiles(testFile, compFile, [6]) - - assert nwBuild._saveDocument(nwBuild.FMT_JSON_M) - projFile = prjLipsum / "Lorem Ipsum.json" - testFile = tstPaths.outDir / "guiBuild_Tool_Step4M_Lorem_Ipsum.json" - compFile = tstPaths.refDir / "guiBuild_Tool_Step4M_Lorem_Ipsum.json" - copyfile(projFile, testFile) - assert cmpFiles(testFile, compFile, [6]) - - # Since odt and fodt is built by the same code, we don't check the - # output. but just that the different format can be written as well - assert nwBuild._saveDocument(nwBuild.FMT_ODT) - assert (prjLipsum / "Lorem Ipsum.odt").is_file() - - # Print to PDF - if not CONFIG.osDarwin: - assert nwBuild._saveDocument(nwBuild.FMT_PDF) - assert (prjLipsum / "Lorem Ipsum.pdf").is_file() - - # Close the build tool - htmlText = nwBuild.htmlText - htmlStyle = nwBuild.htmlStyle - buildTime = nwBuild.buildTime - nwBuild._doClose() - - # Re-open build dialog from cahce - nwGUI.mainMenu.aBuildProject.activate(QAction.Trigger) - qtbot.waitUntil(lambda: getGuiItem("GuiBuildNovel") is not None, timeout=1000) - - nwBuild = getGuiItem("GuiBuildNovel") - assert isinstance(nwBuild, GuiBuildNovel) - - assert nwBuild.viewCachedDoc() - assert nwBuild.htmlText == htmlText - assert nwBuild.htmlStyle == htmlStyle - assert nwBuild.buildTime == buildTime - - nwBuild._doClose() - - # qtbot.stop() - -# END Test testToolBuild_Main