Update the novel tree GUI (#1084)

This commit is contained in:
Veronica Berglyd Olsen
2022-06-15 16:52:42 +02:00
committed by GitHub
41 changed files with 1047 additions and 316 deletions
@@ -83,3 +83,8 @@ view_build = typ_export.svg
view_editor = mixed_edit.svg view_editor = mixed_edit.svg
view_novel = typ_book-grey.svg view_novel = typ_book-grey.svg
view_outline = typ_puzzle-outline.svg view_outline = typ_puzzle-outline.svg
deco_doc_h1 = nw_deco-h1.svg
deco_doc_h2 = nw_deco-h2.svg
deco_doc_h3 = nw_deco-h3.svg
deco_doc_h4 = nw_deco-h4.svg
@@ -0,0 +1,35 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
version="1.2"
width="4"
height="24"
viewBox="0 0 4 24"
id="svg2161"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:dc="http://purl.org/dc/elements/1.1/">
<metadata
id="metadata2167">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
</cc:Work>
</rdf:RDF>
</metadata>
<defs
id="defs2165" />
<rect
style="fill:#848484;fill-opacity:1;stroke-width:0;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:0.2"
id="rect1955"
width="4"
height="22"
x="0"
y="1"
rx="2"
ry="2" />
</svg>

After

Width:  |  Height:  |  Size: 902 B

@@ -0,0 +1,35 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
version="1.2"
width="4"
height="24"
viewBox="0 0 4 24"
id="svg2161"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:dc="http://purl.org/dc/elements/1.1/">
<metadata
id="metadata2167">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
</cc:Work>
</rdf:RDF>
</metadata>
<defs
id="defs2165" />
<rect
style="fill:#99cc99;fill-opacity:1;stroke-width:0;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:0.2"
id="rect1955"
width="4"
height="22"
x="0"
y="1"
rx="2"
ry="2" />
</svg>

After

Width:  |  Height:  |  Size: 902 B

@@ -0,0 +1,35 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
version="1.2"
width="20"
height="24"
viewBox="0 0 20 24"
id="svg2161"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:dc="http://purl.org/dc/elements/1.1/">
<metadata
id="metadata2167">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
</cc:Work>
</rdf:RDF>
</metadata>
<defs
id="defs2165" />
<rect
style="fill:#d64848;fill-opacity:1;stroke-width:0;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:0.2"
id="rect1955"
width="4"
height="22"
x="16"
y="1"
rx="2"
ry="2" />
</svg>

After

Width:  |  Height:  |  Size: 905 B

@@ -0,0 +1,35 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
version="1.2"
width="36"
height="24"
viewBox="0 0 36 24"
id="svg2161"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:dc="http://purl.org/dc/elements/1.1/">
<metadata
id="metadata2167">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
</cc:Work>
</rdf:RDF>
</metadata>
<defs
id="defs2165" />
<rect
style="fill:#6699cc;fill-opacity:1;stroke-width:0;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:0.2"
id="rect1955"
width="4"
height="22"
x="32"
y="1"
rx="2"
ry="2" />
</svg>

After

Width:  |  Height:  |  Size: 905 B

@@ -0,0 +1,35 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
version="1.2"
width="52"
height="24"
viewBox="0 0 52 24"
id="svg2161"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:dc="http://purl.org/dc/elements/1.1/">
<metadata
id="metadata2167">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
</cc:Work>
</rdf:RDF>
</metadata>
<defs
id="defs2165" />
<rect
style="fill:#ffffff;fill-opacity:1;stroke-width:0;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:0.2"
id="rect1955"
width="4"
height="22"
x="48"
y="1"
rx="2"
ry="2" />
</svg>

After

Width:  |  Height:  |  Size: 905 B

@@ -83,3 +83,8 @@ view_build = typ_export.svg
view_editor = mixed_edit.svg view_editor = mixed_edit.svg
view_novel = typ_book-grey.svg view_novel = typ_book-grey.svg
view_outline = typ_puzzle-outline.svg view_outline = typ_puzzle-outline.svg
deco_doc_h1 = nw_deco-h1.svg
deco_doc_h2 = nw_deco-h2.svg
deco_doc_h3 = nw_deco-h3.svg
deco_doc_h4 = nw_deco-h4.svg
@@ -0,0 +1,35 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
version="1.2"
width="4"
height="24"
viewBox="0 0 4 24"
id="svg2161"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:dc="http://purl.org/dc/elements/1.1/">
<metadata
id="metadata2167">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
</cc:Work>
</rdf:RDF>
</metadata>
<defs
id="defs2165" />
<rect
style="fill:#848484;fill-opacity:1;stroke-width:0;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:0.2"
id="rect1955"
width="4"
height="22"
x="0"
y="1"
rx="2"
ry="2" />
</svg>

After

Width:  |  Height:  |  Size: 902 B

@@ -0,0 +1,35 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
version="1.2"
width="4"
height="24"
viewBox="0 0 4 24"
id="svg2161"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:dc="http://purl.org/dc/elements/1.1/">
<metadata
id="metadata2167">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
</cc:Work>
</rdf:RDF>
</metadata>
<defs
id="defs2165" />
<rect
style="fill:#718c00;fill-opacity:1;stroke-width:0;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:0.2"
id="rect1955"
width="4"
height="22"
x="0"
y="1"
rx="2"
ry="2" />
</svg>

After

Width:  |  Height:  |  Size: 902 B

@@ -0,0 +1,35 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
version="1.2"
width="20"
height="24"
viewBox="0 0 20 24"
id="svg2161"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:dc="http://purl.org/dc/elements/1.1/">
<metadata
id="metadata2167">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
</cc:Work>
</rdf:RDF>
</metadata>
<defs
id="defs2165" />
<rect
style="fill:#c82829;fill-opacity:1;stroke-width:0;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:0.2"
id="rect1955"
width="4"
height="22"
x="16"
y="1"
rx="2"
ry="2" />
</svg>

After

Width:  |  Height:  |  Size: 905 B

@@ -0,0 +1,35 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
version="1.2"
width="36"
height="24"
viewBox="0 0 36 24"
id="svg2161"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:dc="http://purl.org/dc/elements/1.1/">
<metadata
id="metadata2167">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
</cc:Work>
</rdf:RDF>
</metadata>
<defs
id="defs2165" />
<rect
style="fill:#4271ae;fill-opacity:1;stroke-width:0;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:0.2"
id="rect1955"
width="4"
height="22"
x="32"
y="1"
rx="2"
ry="2" />
</svg>

After

Width:  |  Height:  |  Size: 905 B

@@ -0,0 +1,35 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
version="1.2"
width="52"
height="24"
viewBox="0 0 52 24"
id="svg2161"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:dc="http://purl.org/dc/elements/1.1/">
<metadata
id="metadata2167">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
</cc:Work>
</rdf:RDF>
</metadata>
<defs
id="defs2165" />
<rect
style="fill:#333333;fill-opacity:1;stroke-width:0;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:0.2"
id="rect1955"
width="4"
height="22"
x="48"
y="1"
rx="2"
ry="2" />
</svg>

After

Width:  |  Height:  |  Size: 905 B

+9
View File
@@ -57,6 +57,15 @@ class nwRegEx:
# END Class nwRegEx # END Class nwRegEx
class nwHeaders:
H_VALID = ("H0", "H1", "H2", "H3", "H4")
H_LEVEL = {"H0": 0, "H1": 1, "H2": 2, "H3": 3, "H4": 4}
TT_NONE = "T000000"
# END Class nwHeaders
class nwFiles: class nwFiles:
PROJ_FILE = "nwProject.nwx" PROJ_FILE = "nwProject.nwx"
+8 -12
View File
@@ -34,7 +34,7 @@ from time import time
from novelwriter.enum import nwItemType, nwItemLayout from novelwriter.enum import nwItemType, nwItemLayout
from novelwriter.error import logException from novelwriter.error import logException
from novelwriter.constants import nwFiles, nwKeyWords, nwUnicode from novelwriter.constants import nwFiles, nwKeyWords, nwUnicode, nwHeaders
from novelwriter.core.document import NWDoc from novelwriter.core.document import NWDoc
from novelwriter.common import ( from novelwriter.common import (
checkInt, isHandle, isItemClass, isTitleTag, jsonEncode checkInt, isHandle, isItemClass, isTitleTag, jsonEncode
@@ -42,10 +42,6 @@ from novelwriter.common import (
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
H_VALID = ("H0", "H1", "H2", "H3", "H4")
H_LEVEL = {"H0": 0, "H1": 1, "H2": 2, "H3": 3, "H4": 4}
TT_NONE = "T000000"
class NWIndex: class NWIndex:
"""This class holds the entire index for a given project. The index """This class holds the entire index for a given project. The index
@@ -477,7 +473,7 @@ class NWIndex:
""" """
hCount = [0, 0, 0, 0, 0] hCount = [0, 0, 0, 0, 0]
for _, _, hItem in self._itemIndex.iterNovelStructure(skipExcl=skipExcl): for _, _, hItem in self._itemIndex.iterNovelStructure(skipExcl=skipExcl):
iLevel = H_LEVEL.get(hItem.level, 0) iLevel = nwHeaders.H_LEVEL.get(hItem.level, 0)
hCount[iLevel] += 1 hCount[iLevel] += 1
return hCount return hCount
@@ -510,7 +506,7 @@ class NWIndex:
pKey = None pKey = None
for tHandle, sTitle, hItem in self._itemIndex.iterNovelStructure(skipExcl=skipExcl): for tHandle, sTitle, hItem in self._itemIndex.iterNovelStructure(skipExcl=skipExcl):
tKey = f"{tHandle}:{sTitle}" tKey = f"{tHandle}:{sTitle}"
iLevel = H_LEVEL.get(hItem.level, 0) iLevel = nwHeaders.H_LEVEL.get(hItem.level, 0)
if iLevel > maxDepth: if iLevel > maxDepth:
if pKey in tData: if pKey in tData:
tData[pKey]["words"] += hItem.wordCount tData[pKey]["words"] += hItem.wordCount
@@ -660,7 +656,7 @@ class TagsIndex:
""" """
if tagKey in self._tags: if tagKey in self._tags:
return self._tags.get(tagKey).get("heading") return self._tags.get(tagKey).get("heading")
return TT_NONE return nwHeaders.TT_NONE
def tagClass(self, tagKey): def tagClass(self, tagKey):
"""Get the class of a given tag. """Get the class of a given tag.
@@ -906,7 +902,7 @@ class IndexItem:
self._index = 0 self._index = 0
# Add a placeholder heading # Add a placeholder heading
self._headings[TT_NONE] = IndexHeading(TT_NONE) self._headings[nwHeaders.TT_NONE] = IndexHeading(nwHeaders.TT_NONE)
return return
@@ -940,8 +936,8 @@ class IndexItem:
"""Add a heading to the item. Also remove the placeholder entry """Add a heading to the item. Also remove the placeholder entry
if it exists. if it exists.
""" """
if TT_NONE in self._headings: if nwHeaders.TT_NONE in self._headings:
self._headings.pop(TT_NONE) self._headings.pop(nwHeaders.TT_NONE)
self._headings[tHeading.key] = tHeading self._headings[tHeading.key] = tHeading
return return
@@ -1110,7 +1106,7 @@ class IndexHeading:
def setLevel(self, level): def setLevel(self, level):
"""Set the level of the header if it's a valid value. """Set the level of the header if it's a valid value.
""" """
if level in H_VALID: if level in nwHeaders.H_VALID:
self._level = level self._level = level
return return
+20 -2
View File
@@ -28,6 +28,8 @@ import os
import json import json
import logging import logging
from enum import Enum
from novelwriter.error import logException from novelwriter.error import logException
from novelwriter.common import checkBool, checkFloat, checkInt, checkString from novelwriter.common import checkBool, checkFloat, checkInt, checkString
from novelwriter.constants import nwFiles from novelwriter.constants import nwFiles
@@ -56,7 +58,8 @@ VALID_MAP = {
"winWidth", "winHeight", "widthCol0", "widthCol1", "widthCol2", "winWidth", "winHeight", "widthCol0", "widthCol1", "widthCol2",
"widthCol3", "widthCol4", "wordsPerPage", "countFrom", "clearDouble" "widthCol3", "widthCol4", "wordsPerPage", "countFrom", "clearDouble"
}, },
"GuiWordList": {"winWidth", "winHeight"} "GuiWordList": {"winWidth", "winHeight"},
"GuiNovelView": {"lastCol"},
} }
@@ -137,7 +140,10 @@ class OptionState():
if group not in self._theState: if group not in self._theState:
self._theState[group] = {} self._theState[group] = {}
self._theState[group][name] = value if isinstance(value, Enum):
self._theState[group][name] = value.name
else:
self._theState[group][name] = value
return True return True
@@ -186,4 +192,16 @@ class OptionState():
return checkBool(self._theState[group].get(name, default), default) return checkBool(self._theState[group].get(name, default), default)
return default return default
def getEnum(self, group, name, lookup, default):
"""Return the value mapped to an enum. Otherwise return the
default value
"""
if issubclass(lookup, Enum):
if group in self._theState:
if name in self._theState[group]:
value = self._theState[group][name]
if value in lookup.__members__:
return lookup[value]
return default
# END Class OptionState # END Class OptionState
+17 -1
View File
@@ -102,6 +102,8 @@ class NWProject():
self.importItems = None # Note file importance values self.importItems = None # Note file importance values
self.lastEdited = None # The handle of the last file to be edited self.lastEdited = None # The handle of the last file to be edited
self.lastViewed = None # The handle of the last file to be viewed self.lastViewed = None # The handle of the last file to be viewed
self.lastNovel = None # The handle of the last novel root viewed
self.lastOutline = None # The handle of the last outline root viewed
self.lastWCount = 0 # The project word count from last session self.lastWCount = 0 # The project word count from last session
self.lastNovelWC = 0 # The novel files word count from last session self.lastNovelWC = 0 # The novel files word count from last session
self.lastNotesWC = 0 # The note files word count from last session self.lastNotesWC = 0 # The note files word count from last session
@@ -612,6 +614,10 @@ class NWProject():
self.lastEdited = checkString(xItem.text, None, True) self.lastEdited = checkString(xItem.text, None, True)
elif xItem.tag == "lastViewed": elif xItem.tag == "lastViewed":
self.lastViewed = checkString(xItem.text, None, True) self.lastViewed = checkString(xItem.text, None, True)
elif xItem.tag == "lastNovel":
self.lastNovel = checkString(xItem.text, None, True)
elif xItem.tag == "lastOutline":
self.lastOutline = checkString(xItem.text, None, True)
elif xItem.tag == "lastWordCount": elif xItem.tag == "lastWordCount":
self.lastWCount = checkInt(xItem.text, 0, False) self.lastWCount = checkInt(xItem.text, 0, False)
elif xItem.tag == "novelWordCount": elif xItem.tag == "novelWordCount":
@@ -647,7 +653,7 @@ class NWProject():
self._legacyDataFolder(projItem) self._legacyDataFolder(projItem)
except Exception: except Exception:
self.mainGui.makeAlert(self.tr( self.mainGui.makeAlert(self.tr(
"There was an error while converting project version 1.0. " "There was an error updating the project. "
"Some data may not have been preserved." "Some data may not have been preserved."
), nwAlert.ERROR) ), nwAlert.ERROR)
@@ -732,6 +738,8 @@ class NWProject():
self._packProjectValue(xSettings, "autoOutline", self.autoOutline) self._packProjectValue(xSettings, "autoOutline", self.autoOutline)
self._packProjectValue(xSettings, "lastEdited", self.lastEdited) self._packProjectValue(xSettings, "lastEdited", self.lastEdited)
self._packProjectValue(xSettings, "lastViewed", self.lastViewed) self._packProjectValue(xSettings, "lastViewed", self.lastViewed)
self._packProjectValue(xSettings, "lastNovel", self.lastNovel)
self._packProjectValue(xSettings, "lastOutline", self.lastOutline)
self._packProjectValue(xSettings, "lastWordCount", self.currWCount) self._packProjectValue(xSettings, "lastWordCount", self.currWCount)
self._packProjectValue(xSettings, "novelWordCount", self.currNovelWC) self._packProjectValue(xSettings, "novelWordCount", self.currNovelWC)
self._packProjectValue(xSettings, "notesWordCount", self.currNotesWC) self._packProjectValue(xSettings, "notesWordCount", self.currNotesWC)
@@ -1123,6 +1131,14 @@ class NWProject():
self.setProjectChanged(True) self.setProjectChanged(True)
return True return True
def setLastNovelViewed(self, tHandle):
"""Set last viewed novel root in the novel tree.
"""
if self.lastNovel != tHandle:
self.lastNovel = tHandle
self.setProjectChanged(True)
return True
def setStatusColours(self, newCols, delCols): def setStatusColours(self, newCols, delCols):
"""Update the list of novel file status flags. """Update the list of novel file status flags.
""" """
+2 -2
View File
@@ -23,7 +23,7 @@ from novelwriter.gui.doceditor import GuiDocEditor
from novelwriter.gui.docviewer import GuiDocViewer, GuiDocViewDetails from novelwriter.gui.docviewer import GuiDocViewer, GuiDocViewDetails
from novelwriter.gui.itemdetails import GuiItemDetails from novelwriter.gui.itemdetails import GuiItemDetails
from novelwriter.gui.mainmenu import GuiMainMenu from novelwriter.gui.mainmenu import GuiMainMenu
from novelwriter.gui.noveltree import GuiNovelTree from novelwriter.gui.noveltree import GuiNovelView
from novelwriter.gui.outline import GuiOutlineView from novelwriter.gui.outline import GuiOutlineView
from novelwriter.gui.projtree import GuiProjectView from novelwriter.gui.projtree import GuiProjectView
from novelwriter.gui.statusbar import GuiMainStatus from novelwriter.gui.statusbar import GuiMainStatus
@@ -37,7 +37,7 @@ __all__ = [
"GuiItemDetails", "GuiItemDetails",
"GuiMainMenu", "GuiMainMenu",
"GuiMainStatus", "GuiMainStatus",
"GuiNovelTree", "GuiNovelView",
"GuiOutlineView", "GuiOutlineView",
"GuiProjectView", "GuiProjectView",
"GuiTheme", "GuiTheme",
+2
View File
@@ -511,11 +511,13 @@ class GuiDocEditor(QTextEdit):
self.theProject.index.scanText(tHandle, docText) self.theProject.index.scanText(tHandle, docText)
newHeader = self.theProject.index.getHandleHeaderLevel(tHandle) newHeader = self.theProject.index.getHandleHeaderLevel(tHandle)
# ToDo: This should be a signal
if self._updateHeaders(checkLevel=True): if self._updateHeaders(checkLevel=True):
self.mainGui.requestNovelTreeRefresh() self.mainGui.requestNovelTreeRefresh()
else: else:
self.mainGui.novelView.updateWordCounts(tHandle) self.mainGui.novelView.updateWordCounts(tHandle)
# ToDo: This should be a signal
if oldHeader != newHeader: if oldHeader != newHeader:
self.mainGui.projView.setTreeItemValues(tHandle) self.mainGui.projView.setTreeItemValues(tHandle)
self.mainGui.itemDetails.updateViewBox(tHandle) self.mainGui.itemDetails.updateViewBox(tHandle)
+412 -123
View File
@@ -4,7 +4,9 @@ novelWriter GUI Novel Tree
GUI classe for the main window novel tree GUI classe for the main window novel tree
File History: File History:
Created: 2020-12-20 [1.1a0] Created: 2020-12-20 [1.1a0] GuiNovelTree
Created: 2022-06-12 [1.7b1] GuiNovelView
Created: 2022-06-12 [1.7b1] GuiNovelToolBar
This file is a part of novelWriter This file is a part of novelWriter
Copyright 20182020, Veronica Berglyd Olsen Copyright 20182020, Veronica Berglyd Olsen
@@ -26,84 +28,376 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
import logging import logging
import novelwriter import novelwriter
from enum import Enum
from time import time from time import time
from PyQt5.QtCore import Qt, QSize from PyQt5.QtGui import QPalette
from PyQt5.QtCore import Qt, QSize, pyqtSlot, pyqtSignal
from PyQt5.QtWidgets import ( from PyQt5.QtWidgets import (
QTreeWidget, QTreeWidgetItem, QAbstractItemView, QFrame QAbstractItemView, QActionGroup, QFrame, QHBoxLayout, QHeaderView, QLabel,
QMenu, QSizePolicy, QToolButton, QTreeWidget, QTreeWidgetItem, QVBoxLayout,
QWidget
) )
from novelwriter.enum import nwDocMode, nwItemClass
from novelwriter.common import checkInt from novelwriter.common import checkInt
from novelwriter.constants import nwKeyWords from novelwriter.constants import nwHeaders, nwKeyWords, nwLabels, trConst
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
class NovelTreeColumn(Enum):
HIDDEN = 0
POV = 1
FOCUS = 2
PLOT = 3
# END Enum NovelTreeColumn
class GuiNovelView(QWidget):
# Signals for user interaction with the novel tree
selectedItemChanged = pyqtSignal(str)
openDocumentRequest = pyqtSignal(str, Enum, int, str)
def __init__(self, mainGui):
QWidget.__init__(self, mainGui)
self.mainGui = mainGui
self.theProject = mainGui.theProject
# Build GUI
self.novelTree = GuiNovelTree(self)
self.novelBar = GuiNovelToolBar(self)
# Assemble
self.outerBox = QVBoxLayout()
self.outerBox.addWidget(self.novelBar, 0)
self.outerBox.addWidget(self.novelTree, 1)
self.outerBox.setContentsMargins(0, 0, 0, 0)
self.outerBox.setSpacing(0)
self.setLayout(self.outerBox)
# Function Mappings
self.updateWordCounts = self.novelTree.updateWordCounts
self.getSelectedHandle = self.novelTree.getSelectedHandle
return
##
# Methods
##
def initSettings(self):
self.novelTree.initSettings()
return
def refreshTree(self):
"""Refresh the current tree.
"""
self.novelTree.refreshTree(rootHandle=self.theProject.lastNovel)
return
def clearProject(self):
"""Clear project-related GUI content.
"""
self.novelTree.clearContent()
self.novelBar.clearContent()
return
def openProjectTasks(self):
"""Run opening project tasks.
"""
lastNovel = self.theProject.lastNovel
if lastNovel not in self.theProject.tree:
lastNovel = self.theProject.tree.findRoot(nwItemClass.NOVEL)
logger.debug("Setting novel tree to root item '%s'", lastNovel)
lastCol = self.theProject.options.getEnum(
"GuiNovelView", "lastCol", NovelTreeColumn, NovelTreeColumn.HIDDEN
)
self.clearProject()
self.novelBar.buildNovelRootMenu()
self.novelBar.setLastColType(lastCol, doRefresh=False)
self.novelBar.setCurrentRoot(lastNovel)
return
def closeProjectTasks(self):
"""Run closing project tasks.
"""
lastColType = self.novelTree.lastColType
self.theProject.options.setValue("GuiNovelView", "lastCol", lastColType)
return
def setFocus(self):
"""Forward the set focus call to the tree widget.
"""
self.novelTree.setFocus()
return
def treeHasFocus(self):
"""Check if the novel tree has focus.
"""
return self.novelTree.hasFocus()
##
# Public Slots
##
@pyqtSlot(str)
def updateRootItem(self, tHandle):
"""If any root item changes, rebuild the novel root menu.
"""
self.novelBar.buildNovelRootMenu()
return
# END Class GuiNovelView
class GuiNovelToolBar(QWidget):
def __init__(self, novelView):
QTreeWidget.__init__(self, novelView)
logger.debug("Initialising GuiNovelToolBar ...")
self.mainConf = novelwriter.CONFIG
self.novelView = novelView
self.theProject = novelView.mainGui.theProject
self.mainTheme = novelView.mainGui.mainTheme
iPx = self.mainTheme.baseIconSize
mPx = self.mainConf.pxInt(3)
self.setContentsMargins(0, 0, 0, 0)
self.setAutoFillBackground(True)
qPalette = self.palette()
qPalette.setBrush(QPalette.Window, qPalette.base())
self.setPalette(qPalette)
fadeCol = qPalette.text().color()
buttonStyle = (
"QToolButton {{padding: {0}px; border: none; background: transparent;}} "
"QToolButton:hover {{border: none; background: rgba({1},{2},{3},0.2);}}"
).format(mPx, fadeCol.red(), fadeCol.green(), fadeCol.blue())
# Widget Label
self.viewLabel = QLabel("<b>%s</b>" % self.tr("Novel Outline"))
self.viewLabel.setContentsMargins(0, 0, 0, 0)
self.viewLabel.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
# Refresh Button
self.tbRefresh = QToolButton(self)
self.tbRefresh.setToolTip(self.tr("Refresh"))
self.tbRefresh.setIcon(self.mainTheme.getIcon("refresh"))
self.tbRefresh.setIconSize(QSize(iPx, iPx))
self.tbRefresh.setStyleSheet(buttonStyle)
self.tbRefresh.clicked.connect(self._refreshNovelTree)
# Novel Root Menu
self.mRoot = QMenu()
self.gRoot = QActionGroup(self.mRoot)
self.aRoot = {}
self.tbRoot = QToolButton(self)
self.tbRoot.setToolTip(self.tr("Novel Root"))
self.tbRoot.setIcon(self.mainTheme.getIcon(nwLabels.CLASS_ICON[nwItemClass.NOVEL]))
self.tbRoot.setIconSize(QSize(iPx, iPx))
self.tbRoot.setStyleSheet(buttonStyle)
self.tbRoot.setMenu(self.mRoot)
self.tbRoot.setPopupMode(QToolButton.InstantPopup)
# More Options Menu
self.mMore = QMenu()
self.mLastCol = self.mMore.addMenu(self.tr("Last Column"))
self.gLastCol = QActionGroup(self.mMore)
self.aLastCol = {}
self._addLastColAction(NovelTreeColumn.HIDDEN, self.tr("Hidden"))
self._addLastColAction(NovelTreeColumn.POV, self.tr("Point of View Character"))
self._addLastColAction(NovelTreeColumn.FOCUS, self.tr("Focus Character"))
self._addLastColAction(NovelTreeColumn.PLOT, self.tr("Novel Plot"))
self.tbMore = QToolButton(self)
self.tbMore.setToolTip(self.tr("More Options"))
self.tbMore.setIcon(self.mainTheme.getIcon("menu"))
self.tbMore.setIconSize(QSize(iPx, iPx))
self.tbMore.setStyleSheet(buttonStyle)
self.tbMore.setMenu(self.mMore)
self.tbMore.setPopupMode(QToolButton.InstantPopup)
# Assemble
self.outerBox = QHBoxLayout()
self.outerBox.addWidget(self.viewLabel)
self.outerBox.addWidget(self.tbRefresh)
self.outerBox.addWidget(self.tbRoot)
self.outerBox.addWidget(self.tbMore)
self.outerBox.setContentsMargins(mPx, mPx, 0, mPx)
self.outerBox.setSpacing(0)
self.setLayout(self.outerBox)
logger.debug("GuiNovelToolBar initialisation complete")
return
##
# Methods
##
def clearContent(self):
"""Run clearing project tasks.
"""
self.mRoot.clear()
self.aRoot = {}
return
def buildNovelRootMenu(self):
"""Build the novel root menu.
"""
self.mRoot.clear()
self.aRoot = {}
for n, (tHandle, nwItem) in enumerate(self.theProject.tree.iterRoots(nwItemClass.NOVEL)):
aRoot = self.mRoot.addAction(nwItem.itemName)
aRoot.setData(tHandle)
aRoot.setCheckable(True)
aRoot.triggered.connect(lambda n, tHandle=tHandle: self.setCurrentRoot(tHandle))
self.gRoot.addAction(aRoot)
self.aRoot[tHandle] = aRoot
return
def setCurrentRoot(self, rootHandle):
"""Set the current active root handle.
"""
if rootHandle in self.aRoot:
self.aRoot[rootHandle].setChecked(True)
self.novelView.novelTree.refreshTree(rootHandle=rootHandle, overRide=True)
return
def setLastColType(self, colType, doRefresh=True):
"""Set the last column type.
"""
self.aLastCol[colType].setChecked(True)
self.novelView.novelTree.setLastColType(colType, doRefresh=doRefresh)
return
##
# Private Slots
##
@pyqtSlot()
def _refreshNovelTree(self):
"""Rebuild the current tree.
"""
rootHandle = self.theProject.lastNovel
self.novelView.novelTree.refreshTree(rootHandle=rootHandle, overRide=True)
return
##
# Internal Functions
##
def _addLastColAction(self, colType, actionLabel):
"""Add a column selection entry to the last column menu.
"""
aLast = self.mLastCol.addAction(actionLabel)
aLast.setCheckable(True)
aLast.setActionGroup(self.gLastCol)
aLast.triggered.connect(lambda: self.setLastColType(colType))
self.aLastCol[colType] = aLast
return
# END Class GuiNovelToolBar
class GuiNovelTree(QTreeWidget): class GuiNovelTree(QTreeWidget):
C_TITLE = 0 C_TITLE = 0
C_WORDS = 1 C_WORDS = 1
C_POV = 2 C_LAST = 2
def __init__(self, mainGui): def __init__(self, novelView):
QTreeWidget.__init__(self, mainGui) QTreeWidget.__init__(self, novelView)
logger.debug("Initialising GuiNovelTree ...") logger.debug("Initialising GuiNovelTree ...")
self.mainConf = novelwriter.CONFIG self.mainConf = novelwriter.CONFIG
self.mainGui = mainGui self.novelView = novelView
self.mainTheme = mainGui.mainTheme self.mainGui = novelView.mainGui
self.theProject = mainGui.theProject self.mainTheme = novelView.mainGui.mainTheme
self.theProject = novelView.mainGui.theProject
# Internal Variables # Internal Variables
self._treeMap = {} self._treeMap = {}
self._lastBuild = 0 self._lastBuild = 0
self._lastCol = NovelTreeColumn.POV
# Cached Strings
self._povLabel = trConst(nwLabels.KEY_NAME[nwKeyWords.POV_KEY])
self._focLabel = trConst(nwLabels.KEY_NAME[nwKeyWords.FOCUS_KEY])
self._pltLabel = trConst(nwLabels.KEY_NAME[nwKeyWords.PLOT_KEY])
# Build GUI # Build GUI
# =========
iPx = self.mainTheme.baseIconSize iPx = self.mainTheme.baseIconSize
self.setFrameStyle(QFrame.NoFrame) cMg = self.mainConf.pxInt(6)
self.setIconSize(QSize(iPx, iPx)) self.setIconSize(QSize(iPx, iPx))
self.setIndentation(iPx) self.setFrameStyle(QFrame.NoFrame)
self.setHeaderHidden(True)
self.setIndentation(0)
self.setColumnCount(3) self.setColumnCount(3)
self.setHeaderLabels([
self.tr("Novel Outline"),
self.tr("Words"),
self.tr("POV")
])
self.itemDoubleClicked.connect(self._treeDoubleClick)
self.itemSelectionChanged.connect(self._itemSelected)
self.setSelectionBehavior(QAbstractItemView.SelectRows) self.setSelectionBehavior(QAbstractItemView.SelectRows)
self.setSelectionMode(QAbstractItemView.SingleSelection) self.setSelectionMode(QAbstractItemView.SingleSelection)
self.setExpandsOnDoubleClick(False) self.setExpandsOnDoubleClick(False)
self.setDragEnabled(False) self.setDragEnabled(False)
treeHeadItem = self.headerItem() # Lock the column sizes
treeHeadItem.setTextAlignment(self.C_WORDS, Qt.AlignRight)
treeHeadItem.setToolTip(self.C_TITLE, self.tr("Section title"))
treeHeadItem.setToolTip(self.C_WORDS, self.tr("Word count"))
treeHeadItem.setToolTip(self.C_POV, self.tr("Point-of-view character"))
treeHeader = self.header() treeHeader = self.header()
treeHeader.setStretchLastSection(True) treeHeader.setStretchLastSection(False)
treeHeader.setMinimumSectionSize(iPx + 6) treeHeader.setMinimumSectionSize(iPx + cMg)
treeHeader.setSectionResizeMode(self.C_TITLE, QHeaderView.Stretch)
treeHeader.setSectionResizeMode(self.C_WORDS, QHeaderView.ResizeToContents)
treeHeader.setSectionResizeMode(self.C_LAST, QHeaderView.ResizeToContents)
# Get user's column width preferences for NAME and COUNT # Pre-Generate Tree Formatting
treeColWidth = self.mainConf.getNovelColWidths() fH1 = self.font()
if len(treeColWidth) <= 3: fH1.setBold(True)
for colN, colW in enumerate(treeColWidth): fH1.setUnderline(True)
self.setColumnWidth(colN, colW)
# The last column should just auto-scale fH2 = self.font()
self.resizeColumnToContents(self.C_POV) fH2.setBold(True)
self._hFonts = [self.font(), fH1, fH2, self.font(), self.font()]
self._pIndent = [
self.mainTheme.loadDecoration("deco_doc_h0", pxH=iPx),
self.mainTheme.loadDecoration("deco_doc_h1", pxH=iPx),
self.mainTheme.loadDecoration("deco_doc_h2", pxH=iPx),
self.mainTheme.loadDecoration("deco_doc_h3", pxH=iPx),
self.mainTheme.loadDecoration("deco_doc_h4", pxH=iPx),
]
# Connect signals
self.itemDoubleClicked.connect(self._treeDoubleClick)
self.itemSelectionChanged.connect(self._treeSelectionChange)
# Set custom settings # Set custom settings
self.initTree() self.initSettings()
logger.debug("GuiNovelTree initialisation complete") logger.debug("GuiNovelTree initialisation complete")
return return
def initTree(self): def initSettings(self):
"""Set or update tree widget settings. """Set or update tree widget settings.
""" """
# Scroll bars # Scroll bars
@@ -119,11 +413,19 @@ class GuiNovelTree(QTreeWidget):
return return
##
# Properties
##
@property
def lastColType(self):
return self._lastCol
## ##
# Class Methods # Class Methods
## ##
def clearTree(self): def clearContent(self):
"""Clear the GUI content and the related maps. """Clear the GUI content and the related maps.
""" """
self.clear() self.clear()
@@ -131,12 +433,15 @@ class GuiNovelTree(QTreeWidget):
self._lastBuild = 0 self._lastBuild = 0
return return
def refreshTree(self, overRide=False): def refreshTree(self, rootHandle=None, overRide=False):
"""Called whenever the Novel tab is activated. """Called whenever the Novel tab is activated.
""" """
logger.verbose("Requesting refresh of the novel tree") logger.verbose("Requesting refresh of the novel tree")
if rootHandle is None:
rootHandle = self.theProject.tree.findRoot(nwItemClass.NOVEL)
treeChanged = self.mainGui.projView.changedSince(self._lastBuild) treeChanged = self.mainGui.projView.changedSince(self._lastBuild)
indexChanged = self.theProject.index.indexChangedSince(self._lastBuild) indexChanged = self.theProject.index.rootChangedSince(rootHandle, 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
@@ -146,7 +451,8 @@ class GuiNovelTree(QTreeWidget):
if selItem: if selItem:
titleKey = selItem[0].data(self.C_TITLE, Qt.UserRole)[2] titleKey = selItem[0].data(self.C_TITLE, Qt.UserRole)[2]
self._populateTree() self._populateTree(rootHandle)
self.theProject.setLastNovelViewed(rootHandle)
if titleKey is not None and titleKey in self._treeMap: if titleKey is not None and titleKey in self._treeMap:
self._treeMap[titleKey].setSelected(True) self._treeMap[titleKey].setSelected(True)
@@ -162,15 +468,6 @@ class GuiNovelTree(QTreeWidget):
self._treeMap[titleKey].setText(self.C_WORDS, f"{wCount:n}") self._treeMap[titleKey].setText(self.C_WORDS, f"{wCount:n}")
return return
def getColumnSizes(self):
"""Return the column widths for the tree columns.
"""
retVals = [
self.columnWidth(0),
self.columnWidth(1),
]
return retVals
def getSelectedHandle(self): def getSelectedHandle(self):
"""Get the currently selected handle. If multiple items are """Get the currently selected handle. If multiple items are
selected, return the first. selected, return the first.
@@ -184,6 +481,17 @@ class GuiNovelTree(QTreeWidget):
return tHandle, tLine return tHandle, tLine
def setLastColType(self, colType, doRefresh=True):
"""Change the content type of the last column and rebuild.
"""
if self._lastCol != colType:
logger.debug("Changing last column to %s", colType.name)
self._lastCol = colType
self.setColumnHidden(self.C_LAST, colType == NovelTreeColumn.HIDDEN)
if doRefresh:
self.refreshTree(rootHandle=self.theProject.lastNovel, overRide=True)
return
## ##
# Events # Events
## ##
@@ -209,113 +517,94 @@ class GuiNovelTree(QTreeWidget):
if tHandle is None: if tHandle is None:
return return
self.mainGui.viewDocument(tHandle) self.novelView.openDocumentRequest.emit(tHandle, nwDocMode.VIEW, -1, "")
return return
## ##
# Slots # Private Slots
## ##
def _treeDoubleClick(self, tItem, tCol): @pyqtSlot()
def _treeSelectionChange(self):
"""Extract the handle and line number of the currently selected
title, and send it to the tree meta panel.
"""
tHandle, _ = self.getSelectedHandle()
if tHandle is not None:
self.novelView.selectedItemChanged.emit(tHandle)
return
@pyqtSlot("QTreeWidgetItem*", int)
def _treeDoubleClick(self, tItem, colNo):
"""Extract the handle and line number of the title double- """Extract the handle and line number of the title double-
clicked, and send it to the main gui class for opening in the clicked, and send it to the main gui class for opening in the
document editor. document editor.
""" """
tHandle, tLine = self.getSelectedHandle() tHandle, tLine = self.getSelectedHandle()
self.mainGui.openDocument(tHandle, tLine=tLine-1, doScroll=True) self.novelView.openDocumentRequest.emit(tHandle, nwDocMode.EDIT, tLine, "")
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.mainGui.itemDetails.updateViewBox(tHandle)
return return
## ##
# Internal Functions # Internal Functions
## ##
def _populateTree(self): def _populateTree(self, rootHandle):
"""Build the tree based on the project index. """Build the tree based on the project index.
""" """
self.clearTree() self.clearContent()
tStart = time()
logger.verbose("Building novel tree for root item '%s'", rootHandle)
currTitle = None novStruct = self.theProject.index.novelStructure(rootHandle=rootHandle, skipExcl=True)
currChapter = None for tKey, tHandle, sTitle, novIdx in novStruct:
currScene = None
for tKey, tHandle, sTitle, novIdx in self.theProject.index.novelStructure(skipExcl=True): iLevel = nwHeaders.H_LEVEL.get(novIdx.level, 0)
if iLevel == 0:
continue
tItem = self._createTreeItem(tHandle, sTitle, tKey, novIdx) newItem = QTreeWidgetItem()
self._treeMap[tKey] = tItem theData = (tHandle, sTitle[1:].lstrip("0"), tKey)
tLevel = novIdx.level newItem.setData(self.C_TITLE, Qt.DecorationRole, self._pIndent[iLevel])
if tLevel == "H1": newItem.setText(self.C_TITLE, novIdx.title)
self.addTopLevelItem(tItem) newItem.setData(self.C_TITLE, Qt.UserRole, theData)
currTitle = tItem newItem.setFont(self.C_TITLE, self._hFonts[iLevel])
currChapter = None newItem.setText(self.C_WORDS, f"{novIdx.wordCount:n}")
currScene = None newItem.setTextAlignment(self.C_WORDS, Qt.AlignRight)
elif tLevel == "H2": lastText, toolTip = self._getLastColumnText(tHandle, sTitle)
if currTitle is None: newItem.setText(self.C_LAST, lastText)
self.addTopLevelItem(tItem) if lastText:
else: newItem.setToolTip(self.C_LAST, toolTip)
currTitle.addChild(tItem)
currChapter = tItem
currScene = None
elif tLevel == "H3": self._treeMap[tKey] = newItem
if currChapter is None: self.addTopLevelItem(newItem)
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)
logger.verbose("Novel Tree built in %.3f ms", (time() - tStart)*1000)
self._lastBuild = time() self._lastBuild = time()
return return
def _createTreeItem(self, tHandle, sTitle, titleKey, novIdx): def _getLastColumnText(self, tHandle, sTitle):
"""Populate a tree item with all the column values. """Generate the text for the last column based on user settings.
""" """
newItem = QTreeWidgetItem() if self._lastCol == NovelTreeColumn.HIDDEN:
hIcon = "doc_%s" % novIdx.level.lower() return "", ""
theData = (tHandle, sTitle[1:].lstrip("0"), titleKey)
wC = int(novIdx.wordCount)
newItem.setText(self.C_TITLE, novIdx.title)
newItem.setData(self.C_TITLE, Qt.UserRole, theData)
newItem.setIcon(self.C_TITLE, self.mainTheme.getIcon(hIcon))
newItem.setText(self.C_WORDS, f"{wC:n}")
newItem.setTextAlignment(self.C_WORDS, Qt.AlignRight)
theRefs = self.theProject.index.getReferences(tHandle, sTitle) theRefs = self.theProject.index.getReferences(tHandle, sTitle)
newItem.setText(self.C_POV, ", ".join(theRefs[nwKeyWords.POV_KEY])) if self._lastCol == NovelTreeColumn.POV:
newText = ", ".join(theRefs[nwKeyWords.POV_KEY])
return newText, f"{self._povLabel}: {newText}"
return newItem elif self._lastCol == NovelTreeColumn.FOCUS:
newText = ", ".join(theRefs[nwKeyWords.FOCUS_KEY])
return newText, f"{self._focLabel}: {newText}"
elif self._lastCol == NovelTreeColumn.PLOT:
newText = ", ".join(theRefs[nwKeyWords.PLOT_KEY])
return newText, f"{self._pltLabel}: {newText}"
return "", ""
# END Class GuiNovelTree # END Class GuiNovelTree
+1 -1
View File
@@ -117,7 +117,7 @@ class GuiOutlineView(QWidget):
self.outlineTree.refreshTree(overRide=overRide, novelChanged=novelChanged) self.outlineTree.refreshTree(overRide=overRide, novelChanged=novelChanged)
return return
def treeFocus(self): def treeHasFocus(self):
return self.outlineTree.hasFocus() return self.outlineTree.hasFocus()
def setTreeFocus(self): def setTreeFocus(self):
+24 -31
View File
@@ -31,8 +31,8 @@ import novelwriter
from enum import Enum from enum import Enum
from time import time from time import time
from PyQt5.QtGui import QPalette
from PyQt5.QtCore import Qt, QSize, pyqtSignal, pyqtSlot from PyQt5.QtCore import Qt, QSize, pyqtSignal, pyqtSlot
from PyQt5.QtGui import QIcon, QPalette
from PyQt5.QtWidgets import ( from PyQt5.QtWidgets import (
QAbstractItemView, QFrame, QHBoxLayout, QHeaderView, QLabel, QAbstractItemView, QFrame, QHBoxLayout, QHeaderView, QLabel,
QMenu, QShortcut, QSizePolicy, QToolButton, QTreeWidget, QTreeWidgetItem, QMenu, QShortcut, QSizePolicy, QToolButton, QTreeWidget, QTreeWidgetItem,
@@ -61,7 +61,7 @@ class GuiProjectView(QWidget):
# Signals for user interaction with the project tree # Signals for user interaction with the project tree
selectedItemChanged = pyqtSignal(str) selectedItemChanged = pyqtSignal(str)
openDocumentRequest = pyqtSignal(str, Enum) openDocumentRequest = pyqtSignal(str, Enum, int, str)
def __init__(self, mainGui): def __init__(self, mainGui):
QWidget.__init__(self, mainGui) QWidget.__init__(self, mainGui)
@@ -142,7 +142,7 @@ class GuiProjectView(QWidget):
self.projTree.setFocus() self.projTree.setFocus()
return return
def treeFocus(self): def treeHasFocus(self):
"""Check if the project tree has focus. """Check if the project tree has focus.
""" """
return self.projTree.hasFocus() return self.projTree.hasFocus()
@@ -177,7 +177,7 @@ class GuiProjectToolBar(QWidget):
self.mainTheme = projView.mainGui.mainTheme self.mainTheme = projView.mainGui.mainTheme
iPx = self.mainTheme.baseIconSize iPx = self.mainTheme.baseIconSize
mPx = self.mainConf.pxInt(4) mPx = self.mainConf.pxInt(3)
self.setContentsMargins(0, 0, 0, 0) self.setContentsMargins(0, 0, 0, 0)
self.setAutoFillBackground(True) self.setAutoFillBackground(True)
@@ -192,10 +192,10 @@ class GuiProjectToolBar(QWidget):
"QToolButton:hover {{border: none; background: rgba({1},{2},{3},0.2);}}" "QToolButton:hover {{border: none; background: rgba({1},{2},{3},0.2);}}"
).format(mPx, fadeCol.red(), fadeCol.green(), fadeCol.blue()) ).format(mPx, fadeCol.red(), fadeCol.green(), fadeCol.blue())
# Tree Label # Widget Label
self.projLabel = QLabel("<b>%s</b>" % self.tr("Project Content")) self.viewLabel = QLabel("<b>%s</b>" % self.tr("Project Content"))
self.projLabel.setContentsMargins(0, 0, 0, 0) self.viewLabel.setContentsMargins(0, 0, 0, 0)
self.projLabel.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) self.viewLabel.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
# Move Buttons # Move Buttons
self.tbMoveU = QToolButton(self) self.tbMoveU = QToolButton(self)
@@ -252,7 +252,7 @@ class GuiProjectToolBar(QWidget):
self._addRootFolderEntry(nwItemClass.PLOT) self._addRootFolderEntry(nwItemClass.PLOT)
self._addRootFolderEntry(nwItemClass.CHARACTER) self._addRootFolderEntry(nwItemClass.CHARACTER)
self._addRootFolderEntry(nwItemClass.WORLD) self._addRootFolderEntry(nwItemClass.WORLD)
self._addRootFolderEntry(nwItemClass.ARCHIVE) self._addRootFolderEntry(nwItemClass.TIMELINE)
self._addRootFolderEntry(nwItemClass.OBJECT) self._addRootFolderEntry(nwItemClass.OBJECT)
self._addRootFolderEntry(nwItemClass.ENTITY) self._addRootFolderEntry(nwItemClass.ENTITY)
self._addRootFolderEntry(nwItemClass.CUSTOM) self._addRootFolderEntry(nwItemClass.CUSTOM)
@@ -285,7 +285,7 @@ class GuiProjectToolBar(QWidget):
# Assemble # Assemble
self.outerBox = QHBoxLayout() self.outerBox = QHBoxLayout()
self.outerBox.addWidget(self.projLabel) self.outerBox.addWidget(self.viewLabel)
self.outerBox.addWidget(self.tbMoveU) self.outerBox.addWidget(self.tbMoveU)
self.outerBox.addWidget(self.tbMoveD) self.outerBox.addWidget(self.tbMoveD)
self.outerBox.addWidget(self.tbAdd) self.outerBox.addWidget(self.tbAdd)
@@ -337,9 +337,8 @@ class GuiProjectTree(QTreeWidget):
self._lastMove = {} self._lastMove = {}
self._timeChanged = 0 self._timeChanged = 0
## # Build GUI
# Build GUI # =========
##
# Context Menu # Context Menu
self.setContextMenuPolicy(Qt.CustomContextMenu) self.setContextMenuPolicy(Qt.CustomContextMenu)
@@ -348,6 +347,7 @@ class GuiProjectTree(QTreeWidget):
# Tree Settings # Tree Settings
iPx = self.mainTheme.baseIconSize iPx = self.mainTheme.baseIconSize
cMg = self.mainConf.pxInt(6) cMg = self.mainConf.pxInt(6)
self.setIconSize(QSize(iPx, iPx)) self.setIconSize(QSize(iPx, iPx))
self.setFrameStyle(QFrame.NoFrame) self.setFrameStyle(QFrame.NoFrame)
self.setExpandsOnDoubleClick(False) self.setExpandsOnDoubleClick(False)
@@ -778,13 +778,6 @@ class GuiProjectTree(QTreeWidget):
if trItem is None or nwItem is None: if trItem is None or nwItem is None:
return return
expIcon = QIcon()
if nwItem.itemType == nwItemType.FILE:
if nwItem.isExported:
expIcon = self.mainTheme.getIcon("check")
else:
expIcon = self.mainTheme.getIcon("cross")
itemStatus, statusIcon = nwItem.getImportStatus() itemStatus, statusIcon = nwItem.getImportStatus()
hLevel = self.theProject.index.getHandleHeaderLevel(tHandle) hLevel = self.theProject.index.getHandleHeaderLevel(tHandle)
itemIcon = self.mainTheme.getItemIcon( itemIcon = self.mainTheme.getItemIcon(
@@ -793,18 +786,18 @@ class GuiProjectTree(QTreeWidget):
trItem.setIcon(self.C_NAME, itemIcon) trItem.setIcon(self.C_NAME, itemIcon)
trItem.setText(self.C_NAME, nwItem.itemName) trItem.setText(self.C_NAME, nwItem.itemName)
trItem.setIcon(self.C_EXPORT, expIcon)
trItem.setIcon(self.C_STATUS, statusIcon) trItem.setIcon(self.C_STATUS, statusIcon)
trItem.setToolTip(self.C_STATUS, itemStatus) trItem.setToolTip(self.C_STATUS, itemStatus)
if nwItem.itemType == nwItemType.FILE:
trItem.setIcon(
self.C_EXPORT, self.mainTheme.getIcon("check" if nwItem.isExported else "cross")
)
if self.mainConf.emphLabels and nwItem.itemLayout == nwItemLayout.DOCUMENT: if self.mainConf.emphLabels and nwItem.itemLayout == nwItemLayout.DOCUMENT:
trFont = trItem.font(self.C_NAME) trFont = trItem.font(self.C_NAME)
if hLevel in ("H1", "H2"): trFont.setBold(hLevel == "H1" or hLevel == "H2")
trFont.setBold(True) trFont.setUnderline(hLevel == "H1")
trFont.setUnderline(True)
else:
trFont.setBold(False)
trFont.setUnderline(False)
trItem.setFont(self.C_NAME, trFont) trItem.setFont(self.C_NAME, trFont)
return return
@@ -972,7 +965,7 @@ class GuiProjectTree(QTreeWidget):
return return
if tItem.itemType == nwItemType.FILE: if tItem.itemType == nwItemType.FILE:
self.projView.openDocumentRequest.emit(tHandle, nwDocMode.EDIT) self.projView.openDocumentRequest.emit(tHandle, nwDocMode.EDIT, -1, "")
else: else:
trItem = self._getTreeItem(tHandle) trItem = self._getTreeItem(tHandle)
if trItem is not None: if trItem is not None:
@@ -1016,11 +1009,11 @@ class GuiProjectTree(QTreeWidget):
if isFile: if isFile:
ctxMenu.addAction( ctxMenu.addAction(
self.tr("Open Document"), self.tr("Open Document"),
lambda: self.projView.openDocumentRequest.emit(tHandle, nwDocMode.EDIT) lambda: self.projView.openDocumentRequest.emit(tHandle, nwDocMode.EDIT, -1, "")
) )
ctxMenu.addAction( ctxMenu.addAction(
self.tr("View Document"), self.tr("View Document"),
lambda: self.projView.openDocumentRequest.emit(tHandle, nwDocMode.VIEW) lambda: self.projView.openDocumentRequest.emit(tHandle, nwDocMode.VIEW, -1, "")
) )
ctxMenu.addSeparator() ctxMenu.addSeparator()
@@ -1112,7 +1105,7 @@ class GuiProjectTree(QTreeWidget):
return return
if tItem.itemType == nwItemType.FILE: if tItem.itemType == nwItemType.FILE:
self.projView.openDocumentRequest.emit(tHandle, nwDocMode.VIEW) self.projView.openDocumentRequest.emit(tHandle, nwDocMode.VIEW, -1, "")
return return
+14 -8
View File
@@ -472,9 +472,12 @@ class GuiIcons:
# Switches # Switches
"sticky-on", "sticky-off", "sticky-on", "sticky-off",
"bullet-on", "bullet-off", "bullet-on", "bullet-off",
# Decorations
"deco_doc_h0", "deco_doc_h1", "deco_doc_h2", "deco_doc_h3", "deco_doc_h4",
} }
DECO_MAP = { IMAGE_MAP = {
"wiz-back": "wizard-back.jpg", "wiz-back": "wizard-back.jpg",
} }
@@ -573,19 +576,22 @@ class GuiIcons:
# Access Functions # Access Functions
## ##
def loadDecoration(self, decoKey, pxW, pxH): def loadDecoration(self, decoKey, pxW=None, pxH=None):
"""Load graphical decoration element based on the decoration """Load graphical decoration element based on the decoration
map. This function always returns a QSwgWidget. map or the icon map. This function always returns a QPixmap.
""" """
if decoKey not in self.DECO_MAP: if decoKey in self._themeMap:
imgPath = self._themeMap[decoKey]
elif decoKey in self.IMAGE_MAP:
imgPath = os.path.join(
self.mainConf.assetPath, "images", self.IMAGE_MAP[decoKey]
)
else:
logger.error("Decoration with name '%s' does not exist", decoKey) logger.error("Decoration with name '%s' does not exist", decoKey)
return QPixmap() return QPixmap()
imgPath = os.path.join(
self.mainConf.assetPath, "images", self.DECO_MAP[decoKey]
)
if not os.path.isfile(imgPath): if not os.path.isfile(imgPath):
logger.error("Decoration file '%s' not in assets folder", self.DECO_MAP[decoKey]) logger.error("Asset '%s' not found", self.IMAGE_MAP[decoKey])
return QPixmap() return QPixmap()
theDeco = QPixmap(imgPath) theDeco = QPixmap(imgPath)
+30 -24
View File
@@ -40,7 +40,7 @@ from PyQt5.QtWidgets import (
from novelwriter.gui import ( from novelwriter.gui import (
GuiDocEditor, GuiDocViewDetails, GuiDocViewer, GuiItemDetails, GuiMainMenu, GuiDocEditor, GuiDocViewDetails, GuiDocViewer, GuiItemDetails, GuiMainMenu,
GuiMainStatus, GuiNovelTree, GuiOutlineView, GuiProjectView, GuiTheme, GuiMainStatus, GuiNovelView, GuiOutlineView, GuiProjectView, GuiTheme,
GuiViewsBar GuiViewsBar
) )
from novelwriter.dialogs import ( from novelwriter.dialogs import (
@@ -106,7 +106,7 @@ class GuiMain(QMainWindow):
# Main GUI Elements # Main GUI Elements
self.statusBar = GuiMainStatus(self) self.statusBar = GuiMainStatus(self)
self.projView = GuiProjectView(self) self.projView = GuiProjectView(self)
self.novelView = GuiNovelTree(self) self.novelView = GuiNovelView(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)
@@ -205,6 +205,10 @@ class GuiMain(QMainWindow):
self.projView.treeItemChanged.connect(self.docViewer.updateDocInfo) self.projView.treeItemChanged.connect(self.docViewer.updateDocInfo)
self.projView.treeItemChanged.connect(self.itemDetails.updateViewBox) self.projView.treeItemChanged.connect(self.itemDetails.updateViewBox)
self.projView.rootFolderChanged.connect(self.outlineView.updateRootItem) self.projView.rootFolderChanged.connect(self.outlineView.updateRootItem)
self.projView.rootFolderChanged.connect(self.novelView.updateRootItem)
self.novelView.selectedItemChanged.connect(self.itemDetails.updateViewBox)
self.novelView.openDocumentRequest.connect(self._openDocument)
self.docEditor.spellDictionaryChanged.connect(self.statusBar.setLanguage) self.docEditor.spellDictionaryChanged.connect(self.statusBar.setLanguage)
self.docEditor.docEditedStatusChanged.connect(self.statusBar.doUpdateDocumentStatus) self.docEditor.docEditedStatusChanged.connect(self.statusBar.doUpdateDocumentStatus)
@@ -292,7 +296,7 @@ class GuiMain(QMainWindow):
""" """
# Project Area # Project Area
self.projView.clearProject() self.projView.clearProject()
self.novelView.clearTree() self.novelView.clearProject()
self.itemDetails.clearDetails() self.itemDetails.clearDetails()
# Work Area # Work Area
@@ -362,6 +366,7 @@ class GuiMain(QMainWindow):
self.saveProject() self.saveProject()
self.docEditor.setDictionaries() self.docEditor.setDictionaries()
self.outlineView.updateRootItem(None) self.outlineView.updateRootItem(None)
self.novelView.openProjectTasks()
self.rebuildIndex(beQuiet=True) self.rebuildIndex(beQuiet=True)
self.statusBar.setRefTime(self.theProject.projOpened) self.statusBar.setRefTime(self.theProject.projOpened)
self.statusBar.setProjectStatus(nwState.GOOD) self.statusBar.setProjectStatus(nwState.GOOD)
@@ -418,6 +423,7 @@ class GuiMain(QMainWindow):
self.closeDocument() self.closeDocument()
self.docViewer.clearNavHistory() self.docViewer.clearNavHistory()
self.outlineView.closeOutline() self.outlineView.closeOutline()
self.novelView.closeProjectTasks()
self.theProject.closeProject(self.idleTime) self.theProject.closeProject(self.idleTime)
self.idleRefTime = time() self.idleRefTime = time()
@@ -509,6 +515,7 @@ class GuiMain(QMainWindow):
self.docEditor.toggleSpellCheck(self.theProject.spellCheck) self.docEditor.toggleSpellCheck(self.theProject.spellCheck)
self.statusBar.setRefTime(self.theProject.projOpened) self.statusBar.setRefTime(self.theProject.projOpened)
self.outlineView.updateRootItem(None) self.outlineView.updateRootItem(None)
self.novelView.openProjectTasks()
self._updateStatusWordCount() self._updateStatusWordCount()
# Restore previously open documents, if any # Restore previously open documents, if any
@@ -789,11 +796,11 @@ class GuiMain(QMainWindow):
tHandle = None tHandle = None
tLine = None tLine = None
if self.projView.treeFocus(): if self.projView.treeHasFocus():
tHandle = self.projView.getSelectedHandle() tHandle = self.projView.getSelectedHandle()
elif self.novelView.hasFocus(): elif self.novelView.treeHasFocus():
tHandle, tLine = self.novelView.getSelectedHandle() tHandle, tLine = self.novelView.getSelectedHandle()
elif self.outlineView.treeFocus(): elif self.outlineView.treeHasFocus():
tHandle, tLine = self.outlineView.getSelectedHandle() tHandle, tLine = self.outlineView.getSelectedHandle()
else: else:
logger.warning("No item selected") logger.warning("No item selected")
@@ -825,7 +832,7 @@ class GuiMain(QMainWindow):
"""Rebuild the project tree. """Rebuild the project tree.
""" """
self.projView.populateTree() self.projView.populateTree()
self.novelView.refreshTree() # self.novelView.refreshTree()
return return
def requestNovelTreeRefresh(self): def requestNovelTreeRefresh(self):
@@ -920,7 +927,7 @@ class GuiMain(QMainWindow):
self.docEditor.initEditor() self.docEditor.initEditor()
self.docViewer.initViewer() self.docViewer.initViewer()
self.projView.initSettings() self.projView.initSettings()
self.novelView.initTree() self.novelView.initSettings()
self.outlineView.initOutline() self.outlineView.initOutline()
self._updateStatusWordCount() self._updateStatusWordCount()
@@ -1161,7 +1168,6 @@ class GuiMain(QMainWindow):
self.mainConf.setViewPanePos(self.splitView.sizes()) self.mainConf.setViewPanePos(self.splitView.sizes())
self.mainConf.setShowRefPanel(self.viewMeta.isVisible()) self.mainConf.setShowRefPanel(self.viewMeta.isVisible())
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())
@@ -1469,15 +1475,15 @@ class GuiMain(QMainWindow):
self.viewDocument(tHandle=tHandle, tAnchor=f"#{sTitle}") self.viewDocument(tHandle=tHandle, tAnchor=f"#{sTitle}")
return return
@pyqtSlot(str, Enum) @pyqtSlot(str, Enum, int, str)
def _openDocument(self, tHandle, tMode): def _openDocument(self, tHandle, tMode, tLine, tAnchor):
"""Handle an open document request. """Handle an open document request from one of the tree views.
""" """
if tHandle is not None: if tHandle is not None:
if tMode == nwDocMode.EDIT: if tMode == nwDocMode.EDIT:
self.openDocument(tHandle, changeFocus=False) self.openDocument(tHandle, tLine=tLine, changeFocus=False)
elif tMode == nwDocMode.VIEW: elif tMode == nwDocMode.VIEW:
self.viewDocument(tHandle=tHandle) self.viewDocument(tHandle=tHandle, tAnchor=(tAnchor or None))
return return
@pyqtSlot(nwView) @pyqtSlot(nwView)
@@ -1576,30 +1582,30 @@ class GuiMain(QMainWindow):
return return
@pyqtSlot(int) @pyqtSlot(int)
def _mainStackChanged(self, tabIndex): def _mainStackChanged(self, stIndex):
"""Activated when the main window tab is changed. """Activated when the main window tab is changed.
""" """
if tabIndex == self.idxEditorView: if stIndex == self.idxEditorView:
logger.verbose("Editor tab activated") logger.verbose("Editor View activated")
elif tabIndex == self.idxOutlineView: elif stIndex == self.idxOutlineView:
logger.verbose("Project outline tab activated") logger.verbose("Outline View activated")
if self.hasProject: if self.hasProject:
self.outlineView.refreshView() self.outlineView.refreshView()
return return
@pyqtSlot(int) @pyqtSlot(int)
def _projStackChanged(self, tabIndex): def _projStackChanged(self, stIndex):
"""Activated when the project view tab is changed. """Activated when the project view tab is changed.
""" """
sHandle = None sHandle = None
if tabIndex == self.idxProjView: if stIndex == self.idxProjView:
logger.verbose("Project tree tab activated") logger.verbose("Project Tree View activated")
sHandle = self.projView.getSelectedHandle() sHandle = self.projView.getSelectedHandle()
elif tabIndex == self.idxNovelView: elif stIndex == self.idxNovelView:
logger.verbose("Novel tree tab activated") logger.verbose("Novel Tree View activated")
if self.hasProject: if self.hasProject:
self.novelView.refreshTree() self.novelView.refreshTree()
sHandle, _ = self.novelView.getSelectedHandle() sHandle, _ = self.novelView.getSelectedHandle()
+1 -1
View File
@@ -1,7 +1,7 @@
%%~name: Chapter One %%~name: Chapter One
%%~path: e5e47ebf63b1c/a520879ca0b45 %%~path: e5e47ebf63b1c/a520879ca0b45
%%~kind: NOVEL/DOCUMENT %%~kind: NOVEL/DOCUMENT
### Chapter One ## Chapter One
@pov: Jane @pov: Jane
+1 -1
View File
@@ -3,4 +3,4 @@
%%~kind: NOVEL/DOCUMENT %%~kind: NOVEL/DOCUMENT
# Part One # Part One
>> In the beginning … << >> In the beginning … <<
+13 -11
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-11 13:36:01"> <novelWriterXML appVersion="1.7-beta1" hexVersion="0x010700b1" fileVersion="1.4" timeStamp="2022-06-15 13:07:17">
<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>1345</saveCount> <saveCount>1371</saveCount>
<autoCount>229</autoCount> <autoCount>236</autoCount>
<editTime>68286</editTime> <editTime>69222</editTime>
</project> </project>
<settings> <settings>
<doBackup>False</doBackup> <doBackup>False</doBackup>
@@ -17,6 +17,8 @@
<autoOutline>True</autoOutline> <autoOutline>True</autoOutline>
<lastEdited>636b6aa9b697b</lastEdited> <lastEdited>636b6aa9b697b</lastEdited>
<lastViewed>636b6aa9b697b</lastViewed> <lastViewed>636b6aa9b697b</lastViewed>
<lastNovel>7031beac91f75</lastNovel>
<lastOutline>None</lastOutline>
<lastWordCount>1363</lastWordCount> <lastWordCount>1363</lastWordCount>
<novelWordCount>954</novelWordCount> <novelWordCount>954</novelWordCount>
<notesWordCount>409</notesWordCount> <notesWordCount>409</notesWordCount>
@@ -33,9 +35,9 @@
<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="4" red="100" green="100" blue="100">New</entry>
<entry key="sf24ce6" count="2" red="200" green="50" blue="0">Notes</entry> <entry key="sf24ce6" count="2" 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="3" red="182" green="60" blue="0">Started</entry>
<entry key="s90e6c9" count="7" red="193" green="129" blue="0">1st Draft</entry> <entry key="s90e6c9" count="7" red="193" green="129" blue="0">1st Draft</entry>
<entry key="sd51c5b" count="0" red="193" green="129" blue="0">2nd Draft</entry> <entry key="sd51c5b" count="0" 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>
@@ -70,11 +72,11 @@
<name status="sf24ce6" import="ia857f0" exported="True">Chapter One</name> <name status="sf24ce6" import="ia857f0" exported="True">Chapter One</name>
</item> </item>
<item handle="636b6aa9b697b" parent="6a2d6d5f4f401" root="7031beac91f75" order="0" type="FILE" class="NOVEL" layout="DOCUMENT"> <item handle="636b6aa9b697b" parent="6a2d6d5f4f401" root="7031beac91f75" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="False" charCount="2687" wordCount="479" paraCount="14" cursorPos="61"/> <meta expanded="False" charCount="2687" wordCount="479" paraCount="14" cursorPos="2183"/>
<name status="s90e6c9" import="ia857f0" exported="True">Making a Scene</name> <name status="s90e6c9" import="ia857f0" exported="True">Making a Scene</name>
</item> </item>
<item handle="bc0cbd2a407f3" parent="6a2d6d5f4f401" root="7031beac91f75" order="1" type="FILE" class="NOVEL" layout="DOCUMENT"> <item handle="bc0cbd2a407f3" parent="6a2d6d5f4f401" root="7031beac91f75" order="1" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="False" charCount="548" wordCount="108" paraCount="3" cursorPos="649"/> <meta expanded="False" charCount="548" wordCount="108" paraCount="3" cursorPos="465"/>
<name status="s90e6c9" import="ia857f0" exported="True">Another Scene</name> <name status="s90e6c9" import="ia857f0" exported="True">Another Scene</name>
</item> </item>
<item handle="ba8a28a246524" parent="7031beac91f75" root="7031beac91f75" order="4" type="FILE" class="NOVEL" layout="DOCUMENT"> <item handle="ba8a28a246524" parent="7031beac91f75" root="7031beac91f75" order="4" type="FILE" class="NOVEL" layout="DOCUMENT">
@@ -86,11 +88,11 @@
<name status="sf24ce6" import="ia857f0" exported="False">A Note on Structure</name> <name status="sf24ce6" import="ia857f0" exported="False">A Note on Structure</name>
</item> </item>
<item handle="88706ddc78b1b" parent="7031beac91f75" root="7031beac91f75" order="6" type="FILE" class="NOVEL" layout="DOCUMENT"> <item handle="88706ddc78b1b" parent="7031beac91f75" root="7031beac91f75" order="6" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="True" charCount="139" wordCount="28" paraCount="1" cursorPos="343"/> <meta expanded="True" charCount="139" wordCount="28" paraCount="1" cursorPos="188"/>
<name status="s90e6c9" import="ia857f0" exported="True">Chapter Two</name> <name status="s90e6c9" import="ia857f0" exported="True">Chapter Two</name>
</item> </item>
<item handle="ae7339df26ded" parent="88706ddc78b1b" root="7031beac91f75" order="0" type="FILE" class="NOVEL" layout="DOCUMENT"> <item handle="ae7339df26ded" parent="88706ddc78b1b" root="7031beac91f75" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="False" charCount="189" wordCount="37" paraCount="1" cursorPos="224"/> <meta expanded="False" charCount="189" wordCount="37" paraCount="1" cursorPos="0"/>
<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="e5e47ebf63b1c" parent="None" root="e5e47ebf63b1c" order="1" type="ROOT" class="NOVEL"> <item handle="e5e47ebf63b1c" parent="None" root="e5e47ebf63b1c" order="1" type="ROOT" class="NOVEL">
@@ -99,7 +101,7 @@
</item> </item>
<item handle="bacb7059e3083" parent="e5e47ebf63b1c" root="e5e47ebf63b1c" order="0" type="FILE" class="NOVEL" layout="DOCUMENT"> <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"/> <meta expanded="False" charCount="27" wordCount="5" paraCount="1" cursorPos="100"/>
<name status="sf12341" import="ia857f0" exported="True">Title Page</name> <name status="sc24b8f" import="ia857f0" exported="True">Title Page</name>
</item> </item>
<item handle="a520879ca0b45" parent="e5e47ebf63b1c" root="e5e47ebf63b1c" order="1" type="FILE" class="NOVEL" layout="DOCUMENT"> <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"/> <meta expanded="False" charCount="299" wordCount="55" paraCount="2" cursorPos="104"/>
+2
View File
@@ -16,6 +16,8 @@
<autoOutline>True</autoOutline> <autoOutline>True</autoOutline>
<lastEdited>7a992350f3eb6</lastEdited> <lastEdited>7a992350f3eb6</lastEdited>
<lastViewed>None</lastViewed> <lastViewed>None</lastViewed>
<lastNovel>None</lastNovel>
<lastOutline>None</lastOutline>
<lastWordCount>3847</lastWordCount> <lastWordCount>3847</lastWordCount>
<novelWordCount>3109</novelWordCount> <novelWordCount>3109</novelWordCount>
<notesWordCount>738</notesWordCount> <notesWordCount>738</notesWordCount>
+2
View File
@@ -17,6 +17,8 @@
<autoOutline>True</autoOutline> <autoOutline>True</autoOutline>
<lastEdited>None</lastEdited> <lastEdited>None</lastEdited>
<lastViewed>None</lastViewed> <lastViewed>None</lastViewed>
<lastNovel>None</lastNovel>
<lastOutline>None</lastOutline>
<lastWordCount>10</lastWordCount> <lastWordCount>10</lastWordCount>
<novelWordCount>10</novelWordCount> <novelWordCount>10</novelWordCount>
<notesWordCount>0</notesWordCount> <notesWordCount>0</notesWordCount>
@@ -1,5 +1,5 @@
<?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-05-21 17:49:23"> <novelWriterXML appVersion="1.7-beta1" hexVersion="0x010700b1" fileVersion="1.4" timeStamp="2022-06-12 17:45:51">
<project> <project>
<name>Test Custom</name> <name>Test Custom</name>
<title>Test Novel</title> <title>Test Novel</title>
@@ -17,6 +17,8 @@
<autoOutline>True</autoOutline> <autoOutline>True</autoOutline>
<lastEdited>None</lastEdited> <lastEdited>None</lastEdited>
<lastViewed>None</lastViewed> <lastViewed>None</lastViewed>
<lastNovel>None</lastNovel>
<lastOutline>None</lastOutline>
<lastWordCount>0</lastWordCount> <lastWordCount>0</lastWordCount>
<novelWordCount>0</novelWordCount> <novelWordCount>0</novelWordCount>
<notesWordCount>0</notesWordCount> <notesWordCount>0</notesWordCount>
@@ -1,5 +1,5 @@
<?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-05-21 17:48:40"> <novelWriterXML appVersion="1.7-beta1" hexVersion="0x010700b1" fileVersion="1.4" timeStamp="2022-06-12 17:45:51">
<project> <project>
<name>Test Custom</name> <name>Test Custom</name>
<title>Test Novel</title> <title>Test Novel</title>
@@ -17,6 +17,8 @@
<autoOutline>True</autoOutline> <autoOutline>True</autoOutline>
<lastEdited>None</lastEdited> <lastEdited>None</lastEdited>
<lastViewed>None</lastViewed> <lastViewed>None</lastViewed>
<lastNovel>None</lastNovel>
<lastOutline>None</lastOutline>
<lastWordCount>0</lastWordCount> <lastWordCount>0</lastWordCount>
<novelWordCount>0</novelWordCount> <novelWordCount>0</novelWordCount>
<notesWordCount>0</notesWordCount> <notesWordCount>0</notesWordCount>
@@ -1,5 +1,5 @@
<?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-06 21:17:27"> <novelWriterXML appVersion="1.7-beta1" hexVersion="0x010700b1" fileVersion="1.4" timeStamp="2022-06-12 17:45:51">
<project> <project>
<name>New Project</name> <name>New Project</name>
<title>New Novel</title> <title>New Novel</title>
@@ -16,6 +16,8 @@
<autoOutline>True</autoOutline> <autoOutline>True</autoOutline>
<lastEdited>None</lastEdited> <lastEdited>None</lastEdited>
<lastViewed>None</lastViewed> <lastViewed>None</lastViewed>
<lastNovel>None</lastNovel>
<lastOutline>None</lastOutline>
<lastWordCount>2</lastWordCount> <lastWordCount>2</lastWordCount>
<novelWordCount>1</novelWordCount> <novelWordCount>1</novelWordCount>
<notesWordCount>1</notesWordCount> <notesWordCount>1</notesWordCount>
@@ -1,5 +1,5 @@
<?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-05-21 17:50:26"> <novelWriterXML appVersion="1.7-beta1" hexVersion="0x010700b1" fileVersion="1.4" timeStamp="2022-06-12 17:45:51">
<project> <project>
<name>New Project</name> <name>New Project</name>
<title></title> <title></title>
@@ -15,6 +15,8 @@
<autoOutline>True</autoOutline> <autoOutline>True</autoOutline>
<lastEdited>None</lastEdited> <lastEdited>None</lastEdited>
<lastViewed>None</lastViewed> <lastViewed>None</lastViewed>
<lastNovel>None</lastNovel>
<lastOutline>None</lastOutline>
<lastWordCount>0</lastWordCount> <lastWordCount>0</lastWordCount>
<novelWordCount>0</novelWordCount> <novelWordCount>0</novelWordCount>
<notesWordCount>0</notesWordCount> <notesWordCount>0</notesWordCount>
@@ -1,5 +1,5 @@
<?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-05-21 22:07:28"> <novelWriterXML appVersion="1.7-beta1" hexVersion="0x010700b1" fileVersion="1.4" timeStamp="2022-06-12 17:45:51">
<project> <project>
<name>New Project</name> <name>New Project</name>
<title>New Novel</title> <title>New Novel</title>
@@ -16,6 +16,8 @@
<autoOutline>True</autoOutline> <autoOutline>True</autoOutline>
<lastEdited>None</lastEdited> <lastEdited>None</lastEdited>
<lastViewed>None</lastViewed> <lastViewed>None</lastViewed>
<lastNovel>None</lastNovel>
<lastOutline>None</lastOutline>
<lastWordCount>0</lastWordCount> <lastWordCount>0</lastWordCount>
<novelWordCount>0</novelWordCount> <novelWordCount>0</novelWordCount>
<notesWordCount>0</notesWordCount> <notesWordCount>0</notesWordCount>
@@ -1,5 +1,5 @@
<?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-05-21 17:27:16"> <novelWriterXML appVersion="1.7-beta1" hexVersion="0x010700b1" fileVersion="1.4" timeStamp="2022-06-12 17:52:44">
<project> <project>
<name>New Project</name> <name>New Project</name>
<title>New Novel</title> <title>New Novel</title>
@@ -16,6 +16,8 @@
<autoOutline>True</autoOutline> <autoOutline>True</autoOutline>
<lastEdited>000000000000f</lastEdited> <lastEdited>000000000000f</lastEdited>
<lastViewed>None</lastViewed> <lastViewed>None</lastViewed>
<lastNovel>0000000000008</lastNovel>
<lastOutline>None</lastOutline>
<lastWordCount>129</lastWordCount> <lastWordCount>129</lastWordCount>
<novelWordCount>102</novelWordCount> <novelWordCount>102</novelWordCount>
<notesWordCount>27</notesWordCount> <notesWordCount>27</notesWordCount>
@@ -1,5 +1,5 @@
<?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-05-21 17:25:41"> <novelWriterXML appVersion="1.7-beta1" hexVersion="0x010700b1" fileVersion="1.4" timeStamp="2022-06-12 17:45:57">
<project> <project>
<name>New Project</name> <name>New Project</name>
<title>New Novel</title> <title>New Novel</title>
@@ -16,6 +16,8 @@
<autoOutline>True</autoOutline> <autoOutline>True</autoOutline>
<lastEdited>None</lastEdited> <lastEdited>None</lastEdited>
<lastViewed>None</lastViewed> <lastViewed>None</lastViewed>
<lastNovel>None</lastNovel>
<lastOutline>None</lastOutline>
<lastWordCount>9</lastWordCount> <lastWordCount>9</lastWordCount>
<novelWordCount>9</novelWordCount> <novelWordCount>9</novelWordCount>
<notesWordCount>0</notesWordCount> <notesWordCount>0</notesWordCount>
@@ -1,5 +1,5 @@
<?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-05-21 17:31:17"> <novelWriterXML appVersion="1.7-beta1" hexVersion="0x010700b1" fileVersion="1.4" timeStamp="2022-06-12 17:45:53">
<project> <project>
<name>Project Name</name> <name>Project Name</name>
<title>Project Title</title> <title>Project Title</title>
@@ -17,6 +17,8 @@
<autoOutline>True</autoOutline> <autoOutline>True</autoOutline>
<lastEdited>None</lastEdited> <lastEdited>None</lastEdited>
<lastViewed>None</lastViewed> <lastViewed>None</lastViewed>
<lastNovel>None</lastNovel>
<lastOutline>None</lastOutline>
<lastWordCount>9</lastWordCount> <lastWordCount>9</lastWordCount>
<novelWordCount>9</novelWordCount> <novelWordCount>9</novelWordCount>
<notesWordCount>0</notesWordCount> <notesWordCount>0</notesWordCount>
+1 -1
View File
@@ -470,7 +470,7 @@ def testCoreProject_Open(monkeypatch, nwMinimal, mockGUI):
writeFile(os.path.join(nwMinimal, "data_0", "123456789abc_main.bak"), "stuff") writeFile(os.path.join(nwMinimal, "data_0", "123456789abc_main.bak"), "stuff")
mockGUI.clear() mockGUI.clear()
assert theProject.openProject(nwMinimal) is True assert theProject.openProject(nwMinimal) is True
assert "version 1.0" in mockGUI.lastAlert assert "There was an error updating the project." in mockGUI.lastAlert
assert theProject.closeProject() assert theProject.closeProject()
+11 -11
View File
@@ -28,8 +28,8 @@ from tools import cmpFiles, buildTestProject, XML_IGNORE, writeFile
from PyQt5.QtCore import Qt from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import QMessageBox, QInputDialog from PyQt5.QtWidgets import QMessageBox, QInputDialog
from novelwriter.gui import GuiDocEditor, GuiNovelTree, GuiOutlineView from novelwriter.gui import GuiDocEditor, GuiNovelView, GuiOutlineView
from novelwriter.enum import nwItemType, nwWidget from novelwriter.enum import nwItemType, nwView, nwWidget
from novelwriter.tools import GuiProjectWizard from novelwriter.tools import GuiProjectWizard
from novelwriter.gui.projtree import GuiProjectTree from novelwriter.gui.projtree import GuiProjectTree
from novelwriter.dialogs import GuiEditLabel from novelwriter.dialogs import GuiEditLabel
@@ -120,6 +120,7 @@ def testGuiMain_ProjectTreeItems(qtbot, monkeypatch, nwGUI, fncProj, mockRnd):
assert nwGUI.openSelectedItem() is False assert nwGUI.openSelectedItem() is False
# Project Tree has focus # Project Tree has focus
nwGUI._changeView(nwView.PROJECT)
nwGUI.switchFocus(nwWidget.TREE) nwGUI.switchFocus(nwWidget.TREE)
nwGUI.projStack.setCurrentIndex(0) nwGUI.projStack.setCurrentIndex(0)
with monkeypatch.context() as mp: with monkeypatch.context() as mp:
@@ -131,23 +132,22 @@ def testGuiMain_ProjectTreeItems(qtbot, monkeypatch, nwGUI, fncProj, mockRnd):
assert nwGUI.closeDocument() is True assert nwGUI.closeDocument() is True
# Novel Tree has focus # Novel Tree has focus
nwGUI.projStack.setCurrentIndex(1) nwGUI._changeView(nwView.NOVEL)
nwGUI.novelView.refreshTree(True) nwGUI.novelView.novelTree.refreshTree(rootHandle=None, overRide=True)
with monkeypatch.context() as mp: with monkeypatch.context() as mp:
mp.setattr(GuiNovelTree, "hasFocus", lambda *a: True) mp.setattr(GuiNovelView, "treeHasFocus", lambda *a: True)
assert nwGUI.docEditor.docHandle() is None assert nwGUI.docEditor.docHandle() is None
actItem = nwGUI.novelView.topLevelItem(0) selItem = nwGUI.novelView.novelTree.topLevelItem(2)
chpItem = actItem.child(0) nwGUI.novelView.novelTree.setCurrentItem(selItem)
selItem = chpItem.child(0)
nwGUI.novelView.setCurrentItem(selItem)
nwGUI._keyPressReturn() nwGUI._keyPressReturn()
assert nwGUI.docEditor.docHandle() == sHandle assert nwGUI.docEditor.docHandle() == sHandle
assert nwGUI.closeDocument() is True assert nwGUI.closeDocument() is True
# Project Outline has focus # Project Outline has focus
nwGUI._changeView(nwView.OUTLINE)
nwGUI.switchFocus(nwWidget.OUTLINE) nwGUI.switchFocus(nwWidget.OUTLINE)
with monkeypatch.context() as mp: with monkeypatch.context() as mp:
mp.setattr(GuiOutlineView, "treeFocus", lambda *a: True) mp.setattr(GuiOutlineView, "treeHasFocus", lambda *a: True)
assert nwGUI.docEditor.docHandle() is None assert nwGUI.docEditor.docHandle() is None
actItem = nwGUI.outlineView.outlineTree.topLevelItem(0) actItem = nwGUI.outlineView.outlineTree.topLevelItem(0)
chpItem = actItem.child(0) chpItem = actItem.child(0)
@@ -157,7 +157,7 @@ def testGuiMain_ProjectTreeItems(qtbot, monkeypatch, nwGUI, fncProj, mockRnd):
assert nwGUI.docEditor.docHandle() == sHandle assert nwGUI.docEditor.docHandle() == sHandle
assert nwGUI.closeDocument() is True assert nwGUI.closeDocument() is True
# qtbot.stopForInteraction() # qtbot.stop()
# END Test testGuiMain_ProjectTreeItems # END Test testGuiMain_ProjectTreeItems
+91 -77
View File
@@ -19,73 +19,93 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>. along with this program. If not, see <https://www.gnu.org/licenses/>.
""" """
import pytest
import os import os
import pytest
from tools import writeFile from tools import buildTestProject, writeFile
from PyQt5.QtCore import Qt from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import QMessageBox from PyQt5.QtWidgets import QMessageBox
from novelwriter.enum import nwWidget, nwItemType
from novelwriter.dialogs import GuiEditLabel
from novelwriter.gui.noveltree import NovelTreeColumn
@pytest.mark.gui @pytest.mark.gui
def testGuiNovelTree_TreeItems(qtbot, monkeypatch, nwGUI, nwMinimal): def testGuiNovelTree_TreeItems(qtbot, monkeypatch, nwGUI, fncProj, mockRnd):
"""Test navigating the novel tree. """Test navigating the novel tree.
""" """
# Block message box # Block message box
monkeypatch.setattr(QMessageBox, "question", lambda *a: QMessageBox.Yes) monkeypatch.setattr(QMessageBox, "question", lambda *a: QMessageBox.Yes)
monkeypatch.setattr(QMessageBox, "information", lambda *a: QMessageBox.Yes) monkeypatch.setattr(QMessageBox, "information", lambda *a: QMessageBox.Yes)
monkeypatch.setattr(GuiEditLabel, "getLabel", lambda *a, text: (text, True))
nwGUI.openProject(nwMinimal) buildTestProject(nwGUI, fncProj)
nwTree = nwGUI.novelView
## nwGUI.switchFocus(nwWidget.TREE)
# Show/Hide Scrollbars nwGUI.projView.projTree.clearSelection()
## nwGUI.projView.projTree._getTreeItem("000000000000a").setSelected(True)
nwGUI.projView.projTree.newTreeItem(nwItemType.FILE)
nwTree.mainConf.hideVScroll = True writeFile(
nwTree.mainConf.hideHScroll = True os.path.join(nwGUI.theProject.projContent, "0000000000010.nwd"),
nwTree.initTree() "# Jane Doe\n\n@tag: Jane\n\n"
assert not nwTree.verticalScrollBar().isVisible() )
assert not nwTree.horizontalScrollBar().isVisible() writeFile(
os.path.join(nwGUI.theProject.projContent, "000000000000f.nwd"),
"### Scene One\n\n@pov: Jane\n@focus: Jane\n\n"
)
nwTree.mainConf.hideVScroll = False novelView = nwGUI.novelView
nwTree.mainConf.hideHScroll = False novelTree = novelView.novelTree
nwTree.initTree() novelBar = novelView.novelBar
assert nwTree.verticalScrollBar().isEnabled()
assert nwTree.horizontalScrollBar().isEnabled()
## # Show/Hide Scrollbars
# Populate Tree # ====================
##
nwGUI.mainConf.hideVScroll = True
nwGUI.mainConf.hideHScroll = True
novelView.initSettings()
assert not novelTree.verticalScrollBar().isVisible()
assert not novelTree.horizontalScrollBar().isVisible()
nwGUI.mainConf.hideVScroll = False
nwGUI.mainConf.hideHScroll = False
novelView.initSettings()
assert novelTree.verticalScrollBar().isEnabled()
assert novelTree.horizontalScrollBar().isEnabled()
# Populate Tree
# =============
nwGUI.projStack.setCurrentIndex(nwGUI.idxNovelView) nwGUI.projStack.setCurrentIndex(nwGUI.idxNovelView)
nwGUI.rebuildIndex() nwGUI.rebuildIndex()
nwTree._populateTree() novelTree._populateTree(rootHandle=None)
assert nwTree.topLevelItemCount() == 1 assert novelTree.topLevelItemCount() == 3
# Rebuild should preserve selection # Rebuild should preserve selection
topItem = nwTree.topLevelItem(0) topItem = novelTree.topLevelItem(0)
assert not topItem.isSelected() assert not topItem.isSelected()
topItem.setSelected(True) topItem.setSelected(True)
assert nwTree.selectedItems()[0] == topItem assert novelTree.selectedItems()[0] == topItem
assert nwTree.getSelectedHandle() == ("a35baf2e93843", 0) assert novelView.getSelectedHandle() == ("000000000000c", 0)
nwTree.refreshTree() # Refresh using the slot for the butoom
assert nwTree.topLevelItem(0).isSelected() novelBar._refreshNovelTree()
assert novelTree.topLevelItem(0).isSelected()
## # Open Items
# Open Items # ==========
##
# Clear selection # Clear selection
nwTree.clearSelection() novelTree.clearSelection()
scItem = nwTree.topLevelItem(0).child(0).child(0) scItem = novelTree.topLevelItem(2)
scItem.setSelected(True) scItem.setSelected(True)
assert scItem.isSelected() assert scItem.isSelected()
# Clear selection with mouse # Clear selection with mouse
vPort = nwTree.viewport() vPort = novelTree.viewport()
qtbot.mouseClick(vPort, Qt.LeftButton, pos=vPort.rect().center(), delay=10) qtbot.mouseClick(vPort, Qt.LeftButton, pos=vPort.rect().center(), delay=10)
assert not scItem.isSelected() assert not scItem.isSelected()
@@ -93,8 +113,8 @@ def testGuiNovelTree_TreeItems(qtbot, monkeypatch, nwGUI, nwMinimal):
scItem.setSelected(True) scItem.setSelected(True)
assert scItem.isSelected() assert scItem.isSelected()
assert nwGUI.docEditor.docHandle() is None assert nwGUI.docEditor.docHandle() is None
nwTree._treeDoubleClick(scItem, 0) novelTree._treeDoubleClick(scItem, 0)
assert nwGUI.docEditor.docHandle() == "8c659a11cd429" assert nwGUI.docEditor.docHandle() == "000000000000f"
# Open item with middle mouse button # Open item with middle mouse button
scItem.setSelected(True) scItem.setSelected(True)
@@ -103,58 +123,52 @@ def testGuiNovelTree_TreeItems(qtbot, monkeypatch, nwGUI, nwMinimal):
qtbot.mouseClick(vPort, Qt.MiddleButton, pos=vPort.rect().center(), delay=10) qtbot.mouseClick(vPort, Qt.MiddleButton, pos=vPort.rect().center(), delay=10)
assert nwGUI.docViewer.docHandle() is None assert nwGUI.docViewer.docHandle() is None
scRect = nwTree.visualItemRect(scItem) scRect = novelTree.visualItemRect(scItem)
oldData = scItem.data(nwTree.C_TITLE, Qt.UserRole) oldData = scItem.data(novelTree.C_TITLE, Qt.UserRole)
scItem.setData(nwTree.C_TITLE, Qt.UserRole, (None, "", "")) scItem.setData(novelTree.C_TITLE, Qt.UserRole, (None, "", ""))
qtbot.mouseClick(vPort, Qt.MiddleButton, pos=scRect.center(), delay=10) qtbot.mouseClick(vPort, Qt.MiddleButton, pos=scRect.center(), delay=10)
assert nwGUI.docViewer.docHandle() is None assert nwGUI.docViewer.docHandle() is None
scItem.setData(nwTree.C_TITLE, Qt.UserRole, oldData) scItem.setData(novelTree.C_TITLE, Qt.UserRole, oldData)
qtbot.mouseClick(vPort, Qt.MiddleButton, pos=scRect.center(), delay=10) qtbot.mouseClick(vPort, Qt.MiddleButton, pos=scRect.center(), delay=10)
assert nwGUI.docViewer.docHandle() == "8c659a11cd429" assert nwGUI.docViewer.docHandle() == "000000000000f"
## # Last Column
# Populate Tree # ===========
##
# Add weird titles to first file to check hnadling of non-standard novelBar.setLastColType(NovelTreeColumn.HIDDEN)
# order of title levels. assert novelTree.isColumnHidden(novelTree.C_LAST) is True
writeFile(os.path.join(nwMinimal, "content", "a35baf2e93843.nwd"), ( assert novelTree.lastColType == NovelTreeColumn.HIDDEN
"#### Section wo/Scene\n\n" assert novelTree._getLastColumnText("000000000000f", "T000001") == ("", "")
"### 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) novelBar.setLastColType(NovelTreeColumn.POV)
assert tTitle.child(0).text(nwTree.C_TITLE) == "Section w/Title, wo/Scene" assert novelTree.isColumnHidden(novelTree.C_LAST) is False
assert tTitle.child(1).text(nwTree.C_TITLE) == "Scene w/Title, wo/Chapter" assert novelTree.lastColType == NovelTreeColumn.POV
assert tTitle.child(2).text(nwTree.C_TITLE) == "Chapter" assert novelTree._getLastColumnText("000000000000f", "T000001") == (
"Jane", "Point of View: Jane"
)
tChap = tTitle.child(2) novelBar.setLastColType(NovelTreeColumn.FOCUS)
assert tChap.child(0).text(nwTree.C_TITLE) == "Section w/Chapter, wo/Scene" assert novelTree.isColumnHidden(novelTree.C_LAST) is False
assert tChap.child(1).text(nwTree.C_TITLE) == "Scene" assert novelTree.lastColType == NovelTreeColumn.FOCUS
assert novelTree._getLastColumnText("000000000000f", "T000001") == (
"Jane", "Focus: Jane"
)
tScene = tChap.child(1) novelBar.setLastColType(NovelTreeColumn.PLOT)
assert tScene.child(0).text(nwTree.C_TITLE) == "Section" assert novelTree.isColumnHidden(novelTree.C_LAST) is False
assert novelTree.lastColType == NovelTreeColumn.PLOT
assert novelTree._getLastColumnText("000000000000f", "T000001") == (
"", "Plot: "
)
## novelTree._lastCol = None
# Close assert novelTree._getLastColumnText("0000000000000", "T000000") == ("", "")
##
# qtbot.stopForInteraction() # Close
# =====
# qtbot.stop()
nwGUI.closeProject() nwGUI.closeProject()
# END Test testGuiNovelTree_TreeItems # END Test testGuiNovelTree_TreeItems
+1 -1
View File
@@ -71,7 +71,7 @@ def testGuiOutline_Main(qtbot, monkeypatch, nwGUI, fncDir):
# Check focus # Check focus
with monkeypatch.context() as mp: with monkeypatch.context() as mp:
mp.setattr(QWidget, "hasFocus", lambda *a: True) mp.setattr(QWidget, "hasFocus", lambda *a: True)
assert outlineView.treeFocus() is True assert outlineView.treeHasFocus() is True
outlineView.setTreeFocus() # Can't check. just ensures that it doesn't error outlineView.setTreeFocus() # Can't check. just ensures that it doesn't error
+1 -1
View File
@@ -127,7 +127,7 @@ def testGuiTheme_Main(qtbot, monkeypatch, nwMinimal, tmpDir):
assert anImg.isNull() assert anImg.isNull()
# Add a non-existent file and request it # Add a non-existent file and request it
iconCache.DECO_MAP["nonsense"] = "nofile.jpg" iconCache.IMAGE_MAP["nonsense"] = "nofile.jpg"
anImg = iconCache.loadDecoration("nonsense", 20, 20) anImg = iconCache.loadDecoration("nonsense", 20, 20)
assert isinstance(anImg, QPixmap) assert isinstance(anImg, QPixmap)
assert anImg.isNull() assert anImg.isNull()