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