@@ -2,6 +2,17 @@
|
|||||||
|
|
||||||
## Version 1.1 Dev (Alpha)
|
## Version 1.1 Dev (Alpha)
|
||||||
|
|
||||||
|
### Release Notes
|
||||||
|
|
||||||
|
### Detailed Changelog
|
||||||
|
|
||||||
|
**User Interface**
|
||||||
|
|
||||||
|
* Added a Novel tab under the project tree where the user can navigate the novel's layout of
|
||||||
|
chapters and scenes, similar to the Outline view, but next to the document editor. The Outline
|
||||||
|
view and Novel/Project trees now also behave more in cooperation. When files on one are selected
|
||||||
|
or moved, the other will follow and update. PR #537.
|
||||||
|
|
||||||
----
|
----
|
||||||
|
|
||||||
## Version 1.0 [2021-01-03]
|
## Version 1.0 [2021-01-03]
|
||||||
|
|||||||
+21
-8
@@ -95,14 +95,15 @@ class Config:
|
|||||||
self.lastNotes = "" # The latest release notes that have been shown
|
self.lastNotes = "" # The latest release notes that have been shown
|
||||||
|
|
||||||
## Sizes
|
## Sizes
|
||||||
self.winGeometry = [1200, 650]
|
self.winGeometry = [1200, 650]
|
||||||
self.treeColWidth = [200, 50, 30]
|
self.treeColWidth = [200, 50, 30]
|
||||||
self.projColWidth = [200, 60, 140]
|
self.novelColWidth = [200, 50]
|
||||||
self.mainPanePos = [300, 800]
|
self.projColWidth = [200, 60, 140]
|
||||||
self.docPanePos = [400, 400]
|
self.mainPanePos = [300, 800]
|
||||||
self.viewPanePos = [500, 150]
|
self.docPanePos = [400, 400]
|
||||||
self.outlnPanePos = [500, 150]
|
self.viewPanePos = [500, 150]
|
||||||
self.isFullScreen = False
|
self.outlnPanePos = [500, 150]
|
||||||
|
self.isFullScreen = False
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
self.hideVScroll = False # Hide vertical scroll bars on main widgets
|
self.hideVScroll = False # Hide vertical scroll bars on main widgets
|
||||||
@@ -395,6 +396,9 @@ class Config:
|
|||||||
self.treeColWidth = self._parseLine(
|
self.treeColWidth = self._parseLine(
|
||||||
cnfParse, cnfSec, "treecols", self.CNF_I_LST, self.treeColWidth
|
cnfParse, cnfSec, "treecols", self.CNF_I_LST, self.treeColWidth
|
||||||
)
|
)
|
||||||
|
self.novelColWidth = self._parseLine(
|
||||||
|
cnfParse, cnfSec, "novelcols", self.CNF_I_LST, self.novelColWidth
|
||||||
|
)
|
||||||
self.projColWidth = self._parseLine(
|
self.projColWidth = self._parseLine(
|
||||||
cnfParse, cnfSec, "projcols", self.CNF_I_LST, self.projColWidth
|
cnfParse, cnfSec, "projcols", self.CNF_I_LST, self.projColWidth
|
||||||
)
|
)
|
||||||
@@ -597,6 +601,7 @@ class Config:
|
|||||||
cnfParse.add_section(cnfSec)
|
cnfParse.add_section(cnfSec)
|
||||||
cnfParse.set(cnfSec, "geometry", self._packList(self.winGeometry))
|
cnfParse.set(cnfSec, "geometry", self._packList(self.winGeometry))
|
||||||
cnfParse.set(cnfSec, "treecols", self._packList(self.treeColWidth))
|
cnfParse.set(cnfSec, "treecols", self._packList(self.treeColWidth))
|
||||||
|
cnfParse.set(cnfSec, "novelcols", self._packList(self.novelColWidth))
|
||||||
cnfParse.set(cnfSec, "projcols", self._packList(self.projColWidth))
|
cnfParse.set(cnfSec, "projcols", self._packList(self.projColWidth))
|
||||||
cnfParse.set(cnfSec, "mainpane", self._packList(self.mainPanePos))
|
cnfParse.set(cnfSec, "mainpane", self._packList(self.mainPanePos))
|
||||||
cnfParse.set(cnfSec, "docpane", self._packList(self.docPanePos))
|
cnfParse.set(cnfSec, "docpane", self._packList(self.docPanePos))
|
||||||
@@ -816,6 +821,11 @@ class Config:
|
|||||||
self.confChanged = True
|
self.confChanged = True
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
def setNovelColWidths(self, colWidths):
|
||||||
|
self.novelColWidth = [int(x/self.guiScale) for x in colWidths]
|
||||||
|
self.confChanged = True
|
||||||
|
return True
|
||||||
|
|
||||||
def setProjColWidths(self, colWidths):
|
def setProjColWidths(self, colWidths):
|
||||||
self.projColWidth = [int(x/self.guiScale) for x in colWidths]
|
self.projColWidth = [int(x/self.guiScale) for x in colWidths]
|
||||||
self.confChanged = True
|
self.confChanged = True
|
||||||
@@ -872,6 +882,9 @@ class Config:
|
|||||||
def getTreeColWidths(self):
|
def getTreeColWidths(self):
|
||||||
return [int(x*self.guiScale) for x in self.treeColWidth]
|
return [int(x*self.guiScale) for x in self.treeColWidth]
|
||||||
|
|
||||||
|
def getNovelColWidths(self):
|
||||||
|
return [int(x*self.guiScale) for x in self.novelColWidth]
|
||||||
|
|
||||||
def getProjColWidths(self):
|
def getProjColWidths(self):
|
||||||
return [int(x*self.guiScale) for x in self.projColWidth]
|
return [int(x*self.guiScale) for x in self.projColWidth]
|
||||||
|
|
||||||
|
|||||||
+147
-128
@@ -51,18 +51,16 @@ class NWIndex():
|
|||||||
self.indexBroken = False
|
self.indexBroken = False
|
||||||
|
|
||||||
# Indices
|
# Indices
|
||||||
self.tagIndex = None
|
self._tagIndex = {}
|
||||||
self.refIndex = None
|
self._refIndex = {}
|
||||||
self.novelIndex = None
|
self._novelIndex = {}
|
||||||
self.noteIndex = None
|
self._noteIndex = {}
|
||||||
self.textCounts = None
|
self._textCounts = {}
|
||||||
|
|
||||||
# TimeStamps
|
# TimeStamps
|
||||||
self.timeNovel = 0
|
self._timeNovel = 0
|
||||||
self.timeNote = 0
|
self._timeNotes = 0
|
||||||
self.timeIndex = 0
|
self._timeIndex = 0
|
||||||
|
|
||||||
self.clearIndex()
|
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -73,14 +71,14 @@ class NWIndex():
|
|||||||
def clearIndex(self):
|
def clearIndex(self):
|
||||||
"""Clear the index dictionaries and time stamps.
|
"""Clear the index dictionaries and time stamps.
|
||||||
"""
|
"""
|
||||||
self.tagIndex = {}
|
self._tagIndex = {}
|
||||||
self.refIndex = {}
|
self._refIndex = {}
|
||||||
self.novelIndex = {}
|
self._novelIndex = {}
|
||||||
self.noteIndex = {}
|
self._noteIndex = {}
|
||||||
self.textCounts = {}
|
self._textCounts = {}
|
||||||
self.timeNovel = 0
|
self._timeNovel = 0
|
||||||
self.timeNote = 0
|
self._timeNotes = 0
|
||||||
self.timeIndex = 0
|
self._timeIndex = 0
|
||||||
return
|
return
|
||||||
|
|
||||||
def deleteHandle(self, tHandle):
|
def deleteHandle(self, tHandle):
|
||||||
@@ -89,17 +87,17 @@ class NWIndex():
|
|||||||
logger.debug("Removing item %s from the index" % tHandle)
|
logger.debug("Removing item %s from the index" % tHandle)
|
||||||
|
|
||||||
delTags = []
|
delTags = []
|
||||||
for tTag in self.tagIndex:
|
for tTag in self._tagIndex:
|
||||||
if self.tagIndex[tTag][1] == tHandle:
|
if self._tagIndex[tTag][1] == tHandle:
|
||||||
delTags.append(tTag)
|
delTags.append(tTag)
|
||||||
|
|
||||||
for tTag in delTags:
|
for tTag in delTags:
|
||||||
self.tagIndex.pop(tTag, None)
|
self._tagIndex.pop(tTag, None)
|
||||||
|
|
||||||
self.refIndex.pop(tHandle, None)
|
self._refIndex.pop(tHandle, None)
|
||||||
self.novelIndex.pop(tHandle, None)
|
self._novelIndex.pop(tHandle, None)
|
||||||
self.noteIndex.pop(tHandle, None)
|
self._noteIndex.pop(tHandle, None)
|
||||||
self.textCounts.pop(tHandle, None)
|
self._textCounts.pop(tHandle, None)
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -123,6 +121,21 @@ class NWIndex():
|
|||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
def novelChangedSince(self, checkTime):
|
||||||
|
"""Check if the novel index has changed since a given time.
|
||||||
|
"""
|
||||||
|
return self._timeNovel > checkTime
|
||||||
|
|
||||||
|
def notesChangedSince(self, checkTime):
|
||||||
|
"""Check if the notes index has changed since a given time.
|
||||||
|
"""
|
||||||
|
return self._timeNotes > checkTime
|
||||||
|
|
||||||
|
def indexChangedSince(self, checkTime):
|
||||||
|
"""Check if the index has changed since a given time.
|
||||||
|
"""
|
||||||
|
return self._timeIndex > checkTime
|
||||||
|
|
||||||
##
|
##
|
||||||
# Load and Save Index to/from File
|
# Load and Save Index to/from File
|
||||||
##
|
##
|
||||||
@@ -143,16 +156,16 @@ class NWIndex():
|
|||||||
logger.error(str(e))
|
logger.error(str(e))
|
||||||
return False
|
return False
|
||||||
|
|
||||||
self.tagIndex = theData.get("tagIndex", {})
|
self._tagIndex = theData.get("tagIndex", {})
|
||||||
self.refIndex = theData.get("refIndex", {})
|
self._refIndex = theData.get("refIndex", {})
|
||||||
self.novelIndex = theData.get("novelIndex", {})
|
self._novelIndex = theData.get("novelIndex", {})
|
||||||
self.noteIndex = theData.get("noteIndex", {})
|
self._noteIndex = theData.get("noteIndex", {})
|
||||||
self.textCounts = theData.get("textCounts", {})
|
self._textCounts = theData.get("textCounts", {})
|
||||||
|
|
||||||
nowTime = round(time())
|
nowTime = round(time())
|
||||||
self.timeNovel = nowTime
|
self._timeNovel = nowTime
|
||||||
self.timeNote = nowTime
|
self._timeNotes = nowTime
|
||||||
self.timeIndex = nowTime
|
self._timeIndex = nowTime
|
||||||
|
|
||||||
self.checkIndex()
|
self.checkIndex()
|
||||||
|
|
||||||
@@ -168,11 +181,11 @@ class NWIndex():
|
|||||||
try:
|
try:
|
||||||
with open(indexFile, mode="w+", encoding="utf8") as outFile:
|
with open(indexFile, mode="w+", encoding="utf8") as outFile:
|
||||||
json.dump({
|
json.dump({
|
||||||
"tagIndex" : self.tagIndex,
|
"tagIndex" : self._tagIndex,
|
||||||
"refIndex" : self.refIndex,
|
"refIndex" : self._refIndex,
|
||||||
"novelIndex" : self.novelIndex,
|
"novelIndex" : self._novelIndex,
|
||||||
"noteIndex" : self.noteIndex,
|
"noteIndex" : self._noteIndex,
|
||||||
"textCounts" : self.textCounts,
|
"textCounts" : self._textCounts,
|
||||||
}, outFile, indent=2)
|
}, outFile, indent=2)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error("Failed to save index file")
|
logger.error("Failed to save index file")
|
||||||
@@ -189,28 +202,28 @@ class NWIndex():
|
|||||||
self.indexBroken = False
|
self.indexBroken = False
|
||||||
|
|
||||||
try:
|
try:
|
||||||
for tTag in self.tagIndex:
|
for tTag in self._tagIndex:
|
||||||
if len(self.tagIndex[tTag]) != 4:
|
if len(self._tagIndex[tTag]) != 4:
|
||||||
self.indexBroken = True
|
self.indexBroken = True
|
||||||
|
|
||||||
for tHandle in self.refIndex:
|
for tHandle in self._refIndex:
|
||||||
for sTitle in self.refIndex[tHandle]:
|
for sTitle in self._refIndex[tHandle]:
|
||||||
for tEntry in self.refIndex[tHandle][sTitle]["tags"]:
|
for tEntry in self._refIndex[tHandle][sTitle]["tags"]:
|
||||||
if len(tEntry) != 3:
|
if len(tEntry) != 3:
|
||||||
self.indexBroken = True
|
self.indexBroken = True
|
||||||
|
|
||||||
for tHandle in self.novelIndex:
|
for tHandle in self._novelIndex:
|
||||||
for sLine in self.novelIndex[tHandle]:
|
for sLine in self._novelIndex[tHandle]:
|
||||||
if len(self.novelIndex[tHandle][sLine].keys()) != 8:
|
if len(self._novelIndex[tHandle][sLine].keys()) != 8:
|
||||||
self.indexBroken = True
|
self.indexBroken = True
|
||||||
|
|
||||||
for tHandle in self.noteIndex:
|
for tHandle in self._noteIndex:
|
||||||
for sLine in self.noteIndex[tHandle]:
|
for sLine in self._noteIndex[tHandle]:
|
||||||
if len(self.noteIndex[tHandle][sLine].keys()) != 8:
|
if len(self._noteIndex[tHandle][sLine].keys()) != 8:
|
||||||
self.indexBroken = True
|
self.indexBroken = True
|
||||||
|
|
||||||
for tHandle in self.textCounts:
|
for tHandle in self._textCounts:
|
||||||
if len(self.textCounts[tHandle]) != 3:
|
if len(self._textCounts[tHandle]) != 3:
|
||||||
self.indexBroken = True
|
self.indexBroken = True
|
||||||
|
|
||||||
except Exception:
|
except Exception:
|
||||||
@@ -254,7 +267,7 @@ class NWIndex():
|
|||||||
|
|
||||||
# Run word counter for the whole text
|
# Run word counter for the whole text
|
||||||
cC, wC, pC = countWords(theText)
|
cC, wC, pC = countWords(theText)
|
||||||
self.textCounts[tHandle] = [cC, wC, pC]
|
self._textCounts[tHandle] = [cC, wC, pC]
|
||||||
|
|
||||||
# If the file is archived or trashed, we don't index the file itself
|
# If the file is archived or trashed, we don't index the file itself
|
||||||
if self.theProject.projTree.isTrashRoot(theItem.itemParent):
|
if self.theProject.projTree.isTrashRoot(theItem.itemParent):
|
||||||
@@ -271,25 +284,25 @@ class NWIndex():
|
|||||||
|
|
||||||
# Check file type, and reset its old index
|
# Check file type, and reset its old index
|
||||||
# Also add a dummy entry T000000 in case the file has no title
|
# Also add a dummy entry T000000 in case the file has no title
|
||||||
self.refIndex[tHandle] = {}
|
self._refIndex[tHandle] = {}
|
||||||
self.refIndex[tHandle]["T000000"] = {
|
self._refIndex[tHandle]["T000000"] = {
|
||||||
"tags" : [],
|
"tags" : [],
|
||||||
"updated" : round(time()),
|
"updated" : round(time()),
|
||||||
}
|
}
|
||||||
if itemLayout == nwItemLayout.NOTE:
|
if itemLayout == nwItemLayout.NOTE:
|
||||||
self.noteIndex[tHandle] = {}
|
self._noteIndex[tHandle] = {}
|
||||||
isNovel = False
|
isNovel = False
|
||||||
else:
|
else:
|
||||||
self.novelIndex[tHandle] = {}
|
self._novelIndex[tHandle] = {}
|
||||||
isNovel = True
|
isNovel = True
|
||||||
|
|
||||||
# Also clear references to file in tag index
|
# Also clear references to file in tag index
|
||||||
clearTags = []
|
clearTags = []
|
||||||
for aTag in self.tagIndex:
|
for aTag in self._tagIndex:
|
||||||
if self.tagIndex[aTag][1] == tHandle:
|
if self._tagIndex[aTag][1] == tHandle:
|
||||||
clearTags.append(aTag)
|
clearTags.append(aTag)
|
||||||
for aTag in clearTags:
|
for aTag in clearTags:
|
||||||
self.tagIndex.pop(aTag)
|
self._tagIndex.pop(aTag)
|
||||||
|
|
||||||
nLine = 0
|
nLine = 0
|
||||||
nTitle = 0
|
nTitle = 0
|
||||||
@@ -330,11 +343,11 @@ class NWIndex():
|
|||||||
|
|
||||||
# Update timestamps for index changes
|
# Update timestamps for index changes
|
||||||
nowTime = round(time())
|
nowTime = round(time())
|
||||||
self.timeIndex = nowTime
|
self._timeIndex = nowTime
|
||||||
if isNovel:
|
if isNovel:
|
||||||
self.timeNovel = nowTime
|
self._timeNovel = nowTime
|
||||||
else:
|
else:
|
||||||
self.timeNote = nowTime
|
self._timeNotes = nowTime
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
@@ -362,7 +375,7 @@ class NWIndex():
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
sTitle = "T%06d" % nLine
|
sTitle = "T%06d" % nLine
|
||||||
self.refIndex[tHandle][sTitle] = {
|
self._refIndex[tHandle][sTitle] = {
|
||||||
"tags" : [],
|
"tags" : [],
|
||||||
"updated" : round(time()),
|
"updated" : round(time()),
|
||||||
}
|
}
|
||||||
@@ -379,11 +392,11 @@ class NWIndex():
|
|||||||
|
|
||||||
if hText != "":
|
if hText != "":
|
||||||
if isNovel:
|
if isNovel:
|
||||||
if tHandle in self.novelIndex:
|
if tHandle in self._novelIndex:
|
||||||
self.novelIndex[tHandle][sTitle] = theData
|
self._novelIndex[tHandle][sTitle] = theData
|
||||||
else:
|
else:
|
||||||
if tHandle in self.noteIndex:
|
if tHandle in self._noteIndex:
|
||||||
self.noteIndex[tHandle][sTitle] = theData
|
self._noteIndex[tHandle][sTitle] = theData
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
@@ -393,19 +406,19 @@ class NWIndex():
|
|||||||
cC, wC, pC = countWords(theText)
|
cC, wC, pC = countWords(theText)
|
||||||
sTitle = "T%06d" % nTitle
|
sTitle = "T%06d" % nTitle
|
||||||
if isNovel:
|
if isNovel:
|
||||||
if tHandle in self.novelIndex:
|
if tHandle in self._novelIndex:
|
||||||
if sTitle in self.novelIndex[tHandle]:
|
if sTitle in self._novelIndex[tHandle]:
|
||||||
self.novelIndex[tHandle][sTitle]["cCount"] = cC
|
self._novelIndex[tHandle][sTitle]["cCount"] = cC
|
||||||
self.novelIndex[tHandle][sTitle]["wCount"] = wC
|
self._novelIndex[tHandle][sTitle]["wCount"] = wC
|
||||||
self.novelIndex[tHandle][sTitle]["pCount"] = pC
|
self._novelIndex[tHandle][sTitle]["pCount"] = pC
|
||||||
self.novelIndex[tHandle][sTitle]["updated"] = round(time())
|
self._novelIndex[tHandle][sTitle]["updated"] = round(time())
|
||||||
else:
|
else:
|
||||||
if tHandle in self.noteIndex:
|
if tHandle in self._noteIndex:
|
||||||
if sTitle in self.noteIndex[tHandle]:
|
if sTitle in self._noteIndex[tHandle]:
|
||||||
self.noteIndex[tHandle][sTitle]["cCount"] = cC
|
self._noteIndex[tHandle][sTitle]["cCount"] = cC
|
||||||
self.noteIndex[tHandle][sTitle]["wCount"] = wC
|
self._noteIndex[tHandle][sTitle]["wCount"] = wC
|
||||||
self.noteIndex[tHandle][sTitle]["pCount"] = pC
|
self._noteIndex[tHandle][sTitle]["pCount"] = pC
|
||||||
self.noteIndex[tHandle][sTitle]["updated"] = round(time())
|
self._noteIndex[tHandle][sTitle]["updated"] = round(time())
|
||||||
return
|
return
|
||||||
|
|
||||||
def _indexSynopsis(self, tHandle, isNovel, theText, nTitle):
|
def _indexSynopsis(self, tHandle, isNovel, theText, nTitle):
|
||||||
@@ -413,15 +426,15 @@ class NWIndex():
|
|||||||
"""
|
"""
|
||||||
sTitle = "T%06d" % nTitle
|
sTitle = "T%06d" % nTitle
|
||||||
if isNovel:
|
if isNovel:
|
||||||
if tHandle in self.novelIndex:
|
if tHandle in self._novelIndex:
|
||||||
if sTitle in self.novelIndex[tHandle]:
|
if sTitle in self._novelIndex[tHandle]:
|
||||||
self.novelIndex[tHandle][sTitle]["synopsis"] = theText
|
self._novelIndex[tHandle][sTitle]["synopsis"] = theText
|
||||||
self.novelIndex[tHandle][sTitle]["updated"] = round(time())
|
self._novelIndex[tHandle][sTitle]["updated"] = round(time())
|
||||||
else:
|
else:
|
||||||
if tHandle in self.noteIndex:
|
if tHandle in self._noteIndex:
|
||||||
if sTitle in self.noteIndex[tHandle]:
|
if sTitle in self._noteIndex[tHandle]:
|
||||||
self.noteIndex[tHandle][sTitle]["synopsis"] = theText
|
self._noteIndex[tHandle][sTitle]["synopsis"] = theText
|
||||||
self.noteIndex[tHandle][sTitle]["updated"] = round(time())
|
self._noteIndex[tHandle][sTitle]["updated"] = round(time())
|
||||||
return
|
return
|
||||||
|
|
||||||
def _indexNoteRef(self, tHandle, aLine, nLine, nTitle):
|
def _indexNoteRef(self, tHandle, aLine, nLine, nTitle):
|
||||||
@@ -433,9 +446,9 @@ class NWIndex():
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
sTitle = "T%06d" % nTitle
|
sTitle = "T%06d" % nTitle
|
||||||
if sTitle in self.refIndex[tHandle] and theBits[0] != nwKeyWords.TAG_KEY:
|
if sTitle in self._refIndex[tHandle] and theBits[0] != nwKeyWords.TAG_KEY:
|
||||||
for aVal in theBits[1:]:
|
for aVal in theBits[1:]:
|
||||||
self.refIndex[tHandle][sTitle]["tags"].append([nLine, theBits[0], aVal])
|
self._refIndex[tHandle][sTitle]["tags"].append([nLine, theBits[0], aVal])
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
@@ -448,7 +461,7 @@ class NWIndex():
|
|||||||
|
|
||||||
if theBits[0] == nwKeyWords.TAG_KEY:
|
if theBits[0] == nwKeyWords.TAG_KEY:
|
||||||
sTitle = "T%06d" % nTitle
|
sTitle = "T%06d" % nTitle
|
||||||
self.tagIndex[theBits[1]] = [nLine, tHandle, itemClass.name, sTitle]
|
self._tagIndex[theBits[1]] = [nLine, tHandle, itemClass.name, sTitle]
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
@@ -512,8 +525,8 @@ class NWIndex():
|
|||||||
# is ignored
|
# is ignored
|
||||||
if theBits[0] == nwKeyWords.TAG_KEY and nBits > 1:
|
if theBits[0] == nwKeyWords.TAG_KEY and nBits > 1:
|
||||||
isGood[0] = True
|
isGood[0] = True
|
||||||
if theBits[1] in self.tagIndex:
|
if theBits[1] in self._tagIndex:
|
||||||
if self.tagIndex[theBits[1]][1] == tItem.itemHandle:
|
if self._tagIndex[theBits[1]][1] == tItem.itemHandle:
|
||||||
isGood[1] = True
|
isGood[1] = True
|
||||||
else:
|
else:
|
||||||
isGood[1] = False
|
isGood[1] = False
|
||||||
@@ -523,8 +536,8 @@ class NWIndex():
|
|||||||
|
|
||||||
# If we're still here, we better check that the references exist
|
# If we're still here, we better check that the references exist
|
||||||
for n in range(1, nBits):
|
for n in range(1, nBits):
|
||||||
if theBits[n] in self.tagIndex:
|
if theBits[n] in self._tagIndex:
|
||||||
isGood[n] = nwKeyWords.KEY_CLASS[theBits[0]].name == self.tagIndex[theBits[n]][2]
|
isGood[n] = nwKeyWords.KEY_CLASS[theBits[0]].name == self._tagIndex[theBits[n]][2]
|
||||||
|
|
||||||
return isGood
|
return isGood
|
||||||
|
|
||||||
@@ -532,23 +545,21 @@ class NWIndex():
|
|||||||
# Extract Data
|
# Extract Data
|
||||||
##
|
##
|
||||||
|
|
||||||
def getNovelStructure(self, skipExcluded=True):
|
def novelStructure(self, skipExcluded=True):
|
||||||
"""Builds a list of all titles in the novel, in the correct
|
"""Iterate over all titles in the novel, in the correct order as
|
||||||
order as they appear in the tree view and in the respective
|
they appear in the tree view and in the respective document
|
||||||
document files, but skipping all note files.
|
files, but skipping all note files.
|
||||||
"""
|
"""
|
||||||
theStructure = []
|
|
||||||
for tItem in self.theProject.projTree:
|
for tItem in self.theProject.projTree:
|
||||||
if tItem is not None:
|
if tItem is not None:
|
||||||
if not tItem.isExported and skipExcluded:
|
if not tItem.isExported and skipExcluded:
|
||||||
continue
|
continue
|
||||||
tHandle = tItem.itemHandle
|
tHandle = tItem.itemHandle
|
||||||
if tHandle not in self.novelIndex:
|
if tHandle not in self._novelIndex:
|
||||||
continue
|
continue
|
||||||
for sTitle in sorted(self.novelIndex[tHandle].keys()):
|
for sTitle in sorted(self._novelIndex[tHandle]):
|
||||||
theStructure.append("%s:%s" % (tHandle, sTitle))
|
tKey = "%s:%s" % (tHandle, sTitle)
|
||||||
|
yield tKey, tHandle, sTitle, self._novelIndex[tHandle][sTitle]
|
||||||
return theStructure
|
|
||||||
|
|
||||||
def getCounts(self, tHandle, sTitle=None):
|
def getCounts(self, tHandle, sTitle=None):
|
||||||
"""Returns the counts for a file, or a section of a file
|
"""Returns the counts for a file, or a section of a file
|
||||||
@@ -559,21 +570,21 @@ class NWIndex():
|
|||||||
pC = 0
|
pC = 0
|
||||||
|
|
||||||
if sTitle is None:
|
if sTitle is None:
|
||||||
if tHandle in self.textCounts:
|
if tHandle in self._textCounts:
|
||||||
cC = self.textCounts[tHandle][0]
|
cC = self._textCounts[tHandle][0]
|
||||||
wC = self.textCounts[tHandle][1]
|
wC = self._textCounts[tHandle][1]
|
||||||
pC = self.textCounts[tHandle][2]
|
pC = self._textCounts[tHandle][2]
|
||||||
else:
|
else:
|
||||||
if tHandle in self.novelIndex:
|
if tHandle in self._novelIndex:
|
||||||
if sTitle in self.novelIndex[tHandle]:
|
if sTitle in self._novelIndex[tHandle]:
|
||||||
cC = self.novelIndex[tHandle][sTitle]["cCount"]
|
cC = self._novelIndex[tHandle][sTitle]["cCount"]
|
||||||
wC = self.novelIndex[tHandle][sTitle]["wCount"]
|
wC = self._novelIndex[tHandle][sTitle]["wCount"]
|
||||||
pC = self.novelIndex[tHandle][sTitle]["pCount"]
|
pC = self._novelIndex[tHandle][sTitle]["pCount"]
|
||||||
elif tHandle in self.noteIndex:
|
elif tHandle in self._noteIndex:
|
||||||
if sTitle in self.noteIndex[tHandle]:
|
if sTitle in self._noteIndex[tHandle]:
|
||||||
cC = self.noteIndex[tHandle][sTitle]["cCount"]
|
cC = self._noteIndex[tHandle][sTitle]["cCount"]
|
||||||
wC = self.noteIndex[tHandle][sTitle]["wCount"]
|
wC = self._noteIndex[tHandle][sTitle]["wCount"]
|
||||||
pC = self.noteIndex[tHandle][sTitle]["pCount"]
|
pC = self._noteIndex[tHandle][sTitle]["pCount"]
|
||||||
|
|
||||||
return cC, wC, pC
|
return cC, wC, pC
|
||||||
|
|
||||||
@@ -585,16 +596,24 @@ class NWIndex():
|
|||||||
for tKey in nwKeyWords.KEY_CLASS:
|
for tKey in nwKeyWords.KEY_CLASS:
|
||||||
theRefs[tKey] = []
|
theRefs[tKey] = []
|
||||||
|
|
||||||
if tHandle not in self.refIndex:
|
if tHandle not in self._refIndex:
|
||||||
return theRefs
|
return theRefs
|
||||||
|
|
||||||
for refTitle in self.refIndex[tHandle]:
|
for refTitle in self._refIndex[tHandle]:
|
||||||
for aTag in self.refIndex[tHandle][refTitle].get("tags", []):
|
for aTag in self._refIndex[tHandle][refTitle].get("tags", []):
|
||||||
if len(aTag) == 3 and (sTitle is None or sTitle == refTitle):
|
if len(aTag) == 3 and (sTitle is None or sTitle == refTitle):
|
||||||
theRefs[aTag[1]].append(aTag[2])
|
theRefs[aTag[1]].append(aTag[2])
|
||||||
|
|
||||||
return theRefs
|
return theRefs
|
||||||
|
|
||||||
|
def getNovelData(self, tHandle, sTitle):
|
||||||
|
"""Return the novel data of a given handle and title.
|
||||||
|
"""
|
||||||
|
if tHandle in self._novelIndex:
|
||||||
|
if sTitle in self._novelIndex[tHandle]:
|
||||||
|
return self._novelIndex[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.
|
||||||
@@ -604,14 +623,14 @@ class NWIndex():
|
|||||||
return theRefs
|
return theRefs
|
||||||
|
|
||||||
theTags = set()
|
theTags = set()
|
||||||
for tTag in self.tagIndex:
|
for tTag in self._tagIndex:
|
||||||
if tHandle == self.tagIndex[tTag][1]:
|
if tHandle == self._tagIndex[tTag][1]:
|
||||||
theTags.add(tTag)
|
theTags.add(tTag)
|
||||||
|
|
||||||
if theTags:
|
if theTags:
|
||||||
for tHandle in self.refIndex:
|
for tHandle in self._refIndex:
|
||||||
for sTitle in self.refIndex[tHandle]:
|
for sTitle in self._refIndex[tHandle]:
|
||||||
for _, _, tTag in self.refIndex[tHandle][sTitle]["tags"]:
|
for _, _, tTag in self._refIndex[tHandle][sTitle]["tags"]:
|
||||||
if tTag in theTags and tHandle not in theRefs:
|
if tTag in theTags and tHandle not in theRefs:
|
||||||
theRefs[tHandle] = sTitle
|
theRefs[tHandle] = sTitle
|
||||||
|
|
||||||
@@ -620,8 +639,8 @@ class NWIndex():
|
|||||||
def getTagSource(self, theTag):
|
def getTagSource(self, theTag):
|
||||||
"""Return the source location of a given tag.
|
"""Return the source location of a given tag.
|
||||||
"""
|
"""
|
||||||
if theTag in self.tagIndex:
|
if theTag in self._tagIndex:
|
||||||
theRef = self.tagIndex[theTag]
|
theRef = self._tagIndex[theTag]
|
||||||
if len(theRef) == 4:
|
if len(theRef) == 4:
|
||||||
return theRef[1], theRef[0], theRef[3]
|
return theRef[1], theRef[0], theRef[3]
|
||||||
return None, 0, "T000000"
|
return None, 0, "T000000"
|
||||||
|
|||||||
+2
-2
@@ -1025,7 +1025,7 @@ class NWProject():
|
|||||||
by drag-and-drop. Forwarded to the NWTree class.
|
by drag-and-drop. Forwarded to the NWTree class.
|
||||||
"""
|
"""
|
||||||
if len(self.projTree) != len(newOrder):
|
if len(self.projTree) != len(newOrder):
|
||||||
logger.warning("Size of new and old tree order do not match")
|
logger.warning("Sizes of new and old tree order do not match")
|
||||||
self.projTree.setOrder(newOrder)
|
self.projTree.setOrder(newOrder)
|
||||||
self.setProjectChanged(True)
|
self.setProjectChanged(True)
|
||||||
return True
|
return True
|
||||||
@@ -1341,7 +1341,7 @@ class NWProject():
|
|||||||
if oLayout is None:
|
if oLayout is None:
|
||||||
oLayout = nwItemLayout.NOTE
|
oLayout = nwItemLayout.NOTE
|
||||||
|
|
||||||
if oParent is None or not self.projTree.handleExists(oParent):
|
if oParent is None or oParent not in self.projTree:
|
||||||
oParent = self.projTree.findRoot(oClass)
|
oParent = self.projTree.findRoot(oClass)
|
||||||
if oParent is None:
|
if oParent is None:
|
||||||
oParent = self.projTree.findRoot(nwItemClass.NOVEL)
|
oParent = self.projTree.findRoot(nwItemClass.NOVEL)
|
||||||
|
|||||||
@@ -294,11 +294,6 @@ class NWTree():
|
|||||||
tTree.append(tHandle)
|
tTree.append(tHandle)
|
||||||
return tTree
|
return tTree
|
||||||
|
|
||||||
def handleExists(self, tHandle):
|
|
||||||
"""Check if a handle exists in the project.
|
|
||||||
"""
|
|
||||||
return tHandle in self._treeOrder
|
|
||||||
|
|
||||||
##
|
##
|
||||||
# Setters
|
# Setters
|
||||||
##
|
##
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ from nw.gui.docviewer import GuiDocViewer, GuiDocViewDetails
|
|||||||
from nw.gui.itemdetails import GuiItemDetails
|
from nw.gui.itemdetails import GuiItemDetails
|
||||||
from nw.gui.itemeditor import GuiItemEditor
|
from nw.gui.itemeditor import GuiItemEditor
|
||||||
from nw.gui.mainmenu import GuiMainMenu
|
from nw.gui.mainmenu import GuiMainMenu
|
||||||
|
from nw.gui.noveltree import GuiNovelTree
|
||||||
from nw.gui.outline import GuiOutline
|
from nw.gui.outline import GuiOutline
|
||||||
from nw.gui.outlinedetails import GuiOutlineDetails
|
from nw.gui.outlinedetails import GuiOutlineDetails
|
||||||
from nw.gui.preferences import GuiPreferences
|
from nw.gui.preferences import GuiPreferences
|
||||||
@@ -31,6 +32,7 @@ __all__ = [
|
|||||||
"GuiItemDetails",
|
"GuiItemDetails",
|
||||||
"GuiItemEditor",
|
"GuiItemEditor",
|
||||||
"GuiMainMenu",
|
"GuiMainMenu",
|
||||||
|
"GuiNovelTree",
|
||||||
"GuiMainStatus",
|
"GuiMainStatus",
|
||||||
"GuiOutline",
|
"GuiOutline",
|
||||||
"GuiOutlineDetails",
|
"GuiOutlineDetails",
|
||||||
|
|||||||
@@ -0,0 +1,312 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""novelWriter GUI Novel Tree
|
||||||
|
|
||||||
|
novelWriter – GUI Novel Tree
|
||||||
|
==============================
|
||||||
|
Class holding the project's novel files tree view
|
||||||
|
|
||||||
|
File History:
|
||||||
|
Created: 2020-12-20 [1.1a0]
|
||||||
|
|
||||||
|
This file is a part of novelWriter
|
||||||
|
Copyright 2018–2020, Veronica Berglyd Olsen
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful, but
|
||||||
|
WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||||
|
General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import nw
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from time import time
|
||||||
|
|
||||||
|
from PyQt5.QtCore import Qt, QSize
|
||||||
|
from PyQt5.QtWidgets import QTreeWidget, QTreeWidgetItem, QAbstractItemView
|
||||||
|
|
||||||
|
from nw.constants import nwKeyWords
|
||||||
|
from nw.common import checkInt
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
class GuiNovelTree(QTreeWidget):
|
||||||
|
|
||||||
|
C_TITLE = 0
|
||||||
|
C_WORDS = 1
|
||||||
|
C_POV = 2
|
||||||
|
|
||||||
|
def __init__(self, theParent):
|
||||||
|
QTreeWidget.__init__(self, theParent)
|
||||||
|
|
||||||
|
logger.debug("Initialising GuiNovelTree ...")
|
||||||
|
|
||||||
|
self.mainConf = nw.CONFIG
|
||||||
|
self.theParent = theParent
|
||||||
|
self.theTheme = theParent.theTheme
|
||||||
|
self.theProject = theParent.theProject
|
||||||
|
self.theIndex = theParent.theIndex
|
||||||
|
|
||||||
|
# Internal Variables
|
||||||
|
self._treeMap = {}
|
||||||
|
self._lastBuild = 0
|
||||||
|
|
||||||
|
# Build GUI
|
||||||
|
iPx = self.theTheme.baseIconSize
|
||||||
|
self.setIconSize(QSize(iPx, iPx))
|
||||||
|
self.setIndentation(iPx)
|
||||||
|
self.setColumnCount(3)
|
||||||
|
self.setHeaderLabels(["Title", "Words", "POV"])
|
||||||
|
self.itemDoubleClicked.connect(self._treeDoubleClick)
|
||||||
|
self.itemSelectionChanged.connect(self._itemSelected)
|
||||||
|
self.setSelectionBehavior(QAbstractItemView.SelectRows)
|
||||||
|
self.setSelectionMode(QAbstractItemView.SingleSelection)
|
||||||
|
self.setExpandsOnDoubleClick(False)
|
||||||
|
self.setDragEnabled(False)
|
||||||
|
|
||||||
|
treeHeadItem = self.headerItem()
|
||||||
|
treeHeadItem.setTextAlignment(self.C_WORDS, Qt.AlignRight)
|
||||||
|
treeHeadItem.setToolTip(self.C_TITLE, "Section title")
|
||||||
|
treeHeadItem.setToolTip(self.C_WORDS, "Word count")
|
||||||
|
treeHeadItem.setToolTip(self.C_POV, "Point-of-view character")
|
||||||
|
|
||||||
|
treeHeader = self.header()
|
||||||
|
treeHeader.setStretchLastSection(True)
|
||||||
|
treeHeader.setMinimumSectionSize(iPx + 6)
|
||||||
|
|
||||||
|
# Get user's column width preferences for NAME and COUNT
|
||||||
|
treeColWidth = self.mainConf.getNovelColWidths()
|
||||||
|
if len(treeColWidth) <= 3:
|
||||||
|
for colN, colW in enumerate(treeColWidth):
|
||||||
|
self.setColumnWidth(colN, colW)
|
||||||
|
|
||||||
|
# The last column should just auto-scale
|
||||||
|
self.resizeColumnToContents(self.C_POV)
|
||||||
|
|
||||||
|
# Set custom settings
|
||||||
|
self.initTree()
|
||||||
|
|
||||||
|
logger.debug("GuiNovelTree initialisation complete")
|
||||||
|
|
||||||
|
# Internal Mapping
|
||||||
|
self.makeAlert = self.theParent.makeAlert
|
||||||
|
|
||||||
|
return
|
||||||
|
|
||||||
|
def initTree(self):
|
||||||
|
"""Set or update tree widget settings.
|
||||||
|
"""
|
||||||
|
# Scroll bars
|
||||||
|
if self.mainConf.hideVScroll:
|
||||||
|
self.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
|
||||||
|
else:
|
||||||
|
self.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded)
|
||||||
|
|
||||||
|
if self.mainConf.hideHScroll:
|
||||||
|
self.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
|
||||||
|
else:
|
||||||
|
self.setHorizontalScrollBarPolicy(Qt.ScrollBarAsNeeded)
|
||||||
|
|
||||||
|
return
|
||||||
|
|
||||||
|
##
|
||||||
|
# Class Methods
|
||||||
|
##
|
||||||
|
|
||||||
|
def clearTree(self):
|
||||||
|
"""Clear the GUI content and the related maps.
|
||||||
|
"""
|
||||||
|
self.clear()
|
||||||
|
self._treeMap = {}
|
||||||
|
self._lastBuild = 0
|
||||||
|
return
|
||||||
|
|
||||||
|
def refreshTree(self, overRide=False):
|
||||||
|
"""Called whenever the Novel tab is activated.
|
||||||
|
"""
|
||||||
|
treeChanged = self.theParent.treeView.changedSince(self._lastBuild)
|
||||||
|
indexChanged = self.theIndex.novelChangedSince(self._lastBuild)
|
||||||
|
if not (treeChanged or indexChanged):
|
||||||
|
logger.verbose("No changes made to the novel")
|
||||||
|
return
|
||||||
|
|
||||||
|
selItem = self.selectedItems()
|
||||||
|
titleKey = None
|
||||||
|
if selItem:
|
||||||
|
titleKey = selItem[0].data(self.C_TITLE, Qt.UserRole)[2]
|
||||||
|
|
||||||
|
self.theParent.treeView.flushTreeOrder()
|
||||||
|
self._populateTree()
|
||||||
|
|
||||||
|
if titleKey is not None and titleKey in self._treeMap:
|
||||||
|
self._treeMap[titleKey].setSelected(True)
|
||||||
|
|
||||||
|
return
|
||||||
|
|
||||||
|
def getColumnSizes(self):
|
||||||
|
"""Return the column widths for the tree columns.
|
||||||
|
"""
|
||||||
|
retVals = [
|
||||||
|
self.columnWidth(0),
|
||||||
|
self.columnWidth(1),
|
||||||
|
]
|
||||||
|
return retVals
|
||||||
|
|
||||||
|
def getSelectedHandle(self):
|
||||||
|
"""Get the currently selected handle. If multiple items are
|
||||||
|
selected, return the first.
|
||||||
|
"""
|
||||||
|
selItem = self.selectedItems()
|
||||||
|
if selItem:
|
||||||
|
return selItem[0].data(self.C_TITLE, Qt.UserRole)[0]
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
##
|
||||||
|
# Events
|
||||||
|
##
|
||||||
|
|
||||||
|
def mousePressEvent(self, theEvent):
|
||||||
|
"""Overload mousePressEvent to clear selection if clicking the
|
||||||
|
mouse in a blank area of the tree view, and to load a document
|
||||||
|
for viewing if the user middle-clicked.
|
||||||
|
"""
|
||||||
|
QTreeWidget.mousePressEvent(self, theEvent)
|
||||||
|
|
||||||
|
if theEvent.button() == Qt.LeftButton:
|
||||||
|
selItem = self.indexAt(theEvent.pos())
|
||||||
|
if not selItem.isValid():
|
||||||
|
self.clearSelection()
|
||||||
|
|
||||||
|
elif theEvent.button() == Qt.MiddleButton:
|
||||||
|
selItem = self.itemAt(theEvent.pos())
|
||||||
|
if not isinstance(selItem, QTreeWidgetItem):
|
||||||
|
return
|
||||||
|
|
||||||
|
tHandle = self.getSelectedHandle()
|
||||||
|
if tHandle is None:
|
||||||
|
return
|
||||||
|
|
||||||
|
self.theParent.viewDocument(tHandle)
|
||||||
|
|
||||||
|
return
|
||||||
|
|
||||||
|
##
|
||||||
|
# Slots
|
||||||
|
##
|
||||||
|
|
||||||
|
def _treeDoubleClick(self, tItem, tCol):
|
||||||
|
"""Extract the handle and line number of the title double-
|
||||||
|
clicked, and send it to the main gui class for opening in the
|
||||||
|
document editor.
|
||||||
|
"""
|
||||||
|
theData = tItem.data(self.C_TITLE, Qt.UserRole)
|
||||||
|
tHandle = theData[0]
|
||||||
|
tLine = checkInt(theData[1], 1)
|
||||||
|
|
||||||
|
logger.verbose("User selected entry with handle %s on line %s" % (tHandle, tLine))
|
||||||
|
self.theParent.openDocument(tHandle, tLine=tLine-1, doScroll=True)
|
||||||
|
|
||||||
|
return
|
||||||
|
|
||||||
|
def _itemSelected(self):
|
||||||
|
"""Extract the handle and line number of the currently selected
|
||||||
|
title, and send it to the tree meta panel.
|
||||||
|
"""
|
||||||
|
selItems = self.selectedItems()
|
||||||
|
if selItems:
|
||||||
|
tHandle = selItems[0].data(self.C_TITLE, Qt.UserRole)[0]
|
||||||
|
self.theParent.treeMeta.updateViewBox(tHandle)
|
||||||
|
|
||||||
|
return
|
||||||
|
|
||||||
|
##
|
||||||
|
# Internal Functions
|
||||||
|
##
|
||||||
|
|
||||||
|
def _populateTree(self):
|
||||||
|
"""Build the tree based on the project index.
|
||||||
|
"""
|
||||||
|
self.clearTree()
|
||||||
|
|
||||||
|
currTitle = None
|
||||||
|
currChapter = None
|
||||||
|
currScene = None
|
||||||
|
|
||||||
|
for tKey, tHandle, sTitle, novIdx in self.theIndex.novelStructure(skipExcluded=True):
|
||||||
|
|
||||||
|
tItem = self._createTreeItem(tHandle, sTitle, tKey, novIdx)
|
||||||
|
self._treeMap[tKey] = tItem
|
||||||
|
|
||||||
|
tLevel = novIdx["level"]
|
||||||
|
if tLevel == "H1":
|
||||||
|
self.addTopLevelItem(tItem)
|
||||||
|
currTitle = tItem
|
||||||
|
currChapter = None
|
||||||
|
currScene = None
|
||||||
|
|
||||||
|
elif tLevel == "H2":
|
||||||
|
if currTitle is None:
|
||||||
|
self.addTopLevelItem(tItem)
|
||||||
|
else:
|
||||||
|
currTitle.addChild(tItem)
|
||||||
|
currChapter = tItem
|
||||||
|
currScene = None
|
||||||
|
|
||||||
|
elif tLevel == "H3":
|
||||||
|
if currChapter is None:
|
||||||
|
if currTitle is None:
|
||||||
|
self.addTopLevelItem(tItem)
|
||||||
|
else:
|
||||||
|
currTitle.addChild(tItem)
|
||||||
|
else:
|
||||||
|
currChapter.addChild(tItem)
|
||||||
|
currScene = tItem
|
||||||
|
|
||||||
|
elif tLevel == "H4":
|
||||||
|
if currScene is None:
|
||||||
|
if currChapter is None:
|
||||||
|
if currTitle is None:
|
||||||
|
self.addTopLevelItem(tItem)
|
||||||
|
else:
|
||||||
|
currTitle.addChild(tItem)
|
||||||
|
else:
|
||||||
|
currChapter.addChild(tItem)
|
||||||
|
else:
|
||||||
|
currScene.addChild(tItem)
|
||||||
|
|
||||||
|
tItem.setExpanded(True)
|
||||||
|
|
||||||
|
self._lastBuild = time()
|
||||||
|
|
||||||
|
return
|
||||||
|
|
||||||
|
def _createTreeItem(self, tHandle, sTitle, titleKey, novIdx):
|
||||||
|
"""Populate a tree item with all the column values.
|
||||||
|
"""
|
||||||
|
newItem = QTreeWidgetItem()
|
||||||
|
hIcon = "doc_%s" % novIdx["level"].lower()
|
||||||
|
theData = (tHandle, sTitle[1:].lstrip("0"), titleKey)
|
||||||
|
|
||||||
|
wC = int(novIdx["wCount"])
|
||||||
|
|
||||||
|
newItem.setText(self.C_TITLE, novIdx["title"])
|
||||||
|
newItem.setData(self.C_TITLE, Qt.UserRole, theData)
|
||||||
|
newItem.setIcon(self.C_TITLE, self.theTheme.getIcon(hIcon))
|
||||||
|
newItem.setText(self.C_WORDS, f"{wC:n}")
|
||||||
|
newItem.setTextAlignment(self.C_WORDS, Qt.AlignRight)
|
||||||
|
|
||||||
|
theRefs = self.theIndex.getReferences(tHandle, sTitle)
|
||||||
|
newItem.setText(self.C_POV, ", ".join(theRefs[nwKeyWords.POV_KEY]))
|
||||||
|
|
||||||
|
return newItem
|
||||||
|
|
||||||
|
# END Class GuiNovelTree
|
||||||
+13
-28
@@ -165,7 +165,7 @@ class GuiOutline(QTreeWidget):
|
|||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
def refreshTree(self, overRide=False):
|
def refreshTree(self, overRide=False, novelChanged=False):
|
||||||
"""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.
|
||||||
@@ -177,13 +177,10 @@ class GuiOutline(QTreeWidget):
|
|||||||
self.firstView = False
|
self.firstView = False
|
||||||
return
|
return
|
||||||
|
|
||||||
# If the novel index has changed since the tree was last built,
|
# If the novel index or novel tree has changed since the tree
|
||||||
# we rebuild the tree from the updated index.
|
# was last built, we rebuild the tree from the updated index.
|
||||||
lastChange = self.theParent.theIndex.timeNovel
|
indexChanged = self.theIndex.novelChangedSince(self.lastBuild)
|
||||||
logger.verbose("Last outline build: %.3f" % self.lastBuild)
|
doBuild = (novelChanged or indexChanged) and self.theProject.autoOutline
|
||||||
logger.verbose("Novel index change: %.3f" % lastChange)
|
|
||||||
|
|
||||||
doBuild = lastChange > self.lastBuild and self.theProject.autoOutline
|
|
||||||
if doBuild or overRide:
|
if doBuild or overRide:
|
||||||
logger.debug("Rebuilding Project Outline")
|
logger.debug("Rebuilding Project Outline")
|
||||||
self._populateTree()
|
self._populateTree()
|
||||||
@@ -227,6 +224,7 @@ class GuiOutline(QTreeWidget):
|
|||||||
tHandle = selItems[0].data(self.colIndex[nwOutline.TITLE], Qt.UserRole)
|
tHandle = selItems[0].data(self.colIndex[nwOutline.TITLE], Qt.UserRole)
|
||||||
sTitle = selItems[0].data(self.colIndex[nwOutline.LINE], Qt.UserRole)
|
sTitle = selItems[0].data(self.colIndex[nwOutline.LINE], Qt.UserRole)
|
||||||
self.theParent.projMeta.showItem(tHandle, sTitle)
|
self.theParent.projMeta.showItem(tHandle, sTitle)
|
||||||
|
self.theParent.treeView.setSelectedHandle(tHandle)
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -377,23 +375,12 @@ class GuiOutline(QTreeWidget):
|
|||||||
currChapter = None
|
currChapter = None
|
||||||
currScene = None
|
currScene = None
|
||||||
|
|
||||||
for titleKey in self.theIndex.getNovelStructure(skipExcluded=True):
|
for tKey, tHandle, sTitle, novIdx in self.theIndex.novelStructure(skipExcluded=True):
|
||||||
|
|
||||||
if len(titleKey) < 16:
|
tItem = self._createTreeItem(tHandle, sTitle, novIdx)
|
||||||
continue
|
self.treeMap[tKey] = tItem
|
||||||
|
|
||||||
tHandle = titleKey[:13]
|
|
||||||
sTitle = titleKey[14:]
|
|
||||||
|
|
||||||
if tHandle not in self.theIndex.novelIndex:
|
|
||||||
continue
|
|
||||||
if sTitle not in self.theIndex.novelIndex[tHandle]:
|
|
||||||
continue
|
|
||||||
|
|
||||||
tLevel = self.theIndex.novelIndex[tHandle][sTitle]["level"]
|
|
||||||
tItem = self._createTreeItem(tHandle, sTitle, tLevel)
|
|
||||||
self.treeMap[titleKey] = tItem
|
|
||||||
|
|
||||||
|
tLevel = novIdx["level"]
|
||||||
if tLevel == "H1":
|
if tLevel == "H1":
|
||||||
self.addTopLevelItem(tItem)
|
self.addTopLevelItem(tItem)
|
||||||
currTitle = tItem
|
currTitle = tItem
|
||||||
@@ -436,14 +423,12 @@ class GuiOutline(QTreeWidget):
|
|||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
def _createTreeItem(self, tHandle, sTitle, tLevel):
|
def _createTreeItem(self, tHandle, sTitle, novIdx):
|
||||||
"""Populate a tree item with all the column values.
|
"""Populate a tree item with all the column values.
|
||||||
"""
|
"""
|
||||||
nwItem = self.theProject.projTree[tHandle]
|
nwItem = self.theProject.projTree[tHandle]
|
||||||
novIdx = self.theIndex.novelIndex[tHandle][sTitle]
|
|
||||||
|
|
||||||
newItem = QTreeWidgetItem()
|
newItem = QTreeWidgetItem()
|
||||||
hIcon = "doc_%s" % tLevel.lower()
|
hIcon = "doc_%s" % novIdx["level"].lower()
|
||||||
|
|
||||||
cC = int(novIdx["cCount"])
|
cC = int(novIdx["cCount"])
|
||||||
wC = int(novIdx["wCount"])
|
wC = int(novIdx["wCount"])
|
||||||
|
|||||||
@@ -271,11 +271,10 @@ class GuiOutlineDetails(QScrollArea):
|
|||||||
"""Update the content of the tree with the given handle and line
|
"""Update the content of the tree with the given handle and line
|
||||||
number pointing to a header.
|
number pointing to a header.
|
||||||
"""
|
"""
|
||||||
try:
|
nwItem = self.theProject.projTree[tHandle]
|
||||||
nwItem = self.theProject.projTree[tHandle]
|
novIdx = self.theIndex.getNovelData(tHandle, sTitle)
|
||||||
novIdx = self.theIndex.novelIndex[tHandle][sTitle]
|
theRefs = self.theIndex.getReferences(tHandle, sTitle)
|
||||||
theRefs = self.theIndex.getReferences(tHandle, sTitle)
|
if nwItem is None or novIdx is None:
|
||||||
except Exception:
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
if novIdx["level"] in self.LVL_MAP:
|
if novIdx["level"] in self.LVL_MAP:
|
||||||
|
|||||||
@@ -118,7 +118,7 @@ class GuiProjectSettings(PagedDialog):
|
|||||||
self.theProject.setImportColours(importCol)
|
self.theProject.setImportColours(importCol)
|
||||||
|
|
||||||
if self.tabStatus.colChanged or self.tabImport.colChanged:
|
if self.tabStatus.colChanged or self.tabImport.colChanged:
|
||||||
self.theParent.rebuildTree()
|
self.theParent.rebuildTrees()
|
||||||
|
|
||||||
if self.tabReplace.arChanged:
|
if self.tabReplace.arChanged:
|
||||||
newList = self.tabReplace.getNewList()
|
newList = self.tabReplace.getNewList()
|
||||||
|
|||||||
+77
-46
@@ -29,7 +29,9 @@
|
|||||||
import nw
|
import nw
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from PyQt5.QtCore import Qt, QSize
|
from time import time
|
||||||
|
|
||||||
|
from PyQt5.QtCore import Qt, QSize, pyqtSignal
|
||||||
from PyQt5.QtGui import QIcon
|
from PyQt5.QtGui import QIcon
|
||||||
from PyQt5.QtWidgets import (
|
from PyQt5.QtWidgets import (
|
||||||
qApp, QTreeWidget, QTreeWidgetItem, QAbstractItemView, QMenu, QAction
|
qApp, QTreeWidget, QTreeWidgetItem, QAbstractItemView, QMenu, QAction
|
||||||
@@ -49,6 +51,9 @@ class GuiProjectTree(QTreeWidget):
|
|||||||
C_EXPORT = 2
|
C_EXPORT = 2
|
||||||
C_FLAGS = 3
|
C_FLAGS = 3
|
||||||
|
|
||||||
|
novelItemChanged = pyqtSignal()
|
||||||
|
noteItemChanged = pyqtSignal()
|
||||||
|
|
||||||
def __init__(self, theParent):
|
def __init__(self, theParent):
|
||||||
QTreeWidget.__init__(self, theParent)
|
QTreeWidget.__init__(self, theParent)
|
||||||
|
|
||||||
@@ -60,22 +65,27 @@ class GuiProjectTree(QTreeWidget):
|
|||||||
self.theProject = theParent.theProject
|
self.theProject = theParent.theProject
|
||||||
self.theIndex = theParent.theIndex
|
self.theIndex = theParent.theIndex
|
||||||
|
|
||||||
# Tree Settings
|
# Internal Variables
|
||||||
self.theMap = {}
|
self._treeMap = {}
|
||||||
self.treeChanged = False
|
self._treeChanged = False
|
||||||
|
self._timeChanged = 0
|
||||||
|
|
||||||
|
##
|
||||||
|
# Build GUI
|
||||||
|
##
|
||||||
|
|
||||||
|
# Context Menu
|
||||||
self.ctxMenu = GuiProjectTreeMenu(self)
|
self.ctxMenu = GuiProjectTreeMenu(self)
|
||||||
self.clearTree()
|
self.setContextMenuPolicy(Qt.CustomContextMenu)
|
||||||
|
self.customContextMenuRequested.connect(self._rightClickMenu)
|
||||||
|
|
||||||
# Build GUI
|
# Tree Settings
|
||||||
iPx = self.theTheme.baseIconSize
|
iPx = self.theTheme.baseIconSize
|
||||||
self.setIconSize(QSize(iPx, iPx))
|
self.setIconSize(QSize(iPx, iPx))
|
||||||
self.setExpandsOnDoubleClick(True)
|
self.setExpandsOnDoubleClick(True)
|
||||||
self.setIndentation(iPx)
|
self.setIndentation(iPx)
|
||||||
self.setColumnCount(4)
|
self.setColumnCount(4)
|
||||||
self.setHeaderLabels(["Label", "Words", "Inc", "Flags"])
|
self.setHeaderLabels(["Label", "Words", "Inc", "Flags"])
|
||||||
self.setContextMenuPolicy(Qt.CustomContextMenu)
|
|
||||||
self.customContextMenuRequested.connect(self._rightClickMenu)
|
|
||||||
|
|
||||||
treeHeadItem = self.headerItem()
|
treeHeadItem = self.headerItem()
|
||||||
treeHeadItem.setTextAlignment(self.C_COUNT, Qt.AlignRight)
|
treeHeadItem.setTextAlignment(self.C_COUNT, Qt.AlignRight)
|
||||||
@@ -102,7 +112,7 @@ class GuiProjectTree(QTreeWidget):
|
|||||||
# Set Multiple Selection by CTRL
|
# Set Multiple Selection by CTRL
|
||||||
# Disabled for now, until the merge files option has been added
|
# Disabled for now, until the merge files option has been added
|
||||||
# self.setSelectionMode(QAbstractItemView.ExtendedSelection)
|
# self.setSelectionMode(QAbstractItemView.ExtendedSelection)
|
||||||
# self.setSelectionBehavior(QAbstractItemView.SelectRows)
|
self.setSelectionBehavior(QAbstractItemView.SelectRows)
|
||||||
|
|
||||||
# Get user's column width preferences for NAME and COUNT
|
# Get user's column width preferences for NAME and COUNT
|
||||||
treeColWidth = self.mainConf.getTreeColWidths()
|
treeColWidth = self.mainConf.getTreeColWidths()
|
||||||
@@ -116,10 +126,11 @@ class GuiProjectTree(QTreeWidget):
|
|||||||
# Set custom settings
|
# Set custom settings
|
||||||
self.initTree()
|
self.initTree()
|
||||||
|
|
||||||
logger.debug("GuiProjectTree initialisation complete")
|
# Internal Function Mapping
|
||||||
|
self.makeAlert = self.theParent.makeAlert
|
||||||
|
self.askQuestion = self.theParent.askQuestion
|
||||||
|
|
||||||
# Internal Mapping
|
logger.debug("GuiProjectTree initialisation complete")
|
||||||
self.makeAlert = self.theParent.makeAlert
|
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -147,8 +158,9 @@ class GuiProjectTree(QTreeWidget):
|
|||||||
"""Clear the GUI content and the related map.
|
"""Clear the GUI content and the related map.
|
||||||
"""
|
"""
|
||||||
self.clear()
|
self.clear()
|
||||||
self.theMap = {}
|
self._treeMap = {}
|
||||||
self.treeChanged = False
|
self._treeChanged = False
|
||||||
|
self._timeChanged = 0
|
||||||
return
|
return
|
||||||
|
|
||||||
def newTreeItem(self, itemType, itemClass):
|
def newTreeItem(self, itemType, itemClass):
|
||||||
@@ -274,10 +286,13 @@ class GuiProjectTree(QTreeWidget):
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
pHandle = nwItem.itemParent
|
pHandle = nwItem.itemParent
|
||||||
if pHandle is not None and pHandle in self.theMap:
|
if pHandle is not None and pHandle in self._treeMap:
|
||||||
self.theMap[pHandle].setExpanded(True)
|
self._treeMap[pHandle].setExpanded(True)
|
||||||
|
|
||||||
|
self._emitItemChange(tHandle)
|
||||||
self.clearSelection()
|
self.clearSelection()
|
||||||
trItem.setSelected(True)
|
trItem.setSelected(True)
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def moveTreeItem(self, nStep):
|
def moveTreeItem(self, nStep):
|
||||||
@@ -318,6 +333,7 @@ class GuiProjectTree(QTreeWidget):
|
|||||||
self.clearSelection()
|
self.clearSelection()
|
||||||
cItem.setSelected(True)
|
cItem.setSelected(True)
|
||||||
self._setTreeChanged(True)
|
self._setTreeChanged(True)
|
||||||
|
self._emitItemChange(tHandle)
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
@@ -338,7 +354,7 @@ class GuiProjectTree(QTreeWidget):
|
|||||||
"""Calls saveTreeOrder if there are unsaved changes, otherwise
|
"""Calls saveTreeOrder if there are unsaved changes, otherwise
|
||||||
does nothing.
|
does nothing.
|
||||||
"""
|
"""
|
||||||
if self.treeChanged:
|
if self._treeChanged:
|
||||||
logger.verbose("Flushing project tree to project class")
|
logger.verbose("Flushing project tree to project class")
|
||||||
self.saveTreeOrder()
|
self.saveTreeOrder()
|
||||||
self._setTreeChanged(False)
|
self._setTreeChanged(False)
|
||||||
@@ -391,7 +407,7 @@ class GuiProjectTree(QTreeWidget):
|
|||||||
self.makeAlert("The Trash folder is already empty.", nwAlert.INFO)
|
self.makeAlert("The Trash folder is already empty.", nwAlert.INFO)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
msgYes = self.theParent.askQuestion(
|
msgYes = self.askQuestion(
|
||||||
"Empty Trash", "Permanently delete %d file(s) from Trash?" % nTrash
|
"Empty Trash", "Permanently delete %d file(s) from Trash?" % nTrash
|
||||||
)
|
)
|
||||||
if not msgYes:
|
if not msgYes:
|
||||||
@@ -446,7 +462,7 @@ class GuiProjectTree(QTreeWidget):
|
|||||||
# user if they want to permanently delete the file.
|
# user if they want to permanently delete the file.
|
||||||
doPermanent = False
|
doPermanent = False
|
||||||
if not alreadyAsked:
|
if not alreadyAsked:
|
||||||
msgYes = self.theParent.askQuestion(
|
msgYes = self.askQuestion(
|
||||||
"Delete File", "Permanently delete file '%s'?" % nwItemS.itemName
|
"Delete File", "Permanently delete file '%s'?" % nwItemS.itemName
|
||||||
)
|
)
|
||||||
if msgYes:
|
if msgYes:
|
||||||
@@ -474,7 +490,7 @@ class GuiProjectTree(QTreeWidget):
|
|||||||
# move it there.
|
# move it there.
|
||||||
doTrash = False
|
doTrash = False
|
||||||
if askForTrash:
|
if askForTrash:
|
||||||
msgYes = self.theParent.askQuestion(
|
msgYes = self.askQuestion(
|
||||||
"Delete File", "Move file '%s' to Trash?" % nwItemS.itemName
|
"Delete File", "Move file '%s' to Trash?" % nwItemS.itemName
|
||||||
)
|
)
|
||||||
if msgYes:
|
if msgYes:
|
||||||
@@ -533,7 +549,9 @@ class GuiProjectTree(QTreeWidget):
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
def setTreeItemValues(self, tHandle):
|
def setTreeItemValues(self, tHandle):
|
||||||
"""Set the name and flag values for a tree item.
|
"""Set the name and flag values for a tree item from a handle in
|
||||||
|
the project tree. Does not trigger a tree change as the data is
|
||||||
|
already coming from the project tree.
|
||||||
"""
|
"""
|
||||||
trItem = self._getTreeItem(tHandle)
|
trItem = self._getTreeItem(tHandle)
|
||||||
nwItem = self.theProject.projTree[tHandle]
|
nwItem = self.theProject.projTree[tHandle]
|
||||||
@@ -622,9 +640,9 @@ class GuiProjectTree(QTreeWidget):
|
|||||||
sent first.
|
sent first.
|
||||||
"""
|
"""
|
||||||
logger.debug("Building the project tree ...")
|
logger.debug("Building the project tree ...")
|
||||||
self.clear()
|
self.clearTree()
|
||||||
iCount = 0
|
|
||||||
|
|
||||||
|
iCount = 0
|
||||||
for nwItem in self.theProject.getProjectItems():
|
for nwItem in self.theProject.getProjectItems():
|
||||||
iCount += 1
|
iCount += 1
|
||||||
self._addTreeItem(nwItem)
|
self._addTreeItem(nwItem)
|
||||||
@@ -642,21 +660,10 @@ class GuiProjectTree(QTreeWidget):
|
|||||||
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def getSelectedHandles(self):
|
|
||||||
"""Return a list of all currently selected item handles.
|
|
||||||
"""
|
|
||||||
selItems = self.selectedItems()
|
|
||||||
selHandles = []
|
|
||||||
for n in range(len(selItems)):
|
|
||||||
if isinstance(selItems[n], QTreeWidgetItem):
|
|
||||||
selHandles.append(selItems[n].data(self.C_NAME, Qt.UserRole))
|
|
||||||
|
|
||||||
return selHandles
|
|
||||||
|
|
||||||
def setSelectedHandle(self, tHandle, doScroll=False):
|
def setSelectedHandle(self, tHandle, doScroll=False):
|
||||||
"""Set a specific handle as the selected item.
|
"""Set a specific handle as the selected item.
|
||||||
"""
|
"""
|
||||||
if tHandle not in self.theMap:
|
if tHandle not in self._treeMap:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
tItem = self._getTreeItem(tHandle)
|
tItem = self._getTreeItem(tHandle)
|
||||||
@@ -664,7 +671,7 @@ class GuiProjectTree(QTreeWidget):
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
self.clearSelection()
|
self.clearSelection()
|
||||||
self.theMap[tHandle].setSelected(True)
|
self._treeMap[tHandle].setSelected(True)
|
||||||
|
|
||||||
selItems = self.selectedIndexes()
|
selItems = self.selectedIndexes()
|
||||||
if selItems and doScroll:
|
if selItems and doScroll:
|
||||||
@@ -672,6 +679,11 @@ class GuiProjectTree(QTreeWidget):
|
|||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
def changedSince(self, checkTime):
|
||||||
|
"""Check if the tree has changed since a given time.
|
||||||
|
"""
|
||||||
|
return self._timeChanged > checkTime
|
||||||
|
|
||||||
##
|
##
|
||||||
# Slots
|
# Slots
|
||||||
##
|
##
|
||||||
@@ -699,7 +711,7 @@ class GuiProjectTree(QTreeWidget):
|
|||||||
def mousePressEvent(self, theEvent):
|
def mousePressEvent(self, theEvent):
|
||||||
"""Overload mousePressEvent to clear selection if clicking the
|
"""Overload mousePressEvent to clear selection if clicking the
|
||||||
mouse in a blank area of the tree view, and to load a document
|
mouse in a blank area of the tree view, and to load a document
|
||||||
for viewing if the suer middle clicked.
|
for viewing if the user middle-clicked.
|
||||||
"""
|
"""
|
||||||
QTreeWidget.mousePressEvent(self, theEvent)
|
QTreeWidget.mousePressEvent(self, theEvent)
|
||||||
|
|
||||||
@@ -783,6 +795,10 @@ class GuiProjectTree(QTreeWidget):
|
|||||||
else:
|
else:
|
||||||
self.theIndex.reIndexHandle(sHandle)
|
self.theIndex.reIndexHandle(sHandle)
|
||||||
|
|
||||||
|
# Trigger dependent updates
|
||||||
|
self._setTreeChanged(True)
|
||||||
|
self._emitItemChange(sHandle)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
theEvent.ignore()
|
theEvent.ignore()
|
||||||
logger.debug("Drag'n'drop of item %s not accepted" % sHandle)
|
logger.debug("Drag'n'drop of item %s not accepted" % sHandle)
|
||||||
@@ -797,7 +813,7 @@ class GuiProjectTree(QTreeWidget):
|
|||||||
def _getTreeItem(self, tHandle):
|
def _getTreeItem(self, tHandle):
|
||||||
"""Returns the QTreeWidgetItem of a given item handle.
|
"""Returns the QTreeWidgetItem of a given item handle.
|
||||||
"""
|
"""
|
||||||
return self.theMap.get(tHandle, None)
|
return self._treeMap.get(tHandle, None)
|
||||||
|
|
||||||
def _scanChildren(self, theList, theItem, theIndex):
|
def _scanChildren(self, theList, theItem, theIndex):
|
||||||
"""This is a recursive function returning all items in a tree
|
"""This is a recursive function returning all items in a tree
|
||||||
@@ -834,7 +850,7 @@ class GuiProjectTree(QTreeWidget):
|
|||||||
newItem.setData(self.C_NAME, Qt.UserRole, tHandle)
|
newItem.setData(self.C_NAME, Qt.UserRole, tHandle)
|
||||||
newItem.setData(self.C_COUNT, Qt.UserRole, 0)
|
newItem.setData(self.C_COUNT, Qt.UserRole, 0)
|
||||||
|
|
||||||
self.theMap[tHandle] = newItem
|
self._treeMap[tHandle] = newItem
|
||||||
if pHandle is None:
|
if pHandle is None:
|
||||||
if nwItem.itemType == nwItemType.ROOT:
|
if nwItem.itemType == nwItemType.ROOT:
|
||||||
self.addTopLevelItem(newItem)
|
self.addTopLevelItem(newItem)
|
||||||
@@ -845,20 +861,20 @@ class GuiProjectTree(QTreeWidget):
|
|||||||
self.makeAlert(
|
self.makeAlert(
|
||||||
"There is nowhere to add item with name '%s'" % nwItem.itemName, nwAlert.ERROR
|
"There is nowhere to add item with name '%s'" % nwItem.itemName, nwAlert.ERROR
|
||||||
)
|
)
|
||||||
del self.theMap[tHandle]
|
del self._treeMap[tHandle]
|
||||||
return None
|
return None
|
||||||
|
|
||||||
else:
|
else:
|
||||||
byIndex = -1
|
byIndex = -1
|
||||||
if nHandle is not None and nHandle in self.theMap:
|
if nHandle is not None and nHandle in self._treeMap:
|
||||||
try:
|
try:
|
||||||
byIndex = self.theMap[pHandle].indexOfChild(self.theMap[nHandle])
|
byIndex = self._treeMap[pHandle].indexOfChild(self._treeMap[nHandle])
|
||||||
except Exception:
|
except Exception:
|
||||||
logger.error("Failed to get index of item with handle %s" % nHandle)
|
logger.error("Failed to get index of item with handle %s" % nHandle)
|
||||||
if byIndex >= 0:
|
if byIndex >= 0:
|
||||||
self.theMap[pHandle].insertChild(byIndex+1, newItem)
|
self._treeMap[pHandle].insertChild(byIndex+1, newItem)
|
||||||
else:
|
else:
|
||||||
self.theMap[pHandle].addChild(newItem)
|
self._treeMap[pHandle].addChild(newItem)
|
||||||
self.propagateCount(tHandle, nwItem.wordCount)
|
self.propagateCount(tHandle, nwItem.wordCount)
|
||||||
|
|
||||||
self.setTreeItemValues(tHandle)
|
self.setTreeItemValues(tHandle)
|
||||||
@@ -910,7 +926,6 @@ class GuiProjectTree(QTreeWidget):
|
|||||||
pHandle = trItemP.data(self.C_NAME, Qt.UserRole)
|
pHandle = trItemP.data(self.C_NAME, Qt.UserRole)
|
||||||
nwItemS.setParent(pHandle)
|
nwItemS.setParent(pHandle)
|
||||||
self.setTreeItemValues(tHandle)
|
self.setTreeItemValues(tHandle)
|
||||||
self._setTreeChanged(True)
|
|
||||||
|
|
||||||
logger.debug("The parent of item %s has been changed to %s" % (tHandle, pHandle))
|
logger.debug("The parent of item %s has been changed to %s" % (tHandle, pHandle))
|
||||||
|
|
||||||
@@ -919,11 +934,27 @@ class GuiProjectTree(QTreeWidget):
|
|||||||
def _setTreeChanged(self, theState):
|
def _setTreeChanged(self, theState):
|
||||||
"""Set the tree change flag, and propagate to the project.
|
"""Set the tree change flag, and propagate to the project.
|
||||||
"""
|
"""
|
||||||
self.treeChanged = theState
|
self._treeChanged = theState
|
||||||
if theState:
|
if theState:
|
||||||
|
self._timeChanged = time()
|
||||||
self.theProject.setProjectChanged(True)
|
self.theProject.setProjectChanged(True)
|
||||||
return
|
return
|
||||||
|
|
||||||
|
def _emitItemChange(self, tHandle):
|
||||||
|
"""Emit an item change signal for a given handle.
|
||||||
|
"""
|
||||||
|
nwItem = self.theProject.projTree[tHandle]
|
||||||
|
if nwItem is None:
|
||||||
|
return
|
||||||
|
|
||||||
|
if nwItem.itemType == nwItemType.FILE:
|
||||||
|
if nwItem.itemClass == nwItemClass.NOVEL:
|
||||||
|
self.novelItemChanged.emit()
|
||||||
|
else:
|
||||||
|
self.noteItemChanged.emit()
|
||||||
|
|
||||||
|
return
|
||||||
|
|
||||||
# END Class GuiProjectTree
|
# END Class GuiProjectTree
|
||||||
|
|
||||||
class GuiProjectTreeMenu(QMenu):
|
class GuiProjectTreeMenu(QMenu):
|
||||||
|
|||||||
+97
-34
@@ -32,7 +32,7 @@ import os
|
|||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from time import time
|
from time import time
|
||||||
|
|
||||||
from PyQt5.QtCore import Qt, QTimer, QThreadPool
|
from PyQt5.QtCore import Qt, QTimer, QThreadPool, pyqtSlot
|
||||||
from PyQt5.QtGui import QIcon, QPixmap, QColor, QKeySequence, QCursor
|
from PyQt5.QtGui import QIcon, QPixmap, QColor, QKeySequence, QCursor
|
||||||
from PyQt5.QtWidgets import (
|
from PyQt5.QtWidgets import (
|
||||||
qApp, QMainWindow, QVBoxLayout, QWidget, QSplitter, QFileDialog, QShortcut,
|
qApp, QMainWindow, QVBoxLayout, QWidget, QSplitter, QFileDialog, QShortcut,
|
||||||
@@ -42,9 +42,9 @@ from PyQt5.QtWidgets import (
|
|||||||
from nw.gui import (
|
from nw.gui import (
|
||||||
GuiAbout, GuiBuildNovel, GuiDocEditor, GuiDocMerge, GuiDocSplit,
|
GuiAbout, GuiBuildNovel, GuiDocEditor, GuiDocMerge, GuiDocSplit,
|
||||||
GuiDocViewDetails, GuiDocViewer, GuiItemDetails, GuiItemEditor,
|
GuiDocViewDetails, GuiDocViewer, GuiItemDetails, GuiItemEditor,
|
||||||
GuiMainMenu, GuiMainStatus, GuiOutline, GuiOutlineDetails, GuiPreferences,
|
GuiMainMenu, GuiMainStatus, GuiNovelTree, GuiOutline, GuiOutlineDetails,
|
||||||
GuiProjectLoad, GuiProjectSettings, GuiProjectTree, GuiProjectWizard,
|
GuiPreferences, GuiProjectLoad, GuiProjectSettings, GuiProjectTree,
|
||||||
GuiTheme, GuiWritingStats
|
GuiProjectWizard, GuiTheme, GuiWritingStats
|
||||||
)
|
)
|
||||||
from nw.core import NWProject, NWDoc, NWIndex
|
from nw.core import NWProject, NWDoc, NWIndex
|
||||||
from nw.constants import nwItemType, nwItemClass, nwAlert, nwConst
|
from nw.constants import nwItemType, nwItemClass, nwAlert, nwConst
|
||||||
@@ -99,6 +99,7 @@ class GuiMain(QMainWindow):
|
|||||||
# Main GUI Elements
|
# Main GUI Elements
|
||||||
self.statusBar = GuiMainStatus(self)
|
self.statusBar = GuiMainStatus(self)
|
||||||
self.treeView = GuiProjectTree(self)
|
self.treeView = GuiProjectTree(self)
|
||||||
|
self.novelView = GuiNovelTree(self)
|
||||||
self.docEditor = GuiDocEditor(self)
|
self.docEditor = GuiDocEditor(self)
|
||||||
self.viewMeta = GuiDocViewDetails(self)
|
self.viewMeta = GuiDocViewDetails(self)
|
||||||
self.docViewer = GuiDocViewer(self)
|
self.docViewer = GuiDocViewer(self)
|
||||||
@@ -111,11 +112,24 @@ class GuiMain(QMainWindow):
|
|||||||
self.statusIcons = []
|
self.statusIcons = []
|
||||||
self.importIcons = []
|
self.importIcons = []
|
||||||
|
|
||||||
|
# Project Tabs : Project / Novel
|
||||||
|
self.projTabs = QTabWidget()
|
||||||
|
self.projTabs.setTabPosition(QTabWidget.South)
|
||||||
|
self.projTabs.setStyleSheet("QTabWidget::pane {border: 0;};")
|
||||||
|
self.projTabs.addTab(self.treeView, "Project")
|
||||||
|
self.projTabs.addTab(self.novelView, "Novel")
|
||||||
|
self.projTabs.currentChanged.connect(self._projTabsChanged)
|
||||||
|
|
||||||
|
tabFont = self.projTabs.tabBar().font()
|
||||||
|
tabFont.setPointSize(round(0.9*self.theTheme.fontPointSize))
|
||||||
|
self.projTabs.tabBar().setFont(tabFont)
|
||||||
|
|
||||||
# Project Tree View
|
# Project Tree View
|
||||||
self.treePane = QWidget()
|
self.treePane = QWidget()
|
||||||
self.treeBox = QVBoxLayout()
|
self.treeBox = QVBoxLayout()
|
||||||
self.treeBox.setContentsMargins(0, 0, 0, 0)
|
self.treeBox.setContentsMargins(0, 0, 0, 0)
|
||||||
self.treeBox.addWidget(self.treeView)
|
self.treeBox.setSpacing(0)
|
||||||
|
self.treeBox.addWidget(self.projTabs)
|
||||||
self.treeBox.addWidget(self.treeMeta)
|
self.treeBox.addWidget(self.treeMeta)
|
||||||
self.treePane.setLayout(self.treeBox)
|
self.treePane.setLayout(self.treeBox)
|
||||||
|
|
||||||
@@ -136,31 +150,33 @@ class GuiMain(QMainWindow):
|
|||||||
self.splitOutline.addWidget(self.projMeta)
|
self.splitOutline.addWidget(self.projMeta)
|
||||||
self.splitOutline.setSizes(self.mainConf.getOutlinePanePos())
|
self.splitOutline.setSizes(self.mainConf.getOutlinePanePos())
|
||||||
|
|
||||||
# Main Tabs : Edirot / Outline
|
# Main Tabs : Editor / Outline
|
||||||
self.tabWidget = QTabWidget()
|
self.mainTabs = QTabWidget()
|
||||||
self.tabWidget.setTabPosition(QTabWidget.East)
|
self.mainTabs.setTabPosition(QTabWidget.East)
|
||||||
self.tabWidget.setStyleSheet("QTabWidget::pane {border: 0;}")
|
self.mainTabs.setStyleSheet("QTabWidget::pane {border: 0;}")
|
||||||
self.tabWidget.addTab(self.splitDocs, "Editor")
|
self.mainTabs.addTab(self.splitDocs, "Editor")
|
||||||
self.tabWidget.addTab(self.splitOutline, "Outline")
|
self.mainTabs.addTab(self.splitOutline, "Outline")
|
||||||
self.tabWidget.currentChanged.connect(self._mainTabChanged)
|
self.mainTabs.currentChanged.connect(self._mainTabChanged)
|
||||||
|
|
||||||
# Splitter : Project Tree / Main Tabs
|
# Splitter : Project Tree / Main Tabs
|
||||||
xCM = self.mainConf.pxInt(4)
|
xCM = self.mainConf.pxInt(4)
|
||||||
self.splitMain = QSplitter(Qt.Horizontal)
|
self.splitMain = QSplitter(Qt.Horizontal)
|
||||||
self.splitMain.setContentsMargins(xCM, xCM, xCM, xCM)
|
self.splitMain.setContentsMargins(xCM, xCM, xCM, xCM)
|
||||||
self.splitMain.addWidget(self.treePane)
|
self.splitMain.addWidget(self.treePane)
|
||||||
self.splitMain.addWidget(self.tabWidget)
|
self.splitMain.addWidget(self.mainTabs)
|
||||||
self.splitMain.setSizes(self.mainConf.getMainPanePos())
|
self.splitMain.setSizes(self.mainConf.getMainPanePos())
|
||||||
|
|
||||||
# Indices of All Splitter Widgets
|
# Indices of All Splitter Widgets
|
||||||
self.idxTree = self.splitMain.indexOf(self.treePane)
|
self.idxTree = self.splitMain.indexOf(self.treePane)
|
||||||
self.idxMain = self.splitMain.indexOf(self.tabWidget)
|
self.idxMain = self.splitMain.indexOf(self.mainTabs)
|
||||||
self.idxEditor = self.splitDocs.indexOf(self.docEditor)
|
self.idxEditor = self.splitDocs.indexOf(self.docEditor)
|
||||||
self.idxViewer = self.splitDocs.indexOf(self.splitView)
|
self.idxViewer = self.splitDocs.indexOf(self.splitView)
|
||||||
self.idxViewDoc = self.splitView.indexOf(self.docViewer)
|
self.idxViewDoc = self.splitView.indexOf(self.docViewer)
|
||||||
self.idxViewMeta = self.splitView.indexOf(self.viewMeta)
|
self.idxViewMeta = self.splitView.indexOf(self.viewMeta)
|
||||||
self.idxTabEdit = self.tabWidget.indexOf(self.splitDocs)
|
self.idxTabEdit = self.mainTabs.indexOf(self.splitDocs)
|
||||||
self.idxTabProj = self.tabWidget.indexOf(self.splitOutline)
|
self.idxTabProj = self.mainTabs.indexOf(self.splitOutline)
|
||||||
|
self.idxTreeView = self.projTabs.indexOf(self.treeView)
|
||||||
|
self.idxNovelView = self.projTabs.indexOf(self.novelView)
|
||||||
|
|
||||||
# Splitter Behaviour
|
# Splitter Behaviour
|
||||||
self.splitMain.setCollapsible(self.idxTree, False)
|
self.splitMain.setCollapsible(self.idxTree, False)
|
||||||
@@ -177,7 +193,8 @@ class GuiMain(QMainWindow):
|
|||||||
# Initialise the Project Tree
|
# Initialise the Project Tree
|
||||||
self.treeView.itemSelectionChanged.connect(self._treeSingleClick)
|
self.treeView.itemSelectionChanged.connect(self._treeSingleClick)
|
||||||
self.treeView.itemDoubleClicked.connect(self._treeDoubleClick)
|
self.treeView.itemDoubleClicked.connect(self._treeDoubleClick)
|
||||||
self.rebuildTree()
|
self.treeView.novelItemChanged.connect(self._treeNovelItemChanged)
|
||||||
|
self.rebuildTrees()
|
||||||
|
|
||||||
# Set Main Window Elements
|
# Set Main Window Elements
|
||||||
self.setMenuBar(self.mainMenu)
|
self.setMenuBar(self.mainMenu)
|
||||||
@@ -253,6 +270,7 @@ class GuiMain(QMainWindow):
|
|||||||
"""Wrapper function to clear all sub-elements of the main GUI.
|
"""Wrapper function to clear all sub-elements of the main GUI.
|
||||||
"""
|
"""
|
||||||
self.treeView.clearTree()
|
self.treeView.clearTree()
|
||||||
|
self.novelView.clearTree()
|
||||||
self.docEditor.clearEditor()
|
self.docEditor.clearEditor()
|
||||||
self.closeDocViewer()
|
self.closeDocViewer()
|
||||||
self.statusBar.clearStatus()
|
self.statusBar.clearStatus()
|
||||||
@@ -299,7 +317,7 @@ class GuiMain(QMainWindow):
|
|||||||
|
|
||||||
logger.info("Creating new project")
|
logger.info("Creating new project")
|
||||||
if self.theProject.newProject(projData):
|
if self.theProject.newProject(projData):
|
||||||
self.rebuildTree()
|
self.rebuildTrees()
|
||||||
self.saveProject()
|
self.saveProject()
|
||||||
self.hasProject = True
|
self.hasProject = True
|
||||||
self.statusBar.setRefTime(self.theProject.projOpened)
|
self.statusBar.setRefTime(self.theProject.projOpened)
|
||||||
@@ -355,7 +373,7 @@ class GuiMain(QMainWindow):
|
|||||||
self.theIndex.clearIndex()
|
self.theIndex.clearIndex()
|
||||||
self.clearGUI()
|
self.clearGUI()
|
||||||
self.hasProject = False
|
self.hasProject = False
|
||||||
self.tabWidget.setCurrentWidget(self.splitDocs)
|
self.mainTabs.setCurrentWidget(self.splitDocs)
|
||||||
|
|
||||||
return saveOK
|
return saveOK
|
||||||
|
|
||||||
@@ -371,7 +389,7 @@ class GuiMain(QMainWindow):
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
# Switch main tab to editor view
|
# Switch main tab to editor view
|
||||||
self.tabWidget.setCurrentWidget(self.splitDocs)
|
self.mainTabs.setCurrentWidget(self.splitDocs)
|
||||||
|
|
||||||
# Try to open the project
|
# Try to open the project
|
||||||
if not self.theProject.openProject(projFile):
|
if not self.theProject.openProject(projFile):
|
||||||
@@ -423,7 +441,7 @@ class GuiMain(QMainWindow):
|
|||||||
|
|
||||||
# Update GUI
|
# Update GUI
|
||||||
self._setWindowTitle(self.theProject.projName)
|
self._setWindowTitle(self.theProject.projName)
|
||||||
self.rebuildTree()
|
self.rebuildTrees()
|
||||||
self.docEditor.setDictionaries()
|
self.docEditor.setDictionaries()
|
||||||
self.docEditor.setSpellCheck(self.theProject.spellCheck)
|
self.docEditor.setSpellCheck(self.theProject.spellCheck)
|
||||||
self.mainMenu.setAutoOutline(self.theProject.autoOutline)
|
self.mainMenu.setAutoOutline(self.theProject.autoOutline)
|
||||||
@@ -490,7 +508,7 @@ class GuiMain(QMainWindow):
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
self.closeDocument()
|
self.closeDocument()
|
||||||
self.tabWidget.setCurrentWidget(self.splitDocs)
|
self.mainTabs.setCurrentWidget(self.splitDocs)
|
||||||
if self.docEditor.loadText(tHandle, tLine):
|
if self.docEditor.loadText(tHandle, tLine):
|
||||||
if changeFocus:
|
if changeFocus:
|
||||||
self.docEditor.setFocus()
|
self.docEditor.setFocus()
|
||||||
@@ -575,7 +593,7 @@ class GuiMain(QMainWindow):
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
# Make sure main tab is in Editor view
|
# Make sure main tab is in Editor view
|
||||||
self.tabWidget.setCurrentWidget(self.splitDocs)
|
self.mainTabs.setCurrentWidget(self.splitDocs)
|
||||||
|
|
||||||
logger.debug("Viewing document with handle %s" % tHandle)
|
logger.debug("Viewing document with handle %s" % tHandle)
|
||||||
if self.docViewer.loadText(tHandle):
|
if self.docViewer.loadText(tHandle):
|
||||||
@@ -739,13 +757,13 @@ class GuiMain(QMainWindow):
|
|||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
def rebuildTree(self):
|
def rebuildTrees(self):
|
||||||
"""Rebuild the project tree.
|
"""Rebuild the project tree.
|
||||||
"""
|
"""
|
||||||
self._makeStatusIcons()
|
self._makeStatusIcons()
|
||||||
self._makeImportIcons()
|
self._makeImportIcons()
|
||||||
self.treeView.clearTree()
|
|
||||||
self.treeView.buildTree()
|
self.treeView.buildTree()
|
||||||
|
self.novelView.refreshTree()
|
||||||
return
|
return
|
||||||
|
|
||||||
def rebuildIndex(self, beQuiet=False):
|
def rebuildIndex(self, beQuiet=False):
|
||||||
@@ -803,7 +821,7 @@ class GuiMain(QMainWindow):
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
logger.verbose("Forcing a rebuild of the Project Outline")
|
logger.verbose("Forcing a rebuild of the Project Outline")
|
||||||
self.tabWidget.setCurrentWidget(self.splitOutline)
|
self.mainTabs.setCurrentWidget(self.splitOutline)
|
||||||
self.projView.refreshTree(overRide=True)
|
self.projView.refreshTree(overRide=True)
|
||||||
|
|
||||||
return True
|
return True
|
||||||
@@ -866,6 +884,7 @@ class GuiMain(QMainWindow):
|
|||||||
self.docEditor.initEditor()
|
self.docEditor.initEditor()
|
||||||
self.docViewer.initViewer()
|
self.docViewer.initViewer()
|
||||||
self.treeView.initTree()
|
self.treeView.initTree()
|
||||||
|
self.novelView.initTree()
|
||||||
self.projView.initOutline()
|
self.projView.initOutline()
|
||||||
self.projMeta.initDetails()
|
self.projMeta.initDetails()
|
||||||
|
|
||||||
@@ -1027,6 +1046,7 @@ class GuiMain(QMainWindow):
|
|||||||
|
|
||||||
self.mainConf.setShowRefPanel(self.viewMeta.isVisible())
|
self.mainConf.setShowRefPanel(self.viewMeta.isVisible())
|
||||||
self.mainConf.setTreeColWidths(self.treeView.getColumnSizes())
|
self.mainConf.setTreeColWidths(self.treeView.getColumnSizes())
|
||||||
|
self.mainConf.setNovelColWidths(self.novelView.getColumnSizes())
|
||||||
if not self.mainConf.isFullScreen:
|
if not self.mainConf.isFullScreen:
|
||||||
self.mainConf.setWinSize(self.width(), self.height())
|
self.mainConf.setWinSize(self.width(), self.height())
|
||||||
|
|
||||||
@@ -1083,7 +1103,7 @@ class GuiMain(QMainWindow):
|
|||||||
self.mainMenu.aFocusMode.setChecked(self.isFocusMode)
|
self.mainMenu.aFocusMode.setChecked(self.isFocusMode)
|
||||||
if self.isFocusMode:
|
if self.isFocusMode:
|
||||||
logger.debug("Activating Focus Mode")
|
logger.debug("Activating Focus Mode")
|
||||||
self.tabWidget.setCurrentWidget(self.splitDocs)
|
self.mainTabs.setCurrentWidget(self.splitDocs)
|
||||||
else:
|
else:
|
||||||
logger.debug("Deactivating Focus Mode")
|
logger.debug("Deactivating Focus Mode")
|
||||||
|
|
||||||
@@ -1091,7 +1111,7 @@ class GuiMain(QMainWindow):
|
|||||||
self.treePane.setVisible(isVisible)
|
self.treePane.setVisible(isVisible)
|
||||||
self.statusBar.setVisible(isVisible)
|
self.statusBar.setVisible(isVisible)
|
||||||
self.mainMenu.setVisible(isVisible)
|
self.mainMenu.setVisible(isVisible)
|
||||||
self.tabWidget.tabBar().setVisible(isVisible)
|
self.mainTabs.tabBar().setVisible(isVisible)
|
||||||
|
|
||||||
hideDocFooter = self.isFocusMode and self.mainConf.hideFocusFooter
|
hideDocFooter = self.isFocusMode and self.mainConf.hideFocusFooter
|
||||||
self.docEditor.docFooter.setVisible(not hideDocFooter)
|
self.docEditor.docFooter.setVisible(not hideDocFooter)
|
||||||
@@ -1300,9 +1320,10 @@ class GuiMain(QMainWindow):
|
|||||||
return
|
return
|
||||||
|
|
||||||
##
|
##
|
||||||
# Signal Handlers
|
# Slots
|
||||||
##
|
##
|
||||||
|
|
||||||
|
@pyqtSlot()
|
||||||
def _treeSingleClick(self):
|
def _treeSingleClick(self):
|
||||||
"""Single click on a project tree item just updates the details
|
"""Single click on a project tree item just updates the details
|
||||||
panel below the tree.
|
panel below the tree.
|
||||||
@@ -1312,12 +1333,14 @@ class GuiMain(QMainWindow):
|
|||||||
self.treeMeta.updateViewBox(sHandle)
|
self.treeMeta.updateViewBox(sHandle)
|
||||||
return
|
return
|
||||||
|
|
||||||
|
@pyqtSlot("QTreeWidgetItem*", int)
|
||||||
def _treeDoubleClick(self, tItem, colNo):
|
def _treeDoubleClick(self, tItem, colNo):
|
||||||
"""The user double-clicked an item in the tree. If it is a file,
|
"""The user double-clicked an item in the tree. If it is a file,
|
||||||
we open it. Otherwise, we do nothing.
|
we open it. Otherwise, we do nothing.
|
||||||
"""
|
"""
|
||||||
tHandle = tItem.data(self.treeView.C_NAME, Qt.UserRole)
|
tHandle = tItem.data(self.treeView.C_NAME, Qt.UserRole)
|
||||||
logger.verbose("User double clicked tree item with handle %s" % tHandle)
|
logger.verbose("User double clicked tree item with handle %s" % tHandle)
|
||||||
|
|
||||||
nwItem = self.theProject.projTree[tHandle]
|
nwItem = self.theProject.projTree[tHandle]
|
||||||
if nwItem is not None:
|
if nwItem is not None:
|
||||||
if nwItem.itemType == nwItemType.FILE:
|
if nwItem.itemType == nwItemType.FILE:
|
||||||
@@ -1328,6 +1351,20 @@ class GuiMain(QMainWindow):
|
|||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
|
@pyqtSlot()
|
||||||
|
def _treeNovelItemChanged(self):
|
||||||
|
"""Triggered when there is a change to a novel item in the
|
||||||
|
project tree.
|
||||||
|
"""
|
||||||
|
if self.mainTabs.currentIndex() == self.idxTabProj:
|
||||||
|
logger.verbose("Novel tree changed while Outline tab active")
|
||||||
|
if self.hasProject:
|
||||||
|
self.treeView.flushTreeOrder()
|
||||||
|
self.projView.refreshTree(novelChanged=True)
|
||||||
|
|
||||||
|
return
|
||||||
|
|
||||||
|
@pyqtSlot()
|
||||||
def _treeKeyPressReturn(self):
|
def _treeKeyPressReturn(self):
|
||||||
"""The user pressed return on an item in the tree. If it is a
|
"""The user pressed return on an item in the tree. If it is a
|
||||||
file, we open it. Otherwise, we do nothing. Pressing return does
|
file, we open it. Otherwise, we do nothing. Pressing return does
|
||||||
@@ -1335,6 +1372,7 @@ class GuiMain(QMainWindow):
|
|||||||
"""
|
"""
|
||||||
tHandle = self.treeView.getSelectedHandle()
|
tHandle = self.treeView.getSelectedHandle()
|
||||||
logger.verbose("User pressed return on tree item with handle %s" % tHandle)
|
logger.verbose("User pressed return on tree item with handle %s" % tHandle)
|
||||||
|
|
||||||
nwItem = self.theProject.projTree[tHandle]
|
nwItem = self.theProject.projTree[tHandle]
|
||||||
if nwItem is not None:
|
if nwItem is not None:
|
||||||
if nwItem.itemType == nwItemType.FILE:
|
if nwItem.itemType == nwItemType.FILE:
|
||||||
@@ -1342,8 +1380,10 @@ class GuiMain(QMainWindow):
|
|||||||
self.openDocument(tHandle, changeFocus=False, doScroll=False)
|
self.openDocument(tHandle, changeFocus=False, doScroll=False)
|
||||||
else:
|
else:
|
||||||
logger.verbose("Requested item %s is a folder" % tHandle)
|
logger.verbose("Requested item %s is a folder" % tHandle)
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
|
@pyqtSlot()
|
||||||
def _keyPressEscape(self):
|
def _keyPressEscape(self):
|
||||||
"""When the escape key is pressed somewhere in the main window,
|
"""When the escape key is pressed somewhere in the main window,
|
||||||
do the following, in order:
|
do the following, in order:
|
||||||
@@ -1352,8 +1392,10 @@ class GuiMain(QMainWindow):
|
|||||||
self.docEditor.closeSearch()
|
self.docEditor.closeSearch()
|
||||||
elif self.isFocusMode:
|
elif self.isFocusMode:
|
||||||
self.toggleFocusMode()
|
self.toggleFocusMode()
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
|
@pyqtSlot(int)
|
||||||
def _mainTabChanged(self, tabIndex):
|
def _mainTabChanged(self, tabIndex):
|
||||||
"""Activated when the main window tab is changed.
|
"""Activated when the main window tab is changed.
|
||||||
"""
|
"""
|
||||||
@@ -1363,6 +1405,27 @@ class GuiMain(QMainWindow):
|
|||||||
logger.verbose("Project outline tab activated")
|
logger.verbose("Project outline tab activated")
|
||||||
if self.hasProject:
|
if self.hasProject:
|
||||||
self.projView.refreshTree()
|
self.projView.refreshTree()
|
||||||
|
|
||||||
|
return
|
||||||
|
|
||||||
|
@pyqtSlot(int)
|
||||||
|
def _projTabsChanged(self, tabIndex):
|
||||||
|
"""Activated when the project view tab is changed.
|
||||||
|
"""
|
||||||
|
sHandle = None
|
||||||
|
|
||||||
|
if tabIndex == self.idxTreeView:
|
||||||
|
logger.verbose("Project tree tab activated")
|
||||||
|
sHandle = self.treeView.getSelectedHandle()
|
||||||
|
|
||||||
|
elif tabIndex == self.idxNovelView:
|
||||||
|
logger.verbose("Novel tree tab activated")
|
||||||
|
if self.hasProject:
|
||||||
|
self.novelView.refreshTree()
|
||||||
|
sHandle = self.novelView.getSelectedHandle()
|
||||||
|
|
||||||
|
self.treeMeta.updateViewBox(sHandle)
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
# END Class GuiMain
|
# END Class GuiMain
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
<?xml version='1.0' encoding='utf-8'?>
|
<?xml version='1.0' encoding='utf-8'?>
|
||||||
<novelWriterXML appVersion="1.0" hexVersion="0x010000f0" fileVersion="1.2" timeStamp="2021-01-03 16:47:07">
|
<novelWriterXML appVersion="1.1a0" hexVersion="0x010100a0" fileVersion="1.2" timeStamp="2021-01-03 17:17:01">
|
||||||
<project>
|
<project>
|
||||||
<name>Sample Project</name>
|
<name>Sample Project</name>
|
||||||
<title>Sample Project</title>
|
<title>Sample Project</title>
|
||||||
<author>Jane Smith</author>
|
<author>Jane Smith</author>
|
||||||
<author>Jay Doh</author>
|
<author>Jay Doh</author>
|
||||||
<saveCount>823</saveCount>
|
<saveCount>824</saveCount>
|
||||||
<autoCount>153</autoCount>
|
<autoCount>153</autoCount>
|
||||||
<editTime>39446</editTime>
|
<editTime>39448</editTime>
|
||||||
</project>
|
</project>
|
||||||
<settings>
|
<settings>
|
||||||
<doBackup>False</doBackup>
|
<doBackup>False</doBackup>
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ lastnotes = 1.0
|
|||||||
[Sizes]
|
[Sizes]
|
||||||
geometry = 1200, 650
|
geometry = 1200, 650
|
||||||
treecols = 200, 50, 30
|
treecols = 200, 50, 30
|
||||||
|
novelcols = 200, 50
|
||||||
projcols = 200, 60, 140
|
projcols = 200, 60, 140
|
||||||
mainpane = 300, 800
|
mainpane = 300, 800
|
||||||
docpane = 400, 400
|
docpane = 400, 400
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ lastnotes = 1.0
|
|||||||
[Sizes]
|
[Sizes]
|
||||||
geometry = 1100, 650
|
geometry = 1100, 650
|
||||||
treecols = 120, 30, 50
|
treecols = 120, 30, 50
|
||||||
|
novelcols = 200, 50
|
||||||
projcols = 140, 55, 140
|
projcols = 140, 55, 140
|
||||||
mainpane = 300, 800
|
mainpane = 300, 800
|
||||||
docpane = 400, 400
|
docpane = 400, 400
|
||||||
|
|||||||
@@ -304,6 +304,19 @@ def testBaseConfig_SettersGetters(tmpConf, tmpDir, outDir, refDir):
|
|||||||
|
|
||||||
assert tmpConf.setTreeColWidths([200, 50, 30])
|
assert tmpConf.setTreeColWidths([200, 50, 30])
|
||||||
|
|
||||||
|
# Novel Tree Columns
|
||||||
|
tmpConf.guiScale = 2.0
|
||||||
|
assert tmpConf.setNovelColWidths([10, 20])
|
||||||
|
assert tmpConf.getNovelColWidths() == [10, 20]
|
||||||
|
assert tmpConf.novelColWidth == [5, 10]
|
||||||
|
|
||||||
|
tmpConf.guiScale = 1.0
|
||||||
|
assert tmpConf.setNovelColWidths([10, 20])
|
||||||
|
assert tmpConf.getNovelColWidths() == [10, 20]
|
||||||
|
assert tmpConf.novelColWidth == [10, 20]
|
||||||
|
|
||||||
|
assert tmpConf.setNovelColWidths([200, 50])
|
||||||
|
|
||||||
# Project Settings Tree Columns
|
# Project Settings Tree Columns
|
||||||
tmpConf.guiScale = 2.0
|
tmpConf.guiScale = 2.0
|
||||||
assert tmpConf.setProjColWidths([10, 20, 30])
|
assert tmpConf.setProjColWidths([10, 20, 30])
|
||||||
|
|||||||
+138
-108
@@ -56,30 +56,30 @@ def testCoreIndex_LoadSave(monkeypatch, nwLipsum, dummyGUI, outDir, refDir):
|
|||||||
assert theIndex.saveIndex()
|
assert theIndex.saveIndex()
|
||||||
|
|
||||||
# Take a copy of the index
|
# Take a copy of the index
|
||||||
tagIndex = str(theIndex.tagIndex)
|
tagIndex = str(theIndex._tagIndex)
|
||||||
refIndex = str(theIndex.refIndex)
|
refIndex = str(theIndex._refIndex)
|
||||||
novelIndex = str(theIndex.novelIndex)
|
novelIndex = str(theIndex._novelIndex)
|
||||||
noteIndex = str(theIndex.noteIndex)
|
noteIndex = str(theIndex._noteIndex)
|
||||||
textCounts = str(theIndex.textCounts)
|
textCounts = str(theIndex._textCounts)
|
||||||
|
|
||||||
# Delete a handle
|
# Delete a handle
|
||||||
assert theIndex.tagIndex.get("Bod", None) is not None
|
assert theIndex._tagIndex.get("Bod", None) is not None
|
||||||
assert theIndex.refIndex.get("4c4f28287af27", None) is not None
|
assert theIndex._refIndex.get("4c4f28287af27", None) is not None
|
||||||
assert theIndex.noteIndex.get("4c4f28287af27", None) is not None
|
assert theIndex._noteIndex.get("4c4f28287af27", None) is not None
|
||||||
assert theIndex.textCounts.get("4c4f28287af27", None) is not None
|
assert theIndex._textCounts.get("4c4f28287af27", None) is not None
|
||||||
theIndex.deleteHandle("4c4f28287af27")
|
theIndex.deleteHandle("4c4f28287af27")
|
||||||
assert theIndex.tagIndex.get("Bod", None) is None
|
assert theIndex._tagIndex.get("Bod", None) is None
|
||||||
assert theIndex.refIndex.get("4c4f28287af27", None) is None
|
assert theIndex._refIndex.get("4c4f28287af27", None) is None
|
||||||
assert theIndex.noteIndex.get("4c4f28287af27", None) is None
|
assert theIndex._noteIndex.get("4c4f28287af27", None) is None
|
||||||
assert theIndex.textCounts.get("4c4f28287af27", None) is None
|
assert theIndex._textCounts.get("4c4f28287af27", None) is None
|
||||||
|
|
||||||
# Clear the index
|
# Clear the index
|
||||||
theIndex.clearIndex()
|
theIndex.clearIndex()
|
||||||
assert not theIndex.tagIndex
|
assert not theIndex._tagIndex
|
||||||
assert not theIndex.refIndex
|
assert not theIndex._refIndex
|
||||||
assert not theIndex.novelIndex
|
assert not theIndex._novelIndex
|
||||||
assert not theIndex.noteIndex
|
assert not theIndex._noteIndex
|
||||||
assert not theIndex.textCounts
|
assert not theIndex._textCounts
|
||||||
|
|
||||||
# Make the load fail
|
# Make the load fail
|
||||||
monkeypatch.setattr(json, "load", doPanic)
|
monkeypatch.setattr(json, "load", doPanic)
|
||||||
@@ -89,46 +89,46 @@ def testCoreIndex_LoadSave(monkeypatch, nwLipsum, dummyGUI, outDir, refDir):
|
|||||||
monkeypatch.undo()
|
monkeypatch.undo()
|
||||||
assert theIndex.loadIndex()
|
assert theIndex.loadIndex()
|
||||||
|
|
||||||
assert str(theIndex.tagIndex) == tagIndex
|
assert str(theIndex._tagIndex) == tagIndex
|
||||||
assert str(theIndex.refIndex) == refIndex
|
assert str(theIndex._refIndex) == refIndex
|
||||||
assert str(theIndex.novelIndex) == novelIndex
|
assert str(theIndex._novelIndex) == novelIndex
|
||||||
assert str(theIndex.noteIndex) == noteIndex
|
assert str(theIndex._noteIndex) == noteIndex
|
||||||
assert str(theIndex.textCounts) == textCounts
|
assert str(theIndex._textCounts) == textCounts
|
||||||
|
|
||||||
# Break the index and check that we notice
|
# Break the index and check that we notice
|
||||||
assert not theIndex.indexBroken
|
assert not theIndex.indexBroken
|
||||||
theIndex.tagIndex["Bod"].append("Stuff") # No longer len() == 4
|
theIndex._tagIndex["Bod"].append("Stuff") # No longer len() == 4
|
||||||
theIndex.checkIndex()
|
theIndex.checkIndex()
|
||||||
assert theIndex.indexBroken
|
assert theIndex.indexBroken
|
||||||
|
|
||||||
assert theIndex.loadIndex()
|
assert theIndex.loadIndex()
|
||||||
assert not theIndex.indexBroken
|
assert not theIndex.indexBroken
|
||||||
theIndex.refIndex["fb609cd8319dc"]["T000001"]["tags"].append("Stuff") # No longer len() == 3
|
theIndex._refIndex["fb609cd8319dc"]["T000001"]["tags"].append("Stuff") # No longer len() == 3
|
||||||
theIndex.checkIndex()
|
theIndex.checkIndex()
|
||||||
assert theIndex.indexBroken
|
assert theIndex.indexBroken
|
||||||
|
|
||||||
assert theIndex.loadIndex()
|
assert theIndex.loadIndex()
|
||||||
assert not theIndex.indexBroken
|
assert not theIndex.indexBroken
|
||||||
theIndex.novelIndex["7a992350f3eb6"]["T000001"]["Stuff"] = "" # No longer len(keys()) == 8
|
theIndex._novelIndex["7a992350f3eb6"]["T000001"]["Stuff"] = "" # No longer len(keys()) == 8
|
||||||
theIndex.checkIndex()
|
theIndex.checkIndex()
|
||||||
assert theIndex.indexBroken
|
assert theIndex.indexBroken
|
||||||
|
|
||||||
assert theIndex.loadIndex()
|
assert theIndex.loadIndex()
|
||||||
assert not theIndex.indexBroken
|
assert not theIndex.indexBroken
|
||||||
theIndex.noteIndex["4c4f28287af27"]["T000001"]["Stuff"] = "" # No longer len(keys()) == 8
|
theIndex._noteIndex["4c4f28287af27"]["T000001"]["Stuff"] = "" # No longer len(keys()) == 8
|
||||||
theIndex.checkIndex()
|
theIndex.checkIndex()
|
||||||
assert theIndex.indexBroken
|
assert theIndex.indexBroken
|
||||||
|
|
||||||
assert theIndex.loadIndex()
|
assert theIndex.loadIndex()
|
||||||
assert not theIndex.indexBroken
|
assert not theIndex.indexBroken
|
||||||
theIndex.textCounts["7a992350f3eb6"].append("Stuff") # No longer len() == 3
|
theIndex._textCounts["7a992350f3eb6"].append("Stuff") # No longer len() == 3
|
||||||
theIndex.checkIndex()
|
theIndex.checkIndex()
|
||||||
assert theIndex.indexBroken
|
assert theIndex.indexBroken
|
||||||
|
|
||||||
# Make the try/except trigger as well
|
# Make the try/except trigger as well
|
||||||
assert theIndex.loadIndex()
|
assert theIndex.loadIndex()
|
||||||
assert not theIndex.indexBroken
|
assert not theIndex.indexBroken
|
||||||
theIndex.refIndex["fb609cd8319dc"]["T000001"] = {"tagssss": []} # Wrong key name
|
theIndex._refIndex["fb609cd8319dc"]["T000001"] = {"tagssss": []} # Wrong key name
|
||||||
theIndex.checkIndex()
|
theIndex.checkIndex()
|
||||||
assert theIndex.indexBroken
|
assert theIndex.indexBroken
|
||||||
|
|
||||||
@@ -205,6 +205,10 @@ def testCoreIndex_CheckThese(nwMinimal, dummyGUI):
|
|||||||
nItem = theProject.projTree[nHandle]
|
nItem = theProject.projTree[nHandle]
|
||||||
cItem = theProject.projTree[cHandle]
|
cItem = theProject.projTree[cHandle]
|
||||||
|
|
||||||
|
assert not theIndex.novelChangedSince(0)
|
||||||
|
assert not theIndex.notesChangedSince(0)
|
||||||
|
assert not theIndex.indexChangedSince(0)
|
||||||
|
|
||||||
assert theIndex.scanText(cHandle, (
|
assert theIndex.scanText(cHandle, (
|
||||||
"# Jane Smith\n"
|
"# Jane Smith\n"
|
||||||
"@tag: Jane"
|
"@tag: Jane"
|
||||||
@@ -213,8 +217,12 @@ def testCoreIndex_CheckThese(nwMinimal, dummyGUI):
|
|||||||
"# Hello World!\n"
|
"# Hello World!\n"
|
||||||
"@pov: Jane"
|
"@pov: Jane"
|
||||||
))
|
))
|
||||||
assert theIndex.tagIndex == {"Jane": [2, cHandle, "CHARACTER", "T000001"]}
|
assert theIndex._tagIndex == {"Jane": [2, cHandle, "CHARACTER", "T000001"]}
|
||||||
assert theIndex.novelIndex[nHandle]["T000001"]["title"] == "Hello World!"
|
assert theIndex.getNovelData(nHandle, "T000001")["title"] == "Hello World!"
|
||||||
|
|
||||||
|
assert theIndex.novelChangedSince(0)
|
||||||
|
assert theIndex.notesChangedSince(0)
|
||||||
|
assert theIndex.indexChangedSince(0)
|
||||||
|
|
||||||
assert theIndex.checkThese([], cItem) == []
|
assert theIndex.checkThese([], cItem) == []
|
||||||
assert theIndex.checkThese(["@tag", "Jane"], cItem) == [True, True]
|
assert theIndex.checkThese(["@tag", "Jane"], cItem) == [True, True]
|
||||||
@@ -285,8 +293,8 @@ def testCoreIndex_ScanText(nwMinimal, dummyGUI):
|
|||||||
"This is a story about Jane Smith.\n\n"
|
"This is a story about Jane Smith.\n\n"
|
||||||
"Well, not really.\n"
|
"Well, not really.\n"
|
||||||
))
|
))
|
||||||
assert str(theIndex.tagIndex) == "{'Jane': [2, '%s', 'CHARACTER', 'T000001']}" % cHandle
|
assert str(theIndex._tagIndex) == "{'Jane': [2, '%s', 'CHARACTER', 'T000001']}" % cHandle
|
||||||
assert theIndex.novelIndex[nHandle]["T000001"]["title"] == "Hello World!"
|
assert theIndex.getNovelData(nHandle, "T000001")["title"] == "Hello World!"
|
||||||
|
|
||||||
# Check that title sections are indexed properly
|
# Check that title sections are indexed properly
|
||||||
assert theIndex.scanText(nHandle, (
|
assert theIndex.scanText(nHandle, (
|
||||||
@@ -305,68 +313,68 @@ def testCoreIndex_ScanText(nwMinimal, dummyGUI):
|
|||||||
"##### Title Five\n\n" # Not interpreted as a title, the hashes is counted as a word
|
"##### Title Five\n\n" # Not interpreted as a title, the hashes is counted as a word
|
||||||
"Paragraph Five.\n\n"
|
"Paragraph Five.\n\n"
|
||||||
))
|
))
|
||||||
assert theIndex.refIndex[nHandle].get("T000000", None) is not None # Always there
|
assert theIndex._refIndex[nHandle].get("T000000", None) is not None # Always there
|
||||||
assert theIndex.refIndex[nHandle].get("T000001", None) is not None # Heading 1
|
assert theIndex._refIndex[nHandle].get("T000001", None) is not None # Heading 1
|
||||||
assert theIndex.refIndex[nHandle].get("T000002", None) is None
|
assert theIndex._refIndex[nHandle].get("T000002", None) is None
|
||||||
assert theIndex.refIndex[nHandle].get("T000003", None) is None
|
assert theIndex._refIndex[nHandle].get("T000003", None) is None
|
||||||
assert theIndex.refIndex[nHandle].get("T000004", None) is None
|
assert theIndex._refIndex[nHandle].get("T000004", None) is None
|
||||||
assert theIndex.refIndex[nHandle].get("T000005", None) is None
|
assert theIndex._refIndex[nHandle].get("T000005", None) is None
|
||||||
assert theIndex.refIndex[nHandle].get("T000006", None) is None
|
assert theIndex._refIndex[nHandle].get("T000006", None) is None
|
||||||
assert theIndex.refIndex[nHandle].get("T000007", None) is not None # Heading 2
|
assert theIndex._refIndex[nHandle].get("T000007", None) is not None # Heading 2
|
||||||
assert theIndex.refIndex[nHandle].get("T000008", None) is None
|
assert theIndex._refIndex[nHandle].get("T000008", None) is None
|
||||||
assert theIndex.refIndex[nHandle].get("T000009", None) is None
|
assert theIndex._refIndex[nHandle].get("T000009", None) is None
|
||||||
assert theIndex.refIndex[nHandle].get("T000010", None) is None
|
assert theIndex._refIndex[nHandle].get("T000010", None) is None
|
||||||
assert theIndex.refIndex[nHandle].get("T000011", None) is None
|
assert theIndex._refIndex[nHandle].get("T000011", None) is None
|
||||||
assert theIndex.refIndex[nHandle].get("T000012", None) is None
|
assert theIndex._refIndex[nHandle].get("T000012", None) is None
|
||||||
assert theIndex.refIndex[nHandle].get("T000013", None) is not None # Heading 3
|
assert theIndex._refIndex[nHandle].get("T000013", None) is not None # Heading 3
|
||||||
assert theIndex.refIndex[nHandle].get("T000014", None) is None
|
assert theIndex._refIndex[nHandle].get("T000014", None) is None
|
||||||
assert theIndex.refIndex[nHandle].get("T000015", None) is None
|
assert theIndex._refIndex[nHandle].get("T000015", None) is None
|
||||||
assert theIndex.refIndex[nHandle].get("T000016", None) is None
|
assert theIndex._refIndex[nHandle].get("T000016", None) is None
|
||||||
assert theIndex.refIndex[nHandle].get("T000017", None) is None
|
assert theIndex._refIndex[nHandle].get("T000017", None) is None
|
||||||
assert theIndex.refIndex[nHandle].get("T000018", None) is None
|
assert theIndex._refIndex[nHandle].get("T000018", None) is None
|
||||||
assert theIndex.refIndex[nHandle].get("T000019", None) is not None # Heading 4
|
assert theIndex._refIndex[nHandle].get("T000019", None) is not None # Heading 4
|
||||||
assert theIndex.refIndex[nHandle].get("T000020", None) is None
|
assert theIndex._refIndex[nHandle].get("T000020", None) is None
|
||||||
assert theIndex.refIndex[nHandle].get("T000021", None) is None
|
assert theIndex._refIndex[nHandle].get("T000021", None) is None
|
||||||
assert theIndex.refIndex[nHandle].get("T000022", None) is None
|
assert theIndex._refIndex[nHandle].get("T000022", None) is None
|
||||||
assert theIndex.refIndex[nHandle].get("T000023", None) is None
|
assert theIndex._refIndex[nHandle].get("T000023", None) is None
|
||||||
assert theIndex.refIndex[nHandle].get("T000024", None) is None
|
assert theIndex._refIndex[nHandle].get("T000024", None) is None
|
||||||
assert theIndex.refIndex[nHandle].get("T000025", None) is None
|
assert theIndex._refIndex[nHandle].get("T000025", None) is None
|
||||||
assert theIndex.refIndex[nHandle].get("T000026", None) is None
|
assert theIndex._refIndex[nHandle].get("T000026", None) is None
|
||||||
|
|
||||||
assert theIndex.novelIndex[nHandle]["T000001"]["level"] == "H1"
|
assert theIndex._novelIndex[nHandle]["T000001"]["level"] == "H1"
|
||||||
assert theIndex.novelIndex[nHandle]["T000007"]["level"] == "H2"
|
assert theIndex._novelIndex[nHandle]["T000007"]["level"] == "H2"
|
||||||
assert theIndex.novelIndex[nHandle]["T000013"]["level"] == "H3"
|
assert theIndex._novelIndex[nHandle]["T000013"]["level"] == "H3"
|
||||||
assert theIndex.novelIndex[nHandle]["T000019"]["level"] == "H4"
|
assert theIndex._novelIndex[nHandle]["T000019"]["level"] == "H4"
|
||||||
|
|
||||||
assert theIndex.novelIndex[nHandle]["T000001"]["title"] == "Title One"
|
assert theIndex._novelIndex[nHandle]["T000001"]["title"] == "Title One"
|
||||||
assert theIndex.novelIndex[nHandle]["T000007"]["title"] == "Title Two"
|
assert theIndex._novelIndex[nHandle]["T000007"]["title"] == "Title Two"
|
||||||
assert theIndex.novelIndex[nHandle]["T000013"]["title"] == "Title Three"
|
assert theIndex._novelIndex[nHandle]["T000013"]["title"] == "Title Three"
|
||||||
assert theIndex.novelIndex[nHandle]["T000019"]["title"] == "Title Four"
|
assert theIndex._novelIndex[nHandle]["T000019"]["title"] == "Title Four"
|
||||||
|
|
||||||
assert theIndex.novelIndex[nHandle]["T000001"]["layout"] == "SCENE"
|
assert theIndex._novelIndex[nHandle]["T000001"]["layout"] == "SCENE"
|
||||||
assert theIndex.novelIndex[nHandle]["T000007"]["layout"] == "SCENE"
|
assert theIndex._novelIndex[nHandle]["T000007"]["layout"] == "SCENE"
|
||||||
assert theIndex.novelIndex[nHandle]["T000013"]["layout"] == "SCENE"
|
assert theIndex._novelIndex[nHandle]["T000013"]["layout"] == "SCENE"
|
||||||
assert theIndex.novelIndex[nHandle]["T000019"]["layout"] == "SCENE"
|
assert theIndex._novelIndex[nHandle]["T000019"]["layout"] == "SCENE"
|
||||||
|
|
||||||
assert theIndex.novelIndex[nHandle]["T000001"]["synopsis"] == "Synopsis One."
|
assert theIndex._novelIndex[nHandle]["T000001"]["synopsis"] == "Synopsis One."
|
||||||
assert theIndex.novelIndex[nHandle]["T000007"]["synopsis"] == "Synopsis Two."
|
assert theIndex._novelIndex[nHandle]["T000007"]["synopsis"] == "Synopsis Two."
|
||||||
assert theIndex.novelIndex[nHandle]["T000013"]["synopsis"] == "Synopsis Three."
|
assert theIndex._novelIndex[nHandle]["T000013"]["synopsis"] == "Synopsis Three."
|
||||||
assert theIndex.novelIndex[nHandle]["T000019"]["synopsis"] == "Synopsis Four."
|
assert theIndex._novelIndex[nHandle]["T000019"]["synopsis"] == "Synopsis Four."
|
||||||
|
|
||||||
assert theIndex.novelIndex[nHandle]["T000001"]["cCount"] == 23
|
assert theIndex._novelIndex[nHandle]["T000001"]["cCount"] == 23
|
||||||
assert theIndex.novelIndex[nHandle]["T000007"]["cCount"] == 23
|
assert theIndex._novelIndex[nHandle]["T000007"]["cCount"] == 23
|
||||||
assert theIndex.novelIndex[nHandle]["T000013"]["cCount"] == 27
|
assert theIndex._novelIndex[nHandle]["T000013"]["cCount"] == 27
|
||||||
assert theIndex.novelIndex[nHandle]["T000019"]["cCount"] == 56
|
assert theIndex._novelIndex[nHandle]["T000019"]["cCount"] == 56
|
||||||
|
|
||||||
assert theIndex.novelIndex[nHandle]["T000001"]["wCount"] == 4
|
assert theIndex._novelIndex[nHandle]["T000001"]["wCount"] == 4
|
||||||
assert theIndex.novelIndex[nHandle]["T000007"]["wCount"] == 4
|
assert theIndex._novelIndex[nHandle]["T000007"]["wCount"] == 4
|
||||||
assert theIndex.novelIndex[nHandle]["T000013"]["wCount"] == 4
|
assert theIndex._novelIndex[nHandle]["T000013"]["wCount"] == 4
|
||||||
assert theIndex.novelIndex[nHandle]["T000019"]["wCount"] == 9
|
assert theIndex._novelIndex[nHandle]["T000019"]["wCount"] == 9
|
||||||
|
|
||||||
assert theIndex.novelIndex[nHandle]["T000001"]["pCount"] == 1
|
assert theIndex._novelIndex[nHandle]["T000001"]["pCount"] == 1
|
||||||
assert theIndex.novelIndex[nHandle]["T000007"]["pCount"] == 1
|
assert theIndex._novelIndex[nHandle]["T000007"]["pCount"] == 1
|
||||||
assert theIndex.novelIndex[nHandle]["T000013"]["pCount"] == 1
|
assert theIndex._novelIndex[nHandle]["T000013"]["pCount"] == 1
|
||||||
assert theIndex.novelIndex[nHandle]["T000019"]["pCount"] == 3
|
assert theIndex._novelIndex[nHandle]["T000019"]["pCount"] == 3
|
||||||
|
|
||||||
assert theIndex.scanText(cHandle, (
|
assert theIndex.scanText(cHandle, (
|
||||||
"# Title One\n\n"
|
"# Title One\n\n"
|
||||||
@@ -374,22 +382,22 @@ def testCoreIndex_ScanText(nwMinimal, dummyGUI):
|
|||||||
"% synopsis: Synopsis One.\n\n"
|
"% synopsis: Synopsis One.\n\n"
|
||||||
"Paragraph One.\n\n"
|
"Paragraph One.\n\n"
|
||||||
))
|
))
|
||||||
assert theIndex.refIndex[cHandle].get("T000000", None) is not None
|
assert theIndex._refIndex[cHandle].get("T000000", None) is not None
|
||||||
assert theIndex.refIndex[cHandle].get("T000001", None) is not None
|
assert theIndex._refIndex[cHandle].get("T000001", None) is not None
|
||||||
assert theIndex.refIndex[cHandle].get("T000002", None) is None
|
assert theIndex._refIndex[cHandle].get("T000002", None) is None
|
||||||
assert theIndex.refIndex[cHandle].get("T000003", None) is None
|
assert theIndex._refIndex[cHandle].get("T000003", None) is None
|
||||||
assert theIndex.refIndex[cHandle].get("T000004", None) is None
|
assert theIndex._refIndex[cHandle].get("T000004", None) is None
|
||||||
assert theIndex.refIndex[cHandle].get("T000005", None) is None
|
assert theIndex._refIndex[cHandle].get("T000005", None) is None
|
||||||
assert theIndex.refIndex[cHandle].get("T000006", None) is None
|
assert theIndex._refIndex[cHandle].get("T000006", None) is None
|
||||||
assert theIndex.refIndex[cHandle].get("T000007", None) is None
|
assert theIndex._refIndex[cHandle].get("T000007", None) is None
|
||||||
|
|
||||||
assert theIndex.noteIndex[cHandle]["T000001"]["level"] == "H1"
|
assert theIndex._noteIndex[cHandle]["T000001"]["level"] == "H1"
|
||||||
assert theIndex.noteIndex[cHandle]["T000001"]["title"] == "Title One"
|
assert theIndex._noteIndex[cHandle]["T000001"]["title"] == "Title One"
|
||||||
assert theIndex.noteIndex[cHandle]["T000001"]["layout"] == "NOTE"
|
assert theIndex._noteIndex[cHandle]["T000001"]["layout"] == "NOTE"
|
||||||
assert theIndex.noteIndex[cHandle]["T000001"]["synopsis"] == "Synopsis One."
|
assert theIndex._noteIndex[cHandle]["T000001"]["synopsis"] == "Synopsis One."
|
||||||
assert theIndex.noteIndex[cHandle]["T000001"]["cCount"] == 23
|
assert theIndex._noteIndex[cHandle]["T000001"]["cCount"] == 23
|
||||||
assert theIndex.noteIndex[cHandle]["T000001"]["wCount"] == 4
|
assert theIndex._noteIndex[cHandle]["T000001"]["wCount"] == 4
|
||||||
assert theIndex.noteIndex[cHandle]["T000001"]["pCount"] == 1
|
assert theIndex._noteIndex[cHandle]["T000001"]["pCount"] == 1
|
||||||
|
|
||||||
assert theIndex.scanText(sHandle, (
|
assert theIndex.scanText(sHandle, (
|
||||||
"# Title One\n\n"
|
"# Title One\n\n"
|
||||||
@@ -399,7 +407,7 @@ def testCoreIndex_ScanText(nwMinimal, dummyGUI):
|
|||||||
"% synopsis: Synopsis One.\n\n"
|
"% synopsis: Synopsis One.\n\n"
|
||||||
"Paragraph One.\n\n"
|
"Paragraph One.\n\n"
|
||||||
))
|
))
|
||||||
assert theIndex.refIndex[sHandle]["T000001"]["tags"] == (
|
assert theIndex._refIndex[sHandle]["T000001"]["tags"] == (
|
||||||
[[3, "@pov", "One"], [5, "@char", "Two"]]
|
[[3, "@pov", "One"], [5, "@char", "Two"]]
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -419,6 +427,9 @@ def testCoreIndex_ExtractData(nwMinimal, dummyGUI):
|
|||||||
nHandle = theProject.newFile("Hello", nwItemClass.NOVEL, "a508bb932959c")
|
nHandle = theProject.newFile("Hello", nwItemClass.NOVEL, "a508bb932959c")
|
||||||
cHandle = theProject.newFile("Jane", nwItemClass.CHARACTER, "afb3043c7b2b3")
|
cHandle = theProject.newFile("Jane", nwItemClass.CHARACTER, "afb3043c7b2b3")
|
||||||
|
|
||||||
|
assert theIndex.getNovelData("", "") is None
|
||||||
|
assert theIndex.getNovelData("a508bb932959c", "") is None
|
||||||
|
|
||||||
assert theIndex.scanText(cHandle, (
|
assert theIndex.scanText(cHandle, (
|
||||||
"# Jane Smith\n"
|
"# Jane Smith\n"
|
||||||
"@tag: Jane\n"
|
"@tag: Jane\n"
|
||||||
@@ -433,13 +444,32 @@ def testCoreIndex_ExtractData(nwMinimal, dummyGUI):
|
|||||||
))
|
))
|
||||||
|
|
||||||
# The novel structure should contain the pointer to the novel file header
|
# The novel structure should contain the pointer to the novel file header
|
||||||
assert theIndex.getNovelStructure() == ["%s:T000001" % nHandle]
|
theKeys = []
|
||||||
|
for aKey, _, _, _ in theIndex.novelStructure():
|
||||||
|
theKeys.append(aKey)
|
||||||
|
|
||||||
|
assert theKeys == ["%s:T000001" % nHandle]
|
||||||
|
|
||||||
# Check that excluded files can be skipped
|
# Check that excluded files can be skipped
|
||||||
theProject.projTree[nHandle].setExported(False)
|
theProject.projTree[nHandle].setExported(False)
|
||||||
assert theIndex.getNovelStructure(skipExcluded=False) == ["%s:T000001" % nHandle]
|
|
||||||
assert theIndex.getNovelStructure(skipExcluded=True) == []
|
theKeys = []
|
||||||
assert theIndex.getNovelStructure() == []
|
for aKey, _, _, _ in theIndex.novelStructure(skipExcluded=False):
|
||||||
|
theKeys.append(aKey)
|
||||||
|
|
||||||
|
assert theKeys == ["%s:T000001" % nHandle]
|
||||||
|
|
||||||
|
theKeys = []
|
||||||
|
for aKey, _, _, _ in theIndex.novelStructure(skipExcluded=True):
|
||||||
|
theKeys.append(aKey)
|
||||||
|
|
||||||
|
assert theKeys == []
|
||||||
|
|
||||||
|
theKeys = []
|
||||||
|
for aKey, _, _, _ in theIndex.novelStructure():
|
||||||
|
theKeys.append(aKey)
|
||||||
|
|
||||||
|
assert theKeys == []
|
||||||
|
|
||||||
# The novel file should have the correct counts
|
# The novel file should have the correct counts
|
||||||
cC, wC, pC = theIndex.getCounts(nHandle)
|
cC, wC, pC = theIndex.getCounts(nHandle)
|
||||||
|
|||||||
@@ -26,11 +26,11 @@ def testGuiViewer_Main(qtbot, monkeypatch, nwGUI, nwLipsum):
|
|||||||
assert nwGUI.openProject(nwLipsum)
|
assert nwGUI.openProject(nwLipsum)
|
||||||
|
|
||||||
# Rebuild the index as it isn't automatically copied
|
# Rebuild the index as it isn't automatically copied
|
||||||
assert nwGUI.theIndex.tagIndex == {}
|
assert nwGUI.theIndex._tagIndex == {}
|
||||||
assert nwGUI.theIndex.refIndex == {}
|
assert nwGUI.theIndex._refIndex == {}
|
||||||
nwGUI.mainMenu.aRebuildIndex.activate(QAction.Trigger)
|
nwGUI.mainMenu.aRebuildIndex.activate(QAction.Trigger)
|
||||||
assert nwGUI.theIndex.tagIndex != {}
|
assert nwGUI.theIndex._tagIndex != {}
|
||||||
assert nwGUI.theIndex.refIndex != {}
|
assert nwGUI.theIndex._refIndex != {}
|
||||||
|
|
||||||
# Select a document in the project tree
|
# Select a document in the project tree
|
||||||
assert nwGUI.treeView.setSelectedHandle("88243afbe5ed8")
|
assert nwGUI.treeView.setSelectedHandle("88243afbe5ed8")
|
||||||
|
|||||||
@@ -0,0 +1,147 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""novelWriter Main GUI Project Tree Class Tester
|
||||||
|
"""
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
import os
|
||||||
|
|
||||||
|
from tools import writeFile
|
||||||
|
|
||||||
|
from PyQt5.QtCore import Qt
|
||||||
|
from PyQt5.QtWidgets import QMessageBox
|
||||||
|
|
||||||
|
@pytest.mark.gui
|
||||||
|
def testGuiNovelTree_TreeItems(qtbot, caplog, monkeypatch, nwGUI, nwMinimal):
|
||||||
|
"""Test navigating the novel tree.
|
||||||
|
"""
|
||||||
|
# Block message box
|
||||||
|
monkeypatch.setattr(QMessageBox, "question", lambda *args: QMessageBox.Yes)
|
||||||
|
monkeypatch.setattr(QMessageBox, "information", lambda *args: QMessageBox.Yes)
|
||||||
|
|
||||||
|
nwGUI.openProject(nwMinimal)
|
||||||
|
nwGUI.theProject.projTree.setSeed(42)
|
||||||
|
nwTree = nwGUI.novelView
|
||||||
|
|
||||||
|
##
|
||||||
|
# Show/Hide Scrollbars
|
||||||
|
##
|
||||||
|
|
||||||
|
nwTree.mainConf.hideVScroll = True
|
||||||
|
nwTree.mainConf.hideHScroll = True
|
||||||
|
nwTree.initTree()
|
||||||
|
assert not nwTree.verticalScrollBar().isVisible()
|
||||||
|
assert not nwTree.horizontalScrollBar().isVisible()
|
||||||
|
|
||||||
|
nwTree.mainConf.hideVScroll = False
|
||||||
|
nwTree.mainConf.hideHScroll = False
|
||||||
|
nwTree.initTree()
|
||||||
|
assert nwTree.verticalScrollBar().isEnabled()
|
||||||
|
assert nwTree.horizontalScrollBar().isEnabled()
|
||||||
|
|
||||||
|
##
|
||||||
|
# Populate Tree
|
||||||
|
##
|
||||||
|
|
||||||
|
nwGUI.projTabs.setCurrentIndex(nwGUI.idxNovelView)
|
||||||
|
|
||||||
|
# The tree should be empty as there is no index
|
||||||
|
assert nwTree.topLevelItemCount() == 0
|
||||||
|
|
||||||
|
nwGUI.rebuildIndex()
|
||||||
|
nwTree._populateTree()
|
||||||
|
assert nwTree.topLevelItemCount() == 1
|
||||||
|
|
||||||
|
# Rebuild should preserve selection
|
||||||
|
topItem = nwTree.topLevelItem(0)
|
||||||
|
assert not topItem.isSelected()
|
||||||
|
topItem.setSelected(True)
|
||||||
|
assert nwTree.selectedItems()[0] == topItem
|
||||||
|
assert nwTree.getSelectedHandle() == "a35baf2e93843"
|
||||||
|
|
||||||
|
nwTree.refreshTree()
|
||||||
|
assert nwTree.topLevelItem(0).isSelected()
|
||||||
|
|
||||||
|
##
|
||||||
|
# Open Items
|
||||||
|
##
|
||||||
|
|
||||||
|
# Clear selection
|
||||||
|
nwTree.clearSelection()
|
||||||
|
scItem = nwTree.topLevelItem(0).child(0).child(0)
|
||||||
|
scItem.setSelected(True)
|
||||||
|
assert scItem.isSelected()
|
||||||
|
|
||||||
|
# Clear selection with mouse
|
||||||
|
vPort = nwTree.viewport()
|
||||||
|
qtbot.mouseClick(vPort, Qt.LeftButton, pos=vPort.rect().center(), delay=10)
|
||||||
|
assert not scItem.isSelected()
|
||||||
|
|
||||||
|
# Double-click item
|
||||||
|
scItem.setSelected(True)
|
||||||
|
assert scItem.isSelected()
|
||||||
|
assert nwGUI.docEditor.theHandle is None
|
||||||
|
nwTree._treeDoubleClick(scItem, 0)
|
||||||
|
assert nwGUI.docEditor.theHandle == "8c659a11cd429"
|
||||||
|
|
||||||
|
# Open item with middle mouse button
|
||||||
|
scItem.setSelected(True)
|
||||||
|
assert scItem.isSelected()
|
||||||
|
assert nwGUI.docViewer.theHandle is None
|
||||||
|
qtbot.mouseClick(vPort, Qt.MiddleButton, pos=vPort.rect().center(), delay=10)
|
||||||
|
assert nwGUI.docViewer.theHandle is None
|
||||||
|
|
||||||
|
scRect = nwTree.visualItemRect(scItem)
|
||||||
|
oldData = scItem.data(nwTree.C_TITLE, Qt.UserRole)
|
||||||
|
scItem.setData(nwTree.C_TITLE, Qt.UserRole, (None, "", ""))
|
||||||
|
qtbot.mouseClick(vPort, Qt.MiddleButton, pos=scRect.center(), delay=10)
|
||||||
|
assert nwGUI.docViewer.theHandle is None
|
||||||
|
|
||||||
|
scItem.setData(nwTree.C_TITLE, Qt.UserRole, oldData)
|
||||||
|
qtbot.mouseClick(vPort, Qt.MiddleButton, pos=scRect.center(), delay=10)
|
||||||
|
assert nwGUI.docViewer.theHandle == "8c659a11cd429"
|
||||||
|
|
||||||
|
##
|
||||||
|
# Populate Tree
|
||||||
|
##
|
||||||
|
|
||||||
|
# Add weird titles to first file to check hnadling of non-standard
|
||||||
|
# order of title levels.
|
||||||
|
writeFile(os.path.join(nwMinimal, "content", "a35baf2e93843.nwd"), (
|
||||||
|
"#### Section wo/Scene\n\n"
|
||||||
|
"### Scene wo/Chapter\n\n"
|
||||||
|
"## Chapter wo/Title\n\n"
|
||||||
|
"# Title\n\n"
|
||||||
|
"#### Section w/Title, wo/Scene\n\n"
|
||||||
|
"### Scene w/Title, wo/Chapter\n\n"
|
||||||
|
"## Chapter\n\n"
|
||||||
|
"#### Section w/Chapter, wo/Scene\n\n"
|
||||||
|
"### Scene\n\n"
|
||||||
|
"#### Section\n\n"
|
||||||
|
))
|
||||||
|
nwGUI.rebuildIndex()
|
||||||
|
nwTree._populateTree()
|
||||||
|
assert nwTree.topLevelItem(0).text(nwTree.C_TITLE) == "Section wo/Scene"
|
||||||
|
assert nwTree.topLevelItem(1).text(nwTree.C_TITLE) == "Scene wo/Chapter"
|
||||||
|
assert nwTree.topLevelItem(2).text(nwTree.C_TITLE) == "Chapter wo/Title"
|
||||||
|
assert nwTree.topLevelItem(3).text(nwTree.C_TITLE) == "Title"
|
||||||
|
|
||||||
|
tTitle = nwTree.topLevelItem(3)
|
||||||
|
assert tTitle.child(0).text(nwTree.C_TITLE) == "Section w/Title, wo/Scene"
|
||||||
|
assert tTitle.child(1).text(nwTree.C_TITLE) == "Scene w/Title, wo/Chapter"
|
||||||
|
assert tTitle.child(2).text(nwTree.C_TITLE) == "Chapter"
|
||||||
|
|
||||||
|
tChap = tTitle.child(2)
|
||||||
|
assert tChap.child(0).text(nwTree.C_TITLE) == "Section w/Chapter, wo/Scene"
|
||||||
|
assert tChap.child(1).text(nwTree.C_TITLE) == "Scene"
|
||||||
|
|
||||||
|
tScene = tChap.child(1)
|
||||||
|
assert tScene.child(0).text(nwTree.C_TITLE) == "Section"
|
||||||
|
|
||||||
|
##
|
||||||
|
# Close
|
||||||
|
##
|
||||||
|
|
||||||
|
# qtbot.stopForInteraction()
|
||||||
|
nwGUI.closeProject()
|
||||||
|
|
||||||
|
# END Test testGuiNovelTree_TreeItems
|
||||||
@@ -24,7 +24,7 @@ def testGuiOutline_Main(qtbot, monkeypatch, nwGUI, nwLipsum):
|
|||||||
nwGUI.mainConf.lastPath = nwLipsum
|
nwGUI.mainConf.lastPath = nwLipsum
|
||||||
|
|
||||||
nwGUI.rebuildIndex()
|
nwGUI.rebuildIndex()
|
||||||
nwGUI.tabWidget.setCurrentIndex(nwGUI.idxTabProj)
|
nwGUI.mainTabs.setCurrentIndex(nwGUI.idxTabProj)
|
||||||
|
|
||||||
assert nwGUI.projView.topLevelItemCount() > 0
|
assert nwGUI.projView.topLevelItemCount() > 0
|
||||||
|
|
||||||
|
|||||||
@@ -220,10 +220,11 @@ def testGuiPreferences_Main(qtbot, monkeypatch, fncDir, outDir, refDir):
|
|||||||
compFile = os.path.join(refDir, "guiPreferences_novelwriter.conf")
|
compFile = os.path.join(refDir, "guiPreferences_novelwriter.conf")
|
||||||
copyfile(projFile, testFile)
|
copyfile(projFile, testFile)
|
||||||
ignoreLines = [
|
ignoreLines = [
|
||||||
2, # Timestamp
|
2, # Timestamp
|
||||||
9, # Release Notes
|
9, # Release Notes
|
||||||
12, 13, 14, 15, 16, 17, 18, # Window sizes
|
12, 13, 14, 15, # Window sizes
|
||||||
7, 28, # Fonts (depends on system default)
|
16, 17, 18, 19, # Window sizes
|
||||||
|
7, 29, # Fonts (depends on system default)
|
||||||
]
|
]
|
||||||
assert cmpFiles(testFile, compFile, ignoreLines)
|
assert cmpFiles(testFile, compFile, ignoreLines)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user