|
QtSpell
0.8.2
Spell checking for Qt text widgets
|
00001 /* QtSpell - Spell checking for Qt text widgets. 00002 * Copyright (c) 2014 Sandro Mani 00003 * 00004 * This program is free software; you can redistribute it and/or modify 00005 * it under the terms of the GNU General Public License as published by 00006 * the Free Software Foundation; either version 2 of the License, or 00007 * (at your option) any later version. 00008 * 00009 * This program is distributed in the hope that it will be useful, 00010 * but WITHOUT ANY WARRANTY; without even the implied warranty of 00011 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 00012 * GNU General Public License for more details. 00013 * 00014 * You should have received a copy of the GNU General Public License along 00015 * with this program; if not, write to the Free Software Foundation, Inc., 00016 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 00017 */ 00018 00019 #include "QtSpell.hpp" 00020 #include "Codetable.hpp" 00021 00022 #include <enchant++.h> 00023 #include <QApplication> 00024 #include <QLibraryInfo> 00025 #include <QLocale> 00026 #include <QMenu> 00027 #include <QTranslator> 00028 00029 static void dict_describe_cb(const char* const lang_tag, 00030 const char* const /*provider_name*/, 00031 const char* const /*provider_desc*/, 00032 const char* const /*provider_file*/, 00033 void* user_data) 00034 { 00035 QList<QString>* languages = static_cast<QList<QString>*>(user_data); 00036 languages->append(lang_tag); 00037 } 00038 00039 00040 class TranslationsInit { 00041 public: 00042 TranslationsInit(){ 00043 QString translationsPath = QLibraryInfo::location(QLibraryInfo::TranslationsPath); 00044 #ifdef Q_OS_WIN 00045 QDir packageDir = QDir(QString("%1/../").arg(QApplication::applicationDirPath())); 00046 translationsPath = packageDir.absolutePath() + translationsPath.mid(QLibraryInfo::location(QLibraryInfo::PrefixPath).length()); 00047 #endif 00048 spellTranslator.load("QtSpell_" + QLocale::system().name(), translationsPath); 00049 QApplication::instance()->installTranslator(&spellTranslator); 00050 } 00051 private: 00052 QTranslator spellTranslator; 00053 }; 00054 00055 00056 namespace QtSpell { 00057 00058 bool checkLanguageInstalled(const QString &lang) 00059 { 00060 return enchant::Broker::instance()->dict_exists(lang.toStdString()); 00061 } 00062 00063 Checker::Checker(QObject* parent) 00064 : QObject(parent), 00065 m_speller(0), 00066 m_decodeCodes(false), 00067 m_spellingCheckbox(false), 00068 m_spellingEnabled(true) 00069 { 00070 static TranslationsInit tsInit; 00071 Q_UNUSED(tsInit); 00072 00073 // setLanguageInternal: setLanguage is virtual and cannot be called in the constructor 00074 setLanguageInternal(""); 00075 } 00076 00077 Checker::~Checker() 00078 { 00079 delete m_speller; 00080 } 00081 00082 bool Checker::setLanguage(const QString &lang) 00083 { 00084 bool success = setLanguageInternal(lang); 00085 if(isAttached()){ 00086 checkSpelling(); 00087 } 00088 return success; 00089 } 00090 00091 bool Checker::setLanguageInternal(const QString &lang) 00092 { 00093 delete m_speller; 00094 m_speller = 0; 00095 m_lang = lang; 00096 00097 // Determine language from system locale 00098 if(m_lang.isEmpty()){ 00099 m_lang = QLocale::system().name(); 00100 if(m_lang.toLower() == "c" || m_lang.isEmpty()){ 00101 qWarning("Cannot use system locale %s", m_lang.toLatin1().data()); 00102 m_lang = QString::null; 00103 return false; 00104 } 00105 } 00106 00107 // Request dictionary 00108 try { 00109 m_speller = enchant::Broker::instance()->request_dict(m_lang.toStdString()); 00110 } catch(enchant::Exception& e) { 00111 qWarning("Failed to load dictionary: %s", e.what()); 00112 m_lang = QString::null; 00113 return false; 00114 } 00115 00116 return true; 00117 } 00118 00119 void Checker::addWordToDictionary(const QString &word) 00120 { 00121 if(m_speller){ 00122 m_speller->add(word.toUtf8().data()); 00123 } 00124 } 00125 00126 bool Checker::checkWord(const QString &word) const 00127 { 00128 if(!m_speller || !m_spellingEnabled){ 00129 return true; 00130 } 00131 // Skip empty strings and single characters 00132 if(word.length() < 2){ 00133 return true; 00134 } 00135 try{ 00136 return m_speller->check(word.toUtf8().data()); 00137 }catch(const enchant::Exception&){ 00138 return true; 00139 } 00140 } 00141 00142 void Checker::ignoreWord(const QString &word) const 00143 { 00144 m_speller->add_to_session(word.toUtf8().data()); 00145 } 00146 00147 QList<QString> Checker::getSpellingSuggestions(const QString& word) const 00148 { 00149 QList<QString> list; 00150 if(m_speller){ 00151 std::vector<std::string> suggestions; 00152 m_speller->suggest(word.toUtf8().data(), suggestions); 00153 for(std::size_t i = 0, n = suggestions.size(); i < n; ++i){ 00154 list.append(QString::fromUtf8(suggestions[i].c_str())); 00155 } 00156 } 00157 return list; 00158 } 00159 00160 QList<QString> Checker::getLanguageList() 00161 { 00162 enchant::Broker* broker = enchant::Broker::instance(); 00163 QList<QString> languages; 00164 broker->list_dicts(dict_describe_cb, &languages); 00165 qSort(languages); 00166 return languages; 00167 } 00168 00169 QString Checker::decodeLanguageCode(const QString &lang) 00170 { 00171 QString language, country; 00172 Codetable::instance()->lookup(lang, language, country); 00173 if(!country.isEmpty()){ 00174 return QString("%1 (%2)").arg(language, country); 00175 }else{ 00176 return language; 00177 } 00178 } 00179 00180 void Checker::showContextMenu(QMenu* menu, const QPoint& pos, int wordPos) 00181 { 00182 QAction* insertPos = menu->actions().first(); 00183 if(m_speller && m_spellingEnabled){ 00184 QString word = getWord(wordPos); 00185 00186 if(!checkWord(word)) { 00187 QList<QString> suggestions = getSpellingSuggestions(word); 00188 if(!suggestions.isEmpty()){ 00189 for(int i = 0, n = qMin(10, suggestions.length()); i < n; ++i){ 00190 QAction* action = new QAction(suggestions[i], menu); 00191 action->setProperty("wordPos", wordPos); 00192 action->setProperty("suggestion", suggestions[i]); 00193 connect(action, SIGNAL(triggered()), this, SLOT(slotReplaceWord())); 00194 menu->insertAction(insertPos, action); 00195 } 00196 if(suggestions.length() > 10) { 00197 QMenu* moreMenu = new QMenu(); 00198 for(int i = 10, n = suggestions.length(); i < n; ++i){ 00199 QAction* action = new QAction(suggestions[i], moreMenu); 00200 action->setProperty("wordPos", wordPos); 00201 action->setProperty("suggestion", suggestions[i]); 00202 connect(action, SIGNAL(triggered()), this, SLOT(slotReplaceWord())); 00203 moreMenu->addAction(action); 00204 } 00205 QAction* action = new QAction(tr("More..."), menu); 00206 menu->insertAction(insertPos, action); 00207 action->setMenu(moreMenu); 00208 } 00209 menu->insertSeparator(insertPos); 00210 } 00211 00212 QAction* addAction = new QAction(tr("Add \"%1\" to dictionary").arg(word), menu); 00213 addAction->setData(wordPos); 00214 connect(addAction, SIGNAL(triggered()), this, SLOT(slotAddWord())); 00215 menu->insertAction(insertPos, addAction); 00216 00217 QAction* ignoreAction = new QAction(tr("Ignore \"%1\"").arg(word), menu); 00218 ignoreAction->setData(wordPos); 00219 connect(ignoreAction, SIGNAL(triggered()), this, SLOT(slotIgnoreWord())); 00220 menu->insertAction(insertPos, ignoreAction); 00221 menu->insertSeparator(insertPos); 00222 } 00223 } 00224 if(m_spellingCheckbox){ 00225 QAction* action = new QAction(tr("Check spelling"), menu); 00226 action->setCheckable(true); 00227 action->setChecked(m_spellingEnabled); 00228 connect(action, SIGNAL(toggled(bool)), this, SLOT(setSpellingEnabled(bool))); 00229 menu->insertAction(insertPos, action); 00230 } 00231 if(m_speller && m_spellingEnabled){ 00232 QMenu* languagesMenu = new QMenu(); 00233 QActionGroup* actionGroup = new QActionGroup(languagesMenu); 00234 foreach(const QString& lang, getLanguageList()){ 00235 QString text = getDecodeLanguageCodes() ? decodeLanguageCode(lang) : lang; 00236 QAction* action = new QAction(text, languagesMenu); 00237 action->setData(lang); 00238 action->setCheckable(true); 00239 action->setChecked(lang == getLanguage()); 00240 connect(action, SIGNAL(triggered(bool)), this, SLOT(slotSetLanguage(bool))); 00241 languagesMenu->addAction(action); 00242 actionGroup->addAction(action); 00243 } 00244 QAction* langsAction = new QAction(tr("Languages"), menu); 00245 langsAction->setMenu(languagesMenu); 00246 menu->insertAction(insertPos, langsAction); 00247 menu->insertSeparator(insertPos); 00248 } 00249 00250 menu->exec(pos); 00251 delete menu; 00252 } 00253 00254 void Checker::slotAddWord() 00255 { 00256 int wordPos = qobject_cast<QAction*>(QObject::sender())->property("wordPos").toInt(); 00257 int start, end; 00258 addWordToDictionary(getWord(wordPos, &start, &end)); 00259 checkSpelling(start, end); 00260 } 00261 00262 void Checker::slotIgnoreWord() 00263 { 00264 int wordPos = qobject_cast<QAction*>(QObject::sender())->property("wordPos").toInt(); 00265 int start, end; 00266 ignoreWord(getWord(wordPos, &start, &end)); 00267 checkSpelling(start, end); 00268 } 00269 00270 void Checker::slotReplaceWord() 00271 { 00272 QAction* action = qobject_cast<QAction*>(QObject::sender()); 00273 int wordPos = action->property("wordPos").toInt(); 00274 int start, end; 00275 getWord(wordPos, &start, &end); 00276 insertWord(start, end, action->property("suggestion").toString()); 00277 } 00278 00279 void Checker::slotSetLanguage(bool checked) 00280 { 00281 if(checked) { 00282 QAction* action = qobject_cast<QAction*>(QObject::sender()); 00283 QString lang = action->data().toString(); 00284 if(!setLanguage(lang)){ 00285 action->setChecked(false); 00286 lang = ""; 00287 } 00288 emit languageChanged(lang); 00289 } 00290 } 00291 00292 } // QtSpell
1.7.6.1