Skip to content

Commit

Permalink
Merge pull request #1 from vkbo/update_spellcheck
Browse files Browse the repository at this point in the history
Improved SpellCheck Context
  • Loading branch information
vkbo committed May 10, 2019
2 parents 780e729 + 73f1547 commit f3fd51e
Show file tree
Hide file tree
Showing 4 changed files with 106 additions and 64 deletions.
119 changes: 68 additions & 51 deletions nw/gui/doceditor.py
Expand Up @@ -16,9 +16,9 @@

from time import time

from PyQt5.QtWidgets import QTextEdit, QAction, QMenu
from PyQt5.QtCore import Qt, QTimer, QEvent, pyqtSignal
from PyQt5.QtGui import QTextCursor, QTextOption, QMouseEvent
from PyQt5.QtWidgets import QTextEdit, QAction, QMenu, QShortcut
from PyQt5.QtGui import QTextCursor, QTextOption, QIcon, QKeySequence
from PyQt5.QtCore import Qt, QTimer

from nw.gui.dochighlight import GuiDocHighlighter
from nw.gui.wordcounter import WordCounter
Expand All @@ -37,6 +37,7 @@ def __init__(self, theParent):
self.mainConf = nw.CONFIG
self.theParent = theParent
self.docChanged = False
self.pwlFile = None

# Document Variables
self.charCount = 0
Expand All @@ -54,7 +55,12 @@ def __init__(self, theParent):
# Core Elements
self.theDoc = self.document()
self.theDict = enchant.Dict(self.mainConf.spellLanguage)
self.hLight = GuiDocHighlighter(self.theDoc, self.theDict)
self.hLight = GuiDocHighlighter(self.theDoc)
self.hLight.setDict(self.theDict)

# Context Menu
self.setContextMenuPolicy(Qt.CustomContextMenu)
self.customContextMenuRequested.connect(self._openContextMenu)

# Editor State
self.hasSelection = False
Expand All @@ -76,6 +82,9 @@ def __init__(self, theParent):
self.setAcceptRichText(False)
self.setFontPointSize(self.mainConf.textSize)

# Custom Shortcuts
QShortcut(QKeySequence("Ctrl+."), self, context=Qt.WidgetShortcut, activated=self._openSpellContext)

# Set Up Word Count Thread and Timer
self.wcInterval = self.mainConf.wordCountTimer
self.wcTimer = QTimer()
Expand Down Expand Up @@ -106,6 +115,13 @@ def setText(self, theText):
self.setDocumentChanged(False)
return True

def setPwl(self, pwlFile):
if pwlFile is not None:
self.pwlFile = pwlFile
self.theDict = enchant.DictWithPWL(self.mainConf.spellLanguage,pwlFile)
self.hLight.setDict(self.theDict)
return

def getText(self):
theText = self.toPlainText()
return theText
Expand Down Expand Up @@ -155,54 +171,63 @@ def keyPressEvent(self, keyEvent):
QTextEdit.keyPressEvent(self, keyEvent)
return

def mousePressEvent(self, theEvent):
"""Capture right click events and rewrite them to left button event. This moves the cursor
to the location of the pointer. Needed to select the word under the right click.
Adapted from: https://nachtimwald.com/2009/08/22/qplaintextedit-with-in-line-spell-check
"""
if theEvent.button() == Qt.RightButton:
theEvent = QMouseEvent(
QEvent.MouseButtonPress, theEvent.pos(), Qt.LeftButton, Qt.LeftButton, Qt.NoModifier
)
QTextEdit.mousePressEvent(self, theEvent)
##
# Internal Functions
##

def _openSpellContext(self):
self._openContextMenu(self.cursorRect().center())
return

def contextMenuEvent(self, theEvent):
"""Intercept the context menu and insert spelling suggestions, if any.
Uses the custom QAction class SpellAction from the same example code.
Adapted from: https://nachtimwald.com/2009/08/22/qplaintextedit-with-in-line-spell-check
"""
mnuSpell = self.createStandardContextMenu()
theCursor = self.textCursor()
def _openContextMenu(self, thePos):

