From dbd1964ff44773e3b858a9d724054140bd648933 Mon Sep 17 00:00:00 2001 From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com> Date: Sun, 13 Oct 2019 12:18:17 +0200 Subject: [PATCH 01/14] Added export dialog class --- nw/gui/exports.py | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 nw/gui/exports.py diff --git a/nw/gui/exports.py b/nw/gui/exports.py new file mode 100644 index 00000000..1e9babc8 --- /dev/null +++ b/nw/gui/exports.py @@ -0,0 +1,24 @@ +# -*- coding: utf-8 -*- +"""novelWriter GUI Export Tools + + novelWriter – GUI Export Tool +================================ + Tool for exporting project files to other formats + + File History: + Created: 2019-10-13 [0.2.3] + +""" + +import logging +import nw + +from os import path + +from PyQt5.QtWidgets import QDialog + +logger = logging.getLogger(__name__) + +class GuiExport(QDialog): + +# END Class GuiExport From 32a4e434593842689f2103694c0cec35f7c22fc4 Mon Sep 17 00:00:00 2001 From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com> Date: Sun, 13 Oct 2019 13:31:59 +0200 Subject: [PATCH 02/14] Export dialog working --- nw/gui/configeditor.py | 2 +- nw/gui/export.py | 80 ++++++++++++++++++++++++++++++++++++++++++ nw/gui/exports.py | 24 ------------- nw/gui/mainmenu.py | 7 ++++ nw/gui/winmain.py | 7 ++++ 5 files changed, 95 insertions(+), 25 deletions(-) create mode 100644 nw/gui/export.py delete mode 100644 nw/gui/exports.py diff --git a/nw/gui/configeditor.py b/nw/gui/configeditor.py index 69376a78..016040c6 100644 --- a/nw/gui/configeditor.py +++ b/nw/gui/configeditor.py @@ -68,7 +68,7 @@ class GuiConfigEditor(QDialog): self.show() - logger.debug("ProjectEditor ConfigEditor complete") + logger.debug("ConfigEditor initialisation complete") return diff --git a/nw/gui/export.py b/nw/gui/export.py new file mode 100644 index 00000000..86b249ab --- /dev/null +++ b/nw/gui/export.py @@ -0,0 +1,80 @@ +# -*- coding: utf-8 -*- +"""novelWriter GUI Export Tools + + novelWriter – GUI Export Tool +================================ + Tool for exporting project files to other formats + + File History: + Created: 2019-10-13 [0.2.3] + +""" + +import logging +import nw + +from os import path + +from PyQt5.QtCore import Qt, QSize +from PyQt5.QtSvg import QSvgWidget +from PyQt5.QtWidgets import ( + QDialog, QHBoxLayout, QVBoxLayout, QWidget, QTabWidget, QDialogButtonBox +) + +logger = logging.getLogger(__name__) + +class GuiExport(QDialog): + + def __init__(self, theParent, theProject): + QDialog.__init__(self, theParent) + + logger.debug("Initialising GuiExport ...") + + self.mainConf = nw.CONFIG + self.theParent = theParent + self.theProject = theProject + + self.outerBox = QHBoxLayout() + self.innerBox = QVBoxLayout() + self.setWindowTitle("Export Project") + self.setLayout(self.outerBox) + + self.gradPath = path.abspath(path.join(self.mainConf.appPath,"graphics","gear.svg")) + self.svgGradient = QSvgWidget(self.gradPath) + self.svgGradient.setFixedSize(QSize(64,64)) + + self.theProject.countStatus() + self.tabMain = GuiExportMain(self.theParent, self.theProject) + + self.tabWidget = QTabWidget() + self.tabWidget.addTab(self.tabMain, "Settings") + + self.outerBox.addWidget(self.svgGradient, 0, Qt.AlignTop) + self.outerBox.addLayout(self.innerBox) + + self.buttonBox = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel) + # self.buttonBox.accepted.connect(self._doSave) + # self.buttonBox.rejected.connect(self._doClose) + + self.innerBox.addWidget(self.tabWidget) + self.innerBox.addWidget(self.buttonBox) + + self.show() + + logger.debug("GuiExport initialisation complete") + + return + +# END Class GuiExport + +class GuiExportMain(QWidget): + + def __init__(self, theParent, theProject): + QWidget.__init__(self, theParent) + + self.theParent = theParent + self.theProject = theProject + + return + +# END Class GuiExportMain diff --git a/nw/gui/exports.py b/nw/gui/exports.py deleted file mode 100644 index 1e9babc8..00000000 --- a/nw/gui/exports.py +++ /dev/null @@ -1,24 +0,0 @@ -# -*- coding: utf-8 -*- -"""novelWriter GUI Export Tools - - novelWriter – GUI Export Tool -================================ - Tool for exporting project files to other formats - - File History: - Created: 2019-10-13 [0.2.3] - -""" - -import logging -import nw - -from os import path - -from PyQt5.QtWidgets import QDialog - -logger = logging.getLogger(__name__) - -class GuiExport(QDialog): - -# END Class GuiExport diff --git a/nw/gui/mainmenu.py b/nw/gui/mainmenu.py index c319dc20..a5d77820 100644 --- a/nw/gui/mainmenu.py +++ b/nw/gui/mainmenu.py @@ -192,6 +192,13 @@ class GuiMainMenu(QMenuBar): menuItem.triggered.connect(self.theParent.editProjectDialog) self.projMenu.addAction(menuItem) + # Project > Export Project + menuItem = QAction("Export Project", self) + menuItem.setStatusTip("Export project") + menuItem.setShortcut("F5") + menuItem.triggered.connect(self.theParent.exportProjectDialog) + self.projMenu.addAction(menuItem) + # Project > Separator self.projMenu.addSeparator() diff --git a/nw/gui/winmain.py b/nw/gui/winmain.py index 8dee5c41..7d3d0aa8 100644 --- a/nw/gui/winmain.py +++ b/nw/gui/winmain.py @@ -30,6 +30,7 @@ from nw.gui.searchbar import GuiSearchBar from nw.gui.mainmenu import GuiMainMenu from nw.gui.configeditor import GuiConfigEditor from nw.gui.projecteditor import GuiProjectEditor +from nw.gui.export import GuiExport from nw.gui.itemeditor import GuiItemEditor from nw.gui.statusbar import GuiMainStatus from nw.gui.timelineview import GuiTimeLineView @@ -511,6 +512,12 @@ class GuiMain(QMainWindow): self._setWindowTitle(self.theProject.projName) return True + def exportProjectDialog(self): + if self.hasProject: + dlgExport = GuiExport(self, self.theProject) + dlgExport.exec_() + return True + def showTimeLineDialog(self): if self.hasProject: dlgTLine = GuiTimeLineView(self, self.theProject, self.theIndex) From e3fadea203f7fcf46f3c371e969e270cad5ec4a3 Mon Sep 17 00:00:00 2001 From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com> Date: Sun, 13 Oct 2019 15:32:29 +0200 Subject: [PATCH 03/14] Added function to translate numbers to words --- nw/tools/translate.py | 93 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 93 insertions(+) create mode 100644 nw/tools/translate.py diff --git a/nw/tools/translate.py b/nw/tools/translate.py new file mode 100644 index 00000000..33e4885f --- /dev/null +++ b/nw/tools/translate.py @@ -0,0 +1,93 @@ +# -*- coding: utf-8 -*- +"""novelWriter Translate Tools + + novelWriter – Translate Tools +=============================== + Various translate tools + + File History: + Created: 2019-10-13 [0.2.3] + +""" + +import logging +import nw + +from os import path + +logger = logging.getLogger(__name__) + +def numberToWord(numVal, theLanguage): + numWord = "" + if theLanguage == "EN": + numWord = _numberToWordEN(numVal) + else: + numWord = _numberToWordEN(numVal) + # print("%4d : %s" % (numVal, numWord)) + return numWord + +def _numberToWordEN(numVal): + + numWord = "" + oneWord = "" + tenWord = "" + hunWord = "" + + if numVal == 0: + return "Zero" + + oneVal = numVal % 10 + tenVal = (numVal-oneVal) % 100 + hunVal = (numVal-tenVal-oneVal) % 1000 + + if hunVal == 100: hunWord = "One Hundred" + if hunVal == 200: hunWord = "Two Hundred" + if hunVal == 300: hunWord = "Three Hundred" + if hunVal == 400: hunWord = "Four Hundred" + if hunVal == 500: hunWord = "Five Hundred" + if hunVal == 600: hunWord = "Six Hundred" + if hunVal == 700: hunWord = "Seven Hundred" + if hunVal == 800: hunWord = "Eight Hundred" + if hunVal == 900: hunWord = "Nine Hundred" + + if tenVal == 20: tenWord = "Twenty" + if tenVal == 30: tenWord = "Thirty" + if tenVal == 40: tenWord = "Forty" + if tenVal == 50: tenWord = "Fifty" + if tenVal == 60: tenWord = "Sixty" + if tenVal == 70: tenWord = "Seventy" + if tenVal == 80: tenWord = "Eighty" + if tenVal == 90: tenWord = "Ninety" + + if tenVal == 10: + if oneVal == 0: oneWord = "Ten" + if oneVal == 1: oneWord = "Eleven" + if oneVal == 2: oneWord = "Twelve" + if oneVal == 3: oneWord = "Thirteen" + if oneVal == 4: oneWord = "Fourteen" + if oneVal == 5: oneWord = "Fifteen" + if oneVal == 6: oneWord = "Sixteen" + if oneVal == 7: oneWord = "Seventeen" + if oneVal == 8: oneWord = "Eighteen" + if oneVal == 9: oneWord = "Nineteen" + numWord = ("%s %s" % (hunWord, oneWord)).strip() + else: + if oneVal == 0: oneWord = "" + if oneVal == 1: oneWord = "One" + if oneVal == 2: oneWord = "Two" + if oneVal == 3: oneWord = "Three" + if oneVal == 4: oneWord = "Four" + if oneVal == 5: oneWord = "Five" + if oneVal == 6: oneWord = "Six" + if oneVal == 7: oneWord = "Seven" + if oneVal == 8: oneWord = "Eight" + if oneVal == 9: oneWord = "Nine" + if tenVal == 0: + numWord = ("%s %s" % (hunWord, oneWord)).strip() + else: + if oneVal == 0: + numWord = ("%s %s" % (hunWord, tenWord)).strip() + else: + numWord = ("%s %s-%s" % (hunWord, tenWord, oneWord)).strip() + + return numWord From bc06bde05c0932fc9d0090ca4a16a2525c98920d Mon Sep 17 00:00:00 2001 From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com> Date: Sun, 13 Oct 2019 15:36:57 +0200 Subject: [PATCH 04/14] Added more options to export GUI --- nw/gui/configeditor.py | 8 ++--- nw/gui/export.py | 69 +++++++++++++++++++++++++++++++++++++++--- 2 files changed, 69 insertions(+), 8 deletions(-) diff --git a/nw/gui/configeditor.py b/nw/gui/configeditor.py index 016040c6..3a7406ff 100644 --- a/nw/gui/configeditor.py +++ b/nw/gui/configeditor.py @@ -111,10 +111,10 @@ class GuiConfigEditGeneral(QWidget): def __init__(self, theParent): QWidget.__init__(self, theParent) - self.mainConf = nw.CONFIG - self.theParent = theParent - self.theTheme = theParent.theTheme - self.outerBox = QGridLayout() + self.mainConf = nw.CONFIG + self.theParent = theParent + self.theTheme = theParent.theTheme + self.outerBox = QGridLayout() # User Interface self.guiLook = QGroupBox("User Interface", self) diff --git a/nw/gui/export.py b/nw/gui/export.py index 86b249ab..41a7e122 100644 --- a/nw/gui/export.py +++ b/nw/gui/export.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- """novelWriter GUI Export Tools - novelWriter – GUI Export Tool + novelWriter – GUI Export Tools ================================ Tool for exporting project files to other formats @@ -18,9 +18,12 @@ from os import path from PyQt5.QtCore import Qt, QSize from PyQt5.QtSvg import QSvgWidget from PyQt5.QtWidgets import ( - QDialog, QHBoxLayout, QVBoxLayout, QWidget, QTabWidget, QDialogButtonBox + QDialog, QHBoxLayout, QVBoxLayout, QWidget, QTabWidget, QDialogButtonBox, QGridLayout, + QGroupBox, QCheckBox, QLabel, QComboBox, QLineEdit ) +from nw.tools.translate import numberToWord + logger = logging.getLogger(__name__) class GuiExport(QDialog): @@ -61,6 +64,9 @@ class GuiExport(QDialog): self.show() + for n in range(1000): + numberToWord(n,"EN") + logger.debug("GuiExport initialisation complete") return @@ -69,11 +75,66 @@ class GuiExport(QDialog): class GuiExportMain(QWidget): + CHFMT_NUM = 1 + CHFMT_NUMWORD = 2 + CHFMT_TITLE = 3 + CHFMT_LABEL = 4 + CHFMT_NUMTITLE = 5 + CHFMT_NUMLABEL = 6 + CHFMT_CUSTOM = 7 + def __init__(self, theParent, theProject): QWidget.__init__(self, theParent) - self.theParent = theParent - self.theProject = theProject + self.theParent = theParent + self.theProject = theProject + self.outerBox = QGridLayout() + + # Select Files + self.guiFiles = QGroupBox("Export Files", self) + self.guiFilesForm = QGridLayout(self) + self.guiFiles.setLayout(self.guiFilesForm) + + self.expNovel = QCheckBox(self) + self.expNovel.setToolTip("Include all novel files in the exported document") + self.expNotes = QCheckBox(self) + self.expNotes.setToolTip("Include all note files in the exported document") + + self.guiFilesForm.addWidget(QLabel("Novel files"), 0, 0) + self.guiFilesForm.addWidget(self.expNovel, 0, 1) + self.guiFilesForm.addWidget(QLabel("Note files"), 1, 0) + self.guiFilesForm.addWidget(self.expNotes, 1, 1) + + # Chapter Settings + self.guiChapters = QGroupBox("Chapters", self) + self.guiChaptersForm = QGridLayout(self) + self.guiChapters.setLayout(self.guiChaptersForm) + + self.chapterFormat = QComboBox(self) + self.chapterFormat.addItem("Chapter 1", self.CHFMT_NUM) + self.chapterFormat.addItem("Chapter One", self.CHFMT_NUMWORD) + self.chapterFormat.addItem("[Title]", self.CHFMT_TITLE) + self.chapterFormat.addItem("[Label]", self.CHFMT_LABEL) + self.chapterFormat.addItem("1. [Title]", self.CHFMT_NUMTITLE) + self.chapterFormat.addItem("1. [Label]", self.CHFMT_NUMLABEL) + self.chapterFormat.addItem("Custom", self.CHFMT_CUSTOM) + + self.chapterCustom = QLineEdit() + self.chapterCustom.setText("%num%. %title%") + self.chapterCustom.setToolTip("Available options: %num%, %numword%, %title%, %label%") + self.chapterCustom.setMinimumWidth(200) + + self.guiChaptersForm.addWidget(QLabel("Name format"), 0, 0) + self.guiChaptersForm.addWidget(self.chapterFormat, 0, 1) + self.guiChaptersForm.addWidget(QLabel("Custom format"), 1, 0) + self.guiChaptersForm.addWidget(self.chapterCustom, 1, 1) + + # Assemble + self.outerBox.addWidget(self.guiFiles, 0, 0) + self.outerBox.addWidget(self.guiChapters, 1, 0) + self.outerBox.setColumnStretch(2, 1) + self.outerBox.setRowStretch(4, 1) + self.setLayout(self.outerBox) return From 1d9c4e1e4911cea1f20b8c8b96db2861dcf173fb Mon Sep 17 00:00:00 2001 From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com> Date: Tue, 15 Oct 2019 19:05:13 +0200 Subject: [PATCH 05/14] Added more elements to the export GUI --- nw/gui/configeditor.py | 4 ++ nw/gui/export.py | 114 +++++++++++++++++++++++++++++------------ 2 files changed, 86 insertions(+), 32 deletions(-) diff --git a/nw/gui/configeditor.py b/nw/gui/configeditor.py index 3a7406ff..08b6cefd 100644 --- a/nw/gui/configeditor.py +++ b/nw/gui/configeditor.py @@ -72,6 +72,10 @@ class GuiConfigEditor(QDialog): return + ## + # Buttons + ## + def _doSave(self): logger.verbose("ConfigEditor save button clicked") diff --git a/nw/gui/export.py b/nw/gui/export.py index 41a7e122..871e98ee 100644 --- a/nw/gui/export.py +++ b/nw/gui/export.py @@ -19,7 +19,7 @@ from PyQt5.QtCore import Qt, QSize from PyQt5.QtSvg import QSvgWidget from PyQt5.QtWidgets import ( QDialog, QHBoxLayout, QVBoxLayout, QWidget, QTabWidget, QDialogButtonBox, QGridLayout, - QGroupBox, QCheckBox, QLabel, QComboBox, QLineEdit + QGroupBox, QCheckBox, QLabel, QComboBox, QLineEdit, QPushButton ) from nw.tools.translate import numberToWord @@ -55,39 +55,49 @@ class GuiExport(QDialog): self.outerBox.addWidget(self.svgGradient, 0, Qt.AlignTop) self.outerBox.addLayout(self.innerBox) - self.buttonBox = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel) - # self.buttonBox.accepted.connect(self._doSave) - # self.buttonBox.rejected.connect(self._doClose) + self.buttonBox = QDialogButtonBox(QDialogButtonBox.Save | QDialogButtonBox.Cancel) + self.buttonBox.accepted.connect(self._doSave) + self.buttonBox.rejected.connect(self._doClose) self.innerBox.addWidget(self.tabWidget) self.innerBox.addWidget(self.buttonBox) self.show() - for n in range(1000): - numberToWord(n,"EN") - logger.debug("GuiExport initialisation complete") return + ## + # Buttons + ## + + def _doSave(self): + logger.verbose("GuiExport save button clicked") + self.close() + return + + def _doClose(self): + logger.verbose("GuiExport close button clicked") + self.close() + return + # END Class GuiExport class GuiExportMain(QWidget): - CHFMT_NUM = 1 - CHFMT_NUMWORD = 2 - CHFMT_TITLE = 3 - CHFMT_LABEL = 4 - CHFMT_NUMTITLE = 5 - CHFMT_NUMLABEL = 6 - CHFMT_CUSTOM = 7 + FMT_MD = 1 + FMT_HTML = 2 + FMT_EBOOK = 3 + FMT_FODT = 4 + FMT_PDF = 5 def __init__(self, theParent, theProject): QWidget.__init__(self, theParent) self.theParent = theParent self.theProject = theProject + self.theTheme = theParent.theTheme self.outerBox = QGridLayout() # Select Files @@ -110,30 +120,70 @@ class GuiExportMain(QWidget): self.guiChaptersForm = QGridLayout(self) self.guiChapters.setLayout(self.guiChaptersForm) - self.chapterFormat = QComboBox(self) - self.chapterFormat.addItem("Chapter 1", self.CHFMT_NUM) - self.chapterFormat.addItem("Chapter One", self.CHFMT_NUMWORD) - self.chapterFormat.addItem("[Title]", self.CHFMT_TITLE) - self.chapterFormat.addItem("[Label]", self.CHFMT_LABEL) - self.chapterFormat.addItem("1. [Title]", self.CHFMT_NUMTITLE) - self.chapterFormat.addItem("1. [Label]", self.CHFMT_NUMLABEL) - self.chapterFormat.addItem("Custom", self.CHFMT_CUSTOM) + self.chapterFormat = QLineEdit() + self.chapterFormat.setText("Chapter %numword%") + self.chapterFormat.setToolTip("Available formats: %num%, %numword%, %title%, %label%") + self.chapterFormat.setMinimumWidth(250) - self.chapterCustom = QLineEdit() - self.chapterCustom.setText("%num%. %title%") - self.chapterCustom.setToolTip("Available options: %num%, %numword%, %title%, %label%") - self.chapterCustom.setMinimumWidth(200) + self.guiChaptersForm.addWidget(QLabel("Format"), 0, 0) + self.guiChaptersForm.addWidget(self.chapterFormat, 0, 1) - self.guiChaptersForm.addWidget(QLabel("Name format"), 0, 0) - self.guiChaptersForm.addWidget(self.chapterFormat, 0, 1) - self.guiChaptersForm.addWidget(QLabel("Custom format"), 1, 0) - self.guiChaptersForm.addWidget(self.chapterCustom, 1, 1) + # Output Format + self.guiOutput = QGroupBox("Output", self) + self.guiOutputForm = QGridLayout(self) + self.guiOutput.setLayout(self.guiOutputForm) + + self.outputFormat = QComboBox(self) + self.outputFormat.addItem("Markdown", self.FMT_MD) + self.outputFormat.addItem("HTML (Plain)", self.FMT_HTML) + self.outputFormat.addItem("HTML (eBook)", self.FMT_EBOOK) + self.outputFormat.addItem("Open Document", self.FMT_FODT) + self.outputFormat.addItem("PDF (PDFLaTeX)", self.FMT_PDF) + + self.outputComments = QCheckBox("include comments", self) + + self.guiOutputForm.addWidget(QLabel("Export format"), 0, 0) + self.guiOutputForm.addWidget(self.outputFormat, 0, 1) + self.guiOutputForm.addWidget(self.outputComments, 0, 2) + self.guiOutputForm.setColumnStretch(2, 1) + + # Scene Settings + self.guiScenes = QGroupBox("Scenes", self) + self.guiScenesForm = QGridLayout(self) + self.guiScenes.setLayout(self.guiScenesForm) + + self.sceneFormat = QLineEdit() + self.sceneFormat.setText("* * *") + self.sceneFormat.setToolTip("Available formats: %title%") + self.sceneFormat.setMinimumWidth(100) + + self.guiScenesForm.addWidget(QLabel("Format"), 0, 0) + self.guiScenesForm.addWidget(self.sceneFormat, 0, 1) + + # Output Path + self.exportTo = QGroupBox("Backup", self) + self.exportToForm = QGridLayout(self) + self.exportTo.setLayout(self.exportToForm) + + self.exportPath = QLineEdit() + + self.exportGetPath = QPushButton(self.theTheme.getIcon("folder"),"") + # self.exportGetPath.clicked.connect(self._backupFolder) + + self.exportToForm.addWidget(QLabel("Save to"), 0, 0) + self.exportToForm.addWidget(self.exportPath, 0, 1) + self.exportToForm.addWidget(self.exportGetPath, 0, 2) # Assemble self.outerBox.addWidget(self.guiFiles, 0, 0) - self.outerBox.addWidget(self.guiChapters, 1, 0) + self.outerBox.addWidget(self.guiOutput, 0, 1, 1, 2) + self.outerBox.addWidget(self.guiChapters, 1, 0, 1, 2) + self.outerBox.addWidget(self.guiScenes, 1, 2) + self.outerBox.addWidget(self.exportTo, 2, 0, 1, 3) + self.outerBox.setColumnStretch(0, 1) + self.outerBox.setColumnStretch(1, 1) self.outerBox.setColumnStretch(2, 1) - self.outerBox.setRowStretch(4, 1) + # self.outerBox.setRowStretch(4, 1) self.setLayout(self.outerBox) return From a5ad2335de304ccd944bdfee2dddadfbcd4f2716 Mon Sep 17 00:00:00 2001 From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com> Date: Fri, 18 Oct 2019 19:02:28 +0200 Subject: [PATCH 06/14] Export GUI ready --- nw/constants.py | 1 + nw/gui/configeditor.py | 1 + nw/gui/docdetails.py | 2 +- nw/gui/export.py | 219 +++++++++++++++++++++++++++++++++++++---- 4 files changed, 204 insertions(+), 19 deletions(-) diff --git a/nw/constants.py b/nw/constants.py index 9c56b012..a5244d46 100644 --- a/nw/constants.py +++ b/nw/constants.py @@ -19,6 +19,7 @@ class nwFiles(): PROJ_DICT = "wordlist.txt" SESS_INFO = "sessionInfo.log" INDEX_FILE = "tagsIndex.json" + EXPORT_OPT = "exportOptions.json" # END Class nwFiles diff --git a/nw/gui/configeditor.py b/nw/gui/configeditor.py index 08b6cefd..5010e500 100644 --- a/nw/gui/configeditor.py +++ b/nw/gui/configeditor.py @@ -282,6 +282,7 @@ class GuiConfigEditGeneral(QWidget): if newDir: self.projBackupPath.setText(newDir) return True + return False # END Class GuiConfigEditGeneral diff --git a/nw/gui/docdetails.py b/nw/gui/docdetails.py index 804b43bc..888e295e 100644 --- a/nw/gui/docdetails.py +++ b/nw/gui/docdetails.py @@ -54,7 +54,7 @@ class GuiDocDetails(QFrame): QLabel("") ] - colOne = ["Name","Status","Class","Layout"] + colOne = ["Label","Status","Class","Layout"] for nRow in range(4): lblOne = QLabel(colOne[nRow]) lblOne.setFont(self.fntOne) diff --git a/nw/gui/export.py b/nw/gui/export.py index 871e98ee..995666f3 100644 --- a/nw/gui/export.py +++ b/nw/gui/export.py @@ -11,6 +11,7 @@ """ import logging +import json import nw from os import path @@ -19,10 +20,12 @@ from PyQt5.QtCore import Qt, QSize from PyQt5.QtSvg import QSvgWidget from PyQt5.QtWidgets import ( QDialog, QHBoxLayout, QVBoxLayout, QWidget, QTabWidget, QDialogButtonBox, QGridLayout, - QGroupBox, QCheckBox, QLabel, QComboBox, QLineEdit, QPushButton + QGroupBox, QCheckBox, QLabel, QComboBox, QLineEdit, QPushButton, QFileDialog ) from nw.tools.translate import numberToWord +from nw.common import checkString, checkBool, checkInt +from nw.constants import nwFiles logger = logging.getLogger(__name__) @@ -36,6 +39,7 @@ class GuiExport(QDialog): self.mainConf = nw.CONFIG self.theParent = theParent self.theProject = theProject + self.optState = ExportLastState(self.theProject) self.outerBox = QHBoxLayout() self.innerBox = QVBoxLayout() @@ -47,7 +51,7 @@ class GuiExport(QDialog): self.svgGradient.setFixedSize(QSize(64,64)) self.theProject.countStatus() - self.tabMain = GuiExportMain(self.theParent, self.theProject) + self.tabMain = GuiExportMain(self.theParent, self.theProject, self.optState) self.tabWidget = QTabWidget() self.tabWidget.addTab(self.tabMain, "Settings") @@ -73,8 +77,30 @@ class GuiExport(QDialog): ## def _doSave(self): + logger.verbose("GuiExport save button clicked") + + wNovel = self.tabMain.expNovel.isChecked() + wNotes = self.tabMain.expNotes.isChecked() + wTOC = self.tabMain.expTOC.isChecked() + eFormat = self.tabMain.outputFormat.currentData() + wComments = self.tabMain.outputComments.isChecked() + chFormat = self.tabMain.chapterFormat.text() + scFormat = self.tabMain.sceneFormat.text() + saveTo = self.tabMain.exportPath.text() + + self.optState.setSetting("wNovel", wNovel) + self.optState.setSetting("wNotes", wNotes) + self.optState.setSetting("wTOC", wTOC) + self.optState.setSetting("eFormat", eFormat) + self.optState.setSetting("wComments",wComments) + self.optState.setSetting("chFormat", chFormat) + self.optState.setSetting("scFormat", scFormat) + self.optState.setSetting("saveTo", saveTo) + + self.optState.saveSettings() self.close() + return def _doClose(self): @@ -89,16 +115,39 @@ class GuiExportMain(QWidget): FMT_MD = 1 FMT_HTML = 2 FMT_EBOOK = 3 - FMT_FODT = 4 - FMT_PDF = 5 + FMT_ODT = 4 + FMT_TEX = 5 + FMT_HELP = { + FMT_MD : ( + "Exports a standard markdown file. " + "Comments are converted to preformatted text blocks." + ), + FMT_HTML : ( + "Exports a plain html5 file. " + "Comments are converted to preformatted text blocks." + ), + FMT_EBOOK : ( + "Exports an html5 file that can be converted to eBook with Calibre. " + "Comments are not exported in this format." + ), + FMT_ODT : ( + "Exports an open document file that can be read by office applications. " + "Comments are exported as grey text." + ), + FMT_TEX : ( + "Exports a LaTeX file that can be compiled to PDF using for instance PDFLaTeX. " + "Comments are exported as LaTeX comments." + ), + } - def __init__(self, theParent, theProject): + def __init__(self, theParent, theProject, optState): QWidget.__init__(self, theParent) self.theParent = theParent self.theProject = theProject self.theTheme = theParent.theTheme self.outerBox = QGridLayout() + self.optState = optState # Select Files self.guiFiles = QGroupBox("Export Files", self) @@ -106,14 +155,21 @@ class GuiExportMain(QWidget): self.guiFiles.setLayout(self.guiFilesForm) self.expNovel = QCheckBox(self) - self.expNovel.setToolTip("Include all novel files in the exported document") self.expNotes = QCheckBox(self) + self.expTOC = QCheckBox(self) + self.expNovel.setToolTip("Include all novel files in the exported document") self.expNotes.setToolTip("Include all note files in the exported document") + self.expTOC.setToolTip("Generate a Table of Contents (ToC)") + self.expNovel.setChecked(self.optState.wNovel()) + self.expNotes.setChecked(self.optState.wNotes()) + self.expTOC.setChecked(self.optState.wTOC()) self.guiFilesForm.addWidget(QLabel("Novel files"), 0, 0) self.guiFilesForm.addWidget(self.expNovel, 0, 1) self.guiFilesForm.addWidget(QLabel("Note files"), 1, 0) self.guiFilesForm.addWidget(self.expNotes, 1, 1) + self.guiFilesForm.addWidget(QLabel("Contents"), 2, 0) + self.guiFilesForm.addWidget(self.expTOC, 2, 1) # Chapter Settings self.guiChapters = QGroupBox("Chapters", self) @@ -121,7 +177,7 @@ class GuiExportMain(QWidget): self.guiChapters.setLayout(self.guiChaptersForm) self.chapterFormat = QLineEdit() - self.chapterFormat.setText("Chapter %numword%") + self.chapterFormat.setText(self.optState.chFormat()) self.chapterFormat.setToolTip("Available formats: %num%, %numword%, %title%, %label%") self.chapterFormat.setMinimumWidth(250) @@ -133,18 +189,31 @@ class GuiExportMain(QWidget): self.guiOutputForm = QGridLayout(self) self.guiOutput.setLayout(self.guiOutputForm) - self.outputFormat = QComboBox(self) - self.outputFormat.addItem("Markdown", self.FMT_MD) - self.outputFormat.addItem("HTML (Plain)", self.FMT_HTML) - self.outputFormat.addItem("HTML (eBook)", self.FMT_EBOOK) - self.outputFormat.addItem("Open Document", self.FMT_FODT) - self.outputFormat.addItem("PDF (PDFLaTeX)", self.FMT_PDF) - self.outputComments = QCheckBox("include comments", self) + self.outputComments.setChecked(self.optState.wComments()) + + self.outputHelp = QLabel("") + self.outputHelp.setWordWrap(True) + self.outputHelp.setMinimumHeight(55) + self.outputHelp.setAlignment(Qt.AlignTop) + + self.outputFormat = QComboBox(self) + self.outputFormat.addItem("Markdown", self.FMT_MD) + self.outputFormat.addItem("HTML5 (Plain)", self.FMT_HTML) + self.outputFormat.addItem("HTML5 (eBook)", self.FMT_EBOOK) + self.outputFormat.addItem("Open Document", self.FMT_ODT) + self.outputFormat.addItem("LaTeX (PDF)", self.FMT_TEX) + self.outputFormat.currentIndexChanged.connect(self._updateFormatHelp) + + optIdx = self.outputFormat.findData(self.optState.eFormat()) + if optIdx != -1: + self.outputFormat.setCurrentIndex(optIdx) + self._updateFormatHelp(optIdx) self.guiOutputForm.addWidget(QLabel("Export format"), 0, 0) self.guiOutputForm.addWidget(self.outputFormat, 0, 1) self.guiOutputForm.addWidget(self.outputComments, 0, 2) + self.guiOutputForm.addWidget(self.outputHelp, 1, 0, 1, 3) self.guiOutputForm.setColumnStretch(2, 1) # Scene Settings @@ -153,7 +222,7 @@ class GuiExportMain(QWidget): self.guiScenes.setLayout(self.guiScenesForm) self.sceneFormat = QLineEdit() - self.sceneFormat.setText("* * *") + self.sceneFormat.setText(self.optState.scFormat()) self.sceneFormat.setToolTip("Available formats: %title%") self.sceneFormat.setMinimumWidth(100) @@ -165,10 +234,10 @@ class GuiExportMain(QWidget): self.exportToForm = QGridLayout(self) self.exportTo.setLayout(self.exportToForm) - self.exportPath = QLineEdit() + self.exportPath = QLineEdit(self.optState.saveTo()) self.exportGetPath = QPushButton(self.theTheme.getIcon("folder"),"") - # self.exportGetPath.clicked.connect(self._backupFolder) + self.exportGetPath.clicked.connect(self._exportFolder) self.exportToForm.addWidget(QLabel("Save to"), 0, 0) self.exportToForm.addWidget(self.exportPath, 0, 1) @@ -183,9 +252,123 @@ class GuiExportMain(QWidget): self.outerBox.setColumnStretch(0, 1) self.outerBox.setColumnStretch(1, 1) self.outerBox.setColumnStretch(2, 1) - # self.outerBox.setRowStretch(4, 1) self.setLayout(self.outerBox) return + ## + # Internal Functions + ## + + def _updateFormatHelp(self, currIdx): + """Update help text under output format selection. + """ + if currIdx == -1: + self.outputHelp.setText("") + else: + fmtIdx = self.outputFormat.itemData(currIdx) + self.outputHelp.setText("%s" % self.FMT_HELP[fmtIdx]) + + return + + def _exportFolder(self): + + currDir = self.exportPath.text() + if not path.isdir(currDir): + currDir = "" + + dlgOpt = QFileDialog.Options() + dlgOpt |= QFileDialog.ShowDirsOnly + dlgOpt |= QFileDialog.DontUseNativeDialog + newDir = QFileDialog.getExistingDirectory( + self,"Export Directory",currDir,options=dlgOpt + ) + if newDir: + self.exportPath.setText(newDir) + return True + + return False + # END Class GuiExportMain + +class ExportLastState(): + + def __init__(self, theProject): + + self.theProject = theProject + self.theState = { + "wNovel" : True, + "wNotes" : False, + "wTOC" : True, + "eFormat" : 1, + "wComments" : False, + "chFormat" : "Chapter %numword%", + "scFormat" : "* * *", + "saveTo" : "", + } + self.loadSettings() + + return + + def loadSettings(self): + + stateFile = path.join(self.theProject.projMeta, nwFiles.EXPORT_OPT) + if path.isfile(stateFile): + logger.debug("Loading export options file") + try: + with open(stateFile,mode="r") as inFile: + theJson = inFile.read() + self.theState = json.loads(theJson) + except Exception as e: + logger.error("Failed to load export options file") + logger.error(str(e)) + return False + + return True + + def saveSettings(self): + + stateFile = path.join(self.theProject.projMeta, nwFiles.EXPORT_OPT) + logger.debug("Saving export options file") + try: + with open(stateFile,mode="w+") as outFile: + outFile.write(json.dumps(self.theState, indent=2)) + except Exception as e: + logger.error("Failed to save export options file") + logger.error(str(e)) + return False + + return True + + def setSetting(self, setName, setValue): + if setName in self.theState: + self.theState[setName] = setValue + else: + return False + return True + + def wNovel(self): + return checkBool(self.theState["wNovel"],False,False) + + def wNotes(self): + return checkBool(self.theState["wNotes"],False,False) + + def wTOC(self): + return checkBool(self.theState["wTOC"],False,False) + + def eFormat(self): + return checkInt(self.theState["eFormat"],1,False) + + def wComments(self): + return checkBool(self.theState["wComments"],False,False) + + def chFormat(self): + return checkString(self.theState["chFormat"],"Chapter %numword%",False) + + def scFormat(self): + return checkString(self.theState["scFormat"],"* * *",False) + + def saveTo(self): + return checkString(self.theState["saveTo"],"",False) + +# END Class ExportLastState From f8878535ba9aae698293aeb644554400752def32 Mon Sep 17 00:00:00 2001 From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com> Date: Fri, 18 Oct 2019 22:09:01 +0200 Subject: [PATCH 07/14] Basic export to plain text now works, but many options not connected yet --- nw/convert/textfile.py | 126 ++++++++++++++++++++++++++++++++++++++++ nw/convert/tokenizer.py | 77 ++++++++++++++++++++++++ nw/gui/export.py | 120 +++++++++++++++++++++++++++++--------- 3 files changed, 296 insertions(+), 27 deletions(-) create mode 100644 nw/convert/textfile.py diff --git a/nw/convert/textfile.py b/nw/convert/textfile.py new file mode 100644 index 00000000..04cd4b3a --- /dev/null +++ b/nw/convert/textfile.py @@ -0,0 +1,126 @@ +# -*- coding: utf-8 -*- +"""novelWriter Text File + + novelWriter – Text File +========================= + Writes the project to a plain text file + + File History: + Created: 2019-10-18 [0.2.3] + +""" + +import logging +import nw + +from os import path +from PyQt5.QtWidgets import QMessageBox + +from nw.convert.tokenizer import Tokenizer +from nw.enum import nwAlert + +logger = logging.getLogger(__name__) + +class TextFile(): + + def __init__(self, theProject, theParent): + + self.mainConf = nw.CONFIG + self.theProject = theProject + self.theParent = theParent + self.fileExt = "txt" + + self.outFile = None + self.fileName = "" + self.theText = "" + self.doComments = False + self.doMeta = False + self.wordWrap = 80 + self.winEnding = False + + self.makeAlert = self.theParent.makeAlert + + return + + ## + # Setters + ## + + def setComments(self, doComments): + self.doComments = doComments + return + + def setMeta(self, doMeta): + self.doMeta = doMeta + return + + ## + # Core Methods + ## + + def openFile(self, saveTo, baseName): + + fileName = "%s.%s" % (baseName.strip(),self.fileExt) + filePath = path.join(saveTo,fileName) + + self.fileName = fileName + + if path.isfile(filePath)and self.mainConf.showGUI: + msgBox = QMessageBox() + msgRes = msgBox.question( + self.theParent, "Overwrite", ("File '%s' already exists.
Do you want to overwrite it?" % fileName) + ) + if msgRes != QMessageBox.Yes: + return False + + self._doOpenFile(filePath) + + return True + + def closeFile(self): + self._doCloseFile() + return True + + def addText(self, tHandle): + + logger.verbose("Parsing content of item '%s'" % tHandle) + + aDoc = Tokenizer(self.theProject, self.theParent) + aDoc.setText(tHandle) + aDoc.doAutoReplace() + aDoc.tokenizeText() + + aDoc.setComments(self.doComments) + aDoc.setCommands(self.doMeta) + aDoc.setWordWrap(self.wordWrap) + + aDoc.doConvert() + + theText = "" + if aDoc.theResult is not None: + theText = aDoc.theResult + + if self.winEnding: + theText = theText.replace("\n","\r\n") + + self.outFile.write(theText) + + return True + + ## + # Internal Functions + ## + + def _doOpenFile(self, filePath): + try: + self.outFile = open(filePath,mode="w+") + except Exception as e: + self.makeAlert(["Failed to open file.",str(e)], nwAlert.ERROR) + return False + return True + + def _doCloseFile(self): + self.outFile.close() + return True + +# END Class OutFile diff --git a/nw/convert/tokenizer.py b/nw/convert/tokenizer.py index 0d62175f..bbb7877b 100644 --- a/nw/convert/tokenizer.py +++ b/nw/convert/tokenizer.py @@ -10,6 +10,7 @@ """ +import textwrap import logging import re import nw @@ -41,6 +42,25 @@ class Tokenizer(): self.theTokens = None self.theResult = None + self.wordWrap = 80 + self.doComments = False + self.doCommands = False + + return + + def setComments(self, doComments): + self.doComments = doComments + return + + def setCommands(self, doCommands): + self.doCommands = doCommands + return + + def setWordWrap(self, wordWrap): + if wordWrap >= 0: + self.wordWrap = wordWrap + else: + self.wordWrap = 0 return def setText(self, theHandle, theText=None): @@ -129,4 +149,61 @@ class Tokenizer(): return + def doConvert(self): + """Converts the tokenized text into plain text. + """ + + self.theResult = "" + thisPar = [] + for tType, tText, tFormat in self.theTokens: + + # First check if we have a comment or plain text, as they need some + # extra replacing before we proceed + if tType == "comment": + tText = "[%s]" % tText + + elif tType == "text": + tTemp = tText + for xPos, xLen, xFmt in reversed(tFormat): + tTemp = tTemp[:xPos]+tTemp[xPos+xLen:] + tText = tTemp + + # The text can now be word wrapped, if we have requested this + if self.wordWrap > 0: + tText = textwrap.fill(tText, width=self.wordWrap) + + # Then the text can receive final formatting before we append it + # to the results. We store text bits in a buffer and merge them only + # when we find an empty line, indicating a new paragraph + if tType == "empty": + if len(thisPar) > 0: + self.theResult += "%s\n\n" % " ".join(thisPar) + thisPar = [] + + elif tType == "header1": + uLine = "="*min(len(tText),self.wordWrap) + self.theResult += "%s\n%s\n\n" % (tText,uLine) + + elif tType == "header2": + uLine = "~"*min(len(tText),self.wordWrap) + self.theResult += "%s\n%s\n\n" % (tText,uLine) + + elif tType == "header3": + uLine = "-"*min(len(tText),self.wordWrap) + self.theResult += "%s\n%s\n\n" % (tText,uLine) + + elif tType == "header4": + self.theResult += "%s\n\n" % tText + + elif tType == "text": + thisPar.append(tText) + + elif tType == "comment" and self.doComments: + self.theResult += "%s\n\n" % tText + + elif tType == "command" and self.doCommands: + self.theResult += "%s\n\n" % tText + + return + # END Class Tokenizer diff --git a/nw/gui/export.py b/nw/gui/export.py index 995666f3..0c78211d 100644 --- a/nw/gui/export.py +++ b/nw/gui/export.py @@ -11,6 +11,7 @@ """ import logging +import time import json import nw @@ -19,13 +20,16 @@ from os import path from PyQt5.QtCore import Qt, QSize from PyQt5.QtSvg import QSvgWidget from PyQt5.QtWidgets import ( - QDialog, QHBoxLayout, QVBoxLayout, QWidget, QTabWidget, QDialogButtonBox, QGridLayout, - QGroupBox, QCheckBox, QLabel, QComboBox, QLineEdit, QPushButton, QFileDialog + QDialog, QHBoxLayout, QVBoxLayout, QWidget, QTabWidget, QGridLayout, QGroupBox, QCheckBox, + QLabel, QComboBox, QLineEdit, QPushButton, QFileDialog, QProgressBar ) -from nw.tools.translate import numberToWord -from nw.common import checkString, checkBool, checkInt -from nw.constants import nwFiles +from nw.project.document import NWDoc +from nw.tools.translate import numberToWord +from nw.convert.textfile import TextFile +from nw.common import checkString, checkBool, checkInt +from nw.constants import nwFiles +from nw.enum import nwItemType logger = logging.getLogger(__name__) @@ -59,13 +63,27 @@ class GuiExport(QDialog): self.outerBox.addWidget(self.svgGradient, 0, Qt.AlignTop) self.outerBox.addLayout(self.innerBox) - self.buttonBox = QDialogButtonBox(QDialogButtonBox.Save | QDialogButtonBox.Cancel) - self.buttonBox.accepted.connect(self._doSave) - self.buttonBox.rejected.connect(self._doClose) + self.doExportForm = QGridLayout() + self.doExportForm.setContentsMargins(10,5,0,10) + + self.exportButton = QPushButton("Export") + self.exportButton.clicked.connect(self._doExport) + + self.closeButton = QPushButton("Close") + self.closeButton.clicked.connect(self._doClose) + + self.exportStatus = QLabel("Ready ...") + self.exportProgress = QProgressBar(self) + + self.doExportForm.addWidget(self.exportStatus, 0, 0, 1, 3) + self.doExportForm.addWidget(self.exportProgress, 1, 0) + self.doExportForm.addWidget(self.exportButton, 1, 1) + self.doExportForm.addWidget(self.closeButton, 1, 2) self.innerBox.addWidget(self.tabWidget) - self.innerBox.addWidget(self.buttonBox) + self.innerBox.addLayout(self.doExportForm) + self.rejected.connect(self._doClose) self.show() logger.debug("GuiExport initialisation complete") @@ -76,9 +94,56 @@ class GuiExport(QDialog): # Buttons ## - def _doSave(self): + def _doExport(self): - logger.verbose("GuiExport save button clicked") + logger.verbose("GuiExport export button clicked") + + eFormat = self.tabMain.outputFormat.currentData() + wComments = self.tabMain.outputComments.isChecked() + saveTo = self.tabMain.exportPath.text() + + outFile = None + if eFormat == GuiExportMain.FMT_TXT: + outFile = TextFile(self.theProject, self.theParent) + + if outFile is None: + return False + + outFile.openFile(saveTo,"testfile") + outFile.setComments(wComments) + + nItems = len(self.theProject.treeOrder) + self.exportProgress.setMinimum(0) + self.exportProgress.setMaximum(nItems) + self.exportProgress.setValue(0) + + nDone = 0 + for tHandle in self.theProject.treeOrder: + + time.sleep(0.1) + + self.exportProgress.setValue(nDone) + tItem = self.theProject.getItem(tHandle) + + self.exportStatus.setText("Exporting: %s" % tItem.itemName) + logger.verbose("Exporting: %s" % tItem.itemName) + + if tItem is not None and tItem.itemType == nwItemType.FILE: + outFile.addText(tHandle) + + nDone += 1 + + outFile.closeFile() + + self.exportProgress.setValue(nDone) + self.exportStatus.setText("Export to %s complete" % outFile.fileName) + logger.verbose("Export to %s complete" % outFile.fileName) + + return + + def _doClose(self): + + logger.verbose("GuiExport close button clicked") wNovel = self.tabMain.expNovel.isChecked() wNotes = self.tabMain.expNotes.isChecked() @@ -103,21 +168,21 @@ class GuiExport(QDialog): return - def _doClose(self): - logger.verbose("GuiExport close button clicked") - self.close() - return - # END Class GuiExport class GuiExportMain(QWidget): - FMT_MD = 1 - FMT_HTML = 2 - FMT_EBOOK = 3 - FMT_ODT = 4 - FMT_TEX = 5 + FMT_TXT = 1 + FMT_MD = 2 + FMT_HTML = 3 + FMT_EBOOK = 4 + FMT_ODT = 5 + FMT_TEX = 6 FMT_HELP = { + FMT_TXT : ( + "Exports a plain text file. " + "All formatting is stripped and comments are in square brackets." + ), FMT_MD : ( "Exports a standard markdown file. " "Comments are converted to preformatted text blocks." @@ -198,11 +263,12 @@ class GuiExportMain(QWidget): self.outputHelp.setAlignment(Qt.AlignTop) self.outputFormat = QComboBox(self) - self.outputFormat.addItem("Markdown", self.FMT_MD) - self.outputFormat.addItem("HTML5 (Plain)", self.FMT_HTML) - self.outputFormat.addItem("HTML5 (eBook)", self.FMT_EBOOK) - self.outputFormat.addItem("Open Document", self.FMT_ODT) - self.outputFormat.addItem("LaTeX (PDF)", self.FMT_TEX) + self.outputFormat.addItem("Plain Text", self.FMT_TXT) + # self.outputFormat.addItem("Markdown", self.FMT_MD) + # self.outputFormat.addItem("HTML5 (Plain)", self.FMT_HTML) + # self.outputFormat.addItem("HTML5 (eBook)", self.FMT_EBOOK) + # self.outputFormat.addItem("Open Document", self.FMT_ODT) + # self.outputFormat.addItem("LaTeX (PDF)", self.FMT_TEX) self.outputFormat.currentIndexChanged.connect(self._updateFormatHelp) optIdx = self.outputFormat.findData(self.optState.eFormat()) @@ -300,7 +366,7 @@ class ExportLastState(): "wNovel" : True, "wNotes" : False, "wTOC" : True, - "eFormat" : 1, + "eFormat" : 2, "wComments" : False, "chFormat" : "Chapter %numword%", "scFormat" : "* * *", From d4e74848bcda8659e710e7552460008792c33712 Mon Sep 17 00:00:00 2001 From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com> Date: Fri, 18 Oct 2019 22:41:54 +0200 Subject: [PATCH 08/14] Some improvements and added comments to the converter classes --- nw/convert/textfile.py | 13 +++++ nw/convert/tohtml.py | 12 ++--- nw/convert/tokenizer.py | 105 ++++++++++++++++++++++++++-------------- 3 files changed, 88 insertions(+), 42 deletions(-) diff --git a/nw/convert/textfile.py b/nw/convert/textfile.py index 04cd4b3a..63d5d315 100644 --- a/nw/convert/textfile.py +++ b/nw/convert/textfile.py @@ -54,6 +54,13 @@ class TextFile(): self.doMeta = doMeta return + def setWordWrap(self, wordWrap): + if wordWrap >= 0: + self.wordWrap = wordWrap + else: + self.wordWrap = 0 + return + ## # Core Methods ## @@ -112,6 +119,9 @@ class TextFile(): ## def _doOpenFile(self, filePath): + """This function does the actual opening of the file, and can be overloaded by a subclass + that uses a different file format that requires a different approach. + """ try: self.outFile = open(filePath,mode="w+") except Exception as e: @@ -120,6 +130,9 @@ class TextFile(): return True def _doCloseFile(self): + """This function closes a file, and is meant to be overloaded by the subclass for other + file formats. + """ self.outFile.close() return True diff --git a/nw/convert/tohtml.py b/nw/convert/tohtml.py index c6ec8975..10389bdd 100644 --- a/nw/convert/tohtml.py +++ b/nw/convert/tohtml.py @@ -53,19 +53,19 @@ class ToHtml(Tokenizer): thisPar = [] for tType, tText, tFormat in self.theTokens: - if tType == "empty": + if tType == self.T_EMPTY: if len(thisPar) > 0: self.theResult += "

