Allow switching between root novel folders

This commit is contained in:
Veronica Berglyd Olsen
2022-06-05 15:04:04 +02:00
parent de07546e3f
commit 2873336dce
7 changed files with 88 additions and 56 deletions
+18 -30
View File
@@ -73,9 +73,8 @@ class NWIndex:
self._indexBroken = False self._indexBroken = False
# TimeStamps # TimeStamps
self._timeNovel = 0 self._indexChange = 0
self._timeNotes = 0 self._rootChange = {}
self._timeIndex = 0
return return
@@ -99,9 +98,8 @@ class NWIndex:
""" """
self._tagsIndex.clear() self._tagsIndex.clear()
self._itemIndex.clear() self._itemIndex.clear()
self._timeNovel = 0 self._indexChange = 0
self._timeNotes = 0 self._rootChange = {}
self._timeIndex = 0
return return
def deleteHandle(self, tHandle): def deleteHandle(self, tHandle):
@@ -129,20 +127,16 @@ 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): def indexChangedSince(self, checkTime):
"""Check if the index has changed since a given time. """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 # Load and Save Index to/from File
@@ -184,10 +178,7 @@ class NWIndex:
logger.warning("Item '%s' is not in the index", fHandle) logger.warning("Item '%s' is not in the index", fHandle)
self.reIndexHandle(fHandle) self.reIndexHandle(fHandle)
nowTime = round(time()) self._indexChange = round(time())
self._timeNovel = nowTime
self._timeNotes = nowTime
self._timeIndex = nowTime
logger.verbose("Index loaded in %.3f ms", (time() - tStart)*1000) logger.verbose("Index loaded in %.3f ms", (time() - tStart)*1000)
@@ -307,11 +298,8 @@ class NWIndex:
# Update timestamps for index changes # Update timestamps for index changes
nowTime = round(time()) nowTime = round(time())
self._timeIndex = nowTime self._indexChange = nowTime
if theItem.itemLayout == nwItemLayout.NOTE: self._rootChange[theItem.itemRoot] = nowTime
self._timeNotes = nowTime
else:
self._timeNovel = nowTime
return True return True
@@ -466,14 +454,14 @@ class NWIndex:
# Extract Data # 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 """Iterate over all titles in the novel, in the correct order as
they appear in the tree view and in the respective document they appear in the tree view and in the respective document
files, but skipping all note files. files, but skipping all note files.
""" """
for tHandle, sTitle, hItem in self._itemIndex.iterNovelStructure(skipExcl=skipExcl): novStruct = self._itemIndex.iterNovelStructure(rootHandle=rootHandle, skipExcl=skipExcl)
tKey = f"{tHandle}:{sTitle}" for tHandle, sTitle, hItem in novStruct:
yield tKey, tHandle, sTitle, hItem yield f"{tHandle}:{sTitle}", tHandle, sTitle, hItem
return return
def getNovelWordCount(self, skipExcl=True): def getNovelWordCount(self, skipExcl=True):
+1 -1
View File
@@ -136,7 +136,7 @@ class GuiNovelTree(QTreeWidget):
""" """
logger.verbose("Requesting refresh of the novel tree") logger.verbose("Requesting refresh of the novel tree")
treeChanged = self.theParent.treeView.changedSince(self._lastBuild) 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): if not (treeChanged or indexChanged or overRide):
logger.verbose("No changes have been made to the novel index") logger.verbose("No changes have been made to the novel index")
return return
+15 -6
View File
@@ -83,6 +83,7 @@ class GuiOutline(QWidget):
self.outlineView.hiddenStateChanged.connect(self._updateMenuColumns) self.outlineView.hiddenStateChanged.connect(self._updateMenuColumns)
self.outlineView.activeItemChanged.connect(self.outlineData.showItem) self.outlineView.activeItemChanged.connect(self.outlineData.showItem)
self.outlineData.itemTagClicked.connect(self._tagClicked) self.outlineData.itemTagClicked.connect(self._tagClicked)
self.outlineBar.novelRootChanged.connect(self._rootItemChanged)
self.outlineBar.viewColumnToggled.connect(self.outlineView.menuColumnToggled) self.outlineBar.viewColumnToggled.connect(self.outlineView.menuColumnToggled)
self.outlineBar.viewRefreshRequested.connect( self.outlineBar.viewRefreshRequested.connect(
lambda: self.outlineView.refreshTree(overRide=True) lambda: self.outlineView.refreshTree(overRide=True)
@@ -153,6 +154,13 @@ class GuiOutline(QWidget):
self.loadDocumentTagRequest.emit(link, nwDocMode.VIEW) self.loadDocumentTagRequest.emit(link, nwDocMode.VIEW)
return 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 # END Class GuiOutline
@@ -394,7 +402,7 @@ class GuiOutlineView(QTreeWidget):
return 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 """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.
@@ -402,17 +410,17 @@ class GuiOutlineView(QTreeWidget):
# If it's the first time, we always build # If it's the first time, we always build
if self._firstView or self._firstView and overRide: if self._firstView or self._firstView and overRide:
self._loadHeaderState() self._loadHeaderState()
self._populateTree() self._populateTree(rootHandle)
self._firstView = False self._firstView = False
return return
# If the novel index or novel tree has changed since the tree # If the novel index or novel tree has changed since the tree
# was last built, we rebuild the tree from the updated index. # 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 doBuild = (novelChanged or indexChanged) 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(rootHandle)
return return
@@ -577,7 +585,7 @@ class GuiOutlineView(QTreeWidget):
return return
def _populateTree(self): def _populateTree(self, rootHandle):
"""Build the tree based on the project index, and the header """Build the tree based on the project index, and the header
based on the defined constants, default values and user selected based on the defined constants, default values and user selected
width, order and hidden state. All columns are populated, even width, order and hidden state. All columns are populated, even
@@ -610,7 +618,8 @@ class GuiOutlineView(QTreeWidget):
currChapter = None currChapter = None
currScene = 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) tItem = self._createTreeItem(tHandle, sTitle, novIdx)
+17
View File
@@ -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.
+8
View File
@@ -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
View File
@@ -1,13 +1,13 @@
<?xml version='1.0' encoding='utf-8'?> <?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> <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>1331</saveCount> <saveCount>1334</saveCount>
<autoCount>220</autoCount> <autoCount>225</autoCount>
<editTime>67108</editTime> <editTime>67746</editTime>
</project> </project>
<settings> <settings>
<doBackup>False</doBackup> <doBackup>False</doBackup>
@@ -15,10 +15,10 @@
<spellCheck>True</spellCheck> <spellCheck>True</spellCheck>
<spellLang>None</spellLang> <spellLang>None</spellLang>
<autoOutline>True</autoOutline> <autoOutline>True</autoOutline>
<lastEdited>636b6aa9b697b</lastEdited> <lastEdited>a520879ca0b45</lastEdited>
<lastViewed>636b6aa9b697b</lastViewed> <lastViewed>636b6aa9b697b</lastViewed>
<lastWordCount>1303</lastWordCount> <lastWordCount>1363</lastWordCount>
<novelWordCount>894</novelWordCount> <novelWordCount>954</novelWordCount>
<notesWordCount>409</notesWordCount> <notesWordCount>409</notesWordCount>
<autoReplace> <autoReplace>
<entry key="A">B</entry> <entry key="A">B</entry>
@@ -33,10 +33,10 @@
<section></section> <section></section>
</titleFormat> </titleFormat>
<status> <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="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="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="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="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> <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> <entry key="i56be10" count="1" red="117" green="0" blue="175">Main</entry>
</importance> </importance>
</settings> </settings>
<content count="24"> <content count="27">
<item handle="7031beac91f75" parent="None" root="7031beac91f75" order="0" type="ROOT" class="NOVEL"> <item handle="7031beac91f75" parent="None" root="7031beac91f75" order="0" type="ROOT" class="NOVEL">
<meta expanded="True"/> <meta expanded="True"/>
<name status="sc24b8f" import="ia857f0">Novel</name> <name status="sc24b8f" import="ia857f0">Novel</name>
</item> </item>
<item handle="53b69b83cdafc" parent="7031beac91f75" root="7031beac91f75" order="0" type="FILE" class="NOVEL" layout="DOCUMENT"> <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> <name status="sc24b8f" import="ia857f0" exported="True">Title Page</name>
</item> </item>
<item handle="974e400180a99" parent="7031beac91f75" root="7031beac91f75" order="1" type="FILE" class="NOVEL" layout="DOCUMENT"> <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"/> <meta expanded="False" charCount="189" wordCount="37" paraCount="1" cursorPos="224"/>
<name status="s90e6c9" import="ia857f0" exported="True">We Found John!</name> <name status="s90e6c9" import="ia857f0" exported="True">We Found John!</name>
</item> </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"/> <meta expanded="True"/>
<name status="sf12341" import="ia857f0">Characters</name> <name status="sf12341" import="ia857f0">Characters</name>
</item> </item>
@@ -109,7 +121,7 @@
<meta expanded="False" charCount="55" wordCount="9" paraCount="1" cursorPos="25"/> <meta expanded="False" charCount="55" wordCount="9" paraCount="1" cursorPos="25"/>
<name status="sf12341" import="i2d7a54" exported="True">Jane Smith</name> <name status="sf12341" import="i2d7a54" exported="True">Jane Smith</name>
</item> </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"/> <meta expanded="True"/>
<name status="sf12341" import="ia857f0">Locations</name> <name status="sf12341" import="ia857f0">Locations</name>
</item> </item>
@@ -125,7 +137,7 @@
<meta expanded="False" charCount="28" wordCount="6" paraCount="1" cursorPos="45"/> <meta expanded="False" charCount="28" wordCount="6" paraCount="1" cursorPos="45"/>
<name status="sf12341" import="i2d7a54" exported="True">Mars</name> <name status="sf12341" import="i2d7a54" exported="True">Mars</name>
</item> </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"/> <meta expanded="True"/>
<name status="sf12341" import="ia857f0">Archive</name> <name status="sf12341" import="ia857f0">Archive</name>
</item> </item>
@@ -137,7 +149,7 @@
<meta expanded="False" charCount="232" wordCount="42" paraCount="1" cursorPos="239"/> <meta expanded="False" charCount="232" wordCount="42" paraCount="1" cursorPos="239"/>
<name status="s90e6c9" import="ia857f0" exported="True">Old File</name> <name status="s90e6c9" import="ia857f0" exported="True">Old File</name>
</item> </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"/> <meta expanded="True"/>
<name status="sf12341" import="ia857f0">Trash</name> <name status="sf12341" import="ia857f0">Trash</name>
</item> </item>
+2 -4
View File
@@ -197,8 +197,7 @@ def testCoreIndex_CheckThese(mockGUI, fncDir, mockRnd):
nItem = theProject.tree[nHandle] nItem = theProject.tree[nHandle]
cItem = theProject.tree[cHandle] cItem = theProject.tree[cHandle]
assert theIndex.novelChangedSince(0) is False assert theIndex.rootChangedSince("0000000000010", 0) is False
assert theIndex.notesChangedSince(0) is False
assert theIndex.indexChangedSince(0) is False assert theIndex.indexChangedSince(0) is False
assert theIndex.scanText(cHandle, ( assert theIndex.scanText(cHandle, (
@@ -228,8 +227,7 @@ def testCoreIndex_CheckThese(mockGUI, fncDir, mockRnd):
"@time": [] "@time": []
} }
assert theIndex.novelChangedSince(0) is True assert theIndex.rootChangedSince("0000000000010", 0) is True
assert theIndex.notesChangedSince(0) is True
assert theIndex.indexChangedSince(0) is True assert theIndex.indexChangedSince(0) is True
assert theIndex.getHandleHeaderLevel(cHandle) == "H1" assert theIndex.getHandleHeaderLevel(cHandle) == "H1"