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