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)
G_DEFINE_TYPE(ClawsSpellEntry,claws_spell_entry,GTK_TYPE_ENTRY)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 
claws_spell_entry_init(ClawsSpellEntry * entry)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 
claws_spell_entry_destroy(GtkObject * object)130 static void claws_spell_entry_destroy(GtkObject *object)
131 {
132 	GTK_OBJECT_CLASS(parent_class)->destroy(object);
133 }
134 
claws_spell_entry_new(void)135 GtkWidget *claws_spell_entry_new(void)
136 {
137 	return GTK_WIDGET( g_object_new(CLAWS_TYPE_SPELL_ENTRY, NULL) );
138 }
139 
claws_spell_entry_set_gtkaspell(ClawsSpellEntry * entry,GtkAspell * gtkaspell)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 
claws_spell_entry_find_position(ClawsSpellEntry * _entry,gint x)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 
get_word_extents_from_position(ClawsSpellEntry * entry,gint * start,gint * end,guint position)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 
get_word(ClawsSpellEntry * entry,const int start,const int end)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 
replace_word(ClawsSpellEntry * entry,const gchar * newword)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 
word_misspelled(ClawsSpellEntry * entry,int start,int end)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 
is_word_end(GtkEntry * entry,const int offset)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 
entry_strsplit_utf8(GtkEntry * entry,gchar *** set,gint ** starts,gint ** ends)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 
insert_misspelled_marker(ClawsSpellEntry * entry,guint start,guint end)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 
check_word(ClawsSpellEntry * entry,int start,int end)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 
claws_spell_entry_recheck_all(ClawsSpellEntry * entry)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 
claws_spell_entry_expose(GtkWidget * widget,GdkEventExpose * event)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 
claws_spell_entry_button_press(GtkWidget * widget,GdkEventButton * event)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 
claws_spell_entry_popup_menu(GtkWidget * widget,ClawsSpellEntry * entry)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 
set_position(gpointer data,gint pos)485 static void set_position(gpointer data, gint pos)
486 {
487 	gtk_editable_set_position(GTK_EDITABLE(data), pos);
488 }
489 
find_misspelled_cb(gpointer data,gboolean forward)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 
check_word_cb(gpointer data)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 
replace_word_cb(gpointer data,const gchar * newword)548 static void replace_word_cb(gpointer data, const gchar *newword)
549 {
550 	replace_word((ClawsSpellEntry *) data, newword);
551 }
552 
set_menu_pos(GtkMenu * menu,gint * x,gint * y,gboolean * push_in,gpointer data)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 
claws_spell_entry_context_set(ClawsSpellEntry * entry)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 
claws_spell_entry_populate_popup(ClawsSpellEntry * entry,GtkMenu * menu,gpointer data)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 
claws_spell_entry_changed(GtkEditable * editable,gpointer data)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 
claws_spell_entry_preedit_changed(GtkEntry * _entry,gchar * preedit,gpointer data)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 
continue_check(gpointer * data)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 
claws_spell_entry_check_all(ClawsSpellEntry * entry)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 
claws_spell_entry_check_backwards(ClawsSpellEntry * entry)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 
claws_spell_entry_check_forwards_go(ClawsSpellEntry * entry)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 */
716