QtSpell  0.8.4
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;
181  Codetable::instance()->lookup(lang, language, country);
182  if(!country.isEmpty()){
183  return QString("%1 (%2)").arg(language, country);
184  }else{
185  return language;
186  }
187 }
188 
189 void Checker::showContextMenu(QMenu* menu, const QPoint& pos, int wordPos)
190 {
191  QAction* insertPos = menu->actions().first();
192  if(m_speller && m_spellingEnabled){
193  QString word = getWord(wordPos);
194 
195  if(!checkWord(word)) {
196  QList<QString> suggestions = getSpellingSuggestions(word);
197  if(!suggestions.isEmpty()){
198  for(int i = 0, n = qMin(10, suggestions.length()); i < n; ++i){
199  QAction* action = new QAction(suggestions[i], menu);
200  action->setProperty("wordPos", wordPos);
201  action->setProperty("suggestion", suggestions[i]);
202  connect(action, SIGNAL(triggered()), this, SLOT(slotReplaceWord()));
203  menu->insertAction(insertPos, action);
204  }
205  if(suggestions.length() > 10) {
206  QMenu* moreMenu = new QMenu();
207  for(int i = 10, n = suggestions.length(); i < n; ++i){
208  QAction* action = new QAction(suggestions[i], moreMenu);
209  action->setProperty("wordPos", wordPos);
210  action->setProperty("suggestion", suggestions[i]);
211  connect(action, SIGNAL(triggered()), this, SLOT(slotReplaceWord()));
212  moreMenu->addAction(action);
213  }
214  QAction* action = new QAction(tr("More..."), menu);
215  menu->insertAction(insertPos, action);
216  action->setMenu(moreMenu);
217  }
218  menu->insertSeparator(insertPos);
219  }
220 
221  QAction* addAction = new QAction(tr("Add \"%1\" to dictionary").arg(word), menu);
222  addAction->setData(wordPos);
223  connect(addAction, SIGNAL(triggered()), this, SLOT(slotAddWord()));
224  menu->insertAction(insertPos, addAction);
225 
226  QAction* ignoreAction = new QAction(tr("Ignore \"%1\"").arg(word), menu);
227  ignoreAction->setData(wordPos);
228  connect(ignoreAction, SIGNAL(triggered()), this, SLOT(slotIgnoreWord()));
229  menu->insertAction(insertPos, ignoreAction);
230  menu->insertSeparator(insertPos);
231  }
232  }
233  if(m_spellingCheckbox){
234  QAction* action = new QAction(tr("Check spelling"), menu);
235  action->setCheckable(true);
236  action->setChecked(m_spellingEnabled);
237  connect(action, SIGNAL(toggled(bool)), this, SLOT(setSpellingEnabled(bool)));
238  menu->insertAction(insertPos, action);
239  }
240  if(m_speller && m_spellingEnabled){
241  QMenu* languagesMenu = new QMenu();
242  QActionGroup* actionGroup = new QActionGroup(languagesMenu);
243  foreach(const QString& lang, getLanguageList()){
244  QString text = getDecodeLanguageCodes() ? decodeLanguageCode(lang) : lang;
245  QAction* action = new QAction(text, languagesMenu);
246  action->setData(lang);
247  action->setCheckable(true);
248  action->setChecked(lang == getLanguage());
249  connect(action, SIGNAL(triggered(bool)), this, SLOT(slotSetLanguage(bool)));
250  languagesMenu->addAction(action);
251  actionGroup->addAction(action);
252  }
253  QAction* langsAction = new QAction(tr("Languages"), menu);
254  langsAction->setMenu(languagesMenu);
255  menu->insertAction(insertPos, langsAction);
256  menu->insertSeparator(insertPos);
257  }
258 
259  menu->exec(pos);
260  delete menu;
261 }
262 
263 void Checker::slotAddWord()
264 {
265  int wordPos = qobject_cast<QAction*>(QObject::sender())->property("wordPos").toInt();
266  int start, end;
267  addWordToDictionary(getWord(wordPos, &start, &end));
268  checkSpelling(start, end);
269 }
270 
271 void Checker::slotIgnoreWord()
272 {
273  int wordPos = qobject_cast<QAction*>(QObject::sender())->property("wordPos").toInt();
274  int start, end;
275  ignoreWord(getWord(wordPos, &start, &end));
276  checkSpelling(start, end);
277 }
278 
279 void Checker::slotReplaceWord()
280 {
281  QAction* action = qobject_cast<QAction*>(QObject::sender());
282  int wordPos = action->property("wordPos").toInt();
283  int start, end;
284  getWord(wordPos, &start, &end);
285  insertWord(start, end, action->property("suggestion").toString());
286 }
287 
288 void Checker::slotSetLanguage(bool checked)
289 {
290  if(checked) {
291  QAction* action = qobject_cast<QAction*>(QObject::sender());
292  QString lang = action->data().toString();
293  if(!setLanguage(lang)){
294  action->setChecked(false);
295  lang = "";
296  }
297  emit languageChanged(lang);
298  }
299 }
300 
301 } // 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 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:72
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.
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
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