Merge branch 'main' into multi_root
This commit is contained in:
@@ -42,6 +42,7 @@ doc_h2 = mixed_heading2.svg
|
||||
doc_h3 = mixed_heading3.svg
|
||||
doc_h4 = mixed_heading4.svg
|
||||
done = typ_input-checked.svg
|
||||
down = typ_chevron-down.svg
|
||||
edit = typ_pencil.svg
|
||||
forward = typ_chevron-right.svg
|
||||
hash = typ_hash.svg
|
||||
@@ -74,3 +75,4 @@ status_stats = typ_chart-bar-grey.svg
|
||||
status_time = typ_stopwatch-grey.svg
|
||||
sticky-off = typ_pin-outline.svg
|
||||
sticky-on = typ_pin.svg
|
||||
up = typ_chevron-up.svg
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
id="svg8980"
|
||||
viewBox="0 0 24 24"
|
||||
height="24"
|
||||
width="24"
|
||||
version="1.2">
|
||||
<metadata
|
||||
id="metadata8986">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<defs
|
||||
id="defs8984" />
|
||||
<path
|
||||
style="fill:#6699cc;fill-opacity:1;stroke-width:1.28574"
|
||||
id="path8978"
|
||||
d="m 3.7531209,6.4345258 c -1.004161,1.002875 -1.004161,2.63319 0,3.6360652 l 8.2467181,8.248004 8.246719,-8.248004 C 20.749281,9.5691528 21,8.9108558 21,8.2525578 c 0,-0.658297 -0.250719,-1.316595 -0.753442,-1.818032 -1.004161,-1.004161 -2.631904,-1.004161 -3.636065,0 L 11.999839,11.043894 7.3891859,6.4345258 c -1.004161,-1.004161 -2.631905,-1.004161 -3.636065,0 z" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
@@ -0,0 +1,31 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
id="svg8980"
|
||||
viewBox="0 0 24 24"
|
||||
height="24"
|
||||
width="24"
|
||||
version="1.2">
|
||||
<metadata
|
||||
id="metadata8986">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<defs
|
||||
id="defs8984" />
|
||||
<path
|
||||
style="fill:#6699cc;fill-opacity:1;stroke-width:1.28574"
|
||||
id="path8978"
|
||||
d="m 20.246879,17.565474 c 1.004161,-1.002875 1.004161,-2.63319 0,-3.636065 L 12.000161,5.6814052 3.7534421,13.929409 c -0.502723,0.501438 -0.753442,1.159735 -0.753442,1.818033 0,0.658297 0.250719,1.316595 0.753442,1.818032 1.004161,1.004161 2.631904,1.004161 3.636065,0 l 4.6106539,-4.609368 4.610653,4.609368 c 1.004161,1.004161 2.631905,1.004161 3.636065,0 z" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
@@ -42,6 +42,7 @@ doc_h2 = mixed_heading2.svg
|
||||
doc_h3 = mixed_heading3.svg
|
||||
doc_h4 = mixed_heading4.svg
|
||||
done = typ_input-checked.svg
|
||||
down = typ_chevron-down.svg
|
||||
edit = typ_pencil.svg
|
||||
forward = typ_chevron-right.svg
|
||||
hash = typ_hash.svg
|
||||
@@ -74,3 +75,4 @@ status_stats = typ_chart-bar-grey.svg
|
||||
status_time = typ_stopwatch-grey.svg
|
||||
sticky-off = typ_pin-outline.svg
|
||||
sticky-on = typ_pin.svg
|
||||
up = typ_chevron-up.svg
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
id="svg8980"
|
||||
viewBox="0 0 24 24"
|
||||
height="24"
|
||||
width="24"
|
||||
version="1.2">
|
||||
<metadata
|
||||
id="metadata8986">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<defs
|
||||
id="defs8984" />
|
||||
<path
|
||||
style="fill:#4271ae;fill-opacity:1;stroke-width:1.28574"
|
||||
id="path8978"
|
||||
d="m 3.7531209,6.4345258 c -1.004161,1.002875 -1.004161,2.63319 0,3.6360652 l 8.2467181,8.248004 8.246719,-8.248004 C 20.749281,9.5691528 21,8.9108558 21,8.2525578 c 0,-0.658297 -0.250719,-1.316595 -0.753442,-1.818032 -1.004161,-1.004161 -2.631904,-1.004161 -3.636065,0 L 11.999839,11.043894 7.3891859,6.4345258 c -1.004161,-1.004161 -2.631905,-1.004161 -3.636065,0 z" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
@@ -0,0 +1,31 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
id="svg8980"
|
||||
viewBox="0 0 24 24"
|
||||
height="24"
|
||||
width="24"
|
||||
version="1.2">
|
||||
<metadata
|
||||
id="metadata8986">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<defs
|
||||
id="defs8984" />
|
||||
<path
|
||||
style="fill:#4271ae;fill-opacity:1;stroke-width:1.28574"
|
||||
id="path8978"
|
||||
d="m 20.246879,17.565474 c 1.004161,-1.002875 1.004161,-2.63319 0,-3.636065 L 12.000161,5.6814052 3.7534421,13.929409 c -0.502723,0.501438 -0.753442,1.159735 -0.753442,1.818033 0,0.658297 0.250719,1.316595 0.753442,1.818032 1.004161,1.004161 2.631904,1.004161 3.636065,0 l 4.6106539,-4.609368 4.610653,4.609368 c 1.004161,1.004161 2.631905,1.004161 3.636065,0 z" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
@@ -184,6 +184,12 @@ def checkIntRange(value, first, last, default):
|
||||
return default
|
||||
|
||||
|
||||
def minmax(value, minVal, maxVal):
|
||||
"""Make sure an integer is between min and max value (inclusive).
|
||||
"""
|
||||
return min(maxVal, max(minVal, value))
|
||||
|
||||
|
||||
def checkIntTuple(value, valid, default):
|
||||
"""Check that an int is an element of a tuple. If it isn't, return
|
||||
the default value.
|
||||
@@ -245,6 +251,13 @@ def formatTime(tS):
|
||||
# String Functions
|
||||
# =============================================================================================== #
|
||||
|
||||
def simplified(string):
|
||||
"""Take a string an strip leading and trailing whitespaces, and
|
||||
replace all occurences of (multiple) whitespaces with a 0x20 space.
|
||||
"""
|
||||
return " ".join(str(string).strip().split())
|
||||
|
||||
|
||||
def splitVersionNumber(value):
|
||||
"""Split a version string on the form aa.bb.cc into major, minor
|
||||
and patch, and computes an integer value aabbcc.
|
||||
|
||||
@@ -679,16 +679,18 @@ class NWIndex():
|
||||
logException()
|
||||
self._indexBroken = True
|
||||
|
||||
# Check that project files are indexed
|
||||
for fHandle in self.theProject.projFiles:
|
||||
if fHandle not in self._fileMeta:
|
||||
self._indexBroken = True
|
||||
break
|
||||
|
||||
logger.verbose("Index check completed in %.3f ms", (time() - tStart)*1000)
|
||||
|
||||
if self._indexBroken:
|
||||
self.clearIndex()
|
||||
logger.verbose("Index check completed in %.3f ms", (time() - tStart)*1000)
|
||||
return
|
||||
|
||||
# If the index was ok, we check that project files are indexed
|
||||
for fHandle in self.theProject.projFiles:
|
||||
if fHandle not in self._fileMeta:
|
||||
logger.warning("Item '%s' is not in the index", fHandle)
|
||||
self.reIndexHandle(fHandle)
|
||||
|
||||
logger.verbose("Index check completed in %.3f ms", (time() - tStart)*1000)
|
||||
|
||||
return
|
||||
|
||||
|
||||
+60
-60
@@ -29,7 +29,7 @@ from lxml import etree
|
||||
|
||||
from novelwriter.enum import nwItemType, nwItemClass, nwItemLayout
|
||||
from novelwriter.common import (
|
||||
checkInt, isHandle, isItemClass, isItemLayout, isItemType
|
||||
checkInt, isHandle, isItemClass, isItemLayout, isItemType, simplified
|
||||
)
|
||||
from novelwriter.constants import nwLabels, nwLists, trConst
|
||||
|
||||
@@ -287,11 +287,11 @@ class NWItem():
|
||||
the current item based on its class.
|
||||
"""
|
||||
if self._class in nwLists.CLS_NOVEL:
|
||||
stName = self.theProject.statusItems.checkEntry(self._status)
|
||||
stIcon = self.theProject.statusItems.getIcon(stName)
|
||||
stName = self.theProject.statusItems.name(self._status)
|
||||
stIcon = self.theProject.statusItems.icon(self._status)
|
||||
else:
|
||||
stName = self.theProject.importItems.checkEntry(self._import)
|
||||
stIcon = self.theProject.importItems.getIcon(stName)
|
||||
stName = self.theProject.importItems.name(self._import)
|
||||
stIcon = self.theProject.importItems.icon(self._import)
|
||||
return stName, stIcon
|
||||
|
||||
def setImportStatus(self, theLabel):
|
||||
@@ -308,153 +308,153 @@ class NWItem():
|
||||
# Set Item Values
|
||||
##
|
||||
|
||||
def setName(self, theName):
|
||||
def setName(self, name):
|
||||
"""Set the item name.
|
||||
"""
|
||||
if isinstance(theName, str):
|
||||
self._name = theName.strip()
|
||||
if isinstance(name, str):
|
||||
self._name = simplified(name)
|
||||
else:
|
||||
self._name = ""
|
||||
return
|
||||
|
||||
def setHandle(self, theHandle):
|
||||
def setHandle(self, tHandle):
|
||||
"""Set the item handle, and ensure it is valid.
|
||||
"""
|
||||
if isHandle(theHandle):
|
||||
self._handle = theHandle
|
||||
if isHandle(tHandle):
|
||||
self._handle = tHandle
|
||||
else:
|
||||
self._handle = None
|
||||
return
|
||||
|
||||
def setParent(self, theParent):
|
||||
def setParent(self, pHandle):
|
||||
"""Set the parent handle, and ensure it is valid.
|
||||
"""
|
||||
if theParent is None:
|
||||
if pHandle is None:
|
||||
self._parent = None
|
||||
elif isHandle(theParent):
|
||||
self._parent = theParent
|
||||
elif isHandle(pHandle):
|
||||
self._parent = pHandle
|
||||
else:
|
||||
self._parent = None
|
||||
return
|
||||
|
||||
def setRoot(self, theRoot):
|
||||
def setRoot(self, rHandle):
|
||||
"""Set the root handle, and ensure it is valid.
|
||||
"""
|
||||
if theRoot is None:
|
||||
if rHandle is None:
|
||||
self._root = None
|
||||
elif isHandle(theRoot):
|
||||
self._root = theRoot
|
||||
elif isHandle(rHandle):
|
||||
self._root = rHandle
|
||||
else:
|
||||
self._root = None
|
||||
return
|
||||
|
||||
def setOrder(self, theOrder):
|
||||
def setOrder(self, order):
|
||||
"""Set the item order, and ensure that it is valid. This value
|
||||
is purely a meta value, and not actually used by novelWriter at
|
||||
the moment.
|
||||
"""
|
||||
self._order = checkInt(theOrder, 0)
|
||||
self._order = checkInt(order, 0)
|
||||
return
|
||||
|
||||
def setType(self, theType):
|
||||
def setType(self, itemType):
|
||||
"""Set the item type from either a proper nwItemType, or set it
|
||||
from a string representing an nwItemType.
|
||||
"""
|
||||
if isinstance(theType, nwItemType):
|
||||
self._type = theType
|
||||
elif isItemType(theType):
|
||||
self._type = nwItemType[theType]
|
||||
if isinstance(itemType, nwItemType):
|
||||
self._type = itemType
|
||||
elif isItemType(itemType):
|
||||
self._type = nwItemType[itemType]
|
||||
else:
|
||||
logger.error("Unrecognised item type '%s'", theType)
|
||||
logger.error("Unrecognised item type '%s'", itemType)
|
||||
self._type = nwItemType.NO_TYPE
|
||||
return
|
||||
|
||||
def setClass(self, theClass):
|
||||
def setClass(self, itemClass):
|
||||
"""Set the item class from either a proper nwItemClass, or set
|
||||
it from a string representing an nwItemClass.
|
||||
"""
|
||||
if isinstance(theClass, nwItemClass):
|
||||
self._class = theClass
|
||||
elif isItemClass(theClass):
|
||||
self._class = nwItemClass[theClass]
|
||||
if isinstance(itemClass, nwItemClass):
|
||||
self._class = itemClass
|
||||
elif isItemClass(itemClass):
|
||||
self._class = nwItemClass[itemClass]
|
||||
else:
|
||||
logger.error("Unrecognised item class '%s'", theClass)
|
||||
logger.error("Unrecognised item class '%s'", itemClass)
|
||||
self._class = nwItemClass.NO_CLASS
|
||||
return
|
||||
|
||||
def setLayout(self, theLayout):
|
||||
def setLayout(self, itemLayout):
|
||||
"""Set the item layout from either a proper nwItemLayout, or set
|
||||
it from a string representing an nwItemLayout.
|
||||
"""
|
||||
if isinstance(theLayout, nwItemLayout):
|
||||
self._layout = theLayout
|
||||
elif isItemLayout(theLayout):
|
||||
self._layout = nwItemLayout[theLayout]
|
||||
elif theLayout in nwLists.DEP_LAYOUT:
|
||||
if isinstance(itemLayout, nwItemLayout):
|
||||
self._layout = itemLayout
|
||||
elif isItemLayout(itemLayout):
|
||||
self._layout = nwItemLayout[itemLayout]
|
||||
elif itemLayout in nwLists.DEP_LAYOUT:
|
||||
self._layout = nwItemLayout.DOCUMENT
|
||||
else:
|
||||
logger.error("Unrecognised item layout '%s'", theLayout)
|
||||
logger.error("Unrecognised item layout '%s'", itemLayout)
|
||||
self._layout = nwItemLayout.NO_LAYOUT
|
||||
return
|
||||
|
||||
def setStatus(self, theStatus):
|
||||
def setStatus(self, itemStatus):
|
||||
"""Set the item status by looking it up in the valid status
|
||||
items of the current project.
|
||||
"""
|
||||
self._status = self.theProject.statusItems.checkEntry(theStatus)
|
||||
self._status = self.theProject.statusItems.check(itemStatus)
|
||||
return
|
||||
|
||||
def setImport(self, theImport):
|
||||
def setImport(self, itemImport):
|
||||
"""Set the item importance by looking it up in the valid import
|
||||
items of the current project.
|
||||
"""
|
||||
self._import = self.theProject.importItems.checkEntry(theImport)
|
||||
self._import = self.theProject.importItems.check(itemImport)
|
||||
return
|
||||
|
||||
def setExpanded(self, expState):
|
||||
def setExpanded(self, state):
|
||||
"""Set the expanded status of an item in the project tree.
|
||||
"""
|
||||
if isinstance(expState, str):
|
||||
self._expanded = (expState == str(True))
|
||||
if isinstance(state, str):
|
||||
self._expanded = (state == str(True))
|
||||
else:
|
||||
self._expanded = (expState is True)
|
||||
self._expanded = (state is True)
|
||||
return
|
||||
|
||||
def setExported(self, expState):
|
||||
def setExported(self, state):
|
||||
"""Set the export flag.
|
||||
"""
|
||||
if isinstance(expState, str):
|
||||
self._exported = (expState == str(True))
|
||||
if isinstance(state, str):
|
||||
self._exported = (state == str(True))
|
||||
else:
|
||||
self._exported = (expState is True)
|
||||
self._exported = (state is True)
|
||||
return
|
||||
|
||||
##
|
||||
# Set Document Meta Data
|
||||
##
|
||||
|
||||
def setCharCount(self, theCount):
|
||||
def setCharCount(self, count):
|
||||
"""Set the character count, and ensure that it is an integer.
|
||||
"""
|
||||
self._charCount = max(0, checkInt(theCount, 0))
|
||||
self._charCount = max(0, checkInt(count, 0))
|
||||
return
|
||||
|
||||
def setWordCount(self, theCount):
|
||||
def setWordCount(self, count):
|
||||
"""Set the word count, and ensure that it is an integer.
|
||||
"""
|
||||
self._wordCount = max(0, checkInt(theCount, 0))
|
||||
self._wordCount = max(0, checkInt(count, 0))
|
||||
return
|
||||
|
||||
def setParaCount(self, theCount):
|
||||
def setParaCount(self, count):
|
||||
"""Set the paragraph count, and ensure that it is an integer.
|
||||
"""
|
||||
self._paraCount = max(0, checkInt(theCount, 0))
|
||||
self._paraCount = max(0, checkInt(count, 0))
|
||||
return
|
||||
|
||||
def setCursorPos(self, thePosition):
|
||||
def setCursorPos(self, position):
|
||||
"""Set the cursor position, and ensure that it is an integer.
|
||||
"""
|
||||
self._cursorPos = max(0, checkInt(thePosition, 0))
|
||||
self._cursorPos = max(0, checkInt(position, 0))
|
||||
return
|
||||
|
||||
def saveInitialCount(self):
|
||||
|
||||
+63
-45
@@ -44,7 +44,7 @@ from novelwriter.enum import nwItemType, nwItemClass, nwItemLayout, nwAlert
|
||||
from novelwriter.error import logException
|
||||
from novelwriter.common import (
|
||||
checkString, checkBool, checkInt, isHandle, formatTimeStamp,
|
||||
makeFileNameSafe, hexToInt
|
||||
makeFileNameSafe, hexToInt, simplified
|
||||
)
|
||||
from novelwriter.constants import nwLists, trConst, nwFiles, nwLabels
|
||||
|
||||
@@ -217,16 +217,16 @@ class NWProject():
|
||||
}
|
||||
self.spellCheck = False
|
||||
self.autoOutline = True
|
||||
self.statusItems = NWStatus()
|
||||
self.statusItems.addEntry(self.tr("New"), (100, 100, 100))
|
||||
self.statusItems.addEntry(self.tr("Note"), (200, 50, 0))
|
||||
self.statusItems.addEntry(self.tr("Draft"), (200, 150, 0))
|
||||
self.statusItems.addEntry(self.tr("Finished"), (50, 200, 0))
|
||||
self.importItems = NWStatus()
|
||||
self.importItems.addEntry(self.tr("New"), (100, 100, 100))
|
||||
self.importItems.addEntry(self.tr("Minor"), (200, 50, 0))
|
||||
self.importItems.addEntry(self.tr("Major"), (200, 150, 0))
|
||||
self.importItems.addEntry(self.tr("Main"), (50, 200, 0))
|
||||
self.statusItems = NWStatus(NWStatus.STATUS)
|
||||
self.statusItems.write(None, self.tr("New"), (100, 100, 100))
|
||||
self.statusItems.write(None, self.tr("Note"), (200, 50, 0))
|
||||
self.statusItems.write(None, self.tr("Draft"), (200, 150, 0))
|
||||
self.statusItems.write(None, self.tr("Finished"), (50, 200, 0))
|
||||
self.importItems = NWStatus(NWStatus.IMPORT)
|
||||
self.importItems.write(None, self.tr("New"), (100, 100, 100))
|
||||
self.importItems.write(None, self.tr("Minor"), (200, 50, 0))
|
||||
self.importItems.write(None, self.tr("Major"), (200, 150, 0))
|
||||
self.importItems.write(None, self.tr("Main"), (50, 200, 0))
|
||||
self.lastEdited = None
|
||||
self.lastViewed = None
|
||||
self.lastWCount = 0
|
||||
@@ -266,6 +266,7 @@ class NWProject():
|
||||
logger.error("No project path set for the new project")
|
||||
return False
|
||||
|
||||
self.clearProject()
|
||||
if not self.setProjectPath(projPath, newProject=True):
|
||||
return False
|
||||
|
||||
@@ -532,14 +533,16 @@ class NWProject():
|
||||
if xItem.text is None:
|
||||
continue
|
||||
if xItem.tag == "name":
|
||||
logger.verbose("Working Title: '%s'", xItem.text)
|
||||
self.projName = xItem.text
|
||||
self.projName = checkString(simplified(xItem.text), "")
|
||||
logger.verbose("Working Title: '%s'", self.projName)
|
||||
elif xItem.tag == "title":
|
||||
logger.verbose("Title is '%s'", xItem.text)
|
||||
self.bookTitle = xItem.text
|
||||
self.bookTitle = checkString(simplified(xItem.text), "")
|
||||
logger.verbose("Title is '%s'", self.bookTitle)
|
||||
elif xItem.tag == "author":
|
||||
logger.verbose("Author: '%s'", xItem.text)
|
||||
self.bookAuthors.append(xItem.text)
|
||||
author = checkString(simplified(xItem.text), "")
|
||||
if author:
|
||||
self.bookAuthors.append(author)
|
||||
logger.verbose("Author: '%s'", author)
|
||||
elif xItem.tag == "saveCount":
|
||||
self.saveCount = checkInt(xItem.text, 0)
|
||||
elif xItem.tag == "autoCount":
|
||||
@@ -693,6 +696,8 @@ class NWProject():
|
||||
if len(aKey) > 0:
|
||||
self._packProjectValue(xTitleFmt, aKey, aValue)
|
||||
|
||||
# Save Status/Importance
|
||||
self.countStatus()
|
||||
xStatus = etree.SubElement(xSettings, "status")
|
||||
self.statusItems.packXML(xStatus)
|
||||
xStatus = etree.SubElement(xSettings, "importance")
|
||||
@@ -959,14 +964,14 @@ class NWProject():
|
||||
"""Set the project name (working title), This is the the title
|
||||
used for backup files etc.
|
||||
"""
|
||||
self.projName = projName.strip()
|
||||
self.projName = simplified(projName)
|
||||
self.setProjectChanged(True)
|
||||
return True
|
||||
|
||||
def setBookTitle(self, bookTitle):
|
||||
"""Set the book title, that is, the title to include in exports.
|
||||
"""
|
||||
self.bookTitle = bookTitle.strip()
|
||||
self.bookTitle = simplified(bookTitle)
|
||||
self.setProjectChanged(True)
|
||||
return True
|
||||
|
||||
@@ -978,7 +983,7 @@ class NWProject():
|
||||
|
||||
self.bookAuthors = []
|
||||
for bookAuthor in bookAuthors.splitlines():
|
||||
bookAuthor = bookAuthor.strip()
|
||||
bookAuthor = simplified(bookAuthor)
|
||||
if bookAuthor == "":
|
||||
continue
|
||||
self.bookAuthors.append(bookAuthor)
|
||||
@@ -1024,7 +1029,8 @@ class NWProject():
|
||||
if self.projSpell != theLang:
|
||||
self.projSpell = theLang
|
||||
self.setProjectChanged(True)
|
||||
return True
|
||||
return True
|
||||
return False
|
||||
|
||||
def setProjectLang(self, theLang):
|
||||
"""Set the project-specific language.
|
||||
@@ -1071,34 +1077,22 @@ class NWProject():
|
||||
self.setProjectChanged(True)
|
||||
return True
|
||||
|
||||
def setStatusColours(self, newCols):
|
||||
"""Update the list of novel file status flags. Also iterate
|
||||
through the project and replace keys that have been renamed.
|
||||
def setStatusColours(self, newCols, delCols):
|
||||
"""Update the list of novel file status flags.
|
||||
"""
|
||||
replaceMap = self.statusItems.setNewEntries(newCols)
|
||||
for nwItem in self.projTree:
|
||||
if nwItem.itemClass in nwLists.CLS_NOVEL:
|
||||
if nwItem.itemStatus in replaceMap:
|
||||
nwItem.setStatus(replaceMap[nwItem.itemStatus])
|
||||
self.setProjectChanged(True)
|
||||
return True
|
||||
return self._setStatusImport(newCols, delCols, self.statusItems)
|
||||
|
||||
def setImportColours(self, newCols):
|
||||
"""Update the list of note file importance flags. Also iterate
|
||||
through the project and replace keys that have been renamed.
|
||||
def setImportColours(self, newCols, delCols):
|
||||
"""Update the list of note file importance flags.
|
||||
"""
|
||||
replaceMap = self.importItems.setNewEntries(newCols)
|
||||
for nwItem in self.projTree:
|
||||
if nwItem.itemClass not in nwLists.CLS_NOVEL:
|
||||
if nwItem.itemImport in replaceMap:
|
||||
nwItem.setImport(replaceMap[nwItem.itemImport])
|
||||
self.setProjectChanged(True)
|
||||
return True
|
||||
return self._setStatusImport(newCols, delCols, self.importItems)
|
||||
|
||||
def setAutoReplace(self, autoReplace):
|
||||
"""Update the auto-replace dictionary.
|
||||
"""
|
||||
self.autoReplace = autoReplace
|
||||
self.autoReplace = {}
|
||||
for key, entry in autoReplace.items():
|
||||
self.autoReplace[key] = simplified(entry)
|
||||
self.setProjectChanged(True)
|
||||
return True
|
||||
|
||||
@@ -1107,7 +1101,9 @@ class NWProject():
|
||||
"""
|
||||
for valKey, valEntry in titleFormat.items():
|
||||
if valKey in self.titleFormat:
|
||||
self.titleFormat[valKey] = checkString(valEntry, self.titleFormat[valKey])
|
||||
self.titleFormat[valKey] = checkString(
|
||||
simplified(valEntry), self.titleFormat[valKey]
|
||||
)
|
||||
return True
|
||||
|
||||
def setProjectChanged(self, bValue):
|
||||
@@ -1213,9 +1209,9 @@ class NWProject():
|
||||
self.importItems.resetCounts()
|
||||
for nwItem in self.projTree:
|
||||
if nwItem.itemClass in nwLists.CLS_NOVEL:
|
||||
self.statusItems.countEntry(nwItem.itemStatus)
|
||||
self.statusItems.increment(nwItem.itemStatus)
|
||||
else:
|
||||
self.importItems.countEntry(nwItem.itemImport)
|
||||
self.importItems.increment(nwItem.itemImport)
|
||||
return
|
||||
|
||||
def localLookup(self, theWord):
|
||||
@@ -1229,6 +1225,28 @@ class NWProject():
|
||||
# Internal Functions
|
||||
##
|
||||
|
||||
def _setStatusImport(self, new, delete, target):
|
||||
"""Update the list of novel file status or importance flags, and
|
||||
delete those that have been requested deleted.
|
||||
"""
|
||||
if not (new or delete):
|
||||
return False
|
||||
|
||||
order = []
|
||||
for entry in new:
|
||||
key = entry.get("key", None)
|
||||
name = entry.get("name", "")
|
||||
cols = entry.get("cols", (100, 100, 100))
|
||||
if name:
|
||||
order.append(target.write(key, name, cols))
|
||||
|
||||
for key in delete:
|
||||
target.remove(key)
|
||||
|
||||
target.reorder(order)
|
||||
|
||||
return True
|
||||
|
||||
def _loadProjectLocalisation(self):
|
||||
"""Load the language data for the current project language.
|
||||
"""
|
||||
|
||||
+198
-109
@@ -4,7 +4,8 @@ novelWriter – Project Item Status Class
|
||||
Data class for the status/importance settings of a project item
|
||||
|
||||
File History:
|
||||
Created: 2019-05-19 [0.1.3]
|
||||
Created: 2019-05-19 [0.1.3]
|
||||
Rewritten: 2022-04-05 [1.7a0]
|
||||
|
||||
This file is a part of novelWriter
|
||||
Copyright 2018–2022, Veronica Berglyd Olsen
|
||||
@@ -23,6 +24,7 @@ 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 random
|
||||
import logging
|
||||
import novelwriter
|
||||
|
||||
@@ -30,134 +32,208 @@ from lxml import etree
|
||||
|
||||
from PyQt5.QtGui import QIcon, QPixmap, QColor
|
||||
|
||||
from novelwriter.common import checkInt
|
||||
from novelwriter.common import checkInt, minmax, simplified
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class NWStatus():
|
||||
|
||||
def __init__(self):
|
||||
STATUS = 1
|
||||
IMPORT = 2
|
||||
|
||||
def __init__(self, type):
|
||||
|
||||
self._type = type
|
||||
self._store = {}
|
||||
self._reverse = {}
|
||||
self._default = None
|
||||
|
||||
self._theLabels = []
|
||||
self._theColours = []
|
||||
self._theCounts = []
|
||||
self._theIcons = []
|
||||
self._theMap = {}
|
||||
self._theLength = 0
|
||||
self._theIndex = 0
|
||||
self._iconSize = novelwriter.CONFIG.pxInt(32)
|
||||
pixmap = QPixmap(self._iconSize, self._iconSize)
|
||||
pixmap.fill(QColor(100, 100, 100))
|
||||
self._defaultIcon = QIcon(pixmap)
|
||||
|
||||
if self._type == self.STATUS:
|
||||
self._prefix = "s"
|
||||
elif self._type == self.IMPORT:
|
||||
self._prefix = "i"
|
||||
else:
|
||||
raise Exception("This is a bug!")
|
||||
|
||||
return
|
||||
|
||||
def addEntry(self, theLabel, theColours):
|
||||
"""Add a status entry to the status object, but ensure it isn't
|
||||
a duplicate.
|
||||
def write(self, key, name, cols, count=None):
|
||||
"""Add or update a status entry. If the key is invalid, a new
|
||||
key is generated.
|
||||
"""
|
||||
theLabel = theLabel.strip()
|
||||
if self._getIndex(theLabel) is None:
|
||||
theIcon = QPixmap(self._iconSize, self._iconSize)
|
||||
theIcon.fill(QColor(*theColours))
|
||||
self._theIcons.append(QIcon(theIcon))
|
||||
self._theLabels.append(theLabel)
|
||||
self._theColours.append(theColours)
|
||||
self._theCounts.append(0)
|
||||
self._theMap[theLabel] = self._theLength
|
||||
self._theLength += 1
|
||||
if not self._isKey(key):
|
||||
key = self._newKey()
|
||||
if not isinstance(cols, tuple):
|
||||
cols = (100, 100, 100)
|
||||
if len(cols) != 3:
|
||||
cols = (100, 100, 100)
|
||||
|
||||
pixmap = QPixmap(self._iconSize, self._iconSize)
|
||||
pixmap.fill(QColor(*cols))
|
||||
|
||||
name = simplified(name)
|
||||
if count is None:
|
||||
count = self._store[key]["count"] if key in self._store else 0
|
||||
|
||||
self._store[key] = {
|
||||
"name": name,
|
||||
"icon": QIcon(pixmap),
|
||||
"cols": cols,
|
||||
"count": count,
|
||||
}
|
||||
self._reverse[name] = key
|
||||
|
||||
if self._default is None:
|
||||
self._default = key
|
||||
|
||||
return key
|
||||
|
||||
def remove(self, key):
|
||||
"""Remove an entry in the list, but not if the count is larger
|
||||
than 0.
|
||||
"""
|
||||
if key not in self._store:
|
||||
return False
|
||||
if self._store[key]["count"] > 0:
|
||||
return False
|
||||
|
||||
del self._reverse[self._store[key]["name"]]
|
||||
del self._store[key]
|
||||
|
||||
keys = list(self._store.keys())
|
||||
if key == self._default:
|
||||
if len(keys) > 0:
|
||||
self._default = keys[0]
|
||||
else:
|
||||
self._default = None
|
||||
|
||||
return True
|
||||
|
||||
def checkEntry(self, theStatus):
|
||||
"""Check if a status value is valid, and returns the safe
|
||||
reference to be used internally.
|
||||
def check(self, value):
|
||||
"""Check the key against the stored status names.
|
||||
"""
|
||||
if isinstance(theStatus, str):
|
||||
if self._getIndex(theStatus) is not None:
|
||||
return theStatus.strip()
|
||||
return self._theLabels[0]
|
||||
if self._isKey(value) and value in self._store:
|
||||
return value
|
||||
elif value in self._reverse:
|
||||
return self._reverse[value]
|
||||
elif self._default is not None:
|
||||
return self._default
|
||||
else:
|
||||
return ""
|
||||
|
||||
def getIcon(self, theLabel):
|
||||
"""Return the icon for the given status item.
|
||||
def name(self, key):
|
||||
"""Return the name associated with a given key.
|
||||
"""
|
||||
theIndex = self._getIndex(theLabel)
|
||||
if theIndex is not None:
|
||||
return self._theIcons[theIndex]
|
||||
return QIcon()
|
||||
if key in self._store:
|
||||
return self._store[key]["name"]
|
||||
elif self._default is not None:
|
||||
return self._store[self._default]["name"]
|
||||
else:
|
||||
return ""
|
||||
|
||||
def setNewEntries(self, newList):
|
||||
"""Update the list of entries after they have been modified by
|
||||
the GUI tool.
|
||||
def cols(self, key):
|
||||
"""Return the colours associated with a given key.
|
||||
"""
|
||||
replaceMap = {}
|
||||
if key in self._store:
|
||||
return self._store[key]["cols"]
|
||||
elif self._default is not None:
|
||||
return self._store[self._default]["cols"]
|
||||
else:
|
||||
return (100, 100, 100)
|
||||
|
||||
if newList is not None:
|
||||
self._theLabels = []
|
||||
self._theColours = []
|
||||
self._theCounts = []
|
||||
self._theIcons = []
|
||||
self._theMap = {}
|
||||
self._theLength = 0
|
||||
self._theIndex = 0
|
||||
def count(self, key):
|
||||
"""Return the count associated with a given key.
|
||||
"""
|
||||
if key in self._store:
|
||||
return self._store[key]["count"]
|
||||
elif self._default is not None:
|
||||
return self._store[self._default]["count"]
|
||||
else:
|
||||
return 0
|
||||
|
||||
for nName, nR, nG, nB, oName in newList:
|
||||
self.addEntry(nName, (nR, nG, nB))
|
||||
if nName != oName and oName is not None:
|
||||
replaceMap[oName] = nName
|
||||
def icon(self, key):
|
||||
"""Return the icon associated with a given key.
|
||||
"""
|
||||
if key in self._store:
|
||||
return self._store[key]["icon"]
|
||||
elif self._default is not None:
|
||||
return self._store[self._default]["icon"]
|
||||
else:
|
||||
return self._defaultIcon
|
||||
|
||||
return replaceMap
|
||||
def reorder(self, order):
|
||||
"""Reorder the items according to list.
|
||||
"""
|
||||
if len(order) != len(self._store):
|
||||
logger.error("Length mismatch between new and old order")
|
||||
return False
|
||||
|
||||
if order == list(self._store.keys()):
|
||||
return False
|
||||
|
||||
store = {}
|
||||
for key in order:
|
||||
if key in self._store:
|
||||
store[key] = self._store[key]
|
||||
else:
|
||||
logger.error("Unknown key '%s' in order", key)
|
||||
return False
|
||||
|
||||
self._store = store
|
||||
|
||||
return True
|
||||
|
||||
def resetCounts(self):
|
||||
"""Clear the counts of references to the status entries.
|
||||
"""
|
||||
self._theCounts = [0]*self._theLength
|
||||
for key in self._store:
|
||||
self._store[key]["count"] = 0
|
||||
return
|
||||
|
||||
def countEntry(self, theLabel):
|
||||
"""Increment the counter for a given label. This should be used
|
||||
together with resetCounts in a loop over project items.
|
||||
def increment(self, key):
|
||||
"""Increment the counter for a given entry.
|
||||
"""
|
||||
theIndex = self._getIndex(theLabel)
|
||||
if theIndex is not None:
|
||||
self._theCounts[theIndex] += 1
|
||||
if key in self._store:
|
||||
self._store[key]["count"] += 1
|
||||
return
|
||||
|
||||
def packXML(self, xParent):
|
||||
"""Pack the status entries into an XML object for saving to the
|
||||
main project file.
|
||||
"""
|
||||
for n in range(self._theLength):
|
||||
for key, data in self._store.items():
|
||||
xSub = etree.SubElement(xParent, "entry", attrib={
|
||||
"red": str(self._theColours[n][0]),
|
||||
"green": str(self._theColours[n][1]),
|
||||
"blue": str(self._theColours[n][2]),
|
||||
"key": key,
|
||||
"count": str(data["count"]),
|
||||
"red": str(data["cols"][0]),
|
||||
"green": str(data["cols"][1]),
|
||||
"blue": str(data["cols"][2]),
|
||||
})
|
||||
xSub.text = self._theLabels[n]
|
||||
xSub.text = data["name"]
|
||||
|
||||
return True
|
||||
|
||||
def unpackXML(self, xParent):
|
||||
"""Unpack an XML tree and set the class values.
|
||||
"""
|
||||
theLabels = []
|
||||
theColours = []
|
||||
self._store = {}
|
||||
self._reverse = {}
|
||||
self._default = None
|
||||
|
||||
for xChild in xParent:
|
||||
theLabels.append(xChild.text)
|
||||
cR = checkInt(xChild.attrib.get("red", 0), 0, False)
|
||||
cG = checkInt(xChild.attrib.get("green", 0), 0, False)
|
||||
cB = checkInt(xChild.attrib.get("blue", 0), 0, False)
|
||||
theColours.append((cR, cG, cB))
|
||||
|
||||
if len(theLabels) > 0:
|
||||
self._theLabels = []
|
||||
self._theColours = []
|
||||
self._theCounts = []
|
||||
self._theIcons = []
|
||||
self._theMap = {}
|
||||
self._theLength = 0
|
||||
self._theIndex = 0
|
||||
|
||||
for n in range(len(theLabels)):
|
||||
self.addEntry(theLabels[n], theColours[n])
|
||||
key = xChild.attrib.get("key", None)
|
||||
name = xChild.text.strip()
|
||||
count = max(checkInt(xChild.attrib.get("count", 0), 0), 0)
|
||||
red = minmax(checkInt(xChild.attrib.get("red", 100), 100), 0, 255)
|
||||
green = minmax(checkInt(xChild.attrib.get("green", 100), 100), 0, 255)
|
||||
blue = minmax(checkInt(xChild.attrib.get("blue", 100), 100), 0, 255)
|
||||
self.write(key, name, (red, green, blue), count)
|
||||
|
||||
return True
|
||||
|
||||
@@ -165,39 +241,52 @@ class NWStatus():
|
||||
# Internal Functions
|
||||
##
|
||||
|
||||
def _getIndex(self, theLabel):
|
||||
"""Look up a status entry in the object lists, and return it if
|
||||
it exists.
|
||||
def _newKey(self):
|
||||
"""Generate a new key for a status flag. This method is
|
||||
recursive, but should only fail if there is an issue with the
|
||||
random number generator or the user has added a lot of status
|
||||
flags. The Python recursion limit is given the job to handle
|
||||
the extreme case and will cause an app crash.
|
||||
"""
|
||||
if theLabel is None:
|
||||
return None
|
||||
return self._theMap.get(theLabel.strip(), None)
|
||||
key = f"{self._prefix}{random.getrandbits(24):06x}"
|
||||
if key in self._store:
|
||||
key = self._newKey()
|
||||
return key
|
||||
|
||||
def _isKey(self, value):
|
||||
"""Check if a value is a key or not.
|
||||
"""
|
||||
if not isinstance(value, str):
|
||||
return False
|
||||
if len(value) != 7:
|
||||
return False
|
||||
if value[0] != self._prefix:
|
||||
return False
|
||||
for c in value[1:]:
|
||||
if c not in "0123456789abcdef":
|
||||
return False
|
||||
return True
|
||||
|
||||
##
|
||||
# Iterator Bits
|
||||
##
|
||||
|
||||
def __getitem__(self, n):
|
||||
"""Return an entry by its index.
|
||||
"""
|
||||
if n >= 0 and n < self._theLength:
|
||||
return self._theLabels[n], self._theColours[n], self._theCounts[n], self._theIcons[n]
|
||||
return None, None, None, QIcon()
|
||||
def __len__(self):
|
||||
return len(self._store)
|
||||
|
||||
def __getitem__(self, key):
|
||||
return self._store[key]
|
||||
|
||||
def __iter__(self):
|
||||
"""Initialise the iterator.
|
||||
"""
|
||||
self._theIndex = 0
|
||||
return self
|
||||
return iter(self._store)
|
||||
|
||||
def __next__(self):
|
||||
"""Return the next entry for the iterator.
|
||||
"""
|
||||
if self._theIndex < self._theLength:
|
||||
theLabel, theColour, theCount, theIcon = self.__getitem__(self._theIndex)
|
||||
self._theIndex += 1
|
||||
return theLabel, theColour, theCount, theIcon
|
||||
else:
|
||||
raise StopIteration
|
||||
def keys(self):
|
||||
return self._store.keys()
|
||||
|
||||
def items(self):
|
||||
return self._store.items()
|
||||
|
||||
def values(self):
|
||||
return self._store.values()
|
||||
|
||||
# END Class NWStatus
|
||||
|
||||
@@ -75,11 +75,20 @@ class GuiItemEditor(QDialog):
|
||||
self.editStatus = QComboBox()
|
||||
self.editStatus.setMinimumWidth(mVd)
|
||||
if self.theItem.itemClass in nwLists.CLS_NOVEL:
|
||||
for sLabel, _, _, sIcon in self.theProject.statusItems:
|
||||
self.editStatus.addItem(sIcon, sLabel, sLabel)
|
||||
for key, entry in self.theProject.statusItems.items():
|
||||
self.editStatus.addItem(entry["icon"], entry["name"], key)
|
||||
|
||||
index = self.editStatus.findData(self.theItem.itemStatus)
|
||||
if index != -1:
|
||||
self.editStatus.setCurrentIndex(index)
|
||||
|
||||
else:
|
||||
for sLabel, _, _, sIcon in self.theProject.importItems:
|
||||
self.editStatus.addItem(sIcon, sLabel, sLabel)
|
||||
for key, entry in self.theProject.importItems.items():
|
||||
self.editStatus.addItem(entry["icon"], entry["name"], key)
|
||||
|
||||
index = self.editStatus.findData(self.theItem.itemImport)
|
||||
if index != -1:
|
||||
self.editStatus.setCurrentIndex(index)
|
||||
|
||||
# Item Layout
|
||||
self.editLayout = QComboBox()
|
||||
@@ -97,6 +106,10 @@ class GuiItemEditor(QDialog):
|
||||
if itemLayout in validLayouts:
|
||||
self.editLayout.addItem(trConst(nwLabels.LAYOUT_NAME[itemLayout]), itemLayout)
|
||||
|
||||
index = self.editLayout.findData(self.theItem.itemLayout)
|
||||
if index != -1:
|
||||
self.editLayout.setCurrentIndex(index)
|
||||
|
||||
# Export Switch
|
||||
self.textExport = QLabel(self.tr("Include when building project"))
|
||||
self.editExport = QSwitch()
|
||||
@@ -116,15 +129,6 @@ class GuiItemEditor(QDialog):
|
||||
self.editName.setText(self.theItem.itemName)
|
||||
self.editName.selectAll()
|
||||
|
||||
currStatus, _ = self.theItem.getImportStatus()
|
||||
statusIdx = self.editStatus.findData(currStatus)
|
||||
if statusIdx != -1:
|
||||
self.editStatus.setCurrentIndex(statusIdx)
|
||||
|
||||
layoutIdx = self.editLayout.findData(self.theItem.itemLayout)
|
||||
if layoutIdx != -1:
|
||||
self.editLayout.setCurrentIndex(layoutIdx)
|
||||
|
||||
##
|
||||
# Assemble
|
||||
##
|
||||
|
||||
@@ -35,6 +35,7 @@ from PyQt5.QtWidgets import (
|
||||
)
|
||||
|
||||
from novelwriter.enum import nwAlert
|
||||
from novelwriter.common import simplified
|
||||
from novelwriter.gui.custom import QSwitch, PagedDialog, QConfigLayout
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -81,6 +82,9 @@ class GuiProjectSettings(PagedDialog):
|
||||
self.buttonBox.rejected.connect(self._doClose)
|
||||
self.addControls(self.buttonBox)
|
||||
|
||||
# Flags
|
||||
self.spellChanged = False
|
||||
|
||||
logger.debug("GuiProjectSettings initialisation complete")
|
||||
|
||||
return
|
||||
@@ -103,16 +107,18 @@ class GuiProjectSettings(PagedDialog):
|
||||
self.theProject.setProjectName(projName)
|
||||
self.theProject.setBookTitle(bookTitle)
|
||||
self.theProject.setBookAuthors(bookAuthors)
|
||||
self.theProject.setSpellLang(spellLang)
|
||||
self.theProject.setProjBackup(doBackup)
|
||||
|
||||
# Remember this as updating spell dictionary can be expensive
|
||||
self.spellChanged = self.theProject.setSpellLang(spellLang)
|
||||
|
||||
if self.tabStatus.colChanged:
|
||||
statusCol = self.tabStatus.getNewList()
|
||||
self.theProject.setStatusColours(statusCol)
|
||||
newList, delList = self.tabStatus.getNewList()
|
||||
self.theProject.setStatusColours(newList, delList)
|
||||
|
||||
if self.tabImport.colChanged:
|
||||
importCol = self.tabImport.getNewList()
|
||||
self.theProject.setImportColours(importCol)
|
||||
newList, delList = self.tabImport.getNewList()
|
||||
self.theProject.setImportColours(newList, delList)
|
||||
|
||||
if self.tabStatus.colChanged or self.tabImport.colChanged:
|
||||
self.theParent.rebuildTrees()
|
||||
@@ -245,6 +251,10 @@ class GuiProjectEditStatus(QWidget):
|
||||
COL_LABEL = 0
|
||||
COL_USAGE = 1
|
||||
|
||||
KEY_ROLE = Qt.UserRole
|
||||
COL_ROLE = Qt.UserRole + 1
|
||||
NUM_ROLE = Qt.UserRole + 2
|
||||
|
||||
def __init__(self, theParent, theProject, isStatus):
|
||||
QWidget.__init__(self, theParent)
|
||||
|
||||
@@ -267,10 +277,9 @@ class GuiProjectEditStatus(QWidget):
|
||||
self.optState.getInt("GuiProjectSettings", colSetting, 130)
|
||||
)
|
||||
|
||||
self.colData = []
|
||||
self.colCounts = []
|
||||
self.colDeleted = []
|
||||
self.colChanged = False
|
||||
self.selColour = None
|
||||
self.selColour = QColor(100, 100, 100)
|
||||
|
||||
self.iPx = self.theTheme.baseIconSize
|
||||
|
||||
@@ -285,8 +294,8 @@ class GuiProjectEditStatus(QWidget):
|
||||
self.listBox.setColumnWidth(self.COL_LABEL, wCol0)
|
||||
self.listBox.setIndentation(0)
|
||||
|
||||
for iName, iCol, nUse, _ in self.theStatus:
|
||||
self._addItem(iName, iCol, iName, nUse)
|
||||
for key, entry in self.theStatus.items():
|
||||
self._addItem(key, entry["name"], entry["cols"], entry["count"])
|
||||
|
||||
# List Controls
|
||||
# =============
|
||||
@@ -297,6 +306,12 @@ class GuiProjectEditStatus(QWidget):
|
||||
self.delButton = QPushButton(self.theTheme.getIcon("remove"), "")
|
||||
self.delButton.clicked.connect(self._delItem)
|
||||
|
||||
self.upButton = QPushButton(self.theTheme.getIcon("up"), "")
|
||||
self.upButton.clicked.connect(lambda: self._moveItem(-1))
|
||||
|
||||
self.dnButton = QPushButton(self.theTheme.getIcon("down"), "")
|
||||
self.dnButton.clicked.connect(lambda: self._moveItem(1))
|
||||
|
||||
# Edit Form
|
||||
# =========
|
||||
|
||||
@@ -306,7 +321,7 @@ class GuiProjectEditStatus(QWidget):
|
||||
self.editName.setPlaceholderText(self.tr("Select item to edit"))
|
||||
|
||||
self.colPixmap = QPixmap(self.iPx, self.iPx)
|
||||
self.colPixmap.fill(QColor(120, 120, 120))
|
||||
self.colPixmap.fill(QColor(100, 100, 100))
|
||||
self.colButton = QPushButton(QIcon(self.colPixmap), self.tr("Colour"))
|
||||
self.colButton.setIconSize(self.colPixmap.rect().size())
|
||||
self.colButton.clicked.connect(self._selectColour)
|
||||
@@ -320,6 +335,8 @@ class GuiProjectEditStatus(QWidget):
|
||||
self.listControls = QVBoxLayout()
|
||||
self.listControls.addWidget(self.addButton)
|
||||
self.listControls.addWidget(self.delButton)
|
||||
self.listControls.addWidget(self.upButton)
|
||||
self.listControls.addWidget(self.dnButton)
|
||||
self.listControls.addStretch(1)
|
||||
|
||||
self.editBox = QHBoxLayout()
|
||||
@@ -349,12 +366,15 @@ class GuiProjectEditStatus(QWidget):
|
||||
if self.colChanged:
|
||||
newList = []
|
||||
for n in range(self.listBox.topLevelItemCount()):
|
||||
nItem = self.listBox.topLevelItem(n)
|
||||
nIdx = nItem.data(self.COL_LABEL, Qt.UserRole)
|
||||
newList.append(self.colData[nIdx])
|
||||
return newList
|
||||
item = self.listBox.topLevelItem(n)
|
||||
newList.append({
|
||||
"key": item.data(self.COL_LABEL, self.KEY_ROLE),
|
||||
"name": item.text(self.COL_LABEL),
|
||||
"cols": item.data(self.COL_LABEL, self.COL_ROLE),
|
||||
})
|
||||
return newList, self.colDeleted
|
||||
|
||||
return None
|
||||
return [], []
|
||||
|
||||
##
|
||||
# User Actions
|
||||
@@ -369,16 +389,16 @@ class GuiProjectEditStatus(QWidget):
|
||||
)
|
||||
if newCol.isValid():
|
||||
self.selColour = newCol
|
||||
colPixmap = QPixmap(self.iPx, self.iPx)
|
||||
colPixmap.fill(newCol)
|
||||
self.colButton.setIcon(QIcon(colPixmap))
|
||||
self.colButton.setIconSize(colPixmap.rect().size())
|
||||
pixmap = QPixmap(self.iPx, self.iPx)
|
||||
pixmap.fill(newCol)
|
||||
self.colButton.setIcon(QIcon(pixmap))
|
||||
self.colButton.setIconSize(pixmap.rect().size())
|
||||
return
|
||||
|
||||
def _newItem(self):
|
||||
"""Create a new status item.
|
||||
"""
|
||||
newItem = self._addItem(self.tr("New Item"), (0, 0, 0), None, 0)
|
||||
newItem = self._addItem(None, self.tr("New Item"), (100, 100, 100), 0)
|
||||
newItem.setBackground(self.COL_LABEL, QBrush(QColor(0, 255, 0, 70)))
|
||||
newItem.setBackground(self.COL_USAGE, QBrush(QColor(0, 255, 0, 70)))
|
||||
self.colChanged = True
|
||||
@@ -390,14 +410,14 @@ class GuiProjectEditStatus(QWidget):
|
||||
selItem = self._getSelectedItem()
|
||||
if selItem is not None:
|
||||
iRow = self.listBox.indexOfTopLevelItem(selItem)
|
||||
selIdx = selItem.data(self.COL_LABEL, Qt.UserRole)
|
||||
if self.colCounts[selIdx] == 0:
|
||||
self.listBox.takeTopLevelItem(iRow)
|
||||
self.colChanged = True
|
||||
else:
|
||||
if selItem.data(self.COL_LABEL, self.NUM_ROLE) > 0:
|
||||
self.theParent.makeAlert(self.tr(
|
||||
"Cannot delete a status item that is in use."
|
||||
), nwAlert.ERROR)
|
||||
else:
|
||||
self.listBox.takeTopLevelItem(iRow)
|
||||
self.colDeleted.append(selItem.data(self.COL_LABEL, self.KEY_ROLE))
|
||||
self.colChanged = True
|
||||
return
|
||||
|
||||
def _saveItem(self):
|
||||
@@ -405,53 +425,75 @@ class GuiProjectEditStatus(QWidget):
|
||||
"""
|
||||
selItem = self._getSelectedItem()
|
||||
if selItem is not None:
|
||||
selIdx = selItem.data(self.COL_LABEL, Qt.UserRole)
|
||||
self.colData[selIdx] = (
|
||||
self.editName.text().strip(),
|
||||
self.selColour.red(),
|
||||
self.selColour.green(),
|
||||
self.selColour.blue(),
|
||||
self.colData[selIdx][4]
|
||||
)
|
||||
selItem.setText(self.COL_LABEL, self.colData[selIdx][0])
|
||||
selItem.setText(self.COL_USAGE, self._usageString(self.colCounts[selIdx]))
|
||||
selItem.setText(self.COL_LABEL, simplified(self.editName.text()))
|
||||
selItem.setIcon(self.COL_LABEL, self.colButton.icon())
|
||||
selItem.setData(self.COL_LABEL, self.COL_ROLE, (
|
||||
self.selColour.red(), self.selColour.green(), self.selColour.blue()
|
||||
))
|
||||
self.editName.setEnabled(False)
|
||||
self.colChanged = True
|
||||
|
||||
return
|
||||
|
||||
def _addItem(self, iName, iCol, oName, nUse):
|
||||
def _addItem(self, key, name, cols, count):
|
||||
"""Add a status item to the list.
|
||||
"""
|
||||
newIcon = QPixmap(self.iPx, self.iPx)
|
||||
newIcon.fill(QColor(*iCol))
|
||||
newItem = QTreeWidgetItem()
|
||||
newItem.setText(self.COL_LABEL, iName)
|
||||
newItem.setText(self.COL_USAGE, self._usageString(nUse))
|
||||
newItem.setIcon(self.COL_LABEL, QIcon(newIcon))
|
||||
newItem.setData(self.COL_LABEL, Qt.UserRole, len(self.colData))
|
||||
self.listBox.addTopLevelItem(newItem)
|
||||
self.colData.append((iName, iCol[0], iCol[1], iCol[2], oName))
|
||||
self.colCounts.append(nUse)
|
||||
return newItem
|
||||
pixmap = QPixmap(self.iPx, self.iPx)
|
||||
pixmap.fill(QColor(*cols))
|
||||
|
||||
item = QTreeWidgetItem()
|
||||
item.setText(self.COL_LABEL, name)
|
||||
item.setIcon(self.COL_LABEL, QIcon(pixmap))
|
||||
item.setData(self.COL_LABEL, self.KEY_ROLE, key)
|
||||
item.setData(self.COL_LABEL, self.COL_ROLE, cols)
|
||||
item.setData(self.COL_LABEL, self.NUM_ROLE, count)
|
||||
item.setText(self.COL_USAGE, self._usageString(count))
|
||||
|
||||
self.listBox.addTopLevelItem(item)
|
||||
|
||||
return item
|
||||
|
||||
def _moveItem(self, step):
|
||||
"""Move and item up or down step.
|
||||
"""
|
||||
selItem = self._getSelectedItem()
|
||||
if selItem is None:
|
||||
return
|
||||
|
||||
tIndex = self.listBox.indexOfTopLevelItem(selItem)
|
||||
nChild = self.listBox.topLevelItemCount()
|
||||
nIndex = tIndex + step
|
||||
if nIndex < 0 or nIndex >= nChild:
|
||||
return
|
||||
|
||||
cItem = self.listBox.takeTopLevelItem(tIndex)
|
||||
self.listBox.insertTopLevelItem(nIndex, cItem)
|
||||
self.listBox.clearSelection()
|
||||
|
||||
cItem.setSelected(True)
|
||||
self.colChanged = True
|
||||
|
||||
return
|
||||
|
||||
def _selectedItem(self):
|
||||
"""Extract the info of a selected item and populate the settings
|
||||
boxes and button.
|
||||
"""
|
||||
selItem = self._getSelectedItem()
|
||||
if selItem is not None:
|
||||
selIdx = selItem.data(self.COL_LABEL, Qt.UserRole)
|
||||
selVal = self.colData[selIdx]
|
||||
self.selColour = QColor(selVal[1], selVal[2], selVal[3])
|
||||
newIcon = QPixmap(self.iPx, self.iPx)
|
||||
newIcon.fill(self.selColour)
|
||||
self.editName.setText(selVal[0])
|
||||
self.colButton.setIcon(QIcon(newIcon))
|
||||
self.editName.setEnabled(True)
|
||||
self.editName.selectAll()
|
||||
self.editName.setFocus()
|
||||
if selItem is None:
|
||||
return
|
||||
|
||||
cols = selItem.data(self.COL_LABEL, self.COL_ROLE)
|
||||
name = selItem.text(self.COL_LABEL)
|
||||
|
||||
pixmap = QPixmap(self.iPx, self.iPx)
|
||||
pixmap.fill(QColor(*cols))
|
||||
self.selColour = QColor(*cols)
|
||||
self.editName.setText(name)
|
||||
self.colButton.setIcon(QIcon(pixmap))
|
||||
self.editName.setEnabled(True)
|
||||
self.editName.selectAll()
|
||||
self.editName.setFocus()
|
||||
|
||||
return
|
||||
|
||||
@@ -467,12 +509,6 @@ class GuiProjectEditStatus(QWidget):
|
||||
return selItem[0]
|
||||
return None
|
||||
|
||||
def _rowsMoved(self):
|
||||
"""A row has been moved, so set the changed flag.
|
||||
"""
|
||||
self.colChanged = True
|
||||
return
|
||||
|
||||
def _usageString(self, nUse):
|
||||
"""Generate usage string.
|
||||
"""
|
||||
|
||||
@@ -214,6 +214,11 @@ class GuiItemDetails(QWidget):
|
||||
|
||||
return
|
||||
|
||||
def refreshDetails(self):
|
||||
"""Reload the content of the details panel.
|
||||
"""
|
||||
self.updateViewBox(self._itemHandle)
|
||||
|
||||
def updateViewBox(self, tHandle):
|
||||
"""Populate the details box from a given handle.
|
||||
"""
|
||||
|
||||
@@ -291,8 +291,10 @@ class GuiOutlineDetails(QScrollArea):
|
||||
self.titleLabel.setText("<b>%s</b>" % self.tr("Title"))
|
||||
self.titleValue.setText(novIdx["title"])
|
||||
|
||||
itemStatus, _ = nwItem.getImportStatus()
|
||||
|
||||
self.fileValue.setText(nwItem.itemName)
|
||||
self.itemValue.setText(nwItem.itemStatus)
|
||||
self.itemValue.setText(itemStatus)
|
||||
|
||||
cC = checkInt(novIdx["cCount"], 0)
|
||||
wC = checkInt(novIdx["wCount"], 0)
|
||||
|
||||
@@ -469,7 +469,7 @@ class GuiIcons:
|
||||
"delete", "close", "done", "clear", "save", "add", "remove",
|
||||
"search", "search_replace", "edit", "check", "cross", "hash",
|
||||
"maximise", "minimise", "refresh", "reference", "backward",
|
||||
"forward", "settings",
|
||||
"forward", "settings", "up", "down",
|
||||
|
||||
# Switches
|
||||
"sticky-on", "sticky-off",
|
||||
|
||||
@@ -545,7 +545,7 @@ class GuiMain(QMainWindow):
|
||||
if self.theIndex.indexBroken:
|
||||
self.makeAlert(self.tr(
|
||||
"The project index is outdated or broken. Rebuilding index."
|
||||
), nwAlert.WARN)
|
||||
), nwAlert.INFO)
|
||||
self.rebuildIndex()
|
||||
|
||||
# Make sure the changed status is set to false on things opened
|
||||
@@ -1002,7 +1002,9 @@ class GuiMain(QMainWindow):
|
||||
|
||||
if dlgProj.result() == QDialog.Accepted:
|
||||
logger.debug("Applying new project settings")
|
||||
self.docEditor.setDictionaries()
|
||||
if dlgProj.spellChanged:
|
||||
self.docEditor.setDictionaries()
|
||||
self.treeMeta.refreshDetails()
|
||||
self._updateWindowTitle(self.theProject.projName)
|
||||
|
||||
return True
|
||||
|
||||
Reference in New Issue
Block a user