diff --git a/nw/gui/elements/doceditor.py b/nw/gui/elements/doceditor.py index c961f301..7cc4a186 100644 --- a/nw/gui/elements/doceditor.py +++ b/nw/gui/elements/doceditor.py @@ -314,17 +314,41 @@ class GuiDocEditor(QTextEdit): ## def setDictionaries(self): + """Set the spell checker dictionary language, and update the + status bar to show the one actually loaded by the spell checker + class. + """ self.theDict.setLanguage(self.mainConf.spellLanguage, self.theProject.projDict) - self.theParent.statusBar.setLanguage(self.mainConf.spellLanguage) + self.theParent.statusBar.setLanguage(self.theDict.spellLanguage) return True def setSpellCheck(self, theMode): + """This is the master spell check setting function, and this one + should call all other setSpellCheck functions in other classes. + If the spell check mode is not defined, then toggle the current + status saved in the class. + """ + + if theMode is None: + theMode = not self.spellCheck + + if self.theDict.spellLanguage is None: + theMode = False + self.spellCheck = theMode + self.theParent.mainMenu.setSpellCheck(theMode) + self.theProject.setSpellCheck(theMode) self.hLight.setSpellCheck(theMode) self.hLight.rehighlight() + + logger.verbose("Spell check is set to %s" % str(theMode)) + return True def updateSpellCheck(self): + """Rerun the highlighter to update spell checking status of the + currently loaded text. + """ if self.spellCheck: self.hLight.rehighlight() return True @@ -506,9 +530,12 @@ class GuiDocEditor(QTextEdit): theCursor = self.cursorForPosition(thePos) theCursor.select(QTextCursor.WordUnderCursor) + theWord = theCursor.selectedText().strip().strip(self.nonWord) if theWord == "": return + + logger.verbose("Looking up '%s' in the dictionary" % theWord) if self.theDict.checkWord(theWord): return diff --git a/nw/gui/mainmenu.py b/nw/gui/mainmenu.py index 4205c8ad..179a6077 100644 --- a/nw/gui/mainmenu.py +++ b/nw/gui/mainmenu.py @@ -68,7 +68,6 @@ class GuiMainMenu(QMenuBar): def updateMenu(self): self.updateRecentProjects() - self.updateSpellCheck() return def updateRecentProjects(self): @@ -91,10 +90,12 @@ class GuiMainMenu(QMenuBar): return - def updateSpellCheck(self): - if self.theParent.hasProject: - self.aSpellCheck.setChecked(self.theProject.spellCheck) - logger.verbose("Spell check is set to %s" % str(self.theProject.spellCheck)) + def setSpellCheck(self, theMode): + """Set the spell check check box to theMode. This is controlled + by the document editor class, which holds the master spell check + flag. + """ + self.aSpellCheck.setChecked(theMode) return ## @@ -106,12 +107,11 @@ class GuiMainMenu(QMenuBar): return def _toggleSpellCheck(self): - if self.theParent.hasProject: - self.theProject.setSpellCheck(self.aSpellCheck.isChecked()) - self.theParent.docEditor.setSpellCheck(self.aSpellCheck.isChecked()) - logger.verbose("Spell check is set to %s" % str(self.theProject.spellCheck)) - else: - self.aSpellCheck.setChecked(False) + """Toggle spell checking. The active status of the spell check + flag is handled by the document editor class, so we make no + decision, just pass a None to the function and let it decide. + """ + self.theParent.docEditor.setSpellCheck(None) return True def _toggleViewComments(self): @@ -609,7 +609,8 @@ class GuiMainMenu(QMenuBar): self.aSpellCheck.setStatusTip("Toggle check spelling") self.aSpellCheck.setCheckable(True) self.aSpellCheck.setChecked(self.theProject.spellCheck) - self.aSpellCheck.toggled.connect(self._toggleSpellCheck) + # Here we must used triggered, not toggled, to avoid recursion + self.aSpellCheck.triggered.connect(self._toggleSpellCheck) self.aSpellCheck.setShortcut("Ctrl+F7") self.toolsMenu.addAction(self.aSpellCheck) diff --git a/nw/gui/statusbar.py b/nw/gui/statusbar.py index 08b30870..81dd6a7c 100644 --- a/nw/gui/statusbar.py +++ b/nw/gui/statusbar.py @@ -116,7 +116,10 @@ class GuiMainStatus(QStatusBar): return def setLanguage(self, theLanguage): - self.langBox.setText(NWSpellCheck.expandLanguage(theLanguage)) + if theLanguage is None: + self.langBox.setText("None") + else: + self.langBox.setText(NWSpellCheck.expandLanguage(theLanguage)) return def setProjectStatus(self, isChanged): diff --git a/nw/gui/tools/dochighlight.py b/nw/gui/tools/dochighlight.py index b151ac94..fd093933 100644 --- a/nw/gui/tools/dochighlight.py +++ b/nw/gui/tools/dochighlight.py @@ -18,6 +18,8 @@ from PyQt5.QtGui import ( QColor, QTextCharFormat, QFont, QSyntaxHighlighter, QBrush ) +from nw.constants import nwUnicode + logger = logging.getLogger(__name__) class GuiDocHighlighter(QSyntaxHighlighter): @@ -150,7 +152,7 @@ class GuiDocHighlighter(QSyntaxHighlighter): # Non-breaking Space self.hRules.append(( - "[\u00a0]+", { + "[%s]+" % nwUnicode.U_NBSP, { 0 : self.hStyles["nobreak"], } )) @@ -201,13 +203,19 @@ class GuiDocHighlighter(QSyntaxHighlighter): } )) - # Build a QRegExp for each pattern and for the spell checker + # Build a QRegExp for each highlight pattern self.rxRules = [] for regEx, regRules in self.hRules: hReg = QRegularExpression(regEx) hReg.setPatternOptions(QRegularExpression.UseUnicodePropertiesOption) self.rxRules.append((hReg, regRules)) - self.spellRx = QRegularExpression(r"\b[^\s]+\b") + + # Build a QRegExp for spell checker + # Include additional characters that the highlighter should + # consider to be word separators + wordSep = "_+" + wordSep += nwUnicode.U_EMDASH + self.spellRx = QRegularExpression("\\b[^\\s%s]+\\b" % wordSep) self.spellRx.setPatternOptions(QRegularExpression.UseUnicodePropertiesOption) return True diff --git a/nw/tools/spellcheck.py b/nw/tools/spellcheck.py index fd5142c8..7979f2b9 100644 --- a/nw/tools/spellcheck.py +++ b/nw/tools/spellcheck.py @@ -13,6 +13,8 @@ import logging import nw +from os import path + from nw.constants import isoLanguage logger = logging.getLogger(__name__) @@ -29,6 +31,7 @@ class NWSpellCheck(): def __init__(self): self.mainConf = nw.CONFIG self.projectDict = None + self.spellLanguage = None return def setLanguage(self, theLang, projectDict=None): @@ -45,11 +48,10 @@ class NWSpellCheck(): newWord = newWord.strip() self.PROJW.append(newWord) try: - with open(self.projectDict,mode="w+",encoding="utf-8") as outFile: - for pWord in self.PROJW: - outFile.write("%s\n" % pWord) + with open(self.projectDict,mode="a+",encoding="utf-8") as outFile: + outFile.write("%s\n" % newWord) except Exception as e: - logger.error("Failed to write to project word list at %s" % str(self.projectDict)) + logger.error("Failed to add word to project word list %s" % str(self.projectDict)) logger.error(str(e)) return @@ -75,6 +77,8 @@ class NWSpellCheck(): self.PROJW = [] if projectDict is not None: self.projectDict = projectDict + if not path.isfile(projectDict): + return try: with open(projectDict,mode="r",encoding="utf-8") as wordsFile: for theLine in wordsFile: diff --git a/nw/tools/spellenchant.py b/nw/tools/spellenchant.py index 1bff2329..7c60c8dd 100644 --- a/nw/tools/spellenchant.py +++ b/nw/tools/spellenchant.py @@ -32,10 +32,12 @@ class NWSpellEnchant(NWSpellCheck): """ try: self.theDict = enchant.Dict(theLang) + self.spellLanguage = theLang logger.debug("Enchant spell checking for language %s loaded" % theLang) except: logger.error("Failed to load enchant spell checking for language %s" % theLang) self.theDict = NWSpellEnchantDummy() + self.spellLanguage = None self._readProjectDictionary(projectDict) for pWord in self.PROJW: diff --git a/nw/tools/spellsimple.py b/nw/tools/spellsimple.py index d98947e5..ab4bf52c 100644 --- a/nw/tools/spellsimple.py +++ b/nw/tools/spellsimple.py @@ -41,9 +41,11 @@ class NWSpellSimple(NWSpellCheck): self.WORDS.append(theLine.strip().lower()) logger.debug("Spell check word list for language %s loaded" % theLang) logger.debug("Word list contains %d words" % len(self.WORDS)) + self.spellLanguage = theLang except Exception as e: logger.error("Failed to load spell check word list for language %s" % theLang) logger.error(str(e)) + self.spellLanguage = None self._readProjectDictionary(projectDict) for pWord in self.PROJW: @@ -104,7 +106,7 @@ class NWSpellSimple(NWSpellCheck): if theBits[1] != ".dict": continue - spName = "%s [nternal]" % self.expandLanguage(theBits[0]) + spName = "%s [Internal]" % self.expandLanguage(theBits[0]) retList.append((theBits[0], spName)) return retList diff --git a/sample/sampleNovel/data_6/36b6aa9b697b_main.nwd b/sample/sampleNovel/data_6/36b6aa9b697b_main.nwd index 70e5eafa..e1578fec 100644 --- a/sample/sampleNovel/data_6/36b6aa9b697b_main.nwd +++ b/sample/sampleNovel/data_6/36b6aa9b697b_main.nwd @@ -12,6 +12,8 @@ In addition, the editor supports automatic formatting of “quotes”, both doub If you have the need for it, you can also add text that can be automatically replaced by other text when you generate a preview or export the project. Now, let’s auto-replace this A with , and this C with . While is just . Press Ctrl+R to see what this looks like in the view pane. +The editor also supports non breaking spaces, and the spell checker accepts long dashes—like this—as valid word separators. + #### Some Section Here If you need to split a scene file up into further pieces, you can do so with the level four heading, like above. This is referred to as a section. diff --git a/sample/sampleNovel/nwProject.nwx b/sample/sampleNovel/nwProject.nwx index 63b80110..fe093bba 100644 --- a/sample/sampleNovel/nwProject.nwx +++ b/sample/sampleNovel/nwProject.nwx @@ -1,5 +1,5 @@ - + Sample Project Sample Project @@ -9,9 +9,9 @@ True - 96b68994dfa3d + 636b6aa9b697b b3e74dbc1f584 - 855 + 875 B E @@ -79,10 +79,10 @@ 1st Draft False SCENE - 1076 - 196 - 6 - 59 + 1199 + 216 + 7 + 949 Another Scene @@ -118,7 +118,7 @@ 1692 313 6 - 216 + 530 Chapter Two diff --git a/tests/profilestats.py b/tests/profilestats.py index f55c7b01..cbb18aeb 100755 --- a/tests/profilestats.py +++ b/tests/profilestats.py @@ -13,4 +13,4 @@ print("") profMainWindows = pstats.Stats(path.join(profDir,"testMainWindows.prof")) profMainWindows.sort_stats("cumtime") -profMainWindows.print_stats("nw",50) +profMainWindows.print_stats("nw/")