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