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