* QPlainTextEdit With In Line Spell Check
Posted on August 22nd, 2009 by John. Filed under programming.
***Update: Simplified Highlighter.highlightBlock function
One thing Qt lacks is an integrated spell check in the text entry components. For a project I’m working on this is necessary. Using python-enchant and the QSyntaxHighlighter I was able to implement this functionality. Here is how to add an in line spell check support to a QPlainTextEdit.
#!/usr/bin/env python # -*- coding: utf-8 -*- __license__ = 'MIT' __copyright__ = '2009, John Schember ' __docformat__ = 'restructuredtext en' import re import sys import enchant from PyQt4.Qt import QAction from PyQt4.Qt import QApplication from PyQt4.Qt import QEvent from PyQt4.Qt import QMenu from PyQt4.Qt import QMouseEvent from PyQt4.Qt import QPlainTextEdit from PyQt4.Qt import QSyntaxHighlighter from PyQt4.Qt import QTextCharFormat from PyQt4.Qt import QTextCursor from PyQt4.Qt import Qt from PyQt4.QtCore import pyqtSignal class SpellTextEdit(QPlainTextEdit): def __init__(self, *args): QPlainTextEdit.__init__(self, *args) # Default dictionary based on the current locale. self.dict = enchant.Dict() self.highlighter = Highlighter(self.document()) self.highlighter.setDict(self.dict) def mousePressEvent(self, event): if event.button() == Qt.RightButton: # Rewrite the mouse event to a left button event so the cursor is # moved to the location of the pointer. event = QMouseEvent(QEvent.MouseButtonPress, event.pos(), Qt.LeftButton, Qt.LeftButton, Qt.NoModifier) QPlainTextEdit.mousePressEvent(self, event) def contextMenuEvent(self, event): popup_menu = self.createStandardContextMenu() # Select the word under the cursor. cursor = self.textCursor() cursor.select(QTextCursor.WordUnderCursor) self.setTextCursor(cursor) # Check if the selected word is misspelled and offer spelling # suggestions if it is. if self.textCursor().hasSelection(): text = unicode(self.textCursor().selectedText()) if not self.dict.check(text): spell_menu = QMenu('Spelling Suggestions') for word in self.dict.suggest(text): action = SpellAction(word, spell_menu) action.correct.connect(self.correctWord) spell_menu.addAction(action) # Only add the spelling suggests to the menu if there are # suggestions. if len(spell_menu.actions()) != 0: popup_menu.insertSeparator(popup_menu.actions()[0]) popup_menu.insertMenu(popup_menu.actions()[0], spell_menu) popup_menu.exec_(event.globalPos()) def correctWord(self, word): ''' Replaces the selected text with word. ''' cursor = self.textCursor() cursor.beginEditBlock() cursor.removeSelectedText() cursor.insertText(word) cursor.endEditBlock() class Highlighter(QSyntaxHighlighter): WORDS = u'(?iu)[\w\']+' def __init__(self, *args): QSyntaxHighlighter.__init__(self, *args) self.dict = None def setDict(self, dict): self.dict = dict def highlightBlock(self, text): if not self.dict: return text = unicode(text) format = QTextCharFormat() format.setUnderlineColor(Qt.red) format.setUnderlineStyle(QTextCharFormat.SpellCheckUnderline) for word_object in re.finditer(self.WORDS, text): if not self.dict.check(word_object.group()): self.setFormat(word_object.start(), word_object.end() - word_object.start(), format) class SpellAction(QAction): ''' A special QAction that returns the text in a signal. ''' correct = pyqtSignal(unicode) def __init__(self, *args): QAction.__init__(self, *args) self.triggered.connect(lambda x: self.correct.emit( unicode(self.text()))) def main(args=sys.argv): app = QApplication(args) spellEdit = SpellTextEdit() spellEdit.show() return app.exec_() if __name__ == '__main__': sys.exit(main())
The SpellTextEdit’s purpose is straightforward. It will mark misspelled words. Right clicking on a word in the SpellTextEdit will cause the word to become selected and display a context menu. If the word is misspelled and there are spelling suggestions the context menu will include a sub menu of those suggestions. Selecting a suggestion will replace the misspelled text with the selection.
The Highlighter class takes text, breaks it into words, checks if they are spelled correctly and if not underlines the misspelled ones with a red squiggle. I’m using a regular expression to split the words instead of using str.split because str.split will only split on whitespace and include punctuation (e.g. “.!*) as part of the words.
SpellAction is a simple class that allows for the action’s text to be sent with the signal. This is necessary for dynamically creating the list of possible correction words in the right click menu. The SpellAction is connected to a function that replaces the selected text with the signal text.
3 Responses to “QPlainTextEdit With In Line Spell Check”
Leave a Reply
Tags
Archives
- July 2010 (4)
- June 2010 (1)
- May 2010 (2)
- March 2010 (1)
- January 2010 (8)
- December 2009 (5)
- November 2009 (6)
- October 2009 (4)
- September 2009 (2)
- August 2009 (6)
- July 2009 (6)
- June 2009 (4)
- May 2009 (6)
- April 2009 (4)
- March 2009 (2)
- February 2009 (4)
- January 2009 (4)
- December 2008 (7)
- November 2008 (2)
August 27th, 2009 at 5:38 am
Thanks… This is great…
I had some problems importing pyqtSignal though… Ubuntu 9.04, Qt 4.5.
Nevertheless, replacing the custom SpellAction with a normal Action connected to a partial function did the trick:
action = QAction(suggestion, spell_menu)
action.connect(action, SIGNAL(“triggered()”), functools.partial(self.correctWord, word = suggestion))
spell_menu.addAction(action)
Don’t know if this approach is better, but it worked for me…
And again thanks for publishing this… Otherwise this would not be a feature I’d waste time on
(But it’s just so neat to have…)
August 27th, 2009 at 3:52 pm
The reason you can’t import pyqtSignal is because it is only available in PyQt 4.5+. While, Ubuntu 9.04 ships with Qt 4.5, it only ships with PyQt 4.4.4. The partial solution still works fine and is probably the cleanest solution for PyQt 4.4.
September 14th, 2009 at 1:17 pm
Hi,
Just realized that instead of overwriting the mousePressEvent on the Q(Plain)TextEdit an convert right clicks to left clicks inorder to move the cursor… Which is sort of an ugly hack, you can use QPlainTextEdit.cursorForPosition(QPoint)
cursor = self.cursorForPosition(event.pos())
instead of cursor = self.textCursor() in the contextMenuEvent implementation, this also means that the visible text cursor won’t move, and the user will not see that the text is selected…
This could still be achieved though, by calling self.setTextCursor(cursor)