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