root / plugins / spellchecker / spellchecker.cpp @ 96aa415e
History | View | Annotate | Download (13.4 kB)
| 1 | /*
|
|---|---|
| 2 | * %kadu copyright begin% |
| 3 | * Copyright 2011 Tomasz Rostanski (rozteck@interia.pl) |
| 4 | * Copyright 2009, 2010, 2011 Piotr Galiszewski (piotr.galiszewski@kadu.im) |
| 5 | * Copyright 2009 Wojciech Treter (juzefwt@gmail.com) |
| 6 | * Copyright 2009 Tomasz Rostański (rozteck@interia.pl) |
| 7 | * Copyright 2011 Sławomir Stępień (s.stepien@interia.pl) |
| 8 | * Copyright 2009 Michał Podsiadlik (michal@kadu.net) |
| 9 | * Copyright 2009 Bartłomiej Zimoń (uzi18@o2.pl) |
| 10 | * Copyright 2008, 2009, 2010, 2011 Rafał Malinowski (rafal.przemyslaw.malinowski@gmail.com) |
| 11 | * Copyright 2010, 2011 Bartosz Brachaczek (b.brachaczek@gmail.com) |
| 12 | * %kadu copyright end% |
| 13 | * |
| 14 | * This program is free software; you can redistribute it and/or |
| 15 | * modify it under the terms of the GNU General Public License as |
| 16 | * published by the Free Software Foundation; either version 2 of |
| 17 | * the License, or (at your option) any later version. |
| 18 | * |
| 19 | * This program is distributed in the hope that it will be useful, |
| 20 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 21 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| 22 | * GNU General Public License for more details. |
| 23 | * |
| 24 | * You should have received a copy of the GNU General Public License |
| 25 | * along with this program. If not, see <http://www.gnu.org/licenses/>. |
| 26 | */ |
| 27 | |
| 28 | #if defined(HAVE_ASPELL)
|
| 29 | #define ASPELL_STATIC
|
| 30 | #include <aspell.h> |
| 31 | #elif defined(HAVE_ENCHANT)
|
| 32 | #include <enchant++.h> |
| 33 | #endif
|
| 34 | |
| 35 | #include <QtCore/QTextCodec> |
| 36 | #include <QtGui/QApplication> |
| 37 | #include <QtGui/QGridLayout> |
| 38 | #include <QtGui/QLabel> |
| 39 | #include <QtGui/QListWidget> |
| 40 | #include <QtGui/QPushButton> |
| 41 | |
| 42 | #if defined(Q_WS_MAC)
|
| 43 | #include "macspellchecker.h" |
| 44 | #endif
|
| 45 | |
| 46 | #include "gui/widgets/chat-edit-box.h" |
| 47 | #include "gui/widgets/chat-widget-manager.h" |
| 48 | #include "gui/widgets/chat-widget.h" |
| 49 | #include "gui/widgets/configuration/config-group-box.h" |
| 50 | #include "gui/widgets/configuration/configuration-widget.h" |
| 51 | #include "gui/widgets/custom-input.h" |
| 52 | #include "gui/windows/message-dialog.h" |
| 53 | #include "misc/misc.h" |
| 54 | |
| 55 | #include "highlighter.h" |
| 56 | #include "suggester.h" |
| 57 | |
| 58 | #include "configuration/spellchecker-configuration.h" |
| 59 | #include "spellchecker.h" |
| 60 | |
| 61 | #if defined(HAVE_ENCHANT)
|
| 62 | typedef std::pair<SpellChecker::Checkers *, QStringList *> DescWrapper;
|
| 63 | |
| 64 | static void enchantDictDescribe(const char * const langTag, const char * const providerName, |
| 65 | const char * const providerDesc, const char * const providerFile, void *userData) |
| 66 | {
|
| 67 | Q_UNUSED(providerName) |
| 68 | Q_UNUSED(providerDesc) |
| 69 | Q_UNUSED(providerFile) |
| 70 | |
| 71 | DescWrapper *pWrapper = static_cast<DescWrapper *>(userData);
|
| 72 | const SpellChecker::Checkers &checkers = *pWrapper->first;
|
| 73 | QStringList &result = *pWrapper->second; |
| 74 | if (!checkers.contains(langTag))
|
| 75 | result.append(langTag); |
| 76 | } |
| 77 | #endif
|
| 78 | |
| 79 | SpellChecker::SpellChecker(QObject *parent) : |
| 80 | ConfigurationUiHandler(parent) |
| 81 | {
|
| 82 | connect(ChatWidgetManager::instance(), SIGNAL(chatWidgetCreated(ChatWidget *)), |
| 83 | this, SLOT(chatCreated(ChatWidget *)));
|
| 84 | |
| 85 | #if defined(HAVE_ASPELL)
|
| 86 | // prepare configuration of spellchecker
|
| 87 | SpellConfig = new_aspell_config(); |
| 88 | aspell_config_replace(SpellConfig, "encoding", "utf-8"); |
| 89 | aspell_config_replace(SpellConfig, "sug-mode", "ultra"); |
| 90 | |
| 91 | #if defined(Q_OS_WIN32)
|
| 92 | aspell_config_replace(SpellConfig, "dict-dir", qPrintable(KaduPaths::instance()->dataPath() + QLatin1String("aspell"))); |
| 93 | aspell_config_replace(SpellConfig, "data-dir", qPrintable(KaduPaths::instance()->dataPath() + QLatin1String("aspell"))); |
| 94 | aspell_config_replace(SpellConfig, "prefix", qPrintable(KaduPaths::instance()->profilePath() + QLatin1String("dicts"))); |
| 95 | #endif // Q_OS_WIN32 |
| 96 | #endif // HAVE_ASPELL |
| 97 | } |
| 98 | |
| 99 | SpellChecker::~SpellChecker() |
| 100 | {
|
| 101 | disconnect(ChatWidgetManager::instance(), SIGNAL(chatWidgetCreated(ChatWidget *)), |
| 102 | this, SLOT(chatCreated(ChatWidget *)));
|
| 103 | |
| 104 | Highlighter::removeAll(); |
| 105 | |
| 106 | #if defined(HAVE_ASPELL)
|
| 107 | delete_aspell_config(SpellConfig); |
| 108 | |
| 109 | foreach (AspellSpeller *speller, MyCheckers) |
| 110 | delete_aspell_speller(speller); |
| 111 | #else
|
| 112 | qDeleteAll(MyCheckers); |
| 113 | #endif
|
| 114 | |
| 115 | } |
| 116 | |
| 117 | QStringList SpellChecker::notCheckedLanguages() |
| 118 | {
|
| 119 | QStringList result; |
| 120 | |
| 121 | #if defined(HAVE_ASPELL)
|
| 122 | AspellDictInfoList *dlist; |
| 123 | AspellDictInfoEnumeration *dels; |
| 124 | const AspellDictInfo *entry;
|
| 125 | |
| 126 | /* the returned pointer should _not_ need to be deleted */
|
| 127 | dlist = get_aspell_dict_info_list(SpellConfig); |
| 128 | |
| 129 | dels = aspell_dict_info_list_elements(dlist); |
| 130 | while ((entry = aspell_dict_info_enumeration_next(dels)))
|
| 131 | if (!MyCheckers.contains(entry->name))
|
| 132 | result.push_back(entry->name); |
| 133 | delete_aspell_dict_info_enumeration(dels); |
| 134 | #elif defined(HAVE_ENCHANT)
|
| 135 | DescWrapper aWrapper(&MyCheckers, &result); |
| 136 | enchant::Broker::instance()->list_dicts(enchantDictDescribe, &aWrapper); |
| 137 | #endif
|
| 138 | |
| 139 | return result;
|
| 140 | } |
| 141 | |
| 142 | QStringList SpellChecker::checkedLanguages() |
| 143 | {
|
| 144 | QStringList result; |
| 145 | for (Checkers::const_iterator it = MyCheckers.constBegin(); it != MyCheckers.constEnd(); ++it)
|
| 146 | result.append(it.key()); |
| 147 | return result;
|
| 148 | } |
| 149 | |
| 150 | bool SpellChecker::addCheckedLang(const QString &name) |
| 151 | {
|
| 152 | if (MyCheckers.contains(name))
|
| 153 | return true; |
| 154 | |
| 155 | bool ok = true; |
| 156 | const char *errorMsg = 0; |
| 157 | |
| 158 | #if defined(HAVE_ASPELL)
|
| 159 | aspell_config_replace(SpellConfig, "lang", name.toAscii().constData());
|
| 160 | |
| 161 | // create spell checker using prepared configuration
|
| 162 | AspellCanHaveError *possibleErr = new_aspell_speller(SpellConfig); |
| 163 | if (aspell_error_number(possibleErr) == 0) |
| 164 | MyCheckers.insert(name, to_aspell_speller(possibleErr)); |
| 165 | else
|
| 166 | {
|
| 167 | errorMsg = aspell_error_message(possibleErr); |
| 168 | ok = false;
|
| 169 | } |
| 170 | #elif defined(HAVE_ENCHANT)
|
| 171 | try
|
| 172 | {
|
| 173 | MyCheckers.insert(name, enchant::Broker::instance()->request_dict(name.toStdString())); |
| 174 | } |
| 175 | catch (enchant::Exception &e)
|
| 176 | {
|
| 177 | errorMsg = e.what(); |
| 178 | ok = false;
|
| 179 | } |
| 180 | #elif defined(Q_WS_MAC)
|
| 181 | MyCheckers.insert(name, new MacSpellChecker());
|
| 182 | #endif
|
| 183 | |
| 184 | if (!ok)
|
| 185 | {
|
| 186 | MessageDialog::show(KaduIcon("dialog-error"), tr("Kadu"), tr("Could not find dictionary for %1 language.").arg(name) |
| 187 | + (qstrlen(errorMsg) > 0 ? QString(" %1: %2").arg(tr("Details"), errorMsg) : QString())); |
| 188 | |
| 189 | // remove this checker from configuration
|
| 190 | configurationWindowApplied(); |
| 191 | return false; |
| 192 | } |
| 193 | |
| 194 | if (MyCheckers.size() == 1) |
| 195 | foreach (ChatWidget *chat, ChatWidgetManager::instance()->chats()) |
| 196 | chatCreated(chat); |
| 197 | |
| 198 | return true; |
| 199 | } |
| 200 | |
| 201 | void SpellChecker::removeCheckedLang(const QString &name) |
| 202 | {
|
| 203 | Checkers::iterator checker = MyCheckers.find(name); |
| 204 | if (checker != MyCheckers.end())
|
| 205 | {
|
| 206 | #if defined(HAVE_ASPELL)
|
| 207 | delete_aspell_speller(checker.value()); |
| 208 | #else
|
| 209 | delete checker.value();
|
| 210 | #endif
|
| 211 | MyCheckers.erase(checker); |
| 212 | } |
| 213 | } |
| 214 | |
| 215 | void SpellChecker::buildCheckers()
|
| 216 | {
|
| 217 | #if defined(HAVE_ASPELL)
|
| 218 | foreach (AspellSpeller *speller, MyCheckers) |
| 219 | delete_aspell_speller(speller); |
| 220 | #else
|
| 221 | qDeleteAll(MyCheckers); |
| 222 | #endif
|
| 223 | MyCheckers.clear(); |
| 224 | |
| 225 | #if defined(HAVE_ASPELL)
|
| 226 | if (SpellcheckerConfiguration::instance()->accents())
|
| 227 | aspell_config_replace(SpellConfig, "ignore-accents", "true"); |
| 228 | else
|
| 229 | aspell_config_replace(SpellConfig, "ignore-accents", "false"); |
| 230 | |
| 231 | if (SpellcheckerConfiguration::instance()->casesens())
|
| 232 | aspell_config_replace(SpellConfig, "ignore-case", "true"); |
| 233 | else
|
| 234 | aspell_config_replace(SpellConfig, "ignore-case", "false"); |
| 235 | #endif
|
| 236 | |
| 237 | foreach (const QString &checked, SpellcheckerConfiguration::instance()->checked())
|
| 238 | addCheckedLang(checked); |
| 239 | } |
| 240 | |
| 241 | void SpellChecker::buildMarkTag()
|
| 242 | {
|
| 243 | QTextCharFormat format; |
| 244 | |
| 245 | if (SpellcheckerConfiguration::instance()->bold())
|
| 246 | format.setFontWeight(600);
|
| 247 | if (SpellcheckerConfiguration::instance()->italic())
|
| 248 | format.setFontItalic(true);
|
| 249 | if (SpellcheckerConfiguration::instance()->underline())
|
| 250 | {
|
| 251 | format.setFontUnderline(true);
|
| 252 | format.setUnderlineColor(SpellcheckerConfiguration::instance()->color()); |
| 253 | format.setUnderlineStyle(QTextCharFormat::SpellCheckUnderline); |
| 254 | } |
| 255 | format.setForeground(QBrush(SpellcheckerConfiguration::instance()->color())); |
| 256 | |
| 257 | Highlighter::setHighlightFormat(format); |
| 258 | Highlighter::rehighlightAll(); |
| 259 | } |
| 260 | |
| 261 | void SpellChecker::chatCreated(ChatWidget *chat)
|
| 262 | {
|
| 263 | if (!MyCheckers.isEmpty())
|
| 264 | {
|
| 265 | chat->getChatEditBox()->inputBox()->installEventFilter(Suggester::instance()); |
| 266 | new Highlighter(chat->edit()->document());
|
| 267 | } |
| 268 | } |
| 269 | |
| 270 | void SpellChecker::configForward()
|
| 271 | {
|
| 272 | if (!AvailableLanguagesList->selectedItems().isEmpty())
|
| 273 | configForward2(AvailableLanguagesList->selectedItems().at(0));
|
| 274 | } |
| 275 | |
| 276 | void SpellChecker::configBackward()
|
| 277 | {
|
| 278 | if (!CheckedLanguagesList->selectedItems().isEmpty())
|
| 279 | configBackward2(CheckedLanguagesList->selectedItems().at(0));
|
| 280 | } |
| 281 | |
| 282 | void SpellChecker::configForward2(QListWidgetItem *item)
|
| 283 | {
|
| 284 | QString langName = item->text(); |
| 285 | if (addCheckedLang(langName))
|
| 286 | {
|
| 287 | CheckedLanguagesList->addItem(langName); |
| 288 | delete AvailableLanguagesList->takeItem(AvailableLanguagesList->row(item));
|
| 289 | } |
| 290 | } |
| 291 | |
| 292 | void SpellChecker::configBackward2(QListWidgetItem *item)
|
| 293 | {
|
| 294 | QString langName = item->text(); |
| 295 | AvailableLanguagesList->addItem(langName); |
| 296 | delete CheckedLanguagesList->takeItem(CheckedLanguagesList->row(item));
|
| 297 | removeCheckedLang(langName); |
| 298 | } |
| 299 | |
| 300 | void SpellChecker::mainConfigurationWindowCreated(MainConfigurationWindow *mainConfigurationWindow)
|
| 301 | {
|
| 302 | connect(mainConfigurationWindow, SIGNAL(configurationWindowApplied()), |
| 303 | this, SLOT(configurationWindowApplied()));
|
| 304 | |
| 305 | #if !defined(HAVE_ASPELL)
|
| 306 | mainConfigurationWindow->widget()->widgetById("spellchecker/ignoreCase")->hide();
|
| 307 | #endif
|
| 308 | |
| 309 | ConfigGroupBox *optionsGroupBox = mainConfigurationWindow->widget()->configGroupBox("Chat", "SpellChecker", qApp->translate("@default", "Spell Checker Options")); |
| 310 | |
| 311 | QWidget *options = new QWidget(optionsGroupBox->widget());
|
| 312 | QGridLayout *optionsLayout = new QGridLayout(options);
|
| 313 | |
| 314 | AvailableLanguagesList = new QListWidget(options);
|
| 315 | QPushButton *moveToChecked = new QPushButton(tr("Move to 'Checked'"), options); |
| 316 | |
| 317 | optionsLayout->addWidget(new QLabel(tr("Available languages"), options), 0, 0); |
| 318 | optionsLayout->addWidget(AvailableLanguagesList, 1, 0); |
| 319 | optionsLayout->addWidget(moveToChecked, 2, 0); |
| 320 | |
| 321 | CheckedLanguagesList = new QListWidget(options);
|
| 322 | QPushButton *moveToAvailable = new QPushButton(tr("Move to 'Available languages'"), options); |
| 323 | |
| 324 | optionsLayout->addWidget(new QLabel(tr("Checked"), options), 0, 1); |
| 325 | optionsLayout->addWidget(CheckedLanguagesList, 1, 1); |
| 326 | optionsLayout->addWidget(moveToAvailable, 2, 1); |
| 327 | |
| 328 | connect(moveToChecked, SIGNAL(clicked()), this, SLOT(configForward()));
|
| 329 | connect(moveToAvailable, SIGNAL(clicked()), this, SLOT(configBackward()));
|
| 330 | connect(CheckedLanguagesList, SIGNAL(itemDoubleClicked(QListWidgetItem *)), |
| 331 | this, SLOT(configBackward2(QListWidgetItem *)));
|
| 332 | connect(AvailableLanguagesList, SIGNAL(itemDoubleClicked(QListWidgetItem *)), |
| 333 | this, SLOT(configForward2(QListWidgetItem*)));
|
| 334 | |
| 335 | optionsGroupBox->addWidgets(0, options);
|
| 336 | |
| 337 | AvailableLanguagesList->setSelectionMode(QAbstractItemView::SingleSelection); |
| 338 | CheckedLanguagesList->setSelectionMode(QAbstractItemView::SingleSelection); |
| 339 | AvailableLanguagesList->addItems(notCheckedLanguages()); |
| 340 | CheckedLanguagesList->addItems(checkedLanguages()); |
| 341 | } |
| 342 | |
| 343 | void SpellChecker::configurationWindowApplied()
|
| 344 | {
|
| 345 | SpellcheckerConfiguration::instance()->setChecked(checkedLanguages()); |
| 346 | } |
| 347 | |
| 348 | bool SpellChecker::checkWord(const QString &word) |
| 349 | {
|
| 350 | bool isWordValid = false; |
| 351 | |
| 352 | if (MyCheckers.isEmpty())
|
| 353 | return true; |
| 354 | |
| 355 | if (!word.contains(QRegExp("\\D"))) |
| 356 | isWordValid = true;
|
| 357 | else
|
| 358 | for (Checkers::const_iterator it = MyCheckers.constBegin(); it != MyCheckers.constEnd(); ++it)
|
| 359 | #if defined(HAVE_ASPELL)
|
| 360 | if (aspell_speller_check(it.value(), word.toUtf8().constData(), -1)) |
| 361 | #elif defined(HAVE_ENCHANT)
|
| 362 | if (it.value()->check(word.toUtf8().constData()))
|
| 363 | #elif defined(Q_WS_MAC)
|
| 364 | if (it.value()->isCorrect(word.toUtf8().constData()))
|
| 365 | #endif
|
| 366 | {
|
| 367 | isWordValid = true;
|
| 368 | break;
|
| 369 | } |
| 370 | return isWordValid;
|
| 371 | } |
| 372 | |
| 373 | QStringList SpellChecker::buildSuggestList(const QString &word)
|
| 374 | {
|
| 375 | QStringList suggestWordList; |
| 376 | |
| 377 | #if defined(HAVE_ASPELL)
|
| 378 | QTextCodec *codec = QTextCodec::codecForName("utf-8");
|
| 379 | #endif
|
| 380 | |
| 381 | int suggesterWordCount = SpellcheckerConfiguration::instance()->suggesterWordCount();
|
| 382 | if (MyCheckers.size() > suggesterWordCount)
|
| 383 | suggesterWordCount = 1;
|
| 384 | else
|
| 385 | suggesterWordCount /= MyCheckers.size(); |
| 386 | |
| 387 | int wordsForLanguage = 0; |
| 388 | for (Checkers::const_iterator it = MyCheckers.constBegin(); it != MyCheckers.constEnd(); ++it)
|
| 389 | {
|
| 390 | wordsForLanguage = suggesterWordCount; |
| 391 | #if defined(HAVE_ASPELL)
|
| 392 | const AspellWordList *aspellTmpList = aspell_speller_suggest(it.value(), word.toUtf8().constData(), -1); |
| 393 | |
| 394 | if (!aspell_word_list_empty(aspellTmpList))
|
| 395 | {
|
| 396 | struct AspellStringEnumeration *aspellStringEnum = aspell_word_list_elements(aspellTmpList);
|
| 397 | |
| 398 | while((!aspell_string_enumeration_at_end(aspellStringEnum)) && wordsForLanguage)
|
| 399 | {
|
| 400 | if (MyCheckers.size() > 1) |
| 401 | suggestWordList.append(codec->toUnicode(aspell_string_enumeration_next(aspellStringEnum)) + " (" + it.key() + ")"); |
| 402 | else
|
| 403 | suggestWordList.append(codec->toUnicode(aspell_string_enumeration_next(aspellStringEnum))); |
| 404 | |
| 405 | --wordsForLanguage; |
| 406 | } |
| 407 | |
| 408 | delete_aspell_string_enumeration(aspellStringEnum); |
| 409 | } |
| 410 | #elif defined(HAVE_ENCHANT)
|
| 411 | size_t numberOfSuggs; |
| 412 | EnchantBroker *broker = enchant_broker_init(); |
| 413 | EnchantDict *dict = enchant_broker_request_dict(broker, it.key().toUtf8().constData()); |
| 414 | char **suggs = enchant_dict_suggest(dict, word.toUtf8().constData(), word.toUtf8().size(), &numberOfSuggs);
|
| 415 | |
| 416 | if ((suggs) && (numberOfSuggs))
|
| 417 | {
|
| 418 | for (size_t i = 0; i < numberOfSuggs; ++i) |
| 419 | {
|
| 420 | if (!wordsForLanguage)
|
| 421 | break;
|
| 422 | |
| 423 | if (MyCheckers.size() > 1) |
| 424 | suggestWordList.append(QString::fromUtf8(suggs[i]) + " (" + it.key() + ")"); |
| 425 | else
|
| 426 | suggestWordList.append(QString::fromUtf8(suggs[i])); |
| 427 | |
| 428 | --wordsForLanguage; |
| 429 | } |
| 430 | } |
| 431 | |
| 432 | enchant_dict_free_string_list(dict, suggs); |
| 433 | enchant_broker_free_dict(broker, dict); |
| 434 | enchant_broker_free(broker); |
| 435 | #elif defined(Q_WS_MAC)
|
| 436 | suggestWordList.append(it.value()->suggestions(word)); |
| 437 | #endif
|
| 438 | } |
| 439 | |
| 440 | return suggestWordList;
|
| 441 | } |