1 /*
2  * e-spell-checker.c
3  *
4  * This program is free software; you can redistribute it and/or
5  * modify it under the terms of version 2 of the GNU Lesser General Public
6  * License as published by the Free Software Foundation.
7  *
8  * This program is distributed in the hope that it will be useful,
9  * but WITHOUT ANY WARRANTY; without even the implied warranty of
10  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
11  * General Public License for more details.
12  *
13  * You should have received a copy of the GNU Lesser General Public
14  * License along with this program; if not, write to the
15  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
16  * Boston, MA 02111-1307, USA.
17  */
18 
19 #include "evolution-config.h"
20 
21 #include <enchant.h>
22 
23 #include "e-spell-checker.h"
24 #include "e-spell-dictionary.h"
25 
26 #include <libebackend/libebackend.h>
27 #include <pango/pango.h>
28 #include <gtk/gtk.h>
29 #include <string.h>
30 
31 #define E_SPELL_CHECKER_GET_PRIVATE(obj) \
32 	(G_TYPE_INSTANCE_GET_PRIVATE \
33 	((obj), E_TYPE_SPELL_CHECKER, ESpellCheckerPrivate))
34 
35 #define MAX_SUGGESTIONS 10
36 
37 struct _ESpellCheckerPrivate {
38 	GHashTable *active_dictionaries;
39 	GHashTable *dictionaries_cache;
40 };
41 
42 enum {
43 	PROP_0,
44 	PROP_ACTIVE_LANGUAGES
45 };
46 
47 G_DEFINE_TYPE_EXTENDED (
48 	ESpellChecker,
49 	e_spell_checker,
50 	G_TYPE_OBJECT,
51 	0,
52 	G_IMPLEMENT_INTERFACE (
53 		E_TYPE_EXTENSIBLE, NULL))
54 
55 /**
56  * ESpellChecker:
57  *
58  * #ESpellChecker represents a spellchecker in Evolution. It can be used as a
59  * provider for dictionaries.
60  */
61 
62 
63 /* We retain ownership of the EnchantDict's since they
64  * have to be freed through enchant_broker_free_dict()
65  * and we also own the EnchantBroker. */
66 static GHashTable *global_enchant_dicts;
67 static GHashTable *global_language_tags; /* gchar * ~> NULL */
68 static EnchantBroker *global_broker;
69 G_LOCK_DEFINE_STATIC (global_memory);
70 
71 static gboolean
spell_checker_enchant_dicts_foreach_cb(gpointer key,gpointer value,gpointer user_data)72 spell_checker_enchant_dicts_foreach_cb (gpointer key,
73                                         gpointer value,
74                                         gpointer user_data)
75 {
76 	EnchantDict *enchant_dict = value;
77 	EnchantBroker *enchant_broker = user_data;
78 
79 	if (enchant_dict)
80 		enchant_broker_free_dict (enchant_broker, enchant_dict);
81 
82 	return TRUE;
83 }
84 
85 static void
spell_checker_get_property(GObject * object,guint property_id,GValue * value,GParamSpec * pspec)86 spell_checker_get_property (GObject *object,
87                             guint property_id,
88                             GValue *value,
89                             GParamSpec *pspec)
90 {
91 	switch (property_id) {
92 		case PROP_ACTIVE_LANGUAGES:
93 			g_value_take_boxed (
94 				value,
95 				e_spell_checker_list_active_languages (
96 				E_SPELL_CHECKER (object), NULL));
97 			return;
98 	}
99 
100 	G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
101 }
102 
103 static void
spell_checker_dispose(GObject * object)104 spell_checker_dispose (GObject *object)
105 {
106 	ESpellCheckerPrivate *priv;
107 
108 	priv = E_SPELL_CHECKER_GET_PRIVATE (object);
109 
110 	g_hash_table_remove_all (priv->active_dictionaries);
111 	g_hash_table_remove_all (priv->dictionaries_cache);
112 
113 	/* Chain up to parent's dispose() method. */
114 	G_OBJECT_CLASS (e_spell_checker_parent_class)->dispose (object);
115 }
116 
117 static void
spell_checker_finalize(GObject * object)118 spell_checker_finalize (GObject *object)
119 {
120 	ESpellCheckerPrivate *priv;
121 
122 	priv = E_SPELL_CHECKER_GET_PRIVATE (object);
123 
124 	g_hash_table_destroy (priv->active_dictionaries);
125 	g_hash_table_destroy (priv->dictionaries_cache);
126 
127 	/* Chain up to parent's finalize() method. */
128 	G_OBJECT_CLASS (e_spell_checker_parent_class)->finalize (object);
129 }
130 
131 static void
spell_checker_constructed(GObject * object)132 spell_checker_constructed (GObject *object)
133 {
134 	/* Chain up to parent's constructed() method. */
135 	G_OBJECT_CLASS (e_spell_checker_parent_class)->constructed (object);
136 
137 	e_extensible_load_extensions (E_EXTENSIBLE (object));
138 }
139 
140 static void
e_spell_checker_class_init(ESpellCheckerClass * class)141 e_spell_checker_class_init (ESpellCheckerClass *class)
142 {
143 	GObjectClass *object_class;
144 
145 	g_type_class_add_private (class, sizeof (ESpellCheckerPrivate));
146 
147 	object_class = G_OBJECT_CLASS (class);
148 	object_class->get_property = spell_checker_get_property;
149 	object_class->dispose = spell_checker_dispose;
150 	object_class->finalize = spell_checker_finalize;
151 	object_class->constructed = spell_checker_constructed;
152 
153 	g_object_class_install_property (
154 		object_class,
155 		PROP_ACTIVE_LANGUAGES,
156 		g_param_spec_boxed (
157 			"active-languages",
158 			"Active Languages",
159 			"Active spell check language codes",
160 			G_TYPE_STRV,
161 			G_PARAM_READABLE |
162 			G_PARAM_STATIC_STRINGS));
163 }
164 
165 static void
e_spell_checker_init(ESpellChecker * checker)166 e_spell_checker_init (ESpellChecker *checker)
167 {
168 	GHashTable *active_dictionaries;
169 	GHashTable *dictionaries_cache;
170 
171 	active_dictionaries = g_hash_table_new_full (
172 		(GHashFunc) e_spell_dictionary_hash,
173 		(GEqualFunc) e_spell_dictionary_equal,
174 		(GDestroyNotify) g_object_unref,
175 		(GDestroyNotify) NULL);
176 
177 	dictionaries_cache = g_hash_table_new_full (
178 		(GHashFunc) g_str_hash,
179 		(GEqualFunc) g_str_equal,
180 		(GDestroyNotify) NULL,
181 		(GDestroyNotify) g_object_unref);
182 
183 	checker->priv = E_SPELL_CHECKER_GET_PRIVATE (checker);
184 
185 	checker->priv->active_dictionaries = active_dictionaries;
186 	checker->priv->dictionaries_cache = dictionaries_cache;
187 }
188 
189 /**
190  * e_spell_checker_new:
191  *
192  * Creates a new #ESpellChecker instance.
193  *
194  * Returns: a new #ESpellChecker
195  **/
196 ESpellChecker *
e_spell_checker_new(void)197 e_spell_checker_new (void)
198 {
199 	return g_object_new (E_TYPE_SPELL_CHECKER, NULL);
200 }
201 
202 static void
list_enchant_dicts(const gchar * const language_tag,const gchar * const provider_name,const gchar * const provider_desc,const gchar * const provider_file,gpointer user_data)203 list_enchant_dicts (const gchar * const language_tag,
204                     const gchar * const provider_name,
205                     const gchar * const provider_desc,
206                     const gchar * const provider_file,
207                     gpointer user_data)
208 {
209 	g_hash_table_insert (
210 		global_language_tags,
211 		g_strdup (language_tag), NULL);
212 }
213 
214 static void
copy_enchant_dicts(gpointer planguage_tag,gpointer punused,gpointer user_data)215 copy_enchant_dicts (gpointer planguage_tag,
216 		    gpointer punused,
217 		    gpointer user_data)
218 {
219 	ESpellChecker *checker = user_data;
220 
221 	if (planguage_tag) {
222 		ESpellDictionary *dictionary;
223 		const gchar *code;
224 
225 		/* Note that we retain ownership of the EnchantDict.
226 		 * Since EnchantDict is not reference counted, we're
227 		 * merely loaning the pointer to ESpellDictionary. */
228 		dictionary = e_spell_dictionary_new_bare (checker, planguage_tag);
229 		code = e_spell_dictionary_get_code (dictionary);
230 
231 		g_hash_table_insert (
232 			checker->priv->dictionaries_cache,
233 			(gpointer) code, dictionary);
234 	}
235 }
236 
237 static void
e_spell_checker_init_global_memory(void)238 e_spell_checker_init_global_memory (void)
239 {
240 	G_LOCK (global_memory);
241 
242 	if (!global_broker) {
243 		global_broker = enchant_broker_init ();
244 		global_enchant_dicts = g_hash_table_new_full (
245 			(GHashFunc) g_str_hash,
246 			(GEqualFunc) g_str_equal,
247 			(GDestroyNotify) g_free,
248 			(GDestroyNotify) NULL);
249 		global_language_tags = g_hash_table_new_full (
250 			g_str_hash, g_str_equal,
251 			g_free, NULL);
252 
253 		enchant_broker_list_dicts (
254 			global_broker,
255 			list_enchant_dicts, global_broker);
256 	}
257 
258 	G_UNLOCK (global_memory);
259 }
260 
261 /**
262  * e_spell_checker_free_global_memory:
263  *
264  * Frees global memory used by the ESpellChecker. This should be called at
265  * the end of main(), to avoid memory leaks.
266  *
267  * Since: 3.16
268  **/
269 void
e_spell_checker_free_global_memory(void)270 e_spell_checker_free_global_memory (void)
271 {
272 	G_LOCK (global_memory);
273 
274 	if (global_enchant_dicts) {
275 		/* Freeing EnchantDicts requires help from EnchantBroker. */
276 		g_hash_table_foreach_remove (
277 			global_enchant_dicts,
278 			spell_checker_enchant_dicts_foreach_cb,
279 			global_broker);
280 		g_hash_table_destroy (global_enchant_dicts);
281 		global_enchant_dicts = NULL;
282 
283 		enchant_broker_free (global_broker);
284 		global_broker = NULL;
285 	}
286 
287 	g_clear_pointer (&global_language_tags, g_hash_table_destroy);
288 
289 	G_UNLOCK (global_memory);
290 }
291 
292 /**
293  * e_spell_checker_list_available_dicts:
294  * @checker: An #ESpellChecker
295  *
296  * Returns list of all dictionaries available to the actual
297  * spell-checking backend.
298  *
299  * Returns: new copy of #GList of #ESpellDictionary. The dictionaries are
300  * owned by the @checker and should not be free'd. The list should be freed
301  * using g_list_free() when not needed anymore. [transfer-list]
302  */
303 GList *
e_spell_checker_list_available_dicts(ESpellChecker * checker)304 e_spell_checker_list_available_dicts (ESpellChecker *checker)
305 {
306 	GList *list;
307 
308 	g_return_val_if_fail (E_IS_SPELL_CHECKER (checker), NULL);
309 
310 	if (g_hash_table_size (checker->priv->dictionaries_cache) == 0) {
311 		e_spell_checker_init_global_memory ();
312 		G_LOCK (global_memory);
313 		g_hash_table_foreach (global_language_tags, copy_enchant_dicts, checker);
314 		G_UNLOCK (global_memory);
315 	}
316 
317 	list = g_hash_table_get_values (checker->priv->dictionaries_cache);
318 
319 	return g_list_sort (list, (GCompareFunc) e_spell_dictionary_compare);
320 }
321 
322 /**
323  * e_spell_checker_count_available_dicts:
324  * @checker: An #ESpellChecker
325  *
326  * Returns: Count of available dictionaries.
327  **/
328 guint
e_spell_checker_count_available_dicts(ESpellChecker * checker)329 e_spell_checker_count_available_dicts (ESpellChecker *checker)
330 {
331 	g_return_val_if_fail (E_IS_SPELL_CHECKER (checker), 0);
332 
333 	if (g_hash_table_size (checker->priv->dictionaries_cache) == 0) {
334 		e_spell_checker_init_global_memory ();
335 		G_LOCK (global_memory);
336 		g_hash_table_foreach (global_language_tags, copy_enchant_dicts, checker);
337 		G_UNLOCK (global_memory);
338 	}
339 
340 	return g_hash_table_size (checker->priv->dictionaries_cache);
341 }
342 
343 /**
344  * e_spell_checker_ref_dictionary:
345  * @checker: an #ESpellChecker
346  * @language_code: (allow-none): language code of a dictionary, or %NULL
347  *
348  * Tries to find an #ESpellDictionary for given @language_code.
349  * If @language_code is %NULL, the function will return a default
350  * #ESpellDictionary.
351  *
352  * Returns: an #ESpellDictionary for @language_code
353  */
354 ESpellDictionary *
e_spell_checker_ref_dictionary(ESpellChecker * checker,const gchar * language_code)355 e_spell_checker_ref_dictionary (ESpellChecker *checker,
356                                 const gchar *language_code)
357 {
358 	ESpellDictionary *dictionary;
359 	GList *list;
360 
361 	g_return_val_if_fail (E_IS_SPELL_CHECKER (checker), NULL);
362 
363 	/* If the cache has not yet been initialized, do so - we will need
364 	 * it anyway, Otherwise is this call very cheap */
365 	list = e_spell_checker_list_available_dicts (checker);
366 
367 	if (language_code == NULL) {
368 		dictionary = (list != NULL) ? list->data : NULL;
369 	} else {
370 		dictionary = g_hash_table_lookup (
371 			checker->priv->dictionaries_cache,
372 			language_code);
373 	}
374 
375 	if (dictionary != NULL)
376 		g_object_ref (dictionary);
377 
378 	g_list_free (list);
379 
380 	return dictionary;
381 }
382 
383 /**
384  * e_spell_checker_get_enchant_dict:
385  * @checker: an #ESpellChecker
386  * @language_code: language code of a dictionary, or %NULL
387  *
388  * Returns the #EnchantDict for @language_code, or %NULL if there is none.
389  *
390  * Returns: the #EnchantDict for @language_code, or %NULL
391  **/
392 gpointer
e_spell_checker_get_enchant_dict(ESpellChecker * checker,const gchar * language_code)393 e_spell_checker_get_enchant_dict (ESpellChecker *checker,
394                                   const gchar *language_code)
395 {
396 	EnchantDict *dict;
397 
398 	g_return_val_if_fail (E_IS_SPELL_CHECKER (checker), NULL);
399 	g_return_val_if_fail (language_code != NULL, NULL);
400 
401 	e_spell_checker_init_global_memory ();
402 
403 	G_LOCK (global_memory);
404 
405 	dict = g_hash_table_lookup (global_enchant_dicts, language_code);
406 	if (((gpointer) dict) == GINT_TO_POINTER (1)) {
407 		dict = NULL;
408 	} else if (!dict) {
409 		dict = enchant_broker_request_dict (global_broker, language_code);
410 		if (dict)
411 			g_hash_table_insert (global_enchant_dicts, g_strdup (language_code), dict);
412 		else
413 			g_hash_table_insert (global_enchant_dicts, g_strdup (language_code), GINT_TO_POINTER (1));
414 	}
415 
416 	G_UNLOCK (global_memory);
417 
418 	return dict;
419 }
420 
421 gboolean
e_spell_checker_get_language_active(ESpellChecker * checker,const gchar * language_code)422 e_spell_checker_get_language_active (ESpellChecker *checker,
423                                      const gchar *language_code)
424 {
425 	ESpellDictionary *dictionary;
426 	GHashTable *active_dictionaries;
427 	gboolean active;
428 
429 	g_return_val_if_fail (E_IS_SPELL_CHECKER (checker), FALSE);
430 	g_return_val_if_fail (language_code != NULL, FALSE);
431 
432 	dictionary = e_spell_checker_ref_dictionary (checker, language_code);
433 	if (!dictionary)
434 		return FALSE;
435 
436 	active_dictionaries = checker->priv->active_dictionaries;
437 	active = g_hash_table_contains (active_dictionaries, dictionary);
438 
439 	g_object_unref (dictionary);
440 
441 	return active;
442 }
443 
444 void
e_spell_checker_set_language_active(ESpellChecker * checker,const gchar * language_code,gboolean active)445 e_spell_checker_set_language_active (ESpellChecker *checker,
446                                      const gchar *language_code,
447                                      gboolean active)
448 {
449 	ESpellDictionary *dictionary;
450 	GHashTable *active_dictionaries;
451 	gboolean is_active;
452 
453 	g_return_if_fail (E_IS_SPELL_CHECKER (checker));
454 	g_return_if_fail (language_code != NULL);
455 
456 	dictionary = e_spell_checker_ref_dictionary (checker, language_code);
457 	if (!dictionary)
458 		return;
459 
460 	active_dictionaries = checker->priv->active_dictionaries;
461 	is_active = g_hash_table_contains (active_dictionaries, dictionary);
462 
463 	if (active && !is_active) {
464 		g_object_ref (dictionary);
465 		g_hash_table_add (active_dictionaries, dictionary);
466 		g_object_notify (G_OBJECT (checker), "active-languages");
467 	} else if (!active && is_active) {
468 		g_hash_table_remove (active_dictionaries, dictionary);
469 		g_object_notify (G_OBJECT (checker), "active-languages");
470 	}
471 
472 	g_object_unref (dictionary);
473 }
474 
475 /**
476  * e_spell_checker_set_active_languages:
477  * @checker: An #ESpellChecker
478  * @languages: A list of languages to have activated
479  *
480  * Activates only the languages from @languages, all others will
481  * be deactivated after this function is finished.
482  **/
483 void
e_spell_checker_set_active_languages(ESpellChecker * checker,const gchar * const * languages)484 e_spell_checker_set_active_languages (ESpellChecker *checker,
485 				      const gchar * const *languages)
486 {
487 	gint ii;
488 
489 	g_return_if_fail (E_IS_SPELL_CHECKER (checker));
490 
491 	g_object_freeze_notify (G_OBJECT (checker));
492 
493 	for (ii = 0; languages && languages[ii]; ii++) {
494 		e_spell_checker_set_language_active (checker, languages[ii], TRUE);
495 	}
496 
497 	if (ii == g_hash_table_size (checker->priv->active_dictionaries)) {
498 		g_object_thaw_notify (G_OBJECT (checker));
499 		return;
500 	}
501 
502 	g_hash_table_remove_all (checker->priv->active_dictionaries);
503 	for (ii = 0; languages && languages[ii]; ii++) {
504 		e_spell_checker_set_language_active (checker, languages[ii], TRUE);
505 	}
506 
507 	g_object_notify (G_OBJECT (checker), "active-languages");
508 	g_object_thaw_notify (G_OBJECT (checker));
509 }
510 
511 /**
512  * e_spell_checker_list_active_languages:
513  * @checker: an #ESpellChecker
514  * @n_languages: return location for the number of active languages, or %NULL
515  *
516  * Returns a %NULL-terminated array of language codes actively being used
517  * for spell checking.  Free the returned array with g_strfreev().
518  *
519  * Returns: a %NULL-teriminated array of language codes
520  **/
521 gchar **
e_spell_checker_list_active_languages(ESpellChecker * checker,guint * n_languages)522 e_spell_checker_list_active_languages (ESpellChecker *checker,
523                                        guint *n_languages)
524 {
525 	GHashTable *active_dictionaries;
526 	GList *list, *link;
527 	gchar **active_languages;
528 	guint size;
529 	gint ii = 0;
530 
531 	g_return_val_if_fail (E_IS_SPELL_CHECKER (checker), NULL);
532 
533 	active_dictionaries = checker->priv->active_dictionaries;
534 	list = g_hash_table_get_keys (active_dictionaries);
535 	size = g_hash_table_size (active_dictionaries);
536 
537 	active_languages = g_new0 (gchar *, size + 1);
538 
539 	list = g_list_sort (list, (GCompareFunc) e_spell_dictionary_compare);
540 
541 	for (link = list; link != NULL; link = g_list_next (link)) {
542 		ESpellDictionary *dictionary;
543 		const gchar *language_code;
544 
545 		dictionary = E_SPELL_DICTIONARY (link->data);
546 		language_code = e_spell_dictionary_get_code (dictionary);
547 		active_languages[ii++] = g_strdup (language_code);
548 	}
549 
550 	if (n_languages != NULL)
551 		*n_languages = size;
552 
553 	g_list_free (list);
554 
555 	return active_languages;
556 }
557 
558 /**
559  * e_spell_checker_count_active_languages:
560  * @checker: an #ESpellChecker
561  *
562  * Returns the number of languages actively being used for spell checking.
563  *
564  * Returns: number of active spell checking languages
565  **/
566 guint
e_spell_checker_count_active_languages(ESpellChecker * checker)567 e_spell_checker_count_active_languages (ESpellChecker *checker)
568 {
569 	g_return_val_if_fail (E_IS_SPELL_CHECKER (checker), 0);
570 
571 	return g_hash_table_size (checker->priv->active_dictionaries);
572 }
573 
574 /**
575  * e_spell_checker_check_word:
576  * @checker: a #SpellChecker
577  * @word: a word to spell-check
578  * @length: length of @word in bytes or -1 when %NULL-terminated
579  *
580  * Calls e_spell_dictionary_check_word() on all active dictionaries in
581  * @checker, and returns %TRUE if @word is recognized by any of them.
582  *
583  * Returns: %TRUE if @word is recognized, %FALSE otherwise
584  **/
585 gboolean
e_spell_checker_check_word(ESpellChecker * checker,const gchar * word,gsize length)586 e_spell_checker_check_word (ESpellChecker *checker,
587                             const gchar *word,
588                             gsize length)
589 {
590 	GList *list, *link;
591 	gboolean recognized = FALSE;
592 
593 	g_return_val_if_fail (E_IS_SPELL_CHECKER (checker), TRUE);
594 	g_return_val_if_fail (word != NULL && *word != '\0', TRUE);
595 
596 	list = g_hash_table_get_keys (checker->priv->active_dictionaries);
597 
598 	for (link = list; link != NULL; link = g_list_next (link)) {
599 		ESpellDictionary *dictionary;
600 
601 		dictionary = E_SPELL_DICTIONARY (link->data);
602 		if (e_spell_dictionary_check_word (dictionary, word, length)) {
603 			recognized = TRUE;
604 			break;
605 		}
606 	}
607 
608 	g_list_free (list);
609 
610 	return recognized;
611 }
612 
613 /**
614  * e_spell_checker_ignore_word:
615  * @checker: an #ESpellChecker
616  * @word: word to ignore for the rest of session
617  *
618  * Calls e_spell_dictionary_ignore_word() on all active dictionaries in
619  * the @checker.
620  */
621 void
e_spell_checker_ignore_word(ESpellChecker * checker,const gchar * word)622 e_spell_checker_ignore_word (ESpellChecker *checker,
623                              const gchar *word)
624 {
625 	/* Carefully, this will add the word to all active dictionaries */
626 
627 	GHashTable *active_dictionaries;
628 	GList *list, *link;
629 
630 	g_return_if_fail (E_IS_SPELL_CHECKER (checker));
631 
632 	active_dictionaries = checker->priv->active_dictionaries;
633 	list = g_hash_table_get_keys (active_dictionaries);
634 
635 	for (link = list; link != NULL; link = g_list_next (link)) {
636 		ESpellDictionary *dictionary;
637 
638 		dictionary = E_SPELL_DICTIONARY (link->data);
639 		e_spell_dictionary_ignore_word (dictionary, word, -1);
640 	}
641 
642 	g_list_free (list);
643 }
644 
645 /**
646  * e_spell_checker_learn_word:
647  * @checker: an #ESpellChecker
648  * @word: word to learn
649  *
650  * Calls e_spell_dictionary_learn_word() on all active dictionaries in
651  * the @checker.
652  */
653 void
e_spell_checker_learn_word(ESpellChecker * checker,const gchar * word)654 e_spell_checker_learn_word (ESpellChecker *checker,
655                             const gchar *word)
656 {
657 	/* Carefully, this will add the word to all active dictionaries! */
658 
659 	GHashTable *active_dictionaries;
660 	GList *list, *link;
661 
662 	g_return_if_fail (E_IS_SPELL_CHECKER (checker));
663 
664 	active_dictionaries = checker->priv->active_dictionaries;
665 	list = g_hash_table_get_keys (active_dictionaries);
666 
667 	for (link = list; link != NULL; link = g_list_next (link)) {
668 		ESpellDictionary *dictionary;
669 
670 		dictionary = E_SPELL_DICTIONARY (link->data);
671 		e_spell_dictionary_learn_word (dictionary, word, -1);
672 	}
673 
674 	g_list_free (list);
675 }
676 
677 /**
678  * e_spell_checker_get_guesses_for_word:
679  * @checker: an #ESpellChecker
680  * @word: word to get guesses for
681  *
682  * Returns: a NULL-terminated array of guesses for the @word. Free the returned
683  *    pointer with g_strfreev() when done with it.
684  **/
685 gchar **
e_spell_checker_get_guesses_for_word(ESpellChecker * checker,const gchar * word)686 e_spell_checker_get_guesses_for_word (ESpellChecker *checker,
687 				      const gchar *word)
688 {
689 	GHashTable *active_dictionaries;
690 	GList *list, *link;
691 	gchar **guesses;
692 	gint ii = 0;
693 
694 	g_return_val_if_fail (E_IS_SPELL_CHECKER (checker), NULL);
695 	g_return_val_if_fail (word != NULL, NULL);
696 
697 	guesses = g_new0 (gchar *, MAX_SUGGESTIONS + 1);
698 
699 	active_dictionaries = checker->priv->active_dictionaries;
700 	list = g_hash_table_get_keys (active_dictionaries);
701 
702 	for (link = list; link != NULL; link = g_list_next (link)) {
703 		ESpellDictionary *dictionary;
704 		GList *suggestions;
705 
706 		dictionary = E_SPELL_DICTIONARY (link->data);
707 		suggestions = e_spell_dictionary_get_suggestions (
708 			dictionary, word, -1);
709 
710 		while (suggestions != NULL && ii < MAX_SUGGESTIONS) {
711 			guesses[ii++] = suggestions->data;
712 			suggestions->data = NULL;
713 
714 			suggestions = g_list_delete_link (
715 				suggestions, suggestions);
716 		}
717 
718 		g_list_free_full (suggestions, (GDestroyNotify) g_free);
719 
720 		if (ii >= MAX_SUGGESTIONS)
721 			break;
722 	}
723 
724 	g_list_free (list);
725 
726 	return guesses;
727 }
728