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