diff --git a/nw/common.py b/nw/common.py new file mode 100644 index 00000000..ab413a43 --- /dev/null +++ b/nw/common.py @@ -0,0 +1,52 @@ +# -*- coding: utf-8 -*- +"""novelWriter Common Functions + + novelWriter – Common Functions +================================ + Various functions used multiple places + + File History: + Created: 2019-05-12 [0.1.0] + +""" + +import logging +import nw + +logger = logging.getLogger(__name__) + +def checkString(checkValue, defaultValue, allowNone=False): + if allowNone: + if checkValue == None: return None + if checkValue == "None": return None + if isinstance(checkValue,str): return str(checkValue) + return defaultValue + +def checkInt(checkValue, defaultValue, allowNone=False): + if allowNone: + if checkValue == None: return None + if checkValue == "None": return None + try: + return int(checkValue) + except: + return defaultValue + +def checkBool(checkValue, defaultValue, allowNone=False): + if allowNone: + if checkValue == None: return None + if checkValue == "None": return None + if isinstance(checkValue, str): + if checkValue == "True": + return True + elif checkValue == "False": + return False + else: + return defaultValue + elif isinstance(checkValue, int): + if checkValue == 1: + return True + elif checkValue == 0: + return False + else: + return defaultValue + return defaultValue diff --git a/nw/gui/doceditor.py b/nw/gui/doceditor.py index 90e7072b..5ff38d78 100644 --- a/nw/gui/doceditor.py +++ b/nw/gui/doceditor.py @@ -38,6 +38,7 @@ class GuiDocEditor(QTextEdit): self.theParent = theParent self.docChanged = False self.pwlFile = None + self.spellCheck = False # Document Variables self.charCount = 0 @@ -105,7 +106,7 @@ class GuiDocEditor(QTextEdit): def setDocumentChanged(self, bValue): self.docChanged = bValue self.theParent.statusBar.setDocumentStatus(self.docChanged) - return + return self.docChanged def setText(self, theText): self.setPlainText(theText) @@ -120,7 +121,18 @@ class GuiDocEditor(QTextEdit): self.pwlFile = pwlFile self.theDict = enchant.DictWithPWL(self.mainConf.spellLanguage,pwlFile) self.hLight.setDict(self.theDict) - return + return True + + def setSpellCheck(self, theMode): + self.spellCheck = theMode + self.hLight.setSpellCheck(theMode) + self.rehighlightDocument() + return True + + def updateSpellCheck(self): + if self.spellCheck: + self.rehighlightDocument() + return True def getText(self): theText = self.toPlainText() @@ -154,6 +166,11 @@ class GuiDocEditor(QTextEdit): elif theAction == nwDocAction.SEL_PARA: self._makeSelection(QTextCursor.BlockUnderCursor) else: logger.error("Unknown or unsupported document action %s" % str(theAction)) + return False + return True + + def rehighlightDocument(self): + self.hLight.rehighlight() return ## @@ -181,6 +198,9 @@ class GuiDocEditor(QTextEdit): def _openContextMenu(self, thePos): + if not self.spellCheck: + return + theCursor = self.cursorForPosition(thePos) theCursor.select(QTextCursor.WordUnderCursor) theWord = theCursor.selectedText() @@ -237,7 +257,7 @@ class GuiDocEditor(QTextEdit): self.wcTimer.start() if self.mainConf.doReplace and not self.hasSelection: self._docAutoReplace(self.theDoc.findBlock(thePos)) - # logger.verbose("Doc change signal took %.3f µs" % ((time()-self.lastEdit)*1e6)) + logger.verbose("Doc change signal took %.3f µs" % ((time()-self.lastEdit)*1e6)) return def _docAutoReplace(self, theBlock): diff --git a/nw/gui/dochighlight.py b/nw/gui/dochighlight.py index 59923808..30787793 100644 --- a/nw/gui/dochighlight.py +++ b/nw/gui/dochighlight.py @@ -24,22 +24,23 @@ class GuiDocHighlighter(QSyntaxHighlighter): QSyntaxHighlighter.__init__(self, theDoc) logger.debug("Initialising DocHighlighter ...") - self.mainConf = nw.CONFIG - self.theDoc = theDoc - self.theDict = None - self.hRules = [] + self.mainConf = nw.CONFIG + self.theDoc = theDoc + self.theDict = None + self.spellCheck = False + self.hRules = [] - self.colHead = QColor( 0,155,200) - self.colHeadH = QColor( 0,105,135) - self.colEmph = QColor(200,120, 0) - self.colDialN = QColor(200, 46, 0) - self.colDialD = QColor(184,200, 0) - self.colDialS = QColor(136,200, 0) - self.colComm = QColor(150,150,150) - self.colKey = QColor(200, 46, 0) - self.colVal = QColor(184,200, 0) + self.colHead = QColor( 0,155,200) + self.colHeadH = QColor( 0,105,135) + self.colEmph = QColor(200,120, 0) + self.colDialN = QColor(200, 46, 0) + self.colDialD = QColor(184,200, 0) + self.colDialS = QColor(136,200, 0) + self.colComm = QColor(150,150,150) + self.colKey = QColor(200, 46, 0) + self.colVal = QColor(184,200, 0) - self.colSpell = QColor(200, 46, 0) + self.colSpell = QColor(200, 46, 0) self.hStyles = { "header1" : self._makeFormat(self.colHead, "bold",1.8), @@ -153,7 +154,11 @@ class GuiDocHighlighter(QSyntaxHighlighter): def setDict(self, theDict): self.theDict = theDict - return + return True + + def setSpellCheck(self, theMode): + self.spellCheck = theMode + return True def _makeFormat(self, fmtCol=None, fmtStyle=None, fmtSize=None): theFormat = QTextCharFormat() @@ -189,7 +194,7 @@ class GuiDocHighlighter(QSyntaxHighlighter): self.setCurrentBlockState(0) - if self.theDict is None: + if self.theDict is None or not self.spellCheck: return rxSpell = self.spellRx.globalMatch(theText.replace("_"," "), 0) diff --git a/nw/gui/mainmenu.py b/nw/gui/mainmenu.py index fdd0c3a1..488cf8ff 100644 --- a/nw/gui/mainmenu.py +++ b/nw/gui/mainmenu.py @@ -61,6 +61,15 @@ class GuiMainMenu(QMenuBar): ) return + ## + # Update Menu on Settings Changed + ## + + def updateMenu(self): + self.updateRecentProjects() + self.updateSpellCheck() + return + def updateRecentProjects(self): self.recentMenu.clear() for n in range(len(self.mainConf.recentList)): @@ -71,6 +80,11 @@ class GuiMainMenu(QMenuBar): self.recentMenu.addAction(menuItem) return + def updateSpellCheck(self): + self.toolsSpellCheck.setChecked(self.theProject.spellCheck) + logger.verbose("Spell check is set to %s" % str(self.theProject.spellCheck)) + return + ## # Menu Action ## @@ -80,6 +94,12 @@ class GuiMainMenu(QMenuBar): qApp.quit() return True + def _toggleSpellCheck(self): + self.theProject.setSpellCheck(self.toolsSpellCheck.isChecked()) + self.theParent.docEditor.setSpellCheck(self.toolsSpellCheck.isChecked()) + logger.verbose("Spell check is set to %s" % str(self.theProject.spellCheck)) + return + def _showAbout(self): msgBox = QMessageBox() msgBox.about(self.theParent, "About %s" % nw.__package__, ( @@ -407,8 +427,24 @@ class GuiMainMenu(QMenuBar): self.toolsMoveDown.triggered.connect(lambda : self._moveTreeItem(1)) self.toolsMenu.addAction(self.toolsMoveDown) - # # Tools > Separator - # self.toolsMenu.addSeparator() + # Tools > Separator + self.toolsMenu.addSeparator() + + # Tools > Toggle Spell Check + self.toolsSpellCheck = QAction("Check Spelling", self) + self.toolsSpellCheck.setStatusTip("Toggle Check Spelling") + self.toolsSpellCheck.setCheckable(True) + self.toolsSpellCheck.setChecked(self.theProject.spellCheck) + self.toolsSpellCheck.toggled.connect(self._toggleSpellCheck) + self.toolsSpellCheck.setShortcut("Ctrl+F7") + self.toolsMenu.addAction(self.toolsSpellCheck) + + # Tools > Update Spell Check + menuItem = QAction(QIcon.fromTheme("tools-check-spelling"), "Re-Run Spell Check", self) + menuItem.setStatusTip("Rus the Spell Checker on Current Document") + menuItem.setShortcut("F7") + menuItem.triggered.connect(self.theParent.docEditor.updateSpellCheck) + self.toolsMenu.addAction(menuItem) # # Tools > Settings # menuItem = QAction(QIcon.fromTheme("preferences-system"), "Preferences", self) diff --git a/nw/gui/winmain.py b/nw/gui/winmain.py index ab12372f..673fdb27 100644 --- a/nw/gui/winmain.py +++ b/nw/gui/winmain.py @@ -172,10 +172,11 @@ class GuiMain(QMainWindow): self.treeView.clearTree() self.theProject.openProject(projFile) self.treeView.buildTree() - self.mainMenu.updateRecentProjects() self._setWindowTitle(self.theProject.projName) self._makeStatusIcons() self.docEditor.setPwl(path.join(self.theProject.projMeta,"wordlist.txt")) + self.docEditor.setSpellCheck(self.theProject.spellCheck) + self.mainMenu.updateMenu() return True def saveProject(self): diff --git a/nw/project/item.py b/nw/project/item.py index 4e2366c4..b41342c9 100644 --- a/nw/project/item.py +++ b/nw/project/item.py @@ -13,11 +13,12 @@ import logging import nw -from os import path, mkdir -from lxml import etree -from datetime import datetime +from os import path, mkdir +from lxml import etree +from datetime import datetime -from nw.enum import nwItemType, nwItemClass, nwItemLayout +from nw.enum import nwItemType, nwItemClass, nwItemLayout +from nw.common import checkInt logger = logging.getLogger(__name__) @@ -153,7 +154,7 @@ class NWItem(): return def setStatus(self, theStatus): - theStatus = self._checkInt(theStatus,0) + theStatus = checkInt(theStatus,0) self.itemStatus = theStatus return @@ -169,31 +170,18 @@ class NWItem(): ## def setCharCount(self, theCount): - theCount = self._checkInt(theCount,0) + theCount = checkInt(theCount,0) self.charCount = theCount return def setWordCount(self, theCount): - theCount = self._checkInt(theCount,0) + theCount = checkInt(theCount,0) self.wordCount = theCount return def setParaCount(self, theCount): - theCount = self._checkInt(theCount,0) + theCount = checkInt(theCount,0) self.paraCount = theCount return - ## - # Internal Functions - ## - - def _checkInt(self,checkValue,defaultValue,allowNone=False): - if allowNone: - if checkValue == None: return None - if checkValue == "None": return None - try: - return int(checkValue) - except: - return defaultValue - # END Class NWItem diff --git a/nw/project/project.py b/nw/project/project.py index 25a96609..23821055 100644 --- a/nw/project/project.py +++ b/nw/project/project.py @@ -20,6 +20,7 @@ from datetime import datetime from time import time from nw.enum import nwItemType, nwItemClass, nwItemLayout +from nw.common import checkString, checkBool from nw.project.item import NWItem logger = logging.getLogger(__name__) @@ -36,7 +37,7 @@ class NWProject(): # Debug self.handleSeed = None - # Project Settings + # Class Settings self.projTree = None self.treeOrder = None self.treeRoots = None @@ -45,11 +46,17 @@ class NWProject(): self.projMeta = None self.projCache = None self.projFile = None + self.statusCols = None + + # Project Meta self.projName = None self.bookTitle = None self.bookAuthors = None - self.statusCols = None + # Project Settings + self.spellCheck = False + + # Set Defaults self.clearProject() return @@ -130,6 +137,7 @@ class NWProject(): self.projName = "" self.bookTitle = "" self.bookAuthors = [] + self.spellCheck = False self.statusCols = [ ("New", 100,100,100), ("Note", 200, 50, 0), @@ -185,6 +193,12 @@ class NWProject(): elif xItem.tag == "author": logger.verbose("Author: '%s'" % xItem.text) self.bookAuthors.append(xItem.text) + elif xChild.tag == "settings": + logger.debug("Found project settings") + for xItem in xChild: + if xItem.text is None: continue + if xItem.tag == "spellCheck": + self.spellCheck = checkBool(xItem.text,False) elif xChild.tag == "content": logger.debug("Found project content") for xItem in xChild: @@ -233,15 +247,16 @@ class NWProject(): "appVersion" : str(nw.__version__), "timeStamp" : datetime.now().strftime("%Y-%m-%d %H:%M:%S"), }) - xProject = etree.SubElement(nwXML,"project") - xProjName = etree.SubElement(xProject,"name") - xProjName.text = self.projName - xBookTitle = etree.SubElement(xProject,"title") - xBookTitle.text = self.bookTitle - for bookAuthor in self.bookAuthors: - if bookAuthor == "": continue - xBookAuthor = etree.SubElement(xProject,"author") - xBookAuthor.text = bookAuthor + + # Save Project Meta + xProject = etree.SubElement(nwXML,"project") + self._saveProjectValue(xProject,"name", self.projName, True) + self._saveProjectValue(xProject,"title", self.bookTitle, True) + self._saveProjectValue(xProject,"author",self.bookAuthors) + + # Save Project Settings + xSettings = etree.SubElement(nwXML,"settings") + self._saveProjectValue(xSettings,"spellCheck",self.spellCheck) # Save Tree Content logger.debug("Writing project content") @@ -298,6 +313,11 @@ class NWProject(): self.setProjectChanged(True) return True + def setSpellCheck(self, theMode): + self.spellCheck = theMode + self.setProjectChanged(True) + return True + def setTreeOrder(self, newOrder): if len(self.treeOrder) != len(newOrder): logger.warning("Size of new and old tree order does not match") @@ -351,6 +371,17 @@ class NWProject(): return False return True + def _saveProjectValue(self, xParent, theName, theValue, allowNone=True): + if not isinstance(theValue, list): + theValue = [theValue] + for aValue in theValue: + if not isinstance(aValue, str): + aValue = str(aValue) + if aValue == "" and not allowNone: continue + xItem = etree.SubElement(xParent,theName) + xItem.text = aValue + return + def _scanProjectFolder(self): if self.projPath is None: @@ -402,8 +433,8 @@ class NWProject(): return def _appendItem(self, tHandle, pHandle, nwItem): - tHandle = self._checkString(tHandle,self._makeHandle(),False) - pHandle = self._checkString(pHandle,None,True) + tHandle = checkString(tHandle,self._makeHandle(),False) + pHandle = checkString(pHandle,None,True) logger.verbose("Adding entry %s with parent %s" % (str(tHandle),str(pHandle))) nwItem.setHandle(tHandle) @@ -441,20 +472,4 @@ class NWProject(): itemHandle = self._makeHandle(addSeed+"!") return itemHandle - def _checkString(self,checkValue,defaultValue,allowNone=False): - if allowNone: - if checkValue == None: return None - if checkValue == "None": return None - if isinstance(checkValue,str): return str(checkValue) - return defaultValue - - def _checkInt(self,checkValue,defaultValue,allowNone=False): - if allowNone: - if checkValue == None: return None - if checkValue == "None": return None - try: - return int(checkValue) - except: - return defaultValue - # END Class NWProject diff --git a/tests/reference/new_nwProject.nwx b/tests/reference/new_nwProject.nwx index 742afe41..e9a65102 100644 --- a/tests/reference/new_nwProject.nwx +++ b/tests/reference/new_nwProject.nwx @@ -4,6 +4,9 @@ + + False + Novel diff --git a/tests/reference/roots_nwProject.nwx b/tests/reference/roots_nwProject.nwx index 1971370a..8cad23c9 100644 --- a/tests/reference/roots_nwProject.nwx +++ b/tests/reference/roots_nwProject.nwx @@ -4,6 +4,9 @@ + + False + Novel