Merge branch 'main' into release_1.0rc1

This commit is contained in:
Veronica K. B. Olsen
2020-11-01 17:08:44 +01:00
20 changed files with 166 additions and 113 deletions
+2 -2
View File
@@ -165,9 +165,9 @@ def formatTimeStamp(theTime, fileSafe=False):
it to a timestamp string.
"""
if fileSafe:
return datetime.fromtimestamp(theTime).strftime(nwConst.fStampFmt)
return datetime.fromtimestamp(theTime).strftime(nwConst.FMT_FSTAMP)
else:
return datetime.fromtimestamp(theTime).strftime(nwConst.tStampFmt)
return datetime.fromtimestamp(theTime).strftime(nwConst.FMT_TSTAMP)
def formatTime(tS):
"""Format a time in seconds in HH:MM:SS format or d-HH:MM:SS format
+7 -7
View File
@@ -37,7 +37,7 @@ from shutil import which
from PyQt5.Qt import PYQT_VERSION_STR
from PyQt5.QtCore import QT_VERSION_STR, QStandardPaths, QSysInfo
from nw.constants import nwFiles, nwUnicode
from nw.constants import nwConst, nwFiles, nwUnicode
from nw.common import splitVersionNumber, formatTimeStamp
logger = logging.getLogger(__name__)
@@ -88,14 +88,14 @@ class Config:
self.guiIcons = "typicons_colour_light"
self.guiDark = False # Load icons for dark backgrounds, if available
self.guiLang = "en" # Hardcoded for now since the GUI is only in English
self.guiFont = "" # Defaults to system defualt font
self.guiFont = "" # Defaults to system default font
self.guiFontSize = 11
self.guiScale = 1.0 # Set automatically by Theme class
## Sizes
self.winGeometry = [1100, 650]
self.treeColWidth = [120, 30, 50]
self.projColWidth = [140, 55, 140]
self.winGeometry = [1200, 650]
self.treeColWidth = [200, 50, 30]
self.projColWidth = [200, 60, 140]
self.mainPanePos = [300, 800]
self.docPanePos = [400, 400]
self.viewPanePos = [500, 150]
@@ -284,7 +284,7 @@ class Config:
logger.verbose("App path: %s" % self.appPath)
logger.verbose("Last path: %s" % self.lastPath)
# If config folder does not exist, make it.
# If config folder does not exist, create it.
# This assumes that the os config folder itself exists.
if not os.path.isdir(self.confPath):
try:
@@ -327,7 +327,7 @@ class Config:
self._checkOptionalPackages()
if self.spellTool is None:
self.spellTool = "internal"
self.spellTool = nwConst.SP_INTERNAL
if self.spellLanguage is None:
self.spellLanguage = "en"
+30 -6
View File
@@ -29,13 +29,19 @@ from nw.constants.enum import nwItemClass, nwItemLayout, nwOutline
class nwConst():
tStampFmt = "%Y-%m-%d %H:%M:%S" # Default format
fStampFmt = "%Y-%m-%d %H.%M.%S" # FileName safe format
dStampFmt = "%Y-%m-%d" # Date only format
# Date and Time Formats
FMT_TSTAMP = "%Y-%m-%d %H:%M:%S" # Default format
FMT_FSTAMP = "%Y-%m-%d %H.%M.%S" # FileName safe format
FMT_DSTAMP = "%Y-%m-%d" # Date only format
maxDepth = 30 # Maximum folder depth of a project
maxDocSize = 5000000 # Maxium size of a single document
maxBuildSize = 10000000 # Maxium size of a project build
# Various Hard Limits
MAX_DEPTH = 30 # Maximum folder depth of a project
MAX_DOCSIZE = 5000000 # Maxium size of a single document
MAX_BUILDSIZE = 10000000 # Maxium size of a project build
# Spell Check Providers
SP_INTERNAL = "internal"
SP_ENCHANT = "enchant"
# END Class nwConst
@@ -74,6 +80,24 @@ class nwKeyWords:
ENTITY_KEY = "@entity"
CUSTOM_KEY = "@custom"
# Set of Valid Keys
VALID_KEYS = {
TAG_KEY, POV_KEY, CHAR_KEY, PLOT_KEY, TIME_KEY,
WORLD_KEY, OBJECT_KEY, ENTITY_KEY, CUSTOM_KEY
}
# Map from Keys to Item Class
KEY_CLASS = {
CHAR_KEY : nwItemClass.CHARACTER,
POV_KEY : nwItemClass.CHARACTER,
PLOT_KEY : nwItemClass.PLOT,
TIME_KEY : nwItemClass.TIMELINE,
WORLD_KEY : nwItemClass.WORLD,
OBJECT_KEY : nwItemClass.OBJECT,
ENTITY_KEY : nwItemClass.ENTITY,
CUSTOM_KEY : nwItemClass.CUSTOM,
}
# END Class nwKeyWords
class nwLabels():
+1 -1
View File
@@ -251,7 +251,7 @@ class NWDoc():
self._docMeta["layout"] = nwItemLayout[metaBits[1]]
else:
logger.debug("Ignoring meta data: '%s'" % metaLine)
logger.debug("Ignoring meta data: '%s'" % metaLine.strip())
return
+3 -25
View File
@@ -42,28 +42,6 @@ logger = logging.getLogger(__name__)
class NWIndex():
VALID_KEYS = {
nwKeyWords.TAG_KEY,
nwKeyWords.PLOT_KEY,
nwKeyWords.POV_KEY,
nwKeyWords.CHAR_KEY,
nwKeyWords.WORLD_KEY,
nwKeyWords.TIME_KEY,
nwKeyWords.OBJECT_KEY,
nwKeyWords.ENTITY_KEY,
nwKeyWords.CUSTOM_KEY
}
TAG_CLASS = {
nwKeyWords.CHAR_KEY : nwItemClass.CHARACTER,
nwKeyWords.POV_KEY : nwItemClass.CHARACTER,
nwKeyWords.PLOT_KEY : nwItemClass.PLOT,
nwKeyWords.TIME_KEY : nwItemClass.TIMELINE,
nwKeyWords.WORLD_KEY : nwItemClass.WORLD,
nwKeyWords.OBJECT_KEY : nwItemClass.OBJECT,
nwKeyWords.ENTITY_KEY : nwItemClass.ENTITY,
nwKeyWords.CUSTOM_KEY : nwItemClass.CUSTOM,
}
def __init__(self, theProject, theParent):
# Internal
@@ -530,7 +508,7 @@ class NWIndex():
return []
# Check that the key is valid
isGood[0] = theBits[0] in self.VALID_KEYS
isGood[0] = theBits[0] in nwKeyWords.VALID_KEYS
if not isGood[0] or nBits == 1:
return isGood
@@ -550,7 +528,7 @@ class NWIndex():
# If we're still here, we better check that the references exist
for n in range(1, nBits):
if theBits[n] in self.tagIndex:
isGood[n] = self.TAG_CLASS[theBits[0]].name == self.tagIndex[theBits[n]][2]
isGood[n] = nwKeyWords.KEY_CLASS[theBits[0]].name == self.tagIndex[theBits[n]][2]
return isGood
@@ -608,7 +586,7 @@ class NWIndex():
section. sTitle must be a string.
"""
theRefs = {}
for tKey in self.TAG_CLASS:
for tKey in nwKeyWords.KEY_CLASS:
theRefs[tKey] = []
if tHandle not in self.refIndex:
+12 -16
View File
@@ -31,7 +31,7 @@ import os
from difflib import get_close_matches
from nw.constants import isoLanguage
from nw.constants import nwConst, isoLanguage
logger = logging.getLogger(__name__)
@@ -41,11 +41,8 @@ logger = logging.getLogger(__name__)
class NWSpellCheck():
SP_INTERNAL = "internal"
SP_ENCHANT = "enchant"
theDict = None
PROJW = []
projDict = []
def __init__(self):
self.mainConf = nw.CONFIG
@@ -71,9 +68,9 @@ class NWSpellCheck():
def addWord(self, newWord):
"""Add a word to the project dictionary.
"""
if self.projectDict is not None and newWord not in self.PROJW:
if self.projectDict is not None and newWord not in self.projDict:
newWord = newWord.strip()
self.PROJW.append(newWord)
self.projDict.append(newWord)
try:
with open(self.projectDict, mode="a+", encoding="utf-8") as outFile:
outFile.write("%s\n" % newWord)
@@ -110,7 +107,7 @@ class NWSpellCheck():
"""Read the content of the project dictionary, and add it to the
lookup lists.
"""
self.PROJW = []
self.projDict = []
if projectDict is not None:
self.projectDict = projectDict
if not os.path.isfile(projectDict):
@@ -120,9 +117,9 @@ class NWSpellCheck():
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.PROJW:
self.PROJW.append(theLine)
logger.debug("Project word list contains %d words" % len(self.PROJW))
if len(theLine) > 0 and theLine not in self.projDict:
self.projDict.append(theLine)
logger.debug("Project word list contains %d words" % len(self.projDict))
except Exception as e:
logger.error("Failed to load project word list")
logger.error(str(e))
@@ -157,7 +154,7 @@ class NWSpellEnchant(NWSpellCheck):
self.spellLanguage = None
self._readProjectDictionary(projectDict)
for pWord in self.PROJW:
for pWord in self.projDict:
self.theDict.add_to_session(pWord)
return
@@ -236,7 +233,6 @@ class NWSpellSimple(NWSpellCheck):
when no other is available. This method is fairly slow compared to
other implementations.
"""
WORDS = []
def __init__(self):
@@ -266,7 +262,7 @@ class NWSpellSimple(NWSpellCheck):
self.spellLanguage = None
self._readProjectDictionary(projectDict)
for pWord in self.PROJW:
for pWord in self.projDict:
if pWord not in self.WORDS:
self.WORDS.append(pWord)
@@ -324,7 +320,7 @@ class NWSpellSimple(NWSpellCheck):
if theBits[1] != ".dict":
continue
spName = "%s [internal]" % self.expandLanguage(theBits[0])
spName = "%s [%s]" % (self.expandLanguage(theBits[0]), nwConst.SP_INTERNAL)
retList.append((theBits[0], spName))
return retList
@@ -333,6 +329,6 @@ class NWSpellSimple(NWSpellCheck):
"""Return the tag and provider of the currently loaded
dictionary.
"""
return self.theLang, "internal"
return self.theLang, nwConst.SP_INTERNAL
# END Class NWSpellSimple
+1 -1
View File
@@ -216,7 +216,7 @@ class Tokenizer():
self.theText = theDocument.openDocument(theHandle)
docSize = len(self.theText)
if docSize > nwConst.maxDocSize:
if docSize > nwConst.MAX_DOCSIZE:
errVal = "Document '%s' is too big (%.2f MB). Skipping." % (
self.theItem.itemName, docSize/1.0e6
)
+2 -2
View File
@@ -260,7 +260,7 @@ class NWTree():
"""
tItem = self.__getitem__(tHandle)
if tItem is not None:
for i in range(nwConst.maxDepth + 1):
for i in range(nwConst.MAX_DEPTH + 1):
if tItem.itemParent is None:
return tItem
else:
@@ -278,7 +278,7 @@ class NWTree():
tItem = self.__getitem__(tHandle)
if tItem is not None:
tTree.append(tHandle)
for i in range(nwConst.maxDepth + 1):
for i in range(nwConst.MAX_DEPTH + 1):
if tItem.itemParent is None:
return tTree
else:
+12 -5
View File
@@ -422,7 +422,7 @@ class GuiBuildNovel(QDialog):
self.buttonBox.addWidget(self.btnSave)
self.buttonBox.addWidget(self.btnPrint)
self.buttonBox.addWidget(self.btnClose)
self.buttonBox.setSpacing(4)
self.buttonBox.setSpacing(self.mainConf.pxInt(4))
# Assemble GUI
# ============
@@ -464,12 +464,13 @@ class GuiBuildNovel(QDialog):
self.toolsArea.setHorizontalScrollBarPolicy(Qt.ScrollBarAsNeeded)
# Tools and Buttons Layout
tSp = self.mainConf.pxInt(8)
self.innerBox = QVBoxLayout()
self.innerBox.addWidget(self.toolsArea)
self.innerBox.addSpacing(8)
self.innerBox.addSpacing(tSp)
self.innerBox.addWidget(self.buildProgress)
self.innerBox.addWidget(self.buildNovel)
self.innerBox.addSpacing(8)
self.innerBox.addSpacing(tSp)
self.innerBox.addLayout(self.buttonBox)
# Tools and Buttons Wrapper Widget
@@ -482,6 +483,12 @@ class GuiBuildNovel(QDialog):
self.mainSplit.addWidget(self.docView)
self.mainSplit.setSizes([boxWidth, docWidth])
self.idxSettings = self.mainSplit.indexOf(self.innerWidget)
self.idxDocument = self.mainSplit.indexOf(self.docView)
self.mainSplit.setCollapsible(self.idxSettings, False)
self.mainSplit.setCollapsible(self.idxDocument, False)
# Outer Layout
self.outerBox = QHBoxLayout()
self.outerBox.addWidget(self.mainSplit)
@@ -508,7 +515,7 @@ class GuiBuildNovel(QDialog):
self.docView.setStyleSheet(self.htmlStyle)
htmlSize = sum([len(x) for x in self.htmlText])
if htmlSize < nwConst.maxBuildSize:
if htmlSize < nwConst.MAX_BUILDSIZE:
qApp.processEvents()
self.docView.setContent(self.htmlText, self.buildTime)
else:
@@ -649,7 +656,7 @@ class GuiBuildNovel(QDialog):
else:
self.docView.setStyleSheet(self.htmlStyle)
if htmlSize < nwConst.maxBuildSize:
if htmlSize < nwConst.MAX_BUILDSIZE:
self.docView.setContent(self.htmlText, self.buildTime)
self._enableQtSave(True)
else:
+11 -8
View File
@@ -50,7 +50,7 @@ from PyQt5.QtWidgets import (
QFrame
)
from nw.core import NWDoc, NWSpellCheck, NWSpellSimple, countWords
from nw.core import NWDoc, NWSpellSimple, countWords
from nw.gui.dochighlight import GuiDocHighlighter
from nw.common import transferCase
from nw.constants import (
@@ -284,12 +284,12 @@ class GuiDocEditor(QTextEdit):
return False
docSize = len(theDoc)
if docSize > nwConst.maxDocSize:
if docSize > nwConst.MAX_DOCSIZE:
self.theParent.makeAlert((
"The document you are trying to open is too big. "
"The document size is %.2f\u202fMB. "
"The maximum size allowed is %.2f\u202fMB."
) % (docSize/1.0e6, nwConst.maxDocSize/1.0e6), nwAlert.ERROR)
) % (docSize/1.0e6, nwConst.MAX_DOCSIZE/1.0e6), nwAlert.ERROR)
self.clearEditor()
return False
@@ -361,12 +361,12 @@ class GuiDocEditor(QTextEdit):
text. This also clears undo history.
"""
docSize = len(theText)
if docSize > nwConst.maxDocSize:
if docSize > nwConst.MAX_DOCSIZE:
self.theParent.makeAlert((
"The text you are trying to add is too big. "
"The text size is %.2f\u202fMB. "
"The maximum size allowed is %.2f\u202fMB."
) % (docSize/1.0e6, nwConst.maxDocSize/1.0e6), nwAlert.ERROR)
) % (docSize/1.0e6, nwConst.MAX_DOCSIZE/1.0e6), nwAlert.ERROR)
return False
qApp.setOverrideCursor(QCursor(Qt.WaitCursor))
@@ -859,11 +859,11 @@ class GuiDocEditor(QTextEdit):
"""
self.lastEdit = time()
self.lastFind = None
if self.qDocument.characterCount() > nwConst.maxDocSize:
if self.qDocument.characterCount() > nwConst.MAX_DOCSIZE:
self.theParent.makeAlert((
"The document has grown too big and you cannot add more text to it. "
"The maximum size of a single novelWriter document is %.2f\u202fMB."
) % (nwConst.maxDocSize/1.0e6), nwAlert.ERROR)
) % (nwConst.MAX_DOCSIZE/1.0e6), nwAlert.ERROR)
self.undo()
return
if not self.docChanged:
@@ -1004,6 +1004,9 @@ class GuiDocEditor(QTextEdit):
"""Decide whether to run the word counter, or not due to
inactivity.
"""
if self.theHandle is None:
return
if self.wCounter.isRunning():
logger.verbose("Word counter is busy")
return
@@ -1611,7 +1614,7 @@ class GuiDocEditor(QTextEdit):
"""Create the spell checking object based on the spellTool
setting in config.
"""
if self.mainConf.spellTool == NWSpellCheck.SP_ENCHANT:
if self.mainConf.spellTool == nwConst.SP_ENCHANT:
from nw.core.spellcheck import NWSpellEnchant
self.theDict = NWSpellEnchant()
else:
+1 -1
View File
@@ -155,7 +155,7 @@ class GuiDocSplit(QDialog):
# Check that another folder can be created
parTree = self.theProject.projTree.getItemPath(srcItem.itemParent)
if len(parTree) >= nwConst.maxDepth - 1:
if len(parTree) >= nwConst.MAX_DEPTH - 1:
self.theParent.makeAlert((
"Cannot add new folder for the document split. "
"Maximum folder depth has been reached. "
+21
View File
@@ -246,6 +246,27 @@ class GuiOutlineDetails(QScrollArea):
return
def clearDetails(self):
"""Clear all the data labels.
"""
self.titleLabel.setText("<b>Title</b>")
self.titleValue.setText("")
self.fileValue.setText("")
self.itemValue.setText("")
self.cCValue.setText("")
self.wCValue.setText("")
self.pCValue.setText("")
self.synopValue.setText("")
self.povKeyValue.setText("")
self.chrKeyValue.setText("")
self.pltKeyValue.setText("")
self.timKeyValue.setText("")
self.wldKeyValue.setText("")
self.objKeyValue.setText("")
self.entKeyValue.setText("")
self.cstKeyValue.setText("")
return
def showItem(self, tHandle, sTitle):
"""Update the content of the tree with the given handle and line
number pointing to a header.
+6 -5
View File
@@ -37,7 +37,8 @@ from PyQt5.QtWidgets import (
)
from nw.gui.custom import QSwitch, QConfigLayout, PagedDialog, QuotesDialog
from nw.core import NWSpellCheck, NWSpellSimple, NWSpellEnchant
from nw.core import NWSpellSimple, NWSpellEnchant
from nw.constants import nwConst
logger = logging.getLogger(__name__)
@@ -703,11 +704,11 @@ class GuiConfigEditEditingTab(QWidget):
## Spell Check Provider and Language
self.spellLangList = QComboBox(self)
self.spellToolList = QComboBox(self)
self.spellToolList.addItem("Internal (difflib)", NWSpellCheck.SP_INTERNAL)
self.spellToolList.addItem("Spell Enchant (pyenchant)", NWSpellCheck.SP_ENCHANT)
self.spellToolList.addItem("Internal (difflib)", nwConst.SP_INTERNAL)
self.spellToolList.addItem("Spell Enchant (pyenchant)", nwConst.SP_ENCHANT)
theModel = self.spellToolList.model()
idEnchant = self.spellToolList.findData(NWSpellCheck.SP_ENCHANT)
idEnchant = self.spellToolList.findData(nwConst.SP_ENCHANT)
theModel.item(idEnchant).setEnabled(self.mainConf.hasEnchant)
self.spellToolList.currentIndexChanged.connect(self._doUpdateSpellTool)
@@ -814,7 +815,7 @@ class GuiConfigEditEditingTab(QWidget):
preserve the language choice, if the language exists in the
updated list.
"""
if spellTool == NWSpellCheck.SP_ENCHANT:
if spellTool == nwConst.SP_ENCHANT:
theDict = NWSpellEnchant()
else:
theDict = NWSpellSimple()
+3 -3
View File
@@ -243,8 +243,8 @@ class GuiProjectTree(QTreeWidget):
tHandle = self.theProject.newFile("New File", itemClass, pHandle)
elif itemType == nwItemType.FOLDER:
if len(parTree) >= nwConst.maxDepth - 1:
# Folders cannot be deeper than maxDepth - 1, leaving room
if len(parTree) >= nwConst.MAX_DEPTH - 1:
# Folders cannot be deeper than MAX_DEPTH - 1, leaving room
# for one more level of files.
self.makeAlert((
"Cannot add new folder to this item. "
@@ -579,7 +579,7 @@ class GuiProjectTree(QTreeWidget):
pCount += int(pItem.child(i).data(self.C_COUNT, Qt.UserRole))
pHandle = pItem.data(self.C_NAME, Qt.UserRole)
if not nDepth > nwConst.maxDepth + 1 and pHandle != "":
if not nDepth > nwConst.MAX_DEPTH + 1 and pHandle != "":
self.propagateCount(pHandle, pCount, nDepth+1)
return
+4 -4
View File
@@ -428,10 +428,10 @@ class GuiWritingStats(QDialog):
continue
dStart = datetime.strptime(
"%s %s" % (inData[0], inData[1]), nwConst.tStampFmt
"%s %s" % (inData[0], inData[1]), nwConst.FMT_TSTAMP
)
dEnd = datetime.strptime(
"%s %s" % (inData[2], inData[3]), nwConst.tStampFmt
"%s %s" % (inData[2], inData[3]), nwConst.FMT_TSTAMP
)
tDiff = dEnd - dStart
@@ -528,9 +528,9 @@ class GuiWritingStats(QDialog):
isFirst = False
if groupByDay:
sStart = dStart.strftime(nwConst.dStampFmt)
sStart = dStart.strftime(nwConst.FMT_DSTAMP)
else:
sStart = dStart.strftime(nwConst.tStampFmt)
sStart = dStart.strftime(nwConst.FMT_TSTAMP)
self.filterData.append((dStart, sStart, sDiff, dwTotal, wcNovel, wcNotes))
listMax = min(max(listMax, dwTotal), histMax)
+1
View File
@@ -345,6 +345,7 @@ class GuiMain(QMainWindow):
self.closeDocument()
self.docViewer.clearNavHistory()
self.projView.closeOutline()
self.projMeta.clearDetails()
self.theProject.closeProject()
self.theIndex.clearIndex()
self.clearGUI()