theCursor = self.cursorForPosition(thePos)
theCursor.select(QTextCursor.WordUnderCursor)
self.setTextCursor(theCursor)
theWord = theCursor.selectedText()
if theWord == "":
return
if self.theDict.check(theWord):
return

if self.textCursor().hasSelection():
theText = self.textCursor().selectedText()
if not self.theDict.check(theText):
mnuSuggest = QMenu("Spelling Suggestions")
for aWord in self.theDict.suggest(theText):
action = SpellAction(aWord, mnuSuggest)
action.correct.connect(self._correctWord)
mnuSuggest.addAction(action)
if len(mnuSuggest.actions()) > 0:
theActions = mnuSpell.actions()
mnuSpell.insertSeparator(theActions[0])
mnuSpell.insertMenu(theActions[0], mnuSuggest)

mnuSpell.exec_(theEvent.globalPos())
return
mnuSuggest = QMenu()
spIcon = QIcon.fromTheme("tools-check-spelling")
mnuHead = QAction(spIcon,"Spelling Suggestion", mnuSuggest)
mnuSuggest.addAction(mnuHead)
mnuSuggest.addSeparator()
theSuggest = self.theDict.suggest(theWord)
if len(theSuggest) > 0:
for aWord in theSuggest:
mnuWord = QAction(aWord, mnuSuggest)
mnuWord.triggered.connect(lambda thePos, aWord=aWord : self._correctWord(theCursor, aWord))
mnuSuggest.addAction(mnuWord)
mnuSuggest.addSeparator()
mnuAdd = QAction("Add Word to Dictionary", mnuSuggest)
mnuAdd.triggered.connect(lambda thePos : self._addWord(theCursor))
mnuSuggest.addAction(mnuAdd)
else:
mnuHead = QAction("No Suggestions", mnuSuggest)
mnuSuggest.addAction(mnuHead)

##
# Internal Functions
##
mnuSuggest.exec_(self.viewport().mapToGlobal(thePos))

def _correctWord(self, word):
theCursor = self.textCursor()
return

def _correctWord(self, theCursor, theWord):
xPos = theCursor.selectionStart()
theCursor.beginEditBlock()
theCursor.removeSelectedText()
theCursor.insertText(word)
theCursor.insertText(theWord)
theCursor.endEditBlock()
theCursor.setPosition(xPos)
self.setTextCursor(theCursor)
return

def _addWord(self, theCursor):
theWord = theCursor.selectedText().strip()
logger.info("Added '%s' to project dictionary" % theWord)
self.theDict.add_to_pwl(theWord)
self.hLight.setDict(self.theDict)
self.hLight.rehighlightBlock(theCursor.block())
return

def _docChange(self, thePos, charsRemoved, charsAdded):
Expand Down Expand Up @@ -328,11 +353,3 @@ def _makeSelection(self, selMode):
return

# END Class GuiDocEditor

class SpellAction(QAction):
correct = pyqtSignal(str)
def __init__(self, *args):
QAction.__init__(self, *args)
self.triggered.connect(lambda x: self.correct.emit(self.text()))

# END Class SpellAction
12 changes: 8 additions & 4 deletions nw/gui/dochighlight.py
Expand Up @@ -20,13 +20,13 @@

class GuiDocHighlighter(QSyntaxHighlighter):

def __init__(self, theDoc, theDict):
def __init__(self, theDoc):
QSyntaxHighlighter.__init__(self, theDoc)

logger.debug("Initialising DocHighlighter ...")
self.mainConf = nw.CONFIG
self.theDoc = theDoc
self.theDict = theDict
self.theDict = None
self.hRules = []

self.colHead = QColor( 0,155,200)
Expand Down Expand Up @@ -145,12 +145,16 @@ def __init__(self, theDoc, theDict):

