+4
-6
@@ -14,12 +14,10 @@ docs/source/_*
|
||||
__pycache__
|
||||
|
||||
# Sample Project
|
||||
sample/**/cache
|
||||
sample/**/wordlist.txt
|
||||
sample/**/sessionInfo.log
|
||||
sample/**/*.bak
|
||||
sample/**/*.json
|
||||
sample/**/*.lock
|
||||
sample/cache
|
||||
sample/meta
|
||||
sample/*.bak
|
||||
sample/*.lock
|
||||
|
||||
# PyTest
|
||||
tests/temp
|
||||
|
||||
+22
-1
@@ -1,5 +1,25 @@
|
||||
# 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]
|
||||
|
||||
**User Interface**
|
||||
@@ -27,7 +47,8 @@
|
||||
|
||||
## 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]
|
||||
|
||||
|
||||
+2
-2
@@ -24,9 +24,9 @@ copyright = "2018-2020, Veronica Berglyd Olsen"
|
||||
author = "Veronica Berglyd Olsen"
|
||||
|
||||
# The short X.Y version
|
||||
version = "0.6.3"
|
||||
version = "0.7.0"
|
||||
# The full version, including alpha/beta/rc tags
|
||||
release = "0.6.3"
|
||||
release = "0.7.0rc1"
|
||||
|
||||
|
||||
# -- General configuration ---------------------------------------------------
|
||||
|
||||
@@ -30,9 +30,9 @@ The project XML file is indent-formatted, suitable for diff tools and version co
|
||||
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.
|
||||
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.
|
||||
|
||||
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"
|
||||
__copyright__ = "Copyright 2018–2020, Veronica Berglyd Olsen"
|
||||
__license__ = "GPLv3"
|
||||
__version__ = "0.6.3"
|
||||
__hexversion__ = "0x000603f0"
|
||||
__version__ = "0.7.0rc1"
|
||||
__hexversion__ = "0x000700c1"
|
||||
__date__ = "2020-05-28"
|
||||
__maintainer__ = "Veronica Berglyd Olsen"
|
||||
__email__ = "code@vkbo.net"
|
||||
|
||||
+3
-1
@@ -533,7 +533,9 @@ class Config:
|
||||
|
||||
# Write config file
|
||||
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
|
||||
except Exception as e:
|
||||
logger.error("Could not save config file")
|
||||
|
||||
@@ -39,6 +39,8 @@ class nwFiles():
|
||||
PROJ_FILE = "nwProject.nwx"
|
||||
PROJ_DICT = "wordlist.txt"
|
||||
PROJ_LOCK = "nwProject.lock"
|
||||
TOC_TXT = "ToC.txt"
|
||||
TOC_JSON = "ToC.json"
|
||||
SESS_INFO = "sessionInfo.log"
|
||||
INDEX_FILE = "tagsIndex.json"
|
||||
OPTS_FILE = "guiOptions.json"
|
||||
|
||||
@@ -9,7 +9,6 @@ from nw.core.spellcheck import NWSpellSimple
|
||||
from nw.core.tokenizer import Tokenizer
|
||||
from nw.core.tohtml import ToHtml
|
||||
from nw.core.tools import countWords
|
||||
from nw.core.tools import projectMaintenance
|
||||
from nw.core.tools import numberToWord
|
||||
|
||||
__all__ = [
|
||||
@@ -22,6 +21,5 @@ __all__ = [
|
||||
"Tokenizer",
|
||||
"ToHtml",
|
||||
"countWords",
|
||||
"projectMaintenance",
|
||||
"numberToWord",
|
||||
]
|
||||
|
||||
+24
-44
@@ -37,8 +37,6 @@ logger = logging.getLogger(__name__)
|
||||
|
||||
class NWDoc():
|
||||
|
||||
FILE_MN = "main.nwd"
|
||||
|
||||
def __init__(self, theProject, theParent):
|
||||
|
||||
self.mainConf = nw.CONFIG
|
||||
@@ -93,17 +91,17 @@ class NWDoc():
|
||||
if self.theItem.parHandle == self.theProject.projTree.trashRoot():
|
||||
self.docEditable = False
|
||||
|
||||
docDir, docFile = self._assemblePath(self.docHandle, self.FILE_MN)
|
||||
self.fileLoc = path.join(docDir,docFile)
|
||||
logger.debug("Opening document %s" % self.fileLoc)
|
||||
dataDir = path.join(self.theProject.projPath, docDir)
|
||||
docPath = path.join(dataDir, docFile)
|
||||
docFile = self.docHandle+".nwd"
|
||||
logger.debug("Opening document %s" % docFile)
|
||||
|
||||
docPath = path.join(self.theProject.projContent, docFile)
|
||||
self.fileLoc = docPath
|
||||
|
||||
theText = ""
|
||||
self.docMeta = ""
|
||||
if path.isfile(docPath):
|
||||
try:
|
||||
with open(docPath,mode="r",encoding="utf8") as inFile:
|
||||
with open(docPath, mode="r", encoding="utf8") as inFile:
|
||||
fstLine = inFile.readline()
|
||||
if fstLine.startswith("%%~ "):
|
||||
# This is the meta line
|
||||
@@ -113,7 +111,7 @@ class NWDoc():
|
||||
theText += inFile.read()
|
||||
|
||||
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,
|
||||
# or else the auto-save or save will try to overwrite it
|
||||
# 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:
|
||||
return False
|
||||
|
||||
docDir, docFile = self._assemblePath(self.docHandle, self.FILE_MN)
|
||||
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)
|
||||
self.theProject.ensureFolderStructure()
|
||||
|
||||
docTemp = path.join(dataPath, docFile+"~")
|
||||
docBack = path.join(dataPath, docFile[:-3]+"bak")
|
||||
docFile = self.docHandle+".nwd"
|
||||
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)
|
||||
docMeta = "%%~ "+":".join(itemPath)+":"+self.theItem.itemName+"\n"
|
||||
|
||||
try:
|
||||
with open(docTemp,mode="w",encoding="utf8") as outFile:
|
||||
with open(docTemp, mode="w", encoding="utf8") as outFile:
|
||||
outFile.write(docMeta)
|
||||
outFile.write(docText)
|
||||
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
|
||||
|
||||
# If we're here, the file was successfully saved,
|
||||
# so let's sort out the temps and backups
|
||||
if path.isfile(docBack):
|
||||
unlink(docBack)
|
||||
# If we're here, the file was successfully saved, so we can
|
||||
# replace the temp file with the actual file
|
||||
if path.isfile(docPath):
|
||||
rename(docPath, docBack)
|
||||
unlink(docPath)
|
||||
rename(docTemp, docPath)
|
||||
|
||||
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
|
||||
from the project data folder.
|
||||
"""
|
||||
docDir, docFile = self._assemblePath(tHandle, self.FILE_MN)
|
||||
dataPath = path.join(self.theProject.projPath, docDir)
|
||||
docFile = self.docHandle+".nwd"
|
||||
|
||||
chkList = []
|
||||
chkList.append(path.join(dataPath, docFile))
|
||||
chkList.append(path.join(dataPath, docFile+"~"))
|
||||
chkList.append(path.join(dataPath, docFile[:-3]+"bak"))
|
||||
chkList.append(path.join(self.theProject.projContent, docFile))
|
||||
chkList.append(path.join(self.theProject.projContent, docFile+"~"))
|
||||
|
||||
for chkFile in chkList:
|
||||
if path.isfile(chkFile):
|
||||
try:
|
||||
@@ -191,6 +184,7 @@ class NWDoc():
|
||||
except Exception as e:
|
||||
self.makeAlert(["Could not delete document file.",str(e)], nwAlert.ERROR)
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
##
|
||||
@@ -224,18 +218,4 @@ class NWDoc():
|
||||
|
||||
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
|
||||
|
||||
+17
-17
@@ -199,7 +199,7 @@ class NWIndex():
|
||||
|
||||
try:
|
||||
for tTag in self.tagIndex:
|
||||
if len(self.tagIndex[tTag]) != 3:
|
||||
if len(self.tagIndex[tTag]) != 4:
|
||||
self.indexBroken = True
|
||||
|
||||
for tHandle in self.refIndex:
|
||||
@@ -228,7 +228,7 @@ class NWIndex():
|
||||
if self.indexBroken:
|
||||
self.clearIndex()
|
||||
self.theParent.makeAlert(
|
||||
"The index loaded from project cache contains errors. Rebuilding index.",
|
||||
"The project index is outdated or broken. Rebuilding index.",
|
||||
nwAlert.WARN
|
||||
)
|
||||
|
||||
@@ -260,9 +260,9 @@ class NWIndex():
|
||||
logger.debug("Indexing item with handle %s" % tHandle)
|
||||
|
||||
# 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]["T0"] = {
|
||||
self.refIndex[tHandle]["T000000"] = {
|
||||
"tags" : [],
|
||||
"updated" : time(),
|
||||
}
|
||||
@@ -301,7 +301,7 @@ class NWIndex():
|
||||
|
||||
elif aLine.startswith(r"@"):
|
||||
self._indexNoteRef(tHandle, aLine, nLine, nTitle)
|
||||
self._indexTag(tHandle, aLine, nLine, itemClass)
|
||||
self._indexTag(tHandle, aLine, nLine, nTitle, itemClass)
|
||||
|
||||
elif aLine.startswith(r"%"):
|
||||
if nTitle > 0:
|
||||
@@ -436,7 +436,7 @@ class NWIndex():
|
||||
|
||||
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.
|
||||
"""
|
||||
isValid, theBits, thePos = self.scanThis(aLine)
|
||||
@@ -444,7 +444,8 @@ class NWIndex():
|
||||
return False
|
||||
|
||||
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
|
||||
|
||||
@@ -606,18 +607,17 @@ class NWIndex():
|
||||
if tHandle is None:
|
||||
return theRefs
|
||||
|
||||
theTag = None
|
||||
theTags = set()
|
||||
for tTag in self.tagIndex:
|
||||
if tHandle == self.tagIndex[tTag][1]:
|
||||
theTag = tTag
|
||||
break
|
||||
theTags.add(tTag)
|
||||
|
||||
if theTag is not None:
|
||||
if theTags:
|
||||
for tHandle in self.refIndex:
|
||||
for sTitle in self.refIndex[tHandle]:
|
||||
for nLine, tKey, tTag in self.refIndex[tHandle][sTitle]["tags"]:
|
||||
if tTag == theTag:
|
||||
theRefs[tHandle] = nLine
|
||||
for _, _, tTag in self.refIndex[tHandle][sTitle]["tags"]:
|
||||
if tTag in theTags and tHandle not in theRefs:
|
||||
theRefs[tHandle] = sTitle
|
||||
|
||||
return theRefs
|
||||
|
||||
@@ -626,8 +626,8 @@ class NWIndex():
|
||||
"""
|
||||
if theTag in self.tagIndex:
|
||||
theRef = self.tagIndex[theTag]
|
||||
if len(theRef) == 3:
|
||||
return theRef[1], theRef[0]
|
||||
return None, 0
|
||||
if len(theRef) == 4:
|
||||
return theRef[1], theRef[0], theRef[3]
|
||||
return None, 0, "T000000"
|
||||
|
||||
# END Class NWIndex
|
||||
|
||||
+282
-54
@@ -31,9 +31,10 @@
|
||||
"""
|
||||
|
||||
import logging
|
||||
import json
|
||||
import nw
|
||||
|
||||
from os import path, mkdir, listdir, unlink, rename
|
||||
from os import path, mkdir, listdir, unlink, rename, rmdir
|
||||
from lxml import etree
|
||||
from hashlib import sha256
|
||||
from time import time
|
||||
@@ -42,7 +43,6 @@ from shutil import make_archive
|
||||
from PyQt5.QtWidgets import QMessageBox
|
||||
|
||||
from nw.gui.tools import OptionState
|
||||
from nw.core.tools import projectMaintenance
|
||||
from nw.core.document import NWDoc
|
||||
from nw.common import checkString, checkBool, checkInt, formatTimeStamp
|
||||
from nw.constants import (
|
||||
@@ -70,12 +70,15 @@ class NWProject():
|
||||
self.lockedBy = None # Data on which computer has the project open
|
||||
self.saveCount = 0 # Meta data: number of saves
|
||||
self.autoCount = 0 # Meta data: number of automatic saves
|
||||
self.editTime = 0 # The accumulated edit time read from the project file
|
||||
|
||||
# Class Settings
|
||||
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.projDict = None # The spell check dictionary
|
||||
self.projFile = None # The file name of the project main XML file
|
||||
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.projCache = None # The full path to the project's cache folder
|
||||
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
|
||||
self.projName = "" # Project name (working title)
|
||||
@@ -112,7 +115,7 @@ class NWProject():
|
||||
CUSTOM, and always have parent handle set to None.
|
||||
"""
|
||||
if not self.projTree.checkRootUnique(rootClass):
|
||||
self.makeAlert("Duplicate root item detected!", nwAlert.ERROR)
|
||||
self.makeAlert("Duplicate root item detected.", nwAlert.ERROR)
|
||||
return None
|
||||
newItem = NWItem(self)
|
||||
newItem.setName(rootName)
|
||||
@@ -195,6 +198,8 @@ class NWProject():
|
||||
# Project Settings
|
||||
self.projPath = None
|
||||
self.projMeta = None
|
||||
self.projCache = None
|
||||
self.projContent = None
|
||||
self.projDict = None
|
||||
self.projFile = nwFiles.PROJ_FILE
|
||||
self.projName = ""
|
||||
@@ -203,7 +208,7 @@ class NWProject():
|
||||
self.autoReplace = {}
|
||||
self.titleFormat = {
|
||||
"title" : r"%title%",
|
||||
"chapter" : r"Chapter %num%\\%title%",
|
||||
"chapter" : r"Chapter %ch%: %title%",
|
||||
"unnumbered" : r"%title%",
|
||||
"scene" : r"* * *",
|
||||
"section" : r"",
|
||||
@@ -246,11 +251,30 @@ class NWProject():
|
||||
self.projPath = path.abspath(path.dirname(fileName))
|
||||
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)
|
||||
|
||||
if not self._checkFolder(self.projMeta):
|
||||
return False
|
||||
# Check for Old Legacy Data
|
||||
# =========================
|
||||
|
||||
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:
|
||||
self._clearLockFile()
|
||||
@@ -267,10 +291,8 @@ class NWProject():
|
||||
else:
|
||||
logger.verbose("Project is not locked")
|
||||
|
||||
try:
|
||||
projectMaintenance(self)
|
||||
except Exception as E:
|
||||
logger.error(str(E))
|
||||
# Open The Project XML File
|
||||
# =========================
|
||||
|
||||
try:
|
||||
nwXML = etree.parse(fileName)
|
||||
@@ -291,7 +313,7 @@ class NWProject():
|
||||
self.clearProject()
|
||||
return False
|
||||
|
||||
xRoot = nwXML.getroot()
|
||||
xRoot = nwXML.getroot()
|
||||
nwxRoot = xRoot.tag
|
||||
|
||||
appVersion = "Unknown"
|
||||
@@ -310,17 +332,51 @@ class NWProject():
|
||||
self.saveCount = checkInt(xRoot.attrib["saveCount"], 0, False)
|
||||
if "autoCount" in xRoot.attrib:
|
||||
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("File version is %s" % fileVersion)
|
||||
|
||||
if not nwxRoot == "novelWriterXML" or not fileVersion == "1.0":
|
||||
# Check File Type
|
||||
# ===============
|
||||
|
||||
if not nwxRoot == "novelWriterXML":
|
||||
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
|
||||
)
|
||||
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:
|
||||
msgBox = QMessageBox()
|
||||
msgRes = msgBox.question(self.theParent, "Version Conflict", (
|
||||
@@ -333,6 +389,9 @@ class NWProject():
|
||||
if msgRes != QMessageBox.Yes:
|
||||
return False
|
||||
|
||||
# Start Parsing XML
|
||||
# =================
|
||||
|
||||
for xChild in xRoot:
|
||||
if xChild.tag == "project":
|
||||
logger.debug("Found project meta")
|
||||
@@ -350,6 +409,7 @@ class NWProject():
|
||||
self.bookAuthors.append(xItem.text)
|
||||
elif xItem.tag == "backup":
|
||||
self.doBackup = checkBool(xItem.text, False)
|
||||
|
||||
elif xChild.tag == "settings":
|
||||
logger.debug("Found project settings")
|
||||
for xItem in xChild:
|
||||
@@ -377,6 +437,7 @@ class NWProject():
|
||||
for xEntry in xItem:
|
||||
titleFormat[xEntry.tag] = checkString(xEntry.text, "", False)
|
||||
self.setTitleFormat(titleFormat)
|
||||
|
||||
elif xChild.tag == "content":
|
||||
logger.debug("Found project content")
|
||||
self.projTree.unpackXML(xChild)
|
||||
@@ -404,15 +465,13 @@ class NWProject():
|
||||
file.
|
||||
"""
|
||||
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
|
||||
|
||||
self.projMeta = path.join(self.projPath,"meta")
|
||||
saveTime = time()
|
||||
|
||||
if not self._checkFolder(self.projPath):
|
||||
return False
|
||||
if not self._checkFolder(self.projMeta):
|
||||
if not self.ensureFolderStructure():
|
||||
return False
|
||||
|
||||
logger.debug("Saving project: %s" % self.projPath)
|
||||
@@ -427,10 +486,11 @@ class NWProject():
|
||||
nwXML = etree.Element("novelWriterXML",attrib={
|
||||
"appVersion" : str(nw.__version__),
|
||||
"hexVersion" : str(nw.__hexversion__),
|
||||
"fileVersion" : "1.0",
|
||||
"fileVersion" : "1.1",
|
||||
"saveCount" : str(self.saveCount),
|
||||
"autoCount" : str(self.autoCount),
|
||||
"timeStamp" : formatTimeStamp(saveTime),
|
||||
"editTime" : str(int(self.editTime + saveTime - self.projOpened)),
|
||||
})
|
||||
|
||||
# Save Project Meta
|
||||
@@ -480,7 +540,7 @@ class NWProject():
|
||||
xml_declaration = True
|
||||
))
|
||||
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
|
||||
|
||||
# If we're here, the file was successfully saved,
|
||||
@@ -507,12 +567,33 @@ class NWProject():
|
||||
def closeProject(self):
|
||||
"""Close the current project and clear all meta data.
|
||||
"""
|
||||
self.projTree.writeToCFiles()
|
||||
self._appendSessionStats()
|
||||
self._clearLockFile()
|
||||
self.clearProject()
|
||||
self.lockedBy = None
|
||||
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
|
||||
##
|
||||
@@ -937,35 +1018,26 @@ class NWProject():
|
||||
if self.projPath is None:
|
||||
return
|
||||
|
||||
# First, scan the project data folders
|
||||
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
|
||||
# Then check the files in the data folder
|
||||
orphanFiles = []
|
||||
for fileItem in itemList:
|
||||
if len(fileItem) != 28:
|
||||
# Just to be safe, shouldn't happen
|
||||
for fileItem in listdir(self.projContent):
|
||||
if not fileItem.endswith(".nwd"):
|
||||
logger.warning("Skipping file %s" % fileItem)
|
||||
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:
|
||||
logger.debug("Checking file %s, handle %s: OK" % (fileItem,fHandle))
|
||||
logger.debug("Checking file %s, handle %s: OK" % (fileItem, fHandle))
|
||||
else:
|
||||
logger.debug("Checking file %s, handle %s: Orphaned" % (fileItem,fHandle))
|
||||
logger.debug("Checking file %s, handle %s: Orphaned" % (fileItem, fHandle))
|
||||
orphanFiles.append(fHandle)
|
||||
|
||||
# Report status
|
||||
if len(orphanFiles) > 0:
|
||||
self.makeAlert(
|
||||
"Found %d orphaned file(s) in project folder!" % len(orphanFiles),
|
||||
"Found %d orphaned file(s) in project folder." % len(orphanFiles),
|
||||
nwAlert.WARN
|
||||
)
|
||||
else:
|
||||
@@ -998,7 +1070,7 @@ class NWProject():
|
||||
def _appendSessionStats(self):
|
||||
"""Append session statistics to the sessions log file.
|
||||
"""
|
||||
if self.projMeta is None:
|
||||
if not self.ensureFolderStructure():
|
||||
return False
|
||||
|
||||
sessionFile = path.join(self.projMeta, nwFiles.SESS_INFO)
|
||||
@@ -1016,12 +1088,122 @@ class NWProject():
|
||||
|
||||
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
|
||||
|
||||
# ================================================================================================ #
|
||||
# =============================================================================================== #
|
||||
# NWTree
|
||||
# Class holding the project tree for the NWProject
|
||||
# ================================================================================================ #
|
||||
# =============================================================================================== #
|
||||
|
||||
class NWTree():
|
||||
|
||||
@@ -1102,7 +1284,7 @@ class NWTree():
|
||||
for tHandle in self._treeOrder:
|
||||
tItem = self.__getitem__(tHandle)
|
||||
tItem.packXML(xContent)
|
||||
return
|
||||
return
|
||||
|
||||
def unpackXML(self, xContent):
|
||||
"""Iterate through all items of a content XML object and add
|
||||
@@ -1120,6 +1302,51 @@ class NWTree():
|
||||
|
||||
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
|
||||
##
|
||||
@@ -1356,10 +1583,10 @@ class NWTree():
|
||||
|
||||
# END Class NWTree
|
||||
|
||||
# ================================================================================================ #
|
||||
# =============================================================================================== #
|
||||
# NWItem
|
||||
# Class holding the project items making up the NWProject
|
||||
# ================================================================================================ #
|
||||
# =============================================================================================== #
|
||||
|
||||
class NWItem():
|
||||
|
||||
@@ -1402,7 +1629,6 @@ class NWItem():
|
||||
xSub = self._subPack(xPack,"type", text=str(self.itemType.name))
|
||||
xSub = self._subPack(xPack,"class", text=str(self.itemClass.name))
|
||||
xSub = self._subPack(xPack,"status", text=str(self.itemStatus))
|
||||
xSub = self._subPack(xPack,"expanded", text=str(self.isExpanded))
|
||||
if self.itemType == nwItemType.FILE:
|
||||
xSub = self._subPack(xPack,"exported", text=str(self.isExported))
|
||||
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,"paraCount", text=str(self.paraCount), none=False)
|
||||
xSub = self._subPack(xPack,"cursorPos", text=str(self.cursorPos), none=False)
|
||||
else:
|
||||
xSub = self._subPack(xPack,"expanded", text=str(self.isExpanded))
|
||||
return
|
||||
|
||||
def unpackXML(self, xItem):
|
||||
@@ -1601,10 +1829,10 @@ class NWItem():
|
||||
|
||||
# END Class NWItem
|
||||
|
||||
# ================================================================================================ #
|
||||
# =============================================================================================== #
|
||||
# NWStatus
|
||||
# Class holding the item status values stored in the NWProject
|
||||
# ================================================================================================ #
|
||||
# =============================================================================================== #
|
||||
|
||||
class NWStatus():
|
||||
|
||||
|
||||
@@ -35,9 +35,9 @@ from nw.constants import isoLanguage
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# ================================================================================================ #
|
||||
# =============================================================================================== #
|
||||
# SpellChecking SuperClass
|
||||
# ================================================================================================ #
|
||||
# =============================================================================================== #
|
||||
|
||||
class NWSpellCheck():
|
||||
|
||||
@@ -129,9 +129,9 @@ class NWSpellCheck():
|
||||
|
||||
# END Class NWSpellCheck
|
||||
|
||||
# ================================================================================================ #
|
||||
# =============================================================================================== #
|
||||
# Enchant Based SpellChecking
|
||||
# ================================================================================================ #
|
||||
# =============================================================================================== #
|
||||
|
||||
class NWSpellEnchant(NWSpellCheck):
|
||||
|
||||
@@ -211,9 +211,9 @@ class NWSpellEnchantDummy:
|
||||
|
||||
# END Class NWSpellEnchantDummy
|
||||
|
||||
# ================================================================================================ #
|
||||
# =============================================================================================== #
|
||||
# Fallback SpellChecking Using difflib
|
||||
# ================================================================================================ #
|
||||
# =============================================================================================== #
|
||||
|
||||
class NWSpellSimple(NWSpellCheck):
|
||||
"""Internal spell check tool that uses standard Python packages with
|
||||
|
||||
+21
-16
@@ -143,7 +143,7 @@ class ToHtml(Tokenizer):
|
||||
parStyle = None
|
||||
tmpResult = []
|
||||
hasHardBreak = False
|
||||
for tType, tText, tFormat, tStyle in self.theTokens:
|
||||
for tType, tLine, tText, tFormat, tStyle in self.theTokens:
|
||||
|
||||
# Styles
|
||||
aStyle = []
|
||||
@@ -174,6 +174,11 @@ class ToHtml(Tokenizer):
|
||||
else:
|
||||
hStyle = ""
|
||||
|
||||
if self.linkHeaders:
|
||||
aNm = "<a name='head_%s:T%06d'></a>" % (self.theHandle, tLine)
|
||||
else:
|
||||
aNm = ""
|
||||
|
||||
# Process TextType
|
||||
if tType == self.T_EMPTY:
|
||||
if parStyle is None:
|
||||
@@ -191,23 +196,23 @@ class ToHtml(Tokenizer):
|
||||
|
||||
elif tType == self.T_TITLE:
|
||||
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:
|
||||
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:
|
||||
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:
|
||||
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:
|
||||
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:
|
||||
tmpResult.append("<p class='sep'>%s</p>\n" % tText)
|
||||
@@ -296,17 +301,17 @@ class ToHtml(Tokenizer):
|
||||
refTags = []
|
||||
if theBits[0] in nwLabels.KEY_NAME:
|
||||
retText += "<span class='tags'>%s:</span> " % nwLabels.KEY_NAME[theBits[0]]
|
||||
if self.genMode == self.M_PREVIEW:
|
||||
for tTag in theBits[1:]:
|
||||
refTags.append("<a href='#%s=%s'>%s</a>" % (
|
||||
theBits[0][1:], tTag, tTag
|
||||
))
|
||||
retText += ", ".join(refTags)
|
||||
if theBits[0] == nwKeyWords.TAG_KEY:
|
||||
retText += "<a name='tag_%s'>%s</a>" % (
|
||||
theBits[1], theBits[1]
|
||||
)
|
||||
else:
|
||||
if theBits[0] == nwKeyWords.TAG_KEY:
|
||||
retText += "<a name='tag_%s'/>%s" % (
|
||||
theBits[1], theBits[1]
|
||||
)
|
||||
if self.genMode == self.M_PREVIEW:
|
||||
for tTag in theBits[1:]:
|
||||
refTags.append("<a href='#%s=%s'>%s</a>" % (
|
||||
theBits[0][1:], tTag, tTag
|
||||
))
|
||||
retText += ", ".join(refTags)
|
||||
else:
|
||||
for tTag in theBits[1:]:
|
||||
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.hideSection = False # Do not include section headers
|
||||
|
||||
self.linkHeaders = False # Add an anchor before headers
|
||||
|
||||
# Instance Variables
|
||||
self.numChapter = 0 # Counter for chapter numbers
|
||||
self.numChScene = 0 # Counter for scene number within chapter
|
||||
@@ -174,6 +176,10 @@ class Tokenizer():
|
||||
self.hideSection = hideSection
|
||||
return
|
||||
|
||||
def setLinkHeaders(self, linkHeaders):
|
||||
self.linkHeaders = linkHeaders
|
||||
return
|
||||
|
||||
def setBodyText(self, doBodyText):
|
||||
self.doBodyText = doBodyText
|
||||
return
|
||||
@@ -307,12 +313,16 @@ class Tokenizer():
|
||||
self.theTokens = []
|
||||
self.theMarkdown = ""
|
||||
tmpMarkdown = []
|
||||
nLine = 0
|
||||
for aLine in self.theText.splitlines():
|
||||
nLine += 1
|
||||
|
||||
# Tag lines starting with specific characters
|
||||
if len(aLine.strip()) == 0:
|
||||
self.theTokens.append((
|
||||
self.T_EMPTY, "", None, self.A_NONE
|
||||
self.T_EMPTY, nLine,
|
||||
"", None,
|
||||
self.A_NONE
|
||||
))
|
||||
tmpMarkdown.append("\n")
|
||||
|
||||
@@ -320,45 +330,59 @@ class Tokenizer():
|
||||
cLine = aLine[1:].strip()
|
||||
if cLine.lower().startswith("synopsis:"):
|
||||
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:
|
||||
tmpMarkdown.append("%s\n" % aLine)
|
||||
else:
|
||||
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:
|
||||
tmpMarkdown.append("%s\n" % aLine)
|
||||
|
||||
elif aLine[0] == "@":
|
||||
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:
|
||||
tmpMarkdown.append("%s\n" % aLine)
|
||||
|
||||
elif aLine[:2] == "# ":
|
||||
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)
|
||||
|
||||
elif aLine[:3] == "## ":
|
||||
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)
|
||||
|
||||
elif aLine[:4] == "### ":
|
||||
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)
|
||||
|
||||
elif aLine[:5] == "#### ":
|
||||
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)
|
||||
|
||||
@@ -383,13 +407,17 @@ class Tokenizer():
|
||||
# sorted by position
|
||||
fmtPos = sorted(fmtPos, key=itemgetter(0))
|
||||
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)
|
||||
|
||||
# Always add an empty line at the end
|
||||
self.theTokens.append((
|
||||
self.T_EMPTY, "", None, self.A_NONE
|
||||
self.T_EMPTY, nLine,
|
||||
"", None,
|
||||
self.A_NONE
|
||||
))
|
||||
tmpMarkdown.append("\n")
|
||||
|
||||
@@ -413,100 +441,146 @@ class Tokenizer():
|
||||
for n in range(len(self.theTokens)):
|
||||
|
||||
tToken = self.theTokens[n]
|
||||
tType = tToken[0]
|
||||
tText = tToken[1]
|
||||
|
||||
# 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
|
||||
|
||||
elif tType == self.T_HEAD1:
|
||||
elif tToken[0] == self.T_HEAD1:
|
||||
# Main Title
|
||||
# ==========
|
||||
|
||||
tText = self._formatHeading(self.fmtTitle, tText)
|
||||
tTemp = self._formatHeading(self.fmtTitle, tToken[2])
|
||||
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
|
||||
# =============
|
||||
|
||||
# Numbered or Unnumbered
|
||||
if self.isUnNum:
|
||||
tText = self._formatHeading(self.fmtUnNum, tText)
|
||||
tTemp = self._formatHeading(self.fmtUnNum, tToken[2])
|
||||
else:
|
||||
self.numChapter += 1
|
||||
tText = self._formatHeading(self.fmtChapter, tText)
|
||||
tTemp = self._formatHeading(self.fmtChapter, tToken[2])
|
||||
|
||||
# Format the chapter header
|
||||
self.theTokens[n] = (
|
||||
tType, tText, None, self.A_PBB
|
||||
tToken[0],
|
||||
tToken[1],
|
||||
tTemp,
|
||||
None,
|
||||
self.A_PBB
|
||||
)
|
||||
|
||||
# Set scene variables
|
||||
self.firstScene = True
|
||||
self.numChScene = 0
|
||||
|
||||
elif tType == self.T_HEAD3:
|
||||
elif tToken[0] == self.T_HEAD3:
|
||||
# Novel Scene
|
||||
# ===========
|
||||
|
||||
self.numChScene += 1
|
||||
self.numAbsScene += 1
|
||||
|
||||
tTemp = self._formatHeading(self.fmtScene, tText)
|
||||
tTemp = self._formatHeading(self.fmtScene, tToken[2])
|
||||
if tTemp == "" and self.hideScene:
|
||||
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:
|
||||
if self.firstScene:
|
||||
self.theTokens[n] = (
|
||||
self.T_EMPTY, "", None, self.A_NONE
|
||||
self.T_EMPTY,
|
||||
tToken[1],
|
||||
"",
|
||||
None,
|
||||
self.A_NONE
|
||||
)
|
||||
else:
|
||||
self.theTokens[n] = (
|
||||
self.T_SKIP, "", None, self.A_NONE
|
||||
self.T_SKIP,
|
||||
tToken[1],
|
||||
"",
|
||||
None,
|
||||
self.A_NONE
|
||||
)
|
||||
elif tTemp == self.fmtScene:
|
||||
if self.firstScene:
|
||||
self.theTokens[n] = (
|
||||
self.T_EMPTY, "", None, self.A_NONE
|
||||
self.T_EMPTY,
|
||||
tToken[1],
|
||||
"",
|
||||
None,
|
||||
self.A_NONE
|
||||
)
|
||||
else:
|
||||
self.theTokens[n] = (
|
||||
self.T_SEP, tTemp, None, self.A_CENTRE
|
||||
self.T_SEP,
|
||||
tToken[1],
|
||||
tTemp,
|
||||
None,
|
||||
self.A_CENTRE
|
||||
)
|
||||
else:
|
||||
self.theTokens[n] = (
|
||||
tType, tTemp, None, self.A_NONE
|
||||
tToken[0],
|
||||
tToken[1],
|
||||
tTemp,
|
||||
None,
|
||||
self.A_NONE
|
||||
)
|
||||
|
||||
# Definitely no longer the first scene
|
||||
self.firstScene = False
|
||||
|
||||
elif tType == self.T_HEAD4:
|
||||
elif tToken[0] == self.T_HEAD4:
|
||||
# Novel Section
|
||||
# =============
|
||||
|
||||
tTemp = self._formatHeading(self.fmtSection, tText)
|
||||
tTemp = self._formatHeading(self.fmtSection, tToken[2])
|
||||
if tTemp == "" and self.hideSection:
|
||||
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:
|
||||
self.theTokens[n] = (
|
||||
self.T_SKIP, "", None, self.A_NONE
|
||||
self.T_SKIP,
|
||||
tToken[1],
|
||||
"",
|
||||
None,
|
||||
self.A_NONE
|
||||
)
|
||||
elif tTemp == self.fmtSection:
|
||||
self.theTokens[n] = (
|
||||
self.T_SEP, tTemp, None, self.A_CENTRE
|
||||
self.T_SEP,
|
||||
tToken[1],
|
||||
tTemp,
|
||||
None,
|
||||
self.A_CENTRE
|
||||
)
|
||||
else:
|
||||
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.
|
||||
@@ -515,21 +589,30 @@ class Tokenizer():
|
||||
# We also swap header level 1 with a title type instead.
|
||||
if self.isTitle or self.isPart:
|
||||
for n, tToken in enumerate(self.theTokens):
|
||||
tType = tToken[0]
|
||||
tText = tToken[1]
|
||||
tFormat = tToken[2]
|
||||
if tType == self.T_HEAD1:
|
||||
if tToken[0] == self.T_HEAD1:
|
||||
if self.isTitle:
|
||||
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:
|
||||
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:
|
||||
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
|
||||
@@ -537,23 +620,32 @@ class Tokenizer():
|
||||
if n >= 0:
|
||||
tToken = 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
|
||||
# page, unless it's empty.
|
||||
if self.isPage:
|
||||
for n, tToken in enumerate(self.theTokens):
|
||||
tType = tToken[0]
|
||||
tText = tToken[1]
|
||||
tFormat = tToken[2]
|
||||
if n == 0:
|
||||
self.theTokens[n] = (
|
||||
tType, tText, tFormat, self.A_LEFT | self.A_PBB
|
||||
tToken[0],
|
||||
tToken[1],
|
||||
tToken[2],
|
||||
tToken[3],
|
||||
self.A_LEFT | self.A_PBB
|
||||
)
|
||||
else:
|
||||
self.theTokens[n] = (
|
||||
tType, tText, tFormat, self.A_LEFT
|
||||
tToken[0],
|
||||
tToken[1],
|
||||
tToken[2],
|
||||
tToken[3],
|
||||
self.A_LEFT
|
||||
)
|
||||
|
||||
return
|
||||
@@ -566,10 +658,11 @@ class Tokenizer():
|
||||
"""Replaces the %keyword% strings.
|
||||
"""
|
||||
theTitle = theTitle.replace(r"%title%", theText)
|
||||
theTitle = theTitle.replace(r"%ch%", str(self.numChapter))
|
||||
theTitle = theTitle.replace(r"%sc%", str(self.numChScene))
|
||||
theTitle = theTitle.replace(r"%sca%", str(self.numAbsScene))
|
||||
theTitle = theTitle.replace(r"%chw%", numberToWord(self.numChapter,"en"))
|
||||
theTitle = theTitle.replace(r"%ch%", str(self.numChapter))
|
||||
theTitle = theTitle.replace(r"%sc%", str(self.numChScene))
|
||||
theTitle = theTitle.replace(r"%sca%", str(self.numAbsScene))
|
||||
if r"%chw%" in theTitle:
|
||||
theTitle = theTitle.replace(r"%chw%", numberToWord(self.numChapter,"en"))
|
||||
return theTitle
|
||||
|
||||
# END Class Tokenizer
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
File History:
|
||||
Created: 2019-04-22 [0.0.1] countWords
|
||||
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
|
||||
|
||||
This file is a part of novelWriter
|
||||
@@ -81,50 +80,6 @@ def countWords(theText):
|
||||
|
||||
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):
|
||||
"""Wrapper for converting numbers to words for chapter headings.
|
||||
"""
|
||||
|
||||
+2
-2
@@ -9,7 +9,7 @@ from nw.gui.theme import GuiTheme
|
||||
|
||||
# Dialogs
|
||||
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.docsplit import GuiDocSplit
|
||||
from nw.gui.dialogs.itemeditor import GuiItemEditor
|
||||
@@ -40,7 +40,7 @@ __all__ = [
|
||||
"GuiMainStatus",
|
||||
"GuiTheme",
|
||||
"GuiAbout",
|
||||
"GuiConfigEditor",
|
||||
"GuiPreferences",
|
||||
"GuiDocMerge",
|
||||
"GuiDocSplit",
|
||||
"GuiItemEditor",
|
||||
|
||||
+33
-24
@@ -34,12 +34,12 @@ from time import time
|
||||
from PyQt5.QtCore import Qt, QByteArray
|
||||
from PyQt5.QtPrintSupport import QPrinter, QPrintPreviewDialog
|
||||
from PyQt5.QtGui import (
|
||||
QTextOption, QPalette, QColor, QTextDocumentWriter, QFont
|
||||
QPalette, QColor, QTextDocumentWriter, QFont
|
||||
)
|
||||
from PyQt5.QtWidgets import (
|
||||
QDialog, QVBoxLayout, QHBoxLayout, QTextBrowser, QPushButton, QLabel,
|
||||
QLineEdit, QGroupBox, QGridLayout, QProgressBar, QMenu, QAction,
|
||||
QFileDialog, QFontComboBox, QSpinBox, QDialogButtonBox
|
||||
QFileDialog, QFontComboBox, QSpinBox
|
||||
)
|
||||
|
||||
from nw.gui.additions import QSwitch
|
||||
@@ -83,8 +83,7 @@ class GuiBuildNovel(QDialog):
|
||||
self.optState.getInt("GuiBuildNovel", "winHeight", 800)
|
||||
)
|
||||
|
||||
self.outerBox = QVBoxLayout()
|
||||
self.innerBox = QHBoxLayout()
|
||||
self.outerBox = QHBoxLayout()
|
||||
self.toolsBox = QVBoxLayout()
|
||||
|
||||
self.docView = GuiBuildNovelDocView(self, self.theProject)
|
||||
@@ -173,7 +172,9 @@ class GuiBuildNovel(QDialog):
|
||||
|
||||
self.textFont = QFontComboBox()
|
||||
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(
|
||||
QFont(self.optState.getString("GuiBuildNovel", "textFont", self.mainConf.textFont))
|
||||
)
|
||||
@@ -183,13 +184,17 @@ class GuiBuildNovel(QDialog):
|
||||
self.textSize.setMinimum(5)
|
||||
self.textSize.setMaximum(48)
|
||||
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.optState.getInt("GuiBuildNovel", "textSize", self.mainConf.textSize)
|
||||
)
|
||||
|
||||
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.optState.getBool("GuiBuildNovel", "justifyText", False)
|
||||
)
|
||||
@@ -211,15 +216,21 @@ class GuiBuildNovel(QDialog):
|
||||
self.includeGroup.setLayout(self.includeForm)
|
||||
|
||||
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.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.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.includeForm.addWidget(QLabel("Include synopsis"), 0, 0, 1, 1, Qt.AlignLeft)
|
||||
@@ -292,7 +303,7 @@ class GuiBuildNovel(QDialog):
|
||||
|
||||
# Action Buttons
|
||||
# ==============
|
||||
self.buttonForm = QGridLayout()
|
||||
self.buttonBox = QHBoxLayout()
|
||||
|
||||
self.btnPrint = QPushButton("Print")
|
||||
self.btnPrint.clicked.connect(self._printDocument)
|
||||
@@ -326,12 +337,13 @@ class GuiBuildNovel(QDialog):
|
||||
self.saveTXT.triggered.connect(lambda: self._saveDocument(self.FMT_TXT))
|
||||
self.saveMenu.addAction(self.saveTXT)
|
||||
|
||||
self.buttonForm.addWidget(self.btnSave, 0, 0)
|
||||
self.buttonForm.addWidget(self.btnPrint, 0, 1)
|
||||
self.btnClose = QPushButton("Close")
|
||||
self.btnClose.clicked.connect(self._doClose)
|
||||
|
||||
# Buttons
|
||||
self.buttonBox = QDialogButtonBox(QDialogButtonBox.Close)
|
||||
self.buttonBox.rejected.connect(self._doClose)
|
||||
self.buttonBox.addWidget(self.btnSave)
|
||||
self.buttonBox.addWidget(self.btnPrint)
|
||||
self.buttonBox.addWidget(self.btnClose)
|
||||
self.buttonBox.setSpacing(4)
|
||||
|
||||
# Assemble GUI
|
||||
# ============
|
||||
@@ -343,18 +355,15 @@ class GuiBuildNovel(QDialog):
|
||||
self.toolsBox.addWidget(self.buildProgress)
|
||||
self.toolsBox.addWidget(self.buildNovel)
|
||||
self.toolsBox.addSpacing(8)
|
||||
self.toolsBox.addLayout(self.buttonForm)
|
||||
self.toolsBox.addLayout(self.buttonBox)
|
||||
|
||||
self.innerBox.addLayout(self.toolsBox)
|
||||
self.innerBox.addWidget(self.docView)
|
||||
self.outerBox.addLayout(self.toolsBox)
|
||||
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.innerBox.setStretch(0, 0)
|
||||
self.innerBox.setStretch(1, 1)
|
||||
|
||||
self.show()
|
||||
|
||||
logger.debug("GuiBuildNovel initialisation complete")
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
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.docsplit import GuiDocSplit
|
||||
from nw.gui.dialogs.itemeditor import GuiItemEditor
|
||||
@@ -11,7 +11,7 @@ from nw.gui.dialogs.sessionlog import GuiSessionLogView
|
||||
|
||||
__all__ = [
|
||||
"GuiAbout",
|
||||
"GuiConfigEditor",
|
||||
"GuiPreferences",
|
||||
"GuiDocMerge",
|
||||
"GuiDocSplit",
|
||||
"GuiItemEditor",
|
||||
|
||||
+13
-10
@@ -119,16 +119,19 @@ class GuiAbout(QDialog):
|
||||
"<h2>About {name:s}</h2>"
|
||||
"<p>{copyright:s}.</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 "
|
||||
"novels. It is written in Python 3 with a Qt5 GUI, using PyQt5.</p>"
|
||||
"<p>{name:s} is free software: you can redistribute it and/or modify it under the "
|
||||
"terms of the GNU General Public License as published by the Free Software Foundation, "
|
||||
"either version 3 of the License, or (at your option) any later version.</p>"
|
||||
"<p>{name:s} is distributed in the hope that it will be useful, but WITHOUT ANY "
|
||||
"WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A "
|
||||
"PARTICULAR PURPOSE.</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>"
|
||||
"<p>{name:s} is a markdown-like text editor designed for "
|
||||
"organising and writing novels. It is written in Python 3 with a "
|
||||
"Qt5 GUI, using PyQt5.</p>"
|
||||
"<p>{name:s} is free software: you can redistribute it and/or "
|
||||
"modify it under the terms of the GNU General Public License as "
|
||||
"published by the Free Software Foundation, either version 3 of "
|
||||
"the License, or (at your option) any later version.</p>"
|
||||
"<p>{name:s} is distributed in the hope that it will be useful, "
|
||||
"but WITHOUT ANY WARRANTY; without even the implied warranty of "
|
||||
"MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.</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>"
|
||||
"<p>{credits:s}</p>"
|
||||
).format(
|
||||
|
||||
@@ -150,7 +150,9 @@ class GuiDocSplit(QDialog):
|
||||
), nwAlert.ERROR)
|
||||
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)
|
||||
logger.verbose("Creating folder %s" % fHandle)
|
||||
|
||||
@@ -165,7 +167,7 @@ class GuiDocSplit(QDialog):
|
||||
elif wTitle.startswith("### "):
|
||||
itemLayout = nwItemLayout.SCENE
|
||||
elif wTitle.startswith("#### "):
|
||||
itemLayout = nwItemLayout.PAGE
|
||||
itemLayout = nwItemLayout.SCENE
|
||||
|
||||
wTitle = wTitle.lstrip("#")
|
||||
wTitle = wTitle.strip()
|
||||
@@ -174,7 +176,9 @@ class GuiDocSplit(QDialog):
|
||||
newItem = self.theProject.projTree[nHandle]
|
||||
newItem.setLayout(itemLayout)
|
||||
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])
|
||||
@@ -227,7 +231,9 @@ class GuiDocSplit(QDialog):
|
||||
|
||||
spLevel = self.splitLevel.currentData()
|
||||
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
|
||||
for aLine in theText.splitlines():
|
||||
|
||||
@@ -44,12 +44,12 @@ from nw.constants import nwAlert, nwQuotes
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
class GuiConfigEditor(PagedDialog):
|
||||
class GuiPreferences(PagedDialog):
|
||||
|
||||
def __init__(self, theParent, theProject):
|
||||
PagedDialog.__init__(self, theParent)
|
||||
|
||||
logger.debug("Initialising ConfigEditor ...")
|
||||
logger.debug("Initialising GuiPreferences ...")
|
||||
|
||||
self.mainConf = nw.CONFIG
|
||||
self.theParent = theParent
|
||||
@@ -74,7 +74,7 @@ class GuiConfigEditor(PagedDialog):
|
||||
|
||||
self.show()
|
||||
|
||||
logger.debug("ConfigEditor initialisation complete")
|
||||
logger.debug("GuiPreferences initialisation complete")
|
||||
|
||||
return
|
||||
|
||||
@@ -122,7 +122,7 @@ class GuiConfigEditor(PagedDialog):
|
||||
self.close()
|
||||
return
|
||||
|
||||
# END Class GuiConfigEditor
|
||||
# END Class GuiPreferences
|
||||
|
||||
class GuiConfigEditGeneralTab(QWidget):
|
||||
|
||||
@@ -228,7 +228,7 @@ class GuiConfigEditGeneralTab(QWidget):
|
||||
|
||||
## Backup Path
|
||||
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.backupPathRow = self.mainForm.addRow(
|
||||
"Backup storage location",
|
||||
@@ -362,7 +362,9 @@ class GuiProjectEditStatus(QWidget):
|
||||
self.listBox.takeItem(iRow)
|
||||
self.colChanged = True
|
||||
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
|
||||
|
||||
def _saveItem(self):
|
||||
|
||||
@@ -180,11 +180,15 @@ class GuiSessionLogView(QDialog):
|
||||
inData = inLine.split()
|
||||
if len(inData) != 8:
|
||||
continue
|
||||
dStart = datetime.strptime("%s %s" % (inData[1],inData[2]), nwConst.tStampFmt)
|
||||
dEnd = datetime.strptime("%s %s" % (inData[4],inData[5]), nwConst.tStampFmt)
|
||||
dStart = datetime.strptime(
|
||||
"%s %s" % (inData[1],inData[2]), nwConst.tStampFmt
|
||||
)
|
||||
dEnd = datetime.strptime(
|
||||
"%s %s" % (inData[4],inData[5]), nwConst.tStampFmt
|
||||
)
|
||||
nWords = int(inData[7])
|
||||
tDiff = dEnd - dStart
|
||||
sDiff = tDiff.total_seconds()
|
||||
tDiff = dEnd - dStart
|
||||
sDiff = tDiff.total_seconds()
|
||||
|
||||
self.timeTotal += sDiff
|
||||
if abs(nWords) > 0:
|
||||
@@ -196,7 +200,9 @@ class GuiSessionLogView(QDialog):
|
||||
if hideNegative and nWords < 0:
|
||||
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(2,Qt.AlignRight)
|
||||
@@ -208,7 +214,9 @@ class GuiSessionLogView(QDialog):
|
||||
self.listBox.addTopLevelItem(newItem)
|
||||
|
||||
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
|
||||
|
||||
self.labelFilter.setText(self._formatTime(self.timeFilter))
|
||||
|
||||
@@ -405,6 +405,8 @@ class GuiDocEditor(QTextEdit):
|
||||
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)
|
||||
@@ -420,12 +422,13 @@ class GuiDocEditor(QTextEdit):
|
||||
def setCursorLine(self, theLine):
|
||||
"""Move the cursor to a given line in the document.
|
||||
"""
|
||||
if theLine is None:
|
||||
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
|
||||
|
||||
##
|
||||
@@ -480,7 +483,9 @@ class GuiDocEditor(QTextEdit):
|
||||
self.hLight.rehighlight()
|
||||
qApp.restoreOverrideCursor()
|
||||
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
|
||||
|
||||
@@ -717,7 +722,9 @@ class GuiDocEditor(QTextEdit):
|
||||
"""
|
||||
sinceActive = time()-self.lastEdit
|
||||
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()
|
||||
elif self.wCounter.isRunning():
|
||||
logger.verbose("Word counter thread is busy")
|
||||
@@ -953,7 +960,9 @@ class GuiDocEditor(QTextEdit):
|
||||
theText = newText
|
||||
cOffset -= 0
|
||||
else:
|
||||
logger.error("Unknown or unsupported block format requested: %s" % str(docAction))
|
||||
logger.error(
|
||||
"Unknown or unsupported block format requested: %s" % str(docAction)
|
||||
)
|
||||
return
|
||||
|
||||
# Replace the block text
|
||||
|
||||
@@ -396,7 +396,7 @@ class GuiDocTree(QTreeWidget):
|
||||
trItemP.takeChild(tIndex)
|
||||
del self.theProject.projTree[tHandle]
|
||||
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
|
||||
|
||||
elif nwItemS.itemType == nwItemType.ROOT:
|
||||
@@ -407,7 +407,7 @@ class GuiDocTree(QTreeWidget):
|
||||
self.theParent.mainMenu.setAvailableRoot()
|
||||
self.theProject.setProjectChanged(True)
|
||||
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 True
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
import logging
|
||||
import nw
|
||||
|
||||
from PyQt5.QtCore import Qt
|
||||
from PyQt5.QtCore import Qt, QUrl
|
||||
from PyQt5.QtWidgets import QTextBrowser
|
||||
from PyQt5.QtGui import QTextOption, QFont, QPalette, QColor, QTextCursor
|
||||
|
||||
@@ -134,6 +134,7 @@ class GuiDocViewer(QTextBrowser):
|
||||
sPos = self.verticalScrollBar().value()
|
||||
aDoc = ToHtml(self.theProject, self.theParent)
|
||||
aDoc.setPreview(True, self.mainConf.viewComments)
|
||||
aDoc.setLinkHeaders(True)
|
||||
aDoc.setText(tHandle)
|
||||
aDoc.doAutoReplace()
|
||||
aDoc.tokenizeText()
|
||||
@@ -164,19 +165,17 @@ class GuiDocViewer(QTextBrowser):
|
||||
index being up to date.
|
||||
"""
|
||||
logger.debug("Loading document from tag '%s'" % theTag)
|
||||
|
||||
if theTag in self.theParent.theIndex.tagIndex.keys():
|
||||
theTarget = self.theParent.theIndex.tagIndex[theTag]
|
||||
tHandle, onLine, sTitle = self.theParent.theIndex.getTagSource(theTag)
|
||||
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, or by pressing F9."
|
||||
) % theTag, nwAlert.ERROR)
|
||||
return
|
||||
else:
|
||||
logger.debug("The tag was not found in the index")
|
||||
return False
|
||||
|
||||
if len(theTarget) != 3:
|
||||
# Just to make sure the index is not messed up
|
||||
return False
|
||||
|
||||
self.loadText(theTarget[1])
|
||||
|
||||
self.loadText(tHandle)
|
||||
self.navigateTo("#head_%s:%s" % (tHandle, sTitle))
|
||||
return True
|
||||
|
||||
def docAction(self, theAction):
|
||||
@@ -200,6 +199,15 @@ class GuiDocViewer(QTextBrowser):
|
||||
return False
|
||||
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):
|
||||
"""Automatically adjust the margins so the text is centred if
|
||||
Config.textFixedW is enabled or we're in Zen mode. Otherwise,
|
||||
@@ -233,6 +241,33 @@ class GuiDocViewer(QTextBrowser):
|
||||
self.updateDocMargins()
|
||||
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
|
||||
##
|
||||
@@ -262,25 +297,11 @@ class GuiDocViewer(QTextBrowser):
|
||||
"""Slot for a link in the document being clicked.
|
||||
"""
|
||||
theLink = theURL.url()
|
||||
tHandle = None
|
||||
onLine = 0
|
||||
theTag = ""
|
||||
logger.verbose("Clicked link: '%s'" % theLink)
|
||||
if len(theLink) > 0:
|
||||
theBits = theLink.split("=")
|
||||
if len(theBits) == 2:
|
||||
theTag = 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)
|
||||
|
||||
self.loadFromTag(theBits[1])
|
||||
return
|
||||
|
||||
def _makeStyleSheet(self):
|
||||
|
||||
@@ -107,7 +107,9 @@ class GuiDocViewDetails(QWidget):
|
||||
for tHandle in theRefs:
|
||||
tItem = self.theProject.projTree[tHandle]
|
||||
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.adjustSize()
|
||||
@@ -122,9 +124,10 @@ class GuiDocViewDetails(QWidget):
|
||||
"""Capture the link-click and forward it to the document viewer
|
||||
class for handling.
|
||||
"""
|
||||
if len(theLink) == 18:
|
||||
tHandle = theLink[-13:]
|
||||
self.theParent.viewDocument(tHandle)
|
||||
logger.verbose("Clicked link: '%s'" % theLink)
|
||||
if len(theLink) == 27:
|
||||
tHandle = theLink[6:19]
|
||||
self.theParent.viewDocument(tHandle, theLink)
|
||||
return
|
||||
|
||||
def _doShowHide(self, chState):
|
||||
|
||||
+6
-2
@@ -249,7 +249,9 @@ class GuiIcons:
|
||||
try:
|
||||
confParser.read_file(open(themeConf, mode="r", encoding="utf8"))
|
||||
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
|
||||
themeName = ""
|
||||
if confParser.has_section("Main"):
|
||||
@@ -295,7 +297,9 @@ class GuiIcons:
|
||||
|
||||
# Finally. we check if we have a fallback icon
|
||||
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):
|
||||
logger.verbose("Loading icon '%s' from fallback theme" % iconKey)
|
||||
return QIcon(fbackIcon)
|
||||
|
||||
+5
-1
@@ -137,13 +137,17 @@ class GuiMainStatus(QStatusBar):
|
||||
self._updateTime()
|
||||
return True
|
||||
|
||||
##
|
||||
# Setters
|
||||
##
|
||||
|
||||
def setRefTime(self, theTime):
|
||||
"""Set the reference time for the status bar clock.
|
||||
"""
|
||||
self.refTime = theTime
|
||||
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.
|
||||
"""
|
||||
self.showMessage(theMessage, int(timeOut*1000))
|
||||
|
||||
+6
-2
@@ -284,7 +284,9 @@ class GuiTheme:
|
||||
try:
|
||||
confParser.read_file(open(themeConf, mode="r", encoding="utf8"))
|
||||
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
|
||||
themeName = ""
|
||||
if confParser.has_section("Main"):
|
||||
@@ -314,7 +316,9 @@ class GuiTheme:
|
||||
try:
|
||||
confParser.read_file(open(syntaxPath, mode="r", encoding="utf8"))
|
||||
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 []
|
||||
syntaxName = ""
|
||||
if confParser.has_section("Main"):
|
||||
|
||||
+25
-19
@@ -42,7 +42,7 @@ from PyQt5.QtWidgets import (
|
||||
from nw.gui import (
|
||||
GuiMainMenu, GuiMainStatus, GuiTheme, GuiDocTree, GuiDocEditor,
|
||||
GuiDocViewer, GuiDocDetails, GuiSearchBar, GuiNoticeBar, GuiDocViewDetails,
|
||||
GuiConfigEditor, GuiProjectSettings, GuiItemEditor, GuiProjectOutline,
|
||||
GuiPreferences, GuiProjectSettings, GuiItemEditor, GuiProjectOutline,
|
||||
GuiSessionLogView, GuiDocMerge, GuiDocSplit, GuiProjectLoad, GuiBuildNovel
|
||||
)
|
||||
from nw.core import NWProject, NWDoc, NWIndex
|
||||
@@ -485,7 +485,7 @@ class GuiMain(QMainWindow):
|
||||
self.docEditor.saveText()
|
||||
return True
|
||||
|
||||
def viewDocument(self, tHandle=None):
|
||||
def viewDocument(self, tHandle=None, navLink=None):
|
||||
"""Load a document for viewing in the view panel.
|
||||
"""
|
||||
if tHandle is None:
|
||||
@@ -503,13 +503,15 @@ class GuiMain(QMainWindow):
|
||||
# Make sure main tab is in Editor view
|
||||
self.tabWidget.setCurrentWidget(self.splitView)
|
||||
|
||||
if self.docViewer.loadText(tHandle) and not self.viewPane.isVisible():
|
||||
bPos = self.splitMain.sizes()
|
||||
self.viewPane.setVisible(True)
|
||||
vPos = [0,0]
|
||||
vPos[0] = int(bPos[1]/2)
|
||||
vPos[1] = bPos[1]-vPos[0]
|
||||
self.splitView.setSizes(vPos)
|
||||
if self.docViewer.loadText(tHandle):
|
||||
if not self.viewPane.isVisible():
|
||||
bPos = self.splitMain.sizes()
|
||||
self.viewPane.setVisible(True)
|
||||
vPos = [0,0]
|
||||
vPos[0] = int(bPos[1]/2)
|
||||
vPos[1] = bPos[1]-vPos[0]
|
||||
self.splitView.setSizes(vPos)
|
||||
self.docViewer.navigateTo(navLink)
|
||||
|
||||
return True
|
||||
|
||||
@@ -551,7 +553,7 @@ class GuiMain(QMainWindow):
|
||||
|
||||
if self.docEditor.theHandle is None:
|
||||
self.makeAlert(
|
||||
["Please open a document to import the text file into."],
|
||||
"Please open a document to import the text file into.",
|
||||
nwAlert.ERROR
|
||||
)
|
||||
return False
|
||||
@@ -666,6 +668,12 @@ class GuiMain(QMainWindow):
|
||||
|
||||
theDoc = NWDoc(self.theProject, self)
|
||||
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:
|
||||
logger.verbose("Scanning: %s" % tItem.itemName)
|
||||
theText = theDoc.openDocument(tItem.itemHandle, showStatus=False)
|
||||
@@ -681,16 +689,14 @@ class GuiMain(QMainWindow):
|
||||
self.treeView.propagateCount(tItem.itemHandle, wC)
|
||||
self.treeView.projectWordCount()
|
||||
|
||||
self.statusBar.setStatus("Building index: %.2f%%" % (100.0*(nDone + 1)/nItems))
|
||||
|
||||
self.docEditor.reloadText()
|
||||
qApp.restoreOverrideCursor()
|
||||
tEnd = time()
|
||||
self.statusBar.setStatus("Indexing completed in %.1f ms" % ((tEnd - tStart)*1000.0))
|
||||
self.docEditor.reloadText()
|
||||
|
||||
qApp.restoreOverrideCursor()
|
||||
|
||||
if self.mainConf.showGUI:
|
||||
self.makeAlert(
|
||||
"Project index rebuilt in %.3f seconds." % (tEnd - tStart), nwAlert.INFO
|
||||
)
|
||||
self.makeAlert("The project index has been successfully rebuilt.", nwAlert.INFO)
|
||||
|
||||
return True
|
||||
|
||||
@@ -735,7 +741,7 @@ class GuiMain(QMainWindow):
|
||||
def editConfigDialog(self):
|
||||
"""Open the preferences dialog.
|
||||
"""
|
||||
dlgConf = GuiConfigEditor(self, self.theProject)
|
||||
dlgConf = GuiPreferences(self, self.theProject)
|
||||
if dlgConf.exec_() == QDialog.Accepted:
|
||||
logger.debug("Applying new preferences")
|
||||
self.initMain()
|
||||
@@ -776,7 +782,7 @@ class GuiMain(QMainWindow):
|
||||
0 = info, 1 = warning, and 2 = error.
|
||||
"""
|
||||
if isinstance(theMessage, list):
|
||||
popMsg = " ".join(theMessage)
|
||||
popMsg = "<br>".join(theMessage)
|
||||
logMsg = theMessage
|
||||
else:
|
||||
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'?>
|
||||
<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>
|
||||
<name>Sample Project</name>
|
||||
<title>Sample Project</title>
|
||||
@@ -11,7 +11,7 @@
|
||||
<spellCheck>True</spellCheck>
|
||||
<autoOutline>True</autoOutline>
|
||||
<lastEdited>636b6aa9b697b</lastEdited>
|
||||
<lastViewed>ba8a28a246524</lastViewed>
|
||||
<lastViewed>b3e74dbc1f584</lastViewed>
|
||||
<lastWordCount>914</lastWordCount>
|
||||
<autoReplace>
|
||||
<A>B</A>
|
||||
@@ -57,7 +57,6 @@
|
||||
<type>FILE</type>
|
||||
<class>NOVEL</class>
|
||||
<status>Started</status>
|
||||
<expanded>False</expanded>
|
||||
<exported>True</exported>
|
||||
<layout>TITLE</layout>
|
||||
<charCount>72</charCount>
|
||||
@@ -70,10 +69,9 @@
|
||||
<type>FILE</type>
|
||||
<class>NOVEL</class>
|
||||
<status>New</status>
|
||||
<expanded>False</expanded>
|
||||
<exported>True</exported>
|
||||
<layout>PAGE</layout>
|
||||
<charCount>208</charCount>
|
||||
<charCount>210</charCount>
|
||||
<wordCount>40</wordCount>
|
||||
<paraCount>2</paraCount>
|
||||
<cursorPos>213</cursorPos>
|
||||
@@ -83,7 +81,6 @@
|
||||
<type>FILE</type>
|
||||
<class>NOVEL</class>
|
||||
<status>New</status>
|
||||
<expanded>False</expanded>
|
||||
<exported>True</exported>
|
||||
<layout>PARTITION</layout>
|
||||
<charCount>23</charCount>
|
||||
@@ -103,7 +100,6 @@
|
||||
<type>FILE</type>
|
||||
<class>NOVEL</class>
|
||||
<status>Notes</status>
|
||||
<expanded>False</expanded>
|
||||
<exported>True</exported>
|
||||
<layout>CHAPTER</layout>
|
||||
<charCount>12</charCount>
|
||||
@@ -116,20 +112,18 @@
|
||||
<type>FILE</type>
|
||||
<class>NOVEL</class>
|
||||
<status>1st Draft</status>
|
||||
<expanded>False</expanded>
|
||||
<exported>True</exported>
|
||||
<layout>SCENE</layout>
|
||||
<charCount>1199</charCount>
|
||||
<wordCount>216</wordCount>
|
||||
<paraCount>7</paraCount>
|
||||
<cursorPos>527</cursorPos>
|
||||
<cursorPos>1266</cursorPos>
|
||||
</item>
|
||||
<item handle="bc0cbd2a407f3" order="2" parent="e7ded148d6e4a">
|
||||
<name>Another Scene</name>
|
||||
<type>FILE</type>
|
||||
<class>NOVEL</class>
|
||||
<status>1st Draft</status>
|
||||
<expanded>False</expanded>
|
||||
<exported>True</exported>
|
||||
<layout>SCENE</layout>
|
||||
<charCount>476</charCount>
|
||||
@@ -142,7 +136,6 @@
|
||||
<type>FILE</type>
|
||||
<class>NOVEL</class>
|
||||
<status>Finished</status>
|
||||
<expanded>False</expanded>
|
||||
<exported>True</exported>
|
||||
<layout>UNNUMBERED</layout>
|
||||
<charCount>633</charCount>
|
||||
@@ -155,7 +148,6 @@
|
||||
<type>FILE</type>
|
||||
<class>NOVEL</class>
|
||||
<status>2nd Draft</status>
|
||||
<expanded>False</expanded>
|
||||
<exported>False</exported>
|
||||
<layout>NOTE</layout>
|
||||
<charCount>1692</charCount>
|
||||
@@ -168,7 +160,6 @@
|
||||
<type>FILE</type>
|
||||
<class>NOVEL</class>
|
||||
<status>1st Draft</status>
|
||||
<expanded>False</expanded>
|
||||
<exported>True</exported>
|
||||
<layout>CHAPTER</layout>
|
||||
<charCount>139</charCount>
|
||||
@@ -181,7 +172,6 @@
|
||||
<type>FILE</type>
|
||||
<class>NOVEL</class>
|
||||
<status>1st Draft</status>
|
||||
<expanded>False</expanded>
|
||||
<exported>True</exported>
|
||||
<layout>SCENE</layout>
|
||||
<charCount>189</charCount>
|
||||
@@ -208,7 +198,6 @@
|
||||
<type>FILE</type>
|
||||
<class>CHARACTER</class>
|
||||
<status>Minor</status>
|
||||
<expanded>False</expanded>
|
||||
<exported>True</exported>
|
||||
<layout>NOTE</layout>
|
||||
<charCount>49</charCount>
|
||||
@@ -221,7 +210,6 @@
|
||||
<type>FILE</type>
|
||||
<class>CHARACTER</class>
|
||||
<status>Major</status>
|
||||
<expanded>False</expanded>
|
||||
<exported>True</exported>
|
||||
<layout>NOTE</layout>
|
||||
<charCount>55</charCount>
|
||||
@@ -241,7 +229,6 @@
|
||||
<type>FILE</type>
|
||||
<class>WORLD</class>
|
||||
<status>Main</status>
|
||||
<expanded>False</expanded>
|
||||
<exported>True</exported>
|
||||
<layout>NOTE</layout>
|
||||
<charCount>76</charCount>
|
||||
@@ -254,7 +241,6 @@
|
||||
<type>FILE</type>
|
||||
<class>WORLD</class>
|
||||
<status>Minor</status>
|
||||
<expanded>False</expanded>
|
||||
<exported>True</exported>
|
||||
<layout>NOTE</layout>
|
||||
<charCount>115</charCount>
|
||||
@@ -267,7 +253,6 @@
|
||||
<type>FILE</type>
|
||||
<class>WORLD</class>
|
||||
<status>Major</status>
|
||||
<expanded>False</expanded>
|
||||
<exported>True</exported>
|
||||
<layout>NOTE</layout>
|
||||
<charCount>28</charCount>
|
||||
@@ -287,7 +272,6 @@
|
||||
<type>FILE</type>
|
||||
<class>NOVEL</class>
|
||||
<status>New</status>
|
||||
<expanded>False</expanded>
|
||||
<exported>True</exported>
|
||||
<layout>SCENE</layout>
|
||||
<charCount>0</charCount>
|
||||
|
||||
@@ -6,7 +6,7 @@ with open("README.md", "r") as inFile:
|
||||
|
||||
setuptools.setup(
|
||||
name = "novelWriter",
|
||||
version = "0.6.3",
|
||||
version = "0.7.0rc1",
|
||||
author = "Veronica Berglyd Olsen",
|
||||
author_email = "code@vkbo.net",
|
||||
description = "A markdown-like document editor for writing novels",
|
||||
@@ -27,7 +27,10 @@ setuptools.setup(
|
||||
"Source Code": "https://github.com/vkbo/novelWriter",
|
||||
},
|
||||
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)",
|
||||
"Development Status :: 3 - Alpha",
|
||||
"Operating System :: OS Independent",
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<?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>
|
||||
<name>New Project</name>
|
||||
<title></title>
|
||||
@@ -14,7 +14,7 @@
|
||||
<autoReplace/>
|
||||
<titleFormat>
|
||||
<title>%title%</title>
|
||||
<chapter>Chapter %num%\\%title%</chapter>
|
||||
<chapter>Chapter %ch%: %title%</chapter>
|
||||
<unnumbered>%title%</unnumbered>
|
||||
<scene>* * *</scene>
|
||||
<section></section>
|
||||
@@ -55,7 +55,6 @@
|
||||
<type>FILE</type>
|
||||
<class>NOVEL</class>
|
||||
<status>New</status>
|
||||
<expanded>False</expanded>
|
||||
<exported>True</exported>
|
||||
<layout>SCENE</layout>
|
||||
<charCount>0</charCount>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<?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>
|
||||
<name>New Project</name>
|
||||
<title></title>
|
||||
@@ -14,7 +14,7 @@
|
||||
<autoReplace/>
|
||||
<titleFormat>
|
||||
<title>%title%</title>
|
||||
<chapter>Chapter %num%\\%title%</chapter>
|
||||
<chapter>Chapter %ch%: %title%</chapter>
|
||||
<unnumbered>%title%</unnumbered>
|
||||
<scene>* * *</scene>
|
||||
<section></section>
|
||||
@@ -55,7 +55,6 @@
|
||||
<type>FILE</type>
|
||||
<class>NOVEL</class>
|
||||
<status>New</status>
|
||||
<expanded>False</expanded>
|
||||
<exported>True</exported>
|
||||
<layout>SCENE</layout>
|
||||
<charCount>331</charCount>
|
||||
@@ -75,7 +74,6 @@
|
||||
<type>FILE</type>
|
||||
<class>CHARACTER</class>
|
||||
<status>New</status>
|
||||
<expanded>False</expanded>
|
||||
<exported>True</exported>
|
||||
<layout>NOTE</layout>
|
||||
<charCount>34</charCount>
|
||||
@@ -95,7 +93,6 @@
|
||||
<type>FILE</type>
|
||||
<class>PLOT</class>
|
||||
<status>New</status>
|
||||
<expanded>False</expanded>
|
||||
<exported>True</exported>
|
||||
<layout>NOTE</layout>
|
||||
<charCount>48</charCount>
|
||||
@@ -115,7 +112,6 @@
|
||||
<type>FILE</type>
|
||||
<class>WORLD</class>
|
||||
<status>New</status>
|
||||
<expanded>False</expanded>
|
||||
<exported>True</exported>
|
||||
<layout>NOTE</layout>
|
||||
<charCount>51</charCount>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<?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>
|
||||
<name>Project Name</name>
|
||||
<title>Project Title</title>
|
||||
@@ -18,7 +18,7 @@
|
||||
</autoReplace>
|
||||
<titleFormat>
|
||||
<title>%title%</title>
|
||||
<chapter>Chapter %num%\\%title%</chapter>
|
||||
<chapter>Chapter %ch%: %title%</chapter>
|
||||
<unnumbered>%title%</unnumbered>
|
||||
<scene>* * *</scene>
|
||||
<section></section>
|
||||
@@ -59,7 +59,6 @@
|
||||
<type>FILE</type>
|
||||
<class>NOVEL</class>
|
||||
<status>New</status>
|
||||
<expanded>False</expanded>
|
||||
<exported>True</exported>
|
||||
<layout>SCENE</layout>
|
||||
<charCount>0</charCount>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<?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>
|
||||
<name>New Project</name>
|
||||
<title></title>
|
||||
@@ -14,7 +14,7 @@
|
||||
<autoReplace/>
|
||||
<titleFormat>
|
||||
<title>%title%</title>
|
||||
<chapter>Chapter %num%\\%title%</chapter>
|
||||
<chapter>Chapter %ch%: %title%</chapter>
|
||||
<unnumbered>%title%</unnumbered>
|
||||
<scene>* * *</scene>
|
||||
<section></section>
|
||||
@@ -55,7 +55,6 @@
|
||||
<type>FILE</type>
|
||||
<class>NOVEL</class>
|
||||
<status>Note</status>
|
||||
<expanded>False</expanded>
|
||||
<exported>False</exported>
|
||||
<layout>PAGE</layout>
|
||||
<charCount>0</charCount>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<?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>
|
||||
<name>New Project</name>
|
||||
<title></title>
|
||||
@@ -14,7 +14,7 @@
|
||||
<autoReplace/>
|
||||
<titleFormat>
|
||||
<title>%title%</title>
|
||||
<chapter>Chapter %num%\\%title%</chapter>
|
||||
<chapter>Chapter %ch%: %title%</chapter>
|
||||
<unnumbered>%title%</unnumbered>
|
||||
<scene>* * *</scene>
|
||||
<section></section>
|
||||
@@ -76,7 +76,6 @@
|
||||
<type>FILE</type>
|
||||
<class>NOVEL</class>
|
||||
<status>New</status>
|
||||
<expanded>False</expanded>
|
||||
<exported>True</exported>
|
||||
<layout>SCENE</layout>
|
||||
<charCount>0</charCount>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<?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>
|
||||
<name>New Project</name>
|
||||
<title></title>
|
||||
@@ -14,7 +14,7 @@
|
||||
<autoReplace/>
|
||||
<titleFormat>
|
||||
<title>%title%</title>
|
||||
<chapter>Chapter %num%\\%title%</chapter>
|
||||
<chapter>Chapter %ch%: %title%</chapter>
|
||||
<unnumbered>%title%</unnumbered>
|
||||
<scene>* * *</scene>
|
||||
<section></section>
|
||||
@@ -76,7 +76,6 @@
|
||||
<type>FILE</type>
|
||||
<class>NOVEL</class>
|
||||
<status>New</status>
|
||||
<expanded>False</expanded>
|
||||
<exported>True</exported>
|
||||
<layout>SCENE</layout>
|
||||
<charCount>0</charCount>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<?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>
|
||||
<name>New Project</name>
|
||||
<title></title>
|
||||
@@ -14,7 +14,7 @@
|
||||
<autoReplace/>
|
||||
<titleFormat>
|
||||
<title>%title%</title>
|
||||
<chapter>Chapter %num%\\%title%</chapter>
|
||||
<chapter>Chapter %ch%: %title%</chapter>
|
||||
<unnumbered>%title%</unnumbered>
|
||||
<scene>* * *</scene>
|
||||
<section></section>
|
||||
@@ -76,7 +76,6 @@
|
||||
<type>FILE</type>
|
||||
<class>NOVEL</class>
|
||||
<status>New</status>
|
||||
<expanded>False</expanded>
|
||||
<exported>True</exported>
|
||||
<layout>SCENE</layout>
|
||||
<charCount>0</charCount>
|
||||
@@ -117,7 +116,6 @@
|
||||
<type>FILE</type>
|
||||
<class>NOVEL</class>
|
||||
<status>New</status>
|
||||
<expanded>False</expanded>
|
||||
<exported>True</exported>
|
||||
<layout>SCENE</layout>
|
||||
<charCount>0</charCount>
|
||||
@@ -130,7 +128,6 @@
|
||||
<type>FILE</type>
|
||||
<class>CHARACTER</class>
|
||||
<status>New</status>
|
||||
<expanded>False</expanded>
|
||||
<exported>True</exported>
|
||||
<layout>NOTE</layout>
|
||||
<charCount>0</charCount>
|
||||
|
||||
+8
-8
@@ -237,14 +237,14 @@ def testMainWindows(qtbot, nwTempGUI, nwRef, nwTemp):
|
||||
# Check the files
|
||||
refFile = path.join(nwTempGUI,"nwProject.nwx")
|
||||
assert cmpFiles(refFile, path.join(nwRef,"gui","1_nwProject.nwx"), [2])
|
||||
refFile = path.join(nwTempGUI,"data_0","e17daca5f3e1_main.nwd")
|
||||
assert cmpFiles(refFile, path.join(nwRef,"gui","1_e17daca5f3e1_main.nwd"))
|
||||
refFile = path.join(nwTempGUI,"data_9","8010bd9270f9_main.nwd")
|
||||
assert cmpFiles(refFile, path.join(nwRef,"gui","1_8010bd9270f9_main.nwd"))
|
||||
refFile = path.join(nwTempGUI,"data_3","1489056e0916_main.nwd")
|
||||
assert cmpFiles(refFile, path.join(nwRef,"gui","1_1489056e0916_main.nwd"))
|
||||
refFile = path.join(nwTempGUI,"data_1","a6562590ef19_main.nwd")
|
||||
assert cmpFiles(refFile, path.join(nwRef,"gui","1_a6562590ef19_main.nwd"))
|
||||
refFile = path.join(nwTempGUI,"content","0e17daca5f3e1.nwd")
|
||||
assert cmpFiles(refFile, path.join(nwRef,"gui","1_0e17daca5f3e1.nwd"))
|
||||
refFile = path.join(nwTempGUI,"content","98010bd9270f9.nwd")
|
||||
assert cmpFiles(refFile, path.join(nwRef,"gui","1_98010bd9270f9.nwd"))
|
||||
refFile = path.join(nwTempGUI,"content","31489056e0916.nwd")
|
||||
assert cmpFiles(refFile, path.join(nwRef,"gui","1_31489056e0916.nwd"))
|
||||
refFile = path.join(nwTempGUI,"content","1a6562590ef19.nwd")
|
||||
assert cmpFiles(refFile, path.join(nwRef,"gui","1_1a6562590ef19.nwd"))
|
||||
|
||||
nwGUI.closeMain()
|
||||
# qtbot.stopForInteraction()
|
||||
|
||||
@@ -155,7 +155,7 @@ def testIndexCheckThese(nwTempProj):
|
||||
"# Hello World!\n"
|
||||
"@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 str(theIndex.checkThese(["@tag", "Jane"], cItem)) == "[True, True]"
|
||||
@@ -195,7 +195,7 @@ def testIndexMeta(nwTempProj):
|
||||
"\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!"
|
||||
|
||||
# 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
|
||||
theRefs = theIndex.getBackReferenceList(cHandle)
|
||||
assert str(theRefs) == "{'41cfc0d1f2d12': 3}"
|
||||
assert str(theRefs) == "{'41cfc0d1f2d12': 'T000001'}"
|
||||
|
||||
assert theProject.closeProject()
|
||||
|
||||
Reference in New Issue
Block a user