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