# Build a QRegExp for each pattern and for the spell checker
self.rules = [(QRegularExpression(a),b) for (a,b) in self.hRules]
self.spellRx = QRegularExpression(r"[\w\'{:s}]+".format(self.mainConf.fmtApostrophe))
self.spellRx = QRegularExpression(r"\b[^\s]+\b")

logger.debug("DocHighlighter initialisation complete")

return

def setDict(self, theDict):
self.theDict = theDict
return

def _makeFormat(self, fmtCol=None, fmtStyle=None, fmtSize=None):
theFormat = QTextCharFormat()

Expand Down Expand Up @@ -188,7 +192,7 @@ def highlightBlock(self, theText):
if self.theDict is None:
return

rxSpell = self.spellRx.globalMatch(theText, 0)
rxSpell = self.spellRx.globalMatch(theText.replace("_"," "), 0)
while rxSpell.hasNext():
rxMatch = rxSpell.next()
if not self.theDict.check(rxMatch.captured(0)):
Expand Down
6 changes: 4 additions & 2 deletions nw/gui/winmain.py
Expand Up @@ -147,8 +147,9 @@ def makeAlert(self, theMessage, theLevel):
def newProject(self):
logger.info("Creating new project")
self.treeView.clearTree()
self.theProject.newProject()
self.treeView.buildTree()
if self.saveProject():
self.theProject.newProject()
self.treeView.buildTree()
return

def openProject(self, projFile=None):
Expand All @@ -161,6 +162,7 @@ def openProject(self, projFile=None):
self.treeView.buildTree()
self.mainMenu.updateRecentProjects()
self._setWindowTitle(self.theProject.projName)
self.docEditor.setPwl(path.join(self.theProject.projMeta,"wordlist.txt"))
return True

def saveProject(self):
Expand Down
33 changes: 26 additions & 7 deletions nw/project/project.py
Expand Up @@ -41,6 +41,8 @@ def __init__(self, theParent):
self.treeRoots = None
self.trashRoot = None
self.projPath = None
self.projMeta = None
self.projCache = None
self.projFile = None
self.projName = None
self.bookTitle = None
Expand Down Expand Up @@ -120,6 +122,8 @@ def clearProject(self):
self.treeRoots = []
self.trashRoot = None
self.projPath = None
self.projMeta = None
self.projCache = None
self.projFile = "nwProject.nwx"
self.projName = ""
self.bookTitle = ""
Expand Down Expand Up @@ -149,6 +153,12 @@ def openProject(self, fileName):
self.projPath = path.dirname(fileName)
logger.debug("Opening project: %s" % self.projPath)

self.projMeta = path.join(self.projPath,"meta")
self.projCache = path.join(self.projPath,"cache")

if not self._checkFolder(self.projMeta): return
if not self._checkFolder(self.projCache): return

nwXML = etree.parse(fileName)
xRoot = nwXML.getroot()

Expand Down Expand Up @@ -210,13 +220,12 @@ def saveProject(self):
self.theParent.makeAlert("Project path not set, cannot save.",2)
return False

if not path.isdir(self.projPath):
try:
mkdir(self.projPath)
logger.info("Created folder %s" % self.projPath)
except Exception as e:
self.theParent.makeAlert(["Could not create folder.",str(e)],2)
return False
self.projMeta = path.join(self.projPath,"meta")
self.projCache = path.join(self.projPath,"cache")

if not self._checkFolder(self.projPath): return
if not self._checkFolder(self.projMeta): return
if not self._checkFolder(self.projCache): return

logger.debug("Saving project: %s" % self.projPath)

Expand Down Expand Up @@ -335,6 +344,16 @@ def checkRootUnique(self, theClass):
# Internal Functions
##

def _checkFolder(self, thePath):
if not path.isdir(thePath):
try:
mkdir(thePath)
logger.info("Created folder %s" % thePath)
except Exception as e:
self.theParent.makeAlert(["Could not create folder.",str(e)],2)
return False
return True

def _scanProjectFolder(self):

if self.projPath is None:
Expand Down

0 comments on commit f3fd51e

Please sign in to comment.