~raoul-snyman/openlp/dont-test-uno-on-macos

« back to all changes in this revision

Viewing changes to openlp/core/ui/lib/spelltextedit.py

  • Committer: Raoul Snyman
  • Date: 2017-10-25 21:18:38 UTC
  • mfrom: (2780.1.2 refactorings-3)
  • Revision ID: raoul@snyman.info-20171025211838-be4wutkihlytvkk3
Merge in refactorings-3

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# -*- coding: utf-8 -*-
2
 
# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
3
 
 
4
 
###############################################################################
5
 
# OpenLP - Open Source Lyrics Projection                                      #
6
 
# --------------------------------------------------------------------------- #
7
 
# Copyright (c) 2008-2017 OpenLP Developers                                   #
8
 
# --------------------------------------------------------------------------- #
9
 
# This program is free software; you can redistribute it and/or modify it     #
10
 
# under the terms of the GNU General Public License as published by the Free  #
11
 
# Software Foundation; version 2 of the License.                              #
12
 
#                                                                             #
13
 
# This program is distributed in the hope that it will be useful, but WITHOUT #
14
 
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or       #
15
 
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for    #
16
 
# more details.                                                               #
17
 
#                                                                             #
18
 
# You should have received a copy of the GNU General Public License along     #
19
 
# with this program; if not, write to the Free Software Foundation, Inc., 59  #
20
 
# Temple Place, Suite 330, Boston, MA 02111-1307 USA                          #
21
 
###############################################################################
22
 
"""
23
 
The :mod:`~openlp.core.lib.spelltextedit` module contains a classes to add spell checking to an edit widget.
24
 
"""
25
 
 
26
 
import logging
27
 
import re
28
 
 
29
 
try:
30
 
    import enchant
31
 
    from enchant import DictNotFoundError
32
 
    from enchant.errors import Error
33
 
    ENCHANT_AVAILABLE = True
34
 
except ImportError:
35
 
    ENCHANT_AVAILABLE = False
36
 
 
37
 
# based on code from http://john.nachtimwald.com/2009/08/22/qplaintextedit-with-in-line-spell-check
38
 
 
39
 
from PyQt5 import QtCore, QtGui, QtWidgets
40
 
 
41
 
from openlp.core.common.i18n import translate
42
 
from openlp.core.lib import FormattingTags
43
 
from openlp.core.lib.ui import create_action
44
 
 
45
 
log = logging.getLogger(__name__)
46
 
 
47
 
 
48
 
class SpellTextEdit(QtWidgets.QPlainTextEdit):
49
 
    """
50
 
    Spell checking widget based on QPlanTextEdit.
51
 
    """
52
 
    def __init__(self, parent=None, formatting_tags_allowed=True):
53
 
        """
54
 
        Constructor.
55
 
        """
56
 
        global ENCHANT_AVAILABLE
57
 
        super(SpellTextEdit, self).__init__(parent)
58
 
        self.formatting_tags_allowed = formatting_tags_allowed
59
 
        # Default dictionary based on the current locale.
60
 
        if ENCHANT_AVAILABLE:
61
 
            try:
62
 
                self.dictionary = enchant.Dict()
63
 
                self.highlighter = Highlighter(self.document())
64
 
                self.highlighter.spelling_dictionary = self.dictionary
65
 
            except (Error, DictNotFoundError):
66
 
                ENCHANT_AVAILABLE = False
67
 
                log.debug('Could not load default dictionary')
68
 
 
69
 
    def mousePressEvent(self, event):
70
 
        """
71
 
        Handle mouse clicks within the text edit region.
72
 
        """
73
 
        if event.button() == QtCore.Qt.RightButton:
74
 
            # Rewrite the mouse event to a left button event so the cursor is moved to the location of the pointer.
75
 
            event = QtGui.QMouseEvent(QtCore.QEvent.MouseButtonPress,
76
 
                                      event.pos(), QtCore.Qt.LeftButton, QtCore.Qt.LeftButton, QtCore.Qt.NoModifier)
77
 
        QtWidgets.QPlainTextEdit.mousePressEvent(self, event)
78
 
 
79
 
    def contextMenuEvent(self, event):
80
 
        """
81
 
        Provide the context menu for the text edit region.
82
 
        """
83
 
        popup_menu = self.createStandardContextMenu()
84
 
        # Select the word under the cursor.
85
 
        cursor = self.textCursor()
86
 
        # only select text if not already selected
87
 
        if not cursor.hasSelection():
88
 
            cursor.select(QtGui.QTextCursor.WordUnderCursor)
89
 
        self.setTextCursor(cursor)
90
 
        # Add menu with available languages.
91
 
        if ENCHANT_AVAILABLE:
92
 
            lang_menu = QtWidgets.QMenu(translate('OpenLP.SpellTextEdit', 'Language:'))
93
 
            for lang in enchant.list_languages():
94
 
                action = create_action(lang_menu, lang, text=lang, checked=lang == self.dictionary.tag)
95
 
                lang_menu.addAction(action)
96
 
            popup_menu.insertSeparator(popup_menu.actions()[0])
