Allow switching between root novel folders
This commit is contained in:
+18
-30
@@ -73,9 +73,8 @@ class NWIndex:
|
||||
self._indexBroken = False
|
||||
|
||||
# TimeStamps
|
||||
self._timeNovel = 0
|
||||
self._timeNotes = 0
|
||||
self._timeIndex = 0
|
||||
self._indexChange = 0
|
||||
self._rootChange = {}
|
||||
|
||||
return
|
||||
|
||||
@@ -99,9 +98,8 @@ class NWIndex:
|
||||
"""
|
||||
self._tagsIndex.clear()
|
||||
self._itemIndex.clear()
|
||||
self._timeNovel = 0
|
||||
self._timeNotes = 0
|
||||
self._timeIndex = 0
|
||||
self._indexChange = 0
|
||||
self._rootChange = {}
|
||||
return
|
||||
|
||||
def deleteHandle(self, tHandle):
|
||||
@@ -129,20 +127,16 @@ class NWIndex:
|
||||
|
||||
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
|
||||
return self._indexChange > checkTime
|
||||
|
||||
def rootChangedSince(self, rootHandle, checkTime):
|
||||
"""Check if the index has changed since a given time for a
|
||||
given root item.
|
||||
"""
|
||||
return self._rootChange.get(rootHandle, self._indexChange) > checkTime
|
||||
|
||||
##
|
||||
# Load and Save Index to/from File
|
||||
@@ -184,10 +178,7 @@ class NWIndex:
|
||||
logger.warning("Item '%s' is not in the index", fHandle)
|
||||
self.reIndexHandle(fHandle)
|
||||
|
||||
nowTime = round(time())
|
||||
self._timeNovel = nowTime
|
||||
self._timeNotes = nowTime
|
||||
self._timeIndex = nowTime
|
||||
self._indexChange = round(time())
|
||||
|
||||
logger.verbose("Index loaded in %.3f ms", (time() - tStart)*1000)
|
||||
|
||||
@@ -307,11 +298,8 @@ class NWIndex:
|
||||
|
||||
# Update timestamps for index changes
|
||||
nowTime = round(time())
|
||||
self._timeIndex = nowTime
|
||||
if theItem.itemLayout == nwItemLayout.NOTE:
|
||||
self._timeNotes = nowTime
|
||||
else:
|
||||
self._timeNovel = nowTime
|
||||
self._indexChange = nowTime
|
||||
self._rootChange[theItem.itemRoot] = nowTime
|
||||
|
||||
return True
|
||||
|
||||
@@ -466,14 +454,14 @@ class NWIndex:
|
||||
# Extract Data
|
||||
##
|
||||
|
||||
def novelStructure(self, skipExcl=True):
|
||||
def novelStructure(self, rootHandle=None, skipExcl=True):
|
||||
"""Iterate over all titles in the novel, in the correct order as
|
||||
they appear in the tree view and in the respective document
|
||||
files, but skipping all note files.
|
||||
"""
|
||||
for tHandle, sTitle, hItem in self._itemIndex.iterNovelStructure(skipExcl=skipExcl):
|
||||
tKey = f"{tHandle}:{sTitle}"
|
||||
yield tKey, tHandle, sTitle, hItem
|
||||
novStruct = self._itemIndex.iterNovelStructure(rootHandle=rootHandle, skipExcl=skipExcl)
|
||||
for tHandle, sTitle, hItem in novStruct:
|
||||
yield f"{tHandle}:{sTitle}", tHandle, sTitle, hItem
|
||||
return
|
||||
|
||||
def getNovelWordCount(self, skipExcl=True):
|
||||
|
||||
@@ -136,7 +136,7 @@ class GuiNovelTree(QTreeWidget):
|
||||
"""
|
||||
logger.verbose("Requesting refresh of the novel tree")
|
||||
treeChanged = self.theParent.treeView.changedSince(self._lastBuild)
|
||||
indexChanged = self.theProject.index.novelChangedSince(self._lastBuild)
|
||||
indexChanged = self.theProject.index.indexChangedSince(self._lastBuild)
|
||||
if not (treeChanged or indexChanged or overRide):
|
||||
logger.verbose("No changes have been made to the novel index")
|
||||
return
|
||||
|
||||
@@ -83,6 +83,7 @@ class GuiOutline(QWidget):
|
||||
self.outlineView.hiddenStateChanged.connect(self._updateMenuColumns)
|
||||
self.outlineView.activeItemChanged.connect(self.outlineData.showItem)
|
||||
self.outlineData.itemTagClicked.connect(self._tagClicked)
|
||||
self.outlineBar.novelRootChanged.connect(self._rootItemChanged)
|
||||
self.outlineBar.viewColumnToggled.connect(self.outlineView.menuColumnToggled)
|
||||
self.outlineBar.viewRefreshRequested.connect(
|
||||
lambda: self.outlineView.refreshTree(overRide=True)
|
||||
@@ -153,6 +154,13 @@ class GuiOutline(QWidget):
|
||||
self.loadDocumentTagRequest.emit(link, nwDocMode.VIEW)
|
||||
return
|
||||
|
||||
@pyqtSlot(str)
|
||||
def _rootItemChanged(self, handle):
|
||||
"""The root novel handle has been changed.
|
||||
"""
|
||||
self.outlineView.refreshTree(rootHandle=handle, overRide=True)
|
||||
return
|
||||
|
||||
# END Class GuiOutline
|
||||
|
||||
|
||||
@@ -394,7 +402,7 @@ class GuiOutlineView(QTreeWidget):
|
||||
|
||||
return
|
||||
|
||||
def refreshTree(self, overRide=False, novelChanged=False):
|
||||
def refreshTree(self, rootHandle=None, overRide=False, novelChanged=False):
|
||||
"""Called whenever the Outline tab is activated and controls
|
||||
what data to load, and if necessary, force a rebuild of the
|
||||
tree.
|
||||
@@ -402,17 +410,17 @@ class GuiOutlineView(QTreeWidget):
|
||||
# If it's the first time, we always build
|
||||
if self._firstView or self._firstView and overRide:
|
||||
self._loadHeaderState()
|
||||
self._populateTree()
|
||||
self._populateTree(rootHandle)
|
||||
self._firstView = False
|
||||
return
|
||||
|
||||
# If the novel index or novel tree has changed since the tree
|
||||
# was last built, we rebuild the tree from the updated index.
|
||||
indexChanged = self.theProject.index.novelChangedSince(self._lastBuild)
|
||||
indexChanged = self.theProject.index.rootChangedSince(rootHandle, self._lastBuild)
|
||||
doBuild = (novelChanged or indexChanged) and self.theProject.autoOutline
|
||||
if doBuild or overRide:
|
||||
logger.debug("Rebuilding Project Outline")
|
||||
self._populateTree()
|
||||
self._populateTree(rootHandle)
|
||||
|
||||
return
|
||||
|
||||
@@ -577,7 +585,7 @@ class GuiOutlineView(QTreeWidget):
|
||||
|
||||
return
|
||||
|
||||
def _populateTree(self):
|
||||
def _populateTree(self, rootHandle):
|
||||
"""Build the tree based on the project index, and the header
|
||||
based on the defined constants, default values and user selected
|
||||
width, order and hidden state. All columns are populated, even
|
||||
@@ -610,7 +618,8 @@ class GuiOutlineView(QTreeWidget):
|
||||
currChapter = None
|
||||
currScene = None
|
||||
|
||||
for _, tHandle, sTitle, novIdx in self.theProject.index.novelStructure(skipExcl=True):
|
||||
novStruct = self.theProject.index.novelStructure(rootHandle=rootHandle, skipExcl=True)
|
||||
for _, tHandle, sTitle, novIdx in novStruct:
|
||||
|
||||
tItem = self._createTreeItem(tHandle, sTitle, novIdx)
|
||||
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
%%~name: Chapter One
|
||||
%%~path: e5e47ebf63b1c/a520879ca0b45
|
||||
%%~kind: NOVEL/DOCUMENT
|
||||
### Chapter One
|
||||
|
||||
@pov: Jane
|
||||
|
||||
% Synopsis: Remember Jane and John?
|
||||
|
||||
### Scene One
|
||||
|
||||
@pov: Jane
|
||||
@focus: John
|
||||
|
||||
A project can have multiple novel root folders for multiple novels. This is the first scene of a sequel to the first novel.
|
||||
|
||||
In this way, the writer can keep the same notes for multiple novels. This can be especially useful if the writer is planning a multi-novel story in advance.
|
||||
@@ -0,0 +1,8 @@
|
||||
%%~name: Title Page
|
||||
%%~path: e5e47ebf63b1c/bacb7059e3083
|
||||
%%~kind: NOVEL/DOCUMENT
|
||||
#! Sequel Novel
|
||||
|
||||
>> **By Jane Doh** <<
|
||||
|
||||
% Synopsis: Jane and John are back in a sequel to My Novel!
|
||||
+27
-15
@@ -1,13 +1,13 @@
|
||||
<?xml version='1.0' encoding='utf-8'?>
|
||||
<novelWriterXML appVersion="1.7-beta1" hexVersion="0x010700b1" fileVersion="1.4" timeStamp="2022-06-05 13:28:34">
|
||||
<novelWriterXML appVersion="1.7-beta1" hexVersion="0x010700b1" fileVersion="1.4" timeStamp="2022-06-05 15:03:00">
|
||||
<project>
|
||||
<name>Sample Project</name>
|
||||
<title>Sample Project</title>
|
||||
<author>Jane Smith</author>
|
||||
<author>Jay Doh</author>
|
||||
<saveCount>1331</saveCount>
|
||||
<autoCount>220</autoCount>
|
||||
<editTime>67108</editTime>
|
||||
<saveCount>1334</saveCount>
|
||||
<autoCount>225</autoCount>
|
||||
<editTime>67746</editTime>
|
||||
</project>
|
||||
<settings>
|
||||
<doBackup>False</doBackup>
|
||||
@@ -15,10 +15,10 @@
|
||||
<spellCheck>True</spellCheck>
|
||||
<spellLang>None</spellLang>
|
||||
<autoOutline>True</autoOutline>
|
||||
<lastEdited>636b6aa9b697b</lastEdited>
|
||||
<lastEdited>a520879ca0b45</lastEdited>
|
||||
<lastViewed>636b6aa9b697b</lastViewed>
|
||||
<lastWordCount>1303</lastWordCount>
|
||||
<novelWordCount>894</novelWordCount>
|
||||
<lastWordCount>1363</lastWordCount>
|
||||
<novelWordCount>954</novelWordCount>
|
||||
<notesWordCount>409</notesWordCount>
|
||||
<autoReplace>
|
||||
<entry key="A">B</entry>
|
||||
@@ -33,10 +33,10 @@
|
||||
<section></section>
|
||||
</titleFormat>
|
||||
<status>
|
||||
<entry key="sf12341" count="5" red="100" green="100" blue="100">New</entry>
|
||||
<entry key="sf12341" count="7" red="100" green="100" blue="100">New</entry>
|
||||
<entry key="sf24ce6" count="1" red="200" green="50" blue="0">Notes</entry>
|
||||
<entry key="sc24b8f" count="2" red="182" green="60" blue="0">Started</entry>
|
||||
<entry key="s90e6c9" count="5" red="193" green="129" blue="0">1st Draft</entry>
|
||||
<entry key="s90e6c9" count="6" red="193" green="129" blue="0">1st Draft</entry>
|
||||
<entry key="sd51c5b" count="1" red="193" green="129" blue="0">2nd Draft</entry>
|
||||
<entry key="s8ae72a" count="0" red="193" green="129" blue="0">3rd Draft</entry>
|
||||
<entry key="s78ea90" count="0" red="58" green="180" blue="58">Finished</entry>
|
||||
@@ -48,13 +48,13 @@
|
||||
<entry key="i56be10" count="1" red="117" green="0" blue="175">Main</entry>
|
||||
</importance>
|
||||
</settings>
|
||||
<content count="24">
|
||||
<content count="27">
|
||||
<item handle="7031beac91f75" parent="None" root="7031beac91f75" order="0" type="ROOT" class="NOVEL">
|
||||
<meta expanded="True"/>
|
||||
<name status="sc24b8f" import="ia857f0">Novel</name>
|
||||
</item>
|
||||
<item handle="53b69b83cdafc" parent="7031beac91f75" root="7031beac91f75" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
|
||||
<meta expanded="False" charCount="93" wordCount="19" paraCount="2" cursorPos="2"/>
|
||||
<meta expanded="False" charCount="93" wordCount="19" paraCount="2" cursorPos="119"/>
|
||||
<name status="sc24b8f" import="ia857f0" exported="True">Title Page</name>
|
||||
</item>
|
||||
<item handle="974e400180a99" parent="7031beac91f75" root="7031beac91f75" order="1" type="FILE" class="NOVEL" layout="DOCUMENT">
|
||||
@@ -93,7 +93,19 @@
|
||||
<meta expanded="False" charCount="189" wordCount="37" paraCount="1" cursorPos="224"/>
|
||||
<name status="s90e6c9" import="ia857f0" exported="True">We Found John!</name>
|
||||
</item>
|
||||
<item handle="f6622b4617424" parent="None" root="f6622b4617424" order="1" type="ROOT" class="CHARACTER">
|
||||
<item handle="e5e47ebf63b1c" parent="None" root="e5e47ebf63b1c" order="1" type="ROOT" class="NOVEL">
|
||||
<meta expanded="True"/>
|
||||
<name status="sf12341" import="ia857f0">Sequel</name>
|
||||
</item>
|
||||
<item handle="bacb7059e3083" parent="e5e47ebf63b1c" root="e5e47ebf63b1c" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
|
||||
<meta expanded="False" charCount="27" wordCount="5" paraCount="1" cursorPos="100"/>
|
||||
<name status="sf12341" import="ia857f0" exported="True">Title Page</name>
|
||||
</item>
|
||||
<item handle="a520879ca0b45" parent="e5e47ebf63b1c" root="e5e47ebf63b1c" order="1" type="FILE" class="NOVEL" layout="DOCUMENT">
|
||||
<meta expanded="False" charCount="299" wordCount="55" paraCount="2" cursorPos="104"/>
|
||||
<name status="s90e6c9" import="ia857f0" exported="True">Chapter One</name>
|
||||
</item>
|
||||
<item handle="f6622b4617424" parent="None" root="f6622b4617424" order="2" type="ROOT" class="CHARACTER">
|
||||
<meta expanded="True"/>
|
||||
<name status="sf12341" import="ia857f0">Characters</name>
|
||||
</item>
|
||||
@@ -109,7 +121,7 @@
|
||||
<meta expanded="False" charCount="55" wordCount="9" paraCount="1" cursorPos="25"/>
|
||||
<name status="sf12341" import="i2d7a54" exported="True">Jane Smith</name>
|
||||
</item>
|
||||
<item handle="15c4492bd5107" parent="None" root="15c4492bd5107" order="2" type="ROOT" class="WORLD">
|
||||
<item handle="15c4492bd5107" parent="None" root="15c4492bd5107" order="3" type="ROOT" class="WORLD">
|
||||
<meta expanded="True"/>
|
||||
<name status="sf12341" import="ia857f0">Locations</name>
|
||||
</item>
|
||||
@@ -125,7 +137,7 @@
|
||||
<meta expanded="False" charCount="28" wordCount="6" paraCount="1" cursorPos="45"/>
|
||||
<name status="sf12341" import="i2d7a54" exported="True">Mars</name>
|
||||
</item>
|
||||
<item handle="6827118336ac1" parent="None" root="6827118336ac1" order="3" type="ROOT" class="ARCHIVE">
|
||||
<item handle="6827118336ac1" parent="None" root="6827118336ac1" order="4" type="ROOT" class="ARCHIVE">
|
||||
<meta expanded="True"/>
|
||||
<name status="sf12341" import="ia857f0">Archive</name>
|
||||
</item>
|
||||
@@ -137,7 +149,7 @@
|
||||
<meta expanded="False" charCount="232" wordCount="42" paraCount="1" cursorPos="239"/>
|
||||
<name status="s90e6c9" import="ia857f0" exported="True">Old File</name>
|
||||
</item>
|
||||
<item handle="98acd8c76c93a" parent="None" root="98acd8c76c93a" order="4" type="ROOT" class="TRASH">
|
||||
<item handle="98acd8c76c93a" parent="None" root="98acd8c76c93a" order="5" type="ROOT" class="TRASH">
|
||||
<meta expanded="True"/>
|
||||
<name status="sf12341" import="ia857f0">Trash</name>
|
||||
</item>
|
||||
|
||||
@@ -197,8 +197,7 @@ def testCoreIndex_CheckThese(mockGUI, fncDir, mockRnd):
|
||||
nItem = theProject.tree[nHandle]
|
||||
cItem = theProject.tree[cHandle]
|
||||
|
||||
assert theIndex.novelChangedSince(0) is False
|
||||
assert theIndex.notesChangedSince(0) is False
|
||||
assert theIndex.rootChangedSince("0000000000010", 0) is False
|
||||
assert theIndex.indexChangedSince(0) is False
|
||||
|
||||
assert theIndex.scanText(cHandle, (
|
||||
@@ -228,8 +227,7 @@ def testCoreIndex_CheckThese(mockGUI, fncDir, mockRnd):
|
||||
"@time": []
|
||||
}
|
||||
|
||||
assert theIndex.novelChangedSince(0) is True
|
||||
assert theIndex.notesChangedSince(0) is True
|
||||
assert theIndex.rootChangedSince("0000000000010", 0) is True
|
||||
assert theIndex.indexChangedSince(0) is True
|
||||
|
||||
assert theIndex.getHandleHeaderLevel(cHandle) == "H1"
|
||||
|
||||
Reference in New Issue
Block a user