2 * @file libsexy/sexy-icon-entry.c Entry widget
4 * @Copyright (C) 2004-2006 Christian Hammond.
5 * Some of this code is from gtkspell, Copyright (C) 2002 Evan Martin.
7 * This library is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU Lesser General Public
9 * License as published by the Free Software Foundation; either
10 * version 2 of the License, or (at your option) any later version.
12 * This library is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * Lesser General Public License for more details.
17 * You should have received a copy of the GNU Lesser General Public
18 * License along with this library; if not, write to the
19 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
20 * Boston, MA 02111-1307, USA.
24 * Adapted for Claws Mail (c) 2009 Pawel Pekala and the Claws Mail team
26 * The adaptations for Claws Mail are released under the terms of the
27 * GNU General Public License as published by the Free Software Foundation;
28 * either version 3 of the License, or (at your option) any later version.
38 #include <glib/gi18n.h>
41 #include <gdk/gdkkeysyms.h>
47 #include "spell_entry.h"
48 #include "prefs_common.h"
52 static void claws_spell_entry_init (ClawsSpellEntry *entry);
53 static void claws_spell_entry_editable_init (GtkEditableClass *iface);
54 static void claws_spell_entry_finalize (GObject *object);
55 static void claws_spell_entry_destroy (GtkObject *object);
56 static gint claws_spell_entry_expose (GtkWidget *widget,
57 GdkEventExpose *event);
58 static gint claws_spell_entry_button_press (GtkWidget *widget,
59 GdkEventButton *event);
60 static gboolean claws_spell_entry_popup_menu (GtkWidget *widget,
61 ClawsSpellEntry *entry);
62 static void claws_spell_entry_populate_popup (ClawsSpellEntry *entry,
65 static void claws_spell_entry_changed (GtkEditable *editable,
68 struct _ClawsSpellEntryPriv
70 PangoAttrList *attr_list;
77 static GtkEntryClass *parent_class = NULL;
80 G_DEFINE_TYPE_EXTENDED(ClawsSpellEntry, claws_spell_entry, GTK_TYPE_ENTRY, 0, G_IMPLEMENT_INTERFACE(GTK_TYPE_EDITABLE, claws_spell_entry_editable_init));
83 static void claws_spell_entry_class_init(ClawsSpellEntryClass *klass)
85 GObjectClass *g_object_class;
86 GtkObjectClass *gtk_object_class;
87 GtkWidgetClass *widget_class;
89 parent_class = g_type_class_peek_parent(klass);
91 g_object_class = G_OBJECT_CLASS(klass);
92 g_object_class->finalize = claws_spell_entry_finalize;
94 gtk_object_class = GTK_OBJECT_CLASS(klass);
95 gtk_object_class->destroy = claws_spell_entry_destroy;
97 widget_class = GTK_WIDGET_CLASS(klass);
98 widget_class->expose_event = claws_spell_entry_expose;
99 widget_class->button_press_event = claws_spell_entry_button_press;
101 g_type_class_add_private(g_object_class,
102 sizeof(struct _ClawsSpellEntryPriv));
105 static void claws_spell_entry_init(ClawsSpellEntry *entry)
107 entry->gtkaspell = NULL;
109 entry->priv = g_new0(ClawsSpellEntryPriv, 1);
110 entry->priv->attr_list = pango_attr_list_new();
112 g_signal_connect(G_OBJECT(entry), "popup-menu",
113 G_CALLBACK(claws_spell_entry_popup_menu), entry);
114 g_signal_connect(G_OBJECT(entry), "populate-popup",
115 G_CALLBACK(claws_spell_entry_populate_popup), NULL);
116 g_signal_connect(G_OBJECT(entry), "changed",
117 G_CALLBACK(claws_spell_entry_changed), NULL);
120 static void claws_spell_entry_editable_init (GtkEditableClass *iface) {}
122 static void claws_spell_entry_finalize(GObject *object)
124 ClawsSpellEntry *entry = CLAWS_SPELL_ENTRY(object);
126 if (entry->priv->attr_list)
127 pango_attr_list_unref(entry->priv->attr_list);
128 if (entry->priv->words)
129 g_strfreev(entry->priv->words);
131 g_free(entry->priv->word_starts);
132 g_free(entry->priv->word_ends);
136 G_OBJECT_CLASS(parent_class)->finalize(object);
139 static void claws_spell_entry_destroy(GtkObject *object)
141 GTK_OBJECT_CLASS(parent_class)->destroy(object);
144 GtkWidget *claws_spell_entry_new(void)
146 return GTK_WIDGET( g_object_new(CLAWS_TYPE_SPELL_ENTRY, NULL) );
149 void claws_spell_entry_set_gtkaspell(ClawsSpellEntry *entry, GtkAspell *gtkaspell)
151 g_return_if_fail(CLAWS_IS_SPELL_ENTRY(entry));
152 g_return_if_fail(gtkaspell != NULL);
154 entry->gtkaspell = gtkaspell;
157 static gint gtk_entry_find_position (GtkEntry *entry, gint x)
160 PangoLayoutLine *line;
167 x = x + entry->scroll_offset;
169 layout = gtk_entry_get_layout(entry);
170 text = pango_layout_get_text(layout);
171 cursor_index = g_utf8_offset_to_pointer(text, entry->current_pos) - text;
173 line = pango_layout_get_lines(layout)->data;
174 pango_layout_line_x_to_index(line, x * PANGO_SCALE, &index, &trailing);
176 if (index >= cursor_index && entry->preedit_length) {
177 if (index >= cursor_index + entry->preedit_length) {
178 index -= entry->preedit_length;
180 index = cursor_index;
185 pos = g_utf8_pointer_to_offset (text, text + index);
191 static void get_word_extents_from_position(ClawsSpellEntry *entry, gint *start,
192 gint *end, guint position)
200 if (entry->priv->words == NULL)
203 text = gtk_entry_get_text(GTK_ENTRY(entry));
204 bytes_pos = (gint) (g_utf8_offset_to_pointer(text, position) - text);
206 for (i = 0; entry->priv->words[i]; i++) {
207 if (bytes_pos >= entry->priv->word_starts[i] &&
208 bytes_pos <= entry->priv->word_ends[i]) {
209 *start = entry->priv->word_starts[i];
210 *end = entry->priv->word_ends[i];
216 static gchar *get_word(ClawsSpellEntry *entry, const int start, const int end)
224 text = gtk_entry_get_text(GTK_ENTRY(entry));
225 word = g_new0(gchar, end - start + 2);
226 g_strlcpy(word, text + start, end - start + 1);
231 static void replace_word(ClawsSpellEntry *entry, const gchar *newword)
233 gint cursor, start_pos, end_pos;
234 const gchar *text = gtk_entry_get_text(GTK_ENTRY(entry));
236 start_pos = entry->gtkaspell->start_pos;
237 end_pos = entry->gtkaspell->end_pos;
239 cursor = gtk_editable_get_position(GTK_EDITABLE(entry));
240 /* is the cursor at the end? If so, restore it there */
241 if (g_utf8_strlen(text, -1) == cursor)
243 else if(cursor < entry->priv->mark_character ||
244 cursor > entry->priv->mark_character)
245 cursor = entry->priv->mark_character;
247 gtk_editable_delete_text(GTK_EDITABLE(entry), start_pos, end_pos);
248 gtk_editable_insert_text(GTK_EDITABLE(entry), newword, strlen(newword),
250 gtk_editable_set_position(GTK_EDITABLE(entry), cursor);
254 static gboolean word_misspelled(ClawsSpellEntry *entry, int start, int end)
259 word = get_word(entry, start, end);
263 ret = gtkaspell_misspelled_test(entry->gtkaspell, word);
269 static void entry_strsplit_utf8(GtkEntry *entry, gchar ***set, gint **starts, gint **ends)
272 PangoLogAttr *log_attrs;
274 gint n_attrs, n_strings, i, j;
276 layout = gtk_entry_get_layout(GTK_ENTRY(entry));
277 text = gtk_entry_get_text(GTK_ENTRY(entry));
278 pango_layout_get_log_attrs(layout, &log_attrs, &n_attrs);
280 /* Find how many words we have */
282 for (i = 0; i < n_attrs; i++)
283 if (log_attrs[i].is_word_start)
286 *set = g_new0(gchar *, n_strings + 1);
287 *starts = g_new0(gint, n_strings);
288 *ends = g_new0(gint, n_strings);
290 /* Copy out strings */
291 for (i = 0, j = 0; i < n_attrs; i++) {
292 if (log_attrs[i].is_word_start) {
296 /* Find the end of this string */
298 while (!(log_attrs[cend].is_word_end))
301 /* Copy sub-string */
302 start = g_utf8_offset_to_pointer(text, i);
303 bytes = (gint) (g_utf8_offset_to_pointer(text, cend) - start);
304 (*set)[j] = g_new0(gchar, bytes + 1);
305 (*starts)[j] = (gint) (start - text);
306 (*ends)[j] = (gint) (start - text + bytes);
307 g_utf8_strncpy((*set)[j], start, cend - i);
309 /* Move on to the next word */
317 static void insert_misspelled_marker(ClawsSpellEntry *entry, guint start, guint end)
319 guint16 red = (guint16) (((gdouble)((prefs_common.misspelled_col &
320 0xff0000) >> 16) / 255.0) * 65535.0);
321 guint16 green = (guint16) (((gdouble)((prefs_common.misspelled_col &
322 0x00ff00) >> 8) / 255.0) * 65535.0);
323 guint16 blue = (guint16) (((gdouble) (prefs_common.misspelled_col &
324 0x0000ff) / 255.0) * 65535.0);
325 PangoAttribute *fcolor, *ucolor, *unline;
327 if(prefs_common.misspelled_col != 0) {
328 fcolor = pango_attr_foreground_new(red, green, blue);
329 fcolor->start_index = start;
330 fcolor->end_index = end;
332 pango_attr_list_insert(entry->priv->attr_list, fcolor);
334 ucolor = pango_attr_underline_color_new (65535, 0, 0);
335 unline = pango_attr_underline_new (PANGO_UNDERLINE_ERROR);
337 ucolor->start_index = start;
338 unline->start_index = start;
340 ucolor->end_index = end;
341 unline->end_index = end;
343 pango_attr_list_insert (entry->priv->attr_list, ucolor);
344 pango_attr_list_insert (entry->priv->attr_list, unline);
348 static gboolean check_word(ClawsSpellEntry *entry, int start, int end)
350 GtkAspell *gtkaspell = entry->gtkaspell;
351 PangoAttrIterator *it;
354 gchar *text = gtk_editable_get_chars(GTK_EDITABLE(entry), 0, -1);
357 /* Check to see if we've got any attributes at this position.
358 * If so, free them, since we'll readd it if the word is misspelled */
359 it = pango_attr_list_get_iterator(entry->priv->attr_list);
363 pango_attr_iterator_range(it, &s, &e);
365 GSList *attrs = pango_attr_iterator_get_attrs(it);
366 g_slist_foreach(attrs, (GFunc) pango_attribute_destroy, NULL);
369 } while (pango_attr_iterator_next(it));
370 pango_attr_iterator_destroy(it);
372 if ((misspelled = word_misspelled(entry, start, end))) {
373 insert_misspelled_marker(entry, start, end);
375 word = get_word(entry, start, end);
376 strncpy(gtkaspell->theword, (gchar *)word, GTKASPELLWORDSIZE - 1);
377 gtkaspell->theword[GTKASPELLWORDSIZE - 1] = 0;
378 gtkaspell->start_pos = g_utf8_pointer_to_offset(text, (text+start));
379 gtkaspell->end_pos = g_utf8_pointer_to_offset(text, (text+end));
380 gtkaspell_free_suggestions_list(gtkaspell);
389 void claws_spell_entry_recheck_all(ClawsSpellEntry *entry)
395 g_return_if_fail(CLAWS_IS_SPELL_ENTRY(entry));
396 g_return_if_fail(entry->gtkaspell != NULL);
398 if (entry->priv->words == NULL)
401 /* Remove all existing pango attributes. These will get readded as we check */
402 pango_attr_list_unref(entry->priv->attr_list);
403 entry->priv->attr_list = pango_attr_list_new();
405 /* Loop through words */
406 for (i = 0; entry->priv->words[i]; i++) {
407 length = strlen(entry->priv->words[i]);
410 check_word(entry, entry->priv->word_starts[i], entry->priv->word_ends[i]);
413 layout = gtk_entry_get_layout(GTK_ENTRY(entry));
414 pango_layout_set_attributes(layout, entry->priv->attr_list);
416 if (GTK_WIDGET_REALIZED(GTK_WIDGET(entry))) {
417 rect.x = 0; rect.y = 0;
418 rect.width = GTK_WIDGET(entry)->allocation.width;
419 rect.height = GTK_WIDGET(entry)->allocation.height;
420 gdk_window_invalidate_rect(GTK_WIDGET(entry)->window, &rect, TRUE);
424 static gint claws_spell_entry_expose(GtkWidget *widget, GdkEventExpose *event)
426 ClawsSpellEntry *entry = CLAWS_SPELL_ENTRY(widget);
427 GtkEntry *gtk_entry = GTK_ENTRY(widget);
430 if (entry->gtkaspell != NULL) {
431 layout = gtk_entry_get_layout(gtk_entry);
432 pango_layout_set_attributes(layout, entry->priv->attr_list);
435 return GTK_WIDGET_CLASS(parent_class)->expose_event (widget, event);
438 static gint claws_spell_entry_button_press(GtkWidget *widget, GdkEventButton *event)
440 ClawsSpellEntry *entry = CLAWS_SPELL_ENTRY(widget);
441 GtkEntry *gtk_entry = GTK_ENTRY(widget);
444 pos = gtk_entry_find_position(gtk_entry, event->x);
445 entry->priv->mark_character = pos;
447 return GTK_WIDGET_CLASS(parent_class)->button_press_event (widget, event);
450 static gboolean claws_spell_entry_popup_menu(GtkWidget *widget, ClawsSpellEntry *entry)
452 entry->priv->mark_character = gtk_editable_get_position (GTK_EDITABLE (entry));
456 static void set_position(gpointer data, gint pos)
458 gtk_editable_set_position(GTK_EDITABLE(data), pos);
461 static gboolean find_misspelled_cb(gpointer data, gboolean forward)
463 ClawsSpellEntry *entry = (ClawsSpellEntry *)data;
464 GtkAspell *gtkaspell = entry->gtkaspell;
465 gboolean misspelled = FALSE;
466 gint cursor, minpos, maxpos, i, words_len = 0;
470 if (entry->priv->words == NULL)
473 gtkaspell->orig_pos = gtk_editable_get_position(GTK_EDITABLE(entry));
474 text = gtk_editable_get_chars(GTK_EDITABLE(entry), 0, -1);
475 cursor = g_utf8_offset_to_pointer(text, gtkaspell->orig_pos) - text;
477 if (gtk_editable_get_selection_bounds(GTK_EDITABLE(entry), &start, &end)) {
478 minpos = g_utf8_offset_to_pointer(text, start) - text;
479 maxpos = g_utf8_offset_to_pointer(text, end) - text;
481 minpos = forward ? cursor : 0;
482 maxpos = forward ? strlen(text)-1 : cursor;
486 while(entry->priv->words[words_len])
490 for(i=0; i < words_len; i++)
491 if (entry->priv->word_ends[i] > minpos &&
492 (misspelled = check_word(entry,
493 entry->priv->word_starts[i],
494 entry->priv->word_ends[i])))
497 for(i=words_len-1; i >= 0; i--)
498 if (entry->priv->word_starts[i] < maxpos &&
499 (misspelled = check_word(entry,
500 entry->priv->word_starts[i],
501 entry->priv->word_ends[i])))
508 static gboolean check_word_cb(gpointer data)
510 ClawsSpellEntry *entry = (ClawsSpellEntry *)data;
513 get_word_extents_from_position(entry, &start, &end, entry->priv->mark_character);
514 return check_word(entry, start, end);
517 static void replace_word_cb(gpointer data, const gchar *newword)
519 replace_word((ClawsSpellEntry *) data, newword);
522 static void set_menu_pos(GtkMenu *menu, gint *x, gint *y,
523 gboolean *push_in, gpointer data)
525 ClawsSpellEntry *entry = (ClawsSpellEntry *) data;
526 GtkAspell *gtkaspell = entry->gtkaspell;
527 gint pango_offset, win_x, win_y, scr_x, scr_y, text_index, entry_x;
529 GtkRequisition subject_rq;
530 PangoLayout *layout = gtk_entry_get_layout(GTK_ENTRY(entry));
531 PangoLayoutLine *line = pango_layout_get_lines(layout)->data;
533 gtk_widget_get_child_requisition(GTK_WIDGET(entry), &subject_rq);
535 /* screen -> compose window coords */
536 gdk_window_get_origin(GTK_WIDGET(gtkaspell->parent_window)->window,
539 /* compose window -> subject entry coords */
540 gtk_widget_translate_coordinates(GTK_WIDGET(entry),
541 gtkaspell->parent_window, 0, 0, &win_x, &win_y);
543 text = gtk_editable_get_chars(GTK_EDITABLE(entry), 0, -1);
544 text_index = g_utf8_offset_to_pointer(text, gtkaspell->end_pos) - text;
547 pango_offset = gtk_entry_text_index_to_layout_index(GTK_ENTRY(entry),
549 pango_layout_line_index_to_x(line, pango_offset, TRUE, &entry_x);
551 *x = scr_x + win_x + PANGO_PIXELS(entry_x) + 8;
552 *y = scr_y + win_y + subject_rq.height;
555 void claws_spell_entry_context_set(ClawsSpellEntry *entry)
557 g_return_if_fail(CLAWS_IS_SPELL_ENTRY(entry));
558 g_return_if_fail(entry->gtkaspell != NULL);
560 entry->gtkaspell->ctx.set_position = set_position;
561 entry->gtkaspell->ctx.set_menu_pos = set_menu_pos;
562 entry->gtkaspell->ctx.find_misspelled = find_misspelled_cb;
563 entry->gtkaspell->ctx.check_word = check_word_cb;
564 entry->gtkaspell->ctx.replace_word = replace_word_cb;
565 entry->gtkaspell->ctx.data = (gpointer) entry;
568 static void claws_spell_entry_populate_popup(ClawsSpellEntry *entry, GtkMenu *menu,
571 GtkAspell *gtkaspell = entry->gtkaspell;
575 if (gtkaspell == NULL)
578 get_word_extents_from_position(entry, &start, &end, entry->priv->mark_character);
580 if ((word = get_word(entry, start, end)) != NULL) {
581 strncpy(gtkaspell->theword, word, GTKASPELLWORDSIZE - 1);
585 gtkaspell->misspelled = word_misspelled(entry, start, end);
587 claws_spell_entry_context_set(entry);
588 gtkaspell_make_context_menu(menu, gtkaspell);
591 static void claws_spell_entry_changed(GtkEditable *editable, gpointer data)
593 ClawsSpellEntry *entry = CLAWS_SPELL_ENTRY(editable);
595 if (entry->gtkaspell == NULL)
598 if (entry->priv->words) {
599 g_strfreev(entry->priv->words);
600 g_free(entry->priv->word_starts);
601 g_free(entry->priv->word_ends);
603 entry_strsplit_utf8(GTK_ENTRY(entry), &entry->priv->words,
604 &entry->priv->word_starts, &entry->priv->word_ends);
605 if(entry->gtkaspell->check_while_typing == TRUE)
606 claws_spell_entry_recheck_all(entry);
609 static void continue_check(gpointer *data)
611 ClawsSpellEntry *entry = (ClawsSpellEntry *)data;
612 GtkAspell *gtkaspell = entry->gtkaspell;
613 gint pos = gtk_editable_get_position(GTK_EDITABLE(entry));
615 if (gtkaspell->misspelled && pos < gtkaspell->end_check_pos)
616 gtkaspell->misspelled = gtkaspell_check_next_prev(gtkaspell, TRUE);
618 gtkaspell->continue_check = NULL;
621 void claws_spell_entry_check_all(ClawsSpellEntry *entry)
626 g_return_if_fail(CLAWS_IS_SPELL_ENTRY(entry));
627 g_return_if_fail(entry->gtkaspell != NULL);
629 if (!gtk_editable_get_selection_bounds(GTK_EDITABLE(entry), &start, &end)) {
630 text = gtk_editable_get_chars(GTK_EDITABLE(entry), 0, -1);
633 end = g_utf8_strlen(text, -1) - 1;
638 gtk_editable_set_position(GTK_EDITABLE(entry), start);
639 entry->gtkaspell->continue_check = continue_check;
640 entry->gtkaspell->end_check_pos = end;
642 claws_spell_entry_context_set(entry);
643 entry->gtkaspell->misspelled =
644 gtkaspell_check_next_prev(entry->gtkaspell, TRUE);
647 void claws_spell_entry_check_backwards(ClawsSpellEntry *entry)
649 g_return_if_fail(CLAWS_IS_SPELL_ENTRY(entry));
650 g_return_if_fail(entry->gtkaspell != NULL);
652 entry->gtkaspell->continue_check = NULL;
653 claws_spell_entry_context_set(entry);
654 gtkaspell_check_next_prev(entry->gtkaspell, FALSE);
657 void claws_spell_entry_check_forwards_go(ClawsSpellEntry *entry)
659 g_return_if_fail(CLAWS_IS_SPELL_ENTRY(entry));
660 g_return_if_fail(entry->gtkaspell != NULL);
662 entry->gtkaspell->continue_check = NULL;
663 claws_spell_entry_context_set(entry);
664 gtkaspell_check_next_prev(entry->gtkaspell, TRUE);
667 #endif /* USE_ENCHANT */