diff --git a/README.md b/README.md
index a69a866b..bf1efa1c 100644
--- a/README.md
+++ b/README.md
@@ -99,6 +99,12 @@ python3 -m pip install lxml
python3 -m pip install pyenchant
```
+PyQt/Qt should be at least 5.2.1, but ideally 5.10 or higher for nearly all features to work.
+Exporting to markdown requires PyQt/Qt 5.14.
+There are no known minimum for lxml, but the code was originally written with 4.2.
+The optional spell check library must be at least 3.0.0 to work with Windows.
+On Linux, 2.0.0 also works fine.
+
If no external spell checking tool is installed, novelWriter will use a basic spell checker based on standard Python package `difflib`.
Currently, only English dictionaries are available for this spell checker, but more can be added to the `nw/assets/dict` folder.
See the [nw/assets/dict/README.md](README.md) file in that folder for how to generate more dictionaries.
diff --git a/docs/source/started.txt b/docs/source/started.txt
index 5c279e14..c27ff27e 100644
--- a/docs/source/started.txt
+++ b/docs/source/started.txt
@@ -35,6 +35,11 @@ The following are optional, but recommended:
* ``pyenchant`` for spell checking
+PyQt/Qt should be at least 5.2.1, but ideally 5.10 or higher for nearly all features to work.
+Exporting to markdown requires PyQt/Qt 5.14.
+There are no known minimum for lxml, but the code was originally written with 4.2.
+The optional spell check library must be at least 3.0.0 to work with Windows.
+On Linux, 2.0.0 also works fine.
Running novelWriter
===================
diff --git a/nw/core/document.py b/nw/core/document.py
index 6bcba588..4c502bea 100644
--- a/nw/core/document.py
+++ b/nw/core/document.py
@@ -136,7 +136,6 @@ class NWDoc():
"""Save the document via temp file in case of save failure, and
in any case keep a backup of the file.
"""
-
if self.docHandle is None or not self.docEditable:
return False
@@ -202,7 +201,6 @@ class NWDoc():
"""Parses the document meta tag and returns the path and name as
a list and a string.
"""
-
if len(self.docMeta) < 14:
# Not enough information
return "", []
@@ -232,6 +230,8 @@ class NWDoc():
@staticmethod
def _assemblePath(tHandle, docExt):
+ """Assemble the file path for a given handle.
+ """
if tHandle is None:
return None, None
docDir = "data_"+tHandle[0]
diff --git a/nw/core/index.py b/nw/core/index.py
index 60129b35..3633c841 100644
--- a/nw/core/index.py
+++ b/nw/core/index.py
@@ -244,7 +244,6 @@ class NWIndex():
and text as separate inputs as we want to primarily scan the
files before we save them, unless we're rebuilding the index.
"""
-
theItem = self.theProject.projTree[tHandle]
if theItem is None:
return False
@@ -529,13 +528,18 @@ class NWIndex():
# Extract Data
##
- def getNovelStructure(self):
+ def getNovelStructure(self, skipExcluded=True):
"""Builds a list of all titles in the novel, in the correct
order as they appear in the tree view and in the respective
document files, but skipping all note files.
"""
theStructure = []
- for tHandle in self.theProject.projTree.handles():
+ for tItem in self.theProject.projTree:
+ if tItem is None:
+ continue
+ if not tItem.isExported and skipExcluded:
+ continue
+ tHandle = tItem.itemHandle
if tHandle not in self.novelIndex:
continue
for sTitle in sorted(self.novelIndex[tHandle].keys()):
diff --git a/nw/core/project.py b/nw/core/project.py
index 7838d5fd..4516d1aa 100644
--- a/nw/core/project.py
+++ b/nw/core/project.py
@@ -181,7 +181,6 @@ class NWProject():
"""Clear the data for the current project, and set them to
default values.
"""
-
# Project Status
self.projOpened = 0
self.projChanged = False
@@ -236,7 +235,6 @@ class NWProject():
parse the XML of the file and populate the project variables and
build the tree of project items.
"""
-
if not path.isfile(fileName):
fileName = path.join(fileName, nwFiles.PROJ_FILE)
if not path.isfile(fileName):
@@ -404,7 +402,6 @@ class NWProject():
make sure if the save fails, we're not left with a truncated
file.
"""
-
if self.projPath is None:
self.makeAlert("Project path not set, cannot save.", nwAlert.ERROR)
return False
@@ -522,7 +519,6 @@ class NWProject():
def zipIt(self, doNotify):
"""Create a zip file of the entire project.
"""
-
logger.info("Backing up project")
self.theParent.statusBar.setStatus("Backing up project ...")
@@ -836,7 +832,6 @@ class NWProject():
def _readLockFile(self):
"""Reads the lock file in the project folder.
"""
-
if self.projPath is None:
return ["ERROR"]
@@ -863,7 +858,6 @@ class NWProject():
def _writeLockFile(self):
"""Writes a lock file to the project folder.
"""
-
if self.projPath is None:
return False
@@ -901,6 +895,8 @@ class NWProject():
return None
def _checkFolder(self, thePath):
+ """Check if a folder exists, and if it doesn't, create it.
+ """
if not path.isdir(thePath):
try:
mkdir(thePath)
@@ -911,6 +907,8 @@ class NWProject():
return True
def _packProjectValue(self, xParent, theName, theValue, allowNone=True):
+ """Pack a list of values into an xml element.
+ """
if not isinstance(theValue, list):
theValue = [theValue]
for aValue in theValue:
@@ -927,7 +925,6 @@ class NWProject():
orphaned files so the user can either delete them, or put them
back into the project tree.
"""
-
if self.projPath is None:
return
@@ -992,7 +989,6 @@ class NWProject():
def _appendSessionStats(self):
"""Append session statistics to the sessions log file.
"""
-
if self.projMeta is None:
return False
@@ -1234,9 +1230,13 @@ class NWTree():
##
def __len__(self):
+ """Return the length counter. Does not check that it is correct!
+ """
return self._theLength
def __bool__(self):
+ """Returns True if the tree has any entries.
+ """
return self._theLength > 0
##
@@ -1382,7 +1382,6 @@ class NWItem():
def unpackXML(self, xItem):
"""Sets the values from an XML entry of type 'item'.
"""
-
if xItem.tag != "item":
logger.error("XML entry is not an NWItem")
return False
@@ -1420,9 +1419,11 @@ class NWItem():
@staticmethod
def _subPack(xParent, name, attrib=None, text=None, none=True):
+ """Packs the values into an xml element.
+ """
if not none and (text == None or text == "None"):
return None
- xSub = etree.SubElement(xParent,name,attrib=attrib)
+ xSub = etree.SubElement(xParent, name, attrib=attrib)
if text is not None:
xSub.text = text
return xSub
@@ -1432,10 +1433,14 @@ class NWItem():
##
def setName(self, theName):
+ """Set the item name.
+ """
self.itemName = theName.strip()
return
def setHandle(self, theHandle):
+ """Set the item handle, and ensure it is valid.
+ """
if isinstance(theHandle, str):
if len(theHandle) == 13:
self.itemHandle = theHandle
@@ -1446,6 +1451,8 @@ class NWItem():
return
def setParent(self, theParent):
+ """Set the parent handle, and ensure that it is valid.
+ """
if theParent is None:
self.parHandle = None
elif isinstance(theParent, str):
@@ -1458,10 +1465,16 @@ class NWItem():
return
def setOrder(self, theOrder):
+ """Set the item order, and ensure that it is valid. This value
+ is purely a meta value, not actually used by novelWriter.
+ """
self.itemOrder = checkInt(theOrder, 0)
return
def setType(self, theType):
+ """Set the item type from either a proper nwItemType, or set it
+ from a string representing a nwItemType.
+ """
if isinstance(theType, nwItemType):
self.itemType = theType
elif theType in nwItemType.__members__:
@@ -1472,6 +1485,9 @@ class NWItem():
return
def setClass(self, theClass):
+ """Set the item class from either a proper nwItemClass, or set
+ it from a string representing a nwItemClass.
+ """
if isinstance(theClass, nwItemClass):
self.itemClass = theClass
elif theClass in nwItemClass.__members__:
@@ -1482,6 +1498,9 @@ class NWItem():
return
def setLayout(self, theLayout):
+ """Set the item layout from either a proper nwItemLayout, or set
+ it from a string representing a nwItemLayout.
+ """
if isinstance(theLayout, nwItemLayout):
self.itemLayout = theLayout
elif theLayout in nwItemLayout.__members__:
@@ -1492,6 +1511,9 @@ class NWItem():
return
def setStatus(self, theStatus):
+ """Set the item status by looking it up in the valid status
+ items of the current project.
+ """
if self.itemClass == nwItemClass.NOVEL:
self.itemStatus = self.theProject.statusItems.checkEntry(theStatus)
else:
@@ -1499,6 +1521,8 @@ class NWItem():
return
def setExpanded(self, expState):
+ """Save the expanded status of an item in the project tree.
+ """
if isinstance(expState, str):
self.isExpanded = expState == str(True)
else:
@@ -1506,6 +1530,8 @@ class NWItem():
return
def setExported(self, expState):
+ """Save the export flag.
+ """
if isinstance(expState, str):
self.isExported = expState == str(True)
else:
@@ -1517,19 +1543,27 @@ class NWItem():
##
def setCharCount(self, theCount):
- self.charCount = checkInt(theCount,0)
+ """Set the character count, and ensure that it is an integer.
+ """
+ self.charCount = checkInt(theCount, 0)
return
def setWordCount(self, theCount):
- self.wordCount = checkInt(theCount,0)
+ """Set the word count, and ensure that it is an integer.
+ """
+ self.wordCount = checkInt(theCount, 0)
return
def setParaCount(self, theCount):
- self.paraCount = checkInt(theCount,0)
+ """Set the paragraph count, and ensure that it is an integer.
+ """
+ self.paraCount = checkInt(theCount, 0)
return
def setCursorPos(self, thePosition):
- self.cursorPos = checkInt(thePosition,0)
+ """Set the cursor position, and ensure that it is an integer.
+ """
+ self.cursorPos = checkInt(thePosition, 0)
return
# END Class NWItem
@@ -1551,6 +1585,9 @@ class NWStatus():
return
def addEntry(self, theLabel, theColours):
+ """Add a status entry to the status object, but ensure it isn't
+ a duplicate.
+ """
theLabel = theLabel.strip()
if self.lookupEntry(theLabel) is None:
self.theLabels.append(theLabel)
@@ -1561,6 +1598,9 @@ class NWStatus():
return True
def lookupEntry(self, theLabel):
+ """Look up a status entry in the object lists, and return it if
+ it exists.
+ """
if theLabel is None:
return None
theLabel = theLabel.strip()
@@ -1569,6 +1609,9 @@ class NWStatus():
return None
def checkEntry(self, theStatus):
+ """Check if a status value is valid, and returns the safe
+ reference to be used internally.
+ """
if isinstance(theStatus, str):
theStatus = theStatus.strip()
if self.lookupEntry(theStatus) is not None:
@@ -1578,11 +1621,12 @@ class NWStatus():
return self.theLabels[theStatus]
def setNewEntries(self, newList):
-
+ """Update the list of entries after they have been modified by
+ the GUI tool.
+ """
replaceMap = {}
if newList is not None:
-
self.theLabels = []
self.theColours = []
self.theCounts = []
@@ -1598,10 +1642,14 @@ class NWStatus():
return replaceMap
def resetCounts(self):
+ """Clear the counts of references to the status entries.
+ """
self.theCounts = [0]*self.theLength
return
def countEntry(self, theLabel):
+ """Lookup the usage count of a given entry.
+ """
theIndex = self.lookupEntry(theLabel)
if theIndex is not None:
self.theCounts[theIndex] += 1
@@ -1623,7 +1671,6 @@ class NWStatus():
def unpackEntries(self, xParent):
"""Unpack an XML tree and set the class values.
"""
-
theLabels = []
theColours = []
@@ -1661,15 +1708,21 @@ class NWStatus():
##
def __getitem__(self, n):
+ """Return an entry by its index.
+ """
if n >= 0 and n < self.theLength:
return self.theLabels[n], self.theColours[n], self.theCounts[n]
return None, None, None
def __iter__(self):
+ """Initialise the iterator.
+ """
self.theIndex = 0
return self
def __next__(self):
+ """Return the next entry for the iterator.
+ """
if self.theIndex < self.theLength:
theLabel, theColour, theCount = self.__getitem__(self.theIndex)
self.theIndex += 1
diff --git a/nw/core/spellcheck.py b/nw/core/spellcheck.py
index fda4022d..2af06f42 100644
--- a/nw/core/spellcheck.py
+++ b/nw/core/spellcheck.py
@@ -55,15 +55,23 @@ class NWSpellCheck():
return
def setLanguage(self, theLang, projectDict=None):
+ """Dummy function.
+ """
return
def checkWord(self, theWord):
+ """Dummy function.
+ """
return True
def suggestWords(self, theWord):
+ """Dummy function.
+ """
return []
def addWord(self, newWord):
+ """Add a word to the project dictionary.
+ """
if self.projectDict is not None and newWord not in self.PROJW:
newWord = newWord.strip()
self.PROJW.append(newWord)
@@ -76,10 +84,14 @@ class NWSpellCheck():
return
def listDictionaries(self):
+ """Dummy function.
+ """
return []
@staticmethod
def expandLanguage(spTag):
+ """Translate a language tag to something more suer friendly.
+ """
spBits = spTag.split("_")
if spBits[0] in isoLanguage.ISO_639_1:
spLang = isoLanguage.ISO_639_1[spBits[0]]
@@ -94,6 +106,9 @@ class NWSpellCheck():
##
def _readProjectDictionary(self, projectDict):
+ """Read the content of the project dictionary, and add it to the
+ lookup lists.
+ """
self.PROJW = []
if projectDict is not None:
self.projectDict = projectDict
@@ -147,17 +162,25 @@ class NWSpellEnchant(NWSpellCheck):
return
def checkWord(self, theWord):
+ """Wrapper function for pyenchant.
+ """
return self.theDict.check(theWord)
def suggestWords(self, theWord):
+ """Wrapper function for pyenchant.
+ """
return self.theDict.suggest(theWord)
def addWord(self, newWord):
+ """Wrapper function for pyenchant.
+ """
self.theDict.add_to_session(newWord)
NWSpellCheck.addWord(self, newWord)
return
def listDictionaries(self):
+ """Wrapper function for pyenchant.
+ """
retList = []
try:
import enchant
@@ -171,6 +194,8 @@ class NWSpellEnchant(NWSpellCheck):
# END Class NWSpellEnchant
class NWSpellEnchantDummy:
+ """Fallback for when Enchant is selected, but not installed.
+ """
def __init__(self):
return
@@ -191,6 +216,11 @@ class NWSpellEnchantDummy:
# ================================================================================================ #
class NWSpellSimple(NWSpellCheck):
+ """Internal spell check tool that uses standard Python packages with
+ no other external dependencies. This is the fallback spell checker
+ when no other is available. This method is fairly slow compared to
+ other implementations.
+ """
WORDS = []
@@ -200,7 +230,8 @@ class NWSpellSimple(NWSpellCheck):
return
def setLanguage(self, theLang, projectDict=None):
-
+ """Load a dictionary as a list from the app assets folder.
+ """
self.WORDS = []
dictFile = path.join(self.mainConf.dictPath,theLang+".dict")
try:
@@ -259,6 +290,8 @@ class NWSpellSimple(NWSpellCheck):
return theOptions
def addWord(self, newWord):
+ """Wrapper for the internal project dictionary feature.
+ """
newWord = newWord.strip().lower()
if newWord not in self.WORDS:
self.WORDS.append(newWord)
@@ -266,7 +299,8 @@ class NWSpellSimple(NWSpellCheck):
return
def listDictionaries(self):
-
+ """Lists the dictionary files in the app assets folder.
+ """
retList = []
for dictFile in listdir(self.mainConf.dictPath):
diff --git a/nw/core/tokenizer.py b/nw/core/tokenizer.py
index c94f418f..2fa784f5 100644
--- a/nw/core/tokenizer.py
+++ b/nw/core/tokenizer.py
@@ -540,6 +540,22 @@ class Tokenizer():
tToken[0], tToken[1], tToken[2], tToken[3] | self.A_PBA
)
+ # A single page is always left-aligned and starts on a fresh
+ # page, unless it's empty.
+ if self.isPage:
+ for n, tToken in enumerate(self.theTokens):
+ tType = tToken[0]
+ tText = tToken[1]
+ tFormat = tToken[2]
+ if n == 0:
+ self.theTokens[n] = (
+ tType, tText, tFormat, self.A_LEFT | self.A_PBB
+ )
+ else:
+ self.theTokens[n] = (
+ tType, tText, tFormat, self.A_LEFT
+ )
+
return
##
diff --git a/nw/core/tools.py b/nw/core/tools.py
index 2c96cee6..a6eb244b 100644
--- a/nw/core/tools.py
+++ b/nw/core/tools.py
@@ -39,7 +39,6 @@ def countWords(theText):
"""Count words in a piece of text, skipping special syntax and
comments.
"""
-
charCount = 0
wordCount = 0
paraCount = 0
@@ -86,7 +85,6 @@ def projectMaintenance(theProject):
"""Wrapper class for handling various tasks related to managing old
projects with content from older versions of novelWriter.
"""
-
# Remove no longer used project cache folder
if path.isdir(theProject.projPath):
cacheDir = path.join(theProject.projPath, "cache")
@@ -141,7 +139,6 @@ def numberToWord(numVal, theLanguage):
def _numberToWordEN(numVal):
"""Convert numbers to English words.
"""
-
numWord = ""
oneWord = ""
tenWord = ""
diff --git a/nw/gui/additions/pageddialog.py b/nw/gui/additions/pageddialog.py
index 6e5c2e23..2b65cbb9 100644
--- a/nw/gui/additions/pageddialog.py
+++ b/nw/gui/additions/pageddialog.py
@@ -69,10 +69,14 @@ class PagedDialog(QDialog):
return
def addTab(self, tabWidget, tabLabel):
+ """Forwards the adding of tabs to the QTabWidget.
+ """
self._tabBox.addTab(tabWidget, tabLabel)
return
def addControls(self, buttonBar):
+ """Adds a button bar to the dialog.
+ """
self._buttonBox.addWidget(buttonBar)
return
@@ -85,12 +89,16 @@ class VerticalTabBar(QTabBar):
return
def tabSizeHint(self, theIndex):
+ """Returns a transposed size hint for the rotated bar.
+ """
tSize = QTabBar.tabSizeHint(self, theIndex)
tSize.transpose()
return tSize
def paintEvent(self, theEvent):
-
+ """Custom implementation of the label painter that rotates the
+ label 90 degrees.
+ """
pObj = QStylePainter(self)
oObj = QStyleOptionTab()
diff --git a/nw/gui/additions/qconfiglayout.py b/nw/gui/additions/qconfiglayout.py
index 032d0e6b..f4eb1ca7 100644
--- a/nw/gui/additions/qconfiglayout.py
+++ b/nw/gui/additions/qconfiglayout.py
@@ -69,11 +69,15 @@ class QConfigLayout(QGridLayout):
return
def setHelpText(self, intRow, theText):
+ """Set the text for the help label.
+ """
if intRow in self._itemMap:
self._itemMap[intRow]["help"].setText(theText)
return
def setLabelText(self, intRow, theText):
+ """Set the text for the main label.
+ """
if intRow in self._itemMap:
self._itemMap[intRow]["label"].setText(theText)
return
@@ -85,7 +89,6 @@ class QConfigLayout(QGridLayout):
def addGroupLabel(self, theLabel):
"""Adds a text label to separate groups of settings.
"""
-
if isinstance(theLabel, QLabel):
qLabel = theLabel
elif isinstance(theLabel, str):
@@ -107,7 +110,6 @@ class QConfigLayout(QGridLayout):
def addRow(self, theLabel, theWidget, helpText=None, theUnit=None):
"""Add a label and a widget as a new row of the grid.
"""
-
thisEntry = {
"label" : None,
"help" : None,
diff --git a/nw/gui/additions/qswitch.py b/nw/gui/additions/qswitch.py
index 39fc5b8a..7eb32830 100644
--- a/nw/gui/additions/qswitch.py
+++ b/nw/gui/additions/qswitch.py
@@ -97,7 +97,6 @@ class QSwitch(QAbstractButton):
def paintEvent(self, event):
"""Drawing the switch itself.
"""
-
qPaint = QPainter(self)
qPaint.setRenderHint(QPainter.Antialiasing, True)
qPaint.setPen(Qt.NoPen)
diff --git a/nw/gui/elements/docdetails.py b/nw/gui/elements/docdetails.py
index 12eb6030..6a12f0b3 100644
--- a/nw/gui/elements/docdetails.py
+++ b/nw/gui/elements/docdetails.py
@@ -143,31 +143,33 @@ class GuiDocDetails(QFrame):
self.pCountData.setAlignment(Qt.AlignRight)
# Assemble
- self.mainBox.addWidget(self.labelName, 0, 0, 1, 1)
- self.mainBox.addWidget(self.labelFlag, 0, 1, 1, 1)
- self.mainBox.addWidget(self.labelData, 0, 2, 1, 3)
+ self.mainBox.addWidget(self.labelName, 0, 0, 1, 1, Qt.AlignTop)
+ self.mainBox.addWidget(self.labelFlag, 0, 1, 1, 1, Qt.AlignTop)
+ self.mainBox.addWidget(self.labelData, 0, 2, 1, 3, Qt.AlignTop)
- self.mainBox.addWidget(self.statusName, 1, 0, 1, 1)
- self.mainBox.addWidget(self.statusFlag, 1, 1, 1, 1)
- self.mainBox.addWidget(self.statusData, 1, 2, 1, 1)
- self.mainBox.addWidget(self.cCountName, 1, 3, 1, 1)
- self.mainBox.addWidget(self.cCountData, 1, 4, 1, 1)
+ self.mainBox.addWidget(self.statusName, 1, 0, 1, 1, Qt.AlignTop)
+ self.mainBox.addWidget(self.statusFlag, 1, 1, 1, 1, Qt.AlignTop)
+ self.mainBox.addWidget(self.statusData, 1, 2, 1, 1, Qt.AlignTop)
+ self.mainBox.addWidget(self.cCountName, 1, 3, 1, 1, Qt.AlignTop)
+ self.mainBox.addWidget(self.cCountData, 1, 4, 1, 1, Qt.AlignTop)
- self.mainBox.addWidget(self.className, 2, 0, 1, 1)
- self.mainBox.addWidget(self.classFlag, 2, 1, 1, 1)
- self.mainBox.addWidget(self.classData, 2, 2, 1, 1)
- self.mainBox.addWidget(self.wCountName, 2, 3, 1, 1)
- self.mainBox.addWidget(self.wCountData, 2, 4, 1, 1)
+ self.mainBox.addWidget(self.className, 2, 0, 1, 1, Qt.AlignTop)
+ self.mainBox.addWidget(self.classFlag, 2, 1, 1, 1, Qt.AlignTop)
+ self.mainBox.addWidget(self.classData, 2, 2, 1, 1, Qt.AlignTop)
+ self.mainBox.addWidget(self.wCountName, 2, 3, 1, 1, Qt.AlignTop)
+ self.mainBox.addWidget(self.wCountData, 2, 4, 1, 1, Qt.AlignTop)
- self.mainBox.addWidget(self.layoutName, 3, 0, 1, 1)
- self.mainBox.addWidget(self.layoutFlag, 3, 1, 1, 1)
- self.mainBox.addWidget(self.layoutData, 3, 2, 1, 1)
- self.mainBox.addWidget(self.pCountName, 3, 3, 1, 1)
- self.mainBox.addWidget(self.pCountData, 3, 4, 1, 1)
+ self.mainBox.addWidget(self.layoutName, 3, 0, 1, 1, Qt.AlignTop)
+ self.mainBox.addWidget(self.layoutFlag, 3, 1, 1, 1, Qt.AlignTop)
+ self.mainBox.addWidget(self.layoutData, 3, 2, 1, 1, Qt.AlignTop)
+ self.mainBox.addWidget(self.pCountName, 3, 3, 1, 1, Qt.AlignTop)
+ self.mainBox.addWidget(self.pCountData, 3, 4, 1, 1, Qt.AlignTop)
self.mainBox.setColumnStretch(0,0)
self.mainBox.setColumnStretch(1,0)
self.mainBox.setColumnStretch(2,1)
+ self.mainBox.setColumnStretch(3,0)
+ self.mainBox.setColumnStretch(4,0)
logger.debug("DocDetails initialisation complete")
@@ -180,7 +182,6 @@ class GuiDocDetails(QFrame):
def updateViewBox(self, tHandle):
"""Populate the details box from a given handle.
"""
-
nwItem = self.theProject.projTree[tHandle]
if nwItem is None:
diff --git a/nw/gui/elements/doceditor.py b/nw/gui/elements/doceditor.py
index 5e5e6228..906d4c65 100644
--- a/nw/gui/elements/doceditor.py
+++ b/nw/gui/elements/doceditor.py
@@ -141,7 +141,6 @@ class GuiDocEditor(QTextEdit):
"""Clear the current document and reset all document related
flags and counters.
"""
-
self.nwDocument.clearDocument()
self.setReadOnly(True)
self.clear()
@@ -167,7 +166,6 @@ class GuiDocEditor(QTextEdit):
settings. This function is both called when the editor is
created, and when the user changes the main editor preferences.
"""
-
# Some Constants
self.nonWord = "\"'"
self.nonWord += "".join(self.mainConf.fmtDoubleQuotes)
@@ -241,7 +239,6 @@ class GuiDocEditor(QTextEdit):
document is new (empty string), we set up the editor for editing
the file.
"""
-
theDoc = self.nwDocument.openDocument(tHandle, showStatus=showStatus)
if theDoc is None:
# There was an io error
@@ -319,7 +316,6 @@ class GuiDocEditor(QTextEdit):
Config.textFixedW is enabled or we're in Zen mode. Otherwise,
just ensure the margins are set correctly.
"""
-
if self.mainConf.textFixedW or self.theParent.isZenMode:
vBar = self.verticalScrollBar()
if vBar.isVisible():
@@ -376,6 +372,10 @@ class GuiDocEditor(QTextEdit):
##
def setDocumentChanged(self, bValue):
+ """Keeps track of the document changed variable, and ensures
+ that the corresponding icon on the status bar shows the same
+ status.
+ """
self.docChanged = bValue
self.theParent.statusBar.setDocumentStatus(self.docChanged)
return self.docChanged
@@ -437,7 +437,6 @@ class GuiDocEditor(QTextEdit):
If the spell check mode (theMode) is not defined (None), then
toggle the current status saved in this class.
"""
-
if theMode is None:
theMode = not self.spellCheck
@@ -460,7 +459,6 @@ class GuiDocEditor(QTextEdit):
currently loaded text. The fastest way to do this, at least as
of Qt 5.13, is to clear the text and put it back.
"""
-
logger.verbose("Running spell checker")
if self.spellCheck:
bfTime = time()
@@ -481,6 +479,12 @@ class GuiDocEditor(QTextEdit):
##
def docAction(self, theAction):
+ """Perform an action on the current document based on an action
+ flag. This is just a single entry point wrapper function to
+ ensure all the feature functions get the correct information
+ passed to it without having to consider the internal logic of
+ this class when calling these actions from other classes.
+ """
logger.verbose("Requesting action: %s" % theAction.name)
if not self.theParent.hasProject:
logger.error("No project open")
@@ -537,9 +541,14 @@ class GuiDocEditor(QTextEdit):
return True
def isEmpty(self):
+ """Wrapper function to check if the current document is empty.
+ """
return self.qDocument.isEmpty()
def revealLocation(self):
+ """Tell the user where on the file system the file in the editor
+ is saved.
+ """
if self.theHandle is not None:
msgBox = QMessageBox()
msgBox.information(self, "File Location", (
@@ -568,7 +577,6 @@ class GuiDocEditor(QTextEdit):
However, we don't want to spend a lot of time in this function
as it is triggered on every keypress when typing.
"""
-
self.hasSelection = self.textCursor().hasSelection()
if keyEvent.modifiers() == Qt.ShiftModifier:
@@ -628,7 +636,6 @@ class GuiDocEditor(QTextEdit):
"""Triggered by right click to open the context menu. Also
triggered by the Ctrl+. shortcut.
"""
-
if not self.spellCheck:
return
@@ -667,7 +674,11 @@ class GuiDocEditor(QTextEdit):
return
+ @pyqtSlot("QTextCursor", str)
def _correctWord(self, theCursor, theWord):
+ """Slot for the spell check context menu triggering the
+ replacement of a word with the word from the dictionary.
+ """
xPos = theCursor.selectionStart()
theCursor.beginEditBlock()
theCursor.removeSelectedText()
@@ -677,7 +688,11 @@ class GuiDocEditor(QTextEdit):
self.setTextCursor(theCursor)
return
+ @pyqtSlot("QTextCursor")
def _addWord(self, theCursor):
+ """Slot for the spell check context menu triggered when the user
+ wants to add a word to the project dictionary.
+ """
theWord = theCursor.selectedText().strip().strip(self.nonWord)
logger.debug("Added '%s' to project dictionary" % theWord)
self.theDict.addWord(theWord)
@@ -729,7 +744,6 @@ class GuiDocEditor(QTextEdit):
tag and can tell the document viewer to try and find and load
the file where the tag is defined.
"""
-
if theCursor is None:
theCursor = self.textCursor()
@@ -772,13 +786,15 @@ class GuiDocEditor(QTextEdit):
return
def _openSpellContext(self):
+ """Opens the spell check context menu at the current point of
+ the cursor.
+ """
self._openContextMenu(self.cursorRect().center())
return
def _docAutoReplace(self, theBlock):
"""Autoreplace text elements based on main configuration.
"""
-
if not theBlock.isValid():
return
@@ -870,7 +886,6 @@ class GuiDocEditor(QTextEdit):
def _formatBlock(self, docAction):
"""Changes the block format of the block under the cursor.
"""
-
theCursor = self.textCursor()
theBlock = theCursor.block()
if not theBlock.isValid():
@@ -949,6 +964,8 @@ class GuiDocEditor(QTextEdit):
return
def _makeSelection(self, selMode):
+ """Wrapper function to select a word based on a selection mode.
+ """
theCursor = self.textCursor()
theCursor.clearSelection()
theCursor.select(selMode)
@@ -1025,7 +1042,6 @@ class GuiDocEditor(QTextEdit):
"""Create the spell checking object based on the spellTool
setting in config.
"""
-
if self.mainConf.spellTool == "enchant":
from nw.core.spellcheck import NWSpellEnchant
self.theDict = NWSpellEnchant()
diff --git a/nw/gui/elements/doctitlebar.py b/nw/gui/elements/doctitlebar.py
index 9e05f479..38425961 100644
--- a/nw/gui/elements/doctitlebar.py
+++ b/nw/gui/elements/doctitlebar.py
@@ -79,7 +79,6 @@ class GuiDocTitleBar(QLabel):
"""Sets the document title from the handle, or alternatively,
set the whole document path.
"""
-
self.setText("")
self.theHandle = tHandle
if tHandle is None:
diff --git a/nw/gui/elements/doctree.py b/nw/gui/elements/doctree.py
index a91ee10e..2c81cf91 100644
--- a/nw/gui/elements/doctree.py
+++ b/nw/gui/elements/doctree.py
@@ -105,13 +105,19 @@ class GuiDocTree(QTreeWidget):
##
def clearTree(self):
+ """Clear the GUI content and the related maps.
+ """
self.clear()
self.theMap = {}
self.orphRoot = None
return
def newTreeItem(self, itemType, itemClass):
-
+ """Add new item to the tree, with a given itemType and
+ itemClass, and attach it to the selected handle. Also make sure
+ the item is added in a place it can be added, and that other
+ meta data is set correctly to ensure a valid project tree.
+ """
pHandle = self.getSelectedHandle()
if not self.theParent.hasProject:
@@ -274,7 +280,6 @@ class GuiDocTree(QTreeWidget):
function only asks for confirmation once, and calls the regular
deleteItem function for each document in the Trash folder.
"""
-
trashHandle = self.theProject.projTree.trashRoot()
logger.debug("Emptying Trash folder")
@@ -315,7 +320,6 @@ class GuiDocTree(QTreeWidget):
that to save memory. Items not in the tree are not saved to the
project file, so a loaded project will be clean anyway.
"""
-
if tHandle is None:
tHandle = self.getSelectedHandle()
@@ -446,6 +450,13 @@ class GuiDocTree(QTreeWidget):
return
def propagateCount(self, tHandle, theCount, nDepth=0):
+ """Recursive function setting the word count for a given item,
+ and propagating that count upwards in the tree until reaching a
+ root item. This function is more efficient than recalculating
+ everything each time the word count is updated, but is also
+ prone to diverging from the true values if the counts are not
+ properly reported to the function.
+ """
tItem = self._getTreeItem(tHandle)
if tItem is not None:
tItem.setText(self.C_COUNT,str(theCount))
@@ -460,6 +471,12 @@ class GuiDocTree(QTreeWidget):
return
def projectWordCount(self):
+ """Sum up the word counts for all root items and set the
+ relevant values in the project and on the status bar. This call
+ is a fast way of getting this number, and depends on the
+ propagateCount function being called when it should to maintain
+ the correct count.
+ """
nWords = 0
for n in range(self.topLevelItemCount()):
tItem = self.topLevelItem(n)
@@ -472,6 +489,11 @@ class GuiDocTree(QTreeWidget):
return
def buildTree(self):
+ """Build the entire project tree from scratch. This depends on
+ the save project item iterator in the project class which will
+ always make sure items with a parent have had their parent item
+ sent first.
+ """
self.clear()
for nwItem in self.theProject.getProjectItems():
self._addTreeItem(nwItem)
@@ -517,11 +539,16 @@ class GuiDocTree(QTreeWidget):
##
def _getTreeItem(self, tHandle):
+ """Returns the QTreeWidgetItem of a given item handle.
+ """
if tHandle in self.theMap.keys():
return self.theMap[tHandle]
return None
def _scanChildren(self, theList, theItem, theIndex):
+ """This is a recursive function returning all items in a tree
+ starting at a given QTreeWidgetItem.
+ """
tHandle = theItem.text(self.C_HANDLE)
nwItem = self.theProject.projTree[tHandle]
nwItem.setExpanded(theItem.isExpanded())
@@ -532,7 +559,9 @@ class GuiDocTree(QTreeWidget):
return theList
def _addTreeItem(self, nwItem):
-
+ """Create a QTreeWidgetItem from an NWItem and add it to the
+ project tree.
+ """
tHandle = nwItem.itemHandle
pHandle = nwItem.parHandle
tClass = nwItem.itemClass
@@ -591,6 +620,9 @@ class GuiDocTree(QTreeWidget):
return trItem
def _addOrphanedRoot(self):
+ """Add the special Orphaned Files root item to hold non-root
+ items with no parent set.
+ """
if self.orphRoot is None:
newItem = QTreeWidgetItem([""]*4)
newItem.setText(self.C_NAME, "Orphaned Files")
@@ -604,6 +636,8 @@ class GuiDocTree(QTreeWidget):
return
def _cleanOrphanedRoot(self):
+ """Remove the special Orphaned Files root folder if it is empty.
+ """
if self.orphRoot is not None:
if self.orphRoot.childCount() == 0:
self.takeTopLevelItem(self.indexOfTopLevelItem(self.orphRoot))
@@ -615,7 +649,6 @@ class GuiDocTree(QTreeWidget):
in the project is consistent with the treeView. Also move the
word count over to the new parent tree.
"""
-
trItemS = self._getTreeItem(tHandle)
nwItemS = self.theProject.projTree[tHandle]
trItemP = trItemS.parent()
@@ -636,6 +669,10 @@ class GuiDocTree(QTreeWidget):
return True
def _moveOrphanedItem(self, tHandle, dHandle):
+ """Move an Orphaned Item to a new dHandle parent item. This
+ function will set all the missing meta data based on the meta
+ data of the destination item.
+ """
trItemS = self._getTreeItem(tHandle)
nwItemS = self.theProject.projTree[tHandle]
nwItemD = self.theProject.projTree[dHandle]
diff --git a/nw/gui/elements/docviewer.py b/nw/gui/elements/docviewer.py
index c99b1de1..7fdf22e4 100644
--- a/nw/gui/elements/docviewer.py
+++ b/nw/gui/elements/docviewer.py
@@ -88,7 +88,6 @@ class GuiDocViewer(QTextBrowser):
def initViewer(self):
"""Set editor settings from main config.
"""
-
self._makeStyleSheet()
# Set Font
@@ -122,7 +121,6 @@ class GuiDocViewer(QTextBrowser):
def loadText(self, tHandle):
"""Load text into the viewer from an item handle.
"""
-
tItem = self.theProject.projTree[tHandle]
if tItem is None:
logger.warning("Item not found")
@@ -153,11 +151,16 @@ class GuiDocViewer(QTextBrowser):
return True
def reloadText(self):
+ """Reload the text in the current document.
+ """
self.loadText(self.theHandle)
return
def loadFromTag(self, theTag):
-
+ """Load text in the document from a reference given by a meta
+ tag rather than a known handle. This function depends on the
+ index being up to date.
+ """
logger.debug("Loading document from tag '%s'" % theTag)
if theTag in self.theParent.theIndex.tagIndex.keys():
@@ -175,6 +178,9 @@ class GuiDocViewer(QTextBrowser):
return True
def docAction(self, theAction):
+ """Wrapper function for various document actions on the current
+ document.
+ """
logger.verbose("Requesting action: %s" % theAction.name)
if self.theHandle is None:
logger.error("No document open")
@@ -225,6 +231,9 @@ class GuiDocViewer(QTextBrowser):
##
def _makeSelection(self, selMode):
+ """Wrapper function for making a selection based on a specific
+ selection mode.
+ """
theCursor = self.textCursor()
theCursor.clearSelection()
theCursor.select(selMode)
@@ -232,7 +241,8 @@ class GuiDocViewer(QTextBrowser):
return
def _linkClicked(self, theURL):
-
+ """Slot for a link in the document being clicked.
+ """
theLink = theURL.url()
tHandle = None
onLine = 0
@@ -256,7 +266,9 @@ class GuiDocViewer(QTextBrowser):
return
def _makeStyleSheet(self):
-
+ """Generate an appropriate style sheet for the document viewer,
+ based on the current syntax highlighter theme,
+ """
styleSheet = (
"body {{"
" color: rgb({tColR},{tColG},{tColB});"
diff --git a/nw/gui/elements/noticebar.py b/nw/gui/elements/noticebar.py
index b5580984..4b6d57aa 100644
--- a/nw/gui/elements/noticebar.py
+++ b/nw/gui/elements/noticebar.py
@@ -67,11 +67,15 @@ class GuiNoticeBar(QFrame):
return
def showNote(self, theNote):
+ """Show the note on the noticebar.
+ """
self.noteLabel.setText("Note: %s" % theNote)
self.setVisible(True)
return
def hideNote(self):
+ """Clear the noticebar and hide it.
+ """
self.noteLabel.setText("")
self.setVisible(False)
return
diff --git a/nw/gui/elements/outline.py b/nw/gui/elements/outline.py
index 54cfbf5a..f09a38b6 100644
--- a/nw/gui/elements/outline.py
+++ b/nw/gui/elements/outline.py
@@ -125,7 +125,6 @@ class GuiProjectOutline(QTreeWidget):
"""Clear the tree and header and set the default values for the
columns arrays.
"""
-
self.clear()
self.setColumnCount(1)
self.setHeaderLabel(nwLabels.OUTLINE_COLS[nwOutline.TITLE])
@@ -150,7 +149,6 @@ class GuiProjectOutline(QTreeWidget):
what data to load, and if necessary, force a rebuild of the
tree.
"""
-
# If it's the first time, we always build
if self.firstView or self.firstView and overRide:
self._loadHeaderState()
@@ -229,7 +227,6 @@ class GuiProjectOutline(QTreeWidget):
"""Load the state of the main tree header, that is, column order
and column width.
"""
-
# Load whatever we saved last time, regardless of wether it
# contains the correct names or number of columns. The names
# must be valid though.
@@ -280,7 +277,6 @@ class GuiProjectOutline(QTreeWidget):
save the current width of hidden columns though. This preserves
the last known width in case they're unhidden again.
"""
-
# If we haven't built the tree, there is nothing to save.
if self.lastBuild == 0:
return
@@ -319,7 +315,6 @@ class GuiProjectOutline(QTreeWidget):
if they are hidden. This ensures that showing and hiding columns
is fast and doesn't require a rebuild of the tree.
"""
-
self.clear()
if self.firstView:
@@ -347,7 +342,7 @@ class GuiProjectOutline(QTreeWidget):
currChapter = None
currScene = None
- for titleKey in self.theIndex.getNovelStructure():
+ for titleKey in self.theIndex.getNovelStructure(skipExcluded=True):
if len(titleKey) < 16:
continue
@@ -404,7 +399,6 @@ class GuiProjectOutline(QTreeWidget):
def _createTreeItem(self, tHandle, sTitle):
"""Populate a tree item with all the column values.
"""
-
nwItem = self.theProject.projTree[tHandle]
novIdx = self.theIndex.novelIndex[tHandle][sTitle]
diff --git a/nw/gui/elements/searchbar.py b/nw/gui/elements/searchbar.py
index 431bddad..590d8d0a 100644
--- a/nw/gui/elements/searchbar.py
+++ b/nw/gui/elements/searchbar.py
@@ -98,6 +98,9 @@ class GuiSearchBar(QFrame):
##
def setSearchText(self, theText):
+ """Open the search bar and set the search text to the text
+ provided, if any.
+ """
if not self.isVisible():
self.setVisible(True)
self.searchBox.setText(theText)
@@ -106,15 +109,21 @@ class GuiSearchBar(QFrame):
return True
def setReplaceText(self, theText):
+ """Set the replace text.
+ """
self._replaceVisible(True)
self.replaceBox.setFocus()
self.replaceBox.setText(theText)
return True
def getSearchText(self):
+ """Return the current search text.
+ """
return self.searchBox.text()
def getReplaceText(self):
+ """Return the current replace text.
+ """
return self.replaceBox.text()
##
@@ -122,11 +131,15 @@ class GuiSearchBar(QFrame):
##
def _doClose(self):
+ """Hide the search/replace bar.
+ """
self._replaceVisible(False)
self.setVisible(False)
return
def _doSearch(self):
+ """Call the search action function for the document editor.
+ """
modKey = qApp.keyboardModifiers()
if modKey == Qt.ShiftModifier:
self.theParent.docEditor.docAction(nwDocAction.GO_PREV)
@@ -135,10 +148,14 @@ class GuiSearchBar(QFrame):
return
def _doReplace(self):
+ """Call the replace action function for the document editor.
+ """
self.theParent.docEditor.docAction(nwDocAction.REPL_NEXT)
return
def _replaceVisible(self, isVisible):
+ """Set the visibility of all the replace widgets.
+ """
self.replaceLabel.setVisible(isVisible)
self.replaceBox.setVisible(isVisible)
self.replaceButton.setVisible(isVisible)
diff --git a/nw/gui/elements/viewdetails.py b/nw/gui/elements/viewdetails.py
index cb0d6af3..589ffee5 100644
--- a/nw/gui/elements/viewdetails.py
+++ b/nw/gui/elements/viewdetails.py
@@ -94,7 +94,9 @@ class GuiDocViewDetails(QWidget):
return
def refreshReferences(self, tHandle):
-
+ """Update the current list of document references from the
+ project index.
+ """
self.currHandle = tHandle
if self.isSticky.isChecked():
@@ -117,12 +119,17 @@ class GuiDocViewDetails(QWidget):
##
def _linkClicked(self, theLink):
+ """Capture the link-click and forward it to the document viewer
+ class for handling.
+ """
if len(theLink) == 18:
tHandle = theLink[-13:]
self.theParent.viewDocument(tHandle)
return
def _doShowHide(self, chState):
+ """Toggle the expand/collapse of the panel.
+ """
self.scrollBox.setVisible(chState)
self.mainConf.setShowRefPanel(chState)
if chState:
@@ -132,6 +139,8 @@ class GuiDocViewDetails(QWidget):
return
def _doSticky(self, chState):
+ """Toggle the sticky feature of the references.
+ """
if not chState and self.currHandle is not None:
self.refreshReferences(self.currHandle)
return
diff --git a/nw/gui/tools/dochighlight.py b/nw/gui/tools/dochighlight.py
index 9d57b612..cda6ac03 100644
--- a/nw/gui/tools/dochighlight.py
+++ b/nw/gui/tools/dochighlight.py
@@ -75,6 +75,9 @@ class GuiDocHighlighter(QSyntaxHighlighter):
return
def initHighlighter(self):
+ """Initialise the syntax highlighter, setting all the colour
+ rules and building the regexes.
+ """
logger.debug("Setting up highlighting rules")
@@ -205,14 +208,21 @@ class GuiDocHighlighter(QSyntaxHighlighter):
##
def setDict(self, theDict):
+ """Set the dictionary object for spell check underlines lookup.
+ """
self.theDict = theDict
return True
def setSpellCheck(self, theMode):
+ """Enable/disable the real time spell checker.
+ """
self.spellCheck = theMode
return True
def setHandle(self, theHandle):
+ """Set the handle of the currently highlighted document. This is
+ needed for the index lookup for validating tags and references.
+ """
self.theHandle = theHandle
return True
@@ -226,7 +236,6 @@ class GuiDocHighlighter(QSyntaxHighlighter):
is significantly faster than running the regex checks we use for
text paragraphs.
"""
-
if self.theHandle is None or not theText:
return
@@ -317,7 +326,6 @@ class GuiDocHighlighter(QSyntaxHighlighter):
"""Generate a valid character format to be applied to the text
that is to be highlighted.
"""
-
theFormat = QTextCharFormat()
if fmtCol is not None:
diff --git a/nw/gui/tools/optionstate.py b/nw/gui/tools/optionstate.py
index ed45e7cf..5f265622 100644
--- a/nw/gui/tools/optionstate.py
+++ b/nw/gui/tools/optionstate.py
@@ -51,7 +51,6 @@ class OptionState():
def loadSettings(self):
"""Load the options dictionary from the project settings file.
"""
-
if self.theProject.projMeta is None:
return False
@@ -76,7 +75,6 @@ class OptionState():
def saveSettings(self):
"""Save the options dictionary to the project settings file.
"""
-
if self.theProject.projMeta is None:
return False
diff --git a/nw/gui/tools/wordcounter.py b/nw/gui/tools/wordcounter.py
index 24a0b9ca..49255040 100644
--- a/nw/gui/tools/wordcounter.py
+++ b/nw/gui/tools/wordcounter.py
@@ -45,7 +45,9 @@ class WordCounter(QThread):
return
def run(self):
-
+ """Overloaded run function for the word counter, forwarding the
+ call to the function that does the actual counting.
+ """
theText = self.theParent.getText()
cC, wC, pC = countWords(theText)
diff --git a/nw/guimain.py b/nw/guimain.py
index 558712ed..b15edbe9 100644
--- a/nw/guimain.py
+++ b/nw/guimain.py
@@ -57,7 +57,7 @@ class GuiMain(QMainWindow):
logger.info("Starting %s" % nw.__package__)
logger.debug("Initialising GUI ...")
- self.mainConf = nw.CONFIG
+ self.mainConf = nw.CONFIG
# Some runtime info useful for debugging
logger.info("OS: %s" % self.mainConf.osType)
diff --git a/requirements.txt b/requirements.txt
index 08fc42c3..c50e7b68 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,3 +1,3 @@
-pyqt5
-lxml
-pyenchant
+pyqt5>=5.2.1
+lxml>=4.2.0
+pyenchant>=3.0.0
diff --git a/sample/sampleNovel/data_1/4298de4d9524_main.nwd b/sample/data_1/4298de4d9524_main.nwd
similarity index 100%
rename from sample/sampleNovel/data_1/4298de4d9524_main.nwd
rename to sample/data_1/4298de4d9524_main.nwd
diff --git a/sample/sampleNovel/data_5/3b69b83cdafc_main.nwd b/sample/data_5/3b69b83cdafc_main.nwd
similarity index 100%
rename from sample/sampleNovel/data_5/3b69b83cdafc_main.nwd
rename to sample/data_5/3b69b83cdafc_main.nwd
diff --git a/sample/sampleNovel/data_5/eaea4e8cdee8_main.nwd b/sample/data_5/eaea4e8cdee8_main.nwd
similarity index 100%
rename from sample/sampleNovel/data_5/eaea4e8cdee8_main.nwd
rename to sample/data_5/eaea4e8cdee8_main.nwd
diff --git a/sample/sampleNovel/data_6/36b6aa9b697b_main.nwd b/sample/data_6/36b6aa9b697b_main.nwd
similarity index 100%
rename from sample/sampleNovel/data_6/36b6aa9b697b_main.nwd
rename to sample/data_6/36b6aa9b697b_main.nwd
diff --git a/sample/sampleNovel/data_6/a2d6d5f4f401_main.nwd b/sample/data_6/a2d6d5f4f401_main.nwd
similarity index 100%
rename from sample/sampleNovel/data_6/a2d6d5f4f401_main.nwd
rename to sample/data_6/a2d6d5f4f401_main.nwd
diff --git a/sample/sampleNovel/data_8/8706ddc78b1b_main.nwd b/sample/data_8/8706ddc78b1b_main.nwd
similarity index 100%
rename from sample/sampleNovel/data_8/8706ddc78b1b_main.nwd
rename to sample/data_8/8706ddc78b1b_main.nwd
diff --git a/sample/sampleNovel/data_9/6b68994dfa3d_main.nwd b/sample/data_9/6b68994dfa3d_main.nwd
similarity index 100%
rename from sample/sampleNovel/data_9/6b68994dfa3d_main.nwd
rename to sample/data_9/6b68994dfa3d_main.nwd
diff --git a/sample/data_9/74e400180a99_main.nwd b/sample/data_9/74e400180a99_main.nwd
new file mode 100644
index 00000000..303d5232
--- /dev/null
+++ b/sample/data_9/74e400180a99_main.nwd
@@ -0,0 +1,4 @@
+%%~ 974e400180a99:7031beac91f75:Page
+This is a plain page with some text on it.
+
+This file should receive no special formatting, but the text will always be left aligned and the content will always start on a fresh page when the project is exported.
diff --git a/sample/sampleNovel/data_a/e7339df26ded_main.nwd b/sample/data_a/e7339df26ded_main.nwd
similarity index 100%
rename from sample/sampleNovel/data_a/e7339df26ded_main.nwd
rename to sample/data_a/e7339df26ded_main.nwd
diff --git a/sample/sampleNovel/data_b/3e74dbc1f584_main.nwd b/sample/data_b/3e74dbc1f584_main.nwd
similarity index 100%
rename from sample/sampleNovel/data_b/3e74dbc1f584_main.nwd
rename to sample/data_b/3e74dbc1f584_main.nwd
diff --git a/sample/sampleNovel/data_b/8136a5a774a0_main.nwd b/sample/data_b/8136a5a774a0_main.nwd
similarity index 100%
rename from sample/sampleNovel/data_b/8136a5a774a0_main.nwd
rename to sample/data_b/8136a5a774a0_main.nwd
diff --git a/sample/sampleNovel/data_b/a8a28a246524_main.nwd b/sample/data_b/a8a28a246524_main.nwd
similarity index 100%
rename from sample/sampleNovel/data_b/a8a28a246524_main.nwd
rename to sample/data_b/a8a28a246524_main.nwd
diff --git a/sample/sampleNovel/data_b/b2c23b3c42cc_main.nwd b/sample/data_b/b2c23b3c42cc_main.nwd
similarity index 100%
rename from sample/sampleNovel/data_b/b2c23b3c42cc_main.nwd
rename to sample/data_b/b2c23b3c42cc_main.nwd
diff --git a/sample/sampleNovel/data_b/c0cbd2a407f3_main.nwd b/sample/data_b/c0cbd2a407f3_main.nwd
similarity index 100%
rename from sample/sampleNovel/data_b/c0cbd2a407f3_main.nwd
rename to sample/data_b/c0cbd2a407f3_main.nwd
diff --git a/sample/sampleNovel/data_e/dca4be2fcaf8_main.nwd b/sample/data_e/dca4be2fcaf8_main.nwd
similarity index 100%
rename from sample/sampleNovel/data_e/dca4be2fcaf8_main.nwd
rename to sample/data_e/dca4be2fcaf8_main.nwd
diff --git a/sample/sampleNovel/data_f/1471bef9f2ae_main.nwd b/sample/data_f/1471bef9f2ae_main.nwd
similarity index 100%
rename from sample/sampleNovel/data_f/1471bef9f2ae_main.nwd
rename to sample/data_f/1471bef9f2ae_main.nwd
diff --git a/sample/sampleNovel/nwProject.nwx b/sample/nwProject.nwx
similarity index 90%
rename from sample/sampleNovel/nwProject.nwx
rename to sample/nwProject.nwx
index b4b7b117..ad832035 100644
--- a/sample/sampleNovel/nwProject.nwx
+++ b/sample/nwProject.nwx
@@ -1,5 +1,5 @@
-
+
Sample Project
Sample Project
@@ -11,8 +11,8 @@
True
True
636b6aa9b697b
- 6a2d6d5f4f401
- 875
+ ba8a28a246524
+ 914
B
E
@@ -44,7 +44,7 @@
Main
-
+
-
Novel
ROOT
@@ -65,20 +65,33 @@
2
78
- -
- Part 1
+
-
+ Page
+ FILE
+ NOVEL
+ New
+ False
+ True
+ PAGE
+ 208
+ 40
+ 2
+ 213
+
+ -
+ Part One
FILE
NOVEL
New
False
True
PARTITION
- 0
- 0
- 0
+ 23
+ 5
+ 1
0
- -
+
-
A Folder
FOLDER
NOVEL
@@ -109,7 +122,7 @@
1199
216
7
- 0
+ 527
-
Another Scene
@@ -277,9 +290,9 @@
False
True
SCENE
- 30
- 6
- 1
+ 0
+ 0
+ 0
36
diff --git a/setup.py b/setup.py
index f9736fee..9a59bccf 100755
--- a/setup.py
+++ b/setup.py
@@ -37,8 +37,8 @@ setuptools.setup(
],
python_requires = ">=3.6",
install_requires = [
- "pyqt5",
- "lxml",
- "pyenchant",
+ "pyqt5>=5.2.1",
+ "lxml>=4.2.0",
+ "pyenchant>=3.0.0",
],
)