# -*- coding: utf-8 -*-
"""novelWriter GUI Build Novel
novelWriter – GUI Build Novel
===============================
Class holding the build novel window
File History:
Created: 2020-05-09 [0.5]
This file is a part of novelWriter
Copyright 2020, 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 logging
import nw
from os import path
from PyQt5.QtCore import Qt, QByteArray
from PyQt5.QtGui import (
QTextOption, QPalette, QColor, QTextDocumentWriter
)
from PyQt5.QtWidgets import (
QDialog, QVBoxLayout, QHBoxLayout, QTextBrowser, QPushButton,
QLabel, QLineEdit, QGroupBox, QGridLayout, QComboBox, QProgressBar,
QMenu, QAction, QFileDialog
)
from nw.gui.additions import QSwitch
from nw.core import ToHtml
from nw.constants import nwConst, nwFiles, nwAlert, nwItemType
logger = logging.getLogger(__name__)
class GuiBuildNovel(QDialog):
FMT_ODT = 1
FMT_PDF = 2
FMT_HTM1 = 3
FMT_HTM2 = 4
FMT_MD = 4
FMT_TXT = 5
def __init__(self, theParent, theProject):
QDialog.__init__(self, theParent)
logger.debug("Initialising GuiBuildNovel ...")
self.mainConf = nw.CONFIG
self.theProject = theProject
self.theParent = theParent
self.theTheme = theParent.theTheme
self.optState = self.theProject.optState
self.htmlText = ""
self.setWindowTitle("Build Project")
self.setMinimumWidth(800)
self.setMinimumHeight(700)
self.resize(
self.optState.getInt("GuiBuildNovel", "winWidth", 800),
self.optState.getInt("GuiBuildNovel", "winHeight", 700)
)
self.outerBox = QVBoxLayout()
self.innerBox = QHBoxLayout()
self.toolsBox = QVBoxLayout()
self.docView = GuiBuildNovelDocView(self, self.theProject)
# Title Formats
# =============
self.titleGroup = QGroupBox("Novel Title Formats", self)
self.titleForm = QGridLayout(self)
self.titleGroup.setLayout(self.titleForm)
self.fmtTitle = QLineEdit()
self.fmtTitle.setMaxLength(200)
self.fmtTitle.setFixedWidth(200)
self.fmtTitle.setText(self.theProject.titleFormat["title"])
self.fmtChapter = QLineEdit()
self.fmtChapter.setMaxLength(200)
self.fmtChapter.setFixedWidth(200)
self.fmtChapter.setText(self.theProject.titleFormat["chapter"])
self.fmtUnnumbered = QLineEdit()
self.fmtUnnumbered.setMaxLength(200)
self.fmtUnnumbered.setFixedWidth(200)
self.fmtUnnumbered.setText(self.theProject.titleFormat["unnumbered"])
self.fmtScene = QLineEdit()
self.fmtScene.setMaxLength(200)
self.fmtScene.setFixedWidth(200)
self.fmtScene.setText(self.theProject.titleFormat["scene"])
self.fmtSection = QLineEdit()
self.fmtSection.setMaxLength(200)
self.fmtSection.setFixedWidth(200)
self.fmtSection.setText(self.theProject.titleFormat["section"])
self.titleForm.addWidget(QLabel("Title"), 0, 0)
self.titleForm.addWidget(self.fmtTitle, 0, 1)
self.titleForm.addWidget(QLabel("Chapter"), 1, 0)
self.titleForm.addWidget(self.fmtChapter, 1, 1)
self.titleForm.addWidget(QLabel("Unnumbered"), 2, 0)
self.titleForm.addWidget(self.fmtUnnumbered, 2, 1)
self.titleForm.addWidget(QLabel("Scene"), 3, 0)
self.titleForm.addWidget(self.fmtScene, 3, 1)
self.titleForm.addWidget(QLabel("Section"), 4, 0)
self.titleForm.addWidget(self.fmtSection, 4, 1)
self.titleForm.setColumnStretch(0, 1)
self.titleForm.setColumnStretch(1, 0)
# Build Settings
# ==============
self.buildGroup = QGroupBox("Build Overrides", self)
self.buildForm = QGridLayout(self)
self.buildGroup.setLayout(self.buildForm)
self.outlineMode = QSwitch()
self.outlineMode.setChecked(self.optState.getBool("GuiBuildNovel", "outlineMode", False))
self.buildForm.addWidget(QLabel("Novel Outline Mode"), 0, 0)
self.buildForm.addWidget(self.outlineMode, 0, 1)
self.buildForm.setColumnStretch(0, 1)
self.buildForm.setColumnStretch(1, 0)
# Include Switches
# ================
self.includeGroup = QGroupBox("Include Non-Text Elements", self)
self.includeForm = QGridLayout(self)
self.includeGroup.setLayout(self.includeForm)
self.includeSynopsis = QSwitch()
self.includeSynopsis.setChecked(self.theProject.titleFormat["withSynopsis"])
self.includeComments = QSwitch()
self.includeComments.setChecked(self.theProject.titleFormat["withComments"])
self.includeKeywords = QSwitch()
self.includeKeywords.setChecked(self.theProject.titleFormat["withKeywords"])
self.includeForm.addWidget(QLabel("Include Synopsis"), 0, 0)
self.includeForm.addWidget(self.includeSynopsis, 0, 1)
self.includeForm.addWidget(QLabel("Include Comments"), 1, 0)
self.includeForm.addWidget(self.includeComments, 1, 1)
self.includeForm.addWidget(QLabel("Include Keywords"), 2, 0)
self.includeForm.addWidget(self.includeKeywords, 2, 1)
self.includeForm.setColumnStretch(0, 1)
self.includeForm.setColumnStretch(1, 0)
# Additional Options
# ==================
self.addsGroup = QGroupBox("Additional Options", self)
self.addsForm = QGridLayout(self)
self.addsGroup.setLayout(self.addsForm)
self.novelFiles = QSwitch()
self.novelFiles.setChecked(self.optState.getBool("GuiBuildNovel", "addNovel", True))
self.noteFiles = QSwitch()
self.noteFiles.setChecked(self.optState.getBool("GuiBuildNovel", "addNotes", False))
self.ignoreFlag = QSwitch()
self.ignoreFlag.setChecked(self.optState.getBool("GuiBuildNovel", "ignoreFlag", False))
self.addsForm.addWidget(QLabel("Include Novel Files"), 0, 0)
self.addsForm.addWidget(self.novelFiles, 0, 1)
self.addsForm.addWidget(QLabel("Include Note Files"), 1, 0)
self.addsForm.addWidget(self.noteFiles, 1, 1)
self.addsForm.addWidget(QLabel("Ignore Export Flag"), 2, 0)
self.addsForm.addWidget(self.ignoreFlag, 2, 1)
self.addsForm.setColumnStretch(0, 1)
self.addsForm.setColumnStretch(1, 0)
# Build Button
# ============
self.buildProgress = QProgressBar()
self.genPreview = QPushButton("Generate Preview")
self.genPreview.clicked.connect(self._buildPreview)
# Action Buttons
# ==============
self.buttonForm = QGridLayout()
self.btnHelp = QPushButton("Help")
self.btnHelp.clicked.connect(self._showHelp)
self.btnPrint = QPushButton("Print")
self.btnPrint.clicked.connect(self._printDocument)
self.btnSave = QPushButton("Save As")
self.saveMenu = QMenu(self)
self.btnSave.setMenu(self.saveMenu)
self.saveODT = QAction("Open Document (.odt)")
self.saveODT.triggered.connect(lambda: self._saveDocument(self.FMT_ODT))
self.saveMenu.addAction(self.saveODT)
# self.savePDF = QAction("Portable Document (.pdf)")
# self.savePDF.triggered.connect(lambda: self._saveDocument(self.FMT_PDF))
# self.saveMenu.addAction(self.savePDF)
self.saveHTM1 = QAction("Qt Style HTML (.htm)")
self.saveHTM1.triggered.connect(lambda: self._saveDocument(self.FMT_HTM1))
self.saveMenu.addAction(self.saveHTM1)
self.saveHTM2 = QAction("Plain HTML (.htm)")
self.saveHTM2.triggered.connect(lambda: self._saveDocument(self.FMT_HTM2))
self.saveMenu.addAction(self.saveHTM2)
# self.saveMD = QAction("Markdown (.md)")
# self.saveMD.triggered.connect(lambda: self._saveDocument(self.FMT_MD))
# self.saveMenu.addAction(self.saveMD)
self.saveTXT = QAction("Plain Text (.txt)")
self.saveTXT.triggered.connect(lambda: self._saveDocument(self.FMT_TXT))
self.saveMenu.addAction(self.saveTXT)
self.btnClose = QPushButton("Close")
self.btnClose.clicked.connect(self._doClose)
self.buttonForm.addWidget(self.btnHelp, 0, 0)
self.buttonForm.addWidget(self.btnPrint, 0, 1)
self.buttonForm.addWidget(self.btnSave, 1, 0)
self.buttonForm.addWidget(self.btnClose, 1, 1)
# Assemble GUI
# ============
self.toolsBox.addWidget(self.titleGroup)
self.toolsBox.addWidget(self.buildGroup)
self.toolsBox.addWidget(self.includeGroup)
self.toolsBox.addWidget(self.addsGroup)
self.toolsBox.addStretch(1)
self.toolsBox.addWidget(self.buildProgress)
self.toolsBox.addWidget(self.genPreview)
self.toolsBox.addSpacing(8)
self.toolsBox.addLayout(self.buttonForm)
self.innerBox.addLayout(self.toolsBox)
self.innerBox.addWidget(self.docView)
self.outerBox.addLayout(self.innerBox)
self.setLayout(self.outerBox)
self.innerBox.setStretch(0, 0)
self.innerBox.setStretch(1, 1)
self.outlineMode.toggled.connect(self._toggelOutlineMode)
self._toggelOutlineMode(self.outlineMode.isChecked())
self.show()
logger.debug("GuiBuildNovel initialisation complete")
return
##
# Slots
##
def _buildPreview(self):
"""Build a preview of the project in the document viewer.
"""
makeHtml = ToHtml(self.theProject, self.theParent)
self.htmlText = ""
for tItem in self.theProject.projTree:
if tItem is not None and tItem.itemType == nwItemType.FILE:
makeHtml.setText(tItem.itemHandle)
makeHtml.doAutoReplace()
makeHtml.tokenizeText()
makeHtml.doHeaders()
makeHtml.doConvert()
makeHtml.doPostProcessing()
self.htmlText += makeHtml.getResult()
self.docView.setHtml(self.htmlText)
return
def _saveDocument(self, theFormat):
"""Save the document to various formats.
"""
# FMT_PDF
byteFmt = QByteArray()
fileExt = ""
textFmt = ""
outTool = ""
# Create the settings
if theFormat == self.FMT_ODT:
byteFmt.append("odf")
fileExt = "odf"
textFmt = "Open Document"
outTool = "Qt"
elif theFormat == self.FMT_HTM1:
byteFmt.append("html")
fileExt = "htm"
textFmt = "Qt Style HTML"
outTool = "Qt"
elif theFormat == self.FMT_HTM2:
fileExt = "htm"
textFmt = "Plain HTML"
outTool = "NW"
elif theFormat == self.FMT_MD:
byteFmt.append("markdown")
fileExt = "md"
textFmt = "Markdown"
outTool = "Qt"
elif theFormat == self.FMT_TXT:
byteFmt.append("plaintext")
fileExt = "txt"
textFmt = "Plain Text"
outTool = "Qt"
else:
return False
# Generate the file name
if fileExt:
cleanName = self.theProject.getFileSafeProjectName()
fileName = "%s.%s" % (cleanName, fileExt)
saveDir = self.mainConf.lastPath
savePath = path.join(saveDir, fileName)
if not path.isdir(saveDir):
saveDir = self.mainConf.homePath
dlgOpt = QFileDialog.Options()
dlgOpt |= QFileDialog.DontUseNativeDialog
saveTo = QFileDialog.getSaveFileName(
self, "Save Document As", savePath, options=dlgOpt
)
if saveTo[0]:
savePath = saveTo[0]
else:
return False
self.mainConf.setLastPath(savePath)
else:
return False
# Do the actual writing
if outTool == "Qt":
docWriter = QTextDocumentWriter()
docWriter.setFileName(savePath)
docWriter.setFormat(byteFmt)
if docWriter.write(self.docView.qDocument):
self.theParent.makeAlert(
"Document successfully written in %s format to file: %s" % (
textFmt, savePath
), nwAlert.INFO
)
else:
self.theParent.makeAlert(
"Failed to write document in %s format to file: %s" % (
textFmt, savePath
), nwAlert.ERROR
)
elif outTool == "NW":
if theFormat == self.FMT_HTM2:
try:
with open(savePath, mode="w", encoding="utf8") as outFile:
outFile.write("\n")
outFile.write("\n")
outFile.write("
\n")
outFile.write("\n")
outFile.write("\n")
outFile.write("\n")
outFile.write(self.htmlText)
outFile.write("\n")
outFile.write("\n")
self.theParent.makeAlert(
"Document successfully written in %s format to file: %s" % (
textFmt, savePath
), nwAlert.INFO
)
except Exception as e:
self.theParent.makeAlert(
"Failed to write document in %s format to file: %s" % (
textFmt, str(e)
), nwAlert.ERROR
)
else:
return False
return True
def _printDocument(self):
return
def _toggelOutlineMode(self, theState):
"""Enables or disables the options that are overridden in#
outline mode.
"""
self.fmtTitle.setEnabled(not theState)
self.fmtChapter.setEnabled(not theState)
self.fmtUnnumbered.setEnabled(not theState)
self.fmtScene.setEnabled(not theState)
self.fmtSection.setEnabled(not theState)
self.includeSynopsis.setEnabled(not theState)
self.novelFiles.setEnabled(not theState)
self.noteFiles.setEnabled(not theState)
return
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()
QDialog.closeEvent(self, theEvent)
return
##
# Internal Functions
##
def _saveSettings(self):
"""Save the various user settings.
"""
logger.debug("Saving GuiBuildNovel settings")
# Formatting
self.theProject.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(),
"withSynopsis" : self.includeSynopsis.isChecked(),
"withComments" : self.includeComments.isChecked(),
"withKeywords" : self.includeKeywords.isChecked(),
})
# GUI Settings
self.optState.setValue("GuiBuildNovel", "winWidth", self.width())
self.optState.setValue("GuiBuildNovel", "winHeight", self.height())
self.optState.setValue("GuiBuildNovel", "outlineMode", self.outlineMode.isChecked())
self.optState.setValue("GuiBuildNovel", "addNovel", self.novelFiles.isChecked())
self.optState.setValue("GuiBuildNovel", "addNotes", self.noteFiles.isChecked())
self.optState.setValue("GuiBuildNovel", "ignoreFlag", self.ignoreFlag.isChecked())
self.optState.saveSettings()
return
def _showHelp(self):
"""Generate a help text and show it in the document window.
"""
docName = "exportHelp_%s.htm" % self.mainConf.guiLang
docPath = path.join(self.mainConf.assetPath, "text", docName)
if path.isfile(docPath):
with open(docPath, mode="r", encoding="utf8") as inFile:
helpText = inFile.read()
self.docView.setText(helpText)
else:
self.theParent.makeAlert(
"Could not open help text file for Build Project.", nwAlert.ERROR
)
return
# END Class GuiBuildNovel
class GuiBuildNovelDocView(QTextBrowser):
def __init__(self, theParent, theProject):
QTextBrowser.__init__(self, theParent)
logger.debug("Initialising GuiBuildNovelDocView ...")
self.mainConf = nw.CONFIG
self.theProject = theProject
self.theParent = theParent
self.setMinimumWidth(400)
self.setOpenExternalLinks(False)
self.qDocument = self.document()
self.qDocument.setDocumentMargin(self.mainConf.textMargin)
theOpt = QTextOption()
if self.mainConf.doJustify:
theOpt.setAlignment(Qt.AlignJustify)
self.qDocument.setDefaultTextOption(theOpt)
docPalette = self.palette()
docPalette.setColor(QPalette.Base, QColor(255, 255, 255))
docPalette.setColor(QPalette.Text, QColor( 0, 0, 0))
self.setPalette(docPalette)
self._makeStyleSheet()
self.show()
logger.debug("GuiBuildNovelDocView initialisation complete")
return
def setText(self, theText):
self.setHtml(theText)
return
##
# Internal Functions
##
def _makeStyleSheet(self):
styleSheet = (
"h1, h2 {"
" color: rgb(66, 113, 174);"
"}\n"
"h3, h4 {"
" color: rgb(50, 50, 50);"
"}\n"
"a {"
" color: rgb(137, 89, 168);"
"}\n"
"mark {"
" background-color: rgb(240, 198, 116);"
"}\n"
)
self.qDocument.setDefaultStyleSheet(styleSheet)
return
# END Class GuiBuildNovelDocView