+4
-6
@@ -14,12 +14,10 @@ docs/source/_*
|
|||||||
__pycache__
|
__pycache__
|
||||||
|
|
||||||
# Sample Project
|
# Sample Project
|
||||||
sample/**/cache
|
sample/cache
|
||||||
sample/**/wordlist.txt
|
sample/meta
|
||||||
sample/**/sessionInfo.log
|
sample/*.bak
|
||||||
sample/**/*.bak
|
sample/*.lock
|
||||||
sample/**/*.json
|
|
||||||
sample/**/*.lock
|
|
||||||
|
|
||||||
# PyTest
|
# PyTest
|
||||||
tests/temp
|
tests/temp
|
||||||
|
|||||||
+22
-1
@@ -1,5 +1,25 @@
|
|||||||
# novelWriter ChangeLog
|
# novelWriter ChangeLog
|
||||||
|
|
||||||
|
## Version 0.7 RC1 [2020-xx-xx]
|
||||||
|
|
||||||
|
**User Interface**
|
||||||
|
|
||||||
|
* The back-references list now shows references to any tag in the open document, not just the first tag. Issue #227, PR #234.
|
||||||
|
* Clicking a tag now tries to scroll to the header where the tag is set. The index needed a couple of minor changes for this feature, so this will invalidate the old index for a project, and require a new to be built. This is done automatically. PR #234.
|
||||||
|
* Moved the Close button on the "Build Novel project" dialog to the area with the other buttons since we anyway increased the size of that area. PR #256.
|
||||||
|
|
||||||
|
**Project Structure**
|
||||||
|
|
||||||
|
* The project folder structure has been simplified and cleaned up. We also now freeze the main entry values in the main XML file. The XML file is now given version 1.1, and no further core changes to its structure will be made without bumping this version. We're also locking it to only be opened by version 0.7 or later. An old project file is converted on first open. PRs #253 and #261.
|
||||||
|
* When a project is closed, two table of contents files are written to the project folder. They are named `ToC.txt` and `ToC.json` and are there for the user's convenience if they want to find a specific file from the project in the data folders. As discussed in Issue #259, PR #261.
|
||||||
|
* The expanded node flag from the project tree was also saved for file entries, which cannot actually be expanded. These flags are no longer saved in the XML file. PR #261.
|
||||||
|
|
||||||
|
**Other Changes**
|
||||||
|
|
||||||
|
* Dropped the usage of .bak copies of document files. This was the old method to ensure the document data was written successfully, but it uses twice the storage space. Instead, writing via a temp file is the safe way to save files. PR #248.
|
||||||
|
* The project class now records the accumulated time in seconds a project has been opened. This data is not yet displayed anywhere, but it is being tracked in the project XML file. PR #261.
|
||||||
|
|
||||||
|
|
||||||
## Version 0.6.4 [2020-xx-xx]
|
## Version 0.6.4 [2020-xx-xx]
|
||||||
|
|
||||||
**User Interface**
|
**User Interface**
|
||||||
@@ -27,7 +47,8 @@
|
|||||||
|
|
||||||
## Version 0.6.2 [2020-05-28]
|
## Version 0.6.2 [2020-05-28]
|
||||||
|
|
||||||
* Botched release. Replaced with 0.6.3. Crashes when Build Novel project is opened.
|
* Botched release. Replaced with 0.6.3. Crashes when Build Novel Project is opened.
|
||||||
|
|
||||||
|
|
||||||
## Version 0.6.1 [2020-05-25]
|
## Version 0.6.1 [2020-05-25]
|
||||||
|
|
||||||
|
|||||||
+2
-2
@@ -24,9 +24,9 @@ copyright = "2018-2020, Veronica Berglyd Olsen"
|
|||||||
author = "Veronica Berglyd Olsen"
|
author = "Veronica Berglyd Olsen"
|
||||||
|
|
||||||
# The short X.Y version
|
# The short X.Y version
|
||||||
version = "0.6.3"
|
version = "0.7.0"
|
||||||
# The full version, including alpha/beta/rc tags
|
# The full version, including alpha/beta/rc tags
|
||||||
release = "0.6.3"
|
release = "0.7.0rc1"
|
||||||
|
|
||||||
|
|
||||||
# -- General configuration ---------------------------------------------------
|
# -- General configuration ---------------------------------------------------
|
||||||
|
|||||||
@@ -30,9 +30,9 @@ The project XML file is indent-formatted, suitable for diff tools and version co
|
|||||||
Project Documents
|
Project Documents
|
||||||
-----------------
|
-----------------
|
||||||
|
|
||||||
The project documents are saved in folders starting with ``data_``.
|
The project documents are saved in a folder in the main project folder named ``content``.
|
||||||
Each document has a file handle taken from the first 13 characters of a SHA256 hash of the system time when the file was first created.
|
Each document has a file handle taken from the first 13 characters of a SHA256 hash of the system time when the file was first created.
|
||||||
The documents are saved with a folder and filename derived from this hash.
|
The documents are saved with a filename assembled from this hash and the file extension ``.nwd``.
|
||||||
If you wish to find the physical location of a file in the project, you can either look it up in the project XML file, or select :menuselection:`Document --> Show File Details` in the menu when having the document open.
|
If you wish to find the physical location of a file in the project, you can either look it up in the project XML file, or select :menuselection:`Document --> Show File Details` in the menu when having the document open.
|
||||||
|
|
||||||
The reason for this cryptic file naming is to avoid issues with file naming conventions and restrictions on different operating systems, and also to have a file name that does not depend on what the user names the files, or changes it to.
|
The reason for this cryptic file naming is to avoid issues with file naming conventions and restrictions on different operating systems, and also to have a file name that does not depend on what the user names the files, or changes it to.
|
||||||
|
|||||||
+2
-2
@@ -40,8 +40,8 @@ __package__ = "novelWriter"
|
|||||||
__author__ = "Veronica Berglyd Olsen"
|
__author__ = "Veronica Berglyd Olsen"
|
||||||
__copyright__ = "Copyright 2018–2020, Veronica Berglyd Olsen"
|
__copyright__ = "Copyright 2018–2020, Veronica Berglyd Olsen"
|
||||||
__license__ = "GPLv3"
|
__license__ = "GPLv3"
|
||||||
__version__ = "0.6.3"
|
__version__ = "0.7.0rc1"
|
||||||
__hexversion__ = "0x000603f0"
|
__hexversion__ = "0x000700c1"
|
||||||
__date__ = "2020-05-28"
|
__date__ = "2020-05-28"
|
||||||
__maintainer__ = "Veronica Berglyd Olsen"
|
__maintainer__ = "Veronica Berglyd Olsen"
|
||||||
__email__ = "code@vkbo.net"
|
__email__ = "code@vkbo.net"
|
||||||
|
|||||||
+3
-1
@@ -533,7 +533,9 @@ class Config:
|
|||||||
|
|
||||||
# Write config file
|
# Write config file
|
||||||
try:
|
try:
|
||||||
cnfParse.write(open(path.join(self.confPath,self.confFile),mode="w",encoding="utf8"))
|
cnfParse.write(
|
||||||
|
open(path.join(self.confPath, self.confFile), mode="w", encoding="utf8")
|
||||||
|
)
|
||||||
self.confChanged = False
|
self.confChanged = False
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error("Could not save config file")
|
logger.error("Could not save config file")
|
||||||
|
|||||||
@@ -39,6 +39,8 @@ class nwFiles():
|
|||||||
PROJ_FILE = "nwProject.nwx"
|
PROJ_FILE = "nwProject.nwx"
|
||||||
PROJ_DICT = "wordlist.txt"
|
PROJ_DICT = "wordlist.txt"
|
||||||
PROJ_LOCK = "nwProject.lock"
|
PROJ_LOCK = "nwProject.lock"
|
||||||
|
TOC_TXT = "ToC.txt"
|
||||||
|
TOC_JSON = "ToC.json"
|
||||||
SESS_INFO = "sessionInfo.log"
|
SESS_INFO = "sessionInfo.log"
|
||||||
INDEX_FILE = "tagsIndex.json"
|
INDEX_FILE = "tagsIndex.json"
|
||||||
OPTS_FILE = "guiOptions.json"
|
OPTS_FILE = "guiOptions.json"
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ from nw.core.spellcheck import NWSpellSimple
|
|||||||
from nw.core.tokenizer import Tokenizer
|
from nw.core.tokenizer import Tokenizer
|
||||||
from nw.core.tohtml import ToHtml
|
from nw.core.tohtml import ToHtml
|
||||||
from nw.core.tools import countWords
|
from nw.core.tools import countWords
|
||||||
from nw.core.tools import projectMaintenance
|
|
||||||
from nw.core.tools import numberToWord
|
from nw.core.tools import numberToWord
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
@@ -22,6 +21,5 @@ __all__ = [
|
|||||||
"Tokenizer",
|
"Tokenizer",
|
||||||
"ToHtml",
|
"ToHtml",
|
||||||
"countWords",
|
"countWords",
|
||||||
"projectMaintenance",
|
|
||||||
"numberToWord",
|
"numberToWord",
|
||||||
]
|
]
|
||||||
|
|||||||
+24
-44
@@ -37,8 +37,6 @@ logger = logging.getLogger(__name__)
|
|||||||
|
|
||||||
class NWDoc():
|
class NWDoc():
|
||||||
|
|
||||||
FILE_MN = "main.nwd"
|
|
||||||
|
|
||||||
def __init__(self, theProject, theParent):
|
def __init__(self, theProject, theParent):
|
||||||
|
|
||||||
self.mainConf = nw.CONFIG
|
self.mainConf = nw.CONFIG
|
||||||
@@ -93,17 +91,17 @@ class NWDoc():
|
|||||||
if self.theItem.parHandle == self.theProject.projTree.trashRoot():
|
if self.theItem.parHandle == self.theProject.projTree.trashRoot():
|
||||||
self.docEditable = False
|
self.docEditable = False
|
||||||
|
|
||||||
docDir, docFile = self._assemblePath(self.docHandle, self.FILE_MN)
|
docFile = self.docHandle+".nwd"
|
||||||
self.fileLoc = path.join(docDir,docFile)
|
logger.debug("Opening document %s" % docFile)
|
||||||
logger.debug("Opening document %s" % self.fileLoc)
|
|
||||||
dataDir = path.join(self.theProject.projPath, docDir)
|
docPath = path.join(self.theProject.projContent, docFile)
|
||||||
docPath = path.join(dataDir, docFile)
|
self.fileLoc = docPath
|
||||||
|
|
||||||
theText = ""
|
theText = ""
|
||||||
self.docMeta = ""
|
self.docMeta = ""
|
||||||
if path.isfile(docPath):
|
if path.isfile(docPath):
|
||||||
try:
|
try:
|
||||||
with open(docPath,mode="r",encoding="utf8") as inFile:
|
with open(docPath, mode="r", encoding="utf8") as inFile:
|
||||||
fstLine = inFile.readline()
|
fstLine = inFile.readline()
|
||||||
if fstLine.startswith("%%~ "):
|
if fstLine.startswith("%%~ "):
|
||||||
# This is the meta line
|
# This is the meta line
|
||||||
@@ -113,7 +111,7 @@ class NWDoc():
|
|||||||
theText += inFile.read()
|
theText += inFile.read()
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.makeAlert(["Failed to open document file.",str(e)], nwAlert.ERROR)
|
self.makeAlert(["Failed to open document file.", str(e)], nwAlert.ERROR)
|
||||||
# Note: Document must be cleared in case of an io error,
|
# Note: Document must be cleared in case of an io error,
|
||||||
# or else the auto-save or save will try to overwrite it
|
# or else the auto-save or save will try to overwrite it
|
||||||
# with an empty file. Return None to alert the caller.
|
# with an empty file. Return None to alert the caller.
|
||||||
@@ -139,34 +137,29 @@ class NWDoc():
|
|||||||
if self.docHandle is None or not self.docEditable:
|
if self.docHandle is None or not self.docEditable:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
docDir, docFile = self._assemblePath(self.docHandle, self.FILE_MN)
|
self.theProject.ensureFolderStructure()
|
||||||
logger.debug("Saving document %s" % path.join(docDir,docFile))
|
|
||||||
dataPath = path.join(self.theProject.projPath, docDir)
|
|
||||||
docPath = path.join(dataPath, docFile)
|
|
||||||
if not path.isdir(dataPath):
|
|
||||||
mkdir(dataPath)
|
|
||||||
logger.debug("Created folder %s" % dataPath)
|
|
||||||
|
|
||||||
docTemp = path.join(dataPath, docFile+"~")
|
docFile = self.docHandle+".nwd"
|
||||||
docBack = path.join(dataPath, docFile[:-3]+"bak")
|
logger.debug("Saving document %s" % docFile)
|
||||||
|
|
||||||
|
docPath = path.join(self.theProject.projContent, docFile)
|
||||||
|
docTemp = path.join(self.theProject.projContent, docFile+"~")
|
||||||
|
|
||||||
itemPath = self.theProject.projTree.getItemPath(self.docHandle)
|
itemPath = self.theProject.projTree.getItemPath(self.docHandle)
|
||||||
docMeta = "%%~ "+":".join(itemPath)+":"+self.theItem.itemName+"\n"
|
docMeta = "%%~ "+":".join(itemPath)+":"+self.theItem.itemName+"\n"
|
||||||
|
|
||||||
try:
|
try:
|
||||||
with open(docTemp,mode="w",encoding="utf8") as outFile:
|
with open(docTemp, mode="w", encoding="utf8") as outFile:
|
||||||
outFile.write(docMeta)
|
outFile.write(docMeta)
|
||||||
outFile.write(docText)
|
outFile.write(docText)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.makeAlert(["Could not save document.",str(e)], nwAlert.ERROR)
|
self.makeAlert(["Could not save document.", str(e)], nwAlert.ERROR)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# If we're here, the file was successfully saved,
|
# If we're here, the file was successfully saved, so we can
|
||||||
# so let's sort out the temps and backups
|
# replace the temp file with the actual file
|
||||||
if path.isfile(docBack):
|
|
||||||
unlink(docBack)
|
|
||||||
if path.isfile(docPath):
|
if path.isfile(docPath):
|
||||||
rename(docPath, docBack)
|
unlink(docPath)
|
||||||
rename(docTemp, docPath)
|
rename(docTemp, docPath)
|
||||||
|
|
||||||
self.theParent.statusBar.setStatus("Saved Document: %s" % self.theItem.itemName)
|
self.theParent.statusBar.setStatus("Saved Document: %s" % self.theItem.itemName)
|
||||||
@@ -177,12 +170,12 @@ class NWDoc():
|
|||||||
"""Permanently delete a document source file and its backups
|
"""Permanently delete a document source file and its backups
|
||||||
from the project data folder.
|
from the project data folder.
|
||||||
"""
|
"""
|
||||||
docDir, docFile = self._assemblePath(tHandle, self.FILE_MN)
|
docFile = self.docHandle+".nwd"
|
||||||
dataPath = path.join(self.theProject.projPath, docDir)
|
|
||||||
chkList = []
|
chkList = []
|
||||||
chkList.append(path.join(dataPath, docFile))
|
chkList.append(path.join(self.theProject.projContent, docFile))
|
||||||
chkList.append(path.join(dataPath, docFile+"~"))
|
chkList.append(path.join(self.theProject.projContent, docFile+"~"))
|
||||||
chkList.append(path.join(dataPath, docFile[:-3]+"bak"))
|
|
||||||
for chkFile in chkList:
|
for chkFile in chkList:
|
||||||
if path.isfile(chkFile):
|
if path.isfile(chkFile):
|
||||||
try:
|
try:
|
||||||
@@ -191,6 +184,7 @@ class NWDoc():
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.makeAlert(["Could not delete document file.",str(e)], nwAlert.ERROR)
|
self.makeAlert(["Could not delete document file.",str(e)], nwAlert.ERROR)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
##
|
##
|
||||||
@@ -224,18 +218,4 @@ class NWDoc():
|
|||||||
|
|
||||||
return theMeta, thePath
|
return theMeta, thePath
|
||||||
|
|
||||||
##
|
|
||||||
# Internal Functions
|
|
||||||
##
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def _assemblePath(tHandle, docExt):
|
|
||||||
"""Assemble the file path for a given handle.
|
|
||||||
"""
|
|
||||||
if tHandle is None:
|
|
||||||
return None, None
|
|
||||||
docDir = "data_"+tHandle[0]
|
|
||||||
docFile = tHandle[1:13]+"_"+docExt
|
|
||||||
return docDir, docFile
|
|
||||||
|
|
||||||
# END Class NWDoc
|
# END Class NWDoc
|
||||||
|
|||||||
+17
-17
@@ -199,7 +199,7 @@ class NWIndex():
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
for tTag in self.tagIndex:
|
for tTag in self.tagIndex:
|
||||||
if len(self.tagIndex[tTag]) != 3:
|
if len(self.tagIndex[tTag]) != 4:
|
||||||
self.indexBroken = True
|
self.indexBroken = True
|
||||||
|
|
||||||
for tHandle in self.refIndex:
|
for tHandle in self.refIndex:
|
||||||
@@ -228,7 +228,7 @@ class NWIndex():
|
|||||||
if self.indexBroken:
|
if self.indexBroken:
|
||||||
self.clearIndex()
|
self.clearIndex()
|
||||||
self.theParent.makeAlert(
|
self.theParent.makeAlert(
|
||||||
"The index loaded from project cache contains errors. Rebuilding index.",
|
"The project index is outdated or broken. Rebuilding index.",
|
||||||
nwAlert.WARN
|
nwAlert.WARN
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -260,9 +260,9 @@ class NWIndex():
|
|||||||
logger.debug("Indexing item with handle %s" % tHandle)
|
logger.debug("Indexing item with handle %s" % tHandle)
|
||||||
|
|
||||||
# Check file type, and reset its old index
|
# Check file type, and reset its old index
|
||||||
# Also add a dummy entry for T0 in case the file has no title
|
# Also add a dummy entry T000000 in case the file has no title
|
||||||
self.refIndex[tHandle] = {}
|
self.refIndex[tHandle] = {}
|
||||||
self.refIndex[tHandle]["T0"] = {
|
self.refIndex[tHandle]["T000000"] = {
|
||||||
"tags" : [],
|
"tags" : [],
|
||||||
"updated" : time(),
|
"updated" : time(),
|
||||||
}
|
}
|
||||||
@@ -301,7 +301,7 @@ class NWIndex():
|
|||||||
|
|
||||||
elif aLine.startswith(r"@"):
|
elif aLine.startswith(r"@"):
|
||||||
self._indexNoteRef(tHandle, aLine, nLine, nTitle)
|
self._indexNoteRef(tHandle, aLine, nLine, nTitle)
|
||||||
self._indexTag(tHandle, aLine, nLine, itemClass)
|
self._indexTag(tHandle, aLine, nLine, nTitle, itemClass)
|
||||||
|
|
||||||
elif aLine.startswith(r"%"):
|
elif aLine.startswith(r"%"):
|
||||||
if nTitle > 0:
|
if nTitle > 0:
|
||||||
@@ -436,7 +436,7 @@ class NWIndex():
|
|||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def _indexTag(self, tHandle, aLine, nLine, itemClass):
|
def _indexTag(self, tHandle, aLine, nLine, nTitle, itemClass):
|
||||||
"""Validate and save the information from a tag.
|
"""Validate and save the information from a tag.
|
||||||
"""
|
"""
|
||||||
isValid, theBits, thePos = self.scanThis(aLine)
|
isValid, theBits, thePos = self.scanThis(aLine)
|
||||||
@@ -444,7 +444,8 @@ class NWIndex():
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
if theBits[0] == nwKeyWords.TAG_KEY:
|
if theBits[0] == nwKeyWords.TAG_KEY:
|
||||||
self.tagIndex[theBits[1]] = [nLine, tHandle, itemClass.name]
|
sTitle = "T%06d" % nTitle
|
||||||
|
self.tagIndex[theBits[1]] = [nLine, tHandle, itemClass.name, sTitle]
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
@@ -606,18 +607,17 @@ class NWIndex():
|
|||||||
if tHandle is None:
|
if tHandle is None:
|
||||||
return theRefs
|
return theRefs
|
||||||
|
|
||||||
theTag = None
|
theTags = set()
|
||||||
for tTag in self.tagIndex:
|
for tTag in self.tagIndex:
|
||||||
if tHandle == self.tagIndex[tTag][1]:
|
if tHandle == self.tagIndex[tTag][1]:
|
||||||
theTag = tTag
|
theTags.add(tTag)
|
||||||
break
|
|
||||||
|
|
||||||
if theTag is not None:
|
if theTags:
|
||||||
for tHandle in self.refIndex:
|
for tHandle in self.refIndex:
|
||||||
for sTitle in self.refIndex[tHandle]:
|
for sTitle in self.refIndex[tHandle]:
|
||||||
for nLine, tKey, tTag in self.refIndex[tHandle][sTitle]["tags"]:
|
for _, _, tTag in self.refIndex[tHandle][sTitle]["tags"]:
|
||||||
if tTag == theTag:
|
if tTag in theTags and tHandle not in theRefs:
|
||||||
theRefs[tHandle] = nLine
|
theRefs[tHandle] = sTitle
|
||||||
|
|
||||||
return theRefs
|
return theRefs
|
||||||
|
|
||||||
@@ -626,8 +626,8 @@ class NWIndex():
|
|||||||
"""
|
"""
|
||||||
if theTag in self.tagIndex:
|
if theTag in self.tagIndex:
|
||||||
theRef = self.tagIndex[theTag]
|
theRef = self.tagIndex[theTag]
|
||||||
if len(theRef) == 3:
|
if len(theRef) == 4:
|
||||||
return theRef[1], theRef[0]
|
return theRef[1], theRef[0], theRef[3]
|
||||||
return None, 0
|
return None, 0, "T000000"
|
||||||
|
|
||||||
# END Class NWIndex
|
# END Class NWIndex
|
||||||
|
|||||||
+281
-53
@@ -31,9 +31,10 @@
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
import json
|
||||||
import nw
|
import nw
|
||||||
|
|
||||||
from os import path, mkdir, listdir, unlink, rename
|
from os import path, mkdir, listdir, unlink, rename, rmdir
|
||||||
from lxml import etree
|
from lxml import etree
|
||||||
from hashlib import sha256
|
from hashlib import sha256
|
||||||
from time import time
|
from time import time
|
||||||
@@ -42,7 +43,6 @@ from shutil import make_archive
|
|||||||
from PyQt5.QtWidgets import QMessageBox
|
from PyQt5.QtWidgets import QMessageBox
|
||||||
|
|
||||||
from nw.gui.tools import OptionState
|
from nw.gui.tools import OptionState
|
||||||
from nw.core.tools import projectMaintenance
|
|
||||||
from nw.core.document import NWDoc
|
from nw.core.document import NWDoc
|
||||||
from nw.common import checkString, checkBool, checkInt, formatTimeStamp
|
from nw.common import checkString, checkBool, checkInt, formatTimeStamp
|
||||||
from nw.constants import (
|
from nw.constants import (
|
||||||
@@ -70,12 +70,15 @@ class NWProject():
|
|||||||
self.lockedBy = None # Data on which computer has the project open
|
self.lockedBy = None # Data on which computer has the project open
|
||||||
self.saveCount = 0 # Meta data: number of saves
|
self.saveCount = 0 # Meta data: number of saves
|
||||||
self.autoCount = 0 # Meta data: number of automatic saves
|
self.autoCount = 0 # Meta data: number of automatic saves
|
||||||
|
self.editTime = 0 # The accumulated edit time read from the project file
|
||||||
|
|
||||||
# Class Settings
|
# Class Settings
|
||||||
self.projPath = None # The full path to where the currently open project is saved
|
self.projPath = None # The full path to where the currently open project is saved
|
||||||
self.projMeta = None # The full path to the project's meta data folder
|
self.projMeta = None # The full path to the project's meta data folder
|
||||||
self.projDict = None # The spell check dictionary
|
self.projCache = None # The full path to the project's cache folder
|
||||||
self.projFile = None # The file name of the project main XML file
|
self.projContent = None # The full path to the project's content folder
|
||||||
|
self.projDict = None # The spell check dictionary
|
||||||
|
self.projFile = None # The file name of the project main XML file
|
||||||
|
|
||||||
# Project Meta
|
# Project Meta
|
||||||
self.projName = "" # Project name (working title)
|
self.projName = "" # Project name (working title)
|
||||||
@@ -112,7 +115,7 @@ class NWProject():
|
|||||||
CUSTOM, and always have parent handle set to None.
|
CUSTOM, and always have parent handle set to None.
|
||||||
"""
|
"""
|
||||||
if not self.projTree.checkRootUnique(rootClass):
|
if not self.projTree.checkRootUnique(rootClass):
|
||||||
self.makeAlert("Duplicate root item detected!", nwAlert.ERROR)
|
self.makeAlert("Duplicate root item detected.", nwAlert.ERROR)
|
||||||
return None
|
return None
|
||||||
newItem = NWItem(self)
|
newItem = NWItem(self)
|
||||||
newItem.setName(rootName)
|
newItem.setName(rootName)
|
||||||
@@ -195,6 +198,8 @@ class NWProject():
|
|||||||
# Project Settings
|
# Project Settings
|
||||||
self.projPath = None
|
self.projPath = None
|
||||||
self.projMeta = None
|
self.projMeta = None
|
||||||
|
self.projCache = None
|
||||||
|
self.projContent = None
|
||||||
self.projDict = None
|
self.projDict = None
|
||||||
self.projFile = nwFiles.PROJ_FILE
|
self.projFile = nwFiles.PROJ_FILE
|
||||||
self.projName = ""
|
self.projName = ""
|
||||||
@@ -203,7 +208,7 @@ class NWProject():
|
|||||||
self.autoReplace = {}
|
self.autoReplace = {}
|
||||||
self.titleFormat = {
|
self.titleFormat = {
|
||||||
"title" : r"%title%",
|
"title" : r"%title%",
|
||||||
"chapter" : r"Chapter %num%\\%title%",
|
"chapter" : r"Chapter %ch%: %title%",
|
||||||
"unnumbered" : r"%title%",
|
"unnumbered" : r"%title%",
|
||||||
"scene" : r"* * *",
|
"scene" : r"* * *",
|
||||||
"section" : r"",
|
"section" : r"",
|
||||||
@@ -246,11 +251,30 @@ class NWProject():
|
|||||||
self.projPath = path.abspath(path.dirname(fileName))
|
self.projPath = path.abspath(path.dirname(fileName))
|
||||||
logger.debug("Opening project: %s" % self.projPath)
|
logger.debug("Opening project: %s" % self.projPath)
|
||||||
|
|
||||||
self.projMeta = path.join(self.projPath,"meta")
|
# Standard Folders and Files
|
||||||
|
# ==========================
|
||||||
|
|
||||||
|
if not self.ensureFolderStructure():
|
||||||
|
return False
|
||||||
|
|
||||||
self.projDict = path.join(self.projMeta, nwFiles.PROJ_DICT)
|
self.projDict = path.join(self.projMeta, nwFiles.PROJ_DICT)
|
||||||
|
|
||||||
if not self._checkFolder(self.projMeta):
|
# Check for Old Legacy Data
|
||||||
return False
|
# =========================
|
||||||
|
|
||||||
|
errList = []
|
||||||
|
for projItem in listdir(self.projPath):
|
||||||
|
logger.verbose("Project contains: %s" % projItem)
|
||||||
|
if projItem.startswith("data_"):
|
||||||
|
self._legacyDataFolder(projItem)
|
||||||
|
|
||||||
|
if errList:
|
||||||
|
self.makeAlert(errList, nwAlert.ERROR)
|
||||||
|
|
||||||
|
self._deprecatedFiles()
|
||||||
|
|
||||||
|
# Project Lock
|
||||||
|
# ============
|
||||||
|
|
||||||
if overrideLock:
|
if overrideLock:
|
||||||
self._clearLockFile()
|
self._clearLockFile()
|
||||||
@@ -267,10 +291,8 @@ class NWProject():
|
|||||||
else:
|
else:
|
||||||
logger.verbose("Project is not locked")
|
logger.verbose("Project is not locked")
|
||||||
|
|
||||||
try:
|
# Open The Project XML File
|
||||||
projectMaintenance(self)
|
# =========================
|
||||||
except Exception as E:
|
|
||||||
logger.error(str(E))
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
nwXML = etree.parse(fileName)
|
nwXML = etree.parse(fileName)
|
||||||
@@ -291,7 +313,7 @@ class NWProject():
|
|||||||
self.clearProject()
|
self.clearProject()
|
||||||
return False
|
return False
|
||||||
|
|
||||||
xRoot = nwXML.getroot()
|
xRoot = nwXML.getroot()
|
||||||
nwxRoot = xRoot.tag
|
nwxRoot = xRoot.tag
|
||||||
|
|
||||||
appVersion = "Unknown"
|
appVersion = "Unknown"
|
||||||
@@ -310,17 +332,51 @@ class NWProject():
|
|||||||
self.saveCount = checkInt(xRoot.attrib["saveCount"], 0, False)
|
self.saveCount = checkInt(xRoot.attrib["saveCount"], 0, False)
|
||||||
if "autoCount" in xRoot.attrib:
|
if "autoCount" in xRoot.attrib:
|
||||||
self.autoCount = checkInt(xRoot.attrib["autoCount"], 0, False)
|
self.autoCount = checkInt(xRoot.attrib["autoCount"], 0, False)
|
||||||
|
if "editTime" in xRoot.attrib:
|
||||||
|
self.editTime = checkInt(xRoot.attrib["editTime"], 0, False)
|
||||||
|
|
||||||
logger.verbose("XML root is %s" % nwxRoot)
|
logger.verbose("XML root is %s" % nwxRoot)
|
||||||
logger.verbose("File version is %s" % fileVersion)
|
logger.verbose("File version is %s" % fileVersion)
|
||||||
|
|
||||||
if not nwxRoot == "novelWriterXML" or not fileVersion == "1.0":
|
# Check File Type
|
||||||
|
# ===============
|
||||||
|
|
||||||
|
if not nwxRoot == "novelWriterXML":
|
||||||
self.makeAlert(
|
self.makeAlert(
|
||||||
"Project file does not appear to be a novelWriterXML file version 1.0",
|
"Project file does not appear to be a novelWriterXML file.",
|
||||||
nwAlert.ERROR
|
nwAlert.ERROR
|
||||||
)
|
)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
# Check Project Storage Version
|
||||||
|
# =============================
|
||||||
|
|
||||||
|
if fileVersion == "1.0":
|
||||||
|
msgBox = QMessageBox()
|
||||||
|
msgRes = msgBox.question(self.theParent, "Old Project Version", (
|
||||||
|
"The project file and data is created by a %s version lower than 0.7. "
|
||||||
|
"Do you want to upgrade the project to the most recent format?<br><br>"
|
||||||
|
"Note that after the upgrade, you cannot open the project with an older "
|
||||||
|
"version of %s any more, so make sure you have a recent backup."
|
||||||
|
) % (
|
||||||
|
nw.__package__, nw.__package__
|
||||||
|
))
|
||||||
|
if msgRes == QMessageBox.Yes:
|
||||||
|
self._updateStorage()
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
elif fileVersion != "1.1":
|
||||||
|
self.makeAlert((
|
||||||
|
"Unknown or unsupported %s project format. "
|
||||||
|
"The project cannot be opened by this version of %s."
|
||||||
|
) % (
|
||||||
|
nw.__package__, nw.__package__
|
||||||
|
), nwAlert.ERROR)
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Check novelWriter Version
|
||||||
|
# =========================
|
||||||
|
|
||||||
if int(hexVersion, 16) > int(nw.__hexversion__, 16) and self.mainConf.showGUI:
|
if int(hexVersion, 16) > int(nw.__hexversion__, 16) and self.mainConf.showGUI:
|
||||||
msgBox = QMessageBox()
|
msgBox = QMessageBox()
|
||||||
msgRes = msgBox.question(self.theParent, "Version Conflict", (
|
msgRes = msgBox.question(self.theParent, "Version Conflict", (
|
||||||
@@ -333,6 +389,9 @@ class NWProject():
|
|||||||
if msgRes != QMessageBox.Yes:
|
if msgRes != QMessageBox.Yes:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
# Start Parsing XML
|
||||||
|
# =================
|
||||||
|
|
||||||
for xChild in xRoot:
|
for xChild in xRoot:
|
||||||
if xChild.tag == "project":
|
if xChild.tag == "project":
|
||||||
logger.debug("Found project meta")
|
logger.debug("Found project meta")
|
||||||
@@ -350,6 +409,7 @@ class NWProject():
|
|||||||
self.bookAuthors.append(xItem.text)
|
self.bookAuthors.append(xItem.text)
|
||||||
elif xItem.tag == "backup":
|
elif xItem.tag == "backup":
|
||||||
self.doBackup = checkBool(xItem.text, False)
|
self.doBackup = checkBool(xItem.text, False)
|
||||||
|
|
||||||
elif xChild.tag == "settings":
|
elif xChild.tag == "settings":
|
||||||
logger.debug("Found project settings")
|
logger.debug("Found project settings")
|
||||||
for xItem in xChild:
|
for xItem in xChild:
|
||||||
@@ -377,6 +437,7 @@ class NWProject():
|
|||||||
for xEntry in xItem:
|
for xEntry in xItem:
|
||||||
titleFormat[xEntry.tag] = checkString(xEntry.text, "", False)
|
titleFormat[xEntry.tag] = checkString(xEntry.text, "", False)
|
||||||
self.setTitleFormat(titleFormat)
|
self.setTitleFormat(titleFormat)
|
||||||
|
|
||||||
elif xChild.tag == "content":
|
elif xChild.tag == "content":
|
||||||
logger.debug("Found project content")
|
logger.debug("Found project content")
|
||||||
self.projTree.unpackXML(xChild)
|
self.projTree.unpackXML(xChild)
|
||||||
@@ -404,15 +465,13 @@ class NWProject():
|
|||||||
file.
|
file.
|
||||||
"""
|
"""
|
||||||
if self.projPath is None:
|
if self.projPath is None:
|
||||||
self.makeAlert("Project path not set, cannot save.", nwAlert.ERROR)
|
self.makeAlert(
|
||||||
|
"Project path not set, cannot save project.", nwAlert.ERROR
|
||||||
|
)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
self.projMeta = path.join(self.projPath,"meta")
|
|
||||||
saveTime = time()
|
saveTime = time()
|
||||||
|
if not self.ensureFolderStructure():
|
||||||
if not self._checkFolder(self.projPath):
|
|
||||||
return False
|
|
||||||
if not self._checkFolder(self.projMeta):
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
logger.debug("Saving project: %s" % self.projPath)
|
logger.debug("Saving project: %s" % self.projPath)
|
||||||
@@ -427,10 +486,11 @@ class NWProject():
|
|||||||
nwXML = etree.Element("novelWriterXML",attrib={
|
nwXML = etree.Element("novelWriterXML",attrib={
|
||||||
"appVersion" : str(nw.__version__),
|
"appVersion" : str(nw.__version__),
|
||||||
"hexVersion" : str(nw.__hexversion__),
|
"hexVersion" : str(nw.__hexversion__),
|
||||||
"fileVersion" : "1.0",
|
"fileVersion" : "1.1",
|
||||||
"saveCount" : str(self.saveCount),
|
"saveCount" : str(self.saveCount),
|
||||||
"autoCount" : str(self.autoCount),
|
"autoCount" : str(self.autoCount),
|
||||||
"timeStamp" : formatTimeStamp(saveTime),
|
"timeStamp" : formatTimeStamp(saveTime),
|
||||||
|
"editTime" : str(int(self.editTime + saveTime - self.projOpened)),
|
||||||
})
|
})
|
||||||
|
|
||||||
# Save Project Meta
|
# Save Project Meta
|
||||||
@@ -480,7 +540,7 @@ class NWProject():
|
|||||||
xml_declaration = True
|
xml_declaration = True
|
||||||
))
|
))
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.makeAlert(["Failed to save project.",str(e)], nwAlert.ERROR)
|
self.makeAlert(["Failed to save project.", str(e)], nwAlert.ERROR)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# If we're here, the file was successfully saved,
|
# If we're here, the file was successfully saved,
|
||||||
@@ -507,12 +567,33 @@ class NWProject():
|
|||||||
def closeProject(self):
|
def closeProject(self):
|
||||||
"""Close the current project and clear all meta data.
|
"""Close the current project and clear all meta data.
|
||||||
"""
|
"""
|
||||||
|
self.projTree.writeToCFiles()
|
||||||
self._appendSessionStats()
|
self._appendSessionStats()
|
||||||
self._clearLockFile()
|
self._clearLockFile()
|
||||||
self.clearProject()
|
self.clearProject()
|
||||||
self.lockedBy = None
|
self.lockedBy = None
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
def ensureFolderStructure(self):
|
||||||
|
"""Ensure that all necessary folders exist in the project
|
||||||
|
folder.
|
||||||
|
"""
|
||||||
|
if self.projPath is None or self.projPath == "":
|
||||||
|
return False
|
||||||
|
|
||||||
|
self.projMeta = path.join(self.projPath, "meta")
|
||||||
|
self.projCache = path.join(self.projPath, "cache")
|
||||||
|
self.projContent = path.join(self.projPath, "content")
|
||||||
|
|
||||||
|
if not self._checkFolder(self.projMeta):
|
||||||
|
return False
|
||||||
|
if not self._checkFolder(self.projCache):
|
||||||
|
return False
|
||||||
|
if not self._checkFolder(self.projContent):
|
||||||
|
return False
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
##
|
##
|
||||||
# Backup Project
|
# Backup Project
|
||||||
##
|
##
|
||||||
@@ -937,35 +1018,26 @@ class NWProject():
|
|||||||
if self.projPath is None:
|
if self.projPath is None:
|
||||||
return
|
return
|
||||||
|
|
||||||
# First, scan the project data folders
|
# Then check the files in the data folder
|
||||||
itemList = []
|
|
||||||
for subItem in listdir(self.projPath):
|
|
||||||
if subItem[:5] != "data_":
|
|
||||||
continue
|
|
||||||
dataDir = path.join(self.projPath,subItem)
|
|
||||||
for subFile in listdir(dataDir):
|
|
||||||
if subFile[-4:] == ".nwd":
|
|
||||||
newItem = path.join(subItem,subFile)
|
|
||||||
itemList.append(newItem)
|
|
||||||
|
|
||||||
# Then check the valid files
|
|
||||||
orphanFiles = []
|
orphanFiles = []
|
||||||
for fileItem in itemList:
|
for fileItem in listdir(self.projContent):
|
||||||
if len(fileItem) != 28:
|
if not fileItem.endswith(".nwd"):
|
||||||
# Just to be safe, shouldn't happen
|
|
||||||
logger.warning("Skipping file %s" % fileItem)
|
logger.warning("Skipping file %s" % fileItem)
|
||||||
continue
|
continue
|
||||||
fHandle = fileItem[5]+fileItem[7:19]
|
if len(fileItem) != 17:
|
||||||
|
logger.warning("Skipping file %s" % fileItem)
|
||||||
|
continue
|
||||||
|
fHandle = fileItem[:13]
|
||||||
if fHandle in self.projTree:
|
if fHandle in self.projTree:
|
||||||
logger.debug("Checking file %s, handle %s: OK" % (fileItem,fHandle))
|
logger.debug("Checking file %s, handle %s: OK" % (fileItem, fHandle))
|
||||||
else:
|
else:
|
||||||
logger.debug("Checking file %s, handle %s: Orphaned" % (fileItem,fHandle))
|
logger.debug("Checking file %s, handle %s: Orphaned" % (fileItem, fHandle))
|
||||||
orphanFiles.append(fHandle)
|
orphanFiles.append(fHandle)
|
||||||
|
|
||||||
# Report status
|
# Report status
|
||||||
if len(orphanFiles) > 0:
|
if len(orphanFiles) > 0:
|
||||||
self.makeAlert(
|
self.makeAlert(
|
||||||
"Found %d orphaned file(s) in project folder!" % len(orphanFiles),
|
"Found %d orphaned file(s) in project folder." % len(orphanFiles),
|
||||||
nwAlert.WARN
|
nwAlert.WARN
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
@@ -998,7 +1070,7 @@ class NWProject():
|
|||||||
def _appendSessionStats(self):
|
def _appendSessionStats(self):
|
||||||
"""Append session statistics to the sessions log file.
|
"""Append session statistics to the sessions log file.
|
||||||
"""
|
"""
|
||||||
if self.projMeta is None:
|
if not self.ensureFolderStructure():
|
||||||
return False
|
return False
|
||||||
|
|
||||||
sessionFile = path.join(self.projMeta, nwFiles.SESS_INFO)
|
sessionFile = path.join(self.projMeta, nwFiles.SESS_INFO)
|
||||||
@@ -1016,12 +1088,122 @@ class NWProject():
|
|||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
##
|
||||||
|
# Legacy Data Structure Handlers
|
||||||
|
##
|
||||||
|
|
||||||
|
def _legacyDataFolder(self, theFolder):
|
||||||
|
"""Clean up legacy data folders.
|
||||||
|
"""
|
||||||
|
errList = []
|
||||||
|
theData = path.join(self.projPath, theFolder)
|
||||||
|
if not path.isdir(theData):
|
||||||
|
errList.append("Not a folder: %s" % theData)
|
||||||
|
return errList
|
||||||
|
|
||||||
|
logger.info("Old data folder %s found" % theFolder)
|
||||||
|
|
||||||
|
# Move Documents to Content
|
||||||
|
# =========================
|
||||||
|
for dataItem in listdir(theData):
|
||||||
|
theFile = path.join(theData, dataItem)
|
||||||
|
if not path.isfile(theFile):
|
||||||
|
theErr = self._moveUnknownItem(theData, dataItem)
|
||||||
|
if theErr:
|
||||||
|
errList.append(theErr)
|
||||||
|
continue
|
||||||
|
|
||||||
|
if len(dataItem) == 21 and dataItem.endswith("_main.nwd"):
|
||||||
|
tHandle = theFolder[-1]+dataItem[:12]
|
||||||
|
newPath = path.join(self.projContent, tHandle+".nwd")
|
||||||
|
try:
|
||||||
|
rename(theFile, newPath)
|
||||||
|
logger.info("Moved file: %s" % theFile)
|
||||||
|
logger.info("New location: %s" % newPath)
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(str(e))
|
||||||
|
errList.append("Could not move: %s" % theFile)
|
||||||
|
|
||||||
|
elif len(dataItem) == 21 and dataItem.endswith("_main.bak"):
|
||||||
|
try:
|
||||||
|
unlink(theFile)
|
||||||
|
logger.info("Deleted file: %s" % theFile)
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(str(e))
|
||||||
|
errList.append("Could not delete: %s" % theFile)
|
||||||
|
|
||||||
|
else:
|
||||||
|
theErr = self._moveUnknownItem(theData, dataItem)
|
||||||
|
if theErr:
|
||||||
|
errList.append(theErr)
|
||||||
|
|
||||||
|
# Remove Data Folder
|
||||||
|
# ==================
|
||||||
|
try:
|
||||||
|
rmdir(theData)
|
||||||
|
logger.info("Removed folder: %s" % theFolder)
|
||||||
|
except:
|
||||||
|
errList.append("Failed to remove: %s" % theFolder)
|
||||||
|
|
||||||
|
return errList
|
||||||
|
|
||||||
|
def _moveUnknownItem(self, theDir, theItem):
|
||||||
|
"""Move an item that doesn't belong in the project folder to
|
||||||
|
a junk folder.
|
||||||
|
"""
|
||||||
|
theJunk = path.join(self.projPath, "junk")
|
||||||
|
if not self._checkFolder(theJunk):
|
||||||
|
return "Could not make folder: %s" % theJunk
|
||||||
|
|
||||||
|
theSrc = path.join(theDir, theItem)
|
||||||
|
theDst = path.join(theJunk, theItem)
|
||||||
|
|
||||||
|
try:
|
||||||
|
rename(theSrc, theDst)
|
||||||
|
logger.info("Moved to junk: %s" % theSrc)
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(str(e))
|
||||||
|
return "Could not move item %s to junk." % theSrc
|
||||||
|
|
||||||
|
return ""
|
||||||
|
|
||||||
|
def _deprecatedFiles(self):
|
||||||
|
"""Delete files that are no longer used by novelWriter.
|
||||||
|
"""
|
||||||
|
rmList = []
|
||||||
|
rmList.append(path.join(self.projCache, "nwProject.nwx.0"))
|
||||||
|
rmList.append(path.join(self.projCache, "nwProject.nwx.1"))
|
||||||
|
rmList.append(path.join(self.projCache, "nwProject.nwx.2"))
|
||||||
|
rmList.append(path.join(self.projCache, "nwProject.nwx.3"))
|
||||||
|
rmList.append(path.join(self.projCache, "nwProject.nwx.4"))
|
||||||
|
rmList.append(path.join(self.projCache, "nwProject.nwx.5"))
|
||||||
|
rmList.append(path.join(self.projCache, "nwProject.nwx.6"))
|
||||||
|
rmList.append(path.join(self.projCache, "nwProject.nwx.7"))
|
||||||
|
rmList.append(path.join(self.projCache, "nwProject.nwx.8"))
|
||||||
|
rmList.append(path.join(self.projCache, "nwProject.nwx.9"))
|
||||||
|
rmList.append(path.join(self.projMeta, "mainOptions.json"))
|
||||||
|
rmList.append(path.join(self.projMeta, "exportOptions.json"))
|
||||||
|
rmList.append(path.join(self.projMeta, "outlineOptions.json"))
|
||||||
|
rmList.append(path.join(self.projMeta, "timelineOptions.json"))
|
||||||
|
rmList.append(path.join(self.projMeta, "docMergeOptions.json"))
|
||||||
|
rmList.append(path.join(self.projMeta, "sessionLogOptions.json"))
|
||||||
|
|
||||||
|
for rmFile in rmList:
|
||||||
|
if path.isfile(rmFile):
|
||||||
|
logger.info("Deleting: %s" % rmFile)
|
||||||
|
try:
|
||||||
|
unlink(rmFile)
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(str(e))
|
||||||
|
|
||||||
|
return
|
||||||
|
|
||||||
# END Class NWProject
|
# END Class NWProject
|
||||||
|
|
||||||
# ================================================================================================ #
|
# =============================================================================================== #
|
||||||
# NWTree
|
# NWTree
|
||||||
# Class holding the project tree for the NWProject
|
# Class holding the project tree for the NWProject
|
||||||
# ================================================================================================ #
|
# =============================================================================================== #
|
||||||
|
|
||||||
class NWTree():
|
class NWTree():
|
||||||
|
|
||||||
@@ -1120,6 +1302,51 @@ class NWTree():
|
|||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
def writeToCFiles(self):
|
||||||
|
"""Write the convenience table of contents files in the root of
|
||||||
|
the project directory. These files are there to assist the user
|
||||||
|
if they wish to browse the stored files.
|
||||||
|
"""
|
||||||
|
tocText = path.join(self.theProject.projPath, nwFiles.TOC_TXT)
|
||||||
|
tocJson = path.join(self.theProject.projPath, nwFiles.TOC_JSON)
|
||||||
|
|
||||||
|
jsonData = []
|
||||||
|
try:
|
||||||
|
# Dump the text
|
||||||
|
with open(tocText, mode="w", encoding="utf8") as outFile:
|
||||||
|
outFile.write("\n")
|
||||||
|
outFile.write(" Table of Contents\n")
|
||||||
|
outFile.write("===================\n")
|
||||||
|
outFile.write("\n")
|
||||||
|
outFile.write(" %-25s %-9s %s\n" %("File Name","Class","Document Label"))
|
||||||
|
outFile.write("-"*80+"\n")
|
||||||
|
for tHandle in sorted(self._treeOrder):
|
||||||
|
tItem = self.__getitem__(tHandle)
|
||||||
|
if tItem is None:
|
||||||
|
continue
|
||||||
|
tFile = tHandle+".nwd"
|
||||||
|
if path.isfile(path.join(self.theProject.projContent, tFile)):
|
||||||
|
outFile.write(" %-25s %-9s %s\n" %(
|
||||||
|
path.join("content", tFile),
|
||||||
|
tItem.itemClass.name,
|
||||||
|
tItem.itemName,
|
||||||
|
))
|
||||||
|
jsonData.append([
|
||||||
|
path.join("content", tFile),
|
||||||
|
tItem.itemClass.name,
|
||||||
|
tItem.itemName,
|
||||||
|
])
|
||||||
|
outFile.write("\n")
|
||||||
|
|
||||||
|
# Dump the JSON
|
||||||
|
with open(tocJson, mode="w+", encoding="utf8") as outFile:
|
||||||
|
outFile.write(json.dumps(jsonData, indent=2))
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(str(e))
|
||||||
|
|
||||||
|
return
|
||||||
|
|
||||||
##
|
##
|
||||||
# Tree Structure Methods
|
# Tree Structure Methods
|
||||||
##
|
##
|
||||||
@@ -1356,10 +1583,10 @@ class NWTree():
|
|||||||
|
|
||||||
# END Class NWTree
|
# END Class NWTree
|
||||||
|
|
||||||
# ================================================================================================ #
|
# =============================================================================================== #
|
||||||
# NWItem
|
# NWItem
|
||||||
# Class holding the project items making up the NWProject
|
# Class holding the project items making up the NWProject
|
||||||
# ================================================================================================ #
|
# =============================================================================================== #
|
||||||
|
|
||||||
class NWItem():
|
class NWItem():
|
||||||
|
|
||||||
@@ -1402,7 +1629,6 @@ class NWItem():
|
|||||||
xSub = self._subPack(xPack,"type", text=str(self.itemType.name))
|
xSub = self._subPack(xPack,"type", text=str(self.itemType.name))
|
||||||
xSub = self._subPack(xPack,"class", text=str(self.itemClass.name))
|
xSub = self._subPack(xPack,"class", text=str(self.itemClass.name))
|
||||||
xSub = self._subPack(xPack,"status", text=str(self.itemStatus))
|
xSub = self._subPack(xPack,"status", text=str(self.itemStatus))
|
||||||
xSub = self._subPack(xPack,"expanded", text=str(self.isExpanded))
|
|
||||||
if self.itemType == nwItemType.FILE:
|
if self.itemType == nwItemType.FILE:
|
||||||
xSub = self._subPack(xPack,"exported", text=str(self.isExported))
|
xSub = self._subPack(xPack,"exported", text=str(self.isExported))
|
||||||
xSub = self._subPack(xPack,"layout", text=str(self.itemLayout.name))
|
xSub = self._subPack(xPack,"layout", text=str(self.itemLayout.name))
|
||||||
@@ -1410,6 +1636,8 @@ class NWItem():
|
|||||||
xSub = self._subPack(xPack,"wordCount", text=str(self.wordCount), none=False)
|
xSub = self._subPack(xPack,"wordCount", text=str(self.wordCount), none=False)
|
||||||
xSub = self._subPack(xPack,"paraCount", text=str(self.paraCount), none=False)
|
xSub = self._subPack(xPack,"paraCount", text=str(self.paraCount), none=False)
|
||||||
xSub = self._subPack(xPack,"cursorPos", text=str(self.cursorPos), none=False)
|
xSub = self._subPack(xPack,"cursorPos", text=str(self.cursorPos), none=False)
|
||||||
|
else:
|
||||||
|
xSub = self._subPack(xPack,"expanded", text=str(self.isExpanded))
|
||||||
return
|
return
|
||||||
|
|
||||||
def unpackXML(self, xItem):
|
def unpackXML(self, xItem):
|
||||||
@@ -1601,10 +1829,10 @@ class NWItem():
|
|||||||
|
|
||||||
# END Class NWItem
|
# END Class NWItem
|
||||||
|
|
||||||
# ================================================================================================ #
|
# =============================================================================================== #
|
||||||
# NWStatus
|
# NWStatus
|
||||||
# Class holding the item status values stored in the NWProject
|
# Class holding the item status values stored in the NWProject
|
||||||
# ================================================================================================ #
|
# =============================================================================================== #
|
||||||
|
|
||||||
class NWStatus():
|
class NWStatus():
|
||||||
|
|
||||||
|
|||||||
@@ -35,9 +35,9 @@ from nw.constants import isoLanguage
|
|||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
# ================================================================================================ #
|
# =============================================================================================== #
|
||||||
# SpellChecking SuperClass
|
# SpellChecking SuperClass
|
||||||
# ================================================================================================ #
|
# =============================================================================================== #
|
||||||
|
|
||||||
class NWSpellCheck():
|
class NWSpellCheck():
|
||||||
|
|
||||||
@@ -129,9 +129,9 @@ class NWSpellCheck():
|
|||||||
|
|
||||||
# END Class NWSpellCheck
|
# END Class NWSpellCheck
|
||||||
|
|
||||||
# ================================================================================================ #
|
# =============================================================================================== #
|
||||||
# Enchant Based SpellChecking
|
# Enchant Based SpellChecking
|
||||||
# ================================================================================================ #
|
# =============================================================================================== #
|
||||||
|
|
||||||
class NWSpellEnchant(NWSpellCheck):
|
class NWSpellEnchant(NWSpellCheck):
|
||||||
|
|
||||||
@@ -211,9 +211,9 @@ class NWSpellEnchantDummy:
|
|||||||
|
|
||||||
# END Class NWSpellEnchantDummy
|
# END Class NWSpellEnchantDummy
|
||||||
|
|
||||||
# ================================================================================================ #
|
# =============================================================================================== #
|
||||||
# Fallback SpellChecking Using difflib
|
# Fallback SpellChecking Using difflib
|
||||||
# ================================================================================================ #
|
# =============================================================================================== #
|
||||||
|
|
||||||
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
|
||||||
|
|||||||
+21
-16
@@ -143,7 +143,7 @@ class ToHtml(Tokenizer):
|
|||||||
parStyle = None
|
parStyle = None
|
||||||
tmpResult = []
|
tmpResult = []
|
||||||
hasHardBreak = False
|
hasHardBreak = False
|
||||||
for tType, tText, tFormat, tStyle in self.theTokens:
|
for tType, tLine, tText, tFormat, tStyle in self.theTokens:
|
||||||
|
|
||||||
# Styles
|
# Styles
|
||||||
aStyle = []
|
aStyle = []
|
||||||
@@ -174,6 +174,11 @@ class ToHtml(Tokenizer):
|
|||||||
else:
|
else:
|
||||||
hStyle = ""
|
hStyle = ""
|
||||||
|
|
||||||
|
if self.linkHeaders:
|
||||||
|
aNm = "<a name='head_%s:T%06d'></a>" % (self.theHandle, tLine)
|
||||||
|
else:
|
||||||
|
aNm = ""
|
||||||
|
|
||||||
# Process TextType
|
# Process TextType
|
||||||
if tType == self.T_EMPTY:
|
if tType == self.T_EMPTY:
|
||||||
if parStyle is None:
|
if parStyle is None:
|
||||||
@@ -191,23 +196,23 @@ class ToHtml(Tokenizer):
|
|||||||
|
|
||||||
elif tType == self.T_TITLE:
|
elif tType == self.T_TITLE:
|
||||||
tHead = tText.replace(r"\\", "<br/>")
|
tHead = tText.replace(r"\\", "<br/>")
|
||||||
tmpResult.append("<h1 class='title'%s>%s</h1>\n" % (hStyle, tHead))
|
tmpResult.append("<h1 class='title'%s>%s%s</h1>\n" % (hStyle, aNm, tHead))
|
||||||
|
|
||||||
elif tType == self.T_HEAD1:
|
elif tType == self.T_HEAD1:
|
||||||
tHead = tText.replace(r"\\", "<br/>")
|
tHead = tText.replace(r"\\", "<br/>")
|
||||||
tmpResult.append("<%s%s>%s</%s>\n" % (h1, hStyle, tHead, h1))
|
tmpResult.append("<%s%s>%s%s</%s>\n" % (h1, hStyle, aNm, tHead, h1))
|
||||||
|
|
||||||
elif tType == self.T_HEAD2:
|
elif tType == self.T_HEAD2:
|
||||||
tHead = tText.replace(r"\\", "<br/>")
|
tHead = tText.replace(r"\\", "<br/>")
|
||||||
tmpResult.append("<%s%s>%s</%s>\n" % (h2, hStyle, tHead, h2))
|
tmpResult.append("<%s%s>%s%s</%s>\n" % (h2, hStyle, aNm, tHead, h2))
|
||||||
|
|
||||||
elif tType == self.T_HEAD3:
|
elif tType == self.T_HEAD3:
|
||||||
tHead = tText.replace(r"\\", "<br/>")
|
tHead = tText.replace(r"\\", "<br/>")
|
||||||
tmpResult.append("<%s%s>%s</%s>\n" % (h3, hStyle, tHead, h3))
|
tmpResult.append("<%s%s>%s%s</%s>\n" % (h3, hStyle, aNm, tHead, h3))
|
||||||
|
|
||||||
elif tType == self.T_HEAD4:
|
elif tType == self.T_HEAD4:
|
||||||
tHead = tText.replace(r"\\", "<br/>")
|
tHead = tText.replace(r"\\", "<br/>")
|
||||||
tmpResult.append("<%s%s>%s</%s>\n" % (h4, hStyle, tHead, h4))
|
tmpResult.append("<%s%s>%s%s</%s>\n" % (h4, hStyle, aNm, tHead, h4))
|
||||||
|
|
||||||
elif tType == self.T_SEP:
|
elif tType == self.T_SEP:
|
||||||
tmpResult.append("<p class='sep'>%s</p>\n" % tText)
|
tmpResult.append("<p class='sep'>%s</p>\n" % tText)
|
||||||
@@ -296,17 +301,17 @@ class ToHtml(Tokenizer):
|
|||||||
refTags = []
|
refTags = []
|
||||||
if theBits[0] in nwLabels.KEY_NAME:
|
if theBits[0] in nwLabels.KEY_NAME:
|
||||||
retText += "<span class='tags'>%s:</span> " % nwLabels.KEY_NAME[theBits[0]]
|
retText += "<span class='tags'>%s:</span> " % nwLabels.KEY_NAME[theBits[0]]
|
||||||
if self.genMode == self.M_PREVIEW:
|
if theBits[0] == nwKeyWords.TAG_KEY:
|
||||||
for tTag in theBits[1:]:
|
retText += "<a name='tag_%s'>%s</a>" % (
|
||||||
refTags.append("<a href='#%s=%s'>%s</a>" % (
|
theBits[1], theBits[1]
|
||||||
theBits[0][1:], tTag, tTag
|
)
|
||||||
))
|
|
||||||
retText += ", ".join(refTags)
|
|
||||||
else:
|
else:
|
||||||
if theBits[0] == nwKeyWords.TAG_KEY:
|
if self.genMode == self.M_PREVIEW:
|
||||||
retText += "<a name='tag_%s'/>%s" % (
|
for tTag in theBits[1:]:
|
||||||
theBits[1], theBits[1]
|
refTags.append("<a href='#%s=%s'>%s</a>" % (
|
||||||
)
|
theBits[0][1:], tTag, tTag
|
||||||
|
))
|
||||||
|
retText += ", ".join(refTags)
|
||||||
else:
|
else:
|
||||||
for tTag in theBits[1:]:
|
for tTag in theBits[1:]:
|
||||||
refTags.append("<a href='#tag_%s'>%s</a>" % (
|
refTags.append("<a href='#tag_%s'>%s</a>" % (
|
||||||
|
|||||||
+144
-51
@@ -102,6 +102,8 @@ class Tokenizer():
|
|||||||
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
|
||||||
|
|
||||||
|
self.linkHeaders = False # Add an anchor before headers
|
||||||
|
|
||||||
# Instance Variables
|
# Instance Variables
|
||||||
self.numChapter = 0 # Counter for chapter numbers
|
self.numChapter = 0 # Counter for chapter numbers
|
||||||
self.numChScene = 0 # Counter for scene number within chapter
|
self.numChScene = 0 # Counter for scene number within chapter
|
||||||
@@ -174,6 +176,10 @@ class Tokenizer():
|
|||||||
self.hideSection = hideSection
|
self.hideSection = hideSection
|
||||||
return
|
return
|
||||||
|
|
||||||
|
def setLinkHeaders(self, linkHeaders):
|
||||||
|
self.linkHeaders = linkHeaders
|
||||||
|
return
|
||||||
|
|
||||||
def setBodyText(self, doBodyText):
|
def setBodyText(self, doBodyText):
|
||||||
self.doBodyText = doBodyText
|
self.doBodyText = doBodyText
|
||||||
return
|
return
|
||||||
@@ -307,12 +313,16 @@ class Tokenizer():
|
|||||||
self.theTokens = []
|
self.theTokens = []
|
||||||
self.theMarkdown = ""
|
self.theMarkdown = ""
|
||||||
tmpMarkdown = []
|
tmpMarkdown = []
|
||||||
|
nLine = 0
|
||||||
for aLine in self.theText.splitlines():
|
for aLine in self.theText.splitlines():
|
||||||
|
nLine += 1
|
||||||
|
|
||||||
# Tag lines starting with specific characters
|
# Tag lines starting with specific characters
|
||||||
if len(aLine.strip()) == 0:
|
if len(aLine.strip()) == 0:
|
||||||
self.theTokens.append((
|
self.theTokens.append((
|
||||||
self.T_EMPTY, "", None, self.A_NONE
|
self.T_EMPTY, nLine,
|
||||||
|
"", None,
|
||||||
|
self.A_NONE
|
||||||
))
|
))
|
||||||
tmpMarkdown.append("\n")
|
tmpMarkdown.append("\n")
|
||||||
|
|
||||||
@@ -320,45 +330,59 @@ class Tokenizer():
|
|||||||
cLine = aLine[1:].strip()
|
cLine = aLine[1:].strip()
|
||||||
if cLine.lower().startswith("synopsis:"):
|
if cLine.lower().startswith("synopsis:"):
|
||||||
self.theTokens.append((
|
self.theTokens.append((
|
||||||
self.T_SYNOPSIS, cLine[9:].strip(), None, self.A_NONE
|
self.T_SYNOPSIS, nLine,
|
||||||
|
cLine[9:].strip(), None,
|
||||||
|
self.A_NONE
|
||||||
))
|
))
|
||||||
if self.doSynopsis:
|
if self.doSynopsis:
|
||||||
tmpMarkdown.append("%s\n" % aLine)
|
tmpMarkdown.append("%s\n" % aLine)
|
||||||
else:
|
else:
|
||||||
self.theTokens.append((
|
self.theTokens.append((
|
||||||
self.T_COMMENT, aLine[1:].strip(), None, self.A_NONE
|
self.T_COMMENT, nLine,
|
||||||
|
aLine[1:].strip(), None,
|
||||||
|
self.A_NONE
|
||||||
))
|
))
|
||||||
if self.doComments:
|
if self.doComments:
|
||||||
tmpMarkdown.append("%s\n" % aLine)
|
tmpMarkdown.append("%s\n" % aLine)
|
||||||
|
|
||||||
elif aLine[0] == "@":
|
elif aLine[0] == "@":
|
||||||
self.theTokens.append((
|
self.theTokens.append((
|
||||||
self.T_KEYWORD, aLine[1:].strip(), None, self.A_NONE
|
self.T_KEYWORD, nLine,
|
||||||
|
aLine[1:].strip(), None,
|
||||||
|
self.A_NONE
|
||||||
))
|
))
|
||||||
if self.doKeywords:
|
if self.doKeywords:
|
||||||
tmpMarkdown.append("%s\n" % aLine)
|
tmpMarkdown.append("%s\n" % aLine)
|
||||||
|
|
||||||
elif aLine[:2] == "# ":
|
elif aLine[:2] == "# ":
|
||||||
self.theTokens.append((
|
self.theTokens.append((
|
||||||
self.T_HEAD1, aLine[2:].strip(), None, self.A_NONE
|
self.T_HEAD1, nLine,
|
||||||
|
aLine[2:].strip(), None,
|
||||||
|
self.A_NONE
|
||||||
))
|
))
|
||||||
tmpMarkdown.append("%s\n" % aLine)
|
tmpMarkdown.append("%s\n" % aLine)
|
||||||
|
|
||||||
elif aLine[:3] == "## ":
|
elif aLine[:3] == "## ":
|
||||||
self.theTokens.append((
|
self.theTokens.append((
|
||||||
self.T_HEAD2, aLine[3:].strip(), None, self.A_NONE
|
self.T_HEAD2, nLine,
|
||||||
|
aLine[3:].strip(), None,
|
||||||
|
self.A_NONE
|
||||||
))
|
))
|
||||||
tmpMarkdown.append("%s\n" % aLine)
|
tmpMarkdown.append("%s\n" % aLine)
|
||||||
|
|
||||||
elif aLine[:4] == "### ":
|
elif aLine[:4] == "### ":
|
||||||
self.theTokens.append((
|
self.theTokens.append((
|
||||||
self.T_HEAD3, aLine[4:].strip(), None, self.A_NONE
|
self.T_HEAD3, nLine,
|
||||||
|
aLine[4:].strip(), None,
|
||||||
|
self.A_NONE
|
||||||
))
|
))
|
||||||
tmpMarkdown.append("%s\n" % aLine)
|
tmpMarkdown.append("%s\n" % aLine)
|
||||||
|
|
||||||
elif aLine[:5] == "#### ":
|
elif aLine[:5] == "#### ":
|
||||||
self.theTokens.append((
|
self.theTokens.append((
|
||||||
self.T_HEAD4, aLine[5:].strip(), None, self.A_NONE
|
self.T_HEAD4, nLine,
|
||||||
|
aLine[5:].strip(), None,
|
||||||
|
self.A_NONE
|
||||||
))
|
))
|
||||||
tmpMarkdown.append("%s\n" % aLine)
|
tmpMarkdown.append("%s\n" % aLine)
|
||||||
|
|
||||||
@@ -383,13 +407,17 @@ class Tokenizer():
|
|||||||
# sorted by position
|
# sorted by position
|
||||||
fmtPos = sorted(fmtPos, key=itemgetter(0))
|
fmtPos = sorted(fmtPos, key=itemgetter(0))
|
||||||
self.theTokens.append((
|
self.theTokens.append((
|
||||||
self.T_TEXT, aLine, fmtPos, self.A_NONE
|
self.T_TEXT, nLine,
|
||||||
|
aLine, fmtPos,
|
||||||
|
self.A_NONE
|
||||||
))
|
))
|
||||||
tmpMarkdown.append("%s\n" % aLine)
|
tmpMarkdown.append("%s\n" % aLine)
|
||||||
|
|
||||||
# Always add an empty line at the end
|
# Always add an empty line at the end
|
||||||
self.theTokens.append((
|
self.theTokens.append((
|
||||||
self.T_EMPTY, "", None, self.A_NONE
|
self.T_EMPTY, nLine,
|
||||||
|
"", None,
|
||||||
|
self.A_NONE
|
||||||
))
|
))
|
||||||
tmpMarkdown.append("\n")
|
tmpMarkdown.append("\n")
|
||||||
|
|
||||||
@@ -413,100 +441,146 @@ class Tokenizer():
|
|||||||
for n in range(len(self.theTokens)):
|
for n in range(len(self.theTokens)):
|
||||||
|
|
||||||
tToken = self.theTokens[n]
|
tToken = self.theTokens[n]
|
||||||
tType = tToken[0]
|
|
||||||
tText = tToken[1]
|
|
||||||
|
|
||||||
# In case we see text before a scene, we reset the flag
|
# In case we see text before a scene, we reset the flag
|
||||||
if tType == self.T_TEXT:
|
if tToken[0] == self.T_TEXT:
|
||||||
self.firstScene = False
|
self.firstScene = False
|
||||||
|
|
||||||
elif tType == self.T_HEAD1:
|
elif tToken[0] == self.T_HEAD1:
|
||||||
# Main Title
|
# Main Title
|
||||||
# ==========
|
# ==========
|
||||||
|
|
||||||
tText = self._formatHeading(self.fmtTitle, tText)
|
tTemp = self._formatHeading(self.fmtTitle, tToken[2])
|
||||||
self.theTokens[n] = (
|
self.theTokens[n] = (
|
||||||
tType, tText, None, self.A_NONE
|
tToken[0],
|
||||||
|
tToken[1],
|
||||||
|
tTemp,
|
||||||
|
None,
|
||||||
|
self.A_NONE
|
||||||
)
|
)
|
||||||
|
|
||||||
elif tType == self.T_HEAD2:
|
elif tToken[0] == self.T_HEAD2:
|
||||||
# Novel Chapter
|
# Novel Chapter
|
||||||
# =============
|
# =============
|
||||||
|
|
||||||
# Numbered or Unnumbered
|
# Numbered or Unnumbered
|
||||||
if self.isUnNum:
|
if self.isUnNum:
|
||||||
tText = self._formatHeading(self.fmtUnNum, tText)
|
tTemp = self._formatHeading(self.fmtUnNum, tToken[2])
|
||||||
else:
|
else:
|
||||||
self.numChapter += 1
|
self.numChapter += 1
|
||||||
tText = self._formatHeading(self.fmtChapter, tText)
|
tTemp = self._formatHeading(self.fmtChapter, tToken[2])
|
||||||
|
|
||||||
# Format the chapter header
|
# Format the chapter header
|
||||||
self.theTokens[n] = (
|
self.theTokens[n] = (
|
||||||
tType, tText, None, self.A_PBB
|
tToken[0],
|
||||||
|
tToken[1],
|
||||||
|
tTemp,
|
||||||
|
None,
|
||||||
|
self.A_PBB
|
||||||
)
|
)
|
||||||
|
|
||||||
# Set scene variables
|
# Set scene variables
|
||||||
self.firstScene = True
|
self.firstScene = True
|
||||||
self.numChScene = 0
|
self.numChScene = 0
|
||||||
|
|
||||||
elif tType == self.T_HEAD3:
|
elif tToken[0] == self.T_HEAD3:
|
||||||
# Novel Scene
|
# Novel Scene
|
||||||
# ===========
|
# ===========
|
||||||
|
|
||||||
self.numChScene += 1
|
self.numChScene += 1
|
||||||
self.numAbsScene += 1
|
self.numAbsScene += 1
|
||||||
|
|
||||||
tTemp = self._formatHeading(self.fmtScene, tText)
|
tTemp = self._formatHeading(self.fmtScene, tToken[2])
|
||||||
if tTemp == "" and self.hideScene:
|
if tTemp == "" and self.hideScene:
|
||||||
self.theTokens[n] = (
|
self.theTokens[n] = (
|
||||||
self.T_EMPTY, "", None, self.A_NONE
|
self.T_EMPTY,
|
||||||
|
tToken[1],
|
||||||
|
"",
|
||||||
|
None,
|
||||||
|
self.A_NONE
|
||||||
)
|
)
|
||||||
elif tTemp == "" and not self.hideScene:
|
elif tTemp == "" and not self.hideScene:
|
||||||
if self.firstScene:
|
if self.firstScene:
|
||||||
self.theTokens[n] = (
|
self.theTokens[n] = (
|
||||||
self.T_EMPTY, "", None, self.A_NONE
|
self.T_EMPTY,
|
||||||
|
tToken[1],
|
||||||
|
"",
|
||||||
|
None,
|
||||||
|
self.A_NONE
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
self.theTokens[n] = (
|
self.theTokens[n] = (
|
||||||
self.T_SKIP, "", None, self.A_NONE
|
self.T_SKIP,
|
||||||
|
tToken[1],
|
||||||
|
"",
|
||||||
|
None,
|
||||||
|
self.A_NONE
|
||||||
)
|
)
|
||||||
elif tTemp == self.fmtScene:
|
elif tTemp == self.fmtScene:
|
||||||
if self.firstScene:
|
if self.firstScene:
|
||||||
self.theTokens[n] = (
|
self.theTokens[n] = (
|
||||||
self.T_EMPTY, "", None, self.A_NONE
|
self.T_EMPTY,
|
||||||
|
tToken[1],
|
||||||
|
"",
|
||||||
|
None,
|
||||||
|
self.A_NONE
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
self.theTokens[n] = (
|
self.theTokens[n] = (
|
||||||
self.T_SEP, tTemp, None, self.A_CENTRE
|
self.T_SEP,
|
||||||
|
tToken[1],
|
||||||
|
tTemp,
|
||||||
|
None,
|
||||||
|
self.A_CENTRE
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
self.theTokens[n] = (
|
self.theTokens[n] = (
|
||||||
tType, tTemp, None, self.A_NONE
|
tToken[0],
|
||||||
|
tToken[1],
|
||||||
|
tTemp,
|
||||||
|
None,
|
||||||
|
self.A_NONE
|
||||||
)
|
)
|
||||||
|
|
||||||
# Definitely no longer the first scene
|
# Definitely no longer the first scene
|
||||||
self.firstScene = False
|
self.firstScene = False
|
||||||
|
|
||||||
elif tType == self.T_HEAD4:
|
elif tToken[0] == self.T_HEAD4:
|
||||||
# Novel Section
|
# Novel Section
|
||||||
# =============
|
# =============
|
||||||
|
|
||||||
tTemp = self._formatHeading(self.fmtSection, tText)
|
tTemp = self._formatHeading(self.fmtSection, tToken[2])
|
||||||
if tTemp == "" and self.hideSection:
|
if tTemp == "" and self.hideSection:
|
||||||
self.theTokens[n] = (
|
self.theTokens[n] = (
|
||||||
self.T_EMPTY, "", None, self.A_NONE
|
self.T_EMPTY,
|
||||||
|
tToken[1],
|
||||||
|
"",
|
||||||
|
None,
|
||||||
|
self.A_NONE
|
||||||
)
|
)
|
||||||
elif tTemp == "" and not self.hideSection:
|
elif tTemp == "" and not self.hideSection:
|
||||||
self.theTokens[n] = (
|
self.theTokens[n] = (
|
||||||
self.T_SKIP, "", None, self.A_NONE
|
self.T_SKIP,
|
||||||
|
tToken[1],
|
||||||
|
"",
|
||||||
|
None,
|
||||||
|
self.A_NONE
|
||||||
)
|
)
|
||||||
elif tTemp == self.fmtSection:
|
elif tTemp == self.fmtSection:
|
||||||
self.theTokens[n] = (
|
self.theTokens[n] = (
|
||||||
self.T_SEP, tTemp, None, self.A_CENTRE
|
self.T_SEP,
|
||||||
|
tToken[1],
|
||||||
|
tTemp,
|
||||||
|
None,
|
||||||
|
self.A_CENTRE
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
self.theTokens[n] = (
|
self.theTokens[n] = (
|
||||||
tType, tTemp, None, self.A_NONE
|
tToken[0],
|
||||||
|
tToken[1],
|
||||||
|
tTemp,
|
||||||
|
None,
|
||||||
|
self.A_NONE
|
||||||
)
|
)
|
||||||
|
|
||||||
# For title page and partitions, we need to centre all text.
|
# For title page and partitions, we need to centre all text.
|
||||||
@@ -515,21 +589,30 @@ class Tokenizer():
|
|||||||
# We also swap header level 1 with a title type instead.
|
# We also swap header level 1 with a title type instead.
|
||||||
if self.isTitle or self.isPart:
|
if self.isTitle or self.isPart:
|
||||||
for n, tToken in enumerate(self.theTokens):
|
for n, tToken in enumerate(self.theTokens):
|
||||||
tType = tToken[0]
|
if tToken[0] == self.T_HEAD1:
|
||||||
tText = tToken[1]
|
|
||||||
tFormat = tToken[2]
|
|
||||||
if tType == self.T_HEAD1:
|
|
||||||
if self.isTitle:
|
if self.isTitle:
|
||||||
self.theTokens[n] = (
|
self.theTokens[n] = (
|
||||||
self.T_TITLE, tText, tFormat, self.A_PBB_NO | self.A_CENTRE
|
self.T_TITLE,
|
||||||
|
tToken[1],
|
||||||
|
tToken[2],
|
||||||
|
tToken[3],
|
||||||
|
self.A_PBB_NO | self.A_CENTRE
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
self.theTokens[n] = (
|
self.theTokens[n] = (
|
||||||
tType, tText, tFormat, self.A_PBB | self.A_CENTRE
|
tToken[0],
|
||||||
|
tToken[1],
|
||||||
|
tToken[2],
|
||||||
|
tToken[3],
|
||||||
|
self.A_PBB | self.A_CENTRE
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
self.theTokens[n] = (
|
self.theTokens[n] = (
|
||||||
tType, tText, tFormat, self.A_CENTRE
|
tToken[0],
|
||||||
|
tToken[1],
|
||||||
|
tToken[2],
|
||||||
|
tToken[3],
|
||||||
|
self.A_CENTRE
|
||||||
)
|
)
|
||||||
|
|
||||||
# Add a page break after the last entry
|
# Add a page break after the last entry
|
||||||
@@ -537,23 +620,32 @@ class Tokenizer():
|
|||||||
if n >= 0:
|
if n >= 0:
|
||||||
tToken = self.theTokens[n]
|
tToken = self.theTokens[n]
|
||||||
self.theTokens[n] = (
|
self.theTokens[n] = (
|
||||||
tToken[0], tToken[1], tToken[2], tToken[3] | self.A_PBA
|
tToken[0],
|
||||||
|
tToken[1],
|
||||||
|
tToken[2],
|
||||||
|
tToken[3],
|
||||||
|
tToken[4] | self.A_PBA
|
||||||
)
|
)
|
||||||
|
|
||||||
# A single page is always left-aligned and starts on a fresh
|
# A single page is always left-aligned and starts on a fresh
|
||||||
# page, unless it's empty.
|
# page, unless it's empty.
|
||||||
if self.isPage:
|
if self.isPage:
|
||||||
for n, tToken in enumerate(self.theTokens):
|
for n, tToken in enumerate(self.theTokens):
|
||||||
tType = tToken[0]
|
|
||||||
tText = tToken[1]
|
|
||||||
tFormat = tToken[2]
|
|
||||||
if n == 0:
|
if n == 0:
|
||||||
self.theTokens[n] = (
|
self.theTokens[n] = (
|
||||||
tType, tText, tFormat, self.A_LEFT | self.A_PBB
|
tToken[0],
|
||||||
|
tToken[1],
|
||||||
|
tToken[2],
|
||||||
|
tToken[3],
|
||||||
|
self.A_LEFT | self.A_PBB
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
self.theTokens[n] = (
|
self.theTokens[n] = (
|
||||||
tType, tText, tFormat, self.A_LEFT
|
tToken[0],
|
||||||
|
tToken[1],
|
||||||
|
tToken[2],
|
||||||
|
tToken[3],
|
||||||
|
self.A_LEFT
|
||||||
)
|
)
|
||||||
|
|
||||||
return
|
return
|
||||||
@@ -566,10 +658,11 @@ class Tokenizer():
|
|||||||
"""Replaces the %keyword% strings.
|
"""Replaces the %keyword% strings.
|
||||||
"""
|
"""
|
||||||
theTitle = theTitle.replace(r"%title%", theText)
|
theTitle = theTitle.replace(r"%title%", theText)
|
||||||
theTitle = theTitle.replace(r"%ch%", str(self.numChapter))
|
theTitle = theTitle.replace(r"%ch%", str(self.numChapter))
|
||||||
theTitle = theTitle.replace(r"%sc%", str(self.numChScene))
|
theTitle = theTitle.replace(r"%sc%", str(self.numChScene))
|
||||||
theTitle = theTitle.replace(r"%sca%", str(self.numAbsScene))
|
theTitle = theTitle.replace(r"%sca%", str(self.numAbsScene))
|
||||||
theTitle = theTitle.replace(r"%chw%", numberToWord(self.numChapter,"en"))
|
if r"%chw%" in theTitle:
|
||||||
|
theTitle = theTitle.replace(r"%chw%", numberToWord(self.numChapter,"en"))
|
||||||
return theTitle
|
return theTitle
|
||||||
|
|
||||||
# END Class Tokenizer
|
# END Class Tokenizer
|
||||||
|
|||||||
@@ -8,7 +8,6 @@
|
|||||||
File History:
|
File History:
|
||||||
Created: 2019-04-22 [0.0.1] countWords
|
Created: 2019-04-22 [0.0.1] countWords
|
||||||
Created: 2019-10-13 [0.2.3] numberToWord, _numberToWordEN
|
Created: 2019-10-13 [0.2.3] numberToWord, _numberToWordEN
|
||||||
Created: 2020-02-13 [0.4.3] projectMaintenance
|
|
||||||
Merged: 2020-05-08 [0.4.5] All of the above into this file
|
Merged: 2020-05-08 [0.4.5] All of the above into this file
|
||||||
|
|
||||||
This file is a part of novelWriter
|
This file is a part of novelWriter
|
||||||
@@ -81,50 +80,6 @@ def countWords(theText):
|
|||||||
|
|
||||||
return charCount, wordCount, paraCount
|
return charCount, wordCount, paraCount
|
||||||
|
|
||||||
def projectMaintenance(theProject):
|
|
||||||
"""Wrapper class for handling various tasks related to managing old
|
|
||||||
projects with content from older versions of novelWriter.
|
|
||||||
"""
|
|
||||||
# Remove no longer used project cache folder
|
|
||||||
if path.isdir(theProject.projPath):
|
|
||||||
cacheDir = path.join(theProject.projPath, "cache")
|
|
||||||
if path.isdir(cacheDir):
|
|
||||||
logger.info("Deprecated cache folder found")
|
|
||||||
rmList = []
|
|
||||||
for i in range(10):
|
|
||||||
rmList.append(path.join(cacheDir, "nwProject.nwx.%d" % i))
|
|
||||||
rmList.append(path.join(cacheDir, "projCount.txt"))
|
|
||||||
for rmFile in rmList:
|
|
||||||
if path.isfile(rmFile):
|
|
||||||
logger.info("Deleting: %s" % rmFile)
|
|
||||||
try:
|
|
||||||
unlink(rmFile)
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(str(e))
|
|
||||||
logger.info("Deleting: %s" % cacheDir)
|
|
||||||
try:
|
|
||||||
rmdir(cacheDir)
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(str(e))
|
|
||||||
|
|
||||||
# Remove no longer used meta files
|
|
||||||
rmList = []
|
|
||||||
rmList.append(path.join(theProject.projMeta, "mainOptions.json"))
|
|
||||||
rmList.append(path.join(theProject.projMeta, "exportOptions.json"))
|
|
||||||
rmList.append(path.join(theProject.projMeta, "outlineOptions.json"))
|
|
||||||
rmList.append(path.join(theProject.projMeta, "timelineOptions.json"))
|
|
||||||
rmList.append(path.join(theProject.projMeta, "docMergeOptions.json"))
|
|
||||||
rmList.append(path.join(theProject.projMeta, "sessionLogOptions.json"))
|
|
||||||
for rmFile in rmList:
|
|
||||||
if path.isfile(rmFile):
|
|
||||||
logger.info("Deleting: %s" % rmFile)
|
|
||||||
try:
|
|
||||||
unlink(rmFile)
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(str(e))
|
|
||||||
|
|
||||||
return
|
|
||||||
|
|
||||||
def numberToWord(numVal, theLanguage):
|
def numberToWord(numVal, theLanguage):
|
||||||
"""Wrapper for converting numbers to words for chapter headings.
|
"""Wrapper for converting numbers to words for chapter headings.
|
||||||
"""
|
"""
|
||||||
|
|||||||
+2
-2
@@ -9,7 +9,7 @@ from nw.gui.theme import GuiTheme
|
|||||||
|
|
||||||
# Dialogs
|
# Dialogs
|
||||||
from nw.gui.dialogs.about import GuiAbout
|
from nw.gui.dialogs.about import GuiAbout
|
||||||
from nw.gui.dialogs.configeditor import GuiConfigEditor
|
from nw.gui.dialogs.preferences import GuiPreferences
|
||||||
from nw.gui.dialogs.docmerge import GuiDocMerge
|
from nw.gui.dialogs.docmerge import GuiDocMerge
|
||||||
from nw.gui.dialogs.docsplit import GuiDocSplit
|
from nw.gui.dialogs.docsplit import GuiDocSplit
|
||||||
from nw.gui.dialogs.itemeditor import GuiItemEditor
|
from nw.gui.dialogs.itemeditor import GuiItemEditor
|
||||||
@@ -40,7 +40,7 @@ __all__ = [
|
|||||||
"GuiMainStatus",
|
"GuiMainStatus",
|
||||||
"GuiTheme",
|
"GuiTheme",
|
||||||
"GuiAbout",
|
"GuiAbout",
|
||||||
"GuiConfigEditor",
|
"GuiPreferences",
|
||||||
"GuiDocMerge",
|
"GuiDocMerge",
|
||||||
"GuiDocSplit",
|
"GuiDocSplit",
|
||||||
"GuiItemEditor",
|
"GuiItemEditor",
|
||||||
|
|||||||
+33
-24
@@ -34,12 +34,12 @@ from time import time
|
|||||||
from PyQt5.QtCore import Qt, QByteArray
|
from PyQt5.QtCore import Qt, QByteArray
|
||||||
from PyQt5.QtPrintSupport import QPrinter, QPrintPreviewDialog
|
from PyQt5.QtPrintSupport import QPrinter, QPrintPreviewDialog
|
||||||
from PyQt5.QtGui import (
|
from PyQt5.QtGui import (
|
||||||
QTextOption, QPalette, QColor, QTextDocumentWriter, QFont
|
QPalette, QColor, QTextDocumentWriter, QFont
|
||||||
)
|
)
|
||||||
from PyQt5.QtWidgets import (
|
from PyQt5.QtWidgets import (
|
||||||
QDialog, QVBoxLayout, QHBoxLayout, QTextBrowser, QPushButton, QLabel,
|
QDialog, QVBoxLayout, QHBoxLayout, QTextBrowser, QPushButton, QLabel,
|
||||||
QLineEdit, QGroupBox, QGridLayout, QProgressBar, QMenu, QAction,
|
QLineEdit, QGroupBox, QGridLayout, QProgressBar, QMenu, QAction,
|
||||||
QFileDialog, QFontComboBox, QSpinBox, QDialogButtonBox
|
QFileDialog, QFontComboBox, QSpinBox
|
||||||
)
|
)
|
||||||
|
|
||||||
from nw.gui.additions import QSwitch
|
from nw.gui.additions import QSwitch
|
||||||
@@ -83,8 +83,7 @@ class GuiBuildNovel(QDialog):
|
|||||||
self.optState.getInt("GuiBuildNovel", "winHeight", 800)
|
self.optState.getInt("GuiBuildNovel", "winHeight", 800)
|
||||||
)
|
)
|
||||||
|
|
||||||
self.outerBox = QVBoxLayout()
|
self.outerBox = QHBoxLayout()
|
||||||
self.innerBox = QHBoxLayout()
|
|
||||||
self.toolsBox = QVBoxLayout()
|
self.toolsBox = QVBoxLayout()
|
||||||
|
|
||||||
self.docView = GuiBuildNovelDocView(self, self.theProject)
|
self.docView = GuiBuildNovelDocView(self, self.theProject)
|
||||||
@@ -173,7 +172,9 @@ class GuiBuildNovel(QDialog):
|
|||||||
|
|
||||||
self.textFont = QFontComboBox()
|
self.textFont = QFontComboBox()
|
||||||
self.textFont.setFixedWidth(220)
|
self.textFont.setFixedWidth(220)
|
||||||
self.textFont.setToolTip("The font is used for PDF and printing. Other formats have no font set.")
|
self.textFont.setToolTip(
|
||||||
|
"The font is used for PDF and printing. Other formats have no font set."
|
||||||
|
)
|
||||||
self.textFont.setCurrentFont(
|
self.textFont.setCurrentFont(
|
||||||
QFont(self.optState.getString("GuiBuildNovel", "textFont", self.mainConf.textFont))
|
QFont(self.optState.getString("GuiBuildNovel", "textFont", self.mainConf.textFont))
|
||||||
)
|
)
|
||||||
@@ -183,13 +184,17 @@ class GuiBuildNovel(QDialog):
|
|||||||
self.textSize.setMinimum(5)
|
self.textSize.setMinimum(5)
|
||||||
self.textSize.setMaximum(48)
|
self.textSize.setMaximum(48)
|
||||||
self.textSize.setSingleStep(1)
|
self.textSize.setSingleStep(1)
|
||||||
self.textSize.setToolTip("The size is used for PDF and printing. Other formats have no size set.")
|
self.textSize.setToolTip(
|
||||||
|
"The size is used for PDF and printing. Other formats have no size set."
|
||||||
|
)
|
||||||
self.textSize.setValue(
|
self.textSize.setValue(
|
||||||
self.optState.getInt("GuiBuildNovel", "textSize", self.mainConf.textSize)
|
self.optState.getInt("GuiBuildNovel", "textSize", self.mainConf.textSize)
|
||||||
)
|
)
|
||||||
|
|
||||||
self.justifyText = QSwitch()
|
self.justifyText = QSwitch()
|
||||||
self.justifyText.setToolTip("Applies to PDF, printing, HTML, and Open Document exports.")
|
self.justifyText.setToolTip(
|
||||||
|
"Applies to PDF, printing, HTML, and Open Document exports."
|
||||||
|
)
|
||||||
self.justifyText.setChecked(
|
self.justifyText.setChecked(
|
||||||
self.optState.getBool("GuiBuildNovel", "justifyText", False)
|
self.optState.getBool("GuiBuildNovel", "justifyText", False)
|
||||||
)
|
)
|
||||||
@@ -211,15 +216,21 @@ class GuiBuildNovel(QDialog):
|
|||||||
self.includeGroup.setLayout(self.includeForm)
|
self.includeGroup.setLayout(self.includeForm)
|
||||||
|
|
||||||
self.includeSynopsis = QSwitch()
|
self.includeSynopsis = QSwitch()
|
||||||
self.includeSynopsis.setToolTip("Include synopsis type comments in the output.")
|
self.includeSynopsis.setToolTip(
|
||||||
|
"Include synopsis type comments in the output."
|
||||||
|
)
|
||||||
self.includeSynopsis.setChecked(self.theProject.titleFormat["withSynopsis"])
|
self.includeSynopsis.setChecked(self.theProject.titleFormat["withSynopsis"])
|
||||||
|
|
||||||
self.includeComments = QSwitch()
|
self.includeComments = QSwitch()
|
||||||
self.includeComments.setToolTip("Include plain comments in the output.")
|
self.includeComments.setToolTip(
|
||||||
|
"Include plain comments in the output."
|
||||||
|
)
|
||||||
self.includeComments.setChecked(self.theProject.titleFormat["withComments"])
|
self.includeComments.setChecked(self.theProject.titleFormat["withComments"])
|
||||||
|
|
||||||
self.includeKeywords = QSwitch()
|
self.includeKeywords = QSwitch()
|
||||||
self.includeKeywords.setToolTip("Include meta keywords (tags, references) in the output.")
|
self.includeKeywords.setToolTip(
|
||||||
|
"Include meta keywords (tags, references) in the output."
|
||||||
|
)
|
||||||
self.includeKeywords.setChecked(self.theProject.titleFormat["withKeywords"])
|
self.includeKeywords.setChecked(self.theProject.titleFormat["withKeywords"])
|
||||||
|
|
||||||
self.includeForm.addWidget(QLabel("Include synopsis"), 0, 0, 1, 1, Qt.AlignLeft)
|
self.includeForm.addWidget(QLabel("Include synopsis"), 0, 0, 1, 1, Qt.AlignLeft)
|
||||||
@@ -292,7 +303,7 @@ class GuiBuildNovel(QDialog):
|
|||||||
|
|
||||||
# Action Buttons
|
# Action Buttons
|
||||||
# ==============
|
# ==============
|
||||||
self.buttonForm = QGridLayout()
|
self.buttonBox = QHBoxLayout()
|
||||||
|
|
||||||
self.btnPrint = QPushButton("Print")
|
self.btnPrint = QPushButton("Print")
|
||||||
self.btnPrint.clicked.connect(self._printDocument)
|
self.btnPrint.clicked.connect(self._printDocument)
|
||||||
@@ -326,12 +337,13 @@ class GuiBuildNovel(QDialog):
|
|||||||
self.saveTXT.triggered.connect(lambda: self._saveDocument(self.FMT_TXT))
|
self.saveTXT.triggered.connect(lambda: self._saveDocument(self.FMT_TXT))
|
||||||
self.saveMenu.addAction(self.saveTXT)
|
self.saveMenu.addAction(self.saveTXT)
|
||||||
|
|
||||||
self.buttonForm.addWidget(self.btnSave, 0, 0)
|
self.btnClose = QPushButton("Close")
|
||||||
self.buttonForm.addWidget(self.btnPrint, 0, 1)
|
self.btnClose.clicked.connect(self._doClose)
|
||||||
|
|
||||||
# Buttons
|
self.buttonBox.addWidget(self.btnSave)
|
||||||
self.buttonBox = QDialogButtonBox(QDialogButtonBox.Close)
|
self.buttonBox.addWidget(self.btnPrint)
|
||||||
self.buttonBox.rejected.connect(self._doClose)
|
self.buttonBox.addWidget(self.btnClose)
|
||||||
|
self.buttonBox.setSpacing(4)
|
||||||
|
|
||||||
# Assemble GUI
|
# Assemble GUI
|
||||||
# ============
|
# ============
|
||||||
@@ -343,18 +355,15 @@ class GuiBuildNovel(QDialog):
|
|||||||
self.toolsBox.addWidget(self.buildProgress)
|
self.toolsBox.addWidget(self.buildProgress)
|
||||||
self.toolsBox.addWidget(self.buildNovel)
|
self.toolsBox.addWidget(self.buildNovel)
|
||||||
self.toolsBox.addSpacing(8)
|
self.toolsBox.addSpacing(8)
|
||||||
self.toolsBox.addLayout(self.buttonForm)
|
self.toolsBox.addLayout(self.buttonBox)
|
||||||
|
|
||||||
self.innerBox.addLayout(self.toolsBox)
|
self.outerBox.addLayout(self.toolsBox)
|
||||||
self.innerBox.addWidget(self.docView)
|
self.outerBox.addWidget(self.docView)
|
||||||
|
self.outerBox.setStretch(0, 0)
|
||||||
|
self.outerBox.setStretch(1, 1)
|
||||||
|
|
||||||
self.outerBox.addLayout(self.innerBox)
|
|
||||||
self.outerBox.addWidget(self.buttonBox)
|
|
||||||
self.setLayout(self.outerBox)
|
self.setLayout(self.outerBox)
|
||||||
|
|
||||||
self.innerBox.setStretch(0, 0)
|
|
||||||
self.innerBox.setStretch(1, 1)
|
|
||||||
|
|
||||||
self.show()
|
self.show()
|
||||||
|
|
||||||
logger.debug("GuiBuildNovel initialisation complete")
|
logger.debug("GuiBuildNovel initialisation complete")
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
from nw.gui.dialogs.about import GuiAbout
|
from nw.gui.dialogs.about import GuiAbout
|
||||||
from nw.gui.dialogs.configeditor import GuiConfigEditor
|
from nw.gui.dialogs.preferences import GuiPreferences
|
||||||
from nw.gui.dialogs.docmerge import GuiDocMerge
|
from nw.gui.dialogs.docmerge import GuiDocMerge
|
||||||
from nw.gui.dialogs.docsplit import GuiDocSplit
|
from nw.gui.dialogs.docsplit import GuiDocSplit
|
||||||
from nw.gui.dialogs.itemeditor import GuiItemEditor
|
from nw.gui.dialogs.itemeditor import GuiItemEditor
|
||||||
@@ -11,7 +11,7 @@ from nw.gui.dialogs.sessionlog import GuiSessionLogView
|
|||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
"GuiAbout",
|
"GuiAbout",
|
||||||
"GuiConfigEditor",
|
"GuiPreferences",
|
||||||
"GuiDocMerge",
|
"GuiDocMerge",
|
||||||
"GuiDocSplit",
|
"GuiDocSplit",
|
||||||
"GuiItemEditor",
|
"GuiItemEditor",
|
||||||
|
|||||||
+13
-10
@@ -119,16 +119,19 @@ class GuiAbout(QDialog):
|
|||||||
"<h2>About {name:s}</h2>"
|
"<h2>About {name:s}</h2>"
|
||||||
"<p>{copyright:s}.</p>"
|
"<p>{copyright:s}.</p>"
|
||||||
"<p>Website: <a href='{website:s}'>{domain:s}</a></p>"
|
"<p>Website: <a href='{website:s}'>{domain:s}</a></p>"
|
||||||
"<p>{name:s} is a markdown-like text editor designed for organising and writing "
|
"<p>{name:s} is a markdown-like text editor designed for "
|
||||||
"novels. It is written in Python 3 with a Qt5 GUI, using PyQt5.</p>"
|
"organising and writing novels. It is written in Python 3 with a "
|
||||||
"<p>{name:s} is free software: you can redistribute it and/or modify it under the "
|
"Qt5 GUI, using PyQt5.</p>"
|
||||||
"terms of the GNU General Public License as published by the Free Software Foundation, "
|
"<p>{name:s} is free software: you can redistribute it and/or "
|
||||||
"either version 3 of the License, or (at your option) any later version.</p>"
|
"modify it under the terms of the GNU General Public License as "
|
||||||
"<p>{name:s} is distributed in the hope that it will be useful, but WITHOUT ANY "
|
"published by the Free Software Foundation, either version 3 of "
|
||||||
"WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A "
|
"the License, or (at your option) any later version.</p>"
|
||||||
"PARTICULAR PURPOSE.</p>"
|
"<p>{name:s} is distributed in the hope that it will be useful, "
|
||||||
"<p>See the License tab for the full text, or visit the GNU website at "
|
"but WITHOUT ANY WARRANTY; without even the implied warranty of "
|
||||||
"<a href='https://www.gnu.org/licenses/gpl-3.0.html'>GPL v3.0</a> for more details.</p>"
|
"MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.</p>"
|
||||||
|
"<p>See the License tab for the full text, or visit the GNU website "
|
||||||
|
"at <a href='https://www.gnu.org/licenses/gpl-3.0.html'>GPL v3.0</a> "
|
||||||
|
"for more details.</p>"
|
||||||
"<h3>Credits</h3>"
|
"<h3>Credits</h3>"
|
||||||
"<p>{credits:s}</p>"
|
"<p>{credits:s}</p>"
|
||||||
).format(
|
).format(
|
||||||
|
|||||||
@@ -150,7 +150,9 @@ class GuiDocSplit(QDialog):
|
|||||||
), nwAlert.ERROR)
|
), nwAlert.ERROR)
|
||||||
return
|
return
|
||||||
|
|
||||||
fHandle = self.theProject.newFolder(srcItem.itemName, srcItem.itemClass, srcItem.parHandle)
|
fHandle = self.theProject.newFolder(
|
||||||
|
srcItem.itemName, srcItem.itemClass, srcItem.parHandle
|
||||||
|
)
|
||||||
self.theParent.treeView.revealTreeItem(fHandle)
|
self.theParent.treeView.revealTreeItem(fHandle)
|
||||||
logger.verbose("Creating folder %s" % fHandle)
|
logger.verbose("Creating folder %s" % fHandle)
|
||||||
|
|
||||||
@@ -165,7 +167,7 @@ class GuiDocSplit(QDialog):
|
|||||||
elif wTitle.startswith("### "):
|
elif wTitle.startswith("### "):
|
||||||
itemLayout = nwItemLayout.SCENE
|
itemLayout = nwItemLayout.SCENE
|
||||||
elif wTitle.startswith("#### "):
|
elif wTitle.startswith("#### "):
|
||||||
itemLayout = nwItemLayout.PAGE
|
itemLayout = nwItemLayout.SCENE
|
||||||
|
|
||||||
wTitle = wTitle.lstrip("#")
|
wTitle = wTitle.lstrip("#")
|
||||||
wTitle = wTitle.strip()
|
wTitle = wTitle.strip()
|
||||||
@@ -174,7 +176,9 @@ class GuiDocSplit(QDialog):
|
|||||||
newItem = self.theProject.projTree[nHandle]
|
newItem = self.theProject.projTree[nHandle]
|
||||||
newItem.setLayout(itemLayout)
|
newItem.setLayout(itemLayout)
|
||||||
logger.verbose(
|
logger.verbose(
|
||||||
"Creating new document %s with text from line %d to %d" % (nHandle, iStart, iEnd-1)
|
"Creating new document %s with text from line %d to %d" % (
|
||||||
|
nHandle, iStart, iEnd-1
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
theText = "\n".join(theLines[iStart:iEnd])
|
theText = "\n".join(theLines[iStart:iEnd])
|
||||||
@@ -227,7 +231,9 @@ class GuiDocSplit(QDialog):
|
|||||||
|
|
||||||
spLevel = self.splitLevel.currentData()
|
spLevel = self.splitLevel.currentData()
|
||||||
self.optState.setValue("GuiDocSplit", "spLevel", spLevel)
|
self.optState.setValue("GuiDocSplit", "spLevel", spLevel)
|
||||||
logger.debug("Scanning document %s for headings level <= %d" % (self.sourceItem, spLevel))
|
logger.debug(
|
||||||
|
"Scanning document %s for headings level <= %d" % (self.sourceItem, spLevel)
|
||||||
|
)
|
||||||
|
|
||||||
lineNo = 0
|
lineNo = 0
|
||||||
for aLine in theText.splitlines():
|
for aLine in theText.splitlines():
|
||||||
|
|||||||
@@ -44,12 +44,12 @@ from nw.constants import nwAlert, nwQuotes
|
|||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
class GuiConfigEditor(PagedDialog):
|
class GuiPreferences(PagedDialog):
|
||||||
|
|
||||||
def __init__(self, theParent, theProject):
|
def __init__(self, theParent, theProject):
|
||||||
PagedDialog.__init__(self, theParent)
|
PagedDialog.__init__(self, theParent)
|
||||||
|
|
||||||
logger.debug("Initialising ConfigEditor ...")
|
logger.debug("Initialising GuiPreferences ...")
|
||||||
|
|
||||||
self.mainConf = nw.CONFIG
|
self.mainConf = nw.CONFIG
|
||||||
self.theParent = theParent
|
self.theParent = theParent
|
||||||
@@ -74,7 +74,7 @@ class GuiConfigEditor(PagedDialog):
|
|||||||
|
|
||||||
self.show()
|
self.show()
|
||||||
|
|
||||||
logger.debug("ConfigEditor initialisation complete")
|
logger.debug("GuiPreferences initialisation complete")
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -122,7 +122,7 @@ class GuiConfigEditor(PagedDialog):
|
|||||||
self.close()
|
self.close()
|
||||||
return
|
return
|
||||||
|
|
||||||
# END Class GuiConfigEditor
|
# END Class GuiPreferences
|
||||||
|
|
||||||
class GuiConfigEditGeneralTab(QWidget):
|
class GuiConfigEditGeneralTab(QWidget):
|
||||||
|
|
||||||
@@ -228,7 +228,7 @@ class GuiConfigEditGeneralTab(QWidget):
|
|||||||
|
|
||||||
## Backup Path
|
## Backup Path
|
||||||
self.backupPath = self.mainConf.backupPath
|
self.backupPath = self.mainConf.backupPath
|
||||||
self.backupGetPath = QPushButton(self.theTheme.getIcon("folder-open"),"Select Folder")
|
self.backupGetPath = QPushButton("Browse")
|
||||||
self.backupGetPath.clicked.connect(self._backupFolder)
|
self.backupGetPath.clicked.connect(self._backupFolder)
|
||||||
self.backupPathRow = self.mainForm.addRow(
|
self.backupPathRow = self.mainForm.addRow(
|
||||||
"Backup storage location",
|
"Backup storage location",
|
||||||
@@ -362,7 +362,9 @@ class GuiProjectEditStatus(QWidget):
|
|||||||
self.listBox.takeItem(iRow)
|
self.listBox.takeItem(iRow)
|
||||||
self.colChanged = True
|
self.colChanged = True
|
||||||
else:
|
else:
|
||||||
self.theParent.makeAlert("Cannot delete status item that is in use.",nwAlert.ERROR)
|
self.theParent.makeAlert(
|
||||||
|
"Cannot delete status item that is in use.", nwAlert.ERROR
|
||||||
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
def _saveItem(self):
|
def _saveItem(self):
|
||||||
|
|||||||
@@ -180,11 +180,15 @@ class GuiSessionLogView(QDialog):
|
|||||||
inData = inLine.split()
|
inData = inLine.split()
|
||||||
if len(inData) != 8:
|
if len(inData) != 8:
|
||||||
continue
|
continue
|
||||||
dStart = datetime.strptime("%s %s" % (inData[1],inData[2]), nwConst.tStampFmt)
|
dStart = datetime.strptime(
|
||||||
dEnd = datetime.strptime("%s %s" % (inData[4],inData[5]), nwConst.tStampFmt)
|
"%s %s" % (inData[1],inData[2]), nwConst.tStampFmt
|
||||||
|
)
|
||||||
|
dEnd = datetime.strptime(
|
||||||
|
"%s %s" % (inData[4],inData[5]), nwConst.tStampFmt
|
||||||
|
)
|
||||||
nWords = int(inData[7])
|
nWords = int(inData[7])
|
||||||
tDiff = dEnd - dStart
|
tDiff = dEnd - dStart
|
||||||
sDiff = tDiff.total_seconds()
|
sDiff = tDiff.total_seconds()
|
||||||
|
|
||||||
self.timeTotal += sDiff
|
self.timeTotal += sDiff
|
||||||
if abs(nWords) > 0:
|
if abs(nWords) > 0:
|
||||||
@@ -196,7 +200,9 @@ class GuiSessionLogView(QDialog):
|
|||||||
if hideNegative and nWords < 0:
|
if hideNegative and nWords < 0:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
newItem = QTreeWidgetItem([str(dStart),self._formatTime(sDiff),str(nWords),""])
|
newItem = QTreeWidgetItem(
|
||||||
|
[str(dStart), self._formatTime(sDiff), str(nWords), ""]
|
||||||
|
)
|
||||||
|
|
||||||
newItem.setTextAlignment(1,Qt.AlignRight)
|
newItem.setTextAlignment(1,Qt.AlignRight)
|
||||||
newItem.setTextAlignment(2,Qt.AlignRight)
|
newItem.setTextAlignment(2,Qt.AlignRight)
|
||||||
@@ -208,7 +214,9 @@ class GuiSessionLogView(QDialog):
|
|||||||
self.listBox.addTopLevelItem(newItem)
|
self.listBox.addTopLevelItem(newItem)
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.theParent.makeAlert(["Failed to read session log file.",str(e)], nwAlert.ERROR)
|
self.theParent.makeAlert(
|
||||||
|
["Failed to read session log file.",str(e)], nwAlert.ERROR
|
||||||
|
)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
self.labelFilter.setText(self._formatTime(self.timeFilter))
|
self.labelFilter.setText(self._formatTime(self.timeFilter))
|
||||||
|
|||||||
@@ -405,6 +405,8 @@ class GuiDocEditor(QTextEdit):
|
|||||||
def setCursorPosition(self, thePosition):
|
def setCursorPosition(self, thePosition):
|
||||||
"""Move the cursor to a given position in the document.
|
"""Move the cursor to a given position in the document.
|
||||||
"""
|
"""
|
||||||
|
if not isinstance(thePosition, int):
|
||||||
|
return False
|
||||||
if thePosition >= 0:
|
if thePosition >= 0:
|
||||||
theCursor = self.textCursor()
|
theCursor = self.textCursor()
|
||||||
theCursor.setPosition(thePosition)
|
theCursor.setPosition(thePosition)
|
||||||
@@ -420,12 +422,13 @@ class GuiDocEditor(QTextEdit):
|
|||||||
def setCursorLine(self, theLine):
|
def setCursorLine(self, theLine):
|
||||||
"""Move the cursor to a given line in the document.
|
"""Move the cursor to a given line in the document.
|
||||||
"""
|
"""
|
||||||
if theLine is None:
|
if not isinstance(theLine, int):
|
||||||
return False
|
return False
|
||||||
if theLine >= 0:
|
if theLine >= 0:
|
||||||
theBlock = self.qDocument.findBlockByLineNumber(theLine)
|
theBlock = self.qDocument.findBlockByLineNumber(theLine)
|
||||||
if theBlock:
|
if theBlock:
|
||||||
self.setCursorPosition(theBlock.position())
|
self.setCursorPosition(theBlock.position())
|
||||||
|
logger.verbose("Cursor moved to line %d" % theLine)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
##
|
##
|
||||||
@@ -480,7 +483,9 @@ class GuiDocEditor(QTextEdit):
|
|||||||
self.hLight.rehighlight()
|
self.hLight.rehighlight()
|
||||||
qApp.restoreOverrideCursor()
|
qApp.restoreOverrideCursor()
|
||||||
afTime = time()
|
afTime = time()
|
||||||
logger.debug("Document re-highlighted in %.3f milliseconds" % (1000*(afTime-bfTime)))
|
logger.debug(
|
||||||
|
"Document re-highlighted in %.3f milliseconds" % (1000*(afTime-bfTime))
|
||||||
|
)
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
@@ -717,7 +722,9 @@ class GuiDocEditor(QTextEdit):
|
|||||||
"""
|
"""
|
||||||
sinceActive = time()-self.lastEdit
|
sinceActive = time()-self.lastEdit
|
||||||
if sinceActive > 5*self.wcInterval:
|
if sinceActive > 5*self.wcInterval:
|
||||||
logger.debug("Stopping word count timer: no activity last %.1f seconds" % sinceActive)
|
logger.debug(
|
||||||
|
"Stopping word count timer: no activity last %.1f seconds" % sinceActive
|
||||||
|
)
|
||||||
self.wcTimer.stop()
|
self.wcTimer.stop()
|
||||||
elif self.wCounter.isRunning():
|
elif self.wCounter.isRunning():
|
||||||
logger.verbose("Word counter thread is busy")
|
logger.verbose("Word counter thread is busy")
|
||||||
@@ -953,7 +960,9 @@ class GuiDocEditor(QTextEdit):
|
|||||||
theText = newText
|
theText = newText
|
||||||
cOffset -= 0
|
cOffset -= 0
|
||||||
else:
|
else:
|
||||||
logger.error("Unknown or unsupported block format requested: %s" % str(docAction))
|
logger.error(
|
||||||
|
"Unknown or unsupported block format requested: %s" % str(docAction)
|
||||||
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
# Replace the block text
|
# Replace the block text
|
||||||
|
|||||||
@@ -396,7 +396,7 @@ class GuiDocTree(QTreeWidget):
|
|||||||
trItemP.takeChild(tIndex)
|
trItemP.takeChild(tIndex)
|
||||||
del self.theProject.projTree[tHandle]
|
del self.theProject.projTree[tHandle]
|
||||||
else:
|
else:
|
||||||
self.makeAlert(["Cannot delete folder.","It is not empty."], nwAlert.ERROR)
|
self.makeAlert("Cannot delete folder. It is not empty.", nwAlert.ERROR)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
elif nwItemS.itemType == nwItemType.ROOT:
|
elif nwItemS.itemType == nwItemType.ROOT:
|
||||||
@@ -407,7 +407,7 @@ class GuiDocTree(QTreeWidget):
|
|||||||
self.theParent.mainMenu.setAvailableRoot()
|
self.theParent.mainMenu.setAvailableRoot()
|
||||||
self.theProject.setProjectChanged(True)
|
self.theProject.setProjectChanged(True)
|
||||||
else:
|
else:
|
||||||
self.makeAlert(["Cannot delete root folder.","It is not empty."], nwAlert.ERROR)
|
self.makeAlert("Cannot delete root folder. It is not empty.", nwAlert.ERROR)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|||||||
@@ -28,7 +28,7 @@
|
|||||||
import logging
|
import logging
|
||||||
import nw
|
import nw
|
||||||
|
|
||||||
from PyQt5.QtCore import Qt
|
from PyQt5.QtCore import Qt, QUrl
|
||||||
from PyQt5.QtWidgets import QTextBrowser
|
from PyQt5.QtWidgets import QTextBrowser
|
||||||
from PyQt5.QtGui import QTextOption, QFont, QPalette, QColor, QTextCursor
|
from PyQt5.QtGui import QTextOption, QFont, QPalette, QColor, QTextCursor
|
||||||
|
|
||||||
@@ -134,6 +134,7 @@ class GuiDocViewer(QTextBrowser):
|
|||||||
sPos = self.verticalScrollBar().value()
|
sPos = self.verticalScrollBar().value()
|
||||||
aDoc = ToHtml(self.theProject, self.theParent)
|
aDoc = ToHtml(self.theProject, self.theParent)
|
||||||
aDoc.setPreview(True, self.mainConf.viewComments)
|
aDoc.setPreview(True, self.mainConf.viewComments)
|
||||||
|
aDoc.setLinkHeaders(True)
|
||||||
aDoc.setText(tHandle)
|
aDoc.setText(tHandle)
|
||||||
aDoc.doAutoReplace()
|
aDoc.doAutoReplace()
|
||||||
aDoc.tokenizeText()
|
aDoc.tokenizeText()
|
||||||
@@ -164,19 +165,17 @@ class GuiDocViewer(QTextBrowser):
|
|||||||
index being up to date.
|
index being up to date.
|
||||||
"""
|
"""
|
||||||
logger.debug("Loading document from tag '%s'" % theTag)
|
logger.debug("Loading document from tag '%s'" % theTag)
|
||||||
|
tHandle, onLine, sTitle = self.theParent.theIndex.getTagSource(theTag)
|
||||||
if theTag in self.theParent.theIndex.tagIndex.keys():
|
if tHandle is None:
|
||||||
theTarget = self.theParent.theIndex.tagIndex[theTag]
|
self.theParent.makeAlert((
|
||||||
|
"Could not find the reference for tag '%s'. It either doesn't "
|
||||||
|
"exist, or the index is out of date. The index can be updated "
|
||||||
|
"from the Tools menu, or by pressing F9."
|
||||||
|
) % theTag, nwAlert.ERROR)
|
||||||
|
return
|
||||||
else:
|
else:
|
||||||
logger.debug("The tag was not found in the index")
|
self.loadText(tHandle)
|
||||||
return False
|
self.navigateTo("#head_%s:%s" % (tHandle, sTitle))
|
||||||
|
|
||||||
if len(theTarget) != 3:
|
|
||||||
# Just to make sure the index is not messed up
|
|
||||||
return False
|
|
||||||
|
|
||||||
self.loadText(theTarget[1])
|
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def docAction(self, theAction):
|
def docAction(self, theAction):
|
||||||
@@ -200,6 +199,15 @@ class GuiDocViewer(QTextBrowser):
|
|||||||
return False
|
return False
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
def navigateTo(self, navLink):
|
||||||
|
"""Go to a specific #link in the document.
|
||||||
|
"""
|
||||||
|
if not isinstance(navLink, str):
|
||||||
|
return False
|
||||||
|
if navLink.startswith("#"):
|
||||||
|
self.setSource(QUrl(navLink))
|
||||||
|
return True
|
||||||
|
|
||||||
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 if
|
||||||
Config.textFixedW is enabled or we're in Zen mode. Otherwise,
|
Config.textFixedW is enabled or we're in Zen mode. Otherwise,
|
||||||
@@ -233,6 +241,33 @@ class GuiDocViewer(QTextBrowser):
|
|||||||
self.updateDocMargins()
|
self.updateDocMargins()
|
||||||
return
|
return
|
||||||
|
|
||||||
|
##
|
||||||
|
# Setters
|
||||||
|
##
|
||||||
|
|
||||||
|
def setCursorPosition(self, thePosition):
|
||||||
|
"""Move the cursor to a given position in the document.
|
||||||
|
"""
|
||||||
|
if not isinstance(thePosition, int):
|
||||||
|
return False
|
||||||
|
if thePosition >= 0:
|
||||||
|
theCursor = self.textCursor()
|
||||||
|
theCursor.setPosition(thePosition)
|
||||||
|
self.setTextCursor(theCursor)
|
||||||
|
return True
|
||||||
|
|
||||||
|
def setCursorLine(self, theLine):
|
||||||
|
"""Move the cursor to a given line in the document.
|
||||||
|
"""
|
||||||
|
if not isinstance(theLine, int):
|
||||||
|
return False
|
||||||
|
if theLine >= 0:
|
||||||
|
theBlock = self.qDocument.findBlockByLineNumber(theLine)
|
||||||
|
if theBlock:
|
||||||
|
self.setCursorPosition(theBlock.position())
|
||||||
|
logger.verbose("Cursor moved to line %d" % theLine)
|
||||||
|
return True
|
||||||
|
|
||||||
##
|
##
|
||||||
# Events
|
# Events
|
||||||
##
|
##
|
||||||
@@ -262,25 +297,11 @@ class GuiDocViewer(QTextBrowser):
|
|||||||
"""Slot for a link in the document being clicked.
|
"""Slot for a link in the document being clicked.
|
||||||
"""
|
"""
|
||||||
theLink = theURL.url()
|
theLink = theURL.url()
|
||||||
tHandle = None
|
logger.verbose("Clicked link: '%s'" % theLink)
|
||||||
onLine = 0
|
|
||||||
theTag = ""
|
|
||||||
if len(theLink) > 0:
|
if len(theLink) > 0:
|
||||||
theBits = theLink.split("=")
|
theBits = theLink.split("=")
|
||||||
if len(theBits) == 2:
|
if len(theBits) == 2:
|
||||||
theTag = theBits[1]
|
self.loadFromTag(theBits[1])
|
||||||
tHandle, onLine = self.theParent.theIndex.getTagSource(theBits[1])
|
|
||||||
|
|
||||||
if tHandle is None:
|
|
||||||
self.theParent.makeAlert((
|
|
||||||
"Could not find the reference for tag '%s'. It either doesn't exist, or the index "
|
|
||||||
"is out of date. The index can be updated from the Tools menu.") % theTag,
|
|
||||||
nwAlert.ERROR
|
|
||||||
)
|
|
||||||
return
|
|
||||||
else:
|
|
||||||
self.loadText(tHandle)
|
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
def _makeStyleSheet(self):
|
def _makeStyleSheet(self):
|
||||||
|
|||||||
@@ -107,7 +107,9 @@ class GuiDocViewDetails(QWidget):
|
|||||||
for tHandle in theRefs:
|
for tHandle in theRefs:
|
||||||
tItem = self.theProject.projTree[tHandle]
|
tItem = self.theProject.projTree[tHandle]
|
||||||
if tItem is not None:
|
if tItem is not None:
|
||||||
theList.append("<a href='#tag=%s'>%s</a>" % (tHandle,tItem.itemName))
|
theList.append("<a href='#head_%s:%s'>%s</a>" % (
|
||||||
|
tHandle, theRefs[tHandle], tItem.itemName
|
||||||
|
))
|
||||||
|
|
||||||
self.refList.setText(", ".join(theList))
|
self.refList.setText(", ".join(theList))
|
||||||
self.refList.adjustSize()
|
self.refList.adjustSize()
|
||||||
@@ -122,9 +124,10 @@ class GuiDocViewDetails(QWidget):
|
|||||||
"""Capture the link-click and forward it to the document viewer
|
"""Capture the link-click and forward it to the document viewer
|
||||||
class for handling.
|
class for handling.
|
||||||
"""
|
"""
|
||||||
if len(theLink) == 18:
|
logger.verbose("Clicked link: '%s'" % theLink)
|
||||||
tHandle = theLink[-13:]
|
if len(theLink) == 27:
|
||||||
self.theParent.viewDocument(tHandle)
|
tHandle = theLink[6:19]
|
||||||
|
self.theParent.viewDocument(tHandle, theLink)
|
||||||
return
|
return
|
||||||
|
|
||||||
def _doShowHide(self, chState):
|
def _doShowHide(self, chState):
|
||||||
|
|||||||
+6
-2
@@ -249,7 +249,9 @@ class GuiIcons:
|
|||||||
try:
|
try:
|
||||||
confParser.read_file(open(themeConf, mode="r", encoding="utf8"))
|
confParser.read_file(open(themeConf, mode="r", encoding="utf8"))
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.theParent.makeAlert(["Could not load theme config file.",str(e)],nwAlert.ERROR)
|
self.theParent.makeAlert(
|
||||||
|
["Could not load theme config file.",str(e)], nwAlert.ERROR
|
||||||
|
)
|
||||||
continue
|
continue
|
||||||
themeName = ""
|
themeName = ""
|
||||||
if confParser.has_section("Main"):
|
if confParser.has_section("Main"):
|
||||||
@@ -295,7 +297,9 @@ class GuiIcons:
|
|||||||
|
|
||||||
# Finally. we check if we have a fallback icon
|
# Finally. we check if we have a fallback icon
|
||||||
if self.mainConf.guiDark:
|
if self.mainConf.guiDark:
|
||||||
fbackIcon = path.join(self.mainConf.iconPath, self.fbackName, "%s-dark.svg" % iconKey)
|
fbackIcon = path.join(
|
||||||
|
self.mainConf.iconPath, self.fbackName, "%s-dark.svg" % iconKey
|
||||||
|
)
|
||||||
if path.isfile(fbackIcon):
|
if path.isfile(fbackIcon):
|
||||||
logger.verbose("Loading icon '%s' from fallback theme" % iconKey)
|
logger.verbose("Loading icon '%s' from fallback theme" % iconKey)
|
||||||
return QIcon(fbackIcon)
|
return QIcon(fbackIcon)
|
||||||
|
|||||||
+5
-1
@@ -137,13 +137,17 @@ class GuiMainStatus(QStatusBar):
|
|||||||
self._updateTime()
|
self._updateTime()
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
##
|
||||||
|
# Setters
|
||||||
|
##
|
||||||
|
|
||||||
def setRefTime(self, theTime):
|
def setRefTime(self, theTime):
|
||||||
"""Set the reference time for the status bar clock.
|
"""Set the reference time for the status bar clock.
|
||||||
"""
|
"""
|
||||||
self.refTime = theTime
|
self.refTime = theTime
|
||||||
return
|
return
|
||||||
|
|
||||||
def setStatus(self, theMessage, timeOut=10.0):
|
def setStatus(self, theMessage, timeOut=20.0):
|
||||||
"""Set the status bar message to display for 'timeOut' seconds.
|
"""Set the status bar message to display for 'timeOut' seconds.
|
||||||
"""
|
"""
|
||||||
self.showMessage(theMessage, int(timeOut*1000))
|
self.showMessage(theMessage, int(timeOut*1000))
|
||||||
|
|||||||
+6
-2
@@ -284,7 +284,9 @@ class GuiTheme:
|
|||||||
try:
|
try:
|
||||||
confParser.read_file(open(themeConf, mode="r", encoding="utf8"))
|
confParser.read_file(open(themeConf, mode="r", encoding="utf8"))
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.theParent.makeAlert(["Could not load theme config file.",str(e)],nwAlert.ERROR)
|
self.theParent.makeAlert(
|
||||||
|
["Could not load theme config file.",str(e)], nwAlert.ERROR
|
||||||
|
)
|
||||||
continue
|
continue
|
||||||
themeName = ""
|
themeName = ""
|
||||||
if confParser.has_section("Main"):
|
if confParser.has_section("Main"):
|
||||||
@@ -314,7 +316,9 @@ class GuiTheme:
|
|||||||
try:
|
try:
|
||||||
confParser.read_file(open(syntaxPath, mode="r", encoding="utf8"))
|
confParser.read_file(open(syntaxPath, mode="r", encoding="utf8"))
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.theParent.makeAlert(["Could not load syntax file.",str(e)],nwAlert.ERROR)
|
self.theParent.makeAlert(
|
||||||
|
["Could not load syntax file.",str(e)], nwAlert.ERROR
|
||||||
|
)
|
||||||
return []
|
return []
|
||||||
syntaxName = ""
|
syntaxName = ""
|
||||||
if confParser.has_section("Main"):
|
if confParser.has_section("Main"):
|
||||||
|
|||||||
+25
-19
@@ -42,7 +42,7 @@ from PyQt5.QtWidgets import (
|
|||||||
from nw.gui import (
|
from nw.gui import (
|
||||||
GuiMainMenu, GuiMainStatus, GuiTheme, GuiDocTree, GuiDocEditor,
|
GuiMainMenu, GuiMainStatus, GuiTheme, GuiDocTree, GuiDocEditor,
|
||||||
GuiDocViewer, GuiDocDetails, GuiSearchBar, GuiNoticeBar, GuiDocViewDetails,
|
GuiDocViewer, GuiDocDetails, GuiSearchBar, GuiNoticeBar, GuiDocViewDetails,
|
||||||
GuiConfigEditor, GuiProjectSettings, GuiItemEditor, GuiProjectOutline,
|
GuiPreferences, GuiProjectSettings, GuiItemEditor, GuiProjectOutline,
|
||||||
GuiSessionLogView, GuiDocMerge, GuiDocSplit, GuiProjectLoad, GuiBuildNovel
|
GuiSessionLogView, GuiDocMerge, GuiDocSplit, GuiProjectLoad, GuiBuildNovel
|
||||||
)
|
)
|
||||||
from nw.core import NWProject, NWDoc, NWIndex
|
from nw.core import NWProject, NWDoc, NWIndex
|
||||||
@@ -485,7 +485,7 @@ class GuiMain(QMainWindow):
|
|||||||
self.docEditor.saveText()
|
self.docEditor.saveText()
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def viewDocument(self, tHandle=None):
|
def viewDocument(self, tHandle=None, navLink=None):
|
||||||
"""Load a document for viewing in the view panel.
|
"""Load a document for viewing in the view panel.
|
||||||
"""
|
"""
|
||||||
if tHandle is None:
|
if tHandle is None:
|
||||||
@@ -503,13 +503,15 @@ class GuiMain(QMainWindow):
|
|||||||
# Make sure main tab is in Editor view
|
# Make sure main tab is in Editor view
|
||||||
self.tabWidget.setCurrentWidget(self.splitView)
|
self.tabWidget.setCurrentWidget(self.splitView)
|
||||||
|
|
||||||
if self.docViewer.loadText(tHandle) and not self.viewPane.isVisible():
|
if self.docViewer.loadText(tHandle):
|
||||||
bPos = self.splitMain.sizes()
|
if not self.viewPane.isVisible():
|
||||||
self.viewPane.setVisible(True)
|
bPos = self.splitMain.sizes()
|
||||||
vPos = [0,0]
|
self.viewPane.setVisible(True)
|
||||||
vPos[0] = int(bPos[1]/2)
|
vPos = [0,0]
|
||||||
vPos[1] = bPos[1]-vPos[0]
|
vPos[0] = int(bPos[1]/2)
|
||||||
self.splitView.setSizes(vPos)
|
vPos[1] = bPos[1]-vPos[0]
|
||||||
|
self.splitView.setSizes(vPos)
|
||||||
|
self.docViewer.navigateTo(navLink)
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
@@ -551,7 +553,7 @@ class GuiMain(QMainWindow):
|
|||||||
|
|
||||||
if self.docEditor.theHandle is None:
|
if self.docEditor.theHandle is None:
|
||||||
self.makeAlert(
|
self.makeAlert(
|
||||||
["Please open a document to import the text file into."],
|
"Please open a document to import the text file into.",
|
||||||
nwAlert.ERROR
|
nwAlert.ERROR
|
||||||
)
|
)
|
||||||
return False
|
return False
|
||||||
@@ -666,6 +668,12 @@ class GuiMain(QMainWindow):
|
|||||||
|
|
||||||
theDoc = NWDoc(self.theProject, self)
|
theDoc = NWDoc(self.theProject, self)
|
||||||
for nDone, tItem in enumerate(self.theProject.projTree):
|
for nDone, tItem in enumerate(self.theProject.projTree):
|
||||||
|
|
||||||
|
if tItem is not None:
|
||||||
|
self.statusBar.setStatus("Indexing: '%s'" % tItem.itemName)
|
||||||
|
else:
|
||||||
|
self.statusBar.setStatus("Indexing: Unknown item")
|
||||||
|
|
||||||
if tItem is not None and tItem.itemType == nwItemType.FILE:
|
if tItem is not None and tItem.itemType == nwItemType.FILE:
|
||||||
logger.verbose("Scanning: %s" % tItem.itemName)
|
logger.verbose("Scanning: %s" % tItem.itemName)
|
||||||
theText = theDoc.openDocument(tItem.itemHandle, showStatus=False)
|
theText = theDoc.openDocument(tItem.itemHandle, showStatus=False)
|
||||||
@@ -681,16 +689,14 @@ class GuiMain(QMainWindow):
|
|||||||
self.treeView.propagateCount(tItem.itemHandle, wC)
|
self.treeView.propagateCount(tItem.itemHandle, wC)
|
||||||
self.treeView.projectWordCount()
|
self.treeView.projectWordCount()
|
||||||
|
|
||||||
self.statusBar.setStatus("Building index: %.2f%%" % (100.0*(nDone + 1)/nItems))
|
|
||||||
|
|
||||||
self.docEditor.reloadText()
|
|
||||||
qApp.restoreOverrideCursor()
|
|
||||||
tEnd = time()
|
tEnd = time()
|
||||||
|
self.statusBar.setStatus("Indexing completed in %.1f ms" % ((tEnd - tStart)*1000.0))
|
||||||
|
self.docEditor.reloadText()
|
||||||
|
|
||||||
|
qApp.restoreOverrideCursor()
|
||||||
|
|
||||||
if self.mainConf.showGUI:
|
if self.mainConf.showGUI:
|
||||||
self.makeAlert(
|
self.makeAlert("The project index has been successfully rebuilt.", nwAlert.INFO)
|
||||||
"Project index rebuilt in %.3f seconds." % (tEnd - tStart), nwAlert.INFO
|
|
||||||
)
|
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
@@ -735,7 +741,7 @@ class GuiMain(QMainWindow):
|
|||||||
def editConfigDialog(self):
|
def editConfigDialog(self):
|
||||||
"""Open the preferences dialog.
|
"""Open the preferences dialog.
|
||||||
"""
|
"""
|
||||||
dlgConf = GuiConfigEditor(self, self.theProject)
|
dlgConf = GuiPreferences(self, self.theProject)
|
||||||
if dlgConf.exec_() == QDialog.Accepted:
|
if dlgConf.exec_() == QDialog.Accepted:
|
||||||
logger.debug("Applying new preferences")
|
logger.debug("Applying new preferences")
|
||||||
self.initMain()
|
self.initMain()
|
||||||
@@ -776,7 +782,7 @@ class GuiMain(QMainWindow):
|
|||||||
0 = info, 1 = warning, and 2 = error.
|
0 = info, 1 = warning, and 2 = error.
|
||||||
"""
|
"""
|
||||||
if isinstance(theMessage, list):
|
if isinstance(theMessage, list):
|
||||||
popMsg = " ".join(theMessage)
|
popMsg = "<br>".join(theMessage)
|
||||||
logMsg = theMessage
|
logMsg = theMessage
|
||||||
else:
|
else:
|
||||||
popMsg = theMessage
|
popMsg = theMessage
|
||||||
|
|||||||
@@ -0,0 +1,82 @@
|
|||||||
|
[
|
||||||
|
[
|
||||||
|
"content/14298de4d9524.nwd",
|
||||||
|
"CHARACTER",
|
||||||
|
"John Smith"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"content/53b69b83cdafc.nwd",
|
||||||
|
"NOVEL",
|
||||||
|
"Title Page"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"content/5eaea4e8cdee8.nwd",
|
||||||
|
"WORLD",
|
||||||
|
"Mars"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"content/636b6aa9b697b.nwd",
|
||||||
|
"NOVEL",
|
||||||
|
"Making a Scene"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"content/6a2d6d5f4f401.nwd",
|
||||||
|
"NOVEL",
|
||||||
|
"Chapter One"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"content/88706ddc78b1b.nwd",
|
||||||
|
"NOVEL",
|
||||||
|
"Chapter Two"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"content/96b68994dfa3d.nwd",
|
||||||
|
"NOVEL",
|
||||||
|
"A Note on Structure"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"content/974e400180a99.nwd",
|
||||||
|
"NOVEL",
|
||||||
|
"Page"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"content/ae7339df26ded.nwd",
|
||||||
|
"NOVEL",
|
||||||
|
"We Found John!"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"content/b3e74dbc1f584.nwd",
|
||||||
|
"WORLD",
|
||||||
|
"Earth"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"content/b8136a5a774a0.nwd",
|
||||||
|
"NOVEL",
|
||||||
|
"Delete Me!"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"content/ba8a28a246524.nwd",
|
||||||
|
"NOVEL",
|
||||||
|
"Interlude"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"content/bb2c23b3c42cc.nwd",
|
||||||
|
"CHARACTER",
|
||||||
|
"Jane Smith"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"content/bc0cbd2a407f3.nwd",
|
||||||
|
"NOVEL",
|
||||||
|
"Another Scene"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"content/edca4be2fcaf8.nwd",
|
||||||
|
"NOVEL",
|
||||||
|
"Part One"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"content/f1471bef9f2ae.nwd",
|
||||||
|
"WORLD",
|
||||||
|
"Space"
|
||||||
|
]
|
||||||
|
]
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
|
||||||
|
Table of Contents
|
||||||
|
===================
|
||||||
|
|
||||||
|
File Name Class Document Label
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
content/14298de4d9524.nwd CHARACTER John Smith
|
||||||
|
content/53b69b83cdafc.nwd NOVEL Title Page
|
||||||
|
content/5eaea4e8cdee8.nwd WORLD Mars
|
||||||
|
content/636b6aa9b697b.nwd NOVEL Making a Scene
|
||||||
|
content/6a2d6d5f4f401.nwd NOVEL Chapter One
|
||||||
|
content/88706ddc78b1b.nwd NOVEL Chapter Two
|
||||||
|
content/96b68994dfa3d.nwd NOVEL A Note on Structure
|
||||||
|
content/974e400180a99.nwd NOVEL Page
|
||||||
|
content/ae7339df26ded.nwd NOVEL We Found John!
|
||||||
|
content/b3e74dbc1f584.nwd WORLD Earth
|
||||||
|
content/b8136a5a774a0.nwd NOVEL Delete Me!
|
||||||
|
content/ba8a28a246524.nwd NOVEL Interlude
|
||||||
|
content/bb2c23b3c42cc.nwd CHARACTER Jane Smith
|
||||||
|
content/bc0cbd2a407f3.nwd NOVEL Another Scene
|
||||||
|
content/edca4be2fcaf8.nwd NOVEL Part One
|
||||||
|
content/f1471bef9f2ae.nwd WORLD Space
|
||||||
|
|
||||||
+4
-20
@@ -1,5 +1,5 @@
|
|||||||
<?xml version='1.0' encoding='utf-8'?>
|
<?xml version='1.0' encoding='utf-8'?>
|
||||||
<novelWriterXML appVersion="0.6.1" hexVersion="0x000601f0" fileVersion="1.0" saveCount="186" autoCount="28" timeStamp="2020-05-28 09:59:15">
|
<novelWriterXML appVersion="0.7.0rc1" hexVersion="0x000700c1" fileVersion="1.1" saveCount="202" autoCount="29" timeStamp="2020-05-30 00:19:45" editTime="35">
|
||||||
<project>
|
<project>
|
||||||
<name>Sample Project</name>
|
<name>Sample Project</name>
|
||||||
<title>Sample Project</title>
|
<title>Sample Project</title>
|
||||||
@@ -11,7 +11,7 @@
|
|||||||
<spellCheck>True</spellCheck>
|
<spellCheck>True</spellCheck>
|
||||||
<autoOutline>True</autoOutline>
|
<autoOutline>True</autoOutline>
|
||||||
<lastEdited>636b6aa9b697b</lastEdited>
|
<lastEdited>636b6aa9b697b</lastEdited>
|
||||||
<lastViewed>ba8a28a246524</lastViewed>
|
<lastViewed>b3e74dbc1f584</lastViewed>
|
||||||
<lastWordCount>914</lastWordCount>
|
<lastWordCount>914</lastWordCount>
|
||||||
<autoReplace>
|
<autoReplace>
|
||||||
<A>B</A>
|
<A>B</A>
|
||||||
@@ -57,7 +57,6 @@
|
|||||||
<type>FILE</type>
|
<type>FILE</type>
|
||||||
<class>NOVEL</class>
|
<class>NOVEL</class>
|
||||||
<status>Started</status>
|
<status>Started</status>
|
||||||
<expanded>False</expanded>
|
|
||||||
<exported>True</exported>
|
<exported>True</exported>
|
||||||
<layout>TITLE</layout>
|
<layout>TITLE</layout>
|
||||||
<charCount>72</charCount>
|
<charCount>72</charCount>
|
||||||
@@ -70,10 +69,9 @@
|
|||||||
<type>FILE</type>
|
<type>FILE</type>
|
||||||
<class>NOVEL</class>
|
<class>NOVEL</class>
|
||||||
<status>New</status>
|
<status>New</status>
|
||||||
<expanded>False</expanded>
|
|
||||||
<exported>True</exported>
|
<exported>True</exported>
|
||||||
<layout>PAGE</layout>
|
<layout>PAGE</layout>
|
||||||
<charCount>208</charCount>
|
<charCount>210</charCount>
|
||||||
<wordCount>40</wordCount>
|
<wordCount>40</wordCount>
|
||||||
<paraCount>2</paraCount>
|
<paraCount>2</paraCount>
|
||||||
<cursorPos>213</cursorPos>
|
<cursorPos>213</cursorPos>
|
||||||
@@ -83,7 +81,6 @@
|
|||||||
<type>FILE</type>
|
<type>FILE</type>
|
||||||
<class>NOVEL</class>
|
<class>NOVEL</class>
|
||||||
<status>New</status>
|
<status>New</status>
|
||||||
<expanded>False</expanded>
|
|
||||||
<exported>True</exported>
|
<exported>True</exported>
|
||||||
<layout>PARTITION</layout>
|
<layout>PARTITION</layout>
|
||||||
<charCount>23</charCount>
|
<charCount>23</charCount>
|
||||||
@@ -103,7 +100,6 @@
|
|||||||
<type>FILE</type>
|
<type>FILE</type>
|
||||||
<class>NOVEL</class>
|
<class>NOVEL</class>
|
||||||
<status>Notes</status>
|
<status>Notes</status>
|
||||||
<expanded>False</expanded>
|
|
||||||
<exported>True</exported>
|
<exported>True</exported>
|
||||||
<layout>CHAPTER</layout>
|
<layout>CHAPTER</layout>
|
||||||
<charCount>12</charCount>
|
<charCount>12</charCount>
|
||||||
@@ -116,20 +112,18 @@
|
|||||||
<type>FILE</type>
|
<type>FILE</type>
|
||||||
<class>NOVEL</class>
|
<class>NOVEL</class>
|
||||||
<status>1st Draft</status>
|
<status>1st Draft</status>
|
||||||
<expanded>False</expanded>
|
|
||||||
<exported>True</exported>
|
<exported>True</exported>
|
||||||
<layout>SCENE</layout>
|
<layout>SCENE</layout>
|
||||||
<charCount>1199</charCount>
|
<charCount>1199</charCount>
|
||||||
<wordCount>216</wordCount>
|
<wordCount>216</wordCount>
|
||||||
<paraCount>7</paraCount>
|
<paraCount>7</paraCount>
|
||||||
<cursorPos>527</cursorPos>
|
<cursorPos>1266</cursorPos>
|
||||||
</item>
|
</item>
|
||||||
<item handle="bc0cbd2a407f3" order="2" parent="e7ded148d6e4a">
|
<item handle="bc0cbd2a407f3" order="2" parent="e7ded148d6e4a">
|
||||||
<name>Another Scene</name>
|
<name>Another Scene</name>
|
||||||
<type>FILE</type>
|
<type>FILE</type>
|
||||||
<class>NOVEL</class>
|
<class>NOVEL</class>
|
||||||
<status>1st Draft</status>
|
<status>1st Draft</status>
|
||||||
<expanded>False</expanded>
|
|
||||||
<exported>True</exported>
|
<exported>True</exported>
|
||||||
<layout>SCENE</layout>
|
<layout>SCENE</layout>
|
||||||
<charCount>476</charCount>
|
<charCount>476</charCount>
|
||||||
@@ -142,7 +136,6 @@
|
|||||||
<type>FILE</type>
|
<type>FILE</type>
|
||||||
<class>NOVEL</class>
|
<class>NOVEL</class>
|
||||||
<status>Finished</status>
|
<status>Finished</status>
|
||||||
<expanded>False</expanded>
|
|
||||||
<exported>True</exported>
|
<exported>True</exported>
|
||||||
<layout>UNNUMBERED</layout>
|
<layout>UNNUMBERED</layout>
|
||||||
<charCount>633</charCount>
|
<charCount>633</charCount>
|
||||||
@@ -155,7 +148,6 @@
|
|||||||
<type>FILE</type>
|
<type>FILE</type>
|
||||||
<class>NOVEL</class>
|
<class>NOVEL</class>
|
||||||
<status>2nd Draft</status>
|
<status>2nd Draft</status>
|
||||||
<expanded>False</expanded>
|
|
||||||
<exported>False</exported>
|
<exported>False</exported>
|
||||||
<layout>NOTE</layout>
|
<layout>NOTE</layout>
|
||||||
<charCount>1692</charCount>
|
<charCount>1692</charCount>
|
||||||
@@ -168,7 +160,6 @@
|
|||||||
<type>FILE</type>
|
<type>FILE</type>
|
||||||
<class>NOVEL</class>
|
<class>NOVEL</class>
|
||||||
<status>1st Draft</status>
|
<status>1st Draft</status>
|
||||||
<expanded>False</expanded>
|
|
||||||
<exported>True</exported>
|
<exported>True</exported>
|
||||||
<layout>CHAPTER</layout>
|
<layout>CHAPTER</layout>
|
||||||
<charCount>139</charCount>
|
<charCount>139</charCount>
|
||||||
@@ -181,7 +172,6 @@
|
|||||||
<type>FILE</type>
|
<type>FILE</type>
|
||||||
<class>NOVEL</class>
|
<class>NOVEL</class>
|
||||||
<status>1st Draft</status>
|
<status>1st Draft</status>
|
||||||
<expanded>False</expanded>
|
|
||||||
<exported>True</exported>
|
<exported>True</exported>
|
||||||
<layout>SCENE</layout>
|
<layout>SCENE</layout>
|
||||||
<charCount>189</charCount>
|
<charCount>189</charCount>
|
||||||
@@ -208,7 +198,6 @@
|
|||||||
<type>FILE</type>
|
<type>FILE</type>
|
||||||
<class>CHARACTER</class>
|
<class>CHARACTER</class>
|
||||||
<status>Minor</status>
|
<status>Minor</status>
|
||||||
<expanded>False</expanded>
|
|
||||||
<exported>True</exported>
|
<exported>True</exported>
|
||||||
<layout>NOTE</layout>
|
<layout>NOTE</layout>
|
||||||
<charCount>49</charCount>
|
<charCount>49</charCount>
|
||||||
@@ -221,7 +210,6 @@
|
|||||||
<type>FILE</type>
|
<type>FILE</type>
|
||||||
<class>CHARACTER</class>
|
<class>CHARACTER</class>
|
||||||
<status>Major</status>
|
<status>Major</status>
|
||||||
<expanded>False</expanded>
|
|
||||||
<exported>True</exported>
|
<exported>True</exported>
|
||||||
<layout>NOTE</layout>
|
<layout>NOTE</layout>
|
||||||
<charCount>55</charCount>
|
<charCount>55</charCount>
|
||||||
@@ -241,7 +229,6 @@
|
|||||||
<type>FILE</type>
|
<type>FILE</type>
|
||||||
<class>WORLD</class>
|
<class>WORLD</class>
|
||||||
<status>Main</status>
|
<status>Main</status>
|
||||||
<expanded>False</expanded>
|
|
||||||
<exported>True</exported>
|
<exported>True</exported>
|
||||||
<layout>NOTE</layout>
|
<layout>NOTE</layout>
|
||||||
<charCount>76</charCount>
|
<charCount>76</charCount>
|
||||||
@@ -254,7 +241,6 @@
|
|||||||
<type>FILE</type>
|
<type>FILE</type>
|
||||||
<class>WORLD</class>
|
<class>WORLD</class>
|
||||||
<status>Minor</status>
|
<status>Minor</status>
|
||||||
<expanded>False</expanded>
|
|
||||||
<exported>True</exported>
|
<exported>True</exported>
|
||||||
<layout>NOTE</layout>
|
<layout>NOTE</layout>
|
||||||
<charCount>115</charCount>
|
<charCount>115</charCount>
|
||||||
@@ -267,7 +253,6 @@
|
|||||||
<type>FILE</type>
|
<type>FILE</type>
|
||||||
<class>WORLD</class>
|
<class>WORLD</class>
|
||||||
<status>Major</status>
|
<status>Major</status>
|
||||||
<expanded>False</expanded>
|
|
||||||
<exported>True</exported>
|
<exported>True</exported>
|
||||||
<layout>NOTE</layout>
|
<layout>NOTE</layout>
|
||||||
<charCount>28</charCount>
|
<charCount>28</charCount>
|
||||||
@@ -287,7 +272,6 @@
|
|||||||
<type>FILE</type>
|
<type>FILE</type>
|
||||||
<class>NOVEL</class>
|
<class>NOVEL</class>
|
||||||
<status>New</status>
|
<status>New</status>
|
||||||
<expanded>False</expanded>
|
|
||||||
<exported>True</exported>
|
<exported>True</exported>
|
||||||
<layout>SCENE</layout>
|
<layout>SCENE</layout>
|
||||||
<charCount>0</charCount>
|
<charCount>0</charCount>
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ with open("README.md", "r") as inFile:
|
|||||||
|
|
||||||
setuptools.setup(
|
setuptools.setup(
|
||||||
name = "novelWriter",
|
name = "novelWriter",
|
||||||
version = "0.6.3",
|
version = "0.7.0rc1",
|
||||||
author = "Veronica Berglyd Olsen",
|
author = "Veronica Berglyd Olsen",
|
||||||
author_email = "code@vkbo.net",
|
author_email = "code@vkbo.net",
|
||||||
description = "A markdown-like document editor for writing novels",
|
description = "A markdown-like document editor for writing novels",
|
||||||
@@ -27,7 +27,10 @@ setuptools.setup(
|
|||||||
"Source Code": "https://github.com/vkbo/novelWriter",
|
"Source Code": "https://github.com/vkbo/novelWriter",
|
||||||
},
|
},
|
||||||
classifiers = [
|
classifiers = [
|
||||||
"Programming Language :: Python :: 3",
|
"Programming Language :: Python :: 3 :: Only",
|
||||||
|
"Programming Language :: Python :: 3.6",
|
||||||
|
"Programming Language :: Python :: 3.7",
|
||||||
|
"Programming Language :: Python :: 3.8",
|
||||||
"License :: OSI Approved :: GNU General Public License v3 (GPLv3)",
|
"License :: OSI Approved :: GNU General Public License v3 (GPLv3)",
|
||||||
"Development Status :: 3 - Alpha",
|
"Development Status :: 3 - Alpha",
|
||||||
"Operating System :: OS Independent",
|
"Operating System :: OS Independent",
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<?xml version='1.0' encoding='utf-8'?>
|
<?xml version='1.0' encoding='utf-8'?>
|
||||||
<novelWriterXML appVersion="0.6.1" hexVersion="0x000601f0" fileVersion="1.0" saveCount="2" autoCount="0" timeStamp="2020-05-27 18:25:48">
|
<novelWriterXML appVersion="0.7.0rc1" hexVersion="0x000700c1" fileVersion="1.1" saveCount="2" autoCount="0" timeStamp="2020-05-29 23:05:28">
|
||||||
<project>
|
<project>
|
||||||
<name>New Project</name>
|
<name>New Project</name>
|
||||||
<title></title>
|
<title></title>
|
||||||
@@ -14,7 +14,7 @@
|
|||||||
<autoReplace/>
|
<autoReplace/>
|
||||||
<titleFormat>
|
<titleFormat>
|
||||||
<title>%title%</title>
|
<title>%title%</title>
|
||||||
<chapter>Chapter %num%\\%title%</chapter>
|
<chapter>Chapter %ch%: %title%</chapter>
|
||||||
<unnumbered>%title%</unnumbered>
|
<unnumbered>%title%</unnumbered>
|
||||||
<scene>* * *</scene>
|
<scene>* * *</scene>
|
||||||
<section></section>
|
<section></section>
|
||||||
@@ -55,7 +55,6 @@
|
|||||||
<type>FILE</type>
|
<type>FILE</type>
|
||||||
<class>NOVEL</class>
|
<class>NOVEL</class>
|
||||||
<status>New</status>
|
<status>New</status>
|
||||||
<expanded>False</expanded>
|
|
||||||
<exported>True</exported>
|
<exported>True</exported>
|
||||||
<layout>SCENE</layout>
|
<layout>SCENE</layout>
|
||||||
<charCount>0</charCount>
|
<charCount>0</charCount>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<?xml version='1.0' encoding='utf-8'?>
|
<?xml version='1.0' encoding='utf-8'?>
|
||||||
<novelWriterXML appVersion="0.6.1" hexVersion="0x000601f0" fileVersion="1.0" saveCount="4" autoCount="0" timeStamp="2020-05-27 18:27:20">
|
<novelWriterXML appVersion="0.7.0rc1" hexVersion="0x000700c1" fileVersion="1.1" saveCount="4" autoCount="0" timeStamp="2020-05-29 23:06:02">
|
||||||
<project>
|
<project>
|
||||||
<name>New Project</name>
|
<name>New Project</name>
|
||||||
<title></title>
|
<title></title>
|
||||||
@@ -14,7 +14,7 @@
|
|||||||
<autoReplace/>
|
<autoReplace/>
|
||||||
<titleFormat>
|
<titleFormat>
|
||||||
<title>%title%</title>
|
<title>%title%</title>
|
||||||
<chapter>Chapter %num%\\%title%</chapter>
|
<chapter>Chapter %ch%: %title%</chapter>
|
||||||
<unnumbered>%title%</unnumbered>
|
<unnumbered>%title%</unnumbered>
|
||||||
<scene>* * *</scene>
|
<scene>* * *</scene>
|
||||||
<section></section>
|
<section></section>
|
||||||
@@ -55,7 +55,6 @@
|
|||||||
<type>FILE</type>
|
<type>FILE</type>
|
||||||
<class>NOVEL</class>
|
<class>NOVEL</class>
|
||||||
<status>New</status>
|
<status>New</status>
|
||||||
<expanded>False</expanded>
|
|
||||||
<exported>True</exported>
|
<exported>True</exported>
|
||||||
<layout>SCENE</layout>
|
<layout>SCENE</layout>
|
||||||
<charCount>331</charCount>
|
<charCount>331</charCount>
|
||||||
@@ -75,7 +74,6 @@
|
|||||||
<type>FILE</type>
|
<type>FILE</type>
|
||||||
<class>CHARACTER</class>
|
<class>CHARACTER</class>
|
||||||
<status>New</status>
|
<status>New</status>
|
||||||
<expanded>False</expanded>
|
|
||||||
<exported>True</exported>
|
<exported>True</exported>
|
||||||
<layout>NOTE</layout>
|
<layout>NOTE</layout>
|
||||||
<charCount>34</charCount>
|
<charCount>34</charCount>
|
||||||
@@ -95,7 +93,6 @@
|
|||||||
<type>FILE</type>
|
<type>FILE</type>
|
||||||
<class>PLOT</class>
|
<class>PLOT</class>
|
||||||
<status>New</status>
|
<status>New</status>
|
||||||
<expanded>False</expanded>
|
|
||||||
<exported>True</exported>
|
<exported>True</exported>
|
||||||
<layout>NOTE</layout>
|
<layout>NOTE</layout>
|
||||||
<charCount>48</charCount>
|
<charCount>48</charCount>
|
||||||
@@ -115,7 +112,6 @@
|
|||||||
<type>FILE</type>
|
<type>FILE</type>
|
||||||
<class>WORLD</class>
|
<class>WORLD</class>
|
||||||
<status>New</status>
|
<status>New</status>
|
||||||
<expanded>False</expanded>
|
|
||||||
<exported>True</exported>
|
<exported>True</exported>
|
||||||
<layout>NOTE</layout>
|
<layout>NOTE</layout>
|
||||||
<charCount>51</charCount>
|
<charCount>51</charCount>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<?xml version='1.0' encoding='utf-8'?>
|
<?xml version='1.0' encoding='utf-8'?>
|
||||||
<novelWriterXML appVersion="0.5" fileVersion="1.0" saveCount="2" autoCount="0" timeStamp="2020-05-10 23:19:00">
|
<novelWriterXML appVersion="0.7.0rc1" hexVersion="0x000700c1" fileVersion="1.1" saveCount="2" autoCount="0" timeStamp="2020-05-29 23:07:05">
|
||||||
<project>
|
<project>
|
||||||
<name>Project Name</name>
|
<name>Project Name</name>
|
||||||
<title>Project Title</title>
|
<title>Project Title</title>
|
||||||
@@ -18,7 +18,7 @@
|
|||||||
</autoReplace>
|
</autoReplace>
|
||||||
<titleFormat>
|
<titleFormat>
|
||||||
<title>%title%</title>
|
<title>%title%</title>
|
||||||
<chapter>Chapter %num%\\%title%</chapter>
|
<chapter>Chapter %ch%: %title%</chapter>
|
||||||
<unnumbered>%title%</unnumbered>
|
<unnumbered>%title%</unnumbered>
|
||||||
<scene>* * *</scene>
|
<scene>* * *</scene>
|
||||||
<section></section>
|
<section></section>
|
||||||
@@ -59,7 +59,6 @@
|
|||||||
<type>FILE</type>
|
<type>FILE</type>
|
||||||
<class>NOVEL</class>
|
<class>NOVEL</class>
|
||||||
<status>New</status>
|
<status>New</status>
|
||||||
<expanded>False</expanded>
|
|
||||||
<exported>True</exported>
|
<exported>True</exported>
|
||||||
<layout>SCENE</layout>
|
<layout>SCENE</layout>
|
||||||
<charCount>0</charCount>
|
<charCount>0</charCount>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<?xml version='1.0' encoding='utf-8'?>
|
<?xml version='1.0' encoding='utf-8'?>
|
||||||
<novelWriterXML appVersion="0.6.1" hexVersion="0x000601f0" fileVersion="1.0" saveCount="2" autoCount="0" timeStamp="2020-05-27 18:30:26">
|
<novelWriterXML appVersion="0.7.0rc1" hexVersion="0x000700c1" fileVersion="1.1" saveCount="2" autoCount="0" timeStamp="2020-05-29 23:07:37">
|
||||||
<project>
|
<project>
|
||||||
<name>New Project</name>
|
<name>New Project</name>
|
||||||
<title></title>
|
<title></title>
|
||||||
@@ -14,7 +14,7 @@
|
|||||||
<autoReplace/>
|
<autoReplace/>
|
||||||
<titleFormat>
|
<titleFormat>
|
||||||
<title>%title%</title>
|
<title>%title%</title>
|
||||||
<chapter>Chapter %num%\\%title%</chapter>
|
<chapter>Chapter %ch%: %title%</chapter>
|
||||||
<unnumbered>%title%</unnumbered>
|
<unnumbered>%title%</unnumbered>
|
||||||
<scene>* * *</scene>
|
<scene>* * *</scene>
|
||||||
<section></section>
|
<section></section>
|
||||||
@@ -55,7 +55,6 @@
|
|||||||
<type>FILE</type>
|
<type>FILE</type>
|
||||||
<class>NOVEL</class>
|
<class>NOVEL</class>
|
||||||
<status>Note</status>
|
<status>Note</status>
|
||||||
<expanded>False</expanded>
|
|
||||||
<exported>False</exported>
|
<exported>False</exported>
|
||||||
<layout>PAGE</layout>
|
<layout>PAGE</layout>
|
||||||
<charCount>0</charCount>
|
<charCount>0</charCount>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<?xml version='1.0' encoding='utf-8'?>
|
<?xml version='1.0' encoding='utf-8'?>
|
||||||
<novelWriterXML appVersion="0.6.1" hexVersion="0x000601f0" fileVersion="1.0" saveCount="1" autoCount="0" timeStamp="2020-05-27 18:23:46">
|
<novelWriterXML appVersion="0.7.0rc1" hexVersion="0x000700c1" fileVersion="1.1" saveCount="1" autoCount="0" timeStamp="2020-05-29 23:03:58">
|
||||||
<project>
|
<project>
|
||||||
<name>New Project</name>
|
<name>New Project</name>
|
||||||
<title></title>
|
<title></title>
|
||||||
@@ -14,7 +14,7 @@
|
|||||||
<autoReplace/>
|
<autoReplace/>
|
||||||
<titleFormat>
|
<titleFormat>
|
||||||
<title>%title%</title>
|
<title>%title%</title>
|
||||||
<chapter>Chapter %num%\\%title%</chapter>
|
<chapter>Chapter %ch%: %title%</chapter>
|
||||||
<unnumbered>%title%</unnumbered>
|
<unnumbered>%title%</unnumbered>
|
||||||
<scene>* * *</scene>
|
<scene>* * *</scene>
|
||||||
<section></section>
|
<section></section>
|
||||||
@@ -76,7 +76,6 @@
|
|||||||
<type>FILE</type>
|
<type>FILE</type>
|
||||||
<class>NOVEL</class>
|
<class>NOVEL</class>
|
||||||
<status>New</status>
|
<status>New</status>
|
||||||
<expanded>False</expanded>
|
|
||||||
<exported>True</exported>
|
<exported>True</exported>
|
||||||
<layout>SCENE</layout>
|
<layout>SCENE</layout>
|
||||||
<charCount>0</charCount>
|
<charCount>0</charCount>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<?xml version='1.0' encoding='utf-8'?>
|
<?xml version='1.0' encoding='utf-8'?>
|
||||||
<novelWriterXML appVersion="0.6.1" hexVersion="0x000601f0" fileVersion="1.0" saveCount="4" autoCount="0" timeStamp="2020-05-27 18:24:35">
|
<novelWriterXML appVersion="0.7.0rc1" hexVersion="0x000700c1" fileVersion="1.1" saveCount="4" autoCount="0" timeStamp="2020-05-29 23:04:31">
|
||||||
<project>
|
<project>
|
||||||
<name>New Project</name>
|
<name>New Project</name>
|
||||||
<title></title>
|
<title></title>
|
||||||
@@ -14,7 +14,7 @@
|
|||||||
<autoReplace/>
|
<autoReplace/>
|
||||||
<titleFormat>
|
<titleFormat>
|
||||||
<title>%title%</title>
|
<title>%title%</title>
|
||||||
<chapter>Chapter %num%\\%title%</chapter>
|
<chapter>Chapter %ch%: %title%</chapter>
|
||||||
<unnumbered>%title%</unnumbered>
|
<unnumbered>%title%</unnumbered>
|
||||||
<scene>* * *</scene>
|
<scene>* * *</scene>
|
||||||
<section></section>
|
<section></section>
|
||||||
@@ -76,7 +76,6 @@
|
|||||||
<type>FILE</type>
|
<type>FILE</type>
|
||||||
<class>NOVEL</class>
|
<class>NOVEL</class>
|
||||||
<status>New</status>
|
<status>New</status>
|
||||||
<expanded>False</expanded>
|
|
||||||
<exported>True</exported>
|
<exported>True</exported>
|
||||||
<layout>SCENE</layout>
|
<layout>SCENE</layout>
|
||||||
<charCount>0</charCount>
|
<charCount>0</charCount>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<?xml version='1.0' encoding='utf-8'?>
|
<?xml version='1.0' encoding='utf-8'?>
|
||||||
<novelWriterXML appVersion="0.6.1" hexVersion="0x000601f0" fileVersion="1.0" saveCount="5" autoCount="0" timeStamp="2020-05-27 18:24:56">
|
<novelWriterXML appVersion="0.7.0rc1" hexVersion="0x000700c1" fileVersion="1.1" saveCount="5" autoCount="0" timeStamp="2020-05-29 23:04:50">
|
||||||
<project>
|
<project>
|
||||||
<name>New Project</name>
|
<name>New Project</name>
|
||||||
<title></title>
|
<title></title>
|
||||||
@@ -14,7 +14,7 @@
|
|||||||
<autoReplace/>
|
<autoReplace/>
|
||||||
<titleFormat>
|
<titleFormat>
|
||||||
<title>%title%</title>
|
<title>%title%</title>
|
||||||
<chapter>Chapter %num%\\%title%</chapter>
|
<chapter>Chapter %ch%: %title%</chapter>
|
||||||
<unnumbered>%title%</unnumbered>
|
<unnumbered>%title%</unnumbered>
|
||||||
<scene>* * *</scene>
|
<scene>* * *</scene>
|
||||||
<section></section>
|
<section></section>
|
||||||
@@ -76,7 +76,6 @@
|
|||||||
<type>FILE</type>
|
<type>FILE</type>
|
||||||
<class>NOVEL</class>
|
<class>NOVEL</class>
|
||||||
<status>New</status>
|
<status>New</status>
|
||||||
<expanded>False</expanded>
|
|
||||||
<exported>True</exported>
|
<exported>True</exported>
|
||||||
<layout>SCENE</layout>
|
<layout>SCENE</layout>
|
||||||
<charCount>0</charCount>
|
<charCount>0</charCount>
|
||||||
@@ -117,7 +116,6 @@
|
|||||||
<type>FILE</type>
|
<type>FILE</type>
|
||||||
<class>NOVEL</class>
|
<class>NOVEL</class>
|
||||||
<status>New</status>
|
<status>New</status>
|
||||||
<expanded>False</expanded>
|
|
||||||
<exported>True</exported>
|
<exported>True</exported>
|
||||||
<layout>SCENE</layout>
|
<layout>SCENE</layout>
|
||||||
<charCount>0</charCount>
|
<charCount>0</charCount>
|
||||||
@@ -130,7 +128,6 @@
|
|||||||
<type>FILE</type>
|
<type>FILE</type>
|
||||||
<class>CHARACTER</class>
|
<class>CHARACTER</class>
|
||||||
<status>New</status>
|
<status>New</status>
|
||||||
<expanded>False</expanded>
|
|
||||||
<exported>True</exported>
|
<exported>True</exported>
|
||||||
<layout>NOTE</layout>
|
<layout>NOTE</layout>
|
||||||
<charCount>0</charCount>
|
<charCount>0</charCount>
|
||||||
|
|||||||
+8
-8
@@ -237,14 +237,14 @@ def testMainWindows(qtbot, nwTempGUI, nwRef, nwTemp):
|
|||||||
# Check the files
|
# Check the files
|
||||||
refFile = path.join(nwTempGUI,"nwProject.nwx")
|
refFile = path.join(nwTempGUI,"nwProject.nwx")
|
||||||
assert cmpFiles(refFile, path.join(nwRef,"gui","1_nwProject.nwx"), [2])
|
assert cmpFiles(refFile, path.join(nwRef,"gui","1_nwProject.nwx"), [2])
|
||||||
refFile = path.join(nwTempGUI,"data_0","e17daca5f3e1_main.nwd")
|
refFile = path.join(nwTempGUI,"content","0e17daca5f3e1.nwd")
|
||||||
assert cmpFiles(refFile, path.join(nwRef,"gui","1_e17daca5f3e1_main.nwd"))
|
assert cmpFiles(refFile, path.join(nwRef,"gui","1_0e17daca5f3e1.nwd"))
|
||||||
refFile = path.join(nwTempGUI,"data_9","8010bd9270f9_main.nwd")
|
refFile = path.join(nwTempGUI,"content","98010bd9270f9.nwd")
|
||||||
assert cmpFiles(refFile, path.join(nwRef,"gui","1_8010bd9270f9_main.nwd"))
|
assert cmpFiles(refFile, path.join(nwRef,"gui","1_98010bd9270f9.nwd"))
|
||||||
refFile = path.join(nwTempGUI,"data_3","1489056e0916_main.nwd")
|
refFile = path.join(nwTempGUI,"content","31489056e0916.nwd")
|
||||||
assert cmpFiles(refFile, path.join(nwRef,"gui","1_1489056e0916_main.nwd"))
|
assert cmpFiles(refFile, path.join(nwRef,"gui","1_31489056e0916.nwd"))
|
||||||
refFile = path.join(nwTempGUI,"data_1","a6562590ef19_main.nwd")
|
refFile = path.join(nwTempGUI,"content","1a6562590ef19.nwd")
|
||||||
assert cmpFiles(refFile, path.join(nwRef,"gui","1_a6562590ef19_main.nwd"))
|
assert cmpFiles(refFile, path.join(nwRef,"gui","1_1a6562590ef19.nwd"))
|
||||||
|
|
||||||
nwGUI.closeMain()
|
nwGUI.closeMain()
|
||||||
# qtbot.stopForInteraction()
|
# qtbot.stopForInteraction()
|
||||||
|
|||||||
@@ -155,7 +155,7 @@ def testIndexCheckThese(nwTempProj):
|
|||||||
"# Hello World!\n"
|
"# Hello World!\n"
|
||||||
"@pov: Jane"
|
"@pov: Jane"
|
||||||
))
|
))
|
||||||
assert str(theIndex.tagIndex) == "{'Jane': [2, '2858dcd1057d3', 'CHARACTER']}"
|
assert str(theIndex.tagIndex) == "{'Jane': [2, '2858dcd1057d3', 'CHARACTER', 'T000001']}"
|
||||||
assert theIndex.novelIndex[nHandle]["T000001"]["title"] == "Hello World!"
|
assert theIndex.novelIndex[nHandle]["T000001"]["title"] == "Hello World!"
|
||||||
|
|
||||||
assert str(theIndex.checkThese(["@tag", "Jane"], cItem)) == "[True, True]"
|
assert str(theIndex.checkThese(["@tag", "Jane"], cItem)) == "[True, True]"
|
||||||
@@ -195,7 +195,7 @@ def testIndexMeta(nwTempProj):
|
|||||||
"\n"
|
"\n"
|
||||||
"Well, not really.\n"
|
"Well, not really.\n"
|
||||||
))
|
))
|
||||||
assert str(theIndex.tagIndex) == "{'Jane': [2, '2858dcd1057d3', 'CHARACTER']}"
|
assert str(theIndex.tagIndex) == "{'Jane': [2, '2858dcd1057d3', 'CHARACTER', 'T000001']}"
|
||||||
assert theIndex.novelIndex[nHandle]["T000001"]["title"] == "Hello World!"
|
assert theIndex.novelIndex[nHandle]["T000001"]["title"] == "Hello World!"
|
||||||
|
|
||||||
# The novel structure should contain the pointer to the novel file header
|
# The novel structure should contain the pointer to the novel file header
|
||||||
@@ -214,6 +214,6 @@ def testIndexMeta(nwTempProj):
|
|||||||
|
|
||||||
# The character file should have a record of the reference from the novel file
|
# The character file should have a record of the reference from the novel file
|
||||||
theRefs = theIndex.getBackReferenceList(cHandle)
|
theRefs = theIndex.getBackReferenceList(cHandle)
|
||||||
assert str(theRefs) == "{'41cfc0d1f2d12': 3}"
|
assert str(theRefs) == "{'41cfc0d1f2d12': 'T000001'}"
|
||||||
|
|
||||||
assert theProject.closeProject()
|
assert theProject.closeProject()
|
||||||
|
|||||||
Reference in New Issue
Block a user