From 4d9e2d03a37e81c27967479705aba8cb64201636 Mon Sep 17 00:00:00 2001 From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com> Date: Sun, 10 May 2020 19:31:53 +0200 Subject: [PATCH] Done building the buttons etc for BuildNovel dialog --- nw/assets/text/exportHelp_en.htm | 44 ++++ nw/config.py | 1 + nw/core/project.py | 23 ++- nw/core/tokenizer.py | 5 + nw/gui/build.py | 337 ++++++++++++++++++++++++++++++- sample/sampleNovel/nwProject.nwx | 13 +- 6 files changed, 400 insertions(+), 23 deletions(-) create mode 100644 nw/assets/text/exportHelp_en.htm diff --git a/nw/assets/text/exportHelp_en.htm b/nw/assets/text/exportHelp_en.htm new file mode 100644 index 00000000..df8eca2a --- /dev/null +++ b/nw/assets/text/exportHelp_en.htm @@ -0,0 +1,44 @@ +

Help!

+

A brief guide to make the most out of the Build Project tool.

+ +

Novel Title Formats

+

The format of the various title levels in the files under the Novel folder can be customised in + these settings. The actual title given in the headings of your files will replace all + occurrences of the keyword %title%. Any static text will be left as-is in the + final title. An empty field means the title isn't written out at all.

+

The available formatting keywords are:

+

%title% – This is replaced with the text you put in your headings in your + documents

+

%num% – This is replaced with the chapter number of your chapter type + headings. These are generated automaticall starting from 1.

+

%numword% – This is replaced with the chapter number of your chapter type + headings, but instead of an arabic number, the word for it is used instead, e.g. One, Two, + Fifteen, Twenty-Five, etc.

+

\\ – Two backslashes are replaced by a line break.

+

Note: The Scene format is treated slightly differently than the other title formats. If a + scene format is a constant text, that is, contains no %title%, it will be treated + as a scene separator instead. Scene separators are centred, and not shown if the chapter starts + directly on the first scene. If the field is blank, a large space between the scenes is added + instead.

+ +

Build Overrides

+

Novel Outline Mode: This option will build an outline version of the novel rather than the + full thing. It overrides the title format settings without changing them. Each title will be + written out, and the synopsis text will appear instead of the body text of the files. Some of + the other options are still available in Outline Mode.

+ +

Include Non-Text Elements

+

Include Synopsis: This will add the synopsis comment as the first paragraph after each + heading.

+

Include Comments: This will include any comments as additional paragraphs in the text.

+

Include Keywords: This will include any keywords and tags as clickable links after each + heading.

+ +

Additional Options

+

Include Novel Files: This means all files that don't have a layout of type "Note" will be + included. This is the normal mode when exporting the novel itself without the notes.

+

Include Note Files: This means all files with a layout of type "Note" will be + included. Titles in note files are always left as they appear.

+

Ignore Export Flag: Each file in the project tree has an "Include when building project" + option set, which is indicated by a little check mark in the "Flags" column. Files without This + tick will normally be skipped during build, but can be included if this option is enabled.

