1d6dbb57f788a9cfeca28e2cf1e69942d974bfbc
[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-2011 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 #endif
26
27 #ifdef USE_ENCHANT
28
29 #include <glib.h>
30 #include <glib/gi18n.h>
31
32 #include <gdk/gdk.h>
33 #include <gdk/gdkkeysyms.h>
34
35 #include <string.h>
36 #include <glib.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_editable_init     (GtkEditableClass *iface);
47 static void claws_spell_entry_finalize          (GObject *object);
48 #if !GTK_CHECK_VERSION(3, 0, 0)
49 static void claws_spell_entry_destroy           (GtkObject *object);
50 static gint claws_spell_entry_expose            (GtkWidget *widget,
51                                                  GdkEventExpose *event);
52 #else
53 static void claws_spell_entry_destroy           (GtkWidget *object);
54 static gint claws_spell_entry_expose            (GtkWidget *widget,
55                                                  cairo_t *cr);
56 #endif
57 static gint claws_spell_entry_button_press      (GtkWidget *widget,
58                                                  GdkEventButton *event);
59 static gboolean claws_spell_entry_popup_menu    (GtkWidget *widget,
60                                                  ClawsSpellEntry *entry);
61 static void claws_spell_entry_populate_popup    (ClawsSpellEntry *entry,
62                                                  GtkMenu *menu,
63                                                  gpointer data);
64 static void claws_spell_entry_changed           (GtkEditable *editable,
65                                                  gpointer data);
66
67 struct _ClawsSpellEntryPriv
68 {
69         PangoAttrList        *attr_list;
70         gint                  mark_character;
71         gchar               **words;
72         gint                 *word_starts;
73         gint                 *word_ends;
74 };
75
76 static GtkEntryClass *parent_class = NULL;
77
78
79 G_DEFINE_TYPE_EXTENDED(ClawsSpellEntry, claws_spell_entry, GTK_TYPE_ENTRY, 0, G_IMPLEMENT_INTERFACE(GTK_TYPE_EDITABLE, claws_spell_entry_editable_init)); 
80
81
82 static void claws_spell_entry_class_init(ClawsSpellEntryClass *klass)
83 {
84         GObjectClass    *g_object_class;
85 #if !GTK_CHECK_VERSION(3, 0, 0)
86         GtkObjectClass  *gtk_object_class;
87 #endif
88         GtkWidgetClass  *widget_class;
89         
90         parent_class = g_type_class_peek_parent(klass);
91         
92         g_object_class = G_OBJECT_CLASS(klass);
93         g_object_class->finalize = claws_spell_entry_finalize;
94         
95 #if !GTK_CHECK_VERSION(3, 0, 0)
96         gtk_object_class = GTK_OBJECT_CLASS(klass);
97         gtk_object_class->destroy = claws_spell_entry_destroy;
98 #endif
99         
100         widget_class = GTK_WIDGET_CLASS(klass);
101         widget_class->button_press_event = claws_spell_entry_button_press;
102 #if !GTK_CHECK_VERSION(3, 0, 0)
103         widget_class->expose_event = claws_spell_entry_expose;
104 #else
105         widget_class->draw = claws_spell_entry_expose;
106         widget_class->destroy = claws_spell_entry_destroy;
107 #endif
108         
109         g_type_class_add_private(g_object_class,
110                         sizeof(struct _ClawsSpellEntryPriv));
111 }
112
113 static void claws_spell_entry_init(ClawsSpellEntry *entry)
114 {
115         entry->gtkaspell = NULL;
116         
117         entry->priv = g_new0(ClawsSpellEntryPriv, 1);
118         entry->priv->attr_list = pango_attr_list_new();
119                                         
120         g_signal_connect(G_OBJECT(entry), "popup-menu",
121                         G_CALLBACK(claws_spell_entry_popup_menu), entry);
122         g_signal_connect(G_OBJECT(entry), "populate-popup",
123                         G_CALLBACK(claws_spell_entry_populate_popup), NULL);
124         g_signal_connect(G_OBJECT(entry), "changed",
125                         G_CALLBACK(claws_spell_entry_changed), NULL);
126 }
127
128 static void claws_spell_entry_editable_init (GtkEditableClass *iface) {}
129
130 static void claws_spell_entry_finalize(GObject *object)
131 {
132         ClawsSpellEntry *entry = CLAWS_SPELL_ENTRY(object);
133
134         if (entry->priv->attr_list)
135                 pango_attr_list_unref(entry->priv->attr_list);
136         if (entry->priv->words)
137                 g_strfreev(entry->priv->words);
138
139         g_free(entry->priv->word_starts);
140         g_free(entry->priv->word_ends);
141         g_free(entry->priv);
142         entry->priv = NULL;
143         
144         G_OBJECT_CLASS(parent_class)->finalize(object);
145 }
146
147 #if !GTK_CHECK_VERSION(3, 0, 0)
148 static void claws_spell_entry_destroy(GtkObject *object)
149 {
150         GTK_OBJECT_CLASS(parent_class)->destroy(object);
151 }
152 #else
153 static void claws_spell_entry_destroy(GtkWidget *object)
154 {
155         GTK_WIDGET_CLASS(parent_class)->destroy(object);
156 }
157 #endif
158
159 GtkWidget *claws_spell_entry_new(void)
160 {
161         return GTK_WIDGET( g_object_new(CLAWS_TYPE_SPELL_ENTRY, NULL) );
162 }
163
164 void claws_spell_entry_set_gtkaspell(ClawsSpellEntry *entry, GtkAspell *gtkaspell)
165 {
166         cm_return_if_fail(CLAWS_IS_SPELL_ENTRY(entry));
167
168         entry->gtkaspell = gtkaspell;
169 }
170
171 static gint gtk_entry_find_position (GtkEntry *entry, gint x)
172 {
173         PangoLayout *layout;
174         PangoLayoutLine *line;
175         const gchar *text;
176         gint cursor_index;
177         gint index;
178         gint pos;
179         gboolean trailing;
180
181         x = x + entry->scroll_offset;
182
183         layout = gtk_entry_get_layout(entry);
184         text = pango_layout_get_text(layout);
185         cursor_index = g_utf8_offset_to_pointer(text, entry->current_pos) - text;
186
187         line = pango_layout_get_lines(layout)->data;
188         pango_layout_line_x_to_index(line, x * PANGO_SCALE, &index, &trailing);
189
190         if (index >= cursor_index && entry->preedit_length) {
191                 if (index >= cursor_index + entry->preedit_length) {
192                         index -= entry->preedit_length;
193                 } else {
194                         index = cursor_index;
195                         trailing = FALSE;
196                 }
197         }
198
199         pos = g_utf8_pointer_to_offset (text, text + index);
200         pos += trailing;
201
202         return pos;
203 }
204
205 static void get_word_extents_from_position(ClawsSpellEntry *entry, gint *start,
206                                            gint *end, guint position)
207 {
208         const gchar *text;
209         gint i, bytes_pos;
210
211         *start = -1;
212         *end = -1;
213
214         if (entry->priv->words == NULL)
215                 return;
216
217         text = gtk_entry_get_text(GTK_ENTRY(entry));
218         bytes_pos = (gint) (g_utf8_offset_to_pointer(text, position) - text);
219
220         for (i = 0; entry->priv->words[i]; i++) {
221                 if (bytes_pos >= entry->priv->word_starts[i] &&
222                     bytes_pos <= entry->priv->word_ends[i]) {
223                         *start = entry->priv->word_starts[i];
224                         *end   = entry->priv->word_ends[i];
225                         return;
226                 }
227         }
228 }
229
230 static gchar *get_word(ClawsSpellEntry *entry, const int start, const int end)
231 {
232         const gchar *text;
233         gchar *word;
234         
235         if (start >= end)
236                 return NULL;
237
238         text = gtk_entry_get_text(GTK_ENTRY(entry));
239         word = g_new0(gchar, end - start + 2);
240         g_strlcpy(word, text + start, end - start + 1);
241
242         return word;
243 }
244
245 static void replace_word(ClawsSpellEntry *entry, const gchar *newword)
246 {
247         gint cursor, start_pos, end_pos;
248         const gchar *text = gtk_entry_get_text(GTK_ENTRY(entry));
249
250         start_pos = entry->gtkaspell->start_pos;
251         end_pos = entry->gtkaspell->end_pos;
252                 
253         cursor = gtk_editable_get_position(GTK_EDITABLE(entry));
254         /* is the cursor at the end? If so, restore it there */
255         if (g_utf8_strlen(text, -1) == cursor)
256                 cursor = -1;
257         else if(cursor < entry->priv->mark_character ||
258                 cursor > entry->priv->mark_character)
259                         cursor = entry->priv->mark_character;
260
261         gtk_editable_delete_text(GTK_EDITABLE(entry), start_pos, end_pos);
262         gtk_editable_insert_text(GTK_EDITABLE(entry), newword, strlen(newword),
263                                                          &start_pos);
264         gtk_editable_set_position(GTK_EDITABLE(entry), cursor);
265 }
266
267
268 static gboolean word_misspelled(ClawsSpellEntry *entry, int start, int end)
269 {
270         gchar *word;
271         gboolean ret;
272
273         word = get_word(entry, start, end);
274         if (word == NULL || g_unichar_isdigit(word[0]))
275                 return FALSE;
276                 
277         ret = gtkaspell_misspelled_test(entry->gtkaspell, word);
278
279         g_free(word);
280         return ret;
281 }
282
283 static gboolean is_word_end (GtkEntry *entry, const int offset)
284 {
285         gchar *p = gtk_editable_get_chars(GTK_EDITABLE(entry), offset, offset+1);
286         gunichar ch;
287         
288         ch = g_utf8_get_char(p);
289         g_free(p);
290         
291         if (ch == '\0')
292                 return TRUE;
293
294         if (ch == '\'') {
295                 p = gtk_editable_get_chars(GTK_EDITABLE(entry), offset+1, offset+2);
296                 ch = g_utf8_get_char(p);
297                 g_free(p);
298                 
299                 return (g_unichar_isspace(ch) || g_unichar_ispunct(ch)
300                         || g_unichar_isdigit(ch));
301         }
302         
303         return (g_unichar_isspace(ch) || g_unichar_ispunct(ch));
304 }
305
306 static void entry_strsplit_utf8(GtkEntry *entry, gchar ***set, gint **starts, gint **ends)
307 {
308         PangoLayout   *layout;
309         PangoLogAttr  *log_attrs;
310         const gchar   *text;
311         gint           n_attrs, n_strings, i, j;
312
313         layout = gtk_entry_get_layout(GTK_ENTRY(entry));
314         text = gtk_entry_get_text(GTK_ENTRY(entry));
315         pango_layout_get_log_attrs(layout, &log_attrs, &n_attrs);
316
317         /* Find how many words we have */
318         n_strings = 0;
319         for (i = 0; i < n_attrs; i++)
320                 if (log_attrs[i].is_word_start)
321                         n_strings++;
322
323         *set    = g_new0(gchar *, n_strings + 1);
324         *starts = g_new0(gint, n_strings);
325         *ends   = g_new0(gint, n_strings);
326
327         /* Copy out strings */
328         for (i = 0, j = 0; i < n_attrs; i++) {
329                 if (log_attrs[i].is_word_start) {
330                         gint cend, bytes;
331                         gchar *start;
332
333                         /* Find the end of this string */
334                         cend = i;
335                         while (!is_word_end(entry, cend))
336                                 cend++;
337
338                         /* Copy sub-string */
339                         start = g_utf8_offset_to_pointer(text, i);
340                         bytes = (gint) (g_utf8_offset_to_pointer(text, cend) - start);
341                         (*set)[j]    = g_new0(gchar, bytes + 1);
342                         (*starts)[j] = (gint) (start - text);
343                         (*ends)[j]   = (gint) (start - text + bytes);
344                         g_utf8_strncpy((*set)[j], start, cend - i);
345
346                         /* Move on to the next word */
347                         j++;
348                 }
349         }
350
351         g_free (log_attrs);
352 }
353
354 static void insert_misspelled_marker(ClawsSpellEntry *entry, guint start, guint end)
355 {
356         guint16 red   = (guint16) (((gdouble)((prefs_common.misspelled_col & 
357                                         0xff0000) >> 16) / 255.0) * 65535.0);
358         guint16 green = (guint16) (((gdouble)((prefs_common.misspelled_col & 
359                                         0x00ff00) >> 8) / 255.0) * 65535.0);
360         guint16 blue  = (guint16) (((gdouble) (prefs_common.misspelled_col & 
361                                         0x0000ff) / 255.0) * 65535.0);
362         PangoAttribute *fcolor, *ucolor, *unline;
363         
364         if(prefs_common.misspelled_col != 0) {
365                 fcolor = pango_attr_foreground_new(red, green, blue);
366                 fcolor->start_index = start;
367                 fcolor->end_index = end;
368                 
369                 pango_attr_list_insert(entry->priv->attr_list, fcolor);
370         } else {
371                 ucolor = pango_attr_underline_color_new (65535, 0, 0);
372                 unline = pango_attr_underline_new (PANGO_UNDERLINE_ERROR);
373
374                 ucolor->start_index = start;
375                 unline->start_index = start;
376
377                 ucolor->end_index = end;
378                 unline->end_index = end;
379
380                 pango_attr_list_insert (entry->priv->attr_list, ucolor);
381                 pango_attr_list_insert (entry->priv->attr_list, unline);
382         }
383 }
384
385 static gboolean check_word(ClawsSpellEntry *entry, int start, int end)
386 {
387         GtkAspell *gtkaspell = entry->gtkaspell;
388         PangoAttrIterator *it;
389         gint s, e;
390         gboolean misspelled;
391         gchar *text = gtk_editable_get_chars(GTK_EDITABLE(entry), 0, -1);
392         gchar *word = NULL;
393
394         /* Check to see if we've got any attributes at this position.
395          * If so, free them, since we'll readd it if the word is misspelled */
396         it = pango_attr_list_get_iterator(entry->priv->attr_list);
397         if (it == NULL)
398                 return FALSE;
399         do {
400                 pango_attr_iterator_range(it, &s, &e);
401                 if (s == start) {
402                         GSList *attrs = pango_attr_iterator_get_attrs(it);
403                         g_slist_foreach(attrs, (GFunc) pango_attribute_destroy, NULL);
404                         g_slist_free(attrs);
405                 }
406         } while (pango_attr_iterator_next(it));
407         pango_attr_iterator_destroy(it);
408
409         if ((misspelled = word_misspelled(entry, start, end))) {
410                 insert_misspelled_marker(entry, start, end);
411         
412                 word = get_word(entry, start, end);
413                 strncpy(gtkaspell->theword, (gchar *)word, GTKASPELLWORDSIZE - 1);
414                 gtkaspell->theword[GTKASPELLWORDSIZE - 1] = 0;
415                 gtkaspell->start_pos  = g_utf8_pointer_to_offset(text, (text+start));
416                 gtkaspell->end_pos    = g_utf8_pointer_to_offset(text, (text+end));
417                 gtkaspell_free_suggestions_list(gtkaspell);
418                 g_free(word);
419         }
420         
421         g_free(text);
422         
423         return misspelled;
424 }
425
426 void claws_spell_entry_recheck_all(ClawsSpellEntry *entry)
427 {
428         GdkRectangle rect;
429         PangoLayout *layout;
430         int length, i;
431
432         cm_return_if_fail(CLAWS_IS_SPELL_ENTRY(entry));
433         cm_return_if_fail(entry->gtkaspell != NULL);
434
435         if (entry->priv->words == NULL)
436                 return;
437
438         /* Remove all existing pango attributes.  These will get readded as we check */
439         pango_attr_list_unref(entry->priv->attr_list);
440         entry->priv->attr_list = pango_attr_list_new();
441
442         /* Loop through words */
443         for (i = 0; entry->priv->words[i]; i++) {
444                 length = strlen(entry->priv->words[i]);
445                 if (length == 0)
446                         continue;
447                 check_word(entry, entry->priv->word_starts[i], entry->priv->word_ends[i]);
448         }
449
450         layout = gtk_entry_get_layout(GTK_ENTRY(entry));
451         pango_layout_set_attributes(layout, entry->priv->attr_list);
452
453         if (gtkut_widget_get_realized(GTK_WIDGET(entry))) {
454                 rect.x = 0; rect.y = 0;
455                 rect.width  = GTK_WIDGET(entry)->allocation.width;
456                 rect.height = GTK_WIDGET(entry)->allocation.height;
457                 gdk_window_invalidate_rect(GTK_WIDGET(entry)->window, &rect, TRUE);
458         }
459 }
460
461 #if !GTK_CHECK_VERSION(3, 0, 0)
462 static gint claws_spell_entry_expose(GtkWidget *widget, GdkEventExpose *event)
463 #else
464 static gint claws_spell_entry_expose(GtkWidget *widget, cairo_t *cr)
465 #endif
466 {
467         ClawsSpellEntry *entry = CLAWS_SPELL_ENTRY(widget);
468         GtkEntry *gtk_entry = GTK_ENTRY(widget);
469         PangoLayout *layout;
470
471         if (entry->gtkaspell != NULL) {
472                 layout = gtk_entry_get_layout(gtk_entry);
473                 pango_layout_set_attributes(layout, entry->priv->attr_list);
474         }
475
476 #if !GTK_CHECK_VERSION(3, 0, 0)
477         return GTK_WIDGET_CLASS(parent_class)->expose_event (widget, event);
478 #else
479         return GTK_WIDGET_CLASS(parent_class)->draw (widget, cr);
480 #endif
481 }
482
483 static gint claws_spell_entry_button_press(GtkWidget *widget, GdkEventButton *event)
484 {
485         ClawsSpellEntry *entry = CLAWS_SPELL_ENTRY(widget);
486         GtkEntry *gtk_entry = GTK_ENTRY(widget);
487         gint pos;
488
489         pos = gtk_entry_find_position(gtk_entry, event->x);
490         entry->priv->mark_character = pos;
491
492         return GTK_WIDGET_CLASS(parent_class)->button_press_event (widget, event);
493 }
494
495 static gboolean claws_spell_entry_popup_menu(GtkWidget *widget, ClawsSpellEntry *entry)
496 {
497         entry->priv->mark_character = gtk_editable_get_position (GTK_EDITABLE (entry));
498         return FALSE;
499 }
500
501 static void set_position(gpointer data, gint pos)
502 {
503         gtk_editable_set_position(GTK_EDITABLE(data), pos);
504 }
505
506 static gboolean find_misspelled_cb(gpointer data, gboolean forward)
507 {
508         ClawsSpellEntry *entry = (ClawsSpellEntry *)data;
509         GtkAspell *gtkaspell = entry->gtkaspell;
510         gboolean misspelled = FALSE;
511         gint cursor, minpos, maxpos, i, words_len = 0;
512         gint start, end;
513         gchar *text;
514         
515         if (entry->priv->words == NULL)
516                 return FALSE;
517
518         gtkaspell->orig_pos = gtk_editable_get_position(GTK_EDITABLE(entry));
519         text = gtk_editable_get_chars(GTK_EDITABLE(entry), 0, -1);
520         cursor = g_utf8_offset_to_pointer(text, gtkaspell->orig_pos) - text;
521
522         if (gtk_editable_get_selection_bounds(GTK_EDITABLE(entry), &start, &end)) {
523                 minpos = g_utf8_offset_to_pointer(text, start) - text;
524                 maxpos = g_utf8_offset_to_pointer(text, end) - text;
525         } else {
526                 minpos = forward ? cursor : 0;
527                 maxpos = forward ? strlen(text)-1 : cursor;
528         }
529         g_free(text);
530
531         while(entry->priv->words[words_len])
532                 words_len++;
533
534         if (forward) {
535                 for(i=0; i < words_len; i++)
536                         if (entry->priv->word_ends[i] > minpos &&
537                             (misspelled = check_word(entry,
538                                         entry->priv->word_starts[i],
539                                         entry->priv->word_ends[i])))
540                                 break;
541         } else {
542                 for(i=words_len-1; i >= 0; i--)
543                         if (entry->priv->word_starts[i] < maxpos &&
544                             (misspelled = check_word(entry,
545                                         entry->priv->word_starts[i],
546                                         entry->priv->word_ends[i])))
547                                 break;
548         }
549         
550         return misspelled;
551 }
552
553 static gboolean check_word_cb(gpointer data)
554 {
555         ClawsSpellEntry *entry = (ClawsSpellEntry *)data;
556         gint start, end;
557         
558         get_word_extents_from_position(entry, &start, &end, entry->priv->mark_character);
559         return check_word(entry, start, end);
560 }
561
562 static void replace_word_cb(gpointer data, const gchar *newword)
563 {
564         replace_word((ClawsSpellEntry *) data, newword);
565 }
566
567 static void set_menu_pos(GtkMenu *menu, gint *x, gint *y, 
568                          gboolean *push_in, gpointer data)
569 {
570         ClawsSpellEntry *entry = (ClawsSpellEntry *) data;
571         GtkAspell *gtkaspell = entry->gtkaspell;
572         gint pango_offset, win_x, win_y, scr_x, scr_y, text_index, entry_x;
573         gchar *text;
574         GtkRequisition subject_rq;
575         PangoLayout *layout = gtk_entry_get_layout(GTK_ENTRY(entry));
576         PangoLayoutLine *line = pango_layout_get_lines(layout)->data;
577
578         gtk_widget_get_child_requisition(GTK_WIDGET(entry), &subject_rq);
579         
580         /* screen -> compose window coords */
581         gdk_window_get_origin(GTK_WIDGET(gtkaspell->parent_window)->window,
582                                 &scr_x, &scr_y);
583
584         /* compose window -> subject entry coords */
585         gtk_widget_translate_coordinates(GTK_WIDGET(entry),
586                         gtkaspell->parent_window, 0, 0, &win_x, &win_y);
587
588         text = gtk_editable_get_chars(GTK_EDITABLE(entry), 0, -1);
589         text_index = g_utf8_offset_to_pointer(text, gtkaspell->end_pos) - text;
590         g_free(text);
591
592         pango_offset = gtk_entry_text_index_to_layout_index(GTK_ENTRY(entry),
593                                         text_index);
594         pango_layout_line_index_to_x(line, pango_offset, TRUE, &entry_x);
595
596         *x = scr_x + win_x + PANGO_PIXELS(entry_x) + 8;
597         *y = scr_y + win_y + subject_rq.height;
598 }
599
600 void claws_spell_entry_context_set(ClawsSpellEntry *entry)
601 {
602         cm_return_if_fail(CLAWS_IS_SPELL_ENTRY(entry));
603         cm_return_if_fail(entry->gtkaspell != NULL);
604
605         entry->gtkaspell->ctx.set_position      = set_position;
606         entry->gtkaspell->ctx.set_menu_pos      = set_menu_pos;
607         entry->gtkaspell->ctx.find_misspelled   = find_misspelled_cb;
608         entry->gtkaspell->ctx.check_word        = check_word_cb;
609         entry->gtkaspell->ctx.replace_word      = replace_word_cb;
610         entry->gtkaspell->ctx.data              = (gpointer) entry;
611 }
612
613 static void claws_spell_entry_populate_popup(ClawsSpellEntry *entry, GtkMenu *menu,
614                                                 gpointer data)
615 {
616         GtkAspell *gtkaspell = entry->gtkaspell;
617         gint start, end;
618         gchar *word, *text;
619         
620         if (gtkaspell == NULL)
621                 return;
622         
623         get_word_extents_from_position(entry, &start, &end, entry->priv->mark_character);
624
625         if ((word = get_word(entry, start, end)) != NULL) {
626                 strncpy(gtkaspell->theword, word, GTKASPELLWORDSIZE - 1);
627                 g_free(word);
628         }
629
630         gtkaspell->misspelled = word_misspelled(entry, start, end);
631
632         text = gtk_editable_get_chars(GTK_EDITABLE(entry), 0, -1);
633         gtkaspell->start_pos  = g_utf8_pointer_to_offset(text, (text+start));
634         gtkaspell->end_pos    = g_utf8_pointer_to_offset(text, (text+end));
635         g_free(text);
636
637         claws_spell_entry_context_set(entry);
638         gtkaspell_make_context_menu(menu, gtkaspell);
639 }
640
641 static void claws_spell_entry_changed(GtkEditable *editable, gpointer data)
642 {
643         ClawsSpellEntry *entry = CLAWS_SPELL_ENTRY(editable);
644
645         if (entry->gtkaspell == NULL)
646                 return;
647
648         if (entry->priv->words) {
649                 g_strfreev(entry->priv->words);
650                 g_free(entry->priv->word_starts);
651                 g_free(entry->priv->word_ends);
652         }
653         entry_strsplit_utf8(GTK_ENTRY(entry), &entry->priv->words, 
654                         &entry->priv->word_starts, &entry->priv->word_ends);
655         if(entry->gtkaspell->check_while_typing == TRUE)
656                 claws_spell_entry_recheck_all(entry);
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 */