Case insensitive tags (#1522)
This commit is contained in:
+152
-144
@@ -151,11 +151,13 @@ class NWIndex:
|
|||||||
"""Check if the index has changed since a given time."""
|
"""Check if the index has changed since a given time."""
|
||||||
return self._indexChange > float(checkTime)
|
return self._indexChange > float(checkTime)
|
||||||
|
|
||||||
def rootChangedSince(self, rootHandle: str, checkTime: int | float) -> bool:
|
def rootChangedSince(self, rootHandle: str | None, checkTime: int | float) -> bool:
|
||||||
"""Check if the index has changed since a given time for a
|
"""Check if the index has changed since a given time for a
|
||||||
given root item.
|
given root item.
|
||||||
"""
|
"""
|
||||||
return self._rootChange.get(rootHandle, self._indexChange) > float(checkTime)
|
if isinstance(rootHandle, str):
|
||||||
|
return self._rootChange.get(rootHandle, self._indexChange) > float(checkTime)
|
||||||
|
return False
|
||||||
|
|
||||||
##
|
##
|
||||||
# Load and Save Index to/from File
|
# Load and Save Index to/from File
|
||||||
@@ -167,15 +169,13 @@ class NWIndex:
|
|||||||
if not isinstance(indexFile, Path):
|
if not isinstance(indexFile, Path):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
theData = {}
|
|
||||||
tStart = time()
|
tStart = time()
|
||||||
|
|
||||||
self._indexBroken = False
|
self._indexBroken = False
|
||||||
if indexFile.exists():
|
if indexFile.exists():
|
||||||
logger.debug("Loading index file")
|
logger.debug("Loading index file")
|
||||||
try:
|
try:
|
||||||
with open(indexFile, mode="r", encoding="utf-8") as inFile:
|
with open(indexFile, mode="r", encoding="utf-8") as inFile:
|
||||||
theData = json.load(inFile)
|
data = json.load(inFile)
|
||||||
except Exception:
|
except Exception:
|
||||||
logger.error("Failed to load index file")
|
logger.error("Failed to load index file")
|
||||||
logException()
|
logException()
|
||||||
@@ -183,8 +183,8 @@ class NWIndex:
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
try:
|
try:
|
||||||
self._tagsIndex.unpackData(theData["novelWriter.tagsIndex"])
|
self._tagsIndex.unpackData(data["novelWriter.tagsIndex"])
|
||||||
self._itemIndex.unpackData(theData["novelWriter.itemIndex"])
|
self._itemIndex.unpackData(data["novelWriter.itemIndex"])
|
||||||
except Exception:
|
except Exception:
|
||||||
logger.error("The index content is invalid")
|
logger.error("The index content is invalid")
|
||||||
logException()
|
logException()
|
||||||
@@ -238,50 +238,50 @@ class NWIndex:
|
|||||||
# Index Building
|
# Index Building
|
||||||
##
|
##
|
||||||
|
|
||||||
def scanText(self, tHandle: str, theText: str) -> bool:
|
def scanText(self, tHandle: str, text: str) -> bool:
|
||||||
"""Scan a piece of text associated with a handle. This will
|
"""Scan a piece of text associated with a handle. This will
|
||||||
update the indices accordingly. This function takes the handle
|
update the indices accordingly. This function takes the handle
|
||||||
and text as separate inputs as we want to primarily scan the
|
and text as separate inputs as we want to primarily scan the
|
||||||
files before we save them, in which case we already have the
|
files before we save them, in which case we already have the
|
||||||
text.
|
text.
|
||||||
"""
|
"""
|
||||||
theItem = self._project.tree[tHandle]
|
tItem = self._project.tree[tHandle]
|
||||||
if theItem is None:
|
if tItem is None:
|
||||||
logger.info("Not indexing unknown item '%s'", tHandle)
|
logger.info("Not indexing unknown item '%s'", tHandle)
|
||||||
return False
|
return False
|
||||||
if not theItem.isFileType():
|
if not tItem.isFileType():
|
||||||
logger.info("Not indexing non-file item '%s'", tHandle)
|
logger.info("Not indexing non-file item '%s'", tHandle)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# Keep a record of existing tags, and create a new item entry
|
# Keep a record of existing tags, and create a new item entry
|
||||||
itemTags = dict.fromkeys(self._itemIndex.allItemTags(tHandle), False)
|
itemTags = dict.fromkeys(self._itemIndex.allItemTags(tHandle), False)
|
||||||
self._itemIndex.add(tHandle, theItem)
|
self._itemIndex.add(tHandle, tItem)
|
||||||
|
|
||||||
# Run word counter for the whole text
|
# Run word counter for the whole text
|
||||||
cC, wC, pC = countWords(theText)
|
cC, wC, pC = countWords(text)
|
||||||
theItem.setCharCount(cC)
|
tItem.setCharCount(cC)
|
||||||
theItem.setWordCount(wC)
|
tItem.setWordCount(wC)
|
||||||
theItem.setParaCount(pC)
|
tItem.setParaCount(pC)
|
||||||
|
|
||||||
# If the file's meta data is missing, or the file is out of the
|
# If the file's meta data is missing, or the file is out of the
|
||||||
# main project, we don't index the content
|
# main project, we don't index the content
|
||||||
if theItem.itemLayout == nwItemLayout.NO_LAYOUT:
|
if tItem.itemLayout == nwItemLayout.NO_LAYOUT:
|
||||||
logger.info("Not indexing no-layout item '%s'", tHandle)
|
logger.info("Not indexing no-layout item '%s'", tHandle)
|
||||||
return False
|
return False
|
||||||
if theItem.itemParent is None:
|
if tItem.itemParent is None:
|
||||||
logger.info("Not indexing orphaned item '%s'", tHandle)
|
logger.info("Not indexing orphaned item '%s'", tHandle)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
logger.debug("Indexing item with handle '%s'", tHandle)
|
logger.debug("Indexing item with handle '%s'", tHandle)
|
||||||
if theItem.isInactiveClass():
|
if tItem.isInactiveClass():
|
||||||
self._scanInactive(theItem, theText)
|
self._scanInactive(tItem, text)
|
||||||
else:
|
else:
|
||||||
self._scanActive(tHandle, theItem, theText, itemTags)
|
self._scanActive(tHandle, tItem, text, itemTags)
|
||||||
|
|
||||||
# Update timestamps for index changes
|
# Update timestamps for index changes
|
||||||
nowTime = time()
|
nowTime = time()
|
||||||
self._indexChange = nowTime
|
self._indexChange = nowTime
|
||||||
self._rootChange[theItem.itemRoot] = nowTime
|
self._rootChange[tItem.itemRoot] = nowTime
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
@@ -296,14 +296,14 @@ class NWIndex:
|
|||||||
pTitle = TT_NONE # Tag of the previous title
|
pTitle = TT_NONE # Tag of the previous title
|
||||||
canSetHeader = True # First header has not yet been set
|
canSetHeader = True # First header has not yet been set
|
||||||
|
|
||||||
theLines = text.splitlines()
|
lines = text.splitlines()
|
||||||
for nLine, aLine in enumerate(theLines, start=1):
|
for n, line in enumerate(lines, start=1):
|
||||||
|
|
||||||
if aLine.strip() == "":
|
if line.strip() == "":
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if aLine.startswith("#"):
|
if line.startswith("#"):
|
||||||
hDepth, hText = self._splitHeading(aLine)
|
hDepth, hText = self._splitHeading(line)
|
||||||
if hDepth == "H0":
|
if hDepth == "H0":
|
||||||
continue
|
continue
|
||||||
|
|
||||||
@@ -311,33 +311,33 @@ class NWIndex:
|
|||||||
nwItem.setMainHeading(hDepth)
|
nwItem.setMainHeading(hDepth)
|
||||||
canSetHeader = False
|
canSetHeader = False
|
||||||
|
|
||||||
cTitle = self._itemIndex.addItemHeading(tHandle, nLine, hDepth, hText)
|
cTitle = self._itemIndex.addItemHeading(tHandle, n, hDepth, hText)
|
||||||
if cTitle != TT_NONE:
|
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
|
# 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(lines[nTitle-1:n-1])
|
||||||
self._indexWordCounts(tHandle, lastText, pTitle)
|
self._indexWordCounts(tHandle, lastText, pTitle)
|
||||||
nTitle = nLine
|
nTitle = n
|
||||||
pTitle = cTitle
|
pTitle = cTitle
|
||||||
|
|
||||||
elif aLine.startswith("@"):
|
elif line.startswith("@"):
|
||||||
if cTitle != TT_NONE:
|
if cTitle != TT_NONE:
|
||||||
self._indexKeyword(tHandle, aLine, cTitle, nwItem.itemClass, tags)
|
self._indexKeyword(tHandle, line, cTitle, nwItem.itemClass, tags)
|
||||||
|
|
||||||
elif aLine.startswith("%"):
|
elif line.startswith("%"):
|
||||||
if cTitle != TT_NONE:
|
if cTitle != TT_NONE:
|
||||||
toCheck = aLine[1:].lstrip()
|
toCheck = line[1:].lstrip()
|
||||||
synTag = toCheck[:9].lower()
|
synTag = toCheck[:9].lower()
|
||||||
tLen = len(aLine)
|
tLen = len(line)
|
||||||
cLen = len(toCheck)
|
cLen = len(toCheck)
|
||||||
cOff = tLen - cLen
|
cOff = tLen - cLen
|
||||||
if synTag == "synopsis:":
|
if synTag == "synopsis:":
|
||||||
sText = aLine[cOff+9:].strip()
|
sText = line[cOff+9:].strip()
|
||||||
self._itemIndex.setHeadingSynopsis(tHandle, cTitle, sText)
|
self._itemIndex.setHeadingSynopsis(tHandle, cTitle, sText)
|
||||||
|
|
||||||
# Count words for remaining text after last heading
|
# Count words for remaining text after last heading
|
||||||
if pTitle != TT_NONE:
|
if pTitle != TT_NONE:
|
||||||
lastText = "\n".join(theLines[nTitle-1:])
|
lastText = "\n".join(lines[nTitle-1:])
|
||||||
self._indexWordCounts(tHandle, lastText, pTitle)
|
self._indexWordCounts(tHandle, lastText, pTitle)
|
||||||
|
|
||||||
# Also count words on a page with no titles
|
# Also count words on a page with no titles
|
||||||
@@ -354,9 +354,9 @@ class NWIndex:
|
|||||||
|
|
||||||
def _scanInactive(self, nwItem: NWItem, text: str) -> None:
|
def _scanInactive(self, nwItem: NWItem, text: str) -> None:
|
||||||
"""Scan an inactive document for meta data."""
|
"""Scan an inactive document for meta data."""
|
||||||
for aLine in text.splitlines():
|
for line in text.splitlines():
|
||||||
if aLine.startswith("#"):
|
if line.startswith("#"):
|
||||||
hDepth, _ = self._splitHeading(aLine)
|
hDepth, _ = self._splitHeading(line)
|
||||||
if hDepth != "H0":
|
if hDepth != "H0":
|
||||||
nwItem.setMainHeading(hDepth)
|
nwItem.setMainHeading(hDepth)
|
||||||
break
|
break
|
||||||
@@ -378,7 +378,7 @@ class NWIndex:
|
|||||||
return "H2", line[4:].strip()
|
return "H2", line[4:].strip()
|
||||||
return "H0", ""
|
return "H0", ""
|
||||||
|
|
||||||
def _indexWordCounts(self, tHandle: str, text: str, sTitle: str):
|
def _indexWordCounts(self, tHandle: str, text: str, sTitle: str) -> None:
|
||||||
"""Count text stats and save the counts to the index."""
|
"""Count text stats and save the counts to the index."""
|
||||||
cC, wC, pC = countWords(text)
|
cC, wC, pC = countWords(text)
|
||||||
self._itemIndex.setHeadingCounts(tHandle, sTitle, cC, wC, pC)
|
self._itemIndex.setHeadingCounts(tHandle, sTitle, cC, wC, pC)
|
||||||
@@ -391,22 +391,22 @@ class NWIndex:
|
|||||||
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
|
||||||
pruned later.
|
pruned later.
|
||||||
"""
|
"""
|
||||||
isValid, theBits, _ = self.scanThis(line)
|
isValid, tBits, _ = self.scanThis(line)
|
||||||
if not isValid or len(theBits) < 2:
|
if not isValid or len(tBits) < 2:
|
||||||
logger.warning("Skipping keyword with %d value(s) in '%s'", len(theBits), tHandle)
|
logger.warning("Skipping keyword with %d value(s) in '%s'", len(tBits), tHandle)
|
||||||
return
|
return
|
||||||
|
|
||||||
if theBits[0] not in nwKeyWords.VALID_KEYS:
|
if tBits[0] not in nwKeyWords.VALID_KEYS:
|
||||||
logger.warning("Skipping invalid keyword '%s' in '%s'", theBits[0], tHandle)
|
logger.warning("Skipping invalid keyword '%s' in '%s'", tBits[0], tHandle)
|
||||||
return
|
return
|
||||||
|
|
||||||
if theBits[0] == nwKeyWords.TAG_KEY:
|
if tBits[0] == nwKeyWords.TAG_KEY:
|
||||||
tagName = theBits[1]
|
tagName = tBits[1]
|
||||||
self._tagsIndex.add(tagName, tHandle, sTitle, itemClass)
|
self._tagsIndex.add(tagName, tHandle, sTitle, itemClass)
|
||||||
self._itemIndex.setHeadingTag(tHandle, sTitle, tagName)
|
self._itemIndex.setHeadingTag(tHandle, sTitle, tagName)
|
||||||
tags[tagName] = True
|
tags[tagName] = True
|
||||||
else:
|
else:
|
||||||
self._itemIndex.addHeadingReferences(tHandle, sTitle, theBits[1:], theBits[0])
|
self._itemIndex.addHeadingRef(tHandle, sTitle, tBits[1:], tBits[0])
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -475,10 +475,10 @@ class NWIndex:
|
|||||||
return isGood
|
return isGood
|
||||||
|
|
||||||
# If we're still here, we check that the references exist
|
# If we're still here, we check that the references exist
|
||||||
theKey = nwKeyWords.KEY_CLASS[tBits[0]].name
|
refKey = nwKeyWords.KEY_CLASS[tBits[0]].name
|
||||||
for n in range(1, nBits):
|
for n in range(1, nBits):
|
||||||
if tBits[n] in self._tagsIndex:
|
if tBits[n] in self._tagsIndex:
|
||||||
isGood[n] = self._tagsIndex.tagClass(tBits[n]) == theKey
|
isGood[n] = self._tagsIndex.tagClass(tBits[n]) == refKey
|
||||||
|
|
||||||
return isGood
|
return isGood
|
||||||
|
|
||||||
@@ -504,8 +504,8 @@ class NWIndex:
|
|||||||
they appear in the tree view and in the respective document
|
they appear in the tree view and in the respective document
|
||||||
files, but skipping all note files.
|
files, but skipping all note files.
|
||||||
"""
|
"""
|
||||||
novStruct = self._itemIndex.iterNovelStructure(rHandle=rootHandle, skipExcl=skipExcl)
|
structure = self._itemIndex.iterNovelStructure(rHandle=rootHandle, skipExcl=skipExcl)
|
||||||
for tHandle, sTitle, hItem in novStruct:
|
for tHandle, sTitle, hItem in structure:
|
||||||
yield f"{tHandle}:{sTitle}", tHandle, sTitle, hItem
|
yield f"{tHandle}:{sTitle}", tHandle, sTitle, hItem
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -555,14 +555,14 @@ class NWIndex:
|
|||||||
"words": hItem.wordCount,
|
"words": hItem.wordCount,
|
||||||
}
|
}
|
||||||
|
|
||||||
theToC = [(
|
result = [(
|
||||||
tKey,
|
tKey,
|
||||||
tData[tKey]["level"],
|
tData[tKey]["level"],
|
||||||
tData[tKey]["title"],
|
tData[tKey]["title"],
|
||||||
tData[tKey]["words"]
|
tData[tKey]["words"]
|
||||||
) for tKey in tOrder]
|
) for tKey in tOrder]
|
||||||
|
|
||||||
return theToC
|
return result
|
||||||
|
|
||||||
def getCounts(self, tHandle: str, sTitle: str | None = None) -> tuple[int, int, int]:
|
def getCounts(self, tHandle: str, sTitle: str | None = None) -> tuple[int, int, int]:
|
||||||
"""Return the counts for a file, or a section of a file,
|
"""Return the counts for a file, or a section of a file,
|
||||||
@@ -586,15 +586,15 @@ class NWIndex:
|
|||||||
"""Extract all references made in a file, and optionally title
|
"""Extract all references made in a file, and optionally title
|
||||||
section.
|
section.
|
||||||
"""
|
"""
|
||||||
theRefs = {x: [] for x in nwKeyWords.KEY_CLASS}
|
tRefs = {x: [] for x in nwKeyWords.KEY_CLASS}
|
||||||
for rTitle, hItem in self._itemIndex.iterItemHeaders(tHandle):
|
for rTitle, hItem in self._itemIndex.iterItemHeaders(tHandle):
|
||||||
if sTitle is None or sTitle == rTitle:
|
if sTitle is None or sTitle == rTitle:
|
||||||
for aTag, refTypes in hItem.references.items():
|
for aTag, refTypes in hItem.references.items():
|
||||||
for refType in refTypes:
|
for refType in refTypes:
|
||||||
if refType in theRefs:
|
if refType in tRefs:
|
||||||
theRefs[refType].append(aTag)
|
tRefs[refType].append(self._tagsIndex.tagName(aTag))
|
||||||
|
|
||||||
return theRefs
|
return tRefs
|
||||||
|
|
||||||
def getBackReferenceList(self, tHandle: str) -> dict[str, str]:
|
def getBackReferenceList(self, tHandle: str) -> dict[str, str]:
|
||||||
"""Build a list of files referring back to our file, specified
|
"""Build a list of files referring back to our file, specified
|
||||||
@@ -603,17 +603,17 @@ class NWIndex:
|
|||||||
if tHandle is None or tHandle not in self._itemIndex:
|
if tHandle is None or tHandle not in self._itemIndex:
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
theRefs = {}
|
tRefs = {}
|
||||||
theTags = self._itemIndex.allItemTags(tHandle)
|
tTags = self._itemIndex.allItemTags(tHandle)
|
||||||
if not theTags:
|
if not tTags:
|
||||||
return theRefs
|
return tRefs
|
||||||
|
|
||||||
for aHandle, sTitle, hItem in self._itemIndex.iterAllHeaders():
|
for aHandle, sTitle, hItem in self._itemIndex.iterAllHeaders():
|
||||||
for aTag in hItem.references:
|
for aTag in hItem.references:
|
||||||
if aTag in theTags and aHandle not in theRefs:
|
if aTag in tTags and aHandle not in tRefs:
|
||||||
theRefs[aHandle] = sTitle
|
tRefs[aHandle] = sTitle
|
||||||
|
|
||||||
return theRefs
|
return tRefs
|
||||||
|
|
||||||
def getTagSource(self, tagKey: str) -> tuple[str, str]:
|
def getTagSource(self, tagKey: str) -> tuple[str, str]:
|
||||||
"""Return the source location of a given tag."""
|
"""Return the source location of a given tag."""
|
||||||
@@ -638,47 +638,51 @@ class TagsIndex:
|
|||||||
|
|
||||||
__slots__ = ("_tags")
|
__slots__ = ("_tags")
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self) -> None:
|
||||||
self._tags: dict[str, dict] = {}
|
self._tags: dict[str, dict] = {}
|
||||||
return
|
return
|
||||||
|
|
||||||
def __contains__(self, tagKey):
|
def __contains__(self, tagKey: str) -> bool:
|
||||||
return tagKey in self._tags
|
return tagKey.lower() in self._tags
|
||||||
|
|
||||||
def __delitem__(self, tagKey):
|
def __delitem__(self, tagKey: str) -> None:
|
||||||
self._tags.pop(tagKey, None)
|
self._tags.pop(tagKey.lower(), None)
|
||||||
return
|
return
|
||||||
|
|
||||||
def __getitem__(self, tagKey):
|
def __getitem__(self, tagKey: str) -> dict | None:
|
||||||
return self._tags.get(tagKey, None)
|
return self._tags.get(tagKey.lower(), None)
|
||||||
|
|
||||||
##
|
##
|
||||||
# Methods
|
# Methods
|
||||||
##
|
##
|
||||||
|
|
||||||
def clear(self):
|
def clear(self) -> None:
|
||||||
"""Clear the index."""
|
"""Clear the index."""
|
||||||
self._tags = {}
|
self._tags = {}
|
||||||
return
|
return
|
||||||
|
|
||||||
def add(self, tagKey: str, tHandle: str, sTitle: str, itemClass: nwItemClass):
|
def add(self, tagKey: str, tHandle: str, sTitle: str, itemClass: nwItemClass) -> None:
|
||||||
"""Add a key to the index and set all values."""
|
"""Add a key to the index and set all values."""
|
||||||
self._tags[tagKey] = {
|
self._tags[tagKey.lower()] = {
|
||||||
"handle": tHandle, "heading": sTitle, "class": itemClass.name
|
"name": tagKey, "handle": tHandle, "heading": sTitle, "class": itemClass.name
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
|
|
||||||
|
def tagName(self, tagKey: str) -> str:
|
||||||
|
"""Get the display name of a given tag."""
|
||||||
|
return self._tags.get(tagKey.lower(), {}).get("name", "")
|
||||||
|
|
||||||
def tagHandle(self, tagKey: str) -> str:
|
def tagHandle(self, tagKey: str) -> str:
|
||||||
"""Get the handle of a given tag."""
|
"""Get the handle of a given tag."""
|
||||||
return self._tags.get(tagKey, {}).get("handle", None)
|
return self._tags.get(tagKey.lower(), {}).get("handle", None)
|
||||||
|
|
||||||
def tagHeading(self, tagKey: str) -> str:
|
def tagHeading(self, tagKey: str) -> str:
|
||||||
"""Get the heading of a given tag."""
|
"""Get the heading of a given tag."""
|
||||||
return self._tags.get(tagKey, {}).get("heading", TT_NONE)
|
return self._tags.get(tagKey.lower(), {}).get("heading", TT_NONE)
|
||||||
|
|
||||||
def tagClass(self, tagKey: str) -> str | None:
|
def tagClass(self, tagKey: str) -> str | None:
|
||||||
"""Get the class of a given tag."""
|
"""Get the class of a given tag."""
|
||||||
return self._tags.get(tagKey, {}).get("class", None)
|
return self._tags.get(tagKey.lower(), {}).get("class", None)
|
||||||
|
|
||||||
##
|
##
|
||||||
# Pack/Unpack
|
# Pack/Unpack
|
||||||
@@ -688,7 +692,7 @@ class TagsIndex:
|
|||||||
"""Pack all the data of the tags into a single dictionary."""
|
"""Pack all the data of the tags into a single dictionary."""
|
||||||
return self._tags
|
return self._tags
|
||||||
|
|
||||||
def unpackData(self, data: dict):
|
def unpackData(self, data: dict) -> None:
|
||||||
"""Iterate through the tagsIndex loaded from cache and check
|
"""Iterate through the tagsIndex loaded from cache and check
|
||||||
that it's valid.
|
that it's valid.
|
||||||
"""
|
"""
|
||||||
@@ -699,12 +703,16 @@ class TagsIndex:
|
|||||||
for tagKey, tagData in data.items():
|
for tagKey, tagData in data.items():
|
||||||
if not isinstance(tagKey, str):
|
if not isinstance(tagKey, str):
|
||||||
raise ValueError("tagsIndex keys must be a strings")
|
raise ValueError("tagsIndex keys must be a strings")
|
||||||
|
if "name" not in tagData:
|
||||||
|
raise KeyError("A tagIndex item is missing a name entry")
|
||||||
if "handle" not in tagData:
|
if "handle" not in tagData:
|
||||||
raise KeyError("A tagIndex item is missing a handle entry")
|
raise KeyError("A tagIndex item is missing a handle entry")
|
||||||
if "heading" not in tagData:
|
if "heading" not in tagData:
|
||||||
raise KeyError("A tagIndex item is missing a heading entry")
|
raise KeyError("A tagIndex item is missing a heading entry")
|
||||||
if "class" not in tagData:
|
if "class" not in tagData:
|
||||||
raise KeyError("A tagIndex item is missing a class entry")
|
raise KeyError("A tagIndex item is missing a class entry")
|
||||||
|
if tagData["name"].lower() != tagKey:
|
||||||
|
raise ValueError("tagsIndex name must match key")
|
||||||
if not isHandle(tagData["handle"]):
|
if not isHandle(tagData["handle"]):
|
||||||
raise ValueError("tagsIndex handle must be a handle")
|
raise ValueError("tagsIndex handle must be a handle")
|
||||||
if not isTitleTag(tagData["heading"]):
|
if not isTitleTag(tagData["heading"]):
|
||||||
@@ -735,7 +743,7 @@ class ItemIndex:
|
|||||||
|
|
||||||
__slots__ = ("_project", "_items")
|
__slots__ = ("_project", "_items")
|
||||||
|
|
||||||
def __init__(self, project: NWProject):
|
def __init__(self, project: NWProject) -> None:
|
||||||
self._project = project
|
self._project = project
|
||||||
self._items: dict[str, IndexItem] = {}
|
self._items: dict[str, IndexItem] = {}
|
||||||
return
|
return
|
||||||
@@ -743,7 +751,7 @@ class ItemIndex:
|
|||||||
def __contains__(self, tHandle: str) -> bool:
|
def __contains__(self, tHandle: str) -> bool:
|
||||||
return tHandle in self._items
|
return tHandle in self._items
|
||||||
|
|
||||||
def __delitem__(self, tHandle: str):
|
def __delitem__(self, tHandle: str) -> None:
|
||||||
self._items.pop(tHandle, None)
|
self._items.pop(tHandle, None)
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -754,12 +762,12 @@ class ItemIndex:
|
|||||||
# Methods
|
# Methods
|
||||||
##
|
##
|
||||||
|
|
||||||
def clear(self):
|
def clear(self) -> None:
|
||||||
"""Clear the index."""
|
"""Clear the index."""
|
||||||
self._items = {}
|
self._items = {}
|
||||||
return
|
return
|
||||||
|
|
||||||
def add(self, tHandle: str, nwItem: NWItem):
|
def add(self, tHandle: str, nwItem: NWItem) -> None:
|
||||||
"""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.
|
||||||
"""
|
"""
|
||||||
@@ -827,7 +835,7 @@ class ItemIndex:
|
|||||||
return sTitle
|
return sTitle
|
||||||
return TT_NONE
|
return TT_NONE
|
||||||
|
|
||||||
def setHeadingCounts(self, tHandle: str, sTitle: str, cC: int, wC: int, pC: int):
|
def setHeadingCounts(self, tHandle: str, sTitle: str, cC: int, wC: int, pC: int) -> None:
|
||||||
"""Set the character, word and paragraph counts of a heading
|
"""Set the character, word and paragraph counts of a heading
|
||||||
on a given item.
|
on a given item.
|
||||||
"""
|
"""
|
||||||
@@ -835,22 +843,22 @@ class ItemIndex:
|
|||||||
self._items[tHandle].setHeadingCounts(sTitle, cC, wC, pC)
|
self._items[tHandle].setHeadingCounts(sTitle, cC, wC, pC)
|
||||||
return
|
return
|
||||||
|
|
||||||
def setHeadingSynopsis(self, tHandle: str, sTitle: str, text: str):
|
def setHeadingSynopsis(self, tHandle: str, sTitle: str, text: str) -> None:
|
||||||
"""Set the synopsis text for a heading on a given item."""
|
"""Set the synopsis text for a heading on a given item."""
|
||||||
if tHandle in self._items:
|
if tHandle in self._items:
|
||||||
self._items[tHandle].setHeadingSynopsis(sTitle, text)
|
self._items[tHandle].setHeadingSynopsis(sTitle, text)
|
||||||
return
|
return
|
||||||
|
|
||||||
def setHeadingTag(self, tHandle: str, sTitle: str, tagKey: str):
|
def setHeadingTag(self, tHandle: str, sTitle: str, tagKey: str) -> None:
|
||||||
"""Set the main tag for a heading on a given item."""
|
"""Set the main tag for a heading on a given item."""
|
||||||
if tHandle in self._items:
|
if tHandle in self._items:
|
||||||
self._items[tHandle].setHeadingTag(sTitle, tagKey)
|
self._items[tHandle].setHeadingTag(sTitle, tagKey)
|
||||||
return
|
return
|
||||||
|
|
||||||
def addHeadingReferences(self, tHandle: str, sTitle: str, tagKeys: list[str], refType: str):
|
def addHeadingRef(self, tHandle: str, sTitle: str, tagKeys: list[str], refType: str) -> None:
|
||||||
"""Set the reference tags for a heading on a given item."""
|
"""Set the reference tags for a heading on a given item."""
|
||||||
if tHandle in self._items:
|
if tHandle in self._items:
|
||||||
self._items[tHandle].addHeadingReferences(sTitle, tagKeys, refType)
|
self._items[tHandle].addHeadingRef(sTitle, tagKeys, refType)
|
||||||
return
|
return
|
||||||
|
|
||||||
##
|
##
|
||||||
@@ -861,7 +869,7 @@ class ItemIndex:
|
|||||||
"""Pack all the data of the index into a single dictionary."""
|
"""Pack all the data of the index into a single dictionary."""
|
||||||
return {handle: item.packData() for handle, item in self._items.items()}
|
return {handle: item.packData() for handle, item in self._items.items()}
|
||||||
|
|
||||||
def unpackData(self, data: dict):
|
def unpackData(self, data: dict) -> None:
|
||||||
"""Iterate through the itemIndex loaded from cache and check
|
"""Iterate through the itemIndex loaded from cache and check
|
||||||
that it's valid. This will raise errors if there is a problem.
|
that it's valid. This will raise errors if there is a problem.
|
||||||
"""
|
"""
|
||||||
@@ -894,17 +902,13 @@ 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")
|
__slots__ = ("_handle", "_item", "_headings", "_count")
|
||||||
|
|
||||||
def __init__(self, tHandle: str, nwItem: NWItem):
|
def __init__(self, tHandle: str, nwItem: NWItem) -> None:
|
||||||
self._handle = tHandle
|
self._handle = tHandle
|
||||||
self._item = nwItem
|
self._item = nwItem
|
||||||
self._headings: dict[str, IndexHeading] = {}
|
self._headings: dict[str, IndexHeading] = {TT_NONE: IndexHeading(TT_NONE)}
|
||||||
self._count = 0
|
self._count = 0
|
||||||
|
|
||||||
# Add a placeholder heading
|
|
||||||
self._headings[TT_NONE] = IndexHeading(TT_NONE)
|
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
def __repr__(self) -> str:
|
||||||
@@ -925,13 +929,14 @@ class IndexItem:
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def item(self) -> NWItem:
|
def item(self) -> NWItem:
|
||||||
|
"""Return the project item of the index item."""
|
||||||
return self._item
|
return self._item
|
||||||
|
|
||||||
##
|
##
|
||||||
# Setters
|
# Setters
|
||||||
##
|
##
|
||||||
|
|
||||||
def addHeading(self, tHeading: IndexHeading):
|
def addHeading(self, tHeading: IndexHeading) -> None:
|
||||||
"""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.
|
||||||
"""
|
"""
|
||||||
@@ -940,25 +945,25 @@ class IndexItem:
|
|||||||
self._headings[tHeading.key] = tHeading
|
self._headings[tHeading.key] = tHeading
|
||||||
return
|
return
|
||||||
|
|
||||||
def setHeadingCounts(self, sTitle: str, cCount: int, wCount: int, pCount: int):
|
def setHeadingCounts(self, sTitle: str, cCount: int, wCount: int, pCount: int) -> None:
|
||||||
"""Set the character, word and paragraph count of a heading."""
|
"""Set the character, word and paragraph count of a heading."""
|
||||||
if sTitle in self._headings:
|
if sTitle in self._headings:
|
||||||
self._headings[sTitle].setCounts(cCount, wCount, pCount)
|
self._headings[sTitle].setCounts(cCount, wCount, pCount)
|
||||||
return
|
return
|
||||||
|
|
||||||
def setHeadingSynopsis(self, sTitle: str, text: str):
|
def setHeadingSynopsis(self, sTitle: str, text: str) -> None:
|
||||||
"""Set the synopsis text of a heading."""
|
"""Set the synopsis text of a heading."""
|
||||||
if sTitle in self._headings:
|
if sTitle in self._headings:
|
||||||
self._headings[sTitle].setSynopsis(text)
|
self._headings[sTitle].setSynopsis(text)
|
||||||
return
|
return
|
||||||
|
|
||||||
def setHeadingTag(self, sTitle: str, tagKey: str):
|
def setHeadingTag(self, sTitle: str, tagKey: str) -> None:
|
||||||
"""Set the tag of a heading."""
|
"""Set the tag of a heading."""
|
||||||
if sTitle in self._headings:
|
if sTitle in self._headings:
|
||||||
self._headings[sTitle].setTag(tagKey)
|
self._headings[sTitle].setTag(tagKey)
|
||||||
return
|
return
|
||||||
|
|
||||||
def addHeadingReferences(self, sTitle: str, tagKeys: list[str], refType: str):
|
def addHeadingRef(self, sTitle: str, tagKeys: list[str], refType: str) -> None:
|
||||||
"""Add a reference key and all its types to a heading."""
|
"""Add a reference key and all its types to a heading."""
|
||||||
if sTitle in self._headings:
|
if sTitle in self._headings:
|
||||||
for tagKey in tagKeys:
|
for tagKey in tagKeys:
|
||||||
@@ -970,9 +975,11 @@ class IndexItem:
|
|||||||
##
|
##
|
||||||
|
|
||||||
def items(self) -> ItemsView[str, IndexHeading]:
|
def items(self) -> ItemsView[str, IndexHeading]:
|
||||||
|
"""Return IndexHeading items."""
|
||||||
return self._headings.items()
|
return self._headings.items()
|
||||||
|
|
||||||
def headings(self) -> list[str]:
|
def headings(self) -> list[str]:
|
||||||
|
"""Return heading keys in sorted order."""
|
||||||
return sorted(self._headings.keys())
|
return sorted(self._headings.keys())
|
||||||
|
|
||||||
def allTags(self) -> list[str]:
|
def allTags(self) -> list[str]:
|
||||||
@@ -1005,7 +1012,7 @@ class IndexItem:
|
|||||||
|
|
||||||
return data
|
return data
|
||||||
|
|
||||||
def unpackData(self, data: dict):
|
def unpackData(self, data: dict) -> None:
|
||||||
"""Unpack an item entry from the data."""
|
"""Unpack an item entry from the data."""
|
||||||
references = data.get("references", {})
|
references = data.get("references", {})
|
||||||
for sTitle, hData in data.get("headings", {}).items():
|
for sTitle, hData in data.get("headings", {}).items():
|
||||||
@@ -1033,7 +1040,7 @@ class IndexHeading:
|
|||||||
"_paraCount", "_synopsis", "_tag", "_refs",
|
"_paraCount", "_synopsis", "_tag", "_refs",
|
||||||
)
|
)
|
||||||
|
|
||||||
def __init__(self, key: str, line: int = 0, level: str = "H0", title: str = ""):
|
def __init__(self, key: str, line: int = 0, level: str = "H0", title: str = "") -> None:
|
||||||
self._key = key
|
self._key = key
|
||||||
self._line = line
|
self._line = line
|
||||||
self._level = level
|
self._level = level
|
||||||
@@ -1100,18 +1107,18 @@ class IndexHeading:
|
|||||||
# Setters
|
# Setters
|
||||||
##
|
##
|
||||||
|
|
||||||
def setLevel(self, level: str):
|
def setLevel(self, level: str) -> None:
|
||||||
"""Set the level of the header if it's a valid value."""
|
"""Set the level of the header if it's a valid value."""
|
||||||
if level in nwHeaders.H_VALID:
|
if level in nwHeaders.H_VALID:
|
||||||
self._level = level
|
self._level = level
|
||||||
return
|
return
|
||||||
|
|
||||||
def setLine(self, line: int):
|
def setLine(self, line: int) -> None:
|
||||||
"""Set the line number of a heading."""
|
"""Set the line number of a heading."""
|
||||||
self._line = max(0, checkInt(line, 0))
|
self._line = max(0, checkInt(line, 0))
|
||||||
return
|
return
|
||||||
|
|
||||||
def setCounts(self, charCount: int, wordCount: int, paraCount: int):
|
def setCounts(self, charCount: int, wordCount: int, paraCount: int) -> None:
|
||||||
"""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.
|
||||||
"""
|
"""
|
||||||
@@ -1120,21 +1127,22 @@ class IndexHeading:
|
|||||||
self._paraCount = max(0, checkInt(paraCount, 0))
|
self._paraCount = max(0, checkInt(paraCount, 0))
|
||||||
return
|
return
|
||||||
|
|
||||||
def setSynopsis(self, text: str):
|
def setSynopsis(self, text: str) -> None:
|
||||||
"""Set the synopsis text and make sure it is a string."""
|
"""Set the synopsis text and make sure it is a string."""
|
||||||
self._synopsis = str(text)
|
self._synopsis = str(text)
|
||||||
return
|
return
|
||||||
|
|
||||||
def setTag(self, tagKey: str):
|
def setTag(self, tagKey: str) -> None:
|
||||||
"""Set the tag for references, and make sure it is a string."""
|
"""Set the tag for references, and make sure it is a string."""
|
||||||
self._tag = str(tagKey)
|
self._tag = str(tagKey).lower()
|
||||||
return
|
return
|
||||||
|
|
||||||
def addReference(self, tagKey: str, refType: str):
|
def addReference(self, tagKey: str, refType: str) -> None:
|
||||||
"""Add a record of a reference tag, and what keyword types it is
|
"""Add a record of a reference tag, and what keyword types it is
|
||||||
associated with.
|
associated with.
|
||||||
"""
|
"""
|
||||||
if refType in nwKeyWords.VALID_KEYS:
|
if refType in nwKeyWords.VALID_KEYS:
|
||||||
|
tagKey = tagKey.lower()
|
||||||
if tagKey not in self._refs:
|
if tagKey not in self._refs:
|
||||||
self._refs[tagKey] = set()
|
self._refs[tagKey] = set()
|
||||||
self._refs[tagKey].add(refType)
|
self._refs[tagKey].add(refType)
|
||||||
@@ -1165,7 +1173,7 @@ class IndexHeading:
|
|||||||
"""
|
"""
|
||||||
return {key: ",".join(sorted(list(value))) for key, value in self._refs.items()}
|
return {key: ",".join(sorted(list(value))) for key, value in self._refs.items()}
|
||||||
|
|
||||||
def unpackData(self, data: dict):
|
def unpackData(self, data: dict) -> None:
|
||||||
"""Unpack a heading entry from a dictionary."""
|
"""Unpack a heading entry from a dictionary."""
|
||||||
self.setLevel(data.get("level", "H0"))
|
self.setLevel(data.get("level", "H0"))
|
||||||
self._title = str(data.get("title", ""))
|
self._title = str(data.get("title", ""))
|
||||||
@@ -1179,7 +1187,7 @@ class IndexHeading:
|
|||||||
self._synopsis = str(data.get("synopsis", ""))
|
self._synopsis = str(data.get("synopsis", ""))
|
||||||
return
|
return
|
||||||
|
|
||||||
def unpackReferences(self, data: dict):
|
def unpackReferences(self, data: dict) -> None:
|
||||||
"""Unpack a set of references from a dictionary."""
|
"""Unpack a set of references from a dictionary."""
|
||||||
for tagKey, refTypes in data.items():
|
for tagKey, refTypes in data.items():
|
||||||
if not isinstance(tagKey, str):
|
if not isinstance(tagKey, str):
|
||||||
@@ -1221,55 +1229,55 @@ def countWords(text: str) -> tuple[int, int, int]:
|
|||||||
if nwUnicode.U_EMDASH in text:
|
if nwUnicode.U_EMDASH in text:
|
||||||
text = text.replace(nwUnicode.U_EMDASH, " ")
|
text = text.replace(nwUnicode.U_EMDASH, " ")
|
||||||
|
|
||||||
for aLine in text.splitlines():
|
for line in text.splitlines():
|
||||||
|
|
||||||
countPara = True
|
countPara = True
|
||||||
|
|
||||||
if not aLine:
|
if not line:
|
||||||
prevEmpty = True
|
prevEmpty = True
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if aLine[0] == "@" or aLine[0] == "%":
|
if line[0] == "@" or line[0] == "%":
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if aLine[0] == "[":
|
if line[0] == "[":
|
||||||
if aLine.startswith(("[NEWPAGE]", "[NEW PAGE]", "[VSPACE]")):
|
if line.startswith(("[NEWPAGE]", "[NEW PAGE]", "[VSPACE]")):
|
||||||
continue
|
continue
|
||||||
elif aLine.startswith("[VSPACE:") and aLine.endswith("]"):
|
elif line.startswith("[VSPACE:") and line.endswith("]"):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
elif aLine[0] == "#":
|
elif line[0] == "#":
|
||||||
if aLine[:5] == "#### ":
|
if line[:5] == "#### ":
|
||||||
aLine = aLine[5:]
|
line = line[5:]
|
||||||
countPara = False
|
countPara = False
|
||||||
elif aLine[:4] == "### ":
|
elif line[:4] == "### ":
|
||||||
aLine = aLine[4:]
|
line = line[4:]
|
||||||
countPara = False
|
countPara = False
|
||||||
elif aLine[:3] == "## ":
|
elif line[:3] == "## ":
|
||||||
aLine = aLine[3:]
|
line = line[3:]
|
||||||
countPara = False
|
countPara = False
|
||||||
elif aLine[:2] == "# ":
|
elif line[:2] == "# ":
|
||||||
aLine = aLine[2:]
|
line = line[2:]
|
||||||
countPara = False
|
countPara = False
|
||||||
elif aLine[:3] == "#! ":
|
elif line[:3] == "#! ":
|
||||||
aLine = aLine[3:]
|
line = line[3:]
|
||||||
countPara = False
|
countPara = False
|
||||||
elif aLine[:4] == "##! ":
|
elif line[:4] == "##! ":
|
||||||
aLine = aLine[4:]
|
line = line[4:]
|
||||||
countPara = False
|
countPara = False
|
||||||
|
|
||||||
elif aLine[0] == ">" or aLine[-1] == "<":
|
elif line[0] == ">" or line[-1] == "<":
|
||||||
if aLine[:2] == ">>":
|
if line[:2] == ">>":
|
||||||
aLine = aLine[2:].lstrip(" ")
|
line = line[2:].lstrip(" ")
|
||||||
elif aLine[:1] == ">":
|
elif line[:1] == ">":
|
||||||
aLine = aLine[1:].lstrip(" ")
|
line = line[1:].lstrip(" ")
|
||||||
if aLine[-2:] == "<<":
|
if line[-2:] == "<<":
|
||||||
aLine = aLine[:-2].rstrip(" ")
|
line = line[:-2].rstrip(" ")
|
||||||
elif aLine[-1:] == "<":
|
elif line[-1:] == "<":
|
||||||
aLine = aLine[:-1].rstrip(" ")
|
line = line[:-1].rstrip(" ")
|
||||||
|
|
||||||
wordCount += len(aLine.split())
|
wordCount += len(line.split())
|
||||||
charCount += len(aLine)
|
charCount += len(line)
|
||||||
if countPara and prevEmpty:
|
if countPara and prevEmpty:
|
||||||
paraCount += 1
|
paraCount += 1
|
||||||
|
|
||||||
|
|||||||
@@ -62,7 +62,7 @@ class NovelSelector(QComboBox):
|
|||||||
# Methods
|
# Methods
|
||||||
##
|
##
|
||||||
|
|
||||||
def setHandle(self, tHandle: str, blockSignal: bool = True) -> None:
|
def setHandle(self, tHandle: str | None, blockSignal: bool = True) -> None:
|
||||||
"""Set the currently selected handle."""
|
"""Set the currently selected handle."""
|
||||||
self._blockSignal = blockSignal
|
self._blockSignal = blockSignal
|
||||||
if tHandle is None:
|
if tHandle is None:
|
||||||
|
|||||||
+143
-165
@@ -59,8 +59,8 @@ class GuiOutlineView(QWidget):
|
|||||||
loadDocumentTagRequest = pyqtSignal(str, Enum)
|
loadDocumentTagRequest = pyqtSignal(str, Enum)
|
||||||
openDocumentRequest = pyqtSignal(str, Enum, str, bool)
|
openDocumentRequest = pyqtSignal(str, Enum, str, bool)
|
||||||
|
|
||||||
def __init__(self, mainGui):
|
def __init__(self, parent: QWidget) -> None:
|
||||||
super().__init__(parent=mainGui)
|
super().__init__(parent=parent)
|
||||||
|
|
||||||
# Build GUI
|
# Build GUI
|
||||||
self.outlineTree = GuiOutlineTree(self)
|
self.outlineTree = GuiOutlineTree(self)
|
||||||
@@ -98,38 +98,33 @@ class GuiOutlineView(QWidget):
|
|||||||
# Methods
|
# Methods
|
||||||
##
|
##
|
||||||
|
|
||||||
def updateTheme(self):
|
def updateTheme(self) -> None:
|
||||||
"""Update theme elements.
|
"""Update theme elements."""
|
||||||
"""
|
|
||||||
self.outlineBar.updateTheme()
|
self.outlineBar.updateTheme()
|
||||||
self.refreshTree()
|
self.refreshTree()
|
||||||
return
|
return
|
||||||
|
|
||||||
def initSettings(self):
|
def initSettings(self) -> None:
|
||||||
"""Initialise GUI elements that depend on specific settings.
|
"""Initialise GUI elements that depend on specific settings."""
|
||||||
"""
|
|
||||||
self.outlineTree.initSettings()
|
self.outlineTree.initSettings()
|
||||||
self.outlineData.initSettings()
|
self.outlineData.initSettings()
|
||||||
return
|
return
|
||||||
|
|
||||||
def refreshTree(self):
|
def refreshTree(self) -> None:
|
||||||
"""Refresh the current tree.
|
"""Refresh the current tree."""
|
||||||
"""
|
|
||||||
self.outlineTree.refreshTree(rootHandle=SHARED.project.data.getLastHandle("outline"))
|
self.outlineTree.refreshTree(rootHandle=SHARED.project.data.getLastHandle("outline"))
|
||||||
return
|
return
|
||||||
|
|
||||||
def clearOutline(self):
|
def clearOutline(self) -> None:
|
||||||
"""Clear project-related GUI content.
|
"""Clear project-related GUI content."""
|
||||||
"""
|
|
||||||
self.outlineData.clearDetails()
|
self.outlineData.clearDetails()
|
||||||
self.outlineBar.setEnabled(False)
|
self.outlineBar.setEnabled(False)
|
||||||
return
|
return
|
||||||
|
|
||||||
def openProjectTasks(self):
|
def openProjectTasks(self) -> None:
|
||||||
"""Run open project tasks.
|
"""Run open project tasks."""
|
||||||
"""
|
|
||||||
lastOutline = SHARED.project.data.getLastHandle("outline")
|
lastOutline = SHARED.project.data.getLastHandle("outline")
|
||||||
if not (lastOutline in SHARED.project.tree or lastOutline is None):
|
if not (lastOutline is None or lastOutline in SHARED.project.tree):
|
||||||
lastOutline = SHARED.project.tree.findRoot(nwItemClass.NOVEL)
|
lastOutline = SHARED.project.tree.findRoot(nwItemClass.NOVEL)
|
||||||
|
|
||||||
logger.debug("Setting outline tree to root item '%s'", lastOutline)
|
logger.debug("Setting outline tree to root item '%s'", lastOutline)
|
||||||
@@ -141,23 +136,23 @@ class GuiOutlineView(QWidget):
|
|||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
def closeProjectTasks(self):
|
def closeProjectTasks(self) -> None:
|
||||||
|
"""Run closing project tasks."""
|
||||||
self.outlineTree.closeProjectTasks()
|
self.outlineTree.closeProjectTasks()
|
||||||
self.outlineData.updateClasses()
|
self.outlineData.updateClasses()
|
||||||
self.clearOutline()
|
self.clearOutline()
|
||||||
return
|
return
|
||||||
|
|
||||||
def splitSizes(self):
|
def splitSizes(self) -> list[int]:
|
||||||
|
"""Get the sizes of the splitter widget."""
|
||||||
return self.splitOutline.sizes()
|
return self.splitOutline.sizes()
|
||||||
|
|
||||||
def setTreeFocus(self):
|
def setTreeFocus(self) -> None:
|
||||||
"""Set the focus to the tree widget.
|
"""Set the focus to the tree widget."""
|
||||||
"""
|
|
||||||
return self.outlineTree.setFocus()
|
return self.outlineTree.setFocus()
|
||||||
|
|
||||||
def treeHasFocus(self):
|
def treeHasFocus(self) -> bool:
|
||||||
"""Check if the outline tree has focus.
|
"""Check if the outline tree has focus."""
|
||||||
"""
|
|
||||||
return self.outlineTree.hasFocus()
|
return self.outlineTree.hasFocus()
|
||||||
|
|
||||||
##
|
##
|
||||||
@@ -165,9 +160,8 @@ class GuiOutlineView(QWidget):
|
|||||||
##
|
##
|
||||||
|
|
||||||
@pyqtSlot(str)
|
@pyqtSlot(str)
|
||||||
def updateRootItem(self, tHandle):
|
def updateRootItem(self, tHandle: str) -> None:
|
||||||
"""Should be called whenever a root folders changes.
|
"""Handle tasks whenever a root folders changes."""
|
||||||
"""
|
|
||||||
self.outlineBar.populateNovelList()
|
self.outlineBar.populateNovelList()
|
||||||
self.outlineData.updateClasses()
|
self.outlineData.updateClasses()
|
||||||
return
|
return
|
||||||
@@ -177,7 +171,7 @@ class GuiOutlineView(QWidget):
|
|||||||
##
|
##
|
||||||
|
|
||||||
@pyqtSlot()
|
@pyqtSlot()
|
||||||
def _updateMenuColumns(self):
|
def _updateMenuColumns(self) -> None:
|
||||||
"""Trigger an update of the toggled state of the column menu
|
"""Trigger an update of the toggled state of the column menu
|
||||||
checkboxes whenever a signal is received that the hidden state
|
checkboxes whenever a signal is received that the hidden state
|
||||||
of columns has changed.
|
of columns has changed.
|
||||||
@@ -186,18 +180,16 @@ class GuiOutlineView(QWidget):
|
|||||||
return
|
return
|
||||||
|
|
||||||
@pyqtSlot(str)
|
@pyqtSlot(str)
|
||||||
def _tagClicked(self, link):
|
def _tagClicked(self, link: str) -> None:
|
||||||
"""Capture the click of a tag in the details panel.
|
"""Capture the click of a tag in the details panel."""
|
||||||
"""
|
|
||||||
if link:
|
if link:
|
||||||
self.loadDocumentTagRequest.emit(link, nwDocMode.VIEW)
|
self.loadDocumentTagRequest.emit(link, nwDocMode.VIEW)
|
||||||
return
|
return
|
||||||
|
|
||||||
@pyqtSlot(str)
|
@pyqtSlot(str)
|
||||||
def _rootItemChanged(self, handle):
|
def _rootItemChanged(self, tHandle) -> None:
|
||||||
"""The root novel handle has changed or needs to be refreshed.
|
"""Handle root novel changed or needs to be refreshed."""
|
||||||
"""
|
self.outlineTree.refreshTree(rootHandle=(tHandle or None), overRide=True)
|
||||||
self.outlineTree.refreshTree(rootHandle=(handle or None), overRide=True)
|
|
||||||
return
|
return
|
||||||
|
|
||||||
# END Class GuiOutlineView
|
# END Class GuiOutlineView
|
||||||
@@ -208,8 +200,8 @@ class GuiOutlineToolBar(QToolBar):
|
|||||||
loadNovelRootRequest = pyqtSignal(str)
|
loadNovelRootRequest = pyqtSignal(str)
|
||||||
viewColumnToggled = pyqtSignal(bool, Enum)
|
viewColumnToggled = pyqtSignal(bool, Enum)
|
||||||
|
|
||||||
def __init__(self, theOutline):
|
def __init__(self, outlineView: GuiOutlineView) -> None:
|
||||||
super().__init__(parent=theOutline)
|
super().__init__(parent=outlineView)
|
||||||
|
|
||||||
logger.debug("Create: GuiOutlineToolBar")
|
logger.debug("Create: GuiOutlineToolBar")
|
||||||
|
|
||||||
@@ -221,7 +213,7 @@ class GuiOutlineToolBar(QToolBar):
|
|||||||
self.setContentsMargins(0, 0, 0, 0)
|
self.setContentsMargins(0, 0, 0, 0)
|
||||||
|
|
||||||
stretch = QWidget(self)
|
stretch = QWidget(self)
|
||||||
stretch.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
|
stretch.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding)
|
||||||
|
|
||||||
# Novel Selector
|
# Novel Selector
|
||||||
self.novelLabel = QLabel(self.tr("Outline of"))
|
self.novelLabel = QLabel(self.tr("Outline of"))
|
||||||
@@ -243,7 +235,7 @@ class GuiOutlineToolBar(QToolBar):
|
|||||||
|
|
||||||
self.tbColumns = QToolButton(self)
|
self.tbColumns = QToolButton(self)
|
||||||
self.tbColumns.setMenu(self.mColumns)
|
self.tbColumns.setMenu(self.mColumns)
|
||||||
self.tbColumns.setPopupMode(QToolButton.InstantPopup)
|
self.tbColumns.setPopupMode(QToolButton.ToolButtonPopupMode.InstantPopup)
|
||||||
|
|
||||||
# Assemble
|
# Assemble
|
||||||
self.addWidget(self.novelLabel)
|
self.addWidget(self.novelLabel)
|
||||||
@@ -263,32 +255,26 @@ class GuiOutlineToolBar(QToolBar):
|
|||||||
# Methods
|
# Methods
|
||||||
##
|
##
|
||||||
|
|
||||||
def updateTheme(self):
|
def updateTheme(self) -> None:
|
||||||
"""Update theme elements.
|
"""Update theme elements."""
|
||||||
"""
|
|
||||||
self.setStyleSheet("QToolBar {border: 0px;}")
|
self.setStyleSheet("QToolBar {border: 0px;}")
|
||||||
|
|
||||||
self.novelValue.updateList(includeAll=True)
|
self.novelValue.updateList(includeAll=True)
|
||||||
self.aRefresh.setIcon(SHARED.theme.getIcon("refresh"))
|
self.aRefresh.setIcon(SHARED.theme.getIcon("refresh"))
|
||||||
self.tbColumns.setIcon(SHARED.theme.getIcon("menu"))
|
self.tbColumns.setIcon(SHARED.theme.getIcon("menu"))
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
def populateNovelList(self):
|
def populateNovelList(self) -> None:
|
||||||
"""Reload the content of the novel list.
|
"""Reload the content of the novel list."""
|
||||||
"""
|
|
||||||
self.novelValue.updateList(includeAll=True)
|
self.novelValue.updateList(includeAll=True)
|
||||||
return
|
return
|
||||||
|
|
||||||
def setCurrentRoot(self, rootHandle):
|
def setCurrentRoot(self, rootHandle: str | None) -> None:
|
||||||
"""Set the current active root handle.
|
"""Set the current active root handle."""
|
||||||
"""
|
|
||||||
self.novelValue.setHandle(rootHandle)
|
self.novelValue.setHandle(rootHandle)
|
||||||
return
|
return
|
||||||
|
|
||||||
def setColumnHiddenState(self, hiddenState):
|
def setColumnHiddenState(self, hiddenState: dict[nwOutline, bool]) -> None:
|
||||||
"""Forward the change of column hidden states to the menu.
|
"""Forward the change of column hidden states to the menu."""
|
||||||
"""
|
|
||||||
self.mColumns.setHiddenState(hiddenState)
|
self.mColumns.setHiddenState(hiddenState)
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -297,16 +283,14 @@ class GuiOutlineToolBar(QToolBar):
|
|||||||
##
|
##
|
||||||
|
|
||||||
@pyqtSlot(str)
|
@pyqtSlot(str)
|
||||||
def _novelValueChanged(self, tHandle):
|
def _novelValueChanged(self, tHandle: str) -> None:
|
||||||
"""Emit a signal containing the handle of the selected item.
|
"""Emit a signal containing the handle of the selected item."""
|
||||||
"""
|
|
||||||
self.loadNovelRootRequest.emit(tHandle)
|
self.loadNovelRootRequest.emit(tHandle)
|
||||||
return
|
return
|
||||||
|
|
||||||
@pyqtSlot()
|
@pyqtSlot()
|
||||||
def _refreshRequested(self):
|
def _refreshRequested(self) -> None:
|
||||||
"""Emit a signal containing the handle of the selected item.
|
"""Emit a signal containing the handle of the selected item."""
|
||||||
"""
|
|
||||||
self.loadNovelRootRequest.emit(self.novelValue.handle)
|
self.loadNovelRootRequest.emit(self.novelValue.handle)
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -361,7 +345,7 @@ class GuiOutlineTree(QTreeWidget):
|
|||||||
hiddenStateChanged = pyqtSignal()
|
hiddenStateChanged = pyqtSignal()
|
||||||
activeItemChanged = pyqtSignal(str, str)
|
activeItemChanged = pyqtSignal(str, str)
|
||||||
|
|
||||||
def __init__(self, outlineView):
|
def __init__(self, outlineView: GuiOutlineView) -> None:
|
||||||
super().__init__(parent=outlineView)
|
super().__init__(parent=outlineView)
|
||||||
|
|
||||||
logger.debug("Create: GuiOutlineTree")
|
logger.debug("Create: GuiOutlineTree")
|
||||||
@@ -369,9 +353,9 @@ class GuiOutlineTree(QTreeWidget):
|
|||||||
self.outlineView = outlineView
|
self.outlineView = outlineView
|
||||||
|
|
||||||
self.setUniformRowHeights(True)
|
self.setUniformRowHeights(True)
|
||||||
self.setFrameStyle(QFrame.NoFrame)
|
self.setFrameStyle(QFrame.Shape.NoFrame)
|
||||||
self.setSelectionBehavior(QAbstractItemView.SelectRows)
|
self.setSelectionBehavior(QAbstractItemView.SelectionBehavior.SelectRows)
|
||||||
self.setSelectionMode(QAbstractItemView.SingleSelection)
|
self.setSelectionMode(QAbstractItemView.SelectionMode.SingleSelection)
|
||||||
self.setExpandsOnDoubleClick(False)
|
self.setExpandsOnDoubleClick(False)
|
||||||
self.setDragEnabled(False)
|
self.setDragEnabled(False)
|
||||||
self.itemDoubleClicked.connect(self._treeDoubleClick)
|
self.itemDoubleClicked.connect(self._treeDoubleClick)
|
||||||
@@ -431,23 +415,19 @@ class GuiOutlineTree(QTreeWidget):
|
|||||||
# Methods
|
# Methods
|
||||||
##
|
##
|
||||||
|
|
||||||
def initSettings(self):
|
def initSettings(self) -> None:
|
||||||
"""Set or update outline settings.
|
"""Set or update outline settings."""
|
||||||
"""
|
|
||||||
# Scroll bars
|
|
||||||
if CONFIG.hideVScroll:
|
if CONFIG.hideVScroll:
|
||||||
self.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
|
self.setVerticalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOff)
|
||||||
else:
|
else:
|
||||||
self.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded)
|
self.setVerticalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAsNeeded)
|
||||||
|
|
||||||
if CONFIG.hideHScroll:
|
if CONFIG.hideHScroll:
|
||||||
self.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
|
self.setHorizontalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOff)
|
||||||
else:
|
else:
|
||||||
self.setHorizontalScrollBarPolicy(Qt.ScrollBarAsNeeded)
|
self.setHorizontalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAsNeeded)
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
def clearContent(self):
|
def clearContent(self) -> None:
|
||||||
"""Clear the tree and header and set the default values for the
|
"""Clear the tree and header and set the default values for the
|
||||||
columns arrays.
|
columns arrays.
|
||||||
"""
|
"""
|
||||||
@@ -455,10 +435,10 @@ class GuiOutlineTree(QTreeWidget):
|
|||||||
self.setColumnCount(1)
|
self.setColumnCount(1)
|
||||||
self.setHeaderLabel(trConst(nwLabels.OUTLINE_COLS[nwOutline.TITLE]))
|
self.setHeaderLabel(trConst(nwLabels.OUTLINE_COLS[nwOutline.TITLE]))
|
||||||
|
|
||||||
self._treeOrder = []
|
self._treeOrder: list[nwOutline] = []
|
||||||
self._colWidth = {}
|
self._colWidth: dict[nwOutline, int] = {}
|
||||||
self._colHidden = {}
|
self._colHidden: dict[nwOutline, bool] = {}
|
||||||
self._colIdx = {}
|
self._colIdx: dict[nwOutline, int] = {}
|
||||||
self._treeNCols = 0
|
self._treeNCols = 0
|
||||||
|
|
||||||
for hItem in nwOutline:
|
for hItem in nwOutline:
|
||||||
@@ -470,7 +450,8 @@ class GuiOutlineTree(QTreeWidget):
|
|||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
def refreshTree(self, rootHandle=None, overRide=False, novelChanged=False):
|
def refreshTree(self, rootHandle: str | None = None,
|
||||||
|
overRide: bool = False, novelChanged: bool = False) -> None:
|
||||||
"""Called whenever the Outline tab is activated and controls
|
"""Called whenever the Outline tab is activated and controls
|
||||||
what data to load, and if necessary, force a rebuild of the
|
what data to load, and if necessary, force a rebuild of the
|
||||||
tree.
|
tree.
|
||||||
@@ -494,15 +475,14 @@ class GuiOutlineTree(QTreeWidget):
|
|||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
def closeProjectTasks(self):
|
def closeProjectTasks(self) -> None:
|
||||||
"""Called before a project is closed.
|
"""Called before a project is closed."""
|
||||||
"""
|
|
||||||
self._saveHeaderState()
|
self._saveHeaderState()
|
||||||
self.clearContent()
|
self.clearContent()
|
||||||
self._firstView = True
|
self._firstView = True
|
||||||
return
|
return
|
||||||
|
|
||||||
def getSelectedHandle(self):
|
def getSelectedHandle(self) -> tuple[str | None, str | None]:
|
||||||
"""Get the currently selected handle. If multiple items are
|
"""Get the currently selected handle. If multiple items are
|
||||||
selected, return the first.
|
selected, return the first.
|
||||||
"""
|
"""
|
||||||
@@ -518,7 +498,7 @@ class GuiOutlineTree(QTreeWidget):
|
|||||||
##
|
##
|
||||||
|
|
||||||
@pyqtSlot("QTreeWidgetItem*", int)
|
@pyqtSlot("QTreeWidgetItem*", int)
|
||||||
def _treeDoubleClick(self, tItem, tCol):
|
def _treeDoubleClick(self, tItem: QTreeWidgetItem, tCol: int) -> None:
|
||||||
"""Extract the handle and line number of the title double-
|
"""Extract the handle and line number of the title double-
|
||||||
clicked, and send it to the main gui class for opening in the
|
clicked, and send it to the main gui class for opening in the
|
||||||
document editor.
|
document editor.
|
||||||
@@ -530,7 +510,7 @@ class GuiOutlineTree(QTreeWidget):
|
|||||||
return
|
return
|
||||||
|
|
||||||
@pyqtSlot()
|
@pyqtSlot()
|
||||||
def _itemSelected(self):
|
def _itemSelected(self) -> None:
|
||||||
"""Extract the handle and line number of the currently selected
|
"""Extract the handle and line number of the currently selected
|
||||||
title, and send it to the details panel.
|
title, and send it to the details panel.
|
||||||
"""
|
"""
|
||||||
@@ -542,7 +522,7 @@ class GuiOutlineTree(QTreeWidget):
|
|||||||
return
|
return
|
||||||
|
|
||||||
@pyqtSlot(int, int, int)
|
@pyqtSlot(int, int, int)
|
||||||
def _columnMoved(self, logIdx, oldVisualIdx, newVisualIdx):
|
def _columnMoved(self, logIdx: int, oldVisualIdx: int, newVisualIdx: int) -> None:
|
||||||
"""Make sure the order array is up to date with the actual order
|
"""Make sure the order array is up to date with the actual order
|
||||||
of the columns.
|
of the columns.
|
||||||
"""
|
"""
|
||||||
@@ -551,12 +531,12 @@ class GuiOutlineTree(QTreeWidget):
|
|||||||
return
|
return
|
||||||
|
|
||||||
@pyqtSlot(bool, Enum)
|
@pyqtSlot(bool, Enum)
|
||||||
def menuColumnToggled(self, isChecked, theItem):
|
def menuColumnToggled(self, isChecked: bool, hItem: nwOutline) -> None:
|
||||||
"""Receive the changes to column visibility forwarded by the
|
"""Receive the changes to column visibility forwarded by the
|
||||||
column selection menu.
|
column selection menu.
|
||||||
"""
|
"""
|
||||||
if theItem in self._colIdx:
|
if hItem in self._colIdx:
|
||||||
self.setColumnHidden(self._colIdx[theItem], not isChecked)
|
self.setColumnHidden(self._colIdx[hItem], not isChecked)
|
||||||
self._saveHeaderState()
|
self._saveHeaderState()
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -600,7 +580,7 @@ class GuiOutlineTree(QTreeWidget):
|
|||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
def _saveHeaderState(self):
|
def _saveHeaderState(self) -> None:
|
||||||
"""Save the state of the main tree header, that is, column
|
"""Save the state of the main tree header, that is, column
|
||||||
order, column width and column hidden state. We don't want to
|
order, column width and column hidden state. We don't want to
|
||||||
save the current width of hidden columns though. This preserves
|
save the current width of hidden columns though. This preserves
|
||||||
@@ -627,7 +607,7 @@ class GuiOutlineTree(QTreeWidget):
|
|||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
def _populateTree(self, rootHandle):
|
def _populateTree(self, rootHandle: str | None) -> None:
|
||||||
"""Build the tree based on the project index, and the header
|
"""Build the tree based on the project index, and the header
|
||||||
based on the defined constants, default values and user selected
|
based on the defined constants, default values and user selected
|
||||||
width, order and hidden state. All columns are populated, even
|
width, order and hidden state. All columns are populated, even
|
||||||
@@ -653,22 +633,25 @@ class GuiOutlineTree(QTreeWidget):
|
|||||||
|
|
||||||
headItem = self.headerItem()
|
headItem = self.headerItem()
|
||||||
if isinstance(headItem, QTreeWidgetItem):
|
if isinstance(headItem, QTreeWidgetItem):
|
||||||
headItem.setTextAlignment(self._colIdx[nwOutline.CCOUNT], Qt.AlignRight)
|
headItem.setTextAlignment(
|
||||||
headItem.setTextAlignment(self._colIdx[nwOutline.WCOUNT], Qt.AlignRight)
|
self._colIdx[nwOutline.CCOUNT], Qt.AlignmentFlag.AlignRight)
|
||||||
headItem.setTextAlignment(self._colIdx[nwOutline.PCOUNT], Qt.AlignRight)
|
headItem.setTextAlignment(
|
||||||
|
self._colIdx[nwOutline.WCOUNT], Qt.AlignmentFlag.AlignRight)
|
||||||
|
headItem.setTextAlignment(
|
||||||
|
self._colIdx[nwOutline.PCOUNT], Qt.AlignmentFlag.AlignRight)
|
||||||
|
|
||||||
novStruct = SHARED.project.index.novelStructure(rootHandle=rootHandle, skipExcl=True)
|
novStruct = SHARED.project.index.novelStructure(rootHandle=rootHandle, skipExcl=True)
|
||||||
for _, tHandle, sTitle, novIdx in novStruct:
|
for _, tHandle, sTitle, novIdx in novStruct:
|
||||||
|
|
||||||
iLevel = nwHeaders.H_LEVEL.get(novIdx.level, 0)
|
iLevel = nwHeaders.H_LEVEL.get(novIdx.level, 0)
|
||||||
if iLevel == 0:
|
nwItem = SHARED.project.tree[tHandle]
|
||||||
|
if iLevel == 0 or nwItem is None:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
trItem = QTreeWidgetItem()
|
trItem = QTreeWidgetItem()
|
||||||
nwItem = SHARED.project.tree[tHandle]
|
|
||||||
hDec = SHARED.theme.getHeaderDecoration(iLevel)
|
hDec = SHARED.theme.getHeaderDecoration(iLevel)
|
||||||
|
|
||||||
trItem.setData(self._colIdx[nwOutline.TITLE], Qt.DecorationRole, hDec)
|
trItem.setData(self._colIdx[nwOutline.TITLE], Qt.ItemDataRole.DecorationRole, hDec)
|
||||||
trItem.setText(self._colIdx[nwOutline.TITLE], novIdx.title)
|
trItem.setText(self._colIdx[nwOutline.TITLE], novIdx.title)
|
||||||
trItem.setData(self._colIdx[nwOutline.TITLE], self.D_HANDLE, tHandle)
|
trItem.setData(self._colIdx[nwOutline.TITLE], self.D_HANDLE, tHandle)
|
||||||
trItem.setData(self._colIdx[nwOutline.TITLE], self.D_TITLE, sTitle)
|
trItem.setData(self._colIdx[nwOutline.TITLE], self.D_TITLE, sTitle)
|
||||||
@@ -681,9 +664,9 @@ class GuiOutlineTree(QTreeWidget):
|
|||||||
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}")
|
||||||
trItem.setText(self._colIdx[nwOutline.PCOUNT], f"{novIdx.paraCount:n}")
|
trItem.setText(self._colIdx[nwOutline.PCOUNT], f"{novIdx.paraCount:n}")
|
||||||
trItem.setTextAlignment(self._colIdx[nwOutline.CCOUNT], Qt.AlignRight)
|
trItem.setTextAlignment(self._colIdx[nwOutline.CCOUNT], Qt.AlignmentFlag.AlignRight)
|
||||||
trItem.setTextAlignment(self._colIdx[nwOutline.WCOUNT], Qt.AlignRight)
|
trItem.setTextAlignment(self._colIdx[nwOutline.WCOUNT], Qt.AlignmentFlag.AlignRight)
|
||||||
trItem.setTextAlignment(self._colIdx[nwOutline.PCOUNT], Qt.AlignRight)
|
trItem.setTextAlignment(self._colIdx[nwOutline.PCOUNT], Qt.AlignmentFlag.AlignRight)
|
||||||
|
|
||||||
refs = SHARED.project.index.getReferences(tHandle, sTitle)
|
refs = SHARED.project.index.getReferences(tHandle, sTitle)
|
||||||
trItem.setText(self._colIdx[nwOutline.POV], ", ".join(refs[nwKeyWords.POV_KEY]))
|
trItem.setText(self._colIdx[nwOutline.POV], ", ".join(refs[nwKeyWords.POV_KEY]))
|
||||||
@@ -709,8 +692,8 @@ class GuiOutlineHeaderMenu(QMenu):
|
|||||||
|
|
||||||
columnToggled = pyqtSignal(bool, Enum)
|
columnToggled = pyqtSignal(bool, Enum)
|
||||||
|
|
||||||
def __init__(self, theOutline):
|
def __init__(self, outlineToolBar: GuiOutlineToolBar) -> None:
|
||||||
super().__init__(parent=theOutline)
|
super().__init__(parent=outlineToolBar)
|
||||||
|
|
||||||
self.acceptToggle = True
|
self.acceptToggle = True
|
||||||
|
|
||||||
@@ -731,7 +714,7 @@ class GuiOutlineHeaderMenu(QMenu):
|
|||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
def setHiddenState(self, hiddenState):
|
def setHiddenState(self, hiddenState: dict[nwOutline, bool]) -> None:
|
||||||
"""Overwrite the checked state of the columns as the inverse of
|
"""Overwrite the checked state of the columns as the inverse of
|
||||||
the hidden state. Skip the TITLE column as it cannot be hidden.
|
the hidden state. Skip the TITLE column as it cannot be hidden.
|
||||||
"""
|
"""
|
||||||
@@ -760,12 +743,12 @@ class GuiOutlineDetails(QScrollArea):
|
|||||||
|
|
||||||
itemTagClicked = pyqtSignal(str)
|
itemTagClicked = pyqtSignal(str)
|
||||||
|
|
||||||
def __init__(self, theOutline):
|
def __init__(self, outlineView: GuiOutlineView) -> None:
|
||||||
super().__init__(parent=theOutline)
|
super().__init__(parent=outlineView)
|
||||||
|
|
||||||
logger.debug("Create: GuiOutlineDetails")
|
logger.debug("Create: GuiOutlineDetails")
|
||||||
|
|
||||||
self.theOutline = theOutline
|
self.outlineView = outlineView
|
||||||
|
|
||||||
# Sizes
|
# Sizes
|
||||||
minTitle = 30*SHARED.theme.textNWidth
|
minTitle = 30*SHARED.theme.textNWidth
|
||||||
@@ -878,23 +861,26 @@ class GuiOutlineDetails(QScrollArea):
|
|||||||
|
|
||||||
# Selected Item Details
|
# Selected Item Details
|
||||||
self.mainGroup = QGroupBox(self.tr("Title Details"), self)
|
self.mainGroup = QGroupBox(self.tr("Title Details"), self)
|
||||||
self.mainForm = QGridLayout()
|
self.mainForm = QGridLayout()
|
||||||
self.mainGroup.setLayout(self.mainForm)
|
self.mainGroup.setLayout(self.mainForm)
|
||||||
|
|
||||||
self.mainForm.addWidget(self.titleLabel, 0, 0, 1, 1, Qt.AlignTop | Qt.AlignLeft)
|
topLeft = Qt.AlignmentFlag.AlignTop | Qt.AlignmentFlag.AlignLeft
|
||||||
self.mainForm.addWidget(self.titleValue, 0, 1, 1, 1, Qt.AlignTop | Qt.AlignLeft)
|
topRight = Qt.AlignmentFlag.AlignTop | Qt.AlignmentFlag.AlignRight
|
||||||
self.mainForm.addWidget(self.cCLabel, 0, 2, 1, 1, Qt.AlignTop | Qt.AlignLeft)
|
|
||||||
self.mainForm.addWidget(self.cCValue, 0, 3, 1, 1, Qt.AlignTop | Qt.AlignRight)
|
self.mainForm.addWidget(self.titleLabel, 0, 0, 1, 1, topLeft)
|
||||||
self.mainForm.addWidget(self.fileLabel, 1, 0, 1, 1, Qt.AlignTop | Qt.AlignLeft)
|
self.mainForm.addWidget(self.titleValue, 0, 1, 1, 1, topLeft)
|
||||||
self.mainForm.addWidget(self.fileValue, 1, 1, 1, 1, Qt.AlignTop | Qt.AlignLeft)
|
self.mainForm.addWidget(self.cCLabel, 0, 2, 1, 1, topLeft)
|
||||||
self.mainForm.addWidget(self.wCLabel, 1, 2, 1, 1, Qt.AlignTop | Qt.AlignLeft)
|
self.mainForm.addWidget(self.cCValue, 0, 3, 1, 1, topRight)
|
||||||
self.mainForm.addWidget(self.wCValue, 1, 3, 1, 1, Qt.AlignTop | Qt.AlignRight)
|
self.mainForm.addWidget(self.fileLabel, 1, 0, 1, 1, topLeft)
|
||||||
self.mainForm.addWidget(self.itemLabel, 2, 0, 1, 1, Qt.AlignTop | Qt.AlignLeft)
|
self.mainForm.addWidget(self.fileValue, 1, 1, 1, 1, topLeft)
|
||||||
self.mainForm.addWidget(self.itemValue, 2, 1, 1, 1, Qt.AlignTop | Qt.AlignLeft)
|
self.mainForm.addWidget(self.wCLabel, 1, 2, 1, 1, topLeft)
|
||||||
self.mainForm.addWidget(self.pCLabel, 2, 2, 1, 1, Qt.AlignTop | Qt.AlignLeft)
|
self.mainForm.addWidget(self.wCValue, 1, 3, 1, 1, topRight)
|
||||||
self.mainForm.addWidget(self.pCValue, 2, 3, 1, 1, Qt.AlignTop | Qt.AlignRight)
|
self.mainForm.addWidget(self.itemLabel, 2, 0, 1, 1, topLeft)
|
||||||
self.mainForm.addWidget(self.synopLabel, 3, 0, 1, 4, Qt.AlignTop | Qt.AlignLeft)
|
self.mainForm.addWidget(self.itemValue, 2, 1, 1, 1, topLeft)
|
||||||
self.mainForm.addLayout(self.synopLWrap, 4, 0, 1, 4, Qt.AlignTop | Qt.AlignLeft)
|
self.mainForm.addWidget(self.pCLabel, 2, 2, 1, 1, topLeft)
|
||||||
|
self.mainForm.addWidget(self.pCValue, 2, 3, 1, 1, topRight)
|
||||||
|
self.mainForm.addWidget(self.synopLabel, 3, 0, 1, 4, topLeft)
|
||||||
|
self.mainForm.addLayout(self.synopLWrap, 4, 0, 1, 4, topLeft)
|
||||||
|
|
||||||
self.mainForm.setColumnStretch(1, 1)
|
self.mainForm.setColumnStretch(1, 1)
|
||||||
self.mainForm.setRowStretch(4, 1)
|
self.mainForm.setRowStretch(4, 1)
|
||||||
@@ -906,24 +892,24 @@ class GuiOutlineDetails(QScrollArea):
|
|||||||
self.tagsForm = QGridLayout()
|
self.tagsForm = QGridLayout()
|
||||||
self.tagsGroup.setLayout(self.tagsForm)
|
self.tagsGroup.setLayout(self.tagsForm)
|
||||||
|
|
||||||
self.tagsForm.addWidget(self.povKeyLabel, 0, 0, 1, 1, Qt.AlignTop | Qt.AlignLeft)
|
self.tagsForm.addWidget(self.povKeyLabel, 0, 0, 1, 1, topLeft)
|
||||||
self.tagsForm.addLayout(self.povKeyLWrap, 0, 1, 1, 1, Qt.AlignTop | Qt.AlignLeft)
|
self.tagsForm.addLayout(self.povKeyLWrap, 0, 1, 1, 1, topLeft)
|
||||||
self.tagsForm.addWidget(self.focKeyLabel, 1, 0, 1, 1, Qt.AlignTop | Qt.AlignLeft)
|
self.tagsForm.addWidget(self.focKeyLabel, 1, 0, 1, 1, topLeft)
|
||||||
self.tagsForm.addLayout(self.focKeyLWrap, 1, 1, 1, 1, Qt.AlignTop | Qt.AlignLeft)
|
self.tagsForm.addLayout(self.focKeyLWrap, 1, 1, 1, 1, topLeft)
|
||||||
self.tagsForm.addWidget(self.chrKeyLabel, 2, 0, 1, 1, Qt.AlignTop | Qt.AlignLeft)
|
self.tagsForm.addWidget(self.chrKeyLabel, 2, 0, 1, 1, topLeft)
|
||||||
self.tagsForm.addLayout(self.chrKeyLWrap, 2, 1, 1, 1, Qt.AlignTop | Qt.AlignLeft)
|
self.tagsForm.addLayout(self.chrKeyLWrap, 2, 1, 1, 1, topLeft)
|
||||||
self.tagsForm.addWidget(self.pltKeyLabel, 3, 0, 1, 1, Qt.AlignTop | Qt.AlignLeft)
|
self.tagsForm.addWidget(self.pltKeyLabel, 3, 0, 1, 1, topLeft)
|
||||||
self.tagsForm.addLayout(self.pltKeyLWrap, 3, 1, 1, 1, Qt.AlignTop | Qt.AlignLeft)
|
self.tagsForm.addLayout(self.pltKeyLWrap, 3, 1, 1, 1, topLeft)
|
||||||
self.tagsForm.addWidget(self.timKeyLabel, 4, 0, 1, 1, Qt.AlignTop | Qt.AlignLeft)
|
self.tagsForm.addWidget(self.timKeyLabel, 4, 0, 1, 1, topLeft)
|
||||||
self.tagsForm.addLayout(self.timKeyLWrap, 4, 1, 1, 1, Qt.AlignTop | Qt.AlignLeft)
|
self.tagsForm.addLayout(self.timKeyLWrap, 4, 1, 1, 1, topLeft)
|
||||||
self.tagsForm.addWidget(self.wldKeyLabel, 5, 0, 1, 1, Qt.AlignTop | Qt.AlignLeft)
|
self.tagsForm.addWidget(self.wldKeyLabel, 5, 0, 1, 1, topLeft)
|
||||||
self.tagsForm.addLayout(self.wldKeyLWrap, 5, 1, 1, 1, Qt.AlignTop | Qt.AlignLeft)
|
self.tagsForm.addLayout(self.wldKeyLWrap, 5, 1, 1, 1, topLeft)
|
||||||
self.tagsForm.addWidget(self.objKeyLabel, 6, 0, 1, 1, Qt.AlignTop | Qt.AlignLeft)
|
self.tagsForm.addWidget(self.objKeyLabel, 6, 0, 1, 1, topLeft)
|
||||||
self.tagsForm.addLayout(self.objKeyLWrap, 6, 1, 1, 1, Qt.AlignTop | Qt.AlignLeft)
|
self.tagsForm.addLayout(self.objKeyLWrap, 6, 1, 1, 1, topLeft)
|
||||||
self.tagsForm.addWidget(self.entKeyLabel, 7, 0, 1, 1, Qt.AlignTop | Qt.AlignLeft)
|
self.tagsForm.addWidget(self.entKeyLabel, 7, 0, 1, 1, topLeft)
|
||||||
self.tagsForm.addLayout(self.entKeyLWrap, 7, 1, 1, 1, Qt.AlignTop | Qt.AlignLeft)
|
self.tagsForm.addLayout(self.entKeyLWrap, 7, 1, 1, 1, topLeft)
|
||||||
self.tagsForm.addWidget(self.cstKeyLabel, 8, 0, 1, 1, Qt.AlignTop | Qt.AlignLeft)
|
self.tagsForm.addWidget(self.cstKeyLabel, 8, 0, 1, 1, topLeft)
|
||||||
self.tagsForm.addLayout(self.cstKeyLWrap, 8, 1, 1, 1, Qt.AlignTop | Qt.AlignLeft)
|
self.tagsForm.addLayout(self.cstKeyLWrap, 8, 1, 1, 1, topLeft)
|
||||||
|
|
||||||
self.tagsForm.setColumnStretch(1, 1)
|
self.tagsForm.setColumnStretch(1, 1)
|
||||||
self.tagsForm.setRowStretch(8, 1)
|
self.tagsForm.setRowStretch(8, 1)
|
||||||
@@ -939,10 +925,10 @@ class GuiOutlineDetails(QScrollArea):
|
|||||||
self.outerWidget.setLayout(self.outerBox)
|
self.outerWidget.setLayout(self.outerBox)
|
||||||
self.setWidget(self.outerWidget)
|
self.setWidget(self.outerWidget)
|
||||||
|
|
||||||
self.setHorizontalScrollBarPolicy(Qt.ScrollBarAsNeeded)
|
self.setHorizontalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAsNeeded)
|
||||||
self.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded)
|
self.setVerticalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAsNeeded)
|
||||||
self.setWidgetResizable(True)
|
self.setWidgetResizable(True)
|
||||||
self.setFrameStyle(QFrame.NoFrame)
|
self.setFrameStyle(QFrame.Shape.NoFrame)
|
||||||
|
|
||||||
self.initSettings()
|
self.initSettings()
|
||||||
|
|
||||||
@@ -950,27 +936,21 @@ class GuiOutlineDetails(QScrollArea):
|
|||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
def initSettings(self):
|
def initSettings(self) -> None:
|
||||||
"""Set or update outline settings.
|
"""Set or update outline settings."""
|
||||||
"""
|
|
||||||
# Scroll bars
|
|
||||||
if CONFIG.hideVScroll:
|
if CONFIG.hideVScroll:
|
||||||
self.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
|
self.setVerticalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOff)
|
||||||
else:
|
else:
|
||||||
self.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded)
|
self.setVerticalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAsNeeded)
|
||||||
|
|
||||||
if CONFIG.hideHScroll:
|
if CONFIG.hideHScroll:
|
||||||
self.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
|
self.setHorizontalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOff)
|
||||||
else:
|
else:
|
||||||
self.setHorizontalScrollBarPolicy(Qt.ScrollBarAsNeeded)
|
self.setHorizontalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAsNeeded)
|
||||||
|
|
||||||
self.updateClasses()
|
self.updateClasses()
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
def clearDetails(self):
|
def clearDetails(self) -> None:
|
||||||
"""Clear all the data labels.
|
"""Clear all the data labels."""
|
||||||
"""
|
|
||||||
self.titleLabel.setText("<b>%s</b>" % self.tr("Title"))
|
self.titleLabel.setText("<b>%s</b>" % self.tr("Title"))
|
||||||
self.titleValue.setText("")
|
self.titleValue.setText("")
|
||||||
self.fileValue.setText("")
|
self.fileValue.setText("")
|
||||||
@@ -996,7 +976,7 @@ class GuiOutlineDetails(QScrollArea):
|
|||||||
##
|
##
|
||||||
|
|
||||||
@pyqtSlot(str, str)
|
@pyqtSlot(str, str)
|
||||||
def showItem(self, tHandle, sTitle):
|
def showItem(self, tHandle: str, sTitle: str) -> bool:
|
||||||
"""Update the content of the tree with the given handle and line
|
"""Update the content of the tree with the given handle and line
|
||||||
number pointing to a header.
|
number pointing to a header.
|
||||||
"""
|
"""
|
||||||
@@ -1041,9 +1021,8 @@ class GuiOutlineDetails(QScrollArea):
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
@pyqtSlot()
|
@pyqtSlot()
|
||||||
def updateClasses(self):
|
def updateClasses(self) -> None:
|
||||||
"""Update the visibility status of class details.
|
"""Update the visibility status of class details."""
|
||||||
"""
|
|
||||||
usedClasses = SHARED.project.tree.rootClasses()
|
usedClasses = SHARED.project.tree.rootClasses()
|
||||||
|
|
||||||
pltVisible = nwItemClass.PLOT in usedClasses
|
pltVisible = nwItemClass.PLOT in usedClasses
|
||||||
@@ -1069,9 +1048,8 @@ class GuiOutlineDetails(QScrollArea):
|
|||||||
return
|
return
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _formatTags(refs, key):
|
def _formatTags(refs: dict[str, list[str]], key: str) -> str:
|
||||||
"""Convert a list of tags into a list of clickable tag links.
|
"""Convert a list of tags into a list of clickable tag links."""
|
||||||
"""
|
|
||||||
return ", ".join(
|
return ", ".join(
|
||||||
[f"<a href='{tag}'>{tag}</a>" for tag in refs.get(key, [])]
|
[f"<a href='{tag}'>{tag}</a>" for tag in refs.get(key, [])]
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
{
|
{
|
||||||
"novelWriter.tagsIndex": {
|
"novelWriter.tagsIndex": {
|
||||||
"Bod": {"handle": "4c4f28287af27", "heading": "T0001", "class": "CHARACTER"},
|
"bod": {"name": "Bod", "handle": "4c4f28287af27", "heading": "T0001", "class": "CHARACTER"},
|
||||||
"Main": {"handle": "2426c6f0ca922", "heading": "T0001", "class": "PLOT"},
|
"main": {"name": "Main", "handle": "2426c6f0ca922", "heading": "T0001", "class": "PLOT"},
|
||||||
"Europe": {"handle": "04468803b92e1", "heading": "T0001", "class": "WORLD"}
|
"europe": {"name": "Europe", "handle": "04468803b92e1", "heading": "T0001", "class": "WORLD"}
|
||||||
},
|
},
|
||||||
"novelWriter.itemIndex": {
|
"novelWriter.itemIndex": {
|
||||||
"7a992350f3eb6": {
|
"7a992350f3eb6": {
|
||||||
@@ -30,7 +30,7 @@
|
|||||||
"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."}
|
"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": {
|
||||||
"T0001": {"Bod": "@pov", "Main": "@plot", "Europe": "@location"}
|
"T0001": {"bod": "@pov", "main": "@plot", "europe": "@location"}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"88243afbe5ed8": {
|
"88243afbe5ed8": {
|
||||||
@@ -39,7 +39,7 @@
|
|||||||
"T0002": {"level": "H4", "title": "Scene One, Section Two", "line": 13, "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": {
|
||||||
"T0001": {"Bod": "@pov", "Main": "@plot", "Europe": "@location"}
|
"T0001": {"bod": "@pov", "main": "@plot", "europe": "@location"}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"f96ec11c6a3da": {
|
"f96ec11c6a3da": {
|
||||||
@@ -48,7 +48,7 @@
|
|||||||
"T0002": {"level": "H4", "title": "Scene Two, Section Two", "line": 15, "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": {
|
||||||
"T0001": {"Bod": "@pov", "Main": "@plot", "Europe": "@location"}
|
"T0001": {"bod": "@pov", "main": "@plot", "europe": "@location"}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"846352075de7d": {
|
"846352075de7d": {
|
||||||
@@ -61,7 +61,7 @@
|
|||||||
"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."}
|
"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": {
|
||||||
"T0001": {"Bod": "@pov", "Main": "@plot", "Europe": "@location"}
|
"T0001": {"bod": "@pov", "main": "@plot", "europe": "@location"}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"eb103bc70c90c": {
|
"eb103bc70c90c": {
|
||||||
@@ -69,7 +69,7 @@
|
|||||||
"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."}
|
"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": {
|
||||||
"T0001": {"Bod": "@pov", "Main": "@plot", "Europe": "@location"}
|
"T0001": {"bod": "@pov", "main": "@plot", "europe": "@location"}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"f8c0562e50f1b": {
|
"f8c0562e50f1b": {
|
||||||
@@ -77,7 +77,7 @@
|
|||||||
"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."}
|
"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": {
|
||||||
"T0001": {"Bod": "@pov", "Main": "@plot", "Europe": "@location"}
|
"T0001": {"bod": "@pov", "main": "@plot", "europe": "@location"}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"47666c91c7ccf": {
|
"47666c91c7ccf": {
|
||||||
@@ -85,25 +85,25 @@
|
|||||||
"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."}
|
"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": {
|
||||||
"T0001": {"Bod": "@pov", "Main": "@plot", "Europe": "@location"}
|
"T0001": {"bod": "@pov", "main": "@plot", "europe": "@location"}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"4c4f28287af27": {
|
"4c4f28287af27": {
|
||||||
"headings": {
|
"headings": {
|
||||||
"T0001": {"level": "H1", "title": "Nobody Owens", "line": 1, "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": {
|
||||||
"T0001": {"Main": "@plot"}
|
"T0001": {"main": "@plot"}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"2426c6f0ca922": {
|
"2426c6f0ca922": {
|
||||||
"headings": {
|
"headings": {
|
||||||
"T0001": {"level": "H1", "title": "Main Plot", "line": 1, "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": {
|
||||||
"T0001": {"level": "H1", "title": "Ancient Europe", "line": 1, "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": ""}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+390
-343
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user