fix CID 1596595: Resource leaks, and CID 1596594: (CHECKED_RETURN)
[claws.git] / src / gtk / spell_entry.c
1 /*
2  * @file libsexy/sexy-icon-entry.c Entry widget
3  *
4  * @Copyright (C) 2004-2006 Christian Hammond.
5  * Some of this code is from gtkspell, Copyright (C) 2002 Evan Martin.
6  * Adapted for Claws Mail (c) 2009-2012 Pawel Pekala and the Claws Mail team
7  *
8  * This program is free software; you can redistribute it and/or modify
9  * it under the terms of the GNU General Public License as published by
10  * the Free Software Foundation; either version 3 of the License, or
11  * (at your option) any later version.
12  *
13  * This program is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16  * GNU General Public License for more details.
17  *
18  * You should have received a copy of the GNU General Public License
19  * along with this program. If not, see <http://www.gnu.org/licenses/>.
20  * 
21  */
22
23 #ifdef HAVE_CONFIG_H
24 #  include "config.h"
25 #include "claws-features.h"
26 #endif
27
28 #ifdef USE_ENCHANT
29
30 #include <glib.h>
31 #include <glib/gi18n.h>
32
33 #include <gdk/gdk.h>
34 #include <gdk/gdkkeysyms.h>
35
36 #include <string.h>
37 #include <gtk/gtk.h>
38
39 #include "spell_entry.h"
40 #include "prefs_common.h"
41 #include "codeconv.h"
42 #include "defs.h"
43 #include "gtkutils.h"
44
45 static void claws_spell_entry_init              (ClawsSpellEntry *entry);
46 static void claws_spell_entry_destroy           (GtkObject *object);
47 static gint claws_spell_entry_expose            (GtkWidget *widget,
48                                                  GdkEventExpose *event);
49 static gint claws_spell_entry_button_press      (GtkWidget *widget,
50                                                  GdkEventButton *event);
51 static gboolean claws_spell_entry_popup_menu    (GtkWidget *widget,
52                                                  ClawsSpellEntry *entry);
53 static void claws_spell_entry_populate_popup    (ClawsSpellEntry *entry,
54                                                  GtkMenu *menu,
55                                                  gpointer data);
56 static void claws_spell_entry_changed           (GtkEditable *editable,
57                                                  gpointer data);
58 static void claws_spell_entry_preedit_changed           (GtkEntry *entry,
59                                                  gchar *preedit,
60                                                  gpointer data);
61
62 typedef struct
63 {
64         PangoAttrList        *attr_list;
65         gint                  mark_character;
66         gchar               **words;
67         gint                 *word_starts;
68         gint                 *word_ends;
69         gint                  preedit_length;
70 } ClawsSpellEntryPrivate;
71
72 #define CLAWS_SPELL_ENTRY_GET_PRIVATE(entry) \
73         G_TYPE_INSTANCE_GET_PRIVATE (entry, CLAWS_TYPE_SPELL_ENTRY, \
74                         ClawsSpellEntryPrivate)
75
76 static GtkEntryClass *parent_class = NULL;
77
78 #if !GLIB_CHECK_VERSION(2,58, 0)
79 G_DEFINE_TYPE(ClawsSpellEntry, claws_spell_entry, GTK_TYPE_ENTRY)
80 #else
81 G_DEFINE_TYPE_WITH_CODE(ClawsSpellEntry, claws_spell_entry, GTK_TYPE_ENTRY,
82                 G_ADD_PRIVATE(ClawsSpellEntry))
83 #endif
84
85
86 static void claws_spell_entry_class_init(ClawsSpellEntryClass *klass)
87 {
88 #if !GLIB_CHECK_VERSION(2,58, 0)
89         GObjectClass    *g_object_class;
90 #endif
91         GtkObjectClass  *gtk_object_class;
92         GtkWidgetClass  *widget_class;
93         
94         parent_class = g_type_class_peek_parent(klass);
95         
96         gtk_object_class = GTK_OBJECT_CLASS(klass);
97         gtk_object_class->destroy = claws_spell_entry_destroy;
98         
99         widget_class = GTK_WIDGET_CLASS(klass);
100         widget_class->button_press_event = claws_spell_entry_button_press;
101         widget_class->expose_event = claws_spell_entry_expose;
102
103 #if !GLIB_CHECK_VERSION(2,58, 0)
104         g_object_class = G_OBJECT_CLASS(klass);
105         g_type_class_add_private(g_object_class,
106                         sizeof(ClawsSpellEntryPrivate));
107 #endif
108 }
109
110 static void claws_spell_entry_init(ClawsSpellEntry *entry)
111 {
112         ClawsSpellEntryPrivate *priv = CLAWS_SPELL_ENTRY_GET_PRIVATE(entry);
113
114         entry->gtkaspell = NULL;
115         
116         priv->attr_list = pango_attr_list_new();
117         priv->preedit_length = 0;
118                                         
119         g_signal_connect(G_OBJECT(entry), "popup-menu",
120                         G_CALLBACK(claws_spell_entry_popup_menu), entry);
121         g_signal_connect(G_OBJECT(entry), "populate-popup",
122                         G_CALLBACK(claws_spell_entry_populate_popup), NULL);
123         g_signal_connect(G_OBJECT(entry), "changed",
124                         G_CALLBACK(claws_spell_entry_changed), NULL);
125         g_signal_connect(G_OBJECT(entry), "preedit-changed",
126                         G_CALLBACK(claws_spell_entry_preedit_changed), NULL);
127 }
128
129 static void claws_spell_entry_destroy(GtkObject *object)
130 {
131         GTK_OBJECT_CLASS(parent_class)->destroy(object);
132 }
133
134 GtkWidget *claws_spell_entry_new(void)
135 {
136         return GTK_WIDGET( g_object_new(CLAWS_TYPE_SPELL_ENTRY, NULL) );
137 }
138
139 void claws_spell_entry_set_gtkaspell(ClawsSpellEntry *entry, GtkAspell *gtkaspell)
140 {
141         cm_return_if_fail(CLAWS_IS_SPELL_ENTRY(entry));
142
143         entry->gtkaspell = gtkaspell;
144 }
145
146 static gint claws_spell_entry_find_position (ClawsSpellEntry *_entry, gint x)
147 {
148         PangoLayout *layout;
149         PangoLayoutLine *line;
150         const gchar *text;
151         gint cursor_index;
152         gint index;
153         gint pos, current_pos;
154         gint scroll_offset;
155         gboolean trailing;
156         GtkEntry *entry = GTK_ENTRY(_entry);
157         ClawsSpellEntryPrivate *priv = CLAWS_SPELL_ENTRY_GET_PRIVATE(entry);
158
159         g_object_get(entry, "scroll-offset", &scroll_offset, NULL);
160         x = x + scroll_offset;
161
162         layout = gtk_entry_get_layout(entry);
163         text = pango_layout_get_text(layout);
164         g_object_get(entry, "cursor-position", &current_pos, NULL);
165         cursor_index = g_utf8_offset_to_pointer(text, current_pos) - text;
166
167         line = pango_layout_get_lines(layout)->data;
168         pango_layout_line_x_to_index(line, x * PANGO_SCALE, &index, &trailing);
169
170         if (index >= cursor_index && priv->preedit_length) {
171                 if (index >= cursor_index + priv->preedit_length) {
172                         index -= priv->preedit_length;
173                 } else {
174                         index = cursor_index;
175                         trailing = FALSE;
176                 }
177         }
178
179         pos = g_utf8_pointer_to_offset (text, text + index);
180         pos += trailing;
181
182         return pos;
183 }
184
185 static void get_word_extents_from_position(ClawsSpellEntry *entry, gint *start,
186                                            gint *end, guint position)
187 {
188         const gchar *text;
189         gint i, bytes_pos;
190         ClawsSpellEntryPrivate *priv = CLAWS_SPELL_ENTRY_GET_PRIVATE(entry);
191
192         *start = -1;
193         *end = -1;
194
195         if (priv->words == NULL)
196                 return;
197
198         text = gtk_entry_get_text(GTK_ENTRY(entry));
199         bytes_pos = (gint) (g_utf8_offset_to_pointer(text, position) - text);
200
201         for (i = 0; priv->words[i]; i++) {
202                 if (bytes_pos >= priv->word_starts[i] &&
203                     bytes_pos <= priv->word_ends[i]) {
204                         *start = priv->word_starts[i];
205                         *end   = priv->word_ends[i];
206                         return;
207                 }
208         }
209 }
210
211 static gchar *get_word(ClawsSpellEntry *entry, const int start, const int end)
212 {
213         const gchar *text;
214         gchar *word;
215         
216         if (start >= end)
217                 return NULL;
218
219         text = gtk_entry_get_text(GTK_ENTRY(entry));
220         word = g_new0(gchar, end - start + 2);
221         g_strlcpy(word, text + start, end - start + 1);
222
223         return word;
224 }
225
226 static void replace_word(ClawsSpellEntry *entry, const gchar *newword)
227 {
228         gint cursor, start_pos, end_pos;
229         const gchar *text = gtk_entry_get_text(GTK_ENTRY(entry));
230         ClawsSpellEntryPrivate *priv = CLAWS_SPELL_ENTRY_GET_PRIVATE(entry);
231
232         start_pos = entry->gtkaspell->start_pos;
233         end_pos = entry->gtkaspell->end_pos;
234                 
235         cursor = gtk_editable_get_position(GTK_EDITABLE(entry));
236         /* is the cursor at the end? If so, restore it there */
237         if (g_utf8_strlen(text, -1) == cursor)
238                 cursor = -1;
239         else if(cursor < priv->mark_character ||
240                 cursor > priv->mark_character)
241                         cursor = priv->mark_character;
242
243         gtk_editable_delete_text(GTK_EDITABLE(entry), start_pos, end_pos);
244         gtk_editable_insert_text(GTK_EDITABLE(entry), newword, strlen(newword),
245                                                          &start_pos);
246         gtk_editable_set_position(GTK_EDITABLE(entry), cursor);
247 }
248
249
250 static gboolean word_misspelled(ClawsSpellEntry *entry, int start, int end)
251 {
252         gchar *word;
253         gboolean ret;
254
255         word = get_word(entry, start, end);
256         if (word == NULL || g_unichar_isdigit(word[0])) {
257                 if (word)
258                         g_free(word);
259                 return FALSE;
260         }
261                 
262         ret = gtkaspell_misspelled_test(entry->gtkaspell, word);
263
264         g_free(word);
265         return ret;
266 }
267
268 static gboolean is_word_end (GtkEntry *entry, const int offset)
269 {
270         gchar *p = gtk_editable_get_chars(GTK_EDITABLE(entry), offset, offset+1);
271         gunichar ch;
272         
273         ch = g_utf8_get_char(p);
274         g_free(p);
275         
276         if (ch == '\0')
277                 return TRUE;
278
279         if (ch == '\'') {
280                 p = gtk_editable_get_chars(GTK_EDITABLE(entry), offset+1, offset+2);
281                 ch = g_utf8_get_char(p);
282                 g_free(p);
283                 
284                 return (g_unichar_isspace(ch) || g_unichar_ispunct(ch)
285                         || g_unichar_isdigit(ch));
286         }
287         
288         return (g_unichar_isspace(ch) || g_unichar_ispunct(ch));
289 }
290
291 static void entry_strsplit_utf8(GtkEntry *entry, gchar ***set, gint **starts, gint **ends)
292 {
293         PangoLayout   *layout;
294         PangoLogAttr  *log_attrs;
295         const gchar   *text;
296         gint           n_attrs, n_strings, i, j;
297
298         layout = gtk_entry_get_layout(GTK_ENTRY(entry));
299         text = gtk_entry_get_text(GTK_ENTRY(entry));
300         pango_layout_get_log_attrs(layout, &log_attrs, &n_attrs);
301
302         /* Find how many words we have */
303         n_strings = 0;
304         for (i = 0; i < n_attrs; i++)
305                 if (log_attrs[i].is_word_start)
306                         n_strings++;
307
308         *set    = g_new0(gchar *, n_strings + 1);
309         *starts = g_new0(gint, n_strings);
310         *ends   = g_new0(gint, n_strings);
311
312         /* Copy out strings */
313         for (i = 0, j = 0; i < n_attrs; i++) {
314                 if (log_attrs[i].is_word_start) {
315                         gint cend, bytes;
316                         gchar *start;
317
318                         /* Find the end of this string */
319                         cend = i;
320                         while (!is_word_end(entry, cend))
321                                 cend++;
322
323                         /* Copy sub-string */
324                         start = g_utf8_offset_to_pointer(text, i);
325                         bytes = (gint) (g_utf8_offset_to_pointer(text, cend) - start);
326                         (*set)[j]    = g_new0(gchar, bytes + 1);
327                         (*starts)[j] = (gint) (start - text);
328                         (*ends)[j]   = (gint) (start - text + bytes);
329                         g_utf8_strncpy((*set)[j], start, cend - i);
330
331                         /* Move on to the next word */
332                         j++;
333                 }
334         }
335
336         g_free (log_attrs);
337 }
338
339 static void insert_misspelled_marker(ClawsSpellEntry *entry, guint start, guint end)
340 {
341         ClawsSpellEntryPrivate *priv = CLAWS_SPELL_ENTRY_GET_PRIVATE(entry);
342         guint16 red   = (guint16) (((gdouble)((prefs_common.color[COL_MISSPELLED] &
343                                         0xff0000) >> 16) / 255.0) * 65535.0);
344         guint16 green = (guint16) (((gdouble)((prefs_common.color[COL_MISSPELLED] &
345                                         0x00ff00) >> 8) / 255.0) * 65535.0);
346         guint16 blue  = (guint16) (((gdouble) (prefs_common.color[COL_MISSPELLED] &
347                                         0x0000ff) / 255.0) * 65535.0);
348         PangoAttribute *fcolor, *ucolor, *unline;
349         
350         if(prefs_common.color[COL_MISSPELLED] != 0) {
351                 fcolor = pango_attr_foreground_new(red, green, blue);
352                 fcolor->start_index = start;
353                 fcolor->end_index = end;
354                 
355                 pango_attr_list_insert(priv->attr_list, fcolor);
356         } else {
357                 ucolor = pango_attr_underline_color_new (65535, 0, 0);
358                 unline = pango_attr_underline_new (PANGO_UNDERLINE_ERROR);
359
360                 ucolor->start_index = start;
361                 unline->start_index = start;
362
363                 ucolor->end_index = end;
364                 unline->end_index = end;
365
366                 pango_attr_list_insert (priv->attr_list, ucolor);
367                 pango_attr_list_insert (priv->attr_list, unline);
368         }
369 }
370
371 static gboolean check_word(ClawsSpellEntry *entry, int start, int end)
372 {
373         GtkAspell *gtkaspell = entry->gtkaspell;
374         ClawsSpellEntryPrivate *priv = CLAWS_SPELL_ENTRY_GET_PRIVATE(entry);
375         PangoAttrIterator *it;
376         gint s, e;
377         gboolean misspelled;
378         gchar *text = gtk_editable_get_chars(GTK_EDITABLE(entry), 0, -1);
379         gchar *word = NULL;
380
381         /* Check to see if we've got any attributes at this position.
382          * If so, free them, since we'll readd it if the word is misspelled */
383         it = pango_attr_list_get_iterator(priv->attr_list);
384         if (it == NULL)
385                 return FALSE;
386         do {
387                 pango_attr_iterator_range(it, &s, &e);
388                 if (s == start) {
389                         GSList *attrs = pango_attr_iterator_get_attrs(it);
390                         g_slist_foreach(attrs, (GFunc) pango_attribute_destroy, NULL);
391                         g_slist_free(attrs);
392                 }
393         } while (pango_attr_iterator_next(it));
394         pango_attr_iterator_destroy(it);
395
396         if ((misspelled = word_misspelled(entry, start, end))) {
397                 insert_misspelled_marker(entry, start, end);
398         
399                 word = get_word(entry, start, end);
400                 strncpy(gtkaspell->theword, (gchar *)word, GTKASPELLWORDSIZE - 1);
401                 gtkaspell->theword[GTKASPELLWORDSIZE - 1] = 0;
402                 gtkaspell->start_pos  = g_utf8_pointer_to_offset(text, (text+start));
403                 gtkaspell->end_pos    = g_utf8_pointer_to_offset(text, (text+end));
404                 gtkaspell_free_suggestions_list(gtkaspell);
405                 g_free(word);
406         }
407         
408         g_free(text);
409         
410         return misspelled;
411 }
412
413 void claws_spell_entry_recheck_all(ClawsSpellEntry *entry)
414 {
415         GtkAllocation allocation;
416         GdkRectangle rect;
417         PangoLayout *layout;
418         ClawsSpellEntryPrivate *priv = CLAWS_SPELL_ENTRY_GET_PRIVATE(entry);
419         int length, i;
420
421         cm_return_if_fail(CLAWS_IS_SPELL_ENTRY(entry));
422         cm_return_if_fail(entry->gtkaspell != NULL);
423
424         if (priv->words == NULL)
425                 return;
426
427         /* Remove all existing pango attributes.  These will get readded as we check */
428         pango_attr_list_unref(priv->attr_list);
429         priv->attr_list = pango_attr_list_new();
430
431         /* Loop through words */
432         for (i = 0; priv->words[i]; i++) {
433                 length = strlen(priv->words[i]);
434                 if (length == 0)
435                         continue;
436                 check_word(entry, priv->word_starts[i], priv->word_ends[i]);
437         }
438
439         layout = gtk_entry_get_layout(GTK_ENTRY(entry));
440         pango_layout_set_attributes(layout, priv->attr_list);
441
442         if (gtk_widget_get_realized(GTK_WIDGET(entry))) {
443                 rect.x = 0; rect.y = 0;
444                 gtk_widget_get_allocation(GTK_WIDGET(entry), &allocation);
445                 rect.width  = allocation.width;
446                 rect.height = allocation.height;
447                 gdk_window_invalidate_rect(gtk_widget_get_window(GTK_WIDGET(entry)),
448                                 &rect, TRUE);
449         }
450 }
451
452 static gint claws_spell_entry_expose(GtkWidget *widget, GdkEventExpose *event)
453 {
454         ClawsSpellEntry *entry = CLAWS_SPELL_ENTRY(widget);
455         ClawsSpellEntryPrivate *priv = CLAWS_SPELL_ENTRY_GET_PRIVATE(entry);
456         GtkEntry *gtk_entry = GTK_ENTRY(widget);
457         PangoLayout *layout;
458
459         if (entry->gtkaspell != NULL) {
460                 layout = gtk_entry_get_layout(gtk_entry);
461                 pango_layout_set_attributes(layout, priv->attr_list);
462         }
463
464         return GTK_WIDGET_CLASS(parent_class)->expose_event (widget, event);
465 }
466
467 static gint claws_spell_entry_button_press(GtkWidget *widget, GdkEventButton *event)
468 {
469         ClawsSpellEntry *entry = CLAWS_SPELL_ENTRY(widget);
470         ClawsSpellEntryPrivate *priv = CLAWS_SPELL_ENTRY_GET_PRIVATE(entry);
471         gint pos;
472
473         pos = claws_spell_entry_find_position(entry, event->x);
474         priv->mark_character = pos;
475
476         return GTK_WIDGET_CLASS(parent_class)->button_press_event (widget, event);
477 }
478
479 static gboolean claws_spell_entry_popup_menu(GtkWidget *widget, ClawsSpellEntry *entry)
480 {
481         ClawsSpellEntryPrivate *priv = CLAWS_SPELL_ENTRY_GET_PRIVATE(entry);
482
483         priv->mark_character = gtk_editable_get_position (GTK_EDITABLE (entry));
484         return FALSE;
485 }
486
487 static void set_position(gpointer data, gint pos)
488 {
489         gtk_editable_set_position(GTK_EDITABLE(data), pos);
490 }
491
492 static gboolean find_misspelled_cb(gpointer data, gboolean forward)
493 {
494         ClawsSpellEntry *entry = (ClawsSpellEntry *)data;
495         ClawsSpellEntryPrivate *priv = CLAWS_SPELL_ENTRY_GET_PRIVATE(entry);
496         GtkAspell *gtkaspell = entry->gtkaspell;
497         gboolean misspelled = FALSE;
498         gint cursor, minpos, maxpos, i, words_len = 0;
499         gint start, end;
500         gchar *text;
501         
502         if (priv->words == NULL)
503                 return FALSE;
504
505         gtkaspell->orig_pos = gtk_editable_get_position(GTK_EDITABLE(entry));
506         text = gtk_editable_get_chars(GTK_EDITABLE(entry), 0, -1);
507         cursor = g_utf8_offset_to_pointer(text, gtkaspell->orig_pos) - text;
508
509         if (gtk_editable_get_selection_bounds(GTK_EDITABLE(entry), &start, &end)) {
510                 minpos = g_utf8_offset_to_pointer(text, start) - text;
511                 maxpos = g_utf8_offset_to_pointer(text, end) - text;
512         } else {
513                 minpos = forward ? cursor : 0;
514                 maxpos = forward ? strlen(text)-1 : cursor;
515         }
516         g_free(text);
517
518         while(priv->words[words_len])
519                 words_len++;
520
521         if (forward) {
522                 for(i=0; i < words_len; i++)
523                         if (priv->word_ends[i] > minpos &&
524                             (misspelled = check_word(entry,
525                                         priv->word_starts[i],
526                                         priv->word_ends[i])))
527                                 break;
528         } else {
529                 for(i=words_len-1; i >= 0; i--)
530                         if (priv->word_starts[i] < maxpos &&
531                             (misspelled = check_word(entry,
532                                         priv->word_starts[i],
533                                         priv->word_ends[i])))
534                                 break;
535         }
536         
537         return misspelled;
538 }
539
540 static gboolean check_word_cb(gpointer data)
541 {
542         ClawsSpellEntry *entry = (ClawsSpellEntry *)data;
543         ClawsSpellEntryPrivate *priv = CLAWS_SPELL_ENTRY_GET_PRIVATE(entry);
544         gint start, end;
545         
546         get_word_extents_from_position(entry, &start, &end, priv->mark_character);
547         return check_word(entry, start, end);
548 }
549
550 static void replace_word_cb(gpointer data, const gchar *newword)
551 {
552         replace_word((ClawsSpellEntry *) data, newword);
553 }
554
555 static void set_menu_pos(GtkMenu *menu, gint *x, gint *y, 
556                          gboolean *push_in, gpointer data)
557 {
558         ClawsSpellEntry *entry = (ClawsSpellEntry *) data;
559         GtkAspell *gtkaspell = entry->gtkaspell;
560         gint pango_offset, win_x, win_y, scr_x, scr_y, text_index, entry_x;
561         gchar *text;
562         GtkRequisition subject_rq;
563         PangoLayout *layout = gtk_entry_get_layout(GTK_ENTRY(entry));
564         PangoLayoutLine *line = pango_layout_get_lines(layout)->data;
565
566         gtk_widget_get_child_requisition(GTK_WIDGET(entry), &subject_rq);
567         
568         /* screen -> compose window coords */
569         gdk_window_get_origin(gtk_widget_get_window(GTK_WIDGET(gtkaspell->parent_window)),
570                                 &scr_x, &scr_y);
571
572         /* compose window -> subject entry coords */
573         gtk_widget_translate_coordinates(GTK_WIDGET(entry),
574                         gtkaspell->parent_window, 0, 0, &win_x, &win_y);
575
576         text = gtk_editable_get_chars(GTK_EDITABLE(entry), 0, -1);
577         text_index = g_utf8_offset_to_pointer(text, gtkaspell->end_pos) - text;
578         g_free(text);
579
580         pango_offset = gtk_entry_text_index_to_layout_index(GTK_ENTRY(entry),
581                                         text_index);
582         pango_layout_line_index_to_x(line, pango_offset, TRUE, &entry_x);
583
584         *x = scr_x + win_x + PANGO_PIXELS(entry_x) + 8;
585         *y = scr_y + win_y + subject_rq.height;
586 }
587
588 void claws_spell_entry_context_set(ClawsSpellEntry *entry)
589 {
590         cm_return_if_fail(CLAWS_IS_SPELL_ENTRY(entry));
591         cm_return_if_fail(entry->gtkaspell != NULL);
592
593         entry->gtkaspell->ctx.set_position      = set_position;
594         entry->gtkaspell->ctx.set_menu_pos      = set_menu_pos;
595         entry->gtkaspell->ctx.find_misspelled   = find_misspelled_cb;
596         entry->gtkaspell->ctx.check_word        = check_word_cb;
597         entry->gtkaspell->ctx.replace_word      = replace_word_cb;
598         entry->gtkaspell->ctx.data              = (gpointer) entry;
599 }
600
601 static void claws_spell_entry_populate_popup(ClawsSpellEntry *entry, GtkMenu *menu,
602                                                 gpointer data)
603 {
604         GtkAspell *gtkaspell = entry->gtkaspell;
605         ClawsSpellEntryPrivate *priv = CLAWS_SPELL_ENTRY_GET_PRIVATE(entry);
606         gint start, end;
607         gchar *word, *text;
608         
609         if (gtkaspell == NULL)
610                 return;
611         
612         get_word_extents_from_position(entry, &start, &end, priv->mark_character);
613
614         if ((word = get_word(entry, start, end)) != NULL) {
615                 strncpy(gtkaspell->theword, word, GTKASPELLWORDSIZE - 1);
616                 g_free(word);
617         }
618
619         gtkaspell->misspelled = word_misspelled(entry, start, end);
620
621         text = gtk_editable_get_chars(GTK_EDITABLE(entry), 0, -1);
622         gtkaspell->start_pos  = g_utf8_pointer_to_offset(text, (text+start));
623         gtkaspell->end_pos    = g_utf8_pointer_to_offset(text, (text+end));
624         g_free(text);
625
626         claws_spell_entry_context_set(entry);
627         gtkaspell_make_context_menu(menu, gtkaspell);
628 }
629
630 static void claws_spell_entry_changed(GtkEditable *editable, gpointer data)
631 {
632         ClawsSpellEntry *entry = CLAWS_SPELL_ENTRY(editable);
633         ClawsSpellEntryPrivate *priv = CLAWS_SPELL_ENTRY_GET_PRIVATE(entry);
634
635         if (entry->gtkaspell == NULL)
636                 return;
637
638         if (priv->words) {
639                 g_strfreev(priv->words);
640                 g_free(priv->word_starts);
641                 g_free(priv->word_ends);
642         }
643         entry_strsplit_utf8(GTK_ENTRY(entry), &priv->words, 
644                         &priv->word_starts, &priv->word_ends);
645         if(entry->gtkaspell->check_while_typing == TRUE)
646                 claws_spell_entry_recheck_all(entry);
647 }
648
649 static void claws_spell_entry_preedit_changed           (GtkEntry *_entry,
650                                                  gchar *preedit,
651                                                  gpointer data)
652 {
653         ClawsSpellEntry *entry = CLAWS_SPELL_ENTRY(_entry);
654         ClawsSpellEntryPrivate *priv = CLAWS_SPELL_ENTRY_GET_PRIVATE(entry);
655
656         priv->preedit_length = preedit != NULL ? strlen(preedit) : 0;
657 }
658
659 static void continue_check(gpointer *data)
660 {
661         ClawsSpellEntry *entry = (ClawsSpellEntry *)data;
662         GtkAspell *gtkaspell = entry->gtkaspell;
663         gint pos = gtk_editable_get_position(GTK_EDITABLE(entry));
664         
665         if (gtkaspell->misspelled && pos < gtkaspell->end_check_pos)
666                 gtkaspell->misspelled = gtkaspell_check_next_prev(gtkaspell, TRUE);
667         else
668                 gtkaspell->continue_check = NULL;
669 }
670
671 void claws_spell_entry_check_all(ClawsSpellEntry *entry)
672 {
673         gint start, end;
674         gchar *text;
675         
676         cm_return_if_fail(CLAWS_IS_SPELL_ENTRY(entry));
677         cm_return_if_fail(entry->gtkaspell != NULL);
678         
679         if (!gtk_editable_get_selection_bounds(GTK_EDITABLE(entry), &start, &end)) {
680                 text = gtk_editable_get_chars(GTK_EDITABLE(entry), 0, -1);      
681                 
682                 start = 0;
683                 end = g_utf8_strlen(text, -1) - 1;
684                 
685                 g_free(text);
686         }
687
688         gtk_editable_set_position(GTK_EDITABLE(entry), start);
689         entry->gtkaspell->continue_check = continue_check;
690         entry->gtkaspell->end_check_pos  = end;
691
692         claws_spell_entry_context_set(entry);
693         entry->gtkaspell->misspelled = 
694                         gtkaspell_check_next_prev(entry->gtkaspell, TRUE);
695 }
696
697 void claws_spell_entry_check_backwards(ClawsSpellEntry *entry)
698 {
699         cm_return_if_fail(CLAWS_IS_SPELL_ENTRY(entry));
700         cm_return_if_fail(entry->gtkaspell != NULL);
701         
702         entry->gtkaspell->continue_check = NULL;
703         claws_spell_entry_context_set(entry);
704         gtkaspell_check_next_prev(entry->gtkaspell, FALSE);
705 }
706
707 void claws_spell_entry_check_forwards_go(ClawsSpellEntry *entry)
708 {
709         cm_return_if_fail(CLAWS_IS_SPELL_ENTRY(entry));
710         cm_return_if_fail(entry->gtkaspell != NULL);
711
712         entry->gtkaspell->continue_check = NULL;
713         claws_spell_entry_context_set(entry);
714         gtkaspell_check_next_prev(entry->gtkaspell, TRUE);
715 }
716
717 #endif  /* USE_ENCHANT */