1 /* gtkaspell - a spell-checking addon for GtkText
2 * Copyright (c) 2000 Evan Martin (original code for ispell).
3 * Copyright (c) 2002 Melvin Hadasht.
5 * This library is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU Lesser General Public
7 * License as published by the Free Software Foundation; either
8 * version 3 of the License, or (at your option) any later version.
10 * This library is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * Lesser General Public License for more details.
15 * You should have received a copy of the GNU Lesser General Public
16 * License along with this library. If not, see <http://www.gnu.org/licenses/>.
19 * Stuphead: (C) 2000,2001 Grigroy Bakunov, Sergey Pinaev
20 * Adapted for Sylpheed (Claws) (c) 2001-2002 by Hiroyuki Yamamoto &
21 * The Claws Mail Team.
22 * Adapted for pspell (c) 2001-2002 Melvin Hadasht
23 * Adapted for GNU/aspell (c) 2002 Melvin Hadasht
35 #include <sys/types.h>
47 #include <glib/gi18n.h>
51 #include <gdk/gdkkeysyms.h>
56 #include "alertpanel.h"
57 #include "gtkaspell.h"
58 #include "gtk/gtkutils.h"
59 #include "gtk/combobox.h"
61 #define ASPELL_FASTMODE 1
62 #define ASPELL_NORMALMODE 2
63 #define ASPELL_BADSPELLERMODE 3
65 #define GTKASPELLWORDSIZE 1024
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 typedef struct _Dictionary {
90 typedef struct _GtkAspeller {
91 Dictionary *dictionary;
92 EnchantBroker *broker;
96 typedef void (*ContCheckFunc) (gpointer *gtkaspell);
100 GtkAspeller *gtkaspeller;
101 GtkAspeller *alternate_speller;
102 gchar theword[GTKASPELLWORDSIZE];
108 gboolean check_while_typing;
109 gboolean recheck_when_changing_dict;
110 gboolean use_alternate;
111 gboolean use_both_dicts;
113 ContCheckFunc continue_check;
115 GtkWidget *replace_entry;
116 GtkWidget *parent_window;
119 GList *suggestions_list;
121 GtkTextView *gtktext;
123 GtkAccelGroup *accel_group;
124 void (*menu_changed_cb)(void *data);
125 void *menu_changed_data;
128 /******************************************************************************/
130 static GtkAspellCheckers *gtkaspellcheckers;
132 /* Error message storage */
133 static void gtkaspell_checkers_error_message (gchar *message);
136 static void entry_insert_cb (GtkTextBuffer *textbuf,
140 GtkAspell *gtkaspell);
141 static void entry_delete_cb (GtkTextBuffer *textbuf,
142 GtkTextIter *startiter,
143 GtkTextIter *enditer,
144 GtkAspell *gtkaspell);
145 /*static gint button_press_intercept_cb (GtkTextView *gtktext,
147 GtkAspell *gtkaspell);
149 static void button_press_intercept_cb(GtkTextView *gtktext,
150 GtkMenu *menu, GtkAspell *gtkaspell);
152 /* Checker creation */
153 static GtkAspeller* gtkaspeller_new (Dictionary *dict);
154 static GtkAspeller* gtkaspeller_real_new (Dictionary *dict);
155 static GtkAspeller* gtkaspeller_delete (GtkAspeller *gtkaspeller);
156 static GtkAspeller* gtkaspeller_real_delete (GtkAspeller *gtkaspeller);
158 /* Checker configuration */
159 static EnchantDict *set_dictionary (EnchantBroker *broker,
161 static void set_use_both_cb (GtkMenuItem *w,
162 GtkAspell *gtkaspell);
164 /* Checker actions */
165 static gboolean check_at (GtkAspell *gtkaspell,
167 static gboolean check_next_prev (GtkAspell *gtkaspell,
169 static GList* misspelled_suggest (GtkAspell *gtkaspell,
171 static void add_word_to_session_cb (GtkWidget *w,
173 static void add_word_to_personal_cb (GtkWidget *w,
175 static void replace_with_create_dialog_cb (GtkWidget *w,
177 static void replace_with_supplied_word_cb (GtkWidget *w,
178 GtkAspell *gtkaspell);
179 static void replace_word_cb (GtkWidget *w,
181 static void replace_real_word (GtkAspell *gtkaspell,
182 const gchar *newword);
183 static void check_with_alternate_cb (GtkWidget *w,
185 static void use_alternate_dict (GtkAspell *gtkaspell);
186 static void toggle_check_while_typing_cb (GtkWidget *w,
190 static GSList* make_sug_menu (GtkAspell *gtkaspell);
191 static GSList * populate_submenu (GtkAspell *gtkaspell);
192 GSList* gtkaspell_make_config_menu (GtkAspell *gtkaspell);
193 static void set_menu_pos (GtkMenu *menu,
198 /* Other menu callbacks */
199 static gboolean aspell_key_pressed (GtkWidget *widget,
201 GtkAspell *gtkaspell);
202 static void change_dict_cb (GtkWidget *w,
203 GtkAspell *gtkaspell);
204 static void switch_to_alternate_cb (GtkWidget *w,
207 /* Misc. helper functions */
208 static void set_point_continue (GtkAspell *gtkaspell);
209 static void continue_check (gpointer *gtkaspell);
210 static gboolean iswordsep (gunichar c);
211 static gunichar get_text_index_whar (GtkAspell *gtkaspell,
213 static gboolean get_word_from_pos (GtkAspell *gtkaspell,
219 static void allocate_color (GtkAspell *gtkaspell,
221 static void change_color (GtkAspell *gtkaspell,
226 static gint compare_dict (Dictionary *a,
228 static void dictionary_delete (Dictionary *dict);
229 static Dictionary * dictionary_dup (const Dictionary *dict);
230 static void free_suggestions_list (GtkAspell *gtkaspell);
231 static void reset_theword_data (GtkAspell *gtkaspell);
232 static void free_checkers (gpointer elt,
234 static gint find_gtkaspeller (gconstpointer aa,
237 static void destroy_menu(GtkWidget *widget, gpointer user_data);
239 /******************************************************************************/
240 static gint get_textview_buffer_charcount(GtkTextView *view);
242 static void gtkaspell_free_dictionary_list (GSList *list);
243 static GSList* gtkaspell_get_dictionary_list (gint refresh);
245 static void gtkaspell_uncheck_all (GtkAspell *gtkaspell);
247 static gint get_textview_buffer_charcount(GtkTextView *view)
249 GtkTextBuffer *buffer;
251 g_return_val_if_fail(view, 0);
253 buffer = gtk_text_view_get_buffer(view);
254 g_return_val_if_fail(buffer, 0);
256 return gtk_text_buffer_get_char_count(buffer);
258 static gint get_textview_buffer_offset(GtkTextView *view)
260 GtkTextBuffer * buffer;
264 g_return_val_if_fail(view, 0);
266 buffer = gtk_text_view_get_buffer(view);
267 g_return_val_if_fail(buffer, 0);
269 mark = gtk_text_buffer_get_insert(buffer);
270 g_return_val_if_fail(mark, 0);
272 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
274 return gtk_text_iter_get_offset(&iter);
276 static void set_textview_buffer_offset(GtkTextView *view, gint offset)
278 GtkTextBuffer *buffer;
281 g_return_if_fail(view);
283 buffer = gtk_text_view_get_buffer(view);
284 g_return_if_fail(buffer);
286 gtk_text_buffer_get_iter_at_offset(buffer, &iter, offset);
287 gtk_text_buffer_place_cursor(buffer, &iter);
289 /******************************************************************************/
291 void gtkaspell_checkers_init(void)
293 gtkaspellcheckers = g_new(GtkAspellCheckers, 1);
294 gtkaspellcheckers->checkers = NULL;
295 gtkaspellcheckers->dictionary_list = NULL;
296 gtkaspellcheckers->error_message = NULL;
299 void gtkaspell_checkers_quit(void)
304 if (gtkaspellcheckers == NULL)
307 if ((checkers = gtkaspellcheckers->checkers)) {
308 debug_print("Aspell: number of running checkers to delete %d\n",
309 g_slist_length(checkers));
311 g_slist_foreach(checkers, free_checkers, NULL);
312 g_slist_free(checkers);
313 gtkaspellcheckers->checkers = NULL;
316 if ((dict_list = gtkaspellcheckers->dictionary_list)) {
317 debug_print("Aspell: number of dictionaries to delete %d\n",
318 g_slist_length(dict_list));
320 gtkaspell_free_dictionary_list(dict_list);
321 gtkaspellcheckers->dictionary_list = NULL;
324 g_free(gtkaspellcheckers->error_message);
325 gtkaspellcheckers->error_message = NULL;
329 static void gtkaspell_checkers_error_message (gchar *message)
332 if (gtkaspellcheckers->error_message) {
333 tmp = g_strdup_printf("%s\n%s",
334 gtkaspellcheckers->error_message,
337 g_free(gtkaspellcheckers->error_message);
338 gtkaspellcheckers->error_message = tmp;
340 gtkaspellcheckers->error_message = message;
345 const char *gtkaspell_checkers_strerror(void)
347 g_return_val_if_fail(gtkaspellcheckers, "");
348 return gtkaspellcheckers->error_message;
351 void gtkaspell_checkers_reset_error(void)
353 g_return_if_fail(gtkaspellcheckers);
355 g_free(gtkaspellcheckers->error_message);
357 gtkaspellcheckers->error_message = NULL;
360 GtkAspell *gtkaspell_new(const gchar *dictionary,
361 const gchar *alt_dictionary,
362 const gchar *encoding, /* unused */
363 gint misspelled_color,
364 gboolean check_while_typing,
365 gboolean recheck_when_changing_dict,
366 gboolean use_alternate,
367 gboolean use_both_dicts,
368 GtkTextView *gtktext,
369 GtkWindow *parent_win,
370 void (*spell_menu_cb)(void *data),
374 GtkAspell *gtkaspell;
375 GtkAspeller *gtkaspeller;
376 GtkTextBuffer *buffer;
378 g_return_val_if_fail(gtktext, NULL);
379 if (!dictionary || !*dictionary) {
380 gtkaspell_checkers_error_message(
381 g_strdup(_("No dictionary selected.")));
385 buffer = gtk_text_view_get_buffer(gtktext);
387 dict = g_new0(Dictionary, 1);
388 if (strrchr(dictionary, '/')) {
389 dict->fullname = g_strdup(strrchr(dictionary, '/')+1);
390 dict->dictname = g_strdup(strrchr(dictionary, '/')+1);
392 dict->fullname = g_strdup(dictionary);
393 dict->dictname = g_strdup(dictionary);
396 if (strchr(dictionary, '-')) {
397 *(strchr(dictionary, '-')) = '\0';
399 gtkaspeller = gtkaspeller_new(dict);
400 dictionary_delete(dict);
403 gtkaspell_checkers_error_message(
404 g_strdup_printf(_("Couldn't initialize %s speller."), dictionary));
408 gtkaspell = g_new0(GtkAspell, 1);
410 gtkaspell->gtkaspeller = gtkaspeller;
412 if (use_alternate && alt_dictionary && *alt_dictionary) {
413 Dictionary *alt_dict;
414 GtkAspeller *alt_gtkaspeller;
416 alt_dict = g_new0(Dictionary, 1);
417 if (strrchr(alt_dictionary, '/')) {
418 alt_dict->fullname = g_strdup(strrchr(alt_dictionary, '/')+1);
419 alt_dict->dictname = g_strdup(strrchr(alt_dictionary, '/')+1);
421 alt_dict->fullname = g_strdup(alt_dictionary);
422 alt_dict->dictname = g_strdup(alt_dictionary);
424 if (strchr(alt_dictionary, '-')) {
425 *(strchr(alt_dictionary, '-')) = '\0';
428 alt_gtkaspeller = gtkaspeller_new(alt_dict);
429 dictionary_delete(alt_dict);
431 if (!alt_gtkaspeller) {
432 gtkaspell_checkers_error_message(
433 g_strdup_printf(_("Couldn't initialize %s speller."), dictionary));
437 gtkaspell->alternate_speller = alt_gtkaspeller;
439 gtkaspell->alternate_speller = NULL;
442 gtkaspell->theword[0] = 0x00;
443 gtkaspell->start_pos = 0;
444 gtkaspell->end_pos = 0;
445 gtkaspell->orig_pos = -1;
446 gtkaspell->end_check_pos = -1;
447 gtkaspell->misspelled = -1;
448 gtkaspell->check_while_typing = check_while_typing;
449 gtkaspell->recheck_when_changing_dict = recheck_when_changing_dict;
450 gtkaspell->continue_check = NULL;
451 gtkaspell->replace_entry = NULL;
452 gtkaspell->gtktext = gtktext;
453 gtkaspell->max_sug = -1;
454 gtkaspell->suggestions_list = NULL;
455 gtkaspell->use_alternate = use_alternate;
456 gtkaspell->use_both_dicts = use_both_dicts;
457 gtkaspell->parent_window = GTK_WIDGET(parent_win);
458 gtkaspell->menu_changed_cb = spell_menu_cb;
459 gtkaspell->menu_changed_data = data;
461 allocate_color(gtkaspell, misspelled_color);
463 g_signal_connect_after(G_OBJECT(buffer), "insert-text",
464 G_CALLBACK(entry_insert_cb), gtkaspell);
465 g_signal_connect_after(G_OBJECT(buffer), "delete-range",
466 G_CALLBACK(entry_delete_cb), gtkaspell);
467 /*g_signal_connect(G_OBJECT(gtktext), "button-press-event",
468 G_CALLBACK(button_press_intercept_cb),
470 g_signal_connect(G_OBJECT(gtktext), "populate-popup",
471 G_CALLBACK(button_press_intercept_cb), gtkaspell);
473 debug_print("Aspell: created gtkaspell %p\n", gtkaspell);
478 void gtkaspell_delete(GtkAspell *gtkaspell)
480 GtkTextView *gtktext = gtkaspell->gtktext;
482 g_signal_handlers_disconnect_by_func(G_OBJECT(gtktext),
483 G_CALLBACK(entry_insert_cb),
485 g_signal_handlers_disconnect_by_func(G_OBJECT(gtktext),
486 G_CALLBACK(entry_delete_cb),
488 g_signal_handlers_disconnect_by_func(G_OBJECT(gtktext),
489 G_CALLBACK(button_press_intercept_cb),
492 gtkaspell_uncheck_all(gtkaspell);
494 gtkaspeller_delete(gtkaspell->gtkaspeller);
496 if (gtkaspell->alternate_speller)
497 gtkaspeller_delete(gtkaspell->alternate_speller);
499 if (gtkaspell->suggestions_list)
500 free_suggestions_list(gtkaspell);
502 debug_print("Aspell: deleting gtkaspell %p\n", gtkaspell);
509 static void entry_insert_cb(GtkTextBuffer *textbuf,
513 GtkAspell *gtkaspell)
517 g_return_if_fail(gtkaspell->gtkaspeller->speller);
519 if (!gtkaspell->check_while_typing)
522 pos = gtk_text_iter_get_offset(iter);
524 if (iswordsep(g_utf8_get_char(newtext))) {
525 /* did we just end a word? */
527 check_at(gtkaspell, pos - 2);
529 /* did we just split a word? */
530 if (pos < gtk_text_buffer_get_char_count(textbuf))
531 check_at(gtkaspell, pos + 1);
533 /* check as they type, *except* if they're typing at the end (the most
536 if (pos < gtk_text_buffer_get_char_count(textbuf) &&
537 !iswordsep(get_text_index_whar(gtkaspell, pos))) {
538 check_at(gtkaspell, pos - 1);
543 static void entry_delete_cb(GtkTextBuffer *textbuf,
544 GtkTextIter *startiter,
545 GtkTextIter *enditer,
546 GtkAspell *gtkaspell)
551 g_return_if_fail(gtkaspell->gtkaspeller->speller);
553 if (!gtkaspell->check_while_typing)
556 start = gtk_text_iter_get_offset(startiter);
557 end = gtk_text_iter_get_offset(enditer);
558 origpos = get_textview_buffer_offset(gtkaspell->gtktext);
560 check_at(gtkaspell, start - 1);
561 check_at(gtkaspell, start);
564 set_textview_buffer_offset(gtkaspell->gtktext, origpos);
565 /* this is to *UNDO* the selection, in case they were holding shift
566 * while hitting backspace. */
567 /* needed with textview ??? */
568 /* gtk_editable_select_region(GTK_EDITABLE(gtktext), origpos, origpos); */
571 static void button_press_intercept_cb(GtkTextView *gtktext,
572 GtkMenu *menu, GtkAspell *gtkaspell)
574 GtkMenuItem *menuitem;
575 GSList *spell_menu = NULL;
577 gboolean suggest = FALSE;
579 menuitem = GTK_MENU_ITEM(gtk_separator_menu_item_new());
580 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), GTK_WIDGET(menuitem));
581 gtk_widget_show(GTK_WIDGET(menuitem));
583 gtktext = gtkaspell->gtktext;
585 gtkaspell->orig_pos = get_textview_buffer_offset(gtktext);
587 if (check_at(gtkaspell, gtkaspell->orig_pos)) {
589 if (misspelled_suggest(gtkaspell, gtkaspell->theword)) {
590 spell_menu = make_sug_menu(gtkaspell);
595 spell_menu = gtkaspell_make_config_menu(gtkaspell);
597 spell_menu = g_slist_reverse(spell_menu);
598 for (items = spell_menu;
599 items; items = items->next) {
600 menuitem = GTK_MENU_ITEM(items->data);
601 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), GTK_WIDGET(menuitem));
602 gtk_widget_show(GTK_WIDGET(menuitem));
604 g_slist_free(spell_menu);
606 g_signal_connect(G_OBJECT(menu), "deactivate",
607 G_CALLBACK(destroy_menu),
610 g_signal_connect(G_OBJECT(menu),
612 G_CALLBACK(aspell_key_pressed), gtkaspell);
616 /* Checker creation */
617 static GtkAspeller *gtkaspeller_new(Dictionary *dictionary)
620 GtkAspeller *gtkaspeller = NULL;
624 g_return_val_if_fail(gtkaspellcheckers, NULL);
626 g_return_val_if_fail(dictionary, NULL);
628 if (dictionary->dictname == NULL)
629 gtkaspell_checkers_error_message(
630 g_strdup(_("No dictionary selected.")));
632 g_return_val_if_fail(dictionary->fullname, NULL);
634 dict = dictionary_dup(dictionary);
636 tmp = g_new0(GtkAspeller, 1);
637 tmp->dictionary = dict;
639 exist = g_slist_find_custom(gtkaspellcheckers->checkers, tmp,
644 if ((gtkaspeller = gtkaspeller_real_new(dict)) != NULL) {
645 gtkaspellcheckers->checkers = g_slist_append(
646 gtkaspellcheckers->checkers,
649 debug_print("Aspell: Created a new gtkaspeller %p\n",
652 dictionary_delete(dict);
654 debug_print("Aspell: Could not create spell checker.\n");
657 debug_print("Aspell: number of existing checkers %d\n",
658 g_slist_length(gtkaspellcheckers->checkers));
663 static GtkAspeller *gtkaspeller_real_new(Dictionary *dict)
665 GtkAspeller *gtkaspeller;
666 EnchantBroker *broker;
667 EnchantDict *speller;
669 g_return_val_if_fail(gtkaspellcheckers, NULL);
670 g_return_val_if_fail(dict, NULL);
672 gtkaspeller = g_new(GtkAspeller, 1);
674 gtkaspeller->dictionary = dict;
676 broker = enchant_broker_init();
679 gtkaspell_checkers_error_message(
680 g_strdup(_("Couldn't initialize Enchant broker.")));
683 if ((speller = set_dictionary(broker, dict)) == NULL) {
684 gtkaspell_checkers_error_message(
685 g_strdup_printf(_("Couldn't initialize %s dictionary:"), dict->fullname));
686 gtkaspell_checkers_error_message(
687 g_strdup(enchant_broker_get_error(broker)));
690 gtkaspeller->speller = speller;
691 gtkaspeller->broker = broker;
696 static GtkAspeller *gtkaspeller_delete(GtkAspeller *gtkaspeller)
698 g_return_val_if_fail(gtkaspellcheckers, NULL);
700 gtkaspellcheckers->checkers =
701 g_slist_remove(gtkaspellcheckers->checkers,
704 debug_print("Aspell: Deleting gtkaspeller %p.\n",
707 gtkaspeller_real_delete(gtkaspeller);
709 debug_print("Aspell: number of existing checkers %d\n",
710 g_slist_length(gtkaspellcheckers->checkers));
715 static GtkAspeller *gtkaspeller_real_delete(GtkAspeller *gtkaspeller)
717 g_return_val_if_fail(gtkaspeller, NULL);
718 g_return_val_if_fail(gtkaspeller->speller, NULL);
720 enchant_broker_free_dict(gtkaspeller->broker, gtkaspeller->speller);
721 enchant_broker_free(gtkaspeller->broker);
723 dictionary_delete(gtkaspeller->dictionary);
725 debug_print("Aspell: gtkaspeller %p deleted.\n",
733 /*****************************************************************************/
734 /* Checker configuration */
736 static EnchantDict *set_dictionary(EnchantBroker *broker, Dictionary *dict)
738 g_return_val_if_fail(broker, FALSE);
739 g_return_val_if_fail(dict, FALSE);
741 return enchant_broker_request_dict(broker, dict->dictname );
744 static void set_use_both_cb(GtkMenuItem *w, GtkAspell *gtkaspell)
746 gtkaspell->use_both_dicts = gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(w));
748 if (gtkaspell->recheck_when_changing_dict) {
749 gtkaspell_highlight_all(gtkaspell);
752 if (gtkaspell->menu_changed_cb)
753 gtkaspell->menu_changed_cb(gtkaspell->menu_changed_data);
756 /* misspelled_suggest() - Create a suggestion list for word */
757 static GList *misspelled_suggest(GtkAspell *gtkaspell, gchar *word)
762 g_return_val_if_fail(word, NULL);
767 free_suggestions_list(gtkaspell);
769 suggestions = enchant_dict_suggest(gtkaspell->gtkaspeller->speller, word, strlen(word), &num_sug);
770 if (suggestions == NULL || num_sug == 0) {
771 gtkaspell->max_sug = - 1;
772 gtkaspell->suggestions_list = NULL;
775 list = g_list_append(list, g_strdup(word));
776 for (i = 0; i < num_sug; i++)
777 list = g_list_append(list, g_strdup((gchar *)suggestions[i]));
779 gtkaspell->max_sug = num_sug - 1;
780 gtkaspell->suggestions_list = list;
781 enchant_dict_free_string_list(gtkaspell->gtkaspeller->speller, suggestions);
785 /* misspelled_test() - Just test if word is correctly spelled */
786 static int misspelled_test(GtkAspell *gtkaspell, char *word)
789 g_return_val_if_fail(word, 0);
794 result = enchant_dict_check(gtkaspell->gtkaspeller->speller, word, strlen(word));
796 if (result && gtkaspell->use_both_dicts && gtkaspell->alternate_speller) {
797 use_alternate_dict(gtkaspell);
798 result = enchant_dict_check(gtkaspell->gtkaspeller->speller, word, strlen(word));
799 use_alternate_dict(gtkaspell);
805 static gboolean iswordsep(gunichar c)
807 return (g_unichar_isspace(c) || g_unichar_ispunct(c)) && c != (gunichar)'\'';
810 static gunichar get_text_index_whar(GtkAspell *gtkaspell, int pos)
812 GtkTextView *view = gtkaspell->gtktext;
813 GtkTextBuffer *buffer = gtk_text_view_get_buffer(view);
814 GtkTextIter start, end;
818 gtk_text_buffer_get_iter_at_offset(buffer, &start, pos);
819 gtk_text_buffer_get_iter_at_offset(buffer, &end, pos+1);
821 utf8chars = gtk_text_iter_get_text(&start, &end);
822 a = g_utf8_get_char(utf8chars);
828 /* get_word_from_pos () - return the word pointed to. */
829 /* Handles correctly the quotes. */
830 static gboolean get_word_from_pos(GtkAspell *gtkaspell, gint pos,
831 char* buf, gint buflen,
832 gint *pstart, gint *pend)
835 /* TODO : when correcting a word into quotes, change the color of */
836 /* the quotes too, as may be they were highlighted before. To do */
837 /* this, we can use two others pointers that points to the whole */
838 /* word including quotes. */
844 GtkTextView *gtktext;
846 gtktext = gtkaspell->gtktext;
847 if (iswordsep(get_text_index_whar(gtkaspell, pos)))
850 /* The apostrophe character is somtimes used for quotes
851 * So include it in the word only if it is not surrounded
852 * by other characters.
855 for (start = pos; start >= 0; --start) {
856 c = get_text_index_whar(gtkaspell, start);
857 if (c == (gunichar)'\'') {
859 if (g_unichar_isspace(get_text_index_whar(gtkaspell,
861 || g_unichar_ispunct(get_text_index_whar(gtkaspell,
863 || g_unichar_isdigit(get_text_index_whar(gtkaspell,
865 /* start_quote = TRUE; */
870 /* start_quote = TRUE; */
874 else if (g_unichar_isspace(c) || g_unichar_ispunct(c) || g_unichar_isdigit(c))
880 for (end = pos; end < get_textview_buffer_charcount(gtktext); end++) {
881 c = get_text_index_whar(gtkaspell, end);
882 if (c == (gunichar)'\'') {
883 if (end < get_textview_buffer_charcount(gtktext)) {
884 if (g_unichar_isspace(get_text_index_whar(gtkaspell,
886 || g_unichar_ispunct(get_text_index_whar(gtkaspell,
888 || g_unichar_isdigit(get_text_index_whar(gtkaspell,
890 /* end_quote = TRUE; */
895 /* end_quote = TRUE; */
899 else if (g_unichar_isspace(c) || g_unichar_ispunct(c) || g_unichar_isdigit(c))
909 if (end - start < buflen) {
910 GtkTextIter iterstart, iterend;
912 GtkTextBuffer *buffer = gtk_text_view_get_buffer(gtktext);
913 gtk_text_buffer_get_iter_at_offset(buffer, &iterstart, start);
914 gtk_text_buffer_get_iter_at_offset(buffer, &iterend, end);
915 tmp = gtk_text_buffer_get_text(buffer, &iterstart, &iterend, FALSE);
916 strncpy(buf, tmp, buflen-1);
926 static gboolean check_at(GtkAspell *gtkaspell, gint from_pos)
929 char buf[GTKASPELLWORDSIZE];
930 GtkTextView *gtktext;
932 g_return_val_if_fail(from_pos >= 0, FALSE);
934 gtktext = gtkaspell->gtktext;
936 if (!get_word_from_pos(gtkaspell, from_pos, buf, sizeof(buf),
940 if (misspelled_test(gtkaspell, buf)
941 && strcasecmp(buf, "sylpheed") && strcasecmp(buf, "claws-mail")) {
942 strncpy(gtkaspell->theword, (gchar *)buf, GTKASPELLWORDSIZE - 1);
943 gtkaspell->theword[GTKASPELLWORDSIZE - 1] = 0;
944 gtkaspell->start_pos = start;
945 gtkaspell->end_pos = end;
946 free_suggestions_list(gtkaspell);
948 change_color(gtkaspell, start, end, (gchar *)buf, &(gtkaspell->highlight));
951 change_color(gtkaspell, start, end, (gchar *)buf, NULL);
956 static gboolean check_next_prev(GtkAspell *gtkaspell, gboolean forward)
965 maxpos = gtkaspell->end_check_pos;
973 pos = get_textview_buffer_offset(gtkaspell->gtktext);
974 gtkaspell->orig_pos = pos;
975 while (iswordsep(get_text_index_whar(gtkaspell, pos)) &&
976 pos > minpos && pos <= maxpos)
978 while (!(misspelled = check_at(gtkaspell, pos)) &&
979 pos > minpos && pos <= maxpos) {
981 while (!iswordsep(get_text_index_whar(gtkaspell, pos)) &&
982 pos > minpos && pos <= maxpos)
985 while (iswordsep(get_text_index_whar(gtkaspell, pos)) &&
986 pos > minpos && pos <= maxpos)
992 misspelled_suggest(gtkaspell, gtkaspell->theword);
995 gtkaspell->orig_pos = gtkaspell->end_pos;
997 set_textview_buffer_offset(gtkaspell->gtktext,
999 /* scroll line to window center */
1000 gtk_text_view_scroll_to_mark(gtkaspell->gtktext,
1001 gtk_text_buffer_get_insert(
1002 gtk_text_view_get_buffer(gtkaspell->gtktext)),
1003 0.0, TRUE, 0.0, 0.5);
1004 /* let textview recalculate coordinates (set_menu_pos) */
1005 while (gtk_events_pending ())
1006 gtk_main_iteration ();
1008 list = make_sug_menu(gtkaspell);
1009 menu = gtk_menu_new();
1010 for (cur = list; cur; cur = cur->next)
1011 gtk_menu_shell_append(GTK_MENU_SHELL(menu), GTK_WIDGET(cur->data));
1013 gtk_menu_popup(GTK_MENU(menu), NULL, NULL,
1014 set_menu_pos, gtkaspell, 0, GDK_CURRENT_TIME);
1015 g_signal_connect(G_OBJECT(menu), "deactivate",
1016 G_CALLBACK(destroy_menu),
1018 g_signal_connect(G_OBJECT(menu),
1020 G_CALLBACK(aspell_key_pressed), gtkaspell);
1024 reset_theword_data(gtkaspell);
1026 alertpanel_notice(_("No misspelled word found."));
1027 set_textview_buffer_offset(gtkaspell->gtktext,
1028 gtkaspell->orig_pos);
1034 void gtkaspell_check_backwards(GtkAspell *gtkaspell)
1036 gtkaspell->continue_check = NULL;
1037 gtkaspell->end_check_pos =
1038 get_textview_buffer_charcount(gtkaspell->gtktext);
1039 check_next_prev(gtkaspell, FALSE);
1042 void gtkaspell_check_forwards_go(GtkAspell *gtkaspell)
1045 gtkaspell->continue_check = NULL;
1046 gtkaspell->end_check_pos =
1047 get_textview_buffer_charcount(gtkaspell->gtktext);
1048 check_next_prev(gtkaspell, TRUE);
1051 void gtkaspell_check_all(GtkAspell *gtkaspell)
1053 GtkTextView *gtktext;
1055 GtkTextBuffer *buffer;
1056 GtkTextIter startiter, enditer;
1058 g_return_if_fail(gtkaspell);
1059 g_return_if_fail(gtkaspell->gtktext);
1061 gtktext = gtkaspell->gtktext;
1062 buffer = gtk_text_view_get_buffer(gtktext);
1063 gtk_text_buffer_get_selection_bounds(buffer, &startiter, &enditer);
1064 start = gtk_text_iter_get_offset(&startiter);
1065 end = gtk_text_iter_get_offset(&enditer);
1069 end = gtk_text_buffer_get_char_count(buffer);
1070 } else if (start > end) {
1078 set_textview_buffer_offset(gtktext, start);
1080 gtkaspell->continue_check = continue_check;
1081 gtkaspell->end_check_pos = end;
1083 gtkaspell->misspelled = check_next_prev(gtkaspell, TRUE);
1086 static void continue_check(gpointer *data)
1088 GtkAspell *gtkaspell = (GtkAspell *) data;
1089 gint pos = get_textview_buffer_offset(gtkaspell->gtktext);
1090 if (pos < gtkaspell->end_check_pos && gtkaspell->misspelled)
1091 gtkaspell->misspelled = check_next_prev(gtkaspell, TRUE);
1093 gtkaspell->continue_check = NULL;
1096 void gtkaspell_highlight_all(GtkAspell *gtkaspell)
1101 GtkTextView *gtktext;
1103 g_return_if_fail(gtkaspell->gtkaspeller->speller);
1105 gtktext = gtkaspell->gtktext;
1107 len = get_textview_buffer_charcount(gtktext);
1109 origpos = get_textview_buffer_offset(gtktext);
1113 iswordsep(get_text_index_whar(gtkaspell, pos)))
1116 !iswordsep(get_text_index_whar(gtkaspell, pos)))
1119 check_at(gtkaspell, pos - 1);
1121 set_textview_buffer_offset(gtktext, origpos);
1124 static void replace_with_supplied_word_cb(GtkWidget *w, GtkAspell *gtkaspell)
1127 GdkEvent *e= (GdkEvent *) gtk_get_current_event();
1129 newword = gtk_editable_get_chars(GTK_EDITABLE(gtkaspell->replace_entry),
1132 if (strcmp(newword, gtkaspell->theword)) {
1133 replace_real_word(gtkaspell, newword);
1135 if ((e->type == GDK_KEY_PRESS &&
1136 ((GdkEventKey *) e)->state & GDK_CONTROL_MASK)) {
1137 enchant_dict_store_replacement(gtkaspell->gtkaspeller->speller,
1138 gtkaspell->theword, strlen(gtkaspell->theword),
1139 newword, strlen(newword));
1141 gtkaspell->replace_entry = NULL;
1146 if (w && GTK_IS_DIALOG(w)) {
1147 gtk_widget_destroy(w);
1150 set_point_continue(gtkaspell);
1154 static void replace_word_cb(GtkWidget *w, gpointer data)
1156 const char *newword;
1157 GtkAspell *gtkaspell = (GtkAspell *) data;
1158 GdkEvent *e= (GdkEvent *) gtk_get_current_event();
1160 newword = gtk_label_get_text(GTK_LABEL(gtk_bin_get_child(GTK_BIN((w)))));
1162 replace_real_word(gtkaspell, newword);
1164 if ((e->type == GDK_KEY_PRESS &&
1165 ((GdkEventKey *) e)->state & GDK_CONTROL_MASK) ||
1166 (e->type == GDK_BUTTON_RELEASE &&
1167 ((GdkEventButton *) e)->state & GDK_CONTROL_MASK)) {
1168 enchant_dict_store_replacement(gtkaspell->gtkaspeller->speller,
1169 gtkaspell->theword, strlen(gtkaspell->theword),
1170 newword, strlen(newword));
1173 gtk_menu_shell_deactivate(GTK_MENU_SHELL(w->parent));
1175 set_point_continue(gtkaspell);
1178 static void replace_real_word(GtkAspell *gtkaspell, const gchar *newword)
1180 int oldlen, newlen, wordlen;
1183 GtkTextView *gtktext;
1184 GtkTextBuffer *textbuf;
1185 GtkTextIter startiter, enditer;
1187 if (!newword) return;
1189 gtktext = gtkaspell->gtktext;
1190 textbuf = gtk_text_view_get_buffer(gtktext);
1192 origpos = gtkaspell->orig_pos;
1194 oldlen = gtkaspell->end_pos - gtkaspell->start_pos;
1195 wordlen = strlen(gtkaspell->theword);
1197 newlen = strlen(newword); /* FIXME: multybyte characters? */
1199 g_signal_handlers_block_by_func(G_OBJECT(gtktext),
1200 G_CALLBACK(entry_insert_cb),
1202 g_signal_handlers_block_by_func(G_OBJECT(gtktext),
1203 G_CALLBACK(entry_delete_cb),
1206 gtk_text_buffer_get_iter_at_offset(textbuf, &startiter,
1207 gtkaspell->start_pos);
1208 gtk_text_buffer_get_iter_at_offset(textbuf, &enditer,
1209 gtkaspell->end_pos);
1210 g_signal_emit_by_name(G_OBJECT(textbuf), "delete-range",
1211 &startiter, &enditer, gtkaspell);
1212 g_signal_emit_by_name(G_OBJECT(textbuf), "insert-text",
1213 &startiter, newword, newlen, gtkaspell);
1215 g_signal_handlers_unblock_by_func(G_OBJECT(gtktext),
1216 G_CALLBACK(entry_insert_cb),
1218 g_signal_handlers_unblock_by_func(G_OBJECT(gtktext),
1219 G_CALLBACK(entry_delete_cb),
1222 /* Put the point and the position where we clicked with the mouse
1223 * It seems to be a hack, as I must thaw,freeze,thaw the widget
1224 * to let it update correctly the word insertion and then the
1225 * point & position position. If not, SEGV after the first replacement
1226 * If the new word ends before point, put the point at its end.
1229 if (origpos - gtkaspell->start_pos < oldlen &&
1230 origpos - gtkaspell->start_pos >= 0) {
1231 /* Original point was in the word.
1232 * Let it there unless point is going to be outside of the word
1234 if (origpos - gtkaspell->start_pos >= newlen) {
1235 pos = gtkaspell->start_pos + newlen;
1238 else if (origpos >= gtkaspell->end_pos) {
1239 /* move the position according to the change of length */
1240 pos = origpos + newlen - oldlen;
1243 gtkaspell->end_pos = gtkaspell->start_pos + strlen(newword); /* FIXME: multibyte characters? */
1245 if (get_textview_buffer_charcount(gtktext) < pos)
1246 pos = get_textview_buffer_charcount(gtktext);
1247 gtkaspell->orig_pos = pos;
1249 set_textview_buffer_offset(gtktext, gtkaspell->orig_pos);
1252 /* Accept this word for this session */
1253 static void add_word_to_session_cb(GtkWidget *w, gpointer data)
1256 GtkTextView *gtktext;
1257 GtkAspell *gtkaspell = (GtkAspell *) data;
1258 gtktext = gtkaspell->gtktext;
1260 pos = get_textview_buffer_offset(gtktext);
1262 enchant_dict_add_to_session(gtkaspell->gtkaspeller->speller, gtkaspell->theword, strlen(gtkaspell->theword));
1264 check_at(gtkaspell, gtkaspell->start_pos);
1266 gtk_menu_shell_deactivate(GTK_MENU_SHELL(GTK_WIDGET(w)->parent));
1268 set_point_continue(gtkaspell);
1271 /* add_word_to_personal_cb() - add word to personal dict. */
1272 static void add_word_to_personal_cb(GtkWidget *w, gpointer data)
1274 GtkAspell *gtkaspell = (GtkAspell *) data;
1276 enchant_dict_add_to_pwl(gtkaspell->gtkaspeller->speller, gtkaspell->theword, strlen(gtkaspell->theword));
1278 check_at(gtkaspell, gtkaspell->start_pos);
1280 gtk_menu_shell_deactivate(GTK_MENU_SHELL(GTK_WIDGET(w)->parent));
1281 set_point_continue(gtkaspell);
1284 static void check_with_alternate_cb(GtkWidget *w, gpointer data)
1286 GtkAspell *gtkaspell = (GtkAspell *) data;
1289 gtk_menu_shell_deactivate(GTK_MENU_SHELL(GTK_WIDGET(w)->parent));
1291 use_alternate_dict(gtkaspell);
1292 misspelled = check_at(gtkaspell, gtkaspell->start_pos);
1294 if (!gtkaspell->continue_check) {
1296 gtkaspell->misspelled = misspelled;
1298 if (gtkaspell->misspelled) {
1301 misspelled_suggest(gtkaspell, gtkaspell->theword);
1303 set_textview_buffer_offset(gtkaspell->gtktext,
1304 gtkaspell->end_pos);
1306 list = make_sug_menu(gtkaspell);
1307 menu = gtk_menu_new();
1308 for (cur = list; cur; cur = cur->next)
1309 gtk_menu_shell_append(GTK_MENU_SHELL(menu), GTK_WIDGET(cur->data));
1311 gtk_menu_popup(GTK_MENU(menu), NULL, NULL,
1312 set_menu_pos, gtkaspell, 0,
1314 g_signal_connect(G_OBJECT(menu), "deactivate",
1315 G_CALLBACK(destroy_menu),
1317 g_signal_connect(G_OBJECT(menu),
1319 G_CALLBACK(aspell_key_pressed), gtkaspell);
1323 gtkaspell->orig_pos = gtkaspell->start_pos;
1325 set_point_continue(gtkaspell);
1328 static gboolean replace_key_pressed(GtkWidget *widget,
1330 GtkAspell *gtkaspell)
1332 if (event && event->keyval == GDK_Escape) {
1333 gtk_widget_destroy(widget);
1335 } else if (event && event->keyval == GDK_Return) {
1336 replace_with_supplied_word_cb(widget, gtkaspell);
1342 static void replace_with_create_dialog_cb(GtkWidget *w, gpointer data)
1344 static PangoFontDescription *font_desc;
1351 GtkWidget *ok_button;
1352 GtkWidget *cancel_button;
1353 GtkWidget *confirm_area;
1355 gchar *utf8buf, *thelabel;
1357 GtkAspell *gtkaspell = (GtkAspell *) data;
1359 gdk_window_get_origin((GTK_WIDGET(w)->parent)->window, &xx, &yy);
1361 gtk_menu_shell_deactivate(GTK_MENU_SHELL(GTK_WIDGET(w)->parent));
1363 dialog = gtk_dialog_new();
1365 gtk_window_set_resizable(GTK_WINDOW(dialog), FALSE);
1366 gtk_window_set_title(GTK_WINDOW(dialog),_("Replace unknown word"));
1367 gtk_dialog_set_has_separator (GTK_DIALOG (dialog), FALSE);
1368 gtk_window_move(GTK_WINDOW(dialog), xx, yy);
1370 g_signal_connect_swapped(G_OBJECT(dialog), "destroy",
1371 G_CALLBACK(gtk_widget_destroy),
1374 gtk_box_set_spacing (GTK_BOX (GTK_DIALOG (dialog)->vbox), 14);
1375 hbox = gtk_hbox_new (FALSE, 12);
1376 gtk_container_set_border_width (GTK_CONTAINER (hbox), 5);
1377 gtk_widget_show (hbox);
1378 gtk_box_pack_start (GTK_BOX (GTK_DIALOG (dialog)->vbox), hbox,
1381 utf8buf = g_strdup(gtkaspell->theword);
1383 thelabel = g_strdup_printf(_("<span weight=\"bold\" "
1384 "size=\"larger\">Replace \"%s\" with: </span>"),
1386 /* for title label */
1387 w_hbox = gtk_hbox_new(FALSE, 0);
1389 icon = gtk_image_new_from_stock(GTK_STOCK_DIALOG_QUESTION,
1390 GTK_ICON_SIZE_DIALOG);
1391 gtk_misc_set_alignment (GTK_MISC (icon), 0.5, 0.0);
1392 gtk_box_pack_start (GTK_BOX (hbox), icon, FALSE, FALSE, 0);
1394 vbox = gtk_vbox_new (FALSE, 12);
1395 gtk_box_pack_start (GTK_BOX (hbox), vbox, TRUE, TRUE, 0);
1396 gtk_widget_show (vbox);
1398 label = gtk_label_new(thelabel);
1399 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
1400 gtk_label_set_justify(GTK_LABEL(label), GTK_JUSTIFY_LEFT);
1401 gtk_label_set_use_markup (GTK_LABEL (label), TRUE);
1402 gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 0);
1403 gtk_label_set_line_wrap(GTK_LABEL(label), TRUE);
1407 size = pango_font_description_get_size
1408 (label->style->font_desc);
1409 font_desc = pango_font_description_new();
1410 pango_font_description_set_weight
1411 (font_desc, PANGO_WEIGHT_BOLD);
1412 pango_font_description_set_size
1413 (font_desc, size * PANGO_SCALE_LARGE);
1416 gtk_widget_modify_font(label, font_desc);
1419 entry = gtk_entry_new();
1420 gtkaspell->replace_entry = entry;
1421 gtk_entry_set_text(GTK_ENTRY(entry), utf8buf);
1422 gtk_editable_select_region(GTK_EDITABLE(entry), 0, -1);
1423 g_signal_connect(G_OBJECT(dialog),
1425 G_CALLBACK(replace_key_pressed), gtkaspell);
1426 gtk_box_pack_start(GTK_BOX(vbox), entry, FALSE, FALSE, 0);
1429 label = gtk_label_new(_("Holding down Control key while pressing "
1430 "Enter\nwill learn from mistake.\n"));
1431 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
1432 gtk_label_set_justify(GTK_LABEL(label), GTK_JUSTIFY_LEFT);
1433 gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 0);
1434 gtk_widget_show(label);
1436 hbox = gtk_hbox_new(TRUE, 0);
1438 gtkut_stock_button_set_create(&confirm_area,
1439 &cancel_button, GTK_STOCK_CANCEL,
1440 &ok_button, GTK_STOCK_OK,
1443 gtk_box_pack_end(GTK_BOX(GTK_DIALOG(dialog)->action_area),
1444 confirm_area, FALSE, FALSE, 0);
1445 gtk_container_set_border_width(GTK_CONTAINER(confirm_area), 5);
1447 g_signal_connect(G_OBJECT(ok_button), "clicked",
1448 G_CALLBACK(replace_with_supplied_word_cb),
1450 g_signal_connect_swapped(G_OBJECT(ok_button), "clicked",
1451 G_CALLBACK(gtk_widget_destroy),
1454 g_signal_connect_swapped(G_OBJECT(cancel_button), "clicked",
1455 G_CALLBACK(gtk_widget_destroy),
1458 gtk_widget_grab_focus(entry);
1460 gtk_window_set_modal(GTK_WINDOW(dialog), TRUE);
1462 gtk_widget_show_all(dialog);
1465 static void gtkaspell_uncheck_all(GtkAspell * gtkaspell)
1467 GtkTextView *gtktext;
1468 GtkTextBuffer *buffer;
1469 GtkTextIter startiter, enditer;
1471 gtktext = gtkaspell->gtktext;
1473 buffer = gtk_text_view_get_buffer(gtktext);
1474 gtk_text_buffer_get_iter_at_offset(buffer, &startiter, 0);
1475 gtk_text_buffer_get_iter_at_offset(buffer, &enditer,
1476 get_textview_buffer_charcount(gtktext)-1);
1477 gtk_text_buffer_remove_tag_by_name(buffer, "misspelled",
1478 &startiter, &enditer);
1481 static void toggle_check_while_typing_cb(GtkWidget *w, gpointer data)
1483 GtkAspell *gtkaspell = (GtkAspell *) data;
1485 gtkaspell->check_while_typing = gtkaspell->check_while_typing == FALSE;
1487 if (!gtkaspell->check_while_typing)
1488 gtkaspell_uncheck_all(gtkaspell);
1489 if (gtkaspell->menu_changed_cb)
1490 gtkaspell->menu_changed_cb(gtkaspell->menu_changed_data);
1493 static GSList *create_empty_dictionary_list(void)
1495 GSList *list = NULL;
1498 dict = g_new0(Dictionary, 1);
1499 dict->fullname = g_strdup(_("None"));
1500 dict->dictname = NULL;
1502 return g_slist_append(list, dict);
1505 static void list_dict_cb(const char * const lang_tag,
1506 const char * const provider_name,
1507 const char * const provider_desc,
1508 const char * const provider_file,
1511 GSList **list = (GSList **)data;
1512 Dictionary *dict = g_new0(Dictionary, 1);
1513 dict->fullname = g_strdup(lang_tag);
1514 dict->dictname = g_strdup(lang_tag);
1516 if (g_slist_find_custom(*list, dict,
1517 (GCompareFunc) compare_dict) == NULL) {
1518 debug_print("Aspell: found dictionary %s %s\n", dict->fullname,
1520 *list = g_slist_insert_sorted(*list, dict,
1521 (GCompareFunc) compare_dict);
1523 dictionary_delete(dict);
1527 /* gtkaspell_get_dictionary_list() - returns list of dictionary names */
1528 static GSList *gtkaspell_get_dictionary_list(gint refresh)
1531 EnchantBroker *broker;
1533 if (!gtkaspellcheckers)
1534 gtkaspell_checkers_init();
1536 if (gtkaspellcheckers->dictionary_list && !refresh)
1537 return gtkaspellcheckers->dictionary_list;
1539 gtkaspell_free_dictionary_list(
1540 gtkaspellcheckers->dictionary_list);
1543 broker = enchant_broker_init();
1545 enchant_broker_list_dicts(broker, list_dict_cb, &list);
1547 enchant_broker_free(broker);
1551 debug_print("Aspell: error when searching for dictionaries: "
1552 "No dictionary found.\n");
1553 list = create_empty_dictionary_list();
1556 gtkaspellcheckers->dictionary_list = list;
1561 static void gtkaspell_free_dictionary_list(GSList *list)
1565 for (walk = list; walk != NULL; walk = g_slist_next(walk))
1567 dict = (Dictionary *) walk->data;
1568 dictionary_delete(dict);
1573 GtkTreeModel *gtkaspell_dictionary_store_new_with_refresh(gboolean refresh)
1575 GSList *dict_list, *tmp;
1576 GtkListStore *store;
1580 dict_list = gtkaspell_get_dictionary_list(refresh);
1581 g_return_val_if_fail(dict_list, NULL);
1583 store = gtk_list_store_new(SET_GTKASPELL_SIZE,
1588 for (tmp = dict_list; tmp != NULL; tmp = g_slist_next(tmp)) {
1589 dict = (Dictionary *) tmp->data;
1591 gtk_list_store_append(store, &iter);
1592 gtk_list_store_set(store, &iter,
1593 SET_GTKASPELL_NAME, dict->dictname,
1594 SET_GTKASPELL_FULLNAME, dict->fullname,
1598 return GTK_TREE_MODEL(store);
1601 GtkTreeModel *gtkaspell_dictionary_store_new(void)
1603 return gtkaspell_dictionary_store_new_with_refresh
1607 GtkWidget *gtkaspell_dictionary_combo_new(const gboolean refresh)
1610 GtkCellRenderer *renderer;
1612 combo = gtk_combo_box_new_with_model(
1613 gtkaspell_dictionary_store_new_with_refresh(refresh));
1614 gtk_combo_box_set_active(GTK_COMBO_BOX(combo), 0);
1615 gtk_widget_show(combo);
1617 renderer = gtk_cell_renderer_text_new();
1618 gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(combo), renderer, TRUE);
1619 gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(combo),renderer,
1620 "text", SET_GTKASPELL_NAME, NULL);
1625 gchar *gtkaspell_get_dictionary_menu_active_item(GtkComboBox *combo)
1628 GtkTreeModel *model;
1629 gchar *dict_fullname = NULL;
1631 g_return_val_if_fail(GTK_IS_COMBO_BOX(combo), NULL);
1632 g_return_val_if_fail(gtk_combo_box_get_active_iter(combo, &iter), NULL);
1634 model = gtk_combo_box_get_model(combo);
1638 gtk_tree_model_get(model, &iter,
1639 SET_GTKASPELL_FULLNAME, &dict_fullname,
1642 return dict_fullname;
1645 gint gtkaspell_set_dictionary_menu_active_item(GtkComboBox *combo,
1646 const gchar *dictionary)
1648 GtkTreeModel *model;
1650 gchar *dict_name = NULL;
1652 g_return_val_if_fail(combo != NULL, 0);
1653 g_return_val_if_fail(dictionary != NULL, 0);
1654 g_return_val_if_fail(GTK_IS_COMBO_BOX(combo), 0);
1656 if((model = gtk_combo_box_get_model(combo)) == NULL)
1658 if((gtk_tree_model_get_iter_first(model, &iter)) == FALSE)
1662 gtk_tree_model_get(model, &iter,
1663 SET_GTKASPELL_FULLNAME, &dict_name,
1666 if ((dict_name != NULL) && !strcmp2(dict_name, dictionary)) {
1667 gtk_combo_box_set_active_iter(combo, &iter);
1674 } while ((gtk_tree_model_iter_next(model, &iter)) == TRUE);
1679 static void use_alternate_dict(GtkAspell *gtkaspell)
1683 tmp = gtkaspell->gtkaspeller;
1684 gtkaspell->gtkaspeller = gtkaspell->alternate_speller;
1685 gtkaspell->alternate_speller = tmp;
1688 static void destroy_menu(GtkWidget *widget,
1689 gpointer user_data) {
1691 GtkAspell *gtkaspell = (GtkAspell *)user_data;
1693 if (gtkaspell->accel_group) {
1694 gtk_window_remove_accel_group(GTK_WINDOW(gtkaspell->parent_window),
1695 gtkaspell->accel_group);
1696 gtkaspell->accel_group = NULL;
1700 static gboolean aspell_key_pressed(GtkWidget *widget,
1702 GtkAspell *gtkaspell)
1704 if (event && (isascii(event->keyval) || event->keyval == GDK_Return)) {
1705 gtk_accel_groups_activate(
1706 G_OBJECT(gtkaspell->parent_window),
1707 event->keyval, event->state);
1708 } else if (event && event->keyval == GDK_Escape) {
1709 destroy_menu(NULL, gtkaspell);
1714 /* make_sug_menu() - Add menus to accept this word for this session
1715 * and to add it to personal dictionary
1717 static GSList *make_sug_menu(GtkAspell *gtkaspell)
1721 GtkTextView *gtktext;
1722 GtkAccelGroup *accel;
1723 GList *l = gtkaspell->suggestions_list;
1725 GSList *list = NULL;
1726 gtktext = gtkaspell->gtktext;
1731 accel = gtk_accel_group_new();
1733 if (gtkaspell->accel_group) {
1734 gtk_window_remove_accel_group(GTK_WINDOW(gtkaspell->parent_window),
1735 gtkaspell->accel_group);
1736 gtkaspell->accel_group = NULL;
1739 utf8buf = g_strdup(l->data);
1740 caption = g_strdup_printf(_("\"%s\" unknown in %s"),
1742 gtkaspell->gtkaspeller->dictionary->dictname);
1743 item = gtk_menu_item_new_with_label(caption);
1745 gtk_widget_show(item);
1746 list = g_slist_append(list, item);
1747 gtk_misc_set_alignment(GTK_MISC(gtk_bin_get_child(GTK_BIN((item)))), 0.5, 0.5);
1750 item = gtk_menu_item_new();
1751 gtk_widget_show(item);
1752 list = g_slist_append(list, item);
1754 item = gtk_menu_item_new_with_label(_("Accept in this session"));
1755 gtk_widget_show(item);
1756 list = g_slist_append(list, item);
1757 g_signal_connect(G_OBJECT(item), "activate",
1758 G_CALLBACK(add_word_to_session_cb),
1760 gtk_widget_add_accelerator(item, "activate", accel, GDK_space,
1762 GTK_ACCEL_LOCKED | GTK_ACCEL_VISIBLE);
1764 item = gtk_menu_item_new_with_label(_("Add to personal dictionary"));
1765 gtk_widget_show(item);
1766 list = g_slist_append(list, item);
1767 g_signal_connect(G_OBJECT(item), "activate",
1768 G_CALLBACK(add_word_to_personal_cb),
1770 gtk_widget_add_accelerator(item, "activate", accel, GDK_Return,
1772 GTK_ACCEL_LOCKED | GTK_ACCEL_VISIBLE);
1774 item = gtk_menu_item_new_with_label(_("Replace with..."));
1775 gtk_widget_show(item);
1776 list = g_slist_append(list, item);
1777 g_signal_connect(G_OBJECT(item), "activate",
1778 G_CALLBACK(replace_with_create_dialog_cb),
1780 gtk_widget_add_accelerator(item, "activate", accel, GDK_R, 0,
1781 GTK_ACCEL_LOCKED | GTK_ACCEL_VISIBLE);
1782 gtk_widget_add_accelerator(item, "activate", accel, GDK_R,
1786 if (gtkaspell->use_alternate && gtkaspell->alternate_speller) {
1787 caption = g_strdup_printf(_("Check with %s"),
1788 gtkaspell->alternate_speller->dictionary->dictname);
1789 item = gtk_menu_item_new_with_label(caption);
1791 gtk_widget_show(item);
1792 list = g_slist_append(list, item);
1793 g_signal_connect(G_OBJECT(item), "activate",
1794 G_CALLBACK(check_with_alternate_cb),
1796 gtk_widget_add_accelerator(item, "activate", accel, GDK_X, 0,
1797 GTK_ACCEL_LOCKED | GTK_ACCEL_VISIBLE);
1798 gtk_widget_add_accelerator(item, "activate", accel, GDK_X,
1803 item = gtk_menu_item_new();
1804 gtk_widget_show(item);
1805 list = g_slist_append(list, item);
1809 item = gtk_menu_item_new_with_label(_("(no suggestions)"));
1810 gtk_widget_show(item);
1811 list = g_slist_append(list, item);
1813 GtkWidget *curmenu = NULL;
1817 if (count == MENUCOUNT) {
1820 item = gtk_menu_item_new_with_label(_("More..."));
1821 gtk_widget_show(item);
1823 gtk_menu_shell_append(GTK_MENU_SHELL(curmenu), item);
1825 list = g_slist_append(list, item);
1827 curmenu = gtk_menu_new();
1828 gtk_menu_item_set_submenu(GTK_MENU_ITEM(item),
1832 utf8buf = g_strdup(l->data);
1834 item = gtk_menu_item_new_with_label(utf8buf);
1836 gtk_widget_show(item);
1837 if (curmenu == NULL) {
1838 list = g_slist_append(list, item);
1840 gtk_menu_shell_append(GTK_MENU_SHELL(curmenu), item);
1842 g_signal_connect(G_OBJECT(item), "activate",
1843 G_CALLBACK(replace_word_cb),
1846 if (curmenu == NULL && count < MENUCOUNT) {
1847 gtk_widget_add_accelerator(item, "activate",
1852 gtk_widget_add_accelerator(item, "activate",
1861 } while ((l = l->next) != NULL);
1864 gtk_window_add_accel_group
1865 (GTK_WINDOW(gtkaspell->parent_window),
1867 gtkaspell->accel_group = accel;
1872 static GSList *populate_submenu(GtkAspell *gtkaspell)
1874 GtkWidget *item, *submenu;
1876 GtkAspeller *gtkaspeller = NULL;
1877 GSList *list = NULL;
1882 gtkaspeller = gtkaspell->gtkaspeller;
1883 dictname = g_strdup_printf(_("Dictionary: %s"),
1884 gtkaspeller->dictionary->dictname);
1885 item = gtk_menu_item_new_with_label(dictname);
1886 gtk_misc_set_alignment(GTK_MISC(gtk_bin_get_child(GTK_BIN((item)))), 0.5, 0.5);
1888 gtk_widget_show(item);
1889 list = g_slist_append(list, item);
1891 item = gtk_menu_item_new();
1892 gtk_widget_show(item);
1893 list = g_slist_append(list, item);
1895 if (gtkaspell->use_alternate && gtkaspell->alternate_speller) {
1896 dictname = g_strdup_printf(_("Use alternate (%s)"),
1897 gtkaspell->alternate_speller->dictionary->dictname);
1898 item = gtk_menu_item_new_with_label(dictname);
1900 g_signal_connect(G_OBJECT(item), "activate",
1901 G_CALLBACK(switch_to_alternate_cb),
1903 gtk_widget_show(item);
1904 list = g_slist_append(list, item);
1907 item = gtk_check_menu_item_new_with_label(_("Use both dictionaries"));
1908 if (gtkaspell->use_both_dicts) {
1909 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(item), TRUE);
1911 g_signal_connect(G_OBJECT(item), "activate",
1912 G_CALLBACK(set_use_both_cb),
1914 gtk_widget_show(item);
1915 list = g_slist_append(list, item);
1917 item = gtk_menu_item_new();
1918 gtk_widget_show(item);
1919 list = g_slist_append(list, item);
1921 item = gtk_check_menu_item_new_with_label(_("Check while typing"));
1922 if (gtkaspell->check_while_typing)
1923 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(item), TRUE);
1925 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(item), FALSE);
1926 g_signal_connect(G_OBJECT(item), "activate",
1927 G_CALLBACK(toggle_check_while_typing_cb),
1929 gtk_widget_show(item);
1930 list = g_slist_append(list, item);
1932 item = gtk_menu_item_new();
1933 gtk_widget_show(item);
1934 list = g_slist_append(list, item);
1936 submenu = gtk_menu_new();
1937 item = gtk_menu_item_new_with_label(_("Change dictionary"));
1938 gtk_menu_item_set_submenu(GTK_MENU_ITEM(item),submenu);
1939 gtk_widget_show(item);
1940 list = g_slist_append(list, item);
1943 if (gtkaspellcheckers->dictionary_list == NULL)
1944 gtkaspell_get_dictionary_list(FALSE);
1946 GtkWidget * curmenu = submenu;
1950 tmp = gtkaspellcheckers->dictionary_list;
1952 for (tmp = gtkaspellcheckers->dictionary_list; tmp != NULL;
1953 tmp = g_slist_next(tmp)) {
1954 if (count == MENUCOUNT) {
1957 newmenu = gtk_menu_new();
1958 item = gtk_menu_item_new_with_label(_("More..."));
1959 gtk_menu_item_set_submenu(GTK_MENU_ITEM(item),
1962 gtk_menu_shell_append(GTK_MENU_SHELL(curmenu), item);
1963 gtk_widget_show(item);
1967 dict = (Dictionary *) tmp->data;
1968 item = gtk_check_menu_item_new_with_label(dict->dictname);
1969 g_object_set_data(G_OBJECT(item), "dict_name",
1971 if (strcmp2(dict->fullname,
1972 gtkaspell->gtkaspeller->dictionary->fullname))
1973 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(item), FALSE);
1975 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(item), TRUE);
1976 gtk_widget_set_sensitive(GTK_WIDGET(item),
1979 g_signal_connect(G_OBJECT(item), "activate",
1980 G_CALLBACK(change_dict_cb),
1982 gtk_widget_show(item);
1983 gtk_menu_shell_append(GTK_MENU_SHELL(curmenu), item);
1991 GSList *gtkaspell_make_config_menu(GtkAspell *gtkaspell)
1993 return populate_submenu(gtkaspell);
1996 static void set_menu_pos(GtkMenu *menu, gint *x, gint *y,
1997 gboolean *push_in, gpointer data)
1999 GtkAspell *gtkaspell = (GtkAspell *) data;
2000 gint xx = 0, yy = 0;
2003 GtkTextView *text = GTK_TEXT_VIEW(gtkaspell->gtktext);
2004 GtkTextBuffer *textbuf;
2009 textbuf = gtk_text_view_get_buffer(gtkaspell->gtktext);
2010 gtk_text_buffer_get_iter_at_mark(textbuf, &iter,
2011 gtk_text_buffer_get_insert(textbuf));
2012 gtk_text_view_get_iter_location(gtkaspell->gtktext, &iter, &rect);
2013 gtk_text_view_buffer_to_window_coords(text, GTK_TEXT_WINDOW_TEXT,
2017 gdk_window_get_origin(GTK_WIDGET(gtkaspell->gtktext)->window, &xx, &yy);
2019 sx = gdk_screen_width();
2020 sy = gdk_screen_height();
2022 gtk_widget_get_child_requisition(GTK_WIDGET(menu), &r);
2027 *x = rect.x + xx + 8;
2029 *y = rect.y + rect.height + yy;
2037 /* change the current dictionary of gtkaspell
2038 - if always_set_alt_dict is set, the alternate dict is unconditionally set to the former
2039 current dictionary (common use: from menu callbacks)
2040 - if always_set_alt_dict is NOT set, the alternate dict will be set to the former
2041 current dictionary only if there is no alternate dictionary already set
2042 (this is when we need to set the current dictionary then the alternate one
2043 when creating a compose window, from the account and folder settings)
2045 gboolean gtkaspell_change_dict(GtkAspell *gtkaspell, const gchar *dictionary,
2046 gboolean always_set_alt_dict)
2049 GtkAspeller *gtkaspeller;
2051 g_return_val_if_fail(gtkaspell, FALSE);
2052 g_return_val_if_fail(dictionary, FALSE);
2054 dict = g_new0(Dictionary, 1);
2056 if (strrchr(dictionary, '/')) {
2057 dict->fullname = g_strdup(strrchr(dictionary, '/')+1);
2058 dict->dictname = g_strdup(strrchr(dictionary, '/')+1);
2060 dict->fullname = g_strdup(dictionary);
2061 dict->dictname = g_strdup(dictionary);
2064 if (strchr(dictionary, '-')) {
2065 *(strchr(dictionary, '-')) = '\0';
2068 gtkaspeller = gtkaspeller_new(dict);
2071 alertpanel_warning(_("The spell checker could not change dictionary.\n%s"),
2072 gtkaspellcheckers->error_message);
2074 if (gtkaspell->use_alternate) {
2075 if (gtkaspell->alternate_speller) {
2076 if (always_set_alt_dict) {
2077 gtkaspeller_delete(gtkaspell->alternate_speller);
2078 gtkaspell->alternate_speller = gtkaspell->gtkaspeller;
2080 gtkaspeller_delete(gtkaspell->gtkaspeller);
2082 /* should never be reached as the dicts are always set
2083 to a default value */
2084 gtkaspell->alternate_speller = gtkaspell->gtkaspeller;
2086 gtkaspeller_delete(gtkaspell->gtkaspeller);
2088 gtkaspell->gtkaspeller = gtkaspeller;
2091 dictionary_delete(dict);
2096 /* change the alternate dictionary of gtkaspell (doesn't affect the default dictionary) */
2097 gboolean gtkaspell_change_alt_dict(GtkAspell *gtkaspell, const gchar *alt_dictionary)
2100 GtkAspeller *gtkaspeller;
2102 g_return_val_if_fail(gtkaspell, FALSE);
2103 g_return_val_if_fail(alt_dictionary, FALSE);
2105 dict = g_new0(Dictionary, 1);
2106 if (strrchr(alt_dictionary, '/')) {
2107 dict->fullname = g_strdup(strrchr(alt_dictionary, '/')+1);
2108 dict->dictname = g_strdup(strrchr(alt_dictionary, '/')+1);
2110 dict->fullname = g_strdup(alt_dictionary);
2111 dict->dictname = g_strdup(alt_dictionary);
2114 if (strchr(alt_dictionary, '-')) {
2115 *(strchr(alt_dictionary, '-')) = '\0';
2118 gtkaspeller = gtkaspeller_new(dict);
2121 alertpanel_warning(_("The spell checker could not change the alternate dictionary.\n%s"),
2122 gtkaspellcheckers->error_message);
2124 if (gtkaspell->alternate_speller)
2125 gtkaspeller_delete(gtkaspell->alternate_speller);
2126 gtkaspell->alternate_speller = gtkaspeller;
2129 dictionary_delete(dict);
2134 /* Menu call backs */
2136 /* change_dict_cb() - Menu callback : change dict */
2137 static void change_dict_cb(GtkWidget *w, GtkAspell *gtkaspell)
2141 fullname = (gchar *) g_object_get_data(G_OBJECT(w), "dict_name");
2143 if (!strcmp2(fullname, _("None")))
2146 gtkaspell_change_dict(gtkaspell, fullname, TRUE);
2147 if (gtkaspell->recheck_when_changing_dict) {
2148 gtkaspell_highlight_all(gtkaspell);
2150 if (gtkaspell->menu_changed_cb)
2151 gtkaspell->menu_changed_cb(gtkaspell->menu_changed_data);
2154 static void switch_to_alternate_cb(GtkWidget *w,
2157 GtkAspell *gtkaspell = (GtkAspell *) data;
2158 use_alternate_dict(gtkaspell);
2159 if (gtkaspell->recheck_when_changing_dict) {
2160 gtkaspell_highlight_all(gtkaspell);
2162 if (gtkaspell->menu_changed_cb)
2163 gtkaspell->menu_changed_cb(gtkaspell->menu_changed_data);
2166 /* Misc. helper functions */
2168 static void set_point_continue(GtkAspell *gtkaspell)
2170 GtkTextView *gtktext;
2172 gtktext = gtkaspell->gtktext;
2174 set_textview_buffer_offset(gtktext, gtkaspell->orig_pos);
2176 if (gtkaspell->continue_check)
2177 gtkaspell->continue_check((gpointer *) gtkaspell);
2180 static void allocate_color(GtkAspell *gtkaspell, gint rgbvalue)
2182 GtkTextBuffer *buffer = gtk_text_view_get_buffer(gtkaspell->gtktext);
2183 GdkColor *color = &(gtkaspell->highlight);
2185 /* Shameless copy from Sylpheed's gtkutils.c */
2187 color->red = (int) (((gdouble)((rgbvalue & 0xff0000) >> 16) / 255.0)
2189 color->green = (int) (((gdouble)((rgbvalue & 0x00ff00) >> 8) / 255.0)
2191 color->blue = (int) (((gdouble) (rgbvalue & 0x0000ff) / 255.0)
2195 gtk_text_buffer_create_tag(buffer, "misspelled",
2196 "foreground-gdk", color, NULL);
2198 gtk_text_buffer_create_tag(buffer, "misspelled",
2199 "underline", PANGO_UNDERLINE_ERROR, NULL);
2203 static void change_color(GtkAspell * gtkaspell,
2204 gint start, gint end,
2208 GtkTextView *gtktext;
2209 GtkTextBuffer *buffer;
2210 GtkTextIter startiter, enditer;
2215 gtktext = gtkaspell->gtktext;
2217 buffer = gtk_text_view_get_buffer(gtktext);
2218 gtk_text_buffer_get_iter_at_offset(buffer, &startiter, start);
2219 gtk_text_buffer_get_iter_at_offset(buffer, &enditer, end);
2221 gtk_text_buffer_apply_tag_by_name(buffer, "misspelled",
2222 &startiter, &enditer);
2224 gtk_text_iter_forward_char(&enditer);
2225 gtk_text_buffer_remove_tag_by_name(buffer, "misspelled",
2226 &startiter, &enditer);
2230 /* compare_dict () - compare 2 dict names */
2231 static gint compare_dict(Dictionary *a, Dictionary *b)
2233 guint aparts = 0, bparts = 0;
2236 for (i=0; i < strlen(a->dictname); i++)
2237 if (a->dictname[i] == '-')
2239 for (i=0; i < strlen(b->dictname); i++)
2240 if (b->dictname[i] == '-')
2243 if (aparts != bparts)
2244 return (aparts < bparts) ? -1 : +1;
2247 compare = strcmp2(a->dictname, b->dictname);
2249 compare = strcmp2(a->fullname, b->fullname);
2254 static void dictionary_delete(Dictionary *dict)
2256 g_free(dict->fullname);
2257 g_free(dict->dictname);
2261 static Dictionary *dictionary_dup(const Dictionary *dict)
2265 dict2 = g_new(Dictionary, 1);
2267 dict2->fullname = g_strdup(dict->fullname);
2268 dict2->dictname = g_strdup(dict->dictname);
2273 static void free_suggestions_list(GtkAspell *gtkaspell)
2277 for (list = gtkaspell->suggestions_list; list != NULL;
2281 g_list_free(gtkaspell->suggestions_list);
2283 gtkaspell->max_sug = -1;
2284 gtkaspell->suggestions_list = NULL;
2287 static void reset_theword_data(GtkAspell *gtkaspell)
2289 gtkaspell->start_pos = 0;
2290 gtkaspell->end_pos = 0;
2291 gtkaspell->theword[0] = 0;
2292 gtkaspell->max_sug = -1;
2294 free_suggestions_list(gtkaspell);
2297 static void free_checkers(gpointer elt, gpointer data)
2299 GtkAspeller *gtkaspeller = elt;
2301 g_return_if_fail(gtkaspeller);
2303 gtkaspeller_real_delete(gtkaspeller);
2306 static gint find_gtkaspeller(gconstpointer aa, gconstpointer bb)
2308 Dictionary *a = ((GtkAspeller *) aa)->dictionary;
2309 Dictionary *b = ((GtkAspeller *) bb)->dictionary;
2311 if (a && b && a->fullname && b->fullname)
2312 return strcmp(a->fullname, b->fullname);
2317 gchar *gtkaspell_get_default_dictionary(GtkAspell *gtkaspell)
2319 if (gtkaspell && gtkaspell->gtkaspeller &&
2320 gtkaspell->gtkaspeller->dictionary)
2321 return gtkaspell->gtkaspeller->dictionary->dictname;