%s

\n" % " ".join(thisPar) thisPar = [] - elif tType == "header1": + elif tType == self.T_HEAD1: self.theResult += "

%s

\n" % tText - elif tType == "header2": + elif tType == self.T_HEAD2: self.theResult += "

%s

\n" % tText - elif tType == "header3": + elif tType == self.T_HEAD3: self.theResult += "

%s

\n" % tText - elif tType == "header4": + elif tType == self.T_HEAD4: self.theResult += "

%s

\n" % tText - elif tType == "text": + elif tType == self.T_TEXT: tTemp = tText for xPos, xLen, xFmt in reversed(tFormat): tTemp = tTemp[:xPos]+htmlTags[xFmt]+tTemp[xPos+xLen:] diff --git a/nw/convert/tokenizer.py b/nw/convert/tokenizer.py index bbb7877b..2e223899 100644 --- a/nw/convert/tokenizer.py +++ b/nw/convert/tokenizer.py @@ -23,12 +23,21 @@ logger = logging.getLogger(__name__) class Tokenizer(): - FMT_B_B = 1 # Begin Bold - FMT_B_E = 2 # End Bold - FMT_I_B = 3 # Begin Italics - FMT_I_E = 4 # End Italics - FMT_U_B = 5 # Begin Underline - FMT_U_E = 6 # End Underline + FMT_B_B = "" # Begin bold + FMT_B_E = "" # End bold + FMT_I_B = "" # Begin italics + FMT_I_E = "" # End italics + FMT_U_B = "" # Begin underline + FMT_U_E = "" # End underline + + T_EMPTY = 1 # Empty line (new paragraph) + T_COMMENT = 2 # Comment line + T_COMMAND = 3 # Command line + T_HEAD1 = 4 # Header 1 (title) + T_HEAD2 = 5 # Header 2 (chapter) + T_HEAD3 = 6 # Header 3 (scene) + T_HEAD4 = 7 # Header 4 + T_TEXT = 8 # Text line def __init__(self, theProject, theParent): @@ -48,6 +57,10 @@ class Tokenizer(): return + ## + # Setters + ## + def setComments(self, doComments): self.doComments = doComments return @@ -63,6 +76,10 @@ class Tokenizer(): self.wordWrap = 0 return + ## + # Class Methods + ## + def setText(self, theHandle, theText=None): self.theHandle = theHandle @@ -113,19 +130,19 @@ class Tokenizer(): # Tag lines starting with specific characters if len(aLine) == 0: - self.theTokens.append(("empty","",None)) + self.theTokens.append((self.T_EMPTY,"",None)) elif aLine[0] == "%": - self.theTokens.append(("comment",aLine[1:].strip(),None)) + self.theTokens.append((self.T_COMMENT,aLine[1:].strip(),None)) elif aLine[0] == "@": - self.theTokens.append(("command",aLine[1:].strip(),None)) + self.theTokens.append((self.T_COMMENT,aLine[1:].strip(),None)) elif aLine[:2] == "# ": - self.theTokens.append(("header1",aLine[2:].strip(),None)) + self.theTokens.append((self.T_HEAD1,aLine[2:].strip(),None)) elif aLine[:3] == "## ": - self.theTokens.append(("header2",aLine[3:].strip(),None)) + self.theTokens.append((self.T_HEAD2,aLine[3:].strip(),None)) elif aLine[:4] == "### ": - self.theTokens.append(("header3",aLine[4:].strip(),None)) + self.theTokens.append((self.T_HEAD3,aLine[4:].strip(),None)) elif aLine[:5] == "#### ": - self.theTokens.append(("header4",aLine[5:].strip(),None)) + self.theTokens.append((self.T_HEAD4,aLine[5:].strip(),None)) else: # Otherwise we use RegEx to find formatting tags within a line of text fmtPos = [] @@ -141,11 +158,10 @@ class Tokenizer(): # Save the line as is, but append the array of formatting locations sorted by position fmtPos = sorted(fmtPos,key=itemgetter(0)) - self.theTokens.append(("text",aLine,fmtPos)) + self.theTokens.append((self.T_TEXT,aLine,fmtPos)) # Always add an empty line at the end - self.theTokens.append(("empty","",None)) - # print(self.theTokens) + self.theTokens.append((self.T_EMPTY,"",None)) return @@ -153,55 +169,72 @@ class Tokenizer(): """Converts the tokenized text into plain text. """ + if self.wordWrap > 0: + tWrap = textwrap.TextWrapper( + width = self.wordWrap, + initial_indent = "", + subsequent_indent = "", + expand_tabs = True, + replace_whitespace = True, + fix_sentence_endings = False, + break_long_words = True, + drop_whitespace = True, + break_on_hyphens = True, + tabsize = 8, + max_lines = None + ) + self.theResult = "" thisPar = [] for tType, tText, tFormat in self.theTokens: # First check if we have a comment or plain text, as they need some - # extra replacing before we proceed - if tType == "comment": + # extra replacing before we proceed to wrapping and final formatting. + if tType == self.T_COMMAND: tText = "[%s]" % tText - elif tType == "text": + elif tType == self.T_TEXT: tTemp = tText for xPos, xLen, xFmt in reversed(tFormat): tTemp = tTemp[:xPos]+tTemp[xPos+xLen:] tText = tTemp - # The text can now be word wrapped, if we have requested this - if self.wordWrap > 0: - tText = textwrap.fill(tText, width=self.wordWrap) + tLen = len(tText) - # Then the text can receive final formatting before we append it - # to the results. We store text bits in a buffer and merge them only - # when we find an empty line, indicating a new paragraph - if tType == "empty": + # The text can now be word wrapped, if we have requested this and it's needed. + if self.wordWrap > 0 and tLen > self.wordWrap: + tText = tWrap.fill(tText) + + # Then the text can receive final formatting before we append it to the results. + # We also store text lines in a buffer and merge them only when we find an empty line, + # indicating a new paragraph. + if tType == self.T_EMPTY: if len(thisPar) > 0: self.theResult += "%s\n\n" % " ".join(thisPar) thisPar = [] - elif tType == "header1": - uLine = "="*min(len(tText),self.wordWrap) + elif tType == self.T_HEAD1: + uLine = "="*min(tLen,self.wordWrap) self.theResult += "%s\n%s\n\n" % (tText,uLine) - elif tType == "header2": - uLine = "~"*min(len(tText),self.wordWrap) + elif tType == self.T_HEAD2: + uLine = "~"*min(tLen,self.wordWrap) self.theResult += "%s\n%s\n\n" % (tText,uLine) - elif tType == "header3": - uLine = "-"*min(len(tText),self.wordWrap) + elif tType == self.T_HEAD3: + uLine = "-"*min(tLen,self.wordWrap) self.theResult += "%s\n%s\n\n" % (tText,uLine) - elif tType == "header4": + elif tType == self.T_HEAD4: self.theResult += "%s\n\n" % tText - elif tType == "text": + elif tType == self.T_TEXT: thisPar.append(tText) - elif tType == "comment" and self.doComments: + elif tType == self.T_COMMENT and self.doComments: self.theResult += "%s\n\n" % tText - elif tType == "command" and self.doCommands: + elif tType == self.T_COMMAND and self.doCommands: self.theResult += "%s\n\n" % tText return From 287d94369f99957b613339a291fadc53f13c3252 Mon Sep 17 00:00:00 2001 From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com> Date: Sat, 19 Oct 2019 01:17:19 +0200 Subject: [PATCH 09/14] Added header formatting to tokenizer --- nw/convert/textfile.py | 62 +++++++++------ nw/convert/tohtml.py | 2 +- nw/convert/tokenizer.py | 166 ++++++++++++++++++++++++++++++++++++---- nw/gui/export.py | 20 +++-- nw/tools/translate.py | 2 +- 5 files changed, 208 insertions(+), 44 deletions(-) diff --git a/nw/convert/textfile.py b/nw/convert/textfile.py index 63d5d315..08d5addf 100644 --- a/nw/convert/textfile.py +++ b/nw/convert/textfile.py @@ -17,7 +17,7 @@ from os import path from PyQt5.QtWidgets import QMessageBox from nw.convert.tokenizer import Tokenizer -from nw.enum import nwAlert +from nw.enum import nwAlert, nwItemLayout logger = logging.getLogger(__name__) @@ -33,12 +33,16 @@ class TextFile(): self.outFile = None self.fileName = "" self.theText = "" - self.doComments = False - self.doMeta = False - self.wordWrap = 80 + self.expNovel = True + self.expNotes = False self.winEnding = False - self.makeAlert = self.theParent.makeAlert + self.theConv = Tokenizer(self.theProject, self.theParent) + self.makeAlert = self.theParent.makeAlert + + self.setComments(False) + self.setMeta(False) + self.setWordWrap(80) return @@ -46,19 +50,27 @@ class TextFile(): # Setters ## + def setExportNovel(self, doNovel): + self.expNovel = doNovel + return + + def setExportNotes(self, doNotes): + self.expNotes = doNotes + return + def setComments(self, doComments): - self.doComments = doComments + self.theConv.setComments(doComments) return def setMeta(self, doMeta): - self.doMeta = doMeta + self.theConv.setCommands(doMeta) return def setWordWrap(self, wordWrap): if wordWrap >= 0: - self.wordWrap = wordWrap + self.theConv.setWordWrap(wordWrap) else: - self.wordWrap = 0 + self.theConv.setWordWrap(0) return ## @@ -92,25 +104,29 @@ class TextFile(): logger.verbose("Parsing content of item '%s'" % tHandle) - aDoc = Tokenizer(self.theProject, self.theParent) - aDoc.setText(tHandle) - aDoc.doAutoReplace() - aDoc.tokenizeText() + theItem = self.theProject.getItem(tHandle) + isNone = theItem.itemLayout == nwItemLayout.NO_LAYOUT + isNote = theItem.itemLayout == nwItemLayout.NOTE + isNovel = not isNone and not isNote - aDoc.setComments(self.doComments) - aDoc.setCommands(self.doMeta) - aDoc.setWordWrap(self.wordWrap) + if isNone: + return False + if isNote and not self.expNotes: + return False + if isNovel and not self.expNovel: + return False - aDoc.doConvert() - - theText = "" - if aDoc.theResult is not None: - theText = aDoc.theResult + self.theConv.setText(tHandle) + self.theConv.doAutoReplace() + self.theConv.tokenizeText() + self.theConv.doHeaders() + self.theConv.doConvert() if self.winEnding: - theText = theText.replace("\n","\r\n") + self.theConv.windowsEndings() - self.outFile.write(theText) + if self.theConv.theResult is not None: + self.outFile.write(self.theConv.theResult) return True diff --git a/nw/convert/tohtml.py b/nw/convert/tohtml.py index 10389bdd..0f1e0612 100644 --- a/nw/convert/tohtml.py +++ b/nw/convert/tohtml.py @@ -51,7 +51,7 @@ class ToHtml(Tokenizer): self.theResult = "" thisPar = [] - for tType, tText, tFormat in self.theTokens: + for tType, tText, tFormat, tAlign in self.theTokens: if tType == self.T_EMPTY: if len(thisPar) > 0: diff --git a/nw/convert/tokenizer.py b/nw/convert/tokenizer.py index 2e223899..6b82d2f7 100644 --- a/nw/convert/tokenizer.py +++ b/nw/convert/tokenizer.py @@ -17,7 +17,10 @@ import nw from operator import itemgetter from PyQt5.QtCore import QRegularExpression + from nw.project.document import NWDoc +from nw.tools.translate import numberToWord +from nw.enum import nwItemLayout logger = logging.getLogger(__name__) @@ -38,6 +41,12 @@ class Tokenizer(): T_HEAD3 = 6 # Header 3 (scene) T_HEAD4 = 7 # Header 4 T_TEXT = 8 # Text line + T_SEP = 9 # Scene separator + + A_LEFT = 1 # Left aligned + A_RIGHT = 2 # Right aligned + A_CENTRE = 3 # Centred + A_JUSTIFY = 4 # Justified def __init__(self, theProject, theParent): @@ -55,6 +64,17 @@ class Tokenizer(): self.doComments = False self.doCommands = False + self.fmtTitle = "%title%" + self.fmtUnNum = "%title%" + self.fmtChapter = "Chapter %numword%: %title%" + self.fmtScene = "* * *" + self.fmtSection = "%title%" + + self.noSection = True + + self.numChapter = 0 + self.firstScene = False + return ## @@ -130,19 +150,19 @@ class Tokenizer(): # Tag lines starting with specific characters if len(aLine) == 0: - self.theTokens.append((self.T_EMPTY,"",None)) + self.theTokens.append((self.T_EMPTY,"",None,self.A_LEFT)) elif aLine[0] == "%": - self.theTokens.append((self.T_COMMENT,aLine[1:].strip(),None)) + self.theTokens.append((self.T_COMMENT,aLine[1:].strip(),None,self.A_LEFT)) elif aLine[0] == "@": - self.theTokens.append((self.T_COMMENT,aLine[1:].strip(),None)) + self.theTokens.append((self.T_COMMAND,aLine[1:].strip(),None,self.A_LEFT)) elif aLine[:2] == "# ": - self.theTokens.append((self.T_HEAD1,aLine[2:].strip(),None)) + self.theTokens.append((self.T_HEAD1,aLine[2:].strip(),None,self.A_LEFT)) elif aLine[:3] == "## ": - self.theTokens.append((self.T_HEAD2,aLine[3:].strip(),None)) + self.theTokens.append((self.T_HEAD2,aLine[3:].strip(),None,self.A_LEFT)) elif aLine[:4] == "### ": - self.theTokens.append((self.T_HEAD3,aLine[4:].strip(),None)) + self.theTokens.append((self.T_HEAD3,aLine[4:].strip(),None,self.A_LEFT)) elif aLine[:5] == "#### ": - self.theTokens.append((self.T_HEAD4,aLine[5:].strip(),None)) + self.theTokens.append((self.T_HEAD4,aLine[5:].strip(),None,self.A_LEFT)) else: # Otherwise we use RegEx to find formatting tags within a line of text fmtPos = [] @@ -158,10 +178,73 @@ class Tokenizer(): # Save the line as is, but append the array of formatting locations sorted by position fmtPos = sorted(fmtPos,key=itemgetter(0)) - self.theTokens.append((self.T_TEXT,aLine,fmtPos)) + self.theTokens.append((self.T_TEXT,aLine,fmtPos,self.A_LEFT)) # Always add an empty line at the end - self.theTokens.append((self.T_EMPTY,"",None)) + self.theTokens.append((self.T_EMPTY,"",None,self.A_LEFT)) + + return + + def doHeaders(self): + + isNone = self.theItem.itemLayout == nwItemLayout.NO_LAYOUT + isTitle = self.theItem.itemLayout == nwItemLayout.TITLE + isBook = self.theItem.itemLayout == nwItemLayout.BOOK + isPage = self.theItem.itemLayout == nwItemLayout.PAGE + isPart = self.theItem.itemLayout == nwItemLayout.PARTITION + isUnNum = self.theItem.itemLayout == nwItemLayout.UNNUMBERED + isChap = self.theItem.itemLayout == nwItemLayout.CHAPTER + isScene = self.theItem.itemLayout == nwItemLayout.SCENE + isNote = self.theItem.itemLayout == nwItemLayout.NOTE + + # No special header formatting for notes and no layout files + if isNone: return + if isNote: return + + # For novel files, we need to handle chapter numbering and scene breaks + if isBook or isUnNum or isChap or isScene: + for n in range(len(self.theTokens)): + + tToken = self.theTokens[n] + tType = tToken[0] + tText = tToken[1] + + if tType == self.T_TEXT: + self.firstScene = False + + elif tType == self.T_HEAD2: + if not isUnNum: + self.numChapter += 1 + tText = self._doFormatChapter(tText,isUnNum) + self.theTokens[n] = (tType,tText,None,self.A_LEFT) + self.firstScene = True + + elif tType == self.T_HEAD3: + tTemp = self._doFormatScene(tText) + if tTemp == self.fmtScene: + if self.firstScene: + self.theTokens[n] = (self.T_EMPTY,"",None,self.A_LEFT) + else: + self.theTokens[n] = (self.T_SEP,tTemp,None,self.A_LEFT) + else: + self.theTokens[n] = (tType,tTemp,None,self.A_LEFT) + self.firstScene = False + + elif tType == self.T_HEAD4: + if self.noSection: + self.theTokens[n] = (self.T_EMPTY,"",None,self.A_LEFT) + else: + tTemp = self._doFormatSection(tText) + self.theTokens[n] = (self.T_SEP,tTemp,None,self.A_LEFT) + + # For title page and partitions, we need to centre all text + if isTitle or isPart: + for n in range(len(self.theTokens)): + tToken = self.theTokens[n] + tType = tToken[0] + tText = tToken[1] + tFormat = tToken[2] + self.theTokens[n] = (tType,tText,tFormat,self.A_CENTRE) return @@ -186,11 +269,11 @@ class Tokenizer(): self.theResult = "" thisPar = [] - for tType, tText, tFormat in self.theTokens: + for tType, tText, tFormat, tAlign in self.theTokens: # First check if we have a comment or plain text, as they need some # extra replacing before we proceed to wrapping and final formatting. - if tType == self.T_COMMAND: + if tType == self.T_COMMENT: tText = "[%s]" % tText elif tType == self.T_TEXT: @@ -202,8 +285,18 @@ class Tokenizer(): tLen = len(tText) # The text can now be word wrapped, if we have requested this and it's needed. - if self.wordWrap > 0 and tLen > self.wordWrap: - tText = tWrap.fill(tText) + if tAlign == self.A_CENTRE: + if self.wordWrap > 0: + if tLen > self.wordWrap: + aText = tWrap.wrap(tText) + for n in range(len(aText)): + aText[n] = self._centreText(aText[n],self.wordWrap) + tText = "\n".join(aText) + else: + tText = self._centreText(tText,self.wordWrap) + else: + if self.wordWrap > 0 and tLen > self.wordWrap: + tText = tWrap.fill(tText) # Then the text can receive final formatting before we append it to the results. # We also store text lines in a buffer and merge them only when we find an empty line, @@ -215,6 +308,8 @@ class Tokenizer(): elif tType == self.T_HEAD1: uLine = "="*min(tLen,self.wordWrap) + if tAlign == self.A_CENTRE: + uLine = self._centreText(uLine,self.wordWrap) self.theResult += "%s\n%s\n\n" % (tText,uLine) elif tType == self.T_HEAD2: @@ -228,6 +323,11 @@ class Tokenizer(): elif tType == self.T_HEAD4: self.theResult += "%s\n\n" % tText + elif tType == self.T_SEP: + if self.wordWrap > 0 and tLen < self.wordWrap: + tText = self._centreText(tText,self.wordWrap) + self.theResult += "%s\n\n" % tText + elif tType == self.T_TEXT: thisPar.append(tText) @@ -239,4 +339,44 @@ class Tokenizer(): return + def windowsEndings(self): + self.theResult = self.theResult.replace("\n","\r\n") + return + + ## + # Internal Functions + ## + + def _doFormatTitle(self, theText): + theTitle = self.fmtTitle + theTitle = theTitle.replace("%title%", theText) + return theTitle + + def _doFormatChapter(self, theText, noNum): + if noNum: + theTitle = self.fmtUnNum + theTitle = theTitle.replace("%title%", theText) + else: + theTitle = self.fmtChapter + theTitle = theTitle.replace("%title%", theText) + theTitle = theTitle.replace("%num%", str(self.numChapter)) + theTitle = theTitle.replace("%numword%", numberToWord(self.numChapter,"en")) + return theTitle + + def _doFormatScene(self, theText): + theTitle = self.fmtScene + theTitle = theTitle.replace("%title%", theText) + return theTitle + + def _doFormatSection(self, theText): + theTitle = self.fmtSection + theTitle = theTitle.replace("%title%", theText) + return theTitle + + def _centreText(self, theText, theWidth): + tLen = len(theText) + if tLen < theWidth: + return " "*int((theWidth-tLen)/2) + theText + return theText + # END Class Tokenizer diff --git a/nw/gui/export.py b/nw/gui/export.py index 0c78211d..f393d91e 100644 --- a/nw/gui/export.py +++ b/nw/gui/export.py @@ -98,10 +98,21 @@ class GuiExport(QDialog): logger.verbose("GuiExport export button clicked") + wNovel = self.tabMain.expNovel.isChecked() + wNotes = self.tabMain.expNotes.isChecked() eFormat = self.tabMain.outputFormat.currentData() wComments = self.tabMain.outputComments.isChecked() saveTo = self.tabMain.exportPath.text() + nItems = len(self.theProject.treeOrder) + self.exportProgress.setMinimum(0) + self.exportProgress.setMaximum(nItems) + self.exportProgress.setValue(0) + + if not wNovel and not wNotes: + self.exportStatus.setText("Nothing to export ...") + return False + outFile = None if eFormat == GuiExportMain.FMT_TXT: outFile = TextFile(self.theProject, self.theParent) @@ -111,11 +122,8 @@ class GuiExport(QDialog): outFile.openFile(saveTo,"testfile") outFile.setComments(wComments) - - nItems = len(self.theProject.treeOrder) - self.exportProgress.setMinimum(0) - self.exportProgress.setMaximum(nItems) - self.exportProgress.setValue(0) + outFile.setExportNovel(wNovel) + outFile.setExportNotes(wNotes) nDone = 0 for tHandle in self.theProject.treeOrder: @@ -233,7 +241,7 @@ class GuiExportMain(QWidget): self.guiFilesForm.addWidget(self.expNovel, 0, 1) self.guiFilesForm.addWidget(QLabel("Note files"), 1, 0) self.guiFilesForm.addWidget(self.expNotes, 1, 1) - self.guiFilesForm.addWidget(QLabel("Contents"), 2, 0) + self.guiFilesForm.addWidget(QLabel("ToC"), 2, 0) self.guiFilesForm.addWidget(self.expTOC, 2, 1) # Chapter Settings diff --git a/nw/tools/translate.py b/nw/tools/translate.py index 33e4885f..c32db147 100644 --- a/nw/tools/translate.py +++ b/nw/tools/translate.py @@ -19,7 +19,7 @@ logger = logging.getLogger(__name__) def numberToWord(numVal, theLanguage): numWord = "" - if theLanguage == "EN": + if theLanguage == "en": numWord = _numberToWordEN(numVal) else: numWord = _numberToWordEN(numVal) From d325e671ddbe0adf5de0e7a3d7d0c704f13df537 Mon Sep 17 00:00:00 2001 From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com> Date: Sat, 19 Oct 2019 11:32:11 +0200 Subject: [PATCH 10/14] Added some error handling for export file, and improved getting and setting of export options --- nw/convert/textfile.py | 12 +++++-- nw/convert/tokenizer.py | 8 +++-- nw/gui/export.py | 73 +++++++++++++++++------------------------ 3 files changed, 45 insertions(+), 48 deletions(-) diff --git a/nw/convert/textfile.py b/nw/convert/textfile.py index 08d5addf..c7bed1ab 100644 --- a/nw/convert/textfile.py +++ b/nw/convert/textfile.py @@ -94,6 +94,9 @@ class TextFile(): self._doOpenFile(filePath) + if self.outFile is None: + return False + return True def closeFile(self): @@ -125,7 +128,7 @@ class TextFile(): if self.winEnding: self.theConv.windowsEndings() - if self.theConv.theResult is not None: + if self.theConv.theResult is not None and self.outFile is not None: self.outFile.write(self.theConv.theResult) return True @@ -140,6 +143,10 @@ class TextFile(): """ try: self.outFile = open(filePath,mode="w+") + if self.winEnding: + self.outFile.write("\r\n\r\n") + else: + self.outFile.write("\n\n") except Exception as e: self.makeAlert(["Failed to open file.",str(e)], nwAlert.ERROR) return False @@ -149,7 +156,8 @@ class TextFile(): """This function closes a file, and is meant to be overloaded by the subclass for other file formats. """ - self.outFile.close() + if self.outFile is not None: + self.outFile.close() return True # END Class OutFile diff --git a/nw/convert/tokenizer.py b/nw/convert/tokenizer.py index 6b82d2f7..50166e27 100644 --- a/nw/convert/tokenizer.py +++ b/nw/convert/tokenizer.py @@ -221,7 +221,9 @@ class Tokenizer(): elif tType == self.T_HEAD3: tTemp = self._doFormatScene(tText) - if tTemp == self.fmtScene: + if tTemp == "": + self.theTokens[n] = (self.T_EMPTY,"",None,self.A_LEFT) + elif tTemp == self.fmtScene: if self.firstScene: self.theTokens[n] = (self.T_EMPTY,"",None,self.A_LEFT) else: @@ -231,10 +233,10 @@ class Tokenizer(): self.firstScene = False elif tType == self.T_HEAD4: - if self.noSection: + tTemp = self._doFormatSection(tText) + if tTemp == "": self.theTokens[n] = (self.T_EMPTY,"",None,self.A_LEFT) else: - tTemp = self._doFormatSection(tText) self.theTokens[n] = (self.T_SEP,tTemp,None,self.A_LEFT) # For title page and partitions, we need to centre all text diff --git a/nw/gui/export.py b/nw/gui/export.py index f393d91e..c7dcf7d5 100644 --- a/nw/gui/export.py +++ b/nw/gui/export.py @@ -120,10 +120,13 @@ class GuiExport(QDialog): if outFile is None: return False - outFile.openFile(saveTo,"testfile") - outFile.setComments(wComments) - outFile.setExportNovel(wNovel) - outFile.setExportNotes(wNotes) + if outFile.openFile(saveTo,"testfile"): + outFile.setComments(wComments) + outFile.setExportNovel(wNovel) + outFile.setExportNotes(wNotes) + else: + self.exportStatus.setText("Failed to open file for writing ...") + return False nDone = 0 for tHandle in self.theProject.treeOrder: @@ -233,9 +236,9 @@ class GuiExportMain(QWidget): self.expNovel.setToolTip("Include all novel files in the exported document") self.expNotes.setToolTip("Include all note files in the exported document") self.expTOC.setToolTip("Generate a Table of Contents (ToC)") - self.expNovel.setChecked(self.optState.wNovel()) - self.expNotes.setChecked(self.optState.wNotes()) - self.expTOC.setChecked(self.optState.wTOC()) + self.expNovel.setChecked(self.optState.getSetting("wNovel")) + self.expNotes.setChecked(self.optState.getSetting("wNotes")) + self.expTOC.setChecked(self.optState.getSetting("wTOC")) self.guiFilesForm.addWidget(QLabel("Novel files"), 0, 0) self.guiFilesForm.addWidget(self.expNovel, 0, 1) @@ -245,16 +248,16 @@ class GuiExportMain(QWidget): self.guiFilesForm.addWidget(self.expTOC, 2, 1) # Chapter Settings - self.guiChapters = QGroupBox("Chapters", self) + self.guiChapters = QGroupBox("Chapter Heading", self) self.guiChaptersForm = QGridLayout(self) self.guiChapters.setLayout(self.guiChaptersForm) self.chapterFormat = QLineEdit() - self.chapterFormat.setText(self.optState.chFormat()) + self.chapterFormat.setText(self.optState.getSetting("chFormat")) self.chapterFormat.setToolTip("Available formats: %num%, %numword%, %title%, %label%") self.chapterFormat.setMinimumWidth(250) - self.guiChaptersForm.addWidget(QLabel("Format"), 0, 0) + self.guiChaptersForm.addWidget(QLabel("Numbered"), 0, 0) self.guiChaptersForm.addWidget(self.chapterFormat, 0, 1) # Output Format @@ -263,7 +266,7 @@ class GuiExportMain(QWidget): self.guiOutput.setLayout(self.guiOutputForm) self.outputComments = QCheckBox("include comments", self) - self.outputComments.setChecked(self.optState.wComments()) + self.outputComments.setChecked(self.optState.getSetting("wComments")) self.outputHelp = QLabel("") self.outputHelp.setWordWrap(True) @@ -279,7 +282,7 @@ class GuiExportMain(QWidget): # self.outputFormat.addItem("LaTeX (PDF)", self.FMT_TEX) self.outputFormat.currentIndexChanged.connect(self._updateFormatHelp) - optIdx = self.outputFormat.findData(self.optState.eFormat()) + optIdx = self.outputFormat.findData(self.optState.getSetting("eFormat")) if optIdx != -1: self.outputFormat.setCurrentIndex(optIdx) self._updateFormatHelp(optIdx) @@ -296,7 +299,7 @@ class GuiExportMain(QWidget): self.guiScenes.setLayout(self.guiScenesForm) self.sceneFormat = QLineEdit() - self.sceneFormat.setText(self.optState.scFormat()) + self.sceneFormat.setText(self.optState.getSetting("scFormat")) self.sceneFormat.setToolTip("Available formats: %title%") self.sceneFormat.setMinimumWidth(100) @@ -308,7 +311,7 @@ class GuiExportMain(QWidget): self.exportToForm = QGridLayout(self) self.exportTo.setLayout(self.exportToForm) - self.exportPath = QLineEdit(self.optState.saveTo()) + self.exportPath = QLineEdit(self.optState.getSetting("saveTo")) self.exportGetPath = QPushButton(self.theTheme.getIcon("folder"),"") self.exportGetPath.clicked.connect(self._exportFolder) @@ -368,7 +371,6 @@ class GuiExportMain(QWidget): class ExportLastState(): def __init__(self, theProject): - self.theProject = theProject self.theState = { "wNovel" : True, @@ -377,15 +379,18 @@ class ExportLastState(): "eFormat" : 2, "wComments" : False, "chFormat" : "Chapter %numword%", + "unFormat" : "%title%", "scFormat" : "* * *", + "seFormat" : "", "saveTo" : "", } + self.stringOpt = ("chFormat","unFormat","scFormat","seFormat","saveTo") + self.boolOpt = ("wNovel","wNotes","wTOC","wComments") + self.intOpt = ("eFormat") self.loadSettings() - return def loadSettings(self): - stateFile = path.join(self.theProject.projMeta, nwFiles.EXPORT_OPT) if path.isfile(stateFile): logger.debug("Loading export options file") @@ -397,11 +402,9 @@ class ExportLastState(): logger.error("Failed to load export options file") logger.error(str(e)) return False - return True def saveSettings(self): - stateFile = path.join(self.theProject.projMeta, nwFiles.EXPORT_OPT) logger.debug("Saving export options file") try: @@ -411,7 +414,6 @@ class ExportLastState(): logger.error("Failed to save export options file") logger.error(str(e)) return False - return True def setSetting(self, setName, setValue): @@ -421,28 +423,13 @@ class ExportLastState(): return False return True - def wNovel(self): - return checkBool(self.theState["wNovel"],False,False) - - def wNotes(self): - return checkBool(self.theState["wNotes"],False,False) - - def wTOC(self): - return checkBool(self.theState["wTOC"],False,False) - - def eFormat(self): - return checkInt(self.theState["eFormat"],1,False) - - def wComments(self): - return checkBool(self.theState["wComments"],False,False) - - def chFormat(self): - return checkString(self.theState["chFormat"],"Chapter %numword%",False) - - def scFormat(self): - return checkString(self.theState["scFormat"],"* * *",False) - - def saveTo(self): - return checkString(self.theState["saveTo"],"",False) + def getSetting(self, setName): + if setName in self.stringOpt: + return checkString(self.theState[setName],self.theState[setName],False) + elif setName in self.boolOpt: + return checkBool(self.theState[setName],self.theState[setName],False) + elif setName in self.intOpt: + return checkInt(self.theState[setName],self.theState[setName],False) + return None # END Class ExportLastState From 16f1b03781239d2dc4878ccd7aa508a7d8fdd8da Mon Sep 17 00:00:00 2001 From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com> Date: Sat, 19 Oct 2019 12:05:04 +0200 Subject: [PATCH 11/14] Export icon and a few GUI changes --- nw/graphics/export.svg | 44 ++++++++++++++++++++++++++++++++++++++++++ nw/graphics/export.txt | 3 +++ nw/gui/export.py | 43 ++++++++++++++++++++++++++++++----------- 3 files changed, 79 insertions(+), 11 deletions(-) create mode 100644 nw/graphics/export.svg create mode 100644 nw/graphics/export.txt diff --git a/nw/graphics/export.svg b/nw/graphics/export.svg new file mode 100644 index 00000000..9594a980 --- /dev/null +++ b/nw/graphics/export.svg @@ -0,0 +1,44 @@ + +image/svg+xml \ No newline at end of file diff --git a/nw/graphics/export.txt b/nw/graphics/export.txt new file mode 100644 index 00000000..1a65e8da --- /dev/null +++ b/nw/graphics/export.txt @@ -0,0 +1,3 @@ +FROM ICON SET: Typicons +LICENSE: Creative Commons (Attribution-Share Alike 3.0 Unported) +https://creativecommons.org/licenses/by-sa/3.0/ diff --git a/nw/gui/export.py b/nw/gui/export.py index c7dcf7d5..566a6a9f 100644 --- a/nw/gui/export.py +++ b/nw/gui/export.py @@ -50,7 +50,7 @@ class GuiExport(QDialog): self.setWindowTitle("Export Project") self.setLayout(self.outerBox) - self.gradPath = path.abspath(path.join(self.mainConf.appPath,"graphics","gear.svg")) + self.gradPath = path.abspath(path.join(self.mainConf.appPath,"graphics","export.svg")) self.svgGradient = QSvgWidget(self.gradPath) self.svgGradient.setFixedSize(QSize(64,64)) @@ -162,7 +162,9 @@ class GuiExport(QDialog): eFormat = self.tabMain.outputFormat.currentData() wComments = self.tabMain.outputComments.isChecked() chFormat = self.tabMain.chapterFormat.text() + unFormat = self.tabMain.unnumFormat.text() scFormat = self.tabMain.sceneFormat.text() + seFormat = self.tabMain.sectionFormat.text() saveTo = self.tabMain.exportPath.text() self.optState.setSetting("wNovel", wNovel) @@ -171,7 +173,9 @@ class GuiExport(QDialog): self.optState.setSetting("eFormat", eFormat) self.optState.setSetting("wComments",wComments) self.optState.setSetting("chFormat", chFormat) + self.optState.setSetting("unFormat", unFormat) self.optState.setSetting("scFormat", scFormat) + self.optState.setSetting("seFormat", seFormat) self.optState.setSetting("saveTo", saveTo) self.optState.saveSettings() @@ -248,17 +252,24 @@ class GuiExportMain(QWidget): self.guiFilesForm.addWidget(self.expTOC, 2, 1) # Chapter Settings - self.guiChapters = QGroupBox("Chapter Heading", self) + self.guiChapters = QGroupBox("Chapter Headings", self) self.guiChaptersForm = QGridLayout(self) self.guiChapters.setLayout(self.guiChaptersForm) self.chapterFormat = QLineEdit() self.chapterFormat.setText(self.optState.getSetting("chFormat")) - self.chapterFormat.setToolTip("Available formats: %num%, %numword%, %title%, %label%") + self.chapterFormat.setToolTip("Available formats: %num%, %numword%, %title%") self.chapterFormat.setMinimumWidth(250) - self.guiChaptersForm.addWidget(QLabel("Numbered"), 0, 0) - self.guiChaptersForm.addWidget(self.chapterFormat, 0, 1) + self.unnumFormat = QLineEdit() + self.unnumFormat.setText(self.optState.getSetting("unFormat")) + self.unnumFormat.setToolTip("Available formats: %title%") + self.unnumFormat.setMinimumWidth(250) + + self.guiChaptersForm.addWidget(QLabel("Numbered"), 0, 0) + self.guiChaptersForm.addWidget(self.chapterFormat, 0, 1) + self.guiChaptersForm.addWidget(QLabel("Unnumbered"), 1, 0) + self.guiChaptersForm.addWidget(self.unnumFormat, 1, 1) # Output Format self.guiOutput = QGroupBox("Output", self) @@ -293,8 +304,8 @@ class GuiExportMain(QWidget): self.guiOutputForm.addWidget(self.outputHelp, 1, 0, 1, 3) self.guiOutputForm.setColumnStretch(2, 1) - # Scene Settings - self.guiScenes = QGroupBox("Scenes", self) + # Scene and Section Settings + self.guiScenes = QGroupBox("Other Headings", self) self.guiScenesForm = QGridLayout(self) self.guiScenes.setLayout(self.guiScenesForm) @@ -303,11 +314,18 @@ class GuiExportMain(QWidget): self.sceneFormat.setToolTip("Available formats: %title%") self.sceneFormat.setMinimumWidth(100) - self.guiScenesForm.addWidget(QLabel("Format"), 0, 0) - self.guiScenesForm.addWidget(self.sceneFormat, 0, 1) + self.sectionFormat = QLineEdit() + self.sectionFormat.setText(self.optState.getSetting("seFormat")) + self.sectionFormat.setToolTip("Available formats: %title%") + self.sectionFormat.setMinimumWidth(100) + + self.guiScenesForm.addWidget(QLabel("Scenes"), 0, 0) + self.guiScenesForm.addWidget(self.sceneFormat, 0, 1) + self.guiScenesForm.addWidget(QLabel("Sections"), 1, 0) + self.guiScenesForm.addWidget(self.sectionFormat, 1, 1) # Output Path - self.exportTo = QGroupBox("Backup", self) + self.exportTo = QGroupBox("Export Folder", self) self.exportToForm = QGridLayout(self) self.exportTo.setLayout(self.exportToForm) @@ -392,16 +410,19 @@ class ExportLastState(): def loadSettings(self): stateFile = path.join(self.theProject.projMeta, nwFiles.EXPORT_OPT) + theState = {} if path.isfile(stateFile): logger.debug("Loading export options file") try: with open(stateFile,mode="r") as inFile: theJson = inFile.read() - self.theState = json.loads(theJson) + theState = json.loads(theJson) except Exception as e: logger.error("Failed to load export options file") logger.error(str(e)) return False + for anOpt in theState: + self.theState[anOpt] = theState[anOpt] return True def saveSettings(self): From 722a00bae36441fa83b1c6f2238cef073acd1451 Mon Sep 17 00:00:00 2001 From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com> Date: Sat, 19 Oct 2019 13:01:35 +0200 Subject: [PATCH 12/14] Finished heading formats and file name in export --- nw/convert/textfile.py | 32 ++++++++--- nw/convert/tokenizer.py | 26 ++++++++- nw/gui/export.py | 120 ++++++++++++++++++++++++++-------------- 3 files changed, 125 insertions(+), 53 deletions(-) diff --git a/nw/convert/textfile.py b/nw/convert/textfile.py index c7bed1ab..621b9dec 100644 --- a/nw/convert/textfile.py +++ b/nw/convert/textfile.py @@ -73,21 +73,37 @@ class TextFile(): self.theConv.setWordWrap(0) return + def setTitleFormat(self, fmtTitle): + self.theConv.setTitleFormat(fmtTitle) + return + + def setChapterFormat(self, fmtChapter): + self.theConv.setChapterFormat(fmtChapter) + return + + def setUnNumberedFormat(self, fmtUnNum): + self.theConv.setUnNumberedFormat(fmtUnNum) + return + + def setSceneFormat(self, fmtScene): + self.theConv.setSceneFormat(fmtScene) + return + + def setSectionFormat(self, fmtSection): + self.theConv.setSectionFormat(fmtSection) + return + ## # Core Methods ## - def openFile(self, saveTo, baseName): + def openFile(self, filePath): - fileName = "%s.%s" % (baseName.strip(),self.fileExt) - filePath = path.join(saveTo,fileName) - - self.fileName = fileName - - if path.isfile(filePath)and self.mainConf.showGUI: + self.fileName = path.basename(filePath) + if path.isfile(filePath) and self.mainConf.showGUI: msgBox = QMessageBox() msgRes = msgBox.question( - self.theParent, "Overwrite", ("File '%s' already exists.
Do you want to overwrite it?" % fileName) + self.theParent, "Overwrite", ("File '%s' already exists.
Do you want to overwrite it?" % self.fileName) ) if msgRes != QMessageBox.Yes: return False diff --git a/nw/convert/tokenizer.py b/nw/convert/tokenizer.py index 50166e27..c30734bb 100644 --- a/nw/convert/tokenizer.py +++ b/nw/convert/tokenizer.py @@ -65,8 +65,8 @@ class Tokenizer(): self.doCommands = False self.fmtTitle = "%title%" - self.fmtUnNum = "%title%" self.fmtChapter = "Chapter %numword%: %title%" + self.fmtUnNum = "%title%" self.fmtScene = "* * *" self.fmtSection = "%title%" @@ -96,6 +96,26 @@ class Tokenizer(): self.wordWrap = 0 return + def setTitleFormat(self, fmtTitle): + self.fmtTitle = fmtTitle + return + + def setChapterFormat(self, fmtChapter): + self.fmtChapter = fmtChapter + return + + def setUnNumberedFormat(self, fmtUnNum): + self.fmtUnNum = fmtUnNum + return + + def setSceneFormat(self, fmtScene): + self.fmtScene = fmtScene + return + + def setSectionFormat(self, fmtSection): + self.fmtSection = fmtSection + return + ## # Class Methods ## @@ -236,8 +256,10 @@ class Tokenizer(): tTemp = self._doFormatSection(tText) if tTemp == "": self.theTokens[n] = (self.T_EMPTY,"",None,self.A_LEFT) - else: + elif tTemp == self.fmtSection: self.theTokens[n] = (self.T_SEP,tTemp,None,self.A_LEFT) + else: + self.theTokens[n] = (tType,tTemp,None,self.A_LEFT) # For title page and partitions, we need to centre all text if isTitle or isPart: diff --git a/nw/gui/export.py b/nw/gui/export.py index 566a6a9f..2010bd84 100644 --- a/nw/gui/export.py +++ b/nw/gui/export.py @@ -102,6 +102,10 @@ class GuiExport(QDialog): wNotes = self.tabMain.expNotes.isChecked() eFormat = self.tabMain.outputFormat.currentData() wComments = self.tabMain.outputComments.isChecked() + chFormat = self.tabMain.chapterFormat.text() + unFormat = self.tabMain.unnumFormat.text() + scFormat = self.tabMain.sceneFormat.text() + seFormat = self.tabMain.sectionFormat.text() saveTo = self.tabMain.exportPath.text() nItems = len(self.theProject.treeOrder) @@ -120,10 +124,14 @@ class GuiExport(QDialog): if outFile is None: return False - if outFile.openFile(saveTo,"testfile"): + if outFile.openFile(saveTo): outFile.setComments(wComments) outFile.setExportNovel(wNovel) outFile.setExportNotes(wNotes) + outFile.setChapterFormat(chFormat) + outFile.setUnNumberedFormat(unFormat) + outFile.setSceneFormat(scFormat) + outFile.setSectionFormat(seFormat) else: self.exportStatus.setText("Failed to open file for writing ...") return False @@ -193,6 +201,14 @@ class GuiExportMain(QWidget): FMT_EBOOK = 4 FMT_ODT = 5 FMT_TEX = 6 + FMT_EXT = { + FMT_TXT : ".txt", + FMT_MD : ".md", + FMT_HTML : ".htm", + FMT_EBOOK : ".htm", + FMT_ODT : ".odt", + FMT_TEX : ".tex", + } FMT_HELP = { FMT_TXT : ( "Exports a plain text file. " @@ -228,6 +244,7 @@ class GuiExportMain(QWidget): self.theTheme = theParent.theTheme self.outerBox = QGridLayout() self.optState = optState + self.currFormat = self.FMT_TXT # Select Files self.guiFiles = QGroupBox("Export Files", self) @@ -271,39 +288,6 @@ class GuiExportMain(QWidget): self.guiChaptersForm.addWidget(QLabel("Unnumbered"), 1, 0) self.guiChaptersForm.addWidget(self.unnumFormat, 1, 1) - # Output Format - self.guiOutput = QGroupBox("Output", self) - self.guiOutputForm = QGridLayout(self) - self.guiOutput.setLayout(self.guiOutputForm) - - self.outputComments = QCheckBox("include comments", self) - self.outputComments.setChecked(self.optState.getSetting("wComments")) - - self.outputHelp = QLabel("") - self.outputHelp.setWordWrap(True) - self.outputHelp.setMinimumHeight(55) - self.outputHelp.setAlignment(Qt.AlignTop) - - self.outputFormat = QComboBox(self) - self.outputFormat.addItem("Plain Text", self.FMT_TXT) - # self.outputFormat.addItem("Markdown", self.FMT_MD) - # self.outputFormat.addItem("HTML5 (Plain)", self.FMT_HTML) - # self.outputFormat.addItem("HTML5 (eBook)", self.FMT_EBOOK) - # self.outputFormat.addItem("Open Document", self.FMT_ODT) - # self.outputFormat.addItem("LaTeX (PDF)", self.FMT_TEX) - self.outputFormat.currentIndexChanged.connect(self._updateFormatHelp) - - optIdx = self.outputFormat.findData(self.optState.getSetting("eFormat")) - if optIdx != -1: - self.outputFormat.setCurrentIndex(optIdx) - self._updateFormatHelp(optIdx) - - self.guiOutputForm.addWidget(QLabel("Export format"), 0, 0) - self.guiOutputForm.addWidget(self.outputFormat, 0, 1) - self.guiOutputForm.addWidget(self.outputComments, 0, 2) - self.guiOutputForm.addWidget(self.outputHelp, 1, 0, 1, 3) - self.guiOutputForm.setColumnStretch(2, 1) - # Scene and Section Settings self.guiScenes = QGroupBox("Other Headings", self) self.guiScenesForm = QGridLayout(self) @@ -338,6 +322,39 @@ class GuiExportMain(QWidget): self.exportToForm.addWidget(self.exportPath, 0, 1) self.exportToForm.addWidget(self.exportGetPath, 0, 2) + # Output Format + self.guiOutput = QGroupBox("Output", self) + self.guiOutputForm = QGridLayout(self) + self.guiOutput.setLayout(self.guiOutputForm) + + self.outputComments = QCheckBox("include comments", self) + self.outputComments.setChecked(self.optState.getSetting("wComments")) + + self.outputHelp = QLabel("") + self.outputHelp.setWordWrap(True) + self.outputHelp.setMinimumHeight(55) + self.outputHelp.setAlignment(Qt.AlignTop) + + self.outputFormat = QComboBox(self) + self.outputFormat.addItem("Plain Text", self.FMT_TXT) + # self.outputFormat.addItem("Markdown", self.FMT_MD) + # self.outputFormat.addItem("HTML5 (Plain)", self.FMT_HTML) + # self.outputFormat.addItem("HTML5 (eBook)", self.FMT_EBOOK) + # self.outputFormat.addItem("Open Document", self.FMT_ODT) + # self.outputFormat.addItem("LaTeX (PDF)", self.FMT_TEX) + self.outputFormat.currentIndexChanged.connect(self._updateFormat) + + optIdx = self.outputFormat.findData(self.optState.getSetting("eFormat")) + if optIdx != -1: + self.outputFormat.setCurrentIndex(optIdx) + self._updateFormat(optIdx) + + self.guiOutputForm.addWidget(QLabel("Export format"), 0, 0) + self.guiOutputForm.addWidget(self.outputFormat, 0, 1) + self.guiOutputForm.addWidget(self.outputComments, 0, 2) + self.guiOutputForm.addWidget(self.outputHelp, 1, 0, 1, 3) + self.guiOutputForm.setColumnStretch(2, 1) + # Assemble self.outerBox.addWidget(self.guiFiles, 0, 0) self.outerBox.addWidget(self.guiOutput, 0, 1, 1, 2) @@ -355,15 +372,15 @@ class GuiExportMain(QWidget): # Internal Functions ## - def _updateFormatHelp(self, currIdx): - """Update help text under output format selection. + def _updateFormat(self, currIdx): + """Update help text under output format selection and file extension in file box """ if currIdx == -1: self.outputHelp.setText("") else: - fmtIdx = self.outputFormat.itemData(currIdx) - self.outputHelp.setText("%s" % self.FMT_HELP[fmtIdx]) - + self.currFormat = self.outputFormat.itemData(currIdx) + self.outputHelp.setText("%s" % self.FMT_HELP[self.currFormat]) + self._checkFileExtension() return def _exportFolder(self): @@ -372,18 +389,35 @@ class GuiExportMain(QWidget): if not path.isdir(currDir): currDir = "" + extFilter = [ + "Text files (*.txt)", + "Markdown files (*.md)", + "HTML files (*.htm *.html)", + "Open document files (*.odt)", + "LaTeX files (*.tex)", + ] + dlgOpt = QFileDialog.Options() dlgOpt |= QFileDialog.ShowDirsOnly dlgOpt |= QFileDialog.DontUseNativeDialog - newDir = QFileDialog.getExistingDirectory( - self,"Export Directory",currDir,options=dlgOpt + saveTo = QFileDialog.getSaveFileName( + self,"Export File",self.exportPath.text(),options=dlgOpt,filter=";;".join(extFilter) ) - if newDir: - self.exportPath.setText(newDir) + if saveTo: + self.exportPath.setText(saveTo[0]) + self._checkFileExtension() return True return False + def _checkFileExtension(self): + saveTo = self.exportPath.text() + fileBits = path.splitext(saveTo) + if self.currFormat > 0: + saveTo = fileBits[0]+self.FMT_EXT[self.currFormat] + self.exportPath.setText(saveTo) + return + # END Class GuiExportMain class ExportLastState(): From adffe2f4daa911d9fdd333f9397a621f00d284d2 Mon Sep 17 00:00:00 2001 From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com> Date: Sat, 19 Oct 2019 13:39:41 +0200 Subject: [PATCH 13/14] Cleaned up export GUI --- nw/gui/export.py | 85 +++++++++++++++++++++++++++++------------------- 1 file changed, 51 insertions(+), 34 deletions(-) diff --git a/nw/gui/export.py b/nw/gui/export.py index 2010bd84..e79ce681 100644 --- a/nw/gui/export.py +++ b/nw/gui/export.py @@ -21,7 +21,7 @@ from PyQt5.QtCore import Qt, QSize from PyQt5.QtSvg import QSvgWidget from PyQt5.QtWidgets import ( QDialog, QHBoxLayout, QVBoxLayout, QWidget, QTabWidget, QGridLayout, QGroupBox, QCheckBox, - QLabel, QComboBox, QLineEdit, QPushButton, QFileDialog, QProgressBar + QLabel, QComboBox, QLineEdit, QPushButton, QFileDialog, QProgressBar, QSpinBox ) from nw.project.document import NWDoc @@ -101,7 +101,8 @@ class GuiExport(QDialog): wNovel = self.tabMain.expNovel.isChecked() wNotes = self.tabMain.expNotes.isChecked() eFormat = self.tabMain.outputFormat.currentData() - wComments = self.tabMain.outputComments.isChecked() + fixWidth = self.tabMain.fixedWidth.value() + wComments = self.tabMain.expComments.isChecked() chFormat = self.tabMain.chapterFormat.text() unFormat = self.tabMain.unnumFormat.text() scFormat = self.tabMain.sceneFormat.text() @@ -128,6 +129,7 @@ class GuiExport(QDialog): outFile.setComments(wComments) outFile.setExportNovel(wNovel) outFile.setExportNotes(wNotes) + outFile.setWordWrap(fixWidth) outFile.setChapterFormat(chFormat) outFile.setUnNumberedFormat(unFormat) outFile.setSceneFormat(scFormat) @@ -166,9 +168,9 @@ class GuiExport(QDialog): wNovel = self.tabMain.expNovel.isChecked() wNotes = self.tabMain.expNotes.isChecked() - wTOC = self.tabMain.expTOC.isChecked() eFormat = self.tabMain.outputFormat.currentData() - wComments = self.tabMain.outputComments.isChecked() + fixWidth = self.tabMain.fixedWidth.value() + wComments = self.tabMain.expComments.isChecked() chFormat = self.tabMain.chapterFormat.text() unFormat = self.tabMain.unnumFormat.text() scFormat = self.tabMain.sceneFormat.text() @@ -177,8 +179,8 @@ class GuiExport(QDialog): self.optState.setSetting("wNovel", wNovel) self.optState.setSetting("wNotes", wNotes) - self.optState.setSetting("wTOC", wTOC) self.optState.setSetting("eFormat", eFormat) + self.optState.setSetting("fixWidth", fixWidth) self.optState.setSetting("wComments",wComments) self.optState.setSetting("chFormat", chFormat) self.optState.setSetting("unFormat", unFormat) @@ -247,26 +249,28 @@ class GuiExportMain(QWidget): self.currFormat = self.FMT_TXT # Select Files - self.guiFiles = QGroupBox("Export Files", self) + self.guiFiles = QGroupBox("Selection", self) self.guiFilesForm = QGridLayout(self) self.guiFiles.setLayout(self.guiFilesForm) self.expNovel = QCheckBox(self) - self.expNotes = QCheckBox(self) - self.expTOC = QCheckBox(self) - self.expNovel.setToolTip("Include all novel files in the exported document") - self.expNotes.setToolTip("Include all note files in the exported document") - self.expTOC.setToolTip("Generate a Table of Contents (ToC)") self.expNovel.setChecked(self.optState.getSetting("wNovel")) + self.expNovel.setToolTip("Include all novel files in the exported document") + + self.expNotes = QCheckBox(self) self.expNotes.setChecked(self.optState.getSetting("wNotes")) - self.expTOC.setChecked(self.optState.getSetting("wTOC")) + self.expNotes.setToolTip("Include all note files in the exported document") + + self.expComments = QCheckBox(self) + self.expComments.setChecked(self.optState.getSetting("wComments")) + self.expComments.setToolTip("Export comments from all files") self.guiFilesForm.addWidget(QLabel("Novel files"), 0, 0) self.guiFilesForm.addWidget(self.expNovel, 0, 1) self.guiFilesForm.addWidget(QLabel("Note files"), 1, 0) self.guiFilesForm.addWidget(self.expNotes, 1, 1) - self.guiFilesForm.addWidget(QLabel("ToC"), 2, 0) - self.guiFilesForm.addWidget(self.expTOC, 2, 1) + self.guiFilesForm.addWidget(QLabel("Comments"), 2, 0) + self.guiFilesForm.addWidget(self.expComments, 2, 1) # Chapter Settings self.guiChapters = QGroupBox("Chapter Headings", self) @@ -323,25 +327,22 @@ class GuiExportMain(QWidget): self.exportToForm.addWidget(self.exportGetPath, 0, 2) # Output Format - self.guiOutput = QGroupBox("Output", self) + self.guiOutput = QGroupBox("Export", self) self.guiOutputForm = QGridLayout(self) self.guiOutput.setLayout(self.guiOutputForm) - self.outputComments = QCheckBox("include comments", self) - self.outputComments.setChecked(self.optState.getSetting("wComments")) - self.outputHelp = QLabel("") self.outputHelp.setWordWrap(True) self.outputHelp.setMinimumHeight(55) self.outputHelp.setAlignment(Qt.AlignTop) self.outputFormat = QComboBox(self) - self.outputFormat.addItem("Plain Text", self.FMT_TXT) - # self.outputFormat.addItem("Markdown", self.FMT_MD) - # self.outputFormat.addItem("HTML5 (Plain)", self.FMT_HTML) - # self.outputFormat.addItem("HTML5 (eBook)", self.FMT_EBOOK) - # self.outputFormat.addItem("Open Document", self.FMT_ODT) - # self.outputFormat.addItem("LaTeX (PDF)", self.FMT_TEX) + self.outputFormat.addItem("Plain Text (.txt)", self.FMT_TXT) + # self.outputFormat.addItem("Markdown (.md)", self.FMT_MD) + # self.outputFormat.addItem("HTML5 (.htm)", self.FMT_HTML) + # self.outputFormat.addItem("HTML5 for eBook (.htm)", self.FMT_EBOOK) + # self.outputFormat.addItem("Open Document (.odt)", self.FMT_ODT) + # self.outputFormat.addItem("LaTeX for PDF (.tex)", self.FMT_TEX) self.outputFormat.currentIndexChanged.connect(self._updateFormat) optIdx = self.outputFormat.findData(self.optState.getSetting("eFormat")) @@ -349,18 +350,34 @@ class GuiExportMain(QWidget): self.outputFormat.setCurrentIndex(optIdx) self._updateFormat(optIdx) - self.guiOutputForm.addWidget(QLabel("Export format"), 0, 0) - self.guiOutputForm.addWidget(self.outputFormat, 0, 1) - self.guiOutputForm.addWidget(self.outputComments, 0, 2) - self.guiOutputForm.addWidget(self.outputHelp, 1, 0, 1, 3) + self.guiOutputForm.addWidget(QLabel("Format"), 0, 0) + self.guiOutputForm.addWidget(self.outputFormat, 0, 1) + self.guiOutputForm.addWidget(self.outputHelp, 1, 0, 1, 3) self.guiOutputForm.setColumnStretch(2, 1) + # Additional Settings + self.addSettings = QGroupBox("Additional Settings", self) + self.addSettingsForm = QGridLayout(self) + self.addSettings.setLayout(self.addSettingsForm) + + self.fixedWidth = QSpinBox(self) + self.fixedWidth.setMinimum(0) + self.fixedWidth.setMaximum(999) + self.fixedWidth.setSingleStep(1) + self.fixedWidth.setValue(self.optState.getSetting("fixWidth")) + self.fixedWidth.setToolTip("0 disables the feature. Applies to .txt, .md and .tex files.") + + self.addSettingsForm.addWidget(QLabel("Fixed width"), 0, 0) + self.addSettingsForm.addWidget(self.fixedWidth, 0, 1) + self.addSettingsForm.setColumnStretch(2, 1) + # Assemble - self.outerBox.addWidget(self.guiFiles, 0, 0) - self.outerBox.addWidget(self.guiOutput, 0, 1, 1, 2) + self.outerBox.addWidget(self.guiOutput, 0, 0, 1, 2) + self.outerBox.addWidget(self.guiFiles, 0, 2) self.outerBox.addWidget(self.guiChapters, 1, 0, 1, 2) self.outerBox.addWidget(self.guiScenes, 1, 2) - self.outerBox.addWidget(self.exportTo, 2, 0, 1, 3) + self.outerBox.addWidget(self.addSettings, 2, 0, 1, 3) + self.outerBox.addWidget(self.exportTo, 3, 0, 1, 3) self.outerBox.setColumnStretch(0, 1) self.outerBox.setColumnStretch(1, 1) self.outerBox.setColumnStretch(2, 1) @@ -427,8 +444,8 @@ class ExportLastState(): self.theState = { "wNovel" : True, "wNotes" : False, - "wTOC" : True, "eFormat" : 2, + "fixWidth" : 80, "wComments" : False, "chFormat" : "Chapter %numword%", "unFormat" : "%title%", @@ -437,8 +454,8 @@ class ExportLastState(): "saveTo" : "", } self.stringOpt = ("chFormat","unFormat","scFormat","seFormat","saveTo") - self.boolOpt = ("wNovel","wNotes","wTOC","wComments") - self.intOpt = ("eFormat") + self.boolOpt = ("wNovel","wNotes","wComments") + self.intOpt = ("eFormat","fixWidth") self.loadSettings() return From 9730a136574c17a652aedba5dd2d2939fbc3301c Mon Sep 17 00:00:00 2001 From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com> Date: Sat, 19 Oct 2019 13:40:08 +0200 Subject: [PATCH 14/14] Updated sample text to help with testing exports --- .../sampleNovel/data_6/36b6aa9b697b_main.nwd | 4 +- .../sampleNovel/data_6/a2d6d5f4f401_main.nwd | 5 ++ .../sampleNovel/data_8/8706ddc78b1b_main.nwd | 5 ++ .../sampleNovel/data_a/e7339df26ded_main.nwd | 23 ++++++ .../sampleNovel/data_b/c0cbd2a407f3_main.nwd | 6 +- sample/sampleNovel/nwProject.nwx | 80 ++++++++++++++----- 6 files changed, 96 insertions(+), 27 deletions(-) create mode 100644 sample/sampleNovel/data_6/a2d6d5f4f401_main.nwd create mode 100644 sample/sampleNovel/data_8/8706ddc78b1b_main.nwd create mode 100644 sample/sampleNovel/data_a/e7339df26ded_main.nwd diff --git a/sample/sampleNovel/data_6/36b6aa9b697b_main.nwd b/sample/sampleNovel/data_6/36b6aa9b697b_main.nwd index e4c4beb8..0a53e976 100644 --- a/sample/sampleNovel/data_6/36b6aa9b697b_main.nwd +++ b/sample/sampleNovel/data_6/36b6aa9b697b_main.nwd @@ -1,6 +1,4 @@ -# This is the Title - -## This is the Subtitle +### Making a Scene % Begin Meta @pov: Jane diff --git a/sample/sampleNovel/data_6/a2d6d5f4f401_main.nwd b/sample/sampleNovel/data_6/a2d6d5f4f401_main.nwd new file mode 100644 index 00000000..c522a4da --- /dev/null +++ b/sample/sampleNovel/data_6/a2d6d5f4f401_main.nwd @@ -0,0 +1,5 @@ +## So it Begins + +@pov: Jane + +% The first chapter with much action and such. \ No newline at end of file diff --git a/sample/sampleNovel/data_8/8706ddc78b1b_main.nwd b/sample/sampleNovel/data_8/8706ddc78b1b_main.nwd new file mode 100644 index 00000000..e8d20166 --- /dev/null +++ b/sample/sampleNovel/data_8/8706ddc78b1b_main.nwd @@ -0,0 +1,5 @@ +## Where has John Gone? + +@pov: Jane + +% We continue the saga of John and Jane \ No newline at end of file diff --git a/sample/sampleNovel/data_a/e7339df26ded_main.nwd b/sample/sampleNovel/data_a/e7339df26ded_main.nwd new file mode 100644 index 00000000..71020309 --- /dev/null +++ b/sample/sampleNovel/data_a/e7339df26ded_main.nwd @@ -0,0 +1,23 @@ +### We Found John! + +@pov: John + +Jane has been searching for a while, and she finally found John on Mars. He was indeed in space! What was he doing on Mars anyway? Well, it turns out, he was farming potatoes. + +Farming potatoes you say? Why would you do that? + +No one knows, but it seemed like a good idea at the time I suppose. + +### A Note on Potato Farming on Mars + +@pov: John + +% We’re adding a second scene to the same file here, which is perfectly fine I may add. + +Potatoes cannot be farmed on Mars. That is simply a fact. There is no soil. Unless John brings the soil himself. Wait, did he? + +I don’t want to know. + +#### Conclusion + +Why would anyone write about such things? let alone make a film about it? \ No newline at end of file diff --git a/sample/sampleNovel/data_b/c0cbd2a407f3_main.nwd b/sample/sampleNovel/data_b/c0cbd2a407f3_main.nwd index 0a39ca10..525f47c8 100644 --- a/sample/sampleNovel/data_b/c0cbd2a407f3_main.nwd +++ b/sample/sampleNovel/data_b/c0cbd2a407f3_main.nwd @@ -1,6 +1,8 @@ -# This is a New File! +### Another Scene @pov: John @location: Space -Although, not so new now that it has text in it an everything … \ No newline at end of file +This is the second scene in out story. We have no idea what’s going on, so we’re just going to ramble on until we have a few lines of text so that the editor has something to work with. + +In fact, this scene is supposed to be about John, but we don’t really know anything about John, except that he is somewhere in space. Perhaps he’s lost? Or has gone where no man has gone before? Where is Jane then? \ No newline at end of file diff --git a/sample/sampleNovel/nwProject.nwx b/sample/sampleNovel/nwProject.nwx index e0347637..dba1732b 100644 --- a/sample/sampleNovel/nwProject.nwx +++ b/sample/sampleNovel/nwProject.nwx @@ -1,5 +1,5 @@ - + Sample Project Sample Project @@ -11,7 +11,7 @@ True 636b6aa9b697b 636b6aa9b697b - 581 + 758 B E @@ -33,7 +33,7 @@ Main - + Novel ROOT @@ -51,7 +51,7 @@ 23 5 1 - 0 + 16 Some Chapter @@ -60,41 +60,77 @@ Notes True - - New Scene + + Chapter One + FILE + NOVEL + New + False + CHAPTER + 12 + 3 + 0 + 75 + + + Making a Scene + FILE + NOVEL + Notes + False + SCENE + 713 + 132 + 6 + 85 + + + Another Scene + FILE + NOVEL + Notes + False + SCENE + 412 + 82 + 2 + 448 + + + A Note on Ipsums FILE NOVEL Started False - SCENE + NOTE 2571 377 5 0 - - File With Stuff + + Chapter Two FILE NOVEL - Notes + New False - SCENE - 736 - 137 - 6 - 82 + CHAPTER + 20 + 4 + 0 + 76 - - New File + + We Found John! FILE NOVEL - Notes + New False SCENE - 82 - 19 - 1 - 69 + 567 + 112 + 6 + 634 Characters