1#!/usr/bin/env python 2from __future__ import division, absolute_import, unicode_literals 3import re 4 5from qtpy.QtCore import Qt 6from qtpy.QtCore import QEvent 7from qtpy.QtCore import Signal 8from qtpy.QtGui import QMouseEvent 9from qtpy.QtGui import QSyntaxHighlighter 10from qtpy.QtGui import QTextCharFormat 11from qtpy.QtGui import QTextCursor 12from qtpy.QtWidgets import QAction 13 14from .. import qtutils 15from .. import spellcheck 16from ..i18n import N_ 17from .text import HintedTextEdit 18 19 20# pylint: disable=too-many-ancestors 21class SpellCheckTextEdit(HintedTextEdit): 22 def __init__(self, context, hint, parent=None): 23 HintedTextEdit.__init__(self, context, hint, parent) 24 25 # Default dictionary based on the current locale. 26 self.spellcheck = spellcheck.NorvigSpellCheck() 27 self.highlighter = Highlighter(self.document(), self.spellcheck) 28 29 def set_dictionary(self, dictionary): 30 self.spellcheck.set_dictionary(dictionary) 31 32 def mousePressEvent(self, event): 33 if event.button() == Qt.RightButton: 34 # Rewrite the mouse event to a left button event so the cursor is 35 # moved to the location of the pointer. 36 event = QMouseEvent( 37 QEvent.MouseButtonPress, 38 event.pos(), 39 Qt.LeftButton, 40 Qt.LeftButton, 41 Qt.NoModifier, 42 ) 43 HintedTextEdit.mousePressEvent(self, event) 44 45 def context_menu(self): 46 popup_menu = HintedTextEdit.createStandardContextMenu(self) 47 48 # Select the word under the cursor. 49 cursor = self.textCursor() 50 cursor.select(QTextCursor.WordUnderCursor) 51 self.setTextCursor(cursor) 52 53 # Check if the selected word is misspelled and offer spelling 54 # suggestions if it is. 55 spell_menu = None 56 if self.textCursor().hasSelection(): 57 text = self.textCursor().selectedText() 58 if not self.spellcheck.check(text): 59 title = N_('Spelling Suggestions') 60 spell_menu = qtutils.create_menu(title, self) 61 for word in self.spellcheck.suggest(text): 62 action = SpellAction(word, spell_menu) 63 action.result.connect(self.correct) 64 spell_menu.addAction(action) 65 # Only add the spelling suggests to the menu if there are 66 # suggestions. 67 if spell_menu.actions(): 68 popup_menu.addSeparator() 69 popup_menu.addMenu(spell_menu) 70 71 return popup_menu, spell_menu 72 73 def contextMenuEvent(self, event): 74 popup_menu, _ = self.context_menu() 75 popup_menu.exec_(self.mapToGlobal(event.pos())) 76 77 def correct(self, word): 78 """Replaces the selected text with word.""" 79 cursor = self.textCursor() 80 cursor.beginEditBlock() 81 82 cursor.removeSelectedText() 83 cursor.insertText(word) 84 85 cursor.endEditBlock() 86 87 88class Highlighter(QSyntaxHighlighter): 89 90 WORDS = r"(?iu)[\w']+" 91 92 def __init__(self, doc, spellcheck_widget): 93 QSyntaxHighlighter.__init__(self, doc) 94 self.spellcheck = spellcheck_widget 95 self.enabled = False 96 97 def enable(self, enabled): 98 self.enabled = enabled 99 self.rehighlight() 100 101 def highlightBlock(self, text): 102 if not self.enabled: 103 return 104 fmt = QTextCharFormat() 105 fmt.setUnderlineColor(Qt.red) 106 fmt.setUnderlineStyle(QTextCharFormat.SpellCheckUnderline) 107 108 for word_object in re.finditer(self.WORDS, text): 109 if not self.spellcheck.check(word_object.group()): 110 self.setFormat( 111 word_object.start(), word_object.end() - word_object.start(), fmt 112 ) 113 114 115class SpellAction(QAction): 116 """QAction that returns the text in a signal.""" 117 118 result = Signal(object) 119 120 def __init__(self, *args): 121 QAction.__init__(self, *args) 122 # pylint: disable=no-member 123 self.triggered.connect(self.correct) 124 125 def correct(self): 126 self.result.emit(self.text()) 127