QtSpell  0.8.2
Spell checking for Qt text widgets
/usr/src/RPM/BUILD/qtspell-0.8.2/src/Checker.cpp
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