QtSpell 0.8.5
Spell checking for Qt text widgets
Checker.cpp
1/* QtSpell - Spell checking for Qt text widgets.
2 * Copyright (c) 2014 Sandro Mani
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 2 of the License, or
7 * (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License along
15 * with this program; if not, write to the Free Software Foundation, Inc.,
16 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
17 */
18
19#include "QtSpell.hpp"
20#include "Codetable.hpp"
21
22#include <enchant++.h>
23#include <QApplication>
24#include <QLibraryInfo>
25#include <QLocale>
26#include <QMenu>
27#include <QTranslator>
28
29static void dict_describe_cb(const char* const lang_tag,
30 const char* const /*provider_name*/,
31 const char* const /*provider_desc*/,
32 const char* const /*provider_file*/,
33 void* user_data)
34{
35 QList<QString>* languages = static_cast<QList<QString>*>(user_data);
36 languages->append(lang_tag);
37}
38
39static enchant::Broker* get_enchant_broker() {
40#ifdef QTSPELL_ENCHANT2
41 static enchant::Broker broker;
42 return &broker;
43#else
44 return enchant::Broker::instance();
45#endif
46}
47
48
49class TranslationsInit {
50public:
51 TranslationsInit(){
52 QString translationsPath = QLibraryInfo::location(QLibraryInfo::TranslationsPath);
53#ifdef Q_OS_WIN
54 QDir packageDir = QDir(QString("%1/../").arg(QApplication::applicationDirPath()));
55 translationsPath = packageDir.absolutePath() + translationsPath.mid(QLibraryInfo::location(QLibraryInfo::PrefixPath).length());
56#endif
57 spellTranslator.load("QtSpell_" + QLocale::system().name(), translationsPath);
58 QApplication::instance()->installTranslator(&spellTranslator);
59 }
60private:
61 QTranslator spellTranslator;
62};
63
64
65namespace QtSpell {
66
67bool checkLanguageInstalled(const QString &lang)
68{
69 return get_enchant_broker()->dict_exists(lang.toStdString());
70}
71
72Checker::Checker(QObject* parent)
73 : QObject(parent),
74 m_speller(0),
75 m_decodeCodes(false),
76 m_spellingCheckbox(false),
77 m_spellingEnabled(true)
78{
79 static TranslationsInit tsInit;
80 Q_UNUSED(tsInit);
81
82 // setLanguageInternal: setLanguage is virtual and cannot be called in the constructor
83 setLanguageInternal("");
84}
85
87{
88 delete m_speller;
89}
90
91bool Checker::setLanguage(const QString &lang)
92{
93 bool success = setLanguageInternal(lang);
94 if(isAttached()){
96 }
97 return success;
98}
99
100bool Checker::setLanguageInternal(const QString &lang)
101{
102 delete m_speller;
103 m_speller = 0;
104 m_lang = lang;
105
106 // Determine language from system locale
107 if(m_lang.isEmpty()){
108 m_lang = QLocale::system().name();
109 if(m_lang.toLower() == "c" || m_lang.isEmpty()){
110 qWarning("Cannot use system locale %s", m_lang.toLatin1().data());
111 m_lang = QString::null;
112 return false;
113 }
114 }
115
116 // Request dictionary
117 try {
118 m_speller = get_enchant_broker()->request_dict(m_lang.toStdString());
119 } catch(enchant::Exception& e) {
120 qWarning("Failed to load dictionary: %s", e.what());
121 m_lang = QString::null;
122 return false;
123 }
124
125 return true;
126}
127
128void Checker::addWordToDictionary(const QString &word)
129{
130 if(m_speller){
131 m_speller->add(word.toUtf8().data());
132 }
133}
134
135bool Checker::checkWord(const QString &word) const
136{
137 if(!m_speller || !m_spellingEnabled){
138 return true;
139 }
140 // Skip empty strings and single characters
141 if(word.length() < 2){
142 return true;
143 }
144 try{
145 return m_speller->check(word.toUtf8().data());
146 }catch(const enchant::Exception&){
147 return true;
148 }
149}
150
151void Checker::ignoreWord(const QString &word) const
152{
153 m_speller->add_to_session(word.toUtf8().data());
154}
155
156QList<QString> Checker::getSpellingSuggestions(const QString& word) const
157{
158 QList<QString> list;
159 if(m_speller){
160 std::vector<std::string> suggestions;
161 m_speller->suggest(word.toUtf8().data(), suggestions);
162 for(std::size_t i = 0, n = suggestions.size(); i < n; ++i){
163 list.append(QString::fromUtf8(suggestions[i].c_str()));
164 }
165 }
166 return list;
167}
168
170{
171 enchant::Broker* broker = get_enchant_broker();
172 QList<QString> languages;
173 broker->list_dicts(dict_describe_cb, &languages);
174 qSort(languages);
175 return languages;
176}
177
178QString Checker::decodeLanguageCode(const QString &lang)
179{
180 QString language, country, extra;
181 Codetable::instance()->lookup(lang, language, country, extra);
182 if(!country.isEmpty()){
183 QString decoded = QString("%1 (%2)").arg(language, country);
184 if(!extra.isEmpty()) {
185 decoded += QString(" [%1]").arg(extra);
186 }
187 return decoded;
188 }else{
189 return language;
190 }
191}
192
193void Checker::showContextMenu(QMenu* menu, const QPoint& pos, int wordPos)
194{
195 QAction* insertPos = menu->actions().first();
196 if(m_speller && m_spellingEnabled){
197 QString word = getWord(wordPos);
198
199 if(!checkWord(word)) {
200 QList<QString> suggestions = getSpellingSuggestions(word);
201 if(!suggestions.isEmpty()){
202 for(int i = 0, n = qMin(10, suggestions.length()); i < n; ++i){
203 QAction* action = new QAction(suggestions[i], menu);
204 action->setProperty("wordPos", wordPos);
205 action->setProperty("suggestion", suggestions[i]);
206 connect(action, SIGNAL(triggered()), this, SLOT(slotReplaceWord()));
207 menu->insertAction(insertPos, action);
208 }
209 if(suggestions.length() > 10) {
210 QMenu* moreMenu = new QMenu();
211 for(int i = 10, n = suggestions.length(); i < n; ++i){
212 QAction* action = new QAction(suggestions[i], moreMenu);
213 action->setProperty("wordPos", wordPos);
214 action->setProperty("suggestion", suggestions[i]);
215 connect(action, SIGNAL(triggered()), this, SLOT(slotReplaceWord()));
216 moreMenu->addAction(action);
217 }
218 QAction* action = new QAction(tr("More..."), menu);
219 menu->insertAction(insertPos, action);
220 action->setMenu(moreMenu);
221 }
222 menu->insertSeparator(insertPos);
223 }
224
225 QAction* addAction = new QAction(tr("Add \"%1\" to dictionary").arg(word), menu);
226 addAction->setData(wordPos);
227 connect(addAction, SIGNAL(triggered()), this, SLOT(slotAddWord()));
228 menu->insertAction(insertPos, addAction);
229
230 QAction* ignoreAction = new QAction(tr("Ignore \"%1\"").arg(word), menu);
231 ignoreAction->setData(wordPos);
232 connect(ignoreAction, SIGNAL(triggered()), this, SLOT(slotIgnoreWord()));
233 menu->insertAction(insertPos, ignoreAction);
234 menu->insertSeparator(insertPos);
235 }
236 }
237 if(m_spellingCheckbox){
238 QAction* action = new QAction(tr("Check spelling"), menu);
239 action->setCheckable(true);
240 action->setChecked(m_spellingEnabled);
241 connect(action, SIGNAL(toggled(bool)), this, SLOT(setSpellingEnabled(bool)));
242 menu->insertAction(insertPos, action);
243 }
244 if(m_speller && m_spellingEnabled){
245 QMenu* languagesMenu = new QMenu();
246 QActionGroup* actionGroup = new QActionGroup(languagesMenu);
247 foreach(const QString& lang, getLanguageList()){
248 QString text = getDecodeLanguageCodes() ? decodeLanguageCode(lang) : lang;
249 QAction* action = new QAction(text, languagesMenu);
250 action->setData(lang);
251 action->setCheckable(true);
252 action->setChecked(lang == getLanguage());
253 connect(action, SIGNAL(triggered(bool)), this, SLOT(slotSetLanguage(bool)));
254 languagesMenu->addAction(action);
255 actionGroup->addAction(action);
256 }
257 QAction* langsAction = new QAction(tr("Languages"), menu);
258 langsAction->setMenu(languagesMenu);
259 menu->insertAction(insertPos, langsAction);
260 menu->insertSeparator(insertPos);
261 }
262
263 menu->exec(pos);
264 delete menu;
265}
266
267void Checker::slotAddWord()
268{
269 int wordPos = qobject_cast<QAction*>(QObject::sender())->property("wordPos").toInt();
270 int start, end;
271 addWordToDictionary(getWord(wordPos, &start, &end));
272 checkSpelling(start, end);
273}
274
275void Checker::slotIgnoreWord()
276{
277 int wordPos = qobject_cast<QAction*>(QObject::sender())->property("wordPos").toInt();
278 int start, end;
279 ignoreWord(getWord(wordPos, &start, &end));
280 checkSpelling(start, end);
281}
282
283void Checker::slotReplaceWord()
284{
285 QAction* action = qobject_cast<QAction*>(QObject::sender());
286 int wordPos = action->property("wordPos").toInt();
287 int start, end;
288 getWord(wordPos, &start, &end);
289 insertWord(start, end, action->property("suggestion").toString());
290}
291
292void Checker::slotSetLanguage(bool checked)
293{
294 if(checked) {
295 QAction* action = qobject_cast<QAction*>(QObject::sender());
296 QString lang = action->data().toString();
297 if(!setLanguage(lang)){
298 action->setChecked(false);
299 lang = "";
300 }
301 emit languageChanged(lang);
302 }
303}
304
305} // QtSpell
Checker(QObject *parent=0)
QtSpell::Checker object constructor.
Definition: Checker.cpp:72
bool setLanguage(const QString &lang)
Set the spell checking language.
Definition: Checker.cpp:91
virtual bool isAttached() const =0
Returns whether a widget is attached to the checker.
QList< QString > getSpellingSuggestions(const QString &word) const
Retreive a list of spelling suggestions for the misspelled word.
Definition: Checker.cpp:156
static QList< QString > getLanguageList()
Requests the list of languages available for spell checking.
Definition: Checker.cpp:169
virtual ~Checker()
QtSpell::Checker object destructor.
Definition: Checker.cpp:86
const QString & getLanguage() const
Retreive the current spelling language.
Definition: QtSpell.hpp:90
bool getDecodeLanguageCodes() const
Return whether langauge codes are decoded in the UI.
Definition: QtSpell.hpp:103
void setSpellingEnabled(bool enabled)
Set whether spell checking should be performed.
Definition: QtSpell.hpp:171
bool checkWord(const QString &word) const
Check the specified word.
Definition: Checker.cpp:135
void languageChanged(const QString &newLang)
This signal is emitted when the user selects a new language from the spellchecker UI.
virtual void checkSpelling(int start=0, int end=-1)=0
Check the spelling.
virtual QString getWord(int pos, int *start=0, int *end=0) const =0
Get the word at the specified cursor position.
virtual void insertWord(int start, int end, const QString &word)=0
Replaces the specified range with the specified word.
static QString decodeLanguageCode(const QString &lang)
Translates a language code to a human readable format (i.e. "en_US" -> "English (United States)").
Definition: Checker.cpp:178
void addWordToDictionary(const QString &word)
Add the specified word to the user dictionary.
Definition: Checker.cpp:128
void ignoreWord(const QString &word) const
Ignore a word for the current session.
Definition: Checker.cpp:151
static Codetable * instance()
Get codetable instance.
Definition: Codetable.cpp:31
void lookup(const QString &language_code, QString &language_name, QString &country_name, QString &extra) const
Looks up the language and country name for the specified language code. If no matching entries are fo...
Definition: Codetable.cpp:37
QtSpell namespace.
Definition: Checker.cpp:65
bool checkLanguageInstalled(const QString &lang)
Check whether the dictionary for a language is installed.
Definition: Checker.cpp:67