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(dict->fullname, '-')) {
397 *(strchr(dict->fullname, '-')) = '\0';
398 *(strchr(dict->dictname, '-')) = '\0';
400 gtkaspeller = gtkaspeller_new(dict);
401 dictionary_delete(dict);
404 gtkaspell_checkers_error_message(
405 g_strdup_printf(_("Couldn't initialize %s speller."), dictionary));
409 gtkaspell = g_new0(GtkAspell, 1);
411 gtkaspell->gtkaspeller = gtkaspeller;
413 if (use_alternate && alt_dictionary && *alt_dictionary) {
414 Dictionary *alt_dict;
415 GtkAspeller *alt_gtkaspeller;
417 alt_dict = g_new0(Dictionary, 1);
418 if (strrchr(alt_dictionary, '/')) {
419 alt_dict->fullname = g_strdup(strrchr(alt_dictionary, '/')+1);
420 alt_dict->dictname = g_strdup(strrchr(alt_dictionary, '/')+1);
422 alt_dict->fullname = g_strdup(alt_dictionary);
423 alt_dict->dictname = g_strdup(alt_dictionary);
425 if (strchr(alt_dict->fullname, '-')) {
426 *(strchr(alt_dict->fullname, '-')) = '\0';
427 *(strchr(alt_dict->dictname, '-')) = '\0';
430 alt_gtkaspeller = gtkaspeller_new(alt_dict);
431 dictionary_delete(alt_dict);
433 if (!alt_gtkaspeller) {
434 gtkaspell_checkers_error_message(
435 g_strdup_printf(_("Couldn't initialize %s speller."), dictionary));
439 gtkaspell->alternate_speller = alt_gtkaspeller;
441 gtkaspell->alternate_speller = NULL;
444 gtkaspell->theword[0] = 0x00;
445 gtkaspell->start_pos = 0;
446 gtkaspell->end_pos = 0;
447 gtkaspell->orig_pos = -1;
448 gtkaspell->end_check_pos = -1;
449 gtkaspell->misspelled = -1;
450 gtkaspell->check_while_typing = check_while_typing;
451 gtkaspell->recheck_when_changing_dict = recheck_when_changing_dict;
452 gtkaspell->continue_check = NULL;
453 gtkaspell->replace_entry = NULL;
454 gtkaspell->gtktext = gtktext;
455 gtkaspell->max_sug = -1;
456 gtkaspell->suggestions_list = NULL;
457 gtkaspell->use_alternate = use_alternate;
458 gtkaspell->use_both_dicts = use_both_dicts;
459 gtkaspell->parent_window = GTK_WIDGET(parent_win);
460 gtkaspell->menu_changed_cb = spell_menu_cb;
461 gtkaspell->menu_changed_data = data;
463 allocate_color(gtkaspell, misspelled_color);
465 g_signal_connect_after(G_OBJECT(buffer), "insert-text",
466 G_CALLBACK(entry_insert_cb), gtkaspell);
467 g_signal_connect_after(G_OBJECT(buffer), "delete-range",
468 G_CALLBACK(entry_delete_cb), gtkaspell);
469 /*g_signal_connect(G_OBJECT(gtktext), "button-press-event",
470 G_CALLBACK(button_press_intercept_cb),
472 g_signal_connect(G_OBJECT(gtktext), "populate-popup",
473 G_CALLBACK(button_press_intercept_cb), gtkaspell);
475 debug_print("Aspell: created gtkaspell %p\n", gtkaspell);
480 void gtkaspell_delete(GtkAspell *gtkaspell)
482 GtkTextView *gtktext = gtkaspell->gtktext;
484 g_signal_handlers_disconnect_by_func(G_OBJECT(gtktext),
485 G_CALLBACK(entry_insert_cb),
487 g_signal_handlers_disconnect_by_func(G_OBJECT(gtktext),
488 G_CALLBACK(entry_delete_cb),
490 g_signal_handlers_disconnect_by_func(G_OBJECT(gtktext),
491 G_CALLBACK(button_press_intercept_cb),
494 gtkaspell_uncheck_all(gtkaspell);
496 gtkaspeller_delete(gtkaspell->gtkaspeller);
498 if (gtkaspell->alternate_speller)
499 gtkaspeller_delete(gtkaspell->alternate_speller);
501 if (gtkaspell->suggestions_list)
502 free_suggestions_list(gtkaspell);
504 debug_print("Aspell: deleting gtkaspell %p\n", gtkaspell);
511 static void entry_insert_cb(GtkTextBuffer *textbuf,
515 GtkAspell *gtkaspell)
519 g_return_if_fail(gtkaspell->gtkaspeller->speller);
521 if (!gtkaspell->check_while_typing)
524 pos = gtk_text_iter_get_offset(iter);
526 if (iswordsep(g_utf8_get_char(newtext))) {
527 /* did we just end a word? */
529 check_at(gtkaspell, pos - 2);
531 /* did we just split a word? */
532 if (pos < gtk_text_buffer_get_char_count(textbuf))
533 check_at(gtkaspell, pos + 1);
535 /* check as they type, *except* if they're typing at the end (the most
538 if (pos < gtk_text_buffer_get_char_count(textbuf) &&
539 !iswordsep(get_text_index_whar(gtkaspell, pos))) {
540 check_at(gtkaspell, pos - 1);
545 static void entry_delete_cb(GtkTextBuffer *textbuf,
546 GtkTextIter *startiter,
547 GtkTextIter *enditer,
548 GtkAspell *gtkaspell)
553 g_return_if_fail(gtkaspell->gtkaspeller->speller);
555 if (!gtkaspell->check_while_typing)
558 start = gtk_text_iter_get_offset(startiter);
559 end = gtk_text_iter_get_offset(enditer);
560 origpos = get_textview_buffer_offset(gtkaspell->gtktext);
562 check_at(gtkaspell, start - 1);
563 check_at(gtkaspell, start);
566 set_textview_buffer_offset(gtkaspell->gtktext, origpos);
567 /* this is to *UNDO* the selection, in case they were holding shift
568 * while hitting backspace. */
569 /* needed with textview ??? */
570 /* gtk_editable_select_region(GTK_EDITABLE(gtktext), origpos, origpos); */
573 static void button_press_intercept_cb(GtkTextView *gtktext,
574 GtkMenu *menu, GtkAspell *gtkaspell)
576 GtkMenuItem *menuitem;
577 GSList *spell_menu = NULL;
579 gboolean suggest = FALSE;
581 menuitem = GTK_MENU_ITEM(gtk_separator_menu_item_new());
582 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), GTK_WIDGET(menuitem));
583 gtk_widget_show(GTK_WIDGET(menuitem));
585 gtktext = gtkaspell->gtktext;
587 gtkaspell->orig_pos = get_textview_buffer_offset(gtktext);
589 if (check_at(gtkaspell, gtkaspell->orig_pos)) {
591 if (misspelled_suggest(gtkaspell, gtkaspell->theword)) {
592 spell_menu = make_sug_menu(gtkaspell);
597 spell_menu = gtkaspell_make_config_menu(gtkaspell);
599 spell_menu = g_slist_reverse(spell_menu);
600 for (items = spell_menu;
601 items; items = items->next) {
602 menuitem = GTK_MENU_ITEM(items->data);
603 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), GTK_WIDGET(menuitem));
604 gtk_widget_show(GTK_WIDGET(menuitem));
606 g_slist_free(spell_menu);
608 g_signal_connect(G_OBJECT(menu), "deactivate",
609 G_CALLBACK(destroy_menu),
612 g_signal_connect(G_OBJECT(menu),
614 G_CALLBACK(aspell_key_pressed), gtkaspell);
618 /* Checker creation */
619 static GtkAspeller *gtkaspeller_new(Dictionary *dictionary)
622 GtkAspeller *gtkaspeller = NULL;
626 g_return_val_if_fail(gtkaspellcheckers, NULL);
628 g_return_val_if_fail(dictionary, NULL);
630 if (dictionary->dictname == NULL)
631 gtkaspell_checkers_error_message(
632 g_strdup(_("No dictionary selected.")));
634 g_return_val_if_fail(dictionary->fullname, NULL);
636 dict = dictionary_dup(dictionary);
638 tmp = g_new0(GtkAspeller, 1);
639 tmp->dictionary = dict;
641 exist = g_slist_find_custom(gtkaspellcheckers->checkers, tmp,
646 if ((gtkaspeller = gtkaspeller_real_new(dict)) != NULL) {
647 gtkaspellcheckers->checkers = g_slist_append(
648 gtkaspellcheckers->checkers,
651 debug_print("Aspell: Created a new gtkaspeller %p\n",
654 dictionary_delete(dict);
656 debug_print("Aspell: Could not create spell checker.\n");
659 debug_print("Aspell: number of existing checkers %d\n",
660 g_slist_length(gtkaspellcheckers->checkers));
665 static GtkAspeller *gtkaspeller_real_new(Dictionary *dict)
667 GtkAspeller *gtkaspeller;
668 EnchantBroker *broker;
669 EnchantDict *speller;
671 g_return_val_if_fail(gtkaspellcheckers, NULL);
672 g_return_val_if_fail(dict, NULL);
674 gtkaspeller = g_new(GtkAspeller, 1);
676 gtkaspeller->dictionary = dict;
678 broker = enchant_broker_init();
681 gtkaspell_checkers_error_message(
682 g_strdup(_("Couldn't initialize Enchant broker.")));
685 if ((speller = set_dictionary(broker, dict)) == NULL) {
686 gtkaspell_checkers_error_message(
687 g_strdup_printf(_("Couldn't initialize %s dictionary:"), dict->fullname));
688 gtkaspell_checkers_error_message(
689 g_strdup(enchant_broker_get_error(broker)));
692 gtkaspeller->speller = speller;
693 gtkaspeller->broker = broker;
698 static GtkAspeller *gtkaspeller_delete(GtkAspeller *gtkaspeller)
700 g_return_val_if_fail(gtkaspellcheckers, NULL);
702 gtkaspellcheckers->checkers =
703 g_slist_remove(gtkaspellcheckers->checkers,
706 debug_print("Aspell: Deleting gtkaspeller %p.\n",
709 gtkaspeller_real_delete(gtkaspeller);
711 debug_print("Aspell: number of existing checkers %d\n",
712 g_slist_length(gtkaspellcheckers->checkers));
717 static GtkAspeller *gtkaspeller_real_delete(GtkAspeller *gtkaspeller)
719 g_return_val_if_fail(gtkaspeller, NULL);
720 g_return_val_if_fail(gtkaspeller->speller, NULL);
722 enchant_broker_free_dict(gtkaspeller->broker, gtkaspeller->speller);
723 enchant_broker_free(gtkaspeller->broker);
725 dictionary_delete(gtkaspeller->dictionary);
727 debug_print("Aspell: gtkaspeller %p deleted.\n",
735 /*****************************************************************************/
736 /* Checker configuration */
738 static EnchantDict *set_dictionary(EnchantBroker *broker, Dictionary *dict)
740 g_return_val_if_fail(broker, FALSE);
741 g_return_val_if_fail(dict, FALSE);
743 return enchant_broker_request_dict(broker, dict->dictname );
746 static void set_use_both_cb(GtkMenuItem *w, GtkAspell *gtkaspell)
748 gtkaspell->use_both_dicts = gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(w));
750 if (gtkaspell->recheck_when_changing_dict) {
751 gtkaspell_highlight_all(gtkaspell);
754 if (gtkaspell->menu_changed_cb)
755 gtkaspell->menu_changed_cb(gtkaspell->menu_changed_data);
758 /* misspelled_suggest() - Create a suggestion list for word */
759 static GList *misspelled_suggest(GtkAspell *gtkaspell, gchar *word)
764 g_return_val_if_fail(word, NULL);
769 free_suggestions_list(gtkaspell);
771 suggestions = enchant_dict_suggest(gtkaspell->gtkaspeller->speller, word, strlen(word), &num_sug);
772 if (suggestions == NULL || num_sug == 0) {
773 gtkaspell->max_sug = - 1;
774 gtkaspell->suggestions_list = NULL;
777 list = g_list_append(list, g_strdup(word));
778 for (i = 0; i < num_sug; i++)
779 list = g_list_append(list, g_strdup((gchar *)suggestions[i]));
781 gtkaspell->max_sug = num_sug - 1;
782 gtkaspell->suggestions_list = list;
783 enchant_dict_free_string_list(gtkaspell->gtkaspeller->speller, suggestions);
787 /* misspelled_test() - Just test if word is correctly spelled */
788 static int misspelled_test(GtkAspell *gtkaspell, char *word)
791 g_return_val_if_fail(word, 0);
796 result = enchant_dict_check(gtkaspell->gtkaspeller->speller, word, strlen(word));
798 if (result && gtkaspell->use_both_dicts && gtkaspell->alternate_speller) {
799 use_alternate_dict(gtkaspell);
800 result = enchant_dict_check(gtkaspell->gtkaspeller->speller, word, strlen(word));
801 use_alternate_dict(gtkaspell);
807 static gboolean iswordsep(gunichar c)
809 return (g_unichar_isspace(c) || g_unichar_ispunct(c)) && c != (gunichar)'\'';
812 static gunichar get_text_index_whar(GtkAspell *gtkaspell, int pos)
814 GtkTextView *view = gtkaspell->gtktext;
815 GtkTextBuffer *buffer = gtk_text_view_get_buffer(view);
816 GtkTextIter start, end;
820 gtk_text_buffer_get_iter_at_offset(buffer, &start, pos);
821 gtk_text_buffer_get_iter_at_offset(buffer, &end, pos+1);
823 utf8chars = gtk_text_iter_get_text(&start, &end);
824 a = g_utf8_get_char(utf8chars);
830 /* get_word_from_pos () - return the word pointed to. */
831 /* Handles correctly the quotes. */
832 static gboolean get_word_from_pos(GtkAspell *gtkaspell, gint pos,
833 char* buf, gint buflen,
834 gint *pstart, gint *pend)
837 /* TODO : when correcting a word into quotes, change the color of */
838 /* the quotes too, as may be they were highlighted before. To do */
839 /* this, we can use two others pointers that points to the whole */
840 /* word including quotes. */
846 GtkTextView *gtktext;
848 gtktext = gtkaspell->gtktext;
849 if (iswordsep(get_text_index_whar(gtkaspell, pos)))
852 /* The apostrophe character is somtimes used for quotes
853 * So include it in the word only if it is not surrounded
854 * by other characters.
857 for (start = pos; start >= 0; --start) {
858 c = get_text_index_whar(gtkaspell, start);
859 if (c == (gunichar)'\'') {
861 if (g_unichar_isspace(get_text_index_whar(gtkaspell,
863 || g_unichar_ispunct(get_text_index_whar(gtkaspell,
865 || g_unichar_isdigit(get_text_index_whar(gtkaspell,
867 /* start_quote = TRUE; */
872 /* start_quote = TRUE; */
876 else if (g_unichar_isspace(c) || g_unichar_ispunct(c) || g_unichar_isdigit(c))
882 for (end = pos; end < get_textview_buffer_charcount(gtktext); end++) {
883 c = get_text_index_whar(gtkaspell, end);
884 if (c == (gunichar)'\'') {
885 if (end < get_textview_buffer_charcount(gtktext)) {
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 /* end_quote = TRUE; */
897 /* end_quote = TRUE; */
901 else if (g_unichar_isspace(c) || g_unichar_ispunct(c) || g_unichar_isdigit(c))
911 if (end - start < buflen) {
912 GtkTextIter iterstart, iterend;
914 GtkTextBuffer *buffer = gtk_text_view_get_buffer(gtktext);
915 gtk_text_buffer_get_iter_at_offset(buffer, &iterstart, start);
916 gtk_text_buffer_get_iter_at_offset(buffer, &iterend, end);
917 tmp = gtk_text_buffer_get_text(buffer, &iterstart, &iterend, FALSE);
918 strncpy(buf, tmp, buflen-1);
928 static gboolean check_at(GtkAspell *gtkaspell, gint from_pos)
931 char buf[GTKASPELLWORDSIZE];
932 GtkTextView *gtktext;
934 g_return_val_if_fail(from_pos >= 0, FALSE);
936 gtktext = gtkaspell->gtktext;
938 if (!get_word_from_pos(gtkaspell, from_pos, buf, sizeof(buf),
942 if (misspelled_test(gtkaspell, buf)
943 && strcasecmp(buf, "sylpheed") && strcasecmp(buf, "claws-mail")) {
944 strncpy(gtkaspell->theword, (gchar *)buf, GTKASPELLWORDSIZE - 1);
945 gtkaspell->theword[GTKASPELLWORDSIZE - 1] = 0;
946 gtkaspell->start_pos = start;
947 gtkaspell->end_pos = end;
948 free_suggestions_list(gtkaspell);
950 change_color(gtkaspell, start, end, (gchar *)buf, &(gtkaspell->highlight));
953 change_color(gtkaspell, start, end, (gchar *)buf, NULL);
958 static gboolean check_next_prev(GtkAspell *gtkaspell, gboolean forward)
967 maxpos = gtkaspell->end_check_pos;
975 pos = get_textview_buffer_offset(gtkaspell->gtktext);
976 gtkaspell->orig_pos = pos;
977 while (iswordsep(get_text_index_whar(gtkaspell, pos)) &&
978 pos > minpos && pos <= maxpos)
980 while (!(misspelled = check_at(gtkaspell, pos)) &&
981 pos > minpos && pos <= maxpos) {
983 while (!iswordsep(get_text_index_whar(gtkaspell, pos)) &&
984 pos > minpos && pos <= maxpos)
987 while (iswordsep(get_text_index_whar(gtkaspell, pos)) &&
988 pos > minpos && pos <= maxpos)
994 misspelled_suggest(gtkaspell, gtkaspell->theword);
997 gtkaspell->orig_pos = gtkaspell->end_pos;
999 set_textview_buffer_offset(gtkaspell->gtktext,
1000 gtkaspell->end_pos);
1001 /* scroll line to window center */
1002 gtk_text_view_scroll_to_mark(gtkaspell->gtktext,
1003 gtk_text_buffer_get_insert(
1004 gtk_text_view_get_buffer(gtkaspell->gtktext)),
1005 0.0, TRUE, 0.0, 0.5);
1006 /* let textview recalculate coordinates (set_menu_pos) */
1007 while (gtk_events_pending ())
1008 gtk_main_iteration ();
1010 list = make_sug_menu(gtkaspell);
1011 menu = gtk_menu_new();
1012 for (cur = list; cur; cur = cur->next)
1013 gtk_menu_shell_append(GTK_MENU_SHELL(menu), GTK_WIDGET(cur->data));
1015 gtk_menu_popup(GTK_MENU(menu), NULL, NULL,
1016 set_menu_pos, gtkaspell, 0, GDK_CURRENT_TIME);
1017 g_signal_connect(G_OBJECT(menu), "deactivate",
1018 G_CALLBACK(destroy_menu),
1020 g_signal_connect(G_OBJECT(menu),
1022 G_CALLBACK(aspell_key_pressed), gtkaspell);
1026 reset_theword_data(gtkaspell);
1028 alertpanel_notice(_("No misspelled word found."));
1029 set_textview_buffer_offset(gtkaspell->gtktext,
1030 gtkaspell->orig_pos);
1036 void gtkaspell_check_backwards(GtkAspell *gtkaspell)
1038 gtkaspell->continue_check = NULL;
1039 gtkaspell->end_check_pos =
1040 get_textview_buffer_charcount(gtkaspell->gtktext);
1041 check_next_prev(gtkaspell, FALSE);
1044 void gtkaspell_check_forwards_go(GtkAspell *gtkaspell)
1047 gtkaspell->continue_check = NULL;
1048 gtkaspell->end_check_pos =
1049 get_textview_buffer_charcount(gtkaspell->gtktext);
1050 check_next_prev(gtkaspell, TRUE);
1053 void gtkaspell_check_all(GtkAspell *gtkaspell)
1055 GtkTextView *gtktext;
1057 GtkTextBuffer *buffer;
1058 GtkTextIter startiter, enditer;
1060 g_return_if_fail(gtkaspell);
1061 g_return_if_fail(gtkaspell->gtktext);
1063 gtktext = gtkaspell->gtktext;
1064 buffer = gtk_text_view_get_buffer(gtktext);
1065 gtk_text_buffer_get_selection_bounds(buffer, &startiter, &enditer);
1066 start = gtk_text_iter_get_offset(&startiter);
1067 end = gtk_text_iter_get_offset(&enditer);
1071 end = gtk_text_buffer_get_char_count(buffer);
1072 } else if (start > end) {
1080 set_textview_buffer_offset(gtktext, start);
1082 gtkaspell->continue_check = continue_check;
1083 gtkaspell->end_check_pos = end;
1085 gtkaspell->misspelled = check_next_prev(gtkaspell, TRUE);
1088 static void continue_check(gpointer *data)
1090 GtkAspell *gtkaspell = (GtkAspell *) data;
1091 gint pos = get_textview_buffer_offset(gtkaspell->gtktext);
1092 if (pos < gtkaspell->end_check_pos && gtkaspell->misspelled)
1093 gtkaspell->misspelled = check_next_prev(gtkaspell, TRUE);
1095 gtkaspell->continue_check = NULL;
1098 void gtkaspell_highlight_all(GtkAspell *gtkaspell)
1103 GtkTextView *gtktext;
1105 g_return_if_fail(gtkaspell->gtkaspeller->speller);
1107 gtktext = gtkaspell->gtktext;
1109 len = get_textview_buffer_charcount(gtktext);
1111 origpos = get_textview_buffer_offset(gtktext);
1115 iswordsep(get_text_index_whar(gtkaspell, pos)))
1118 !iswordsep(get_text_index_whar(gtkaspell, pos)))
1121 check_at(gtkaspell, pos - 1);
1123 set_textview_buffer_offset(gtktext, origpos);
1126 static void replace_with_supplied_word_cb(GtkWidget *w, GtkAspell *gtkaspell)
1129 GdkEvent *e= (GdkEvent *) gtk_get_current_event();
1131 newword = gtk_editable_get_chars(GTK_EDITABLE(gtkaspell->replace_entry),
1134 if (strcmp(newword, gtkaspell->theword)) {
1135 replace_real_word(gtkaspell, newword);
1137 if ((e->type == GDK_KEY_PRESS &&
1138 ((GdkEventKey *) e)->state & GDK_CONTROL_MASK)) {
1139 enchant_dict_store_replacement(gtkaspell->gtkaspeller->speller,
1140 gtkaspell->theword, strlen(gtkaspell->theword),
1141 newword, strlen(newword));
1143 gtkaspell->replace_entry = NULL;
1148 if (w && GTK_IS_DIALOG(w)) {
1149 gtk_widget_destroy(w);
1152 set_point_continue(gtkaspell);
1156 static void replace_word_cb(GtkWidget *w, gpointer data)
1158 const char *newword;
1159 GtkAspell *gtkaspell = (GtkAspell *) data;
1160 GdkEvent *e= (GdkEvent *) gtk_get_current_event();
1162 newword = gtk_label_get_text(GTK_LABEL(gtk_bin_get_child(GTK_BIN((w)))));
1164 replace_real_word(gtkaspell, newword);
1166 if ((e->type == GDK_KEY_PRESS &&
1167 ((GdkEventKey *) e)->state & GDK_CONTROL_MASK) ||
1168 (e->type == GDK_BUTTON_RELEASE &&
1169 ((GdkEventButton *) e)->state & GDK_CONTROL_MASK)) {
1170 enchant_dict_store_replacement(gtkaspell->gtkaspeller->speller,
1171 gtkaspell->theword, strlen(gtkaspell->theword),
1172 newword, strlen(newword));
1175 gtk_menu_shell_deactivate(GTK_MENU_SHELL(w->parent));
1177 set_point_continue(gtkaspell);
1180 static void replace_real_word(GtkAspell *gtkaspell, const gchar *newword)
1182 int oldlen, newlen, wordlen;
1185 GtkTextView *gtktext;
1186 GtkTextBuffer *textbuf;
1187 GtkTextIter startiter, enditer;
1189 if (!newword) return;
1191 gtktext = gtkaspell->gtktext;
1192 textbuf = gtk_text_view_get_buffer(gtktext);
1194 origpos = gtkaspell->orig_pos;
1196 oldlen = gtkaspell->end_pos - gtkaspell->start_pos;
1197 wordlen = strlen(gtkaspell->theword);
1199 newlen = strlen(newword); /* FIXME: multybyte characters? */
1201 g_signal_handlers_block_by_func(G_OBJECT(gtktext),
1202 G_CALLBACK(entry_insert_cb),
1204 g_signal_handlers_block_by_func(G_OBJECT(gtktext),
1205 G_CALLBACK(entry_delete_cb),
1208 gtk_text_buffer_get_iter_at_offset(textbuf, &startiter,
1209 gtkaspell->start_pos);
1210 gtk_text_buffer_get_iter_at_offset(textbuf, &enditer,
1211 gtkaspell->end_pos);
1212 g_signal_emit_by_name(G_OBJECT(textbuf), "delete-range",
1213 &startiter, &enditer, gtkaspell);
1214 g_signal_emit_by_name(G_OBJECT(textbuf), "insert-text",
1215 &startiter, newword, newlen, gtkaspell);
1217 g_signal_handlers_unblock_by_func(G_OBJECT(gtktext),
1218 G_CALLBACK(entry_insert_cb),
1220 g_signal_handlers_unblock_by_func(G_OBJECT(gtktext),
1221 G_CALLBACK(entry_delete_cb),
1224 /* Put the point and the position where we clicked with the mouse
1225 * It seems to be a hack, as I must thaw,freeze,thaw the widget
1226 * to let it update correctly the word insertion and then the
1227 * point & position position. If not, SEGV after the first replacement
1228 * If the new word ends before point, put the point at its end.
1231 if (origpos - gtkaspell->start_pos < oldlen &&
1232 origpos - gtkaspell->start_pos >= 0) {
1233 /* Original point was in the word.
1234 * Let it there unless point is going to be outside of the word
1236 if (origpos - gtkaspell->start_pos >= newlen) {
1237 pos = gtkaspell->start_pos + newlen;
1240 else if (origpos >= gtkaspell->end_pos) {
1241 /* move the position according to the change of length */
1242 pos = origpos + newlen - oldlen;
1245 gtkaspell->end_pos = gtkaspell->start_pos + strlen(newword); /* FIXME: multibyte characters? */
1247 if (get_textview_buffer_charcount(gtktext) < pos)
1248 pos = get_textview_buffer_charcount(gtktext);
1249 gtkaspell->orig_pos = pos;
1251 set_textview_buffer_offset(gtktext, gtkaspell->orig_pos);
1254 /* Accept this word for this session */
1255 static void add_word_to_session_cb(GtkWidget *w, gpointer data)
1258 GtkTextView *gtktext;
1259 GtkAspell *gtkaspell = (GtkAspell *) data;
1260 gtktext = gtkaspell->gtktext;
1262 pos = get_textview_buffer_offset(gtktext);
1264 enchant_dict_add_to_session(gtkaspell->gtkaspeller->speller, gtkaspell->theword, strlen(gtkaspell->theword));
1266 check_at(gtkaspell, gtkaspell->start_pos);
1268 gtk_menu_shell_deactivate(GTK_MENU_SHELL(GTK_WIDGET(w)->parent));
1270 set_point_continue(gtkaspell);
1273 /* add_word_to_personal_cb() - add word to personal dict. */
1274 static void add_word_to_personal_cb(GtkWidget *w, gpointer data)
1276 GtkAspell *gtkaspell = (GtkAspell *) data;
1278 enchant_dict_add_to_pwl(gtkaspell->gtkaspeller->speller, gtkaspell->theword, strlen(gtkaspell->theword));
1280 check_at(gtkaspell, gtkaspell->start_pos);
1282 gtk_menu_shell_deactivate(GTK_MENU_SHELL(GTK_WIDGET(w)->parent));
1283 set_point_continue(gtkaspell);
1286 static void check_with_alternate_cb(GtkWidget *w, gpointer data)
1288 GtkAspell *gtkaspell = (GtkAspell *) data;
1291 gtk_menu_shell_deactivate(GTK_MENU_SHELL(GTK_WIDGET(w)->parent));
1293 use_alternate_dict(gtkaspell);
1294 misspelled = check_at(gtkaspell, gtkaspell->start_pos);
1296 if (!gtkaspell->continue_check) {
1298 gtkaspell->misspelled = misspelled;
1300 if (gtkaspell->misspelled) {
1303 misspelled_suggest(gtkaspell, gtkaspell->theword);
1305 set_textview_buffer_offset(gtkaspell->gtktext,
1306 gtkaspell->end_pos);
1308 list = make_sug_menu(gtkaspell);
1309 menu = gtk_menu_new();
1310 for (cur = list; cur; cur = cur->next)
1311 gtk_menu_shell_append(GTK_MENU_SHELL(menu), GTK_WIDGET(cur->data));
1313 gtk_menu_popup(GTK_MENU(menu), NULL, NULL,
1314 set_menu_pos, gtkaspell, 0,
1316 g_signal_connect(G_OBJECT(menu), "deactivate",
1317 G_CALLBACK(destroy_menu),
1319 g_signal_connect(G_OBJECT(menu),
1321 G_CALLBACK(aspell_key_pressed), gtkaspell);
1325 gtkaspell->orig_pos = gtkaspell->start_pos;
1327 set_point_continue(gtkaspell);
1330 static gboolean replace_key_pressed(GtkWidget *widget,
1332 GtkAspell *gtkaspell)
1334 if (event && event->keyval == GDK_Escape) {
1335 gtk_widget_destroy(widget);
1337 } else if (event && event->keyval == GDK_Return) {
1338 replace_with_supplied_word_cb(widget, gtkaspell);
1344 static void replace_with_create_dialog_cb(GtkWidget *w, gpointer data)
1346 static PangoFontDescription *font_desc;
1353 GtkWidget *ok_button;
1354 GtkWidget *cancel_button;
1355 GtkWidget *confirm_area;
1357 gchar *utf8buf, *thelabel;
1359 GtkAspell *gtkaspell = (GtkAspell *) data;
1361 gdk_window_get_origin((GTK_WIDGET(w)->parent)->window, &xx, &yy);
1363 gtk_menu_shell_deactivate(GTK_MENU_SHELL(GTK_WIDGET(w)->parent));
1365 dialog = gtk_dialog_new();
1367 gtk_window_set_resizable(GTK_WINDOW(dialog), FALSE);
1368 gtk_window_set_title(GTK_WINDOW(dialog),_("Replace unknown word"));
1369 gtk_dialog_set_has_separator (GTK_DIALOG (dialog), FALSE);
1370 gtk_window_move(GTK_WINDOW(dialog), xx, yy);
1372 g_signal_connect_swapped(G_OBJECT(dialog), "destroy",
1373 G_CALLBACK(gtk_widget_destroy),
1376 gtk_box_set_spacing (GTK_BOX (GTK_DIALOG (dialog)->vbox), 14);
1377 hbox = gtk_hbox_new (FALSE, 12);
1378 gtk_container_set_border_width (GTK_CONTAINER (hbox), 5);
1379 gtk_widget_show (hbox);
1380 gtk_box_pack_start (GTK_BOX (GTK_DIALOG (dialog)->vbox), hbox,
1383 utf8buf = g_strdup(gtkaspell->theword);
1385 thelabel = g_strdup_printf(_("<span weight=\"bold\" "
1386 "size=\"larger\">Replace \"%s\" with: </span>"),
1388 /* for title label */
1389 w_hbox = gtk_hbox_new(FALSE, 0);
1391 icon = gtk_image_new_from_stock(GTK_STOCK_DIALOG_QUESTION,
1392 GTK_ICON_SIZE_DIALOG);
1393 gtk_misc_set_alignment (GTK_MISC (icon), 0.5, 0.0);
1394 gtk_box_pack_start (GTK_BOX (hbox), icon, FALSE, FALSE, 0);
1396 vbox = gtk_vbox_new (FALSE, 12);
1397 gtk_box_pack_start (GTK_BOX (hbox), vbox, TRUE, TRUE, 0);
1398 gtk_widget_show (vbox);
1400 label = gtk_label_new(thelabel);
1401 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
1402 gtk_label_set_justify(GTK_LABEL(label), GTK_JUSTIFY_LEFT);
1403 gtk_label_set_use_markup (GTK_LABEL (label), TRUE);
1404 gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 0);
1405 gtk_label_set_line_wrap(GTK_LABEL(label), TRUE);
1409 size = pango_font_description_get_size
1410 (label->style->font_desc);
1411 font_desc = pango_font_description_new();
1412 pango_font_description_set_weight
1413 (font_desc, PANGO_WEIGHT_BOLD);
1414 pango_font_description_set_size
1415 (font_desc, size * PANGO_SCALE_LARGE);
1418 gtk_widget_modify_font(label, font_desc);
1421 entry = gtk_entry_new();
1422 gtkaspell->replace_entry = entry;
1423 gtk_entry_set_text(GTK_ENTRY(entry), utf8buf);
1424 gtk_editable_select_region(GTK_EDITABLE(entry), 0, -1);
1425 g_signal_connect(G_OBJECT(dialog),
1427 G_CALLBACK(replace_key_pressed), gtkaspell);
1428 gtk_box_pack_start(GTK_BOX(vbox), entry, FALSE, FALSE, 0);
1431 label = gtk_label_new(_("Holding down Control key while pressing "
1432 "Enter\nwill learn from mistake.\n"));
1433 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
1434 gtk_label_set_justify(GTK_LABEL(label), GTK_JUSTIFY_LEFT);
1435 gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 0);
1436 gtk_widget_show(label);
1438 hbox = gtk_hbox_new(TRUE, 0);
1440 gtkut_stock_button_set_create(&confirm_area,
1441 &cancel_button, GTK_STOCK_CANCEL,
1442 &ok_button, GTK_STOCK_OK,
1445 gtk_box_pack_end(GTK_BOX(GTK_DIALOG(dialog)->action_area),
1446 confirm_area, FALSE, FALSE, 0);
1447 gtk_container_set_border_width(GTK_CONTAINER(confirm_area), 5);
1449 g_signal_connect(G_OBJECT(ok_button), "clicked",
1450 G_CALLBACK(replace_with_supplied_word_cb),
1452 g_signal_connect_swapped(G_OBJECT(ok_button), "clicked",
1453 G_CALLBACK(gtk_widget_destroy),
1456 g_signal_connect_swapped(G_OBJECT(cancel_button), "clicked",
1457 G_CALLBACK(gtk_widget_destroy),
1460 gtk_widget_grab_focus(entry);
1462 gtk_window_set_modal(GTK_WINDOW(dialog), TRUE);
1464 gtk_widget_show_all(dialog);
1467 static void gtkaspell_uncheck_all(GtkAspell * gtkaspell)
1469 GtkTextView *gtktext;
1470 GtkTextBuffer *buffer;
1471 GtkTextIter startiter, enditer;
1473 gtktext = gtkaspell->gtktext;
1475 buffer = gtk_text_view_get_buffer(gtktext);
1476 gtk_text_buffer_get_iter_at_offset(buffer, &startiter, 0);
1477 gtk_text_buffer_get_iter_at_offset(buffer, &enditer,
1478 get_textview_buffer_charcount(gtktext)-1);
1479 gtk_text_buffer_remove_tag_by_name(buffer, "misspelled",
1480 &startiter, &enditer);
1483 static void toggle_check_while_typing_cb(GtkWidget *w, gpointer data)
1485 GtkAspell *gtkaspell = (GtkAspell *) data;
1487 gtkaspell->check_while_typing = gtkaspell->check_while_typing == FALSE;
1489 if (!gtkaspell->check_while_typing)
1490 gtkaspell_uncheck_all(gtkaspell);
1491 if (gtkaspell->menu_changed_cb)
1492 gtkaspell->menu_changed_cb(gtkaspell->menu_changed_data);
1495 static GSList *create_empty_dictionary_list(void)
1497 GSList *list = NULL;
1500 dict = g_new0(Dictionary, 1);
1501 dict->fullname = g_strdup(_("None"));
1502 dict->dictname = NULL;
1504 return g_slist_append(list, dict);
1507 static void list_dict_cb(const char * const lang_tag,
1508 const char * const provider_name,
1509 const char * const provider_desc,
1510 const char * const provider_file,
1513 GSList **list = (GSList **)data;
1514 Dictionary *dict = g_new0(Dictionary, 1);
1515 dict->fullname = g_strdup(lang_tag);
1516 dict->dictname = g_strdup(lang_tag);
1518 if (g_slist_find_custom(*list, dict,
1519 (GCompareFunc) compare_dict) == NULL) {
1520 debug_print("Aspell: found dictionary %s %s\n", dict->fullname,
1522 *list = g_slist_insert_sorted(*list, dict,
1523 (GCompareFunc) compare_dict);
1525 dictionary_delete(dict);
1529 /* gtkaspell_get_dictionary_list() - returns list of dictionary names */
1530 static GSList *gtkaspell_get_dictionary_list(gint refresh)
1533 EnchantBroker *broker;
1535 if (!gtkaspellcheckers)
1536 gtkaspell_checkers_init();
1538 if (gtkaspellcheckers->dictionary_list && !refresh)
1539 return gtkaspellcheckers->dictionary_list;
1541 gtkaspell_free_dictionary_list(
1542 gtkaspellcheckers->dictionary_list);
1545 broker = enchant_broker_init();
1547 enchant_broker_list_dicts(broker, list_dict_cb, &list);
1549 enchant_broker_free(broker);
1553 debug_print("Aspell: error when searching for dictionaries: "
1554 "No dictionary found.\n");
1555 list = create_empty_dictionary_list();
1558 gtkaspellcheckers->dictionary_list = list;
1563 static void gtkaspell_free_dictionary_list(GSList *list)
1567 for (walk = list; walk != NULL; walk = g_slist_next(walk))
1569 dict = (Dictionary *) walk->data;
1570 dictionary_delete(dict);
1575 GtkTreeModel *gtkaspell_dictionary_store_new_with_refresh(gboolean refresh)
1577 GSList *dict_list, *tmp;
1578 GtkListStore *store;
1582 dict_list = gtkaspell_get_dictionary_list(refresh);
1583 g_return_val_if_fail(dict_list, NULL);
1585 store = gtk_list_store_new(SET_GTKASPELL_SIZE,
1590 for (tmp = dict_list; tmp != NULL; tmp = g_slist_next(tmp)) {
1591 dict = (Dictionary *) tmp->data;
1593 gtk_list_store_append(store, &iter);
1594 gtk_list_store_set(store, &iter,
1595 SET_GTKASPELL_NAME, dict->dictname,
1596 SET_GTKASPELL_FULLNAME, dict->fullname,
1600 return GTK_TREE_MODEL(store);
1603 GtkTreeModel *gtkaspell_dictionary_store_new(void)
1605 return gtkaspell_dictionary_store_new_with_refresh
1609 GtkWidget *gtkaspell_dictionary_combo_new(const gboolean refresh)
1612 GtkCellRenderer *renderer;
1614 combo = gtk_combo_box_new_with_model(
1615 gtkaspell_dictionary_store_new_with_refresh(refresh));
1616 gtk_combo_box_set_active(GTK_COMBO_BOX(combo), 0);
1617 gtk_widget_show(combo);
1619 renderer = gtk_cell_renderer_text_new();
1620 gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(combo), renderer, TRUE);
1621 gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(combo),renderer,
1622 "text", SET_GTKASPELL_NAME, NULL);
1627 gchar *gtkaspell_get_dictionary_menu_active_item(GtkComboBox *combo)
1630 GtkTreeModel *model;
1631 gchar *dict_fullname = NULL;
1633 g_return_val_if_fail(GTK_IS_COMBO_BOX(combo), NULL);
1634 g_return_val_if_fail(gtk_combo_box_get_active_iter(combo, &iter), NULL);
1636 model = gtk_combo_box_get_model(combo);
1640 gtk_tree_model_get(model, &iter,
1641 SET_GTKASPELL_FULLNAME, &dict_fullname,
1644 return dict_fullname;
1647 gint gtkaspell_set_dictionary_menu_active_item(GtkComboBox *combo,
1648 const gchar *dictionary)
1650 GtkTreeModel *model;
1652 gchar *dict_name = NULL;
1654 g_return_val_if_fail(combo != NULL, 0);
1655 g_return_val_if_fail(dictionary != NULL, 0);
1656 g_return_val_if_fail(GTK_IS_COMBO_BOX(combo), 0);
1658 if((model = gtk_combo_box_get_model(combo)) == NULL)
1660 if((gtk_tree_model_get_iter_first(model, &iter)) == FALSE)
1664 gtk_tree_model_get(model, &iter,
1665 SET_GTKASPELL_FULLNAME, &dict_name,
1668 if ((dict_name != NULL) && !strcmp2(dict_name, dictionary)) {
1669 gtk_combo_box_set_active_iter(combo, &iter);
1676 } while ((gtk_tree_model_iter_next(model, &iter)) == TRUE);
1681 static void use_alternate_dict(GtkAspell *gtkaspell)
1685 tmp = gtkaspell->gtkaspeller;
1686 gtkaspell->gtkaspeller = gtkaspell->alternate_speller;
1687 gtkaspell->alternate_speller = tmp;
1690 static void destroy_menu(GtkWidget *widget,
1691 gpointer user_data) {
1693 GtkAspell *gtkaspell = (GtkAspell *)user_data;
1695 if (gtkaspell->accel_group) {
1696 gtk_window_remove_accel_group(GTK_WINDOW(gtkaspell->parent_window),
1697 gtkaspell->accel_group);
1698 gtkaspell->accel_group = NULL;
1702 static gboolean aspell_key_pressed(GtkWidget *widget,
1704 GtkAspell *gtkaspell)
1706 if (event && (isascii(event->keyval) || event->keyval == GDK_Return)) {
1707 gtk_accel_groups_activate(
1708 G_OBJECT(gtkaspell->parent_window),
1709 event->keyval, event->state);
1710 } else if (event && event->keyval == GDK_Escape) {
1711 destroy_menu(NULL, gtkaspell);
1716 /* make_sug_menu() - Add menus to accept this word for this session
1717 * and to add it to personal dictionary
1719 static GSList *make_sug_menu(GtkAspell *gtkaspell)
1723 GtkTextView *gtktext;
1724 GtkAccelGroup *accel;
1725 GList *l = gtkaspell->suggestions_list;
1727 GSList *list = NULL;
1728 gtktext = gtkaspell->gtktext;
1733 accel = gtk_accel_group_new();
1735 if (gtkaspell->accel_group) {
1736 gtk_window_remove_accel_group(GTK_WINDOW(gtkaspell->parent_window),
1737 gtkaspell->accel_group);
1738 gtkaspell->accel_group = NULL;
1741 utf8buf = g_strdup(l->data);
1742 caption = g_strdup_printf(_("\"%s\" unknown in %s"),
1744 gtkaspell->gtkaspeller->dictionary->dictname);
1745 item = gtk_menu_item_new_with_label(caption);
1747 gtk_widget_show(item);
1748 list = g_slist_append(list, item);
1749 gtk_misc_set_alignment(GTK_MISC(gtk_bin_get_child(GTK_BIN((item)))), 0.5, 0.5);
1752 item = gtk_menu_item_new();
1753 gtk_widget_show(item);
1754 list = g_slist_append(list, item);
1756 item = gtk_menu_item_new_with_label(_("Accept in this session"));
1757 gtk_widget_show(item);
1758 list = g_slist_append(list, item);
1759 g_signal_connect(G_OBJECT(item), "activate",
1760 G_CALLBACK(add_word_to_session_cb),
1762 gtk_widget_add_accelerator(item, "activate", accel, GDK_space,
1764 GTK_ACCEL_LOCKED | GTK_ACCEL_VISIBLE);
1766 item = gtk_menu_item_new_with_label(_("Add to personal dictionary"));
1767 gtk_widget_show(item);
1768 list = g_slist_append(list, item);
1769 g_signal_connect(G_OBJECT(item), "activate",
1770 G_CALLBACK(add_word_to_personal_cb),
1772 gtk_widget_add_accelerator(item, "activate", accel, GDK_Return,
1774 GTK_ACCEL_LOCKED | GTK_ACCEL_VISIBLE);
1776 item = gtk_menu_item_new_with_label(_("Replace with..."));
1777 gtk_widget_show(item);
1778 list = g_slist_append(list, item);
1779 g_signal_connect(G_OBJECT(item), "activate",
1780 G_CALLBACK(replace_with_create_dialog_cb),
1782 gtk_widget_add_accelerator(item, "activate", accel, GDK_R, 0,
1783 GTK_ACCEL_LOCKED | GTK_ACCEL_VISIBLE);
1784 gtk_widget_add_accelerator(item, "activate", accel, GDK_R,
1788 if (gtkaspell->use_alternate && gtkaspell->alternate_speller) {
1789 caption = g_strdup_printf(_("Check with %s"),
1790 gtkaspell->alternate_speller->dictionary->dictname);
1791 item = gtk_menu_item_new_with_label(caption);
1793 gtk_widget_show(item);
1794 list = g_slist_append(list, item);
1795 g_signal_connect(G_OBJECT(item), "activate",
1796 G_CALLBACK(check_with_alternate_cb),
1798 gtk_widget_add_accelerator(item, "activate", accel, GDK_X, 0,
1799 GTK_ACCEL_LOCKED | GTK_ACCEL_VISIBLE);
1800 gtk_widget_add_accelerator(item, "activate", accel, GDK_X,
1805 item = gtk_menu_item_new();
1806 gtk_widget_show(item);
1807 list = g_slist_append(list, item);
1811 item = gtk_menu_item_new_with_label(_("(no suggestions)"));
1812 gtk_widget_show(item);
1813 list = g_slist_append(list, item);
1815 GtkWidget *curmenu = NULL;
1819 if (count == MENUCOUNT) {
1822 item = gtk_menu_item_new_with_label(_("More..."));
1823 gtk_widget_show(item);
1825 gtk_menu_shell_append(GTK_MENU_SHELL(curmenu), item);
1827 list = g_slist_append(list, item);
1829 curmenu = gtk_menu_new();
1830 gtk_menu_item_set_submenu(GTK_MENU_ITEM(item),
1834 utf8buf = g_strdup(l->data);
1836 item = gtk_menu_item_new_with_label(utf8buf);
1838 gtk_widget_show(item);
1839 if (curmenu == NULL) {
1840 list = g_slist_append(list, item);
1842 gtk_menu_shell_append(GTK_MENU_SHELL(curmenu), item);
1844 g_signal_connect(G_OBJECT(item), "activate",
1845 G_CALLBACK(replace_word_cb),
1848 if (curmenu == NULL && count < MENUCOUNT) {
1849 gtk_widget_add_accelerator(item, "activate",
1854 gtk_widget_add_accelerator(item, "activate",
1863 } while ((l = l->next) != NULL);
1866 gtk_window_add_accel_group
1867 (GTK_WINDOW(gtkaspell->parent_window),
1869 gtkaspell->accel_group = accel;
1874 static GSList *populate_submenu(GtkAspell *gtkaspell)
1876 GtkWidget *item, *submenu;
1878 GtkAspeller *gtkaspeller = NULL;
1879 GSList *list = NULL;
1884 gtkaspeller = gtkaspell->gtkaspeller;
1885 dictname = g_strdup_printf(_("Dictionary: %s"),
1886 gtkaspeller->dictionary->dictname);
1887 item = gtk_menu_item_new_with_label(dictname);
1888 gtk_misc_set_alignment(GTK_MISC(gtk_bin_get_child(GTK_BIN((item)))), 0.5, 0.5);
1890 gtk_widget_show(item);
1891 list = g_slist_append(list, item);
1893 item = gtk_menu_item_new();
1894 gtk_widget_show(item);
1895 list = g_slist_append(list, item);
1897 if (gtkaspell->use_alternate && gtkaspell->alternate_speller) {
1898 dictname = g_strdup_printf(_("Use alternate (%s)"),
1899 gtkaspell->alternate_speller->dictionary->dictname);
1900 item = gtk_menu_item_new_with_label(dictname);
1902 g_signal_connect(G_OBJECT(item), "activate",
1903 G_CALLBACK(switch_to_alternate_cb),
1905 gtk_widget_show(item);
1906 list = g_slist_append(list, item);
1909 item = gtk_check_menu_item_new_with_label(_("Use both dictionaries"));
1910 if (gtkaspell->use_both_dicts) {
1911 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(item), TRUE);
1913 g_signal_connect(G_OBJECT(item), "activate",
1914 G_CALLBACK(set_use_both_cb),
1916 gtk_widget_show(item);
1917 list = g_slist_append(list, item);
1919 item = gtk_menu_item_new();
1920 gtk_widget_show(item);
1921 list = g_slist_append(list, item);
1923 item = gtk_check_menu_item_new_with_label(_("Check while typing"));
1924 if (gtkaspell->check_while_typing)
1925 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(item), TRUE);
1927 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(item), FALSE);
1928 g_signal_connect(G_OBJECT(item), "activate",
1929 G_CALLBACK(toggle_check_while_typing_cb),
1931 gtk_widget_show(item);
1932 list = g_slist_append(list, item);
1934 item = gtk_menu_item_new();
1935 gtk_widget_show(item);
1936 list = g_slist_append(list, item);
1938 submenu = gtk_menu_new();
1939 item = gtk_menu_item_new_with_label(_("Change dictionary"));
1940 gtk_menu_item_set_submenu(GTK_MENU_ITEM(item),submenu);
1941 gtk_widget_show(item);
1942 list = g_slist_append(list, item);
1945 if (gtkaspellcheckers->dictionary_list == NULL)
1946 gtkaspell_get_dictionary_list(FALSE);
1948 GtkWidget * curmenu = submenu;
1952 tmp = gtkaspellcheckers->dictionary_list;
1954 for (tmp = gtkaspellcheckers->dictionary_list; tmp != NULL;
1955 tmp = g_slist_next(tmp)) {
1956 if (count == MENUCOUNT) {
1959 newmenu = gtk_menu_new();
1960 item = gtk_menu_item_new_with_label(_("More..."));
1961 gtk_menu_item_set_submenu(GTK_MENU_ITEM(item),
1964 gtk_menu_shell_append(GTK_MENU_SHELL(curmenu), item);
1965 gtk_widget_show(item);
1969 dict = (Dictionary *) tmp->data;
1970 item = gtk_check_menu_item_new_with_label(dict->dictname);
1971 g_object_set_data(G_OBJECT(item), "dict_name",
1973 if (strcmp2(dict->fullname,
1974 gtkaspell->gtkaspeller->dictionary->fullname))
1975 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(item), FALSE);
1977 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(item), TRUE);
1978 gtk_widget_set_sensitive(GTK_WIDGET(item),
1981 g_signal_connect(G_OBJECT(item), "activate",
1982 G_CALLBACK(change_dict_cb),
1984 gtk_widget_show(item);
1985 gtk_menu_shell_append(GTK_MENU_SHELL(curmenu), item);
1993 GSList *gtkaspell_make_config_menu(GtkAspell *gtkaspell)
1995 return populate_submenu(gtkaspell);
1998 static void set_menu_pos(GtkMenu *menu, gint *x, gint *y,
1999 gboolean *push_in, gpointer data)
2001 GtkAspell *gtkaspell = (GtkAspell *) data;
2002 gint xx = 0, yy = 0;
2005 GtkTextView *text = GTK_TEXT_VIEW(gtkaspell->gtktext);
2006 GtkTextBuffer *textbuf;
2011 textbuf = gtk_text_view_get_buffer(gtkaspell->gtktext);
2012 gtk_text_buffer_get_iter_at_mark(textbuf, &iter,
2013 gtk_text_buffer_get_insert(textbuf));
2014 gtk_text_view_get_iter_location(gtkaspell->gtktext, &iter, &rect);
2015 gtk_text_view_buffer_to_window_coords(text, GTK_TEXT_WINDOW_TEXT,
2019 gdk_window_get_origin(GTK_WIDGET(gtkaspell->gtktext)->window, &xx, &yy);
2021 sx = gdk_screen_width();
2022 sy = gdk_screen_height();
2024 gtk_widget_get_child_requisition(GTK_WIDGET(menu), &r);
2029 *x = rect.x + xx + 8;
2031 *y = rect.y + rect.height + yy;
2039 /* change the current dictionary of gtkaspell
2040 - if always_set_alt_dict is set, the alternate dict is unconditionally set to the former
2041 current dictionary (common use: from menu callbacks)
2042 - if always_set_alt_dict is NOT set, the alternate dict will be set to the former
2043 current dictionary only if there is no alternate dictionary already set
2044 (this is when we need to set the current dictionary then the alternate one
2045 when creating a compose window, from the account and folder settings)
2047 gboolean gtkaspell_change_dict(GtkAspell *gtkaspell, const gchar *dictionary,
2048 gboolean always_set_alt_dict)
2051 GtkAspeller *gtkaspeller;
2053 g_return_val_if_fail(gtkaspell, FALSE);
2054 g_return_val_if_fail(dictionary, FALSE);
2056 dict = g_new0(Dictionary, 1);
2058 if (strrchr(dictionary, '/')) {
2059 dict->fullname = g_strdup(strrchr(dictionary, '/')+1);
2060 dict->dictname = g_strdup(strrchr(dictionary, '/')+1);
2062 dict->fullname = g_strdup(dictionary);
2063 dict->dictname = g_strdup(dictionary);
2066 if (strchr(dict->fullname, '-')) {
2067 *(strchr(dict->fullname, '-')) = '\0';
2068 *(strchr(dict->dictname, '-')) = '\0';
2071 if (!dict->fullname || !(*dict->fullname)) {
2072 dictionary_delete(dict);
2075 gtkaspeller = gtkaspeller_new(dict);
2078 alertpanel_warning(_("The spell checker could not change dictionary.\n%s"),
2079 gtkaspellcheckers->error_message);
2081 if (gtkaspell->use_alternate) {
2082 if (gtkaspell->alternate_speller) {
2083 if (always_set_alt_dict) {
2084 gtkaspeller_delete(gtkaspell->alternate_speller);
2085 gtkaspell->alternate_speller = gtkaspell->gtkaspeller;
2087 gtkaspeller_delete(gtkaspell->gtkaspeller);
2089 /* should never be reached as the dicts are always set
2090 to a default value */
2091 gtkaspell->alternate_speller = gtkaspell->gtkaspeller;
2093 gtkaspeller_delete(gtkaspell->gtkaspeller);
2095 gtkaspell->gtkaspeller = gtkaspeller;
2098 dictionary_delete(dict);
2103 /* change the alternate dictionary of gtkaspell (doesn't affect the default dictionary) */
2104 gboolean gtkaspell_change_alt_dict(GtkAspell *gtkaspell, const gchar *alt_dictionary)
2107 GtkAspeller *gtkaspeller;
2109 g_return_val_if_fail(gtkaspell, FALSE);
2110 g_return_val_if_fail(alt_dictionary, FALSE);
2112 dict = g_new0(Dictionary, 1);
2113 if (strrchr(alt_dictionary, '/')) {
2114 dict->fullname = g_strdup(strrchr(alt_dictionary, '/')+1);
2115 dict->dictname = g_strdup(strrchr(alt_dictionary, '/')+1);
2117 dict->fullname = g_strdup(alt_dictionary);
2118 dict->dictname = g_strdup(alt_dictionary);
2121 if (strchr(dict->fullname, '-')) {
2122 *(strchr(dict->fullname, '-')) = '\0';
2123 *(strchr(dict->dictname, '-')) = '\0';
2126 if (!dict->fullname || !(*dict->fullname)) {
2127 dictionary_delete(dict);
2131 gtkaspeller = gtkaspeller_new(dict);
2134 alertpanel_warning(_("The spell checker could not change the alternate dictionary.\n%s"),
2135 gtkaspellcheckers->error_message);
2137 if (gtkaspell->alternate_speller)
2138 gtkaspeller_delete(gtkaspell->alternate_speller);
2139 gtkaspell->alternate_speller = gtkaspeller;
2142 dictionary_delete(dict);
2147 /* Menu call backs */
2149 /* change_dict_cb() - Menu callback : change dict */
2150 static void change_dict_cb(GtkWidget *w, GtkAspell *gtkaspell)
2154 fullname = (gchar *) g_object_get_data(G_OBJECT(w), "dict_name");
2156 if (!strcmp2(fullname, _("None")))
2159 gtkaspell_change_dict(gtkaspell, fullname, TRUE);
2160 if (gtkaspell->recheck_when_changing_dict) {
2161 gtkaspell_highlight_all(gtkaspell);
2163 if (gtkaspell->menu_changed_cb)
2164 gtkaspell->menu_changed_cb(gtkaspell->menu_changed_data);
2167 static void switch_to_alternate_cb(GtkWidget *w,
2170 GtkAspell *gtkaspell = (GtkAspell *) data;
2171 use_alternate_dict(gtkaspell);
2172 if (gtkaspell->recheck_when_changing_dict) {
2173 gtkaspell_highlight_all(gtkaspell);
2175 if (gtkaspell->menu_changed_cb)
2176 gtkaspell->menu_changed_cb(gtkaspell->menu_changed_data);
2179 /* Misc. helper functions */
2181 static void set_point_continue(GtkAspell *gtkaspell)
2183 GtkTextView *gtktext;
2185 gtktext = gtkaspell->gtktext;
2187 set_textview_buffer_offset(gtktext, gtkaspell->orig_pos);
2189 if (gtkaspell->continue_check)
2190 gtkaspell->continue_check((gpointer *) gtkaspell);
2193 static void allocate_color(GtkAspell *gtkaspell, gint rgbvalue)
2195 GtkTextBuffer *buffer = gtk_text_view_get_buffer(gtkaspell->gtktext);
2196 GdkColor *color = &(gtkaspell->highlight);
2198 /* Shameless copy from Sylpheed's gtkutils.c */
2200 color->red = (int) (((gdouble)((rgbvalue & 0xff0000) >> 16) / 255.0)
2202 color->green = (int) (((gdouble)((rgbvalue & 0x00ff00) >> 8) / 255.0)
2204 color->blue = (int) (((gdouble) (rgbvalue & 0x0000ff) / 255.0)
2208 gtk_text_buffer_create_tag(buffer, "misspelled",
2209 "foreground-gdk", color, NULL);
2211 gtk_text_buffer_create_tag(buffer, "misspelled",
2212 "underline", PANGO_UNDERLINE_ERROR, NULL);
2216 static void change_color(GtkAspell * gtkaspell,
2217 gint start, gint end,
2221 GtkTextView *gtktext;
2222 GtkTextBuffer *buffer;
2223 GtkTextIter startiter, enditer;
2228 gtktext = gtkaspell->gtktext;
2230 buffer = gtk_text_view_get_buffer(gtktext);
2231 gtk_text_buffer_get_iter_at_offset(buffer, &startiter, start);
2232 gtk_text_buffer_get_iter_at_offset(buffer, &enditer, end);
2234 gtk_text_buffer_apply_tag_by_name(buffer, "misspelled",
2235 &startiter, &enditer);
2237 gtk_text_iter_forward_char(&enditer);
2238 gtk_text_buffer_remove_tag_by_name(buffer, "misspelled",
2239 &startiter, &enditer);
2243 /* compare_dict () - compare 2 dict names */
2244 static gint compare_dict(Dictionary *a, Dictionary *b)
2246 guint aparts = 0, bparts = 0;
2249 for (i=0; i < strlen(a->dictname); i++)
2250 if (a->dictname[i] == '-')
2252 for (i=0; i < strlen(b->dictname); i++)
2253 if (b->dictname[i] == '-')
2256 if (aparts != bparts)
2257 return (aparts < bparts) ? -1 : +1;
2260 compare = strcmp2(a->dictname, b->dictname);
2262 compare = strcmp2(a->fullname, b->fullname);
2267 static void dictionary_delete(Dictionary *dict)
2269 g_free(dict->fullname);
2270 g_free(dict->dictname);
2274 static Dictionary *dictionary_dup(const Dictionary *dict)
2278 dict2 = g_new(Dictionary, 1);
2280 dict2->fullname = g_strdup(dict->fullname);
2281 dict2->dictname = g_strdup(dict->dictname);
2286 static void free_suggestions_list(GtkAspell *gtkaspell)
2290 for (list = gtkaspell->suggestions_list; list != NULL;
2294 g_list_free(gtkaspell->suggestions_list);
2296 gtkaspell->max_sug = -1;
2297 gtkaspell->suggestions_list = NULL;
2300 static void reset_theword_data(GtkAspell *gtkaspell)
2302 gtkaspell->start_pos = 0;
2303 gtkaspell->end_pos = 0;
2304 gtkaspell->theword[0] = 0;
2305 gtkaspell->max_sug = -1;
2307 free_suggestions_list(gtkaspell);
2310 static void free_checkers(gpointer elt, gpointer data)
2312 GtkAspeller *gtkaspeller = elt;
2314 g_return_if_fail(gtkaspeller);
2316 gtkaspeller_real_delete(gtkaspeller);
2319 static gint find_gtkaspeller(gconstpointer aa, gconstpointer bb)
2321 Dictionary *a = ((GtkAspeller *) aa)->dictionary;
2322 Dictionary *b = ((GtkAspeller *) bb)->dictionary;
2324 if (a && b && a->fullname && b->fullname)
2325 return strcmp(a->fullname, b->fullname);
2330 gchar *gtkaspell_get_default_dictionary(GtkAspell *gtkaspell)
2332 if (gtkaspell && gtkaspell->gtkaspeller &&
2333 gtkaspell->gtkaspeller->dictionary)
2334 return gtkaspell->gtkaspeller->dictionary->dictname;