diff --git a/nw/config.py b/nw/config.py index f0f21f32..e031cb79 100644 --- a/nw/config.py +++ b/nw/config.py @@ -84,6 +84,7 @@ class Config: self.guiTheme = "default" self.guiSyntax = "default_light" self.guiDark = False + self.guiLang = "en" # Hardcoded for now ## Sizes self.winGeometry = [1100, 650] diff --git a/nw/core/project.py b/nw/core/project.py index 8986aeb3..c964c75b 100644 --- a/nw/core/project.py +++ b/nw/core/project.py @@ -200,13 +200,14 @@ class NWProject(): self.bookAuthors = [] self.autoReplace = {} self.titleFormat = { - "title": "%title%", - "chapter": "Chapter %num%", - "chapterSub": "%title%", - "unnumbered": "%title%", - "scene": "", - "sceneSep": "* * *", - "section": "", + "title" : r"%title%", + "chapter" : r"Chapter %num%\\%title%", + "unnumbered" : r"%title%", + "scene" : r"* * *", + "section" : r"", + "withSynopsis" : False, + "withComments" : False, + "withKeywords" : False, } self.spellCheck = False self.autoOutline = True @@ -717,9 +718,11 @@ class NWProject(): def setTitleFormat(self, titleFormat): """Set the formatting of titles in the project. """ - for valKey in titleFormat: - if valKey in self.titleFormat: - self.titleFormat[valKey] = titleFormat[valKey] + for valKey, valEntry in titleFormat.items(): + if valKey in ("title","chapter","unnumbered","scene","section"): + self.titleFormat[valKey] = checkString(valEntry, self.titleFormat[valKey], False) + elif valKey in ("withSynopsis","withComments","withKeywords"): + self.titleFormat[valKey] = checkBool(valEntry, False, False) return def setProjectChanged(self, bValue): diff --git a/nw/core/tokenizer.py b/nw/core/tokenizer.py index f9b0264c..1cf2a40e 100644 --- a/nw/core/tokenizer.py +++ b/nw/core/tokenizer.py @@ -165,6 +165,11 @@ class Tokenizer(): return + def getResult(self): + """Return the result from the conversion. + """ + return self.theResult + def doAutoReplace(self): """Run through the user's auto-replace dictionary. """ diff --git a/nw/gui/build.py b/nw/gui/build.py index 82525d6f..401bf58e 100644 --- a/nw/gui/build.py +++ b/nw/gui/build.py @@ -28,18 +28,29 @@ import logging import nw +from os import path + from PyQt5.QtCore import Qt from PyQt5.QtGui import QTextOption, QPalette, QColor from PyQt5.QtWidgets import ( - QDialog, QVBoxLayout, QHBoxLayout, QTextBrowser + QDialog, QVBoxLayout, QHBoxLayout, QTextBrowser, QPushButton, + QLabel, QLineEdit, QGroupBox, QGridLayout, QComboBox, QProgressBar, + QMenu, QAction ) -from nw.constants import nwConst, nwFiles, nwAlert +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_HTM = 3 + FMT_MD = 4 + def __init__(self, theParent, theProject): QDialog.__init__(self, theParent) @@ -48,28 +59,310 @@ class GuiBuildNovel(QDialog): self.mainConf = nw.CONFIG self.theProject = theProject self.theParent = theParent + self.theTheme = theParent.theTheme self.optState = self.theProject.optState self.setWindowTitle("Build Project") - self.setMinimumWidth(400) - self.setMinimumHeight(400) + 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.saveODT = QAction("Open Document (.odt)") + self.savePDF = QAction("Portable Document (.pdf)") + self.saveHTM = QAction("HTML5 (.htm)") + self.saveMD = QAction("Markdown (.md)") + self.saveODT.triggered.connect(lambda: self._saveDocument(self.FMT_ODT)) + self.savePDF.triggered.connect(lambda: self._saveDocument(self.FMT_PDF)) + self.saveHTM.triggered.connect(lambda: self._saveDocument(self.FMT_HTM)) + self.saveMD.triggered.connect(lambda: self._saveDocument(self.FMT_MD)) + self.saveMenu.addAction(self.saveODT) + self.saveMenu.addAction(self.savePDF) + self.saveMenu.addAction(self.saveHTM) + self.saveMenu.addAction(self.saveMD) + self.btnSave.setMenu(self.saveMenu) + + 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) + theText = "" + + 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() + theText += makeHtml.getResult() + + self.docView.setHtml(theText) + + return + + def _saveDocument(self, theFormat): + return + + 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): @@ -83,10 +376,12 @@ class GuiBuildNovelDocView(QTextBrowser): self.theProject = theProject self.theParent = theParent - self.qDocument = self.document() - self.setMinimumWidth(300) + 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) @@ -97,7 +392,7 @@ class GuiBuildNovelDocView(QTextBrowser): docPalette.setColor(QPalette.Text, QColor( 0, 0, 0)) self.setPalette(docPalette) - self.setHtml("

Hello World

Some text ...

") + self._makeStyleSheet() self.show() @@ -105,4 +400,32 @@ class GuiBuildNovelDocView(QTextBrowser): 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 diff --git a/sample/sampleNovel/nwProject.nwx b/sample/sampleNovel/nwProject.nwx index c911b0a3..05445b4b 100644 --- a/sample/sampleNovel/nwProject.nwx +++ b/sample/sampleNovel/nwProject.nwx @@ -1,5 +1,5 @@ - + Sample Project Sample Project @@ -20,12 +20,13 @@ %title% - Chapter %num% - %title% + Chapter %num%\\%title% %title% - None - * * * -
None
+ * * * +
+ True + False + False
New