* Update instructions and readmes
* Cleanup in base files
* Cleanup in core files
* Cleanup in gui files
* Cleanup in dialog files
* Cleanup in tools files
* Cleanup in test files
This commit is contained in:
Veronica Berglyd Olsen
2021-07-04 17:57:54 +02:00
committed by GitHub
parent 1881b3d32c
commit 6d7ca2bb86
51 changed files with 583 additions and 580 deletions
+5 -1
View File
@@ -22,8 +22,12 @@ either `testing` or `dev` branch.
### What Branch to Use for Contributions ### What Branch to Use for Contributions
* If your contribution is a fix for the latest stable release, branch from the `main` branch. * If your contribution is a fix for the latest stable release, branch from the `main` branch.
* If your contribution is a fix for the latest testing release, branch from the `testing` branch.
* If your contribution is a new feature, branch from the `dev` branch. * If your contribution is a new feature, branch from the `dev` branch.
* The `testing` branch is used infrequently. It is only used for pre-releases when new code is
being developed that is not a part of the current testing release.
The current status of each branch is described in a pinned issue titled
"[Development Flow & Status](https://github.com/vkbo/novelWriter/issues/707)".
Please do not make your changes on a branch with the same name as any of the above mentioned Please do not make your changes on a branch with the same name as any of the above mentioned
branches. You should make a unique and descriptive branch name in your fork for your changes. branches. You should make a unique and descriptive branch name in your fork for your changes.
+2 -1
View File
@@ -75,6 +75,7 @@ In addition, novelWriter adds the following syntax used for its additional featu
be used to make simple tables and lists. Note that for HTML exports, most browsers will treat a be used to make simple tables and lists. Note that for HTML exports, most browsers will treat a
tab as a space, so it may not show up like expected. Open Document exports should produce the tab as a space, so it may not show up like expected. Open Document exports should produce the
expected result. expected result.
* Paragraph alignment and indendtation is supported by a set of tags using `>` and `<` markers.
The core export formats of novelWriter are Open Document and HTML5. Open Document is an open The core export formats of novelWriter are Open Document and HTML5. Open Document is an open
standard for office type documents that is supported by most office applications. See standard for office type documents that is supported by most office applications. See
@@ -105,7 +106,7 @@ four can be used internally in each scene to create separate sections.
Each novel file can be assigned a layout format, which shows up as a flag next to the item in the Each novel file can be assigned a layout format, which shows up as a flag next to the item in the
project tree. These are mostly to help the user track what they contain, but they also have some project tree. These are mostly to help the user track what they contain, but they also have some
impact on the format of the exported document. See the impact on the layout of the exported document. See the
[documentation](https://novelwriter.readthedocs.io) for further details. [documentation](https://novelwriter.readthedocs.io) for further details.
### Project Notes ### Project Notes
+6 -6
View File
@@ -3,14 +3,14 @@
The `i18n` folder contains the translation files for the Qt5 GUI. There are two types of files The `i18n` folder contains the translation files for the Qt5 GUI. There are two types of files
involved: the `nw_XX.ts` and the `nw_XX.qm` files. The `nw_XX.ts` files are located in the `i18n` involved: the `nw_XX.ts` and the `nw_XX.qm` files. The `nw_XX.ts` files are located in the `i18n`
folder at the root of the repository, and the `nw_XX.qm` together with the `project_XX.json` files folder at the root of the repository, and the `nw_XX.qm` together with the `project_XX.json` files
are located in `nw/assets/i18n`. The latter are JSON files translation maps for the novelWriter are located in `nw/assets/i18n`. The latter are JSON translation maps for the novelWriter projects,
projects, used by the Build Novel Project tool. used by the Build Novel Project tool.
**Note** **Note**
When making a new translation, or updating an existing one, only commit the `nw_XX.ts` you have When making a new translation, or updating an existing one, only commit the `nw_XX.ts` you have
made changes to. The `qtlupdate` command mentioned below will likely modify all `nw_XX.ts` made changes to. The `qtlupdate` command mentioned below will likely modify all `nw_XX.ts`
slightly, but please _don't_ commit those changes and add them to the pull request. slightly, but please _don't_ commit those changes to the pull request.
## Qt GUI Localisation ## Qt GUI Localisation
@@ -39,8 +39,8 @@ When you're done editing, you can build the `nw/assets/i18n/nw_XX.qm` file and t
novelWriter. The Preferences dialog should list the newly added language, and you can select it and novelWriter. The Preferences dialog should list the newly added language, and you can select it and
test it. test it.
Please do not submit the `.qm` files to the repository. Only the `.ts` files in the `i18n` folder Please do not submit the `.qm` files to the repository. Only the `.ts` file you just added in the
are needed. `i18n` folder is needed.
**Note** **Note**
@@ -54,7 +54,7 @@ sudo apt install qttools5-dev-tools pyqt5-dev-tools
### Missing QtBase Translations ### Missing QtBase Translations
The default Qt dialogs also have translations, for instance for standard buttons for "Yes", "No", The default Qt dialogs also have translations, for instance for standard buttons like "Yes", "No",
"Ok", "Cancel", etc. Generally, these translations files are installed with the Qt libraries on "Ok", "Cancel", etc. Generally, these translations files are installed with the Qt libraries on
your system, and novelWriter will collect those translations from there. However, these your system, and novelWriter will collect those translations from there. However, these
translations are missing for many languages. translations are missing for many languages.
+9 -9
View File
@@ -39,9 +39,9 @@ from nw.config import Config
# Generally follows PEP 440 # Generally follows PEP 440
# Hex Version: # Hex Version:
# - Digit 1,2 : Major Version (01-ff) # - Digit 1,2 : Major Version (01-ff)
# = Digit 3,4 : Minor Version (01-ff) # - Digit 3,4 : Minor Version (01-ff)
# - Digit 5,6 : Patch Version (01-ff) # - Digit 5,6 : Patch Version (01-ff)
# = Digit 7 : Release Type (a: aplha, b: beta, c: candidate, f: final) # - Digit 7 : Release Type (a: aplha, b: beta, c: candidate, f: final)
# - Digit 8 : Release Number (0-f) # - Digit 8 : Release Number (0-f)
# #
# Example : Full Short Description # Example : Full Short Description
@@ -111,7 +111,7 @@ CONFIG = Config()
def main(sysArgs=None): def main(sysArgs=None):
"""Parses command line, sets up logging, and launches main GUI. """Parse command line, set up logging, and launches main GUI.
""" """
if sysArgs is None: if sysArgs is None:
sysArgs = sys.argv[1:] sysArgs = sys.argv[1:]
@@ -155,13 +155,13 @@ def main(sysArgs=None):
) )
# Defaults # Defaults
logLevel = logging.WARN logLevel = logging.WARN
logFormat = "{levelname:8} {message:}" logFormat = "{levelname:8} {message:}"
confPath = None confPath = None
dataPath = None dataPath = None
testMode = False testMode = False
qtStyle = "Fusion" qtStyle = "Fusion"
cmdOpen = None cmdOpen = None
# Parse Options # Parse Options
try: try:
+1 -1
View File
@@ -208,7 +208,7 @@ def splitVersionNumber(vString):
vMajor = 0 vMajor = 0
vMinor = 0 vMinor = 0
vPatch = 0 vPatch = 0
vInt = 0 vInt = 0
vBits = vString.split(".") vBits = vString.split(".")
nBits = len(vBits) nBits = len(vBits)
+12 -12
View File
@@ -23,12 +23,12 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>. along with this program. If not, see <https://www.gnu.org/licenses/>.
""" """
import os
import sys
import json
import shutil
import logging import logging
import configparser import configparser
import shutil
import json
import sys
import os
from time import time from time import time
@@ -433,9 +433,9 @@ class Config:
return False return False
cnfParse = configparser.ConfigParser() cnfParse = configparser.ConfigParser()
cnfPath = os.path.join(self.confPath, self.confFile) cnfPath = os.path.join(self.confPath, self.confFile)
try: try:
with open(cnfPath, mode="r", encoding="utf8") as inFile: with open(cnfPath, mode="r", encoding="utf-8") as inFile:
cnfParse.read_file(inFile) cnfParse.read_file(inFile)
except Exception as e: except Exception as e:
logger.error("Could not load config file") logger.error("Could not load config file")
@@ -807,7 +807,7 @@ class Config:
# Write config file # Write config file
cnfPath = os.path.join(self.confPath, self.confFile) cnfPath = os.path.join(self.confPath, self.confFile)
try: try:
with open(cnfPath, mode="w", encoding="utf8") as outFile: with open(cnfPath, mode="w", encoding="utf-8") as outFile:
cnfParse.write(outFile) cnfParse.write(outFile)
self.confChanged = False self.confChanged = False
except Exception as e: except Exception as e:
@@ -831,13 +831,13 @@ class Config:
if os.path.isfile(cacheFile): if os.path.isfile(cacheFile):
try: try:
with open(cacheFile, mode="r", encoding="utf8") as inFile: with open(cacheFile, mode="r", encoding="utf-8") as inFile:
theData = json.load(inFile) theData = json.load(inFile)
for projPath in theData.keys(): for projPath in theData.keys():
theEntry = theData[projPath] theEntry = theData[projPath]
theTitle = "" theTitle = ""
lastTime = 0 lastTime = 0
wordCount = 0 wordCount = 0
if "title" in theEntry.keys(): if "title" in theEntry.keys():
theTitle = theEntry["title"] theTitle = theEntry["title"]
@@ -869,7 +869,7 @@ class Config:
cacheTemp = os.path.join(self.dataPath, nwFiles.RECENT_FILE+"~") cacheTemp = os.path.join(self.dataPath, nwFiles.RECENT_FILE+"~")
try: try:
with open(cacheTemp, mode="w+", encoding="utf8") as outFile: with open(cacheTemp, mode="w+", encoding="utf-8") as outFile:
json.dump(self.recentProj, outFile, indent=2) json.dump(self.recentProj, outFile, indent=2)
except Exception as e: except Exception as e:
self.hasError = True self.hasError = True
+2 -2
View File
@@ -82,7 +82,7 @@ class NWDoc():
self._docMeta = {} self._docMeta = {}
if os.path.isfile(docPath): if os.path.isfile(docPath):
try: try:
with open(docPath, mode="r", encoding="utf8") as inFile: with open(docPath, mode="r", encoding="utf-8") as inFile:
# Check the first <= 10 lines for metadata # Check the first <= 10 lines for metadata
for i in range(10): for i in range(10):
@@ -136,7 +136,7 @@ class NWDoc():
) )
try: try:
with open(docTemp, mode="w", encoding="utf8") as outFile: with open(docTemp, mode="w", encoding="utf-8") as outFile:
outFile.write(docMeta) outFile.write(docMeta)
outFile.write(docText) outFile.write(docText)
except Exception as e: except Exception as e:
+15 -15
View File
@@ -144,13 +144,13 @@ class NWIndex():
def loadIndex(self): def loadIndex(self):
"""Load index from last session from the project meta folder. """Load index from last session from the project meta folder.
""" """
theData = {} theData = {}
indexFile = os.path.join(self.theProject.projMeta, nwFiles.INDEX_FILE) indexFile = os.path.join(self.theProject.projMeta, nwFiles.INDEX_FILE)
if os.path.isfile(indexFile): if os.path.isfile(indexFile):
logger.debug("Loading index file") logger.debug("Loading index file")
try: try:
with open(indexFile, mode="r", encoding="utf8") as inFile: with open(indexFile, mode="r", encoding="utf-8") as inFile:
theData = json.load(inFile) theData = json.load(inFile)
except Exception: except Exception:
logger.error("Failed to load index file") logger.error("Failed to load index file")
@@ -158,10 +158,10 @@ class NWIndex():
self.indexBroken = True self.indexBroken = True
return False return False
self._tagIndex = theData.get("tagIndex", {}) self._tagIndex = theData.get("tagIndex", {})
self._refIndex = theData.get("refIndex", {}) self._refIndex = theData.get("refIndex", {})
self._novelIndex = theData.get("novelIndex", {}) self._novelIndex = theData.get("novelIndex", {})
self._noteIndex = theData.get("noteIndex", {}) self._noteIndex = theData.get("noteIndex", {})
self._textCounts = theData.get("textCounts", {}) self._textCounts = theData.get("textCounts", {})
nowTime = round(time()) nowTime = round(time())
@@ -181,12 +181,12 @@ class NWIndex():
indexFile = os.path.join(self.theProject.projMeta, nwFiles.INDEX_FILE) indexFile = os.path.join(self.theProject.projMeta, nwFiles.INDEX_FILE)
try: try:
with open(indexFile, mode="w+", encoding="utf8") as outFile: with open(indexFile, mode="w+", encoding="utf-8") as outFile:
json.dump({ json.dump({
"tagIndex": self._tagIndex, "tagIndex": self._tagIndex,
"refIndex": self._refIndex, "refIndex": self._refIndex,
"novelIndex": self._novelIndex, "novelIndex": self._novelIndex,
"noteIndex": self._noteIndex, "noteIndex": self._noteIndex,
"textCounts": self._textCounts, "textCounts": self._textCounts,
}, outFile, indent=2) }, outFile, indent=2)
except Exception: except Exception:
@@ -292,12 +292,12 @@ class NWIndex():
for aTag in clearTags: for aTag in clearTags:
self._tagIndex.pop(aTag) self._tagIndex.pop(aTag)
nLine = 0 nLine = 0
nTitle = 0 nTitle = 0
theLines = theText.splitlines() theLines = theText.splitlines()
for aLine in theLines: for aLine in theLines:
nLine += 1 nLine += 1
nChar = len(aLine.strip()) nChar = len(aLine.strip())
if nChar == 0: if nChar == 0:
continue continue
@@ -352,16 +352,16 @@ class NWIndex():
""" """
if aLine.startswith("# "): if aLine.startswith("# "):
hDepth = "H1" hDepth = "H1"
hText = aLine[2:].strip() hText = aLine[2:].strip()
elif aLine.startswith("## "): elif aLine.startswith("## "):
hDepth = "H2" hDepth = "H2"
hText = aLine[3:].strip() hText = aLine[3:].strip()
elif aLine.startswith("### "): elif aLine.startswith("### "):
hDepth = "H3" hDepth = "H3"
hText = aLine[4:].strip() hText = aLine[4:].strip()
elif aLine.startswith("#### "): elif aLine.startswith("#### "):
hDepth = "H4" hDepth = "H4"
hText = aLine[5:].strip() hText = aLine[5:].strip()
else: else:
return False return False
+3 -3
View File
@@ -125,12 +125,12 @@ class OptionState():
return False return False
stateFile = os.path.join(self.theProject.projMeta, nwFiles.OPTS_FILE) stateFile = os.path.join(self.theProject.projMeta, nwFiles.OPTS_FILE)
theState = {} theState = {}
if os.path.isfile(stateFile): if os.path.isfile(stateFile):
logger.debug("Loading GUI options file") logger.debug("Loading GUI options file")
try: try:
with open(stateFile, mode="r", encoding="utf8") as inFile: with open(stateFile, mode="r", encoding="utf-8") as inFile:
theState = json.load(inFile) theState = json.load(inFile)
except Exception: except Exception:
logger.error("Failed to load GUI options file") logger.error("Failed to load GUI options file")
@@ -157,7 +157,7 @@ class OptionState():
logger.debug("Saving GUI options file") logger.debug("Saving GUI options file")
try: try:
with open(stateFile, mode="w+", encoding="utf8") as outFile: with open(stateFile, mode="w+", encoding="utf-8") as outFile:
json.dump(self.theState, outFile, indent=2) json.dump(self.theState, outFile, indent=2)
except Exception: except Exception:
logger.error("Failed to save GUI options file") logger.error("Failed to save GUI options file")
+17 -18
View File
@@ -355,7 +355,7 @@ class NWProject():
def openProject(self, fileName, overrideLock=False): def openProject(self, fileName, overrideLock=False):
"""Open the project file provided, or if doesn't exist, assume """Open the project file provided, or if doesn't exist, assume
it is a folder, and look for the file within it. If successful, it is a folder and look for the file within it. If successful,
parse the XML of the file and populate the project variables and parse the XML of the file and populate the project variables and
build the tree of project items. build the tree of project items.
""" """
@@ -483,10 +483,11 @@ class NWProject():
msgYes = self.theParent.askQuestion( msgYes = self.theParent.askQuestion(
self.tr("Version Conflict"), self.tr("Version Conflict"),
self.tr( self.tr(
"This project was saved by a newer version of novelWriter, version " "This project was saved by a newer version of "
"{0}. This is version {1}. If you continue to open the project, " "novelWriter, version {0}. This is version {1}. If you "
"some attributes and settings may not be preserved, but the " "continue to open the project, some attributes and "
"overall project should be fine. Continue opening the project?" "settings may not be preserved, but the overall project "
"should be fine. Continue opening the project?"
).format(appVersion, nw.__version__) ).format(appVersion, nw.__version__)
) )
if not msgYes: if not msgYes:
@@ -1219,7 +1220,7 @@ class NWProject():
loadFile = chkFile2 loadFile = chkFile2
try: try:
with open(loadFile, mode="r", encoding="utf8") as inFile: with open(loadFile, mode="r", encoding="utf-8") as inFile:
self.langData = json.load(inFile) self.langData = json.load(inFile)
logger.debug("Loaded project language file: %s", os.path.basename(loadFile)) logger.debug("Loaded project language file: %s", os.path.basename(loadFile))
@@ -1242,7 +1243,7 @@ class NWProject():
theLines = [] theLines = []
try: try:
with open(lockFile, mode="r", encoding="utf8") as inFile: with open(lockFile, mode="r", encoding="utf-8") as inFile:
theData = inFile.read() theData = inFile.read()
theLines = theData.splitlines() theLines = theData.splitlines()
if len(theLines) != 4: if len(theLines) != 4:
@@ -1263,7 +1264,7 @@ class NWProject():
lockFile = os.path.join(self.projPath, nwFiles.PROJ_LOCK) lockFile = os.path.join(self.projPath, nwFiles.PROJ_LOCK)
try: try:
with open(lockFile, mode="w+", encoding="utf8") as outFile: with open(lockFile, mode="w+", encoding="utf-8") as outFile:
outFile.write("%s\n" % self.mainConf.hostName) outFile.write("%s\n" % self.mainConf.hostName)
outFile.write("%s\n" % self.mainConf.osType) outFile.write("%s\n" % self.mainConf.osType)
outFile.write("%s\n" % self.mainConf.kernelVer) outFile.write("%s\n" % self.mainConf.kernelVer)
@@ -1442,7 +1443,7 @@ class NWProject():
return False return False
try: try:
with open(sessionFile, mode="a+", encoding="utf8") as outFile: with open(sessionFile, mode="a+", encoding="utf-8") as outFile:
if not isFile: if not isFile:
# It's a new file, so add a header # It's a new file, so add a header
if self.lastWCount > 0: if self.lastWCount > 0:
@@ -1481,7 +1482,6 @@ class NWProject():
logger.info("Old data folder %s found", theFolder) logger.info("Old data folder %s found", theFolder)
# Move Documents to Content # Move Documents to Content
# =========================
for dataItem in os.listdir(theData): for dataItem in os.listdir(theData):
theFile = os.path.join(theData, dataItem) theFile = os.path.join(theData, dataItem)
if not os.path.isfile(theFile): if not os.path.isfile(theFile):
@@ -1517,7 +1517,6 @@ class NWProject():
errList.append(theErr) errList.append(theErr)
# Remove Data Folder # Remove Data Folder
# ==================
try: try:
os.rmdir(theData) os.rmdir(theData)
logger.info("Deleted folder: %s", theFolder) logger.info("Deleted folder: %s", theFolder)
@@ -1563,13 +1562,13 @@ class NWProject():
os.path.join(self.projCache, "nwProject.nwx.7"), os.path.join(self.projCache, "nwProject.nwx.7"),
os.path.join(self.projCache, "nwProject.nwx.8"), os.path.join(self.projCache, "nwProject.nwx.8"),
os.path.join(self.projCache, "nwProject.nwx.9"), os.path.join(self.projCache, "nwProject.nwx.9"),
os.path.join(self.projMeta, "mainOptions.json"), os.path.join(self.projMeta, "mainOptions.json"),
os.path.join(self.projMeta, "exportOptions.json"), os.path.join(self.projMeta, "exportOptions.json"),
os.path.join(self.projMeta, "outlineOptions.json"), os.path.join(self.projMeta, "outlineOptions.json"),
os.path.join(self.projMeta, "timelineOptions.json"), os.path.join(self.projMeta, "timelineOptions.json"),
os.path.join(self.projMeta, "docMergeOptions.json"), os.path.join(self.projMeta, "docMergeOptions.json"),
os.path.join(self.projMeta, "sessionLogOptions.json"), os.path.join(self.projMeta, "sessionLogOptions.json"),
os.path.join(self.projPath, "ToC.json"), os.path.join(self.projPath, "ToC.json"),
] ]
for rmFile in rmList: for rmFile in rmList:
+1 -2
View File
@@ -235,8 +235,7 @@ class FakeEnchant:
class NWSpellSimple(NWSpellCheck): class NWSpellSimple(NWSpellCheck):
"""Internal spell check tool that uses standard Python packages with """Internal spell check tool that uses standard Python packages with
no other external dependencies. This is the fallback spell checker no other external dependencies. This is the fallback spell checker
when no other is available. This method is fairly slow compared to when no other is available. This method is slower than enchant.
other implementations.
""" """
theWords = set() theWords = set()
+12 -10
View File
@@ -35,12 +35,14 @@ logger = logging.getLogger(__name__)
class NWStatus(): class NWStatus():
def __init__(self): def __init__(self):
self._theLabels = []
self._theLabels = []
self._theColours = [] self._theColours = []
self._theCounts = [] self._theCounts = []
self._theMap = {} self._theMap = {}
self._theLength = 0 self._theLength = 0
self._theIndex = 0 self._theIndex = 0
return return
def addEntry(self, theLabel, theColours): def addEntry(self, theLabel, theColours):
@@ -87,12 +89,12 @@ class NWStatus():
replaceMap = {} replaceMap = {}
if newList is not None: if newList is not None:
self._theLabels = [] self._theLabels = []
self._theColours = [] self._theColours = []
self._theCounts = [] self._theCounts = []
self._theMap = {} self._theMap = {}
self._theLength = 0 self._theLength = 0
self._theIndex = 0 self._theIndex = 0
for nName, nR, nG, nB, oName in newList: for nName, nR, nG, nB, oName in newList:
self.addEntry(nName, (nR, nG, nB)) self.addEntry(nName, (nR, nG, nB))
+4 -4
View File
@@ -40,9 +40,9 @@ class ToHtml(Tokenizer):
def __init__(self, theProject): def __init__(self, theProject):
Tokenizer.__init__(self, theProject) Tokenizer.__init__(self, theProject)
self.genMode = self.M_EXPORT self.genMode = self.M_EXPORT
self.cssStyles = True self.cssStyles = True
self.fullHTML = [] self.fullHTML = []
# Internals # Internals
self._trMap = {} self._trMap = {}
@@ -59,7 +59,7 @@ class ToHtml(Tokenizer):
need to make a few changes to formatting, which is managed by need to make a few changes to formatting, which is managed by
these flags. these flags.
""" """
self.genMode = self.M_PREVIEW self.genMode = self.M_PREVIEW
self.doKeywords = True self.doKeywords = True
self.doComments = doComments self.doComments = doComments
self.doSynopsis = doSynopsis self.doSynopsis = doSynopsis
@@ -268,7 +268,7 @@ class ToHtml(Tokenizer):
def saveHTML5(self, savePath): def saveHTML5(self, savePath):
"""Save the data to an .html file. """Save the data to an .html file.
""" """
with open(savePath, mode="w", encoding="utf8") as outFile: with open(savePath, mode="w", encoding="utf-8") as outFile:
theStyle = self.getStyleSheet() theStyle = self.getStyleSheet()
theStyle.append("article {width: 800px; margin: 40px auto;}") theStyle.append("article {width: 800px; margin: 40px auto;}")
bodyText = "".join(self.fullHTML) bodyText = "".join(self.fullHTML)
+19 -19
View File
@@ -24,8 +24,8 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
""" """
import nw import nw
import logging
import re import re
import logging
from operator import itemgetter from operator import itemgetter
from functools import partial from functools import partial
@@ -43,12 +43,12 @@ logger = logging.getLogger(__name__)
class Tokenizer(): class Tokenizer():
# In-Text Format # In-Text Format
FMT_B_B = 1 # Begin bold FMT_B_B = 1 # Begin bold
FMT_B_E = 2 # End bold FMT_B_E = 2 # End bold
FMT_I_B = 3 # Begin italics FMT_I_B = 3 # Begin italics
FMT_I_E = 4 # End italics FMT_I_E = 4 # End italics
FMT_D_B = 5 # Begin strikeout FMT_D_B = 5 # Begin strikeout
FMT_D_E = 6 # End strikeout FMT_D_E = 6 # End strikeout
# Block Type # Block Type
T_EMPTY = 1 # Empty line (new paragraph) T_EMPTY = 1 # Empty line (new paragraph)
@@ -86,11 +86,11 @@ class Tokenizer():
self.mainConf = nw.CONFIG self.mainConf = nw.CONFIG
# Data Variables # Data Variables
self.theText = "" # The raw text to be tokenized self.theText = "" # The raw text to be tokenized
self.theHandle = None # The handle associated with the text self.theHandle = None # The handle associated with the text
self.theItem = None # The NWItem associated with the handle self.theItem = None # The NWItem associated with the handle
self.theTokens = [] # The list of the processed tokens self.theTokens = [] # The list of the processed tokens
self.theResult = "" # The result of the last document self.theResult = "" # The result of the last document
self.keepMarkdown = False # Whether to keep the markdown text self.keepMarkdown = False # Whether to keep the markdown text
self.theMarkdown = [] # The result novelWriter markdown of all documents self.theMarkdown = [] # The result novelWriter markdown of all documents
@@ -117,11 +117,11 @@ class Tokenizer():
self.marginMeta = (0.000, 0.584) self.marginMeta = (0.000, 0.584)
# Title Formats # Title Formats
self.fmtTitle = "%title%" # Formatting for titles self.fmtTitle = "%title%" # Formatting for titles
self.fmtChapter = "%title%" # Formatting for numbered chapters self.fmtChapter = "%title%" # Formatting for numbered chapters
self.fmtUnNum = "%title%" # Formatting for unnumbered chapters self.fmtUnNum = "%title%" # Formatting for unnumbered chapters
self.fmtScene = "%title%" # Formatting for scenes self.fmtScene = "%title%" # Formatting for scenes
self.fmtSection = "%title%" # Formatting for sections self.fmtSection = "%title%" # Formatting for sections
self.hideScene = False # Do not include scene headers self.hideScene = False # Do not include scene headers
self.hideSection = False # Do not include section headers self.hideSection = False # Do not include section headers
@@ -320,7 +320,7 @@ class Tokenizer():
return True return True
def doPreProcessing(self): def doPreProcessing(self):
"""Reun trough the various replace doctionaries. """Run trough the various replace doctionaries.
""" """
# Process the user's auto-replace dictionary # Process the user's auto-replace dictionary
if len(self.theProject.autoReplace) > 0: if len(self.theProject.autoReplace) > 0:
@@ -701,7 +701,7 @@ class Tokenizer():
def saveRawMarkdown(self, savePath): def saveRawMarkdown(self, savePath):
"""Save the data to a plain text file. """Save the data to a plain text file.
""" """
with open(savePath, mode="w", encoding="utf8") as outFile: with open(savePath, mode="w", encoding="utf-8") as outFile:
for nwdPage in self.theMarkdown: for nwdPage in self.theMarkdown:
outFile.write(nwdPage) outFile.write(nwdPage)
return return
+14 -8
View File
@@ -72,16 +72,22 @@ class ToMarkdown(Tokenizer):
if self.genMode == self.M_STD: if self.genMode == self.M_STD:
# Standard # Standard
mdTags = { mdTags = {
self.FMT_B_B: "**", self.FMT_B_E: "**", self.FMT_B_B: "**",
self.FMT_I_B: "_", self.FMT_I_E: "_", self.FMT_B_E: "**",
self.FMT_D_B: "", self.FMT_D_E: "", self.FMT_I_B: "_",
self.FMT_I_E: "_",
self.FMT_D_B: "",
self.FMT_D_E: "",
} }
else: else:
# GitHub # GitHub
mdTags = { mdTags = {
self.FMT_B_B: "**", self.FMT_B_E: "**", self.FMT_B_B: "**",
self.FMT_I_B: "_", self.FMT_I_E: "_", self.FMT_B_E: "**",
self.FMT_D_B: "~~", self.FMT_D_E: "~~", self.FMT_I_B: "_",
self.FMT_I_E: "_",
self.FMT_D_B: "~~",
self.FMT_D_E: "~~",
} }
self.theResult = "" self.theResult = ""
@@ -149,7 +155,7 @@ class ToMarkdown(Tokenizer):
def saveMarkdown(self, savePath): def saveMarkdown(self, savePath):
"""Save the data to a plain text file. """Save the data to a plain text file.
""" """
with open(savePath, mode="w", encoding="utf8") as outFile: with open(savePath, mode="w", encoding="utf-8") as outFile:
theText = "".join(self.fullMD) theText = "".join(self.fullMD)
outFile.write(theText) outFile.write(theText)
@@ -173,7 +179,7 @@ class ToMarkdown(Tokenizer):
def _formatKeywords(self, tText, tStyle): def _formatKeywords(self, tText, tStyle):
"""Apply Markdown formatting to keywords. """Apply Markdown formatting to keywords.
""" """
isValid, theBits, thePos = self.theParent.theIndex.scanThis("@"+tText) isValid, theBits, _ = self.theParent.theIndex.scanThis("@"+tText)
if not isValid or not theBits: if not isValid or not theBits:
return "" return ""
+3 -3
View File
@@ -24,11 +24,11 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
""" """
import nw import nw
import logging
import os import os
import logging
from lxml import etree
from time import time from time import time
from lxml import etree
from hashlib import sha256 from hashlib import sha256
from nw.enum import nwItemType, nwItemClass, nwItemLayout from nw.enum import nwItemType, nwItemClass, nwItemLayout
@@ -193,7 +193,7 @@ class NWTree():
try: try:
# Dump the text # Dump the text
tocText = os.path.join(self.theProject.projPath, nwFiles.TOC_TXT) tocText = os.path.join(self.theProject.projPath, nwFiles.TOC_TXT)
with open(tocText, mode="w", encoding="utf8") as outFile: with open(tocText, mode="w", encoding="utf-8") as outFile:
outFile.write("\n") outFile.write("\n")
outFile.write("Table of Contents\n") outFile.write("Table of Contents\n")
outFile.write("=================\n") outFile.write("=================\n")
+4 -4
View File
@@ -24,13 +24,13 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
""" """
import nw import nw
import logging
import os import os
import logging
from datetime import datetime from datetime import datetime
from PyQt5.QtCore import Qt
from PyQt5.QtGui import QCursor from PyQt5.QtGui import QCursor
from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import ( from PyQt5.QtWidgets import (
qApp, QDialog, QHBoxLayout, QVBoxLayout, QDialogButtonBox, QTabWidget, qApp, QDialog, QHBoxLayout, QVBoxLayout, QDialogButtonBox, QTabWidget,
QTextBrowser, QLabel QTextBrowser, QLabel
@@ -229,7 +229,7 @@ class GuiAbout(QDialog):
""" """
docPath = os.path.join(self.mainConf.assetPath, "text", "release_notes.htm") docPath = os.path.join(self.mainConf.assetPath, "text", "release_notes.htm")
if os.path.isfile(docPath): if os.path.isfile(docPath):
with open(docPath, mode="r", encoding="utf8") as inFile: with open(docPath, mode="r", encoding="utf-8") as inFile:
helpText = inFile.read() helpText = inFile.read()
self.pageNotes.setHtml(helpText) self.pageNotes.setHtml(helpText)
else: else:
@@ -241,7 +241,7 @@ class GuiAbout(QDialog):
""" """
docPath = os.path.join(self.mainConf.assetPath, "text", "gplv3_en.htm") docPath = os.path.join(self.mainConf.assetPath, "text", "gplv3_en.htm")
if os.path.isfile(docPath): if os.path.isfile(docPath):
with open(docPath, mode="r", encoding="utf8") as inFile: with open(docPath, mode="r", encoding="utf-8") as inFile:
helpText = inFile.read() helpText = inFile.read()
self.pageLicense.setHtml(helpText) self.pageLicense.setHtml(helpText)
else: else:
+2 -2
View File
@@ -137,7 +137,7 @@ class GuiItemEditor(QDialog):
# Assemble # Assemble
## ##
nameLabel = QLabel(self.tr("Label")) nameLabel = QLabel(self.tr("Label"))
statusLabel = QLabel(self.tr("Status")) statusLabel = QLabel(self.tr("Status"))
layoutLabel = QLabel(self.tr("Layout")) layoutLabel = QLabel(self.tr("Layout"))
@@ -179,7 +179,7 @@ class GuiItemEditor(QDialog):
""" """
logger.verbose("ItemEditor save button clicked") logger.verbose("ItemEditor save button clicked")
itemName = self.editName.text() itemName = self.editName.text()
itemStatus = self.editStatus.currentData() itemStatus = self.editStatus.currentData()
itemLayout = self.editLayout.currentData() itemLayout = self.editLayout.currentData()
isExported = self.editExport.isChecked() isExported = self.editExport.isChecked()
+9 -9
View File
@@ -24,11 +24,11 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
""" """
import nw import nw
import logging
import os import os
import logging
from PyQt5.QtCore import Qt, QLocale
from PyQt5.QtGui import QFont from PyQt5.QtGui import QFont
from PyQt5.QtCore import Qt, QLocale
from PyQt5.QtWidgets import ( from PyQt5.QtWidgets import (
QDialog, QWidget, QComboBox, QSpinBox, QPushButton, QDialogButtonBox, QDialog, QWidget, QComboBox, QSpinBox, QPushButton, QDialogButtonBox,
QLineEdit, QFileDialog, QFontDialog, QDoubleSpinBox QLineEdit, QFileDialog, QFontDialog, QDoubleSpinBox
@@ -127,7 +127,7 @@ class GuiPreferences(PagedDialog):
def _saveWindowSize(self): def _saveWindowSize(self):
"""Save the dialog window size. """Save the dialog window size.
""" """
winWidth = self.mainConf.rpxInt(self.width()) winWidth = self.mainConf.rpxInt(self.width())
winHeight = self.mainConf.rpxInt(self.height()) winHeight = self.mainConf.rpxInt(self.height())
self.mainConf.setPreferencesSize(winWidth, winHeight) self.mainConf.setPreferencesSize(winWidth, winHeight)
return return
@@ -272,12 +272,12 @@ class GuiPreferencesGeneral(QWidget):
def saveValues(self): def saveValues(self):
"""Save the values set for this tab. """Save the values set for this tab.
""" """
guiLang = self.guiLang.currentData() guiLang = self.guiLang.currentData()
guiTheme = self.guiTheme.currentData() guiTheme = self.guiTheme.currentData()
guiIcons = self.guiIcons.currentData() guiIcons = self.guiIcons.currentData()
guiDark = self.guiDark.isChecked() guiDark = self.guiDark.isChecked()
guiFont = self.guiFont.text() guiFont = self.guiFont.text()
guiFontSize = self.guiFontSize.value() guiFontSize = self.guiFontSize.value()
# Check if restart is needed # Check if restart is needed
needsRestart = False needsRestart = False
+4 -4
View File
@@ -24,13 +24,13 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
""" """
import nw import nw
import logging
import os import os
import logging
from datetime import datetime from datetime import datetime
from PyQt5.QtCore import Qt, QSize
from PyQt5.QtGui import QKeySequence from PyQt5.QtGui import QKeySequence
from PyQt5.QtCore import Qt, QSize
from PyQt5.QtWidgets import ( from PyQt5.QtWidgets import (
QDialog, QHBoxLayout, QVBoxLayout, QGridLayout, QPushButton, QTreeWidget, QDialog, QHBoxLayout, QVBoxLayout, QGridLayout, QPushButton, QTreeWidget,
QAbstractItemView, QTreeWidgetItem, QDialogButtonBox, QLabel, QShortcut, QAbstractItemView, QTreeWidgetItem, QDialogButtonBox, QLabel, QShortcut,
@@ -105,8 +105,8 @@ class GuiProjectLoad(QDialog):
treeHead.setTextAlignment(self.C_TIME, Qt.AlignRight) treeHead.setTextAlignment(self.C_TIME, Qt.AlignRight)
self.lblRecent = QLabel("<b>%s</b>" % self.tr("Recently Opened Projects")) self.lblRecent = QLabel("<b>%s</b>" % self.tr("Recently Opened Projects"))
self.lblPath = QLabel("<b>%s</b>" % self.tr("Path")) self.lblPath = QLabel("<b>%s</b>" % self.tr("Path"))
self.selPath = QLineEdit("") self.selPath = QLineEdit("")
self.selPath.setReadOnly(True) self.selPath.setReadOnly(True)
self.browseButton = QPushButton("...") self.browseButton = QPushButton("...")
+4 -4
View File
@@ -26,8 +26,8 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
import nw import nw
import logging import logging
from PyQt5.QtCore import Qt, QLocale
from PyQt5.QtGui import QIcon, QPixmap, QColor, QBrush from PyQt5.QtGui import QIcon, QPixmap, QColor, QBrush
from PyQt5.QtCore import Qt, QLocale
from PyQt5.QtWidgets import ( from PyQt5.QtWidgets import (
QHBoxLayout, QVBoxLayout, QLineEdit, QPlainTextEdit, QLabel, QWidget, QHBoxLayout, QVBoxLayout, QLineEdit, QPlainTextEdit, QLabel, QWidget,
QDialogButtonBox, QPushButton, QColorDialog, QTreeWidget, QTreeWidgetItem, QDialogButtonBox, QPushButton, QColorDialog, QTreeWidget, QTreeWidgetItem,
@@ -393,7 +393,7 @@ class GuiProjectEditStatus(QWidget):
""" """
selItem = self._getSelectedItem() selItem = self._getSelectedItem()
if selItem is not None: if selItem is not None:
iRow = self.listBox.indexOfTopLevelItem(selItem) iRow = self.listBox.indexOfTopLevelItem(selItem)
selIdx = selItem.data(self.COL_LABEL, Qt.UserRole) selIdx = selItem.data(self.COL_LABEL, Qt.UserRole)
if self.colCounts[selIdx] == 0: if self.colCounts[selIdx] == 0:
self.listBox.takeTopLevelItem(iRow) self.listBox.takeTopLevelItem(iRow)
@@ -626,8 +626,8 @@ class GuiProjectEditReplace(QWidget):
if selItem is None: if selItem is None:
return False return False
newKey = self.editKey.text() newKey = self.editKey.text()
newVal = self.editValue.text() newVal = self.editValue.text()
saveKey = self._stripNotAllowed(newKey) saveKey = self._stripNotAllowed(newKey)
if len(saveKey) > 0 and len(newVal) > 0: if len(saveKey) > 0 and len(newVal) > 0:
+3 -3
View File
@@ -24,8 +24,8 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
""" """
import nw import nw
import logging
import os import os
import logging
from PyQt5.QtCore import Qt from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import ( from PyQt5.QtWidgets import (
@@ -155,7 +155,7 @@ class GuiWordList(QDialog):
tmpFile = dctFile + "~" tmpFile = dctFile + "~"
try: try:
with open(tmpFile, mode="w", encoding="utf8") as outFile: with open(tmpFile, mode="w", encoding="utf-8") as outFile:
for i in range(self.listBox.count()): for i in range(self.listBox.count()):
outFile.write(self.listBox.item(i).text() + "\n") outFile.write(self.listBox.item(i).text() + "\n")
@@ -193,7 +193,7 @@ class GuiWordList(QDialog):
logger.debug("No project dictionary file found") logger.debug("No project dictionary file found")
return False return False
with open(wordList, mode="r", encoding="utf8") as inFile: with open(wordList, mode="r", encoding="utf-8") as inFile:
for inLine in inFile: for inLine in inFile:
theWord = inLine.strip() theWord = inLine.strip()
if len(theWord) == 0: if len(theWord) == 0:
+5 -5
View File
@@ -104,11 +104,11 @@ class nwDocAction(Enum):
class nwDocInsert(Enum): class nwDocInsert(Enum):
NO_INSERT = 0 NO_INSERT = 0
QUOTE_LS = 1 QUOTE_LS = 1
QUOTE_RS = 2 QUOTE_RS = 2
QUOTE_LD = 3 QUOTE_LD = 3
QUOTE_RD = 4 QUOTE_RD = 4
# END Enum nwDocInsert # END Enum nwDocInsert
+15 -12
View File
@@ -311,13 +311,13 @@ class QSwitch(QAbstractButton):
if self.isChecked(): if self.isChecked():
trackBrush = qPalette.highlight() trackBrush = qPalette.highlight()
thumbBrush = qPalette.highlightedText() thumbBrush = qPalette.highlightedText()
textColor = qPalette.highlight().color() textColor = qPalette.highlight().color()
thumbText = nwUnicode.U_CHECK thumbText = nwUnicode.U_CHECK
else: else:
trackBrush = qPalette.dark() trackBrush = qPalette.dark()
thumbBrush = qPalette.light() thumbBrush = qPalette.light()
textColor = qPalette.dark().color() textColor = qPalette.dark().color()
thumbText = nwUnicode.U_CROSS thumbText = nwUnicode.U_CROSS
if self.isEnabled(): if self.isEnabled():
trackOpacity = 1.0 trackOpacity = 1.0
@@ -325,7 +325,7 @@ class QSwitch(QAbstractButton):
trackOpacity = 0.6 trackOpacity = 0.6
trackBrush = qPalette.shadow() trackBrush = qPalette.shadow()
thumbBrush = qPalette.mid() thumbBrush = qPalette.mid()
textColor = qPalette.shadow().color() textColor = qPalette.shadow().color()
qPaint.setBrush(trackBrush) qPaint.setBrush(trackBrush)
qPaint.setOpacity(trackOpacity) qPaint.setOpacity(trackOpacity)
@@ -379,18 +379,18 @@ class PagedDialog(QDialog):
def __init__(self, theParent=None): def __init__(self, theParent=None):
QDialog.__init__(self, parent=theParent) QDialog.__init__(self, parent=theParent)
self._outerBox = QVBoxLayout()
self._buttonBox = QHBoxLayout()
self._tabBox = QTabWidget()
self._tabBar = VerticalTabBar(self) self._tabBar = VerticalTabBar(self)
self._tabBox.setTabBar(self._tabBar)
self._tabBox.setTabPosition(QTabWidget.West)
self._tabBar.setExpanding(False) self._tabBar.setExpanding(False)
self._tabBox = QTabWidget()
self._tabBox.setTabBar(self._tabBar)
self._tabBox.setTabPosition(QTabWidget.West)
self._buttonBox = QHBoxLayout()
self._outerBox = QVBoxLayout()
self._outerBox.addWidget(self._tabBox) self._outerBox.addWidget(self._tabBox)
self._outerBox.addLayout(self._buttonBox) self._outerBox.addLayout(self._buttonBox)
self.setLayout(self._outerBox)
# Default Margins # Default Margins
qM = self._outerBox.contentsMargins() qM = self._outerBox.contentsMargins()
@@ -399,11 +399,14 @@ class PagedDialog(QDialog):
mT = qM.top() mT = qM.top()
mB = qM.bottom() mB = qM.bottom()
# Set Margins
self.setContentsMargins(0, 0, 0, 0) self.setContentsMargins(0, 0, 0, 0)
self._outerBox.setContentsMargins(0, 0, 0, mB) self._outerBox.setContentsMargins(0, 0, 0, mB)
self._buttonBox.setContentsMargins(mL, 0, mR, 0) self._buttonBox.setContentsMargins(mL, 0, mR, 0)
self._outerBox.setSpacing(mT) self._outerBox.setSpacing(mT)
self.setLayout(self._outerBox)
return return
def addTab(self, tabWidget, tabLabel): def addTab(self, tabWidget, tabLabel):
+35 -33
View File
@@ -1090,7 +1090,7 @@ class GuiDocEditor(QTextEdit):
# Spell Checking # Spell Checking
# ============== # ==============
posCursor = self.cursorForPosition(thePos) posCursor = self.cursorForPosition(thePos)
spellCheck = self._spellCheck spellCheck = self._spellCheck
if posCursor.block().text().startswith("@"): if posCursor.block().text().startswith("@"):
@@ -1272,7 +1272,7 @@ class GuiDocEditor(QTextEdit):
findOpt |= QTextDocument.FindWholeWords findOpt |= QTextDocument.FindWholeWords
searchFor = self.docSearch.getSearchObject() searchFor = self.docSearch.getSearchObject()
wasFound = self.find(searchFor, findOpt) wasFound = self.find(searchFor, findOpt)
if not wasFound: if not wasFound:
if self.docSearch.doNextFile and not goBack: if self.docSearch.doNextFile and not goBack:
self.theParent.openNextDocument( self.theParent.openNextDocument(
@@ -1330,7 +1330,7 @@ class GuiDocEditor(QTextEdit):
return return
searchFor = self.docSearch.getSearchText() searchFor = self.docSearch.getSearchText()
replWith = self.docSearch.getReplaceText() replWith = self.docSearch.getReplaceText()
if self.docSearch.doMatchCap: if self.docSearch.doMatchCap:
replWith = transferCase(theCursor.selectedText(), replWith) replWith = transferCase(theCursor.selectedText(), replWith)
@@ -1376,7 +1376,7 @@ class GuiDocEditor(QTextEdit):
theCursor = self.textCursor() theCursor = self.textCursor()
theBlock = theCursor.block() theBlock = theCursor.block()
theText = theBlock.text() theText = theBlock.text()
if len(theText) == 0: if len(theText) == 0:
return False return False
@@ -1413,16 +1413,16 @@ class GuiDocEditor(QTextEdit):
if not theBlock.isValid(): if not theBlock.isValid():
return return
theText = theBlock.text() theText = theBlock.text()
theCursor = self.textCursor() theCursor = self.textCursor()
thePos = theCursor.positionInBlock() thePos = theCursor.positionInBlock()
theLen = len(theText) theLen = len(theText)
if theLen < 1 or thePos-1 > theLen: if theLen < 1 or thePos-1 > theLen:
return return
theOne = theText[thePos-1:thePos] theOne = theText[thePos-1:thePos]
theTwo = theText[thePos-2:thePos] theTwo = theText[thePos-2:thePos]
theThree = theText[thePos-3:thePos] theThree = theText[thePos-3:thePos]
if not theOne: # Makes Neo sad if not theOne: # Makes Neo sad
@@ -2028,7 +2028,7 @@ class GuiDocEditSearch(QFrame):
self.searchOpt.setToolButtonStyle(Qt.ToolButtonIconOnly) self.searchOpt.setToolButtonStyle(Qt.ToolButtonIconOnly)
self.searchOpt.setIconSize(QSize(tPx, tPx)) self.searchOpt.setIconSize(QSize(tPx, tPx))
self.searchOpt.setContentsMargins(0, 0, 0, 0) self.searchOpt.setContentsMargins(0, 0, 0, 0)
self.searchOpt.setStyleSheet(r"QToolBar {padding: 0;}") self.searchOpt.setStyleSheet("QToolBar {padding: 0;}")
self.searchLabel = QLabel(self.tr("Search")) self.searchLabel = QLabel(self.tr("Search"))
self.searchLabel.setFont(boxFont) self.searchLabel.setFont(boxFont)
@@ -2100,7 +2100,7 @@ class GuiDocEditSearch(QFrame):
self.showReplace.setArrowType(Qt.RightArrow) self.showReplace.setArrowType(Qt.RightArrow)
self.showReplace.setCheckable(True) self.showReplace.setCheckable(True)
self.showReplace.setToolTip(self.tr("Show/hide the replace text box")) self.showReplace.setToolTip(self.tr("Show/hide the replace text box"))
self.showReplace.setStyleSheet(r"QToolButton {border: none; background: transparent;}") self.showReplace.setStyleSheet("QToolButton {border: none; background: transparent;}")
self.showReplace.toggled.connect(self._doToggleReplace) self.showReplace.toggled.connect(self._doToggleReplace)
self.searchButton = QPushButton(self.theTheme.getIcon("search"), "") self.searchButton = QPushButton(self.theTheme.getIcon("search"), "")
@@ -2139,9 +2139,9 @@ class GuiDocEditSearch(QFrame):
# Construct Box Colours # Construct Box Colours
qPalette = self.searchBox.palette() qPalette = self.searchBox.palette()
baseCol = qPalette.base().color() baseCol = qPalette.base().color()
rCol = baseCol.redF() + 0.1 rCol = baseCol.redF() + 0.1
gCol = baseCol.greenF() - 0.1 gCol = baseCol.greenF() - 0.1
bCol = baseCol.blueF() - 0.1 bCol = baseCol.blueF() - 0.1
mCol = max(rCol, gCol, bCol, 1.0) mCol = max(rCol, gCol, bCol, 1.0)
errCol = QColor() errCol = QColor()
@@ -2161,10 +2161,10 @@ class GuiDocEditSearch(QFrame):
def closeSearch(self): def closeSearch(self):
"""Close the search box. """Close the search box.
""" """
self.mainConf.searchCase = self.isCaseSense self.mainConf.searchCase = self.isCaseSense
self.mainConf.searchWord = self.isWholeWord self.mainConf.searchWord = self.isWholeWord
self.mainConf.searchRegEx = self.isRegEx self.mainConf.searchRegEx = self.isRegEx
self.mainConf.searchLoop = self.doLoop self.mainConf.searchLoop = self.doLoop
self.mainConf.searchNextFile = self.doNextFile self.mainConf.searchNextFile = self.doNextFile
self.mainConf.searchMatchCap = self.doMatchCap self.mainConf.searchMatchCap = self.doMatchCap
@@ -2367,7 +2367,8 @@ class GuiDocEditHeader(QWidget):
self.theParent = docEditor.theParent self.theParent = docEditor.theParent
self.theProject = docEditor.theProject self.theProject = docEditor.theProject
self.theTheme = docEditor.theTheme self.theTheme = docEditor.theTheme
self._docHandle = None
self._docHandle = None
fPx = int(0.9*self.theTheme.fontPixelSize) fPx = int(0.9*self.theTheme.fontPixelSize)
hSp = self.mainConf.pxInt(6) hSp = self.mainConf.pxInt(6)
@@ -2591,8 +2592,9 @@ class GuiDocEditFooter(QWidget):
self.theProject = docEditor.theProject self.theProject = docEditor.theProject
self.theTheme = docEditor.theTheme self.theTheme = docEditor.theTheme
self.optState = docEditor.theProject.optState self.optState = docEditor.theProject.optState
self._docHandle = None
self.theItem = None self._theItem = None
self._docHandle = None
self.sPx = int(round(0.9*self.theTheme.baseIconSize)) self.sPx = int(round(0.9*self.theTheme.baseIconSize))
fPx = int(0.9*self.theTheme.fontPixelSize) fPx = int(0.9*self.theTheme.fontPixelSize)
@@ -2708,9 +2710,9 @@ class GuiDocEditFooter(QWidget):
self._docHandle = tHandle self._docHandle = tHandle
if self._docHandle is None: if self._docHandle is None:
logger.verbose("No handle set, so clearing the editor footer") logger.verbose("No handle set, so clearing the editor footer")
self.theItem = None self._theItem = None
else: else:
self.theItem = self.theProject.projTree[self._docHandle] self._theItem = self.theProject.projTree[self._docHandle]
self.updateInfo() self.updateInfo()
self.updateCounts() self.updateCounts()
@@ -2720,12 +2722,12 @@ class GuiDocEditFooter(QWidget):
def updateInfo(self): def updateInfo(self):
"""Update the content of text labels. """Update the content of text labels.
""" """
if self.theItem is None: if self._theItem is None:
sIcon = QPixmap() sIcon = QPixmap()
sText = "" sText = ""
else: else:
iStatus = self.theItem.itemStatus iStatus = self._theItem.itemStatus
if self.theItem.itemClass == nwItemClass.NOVEL: if self._theItem.itemClass == nwItemClass.NOVEL:
iStatus = self.theProject.statusItems.checkEntry(iStatus) iStatus = self.theProject.statusItems.checkEntry(iStatus)
theIcon = self.theParent.statusIcons[iStatus] theIcon = self.theParent.statusIcons[iStatus]
else: else:
@@ -2733,9 +2735,9 @@ class GuiDocEditFooter(QWidget):
theIcon = self.theParent.importIcons[iStatus] theIcon = self.theParent.importIcons[iStatus]
sIcon = theIcon.pixmap(self.sPx, self.sPx) sIcon = theIcon.pixmap(self.sPx, self.sPx)
sClass = trConst(nwLabels.CLASS_NAME[self.theItem.itemClass]) sClass = trConst(nwLabels.CLASS_NAME[self._theItem.itemClass])
sLayout = trConst(nwLabels.LAYOUT_NAME[self.theItem.itemLayout]) sLayout = trConst(nwLabels.LAYOUT_NAME[self._theItem.itemLayout])
sText = f"{self.theItem.itemStatus} / {sClass} / {sLayout}" sText = f"{self._theItem.itemStatus} / {sClass} / {sLayout}"
self.statusIcon.setPixmap(sIcon) self.statusIcon.setPixmap(sIcon)
self.statusText.setText(sText) self.statusText.setText(sText)
@@ -2745,7 +2747,7 @@ class GuiDocEditFooter(QWidget):
def updateLineCount(self): def updateLineCount(self):
"""Update the word count. """Update the word count.
""" """
if self.theItem is None: if self._theItem is None:
iLine = 0 iLine = 0
iDist = 0 iDist = 0
else: else:
@@ -2762,12 +2764,12 @@ class GuiDocEditFooter(QWidget):
def updateCounts(self): def updateCounts(self):
"""Update the word count. """Update the word count.
""" """
if self.theItem is None: if self._theItem is None:
wCount = 0 wCount = 0
wDiff = 0 wDiff = 0
else: else:
wCount = self.theItem.wordCount wCount = self._theItem.wordCount
wDiff = wCount - self.theItem.initCount wDiff = wCount - self._theItem.initCount
self.wordsText.setText( self.wordsText.setText(
self.tr("Words: {0} ({1})").format(f"{wCount:n}", f"{wDiff:+n}") self.tr("Words: {0} ({1})").format(f"{wCount:n}", f"{wDiff:+n}")
+2 -6
View File
@@ -60,7 +60,7 @@ class GuiDocViewer(QTextBrowser):
self.theProject = theParent.theProject self.theProject = theParent.theProject
# Internal Variables # Internal Variables
self._docHandle = None self._docHandle = None
self._qDocument = self.document() self._qDocument = self.document()
# Settings # Settings
@@ -312,9 +312,7 @@ class GuiDocViewer(QTextBrowser):
return return
def updateDocMargins(self): def updateDocMargins(self):
"""Automatically adjust the margins so the text is centred if """Automatically adjust the margins so the text is centred.
Config.textFixedW is enabled or we're in Focus Mode. Otherwise,
just ensure the margins are set correctly.
""" """
vBar = self.verticalScrollBar() vBar = self.verticalScrollBar()
sW = vBar.width() if vBar.isVisible() else 0 sW = vBar.width() if vBar.isVisible() else 0
@@ -1152,7 +1150,6 @@ class GuiDocViewDetails(QScrollArea):
self.theParent = theParent self.theParent = theParent
self.theProject = theParent.theProject self.theProject = theParent.theProject
self.theTheme = theParent.theTheme self.theTheme = theParent.theTheme
self.currHandle = None
self.refList = QLabel("") self.refList = QLabel("")
self.refList.setWordWrap(True) self.refList.setWordWrap(True)
@@ -1185,7 +1182,6 @@ class GuiDocViewDetails(QScrollArea):
"""Update the current list of document references from the """Update the current list of document references from the
project index. project index.
""" """
self.currHandle = tHandle
if self.theParent.docViewer.stickyRef: if self.theParent.docViewer.stickyRef:
return return
+16 -18
View File
@@ -47,7 +47,7 @@ class GuiMainMenu(QMenuBar):
self.theProject = theParent.theProject self.theProject = theParent.theProject
# Internals # Internals
self.assistProc = None self._assistProc = None
# Build Menu # Build Menu
self._buildProjectMenu() self._buildProjectMenu()
@@ -92,19 +92,19 @@ class GuiMainMenu(QMenuBar):
def closeHelp(self): def closeHelp(self):
"""Close the process used for the Qt Assistant, if it is open. """Close the process used for the Qt Assistant, if it is open.
""" """
if self.assistProc is None: if self._assistProc is None:
return return
if self.assistProc.state() == QProcess.Starting: if self._assistProc.state() == QProcess.Starting:
if self.assistProc.waitForStarted(10000): if self._assistProc.waitForStarted(10000):
self.assistProc.terminate() self._assistProc.terminate()
else: else:
self.assistProc.kill() self._assistProc.kill()
elif self.assistProc.state() == QProcess.Running: elif self._assistProc.state() == QProcess.Running:
self.assistProc.terminate() self._assistProc.terminate()
if not self.assistProc.waitForFinished(10000): if not self._assistProc.waitForFinished(10000):
self.assistProc.kill() self._assistProc.kill()
return return
@@ -134,7 +134,7 @@ class GuiMainMenu(QMenuBar):
# Slots # Slots
## ##
def _toggleSpellCheck(self, isChecked=False): def _toggleSpellCheck(self):
"""Toggle spell checking. The active status of the spell check """Toggle spell checking. The active status of the spell check
flag is handled by the document editor class, so we make no flag is handled by the document editor class, so we make no
decision, just pass a None to the function and let it decide. decision, just pass a None to the function and let it decide.
@@ -155,10 +155,10 @@ class GuiMainMenu(QMenuBar):
self._openWebsite(nw.__docurl__) self._openWebsite(nw.__docurl__)
return False return False
self.assistProc = QProcess(self) self._assistProc = QProcess(self)
self.assistProc.start("assistant", ["-collectionFile", self.mainConf.helpPath]) self._assistProc.start("assistant", ["-collectionFile", self.mainConf.helpPath])
if not self.assistProc.waitForStarted(10000): if not self._assistProc.waitForStarted(10000):
self._openWebsite(nw.__docurl__) self._openWebsite(nw.__docurl__)
return False return False
@@ -239,11 +239,9 @@ class GuiMainMenu(QMenuBar):
self.rootItems[nwItemClass.ENTITY] = QAction(self.tr("Entity Root"), self.rootMenu) self.rootItems[nwItemClass.ENTITY] = QAction(self.tr("Entity Root"), self.rootMenu)
self.rootItems[nwItemClass.CUSTOM] = QAction(self.tr("Custom Root"), self.rootMenu) self.rootItems[nwItemClass.CUSTOM] = QAction(self.tr("Custom Root"), self.rootMenu)
self.rootItems[nwItemClass.ARCHIVE] = QAction(self.tr("Outtakes Root"), self.rootMenu) self.rootItems[nwItemClass.ARCHIVE] = QAction(self.tr("Outtakes Root"), self.rootMenu)
nCount = 0 for n, itemClass in enumerate(self.rootItems.keys()):
for itemClass in self.rootItems.keys():
nCount += 1 # This forces the lambdas to be unique
self.rootItems[itemClass].triggered.connect( self.rootItems[itemClass].triggered.connect(
lambda nCount, itemClass=itemClass: self._newTreeItem(nwItemType.ROOT, itemClass) lambda n, itemClass=itemClass: self._newTreeItem(nwItemType.ROOT, itemClass)
) )
self.rootMenu.addAction(self.rootItems[itemClass]) self.rootMenu.addAction(self.rootItems[itemClass])
+5 -5
View File
@@ -247,9 +247,9 @@ class GuiNovelTree(QTreeWidget):
""" """
self.clearTree() self.clearTree()
currTitle = None currTitle = None
currChapter = None currChapter = None
currScene = None currScene = None
for tKey, tHandle, sTitle, novIdx in self.theIndex.novelStructure(skipExcluded=True): for tKey, tHandle, sTitle, novIdx in self.theIndex.novelStructure(skipExcluded=True):
@@ -259,9 +259,9 @@ class GuiNovelTree(QTreeWidget):
tLevel = novIdx["level"] tLevel = novIdx["level"]
if tLevel == "H1": if tLevel == "H1":
self.addTopLevelItem(tItem) self.addTopLevelItem(tItem)
currTitle = tItem currTitle = tItem
currChapter = None currChapter = None
currScene = None currScene = None
elif tLevel == "H2": elif tLevel == "H2":
if currTitle is None: if currTitle is None:
@@ -269,7 +269,7 @@ class GuiNovelTree(QTreeWidget):
else: else:
currTitle.addChild(tItem) currTitle.addChild(tItem)
currChapter = tItem currChapter = tItem
currScene = None currScene = None
elif tLevel == "H3": elif tLevel == "H3":
if currChapter is None: if currChapter is None:
+88 -86
View File
@@ -28,7 +28,7 @@ import logging
from time import time from time import time
from PyQt5.QtCore import Qt, QSize from PyQt5.QtCore import Qt, QSize, pyqtSlot
from PyQt5.QtWidgets import ( from PyQt5.QtWidgets import (
QTreeWidget, QTreeWidgetItem, QMenu, QAction, QAbstractItemView QTreeWidget, QTreeWidgetItem, QMenu, QAction, QAbstractItemView
) )
@@ -94,9 +94,6 @@ class GuiOutline(QTreeWidget):
self.optState = theParent.theProject.optState self.optState = theParent.theProject.optState
self.headerMenu = GuiOutlineHeaderMenu(self) self.headerMenu = GuiOutlineHeaderMenu(self)
self.firstView = True
self.lastBuild = 0
self.setSelectionBehavior(QAbstractItemView.SelectRows) self.setSelectionBehavior(QAbstractItemView.SelectRows)
self.setSelectionMode(QAbstractItemView.SingleSelection) self.setSelectionMode(QAbstractItemView.SingleSelection)
self.setExpandsOnDoubleClick(False) self.setExpandsOnDoubleClick(False)
@@ -113,16 +110,18 @@ class GuiOutline(QTreeWidget):
self.treeHead.customContextMenuRequested.connect(self._headerRightClick) self.treeHead.customContextMenuRequested.connect(self._headerRightClick)
self.treeHead.sectionMoved.connect(self._columnMoved) self.treeHead.sectionMoved.connect(self._columnMoved)
self.treeMap = {} # Internals
self.treeOrder = [] self._treeOrder = []
self.colWidth = {} self._colWidth = {}
self.colHidden = {} self._colHidden = {}
self.colIndex = {} self._colIdx = {}
self.treeNCols = 0 self._treeNCols = 0
self._firstView = True
self._lastBuild = 0
self.initOutline() self.initOutline()
self.clearOutline() self.clearOutline()
self.headerMenu.setHiddenState(self.colHidden) self.headerMenu.setHiddenState(self._colHidden)
logger.debug("GuiOutline initialisation complete") logger.debug("GuiOutline initialisation complete")
@@ -152,18 +151,18 @@ class GuiOutline(QTreeWidget):
self.setColumnCount(1) self.setColumnCount(1)
self.setHeaderLabel(trConst(nwLabels.OUTLINE_COLS[nwOutline.TITLE])) self.setHeaderLabel(trConst(nwLabels.OUTLINE_COLS[nwOutline.TITLE]))
self.treeOrder = [] self._treeOrder = []
self.colWidth = {} self._colWidth = {}
self.colHidden = {} self._colHidden = {}
self.colIndex = {} self._colIdx = {}
self.treeNCols = 0 self._treeNCols = 0
for i, hItem in enumerate(nwOutline): for hItem in nwOutline:
self.treeOrder.append(hItem) self._treeOrder.append(hItem)
self.colWidth[hItem] = self.DEF_WIDTH[hItem] self._colWidth[hItem] = self.DEF_WIDTH[hItem]
self.colHidden[hItem] = self.DEF_HIDDEN[hItem] self._colHidden[hItem] = self.DEF_HIDDEN[hItem]
self.treeNCols = len(self.treeOrder) self._treeNCols = len(self._treeOrder)
return return
@@ -173,15 +172,15 @@ class GuiOutline(QTreeWidget):
tree. tree.
""" """
# If it's the first time, we always build # If it's the first time, we always build
if self.firstView or self.firstView and overRide: if self._firstView or self._firstView and overRide:
self._loadHeaderState() self._loadHeaderState()
self._populateTree() self._populateTree()
self.firstView = False self._firstView = False
return return
# If the novel index or novel tree has changed since the tree # If the novel index or novel tree has changed since the tree
# was last built, we rebuild the tree from the updated index. # was last built, we rebuild the tree from the updated index.
indexChanged = self.theIndex.novelChangedSince(self.lastBuild) indexChanged = self.theIndex.novelChangedSince(self._lastBuild)
doBuild = (novelChanged or indexChanged) and self.theProject.autoOutline doBuild = (novelChanged or indexChanged) and self.theProject.autoOutline
if doBuild or overRide: if doBuild or overRide:
logger.debug("Rebuilding Project Outline") logger.debug("Rebuilding Project Outline")
@@ -194,21 +193,22 @@ class GuiOutline(QTreeWidget):
""" """
self._saveHeaderState() self._saveHeaderState()
self.clearOutline() self.clearOutline()
self.firstView = True self._firstView = True
return return
## ##
# Slots # Slots
## ##
@pyqtSlot("QTreeWidgetItem*", int)
def _treeDoubleClick(self, tItem, tCol): def _treeDoubleClick(self, tItem, tCol):
"""Extract the handle and line number of the title double- """Extract the handle and line number of the title double-
clicked, and send it to the main gui class for opening in the clicked, and send it to the main gui class for opening in the
document editor. document editor.
""" """
tHandle = tItem.data(self.colIndex[nwOutline.TITLE], Qt.UserRole) tHandle = tItem.data(self._colIdx[nwOutline.TITLE], Qt.UserRole)
try: try:
tLine = int(tItem.text(self.colIndex[nwOutline.LINE])) tLine = int(tItem.text(self._colIdx[nwOutline.LINE]))
except Exception: except Exception:
tLine = 1 tLine = 1
@@ -217,30 +217,33 @@ class GuiOutline(QTreeWidget):
return return
@pyqtSlot()
def _itemSelected(self): def _itemSelected(self):
"""Extract the handle and line number of the currently selected """Extract the handle and line number of the currently selected
title, and send it to the details panel. title, and send it to the details panel.
""" """
selItems = self.selectedItems() selItems = self.selectedItems()
if selItems: if selItems:
tHandle = selItems[0].data(self.colIndex[nwOutline.TITLE], Qt.UserRole) tHandle = selItems[0].data(self._colIdx[nwOutline.TITLE], Qt.UserRole)
sTitle = selItems[0].data(self.colIndex[nwOutline.LINE], Qt.UserRole) sTitle = selItems[0].data(self._colIdx[nwOutline.LINE], Qt.UserRole)
self.theParent.projMeta.showItem(tHandle, sTitle) self.theParent.projMeta.showItem(tHandle, sTitle)
self.theParent.treeView.setSelectedHandle(tHandle) self.theParent.treeView.setSelectedHandle(tHandle)
return return
@pyqtSlot("QPoint")
def _headerRightClick(self, clickPos): def _headerRightClick(self, clickPos):
"""Show the header column menu. """Show the header column menu.
""" """
self.headerMenu.exec_(self.mapToGlobal(clickPos)) self.headerMenu.exec_(self.mapToGlobal(clickPos))
return return
@pyqtSlot(int, int, int)
def _columnMoved(self, logIdx, oldVisualIdx, newVisualIdx): def _columnMoved(self, logIdx, oldVisualIdx, newVisualIdx):
"""Make sure the order array is up to date with the actual order """Make sure the order array is up to date with the actual order
of the columns. of the columns.
""" """
self.treeOrder.insert(newVisualIdx, self.treeOrder.pop(oldVisualIdx)) self._treeOrder.insert(newVisualIdx, self._treeOrder.pop(oldVisualIdx))
self._saveHeaderState() self._saveHeaderState()
return return
@@ -249,8 +252,8 @@ class GuiOutline(QTreeWidget):
header context menu. header context menu.
""" """
logger.verbose("User toggled Outline column '%s'", theItem.name) logger.verbose("User toggled Outline column '%s'", theItem.name)
if theItem in self.colIndex: if theItem in self._colIdx:
self.setColumnHidden(self.colIndex[theItem], not isChecked) self.setColumnHidden(self._colIdx[theItem], not isChecked)
self._saveHeaderState() self._saveHeaderState()
return return
@@ -281,29 +284,29 @@ class GuiOutline(QTreeWidget):
# Check that we now have a complete list, and only if so, save # Check that we now have a complete list, and only if so, save
# the order loaded from file. Otherwise, we keep the default. # the order loaded from file. Otherwise, we keep the default.
if len(treeOrder) == self.treeNCols: if len(treeOrder) == self._treeNCols:
self.treeOrder = treeOrder self._treeOrder = treeOrder
else: else:
logger.error("Failed to extract outline column order from previous session") logger.error("Failed to extract outline column order from previous session")
logger.error("Column count doesn't match %d != %d", len(treeOrder), self.treeNCols) logger.error("Column count doesn't match %d != %d", len(treeOrder), self._treeNCols)
# We load whatever column widths and hidden states we find in # We load whatever column widths and hidden states we find in
# the file, and leave the rest in their default state. # the file, and leave the rest in their default state.
tmpWidth = self.optState.getValue("GuiOutline", "columnWidth", {}) tmpWidth = self.optState.getValue("GuiOutline", "columnWidth", {})
for hName in tmpWidth: for hName in tmpWidth:
try: try:
self.colWidth[nwOutline[hName]] = self.mainConf.pxInt(tmpWidth[hName]) self._colWidth[nwOutline[hName]] = self.mainConf.pxInt(tmpWidth[hName])
except Exception: except Exception:
logger.warning("Ignored unknown outline column '%s'", str(hName)) logger.warning("Ignored unknown outline column '%s'", str(hName))
tmpHidden = self.optState.getValue("GuiOutline", "columnHidden", {}) tmpHidden = self.optState.getValue("GuiOutline", "columnHidden", {})
for hName in tmpHidden: for hName in tmpHidden:
try: try:
self.colHidden[nwOutline[hName]] = tmpHidden[hName] self._colHidden[nwOutline[hName]] = tmpHidden[hName]
except Exception: except Exception:
logger.warning("Ignored unknown outline column '%s'", str(hName)) logger.warning("Ignored unknown outline column '%s'", str(hName))
self.headerMenu.setHiddenState(self.colHidden) self.headerMenu.setHiddenState(self._colHidden)
return return
@@ -314,19 +317,19 @@ class GuiOutline(QTreeWidget):
the last known width in case they're unhidden again. the last known width in case they're unhidden again.
""" """
# If we haven't built the tree, there is nothing to save. # If we haven't built the tree, there is nothing to save.
if self.lastBuild == 0: if self._lastBuild == 0:
return return
treeOrder = [] treeOrder = []
colWidth = {} colWidth = {}
colHidden = {} colHidden = {}
for hItem in nwOutline: for hItem in nwOutline:
colWidth[hItem.name] = self.mainConf.rpxInt(self.colWidth[hItem]) colWidth[hItem.name] = self.mainConf.rpxInt(self._colWidth[hItem])
colHidden[hItem.name] = self.colHidden[hItem] colHidden[hItem.name] = self._colHidden[hItem]
for iCol in range(self.columnCount()): for iCol in range(self.columnCount()):
hName = self.treeOrder[iCol].name hName = self._treeOrder[iCol].name
treeOrder.append(hName) treeOrder.append(hName)
iLog = self.treeHead.logicalIndex(iCol) iLog = self.treeHead.logicalIndex(iCol)
@@ -353,41 +356,40 @@ class GuiOutline(QTreeWidget):
""" """
self.clear() self.clear()
if self.firstView: if self._firstView:
theLabels = [] theLabels = []
for i, hItem in enumerate(self.treeOrder): for i, hItem in enumerate(self._treeOrder):
theLabels.append(trConst(nwLabels.OUTLINE_COLS[hItem])) theLabels.append(trConst(nwLabels.OUTLINE_COLS[hItem]))
self.colIndex[hItem] = i self._colIdx[hItem] = i
self.setHeaderLabels(theLabels) self.setHeaderLabels(theLabels)
for hItem in self.treeOrder: for hItem in self._treeOrder:
self.setColumnWidth(self.colIndex[hItem], self.colWidth[hItem]) self.setColumnWidth(self._colIdx[hItem], self._colWidth[hItem])
self.setColumnHidden(self.colIndex[hItem], self.colHidden[hItem]) self.setColumnHidden(self._colIdx[hItem], self._colHidden[hItem])
# Make sure title column is always visible, # Make sure title column is always visible,
# and handle column always hidden # and handle column always hidden
self.setColumnHidden(self.colIndex[nwOutline.TITLE], False) self.setColumnHidden(self._colIdx[nwOutline.TITLE], False)
headItem = self.headerItem() headItem = self.headerItem()
headItem.setTextAlignment(self.colIndex[nwOutline.CCOUNT], Qt.AlignRight) headItem.setTextAlignment(self._colIdx[nwOutline.CCOUNT], Qt.AlignRight)
headItem.setTextAlignment(self.colIndex[nwOutline.WCOUNT], Qt.AlignRight) headItem.setTextAlignment(self._colIdx[nwOutline.WCOUNT], Qt.AlignRight)
headItem.setTextAlignment(self.colIndex[nwOutline.PCOUNT], Qt.AlignRight) headItem.setTextAlignment(self._colIdx[nwOutline.PCOUNT], Qt.AlignRight)
currTitle = None currTitle = None
currChapter = None currChapter = None
currScene = None currScene = None
for tKey, tHandle, sTitle, novIdx in self.theIndex.novelStructure(skipExcluded=True): for tKey, tHandle, sTitle, novIdx in self.theIndex.novelStructure(skipExcluded=True):
tItem = self._createTreeItem(tHandle, sTitle, novIdx) tItem = self._createTreeItem(tHandle, sTitle, novIdx)
self.treeMap[tKey] = tItem
tLevel = novIdx["level"] tLevel = novIdx["level"]
if tLevel == "H1": if tLevel == "H1":
self.addTopLevelItem(tItem) self.addTopLevelItem(tItem)
currTitle = tItem currTitle = tItem
currChapter = None currChapter = None
currScene = None currScene = None
elif tLevel == "H2": elif tLevel == "H2":
if currTitle is None: if currTitle is None:
@@ -395,7 +397,7 @@ class GuiOutline(QTreeWidget):
else: else:
currTitle.addChild(tItem) currTitle.addChild(tItem)
currChapter = tItem currChapter = tItem
currScene = None currScene = None
elif tLevel == "H3": elif tLevel == "H3":
if currChapter is None: if currChapter is None:
@@ -421,47 +423,47 @@ class GuiOutline(QTreeWidget):
tItem.setExpanded(True) tItem.setExpanded(True)
self.lastBuild = time() self._lastBuild = time()
return return
def _createTreeItem(self, tHandle, sTitle, novIdx): def _createTreeItem(self, tHandle, sTitle, novIdx):
"""Populate a tree item with all the column values. """Populate a tree item with all the column values.
""" """
nwItem = self.theProject.projTree[tHandle] nwItem = self.theProject.projTree[tHandle]
newItem = QTreeWidgetItem() newItem = QTreeWidgetItem()
hIcon = "doc_%s" % novIdx["level"].lower() hIcon = "doc_%s" % novIdx["level"].lower()
cC = int(novIdx["cCount"]) cC = int(novIdx["cCount"])
wC = int(novIdx["wCount"]) wC = int(novIdx["wCount"])
pC = int(novIdx["pCount"]) pC = int(novIdx["pCount"])
newItem.setText(self.colIndex[nwOutline.TITLE], novIdx["title"]) newItem.setText(self._colIdx[nwOutline.TITLE], novIdx["title"])
newItem.setData(self.colIndex[nwOutline.TITLE], Qt.UserRole, tHandle) newItem.setData(self._colIdx[nwOutline.TITLE], Qt.UserRole, tHandle)
newItem.setIcon(self.colIndex[nwOutline.TITLE], self.theTheme.getIcon(hIcon)) newItem.setIcon(self._colIdx[nwOutline.TITLE], self.theTheme.getIcon(hIcon))
newItem.setText(self.colIndex[nwOutline.LEVEL], novIdx["level"]) newItem.setText(self._colIdx[nwOutline.LEVEL], novIdx["level"])
newItem.setText(self.colIndex[nwOutline.LABEL], nwItem.itemName) newItem.setText(self._colIdx[nwOutline.LABEL], nwItem.itemName)
newItem.setIcon(self.colIndex[nwOutline.LABEL], self.theTheme.getIcon("proj_document")) newItem.setIcon(self._colIdx[nwOutline.LABEL], self.theTheme.getIcon("proj_document"))
newItem.setText(self.colIndex[nwOutline.LINE], sTitle[1:].lstrip("0")) newItem.setText(self._colIdx[nwOutline.LINE], sTitle[1:].lstrip("0"))
newItem.setData(self.colIndex[nwOutline.LINE], Qt.UserRole, sTitle) newItem.setData(self._colIdx[nwOutline.LINE], Qt.UserRole, sTitle)
newItem.setText(self.colIndex[nwOutline.SYNOP], novIdx["synopsis"]) newItem.setText(self._colIdx[nwOutline.SYNOP], novIdx["synopsis"])
newItem.setText(self.colIndex[nwOutline.CCOUNT], f"{cC:n}") newItem.setText(self._colIdx[nwOutline.CCOUNT], f"{cC:n}")
newItem.setText(self.colIndex[nwOutline.WCOUNT], f"{wC:n}") newItem.setText(self._colIdx[nwOutline.WCOUNT], f"{wC:n}")
newItem.setText(self.colIndex[nwOutline.PCOUNT], f"{pC:n}") newItem.setText(self._colIdx[nwOutline.PCOUNT], f"{pC:n}")
newItem.setTextAlignment(self.colIndex[nwOutline.CCOUNT], Qt.AlignRight) newItem.setTextAlignment(self._colIdx[nwOutline.CCOUNT], Qt.AlignRight)
newItem.setTextAlignment(self.colIndex[nwOutline.WCOUNT], Qt.AlignRight) newItem.setTextAlignment(self._colIdx[nwOutline.WCOUNT], Qt.AlignRight)
newItem.setTextAlignment(self.colIndex[nwOutline.PCOUNT], Qt.AlignRight) newItem.setTextAlignment(self._colIdx[nwOutline.PCOUNT], Qt.AlignRight)
theRefs = self.theIndex.getReferences(tHandle, sTitle) theRefs = self.theIndex.getReferences(tHandle, sTitle)
newItem.setText(self.colIndex[nwOutline.POV], ", ".join(theRefs[nwKeyWords.POV_KEY])) newItem.setText(self._colIdx[nwOutline.POV], ", ".join(theRefs[nwKeyWords.POV_KEY]))
newItem.setText(self.colIndex[nwOutline.FOCUS], ", ".join(theRefs[nwKeyWords.FOCUS_KEY])) newItem.setText(self._colIdx[nwOutline.FOCUS], ", ".join(theRefs[nwKeyWords.FOCUS_KEY]))
newItem.setText(self.colIndex[nwOutline.CHAR], ", ".join(theRefs[nwKeyWords.CHAR_KEY])) newItem.setText(self._colIdx[nwOutline.CHAR], ", ".join(theRefs[nwKeyWords.CHAR_KEY]))
newItem.setText(self.colIndex[nwOutline.PLOT], ", ".join(theRefs[nwKeyWords.PLOT_KEY])) newItem.setText(self._colIdx[nwOutline.PLOT], ", ".join(theRefs[nwKeyWords.PLOT_KEY]))
newItem.setText(self.colIndex[nwOutline.TIME], ", ".join(theRefs[nwKeyWords.TIME_KEY])) newItem.setText(self._colIdx[nwOutline.TIME], ", ".join(theRefs[nwKeyWords.TIME_KEY]))
newItem.setText(self.colIndex[nwOutline.WORLD], ", ".join(theRefs[nwKeyWords.WORLD_KEY])) newItem.setText(self._colIdx[nwOutline.WORLD], ", ".join(theRefs[nwKeyWords.WORLD_KEY]))
newItem.setText(self.colIndex[nwOutline.OBJECT], ", ".join(theRefs[nwKeyWords.OBJECT_KEY])) newItem.setText(self._colIdx[nwOutline.OBJECT], ", ".join(theRefs[nwKeyWords.OBJECT_KEY]))
newItem.setText(self.colIndex[nwOutline.ENTITY], ", ".join(theRefs[nwKeyWords.ENTITY_KEY])) newItem.setText(self._colIdx[nwOutline.ENTITY], ", ".join(theRefs[nwKeyWords.ENTITY_KEY]))
newItem.setText(self.colIndex[nwOutline.CUSTOM], ", ".join(theRefs[nwKeyWords.CUSTOM_KEY])) newItem.setText(self._colIdx[nwOutline.CUSTOM], ", ".join(theRefs[nwKeyWords.CUSTOM_KEY]))
return newItem return newItem
+3 -3
View File
@@ -191,7 +191,7 @@ class GuiOutlineDetails(QScrollArea):
# Selected Item Tags # Selected Item Tags
self.tagsGroup = QGroupBox(self.tr("Reference Tags"), self) self.tagsGroup = QGroupBox(self.tr("Reference Tags"), self)
self.tagsForm = QGridLayout() self.tagsForm = QGridLayout()
self.tagsGroup.setLayout(self.tagsForm) self.tagsGroup.setLayout(self.tagsForm)
self.tagsForm.addWidget(self.povKeyLabel, 0, 0, 1, 1, Qt.AlignTop | Qt.AlignLeft) self.tagsForm.addWidget(self.povKeyLabel, 0, 0, 1, 1, Qt.AlignTop | Qt.AlignLeft)
@@ -279,8 +279,8 @@ class GuiOutlineDetails(QScrollArea):
"""Update the content of the tree with the given handle and line """Update the content of the tree with the given handle and line
number pointing to a header. number pointing to a header.
""" """
nwItem = self.theProject.projTree[tHandle] nwItem = self.theProject.projTree[tHandle]
novIdx = self.theIndex.getNovelData(tHandle, sTitle) novIdx = self.theIndex.getNovelData(tHandle, sTitle)
theRefs = self.theIndex.getReferences(tHandle, sTitle) theRefs = self.theIndex.getReferences(tHandle, sTitle)
if nwItem is None or novIdx is None: if nwItem is None or novIdx is None:
return False return False
+5 -5
View File
@@ -24,8 +24,8 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
""" """
import nw import nw
import logging
import math import math
import logging
from PyQt5.QtCore import Qt, QSize from PyQt5.QtCore import Qt, QSize
from PyQt5.QtGui import QFont from PyQt5.QtGui import QFont
@@ -66,10 +66,10 @@ class GuiProjectDetails(PagedDialog):
self.mainConf.pxInt(self.optState.getInt("GuiProjectDetails", "winHeight", wH)) self.mainConf.pxInt(self.optState.getInt("GuiProjectDetails", "winHeight", wH))
) )
self.tabMain = GuiProjectDetailsMain(self.theParent, self.theProject) self.tabMain = GuiProjectDetailsMain(self.theParent, self.theProject)
self.tabContents = GuiProjectDetailsContents(self.theParent, self.theProject) self.tabContents = GuiProjectDetailsContents(self.theParent, self.theProject)
self.addTab(self.tabMain, self.tr("Overview")) self.addTab(self.tabMain, self.tr("Overview"))
self.addTab(self.tabContents, self.tr("Contents")) self.addTab(self.tabContents, self.tr("Contents"))
self.buttonBox = QDialogButtonBox(QDialogButtonBox.Close) self.buttonBox = QDialogButtonBox(QDialogButtonBox.Close)
@@ -413,8 +413,8 @@ class GuiProjectDetailsContents(QWidget):
"""Set the content of the chapter/page tree. """Set the content of the chapter/page tree.
""" """
dblPages = self.dblValue.isChecked() dblPages = self.dblValue.isChecked()
wpPage = self.wpValue.value() wpPage = self.wpValue.value()
fstPage = self.poValue.value() - 1 fstPage = self.poValue.value() - 1
pTotal = 0 pTotal = 0
tPages = 1 tPages = 1
+2 -2
View File
@@ -132,7 +132,7 @@ class GuiProjectTree(QTreeWidget):
self.initTree() self.initTree()
# Internal Function Mapping # Internal Function Mapping
self.makeAlert = self.theParent.makeAlert self.makeAlert = self.theParent.makeAlert
self.askQuestion = self.theParent.askQuestion self.askQuestion = self.theParent.askQuestion
logger.debug("GuiProjectTree initialisation complete") logger.debug("GuiProjectTree initialisation complete")
@@ -1167,7 +1167,7 @@ class GuiProjectTreeMenu(QMenu):
inTrash = theItem.itemParent == trashHandle and trashHandle is not None inTrash = theItem.itemParent == trashHandle and trashHandle is not None
isTrash = theItem.itemHandle == trashHandle and trashHandle is not None isTrash = theItem.itemHandle == trashHandle and trashHandle is not None
isFile = theItem.itemType == nwItemType.FILE isFile = theItem.itemType == nwItemType.FILE
allowNew = not (isTrash or inTrash) allowNew = not (isTrash or inTrash)
+4 -4
View File
@@ -51,8 +51,8 @@ class GuiMainStatus(QStatusBar):
self.refTime = None self.refTime = None
self.userIdle = False self.userIdle = False
colNone = QColor(*self.theTheme.statNone) colNone = QColor(*self.theTheme.statNone)
colTrue = QColor(*self.theTheme.statUnsaved) colTrue = QColor(*self.theTheme.statUnsaved)
colFalse = QColor(*self.theTheme.statSaved) colFalse = QColor(*self.theTheme.statSaved)
iPx = self.theTheme.baseIconSize iPx = self.theTheme.baseIconSize
@@ -243,8 +243,8 @@ class StatusLED(QAbstractButton):
self._colNone = colNone self._colNone = colNone
self._colGood = colGood self._colGood = colGood
self._colBad = colBad self._colBad = colBad
self._theCol = colNone self._theCol = colNone
self.setFixedWidth(sW) self.setFixedWidth(sW)
self.setFixedHeight(sH) self.setFixedHeight(sH)
+9 -9
View File
@@ -25,9 +25,9 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
""" """
import nw import nw
import os
import logging import logging
import configparser import configparser
import os
from math import ceil from math import ceil
from functools import partial from functools import partial
@@ -272,7 +272,7 @@ class GuiTheme:
cssData = "" cssData = ""
try: try:
if os.path.isfile(self.cssFile): if os.path.isfile(self.cssFile):
with open(self.cssFile, mode="r", encoding="utf8") as inFile: with open(self.cssFile, mode="r", encoding="utf-8") as inFile:
cssData = inFile.read() cssData = inFile.read()
except Exception: except Exception:
logger.error("Could not load theme css file") logger.error("Could not load theme css file")
@@ -282,7 +282,7 @@ class GuiTheme:
# Config File # Config File
confParser = configparser.ConfigParser() confParser = configparser.ConfigParser()
try: try:
with open(self.confFile, mode="r", encoding="utf8") as inFile: with open(self.confFile, mode="r", encoding="utf-8") as inFile:
confParser.read_file(inFile) confParser.read_file(inFile)
except Exception: except Exception:
logger.error("Could not load theme settings from: %s", self.confFile) logger.error("Could not load theme settings from: %s", self.confFile)
@@ -340,7 +340,7 @@ class GuiTheme:
confParser = configparser.ConfigParser() confParser = configparser.ConfigParser()
try: try:
with open(self.syntaxFile, mode="r", encoding="utf8") as inFile: with open(self.syntaxFile, mode="r", encoding="utf-8") as inFile:
confParser.read_file(inFile) confParser.read_file(inFile)
except Exception: except Exception:
logger.error("Could not load syntax colours from: %s", self.syntaxFile) logger.error("Could not load syntax colours from: %s", self.syntaxFile)
@@ -395,7 +395,7 @@ class GuiTheme:
) )
logger.verbose("Checking theme config for '%s'", themeDir) logger.verbose("Checking theme config for '%s'", themeDir)
try: try:
with open(themeConf, mode="r", encoding="utf8") as inFile: with open(themeConf, mode="r", encoding="utf-8") as inFile:
confParser.read_file(inFile) confParser.read_file(inFile)
except Exception as e: except Exception as e:
self.makeAlert([ self.makeAlert([
@@ -421,14 +421,14 @@ class GuiTheme:
return self.syntaxList return self.syntaxList
confParser = configparser.ConfigParser() confParser = configparser.ConfigParser()
syntaxDir = os.path.join(self.mainConf.themeRoot, self.syntaxPath) syntaxDir = os.path.join(self.mainConf.themeRoot, self.syntaxPath)
for syntaxFile in os.listdir(syntaxDir): for syntaxFile in os.listdir(syntaxDir):
syntaxPath = os.path.join(syntaxDir, syntaxFile) syntaxPath = os.path.join(syntaxDir, syntaxFile)
if not os.path.isfile(syntaxPath): if not os.path.isfile(syntaxPath):
continue continue
logger.verbose("Checking theme syntax for '%s'", syntaxFile) logger.verbose("Checking theme syntax for '%s'", syntaxFile)
try: try:
with open(syntaxPath, mode="r", encoding="utf8") as inFile: with open(syntaxPath, mode="r", encoding="utf-8") as inFile:
confParser.read_file(inFile) confParser.read_file(inFile)
except Exception as e: except Exception as e:
self.makeAlert([ self.makeAlert([
@@ -646,7 +646,7 @@ class GuiIcons:
# Config File # Config File
confParser = configparser.ConfigParser() confParser = configparser.ConfigParser()
try: try:
with open(self.confFile, mode="r", encoding="utf8") as inFile: with open(self.confFile, mode="r", encoding="utf-8") as inFile:
confParser.read_file(inFile) confParser.read_file(inFile)
except Exception: except Exception:
logger.error("Could not load icon theme settings from: %s", self.confFile) logger.error("Could not load icon theme settings from: %s", self.confFile)
@@ -744,7 +744,7 @@ class GuiIcons:
themeConf = os.path.join(themePath, self.confName) themeConf = os.path.join(themePath, self.confName)
logger.verbose("Checking icon theme config for '%s'", themeDir) logger.verbose("Checking icon theme config for '%s'", themeDir)
try: try:
with open(themeConf, mode="r", encoding="utf8") as inFile: with open(themeConf, mode="r", encoding="utf-8") as inFile:
confParser.read_file(inFile) confParser.read_file(inFile)
except Exception as e: except Exception as e:
self.makeAlert([ self.makeAlert([
+26 -27
View File
@@ -24,11 +24,11 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
""" """
import nw import nw
import logging
import os import os
import logging
from datetime import datetime
from time import time from time import time
from datetime import datetime
from PyQt5.QtCore import Qt, QTimer, QSize, QThreadPool, pyqtSlot from PyQt5.QtCore import Qt, QTimer, QSize, QThreadPool, pyqtSlot
from PyQt5.QtGui import QIcon, QPixmap, QColor, QKeySequence, QCursor from PyQt5.QtGui import QIcon, QPixmap, QColor, QKeySequence, QCursor
@@ -71,15 +71,12 @@ class GuiMain(QMainWindow):
logger.info("OS: %s", self.mainConf.osType) logger.info("OS: %s", self.mainConf.osType)
logger.info("Kernel: %s", self.mainConf.kernelVer) logger.info("Kernel: %s", self.mainConf.kernelVer)
logger.info("Host: %s", self.mainConf.hostName) logger.info("Host: %s", self.mainConf.hostName)
logger.info( logger.info("Qt5 Version: %s (%d)",
"Qt5 Version: %s (%d)", self.mainConf.verQtString, self.mainConf.verQtValue self.mainConf.verQtString, self.mainConf.verQtValue)
) logger.info("PyQt5 Version: %s (%d)",
logger.info( self.mainConf.verPyQtString, self.mainConf.verPyQtValue)
"PyQt5 Version: %s (%d)", self.mainConf.verPyQtString, self.mainConf.verPyQtValue logger.info("Python Version: %s (0x%x)",
) self.mainConf.verPyString, self.mainConf.verPyHexVal)
logger.info(
"Python Version: %s (0x%x)", self.mainConf.verPyString, self.mainConf.verPyHexVal
)
logger.info("GUI Language: %s", self.mainConf.guiLang) logger.info("GUI Language: %s", self.mainConf.guiLang)
# Core Classes # Core Classes
@@ -137,7 +134,7 @@ class GuiMain(QMainWindow):
# Project Tree Tabs # Project Tree Tabs
self.projTabs = QTabWidget() self.projTabs = QTabWidget()
self.projTabs.setTabPosition(QTabWidget.South) self.projTabs.setTabPosition(QTabWidget.South)
self.projTabs.setStyleSheet(r"QTabWidget::pane {border: 0;};") self.projTabs.setStyleSheet("QTabWidget::pane {border: 0;};")
self.projTabs.addTab(self.treeView, self.tr("Project")) self.projTabs.addTab(self.treeView, self.tr("Project"))
self.projTabs.addTab(self.novelView, self.tr("Novel")) self.projTabs.addTab(self.novelView, self.tr("Novel"))
self.projTabs.currentChanged.connect(self._projTabsChanged) self.projTabs.currentChanged.connect(self._projTabsChanged)
@@ -152,7 +149,7 @@ class GuiMain(QMainWindow):
self.treeButtons.setToolButtonStyle(Qt.ToolButtonIconOnly) self.treeButtons.setToolButtonStyle(Qt.ToolButtonIconOnly)
self.treeButtons.setIconSize(QSize(btnSize, btnSize)) self.treeButtons.setIconSize(QSize(btnSize, btnSize))
self.treeButtons.setContentsMargins(0, 0, 0, 0) self.treeButtons.setContentsMargins(0, 0, 0, 0)
self.treeButtons.setStyleSheet(r"QToolBar {padding: 0;}") self.treeButtons.setStyleSheet("QToolBar {padding: 0;}")
self.projTabs.setCornerWidget(self.treeButtons, Qt.BottomRightCorner) self.projTabs.setCornerWidget(self.treeButtons, Qt.BottomRightCorner)
self.projDetailsBtn = QAction(self.tr("Project Details")) self.projDetailsBtn = QAction(self.tr("Project Details"))
@@ -199,7 +196,7 @@ class GuiMain(QMainWindow):
# Main Tabs : Editor / Outline # Main Tabs : Editor / Outline
self.mainTabs = QTabWidget() self.mainTabs = QTabWidget()
self.mainTabs.setTabPosition(QTabWidget.East) self.mainTabs.setTabPosition(QTabWidget.East)
self.mainTabs.setStyleSheet(r"QTabWidget::pane {border: 0;}") self.mainTabs.setStyleSheet("QTabWidget::pane {border: 0;}")
self.mainTabs.addTab(self.splitDocs, self.tr("Editor")) self.mainTabs.addTab(self.splitDocs, self.tr("Editor"))
self.mainTabs.addTab(self.splitOutline, self.tr("Outline")) self.mainTabs.addTab(self.splitOutline, self.tr("Outline"))
self.mainTabs.currentChanged.connect(self._mainTabChanged) self.mainTabs.currentChanged.connect(self._mainTabChanged)
@@ -415,7 +412,7 @@ class GuiMain(QMainWindow):
self.saveDocument() self.saveDocument()
if self.theProject.projAltered: if self.theProject.projAltered:
saveOK = self.saveProject() saveOK = self.saveProject()
doBackup = False doBackup = False
if self.theProject.doBackup and self.mainConf.backupOnClose: if self.theProject.doBackup and self.mainConf.backupOnClose:
doBackup = True doBackup = True
@@ -438,7 +435,7 @@ class GuiMain(QMainWindow):
self.theProject.closeProject(self.idleTime) self.theProject.closeProject(self.idleTime)
self.idleRefTime = time() self.idleRefTime = time()
self.idleTime = 0.0 self.idleTime = 0.0
self.theIndex.clearIndex() self.theIndex.clearIndex()
self.clearGUI() self.clearGUI()
@@ -490,14 +487,16 @@ class GuiMain(QMainWindow):
self, self.tr("Project Locked"), self, self.tr("Project Locked"),
"%s<br><br>%s<br>%s" % ( "%s<br><br>%s<br>%s" % (
self.tr( self.tr(
"The project is already open by another instance of novelWriter, and " "The project is already open by another instance of "
"is therefore locked. Override lock and continue anyway?" "novelWriter, and is therefore locked. Override lock "
"and continue anyway?"
), ),
self.tr( self.tr(
"Note: If the program or the computer previously crashed, the lock " "Note: If the program or the computer previously "
"can safely be overridden. If, however, another instance of " "crashed, the lock can safely be overridden. If, "
"novelWriter has the project open, overriding the lock may corrupt " "however, another instance of novelWriter has the "
"the project, and is not recommended." "project open, overriding the lock may corrupt the "
"project, and is not recommended."
), ),
lockDetails lockDetails
), ),
@@ -510,9 +509,9 @@ class GuiMain(QMainWindow):
return False return False
# Project is loaded # Project is loaded
self.hasProject = True self.hasProject = True
self.idleRefTime = time() self.idleRefTime = time()
self.idleTime = 0.0 self.idleTime = 0.0
# Load the tag index # Load the tag index
self.theIndex.loadIndex() self.theIndex.loadIndex()
@@ -721,7 +720,7 @@ class GuiMain(QMainWindow):
theText = None theText = None
try: try:
with open(loadFile, mode="rt", encoding="utf8") as inFile: with open(loadFile, mode="rt", encoding="utf-8") as inFile:
theText = inFile.read() theText = inFile.read()
self.mainConf.setLastPath(loadFile) self.mainConf.setLastPath(loadFile)
except Exception as e: except Exception as e:
@@ -740,8 +739,8 @@ class GuiMain(QMainWindow):
msgYes = self.askQuestion( msgYes = self.askQuestion(
self.tr("Import Document"), self.tr("Import Document"),
self.tr( self.tr(
"Importing the file will overwrite the current content of the document. " "Importing the file will overwrite the current content of "
"Do you want to proceed?" "the document. Do you want to proceed?"
) )
) )
if not msgYes: if not msgYes:
+23 -27
View File
@@ -24,24 +24,24 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
""" """
import nw import nw
import logging
import json
import os import os
import json
import logging
from time import time from time import time
from datetime import datetime from datetime import datetime
from PyQt5.QtCore import Qt, QTimer
from PyQt5.QtPrintSupport import QPrinter, QPrintPreviewDialog
from PyQt5.QtGui import ( from PyQt5.QtGui import (
QPalette, QColor, QFont, QCursor, QFontInfo QPalette, QColor, QFont, QCursor, QFontInfo
) )
from PyQt5.QtCore import Qt, QTimer
from PyQt5.QtWidgets import ( from PyQt5.QtWidgets import (
qApp, QDialog, QVBoxLayout, QHBoxLayout, QTextBrowser, QPushButton, QLabel, qApp, QDialog, QVBoxLayout, QHBoxLayout, QTextBrowser, QPushButton, QLabel,
QLineEdit, QGroupBox, QGridLayout, QProgressBar, QMenu, QAction, QLineEdit, QGroupBox, QGridLayout, QProgressBar, QMenu, QAction,
QFileDialog, QFontDialog, QSpinBox, QScrollArea, QSplitter, QWidget, QFileDialog, QFontDialog, QSpinBox, QScrollArea, QSplitter, QWidget,
QSizePolicy, QDoubleSpinBox, QComboBox QSizePolicy, QDoubleSpinBox, QComboBox
) )
from PyQt5.QtPrintSupport import QPrinter, QPrintPreviewDialog
from nw.core import ToHtml, ToOdt, ToMarkdown from nw.core import ToHtml, ToOdt, ToMarkdown
from nw.enum import nwAlert, nwItemType, nwItemLayout, nwItemClass from nw.enum import nwAlert, nwItemType, nwItemLayout, nwItemClass
@@ -55,14 +55,12 @@ logger = logging.getLogger(__name__)
class GuiBuildNovel(QDialog): class GuiBuildNovel(QDialog):
FMT_PDF = 1 # Print to PDF FMT_PDF = 1 # Print to PDF
FMT_ODT = 2 # Open Document file FMT_ODT = 2 # Open Document file
FMT_FODT = 3 # Flat Open Document file FMT_FODT = 3 # Flat Open Document file
FMT_HTM = 4 # HTML5 FMT_HTM = 4 # HTML5
FMT_NWD = 5 # nW Markdown FMT_NWD = 5 # nW Markdown
FMT_MD = 6 # Standard Markdown FMT_MD = 6 # Standard Markdown
FMT_GH = 7 # GitHub Markdown FMT_GH = 7 # GitHub Markdown
FMT_JSON_H = 8 # HTML5 wrapped in JSON FMT_JSON_H = 8 # HTML5 wrapped in JSON
FMT_JSON_M = 9 # nW Markdown wrapped in JSON FMT_JSON_M = 9 # nW Markdown wrapped in JSON
@@ -560,8 +558,8 @@ class GuiBuildNovel(QDialog):
"""Load the previously generated document from cache. """Load the previously generated document from cache.
""" """
if self._loadCache(): if self._loadCache():
textFont = self.textFont.text() textFont = self.textFont.text()
textSize = self.textSize.value() textSize = self.textSize.value()
justifyText = self.justifyText.isChecked() justifyText = self.justifyText.isChecked()
self.docView.setTextFont(textFont, textSize) self.docView.setTextFont(textFont, textSize)
self.docView.setJustify(justifyText) self.docView.setJustify(justifyText)
@@ -596,14 +594,14 @@ class GuiBuildNovel(QDialog):
""" """
# Get Settings # Get Settings
justifyText = self.justifyText.isChecked() justifyText = self.justifyText.isChecked()
noStyling = self.noStyling.isChecked() noStyling = self.noStyling.isChecked()
textFont = self.textFont.text() textFont = self.textFont.text()
textSize = self.textSize.value() textSize = self.textSize.value()
replaceTabs = self.replaceTabs.isChecked() replaceTabs = self.replaceTabs.isChecked()
self.htmlText = [] self.htmlText = []
self.htmlStyle = [] self.htmlStyle = []
self.htmlSize = 0 self.htmlSize = 0
# Build Preview # Build Preview
# ============= # =============
@@ -672,7 +670,7 @@ class GuiBuildNovel(QDialog):
textFixed = fontInfo.fixedPitch() textFixed = fontInfo.fixedPitch()
isHtml = isinstance(bldObj, ToHtml) isHtml = isinstance(bldObj, ToHtml)
isOdt = isinstance(bldObj, ToOdt) isOdt = isinstance(bldObj, ToOdt)
bldObj.setTitleFormat(fmtTitle) bldObj.setTitleFormat(fmtTitle)
bldObj.setChapterFormat(fmtChapter) bldObj.setChapterFormat(fmtChapter)
@@ -706,7 +704,7 @@ class GuiBuildNovel(QDialog):
for nItt, tItem in enumerate(self.theProject.projTree): for nItt, tItem in enumerate(self.theProject.projTree):
noteRoot = noteFiles noteRoot = noteFiles
noteRoot &= tItem.itemType == nwItemType.ROOT noteRoot &= tItem.itemType == nwItemType.ROOT
noteRoot &= tItem.itemClass != nwItemClass.NOVEL noteRoot &= tItem.itemClass != nwItemClass.NOVEL
noteRoot &= tItem.itemClass != nwItemClass.ARCHIVE noteRoot &= tItem.itemClass != nwItemClass.ARCHIVE
@@ -848,12 +846,12 @@ class GuiBuildNovel(QDialog):
# ================== # ==================
cleanName = makeFileNameSafe(self.theProject.projName) cleanName = makeFileNameSafe(self.theProject.projName)
fileName = "%s.%s" % (cleanName, fileExt) fileName = "%s.%s" % (cleanName, fileExt)
saveDir = self.mainConf.lastPath saveDir = self.mainConf.lastPath
if not os.path.isdir(saveDir): if not os.path.isdir(saveDir):
saveDir = os.path.expanduser("~") saveDir = os.path.expanduser("~")
savePath = os.path.join(saveDir, fileName) savePath = os.path.join(saveDir, fileName)
savePath, _ = QFileDialog.getSaveFileName( savePath, _ = QFileDialog.getSaveFileName(
self, self.tr("Save Document As"), savePath self, self.tr("Save Document As"), savePath
) )
@@ -967,7 +965,7 @@ class GuiBuildNovel(QDialog):
} }
try: try:
with open(savePath, mode="w", encoding="utf8") as outFile: with open(savePath, mode="w", encoding="utf-8") as outFile:
outFile.write(json.dumps(jsonData, indent=2)) outFile.write(json.dumps(jsonData, indent=2))
wSuccess = True wSuccess = True
except Exception as e: except Exception as e:
@@ -1045,10 +1043,9 @@ class GuiBuildNovel(QDialog):
buildCache = os.path.join(self.theProject.projCache, nwFiles.BUILD_CACHE) buildCache = os.path.join(self.theProject.projCache, nwFiles.BUILD_CACHE)
dataCount = 0 dataCount = 0
if os.path.isfile(buildCache): if os.path.isfile(buildCache):
logger.debug("Loading build cache") logger.debug("Loading build cache")
try: try:
with open(buildCache, mode="r", encoding="utf8") as inFile: with open(buildCache, mode="r", encoding="utf-8") as inFile:
theJson = inFile.read() theJson = inFile.read()
theData = json.loads(theJson) theData = json.loads(theJson)
except Exception: except Exception:
@@ -1071,10 +1068,9 @@ class GuiBuildNovel(QDialog):
"""Save the current data to cache. """Save the current data to cache.
""" """
buildCache = os.path.join(self.theProject.projCache, nwFiles.BUILD_CACHE) buildCache = os.path.join(self.theProject.projCache, nwFiles.BUILD_CACHE)
logger.debug("Saving build cache") logger.debug("Saving build cache")
try: try:
with open(buildCache, mode="w+", encoding="utf8") as outFile: with open(buildCache, mode="w+", encoding="utf-8") as outFile:
outFile.write(json.dumps({ outFile.write(json.dumps({
"buildTime": self.buildTime, "buildTime": self.buildTime,
"htmlStyle": self.htmlStyle, "htmlStyle": self.htmlStyle,
@@ -1311,10 +1307,10 @@ class GuiBuildNovelDocView(QTextBrowser):
"""Set the stylesheet for the preview document. """Set the stylesheet for the preview document.
""" """
if not theStyles: if not theStyles:
theStyles.append(r"h1, h2 {color: rgb(66, 113, 174);}") theStyles.append("h1, h2 {color: rgb(66, 113, 174);}")
theStyles.append(r"h3, h4 {color: rgb(50, 50, 50);}") theStyles.append("h3, h4 {color: rgb(50, 50, 50);}")
theStyles.append(r"a {color: rgb(66, 113, 174);}") theStyles.append("a {color: rgb(66, 113, 174);}")
theStyles.append(r".tags {color: rgb(245, 135, 31); font-weight: bold;}") theStyles.append(".tags {color: rgb(245, 135, 31); font-weight: bold;}")
self.qDocument.setDefaultStyleSheet("\n".join(theStyles)) self.qDocument.setDefaultStyleSheet("\n".join(theStyles))
+1 -1
View File
@@ -24,8 +24,8 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
""" """
import nw import nw
import logging
import os import os
import logging
from PyQt5.QtCore import Qt from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import ( from PyQt5.QtWidgets import (
+9 -9
View File
@@ -24,14 +24,14 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
""" """
import nw import nw
import logging
import json
import os import os
import json
import logging
from datetime import datetime from datetime import datetime
from PyQt5.QtCore import Qt
from PyQt5.QtGui import QPixmap, QCursor from PyQt5.QtGui import QPixmap, QCursor
from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import ( from PyQt5.QtWidgets import (
qApp, QDialog, QTreeWidget, QTreeWidgetItem, QDialogButtonBox, QGridLayout, qApp, QDialog, QTreeWidget, QTreeWidgetItem, QDialogButtonBox, QGridLayout,
QLabel, QGroupBox, QMenu, QAction, QFileDialog, QSpinBox, QHBoxLayout QLabel, QGroupBox, QMenu, QAction, QFileDialog, QSpinBox, QHBoxLayout
@@ -132,7 +132,7 @@ class GuiWritingStats(QDialog):
self.barImage.fill(self.palette().highlight().color()) self.barImage.fill(self.palette().highlight().color())
# Session Info # Session Info
self.infoBox = QGroupBox(self.tr("Sum Totals"), self) self.infoBox = QGroupBox(self.tr("Sum Totals"), self)
self.infoForm = QGridLayout(self) self.infoForm = QGridLayout(self)
self.infoBox.setLayout(self.infoForm) self.infoBox.setLayout(self.infoForm)
@@ -186,7 +186,7 @@ class GuiWritingStats(QDialog):
# Filter Options # Filter Options
sPx = self.theTheme.baseIconSize sPx = self.theTheme.baseIconSize
self.filterBox = QGroupBox(self.tr("Filters"), self) self.filterBox = QGroupBox(self.tr("Filters"), self)
self.filterForm = QGridLayout(self) self.filterForm = QGridLayout(self)
self.filterBox.setLayout(self.filterForm) self.filterBox.setLayout(self.filterForm)
@@ -382,7 +382,7 @@ class GuiWritingStats(QDialog):
errMsg = "" errMsg = ""
try: try:
with open(savePath, mode="w", encoding="utf8") as outFile: with open(savePath, mode="w", encoding="utf-8") as outFile:
if dataFmt == self.FMT_JSON: if dataFmt == self.FMT_JSON:
jsonData = [] jsonData = []
for _, sD, tT, wD, wA, wB, tI in self.filterData: for _, sD, tT, wD, wA, wB, tI in self.filterData:
@@ -436,8 +436,8 @@ class GuiWritingStats(QDialog):
ttNovel = 0 ttNovel = 0
ttNotes = 0 ttNotes = 0
ttTime = 0 ttTime = 0
ttIdle = 0 ttIdle = 0
logFile = os.path.join(self.theProject.projMeta, nwFiles.SESS_STATS) logFile = os.path.join(self.theProject.projMeta, nwFiles.SESS_STATS)
if not os.path.isfile(logFile): if not os.path.isfile(logFile):
@@ -445,7 +445,7 @@ class GuiWritingStats(QDialog):
return False return False
try: try:
with open(logFile, mode="r", encoding="utf8") as inFile: with open(logFile, mode="r", encoding="utf-8") as inFile:
for inLine in inFile: for inLine in inFile:
if inLine.startswith("#"): if inLine.startswith("#"):
if inLine.startswith("# Offset"): if inLine.startswith("# Offset"):
+1 -1
View File
@@ -143,7 +143,7 @@ def fncConf(fncDir):
@pytest.fixture(scope="function") @pytest.fixture(scope="function")
def dummyGUI(monkeypatch, tmpConf): def mockGUI(monkeypatch, tmpConf):
"""Create a mock instance of novelWriter's main GUI class. """Create a mock instance of novelWriter's main GUI class.
""" """
monkeypatch.setattr("nw.CONFIG", tmpConf) monkeypatch.setattr("nw.CONFIG", tmpConf)
+21 -24
View File
@@ -23,16 +23,17 @@ import os
import pytest import pytest
from mock import causeOSError from mock import causeOSError
from tools import readFile
from nw.core import NWProject, NWDoc from nw.core import NWProject, NWDoc
from nw.enum import nwItemClass, nwItemLayout from nw.enum import nwItemClass, nwItemLayout
@pytest.mark.core @pytest.mark.core
def testCoreDocument_LoadSave(monkeypatch, dummyGUI, nwMinimal): def testCoreDocument_LoadSave(monkeypatch, mockGUI, nwMinimal):
"""Test loading and saving a document with the NWDoc class. """Test loading and saving a document with the NWDoc class.
""" """
theProject = NWProject(dummyGUI) theProject = NWProject(mockGUI)
assert theProject.openProject(nwMinimal) assert theProject.openProject(nwMinimal)
assert theProject.projPath == nwMinimal assert theProject.projPath == nwMinimal
@@ -75,21 +76,18 @@ def testCoreDocument_LoadSave(monkeypatch, dummyGUI, nwMinimal):
# Check file content # Check file content
docPath = os.path.join(nwMinimal, "content", xHandle+".nwd") docPath = os.path.join(nwMinimal, "content", xHandle+".nwd")
with open(docPath, mode="r", encoding="utf8") as inFile: assert readFile(docPath) == (
assert inFile.read() == ( "%%~name: New File\n"
"%%~name: New File\n" f"%%~path: a508bb932959c/{xHandle}\n"
f"%%~path: a508bb932959c/{xHandle}\n" "%%~kind: NOVEL/SCENE\n"
"%%~kind: NOVEL/SCENE\n" "### Test File\n\n"
"### Test File\n\n" "Text ...\n\n"
"Text ...\n\n" )
)
# Force no meta data # Force no meta data
theDoc._theItem = None theDoc._theItem = None
assert theDoc.writeDocument(theText) assert theDoc.writeDocument(theText)
assert readFile(docPath) == theText
with open(docPath, mode="r", encoding="utf8") as inFile:
assert inFile.read() == theText
# Cause open() to fail while saving # Cause open() to fail while saving
with monkeypatch.context() as mp: with monkeypatch.context() as mp:
@@ -122,10 +120,10 @@ def testCoreDocument_LoadSave(monkeypatch, dummyGUI, nwMinimal):
@pytest.mark.core @pytest.mark.core
def testCoreDocument_Methods(dummyGUI, nwMinimal): def testCoreDocument_Methods(mockGUI, nwMinimal):
"""Test other methods of the NWDoc class. """Test other methods of the NWDoc class.
""" """
theProject = NWProject(dummyGUI) theProject = NWProject(mockGUI)
assert theProject.openProject(nwMinimal) assert theProject.openProject(nwMinimal)
assert theProject.projPath == nwMinimal assert theProject.projPath == nwMinimal
@@ -151,15 +149,14 @@ def testCoreDocument_Methods(dummyGUI, nwMinimal):
# Add meta data garbage # Add meta data garbage
assert theDoc.writeDocument("%%~ stuff\n### Test File\n\nText ...\n\n") assert theDoc.writeDocument("%%~ stuff\n### Test File\n\nText ...\n\n")
with open(docPath, mode="r", encoding="utf8") as inFile: assert readFile(docPath) == (
assert inFile.read() == ( "%%~name: New Scene\n"
"%%~name: New Scene\n" f"%%~path: a6d311a93600a/{sHandle}\n"
f"%%~path: a6d311a93600a/{sHandle}\n" "%%~kind: NOVEL/SCENE\n"
"%%~kind: NOVEL/SCENE\n" "%%~ stuff\n"
"%%~ stuff\n" "### Test File\n\n"
"### Test File\n\n" "Text ...\n\n"
"Text ...\n\n" )
)
assert theDoc.readDocument() == "### Test File\n\nText ...\n\n" assert theDoc.readDocument() == "### Test File\n\nText ...\n\n"
+18 -18
View File
@@ -34,7 +34,7 @@ from nw.enum import nwItemClass, nwItemLayout
@pytest.mark.core @pytest.mark.core
def testCoreIndex_LoadSave(monkeypatch, nwLipsum, dummyGUI, outDir, refDir): def testCoreIndex_LoadSave(monkeypatch, nwLipsum, mockGUI, outDir, refDir):
"""Test core functionality of scaning, saving, loading and checking """Test core functionality of scaning, saving, loading and checking
the index cache file. the index cache file.
""" """
@@ -42,7 +42,7 @@ def testCoreIndex_LoadSave(monkeypatch, nwLipsum, dummyGUI, outDir, refDir):
testFile = os.path.join(outDir, "coreIndex_LoadSave_tagsIndex.json") testFile = os.path.join(outDir, "coreIndex_LoadSave_tagsIndex.json")
compFile = os.path.join(refDir, "coreIndex_LoadSave_tagsIndex.json") compFile = os.path.join(refDir, "coreIndex_LoadSave_tagsIndex.json")
theProject = NWProject(dummyGUI) theProject = NWProject(mockGUI)
theProject.projTree.setSeed(42) theProject.projTree.setSeed(42)
assert theProject.openProject(nwLipsum) assert theProject.openProject(nwLipsum)
@@ -126,10 +126,10 @@ def testCoreIndex_LoadSave(monkeypatch, nwLipsum, dummyGUI, outDir, refDir):
@pytest.mark.core @pytest.mark.core
def testCoreIndex_ScanThis(nwMinimal, dummyGUI): def testCoreIndex_ScanThis(nwMinimal, mockGUI):
"""Test the tag scanner function scanThis. """Test the tag scanner function scanThis.
""" """
theProject = NWProject(dummyGUI) theProject = NWProject(mockGUI)
theProject.projTree.setSeed(42) theProject.projTree.setSeed(42)
assert theProject.openProject(nwMinimal) assert theProject.openProject(nwMinimal)
@@ -178,10 +178,10 @@ def testCoreIndex_ScanThis(nwMinimal, dummyGUI):
@pytest.mark.core @pytest.mark.core
def testCoreIndex_CheckThese(nwMinimal, dummyGUI): def testCoreIndex_CheckThese(nwMinimal, mockGUI):
"""Test the tag checker function checkThese. """Test the tag checker function checkThese.
""" """
theProject = NWProject(dummyGUI) theProject = NWProject(mockGUI)
theProject.projTree.setSeed(42) theProject.projTree.setSeed(42)
assert theProject.openProject(nwMinimal) assert theProject.openProject(nwMinimal)
@@ -240,10 +240,10 @@ def testCoreIndex_CheckThese(nwMinimal, dummyGUI):
@pytest.mark.core @pytest.mark.core
def testCoreIndex_ScanText(nwMinimal, dummyGUI): def testCoreIndex_ScanText(nwMinimal, mockGUI):
"""Check the index text scanner. """Check the index text scanner.
""" """
theProject = NWProject(dummyGUI) theProject = NWProject(mockGUI)
theProject.projTree.setSeed(42) theProject.projTree.setSeed(42)
assert theProject.openProject(nwMinimal) assert theProject.openProject(nwMinimal)
@@ -446,10 +446,10 @@ def testCoreIndex_ScanText(nwMinimal, dummyGUI):
@pytest.mark.core @pytest.mark.core
def testCoreIndex_ExtractData(nwMinimal, dummyGUI): def testCoreIndex_ExtractData(nwMinimal, mockGUI):
"""Check the index data extraction functions. """Check the index data extraction functions.
""" """
theProject = NWProject(dummyGUI) theProject = NWProject(mockGUI)
theProject.projTree.setSeed(42) theProject.projTree.setSeed(42)
assert theProject.openProject(nwMinimal) assert theProject.openProject(nwMinimal)
@@ -679,10 +679,10 @@ def testCoreIndex_ExtractData(nwMinimal, dummyGUI):
@pytest.mark.core @pytest.mark.core
def testCoreIndex_CheckTagIndex(dummyGUI): def testCoreIndex_CheckTagIndex(mockGUI):
"""Test the tag index checker. """Test the tag index checker.
""" """
theProject = NWProject(dummyGUI) theProject = NWProject(mockGUI)
theIndex = NWIndex(theProject) theIndex = NWIndex(theProject)
# Valid Index # Valid Index
@@ -744,10 +744,10 @@ def testCoreIndex_CheckTagIndex(dummyGUI):
@pytest.mark.core @pytest.mark.core
def testCoreIndex_CheckRefIndex(dummyGUI): def testCoreIndex_CheckRefIndex(mockGUI):
"""Test the reference index checker. """Test the reference index checker.
""" """
theProject = NWProject(dummyGUI) theProject = NWProject(mockGUI)
theIndex = NWIndex(theProject) theIndex = NWIndex(theProject)
# Valid Index # Valid Index
@@ -867,10 +867,10 @@ def testCoreIndex_CheckRefIndex(dummyGUI):
@pytest.mark.core @pytest.mark.core
def testCoreIndex_CheckNovelNoteIndex(dummyGUI): def testCoreIndex_CheckNovelNoteIndex(mockGUI):
"""Test the novel and note index checkers. """Test the novel and note index checkers.
""" """
theProject = NWProject(dummyGUI) theProject = NWProject(mockGUI)
theIndex = NWIndex(theProject) theIndex = NWIndex(theProject)
# Valid Index # Valid Index
@@ -1126,10 +1126,10 @@ def testCoreIndex_CheckNovelNoteIndex(dummyGUI):
@pytest.mark.core @pytest.mark.core
def testCoreIndex_CheckTextCounts(dummyGUI): def testCoreIndex_CheckTextCounts(mockGUI):
"""Test the text counts checker. """Test the text counts checker.
""" """
theProject = NWProject(dummyGUI) theProject = NWProject(mockGUI)
theIndex = NWIndex(theProject) theIndex = NWIndex(theProject)
# Valid Index # Valid Index
+10 -10
View File
@@ -29,10 +29,10 @@ from nw.enum import nwItemClass, nwItemType, nwItemLayout
@pytest.mark.core @pytest.mark.core
def testCoreItem_Setters(dummyGUI): def testCoreItem_Setters(mockGUI):
"""Test all the simple setters for the NWItem class. """Test all the simple setters for the NWItem class.
""" """
theProject = NWProject(dummyGUI) theProject = NWProject(mockGUI)
theItem = NWItem(theProject) theItem = NWItem(theProject)
# Name # Name
@@ -167,11 +167,11 @@ def testCoreItem_Setters(dummyGUI):
@pytest.mark.core @pytest.mark.core
def testCoreItem_TypeSetter(dummyGUI): def testCoreItem_TypeSetter(mockGUI):
"""Test the setter for all the nwItemType values for the NWItem """Test the setter for all the nwItemType values for the NWItem
class. class.
""" """
theProject = NWProject(dummyGUI) theProject = NWProject(mockGUI)
theItem = NWItem(theProject) theItem = NWItem(theProject)
# Type # Type
@@ -196,11 +196,11 @@ def testCoreItem_TypeSetter(dummyGUI):
@pytest.mark.core @pytest.mark.core
def testCoreItem_ClassSetter(dummyGUI): def testCoreItem_ClassSetter(mockGUI):
"""Test the setter for all the nwItemClass values for the NWItem """Test the setter for all the nwItemClass values for the NWItem
class. class.
""" """
theProject = NWProject(dummyGUI) theProject = NWProject(mockGUI)
theItem = NWItem(theProject) theItem = NWItem(theProject)
# Class # Class
@@ -237,11 +237,11 @@ def testCoreItem_ClassSetter(dummyGUI):
@pytest.mark.core @pytest.mark.core
def testCoreItem_LayoutSetter(dummyGUI): def testCoreItem_LayoutSetter(mockGUI):
"""Test the setter for all the nwItemLayout values for the NWItem """Test the setter for all the nwItemLayout values for the NWItem
class. class.
""" """
theProject = NWProject(dummyGUI) theProject = NWProject(mockGUI)
theItem = NWItem(theProject) theItem = NWItem(theProject)
# Layout # Layout
@@ -274,10 +274,10 @@ def testCoreItem_LayoutSetter(dummyGUI):
@pytest.mark.core @pytest.mark.core
def testCoreItem_XMLPackUnpack(dummyGUI, caplog): def testCoreItem_XMLPackUnpack(mockGUI, caplog):
"""Test packing and unpacking XML objects for the NWItem class. """Test packing and unpacking XML objects for the NWItem class.
""" """
theProject = NWProject(dummyGUI) theProject = NWProject(mockGUI)
nwXML = etree.Element("novelWriterXML") nwXML = etree.Element("novelWriterXML")
# File # File
+18 -18
View File
@@ -24,6 +24,7 @@ import json
import pytest import pytest
from mock import causeOSError from mock import causeOSError
from tools import writeFile
from nw.core import NWProject from nw.core import NWProject
from nw.core.options import OptionState from nw.core.options import OptionState
@@ -31,28 +32,27 @@ from nw.constants import nwFiles
@pytest.mark.core @pytest.mark.core
def testCoreOptions_LoadSave(monkeypatch, dummyGUI, tmpDir): def testCoreOptions_LoadSave(monkeypatch, mockGUI, tmpDir):
"""Test loading and saving from the OptionState class. """Test loading and saving from the OptionState class.
""" """
theProject = NWProject(dummyGUI) theProject = NWProject(mockGUI)
theOpts = OptionState(theProject) theOpts = OptionState(theProject)
# Write a test file # Write a test file
optFile = os.path.join(tmpDir, nwFiles.OPTS_FILE) optFile = os.path.join(tmpDir, nwFiles.OPTS_FILE)
with open(optFile, mode="w+", encoding="utf8") as outFile: writeFile(optFile, json.dumps({
json.dump({ "GuiBuildNovel": {
"GuiBuildNovel": { "winWidth": 1000,
"winWidth": 1000, "winHeight": 700,
"winHeight": 700, "addNovel": True,
"addNovel": True, "addNotes": False,
"addNotes": False, "textFont": "Cantarell",
"textFont": "Cantarell", "dummyItem": None,
"dummyItem": None, },
}, "DummyGroup": {
"DummyGroup": { "dummyItem": None,
"dummyItem": None, },
}, }))
}, outFile)
# Load and save with no path set # Load and save with no path set
theProject.projMeta = None theProject.projMeta = None
@@ -102,10 +102,10 @@ def testCoreOptions_LoadSave(monkeypatch, dummyGUI, tmpDir):
@pytest.mark.core @pytest.mark.core
def testCoreOptions_SetGet(dummyGUI): def testCoreOptions_SetGet(mockGUI):
"""Test setting and getting values from the OptionState class. """Test setting and getting values from the OptionState class.
""" """
theProject = NWProject(dummyGUI) theProject = NWProject(mockGUI)
theOpts = OptionState(theProject) theOpts = OptionState(theProject)
# Set invalid values # Set invalid values
+56 -59
View File
@@ -36,7 +36,7 @@ from nw.constants import nwFiles
@pytest.mark.core @pytest.mark.core
def testCoreProject_NewMinimal(fncDir, outDir, refDir, dummyGUI): def testCoreProject_NewMinimal(fncDir, outDir, refDir, mockGUI):
"""Create a new project from a project wizard dictionary. With """Create a new project from a project wizard dictionary. With
default setting, creating a Minimal project. default setting, creating a Minimal project.
""" """
@@ -44,7 +44,7 @@ def testCoreProject_NewMinimal(fncDir, outDir, refDir, dummyGUI):
testFile = os.path.join(outDir, "coreProject_NewMinimal_nwProject.nwx") testFile = os.path.join(outDir, "coreProject_NewMinimal_nwProject.nwx")
compFile = os.path.join(refDir, "coreProject_NewMinimal_nwProject.nwx") compFile = os.path.join(refDir, "coreProject_NewMinimal_nwProject.nwx")
theProject = NWProject(dummyGUI) theProject = NWProject(mockGUI)
theProject.projTree.setSeed(42) theProject.projTree.setSeed(42)
# Setting no data should fail # Setting no data should fail
@@ -85,7 +85,7 @@ def testCoreProject_NewMinimal(fncDir, outDir, refDir, dummyGUI):
@pytest.mark.core @pytest.mark.core
def testCoreProject_NewCustomA(fncDir, outDir, refDir, dummyGUI): def testCoreProject_NewCustomA(fncDir, outDir, refDir, mockGUI):
"""Create a new project from a project wizard dictionary. """Create a new project from a project wizard dictionary.
Custom type with chapters and scenes. Custom type with chapters and scenes.
""" """
@@ -113,7 +113,7 @@ def testCoreProject_NewCustomA(fncDir, outDir, refDir, dummyGUI):
"numScenes": 3, "numScenes": 3,
"chFolders": True, "chFolders": True,
} }
theProject = NWProject(dummyGUI) theProject = NWProject(mockGUI)
theProject.projTree.setSeed(42) theProject.projTree.setSeed(42)
assert theProject.newProject(projData) assert theProject.newProject(projData)
@@ -127,7 +127,7 @@ def testCoreProject_NewCustomA(fncDir, outDir, refDir, dummyGUI):
@pytest.mark.core @pytest.mark.core
def testCoreProject_NewCustomB(fncDir, outDir, refDir, dummyGUI): def testCoreProject_NewCustomB(fncDir, outDir, refDir, mockGUI):
"""Create a new project from a project wizard dictionary. """Create a new project from a project wizard dictionary.
Custom type without chapters, but with scenes. Custom type without chapters, but with scenes.
""" """
@@ -155,7 +155,7 @@ def testCoreProject_NewCustomB(fncDir, outDir, refDir, dummyGUI):
"numScenes": 6, "numScenes": 6,
"chFolders": True, "chFolders": True,
} }
theProject = NWProject(dummyGUI) theProject = NWProject(mockGUI)
theProject.projTree.setSeed(42) theProject.projTree.setSeed(42)
assert theProject.newProject(projData) assert theProject.newProject(projData)
@@ -169,7 +169,7 @@ def testCoreProject_NewCustomB(fncDir, outDir, refDir, dummyGUI):
@pytest.mark.core @pytest.mark.core
def testCoreProject_NewSampleA(fncDir, tmpConf, dummyGUI, tmpDir): def testCoreProject_NewSampleA(fncDir, tmpConf, mockGUI, tmpDir):
"""Check that we can create a new project can be created from the """Check that we can create a new project can be created from the
provided sample project via a zip file. provided sample project via a zip file.
""" """
@@ -182,7 +182,7 @@ def testCoreProject_NewSampleA(fncDir, tmpConf, dummyGUI, tmpDir):
"popMinimal": False, "popMinimal": False,
"popCustom": False, "popCustom": False,
} }
theProject = NWProject(dummyGUI) theProject = NWProject(mockGUI)
theProject.projTree.setSeed(42) theProject.projTree.setSeed(42)
# Sample set, but no path # Sample set, but no path
@@ -218,7 +218,7 @@ def testCoreProject_NewSampleA(fncDir, tmpConf, dummyGUI, tmpDir):
@pytest.mark.core @pytest.mark.core
def testCoreProject_NewSampleB(monkeypatch, fncDir, tmpConf, dummyGUI, tmpDir): def testCoreProject_NewSampleB(monkeypatch, fncDir, tmpConf, mockGUI, tmpDir):
"""Check that we can create a new project can be created from the """Check that we can create a new project can be created from the
provided sample project folder. provided sample project folder.
""" """
@@ -231,7 +231,7 @@ def testCoreProject_NewSampleB(monkeypatch, fncDir, tmpConf, dummyGUI, tmpDir):
"popMinimal": False, "popMinimal": False,
"popCustom": False, "popCustom": False,
} }
theProject = NWProject(dummyGUI) theProject = NWProject(mockGUI)
theProject.projTree.setSeed(42) theProject.projTree.setSeed(42)
# Make sure we do not pick up the nw/assets/sample.zip file # Make sure we do not pick up the nw/assets/sample.zip file
@@ -256,14 +256,14 @@ def testCoreProject_NewSampleB(monkeypatch, fncDir, tmpConf, dummyGUI, tmpDir):
@pytest.mark.core @pytest.mark.core
def testCoreProject_NewRoot(fncDir, outDir, refDir, dummyGUI): def testCoreProject_NewRoot(fncDir, outDir, refDir, mockGUI):
"""Check that new root folders can be added to the project. """Check that new root folders can be added to the project.
""" """
projFile = os.path.join(fncDir, "nwProject.nwx") projFile = os.path.join(fncDir, "nwProject.nwx")
testFile = os.path.join(outDir, "coreProject_NewRoot_nwProject.nwx") testFile = os.path.join(outDir, "coreProject_NewRoot_nwProject.nwx")
compFile = os.path.join(refDir, "coreProject_NewRoot_nwProject.nwx") compFile = os.path.join(refDir, "coreProject_NewRoot_nwProject.nwx")
theProject = NWProject(dummyGUI) theProject = NWProject(mockGUI)
theProject.projTree.setSeed(42) theProject.projTree.setSeed(42)
assert theProject.newProject({"projPath": fncDir}) assert theProject.newProject({"projPath": fncDir})
@@ -293,14 +293,14 @@ def testCoreProject_NewRoot(fncDir, outDir, refDir, dummyGUI):
@pytest.mark.core @pytest.mark.core
def testCoreProject_NewFile(fncDir, outDir, refDir, dummyGUI): def testCoreProject_NewFile(fncDir, outDir, refDir, mockGUI):
"""Check that new files can be added to the project. """Check that new files can be added to the project.
""" """
projFile = os.path.join(fncDir, "nwProject.nwx") projFile = os.path.join(fncDir, "nwProject.nwx")
testFile = os.path.join(outDir, "coreProject_NewFile_nwProject.nwx") testFile = os.path.join(outDir, "coreProject_NewFile_nwProject.nwx")
compFile = os.path.join(refDir, "coreProject_NewFile_nwProject.nwx") compFile = os.path.join(refDir, "coreProject_NewFile_nwProject.nwx")
theProject = NWProject(dummyGUI) theProject = NWProject(mockGUI)
theProject.projTree.setSeed(42) theProject.projTree.setSeed(42)
assert theProject.newProject({"projPath": fncDir}) assert theProject.newProject({"projPath": fncDir})
@@ -323,10 +323,10 @@ def testCoreProject_NewFile(fncDir, outDir, refDir, dummyGUI):
@pytest.mark.core @pytest.mark.core
def testCoreProject_Open(monkeypatch, nwMinimal, dummyGUI): def testCoreProject_Open(monkeypatch, nwMinimal, mockGUI):
"""Test opening a project. """Test opening a project.
""" """
theProject = NWProject(dummyGUI) theProject = NWProject(mockGUI)
# Rename the project file to check handling # Rename the project file to check handling
rName = os.path.join(nwMinimal, nwFiles.PROJ_FILE) rName = os.path.join(nwMinimal, nwFiles.PROJ_FILE)
@@ -382,9 +382,9 @@ def testCoreProject_Open(monkeypatch, nwMinimal, dummyGUI):
"timeStamp=\"2020-01-01 00:00:00\">\n" "timeStamp=\"2020-01-01 00:00:00\">\n"
"</novelWriterXML>\n" "</novelWriterXML>\n"
)) ))
dummyGUI.askResponse = False mockGUI.askResponse = False
assert theProject.openProject(nwMinimal) is False assert theProject.openProject(nwMinimal) is False
dummyGUI.undo() mockGUI.undo()
# Future file version # Future file version
writeFile(rName, ( writeFile(rName, (
@@ -408,9 +408,9 @@ def testCoreProject_Open(monkeypatch, nwMinimal, dummyGUI):
"timeStamp=\"2020-01-01 00:00:00\">\n" "timeStamp=\"2020-01-01 00:00:00\">\n"
"</novelWriterXML>\n" "</novelWriterXML>\n"
)) ))
dummyGUI.askResponse = False mockGUI.askResponse = False
assert theProject.openProject(nwMinimal) is False assert theProject.openProject(nwMinimal) is False
dummyGUI.undo() mockGUI.undo()
# Test skipping XML entries # Test skipping XML entries
writeFile(rName, ( writeFile(rName, (
@@ -436,19 +436,19 @@ def testCoreProject_Open(monkeypatch, nwMinimal, dummyGUI):
writeFile(os.path.join(nwMinimal, "junk"), "stuff") writeFile(os.path.join(nwMinimal, "junk"), "stuff")
os.mkdir(os.path.join(nwMinimal, "data_0")) os.mkdir(os.path.join(nwMinimal, "data_0"))
writeFile(os.path.join(nwMinimal, "data_0", "junk"), "stuff") writeFile(os.path.join(nwMinimal, "data_0", "junk"), "stuff")
dummyGUI.clear() mockGUI.clear()
assert theProject.openProject(nwMinimal) is True assert theProject.openProject(nwMinimal) is True
assert "data_0" in dummyGUI.lastAlert assert "data_0" in mockGUI.lastAlert
assert theProject.closeProject() assert theProject.closeProject()
# END Test testCoreProject_Open # END Test testCoreProject_Open
@pytest.mark.core @pytest.mark.core
def testCoreProject_Save(monkeypatch, nwMinimal, dummyGUI, refDir): def testCoreProject_Save(monkeypatch, nwMinimal, mockGUI, refDir):
"""Test saving a project. """Test saving a project.
""" """
theProject = NWProject(dummyGUI) theProject = NWProject(mockGUI)
testFile = os.path.join(nwMinimal, "nwProject.nwx") testFile = os.path.join(nwMinimal, "nwProject.nwx")
compFile = os.path.join(refDir, os.path.pardir, "minimal", "nwProject.nwx") compFile = os.path.join(refDir, os.path.pardir, "minimal", "nwProject.nwx")
@@ -491,10 +491,10 @@ def testCoreProject_Save(monkeypatch, nwMinimal, dummyGUI, refDir):
@pytest.mark.core @pytest.mark.core
def testCoreProject_LockFile(monkeypatch, fncDir, dummyGUI): def testCoreProject_LockFile(monkeypatch, fncDir, mockGUI):
"""Test lock file functions for the project folder. """Test lock file functions for the project folder.
""" """
theProject = NWProject(dummyGUI) theProject = NWProject(mockGUI)
lockFile = os.path.join(fncDir, nwFiles.PROJ_LOCK) lockFile = os.path.join(fncDir, nwFiles.PROJ_LOCK)
@@ -551,10 +551,10 @@ def testCoreProject_LockFile(monkeypatch, fncDir, dummyGUI):
@pytest.mark.core @pytest.mark.core
def testCoreProject_Helpers(monkeypatch, fncDir, dummyGUI): def testCoreProject_Helpers(monkeypatch, fncDir, mockGUI):
"""Test helper functions for the project folder. """Test helper functions for the project folder.
""" """
theProject = NWProject(dummyGUI) theProject = NWProject(mockGUI)
# No path # No path
assert theProject.ensureFolderStructure() is False assert theProject.ensureFolderStructure() is False
@@ -595,10 +595,10 @@ def testCoreProject_Helpers(monkeypatch, fncDir, dummyGUI):
@pytest.mark.core @pytest.mark.core
def testCoreProject_AccessItems(nwMinimal, dummyGUI): def testCoreProject_AccessItems(nwMinimal, mockGUI):
"""Test helper functions for the project folder. """Test helper functions for the project folder.
""" """
theProject = NWProject(dummyGUI) theProject = NWProject(mockGUI)
theProject.openProject(nwMinimal) theProject.openProject(nwMinimal)
# Move Novel ROOT to after its files # Move Novel ROOT to after its files
@@ -655,10 +655,10 @@ def testCoreProject_AccessItems(nwMinimal, dummyGUI):
@pytest.mark.core @pytest.mark.core
def testCoreProject_Methods(monkeypatch, nwMinimal, dummyGUI, tmpDir): def testCoreProject_Methods(monkeypatch, nwMinimal, mockGUI, tmpDir):
"""Test other project class methods and functions. """Test other project class methods and functions.
""" """
theProject = NWProject(dummyGUI) theProject = NWProject(mockGUI)
theProject.projTree.setSeed(42) theProject.projTree.setSeed(42)
assert theProject.openProject(nwMinimal) assert theProject.openProject(nwMinimal)
assert theProject.projPath == nwMinimal assert theProject.projPath == nwMinimal
@@ -906,13 +906,13 @@ def testCoreProject_Methods(monkeypatch, nwMinimal, dummyGUI, tmpDir):
@pytest.mark.core @pytest.mark.core
def testCoreProject_OrphanedFiles(dummyGUI, nwLipsum): def testCoreProject_OrphanedFiles(mockGUI, nwLipsum):
"""Check that files in the content folder that are not tracked in """Check that files in the content folder that are not tracked in
the project XML file are handled correctly by the orphaned files the project XML file are handled correctly by the orphaned files
function. It should also restore as much meta data as possible from function. It should also restore as much meta data as possible from
the meta line at the top of the document file. the meta line at the top of the document file.
""" """
theProject = NWProject(dummyGUI) theProject = NWProject(mockGUI)
assert theProject.openProject(nwLipsum) assert theProject.openProject(nwLipsum)
assert theProject.projTree["636b6aa9b697b"] is None assert theProject.projTree["636b6aa9b697b"] is None
@@ -920,32 +920,29 @@ def testCoreProject_OrphanedFiles(dummyGUI, nwLipsum):
# First Item with Meta Data # First Item with Meta Data
orphPath = os.path.join(nwLipsum, "content", "636b6aa9b697b.nwd") orphPath = os.path.join(nwLipsum, "content", "636b6aa9b697b.nwd")
with open(orphPath, mode="w", encoding="utf8") as outFile: writeFile(orphPath, (
outFile.write("%%~name:[Recovered] Mars\n") "%%~name:[Recovered] Mars\n"
outFile.write("%%~path:5eaea4e8cdee8/636b6aa9b697b\n") "%%~path:5eaea4e8cdee8/636b6aa9b697b\n"
outFile.write("%%~kind:WORLD/NOTE\n") "%%~kind:WORLD/NOTE\n"
outFile.write("%%~invalid\n") "%%~invalid\n"
outFile.write("\n") "\n"
))
# Second Item without Meta Data # Second Item without Meta Data
orphPath = os.path.join(nwLipsum, "content", "736b6aa9b697b.nwd") orphPath = os.path.join(nwLipsum, "content", "736b6aa9b697b.nwd")
with open(orphPath, mode="w", encoding="utf8") as outFile: writeFile(orphPath, "\n")
outFile.write("\n")
# Invalid File Name # Invalid File Name
dummyPath = os.path.join(nwLipsum, "content", "636b6aa9b697b.txt") tstPath = os.path.join(nwLipsum, "content", "636b6aa9b697b.txt")
with open(dummyPath, mode="w", encoding="utf8") as outFile: writeFile(tstPath, "\n")
outFile.write("\n")
# Invalid File Name # Invalid File Name
dummyPath = os.path.join(nwLipsum, "content", "636b6aa9b697bb.nwd") tstPath = os.path.join(nwLipsum, "content", "636b6aa9b697bb.nwd")
with open(dummyPath, mode="w", encoding="utf8") as outFile: writeFile(tstPath, "\n")
outFile.write("\n")
# Invalid File Name # Invalid File Name
dummyPath = os.path.join(nwLipsum, "content", "abcdefghijklm.nwd") tstPath = os.path.join(nwLipsum, "content", "abcdefghijklm.nwd")
with open(dummyPath, mode="w", encoding="utf8") as outFile: writeFile(tstPath, "\n")
outFile.write("\n")
assert theProject.openProject(nwLipsum) assert theProject.openProject(nwLipsum)
assert theProject.projPath is not None assert theProject.projPath is not None
@@ -983,13 +980,13 @@ def testCoreProject_OrphanedFiles(dummyGUI, nwLipsum):
@pytest.mark.core @pytest.mark.core
def testCoreProject_OldFormat(dummyGUI, nwOldProj): def testCoreProject_OldFormat(mockGUI, nwOldProj):
"""Test that a project folder structure of version 1.0 can be """Test that a project folder structure of version 1.0 can be
converted to the latest folder structure. Version 1.0 split the converted to the latest folder structure. Version 1.0 split the
documents into 'data_0' ... 'data_f' folders, which are now all documents into 'data_0' ... 'data_f' folders, which are now all
contained in a single 'content' folder. contained in a single 'content' folder.
""" """
theProject = NWProject(dummyGUI) theProject = NWProject(mockGUI)
# Create mock files for known legacy files # Create mock files for known legacy files
deleteFiles = [ deleteFiles = [
@@ -1073,11 +1070,11 @@ def testCoreProject_OldFormat(dummyGUI, nwOldProj):
@pytest.mark.core @pytest.mark.core
def testCoreProject_LegacyData(monkeypatch, dummyGUI, fncDir): def testCoreProject_LegacyData(monkeypatch, mockGUI, fncDir):
"""Test the functins that handle legacy data folders and structure """Test the functins that handle legacy data folders and structure
with additional tests of failure handling. with additional tests of failure handling.
""" """
theProject = NWProject(dummyGUI) theProject = NWProject(mockGUI)
theProject.setProjectPath(fncDir) theProject.setProjectPath(fncDir)
# assert theProject.newProject({"projPath": fncDir}) # assert theProject.newProject({"projPath": fncDir})
@@ -1179,21 +1176,21 @@ def testCoreProject_LegacyData(monkeypatch, dummyGUI, fncDir):
@pytest.mark.core @pytest.mark.core
def testCoreProject_Backup(monkeypatch, dummyGUI, nwMinimal, tmpDir): def testCoreProject_Backup(monkeypatch, mockGUI, nwMinimal, tmpDir):
"""Test the automated backup feature of the project class. The test """Test the automated backup feature of the project class. The test
creates a backup of the Minimal test project, and then unzips the creates a backup of the Minimal test project, and then unzips the
backupd file and checks that the project XML file is identical to backupd file and checks that the project XML file is identical to
the original file. the original file.
""" """
theProject = NWProject(dummyGUI) theProject = NWProject(mockGUI)
assert theProject.openProject(nwMinimal) assert theProject.openProject(nwMinimal)
# Test faulty settings # Test faulty settings
# No project # No project
dummyGUI.hasProject = False mockGUI.hasProject = False
assert not theProject.zipIt(doNotify=False) assert not theProject.zipIt(doNotify=False)
dummyGUI.hasProject = True mockGUI.hasProject = True
# Invalid path # Invalid path
theProject.mainConf.backupPath = None theProject.mainConf.backupPath = None
+10 -10
View File
@@ -28,11 +28,11 @@ from nw.core import NWProject, NWIndex, ToHtml
@pytest.mark.core @pytest.mark.core
def testCoreToHtml_Format(dummyGUI): def testCoreToHtml_Format(mockGUI):
"""Test all the formatters for the ToHtml class. """Test all the formatters for the ToHtml class.
""" """
theProject = NWProject(dummyGUI) theProject = NWProject(mockGUI)
dummyGUI.theIndex = NWIndex(theProject) mockGUI.theIndex = NWIndex(theProject)
theHtml = ToHtml(theProject) theHtml = ToHtml(theProject)
# Export Mode # Export Mode
@@ -81,11 +81,11 @@ def testCoreToHtml_Format(dummyGUI):
@pytest.mark.core @pytest.mark.core
def testCoreToHtml_Convert(dummyGUI): def testCoreToHtml_Convert(mockGUI):
"""Test the converter of the ToHtml class. """Test the converter of the ToHtml class.
""" """
theProject = NWProject(dummyGUI) theProject = NWProject(mockGUI)
dummyGUI.theIndex = NWIndex(theProject) mockGUI.theIndex = NWIndex(theProject)
theHtml = ToHtml(theProject) theHtml = ToHtml(theProject)
# Export Mode # Export Mode
@@ -347,10 +347,10 @@ def testCoreToHtml_Convert(dummyGUI):
@pytest.mark.core @pytest.mark.core
def testCoreToHtml_Complex(dummyGUI, fncDir): def testCoreToHtml_Complex(mockGUI, fncDir):
"""Test the ave method of the ToHtml class. """Test the ave method of the ToHtml class.
""" """
theProject = NWProject(dummyGUI) theProject = NWProject(mockGUI)
theHtml = ToHtml(theProject) theHtml = ToHtml(theProject)
# Build Project # Build Project
@@ -421,10 +421,10 @@ def testCoreToHtml_Complex(dummyGUI, fncDir):
@pytest.mark.core @pytest.mark.core
def testCoreToHtml_Methods(dummyGUI): def testCoreToHtml_Methods(mockGUI):
"""Test all the other methods of the ToHtml class. """Test all the other methods of the ToHtml class.
""" """
theProject = NWProject(dummyGUI) theProject = NWProject(mockGUI)
theHtml = ToHtml(theProject) theHtml = ToHtml(theProject)
theHtml.setKeepMarkdown(True) theHtml.setKeepMarkdown(True)
+8 -8
View File
@@ -29,10 +29,10 @@ from nw.core.tokenizer import Tokenizer
@pytest.mark.core @pytest.mark.core
def testCoreToken_Setters(dummyGUI): def testCoreToken_Setters(mockGUI):
"""Test all the setters for the Tokenizer class. """Test all the setters for the Tokenizer class.
""" """
theProject = NWProject(dummyGUI) theProject = NWProject(mockGUI)
theToken = Tokenizer(theProject) theToken = Tokenizer(theProject)
# Verify defaults # Verify defaults
@@ -113,10 +113,10 @@ def testCoreToken_Setters(dummyGUI):
@pytest.mark.core @pytest.mark.core
def testCoreToken_TextOps(monkeypatch, nwMinimal, dummyGUI): def testCoreToken_TextOps(monkeypatch, nwMinimal, mockGUI):
"""Test handling files and text in the Tokenizer class. """Test handling files and text in the Tokenizer class.
""" """
theProject = NWProject(dummyGUI) theProject = NWProject(mockGUI)
theProject.projTree.setSeed(42) theProject.projTree.setSeed(42)
theProject.projLang = "en" theProject.projLang = "en"
theProject._loadProjectLocalisation() theProject._loadProjectLocalisation()
@@ -196,10 +196,10 @@ def testCoreToken_TextOps(monkeypatch, nwMinimal, dummyGUI):
@pytest.mark.core @pytest.mark.core
def testCoreToken_Tokenize(dummyGUI): def testCoreToken_Tokenize(mockGUI):
"""Test the tokenization of the Tokenizer class. """Test the tokenization of the Tokenizer class.
""" """
theProject = NWProject(dummyGUI) theProject = NWProject(mockGUI)
theToken = Tokenizer(theProject) theToken = Tokenizer(theProject)
theToken.setKeepMarkdown(True) theToken.setKeepMarkdown(True)
@@ -498,10 +498,10 @@ def testCoreToken_Tokenize(dummyGUI):
@pytest.mark.core @pytest.mark.core
def testCoreToken_Headers(dummyGUI): def testCoreToken_Headers(mockGUI):
"""Test the header and page parser of the Tokenizer class. """Test the header and page parser of the Tokenizer class.
""" """
theProject = NWProject(dummyGUI) theProject = NWProject(mockGUI)
theProject.projLang = "en" theProject.projLang = "en"
theProject._loadProjectLocalisation() theProject._loadProjectLocalisation()
theToken = Tokenizer(theProject) theToken = Tokenizer(theProject)
+3 -3
View File
@@ -45,11 +45,11 @@ def xmlToText(xElem):
@pytest.mark.core @pytest.mark.core
def testCoreToOdt_Convert(dummyGUI): def testCoreToOdt_Convert(mockGUI):
"""Test the converter of the ToHtml class. """Test the converter of the ToHtml class.
""" """
theProject = NWProject(dummyGUI) theProject = NWProject(mockGUI)
dummyGUI.theIndex = NWIndex(theProject) mockGUI.theIndex = NWIndex(theProject)
theDoc = ToOdt(theProject, isFlat=True) theDoc = ToOdt(theProject, isFlat=True)
# Export Mode # Export Mode
+31 -30
View File
@@ -25,16 +25,18 @@ import pytest
from lxml import etree from lxml import etree
from hashlib import sha256 from hashlib import sha256
from tools import readFile
from nw.core.project import NWProject, NWItem, NWTree from nw.core.project import NWProject, NWItem, NWTree
from nw.enum import nwItemClass, nwItemType, nwItemLayout from nw.enum import nwItemClass, nwItemType, nwItemLayout
from nw.constants import nwFiles from nw.constants import nwFiles
@pytest.fixture(scope="function") @pytest.fixture(scope="function")
def dummyItems(dummyGUI): def dummyItems(mockGUI):
"""Create a list of mock items. """Create a list of mock items.
""" """
theProject = NWProject(dummyGUI) theProject = NWProject(mockGUI)
itemA = NWItem(theProject) itemA = NWItem(theProject)
itemA.itemName = "Novel" itemA.itemName = "Novel"
@@ -108,10 +110,10 @@ def dummyItems(dummyGUI):
@pytest.mark.core @pytest.mark.core
def testCoreTree_BuildTree(dummyGUI, dummyItems): def testCoreTree_BuildTree(mockGUI, dummyItems):
"""Test building a project tree from a list of items. """Test building a project tree from a list of items.
""" """
theProject = NWProject(dummyGUI) theProject = NWProject(mockGUI)
theTree = NWTree(theProject) theTree = NWTree(theProject)
theTree.setSeed(42) theTree.setSeed(42)
@@ -205,10 +207,10 @@ def testCoreTree_BuildTree(dummyGUI, dummyItems):
@pytest.mark.core @pytest.mark.core
def testCoreTree_Methods(dummyGUI, dummyItems): def testCoreTree_Methods(mockGUI, dummyItems):
"""Test bvarious class methods. """Test bvarious class methods.
""" """
theProject = NWProject(dummyGUI) theProject = NWProject(mockGUI)
theTree = NWTree(theProject) theTree = NWTree(theProject)
for tHandle, pHandle, nwItem in dummyItems: for tHandle, pHandle, nwItem in dummyItems:
@@ -262,10 +264,10 @@ def testCoreTree_Methods(dummyGUI, dummyItems):
@pytest.mark.core @pytest.mark.core
def testCoreTree_UpdateItemLayout(dummyGUI, dummyItems): def testCoreTree_UpdateItemLayout(mockGUI, dummyItems):
"""Test building a project tree from a list of items. """Test building a project tree from a list of items.
""" """
theProject = NWProject(dummyGUI) theProject = NWProject(mockGUI)
theTree = NWTree(theProject) theTree = NWTree(theProject)
for tHandle, pHandle, nwItem in dummyItems: for tHandle, pHandle, nwItem in dummyItems:
@@ -388,10 +390,10 @@ def testCoreTree_UpdateItemLayout(dummyGUI, dummyItems):
@pytest.mark.core @pytest.mark.core
def testCoreTree_MakeHandles(monkeypatch, dummyGUI): def testCoreTree_MakeHandles(monkeypatch, mockGUI):
"""Test generating item handles. """Test generating item handles.
""" """
theProject = NWProject(dummyGUI) theProject = NWProject(mockGUI)
theTree = NWTree(theProject) theTree = NWTree(theProject)
theTree.setSeed(42) theTree.setSeed(42)
@@ -431,10 +433,10 @@ def testCoreTree_MakeHandles(monkeypatch, dummyGUI):
@pytest.mark.core @pytest.mark.core
def testCoreTree_Stats(dummyGUI, dummyItems): def testCoreTree_Stats(mockGUI, dummyItems):
"""Test project stats methods. """Test project stats methods.
""" """
theProject = NWProject(dummyGUI) theProject = NWProject(mockGUI)
theTree = NWTree(theProject) theTree = NWTree(theProject)
for tHandle, pHandle, nwItem in dummyItems: for tHandle, pHandle, nwItem in dummyItems:
@@ -458,10 +460,10 @@ def testCoreTree_Stats(dummyGUI, dummyItems):
@pytest.mark.core @pytest.mark.core
def testCoreTree_Reorder(dummyGUI, dummyItems): def testCoreTree_Reorder(mockGUI, dummyItems):
"""Test changing tree order. """Test changing tree order.
""" """
theProject = NWProject(dummyGUI) theProject = NWProject(mockGUI)
theTree = NWTree(theProject) theTree = NWTree(theProject)
aHandle = [] aHandle = []
@@ -490,10 +492,10 @@ def testCoreTree_Reorder(dummyGUI, dummyItems):
@pytest.mark.core @pytest.mark.core
def testCoreTree_XMLPackUnpack(dummyGUI, dummyItems): def testCoreTree_XMLPackUnpack(mockGUI, dummyItems):
"""Test packing and unpacking the tree to and from XML. """Test packing and unpacking the tree to and from XML.
""" """
theProject = NWProject(dummyGUI) theProject = NWProject(mockGUI)
theTree = NWTree(theProject) theTree = NWTree(theProject)
for tHandle, pHandle, nwItem in dummyItems: for tHandle, pHandle, nwItem in dummyItems:
@@ -546,10 +548,10 @@ def testCoreTree_XMLPackUnpack(dummyGUI, dummyItems):
@pytest.mark.core @pytest.mark.core
def testCoreTree_ToCFile(monkeypatch, dummyGUI, dummyItems, tmpDir): def testCoreTree_ToCFile(monkeypatch, mockGUI, dummyItems, tmpDir):
"""Test writing the ToC.txt file. """Test writing the ToC.txt file.
""" """
theProject = NWProject(dummyGUI) theProject = NWProject(mockGUI)
theTree = NWTree(theProject) theTree = NWTree(theProject)
for tHandle, pHandle, nwItem in dummyItems: for tHandle, pHandle, nwItem in dummyItems:
@@ -579,17 +581,16 @@ def testCoreTree_ToCFile(monkeypatch, dummyGUI, dummyItems, tmpDir):
pathB = os.path.join("content", "c000000000002.nwd") pathB = os.path.join("content", "c000000000002.nwd")
pathC = os.path.join("content", "b000000000002.nwd") pathC = os.path.join("content", "b000000000002.nwd")
with open(os.path.join(tmpDir, nwFiles.TOC_TXT), mode="r", encoding="utf8") as inFile: assert readFile(os.path.join(tmpDir, nwFiles.TOC_TXT)) == (
assert inFile.read() == ( "\n"
"\n" "Table of Contents\n"
"Table of Contents\n" "=================\n"
"=================\n" "\n"
"\n" "File Name Class Layout Document Label\n"
"File Name Class Layout Document Label\n" "-------------------------------------------------------------\n"
"-------------------------------------------------------------\n" f"{pathA} NOVEL CHAPTER Chapter One\n"
f"{pathA} NOVEL CHAPTER Chapter One\n" f"{pathB} NOVEL SCENE Scene One\n"
f"{pathB} NOVEL SCENE Scene One\n" f"{pathC} CHARACTER NOTE Jane Doe\n"
f"{pathC} CHARACTER NOTE Jane Doe\n" )
)
# END Test testCoreTree_ToCFile # END Test testCoreTree_ToCFile
+3 -2
View File
@@ -26,6 +26,8 @@ from PyQt5.QtCore import Qt
from PyQt5.QtGui import QTextCursor, QTextBlock from PyQt5.QtGui import QTextCursor, QTextBlock
from PyQt5.QtWidgets import QAction, QFileDialog, QMessageBox from PyQt5.QtWidgets import QAction, QFileDialog, QMessageBox
from tools import writeFile
from nw.gui.doceditor import GuiDocEditor from nw.gui.doceditor import GuiDocEditor
from nw.enum import nwDocAction, nwDocInsert, nwWidget from nw.enum import nwDocAction, nwDocInsert, nwWidget
from nw.constants import nwKeyWords, nwUnicode from nw.constants import nwKeyWords, nwUnicode
@@ -659,8 +661,7 @@ def testGuiMenu_Insert(qtbot, monkeypatch, nwGUI, fncDir, fncProj):
assert not nwGUI.importDocument() assert not nwGUI.importDocument()
# Create the file and try again, but with no target document open # Create the file and try again, but with no target document open
with open(theFile, mode="w+", encoding="utf8") as outFile: writeFile(theFile, "Foo")
outFile.write("Foo")
assert not nwGUI.importDocument() assert not nwGUI.importDocument()
# Open the document from before, and add some text to it # Open the document from before, and add some text to it
+1 -1
View File
@@ -54,7 +54,7 @@ def testGuiOutline_Main(qtbot, monkeypatch, nwGUI, nwLipsum):
qtbot.mouseClick(nwGUI.projView, Qt.LeftButton) qtbot.mouseClick(nwGUI.projView, Qt.LeftButton)
nwGUI.projView._loadHeaderState() nwGUI.projView._loadHeaderState()
assert not nwGUI.projView.colHidden[nwOutline.CCOUNT] assert not nwGUI.projView._colHidden[nwOutline.CCOUNT]
# First Item # First Item
nwGUI.rebuildOutline() nwGUI.rebuildOutline()
+4 -4
View File
@@ -32,13 +32,13 @@ def cmpFiles(fileOne, fileTwo, ignoreLines=None):
ignoreLines = [] ignoreLines = []
try: try:
foOne = open(fileOne, mode="r", encoding="utf8") foOne = open(fileOne, mode="r", encoding="utf-8")
except Exception as e: except Exception as e:
print(str(e)) print(str(e))
return False return False
try: try:
foTwo = open(fileTwo, mode="r", encoding="utf8") foTwo = open(fileTwo, mode="r", encoding="utf-8")
except Exception as e: except Exception as e:
print(str(e)) print(str(e))
return False return False
@@ -83,14 +83,14 @@ def getGuiItem(theName):
def readFile(fileName): def readFile(fileName):
"""Returns the content of a file as a string. """Returns the content of a file as a string.
""" """
with open(fileName, mode="r", encoding="utf8") as inFile: with open(fileName, mode="r", encoding="utf-8") as inFile:
return inFile.read() return inFile.read()
def writeFile(fileName, fileData): def writeFile(fileName, fileData):
"""Write the contents of a string to a file. """Write the contents of a string to a file.
""" """
with open(fileName, mode="w", encoding="utf8") as outFile: with open(fileName, mode="w", encoding="utf-8") as outFile:
outFile.write(fileData) outFile.write(fileData)