Merge Novel Layouts (#837)
* Merge novel layouts and simplify the Tokenizer class * Fix core tests * Delete auto-layout code * Remove no longer needed item layouts * Remove references to deleted item layouts in test * Make some minor changes to Tokenizer class and update tests * Update test reference files * Update sample project * Fix a few issues with the Tokenizer * Centre some text in the sample documents * Make some minor changes to how new projects are generated * Add support for New Page and VSpace commands to the Tokenizer * Remove Title and Page layouts and add codes for page break and vertical space * Add document layout and fix some issues in index class with new title formats * Update tests and reference files * Bump the project file version and fix a minor issue in items class * Update tests and test coverage * Clean up some warnings and issues in tests
This commit is contained in:
committed by
GitHub
parent
c6973043e6
commit
bb1a778277
+9
-18
@@ -65,6 +65,9 @@ class nwLists():
|
||||
# Item classes which do not require items to have same class
|
||||
FREE_CLASS = {nwItemClass.ARCHIVE, nwItemClass.TRASH}
|
||||
|
||||
# Deprecated nwItemLayout entries
|
||||
DEP_LAYOUT = ("TITLE", "PAGE", "BOOK", "PARTITION", "UNNUMBERED", "CHAPTER", "SCENE", "STORY")
|
||||
|
||||
# END Class nwLists
|
||||
|
||||
|
||||
@@ -169,26 +172,14 @@ class nwLabels():
|
||||
nwItemClass.TRASH: "cls_trash",
|
||||
}
|
||||
LAYOUT_NAME = {
|
||||
nwItemLayout.NO_LAYOUT: QT_TRANSLATE_NOOP("Constant", "None"),
|
||||
nwItemLayout.TITLE: QT_TRANSLATE_NOOP("Constant", "Title Page"),
|
||||
nwItemLayout.BOOK: QT_TRANSLATE_NOOP("Constant", "Book"),
|
||||
nwItemLayout.PAGE: QT_TRANSLATE_NOOP("Constant", "Plain Page"),
|
||||
nwItemLayout.PARTITION: QT_TRANSLATE_NOOP("Constant", "Partition"),
|
||||
nwItemLayout.UNNUMBERED: QT_TRANSLATE_NOOP("Constant", "Unnumbered"),
|
||||
nwItemLayout.CHAPTER: QT_TRANSLATE_NOOP("Constant", "Chapter"),
|
||||
nwItemLayout.SCENE: QT_TRANSLATE_NOOP("Constant", "Scene"),
|
||||
nwItemLayout.NOTE: QT_TRANSLATE_NOOP("Constant", "Note"),
|
||||
nwItemLayout.NO_LAYOUT: QT_TRANSLATE_NOOP("Constant", "None"),
|
||||
nwItemLayout.DOCUMENT: QT_TRANSLATE_NOOP("Constant", "Novel Document"),
|
||||
nwItemLayout.NOTE: QT_TRANSLATE_NOOP("Constant", "Project Note"),
|
||||
}
|
||||
LAYOUT_FLAG = {
|
||||
nwItemLayout.NO_LAYOUT: "Xo",
|
||||
nwItemLayout.TITLE: "Tt",
|
||||
nwItemLayout.BOOK: "Bk",
|
||||
nwItemLayout.PAGE: "Pg",
|
||||
nwItemLayout.PARTITION: "Pt",
|
||||
nwItemLayout.UNNUMBERED: "Un",
|
||||
nwItemLayout.CHAPTER: "Ch",
|
||||
nwItemLayout.SCENE: "Sc",
|
||||
nwItemLayout.NOTE: "Nt",
|
||||
nwItemLayout.NO_LAYOUT: "Xo",
|
||||
nwItemLayout.DOCUMENT: "Dc",
|
||||
nwItemLayout.NOTE: "Nt",
|
||||
}
|
||||
KEY_NAME = {
|
||||
nwKeyWords.TAG_KEY: QT_TRANSLATE_NOOP("Constant", "Tag"),
|
||||
|
||||
+20
-1
@@ -354,6 +354,12 @@ class NWIndex():
|
||||
elif aLine.startswith("#### "):
|
||||
hDepth = "H4"
|
||||
hText = aLine[5:].strip()
|
||||
elif aLine.startswith("#! "):
|
||||
hDepth = "H1"
|
||||
hText = aLine[2:].strip()
|
||||
elif aLine.startswith("##! "):
|
||||
hDepth = "H2"
|
||||
hText = aLine[4:].strip()
|
||||
else:
|
||||
return False
|
||||
|
||||
@@ -861,7 +867,13 @@ def countWords(theText):
|
||||
if aLine[0] == "@" or aLine[0] == "%":
|
||||
continue
|
||||
|
||||
if aLine[0] == "#":
|
||||
if aLine[0] == "[":
|
||||
if aLine.startswith(("[NEWPAGE]", "[NEW PAGE]", "[VSPACE]")):
|
||||
continue
|
||||
elif aLine.startswith("[VSPACE:") and aLine.endswith("]"):
|
||||
continue
|
||||
|
||||
elif aLine[0] == "#":
|
||||
if aLine[:5] == "#### ":
|
||||
aLine = aLine[5:]
|
||||
countPara = False
|
||||
@@ -874,6 +886,13 @@ def countWords(theText):
|
||||
elif aLine[:2] == "# ":
|
||||
aLine = aLine[2:]
|
||||
countPara = False
|
||||
elif aLine[:3] == "#! ":
|
||||
aLine = aLine[3:]
|
||||
countPara = False
|
||||
elif aLine[:4] == "##! ":
|
||||
aLine = aLine[4:]
|
||||
countPara = False
|
||||
|
||||
elif aLine[0] == ">" or aLine[-1] == "<":
|
||||
if aLine[:2] == ">>":
|
||||
aLine = aLine[2:].lstrip(" ")
|
||||
|
||||
+4
-1
@@ -29,6 +29,7 @@ from lxml import etree
|
||||
|
||||
from nw.enum import nwItemType, nwItemClass, nwItemLayout
|
||||
from nw.common import checkInt, isHandle, isItemClass, isItemLayout, isItemType
|
||||
from nw.constants import nwLists
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -230,6 +231,8 @@ class NWItem():
|
||||
self.itemLayout = theLayout
|
||||
elif isItemLayout(theLayout):
|
||||
self.itemLayout = nwItemLayout[theLayout]
|
||||
elif theLayout in nwLists.DEP_LAYOUT:
|
||||
self.itemLayout = nwItemLayout.DOCUMENT
|
||||
else:
|
||||
logger.error("Unrecognised item layout '%s'", theLayout)
|
||||
self.itemLayout = nwItemLayout.NO_LAYOUT
|
||||
@@ -239,7 +242,7 @@ class NWItem():
|
||||
"""Set the item status by looking it up in the valid status
|
||||
items of the current project.
|
||||
"""
|
||||
if self.itemClass == nwItemClass.NOVEL:
|
||||
if self.itemClass in nwLists.CLS_NOVEL:
|
||||
self.itemStatus = self.theProject.statusItems.checkEntry(theStatus)
|
||||
else:
|
||||
self.itemStatus = self.theProject.importItems.checkEntry(theStatus)
|
||||
|
||||
+35
-20
@@ -52,6 +52,8 @@ logger = logging.getLogger(__name__)
|
||||
|
||||
class NWProject():
|
||||
|
||||
FILE_VERSION = "1.3"
|
||||
|
||||
def __init__(self, theParent):
|
||||
|
||||
# Internal
|
||||
@@ -142,14 +144,14 @@ class NWProject():
|
||||
return newItem.itemHandle
|
||||
|
||||
def newFile(self, fileName, fileClass, pHandle):
|
||||
"""Add a new file with a given name and class, and set a default
|
||||
layout based on the class. SCENE for NOVEL, and otherwise NOTE.
|
||||
"""Add a new file with a given name and class, and set a layout
|
||||
based on the class. DOCUMENT for NOVEL, otherwise NOTE.
|
||||
"""
|
||||
newItem = NWItem(self)
|
||||
newItem.setName(fileName)
|
||||
newItem.setType(nwItemType.FILE)
|
||||
if fileClass == nwItemClass.NOVEL:
|
||||
newItem.setLayout(nwItemLayout.SCENE)
|
||||
newItem.setLayout(nwItemLayout.DOCUMENT)
|
||||
else:
|
||||
newItem.setLayout(nwItemLayout.NOTE)
|
||||
newItem.setClass(fileClass)
|
||||
@@ -204,7 +206,7 @@ class NWProject():
|
||||
self.autoReplace = {}
|
||||
self.titleFormat = {
|
||||
"title": "%title%",
|
||||
"chapter": self.tr("Chapter")+" %ch%: %title%",
|
||||
"chapter": "%title%",
|
||||
"unnumbered": "%title%",
|
||||
"scene": "* * *",
|
||||
"section": "",
|
||||
@@ -261,9 +263,9 @@ class NWProject():
|
||||
self.setBookTitle(projTitle)
|
||||
self.setBookAuthors(projAuthors)
|
||||
|
||||
titlePage = "# %s\n\n" % (self.bookTitle if self.bookTitle else self.projName)
|
||||
titlePage = "#! %s\n\n" % (self.bookTitle if self.bookTitle else self.projName)
|
||||
if self.bookAuthors:
|
||||
titlePage = "%s%s %s\n" % (titlePage, self.tr("By"), self.getAuthors())
|
||||
titlePage = "%s>> %s %s <<\n" % (titlePage, self.tr("By"), self.getAuthors())
|
||||
|
||||
if popMinimal:
|
||||
# Creating a minimal project with a few root folders and a
|
||||
@@ -278,9 +280,6 @@ class NWProject():
|
||||
xHandle[7] = self.newFile(self.tr("New Chapter"), nwItemClass.NOVEL, xHandle[6])
|
||||
xHandle[8] = self.newFile(self.tr("New Scene"), nwItemClass.NOVEL, xHandle[6])
|
||||
|
||||
self.projTree.setFileItemLayout(xHandle[5], nwItemLayout.TITLE)
|
||||
self.projTree.setFileItemLayout(xHandle[7], nwItemLayout.CHAPTER)
|
||||
|
||||
aDoc = NWDoc(self, xHandle[5])
|
||||
aDoc.writeDocument(titlePage)
|
||||
|
||||
@@ -303,7 +302,7 @@ class NWProject():
|
||||
|
||||
# Create a title page
|
||||
tHandle = self.newFile(self.tr("Title Page"), nwItemClass.NOVEL, nHandle)
|
||||
self.projTree.setFileItemLayout(tHandle, nwItemLayout.TITLE)
|
||||
self.projTree.setFileItemLayout(tHandle, nwItemLayout.DOCUMENT)
|
||||
|
||||
aDoc = NWDoc(self, tHandle)
|
||||
aDoc.writeDocument(titlePage)
|
||||
@@ -322,7 +321,7 @@ class NWProject():
|
||||
pHandle = self.newFolder(chTitle, nwItemClass.NOVEL, nHandle)
|
||||
|
||||
cHandle = self.newFile(chTitle, nwItemClass.NOVEL, pHandle)
|
||||
self.projTree.setFileItemLayout(cHandle, nwItemLayout.CHAPTER)
|
||||
self.projTree.setFileItemLayout(cHandle, nwItemLayout.DOCUMENT)
|
||||
|
||||
aDoc = NWDoc(self, cHandle)
|
||||
aDoc.writeDocument("## %s\n\n" % chTitle)
|
||||
@@ -466,8 +465,11 @@ class NWProject():
|
||||
# 1.2 : Changes the way autoReplace entries are stored. The 1.1
|
||||
# parser will lose the autoReplace settings if allowed to
|
||||
# read the file. Introduced in version 0.10.
|
||||
# 1.3 : Reduces the number of layouts to onlye two. One for
|
||||
# novel documents and one for notes. Introduced in version
|
||||
# 1.5.
|
||||
|
||||
if fileVersion not in ("1.0", "1.1", "1.2"):
|
||||
if fileVersion not in ("1.0", "1.1", "1.2", "1.3"):
|
||||
self.makeAlert(self.tr(
|
||||
"Unknown or unsupported novelWriter project file format. "
|
||||
"The project cannot be opened by this version of novelWriter. "
|
||||
@@ -476,6 +478,19 @@ class NWProject():
|
||||
self.clearProject()
|
||||
return False
|
||||
|
||||
if fileVersion != self.FILE_VERSION:
|
||||
msgYes = self.theParent.askQuestion(
|
||||
self.tr("File Version"),
|
||||
self.tr(
|
||||
"The file format of your project is about to be updated. "
|
||||
"If you proceed, this project can no longer be opened by "
|
||||
"an older version of novelWriter. Continue?"
|
||||
).format(appVersion, nw.__version__)
|
||||
)
|
||||
if not msgYes:
|
||||
self.clearProject()
|
||||
return False
|
||||
|
||||
# Check novelWriter Version
|
||||
# =========================
|
||||
|
||||
@@ -621,10 +636,10 @@ class NWProject():
|
||||
# Root element and project details
|
||||
logger.debug("Writing project meta")
|
||||
nwXML = etree.Element("novelWriterXML", attrib={
|
||||
"appVersion": str(nw.__version__),
|
||||
"hexVersion": str(nw.__hexversion__),
|
||||
"fileVersion": "1.2",
|
||||
"timeStamp": formatTimeStamp(saveTime),
|
||||
"appVersion": str(nw.__version__),
|
||||
"hexVersion": str(nw.__hexversion__),
|
||||
"fileVersion": self.FILE_VERSION,
|
||||
"timeStamp": formatTimeStamp(saveTime),
|
||||
})
|
||||
|
||||
editTime = int(self.editTime + saveTime - self.projOpened)
|
||||
@@ -728,14 +743,14 @@ class NWProject():
|
||||
if self.projPath is None or self.projPath == "":
|
||||
return False
|
||||
|
||||
if self.projPath == os.path.expanduser("~"):
|
||||
# Don't make a mess in the user's home folder
|
||||
return False
|
||||
|
||||
self.projMeta = os.path.join(self.projPath, "meta")
|
||||
self.projCache = os.path.join(self.projPath, "cache")
|
||||
self.projContent = os.path.join(self.projPath, "content")
|
||||
|
||||
if self.projPath == os.path.expanduser("~"):
|
||||
# Don't make a mess in the user's home folder
|
||||
return False
|
||||
|
||||
if not self._checkFolder(self.projMeta):
|
||||
return False
|
||||
if not self._checkFolder(self.projCache):
|
||||
|
||||
@@ -143,7 +143,7 @@ class NWSpellEnchant(NWSpellCheck):
|
||||
try:
|
||||
import enchant
|
||||
if self.theBroker is not None:
|
||||
logger.verbose("Deleting old pyenchant broker")
|
||||
logger.debug("Deleting old pyenchant broker")
|
||||
del self.theBroker
|
||||
|
||||
self.theBroker = enchant.Broker()
|
||||
|
||||
+6
-8
@@ -128,9 +128,7 @@ class ToHtml(Tokenizer):
|
||||
}
|
||||
|
||||
if self.isNovel and self.genMode != self.M_PREVIEW:
|
||||
# For novel files for export, we bump the titles one level
|
||||
# up as this is more useful for printing and word processor
|
||||
# imports.
|
||||
# For story files, we bump the titles one level up
|
||||
h1Cl = " class='title'"
|
||||
h1 = "h1"
|
||||
h2 = "h1"
|
||||
@@ -168,13 +166,9 @@ class ToHtml(Tokenizer):
|
||||
|
||||
if tStyle & self.A_PBB:
|
||||
aStyle.append("page-break-before: always;")
|
||||
elif tStyle & self.A_PBB_AUT:
|
||||
aStyle.append("page-break-before: auto;")
|
||||
|
||||
if tStyle & self.A_PBA:
|
||||
aStyle.append("page-break-after: always;")
|
||||
elif tStyle & self.A_PBA_AUT:
|
||||
aStyle.append("page-break-after: auto;")
|
||||
|
||||
if tStyle & self.A_Z_BTMMRG:
|
||||
aStyle.append("margin-bottom: 0;")
|
||||
@@ -206,7 +200,7 @@ class ToHtml(Tokenizer):
|
||||
parClass = ""
|
||||
if len(thisPar) > 0:
|
||||
tTemp = "<br/>".join(thisPar)
|
||||
tmpResult.append("<p%s%s>%s</p>\n" % (parStyle, parClass, tTemp.rstrip()))
|
||||
tmpResult.append("<p%s%s>%s</p>\n" % (parClass, parStyle, tTemp.rstrip()))
|
||||
thisPar = []
|
||||
parStyle = None
|
||||
|
||||
@@ -214,6 +208,10 @@ class ToHtml(Tokenizer):
|
||||
tHead = tText.replace(r"\\", "<br/>")
|
||||
tmpResult.append("<h1 class='title'%s>%s%s</h1>\n" % (hStyle, aNm, tHead))
|
||||
|
||||
elif tType == self.T_UNNUM:
|
||||
tHead = tText.replace(r"\\", "<br/>")
|
||||
tmpResult.append("<%s%s>%s%s</%s>\n" % (h2, hStyle, aNm, tHead, h2))
|
||||
|
||||
elif tType == self.T_HEAD1:
|
||||
tHead = tText.replace(r"\\", "<br/>")
|
||||
tmpResult.append("<%s%s%s>%s%s</%s>\n" % (h1, h1Cl, hStyle, aNm, tHead, h1))
|
||||
|
||||
+219
-197
@@ -33,7 +33,7 @@ from functools import partial
|
||||
from PyQt5.QtCore import QCoreApplication, QRegularExpression
|
||||
|
||||
from nw.enum import nwItemLayout, nwItemType
|
||||
from nw.common import numberToRoman
|
||||
from nw.common import numberToRoman, checkInt
|
||||
from nw.constants import nwConst, nwRegEx, nwUnicode
|
||||
from nw.core.document import NWDoc
|
||||
|
||||
@@ -56,13 +56,14 @@ class Tokenizer():
|
||||
T_COMMENT = 3 # Comment line
|
||||
T_KEYWORD = 4 # Command line
|
||||
T_TITLE = 5 # Title
|
||||
T_HEAD1 = 6 # Header 1
|
||||
T_HEAD2 = 7 # Header 2
|
||||
T_HEAD3 = 8 # Header 3
|
||||
T_HEAD4 = 9 # Header 4
|
||||
T_TEXT = 10 # Text line
|
||||
T_SEP = 11 # Scene separator
|
||||
T_SKIP = 12 # Paragraph break
|
||||
T_UNNUM = 6 # Unnumbered
|
||||
T_HEAD1 = 7 # Header 1
|
||||
T_HEAD2 = 8 # Header 2
|
||||
T_HEAD3 = 9 # Header 3
|
||||
T_HEAD4 = 10 # Header 4
|
||||
T_TEXT = 11 # Text line
|
||||
T_SEP = 12 # Scene separator
|
||||
T_SKIP = 13 # Paragraph break
|
||||
|
||||
# Block Style
|
||||
A_NONE = 0x0000 # No special style
|
||||
@@ -70,14 +71,12 @@ class Tokenizer():
|
||||
A_RIGHT = 0x0002 # Right aligned
|
||||
A_CENTRE = 0x0004 # Centred
|
||||
A_JUSTIFY = 0x0008 # Justified
|
||||
A_PBB = 0x0010 # Page break before always
|
||||
A_PBB_AUT = 0x0020 # Page break before auto
|
||||
A_PBA = 0x0040 # Page break after always
|
||||
A_PBA_AUT = 0x0080 # Page break after auto
|
||||
A_Z_TOPMRG = 0x0100 # Zero top margin
|
||||
A_Z_BTMMRG = 0x0200 # Zero bottom margin
|
||||
A_IND_L = 0x0400 # Left indentation
|
||||
A_IND_R = 0x0800 # Right indentation
|
||||
A_PBB = 0x0010 # Page break before
|
||||
A_PBA = 0x0020 # Page break after
|
||||
A_Z_TOPMRG = 0x0040 # Zero top margin
|
||||
A_Z_BTMMRG = 0x0080 # Zero bottom margin
|
||||
A_IND_L = 0x0100 # Left indentation
|
||||
A_IND_R = 0x0200 # Right indentation
|
||||
|
||||
def __init__(self, theProject):
|
||||
|
||||
@@ -136,15 +135,9 @@ class Tokenizer():
|
||||
|
||||
# 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
|
||||
self.isNote = False
|
||||
self.isFirst = True
|
||||
|
||||
# Error Handling
|
||||
self.errData = []
|
||||
@@ -268,10 +261,16 @@ class Tokenizer():
|
||||
if theItem.itemType != nwItemType.ROOT:
|
||||
return False
|
||||
|
||||
if self.isFirst:
|
||||
textAlign = self.A_CENTRE
|
||||
self.isFirst = False
|
||||
else:
|
||||
textAlign = self.A_PBB | self.A_CENTRE
|
||||
|
||||
theTitle = "%s: %s" % (self._localLookup("Notes"), theItem.itemName)
|
||||
self.theTokens = []
|
||||
self.theTokens.append((
|
||||
self.T_TITLE, 0, theTitle, None, self.A_PBB | self.A_CENTRE
|
||||
self.T_TITLE, 0, theTitle, None, textAlign
|
||||
))
|
||||
if self.keepMarkdown:
|
||||
self.theMarkdown.append("# %s\n\n" % theTitle)
|
||||
@@ -307,15 +306,8 @@ class Tokenizer():
|
||||
self.errData.append(errVal)
|
||||
|
||||
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.isNovel = self.theItem.itemLayout == nwItemLayout.DOCUMENT
|
||||
self.isNote = self.theItem.itemLayout == nwItemLayout.NOTE
|
||||
self.isNovel = self.isBook or self.isUnNum or self.isChap or self.isScene
|
||||
|
||||
return True
|
||||
|
||||
@@ -356,11 +348,11 @@ class Tokenizer():
|
||||
def tokenizeText(self):
|
||||
"""Scan the text for either lines starting with specific
|
||||
characters that indicate headers, comments, commands etc, or
|
||||
just contains plain text. in the case of plain text, apply the
|
||||
just contain 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
|
||||
The format of the token list is an entry with a five-tuple for
|
||||
each line in the file. The tuple is as follows:
|
||||
1: The type of the block, self.T_*
|
||||
2: The line in file where this block occurred
|
||||
@@ -375,73 +367,144 @@ class Tokenizer():
|
||||
(QRegularExpression(nwRegEx.FMT_ST), [None, self.FMT_D_B, None, self.FMT_D_E]),
|
||||
]
|
||||
|
||||
# Determine default text alignment
|
||||
if self.isTitle or self.isPart:
|
||||
defAlign = self.A_CENTRE
|
||||
else:
|
||||
defAlign = self.A_NONE
|
||||
|
||||
self.theTokens = []
|
||||
tmpMarkdown = []
|
||||
nLine = 0
|
||||
breakNext = False
|
||||
for aLine in self.theText.splitlines():
|
||||
nLine += 1
|
||||
sLine = aLine.strip()
|
||||
|
||||
# Tag lines starting with specific characters
|
||||
if len(aLine.strip()) == 0:
|
||||
# Check for blank lines
|
||||
if len(sLine) == 0:
|
||||
self.theTokens.append((
|
||||
self.T_EMPTY, nLine, "", None, defAlign
|
||||
self.T_EMPTY, nLine, "", None, self.A_NONE
|
||||
))
|
||||
if self.keepMarkdown:
|
||||
tmpMarkdown.append("\n")
|
||||
|
||||
continue
|
||||
|
||||
if breakNext:
|
||||
sAlign = self.A_PBB
|
||||
breakNext = False
|
||||
else:
|
||||
sAlign = self.A_NONE
|
||||
|
||||
# Check Line Format
|
||||
# =================
|
||||
|
||||
if aLine[0] == "[":
|
||||
# Parse special formatting line
|
||||
|
||||
if sLine in ("[NEWPAGE]", "[NEW PAGE]"):
|
||||
breakNext = True
|
||||
continue
|
||||
|
||||
elif sLine == "[VSPACE]":
|
||||
self.theTokens.append(
|
||||
(self.T_SKIP, nLine, "", None, sAlign)
|
||||
)
|
||||
continue
|
||||
|
||||
elif sLine.startswith("[VSPACE:") and sLine.endswith("]"):
|
||||
nSkip = checkInt(sLine[8:-1], 0)
|
||||
if nSkip >= 1:
|
||||
self.theTokens.append(
|
||||
(self.T_SKIP, nLine, "", None, sAlign)
|
||||
)
|
||||
if nSkip > 1:
|
||||
self.theTokens += (nSkip - 1) * [
|
||||
(self.T_SKIP, nLine, "", None, self.A_NONE)
|
||||
]
|
||||
continue
|
||||
|
||||
elif aLine[0] == "%":
|
||||
cLine = aLine[1:].lstrip()
|
||||
synTag = cLine[:9].lower()
|
||||
if synTag == "synopsis:":
|
||||
self.theTokens.append((
|
||||
self.T_SYNOPSIS, nLine, cLine[9:].strip(), None, defAlign
|
||||
self.T_SYNOPSIS, nLine, cLine[9:].strip(), None, sAlign
|
||||
))
|
||||
if self.doSynopsis and self.keepMarkdown:
|
||||
tmpMarkdown.append("%s\n" % aLine)
|
||||
else:
|
||||
self.theTokens.append((
|
||||
self.T_COMMENT, nLine, aLine[1:].strip(), None, defAlign
|
||||
self.T_COMMENT, nLine, aLine[1:].strip(), None, sAlign
|
||||
))
|
||||
if self.doComments and self.keepMarkdown:
|
||||
tmpMarkdown.append("%s\n" % aLine)
|
||||
|
||||
elif aLine[0] == "@":
|
||||
self.theTokens.append((
|
||||
self.T_KEYWORD, nLine, aLine[1:].strip(), None, defAlign
|
||||
self.T_KEYWORD, nLine, aLine[1:].strip(), None, sAlign
|
||||
))
|
||||
if self.doKeywords and self.keepMarkdown:
|
||||
tmpMarkdown.append("%s\n" % aLine)
|
||||
|
||||
elif aLine[:2] == "# ":
|
||||
if self.isNovel:
|
||||
sAlign |= self.A_CENTRE
|
||||
|
||||
if self.isNovel and not self.isFirst:
|
||||
sAlign |= self.A_PBB
|
||||
|
||||
self.theTokens.append((
|
||||
self.T_HEAD1, nLine, aLine[2:].strip(), None, defAlign
|
||||
self.T_HEAD1, nLine, aLine[2:].strip(), None, sAlign
|
||||
))
|
||||
if self.keepMarkdown:
|
||||
tmpMarkdown.append("%s\n" % aLine)
|
||||
|
||||
elif aLine[:3] == "## ":
|
||||
if self.isNovel and not self.isFirst:
|
||||
sAlign |= self.A_PBB
|
||||
|
||||
self.theTokens.append((
|
||||
self.T_HEAD2, nLine, aLine[3:].strip(), None, defAlign
|
||||
self.T_HEAD2, nLine, aLine[3:].strip(), None, sAlign
|
||||
))
|
||||
if self.keepMarkdown:
|
||||
tmpMarkdown.append("%s\n" % aLine)
|
||||
|
||||
elif aLine[:4] == "### ":
|
||||
self.theTokens.append((
|
||||
self.T_HEAD3, nLine, aLine[4:].strip(), None, defAlign
|
||||
self.T_HEAD3, nLine, aLine[4:].strip(), None, sAlign
|
||||
))
|
||||
if self.keepMarkdown:
|
||||
tmpMarkdown.append("%s\n" % aLine)
|
||||
|
||||
elif aLine[:5] == "#### ":
|
||||
self.theTokens.append((
|
||||
self.T_HEAD4, nLine, aLine[5:].strip(), None, defAlign
|
||||
self.T_HEAD4, nLine, aLine[5:].strip(), None, sAlign
|
||||
))
|
||||
if self.keepMarkdown:
|
||||
tmpMarkdown.append("%s\n" % aLine)
|
||||
|
||||
elif aLine[:3] == "#! ":
|
||||
if self.isNovel:
|
||||
tStyle = self.T_TITLE
|
||||
else:
|
||||
tStyle = self.T_HEAD1
|
||||
|
||||
if self.isNovel and not self.isFirst:
|
||||
sAlign |= self.A_PBB
|
||||
|
||||
self.theTokens.append((
|
||||
tStyle, nLine, aLine[3:].strip(), None, sAlign | self.A_CENTRE
|
||||
))
|
||||
if self.keepMarkdown:
|
||||
tmpMarkdown.append("%s\n" % aLine)
|
||||
|
||||
elif aLine[:4] == "##! ":
|
||||
if self.isNovel:
|
||||
tStyle = self.T_UNNUM
|
||||
else:
|
||||
tStyle = self.T_HEAD2
|
||||
|
||||
if self.isNovel and not self.isFirst:
|
||||
sAlign |= self.A_PBB
|
||||
|
||||
self.theTokens.append((
|
||||
tStyle, nLine, aLine[4:].strip(), None, sAlign
|
||||
))
|
||||
if self.keepMarkdown:
|
||||
tmpMarkdown.append("%s\n" % aLine)
|
||||
@@ -452,36 +515,35 @@ class Tokenizer():
|
||||
continue
|
||||
|
||||
# Check Alignment and Indentation
|
||||
tagLeft = False
|
||||
tagRight = False
|
||||
alnLeft = False
|
||||
alnRight = False
|
||||
indLeft = False
|
||||
indRight = False
|
||||
if aLine.startswith(">>"):
|
||||
tagRight = True
|
||||
alnRight = True
|
||||
aLine = aLine[2:].lstrip(" ")
|
||||
elif aLine.startswith(">"):
|
||||
indLeft = True
|
||||
aLine = aLine[1:].lstrip(" ")
|
||||
|
||||
if aLine.endswith("<<"):
|
||||
tagLeft = True
|
||||
alnLeft = True
|
||||
aLine = aLine[:-2].rstrip(" ")
|
||||
elif aLine.endswith("<"):
|
||||
indRight = True
|
||||
aLine = aLine[:-1].rstrip(" ")
|
||||
|
||||
textAlign = defAlign
|
||||
if tagLeft and tagRight:
|
||||
textAlign = self.A_CENTRE
|
||||
elif tagLeft:
|
||||
textAlign = self.A_LEFT
|
||||
elif tagRight:
|
||||
textAlign = self.A_RIGHT
|
||||
if alnLeft and alnRight:
|
||||
sAlign |= self.A_CENTRE
|
||||
elif alnLeft:
|
||||
sAlign |= self.A_LEFT
|
||||
elif alnRight:
|
||||
sAlign |= self.A_RIGHT
|
||||
|
||||
if indLeft:
|
||||
textAlign |= self.A_IND_L
|
||||
sAlign |= self.A_IND_L
|
||||
if indRight:
|
||||
textAlign |= self.A_IND_R
|
||||
sAlign |= self.A_IND_R
|
||||
|
||||
# Otherwise we use RegEx to find formatting tags within a line of text
|
||||
fmtPos = []
|
||||
@@ -499,14 +561,18 @@ class Tokenizer():
|
||||
# sorted by position
|
||||
fmtPos = sorted(fmtPos, key=itemgetter(0))
|
||||
self.theTokens.append((
|
||||
self.T_TEXT, nLine, aLine, fmtPos, textAlign
|
||||
self.T_TEXT, nLine, aLine, fmtPos, sAlign
|
||||
))
|
||||
if self.keepMarkdown:
|
||||
tmpMarkdown.append("%s\n" % aLine)
|
||||
|
||||
# Always add an empty line at the end
|
||||
# If we have content, turn off the first page flag
|
||||
if self.isFirst and self.theTokens:
|
||||
self.isFirst = False
|
||||
|
||||
# Always add an empty line at the end of the file
|
||||
self.theTokens.append((
|
||||
self.T_EMPTY, nLine, "", None, defAlign
|
||||
self.T_EMPTY, nLine, "", None, self.A_NONE
|
||||
))
|
||||
if self.keepMarkdown:
|
||||
tmpMarkdown.append("\n")
|
||||
@@ -518,8 +584,8 @@ class Tokenizer():
|
||||
# ===========
|
||||
# Some items need a second pass
|
||||
|
||||
pToken = (self.T_EMPTY, 0, "", None, defAlign)
|
||||
nToken = (self.T_EMPTY, 0, "", None, defAlign)
|
||||
pToken = (self.T_EMPTY, 0, "", None, self.A_NONE)
|
||||
nToken = (self.T_EMPTY, 0, "", None, self.A_NONE)
|
||||
tCount = len(self.theTokens)
|
||||
for n, tToken in enumerate(self.theTokens):
|
||||
|
||||
@@ -541,148 +607,104 @@ class Tokenizer():
|
||||
return
|
||||
|
||||
def doHeaders(self):
|
||||
"""Apply formatting to the text headers according to document
|
||||
layout and user settings.
|
||||
"""Apply formatting to the text headers for novel files. This
|
||||
also applies chapter and scene numbering.
|
||||
"""
|
||||
# No special header formatting for notes and no-layout files
|
||||
if self.isNone or self.isNote:
|
||||
if not self.isNovel:
|
||||
return False
|
||||
|
||||
# For novel files, we need to handle chapter numbering, scene
|
||||
# numbering, and scene breaks
|
||||
if self.isNovel:
|
||||
for n, tToken in enumerate(self.theTokens):
|
||||
for n, tToken in enumerate(self.theTokens):
|
||||
|
||||
# In case we see text before a scene, we reset the flag
|
||||
if tToken[0] == self.T_TEXT:
|
||||
self.firstScene = False
|
||||
# In case we see text before a scene, we reset the flag
|
||||
if tToken[0] == self.T_TEXT:
|
||||
self.firstScene = False
|
||||
|
||||
elif tToken[0] == self.T_HEAD1:
|
||||
# Main Title
|
||||
# ==========
|
||||
elif tToken[0] == self.T_HEAD1:
|
||||
# Partition
|
||||
|
||||
tTemp = self._formatHeading(self.fmtTitle, tToken[2])
|
||||
tTemp = self._formatHeading(self.fmtTitle, tToken[2])
|
||||
self.theTokens[n] = (
|
||||
tToken[0], tToken[1], tTemp, None, tToken[4]
|
||||
)
|
||||
|
||||
elif tToken[0] in (self.T_HEAD2, self.T_UNNUM):
|
||||
# Chapter
|
||||
|
||||
# Numbered or Unnumbered
|
||||
if tToken[2].startswith("*"):
|
||||
tTemp = self._formatHeading(self.fmtUnNum, tToken[2][1:].lstrip())
|
||||
elif tToken[0] == self.T_UNNUM:
|
||||
tTemp = self._formatHeading(self.fmtUnNum, tToken[2])
|
||||
else:
|
||||
self.numChapter += 1
|
||||
tTemp = self._formatHeading(self.fmtChapter, tToken[2])
|
||||
|
||||
# Format the chapter header
|
||||
self.theTokens[n] = (
|
||||
tToken[0], tToken[1], tTemp, None, tToken[4]
|
||||
)
|
||||
|
||||
# Set scene variables
|
||||
self.firstScene = True
|
||||
self.numChScene = 0
|
||||
|
||||
elif tToken[0] == self.T_HEAD3:
|
||||
# Scene
|
||||
|
||||
self.numChScene += 1
|
||||
self.numAbsScene += 1
|
||||
|
||||
tTemp = self._formatHeading(self.fmtScene, tToken[2])
|
||||
if tTemp == "" and self.hideScene:
|
||||
self.theTokens[n] = (
|
||||
self.T_EMPTY, tToken[1], "", None, self.A_NONE
|
||||
)
|
||||
elif tTemp == "" and not self.hideScene:
|
||||
if self.firstScene:
|
||||
self.theTokens[n] = (
|
||||
self.T_EMPTY, tToken[1], "", None, self.A_NONE
|
||||
)
|
||||
else:
|
||||
self.theTokens[n] = (
|
||||
self.T_SKIP, tToken[1], "", None, self.A_NONE
|
||||
)
|
||||
elif tTemp == self.fmtScene:
|
||||
if self.firstScene:
|
||||
self.theTokens[n] = (
|
||||
self.T_EMPTY, tToken[1], "", None, self.A_NONE
|
||||
)
|
||||
else:
|
||||
self.theTokens[n] = (
|
||||
self.T_SEP, tToken[1], tTemp, None, self.A_CENTRE
|
||||
)
|
||||
else:
|
||||
self.theTokens[n] = (
|
||||
tToken[0], tToken[1], tTemp, None, self.A_NONE
|
||||
)
|
||||
|
||||
elif tToken[0] == self.T_HEAD2:
|
||||
# Novel Chapter
|
||||
# =============
|
||||
# Definitely no longer the first scene
|
||||
self.firstScene = False
|
||||
|
||||
# Numbered or Unnumbered
|
||||
if self.isUnNum:
|
||||
tTemp = self._formatHeading(self.fmtUnNum, tToken[2])
|
||||
elif tToken[2].startswith("*"):
|
||||
tTemp = self._formatHeading(self.fmtUnNum, tToken[2][1:].lstrip())
|
||||
else:
|
||||
self.numChapter += 1
|
||||
tTemp = self._formatHeading(self.fmtChapter, tToken[2])
|
||||
elif tToken[0] == self.T_HEAD4:
|
||||
# Section
|
||||
|
||||
# Format the chapter header
|
||||
tTemp = self._formatHeading(self.fmtSection, tToken[2])
|
||||
if tTemp == "" and self.hideSection:
|
||||
self.theTokens[n] = (
|
||||
tToken[0], tToken[1], tTemp, None, self.A_PBB
|
||||
self.T_EMPTY, tToken[1], "", None, self.A_NONE
|
||||
)
|
||||
elif tTemp == "" and not self.hideSection:
|
||||
self.theTokens[n] = (
|
||||
self.T_SKIP, tToken[1], "", None, self.A_NONE
|
||||
)
|
||||
elif tTemp == self.fmtSection:
|
||||
self.theTokens[n] = (
|
||||
self.T_SEP, tToken[1], tTemp, None, self.A_CENTRE
|
||||
)
|
||||
else:
|
||||
self.theTokens[n] = (
|
||||
tToken[0], tToken[1], tTemp, None, self.A_NONE
|
||||
)
|
||||
|
||||
# Set scene variables
|
||||
self.firstScene = True
|
||||
self.numChScene = 0
|
||||
|
||||
elif tToken[0] == self.T_HEAD3:
|
||||
# Novel Scene
|
||||
# ===========
|
||||
|
||||
self.numChScene += 1
|
||||
self.numAbsScene += 1
|
||||
|
||||
tTemp = self._formatHeading(self.fmtScene, tToken[2])
|
||||
if tTemp == "" and self.hideScene:
|
||||
self.theTokens[n] = (
|
||||
self.T_EMPTY, tToken[1], "", None, self.A_NONE
|
||||
)
|
||||
elif tTemp == "" and not self.hideScene:
|
||||
if self.firstScene:
|
||||
self.theTokens[n] = (
|
||||
self.T_EMPTY, tToken[1], "", None, self.A_NONE
|
||||
)
|
||||
else:
|
||||
self.theTokens[n] = (
|
||||
self.T_SKIP, tToken[1], "", None, self.A_NONE
|
||||
)
|
||||
elif tTemp == self.fmtScene:
|
||||
if self.firstScene:
|
||||
self.theTokens[n] = (
|
||||
self.T_EMPTY, tToken[1], "", None, self.A_NONE
|
||||
)
|
||||
else:
|
||||
self.theTokens[n] = (
|
||||
self.T_SEP, tToken[1], tTemp, None, self.A_CENTRE
|
||||
)
|
||||
else:
|
||||
self.theTokens[n] = (
|
||||
tToken[0], tToken[1], tTemp, None, self.A_NONE
|
||||
)
|
||||
|
||||
# Definitely no longer the first scene
|
||||
self.firstScene = False
|
||||
|
||||
elif tToken[0] == self.T_HEAD4:
|
||||
# Novel Section
|
||||
# =============
|
||||
|
||||
tTemp = self._formatHeading(self.fmtSection, tToken[2])
|
||||
if tTemp == "" and self.hideSection:
|
||||
self.theTokens[n] = (
|
||||
self.T_EMPTY, tToken[1], "", None, self.A_NONE
|
||||
)
|
||||
elif tTemp == "" and not self.hideSection:
|
||||
self.theTokens[n] = (
|
||||
self.T_SKIP, tToken[1], "", None, self.A_NONE
|
||||
)
|
||||
elif tTemp == self.fmtSection:
|
||||
self.theTokens[n] = (
|
||||
self.T_SEP, tToken[1], tTemp, None, self.A_CENTRE
|
||||
)
|
||||
else:
|
||||
self.theTokens[n] = (
|
||||
tToken[0], tToken[1], tTemp, None, self.A_NONE
|
||||
)
|
||||
|
||||
# For title page we use a different title class, and we will set
|
||||
# an automatic page break before (i.e. added if needed). For
|
||||
# partitions we always need a page break before.
|
||||
if self.isTitle or self.isPart:
|
||||
for n, tToken in enumerate(self.theTokens):
|
||||
aStyle = tToken[4]
|
||||
if n == 0:
|
||||
if self.isTitle:
|
||||
aStyle |= self.A_PBB_AUT
|
||||
else:
|
||||
aStyle |= self.A_PBB
|
||||
|
||||
if tToken[0] == self.T_HEAD1:
|
||||
if self.isTitle:
|
||||
self.theTokens[n] = (
|
||||
self.T_TITLE, tToken[1], tToken[2], tToken[3], aStyle
|
||||
)
|
||||
else:
|
||||
self.theTokens[n] = (
|
||||
tToken[0], tToken[1], tToken[2], tToken[3], aStyle
|
||||
)
|
||||
|
||||
# Add a page break after the last entry
|
||||
if len(self.theTokens) > 0:
|
||||
tToken = self.theTokens[-1]
|
||||
self.theTokens[-1] = (
|
||||
tToken[0], tToken[1], tToken[2], tToken[3], tToken[4] | self.A_PBA
|
||||
)
|
||||
|
||||
# A single page always starts on a fresh page, unless it's empty.
|
||||
if self.isPage and len(self.theTokens) > 0:
|
||||
tToken = self.theTokens[0]
|
||||
self.theTokens[0] = (
|
||||
tToken[0], tToken[1], tToken[2], tToken[3], tToken[4] | self.A_PBB
|
||||
)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
@@ -108,6 +108,10 @@ class ToMarkdown(Tokenizer):
|
||||
tHead = tText.replace(r"\\", "\n")
|
||||
tmpResult.append("# %s\n\n" % tHead)
|
||||
|
||||
elif tType == self.T_UNNUM:
|
||||
tHead = tText.replace(r"\\", "\n")
|
||||
tmpResult.append("## %s\n\n" % tHead)
|
||||
|
||||
elif tType == self.T_HEAD1:
|
||||
tHead = tText.replace(r"\\", "\n")
|
||||
tmpResult.append("# %s\n\n" % tHead)
|
||||
|
||||
+4
-4
@@ -345,13 +345,9 @@ class ToOdt(Tokenizer):
|
||||
|
||||
if tStyle & self.A_PBB:
|
||||
oStyle.setBreakBefore("page")
|
||||
elif tStyle & self.A_PBB_AUT:
|
||||
oStyle.setBreakBefore("auto")
|
||||
|
||||
if tStyle & self.A_PBA:
|
||||
oStyle.setBreakAfter("page")
|
||||
elif tStyle & self.A_PBA_AUT:
|
||||
oStyle.setBreakAfter("auto")
|
||||
|
||||
if tStyle & self.A_Z_BTMMRG:
|
||||
oStyle.setMarginBottom("0.000cm")
|
||||
@@ -384,6 +380,10 @@ class ToOdt(Tokenizer):
|
||||
tHead = tText.replace(r"\\", "\n")
|
||||
self._addTextPar("Title", oStyle, tHead, isHead=True)
|
||||
|
||||
elif tType == self.T_UNNUM:
|
||||
tHead = tText.replace(r"\\", "\n")
|
||||
self._addTextPar("Heading_2", oStyle, tHead, isHead=True, oLevel="2")
|
||||
|
||||
elif tType == self.T_HEAD1:
|
||||
tHead = tText.replace(r"\\", "\n")
|
||||
self._addTextPar("Heading_1", oStyle, tHead, isHead=True, oLevel="1")
|
||||
|
||||
+1
-48
@@ -33,34 +33,11 @@ from hashlib import sha256
|
||||
|
||||
from nw.enum import nwItemType, nwItemClass, nwItemLayout
|
||||
from nw.common import checkHandle
|
||||
from nw.constants import nwConst, nwLists, nwFiles
|
||||
from nw.constants import nwConst, nwFiles
|
||||
from nw.core.item import NWItem
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# Layout Translation Map
|
||||
LAYOUT_MAP = {
|
||||
nwItemLayout.SCENE: {
|
||||
"H1": nwItemLayout.BOOK,
|
||||
"H2": nwItemLayout.CHAPTER,
|
||||
},
|
||||
nwItemLayout.CHAPTER: {
|
||||
"H1": nwItemLayout.BOOK,
|
||||
"H3": nwItemLayout.SCENE,
|
||||
"H4": nwItemLayout.SCENE,
|
||||
},
|
||||
nwItemLayout.UNNUMBERED: {
|
||||
"H1": nwItemLayout.BOOK,
|
||||
"H3": nwItemLayout.SCENE,
|
||||
"H4": nwItemLayout.SCENE,
|
||||
},
|
||||
nwItemLayout.PARTITION: {
|
||||
"H2": nwItemLayout.CHAPTER,
|
||||
"H3": nwItemLayout.SCENE,
|
||||
"H4": nwItemLayout.SCENE,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
class NWTree():
|
||||
|
||||
@@ -229,30 +206,6 @@ class NWTree():
|
||||
novelWords += tItem.wordCount
|
||||
return novelWords, noteWords
|
||||
|
||||
def updateItemLayout(self, tHandle, hLevel):
|
||||
"""Check if the item layout needs updating based on the header
|
||||
given level.
|
||||
"""
|
||||
tItem = self.__getitem__(tHandle)
|
||||
if tItem is None:
|
||||
return False
|
||||
if tItem.itemClass not in nwLists.CLS_NOVEL:
|
||||
return False
|
||||
if hLevel not in ("H1", "H2", "H3", "H4"):
|
||||
return False
|
||||
|
||||
iLayout = tItem.itemLayout
|
||||
if iLayout in LAYOUT_MAP:
|
||||
if hLevel in LAYOUT_MAP[iLayout]:
|
||||
tItem.itemLayout = LAYOUT_MAP[iLayout][hLevel]
|
||||
logger.debug(
|
||||
"Changed layout for %s from %s to %s",
|
||||
tHandle, iLayout.name, tItem.itemLayout.name
|
||||
)
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
##
|
||||
# Tree Structure Methods
|
||||
##
|
||||
|
||||
@@ -196,16 +196,10 @@ class GuiDocSplit(QDialog):
|
||||
# Loop through, and create the files
|
||||
for wTitle, iStart, iEnd in finalOrder:
|
||||
|
||||
itemLayout = nwItemLayout.NOTE
|
||||
if srcItem.itemClass == nwItemClass.NOVEL:
|
||||
if wTitle.startswith("# "):
|
||||
itemLayout = nwItemLayout.PARTITION
|
||||
elif wTitle.startswith("## "):
|
||||
itemLayout = nwItemLayout.CHAPTER
|
||||
elif wTitle.startswith("### "):
|
||||
itemLayout = nwItemLayout.SCENE
|
||||
elif wTitle.startswith("#### "):
|
||||
itemLayout = nwItemLayout.SCENE
|
||||
itemLayout = nwItemLayout.DOCUMENT
|
||||
else:
|
||||
itemLayout = nwItemLayout.NOTE
|
||||
|
||||
wTitle = wTitle.lstrip("#")
|
||||
wTitle = wTitle.strip()
|
||||
|
||||
@@ -90,13 +90,7 @@ class GuiItemEditor(QDialog):
|
||||
validLayouts = []
|
||||
if self.theItem.itemType == nwItemType.FILE:
|
||||
if self.theItem.itemClass in nwLists.CLS_NOVEL:
|
||||
validLayouts.append(nwItemLayout.TITLE)
|
||||
validLayouts.append(nwItemLayout.BOOK)
|
||||
validLayouts.append(nwItemLayout.PAGE)
|
||||
validLayouts.append(nwItemLayout.PARTITION)
|
||||
validLayouts.append(nwItemLayout.UNNUMBERED)
|
||||
validLayouts.append(nwItemLayout.CHAPTER)
|
||||
validLayouts.append(nwItemLayout.SCENE)
|
||||
validLayouts.append(nwItemLayout.DOCUMENT)
|
||||
validLayouts.append(nwItemLayout.NOTE)
|
||||
else:
|
||||
validLayouts.append(nwItemLayout.NO_LAYOUT)
|
||||
|
||||
+3
-9
@@ -56,15 +56,9 @@ class nwItemClass(Enum):
|
||||
|
||||
class nwItemLayout(Enum):
|
||||
|
||||
NO_LAYOUT = 0
|
||||
TITLE = 1
|
||||
BOOK = 2
|
||||
PAGE = 3
|
||||
PARTITION = 4
|
||||
UNNUMBERED = 5
|
||||
CHAPTER = 6
|
||||
SCENE = 7
|
||||
NOTE = 8
|
||||
NO_LAYOUT = 0
|
||||
DOCUMENT = 1
|
||||
NOTE = 2
|
||||
|
||||
# END Enum nwItemLayout
|
||||
|
||||
|
||||
+7
-7
@@ -468,14 +468,14 @@ class GuiDocEditor(QTextEdit):
|
||||
else:
|
||||
self.theParent.novelView.updateWordCounts(tHandle)
|
||||
|
||||
hLevel = "H0"
|
||||
if self._docHeaders:
|
||||
hLevel = self._docHeaders[0][1]
|
||||
# hLevel = "H0"
|
||||
# if self._docHeaders:
|
||||
# hLevel = self._docHeaders[0][1]
|
||||
|
||||
if self.theProject.projTree.updateItemLayout(tHandle, hLevel):
|
||||
self.theParent.treeView.setTreeItemValues(tHandle)
|
||||
self._nwDocument.writeDocument(docText)
|
||||
self.docFooter.updateInfo()
|
||||
# if self.theProject.projTree.updateItemLayout(tHandle, hLevel):
|
||||
# self.theParent.treeView.setTreeItemValues(tHandle)
|
||||
# self._nwDocument.writeDocument(docText)
|
||||
# self.docFooter.updateInfo()
|
||||
|
||||
# Update the status bar
|
||||
self.theParent.setStatus(
|
||||
|
||||
+44
-15
@@ -34,6 +34,7 @@ from PyQt5.QtGui import (
|
||||
)
|
||||
|
||||
from nw.constants import nwRegEx, nwUnicode
|
||||
from nw.common import checkInt
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -127,6 +128,8 @@ class GuiDocHighlighter(QSyntaxHighlighter):
|
||||
"keyword": self._makeFormat(self.colKey),
|
||||
"modifier": self._makeFormat(self.colMod),
|
||||
"value": self._makeFormat(self.colVal, "underline"),
|
||||
"codevalue": self._makeFormat(self.colVal),
|
||||
"codeinval": self._makeFormat(None, "errline"),
|
||||
}
|
||||
|
||||
self.hRules = []
|
||||
@@ -312,25 +315,32 @@ class GuiDocHighlighter(QSyntaxHighlighter):
|
||||
# so we force a return here
|
||||
return
|
||||
|
||||
elif theText.startswith("# "): # Header 1
|
||||
elif theText.startswith(("# ", "#! ", "## ", "##! ", "### ", "#### ")):
|
||||
self.setCurrentBlockState(self.BLOCK_TITLE)
|
||||
self.setFormat(0, 1, self.hStyles["header1h"])
|
||||
self.setFormat(1, len(theText), self.hStyles["header1"])
|
||||
|
||||
elif theText.startswith("## "): # Header 2
|
||||
self.setCurrentBlockState(self.BLOCK_TITLE)
|
||||
self.setFormat(0, 2, self.hStyles["header2h"])
|
||||
self.setFormat(2, len(theText), self.hStyles["header2"])
|
||||
if theText.startswith("# "): # Header 1
|
||||
self.setFormat(0, 1, self.hStyles["header1h"])
|
||||
self.setFormat(1, len(theText), self.hStyles["header1"])
|
||||
|
||||
elif theText.startswith("### "): # Header 3
|
||||
self.setCurrentBlockState(self.BLOCK_TITLE)
|
||||
self.setFormat(0, 3, self.hStyles["header3h"])
|
||||
self.setFormat(3, len(theText), self.hStyles["header3"])
|
||||
elif theText.startswith("## "): # Header 2
|
||||
self.setFormat(0, 2, self.hStyles["header2h"])
|
||||
self.setFormat(2, len(theText), self.hStyles["header2"])
|
||||
|
||||
elif theText.startswith("#### "): # Header 4
|
||||
self.setCurrentBlockState(self.BLOCK_TITLE)
|
||||
self.setFormat(0, 4, self.hStyles["header4h"])
|
||||
self.setFormat(4, len(theText), self.hStyles["header4"])
|
||||
elif theText.startswith("### "): # Header 3
|
||||
self.setFormat(0, 3, self.hStyles["header3h"])
|
||||
self.setFormat(3, len(theText), self.hStyles["header3"])
|
||||
|
||||
elif theText.startswith("#### "): # Header 4
|
||||
self.setFormat(0, 4, self.hStyles["header4h"])
|
||||
self.setFormat(4, len(theText), self.hStyles["header4"])
|
||||
|
||||
if theText.startswith("#! "): # Title
|
||||
self.setFormat(0, 2, self.hStyles["header1h"])
|
||||
self.setFormat(2, len(theText), self.hStyles["header1"])
|
||||
|
||||
elif theText.startswith("##! "): # Unnumbered
|
||||
self.setFormat(0, 3, self.hStyles["header2h"])
|
||||
self.setFormat(3, len(theText), self.hStyles["header2"])
|
||||
|
||||
elif theText.startswith("%"): # Comments
|
||||
self.setCurrentBlockState(self.BLOCK_TEXT)
|
||||
@@ -346,6 +356,25 @@ class GuiDocHighlighter(QSyntaxHighlighter):
|
||||
self.setFormat(0, tLen, self.hStyles["hidden"])
|
||||
|
||||
else: # Text Paragraph
|
||||
|
||||
if theText.startswith("["): # Special Command
|
||||
sText = theText.rstrip()
|
||||
if sText in ("[NEWPAGE]", "[NEW PAGE]", "[VSPACE]"):
|
||||
self.setFormat(0, len(theText), self.hStyles["keyword"])
|
||||
return
|
||||
|
||||
elif sText.startswith("[VSPACE:") and sText.endswith("]"):
|
||||
tLen = len(sText)
|
||||
tVal = checkInt(sText[8:-1], 0)
|
||||
self.setFormat(0, 8, self.hStyles["keyword"])
|
||||
if tVal > 0:
|
||||
self.setFormat(8, tLen-9, self.hStyles["codevalue"])
|
||||
else:
|
||||
self.setFormat(8, tLen-9, self.hStyles["codeinval"])
|
||||
self.setFormat(tLen-1, tLen, self.hStyles["keyword"])
|
||||
return
|
||||
|
||||
# Regular text
|
||||
self.setCurrentBlockState(self.BLOCK_TEXT)
|
||||
for rX, xFmt in self.rxRules:
|
||||
rxItt = rX.globalMatch(theText, 0)
|
||||
|
||||
@@ -187,6 +187,7 @@ class GuiDocViewer(QTextBrowser):
|
||||
logger.error("Failed to generate preview for document with handle '%s'", tHandle)
|
||||
nw.logException()
|
||||
self.setText(self.tr("An error occurred while generating the preview."))
|
||||
return False
|
||||
|
||||
# Refresh the tab stops
|
||||
if self.mainConf.verQtValue >= 51000:
|
||||
@@ -208,7 +209,9 @@ class GuiDocViewer(QTextBrowser):
|
||||
theCursor.insertText("\t")
|
||||
|
||||
if self._docHandle == tHandle:
|
||||
# This is a refresh, so we set the scrollbar back to where it was
|
||||
self.verticalScrollBar().setValue(sPos)
|
||||
|
||||
self._docHandle = tHandle
|
||||
self.theProject.setLastViewed(tHandle)
|
||||
self.docHeader.setTitleFromHandle(self._docHandle)
|
||||
@@ -539,6 +542,9 @@ class GuiDocViewer(QTextBrowser):
|
||||
".synopsis {{"
|
||||
" color: rgb({mColR}, {mColG}, {mColB});"
|
||||
"}}\n"
|
||||
".title {{"
|
||||
" text-align: center;"
|
||||
"}}\n"
|
||||
).format(
|
||||
tColR=self.theTheme.colText[0],
|
||||
tColG=self.theTheme.colText[1],
|
||||
|
||||
+1
-5
@@ -292,11 +292,7 @@ class GuiProjectTree(QTreeWidget):
|
||||
curTxt = ""
|
||||
|
||||
if curTxt == "":
|
||||
if nwItem.itemLayout == nwItemLayout.CHAPTER:
|
||||
newText = f"## {nwItem.itemName}\n\n"
|
||||
elif nwItem.itemLayout == nwItemLayout.UNNUMBERED:
|
||||
newText = f"## {nwItem.itemName}\n\n"
|
||||
elif nwItem.itemLayout == nwItemLayout.SCENE:
|
||||
if nwItem.itemLayout == nwItemLayout.DOCUMENT:
|
||||
newText = f"### {nwItem.itemName}\n\n"
|
||||
else:
|
||||
newText = f"# {nwItem.itemName}\n\n"
|
||||
|
||||
Reference in New Issue
Block a user