1 /* gtkspell - a spell-checking addon for GTK's TextView widget
2  * Copyright (c) 2002 Evan Martin
3  * Copyright (c) 2012-2013 Sandro Mani
4  *
5  *    This program is free software; you can redistribute it and/or modify
6  *    it under the terms of the GNU General Public License as published by
7  *    the Free Software Foundation; either version 2 of the License, or
8  *    (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
13  *    GNU General Public License for more details.
14  *
15  *    You should have received a copy of the GNU General Public License along
16  *    with this program; if not, write to the Free Software Foundation, Inc.,
17  *    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18  */
19 
20 /* vim: set ts=4 sw=4 wm=5 : */
21 
22 #include "../config.h"
23 #include "gtkspell.h"
24 #include <string.h>
25 #include <libintl.h>
26 #include <locale.h>
27 #include <enchant.h>
28 
29 #ifdef HAVE_ISO_CODES
30 #include "gtkspell-codetable.h"
31 #endif
32 
33 #ifdef G_OS_WIN32
34 #include "gtkspell-win32.h"
35 #endif
36 
37 #define _(String) dgettext (GETTEXT_PACKAGE, String)
38 
39 #define GTK_SPELL_MISSPELLED_TAG "gtkspell-misspelled"
40 #define GTK_SPELL_OBJECT_KEY "gtkspell"
41 
42 static const int debug = 0;
43 static const int quiet = 0;
44 
45 static EnchantBroker *broker = NULL;
46 static int broker_ref_cnt = 0;
47 #ifdef HAVE_ISO_CODES
48 static int codetable_ref_cnt = 0;
49 #endif
50 
51 static void gtk_spell_checker_dispose (GObject *object);
52 static void gtk_spell_checker_finalize (GObject *object);
53 
54 enum
55 {
56   LANGUAGE_CHANGED,
57   LAST_SIGNAL
58 };
59 
60 static guint signals[LAST_SIGNAL] = { 0 };
61 
62 enum
63 {
64   PROP_0,
65   PROP_DECODE_LANGUAGE_CODES
66 };
67 
68 #define GTK_SPELL_CHECKER_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), GTK_SPELL_TYPE_CHECKER, GtkSpellCheckerPrivate))
69 
70 struct _GtkSpellCheckerPrivate
71 {
72   GtkTextView *view;
73   GtkTextBuffer *buffer;
74   GtkTextTag *tag_highlight;
75   GtkTextMark *mark_insert_start;
76   GtkTextMark *mark_insert_end;
77   GtkTextMark *mark_click;
78   gboolean deferred_check;
79   EnchantDict *speller;
80   gchar *lang;
81   gboolean decode_codes;
82 };
83 
G_DEFINE_TYPE(GtkSpellChecker,gtk_spell_checker,G_TYPE_INITIALLY_UNOWNED)84 G_DEFINE_TYPE (GtkSpellChecker, gtk_spell_checker, G_TYPE_INITIALLY_UNOWNED)
85 
86 static gboolean
87 gtk_spell_text_iter_forward_word_end (GtkTextIter *i)
88 {
89   GtkTextIter iter;
90 
91   /* heuristic:
92    * if we're on an singlequote/apostrophe and
93    * if the next letter is alphanumeric,
94    * this is an apostrophe (either single quote, or U+2019 = 8217. */
95 
96   if (!gtk_text_iter_forward_word_end (i))
97     return FALSE;
98 
99   if (gtk_text_iter_get_char (i) != '\'' &&
100       gtk_text_iter_get_char (i) != 8217)
101     return TRUE;
102 
103   iter = *i;
104   if (gtk_text_iter_forward_char (&iter) &&
105       g_unichar_isalpha (gtk_text_iter_get_char (&iter)))
106     return (gtk_text_iter_forward_word_end (i));
107 
108   return TRUE;
109 }
110 
111 static gboolean
gtk_spell_text_iter_backward_word_start(GtkTextIter * i)112 gtk_spell_text_iter_backward_word_start (GtkTextIter *i)
113 {
114   GtkTextIter iter;
115 
116   if (!gtk_text_iter_backward_word_start (i))
117     return FALSE;
118 
119   iter = *i;
120   if (g_unichar_isalpha (gtk_text_iter_get_char (&iter)) &&
121       gtk_text_iter_backward_char (&iter) &&
122       (gtk_text_iter_get_char (&iter) == '\'' ||
123        gtk_text_iter_get_char (&iter) == 8217))
124     return (gtk_text_iter_backward_word_start (i));
125 
126   return TRUE;
127 }
128 
129 #define gtk_text_iter_backward_word_start gtk_spell_text_iter_backward_word_start
130 #define gtk_text_iter_forward_word_end gtk_spell_text_iter_forward_word_end
131 
132 static void
check_word(GtkSpellChecker * spell,GtkTextIter * start,GtkTextIter * end)133 check_word (GtkSpellChecker *spell, GtkTextIter *start, GtkTextIter *end)
134 {
135   char *text;
136   text = gtk_text_buffer_get_text (spell->priv->buffer, start, end, FALSE);
137   if (debug)
138     g_print ("checking: %s\n", text);
139   if (g_unichar_isdigit (*text) == FALSE && /* don't check numbers */
140       enchant_dict_check (spell->priv->speller, text, strlen (text)) != 0)
141     gtk_text_buffer_apply_tag (spell->priv->buffer, spell->priv->tag_highlight, start, end);
142   g_free (text);
143 }
144 
145 static void
print_iter(char * name,GtkTextIter * iter)146 print_iter (char *name, GtkTextIter *iter)
147 {
148   g_print ("%1s[%d%c%c%c] ", name, gtk_text_iter_get_offset (iter),
149            gtk_text_iter_starts_word (iter) ? 's' : ' ',
150            gtk_text_iter_inside_word (iter) ? 'i' : ' ',
151            gtk_text_iter_ends_word (iter) ? 'e' : ' ');
152 }
153 
154 static void
check_range(GtkSpellChecker * spell,GtkTextIter start,GtkTextIter end,gboolean force_all)155 check_range (GtkSpellChecker *spell, GtkTextIter start,
156              GtkTextIter end, gboolean force_all)
157 {
158   g_return_if_fail (spell->priv->speller != NULL); /* for check_word */
159 
160   /* we need to "split" on word boundaries.
161    * luckily, pango knows what "words" are
162    * so we don't have to figure it out. */
163 
164   GtkTextIter wstart, wend, cursor, precursor;
165   gboolean inword, highlight;
166   if (debug)
167     {
168       g_print ("check_range: ");
169       print_iter ("s", &start);
170       print_iter ("e", &end);
171       g_print (" -> ");
172     }
173 
174   if (gtk_text_iter_inside_word (&end))
175     gtk_text_iter_forward_word_end (&end);
176   if (!gtk_text_iter_starts_word (&start))
177     {
178       if (gtk_text_iter_inside_word (&start) ||
179           gtk_text_iter_ends_word (&start))
180         {
181           gtk_text_iter_backward_word_start (&start);
182         }
183       else
184         {
185           /* if we're neither at the beginning nor inside a word,
186            * me must be in some spaces.
187            * skip forward to the beginning of the next word. */
188           //gtk_text_buffer_remove_tag (buffer, tag_highlight, &start, &end);
189           if (gtk_text_iter_forward_word_end (&start))
190             gtk_text_iter_backward_word_start (&start);
191         }
192     }
193   gtk_text_buffer_get_iter_at_mark (spell->priv->buffer, &cursor,
194                                     gtk_text_buffer_get_insert (spell->priv->buffer));
195 
196   precursor = cursor;
197   gtk_text_iter_backward_char (&precursor);
198   highlight = gtk_text_iter_has_tag (&cursor, spell->priv->tag_highlight) ||
199       gtk_text_iter_has_tag (&precursor, spell->priv->tag_highlight);
200 
201   gtk_text_buffer_remove_tag (spell->priv->buffer, spell->priv->tag_highlight, &start, &end);
202 
203   /* Fix a corner case when replacement occurs at beginning of buffer:
204    * An iter at offset 0 seems to always be inside a word,
205    * even if it's not.  Possibly a pango bug.
206    */
207   if (gtk_text_iter_get_offset (&start) == 0)
208     {
209       gtk_text_iter_forward_word_end (&start);
210       gtk_text_iter_backward_word_start (&start);
211     }
212 
213   if (debug)
214     {
215       print_iter ("s", &start);
216       print_iter ("e", &end);
217       g_print ("\n");
218     }
219 
220   wstart = start;
221   while (gtk_text_iter_compare (&wstart, &end) < 0)
222     {
223       /* move wend to the end of the current word. */
224       wend = wstart;
225       gtk_text_iter_forward_word_end (&wend);
226 
227       /* make sure we've actually advanced
228        * (we don't advance in some corner cases, such as after punctuation) */
229       if (gtk_text_iter_equal (&wstart, &wend))
230         break;
231 
232       inword = (gtk_text_iter_compare (&wstart, &cursor) < 0) &&
233                (gtk_text_iter_compare (&cursor, &wend) <= 0);
234 
235       if (inword && !force_all)
236         {
237           /* this word is being actively edited,
238            * only check if it's already highligted,
239            * otherwise defer this check until later. */
240           if (highlight)
241             check_word (spell, &wstart, &wend);
242           else
243             spell->priv->deferred_check = TRUE;
244         }
245       else
246         {
247           check_word (spell, &wstart, &wend);
248           spell->priv->deferred_check = FALSE;
249         }
250 
251       /* now move wend to the beginning of the next word, */
252       gtk_text_iter_forward_word_end (&wend);
253       gtk_text_iter_backward_word_start (&wend);
254       /* make sure we've actually advanced
255        * (we don't advance in some corner cases), */
256       if (gtk_text_iter_equal (&wstart, &wend))
257         break; /* we're done in these cases.. */
258       /* and then pick this as the new next word beginning. */
259       wstart = wend;
260     }
261 }
262 
263 static void
check_deferred_range(GtkSpellChecker * spell,gboolean force_all)264 check_deferred_range (GtkSpellChecker *spell, gboolean force_all)
265 {
266   GtkTextIter start, end;
267   gtk_text_buffer_get_iter_at_mark (spell->priv->buffer, &start, spell->priv->mark_insert_start);
268   gtk_text_buffer_get_iter_at_mark (spell->priv->buffer, &end, spell->priv->mark_insert_end);
269   check_range (spell, start, end, force_all);
270 }
271 
272 /* insertion works like this:
273  *  - before the text is inserted, we mark the position in the buffer.
274  *  - after the text is inserted, we see where our mark is and use that and
275  *    the current position to check the entire range of inserted text.
276  *
277  * this may be overkill for the common case (inserting one character). */
278 
279 static void
insert_text_before(GtkTextBuffer * buffer,GtkTextIter * iter,gchar * text,gint len,GtkSpellChecker * spell)280 insert_text_before (GtkTextBuffer *buffer, GtkTextIter *iter,
281                     gchar *text, gint len, GtkSpellChecker *spell)
282 {
283   g_return_if_fail (buffer == spell->priv->buffer);
284 
285   gtk_text_buffer_move_mark (buffer, spell->priv->mark_insert_start, iter);
286 }
287 
288 static void
insert_text_after(GtkTextBuffer * buffer,GtkTextIter * iter,gchar * text,gint len,GtkSpellChecker * spell)289 insert_text_after (GtkTextBuffer *buffer, GtkTextIter *iter,
290                    gchar *text, gint len, GtkSpellChecker *spell)
291 {
292   g_return_if_fail (buffer == spell->priv->buffer);
293 
294   GtkTextIter start;
295 
296   if (debug)
297     g_print ("insert\n");
298 
299   /* we need to check a range of text. */
300   gtk_text_buffer_get_iter_at_mark (buffer, &start, spell->priv->mark_insert_start);
301   check_range (spell, start, *iter, FALSE);
302 
303   gtk_text_buffer_move_mark (buffer, spell->priv->mark_insert_end, iter);
304 }
305 
306 /* deleting is more simple:  we're given the range of deleted text.
307  * after deletion, the start and end iters should be at the same position
308  * (because all of the text between them was deleted!).
309  * this means we only really check the words immediately bounding the
310  * deletion.
311  */
312 
313 static void
delete_range_after(GtkTextBuffer * buffer,GtkTextIter * start,GtkTextIter * end,GtkSpellChecker * spell)314 delete_range_after (GtkTextBuffer *buffer, GtkTextIter *start,
315                     GtkTextIter *end, GtkSpellChecker *spell)
316 {
317   g_return_if_fail (buffer == spell->priv->buffer);
318 
319   if (debug)
320     g_print ("delete\n");
321   check_range (spell, *start, *end, FALSE);
322 }
323 
324 static void
mark_set(GtkTextBuffer * buffer,GtkTextIter * iter,GtkTextMark * mark,GtkSpellChecker * spell)325 mark_set (GtkTextBuffer *buffer, GtkTextIter *iter,
326           GtkTextMark *mark, GtkSpellChecker *spell)
327 {
328   g_return_if_fail (buffer == spell->priv->buffer);
329 
330   /* if the cursor has moved and there is a deferred check so handle it now */
331   if ((mark == gtk_text_buffer_get_insert (buffer)) && spell->priv->deferred_check)
332     check_deferred_range (spell, FALSE);
333 }
334 
335 static void
get_word_extents_from_mark(GtkTextBuffer * buffer,GtkTextIter * start,GtkTextIter * end,GtkTextMark * mark)336 get_word_extents_from_mark (GtkTextBuffer *buffer, GtkTextIter *start,
337                             GtkTextIter *end, GtkTextMark *mark)
338 {
339   gtk_text_buffer_get_iter_at_mark (buffer, start, mark);
340   if (!gtk_text_iter_starts_word (start))
341     gtk_text_iter_backward_word_start (start);
342   *end = *start;
343   if (gtk_text_iter_inside_word (end))
344     gtk_text_iter_forward_word_end (end);
345 }
346 
347 static void
add_to_dictionary(GtkWidget * menuitem,GtkSpellChecker * spell)348 add_to_dictionary (GtkWidget *menuitem, GtkSpellChecker *spell)
349 {
350   char *word;
351   GtkTextIter start, end;
352 
353   get_word_extents_from_mark (spell->priv->buffer, &start, &end, spell->priv->mark_click);
354   word = gtk_text_buffer_get_text (spell->priv->buffer, &start, &end, FALSE);
355 
356   enchant_dict_add (spell->priv->speller, word, strlen (word));
357 
358   gtk_spell_checker_recheck_all (spell);
359 
360   g_free (word);
361 }
362 
363 static void
ignore_all(GtkWidget * menuitem,GtkSpellChecker * spell)364 ignore_all (GtkWidget *menuitem, GtkSpellChecker *spell)
365 {
366   char *word;
367   GtkTextIter start, end;
368 
369   get_word_extents_from_mark (spell->priv->buffer, &start, &end, spell->priv->mark_click);
370   word = gtk_text_buffer_get_text (spell->priv->buffer, &start, &end, FALSE);
371 
372   enchant_dict_add_to_session (spell->priv->speller, word, strlen (word));
373 
374   gtk_spell_checker_recheck_all (spell);
375 
376   g_free (word);
377 }
378 
379 static void
replace_word(GtkWidget * menuitem,GtkSpellChecker * spell)380 replace_word (GtkWidget *menuitem, GtkSpellChecker *spell)
381 {
382   char *oldword;
383   const char *newword;
384   GtkTextIter start, end;
385 
386   get_word_extents_from_mark (spell->priv->buffer, &start, &end, spell->priv->mark_click);
387   oldword = gtk_text_buffer_get_text (spell->priv->buffer, &start, &end, FALSE);
388   newword = gtk_label_get_text (GTK_LABEL (gtk_bin_get_child (GTK_BIN (menuitem))));
389 
390   if (debug)
391     {
392       g_print ("old word: '%s'\n", oldword);
393       print_iter ("s", &start);
394       print_iter ("e", &end);
395       g_print ("\nnew word: '%s'\n", newword);
396     }
397 
398   gtk_text_buffer_begin_user_action (spell->priv->buffer);
399   gtk_text_buffer_delete (spell->priv->buffer, &start, &end);
400   gtk_text_buffer_insert (spell->priv->buffer, &start, newword, -1);
401   gtk_text_buffer_end_user_action (spell->priv->buffer);
402 
403   enchant_dict_store_replacement (spell->priv->speller,
404                                   oldword, strlen (oldword),
405                                   newword, strlen (newword));
406 
407   g_free (oldword);
408 }
409 
410 /* This function populates suggestions at the top of the passed menu */
411 static void
add_suggestion_menus(GtkSpellChecker * spell,const char * word,GtkWidget * topmenu)412 add_suggestion_menus (GtkSpellChecker *spell, const char *word, GtkWidget *topmenu)
413 {
414   g_return_if_fail (spell->priv->speller != NULL);
415 
416   GtkWidget *menu;
417   GtkWidget *mi;
418   char **suggestions;
419   size_t n_suggs, i;
420   char *label;
421 
422   menu = topmenu;
423 
424   gint menu_position = 0;
425 
426   suggestions = enchant_dict_suggest (spell->priv->speller, word,
427                                       strlen (word), &n_suggs);
428 
429   if (suggestions == NULL || !n_suggs)
430     {
431       /* no suggestions.  put something in the menu anyway... */
432       GtkWidget *label;
433       label = gtk_label_new ("");
434       gtk_label_set_markup (GTK_LABEL (label), _("<i>(no suggestions)</i>"));
435 
436       mi = gtk_menu_item_new ();
437       gtk_container_add (GTK_CONTAINER (mi), label);
438       gtk_widget_show_all (mi);
439       gtk_menu_shell_insert (GTK_MENU_SHELL (menu), mi, menu_position++);
440     }
441   else
442     {
443       /* build a set of menus with suggestions. */
444       gboolean inside_more_submenu = FALSE;
445       for (i = 0; i < n_suggs; i++ )
446         {
447           if (i > 0 && i % 10 == 0)
448             {
449               inside_more_submenu = TRUE;
450               mi = gtk_menu_item_new_with_label (_("More..."));
451               gtk_widget_show (mi);
452               gtk_menu_shell_insert (GTK_MENU_SHELL (menu), mi, menu_position++);
453 
454               menu = gtk_menu_new ();
455               gtk_menu_item_set_submenu (GTK_MENU_ITEM (mi), menu);
456             }
457           mi = gtk_menu_item_new_with_label (suggestions[i]);
458           g_signal_connect (mi, "activate", G_CALLBACK (replace_word), spell);
459           gtk_widget_show (mi);
460           if (inside_more_submenu)
461             gtk_menu_shell_append (GTK_MENU_SHELL (menu), mi);
462           else
463             gtk_menu_shell_insert (GTK_MENU_SHELL (menu), mi, menu_position++);
464         }
465     }
466 
467   if (suggestions)
468     enchant_dict_free_string_list (spell->priv->speller, suggestions);
469 
470   /* + Add to Dictionary */
471   label = g_strdup_printf (_("Add \"%s\" to Dictionary"), word);
472 #if GTK_CHECK_VERSION(3,9,0)
473   mi = gtk_menu_item_new_with_label (label);
474 #else
475   mi = gtk_image_menu_item_new_with_label (label);
476   gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (mi),
477                  gtk_image_new_from_stock (GTK_STOCK_ADD, GTK_ICON_SIZE_MENU));
478 #endif
479   g_free (label);
480   g_signal_connect (mi, "activate", G_CALLBACK (add_to_dictionary), spell);
481   gtk_widget_show_all (mi);
482   gtk_menu_shell_insert (GTK_MENU_SHELL (topmenu), mi, menu_position++);
483 
484   /* - Ignore All */
485 #if GTK_CHECK_VERSION(3,9,0)
486   mi = gtk_menu_item_new_with_label (_("Ignore All"));
487 #else
488   mi = gtk_image_menu_item_new_with_label (_("Ignore All"));
489   gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (mi),
490               gtk_image_new_from_stock (GTK_STOCK_REMOVE, GTK_ICON_SIZE_MENU));
491 #endif
492   g_signal_connect (mi, "activate", G_CALLBACK (ignore_all), spell);
493   gtk_widget_show_all (mi);
494   gtk_menu_shell_insert (GTK_MENU_SHELL (topmenu), mi, menu_position++);
495 }
496 
497 static GtkWidget*
build_suggestion_menu(GtkSpellChecker * spell,const char * word)498 build_suggestion_menu (GtkSpellChecker *spell, const char *word)
499 {
500   GtkWidget *topmenu;
501   topmenu = gtk_menu_new ();
502   add_suggestion_menus (spell, word, topmenu);
503 
504   return topmenu;
505 }
506 
507 static void
language_change_callback(GtkCheckMenuItem * mi,GtkSpellChecker * spell)508 language_change_callback (GtkCheckMenuItem *mi, GtkSpellChecker* spell)
509 {
510   if (gtk_check_menu_item_get_active (mi))
511     {
512       GError* error = NULL;
513       gchar *name;
514       g_object_get (G_OBJECT (mi), "name", &name, NULL);
515       gtk_spell_checker_set_language (spell, name, &error);
516       g_signal_emit (spell, signals[LANGUAGE_CHANGED], 0, spell->priv->lang);
517       g_free (name);
518     }
519 }
520 
521 struct _languages_cb_struct { GList *langs; };
522 
523 static void
dict_describe_cb(const char * const lang_tag,const char * const provider_name,const char * const provider_desc,const char * const provider_file,void * user_data)524 dict_describe_cb (const char * const lang_tag,
525                   const char * const provider_name,
526                   const char * const provider_desc,
527                   const char * const provider_file,
528                   void * user_data)
529 {
530   struct _languages_cb_struct *languages_cb_struct = (struct _languages_cb_struct *)user_data;
531 
532   languages_cb_struct->langs = g_list_insert_sorted (
533       languages_cb_struct->langs, g_strdup (lang_tag),
534       (GCompareFunc) strcmp);
535 }
536 
537 static GtkWidget*
build_languages_menu(GtkSpellChecker * spell)538 build_languages_menu (GtkSpellChecker *spell)
539 {
540   GtkWidget *active_item = NULL, *menu = gtk_menu_new ();
541   GList *langs;
542   GtkWidget *mi;
543   GSList *menu_group = NULL;
544 
545   struct _languages_cb_struct languages_cb_struct;
546   languages_cb_struct.langs = NULL;
547 
548   enchant_broker_list_dicts (broker, dict_describe_cb, &languages_cb_struct);
549 
550   langs = languages_cb_struct.langs;
551 
552   for (; langs; langs = langs->next)
553     {
554       gchar *lang_tag = langs->data;
555 #ifdef HAVE_ISO_CODES
556       if (spell->priv->decode_codes == TRUE)
557         {
558           const gchar *lang_name = "\0";
559           const gchar *country_name = "\0";
560           gchar *label;
561           codetable_lookup (lang_tag, &lang_name, &country_name);
562           if (strlen (country_name) != 0)
563             label = g_strdup_printf ("%s (%s)", lang_name, country_name);
564           else
565             label = g_strdup_printf ("%s", lang_name);
566           mi = gtk_radio_menu_item_new_with_label (menu_group, label);
567           g_free (label);
568         }
569       else
570 #endif
571         mi = gtk_radio_menu_item_new_with_label (menu_group, lang_tag);
572       menu_group = gtk_radio_menu_item_get_group (GTK_RADIO_MENU_ITEM (mi));
573 
574       g_object_set (G_OBJECT (mi), "name", lang_tag, NULL);
575       if (spell->priv->lang && strcmp (spell->priv->lang, lang_tag) == 0)
576         active_item = mi;
577       gtk_widget_show (mi);
578       gtk_menu_shell_append (GTK_MENU_SHELL (menu), mi);
579 
580       g_free (lang_tag);
581     }
582   if (active_item)
583     gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (active_item), TRUE);
584   else
585     {
586       /* For the situation where no language is active (i.e.
587        * spell->priv->lang == NULL), create a "None" item which is active. */
588       mi = gtk_radio_menu_item_new_with_label (menu_group, _("None"));
589       gtk_menu_shell_append (GTK_MENU_SHELL (menu), mi);
590       gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (mi), TRUE);
591       gtk_widget_show (mi);
592     }
593   /* Connect signals to menu items after determining which one is active,
594    * since otherwise the signal is potentially already fired once (since the
595    * first item added to the group is active by default. */
596   for (; menu_group; menu_group = menu_group->next)
597     {
598         mi = menu_group->data;
599         if (!gtk_check_menu_item_get_active (GTK_CHECK_MENU_ITEM (mi)))
600           g_signal_connect (mi, "activate",
601                             G_CALLBACK (language_change_callback), spell);
602     }
603 
604   g_list_free (languages_cb_struct.langs);
605 
606   return menu;
607 }
608 
609 static void
populate_popup(GtkTextView * textview,GtkMenu * menu,GtkSpellChecker * spell)610 populate_popup (GtkTextView *textview, GtkMenu *menu, GtkSpellChecker *spell)
611 {
612   g_return_if_fail (spell->priv->view == textview);
613 
614   GtkWidget *mi;
615   GtkTextIter start, end;
616   char *word;
617 
618   /* menu separator comes first. */
619   mi = gtk_separator_menu_item_new ();
620   gtk_widget_show (mi);
621   gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), mi);
622 
623   /* on top: language selection */
624   mi = gtk_menu_item_new_with_label (_("Languages"));
625   gtk_menu_item_set_submenu (GTK_MENU_ITEM (mi), build_languages_menu (spell));
626   gtk_widget_show_all (mi);
627   gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), mi);
628 
629   /* we need to figure out if they picked a misspelled word. */
630   get_word_extents_from_mark (spell->priv->buffer, &start, &end, spell->priv->mark_click);
631 
632   /* if our highlight algorithm ever messes up,
633    * this isn't correct, either. */
634   if (!gtk_text_iter_has_tag (&start, spell->priv->tag_highlight))
635     return; /* word wasn't misspelled. */
636 
637   /* then, on top of it, the suggestions */
638   word = gtk_text_buffer_get_text (spell->priv->buffer, &start, &end, FALSE);
639   add_suggestion_menus (spell, word, GTK_WIDGET (menu));
640   g_free (word);
641 }
642 
643 /* when the user right-clicks on a word, they want to check that word.
644  * here, we do NOT  move the cursor to the location of the clicked-upon word
645  * since that prevents the use of edit functions on the context menu. */
646 static gboolean
button_press_event(GtkTextView * view,GdkEventButton * event,GtkSpellChecker * spell)647 button_press_event (GtkTextView *view, GdkEventButton *event, GtkSpellChecker *spell)
648 {
649   g_return_val_if_fail (spell->priv->view == view, FALSE);
650   g_return_val_if_fail (spell->priv->buffer == gtk_text_view_get_buffer (view), FALSE);
651 
652   if (event->button == 3)
653     {
654       gint x, y;
655       GtkTextIter iter;
656 
657       /* handle deferred check if it exists */
658       if (spell->priv->deferred_check)
659         check_deferred_range (spell, TRUE);
660 
661       gtk_text_view_window_to_buffer_coords (view, GTK_TEXT_WINDOW_TEXT,
662                                              event->x, event->y, &x, &y);
663       gtk_text_view_get_iter_at_location (view, &iter, x, y);
664       gtk_text_buffer_move_mark (spell->priv->buffer, spell->priv->mark_click, &iter);
665     }
666   return FALSE; /* false: let gtk process this event, too.
667                  * we don't want to eat any events. */
668 }
669 
670 /* This event occurs when the popup menu is requested through a key-binding
671  * (Menu Key or <shift>+F10 by default).  In this case we want to set
672  * spell->priv->mark_click to the cursor position. */
673 static gboolean
popup_menu_event(GtkTextView * view,GtkSpellChecker * spell)674 popup_menu_event (GtkTextView *view, GtkSpellChecker *spell)
675 {
676   g_return_val_if_fail (spell->priv->view == view, FALSE);
677 
678   GtkTextIter iter;
679 
680   gtk_text_buffer_get_iter_at_mark (spell->priv->buffer, &iter,
681                                    gtk_text_buffer_get_insert (spell->priv->buffer));
682   gtk_text_buffer_move_mark (spell->priv->buffer, spell->priv->mark_click, &iter);
683   return FALSE; /* false: let gtk process this event, too. */
684 }
685 
686 static void
set_lang_from_dict(const char * const lang_tag,const char * const provider_name,const char * const provider_desc,const char * const provider_dll_file,void * user_data)687 set_lang_from_dict (const char * const lang_tag,
688                     const char * const provider_name,
689                     const char * const provider_desc,
690                     const char * const provider_dll_file,
691                     void * user_data)
692 {
693   GtkSpellChecker *spell = user_data;
694 
695   g_free (spell->priv->lang);
696   spell->priv->lang = g_strdup (lang_tag);
697 }
698 
699 static gboolean
set_language_internal(GtkSpellChecker * spell,const gchar * lang,GError ** error)700 set_language_internal (GtkSpellChecker *spell, const gchar *lang, GError **error)
701 {
702   EnchantDict *dict;
703 
704   if (lang == NULL)
705     {
706       lang = g_getenv ("LANG");
707       if (lang)
708         {
709           if ((strcmp (lang, "C") == 0) || (strcmp (lang, "c") == 0))
710             lang = NULL;
711           else if (lang[0] == 0)
712             lang = NULL;
713         }
714     }
715 
716   if (!lang)
717     lang = "en";
718 
719   dict = enchant_broker_request_dict (broker, lang);
720 
721   if (!dict)
722     {
723       g_set_error (error, GTK_SPELL_ERROR, GTK_SPELL_ERROR_BACKEND,
724                    _("enchant error for language: %s"), lang);
725       return FALSE;
726     }
727 
728   if (spell->priv->speller)
729     enchant_broker_free_dict (broker, spell->priv->speller);
730   spell->priv->speller = dict;
731 
732   enchant_dict_describe (dict, set_lang_from_dict, spell);
733 
734   return TRUE;
735 }
736 
737 /* changes the buffer
738  * a NULL buffer is acceptable and will only release the current one */
739 static void
set_buffer(GtkSpellChecker * spell,GtkTextBuffer * buffer)740 set_buffer (GtkSpellChecker *spell, GtkTextBuffer *buffer)
741 {
742   GtkTextIter start, end;
743 
744   if (spell->priv->buffer)
745     {
746       g_signal_handlers_disconnect_matched (spell->priv->buffer, G_SIGNAL_MATCH_DATA,
747                                             0, 0, NULL, NULL, spell);
748 
749       gtk_text_buffer_get_bounds (spell->priv->buffer, &start, &end);
750       gtk_text_buffer_remove_tag (spell->priv->buffer, spell->priv->tag_highlight,
751                                   &start, &end);
752       spell->priv->tag_highlight = NULL;
753 
754       gtk_text_buffer_delete_mark (spell->priv->buffer, spell->priv->mark_insert_start);
755       spell->priv->mark_insert_start = NULL;
756       gtk_text_buffer_delete_mark (spell->priv->buffer, spell->priv->mark_insert_end);
757       spell->priv->mark_insert_end = NULL;
758       gtk_text_buffer_delete_mark (spell->priv->buffer, spell->priv->mark_click);
759       spell->priv->mark_click = NULL;
760 
761       g_object_unref (spell->priv->buffer);
762     }
763 
764   spell->priv->buffer = buffer;
765 
766   if (spell->priv->buffer)
767     {
768       g_object_ref (spell->priv->buffer);
769 
770       g_signal_connect (spell->priv->buffer, "insert-text",
771                         G_CALLBACK (insert_text_before), spell);
772       g_signal_connect_after (spell->priv->buffer, "insert-text",
773                         G_CALLBACK (insert_text_after), spell);
774       g_signal_connect_after (spell->priv->buffer, "delete-range",
775                         G_CALLBACK (delete_range_after), spell);
776       g_signal_connect (spell->priv->buffer, "mark-set",
777                         G_CALLBACK (mark_set), spell);
778 
779       GtkTextTagTable *tagtable = gtk_text_buffer_get_tag_table (spell->priv->buffer);
780       spell->priv->tag_highlight = gtk_text_tag_table_lookup (tagtable,
781                                                      GTK_SPELL_MISSPELLED_TAG);
782 
783       if (spell->priv->tag_highlight == NULL)
784         {
785           spell->priv->tag_highlight = gtk_text_buffer_create_tag (spell->priv->buffer,
786                                          GTK_SPELL_MISSPELLED_TAG, "underline",
787                                          PANGO_UNDERLINE_ERROR, NULL);
788         }
789 
790       /* we create the mark here, but we don't use it until text is
791        * inserted, so we don't really care where iter points.  */
792       gtk_text_buffer_get_bounds (spell->priv->buffer, &start, &end);
793       spell->priv->mark_insert_start = gtk_text_buffer_create_mark (spell->priv->buffer,
794                                         "gtkspell-insert-start", &start, TRUE);
795       spell->priv->mark_insert_end = gtk_text_buffer_create_mark (spell->priv->buffer,
796                                           "gtkspell-insert-end", &start, TRUE);
797       spell->priv->mark_click = gtk_text_buffer_create_mark (spell->priv->buffer,
798                                                "gtkspell-click", &start, TRUE);
799 
800       spell->priv->deferred_check = FALSE;
801 
802       /* now check the entire text buffer. */
803       gtk_spell_checker_recheck_all (spell);
804     }
805 }
806 
807 static void
buffer_changed(GtkTextView * view,GParamSpec * pspec,GtkSpellChecker * spell)808 buffer_changed (GtkTextView *view, GParamSpec *pspec, GtkSpellChecker *spell)
809 {
810   g_return_if_fail (spell->priv->view == view);
811 
812   GtkTextBuffer *buf = gtk_text_view_get_buffer (view);
813   if (!buf)
814     gtk_spell_checker_detach (spell);
815   else
816     set_buffer (spell, buf);
817 }
818 
819 static void
gtk_spell_checker_set_property(GObject * object,guint propid,const GValue * value,GParamSpec * pspec)820 gtk_spell_checker_set_property (GObject *object,
821                                 guint propid,
822                                 const GValue *value,
823                                 GParamSpec *pspec)
824 {
825   GtkSpellChecker *spell = GTK_SPELL_CHECKER (object);
826 
827   switch (propid)
828     {
829     case PROP_DECODE_LANGUAGE_CODES:
830       spell->priv->decode_codes = g_value_get_boolean (value);
831       break;
832     default:
833       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, propid, pspec);
834       break;
835     }
836 }
837 
838 static void
gtk_spell_checker_get_property(GObject * object,guint propid,GValue * value,GParamSpec * pspec)839 gtk_spell_checker_get_property (GObject *object,
840                                 guint propid,
841                                 GValue *value,
842                                 GParamSpec *pspec)
843 {
844   GtkSpellChecker *spell = GTK_SPELL_CHECKER (object);
845 
846   switch (propid)
847     {
848     case PROP_DECODE_LANGUAGE_CODES:
849       g_value_set_boolean (value, spell->priv->decode_codes);
850       break;
851     default:
852       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, propid, pspec);
853       break;
854     }
855 }
856 
857 static void
gtk_spell_checker_class_init(GtkSpellCheckerClass * klass)858 gtk_spell_checker_class_init (GtkSpellCheckerClass *klass)
859 {
860   g_type_class_add_private (klass, sizeof (GtkSpellCheckerPrivate));
861   GObjectClass *object_class = G_OBJECT_CLASS (klass);
862   object_class->dispose = gtk_spell_checker_dispose;
863   object_class->finalize = gtk_spell_checker_finalize;
864   object_class->set_property = gtk_spell_checker_set_property;
865   object_class->get_property = gtk_spell_checker_get_property;
866 
867   /**
868    * GtkSpellChecker::language-changed:
869    * @spell: the #GtkSpellChecker object which received the signal.
870    * @lang: the new language which was selected.
871    *
872    * The ::language-changed signal is emitted when the user selects
873    * a new spelling language from the context menu.
874    *
875    */
876   signals[LANGUAGE_CHANGED] = g_signal_new ("language-changed",
877                       G_OBJECT_CLASS_TYPE (object_class),
878                       G_SIGNAL_RUN_LAST,
879                       G_STRUCT_OFFSET (GtkSpellCheckerClass, language_changed),
880                       NULL, NULL,
881                       g_cclosure_marshal_VOID__STRING,
882                       G_TYPE_NONE,
883                       1,
884                       G_TYPE_STRING | G_SIGNAL_TYPE_STATIC_SCOPE);
885 
886   /**
887    * GtkSpellChecker::decode-language-codes:
888    *
889    * Whether to show decoded language codes in the context menu
890    * (requires the iso-codes package).
891    */
892   g_object_class_install_property (object_class, PROP_DECODE_LANGUAGE_CODES,
893         g_param_spec_boolean ("decode-language-codes",
894                               "Decode language codes",
895                               "Whether to show decoded language codes in the "\
896                               "context menu (requires the iso-codes package).",
897                               FALSE,
898                               G_PARAM_READWRITE));
899 }
900 
901 static void
gtk_spell_checker_init(GtkSpellChecker * self)902 gtk_spell_checker_init (GtkSpellChecker *self)
903 {
904   self->priv = GTK_SPELL_CHECKER_GET_PRIVATE (self);
905   self->priv->view = NULL;
906   self->priv->buffer = NULL;
907   self->priv->tag_highlight = NULL;
908   self->priv->mark_insert_start = NULL;
909   self->priv->mark_insert_end = NULL;
910   self->priv->mark_click = NULL;
911   self->priv->deferred_check = FALSE;
912   self->priv->speller = NULL;
913   self->priv->lang = NULL;
914 
915 #ifdef ENABLE_NLS
916   bindtextdomain (PACKAGE_NAME, PACKAGE_LOCALE_DIR);
917   bind_textdomain_codeset (PACKAGE_NAME, "UTF-8");
918 #endif
919 
920   if (!broker)
921     {
922       broker = enchant_broker_init ();
923       broker_ref_cnt = 0;
924     }
925   broker_ref_cnt++;
926 
927 #ifdef HAVE_ISO_CODES
928   if (codetable_ref_cnt == 0)
929     codetable_init ();
930   codetable_ref_cnt++;
931 #endif
932 
933   set_language_internal (self, NULL, NULL);
934 }
935 
936 static void
gtk_spell_checker_dispose(GObject * object)937 gtk_spell_checker_dispose (GObject *object)
938 {
939   GtkSpellChecker *spell = GTK_SPELL_CHECKER (object);
940 
941   gtk_spell_checker_detach (spell);
942 
943   G_INITIALLY_UNOWNED_CLASS (gtk_spell_checker_parent_class)->dispose (object);
944 }
945 
946 static void
gtk_spell_checker_finalize(GObject * object)947 gtk_spell_checker_finalize (GObject *object)
948 {
949   GtkSpellChecker *spell = GTK_SPELL_CHECKER (object);
950 
951   if (broker)
952     {
953       if (spell->priv->speller)
954         enchant_broker_free_dict (broker, spell->priv->speller);
955       broker_ref_cnt--;
956       if (broker_ref_cnt == 0)
957         {
958           enchant_broker_free (broker);
959           broker = NULL;
960         }
961 
962 #ifdef HAVE_ISO_CODES
963       codetable_ref_cnt--;
964       if (codetable_ref_cnt == 0)
965         codetable_free ();
966 #endif
967 
968     }
969 
970   g_free (spell->priv->lang);
971 
972   G_INITIALLY_UNOWNED_CLASS (gtk_spell_checker_parent_class)->finalize (object);
973 }
974 
975 /**
976  * gtk_spell_checker_new:
977  *
978  * Create a new #GtkSpellChecker object.
979  *
980  * Returns: a new #GtkSpellChecker object.
981  */
982 GtkSpellChecker*
gtk_spell_checker_new(void)983 gtk_spell_checker_new (void)
984 {
985   return g_object_new (GTK_SPELL_TYPE_CHECKER, NULL);
986 }
987 
988 /**
989  * gtk_spell_checker_attach:
990  * @spell: A #GtkSpellChecker.
991  * @view: The #GtkTextView to attach to.
992  *
993  * Attach #GtkSpellChecker object to @view.
994  *
995  * Note: Please read the tutorial section of the documentation to make sure
996  * you don't leak references!
997  *
998  * Returns: TRUE on success, FALSE on failure.
999  */
1000 gboolean
gtk_spell_checker_attach(GtkSpellChecker * spell,GtkTextView * view)1001 gtk_spell_checker_attach (GtkSpellChecker *spell, GtkTextView *view)
1002 {
1003   g_return_val_if_fail (GTK_SPELL_IS_CHECKER (spell), FALSE);
1004   g_return_val_if_fail (GTK_IS_TEXT_VIEW (view), FALSE);
1005   g_return_val_if_fail (gtk_text_view_get_buffer (view), FALSE);
1006   g_return_val_if_fail (spell->priv->view == NULL, FALSE);
1007 
1008   /* ensure no existing instance is attached */
1009   GtkSpellChecker *attached;
1010   attached = g_object_get_data (G_OBJECT (view), GTK_SPELL_OBJECT_KEY);
1011   g_return_val_if_fail (attached == NULL, FALSE);
1012 
1013   /* attach to the widget */
1014   spell->priv->view = view;
1015   g_object_ref (view);
1016   g_object_ref_sink (spell);
1017 
1018   g_object_set_data (G_OBJECT (view), GTK_SPELL_OBJECT_KEY, spell);
1019 
1020   g_signal_connect_swapped (view, "destroy",
1021                             G_CALLBACK (gtk_spell_checker_detach), spell);
1022   g_signal_connect (view, "button-press-event",
1023                     G_CALLBACK (button_press_event), spell);
1024   g_signal_connect (view, "populate-popup",
1025                     G_CALLBACK (populate_popup), spell);
1026   g_signal_connect (view, "popup-menu",
1027                     G_CALLBACK (popup_menu_event), spell);
1028   g_signal_connect (view, "notify::buffer",
1029                     G_CALLBACK (buffer_changed), spell);
1030 
1031   set_buffer (spell, gtk_text_view_get_buffer (view));
1032 
1033   return TRUE;
1034 }
1035 
1036 /**
1037  * gtk_spell_checker_detach:
1038  * @spell: A #GtkSpellChecker.
1039  *
1040  * Detaches this #GtkSpellChecker from its #GtkTextView.  Use
1041  * gtk_spell_checker_get_from_text_view () to retrieve a #GtkSpellChecker from
1042  * a #GtkTextView. If the #GtkSpellChecker is not attached to any #GtkTextView,
1043  * the function silently exits.
1044  *
1045  * Note: if the #GtkSpellChecker is owned by the #GtkTextView, you must
1046  * take a reference to it to prevent it from being automatically destroyed.
1047  * Please read the tutorial section of the documentation!
1048  */
1049 void
gtk_spell_checker_detach(GtkSpellChecker * spell)1050 gtk_spell_checker_detach (GtkSpellChecker *spell)
1051 {
1052   g_return_if_fail (GTK_SPELL_IS_CHECKER (spell));
1053   if (spell->priv->view == NULL)
1054     return;
1055 
1056   g_signal_handlers_disconnect_matched (spell->priv->view, G_SIGNAL_MATCH_DATA,
1057         0, 0, NULL, NULL, spell);
1058 
1059   g_object_set_data (G_OBJECT (spell->priv->view), GTK_SPELL_OBJECT_KEY, NULL);
1060 
1061   g_object_unref (spell->priv->view);
1062   spell->priv->view = NULL;
1063   set_buffer (spell, NULL);
1064   spell->priv->deferred_check = FALSE;
1065   g_object_unref (spell);
1066 }
1067 
1068 /**
1069  * gtk_spell_checker_get_from_text_view:
1070  * @view: A #GtkTextView.
1071  *
1072  * Retrieves the #GtkSpellChecker object attached to a text view.
1073  *
1074  * Returns: (transfer none): the #GtkSpellChecker object, or %NULL if there is no #GtkSpellChecker
1075  * attached to @view.
1076  */
1077 GtkSpellChecker*
gtk_spell_checker_get_from_text_view(GtkTextView * view)1078 gtk_spell_checker_get_from_text_view (GtkTextView *view)
1079 {
1080   g_return_val_if_fail (GTK_IS_TEXT_VIEW (view), NULL);
1081   return g_object_get_data (G_OBJECT (view), GTK_SPELL_OBJECT_KEY);
1082 }
1083 
1084 /**
1085  * gtk_spell_checker_get_language:
1086  * @spell: a #GtkSpellChecker
1087  *
1088  * Fetches the current language.
1089  *
1090  * Returns: the current language. This string is
1091  * owned by the spell object and must not be modified or freed.
1092  **/
1093 const gchar*
gtk_spell_checker_get_language(GtkSpellChecker * spell)1094 gtk_spell_checker_get_language (GtkSpellChecker *spell)
1095 {
1096   g_return_val_if_fail (GTK_SPELL_IS_CHECKER (spell), NULL);
1097 
1098   return spell->priv->lang;
1099 }
1100 
1101 /**
1102  * gtk_spell_checker_get_language_list:
1103  *
1104  * Requests the list of available languages from the enchant broker.
1105  *
1106  * Returns: (transfer full) (element-type utf8): a #GList of the available languages.
1107  * Use g_list_free_full with g_free to free the list after use.
1108  *
1109  * Since: 3.0.3
1110  */
1111 GList*
gtk_spell_checker_get_language_list(void)1112 gtk_spell_checker_get_language_list (void)
1113 {
1114   struct _languages_cb_struct languages_cb_struct;
1115 
1116   if (!broker)
1117     {
1118       broker = enchant_broker_init();
1119       broker_ref_cnt = 0;
1120     }
1121 
1122   languages_cb_struct.langs = NULL;
1123   enchant_broker_list_dicts(broker, dict_describe_cb, &languages_cb_struct);
1124 
1125   if (broker_ref_cnt == 0)
1126     {
1127       enchant_broker_free (broker);
1128       broker = NULL;
1129     }
1130 
1131   return languages_cb_struct.langs;
1132 }
1133 
1134 /**
1135  * gtk_spell_checker_decode_language_code:
1136  * @lang: The language locale specifier (i.e. "en_US").
1137  *
1138  * Translates the language code to a human readable format
1139  * (i.e. "en_US" -> "English (United States)").
1140  * Note: If the iso-codes package is not available, the unchanged code is
1141  * returned.
1142  *
1143  * Returns: (transfer full): The translated language specifier. Use g_free to
1144  * free the returned string after use.
1145  *
1146  * Since: 3.0.3
1147  */
1148 gchar*
gtk_spell_checker_decode_language_code(const gchar * lang)1149 gtk_spell_checker_decode_language_code (const gchar *lang)
1150 {
1151   gchar* result;
1152 #ifdef HAVE_ISO_CODES
1153   const gchar *lang_name = "\0";
1154   const gchar *country_name = "\0";
1155   if (codetable_ref_cnt == 0)
1156     codetable_init ();
1157   codetable_lookup (lang, &lang_name, &country_name);
1158   if (strlen (country_name) != 0)
1159     result = g_strdup_printf ("%s (%s)", lang_name, country_name);
1160   else
1161     result = g_strdup_printf ("%s", lang_name);
1162   if (codetable_ref_cnt == 0)
1163     codetable_free ();
1164 #else
1165   result = g_strdup (lang);
1166 #endif
1167   return result;
1168 }
1169 
1170 /**
1171  * gtk_spell_checker_set_language:
1172  * @spell: The #GtkSpellChecker object.
1173  * @lang: (allow-none): The language to use, as a locale specifier (i.e. "en_US").
1174  * If #NULL, attempt to use the default system locale (LANG).
1175  * @error: (out) (allow-none): Return location for error.
1176  *
1177  * Set the language on @spell to @lang, possibily returning an error in
1178  * @error.
1179  *
1180  * Returns: FALSE if there was an error.
1181  */
1182 gboolean
gtk_spell_checker_set_language(GtkSpellChecker * spell,const gchar * lang,GError ** error)1183 gtk_spell_checker_set_language (GtkSpellChecker *spell, const gchar *lang, GError **error)
1184 {
1185   g_return_val_if_fail (GTK_SPELL_IS_CHECKER (spell), FALSE);
1186 
1187   if (error)
1188     g_return_val_if_fail (*error == NULL, FALSE);
1189 
1190   gboolean ret = set_language_internal (spell, lang, error);
1191   if (ret)
1192     gtk_spell_checker_recheck_all (spell);
1193 
1194   return ret;
1195 }
1196 
1197 /**
1198  * gtk_spell_checker_check_word:
1199  * @spell: The #GtkSpellChecker object.
1200  * @word: The word to check.
1201  *
1202  * Check the specified word.
1203  *
1204  * Returns: TRUE if the word is correctly spelled, FALSE otherwise.
1205  *
1206  * Since: 3.0.8
1207  */
1208 gboolean
gtk_spell_checker_check_word(GtkSpellChecker * spell,const gchar * word)1209 gtk_spell_checker_check_word (GtkSpellChecker *spell, const gchar *word)
1210 {
1211   if (g_unichar_isdigit (*word) == TRUE || /* don't check numbers */
1212       enchant_dict_check (spell->priv->speller, word, strlen (word)) == 0)
1213     return TRUE;
1214   return FALSE;
1215 }
1216 
1217 /**
1218  * gtk_spell_checker_recheck_all:
1219  * @spell: The #GtkSpellChecker object.
1220  *
1221  * Recheck the spelling in the entire buffer.
1222  */
1223 void
gtk_spell_checker_recheck_all(GtkSpellChecker * spell)1224 gtk_spell_checker_recheck_all (GtkSpellChecker *spell)
1225 {
1226   g_return_if_fail (GTK_SPELL_IS_CHECKER (spell));
1227 
1228   GtkTextIter start, end;
1229 
1230   if (spell->priv->buffer)
1231     {
1232       gtk_text_buffer_get_bounds (spell->priv->buffer, &start, &end);
1233       check_range (spell, start, end, TRUE);
1234     }
1235 }
1236 
1237 /**
1238  * gtk_spell_checker_add_to_dictionary:
1239  * @spell: The #GtkSpellChecker object.
1240  * @word:  The word to add to the user dictionary.
1241  *
1242  * Add the specified word to the user dictionary.
1243  *
1244  * Since: 3.0.9
1245  */
1246 void
gtk_spell_checker_add_to_dictionary(GtkSpellChecker * spell,const gchar * word)1247 gtk_spell_checker_add_to_dictionary (GtkSpellChecker *spell, const gchar *word)
1248 {
1249   enchant_dict_add (spell->priv->speller, word, strlen (word));
1250   gtk_spell_checker_recheck_all (spell);
1251 }
1252 
1253 /**
1254  * gtk_spell_checker_ignore_word:
1255  * @spell: The #GtkSpellChecker object.
1256  * @word:  The word to add to the user ignore list.
1257  *
1258  * Add the specified word to the user ignore list.
1259  *
1260  * Since: 3.0.9
1261  */
1262 void
gtk_spell_checker_ignore_word(GtkSpellChecker * spell,const gchar * word)1263 gtk_spell_checker_ignore_word (GtkSpellChecker *spell, const gchar *word)
1264 {
1265   enchant_dict_add_to_session (spell->priv->speller, word, strlen (word));
1266   gtk_spell_checker_recheck_all (spell);
1267 }
1268 
1269 /**
1270  * gtk_spell_checker_get_suggestions:
1271  * @spell: A #GtkSpellChecker.
1272  * @word: The word for which to fetch suggestions
1273  *
1274  * Retreives a list of spelling suggestions for the specified word.
1275  *
1276  * Returns: (transfer full) (element-type utf8): the list of spelling
1277  * suggestions for the specified word, or NULL if there are no suggestions.
1278  *
1279  * Since: 3.0.8
1280  */
1281 GList*
gtk_spell_checker_get_suggestions(GtkSpellChecker * spell,const gchar * word)1282 gtk_spell_checker_get_suggestions (GtkSpellChecker *spell, const gchar* word)
1283 {
1284   char **suggestions;
1285   size_t n_suggs, i;
1286   GList* result = NULL;
1287   suggestions = enchant_dict_suggest (spell->priv->speller, word,
1288                                       strlen (word), &n_suggs);
1289   for (i = 0; i < n_suggs; ++i)
1290     {
1291       result = g_list_append (result, g_strdup (suggestions[i]));
1292     }
1293   return result;
1294 }
1295 
1296 /**
1297  * gtk_spell_checker_get_suggestions_menu:
1298  * @spell: A #GtkSpellChecker.
1299  * @iter: Textiter of position in buffer to be corrected if necessary.
1300  *
1301  * Retrieves a submenu of replacement spellings, or NULL if the word at @iter is
1302  * not misspelt.
1303  *
1304  * Returns: (transfer full): the #GtkMenu widget, or %NULL if there is no need for a menu
1305  */
1306 GtkWidget*
gtk_spell_checker_get_suggestions_menu(GtkSpellChecker * spell,GtkTextIter * iter)1307 gtk_spell_checker_get_suggestions_menu (GtkSpellChecker *spell, GtkTextIter *iter)
1308 {
1309   g_return_val_if_fail (GTK_SPELL_IS_CHECKER (spell), NULL);
1310   g_return_val_if_fail (iter != NULL, NULL);
1311 
1312   GtkWidget *submenu = NULL;
1313   GtkTextIter start, end;
1314 
1315   start = *iter;
1316   /* use the same lazy test, with same risk, as does the default menu arrangement */
1317   if (gtk_text_iter_has_tag (&start, spell->priv->tag_highlight))
1318     {
1319       /* word was mis-spelt */
1320       gchar *badword;
1321       /* in case a fix is requested, move the attention-point */
1322       gtk_text_buffer_move_mark (spell->priv->buffer, spell->priv->mark_click, iter);
1323       if (!gtk_text_iter_starts_word (&start))
1324         gtk_text_iter_backward_word_start (&start);
1325       end = start;
1326       if (gtk_text_iter_inside_word (&end))
1327         gtk_text_iter_forward_word_end (&end);
1328       badword = gtk_text_buffer_get_text (spell->priv->buffer, &start, &end, FALSE);
1329 
1330       submenu = build_suggestion_menu (spell, badword);
1331       gtk_widget_show (submenu);
1332 
1333       g_free (badword);
1334     }
1335   return submenu;
1336 }
1337 
1338 GQuark
gtk_spell_error_quark(void)1339 gtk_spell_error_quark (void)
1340 {
1341   static GQuark q = 0;
1342   if (q == 0)
1343     q = g_quark_from_static_string ("gtkspell-error-quark");
1344   return q;
1345 }
1346 
1347 GType
gtk_spell_error_get_type(void)1348 gtk_spell_error_get_type (void)
1349 {
1350   static GType etype = 0;
1351 
1352   if (G_UNLIKELY(etype == 0)) {
1353     static const GEnumValue values[] = {
1354       { GTK_SPELL_ERROR_BACKEND, "GTK_SPELL_ERROR_BACKEND", "backend" },
1355       { 0, NULL, NULL }
1356     };
1357     etype = g_enum_register_static (g_intern_static_string ("GtkSpellError"), values);
1358   }
1359   return etype;
1360 }
1361 
1362