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