2 * Copyright (C) 2011 Igalia S.L.
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Lesser General Public
6 * License as published by the Free Software Foundation; either
7 * version 2 of the License, or (at your option) any later version.
9 * This library is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Lesser General Public License for more details.
14 * You should have received a copy of the GNU Lesser General Public
15 * License along with this library; if not, write to the Free Software
16 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
20 #include "webkitspellcheckerenchant.h"
22 #if ENABLE(SPELLCHECK)
25 #include "webkitspellchecker.h"
28 #include <wtf/text/CString.h>
31 * SECTION:webkitspellcheckerenchant
32 * @short_description: the default spell checking implementation for WebKitGTK+.
34 * #WebKitSpellCheckerEnchant is the default spell checking implementation for
35 * WebKitGTK+. It uses the Enchant dictionaries installed on the system to
38 static EnchantBroker* broker = 0;
40 struct _WebKitSpellCheckerEnchantPrivate {
44 static void webkit_spell_checker_enchant_spell_checker_interface_init(WebKitSpellCheckerInterface* interface);
46 G_DEFINE_TYPE_WITH_CODE(WebKitSpellCheckerEnchant, webkit_spell_checker_enchant, G_TYPE_OBJECT,
47 G_IMPLEMENT_INTERFACE(WEBKIT_TYPE_SPELL_CHECKER,
48 webkit_spell_checker_enchant_spell_checker_interface_init))
50 static void createEnchantBrokerIfNeeded()
53 broker = enchant_broker_init();
56 static void freeSpellCheckingLanguage(gpointer data, gpointer)
58 createEnchantBrokerIfNeeded();
60 enchant_broker_free_dict(broker, static_cast<EnchantDict*>(data));
63 static void webkit_spell_checker_enchant_finalize(GObject* object)
65 WebKitSpellCheckerEnchantPrivate* priv = WEBKIT_SPELL_CHECKER_ENCHANT(object)->priv;
67 g_slist_foreach(priv->enchantDicts, freeSpellCheckingLanguage, 0);
68 g_slist_free(priv->enchantDicts);
70 WEBKIT_SPELL_CHECKER_ENCHANT(object)->priv->~WebKitSpellCheckerEnchantPrivate();
73 static void webkit_spell_checker_enchant_class_init(WebKitSpellCheckerEnchantClass* klass)
75 GObjectClass* objectClass = G_OBJECT_CLASS(klass);
77 objectClass->finalize = webkit_spell_checker_enchant_finalize;
79 g_type_class_add_private(klass, sizeof(WebKitSpellCheckerEnchantPrivate));
82 static void webkit_spell_checker_enchant_init(WebKitSpellCheckerEnchant* checker)
84 WebKitSpellCheckerEnchantPrivate* priv = G_TYPE_INSTANCE_GET_PRIVATE(checker, WEBKIT_TYPE_SPELL_CHECKER_ENCHANT, WebKitSpellCheckerEnchantPrivate);
86 new (priv) WebKitSpellCheckerEnchantPrivate();
88 priv->enchantDicts = 0;
91 static void checkSpellingOfString(WebKitSpellChecker* checker, const char* string, int* misspellingLocation, int* misspellingLength)
93 WebKitSpellCheckerEnchantPrivate* priv = WEBKIT_SPELL_CHECKER_ENCHANT(checker)->priv;
95 GSList* dicts = priv->enchantDicts;
99 int length = g_utf8_strlen(string, -1);
101 PangoLanguage* language(pango_language_get_default());
102 GOwnPtr<PangoLogAttr> attrs(g_new(PangoLogAttr, length + 1));
104 // pango_get_log_attrs uses an aditional position at the end of the text.
105 pango_get_log_attrs(string, -1, -1, language, attrs.get(), length + 1);
107 for (int i = 0; i < length + 1; i++) {
108 // We go through each character until we find an is_word_start,
109 // then we get into an inner loop to find the is_word_end corresponding
111 if (attrs.get()[i].is_word_start) {
116 while (attrs.get()[end].is_word_end < 1)
119 wordLength = end - start;
120 // Set the iterator to be at the current word end, so we don't
121 // check characters twice.
124 gchar* cstart = g_utf8_offset_to_pointer(string, start);
125 gint bytes = static_cast<gint>(g_utf8_offset_to_pointer(string, end) - cstart);
126 GOwnPtr<gchar> word(g_new0(gchar, bytes + 1));
128 g_utf8_strncpy(word.get(), cstart, wordLength);
130 for (; dicts; dicts = dicts->next) {
131 EnchantDict* dict = static_cast<EnchantDict*>(dicts->data);
132 if (enchant_dict_check(dict, word.get(), wordLength)) {
133 *misspellingLocation = start;
134 *misspellingLength = wordLength;
136 // Stop checking, this word is ok in at least one dict.
137 *misspellingLocation = -1;
138 *misspellingLength = 0;
146 static char** getGuessesForWord(WebKitSpellChecker* checker, const char* word, const char* context)
148 WebKitSpellCheckerEnchantPrivate* priv = WEBKIT_SPELL_CHECKER_ENCHANT(checker)->priv;
150 GSList* dicts = priv->enchantDicts;
153 for (; dicts; dicts = dicts->next) {
154 size_t numberOfSuggestions;
157 EnchantDict* dict = static_cast<EnchantDict*>(dicts->data);
158 gchar** suggestions = enchant_dict_suggest(dict, word, -1, &numberOfSuggestions);
160 if (numberOfSuggestions > 0) {
161 if (numberOfSuggestions > 10)
162 numberOfSuggestions = 10;
164 guesses = static_cast<char**>(g_malloc0((numberOfSuggestions + 1) * sizeof(char*)));
165 for (i = 0; i < numberOfSuggestions && i < 10; i++)
166 guesses[i] = g_strdup(suggestions[i]);
170 enchant_dict_free_suggestions(dict, suggestions);
177 static void getAvailableDictionariesCallback(const char* const languageTag, const char* const, const char* const, const char* const, void* data)
179 Vector<CString>* dicts = static_cast<Vector<CString>*>(data);
181 dicts->append(languageTag);
184 static void updateSpellCheckingLanguages(WebKitSpellChecker* checker, const char* languages)
186 GSList* spellDictionaries = 0;
188 WebKitSpellCheckerEnchantPrivate* priv = WEBKIT_SPELL_CHECKER_ENCHANT(checker)->priv;
190 createEnchantBrokerIfNeeded();
193 char** langs = g_strsplit(languages, ",", -1);
194 for (int i = 0; langs[i]; i++) {
195 if (enchant_broker_dict_exists(broker, langs[i])) {
196 EnchantDict* dict = enchant_broker_request_dict(broker, langs[i]);
197 spellDictionaries = g_slist_append(spellDictionaries, dict);
202 const char* language = pango_language_to_string(gtk_get_default_language());
203 if (enchant_broker_dict_exists(broker, language)) {
204 EnchantDict* dict = enchant_broker_request_dict(broker, language);
205 spellDictionaries = g_slist_append(spellDictionaries, dict);
207 // No dictionaries selected, we get one from the list.
208 Vector<CString> allDictionaries;
209 enchant_broker_list_dicts(broker, getAvailableDictionariesCallback, &allDictionaries);
210 if (!allDictionaries.isEmpty()) {
211 EnchantDict* dict = enchant_broker_request_dict(broker, allDictionaries[0].data());
212 spellDictionaries = g_slist_append(spellDictionaries, dict);
216 g_slist_foreach(priv->enchantDicts, freeSpellCheckingLanguage, 0);
217 g_slist_free(priv->enchantDicts);
218 priv->enchantDicts = spellDictionaries;
221 static char* getAutocorrectSuggestionsForMisspelledWord(WebKitSpellChecker* checker, const char* word)
226 static void learnWord(WebKitSpellChecker* checker, const char* word)
228 WebKitSpellCheckerEnchantPrivate* priv = WEBKIT_SPELL_CHECKER_ENCHANT(checker)->priv;
229 GSList* dicts = priv->enchantDicts;
231 for (; dicts; dicts = dicts->next) {
232 EnchantDict* dict = static_cast<EnchantDict*>(dicts->data);
234 enchant_dict_add_to_personal(dict, word, -1);
238 static void ignoreWord(WebKitSpellChecker* checker, const char* word)
240 WebKitSpellCheckerEnchantPrivate* priv = WEBKIT_SPELL_CHECKER_ENCHANT(checker)->priv;
241 GSList* dicts = priv->enchantDicts;
243 for (; dicts; dicts = dicts->next) {
244 EnchantDict* dict = static_cast<EnchantDict*>(dicts->data);
246 enchant_dict_add_to_session(dict, word, -1);
250 static void webkit_spell_checker_enchant_spell_checker_interface_init(WebKitSpellCheckerInterface* interface)
252 interface->check_spelling_of_string = checkSpellingOfString;
253 interface->get_guesses_for_word = getGuessesForWord;
254 interface->update_spell_checking_languages = updateSpellCheckingLanguages;
255 interface->get_autocorrect_suggestions_for_misspelled_word = getAutocorrectSuggestionsForMisspelledWord;
256 interface->learn_word = learnWord;
257 interface->ignore_word = ignoreWord;
260 #endif /* ENABLE(SPELLCHECK) */