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