Fix and optimise code (#904)
* Fix bug in early error reporting in main init * Update docstrings and optimise code in GuiMain * Update docstrings and optimise code in Config * Update docstrings and optimise code in common module * Update docstrings and optimise code in main project classes * Update the OptionState class * Update the spell checker class * Update the file converter classes and extend tests * Update the about, merge, split and item editor classes and extend tests * Update item editor test * Update about dialog tests * Some minor test cleanup * Fix typo and add clarification in contributing guide
This commit is contained in:
committed by
GitHub
parent
2a06db0a2b
commit
1c45331b0a
+10
-10
@@ -117,7 +117,7 @@ def main(sysArgs=None):
|
||||
|
||||
# Valid Input Options
|
||||
shortOpt = "hv"
|
||||
longOpt = [
|
||||
longOpt = [
|
||||
"help",
|
||||
"version",
|
||||
"info",
|
||||
@@ -215,31 +215,31 @@ def main(sysArgs=None):
|
||||
errorCode = 0
|
||||
if sys.hexversion < 0x030600f0:
|
||||
errorData.append(
|
||||
"At least Python 3.6.0 is required, found %s." % CONFIG.verPyString
|
||||
"At least Python 3.6.0 is required, found %s" % CONFIG.verPyString
|
||||
)
|
||||
errorCode |= 4
|
||||
if CONFIG.verQtValue < 50300:
|
||||
errorData.append(
|
||||
"At least Qt5 version 5.3 is required, found %s." % CONFIG.verQtString
|
||||
"At least Qt5 version 5.3 is required, found %s" % CONFIG.verQtString
|
||||
)
|
||||
errorCode |= 8
|
||||
if CONFIG.verPyQtValue < 50300:
|
||||
errorData.append(
|
||||
"At least PyQt5 version 5.3 is required, found %s." % CONFIG.verPyQtString
|
||||
"At least PyQt5 version 5.3 is required, found %s" % CONFIG.verPyQtString
|
||||
)
|
||||
errorCode |= 16
|
||||
|
||||
try:
|
||||
import lxml # noqa: F401
|
||||
except ImportError:
|
||||
errorData.append("Python module 'lxml' is missing.")
|
||||
errorData.append("Python module 'lxml' is missing")
|
||||
errorCode |= 32
|
||||
|
||||
if errorData:
|
||||
errApp = QApplication([])
|
||||
errMsg = QErrorMessage()
|
||||
errMsg.resize(500, 300)
|
||||
errMsg.showMessage((
|
||||
errDlg = QErrorMessage()
|
||||
errDlg.resize(500, 300)
|
||||
errDlg.showMessage((
|
||||
"<h3>A critical error has been encountered</h3>"
|
||||
"<p>novelWriter cannot start due to the following issues:<p>"
|
||||
"<p> - %s</p>"
|
||||
@@ -247,8 +247,8 @@ def main(sysArgs=None):
|
||||
) % (
|
||||
"<br> - ".join(errorData)
|
||||
))
|
||||
for errMsg in errorData:
|
||||
logger.critical(errMsg)
|
||||
for errLine in errorData:
|
||||
logger.critical(errLine)
|
||||
errApp.exec_()
|
||||
sys.exit(errorCode)
|
||||
|
||||
|
||||
+24
-13
@@ -46,7 +46,7 @@ logger = logging.getLogger(__name__)
|
||||
# =============================================================================================== #
|
||||
|
||||
def checkString(value, default, allowNone=False):
|
||||
"""Check if a variable is a string or a none.
|
||||
"""Check if a variable is a string or a None.
|
||||
"""
|
||||
if allowNone and (value is None or value == "None"):
|
||||
return None
|
||||
@@ -56,7 +56,7 @@ def checkString(value, default, allowNone=False):
|
||||
|
||||
|
||||
def checkInt(value, default, allowNone=False):
|
||||
"""Check if a variable is an integer or a none.
|
||||
"""Check if a variable is an integer or a None.
|
||||
"""
|
||||
if allowNone and (value is None or value == "None"):
|
||||
return None
|
||||
@@ -66,8 +66,19 @@ def checkInt(value, default, allowNone=False):
|
||||
return default
|
||||
|
||||
|
||||
def checkFloat(value, default, allowNone=False):
|
||||
"""Check if a variable is a float or a None.
|
||||
"""
|
||||
if allowNone and (value is None or value == "None"):
|
||||
return None
|
||||
try:
|
||||
return float(value)
|
||||
except Exception:
|
||||
return default
|
||||
|
||||
|
||||
def checkBool(value, default, allowNone=False):
|
||||
"""Check if a variable is a boolean or a none.
|
||||
"""Check if a variable is a boolean or a None.
|
||||
"""
|
||||
if allowNone and (value is None or value == "None"):
|
||||
return None
|
||||
@@ -211,7 +222,7 @@ def formatTime(tS):
|
||||
|
||||
|
||||
def parseTimeStamp(theStamp, default, allowNone=False):
|
||||
"""Parses a text representation of a time stamp and converts it into
|
||||
"""Parses a text representation of a timestamp and converts it into
|
||||
a float. Note that negative timestamps cause an OSError on Windows.
|
||||
See https://bugs.python.org/issue29097
|
||||
"""
|
||||
@@ -228,7 +239,7 @@ def parseTimeStamp(theStamp, default, allowNone=False):
|
||||
# =============================================================================================== #
|
||||
|
||||
def splitVersionNumber(value):
|
||||
"""Splits a version string on the form aa.bb.cc into major, minor
|
||||
"""Split a version string on the form aa.bb.cc into major, minor
|
||||
and patch, and computes an integer value aabbcc.
|
||||
"""
|
||||
if not isinstance(value, str):
|
||||
@@ -337,7 +348,7 @@ def fuzzyTime(secDiff):
|
||||
).format(int(round(secDiff/31557600)))
|
||||
|
||||
|
||||
def numberToRoman(numVal, isLower=False):
|
||||
def numberToRoman(numVal, toLower=False):
|
||||
"""Convert an integer to a Roman number.
|
||||
"""
|
||||
if not isinstance(numVal, int):
|
||||
@@ -358,7 +369,7 @@ def numberToRoman(numVal, isLower=False):
|
||||
if numVal <= 0:
|
||||
break
|
||||
|
||||
return romNum.lower() if isLower else romNum
|
||||
return romNum.lower() if toLower else romNum
|
||||
|
||||
|
||||
# =============================================================================================== #
|
||||
@@ -434,11 +445,11 @@ def readTextFile(filePath):
|
||||
return fileText
|
||||
|
||||
|
||||
def makeFileNameSafe(theText):
|
||||
"""Returns a filename safe version of the text.
|
||||
def makeFileNameSafe(value):
|
||||
"""Returns a filename safe string of the value.
|
||||
"""
|
||||
cleanName = ""
|
||||
for c in theText.strip():
|
||||
for c in str(value).strip():
|
||||
if c.isalpha() or c.isdigit() or c == " ":
|
||||
cleanName += c
|
||||
return cleanName
|
||||
@@ -456,7 +467,7 @@ def sha256sum(filePath):
|
||||
for n in iter(lambda: inFile.readinto(mData), 0):
|
||||
hDigest.update(mData[:n])
|
||||
except Exception:
|
||||
logger.error("Could not read sha256sum of: %s", filePath)
|
||||
logger.error("Could not create sha256sum of: %s", filePath)
|
||||
logException()
|
||||
return None
|
||||
|
||||
@@ -467,11 +478,11 @@ def sha256sum(filePath):
|
||||
# Other Functions
|
||||
# =============================================================================================== #
|
||||
|
||||
def getGuiItem(theName):
|
||||
def getGuiItem(objName):
|
||||
"""Returns a QtWidget based on its objectName.
|
||||
"""
|
||||
for qWidget in qApp.topLevelWidgets():
|
||||
if qWidget.objectName() == theName:
|
||||
if qWidget.objectName() == objName:
|
||||
return qWidget
|
||||
return None
|
||||
|
||||
|
||||
+65
-30
@@ -302,7 +302,7 @@ class Config:
|
||||
logger.verbose("App path: %s", self.appPath)
|
||||
logger.verbose("Last path: %s", self.lastPath)
|
||||
|
||||
# If config folder does not exist, create it.
|
||||
# If the config folder does not exist, create it.
|
||||
# This assumes that the os config folder itself exists.
|
||||
if not os.path.isdir(self.confPath):
|
||||
try:
|
||||
@@ -324,7 +324,7 @@ class Config:
|
||||
# If it does not exist, save a copy of the default values
|
||||
self.saveConfig()
|
||||
|
||||
# If data folder does not exist, make it.
|
||||
# If the data folder does not exist, create it.
|
||||
# This assumes that the os data folder itself exists.
|
||||
if self.dataPath is not None:
|
||||
if not os.path.isdir(self.dataPath):
|
||||
@@ -534,7 +534,7 @@ class Config:
|
||||
logger.info("Using straight single quotes, so disabling auto-replace")
|
||||
self.doReplaceSQuote = False
|
||||
|
||||
if self.fmtDoubleQuotes == ["\"", "\""] and self.doReplaceDQuote:
|
||||
if self.fmtDoubleQuotes == ['"', '"'] and self.doReplaceDQuote:
|
||||
logger.info("Using straight double quotes, so disabling auto-replace")
|
||||
self.doReplaceDQuote = False
|
||||
|
||||
@@ -671,36 +671,28 @@ class Config:
|
||||
if self.dataPath is None:
|
||||
return False
|
||||
|
||||
cacheFile = os.path.join(self.dataPath, nwFiles.RECENT_FILE)
|
||||
self.recentProj = {}
|
||||
|
||||
if os.path.isfile(cacheFile):
|
||||
try:
|
||||
with open(cacheFile, mode="r", encoding="utf-8") as inFile:
|
||||
theData = json.load(inFile)
|
||||
cacheFile = os.path.join(self.dataPath, nwFiles.RECENT_FILE)
|
||||
if not os.path.isfile(cacheFile):
|
||||
return True
|
||||
|
||||
for projPath in theData.keys():
|
||||
theEntry = theData[projPath]
|
||||
theTitle = ""
|
||||
lastTime = 0
|
||||
wordCount = 0
|
||||
if "title" in theEntry.keys():
|
||||
theTitle = theEntry["title"]
|
||||
if "time" in theEntry.keys():
|
||||
lastTime = int(theEntry["time"])
|
||||
if "words" in theEntry.keys():
|
||||
wordCount = int(theEntry["words"])
|
||||
self.recentProj[projPath] = {
|
||||
"title": theTitle,
|
||||
"time": lastTime,
|
||||
"words": wordCount,
|
||||
}
|
||||
try:
|
||||
with open(cacheFile, mode="r", encoding="utf-8") as inFile:
|
||||
theData = json.load(inFile)
|
||||
|
||||
except Exception as e:
|
||||
self.hasError = True
|
||||
self.errData.append("Could not load recent project cache")
|
||||
self.errData.append(str(e))
|
||||
return False
|
||||
for projPath, theEntry in theData.items():
|
||||
self.recentProj[projPath] = {
|
||||
"title": theEntry.get("title", ""),
|
||||
"time": theEntry.get("time", 0),
|
||||
"words": theEntry.get("words", 0),
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
self.hasError = True
|
||||
self.errData.append("Could not load recent project cache")
|
||||
self.errData.append(str(e))
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
@@ -755,6 +747,8 @@ class Config:
|
||||
##
|
||||
|
||||
def setConfPath(self, newPath):
|
||||
"""Set the path and filename to the config file.
|
||||
"""
|
||||
if newPath is None:
|
||||
return True
|
||||
if not os.path.isfile(newPath):
|
||||
@@ -765,6 +759,8 @@ class Config:
|
||||
return True
|
||||
|
||||
def setDataPath(self, newPath):
|
||||
"""Set the data path.
|
||||
"""
|
||||
if newPath is None:
|
||||
return True
|
||||
if not os.path.isdir(newPath):
|
||||
@@ -774,6 +770,8 @@ class Config:
|
||||
return True
|
||||
|
||||
def setLastPath(self, lastPath):
|
||||
"""Set the last used path (by the user).
|
||||
"""
|
||||
if lastPath is None or lastPath == "":
|
||||
self.lastPath = ""
|
||||
else:
|
||||
@@ -781,6 +779,11 @@ class Config:
|
||||
return True
|
||||
|
||||
def setWinSize(self, newWidth, newHeight):
|
||||
"""Set the size of the main window, but only if the change is
|
||||
larger than 5 pixels. The OS window manager will sometimes
|
||||
adjust it a bit, and we don't want the main window to shrink or
|
||||
grow each time the app is opened.
|
||||
"""
|
||||
newWidth = int(newWidth/self.guiScale)
|
||||
newHeight = int(newHeight/self.guiScale)
|
||||
if abs(self.winGeometry[0] - newWidth) > 5:
|
||||
@@ -792,57 +795,79 @@ class Config:
|
||||
return True
|
||||
|
||||
def setPreferencesSize(self, newWidth, newHeight):
|
||||
"""Sat the size of the Preferences dialog window.
|
||||
"""
|
||||
self.prefGeometry[0] = int(newWidth/self.guiScale)
|
||||
self.prefGeometry[1] = int(newHeight/self.guiScale)
|
||||
self.confChanged = True
|
||||
return True
|
||||
|
||||
def setTreeColWidths(self, colWidths):
|
||||
"""Set the column widths of the main project tree.
|
||||
"""
|
||||
self.treeColWidth = [int(x/self.guiScale) for x in colWidths]
|
||||
self.confChanged = True
|
||||
return True
|
||||
|
||||
def setNovelColWidths(self, colWidths):
|
||||
"""Set the column widths of the novel tree.
|
||||
"""
|
||||
self.novelColWidth = [int(x/self.guiScale) for x in colWidths]
|
||||
self.confChanged = True
|
||||
return True
|
||||
|
||||
def setProjColWidths(self, colWidths):
|
||||
"""Set the column widths of the Load Project dialog.
|
||||
"""
|
||||
self.projColWidth = [int(x/self.guiScale) for x in colWidths]
|
||||
self.confChanged = True
|
||||
return True
|
||||
|
||||
def setMainPanePos(self, panePos):
|
||||
"""Set the position of the main GUI splitter.
|
||||
"""
|
||||
self.mainPanePos = [int(x/self.guiScale) for x in panePos]
|
||||
self.confChanged = True
|
||||
return True
|
||||
|
||||
def setDocPanePos(self, panePos):
|
||||
"""Set the position of the main editor/viewer splitter.
|
||||
"""
|
||||
self.docPanePos = [int(x/self.guiScale) for x in panePos]
|
||||
self.confChanged = True
|
||||
return True
|
||||
|
||||
def setViewPanePos(self, panePos):
|
||||
"""Set the position of the viewer meta data splitter.
|
||||
"""
|
||||
self.viewPanePos = [int(x/self.guiScale) for x in panePos]
|
||||
self.confChanged = True
|
||||
return True
|
||||
|
||||
def setOutlinePanePos(self, panePos):
|
||||
"""Set the position of the outline details splitter.
|
||||
"""
|
||||
self.outlnPanePos = [int(x/self.guiScale) for x in panePos]
|
||||
self.confChanged = True
|
||||
return True
|
||||
|
||||
def setShowRefPanel(self, checkState):
|
||||
"""Set the visibility state of the reference panel.
|
||||
"""
|
||||
self.showRefPanel = checkState
|
||||
self.confChanged = True
|
||||
return self.showRefPanel
|
||||
|
||||
def setViewComments(self, viewState):
|
||||
"""Set the visibility state of comments in the viewer.
|
||||
"""
|
||||
self.viewComments = viewState
|
||||
self.confChanged = True
|
||||
return self.viewComments
|
||||
|
||||
def setViewSynopsis(self, viewState):
|
||||
"""Set the visibility state of synopsis comments in the viewer.
|
||||
"""
|
||||
self.viewSynopsis = viewState
|
||||
self.confChanged = True
|
||||
return self.viewSynopsis
|
||||
@@ -852,12 +877,18 @@ class Config:
|
||||
##
|
||||
|
||||
def setDefaultGuiTheme(self):
|
||||
"""Reset the GUI theme to default value.
|
||||
"""
|
||||
self.guiTheme = "default"
|
||||
|
||||
def setDefaultSyntaxTheme(self):
|
||||
"""Reset the syntax theme to default value.
|
||||
"""
|
||||
self.guiSyntax = "default_light"
|
||||
|
||||
def setDefaultIconTheme(self):
|
||||
"""Reset the icon theme to default value.
|
||||
"""
|
||||
self.guiIcons = "typicons_light"
|
||||
|
||||
##
|
||||
@@ -904,6 +935,9 @@ class Config:
|
||||
return self.pxInt(self.focusWidth)
|
||||
|
||||
def getErrData(self):
|
||||
"""Compile and return error messages from the initialisation of
|
||||
the Config class, and clear the error buffer.
|
||||
"""
|
||||
errMessage = "<br>".join(self.errData)
|
||||
self.hasError = False
|
||||
self.errData = []
|
||||
@@ -914,7 +948,8 @@ class Config:
|
||||
##
|
||||
|
||||
def _packList(self, inData):
|
||||
"""Pack a list of items into a comma-separated string.
|
||||
"""Pack a list of items into a comma-separated string for saving
|
||||
to the config file.
|
||||
"""
|
||||
return ", ".join([str(inVal) for inVal in inData])
|
||||
|
||||
|
||||
@@ -287,10 +287,8 @@ class NWIndex():
|
||||
nLine = 0
|
||||
nTitle = 0
|
||||
theLines = theText.splitlines()
|
||||
for aLine in theLines:
|
||||
nLine += 1
|
||||
nChar = len(aLine.strip())
|
||||
if nChar == 0:
|
||||
for nLine, aLine in enumerate(theLines, start=1):
|
||||
if len(aLine.strip()) == 0:
|
||||
continue
|
||||
|
||||
if aLine.startswith("#"):
|
||||
@@ -363,7 +361,7 @@ class NWIndex():
|
||||
else:
|
||||
return False
|
||||
|
||||
sTitle = "T%06d" % nLine
|
||||
sTitle = f"T{nLine:06d}"
|
||||
self._fileIndex[tHandle][sTitle] = {
|
||||
"level": hDepth,
|
||||
"title": hText,
|
||||
@@ -391,14 +389,13 @@ class NWIndex():
|
||||
"pCount": 0,
|
||||
"synopsis": "",
|
||||
}
|
||||
|
||||
return
|
||||
|
||||
def _indexWordCounts(self, tHandle, theText, nTitle):
|
||||
"""Count text stats and save the counts to the index.
|
||||
"""
|
||||
cC, wC, pC = countWords(theText)
|
||||
sTitle = "T%06d" % nTitle
|
||||
sTitle = f"T{nTitle:06d}"
|
||||
if tHandle in self._fileIndex:
|
||||
if sTitle in self._fileIndex[tHandle]:
|
||||
self._fileIndex[tHandle][sTitle]["cCount"] = cC
|
||||
@@ -409,7 +406,7 @@ class NWIndex():
|
||||
def _indexSynopsis(self, tHandle, theText, nTitle):
|
||||
"""Save the synopsis to the index.
|
||||
"""
|
||||
sTitle = "T%06d" % nTitle
|
||||
sTitle = f"T{nTitle:06d}"
|
||||
if tHandle in self._fileIndex:
|
||||
if sTitle in self._fileIndex[tHandle]:
|
||||
self._fileIndex[tHandle][sTitle]["synopsis"] = theText
|
||||
@@ -428,7 +425,7 @@ class NWIndex():
|
||||
logger.warning("Skipping invalid keyword '%s' in '%s'", theBits[0], tHandle)
|
||||
return
|
||||
|
||||
sTitle = "T%06d" % nTitle
|
||||
sTitle = f"T{nTitle:06d}"
|
||||
if theBits[0] == nwKeyWords.TAG_KEY:
|
||||
self._tagIndex[theBits[1]] = [nLine, tHandle, itemClass.name, sTitle]
|
||||
|
||||
@@ -525,7 +522,7 @@ class NWIndex():
|
||||
"""
|
||||
for tHandle in self._listNovelHandles(skipExcluded):
|
||||
for sTitle in sorted(self._fileIndex[tHandle]):
|
||||
tKey = "%s:%s" % (tHandle, sTitle)
|
||||
tKey = f"{tHandle}:{sTitle}"
|
||||
yield tKey, tHandle, sTitle, self._fileIndex[tHandle][sTitle]
|
||||
|
||||
def getNovelWordCount(self, skipExcluded=True):
|
||||
@@ -559,7 +556,7 @@ class NWIndex():
|
||||
return theCounts
|
||||
|
||||
for sTitle, sData in hRecord.items():
|
||||
theCounts.append(("%s:%s" % (tHandle, sTitle), sData["wCount"]))
|
||||
theCounts.append((f"{tHandle}:{sTitle}", sData["wCount"]))
|
||||
|
||||
return theCounts
|
||||
|
||||
|
||||
@@ -103,11 +103,8 @@ class NWItem():
|
||||
logger.error("XML item entry does not have a handle")
|
||||
return False
|
||||
|
||||
if "parent" in xItem.attrib:
|
||||
self.setParent(xItem.attrib["parent"])
|
||||
|
||||
if "order" in xItem.attrib:
|
||||
self.setOrder(xItem.attrib["order"])
|
||||
self.setParent(xItem.attrib.get("parent", None))
|
||||
self.setOrder(xItem.attrib.get("order", 0))
|
||||
|
||||
tmpStatus = ""
|
||||
for xValue in xItem:
|
||||
@@ -200,11 +197,8 @@ class NWItem():
|
||||
def setHandle(self, theHandle):
|
||||
"""Set the item handle, and ensure it is valid.
|
||||
"""
|
||||
if isinstance(theHandle, str):
|
||||
if isHandle(theHandle):
|
||||
self.itemHandle = theHandle
|
||||
else:
|
||||
self.itemHandle = None
|
||||
if isHandle(theHandle):
|
||||
self.itemHandle = theHandle
|
||||
else:
|
||||
self.itemHandle = None
|
||||
return
|
||||
@@ -214,11 +208,8 @@ class NWItem():
|
||||
"""
|
||||
if theParent is None:
|
||||
self.itemParent = None
|
||||
elif isinstance(theParent, str):
|
||||
if isHandle(theParent):
|
||||
self.itemParent = theParent
|
||||
else:
|
||||
self.itemParent = None
|
||||
elif isHandle(theParent):
|
||||
self.itemParent = theParent
|
||||
else:
|
||||
self.itemParent = None
|
||||
return
|
||||
|
||||
+70
-130
@@ -30,88 +30,40 @@ import logging
|
||||
import novelwriter
|
||||
|
||||
from novelwriter.constants import nwFiles
|
||||
from novelwriter.common import checkBool, checkFloat, checkInt, checkString
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
VALID_MAP = {
|
||||
"GuiWritingStats": {
|
||||
"winWidth", "winHeight", "widthCol0", "widthCol1", "widthCol2",
|
||||
"widthCol3", "sortCol", "sortOrder", "incNovel", "incNotes",
|
||||
"hideZeros", "hideNegative", "groupByDay", "showIdleTime", "histMax"
|
||||
},
|
||||
"GuiDocSplit": {"spLevel"},
|
||||
"GuiBuildNovel": {
|
||||
"winWidth", "winHeight", "boxWidth", "docWidth", "addNovel",
|
||||
"addNotes", "ignoreFlag", "justifyText", "excludeBody", "textFont",
|
||||
"textSize", "lineHeight", "noStyling", "incSynopsis", "incComments",
|
||||
"incKeywords", "incBodyText", "replaceTabs", "replaceUCode"
|
||||
},
|
||||
"GuiOutline": {"headerOrder", "columnWidth", "columnHidden"},
|
||||
"GuiProjectSettings": {
|
||||
"winWidth", "winHeight", "replaceColW", "statusColW", "importColW"
|
||||
},
|
||||
"GuiProjectDetails": {
|
||||
"winWidth", "winHeight", "widthCol0", "widthCol1", "widthCol2",
|
||||
"widthCol3", "widthCol4", "wordsPerPage", "countFrom", "clearDouble"
|
||||
},
|
||||
"GuiWordList": {"winWidth", "winHeight"}
|
||||
}
|
||||
|
||||
|
||||
class OptionState():
|
||||
|
||||
def __init__(self, theProject):
|
||||
|
||||
self.theProject = theProject
|
||||
self.theState = {}
|
||||
self.validMap = {
|
||||
"GuiWritingStats": {
|
||||
"winWidth",
|
||||
"winHeight",
|
||||
"widthCol0",
|
||||
"widthCol1",
|
||||
"widthCol2",
|
||||
"widthCol3",
|
||||
"sortCol",
|
||||
"sortOrder",
|
||||
"incNovel",
|
||||
"incNotes",
|
||||
"hideZeros",
|
||||
"hideNegative",
|
||||
"groupByDay",
|
||||
"showIdleTime",
|
||||
"histMax",
|
||||
},
|
||||
"GuiDocSplit": {
|
||||
"spLevel",
|
||||
},
|
||||
"GuiBuildNovel": {
|
||||
"winWidth",
|
||||
"winHeight",
|
||||
"boxWidth",
|
||||
"docWidth",
|
||||
"addNovel",
|
||||
"addNotes",
|
||||
"ignoreFlag",
|
||||
"justifyText",
|
||||
"excludeBody",
|
||||
"textFont",
|
||||
"textSize",
|
||||
"lineHeight",
|
||||
"noStyling",
|
||||
"incSynopsis",
|
||||
"incComments",
|
||||
"incKeywords",
|
||||
"incBodyText",
|
||||
"replaceTabs",
|
||||
"replaceUCode",
|
||||
},
|
||||
"GuiOutline": {
|
||||
"headerOrder",
|
||||
"columnWidth",
|
||||
"columnHidden",
|
||||
},
|
||||
"GuiProjectSettings": {
|
||||
"winWidth",
|
||||
"winHeight",
|
||||
"replaceColW",
|
||||
"statusColW",
|
||||
"importColW",
|
||||
},
|
||||
"GuiProjectDetails": {
|
||||
"winWidth",
|
||||
"winHeight",
|
||||
"widthCol0",
|
||||
"widthCol1",
|
||||
"widthCol2",
|
||||
"widthCol3",
|
||||
"widthCol4",
|
||||
"wordsPerPage",
|
||||
"countFrom",
|
||||
"clearDouble",
|
||||
},
|
||||
"GuiWordList": {
|
||||
"winWidth",
|
||||
"winHeight",
|
||||
}
|
||||
}
|
||||
|
||||
self._theState = {}
|
||||
return
|
||||
|
||||
##
|
||||
@@ -139,11 +91,11 @@ class OptionState():
|
||||
|
||||
# Filter out unused variables
|
||||
for aGroup in theState:
|
||||
if aGroup in self.validMap:
|
||||
self.theState[aGroup] = {}
|
||||
if aGroup in VALID_MAP:
|
||||
self._theState[aGroup] = {}
|
||||
for anOpt in theState[aGroup]:
|
||||
if anOpt in self.validMap[aGroup]:
|
||||
self.theState[aGroup][anOpt] = theState[aGroup][anOpt]
|
||||
if anOpt in VALID_MAP[aGroup]:
|
||||
self._theState[aGroup][anOpt] = theState[aGroup][anOpt]
|
||||
|
||||
return True
|
||||
|
||||
@@ -158,7 +110,7 @@ class OptionState():
|
||||
|
||||
try:
|
||||
with open(stateFile, mode="w+", encoding="utf-8") as outFile:
|
||||
json.dump(self.theState, outFile, indent=2)
|
||||
json.dump(self._theState, outFile, indent=2)
|
||||
except Exception:
|
||||
logger.error("Failed to save GUI options file")
|
||||
novelwriter.logException()
|
||||
@@ -170,21 +122,21 @@ class OptionState():
|
||||
# Setters
|
||||
##
|
||||
|
||||
def setValue(self, setGroup, setName, setValue):
|
||||
def setValue(self, group, name, value):
|
||||
"""Saves a value, with a given group and name.
|
||||
"""
|
||||
if setGroup not in self.validMap:
|
||||
logger.error("Unknown option group '%s'", setGroup)
|
||||
if group not in VALID_MAP:
|
||||
logger.error("Unknown option group '%s'", group)
|
||||
return False
|
||||
|
||||
if setName not in self.validMap[setGroup]:
|
||||
logger.error("Unknown option name '%s'", setName)
|
||||
if name not in VALID_MAP[group]:
|
||||
logger.error("Unknown option name '%s'", name)
|
||||
return False
|
||||
|
||||
if setGroup not in self.theState:
|
||||
self.theState[setGroup] = {}
|
||||
if group not in self._theState:
|
||||
self._theState[group] = {}
|
||||
|
||||
self.theState[setGroup][setName] = setValue
|
||||
self._theState[group][name] = value
|
||||
|
||||
return True
|
||||
|
||||
@@ -192,79 +144,67 @@ class OptionState():
|
||||
# Getters
|
||||
##
|
||||
|
||||
def getValue(self, getGroup, getName, defaultValue):
|
||||
def getValue(self, group, name, default):
|
||||
"""Return an arbitrary type value, if it exists. Otherwise,
|
||||
return the default value.
|
||||
"""
|
||||
if getGroup in self.theState:
|
||||
if getName in self.theState[getGroup]:
|
||||
return self.theState[getGroup][getName]
|
||||
return defaultValue
|
||||
if group in self._theState:
|
||||
return self._theState[group].get(name, default)
|
||||
return default
|
||||
|
||||
def getString(self, getGroup, getName, defaultValue):
|
||||
def getString(self, group, name, default):
|
||||
"""Return the value as a string, if it exists. Otherwise, return
|
||||
the default value.
|
||||
"""
|
||||
if getGroup in self.theState:
|
||||
if getName in self.theState[getGroup]:
|
||||
return str(self.theState[getGroup][getName])
|
||||
return defaultValue
|
||||
if group in self._theState:
|
||||
return checkString(self._theState[group].get(name, default), default)
|
||||
return default
|
||||
|
||||
def getInt(self, getGroup, getName, defaultValue):
|
||||
def getInt(self, group, name, default):
|
||||
"""Return the value as an int, if it exists. Otherwise, return
|
||||
the default value.
|
||||
"""
|
||||
if getGroup in self.theState:
|
||||
if getName in self.theState[getGroup]:
|
||||
try:
|
||||
return int(self.theState[getGroup][getName])
|
||||
except Exception as e:
|
||||
logger.warning(str(e))
|
||||
return defaultValue
|
||||
return defaultValue
|
||||
if group in self._theState:
|
||||
return checkInt(self._theState[group].get(name, default), default)
|
||||
return default
|
||||
|
||||
def getFloat(self, getGroup, getName, defaultValue):
|
||||
def getFloat(self, group, name, default):
|
||||
"""Return the value as a float, if it exists. Otherwise, return
|
||||
the default value.
|
||||
"""
|
||||
if getGroup in self.theState:
|
||||
if getName in self.theState[getGroup]:
|
||||
try:
|
||||
return float(self.theState[getGroup][getName])
|
||||
except Exception as e:
|
||||
logger.warning(str(e))
|
||||
return defaultValue
|
||||
return defaultValue
|
||||
if group in self._theState:
|
||||
return checkFloat(self._theState[group].get(name, default), default)
|
||||
return default
|
||||
|
||||
def getBool(self, getGroup, getName, defaultValue):
|
||||
def getBool(self, group, name, default):
|
||||
"""Return the value as a bool, if it exists. Otherwise, return
|
||||
the default value.
|
||||
"""
|
||||
if getGroup in self.theState:
|
||||
if getName in self.theState[getGroup]:
|
||||
return bool(self.theState[getGroup][getName])
|
||||
return defaultValue
|
||||
if group in self._theState:
|
||||
if name in self._theState[group]:
|
||||
return checkBool(self._theState[group].get(name, default), default)
|
||||
return default
|
||||
|
||||
##
|
||||
# Validators
|
||||
##
|
||||
|
||||
def validIntRange(self, theValue, intA, intB, intDefault):
|
||||
def validIntRange(self, value, first, last, default):
|
||||
"""Check that an int is in a given range. If it isn't, return
|
||||
the default value.
|
||||
"""
|
||||
if isinstance(theValue, int):
|
||||
if theValue >= intA and theValue <= intB:
|
||||
return theValue
|
||||
return intDefault
|
||||
if isinstance(value, int):
|
||||
if value >= first and value <= last:
|
||||
return value
|
||||
return default
|
||||
|
||||
def validIntTuple(self, theValue, theTuple, intDefault):
|
||||
def validIntTuple(self, value, valid, default):
|
||||
"""Check that an int is an element of a tuple. If it isn't,
|
||||
return the default value.
|
||||
"""
|
||||
if isinstance(theValue, int):
|
||||
if theValue in theTuple:
|
||||
return theValue
|
||||
return intDefault
|
||||
if isinstance(value, int):
|
||||
if value in valid:
|
||||
return value
|
||||
return default
|
||||
|
||||
# END Class OptionState
|
||||
|
||||
@@ -125,6 +125,7 @@ class NWProject():
|
||||
if not self.projTree.checkRootUnique(rootClass):
|
||||
self.theParent.makeAlert(self.tr("Duplicate root item detected."), nwAlert.ERROR)
|
||||
return None
|
||||
|
||||
newItem = NWItem(self)
|
||||
newItem.setName(rootName)
|
||||
newItem.setType(nwItemType.ROOT)
|
||||
@@ -356,7 +357,7 @@ class NWProject():
|
||||
return True
|
||||
|
||||
def openProject(self, fileName, overrideLock=False):
|
||||
"""Open the project file provided, or if doesn't exist, assume
|
||||
"""Open the project file provided. If it doesn't exist, assume
|
||||
it is a folder and look for the file within it. If successful,
|
||||
parse the XML of the file and populate the project variables and
|
||||
build the tree of project items.
|
||||
@@ -469,8 +470,8 @@ class NWProject():
|
||||
# parser will lose the autoReplace settings if allowed to
|
||||
# read the file. Introduced in version 0.10.
|
||||
# 1.3 : Reduces the number of layouts to onlye two. One for
|
||||
# novel documents and one for notes. Introduced in version
|
||||
# 1.5.
|
||||
# novel documents and one for project notes. Introduced in
|
||||
# version 1.5.
|
||||
|
||||
if fileVersion not in ("1.0", "1.1", "1.2", "1.3"):
|
||||
self.theParent.makeAlert(self.tr(
|
||||
@@ -973,7 +974,7 @@ class NWProject():
|
||||
return False
|
||||
|
||||
self.bookAuthors = []
|
||||
for bookAuthor in bookAuthors.split("\n"):
|
||||
for bookAuthor in bookAuthors.splitlines():
|
||||
bookAuthor = bookAuthor.strip()
|
||||
if bookAuthor == "":
|
||||
continue
|
||||
@@ -1074,7 +1075,7 @@ class NWProject():
|
||||
replaceMap = self.statusItems.setNewEntries(newCols)
|
||||
for nwItem in self.projTree:
|
||||
if nwItem.itemClass == nwItemClass.NOVEL:
|
||||
if nwItem.itemStatus in replaceMap.keys():
|
||||
if nwItem.itemStatus in replaceMap:
|
||||
nwItem.setStatus(replaceMap[nwItem.itemStatus])
|
||||
self.setProjectChanged(True)
|
||||
return True
|
||||
@@ -1086,7 +1087,7 @@ class NWProject():
|
||||
replaceMap = self.importItems.setNewEntries(newCols)
|
||||
for nwItem in self.projTree:
|
||||
if nwItem.itemClass != nwItemClass.NOVEL:
|
||||
if nwItem.itemStatus in replaceMap.keys():
|
||||
if nwItem.itemStatus in replaceMap:
|
||||
nwItem.setStatus(replaceMap[nwItem.itemStatus])
|
||||
self.setProjectChanged(True)
|
||||
return True
|
||||
@@ -1104,7 +1105,7 @@ class NWProject():
|
||||
"""
|
||||
for valKey, valEntry in titleFormat.items():
|
||||
if valKey in self.titleFormat:
|
||||
self.titleFormat[valKey] = checkString(valEntry, self.titleFormat[valKey], False)
|
||||
self.titleFormat[valKey] = checkString(valEntry, self.titleFormat[valKey])
|
||||
return True
|
||||
|
||||
def setProjectChanged(self, bValue):
|
||||
@@ -1346,7 +1347,7 @@ class NWProject():
|
||||
return
|
||||
|
||||
def _packProjectKeyValue(self, xParent, theName, theDict):
|
||||
"""Pack the entries in the auto-replace dictionary.
|
||||
"""Pack the entries of a dictionary into an xml element.
|
||||
"""
|
||||
xAutoRep = etree.SubElement(xParent, theName)
|
||||
for aKey, aValue in theDict.items():
|
||||
|
||||
@@ -35,16 +35,24 @@ class NWSpellEnchant():
|
||||
def __init__(self):
|
||||
|
||||
self.mainConf = novelwriter.CONFIG
|
||||
self.theDict = None
|
||||
self.projDict = set()
|
||||
self.projectDict = None
|
||||
self.spellLanguage = None
|
||||
self.theBroker = None
|
||||
|
||||
self._theDict = None
|
||||
self._projDict = set()
|
||||
self._projectDict = None
|
||||
self._spellLanguage = None
|
||||
self._theBroker = None
|
||||
|
||||
logger.debug("Enchant spell checking activated")
|
||||
|
||||
return
|
||||
|
||||
##
|
||||
# Getters and Setters
|
||||
##
|
||||
|
||||
def spellLanguage(self):
|
||||
return self._spellLanguage
|
||||
|
||||
def setLanguage(self, theLang, projectDict=None):
|
||||
"""Load a dictionary for the language specified in the config.
|
||||
If that fails, we load a mock dictionary so that lookups don't
|
||||
@@ -52,49 +60,53 @@ class NWSpellEnchant():
|
||||
"""
|
||||
try:
|
||||
import enchant
|
||||
if self.theBroker is not None:
|
||||
if self._theBroker is not None:
|
||||
logger.debug("Deleting old pyenchant broker")
|
||||
del self.theBroker
|
||||
del self._theBroker
|
||||
|
||||
self.theBroker = enchant.Broker()
|
||||
self.theDict = self.theBroker.request_dict(theLang)
|
||||
self.spellLanguage = theLang
|
||||
self._theBroker = enchant.Broker()
|
||||
self._theDict = self._theBroker.request_dict(theLang)
|
||||
self._spellLanguage = theLang
|
||||
logger.debug("Enchant spell checking for language '%s' loaded", theLang)
|
||||
|
||||
except Exception:
|
||||
logger.error("Failed to load enchant spell checking for language '%s'", theLang)
|
||||
self.theDict = FakeEnchant()
|
||||
self.spellLanguage = None
|
||||
self._theDict = FakeEnchant()
|
||||
self._spellLanguage = None
|
||||
|
||||
self._readProjectDictionary(projectDict)
|
||||
for pWord in self.projDict:
|
||||
self.theDict.add_to_session(pWord)
|
||||
for pWord in self._projDict:
|
||||
self._theDict.add_to_session(pWord)
|
||||
|
||||
return
|
||||
|
||||
##
|
||||
# Methods
|
||||
##
|
||||
|
||||
def checkWord(self, theWord):
|
||||
"""Wrapper function for pyenchant.
|
||||
"""
|
||||
return self.theDict.check(theWord)
|
||||
return self._theDict.check(theWord)
|
||||
|
||||
def suggestWords(self, theWord):
|
||||
"""Wrapper function for pyenchant.
|
||||
"""
|
||||
return self.theDict.suggest(theWord)
|
||||
return self._theDict.suggest(theWord)
|
||||
|
||||
def addWord(self, newWord):
|
||||
"""Add a word to the project dictionary.
|
||||
"""
|
||||
self.theDict.add_to_session(newWord)
|
||||
self._theDict.add_to_session(newWord)
|
||||
|
||||
if self.projectDict is not None and newWord not in self.projDict:
|
||||
if self._projectDict is not None and newWord not in self._projDict:
|
||||
newWord = newWord.strip()
|
||||
try:
|
||||
with open(self.projectDict, mode="a+", encoding="utf-8") as outFile:
|
||||
with open(self._projectDict, mode="a+", encoding="utf-8") as outFile:
|
||||
outFile.write("%s\n" % newWord)
|
||||
self.projDict.add(newWord)
|
||||
self._projDict.add(newWord)
|
||||
except Exception:
|
||||
logger.error("Failed to add word to project word list %s", str(self.projectDict))
|
||||
logger.error("Failed to add word to project word list %s", str(self._projectDict))
|
||||
novelwriter.logException()
|
||||
return False
|
||||
return True
|
||||
@@ -119,8 +131,8 @@ class NWSpellEnchant():
|
||||
dictionary.
|
||||
"""
|
||||
try:
|
||||
spTag = self.theDict.tag
|
||||
spName = self.theDict.provider.name
|
||||
spTag = self._theDict.tag
|
||||
spName = self._theDict.provider.name
|
||||
except Exception:
|
||||
logger.error("Failed to extract information about the dictionary")
|
||||
novelwriter.logException()
|
||||
@@ -137,8 +149,8 @@ class NWSpellEnchant():
|
||||
"""Read the content of the project dictionary, and add it to the
|
||||
lookup lists.
|
||||
"""
|
||||
self.projDict = set()
|
||||
self.projectDict = projectDict
|
||||
self._projDict = set()
|
||||
self._projectDict = projectDict
|
||||
|
||||
if projectDict is None:
|
||||
return False
|
||||
@@ -151,9 +163,9 @@ class NWSpellEnchant():
|
||||
with open(projectDict, mode="r", encoding="utf-8") as wordsFile:
|
||||
for theLine in wordsFile:
|
||||
theLine = theLine.strip()
|
||||
if len(theLine) > 0 and theLine not in self.projDict:
|
||||
self.projDict.add(theLine)
|
||||
logger.debug("Project word list contains %d words", len(self.projDict))
|
||||
if len(theLine) > 0 and theLine not in self._projDict:
|
||||
self._projDict.add(theLine)
|
||||
logger.debug("Project word list contains %d words", len(self._projDict))
|
||||
|
||||
except Exception:
|
||||
logger.error("Failed to load project word list")
|
||||
|
||||
+59
-63
@@ -109,7 +109,7 @@ class ToHtml(Tokenizer):
|
||||
to theResult.
|
||||
"""
|
||||
if self.genMode == self.M_PREVIEW:
|
||||
htmlTags = { # HTML4 + CSS2
|
||||
htmlTags = { # HTML4 + CSS2 (for Qt)
|
||||
self.FMT_B_B: "<b>",
|
||||
self.FMT_B_E: "</b>",
|
||||
self.FMT_I_B: "<i>",
|
||||
@@ -118,7 +118,7 @@ class ToHtml(Tokenizer):
|
||||
self.FMT_D_E: "</span>",
|
||||
}
|
||||
else:
|
||||
htmlTags = { # HTML5
|
||||
htmlTags = { # HTML5 (for export)
|
||||
self.FMT_B_B: "<strong>",
|
||||
self.FMT_B_E: "</strong>",
|
||||
self.FMT_I_B: "<em>",
|
||||
@@ -176,17 +176,18 @@ class ToHtml(Tokenizer):
|
||||
aStyle.append("margin-top: 0;")
|
||||
|
||||
if tStyle & self.A_IND_L:
|
||||
aStyle.append("margin-left: %dpx;" % self.mainConf.tabWidth)
|
||||
aStyle.append(f"margin-left: {self.mainConf.tabWidth:d}px;")
|
||||
if tStyle & self.A_IND_R:
|
||||
aStyle.append("margin-right: %dpx;" % self.mainConf.tabWidth)
|
||||
aStyle.append(f"margin-right: {self.mainConf.tabWidth:d}px;")
|
||||
|
||||
if len(aStyle) > 0:
|
||||
hStyle = " style='%s'" % (" ".join(aStyle))
|
||||
stVals = " ".join(aStyle)
|
||||
hStyle = f" style='{stVals}'"
|
||||
else:
|
||||
hStyle = ""
|
||||
|
||||
if self.linkHeaders:
|
||||
aNm = "<a name='T%06d'></a>" % tLine
|
||||
aNm = f"<a name='T{tLine:06d}'></a>"
|
||||
else:
|
||||
aNm = ""
|
||||
|
||||
@@ -200,36 +201,36 @@ class ToHtml(Tokenizer):
|
||||
parClass = ""
|
||||
if len(thisPar) > 0:
|
||||
tTemp = "<br/>".join(thisPar)
|
||||
tmpResult.append("<p%s%s>%s</p>\n" % (parClass, parStyle, tTemp.rstrip()))
|
||||
tmpResult.append(f"<p{parClass+parStyle}>{tTemp.rstrip()}</p>\n")
|
||||
thisPar = []
|
||||
parStyle = None
|
||||
|
||||
elif tType == self.T_TITLE:
|
||||
tHead = tText.replace(r"\\", "<br/>")
|
||||
tmpResult.append("<h1 class='title'%s>%s%s</h1>\n" % (hStyle, aNm, tHead))
|
||||
tmpResult.append(f"<h1 class='title'{hStyle}>{aNm}{tHead}</h1>\n")
|
||||
|
||||
elif tType == self.T_UNNUM:
|
||||
tHead = tText.replace(r"\\", "<br/>")
|
||||
tmpResult.append("<%s%s>%s%s</%s>\n" % (h2, hStyle, aNm, tHead, h2))
|
||||
tmpResult.append(f"<{h2}{hStyle}>{aNm}{tHead}</{h2}>\n")
|
||||
|
||||
elif tType == self.T_HEAD1:
|
||||
tHead = tText.replace(r"\\", "<br/>")
|
||||
tmpResult.append("<%s%s%s>%s%s</%s>\n" % (h1, h1Cl, hStyle, aNm, tHead, h1))
|
||||
tmpResult.append(f"<{h1}{h1Cl}{hStyle}>{aNm}{tHead}</{h1}>\n")
|
||||
|
||||
elif tType == self.T_HEAD2:
|
||||
tHead = tText.replace(r"\\", "<br/>")
|
||||
tmpResult.append("<%s%s>%s%s</%s>\n" % (h2, hStyle, aNm, tHead, h2))
|
||||
tmpResult.append(f"<{h2}{hStyle}>{aNm}{tHead}</{h2}>\n")
|
||||
|
||||
elif tType == self.T_HEAD3:
|
||||
tHead = tText.replace(r"\\", "<br/>")
|
||||
tmpResult.append("<%s%s>%s%s</%s>\n" % (h3, hStyle, aNm, tHead, h3))
|
||||
tmpResult.append(f"<{h3}{hStyle}>{aNm}{tHead}</{h3}>\n")
|
||||
|
||||
elif tType == self.T_HEAD4:
|
||||
tHead = tText.replace(r"\\", "<br/>")
|
||||
tmpResult.append("<%s%s>%s%s</%s>\n" % (h4, hStyle, aNm, tHead, h4))
|
||||
tmpResult.append(f"<{h4}{hStyle}>{aNm}{tHead}</{h4}>\n")
|
||||
|
||||
elif tType == self.T_SEP:
|
||||
tmpResult.append("<p class='sep'>%s</p>\n" % tText)
|
||||
tmpResult.append(f"<p class='sep'>{tText}</p>\n")
|
||||
|
||||
elif tType == self.T_SKIP:
|
||||
tmpResult.append("<p class='skip'> </p>\n")
|
||||
@@ -249,7 +250,7 @@ class ToHtml(Tokenizer):
|
||||
tmpResult.append(self._formatComments(tText))
|
||||
|
||||
elif tType == self.T_KEYWORD and self.doKeywords:
|
||||
tTemp = "<p%s>%s</p>\n" % (hStyle, self._formatKeywords(tText))
|
||||
tTemp = f"<p{hStyle}>{self._formatKeywords(tText)}</p>\n"
|
||||
tmpResult.append(tTemp)
|
||||
|
||||
self.theResult = "".join(tmpResult)
|
||||
@@ -298,9 +299,9 @@ class ToHtml(Tokenizer):
|
||||
"""Replace tabs with spaces in the html.
|
||||
"""
|
||||
htmlText = []
|
||||
eightSpace = spaceChar*nSpaces
|
||||
tabSpace = spaceChar*nSpaces
|
||||
for aLine in self.fullHTML:
|
||||
htmlText.append(aLine.replace("\t", eightSpace))
|
||||
htmlText.append(aLine.replace("\t", tabSpace))
|
||||
|
||||
self.fullHTML = htmlText
|
||||
return
|
||||
@@ -315,75 +316,76 @@ class ToHtml(Tokenizer):
|
||||
mScale = self.lineHeight/1.15
|
||||
textAlign = "justify" if self.doJustify else "left"
|
||||
|
||||
theStyles.append("body {font-family: '%s'; font-size: %dpt;}" % (
|
||||
theStyles.append("body {{font-family: '{0:s}'; font-size: {1:d}pt;}}".format(
|
||||
self.textFont, self.textSize
|
||||
))
|
||||
theStyles.append((
|
||||
"p {"
|
||||
"text-align: %s; line-height: %d%%; "
|
||||
"margin-top: %.2fem; margin-bottom: %.2fem;"
|
||||
"}"
|
||||
) % (
|
||||
"p {{"
|
||||
"text-align: {0}; line-height: {1:d}%; "
|
||||
"margin-top: {2:.2f}em; margin-bottom: {3:.2f}em;"
|
||||
"}}"
|
||||
).format(
|
||||
textAlign,
|
||||
round(100 * self.lineHeight),
|
||||
mScale * self.marginText[0],
|
||||
mScale * self.marginText[1],
|
||||
))
|
||||
theStyles.append((
|
||||
"h1 {"
|
||||
"h1 {{"
|
||||
"color: rgb(66, 113, 174); "
|
||||
"page-break-after: avoid; "
|
||||
"margin-top: %.2fem; "
|
||||
"margin-bottom: %.2fem;"
|
||||
"}"
|
||||
) % (
|
||||
"margin-top: {0:.2f}em; "
|
||||
"margin-bottom: {1:.2f}em;"
|
||||
"}}"
|
||||
).format(
|
||||
mScale * self.marginHead1[0], mScale * self.marginHead1[1]
|
||||
))
|
||||
theStyles.append((
|
||||
"h2 {"
|
||||
"h2 {{"
|
||||
"color: rgb(66, 113, 174); "
|
||||
"page-break-after: avoid; "
|
||||
"margin-top: %.2fem; "
|
||||
"margin-bottom: %.2fem;"
|
||||
"}"
|
||||
) % (
|
||||
"margin-top: {0:.2f}em; "
|
||||
"margin-bottom: {1:.2f}em;"
|
||||
"}}"
|
||||
).format(
|
||||
mScale * self.marginHead2[0], mScale * self.marginHead2[1]
|
||||
))
|
||||
theStyles.append((
|
||||
"h3 {"
|
||||
"h3 {{"
|
||||
"color: rgb(50, 50, 50); "
|
||||
"page-break-after: avoid; "
|
||||
"margin-top: %.2fem; "
|
||||
"margin-bottom: %.2fem;"
|
||||
"}"
|
||||
) % (
|
||||
"margin-top: {0:.2f}em; "
|
||||
"margin-bottom: {1:.2f}em;"
|
||||
"}}"
|
||||
).format(
|
||||
mScale * self.marginHead3[0], mScale * self.marginHead3[1]
|
||||
))
|
||||
theStyles.append((
|
||||
"h4 {"
|
||||
"h4 {{"
|
||||
"color: rgb(50, 50, 50); "
|
||||
"page-break-after: avoid; "
|
||||
"margin-top: %.2fem; "
|
||||
"margin-bottom: %.2fem;"
|
||||
"}"
|
||||
) % (
|
||||
"margin-top: {0:.2f}em; "
|
||||
"margin-bottom: {1:.2f}em;"
|
||||
"}}"
|
||||
).format(
|
||||
mScale * self.marginHead4[0], mScale * self.marginHead4[1]
|
||||
))
|
||||
theStyles.append((
|
||||
".title {"
|
||||
".title {{"
|
||||
"font-size: 2.5em; "
|
||||
"margin-top: %.2fem; "
|
||||
"margin-bottom: %.2fem;"
|
||||
"}"
|
||||
) % (
|
||||
"margin-top: {0:.2f}em; "
|
||||
"margin-bottom: {1:.2f}em;"
|
||||
"}}"
|
||||
).format(
|
||||
mScale * self.marginTitle[0], mScale * self.marginTitle[1]
|
||||
))
|
||||
theStyles.append((
|
||||
".sep, .skip {"
|
||||
".sep, .skip {{"
|
||||
"text-align: center; "
|
||||
"margin-top: %.2fem; "
|
||||
"margin-bottom: %.2fem;}"
|
||||
) % (
|
||||
"margin-top: {0:.2f}em; "
|
||||
"margin-bottom: {1:.2f}em;"
|
||||
"}}"
|
||||
).format(
|
||||
mScale, mScale
|
||||
))
|
||||
|
||||
@@ -421,31 +423,25 @@ class ToHtml(Tokenizer):
|
||||
def _formatKeywords(self, tText):
|
||||
"""Apply HTML formatting to keywords.
|
||||
"""
|
||||
isValid, theBits, thePos = self.theParent.theIndex.scanThis("@"+tText)
|
||||
isValid, theBits, _ = self.theParent.theIndex.scanThis("@"+tText)
|
||||
if not isValid or not theBits:
|
||||
return ""
|
||||
|
||||
retText = ""
|
||||
refTags = []
|
||||
if theBits[0] in nwLabels.KEY_NAME:
|
||||
retText += "<span class='tags'>%s:</span> " % nwLabels.KEY_NAME[theBits[0]]
|
||||
retText += f"<span class='tags'>{nwLabels.KEY_NAME[theBits[0]]}:</span> "
|
||||
if len(theBits) > 1:
|
||||
if theBits[0] == nwKeyWords.TAG_KEY:
|
||||
retText += "<a name='tag_%s'>%s</a>" % (
|
||||
theBits[1], theBits[1]
|
||||
)
|
||||
retText += f"<a name='tag_{theBits[1]}'>{theBits[1]}</a>"
|
||||
else:
|
||||
if self.genMode == self.M_PREVIEW:
|
||||
for tTag in theBits[1:]:
|
||||
refTags.append("<a href='#%s=%s'>%s</a>" % (
|
||||
theBits[0][1:], tTag, tTag
|
||||
))
|
||||
refTags.append(f"<a href='#{theBits[0][1:]}={tTag}'>{tTag}</a>")
|
||||
retText += ", ".join(refTags)
|
||||
else:
|
||||
for tTag in theBits[1:]:
|
||||
refTags.append("<a href='#tag_%s'>%s</a>" % (
|
||||
tTag, tTag
|
||||
))
|
||||
refTags.append(f"<a href='#tag_{tTag}'>{tTag}</a>")
|
||||
retText += ", ".join(refTags)
|
||||
|
||||
return retText
|
||||
|
||||
@@ -267,13 +267,14 @@ class Tokenizer():
|
||||
else:
|
||||
textAlign = self.A_PBB | self.A_CENTRE
|
||||
|
||||
theTitle = "%s: %s" % (self._localLookup("Notes"), theItem.itemName)
|
||||
locNotes = self._localLookup("Notes")
|
||||
theTitle = f"{locNotes}: {theItem.itemName}"
|
||||
self.theTokens = []
|
||||
self.theTokens.append((
|
||||
self.T_TITLE, 0, theTitle, None, textAlign
|
||||
))
|
||||
if self.keepMarkdown:
|
||||
self.theMarkdown.append("# %s\n\n" % theTitle)
|
||||
self.theMarkdown.append(f"# {theTitle}\n\n")
|
||||
|
||||
return True
|
||||
|
||||
@@ -302,7 +303,7 @@ class Tokenizer():
|
||||
errVal = self.tr("Document '{0}' is too big ({1} MB). Skipping.").format(
|
||||
self.theItem.itemName, f"{docSize/1.0e6:.2f}"
|
||||
)
|
||||
self.theText = "# %s\n\n%s\n\n" % (self.tr("ERROR"), errVal)
|
||||
self.theText = "# {0}\n\n{1}\n\n".format(self.tr("ERROR"), errVal)
|
||||
self.errData.append(errVal)
|
||||
|
||||
self.isNone = self.theItem.itemLayout == nwItemLayout.NO_LAYOUT
|
||||
@@ -318,7 +319,7 @@ class Tokenizer():
|
||||
if len(self.theProject.autoReplace) > 0:
|
||||
repDict = {}
|
||||
for aKey, aVal in self.theProject.autoReplace.items():
|
||||
repDict["<%s>" % aKey] = aVal
|
||||
repDict[f"<{aKey}>"] = aVal
|
||||
xRep = re.compile("|".join([re.escape(k) for k in repDict.keys()]), flags=re.DOTALL)
|
||||
self.theText = xRep.sub(lambda x: repDict[x.group(0)], self.theText)
|
||||
|
||||
|
||||
+13
-11
@@ -100,33 +100,33 @@ class ToMarkdown(Tokenizer):
|
||||
# Process Text Type
|
||||
if tType == self.T_EMPTY:
|
||||
if len(thisPar) > 0:
|
||||
tTemp = " \n".join(thisPar)
|
||||
tmpResult.append("%s\n\n" % tTemp.rstrip(" "))
|
||||
tTemp = (" \n".join(thisPar)).rstrip(" ")
|
||||
tmpResult.append(f"{tTemp}\n\n")
|
||||
thisPar = []
|
||||
|
||||
elif tType == self.T_TITLE:
|
||||
tHead = tText.replace(r"\\", "\n")
|
||||
tmpResult.append("# %s\n\n" % tHead)
|
||||
tmpResult.append(f"# {tHead}\n\n")
|
||||
|
||||
elif tType == self.T_UNNUM:
|
||||
tHead = tText.replace(r"\\", "\n")
|
||||
tmpResult.append("## %s\n\n" % tHead)
|
||||
tmpResult.append(f"## {tHead}\n\n")
|
||||
|
||||
elif tType == self.T_HEAD1:
|
||||
tHead = tText.replace(r"\\", "\n")
|
||||
tmpResult.append("# %s\n\n" % tHead)
|
||||
tmpResult.append(f"# {tHead}\n\n")
|
||||
|
||||
elif tType == self.T_HEAD2:
|
||||
tHead = tText.replace(r"\\", "\n")
|
||||
tmpResult.append("## %s\n\n" % tHead)
|
||||
tmpResult.append(f"## {tHead}\n\n")
|
||||
|
||||
elif tType == self.T_HEAD3:
|
||||
tHead = tText.replace(r"\\", "\n")
|
||||
tmpResult.append("### %s\n\n" % tHead)
|
||||
tmpResult.append(f"### {tHead}\n\n")
|
||||
|
||||
elif tType == self.T_HEAD4:
|
||||
tHead = tText.replace(r"\\", "\n")
|
||||
tmpResult.append("#### %s\n\n" % tHead)
|
||||
tmpResult.append(f"#### {tHead}\n\n")
|
||||
|
||||
elif tType == self.T_SEP:
|
||||
tmpResult.append("%s\n\n" % tText)
|
||||
@@ -141,10 +141,12 @@ class ToMarkdown(Tokenizer):
|
||||
thisPar.append(tTemp.rstrip())
|
||||
|
||||
elif tType == self.T_SYNOPSIS and self.doSynopsis:
|
||||
tmpResult.append("**%s:** %s\n\n" % (self._localLookup("Synopsis"), tText))
|
||||
locName = self._localLookup("Synopsis")
|
||||
tmpResult.append(f"**{locName}:** {tText}\n\n")
|
||||
|
||||
elif tType == self.T_COMMENT and self.doComments:
|
||||
tmpResult.append("**%s:** %s\n\n" % (self._localLookup("Comment"), tText))
|
||||
locName = self._localLookup("Comment")
|
||||
tmpResult.append(f"**{locName}:** {tText}\n\n")
|
||||
|
||||
elif tType == self.T_KEYWORD and self.doKeywords:
|
||||
tmpResult.append(self._formatKeywords(tText, tStyle))
|
||||
@@ -189,7 +191,7 @@ class ToMarkdown(Tokenizer):
|
||||
|
||||
retText = ""
|
||||
if theBits[0] in nwLabels.KEY_NAME:
|
||||
retText += "**%s:** " % nwLabels.KEY_NAME[theBits[0]]
|
||||
retText += f"**{nwLabels.KEY_NAME[theBits[0]]}:** "
|
||||
|
||||
if len(theBits) > 1:
|
||||
retText += ", ".join(theBits[1:])
|
||||
|
||||
+40
-37
@@ -45,18 +45,35 @@ XML_NS = {
|
||||
"meta": "urn:oasis:names:tc:opendocument:xmlns:meta:1.0",
|
||||
"fo": "urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0",
|
||||
}
|
||||
MANI_NS = {
|
||||
"manifest": "urn:oasis:names:tc:opendocument:xmlns:manifest:1.0"
|
||||
}
|
||||
OFFICE_NS = {
|
||||
"office": XML_NS["office"]
|
||||
}
|
||||
|
||||
|
||||
def _mkTag(nsName, tagName, nsMap=XML_NS):
|
||||
"""Assemble namespace and tag name.
|
||||
"""
|
||||
theNS = nsMap.get(nsName, "")
|
||||
if theNS:
|
||||
return f"{{{theNS}}}{tagName}"
|
||||
logger.warning("Missing xml namespace '%s'", nsName)
|
||||
return tagName
|
||||
|
||||
|
||||
# Mimetype and Version
|
||||
X_MIME = "application/vnd.oasis.opendocument.text"
|
||||
X_VERS = "1.2"
|
||||
|
||||
# Text Formatting Tags
|
||||
TAG_BR = "{%s}line-break" % XML_NS["text"]
|
||||
TAG_SPC = "{%s}s" % XML_NS["text"]
|
||||
TAG_NSPC = "{%s}c" % XML_NS["text"]
|
||||
TAG_TAB = "{%s}tab" % XML_NS["text"]
|
||||
TAG_SPAN = "{%s}span" % XML_NS["text"]
|
||||
TAG_STNM = "{%s}style-name" % XML_NS["text"]
|
||||
TAG_BR = _mkTag("text", "line-break")
|
||||
TAG_SPC = _mkTag("text", "s")
|
||||
TAG_NSPC = _mkTag("text", "c")
|
||||
TAG_TAB = _mkTag("text", "tab")
|
||||
TAG_SPAN = _mkTag("text", "span")
|
||||
TAG_STNM = _mkTag("text", "style-name")
|
||||
|
||||
# Formatting Codes
|
||||
X_BLD = 0x01 # Bold format
|
||||
@@ -313,7 +330,7 @@ class ToOdt(Tokenizer):
|
||||
|
||||
# Meta Data
|
||||
xMeta = etree.SubElement(self._xMeta, _mkTag("meta", "creation-date"))
|
||||
xMeta.text = datetime.now().strftime("%Y-%m-%dT%H:%M:%S")
|
||||
xMeta.text = datetime.now().strftime(r"%Y-%m-%dT%H:%M:%S")
|
||||
|
||||
xMeta = etree.SubElement(self._xMeta, _mkTag("meta", "generator"))
|
||||
xMeta.text = f"novelWriter/{novelwriter.__version__}"
|
||||
@@ -472,24 +489,22 @@ class ToOdt(Tokenizer):
|
||||
def saveOpenDocText(self, savePath):
|
||||
"""Save the data to an .odt file.
|
||||
"""
|
||||
mMap = {"manifest": "urn:oasis:names:tc:opendocument:xmlns:manifest:1.0"}
|
||||
mMani = "{%s}manifest" % mMap["manifest"]
|
||||
mVers = "{%s}version" % mMap["manifest"]
|
||||
mPath = "{%s}full-path" % mMap["manifest"]
|
||||
mType = "{%s}media-type" % mMap["manifest"]
|
||||
mFile = "{%s}file-entry" % mMap["manifest"]
|
||||
mMani = _mkTag("manifest", "manifest", nsMap=MANI_NS)
|
||||
mVers = _mkTag("manifest", "version", nsMap=MANI_NS)
|
||||
mPath = _mkTag("manifest", "full-path", nsMap=MANI_NS)
|
||||
mType = _mkTag("manifest", "media-type", nsMap=MANI_NS)
|
||||
mFile = _mkTag("manifest", "file-entry", nsMap=MANI_NS)
|
||||
|
||||
xMani = etree.Element(mMani, attrib={mVers: X_VERS}, nsmap=mMap)
|
||||
xMani = etree.Element(mMani, attrib={mVers: X_VERS}, nsmap=MANI_NS)
|
||||
etree.SubElement(xMani, mFile, attrib={mPath: "/", mVers: X_VERS, mType: X_MIME})
|
||||
etree.SubElement(xMani, mFile, attrib={mPath: "settings.xml", mType: "text/xml"})
|
||||
etree.SubElement(xMani, mFile, attrib={mPath: "content.xml", mType: "text/xml"})
|
||||
etree.SubElement(xMani, mFile, attrib={mPath: "meta.xml", mType: "text/xml"})
|
||||
etree.SubElement(xMani, mFile, attrib={mPath: "styles.xml", mType: "text/xml"})
|
||||
|
||||
sMap = {"office": "urn:oasis:names:tc:opendocument:xmlns:office:1.0"}
|
||||
oRoot = "{%s}document-settings" % sMap["office"]
|
||||
oSett = "{%s}settings" % sMap["office"]
|
||||
xSett = etree.Element(oRoot, nsmap=sMap)
|
||||
oRoot = _mkTag("office", "document-settings", nsMap=OFFICE_NS)
|
||||
oSett = _mkTag("office", "settings", nsMap=OFFICE_NS)
|
||||
xSett = etree.Element(oRoot, nsmap=OFFICE_NS)
|
||||
etree.SubElement(xSett, oSett)
|
||||
|
||||
with ZipFile(savePath, mode="w") as outFile:
|
||||
@@ -520,16 +535,16 @@ class ToOdt(Tokenizer):
|
||||
"""Apply formatting to synopsis lines.
|
||||
"""
|
||||
sSynop = self._localLookup("Synopsis")
|
||||
rTxt = "**%s:** %s" % (sSynop, tText)
|
||||
rFmt = "_B%s b_ %s" % (" "*len(sSynop), " "*len(tText))
|
||||
rTxt = "**{0}:** {1}".format(sSynop, tText)
|
||||
rFmt = "_B{0} b_ {1}".format(" "*len(sSynop), " "*len(tText))
|
||||
return rTxt, rFmt
|
||||
|
||||
def _formatComments(self, tText):
|
||||
"""Apply formatting to comments.
|
||||
"""
|
||||
sComm = self._localLookup("Comment")
|
||||
rTxt = "**%s:** %s" % (sComm, tText)
|
||||
rFmt = "_B%s b_ %s" % (" "*len(sComm), " "*len(tText))
|
||||
rTxt = "**{0}:** {1}".format(sComm, tText)
|
||||
rFmt = "_B{0} b_ {1}".format(" "*len(sComm), " "*len(tText))
|
||||
return rTxt, rFmt
|
||||
|
||||
def _formatKeywords(self, tText):
|
||||
@@ -543,8 +558,8 @@ class ToOdt(Tokenizer):
|
||||
rFmt = ""
|
||||
if theBits[0] in nwLabels.KEY_NAME:
|
||||
tText = nwLabels.KEY_NAME[theBits[0]]
|
||||
rTxt += "**%s:** " % tText
|
||||
rFmt += "_B%s b_ " % (" "*len(tText))
|
||||
rTxt += "**{0}:** ".format(tText)
|
||||
rFmt += "_B{0} b_ ".format(" "*len(tText))
|
||||
if len(theBits) > 1:
|
||||
if theBits[0] == nwKeyWords.TAG_KEY:
|
||||
rTxt += theBits[1]
|
||||
@@ -1468,16 +1483,4 @@ class XMLParagraph():
|
||||
|
||||
return
|
||||
|
||||
|
||||
# =============================================================================================== #
|
||||
# Local Functions
|
||||
# =============================================================================================== #
|
||||
|
||||
def _mkTag(nsName, tagName):
|
||||
"""Assemble namespace and tag name.
|
||||
"""
|
||||
theNS = XML_NS.get(nsName, "")
|
||||
if theNS:
|
||||
return "{%s}%s" % (theNS, tagName)
|
||||
logger.warning("Missing xml namespace '%s'", nsName)
|
||||
return tagName
|
||||
# END Class XMLParagraph
|
||||
|
||||
@@ -158,7 +158,7 @@ class NWTree():
|
||||
continue
|
||||
tFile = tHandle+".nwd"
|
||||
if os.path.isfile(os.path.join(self.theProject.projContent, tFile)):
|
||||
tocLine = "%-25s %-9s %-8s %s" % (
|
||||
tocLine = "{0:<25s} {1:<9s} {2:<8s} {3:s}".format(
|
||||
os.path.join("content", tFile),
|
||||
tItem.itemClass.name,
|
||||
tItem.itemLayout.name,
|
||||
@@ -175,7 +175,7 @@ class NWTree():
|
||||
outFile.write("Table of Contents\n")
|
||||
outFile.write("=================\n")
|
||||
outFile.write("\n")
|
||||
outFile.write("%-25s %-9s %-8s %s\n" % (
|
||||
outFile.write("{0:<25s} {1:<9s} {2:<8s} {3:s}\n".format(
|
||||
"File Name", "Class", "Layout", "Document Label"
|
||||
))
|
||||
outFile.write("-"*max(tocLen, 62) + "\n")
|
||||
@@ -285,12 +285,12 @@ class NWTree():
|
||||
tItem = self.__getitem__(tHandle)
|
||||
if tItem is not None:
|
||||
tTree.append(tHandle)
|
||||
for i in range(nwConst.MAX_DEPTH + 1):
|
||||
for _ in range(nwConst.MAX_DEPTH + 1):
|
||||
if tItem.itemParent is None:
|
||||
return tTree
|
||||
else:
|
||||
tHandle = tItem.itemParent
|
||||
tItem = self.__getitem__(tHandle)
|
||||
tItem = self.__getitem__(tHandle)
|
||||
if tItem is None:
|
||||
return tTree
|
||||
else:
|
||||
@@ -341,7 +341,7 @@ class NWTree():
|
||||
if tItem is None:
|
||||
return False
|
||||
if tItem.itemType != nwItemType.FILE:
|
||||
logger.error("Item %s is not a file", tHandle)
|
||||
logger.error("Item '%s' is not a file", tHandle)
|
||||
return False
|
||||
if not isinstance(itemLayout, nwItemLayout):
|
||||
return False
|
||||
|
||||
@@ -65,7 +65,7 @@ class GuiAbout(QDialog):
|
||||
self.nwIcon = QLabel()
|
||||
self.nwIcon.setPixmap(self.theParent.theTheme.getPixmap("novelwriter", (nPx, nPx)))
|
||||
self.lblName = QLabel("<b>novelWriter</b>")
|
||||
self.lblVers = QLabel("v%s" % novelwriter.__version__)
|
||||
self.lblVers = QLabel(f"v{novelwriter.__version__}")
|
||||
self.lblDate = QLabel(datetime.strptime(novelwriter.__date__, "%Y-%m-%d").strftime("%x"))
|
||||
|
||||
self.leftBox = QVBoxLayout()
|
||||
@@ -178,7 +178,7 @@ class GuiAbout(QDialog):
|
||||
),
|
||||
)
|
||||
|
||||
aboutMsg += "<h4>%s</h4><p>%s</p>" % (
|
||||
aboutMsg += "<h4>{0}</h4><p>{1}</p>".format(
|
||||
self.tr("Translations"),
|
||||
self._wrapTable([
|
||||
("English", "Veronica Berglyd Olsen"),
|
||||
@@ -193,7 +193,7 @@ class GuiAbout(QDialog):
|
||||
theIcons = self.theParent.theTheme.theIcons
|
||||
if theTheme.themeName and theTheme.themeAuthor != "N/A":
|
||||
licURL = f"<a href='{theTheme.themeLicenseUrl}'>{theTheme.themeLicense}</a>"
|
||||
aboutMsg += "<h4>%s</h4><p>%s</p>" % (
|
||||
aboutMsg += "<h4>{0}</h4><p>{1}</p>".format(
|
||||
self.tr("Theme: {0}").format(theTheme.themeName),
|
||||
self._wrapTable([
|
||||
(self.tr("Author"), theTheme.themeAuthor),
|
||||
@@ -204,7 +204,7 @@ class GuiAbout(QDialog):
|
||||
|
||||
if theIcons.themeName:
|
||||
licURL = f"<a href='{theIcons.themeLicenseUrl}'>{theIcons.themeLicense}</a>"
|
||||
aboutMsg += "<h4>%s</h4><p>%s</p>" % (
|
||||
aboutMsg += "<h4>{0}</h4><p>{1}</p>".format(
|
||||
self.tr("Icons: {0}").format(theIcons.themeName),
|
||||
self._wrapTable([
|
||||
(self.tr("Author"), theIcons.themeAuthor),
|
||||
@@ -215,7 +215,7 @@ class GuiAbout(QDialog):
|
||||
|
||||
if theTheme.syntaxName:
|
||||
licURL = f"<a href='{theTheme.syntaxLicenseUrl}'>{theTheme.syntaxLicense}</a>"
|
||||
aboutMsg += "<h4>%s</h4><p>%s</p>" % (
|
||||
aboutMsg += "<h4>{0}</h4><p>{1}</p>".format(
|
||||
self.tr("Syntax: {0}").format(theTheme.syntaxName),
|
||||
self._wrapTable([
|
||||
(self.tr("Author"), theTheme.syntaxAuthor),
|
||||
@@ -258,7 +258,7 @@ class GuiAbout(QDialog):
|
||||
theTable.append(
|
||||
f"<tr><td><b>{aLabel}:</b></td><td>{aValue}</td></tr>"
|
||||
)
|
||||
return "<table>%s</table>" % "".join(theTable)
|
||||
return "<table>{0}</table>".format("".join(theTable))
|
||||
|
||||
def _setStyleSheet(self):
|
||||
"""Set stylesheet for all browser tabs
|
||||
|
||||
@@ -55,7 +55,7 @@ class GuiDocMerge(QDialog):
|
||||
self.outerBox = QVBoxLayout()
|
||||
self.setWindowTitle(self.tr("Merge Documents"))
|
||||
|
||||
self.headLabel = QLabel("<b>%s</b>" % self.tr("Documents to Merge"))
|
||||
self.headLabel = QLabel("<b>{0}</b>".format(self.tr("Documents to Merge")))
|
||||
self.helpLabel = QHelpLabel(
|
||||
self.tr("Drag and drop items to change the order."), self.theParent.theTheme.helpText
|
||||
)
|
||||
|
||||
@@ -59,7 +59,7 @@ class GuiDocSplit(QDialog):
|
||||
self.outerBox = QVBoxLayout()
|
||||
self.setWindowTitle(self.tr("Split Document"))
|
||||
|
||||
self.headLabel = QLabel("<b>%s</b>" % self.tr("Document Headers"))
|
||||
self.headLabel = QLabel("<b>{0}</b>".format(self.tr("Document Headers")))
|
||||
self.helpLabel = QHelpLabel(
|
||||
self.tr("Select the maximum level to split into files."),
|
||||
self.theParent.theTheme.helpText
|
||||
@@ -174,7 +174,7 @@ class GuiDocSplit(QDialog):
|
||||
|
||||
msgYes = self.theParent.askQuestion(
|
||||
self.tr("Split Document"),
|
||||
"%s<br><br>%s" % (
|
||||
"{0}<br><br>{1}".format(
|
||||
self.tr(
|
||||
"The document will be split into {0} file(s) in a new folder. "
|
||||
"The original document will remain intact.").format(nFiles),
|
||||
@@ -196,10 +196,8 @@ class GuiDocSplit(QDialog):
|
||||
# Loop through, and create the files
|
||||
for wTitle, iStart, iEnd in finalOrder:
|
||||
|
||||
if srcItem.itemClass == nwItemClass.NOVEL:
|
||||
itemLayout = nwItemLayout.DOCUMENT
|
||||
else:
|
||||
itemLayout = nwItemLayout.NOTE
|
||||
isNovel = srcItem.itemClass == nwItemClass.NOVEL
|
||||
itemLayout = nwItemLayout.DOCUMENT if isNovel else nwItemLayout.NOTE
|
||||
|
||||
wTitle = wTitle.lstrip("#")
|
||||
wTitle = wTitle.strip()
|
||||
@@ -209,9 +207,8 @@ class GuiDocSplit(QDialog):
|
||||
newItem.setLayout(itemLayout)
|
||||
newItem.setStatus(srcItem.itemStatus)
|
||||
logger.verbose(
|
||||
"Creating new document %s with text from line %d to %d" % (
|
||||
nHandle, iStart+1, iEnd
|
||||
)
|
||||
"Creating new document '%s' with text from line %d to %d",
|
||||
nHandle, iStart+1, iEnd
|
||||
)
|
||||
|
||||
theText = "\n".join(self.sourceText[iStart:iEnd])
|
||||
@@ -273,7 +270,8 @@ class GuiDocSplit(QDialog):
|
||||
spLevel = self.splitLevel.currentData()
|
||||
self.optState.setValue("GuiDocSplit", "spLevel", spLevel)
|
||||
logger.debug(
|
||||
"Scanning document %s for headings level <= %d" % (self.sourceItem, spLevel)
|
||||
"Scanning document '%s' for headings level <= %d",
|
||||
self.sourceItem, spLevel
|
||||
)
|
||||
|
||||
self.sourceText = theText.splitlines()
|
||||
|
||||
@@ -57,7 +57,8 @@ class GuiItemEditor(QDialog):
|
||||
|
||||
self.theItem = self.theProject.projTree[tHandle]
|
||||
if self.theItem is None:
|
||||
self._doClose()
|
||||
self.close()
|
||||
return
|
||||
|
||||
self.setWindowTitle(self.tr("Item Settings"))
|
||||
|
||||
|
||||
@@ -719,7 +719,7 @@ class GuiDocEditor(QTextEdit):
|
||||
), nwAlert.INFO)
|
||||
theMode = False
|
||||
|
||||
if self.spEnchant.spellLanguage is None:
|
||||
if self.spEnchant.spellLanguage() is None:
|
||||
theMode = False
|
||||
|
||||
self._spellCheck = theMode
|
||||
|
||||
+18
-20
@@ -784,7 +784,7 @@ class GuiMain(QMainWindow):
|
||||
def passDocumentAction(self, theAction):
|
||||
"""Pass on document action to the document viewer if it has
|
||||
focus, or pass it to the document editor if it or any of
|
||||
its clid widgets have focus. If neither has focus, ignore the
|
||||
its child widgets have focus. If neither has focus, ignore the
|
||||
action.
|
||||
"""
|
||||
if self.docViewer.hasFocus():
|
||||
@@ -836,13 +836,13 @@ class GuiMain(QMainWindow):
|
||||
|
||||
if tHandle is None:
|
||||
logger.warning("No item selected")
|
||||
return
|
||||
return False
|
||||
|
||||
tItem = self.theProject.projTree[tHandle]
|
||||
if tItem is None:
|
||||
return
|
||||
return False
|
||||
if tItem.itemType not in nwLists.REG_TYPES:
|
||||
return
|
||||
return False
|
||||
|
||||
logger.verbose("Requesting change to item '%s'", tHandle)
|
||||
dlgProj = GuiItemEditor(self, tHandle)
|
||||
@@ -853,7 +853,7 @@ class GuiMain(QMainWindow):
|
||||
self.docEditor.updateDocInfo(tHandle)
|
||||
self.docViewer.updateDocInfo(tHandle)
|
||||
|
||||
return
|
||||
return True
|
||||
|
||||
def rebuildTrees(self):
|
||||
"""Rebuild the project tree.
|
||||
@@ -938,7 +938,7 @@ class GuiMain(QMainWindow):
|
||||
##
|
||||
|
||||
def showProjectLoadDialog(self):
|
||||
"""Opens the projects dialog for selecting either existing
|
||||
"""Open the projects dialog for selecting either existing
|
||||
projects from a cache of recently opened projects, or provide a
|
||||
browse button for projects not yet cached. Selecting to create a
|
||||
new project is forwarded to the new project wizard.
|
||||
@@ -1058,7 +1058,7 @@ class GuiMain(QMainWindow):
|
||||
return
|
||||
|
||||
def showWritingStatsDialog(self):
|
||||
"""Open the session log dialog.
|
||||
"""Open the session stats dialog.
|
||||
"""
|
||||
if not self.hasProject:
|
||||
logger.error("No project open")
|
||||
@@ -1092,17 +1092,17 @@ class GuiMain(QMainWindow):
|
||||
if showNotes:
|
||||
dlgAbout.showReleaseNotes()
|
||||
|
||||
return
|
||||
return True
|
||||
|
||||
def showAboutQtDialog(self):
|
||||
"""Show the about dialog for Qt.
|
||||
"""
|
||||
msgBox = QMessageBox()
|
||||
msgBox.aboutQt(self, "About Qt")
|
||||
return
|
||||
return True
|
||||
|
||||
def showUpdatesDialog(self):
|
||||
"""Show the updates dialog for novelWriter.
|
||||
"""Show the check for updates dialog.
|
||||
"""
|
||||
dlgUpdate = getGuiItem("GuiUpdates")
|
||||
if dlgUpdate is None:
|
||||
@@ -1117,11 +1117,11 @@ class GuiMain(QMainWindow):
|
||||
return
|
||||
|
||||
def makeAlert(self, theMessage, theLevel=nwAlert.INFO):
|
||||
"""Alert both the user and the logger at the same time. Message
|
||||
can be either a string or an array of strings.
|
||||
"""Alert both the user and the logger at the same time. The
|
||||
message can be either a string or a list of strings.
|
||||
"""
|
||||
if isinstance(theMessage, list):
|
||||
theMessage = list(filter(None, theMessage))
|
||||
theMessage = list(filter(None, theMessage)) # Strip empty strings
|
||||
popMsg = "<br>".join(theMessage)
|
||||
logMsg = theMessage
|
||||
else:
|
||||
@@ -1246,13 +1246,11 @@ class GuiMain(QMainWindow):
|
||||
self.theProject.setLastViewed(None)
|
||||
bPos = self.splitMain.sizes()
|
||||
self.splitView.setVisible(False)
|
||||
vPos = [bPos[1], 0]
|
||||
self.splitDocs.setSizes(vPos)
|
||||
self.splitDocs.setSizes([bPos[1], 0])
|
||||
return not self.splitView.isVisible()
|
||||
|
||||
def toggleFocusMode(self):
|
||||
"""Main GUI Focus Mode hides tree, view pane and optionally also
|
||||
statusbar and menu.
|
||||
"""Main GUI Focus Mode hides tree, view, statusbar and menu.
|
||||
"""
|
||||
if self.docEditor.docHandle() is None:
|
||||
logger.error("No document open, so not activating Focus Mode")
|
||||
@@ -1409,7 +1407,7 @@ class GuiMain(QMainWindow):
|
||||
return True
|
||||
|
||||
def _autoSaveProject(self):
|
||||
"""Triggered by the auto-save project timer to save the project.
|
||||
"""Triggered by the autosave project timer to save the project.
|
||||
"""
|
||||
doSave = self.hasProject
|
||||
doSave &= self.theProject.projChanged
|
||||
@@ -1422,7 +1420,7 @@ class GuiMain(QMainWindow):
|
||||
return
|
||||
|
||||
def _autoSaveDocument(self):
|
||||
"""Triggered by the auto-save document timer to save the
|
||||
"""Triggered by the autosave document timer to save the
|
||||
document.
|
||||
"""
|
||||
if self.hasProject and self.docEditor.docChanged():
|
||||
@@ -1511,7 +1509,7 @@ class GuiMain(QMainWindow):
|
||||
|
||||
@pyqtSlot()
|
||||
def _timeTick(self):
|
||||
"""Triggered on every tick of the timer.
|
||||
"""Triggered on every tick of the main timer.
|
||||
"""
|
||||
if not self.hasProject:
|
||||
return
|
||||
|
||||
Reference in New Issue
Block a user