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.
27 #include "sexy-spell-entry.h"
29 #include <glib/gi18n.h>
30 #include <sys/types.h>
31 /*#include "gtkspell-iso-codes.h"
32 #include "sexy-marshal.h"*/
35 * Bunch of poop to make enchant into a runtime dependency rather than a
36 * compile-time dependency. This makes it so I don't have to hear the
37 * complaints from people with binary distributions who don't get spell
38 * checking because they didn't check their configure output.
43 typedef void (*EnchantDictDescribeFn
) (const char * const lang_tag
,
44 const char * const provider_name
,
45 const char * const provider_desc
,
46 const char * const provider_file
,
49 static struct EnchantBroker
* (*enchant_broker_init
) (void);
50 static void (*enchant_broker_free
) (struct EnchantBroker
* broker
);
51 static void (*enchant_broker_free_dict
) (struct EnchantBroker
* broker
, struct EnchantDict
* dict
);
52 static void (*enchant_broker_list_dicts
) (struct EnchantBroker
* broker
, EnchantDictDescribeFn fn
, void * user_data
);
53 static struct EnchantDict
* (*enchant_broker_request_dict
) (struct EnchantBroker
* broker
, const char *const tag
);
55 static void (*enchant_dict_add_to_personal
) (struct EnchantDict
* dict
, const char *const word
, ssize_t len
);
56 static void (*enchant_dict_add_to_session
) (struct EnchantDict
* dict
, const char *const word
, ssize_t len
);
57 static int (*enchant_dict_check
) (struct EnchantDict
* dict
, const char *const word
, ssize_t len
);
58 static void (*enchant_dict_describe
) (struct EnchantDict
* dict
, EnchantDictDescribeFn fn
, void * user_data
);
59 static void (*enchant_dict_free_suggestions
) (struct EnchantDict
* dict
, char **suggestions
);
60 static void (*enchant_dict_store_replacement
) (struct EnchantDict
* dict
, const char *const mis
, ssize_t mis_len
, const char *const cor
, ssize_t cor_len
);
61 static char ** (*enchant_dict_suggest
) (struct EnchantDict
* dict
, const char *const word
, ssize_t len
, size_t * out_n_suggs
);
62 static gboolean have_enchant
= FALSE
;
64 struct _SexySpellEntryPriv
66 struct EnchantBroker
*broker
;
67 PangoAttrList
*attr_list
;
69 GHashTable
*dict_hash
;
77 static void sexy_spell_entry_class_init(SexySpellEntryClass
*klass
);
78 static void sexy_spell_entry_editable_init (GtkEditableClass
*iface
);
79 static void sexy_spell_entry_init(SexySpellEntry
*entry
);
80 static void sexy_spell_entry_finalize(GObject
*obj
);
81 static void sexy_spell_entry_destroy(GtkObject
*obj
);
82 static gint
sexy_spell_entry_expose(GtkWidget
*widget
, GdkEventExpose
*event
);
83 static gint
sexy_spell_entry_button_press(GtkWidget
*widget
, GdkEventButton
*event
);
85 /* GtkEditable handlers */
86 static void sexy_spell_entry_changed(GtkEditable
*editable
, gpointer data
);
89 static gboolean
sexy_spell_entry_popup_menu(GtkWidget
*widget
, SexySpellEntry
*entry
);
91 /* Internal utility functions */
92 static gint
gtk_entry_find_position (GtkEntry
*entry
,
94 static gboolean
word_misspelled (SexySpellEntry
*entry
,
97 static gboolean
default_word_check (SexySpellEntry
*entry
,
99 static gboolean
sexy_spell_entry_activate_language_internal (SexySpellEntry
*entry
,
102 static gchar
*get_lang_from_dict (struct EnchantDict
*dict
);
103 static void sexy_spell_entry_recheck_all (SexySpellEntry
*entry
);
104 static void entry_strsplit_utf8 (GtkEntry
*entry
,
109 static GtkEntryClass
*parent_class
= NULL
;
111 G_DEFINE_TYPE_EXTENDED(SexySpellEntry
, sexy_spell_entry
, GTK_TYPE_ENTRY
, 0, G_IMPLEMENT_INTERFACE(GTK_TYPE_EDITABLE
, sexy_spell_entry_editable_init
));
118 static guint signals
[LAST_SIGNAL
] = {0};
121 spell_accumulator(GSignalInvocationHint
*hint
, GValue
*return_accu
, const GValue
*handler_return
, gpointer data
)
123 gboolean ret
= g_value_get_boolean(handler_return
);
124 /* Handlers return TRUE if the word is misspelled. In this
125 * case, it means that we want to stop if the word is checked
127 g_value_set_boolean (return_accu
, ret
);
132 initialize_enchant ()
137 enchant
= g_module_open("libenchant", 0);
140 enchant
= g_module_open("libenchant.so.1", 0);
147 #define MODULE_SYMBOL(name, func) \
148 g_module_symbol(enchant, (name), &funcptr); \
151 MODULE_SYMBOL("enchant_broker_init", enchant_broker_init
)
152 MODULE_SYMBOL("enchant_broker_free", enchant_broker_free
)
153 MODULE_SYMBOL("enchant_broker_free_dict", enchant_broker_free_dict
)
154 MODULE_SYMBOL("enchant_broker_list_dicts", enchant_broker_list_dicts
)
155 MODULE_SYMBOL("enchant_broker_request_dict", enchant_broker_request_dict
)
157 MODULE_SYMBOL("enchant_dict_add_to_personal", enchant_dict_add_to_personal
)
158 MODULE_SYMBOL("enchant_dict_add_to_session", enchant_dict_add_to_session
)
159 MODULE_SYMBOL("enchant_dict_check", enchant_dict_check
)
160 MODULE_SYMBOL("enchant_dict_describe", enchant_dict_describe
)
161 MODULE_SYMBOL("enchant_dict_free_suggestions",
162 enchant_dict_free_suggestions
)
163 MODULE_SYMBOL("enchant_dict_store_replacement",
164 enchant_dict_store_replacement
)
165 MODULE_SYMBOL("enchant_dict_suggest", enchant_dict_suggest
)
171 sexy_spell_entry_class_init(SexySpellEntryClass
*klass
)
173 GObjectClass
*gobject_class
;
174 GtkObjectClass
*object_class
;
175 GtkWidgetClass
*widget_class
;
176 GtkEntryClass
*entry_class
;
178 initialize_enchant();
180 parent_class
= g_type_class_peek_parent(klass
);
182 gobject_class
= G_OBJECT_CLASS(klass
);
183 object_class
= GTK_OBJECT_CLASS(klass
);
184 widget_class
= GTK_WIDGET_CLASS(klass
);
185 entry_class
= GTK_ENTRY_CLASS(klass
);
188 klass
->word_check
= default_word_check
;
190 gobject_class
->finalize
= sexy_spell_entry_finalize
;
192 object_class
->destroy
= sexy_spell_entry_destroy
;
194 widget_class
->expose_event
= sexy_spell_entry_expose
;
195 widget_class
->button_press_event
= sexy_spell_entry_button_press
;
198 * SexySpellEntry::word-check:
199 * @entry: The entry on which the signal is emitted.
200 * @word: The word to check.
202 * The ::word-check signal is emitted whenever the entry has to check
203 * a word. This allows the application to mark words as correct even
204 * if none of the active dictionaries contain it, such as nicknames in
207 * Returns: %FALSE to indicate that the word should be marked as
210 /* signals[WORD_CHECK] = g_signal_new("word_check",
211 G_TYPE_FROM_CLASS(object_class),
213 G_STRUCT_OFFSET(SexySpellEntryClass, word_check),
214 (GSignalAccumulator) spell_accumulator, NULL,
215 sexy_marshal_BOOLEAN__STRING,
221 sexy_spell_entry_editable_init (GtkEditableClass
*iface
)
226 gtk_entry_find_position (GtkEntry
*entry
, gint x
)
229 PangoLayoutLine
*line
;
236 x
= x
+ entry
->scroll_offset
;
238 layout
= gtk_entry_get_layout(entry
);
239 text
= pango_layout_get_text(layout
);
240 cursor_index
= g_utf8_offset_to_pointer(text
, entry
->current_pos
) - text
;
242 line
= pango_layout_get_lines(layout
)->data
;
243 pango_layout_line_x_to_index(line
, x
* PANGO_SCALE
, &index
, &trailing
);
245 if (index
>= cursor_index
&& entry
->preedit_length
) {
246 if (index
>= cursor_index
+ entry
->preedit_length
) {
247 index
-= entry
->preedit_length
;
249 index
= cursor_index
;
254 pos
= g_utf8_pointer_to_offset (text
, text
+ index
);
261 insert_underline(SexySpellEntry
*entry
, guint start
, guint end
)
263 PangoAttribute
*ucolor
= pango_attr_underline_color_new (65535, 0, 0);
264 PangoAttribute
*unline
= pango_attr_underline_new (PANGO_UNDERLINE_ERROR
);
266 ucolor
->start_index
= start
;
267 unline
->start_index
= start
;
269 ucolor
->end_index
= end
;
270 unline
->end_index
= end
;
272 pango_attr_list_insert (entry
->priv
->attr_list
, ucolor
);
273 pango_attr_list_insert (entry
->priv
->attr_list
, unline
);
277 get_word_extents_from_position(SexySpellEntry
*entry
, gint
*start
, gint
*end
, guint position
)
285 if (entry
->priv
->words
== NULL
)
288 text
= gtk_entry_get_text(GTK_ENTRY(entry
));
289 bytes_pos
= (gint
) (g_utf8_offset_to_pointer(text
, position
) - text
);
291 for (i
= 0; entry
->priv
->words
[i
]; i
++) {
292 if (bytes_pos
>= entry
->priv
->word_starts
[i
] &&
293 bytes_pos
<= entry
->priv
->word_ends
[i
]) {
294 *start
= entry
->priv
->word_starts
[i
];
295 *end
= entry
->priv
->word_ends
[i
];
302 add_to_dictionary(GtkWidget
*menuitem
, SexySpellEntry
*entry
)
306 struct EnchantDict
*dict
;
311 get_word_extents_from_position(entry
, &start
, &end
, entry
->priv
->mark_character
);
312 word
= gtk_editable_get_chars(GTK_EDITABLE(entry
), start
, end
);
314 dict
= (struct EnchantDict
*) g_object_get_data(G_OBJECT(menuitem
), "enchant-dict");
316 enchant_dict_add_to_personal(dict
, word
, -1);
320 if (entry
->priv
->words
) {
321 g_strfreev(entry
->priv
->words
);
322 g_free(entry
->priv
->word_starts
);
323 g_free(entry
->priv
->word_ends
);
325 entry_strsplit_utf8(GTK_ENTRY(entry
), &entry
->priv
->words
, &entry
->priv
->word_starts
, &entry
->priv
->word_ends
);
326 sexy_spell_entry_recheck_all(entry
);
330 ignore_all(GtkWidget
*menuitem
, SexySpellEntry
*entry
)
339 get_word_extents_from_position(entry
, &start
, &end
, entry
->priv
->mark_character
);
340 word
= gtk_editable_get_chars(GTK_EDITABLE(entry
), start
, end
);
342 for (li
= entry
->priv
->dict_list
; li
; li
= g_slist_next (li
)) {
343 struct EnchantDict
*dict
= (struct EnchantDict
*) li
->data
;
344 enchant_dict_add_to_session(dict
, word
, -1);
349 if (entry
->priv
->words
) {
350 g_strfreev(entry
->priv
->words
);
351 g_free(entry
->priv
->word_starts
);
352 g_free(entry
->priv
->word_ends
);
354 entry_strsplit_utf8(GTK_ENTRY(entry
), &entry
->priv
->words
, &entry
->priv
->word_starts
, &entry
->priv
->word_ends
);
355 sexy_spell_entry_recheck_all(entry
);
359 replace_word(GtkWidget
*menuitem
, SexySpellEntry
*entry
)
365 struct EnchantDict
*dict
;
370 get_word_extents_from_position(entry
, &start
, &end
, entry
->priv
->mark_character
);
371 oldword
= gtk_editable_get_chars(GTK_EDITABLE(entry
), start
, end
);
372 newword
= gtk_label_get_text(GTK_LABEL(GTK_BIN(menuitem
)->child
));
374 cursor
= gtk_editable_get_position(GTK_EDITABLE(entry
));
375 /* is the cursor at the end? If so, restore it there */
376 if (g_utf8_strlen(gtk_entry_get_text(GTK_ENTRY(entry
)), -1) == cursor
)
378 else if(cursor
> start
&& cursor
<= end
)
381 gtk_editable_delete_text(GTK_EDITABLE(entry
), start
, end
);
382 gtk_editable_set_position(GTK_EDITABLE(entry
), start
);
383 gtk_editable_insert_text(GTK_EDITABLE(entry
), newword
, strlen(newword
),
385 gtk_editable_set_position(GTK_EDITABLE(entry
), cursor
);
387 dict
= (struct EnchantDict
*) g_object_get_data(G_OBJECT(menuitem
), "enchant-dict");
390 enchant_dict_store_replacement(dict
,
398 build_suggestion_menu(SexySpellEntry
*entry
, GtkWidget
*menu
, struct EnchantDict
*dict
, const gchar
*word
)
402 size_t n_suggestions
, i
;
407 suggestions
= enchant_dict_suggest(dict
, word
, -1, &n_suggestions
);
409 if (suggestions
== NULL
|| n_suggestions
== 0) {
410 /* no suggestions. put something in the menu anyway... */
411 GtkWidget
*label
= gtk_label_new("");
412 gtk_label_set_markup(GTK_LABEL(label
), _("<i>(no suggestions)</i>"));
414 mi
= gtk_separator_menu_item_new();
415 gtk_container_add(GTK_CONTAINER(mi
), label
);
416 gtk_widget_show_all(mi
);
417 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu
), mi
);
419 /* build a set of menus with suggestions */
420 for (i
= 0; i
< n_suggestions
; i
++) {
421 if ((i
!= 0) && (i
% 10 == 0)) {
422 mi
= gtk_separator_menu_item_new();
424 gtk_menu_shell_append(GTK_MENU_SHELL(menu
), mi
);
426 mi
= gtk_menu_item_new_with_label(_("More..."));
428 gtk_menu_shell_append(GTK_MENU_SHELL(menu
), mi
);
430 menu
= gtk_menu_new();
431 gtk_menu_item_set_submenu(GTK_MENU_ITEM(mi
), menu
);
434 mi
= gtk_menu_item_new_with_label(suggestions
[i
]);
435 g_object_set_data(G_OBJECT(mi
), "enchant-dict", dict
);
436 g_signal_connect(G_OBJECT(mi
), "activate", G_CALLBACK(replace_word
), entry
);
438 gtk_menu_shell_append(GTK_MENU_SHELL(menu
), mi
);
442 enchant_dict_free_suggestions(dict
, suggestions
);
446 build_spelling_menu(SexySpellEntry
*entry
, const gchar
*word
)
448 struct EnchantDict
*dict
;
449 GtkWidget
*topmenu
, *mi
;
455 topmenu
= gtk_menu_new();
457 if (entry
->priv
->dict_list
== NULL
)
461 dict
= (struct EnchantDict
*) entry
->priv
->dict_list
->data
;
462 build_suggestion_menu(entry
, topmenu
, dict
, word
);
465 if (g_slist_length(entry
->priv
->dict_list
) == 1) {
466 dict
= (struct EnchantDict
*) entry
->priv
->dict_list
->data
;
467 build_suggestion_menu(entry
, topmenu
, dict
, word
);
471 gchar
*lang
, *lang_name
;
473 for (li
= entry
->priv
->dict_list
; li
; li
= g_slist_next (li
)) {
474 dict
= (struct EnchantDict
*) li
->data
;
475 lang
= get_lang_from_dict(dict
);
476 lang_name
= gtkspell_iso_codes_lookup_name_for_code(lang
);
478 mi
= gtk_menu_item_new_with_label(lang_name
);
481 mi
= gtk_menu_item_new_with_label(lang
);
486 gtk_menu_shell_append(GTK_MENU_SHELL(topmenu
), mi
);
487 menu
= gtk_menu_new();
488 gtk_menu_item_set_submenu(GTK_MENU_ITEM(mi
), menu
);
489 build_suggestion_menu(entry
, menu
, dict
, word
);
495 mi
= gtk_separator_menu_item_new ();
497 gtk_menu_shell_append(GTK_MENU_SHELL(topmenu
), mi
);
499 /* + Add to Dictionary */
500 label
= g_strdup_printf(_("Add \"%s\" to Dictionary"), word
);
501 mi
= gtk_image_menu_item_new_with_label(label
);
504 gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(mi
), gtk_image_new_from_stock(GTK_STOCK_ADD
, GTK_ICON_SIZE_MENU
));
507 dict
= (struct EnchantDict
*) entry
->priv
->dict_list
->data
;
508 g_object_set_data(G_OBJECT(mi
), "enchant-dict", dict
);
509 g_signal_connect(G_OBJECT(mi
), "activate", G_CALLBACK(add_to_dictionary
), entry
);
511 if (g_slist_length(entry
->priv
->dict_list
) == 1) {
512 dict
= (struct EnchantDict
*) entry
->priv
->dict_list
->data
;
513 g_object_set_data(G_OBJECT(mi
), "enchant-dict", dict
);
514 g_signal_connect(G_OBJECT(mi
), "activate", G_CALLBACK(add_to_dictionary
), entry
);
517 GtkWidget
*menu
, *submi
;
518 gchar
*lang
, *lang_name
;
520 menu
= gtk_menu_new();
521 gtk_menu_item_set_submenu(GTK_MENU_ITEM(mi
), menu
);
523 for (li
= entry
->priv
->dict_list
; li
; li
= g_slist_next(li
)) {
524 dict
= (struct EnchantDict
*)li
->data
;
525 lang
= get_lang_from_dict(dict
);
526 lang_name
= gtkspell_iso_codes_lookup_name_for_code(lang
);
528 submi
= gtk_menu_item_new_with_label(lang_name
);
531 submi
= gtk_menu_item_new_with_label(lang
);
534 g_object_set_data(G_OBJECT(submi
), "enchant-dict", dict
);
536 g_signal_connect(G_OBJECT(submi
), "activate", G_CALLBACK(add_to_dictionary
), entry
);
538 gtk_widget_show(submi
);
539 gtk_menu_shell_append(GTK_MENU_SHELL(menu
), submi
);
544 gtk_widget_show_all(mi
);
545 gtk_menu_shell_append(GTK_MENU_SHELL(topmenu
), mi
);
548 mi
= gtk_image_menu_item_new_with_label(_("Ignore All"));
549 gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(mi
), gtk_image_new_from_stock(GTK_STOCK_REMOVE
, GTK_ICON_SIZE_MENU
));
550 g_signal_connect(G_OBJECT(mi
), "activate", G_CALLBACK(ignore_all
), entry
);
551 gtk_widget_show_all(mi
);
552 gtk_menu_shell_append(GTK_MENU_SHELL(topmenu
), mi
);
558 sexy_spell_entry_populate_popup(SexySpellEntry
*entry
, GtkMenu
*menu
, gpointer data
)
560 GtkWidget
*icon
, *mi
;
564 if ((have_enchant
== FALSE
) || (entry
->priv
->checked
== FALSE
))
567 if (g_slist_length(entry
->priv
->dict_list
) == 0)
570 get_word_extents_from_position(entry
, &start
, &end
, entry
->priv
->mark_character
);
573 if (!word_misspelled(entry
, start
, end
))
577 mi
= gtk_separator_menu_item_new();
579 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu
), mi
);
581 /* Above the separator, show the suggestions menu */
582 icon
= gtk_image_new_from_stock(GTK_STOCK_SPELL_CHECK
, GTK_ICON_SIZE_MENU
);
583 mi
= gtk_image_menu_item_new_with_label(_("Spelling Suggestions"));
584 gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(mi
), icon
);
586 word
= gtk_editable_get_chars(GTK_EDITABLE(entry
), start
, end
);
587 g_assert(word
!= NULL
);
588 gtk_menu_item_set_submenu(GTK_MENU_ITEM(mi
), build_spelling_menu(entry
, word
));
591 gtk_widget_show_all(mi
);
592 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu
), mi
);
596 sexy_spell_entry_init(SexySpellEntry
*entry
)
598 entry
->priv
= g_new0(SexySpellEntryPriv
, 1);
600 entry
->priv
->dict_hash
= g_hash_table_new_full(g_str_hash
, g_str_equal
, g_free
, NULL
);
603 sexy_spell_entry_activate_default_languages(entry
);
605 entry
->priv
->attr_list
= pango_attr_list_new();
607 entry
->priv
->checked
= TRUE
;
609 g_signal_connect(G_OBJECT(entry
), "popup-menu", G_CALLBACK(sexy_spell_entry_popup_menu
), entry
);
610 g_signal_connect(G_OBJECT(entry
), "populate-popup", G_CALLBACK(sexy_spell_entry_populate_popup
), NULL
);
611 g_signal_connect(G_OBJECT(entry
), "changed", G_CALLBACK(sexy_spell_entry_changed
), NULL
);
615 sexy_spell_entry_finalize(GObject
*obj
)
617 SexySpellEntry
*entry
;
619 g_return_if_fail(obj
!= NULL
);
620 g_return_if_fail(SEXY_IS_SPELL_ENTRY(obj
));
622 entry
= SEXY_SPELL_ENTRY(obj
);
624 if (entry
->priv
->attr_list
)
625 pango_attr_list_unref(entry
->priv
->attr_list
);
626 if (entry
->priv
->dict_hash
)
627 g_hash_table_destroy(entry
->priv
->dict_hash
);
628 if (entry
->priv
->words
)
629 g_strfreev(entry
->priv
->words
);
630 if (entry
->priv
->word_starts
)
631 g_free(entry
->priv
->word_starts
);
632 if (entry
->priv
->word_ends
)
633 g_free(entry
->priv
->word_ends
);
636 if (entry
->priv
->broker
) {
638 for (li
= entry
->priv
->dict_list
; li
; li
= g_slist_next(li
)) {
639 struct EnchantDict
*dict
= (struct EnchantDict
*) li
->data
;
640 enchant_broker_free_dict (entry
->priv
->broker
, dict
);
642 g_slist_free (entry
->priv
->dict_list
);
644 enchant_broker_free(entry
->priv
->broker
);
650 if (G_OBJECT_CLASS(parent_class
)->finalize
)
651 G_OBJECT_CLASS(parent_class
)->finalize(obj
);
655 sexy_spell_entry_destroy(GtkObject
*obj
)
657 SexySpellEntry
*entry
;
659 entry
= SEXY_SPELL_ENTRY(obj
);
661 if (GTK_OBJECT_CLASS(parent_class
)->destroy
)
662 GTK_OBJECT_CLASS(parent_class
)->destroy(obj
);
666 * sexy_spell_entry_new
668 * Creates a new SexySpellEntry widget.
670 * Returns: a new #SexySpellEntry.
673 sexy_spell_entry_new(void)
675 return GTK_WIDGET(g_object_new(SEXY_TYPE_SPELL_ENTRY
, NULL
));
679 sexy_spell_error_quark(void)
683 q
= g_quark_from_static_string("sexy-spell-error-quark");
688 default_word_check(SexySpellEntry
*entry
, const gchar
*word
)
690 gboolean result
= TRUE
;
696 if (g_unichar_isalpha(*word
) == FALSE
) {
697 /* We only want to check words */
700 for (li
= entry
->priv
->dict_list
; li
; li
= g_slist_next (li
)) {
701 struct EnchantDict
*dict
= (struct EnchantDict
*) li
->data
;
702 if (enchant_dict_check(dict
, word
, strlen(word
)) == 0) {
711 word_misspelled(SexySpellEntry
*entry
, int start
, int end
)
719 text
= gtk_entry_get_text(GTK_ENTRY(entry
));
720 word
= g_new0(gchar
, end
- start
+ 2);
722 g_strlcpy(word
, text
+ start
, end
- start
+ 1);
725 g_signal_emit(entry
, signals
[WORD_CHECK
], 0, word
, &ret
);
727 ret
= default_word_check (entry
, word
);
735 check_word(SexySpellEntry
*entry
, int start
, int end
)
737 PangoAttrIterator
*it
;
739 /* Check to see if we've got any attributes at this position.
740 * If so, free them, since we'll readd it if the word is misspelled */
741 it
= pango_attr_list_get_iterator(entry
->priv
->attr_list
);
746 pango_attr_iterator_range(it
, &s
, &e
);
748 GSList
*attrs
= pango_attr_iterator_get_attrs(it
);
749 g_slist_foreach(attrs
, (GFunc
) pango_attribute_destroy
, NULL
);
752 } while (pango_attr_iterator_next(it
));
753 pango_attr_iterator_destroy(it
);
755 if (word_misspelled(entry
, start
, end
))
756 insert_underline(entry
, start
, end
);
760 sexy_spell_entry_recheck_all(SexySpellEntry
*entry
)
763 GtkWidget
*widget
= GTK_WIDGET(entry
);
767 if ((have_enchant
== FALSE
) || (entry
->priv
->checked
== FALSE
))
770 if (g_slist_length(entry
->priv
->dict_list
) == 0)
773 /* Remove all existing pango attributes. These will get readded as we check */
774 pango_attr_list_unref(entry
->priv
->attr_list
);
775 entry
->priv
->attr_list
= pango_attr_list_new();
777 /* Loop through words */
778 for (i
= 0; entry
->priv
->words
[i
]; i
++) {
779 length
= strlen(entry
->priv
->words
[i
]);
782 check_word(entry
, entry
->priv
->word_starts
[i
], entry
->priv
->word_ends
[i
]);
785 layout
= gtk_entry_get_layout(GTK_ENTRY(entry
));
786 pango_layout_set_attributes(layout
, entry
->priv
->attr_list
);
788 if (GTK_WIDGET_REALIZED(GTK_WIDGET(entry
))) {
789 rect
.x
= 0; rect
.y
= 0;
790 rect
.width
= widget
->allocation
.width
;
791 rect
.height
= widget
->allocation
.height
;
792 gdk_window_invalidate_rect(widget
->window
, &rect
, TRUE
);
797 sexy_spell_entry_expose(GtkWidget
*widget
, GdkEventExpose
*event
)
799 SexySpellEntry
*entry
= SEXY_SPELL_ENTRY(widget
);
800 GtkEntry
*gtk_entry
= GTK_ENTRY(widget
);
803 if (entry
->priv
->checked
) {
804 layout
= gtk_entry_get_layout(gtk_entry
);
805 pango_layout_set_attributes(layout
, entry
->priv
->attr_list
);
808 return GTK_WIDGET_CLASS(parent_class
)->expose_event (widget
, event
);
812 sexy_spell_entry_button_press(GtkWidget
*widget
, GdkEventButton
*event
)
814 SexySpellEntry
*entry
= SEXY_SPELL_ENTRY(widget
);
815 GtkEntry
*gtk_entry
= GTK_ENTRY(widget
);
818 pos
= gtk_entry_find_position(gtk_entry
, event
->x
);
819 entry
->priv
->mark_character
= pos
;
821 return GTK_WIDGET_CLASS(parent_class
)->button_press_event (widget
, event
);
825 sexy_spell_entry_popup_menu(GtkWidget
*widget
, SexySpellEntry
*entry
)
827 /* Menu popped up from a keybinding (menu key or <shift>+F10). Use
828 * the cursor position as the mark position */
829 entry
->priv
->mark_character
= gtk_editable_get_position (GTK_EDITABLE (entry
));
834 entry_strsplit_utf8(GtkEntry
*entry
, gchar
***set
, gint
**starts
, gint
**ends
)
837 PangoLogAttr
*log_attrs
;
839 gint n_attrs
, n_strings
, i
, j
;
841 layout
= gtk_entry_get_layout(GTK_ENTRY(entry
));
842 text
= gtk_entry_get_text(GTK_ENTRY(entry
));
843 pango_layout_get_log_attrs(layout
, &log_attrs
, &n_attrs
);
845 /* Find how many words we have */
847 for (i
= 0; i
< n_attrs
; i
++)
848 if (log_attrs
[i
].is_word_start
)
851 *set
= g_new0(gchar
*, n_strings
+ 1);
852 *starts
= g_new0(gint
, n_strings
);
853 *ends
= g_new0(gint
, n_strings
);
855 /* Copy out strings */
856 for (i
= 0, j
= 0; i
< n_attrs
; i
++) {
857 if (log_attrs
[i
].is_word_start
) {
861 /* Find the end of this string */
863 while (!(log_attrs
[cend
].is_word_end
))
866 /* Copy sub-string */
867 start
= g_utf8_offset_to_pointer(text
, i
);
868 bytes
= (gint
) (g_utf8_offset_to_pointer(text
, cend
) - start
);
869 (*set
)[j
] = g_new0(gchar
, bytes
+ 1);
870 (*starts
)[j
] = (gint
) (start
- text
);
871 (*ends
)[j
] = (gint
) (start
- text
+ bytes
);
872 g_utf8_strncpy((*set
)[j
], start
, cend
- i
);
874 /* Move on to the next word */
883 sexy_spell_entry_changed(GtkEditable
*editable
, gpointer data
)
885 SexySpellEntry
*entry
= SEXY_SPELL_ENTRY(editable
);
886 if (entry
->priv
->checked
== FALSE
)
888 if (g_slist_length(entry
->priv
->dict_list
) == 0)
891 if (entry
->priv
->words
) {
892 g_strfreev(entry
->priv
->words
);
893 g_free(entry
->priv
->word_starts
);
894 g_free(entry
->priv
->word_ends
);
896 entry_strsplit_utf8(GTK_ENTRY(entry
), &entry
->priv
->words
, &entry
->priv
->word_starts
, &entry
->priv
->word_ends
);
897 sexy_spell_entry_recheck_all(entry
);
901 enchant_has_lang(const gchar
*lang
, GSList
*langs
) {
903 for (i
= langs
; i
; i
= g_slist_next(i
)) {
904 if (strcmp(lang
, i
->data
) == 0) {
912 * sexy_spell_entry_activate_default_languages:
913 * @entry: A #SexySpellEntry.
915 * Activate spell checking for languages specified in the $LANG
916 * or $LANGUAGE environment variables. These languages are
917 * activated by default, so this function need only be called
918 * if they were previously deactivated.
921 sexy_spell_entry_activate_default_languages(SexySpellEntry
*entry
)
923 #if GLIB_CHECK_VERSION (2, 6, 0)
924 const gchar
* const *langs
;
926 gchar
*lastprefix
= NULL
;
927 GSList
*enchant_langs
;
932 if (!entry
->priv
->broker
)
933 entry
->priv
->broker
= enchant_broker_init();
936 langs
= g_get_language_names ();
941 enchant_langs
= sexy_spell_entry_get_languages(entry
);
943 for (i
= 0; langs
[i
]; i
++) {
944 if ((g_strncasecmp(langs
[i
], "C", 1) != 0) &&
945 (strlen(langs
[i
]) >= 2) &&
946 enchant_has_lang(langs
[i
], enchant_langs
)) {
947 if ((lastprefix
== NULL
) || (g_str_has_prefix(langs
[i
], lastprefix
) == FALSE
))
948 sexy_spell_entry_activate_language_internal(entry
, langs
[i
], NULL
);
949 if (lastprefix
!= NULL
)
951 lastprefix
= g_strndup(langs
[i
], 2);
954 if (lastprefix
!= NULL
)
957 g_slist_foreach(enchant_langs
, (GFunc
) g_free
, NULL
);
958 g_slist_free(enchant_langs
);
960 /* If we don't have any languages activated, use "en" */
961 if (entry
->priv
->dict_list
== NULL
)
962 sexy_spell_entry_activate_language_internal(entry
, "en", NULL
);
969 lang
= (gchar
*) g_getenv("LANG");
972 if (g_strncasecmp(lang
, "C", 1) == 0)
974 else if (lang
[0] == '\0')
981 sexy_spell_entry_activate_language_internal(entry
, lang
, NULL
);
986 get_lang_from_dict_cb(const char * const lang_tag
,
987 const char * const provider_name
,
988 const char * const provider_desc
,
989 const char * const provider_file
,
991 gchar
**lang
= (gchar
**)user_data
;
992 *lang
= g_strdup(lang_tag
);
996 get_lang_from_dict(struct EnchantDict
*dict
)
1003 enchant_dict_describe(dict
, get_lang_from_dict_cb
, &lang
);
1008 sexy_spell_entry_activate_language_internal(SexySpellEntry
*entry
, const gchar
*lang
, GError
**error
)
1010 struct EnchantDict
*dict
;
1015 if (!entry
->priv
->broker
)
1016 entry
->priv
->broker
= enchant_broker_init();
1018 if (g_hash_table_lookup(entry
->priv
->dict_hash
, lang
))
1021 dict
= enchant_broker_request_dict(entry
->priv
->broker
, lang
);
1024 g_set_error(error
, SEXY_SPELL_ERROR
, SEXY_SPELL_ERROR_BACKEND
, _("enchant error for language: %s"), lang
);
1028 entry
->priv
->dict_list
= g_slist_append(entry
->priv
->dict_list
, (gpointer
) dict
);
1029 g_hash_table_insert(entry
->priv
->dict_hash
, get_lang_from_dict(dict
), (gpointer
) dict
);
1035 dict_describe_cb(const char * const lang_tag
,
1036 const char * const provider_name
,
1037 const char * const provider_desc
,
1038 const char * const provider_file
,
1041 GSList
**langs
= (GSList
**)user_data
;
1043 *langs
= g_slist_append(*langs
, (gpointer
)g_strdup(lang_tag
));
1047 * sexy_spell_entry_get_languages:
1048 * @entry: A #SexySpellEntry.
1050 * Retrieve a list of language codes for which dictionaries are available.
1052 * Returns: a new #GList object, or %NULL on error.
1055 sexy_spell_entry_get_languages(const SexySpellEntry
*entry
)
1057 GSList
*langs
= NULL
;
1059 g_return_val_if_fail(entry
!= NULL
, NULL
);
1060 g_return_val_if_fail(SEXY_IS_SPELL_ENTRY(entry
), NULL
);
1062 if (enchant_broker_list_dicts
== NULL
)
1065 if (!entry
->priv
->broker
)
1068 enchant_broker_list_dicts(entry
->priv
->broker
, dict_describe_cb
, &langs
);
1074 * sexy_spell_entry_get_language_name:
1075 * @entry: A #SexySpellEntry.
1076 * @lang: The language code to lookup a friendly name for.
1078 * Get a friendly name for a given locale.
1080 * Returns: The name of the locale. Should be freed with g_free()
1083 sexy_spell_entry_get_language_name(const SexySpellEntry
*entry
,
1087 return gtkspell_iso_codes_lookup_name_for_code(lang);*/
1092 * sexy_spell_entry_language_is_active:
1093 * @entry: A #SexySpellEntry.
1094 * @lang: The language to use, in a form enchant understands.
1096 * Determine if a given language is currently active.
1098 * Returns: TRUE if the language is active.
1101 sexy_spell_entry_language_is_active(const SexySpellEntry
*entry
,
1104 return (g_hash_table_lookup(entry
->priv
->dict_hash
, lang
) != NULL
);
1108 * sexy_spell_entry_activate_language:
1109 * @entry: A #SexySpellEntry
1110 * @lang: The language to use in a form Enchant understands. Typically either
1111 * a two letter language code or a locale code in the form xx_XX.
1112 * @error: Return location for error.
1114 * Activate spell checking for the language specifed.
1116 * Returns: FALSE if there was an error.
1119 sexy_spell_entry_activate_language(SexySpellEntry
*entry
, const gchar
*lang
, GError
**error
)
1123 g_return_val_if_fail(entry
!= NULL
, FALSE
);
1124 g_return_val_if_fail(SEXY_IS_SPELL_ENTRY(entry
), FALSE
);
1125 g_return_val_if_fail(lang
!= NULL
&& lang
!= '\0', FALSE
);
1131 g_return_val_if_fail(*error
== NULL
, FALSE
);
1133 ret
= sexy_spell_entry_activate_language_internal(entry
, lang
, error
);
1136 if (entry
->priv
->words
) {
1137 g_strfreev(entry
->priv
->words
);
1138 g_free(entry
->priv
->word_starts
);
1139 g_free(entry
->priv
->word_ends
);
1141 entry_strsplit_utf8(GTK_ENTRY(entry
), &entry
->priv
->words
, &entry
->priv
->word_starts
, &entry
->priv
->word_ends
);
1142 sexy_spell_entry_recheck_all(entry
);
1149 * sexy_spell_entry_deactivate_language:
1150 * @entry: A #SexySpellEntry.
1151 * @lang: The language in a form Enchant understands. Typically either
1152 * a two letter language code or a locale code in the form xx_XX.
1154 * Deactivate spell checking for the language specifed.
1157 sexy_spell_entry_deactivate_language(SexySpellEntry
*entry
, const gchar
*lang
)
1159 g_return_if_fail(entry
!= NULL
);
1160 g_return_if_fail(SEXY_IS_SPELL_ENTRY(entry
));
1165 if (!entry
->priv
->dict_list
)
1169 struct EnchantDict
*dict
;
1171 dict
= g_hash_table_lookup(entry
->priv
->dict_hash
, lang
);
1174 enchant_broker_free_dict(entry
->priv
->broker
, dict
);
1175 entry
->priv
->dict_list
= g_slist_remove(entry
->priv
->dict_list
, dict
);
1176 g_hash_table_remove (entry
->priv
->dict_hash
, lang
);
1178 /* deactivate all */
1180 struct EnchantDict
*dict
;
1182 for (li
= entry
->priv
->dict_list
; li
; li
= g_slist_next(li
)) {
1183 dict
= (struct EnchantDict
*)li
->data
;
1184 enchant_broker_free_dict(entry
->priv
->broker
, dict
);
1187 g_slist_free (entry
->priv
->dict_list
);
1188 g_hash_table_destroy (entry
->priv
->dict_hash
);
1189 entry
->priv
->dict_hash
= g_hash_table_new_full(g_str_hash
, g_str_equal
, g_free
, NULL
);
1190 entry
->priv
->dict_list
= NULL
;
1193 if (entry
->priv
->words
) {
1194 g_strfreev(entry
->priv
->words
);
1195 g_free(entry
->priv
->word_starts
);
1196 g_free(entry
->priv
->word_ends
);
1198 entry_strsplit_utf8(GTK_ENTRY(entry
), &entry
->priv
->words
, &entry
->priv
->word_starts
, &entry
->priv
->word_ends
);
1199 sexy_spell_entry_recheck_all(entry
);
1203 * sexy_spell_entry_set_active_languages:
1204 * @entry: A #SexySpellEntry
1205 * @langs: A list of language codes to activate, in a form Enchant understands.
1206 * Typically either a two letter language code or a locale code in the
1208 * @error: Return location for error.
1210 * Activate spell checking for only the languages specified.
1212 * Returns: FALSE if there was an error.
1215 sexy_spell_entry_set_active_languages(SexySpellEntry
*entry
, GSList
*langs
, GError
**error
)
1219 g_return_val_if_fail(entry
!= NULL
, FALSE
);
1220 g_return_val_if_fail(SEXY_IS_SPELL_ENTRY(entry
), FALSE
);
1221 g_return_val_if_fail(langs
!= NULL
, FALSE
);
1226 /* deactivate all languages first */
1227 sexy_spell_entry_deactivate_language(entry
, NULL
);
1229 for (li
= langs
; li
; li
= g_slist_next(li
)) {
1230 if (sexy_spell_entry_activate_language_internal(entry
,
1231 (const gchar
*) li
->data
, error
) == FALSE
)
1234 if (entry
->priv
->words
) {
1235 g_strfreev(entry
->priv
->words
);
1236 g_free(entry
->priv
->word_starts
);
1237 g_free(entry
->priv
->word_ends
);
1239 entry_strsplit_utf8(GTK_ENTRY(entry
), &entry
->priv
->words
, &entry
->priv
->word_starts
, &entry
->priv
->word_ends
);
1240 sexy_spell_entry_recheck_all(entry
);
1245 * sexy_spell_entry_get_active_languages:
1246 * @entry: A #SexySpellEntry
1248 * Retrieve a list of the currently active languages.
1250 * Returns: A GSList of char* values with language codes (en, fr, etc). Both
1251 * the data and the list must be freed by the user.
1254 sexy_spell_entry_get_active_languages(SexySpellEntry
*entry
)
1256 GSList
*ret
= NULL
, *li
;
1257 struct EnchantDict
*dict
;
1260 g_return_val_if_fail(entry
!= NULL
, NULL
);
1261 g_return_val_if_fail(SEXY_IS_SPELL_ENTRY(entry
), NULL
);
1266 for (li
= entry
->priv
->dict_list
; li
; li
= g_slist_next(li
)) {
1267 dict
= (struct EnchantDict
*) li
->data
;
1268 lang
= get_lang_from_dict(dict
);
1269 ret
= g_slist_append(ret
, lang
);
1275 * sexy_spell_entry_is_checked:
1276 * @entry: A #SexySpellEntry.
1278 * Queries a #SexySpellEntry and returns whether the entry has spell-checking enabled.
1280 * Returns: TRUE if the entry has spell-checking enabled.
1283 sexy_spell_entry_is_checked(SexySpellEntry
*entry
)
1285 return entry
->priv
->checked
;
1289 * sexy_spell_entry_set_checked:
1290 * @entry: A #SexySpellEntry.
1291 * @checked: Whether to enable spell-checking
1293 * Sets whether the entry has spell-checking enabled.
1296 sexy_spell_entry_set_checked(SexySpellEntry
*entry
, gboolean checked
)
1300 if (entry
->priv
->checked
== checked
)
1303 entry
->priv
->checked
= checked
;
1304 widget
= GTK_WIDGET(entry
);
1306 if (checked
== FALSE
&& GTK_WIDGET_REALIZED(widget
)) {
1307 PangoLayout
*layout
;
1310 pango_attr_list_unref(entry
->priv
->attr_list
);
1311 entry
->priv
->attr_list
= pango_attr_list_new();
1313 layout
= gtk_entry_get_layout(GTK_ENTRY(entry
));
1314 pango_layout_set_attributes(layout
, entry
->priv
->attr_list
);
1316 rect
.x
= 0; rect
.y
= 0;
1317 rect
.width
= widget
->allocation
.width
;
1318 rect
.height
= widget
->allocation
.height
;
1319 gdk_window_invalidate_rect(widget
->window
, &rect
, TRUE
);
1321 if (entry
->priv
->words
) {
1322 g_strfreev(entry
->priv
->words
);
1323 g_free(entry
->priv
->word_starts
);
1324 g_free(entry
->priv
->word_ends
);
1326 entry_strsplit_utf8(GTK_ENTRY(entry
), &entry
->priv
->words
, &entry
->priv
->word_starts
, &entry
->priv
->word_ends
);
1327 sexy_spell_entry_recheck_all(entry
);