1/*
2 *  Copyright (C) 2007 Alp Toker <alp@atoker.com>
3 *  Copyright (C) 2008 Nuanti Ltd.
4 *  Copyright (C) 2009 Diego Escalante Urrelo <diegoe@gnome.org>
5 *  Copyright (C) 2006, 2007 Apple Inc.  All rights reserved.
6 *  Copyright (C) 2009, 2010 Igalia S.L.
7 *  Copyright (C) 2010, Martin Robinson <mrobinson@webkit.org>
8 *
9 *  This library is free software; you can redistribute it and/or
10 *  modify it under the terms of the GNU Lesser General Public
11 *  License as published by the Free Software Foundation; either
12 *  version 2 of the License, or (at your option) any later version.
13 *
14 *  This library is distributed in the hope that it will be useful,
15 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
16 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
17 *  Lesser General Public License for more details.
18 *
19 *  You should have received a copy of the GNU Lesser General Public
20 *  License along with this library; if not, write to the Free Software
21 *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
22 */
23
24#include "config.h"
25#include "TextCheckerClientEnchant.h"
26
27#include "NotImplemented.h"
28#include "webkitwebsettingsprivate.h"
29#include "webkitwebviewprivate.h"
30#include <enchant.h>
31#include <glib.h>
32#include <wtf/text/CString.h>
33
34using namespace WebCore;
35
36namespace WebKit {
37
38EnchantBroker* TextCheckerClientEnchant::broker = 0;
39
40TextCheckerClientEnchant::TextCheckerClientEnchant(WebKitWebView* webView)
41    : m_webView(webView)
42    , m_enchantDicts(0)
43{
44}
45
46TextCheckerClientEnchant::~TextCheckerClientEnchant()
47{
48    g_slist_foreach(m_enchantDicts, freeSpellCheckingLanguage, 0);
49    g_slist_free(m_enchantDicts);
50}
51
52void TextCheckerClientEnchant::ignoreWordInSpellDocument(const String& text)
53{
54    GSList* dicts = m_enchantDicts;
55
56    for (; dicts; dicts = dicts->next) {
57        EnchantDict* dict = static_cast<EnchantDict*>(dicts->data);
58
59        enchant_dict_add_to_session(dict, text.utf8().data(), -1);
60    }
61}
62
63void TextCheckerClientEnchant::learnWord(const String& text)
64{
65    GSList* dicts = m_enchantDicts;
66
67    for (; dicts; dicts = dicts->next) {
68        EnchantDict* dict = static_cast<EnchantDict*>(dicts->data);
69
70        enchant_dict_add_to_personal(dict, text.utf8().data(), -1);
71    }
72}
73
74void TextCheckerClientEnchant::checkSpellingOfString(const UChar* text, int length, int* misspellingLocation, int* misspellingLength)
75{
76    GSList* dicts = m_enchantDicts;
77    if (!dicts)
78        return;
79
80    GOwnPtr<gchar> utf8Text(g_utf16_to_utf8(const_cast<gunichar2*>(text), length, 0, 0, 0));
81    int utf8Length = g_utf8_strlen(utf8Text.get(), -1);
82
83    PangoLanguage* language(pango_language_get_default());
84    GOwnPtr<PangoLogAttr> attrs(g_new(PangoLogAttr, utf8Length + 1));
85
86    // pango_get_log_attrs uses an aditional position at the end of the text.
87    pango_get_log_attrs(utf8Text.get(), -1, -1, language, attrs.get(), utf8Length + 1);
88
89    for (int i = 0; i < length + 1; i++) {
90        // We go through each character until we find an is_word_start,
91        // then we get into an inner loop to find the is_word_end corresponding
92        // to it.
93        if (attrs.get()[i].is_word_start) {
94            int start = i;
95            int end = i;
96            int wordLength;
97
98            while (attrs.get()[end].is_word_end < 1)
99                end++;
100
101            wordLength = end - start;
102            // Set the iterator to be at the current word end, so we don't
103            // check characters twice.
104            i = end;
105
106            gchar* cstart = g_utf8_offset_to_pointer(utf8Text.get(), start);
107            gint bytes = static_cast<gint>(g_utf8_offset_to_pointer(utf8Text.get(), end) - cstart);
108            GOwnPtr<gchar> word(g_new0(gchar, bytes + 1));
109
110            g_utf8_strncpy(word.get(), cstart, wordLength);
111
112            for (; dicts; dicts = dicts->next) {
113                EnchantDict* dict = static_cast<EnchantDict*>(dicts->data);
114                if (enchant_dict_check(dict, word.get(), wordLength)) {
115                    *misspellingLocation = start;
116                    *misspellingLength = wordLength;
117                } else {
118                    // Stop checking, this word is ok in at least one dict.
119                    *misspellingLocation = -1;
120                    *misspellingLength = 0;
121                    break;
122                }
123            }
124        }
125    }
126}
127
128String TextCheckerClientEnchant::getAutoCorrectSuggestionForMisspelledWord(const String& inputWord)
129{
130    // This method can be implemented using customized algorithms for the particular browser.
131    // Currently, it computes an empty string.
132    return String();
133}
134
135void TextCheckerClientEnchant::checkGrammarOfString(const UChar*, int, Vector<GrammarDetail>&, int*, int*)
136{
137    notImplemented();
138}
139
140void TextCheckerClientEnchant::getGuessesForWord(const String& word, const String& context, WTF::Vector<String>& guesses)
141{
142    GSList* dicts = m_enchantDicts;
143    guesses.clear();
144
145    for (; dicts; dicts = dicts->next) {
146        size_t numberOfSuggestions;
147        size_t i;
148
149        EnchantDict* dict = static_cast<EnchantDict*>(dicts->data);
150        gchar** suggestions = enchant_dict_suggest(dict, word.utf8().data(), -1, &numberOfSuggestions);
151
152        for (i = 0; i < numberOfSuggestions && i < 10; i++)
153            guesses.append(String::fromUTF8(suggestions[i]));
154
155        if (numberOfSuggestions > 0)
156            enchant_dict_free_suggestions(dict, suggestions);
157    }
158}
159
160static void getAvailableDictionariesCallback(const char* const languageTag, const char* const, const char* const, const char* const, void* data)
161{
162    Vector<CString>* dicts = static_cast<Vector<CString>*>(data);
163
164    dicts->append(languageTag);
165}
166
167void TextCheckerClientEnchant::updateSpellCheckingLanguage(const char* spellCheckingLanguages)
168{
169    EnchantDict* dict;
170    GSList* spellDictionaries = 0;
171
172    if (!broker)
173        broker = enchant_broker_init();
174
175    if (spellCheckingLanguages) {
176        char** langs = g_strsplit(spellCheckingLanguages, ",", -1);
177        for (int i = 0; langs[i]; i++) {
178            if (enchant_broker_dict_exists(broker, langs[i])) {
179                dict = enchant_broker_request_dict(broker, langs[i]);
180                spellDictionaries = g_slist_append(spellDictionaries, dict);
181            }
182        }
183        g_strfreev(langs);
184    } else {
185        const char* language = pango_language_to_string(gtk_get_default_language());
186        if (enchant_broker_dict_exists(broker, language)) {
187            dict = enchant_broker_request_dict(broker, language);
188            spellDictionaries = g_slist_append(spellDictionaries, dict);
189        } else {
190            // No dictionaries selected, we get one from the list
191            Vector<CString> allDictionaries;
192            enchant_broker_list_dicts(broker, getAvailableDictionariesCallback, &allDictionaries);
193            if (!allDictionaries.isEmpty()) {
194                dict = enchant_broker_request_dict(broker, allDictionaries[0].data());
195                spellDictionaries = g_slist_append(spellDictionaries, dict);
196            }
197        }
198    }
199    g_slist_foreach(m_enchantDicts, freeSpellCheckingLanguage, 0);
200    g_slist_free(m_enchantDicts);
201    m_enchantDicts = spellDictionaries;
202}
203
204void TextCheckerClientEnchant::freeSpellCheckingLanguage(gpointer data, gpointer)
205{
206    if (!broker)
207        broker = enchant_broker_init();
208
209    EnchantDict* dict = static_cast<EnchantDict*>(data);
210    enchant_broker_free_dict(broker, dict);
211}
212
213}
214