Update the novel tree GUI (#1084)
@@ -83,3 +83,8 @@ view_build = typ_export.svg
|
||||
view_editor = mixed_edit.svg
|
||||
view_novel = typ_book-grey.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_novel = typ_book-grey.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 |
@@ -57,6 +57,15 @@ 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:
|
||||
|
||||
PROJ_FILE = "nwProject.nwx"
|
||||
|
||||
@@ -34,7 +34,7 @@ from time import time
|
||||
|
||||
from novelwriter.enum import nwItemType, nwItemLayout
|
||||
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.common import (
|
||||
checkInt, isHandle, isItemClass, isTitleTag, jsonEncode
|
||||
@@ -42,10 +42,6 @@ from novelwriter.common import (
|
||||
|
||||
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:
|
||||
"""This class holds the entire index for a given project. The index
|
||||
@@ -477,7 +473,7 @@ class NWIndex:
|
||||
"""
|
||||
hCount = [0, 0, 0, 0, 0]
|
||||
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
|
||||
return hCount
|
||||
|
||||
@@ -510,7 +506,7 @@ class NWIndex:
|
||||
pKey = None
|
||||
for tHandle, sTitle, hItem in self._itemIndex.iterNovelStructure(skipExcl=skipExcl):
|
||||
tKey = f"{tHandle}:{sTitle}"
|
||||
iLevel = H_LEVEL.get(hItem.level, 0)
|
||||
iLevel = nwHeaders.H_LEVEL.get(hItem.level, 0)
|
||||
if iLevel > maxDepth:
|
||||
if pKey in tData:
|
||||
tData[pKey]["words"] += hItem.wordCount
|
||||
@@ -660,7 +656,7 @@ class TagsIndex:
|
||||
"""
|
||||
if tagKey in self._tags:
|
||||
return self._tags.get(tagKey).get("heading")
|
||||
return TT_NONE
|
||||
return nwHeaders.TT_NONE
|
||||
|
||||
def tagClass(self, tagKey):
|
||||
"""Get the class of a given tag.
|
||||
@@ -906,7 +902,7 @@ class IndexItem:
|
||||
self._index = 0
|
||||
|
||||
# Add a placeholder heading
|
||||
self._headings[TT_NONE] = IndexHeading(TT_NONE)
|
||||
self._headings[nwHeaders.TT_NONE] = IndexHeading(nwHeaders.TT_NONE)
|
||||
|
||||
return
|
||||
|
||||
@@ -940,8 +936,8 @@ class IndexItem:
|
||||
"""Add a heading to the item. Also remove the placeholder entry
|
||||
if it exists.
|
||||
"""
|
||||
if TT_NONE in self._headings:
|
||||
self._headings.pop(TT_NONE)
|
||||
if nwHeaders.TT_NONE in self._headings:
|
||||
self._headings.pop(nwHeaders.TT_NONE)
|
||||
self._headings[tHeading.key] = tHeading
|
||||
return
|
||||
|
||||
@@ -1110,7 +1106,7 @@ class IndexHeading:
|
||||
def setLevel(self, level):
|
||||
"""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
|
||||
return
|
||||
|
||||
|
||||
@@ -28,6 +28,8 @@ import os
|
||||
import json
|
||||
import logging
|
||||
|
||||
from enum import Enum
|
||||
|
||||
from novelwriter.error import logException
|
||||
from novelwriter.common import checkBool, checkFloat, checkInt, checkString
|
||||
from novelwriter.constants import nwFiles
|
||||
@@ -56,7 +58,8 @@ VALID_MAP = {
|
||||
"winWidth", "winHeight", "widthCol0", "widthCol1", "widthCol2",
|
||||
"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:
|
||||
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
|
||||
|
||||
@@ -186,4 +192,16 @@ class OptionState():
|
||||
return checkBool(self._theState[group].get(name, default), 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
|
||||
|
||||
@@ -102,6 +102,8 @@ class NWProject():
|
||||
self.importItems = None # Note file importance values
|
||||
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.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.lastNovelWC = 0 # The novel 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)
|
||||
elif xItem.tag == "lastViewed":
|
||||
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":
|
||||
self.lastWCount = checkInt(xItem.text, 0, False)
|
||||
elif xItem.tag == "novelWordCount":
|
||||
@@ -647,7 +653,7 @@ class NWProject():
|
||||
self._legacyDataFolder(projItem)
|
||||
except Exception:
|
||||
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."
|
||||
), nwAlert.ERROR)
|
||||
|
||||
@@ -732,6 +738,8 @@ class NWProject():
|
||||
self._packProjectValue(xSettings, "autoOutline", self.autoOutline)
|
||||
self._packProjectValue(xSettings, "lastEdited", self.lastEdited)
|
||||
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, "novelWordCount", self.currNovelWC)
|
||||
self._packProjectValue(xSettings, "notesWordCount", self.currNotesWC)
|
||||
@@ -1123,6 +1131,14 @@ class NWProject():
|
||||
self.setProjectChanged(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):
|
||||
"""Update the list of novel file status flags.
|
||||
"""
|
||||
|
||||
@@ -23,7 +23,7 @@ from novelwriter.gui.doceditor import GuiDocEditor
|
||||
from novelwriter.gui.docviewer import GuiDocViewer, GuiDocViewDetails
|
||||
from novelwriter.gui.itemdetails import GuiItemDetails
|
||||
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.projtree import GuiProjectView
|
||||
from novelwriter.gui.statusbar import GuiMainStatus
|
||||
@@ -37,7 +37,7 @@ __all__ = [
|
||||
"GuiItemDetails",
|
||||
"GuiMainMenu",
|
||||
"GuiMainStatus",
|
||||
"GuiNovelTree",
|
||||
"GuiNovelView",
|
||||
"GuiOutlineView",
|
||||
"GuiProjectView",
|
||||
"GuiTheme",
|
||||
|
||||
@@ -511,11 +511,13 @@ class GuiDocEditor(QTextEdit):
|
||||
self.theProject.index.scanText(tHandle, docText)
|
||||
newHeader = self.theProject.index.getHandleHeaderLevel(tHandle)
|
||||
|
||||
# ToDo: This should be a signal
|
||||
if self._updateHeaders(checkLevel=True):
|
||||
self.mainGui.requestNovelTreeRefresh()
|
||||
else:
|
||||
self.mainGui.novelView.updateWordCounts(tHandle)
|
||||
|
||||
# ToDo: This should be a signal
|
||||
if oldHeader != newHeader:
|
||||
self.mainGui.projView.setTreeItemValues(tHandle)
|
||||
self.mainGui.itemDetails.updateViewBox(tHandle)
|
||||
|
||||
@@ -4,7 +4,9 @@ novelWriter – GUI Novel Tree
|
||||
GUI classe for the main window novel tree
|
||||
|
||||
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
|
||||
Copyright 2018–2020, Veronica Berglyd Olsen
|
||||
@@ -26,84 +28,376 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
import logging
|
||||
import novelwriter
|
||||
|
||||
from enum import Enum
|
||||
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 (
|
||||
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.constants import nwKeyWords
|
||||
from novelwriter.constants import nwHeaders, nwKeyWords, nwLabels, trConst
|
||||
|
||||
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):
|
||||
|
||||
C_TITLE = 0
|
||||
C_WORDS = 1
|
||||
C_POV = 2
|
||||
C_LAST = 2
|
||||
|
||||
def __init__(self, mainGui):
|
||||
QTreeWidget.__init__(self, mainGui)
|
||||
def __init__(self, novelView):
|
||||
QTreeWidget.__init__(self, novelView)
|
||||
|
||||
logger.debug("Initialising GuiNovelTree ...")
|
||||
|
||||
self.mainConf = novelwriter.CONFIG
|
||||
self.mainGui = mainGui
|
||||
self.mainTheme = mainGui.mainTheme
|
||||
self.theProject = mainGui.theProject
|
||||
self.novelView = novelView
|
||||
self.mainGui = novelView.mainGui
|
||||
self.mainTheme = novelView.mainGui.mainTheme
|
||||
self.theProject = novelView.mainGui.theProject
|
||||
|
||||
# Internal Variables
|
||||
self._treeMap = {}
|
||||
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
|
||||
# =========
|
||||
|
||||
iPx = self.mainTheme.baseIconSize
|
||||
self.setFrameStyle(QFrame.NoFrame)
|
||||
cMg = self.mainConf.pxInt(6)
|
||||
|
||||
self.setIconSize(QSize(iPx, iPx))
|
||||
self.setIndentation(iPx)
|
||||
self.setFrameStyle(QFrame.NoFrame)
|
||||
self.setHeaderHidden(True)
|
||||
self.setIndentation(0)
|
||||
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.setSelectionMode(QAbstractItemView.SingleSelection)
|
||||
self.setExpandsOnDoubleClick(False)
|
||||
self.setDragEnabled(False)
|
||||
|
||||
treeHeadItem = self.headerItem()
|
||||
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"))
|
||||
|
||||
# Lock the column sizes
|
||||
treeHeader = self.header()
|
||||
treeHeader.setStretchLastSection(True)
|
||||
treeHeader.setMinimumSectionSize(iPx + 6)
|
||||
treeHeader.setStretchLastSection(False)
|
||||
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
|
||||
treeColWidth = self.mainConf.getNovelColWidths()
|
||||
if len(treeColWidth) <= 3:
|
||||
for colN, colW in enumerate(treeColWidth):
|
||||
self.setColumnWidth(colN, colW)
|
||||
# Pre-Generate Tree Formatting
|
||||
fH1 = self.font()
|
||||
fH1.setBold(True)
|
||||
fH1.setUnderline(True)
|
||||
|
||||
# The last column should just auto-scale
|
||||
self.resizeColumnToContents(self.C_POV)
|
||||
fH2 = self.font()
|
||||
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
|
||||
self.initTree()
|
||||
self.initSettings()
|
||||
|
||||
logger.debug("GuiNovelTree initialisation complete")
|
||||
|
||||
return
|
||||
|
||||
def initTree(self):
|
||||
def initSettings(self):
|
||||
"""Set or update tree widget settings.
|
||||
"""
|
||||
# Scroll bars
|
||||
@@ -119,11 +413,19 @@ class GuiNovelTree(QTreeWidget):
|
||||
|
||||
return
|
||||
|
||||
##
|
||||
# Properties
|
||||
##
|
||||
|
||||
@property
|
||||
def lastColType(self):
|
||||
return self._lastCol
|
||||
|
||||
##
|
||||
# Class Methods
|
||||
##
|
||||
|
||||
def clearTree(self):
|
||||
def clearContent(self):
|
||||
"""Clear the GUI content and the related maps.
|
||||
"""
|
||||
self.clear()
|
||||
@@ -131,12 +433,15 @@ class GuiNovelTree(QTreeWidget):
|
||||
self._lastBuild = 0
|
||||
return
|
||||
|
||||
def refreshTree(self, overRide=False):
|
||||
def refreshTree(self, rootHandle=None, overRide=False):
|
||||
"""Called whenever the Novel tab is activated.
|
||||
"""
|
||||
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)
|
||||
indexChanged = self.theProject.index.indexChangedSince(self._lastBuild)
|
||||
indexChanged = self.theProject.index.rootChangedSince(rootHandle, self._lastBuild)
|
||||
if not (treeChanged or indexChanged or overRide):
|
||||
logger.verbose("No changes have been made to the novel index")
|
||||
return
|
||||
@@ -146,7 +451,8 @@ class GuiNovelTree(QTreeWidget):
|
||||
if selItem:
|
||||
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:
|
||||
self._treeMap[titleKey].setSelected(True)
|
||||
@@ -162,15 +468,6 @@ class GuiNovelTree(QTreeWidget):
|
||||
self._treeMap[titleKey].setText(self.C_WORDS, f"{wCount:n}")
|
||||
return
|
||||
|
||||
def getColumnSizes(self):
|
||||
"""Return the column widths for the tree columns.
|
||||
"""
|
||||
retVals = [
|
||||
self.columnWidth(0),
|
||||
self.columnWidth(1),
|
||||
]
|
||||
return retVals
|
||||
|
||||
def getSelectedHandle(self):
|
||||
"""Get the currently selected handle. If multiple items are
|
||||
selected, return the first.
|
||||
@@ -184,6 +481,17 @@ class GuiNovelTree(QTreeWidget):
|
||||
|
||||
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
|
||||
##
|
||||
@@ -209,113 +517,94 @@ class GuiNovelTree(QTreeWidget):
|
||||
if tHandle is None:
|
||||
return
|
||||
|
||||
self.mainGui.viewDocument(tHandle)
|
||||
self.novelView.openDocumentRequest.emit(tHandle, nwDocMode.VIEW, -1, "")
|
||||
|
||||
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-
|
||||
clicked, and send it to the main gui class for opening in the
|
||||
document editor.
|
||||
"""
|
||||
tHandle, tLine = self.getSelectedHandle()
|
||||
self.mainGui.openDocument(tHandle, tLine=tLine-1, doScroll=True)
|
||||
return
|
||||
|
||||
def _itemSelected(self):
|
||||
"""Extract the handle and line number of the currently selected
|
||||
title, and send it to the tree meta panel.
|
||||
"""
|
||||
selItems = self.selectedItems()
|
||||
if selItems:
|
||||
tHandle = selItems[0].data(self.C_TITLE, Qt.UserRole)[0]
|
||||
self.mainGui.itemDetails.updateViewBox(tHandle)
|
||||
|
||||
self.novelView.openDocumentRequest.emit(tHandle, nwDocMode.EDIT, tLine, "")
|
||||
return
|
||||
|
||||
##
|
||||
# Internal Functions
|
||||
##
|
||||
|
||||
def _populateTree(self):
|
||||
def _populateTree(self, rootHandle):
|
||||
"""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
|
||||
currChapter = None
|
||||
currScene = None
|
||||
novStruct = self.theProject.index.novelStructure(rootHandle=rootHandle, skipExcl=True)
|
||||
for tKey, tHandle, sTitle, novIdx in novStruct:
|
||||
|
||||
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)
|
||||
self._treeMap[tKey] = tItem
|
||||
newItem = QTreeWidgetItem()
|
||||
theData = (tHandle, sTitle[1:].lstrip("0"), tKey)
|
||||
|
||||
tLevel = novIdx.level
|
||||
if tLevel == "H1":
|
||||
self.addTopLevelItem(tItem)
|
||||
currTitle = tItem
|
||||
currChapter = None
|
||||
currScene = None
|
||||
newItem.setData(self.C_TITLE, Qt.DecorationRole, self._pIndent[iLevel])
|
||||
newItem.setText(self.C_TITLE, novIdx.title)
|
||||
newItem.setData(self.C_TITLE, Qt.UserRole, theData)
|
||||
newItem.setFont(self.C_TITLE, self._hFonts[iLevel])
|
||||
newItem.setText(self.C_WORDS, f"{novIdx.wordCount:n}")
|
||||
newItem.setTextAlignment(self.C_WORDS, Qt.AlignRight)
|
||||
|
||||
elif tLevel == "H2":
|
||||
if currTitle is None:
|
||||
self.addTopLevelItem(tItem)
|
||||
else:
|
||||
currTitle.addChild(tItem)
|
||||
currChapter = tItem
|
||||
currScene = None
|
||||
lastText, toolTip = self._getLastColumnText(tHandle, sTitle)
|
||||
newItem.setText(self.C_LAST, lastText)
|
||||
if lastText:
|
||||
newItem.setToolTip(self.C_LAST, toolTip)
|
||||
|
||||
elif tLevel == "H3":
|
||||
if currChapter is None:
|
||||
if currTitle is None:
|
||||
self.addTopLevelItem(tItem)
|
||||
else:
|
||||
currTitle.addChild(tItem)
|
||||
else:
|
||||
currChapter.addChild(tItem)
|
||||
currScene = tItem
|
||||
|
||||
elif tLevel == "H4":
|
||||
if currScene is None:
|
||||
if currChapter is None:
|
||||
if currTitle is None:
|
||||
self.addTopLevelItem(tItem)
|
||||
else:
|
||||
currTitle.addChild(tItem)
|
||||
else:
|
||||
currChapter.addChild(tItem)
|
||||
else:
|
||||
currScene.addChild(tItem)
|
||||
|
||||
tItem.setExpanded(True)
|
||||
self._treeMap[tKey] = newItem
|
||||
self.addTopLevelItem(newItem)
|
||||
|
||||
logger.verbose("Novel Tree built in %.3f ms", (time() - tStart)*1000)
|
||||
self._lastBuild = time()
|
||||
|
||||
return
|
||||
|
||||
def _createTreeItem(self, tHandle, sTitle, titleKey, novIdx):
|
||||
"""Populate a tree item with all the column values.
|
||||
def _getLastColumnText(self, tHandle, sTitle):
|
||||
"""Generate the text for the last column based on user settings.
|
||||
"""
|
||||
newItem = QTreeWidgetItem()
|
||||
hIcon = "doc_%s" % novIdx.level.lower()
|
||||
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)
|
||||
if self._lastCol == NovelTreeColumn.HIDDEN:
|
||||
return "", ""
|
||||
|
||||
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
|
||||
|
||||
@@ -117,7 +117,7 @@ class GuiOutlineView(QWidget):
|
||||
self.outlineTree.refreshTree(overRide=overRide, novelChanged=novelChanged)
|
||||
return
|
||||
|
||||
def treeFocus(self):
|
||||
def treeHasFocus(self):
|
||||
return self.outlineTree.hasFocus()
|
||||
|
||||
def setTreeFocus(self):
|
||||
|
||||
@@ -31,8 +31,8 @@ import novelwriter
|
||||
from enum import Enum
|
||||
from time import time
|
||||
|
||||
from PyQt5.QtGui import QPalette
|
||||
from PyQt5.QtCore import Qt, QSize, pyqtSignal, pyqtSlot
|
||||
from PyQt5.QtGui import QIcon, QPalette
|
||||
from PyQt5.QtWidgets import (
|
||||
QAbstractItemView, QFrame, QHBoxLayout, QHeaderView, QLabel,
|
||||
QMenu, QShortcut, QSizePolicy, QToolButton, QTreeWidget, QTreeWidgetItem,
|
||||
@@ -61,7 +61,7 @@ class GuiProjectView(QWidget):
|
||||
|
||||
# Signals for user interaction with the project tree
|
||||
selectedItemChanged = pyqtSignal(str)
|
||||
openDocumentRequest = pyqtSignal(str, Enum)
|
||||
openDocumentRequest = pyqtSignal(str, Enum, int, str)
|
||||
|
||||
def __init__(self, mainGui):
|
||||
QWidget.__init__(self, mainGui)
|
||||
@@ -142,7 +142,7 @@ class GuiProjectView(QWidget):
|
||||
self.projTree.setFocus()
|
||||
return
|
||||
|
||||
def treeFocus(self):
|
||||
def treeHasFocus(self):
|
||||
"""Check if the project tree has focus.
|
||||
"""
|
||||
return self.projTree.hasFocus()
|
||||
@@ -177,7 +177,7 @@ class GuiProjectToolBar(QWidget):
|
||||
self.mainTheme = projView.mainGui.mainTheme
|
||||
|
||||
iPx = self.mainTheme.baseIconSize
|
||||
mPx = self.mainConf.pxInt(4)
|
||||
mPx = self.mainConf.pxInt(3)
|
||||
|
||||
self.setContentsMargins(0, 0, 0, 0)
|
||||
self.setAutoFillBackground(True)
|
||||
@@ -192,10 +192,10 @@ class GuiProjectToolBar(QWidget):
|
||||
"QToolButton:hover {{border: none; background: rgba({1},{2},{3},0.2);}}"
|
||||
).format(mPx, fadeCol.red(), fadeCol.green(), fadeCol.blue())
|
||||
|
||||
# Tree Label
|
||||
self.projLabel = QLabel("<b>%s</b>" % self.tr("Project Content"))
|
||||
self.projLabel.setContentsMargins(0, 0, 0, 0)
|
||||
self.projLabel.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
|
||||
# Widget Label
|
||||
self.viewLabel = QLabel("<b>%s</b>" % self.tr("Project Content"))
|
||||
self.viewLabel.setContentsMargins(0, 0, 0, 0)
|
||||
self.viewLabel.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
|
||||
|
||||
# Move Buttons
|
||||
self.tbMoveU = QToolButton(self)
|
||||
@@ -252,7 +252,7 @@ class GuiProjectToolBar(QWidget):
|
||||
self._addRootFolderEntry(nwItemClass.PLOT)
|
||||
self._addRootFolderEntry(nwItemClass.CHARACTER)
|
||||
self._addRootFolderEntry(nwItemClass.WORLD)
|
||||
self._addRootFolderEntry(nwItemClass.ARCHIVE)
|
||||
self._addRootFolderEntry(nwItemClass.TIMELINE)
|
||||
self._addRootFolderEntry(nwItemClass.OBJECT)
|
||||
self._addRootFolderEntry(nwItemClass.ENTITY)
|
||||
self._addRootFolderEntry(nwItemClass.CUSTOM)
|
||||
@@ -285,7 +285,7 @@ class GuiProjectToolBar(QWidget):
|
||||
|
||||
# Assemble
|
||||
self.outerBox = QHBoxLayout()
|
||||
self.outerBox.addWidget(self.projLabel)
|
||||
self.outerBox.addWidget(self.viewLabel)
|
||||
self.outerBox.addWidget(self.tbMoveU)
|
||||
self.outerBox.addWidget(self.tbMoveD)
|
||||
self.outerBox.addWidget(self.tbAdd)
|
||||
@@ -337,9 +337,8 @@ class GuiProjectTree(QTreeWidget):
|
||||
self._lastMove = {}
|
||||
self._timeChanged = 0
|
||||
|
||||
##
|
||||
# Build GUI
|
||||
##
|
||||
# Build GUI
|
||||
# =========
|
||||
|
||||
# Context Menu
|
||||
self.setContextMenuPolicy(Qt.CustomContextMenu)
|
||||
@@ -348,6 +347,7 @@ class GuiProjectTree(QTreeWidget):
|
||||
# Tree Settings
|
||||
iPx = self.mainTheme.baseIconSize
|
||||
cMg = self.mainConf.pxInt(6)
|
||||
|
||||
self.setIconSize(QSize(iPx, iPx))
|
||||
self.setFrameStyle(QFrame.NoFrame)
|
||||
self.setExpandsOnDoubleClick(False)
|
||||
@@ -778,13 +778,6 @@ class GuiProjectTree(QTreeWidget):
|
||||
if trItem is None or nwItem is None:
|
||||
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()
|
||||
hLevel = self.theProject.index.getHandleHeaderLevel(tHandle)
|
||||
itemIcon = self.mainTheme.getItemIcon(
|
||||
@@ -793,18 +786,18 @@ class GuiProjectTree(QTreeWidget):
|
||||
|
||||
trItem.setIcon(self.C_NAME, itemIcon)
|
||||
trItem.setText(self.C_NAME, nwItem.itemName)
|
||||
trItem.setIcon(self.C_EXPORT, expIcon)
|
||||
trItem.setIcon(self.C_STATUS, statusIcon)
|
||||
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:
|
||||
trFont = trItem.font(self.C_NAME)
|
||||
if hLevel in ("H1", "H2"):
|
||||
trFont.setBold(True)
|
||||
trFont.setUnderline(True)
|
||||
else:
|
||||
trFont.setBold(False)
|
||||
trFont.setUnderline(False)
|
||||
trFont.setBold(hLevel == "H1" or hLevel == "H2")
|
||||
trFont.setUnderline(hLevel == "H1")
|
||||
trItem.setFont(self.C_NAME, trFont)
|
||||
|
||||
return
|
||||
@@ -972,7 +965,7 @@ class GuiProjectTree(QTreeWidget):
|
||||
return
|
||||
|
||||
if tItem.itemType == nwItemType.FILE:
|
||||
self.projView.openDocumentRequest.emit(tHandle, nwDocMode.EDIT)
|
||||
self.projView.openDocumentRequest.emit(tHandle, nwDocMode.EDIT, -1, "")
|
||||
else:
|
||||
trItem = self._getTreeItem(tHandle)
|
||||
if trItem is not None:
|
||||
@@ -1016,11 +1009,11 @@ class GuiProjectTree(QTreeWidget):
|
||||
if isFile:
|
||||
ctxMenu.addAction(
|
||||
self.tr("Open Document"),
|
||||
lambda: self.projView.openDocumentRequest.emit(tHandle, nwDocMode.EDIT)
|
||||
lambda: self.projView.openDocumentRequest.emit(tHandle, nwDocMode.EDIT, -1, "")
|
||||
)
|
||||
ctxMenu.addAction(
|
||||
self.tr("View Document"),
|
||||
lambda: self.projView.openDocumentRequest.emit(tHandle, nwDocMode.VIEW)
|
||||
lambda: self.projView.openDocumentRequest.emit(tHandle, nwDocMode.VIEW, -1, "")
|
||||
)
|
||||
ctxMenu.addSeparator()
|
||||
|
||||
@@ -1112,7 +1105,7 @@ class GuiProjectTree(QTreeWidget):
|
||||
return
|
||||
|
||||
if tItem.itemType == nwItemType.FILE:
|
||||
self.projView.openDocumentRequest.emit(tHandle, nwDocMode.VIEW)
|
||||
self.projView.openDocumentRequest.emit(tHandle, nwDocMode.VIEW, -1, "")
|
||||
|
||||
return
|
||||
|
||||
|
||||
@@ -472,9 +472,12 @@ class GuiIcons:
|
||||
# Switches
|
||||
"sticky-on", "sticky-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",
|
||||
}
|
||||
|
||||
@@ -573,19 +576,22 @@ class GuiIcons:
|
||||
# Access Functions
|
||||
##
|
||||
|
||||
def loadDecoration(self, decoKey, pxW, pxH):
|
||||
def loadDecoration(self, decoKey, pxW=None, pxH=None):
|
||||
"""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)
|
||||
return QPixmap()
|
||||
|
||||
imgPath = os.path.join(
|
||||
self.mainConf.assetPath, "images", self.DECO_MAP[decoKey]
|
||||
)
|
||||
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()
|
||||
|
||||
theDeco = QPixmap(imgPath)
|
||||
|
||||
@@ -40,7 +40,7 @@ from PyQt5.QtWidgets import (
|
||||
|
||||
from novelwriter.gui import (
|
||||
GuiDocEditor, GuiDocViewDetails, GuiDocViewer, GuiItemDetails, GuiMainMenu,
|
||||
GuiMainStatus, GuiNovelTree, GuiOutlineView, GuiProjectView, GuiTheme,
|
||||
GuiMainStatus, GuiNovelView, GuiOutlineView, GuiProjectView, GuiTheme,
|
||||
GuiViewsBar
|
||||
)
|
||||
from novelwriter.dialogs import (
|
||||
@@ -106,7 +106,7 @@ class GuiMain(QMainWindow):
|
||||
# Main GUI Elements
|
||||
self.statusBar = GuiMainStatus(self)
|
||||
self.projView = GuiProjectView(self)
|
||||
self.novelView = GuiNovelTree(self)
|
||||
self.novelView = GuiNovelView(self)
|
||||
self.docEditor = GuiDocEditor(self)
|
||||
self.viewMeta = GuiDocViewDetails(self)
|
||||
self.docViewer = GuiDocViewer(self)
|
||||
@@ -205,6 +205,10 @@ class GuiMain(QMainWindow):
|
||||
self.projView.treeItemChanged.connect(self.docViewer.updateDocInfo)
|
||||
self.projView.treeItemChanged.connect(self.itemDetails.updateViewBox)
|
||||
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.docEditedStatusChanged.connect(self.statusBar.doUpdateDocumentStatus)
|
||||
@@ -292,7 +296,7 @@ class GuiMain(QMainWindow):
|
||||
"""
|
||||
# Project Area
|
||||
self.projView.clearProject()
|
||||
self.novelView.clearTree()
|
||||
self.novelView.clearProject()
|
||||
self.itemDetails.clearDetails()
|
||||
|
||||
# Work Area
|
||||
@@ -362,6 +366,7 @@ class GuiMain(QMainWindow):
|
||||
self.saveProject()
|
||||
self.docEditor.setDictionaries()
|
||||
self.outlineView.updateRootItem(None)
|
||||
self.novelView.openProjectTasks()
|
||||
self.rebuildIndex(beQuiet=True)
|
||||
self.statusBar.setRefTime(self.theProject.projOpened)
|
||||
self.statusBar.setProjectStatus(nwState.GOOD)
|
||||
@@ -418,6 +423,7 @@ class GuiMain(QMainWindow):
|
||||
self.closeDocument()
|
||||
self.docViewer.clearNavHistory()
|
||||
self.outlineView.closeOutline()
|
||||
self.novelView.closeProjectTasks()
|
||||
|
||||
self.theProject.closeProject(self.idleTime)
|
||||
self.idleRefTime = time()
|
||||
@@ -509,6 +515,7 @@ class GuiMain(QMainWindow):
|
||||
self.docEditor.toggleSpellCheck(self.theProject.spellCheck)
|
||||
self.statusBar.setRefTime(self.theProject.projOpened)
|
||||
self.outlineView.updateRootItem(None)
|
||||
self.novelView.openProjectTasks()
|
||||
self._updateStatusWordCount()
|
||||
|
||||
# Restore previously open documents, if any
|
||||
@@ -789,11 +796,11 @@ class GuiMain(QMainWindow):
|
||||
|
||||
tHandle = None
|
||||
tLine = None
|
||||
if self.projView.treeFocus():
|
||||
if self.projView.treeHasFocus():
|
||||
tHandle = self.projView.getSelectedHandle()
|
||||
elif self.novelView.hasFocus():
|
||||
elif self.novelView.treeHasFocus():
|
||||
tHandle, tLine = self.novelView.getSelectedHandle()
|
||||
elif self.outlineView.treeFocus():
|
||||
elif self.outlineView.treeHasFocus():
|
||||
tHandle, tLine = self.outlineView.getSelectedHandle()
|
||||
else:
|
||||
logger.warning("No item selected")
|
||||
@@ -825,7 +832,7 @@ class GuiMain(QMainWindow):
|
||||
"""Rebuild the project tree.
|
||||
"""
|
||||
self.projView.populateTree()
|
||||
self.novelView.refreshTree()
|
||||
# self.novelView.refreshTree()
|
||||
return
|
||||
|
||||
def requestNovelTreeRefresh(self):
|
||||
@@ -920,7 +927,7 @@ class GuiMain(QMainWindow):
|
||||
self.docEditor.initEditor()
|
||||
self.docViewer.initViewer()
|
||||
self.projView.initSettings()
|
||||
self.novelView.initTree()
|
||||
self.novelView.initSettings()
|
||||
self.outlineView.initOutline()
|
||||
self._updateStatusWordCount()
|
||||
|
||||
@@ -1161,7 +1168,6 @@ class GuiMain(QMainWindow):
|
||||
self.mainConf.setViewPanePos(self.splitView.sizes())
|
||||
|
||||
self.mainConf.setShowRefPanel(self.viewMeta.isVisible())
|
||||
self.mainConf.setNovelColWidths(self.novelView.getColumnSizes())
|
||||
if not self.mainConf.isFullScreen:
|
||||
self.mainConf.setWinSize(self.width(), self.height())
|
||||
|
||||
@@ -1469,15 +1475,15 @@ class GuiMain(QMainWindow):
|
||||
self.viewDocument(tHandle=tHandle, tAnchor=f"#{sTitle}")
|
||||
return
|
||||
|
||||
@pyqtSlot(str, Enum)
|
||||
def _openDocument(self, tHandle, tMode):
|
||||
"""Handle an open document request.
|
||||
@pyqtSlot(str, Enum, int, str)
|
||||
def _openDocument(self, tHandle, tMode, tLine, tAnchor):
|
||||
"""Handle an open document request from one of the tree views.
|
||||
"""
|
||||
if tHandle is not None:
|
||||
if tMode == nwDocMode.EDIT:
|
||||
self.openDocument(tHandle, changeFocus=False)
|
||||
self.openDocument(tHandle, tLine=tLine, changeFocus=False)
|
||||
elif tMode == nwDocMode.VIEW:
|
||||
self.viewDocument(tHandle=tHandle)
|
||||
self.viewDocument(tHandle=tHandle, tAnchor=(tAnchor or None))
|
||||
return
|
||||
|
||||
@pyqtSlot(nwView)
|
||||
@@ -1576,30 +1582,30 @@ class GuiMain(QMainWindow):
|
||||
return
|
||||
|
||||
@pyqtSlot(int)
|
||||
def _mainStackChanged(self, tabIndex):
|
||||
def _mainStackChanged(self, stIndex):
|
||||
"""Activated when the main window tab is changed.
|
||||
"""
|
||||
if tabIndex == self.idxEditorView:
|
||||
logger.verbose("Editor tab activated")
|
||||
elif tabIndex == self.idxOutlineView:
|
||||
logger.verbose("Project outline tab activated")
|
||||
if stIndex == self.idxEditorView:
|
||||
logger.verbose("Editor View activated")
|
||||
elif stIndex == self.idxOutlineView:
|
||||
logger.verbose("Outline View activated")
|
||||
if self.hasProject:
|
||||
self.outlineView.refreshView()
|
||||
|
||||
return
|
||||
|
||||
@pyqtSlot(int)
|
||||
def _projStackChanged(self, tabIndex):
|
||||
def _projStackChanged(self, stIndex):
|
||||
"""Activated when the project view tab is changed.
|
||||
"""
|
||||
sHandle = None
|
||||
|
||||
if tabIndex == self.idxProjView:
|
||||
logger.verbose("Project tree tab activated")
|
||||
if stIndex == self.idxProjView:
|
||||
logger.verbose("Project Tree View activated")
|
||||
sHandle = self.projView.getSelectedHandle()
|
||||
|
||||
elif tabIndex == self.idxNovelView:
|
||||
logger.verbose("Novel tree tab activated")
|
||||
elif stIndex == self.idxNovelView:
|
||||
logger.verbose("Novel Tree View activated")
|
||||
if self.hasProject:
|
||||
self.novelView.refreshTree()
|
||||
sHandle, _ = self.novelView.getSelectedHandle()
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
%%~name: Chapter One
|
||||
%%~path: e5e47ebf63b1c/a520879ca0b45
|
||||
%%~kind: NOVEL/DOCUMENT
|
||||
### Chapter One
|
||||
## Chapter One
|
||||
|
||||
@pov: Jane
|
||||
|
||||
|
||||
@@ -3,4 +3,4 @@
|
||||
%%~kind: NOVEL/DOCUMENT
|
||||
# Part One
|
||||
|
||||
>> In the beginning … <<
|
||||
>> In the beginning … <<
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
<?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>
|
||||
<name>Sample Project</name>
|
||||
<title>Sample Project</title>
|
||||
<author>Jane Smith</author>
|
||||
<author>Jay Doh</author>
|
||||
<saveCount>1345</saveCount>
|
||||
<autoCount>229</autoCount>
|
||||
<editTime>68286</editTime>
|
||||
<saveCount>1371</saveCount>
|
||||
<autoCount>236</autoCount>
|
||||
<editTime>69222</editTime>
|
||||
</project>
|
||||
<settings>
|
||||
<doBackup>False</doBackup>
|
||||
@@ -17,6 +17,8 @@
|
||||
<autoOutline>True</autoOutline>
|
||||
<lastEdited>636b6aa9b697b</lastEdited>
|
||||
<lastViewed>636b6aa9b697b</lastViewed>
|
||||
<lastNovel>7031beac91f75</lastNovel>
|
||||
<lastOutline>None</lastOutline>
|
||||
<lastWordCount>1363</lastWordCount>
|
||||
<novelWordCount>954</novelWordCount>
|
||||
<notesWordCount>409</notesWordCount>
|
||||
@@ -33,9 +35,9 @@
|
||||
<section></section>
|
||||
</titleFormat>
|
||||
<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="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="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>
|
||||
@@ -70,11 +72,11 @@
|
||||
<name status="sf24ce6" import="ia857f0" exported="True">Chapter One</name>
|
||||
</item>
|
||||
<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>
|
||||
</item>
|
||||
<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>
|
||||
</item>
|
||||
<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>
|
||||
</item>
|
||||
<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>
|
||||
</item>
|
||||
<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>
|
||||
</item>
|
||||
<item handle="e5e47ebf63b1c" parent="None" root="e5e47ebf63b1c" order="1" type="ROOT" class="NOVEL">
|
||||
@@ -99,7 +101,7 @@
|
||||
</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>
|
||||
<name status="sc24b8f" 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"/>
|
||||
|
||||
@@ -16,6 +16,8 @@
|
||||
<autoOutline>True</autoOutline>
|
||||
<lastEdited>7a992350f3eb6</lastEdited>
|
||||
<lastViewed>None</lastViewed>
|
||||
<lastNovel>None</lastNovel>
|
||||
<lastOutline>None</lastOutline>
|
||||
<lastWordCount>3847</lastWordCount>
|
||||
<novelWordCount>3109</novelWordCount>
|
||||
<notesWordCount>738</notesWordCount>
|
||||
|
||||
@@ -17,6 +17,8 @@
|
||||
<autoOutline>True</autoOutline>
|
||||
<lastEdited>None</lastEdited>
|
||||
<lastViewed>None</lastViewed>
|
||||
<lastNovel>None</lastNovel>
|
||||
<lastOutline>None</lastOutline>
|
||||
<lastWordCount>10</lastWordCount>
|
||||
<novelWordCount>10</novelWordCount>
|
||||
<notesWordCount>0</notesWordCount>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<?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>
|
||||
<name>Test Custom</name>
|
||||
<title>Test Novel</title>
|
||||
@@ -17,6 +17,8 @@
|
||||
<autoOutline>True</autoOutline>
|
||||
<lastEdited>None</lastEdited>
|
||||
<lastViewed>None</lastViewed>
|
||||
<lastNovel>None</lastNovel>
|
||||
<lastOutline>None</lastOutline>
|
||||
<lastWordCount>0</lastWordCount>
|
||||
<novelWordCount>0</novelWordCount>
|
||||
<notesWordCount>0</notesWordCount>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<?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>
|
||||
<name>Test Custom</name>
|
||||
<title>Test Novel</title>
|
||||
@@ -17,6 +17,8 @@
|
||||
<autoOutline>True</autoOutline>
|
||||
<lastEdited>None</lastEdited>
|
||||
<lastViewed>None</lastViewed>
|
||||
<lastNovel>None</lastNovel>
|
||||
<lastOutline>None</lastOutline>
|
||||
<lastWordCount>0</lastWordCount>
|
||||
<novelWordCount>0</novelWordCount>
|
||||
<notesWordCount>0</notesWordCount>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<?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>
|
||||
<name>New Project</name>
|
||||
<title>New Novel</title>
|
||||
@@ -16,6 +16,8 @@
|
||||
<autoOutline>True</autoOutline>
|
||||
<lastEdited>None</lastEdited>
|
||||
<lastViewed>None</lastViewed>
|
||||
<lastNovel>None</lastNovel>
|
||||
<lastOutline>None</lastOutline>
|
||||
<lastWordCount>2</lastWordCount>
|
||||
<novelWordCount>1</novelWordCount>
|
||||
<notesWordCount>1</notesWordCount>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<?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>
|
||||
<name>New Project</name>
|
||||
<title></title>
|
||||
@@ -15,6 +15,8 @@
|
||||
<autoOutline>True</autoOutline>
|
||||
<lastEdited>None</lastEdited>
|
||||
<lastViewed>None</lastViewed>
|
||||
<lastNovel>None</lastNovel>
|
||||
<lastOutline>None</lastOutline>
|
||||
<lastWordCount>0</lastWordCount>
|
||||
<novelWordCount>0</novelWordCount>
|
||||
<notesWordCount>0</notesWordCount>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<?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>
|
||||
<name>New Project</name>
|
||||
<title>New Novel</title>
|
||||
@@ -16,6 +16,8 @@
|
||||
<autoOutline>True</autoOutline>
|
||||
<lastEdited>None</lastEdited>
|
||||
<lastViewed>None</lastViewed>
|
||||
<lastNovel>None</lastNovel>
|
||||
<lastOutline>None</lastOutline>
|
||||
<lastWordCount>0</lastWordCount>
|
||||
<novelWordCount>0</novelWordCount>
|
||||
<notesWordCount>0</notesWordCount>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<?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>
|
||||
<name>New Project</name>
|
||||
<title>New Novel</title>
|
||||
@@ -16,6 +16,8 @@
|
||||
<autoOutline>True</autoOutline>
|
||||
<lastEdited>000000000000f</lastEdited>
|
||||
<lastViewed>None</lastViewed>
|
||||
<lastNovel>0000000000008</lastNovel>
|
||||
<lastOutline>None</lastOutline>
|
||||
<lastWordCount>129</lastWordCount>
|
||||
<novelWordCount>102</novelWordCount>
|
||||
<notesWordCount>27</notesWordCount>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<?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>
|
||||
<name>New Project</name>
|
||||
<title>New Novel</title>
|
||||
@@ -16,6 +16,8 @@
|
||||
<autoOutline>True</autoOutline>
|
||||
<lastEdited>None</lastEdited>
|
||||
<lastViewed>None</lastViewed>
|
||||
<lastNovel>None</lastNovel>
|
||||
<lastOutline>None</lastOutline>
|
||||
<lastWordCount>9</lastWordCount>
|
||||
<novelWordCount>9</novelWordCount>
|
||||
<notesWordCount>0</notesWordCount>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<?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>
|
||||
<name>Project Name</name>
|
||||
<title>Project Title</title>
|
||||
@@ -17,6 +17,8 @@
|
||||
<autoOutline>True</autoOutline>
|
||||
<lastEdited>None</lastEdited>
|
||||
<lastViewed>None</lastViewed>
|
||||
<lastNovel>None</lastNovel>
|
||||
<lastOutline>None</lastOutline>
|
||||
<lastWordCount>9</lastWordCount>
|
||||
<novelWordCount>9</novelWordCount>
|
||||
<notesWordCount>0</notesWordCount>
|
||||
|
||||
@@ -470,7 +470,7 @@ def testCoreProject_Open(monkeypatch, nwMinimal, mockGUI):
|
||||
writeFile(os.path.join(nwMinimal, "data_0", "123456789abc_main.bak"), "stuff")
|
||||
mockGUI.clear()
|
||||
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()
|
||||
|
||||
|
||||
@@ -28,8 +28,8 @@ from tools import cmpFiles, buildTestProject, XML_IGNORE, writeFile
|
||||
from PyQt5.QtCore import Qt
|
||||
from PyQt5.QtWidgets import QMessageBox, QInputDialog
|
||||
|
||||
from novelwriter.gui import GuiDocEditor, GuiNovelTree, GuiOutlineView
|
||||
from novelwriter.enum import nwItemType, nwWidget
|
||||
from novelwriter.gui import GuiDocEditor, GuiNovelView, GuiOutlineView
|
||||
from novelwriter.enum import nwItemType, nwView, nwWidget
|
||||
from novelwriter.tools import GuiProjectWizard
|
||||
from novelwriter.gui.projtree import GuiProjectTree
|
||||
from novelwriter.dialogs import GuiEditLabel
|
||||
@@ -120,6 +120,7 @@ def testGuiMain_ProjectTreeItems(qtbot, monkeypatch, nwGUI, fncProj, mockRnd):
|
||||
assert nwGUI.openSelectedItem() is False
|
||||
|
||||
# Project Tree has focus
|
||||
nwGUI._changeView(nwView.PROJECT)
|
||||
nwGUI.switchFocus(nwWidget.TREE)
|
||||
nwGUI.projStack.setCurrentIndex(0)
|
||||
with monkeypatch.context() as mp:
|
||||
@@ -131,23 +132,22 @@ def testGuiMain_ProjectTreeItems(qtbot, monkeypatch, nwGUI, fncProj, mockRnd):
|
||||
assert nwGUI.closeDocument() is True
|
||||
|
||||
# Novel Tree has focus
|
||||
nwGUI.projStack.setCurrentIndex(1)
|
||||
nwGUI.novelView.refreshTree(True)
|
||||
nwGUI._changeView(nwView.NOVEL)
|
||||
nwGUI.novelView.novelTree.refreshTree(rootHandle=None, overRide=True)
|
||||
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
|
||||
actItem = nwGUI.novelView.topLevelItem(0)
|
||||
chpItem = actItem.child(0)
|
||||
selItem = chpItem.child(0)
|
||||
nwGUI.novelView.setCurrentItem(selItem)
|
||||
selItem = nwGUI.novelView.novelTree.topLevelItem(2)
|
||||
nwGUI.novelView.novelTree.setCurrentItem(selItem)
|
||||
nwGUI._keyPressReturn()
|
||||
assert nwGUI.docEditor.docHandle() == sHandle
|
||||
assert nwGUI.closeDocument() is True
|
||||
|
||||
# Project Outline has focus
|
||||
nwGUI._changeView(nwView.OUTLINE)
|
||||
nwGUI.switchFocus(nwWidget.OUTLINE)
|
||||
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
|
||||
actItem = nwGUI.outlineView.outlineTree.topLevelItem(0)
|
||||
chpItem = actItem.child(0)
|
||||
@@ -157,7 +157,7 @@ def testGuiMain_ProjectTreeItems(qtbot, monkeypatch, nwGUI, fncProj, mockRnd):
|
||||
assert nwGUI.docEditor.docHandle() == sHandle
|
||||
assert nwGUI.closeDocument() is True
|
||||
|
||||
# qtbot.stopForInteraction()
|
||||
# qtbot.stop()
|
||||
|
||||
# END Test testGuiMain_ProjectTreeItems
|
||||
|
||||
|
||||
@@ -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/>.
|
||||
"""
|
||||
|
||||
import pytest
|
||||
import os
|
||||
import pytest
|
||||
|
||||
from tools import writeFile
|
||||
from tools import buildTestProject, writeFile
|
||||
|
||||
from PyQt5.QtCore import Qt
|
||||
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
|
||||
def testGuiNovelTree_TreeItems(qtbot, monkeypatch, nwGUI, nwMinimal):
|
||||
def testGuiNovelTree_TreeItems(qtbot, monkeypatch, nwGUI, fncProj, mockRnd):
|
||||
"""Test navigating the novel tree.
|
||||
"""
|
||||
# Block message box
|
||||
monkeypatch.setattr(QMessageBox, "question", lambda *a: QMessageBox.Yes)
|
||||
monkeypatch.setattr(QMessageBox, "information", lambda *a: QMessageBox.Yes)
|
||||
monkeypatch.setattr(GuiEditLabel, "getLabel", lambda *a, text: (text, True))
|
||||
|
||||
nwGUI.openProject(nwMinimal)
|
||||
nwTree = nwGUI.novelView
|
||||
buildTestProject(nwGUI, fncProj)
|
||||
|
||||
##
|
||||
# Show/Hide Scrollbars
|
||||
##
|
||||
nwGUI.switchFocus(nwWidget.TREE)
|
||||
nwGUI.projView.projTree.clearSelection()
|
||||
nwGUI.projView.projTree._getTreeItem("000000000000a").setSelected(True)
|
||||
nwGUI.projView.projTree.newTreeItem(nwItemType.FILE)
|
||||
|
||||
nwTree.mainConf.hideVScroll = True
|
||||
nwTree.mainConf.hideHScroll = True
|
||||
nwTree.initTree()
|
||||
assert not nwTree.verticalScrollBar().isVisible()
|
||||
assert not nwTree.horizontalScrollBar().isVisible()
|
||||
writeFile(
|
||||
os.path.join(nwGUI.theProject.projContent, "0000000000010.nwd"),
|
||||
"# Jane Doe\n\n@tag: Jane\n\n"
|
||||
)
|
||||
writeFile(
|
||||
os.path.join(nwGUI.theProject.projContent, "000000000000f.nwd"),
|
||||
"### Scene One\n\n@pov: Jane\n@focus: Jane\n\n"
|
||||
)
|
||||
|
||||
nwTree.mainConf.hideVScroll = False
|
||||
nwTree.mainConf.hideHScroll = False
|
||||
nwTree.initTree()
|
||||
assert nwTree.verticalScrollBar().isEnabled()
|
||||
assert nwTree.horizontalScrollBar().isEnabled()
|
||||
novelView = nwGUI.novelView
|
||||
novelTree = novelView.novelTree
|
||||
novelBar = novelView.novelBar
|
||||
|
||||
##
|
||||
# Populate Tree
|
||||
##
|
||||
# Show/Hide Scrollbars
|
||||
# ====================
|
||||
|
||||
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.rebuildIndex()
|
||||
nwTree._populateTree()
|
||||
assert nwTree.topLevelItemCount() == 1
|
||||
novelTree._populateTree(rootHandle=None)
|
||||
assert novelTree.topLevelItemCount() == 3
|
||||
|
||||
# Rebuild should preserve selection
|
||||
topItem = nwTree.topLevelItem(0)
|
||||
topItem = novelTree.topLevelItem(0)
|
||||
assert not topItem.isSelected()
|
||||
topItem.setSelected(True)
|
||||
assert nwTree.selectedItems()[0] == topItem
|
||||
assert nwTree.getSelectedHandle() == ("a35baf2e93843", 0)
|
||||
assert novelTree.selectedItems()[0] == topItem
|
||||
assert novelView.getSelectedHandle() == ("000000000000c", 0)
|
||||
|
||||
nwTree.refreshTree()
|
||||
assert nwTree.topLevelItem(0).isSelected()
|
||||
# Refresh using the slot for the butoom
|
||||
novelBar._refreshNovelTree()
|
||||
assert novelTree.topLevelItem(0).isSelected()
|
||||
|
||||
##
|
||||
# Open Items
|
||||
##
|
||||
# Open Items
|
||||
# ==========
|
||||
|
||||
# Clear selection
|
||||
nwTree.clearSelection()
|
||||
scItem = nwTree.topLevelItem(0).child(0).child(0)
|
||||
novelTree.clearSelection()
|
||||
scItem = novelTree.topLevelItem(2)
|
||||
scItem.setSelected(True)
|
||||
assert scItem.isSelected()
|
||||
|
||||
# Clear selection with mouse
|
||||
vPort = nwTree.viewport()
|
||||
vPort = novelTree.viewport()
|
||||
qtbot.mouseClick(vPort, Qt.LeftButton, pos=vPort.rect().center(), delay=10)
|
||||
assert not scItem.isSelected()
|
||||
|
||||
@@ -93,8 +113,8 @@ def testGuiNovelTree_TreeItems(qtbot, monkeypatch, nwGUI, nwMinimal):
|
||||
scItem.setSelected(True)
|
||||
assert scItem.isSelected()
|
||||
assert nwGUI.docEditor.docHandle() is None
|
||||
nwTree._treeDoubleClick(scItem, 0)
|
||||
assert nwGUI.docEditor.docHandle() == "8c659a11cd429"
|
||||
novelTree._treeDoubleClick(scItem, 0)
|
||||
assert nwGUI.docEditor.docHandle() == "000000000000f"
|
||||
|
||||
# Open item with middle mouse button
|
||||
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)
|
||||
assert nwGUI.docViewer.docHandle() is None
|
||||
|
||||
scRect = nwTree.visualItemRect(scItem)
|
||||
oldData = scItem.data(nwTree.C_TITLE, Qt.UserRole)
|
||||
scItem.setData(nwTree.C_TITLE, Qt.UserRole, (None, "", ""))
|
||||
scRect = novelTree.visualItemRect(scItem)
|
||||
oldData = scItem.data(novelTree.C_TITLE, Qt.UserRole)
|
||||
scItem.setData(novelTree.C_TITLE, Qt.UserRole, (None, "", ""))
|
||||
qtbot.mouseClick(vPort, Qt.MiddleButton, pos=scRect.center(), delay=10)
|
||||
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)
|
||||
assert nwGUI.docViewer.docHandle() == "8c659a11cd429"
|
||||
assert nwGUI.docViewer.docHandle() == "000000000000f"
|
||||
|
||||
##
|
||||
# Populate Tree
|
||||
##
|
||||
# Last Column
|
||||
# ===========
|
||||
|
||||
# Add weird titles to first file to check hnadling of non-standard
|
||||
# order of title levels.
|
||||
writeFile(os.path.join(nwMinimal, "content", "a35baf2e93843.nwd"), (
|
||||
"#### Section wo/Scene\n\n"
|
||||
"### Scene wo/Chapter\n\n"
|
||||
"## Chapter wo/Title\n\n"
|
||||
"# Title\n\n"
|
||||
"#### Section w/Title, wo/Scene\n\n"
|
||||
"### Scene w/Title, wo/Chapter\n\n"
|
||||
"## Chapter\n\n"
|
||||
"#### Section w/Chapter, wo/Scene\n\n"
|
||||
"### Scene\n\n"
|
||||
"#### Section\n\n"
|
||||
))
|
||||
nwGUI.rebuildIndex()
|
||||
nwTree._populateTree()
|
||||
assert nwTree.topLevelItem(0).text(nwTree.C_TITLE) == "Section wo/Scene"
|
||||
assert nwTree.topLevelItem(1).text(nwTree.C_TITLE) == "Scene wo/Chapter"
|
||||
assert nwTree.topLevelItem(2).text(nwTree.C_TITLE) == "Chapter wo/Title"
|
||||
assert nwTree.topLevelItem(3).text(nwTree.C_TITLE) == "Title"
|
||||
novelBar.setLastColType(NovelTreeColumn.HIDDEN)
|
||||
assert novelTree.isColumnHidden(novelTree.C_LAST) is True
|
||||
assert novelTree.lastColType == NovelTreeColumn.HIDDEN
|
||||
assert novelTree._getLastColumnText("000000000000f", "T000001") == ("", "")
|
||||
|
||||
tTitle = nwTree.topLevelItem(3)
|
||||
assert tTitle.child(0).text(nwTree.C_TITLE) == "Section w/Title, wo/Scene"
|
||||
assert tTitle.child(1).text(nwTree.C_TITLE) == "Scene w/Title, wo/Chapter"
|
||||
assert tTitle.child(2).text(nwTree.C_TITLE) == "Chapter"
|
||||
novelBar.setLastColType(NovelTreeColumn.POV)
|
||||
assert novelTree.isColumnHidden(novelTree.C_LAST) is False
|
||||
assert novelTree.lastColType == NovelTreeColumn.POV
|
||||
assert novelTree._getLastColumnText("000000000000f", "T000001") == (
|
||||
"Jane", "Point of View: Jane"
|
||||
)
|
||||
|
||||
tChap = tTitle.child(2)
|
||||
assert tChap.child(0).text(nwTree.C_TITLE) == "Section w/Chapter, wo/Scene"
|
||||
assert tChap.child(1).text(nwTree.C_TITLE) == "Scene"
|
||||
novelBar.setLastColType(NovelTreeColumn.FOCUS)
|
||||
assert novelTree.isColumnHidden(novelTree.C_LAST) is False
|
||||
assert novelTree.lastColType == NovelTreeColumn.FOCUS
|
||||
assert novelTree._getLastColumnText("000000000000f", "T000001") == (
|
||||
"Jane", "Focus: Jane"
|
||||
)
|
||||
|
||||
tScene = tChap.child(1)
|
||||
assert tScene.child(0).text(nwTree.C_TITLE) == "Section"
|
||||
novelBar.setLastColType(NovelTreeColumn.PLOT)
|
||||
assert novelTree.isColumnHidden(novelTree.C_LAST) is False
|
||||
assert novelTree.lastColType == NovelTreeColumn.PLOT
|
||||
assert novelTree._getLastColumnText("000000000000f", "T000001") == (
|
||||
"", "Plot: "
|
||||
)
|
||||
|
||||
##
|
||||
# Close
|
||||
##
|
||||
novelTree._lastCol = None
|
||||
assert novelTree._getLastColumnText("0000000000000", "T000000") == ("", "")
|
||||
|
||||
# qtbot.stopForInteraction()
|
||||
# Close
|
||||
# =====
|
||||
|
||||
# qtbot.stop()
|
||||
nwGUI.closeProject()
|
||||
|
||||
# END Test testGuiNovelTree_TreeItems
|
||||
|
||||
@@ -71,7 +71,7 @@ def testGuiOutline_Main(qtbot, monkeypatch, nwGUI, fncDir):
|
||||
# Check focus
|
||||
with monkeypatch.context() as mp:
|
||||
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
|
||||
|
||||
|
||||
@@ -127,7 +127,7 @@ def testGuiTheme_Main(qtbot, monkeypatch, nwMinimal, tmpDir):
|
||||
assert anImg.isNull()
|
||||
|
||||
# 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)
|
||||
assert isinstance(anImg, QPixmap)
|
||||
assert anImg.isNull()
|
||||
|
||||