1
# -*- coding: utf-8 -*-
2
# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
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. #
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 #
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
###############################################################################
23
The :mod:`~openlp.core.lib.spelltextedit` module contains a classes to add spell checking to an edit widget.
31
from enchant import DictNotFoundError
32
from enchant.errors import Error
33
ENCHANT_AVAILABLE = True
35
ENCHANT_AVAILABLE = False
37
# based on code from http://john.nachtimwald.com/2009/08/22/qplaintextedit-with-in-line-spell-check
39
from PyQt5 import QtCore, QtGui, QtWidgets
41
from openlp.core.common.i18n import translate
42
from openlp.core.lib import FormattingTags
43
from openlp.core.lib.ui import create_action
45
log = logging.getLogger(__name__)
48
class SpellTextEdit(QtWidgets.QPlainTextEdit):
50
Spell checking widget based on QPlanTextEdit.
52
def __init__(self, parent=None, formatting_tags_allowed=True):
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.
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')
69
def mousePressEvent(self, event):
71
Handle mouse clicks within the text edit region.
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)
79
def contextMenuEvent(self, event):
81
Provide the context menu for the text edit region.
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.
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())
121
def set_language(self, action):
123
Changes the language for this spelltextedit.
125
:param action: The action.
127
self.dictionary = enchant.Dict(action.text())
128
self.highlighter.spelling_dictionary = self.dictionary
129
self.highlighter.highlightBlock(self.toPlainText())
130
self.highlighter.rehighlight()
132
def correct_word(self, word):
134
Replaces the selected text with word.
136
cursor = self.textCursor()
137
cursor.beginEditBlock()
138
cursor.removeSelectedText()
139
cursor.insertText(word)
140
cursor.endEditBlock()
142
def html_tag(self, tag):
144
Replaces the selected text with word.
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()
159
cursor = self.textCursor()
160
cursor.insertText(html['start tag'])
161
cursor.insertText(html['end tag'])
164
class Highlighter(QtGui.QSyntaxHighlighter):
166
Provides a text highlighter for pointing out spelling errors in text.
168
WORDS = r'(?iu)[\w\']+'
170
def __init__(self, *args):
174
super(Highlighter, self).__init__(*args)
175
self.spelling_dictionary = None
177
def highlightBlock(self, text):
179
Highlight mis spelt words in a block of text.
181
Note, this is a Qt hook.
183
if not self.spelling_dictionary:
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)
194
class SpellAction(QtWidgets.QAction):
196
A special QAction that returns the text in a signal.
198
correct = QtCore.pyqtSignal(str)
200
def __init__(self, *args):
204
super(SpellAction, self).__init__(*args)
205
self.triggered.connect(lambda x: self.correct.emit(self.text()))