Auto-update novel item meta (#1241)
This commit is contained in:
@@ -154,11 +154,11 @@ def isHandle(value):
|
|||||||
|
|
||||||
|
|
||||||
def isTitleTag(value):
|
def isTitleTag(value):
|
||||||
"""Check if a string is a valid title string.
|
"""Check if a string is a valid title tag string.
|
||||||
"""
|
"""
|
||||||
if not isinstance(value, str):
|
if not isinstance(value, str):
|
||||||
return False
|
return False
|
||||||
if len(value) != 7:
|
if len(value) != 5:
|
||||||
return False
|
return False
|
||||||
if not value.startswith("T"):
|
if not value.startswith("T"):
|
||||||
return False
|
return False
|
||||||
|
|||||||
@@ -61,7 +61,6 @@ class nwHeaders:
|
|||||||
|
|
||||||
H_VALID = ("H0", "H1", "H2", "H3", "H4")
|
H_VALID = ("H0", "H1", "H2", "H3", "H4")
|
||||||
H_LEVEL = {"H0": 0, "H1": 1, "H2": 2, "H3": 3, "H4": 4}
|
H_LEVEL = {"H0": 0, "H1": 1, "H2": 2, "H3": 3, "H4": 4}
|
||||||
TT_NONE = "T000000"
|
|
||||||
|
|
||||||
# END Class nwHeaders
|
# END Class nwHeaders
|
||||||
|
|
||||||
|
|||||||
+122
-112
@@ -41,6 +41,8 @@ from novelwriter.common import (
|
|||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
TT_NONE = "T0000"
|
||||||
|
|
||||||
|
|
||||||
class NWIndex:
|
class NWIndex:
|
||||||
"""This class holds the entire index for a given project. The index
|
"""This class holds the entire index for a given project. The index
|
||||||
@@ -281,50 +283,58 @@ class NWIndex:
|
|||||||
def _scanActive(self, tHandle, theItem, theText, itemTags):
|
def _scanActive(self, tHandle, theItem, theText, itemTags):
|
||||||
"""Scan an active document for meta data.
|
"""Scan an active document for meta data.
|
||||||
"""
|
"""
|
||||||
nTitle = 0
|
nTitle = 0 # Line Number of the previous title
|
||||||
findHeader = True
|
cTitle = TT_NONE # Tag of the current title
|
||||||
theLines = theText.splitlines()
|
pTitle = TT_NONE # Tag of the previous title
|
||||||
|
firstHeader = True # First header has been seen
|
||||||
|
|
||||||
|
theLines = theText.splitlines()
|
||||||
for nLine, aLine in enumerate(theLines, start=1):
|
for nLine, aLine in enumerate(theLines, start=1):
|
||||||
|
|
||||||
if len(aLine.strip()) == 0:
|
if aLine.strip() == "":
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if aLine.startswith("#"):
|
if aLine.startswith("#"):
|
||||||
if findHeader:
|
hDepth, hText = self._splitHeading(aLine)
|
||||||
hDepth, _ = self._splitHeading(aLine)
|
if hDepth == "H0":
|
||||||
if hDepth != "H0":
|
continue
|
||||||
theItem.setMainHeading(hDepth)
|
|
||||||
findHeader = False
|
|
||||||
|
|
||||||
isTitle = self._indexTitle(tHandle, aLine, nLine)
|
if firstHeader:
|
||||||
if isTitle and nLine > 0:
|
theItem.setMainHeading(hDepth)
|
||||||
|
firstHeader = False
|
||||||
|
|
||||||
|
cTitle = self._itemIndex.addItemHeading(tHandle, nLine, hDepth, hText)
|
||||||
|
if cTitle != TT_NONE:
|
||||||
if nTitle > 0:
|
if nTitle > 0:
|
||||||
|
# We have a new title, so we need to count the words of the previous one
|
||||||
lastText = "\n".join(theLines[nTitle-1:nLine-1])
|
lastText = "\n".join(theLines[nTitle-1:nLine-1])
|
||||||
self._indexWordCounts(tHandle, lastText, nTitle)
|
self._indexWordCounts(tHandle, lastText, pTitle)
|
||||||
nTitle = nLine
|
nTitle = nLine
|
||||||
|
pTitle = cTitle
|
||||||
|
|
||||||
elif aLine.startswith("@"):
|
elif aLine.startswith("@"):
|
||||||
self._indexKeyword(tHandle, aLine, nTitle, theItem.itemClass, itemTags)
|
if cTitle != TT_NONE:
|
||||||
|
self._indexKeyword(tHandle, aLine, cTitle, theItem.itemClass, itemTags)
|
||||||
|
|
||||||
elif aLine.startswith("%"):
|
elif aLine.startswith("%"):
|
||||||
if nTitle > 0:
|
if cTitle != TT_NONE:
|
||||||
toCheck = aLine[1:].lstrip()
|
toCheck = aLine[1:].lstrip()
|
||||||
synTag = toCheck[:9].lower()
|
synTag = toCheck[:9].lower()
|
||||||
tLen = len(aLine)
|
tLen = len(aLine)
|
||||||
cLen = len(toCheck)
|
cLen = len(toCheck)
|
||||||
cOff = tLen - cLen
|
cOff = tLen - cLen
|
||||||
if synTag == "synopsis:":
|
if synTag == "synopsis:":
|
||||||
self._indexSynopsis(tHandle, aLine[cOff+9:].strip(), nTitle)
|
sText = aLine[cOff+9:].strip()
|
||||||
|
self._itemIndex.setHeadingSynopsis(tHandle, cTitle, sText)
|
||||||
|
|
||||||
# Count words for remaining text after last heading
|
# Count words for remaining text after last heading
|
||||||
if nTitle > 0:
|
if pTitle != TT_NONE:
|
||||||
lastText = "\n".join(theLines[nTitle-1:])
|
lastText = "\n".join(theLines[nTitle-1:])
|
||||||
self._indexWordCounts(tHandle, lastText, nTitle)
|
self._indexWordCounts(tHandle, lastText, pTitle)
|
||||||
|
|
||||||
# Also count words on a page with no titles
|
# Also count words on a page with no titles
|
||||||
if nTitle == 0:
|
if cTitle == TT_NONE:
|
||||||
self._indexWordCounts(tHandle, theText, nTitle)
|
self._indexWordCounts(tHandle, theText, cTitle)
|
||||||
|
|
||||||
# Prune no longer used tags
|
# Prune no longer used tags
|
||||||
for tTag, isActive in itemTags.items():
|
for tTag, isActive in itemTags.items():
|
||||||
@@ -362,34 +372,14 @@ class NWIndex:
|
|||||||
return "H2", aLine[4:].strip()
|
return "H2", aLine[4:].strip()
|
||||||
return "H0", ""
|
return "H0", ""
|
||||||
|
|
||||||
def _indexTitle(self, tHandle, aLine, nTitle):
|
def _indexWordCounts(self, tHandle, theText, sTitle):
|
||||||
"""Save information about the title and its location in the
|
|
||||||
file to the index.
|
|
||||||
"""
|
|
||||||
hDepth, hText = self._splitHeading(aLine)
|
|
||||||
if hDepth == "H0":
|
|
||||||
return False
|
|
||||||
|
|
||||||
sTitle = f"T{nTitle:06d}"
|
|
||||||
self._itemIndex.addItemHeading(tHandle, sTitle, hDepth, hText)
|
|
||||||
return True
|
|
||||||
|
|
||||||
def _indexWordCounts(self, tHandle, theText, nTitle):
|
|
||||||
"""Count text stats and save the counts to the index.
|
"""Count text stats and save the counts to the index.
|
||||||
"""
|
"""
|
||||||
sTitle = f"T{nTitle:06d}"
|
|
||||||
cC, wC, pC = countWords(theText)
|
cC, wC, pC = countWords(theText)
|
||||||
self._itemIndex.setHeadingCounts(tHandle, sTitle, cC, wC, pC)
|
self._itemIndex.setHeadingCounts(tHandle, sTitle, cC, wC, pC)
|
||||||
return
|
return
|
||||||
|
|
||||||
def _indexSynopsis(self, tHandle, theText, nTitle):
|
def _indexKeyword(self, tHandle, aLine, sTitle, itemClass, itemTags):
|
||||||
"""Save the synopsis to the index.
|
|
||||||
"""
|
|
||||||
sTitle = f"T{nTitle:06d}"
|
|
||||||
self._itemIndex.setHeadingSynopsis(tHandle, sTitle, theText)
|
|
||||||
return
|
|
||||||
|
|
||||||
def _indexKeyword(self, tHandle, aLine, nTitle, itemClass, itemTags):
|
|
||||||
"""Validate and save the information about a reference to a tag
|
"""Validate and save the information about a reference to a tag
|
||||||
in another file, or the setting of a tag in the file. A record
|
in another file, or the setting of a tag in the file. A record
|
||||||
of active tags is updated so that no longer used tags can be
|
of active tags is updated so that no longer used tags can be
|
||||||
@@ -404,7 +394,6 @@ class NWIndex:
|
|||||||
logger.warning("Skipping invalid keyword '%s' in '%s'", theBits[0], tHandle)
|
logger.warning("Skipping invalid keyword '%s' in '%s'", theBits[0], tHandle)
|
||||||
return
|
return
|
||||||
|
|
||||||
sTitle = f"T{nTitle:06d}"
|
|
||||||
if theBits[0] == nwKeyWords.TAG_KEY:
|
if theBits[0] == nwKeyWords.TAG_KEY:
|
||||||
tagName = theBits[1]
|
tagName = theBits[1]
|
||||||
self._tagsIndex.add(tagName, tHandle, sTitle, itemClass)
|
self._tagsIndex.add(tagName, tHandle, sTitle, itemClass)
|
||||||
@@ -491,6 +480,19 @@ class NWIndex:
|
|||||||
# Extract Data
|
# Extract Data
|
||||||
##
|
##
|
||||||
|
|
||||||
|
def getItemData(self, tHandle):
|
||||||
|
"""Get the index data for a given item.
|
||||||
|
"""
|
||||||
|
return self._itemIndex[tHandle]
|
||||||
|
|
||||||
|
def getItemHeader(self, tHandle, sTitle):
|
||||||
|
"""Get the header entry for a specific item and heading.
|
||||||
|
"""
|
||||||
|
tItem = self._itemIndex[tHandle]
|
||||||
|
if isinstance(tItem, IndexItem):
|
||||||
|
return tItem[sTitle]
|
||||||
|
return None
|
||||||
|
|
||||||
def novelStructure(self, rootHandle=None, skipExcl=True):
|
def novelStructure(self, rootHandle=None, skipExcl=True):
|
||||||
"""Iterate over all titles in the novel, in the correct order as
|
"""Iterate over all titles in the novel, in the correct order as
|
||||||
they appear in the tree view and in the respective document
|
they appear in the tree view and in the respective document
|
||||||
@@ -518,21 +520,13 @@ class NWIndex:
|
|||||||
hCount[iLevel] += 1
|
hCount[iLevel] += 1
|
||||||
return hCount
|
return hCount
|
||||||
|
|
||||||
def getHandleWordCounts(self, tHandle):
|
def getHandleHeaderCount(self, tHandle):
|
||||||
"""Get all header word counts for a specific handle.
|
"""Get the number of headers in an item.
|
||||||
"""
|
"""
|
||||||
return [
|
tItem = self._itemIndex[tHandle]
|
||||||
(f"{tHandle}:{sTitle}", hItem.wordCount)
|
if isinstance(tItem, IndexItem):
|
||||||
for sTitle, hItem in self._itemIndex.iterItemHeaders(tHandle)
|
return len(tItem)
|
||||||
]
|
return 0
|
||||||
|
|
||||||
def getHandleHeaders(self, tHandle):
|
|
||||||
"""Get all headers for a specific handle.
|
|
||||||
"""
|
|
||||||
return [
|
|
||||||
(sTitle, hItem.level, hItem.title)
|
|
||||||
for sTitle, hItem in self._itemIndex.iterItemHeaders(tHandle)
|
|
||||||
]
|
|
||||||
|
|
||||||
def getTableOfContents(self, rootHandle, maxDepth, skipExcl=True):
|
def getTableOfContents(self, rootHandle, maxDepth, skipExcl=True):
|
||||||
"""Generate a table of contents up to a maximum depth.
|
"""Generate a table of contents up to a maximum depth.
|
||||||
@@ -598,13 +592,6 @@ class NWIndex:
|
|||||||
|
|
||||||
return theRefs
|
return theRefs
|
||||||
|
|
||||||
def getNovelData(self, tHandle, sTitle):
|
|
||||||
"""Return the novel data of a given handle and title.
|
|
||||||
"""
|
|
||||||
if tHandle in self._itemIndex:
|
|
||||||
return self._itemIndex[tHandle][sTitle]
|
|
||||||
return None
|
|
||||||
|
|
||||||
def getBackReferenceList(self, tHandle):
|
def getBackReferenceList(self, tHandle):
|
||||||
"""Build a list of files referring back to our file, specified
|
"""Build a list of files referring back to our file, specified
|
||||||
by tHandle.
|
by tHandle.
|
||||||
@@ -644,10 +631,22 @@ class TagsIndex:
|
|||||||
control of the keys.
|
control of the keys.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
__slots__ = ("_tags")
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self._tags = {}
|
self._tags = {}
|
||||||
return
|
return
|
||||||
|
|
||||||
|
def __contains__(self, tagKey):
|
||||||
|
return tagKey in self._tags
|
||||||
|
|
||||||
|
def __delitem__(self, tagKey):
|
||||||
|
self._tags.pop(tagKey, None)
|
||||||
|
return
|
||||||
|
|
||||||
|
def __getitem__(self, tagKey):
|
||||||
|
return self._tags.get(tagKey, None)
|
||||||
|
|
||||||
##
|
##
|
||||||
# Methods
|
# Methods
|
||||||
##
|
##
|
||||||
@@ -658,22 +657,6 @@ class TagsIndex:
|
|||||||
self._tags = {}
|
self._tags = {}
|
||||||
return
|
return
|
||||||
|
|
||||||
def __contains__(self, tagKey):
|
|
||||||
"""Check if a tag exists in the index,
|
|
||||||
"""
|
|
||||||
return tagKey in self._tags
|
|
||||||
|
|
||||||
def __delitem__(self, tagKey):
|
|
||||||
"""Delete an entry in the index.
|
|
||||||
"""
|
|
||||||
self._tags.pop(tagKey, None)
|
|
||||||
return
|
|
||||||
|
|
||||||
def __getitem__(self, tagKey):
|
|
||||||
"""Return a tag, or return None if it isn't found.
|
|
||||||
"""
|
|
||||||
return self._tags.get(tagKey, None)
|
|
||||||
|
|
||||||
def add(self, tagKey, tHandle, sTitle, itemClass):
|
def add(self, tagKey, tHandle, sTitle, itemClass):
|
||||||
"""Add a key to the index and set all values.
|
"""Add a key to the index and set all values.
|
||||||
"""
|
"""
|
||||||
@@ -690,7 +673,7 @@ class TagsIndex:
|
|||||||
def tagHeading(self, tagKey):
|
def tagHeading(self, tagKey):
|
||||||
"""Get the heading of a given tag.
|
"""Get the heading of a given tag.
|
||||||
"""
|
"""
|
||||||
return self._tags.get(tagKey, {}).get("heading", nwHeaders.TT_NONE)
|
return self._tags.get(tagKey, {}).get("heading", TT_NONE)
|
||||||
|
|
||||||
def tagClass(self, tagKey):
|
def tagClass(self, tagKey):
|
||||||
"""Get the class of a given tag.
|
"""Get the class of a given tag.
|
||||||
@@ -749,11 +732,23 @@ class ItemIndex:
|
|||||||
IndexHeading object for each header of the text.
|
IndexHeading object for each header of the text.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
__slots__ = ("_project", "_items")
|
||||||
|
|
||||||
def __init__(self, project):
|
def __init__(self, project):
|
||||||
self._project = project
|
self._project = project
|
||||||
self._items = {}
|
self._items = {}
|
||||||
return
|
return
|
||||||
|
|
||||||
|
def __contains__(self, tHandle):
|
||||||
|
return tHandle in self._items
|
||||||
|
|
||||||
|
def __delitem__(self, tHandle):
|
||||||
|
self._items.pop(tHandle, None)
|
||||||
|
return
|
||||||
|
|
||||||
|
def __getitem__(self, tHandle):
|
||||||
|
return self._items.get(tHandle, None)
|
||||||
|
|
||||||
##
|
##
|
||||||
# Methods
|
# Methods
|
||||||
##
|
##
|
||||||
@@ -764,22 +759,6 @@ class ItemIndex:
|
|||||||
self._items = {}
|
self._items = {}
|
||||||
return
|
return
|
||||||
|
|
||||||
def __contains__(self, tHandle):
|
|
||||||
"""Check if an item exists in the index,
|
|
||||||
"""
|
|
||||||
return tHandle in self._items
|
|
||||||
|
|
||||||
def __delitem__(self, tHandle):
|
|
||||||
"""Delete an entry in the index.
|
|
||||||
"""
|
|
||||||
self._items.pop(tHandle, None)
|
|
||||||
return
|
|
||||||
|
|
||||||
def __getitem__(self, tHandle):
|
|
||||||
"""Return an item, or return None if it isn't found.
|
|
||||||
"""
|
|
||||||
return self._items.get(tHandle, None)
|
|
||||||
|
|
||||||
def add(self, tHandle, tItem):
|
def add(self, tHandle, tItem):
|
||||||
"""Add a new item to the index. This will overwrite the item if
|
"""Add a new item to the index. This will overwrite the item if
|
||||||
it already exists.
|
it already exists.
|
||||||
@@ -839,13 +818,15 @@ class ItemIndex:
|
|||||||
# Setters
|
# Setters
|
||||||
##
|
##
|
||||||
|
|
||||||
def addItemHeading(self, tHandle, sTitle, hDepth, hText):
|
def addItemHeading(self, tHandle, lineNo, hDepth, hText):
|
||||||
"""Set the main heading level of an item.
|
"""Add a heading to an item.
|
||||||
"""
|
"""
|
||||||
if tHandle in self._items:
|
if tHandle in self._items:
|
||||||
tItem = self._items[tHandle]
|
tItem = self._items[tHandle]
|
||||||
tItem.addHeading(IndexHeading(sTitle, hDepth, hText))
|
sTitle = tItem.nextHeading()
|
||||||
return
|
tItem.addHeading(IndexHeading(sTitle, lineNo, hDepth, hText))
|
||||||
|
return sTitle
|
||||||
|
return TT_NONE
|
||||||
|
|
||||||
def setHeadingCounts(self, tHandle, sTitle, cC, wC, pC):
|
def setHeadingCounts(self, tHandle, sTitle, cC, wC, pC):
|
||||||
"""Set the character, word and paragraph counts of a heading
|
"""Set the character, word and paragraph counts of a heading
|
||||||
@@ -916,20 +897,31 @@ class IndexItem:
|
|||||||
must be reset each time the item is re-indexed.
|
must be reset each time the item is re-indexed.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
__slots__ = ("_handle", "_item", "_headings", "_headings", "_count")
|
||||||
|
|
||||||
def __init__(self, tHandle, tItem):
|
def __init__(self, tHandle, tItem):
|
||||||
self._handle = tHandle
|
self._handle = tHandle
|
||||||
self._item = tItem
|
self._item = tItem
|
||||||
self._headings = {}
|
self._headings = {}
|
||||||
self._index = 0
|
self._count = 0
|
||||||
|
|
||||||
# Add a placeholder heading
|
# Add a placeholder heading
|
||||||
self._headings[nwHeaders.TT_NONE] = IndexHeading(nwHeaders.TT_NONE)
|
self._headings[TT_NONE] = IndexHeading(TT_NONE)
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return f"<IndexItem handle='{self._handle}'>"
|
return f"<IndexItem handle='{self._handle}'>"
|
||||||
|
|
||||||
|
def __len__(self):
|
||||||
|
return len(self._headings)
|
||||||
|
|
||||||
|
def __getitem__(self, sTitle):
|
||||||
|
return self._headings.get(sTitle, None)
|
||||||
|
|
||||||
|
def __contains__(self, sTitle):
|
||||||
|
return sTitle in self._headings
|
||||||
|
|
||||||
##
|
##
|
||||||
# Properties
|
# Properties
|
||||||
##
|
##
|
||||||
@@ -946,8 +938,8 @@ class IndexItem:
|
|||||||
"""Add a heading to the item. Also remove the placeholder entry
|
"""Add a heading to the item. Also remove the placeholder entry
|
||||||
if it exists.
|
if it exists.
|
||||||
"""
|
"""
|
||||||
if nwHeaders.TT_NONE in self._headings:
|
if TT_NONE in self._headings:
|
||||||
self._headings.pop(nwHeaders.TT_NONE)
|
self._headings.pop(TT_NONE)
|
||||||
self._headings[tHeading.key] = tHeading
|
self._headings[tHeading.key] = tHeading
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -984,12 +976,6 @@ class IndexItem:
|
|||||||
# Data Methods
|
# Data Methods
|
||||||
##
|
##
|
||||||
|
|
||||||
def __getitem__(self, sTitle):
|
|
||||||
return self._headings.get(sTitle, None)
|
|
||||||
|
|
||||||
def __contains__(self, sTitle):
|
|
||||||
return sTitle in self._headings
|
|
||||||
|
|
||||||
def items(self):
|
def items(self):
|
||||||
return self._headings.items()
|
return self._headings.items()
|
||||||
|
|
||||||
@@ -1006,6 +992,12 @@ class IndexItem:
|
|||||||
tags.append(tag)
|
tags.append(tag)
|
||||||
return tags
|
return tags
|
||||||
|
|
||||||
|
def nextHeading(self):
|
||||||
|
"""Return the next heading key to be used.
|
||||||
|
"""
|
||||||
|
self._count += 1
|
||||||
|
return f"T{self._count:04d}"
|
||||||
|
|
||||||
##
|
##
|
||||||
# Pack/Unpack
|
# Pack/Unpack
|
||||||
##
|
##
|
||||||
@@ -1051,8 +1043,14 @@ class IndexHeading:
|
|||||||
of all references made under each heading.
|
of all references made under each heading.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, key, level="H0", title=""):
|
__slots__ = (
|
||||||
|
"_key", "_line", "_level", "_title", "_charCount", "_wordCount",
|
||||||
|
"_paraCount", "_synopsis", "_tag", "_refs",
|
||||||
|
)
|
||||||
|
|
||||||
|
def __init__(self, key, line=0, level="H0", title=""):
|
||||||
self._key = key
|
self._key = key
|
||||||
|
self._line = line
|
||||||
self._level = level
|
self._level = level
|
||||||
self._title = title
|
self._title = title
|
||||||
|
|
||||||
@@ -1077,6 +1075,10 @@ class IndexHeading:
|
|||||||
def key(self):
|
def key(self):
|
||||||
return self._key
|
return self._key
|
||||||
|
|
||||||
|
@property
|
||||||
|
def line(self):
|
||||||
|
return self._line
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def level(self):
|
def level(self):
|
||||||
return self._level
|
return self._level
|
||||||
@@ -1120,6 +1122,12 @@ class IndexHeading:
|
|||||||
self._level = level
|
self._level = level
|
||||||
return
|
return
|
||||||
|
|
||||||
|
def setLine(self, line):
|
||||||
|
"""Set the line number of a heading.
|
||||||
|
"""
|
||||||
|
self._line = max(0, checkInt(line, 0))
|
||||||
|
return
|
||||||
|
|
||||||
def setCounts(self, charCount, wordCount, paraCount):
|
def setCounts(self, charCount, wordCount, paraCount):
|
||||||
"""Set the character, word and paragraph count. Make sure the
|
"""Set the character, word and paragraph count. Make sure the
|
||||||
value is an integer and is not smaller than 0.
|
value is an integer and is not smaller than 0.
|
||||||
@@ -1161,6 +1169,7 @@ class IndexHeading:
|
|||||||
return {
|
return {
|
||||||
"level": self._level,
|
"level": self._level,
|
||||||
"title": self._title,
|
"title": self._title,
|
||||||
|
"line": self._line,
|
||||||
"tag": self._tag,
|
"tag": self._tag,
|
||||||
"cCount": self._charCount,
|
"cCount": self._charCount,
|
||||||
"wCount": self._wordCount,
|
"wCount": self._wordCount,
|
||||||
@@ -1182,6 +1191,7 @@ class IndexHeading:
|
|||||||
self.setLevel(data.get("level", "H0"))
|
self.setLevel(data.get("level", "H0"))
|
||||||
self._title = str(data.get("title", ""))
|
self._title = str(data.get("title", ""))
|
||||||
self._tag = str(data.get("tag", ""))
|
self._tag = str(data.get("tag", ""))
|
||||||
|
self.setLine(data.get("line", 0))
|
||||||
self.setCounts(
|
self.setCounts(
|
||||||
data.get("cCount", 0),
|
data.get("cCount", 0),
|
||||||
data.get("wCount", 0),
|
data.get("wCount", 0),
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ from PyQt5.QtWidgets import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
from novelwriter.core import NWSpellEnchant, countWords
|
from novelwriter.core import NWSpellEnchant, countWords
|
||||||
from novelwriter.enum import nwAlert, nwDocAction, nwDocInsert, nwDocMode
|
from novelwriter.enum import nwAlert, nwDocAction, nwDocInsert, nwDocMode, nwItemClass
|
||||||
from novelwriter.common import transferCase
|
from novelwriter.common import transferCase
|
||||||
from novelwriter.constants import nwConst, nwFiles, nwKeyWords, nwUnicode
|
from novelwriter.constants import nwConst, nwFiles, nwKeyWords, nwUnicode
|
||||||
from novelwriter.gui.dochighlight import GuiDocHighlighter
|
from novelwriter.gui.dochighlight import GuiDocHighlighter
|
||||||
@@ -71,6 +71,8 @@ class GuiDocEditor(QTextEdit):
|
|||||||
docEditedStatusChanged = pyqtSignal(bool)
|
docEditedStatusChanged = pyqtSignal(bool)
|
||||||
docCountsChanged = pyqtSignal(str, int, int, int)
|
docCountsChanged = pyqtSignal(str, int, int, int)
|
||||||
loadDocumentTagRequest = pyqtSignal(str, Enum)
|
loadDocumentTagRequest = pyqtSignal(str, Enum)
|
||||||
|
novelStructureChanged = pyqtSignal()
|
||||||
|
novelItemMetaChanged = pyqtSignal(str)
|
||||||
|
|
||||||
def __init__(self, mainGui):
|
def __init__(self, mainGui):
|
||||||
super().__init__(parent=mainGui)
|
super().__init__(parent=mainGui)
|
||||||
@@ -88,7 +90,6 @@ class GuiDocEditor(QTextEdit):
|
|||||||
|
|
||||||
self._docChanged = False # Flag for changed status of document
|
self._docChanged = False # Flag for changed status of document
|
||||||
self._docHandle = None # The handle of the open file
|
self._docHandle = None # The handle of the open file
|
||||||
self._docHeaders = [] # Record of headers in the file
|
|
||||||
|
|
||||||
self._spellCheck = False # Flag for spell checking enabled
|
self._spellCheck = False # Flag for spell checking enabled
|
||||||
self._nonWord = "\"'" # Characters to not include in spell checking
|
self._nonWord = "\"'" # Characters to not include in spell checking
|
||||||
@@ -415,8 +416,8 @@ class GuiDocEditor(QTextEdit):
|
|||||||
self._queuePos = self._nwItem.cursorPos
|
self._queuePos = self._nwItem.cursorPos
|
||||||
else:
|
else:
|
||||||
self.setCursorPosition(self._nwItem.cursorPos)
|
self.setCursorPosition(self._nwItem.cursorPos)
|
||||||
else:
|
elif isinstance(tLine, int):
|
||||||
self.setCursorLine(tLine)
|
self.setCursorLine(tLine - 1)
|
||||||
|
|
||||||
if self.mainConf.scrollPastEnd > 0:
|
if self.mainConf.scrollPastEnd > 0:
|
||||||
fSize = QFontMetrics(self.font()).lineSpacing()
|
fSize = QFontMetrics(self.font()).lineSpacing()
|
||||||
@@ -425,7 +426,6 @@ class GuiDocEditor(QTextEdit):
|
|||||||
self.document().rootFrame().setFrameFormat(docFrame)
|
self.document().rootFrame().setFrameFormat(docFrame)
|
||||||
|
|
||||||
self.docFooter.updateLineCount()
|
self.docFooter.updateLineCount()
|
||||||
self._docHeaders = self.theProject.index.getHandleHeaders(self._docHandle)
|
|
||||||
|
|
||||||
qApp.processEvents()
|
qApp.processEvents()
|
||||||
self.document().clearUndoRedoStacks()
|
self.document().clearUndoRedoStacks()
|
||||||
@@ -531,14 +531,16 @@ class GuiDocEditor(QTextEdit):
|
|||||||
self.setDocumentChanged(False)
|
self.setDocumentChanged(False)
|
||||||
|
|
||||||
oldHeader = self._nwItem.mainHeading
|
oldHeader = self._nwItem.mainHeading
|
||||||
|
oldCount = self.theProject.index.getHandleHeaderCount(tHandle)
|
||||||
self.theProject.index.scanText(tHandle, docText)
|
self.theProject.index.scanText(tHandle, docText)
|
||||||
newHeader = self._nwItem.mainHeading
|
newHeader = self._nwItem.mainHeading
|
||||||
|
newCount = self.theProject.index.getHandleHeaderCount(tHandle)
|
||||||
|
|
||||||
# ToDo: This should be a signal
|
if self._nwItem.itemClass == nwItemClass.NOVEL:
|
||||||
if self._updateHeaders():
|
if oldCount == newCount:
|
||||||
self.mainGui.requestNovelTreeRefresh()
|
self.novelItemMetaChanged.emit(tHandle)
|
||||||
else:
|
else:
|
||||||
self.mainGui.novelView.updateWordCounts(tHandle)
|
self.novelStructureChanged.emit()
|
||||||
|
|
||||||
# ToDo: This should be a signal
|
# ToDo: This should be a signal
|
||||||
if oldHeader != newHeader:
|
if oldHeader != newHeader:
|
||||||
@@ -2065,21 +2067,6 @@ class GuiDocEditor(QTextEdit):
|
|||||||
return False
|
return False
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def _updateHeaders(self):
|
|
||||||
"""Update the headers record and return True if anything
|
|
||||||
changed, if a check flag was provided.
|
|
||||||
"""
|
|
||||||
if self._docHandle is None:
|
|
||||||
return False
|
|
||||||
|
|
||||||
newHeaders = self.theProject.index.getHandleHeaders(self._docHandle)
|
|
||||||
newLev = [x[1] for x in newHeaders]
|
|
||||||
oldLev = [x[1] for x in self._docHeaders]
|
|
||||||
|
|
||||||
self._docHeaders = newHeaders
|
|
||||||
|
|
||||||
return newLev != oldLev
|
|
||||||
|
|
||||||
def _checkDocSize(self, theSize):
|
def _checkDocSize(self, theSize):
|
||||||
"""Check if document size crosses the big document limit set in
|
"""Check if document size crosses the big document limit set in
|
||||||
config. If so, we will set the big document flag to True.
|
config. If so, we will set the big document flag to True.
|
||||||
|
|||||||
@@ -40,7 +40,6 @@ from PyQt5.QtWidgets import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
from novelwriter.enum import nwDocMode, nwItemClass, nwOutline
|
from novelwriter.enum import nwDocMode, nwItemClass, nwOutline
|
||||||
from novelwriter.common import checkInt
|
|
||||||
from novelwriter.constants import nwHeaders, nwKeyWords, nwLabels, trConst
|
from novelwriter.constants import nwHeaders, nwKeyWords, nwLabels, trConst
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
@@ -60,7 +59,7 @@ class GuiNovelView(QWidget):
|
|||||||
|
|
||||||
# Signals for user interaction with the novel tree
|
# Signals for user interaction with the novel tree
|
||||||
selectedItemChanged = pyqtSignal(str)
|
selectedItemChanged = pyqtSignal(str)
|
||||||
openDocumentRequest = pyqtSignal(str, Enum, int, str)
|
openDocumentRequest = pyqtSignal(str, Enum, str)
|
||||||
|
|
||||||
def __init__(self, mainGui):
|
def __init__(self, mainGui):
|
||||||
super().__init__(parent=mainGui)
|
super().__init__(parent=mainGui)
|
||||||
@@ -83,7 +82,6 @@ class GuiNovelView(QWidget):
|
|||||||
self.setLayout(self.outerBox)
|
self.setLayout(self.outerBox)
|
||||||
|
|
||||||
# Function Mappings
|
# Function Mappings
|
||||||
self.updateWordCounts = self.novelTree.updateWordCounts
|
|
||||||
self.getSelectedHandle = self.novelTree.getSelectedHandle
|
self.getSelectedHandle = self.novelTree.getSelectedHandle
|
||||||
self.setActiveHandle = self.novelTree.setActiveHandle
|
self.setActiveHandle = self.novelTree.setActiveHandle
|
||||||
|
|
||||||
@@ -107,12 +105,6 @@ class GuiNovelView(QWidget):
|
|||||||
self.novelTree.initSettings()
|
self.novelTree.initSettings()
|
||||||
return
|
return
|
||||||
|
|
||||||
def refreshTree(self):
|
|
||||||
"""Refresh the current tree.
|
|
||||||
"""
|
|
||||||
self.novelTree.refreshTree(rootHandle=self.theProject.data.getLastHandle("novelTree"))
|
|
||||||
return
|
|
||||||
|
|
||||||
def clearProject(self):
|
def clearProject(self):
|
||||||
"""Clear project-related GUI content.
|
"""Clear project-related GUI content.
|
||||||
"""
|
"""
|
||||||
@@ -164,6 +156,13 @@ class GuiNovelView(QWidget):
|
|||||||
# Public Slots
|
# Public Slots
|
||||||
##
|
##
|
||||||
|
|
||||||
|
@pyqtSlot()
|
||||||
|
def refreshTree(self):
|
||||||
|
"""Refresh the current tree.
|
||||||
|
"""
|
||||||
|
self.novelTree.refreshTree(rootHandle=self.theProject.data.getLastHandle("novelTree"))
|
||||||
|
return
|
||||||
|
|
||||||
@pyqtSlot(str)
|
@pyqtSlot(str)
|
||||||
def updateRootItem(self, tHandle):
|
def updateRootItem(self, tHandle):
|
||||||
"""If any root item changes, rebuild the novel root menu.
|
"""If any root item changes, rebuild the novel root menu.
|
||||||
@@ -171,6 +170,14 @@ class GuiNovelView(QWidget):
|
|||||||
self.novelBar.buildNovelRootMenu()
|
self.novelBar.buildNovelRootMenu()
|
||||||
return
|
return
|
||||||
|
|
||||||
|
@pyqtSlot(str)
|
||||||
|
def updateNovelItemMeta(self, tHandle):
|
||||||
|
"""The meta data of a novel item has changed, and the tree item
|
||||||
|
needs to be refreshed.
|
||||||
|
"""
|
||||||
|
self.novelTree.refreshHandle(tHandle)
|
||||||
|
return
|
||||||
|
|
||||||
# END Class GuiNovelView
|
# END Class GuiNovelView
|
||||||
|
|
||||||
|
|
||||||
@@ -470,7 +477,7 @@ class GuiNovelTree(QTreeWidget):
|
|||||||
return
|
return
|
||||||
|
|
||||||
def refreshTree(self, rootHandle=None, overRide=False):
|
def refreshTree(self, rootHandle=None, overRide=False):
|
||||||
"""Called whenever the Novel tab is activated.
|
"""Refresh the tree if it has been changed.
|
||||||
"""
|
"""
|
||||||
logger.debug("Requesting refresh of the novel tree")
|
logger.debug("Requesting refresh of the novel tree")
|
||||||
if rootHandle is None:
|
if rootHandle is None:
|
||||||
@@ -495,13 +502,24 @@ class GuiNovelTree(QTreeWidget):
|
|||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
def updateWordCounts(self, tHandle):
|
def refreshHandle(self, tHandle):
|
||||||
"""Update the word count for a given handle.
|
"""Refresh the data for a given handle.
|
||||||
"""
|
"""
|
||||||
tHeaders = self.theProject.index.getHandleWordCounts(tHandle)
|
idxData = self.theProject.index.getItemData(tHandle)
|
||||||
for titleKey, wCount in tHeaders:
|
if idxData is None:
|
||||||
if titleKey in self._treeMap:
|
return
|
||||||
self._treeMap[titleKey].setText(self.C_WORDS, f"{wCount:n}")
|
|
||||||
|
logger.debug("Refreshing meta data for item '%s'", tHandle)
|
||||||
|
for sTitle, tHeading in idxData.items():
|
||||||
|
sKey = f"{tHandle}:{sTitle}"
|
||||||
|
trItem = self._treeMap.get(sKey, None)
|
||||||
|
if trItem is None:
|
||||||
|
logger.debug("Heading '%s' not in novel tree", sKey)
|
||||||
|
self.refreshTree()
|
||||||
|
return
|
||||||
|
|
||||||
|
self._updateTreeItemValues(trItem, tHeading, tHandle, sTitle)
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
def getSelectedHandle(self):
|
def getSelectedHandle(self):
|
||||||
@@ -509,14 +527,11 @@ class GuiNovelTree(QTreeWidget):
|
|||||||
selected, return the first.
|
selected, return the first.
|
||||||
"""
|
"""
|
||||||
selItem = self.selectedItems()
|
selItem = self.selectedItems()
|
||||||
tHandle = None
|
|
||||||
tLine = 0
|
|
||||||
if selItem:
|
if selItem:
|
||||||
tHandle = selItem[0].data(self.C_TITLE, self.D_HANDLE)
|
tHandle = selItem[0].data(self.C_TITLE, self.D_HANDLE)
|
||||||
sTitle = selItem[0].data(self.C_TITLE, self.D_TITLE)
|
sTitle = selItem[0].data(self.C_TITLE, self.D_TITLE)
|
||||||
tLine = checkInt(sTitle[1:], 1) - 1
|
return tHandle, sTitle
|
||||||
|
return None, None
|
||||||
return tHandle, tLine
|
|
||||||
|
|
||||||
def setLastColType(self, colType, doRefresh=True):
|
def setLastColType(self, colType, doRefresh=True):
|
||||||
"""Change the content type of the last column and rebuild.
|
"""Change the content type of the last column and rebuild.
|
||||||
@@ -575,11 +590,11 @@ class GuiNovelTree(QTreeWidget):
|
|||||||
if not isinstance(selItem, QTreeWidgetItem):
|
if not isinstance(selItem, QTreeWidgetItem):
|
||||||
return
|
return
|
||||||
|
|
||||||
tHandle, _ = self.getSelectedHandle()
|
tHandle, sTitle = self.getSelectedHandle()
|
||||||
if tHandle is None:
|
if tHandle is None:
|
||||||
return
|
return
|
||||||
|
|
||||||
self.novelView.openDocumentRequest.emit(tHandle, nwDocMode.VIEW, -1, "")
|
self.novelView.openDocumentRequest.emit(tHandle, nwDocMode.VIEW, sTitle or "")
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -621,8 +636,8 @@ class GuiNovelTree(QTreeWidget):
|
|||||||
clicked, and send it to the main gui class for opening in the
|
clicked, and send it to the main gui class for opening in the
|
||||||
document editor.
|
document editor.
|
||||||
"""
|
"""
|
||||||
tHandle, tLine = self.getSelectedHandle()
|
tHandle, sTitle = self.getSelectedHandle()
|
||||||
self.novelView.openDocumentRequest.emit(tHandle, nwDocMode.EDIT, tLine, "")
|
self.novelView.openDocumentRequest.emit(tHandle, nwDocMode.EDIT, sTitle or "")
|
||||||
return
|
return
|
||||||
|
|
||||||
##
|
##
|
||||||
@@ -638,30 +653,16 @@ class GuiNovelTree(QTreeWidget):
|
|||||||
|
|
||||||
novStruct = self.theProject.index.novelStructure(rootHandle=rootHandle, skipExcl=True)
|
novStruct = self.theProject.index.novelStructure(rootHandle=rootHandle, skipExcl=True)
|
||||||
for tKey, tHandle, sTitle, novIdx in novStruct:
|
for tKey, tHandle, sTitle, novIdx in novStruct:
|
||||||
|
if novIdx.level == "H0":
|
||||||
iLevel = nwHeaders.H_LEVEL.get(novIdx.level, 0)
|
|
||||||
if iLevel == 0:
|
|
||||||
continue
|
continue
|
||||||
|
|
||||||
hDec = self.mainTheme.getHeaderDecoration(iLevel)
|
|
||||||
|
|
||||||
newItem = QTreeWidgetItem()
|
newItem = QTreeWidgetItem()
|
||||||
newItem.setData(self.C_TITLE, Qt.DecorationRole, hDec)
|
|
||||||
newItem.setText(self.C_TITLE, novIdx.title)
|
|
||||||
newItem.setData(self.C_TITLE, self.D_HANDLE, tHandle)
|
newItem.setData(self.C_TITLE, self.D_HANDLE, tHandle)
|
||||||
newItem.setData(self.C_TITLE, self.D_TITLE, sTitle)
|
newItem.setData(self.C_TITLE, self.D_TITLE, sTitle)
|
||||||
newItem.setData(self.C_TITLE, self.D_KEY, tKey)
|
newItem.setData(self.C_TITLE, self.D_KEY, tKey)
|
||||||
newItem.setFont(self.C_TITLE, self._hFonts[iLevel])
|
|
||||||
newItem.setText(self.C_WORDS, f"{novIdx.wordCount:n}")
|
|
||||||
newItem.setTextAlignment(self.C_WORDS, Qt.AlignRight)
|
newItem.setTextAlignment(self.C_WORDS, Qt.AlignRight)
|
||||||
newItem.setData(self.C_MORE, Qt.DecorationRole, self._pMore)
|
|
||||||
|
|
||||||
# Custom column
|
|
||||||
lastText, toolTip = self._getLastColumnText(tHandle, sTitle)
|
|
||||||
newItem.setText(self.C_EXTRA, lastText)
|
|
||||||
if lastText:
|
|
||||||
newItem.setToolTip(self.C_EXTRA, toolTip)
|
|
||||||
|
|
||||||
|
self._updateTreeItemValues(newItem, novIdx, tHandle, sTitle)
|
||||||
self._treeMap[tKey] = newItem
|
self._treeMap[tKey] = newItem
|
||||||
self.addTopLevelItem(newItem)
|
self.addTopLevelItem(newItem)
|
||||||
|
|
||||||
@@ -672,6 +673,26 @@ class GuiNovelTree(QTreeWidget):
|
|||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
|
def _updateTreeItemValues(self, trItem, idxItem, tHandle, sTitle):
|
||||||
|
"""Set the tree item values from the index entry.
|
||||||
|
"""
|
||||||
|
iLevel = nwHeaders.H_LEVEL.get(idxItem.level, 0)
|
||||||
|
hDec = self.mainTheme.getHeaderDecoration(iLevel)
|
||||||
|
|
||||||
|
trItem.setData(self.C_TITLE, Qt.DecorationRole, hDec)
|
||||||
|
trItem.setText(self.C_TITLE, idxItem.title)
|
||||||
|
trItem.setFont(self.C_TITLE, self._hFonts[iLevel])
|
||||||
|
trItem.setText(self.C_WORDS, f"{idxItem.wordCount:n}")
|
||||||
|
trItem.setData(self.C_MORE, Qt.DecorationRole, self._pMore)
|
||||||
|
|
||||||
|
# Custom column
|
||||||
|
lastText, toolTip = self._getLastColumnText(tHandle, sTitle)
|
||||||
|
trItem.setText(self.C_EXTRA, lastText)
|
||||||
|
if lastText:
|
||||||
|
trItem.setToolTip(self.C_EXTRA, toolTip)
|
||||||
|
|
||||||
|
return
|
||||||
|
|
||||||
def _getLastColumnText(self, tHandle, sTitle):
|
def _getLastColumnText(self, tHandle, sTitle):
|
||||||
"""Generate the text for the last column based on user settings.
|
"""Generate the text for the last column based on user settings.
|
||||||
"""
|
"""
|
||||||
@@ -699,7 +720,7 @@ class GuiNovelTree(QTreeWidget):
|
|||||||
logger.debug("Generating meta data tooltip for '%s:%s'", tHandle, sTitle)
|
logger.debug("Generating meta data tooltip for '%s:%s'", tHandle, sTitle)
|
||||||
|
|
||||||
pIndex = self.theProject.index
|
pIndex = self.theProject.index
|
||||||
novIdx = pIndex.getNovelData(tHandle, sTitle)
|
novIdx = pIndex.getItemHeader(tHandle, sTitle)
|
||||||
refTags = pIndex.getReferences(tHandle, sTitle)
|
refTags = pIndex.getReferences(tHandle, sTitle)
|
||||||
|
|
||||||
synopText = novIdx.synopsis
|
synopText = novIdx.synopsis
|
||||||
|
|||||||
+18
-17
@@ -55,6 +55,7 @@ logger = logging.getLogger(__name__)
|
|||||||
class GuiOutlineView(QWidget):
|
class GuiOutlineView(QWidget):
|
||||||
|
|
||||||
loadDocumentTagRequest = pyqtSignal(str, Enum)
|
loadDocumentTagRequest = pyqtSignal(str, Enum)
|
||||||
|
openDocumentRequest = pyqtSignal(str, Enum, str)
|
||||||
|
|
||||||
def __init__(self, mainGui):
|
def __init__(self, mainGui):
|
||||||
super().__init__(parent=mainGui)
|
super().__init__(parent=mainGui)
|
||||||
@@ -375,15 +376,16 @@ class GuiOutlineTree(QTreeWidget):
|
|||||||
hiddenStateChanged = pyqtSignal()
|
hiddenStateChanged = pyqtSignal()
|
||||||
activeItemChanged = pyqtSignal(str, str)
|
activeItemChanged = pyqtSignal(str, str)
|
||||||
|
|
||||||
def __init__(self, theOutline):
|
def __init__(self, outlineView):
|
||||||
super().__init__(parent=theOutline)
|
super().__init__(parent=outlineView)
|
||||||
|
|
||||||
logger.debug("Initialising GuiOutlineTree ...")
|
logger.debug("Initialising GuiOutlineTree ...")
|
||||||
|
|
||||||
self.mainConf = novelwriter.CONFIG
|
self.mainConf = novelwriter.CONFIG
|
||||||
self.mainGui = theOutline.mainGui
|
self.outlineView = outlineView
|
||||||
self.theProject = theOutline.mainGui.theProject
|
self.mainGui = outlineView.mainGui
|
||||||
self.mainTheme = theOutline.mainGui.mainTheme
|
self.theProject = outlineView.mainGui.theProject
|
||||||
|
self.mainTheme = outlineView.mainGui.mainTheme
|
||||||
|
|
||||||
self.setUniformRowHeights(True)
|
self.setUniformRowHeights(True)
|
||||||
self.setFrameStyle(QFrame.NoFrame)
|
self.setFrameStyle(QFrame.NoFrame)
|
||||||
@@ -524,13 +526,11 @@ class GuiOutlineTree(QTreeWidget):
|
|||||||
selected, return the first.
|
selected, return the first.
|
||||||
"""
|
"""
|
||||||
selItem = self.selectedItems()
|
selItem = self.selectedItems()
|
||||||
tHandle = None
|
|
||||||
tLine = 0
|
|
||||||
if selItem:
|
if selItem:
|
||||||
tHandle = selItem[0].data(self._colIdx[nwOutline.TITLE], self.D_HANDLE)
|
tHandle = selItem[0].data(self._colIdx[nwOutline.TITLE], self.D_HANDLE)
|
||||||
tLine = checkInt(selItem[0].text(self._colIdx[nwOutline.LINE]), 1) - 1
|
sTitle = selItem[0].data(self._colIdx[nwOutline.TITLE], self.D_TITLE)
|
||||||
|
return tHandle, sTitle
|
||||||
return tHandle, tLine
|
return None, None
|
||||||
|
|
||||||
##
|
##
|
||||||
# Slots
|
# Slots
|
||||||
@@ -542,8 +542,10 @@ class GuiOutlineTree(QTreeWidget):
|
|||||||
clicked, and send it to the main gui class for opening in the
|
clicked, and send it to the main gui class for opening in the
|
||||||
document editor.
|
document editor.
|
||||||
"""
|
"""
|
||||||
tHandle, tLine = self.getSelectedHandle()
|
tHandle, sTitle = self.getSelectedHandle()
|
||||||
self.mainGui.openDocument(tHandle, tLine=tLine - 1, doScroll=True)
|
if tHandle is None:
|
||||||
|
return
|
||||||
|
self.outlineView.openDocumentRequest.emit(tHandle, nwDocMode.EDIT, sTitle or "")
|
||||||
return
|
return
|
||||||
|
|
||||||
@pyqtSlot()
|
@pyqtSlot()
|
||||||
@@ -554,9 +556,8 @@ class GuiOutlineTree(QTreeWidget):
|
|||||||
selItems = self.selectedItems()
|
selItems = self.selectedItems()
|
||||||
if selItems:
|
if selItems:
|
||||||
tHandle = selItems[0].data(self._colIdx[nwOutline.TITLE], self.D_HANDLE)
|
tHandle = selItems[0].data(self._colIdx[nwOutline.TITLE], self.D_HANDLE)
|
||||||
sTitle = selItems[0].data(self._colIdx[nwOutline.TITLE], self.D_TITLE)
|
sTitle = selItems[0].data(self._colIdx[nwOutline.TITLE], self.D_TITLE)
|
||||||
self.activeItemChanged.emit(tHandle, sTitle)
|
self.activeItemChanged.emit(tHandle, sTitle)
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
@pyqtSlot(int, int, int)
|
@pyqtSlot(int, int, int)
|
||||||
@@ -718,7 +719,7 @@ class GuiOutlineTree(QTreeWidget):
|
|||||||
trItem.setText(self._colIdx[nwOutline.LEVEL], novIdx.level)
|
trItem.setText(self._colIdx[nwOutline.LEVEL], novIdx.level)
|
||||||
trItem.setIcon(self._colIdx[nwOutline.LABEL], self._dIcon[nwItem.mainHeading])
|
trItem.setIcon(self._colIdx[nwOutline.LABEL], self._dIcon[nwItem.mainHeading])
|
||||||
trItem.setText(self._colIdx[nwOutline.LABEL], nwItem.itemName)
|
trItem.setText(self._colIdx[nwOutline.LABEL], nwItem.itemName)
|
||||||
trItem.setText(self._colIdx[nwOutline.LINE], sTitle[1:].lstrip("0"))
|
trItem.setText(self._colIdx[nwOutline.LINE], f"{novIdx.line:n}")
|
||||||
trItem.setText(self._colIdx[nwOutline.SYNOP], novIdx.synopsis)
|
trItem.setText(self._colIdx[nwOutline.SYNOP], novIdx.synopsis)
|
||||||
trItem.setText(self._colIdx[nwOutline.CCOUNT], f"{novIdx.charCount:n}")
|
trItem.setText(self._colIdx[nwOutline.CCOUNT], f"{novIdx.charCount:n}")
|
||||||
trItem.setText(self._colIdx[nwOutline.WCOUNT], f"{novIdx.wordCount:n}")
|
trItem.setText(self._colIdx[nwOutline.WCOUNT], f"{novIdx.wordCount:n}")
|
||||||
@@ -1048,7 +1049,7 @@ class GuiOutlineDetails(QScrollArea):
|
|||||||
"""
|
"""
|
||||||
pIndex = self.theProject.index
|
pIndex = self.theProject.index
|
||||||
nwItem = self.theProject.tree[tHandle]
|
nwItem = self.theProject.tree[tHandle]
|
||||||
novIdx = pIndex.getNovelData(tHandle, sTitle)
|
novIdx = pIndex.getItemHeader(tHandle, sTitle)
|
||||||
theRefs = pIndex.getReferences(tHandle, sTitle)
|
theRefs = pIndex.getReferences(tHandle, sTitle)
|
||||||
if nwItem is None or novIdx is None:
|
if nwItem is None or novIdx is None:
|
||||||
return False
|
return False
|
||||||
|
|||||||
@@ -60,7 +60,7 @@ class GuiProjectView(QWidget):
|
|||||||
|
|
||||||
# Signals for user interaction with the project tree
|
# Signals for user interaction with the project tree
|
||||||
selectedItemChanged = pyqtSignal(str)
|
selectedItemChanged = pyqtSignal(str)
|
||||||
openDocumentRequest = pyqtSignal(str, Enum, int, str)
|
openDocumentRequest = pyqtSignal(str, Enum, str)
|
||||||
|
|
||||||
# Requests for the main GUI
|
# Requests for the main GUI
|
||||||
projectSettingsRequest = pyqtSignal(int)
|
projectSettingsRequest = pyqtSignal(int)
|
||||||
@@ -1144,7 +1144,7 @@ class GuiProjectTree(QTreeWidget):
|
|||||||
return
|
return
|
||||||
|
|
||||||
if tItem.isFileType():
|
if tItem.isFileType():
|
||||||
self.projView.openDocumentRequest.emit(tHandle, nwDocMode.EDIT, -1, "")
|
self.projView.openDocumentRequest.emit(tHandle, nwDocMode.EDIT, "")
|
||||||
else:
|
else:
|
||||||
trItem.setExpanded(not trItem.isExpanded())
|
trItem.setExpanded(not trItem.isExpanded())
|
||||||
|
|
||||||
@@ -1190,11 +1190,11 @@ class GuiProjectTree(QTreeWidget):
|
|||||||
if isFile:
|
if isFile:
|
||||||
aOpenDoc = ctxMenu.addAction(self.tr("Open Document"))
|
aOpenDoc = ctxMenu.addAction(self.tr("Open Document"))
|
||||||
aOpenDoc.triggered.connect(
|
aOpenDoc.triggered.connect(
|
||||||
lambda: self.projView.openDocumentRequest.emit(tHandle, nwDocMode.EDIT, -1, "")
|
lambda: self.projView.openDocumentRequest.emit(tHandle, nwDocMode.EDIT, "")
|
||||||
)
|
)
|
||||||
aViewDoc = ctxMenu.addAction(self.tr("View Document"))
|
aViewDoc = ctxMenu.addAction(self.tr("View Document"))
|
||||||
aViewDoc.triggered.connect(
|
aViewDoc.triggered.connect(
|
||||||
lambda: self.projView.openDocumentRequest.emit(tHandle, nwDocMode.VIEW, -1, "")
|
lambda: self.projView.openDocumentRequest.emit(tHandle, nwDocMode.VIEW, "")
|
||||||
)
|
)
|
||||||
ctxMenu.addSeparator()
|
ctxMenu.addSeparator()
|
||||||
|
|
||||||
@@ -1324,7 +1324,7 @@ class GuiProjectTree(QTreeWidget):
|
|||||||
return
|
return
|
||||||
|
|
||||||
if tItem.isFileType():
|
if tItem.isFileType():
|
||||||
self.projView.openDocumentRequest.emit(tHandle, nwDocMode.VIEW, -1, "")
|
self.projView.openDocumentRequest.emit(tHandle, nwDocMode.VIEW, "")
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|||||||
+24
-25
@@ -224,10 +224,13 @@ class GuiMain(QMainWindow):
|
|||||||
self.docEditor.docCountsChanged.connect(self.itemDetails.updateCounts)
|
self.docEditor.docCountsChanged.connect(self.itemDetails.updateCounts)
|
||||||
self.docEditor.docCountsChanged.connect(self.projView.updateCounts)
|
self.docEditor.docCountsChanged.connect(self.projView.updateCounts)
|
||||||
self.docEditor.loadDocumentTagRequest.connect(self._followTag)
|
self.docEditor.loadDocumentTagRequest.connect(self._followTag)
|
||||||
|
self.docEditor.novelStructureChanged.connect(self.novelView.refreshTree)
|
||||||
|
self.docEditor.novelItemMetaChanged.connect(self.novelView.updateNovelItemMeta)
|
||||||
|
|
||||||
self.docViewer.loadDocumentTagRequest.connect(self._followTag)
|
self.docViewer.loadDocumentTagRequest.connect(self._followTag)
|
||||||
|
|
||||||
self.outlineView.loadDocumentTagRequest.connect(self._followTag)
|
self.outlineView.loadDocumentTagRequest.connect(self._followTag)
|
||||||
|
self.outlineView.openDocumentRequest.connect(self._openDocument)
|
||||||
|
|
||||||
# Finalise Initialisation
|
# Finalise Initialisation
|
||||||
# =======================
|
# =======================
|
||||||
@@ -648,7 +651,7 @@ class GuiMain(QMainWindow):
|
|||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def viewDocument(self, tHandle=None, tAnchor=None):
|
def viewDocument(self, tHandle=None, sTitle=None):
|
||||||
"""Load a document for viewing in the view panel.
|
"""Load a document for viewing in the view panel.
|
||||||
"""
|
"""
|
||||||
if not self.hasProject:
|
if not self.hasProject:
|
||||||
@@ -687,7 +690,8 @@ class GuiMain(QMainWindow):
|
|||||||
self.splitDocs.setSizes(vPos)
|
self.splitDocs.setSizes(vPos)
|
||||||
self.viewMeta.setVisible(self.mainConf.showRefPanel)
|
self.viewMeta.setVisible(self.mainConf.showRefPanel)
|
||||||
|
|
||||||
self.docViewer.navigateTo(tAnchor)
|
if sTitle:
|
||||||
|
self.docViewer.navigateTo(f"#{sTitle}")
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
@@ -775,17 +779,23 @@ class GuiMain(QMainWindow):
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
tHandle = None
|
tHandle = None
|
||||||
|
sTitle = None
|
||||||
tLine = None
|
tLine = None
|
||||||
if self.projView.treeHasFocus():
|
if self.projView.treeHasFocus():
|
||||||
tHandle = self.projView.getSelectedHandle()
|
tHandle = self.projView.getSelectedHandle()
|
||||||
elif self.novelView.treeHasFocus():
|
elif self.novelView.treeHasFocus():
|
||||||
tHandle, tLine = self.novelView.getSelectedHandle()
|
tHandle, sTitle = self.novelView.getSelectedHandle()
|
||||||
elif self.outlineView.treeHasFocus():
|
elif self.outlineView.treeHasFocus():
|
||||||
tHandle, tLine = self.outlineView.getSelectedHandle()
|
tHandle, sTitle = self.outlineView.getSelectedHandle()
|
||||||
else:
|
else:
|
||||||
logger.warning("No item selected")
|
logger.warning("No item selected")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
if tHandle is not None and sTitle is not None:
|
||||||
|
hItem = self.theProject.index.getItemHeader(tHandle, sTitle)
|
||||||
|
if hItem is not None:
|
||||||
|
tLine = hItem.line
|
||||||
|
|
||||||
if tHandle is not None:
|
if tHandle is not None:
|
||||||
self.openDocument(tHandle, tLine=tLine, changeFocus=False, doScroll=False)
|
self.openDocument(tHandle, tLine=tLine, changeFocus=False, doScroll=False)
|
||||||
|
|
||||||
@@ -808,17 +818,8 @@ class GuiMain(QMainWindow):
|
|||||||
"""Rebuild the project tree.
|
"""Rebuild the project tree.
|
||||||
"""
|
"""
|
||||||
self.projView.populateTree()
|
self.projView.populateTree()
|
||||||
# self.novelView.refreshTree()
|
|
||||||
return
|
return
|
||||||
|
|
||||||
def requestNovelTreeRefresh(self):
|
|
||||||
"""Update the novel tree, but only if it is visible.
|
|
||||||
"""
|
|
||||||
if self.projStack.currentIndex() == self.idxNovelView and self.hasProject:
|
|
||||||
self.novelView.refreshTree()
|
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
def rebuildIndex(self, beQuiet=False):
|
def rebuildIndex(self, beQuiet=False):
|
||||||
"""Rebuild the entire index.
|
"""Rebuild the entire index.
|
||||||
"""
|
"""
|
||||||
@@ -833,6 +834,7 @@ class GuiMain(QMainWindow):
|
|||||||
self.projView.saveProjectTasks()
|
self.projView.saveProjectTasks()
|
||||||
self.theProject.index.rebuildIndex()
|
self.theProject.index.rebuildIndex()
|
||||||
self.projView.populateTree()
|
self.projView.populateTree()
|
||||||
|
self.novelView.refreshTree()
|
||||||
|
|
||||||
tEnd = time()
|
tEnd = time()
|
||||||
self.setStatus(
|
self.setStatus(
|
||||||
@@ -1471,18 +1473,22 @@ class GuiMain(QMainWindow):
|
|||||||
if tMode == nwDocMode.EDIT:
|
if tMode == nwDocMode.EDIT:
|
||||||
self.openDocument(tHandle)
|
self.openDocument(tHandle)
|
||||||
elif tMode == nwDocMode.VIEW:
|
elif tMode == nwDocMode.VIEW:
|
||||||
self.viewDocument(tHandle=tHandle, tAnchor=f"#{sTitle}")
|
self.viewDocument(tHandle=tHandle, sTitle=sTitle)
|
||||||
return
|
return
|
||||||
|
|
||||||
@pyqtSlot(str, Enum, int, str)
|
@pyqtSlot(str, Enum, str)
|
||||||
def _openDocument(self, tHandle, tMode, tLine, tAnchor):
|
def _openDocument(self, tHandle, tMode, sTitle):
|
||||||
"""Handle an open document request from one of the tree views.
|
"""Handle an open document request from one of the tree views.
|
||||||
"""
|
"""
|
||||||
if tHandle is not None:
|
if tHandle is not None:
|
||||||
if tMode == nwDocMode.EDIT:
|
if tMode == nwDocMode.EDIT:
|
||||||
|
tLine = None
|
||||||
|
hItem = self.theProject.index.getItemHeader(tHandle, sTitle)
|
||||||
|
if hItem is not None:
|
||||||
|
tLine = hItem.line
|
||||||
self.openDocument(tHandle, tLine=tLine, changeFocus=False)
|
self.openDocument(tHandle, tLine=tLine, changeFocus=False)
|
||||||
elif tMode == nwDocMode.VIEW:
|
elif tMode == nwDocMode.VIEW:
|
||||||
self.viewDocument(tHandle=tHandle, tAnchor=(tAnchor or None))
|
self.viewDocument(tHandle=tHandle, sTitle=sTitle)
|
||||||
return
|
return
|
||||||
|
|
||||||
@pyqtSlot(nwView)
|
@pyqtSlot(nwView)
|
||||||
@@ -1564,7 +1570,6 @@ class GuiMain(QMainWindow):
|
|||||||
self.docEditor.closeSearch()
|
self.docEditor.closeSearch()
|
||||||
elif self.isFocusMode:
|
elif self.isFocusMode:
|
||||||
self.toggleFocusMode()
|
self.toggleFocusMode()
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
@pyqtSlot(int)
|
@pyqtSlot(int)
|
||||||
@@ -1581,17 +1586,11 @@ class GuiMain(QMainWindow):
|
|||||||
"""Activated when the project view tab is changed.
|
"""Activated when the project view tab is changed.
|
||||||
"""
|
"""
|
||||||
sHandle = None
|
sHandle = None
|
||||||
|
|
||||||
if stIndex == self.idxProjView:
|
if stIndex == self.idxProjView:
|
||||||
sHandle = self.projView.getSelectedHandle()
|
sHandle = self.projView.getSelectedHandle()
|
||||||
|
|
||||||
elif stIndex == self.idxNovelView:
|
elif stIndex == self.idxNovelView:
|
||||||
if self.hasProject:
|
sHandle, _ = self.novelView.getSelectedHandle()
|
||||||
self.novelView.refreshTree()
|
|
||||||
sHandle, _ = self.novelView.getSelectedHandle()
|
|
||||||
|
|
||||||
self.itemDetails.updateViewBox(sHandle)
|
self.itemDetails.updateViewBox(sHandle)
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
# END Class GuiMain
|
# END Class GuiMain
|
||||||
|
|||||||
@@ -1,109 +1,109 @@
|
|||||||
{
|
{
|
||||||
"tagsIndex": {
|
"tagsIndex": {
|
||||||
"Bod": {"handle": "4c4f28287af27", "heading": "T000001", "class": "CHARACTER"},
|
"Bod": {"handle": "4c4f28287af27", "heading": "T0001", "class": "CHARACTER"},
|
||||||
"Main": {"handle": "2426c6f0ca922", "heading": "T000001", "class": "PLOT"},
|
"Main": {"handle": "2426c6f0ca922", "heading": "T0001", "class": "PLOT"},
|
||||||
"Europe": {"handle": "04468803b92e1", "heading": "T000001", "class": "WORLD"}
|
"Europe": {"handle": "04468803b92e1", "heading": "T0001", "class": "WORLD"}
|
||||||
},
|
},
|
||||||
"itemIndex": {
|
"itemIndex": {
|
||||||
"7a992350f3eb6": {
|
"7a992350f3eb6": {
|
||||||
"headings": {
|
"headings": {
|
||||||
"T000001": {"level": "H1", "title": "Lorem Ipsum", "tag": "", "cCount": 230, "wCount": 40, "pCount": 3, "synopsis": ""}
|
"T0001": {"level": "H1", "title": "Lorem Ipsum", "line": 1, "tag": "", "cCount": 230, "wCount": 40, "pCount": 3, "synopsis": ""}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"8c58a65414c23": {
|
"8c58a65414c23": {
|
||||||
"headings": {
|
"headings": {
|
||||||
"T000000": {"level": "H0", "title": "", "tag": "", "cCount": 1058, "wCount": 176, "pCount": 2, "synopsis": ""}
|
"T0000": {"level": "H0", "title": "", "line": 0, "tag": "", "cCount": 1058, "wCount": 176, "pCount": 2, "synopsis": ""}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"88d59a277361b": {
|
"88d59a277361b": {
|
||||||
"headings": {
|
"headings": {
|
||||||
"T000001": {"level": "H2", "title": "Prologue", "tag": "", "cCount": 584, "wCount": 92, "pCount": 1, "synopsis": "Explanation from the lipsum.com website."}
|
"T0001": {"level": "H2", "title": "Prologue", "line": 1, "tag": "", "cCount": 584, "wCount": 92, "pCount": 1, "synopsis": "Explanation from the lipsum.com website."}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"db7e733775d4d": {
|
"db7e733775d4d": {
|
||||||
"headings": {
|
"headings": {
|
||||||
"T000001": {"level": "H1", "title": "Act One", "tag": "", "cCount": 35, "wCount": 6, "pCount": 1, "synopsis": ""}
|
"T0001": {"level": "H1", "title": "Act One", "line": 1, "tag": "", "cCount": 35, "wCount": 6, "pCount": 1, "synopsis": ""}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"fb609cd8319dc": {
|
"fb609cd8319dc": {
|
||||||
"headings": {
|
"headings": {
|
||||||
"T000001": {"level": "H2", "title": "Chapter One", "tag": "", "cCount": 419, "wCount": 67, "pCount": 1, "synopsis": "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque at aliquam quam."}
|
"T0001": {"level": "H2", "title": "Chapter One", "line": 1, "tag": "", "cCount": 419, "wCount": 67, "pCount": 1, "synopsis": "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque at aliquam quam."}
|
||||||
},
|
},
|
||||||
"references": {
|
"references": {
|
||||||
"T000001": {"Bod": "@pov", "Main": "@plot", "Europe": "@location"}
|
"T0001": {"Bod": "@pov", "Main": "@plot", "Europe": "@location"}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"88243afbe5ed8": {
|
"88243afbe5ed8": {
|
||||||
"headings": {
|
"headings": {
|
||||||
"T000001": {"level": "H3", "title": "Scene One", "tag": "", "cCount": 1197, "wCount": 174, "pCount": 2, "synopsis": "Aenean ut placerat velit. Etiam laoreet ullamcorper risus, eget lobortis enim scelerisque non. Suspendisse id maximus nunc, et mollis sapien. Curabitur vel semper sapien, non pulvinar dolor. Etiam finibus nisi vel mi molestie consectetur."},
|
"T0001": {"level": "H3", "title": "Scene One", "line": 1, "tag": "", "cCount": 1197, "wCount": 174, "pCount": 2, "synopsis": "Aenean ut placerat velit. Etiam laoreet ullamcorper risus, eget lobortis enim scelerisque non. Suspendisse id maximus nunc, et mollis sapien. Curabitur vel semper sapien, non pulvinar dolor. Etiam finibus nisi vel mi molestie consectetur."},
|
||||||
"T000013": {"level": "H4", "title": "Scene One, Section Two", "tag": "", "cCount": 1561, "wCount": 230, "pCount": 2, "synopsis": ""}
|
"T0002": {"level": "H4", "title": "Scene One, Section Two", "line": 13, "tag": "", "cCount": 1561, "wCount": 230, "pCount": 2, "synopsis": ""}
|
||||||
},
|
},
|
||||||
"references": {
|
"references": {
|
||||||
"T000001": {"Bod": "@pov", "Main": "@plot", "Europe": "@location"}
|
"T0001": {"Bod": "@pov", "Main": "@plot", "Europe": "@location"}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"f96ec11c6a3da": {
|
"f96ec11c6a3da": {
|
||||||
"headings": {
|
"headings": {
|
||||||
"T000001": {"level": "H3", "title": "Scene Two", "tag": "", "cCount": 2034, "wCount": 299, "pCount": 3, "synopsis": "Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Integer sapien nulla, dictum at lacus a, dignissim consectetur dolor. Nunc vel eleifend lacus, eu dapibus orci."},
|
"T0001": {"level": "H3", "title": "Scene Two", "line": 1, "tag": "", "cCount": 2034, "wCount": 299, "pCount": 3, "synopsis": "Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Integer sapien nulla, dictum at lacus a, dignissim consectetur dolor. Nunc vel eleifend lacus, eu dapibus orci."},
|
||||||
"T000015": {"level": "H4", "title": "Scene Two, Section Two", "tag": "", "cCount": 2009, "wCount": 301, "pCount": 3, "synopsis": ""}
|
"T0002": {"level": "H4", "title": "Scene Two, Section Two", "line": 15, "tag": "", "cCount": 2009, "wCount": 301, "pCount": 3, "synopsis": ""}
|
||||||
},
|
},
|
||||||
"references": {
|
"references": {
|
||||||
"T000001": {"Bod": "@pov", "Main": "@plot", "Europe": "@location"}
|
"T0001": {"Bod": "@pov", "Main": "@plot", "Europe": "@location"}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"846352075de7d": {
|
"846352075de7d": {
|
||||||
"headings": {
|
"headings": {
|
||||||
"T000001": {"level": "H2", "title": "Why do we use it?", "tag": "", "cCount": 631, "wCount": 109, "pCount": 3, "synopsis": ""}
|
"T0001": {"level": "H2", "title": "Why do we use it?", "line": 1, "tag": "", "cCount": 631, "wCount": 109, "pCount": 3, "synopsis": ""}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"441420a886d82": {
|
"441420a886d82": {
|
||||||
"headings": {
|
"headings": {
|
||||||
"T000001": {"level": "H2", "title": "Chapter Two", "tag": "", "cCount": 477, "wCount": 70, "pCount": 1, "synopsis": "Curabitur a elit posuere, varius ex et, convallis neque. Phasellus sagittis pharetra sem vitae dapibus. Curabitur varius lorem non pulvinar congue."}
|
"T0001": {"level": "H2", "title": "Chapter Two", "line": 1, "tag": "", "cCount": 477, "wCount": 70, "pCount": 1, "synopsis": "Curabitur a elit posuere, varius ex et, convallis neque. Phasellus sagittis pharetra sem vitae dapibus. Curabitur varius lorem non pulvinar congue."}
|
||||||
},
|
},
|
||||||
"references": {
|
"references": {
|
||||||
"T000001": {"Bod": "@pov", "Main": "@plot", "Europe": "@location"}
|
"T0001": {"Bod": "@pov", "Main": "@plot", "Europe": "@location"}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"eb103bc70c90c": {
|
"eb103bc70c90c": {
|
||||||
"headings": {
|
"headings": {
|
||||||
"T000001": {"level": "H3", "title": "Scene Three", "tag": "", "cCount": 3006, "wCount": 439, "pCount": 4, "synopsis": "Aenean ut libero ut lectus porttitor rhoncus vel et massa. Nam pretium, nibh et varius vehicula, urna metus blandit eros, euismod pharetra diam diam et libero. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos."}
|
"T0001": {"level": "H3", "title": "Scene Three", "line": 1, "tag": "", "cCount": 3006, "wCount": 439, "pCount": 4, "synopsis": "Aenean ut libero ut lectus porttitor rhoncus vel et massa. Nam pretium, nibh et varius vehicula, urna metus blandit eros, euismod pharetra diam diam et libero. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos."}
|
||||||
},
|
},
|
||||||
"references": {
|
"references": {
|
||||||
"T000001": {"Bod": "@pov", "Main": "@plot", "Europe": "@location"}
|
"T0001": {"Bod": "@pov", "Main": "@plot", "Europe": "@location"}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"f8c0562e50f1b": {
|
"f8c0562e50f1b": {
|
||||||
"headings": {
|
"headings": {
|
||||||
"T000001": {"level": "H3", "title": "Scene Four", "tag": "", "cCount": 3839, "wCount": 563, "pCount": 6, "synopsis": "Nam tempor blandit magna laoreet aliquet. Vestibulum auctor posuere leo, ac gravida nisi rhoncus varius. Aenean posuere dolor vitae condimentum volutpat. Donec egestas volutpat risus, quis luctus justo."}
|
"T0001": {"level": "H3", "title": "Scene Four", "line": 1, "tag": "", "cCount": 3839, "wCount": 563, "pCount": 6, "synopsis": "Nam tempor blandit magna laoreet aliquet. Vestibulum auctor posuere leo, ac gravida nisi rhoncus varius. Aenean posuere dolor vitae condimentum volutpat. Donec egestas volutpat risus, quis luctus justo."}
|
||||||
},
|
},
|
||||||
"references": {
|
"references": {
|
||||||
"T000001": {"Bod": "@pov", "Main": "@plot", "Europe": "@location"}
|
"T0001": {"Bod": "@pov", "Main": "@plot", "Europe": "@location"}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"47666c91c7ccf": {
|
"47666c91c7ccf": {
|
||||||
"headings": {
|
"headings": {
|
||||||
"T000001": {"level": "H3", "title": "Scene Five", "tag": "", "cCount": 3644, "wCount": 543, "pCount": 5, "synopsis": "Praesent eget est porta, dictum ante in, egestas risus. Mauris risus mauris, consequat aliquam mauris et, feugiat iaculis ipsum. Aliquam arcu ipsum, fermentum ut arcu sed, lobortis euismod sem. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus."}
|
"T0001": {"level": "H3", "title": "Scene Five", "line": 1, "tag": "", "cCount": 3644, "wCount": 543, "pCount": 5, "synopsis": "Praesent eget est porta, dictum ante in, egestas risus. Mauris risus mauris, consequat aliquam mauris et, feugiat iaculis ipsum. Aliquam arcu ipsum, fermentum ut arcu sed, lobortis euismod sem. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus."}
|
||||||
},
|
},
|
||||||
"references": {
|
"references": {
|
||||||
"T000001": {"Bod": "@pov", "Main": "@plot", "Europe": "@location"}
|
"T0001": {"Bod": "@pov", "Main": "@plot", "Europe": "@location"}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"4c4f28287af27": {
|
"4c4f28287af27": {
|
||||||
"headings": {
|
"headings": {
|
||||||
"T000001": {"level": "H1", "title": "Nobody Owens", "tag": "Bod", "cCount": 1864, "wCount": 284, "pCount": 3, "synopsis": ""}
|
"T0001": {"level": "H1", "title": "Nobody Owens", "line": 1, "tag": "Bod", "cCount": 1864, "wCount": 284, "pCount": 3, "synopsis": ""}
|
||||||
},
|
},
|
||||||
"references": {
|
"references": {
|
||||||
"T000001": {"Main": "@plot"}
|
"T0001": {"Main": "@plot"}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"2426c6f0ca922": {
|
"2426c6f0ca922": {
|
||||||
"headings": {
|
"headings": {
|
||||||
"T000001": {"level": "H1", "title": "Main Plot", "tag": "Main", "cCount": 1369, "wCount": 195, "pCount": 2, "synopsis": ""}
|
"T0001": {"level": "H1", "title": "Main Plot", "line": 1, "tag": "Main", "cCount": 1369, "wCount": 195, "pCount": 2, "synopsis": ""}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"04468803b92e1": {
|
"04468803b92e1": {
|
||||||
"headings": {
|
"headings": {
|
||||||
"T000001": {"level": "H1", "title": "Ancient Europe", "tag": "Europe", "cCount": 1770, "wCount": 259, "pCount": 3, "synopsis": ""}
|
"T0001": {"level": "H1", "title": "Ancient Europe", "line": 1, "tag": "Europe", "cCount": 1770, "wCount": 259, "pCount": 3, "synopsis": ""}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -208,12 +208,12 @@ def testBaseCommon_IsHandle():
|
|||||||
def testBaseCommon_IsTitleTag():
|
def testBaseCommon_IsTitleTag():
|
||||||
"""Test the isItemClass function.
|
"""Test the isItemClass function.
|
||||||
"""
|
"""
|
||||||
assert isTitleTag("T123456") is True
|
assert isTitleTag("T1234") is True
|
||||||
|
|
||||||
assert isTitleTag("t123456") is False
|
assert isTitleTag("t1234") is False
|
||||||
assert isTitleTag("S123456") is False
|
assert isTitleTag("S1234") is False
|
||||||
assert isTitleTag("T12345A") is False
|
assert isTitleTag("T123A") is False
|
||||||
assert isTitleTag("T1234567") is False
|
assert isTitleTag("T12345") is False
|
||||||
|
|
||||||
assert isTitleTag("None") is False
|
assert isTitleTag("None") is False
|
||||||
assert isTitleTag(None) is False
|
assert isTitleTag(None) is False
|
||||||
|
|||||||
+213
-212
@@ -29,7 +29,7 @@ from tools import C, buildTestProject, cmpFiles, writeFile
|
|||||||
|
|
||||||
from novelwriter.enum import nwItemClass, nwItemLayout
|
from novelwriter.enum import nwItemClass, nwItemLayout
|
||||||
from novelwriter.constants import nwFiles
|
from novelwriter.constants import nwFiles
|
||||||
from novelwriter.core.index import NWIndex, countWords, TagsIndex
|
from novelwriter.core.index import IndexItem, NWIndex, countWords, TagsIndex
|
||||||
from novelwriter.core.project import NWProject
|
from novelwriter.core.project import NWProject
|
||||||
|
|
||||||
|
|
||||||
@@ -231,10 +231,10 @@ def testCoreIndex_CheckThese(mockGUI, fncPath, mockRnd):
|
|||||||
"@invalid: John\n" # Checks for issue #688
|
"@invalid: John\n" # Checks for issue #688
|
||||||
))
|
))
|
||||||
assert theIndex._tagsIndex.tagHandle("Jane") == cHandle
|
assert theIndex._tagsIndex.tagHandle("Jane") == cHandle
|
||||||
assert theIndex._tagsIndex.tagHeading("Jane") == "T000001"
|
assert theIndex._tagsIndex.tagHeading("Jane") == "T0001"
|
||||||
assert theIndex._tagsIndex.tagClass("Jane") == "CHARACTER"
|
assert theIndex._tagsIndex.tagClass("Jane") == "CHARACTER"
|
||||||
assert theIndex.getNovelData(nHandle, "T000001").title == "Hello World!"
|
assert theIndex.getItemHeader(nHandle, "T0001").title == "Hello World!"
|
||||||
assert theIndex.getReferences(nHandle, "T000001") == {
|
assert theIndex.getReferences(nHandle, "T0001") == {
|
||||||
"@char": [],
|
"@char": [],
|
||||||
"@custom": [],
|
"@custom": [],
|
||||||
"@entity": [],
|
"@entity": [],
|
||||||
@@ -345,9 +345,9 @@ def testCoreIndex_ScanText(mockGUI, fncPath, mockRnd):
|
|||||||
"Well, not really.\n"
|
"Well, not really.\n"
|
||||||
))
|
))
|
||||||
assert theIndex._tagsIndex.tagHandle("Jane") == cHandle
|
assert theIndex._tagsIndex.tagHandle("Jane") == cHandle
|
||||||
assert theIndex._tagsIndex.tagHeading("Jane") == "T000001"
|
assert theIndex._tagsIndex.tagHeading("Jane") == "T0001"
|
||||||
assert theIndex._tagsIndex.tagClass("Jane") == "CHARACTER"
|
assert theIndex._tagsIndex.tagClass("Jane") == "CHARACTER"
|
||||||
assert theIndex.getNovelData(nHandle, "T000001").title == "Hello World!"
|
assert theIndex.getItemHeader(nHandle, "T0001").title == "Hello World!"
|
||||||
|
|
||||||
# Title Indexing
|
# Title Indexing
|
||||||
# ==============
|
# ==============
|
||||||
@@ -369,40 +369,45 @@ def testCoreIndex_ScanText(mockGUI, fncPath, mockRnd):
|
|||||||
"##### Title Five\n\n" # Not interpreted as a title, the hashes are counted as a word
|
"##### Title Five\n\n" # Not interpreted as a title, the hashes are counted as a word
|
||||||
"Paragraph Five.\n\n"
|
"Paragraph Five.\n\n"
|
||||||
))
|
))
|
||||||
assert theIndex._itemIndex[nHandle]["T000001"].references == {}
|
assert theIndex._itemIndex[nHandle]["T0001"].references == {}
|
||||||
assert theIndex._itemIndex[nHandle]["T000007"].references == {}
|
assert theIndex._itemIndex[nHandle]["T0002"].references == {}
|
||||||
assert theIndex._itemIndex[nHandle]["T000013"].references == {}
|
assert theIndex._itemIndex[nHandle]["T0003"].references == {}
|
||||||
assert theIndex._itemIndex[nHandle]["T000019"].references == {}
|
assert theIndex._itemIndex[nHandle]["T0004"].references == {}
|
||||||
|
|
||||||
assert theIndex._itemIndex[nHandle]["T000001"].level == "H1"
|
assert theIndex._itemIndex[nHandle]["T0001"].level == "H1"
|
||||||
assert theIndex._itemIndex[nHandle]["T000007"].level == "H2"
|
assert theIndex._itemIndex[nHandle]["T0002"].level == "H2"
|
||||||
assert theIndex._itemIndex[nHandle]["T000013"].level == "H3"
|
assert theIndex._itemIndex[nHandle]["T0003"].level == "H3"
|
||||||
assert theIndex._itemIndex[nHandle]["T000019"].level == "H4"
|
assert theIndex._itemIndex[nHandle]["T0004"].level == "H4"
|
||||||
|
|
||||||
assert theIndex._itemIndex[nHandle]["T000001"].title == "Title One"
|
assert theIndex._itemIndex[nHandle]["T0001"].line == 1
|
||||||
assert theIndex._itemIndex[nHandle]["T000007"].title == "Title Two"
|
assert theIndex._itemIndex[nHandle]["T0002"].line == 7
|
||||||
assert theIndex._itemIndex[nHandle]["T000013"].title == "Title Three"
|
assert theIndex._itemIndex[nHandle]["T0003"].line == 13
|
||||||
assert theIndex._itemIndex[nHandle]["T000019"].title == "Title Four"
|
assert theIndex._itemIndex[nHandle]["T0004"].line == 19
|
||||||
|
|
||||||
assert theIndex._itemIndex[nHandle]["T000001"].charCount == 23
|
assert theIndex._itemIndex[nHandle]["T0001"].title == "Title One"
|
||||||
assert theIndex._itemIndex[nHandle]["T000007"].charCount == 23
|
assert theIndex._itemIndex[nHandle]["T0002"].title == "Title Two"
|
||||||
assert theIndex._itemIndex[nHandle]["T000013"].charCount == 27
|
assert theIndex._itemIndex[nHandle]["T0003"].title == "Title Three"
|
||||||
assert theIndex._itemIndex[nHandle]["T000019"].charCount == 56
|
assert theIndex._itemIndex[nHandle]["T0004"].title == "Title Four"
|
||||||
|
|
||||||
assert theIndex._itemIndex[nHandle]["T000001"].wordCount == 4
|
assert theIndex._itemIndex[nHandle]["T0001"].charCount == 23
|
||||||
assert theIndex._itemIndex[nHandle]["T000007"].wordCount == 4
|
assert theIndex._itemIndex[nHandle]["T0002"].charCount == 23
|
||||||
assert theIndex._itemIndex[nHandle]["T000013"].wordCount == 4
|
assert theIndex._itemIndex[nHandle]["T0003"].charCount == 27
|
||||||
assert theIndex._itemIndex[nHandle]["T000019"].wordCount == 9
|
assert theIndex._itemIndex[nHandle]["T0004"].charCount == 56
|
||||||
|
|
||||||
assert theIndex._itemIndex[nHandle]["T000001"].paraCount == 1
|
assert theIndex._itemIndex[nHandle]["T0001"].wordCount == 4
|
||||||
assert theIndex._itemIndex[nHandle]["T000007"].paraCount == 1
|
assert theIndex._itemIndex[nHandle]["T0002"].wordCount == 4
|
||||||
assert theIndex._itemIndex[nHandle]["T000013"].paraCount == 1
|
assert theIndex._itemIndex[nHandle]["T0003"].wordCount == 4
|
||||||
assert theIndex._itemIndex[nHandle]["T000019"].paraCount == 3
|
assert theIndex._itemIndex[nHandle]["T0004"].wordCount == 9
|
||||||
|
|
||||||
assert theIndex._itemIndex[nHandle]["T000001"].synopsis == "Synopsis One."
|
assert theIndex._itemIndex[nHandle]["T0001"].paraCount == 1
|
||||||
assert theIndex._itemIndex[nHandle]["T000007"].synopsis == "Synopsis Two."
|
assert theIndex._itemIndex[nHandle]["T0002"].paraCount == 1
|
||||||
assert theIndex._itemIndex[nHandle]["T000013"].synopsis == "Synopsis Three."
|
assert theIndex._itemIndex[nHandle]["T0003"].paraCount == 1
|
||||||
assert theIndex._itemIndex[nHandle]["T000019"].synopsis == "Synopsis Four."
|
assert theIndex._itemIndex[nHandle]["T0004"].paraCount == 3
|
||||||
|
|
||||||
|
assert theIndex._itemIndex[nHandle]["T0001"].synopsis == "Synopsis One."
|
||||||
|
assert theIndex._itemIndex[nHandle]["T0002"].synopsis == "Synopsis Two."
|
||||||
|
assert theIndex._itemIndex[nHandle]["T0003"].synopsis == "Synopsis Three."
|
||||||
|
assert theIndex._itemIndex[nHandle]["T0004"].synopsis == "Synopsis Four."
|
||||||
|
|
||||||
# Note File
|
# Note File
|
||||||
assert theIndex.scanText(cHandle, (
|
assert theIndex.scanText(cHandle, (
|
||||||
@@ -411,13 +416,14 @@ def testCoreIndex_ScanText(mockGUI, fncPath, mockRnd):
|
|||||||
"% synopsis: Synopsis One.\n\n"
|
"% synopsis: Synopsis One.\n\n"
|
||||||
"Paragraph One.\n\n"
|
"Paragraph One.\n\n"
|
||||||
))
|
))
|
||||||
assert theIndex._itemIndex[cHandle]["T000001"].references == {}
|
assert theIndex._itemIndex[cHandle]["T0001"].references == {}
|
||||||
assert theIndex._itemIndex[cHandle]["T000001"].level == "H1"
|
assert theIndex._itemIndex[cHandle]["T0001"].level == "H1"
|
||||||
assert theIndex._itemIndex[cHandle]["T000001"].title == "Title One"
|
assert theIndex._itemIndex[cHandle]["T0001"].line == 1
|
||||||
assert theIndex._itemIndex[cHandle]["T000001"].charCount == 23
|
assert theIndex._itemIndex[cHandle]["T0001"].title == "Title One"
|
||||||
assert theIndex._itemIndex[cHandle]["T000001"].wordCount == 4
|
assert theIndex._itemIndex[cHandle]["T0001"].charCount == 23
|
||||||
assert theIndex._itemIndex[cHandle]["T000001"].paraCount == 1
|
assert theIndex._itemIndex[cHandle]["T0001"].wordCount == 4
|
||||||
assert theIndex._itemIndex[cHandle]["T000001"].synopsis == "Synopsis One."
|
assert theIndex._itemIndex[cHandle]["T0001"].paraCount == 1
|
||||||
|
assert theIndex._itemIndex[cHandle]["T0001"].synopsis == "Synopsis One."
|
||||||
|
|
||||||
# Valid and Invalid References
|
# Valid and Invalid References
|
||||||
assert theIndex.scanText(sHandle, (
|
assert theIndex.scanText(sHandle, (
|
||||||
@@ -428,7 +434,7 @@ def testCoreIndex_ScanText(mockGUI, fncPath, mockRnd):
|
|||||||
"% synopsis: Synopsis One.\n\n"
|
"% synopsis: Synopsis One.\n\n"
|
||||||
"Paragraph One.\n\n"
|
"Paragraph One.\n\n"
|
||||||
))
|
))
|
||||||
assert theIndex._itemIndex[sHandle]["T000001"].references == {
|
assert theIndex._itemIndex[sHandle]["T0001"].references == {
|
||||||
"One": {"@pov"}, "Two": {"@char"}
|
"One": {"@pov"}, "Two": {"@char"}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -439,25 +445,27 @@ def testCoreIndex_ScanText(mockGUI, fncPath, mockRnd):
|
|||||||
"#! My Project\n\n"
|
"#! My Project\n\n"
|
||||||
">> By Jane Doe <<\n\n"
|
">> By Jane Doe <<\n\n"
|
||||||
))
|
))
|
||||||
assert theIndex._itemIndex[cHandle]["T000001"].references == {}
|
assert theIndex._itemIndex[cHandle]["T0001"].references == {}
|
||||||
assert theIndex._itemIndex[tHandle]["T000001"].level == "H1"
|
assert theIndex._itemIndex[tHandle]["T0001"].level == "H1"
|
||||||
assert theIndex._itemIndex[tHandle]["T000001"].title == "My Project"
|
assert theIndex._itemIndex[tHandle]["T0001"].line == 1
|
||||||
assert theIndex._itemIndex[tHandle]["T000001"].charCount == 21
|
assert theIndex._itemIndex[tHandle]["T0001"].title == "My Project"
|
||||||
assert theIndex._itemIndex[tHandle]["T000001"].wordCount == 5
|
assert theIndex._itemIndex[tHandle]["T0001"].charCount == 21
|
||||||
assert theIndex._itemIndex[tHandle]["T000001"].paraCount == 1
|
assert theIndex._itemIndex[tHandle]["T0001"].wordCount == 5
|
||||||
assert theIndex._itemIndex[tHandle]["T000001"].synopsis == ""
|
assert theIndex._itemIndex[tHandle]["T0001"].paraCount == 1
|
||||||
|
assert theIndex._itemIndex[tHandle]["T0001"].synopsis == ""
|
||||||
|
|
||||||
assert theIndex.scanText(tHandle, (
|
assert theIndex.scanText(tHandle, (
|
||||||
"##! Prologue\n\n"
|
"##! Prologue\n\n"
|
||||||
"In the beginning there was time ...\n\n"
|
"In the beginning there was time ...\n\n"
|
||||||
))
|
))
|
||||||
assert theIndex._itemIndex[cHandle]["T000001"].references == {}
|
assert theIndex._itemIndex[cHandle]["T0001"].references == {}
|
||||||
assert theIndex._itemIndex[tHandle]["T000001"].level == "H2"
|
assert theIndex._itemIndex[tHandle]["T0001"].level == "H2"
|
||||||
assert theIndex._itemIndex[tHandle]["T000001"].title == "Prologue"
|
assert theIndex._itemIndex[tHandle]["T0001"].line == 1
|
||||||
assert theIndex._itemIndex[tHandle]["T000001"].charCount == 43
|
assert theIndex._itemIndex[tHandle]["T0001"].title == "Prologue"
|
||||||
assert theIndex._itemIndex[tHandle]["T000001"].wordCount == 8
|
assert theIndex._itemIndex[tHandle]["T0001"].charCount == 43
|
||||||
assert theIndex._itemIndex[tHandle]["T000001"].paraCount == 1
|
assert theIndex._itemIndex[tHandle]["T0001"].wordCount == 8
|
||||||
assert theIndex._itemIndex[tHandle]["T000001"].synopsis == ""
|
assert theIndex._itemIndex[tHandle]["T0001"].paraCount == 1
|
||||||
|
assert theIndex._itemIndex[tHandle]["T0001"].synopsis == ""
|
||||||
|
|
||||||
# Page wo/Title
|
# Page wo/Title
|
||||||
# =============
|
# =============
|
||||||
@@ -466,25 +474,27 @@ def testCoreIndex_ScanText(mockGUI, fncPath, mockRnd):
|
|||||||
assert theIndex.scanText(pHandle, (
|
assert theIndex.scanText(pHandle, (
|
||||||
"This is a page with some text on it.\n\n"
|
"This is a page with some text on it.\n\n"
|
||||||
))
|
))
|
||||||
assert theIndex._itemIndex[pHandle]["T000000"].references == {}
|
assert theIndex._itemIndex[pHandle]["T0000"].references == {}
|
||||||
assert theIndex._itemIndex[pHandle]["T000000"].level == "H0"
|
assert theIndex._itemIndex[pHandle]["T0000"].level == "H0"
|
||||||
assert theIndex._itemIndex[pHandle]["T000000"].title == ""
|
assert theIndex._itemIndex[pHandle]["T0000"].line == 0
|
||||||
assert theIndex._itemIndex[pHandle]["T000000"].charCount == 36
|
assert theIndex._itemIndex[pHandle]["T0000"].title == ""
|
||||||
assert theIndex._itemIndex[pHandle]["T000000"].wordCount == 9
|
assert theIndex._itemIndex[pHandle]["T0000"].charCount == 36
|
||||||
assert theIndex._itemIndex[pHandle]["T000000"].paraCount == 1
|
assert theIndex._itemIndex[pHandle]["T0000"].wordCount == 9
|
||||||
assert theIndex._itemIndex[pHandle]["T000000"].synopsis == ""
|
assert theIndex._itemIndex[pHandle]["T0000"].paraCount == 1
|
||||||
|
assert theIndex._itemIndex[pHandle]["T0000"].synopsis == ""
|
||||||
|
|
||||||
theProject.tree[pHandle]._layout = nwItemLayout.NOTE
|
theProject.tree[pHandle]._layout = nwItemLayout.NOTE
|
||||||
assert theIndex.scanText(pHandle, (
|
assert theIndex.scanText(pHandle, (
|
||||||
"This is a page with some text on it.\n\n"
|
"This is a page with some text on it.\n\n"
|
||||||
))
|
))
|
||||||
assert theIndex._itemIndex[pHandle]["T000000"].references == {}
|
assert theIndex._itemIndex[pHandle]["T0000"].references == {}
|
||||||
assert theIndex._itemIndex[pHandle]["T000000"].level == "H0"
|
assert theIndex._itemIndex[pHandle]["T0000"].level == "H0"
|
||||||
assert theIndex._itemIndex[pHandle]["T000000"].title == ""
|
assert theIndex._itemIndex[pHandle]["T0000"].line == 0
|
||||||
assert theIndex._itemIndex[pHandle]["T000000"].charCount == 36
|
assert theIndex._itemIndex[pHandle]["T0000"].title == ""
|
||||||
assert theIndex._itemIndex[pHandle]["T000000"].wordCount == 9
|
assert theIndex._itemIndex[pHandle]["T0000"].charCount == 36
|
||||||
assert theIndex._itemIndex[pHandle]["T000000"].paraCount == 1
|
assert theIndex._itemIndex[pHandle]["T0000"].wordCount == 9
|
||||||
assert theIndex._itemIndex[pHandle]["T000000"].synopsis == ""
|
assert theIndex._itemIndex[pHandle]["T0000"].paraCount == 1
|
||||||
|
assert theIndex._itemIndex[pHandle]["T0000"].synopsis == ""
|
||||||
|
|
||||||
assert theProject.closeProject() is True
|
assert theProject.closeProject() is True
|
||||||
|
|
||||||
@@ -512,8 +522,8 @@ def testCoreIndex_ExtractData(mockGUI, fncPath, mockRnd):
|
|||||||
nHandle = theProject.newFile("Hello", C.hNovelRoot)
|
nHandle = theProject.newFile("Hello", C.hNovelRoot)
|
||||||
cHandle = theProject.newFile("Jane", C.hCharRoot)
|
cHandle = theProject.newFile("Jane", C.hCharRoot)
|
||||||
|
|
||||||
assert theIndex.getNovelData("", "") is None
|
assert theIndex.getItemHeader("", "") is None
|
||||||
assert theIndex.getNovelData(C.hNovelRoot, "") is None
|
assert theIndex.getItemHeader(C.hNovelRoot, "") is None
|
||||||
|
|
||||||
assert theIndex.scanText(cHandle, (
|
assert theIndex.scanText(cHandle, (
|
||||||
"# Jane Smith\n"
|
"# Jane Smith\n"
|
||||||
@@ -534,10 +544,10 @@ def testCoreIndex_ExtractData(mockGUI, fncPath, mockRnd):
|
|||||||
theKeys.append(aKey)
|
theKeys.append(aKey)
|
||||||
|
|
||||||
assert theKeys == [
|
assert theKeys == [
|
||||||
f"{C.hTitlePage}:T000001",
|
f"{C.hTitlePage}:T0001",
|
||||||
f"{C.hChapterDoc}:T000001",
|
f"{C.hChapterDoc}:T0001",
|
||||||
f"{C.hSceneDoc}:T000001",
|
f"{C.hSceneDoc}:T0001",
|
||||||
f"{nHandle}:T000001",
|
f"{nHandle}:T0001",
|
||||||
]
|
]
|
||||||
|
|
||||||
# Check that excluded files can be skipped
|
# Check that excluded files can be skipped
|
||||||
@@ -548,10 +558,10 @@ def testCoreIndex_ExtractData(mockGUI, fncPath, mockRnd):
|
|||||||
theKeys.append(aKey)
|
theKeys.append(aKey)
|
||||||
|
|
||||||
assert theKeys == [
|
assert theKeys == [
|
||||||
f"{C.hTitlePage}:T000001",
|
f"{C.hTitlePage}:T0001",
|
||||||
f"{C.hChapterDoc}:T000001",
|
f"{C.hChapterDoc}:T0001",
|
||||||
f"{C.hSceneDoc}:T000001",
|
f"{C.hSceneDoc}:T0001",
|
||||||
f"{nHandle}:T000001",
|
f"{nHandle}:T0001",
|
||||||
]
|
]
|
||||||
|
|
||||||
theKeys = []
|
theKeys = []
|
||||||
@@ -559,9 +569,9 @@ def testCoreIndex_ExtractData(mockGUI, fncPath, mockRnd):
|
|||||||
theKeys.append(aKey)
|
theKeys.append(aKey)
|
||||||
|
|
||||||
assert theKeys == [
|
assert theKeys == [
|
||||||
f"{C.hTitlePage}:T000001",
|
f"{C.hTitlePage}:T0001",
|
||||||
f"{C.hChapterDoc}:T000001",
|
f"{C.hChapterDoc}:T0001",
|
||||||
f"{C.hSceneDoc}:T000001",
|
f"{C.hSceneDoc}:T0001",
|
||||||
]
|
]
|
||||||
|
|
||||||
# The novel file should have the correct counts
|
# The novel file should have the correct counts
|
||||||
@@ -570,6 +580,14 @@ def testCoreIndex_ExtractData(mockGUI, fncPath, mockRnd):
|
|||||||
assert wC == 12 # Words in text and title only
|
assert wC == 12 # Words in text and title only
|
||||||
assert pC == 2 # Paragraphs in text only
|
assert pC == 2 # Paragraphs in text only
|
||||||
|
|
||||||
|
# getItemData + getHandleHeaderCount
|
||||||
|
# ==================================
|
||||||
|
|
||||||
|
theItem = theIndex.getItemData(nHandle)
|
||||||
|
assert isinstance(theItem, IndexItem)
|
||||||
|
assert theItem.headings() == ["T0001"]
|
||||||
|
assert theIndex.getHandleHeaderCount(nHandle) == 1
|
||||||
|
|
||||||
# getReferences
|
# getReferences
|
||||||
# =============
|
# =============
|
||||||
|
|
||||||
@@ -594,13 +612,13 @@ def testCoreIndex_ExtractData(mockGUI, fncPath, mockRnd):
|
|||||||
|
|
||||||
# The character file should have a record of the reference from the novel file
|
# The character file should have a record of the reference from the novel file
|
||||||
theRefs = theIndex.getBackReferenceList(cHandle)
|
theRefs = theIndex.getBackReferenceList(cHandle)
|
||||||
assert theRefs == {nHandle: "T000001"}
|
assert theRefs == {nHandle: "T0001"}
|
||||||
|
|
||||||
# getTagSource
|
# getTagSource
|
||||||
# ============
|
# ============
|
||||||
|
|
||||||
assert theIndex.getTagSource("Jane") == (cHandle, "T000001")
|
assert theIndex.getTagSource("Jane") == (cHandle, "T0001")
|
||||||
assert theIndex.getTagSource("John") == (None, "T000000")
|
assert theIndex.getTagSource("John") == (None, "T0000")
|
||||||
|
|
||||||
# getCounts
|
# getCounts
|
||||||
# =========
|
# =========
|
||||||
@@ -632,13 +650,13 @@ def testCoreIndex_ExtractData(mockGUI, fncPath, mockRnd):
|
|||||||
assert pC == 4
|
assert pC == 4
|
||||||
|
|
||||||
# First part
|
# First part
|
||||||
cC, wC, pC = theIndex.getCounts(nHandle, "T000001")
|
cC, wC, pC = theIndex.getCounts(nHandle, "T0001")
|
||||||
assert cC == 62
|
assert cC == 62
|
||||||
assert wC == 12
|
assert wC == 12
|
||||||
assert pC == 2
|
assert pC == 2
|
||||||
|
|
||||||
# Second part
|
# Second part
|
||||||
cC, wC, pC = theIndex.getCounts(nHandle, "T000011")
|
cC, wC, pC = theIndex.getCounts(nHandle, "T0002")
|
||||||
assert cC == 90
|
assert cC == 90
|
||||||
assert wC == 16
|
assert wC == 16
|
||||||
assert pC == 2
|
assert pC == 2
|
||||||
@@ -665,13 +683,13 @@ def testCoreIndex_ExtractData(mockGUI, fncPath, mockRnd):
|
|||||||
assert pC == 4
|
assert pC == 4
|
||||||
|
|
||||||
# First part
|
# First part
|
||||||
cC, wC, pC = theIndex.getCounts(cHandle, "T000001")
|
cC, wC, pC = theIndex.getCounts(cHandle, "T0001")
|
||||||
assert cC == 62
|
assert cC == 62
|
||||||
assert wC == 12
|
assert wC == 12
|
||||||
assert pC == 2
|
assert pC == 2
|
||||||
|
|
||||||
# Second part
|
# Second part
|
||||||
cC, wC, pC = theIndex.getCounts(cHandle, "T000011")
|
cC, wC, pC = theIndex.getCounts(cHandle, "T0002")
|
||||||
assert cC == 90
|
assert cC == 90
|
||||||
assert wC == 16
|
assert wC == 16
|
||||||
assert pC == 2
|
assert pC == 2
|
||||||
@@ -692,36 +710,36 @@ def testCoreIndex_ExtractData(mockGUI, fncPath, mockRnd):
|
|||||||
assert theIndex.scanText(tHandle, "### Scene Two\n\n")
|
assert theIndex.scanText(tHandle, "### Scene Two\n\n")
|
||||||
|
|
||||||
assert [(h, t) for h, t, _ in theIndex._itemIndex.iterNovelStructure(skipExcl=False)] == [
|
assert [(h, t) for h, t, _ in theIndex._itemIndex.iterNovelStructure(skipExcl=False)] == [
|
||||||
(C.hTitlePage, "T000001"),
|
(C.hTitlePage, "T0001"),
|
||||||
(C.hChapterDoc, "T000001"),
|
(C.hChapterDoc, "T0001"),
|
||||||
(C.hSceneDoc, "T000001"),
|
(C.hSceneDoc, "T0001"),
|
||||||
(nHandle, "T000001"),
|
(nHandle, "T0001"),
|
||||||
(nHandle, "T000011"),
|
(nHandle, "T0002"),
|
||||||
(hHandle, "T000001"),
|
(hHandle, "T0001"),
|
||||||
(sHandle, "T000001"),
|
(sHandle, "T0001"),
|
||||||
(tHandle, "T000001"),
|
(tHandle, "T0001"),
|
||||||
]
|
]
|
||||||
|
|
||||||
assert [(h, t) for h, t, _ in theIndex._itemIndex.iterNovelStructure(skipExcl=True)] == [
|
assert [(h, t) for h, t, _ in theIndex._itemIndex.iterNovelStructure(skipExcl=True)] == [
|
||||||
(C.hTitlePage, "T000001"),
|
(C.hTitlePage, "T0001"),
|
||||||
(C.hChapterDoc, "T000001"),
|
(C.hChapterDoc, "T0001"),
|
||||||
(C.hSceneDoc, "T000001"),
|
(C.hSceneDoc, "T0001"),
|
||||||
(hHandle, "T000001"),
|
(hHandle, "T0001"),
|
||||||
(sHandle, "T000001"),
|
(sHandle, "T0001"),
|
||||||
(tHandle, "T000001"),
|
(tHandle, "T0001"),
|
||||||
]
|
]
|
||||||
|
|
||||||
# Add a fake handle to the tree and check that it's ignored
|
# Add a fake handle to the tree and check that it's ignored
|
||||||
theProject.tree._treeOrder.append("0000000000000")
|
theProject.tree._treeOrder.append("0000000000000")
|
||||||
assert [(h, t) for h, t, _ in theIndex._itemIndex.iterNovelStructure(skipExcl=False)] == [
|
assert [(h, t) for h, t, _ in theIndex._itemIndex.iterNovelStructure(skipExcl=False)] == [
|
||||||
(C.hTitlePage, "T000001"),
|
(C.hTitlePage, "T0001"),
|
||||||
(C.hChapterDoc, "T000001"),
|
(C.hChapterDoc, "T0001"),
|
||||||
(C.hSceneDoc, "T000001"),
|
(C.hSceneDoc, "T0001"),
|
||||||
(nHandle, "T000001"),
|
(nHandle, "T0001"),
|
||||||
(nHandle, "T000011"),
|
(nHandle, "T0002"),
|
||||||
(hHandle, "T000001"),
|
(hHandle, "T0001"),
|
||||||
(sHandle, "T000001"),
|
(sHandle, "T0001"),
|
||||||
(tHandle, "T000001"),
|
(tHandle, "T0001"),
|
||||||
]
|
]
|
||||||
theProject.tree._treeOrder.remove("0000000000000")
|
theProject.tree._treeOrder.remove("0000000000000")
|
||||||
|
|
||||||
@@ -734,52 +752,31 @@ def testCoreIndex_ExtractData(mockGUI, fncPath, mockRnd):
|
|||||||
# Table of Contents
|
# Table of Contents
|
||||||
assert theIndex.getTableOfContents(C.hNovelRoot, 0, skipExcl=True) == []
|
assert theIndex.getTableOfContents(C.hNovelRoot, 0, skipExcl=True) == []
|
||||||
assert theIndex.getTableOfContents(C.hNovelRoot, 1, skipExcl=True) == [
|
assert theIndex.getTableOfContents(C.hNovelRoot, 1, skipExcl=True) == [
|
||||||
(f"{C.hTitlePage}:T000001", 1, "New Novel", 15),
|
(f"{C.hTitlePage}:T0001", 1, "New Novel", 15),
|
||||||
]
|
]
|
||||||
assert theIndex.getTableOfContents(C.hNovelRoot, 2, skipExcl=True) == [
|
assert theIndex.getTableOfContents(C.hNovelRoot, 2, skipExcl=True) == [
|
||||||
(f"{C.hTitlePage}:T000001", 1, "New Novel", 5),
|
(f"{C.hTitlePage}:T0001", 1, "New Novel", 5),
|
||||||
(f"{C.hChapterDoc}:T000001", 2, "New Chapter", 4),
|
(f"{C.hChapterDoc}:T0001", 2, "New Chapter", 4),
|
||||||
(f"{hHandle}:T000001", 2, "Chapter One", 6),
|
(f"{hHandle}:T0001", 2, "Chapter One", 6),
|
||||||
]
|
]
|
||||||
assert theIndex.getTableOfContents(C.hNovelRoot, 3, skipExcl=True) == [
|
assert theIndex.getTableOfContents(C.hNovelRoot, 3, skipExcl=True) == [
|
||||||
(f"{C.hTitlePage}:T000001", 1, "New Novel", 5),
|
(f"{C.hTitlePage}:T0001", 1, "New Novel", 5),
|
||||||
(f"{C.hChapterDoc}:T000001", 2, "New Chapter", 2),
|
(f"{C.hChapterDoc}:T0001", 2, "New Chapter", 2),
|
||||||
(f"{C.hSceneDoc}:T000001", 3, "New Scene", 2),
|
(f"{C.hSceneDoc}:T0001", 3, "New Scene", 2),
|
||||||
(f"{hHandle}:T000001", 2, "Chapter One", 2),
|
(f"{hHandle}:T0001", 2, "Chapter One", 2),
|
||||||
(f"{sHandle}:T000001", 3, "Scene One", 2),
|
(f"{sHandle}:T0001", 3, "Scene One", 2),
|
||||||
(f"{tHandle}:T000001", 3, "Scene Two", 2),
|
(f"{tHandle}:T0001", 3, "Scene Two", 2),
|
||||||
]
|
]
|
||||||
|
|
||||||
assert theIndex.getTableOfContents(C.hNovelRoot, 0, skipExcl=False) == []
|
assert theIndex.getTableOfContents(C.hNovelRoot, 0, skipExcl=False) == []
|
||||||
assert theIndex.getTableOfContents(C.hNovelRoot, 1, skipExcl=False) == [
|
assert theIndex.getTableOfContents(C.hNovelRoot, 1, skipExcl=False) == [
|
||||||
(f"{C.hTitlePage}:T000001", 1, "New Novel", 9),
|
(f"{C.hTitlePage}:T0001", 1, "New Novel", 9),
|
||||||
(f"{nHandle}:T000001", 1, "Hello World!", 12),
|
(f"{nHandle}:T0001", 1, "Hello World!", 12),
|
||||||
(f"{nHandle}:T000011", 1, "Hello World!", 22),
|
(f"{nHandle}:T0002", 1, "Hello World!", 22),
|
||||||
]
|
|
||||||
|
|
||||||
# Header Word Counts
|
|
||||||
bHandle = "0000000000000"
|
|
||||||
assert theIndex.getHandleWordCounts(bHandle) == []
|
|
||||||
assert theIndex.getHandleWordCounts(hHandle) == [("%s:T000001" % hHandle, 2)]
|
|
||||||
assert theIndex.getHandleWordCounts(sHandle) == [("%s:T000001" % sHandle, 2)]
|
|
||||||
assert theIndex.getHandleWordCounts(tHandle) == [("%s:T000001" % tHandle, 2)]
|
|
||||||
assert theIndex.getHandleWordCounts(nHandle) == [
|
|
||||||
(f"{nHandle}:T000001", 12), (f"{nHandle}:T000011", 16)
|
|
||||||
]
|
]
|
||||||
|
|
||||||
assert theIndex.saveIndex() is True
|
assert theIndex.saveIndex() is True
|
||||||
assert theProject.saveProject() is True
|
assert theProject.saveProject() is True
|
||||||
|
|
||||||
# Header Record
|
|
||||||
bHandle = "0000000000000"
|
|
||||||
assert theIndex.getHandleHeaders(bHandle) == []
|
|
||||||
assert theIndex.getHandleHeaders(hHandle) == [("T000001", "H2", "Chapter One")]
|
|
||||||
assert theIndex.getHandleHeaders(sHandle) == [("T000001", "H3", "Scene One")]
|
|
||||||
assert theIndex.getHandleHeaders(tHandle) == [("T000001", "H3", "Scene Two")]
|
|
||||||
assert theIndex.getHandleHeaders(nHandle) == [
|
|
||||||
("T000001", "H1", "Hello World!"), ("T000011", "H1", "Hello World!")
|
|
||||||
]
|
|
||||||
|
|
||||||
assert theProject.closeProject() is True
|
assert theProject.closeProject() is True
|
||||||
|
|
||||||
# END Test testCoreIndex_ExtractData
|
# END Test testCoreIndex_ExtractData
|
||||||
@@ -796,25 +793,25 @@ def testCoreIndex_TagsIndex():
|
|||||||
content = {
|
content = {
|
||||||
"Tag1": {
|
"Tag1": {
|
||||||
"handle": "0000000000001",
|
"handle": "0000000000001",
|
||||||
"heading": "T000001",
|
"heading": "T0001",
|
||||||
"class": nwItemClass.NOVEL.name,
|
"class": nwItemClass.NOVEL.name,
|
||||||
},
|
},
|
||||||
"Tag2": {
|
"Tag2": {
|
||||||
"handle": "0000000000002",
|
"handle": "0000000000002",
|
||||||
"heading": "T000002",
|
"heading": "T0002",
|
||||||
"class": nwItemClass.CHARACTER.name,
|
"class": nwItemClass.CHARACTER.name,
|
||||||
},
|
},
|
||||||
"Tag3": {
|
"Tag3": {
|
||||||
"handle": "0000000000003",
|
"handle": "0000000000003",
|
||||||
"heading": "T000003",
|
"heading": "T0003",
|
||||||
"class": nwItemClass.PLOT.name,
|
"class": nwItemClass.PLOT.name,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
# Add data
|
# Add data
|
||||||
tagsIndex.add("Tag1", "0000000000001", "T000001", nwItemClass.NOVEL)
|
tagsIndex.add("Tag1", "0000000000001", "T0001", nwItemClass.NOVEL)
|
||||||
tagsIndex.add("Tag2", "0000000000002", "T000002", nwItemClass.CHARACTER)
|
tagsIndex.add("Tag2", "0000000000002", "T0002", nwItemClass.CHARACTER)
|
||||||
tagsIndex.add("Tag3", "0000000000003", "T000003", nwItemClass.PLOT)
|
tagsIndex.add("Tag3", "0000000000003", "T0003", nwItemClass.PLOT)
|
||||||
assert tagsIndex._tags == content
|
assert tagsIndex._tags == content
|
||||||
|
|
||||||
# Get items
|
# Get items
|
||||||
@@ -836,10 +833,10 @@ def testCoreIndex_TagsIndex():
|
|||||||
assert tagsIndex.tagHandle("Tag4") is None
|
assert tagsIndex.tagHandle("Tag4") is None
|
||||||
|
|
||||||
# Read back headings
|
# Read back headings
|
||||||
assert tagsIndex.tagHeading("Tag1") == "T000001"
|
assert tagsIndex.tagHeading("Tag1") == "T0001"
|
||||||
assert tagsIndex.tagHeading("Tag2") == "T000002"
|
assert tagsIndex.tagHeading("Tag2") == "T0002"
|
||||||
assert tagsIndex.tagHeading("Tag3") == "T000003"
|
assert tagsIndex.tagHeading("Tag3") == "T0003"
|
||||||
assert tagsIndex.tagHeading("Tag4") == "T000000"
|
assert tagsIndex.tagHeading("Tag4") == "T0000"
|
||||||
|
|
||||||
# Read back classes
|
# Read back classes
|
||||||
assert tagsIndex.tagClass("Tag1") == nwItemClass.NOVEL.name
|
assert tagsIndex.tagClass("Tag1") == nwItemClass.NOVEL.name
|
||||||
@@ -880,7 +877,7 @@ def testCoreIndex_TagsIndex():
|
|||||||
tagsIndex.unpackData({
|
tagsIndex.unpackData({
|
||||||
1234: {
|
1234: {
|
||||||
"handle": "0000000000001",
|
"handle": "0000000000001",
|
||||||
"heading": "T000001",
|
"heading": "T0001",
|
||||||
"class": "NOVEL",
|
"class": "NOVEL",
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -889,7 +886,7 @@ def testCoreIndex_TagsIndex():
|
|||||||
with pytest.raises(KeyError):
|
with pytest.raises(KeyError):
|
||||||
tagsIndex.unpackData({
|
tagsIndex.unpackData({
|
||||||
"Tag1": {
|
"Tag1": {
|
||||||
"heading": "T000001",
|
"heading": "T0001",
|
||||||
"class": "NOVEL",
|
"class": "NOVEL",
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -908,7 +905,7 @@ def testCoreIndex_TagsIndex():
|
|||||||
tagsIndex.unpackData({
|
tagsIndex.unpackData({
|
||||||
"Tag1": {
|
"Tag1": {
|
||||||
"handle": "0000000000001",
|
"handle": "0000000000001",
|
||||||
"heading": "T000001",
|
"heading": "T0001",
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -917,7 +914,7 @@ def testCoreIndex_TagsIndex():
|
|||||||
tagsIndex.unpackData({
|
tagsIndex.unpackData({
|
||||||
"Tag1": {
|
"Tag1": {
|
||||||
"handle": "blablabla",
|
"handle": "blablabla",
|
||||||
"heading": "T000001",
|
"heading": "T0001",
|
||||||
"class": "NOVEL",
|
"class": "NOVEL",
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -937,7 +934,7 @@ def testCoreIndex_TagsIndex():
|
|||||||
tagsIndex.unpackData({
|
tagsIndex.unpackData({
|
||||||
"Tag1": {
|
"Tag1": {
|
||||||
"handle": "0000000000001",
|
"handle": "0000000000001",
|
||||||
"heading": "T000001",
|
"heading": "T0001",
|
||||||
"class": "blabla",
|
"class": "blabla",
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -975,66 +972,70 @@ def testCoreIndex_ItemIndex(mockGUI, fncPath, mockRnd):
|
|||||||
assert cHandle in itemIndex
|
assert cHandle in itemIndex
|
||||||
assert itemIndex[cHandle].item == theProject.tree[cHandle]
|
assert itemIndex[cHandle].item == theProject.tree[cHandle]
|
||||||
assert itemIndex.allItemTags(cHandle) == []
|
assert itemIndex.allItemTags(cHandle) == []
|
||||||
assert list(itemIndex.iterItemHeaders(cHandle))[0][0] == "T000000"
|
assert list(itemIndex.iterItemHeaders(cHandle))[0][0] == "T0000"
|
||||||
|
|
||||||
# Add a heading to the item, which should replace the T000000 heading
|
# Add a heading to the item, which should replace the T000000 heading
|
||||||
itemIndex.addItemHeading(cHandle, "T000001", "H2", "Chapter One")
|
assert itemIndex.addItemHeading(cHandle, 1, "H2", "Chapter One") == "T0001"
|
||||||
assert list(itemIndex.iterItemHeaders(cHandle))[0][0] == "T000001"
|
assert list(itemIndex.iterItemHeaders(cHandle))[0][0] == "T0001"
|
||||||
|
|
||||||
|
# Add a heading to an invalid item
|
||||||
|
assert itemIndex.addItemHeading(C.hInvalid, 1, "H1", "Stuff") == "T0000"
|
||||||
|
|
||||||
# Set the remainig data values
|
# Set the remainig data values
|
||||||
itemIndex.setHeadingCounts(cHandle, "T000001", 60, 10, 2)
|
itemIndex.setHeadingCounts(cHandle, "T0001", 60, 10, 2)
|
||||||
itemIndex.setHeadingSynopsis(cHandle, "T000001", "In the beginning ...")
|
itemIndex.setHeadingSynopsis(cHandle, "T0001", "In the beginning ...")
|
||||||
itemIndex.setHeadingTag(cHandle, "T000001", "One")
|
itemIndex.setHeadingTag(cHandle, "T0001", "One")
|
||||||
itemIndex.addHeadingReferences(cHandle, "T000001", ["Jane"], "@pov")
|
itemIndex.addHeadingReferences(cHandle, "T0001", ["Jane"], "@pov")
|
||||||
itemIndex.addHeadingReferences(cHandle, "T000001", ["Jane"], "@focus")
|
itemIndex.addHeadingReferences(cHandle, "T0001", ["Jane"], "@focus")
|
||||||
itemIndex.addHeadingReferences(cHandle, "T000001", ["Jane", "John"], "@char")
|
itemIndex.addHeadingReferences(cHandle, "T0001", ["Jane", "John"], "@char")
|
||||||
idxData = itemIndex.packData()
|
idxData = itemIndex.packData()
|
||||||
|
|
||||||
assert idxData[cHandle]["headings"]["T000001"] == {
|
assert idxData[cHandle]["headings"]["T0001"] == {
|
||||||
"level": "H2", "title": "Chapter One", "tag": "One",
|
"level": "H2", "line": 1, "title": "Chapter One", "tag": "One",
|
||||||
"cCount": 60, "wCount": 10, "pCount": 2, "synopsis": "In the beginning ...",
|
"cCount": 60, "wCount": 10, "pCount": 2, "synopsis": "In the beginning ...",
|
||||||
}
|
}
|
||||||
assert "@pov" in idxData[cHandle]["references"]["T000001"]["Jane"]
|
assert "@pov" in idxData[cHandle]["references"]["T0001"]["Jane"]
|
||||||
assert "@focus" in idxData[cHandle]["references"]["T000001"]["Jane"]
|
assert "@focus" in idxData[cHandle]["references"]["T0001"]["Jane"]
|
||||||
assert "@char" in idxData[cHandle]["references"]["T000001"]["Jane"]
|
assert "@char" in idxData[cHandle]["references"]["T0001"]["Jane"]
|
||||||
assert "@char" in idxData[cHandle]["references"]["T000001"]["John"]
|
assert "@char" in idxData[cHandle]["references"]["T0001"]["John"]
|
||||||
|
|
||||||
# Add the other two files
|
# Add the other two files
|
||||||
itemIndex.add(nHandle, theProject.tree[nHandle])
|
itemIndex.add(nHandle, theProject.tree[nHandle])
|
||||||
itemIndex.add(sHandle, theProject.tree[sHandle])
|
itemIndex.add(sHandle, theProject.tree[sHandle])
|
||||||
itemIndex.addItemHeading(nHandle, "T000001", "H1", "Novel")
|
itemIndex.addItemHeading(nHandle, 1, "H1", "Novel")
|
||||||
itemIndex.addItemHeading(sHandle, "T000001", "H3", "Scene One")
|
itemIndex.addItemHeading(sHandle, 1, "H3", "Scene One")
|
||||||
|
|
||||||
# Check Item and Heading Direct Access
|
# Check Item and Heading Direct Access
|
||||||
# ====================================
|
# ====================================
|
||||||
|
|
||||||
# Check repr strings
|
# Check repr strings
|
||||||
assert repr(itemIndex[nHandle]) == f"<IndexItem handle='{nHandle}'>"
|
assert repr(itemIndex[nHandle]) == f"<IndexItem handle='{nHandle}'>"
|
||||||
assert repr(itemIndex[nHandle]["T000001"]) == "<IndexHeading key='T000001'>"
|
assert repr(itemIndex[nHandle]["T0001"]) == "<IndexHeading key='T0001'>"
|
||||||
|
|
||||||
# Check content of a single item
|
# Check content of a single item
|
||||||
assert "T000001" in itemIndex[nHandle]
|
assert "T0001" in itemIndex[nHandle]
|
||||||
assert itemIndex[cHandle].allTags() == ["One"]
|
assert itemIndex[cHandle].allTags() == ["One"]
|
||||||
|
|
||||||
# Check the content of a single heading
|
# Check the content of a single heading
|
||||||
assert itemIndex[cHandle]["T000001"].key == "T000001"
|
assert itemIndex[cHandle]["T0001"].key == "T0001"
|
||||||
assert itemIndex[cHandle]["T000001"].level == "H2"
|
assert itemIndex[cHandle]["T0001"].level == "H2"
|
||||||
assert itemIndex[cHandle]["T000001"].title == "Chapter One"
|
assert itemIndex[cHandle]["T0001"].line == 1
|
||||||
assert itemIndex[cHandle]["T000001"].tag == "One"
|
assert itemIndex[cHandle]["T0001"].title == "Chapter One"
|
||||||
assert itemIndex[cHandle]["T000001"].charCount == 60
|
assert itemIndex[cHandle]["T0001"].tag == "One"
|
||||||
assert itemIndex[cHandle]["T000001"].wordCount == 10
|
assert itemIndex[cHandle]["T0001"].charCount == 60
|
||||||
assert itemIndex[cHandle]["T000001"].paraCount == 2
|
assert itemIndex[cHandle]["T0001"].wordCount == 10
|
||||||
assert itemIndex[cHandle]["T000001"].synopsis == "In the beginning ..."
|
assert itemIndex[cHandle]["T0001"].paraCount == 2
|
||||||
assert "Jane" in itemIndex[cHandle]["T000001"].references
|
assert itemIndex[cHandle]["T0001"].synopsis == "In the beginning ..."
|
||||||
assert "John" in itemIndex[cHandle]["T000001"].references
|
assert "Jane" in itemIndex[cHandle]["T0001"].references
|
||||||
|
assert "John" in itemIndex[cHandle]["T0001"].references
|
||||||
|
|
||||||
# Check heading level setter
|
# Check heading level setter
|
||||||
itemIndex[cHandle]["T000001"].setLevel("H3") # Change it
|
itemIndex[cHandle]["T0001"].setLevel("H3") # Change it
|
||||||
assert itemIndex[cHandle]["T000001"].level == "H3"
|
assert itemIndex[cHandle]["T0001"].level == "H3"
|
||||||
itemIndex[cHandle]["T000001"].setLevel("H2") # Set it back
|
itemIndex[cHandle]["T0001"].setLevel("H2") # Set it back
|
||||||
assert itemIndex[cHandle]["T000001"].level == "H2"
|
assert itemIndex[cHandle]["T0001"].level == "H2"
|
||||||
itemIndex[cHandle]["T000001"].setLevel("H5") # Invalid level
|
itemIndex[cHandle]["T0001"].setLevel("H5") # Invalid level
|
||||||
assert itemIndex[cHandle]["T000001"].level == "H2"
|
assert itemIndex[cHandle]["T0001"].level == "H2"
|
||||||
|
|
||||||
# Data Extraction
|
# Data Extraction
|
||||||
# ===============
|
# ===============
|
||||||
@@ -1044,9 +1045,9 @@ def testCoreIndex_ItemIndex(mockGUI, fncPath, mockRnd):
|
|||||||
assert allHeads[0][0] == cHandle
|
assert allHeads[0][0] == cHandle
|
||||||
assert allHeads[1][0] == nHandle
|
assert allHeads[1][0] == nHandle
|
||||||
assert allHeads[2][0] == sHandle
|
assert allHeads[2][0] == sHandle
|
||||||
assert allHeads[0][1] == "T000001"
|
assert allHeads[0][1] == "T0001"
|
||||||
assert allHeads[1][1] == "T000001"
|
assert allHeads[1][1] == "T0001"
|
||||||
assert allHeads[2][1] == "T000001"
|
assert allHeads[2][1] == "T0001"
|
||||||
|
|
||||||
# Ask for stuff that doesn't exist
|
# Ask for stuff that doesn't exist
|
||||||
assert itemIndex.allItemTags("blablabla") == []
|
assert itemIndex.allItemTags("blablabla") == []
|
||||||
@@ -1058,7 +1059,7 @@ def testCoreIndex_ItemIndex(mockGUI, fncPath, mockRnd):
|
|||||||
mHandle = theProject.newRoot(nwItemClass.NOVEL)
|
mHandle = theProject.newRoot(nwItemClass.NOVEL)
|
||||||
uHandle = theProject.newFile("Title Page", mHandle)
|
uHandle = theProject.newFile("Title Page", mHandle)
|
||||||
itemIndex.add(uHandle, theProject.tree[uHandle])
|
itemIndex.add(uHandle, theProject.tree[uHandle])
|
||||||
itemIndex.addItemHeading(uHandle, "T000001", "H1", "Novel 2")
|
itemIndex.addItemHeading(uHandle, "T0001", "H1", "Novel 2")
|
||||||
assert uHandle in itemIndex
|
assert uHandle in itemIndex
|
||||||
|
|
||||||
# Structure of all novels
|
# Structure of all novels
|
||||||
@@ -1134,20 +1135,20 @@ def testCoreIndex_ItemIndex(mockGUI, fncPath, mockRnd):
|
|||||||
# Reference without a heading should be rejected
|
# Reference without a heading should be rejected
|
||||||
itemIndex.unpackData({
|
itemIndex.unpackData({
|
||||||
cHandle: {
|
cHandle: {
|
||||||
"headings": {"T000001": {}},
|
"headings": {"T0001": {}},
|
||||||
"references": {"T000001": {}, "T000002": {}},
|
"references": {"T0001": {}, "T0002": {}},
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
assert "T000001" in itemIndex[cHandle]
|
assert "T0001" in itemIndex[cHandle]
|
||||||
assert "T000002" not in itemIndex[cHandle]
|
assert "T0002" not in itemIndex[cHandle]
|
||||||
itemIndex.clear()
|
itemIndex.clear()
|
||||||
|
|
||||||
# Tag keys must be strings
|
# Tag keys must be strings
|
||||||
with pytest.raises(ValueError):
|
with pytest.raises(ValueError):
|
||||||
itemIndex.unpackData({
|
itemIndex.unpackData({
|
||||||
cHandle: {
|
cHandle: {
|
||||||
"headings": {"T000001": {}},
|
"headings": {"T0001": {}},
|
||||||
"references": {"T000001": {1234: "@pov"}},
|
"references": {"T0001": {1234: "@pov"}},
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -1155,8 +1156,8 @@ def testCoreIndex_ItemIndex(mockGUI, fncPath, mockRnd):
|
|||||||
with pytest.raises(ValueError):
|
with pytest.raises(ValueError):
|
||||||
itemIndex.unpackData({
|
itemIndex.unpackData({
|
||||||
cHandle: {
|
cHandle: {
|
||||||
"headings": {"T000001": {}},
|
"headings": {"T0001": {}},
|
||||||
"references": {"T000001": {"John": []}},
|
"references": {"T0001": {"John": []}},
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -1164,16 +1165,16 @@ def testCoreIndex_ItemIndex(mockGUI, fncPath, mockRnd):
|
|||||||
with pytest.raises(ValueError):
|
with pytest.raises(ValueError):
|
||||||
itemIndex.unpackData({
|
itemIndex.unpackData({
|
||||||
cHandle: {
|
cHandle: {
|
||||||
"headings": {"T000001": {}},
|
"headings": {"T0001": {}},
|
||||||
"references": {"T000001": {"John": "@pov,@char,@stuff"}},
|
"references": {"T0001": {"John": "@pov,@char,@stuff"}},
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
# This should pass
|
# This should pass
|
||||||
itemIndex.unpackData({
|
itemIndex.unpackData({
|
||||||
cHandle: {
|
cHandle: {
|
||||||
"headings": {"T000001": {}},
|
"headings": {"T0001": {}},
|
||||||
"references": {"T000001": {"John": "@pov,@char"}},
|
"references": {"T0001": {"John": "@pov,@char"}},
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -118,10 +118,10 @@ def testGuiEditor_LoadText(qtbot, monkeypatch, caplog, nwGUI, projPath, ipsumTex
|
|||||||
assert nwGUI.docEditor.loadText(C.hSceneDoc) is True
|
assert nwGUI.docEditor.loadText(C.hSceneDoc) is True
|
||||||
assert nwGUI.docEditor._bigDoc is True
|
assert nwGUI.docEditor._bigDoc is True
|
||||||
|
|
||||||
# Regular open, with line number
|
# Regular open, with line number (1 indexed)
|
||||||
assert nwGUI.docEditor.loadText(C.hSceneDoc, tLine=4) is True
|
assert nwGUI.docEditor.loadText(C.hSceneDoc, tLine=4) is True
|
||||||
cursPos = nwGUI.docEditor.getCursorPosition()
|
cursPos = nwGUI.docEditor.getCursorPosition()
|
||||||
assert nwGUI.docEditor.document().findBlock(cursPos).blockNumber() == 4
|
assert nwGUI.docEditor.document().findBlock(cursPos).blockNumber() == 3
|
||||||
|
|
||||||
# Load empty document
|
# Load empty document
|
||||||
nwGUI.docEditor.replaceText("")
|
nwGUI.docEditor.replaceText("")
|
||||||
|
|||||||
@@ -57,7 +57,6 @@ def testGuiMain_ProjectBlocker(nwGUI):
|
|||||||
assert nwGUI.importDocument() is False
|
assert nwGUI.importDocument() is False
|
||||||
assert nwGUI.openSelectedItem() is False
|
assert nwGUI.openSelectedItem() is False
|
||||||
assert nwGUI.editItemLabel() is False
|
assert nwGUI.editItemLabel() is False
|
||||||
assert nwGUI.requestNovelTreeRefresh() is False
|
|
||||||
assert nwGUI.rebuildIndex() is False
|
assert nwGUI.rebuildIndex() is False
|
||||||
assert nwGUI.showProjectSettingsDialog() is False
|
assert nwGUI.showProjectSettingsDialog() is False
|
||||||
assert nwGUI.showProjectDetailsDialog() is False
|
assert nwGUI.showProjectDetailsDialog() is False
|
||||||
|
|||||||
@@ -92,7 +92,7 @@ def testGuiNovelTree_TreeItems(qtbot, monkeypatch, nwGUI, projPath, mockRnd):
|
|||||||
assert not topItem.isSelected()
|
assert not topItem.isSelected()
|
||||||
topItem.setSelected(True)
|
topItem.setSelected(True)
|
||||||
assert novelTree.selectedItems()[0] == topItem
|
assert novelTree.selectedItems()[0] == topItem
|
||||||
assert novelView.getSelectedHandle() == (C.hTitlePage, 0)
|
assert novelView.getSelectedHandle() == (C.hTitlePage, "T0001")
|
||||||
|
|
||||||
# Refresh using the slot for the butoom
|
# Refresh using the slot for the butoom
|
||||||
novelBar._refreshNovelTree()
|
novelBar._refreshNovelTree()
|
||||||
@@ -142,31 +142,31 @@ def testGuiNovelTree_TreeItems(qtbot, monkeypatch, nwGUI, projPath, mockRnd):
|
|||||||
novelBar.setLastColType(NovelTreeColumn.HIDDEN)
|
novelBar.setLastColType(NovelTreeColumn.HIDDEN)
|
||||||
assert novelTree.isColumnHidden(novelTree.C_EXTRA) is True
|
assert novelTree.isColumnHidden(novelTree.C_EXTRA) is True
|
||||||
assert novelTree.lastColType == NovelTreeColumn.HIDDEN
|
assert novelTree.lastColType == NovelTreeColumn.HIDDEN
|
||||||
assert novelTree._getLastColumnText(C.hSceneDoc, "T000001") == ("", "")
|
assert novelTree._getLastColumnText(C.hSceneDoc, "T0001") == ("", "")
|
||||||
|
|
||||||
novelBar.setLastColType(NovelTreeColumn.POV)
|
novelBar.setLastColType(NovelTreeColumn.POV)
|
||||||
assert novelTree.isColumnHidden(novelTree.C_EXTRA) is False
|
assert novelTree.isColumnHidden(novelTree.C_EXTRA) is False
|
||||||
assert novelTree.lastColType == NovelTreeColumn.POV
|
assert novelTree.lastColType == NovelTreeColumn.POV
|
||||||
assert novelTree._getLastColumnText(C.hSceneDoc, "T000001") == (
|
assert novelTree._getLastColumnText(C.hSceneDoc, "T0001") == (
|
||||||
"Jane", "Point of View: Jane"
|
"Jane", "Point of View: Jane"
|
||||||
)
|
)
|
||||||
|
|
||||||
novelBar.setLastColType(NovelTreeColumn.FOCUS)
|
novelBar.setLastColType(NovelTreeColumn.FOCUS)
|
||||||
assert novelTree.isColumnHidden(novelTree.C_EXTRA) is False
|
assert novelTree.isColumnHidden(novelTree.C_EXTRA) is False
|
||||||
assert novelTree.lastColType == NovelTreeColumn.FOCUS
|
assert novelTree.lastColType == NovelTreeColumn.FOCUS
|
||||||
assert novelTree._getLastColumnText(C.hSceneDoc, "T000001") == (
|
assert novelTree._getLastColumnText(C.hSceneDoc, "T0001") == (
|
||||||
"Jane", "Focus: Jane"
|
"Jane", "Focus: Jane"
|
||||||
)
|
)
|
||||||
|
|
||||||
novelBar.setLastColType(NovelTreeColumn.PLOT)
|
novelBar.setLastColType(NovelTreeColumn.PLOT)
|
||||||
assert novelTree.isColumnHidden(novelTree.C_EXTRA) is False
|
assert novelTree.isColumnHidden(novelTree.C_EXTRA) is False
|
||||||
assert novelTree.lastColType == NovelTreeColumn.PLOT
|
assert novelTree.lastColType == NovelTreeColumn.PLOT
|
||||||
assert novelTree._getLastColumnText(C.hSceneDoc, "T000001") == (
|
assert novelTree._getLastColumnText(C.hSceneDoc, "T0001") == (
|
||||||
"", "Plot: "
|
"", "Plot: "
|
||||||
)
|
)
|
||||||
|
|
||||||
novelTree._lastCol = None
|
novelTree._lastCol = None
|
||||||
assert novelTree._getLastColumnText("0000000000000", "T000000") == ("", "")
|
assert novelTree._getLastColumnText("0000000000000", "T0000") == ("", "")
|
||||||
|
|
||||||
# Item Meta
|
# Item Meta
|
||||||
# =========
|
# =========
|
||||||
|
|||||||
@@ -225,9 +225,9 @@ def testGuiOutline_Content(qtbot, nwGUI, nwLipsum):
|
|||||||
selItem = outlineTree.topLevelItem(4)
|
selItem = outlineTree.topLevelItem(4)
|
||||||
|
|
||||||
outlineTree.setCurrentItem(selItem)
|
outlineTree.setCurrentItem(selItem)
|
||||||
tHandle, tLine = outlineTree.getSelectedHandle()
|
tHandle, sTitle = outlineTree.getSelectedHandle()
|
||||||
assert tHandle == "88243afbe5ed8"
|
assert tHandle == "88243afbe5ed8"
|
||||||
assert tLine == 0
|
assert sTitle == "T0001"
|
||||||
|
|
||||||
assert outlineData.titleLabel.text() == "<b>Scene</b>"
|
assert outlineData.titleLabel.text() == "<b>Scene</b>"
|
||||||
assert outlineData.titleValue.text() == "Scene One"
|
assert outlineData.titleValue.text() == "Scene One"
|
||||||
@@ -243,9 +243,9 @@ def testGuiOutline_Content(qtbot, nwGUI, nwLipsum):
|
|||||||
selItem = outlineTree.topLevelItem(5)
|
selItem = outlineTree.topLevelItem(5)
|
||||||
|
|
||||||
outlineTree.setCurrentItem(selItem)
|
outlineTree.setCurrentItem(selItem)
|
||||||
tHandle, tLine = outlineTree.getSelectedHandle()
|
tHandle, sTitle = outlineTree.getSelectedHandle()
|
||||||
assert tHandle == "88243afbe5ed8"
|
assert tHandle == "88243afbe5ed8"
|
||||||
assert tLine == 12
|
assert sTitle == "T0002"
|
||||||
|
|
||||||
assert outlineData.titleLabel.text() == "<b>Section</b>"
|
assert outlineData.titleLabel.text() == "<b>Section</b>"
|
||||||
assert outlineData.titleValue.text() == "Scene One, Section Two"
|
assert outlineData.titleValue.text() == "Scene One, Section Two"
|
||||||
|
|||||||
Reference in New Issue
Block a user