]> git.0d.be Git - empathy.git/blob - libempathy-gtk/empathy-spell.c
use the 48x48 version of the local-xmpp icon
[empathy.git] / libempathy-gtk / empathy-spell.c
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /*
3  * Copyright (C) 2004-2007 Imendio AB
4  *
5  * This program is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU General Public License as
7  * published by the Free Software Foundation; either version 2 of the
8  * License, or (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  * General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public
16  * License along with this program; if not, write to the
17  * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
18  * Boston, MA  02110-1301  USA
19  *
20  * Authors: Martyn Russell <martyn@imendio.com>
21  *          Richard Hult <richard@imendio.com>
22  */
23
24 #include "config.h"
25
26 #include <string.h>
27 #include <stdlib.h>
28
29 #include <glib/gi18n-lib.h>
30
31 #ifdef HAVE_ENCHANT
32 #include <enchant.h>
33 #endif
34
35 #include "empathy-spell.h"
36
37 #define DEBUG_FLAG EMPATHY_DEBUG_OTHER
38 #include <libempathy/empathy-debug.h>
39 #include <libempathy/empathy-gsettings.h>
40
41 #ifdef HAVE_ENCHANT
42
43 typedef struct {
44         EnchantBroker *config;
45         EnchantDict   *speller;
46 } SpellLanguage;
47
48 #define ISO_CODES_DATADIR    ISO_CODES_PREFIX "/share/xml/iso-codes"
49 #define ISO_CODES_LOCALESDIR ISO_CODES_PREFIX "/share/locale"
50
51 /* Language code (gchar *) -> language name (gchar *) */
52 static GHashTable  *iso_code_names = NULL;
53 /* Contains only _enabled_ languages
54  * Language code (gchar *) -> language (SpellLanguage *) */
55 static GHashTable  *languages = NULL;
56
57 static void
58 spell_iso_codes_parse_start_tag (GMarkupParseContext  *ctx,
59                                  const gchar          *element_name,
60                                  const gchar         **attr_names,
61                                  const gchar         **attr_values,
62                                  gpointer              data,
63                                  GError              **error)
64 {
65         const gchar *ccode_longB, *ccode_longT, *ccode;
66         const gchar *lang_name;
67
68         if (!g_str_equal (element_name, "iso_639_entry") ||
69             attr_names == NULL || attr_values == NULL) {
70                 return;
71         }
72
73         ccode = NULL;
74         ccode_longB = NULL;
75         ccode_longT = NULL;
76         lang_name = NULL;
77
78         while (*attr_names && *attr_values) {
79                 if (g_str_equal (*attr_names, "iso_639_1_code")) {
80                         if (**attr_values) {
81                                 ccode = *attr_values;
82                         }
83                 }
84                 else if (g_str_equal (*attr_names, "iso_639_2B_code")) {
85                         if (**attr_values) {
86                                 ccode_longB = *attr_values;
87                         }
88                 }
89                 else if (g_str_equal (*attr_names, "iso_639_2T_code")) {
90                         if (**attr_values) {
91                                 ccode_longT = *attr_values;
92                         }
93                 }
94                 else if (g_str_equal (*attr_names, "name")) {
95                         lang_name = *attr_values;
96                 }
97
98                 attr_names++;
99                 attr_values++;
100         }
101
102         if (!lang_name) {
103                 return;
104         }
105
106         if (ccode) {
107                 g_hash_table_insert (iso_code_names,
108                                      g_strdup (ccode),
109                                      g_strdup (lang_name));
110         }
111
112         if (ccode_longB) {
113                 g_hash_table_insert (iso_code_names,
114                                      g_strdup (ccode_longB),
115                                      g_strdup (lang_name));
116         }
117
118         if (ccode_longT) {
119                 g_hash_table_insert (iso_code_names,
120                                      g_strdup (ccode_longT),
121                                      g_strdup (lang_name));
122         }
123 }
124
125 static void
126 spell_iso_code_names_init (void)
127 {
128         GError *err = NULL;
129         gchar  *buf;
130         gsize   buf_len;
131
132         iso_code_names = g_hash_table_new_full (g_str_hash, g_str_equal,
133                                                 g_free, g_free);
134
135         bindtextdomain ("iso_639", ISO_CODES_LOCALESDIR);
136         bind_textdomain_codeset ("iso_639", "UTF-8");
137
138         /* FIXME: We should read this in chunks and pass to the parser. */
139         if (g_file_get_contents (ISO_CODES_DATADIR "/iso_639.xml", &buf, &buf_len, &err)) {
140                 GMarkupParseContext *ctx;
141                 GMarkupParser        parser = {
142                         spell_iso_codes_parse_start_tag,
143                         NULL, NULL, NULL, NULL
144                 };
145
146                 ctx = g_markup_parse_context_new (&parser, 0, NULL, NULL);
147                 if (!g_markup_parse_context_parse (ctx, buf, buf_len, &err)) {
148                         g_warning ("Failed to parse '%s': %s",
149                                    ISO_CODES_DATADIR"/iso_639.xml",
150                                    err->message);
151                         g_error_free (err);
152                 }
153
154                 g_markup_parse_context_free (ctx);
155                 g_free (buf);
156         } else {
157                 g_warning ("Failed to load '%s': %s",
158                                 ISO_CODES_DATADIR"/iso_639.xml", err->message);
159                 g_error_free (err);
160         }
161 }
162
163 static void
164 spell_notify_languages_cb (GSettings   *gsettings,
165                            const gchar *key,
166                            gpointer     user_data)
167 {
168         DEBUG ("Resetting languages due to config change");
169
170         /* We just reset the languages list. */
171         if (languages != NULL) {
172                 g_hash_table_unref (languages);
173                 languages = NULL;
174         }
175 }
176
177 static void
178 empathy_spell_free_language (SpellLanguage *lang)
179 {
180         enchant_broker_free_dict (lang->config, lang->speller);
181         enchant_broker_free (lang->config);
182
183         g_slice_free (SpellLanguage, lang);
184 }
185
186 static void
187 spell_setup_languages (void)
188 {
189         static GSettings *gsettings = NULL;
190         gchar  *str;
191
192         if (gsettings == NULL) {
193                 /* FIXME: this is never uninitialised */
194                 gsettings = g_settings_new (EMPATHY_PREFS_CHAT_SCHEMA);
195
196                 g_signal_connect (gsettings,
197                         "changed::" EMPATHY_PREFS_CHAT_SPELL_CHECKER_LANGUAGES,
198                         G_CALLBACK (spell_notify_languages_cb), NULL);
199         }
200
201         if (languages) {
202                 return;
203         }
204
205         languages = g_hash_table_new_full (g_str_hash, g_str_equal,
206                         g_free, (GDestroyNotify) empathy_spell_free_language);
207
208         str = g_settings_get_string (gsettings,
209                         EMPATHY_PREFS_CHAT_SPELL_CHECKER_LANGUAGES);
210
211         if (str != NULL) {
212                 gchar **strv;
213                 gint    i;
214
215                 strv = g_strsplit (str, ",", -1);
216
217                 i = 0;
218                 while (strv && strv[i]) {
219                         SpellLanguage *lang;
220
221                         DEBUG ("Setting up language:'%s'", strv[i]);
222
223                         lang = g_slice_new0 (SpellLanguage);
224
225                         lang->config = enchant_broker_init ();
226                         lang->speller = enchant_broker_request_dict (lang->config, strv[i]);
227
228                         if (lang->speller == NULL) {
229                                 DEBUG ("language '%s' has no valid dict", strv[i]);
230                         } else {
231                                 g_hash_table_insert (languages,
232                                                      g_strdup (strv[i]),
233                                                      lang);
234                         }
235
236                         i++;
237                 }
238
239                 if (strv) {
240                         g_strfreev (strv);
241                 }
242
243                 g_free (str);
244         }
245 }
246
247 const gchar *
248 empathy_spell_get_language_name (const gchar *code)
249 {
250         const gchar *name;
251
252         g_return_val_if_fail (code != NULL, NULL);
253
254         if (!iso_code_names) {
255                 spell_iso_code_names_init ();
256         }
257
258         name = g_hash_table_lookup (iso_code_names, code);
259         if (!name) {
260                 return NULL;
261         }
262
263         return dgettext ("iso_639", name);
264 }
265
266 static void
267 enumerate_dicts (const gchar * const lang_tag,
268                  const gchar * const provider_name,
269                  const gchar * const provider_desc,
270                  const gchar * const provider_file,
271                  gpointer            user_data)
272 {
273         GList **list = user_data;
274         gchar  *lang = g_strdup (lang_tag);
275
276         if (strchr (lang, '_')) {
277                 /* cut country part out of language */
278                 strchr (lang, '_')[0] = '\0';
279         }
280
281         if (g_list_find_custom (*list, lang, (GCompareFunc) strcmp)) {
282                 /* this language is already part of the list */
283                 g_free (lang);
284                 return;
285         }
286
287         *list = g_list_append (*list, g_strdup (lang));
288 }
289
290 GList *
291 empathy_spell_get_language_codes (void)
292 {
293         EnchantBroker *broker;
294         GList         *list_langs = NULL;
295
296         broker = enchant_broker_init ();
297         enchant_broker_list_dicts (broker, enumerate_dicts, &list_langs);
298         enchant_broker_free (broker);
299
300         return list_langs;
301 }
302
303 GList *
304 empathy_spell_get_enabled_language_codes (void)
305 {
306         spell_setup_languages ();
307         return g_hash_table_get_keys (languages);
308 }
309
310 void
311 empathy_spell_free_language_codes (GList *codes)
312 {
313         g_list_foreach (codes, (GFunc) g_free, NULL);
314         g_list_free (codes);
315 }
316
317 gboolean
318 empathy_spell_check (const gchar *word)
319 {
320         gint         enchant_result = 1;
321         const gchar *p;
322         gboolean     digit;
323         gunichar     c;
324         gint         len;
325         GHashTableIter iter;
326         SpellLanguage  *lang;
327
328         g_return_val_if_fail (word != NULL, FALSE);
329
330         spell_setup_languages ();
331
332         if (!languages) {
333                 return TRUE;
334         }
335
336         /* Ignore certain cases like numbers, etc. */
337         for (p = word, digit = TRUE; *p && digit; p = g_utf8_next_char (p)) {
338                 c = g_utf8_get_char (p);
339                 digit = g_unichar_isdigit (c);
340         }
341
342         if (digit) {
343                 /* We don't spell check digits. */
344                 DEBUG ("Not spell checking word:'%s', it is all digits", word);
345                 return TRUE;
346         }
347
348         len = strlen (word);
349         g_hash_table_iter_init (&iter, languages);
350         while (g_hash_table_iter_next (&iter, NULL, (gpointer *) &lang)) {
351                 enchant_result = enchant_dict_check (lang->speller, word, len);
352
353                 if (enchant_result == 0) {
354                         break;
355                 }
356         }
357
358         return (enchant_result == 0);
359 }
360
361 GList *
362 empathy_spell_get_suggestions (const gchar *code,
363                                const gchar *word)
364 {
365         gint   len;
366         GList *suggestion_list = NULL;
367         SpellLanguage *lang;
368         gchar **suggestions;
369         gsize   i, number_of_suggestions;
370
371         g_return_val_if_fail (code != NULL, NULL);
372         g_return_val_if_fail (word != NULL, NULL);
373
374         spell_setup_languages ();
375
376         if (!languages) {
377                 return NULL;
378         }
379
380         len = strlen (word);
381
382         lang = g_hash_table_lookup (languages, code);
383         if (!lang) {
384                 return NULL;
385         }
386
387         suggestions = enchant_dict_suggest (lang->speller, word, len,
388                                             &number_of_suggestions);
389
390         for (i = 0; i < number_of_suggestions; i++) {
391                 suggestion_list = g_list_append (suggestion_list,
392                                                  g_strdup (suggestions[i]));
393         }
394
395         if (suggestions) {
396                 enchant_dict_free_string_list (lang->speller, suggestions);
397         }
398
399         return suggestion_list;
400 }
401
402 gboolean
403 empathy_spell_supported (void)
404 {
405         if (g_getenv ("EMPATHY_SPELL_DISABLED")) {
406                 DEBUG ("EMPATHY_SPELL_DISABLE env variable defined");
407                 return FALSE;
408         }
409
410         return TRUE;
411 }
412
413 void
414 empathy_spell_add_to_dictionary (const gchar *code,
415                                  const gchar *word)
416 {
417         SpellLanguage *lang;
418
419         g_return_if_fail (code != NULL);
420         g_return_if_fail (word != NULL);
421
422         spell_setup_languages ();
423         if (languages == NULL)
424                 return;
425
426         lang = g_hash_table_lookup (languages, code);
427         if (lang == NULL)
428                 return;
429
430         enchant_dict_add_to_pwl (lang->speller, word, strlen (word));
431 }
432
433 #else /* not HAVE_ENCHANT */
434
435 gboolean
436 empathy_spell_supported (void)
437 {
438         return FALSE;
439 }
440
441 GList *
442 empathy_spell_get_suggestions (const gchar *code,
443                                const gchar *word)
444 {
445         DEBUG ("Support disabled, could not get suggestions");
446
447         return NULL;
448 }
449
450 gboolean
451 empathy_spell_check (const gchar *word)
452 {
453         DEBUG ("Support disabled, could not check spelling");
454
455         return TRUE;
456 }
457
458 const gchar *
459 empathy_spell_get_language_name (const gchar *lang)
460 {
461         DEBUG ("Support disabled, could not get language name");
462
463         return NULL;
464 }
465
466 GList *
467 empathy_spell_get_language_codes (void)
468 {
469         DEBUG ("Support disabled, could not get language codes");
470
471         return NULL;
472 }
473
474 void
475 empathy_spell_free_language_codes (GList *codes)
476 {
477 }
478
479 void
480 empathy_spell_add_to_dictionary (const gchar *code,
481                                  const gchar *word)
482 {
483         DEBUG ("Support disabled, could not expand the dictionary");
484 }
485
486 GList *
487 empathy_spell_get_enabled_language_codes (void)
488 {
489         DEBUG ("Support disabled, could not get enabled language codes");
490
491         return NULL;
492 }
493
494 #endif /* HAVE_ENCHANT */
495
496
497 void
498 empathy_spell_free_suggestions (GList *suggestions)
499 {
500         g_list_foreach (suggestions, (GFunc) g_free, NULL);
501         g_list_free (suggestions);
502 }
503