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