1 /* gtkaspell - a spell-checking addon for GtkText
2 * Copyright (c) 2000 Evan Martin (original code for ispell).
3 * Copyright (c) 2002 Melvin Hadasht.
4 * Copyright (C) 2001-2013 the Claws Mail Team
6 * This library is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public
8 * License as published by the Free Software Foundation; either
9 * version 3 of the License, or (at your option) any later version.
11 * This library is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Lesser General Public License for more details.
16 * You should have received a copy of the GNU Lesser General Public
17 * License along with this library. If not, see <http://www.gnu.org/licenses/>.
20 * Stuphead: (C) 2000,2001 Grigroy Bakunov, Sergey Pinaev
21 * Adapted for Sylpheed (Claws) (c) 2001-2002 by Hiroyuki Yamamoto &
22 * The Claws Mail Team.
23 * Adapted for pspell (c) 2001-2002 Melvin Hadasht
24 * Adapted for GNU/aspell (c) 2002 Melvin Hadasht
29 #include "claws-features.h"
37 #include <sys/types.h>
39 # include <sys/wait.h>
51 #include <glib/gi18n.h>
55 #include <gdk/gdkkeysyms.h>
58 #include "alertpanel.h"
59 #include "gtkaspell.h"
60 #include "gtk/gtkutils.h"
61 #include "gtk/combobox.h"
63 #define ASPELL_FASTMODE 1
64 #define ASPELL_NORMALMODE 2
65 #define ASPELL_BADSPELLERMODE 3
67 /* size of the text buffer used in various word-processing routines. */
70 /* number of suggestions to display on each menu. */
74 SET_GTKASPELL_NAME = 0,
75 SET_GTKASPELL_FULLNAME = 1,
79 typedef struct _GtkAspellCheckers {
81 GSList *dictionary_list;
85 /******************************************************************************/
87 static GtkAspellCheckers *gtkaspellcheckers;
89 /* Error message storage */
90 static void gtkaspell_checkers_error_message (gchar *message);
93 static gboolean key_press_cb (GtkWidget *text_view,
95 GtkAspell *gtkaspell);
96 static void entry_insert_cb (GtkTextBuffer *textbuf,
100 GtkAspell *gtkaspell);
101 static void entry_delete_cb (GtkTextBuffer *textbuf,
102 GtkTextIter *startiter,
103 GtkTextIter *enditer,
104 GtkAspell *gtkaspell);
105 /*static gint button_press_intercept_cb (GtkTextView *gtktext,
107 GtkAspell *gtkaspell);
109 static void button_press_intercept_cb(GtkTextView *gtktext,
110 GtkMenu *menu, GtkAspell *gtkaspell);
112 /* Checker creation */
113 static GtkAspeller* gtkaspeller_new (Dictionary *dict);
114 static GtkAspeller* gtkaspeller_real_new (Dictionary *dict);
115 static GtkAspeller* gtkaspeller_delete (GtkAspeller *gtkaspeller);
116 static GtkAspeller* gtkaspeller_real_delete (GtkAspeller *gtkaspeller);
118 /* Checker configuration */
119 static EnchantDict *set_dictionary (EnchantBroker *broker,
121 static void set_use_both_cb (GtkMenuItem *w,
122 GtkAspell *gtkaspell);
124 /* Checker actions */
125 static gboolean check_at (GtkAspell *gtkaspell,
127 static gboolean check_at_cb (gpointer data);
128 static GList* misspelled_suggest (GtkAspell *gtkaspell,
130 static gboolean find_misspelled_cb (gpointer data,
132 static void add_word_to_session_cb (GtkWidget *w,
134 static void add_word_to_personal_cb (GtkWidget *w,
136 static void replace_with_create_dialog_cb (GtkWidget *w,
138 static void replace_with_supplied_word_cb (GtkWidget *w,
139 GtkAspell *gtkaspell);
140 static void replace_word_cb (GtkWidget *w,
142 static void replace_real_word (GtkAspell *gtkaspell,
143 const gchar *newword);
144 static void replace_real_word_cb (gpointer data,
145 const gchar *newword);
146 static void check_with_alternate_cb (GtkWidget *w,
148 static void toggle_check_while_typing_cb (GtkWidget *w,
152 static GSList* make_sug_menu (GtkAspell *gtkaspell);
153 static GSList * populate_submenu (GtkAspell *gtkaspell);
154 GSList* gtkaspell_make_config_menu (GtkAspell *gtkaspell);
155 static void set_menu_pos (GtkMenu *menu,
160 /* Other menu callbacks */
161 static gboolean aspell_key_pressed (GtkWidget *widget,
163 GtkAspell *gtkaspell);
164 static void change_dict_cb (GtkWidget *w,
165 GtkAspell *gtkaspell);
166 static void switch_to_alternate_cb (GtkWidget *w,
169 /* Misc. helper functions */
170 static void set_point_continue (GtkAspell *gtkaspell);
171 static void continue_check (gpointer *gtkaspell);
172 static gboolean iswordsep (gunichar c);
173 static gunichar get_text_index_whar (GtkAspell *gtkaspell,
175 static gboolean get_word_from_pos (GtkAspell *gtkaspell,
181 static void allocate_color (GtkAspell *gtkaspell,
183 static void change_color (GtkAspell *gtkaspell,
188 static gint compare_dict (Dictionary *a,
190 static void dictionary_delete (Dictionary *dict);
191 static Dictionary * dictionary_dup (const Dictionary *dict);
192 static void reset_theword_data (GtkAspell *gtkaspell);
193 static void free_checkers (gpointer elt,
196 static void destroy_menu(GtkWidget *widget, gpointer user_data);
198 /******************************************************************************/
199 static gint get_textview_buffer_charcount(GtkTextView *view);
201 static void gtkaspell_free_dictionary_list (GSList *list);
202 static GSList* gtkaspell_get_dictionary_list (gint refresh);
204 static void gtkaspell_uncheck_all (GtkAspell *gtkaspell);
206 static gint get_textview_buffer_charcount(GtkTextView *view)
208 GtkTextBuffer *buffer;
210 cm_return_val_if_fail(view, 0);
212 buffer = gtk_text_view_get_buffer(view);
213 cm_return_val_if_fail(buffer, 0);
215 return gtk_text_buffer_get_char_count(buffer);
217 static gint get_textview_buffer_offset(GtkTextView *view)
219 GtkTextBuffer * buffer;
223 cm_return_val_if_fail(view, 0);
225 buffer = gtk_text_view_get_buffer(view);
226 cm_return_val_if_fail(buffer, 0);
228 mark = gtk_text_buffer_get_insert(buffer);
229 cm_return_val_if_fail(mark, 0);
231 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
233 return gtk_text_iter_get_offset(&iter);
235 static void set_textview_buffer_offset(GtkTextView *view, gint offset)
237 GtkTextBuffer *buffer;
240 cm_return_if_fail(view);
242 buffer = gtk_text_view_get_buffer(view);
243 cm_return_if_fail(buffer);
245 gtk_text_buffer_get_iter_at_offset(buffer, &iter, offset);
246 gtk_text_buffer_place_cursor(buffer, &iter);
248 /******************************************************************************/
250 void gtkaspell_checkers_init(void)
252 gtkaspellcheckers = g_new(GtkAspellCheckers, 1);
253 gtkaspellcheckers->checkers = NULL;
254 gtkaspellcheckers->dictionary_list = NULL;
255 gtkaspellcheckers->error_message = NULL;
258 void gtkaspell_checkers_quit(void)
263 if (gtkaspellcheckers == NULL)
266 if ((checkers = gtkaspellcheckers->checkers)) {
267 debug_print("Aspell: number of running checkers to delete %d\n",
268 g_slist_length(checkers));
270 g_slist_foreach(checkers, free_checkers, NULL);
271 g_slist_free(checkers);
272 gtkaspellcheckers->checkers = NULL;
275 if ((dict_list = gtkaspellcheckers->dictionary_list)) {
276 debug_print("Aspell: number of dictionaries to delete %d\n",
277 g_slist_length(dict_list));
279 gtkaspell_free_dictionary_list(dict_list);
280 gtkaspellcheckers->dictionary_list = NULL;
283 g_free(gtkaspellcheckers->error_message);
284 gtkaspellcheckers->error_message = NULL;
288 static void gtkaspell_checkers_error_message (gchar *message)
291 if (gtkaspellcheckers->error_message) {
292 tmp = g_strdup_printf("%s\n%s",
293 gtkaspellcheckers->error_message,
296 g_free(gtkaspellcheckers->error_message);
297 gtkaspellcheckers->error_message = tmp;
299 gtkaspellcheckers->error_message = message;
304 const char *gtkaspell_checkers_strerror(void)
306 cm_return_val_if_fail(gtkaspellcheckers, "");
307 return gtkaspellcheckers->error_message;
310 void gtkaspell_checkers_reset_error(void)
312 cm_return_if_fail(gtkaspellcheckers);
314 g_free(gtkaspellcheckers->error_message);
316 gtkaspellcheckers->error_message = NULL;
319 GtkAspell *gtkaspell_new(const gchar *dictionary,
320 const gchar *alt_dictionary,
321 const gchar *encoding, /* unused */
322 gint misspelled_color,
323 gboolean check_while_typing,
324 gboolean recheck_when_changing_dict,
325 gboolean use_alternate,
326 gboolean use_both_dicts,
327 GtkTextView *gtktext,
328 GtkWindow *parent_win,
329 void (dict_changed_cb)(void *data),
330 void (*spell_menu_cb)(void *data),
334 GtkAspell *gtkaspell;
335 GtkAspeller *gtkaspeller;
336 GtkTextBuffer *buffer;
338 cm_return_val_if_fail(gtktext, NULL);
339 if (!dictionary || !*dictionary) {
340 gtkaspell_checkers_error_message(
341 g_strdup(_("No dictionary selected.")));
345 buffer = gtk_text_view_get_buffer(gtktext);
347 dict = g_new0(Dictionary, 1);
348 if (strrchr(dictionary, '/')) {
349 dict->fullname = g_strdup(strrchr(dictionary, '/')+1);
350 dict->dictname = g_strdup(strrchr(dictionary, '/')+1);
352 dict->fullname = g_strdup(dictionary);
353 dict->dictname = g_strdup(dictionary);
356 if (strchr(dict->fullname, '-')) {
357 *(strchr(dict->fullname, '-')) = '\0';
358 *(strchr(dict->dictname, '-')) = '\0';
360 gtkaspeller = gtkaspeller_new(dict);
361 dictionary_delete(dict);
364 gtkaspell_checkers_error_message(
365 g_strdup_printf(_("Couldn't initialize %s speller."), dictionary));
369 gtkaspell = g_new0(GtkAspell, 1);
371 gtkaspell->gtkaspeller = gtkaspeller;
373 if (use_alternate && alt_dictionary && *alt_dictionary) {
374 Dictionary *alt_dict;
375 GtkAspeller *alt_gtkaspeller;
377 alt_dict = g_new0(Dictionary, 1);
378 if (strrchr(alt_dictionary, '/')) {
379 alt_dict->fullname = g_strdup(strrchr(alt_dictionary, '/')+1);
380 alt_dict->dictname = g_strdup(strrchr(alt_dictionary, '/')+1);
382 alt_dict->fullname = g_strdup(alt_dictionary);
383 alt_dict->dictname = g_strdup(alt_dictionary);
385 if (strchr(alt_dict->fullname, '-')) {
386 *(strchr(alt_dict->fullname, '-')) = '\0';
387 *(strchr(alt_dict->dictname, '-')) = '\0';
390 alt_gtkaspeller = gtkaspeller_new(alt_dict);
391 dictionary_delete(alt_dict);
393 if (!alt_gtkaspeller) {
394 gtkaspell_checkers_error_message(
395 g_strdup_printf(_("Couldn't initialize %s speller."), dictionary));
396 gtkaspeller_delete(gtkaspeller);
401 gtkaspell->alternate_speller = alt_gtkaspeller;
403 gtkaspell->alternate_speller = NULL;
406 gtkaspell->theword[0] = 0x00;
407 gtkaspell->start_pos = 0;
408 gtkaspell->end_pos = 0;
409 gtkaspell->orig_pos = -1;
410 gtkaspell->end_check_pos = -1;
411 gtkaspell->misspelled = -1;
412 gtkaspell->check_while_typing = check_while_typing;
413 gtkaspell->recheck_when_changing_dict = recheck_when_changing_dict;
414 gtkaspell->continue_check = NULL;
415 gtkaspell->replace_entry = NULL;
416 gtkaspell->gtktext = gtktext;
417 gtkaspell->max_sug = -1;
418 gtkaspell->suggestions_list = NULL;
419 gtkaspell->use_alternate = use_alternate;
420 gtkaspell->use_both_dicts = use_both_dicts;
421 gtkaspell->parent_window = GTK_WIDGET(parent_win);
422 gtkaspell->dict_changed_cb = dict_changed_cb;
423 gtkaspell->menu_changed_cb = spell_menu_cb;
424 gtkaspell->menu_changed_data = data;
426 allocate_color(gtkaspell, misspelled_color);
428 g_signal_connect(G_OBJECT(gtktext), "key_press_event",
429 G_CALLBACK(key_press_cb), gtkaspell);
430 g_signal_connect_after(G_OBJECT(buffer), "insert-text",
431 G_CALLBACK(entry_insert_cb), gtkaspell);
432 g_signal_connect_after(G_OBJECT(buffer), "delete-range",
433 G_CALLBACK(entry_delete_cb), gtkaspell);
434 /*g_signal_connect(G_OBJECT(gtktext), "button-press-event",
435 G_CALLBACK(button_press_intercept_cb),
437 g_signal_connect(G_OBJECT(gtktext), "populate-popup",
438 G_CALLBACK(button_press_intercept_cb), gtkaspell);
440 debug_print("Aspell: created gtkaspell %p\n", gtkaspell);
445 void gtkaspell_delete(GtkAspell *gtkaspell)
447 GtkTextView *gtktext = gtkaspell->gtktext;
449 g_signal_handlers_disconnect_by_func(G_OBJECT(gtktext),
450 G_CALLBACK(key_press_cb),
452 g_signal_handlers_disconnect_by_func(G_OBJECT(gtktext),
453 G_CALLBACK(entry_insert_cb),
455 g_signal_handlers_disconnect_by_func(G_OBJECT(gtktext),
456 G_CALLBACK(entry_delete_cb),
458 g_signal_handlers_disconnect_by_func(G_OBJECT(gtktext),
459 G_CALLBACK(button_press_intercept_cb),
462 gtkaspell_uncheck_all(gtkaspell);
464 gtkaspeller_delete(gtkaspell->gtkaspeller);
466 if (gtkaspell->alternate_speller)
467 gtkaspeller_delete(gtkaspell->alternate_speller);
469 if (gtkaspell->suggestions_list)
470 gtkaspell_free_suggestions_list(gtkaspell);
472 debug_print("Aspell: deleting gtkaspell %p\n", gtkaspell);
479 void gtkaspell_dict_changed(GtkAspell *gtkaspell)
481 if(!gtkaspell || !gtkaspell->dict_changed_cb ||
482 !gtkaspell->menu_changed_data)
485 gtkaspell->dict_changed_cb(gtkaspell->menu_changed_data);
488 static gboolean key_press_cb (GtkWidget *text_view,
490 GtkAspell *gtkaspell)
494 cm_return_val_if_fail(gtkaspell->gtkaspeller->speller, FALSE);
496 if (!gtkaspell->check_while_typing)
499 switch (event->keyval) {
505 case GDK_KEY_Page_Up:
506 case GDK_KEY_Page_Down:
509 pos = get_textview_buffer_offset(GTK_TEXT_VIEW(text_view));
511 check_at(gtkaspell, pos - 1);
513 check_at(gtkaspell, pos);
522 static void entry_insert_cb(GtkTextBuffer *textbuf,
526 GtkAspell *gtkaspell)
530 cm_return_if_fail(gtkaspell->gtkaspeller->speller);
532 if (!gtkaspell->check_while_typing)
535 pos = gtk_text_iter_get_offset(iter);
537 if (iswordsep(g_utf8_get_char(newtext))) {
538 /* did we just end a word? */
540 check_at(gtkaspell, pos - 2);
542 /* did we just split a word? */
543 if (pos < gtk_text_buffer_get_char_count(textbuf))
544 check_at(gtkaspell, pos + 1);
546 /* check as they type, *except* if they're typing at the end (the most
549 if (pos < gtk_text_buffer_get_char_count(textbuf) &&
550 !iswordsep(get_text_index_whar(gtkaspell, pos))) {
551 check_at(gtkaspell, pos - 1);
556 static void entry_delete_cb(GtkTextBuffer *textbuf,
557 GtkTextIter *startiter,
558 GtkTextIter *enditer,
559 GtkAspell *gtkaspell)
564 cm_return_if_fail(gtkaspell->gtkaspeller->speller);
566 if (!gtkaspell->check_while_typing)
569 start = gtk_text_iter_get_offset(startiter);
570 origpos = get_textview_buffer_offset(gtkaspell->gtktext);
572 check_at(gtkaspell, start - 1);
573 check_at(gtkaspell, start);
576 set_textview_buffer_offset(gtkaspell->gtktext, origpos);
577 /* this is to *UNDO* the selection, in case they were holding shift
578 * while hitting backspace. */
579 /* needed with textview ??? */
580 /* gtk_editable_select_region(GTK_EDITABLE(gtktext), origpos, origpos); */
583 void gtkaspell_make_context_menu(GtkMenu *menu, GtkAspell *gtkaspell)
585 GtkMenuItem *menuitem;
586 GSList *spell_menu = NULL;
588 gboolean suggest = FALSE;
590 if (gtkaspell->misspelled &&
591 misspelled_suggest(gtkaspell, gtkaspell->theword)) {
592 spell_menu = make_sug_menu(gtkaspell);
596 spell_menu = gtkaspell_make_config_menu(gtkaspell);
598 menuitem = GTK_MENU_ITEM(gtk_separator_menu_item_new());
599 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), GTK_WIDGET(menuitem));
600 gtk_widget_show(GTK_WIDGET(menuitem));
602 spell_menu = g_slist_reverse(spell_menu);
603 for (items = spell_menu;
604 items; items = items->next) {
605 menuitem = GTK_MENU_ITEM(items->data);
606 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), GTK_WIDGET(menuitem));
607 gtk_widget_show(GTK_WIDGET(menuitem));
609 g_slist_free(spell_menu);
611 g_signal_connect(G_OBJECT(menu), "deactivate",
612 G_CALLBACK(destroy_menu),
615 g_signal_connect(G_OBJECT(menu),
617 G_CALLBACK(aspell_key_pressed),
621 static void set_position_cb(gpointer data, gint pos)
623 GtkAspell *gtkaspell = (GtkAspell *) data;
624 set_textview_buffer_offset(gtkaspell->gtktext, pos);
627 void gtkaspell_context_set(GtkAspell *gtkaspell)
629 gtkaspell->ctx.set_position = set_position_cb;
630 gtkaspell->ctx.set_menu_pos = set_menu_pos;
631 gtkaspell->ctx.find_misspelled = find_misspelled_cb;
632 gtkaspell->ctx.check_word = check_at_cb;
633 gtkaspell->ctx.replace_word = replace_real_word_cb;
634 gtkaspell->ctx.data = (gpointer) gtkaspell;
637 static void button_press_intercept_cb(GtkTextView *gtktext,
638 GtkMenu *menu, GtkAspell *gtkaspell)
640 gtktext = gtkaspell->gtktext;
641 gtkaspell->orig_pos = get_textview_buffer_offset(gtktext);
642 gtkaspell->misspelled = check_at(gtkaspell, gtkaspell->orig_pos);
644 gtkaspell_context_set(gtkaspell);
645 gtkaspell_make_context_menu(menu, gtkaspell);
647 /* Checker creation */
648 static GtkAspeller *gtkaspeller_new(Dictionary *dictionary)
650 GtkAspeller *gtkaspeller = NULL;
654 cm_return_val_if_fail(gtkaspellcheckers, NULL);
656 cm_return_val_if_fail(dictionary, NULL);
658 if (dictionary->dictname == NULL)
659 gtkaspell_checkers_error_message(
660 g_strdup(_("No dictionary selected.")));
662 cm_return_val_if_fail(dictionary->fullname, NULL);
664 dict = dictionary_dup(dictionary);
666 tmp = g_new0(GtkAspeller, 1);
667 tmp->dictionary = dict;
671 if ((gtkaspeller = gtkaspeller_real_new(dict)) != NULL) {
672 gtkaspellcheckers->checkers = g_slist_append(
673 gtkaspellcheckers->checkers,
676 debug_print("Aspell: Created a new gtkaspeller %p\n",
679 dictionary_delete(dict);
681 debug_print("Aspell: Could not create spell checker.\n");
684 debug_print("Aspell: number of existing checkers %d\n",
685 g_slist_length(gtkaspellcheckers->checkers));
690 static GtkAspeller *gtkaspeller_real_new(Dictionary *dict)
692 GtkAspeller *gtkaspeller;
693 EnchantBroker *broker;
694 EnchantDict *speller;
696 cm_return_val_if_fail(gtkaspellcheckers, NULL);
697 cm_return_val_if_fail(dict, NULL);
699 gtkaspeller = g_new(GtkAspeller, 1);
701 gtkaspeller->dictionary = dict;
703 broker = enchant_broker_init();
706 gtkaspell_checkers_error_message(
707 g_strdup(_("Couldn't initialize Enchant broker.")));
711 if ((speller = set_dictionary(broker, dict)) == NULL) {
712 gtkaspell_checkers_error_message(
713 g_strdup_printf(_("Couldn't initialize %s dictionary:"), dict->fullname));
714 gtkaspell_checkers_error_message(
715 g_strdup(enchant_broker_get_error(broker)));
719 gtkaspeller->speller = speller;
720 gtkaspeller->broker = broker;
725 static GtkAspeller *gtkaspeller_delete(GtkAspeller *gtkaspeller)
727 cm_return_val_if_fail(gtkaspellcheckers, NULL);
729 gtkaspellcheckers->checkers =
730 g_slist_remove(gtkaspellcheckers->checkers,
733 debug_print("Aspell: Deleting gtkaspeller %p.\n",
736 gtkaspeller_real_delete(gtkaspeller);
738 debug_print("Aspell: number of existing checkers %d\n",
739 g_slist_length(gtkaspellcheckers->checkers));
744 static GtkAspeller *gtkaspeller_real_delete(GtkAspeller *gtkaspeller)
746 cm_return_val_if_fail(gtkaspeller, NULL);
747 cm_return_val_if_fail(gtkaspeller->speller, NULL);
749 enchant_broker_free_dict(gtkaspeller->broker, gtkaspeller->speller);
750 enchant_broker_free(gtkaspeller->broker);
752 dictionary_delete(gtkaspeller->dictionary);
754 debug_print("Aspell: gtkaspeller %p deleted.\n",
762 /*****************************************************************************/
763 /* Checker configuration */
765 static EnchantDict *set_dictionary(EnchantBroker *broker, Dictionary *dict)
767 cm_return_val_if_fail(broker, FALSE);
768 cm_return_val_if_fail(dict, FALSE);
770 return enchant_broker_request_dict(broker, dict->dictname );
773 static void set_use_both_cb(GtkMenuItem *w, GtkAspell *gtkaspell)
775 gtkaspell->use_both_dicts = gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(w));
776 gtkaspell_dict_changed(gtkaspell);
778 if (gtkaspell->menu_changed_cb)
779 gtkaspell->menu_changed_cb(gtkaspell->menu_changed_data);
782 /* misspelled_suggest() - Create a suggestion list for word */
783 static GList *misspelled_suggest(GtkAspell *gtkaspell, gchar *word)
788 cm_return_val_if_fail(word, NULL);
793 gtkaspell_free_suggestions_list(gtkaspell);
795 suggestions = enchant_dict_suggest(gtkaspell->gtkaspeller->speller, word, strlen(word), &num_sug);
796 list = g_list_append(list, g_strdup(word));
797 if (suggestions == NULL || num_sug == 0) {
798 gtkaspell->max_sug = -1;
799 gtkaspell->suggestions_list = list;
802 for (i = 0; i < num_sug; i++)
803 list = g_list_append(list, g_strdup((gchar *)suggestions[i]));
805 gtkaspell->max_sug = num_sug - 1;
806 gtkaspell->suggestions_list = list;
807 enchant_dict_free_string_list(gtkaspell->gtkaspeller->speller, suggestions);
811 /* misspelled_test() - Just test if word is correctly spelled */
812 int gtkaspell_misspelled_test(GtkAspell *gtkaspell, char *word)
815 cm_return_val_if_fail(word, 0);
820 result = enchant_dict_check(gtkaspell->gtkaspeller->speller, word, strlen(word));
822 if (result && gtkaspell->use_both_dicts && gtkaspell->alternate_speller) {
823 gtkaspell_use_alternate_dict(gtkaspell);
824 result = enchant_dict_check(gtkaspell->gtkaspeller->speller, word, strlen(word));
825 gtkaspell_use_alternate_dict(gtkaspell);
827 return (result && strcasecmp(word, "sylpheed") &&
828 strcasecmp(word, "claws-mail"));
832 static gboolean iswordsep(gunichar c)
834 return (g_unichar_isspace(c) || g_unichar_ispunct(c)) && c != (gunichar)'\'';
837 static gunichar get_text_index_whar(GtkAspell *gtkaspell, int pos)
839 GtkTextView *view = gtkaspell->gtktext;
840 GtkTextBuffer *buffer = gtk_text_view_get_buffer(view);
841 GtkTextIter start, end;
845 gtk_text_buffer_get_iter_at_offset(buffer, &start, pos);
846 gtk_text_buffer_get_iter_at_offset(buffer, &end, pos+1);
848 utf8chars = gtk_text_iter_get_text(&start, &end);
849 a = g_utf8_get_char(utf8chars);
855 /* get_word_from_pos () - return the word pointed to. */
856 /* Handles correctly the quotes. */
857 static gboolean get_word_from_pos(GtkAspell *gtkaspell, gint pos,
858 char* buf, gint buflen,
859 gint *pstart, gint *pend)
862 /* TODO : when correcting a word into quotes, change the color of */
863 /* the quotes too, as may be they were highlighted before. To do */
864 /* this, we can use two others pointers that points to the whole */
865 /* word including quotes. */
871 GtkTextView *gtktext;
873 gtktext = gtkaspell->gtktext;
874 if (iswordsep(get_text_index_whar(gtkaspell, pos)))
877 /* The apostrophe character is somtimes used for quotes
878 * So include it in the word only if it is not surrounded
879 * by other characters.
882 for (start = pos; start >= 0; --start) {
883 c = get_text_index_whar(gtkaspell, start);
884 if (c == (gunichar)'\'') {
886 if (g_unichar_isspace(get_text_index_whar(gtkaspell,
888 || g_unichar_ispunct(get_text_index_whar(gtkaspell,
890 || g_unichar_isdigit(get_text_index_whar(gtkaspell,
892 /* start_quote = TRUE; */
897 /* start_quote = TRUE; */
901 else if (g_unichar_isspace(c) || g_unichar_ispunct(c) || g_unichar_isdigit(c))
907 for (end = pos; end < get_textview_buffer_charcount(gtktext); end++) {
908 c = get_text_index_whar(gtkaspell, end);
909 if (c == (gunichar)'\'') {
910 if (end < get_textview_buffer_charcount(gtktext)) {
911 if (g_unichar_isspace(get_text_index_whar(gtkaspell,
913 || g_unichar_ispunct(get_text_index_whar(gtkaspell,
915 || g_unichar_isdigit(get_text_index_whar(gtkaspell,
917 /* end_quote = TRUE; */
922 /* end_quote = TRUE; */
926 else if (g_unichar_isspace(c) || g_unichar_ispunct(c) || g_unichar_isdigit(c))
936 if (end - start < buflen) {
937 GtkTextIter iterstart, iterend;
939 GtkTextBuffer *buffer = gtk_text_view_get_buffer(gtktext);
940 gtk_text_buffer_get_iter_at_offset(buffer, &iterstart, start);
941 gtk_text_buffer_get_iter_at_offset(buffer, &iterend, end);
942 tmp = gtk_text_buffer_get_text(buffer, &iterstart, &iterend, FALSE);
943 strncpy(buf, tmp, buflen-1);
953 static gboolean check_at(GtkAspell *gtkaspell, gint from_pos)
956 char buf[GTKASPELLWORDSIZE];
958 cm_return_val_if_fail(from_pos >= 0, FALSE);
960 if (!get_word_from_pos(gtkaspell, from_pos, buf, sizeof(buf),
964 if (gtkaspell_misspelled_test(gtkaspell, buf)) {
965 strncpy(gtkaspell->theword, (gchar *)buf, GTKASPELLWORDSIZE - 1);
966 gtkaspell->theword[GTKASPELLWORDSIZE - 1] = 0;
967 gtkaspell->start_pos = start;
968 gtkaspell->end_pos = end;
969 gtkaspell_free_suggestions_list(gtkaspell);
971 change_color(gtkaspell, start, end, (gchar *)buf, &(gtkaspell->highlight));
974 change_color(gtkaspell, start, end, (gchar *)buf, NULL);
979 static gboolean check_at_cb(gpointer data)
981 GtkAspell *gtkaspell = (GtkAspell *)data;
982 return check_at(gtkaspell, gtkaspell->start_pos);
985 static gboolean find_misspelled_cb(gpointer data, gboolean forward)
987 GtkAspell *gtkaspell = (GtkAspell *)data;
995 maxpos = gtkaspell->end_check_pos;
1003 pos = get_textview_buffer_offset(gtkaspell->gtktext);
1004 gtkaspell->orig_pos = pos;
1005 while (iswordsep(get_text_index_whar(gtkaspell, pos)) &&
1006 pos > minpos && pos <= maxpos)
1008 while (!(misspelled = check_at(gtkaspell, pos)) &&
1009 pos > minpos && pos <= maxpos) {
1011 while (!iswordsep(get_text_index_whar(gtkaspell, pos)) &&
1012 pos > minpos && pos <= maxpos)
1015 while (iswordsep(get_text_index_whar(gtkaspell, pos)) &&
1016 pos > minpos && pos <= maxpos)
1023 gboolean gtkaspell_check_next_prev(GtkAspell *gtkaspell, gboolean forward)
1025 gboolean misspelled = gtkaspell->ctx.find_misspelled(gtkaspell->ctx.data,
1030 misspelled_suggest(gtkaspell, gtkaspell->theword);
1033 gtkaspell->orig_pos = gtkaspell->end_pos;
1035 gtkaspell->ctx.set_position(gtkaspell->ctx.data, gtkaspell->end_pos);
1037 /* only execute when in textview context */
1038 if (gtkaspell == (GtkAspell *)gtkaspell->ctx.data) {
1039 /* scroll line to window center */
1040 gtk_text_view_scroll_to_mark(gtkaspell->gtktext,
1041 gtk_text_buffer_get_insert(
1042 gtk_text_view_get_buffer(gtkaspell->gtktext)),
1043 0.0, TRUE, 0.0, 0.5);
1044 /* let textview recalculate coordinates (set_menu_pos) */
1045 while (gtk_events_pending ())
1046 gtk_main_iteration ();
1049 list = make_sug_menu(gtkaspell);
1050 menu = gtk_menu_new();
1051 for (cur = list; cur; cur = cur->next)
1052 gtk_menu_shell_append(GTK_MENU_SHELL(menu), GTK_WIDGET(cur->data));
1054 gtk_menu_popup(GTK_MENU(menu), NULL, NULL,
1055 gtkaspell->ctx.set_menu_pos,
1056 gtkaspell->ctx.data,
1057 0, GDK_CURRENT_TIME);
1058 g_signal_connect(G_OBJECT(menu), "deactivate",
1059 G_CALLBACK(destroy_menu),
1061 g_signal_connect(G_OBJECT(menu),
1063 G_CALLBACK(aspell_key_pressed),
1068 reset_theword_data(gtkaspell);
1070 alertpanel_notice(_("No misspelled word found."));
1071 gtkaspell->ctx.set_position(gtkaspell->ctx.data,
1072 gtkaspell->orig_pos);
1078 void gtkaspell_check_backwards(GtkAspell *gtkaspell)
1080 gtkaspell->continue_check = NULL;
1081 gtkaspell->end_check_pos =
1082 get_textview_buffer_charcount(gtkaspell->gtktext);
1083 gtkaspell_context_set(gtkaspell);
1084 gtkaspell_check_next_prev(gtkaspell, FALSE);
1087 void gtkaspell_check_forwards_go(GtkAspell *gtkaspell)
1090 gtkaspell->continue_check = NULL;
1091 gtkaspell->end_check_pos =
1092 get_textview_buffer_charcount(gtkaspell->gtktext);
1093 gtkaspell_context_set(gtkaspell);
1094 gtkaspell_check_next_prev(gtkaspell, TRUE);
1097 void gtkaspell_check_all(GtkAspell *gtkaspell)
1099 GtkTextView *gtktext;
1101 GtkTextBuffer *buffer;
1102 GtkTextIter startiter, enditer;
1104 cm_return_if_fail(gtkaspell);
1105 cm_return_if_fail(gtkaspell->gtktext);
1107 gtktext = gtkaspell->gtktext;
1108 buffer = gtk_text_view_get_buffer(gtktext);
1109 gtk_text_buffer_get_selection_bounds(buffer, &startiter, &enditer);
1110 start = gtk_text_iter_get_offset(&startiter);
1111 end = gtk_text_iter_get_offset(&enditer);
1115 end = gtk_text_buffer_get_char_count(buffer);
1116 } else if (start > end) {
1124 set_textview_buffer_offset(gtktext, start);
1126 gtkaspell->continue_check = continue_check;
1127 gtkaspell->end_check_pos = end;
1129 gtkaspell_context_set(gtkaspell);
1130 gtkaspell->misspelled = gtkaspell_check_next_prev(gtkaspell, TRUE);
1133 static void continue_check(gpointer *data)
1135 GtkAspell *gtkaspell = (GtkAspell *) data;
1136 gint pos = get_textview_buffer_offset(gtkaspell->gtktext);
1137 if (pos < gtkaspell->end_check_pos && gtkaspell->misspelled)
1138 gtkaspell->misspelled = gtkaspell_check_next_prev(gtkaspell, TRUE);
1140 gtkaspell->continue_check = NULL;
1143 void gtkaspell_highlight_all(GtkAspell *gtkaspell)
1148 GtkTextView *gtktext;
1150 cm_return_if_fail(gtkaspell->gtkaspeller->speller);
1152 gtktext = gtkaspell->gtktext;
1154 len = get_textview_buffer_charcount(gtktext);
1156 origpos = get_textview_buffer_offset(gtktext);
1160 iswordsep(get_text_index_whar(gtkaspell, pos)))
1163 !iswordsep(get_text_index_whar(gtkaspell, pos)))
1166 check_at(gtkaspell, pos - 1);
1168 set_textview_buffer_offset(gtktext, origpos);
1171 static void replace_with_supplied_word_cb(GtkWidget *w, GtkAspell *gtkaspell)
1174 GdkEvent *e= (GdkEvent *) gtk_get_current_event();
1176 newword = gtk_editable_get_chars(GTK_EDITABLE(gtkaspell->replace_entry),
1179 if (strcmp(newword, gtkaspell->theword)) {
1180 gtkaspell->ctx.replace_word(gtkaspell->ctx.data, newword);
1182 if ((e->type == GDK_KEY_PRESS &&
1183 ((GdkEventKey *) e)->state & GDK_CONTROL_MASK)) {
1184 enchant_dict_store_replacement(gtkaspell->gtkaspeller->speller,
1185 gtkaspell->theword, strlen(gtkaspell->theword),
1186 newword, strlen(newword));
1188 gtkaspell->replace_entry = NULL;
1193 if (w && GTK_IS_DIALOG(w)) {
1194 gtk_widget_destroy(w);
1197 set_point_continue(gtkaspell);
1201 static void replace_word_cb(GtkWidget *w, gpointer data)
1203 const char *newword;
1204 GtkAspell *gtkaspell = (GtkAspell *) data;
1205 GdkEvent *e= (GdkEvent *) gtk_get_current_event();
1207 newword = gtk_label_get_text(GTK_LABEL(gtk_bin_get_child(GTK_BIN((w)))));
1209 gtkaspell->ctx.replace_word(gtkaspell->ctx.data, newword);
1211 if ((e->type == GDK_KEY_PRESS &&
1212 ((GdkEventKey *) e)->state & GDK_CONTROL_MASK) ||
1213 (e->type == GDK_BUTTON_RELEASE &&
1214 ((GdkEventButton *) e)->state & GDK_CONTROL_MASK)) {
1215 enchant_dict_store_replacement(gtkaspell->gtkaspeller->speller,
1216 gtkaspell->theword, strlen(gtkaspell->theword),
1217 newword, strlen(newword));
1220 gtk_menu_shell_deactivate(GTK_MENU_SHELL(w->parent));
1222 set_point_continue(gtkaspell);
1225 void gtkaspell_block_check(GtkAspell *gtkaspell)
1227 GtkTextView *gtktext;
1229 if (gtkaspell == NULL)
1232 gtktext = gtkaspell->gtktext;
1233 g_signal_handlers_block_by_func(G_OBJECT(gtktext),
1234 G_CALLBACK(key_press_cb),
1236 g_signal_handlers_block_by_func(G_OBJECT(gtktext),
1237 G_CALLBACK(entry_insert_cb),
1239 g_signal_handlers_block_by_func(G_OBJECT(gtktext),
1240 G_CALLBACK(entry_delete_cb),
1244 void gtkaspell_unblock_check(GtkAspell *gtkaspell)
1246 GtkTextView *gtktext;
1248 if (gtkaspell == NULL)
1251 gtktext = gtkaspell->gtktext;
1252 g_signal_handlers_unblock_by_func(G_OBJECT(gtktext),
1253 G_CALLBACK(key_press_cb),
1255 g_signal_handlers_unblock_by_func(G_OBJECT(gtktext),
1256 G_CALLBACK(entry_insert_cb),
1258 g_signal_handlers_unblock_by_func(G_OBJECT(gtktext),
1259 G_CALLBACK(entry_delete_cb),
1263 static void replace_real_word(GtkAspell *gtkaspell, const gchar *newword)
1268 GtkTextView *gtktext;
1269 GtkTextBuffer *textbuf;
1270 GtkTextIter startiter, enditer;
1272 if (!newword) return;
1274 gtktext = gtkaspell->gtktext;
1275 textbuf = gtk_text_view_get_buffer(gtktext);
1277 origpos = gtkaspell->orig_pos;
1279 oldlen = gtkaspell->end_pos - gtkaspell->start_pos;
1281 newlen = strlen(newword); /* FIXME: multybyte characters? */
1283 gtkaspell_block_check(gtkaspell);
1285 gtk_text_buffer_get_iter_at_offset(textbuf, &startiter,
1286 gtkaspell->start_pos);
1287 gtk_text_buffer_get_iter_at_offset(textbuf, &enditer,
1288 gtkaspell->end_pos);
1289 g_signal_emit_by_name(G_OBJECT(textbuf), "delete-range",
1290 &startiter, &enditer, gtkaspell);
1291 g_signal_emit_by_name(G_OBJECT(textbuf), "insert-text",
1292 &startiter, newword, newlen, gtkaspell);
1294 gtkaspell_unblock_check(gtkaspell);
1296 /* Put the point and the position where we clicked with the mouse
1297 * It seems to be a hack, as I must thaw,freeze,thaw the widget
1298 * to let it update correctly the word insertion and then the
1299 * point & position position. If not, SEGV after the first replacement
1300 * If the new word ends before point, put the point at its end.
1303 if (origpos - gtkaspell->start_pos < oldlen &&
1304 origpos - gtkaspell->start_pos >= 0) {
1305 /* Original point was in the word.
1306 * Let it there unless point is going to be outside of the word
1308 if (origpos - gtkaspell->start_pos >= newlen) {
1309 pos = gtkaspell->start_pos + newlen;
1312 else if (origpos >= gtkaspell->end_pos) {
1313 /* move the position according to the change of length */
1314 pos = origpos + newlen - oldlen;
1317 gtkaspell->end_pos = gtkaspell->start_pos + strlen(newword); /* FIXME: multibyte characters? */
1319 if (get_textview_buffer_charcount(gtktext) < pos)
1320 pos = get_textview_buffer_charcount(gtktext);
1321 gtkaspell->orig_pos = pos;
1323 set_textview_buffer_offset(gtktext, gtkaspell->orig_pos);
1326 static void replace_real_word_cb(gpointer data, const gchar *newword)
1328 replace_real_word((GtkAspell *)data, newword);
1331 /* Accept this word for this session */
1332 static void add_word_to_session_cb(GtkWidget *w, gpointer data)
1334 GtkAspell *gtkaspell = (GtkAspell *) data;
1336 enchant_dict_add_to_session(gtkaspell->gtkaspeller->speller, gtkaspell->theword, strlen(gtkaspell->theword));
1338 gtkaspell->ctx.check_word(gtkaspell->ctx.data);
1339 gtkaspell_dict_changed(gtkaspell);
1341 gtk_menu_shell_deactivate(GTK_MENU_SHELL(GTK_WIDGET(w)->parent));
1343 set_point_continue(gtkaspell);
1346 /* add_word_to_personal_cb() - add word to personal dict. */
1347 static void add_word_to_personal_cb(GtkWidget *w, gpointer data)
1349 GtkAspell *gtkaspell = (GtkAspell *) data;
1351 enchant_dict_add_to_pwl(gtkaspell->gtkaspeller->speller, gtkaspell->theword, strlen(gtkaspell->theword));
1353 gtkaspell->ctx.check_word(gtkaspell->ctx.data);
1354 gtkaspell_dict_changed(gtkaspell);
1356 gtk_menu_shell_deactivate(GTK_MENU_SHELL(GTK_WIDGET(w)->parent));
1357 set_point_continue(gtkaspell);
1360 static void check_with_alternate_cb(GtkWidget *w, gpointer data)
1362 GtkAspell *gtkaspell = (GtkAspell *)data;
1365 gtk_menu_shell_deactivate(GTK_MENU_SHELL(GTK_WIDGET(w)->parent));
1367 gtkaspell_use_alternate_dict(gtkaspell);
1368 misspelled = gtkaspell->ctx.check_word(gtkaspell->ctx.data);
1370 if (!gtkaspell->continue_check) {
1372 gtkaspell->misspelled = misspelled;
1374 if (gtkaspell->misspelled) {
1377 misspelled_suggest(gtkaspell, gtkaspell->theword);
1379 gtkaspell->ctx.set_position(gtkaspell->ctx.data,
1380 gtkaspell->end_pos);
1382 list = make_sug_menu(gtkaspell);
1383 menu = gtk_menu_new();
1384 for (cur = list; cur; cur = cur->next)
1385 gtk_menu_shell_append(GTK_MENU_SHELL(menu), GTK_WIDGET(cur->data));
1387 gtk_menu_popup(GTK_MENU(menu), NULL, NULL,
1388 gtkaspell->ctx.set_menu_pos,
1389 gtkaspell->ctx.data, 0,
1391 g_signal_connect(G_OBJECT(menu), "deactivate",
1392 G_CALLBACK(destroy_menu),
1394 g_signal_connect(G_OBJECT(menu),
1396 G_CALLBACK(aspell_key_pressed),
1401 gtkaspell->orig_pos = gtkaspell->start_pos;
1403 set_point_continue(gtkaspell);
1406 static gboolean replace_key_pressed(GtkWidget *widget,
1408 GtkAspell *gtkaspell)
1410 if (event && event->keyval == GDK_KEY_Escape) {
1411 gtk_widget_destroy(widget);
1413 } else if (event && event->keyval == GDK_KEY_Return) {
1414 replace_with_supplied_word_cb(widget, gtkaspell);
1420 static void replace_with_create_dialog_cb(GtkWidget *w, gpointer data)
1422 static PangoFontDescription *font_desc;
1428 GtkWidget *ok_button;
1429 GtkWidget *cancel_button;
1430 GtkWidget *confirm_area;
1432 gchar *utf8buf, *thelabel;
1434 GtkAspell *gtkaspell = (GtkAspell *) data;
1436 gdk_window_get_origin((GTK_WIDGET(w)->parent)->window, &xx, &yy);
1438 gtk_menu_shell_deactivate(GTK_MENU_SHELL(GTK_WIDGET(w)->parent));
1440 dialog = gtk_dialog_new();
1442 gtk_window_set_resizable(GTK_WINDOW(dialog), FALSE);
1443 gtk_window_set_title(GTK_WINDOW(dialog),_("Replace unknown word"));
1444 gtk_window_move(GTK_WINDOW(dialog), xx, yy);
1446 g_signal_connect_swapped(G_OBJECT(dialog), "destroy",
1447 G_CALLBACK(gtk_widget_destroy),
1450 gtk_box_set_spacing (GTK_BOX (GTK_DIALOG (dialog)->vbox), 14);
1451 hbox = gtk_hbox_new (FALSE, 12);
1452 gtk_container_set_border_width (GTK_CONTAINER (hbox), 5);
1453 gtk_widget_show (hbox);
1454 gtk_box_pack_start (GTK_BOX (GTK_DIALOG (dialog)->vbox), hbox,
1457 utf8buf = g_strdup(gtkaspell->theword);
1459 thelabel = g_strdup_printf(g_strconcat("<span weight=\"bold\" size=\"larger\">",
1460 _("Replace \"%s\" with: "), "</span>", NULL),
1463 icon = gtk_image_new_from_stock(GTK_STOCK_DIALOG_QUESTION,
1464 GTK_ICON_SIZE_DIALOG);
1465 gtk_misc_set_alignment (GTK_MISC (icon), 0.5, 0.0);
1466 gtk_box_pack_start (GTK_BOX (hbox), icon, FALSE, FALSE, 0);
1468 vbox = gtk_vbox_new (FALSE, 12);
1469 gtk_box_pack_start (GTK_BOX (hbox), vbox, TRUE, TRUE, 0);
1470 gtk_widget_show (vbox);
1472 label = gtk_label_new(thelabel);
1473 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
1474 gtk_label_set_justify(GTK_LABEL(label), GTK_JUSTIFY_LEFT);
1475 gtk_label_set_use_markup (GTK_LABEL (label), TRUE);
1476 gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 0);
1477 gtk_label_set_line_wrap(GTK_LABEL(label), TRUE);
1481 size = pango_font_description_get_size
1482 (label->style->font_desc);
1483 font_desc = pango_font_description_new();
1484 pango_font_description_set_weight
1485 (font_desc, PANGO_WEIGHT_BOLD);
1486 pango_font_description_set_size
1487 (font_desc, size * PANGO_SCALE_LARGE);
1490 gtk_widget_modify_font(label, font_desc);
1493 entry = gtk_entry_new();
1494 gtkaspell->replace_entry = entry;
1495 gtk_entry_set_text(GTK_ENTRY(entry), utf8buf);
1496 gtk_editable_select_region(GTK_EDITABLE(entry), 0, -1);
1497 g_signal_connect(G_OBJECT(dialog),
1499 G_CALLBACK(replace_key_pressed), gtkaspell);
1500 gtk_box_pack_start(GTK_BOX(vbox), entry, FALSE, FALSE, 0);
1503 label = gtk_label_new(_("Holding down Control key while pressing "
1504 "Enter\nwill learn from mistake.\n"));
1505 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
1506 gtk_label_set_justify(GTK_LABEL(label), GTK_JUSTIFY_LEFT);
1507 gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 0);
1508 gtk_widget_show(label);
1510 gtkut_stock_button_set_create(&confirm_area,
1511 &cancel_button, GTK_STOCK_CANCEL,
1512 &ok_button, GTK_STOCK_OK,
1515 gtk_box_pack_end(GTK_BOX(GTK_DIALOG(dialog)->action_area),
1516 confirm_area, FALSE, FALSE, 0);
1517 gtk_container_set_border_width(GTK_CONTAINER(confirm_area), 5);
1519 g_signal_connect(G_OBJECT(ok_button), "clicked",
1520 G_CALLBACK(replace_with_supplied_word_cb),
1522 g_signal_connect_swapped(G_OBJECT(ok_button), "clicked",
1523 G_CALLBACK(gtk_widget_destroy),
1526 g_signal_connect_swapped(G_OBJECT(cancel_button), "clicked",
1527 G_CALLBACK(gtk_widget_destroy),
1530 gtk_widget_grab_focus(entry);
1532 gtk_window_set_modal(GTK_WINDOW(dialog), TRUE);
1534 gtk_widget_show_all(dialog);
1537 static void gtkaspell_uncheck_all(GtkAspell * gtkaspell)
1539 GtkTextView *gtktext;
1540 GtkTextBuffer *buffer;
1541 GtkTextIter startiter, enditer;
1543 gtktext = gtkaspell->gtktext;
1545 buffer = gtk_text_view_get_buffer(gtktext);
1546 gtk_text_buffer_get_iter_at_offset(buffer, &startiter, 0);
1547 gtk_text_buffer_get_iter_at_offset(buffer, &enditer,
1548 get_textview_buffer_charcount(gtktext)-1);
1549 gtk_text_buffer_remove_tag_by_name(buffer, "misspelled",
1550 &startiter, &enditer);
1553 static void toggle_check_while_typing_cb(GtkWidget *w, gpointer data)
1555 GtkAspell *gtkaspell = (GtkAspell *) data;
1557 gtkaspell->check_while_typing = gtkaspell->check_while_typing == FALSE;
1559 if (!gtkaspell->check_while_typing)
1560 gtkaspell_uncheck_all(gtkaspell);
1561 if (gtkaspell->menu_changed_cb)
1562 gtkaspell->menu_changed_cb(gtkaspell->menu_changed_data);
1565 static GSList *create_empty_dictionary_list(void)
1567 GSList *list = NULL;
1570 dict = g_new0(Dictionary, 1);
1571 dict->fullname = g_strdup(_("None"));
1572 dict->dictname = NULL;
1574 return g_slist_append(list, dict);
1577 static void list_dict_cb(const char * const lang_tag,
1578 const char * const provider_name,
1579 const char * const provider_desc,
1580 const char * const provider_file,
1583 GSList **list = (GSList **)data;
1584 Dictionary *dict = g_new0(Dictionary, 1);
1585 dict->fullname = g_strdup(lang_tag);
1586 dict->dictname = g_strdup(lang_tag);
1588 if (g_slist_find_custom(*list, dict,
1589 (GCompareFunc) compare_dict) == NULL) {
1590 debug_print("Aspell: found dictionary %s %s\n", dict->fullname,
1592 *list = g_slist_insert_sorted(*list, dict,
1593 (GCompareFunc) compare_dict);
1595 dictionary_delete(dict);
1599 /* gtkaspell_get_dictionary_list() - returns list of dictionary names */
1600 static GSList *gtkaspell_get_dictionary_list(gint refresh)
1603 EnchantBroker *broker;
1605 if (!gtkaspellcheckers)
1606 gtkaspell_checkers_init();
1608 if (gtkaspellcheckers->dictionary_list && !refresh)
1609 return gtkaspellcheckers->dictionary_list;
1611 gtkaspell_free_dictionary_list(
1612 gtkaspellcheckers->dictionary_list);
1615 broker = enchant_broker_init();
1617 enchant_broker_list_dicts(broker, list_dict_cb, &list);
1619 enchant_broker_free(broker);
1623 debug_print("Aspell: error when searching for dictionaries: "
1624 "No dictionary found.\n");
1625 list = create_empty_dictionary_list();
1628 gtkaspellcheckers->dictionary_list = list;
1633 static void gtkaspell_free_dictionary_list(GSList *list)
1637 for (walk = list; walk != NULL; walk = g_slist_next(walk))
1639 dict = (Dictionary *) walk->data;
1640 dictionary_delete(dict);
1645 GtkTreeModel *gtkaspell_dictionary_store_new_with_refresh(gboolean refresh)
1647 GSList *dict_list, *tmp;
1648 GtkListStore *store;
1652 dict_list = gtkaspell_get_dictionary_list(refresh);
1653 cm_return_val_if_fail(dict_list, NULL);
1655 store = gtk_list_store_new(SET_GTKASPELL_SIZE,
1660 for (tmp = dict_list; tmp != NULL; tmp = g_slist_next(tmp)) {
1661 dict = (Dictionary *) tmp->data;
1663 gtk_list_store_append(store, &iter);
1664 gtk_list_store_set(store, &iter,
1665 SET_GTKASPELL_NAME, dict->dictname,
1666 SET_GTKASPELL_FULLNAME, dict->fullname,
1670 return GTK_TREE_MODEL(store);
1673 GtkTreeModel *gtkaspell_dictionary_store_new(void)
1675 return gtkaspell_dictionary_store_new_with_refresh
1679 GtkWidget *gtkaspell_dictionary_combo_new(const gboolean refresh)
1682 GtkCellRenderer *renderer;
1684 combo = gtk_combo_box_new_with_model(
1685 gtkaspell_dictionary_store_new_with_refresh(refresh));
1686 gtk_combo_box_set_active(GTK_COMBO_BOX(combo), 0);
1687 gtk_widget_show(combo);
1689 renderer = gtk_cell_renderer_text_new();
1690 gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(combo), renderer, TRUE);
1691 gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(combo),renderer,
1692 "text", SET_GTKASPELL_NAME, NULL);
1697 gchar *gtkaspell_get_dictionary_menu_active_item(GtkComboBox *combo)
1700 GtkTreeModel *model;
1701 gchar *dict_fullname = NULL;
1703 cm_return_val_if_fail(GTK_IS_COMBO_BOX(combo), NULL);
1704 cm_return_val_if_fail(gtk_combo_box_get_active_iter(combo, &iter), NULL);
1706 model = gtk_combo_box_get_model(combo);
1710 gtk_tree_model_get(model, &iter,
1711 SET_GTKASPELL_FULLNAME, &dict_fullname,
1714 return dict_fullname;
1717 gint gtkaspell_set_dictionary_menu_active_item(GtkComboBox *combo,
1718 const gchar *dictionary)
1720 GtkTreeModel *model;
1722 gchar *dict_name = NULL;
1724 cm_return_val_if_fail(combo != NULL, 0);
1725 cm_return_val_if_fail(dictionary != NULL, 0);
1726 cm_return_val_if_fail(GTK_IS_COMBO_BOX(combo), 0);
1728 if((model = gtk_combo_box_get_model(combo)) == NULL)
1730 if((gtk_tree_model_get_iter_first(model, &iter)) == FALSE)
1734 gtk_tree_model_get(model, &iter,
1735 SET_GTKASPELL_FULLNAME, &dict_name,
1738 if ((dict_name != NULL) && !strcmp2(dict_name, dictionary)) {
1739 gtk_combo_box_set_active_iter(combo, &iter);
1746 } while ((gtk_tree_model_iter_next(model, &iter)) == TRUE);
1751 void gtkaspell_use_alternate_dict(GtkAspell *gtkaspell)
1755 tmp = gtkaspell->gtkaspeller;
1756 gtkaspell->gtkaspeller = gtkaspell->alternate_speller;
1757 gtkaspell->alternate_speller = tmp;
1760 static void destroy_menu(GtkWidget *widget,
1761 gpointer user_data) {
1762 GtkAspell *gtkaspell = (GtkAspell *)user_data;
1764 if (gtkaspell->accel_group) {
1765 gtk_window_remove_accel_group(GTK_WINDOW(gtkaspell->parent_window),
1766 gtkaspell->accel_group);
1767 gtkaspell->accel_group = NULL;
1771 static gboolean aspell_key_pressed(GtkWidget *widget,
1773 GtkAspell *gtkaspell)
1775 if (event && (isascii(event->keyval) || event->keyval == GDK_KEY_Return)) {
1776 gtk_accel_groups_activate(
1777 G_OBJECT(gtkaspell->parent_window),
1778 event->keyval, event->state);
1779 } else if (event && event->keyval == GDK_KEY_Escape) {
1780 destroy_menu(NULL, gtkaspell);
1785 /* Create a paged submenu with choice of available dictionaries */
1786 static GtkWidget *make_dictionary_list_submenu(GtkAspell *gtkaspell)
1788 GtkWidget *menu, *curmenu, *moremenu, *item;
1794 if (gtkaspellcheckers->dictionary_list == NULL)
1795 gtkaspell_get_dictionary_list(FALSE);
1797 menu = gtk_menu_new();
1800 for (tmp = gtkaspellcheckers->dictionary_list; tmp != NULL;
1801 tmp = g_slist_next(tmp)) {
1802 if (count == MENUCOUNT) {
1804 moremenu = gtk_menu_new();
1805 item = gtk_menu_item_new_with_label(_("More..."));
1806 gtk_menu_item_set_submenu(GTK_MENU_ITEM(item),
1809 gtk_menu_shell_append(GTK_MENU_SHELL(curmenu), item);
1813 dict = (Dictionary *) tmp->data;
1814 item = gtk_check_menu_item_new_with_label(dict->fullname);
1815 g_object_set_data(G_OBJECT(item), "dict_name",
1817 if (strcmp2(dict->fullname,
1818 gtkaspell->gtkaspeller->dictionary->fullname))
1819 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(item), FALSE);
1821 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(item), TRUE);
1822 gtk_widget_set_sensitive(GTK_WIDGET(item),
1825 g_signal_connect(G_OBJECT(item), "activate",
1826 G_CALLBACK(change_dict_cb),
1828 gtk_menu_shell_append(GTK_MENU_SHELL(curmenu), item);
1833 gtk_widget_show_all(menu);
1837 /* make_sug_menu() - Add menus to accept this word for this session
1838 * and to add it to personal dictionary
1840 static GSList *make_sug_menu(GtkAspell *gtkaspell)
1842 GtkWidget *item, *submenu;
1844 GtkAccelGroup *accel;
1845 GList *l = gtkaspell->suggestions_list;
1847 GSList *list = NULL;
1852 accel = gtk_accel_group_new();
1854 if (gtkaspell->accel_group) {
1855 gtk_window_remove_accel_group(GTK_WINDOW(gtkaspell->parent_window),
1856 gtkaspell->accel_group);
1857 gtkaspell->accel_group = NULL;
1860 utf8buf = g_strdup(l->data);
1861 caption = g_strdup_printf(_("\"%s\" unknown in dictionary '%s'"),
1863 gtkaspell->gtkaspeller->dictionary->dictname);
1864 item = gtk_menu_item_new_with_label(caption);
1865 submenu = make_dictionary_list_submenu(gtkaspell);
1866 gtk_menu_item_set_submenu(GTK_MENU_ITEM(item), submenu);
1868 gtk_widget_show(item);
1869 list = g_slist_append(list, item);
1870 gtk_misc_set_alignment(GTK_MISC(gtk_bin_get_child(GTK_BIN((item)))), 0.5, 0.5);
1873 item = gtk_menu_item_new();
1874 gtk_widget_show(item);
1875 list = g_slist_append(list, item);
1877 item = gtk_menu_item_new_with_label(_("Accept in this session"));
1878 gtk_widget_show(item);
1879 list = g_slist_append(list, item);
1880 g_signal_connect(G_OBJECT(item), "activate",
1881 G_CALLBACK(add_word_to_session_cb),
1883 gtk_widget_add_accelerator(item, "activate", accel, GDK_KEY_space,
1885 GTK_ACCEL_LOCKED | GTK_ACCEL_VISIBLE);
1887 item = gtk_menu_item_new_with_label(_("Add to personal dictionary"));
1888 gtk_widget_show(item);
1889 list = g_slist_append(list, item);
1890 g_signal_connect(G_OBJECT(item), "activate",
1891 G_CALLBACK(add_word_to_personal_cb),
1893 gtk_widget_add_accelerator(item, "activate", accel, GDK_KEY_Return,
1895 GTK_ACCEL_LOCKED | GTK_ACCEL_VISIBLE);
1897 item = gtk_menu_item_new_with_label(_("Replace with..."));
1898 gtk_widget_show(item);
1899 list = g_slist_append(list, item);
1900 g_signal_connect(G_OBJECT(item), "activate",
1901 G_CALLBACK(replace_with_create_dialog_cb),
1903 gtk_widget_add_accelerator(item, "activate", accel, GDK_KEY_R, 0,
1904 GTK_ACCEL_LOCKED | GTK_ACCEL_VISIBLE);
1905 gtk_widget_add_accelerator(item, "activate", accel, GDK_KEY_R,
1909 if (gtkaspell->use_alternate && gtkaspell->alternate_speller) {
1910 caption = g_strdup_printf(_("Check with %s"),
1911 gtkaspell->alternate_speller->dictionary->dictname);
1912 item = gtk_menu_item_new_with_label(caption);
1914 gtk_widget_show(item);
1915 list = g_slist_append(list, item);
1916 g_signal_connect(G_OBJECT(item), "activate",
1917 G_CALLBACK(check_with_alternate_cb),
1919 gtk_widget_add_accelerator(item, "activate", accel, GDK_KEY_X, 0,
1920 GTK_ACCEL_LOCKED | GTK_ACCEL_VISIBLE);
1921 gtk_widget_add_accelerator(item, "activate", accel, GDK_KEY_X,
1926 item = gtk_menu_item_new();
1927 gtk_widget_show(item);
1928 list = g_slist_append(list, item);
1932 item = gtk_menu_item_new_with_label(_("(no suggestions)"));
1933 gtk_widget_show(item);
1934 list = g_slist_append(list, item);
1936 GtkWidget *curmenu = NULL;
1940 if (count == MENUCOUNT) {
1943 item = gtk_menu_item_new_with_label(_("More..."));
1944 gtk_widget_show(item);
1946 gtk_menu_shell_append(GTK_MENU_SHELL(curmenu), item);
1948 list = g_slist_append(list, item);
1950 curmenu = gtk_menu_new();
1951 gtk_menu_item_set_submenu(GTK_MENU_ITEM(item),
1955 utf8buf = g_strdup(l->data);
1957 item = gtk_menu_item_new_with_label(utf8buf);
1959 gtk_widget_show(item);
1960 if (curmenu == NULL) {
1961 list = g_slist_append(list, item);
1963 gtk_menu_shell_append(GTK_MENU_SHELL(curmenu), item);
1965 g_signal_connect(G_OBJECT(item), "activate",
1966 G_CALLBACK(replace_word_cb),
1969 if (curmenu == NULL && count < MENUCOUNT) {
1970 gtk_widget_add_accelerator(item, "activate",
1972 GDK_KEY_A + count, 0,
1975 gtk_widget_add_accelerator(item, "activate",
1984 } while ((l = l->next) != NULL);
1987 gtk_window_add_accel_group
1988 (GTK_WINDOW(gtkaspell->parent_window),
1990 gtkaspell->accel_group = accel;
1995 static GSList *populate_submenu(GtkAspell *gtkaspell)
1997 GtkWidget *item, *submenu, *both_dicts_item;
1999 GtkAspeller *gtkaspeller = NULL;
2000 GSList *list = NULL;
2005 gtkaspeller = gtkaspell->gtkaspeller;
2006 dictname = g_strdup_printf(_("Dictionary: %s"),
2007 gtkaspeller->dictionary->dictname);
2008 item = gtk_menu_item_new_with_label(dictname);
2009 gtk_misc_set_alignment(GTK_MISC(gtk_bin_get_child(GTK_BIN((item)))), 0.5, 0.5);
2011 submenu = make_dictionary_list_submenu(gtkaspell);
2012 gtk_menu_item_set_submenu(GTK_MENU_ITEM(item), submenu);
2013 gtk_widget_show(item);
2014 list = g_slist_append(list, item);
2016 item = gtk_menu_item_new();
2017 gtk_widget_show(item);
2018 list = g_slist_append(list, item);
2020 if (gtkaspell->use_alternate && gtkaspell->alternate_speller) {
2021 dictname = g_strdup_printf(_("Use alternate (%s)"),
2022 gtkaspell->alternate_speller->dictionary->dictname);
2023 item = gtk_menu_item_new_with_label(dictname);
2025 g_signal_connect(G_OBJECT(item), "activate",
2026 G_CALLBACK(switch_to_alternate_cb),
2028 gtk_widget_show(item);
2029 list = g_slist_append(list, item);
2032 both_dicts_item = gtk_check_menu_item_new_with_label(_("Use both dictionaries"));
2033 if (gtkaspell->use_both_dicts && gtkaspell->use_alternate) {
2034 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(both_dicts_item), TRUE);
2036 gtk_widget_set_sensitive(both_dicts_item, gtkaspell->use_alternate);
2038 g_signal_connect(G_OBJECT(both_dicts_item), "activate",
2039 G_CALLBACK(set_use_both_cb),
2041 gtk_widget_show(both_dicts_item);
2042 list = g_slist_append(list, both_dicts_item);
2044 item = gtk_menu_item_new();
2045 gtk_widget_show(item);
2046 list = g_slist_append(list, item);
2048 item = gtk_check_menu_item_new_with_label(_("Check while typing"));
2049 if (gtkaspell->check_while_typing)
2050 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(item), TRUE);
2052 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(item), FALSE);
2053 g_signal_connect(G_OBJECT(item), "activate",
2054 G_CALLBACK(toggle_check_while_typing_cb),
2056 gtk_widget_show(item);
2057 list = g_slist_append(list, item);
2062 GSList *gtkaspell_make_config_menu(GtkAspell *gtkaspell)
2064 return populate_submenu(gtkaspell);
2067 static void set_menu_pos(GtkMenu *menu, gint *x, gint *y,
2068 gboolean *push_in, gpointer data)
2070 GtkAspell *gtkaspell = (GtkAspell *) data;
2071 gint xx = 0, yy = 0;
2074 GtkTextView *text = GTK_TEXT_VIEW(gtkaspell->gtktext);
2075 GtkTextBuffer *textbuf;
2080 textbuf = gtk_text_view_get_buffer(gtkaspell->gtktext);
2081 gtk_text_buffer_get_iter_at_mark(textbuf, &iter,
2082 gtk_text_buffer_get_insert(textbuf));
2083 gtk_text_view_get_iter_location(gtkaspell->gtktext, &iter, &rect);
2084 gtk_text_view_buffer_to_window_coords(text, GTK_TEXT_WINDOW_TEXT,
2088 gdk_window_get_origin(GTK_WIDGET(gtkaspell->gtktext)->window, &xx, &yy);
2090 sx = gdk_screen_width();
2091 sy = gdk_screen_height();
2093 gtk_widget_get_child_requisition(GTK_WIDGET(menu), &r);
2098 *x = rect.x + xx + 8;
2100 *y = rect.y + rect.height + yy;
2108 /* change the current dictionary of gtkaspell
2109 - if always_set_alt_dict is set, the alternate dict is unconditionally set to the former
2110 current dictionary (common use: from menu callbacks)
2111 - if always_set_alt_dict is NOT set, the alternate dict will be set to the former
2112 current dictionary only if there is no alternate dictionary already set
2113 (this is when we need to set the current dictionary then the alternate one
2114 when creating a compose window, from the account and folder settings)
2116 gboolean gtkaspell_change_dict(GtkAspell *gtkaspell, const gchar *dictionary,
2117 gboolean always_set_alt_dict)
2120 GtkAspeller *gtkaspeller;
2122 cm_return_val_if_fail(gtkaspell, FALSE);
2123 cm_return_val_if_fail(dictionary, FALSE);
2125 dict = g_new0(Dictionary, 1);
2127 if (strrchr(dictionary, '/')) {
2128 dict->fullname = g_strdup(strrchr(dictionary, '/')+1);
2129 dict->dictname = g_strdup(strrchr(dictionary, '/')+1);
2131 dict->fullname = g_strdup(dictionary);
2132 dict->dictname = g_strdup(dictionary);
2135 if (dict->fullname && strchr(dict->fullname, '-')) {
2136 *(strchr(dict->fullname, '-')) = '\0';
2137 *(strchr(dict->dictname, '-')) = '\0';
2140 if (!dict->fullname || !(*dict->fullname)) {
2141 dictionary_delete(dict);
2144 gtkaspeller = gtkaspeller_new(dict);
2147 alertpanel_warning(_("The spell checker could not change dictionary.\n%s"),
2148 gtkaspellcheckers->error_message);
2150 if (gtkaspell->use_alternate) {
2151 if (gtkaspell->alternate_speller) {
2152 if (always_set_alt_dict) {
2153 gtkaspeller_delete(gtkaspell->alternate_speller);
2154 gtkaspell->alternate_speller = gtkaspell->gtkaspeller;
2156 gtkaspeller_delete(gtkaspell->gtkaspeller);
2158 /* should never be reached as the dicts are always set
2159 to a default value */
2160 gtkaspell->alternate_speller = gtkaspell->gtkaspeller;
2162 gtkaspeller_delete(gtkaspell->gtkaspeller);
2164 gtkaspell->gtkaspeller = gtkaspeller;
2167 dictionary_delete(dict);
2172 /* change the alternate dictionary of gtkaspell (doesn't affect the default dictionary) */
2173 gboolean gtkaspell_change_alt_dict(GtkAspell *gtkaspell, const gchar *alt_dictionary)
2176 GtkAspeller *gtkaspeller;
2178 cm_return_val_if_fail(gtkaspell, FALSE);
2179 cm_return_val_if_fail(alt_dictionary, FALSE);
2181 dict = g_new0(Dictionary, 1);
2182 if (strrchr(alt_dictionary, '/')) {
2183 dict->fullname = g_strdup(strrchr(alt_dictionary, '/')+1);
2184 dict->dictname = g_strdup(strrchr(alt_dictionary, '/')+1);
2186 dict->fullname = g_strdup(alt_dictionary);
2187 dict->dictname = g_strdup(alt_dictionary);
2190 if (dict->fullname && strchr(dict->fullname, '-')) {
2191 *(strchr(dict->fullname, '-')) = '\0';
2192 *(strchr(dict->dictname, '-')) = '\0';
2195 if (!dict->fullname || !(*dict->fullname)) {
2196 dictionary_delete(dict);
2200 gtkaspeller = gtkaspeller_new(dict);
2203 alertpanel_warning(_("The spell checker could not change the alternate dictionary.\n%s"),
2204 gtkaspellcheckers->error_message);
2206 if (gtkaspell->alternate_speller)
2207 gtkaspeller_delete(gtkaspell->alternate_speller);
2208 gtkaspell->alternate_speller = gtkaspeller;
2211 dictionary_delete(dict);
2216 /* Menu call backs */
2218 /* change_dict_cb() - Menu callback : change dict */
2219 static void change_dict_cb(GtkWidget *w, GtkAspell *gtkaspell)
2223 fullname = (gchar *) g_object_get_data(G_OBJECT(w), "dict_name");
2225 if (!strcmp2(fullname, _("None")))
2228 gtkaspell_change_dict(gtkaspell, fullname, TRUE);
2229 gtkaspell_dict_changed(gtkaspell);
2231 if (gtkaspell->menu_changed_cb)
2232 gtkaspell->menu_changed_cb(gtkaspell->menu_changed_data);
2235 static void switch_to_alternate_cb(GtkWidget *w,
2238 GtkAspell *gtkaspell = (GtkAspell *) data;
2239 gtkaspell_use_alternate_dict(gtkaspell);
2240 gtkaspell_dict_changed(gtkaspell);
2242 if (gtkaspell->menu_changed_cb)
2243 gtkaspell->menu_changed_cb(gtkaspell->menu_changed_data);
2246 /* Misc. helper functions */
2248 static void set_point_continue(GtkAspell *gtkaspell)
2250 gtkaspell->ctx.set_position(gtkaspell->ctx.data, gtkaspell->orig_pos);
2252 if (gtkaspell->continue_check)
2253 gtkaspell->continue_check((gpointer *) gtkaspell->ctx.data);
2256 static void allocate_color(GtkAspell *gtkaspell, gint rgbvalue)
2258 GtkTextBuffer *buffer = gtk_text_view_get_buffer(gtkaspell->gtktext);
2259 GdkColor *color = &(gtkaspell->highlight);
2261 /* Shameless copy from Sylpheed's gtkutils.c */
2263 color->red = (int) (((gdouble)((rgbvalue & 0xff0000) >> 16) / 255.0)
2265 color->green = (int) (((gdouble)((rgbvalue & 0x00ff00) >> 8) / 255.0)
2267 color->blue = (int) (((gdouble) (rgbvalue & 0x0000ff) / 255.0)
2271 gtk_text_buffer_create_tag(buffer, "misspelled",
2272 "foreground-gdk", color, NULL);
2274 gtk_text_buffer_create_tag(buffer, "misspelled",
2275 "underline", PANGO_UNDERLINE_ERROR, NULL);
2279 static void change_color(GtkAspell * gtkaspell,
2280 gint start, gint end,
2284 GtkTextView *gtktext;
2285 GtkTextBuffer *buffer;
2286 GtkTextIter startiter, enditer;
2291 gtktext = gtkaspell->gtktext;
2293 buffer = gtk_text_view_get_buffer(gtktext);
2294 gtk_text_buffer_get_iter_at_offset(buffer, &startiter, start);
2295 gtk_text_buffer_get_iter_at_offset(buffer, &enditer, end);
2297 gtk_text_buffer_apply_tag_by_name(buffer, "misspelled",
2298 &startiter, &enditer);
2300 gtk_text_iter_forward_char(&enditer);
2301 gtk_text_buffer_remove_tag_by_name(buffer, "misspelled",
2302 &startiter, &enditer);
2306 /* compare_dict () - compare 2 dict names */
2307 static gint compare_dict(Dictionary *a, Dictionary *b)
2309 guint aparts = 0, bparts = 0;
2312 for (i=0; i < strlen(a->dictname); i++)
2313 if (a->dictname[i] == '-')
2315 for (i=0; i < strlen(b->dictname); i++)
2316 if (b->dictname[i] == '-')
2319 if (aparts != bparts)
2320 return (aparts < bparts) ? -1 : +1;
2323 compare = strcmp2(a->dictname, b->dictname);
2325 compare = strcmp2(a->fullname, b->fullname);
2330 static void dictionary_delete(Dictionary *dict)
2332 g_free(dict->fullname);
2333 g_free(dict->dictname);
2337 static Dictionary *dictionary_dup(const Dictionary *dict)
2341 dict2 = g_new(Dictionary, 1);
2343 dict2->fullname = g_strdup(dict->fullname);
2344 dict2->dictname = g_strdup(dict->dictname);
2349 void gtkaspell_free_suggestions_list(GtkAspell *gtkaspell)
2353 for (list = gtkaspell->suggestions_list; list != NULL;
2357 g_list_free(gtkaspell->suggestions_list);
2359 gtkaspell->max_sug = -1;
2360 gtkaspell->suggestions_list = NULL;
2363 static void reset_theword_data(GtkAspell *gtkaspell)
2365 gtkaspell->start_pos = 0;
2366 gtkaspell->end_pos = 0;
2367 gtkaspell->theword[0] = 0;
2368 gtkaspell->max_sug = -1;
2370 gtkaspell_free_suggestions_list(gtkaspell);
2373 static void free_checkers(gpointer elt, gpointer data)
2375 GtkAspeller *gtkaspeller = elt;
2377 cm_return_if_fail(gtkaspeller);
2379 gtkaspeller_real_delete(gtkaspeller);
2382 gchar *gtkaspell_get_default_dictionary(GtkAspell *gtkaspell)
2384 if (gtkaspell && gtkaspell->gtkaspeller &&
2385 gtkaspell->gtkaspeller->dictionary)
2386 return gtkaspell->gtkaspeller->dictionary->dictname;