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