Statistics
| Branch: | Tag: | Revision:

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
}