No issues found
1 /*
2 * e-spell-entry.c
3 *
4 * This program is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Lesser General Public
6 * License as published by the Free Software Foundation; either
7 * version 2 of the License, or (at your option) version 3.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Lesser General Public License for more details.
13 *
14 * You should have received a copy of the GNU Lesser General Public
15 * License along with the program; if not, see <http://www.gnu.org/licenses/>
16 *
17 */
18
19 /* This code is based on libsexy's SexySpellEntry */
20
21 #ifdef HAVE_CONFIG_H
22 #include <config.h>
23 #endif
24
25 #include <glib/gi18n-lib.h>
26 #include <gtk/gtk.h>
27
28 #include <editor/gtkhtml-spell-language.h>
29 #include <editor/gtkhtml-spell-checker.h>
30
31 #include "e-spell-entry.h"
32
33 enum {
34 PROP_0,
35 PROP_CHECKING_ENABLED
36 };
37
38 struct _ESpellEntryPrivate
39 {
40 PangoAttrList *attr_list;
41 gint mark_character;
42 gint entry_scroll_offset;
43 GSettings *settings;
44 gboolean custom_checkers;
45 gboolean checking_enabled;
46 GSList *checkers;
47 gchar **words;
48 gint *word_starts;
49 gint *word_ends;
50 };
51
52 #define E_SPELL_ENTRY_GET_PRIVATE(obj) \
53 (G_TYPE_INSTANCE_GET_PRIVATE \
54 ((obj), E_TYPE_SPELL_ENTRY, ESpellEntryPrivate))
55
56 G_DEFINE_TYPE (ESpellEntry, e_spell_entry, GTK_TYPE_ENTRY);
57
58 static gboolean
59 word_misspelled (ESpellEntry *entry,
60 gint start,
61 gint end)
62 {
63 const gchar *text;
64 gchar *word;
65 gboolean result = TRUE;
66
67 if (start == end)
68 return FALSE;
69
70 text = gtk_entry_get_text (GTK_ENTRY (entry));
71 word = g_new0 (gchar, end - start + 2);
72
73 g_strlcpy (word, text + start, end - start + 1);
74
75 if (g_unichar_isalpha (*word)) {
76 GSList *li;
77 gssize wlen = strlen (word);
78
79 for (li = entry->priv->checkers; li; li = g_slist_next (li)) {
80 GtkhtmlSpellChecker *checker = li->data;
81 if (gtkhtml_spell_checker_check_word (checker, word, wlen)) {
82 result = FALSE;
83 break;
84 }
85 }
86 }
87 g_free (word);
88
89 return result;
90 }
91
92 static void
93 insert_underline (ESpellEntry *entry,
94 guint start,
95 guint end)
96 {
97 PangoAttribute *ucolor = pango_attr_underline_color_new (65535, 0, 0);
98 PangoAttribute *unline = pango_attr_underline_new (PANGO_UNDERLINE_ERROR);
99
100 ucolor->start_index = start;
101 unline->start_index = start;
102
103 ucolor->end_index = end;
104 unline->end_index = end;
105
106 pango_attr_list_insert (entry->priv->attr_list, ucolor);
107 pango_attr_list_insert (entry->priv->attr_list, unline);
108 }
109
110 static void
111 check_word (ESpellEntry *entry,
112 gint start,
113 gint end)
114 {
115 PangoAttrIterator *it;
116
117 /* Check to see if we've got any attributes at this position.
118 * If so, free them, since we'll readd it if the word is misspelled */
119 it = pango_attr_list_get_iterator (entry->priv->attr_list);
120
121 if (it == NULL)
122 return;
123 do {
124 gint s, e;
125 pango_attr_iterator_range (it, &s, &e);
126 if (s == start) {
127 GSList *attrs = pango_attr_iterator_get_attrs (it);
128 g_slist_foreach (attrs, (GFunc) pango_attribute_destroy, NULL);
129 g_slist_free (attrs);
130 }
131 } while (pango_attr_iterator_next (it));
132 pango_attr_iterator_destroy (it);
133
134 if (word_misspelled (entry, start, end))
135 insert_underline (entry, start, end);
136 }
137
138 static void
139 spell_entry_recheck_all (ESpellEntry *entry)
140 {
141 GtkWidget *widget = GTK_WIDGET (entry);
142 PangoLayout *layout;
143 gint length, i;
144
145 if (!entry->priv->words)
146 return;
147
148 /* Remove all existing pango attributes. These will get read as we check */
149 pango_attr_list_unref (entry->priv->attr_list);
150 entry->priv->attr_list = pango_attr_list_new ();
151
152 if (entry->priv->checkers && entry->priv->checking_enabled) {
153 /* Loop through words */
154 for (i = 0; entry->priv->words[i]; i++) {
155 length = strlen (entry->priv->words[i]);
156 if (length == 0)
157 continue;
158 check_word (entry, entry->priv->word_starts[i], entry->priv->word_ends[i]);
159 }
160
161 layout = gtk_entry_get_layout (GTK_ENTRY (entry));
162 pango_layout_set_attributes (layout, entry->priv->attr_list);
163 }
164
165 if (gtk_widget_get_realized (widget))
166 gtk_widget_queue_draw (widget);
167 }
168
169 static void
170 get_word_extents_from_position (ESpellEntry *entry,
171 gint *start,
172 gint *end,
173 guint position)
174 {
175 const gchar *text;
176 gint i, bytes_pos;
177
178 *start = -1;
179 *end = -1;
180
181 if (entry->priv->words == NULL)
182 return;
183
184 text = gtk_entry_get_text (GTK_ENTRY (entry));
185 bytes_pos = (gint) (g_utf8_offset_to_pointer (text, position) - text);
186
187 for (i = 0; entry->priv->words[i]; i++) {
188 if (bytes_pos >= entry->priv->word_starts[i] &&
189 bytes_pos <= entry->priv->word_ends[i]) {
190 *start = entry->priv->word_starts[i];
191 *end = entry->priv->word_ends[i];
192 return;
193 }
194 }
195 }
196
197 static void
198 entry_strsplit_utf8 (GtkEntry *entry,
199 gchar ***set,
200 gint **starts,
201 gint **ends)
202 {
203 PangoLayout *layout;
204 PangoLogAttr *log_attrs;
205 const gchar *text;
206 gint n_attrs, n_strings, i, j;
207
208 layout = gtk_entry_get_layout (GTK_ENTRY (entry));
209 text = gtk_entry_get_text (GTK_ENTRY (entry));
210 pango_layout_get_log_attrs (layout, &log_attrs, &n_attrs);
211
212 /* Find how many words we have */
213 n_strings = 0;
214 for (i = 0; i < n_attrs; i++)
215 if (log_attrs[i].is_word_start)
216 n_strings++;
217
218 *set = g_new0 (gchar *, n_strings + 1);
219 *starts = g_new0 (gint, n_strings);
220 *ends = g_new0 (gint, n_strings);
221
222 /* Copy out strings */
223 for (i = 0, j = 0; i < n_attrs; i++) {
224 if (log_attrs[i].is_word_start) {
225 gint cend, bytes;
226 gchar *start;
227
228 /* Find the end of this string */
229 cend = i;
230 while (!(log_attrs[cend].is_word_end))
231 cend++;
232
233 /* Copy sub-string */
234 start = g_utf8_offset_to_pointer (text, i);
235 bytes = (gint) (g_utf8_offset_to_pointer (text, cend) - start);
236 (*set)[j] = g_new0 (gchar, bytes + 1);
237 (*starts)[j] = (gint) (start - text);
238 (*ends)[j] = (gint) (start - text + bytes);
239 g_utf8_strncpy ((*set)[j], start, cend - i);
240
241 /* Move on to the next word */
242 j++;
243 }
244 }
245
246 g_free (log_attrs);
247 }
248
249 static void
250 add_to_dictionary (GtkWidget *menuitem,
251 ESpellEntry *entry)
252 {
253 gchar *word;
254 gint start, end;
255 GtkhtmlSpellChecker *checker;
256
257 get_word_extents_from_position (entry, &start, &end, entry->priv->mark_character);
258 word = gtk_editable_get_chars (GTK_EDITABLE (entry), start, end);
259
260 checker = g_object_get_data (G_OBJECT (menuitem), "spell-entry-checker");
261 if (checker)
262 gtkhtml_spell_checker_add_word (checker, word, -1);
263
264 g_free (word);
265
266 if (entry->priv->words) {
267 g_strfreev (entry->priv->words);
268 g_free (entry->priv->word_starts);
269 g_free (entry->priv->word_ends);
270 }
271
272 entry_strsplit_utf8 (GTK_ENTRY (entry), &entry->priv->words, &entry->priv->word_starts, &entry->priv->word_ends);
273 spell_entry_recheck_all (entry);
274 }
275
276 static void
277 ignore_all (GtkWidget *menuitem,
278 ESpellEntry *entry)
279 {
280 gchar *word;
281 gint start, end;
282 GSList *li;
283
284 get_word_extents_from_position (entry, &start, &end, entry->priv->mark_character);
285 word = gtk_editable_get_chars (GTK_EDITABLE (entry), start, end);
286
287 for (li = entry->priv->checkers; li; li = g_slist_next (li)) {
288 GtkhtmlSpellChecker *checker = li->data;
289 gtkhtml_spell_checker_add_word_to_session (checker, word, -1);
290 }
291
292 g_free (word);
293
294 if (entry->priv->words) {
295 g_strfreev (entry->priv->words);
296 g_free (entry->priv->word_starts);
297 g_free (entry->priv->word_ends);
298 }
299 entry_strsplit_utf8 (GTK_ENTRY (entry), &entry->priv->words, &entry->priv->word_starts, &entry->priv->word_ends);
300 spell_entry_recheck_all (entry);
301 }
302
303 static void
304 replace_word (GtkWidget *menuitem,
305 ESpellEntry *entry)
306 {
307 gchar *oldword;
308 const gchar *newword;
309 gint start, end;
310 gint cursor;
311 GtkhtmlSpellChecker *checker;
312
313 get_word_extents_from_position (entry, &start, &end, entry->priv->mark_character);
314 oldword = gtk_editable_get_chars (GTK_EDITABLE (entry), start, end);
315 newword = gtk_label_get_text (GTK_LABEL (gtk_bin_get_child (GTK_BIN (menuitem))));
316
317 cursor = gtk_editable_get_position (GTK_EDITABLE (entry));
318 /* is the cursor at the end? If so, restore it there */
319 if (g_utf8_strlen (gtk_entry_get_text (GTK_ENTRY (entry)), -1) == cursor)
320 cursor = -1;
321 else if (cursor > start && cursor <= end)
322 cursor = start;
323
324 gtk_editable_delete_text (GTK_EDITABLE (entry), start, end);
325 gtk_editable_set_position (GTK_EDITABLE (entry), start);
326 gtk_editable_insert_text (
327 GTK_EDITABLE (entry), newword, strlen (newword),
328 &start);
329 gtk_editable_set_position (GTK_EDITABLE (entry), cursor);
330
331 checker = g_object_get_data (G_OBJECT (menuitem), "spell-entry-checker");
332
333 if (checker)
334 gtkhtml_spell_checker_store_replacement (checker, oldword, -1, newword, -1);
335
336 g_free (oldword);
337 }
338
339 static void
340 build_suggestion_menu (ESpellEntry *entry,
341 GtkWidget *menu,
342 GtkhtmlSpellChecker *checker,
343 const gchar *word)
344 {
345 GtkWidget *mi;
346 GList *suggestions, *iter;
347
348 suggestions = gtkhtml_spell_checker_get_suggestions (checker, word, -1);
349
350 if (!suggestions) {
351 /* no suggestions. Put something in the menu anyway... */
352 GtkWidget *label = gtk_label_new (_("(no suggestions)"));
353 PangoAttribute *attribute;
354 PangoAttrList *attribute_list;
355
356 attribute_list = pango_attr_list_new ();
357 attribute = pango_attr_style_new (PANGO_STYLE_ITALIC);
358 pango_attr_list_insert (attribute_list, attribute);
359 gtk_label_set_attributes (GTK_LABEL (label), attribute_list);
360 pango_attr_list_unref (attribute_list);
361
362 mi = gtk_separator_menu_item_new ();
363 gtk_container_add (GTK_CONTAINER (mi), label);
364 gtk_widget_show_all (mi);
365 gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), mi);
366 } else {
367 gint ii = 0;
368
369 /* build a set of menus with suggestions */
370 for (iter = suggestions; iter; iter = g_list_next (iter), ii++) {
371 if ((ii != 0) && (ii % 10 == 0)) {
372 mi = gtk_separator_menu_item_new ();
373 gtk_widget_show (mi);
374 gtk_menu_shell_append (GTK_MENU_SHELL (menu), mi);
375
376 mi = gtk_menu_item_new_with_label (_("More..."));
377 gtk_widget_show (mi);
378 gtk_menu_shell_append (GTK_MENU_SHELL (menu), mi);
379
380 menu = gtk_menu_new ();
381 gtk_menu_item_set_submenu (GTK_MENU_ITEM (mi), menu);
382 }
383
384 mi = gtk_menu_item_new_with_label (iter->data);
385 g_object_set_data (G_OBJECT (mi), "spell-entry-checker", checker);
386 g_signal_connect (mi, "activate", G_CALLBACK (replace_word), entry);
387 gtk_widget_show (mi);
388 gtk_menu_shell_append (GTK_MENU_SHELL (menu), mi);
389 }
390 }
391
392 g_list_free_full (suggestions, g_free);
393 }
394
395 static GtkWidget *
396 build_spelling_menu (ESpellEntry *entry,
397 const gchar *word)
398 {
399 GtkhtmlSpellChecker *checker;
400 GtkWidget *topmenu, *mi;
401 gchar *label;
402
403 topmenu = gtk_menu_new ();
404
405 if (!entry->priv->checkers)
406 return topmenu;
407
408 /* Suggestions */
409 if (!entry->priv->checkers->next) {
410 checker = entry->priv->checkers->data;
411 build_suggestion_menu (entry, topmenu, checker, word);
412 } else {
413 GSList *li;
414 GtkWidget *menu;
415 const gchar *lang_name;
416
417 for (li = entry->priv->checkers; li; li = g_slist_next (li)) {
418 const GtkhtmlSpellLanguage *language;
419
420 checker = li->data;
421 language = gtkhtml_spell_checker_get_language (checker);
422 if (!language)
423 continue;
424
425 lang_name = gtkhtml_spell_language_get_name (language);
426 if (!lang_name)
427 lang_name = gtkhtml_spell_language_get_code (language);
428
429 mi = gtk_menu_item_new_with_label (lang_name ? lang_name : "???");
430
431 gtk_widget_show (mi);
432 gtk_menu_shell_append (GTK_MENU_SHELL (topmenu), mi);
433 menu = gtk_menu_new ();
434 gtk_menu_item_set_submenu (GTK_MENU_ITEM (mi), menu);
435 build_suggestion_menu (entry, menu, checker, word);
436 }
437 }
438
439 /* Separator */
440 mi = gtk_separator_menu_item_new ();
441 gtk_widget_show (mi);
442 gtk_menu_shell_append (GTK_MENU_SHELL (topmenu), mi);
443
444 /* + Add to Dictionary */
445 label = g_strdup_printf (_("Add \"%s\" to Dictionary"), word);
446 mi = gtk_image_menu_item_new_with_label (label);
447 g_free (label);
448
449 gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (mi), gtk_image_new_from_stock (GTK_STOCK_ADD, GTK_ICON_SIZE_MENU));
450
451 if (!entry->priv->checkers->next) {
452 checker = entry->priv->checkers->data;
453 g_object_set_data (G_OBJECT (mi), "spell-entry-checker", checker);
454 g_signal_connect (mi, "activate", G_CALLBACK (add_to_dictionary), entry);
455 } else {
456 GSList *li;
457 GtkWidget *menu, *submi;
458 const gchar *lang_name;
459
460 menu = gtk_menu_new ();
461 gtk_menu_item_set_submenu (GTK_MENU_ITEM (mi), menu);
462
463 for (li = entry->priv->checkers; li; li = g_slist_next (li)) {
464 const GtkhtmlSpellLanguage *language;
465
466 checker = li->data;
467 language = gtkhtml_spell_checker_get_language (checker);
468 if (!language)
469 continue;
470
471 lang_name = gtkhtml_spell_language_get_name (language);
472 if (!lang_name)
473 lang_name = gtkhtml_spell_language_get_code (language);
474
475 submi = gtk_menu_item_new_with_label (lang_name ? lang_name : "???");
476 g_object_set_data (G_OBJECT (submi), "spell-entry-checker", checker);
477 g_signal_connect (submi, "activate", G_CALLBACK (add_to_dictionary), entry);
478
479 gtk_widget_show (submi);
480 gtk_menu_shell_append (GTK_MENU_SHELL (menu), submi);
481 }
482 }
483
484 gtk_widget_show_all (mi);
485 gtk_menu_shell_append (GTK_MENU_SHELL (topmenu), mi);
486
487 /* - Ignore All */
488 mi = gtk_image_menu_item_new_with_label (_("Ignore All"));
489 gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (mi), gtk_image_new_from_stock (GTK_STOCK_REMOVE, GTK_ICON_SIZE_MENU));
490 g_signal_connect (mi, "activate", G_CALLBACK (ignore_all), entry);
491 gtk_widget_show_all (mi);
492 gtk_menu_shell_append (GTK_MENU_SHELL (topmenu), mi);
493
494 return topmenu;
495 }
496
497 static void
498 spell_entry_add_suggestions_menu (ESpellEntry *entry,
499 GtkMenu *menu,
500 const gchar *word)
501 {
502 GtkWidget *icon, *mi;
503
504 g_return_if_fail (menu != NULL);
505 g_return_if_fail (word != NULL);
506
507 /* separator */
508 mi = gtk_separator_menu_item_new ();
509 gtk_widget_show (mi);
510 gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), mi);
511
512 /* Above the separator, show the suggestions menu */
513 icon = gtk_image_new_from_stock (GTK_STOCK_SPELL_CHECK, GTK_ICON_SIZE_MENU);
514 mi = gtk_image_menu_item_new_with_label (_("Spelling Suggestions"));
515 gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (mi), icon);
516
517 gtk_menu_item_set_submenu (GTK_MENU_ITEM (mi), build_spelling_menu (entry, word));
518
519 gtk_widget_show_all (mi);
520 gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), mi);
521 }
522
523 static gboolean
524 spell_entry_popup_menu (ESpellEntry *entry)
525 {
526 /* Menu popped up from a keybinding (menu key or <shift>+F10). Use
527 * the cursor position as the mark position */
528 entry->priv->mark_character = gtk_editable_get_position (GTK_EDITABLE (entry));
529
530 return FALSE;
531 }
532
533 static void
534 spell_entry_populate_popup (ESpellEntry *entry,
535 GtkMenu *menu,
536 gpointer data)
537 {
538 gint start, end;
539 gchar *word;
540
541 if (!entry->priv->checkers)
542 return;
543
544 get_word_extents_from_position (entry, &start, &end, entry->priv->mark_character);
545 if (start == end)
546 return;
547
548 if (!word_misspelled (entry, start, end))
549 return;
550
551 word = gtk_editable_get_chars (GTK_EDITABLE (entry), start, end);
552 g_return_if_fail (word != NULL);
553
554 spell_entry_add_suggestions_menu (entry, menu, word);
555
556 g_free (word);
557 }
558
559 static void
560 spell_entry_changed (GtkEditable *editable)
561 {
562 ESpellEntry *entry = E_SPELL_ENTRY (editable);
563
564 if (!entry->priv->checkers)
565 return;
566
567 if (entry->priv->words) {
568 g_strfreev (entry->priv->words);
569 g_free (entry->priv->word_starts);
570 g_free (entry->priv->word_ends);
571 }
572 entry_strsplit_utf8 (GTK_ENTRY (entry), &entry->priv->words, &entry->priv->word_starts, &entry->priv->word_ends);
573 spell_entry_recheck_all (entry);
574 }
575
576 static void
577 spell_entry_notify_scroll_offset (ESpellEntry *spell_entry)
578 {
579 g_return_if_fail (spell_entry != NULL);
580
581 g_object_get (G_OBJECT (spell_entry), "scroll-offset", &spell_entry->priv->entry_scroll_offset, NULL);
582 }
583
584 static GList *
585 spell_entry_load_spell_languages (void)
586 {
587 GSettings *settings;
588 GList *spell_languages = NULL;
589 gchar **strv;
590 gint ii;
591
592 /* Ask GSettings for a list of spell check language codes. */
593 settings = g_settings_new ("org.gnome.evolution.mail");
594 strv = g_settings_get_strv (settings, "composer-spell-languages");
595 g_object_unref (settings);
596
597 /* Convert the codes to spell language structs. */
598 for (ii = 0; strv[ii] != NULL; ii++) {
599 gchar *language_code = strv[ii];
600 const GtkhtmlSpellLanguage *language;
601
602 language = gtkhtml_spell_language_lookup (language_code);
603 if (language != NULL)
604 spell_languages = g_list_prepend (
605 spell_languages, (gpointer) language);
606 }
607
608 g_strfreev (strv);
609
610 spell_languages = g_list_reverse (spell_languages);
611
612 /* Pick a default spell language if it came back empty. */
613 if (spell_languages == NULL) {
614 const GtkhtmlSpellLanguage *language;
615
616 language = gtkhtml_spell_language_lookup (NULL);
617
618 if (language) {
619 spell_languages = g_list_prepend (
620 spell_languages, (gpointer) language);
621 }
622 }
623
624 return spell_languages;
625 }
626
627 static void
628 spell_entry_settings_changed (ESpellEntry *spell_entry,
629 const gchar *key)
630 {
631 GList *languages;
632
633 g_return_if_fail (spell_entry != NULL);
634
635 if (spell_entry->priv->custom_checkers)
636 return;
637
638 if (key && !g_str_equal (key, "composer-spell-languages"))
639 return;
640
641 languages = spell_entry_load_spell_languages ();
642 e_spell_entry_set_languages (spell_entry, languages);
643 g_list_free (languages);
644
645 spell_entry->priv->custom_checkers = FALSE;
646 }
647
648 static gint
649 spell_entry_find_position (ESpellEntry *spell_entry,
650 gint x)
651 {
652 PangoLayout *layout;
653 PangoLayoutLine *line;
654 gint index;
655 gint pos;
656 gint trailing;
657 const gchar *text;
658 GtkEntry *entry = GTK_ENTRY (spell_entry);
659
660 layout = gtk_entry_get_layout (entry);
661 text = pango_layout_get_text (layout);
662
663 line = pango_layout_get_lines_readonly (layout)->data;
664 pango_layout_line_x_to_index (line, x * PANGO_SCALE, &index, &trailing);
665
666 pos = g_utf8_pointer_to_offset (text, text + index);
667 pos += trailing;
668
669 return pos;
670 }
671
672 static gboolean
673 e_spell_entry_draw (GtkWidget *widget,
674 cairo_t *cr)
675 {
676 ESpellEntry *spell_entry = E_SPELL_ENTRY (widget);
677 GtkEntry *entry = GTK_ENTRY (widget);
678 PangoLayout *layout;
679
680 layout = gtk_entry_get_layout (entry);
681 pango_layout_set_attributes (layout, spell_entry->priv->attr_list);
682
683 return GTK_WIDGET_CLASS (e_spell_entry_parent_class)->draw (widget, cr);
684 }
685
686 static gboolean
687 e_spell_entry_button_press (GtkWidget *widget,
688 GdkEventButton *event)
689 {
690 ESpellEntry *spell_entry = E_SPELL_ENTRY (widget);
691
692 spell_entry->priv->mark_character = spell_entry_find_position (
693 spell_entry, event->x + spell_entry->priv->entry_scroll_offset);
694
695 return GTK_WIDGET_CLASS (e_spell_entry_parent_class)->button_press_event (widget, event);
696 }
697
698 static void
699 spell_entry_set_property (GObject *object,
700 guint property_id,
701 const GValue *value,
702 GParamSpec *pspec)
703 {
704 switch (property_id) {
705 case PROP_CHECKING_ENABLED:
706 e_spell_entry_set_checking_enabled (
707 E_SPELL_ENTRY (object),
708 g_value_get_boolean (value));
709 return;
710 }
711
712 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
713 }
714
715 static void
716 spell_entry_get_property (GObject *object,
717 guint property_id,
718 GValue *value,
719 GParamSpec *pspec)
720 {
721 switch (property_id) {
722 case PROP_CHECKING_ENABLED:
723 g_value_set_boolean (
724 value,
725 e_spell_entry_get_checking_enabled (
726 E_SPELL_ENTRY (object)));
727 return;
728 }
729
730 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
731 }
732
733 static void
734 e_spell_entry_init (ESpellEntry *spell_entry)
735 {
736 spell_entry->priv = E_SPELL_ENTRY_GET_PRIVATE (spell_entry);
737 spell_entry->priv->attr_list = pango_attr_list_new ();
738 spell_entry->priv->checkers = NULL;
739 spell_entry->priv->checking_enabled = TRUE;
740
741 g_signal_connect (spell_entry, "popup-menu", G_CALLBACK (spell_entry_popup_menu), NULL);
742 g_signal_connect (spell_entry, "populate-popup", G_CALLBACK (spell_entry_populate_popup), NULL);
743 g_signal_connect (spell_entry, "changed", G_CALLBACK (spell_entry_changed), NULL);
744 g_signal_connect (spell_entry, "notify::scroll-offset", G_CALLBACK (spell_entry_notify_scroll_offset), NULL);
745
746 /* listen for languages changes */
747 spell_entry->priv->settings = g_settings_new ("org.gnome.evolution.mail");
748 g_signal_connect_swapped (spell_entry->priv->settings, "changed", G_CALLBACK (spell_entry_settings_changed), spell_entry);
749
750 /* load current settings */
751 spell_entry_settings_changed (spell_entry, NULL);
752 }
753
754 static void
755 e_spell_entry_finalize (GObject *object)
756 {
757 ESpellEntry *entry;
758
759 g_return_if_fail (object != NULL);
760 g_return_if_fail (E_IS_SPELL_ENTRY (object));
761
762 entry = E_SPELL_ENTRY (object);
763
764 if (entry->priv->settings)
765 g_object_unref (entry->priv->settings);
766 if (entry->priv->checkers)
767 g_slist_free_full (entry->priv->checkers, g_object_unref);
768 if (entry->priv->attr_list)
769 pango_attr_list_unref (entry->priv->attr_list);
770 if (entry->priv->words)
771 g_strfreev (entry->priv->words);
772 if (entry->priv->word_starts)
773 g_free (entry->priv->word_starts);
774 if (entry->priv->word_ends)
775 g_free (entry->priv->word_ends);
776
777 G_OBJECT_CLASS (e_spell_entry_parent_class)->finalize (object);
778 }
779
780 static void
781 e_spell_entry_class_init (ESpellEntryClass *class)
782 {
783 GObjectClass *object_class;
784 GtkWidgetClass *widget_class;
785
786 g_type_class_add_private (class, sizeof (ESpellEntryPrivate));
787
788 object_class = G_OBJECT_CLASS (class);
789 object_class->set_property = spell_entry_set_property;
790 object_class->get_property = spell_entry_get_property;
791 object_class->finalize = e_spell_entry_finalize;
792
793 widget_class = GTK_WIDGET_CLASS (class);
794 widget_class->draw = e_spell_entry_draw;
795 widget_class->button_press_event = e_spell_entry_button_press;
796
797 g_object_class_install_property (
798 object_class,
799 PROP_CHECKING_ENABLED,
800 g_param_spec_boolean (
801 "checking-enabled",
802 "checking enabled",
803 "Spell Checking is Enabled",
804 TRUE,
805 G_PARAM_READWRITE));
806 }
807
808 GtkWidget *
809 e_spell_entry_new (void)
810 {
811 return g_object_new (E_TYPE_SPELL_ENTRY, NULL);
812 }
813
814 /* 'languages' consists of 'const GtkhtmlSpellLanguage *' */
815 void
816 e_spell_entry_set_languages (ESpellEntry *spell_entry,
817 GList *languages)
818 {
819 GList *iter;
820
821 g_return_if_fail (spell_entry != NULL);
822
823 spell_entry->priv->custom_checkers = TRUE;
824
825 if (spell_entry->priv->checkers)
826 g_slist_free_full (spell_entry->priv->checkers, g_object_unref);
827 spell_entry->priv->checkers = NULL;
828
829 for (iter = languages; iter; iter = g_list_next (iter)) {
830 const GtkhtmlSpellLanguage *language = iter->data;
831
832 if (language)
833 spell_entry->priv->checkers = g_slist_prepend (
834 spell_entry->priv->checkers,
835 gtkhtml_spell_checker_new (language));
836 }
837
838 spell_entry->priv->checkers = g_slist_reverse (spell_entry->priv->checkers);
839
840 if (gtk_widget_get_realized (GTK_WIDGET (spell_entry)))
841 spell_entry_recheck_all (spell_entry);
842 }
843
844 gboolean
845 e_spell_entry_get_checking_enabled (ESpellEntry *spell_entry)
846 {
847 g_return_val_if_fail (spell_entry != NULL, FALSE);
848
849 return spell_entry->priv->checking_enabled;
850 }
851
852 void
853 e_spell_entry_set_checking_enabled (ESpellEntry *spell_entry,
854 gboolean enable_checking)
855 {
856 g_return_if_fail (spell_entry != NULL);
857
858 if (spell_entry->priv->checking_enabled == enable_checking)
859 return;
860
861 spell_entry->priv->checking_enabled = enable_checking;
862 spell_entry_recheck_all (spell_entry);
863
864 g_object_notify (G_OBJECT (spell_entry), "checking-enabled");
865
866 }