restructure configure so pkg-config derived SSL flags get used
[rofl0r-ixchat.git] / src / fe-gtk / sexy-spell-entry.c
blob0405e5beb20ee79a040186ae5e95f92bbc5a97bb
1 /*
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.
22 #ifdef HAVE_CONFIG_H
23 # include "config.h"
24 #endif
26 #include <gtk/gtk.h>
27 #include "sexy-spell-entry.h"
28 #include <string.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.
40 struct EnchantDict;
41 struct EnchantBroker;
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,
47 void * user_data);
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;
68 gint mark_character;
69 GHashTable *dict_hash;
70 GSList *dict_list;
71 gchar **words;
72 gint *word_starts;
73 gint *word_ends;
74 gboolean checked;
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);
88 /* Other handlers */
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,
93 gint x);
94 static gboolean word_misspelled (SexySpellEntry *entry,
95 int start,
96 int end);
97 static gboolean default_word_check (SexySpellEntry *entry,
98 const gchar *word);
99 static gboolean sexy_spell_entry_activate_language_internal (SexySpellEntry *entry,
100 const gchar *lang,
101 GError **error);
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,
105 gchar ***set,
106 gint **starts,
107 gint **ends);
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));
113 enum
115 WORD_CHECK,
116 LAST_SIGNAL
118 static guint signals[LAST_SIGNAL] = {0};
120 static gboolean
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
126 * as correct */
127 g_value_set_boolean (return_accu, ret);
128 return ret;
131 static void
132 initialize_enchant ()
134 GModule *enchant;
135 gpointer funcptr;
137 enchant = g_module_open("libenchant", 0);
138 if (enchant == NULL)
140 enchant = g_module_open("libenchant.so.1", 0);
141 if (enchant == NULL)
142 return;
145 have_enchant = TRUE;
147 #define MODULE_SYMBOL(name, func) \
148 g_module_symbol(enchant, (name), &funcptr); \
149 (func) = 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)
167 #undef MODULE_SYMBOL
170 static void
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);
187 if (have_enchant)
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
205 * a chat client.
207 * Returns: %FALSE to indicate that the word should be marked as
208 * correct.
210 /* signals[WORD_CHECK] = g_signal_new("word_check",
211 G_TYPE_FROM_CLASS(object_class),
212 G_SIGNAL_RUN_LAST,
213 G_STRUCT_OFFSET(SexySpellEntryClass, word_check),
214 (GSignalAccumulator) spell_accumulator, NULL,
215 sexy_marshal_BOOLEAN__STRING,
216 G_TYPE_BOOLEAN,
217 1, G_TYPE_STRING);*/
220 static void
221 sexy_spell_entry_editable_init (GtkEditableClass *iface)
225 static gint
226 gtk_entry_find_position (GtkEntry *entry, gint x)
228 PangoLayout *layout;
229 PangoLayoutLine *line;
230 const gchar *text;
231 gint cursor_index;
232 gint index;
233 gint pos;
234 gboolean trailing;
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;
248 } else {
249 index = cursor_index;
250 trailing = FALSE;
254 pos = g_utf8_pointer_to_offset (text, text + index);
255 pos += trailing;
257 return pos;
260 static void
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);
276 static void
277 get_word_extents_from_position(SexySpellEntry *entry, gint *start, gint *end, guint position)
279 const gchar *text;
280 gint i, bytes_pos;
282 *start = -1;
283 *end = -1;
285 if (entry->priv->words == NULL)
286 return;
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];
296 return;
301 static void
302 add_to_dictionary(GtkWidget *menuitem, SexySpellEntry *entry)
304 char *word;
305 gint start, end;
306 struct EnchantDict *dict;
308 if (!have_enchant)
309 return;
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");
315 if (dict)
316 enchant_dict_add_to_personal(dict, word, -1);
318 g_free(word);
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);
329 static void
330 ignore_all(GtkWidget *menuitem, SexySpellEntry *entry)
332 char *word;
333 gint start, end;
334 GSList *li;
336 if (!have_enchant)
337 return;
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);
347 g_free(word);
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);
358 static void
359 replace_word(GtkWidget *menuitem, SexySpellEntry *entry)
361 char *oldword;
362 const char *newword;
363 gint start, end;
364 gint cursor;
365 struct EnchantDict *dict;
367 if (!have_enchant)
368 return;
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)
377 cursor = -1;
378 else if(cursor > start && cursor <= end)
379 cursor = start;
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),
384 &start);
385 gtk_editable_set_position(GTK_EDITABLE(entry), cursor);
387 dict = (struct EnchantDict *) g_object_get_data(G_OBJECT(menuitem), "enchant-dict");
389 if (dict)
390 enchant_dict_store_replacement(dict,
391 oldword, -1,
392 newword, -1);
394 g_free(oldword);
397 static void
398 build_suggestion_menu(SexySpellEntry *entry, GtkWidget *menu, struct EnchantDict *dict, const gchar *word)
400 GtkWidget *mi;
401 gchar **suggestions;
402 size_t n_suggestions, i;
404 if (!have_enchant)
405 return;
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);
418 } else {
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();
423 gtk_widget_show(mi);
424 gtk_menu_shell_append(GTK_MENU_SHELL(menu), mi);
426 mi = gtk_menu_item_new_with_label(_("More..."));
427 gtk_widget_show(mi);
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);
437 gtk_widget_show(mi);
438 gtk_menu_shell_append(GTK_MENU_SHELL(menu), mi);
442 enchant_dict_free_suggestions(dict, suggestions);
445 static GtkWidget *
446 build_spelling_menu(SexySpellEntry *entry, const gchar *word)
448 struct EnchantDict *dict;
449 GtkWidget *topmenu, *mi;
450 gchar *label;
452 if (!have_enchant)
453 return NULL;
455 topmenu = gtk_menu_new();
457 if (entry->priv->dict_list == NULL)
458 return topmenu;
460 #if 1
461 dict = (struct EnchantDict *) entry->priv->dict_list->data;
462 build_suggestion_menu(entry, topmenu, dict, word);
463 #else
464 /* Suggestions */
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);
468 } else {
469 GSList *li;
470 GtkWidget *menu;
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);
477 if (lang_name) {
478 mi = gtk_menu_item_new_with_label(lang_name);
479 g_free(lang_name);
480 } else {
481 mi = gtk_menu_item_new_with_label(lang);
483 g_free(lang);
485 gtk_widget_show(mi);
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);
492 #endif
494 /* Separator */
495 mi = gtk_separator_menu_item_new ();
496 gtk_widget_show(mi);
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);
502 g_free(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));
506 #if 1
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);
510 #else
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);
515 } else {
516 GSList *li;
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);
527 if (lang_name) {
528 submi = gtk_menu_item_new_with_label(lang_name);
529 g_free(lang_name);
530 } else {
531 submi = gtk_menu_item_new_with_label(lang);
533 g_free(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);
542 #endif
544 gtk_widget_show_all(mi);
545 gtk_menu_shell_append(GTK_MENU_SHELL(topmenu), mi);
547 /* - Ignore All */
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);
554 return topmenu;
557 static void
558 sexy_spell_entry_populate_popup(SexySpellEntry *entry, GtkMenu *menu, gpointer data)
560 GtkWidget *icon, *mi;
561 gint start, end;
562 gchar *word;
564 if ((have_enchant == FALSE) || (entry->priv->checked == FALSE))
565 return;
567 if (g_slist_length(entry->priv->dict_list) == 0)
568 return;
570 get_word_extents_from_position(entry, &start, &end, entry->priv->mark_character);
571 if (start == end)
572 return;
573 if (!word_misspelled(entry, start, end))
574 return;
576 /* separator */
577 mi = gtk_separator_menu_item_new();
578 gtk_widget_show(mi);
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));
589 g_free(word);
591 gtk_widget_show_all(mi);
592 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), mi);
595 static void
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);
602 if (have_enchant)
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);
614 static void
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);
635 if (have_enchant) {
636 if (entry->priv->broker) {
637 GSList *li;
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);
648 g_free(entry->priv);
650 if (G_OBJECT_CLASS(parent_class)->finalize)
651 G_OBJECT_CLASS(parent_class)->finalize(obj);
654 static void
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.
672 GtkWidget *
673 sexy_spell_entry_new(void)
675 return GTK_WIDGET(g_object_new(SEXY_TYPE_SPELL_ENTRY, NULL));
678 GQuark
679 sexy_spell_error_quark(void)
681 static GQuark q = 0;
682 if (q == 0)
683 q = g_quark_from_static_string("sexy-spell-error-quark");
684 return q;
687 static gboolean
688 default_word_check(SexySpellEntry *entry, const gchar *word)
690 gboolean result = TRUE;
691 GSList *li;
693 if (!have_enchant)
694 return result;
696 if (g_unichar_isalpha(*word) == FALSE) {
697 /* We only want to check words */
698 return FALSE;
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) {
703 result = FALSE;
704 break;
707 return result;
710 static gboolean
711 word_misspelled(SexySpellEntry *entry, int start, int end)
713 const gchar *text;
714 gchar *word;
715 gboolean ret;
717 if (start == end)
718 return FALSE;
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);
724 #if 0
725 g_signal_emit(entry, signals[WORD_CHECK], 0, word, &ret);
726 #else
727 ret = default_word_check (entry, word);
728 #endif
730 g_free(word);
731 return ret;
734 static void
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);
742 if (it == NULL)
743 return;
744 do {
745 gint s, e;
746 pango_attr_iterator_range(it, &s, &e);
747 if (s == start) {
748 GSList *attrs = pango_attr_iterator_get_attrs(it);
749 g_slist_foreach(attrs, (GFunc) pango_attribute_destroy, NULL);
750 g_slist_free(attrs);
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);
759 static void
760 sexy_spell_entry_recheck_all(SexySpellEntry *entry)
762 GdkRectangle rect;
763 GtkWidget *widget = GTK_WIDGET(entry);
764 PangoLayout *layout;
765 int length, i;
767 if ((have_enchant == FALSE) || (entry->priv->checked == FALSE))
768 return;
770 if (g_slist_length(entry->priv->dict_list) == 0)
771 return;
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]);
780 if (length == 0)
781 continue;
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);
796 static gint
797 sexy_spell_entry_expose(GtkWidget *widget, GdkEventExpose *event)
799 SexySpellEntry *entry = SEXY_SPELL_ENTRY(widget);
800 GtkEntry *gtk_entry = GTK_ENTRY(widget);
801 PangoLayout *layout;
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);
811 static gint
812 sexy_spell_entry_button_press(GtkWidget *widget, GdkEventButton *event)
814 SexySpellEntry *entry = SEXY_SPELL_ENTRY(widget);
815 GtkEntry *gtk_entry = GTK_ENTRY(widget);
816 gint pos;
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);
824 static gboolean
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));
830 return FALSE;
833 static void
834 entry_strsplit_utf8(GtkEntry *entry, gchar ***set, gint **starts, gint **ends)
836 PangoLayout *layout;
837 PangoLogAttr *log_attrs;
838 const gchar *text;
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 */
846 n_strings = 0;
847 for (i = 0; i < n_attrs; i++)
848 if (log_attrs[i].is_word_start)
849 n_strings++;
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) {
858 gint cend, bytes;
859 gchar *start;
861 /* Find the end of this string */
862 cend = i;
863 while (!(log_attrs[cend].is_word_end))
864 cend++;
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 */
875 j++;
879 g_free (log_attrs);
882 static void
883 sexy_spell_entry_changed(GtkEditable *editable, gpointer data)
885 SexySpellEntry *entry = SEXY_SPELL_ENTRY(editable);
886 if (entry->priv->checked == FALSE)
887 return;
888 if (g_slist_length(entry->priv->dict_list) == 0)
889 return;
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);
900 static gboolean
901 enchant_has_lang(const gchar *lang, GSList *langs) {
902 GSList *i;
903 for (i = langs; i; i = g_slist_next(i)) {
904 if (strcmp(lang, i->data) == 0) {
905 return TRUE;
908 return FALSE;
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.
920 void
921 sexy_spell_entry_activate_default_languages(SexySpellEntry *entry)
923 #if GLIB_CHECK_VERSION (2, 6, 0)
924 const gchar* const *langs;
925 int i;
926 gchar *lastprefix = NULL;
927 GSList *enchant_langs;
929 if (!have_enchant)
930 return;
932 if (!entry->priv->broker)
933 entry->priv->broker = enchant_broker_init();
936 langs = g_get_language_names ();
938 if (langs == NULL)
939 return;
941 enchant_langs = sexy_spell_entry_get_languages(entry);
943 for (i = 0; langs[i]; i++) {
944 if ((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)
950 g_free(lastprefix);
951 lastprefix = g_strndup(langs[i], 2);
954 if (lastprefix != NULL)
955 g_free(lastprefix);
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);
963 #else
964 gchar *lang;
966 if (!have_enchant)
967 return;
969 lang = (gchar *) g_getenv("LANG");
971 if (lang != NULL) {
972 if (strncasecmp(lang, "C", 1) == 0)
973 lang = NULL;
974 else if (lang[0] == '\0')
975 lang = NULL;
978 if (lang == NULL)
979 lang = "en";
981 sexy_spell_entry_activate_language_internal(entry, lang, NULL);
982 #endif
985 static void
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,
990 void * user_data) {
991 gchar **lang = (gchar **)user_data;
992 *lang = g_strdup(lang_tag);
995 static gchar *
996 get_lang_from_dict(struct EnchantDict *dict)
998 gchar *lang;
1000 if (!have_enchant)
1001 return NULL;
1003 enchant_dict_describe(dict, get_lang_from_dict_cb, &lang);
1004 return lang;
1007 static gboolean
1008 sexy_spell_entry_activate_language_internal(SexySpellEntry *entry, const gchar *lang, GError **error)
1010 struct EnchantDict *dict;
1012 if (!have_enchant)
1013 return FALSE;
1015 if (!entry->priv->broker)
1016 entry->priv->broker = enchant_broker_init();
1018 if (g_hash_table_lookup(entry->priv->dict_hash, lang))
1019 return TRUE;
1021 dict = enchant_broker_request_dict(entry->priv->broker, lang);
1023 if (!dict) {
1024 g_set_error(error, SEXY_SPELL_ERROR, SEXY_SPELL_ERROR_BACKEND, _("enchant error for language: %s"), lang);
1025 return FALSE;
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);
1031 return TRUE;
1034 static void
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,
1039 void * user_data)
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.
1054 GSList *
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)
1063 return NULL;
1065 if (!entry->priv->broker)
1066 return NULL;
1068 enchant_broker_list_dicts(entry->priv->broker, dict_describe_cb, &langs);
1070 return 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()
1082 gchar *
1083 sexy_spell_entry_get_language_name(const SexySpellEntry *entry,
1084 const gchar *lang)
1086 /*if (have_enchant)
1087 return gtkspell_iso_codes_lookup_name_for_code(lang);*/
1088 return NULL;
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.
1100 gboolean
1101 sexy_spell_entry_language_is_active(const SexySpellEntry *entry,
1102 const gchar *lang)
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.
1118 gboolean
1119 sexy_spell_entry_activate_language(SexySpellEntry *entry, const gchar *lang, GError **error)
1121 gboolean ret;
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);
1127 if (!have_enchant)
1128 return FALSE;
1130 if (error)
1131 g_return_val_if_fail(*error == NULL, FALSE);
1133 ret = sexy_spell_entry_activate_language_internal(entry, lang, error);
1135 if (ret) {
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);
1145 return ret;
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.
1156 void
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));
1162 if (!have_enchant)
1163 return;
1165 if (!entry->priv->dict_list)
1166 return;
1168 if (lang) {
1169 struct EnchantDict *dict;
1171 dict = g_hash_table_lookup(entry->priv->dict_hash, lang);
1172 if (!dict)
1173 return;
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);
1177 } else {
1178 /* deactivate all */
1179 GSList *li;
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
1207 * form xx_XX.
1208 * @error: Return location for error.
1210 * Activate spell checking for only the languages specified.
1212 * Returns: FALSE if there was an error.
1214 gboolean
1215 sexy_spell_entry_set_active_languages(SexySpellEntry *entry, GSList *langs, GError **error)
1217 GSList *li;
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);
1223 if (!have_enchant)
1224 return 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)
1232 return 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);
1241 return TRUE;
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.
1253 GSList *
1254 sexy_spell_entry_get_active_languages(SexySpellEntry *entry)
1256 GSList *ret = NULL, *li;
1257 struct EnchantDict *dict;
1258 gchar *lang;
1260 g_return_val_if_fail(entry != NULL, NULL);
1261 g_return_val_if_fail(SEXY_IS_SPELL_ENTRY(entry), NULL);
1263 if (!have_enchant)
1264 return 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);
1271 return ret;
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.
1282 gboolean
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.
1295 void
1296 sexy_spell_entry_set_checked(SexySpellEntry *entry, gboolean checked)
1298 GtkWidget *widget;
1300 if (entry->priv->checked == checked)
1301 return;
1303 entry->priv->checked = checked;
1304 widget = GTK_WIDGET(entry);
1306 if (checked == FALSE && GTK_WIDGET_REALIZED(widget)) {
1307 PangoLayout *layout;
1308 GdkRectangle rect;
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);
1320 } else {
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);