QtSpell  0.7.2
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 Checker::Checker(QObject* parent)
59  : QObject(parent),
60  m_speller(0),
61  m_decodeCodes(false),
62  m_spellingCheckbox(false),
63  m_spellingEnabled(true),
64  m_wordRegEx("^[A-Za-z0-9']+$")
65 {
66  static TranslationsInit tsInit;
67  Q_UNUSED(tsInit);
68 
69  // setLanguageInternal: setLanguage is virtual and cannot be called in the constructor
70  setLanguageInternal("");
71 }
72 
74 {
75  delete m_speller;
76 }
77 
78 bool Checker::setLanguage(const QString &lang)
79 {
80  bool success = setLanguageInternal(lang);
81  if(isAttached()){
82  checkSpelling();
83  }
84  return success;
85 }
86 
87 bool Checker::setLanguageInternal(const QString &lang)
88 {
89  delete m_speller;
90  m_speller = 0;
91  m_lang = lang;
92 
93  // Determine language from system locale
94  if(m_lang.isEmpty()){
95  m_lang = QLocale::system().name();
96  if(m_lang.toLower() == "c" || m_lang.isEmpty()){
97  qWarning("Cannot use system locale %s", m_lang.toLatin1().data());
98  m_lang = QString::null;
99  return false;
100  }
101  }
102 
103  // Request dictionary
104  try {
105  m_speller = enchant::Broker::instance()->request_dict(m_lang.toStdString());
106  } catch(enchant::Exception& e) {
107  qWarning("Failed to load dictionary: %s", e.what());
108  m_lang = QString::null;
109  return false;
110  }
111 
112  return true;
113 }
114 
115 void Checker::addWordToDictionary(const QString &word)
116 {
117  if(m_speller){
118  m_speller->add(word.toUtf8().data());
119  }
120 }
121 
122 bool Checker::checkWord(const QString &word) const
123 {
124  if(!m_speller || !m_spellingEnabled){
125  return true;
126  }
127  // Skip empty strings and single characters
128  if(word.length() < 2){
129  return true;
130  }
131  // Don't check non-word blocks
132  if(!word.contains(m_wordRegEx)){
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:73
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
void addWordToDictionary(const QString &word)
Add the specified word to the user dictionary.
Definition: Checker.cpp:115
const QString & getLanguage() const
Retreive the current spelling language.
Definition: QtSpell.hpp:78
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:91
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:58
void setSpellingEnabled(bool enabled)
Set whether spell checking should be performed.
Definition: QtSpell.hpp:159
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.
bool setLanguage(const QString &lang)
Set the spell checking language.
Definition: Checker.cpp:78
bool checkWord(const QString &word) const
Check the specified word.
Definition: Checker.cpp:122
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.