97
 
            popup_menu.insertMenu(popup_menu.actions()[0], lang_menu)
98
 
            lang_menu.triggered.connect(self.set_language)
99
 
        # Check if the selected word is misspelled and offer spelling suggestions if it is.
100
 
        if ENCHANT_AVAILABLE and self.textCursor().hasSelection():
101
 
            text = self.textCursor().selectedText()
102
 
            if not self.dictionary.check(text):
103
 
                spell_menu = QtWidgets.QMenu(translate('OpenLP.SpellTextEdit', 'Spelling Suggestions'))
104
 
                for word in self.dictionary.suggest(text):
105
 
                    action = SpellAction(word, spell_menu)
106
 
                    action.correct.connect(self.correct_word)
107
 
                    spell_menu.addAction(action)
108
 
                # Only add the spelling suggests to the menu if there are suggestions.
109
 
                if spell_menu.actions():
110
 
                    popup_menu.insertMenu(popup_menu.actions()[0], spell_menu)
111
 
        tag_menu = QtWidgets.QMenu(translate('OpenLP.SpellTextEdit', 'Formatting Tags'))
112
 
        if self.formatting_tags_allowed:
113
 
            for html in FormattingTags.get_html_tags():
114
 
                action = SpellAction(html['desc'], tag_menu)
115
 
                action.correct.connect(self.html_tag)
116
 
                tag_menu.addAction(action)
117
 
            popup_menu.insertSeparator(popup_menu.actions()[0])
118
 
            popup_menu.insertMenu(popup_menu.actions()[0], tag_menu)
119
 
        popup_menu.exec(event.globalPos())
120
 
 
121
 
    def set_language(self, action):
122
 
        """
123
 
        Changes the language for this spelltextedit.
124
 
 
125
 
        :param action: The action.
126
 
        """
127
 
        self.dictionary = enchant.Dict(action.text())
128
 
        self.highlighter.spelling_dictionary = self.dictionary
129
 
        self.highlighter.highlightBlock(self.toPlainText())
130
 
        self.highlighter.rehighlight()
131
 
 
132
 
    def correct_word(self, word):
133
 
        """
134
 
        Replaces the selected text with word.
135
 
        """
136
 
        cursor = self.textCursor()
137
 
        cursor.beginEditBlock()
138
 
        cursor.removeSelectedText()
139
 
        cursor.insertText(word)
140
 
        cursor.endEditBlock()
141
 
 
142
 
    def html_tag(self, tag):
143
 
        """
144
 
        Replaces the selected text with word.
145
 
        """
146
 
        tag = tag.replace('&', '')
147
 
        for html in FormattingTags.get_html_tags():
148
 
            if tag == html['desc']:
149
 
                cursor = self.textCursor()
150
 
                if self.textCursor().hasSelection():
151
 
                    text = cursor.selectedText()
152
 
                    cursor.beginEditBlock()
153
 
                    cursor.removeSelectedText()
154
 
                    cursor.insertText(html['start tag'])
155
 
                    cursor.insertText(text)
156
 
                    cursor.insertText(html['end tag'])
157
 
                    cursor.endEditBlock()
158
 
                else:
159
 
                    cursor = self.textCursor()
160
 
                    cursor.insertText(html['start tag'])
161
 
                    cursor.insertText(html['end tag'])
162
 
 
163
 
 
164
 
class Highlighter(QtGui.QSyntaxHighlighter):
165
 
    """
166
 
    Provides a text highlighter for pointing out spelling errors in text.
167
 
    """
168
 
    WORDS = r'(?iu)[\w\']+'
169
 
 
170
 
    def __init__(self, *args):
171
 
        """
172
 
        Constructor
173
 
        """
174
 
        super(Highlighter, self).__init__(*args)
175
 
        self.spelling_dictionary = None
176
 
 
177
 
    def highlightBlock(self, text):
178
 
        """
179
 
        Highlight mis spelt words in a block of text.
180
 
 
181
 
        Note, this is a Qt hook.
182
 
        """
183
 
        if not self.spelling_dictionary:
184
 
            return
185
 
        text = str(text)
186
 
        char_format = QtGui.QTextCharFormat()
187
 
        char_format.setUnderlineColor(QtCore.Qt.red)
188
 
        char_format.setUnderlineStyle(QtGui.QTextCharFormat.SpellCheckUnderline)
189
 
        for word_object in re.finditer(self.WORDS, text):
190
 
            if not self.spelling_dictionary.check(word_object.group()):
191
 
                self.setFormat(word_object.start(), word_object.end() - word_object.start(), char_format)
192
 
 
193
 
 
194
 
class SpellAction(QtWidgets.QAction):
195
 
    """
196
 
    A special QAction that returns the text in a signal.
197
 
    """
198
 
    correct = QtCore.pyqtSignal(str)
199
 
 
200
 
    def __init__(self, *args):
201
 
        """
202
 
        Constructor
203
 
        """
204
 
        super(SpellAction, self).__init__(*args)
205
 
        self.triggered.connect(lambda x: self.correct.emit(self.text()))