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