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