Fix various issues in the code (#1197)

This commit is contained in:
Veronica Berglyd Olsen
2022-10-20 12:30:00 +02:00
committed by GitHub
20 changed files with 241 additions and 268 deletions
+99 -127
View File
@@ -216,25 +216,25 @@ def formatInt(value):
return str(value)
def formatTimeStamp(theTime, fileSafe=False):
def formatTimeStamp(value, fileSafe=False):
"""Take a number (on the format returned by time.time()) and convert
it to a timestamp string.
"""
if fileSafe:
return datetime.fromtimestamp(theTime).strftime(nwConst.FMT_FSTAMP)
return datetime.fromtimestamp(value).strftime(nwConst.FMT_FSTAMP)
else:
return datetime.fromtimestamp(theTime).strftime(nwConst.FMT_TSTAMP)
return datetime.fromtimestamp(value).strftime(nwConst.FMT_TSTAMP)
def formatTime(tS):
def formatTime(t):
"""Format a time in seconds in HH:MM:SS format or d-HH:MM:SS format
if a full day or longer.
"""
if isinstance(tS, int):
if tS >= 86400:
return f"{tS//86400:d}-{tS%86400//3600:02d}:{tS%3600//60:02d}:{tS%60:02d}"
if isinstance(t, int):
if t >= 86400:
return f"{t//86400:d}-{t%86400//3600:02d}:{t%3600//60:02d}:{t%60:02d}"
else:
return f"{tS//3600:02d}:{tS%3600//60:02d}:{tS%60:02d}"
return f"{t//3600:02d}:{t%3600//60:02d}:{t%60:02d}"
return "ERROR"
@@ -276,111 +276,111 @@ def splitVersionNumber(value):
return vMajor, vMinor, vPatch, vInt
def transferCase(theSource, theTarget):
def transferCase(source, target):
"""Transfers the case of the source word to the target word. This
will consider all upper or lower, and first char capitalisation.
"""
theResult = theTarget
theResult = target
if not isinstance(theSource, str) or not isinstance(theTarget, str):
if not isinstance(source, str) or not isinstance(target, str):
return theResult
if len(theTarget) < 1 or len(theSource) < 1:
if len(target) < 1 or len(source) < 1:
return theResult
if theSource.istitle():
theResult = theTarget.title()
if source.istitle():
theResult = target.title()
if theSource.isupper():
theResult = theTarget.upper()
elif theSource.islower():
theResult = theTarget.lower()
if source.isupper():
theResult = target.upper()
elif source.islower():
theResult = target.lower()
return theResult
def fuzzyTime(secDiff):
def fuzzyTime(seconds):
"""Converts a time difference in seconds into a fuzzy time string.
"""
if secDiff < 0:
if seconds < 0:
return QCoreApplication.translate(
"Common", "in the future"
)
elif secDiff < 30:
elif seconds < 30:
return QCoreApplication.translate(
"Common", "just now"
)
elif secDiff < 90:
elif seconds < 90:
return QCoreApplication.translate(
"Common", "a minute ago"
)
elif secDiff < 3300: # 55 minutes
elif seconds < 3300: # 55 minutes
return QCoreApplication.translate(
"Common", "{0} minutes ago"
).format(int(round(secDiff/60)))
elif secDiff < 5400: # 90 minutes
).format(int(round(seconds/60)))
elif seconds < 5400: # 90 minutes
return QCoreApplication.translate(
"Common", "an hour ago"
)
elif secDiff < 84600: # 23.5 hours
elif seconds < 84600: # 23.5 hours
return QCoreApplication.translate(
"Common", "{0} hours ago"
).format(int(round(secDiff/3600)))
elif secDiff < 129600: # 1.5 days
).format(int(round(seconds/3600)))
elif seconds < 129600: # 1.5 days
return QCoreApplication.translate(
"Common", "a day ago"
)
elif secDiff < 561600: # 6.5 days
elif seconds < 561600: # 6.5 days
return QCoreApplication.translate(
"Common", "{0} days ago"
).format(int(round(secDiff/86400)))
elif secDiff < 907200: # 10.5 days
).format(int(round(seconds/86400)))
elif seconds < 907200: # 10.5 days
return QCoreApplication.translate(
"Common", "a week ago"
)
elif secDiff < 2419200: # 28 days
elif seconds < 2419200: # 28 days
return QCoreApplication.translate(
"Common", "{0} weeks ago"
).format(int(round(secDiff/604800)))
elif secDiff < 3888000: # 45 days
).format(int(round(seconds/604800)))
elif seconds < 3888000: # 45 days
return QCoreApplication.translate(
"Common", "a month ago"
)
elif secDiff < 29808000: # 345 days
elif seconds < 29808000: # 345 days
return QCoreApplication.translate(
"Common", "{0} months ago"
).format(int(round(secDiff/2592000)))
elif secDiff < 47336400: # 1.5 years
).format(int(round(seconds/2592000)))
elif seconds < 47336400: # 1.5 years
return QCoreApplication.translate(
"Common", "a year ago"
)
else:
return QCoreApplication.translate(
"Common", "{0} years ago"
).format(int(round(secDiff/31557600)))
).format(int(round(seconds/31557600)))
def numberToRoman(numVal, toLower=False):
def numberToRoman(value, toLower=False):
"""Convert an integer to a Roman number.
"""
if not isinstance(numVal, int):
if not isinstance(value, int):
return "NAN"
if numVal < 1 or numVal > 4999:
if value < 1 or value > 4999:
return "OOR"
theValues = [
lookup = [
(1000, "M"), (900, "CM"), (500, "D"), (400, "CD"), (100, "C"), (90, "XC"),
(50, "L"), (40, "XL"), (10, "X"), (9, "IX"), (5, "V"), (4, "IV"), (1, "I"),
]
romNum = ""
for theDiv, theSym in theValues:
n = numVal//theDiv
romNum += n*theSym
numVal -= n*theDiv
if numVal <= 0:
roman = ""
for divisor, symbol in lookup:
n = value//divisor
roman += n*symbol
value -= n*divisor
if value <= 0:
break
return romNum.lower() if toLower else romNum
return roman.lower() if toLower else roman
# =============================================================================================== #
@@ -438,70 +438,70 @@ def jsonEncode(data, n=0, nmax=0):
# File and File System Functions
# =============================================================================================== #
def readTextFile(filePath):
def readTextFile(path):
"""Read the content of a text file in a robust manner.
"""
if not os.path.isfile(filePath):
if not os.path.isfile(path):
return ""
fileText = ""
text = ""
try:
with open(filePath, mode="r", encoding="utf-8") as inFile:
fileText = inFile.read()
with open(path, mode="r", encoding="utf-8") as inFile:
text = inFile.read()
except Exception:
logger.error("Could not read file: %s", filePath)
logger.error("Could not read file: %s", path)
logException()
return ""
return fileText
return text
def makeFileNameSafe(value):
"""Returns a filename safe string of the value.
"""
cleanName = ""
clean = ""
for c in str(value).strip():
if c.isalpha() or c.isdigit() or c == " ":
cleanName += c
return cleanName
clean += c
return clean
def ensureFolder(dirPath, parentPath=None, errLog=None):
def ensureFolder(path, parent=None, errLog=None):
"""Make sure a folder exists, and if it doesn't, create it.
"""
try:
if parentPath:
dirPath = os.path.join(parentPath, dirPath)
if not os.path.isdir(dirPath):
os.mkdir(dirPath)
if parent:
path = os.path.join(parent, path)
if not os.path.isdir(path):
os.mkdir(path)
except Exception as exc:
logger.error("Could not create folder: %s", dirPath)
logger.error("Could not create folder: %s", path)
logException()
if isinstance(errLog, list):
errLog.append(f"Could not create folder: {dirPath}")
errLog.append(f"Could not create folder: {path}")
errLog.append(formatException(exc))
return False
return True
def sha256sum(filePath):
def sha256sum(path):
"""Make a shasum of a file using a buffer.
Based on: https://stackoverflow.com/a/44873382/5825851
"""
hDigest = hashlib.sha256()
digest = hashlib.sha256()
bData = bytearray(65536)
mData = memoryview(bData)
try:
with open(filePath, mode="rb", buffering=0) as inFile:
with open(path, mode="rb", buffering=0) as inFile:
for n in iter(lambda: inFile.readinto(mData), 0):
hDigest.update(mData[:n])
digest.update(mData[:n])
except Exception:
logger.error("Could not create sha256sum of: %s", filePath)
logger.error("Could not create sha256sum of: %s", path)
logException()
return None
return hDigest.hexdigest()
return digest.hexdigest()
# =============================================================================================== #
@@ -523,87 +523,59 @@ def getGuiItem(objName):
class NWConfigParser(ConfigParser):
CNF_STR = 0
CNF_INT = 1
CNF_FLOAT = 2
CNF_BOOL = 3
CNF_S_LST = 4
CNF_I_LST = 5
def __init__(self):
super().__init__()
def rdStr(self, section, option, default):
"""Read string value.
"""
return self._parseLine(section, option, default, self.CNF_STR)
return self.get(section, option, fallback=default)
def rdInt(self, section, option, default):
"""Read integer value.
"""
return self._parseLine(section, option, default, self.CNF_INT)
try:
return self.getint(section, option, fallback=default)
except ValueError:
logger.error("Could not read '%s':'%s' from config", section, option)
return default
def rdFlt(self, section, option, default):
"""Read float value.
"""
return self._parseLine(section, option, default, self.CNF_FLOAT)
try:
return self.getfloat(section, option, fallback=default)
except ValueError:
logger.error("Could not read '%s':'%s' from config", section, option)
return default
def rdBool(self, section, option, default):
"""Read boolean value.
"""
return self._parseLine(section, option, default, self.CNF_BOOL)
try:
return self.getboolean(section, option, fallback=default)
except ValueError:
logger.error("Could not read '%s':'%s' from config", section, option)
return default
def rdStrList(self, section, option, default):
"""Read string list.
"""
return self._parseLine(section, option, default, self.CNF_S_LST)
result = default.copy() if isinstance(default, list) else []
if self.has_option(section, option):
data = self.get(section, option, fallback="").split(",")
for i in range(min(len(data), len(result))):
result[i] = data[i].strip()
return result
def rdIntList(self, section, option, default):
"""Read integer list.
"""
return self._parseLine(section, option, default, self.CNF_I_LST)
##
# Internal Functions
##
def _unpackList(self, value, default, type):
"""Unpack a comma-separated string of items into a list.
"""
inList = value.split(",")
outList = []
if isinstance(default, list):
outList = default.copy()
for i in range(min(len(inList), len(outList))):
try:
if type == self.CNF_S_LST:
outList[i] = inList[i].strip()
elif type == self.CNF_I_LST:
outList[i] = int(inList[i].strip())
except Exception:
continue
return outList
def _parseLine(self, section, option, default, type):
"""Parse a line and return the correct datatype.
"""
result = default.copy() if isinstance(default, list) else []
if self.has_option(section, option):
try:
if type == self.CNF_STR:
return self.get(section, option)
elif type == self.CNF_INT:
return self.getint(section, option)
elif type == self.CNF_FLOAT:
return self.getfloat(section, option)
elif type == self.CNF_BOOL:
return self.getboolean(section, option)
elif type in (self.CNF_I_LST, self.CNF_S_LST):
return self._unpackList(self.get(section, option), default, type)
except ValueError:
logger.error("Could not read '%s':'%s' from config", str(section), str(option))
logException()
return default
return default
data = self.get(section, option, fallback="").split(",")
for i in range(min(len(data), len(result))):
result[i] = checkInt(data[i].strip(), result[i])
return result
# END Class NWConfigParser
+6 -8
View File
@@ -52,12 +52,11 @@ class Config:
# Set Application Variables
self.appName = "novelWriter"
self.appHandle = self.appName.lower()
self.appHandle = "novelwriter"
# Set Paths
self.cmdOpen = None # Path from command line for project to be opened on launch
self.confPath = None # Folder where the config is saved
self.confFile = None # The config file name
self.dataPath = None # Folder where app data is stored
self.lastPath = None # The last user-selected folder (browse dialogs)
self.appPath = None # The full path to the novelwriter package folder
@@ -271,7 +270,6 @@ class Config:
logger.debug("Config path: %s", self.confPath)
logger.debug("Data path: %s", self.dataPath)
self.confFile = self.appHandle+".conf"
self.lastPath = os.path.expanduser("~")
self.appPath = getattr(sys, "_MEIPASS", os.path.abspath(os.path.dirname(__file__)))
self.appRoot = os.path.abspath(os.path.join(self.appPath, os.path.pardir))
@@ -306,12 +304,12 @@ class Config:
# We don't error on these failing since they are not essential
if self.dataPath is not None:
ensureFolder("syntax", parentPath=self.dataPath)
ensureFolder("themes", parentPath=self.dataPath)
ensureFolder("syntax", parent=self.dataPath)
ensureFolder("themes", parent=self.dataPath)
# Check if config file exists
if self.confPath is not None:
if os.path.isfile(os.path.join(self.confPath, self.confFile)):
if os.path.isfile(os.path.join(self.confPath, nwFiles.CONF_FILE)):
# If it exists, load it
self.loadConfig()
else:
@@ -396,7 +394,7 @@ class Config:
return False
theConf = NWConfigParser()
cnfPath = os.path.join(self.confPath, self.confFile)
cnfPath = os.path.join(self.confPath, nwFiles.CONF_FILE)
try:
with open(cnfPath, mode="r", encoding="utf-8") as inFile:
theConf.read_file(inFile)
@@ -619,7 +617,7 @@ class Config:
}
# Write config file
cnfPath = os.path.join(self.confPath, self.confFile)
cnfPath = os.path.join(self.confPath, nwFiles.CONF_FILE)
try:
with open(cnfPath, mode="w", encoding="utf-8") as outFile:
theConf.write(outFile)
+1
View File
@@ -68,6 +68,7 @@ class nwHeaders:
class nwFiles:
CONF_FILE = "novelwriter.conf"
PROJ_FILE = "nwProject.nwx"
PROJ_DICT = "wordlist.txt"
PROJ_LOCK = "nwProject.lock"
+5 -7
View File
@@ -85,7 +85,6 @@ class NWProject:
self.projDict = None # The spell check dictionary
self.projSpell = None # The spell check language, if different than default
self.projLang = None # The project language, used for builds
self.projFile = None # The file name of the project main XML file
self.projFiles = [] # A list of all files in the content folder on load
# Project Meta
@@ -260,7 +259,6 @@ class NWProject:
self.projDict = None
self.projSpell = None
self.projLang = None
self.projFile = nwFiles.PROJ_FILE
self.projFiles = []
self.projName = ""
self.bookTitle = ""
@@ -774,9 +772,9 @@ class NWProject:
self._projTree.packXML(nwXML)
# Write the xml tree to file
tempFile = os.path.join(self.projPath, self.projFile+"~")
saveFile = os.path.join(self.projPath, self.projFile)
backFile = os.path.join(self.projPath, self.projFile[:-3]+"bak")
tempFile = os.path.join(self.projPath, nwFiles.PROJ_FILE+"~")
saveFile = os.path.join(self.projPath, nwFiles.PROJ_FILE)
backFile = os.path.join(self.projPath, nwFiles.PROJ_FILE[:-3]+"bak")
try:
with open(tempFile, mode="wb") as outFile:
outFile.write(etree.tostring(
@@ -816,7 +814,7 @@ class NWProject:
return True
def closeProject(self, idleTime=0):
def closeProject(self, idleTime=0.0):
"""Close the current project and clear all meta data.
"""
logger.info("Closing project: %s", self.projPath)
@@ -1185,7 +1183,7 @@ class NWProject:
information to the GUI statusbar.
"""
self.projChanged = bValue
self.mainGui.statusBar.doUpdateProjectStatus(bValue)
self.mainGui.mainStatus.doUpdateProjectStatus(bValue)
if bValue:
# If we've changed the project at all, this should be True
self.projAltered = True
+1 -1
View File
@@ -117,7 +117,7 @@ class GuiDocMerge(QDialog):
finalItems = []
for i in range(self.listBox.count()):
item = self.listBox.item(i)
if item.checkState() == Qt.Checked:
if item is not None and item.checkState() == Qt.Checked:
finalItems.append(item.data(Qt.UserRole))
self._data["moveToTrash"] = self.trashSwitch.isChecked()
+6 -3
View File
@@ -145,9 +145,12 @@ class GuiDocSplit(QDialog):
headerList = []
for i in range(self.listBox.count()):
item = self.listBox.item(i)
headerList.append(
(item.data(self.LINE_ROLE), item.data(self.LEVEL_ROLE), item.data(self.LABEL_ROLE))
)
if item is not None:
headerList.append((
item.data(self.LINE_ROLE),
item.data(self.LEVEL_ROLE),
item.data(self.LABEL_ROLE),
))
spLevel = self.splitLevel.currentData()
intoFolder = self.folderSwitch.isChecked()
+13 -10
View File
@@ -365,11 +365,12 @@ class GuiProjectEditStatus(QWidget):
newList = []
for n in range(self.listBox.topLevelItemCount()):
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),
})
if item is not None:
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 [], []
@@ -468,7 +469,8 @@ class GuiProjectEditStatus(QWidget):
self.listBox.insertTopLevelItem(nIndex, cItem)
self.listBox.clearSelection()
cItem.setSelected(True)
if cItem is not None:
cItem.setSelected(True)
self.colChanged = True
return
@@ -617,10 +619,11 @@ class GuiProjectEditReplace(QWidget):
newList = {}
for n in range(self.listBox.topLevelItemCount()):
tItem = self.listBox.topLevelItem(n)
aKey = self._stripNotAllowed(tItem.text(0))
aVal = tItem.text(1)
if len(aKey) > 0:
newList[aKey] = aVal
if tItem is not None:
aKey = self._stripNotAllowed(tItem.text(0))
aVal = tItem.text(1)
if len(aKey) > 0:
newList[aKey] = aVal
return newList
+1 -1
View File
@@ -135,7 +135,7 @@ class GuiUpdates(QDialog):
logException()
relVersion = rawData.get("tag_name", "Unknown")
relDate = rawData.get("created_at", None)
relDate = rawData.get("created_at", "")
try:
relDate = datetime.strptime(relDate[:10], "%Y-%m-%d").strftime("%x")
+3 -1
View File
@@ -156,7 +156,9 @@ class GuiWordList(QDialog):
try:
with open(tmpFile, mode="w", encoding="utf-8") as outFile:
for i in range(self.listBox.count()):
outFile.write(self.listBox.item(i).text() + "\n")
item = self.listBox.item(i)
if item is not None:
outFile.write(item.text() + "\n")
except Exception:
logger.error("Could not save new word list")
+2 -2
View File
@@ -199,8 +199,8 @@ def exceptionHandler(exType, exValue, exTrace):
try:
# Try a controlled shutdown
nwGUI.closeProject(isYes=True)
nwGUI.closeMain()
nwGUI.closeProject(isYes=True) # type: ignore
nwGUI.closeMain() # type: ignore
logger.info("Emergency shutdown successful")
except Exception as exc:
+6 -15
View File
@@ -513,7 +513,7 @@ class GuiDocEditor(QTextEdit):
newHeader = self._nwItem.mainHeading
# ToDo: This should be a signal
if self._updateHeaders(checkLevel=True):
if self._updateHeaders():
self.mainGui.requestNovelTreeRefresh()
else:
self.mainGui.novelView.updateWordCounts(tHandle)
@@ -740,7 +740,7 @@ class GuiDocEditor(QTextEdit):
qApp.restoreOverrideCursor()
afTime = time()
logger.debug("Document highlighted in %.3f ms", 1000*(afTime-bfTime))
self.mainGui.statusBar.setStatus(self.tr("Spell check complete"))
self.mainGui.mainStatus.setStatus(self.tr("Spell check complete"))
return True
@@ -2025,7 +2025,7 @@ class GuiDocEditor(QTextEdit):
return False
return True
def _updateHeaders(self, checkPos=False, checkLevel=False):
def _updateHeaders(self):
"""Update the headers record and return True if anything
changed, if a check flag was provided.
"""
@@ -2033,21 +2033,12 @@ class GuiDocEditor(QTextEdit):
return False
newHeaders = self.theProject.index.getHandleHeaders(self._docHandle)
if checkPos:
newPos = [x[0] for x in newHeaders]
oldPos = [x[0] for x in self._docHeaders]
if checkLevel:
newLev = [x[1] for x in newHeaders]
oldLev = [x[1] for x in self._docHeaders]
newLev = [x[1] for x in newHeaders]
oldLev = [x[1] for x in self._docHeaders]
self._docHeaders = newHeaders
if checkPos:
return newPos != oldPos
if checkLevel:
return newLev != oldLev
return False
return newLev != oldLev
def _checkDocSize(self, theSize):
"""Check if document size crosses the big document limit set in
+11 -10
View File
@@ -508,16 +508,17 @@ class GuiNovelTree(QTreeWidget):
self._actHandle = tHandle
for i in range(self.topLevelItemCount()):
tItem = self.topLevelItem(i)
if tItem.data(self.C_TITLE, self.D_HANDLE) == tHandle:
tItem.setBackground(self.C_TITLE, self.palette().alternateBase())
tItem.setBackground(self.C_WORDS, self.palette().alternateBase())
tItem.setBackground(self.C_EXTRA, self.palette().alternateBase())
tItem.setBackground(self.C_MORE, self.palette().alternateBase())
else:
tItem.setBackground(self.C_TITLE, self.palette().base())
tItem.setBackground(self.C_WORDS, self.palette().base())
tItem.setBackground(self.C_EXTRA, self.palette().base())
tItem.setBackground(self.C_MORE, self.palette().base())
if tItem is not None:
if tItem.data(self.C_TITLE, self.D_HANDLE) == tHandle:
tItem.setBackground(self.C_TITLE, self.palette().alternateBase())
tItem.setBackground(self.C_WORDS, self.palette().alternateBase())
tItem.setBackground(self.C_EXTRA, self.palette().alternateBase())
tItem.setBackground(self.C_MORE, self.palette().alternateBase())
else:
tItem.setBackground(self.C_TITLE, self.palette().base())
tItem.setBackground(self.C_WORDS, self.palette().base())
tItem.setBackground(self.C_EXTRA, self.palette().base())
tItem.setBackground(self.C_MORE, self.palette().base())
logger.debug("Highlighted Novel Tree in %.3f ms", (time() - tStart)*1000)
+4 -3
View File
@@ -675,9 +675,10 @@ class GuiOutlineTree(QTreeWidget):
self.setColumnHidden(self._colIdx[nwOutline.TITLE], False)
headItem = self.headerItem()
headItem.setTextAlignment(self._colIdx[nwOutline.CCOUNT], Qt.AlignRight)
headItem.setTextAlignment(self._colIdx[nwOutline.WCOUNT], Qt.AlignRight)
headItem.setTextAlignment(self._colIdx[nwOutline.PCOUNT], Qt.AlignRight)
if headItem is not None:
headItem.setTextAlignment(self._colIdx[nwOutline.CCOUNT], Qt.AlignRight)
headItem.setTextAlignment(self._colIdx[nwOutline.WCOUNT], Qt.AlignRight)
headItem.setTextAlignment(self._colIdx[nwOutline.PCOUNT], Qt.AlignRight)
novStruct = self.theProject.index.novelStructure(rootHandle=rootHandle, skipExcl=True)
for _, tHandle, sTitle, novIdx in novStruct:
+4 -3
View File
@@ -1211,13 +1211,13 @@ class GuiProjectTree(QTreeWidget):
if hasChild and isFolder:
mTrans.addAction(
self.tr("Combine Documents in Folder"),
self.tr("Merge Documents in Folder"),
lambda: self._mergeDocuments(tHandle, True)
)
if isFile:
mTrans.addAction(
self.tr("Split Document by Header"),
self.tr("Split Document by Headers"),
lambda: self._splitDocument(tHandle)
)
@@ -1309,7 +1309,8 @@ class GuiProjectTree(QTreeWidget):
self._postItemMove(sHandle, wCount)
self._recordLastMove(sItem, pItem, pIndex)
self._alertTreeChange(sHandle, flush=True)
sItem.setExpanded(isExpanded)
if sItem is not None:
sItem.setExpanded(isExpanded)
return
+27 -20
View File
@@ -55,6 +55,7 @@ from novelwriter.enum import (
nwDocMode, nwItemType, nwItemClass, nwAlert, nwWidget, nwState, nwView
)
from novelwriter.common import getGuiItem, hexToInt
from novelwriter.constants import nwFiles
logger = logging.getLogger(__name__)
@@ -104,7 +105,7 @@ class GuiMain(QMainWindow):
hWd = self.mainConf.pxInt(4)
# Main GUI Elements
self.statusBar = GuiMainStatus(self)
self.mainStatus = GuiMainStatus(self)
self.projView = GuiProjectView(self)
self.novelView = GuiNovelView(self)
self.docEditor = GuiDocEditor(self)
@@ -189,7 +190,7 @@ class GuiMain(QMainWindow):
# Set Main Window Elements
self.setMenuBar(self.mainMenu)
self.setCentralWidget(self.mainStack)
self.setStatusBar(self.statusBar)
self.setStatusBar(self.mainStatus)
self.addToolBar(Qt.LeftToolBarArea, self.viewsBar)
self.setContextMenuPolicy(Qt.NoContextMenu) # Issue #1147
@@ -211,8 +212,8 @@ class GuiMain(QMainWindow):
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)
self.docEditor.spellDictionaryChanged.connect(self.mainStatus.setLanguage)
self.docEditor.docEditedStatusChanged.connect(self.mainStatus.doUpdateDocumentStatus)
self.docEditor.docCountsChanged.connect(self.itemDetails.updateCounts)
self.docEditor.docCountsChanged.connect(self.projView.updateCounts)
self.docEditor.loadDocumentTagRequest.connect(self._followTag)
@@ -254,7 +255,7 @@ class GuiMain(QMainWindow):
keyEscape.activated.connect(self._keyPressEscape)
# Forward Functions
self.setStatus = self.statusBar.setStatus
self.setStatus = self.mainStatus.setStatus
# Force a show of the GUI
self.show()
@@ -266,7 +267,7 @@ class GuiMain(QMainWindow):
self.initMain()
self.asProjTimer.start()
self.asDocTimer.start()
self.statusBar.clearStatus()
self.mainStatus.clearStatus()
# Handle Windows Mode
self.showNormal()
@@ -307,7 +308,7 @@ class GuiMain(QMainWindow):
self.outlineView.clearProject()
# General
self.statusBar.clearStatus()
self.mainStatus.clearStatus()
self._updateWindowTitle()
return True
@@ -353,7 +354,7 @@ class GuiMain(QMainWindow):
logger.error("No projData or projPath set")
return False
if os.path.isfile(os.path.join(projPath, self.theProject.projFile)):
if os.path.isfile(os.path.join(projPath, nwFiles.PROJ_FILE)):
self.makeAlert(self.tr(
"A project already exists in that location. "
"Please choose another folder."
@@ -376,10 +377,10 @@ class GuiMain(QMainWindow):
self.outlineView.openProjectTasks()
self.rebuildIndex(beQuiet=True)
self.statusBar.setRefTime(self.theProject.projOpened)
self.statusBar.setProjectStatus(nwState.GOOD)
self.statusBar.setDocumentStatus(nwState.NONE)
self.statusBar.setStatus(self.tr("New project created ..."))
self.mainStatus.setRefTime(self.theProject.projOpened)
self.mainStatus.setProjectStatus(nwState.GOOD)
self.mainStatus.setDocumentStatus(nwState.NONE)
self.mainStatus.setStatus(self.tr("New project created ..."))
self._updateWindowTitle(self.theProject.projName)
@@ -523,7 +524,7 @@ class GuiMain(QMainWindow):
self.rebuildTrees()
self.docEditor.setDictionaries()
self.docEditor.toggleSpellCheck(self.theProject.spellCheck)
self.statusBar.setRefTime(self.theProject.projOpened)
self.mainStatus.setRefTime(self.theProject.projOpened)
self.projView.openProjectTasks()
self.novelView.openProjectTasks()
self.outlineView.openProjectTasks()
@@ -625,7 +626,7 @@ class GuiMain(QMainWindow):
fHandle = None # The first file handle we encounter
foundIt = False # We've found tHandle, pick the next we see
for tItem in self.theProject.tree:
if not self.theProject.tree.checkType(tItem.itemHandle, nwItemType.FILE):
if tItem is None or not tItem.isFileType():
continue
if fHandle is None:
fHandle = tItem.itemHandle
@@ -949,6 +950,7 @@ class GuiMain(QMainWindow):
dlgDetails = getGuiItem("GuiProjectDetails")
if dlgDetails is None:
dlgDetails = GuiProjectDetails(self)
assert isinstance(dlgDetails, GuiProjectDetails)
dlgDetails.setModal(False)
dlgDetails.show()
@@ -967,6 +969,7 @@ class GuiMain(QMainWindow):
dlgBuild = getGuiItem("GuiBuildNovel")
if dlgBuild is None:
dlgBuild = GuiBuildNovel(self)
assert isinstance(dlgBuild, GuiBuildNovel)
dlgBuild.setModal(False)
dlgBuild.show()
@@ -986,6 +989,7 @@ class GuiMain(QMainWindow):
dlgLipsum = getGuiItem("GuiLipsum")
if dlgLipsum is None:
dlgLipsum = GuiLipsum(self)
assert isinstance(dlgLipsum, GuiLipsum)
dlgLipsum.setModal(False)
dlgLipsum.show()
@@ -1020,6 +1024,7 @@ class GuiMain(QMainWindow):
dlgStats = getGuiItem("GuiWritingStats")
if dlgStats is None:
dlgStats = GuiWritingStats(self)
assert isinstance(dlgStats, GuiWritingStats)
dlgStats.setModal(False)
dlgStats.show()
@@ -1035,6 +1040,7 @@ class GuiMain(QMainWindow):
dlgAbout = getGuiItem("GuiAbout")
if dlgAbout is None:
dlgAbout = GuiAbout(self)
assert isinstance(dlgAbout, GuiAbout)
dlgAbout.setModal(True)
dlgAbout.show()
@@ -1060,6 +1066,7 @@ class GuiMain(QMainWindow):
dlgUpdate = getGuiItem("GuiUpdates")
if dlgUpdate is None:
dlgUpdate = GuiUpdates(self)
assert isinstance(dlgUpdate, GuiUpdates)
dlgUpdate.setModal(True)
dlgUpdate.show()
@@ -1221,7 +1228,7 @@ class GuiMain(QMainWindow):
isVisible = not self.isFocusMode
self.treePane.setVisible(isVisible)
self.statusBar.setVisible(isVisible)
self.mainStatus.setVisible(isVisible)
self.mainMenu.setVisible(isVisible)
self.viewsBar.setVisible(isVisible)
@@ -1505,12 +1512,12 @@ class GuiMain(QMainWindow):
if editIdle or userIdle:
self.idleTime += currTime - self.idleRefTime
self.statusBar.setUserIdle(True)
self.mainStatus.setUserIdle(True)
else:
self.statusBar.setUserIdle(False)
self.mainStatus.setUserIdle(False)
self.idleRefTime = currTime
self.statusBar.updateTime(idleTime=self.idleTime)
self.mainStatus.updateTime(idleTime=self.idleTime)
return
@@ -1519,7 +1526,7 @@ class GuiMain(QMainWindow):
"""Update the word count on the status bar.
"""
if not self.hasProject:
self.statusBar.setProjectStats(0, 0)
self.mainStatus.setProjectStats(0, 0)
self.theProject.updateWordCounts()
if self.mainConf.incNotesWCount:
@@ -1529,7 +1536,7 @@ class GuiMain(QMainWindow):
currWords = self.theProject.currNovelWC
diffWords = currWords - self.theProject.lastNovelWC
self.statusBar.setProjectStats(currWords, diffWords)
self.mainStatus.setProjectStats(currWords, diffWords)
return
+4 -3
View File
@@ -112,9 +112,10 @@ class GuiWritingStats(QDialog):
self.listBox.setColumnWidth(self.C_COUNT, wCol3)
hHeader = self.listBox.headerItem()
hHeader.setTextAlignment(self.C_LENGTH, Qt.AlignRight)
hHeader.setTextAlignment(self.C_IDLE, Qt.AlignRight)
hHeader.setTextAlignment(self.C_COUNT, Qt.AlignRight)
if hHeader is not None:
hHeader.setTextAlignment(self.C_LENGTH, Qt.AlignRight)
hHeader.setTextAlignment(self.C_IDLE, Qt.AlignRight)
hHeader.setTextAlignment(self.C_COUNT, Qt.AlignRight)
sortCol = minmax(pOptions.getInt("GuiWritingStats", "sortCol", 0), 0, 2)
sortOrder = checkIntTuple(
+1 -1
View File
@@ -30,7 +30,7 @@ class MockGuiMain:
self.mainConf = None
self.hasProject = True
self.theProject = None
self.statusBar = MockStatusBar()
self.mainStatus = MockStatusBar()
# Test Variables
self.askResponse = True
+5 -10
View File
@@ -556,13 +556,13 @@ def testBaseCommon_EnsureFolder(monkeypatch, fncDir):
assert ensureFolder(newDir1) is True
assert os.path.isdir(newDir1)
assert ensureFolder("newDir2", parentPath=fncDir) is True
assert ensureFolder("newDir2", parent=fncDir) is True
assert os.path.isdir(newDir2)
with monkeypatch.context() as mp:
mp.setattr("os.mkdir", causeOSError)
errLog = []
assert ensureFolder("newDir3", parentPath=fncDir, errLog=errLog) is False
assert ensureFolder("newDir3", parent=fncDir, errLog=errLog) is False
assert errLog[0] == f"Could not create folder: {newDir3}"
assert not os.path.isdir(newDir3)
@@ -673,10 +673,10 @@ def testBaseCommon_NWConfigParser(fncDir):
# Read Float
assert cfgParser.rdFlt("main", "intopt1", 13.0) == 42.0
assert cfgParser.rdFlt("main", "float1", 13.0) == 4.2
assert cfgParser.rdInt("main", "stropt", 13.0) == 13.0
assert cfgParser.rdFlt("main", "stropt", 13.0) == 13.0
assert cfgParser.rdInt("nope", "intopt1", 13.0) == 13.0
assert cfgParser.rdInt("main", "blabla", 13.0) == 13.0
assert cfgParser.rdFlt("nope", "intopt1", 13.0) == 13.0
assert cfgParser.rdFlt("main", "blabla", 13.0) == 13.0
# Read String List
assert cfgParser.rdStrList("main", "list1", []) == []
@@ -704,9 +704,4 @@ def testBaseCommon_NWConfigParser(fncDir):
assert cfgParser.rdIntList("nope", "list2", [1]) == [1]
assert cfgParser.rdIntList("main", "blabla", [1]) == [1]
# Internal
# ========
assert cfgParser._parseLine("main", "stropt", None, 999) is None
# END Test testBaseCommon_NWConfigParser
+5 -6
View File
@@ -31,6 +31,7 @@ from PyQt5.QtWidgets import QMessageBox, QInputDialog
from novelwriter.enum import nwItemType, nwView, nwWidget
from novelwriter.tools import GuiProjectWizard
from novelwriter.dialogs import GuiEditLabel
from novelwriter.constants import nwFiles
from novelwriter.gui.outline import GuiOutlineView
from novelwriter.gui.projtree import GuiProjectTree
from novelwriter.gui.doceditor import GuiDocEditor
@@ -91,7 +92,7 @@ def testGuiMain_NewProject(monkeypatch, nwGUI, fncProj):
assert nwGUI.newProject(projData={}) is False
# Project file already exists
projFile = os.path.join(fncProj, nwGUI.theProject.projFile)
projFile = os.path.join(fncProj, nwFiles.PROJ_FILE)
writeFile(projFile, "Stuff")
assert nwGUI.newProject(projData={"projPath": fncProj}) is False
os.unlink(projFile)
@@ -102,7 +103,7 @@ def testGuiMain_NewProject(monkeypatch, nwGUI, fncProj):
# This one should work just fine
assert nwGUI.newProject(projData={"projPath": fncProj}) is True
assert os.path.isfile(os.path.join(fncProj, nwGUI.theProject.projFile))
assert os.path.isfile(os.path.join(fncProj, nwFiles.PROJ_FILE))
assert os.path.isdir(os.path.join(fncProj, "content"))
# END Test testGuiMain_NewProject
@@ -183,7 +184,6 @@ def testGuiMain_Editing(qtbot, monkeypatch, nwGUI, fncProj, refDir, outDir, mock
assert nwGUI.theProject.tree.trashRoot() is None
assert nwGUI.theProject.projPath is None
assert nwGUI.theProject.projMeta is None
assert nwGUI.theProject.projFile == "nwProject.nwx"
assert nwGUI.theProject.projName == ""
assert nwGUI.theProject.bookTitle == ""
assert len(nwGUI.theProject.bookAuthors) == 0
@@ -208,7 +208,6 @@ def testGuiMain_Editing(qtbot, monkeypatch, nwGUI, fncProj, refDir, outDir, mock
assert nwGUI.theProject.tree.trashRoot() is None
assert nwGUI.theProject.projPath == fncProj
assert nwGUI.theProject.projMeta == os.path.join(fncProj, "meta")
assert nwGUI.theProject.projFile == "nwProject.nwx"
assert nwGUI.theProject.projName == "New Project"
assert nwGUI.theProject.bookTitle == "New Novel"
assert len(nwGUI.theProject.bookAuthors) == 1
@@ -575,7 +574,7 @@ def testGuiMain_FocusFullMode(qtbot, monkeypatch, nwGUI, fncProj, mockRnd):
# Enable focus mode
assert nwGUI.toggleFocusMode() is True
assert nwGUI.treePane.isVisible() is False
assert nwGUI.statusBar.isVisible() is False
assert nwGUI.mainStatus.isVisible() is False
assert nwGUI.mainMenu.isVisible() is False
assert nwGUI.viewsBar.isVisible() is False
assert nwGUI.splitView.isVisible() is False
@@ -583,7 +582,7 @@ def testGuiMain_FocusFullMode(qtbot, monkeypatch, nwGUI, fncProj, mockRnd):
# Disable focus mode
assert nwGUI.toggleFocusMode() is True
assert nwGUI.treePane.isVisible() is True
assert nwGUI.statusBar.isVisible() is True
assert nwGUI.mainStatus.isVisible() is True
assert nwGUI.mainMenu.isVisible() is True
assert nwGUI.viewsBar.isVisible() is True
assert nwGUI.splitView.isVisible() is True
+37 -37
View File
@@ -45,57 +45,57 @@ def testGuiStatusBar_Main(qtbot, monkeypatch, nwGUI, fncProj, mockRnd):
# Reference Time
refTime = time.time()
nwGUI.statusBar.setRefTime(refTime)
assert nwGUI.statusBar.refTime == refTime
nwGUI.mainStatus.setRefTime(refTime)
assert nwGUI.mainStatus.refTime == refTime
# Project Status
nwGUI.statusBar.setProjectStatus(nwState.NONE)
assert nwGUI.statusBar.projIcon._theCol == nwGUI.statusBar.projIcon._colNone
nwGUI.statusBar.setProjectStatus(nwState.BAD)
assert nwGUI.statusBar.projIcon._theCol == nwGUI.statusBar.projIcon._colBad
nwGUI.statusBar.setProjectStatus(nwState.GOOD)
assert nwGUI.statusBar.projIcon._theCol == nwGUI.statusBar.projIcon._colGood
nwGUI.mainStatus.setProjectStatus(nwState.NONE)
assert nwGUI.mainStatus.projIcon._theCol == nwGUI.mainStatus.projIcon._colNone
nwGUI.mainStatus.setProjectStatus(nwState.BAD)
assert nwGUI.mainStatus.projIcon._theCol == nwGUI.mainStatus.projIcon._colBad
nwGUI.mainStatus.setProjectStatus(nwState.GOOD)
assert nwGUI.mainStatus.projIcon._theCol == nwGUI.mainStatus.projIcon._colGood
# Document Status
nwGUI.statusBar.setDocumentStatus(nwState.NONE)
assert nwGUI.statusBar.docIcon._theCol == nwGUI.statusBar.docIcon._colNone
nwGUI.statusBar.setDocumentStatus(nwState.BAD)
assert nwGUI.statusBar.docIcon._theCol == nwGUI.statusBar.docIcon._colBad
nwGUI.statusBar.setDocumentStatus(nwState.GOOD)
assert nwGUI.statusBar.docIcon._theCol == nwGUI.statusBar.docIcon._colGood
nwGUI.mainStatus.setDocumentStatus(nwState.NONE)
assert nwGUI.mainStatus.docIcon._theCol == nwGUI.mainStatus.docIcon._colNone
nwGUI.mainStatus.setDocumentStatus(nwState.BAD)
assert nwGUI.mainStatus.docIcon._theCol == nwGUI.mainStatus.docIcon._colBad
nwGUI.mainStatus.setDocumentStatus(nwState.GOOD)
assert nwGUI.mainStatus.docIcon._theCol == nwGUI.mainStatus.docIcon._colGood
# Idle Status
nwGUI.statusBar.mainConf.stopWhenIdle = False
nwGUI.statusBar.setUserIdle(True)
nwGUI.statusBar.updateTime()
assert nwGUI.statusBar.userIdle is False
assert nwGUI.statusBar.timeText.text() == "00:00:00"
nwGUI.mainStatus.mainConf.stopWhenIdle = False
nwGUI.mainStatus.setUserIdle(True)
nwGUI.mainStatus.updateTime()
assert nwGUI.mainStatus.userIdle is False
assert nwGUI.mainStatus.timeText.text() == "00:00:00"
nwGUI.statusBar.mainConf.stopWhenIdle = True
nwGUI.statusBar.setUserIdle(True)
nwGUI.statusBar.updateTime(5)
assert nwGUI.statusBar.userIdle is True
assert nwGUI.statusBar.timeText.text() != "00:00:00"
nwGUI.mainStatus.mainConf.stopWhenIdle = True
nwGUI.mainStatus.setUserIdle(True)
nwGUI.mainStatus.updateTime(5)
assert nwGUI.mainStatus.userIdle is True
assert nwGUI.mainStatus.timeText.text() != "00:00:00"
nwGUI.statusBar.setUserIdle(False)
nwGUI.statusBar.updateTime(5)
assert nwGUI.statusBar.userIdle is False
assert nwGUI.statusBar.timeText.text() != "00:00:00"
nwGUI.mainStatus.setUserIdle(False)
nwGUI.mainStatus.updateTime(5)
assert nwGUI.mainStatus.userIdle is False
assert nwGUI.mainStatus.timeText.text() != "00:00:00"
# Language
nwGUI.statusBar.setLanguage("None", "None")
assert nwGUI.statusBar.langText.text() == "None"
nwGUI.statusBar.setLanguage("en", "None")
assert nwGUI.statusBar.langText.text() == "American English"
nwGUI.mainStatus.setLanguage("None", "None")
assert nwGUI.mainStatus.langText.text() == "None"
nwGUI.mainStatus.setLanguage("en", "None")
assert nwGUI.mainStatus.langText.text() == "American English"
# Project Stats
nwGUI.statusBar.mainConf.incNotesWCount = False
nwGUI.mainStatus.mainConf.incNotesWCount = False
nwGUI._updateStatusWordCount()
assert nwGUI.statusBar.statsText.text() == "Words: 9 (+9)"
nwGUI.statusBar.mainConf.incNotesWCount = True
assert nwGUI.mainStatus.statsText.text() == "Words: 9 (+9)"
nwGUI.mainStatus.mainConf.incNotesWCount = True
nwGUI._updateStatusWordCount()
assert nwGUI.statusBar.statsText.text() == "Words: 11 (+11)"
assert nwGUI.mainStatus.statsText.text() == "Words: 11 (+11)"
# qtbot.stopForInteraction()
# qtbot.stop()
# END Test testGuiStatusBar_Init