" % retText
def _formatComments(self, tText):
+ """Apply HTML formatting to comments.
+ """
if not self.forPreview:
return "
%s
\n" % tText
diff --git a/nw/core/tokenizer.py b/nw/core/tokenizer.py
index 4ac99377..f9b0264c 100644
--- a/nw/core/tokenizer.py
+++ b/nw/core/tokenizer.py
@@ -70,30 +70,45 @@ class Tokenizer():
self.theProject = theProject
self.theParent = theParent
- self.theText = None
- self.theHandle = None
- self.theItem = None
- self.theTokens = None
- self.theResult = None
+ # Data Variables
+ self.theText = None # The raw text to be tokenized
+ self.theHandle = None # The handle associated with the text
+ self.theItem = None # The NWItem associated with the handle
+ self.theTokens = None # The list of the processed tokens
+ self.theResult = None # The result text after conversion
- self.wordWrap = 0
- self.doComments = False
- self.doKeywords = False
+ # User Settings
+ self.doComments = False # Also process comments
+ self.doKeywords = False # Also process keywords like tags and references
- self.fmtTitle = "%title%"
- self.fmtChapter = "%title%"
- self.fmtUnNum = "%title%"
- self.fmtScene = "%title%"
- self.fmtSection = "%title%"
+ self.fmtTitle = "%title%" # Formatting for titles
+ self.fmtChapter = "%title%" # Formatting for numbered chapters
+ self.fmtUnNum = "%title%" # Formatting for unnumbered chapters
+ self.fmtScene = "%title%" # Formatting for scenes
+ self.fmtSection = "%title%" # Formatting for sections
- self.hideScene = False
- self.hideSection = False
+ self.hideScene = False # Do not include scene headers
+ self.hideSection = False # Do not include section headers
- self.numChapter = 0
- self.firstScene = False
+ # Instance Variables
+ self.numChapter = 0 # Counter for chapter numbers
+ self.firstScene = False # Flag to indicate that the first scene of the chapter
return
+ def clearData(self):
+ """Clear the data arrays and variables, but not settings, so the class
+ can be reused for multiple documents.
+ """
+ self.theText = None
+ self.theHandle = None
+ self.theItem = None
+ self.theTokens = None
+ self.theResult = None
+ self.numChapter = 0
+ self.firstScene = False
+ return
+
##
# Setters
##
@@ -106,13 +121,6 @@ class Tokenizer():
self.doKeywords = doKeywords
return
- def setWordWrap(self, wordWrap):
- if wordWrap >= 0:
- self.wordWrap = wordWrap
- else:
- self.wordWrap = 0
- return
-
def setTitleFormat(self, fmtTitle):
self.fmtTitle = fmtTitle
return
@@ -140,6 +148,9 @@ class Tokenizer():
##
def setText(self, theHandle, theText=None):
+ """Set the text for the tokenizer from a handle. If theText is
+ not set, load it from the file.
+ """
self.theHandle = theHandle
self.theItem = self.theProject.projTree[theHandle]
@@ -155,12 +166,16 @@ class Tokenizer():
return
def doAutoReplace(self):
+ """Run through the user's auto-replace dictionary.
+ """
+
if len(self.theProject.autoReplace) > 0:
repDict = {}
for aKey, aVal in self.theProject.autoReplace.items():
repDict["<%s>" % aKey] = aVal
xRep = re.compile("|".join([re.escape(k) for k in repDict.keys()]), flags=re.DOTALL)
self.theText = xRep.sub(lambda x: repDict[x.group(0)], self.theText)
+
return
def doPostProcessing(self):
@@ -229,6 +244,9 @@ class Tokenizer():
return
def doHeaders(self):
+ """Apply formatting to the text headers according to document
+ layout and user settings.
+ """
isNone = self.theItem.itemLayout == nwItemLayout.NO_LAYOUT
isTitle = self.theItem.itemLayout == nwItemLayout.TITLE
@@ -241,8 +259,8 @@ class Tokenizer():
isNote = self.theItem.itemLayout == nwItemLayout.NOTE
# No special header formatting for notes and no-layout files
- if isNone: return
- if isNote: return
+ if isNone or isNote:
+ return
# For novel files, we need to handle chapter numbering and scene
# breaks
@@ -259,12 +277,12 @@ class Tokenizer():
elif tType == self.T_HEAD2:
if not isUnNum:
self.numChapter += 1
- tText = self._doFormatChapter(tText,isUnNum)
+ tText = self._formatChapter(tText,isUnNum)
self.theTokens[n] = (tType,tText,None,self.A_LEFT)
self.firstScene = True
elif tType == self.T_HEAD3:
- tTemp = self._doFormatScene(tText)
+ tTemp = self._formatScene(tText)
if tTemp == "" and self.hideScene:
self.theTokens[n] = (self.T_EMPTY,"",None,self.A_LEFT)
elif tTemp == "" and not self.hideScene:
@@ -282,7 +300,7 @@ class Tokenizer():
self.firstScene = False
elif tType == self.T_HEAD4:
- tTemp = self._doFormatSection(tText)
+ tTemp = self._formatSection(tText)
if tTemp == "" and self.hideSection:
self.theTokens[n] = (self.T_EMPTY,"",None,self.A_LEFT)
elif tTemp == "" and not self.hideSection:
@@ -310,12 +328,16 @@ class Tokenizer():
# Internal Functions
##
- def _doFormatTitle(self, theText):
+ def _formatTitle(self, theText):
+ """Replace tokens for headers level 1.
+ """
theTitle = self.fmtTitle
theTitle = theTitle.replace("%title%", theText)
return theTitle
- def _doFormatChapter(self, theText, noNum):
+ def _formatChapter(self, theText, noNum):
+ """Replace tokens for headers level 2.
+ """
if noNum:
theTitle = self.fmtUnNum
theTitle = theTitle.replace("%title%", theText)
@@ -326,20 +348,18 @@ class Tokenizer():
theTitle = theTitle.replace("%numword%", numberToWord(self.numChapter,"en"))
return theTitle
- def _doFormatScene(self, theText):
+ def _formatScene(self, theText):
+ """Replace tokens for headers level 3.
+ """
theTitle = self.fmtScene
theTitle = theTitle.replace("%title%", theText)
return theTitle
- def _doFormatSection(self, theText):
+ def _formatSection(self, theText):
+ """Replace tokens for headers level 4.
+ """
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
From cf3fc2ef4f7591b31e93b6258b9070c84f6ff37b Mon Sep 17 00:00:00 2001
From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com>
Date: Sun, 10 May 2020 15:57:50 +0200
Subject: [PATCH 09/53] Added title formatting settings to project file
---
nw/core/project.py | 35 +++++++++++++++++++++++++++++++----
1 file changed, 31 insertions(+), 4 deletions(-)
diff --git a/nw/core/project.py b/nw/core/project.py
index 7011a7da..c2ce2902 100644
--- a/nw/core/project.py
+++ b/nw/core/project.py
@@ -80,10 +80,9 @@ class NWProject():
self.bookTitle = "" # The final title; should only be used for exports
self.bookAuthors = [] # A list of book authors
- # Various
- self.autoReplace = {} # Text to auto-replace on exports
-
# Project Settings
+ self.autoReplace = {} # Text to auto-replace on exports
+ self.titleFormat = {} # The formatting of titles for exports
self.spellCheck = False # Controls the spellcheck-as-you-type feature
self.autoOutline = True # If true, the Project Outline is updated automatically
self.statusItems = None # Novel file progress status values
@@ -200,6 +199,15 @@ class NWProject():
self.bookTitle = ""
self.bookAuthors = []
self.autoReplace = {}
+ self.titleFormat = {
+ "title": "%title%",
+ "chapter": "Chapter %num%",
+ "chapterSub": "%title%",
+ "unnumbered": "%title%",
+ "scene": "",
+ "sceneSep": "* * *",
+ "section": "",
+ }
self.spellCheck = False
self.autoOutline = True
self.statusItems = NWStatus()
@@ -347,6 +355,11 @@ class NWProject():
elif xItem.tag == "autoReplace":
for xEntry in xItem:
self.autoReplace[xEntry.tag] = checkString(xEntry.text, None, False)
+ elif xItem.tag == "titleFormat":
+ titleFormat = self.titleFormat.copy()
+ for xEntry in xItem:
+ titleFormat[xEntry.tag] = checkString(xEntry.text, None, False)
+ self.setTitleFormat(titleFormat)
elif xChild.tag == "content":
logger.debug("Found project content")
self.projTree.unpackXML(xChild)
@@ -417,10 +430,16 @@ class NWProject():
self._packProjectValue(xSettings, "lastEdited", self.lastEdited)
self._packProjectValue(xSettings, "lastViewed", self.lastViewed)
self._packProjectValue(xSettings, "lastWordCount", self.currWCount)
+
xAutoRep = etree.SubElement(xSettings, "autoReplace")
for aKey, aValue in self.autoReplace.items():
if len(aKey) > 0:
- self._packProjectValue(xAutoRep,aKey,aValue)
+ self._packProjectValue(xAutoRep, aKey, aValue)
+
+ xTitleFmt = etree.SubElement(xSettings, "titleFormat")
+ for aKey, aValue in self.titleFormat.items():
+ if len(aKey) > 0:
+ self._packProjectValue(xTitleFmt, aKey, aValue)
xStatus = etree.SubElement(xSettings,"status")
self.statusItems.packEntries(xStatus)
@@ -695,6 +714,14 @@ class NWProject():
self.autoReplace = autoReplace
return
+ def setTitleFormat(self, titleFormat):
+ """Set the formatting of titles in the project.
+ """
+ for valKey in titleFormat:
+ if valKey in self.titleFormat:
+ self.titleFormat[valKey] = titleFormat[valKey]
+ return
+
def setProjectChanged(self, bValue):
"""Toggle the project changed flag, and propagate the
information to the GUI statusbar.
From 5ab1402854d94b034c92ff2713f5cf6aba0d1c25 Mon Sep 17 00:00:00 2001
From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com>
Date: Sun, 10 May 2020 16:12:47 +0200
Subject: [PATCH 10/53] Fix tests
---
nw/core/project.py | 2 +-
sample/sampleNovel/nwProject.nwx | 11 ++++++++++-
tests/reference/gui/0_nwProject.nwx | 17 ++++++++++++++++-
tests/reference/gui/1_nwProject.nwx | 20 +++++++++++++++++++-
tests/reference/gui/2_nwProject.nwx | 17 ++++++++++++++++-
tests/reference/gui/3_nwProject.nwx | 17 ++++++++++++++++-
tests/reference/proj/1_nwProject.nwx | 17 ++++++++++++++++-
tests/reference/proj/2_nwProject.nwx | 21 ++++++++++++++++++++-
tests/test_gui.py | 6 +++---
tests/test_item.py | 3 ++-
10 files changed, 119 insertions(+), 12 deletions(-)
diff --git a/nw/core/project.py b/nw/core/project.py
index c2ce2902..8986aeb3 100644
--- a/nw/core/project.py
+++ b/nw/core/project.py
@@ -358,7 +358,7 @@ class NWProject():
elif xItem.tag == "titleFormat":
titleFormat = self.titleFormat.copy()
for xEntry in xItem:
- titleFormat[xEntry.tag] = checkString(xEntry.text, None, False)
+ titleFormat[xEntry.tag] = checkString(xEntry.text, "", False)
self.setTitleFormat(titleFormat)
elif xChild.tag == "content":
logger.debug("Found project content")
diff --git a/sample/sampleNovel/nwProject.nwx b/sample/sampleNovel/nwProject.nwx
index 68ac9522..c911b0a3 100644
--- a/sample/sampleNovel/nwProject.nwx
+++ b/sample/sampleNovel/nwProject.nwx
@@ -1,5 +1,5 @@
-
+Sample ProjectSample Project
@@ -18,6 +18,15 @@
ED
+
+ %title%
+ Chapter %num%
+ %title%
+ %title%
+ None
+ * * *
+ None
+ NewNotes
diff --git a/tests/reference/gui/0_nwProject.nwx b/tests/reference/gui/0_nwProject.nwx
index e5368533..d91e4c30 100644
--- a/tests/reference/gui/0_nwProject.nwx
+++ b/tests/reference/gui/0_nwProject.nwx
@@ -1,5 +1,5 @@
-
+
@@ -12,6 +12,15 @@
None0
+
+ %title%
+ Chapter %num%
+ %title%
+ %title%
+
+ * * *
+
+ NewNote
@@ -32,6 +41,7 @@
NOVELNewFalse
+ TrueNew Chapter
@@ -39,6 +49,7 @@
NOVELNewFalse
+ TrueNew Scene
@@ -46,6 +57,7 @@
NOVELNewFalse
+ TrueSCENE00
@@ -58,6 +70,7 @@
CHARACTERNewFalse
+ TruePlot
@@ -65,6 +78,7 @@
PLOTNewFalse
+ TrueWorld
@@ -72,6 +86,7 @@
WORLDNewFalse
+ True
diff --git a/tests/reference/gui/1_nwProject.nwx b/tests/reference/gui/1_nwProject.nwx
index c55ff681..0cb958d1 100644
--- a/tests/reference/gui/1_nwProject.nwx
+++ b/tests/reference/gui/1_nwProject.nwx
@@ -1,5 +1,5 @@
-
+
@@ -12,6 +12,15 @@
31489056e091686
+
+ %title%
+ Chapter %num%
+ %title%
+ %title%
+
+ * * *
+
+ NewNote
@@ -32,6 +41,7 @@
NOVELNewTrue
+ TrueNew Chapter
@@ -39,6 +49,7 @@
NOVELNewTrue
+ TrueNew Scene
@@ -46,6 +57,7 @@
NOVELNewFalse
+ TrueSCENE33159
@@ -58,6 +70,7 @@
CHARACTERNewTrue
+ TrueNew File
@@ -65,6 +78,7 @@
CHARACTERNewFalse
+ TrueNOTE348
@@ -77,6 +91,7 @@
PLOTNewTrue
+ TrueNew File
@@ -84,6 +99,7 @@
PLOTNewFalse
+ TrueNOTE4810
@@ -96,6 +112,7 @@
WORLDNewTrue
+ TrueNew File
@@ -103,6 +120,7 @@
WORLDNewFalse
+ TrueNOTE519
diff --git a/tests/reference/gui/2_nwProject.nwx b/tests/reference/gui/2_nwProject.nwx
index ca896ec2..0ab86944 100644
--- a/tests/reference/gui/2_nwProject.nwx
+++ b/tests/reference/gui/2_nwProject.nwx
@@ -1,5 +1,5 @@
-
+Project NameProject Title
@@ -16,6 +16,15 @@
With This Stuff
+
+ %title%
+ Chapter %num%
+ %title%
+ %title%
+
+ * * *
+
+ NewNote
@@ -36,6 +45,7 @@
NOVELNewFalse
+ TrueNew Chapter
@@ -43,6 +53,7 @@
NOVELNewFalse
+ TrueNew Scene
@@ -50,6 +61,7 @@
NOVELNewFalse
+ TrueSCENE00
@@ -62,6 +74,7 @@
CHARACTERNewFalse
+ TruePlot
@@ -69,6 +82,7 @@
PLOTNewFalse
+ TrueWorld
@@ -76,6 +90,7 @@
WORLDNewFalse
+ True
diff --git a/tests/reference/gui/3_nwProject.nwx b/tests/reference/gui/3_nwProject.nwx
index f0bda720..3d80a294 100644
--- a/tests/reference/gui/3_nwProject.nwx
+++ b/tests/reference/gui/3_nwProject.nwx
@@ -1,5 +1,5 @@
-
+
@@ -12,6 +12,15 @@
None0
+
+ %title%
+ Chapter %num%
+ %title%
+ %title%
+
+ * * *
+
+ NewNote
@@ -32,6 +41,7 @@
NOVELNewFalse
+ TrueNew Chapter
@@ -39,6 +49,7 @@
NOVELNewFalse
+ TrueJust a Page
@@ -46,6 +57,7 @@
NOVELNoteFalse
+ TruePAGE00
@@ -58,6 +70,7 @@
CHARACTERNewFalse
+ TruePlot
@@ -65,6 +78,7 @@
PLOTNewFalse
+ TrueWorld
@@ -72,6 +86,7 @@
WORLDNewFalse
+ True
diff --git a/tests/reference/proj/1_nwProject.nwx b/tests/reference/proj/1_nwProject.nwx
index 319d24b3..76bfb4ae 100644
--- a/tests/reference/proj/1_nwProject.nwx
+++ b/tests/reference/proj/1_nwProject.nwx
@@ -1,5 +1,5 @@
-
+
@@ -12,6 +12,15 @@
None0
+
+ %title%
+ Chapter %num%
+ %title%
+ %title%
+
+ * * *
+
+ NewNote
@@ -32,6 +41,7 @@
NOVELNewFalse
+ TrueCharacters
@@ -39,6 +49,7 @@
CHARACTERNewFalse
+ TruePlot
@@ -46,6 +57,7 @@
PLOTNewFalse
+ TrueWorld
@@ -53,6 +65,7 @@
WORLDNewFalse
+ TrueNew Chapter
@@ -60,6 +73,7 @@
NOVELNewFalse
+ TrueNew Scene
@@ -67,6 +81,7 @@
NOVELNewFalse
+ TrueSCENE00
diff --git a/tests/reference/proj/2_nwProject.nwx b/tests/reference/proj/2_nwProject.nwx
index 92bd9df4..585e7fb3 100644
--- a/tests/reference/proj/2_nwProject.nwx
+++ b/tests/reference/proj/2_nwProject.nwx
@@ -1,5 +1,5 @@
-
+
@@ -12,6 +12,15 @@
None0
+
+ %title%
+ Chapter %num%
+ %title%
+ %title%
+
+ * * *
+
+ NewNote
@@ -32,6 +41,7 @@
NOVELNewFalse
+ TrueCharacters
@@ -39,6 +49,7 @@
CHARACTERNewFalse
+ TruePlot
@@ -46,6 +57,7 @@
PLOTNewFalse
+ TrueWorld
@@ -53,6 +65,7 @@
WORLDNewFalse
+ TrueNew Chapter
@@ -60,6 +73,7 @@
NOVELNewFalse
+ TrueNew Scene
@@ -67,6 +81,7 @@
NOVELNewFalse
+ TrueSCENE00
@@ -79,6 +94,7 @@
TIMELINENewFalse
+ TrueObject
@@ -86,6 +102,7 @@
OBJECTNewFalse
+ TrueCustom1
@@ -93,6 +110,7 @@
CUSTOMNewFalse
+ TrueCustom2
@@ -100,6 +118,7 @@
CUSTOMNewFalse
+ True
diff --git a/tests/test_gui.py b/tests/test_gui.py
index e2291fff..9e2cd53d 100644
--- a/tests/test_gui.py
+++ b/tests/test_gui.py
@@ -13,7 +13,7 @@ from nw.gui.dialogs.itemeditor import GuiItemEditor
from nw.constants import *
-keyDelay = 10
+keyDelay = 5
stepDelay = 50
@pytest.mark.gui
@@ -361,7 +361,7 @@ def testItemEditor(qtbot, nwTempGUI, nwRef, nwTemp):
layoutIdx = itemEdit.editLayout.findData(nwItemLayout.PAGE)
itemEdit.editLayout.setCurrentIndex(layoutIdx)
- qtbot.mouseClick(itemEdit.saveButton, Qt.LeftButton)
+ itemEdit._doSave()
itemEdit = GuiItemEditor(nwGUI, nwGUI.theProject, "31489056e0916")
qtbot.addWidget(itemEdit)
@@ -369,7 +369,7 @@ def testItemEditor(qtbot, nwTempGUI, nwRef, nwTemp):
assert itemEdit.editStatus.currentData() == "Note"
assert itemEdit.editLayout.currentData() == nwItemLayout.PAGE
- qtbot.mouseClick(itemEdit.closeButton, Qt.LeftButton)
+ itemEdit._doClose()
qtbot.wait(stepDelay)
assert nwGUI.saveProject()
diff --git a/tests/test_item.py b/tests/test_item.py
index 64268e62..19f663f6 100644
--- a/tests/test_item.py
+++ b/tests/test_item.py
@@ -197,7 +197,8 @@ def testItemXMLPackUnpack():
assert etree.tostring(xContent, pretty_print=False, encoding="utf-8") == (
b""
b""
- b"A NameTRASHTRASHMainTrue"
+ b"A NameTRASHTRASHMain"
+ b"TrueTrue"
b""
b""
)
From 4d9e2d03a37e81c27967479705aba8cb64201636 Mon Sep 17 00:00:00 2001
From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com>
Date: Sun, 10 May 2020 19:31:53 +0200
Subject: [PATCH 11/53] Done building the buttons etc for BuildNovel dialog
---
nw/assets/text/exportHelp_en.htm | 44 ++++
nw/config.py | 1 +
nw/core/project.py | 23 ++-
nw/core/tokenizer.py | 5 +
nw/gui/build.py | 337 ++++++++++++++++++++++++++++++-
sample/sampleNovel/nwProject.nwx | 13 +-
6 files changed, 400 insertions(+), 23 deletions(-)
create mode 100644 nw/assets/text/exportHelp_en.htm
diff --git a/nw/assets/text/exportHelp_en.htm b/nw/assets/text/exportHelp_en.htm
new file mode 100644
index 00000000..df8eca2a
--- /dev/null
+++ b/nw/assets/text/exportHelp_en.htm
@@ -0,0 +1,44 @@
+
Help!
+
A brief guide to make the most out of the Build Project tool.
+
+
Novel Title Formats
+
The format of the various title levels in the files under the Novel folder can be customised in
+ these settings. The actual title given in the headings of your files will replace all
+ occurrences of the keyword %title%. Any static text will be left as-is in the
+ final title. An empty field means the title isn't written out at all.
+
The available formatting keywords are:
+
%title% – This is replaced with the text you put in your headings in your
+ documents
+
%num% – This is replaced with the chapter number of your chapter type
+ headings. These are generated automaticall starting from 1.
+
%numword% – This is replaced with the chapter number of your chapter type
+ headings, but instead of an arabic number, the word for it is used instead, e.g. One, Two,
+ Fifteen, Twenty-Five, etc.
+
\\ – Two backslashes are replaced by a line break.
+
Note: The Scene format is treated slightly differently than the other title formats. If a
+ scene format is a constant text, that is, contains no %title%, it will be treated
+ as a scene separator instead. Scene separators are centred, and not shown if the chapter starts
+ directly on the first scene. If the field is blank, a large space between the scenes is added
+ instead.
+
+
Build Overrides
+
Novel Outline Mode: This option will build an outline version of the novel rather than the
+ full thing. It overrides the title format settings without changing them. Each title will be
+ written out, and the synopsis text will appear instead of the body text of the files. Some of
+ the other options are still available in Outline Mode.
+
+
Include Non-Text Elements
+
Include Synopsis: This will add the synopsis comment as the first paragraph after each
+ heading.
+
Include Comments: This will include any comments as additional paragraphs in the text.
+
Include Keywords: This will include any keywords and tags as clickable links after each
+ heading.
+
+
Additional Options
+
Include Novel Files: This means all files that don't have a layout of type "Note" will be
+ included. This is the normal mode when exporting the novel itself without the notes.
+
Include Note Files: This means all files with a layout of type "Note" will be
+ included. Titles in note files are always left as they appear.
+
Ignore Export Flag: Each file in the project tree has an "Include when building project"
+ option set, which is indicated by a little check mark in the "Flags" column. Files without This
+ tick will normally be skipped during build, but can be included if this option is enabled.
" % retText
- def _formatComments(self, tText):
- """Apply HTML formatting to comments.
- """
-
- if not self.forPreview:
- return "
%s
\n" % tText
-
- return "
%s
\n" % tText
-
# END Class ToHtml
diff --git a/nw/core/tokenizer.py b/nw/core/tokenizer.py
index 1cf2a40e..a39d12f2 100644
--- a/nw/core/tokenizer.py
+++ b/nw/core/tokenizer.py
@@ -40,29 +40,30 @@ 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 = 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
- T_EMPTY = 1 # Empty line (new paragraph)
- T_COMMENT = 2 # Comment line
- T_KEYWORD = 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
- T_SEP = 9 # Scene separator
- T_SKIP = 10 # Paragraph break
- T_PBREAK = 11 # Page break
+ T_EMPTY = 1 # Empty line (new paragraph)
+ T_SYNOPSIS = 2 # Synopsis comment
+ T_COMMENT = 3 # Comment line
+ T_KEYWORD = 4 # Command line
+ T_HEAD1 = 5 # Header 1 (title)
+ T_HEAD2 = 6 # Header 2 (chapter)
+ T_HEAD3 = 7 # Header 3 (scene)
+ T_HEAD4 = 8 # Header 4
+ T_TEXT = 9 # Text line
+ T_SEP = 10 # Scene separator
+ T_SKIP = 11 # Paragraph break
+ T_PBREAK = 12 # Page break
- A_LEFT = 1 # Left aligned
- A_RIGHT = 2 # Right aligned
- A_CENTRE = 3 # Centred
- A_JUSTIFY = 4 # Justified
+ A_LEFT = 1 # Left aligned
+ A_RIGHT = 2 # Right aligned
+ A_CENTRE = 3 # Centred
+ A_JUSTIFY = 4 # Justified
def __init__(self, theProject, theParent):
@@ -78,8 +79,11 @@ class Tokenizer():
self.theResult = None # The result text after conversion
# User Settings
+ self.doBodyText = True # Include body text
+ self.doSynopsis = False # Also process synopsis comments
self.doComments = False # Also process comments
self.doKeywords = False # Also process keywords like tags and references
+ self.doJustify = False # Justify text
self.fmtTitle = "%title%" # Formatting for titles
self.fmtChapter = "%title%" # Formatting for numbered chapters
@@ -113,14 +117,6 @@ class Tokenizer():
# Setters
##
- def setComments(self, doComments):
- self.doComments = doComments
- return
-
- def setKeywords(self, doKeywords):
- self.doKeywords = doKeywords
- return
-
def setTitleFormat(self, fmtTitle):
self.fmtTitle = fmtTitle
return
@@ -143,6 +139,26 @@ class Tokenizer():
self.hideSection = hideSection
return
+ def setBodyText(self, doBodyText):
+ self.doBodyText = doBodyText
+ return
+
+ def setSynopsis(self, doSynopsis):
+ self.doSynopsis = doSynopsis
+ return
+
+ def setComments(self, doComments):
+ self.doComments = doComments
+ return
+
+ def setKeywords(self, doKeywords):
+ self.doKeywords = doKeywords
+ return
+
+ def setJustify(self, doJustify):
+ self.doJustify = doJustify
+ return
+
##
# Class Methods
##
@@ -207,25 +223,38 @@ class Tokenizer():
[None, self.FMT_U_B, None, self.FMT_U_E]
)]
+ if self.doJustify:
+ defAlign = self.A_JUSTIFY
+ else:
+ defAlign = self.A_LEFT
+
self.theTokens = []
for aLine in self.theText.splitlines():
# Tag lines starting with specific characters
if len(aLine.strip()) == 0:
- self.theTokens.append((self.T_EMPTY,"",None,self.A_LEFT))
+ self.theTokens.append((self.T_EMPTY, "", None, self.A_LEFT))
elif aLine[0] == "%":
- self.theTokens.append((self.T_COMMENT,aLine[1:].strip(),None,self.A_LEFT))
+ cLine = aLine[1:].strip()
+ if cLine.lower().startswith("synopsis:"):
+ self.theTokens.append((self.T_SYNOPSIS, cLine[9:].strip(), None, defAlign))
+ else:
+ self.theTokens.append((self.T_COMMENT, aLine[1:].strip(), None, defAlign))
elif aLine[0] == "@":
- self.theTokens.append((self.T_KEYWORD,aLine[1:].strip(),None,self.A_LEFT))
+ self.theTokens.append((self.T_KEYWORD, aLine[1:].strip(), None, self.A_LEFT))
elif aLine[:2] == "# ":
- self.theTokens.append((self.T_HEAD1,aLine[2:].strip(),None,self.A_LEFT))
+ 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.A_LEFT))
+ 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.A_LEFT))
+ 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.A_LEFT))
+ self.theTokens.append((self.T_HEAD4, aLine[5:].strip(), None, self.A_LEFT))
else:
+ if not self.doBodyText:
+ # Skip all body text
+ continue
+
# Otherwise we use RegEx to find formatting tags within a line of text
fmtPos = []
for theRX, theKeys in rxFormats:
@@ -240,11 +269,11 @@ 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.A_LEFT))
+ fmtPos = sorted(fmtPos, key=itemgetter(0))
+ self.theTokens.append((self.T_TEXT, aLine, fmtPos, defAlign))
# Always add an empty line at the end
- self.theTokens.append((self.T_EMPTY,"",None,self.A_LEFT))
+ self.theTokens.append((self.T_EMPTY, "", None, self.A_LEFT))
return
@@ -283,37 +312,37 @@ class Tokenizer():
if not isUnNum:
self.numChapter += 1
tText = self._formatChapter(tText,isUnNum)
- self.theTokens[n] = (tType,tText,None,self.A_LEFT)
+ self.theTokens[n] = (tType, tText, None, self.A_LEFT)
self.firstScene = True
elif tType == self.T_HEAD3:
tTemp = self._formatScene(tText)
if tTemp == "" and self.hideScene:
- self.theTokens[n] = (self.T_EMPTY,"",None,self.A_LEFT)
+ self.theTokens[n] = (self.T_EMPTY, "", None, self.A_LEFT)
elif tTemp == "" and not self.hideScene:
if self.firstScene:
- self.theTokens[n] = (self.T_EMPTY,"",None,self.A_LEFT)
+ self.theTokens[n] = (self.T_EMPTY, "", None, self.A_LEFT)
else:
- self.theTokens[n] = (self.T_SKIP,"",None,self.A_LEFT)
+ self.theTokens[n] = (self.T_SKIP, "", None, self.A_LEFT)
elif tTemp == self.fmtScene:
if self.firstScene:
- self.theTokens[n] = (self.T_EMPTY,"",None,self.A_LEFT)
+ self.theTokens[n] = (self.T_EMPTY, "", None, self.A_LEFT)
else:
- self.theTokens[n] = (self.T_SEP,tTemp,None,self.A_CENTRE)
+ self.theTokens[n] = (self.T_SEP, tTemp, None, self.A_CENTRE)
else:
- self.theTokens[n] = (tType,tTemp,None,self.A_LEFT)
+ self.theTokens[n] = (tType, tTemp, None, self.A_LEFT)
self.firstScene = False
elif tType == self.T_HEAD4:
tTemp = self._formatSection(tText)
if tTemp == "" and self.hideSection:
- self.theTokens[n] = (self.T_EMPTY,"",None,self.A_LEFT)
+ self.theTokens[n] = (self.T_EMPTY, "", None, self.A_LEFT)
elif tTemp == "" and not self.hideSection:
- self.theTokens[n] = (self.T_SKIP,"",None,self.A_LEFT)
+ self.theTokens[n] = (self.T_SKIP, "", None, self.A_LEFT)
elif tTemp == self.fmtSection:
- self.theTokens[n] = (self.T_SEP,tTemp,None,self.A_CENTRE)
+ self.theTokens[n] = (self.T_SEP, tTemp, None, self.A_CENTRE)
else:
- self.theTokens[n] = (tType,tTemp,None,self.A_LEFT)
+ self.theTokens[n] = (tType, tTemp, None, self.A_LEFT)
# For title page and partitions, we need to centre all text
# and for some formats, we need a page break
@@ -323,9 +352,9 @@ class Tokenizer():
tType = tToken[0]
tText = tToken[1]
tFormat = tToken[2]
- self.theTokens[n] = (tType,tText,tFormat,self.A_CENTRE)
+ self.theTokens[n] = (tType, tText, tFormat, self.A_CENTRE)
- self.theTokens.append((self.T_PBREAK,"",None,self.A_LEFT))
+ self.theTokens.append((self.T_PBREAK, "", None, self.A_LEFT))
return
diff --git a/nw/gui/build.py b/nw/gui/build.py
index 94c82019..2e1e5f0f 100644
--- a/nw/gui/build.py
+++ b/nw/gui/build.py
@@ -42,7 +42,9 @@ from PyQt5.QtWidgets import (
from nw.gui.additions import QSwitch
from nw.core import ToHtml
-from nw.constants import nwConst, nwFiles, nwAlert, nwItemType
+from nw.constants import (
+ nwConst, nwFiles, nwAlert, nwItemType, nwItemLayout, nwItemClass
+)
logger = logging.getLogger(__name__)
@@ -70,11 +72,11 @@ class GuiBuildNovel(QDialog):
self.setWindowTitle("Build Project")
self.setMinimumWidth(800)
- self.setMinimumHeight(700)
+ self.setMinimumHeight(800)
self.resize(
self.optState.getInt("GuiBuildNovel", "winWidth", 800),
- self.optState.getInt("GuiBuildNovel", "winHeight", 700)
+ self.optState.getInt("GuiBuildNovel", "winHeight", 800)
)
self.outerBox = QVBoxLayout()
@@ -128,6 +130,21 @@ class GuiBuildNovel(QDialog):
self.titleForm.setColumnStretch(0, 1)
self.titleForm.setColumnStretch(1, 0)
+ # Text Options
+ # =============
+ self.textGroup = QGroupBox("Text Options", self)
+ self.textForm = QGridLayout(self)
+ self.textGroup.setLayout(self.textForm)
+
+ self.justifyText = QSwitch()
+ self.justifyText.setChecked(self.optState.getBool("GuiBuildNovel", "justifyText", False))
+
+ self.textForm.addWidget(QLabel("Justify text"), 0, 0)
+ self.textForm.addWidget(self.justifyText, 0, 1)
+
+ self.textForm.setColumnStretch(0, 1)
+ self.textForm.setColumnStretch(1, 0)
+
# Build Settings
# ==============
self.buildGroup = QGroupBox("Build Overrides", self)
@@ -156,11 +173,11 @@ class GuiBuildNovel(QDialog):
self.includeKeywords = QSwitch()
self.includeKeywords.setChecked(self.theProject.titleFormat["withKeywords"])
- self.includeForm.addWidget(QLabel("Include Synopsis"), 0, 0)
+ self.includeForm.addWidget(QLabel("Include synopsis"), 0, 0)
self.includeForm.addWidget(self.includeSynopsis, 0, 1)
- self.includeForm.addWidget(QLabel("Include Comments"), 1, 0)
+ self.includeForm.addWidget(QLabel("Include comments"), 1, 0)
self.includeForm.addWidget(self.includeComments, 1, 1)
- self.includeForm.addWidget(QLabel("Include Keywords"), 2, 0)
+ self.includeForm.addWidget(QLabel("Include keywords"), 2, 0)
self.includeForm.addWidget(self.includeKeywords, 2, 1)
self.includeForm.setColumnStretch(0, 1)
@@ -179,11 +196,11 @@ class GuiBuildNovel(QDialog):
self.ignoreFlag = QSwitch()
self.ignoreFlag.setChecked(self.optState.getBool("GuiBuildNovel", "ignoreFlag", False))
- self.addsForm.addWidget(QLabel("Include Novel Files"), 0, 0)
+ self.addsForm.addWidget(QLabel("Include novel files"), 0, 0)
self.addsForm.addWidget(self.novelFiles, 0, 1)
- self.addsForm.addWidget(QLabel("Include Note Files"), 1, 0)
+ self.addsForm.addWidget(QLabel("Include note files"), 1, 0)
self.addsForm.addWidget(self.noteFiles, 1, 1)
- self.addsForm.addWidget(QLabel("Ignore Export Flag"), 2, 0)
+ self.addsForm.addWidget(QLabel("Ignore export flag"), 2, 0)
self.addsForm.addWidget(self.ignoreFlag, 2, 1)
self.addsForm.setColumnStretch(0, 1)
@@ -245,6 +262,7 @@ class GuiBuildNovel(QDialog):
# Assemble GUI
# ============
self.toolsBox.addWidget(self.titleGroup)
+ self.toolsBox.addWidget(self.textGroup)
self.toolsBox.addWidget(self.buildGroup)
self.toolsBox.addWidget(self.includeGroup)
self.toolsBox.addWidget(self.addsGroup)
@@ -280,11 +298,50 @@ class GuiBuildNovel(QDialog):
"""Build a preview of the project in the document viewer.
"""
- makeHtml = ToHtml(self.theProject, self.theParent)
- self.htmlText = ""
+ # Get Settings
+ fmtTitle = self.fmtTitle.text().strip()
+ fmtChapter = self.fmtChapter.text().strip()
+ fmtUnnumbered = self.fmtUnnumbered.text().strip()
+ fmtScene = self.fmtScene.text().strip()
+ fmtSection = self.fmtSection.text().strip()
+ justifyText = self.justifyText.isChecked()
+ outlineMode = self.outlineMode.isChecked()
+ incSynopsis = self.includeSynopsis.isChecked()
+ incComments = self.includeComments.isChecked()
+ incKeywords = self.includeKeywords.isChecked()
+ novelFiles = self.novelFiles.isChecked()
+ noteFiles = self.noteFiles.isChecked()
+ ignoreFlag = self.ignoreFlag.isChecked()
+ doBodyText = True
- for tItem in self.theProject.projTree:
- if tItem is not None and tItem.itemType == nwItemType.FILE:
+ if outlineMode:
+ fmtTitle = "%title%"
+ fmtChapter = "Chapter: %title%"
+ fmtUnnumbered = "Chapter: %title%"
+ fmtScene = "Scene: %title%"
+ fmtSection = "Section: %title%"
+ doBodyText = False
+ incSynopsis = True
+ novelFiles = True
+ noteFiles = False
+
+ makeHtml = ToHtml(self.theProject, self.theParent)
+ makeHtml.setTitleFormat(fmtTitle)
+ makeHtml.setChapterFormat(fmtChapter)
+ makeHtml.setUnNumberedFormat(fmtUnnumbered)
+ makeHtml.setSceneFormat(fmtScene, fmtScene == "")
+ makeHtml.setSectionFormat(fmtSection, fmtSection == "")
+ makeHtml.setBodyText(doBodyText)
+ makeHtml.setSynopsis(incSynopsis)
+ makeHtml.setComments(incComments)
+ makeHtml.setKeywords(incKeywords)
+ makeHtml.setJustify(justifyText)
+
+ self.htmlText = ""
+ self.buildProgress.setMaximum(len(self.theProject.projTree))
+ self.buildProgress.setValue(0)
+ for nItt, tItem in enumerate(self.theProject.projTree):
+ if self._checkInclude(tItem, noteFiles, novelFiles, ignoreFlag):
makeHtml.setText(tItem.itemHandle)
makeHtml.doAutoReplace()
makeHtml.tokenizeText()
@@ -292,11 +349,49 @@ class GuiBuildNovel(QDialog):
makeHtml.doConvert()
makeHtml.doPostProcessing()
self.htmlText += makeHtml.getResult()
+ self.buildProgress.setValue(nItt+1)
self.docView.setHtml(self.htmlText)
return
+ def _checkInclude(self, theItem, noteFiles, novelFiles, ignoreFlag):
+ """This function checks whether a file should be included in the
+ export or not. For standard note and novel files, this is
+ controlled by the options selected by the user. For other files
+ classified as non-exportable, a few checks must be made, and the
+ following are not:
+ * Items that are not actual files.
+ * Items that have been orphaned which are tagged as NO_LAYOUT
+ and NO_CLASS.
+ * Items that appear in the TRASH folder or have parent set to
+ None (orphaned files).
+ """
+
+ if theItem is None:
+ return False
+
+ if not theItem.isExported and not ignoreFlag:
+ return False
+
+ isNone = theItem.itemType != nwItemType.FILE
+ isNone |= theItem.itemLayout == nwItemLayout.NO_LAYOUT
+ isNone |= theItem.itemClass == nwItemClass.NO_CLASS
+ isNone |= theItem.itemClass == nwItemClass.TRASH
+ isNone |= theItem.parHandle == self.theProject.projTree.trashRoot()
+ isNone |= theItem.parHandle is None
+ isNote = theItem.itemLayout == nwItemLayout.NOTE
+ isNovel = not isNone and not isNote
+
+ if isNone:
+ return False
+ if isNote and not noteFiles:
+ return False
+ if isNovel and not novelFiles:
+ return False
+
+ return True
+
def _saveDocument(self, theFormat):
"""Save the document to various formats.
"""
@@ -474,6 +569,7 @@ class GuiBuildNovel(QDialog):
# GUI Settings
self.optState.setValue("GuiBuildNovel", "winWidth", self.width())
self.optState.setValue("GuiBuildNovel", "winHeight", self.height())
+ self.optState.setValue("GuiBuildNovel", "justifyText", self.justifyText.isChecked())
self.optState.setValue("GuiBuildNovel", "outlineMode", self.outlineMode.isChecked())
self.optState.setValue("GuiBuildNovel", "addNovel", self.novelFiles.isChecked())
self.optState.setValue("GuiBuildNovel", "addNotes", self.noteFiles.isChecked())
@@ -557,6 +653,10 @@ class GuiBuildNovelDocView(QTextBrowser):
"mark {"
" background-color: rgb(240, 198, 116);"
"}\n"
+ ".tags {"
+ " color: rgb(245, 135, 31);"
+ " font-wright: bold;"
+ "}\n"
)
self.qDocument.setDefaultStyleSheet(styleSheet)
diff --git a/sample/sampleNovel/nwProject.nwx b/sample/sampleNovel/nwProject.nwx
index 69ac815d..7b76aea0 100644
--- a/sample/sampleNovel/nwProject.nwx
+++ b/sample/sampleNovel/nwProject.nwx
@@ -1,5 +1,5 @@
-
+Sample ProjectSample Project
@@ -20,7 +20,7 @@
%title%
- Chapter %num%\\%title%
+ Chapter %num%.\\%title%%title%* * *
From a7d2ca24a581b299e0238b2562f161f6feffe0b2 Mon Sep 17 00:00:00 2001
From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com>
Date: Sun, 10 May 2020 22:56:30 +0200
Subject: [PATCH 16/53] Deleted the rest of the no longer needed code
---
nw/common.py | 8 -
nw/constants/__init__.py | 3 +-
nw/constants/constants.py | 28 --
nw/gui/dialogs/export.py | 718 --------------------------------------
requirements.txt | 2 -
5 files changed, 1 insertion(+), 758 deletions(-)
delete mode 100644 nw/gui/dialogs/export.py
diff --git a/nw/common.py b/nw/common.py
index 9f4b8011..bd535a71 100644
--- a/nw/common.py
+++ b/nw/common.py
@@ -172,11 +172,3 @@ def splitVersionNumber(vString):
vInt = vMajor*10000 + vMinor*100 + vPatch
return [vMajor, vMinor, vPatch, vInt]
-
-def packageRefURL(packName):
- from nw.constants import nwDependencies
- if packName in nwDependencies.PACKS.keys():
- return "%s" % (
- nwDependencies.PACKS[packName]["site"], packName
- )
- return packName
diff --git a/nw/constants/__init__.py b/nw/constants/__init__.py
index eeab86cc..1abd529c 100644
--- a/nw/constants/__init__.py
+++ b/nw/constants/__init__.py
@@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
from nw.constants.iso import isoLanguage, isoCountry
from nw.constants.constants import (
- nwConst, nwFiles, nwKeyWords, nwLabels, nwDependencies, nwQuotes, nwUnicode
+ nwConst, nwFiles, nwKeyWords, nwLabels, nwQuotes, nwUnicode
)
from nw.constants.enum import (
nwAlert, nwDocAction, nwItemClass, nwItemLayout, nwItemType, nwOutline
@@ -14,7 +14,6 @@ __all__ = [
"nwFiles",
"nwKeyWords",
"nwLabels",
- "nwDependencies",
"nwQuotes",
"nwUnicode",
"nwAlert",
diff --git a/nw/constants/constants.py b/nw/constants/constants.py
index 33a6fc15..3b61fccc 100644
--- a/nw/constants/constants.py
+++ b/nw/constants/constants.py
@@ -142,34 +142,6 @@ class nwLabels():
# END Class nwLabels
-class nwDependencies():
- """Python package dependencies and their reference links.
- """
- PACKS = {
- "pyqt5" : {
- "site" : "",
- "docs" : "",
- },
- "lxml" : {
- "site" : "",
- "docs" : "",
- },
- "pyenchant" : {
- "site" : "",
- "docs" : "",
- },
- "latexcodec" : {
- "site" : "https://pypi.org/project/latexcodec/",
- "docs" : "https://latexcodec.readthedocs.io/en/latest/",
- },
- "pypandoc" : {
- "site" : "https://pypi.org/project/pypandoc/",
- "docs" : "https://pypi.org/project/pypandoc/",
- },
- }
-
-# END Class nwDependencies
-
class nwQuotes():
"""Allowed quotation marks.
Source: https://en.wikipedia.org/wiki/Quotation_mark
diff --git a/nw/gui/dialogs/export.py b/nw/gui/dialogs/export.py
deleted file mode 100644
index 2d67e482..00000000
--- a/nw/gui/dialogs/export.py
+++ /dev/null
@@ -1,718 +0,0 @@
-# -*- coding: utf-8 -*-
-"""novelWriter GUI Export Tools
-
- novelWriter – GUI Export Tools
-================================
- Tool for exporting project files to other formats
-
- File History:
- Created: 2019-10-13 [0.2.3]
-
- This file is a part of novelWriter
- Copyright 2020, Veronica Berglyd Olsen
-
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation, either version 3 of the License, or
- (at your option) any later version.
-
- This program is distributed in the hope that it will be useful, but
- WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with this program. If not, see .
-"""
-
-import logging
-import time
-import nw
-
-from os import path
-
-from PyQt5.QtCore import Qt
-from PyQt5.QtWidgets import (
- QDialog, QHBoxLayout, QVBoxLayout, QWidget, QTabWidget, QGridLayout,
- QGroupBox, QCheckBox, QLabel, QComboBox, QLineEdit, QPushButton,
- QFileDialog, QProgressBar, QSpinBox, QMessageBox
-)
-
-from nw.convert import TextFile, HtmlFile, MarkdownFile, LaTeXFile, ConcatFile
-from nw.common import packageRefURL
-from nw.constants import nwFiles, nwItemType, nwAlert
-
-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.optState = self.theProject.optState
-
- self.outerBox = QHBoxLayout()
- self.innerBox = QVBoxLayout()
- self.setWindowTitle("Export Project")
- self.setLayout(self.outerBox)
-
- self.guiDeco = self.theParent.theTheme.loadDecoration("export",(64,64))
-
- self.tabMain = GuiExportMain(self.theParent, self.theProject)
- self.tabPandoc = GuiExportPandoc(self.theParent, self.theProject)
-
- self.tabWidget = QTabWidget()
- self.tabWidget.addTab(self.tabMain, "Settings")
- self.tabWidget.addTab(self.tabPandoc, "Pandoc")
-
- self.outerBox.addWidget(self.guiDeco, 0, Qt.AlignTop)
- self.outerBox.addLayout(self.innerBox)
-
- 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.addLayout(self.doExportForm)
-
- self.rejected.connect(self._doClose)
- self.show()
-
- logger.debug("GuiExport initialisation complete")
-
- return
-
- ##
- # Buttons
- ##
-
- def _doExport(self):
-
- logger.verbose("GuiExport export button clicked")
-
- wNovel = self.tabMain.expNovel.isChecked()
- wNotes = self.tabMain.expNotes.isChecked()
- eFormat = self.tabMain.outputFormat.currentData()
- fixWidth = self.tabMain.fixedWidth.value()
- wComments = self.tabMain.expComments.isChecked()
- wKeywords = self.tabMain.expKeywords.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()
- hScene = self.tabMain.hideScene.isChecked()
- hSection = self.tabMain.hideSection.isChecked()
-
- pFormat = self.tabPandoc.outputFormat.currentData()
- tFormat = GuiExportPandoc.FMT_VIA[pFormat]
-
- if saveTo.startswith("~"):
- saveTo = path.expanduser(saveTo)
-
- exportDir = path.dirname(saveTo)
- if not path.isdir(exportDir):
- self.theParent.makeAlert("The export folder does not exist.",nwAlert.ERROR)
- self.exportStatus.setText("Export failed ...")
- return False
-
- nItems = len(self.theProject.projTree)
- if eFormat == GuiExportMain.FMT_PDOC:
- nItems += int(0.2*nItems)
- 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)
- elif eFormat == GuiExportMain.FMT_MD:
- outFile = MarkdownFile(self.theProject, self.theParent)
- elif eFormat == GuiExportMain.FMT_HTML:
- outFile = HtmlFile(self.theProject, self.theParent)
- elif eFormat == GuiExportMain.FMT_TEX:
- outFile = LaTeXFile(self.theProject, self.theParent)
- elif eFormat == GuiExportMain.FMT_NWD:
- outFile = ConcatFile(self.theProject, self.theParent)
- elif eFormat == GuiExportMain.FMT_PDOC:
- if tFormat == "html":
- outFile = HtmlFile(self.theProject, self.theParent)
- elif tFormat == "markdown":
- outFile = MarkdownFile(self.theProject, self.theParent)
-
- if outFile is None:
- return False
-
- if outFile.openFile(saveTo):
- outFile.setComments(wComments)
- outFile.setKeywords(wKeywords)
- outFile.setExportNovel(wNovel)
- outFile.setExportNotes(wNotes)
- outFile.setWordWrap(fixWidth)
- outFile.setChapterFormat(chFormat)
- outFile.setUnNumberedFormat(unFormat)
- outFile.setSceneFormat(scFormat, hScene)
- outFile.setSectionFormat(seFormat, hSection)
- else:
- self.exportStatus.setText("Failed to open file for writing ...")
- return False
-
- time.sleep(0.5)
-
- nDone = 0
- for tItem in self.theProject.projTree:
-
- self.exportProgress.setValue(nDone)
- 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(tItem.itemHandle)
-
- 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)
-
- if eFormat == GuiExportMain.FMT_TEX:
- # Check that encoding was successful
- if outFile.texCodecFail:
- self.theParent.makeAlert((
- "Failed to escape unicode characters while writing LaTeX "
- "file. The generated .tex file may not build properly. "
- "Make sure the python package '{package:s}' is installed "
- "and working."
- ).format(
- package = packageRefURL("latexcodec")
- ), nwAlert.WARN)
-
- if eFormat != GuiExportMain.FMT_PDOC:
- return True
-
- # If we've reached this point, we're also running Pandoc
-
- if self._callPandoc(saveTo, tFormat, pFormat):
- self.exportProgress.setValue(nItems)
- self.exportStatus.setText("Pandoc conversion complete")
- logger.verbose("Pandoc conversion complete")
- else:
- self.exportProgress.setValue(nItems)
- self.exportStatus.setText("Pandoc conversion failed")
- logger.verbose("Pandoc conversion failed")
- return False
-
- return True
-
- def _callPandoc(self, inFile, inFmt, outFmt):
-
- pFmt = {
- GuiExportPandoc.FMT_ODT : "odt",
- GuiExportPandoc.FMT_DOCX : "docx",
- GuiExportPandoc.FMT_EPUB2 : "epub2",
- GuiExportPandoc.FMT_EPUB3 : "epub3",
- GuiExportPandoc.FMT_ZIM : "zimwiki",
- }
-
- try:
- import pypandoc
- except:
- self.theParent.makeAlert((
- "Could not load the '{package:s}' package. "
- "Make sure it is installed, and try again."
- ).format(
- package = packageRefURL("pypandoc")
- ), nwAlert.ERROR)
- return False
-
- outFile = path.splitext(inFile)[0]+GuiExportPandoc.FMT_EXT[outFmt]
- fileName = path.basename(outFile)
-
- if path.isfile(outFile) 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
-
- try:
- pypandoc.convert_file(
- source_file = inFile,
- format = inFmt,
- outputfile = outFile,
- to = pFmt[outFmt],
- extra_args = (),
- encoding = "utf-8",
- filters = None
- )
- except Exception as e:
- self.theParent.makeAlert(
- ["Failed to convert file using pypandoc + Pandoc.",
- str(e)], nwAlert.ERROR
- )
- return False
-
- return True
-
- def _doClose(self):
-
- logger.verbose("GuiExport close button clicked")
-
- # General Settings
- wNovel = self.tabMain.expNovel.isChecked()
- wNotes = self.tabMain.expNotes.isChecked()
- eFormat = self.tabMain.outputFormat.currentData()
- fixWidth = self.tabMain.fixedWidth.value()
- wComments = self.tabMain.expComments.isChecked()
- wKeywords = self.tabMain.expKeywords.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()
- hScene = self.tabMain.hideScene.isChecked()
- hSection = self.tabMain.hideSection.isChecked()
-
- if saveTo.startswith("~"):
- saveTo = path.expanduser(saveTo)
-
- self.optState.setValue("GuiExport", "wNovel", wNovel)
- self.optState.setValue("GuiExport", "wNotes", wNotes)
- self.optState.setValue("GuiExport", "eFormat", eFormat)
- self.optState.setValue("GuiExport", "fixWidth", fixWidth)
- self.optState.setValue("GuiExport", "wComments", wComments)
- self.optState.setValue("GuiExport", "wKeywords", wKeywords)
- self.optState.setValue("GuiExport", "chFormat", chFormat)
- self.optState.setValue("GuiExport", "unFormat", unFormat)
- self.optState.setValue("GuiExport", "scFormat", scFormat)
- self.optState.setValue("GuiExport", "seFormat", seFormat)
- self.optState.setValue("GuiExport", "saveTo", saveTo)
- self.optState.setValue("GuiExport", "hScene", hScene)
- self.optState.setValue("GuiExport", "hSection", hSection)
-
- # Pandoc Settings
- pFormat = self.tabPandoc.outputFormat.currentData()
-
- self.optState.setValue("GuiExport", "pFormat", pFormat)
-
- self.optState.saveSettings()
- self.close()
-
- return
-
-# END Class GuiExport
-
-class GuiExportMain(QWidget):
-
- FMT_NWD = 1 # novelWriter markdown
- FMT_TXT = 2 # Plain text file
- FMT_MD = 3 # Markdown file
- FMT_HTML = 4 # HTML file
- FMT_TEX = 5 # LaTeX file
- FMT_PDOC = 6 # Pass to pandoc
- FMT_EXT = {
- FMT_NWD : ".nwd",
- FMT_TXT : ".txt",
- FMT_MD : ".md",
- FMT_HTML : ".htm",
- FMT_TEX : ".tex",
- FMT_PDOC : ".tmp",
- }
- FMT_HELP = {
- FMT_NWD : (
- "Exports a document using the novelWriter markdown format. "
- "The files selected by the filters are appended as-is, "
- "including comments and other settings."
- ),
- 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."
- ),
- FMT_HTML : (
- "Exports a plain html5 file. Comments are wrapped in "
- "blocks with a yellow background colour."
- ),
- FMT_TEX : (
- "Exports a LaTeX file that can be compiled to PDF using "
- "for instance PDFLaTeX. Comments are exported as LaTeX "
- "comments."
- ),
- FMT_PDOC : (
- "Exports first to markdown or html5. The file is then "
- "passed on to Pandoc for a second stage. Use the Pandoc "
- "tab for settings up the conversion."
- ),
- }
-
- def __init__(self, theParent, theProject):
- QWidget.__init__(self, theParent)
-
- self.theParent = theParent
- self.theProject = theProject
- self.theTheme = theParent.theTheme
- self.outerBox = QGridLayout()
- self.optState = self.theProject.optState
- self.currFormat = self.FMT_TXT
-
- # Select Files
- self.guiFiles = QGroupBox("Selection", self)
- self.guiFilesForm = QGridLayout(self)
- self.guiFiles.setLayout(self.guiFilesForm)
-
- self.expNovel = QCheckBox("Novel files",self)
- self.expNovel.setChecked(
- self.optState.getBool("GuiExport", "wNovel", True)
- )
- self.expNovel.setToolTip("Include all novel files in the exported document")
-
- self.expNotes = QCheckBox("Note files",self)
- self.expNotes.setChecked(
- self.optState.getBool("GuiExport", "wNotes", False)
- )
- self.expNotes.setToolTip("Include all note files in the exported document")
-
- self.expComments = QCheckBox("Comments",self)
- self.expComments.setChecked(
- self.optState.getBool("GuiExport", "wComments", False)
- )
- self.expComments.setToolTip("Export comments from all files")
-
- self.expKeywords = QCheckBox("Keywords",self)
- self.expKeywords.setChecked(
- self.optState.getBool("GuiExport", "wKeywords", False)
- )
- self.expKeywords.setToolTip("Export @keywords from all files")
-
- self.guiFilesForm.addWidget(self.expNovel, 0, 1)
- self.guiFilesForm.addWidget(self.expComments, 0, 2)
- self.guiFilesForm.addWidget(self.expNotes, 1, 1)
- self.guiFilesForm.addWidget(self.expKeywords, 1, 2)
- self.guiFilesForm.setRowStretch(2, 1)
-
- # Chapter Settings
- self.guiChapters = QGroupBox("Chapter Headings", self)
- self.guiChaptersForm = QGridLayout(self)
- self.guiChapters.setLayout(self.guiChaptersForm)
-
- self.chapterFormat = QLineEdit()
- self.chapterFormat.setMaxLength(200)
- self.chapterFormat.setText(
- self.optState.getString("GuiExport", "chFormat", "Chapter %numword%")
- )
- self.chapterFormat.setToolTip("Available formats: %num%, %numword%, %title%")
- self.chapterFormat.setMinimumWidth(250)
-
- self.unnumFormat = QLineEdit()
- self.unnumFormat.setMaxLength(200)
- self.unnumFormat.setText(
- self.optState.getString("GuiExport", "unFormat", "%title%")
- )
- 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)
-
- # Scene and Section Settings
- self.guiScenes = QGroupBox("Other Headings", self)
- self.guiScenesForm = QGridLayout(self)
- self.guiScenes.setLayout(self.guiScenesForm)
-
- self.sceneFormat = QLineEdit()
- self.sceneFormat.setMaxLength(200)
- self.sceneFormat.setText(
- self.optState.getString("GuiExport", "scFormat", "* * *")
- )
- self.sceneFormat.setToolTip("Available formats: %title%")
- self.sceneFormat.setMinimumWidth(100)
-
- self.sectionFormat = QLineEdit()
- self.sectionFormat.setMaxLength(200)
- self.sectionFormat.setText(
- self.optState.getString("GuiExport", "seFormat", "")
- )
- self.sectionFormat.setToolTip("Available formats: %title%")
- self.sectionFormat.setMinimumWidth(100)
-
- self.hideScene = QCheckBox("Skip",self)
- self.hideScene.setChecked(
- self.optState.getBool("GuiExport", "hScene", False)
- )
- self.hideScene.setToolTip("Skip scene titles in export")
-
- self.hideSection = QCheckBox("Skip",self)
- self.hideSection.setChecked(
- self.optState.getBool("GuiExport", "hSection", False)
- )
- self.hideSection.setToolTip("Skip section titles in export")
-
- self.guiScenesForm.addWidget(QLabel("Scenes"), 0, 0)
- self.guiScenesForm.addWidget(self.sceneFormat, 0, 1)
- self.guiScenesForm.addWidget(self.hideScene, 0, 2)
- self.guiScenesForm.addWidget(QLabel("Sections"), 1, 0)
- self.guiScenesForm.addWidget(self.sectionFormat, 1, 1)
- self.guiScenesForm.addWidget(self.hideSection, 1, 2)
-
- # Output Path
- self.exportTo = QGroupBox("Export Folder", self)
- self.exportToForm = QGridLayout(self)
- self.exportTo.setLayout(self.exportToForm)
-
- self.exportPath = QLineEdit(
- self.optState.getString("GuiExport", "saveTo", "")
- )
- self.exportGetPath = QPushButton(self.theTheme.getIcon("folder"),"")
- self.exportGetPath.clicked.connect(self._exportFolder)
-
- self.exportToForm.addWidget(QLabel("Save to"), 0, 0)
- self.exportToForm.addWidget(self.exportPath, 0, 1)
- self.exportToForm.addWidget(self.exportGetPath, 0, 2)
-
- # Output Format
- self.guiOutput = QGroupBox("Export", self)
- self.guiOutputForm = QGridLayout(self)
- self.guiOutput.setLayout(self.guiOutputForm)
-
- self.outputHelp = QLabel("")
- self.outputHelp.setWordWrap(True)
- self.outputHelp.setMinimumHeight(55)
- self.outputHelp.setAlignment(Qt.AlignTop)
-
- self.outputFormat = QComboBox(self)
- self.outputFormat.addItem("novelWriter Markdown (.nwd)", self.FMT_NWD)
- 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("LaTeX for PDF (.tex)", self.FMT_TEX)
- self.outputFormat.addItem("Pandoc via Markdown or HTML", self.FMT_PDOC)
- self.outputFormat.currentIndexChanged.connect(self._updateFormat)
-
- optIdx = self.outputFormat.findData(
- self.optState.getInt("GuiExport", "eFormat", 1)
- )
- if optIdx == -1:
- self.outputFormat.setCurrentIndex(1)
- self._updateFormat(1)
- else:
- self.outputFormat.setCurrentIndex(optIdx)
- self._updateFormat(optIdx)
-
- 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 (Format Dependent)", 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.getInt("GuiExport", "fixWidth", 80)
- )
- self.fixedWidth.setToolTip(
- "Applies to .txt and .md files. A value of '0' disables the feature."
- )
-
- 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.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.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)
- self.setLayout(self.outerBox)
-
- return
-
- ##
- # Internal Functions
- ##
-
- def _updateFormat(self, currIdx):
- """Update help text under output format selection and file
- extension in file box
- """
- if currIdx == -1:
- self.outputHelp.setText("")
- else:
- self.currFormat = self.outputFormat.itemData(currIdx)
- self.outputHelp.setText("%s" % self.FMT_HELP[self.currFormat])
- self._checkFileExtension()
- return
-
- def _exportFolder(self):
-
- currDir = self.exportPath.text()
- if not path.isdir(currDir):
- currDir = ""
-
- extFilter = [
- "novelWriter document files (*.nwd)",
- "Text files (*.txt)",
- "Markdown files (*.md)",
- "HTML files (*.htm *.html)",
- "LaTeX files (*.tex)",
- "All files (*.*)",
- ]
-
- dlgOpt = QFileDialog.Options()
- dlgOpt |= QFileDialog.DontUseNativeDialog
- saveTo = QFileDialog.getSaveFileName(
- self, "Export File", self.exportPath.text(),
- options=dlgOpt, filter=";;".join(extFilter)
- )
- if saveTo:
- self.exportPath.setText(saveTo[0])
- self._checkFileExtension()
- return True
-
- return False
-
- def _checkFileExtension(self):
- saveTo = self.exportPath.text()
- if saveTo.startswith("~"):
- saveTo = path.expanduser(saveTo)
- fileBits = path.splitext(saveTo)
- if self.currFormat > 0 and fileBits[0].strip() != "":
- saveTo = fileBits[0]+self.FMT_EXT[self.currFormat]
- self.exportPath.setText(saveTo)
- return
-
-# END Class GuiExportMain
-
-class GuiExportPandoc(QWidget):
-
- FMT_ODT = 1
- FMT_DOCX = 2
- FMT_EPUB2 = 4
- FMT_EPUB3 = 5
- FMT_ZIM = 6
- FMT_EXT = {
- FMT_ODT : ".odt",
- FMT_DOCX : ".docx",
- FMT_EPUB2 : ".epub",
- FMT_EPUB3 : ".epub",
- FMT_ZIM : ".txt",
- }
- FMT_VIA = {
- FMT_ODT : "html",
- FMT_DOCX : "html",
- FMT_EPUB2 : "markdown",
- FMT_EPUB3 : "markdown",
- FMT_ZIM : "markdown",
- }
-
- def __init__(self, theParent, theProject):
- QWidget.__init__(self, theParent)
-
- self.theParent = theParent
- self.theProject = theProject
- self.outerBox = QGridLayout()
- self.optState = self.theProject.optState
-
- try:
- import pypandoc
- self.hasPyPan = True
- except:
- self.hasPyPan = False
-
- # Information
- self.guiInfo = QGroupBox("Information", self)
- self.guiInfoBox = QVBoxLayout(self)
- self.guiInfo.setLayout(self.guiInfoBox)
-
- self.infoHelp = QLabel("")
- self.infoHelp.setWordWrap(True)
- self.infoHelp.setMinimumHeight(55)
- self.infoHelp.setAlignment(Qt.AlignTop)
-
- self.guiInfoBox.addWidget(self.infoHelp)
-
- if self.hasPyPan:
- self.infoHelp.setText((
- "Additional export to other document formats than in the Settings tab is provided "
- "by Pandoc. the project is first exported to Markdown or HTML, depending on final "
- "format, and then processed by Pandoc into the desired format."
- ))
- else:
- self.infoHelp.setText((
- "The Python package 'pypandoc' is not installed or isn't working. This package is "
- "required for interfacing with Pandoc. Please install it before proceeding."
- ))
-
- # Output Format
- self.guiOutput = QGroupBox("Pandoc Format", self)
- self.guiOutputForm = QGridLayout(self)
- self.guiOutput.setLayout(self.guiOutputForm)
-
- self.outputFormat = QComboBox(self)
- self.outputFormat.addItem("Open Office Document (.odt)", self.FMT_ODT)
- self.outputFormat.addItem("Word Document (.docx)", self.FMT_DOCX)
- self.outputFormat.addItem("ePUB eBook v2 (.epub2)", self.FMT_EPUB2)
- self.outputFormat.addItem("ePUB eBook v3 (.epub3)", self.FMT_EPUB3)
- self.outputFormat.addItem("Zim Wiki (.txt)", self.FMT_ZIM)
-
- optIdx = self.outputFormat.findData(
- self.optState.getInt("GuiExport", "pFormat", 1)
- )
- if optIdx == -1:
- self.outputFormat.setCurrentIndex(1)
- else:
- self.outputFormat.setCurrentIndex(optIdx)
-
- self.guiOutputForm.addWidget(QLabel("Format"), 0, 0)
- self.guiOutputForm.addWidget(self.outputFormat, 0, 1)
- self.guiOutputForm.setColumnStretch(2, 1)
-
- # Assemble
- self.outerBox.addWidget(self.guiInfo, 0, 0)
- self.outerBox.addWidget(self.guiOutput, 1, 0)
- self.outerBox.setRowStretch(2, 1)
- self.setLayout(self.outerBox)
-
- return
-
-# END Class GuiExportPandoc
diff --git a/requirements.txt b/requirements.txt
index 648be38e..08fc42c3 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,5 +1,3 @@
pyqt5
lxml
pyenchant
-latexcodec
-pypandoc
From 0faf1f9986c3172cb45f63e19f723e085eab1616 Mon Sep 17 00:00:00 2001
From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com>
Date: Sun, 10 May 2020 23:24:32 +0200
Subject: [PATCH 17/53] The isExported option should only apply to files
---
nw/core/project.py | 2 +-
nw/gui/dialogs/itemeditor.py | 9 +++++++--
nw/gui/elements/docdetails.py | 11 +++++++----
nw/gui/elements/doctree.py | 18 ++++++++++++------
sample/sampleNovel/nwProject.nwx | 8 +-------
tests/reference/gui/0_nwProject.nwx | 7 +------
tests/reference/gui/1_nwProject.nwx | 7 +------
tests/reference/gui/2_nwProject.nwx | 7 +------
tests/reference/gui/3_nwProject.nwx | 9 ++-------
tests/reference/proj/1_nwProject.nwx | 7 +------
tests/reference/proj/2_nwProject.nwx | 11 +----------
tests/test_gui.py | 3 +++
tests/test_item.py | 4 ++--
13 files changed, 40 insertions(+), 63 deletions(-)
diff --git a/nw/core/project.py b/nw/core/project.py
index a0396c02..0dcb60b9 100644
--- a/nw/core/project.py
+++ b/nw/core/project.py
@@ -1350,8 +1350,8 @@ class NWItem():
xSub = self._subPack(xPack,"class", text=str(self.itemClass.name))
xSub = self._subPack(xPack,"status", text=str(self.itemStatus))
xSub = self._subPack(xPack,"expanded", text=str(self.isExpanded))
- xSub = self._subPack(xPack,"exported", text=str(self.isExported))
if self.itemType == nwItemType.FILE:
+ xSub = self._subPack(xPack,"exported", text=str(self.isExported))
xSub = self._subPack(xPack,"layout", text=str(self.itemLayout.name))
xSub = self._subPack(xPack,"charCount", text=str(self.charCount), none=False)
xSub = self._subPack(xPack,"wordCount", text=str(self.wordCount), none=False)
diff --git a/nw/gui/dialogs/itemeditor.py b/nw/gui/dialogs/itemeditor.py
index 432ddb08..fee4a74f 100644
--- a/nw/gui/dialogs/itemeditor.py
+++ b/nw/gui/dialogs/itemeditor.py
@@ -105,9 +105,14 @@ class GuiItemEditor(QDialog):
if itemLayout in self.validLayouts:
self.editLayout.addItem(nwLabels.LAYOUT_NAME[itemLayout],itemLayout)
- self.editExport = QSwitch()
- self.editExport.setChecked(self.theItem.isExported)
self.textExport = QLabel("Include when building project")
+ self.editExport = QSwitch()
+ if self.theItem.itemType == nwItemType.FILE:
+ self.editExport.setEnabled(True)
+ self.editExport.setChecked(self.theItem.isExported)
+ else:
+ self.editExport.setEnabled(False)
+ self.editExport.setChecked(False)
self.mainForm.addWidget(QLabel("Label"), 0, 0)
self.mainForm.addWidget(self.editName, 0, 1, 1, 2)
diff --git a/nw/gui/elements/docdetails.py b/nw/gui/elements/docdetails.py
index de547c06..8c231fa6 100644
--- a/nw/gui/elements/docdetails.py
+++ b/nw/gui/elements/docdetails.py
@@ -32,7 +32,7 @@ from PyQt5.QtCore import Qt
from PyQt5.QtGui import QFont
from PyQt5.QtWidgets import QFrame, QGridLayout, QLabel
-from nw.constants import nwLabels, nwItemClass, nwUnicode
+from nw.constants import nwLabels, nwItemClass, nwItemType, nwUnicode
logger = logging.getLogger(__name__)
@@ -170,10 +170,13 @@ class GuiDocDetails(QFrame):
iStatus = self.theProject.importItems.checkEntry(iStatus) # Make sure it's valid
flagIcon = self.theParent.importIcons[iStatus]
- if nwItem.isExported:
- exportFlag = nwUnicode.U_CHECK
+ if nwItem.itemType == nwItemType.FILE:
+ if nwItem.isExported:
+ exportFlag = nwUnicode.U_CHECK
+ else:
+ exportFlag = " "
else:
- exportFlag = " "
+ exportFlag = "+"
self.labelFlag.setText(exportFlag)
self.statusFlag.setPixmap(flagIcon.pixmap(10, 10))
diff --git a/nw/gui/elements/doctree.py b/nw/gui/elements/doctree.py
index 0c6b8bb2..4b0a3ed2 100644
--- a/nw/gui/elements/doctree.py
+++ b/nw/gui/elements/doctree.py
@@ -412,13 +412,19 @@ class GuiDocTree(QTreeWidget):
tHandle = nwItem.itemHandle
pHandle = nwItem.parHandle
- if nwItem.isExported:
- tStatus = nwUnicode.U_CHECK
- else:
- tStatus = " "
- tStatus += " "+nwLabels.CLASS_FLAG[nwItem.itemClass]
+ stExport = " "
+ stClass = nwLabels.CLASS_FLAG[nwItem.itemClass]
+ stLayout = ""
+
if nwItem.itemType == nwItemType.FILE:
- tStatus += "."+nwLabels.LAYOUT_FLAG[nwItem.itemLayout]
+ stLayout = "."+nwLabels.LAYOUT_FLAG[nwItem.itemLayout]
+ if nwItem.isExported:
+ stExport = nwUnicode.U_CHECK
+ else:
+ stExport = "+"
+
+ tStatus = stExport+" "+stClass+stLayout
+
iStatus = nwItem.itemStatus
if tClass == nwItemClass.NOVEL:
iStatus = self.theProject.statusItems.checkEntry(iStatus) # Make sure it's valid
diff --git a/sample/sampleNovel/nwProject.nwx b/sample/sampleNovel/nwProject.nwx
index 7b76aea0..eb89f47c 100644
--- a/sample/sampleNovel/nwProject.nwx
+++ b/sample/sampleNovel/nwProject.nwx
@@ -1,5 +1,5 @@
-
+Sample ProjectSample Project
@@ -51,7 +51,6 @@
NOVELStartedTrue
- TrueTitle Page
@@ -72,7 +71,6 @@
NOVEL1st DraftTrue
- TrueChapter One
@@ -171,7 +169,6 @@
CHARACTERNoneTrue
- TrueMain Characters
@@ -179,7 +176,6 @@
CHARACTERNoneTrue
- TrueJohn Smith
@@ -213,7 +209,6 @@
WORLDNoneTrue
- TrueEarth
@@ -260,7 +255,6 @@
TRASHNoneTrue
- TrueDelete Me!
diff --git a/tests/reference/gui/0_nwProject.nwx b/tests/reference/gui/0_nwProject.nwx
index 3007ae93..db1151d3 100644
--- a/tests/reference/gui/0_nwProject.nwx
+++ b/tests/reference/gui/0_nwProject.nwx
@@ -1,5 +1,5 @@
-
+
@@ -42,7 +42,6 @@
NOVELNewFalse
- TrueNew Chapter
@@ -50,7 +49,6 @@
NOVELNewFalse
- TrueNew Scene
@@ -71,7 +69,6 @@
CHARACTERNewFalse
- TruePlot
@@ -79,7 +76,6 @@
PLOTNewFalse
- TrueWorld
@@ -87,7 +83,6 @@
WORLDNewFalse
- True
diff --git a/tests/reference/gui/1_nwProject.nwx b/tests/reference/gui/1_nwProject.nwx
index 38708ab2..bf16def6 100644
--- a/tests/reference/gui/1_nwProject.nwx
+++ b/tests/reference/gui/1_nwProject.nwx
@@ -1,5 +1,5 @@
-
+
@@ -42,7 +42,6 @@
NOVELNewTrue
- TrueNew Chapter
@@ -50,7 +49,6 @@
NOVELNewTrue
- TrueNew Scene
@@ -71,7 +69,6 @@
CHARACTERNewTrue
- TrueNew File
@@ -92,7 +89,6 @@
PLOTNewTrue
- TrueNew File
@@ -113,7 +109,6 @@
WORLDNewTrue
- TrueNew File
diff --git a/tests/reference/gui/2_nwProject.nwx b/tests/reference/gui/2_nwProject.nwx
index 58b80897..bf3c5b61 100644
--- a/tests/reference/gui/2_nwProject.nwx
+++ b/tests/reference/gui/2_nwProject.nwx
@@ -1,5 +1,5 @@
-
+Project NameProject Title
@@ -46,7 +46,6 @@
NOVELNewFalse
- TrueNew Chapter
@@ -54,7 +53,6 @@
NOVELNewFalse
- TrueNew Scene
@@ -75,7 +73,6 @@
CHARACTERNewFalse
- TruePlot
@@ -83,7 +80,6 @@
PLOTNewFalse
- TrueWorld
@@ -91,7 +87,6 @@
WORLDNewFalse
- True
diff --git a/tests/reference/gui/3_nwProject.nwx b/tests/reference/gui/3_nwProject.nwx
index 7c5d8511..bee4eb0a 100644
--- a/tests/reference/gui/3_nwProject.nwx
+++ b/tests/reference/gui/3_nwProject.nwx
@@ -1,5 +1,5 @@
-
+
@@ -42,7 +42,6 @@
NOVELNewFalse
- TrueNew Chapter
@@ -50,7 +49,6 @@
NOVELNewFalse
- TrueJust a Page
@@ -58,7 +56,7 @@
NOVELNoteFalse
- True
+ FalsePAGE00
@@ -71,7 +69,6 @@
CHARACTERNewFalse
- TruePlot
@@ -79,7 +76,6 @@
PLOTNewFalse
- TrueWorld
@@ -87,7 +83,6 @@
WORLDNewFalse
- True
diff --git a/tests/reference/proj/1_nwProject.nwx b/tests/reference/proj/1_nwProject.nwx
index febdc50e..0e537470 100644
--- a/tests/reference/proj/1_nwProject.nwx
+++ b/tests/reference/proj/1_nwProject.nwx
@@ -1,5 +1,5 @@
-
+
@@ -42,7 +42,6 @@
NOVELNewFalse
- TrueCharacters
@@ -50,7 +49,6 @@
CHARACTERNewFalse
- TruePlot
@@ -58,7 +56,6 @@
PLOTNewFalse
- TrueWorld
@@ -66,7 +63,6 @@
WORLDNewFalse
- TrueNew Chapter
@@ -74,7 +70,6 @@
NOVELNewFalse
- TrueNew Scene
diff --git a/tests/reference/proj/2_nwProject.nwx b/tests/reference/proj/2_nwProject.nwx
index b64d0071..eb98cc9f 100644
--- a/tests/reference/proj/2_nwProject.nwx
+++ b/tests/reference/proj/2_nwProject.nwx
@@ -1,5 +1,5 @@
-
+
@@ -42,7 +42,6 @@
NOVELNewFalse
- TrueCharacters
@@ -50,7 +49,6 @@
CHARACTERNewFalse
- TruePlot
@@ -58,7 +56,6 @@
PLOTNewFalse
- TrueWorld
@@ -66,7 +63,6 @@
WORLDNewFalse
- TrueNew Chapter
@@ -74,7 +70,6 @@
NOVELNewFalse
- TrueNew Scene
@@ -95,7 +90,6 @@
TIMELINENewFalse
- TrueObject
@@ -103,7 +97,6 @@
OBJECTNewFalse
- TrueCustom1
@@ -111,7 +104,6 @@
CUSTOMNewFalse
- TrueCustom2
@@ -119,7 +111,6 @@
CUSTOMNewFalse
- True
diff --git a/tests/test_gui.py b/tests/test_gui.py
index 9e2cd53d..c32e8168 100644
--- a/tests/test_gui.py
+++ b/tests/test_gui.py
@@ -361,6 +361,9 @@ def testItemEditor(qtbot, nwTempGUI, nwRef, nwTemp):
layoutIdx = itemEdit.editLayout.findData(nwItemLayout.PAGE)
itemEdit.editLayout.setCurrentIndex(layoutIdx)
+ itemEdit.editExport.setChecked(False)
+ assert not itemEdit.editExport.isChecked()
+
itemEdit._doSave()
itemEdit = GuiItemEditor(nwGUI, nwGUI.theProject, "31489056e0916")
diff --git a/tests/test_item.py b/tests/test_item.py
index 19f663f6..1bab7447 100644
--- a/tests/test_item.py
+++ b/tests/test_item.py
@@ -197,8 +197,8 @@ def testItemXMLPackUnpack():
assert etree.tostring(xContent, pretty_print=False, encoding="utf-8") == (
b""
b""
- b"A NameTRASHTRASHMain"
- b"TrueTrue"
+ b"A NameTRASHTRASH"
+ b"MainTrue"
b""
b""
)
From 3ad4e26dbc5613963394cc12a08329858d9d516e Mon Sep 17 00:00:00 2001
From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com>
Date: Mon, 11 May 2020 00:20:46 +0200
Subject: [PATCH 18/53] Cleanup of unneeded imports
---
nw/core/project.py | 2 +-
nw/gui/build.py | 5 ++---
nw/gui/dialogs/docmerge.py | 3 ++-
nw/gui/dialogs/docsplit.py | 3 ++-
nw/gui/dialogs/itemeditor.py | 2 +-
nw/gui/elements/doceditor.py | 2 +-
nw/gui/tools/optionstate.py | 1 -
nw/guimain.py | 2 +-
8 files changed, 10 insertions(+), 10 deletions(-)
diff --git a/nw/core/project.py b/nw/core/project.py
index 0dcb60b9..23fa2c24 100644
--- a/nw/core/project.py
+++ b/nw/core/project.py
@@ -44,7 +44,7 @@ from nw.core.tools import projectMaintenance
from nw.core.document import NWDoc
from nw.common import checkString, checkBool, checkInt, formatTimeStamp
from nw.constants import (
- nwFiles, nwConst, nwItemType, nwItemClass, nwItemLayout, nwAlert
+ nwFiles, nwItemType, nwItemClass, nwItemLayout, nwAlert
)
logger = logging.getLogger(__name__)
diff --git a/nw/gui/build.py b/nw/gui/build.py
index 2e1e5f0f..455c7ec7 100644
--- a/nw/gui/build.py
+++ b/nw/gui/build.py
@@ -35,9 +35,8 @@ from PyQt5.QtGui import (
QTextOption, QPalette, QColor, QTextDocumentWriter
)
from PyQt5.QtWidgets import (
- QDialog, QVBoxLayout, QHBoxLayout, QTextBrowser, QPushButton,
- QLabel, QLineEdit, QGroupBox, QGridLayout, QComboBox, QProgressBar,
- QMenu, QAction, QFileDialog
+ QDialog, QVBoxLayout, QHBoxLayout, QTextBrowser, QPushButton, QLabel,
+ QLineEdit, QGroupBox, QGridLayout, QProgressBar, QMenu, QAction, QFileDialog
)
from nw.gui.additions import QSwitch
diff --git a/nw/gui/dialogs/docmerge.py b/nw/gui/dialogs/docmerge.py
index 097f4217..1094fb78 100644
--- a/nw/gui/dialogs/docmerge.py
+++ b/nw/gui/dialogs/docmerge.py
@@ -52,10 +52,11 @@ class GuiDocMerge(QDialog):
self.outerBox = QHBoxLayout()
self.innerBox = QVBoxLayout()
- self.setWindowTitle("Merge Documents")
self.setLayout(self.outerBox)
+ self.setWindowTitle("Merge Documents")
self.guiDeco = self.theParent.theTheme.loadDecoration("merge",(64,64))
+ self.outerBox.setSpacing(16)
self.outerBox.addWidget(self.guiDeco, 0, Qt.AlignTop)
self.outerBox.addLayout(self.innerBox)
diff --git a/nw/gui/dialogs/docsplit.py b/nw/gui/dialogs/docsplit.py
index bfb4e8ee..dc795c77 100644
--- a/nw/gui/dialogs/docsplit.py
+++ b/nw/gui/dialogs/docsplit.py
@@ -53,10 +53,11 @@ class GuiDocSplit(QDialog):
self.outerBox = QHBoxLayout()
self.innerBox = QVBoxLayout()
- self.setWindowTitle("Split Document")
self.setLayout(self.outerBox)
+ self.setWindowTitle("Split Document")
self.guiDeco = self.theParent.theTheme.loadDecoration("split",(64,64))
+ self.outerBox.setSpacing(16)
self.outerBox.addWidget(self.guiDeco, 0, Qt.AlignTop)
self.outerBox.addLayout(self.innerBox)
diff --git a/nw/gui/dialogs/itemeditor.py b/nw/gui/dialogs/itemeditor.py
index fee4a74f..c94e053e 100644
--- a/nw/gui/dialogs/itemeditor.py
+++ b/nw/gui/dialogs/itemeditor.py
@@ -31,7 +31,7 @@ import nw
from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import (
QDialog, QHBoxLayout, QVBoxLayout, QGroupBox, QGridLayout, QLineEdit,
- QPushButton, QComboBox, QLabel, QSpacerItem, QSizePolicy, QDialogButtonBox
+ QComboBox, QLabel, QSpacerItem, QSizePolicy, QDialogButtonBox
)
from nw.gui.additions import QSwitch
diff --git a/nw/gui/elements/doceditor.py b/nw/gui/elements/doceditor.py
index 8aa583da..96ab264f 100644
--- a/nw/gui/elements/doceditor.py
+++ b/nw/gui/elements/doceditor.py
@@ -32,7 +32,7 @@ from time import time
from PyQt5.QtCore import Qt, QTimer, pyqtSlot
from PyQt5.QtWidgets import (
- qApp, QTextEdit, QAction, QMenu, QShortcut, QMessageBox, QLabel
+ qApp, QTextEdit, QAction, QMenu, QShortcut, QMessageBox
)
from PyQt5.QtGui import (
QTextCursor, QTextOption, QKeySequence, QFont, QColor, QPalette,
diff --git a/nw/gui/tools/optionstate.py b/nw/gui/tools/optionstate.py
index 776dbcb0..ed45e7cf 100644
--- a/nw/gui/tools/optionstate.py
+++ b/nw/gui/tools/optionstate.py
@@ -32,7 +32,6 @@ import nw
from os import path
-from nw.common import checkString, checkBool, checkInt
from nw.constants import nwFiles
logger = logging.getLogger(__name__)
diff --git a/nw/guimain.py b/nw/guimain.py
index c76e9c6f..8a37386c 100644
--- a/nw/guimain.py
+++ b/nw/guimain.py
@@ -45,7 +45,7 @@ from nw.gui import (
GuiConfigEditor, GuiProjectEditor, GuiItemEditor, GuiProjectOutline,
GuiSessionLogView, GuiDocMerge, GuiDocSplit, GuiProjectLoad, GuiBuildNovel
)
-from nw.core import NWProject, NWDoc, NWIndex, countWords
+from nw.core import NWProject, NWDoc, NWIndex
from nw.constants import nwFiles, nwItemType, nwAlert
logger = logging.getLogger(__name__)
From 4dead91af26f6d7d046f7d199d96c1ad60fdc534 Mon Sep 17 00:00:00 2001
From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com>
Date: Mon, 11 May 2020 18:18:19 +0200
Subject: [PATCH 19/53] Extended the style formatting of tokenizer and tohtml
to allow multiple styles for a block
---
nw/core/tohtml.py | 36 +++++---
nw/core/tokenizer.py | 204 ++++++++++++++++++++++++++++++++++++-------
nw/gui/build.py | 43 ++++-----
3 files changed, 219 insertions(+), 64 deletions(-)
diff --git a/nw/core/tohtml.py b/nw/core/tohtml.py
index 2b98be99..ec3cec22 100644
--- a/nw/core/tohtml.py
+++ b/nw/core/tohtml.py
@@ -102,19 +102,35 @@ class ToHtml(Tokenizer):
self.theResult = ""
thisPar = []
- for tType, tText, tFormat, tAlign in self.theTokens:
+ for tType, tText, tFormat, tStyle in self.theTokens:
# Styles
aStyle = []
- if tAlign == self.A_CENTRE:
- aStyle.append("text-align: center;")
- elif tAlign == self.A_RIGHT:
- aStyle.append("text-align: right;")
- elif tAlign == self.A_JUSTIFY:
- aStyle.append("text-align: justify;")
-
- if tType == self.T_HEAD2:
- aStyle.append("page-break-before: always;")
+ if tStyle is not None:
+ if tStyle & self.A_LEFT:
+ aStyle.append("text-align: left;")
+ if tStyle & self.A_RIGHT:
+ aStyle.append("text-align: right;")
+ if tStyle & self.A_CENTRE:
+ aStyle.append("text-align: center;")
+ if tStyle & self.A_JUSTIFY:
+ aStyle.append("text-align: justify;")
+ if tStyle & self.A_PBB:
+ aStyle.append("page-break-before: always;")
+ if tStyle & self.A_PBB_L:
+ aStyle.append("page-break-before: left;")
+ if tStyle & self.A_PBB_R:
+ aStyle.append("page-break-before: right;")
+ if tStyle & self.A_PBB_AV:
+ aStyle.append("page-break-before: avoid;")
+ if tStyle & self.A_PBA:
+ aStyle.append("page-break-after: always;")
+ if tStyle & self.A_PBA_L:
+ aStyle.append("page-break-after: left;")
+ if tStyle & self.A_PBA_R:
+ aStyle.append("page-break-after: right;")
+ if tStyle & self.A_PBA_AV:
+ aStyle.append("page-break-after: avoid;")
if len(aStyle) > 0:
hStyle = " style='%s'" % (" ".join(aStyle))
diff --git a/nw/core/tokenizer.py b/nw/core/tokenizer.py
index a39d12f2..84fbb100 100644
--- a/nw/core/tokenizer.py
+++ b/nw/core/tokenizer.py
@@ -60,10 +60,18 @@ class Tokenizer():
T_SKIP = 11 # Paragraph break
T_PBREAK = 12 # Page break
- A_LEFT = 1 # Left aligned
- A_RIGHT = 2 # Right aligned
- A_CENTRE = 3 # Centred
- A_JUSTIFY = 4 # Justified
+ A_LEFT = 1 # Left aligned
+ A_RIGHT = 2 # Right aligned
+ A_CENTRE = 4 # Centred
+ A_JUSTIFY = 8 # Justified
+ A_PBB = 16 # Page break before
+ A_PBB_L = 32 # Page break before, left
+ A_PBB_R = 64 # Page break before, right
+ A_PBB_AV = 128 # Page break, avoid
+ A_PBA = 256 # Page break after
+ A_PBA_L = 512 # Page break after, left
+ A_PBA_R = 1024 # Page break after, right
+ A_PBA_AV = 2048 # Page break, avoid
def __init__(self, theProject, theParent):
@@ -208,6 +216,13 @@ class Tokenizer():
just contains plain text. in the case of plain text, apply the
same RegExes that the syntax highlighter uses and save the
locations of these formatting tags into the token array.
+
+ The format of the token list is an entry with a four-tuple for
+ each line in the file. The tuple is as follows:
+ 1: The type of the block, self.T_*
+ 2: The text content of the block, without leading tags
+ 3: The internal formatting map of the text, self.FMT_*
+ 4: The style of the block, self.A_*
"""
# RegExes for adding formatting tags within text lines
@@ -233,23 +248,63 @@ class Tokenizer():
# Tag lines starting with specific characters
if len(aLine.strip()) == 0:
- self.theTokens.append((self.T_EMPTY, "", None, self.A_LEFT))
+ self.theTokens.append((
+ self.T_EMPTY,
+ "",
+ None,
+ None
+ ))
elif aLine[0] == "%":
cLine = aLine[1:].strip()
if cLine.lower().startswith("synopsis:"):
- self.theTokens.append((self.T_SYNOPSIS, cLine[9:].strip(), None, defAlign))
+ self.theTokens.append((
+ self.T_SYNOPSIS,
+ cLine[9:].strip(),
+ None,
+ defAlign
+ ))
else:
- self.theTokens.append((self.T_COMMENT, aLine[1:].strip(), None, defAlign))
+ self.theTokens.append((
+ self.T_COMMENT,
+ aLine[1:].strip(),
+ None,
+ defAlign
+ ))
elif aLine[0] == "@":
- self.theTokens.append((self.T_KEYWORD, aLine[1:].strip(), None, self.A_LEFT))
+ self.theTokens.append((
+ self.T_KEYWORD,
+ aLine[1:].strip(),
+ None,
+ self.A_LEFT
+ ))
elif aLine[:2] == "# ":
- self.theTokens.append((self.T_HEAD1, aLine[2:].strip(), None, self.A_LEFT))
+ self.theTokens.append((
+ self.T_HEAD1,
+ aLine[2:].strip(),
+ None,
+ self.A_LEFT | self.A_PBB
+ ))
elif aLine[:3] == "## ":
- self.theTokens.append((self.T_HEAD2, aLine[3:].strip(), None, self.A_LEFT))
+ self.theTokens.append((
+ self.T_HEAD2,
+ aLine[3:].strip(),
+ None,
+ self.A_LEFT | self.A_PBA_AV
+ ))
elif aLine[:4] == "### ":
- self.theTokens.append((self.T_HEAD3, aLine[4:].strip(), None, self.A_LEFT))
+ self.theTokens.append((
+ self.T_HEAD3,
+ aLine[4:].strip(),
+ None,
+ self.A_LEFT | self.A_PBA_AV
+ ))
elif aLine[:5] == "#### ":
- self.theTokens.append((self.T_HEAD4, aLine[5:].strip(), None, self.A_LEFT))
+ self.theTokens.append((
+ self.T_HEAD4,
+ aLine[5:].strip(),
+ None,
+ self.A_LEFT | self.A_PBA_AV
+ ))
else:
if not self.doBodyText:
# Skip all body text
@@ -270,10 +325,20 @@ 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, defAlign))
+ self.theTokens.append((
+ self.T_TEXT,
+ aLine,
+ fmtPos,
+ defAlign
+ ))
# Always add an empty line at the end
- self.theTokens.append((self.T_EMPTY, "", None, self.A_LEFT))
+ self.theTokens.append((
+ self.T_EMPTY,
+ "",
+ None,
+ None
+ ))
return
@@ -308,53 +373,126 @@ class Tokenizer():
if tType == self.T_TEXT:
self.firstScene = False
- elif tType == self.T_HEAD2:
+ elif tType == self.T_HEAD2: # Novel Chapter
if not isUnNum:
self.numChapter += 1
tText = self._formatChapter(tText,isUnNum)
- self.theTokens[n] = (tType, tText, None, self.A_LEFT)
+ self.theTokens[n] = (
+ tType,
+ tText,
+ None,
+ self.A_LEFT | self.A_PBB_R
+ )
self.firstScene = True
- elif tType == self.T_HEAD3:
+ elif tType == self.T_HEAD3: # Novel Scene
tTemp = self._formatScene(tText)
if tTemp == "" and self.hideScene:
- self.theTokens[n] = (self.T_EMPTY, "", None, self.A_LEFT)
+ self.theTokens[n] = (
+ self.T_EMPTY,
+ "",
+ None,
+ None
+ )
elif tTemp == "" and not self.hideScene:
if self.firstScene:
- self.theTokens[n] = (self.T_EMPTY, "", None, self.A_LEFT)
+ self.theTokens[n] = (
+ self.T_EMPTY,
+ "",
+ None,
+ None
+ )
else:
- self.theTokens[n] = (self.T_SKIP, "", None, self.A_LEFT)
+ self.theTokens[n] = (
+ self.T_SKIP,
+ "",
+ None,
+ None
+ )
elif tTemp == self.fmtScene:
if self.firstScene:
- self.theTokens[n] = (self.T_EMPTY, "", None, self.A_LEFT)
+ self.theTokens[n] = (
+ self.T_EMPTY,
+ "",
+ None,
+ None
+ )
else:
- self.theTokens[n] = (self.T_SEP, tTemp, None, self.A_CENTRE)
+ self.theTokens[n] = (
+ self.T_SEP,
+ tTemp,
+ None,
+ self.A_CENTRE
+ )
else:
- self.theTokens[n] = (tType, tTemp, None, self.A_LEFT)
+ self.theTokens[n] = (
+ tType,
+ tTemp,
+ None,
+ self.A_LEFT | self.A_PBA_AV
+ )
self.firstScene = False
- elif tType == self.T_HEAD4:
+ elif tType == self.T_HEAD4: # Novel Section
tTemp = self._formatSection(tText)
if tTemp == "" and self.hideSection:
- self.theTokens[n] = (self.T_EMPTY, "", None, self.A_LEFT)
+ self.theTokens[n] = (
+ self.T_EMPTY,
+ "",
+ None,
+ None
+ )
elif tTemp == "" and not self.hideSection:
- self.theTokens[n] = (self.T_SKIP, "", None, self.A_LEFT)
+ self.theTokens[n] = (
+ self.T_SKIP,
+ "",
+ None,
+ None
+ )
elif tTemp == self.fmtSection:
- self.theTokens[n] = (self.T_SEP, tTemp, None, self.A_CENTRE)
+ self.theTokens[n] = (
+ self.T_SEP,
+ tTemp,
+ None,
+ self.A_CENTRE
+ )
else:
- self.theTokens[n] = (tType, tTemp, None, self.A_LEFT)
+ self.theTokens[n] = (
+ tType,
+ tTemp,
+ None,
+ self.A_LEFT | self.A_PBA_AV
+ )
- # For title page and partitions, we need to centre all text
- # and for some formats, we need a page break
+ # For title page and partitions, we need to centre all text.
+ # For partition, we also add a page break before, and for
+ # both types we always add a page break after the content.
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)
-
- self.theTokens.append((self.T_PBREAK, "", None, self.A_LEFT))
+ if isTitle:
+ self.theTokens[n] = (
+ tType,
+ tText,
+ tFormat,
+ self.A_CENTRE
+ )
+ else:
+ self.theTokens[n] = (
+ tType,
+ tText,
+ tFormat,
+ self.A_CENTRE | self.A_PBB_R
+ )
+ self.theTokens.append((
+ self.T_PBREAK,
+ "",
+ None,
+ None
+ ))
return
diff --git a/nw/gui/build.py b/nw/gui/build.py
index 455c7ec7..0cca7332 100644
--- a/nw/gui/build.py
+++ b/nw/gui/build.py
@@ -129,21 +129,6 @@ class GuiBuildNovel(QDialog):
self.titleForm.setColumnStretch(0, 1)
self.titleForm.setColumnStretch(1, 0)
- # Text Options
- # =============
- self.textGroup = QGroupBox("Text Options", self)
- self.textForm = QGridLayout(self)
- self.textGroup.setLayout(self.textForm)
-
- self.justifyText = QSwitch()
- self.justifyText.setChecked(self.optState.getBool("GuiBuildNovel", "justifyText", False))
-
- self.textForm.addWidget(QLabel("Justify text"), 0, 0)
- self.textForm.addWidget(self.justifyText, 0, 1)
-
- self.textForm.setColumnStretch(0, 1)
- self.textForm.setColumnStretch(1, 0)
-
# Build Settings
# ==============
self.buildGroup = QGroupBox("Build Overrides", self)
@@ -159,6 +144,21 @@ class GuiBuildNovel(QDialog):
self.buildForm.setColumnStretch(0, 1)
self.buildForm.setColumnStretch(1, 0)
+ # Text Options
+ # =============
+ self.textGroup = QGroupBox("Text Options", self)
+ self.textForm = QGridLayout(self)
+ self.textGroup.setLayout(self.textForm)
+
+ self.justifyText = QSwitch()
+ self.justifyText.setChecked(self.optState.getBool("GuiBuildNovel", "justifyText", False))
+
+ self.textForm.addWidget(QLabel("Justify text"), 0, 0)
+ self.textForm.addWidget(self.justifyText, 0, 1)
+
+ self.textForm.setColumnStretch(0, 1)
+ self.textForm.setColumnStretch(1, 0)
+
# Include Switches
# ================
self.includeGroup = QGroupBox("Include Non-Text Elements", self)
@@ -230,7 +230,7 @@ class GuiBuildNovel(QDialog):
self.saveODT.triggered.connect(lambda: self._saveDocument(self.FMT_ODT))
self.saveMenu.addAction(self.saveODT)
- # self.savePDF = QAction("Portable Document (.pdf)")
+ # self.savePDF = QAction("Portable Document Format (.pdf)")
# self.savePDF.triggered.connect(lambda: self._saveDocument(self.FMT_PDF))
# self.saveMenu.addAction(self.savePDF)
@@ -238,13 +238,14 @@ class GuiBuildNovel(QDialog):
self.saveHTM1.triggered.connect(lambda: self._saveDocument(self.FMT_HTM1))
self.saveMenu.addAction(self.saveHTM1)
- self.saveHTM2 = QAction("Plain HTML (.htm)")
+ self.saveHTM2 = QAction("%s HTML (.htm)" % nw.__package__)
self.saveHTM2.triggered.connect(lambda: self._saveDocument(self.FMT_HTM2))
self.saveMenu.addAction(self.saveHTM2)
- # self.saveMD = QAction("Markdown (.md)")
- # self.saveMD.triggered.connect(lambda: self._saveDocument(self.FMT_MD))
- # self.saveMenu.addAction(self.saveMD)
+ if self.mainConf.verQtValue >= 51400:
+ self.saveMD = QAction("Markdown (.md)")
+ self.saveMD.triggered.connect(lambda: self._saveDocument(self.FMT_MD))
+ self.saveMenu.addAction(self.saveMD)
self.saveTXT = QAction("Plain Text (.txt)")
self.saveTXT.triggered.connect(lambda: self._saveDocument(self.FMT_TXT))
@@ -261,8 +262,8 @@ class GuiBuildNovel(QDialog):
# Assemble GUI
# ============
self.toolsBox.addWidget(self.titleGroup)
- self.toolsBox.addWidget(self.textGroup)
self.toolsBox.addWidget(self.buildGroup)
+ self.toolsBox.addWidget(self.textGroup)
self.toolsBox.addWidget(self.includeGroup)
self.toolsBox.addWidget(self.addsGroup)
self.toolsBox.addStretch(1)
From 7c1f3144bd3f55905e8d5647b9a838c152eb14bf Mon Sep 17 00:00:00 2001
From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com>
Date: Mon, 11 May 2020 19:00:01 +0200
Subject: [PATCH 20/53] Reworked the title formatting functionality. All
keywords can be used in all headings, and also added scene numbers.
---
nw/assets/text/exportHelp_en.htm | 31 ++++++-----
nw/core/tokenizer.py | 93 ++++++++++++++++++--------------
nw/gui/build.py | 12 ++---
sample/sampleNovel/nwProject.nwx | 10 ++--
4 files changed, 82 insertions(+), 64 deletions(-)
diff --git a/nw/assets/text/exportHelp_en.htm b/nw/assets/text/exportHelp_en.htm
index df8eca2a..e921f05d 100644
--- a/nw/assets/text/exportHelp_en.htm
+++ b/nw/assets/text/exportHelp_en.htm
@@ -1,25 +1,30 @@
Help!
-
A brief guide to make the most out of the Build Project tool.
+
A brief guide to make the most out of the Build Novel Project tool.
Novel Title Formats
The format of the various title levels in the files under the Novel folder can be customised in
- these settings. The actual title given in the headings of your files will replace all
- occurrences of the keyword %title%. Any static text will be left as-is in the
+ these settings. The actual title given in the headings of your files will for instance replace
+ all occurrences of the keyword %title%. Any static text will be left as-is in the
final title. An empty field means the title isn't written out at all.
The available formatting keywords are:
%title% – This is replaced with the text you put in your headings in your
documents
-
%num% – This is replaced with the chapter number of your chapter type
- headings. These are generated automaticall starting from 1.
-
%numword% – This is replaced with the chapter number of your chapter type
- headings, but instead of an arabic number, the word for it is used instead, e.g. One, Two,
- Fifteen, Twenty-Five, etc.
+
%chnum% – This is replaced with the chapter number of your chapter type
+ headings. These are generated automaticall starting from 1, but ignoring chapter headings in
+ files with "Unnumbered" layout.
+
%chnumword% – This is replaced with the chapter number, but instead of an
+ arabic number, the word for it is used, e.g. One, Two, Fifteen, Twenty-Five, etc.
+
%scnum% – This is replaced with the scene number. The number is reset to one
+ for each new chapter, so it is the scene number within the current chapter.
+
%scabsnum% – This is replaced with the absolute scene number. That is, the
+ number is counted from the first scene in the novel, and not reset for each chapter.
\\ – Two backslashes are replaced by a line break.
-
Note: The Scene format is treated slightly differently than the other title formats. If a
- scene format is a constant text, that is, contains no %title%, it will be treated
- as a scene separator instead. Scene separators are centred, and not shown if the chapter starts
- directly on the first scene. If the field is blank, a large space between the scenes is added
- instead.
+
Note: The Scene and Section formats are treated slightly differently than the other title
+ formats. If the format is a constant text, that is, contains no %keyword% tags, it
+ will be treated as a separator instead. Scene and Section separators are centred, and for
+ scenes, not shown if placed directly after the chapter heading. For instance, it you want the
+ classic three asterisk * * * separator between scenes, just put that into the
+ scene format box, and nothing else.
Build Overrides
Novel Outline Mode: This option will build an outline version of the novel rather than the
diff --git a/nw/core/tokenizer.py b/nw/core/tokenizer.py
index 84fbb100..f9d64d45 100644
--- a/nw/core/tokenizer.py
+++ b/nw/core/tokenizer.py
@@ -104,6 +104,8 @@ class Tokenizer():
# Instance Variables
self.numChapter = 0 # Counter for chapter numbers
+ self.numChScene = 0 # Counter for scene number within chapter
+ self.numAbsScene = 0 # Counter for scene number within novel
self.firstScene = False # Flag to indicate that the first scene of the chapter
return
@@ -370,23 +372,53 @@ class Tokenizer():
tType = tToken[0]
tText = tToken[1]
+ # In case we see text before a scene, we reset the flag
if tType == self.T_TEXT:
self.firstScene = False
- elif tType == self.T_HEAD2: # Novel Chapter
- if not isUnNum:
- self.numChapter += 1
- tText = self._formatChapter(tText,isUnNum)
+ elif tType == self.T_HEAD1:
+ # Main Title
+ # ==========
+
+ tText = self._formatHeading(self.fmtTitle, tText)
self.theTokens[n] = (
tType,
tText,
None,
self.A_LEFT | self.A_PBB_R
)
- self.firstScene = True
- elif tType == self.T_HEAD3: # Novel Scene
- tTemp = self._formatScene(tText)
+ elif tType == self.T_HEAD2:
+ # Novel Chapter
+ # =============
+
+ # Numbered or Unnumbered
+ if isUnNum:
+ tText = self._formatHeading(self.fmtUnNum, tText)
+ else:
+ self.numChapter += 1
+ tText = self._formatHeading(self.fmtChapter, tText)
+
+ # Format the chapter header
+ self.theTokens[n] = (
+ tType,
+ tText,
+ None,
+ self.A_LEFT | self.A_PBB_R
+ )
+
+ # Set scene variables
+ self.firstScene = True
+ self.numChScene = 0
+
+ elif tType == self.T_HEAD3:
+ # Novel Scene
+ # ===========
+
+ self.numChScene += 1
+ self.numAbsScene += 1
+
+ tTemp = self._formatHeading(self.fmtScene, tText)
if tTemp == "" and self.hideScene:
self.theTokens[n] = (
self.T_EMPTY,
@@ -431,10 +463,15 @@ class Tokenizer():
None,
self.A_LEFT | self.A_PBA_AV
)
+
+ # Definitely no longer the first scene
self.firstScene = False
- elif tType == self.T_HEAD4: # Novel Section
- tTemp = self._formatSection(tText)
+ elif tType == self.T_HEAD4:
+ # Novel Section
+ # =============
+
+ tTemp = self._formatHeading(self.fmtSection, tText)
if tTemp == "" and self.hideSection:
self.theTokens[n] = (
self.T_EMPTY,
@@ -500,38 +537,14 @@ class Tokenizer():
# Internal Functions
##
- def _formatTitle(self, theText):
- """Replace tokens for headers level 1.
+ def _formatHeading(self, theTitle, theText):
+ """Replaces the %keyword% strings.
"""
- theTitle = self.fmtTitle
- theTitle = theTitle.replace("%title%", theText)
- return theTitle
-
- def _formatChapter(self, theText, noNum):
- """Replace tokens for headers level 2.
- """
- 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 _formatScene(self, theText):
- """Replace tokens for headers level 3.
- """
- theTitle = self.fmtScene
- theTitle = theTitle.replace("%title%", theText)
- return theTitle
-
- def _formatSection(self, theText):
- """Replace tokens for headers level 4.
- """
- theTitle = self.fmtSection
- theTitle = theTitle.replace("%title%", theText)
+ theTitle = theTitle.replace(r"%title%", theText)
+ theTitle = theTitle.replace(r"%chnum%", str(self.numChapter))
+ theTitle = theTitle.replace(r"%scnum%", str(self.numChScene))
+ theTitle = theTitle.replace(r"%scabsnum%", str(self.numAbsScene))
+ theTitle = theTitle.replace(r"%chnumword%", numberToWord(self.numChapter,"en"))
return theTitle
# END Class Tokenizer
diff --git a/nw/gui/build.py b/nw/gui/build.py
index 0cca7332..bac21b87 100644
--- a/nw/gui/build.py
+++ b/nw/gui/build.py
@@ -69,7 +69,7 @@ class GuiBuildNovel(QDialog):
self.htmlText = ""
- self.setWindowTitle("Build Project")
+ self.setWindowTitle("Build Novel Project")
self.setMinimumWidth(800)
self.setMinimumHeight(800)
@@ -315,11 +315,11 @@ class GuiBuildNovel(QDialog):
doBodyText = True
if outlineMode:
- fmtTitle = "%title%"
- fmtChapter = "Chapter: %title%"
- fmtUnnumbered = "Chapter: %title%"
- fmtScene = "Scene: %title%"
- fmtSection = "Section: %title%"
+ fmtTitle = r"%title%"
+ fmtChapter = r"Chapter %chnum%: %title%"
+ fmtUnnumbered = r"%title%"
+ fmtScene = r"Scene %chnum%.%scnum%: %title%"
+ fmtSection = r"Section: %title%"
doBodyText = False
incSynopsis = True
novelFiles = True
diff --git a/sample/sampleNovel/nwProject.nwx b/sample/sampleNovel/nwProject.nwx
index eb89f47c..97d54bc9 100644
--- a/sample/sampleNovel/nwProject.nwx
+++ b/sample/sampleNovel/nwProject.nwx
@@ -1,5 +1,5 @@
-
+Sample ProjectSample Project
@@ -10,8 +10,8 @@
TrueTrue
- 6a2d6d5f4f401
- b3e74dbc1f584
+ 96b68994dfa3d
+ 6a2d6d5f4f401875B
@@ -20,9 +20,9 @@
%title%
- Chapter %num%.\\%title%
+ Chapter %chnum%.\\%title%%title%
- * * *
+ Scene %chnum%.%scnum%: %title%TrueFalse
From 8dca20e3aa9b1127de268ec4c83a3da3cbd78658 Mon Sep 17 00:00:00 2001
From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com>
Date: Tue, 12 May 2020 00:10:03 +0200
Subject: [PATCH 21/53] Added printing and save to PDF, and made some changes
to the tohtml class
---
nw/core/tohtml.py | 62 ++++++++------
nw/core/tokenizer.py | 195 ++++++++++++++++---------------------------
nw/gui/build.py | 99 +++++++++++++++-------
nw/gui/mainmenu.py | 2 +-
4 files changed, 182 insertions(+), 176 deletions(-)
diff --git a/nw/core/tohtml.py b/nw/core/tohtml.py
index ec3cec22..20c32e58 100644
--- a/nw/core/tohtml.py
+++ b/nw/core/tohtml.py
@@ -30,15 +30,19 @@ import re
import nw
from nw.core.tokenizer import Tokenizer
-from nw.constants import nwUnicode, nwLabels
+from nw.constants import nwUnicode, nwLabels, nwKeyWords
logger = logging.getLogger(__name__)
class ToHtml(Tokenizer):
+ M_PREVIEW = 0 # Tweak output for the DocViewer
+ M_EXPORT = 1 # Tweak output for saving to HTML or printing
+ M_EBOOK = 2 # Tweak output for converting to epub
+
def __init__(self, theProject, theParent):
Tokenizer.__init__(self, theProject, theParent)
- self.forPreview = False
+ self.genMode = self.M_EXPORT
return
##
@@ -47,11 +51,11 @@ class ToHtml(Tokenizer):
def setPreview(self, forPreview, doComments):
"""If we're using this class to generate markdown preview, we
- need to make a few changes to formatting, which is selected by
- this flag.
+ need to make a few changes to formatting, which is managed by
+ these flags.
"""
- self.forPreview = forPreview
if forPreview:
+ self.genMode = self.M_PREVIEW
self.doKeywords = True
self.doComments = doComments
return
@@ -66,7 +70,7 @@ class ToHtml(Tokenizer):
"""
Tokenizer.doAutoReplace(self)
- if self.forPreview:
+ if self.genMode == self.M_PREVIEW:
tabFmt = " "*8
else:
tabFmt = " "
@@ -102,6 +106,7 @@ class ToHtml(Tokenizer):
self.theResult = ""
thisPar = []
+ parStyle = ""
for tType, tText, tFormat, tStyle in self.theTokens:
# Styles
@@ -141,8 +146,9 @@ class ToHtml(Tokenizer):
if tType == self.T_EMPTY:
if len(thisPar) > 0:
tTemp = "".join(thisPar)
- self.theResult += "
\n" % hStyle
elif tType == self.T_TEXT:
tTemp = tText
+ parStyle = hStyle
for xPos, xLen, xFmt in reversed(tFormat):
tTemp = tTemp[:xPos]+htmlTags[xFmt]+tTemp[xPos+xLen:]
if tText.endswith(" "):
@@ -196,20 +200,18 @@ class ToHtml(Tokenizer):
def _formatSynopsis(self, tText):
"""Apply HTML formatting to synopsis.
"""
-
- if not self.forPreview:
+ if self.genMode == self.M_EXPORT:
return "
Synopsis: %s
\n" % tText
-
- return "
%s
\n" % tText
+ else:
+ return "
%s
\n" % tText
def _formatComments(self, tText):
"""Apply HTML formatting to comments.
"""
-
- if not self.forPreview:
+ if self.genMode == self.M_EXPORT:
return "
Comment: %s
\n" % tText
-
- return "
%s
\n" % tText
+ else:
+ return "
%s
\n" % tText
def _formatKeywords(self, tText):
"""Apply HTML formatting to keywords.
@@ -224,11 +226,23 @@ class ToHtml(Tokenizer):
refTags = []
if theBits[0] in nwLabels.KEY_NAME:
retText += "%s: " % nwLabels.KEY_NAME[theBits[0]]
- for tTag in theBits[1:]:
- refTags.append("%s" % (
- theBits[0][1:], tTag, tTag
- ))
- retText += ", ".join(refTags)
+ if self.genMode == self.M_PREVIEW:
+ for tTag in theBits[1:]:
+ refTags.append("%s" % (
+ theBits[0][1:], tTag, tTag
+ ))
+ retText += ", ".join(refTags)
+ else:
+ if theBits[0] == nwKeyWords.TAG_KEY:
+ retText += "%s" % (
+ theBits[1], theBits[1]
+ )
+ else:
+ for tTag in theBits[1:]:
+ refTags.append("%s" % (
+ tTag, tTag
+ ))
+ retText += ", ".join(refTags)
return "
%s
" % retText
diff --git a/nw/core/tokenizer.py b/nw/core/tokenizer.py
index f9d64d45..20d89623 100644
--- a/nw/core/tokenizer.py
+++ b/nw/core/tokenizer.py
@@ -58,7 +58,6 @@ class Tokenizer():
T_TEXT = 9 # Text line
T_SEP = 10 # Scene separator
T_SKIP = 11 # Paragraph break
- T_PBREAK = 12 # Page break
A_LEFT = 1 # Left aligned
A_RIGHT = 2 # Right aligned
@@ -108,6 +107,18 @@ class Tokenizer():
self.numAbsScene = 0 # Counter for scene number within novel
self.firstScene = False # Flag to indicate that the first scene of the chapter
+ # This File
+ self.isNone = False
+ self.isTitle = False
+ self.isBook = False
+ self.isPage = False
+ self.isPart = False
+ self.isUnNum = False
+ self.isChap = False
+ self.isScene = False
+ self.isNote = False
+ self.isNovel = False
+
return
def clearData(self):
@@ -121,6 +132,18 @@ class Tokenizer():
self.theResult = None
self.numChapter = 0
self.firstScene = False
+
+ self.isNone = False
+ self.isTitle = False
+ self.isBook = False
+ self.isPage = False
+ self.isPart = False
+ self.isUnNum = False
+ self.isChap = False
+ self.isScene = False
+ self.isNote = False
+ self.isNovel = False
+
return
##
@@ -189,6 +212,17 @@ class Tokenizer():
theDocument = NWDoc(self.theProject, self.theParent)
self.theText = theDocument.openDocument(theHandle)
+ self.isNone = self.theItem.itemLayout == nwItemLayout.NO_LAYOUT
+ self.isTitle = self.theItem.itemLayout == nwItemLayout.TITLE
+ self.isBook = self.theItem.itemLayout == nwItemLayout.BOOK
+ self.isPage = self.theItem.itemLayout == nwItemLayout.PAGE
+ self.isPart = self.theItem.itemLayout == nwItemLayout.PARTITION
+ self.isUnNum = self.theItem.itemLayout == nwItemLayout.UNNUMBERED
+ self.isChap = self.theItem.itemLayout == nwItemLayout.CHAPTER
+ self.isScene = self.theItem.itemLayout == nwItemLayout.SCENE
+ self.isNote = self.theItem.itemLayout == nwItemLayout.NOTE
+ self.isNovel = self.isBook or self.isUnNum or self.isChap or self.isScene
+
return
def getResult(self):
@@ -251,61 +285,37 @@ class Tokenizer():
# Tag lines starting with specific characters
if len(aLine.strip()) == 0:
self.theTokens.append((
- self.T_EMPTY,
- "",
- None,
- None
+ self.T_EMPTY, "", None, None
))
elif aLine[0] == "%":
cLine = aLine[1:].strip()
if cLine.lower().startswith("synopsis:"):
self.theTokens.append((
- self.T_SYNOPSIS,
- cLine[9:].strip(),
- None,
- defAlign
+ self.T_SYNOPSIS, cLine[9:].strip(), None, defAlign
))
else:
self.theTokens.append((
- self.T_COMMENT,
- aLine[1:].strip(),
- None,
- defAlign
+ self.T_COMMENT, aLine[1:].strip(), None, defAlign
))
elif aLine[0] == "@":
self.theTokens.append((
- self.T_KEYWORD,
- aLine[1:].strip(),
- None,
- self.A_LEFT
+ self.T_KEYWORD, aLine[1:].strip(), None, self.A_LEFT
))
elif aLine[:2] == "# ":
self.theTokens.append((
- self.T_HEAD1,
- aLine[2:].strip(),
- None,
- self.A_LEFT | self.A_PBB
+ self.T_HEAD1, aLine[2:].strip(), None, self.A_LEFT | self.A_PBB
))
elif aLine[:3] == "## ":
self.theTokens.append((
- self.T_HEAD2,
- aLine[3:].strip(),
- None,
- self.A_LEFT | self.A_PBA_AV
+ self.T_HEAD2, aLine[3:].strip(), None, self.A_LEFT | self.A_PBA_AV
))
elif aLine[:4] == "### ":
self.theTokens.append((
- self.T_HEAD3,
- aLine[4:].strip(),
- None,
- self.A_LEFT | self.A_PBA_AV
+ self.T_HEAD3, aLine[4:].strip(), None, self.A_LEFT | self.A_PBA_AV
))
elif aLine[:5] == "#### ":
self.theTokens.append((
- self.T_HEAD4,
- aLine[5:].strip(),
- None,
- self.A_LEFT | self.A_PBA_AV
+ self.T_HEAD4, aLine[5:].strip(), None, self.A_LEFT | self.A_PBA_AV
))
else:
if not self.doBodyText:
@@ -328,18 +338,12 @@ class Tokenizer():
# sorted by position
fmtPos = sorted(fmtPos, key=itemgetter(0))
self.theTokens.append((
- self.T_TEXT,
- aLine,
- fmtPos,
- defAlign
+ self.T_TEXT, aLine, fmtPos, defAlign
))
# Always add an empty line at the end
self.theTokens.append((
- self.T_EMPTY,
- "",
- None,
- None
+ self.T_EMPTY, "", None, None
))
return
@@ -349,23 +353,13 @@ class Tokenizer():
layout and user settings.
"""
- 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 or isNote:
+ if self.isNone or self.isNote:
return
# For novel files, we need to handle chapter numbering and scene
# breaks
- if isBook or isUnNum or isChap or isScene:
+ if self.isNovel:
for n in range(len(self.theTokens)):
tToken = self.theTokens[n]
@@ -382,10 +376,7 @@ class Tokenizer():
tText = self._formatHeading(self.fmtTitle, tText)
self.theTokens[n] = (
- tType,
- tText,
- None,
- self.A_LEFT | self.A_PBB_R
+ tType, tText, None, self.A_LEFT | self.A_PBB_R
)
elif tType == self.T_HEAD2:
@@ -393,7 +384,7 @@ class Tokenizer():
# =============
# Numbered or Unnumbered
- if isUnNum:
+ if self.isUnNum:
tText = self._formatHeading(self.fmtUnNum, tText)
else:
self.numChapter += 1
@@ -401,10 +392,7 @@ class Tokenizer():
# Format the chapter header
self.theTokens[n] = (
- tType,
- tText,
- None,
- self.A_LEFT | self.A_PBB_R
+ tType, tText, None, self.A_LEFT | self.A_PBB_R
)
# Set scene variables
@@ -421,47 +409,29 @@ class Tokenizer():
tTemp = self._formatHeading(self.fmtScene, tText)
if tTemp == "" and self.hideScene:
self.theTokens[n] = (
- self.T_EMPTY,
- "",
- None,
- None
+ self.T_EMPTY, "", None, None
)
elif tTemp == "" and not self.hideScene:
if self.firstScene:
self.theTokens[n] = (
- self.T_EMPTY,
- "",
- None,
- None
+ self.T_EMPTY, "", None, None
)
else:
self.theTokens[n] = (
- self.T_SKIP,
- "",
- None,
- None
+ self.T_SKIP, "", None, None
)
elif tTemp == self.fmtScene:
if self.firstScene:
self.theTokens[n] = (
- self.T_EMPTY,
- "",
- None,
- None
+ self.T_EMPTY, "", None, None
)
else:
self.theTokens[n] = (
- self.T_SEP,
- tTemp,
- None,
- self.A_CENTRE
+ self.T_SEP, tTemp, None, self.A_CENTRE
)
else:
self.theTokens[n] = (
- tType,
- tTemp,
- None,
- self.A_LEFT | self.A_PBA_AV
+ tType, tTemp, None, self.A_LEFT | self.A_PBA_AV
)
# Definitely no longer the first scene
@@ -474,62 +444,41 @@ class Tokenizer():
tTemp = self._formatHeading(self.fmtSection, tText)
if tTemp == "" and self.hideSection:
self.theTokens[n] = (
- self.T_EMPTY,
- "",
- None,
- None
+ self.T_EMPTY, "", None, None
)
elif tTemp == "" and not self.hideSection:
self.theTokens[n] = (
- self.T_SKIP,
- "",
- None,
- None
+ self.T_SKIP, "", None, None
)
elif tTemp == self.fmtSection:
self.theTokens[n] = (
- self.T_SEP,
- tTemp,
- None,
- self.A_CENTRE
+ self.T_SEP, tTemp, None, self.A_CENTRE
)
else:
self.theTokens[n] = (
- tType,
- tTemp,
- None,
- self.A_LEFT | self.A_PBA_AV
+ tType, tTemp, None, self.A_LEFT | self.A_PBA_AV
)
# For title page and partitions, we need to centre all text.
# For partition, we also add a page break before, and for
# both types we always add a page break after the content.
- if isTitle or isPart:
- for n in range(len(self.theTokens)):
- tToken = self.theTokens[n]
+ if self.isTitle or self.isPart:
+ for n, tToken in enumerate(self.theTokens):
tType = tToken[0]
tText = tToken[1]
tFormat = tToken[2]
- if isTitle:
+ if self.isTitle:
self.theTokens[n] = (
- tType,
- tText,
- tFormat,
- self.A_CENTRE
+ tType, tText, tFormat, self.A_CENTRE
)
- else:
- self.theTokens[n] = (
- tType,
- tText,
- tFormat,
- self.A_CENTRE | self.A_PBB_R
- )
- self.theTokens.append((
- self.T_PBREAK,
- "",
- None,
- None
- ))
+
+ # Add a page break after the last entry
+ n = len(self.theTokens) - 1
+ if n >= 0:
+ tToken = self.theTokens[n]
+ self.theTokens[n] = (
+ tToken[0], tToken[1], tToken[2], tToken[3] | self.A_PBA
+ )
return
diff --git a/nw/gui/build.py b/nw/gui/build.py
index bac21b87..43457d2c 100644
--- a/nw/gui/build.py
+++ b/nw/gui/build.py
@@ -31,6 +31,7 @@ import nw
from os import path
from PyQt5.QtCore import Qt, QByteArray
+from PyQt5.QtPrintSupport import QPrinter, QPrintPreviewDialog
from PyQt5.QtGui import (
QTextOption, QPalette, QColor, QTextDocumentWriter
)
@@ -230,9 +231,9 @@ class GuiBuildNovel(QDialog):
self.saveODT.triggered.connect(lambda: self._saveDocument(self.FMT_ODT))
self.saveMenu.addAction(self.saveODT)
- # self.savePDF = QAction("Portable Document Format (.pdf)")
- # self.savePDF.triggered.connect(lambda: self._saveDocument(self.FMT_PDF))
- # self.saveMenu.addAction(self.savePDF)
+ self.savePDF = QAction("Portable Document Format (.pdf)")
+ self.savePDF.triggered.connect(lambda: self._saveDocument(self.FMT_PDF))
+ self.saveMenu.addAction(self.savePDF)
self.saveHTM1 = QAction("Qt Style HTML (.htm)")
self.saveHTM1.triggered.connect(lambda: self._saveDocument(self.FMT_HTM1))
@@ -406,10 +407,15 @@ class GuiBuildNovel(QDialog):
# Create the settings
if theFormat == self.FMT_ODT:
byteFmt.append("odf")
- fileExt = "odf"
+ fileExt = "odt"
textFmt = "Open Document"
outTool = "Qt"
+ elif theFormat == self.FMT_PDF:
+ fileExt = "pdf"
+ textFmt = "PDF"
+ outTool = "QtPrint"
+
elif theFormat == self.FMT_HTM1:
byteFmt.append("html")
fileExt = "htm"
@@ -479,32 +485,56 @@ class GuiBuildNovel(QDialog):
), nwAlert.ERROR
)
- elif outTool == "NW":
- if theFormat == self.FMT_HTM2:
- try:
- with open(savePath, mode="w", encoding="utf8") as outFile:
- outFile.write("\n")
- outFile.write("\n")
- outFile.write("\n")
- outFile.write("\n")
- outFile.write("\n")
- outFile.write("\n")
- outFile.write(self.htmlText)
- outFile.write("\n")
- outFile.write("\n")
+ elif outTool == "NW" and theFormat == self.FMT_HTM2:
+ try:
+ with open(savePath, mode="w", encoding="utf8") as outFile:
+ outFile.write("\n")
+ outFile.write("\n")
+ outFile.write("\n")
+ outFile.write("\n")
+ outFile.write("\n")
+ outFile.write("\n")
+ outFile.write("\n")
+ outFile.write(self.htmlText)
+ outFile.write("\n")
+ outFile.write("\n")
+ outFile.write("\n")
- self.theParent.makeAlert(
- "Document successfully written in %s format to file: %s" % (
- textFmt, savePath
- ), nwAlert.INFO
- )
+ self.theParent.makeAlert(
+ "Document successfully written in %s format to file: %s" % (
+ textFmt, savePath
+ ), nwAlert.INFO
+ )
- except Exception as e:
- self.theParent.makeAlert(
- "Failed to write document in %s format to file: %s" % (
- textFmt, str(e)
- ), nwAlert.ERROR
- )
+ except Exception as e:
+ self.theParent.makeAlert(
+ "Failed to write document in %s format to file: %s" % (
+ textFmt, str(e)
+ ), nwAlert.ERROR
+ )
+
+ elif outTool == "QtPrint" and theFormat == self.FMT_PDF:
+ try:
+ thePrinter = QPrinter()
+ thePrinter.setOutputFormat(QPrinter.PdfFormat)
+ thePrinter.setOrientation(QPrinter.Portrait)
+ thePrinter.setDuplex(QPrinter.DuplexLongSide)
+ thePrinter.setFontEmbeddingEnabled(True)
+ thePrinter.setColorMode(QPrinter.Color)
+ thePrinter.setOutputFileName(savePath)
+ self.docView.qDocument.print(thePrinter)
+ self.theParent.makeAlert(
+ "Document successfully written in %s format to file: %s" % (
+ textFmt, savePath
+ ), nwAlert.INFO
+ )
+
+ except Exception as e:
+ self.theParent.makeAlert(
+ "Failed to write document in %s format to file: %s" % (
+ textFmt, str(e)
+ ), nwAlert.ERROR
+ )
else:
return False
@@ -512,6 +542,19 @@ class GuiBuildNovel(QDialog):
return True
def _printDocument(self):
+ """Open the print preview dialog.
+ """
+ thePreview = QPrintPreviewDialog(self)
+ thePreview.paintRequested.connect(self._doPrintPreview)
+ thePreview.exec_()
+ return
+
+ def _doPrintPreview(self, thePrinter):
+ """Connect the print preview painter to the document viewer.
+ """
+ thePrinter.setOrientation(QPrinter.Portrait)
+ thePrinter.setOutputFormat(QPrinter.NativeFormat | QPrinter.PdfFormat)
+ self.docView.qDocument.print(thePrinter)
return
def _toggelOutlineMode(self, theState):
diff --git a/nw/gui/mainmenu.py b/nw/gui/mainmenu.py
index ddc60daa..56ec0337 100644
--- a/nw/gui/mainmenu.py
+++ b/nw/gui/mainmenu.py
@@ -352,7 +352,7 @@ class GuiMainMenu(QMenuBar):
self.docuMenu.addAction(self.aCloseView)
# Document > Toggle View Comments
- self.aViewDocComments = QAction("View Comments", self)
+ self.aViewDocComments = QAction("Show Comments", self)
self.aViewDocComments.setStatusTip("Show comments in view panel")
self.aViewDocComments.setCheckable(True)
self.aViewDocComments.setChecked(self.mainConf.viewComments)
From 2a67b69d3256a3fd01ad62095d1a865cde8fcea0 Mon Sep 17 00:00:00 2001
From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com>
Date: Tue, 12 May 2020 18:10:45 +0200
Subject: [PATCH 22/53] Replaced 'Outline Mode' with an 'Exclude body text'
option instead.
---
nw/gui/build.py | 55 +++++++------------------------------------------
1 file changed, 7 insertions(+), 48 deletions(-)
diff --git a/nw/gui/build.py b/nw/gui/build.py
index 43457d2c..d007ae70 100644
--- a/nw/gui/build.py
+++ b/nw/gui/build.py
@@ -130,21 +130,6 @@ class GuiBuildNovel(QDialog):
self.titleForm.setColumnStretch(0, 1)
self.titleForm.setColumnStretch(1, 0)
- # Build Settings
- # ==============
- self.buildGroup = QGroupBox("Build Overrides", self)
- self.buildForm = QGridLayout(self)
- self.buildGroup.setLayout(self.buildForm)
-
- self.outlineMode = QSwitch()
- self.outlineMode.setChecked(self.optState.getBool("GuiBuildNovel", "outlineMode", False))
-
- self.buildForm.addWidget(QLabel("Novel Outline Mode"), 0, 0)
- self.buildForm.addWidget(self.outlineMode, 0, 1)
-
- self.buildForm.setColumnStretch(0, 1)
- self.buildForm.setColumnStretch(1, 0)
-
# Text Options
# =============
self.textGroup = QGroupBox("Text Options", self)
@@ -195,6 +180,8 @@ class GuiBuildNovel(QDialog):
self.noteFiles.setChecked(self.optState.getBool("GuiBuildNovel", "addNotes", False))
self.ignoreFlag = QSwitch()
self.ignoreFlag.setChecked(self.optState.getBool("GuiBuildNovel", "ignoreFlag", False))
+ self.excludeBody = QSwitch()
+ self.excludeBody.setChecked(self.optState.getBool("GuiBuildNovel", "excludeBody", False))
self.addsForm.addWidget(QLabel("Include novel files"), 0, 0)
self.addsForm.addWidget(self.novelFiles, 0, 1)
@@ -202,6 +189,8 @@ class GuiBuildNovel(QDialog):
self.addsForm.addWidget(self.noteFiles, 1, 1)
self.addsForm.addWidget(QLabel("Ignore export flag"), 2, 0)
self.addsForm.addWidget(self.ignoreFlag, 2, 1)
+ self.addsForm.addWidget(QLabel("Exclude body text"), 3, 0)
+ self.addsForm.addWidget(self.excludeBody, 3, 1)
self.addsForm.setColumnStretch(0, 1)
self.addsForm.setColumnStretch(1, 0)
@@ -263,7 +252,6 @@ class GuiBuildNovel(QDialog):
# Assemble GUI
# ============
self.toolsBox.addWidget(self.titleGroup)
- self.toolsBox.addWidget(self.buildGroup)
self.toolsBox.addWidget(self.textGroup)
self.toolsBox.addWidget(self.includeGroup)
self.toolsBox.addWidget(self.addsGroup)
@@ -282,9 +270,6 @@ class GuiBuildNovel(QDialog):
self.innerBox.setStretch(0, 0)
self.innerBox.setStretch(1, 1)
- self.outlineMode.toggled.connect(self._toggelOutlineMode)
- self._toggelOutlineMode(self.outlineMode.isChecked())
-
self.show()
logger.debug("GuiBuildNovel initialisation complete")
@@ -306,25 +291,13 @@ class GuiBuildNovel(QDialog):
fmtScene = self.fmtScene.text().strip()
fmtSection = self.fmtSection.text().strip()
justifyText = self.justifyText.isChecked()
- outlineMode = self.outlineMode.isChecked()
incSynopsis = self.includeSynopsis.isChecked()
incComments = self.includeComments.isChecked()
incKeywords = self.includeKeywords.isChecked()
novelFiles = self.novelFiles.isChecked()
noteFiles = self.noteFiles.isChecked()
ignoreFlag = self.ignoreFlag.isChecked()
- doBodyText = True
-
- if outlineMode:
- fmtTitle = r"%title%"
- fmtChapter = r"Chapter %chnum%: %title%"
- fmtUnnumbered = r"%title%"
- fmtScene = r"Scene %chnum%.%scnum%: %title%"
- fmtSection = r"Section: %title%"
- doBodyText = False
- incSynopsis = True
- novelFiles = True
- noteFiles = False
+ excludeBody = self.excludeBody.isChecked()
makeHtml = ToHtml(self.theProject, self.theParent)
makeHtml.setTitleFormat(fmtTitle)
@@ -332,7 +305,7 @@ class GuiBuildNovel(QDialog):
makeHtml.setUnNumberedFormat(fmtUnnumbered)
makeHtml.setSceneFormat(fmtScene, fmtScene == "")
makeHtml.setSectionFormat(fmtSection, fmtSection == "")
- makeHtml.setBodyText(doBodyText)
+ makeHtml.setBodyText(not excludeBody)
makeHtml.setSynopsis(incSynopsis)
makeHtml.setComments(incComments)
makeHtml.setKeywords(incKeywords)
@@ -557,20 +530,6 @@ class GuiBuildNovel(QDialog):
self.docView.qDocument.print(thePrinter)
return
- def _toggelOutlineMode(self, theState):
- """Enables or disables the options that are overridden in#
- outline mode.
- """
- self.fmtTitle.setEnabled(not theState)
- self.fmtChapter.setEnabled(not theState)
- self.fmtUnnumbered.setEnabled(not theState)
- self.fmtScene.setEnabled(not theState)
- self.fmtSection.setEnabled(not theState)
- self.includeSynopsis.setEnabled(not theState)
- self.novelFiles.setEnabled(not theState)
- self.noteFiles.setEnabled(not theState)
- return
-
def _doClose(self):
"""Close button was clicked.
"""
@@ -613,10 +572,10 @@ class GuiBuildNovel(QDialog):
self.optState.setValue("GuiBuildNovel", "winWidth", self.width())
self.optState.setValue("GuiBuildNovel", "winHeight", self.height())
self.optState.setValue("GuiBuildNovel", "justifyText", self.justifyText.isChecked())
- self.optState.setValue("GuiBuildNovel", "outlineMode", self.outlineMode.isChecked())
self.optState.setValue("GuiBuildNovel", "addNovel", self.novelFiles.isChecked())
self.optState.setValue("GuiBuildNovel", "addNotes", self.noteFiles.isChecked())
self.optState.setValue("GuiBuildNovel", "ignoreFlag", self.ignoreFlag.isChecked())
+ self.optState.setValue("GuiBuildNovel", "excludeBody", self.excludeBody.isChecked())
self.optState.saveSettings()
return
From aa4778d4cd39985237a1c3bc4f4b8897b04f9343 Mon Sep 17 00:00:00 2001
From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com>
Date: Tue, 12 May 2020 18:41:08 +0200
Subject: [PATCH 23/53] Preserve markdown during build, and use lists instead
of strings
---
nw/core/tohtml.py | 25 ++++++++++++++----------
nw/core/tokenizer.py | 46 +++++++++++++++++++++++++++++++++++++-------
nw/gui/build.py | 17 ++++++++++++++--
3 files changed, 69 insertions(+), 19 deletions(-)
diff --git a/nw/core/tohtml.py b/nw/core/tohtml.py
index 20c32e58..7980d80b 100644
--- a/nw/core/tohtml.py
+++ b/nw/core/tohtml.py
@@ -105,8 +105,10 @@ class ToHtml(Tokenizer):
}
self.theResult = ""
+
thisPar = []
parStyle = ""
+ tmpResult = []
for tType, tText, tFormat, tStyle in self.theTokens:
# Styles
@@ -146,31 +148,31 @@ class ToHtml(Tokenizer):
if tType == self.T_EMPTY:
if len(thisPar) > 0:
tTemp = "".join(thisPar)
- self.theResult += "