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