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 
29 static 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 
39 static 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 
49 class TranslationsInit {
50 public:
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  }
60 private:
61  QTranslator spellTranslator;
62 };
63 
64 
65 namespace QtSpell {
66 
67 bool checkLanguageInstalled(const QString &lang)
68 {
69  return get_enchant_broker()->dict_exists(lang.toStdString());
70 }
71 
72 Checker::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 
91 bool Checker::setLanguage(const QString &lang)
92 {
93  bool success = setLanguageInternal(lang);
94  if(isAttached()){
95  checkSpelling();
96  }
97  return success;
98 }
99 
100 bool 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 
128 void Checker::addWordToDictionary(const QString &word)
129 {
130  if(m_speller){
131  m_speller->add(word.toUtf8().data());
132  }
133 }
134 
135 bool 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 
151 void Checker::ignoreWord(const QString &word) const
152 {
153  m_speller->add_to_session(word.toUtf8().data());
154 }
155 
156 QList<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 
169 QList<QString> Checker::getLanguageList()
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 
178 QString 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 
193 void 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 
267 void 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 
275 void 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 
283 void 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 
292 void 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
virtual ~Checker()
QtSpell::Checker object destructor.
Definition: Checker.cpp:86
static Codetable * instance()
Get codetable instance.
Definition: Codetable.cpp:31
QList< QString > getSpellingSuggestions(const QString &word) const
Retreive a list of spelling suggestions for the misspelled word.
Definition: Checker.cpp:156
bool checkLanguageInstalled(const QString &lang)
Check whether the dictionary for a language is installed.
Definition: Checker.cpp:67
bool getDecodeLanguageCodes() const
Return whether langauge codes are decoded in the UI.
Definition: QtSpell.hpp:103
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
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 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
void languageChanged(const QString &newLang)
This signal is emitted when the user selects a new language from the spellchecker UI...
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. "en_US" -> "English (United States)")...
Definition: Checker.cpp:178
virtual QString getWord(int pos, int *start=0, int *end=0) const =0
Get the word at the specified cursor position.
QtSpell namespace.
Definition: Checker.cpp:65
bool setLanguage(const QString &lang)
Set the spell checking language.
Definition: Checker.cpp:91
static QList< QString > getLanguageList()
Requests the list of languages available for spell checking.
Definition: Checker.cpp:169
virtual bool isAttached() const =0
Returns whether a widget is attached to the checker.
const QString & getLanguage() const
Retreive the current spelling language.
Definition: QtSpell.hpp:90
bool checkWord(const QString &word) const
Check the specified word.
Definition: Checker.cpp:135