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 <gtk/gtkoptionmenu.h>
52 #include <gtk/gtkmenu.h>
53 #include <gtk/gtkmenuitem.h>
54 #include <gdk/gdkkeysyms.h>
60 #include "alertpanel.h"
61 #include "gtkaspell.h"
62 #include "gtk/gtkutils.h"
64 #define ASPELL_FASTMODE 1
65 #define ASPELL_NORMALMODE 2
66 #define ASPELL_BADSPELLERMODE 3
68 #define GTKASPELLWORDSIZE 1024
70 /* size of the text buffer used in various word-processing routines. */
73 /* number of suggestions to display on each menu. */
76 /* 'config' must be defined as a 'AspellConfig *' */
77 #define RETURN_FALSE_IF_CONFIG_ERROR() \
79 if (aspell_config_error_number(config) != 0) { \
80 gtkaspellcheckers->error_message = g_strdup(aspell_config_error_message(config)); \
85 #define CONFIG_REPLACE_RETURN_FALSE_IF_FAIL(option, value) { \
86 aspell_config_replace(config, option, value); \
87 RETURN_FALSE_IF_CONFIG_ERROR(); \
90 typedef struct _GtkAspellCheckers {
92 GSList *dictionary_list;
96 typedef struct _Dictionary {
102 typedef struct _GtkAspeller {
103 Dictionary *dictionary;
105 AspellConfig *config;
106 AspellSpeller *checker;
109 typedef void (*ContCheckFunc) (gpointer *gtkaspell);
113 GtkAspeller *gtkaspeller;
114 GtkAspeller *alternate_speller;
115 gchar *dictionary_path;
116 gchar theword[GTKASPELLWORDSIZE];
122 gboolean check_while_typing;
123 gboolean recheck_when_changing_dict;
124 gboolean use_alternate;
125 gboolean use_both_dicts;
127 ContCheckFunc continue_check;
129 GtkWidget *replace_entry;
130 GtkWidget *parent_window;
132 gint default_sug_mode;
134 GList *suggestions_list;
136 GtkTextView *gtktext;
138 GtkAccelGroup *accel_group;
139 void (*menu_changed_cb)(void *data);
140 void *menu_changed_data;
143 typedef AspellConfig GtkAspellConfig;
145 /******************************************************************************/
147 static GtkAspellCheckers *gtkaspellcheckers;
149 /* Error message storage */
150 static void gtkaspell_checkers_error_message (gchar *message);
153 static void entry_insert_cb (GtkTextBuffer *textbuf,
157 GtkAspell *gtkaspell);
158 static void entry_delete_cb (GtkTextBuffer *textbuf,
159 GtkTextIter *startiter,
160 GtkTextIter *enditer,
161 GtkAspell *gtkaspell);
162 /*static gint button_press_intercept_cb (GtkTextView *gtktext,
164 GtkAspell *gtkaspell);
166 static void button_press_intercept_cb(GtkTextView *gtktext,
167 GtkMenu *menu, GtkAspell *gtkaspell);
169 /* Checker creation */
170 static GtkAspeller* gtkaspeller_new (Dictionary *dict);
171 static GtkAspeller* gtkaspeller_real_new (Dictionary *dict);
172 static GtkAspeller* gtkaspeller_delete (GtkAspeller *gtkaspeller);
173 static GtkAspeller* gtkaspeller_real_delete (GtkAspeller *gtkaspeller);
175 /* Checker configuration */
176 static gint set_dictionary (AspellConfig *config,
178 static void set_sug_mode_cb (GtkMenuItem *w,
179 GtkAspell *gtkaspell);
180 static void set_use_both_cb (GtkMenuItem *w,
181 GtkAspell *gtkaspell);
182 static void set_real_sug_mode (GtkAspell *gtkaspell,
183 const char *themode);
185 /* Checker actions */
186 static gboolean check_at (GtkAspell *gtkaspell,
188 static gboolean check_next_prev (GtkAspell *gtkaspell,
190 static GList* misspelled_suggest (GtkAspell *gtkaspell,
192 static void add_word_to_session_cb (GtkWidget *w,
194 static void add_word_to_personal_cb (GtkWidget *w,
196 static void replace_with_create_dialog_cb (GtkWidget *w,
198 static void replace_with_supplied_word_cb (GtkWidget *w,
199 GtkAspell *gtkaspell);
200 static void replace_word_cb (GtkWidget *w,
202 static void replace_real_word (GtkAspell *gtkaspell,
204 static void check_with_alternate_cb (GtkWidget *w,
206 static void use_alternate_dict (GtkAspell *gtkaspell);
207 static void toggle_check_while_typing_cb (GtkWidget *w,
211 static GSList* make_sug_menu (GtkAspell *gtkaspell);
212 static GSList * populate_submenu (GtkAspell *gtkaspell);
213 GSList* gtkaspell_make_config_menu (GtkAspell *gtkaspell);
214 static void set_menu_pos (GtkMenu *menu,
219 /* Other menu callbacks */
220 static gboolean aspell_key_pressed (GtkWidget *widget,
222 GtkAspell *gtkaspell);
223 static void change_dict_cb (GtkWidget *w,
224 GtkAspell *gtkaspell);
225 static void switch_to_alternate_cb (GtkWidget *w,
228 /* Misc. helper functions */
229 static void set_point_continue (GtkAspell *gtkaspell);
230 static void continue_check (gpointer *gtkaspell);
231 static gboolean iswordsep (gunichar c);
232 static gunichar get_text_index_whar (GtkAspell *gtkaspell,
234 static gboolean get_word_from_pos (GtkAspell *gtkaspell,
240 static void allocate_color (GtkAspell *gtkaspell,
242 static void change_color (GtkAspell *gtkaspell,
247 static gchar* convert_to_aspell_encoding (const gchar *encoding);
248 static gint compare_dict (Dictionary *a,
250 static void dictionary_delete (Dictionary *dict);
251 static Dictionary * dictionary_dup (const Dictionary *dict);
252 static void free_suggestions_list (GtkAspell *gtkaspell);
253 static void reset_theword_data (GtkAspell *gtkaspell);
254 static void free_checkers (gpointer elt,
256 static gint find_gtkaspeller (gconstpointer aa,
258 /* gtkspellconfig - only one config per session */
259 GtkAspellConfig * gtkaspellconfig;
260 static void destroy_menu(GtkWidget *widget, gpointer user_data);
262 /******************************************************************************/
263 static gint get_textview_buffer_charcount(GtkTextView *view);
265 static void gtkaspell_free_dictionary_list (GSList *list);
266 static GSList* gtkaspell_get_dictionary_list (const char *aspell_path,
269 static void gtkaspell_uncheck_all (GtkAspell *gtkaspell);
271 static gint get_textview_buffer_charcount(GtkTextView *view)
273 GtkTextBuffer *buffer;
275 g_return_val_if_fail(view, 0);
277 buffer = gtk_text_view_get_buffer(view);
278 g_return_val_if_fail(buffer, 0);
280 return gtk_text_buffer_get_char_count(buffer);
282 static gint get_textview_buffer_offset(GtkTextView *view)
284 GtkTextBuffer * buffer;
288 g_return_val_if_fail(view, 0);
290 buffer = gtk_text_view_get_buffer(view);
291 g_return_val_if_fail(buffer, 0);
293 mark = gtk_text_buffer_get_insert(buffer);
294 g_return_val_if_fail(mark, 0);
296 gtk_text_buffer_get_iter_at_mark(buffer, &iter, mark);
298 return gtk_text_iter_get_offset(&iter);
300 static void set_textview_buffer_offset(GtkTextView *view, gint offset)
302 GtkTextBuffer *buffer;
305 g_return_if_fail(view);
307 buffer = gtk_text_view_get_buffer(view);
308 g_return_if_fail(buffer);
310 gtk_text_buffer_get_iter_at_offset(buffer, &iter, offset);
311 gtk_text_buffer_place_cursor(buffer, &iter);
313 /******************************************************************************/
315 void gtkaspell_checkers_init(void)
317 gtkaspellcheckers = g_new(GtkAspellCheckers, 1);
318 gtkaspellcheckers->checkers = NULL;
319 gtkaspellcheckers->dictionary_list = NULL;
320 gtkaspellcheckers->error_message = NULL;
323 void gtkaspell_checkers_quit(void)
328 if (gtkaspellcheckers == NULL)
331 if ((checkers = gtkaspellcheckers->checkers)) {
332 debug_print("Aspell: number of running checkers to delete %d\n",
333 g_slist_length(checkers));
335 g_slist_foreach(checkers, free_checkers, NULL);
336 g_slist_free(checkers);
337 gtkaspellcheckers->checkers = NULL;
340 if ((dict_list = gtkaspellcheckers->dictionary_list)) {
341 debug_print("Aspell: number of dictionaries to delete %d\n",
342 g_slist_length(dict_list));
344 gtkaspell_free_dictionary_list(dict_list);
345 gtkaspellcheckers->dictionary_list = NULL;
348 g_free(gtkaspellcheckers->error_message);
349 gtkaspellcheckers->error_message = NULL;
353 static void gtkaspell_checkers_error_message (gchar *message)
356 if (gtkaspellcheckers->error_message) {
357 tmp = g_strdup_printf("%s\n%s",
358 gtkaspellcheckers->error_message,
361 g_free(gtkaspellcheckers->error_message);
362 gtkaspellcheckers->error_message = tmp;
364 gtkaspellcheckers->error_message = message;
367 const char *gtkaspell_checkers_strerror(void)
369 g_return_val_if_fail(gtkaspellcheckers, "");
370 return gtkaspellcheckers->error_message;
373 void gtkaspell_checkers_reset_error(void)
375 g_return_if_fail(gtkaspellcheckers);
377 g_free(gtkaspellcheckers->error_message);
379 gtkaspellcheckers->error_message = NULL;
382 GtkAspell *gtkaspell_new(const gchar *dictionary_path,
383 const gchar *dictionary,
384 const gchar *alt_dictionary,
385 const gchar *encoding,
386 gint misspelled_color,
387 gboolean check_while_typing,
388 gboolean recheck_when_changing_dict,
389 gboolean use_alternate,
390 gboolean use_both_dicts,
391 GtkTextView *gtktext,
392 GtkWindow *parent_win,
393 void (*spell_menu_cb)(void *data),
397 GtkAspell *gtkaspell;
398 GtkAspeller *gtkaspeller;
399 GtkTextBuffer *buffer;
401 g_return_val_if_fail(gtktext, NULL);
402 g_return_val_if_fail(dictionary && strlen(dictionary) > 0,
405 g_return_val_if_fail(dictionary_path && strlen(dictionary_path) > 0,
408 buffer = gtk_text_view_get_buffer(gtktext);
410 dict = g_new0(Dictionary, 1);
411 dict->fullname = g_strdup(dictionary);
412 dict->encoding = g_strdup(encoding);
414 gtkaspeller = gtkaspeller_new(dict);
415 dictionary_delete(dict);
420 gtkaspell = g_new0(GtkAspell, 1);
422 gtkaspell->dictionary_path = g_strdup(dictionary_path);
424 gtkaspell->gtkaspeller = gtkaspeller;
426 if (use_alternate && alt_dictionary && *alt_dictionary) {
427 Dictionary *alt_dict;
428 GtkAspeller *alt_gtkaspeller;
430 alt_dict = g_new0(Dictionary, 1);
431 alt_dict->fullname = g_strdup(alt_dictionary);
432 alt_dict->encoding = g_strdup(encoding);
434 alt_gtkaspeller = gtkaspeller_new(alt_dict);
435 dictionary_delete(alt_dict);
437 if (!alt_gtkaspeller)
440 gtkaspell->alternate_speller = alt_gtkaspeller;
442 gtkaspell->alternate_speller = NULL;
445 gtkaspell->theword[0] = 0x00;
446 gtkaspell->start_pos = 0;
447 gtkaspell->end_pos = 0;
448 gtkaspell->orig_pos = -1;
449 gtkaspell->end_check_pos = -1;
450 gtkaspell->misspelled = -1;
451 gtkaspell->check_while_typing = check_while_typing;
452 gtkaspell->recheck_when_changing_dict = recheck_when_changing_dict;
453 gtkaspell->continue_check = NULL;
454 gtkaspell->replace_entry = NULL;
455 gtkaspell->gtktext = gtktext;
456 gtkaspell->default_sug_mode = ASPELL_FASTMODE;
457 gtkaspell->max_sug = -1;
458 gtkaspell->suggestions_list = NULL;
459 gtkaspell->use_alternate = use_alternate;
460 gtkaspell->use_both_dicts = use_both_dicts;
461 gtkaspell->parent_window = GTK_WIDGET(parent_win);
462 gtkaspell->menu_changed_cb = spell_menu_cb;
463 gtkaspell->menu_changed_data = data;
465 allocate_color(gtkaspell, misspelled_color);
467 g_signal_connect_after(G_OBJECT(buffer), "insert-text",
468 G_CALLBACK(entry_insert_cb), gtkaspell);
469 g_signal_connect_after(G_OBJECT(buffer), "delete-range",
470 G_CALLBACK(entry_delete_cb), gtkaspell);
471 /*g_signal_connect(G_OBJECT(gtktext), "button-press-event",
472 G_CALLBACK(button_press_intercept_cb),
474 g_signal_connect(G_OBJECT(gtktext), "populate-popup",
475 G_CALLBACK(button_press_intercept_cb), gtkaspell);
477 debug_print("Aspell: created gtkaspell %p\n", gtkaspell);
482 void gtkaspell_delete(GtkAspell *gtkaspell)
484 GtkTextView *gtktext = gtkaspell->gtktext;
486 g_signal_handlers_disconnect_by_func(G_OBJECT(gtktext),
487 G_CALLBACK(entry_insert_cb),
489 g_signal_handlers_disconnect_by_func(G_OBJECT(gtktext),
490 G_CALLBACK(entry_delete_cb),
492 g_signal_handlers_disconnect_by_func(G_OBJECT(gtktext),
493 G_CALLBACK(button_press_intercept_cb),
496 gtkaspell_uncheck_all(gtkaspell);
498 gtkaspeller_delete(gtkaspell->gtkaspeller);
500 if (gtkaspell->alternate_speller)
501 gtkaspeller_delete(gtkaspell->alternate_speller);
503 if (gtkaspell->suggestions_list)
504 free_suggestions_list(gtkaspell);
506 g_free((gchar *)gtkaspell->dictionary_path);
507 gtkaspell->dictionary_path = NULL;
509 debug_print("Aspell: deleting gtkaspell %p\n", gtkaspell);
516 static void entry_insert_cb(GtkTextBuffer *textbuf,
520 GtkAspell *gtkaspell)
524 g_return_if_fail(gtkaspell->gtkaspeller->checker);
526 if (!gtkaspell->check_while_typing)
529 pos = gtk_text_iter_get_offset(iter);
531 if (iswordsep(g_utf8_get_char(newtext))) {
532 /* did we just end a word? */
534 check_at(gtkaspell, pos - 2);
536 /* did we just split a word? */
537 if (pos < gtk_text_buffer_get_char_count(textbuf))
538 check_at(gtkaspell, pos + 1);
540 /* check as they type, *except* if they're typing at the end (the most
543 if (pos < gtk_text_buffer_get_char_count(textbuf) &&
544 !iswordsep(get_text_index_whar(gtkaspell, pos))) {
545 check_at(gtkaspell, pos - 1);
550 static void entry_delete_cb(GtkTextBuffer *textbuf,
551 GtkTextIter *startiter,
552 GtkTextIter *enditer,
553 GtkAspell *gtkaspell)
558 g_return_if_fail(gtkaspell->gtkaspeller->checker);
560 if (!gtkaspell->check_while_typing)
563 start = gtk_text_iter_get_offset(startiter);
564 end = gtk_text_iter_get_offset(enditer);
565 origpos = get_textview_buffer_offset(gtkaspell->gtktext);
567 check_at(gtkaspell, start - 1);
568 check_at(gtkaspell, start);
571 set_textview_buffer_offset(gtkaspell->gtktext, origpos);
572 /* this is to *UNDO* the selection, in case they were holding shift
573 * while hitting backspace. */
574 /* needed with textview ??? */
575 /* gtk_editable_select_region(GTK_EDITABLE(gtktext), origpos, origpos); */
578 static void button_press_intercept_cb(GtkTextView *gtktext,
579 GtkMenu *menu, GtkAspell *gtkaspell)
581 GtkMenuItem *menuitem;
582 GSList *spell_menu = NULL;
584 gboolean suggest = FALSE;
586 menuitem = GTK_MENU_ITEM(gtk_separator_menu_item_new());
587 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), GTK_WIDGET(menuitem));
588 gtk_widget_show(GTK_WIDGET(menuitem));
590 gtktext = gtkaspell->gtktext;
592 gtkaspell->orig_pos = get_textview_buffer_offset(gtktext);
594 if (check_at(gtkaspell, gtkaspell->orig_pos)) {
596 if (misspelled_suggest(gtkaspell, gtkaspell->theword)) {
597 spell_menu = make_sug_menu(gtkaspell);
602 spell_menu = gtkaspell_make_config_menu(gtkaspell);
604 spell_menu = g_slist_reverse(spell_menu);
605 for (items = spell_menu;
606 items; items = items->next) {
607 menuitem = GTK_MENU_ITEM(items->data);
608 gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), GTK_WIDGET(menuitem));
609 gtk_widget_show(GTK_WIDGET(menuitem));
611 g_slist_free(spell_menu);
613 g_signal_connect(G_OBJECT(menu), "deactivate",
614 G_CALLBACK(destroy_menu),
617 g_signal_connect(G_OBJECT(menu),
619 G_CALLBACK(aspell_key_pressed), gtkaspell);
623 /* Checker creation */
624 static GtkAspeller *gtkaspeller_new(Dictionary *dictionary)
627 GtkAspeller *gtkaspeller = NULL;
631 g_return_val_if_fail(gtkaspellcheckers, NULL);
633 g_return_val_if_fail(dictionary, NULL);
635 if (dictionary->fullname == NULL)
636 gtkaspell_checkers_error_message(
637 g_strdup(_("No dictionary selected.")));
639 g_return_val_if_fail(dictionary->fullname, NULL);
641 if (dictionary->dictname == NULL) {
644 tmp = strrchr(dictionary->fullname, G_DIR_SEPARATOR);
647 dictionary->dictname = dictionary->fullname;
649 dictionary->dictname = tmp + 1;
652 dict = dictionary_dup(dictionary);
654 tmp = g_new0(GtkAspeller, 1);
655 tmp->dictionary = dict;
657 exist = g_slist_find_custom(gtkaspellcheckers->checkers, tmp,
662 if ((gtkaspeller = gtkaspeller_real_new(dict)) != NULL) {
663 gtkaspellcheckers->checkers = g_slist_append(
664 gtkaspellcheckers->checkers,
667 debug_print("Aspell: Created a new gtkaspeller %p\n",
670 dictionary_delete(dict);
672 debug_print("Aspell: Could not create spell checker.\n");
675 debug_print("Aspell: number of existing checkers %d\n",
676 g_slist_length(gtkaspellcheckers->checkers));
681 static GtkAspeller *gtkaspeller_real_new(Dictionary *dict)
683 GtkAspeller *gtkaspeller;
684 AspellConfig *config;
685 AspellCanHaveError *ret;
687 g_return_val_if_fail(gtkaspellcheckers, NULL);
688 g_return_val_if_fail(dict, NULL);
690 gtkaspeller = g_new(GtkAspeller, 1);
692 gtkaspeller->dictionary = dict;
693 gtkaspeller->sug_mode = ASPELL_FASTMODE;
695 config = new_aspell_config();
697 if (!set_dictionary(config, dict))
700 ret = new_aspell_speller(config);
701 delete_aspell_config(config);
703 if (aspell_error_number(ret) != 0) {
704 gtkaspellcheckers->error_message
705 = g_strdup(aspell_error_message(ret));
707 delete_aspell_can_have_error(ret);
712 gtkaspeller->checker = to_aspell_speller(ret);
713 gtkaspeller->config = aspell_speller_config(gtkaspeller->checker);
718 static GtkAspeller *gtkaspeller_delete(GtkAspeller *gtkaspeller)
720 g_return_val_if_fail(gtkaspellcheckers, NULL);
722 gtkaspellcheckers->checkers =
723 g_slist_remove(gtkaspellcheckers->checkers,
726 debug_print("Aspell: Deleting gtkaspeller %p.\n",
729 gtkaspeller_real_delete(gtkaspeller);
731 debug_print("Aspell: number of existing checkers %d\n",
732 g_slist_length(gtkaspellcheckers->checkers));
737 static GtkAspeller *gtkaspeller_real_delete(GtkAspeller *gtkaspeller)
739 g_return_val_if_fail(gtkaspeller, NULL);
740 g_return_val_if_fail(gtkaspeller->checker, NULL);
742 aspell_speller_save_all_word_lists(gtkaspeller->checker);
744 delete_aspell_speller(gtkaspeller->checker);
746 dictionary_delete(gtkaspeller->dictionary);
748 debug_print("Aspell: gtkaspeller %p deleted.\n",
756 /*****************************************************************************/
757 /* Checker configuration */
759 static gboolean set_dictionary(AspellConfig *config, Dictionary *dict)
761 gchar *language = NULL;
762 gchar *jargon = NULL;
766 g_return_val_if_fail(config, FALSE);
767 g_return_val_if_fail(dict, FALSE);
769 strncpy(buf, dict->fullname, BUFSIZE-1);
770 buf[BUFSIZE-1] = 0x00;
772 buf[dict->dictname - dict->fullname] = 0x00;
774 CONFIG_REPLACE_RETURN_FALSE_IF_FAIL("dict-dir", buf);
775 debug_print("Aspell: looking for dictionaries in path %s.\n", buf);
777 strncpy(buf, dict->dictname, BUFSIZE-1);
780 if ((size = strrchr(buf, '-')) && isdigit((int) size[1]))
785 if ((jargon = strchr(language, '-')) != NULL)
788 if (size != NULL && jargon == size)
791 debug_print("Aspell: language: %s, jargon: %s, size: %s\n",
792 language, jargon ? jargon : "",
796 CONFIG_REPLACE_RETURN_FALSE_IF_FAIL("lang", language);
798 CONFIG_REPLACE_RETURN_FALSE_IF_FAIL("jargon", jargon);
800 CONFIG_REPLACE_RETURN_FALSE_IF_FAIL("size", size);
801 if (dict->encoding) {
804 aspell_enc = convert_to_aspell_encoding (dict->encoding);
805 aspell_config_replace(config, "encoding",
806 (const char *) aspell_enc);
809 RETURN_FALSE_IF_CONFIG_ERROR();
815 /* set_sug_mode_cb() - Menu callback: Set the suggestion mode */
816 static void set_sug_mode_cb(GtkMenuItem *w, GtkAspell *gtkaspell)
820 themode = (char *) gtk_label_get_text(GTK_LABEL(GTK_BIN(w)->child));
821 themode = g_strdup(themode);
823 set_real_sug_mode(gtkaspell, themode);
825 if (gtkaspell->menu_changed_cb)
826 gtkaspell->menu_changed_cb(gtkaspell->menu_changed_data);
829 /* set_sug_mode_cb() - Menu callback: Set the suggestion mode */
830 static void set_use_both_cb(GtkMenuItem *w, GtkAspell *gtkaspell)
832 gtkaspell->use_both_dicts = gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(w));
834 if (gtkaspell->recheck_when_changing_dict) {
835 gtkaspell_highlight_all(gtkaspell);
838 if (gtkaspell->menu_changed_cb)
839 gtkaspell->menu_changed_cb(gtkaspell->menu_changed_data);
842 static void set_real_sug_mode(GtkAspell *gtkaspell, const char *themode)
845 gint mode = ASPELL_FASTMODE;
846 g_return_if_fail(gtkaspell);
847 g_return_if_fail(gtkaspell->gtkaspeller);
848 g_return_if_fail(themode);
850 if (!strcmp(themode,_("Normal Mode")))
851 mode = ASPELL_NORMALMODE;
852 else if (!strcmp( themode,_("Bad Spellers Mode")))
853 mode = ASPELL_BADSPELLERMODE;
855 result = gtkaspell_set_sug_mode(gtkaspell, mode);
858 debug_print("Aspell: error while changing suggestion mode:%s\n",
859 gtkaspellcheckers->error_message);
860 gtkaspell_checkers_reset_error();
864 /* gtkaspell_set_sug_mode() - Set the suggestion mode */
865 gboolean gtkaspell_set_sug_mode(GtkAspell *gtkaspell, gint themode)
867 AspellConfig *config;
869 g_return_val_if_fail(gtkaspell, FALSE);
870 g_return_val_if_fail(gtkaspell->gtkaspeller, FALSE);
871 g_return_val_if_fail(gtkaspell->gtkaspeller->config, FALSE);
873 debug_print("Aspell: setting sug mode of gtkaspeller %p to %d\n",
874 gtkaspell->gtkaspeller, themode);
876 config = gtkaspell->gtkaspeller->config;
879 case ASPELL_FASTMODE:
880 CONFIG_REPLACE_RETURN_FALSE_IF_FAIL("sug-mode", "fast");
882 case ASPELL_NORMALMODE:
883 CONFIG_REPLACE_RETURN_FALSE_IF_FAIL("sug-mode", "normal");
885 case ASPELL_BADSPELLERMODE:
886 CONFIG_REPLACE_RETURN_FALSE_IF_FAIL("sug-mode",
890 gtkaspellcheckers->error_message =
891 g_strdup(_("Unknown suggestion mode."));
895 gtkaspell->gtkaspeller->sug_mode = themode;
896 gtkaspell->default_sug_mode = themode;
901 /* misspelled_suggest() - Create a suggestion list for word */
902 static GList *misspelled_suggest(GtkAspell *gtkaspell, gchar *word)
904 const guchar *newword;
906 const AspellWordList *suggestions;
907 AspellStringEnumeration *elements;
909 g_return_val_if_fail(word, NULL);
911 if (!aspell_speller_check(gtkaspell->gtkaspeller->checker, word, -1)) {
912 free_suggestions_list(gtkaspell);
914 suggestions = aspell_speller_suggest(
915 gtkaspell->gtkaspeller->checker,
916 (const char *)word, -1);
917 elements = aspell_word_list_elements(suggestions);
918 list = g_list_append(list, g_strdup(word));
920 while ((newword = (guchar *)aspell_string_enumeration_next(elements)) != NULL)
921 list = g_list_append(list, g_strdup((gchar *)newword));
923 gtkaspell->max_sug = g_list_length(list) - 1;
924 gtkaspell->suggestions_list = list;
929 free_suggestions_list(gtkaspell);
934 /* misspelled_test() - Just test if word is correctly spelled */
935 static int misspelled_test(GtkAspell *gtkaspell, unsigned char *word)
937 gint result = aspell_speller_check(gtkaspell->gtkaspeller->checker, (char *)word, -1)
939 if (result && gtkaspell->use_both_dicts && gtkaspell->alternate_speller) {
940 use_alternate_dict(gtkaspell);
941 result = aspell_speller_check(gtkaspell->gtkaspeller->checker, (char *)word, -1)
943 use_alternate_dict(gtkaspell);
949 static gboolean iswordsep(gunichar c)
951 return (g_unichar_isspace(c) || g_unichar_ispunct(c)) && c != (gunichar)'\'';
954 static gunichar get_text_index_whar(GtkAspell *gtkaspell, int pos)
956 GtkTextView *view = gtkaspell->gtktext;
957 GtkTextBuffer *buffer = gtk_text_view_get_buffer(view);
958 GtkTextIter start, end;
962 gtk_text_buffer_get_iter_at_offset(buffer, &start, pos);
963 gtk_text_buffer_get_iter_at_offset(buffer, &end, pos+1);
965 utf8chars = gtk_text_iter_get_text(&start, &end);
966 if (is_ascii_str(utf8chars)) {
967 a = utf8chars ? (gunichar)utf8chars[0] : '\0' ;
969 gchar *tr = conv_iconv_strdup(utf8chars, CS_UTF_8,
970 gtkaspell->gtkaspeller->dictionary->encoding);
972 a = g_utf8_get_char(tr);
981 /* get_word_from_pos () - return the word pointed to. */
982 /* Handles correctly the quotes. */
983 static gboolean get_word_from_pos(GtkAspell *gtkaspell, gint pos,
984 unsigned char* buf, gint buflen,
985 gint *pstart, gint *pend)
988 /* TODO : when correcting a word into quotes, change the color of */
989 /* the quotes too, as may be they were highlighted before. To do */
990 /* this, we can use two others pointers that points to the whole */
991 /* word including quotes. */
997 GtkTextView *gtktext;
999 gtktext = gtkaspell->gtktext;
1000 if (iswordsep(get_text_index_whar(gtkaspell, pos)))
1003 /* The apostrophe character is somtimes used for quotes
1004 * So include it in the word only if it is not surrounded
1005 * by other characters.
1008 for (start = pos; start >= 0; --start) {
1009 c = get_text_index_whar(gtkaspell, start);
1010 if (c == (gunichar)'\'') {
1012 if (g_unichar_isspace(get_text_index_whar(gtkaspell,
1014 || g_unichar_ispunct(get_text_index_whar(gtkaspell,
1016 || g_unichar_isdigit(get_text_index_whar(gtkaspell,
1018 /* start_quote = TRUE; */
1023 /* start_quote = TRUE; */
1027 else if (g_unichar_isspace(c) || g_unichar_ispunct(c) || g_unichar_isdigit(c))
1033 for (end = pos; end < get_textview_buffer_charcount(gtktext); end++) {
1034 c = get_text_index_whar(gtkaspell, end);
1035 if (c == (gunichar)'\'') {
1036 if (end < get_textview_buffer_charcount(gtktext)) {
1037 if (g_unichar_isspace(get_text_index_whar(gtkaspell,
1039 || g_unichar_ispunct(get_text_index_whar(gtkaspell,
1041 || g_unichar_isdigit(get_text_index_whar(gtkaspell,
1043 /* end_quote = TRUE; */
1048 /* end_quote = TRUE; */
1052 else if (g_unichar_isspace(c) || g_unichar_ispunct(c) || g_unichar_isdigit(c))
1062 if (end - start < buflen) {
1063 GtkTextIter iterstart, iterend;
1065 GtkTextBuffer *buffer = gtk_text_view_get_buffer(gtktext);
1066 gtk_text_buffer_get_iter_at_offset(buffer, &iterstart, start);
1067 gtk_text_buffer_get_iter_at_offset(buffer, &iterend, end);
1068 tmp = gtk_text_buffer_get_text(buffer, &iterstart, &iterend, FALSE);
1069 conv = conv_iconv_strdup(tmp, CS_UTF_8,
1070 gtkaspell->gtkaspeller->dictionary->encoding);
1072 strncpy((char *)buf, conv, buflen-1);
1082 static gboolean check_at(GtkAspell *gtkaspell, gint from_pos)
1085 unsigned char buf[GTKASPELLWORDSIZE];
1086 GtkTextView *gtktext;
1088 g_return_val_if_fail(from_pos >= 0, FALSE);
1090 gtktext = gtkaspell->gtktext;
1092 if (!get_word_from_pos(gtkaspell, from_pos, buf, sizeof(buf),
1096 if (misspelled_test(gtkaspell, buf)
1097 && strcasecmp((char *)buf, "sylpheed") && strcasecmp((char *)buf, "claws-mail")) {
1098 strncpy(gtkaspell->theword, (gchar *)buf, GTKASPELLWORDSIZE - 1);
1099 gtkaspell->theword[GTKASPELLWORDSIZE - 1] = 0;
1100 gtkaspell->start_pos = start;
1101 gtkaspell->end_pos = end;
1102 free_suggestions_list(gtkaspell);
1104 change_color(gtkaspell, start, end, (gchar *)buf, &(gtkaspell->highlight));
1107 change_color(gtkaspell, start, end, (gchar *)buf, NULL);
1112 static gboolean check_next_prev(GtkAspell *gtkaspell, gboolean forward)
1118 gboolean misspelled;
1121 maxpos = gtkaspell->end_check_pos;
1129 pos = get_textview_buffer_offset(gtkaspell->gtktext);
1130 gtkaspell->orig_pos = pos;
1131 while (iswordsep(get_text_index_whar(gtkaspell, pos)) &&
1132 pos > minpos && pos <= maxpos)
1134 while (!(misspelled = check_at(gtkaspell, pos)) &&
1135 pos > minpos && pos <= maxpos) {
1137 while (!iswordsep(get_text_index_whar(gtkaspell, pos)) &&
1138 pos > minpos && pos <= maxpos)
1141 while (iswordsep(get_text_index_whar(gtkaspell, pos)) &&
1142 pos > minpos && pos <= maxpos)
1148 misspelled_suggest(gtkaspell, gtkaspell->theword);
1151 gtkaspell->orig_pos = gtkaspell->end_pos;
1153 set_textview_buffer_offset(gtkaspell->gtktext,
1154 gtkaspell->end_pos);
1155 /* scroll line to window center */
1156 gtk_text_view_scroll_to_mark(gtkaspell->gtktext,
1157 gtk_text_buffer_get_insert(
1158 gtk_text_view_get_buffer(gtkaspell->gtktext)),
1159 0.0, TRUE, 0.0, 0.5);
1160 /* let textview recalculate coordinates (set_menu_pos) */
1161 while (gtk_events_pending ())
1162 gtk_main_iteration ();
1164 list = make_sug_menu(gtkaspell);
1165 menu = gtk_menu_new();
1166 for (cur = list; cur; cur = cur->next)
1167 gtk_menu_append(menu, GTK_WIDGET(cur->data));
1169 gtk_menu_popup(GTK_MENU(menu), NULL, NULL,
1170 set_menu_pos, gtkaspell, 0, GDK_CURRENT_TIME);
1171 g_signal_connect(G_OBJECT(menu), "deactivate",
1172 G_CALLBACK(destroy_menu),
1174 g_signal_connect(G_OBJECT(menu),
1176 G_CALLBACK(aspell_key_pressed), gtkaspell);
1180 reset_theword_data(gtkaspell);
1182 alertpanel_notice(_("No misspelled word found."));
1183 set_textview_buffer_offset(gtkaspell->gtktext,
1184 gtkaspell->orig_pos);
1190 void gtkaspell_check_backwards(GtkAspell *gtkaspell)
1192 gtkaspell->continue_check = NULL;
1193 gtkaspell->end_check_pos =
1194 get_textview_buffer_charcount(gtkaspell->gtktext);
1195 check_next_prev(gtkaspell, FALSE);
1198 void gtkaspell_check_forwards_go(GtkAspell *gtkaspell)
1201 gtkaspell->continue_check = NULL;
1202 gtkaspell->end_check_pos =
1203 get_textview_buffer_charcount(gtkaspell->gtktext);
1204 check_next_prev(gtkaspell, TRUE);
1207 void gtkaspell_check_all(GtkAspell *gtkaspell)
1209 GtkTextView *gtktext;
1211 GtkTextBuffer *buffer;
1212 GtkTextIter startiter, enditer;
1214 g_return_if_fail(gtkaspell);
1215 g_return_if_fail(gtkaspell->gtktext);
1217 gtktext = gtkaspell->gtktext;
1218 buffer = gtk_text_view_get_buffer(gtktext);
1219 gtk_text_buffer_get_selection_bounds(buffer, &startiter, &enditer);
1220 start = gtk_text_iter_get_offset(&startiter);
1221 end = gtk_text_iter_get_offset(&enditer);
1225 end = gtk_text_buffer_get_char_count(buffer);
1226 } else if (start > end) {
1234 set_textview_buffer_offset(gtktext, start);
1236 gtkaspell->continue_check = continue_check;
1237 gtkaspell->end_check_pos = end;
1239 gtkaspell->misspelled = check_next_prev(gtkaspell, TRUE);
1242 static void continue_check(gpointer *data)
1244 GtkAspell *gtkaspell = (GtkAspell *) data;
1245 gint pos = get_textview_buffer_offset(gtkaspell->gtktext);
1246 if (pos < gtkaspell->end_check_pos && gtkaspell->misspelled)
1247 gtkaspell->misspelled = check_next_prev(gtkaspell, TRUE);
1249 gtkaspell->continue_check = NULL;
1252 void gtkaspell_highlight_all(GtkAspell *gtkaspell)
1257 GtkTextView *gtktext;
1259 g_return_if_fail(gtkaspell->gtkaspeller->checker);
1261 gtktext = gtkaspell->gtktext;
1263 len = get_textview_buffer_charcount(gtktext);
1265 origpos = get_textview_buffer_offset(gtktext);
1269 iswordsep(get_text_index_whar(gtkaspell, pos)))
1272 !iswordsep(get_text_index_whar(gtkaspell, pos)))
1275 check_at(gtkaspell, pos - 1);
1277 set_textview_buffer_offset(gtktext, origpos);
1280 static void replace_with_supplied_word_cb(GtkWidget *w, GtkAspell *gtkaspell)
1282 unsigned char *newword;
1283 GdkEvent *e= (GdkEvent *) gtk_get_current_event();
1285 newword = (unsigned char *)gtk_editable_get_chars(GTK_EDITABLE(gtkaspell->replace_entry),
1288 if (strcmp((char *)newword, gtkaspell->theword)) {
1289 replace_real_word(gtkaspell, (char *)newword);
1291 if ((e->type == GDK_KEY_PRESS &&
1292 ((GdkEventKey *) e)->state & GDK_CONTROL_MASK)) {
1293 aspell_speller_store_replacement(
1294 gtkaspell->gtkaspeller->checker,
1295 gtkaspell->theword, -1,
1296 (char *)newword, -1);
1298 gtkaspell->replace_entry = NULL;
1303 if (w && GTK_IS_DIALOG(w)) {
1304 gtk_widget_destroy(w);
1307 set_point_continue(gtkaspell);
1311 static void replace_word_cb(GtkWidget *w, gpointer data)
1313 unsigned char *newword;
1314 GtkAspell *gtkaspell = (GtkAspell *) data;
1315 GdkEvent *e= (GdkEvent *) gtk_get_current_event();
1317 newword = (unsigned char *) gtk_label_get_text(GTK_LABEL(GTK_BIN(w)->child));
1318 newword = (unsigned char *)g_strdup((char *)newword);
1320 replace_real_word(gtkaspell, (char *)newword);
1322 if ((e->type == GDK_KEY_PRESS &&
1323 ((GdkEventKey *) e)->state & GDK_CONTROL_MASK) ||
1324 (e->type == GDK_BUTTON_RELEASE &&
1325 ((GdkEventButton *) e)->state & GDK_CONTROL_MASK)) {
1326 aspell_speller_store_replacement(
1327 gtkaspell->gtkaspeller->checker,
1328 gtkaspell->theword, -1,
1329 (char *)newword, -1);
1332 gtk_menu_shell_deactivate(GTK_MENU_SHELL(w->parent));
1334 set_point_continue(gtkaspell);
1338 static void replace_real_word(GtkAspell *gtkaspell, gchar *newword)
1340 int oldlen, newlen, wordlen;
1343 GtkTextView *gtktext;
1344 GtkTextBuffer *textbuf;
1345 GtkTextIter startiter, enditer;
1347 if (!newword) return;
1349 gtktext = gtkaspell->gtktext;
1350 textbuf = gtk_text_view_get_buffer(gtktext);
1352 origpos = gtkaspell->orig_pos;
1354 oldlen = gtkaspell->end_pos - gtkaspell->start_pos;
1355 wordlen = strlen(gtkaspell->theword);
1357 newlen = strlen(newword); /* FIXME: multybyte characters? */
1359 g_signal_handlers_block_by_func(G_OBJECT(gtktext),
1360 G_CALLBACK(entry_insert_cb),
1362 g_signal_handlers_block_by_func(G_OBJECT(gtktext),
1363 G_CALLBACK(entry_delete_cb),
1366 gtk_text_buffer_get_iter_at_offset(textbuf, &startiter,
1367 gtkaspell->start_pos);
1368 gtk_text_buffer_get_iter_at_offset(textbuf, &enditer,
1369 gtkaspell->end_pos);
1370 g_signal_emit_by_name(G_OBJECT(textbuf), "delete-range",
1371 &startiter, &enditer, gtkaspell);
1372 g_signal_emit_by_name(G_OBJECT(textbuf), "insert-text",
1373 &startiter, newword, newlen, gtkaspell);
1375 g_signal_handlers_unblock_by_func(G_OBJECT(gtktext),
1376 G_CALLBACK(entry_insert_cb),
1378 g_signal_handlers_unblock_by_func(G_OBJECT(gtktext),
1379 G_CALLBACK(entry_delete_cb),
1382 /* Put the point and the position where we clicked with the mouse
1383 * It seems to be a hack, as I must thaw,freeze,thaw the widget
1384 * to let it update correctly the word insertion and then the
1385 * point & position position. If not, SEGV after the first replacement
1386 * If the new word ends before point, put the point at its end.
1389 if (origpos - gtkaspell->start_pos < oldlen &&
1390 origpos - gtkaspell->start_pos >= 0) {
1391 /* Original point was in the word.
1392 * Let it there unless point is going to be outside of the word
1394 if (origpos - gtkaspell->start_pos >= newlen) {
1395 pos = gtkaspell->start_pos + newlen;
1398 else if (origpos >= gtkaspell->end_pos) {
1399 /* move the position according to the change of length */
1400 pos = origpos + newlen - oldlen;
1403 gtkaspell->end_pos = gtkaspell->start_pos + strlen(newword); /* FIXME: multibyte characters? */
1405 if (get_textview_buffer_charcount(gtktext) < pos)
1406 pos = get_textview_buffer_charcount(gtktext);
1407 gtkaspell->orig_pos = pos;
1409 set_textview_buffer_offset(gtktext, gtkaspell->orig_pos);
1412 /* Accept this word for this session */
1413 static void add_word_to_session_cb(GtkWidget *w, gpointer data)
1416 GtkTextView *gtktext;
1417 GtkAspell *gtkaspell = (GtkAspell *) data;
1418 gtktext = gtkaspell->gtktext;
1420 pos = get_textview_buffer_offset(gtktext);
1422 aspell_speller_add_to_session(gtkaspell->gtkaspeller->checker,
1424 strlen(gtkaspell->theword));
1426 check_at(gtkaspell, gtkaspell->start_pos);
1428 gtk_menu_shell_deactivate(GTK_MENU_SHELL(GTK_WIDGET(w)->parent));
1430 set_point_continue(gtkaspell);
1433 /* add_word_to_personal_cb() - add word to personal dict. */
1434 static void add_word_to_personal_cb(GtkWidget *w, gpointer data)
1436 GtkAspell *gtkaspell = (GtkAspell *) data;
1438 aspell_speller_add_to_personal(gtkaspell->gtkaspeller->checker,
1440 strlen(gtkaspell->theword));
1442 check_at(gtkaspell, gtkaspell->start_pos);
1444 gtk_menu_shell_deactivate(GTK_MENU_SHELL(GTK_WIDGET(w)->parent));
1445 set_point_continue(gtkaspell);
1448 static void check_with_alternate_cb(GtkWidget *w, gpointer data)
1450 GtkAspell *gtkaspell = (GtkAspell *) data;
1453 gtk_menu_shell_deactivate(GTK_MENU_SHELL(GTK_WIDGET(w)->parent));
1455 use_alternate_dict(gtkaspell);
1456 misspelled = check_at(gtkaspell, gtkaspell->start_pos);
1458 if (!gtkaspell->continue_check) {
1460 gtkaspell->misspelled = misspelled;
1462 if (gtkaspell->misspelled) {
1465 misspelled_suggest(gtkaspell, gtkaspell->theword);
1467 set_textview_buffer_offset(gtkaspell->gtktext,
1468 gtkaspell->end_pos);
1470 list = make_sug_menu(gtkaspell);
1471 menu = gtk_menu_new();
1472 for (cur = list; cur; cur = cur->next)
1473 gtk_menu_append(menu, GTK_WIDGET(cur->data));
1475 gtk_menu_popup(GTK_MENU(menu), NULL, NULL,
1476 set_menu_pos, gtkaspell, 0,
1478 g_signal_connect(G_OBJECT(menu), "deactivate",
1479 G_CALLBACK(destroy_menu),
1481 g_signal_connect(G_OBJECT(menu),
1483 G_CALLBACK(aspell_key_pressed), gtkaspell);
1487 gtkaspell->orig_pos = gtkaspell->start_pos;
1489 set_point_continue(gtkaspell);
1492 static gboolean replace_key_pressed(GtkWidget *widget,
1494 GtkAspell *gtkaspell)
1496 if (event && event->keyval == GDK_Escape) {
1497 gtk_widget_destroy(widget);
1499 } else if (event && event->keyval == GDK_Return) {
1500 replace_with_supplied_word_cb(widget, gtkaspell);
1506 static void replace_with_create_dialog_cb(GtkWidget *w, gpointer data)
1508 static PangoFontDescription *font_desc;
1515 GtkWidget *ok_button;
1516 GtkWidget *cancel_button;
1517 GtkWidget *confirm_area;
1521 GtkAspell *gtkaspell = (GtkAspell *) data;
1523 gdk_window_get_origin((GTK_WIDGET(w)->parent)->window, &xx, &yy);
1525 gtk_menu_shell_deactivate(GTK_MENU_SHELL(GTK_WIDGET(w)->parent));
1527 dialog = gtk_dialog_new();
1529 gtk_window_set_resizable(GTK_WINDOW(dialog), FALSE);
1530 gtk_window_set_title(GTK_WINDOW(dialog),_("Replace unknown word"));
1531 gtk_dialog_set_has_separator (GTK_DIALOG (dialog), FALSE);
1532 gtk_widget_set_uposition(dialog, xx, yy);
1534 g_signal_connect_swapped(G_OBJECT(dialog), "destroy",
1535 G_CALLBACK(gtk_widget_destroy),
1538 gtk_box_set_spacing (GTK_BOX (GTK_DIALOG (dialog)->vbox), 14);
1539 hbox = gtk_hbox_new (FALSE, 12);
1540 gtk_container_set_border_width (GTK_CONTAINER (hbox), 5);
1541 gtk_widget_show (hbox);
1542 gtk_box_pack_start (GTK_BOX (GTK_DIALOG (dialog)->vbox), hbox,
1545 thelabel = g_strdup_printf(_("<span weight=\"bold\" "
1546 "size=\"larger\">Replace \"%s\" with: </span>"),
1547 gtkaspell->theword);
1548 /* for title label */
1549 w_hbox = gtk_hbox_new(FALSE, 0);
1551 icon = gtk_image_new_from_stock(GTK_STOCK_DIALOG_QUESTION,
1552 GTK_ICON_SIZE_DIALOG);
1553 gtk_misc_set_alignment (GTK_MISC (icon), 0.5, 0.0);
1554 gtk_box_pack_start (GTK_BOX (hbox), icon, FALSE, FALSE, 0);
1556 vbox = gtk_vbox_new (FALSE, 12);
1557 gtk_box_pack_start (GTK_BOX (hbox), vbox, TRUE, TRUE, 0);
1558 gtk_widget_show (vbox);
1560 label = gtk_label_new(thelabel);
1561 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
1562 gtk_label_set_justify(GTK_LABEL(label), GTK_JUSTIFY_LEFT);
1563 gtk_label_set_use_markup (GTK_LABEL (label), TRUE);
1564 gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 0);
1565 gtk_label_set_line_wrap(GTK_LABEL(label), TRUE);
1569 size = pango_font_description_get_size
1570 (label->style->font_desc);
1571 font_desc = pango_font_description_new();
1572 pango_font_description_set_weight
1573 (font_desc, PANGO_WEIGHT_BOLD);
1574 pango_font_description_set_size
1575 (font_desc, size * PANGO_SCALE_LARGE);
1578 gtk_widget_modify_font(label, font_desc);
1581 entry = gtk_entry_new();
1582 gtkaspell->replace_entry = entry;
1583 gtk_entry_set_text(GTK_ENTRY(entry), gtkaspell->theword);
1584 gtk_editable_select_region(GTK_EDITABLE(entry), 0, -1);
1585 g_signal_connect(G_OBJECT(dialog),
1587 G_CALLBACK(replace_key_pressed), gtkaspell);
1588 gtk_box_pack_start(GTK_BOX(vbox), entry, FALSE, FALSE, 0);
1590 label = gtk_label_new(_("Holding down Control key while pressing "
1591 "Enter\nwill learn from mistake.\n"));
1592 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
1593 gtk_label_set_justify(GTK_LABEL(label), GTK_JUSTIFY_LEFT);
1594 gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 0);
1595 gtk_widget_show(label);
1597 hbox = gtk_hbox_new(TRUE, 0);
1599 gtkut_stock_button_set_create(&confirm_area,
1600 &cancel_button, GTK_STOCK_CANCEL,
1601 &ok_button, GTK_STOCK_OK,
1604 gtk_box_pack_end(GTK_BOX(GTK_DIALOG(dialog)->action_area),
1605 confirm_area, FALSE, FALSE, 0);
1606 gtk_container_set_border_width(GTK_CONTAINER(confirm_area), 5);
1608 g_signal_connect(G_OBJECT(ok_button), "clicked",
1609 G_CALLBACK(replace_with_supplied_word_cb),
1611 g_signal_connect_swapped(G_OBJECT(ok_button), "clicked",
1612 G_CALLBACK(gtk_widget_destroy),
1615 g_signal_connect_swapped(G_OBJECT(cancel_button), "clicked",
1616 G_CALLBACK(gtk_widget_destroy),
1619 gtk_widget_grab_focus(entry);
1621 gtk_window_set_modal(GTK_WINDOW(dialog), TRUE);
1623 gtk_widget_show_all(dialog);
1626 static void gtkaspell_uncheck_all(GtkAspell * gtkaspell)
1628 GtkTextView *gtktext;
1629 GtkTextBuffer *buffer;
1630 GtkTextIter startiter, enditer;
1632 gtktext = gtkaspell->gtktext;
1634 buffer = gtk_text_view_get_buffer(gtktext);
1635 gtk_text_buffer_get_iter_at_offset(buffer, &startiter, 0);
1636 gtk_text_buffer_get_iter_at_offset(buffer, &enditer,
1637 get_textview_buffer_charcount(gtktext)-1);
1638 gtk_text_buffer_remove_tag_by_name(buffer, "misspelled",
1639 &startiter, &enditer);
1642 static void toggle_check_while_typing_cb(GtkWidget *w, gpointer data)
1644 GtkAspell *gtkaspell = (GtkAspell *) data;
1646 gtkaspell->check_while_typing = gtkaspell->check_while_typing == FALSE;
1648 if (!gtkaspell->check_while_typing)
1649 gtkaspell_uncheck_all(gtkaspell);
1650 if (gtkaspell->menu_changed_cb)
1651 gtkaspell->menu_changed_cb(gtkaspell->menu_changed_data);
1654 static GSList *create_empty_dictionary_list(void)
1656 GSList *list = NULL;
1659 dict = g_new0(Dictionary, 1);
1660 dict->fullname = g_strdup(_("None"));
1661 dict->dictname = dict->fullname;
1662 dict->encoding = NULL;
1664 return g_slist_append(list, dict);
1667 /* gtkaspell_get_dictionary_list() - returns list of dictionary names */
1668 static GSList *gtkaspell_get_dictionary_list(const gchar *aspell_path, gint refresh)
1672 AspellConfig *config;
1673 AspellDictInfoList *dlist;
1674 AspellDictInfoEnumeration *dels;
1675 const AspellDictInfo *entry;
1677 if (!gtkaspellcheckers)
1678 gtkaspell_checkers_init();
1680 if (gtkaspellcheckers->dictionary_list && !refresh)
1681 return gtkaspellcheckers->dictionary_list;
1683 gtkaspell_free_dictionary_list(
1684 gtkaspellcheckers->dictionary_list);
1687 config = new_aspell_config();
1689 aspell_config_replace(config, "dict-dir", aspell_path);
1690 if (aspell_config_error_number(config) != 0) {
1691 gtkaspellcheckers->error_message = g_strdup(
1692 aspell_config_error_message(config));
1693 gtkaspellcheckers->dictionary_list =
1694 create_empty_dictionary_list();
1696 return gtkaspellcheckers->dictionary_list;
1699 dlist = get_aspell_dict_info_list(config);
1700 delete_aspell_config(config);
1702 debug_print("Aspell: checking for dictionaries in %s\n", aspell_path);
1703 dels = aspell_dict_info_list_elements(dlist);
1704 while ( (entry = aspell_dict_info_enumeration_next(dels)) != 0)
1706 dict = g_new0(Dictionary, 1);
1707 dict->fullname = g_strdup_printf("%s%s", aspell_path,
1709 dict->dictname = dict->fullname + strlen(aspell_path);
1710 dict->encoding = g_strdup(entry->code);
1712 if (g_slist_find_custom(list, dict,
1713 (GCompareFunc) compare_dict) != NULL) {
1714 dictionary_delete(dict);
1718 debug_print("Aspell: found dictionary %s %s %s\n", dict->fullname,
1719 dict->dictname, dict->encoding);
1720 list = g_slist_insert_sorted(list, dict,
1721 (GCompareFunc) compare_dict);
1724 delete_aspell_dict_info_enumeration(dels);
1728 debug_print("Aspell: error when searching for dictionaries: "
1729 "No dictionary found.\n");
1730 list = create_empty_dictionary_list();
1733 gtkaspellcheckers->dictionary_list = list;
1738 static void gtkaspell_free_dictionary_list(GSList *list)
1742 for (walk = list; walk != NULL; walk = g_slist_next(walk))
1744 dict = (Dictionary *) walk->data;
1745 dictionary_delete(dict);
1751 GtkWidget *gtkaspell_dictionary_option_menu_new_with_refresh(const gchar *aspell_path,
1754 GSList *dict_list, *tmp;
1759 dict_list = gtkaspell_get_dictionary_list(aspell_path, refresh);
1760 g_return_val_if_fail(dict_list, NULL);
1762 menu = gtk_menu_new();
1764 for (tmp = dict_list; tmp != NULL; tmp = g_slist_next(tmp)) {
1765 dict = (Dictionary *) tmp->data;
1766 item = gtk_menu_item_new_with_label(dict->dictname);
1767 g_object_set_data(G_OBJECT(item), "dict_name",
1770 gtk_menu_append(GTK_MENU(menu), item);
1771 gtk_widget_show(item);
1774 gtk_widget_show(menu);
1779 GtkWidget *gtkaspell_dictionary_option_menu_new(const gchar *aspell_path)
1781 return gtkaspell_dictionary_option_menu_new_with_refresh
1782 (aspell_path, TRUE);
1786 gchar *gtkaspell_get_dictionary_menu_active_item(GtkWidget *menu)
1788 GtkWidget *menuitem;
1789 gchar *dict_fullname;
1792 g_return_val_if_fail(GTK_IS_MENU(menu), NULL);
1794 menuitem = gtk_menu_get_active(GTK_MENU(menu));
1795 dict_fullname = (gchar *) g_object_get_data(G_OBJECT(menuitem),
1797 g_return_val_if_fail(dict_fullname, NULL);
1799 label = g_strdup(dict_fullname);
1805 gint gtkaspell_set_dictionary_menu_active_item(GtkWidget *menu,
1806 const gchar *dictionary)
1811 g_return_val_if_fail(menu != NULL, 0);
1812 g_return_val_if_fail(dictionary != NULL, 0);
1813 g_return_val_if_fail(GTK_IS_OPTION_MENU(menu), 0);
1816 for (cur = GTK_MENU_SHELL(gtk_option_menu_get_menu(
1817 GTK_OPTION_MENU(menu)))->children;
1818 cur != NULL; cur = cur->next) {
1819 GtkWidget *menuitem;
1822 menuitem = GTK_WIDGET(cur->data);
1823 dict_name = g_object_get_data(G_OBJECT(menuitem),
1825 if ((dict_name != NULL) && !strcmp2(dict_name, dictionary)) {
1826 gtk_option_menu_set_history(GTK_OPTION_MENU(menu), n);
1837 GtkWidget *gtkaspell_sugmode_option_menu_new(gint sugmode)
1842 menu = gtk_menu_new();
1843 gtk_widget_show(menu);
1845 item = gtk_menu_item_new_with_label(_("Fast Mode"));
1846 gtk_widget_show(item);
1847 gtk_menu_append(GTK_MENU(menu), item);
1848 g_object_set_data(G_OBJECT(item), "sugmode",
1849 GINT_TO_POINTER(ASPELL_FASTMODE));
1851 item = gtk_menu_item_new_with_label(_("Normal Mode"));
1852 gtk_widget_show(item);
1853 gtk_menu_append(GTK_MENU(menu), item);
1854 g_object_set_data(G_OBJECT(item), "sugmode",
1855 GINT_TO_POINTER(ASPELL_NORMALMODE));
1857 item = gtk_menu_item_new_with_label(_("Bad Spellers Mode"));
1858 gtk_widget_show(item);
1859 gtk_menu_append(GTK_MENU(menu), item);
1860 g_object_set_data(G_OBJECT(item), "sugmode",
1861 GINT_TO_POINTER(ASPELL_BADSPELLERMODE));
1866 void gtkaspell_sugmode_option_menu_set(GtkOptionMenu *optmenu, gint sugmode)
1868 g_return_if_fail(GTK_IS_OPTION_MENU(optmenu));
1870 g_return_if_fail(sugmode == ASPELL_FASTMODE ||
1871 sugmode == ASPELL_NORMALMODE ||
1872 sugmode == ASPELL_BADSPELLERMODE);
1874 gtk_option_menu_set_history(GTK_OPTION_MENU(optmenu), sugmode - 1);
1877 gint gtkaspell_get_sugmode_from_option_menu(GtkOptionMenu *optmenu)
1882 g_return_val_if_fail(GTK_IS_OPTION_MENU(optmenu), -1);
1884 item = gtk_menu_get_active(GTK_MENU(gtk_option_menu_get_menu(optmenu)));
1886 sugmode = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(item),
1892 static void use_alternate_dict(GtkAspell *gtkaspell)
1896 tmp = gtkaspell->gtkaspeller;
1897 gtkaspell->gtkaspeller = gtkaspell->alternate_speller;
1898 gtkaspell->alternate_speller = tmp;
1901 static void destroy_menu(GtkWidget *widget,
1902 gpointer user_data) {
1904 GtkAspell *gtkaspell = (GtkAspell *)user_data;
1906 if (gtkaspell->accel_group) {
1907 gtk_window_remove_accel_group(GTK_WINDOW(gtkaspell->parent_window),
1908 gtkaspell->accel_group);
1909 gtkaspell->accel_group = NULL;
1913 static gboolean aspell_key_pressed(GtkWidget *widget,
1915 GtkAspell *gtkaspell)
1917 if (event && (isascii(event->keyval) || event->keyval == GDK_Return)) {
1918 gtk_accel_groups_activate(
1919 G_OBJECT(gtkaspell->parent_window),
1920 event->keyval, event->state);
1921 } else if (event && event->keyval == GDK_Escape) {
1922 destroy_menu(NULL, gtkaspell);
1927 /* make_sug_menu() - Add menus to accept this word for this session
1928 * and to add it to personal dictionary
1930 static GSList *make_sug_menu(GtkAspell *gtkaspell)
1934 GtkTextView *gtktext;
1935 GtkAccelGroup *accel;
1936 GList *l = gtkaspell->suggestions_list;
1938 GSList *list = NULL;
1939 gtktext = gtkaspell->gtktext;
1941 accel = gtk_accel_group_new();
1943 if (gtkaspell->accel_group) {
1944 gtk_window_remove_accel_group(GTK_WINDOW(gtkaspell->parent_window),
1945 gtkaspell->accel_group);
1946 gtkaspell->accel_group = NULL;
1949 utf8buf = conv_codeset_strdup((char*)l->data,
1950 conv_get_locale_charset_str(),
1952 caption = g_strdup_printf(_("\"%s\" unknown in %s"),
1954 gtkaspell->gtkaspeller->dictionary->dictname);
1955 item = gtk_menu_item_new_with_label(caption);
1957 gtk_widget_show(item);
1958 list = g_slist_append(list, item);
1959 gtk_misc_set_alignment(GTK_MISC(GTK_BIN(item)->child), 0.5, 0.5);
1962 item = gtk_menu_item_new();
1963 gtk_widget_show(item);
1964 list = g_slist_append(list, item);
1966 item = gtk_menu_item_new_with_label(_("Accept in this session"));
1967 gtk_widget_show(item);
1968 list = g_slist_append(list, item);
1969 g_signal_connect(G_OBJECT(item), "activate",
1970 G_CALLBACK(add_word_to_session_cb),
1972 gtk_widget_add_accelerator(item, "activate", accel, GDK_space,
1974 GTK_ACCEL_LOCKED | GTK_ACCEL_VISIBLE);
1976 item = gtk_menu_item_new_with_label(_("Add to personal dictionary"));
1977 gtk_widget_show(item);
1978 list = g_slist_append(list, item);
1979 g_signal_connect(G_OBJECT(item), "activate",
1980 G_CALLBACK(add_word_to_personal_cb),
1982 gtk_widget_add_accelerator(item, "activate", accel, GDK_Return,
1984 GTK_ACCEL_LOCKED | GTK_ACCEL_VISIBLE);
1986 item = gtk_menu_item_new_with_label(_("Replace with..."));
1987 gtk_widget_show(item);
1988 list = g_slist_append(list, item);
1989 g_signal_connect(G_OBJECT(item), "activate",
1990 G_CALLBACK(replace_with_create_dialog_cb),
1992 gtk_widget_add_accelerator(item, "activate", accel, GDK_R, 0,
1993 GTK_ACCEL_LOCKED | GTK_ACCEL_VISIBLE);
1994 gtk_widget_add_accelerator(item, "activate", accel, GDK_R,
1998 if (gtkaspell->use_alternate && gtkaspell->alternate_speller) {
1999 caption = g_strdup_printf(_("Check with %s"),
2000 gtkaspell->alternate_speller->dictionary->dictname);
2001 item = gtk_menu_item_new_with_label(caption);
2003 gtk_widget_show(item);
2004 list = g_slist_append(list, item);
2005 g_signal_connect(G_OBJECT(item), "activate",
2006 G_CALLBACK(check_with_alternate_cb),
2008 gtk_widget_add_accelerator(item, "activate", accel, GDK_X, 0,
2009 GTK_ACCEL_LOCKED | GTK_ACCEL_VISIBLE);
2010 gtk_widget_add_accelerator(item, "activate", accel, GDK_X,
2015 item = gtk_menu_item_new();
2016 gtk_widget_show(item);
2017 list = g_slist_append(list, item);
2021 item = gtk_menu_item_new_with_label(_("(no suggestions)"));
2022 gtk_widget_show(item);
2023 list = g_slist_append(list, item);
2025 GtkWidget *curmenu = NULL;
2029 if (count == MENUCOUNT) {
2032 item = gtk_menu_item_new_with_label(_("More..."));
2033 gtk_widget_show(item);
2035 gtk_menu_append(GTK_MENU(curmenu), item);
2037 list = g_slist_append(list, item);
2039 curmenu = gtk_menu_new();
2040 gtk_menu_item_set_submenu(GTK_MENU_ITEM(item),
2044 utf8buf = conv_codeset_strdup((char*)l->data,
2045 conv_get_locale_charset_str(),
2047 item = gtk_menu_item_new_with_label(utf8buf);
2049 gtk_widget_show(item);
2050 if (curmenu == NULL) {
2051 list = g_slist_append(list, item);
2053 gtk_menu_append(GTK_MENU(curmenu), item);
2055 g_signal_connect(G_OBJECT(item), "activate",
2056 G_CALLBACK(replace_word_cb),
2059 if (curmenu == NULL && count < MENUCOUNT) {
2060 gtk_widget_add_accelerator(item, "activate",
2065 gtk_widget_add_accelerator(item, "activate",
2074 } while ((l = l->next) != NULL);
2077 gtk_window_add_accel_group
2078 (GTK_WINDOW(gtkaspell->parent_window),
2080 gtkaspell->accel_group = accel;
2085 static GSList *populate_submenu(GtkAspell *gtkaspell)
2087 GtkWidget *item, *submenu;
2089 GtkAspeller *gtkaspeller = NULL;
2090 GSList *list = NULL;
2095 gtkaspeller = gtkaspell->gtkaspeller;
2096 dictname = g_strdup_printf(_("Dictionary: %s"),
2097 gtkaspeller->dictionary->dictname);
2098 item = gtk_menu_item_new_with_label(dictname);
2099 gtk_misc_set_alignment(GTK_MISC(GTK_BIN(item)->child), 0.5, 0.5);
2101 gtk_widget_show(item);
2102 list = g_slist_append(list, item);
2104 item = gtk_menu_item_new();
2105 gtk_widget_show(item);
2106 list = g_slist_append(list, item);
2108 if (gtkaspell->use_alternate && gtkaspell->alternate_speller) {
2109 dictname = g_strdup_printf(_("Use alternate (%s)"),
2110 gtkaspell->alternate_speller->dictionary->dictname);
2111 item = gtk_menu_item_new_with_label(dictname);
2113 g_signal_connect(G_OBJECT(item), "activate",
2114 G_CALLBACK(switch_to_alternate_cb),
2116 gtk_widget_show(item);
2117 list = g_slist_append(list, item);
2120 item = gtk_check_menu_item_new_with_label(_("Use both dictionaries"));
2121 if (gtkaspell->use_both_dicts) {
2122 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(item), TRUE);
2124 g_signal_connect(G_OBJECT(item), "activate",
2125 G_CALLBACK(set_use_both_cb),
2127 gtk_widget_show(item);
2128 list = g_slist_append(list, item);
2130 item = gtk_menu_item_new();
2131 gtk_widget_show(item);
2132 list = g_slist_append(list, item);
2134 item = gtk_check_menu_item_new_with_label(_("Fast Mode"));
2135 gtk_check_menu_item_set_draw_as_radio(GTK_CHECK_MENU_ITEM(item), TRUE);
2136 if (gtkaspell->gtkaspeller->sug_mode == ASPELL_FASTMODE) {
2137 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(item),TRUE);
2138 gtk_widget_set_sensitive(GTK_WIDGET(item),FALSE);
2140 g_signal_connect(G_OBJECT(item), "activate",
2141 G_CALLBACK(set_sug_mode_cb),
2143 gtk_widget_show(item);
2144 list = g_slist_append(list, item);
2146 item = gtk_check_menu_item_new_with_label(_("Normal Mode"));
2147 gtk_check_menu_item_set_draw_as_radio(GTK_CHECK_MENU_ITEM(item), TRUE);
2148 if (gtkaspell->gtkaspeller->sug_mode == ASPELL_NORMALMODE) {
2149 gtk_widget_set_sensitive(GTK_WIDGET(item), FALSE);
2150 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(item), TRUE);
2152 g_signal_connect(G_OBJECT(item), "activate",
2153 G_CALLBACK(set_sug_mode_cb),
2155 gtk_widget_show(item);
2156 list = g_slist_append(list, item);
2158 item = gtk_check_menu_item_new_with_label(_("Bad Spellers Mode"));
2159 gtk_check_menu_item_set_draw_as_radio(GTK_CHECK_MENU_ITEM(item), TRUE);
2160 if (gtkaspell->gtkaspeller->sug_mode == ASPELL_BADSPELLERMODE) {
2161 gtk_widget_set_sensitive(GTK_WIDGET(item), FALSE);
2162 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(item), TRUE);
2164 g_signal_connect(G_OBJECT(item), "activate",
2165 G_CALLBACK(set_sug_mode_cb),
2167 gtk_widget_show(item);
2168 list = g_slist_append(list, item);
2170 item = gtk_menu_item_new();
2171 gtk_widget_show(item);
2172 list = g_slist_append(list, item);
2174 item = gtk_check_menu_item_new_with_label(_("Check while typing"));
2175 if (gtkaspell->check_while_typing)
2176 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(item), TRUE);
2178 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(item), FALSE);
2179 g_signal_connect(G_OBJECT(item), "activate",
2180 G_CALLBACK(toggle_check_while_typing_cb),
2182 gtk_widget_show(item);
2183 list = g_slist_append(list, item);
2185 item = gtk_menu_item_new();
2186 gtk_widget_show(item);
2187 list = g_slist_append(list, item);
2189 submenu = gtk_menu_new();
2190 item = gtk_menu_item_new_with_label(_("Change dictionary"));
2191 gtk_menu_item_set_submenu(GTK_MENU_ITEM(item),submenu);
2192 gtk_widget_show(item);
2193 list = g_slist_append(list, item);
2196 if (gtkaspellcheckers->dictionary_list == NULL)
2197 gtkaspell_get_dictionary_list(gtkaspell->dictionary_path, FALSE);
2199 GtkWidget * curmenu = submenu;
2203 tmp = gtkaspellcheckers->dictionary_list;
2205 for (tmp = gtkaspellcheckers->dictionary_list; tmp != NULL;
2206 tmp = g_slist_next(tmp)) {
2207 if (count == MENUCOUNT) {
2210 newmenu = gtk_menu_new();
2211 item = gtk_menu_item_new_with_label(_("More..."));
2212 gtk_menu_item_set_submenu(GTK_MENU_ITEM(item),
2215 gtk_menu_append(GTK_MENU(curmenu), item);
2216 gtk_widget_show(item);
2220 dict = (Dictionary *) tmp->data;
2221 item = gtk_check_menu_item_new_with_label(dict->dictname);
2222 g_object_set_data(G_OBJECT(item), "dict_name",
2224 if (strcmp2(dict->fullname,
2225 gtkaspell->gtkaspeller->dictionary->fullname))
2226 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(item), FALSE);
2228 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(item), TRUE);
2229 gtk_widget_set_sensitive(GTK_WIDGET(item),
2232 g_signal_connect(G_OBJECT(item), "activate",
2233 G_CALLBACK(change_dict_cb),
2235 gtk_widget_show(item);
2236 gtk_menu_append(GTK_MENU(curmenu), item);
2244 GSList *gtkaspell_make_config_menu(GtkAspell *gtkaspell)
2246 return populate_submenu(gtkaspell);
2249 static void set_menu_pos(GtkMenu *menu, gint *x, gint *y,
2250 gboolean *push_in, gpointer data)
2252 GtkAspell *gtkaspell = (GtkAspell *) data;
2253 gint xx = 0, yy = 0;
2256 GtkTextView *text = GTK_TEXT_VIEW(gtkaspell->gtktext);
2257 GtkTextBuffer *textbuf;
2262 textbuf = gtk_text_view_get_buffer(gtkaspell->gtktext);
2263 gtk_text_buffer_get_iter_at_mark(textbuf, &iter,
2264 gtk_text_buffer_get_insert(textbuf));
2265 gtk_text_view_get_iter_location(gtkaspell->gtktext, &iter, &rect);
2266 gtk_text_view_buffer_to_window_coords(text, GTK_TEXT_WINDOW_TEXT,
2270 gdk_window_get_origin(GTK_WIDGET(gtkaspell->gtktext)->window, &xx, &yy);
2272 sx = gdk_screen_width();
2273 sy = gdk_screen_height();
2275 gtk_widget_get_child_requisition(GTK_WIDGET(menu), &r);
2281 gdk_char_width(gtk_style_get_font(GTK_WIDGET(text)->style), ' ');
2283 *y = rect.y + rect.height + yy;
2289 gdk_string_height(gtk_style_get_font(
2290 GTK_WIDGET(text)->style),
2291 gtkaspell->theword);
2294 /* change the current dictionary of gtkaspell
2295 - if always_set_alt_dict is set, the alternate dict is unconditionally set to the former
2296 current dictionary (common use: from menu callbacks)
2297 - if always_set_alt_dict is NOT set, the alternate dict will be set to the former
2298 current dictionary only if there is no alternate dictionary already set
2299 (this is when we need to set the current dictionary then the alternate one
2300 when creating a compose window, from the account and folder settings)
2302 gboolean gtkaspell_change_dict(GtkAspell *gtkaspell, const gchar *dictionary,
2303 gboolean always_set_alt_dict)
2306 GtkAspeller *gtkaspeller;
2309 g_return_val_if_fail(gtkaspell, FALSE);
2310 g_return_val_if_fail(dictionary, FALSE);
2312 sug_mode = gtkaspell->default_sug_mode;
2314 dict = g_new0(Dictionary, 1);
2315 dict->fullname = g_strdup(dictionary);
2316 dict->encoding = g_strdup(gtkaspell->gtkaspeller->dictionary->encoding);
2318 gtkaspeller = gtkaspeller_new(dict);
2322 message = g_strdup_printf(_("The spell checker could not change dictionary.\n%s"),
2323 gtkaspellcheckers->error_message);
2325 alertpanel_warning(message);
2328 if (gtkaspell->use_alternate) {
2329 if (gtkaspell->alternate_speller) {
2330 if (always_set_alt_dict) {
2331 gtkaspeller_delete(gtkaspell->alternate_speller);
2332 gtkaspell->alternate_speller = gtkaspell->gtkaspeller;
2334 gtkaspeller_delete(gtkaspell->gtkaspeller);
2336 /* should never be reached as the dicts are always set
2337 to a default value */
2338 gtkaspell->alternate_speller = gtkaspell->gtkaspeller;
2340 gtkaspeller_delete(gtkaspell->gtkaspeller);
2342 gtkaspell->gtkaspeller = gtkaspeller;
2343 gtkaspell_set_sug_mode(gtkaspell, sug_mode);
2346 dictionary_delete(dict);
2351 /* change the alternate dictionary of gtkaspell (doesn't affect the default dictionary) */
2352 gboolean gtkaspell_change_alt_dict(GtkAspell *gtkaspell, const gchar *alt_dictionary)
2355 GtkAspeller *gtkaspeller;
2357 g_return_val_if_fail(gtkaspell, FALSE);
2358 g_return_val_if_fail(alt_dictionary, FALSE);
2360 dict = g_new0(Dictionary, 1);
2361 dict->fullname = g_strdup(alt_dictionary);
2362 dict->encoding = g_strdup(gtkaspell->gtkaspeller->dictionary->encoding);
2364 gtkaspeller = gtkaspeller_new(dict);
2368 message = g_strdup_printf(_("The spell checker could not change the alternate dictionary.\n%s"),
2369 gtkaspellcheckers->error_message);
2371 alertpanel_warning(message);
2374 if (gtkaspell->alternate_speller)
2375 gtkaspeller_delete(gtkaspell->alternate_speller);
2376 gtkaspell->alternate_speller = gtkaspeller;
2379 dictionary_delete(dict);
2384 /* Menu call backs */
2386 /* change_dict_cb() - Menu callback : change dict */
2387 static void change_dict_cb(GtkWidget *w, GtkAspell *gtkaspell)
2391 fullname = (gchar *) g_object_get_data(G_OBJECT(w), "dict_name");
2393 if (!strcmp2(fullname, _("None")))
2396 gtkaspell_change_dict(gtkaspell, fullname, TRUE);
2397 if (gtkaspell->recheck_when_changing_dict) {
2398 gtkaspell_highlight_all(gtkaspell);
2400 if (gtkaspell->menu_changed_cb)
2401 gtkaspell->menu_changed_cb(gtkaspell->menu_changed_data);
2404 static void switch_to_alternate_cb(GtkWidget *w,
2407 GtkAspell *gtkaspell = (GtkAspell *) data;
2408 use_alternate_dict(gtkaspell);
2409 if (gtkaspell->recheck_when_changing_dict) {
2410 gtkaspell_highlight_all(gtkaspell);
2412 if (gtkaspell->menu_changed_cb)
2413 gtkaspell->menu_changed_cb(gtkaspell->menu_changed_data);
2416 /* Misc. helper functions */
2418 static void set_point_continue(GtkAspell *gtkaspell)
2420 GtkTextView *gtktext;
2422 gtktext = gtkaspell->gtktext;
2424 set_textview_buffer_offset(gtktext, gtkaspell->orig_pos);
2426 if (gtkaspell->continue_check)
2427 gtkaspell->continue_check((gpointer *) gtkaspell);
2430 static void allocate_color(GtkAspell *gtkaspell, gint rgbvalue)
2432 GtkTextBuffer *buffer = gtk_text_view_get_buffer(gtkaspell->gtktext);
2433 GdkColor *color = &(gtkaspell->highlight);
2435 /* Shameless copy from Sylpheed's gtkutils.c */
2437 color->red = (int) (((gdouble)((rgbvalue & 0xff0000) >> 16) / 255.0)
2439 color->green = (int) (((gdouble)((rgbvalue & 0x00ff00) >> 8) / 255.0)
2441 color->blue = (int) (((gdouble) (rgbvalue & 0x0000ff) / 255.0)
2445 gtk_text_buffer_create_tag(buffer, "misspelled",
2446 "foreground-gdk", color, NULL);
2448 gtk_text_buffer_create_tag(buffer, "misspelled",
2449 "underline", PANGO_UNDERLINE_ERROR, NULL);
2453 static void change_color(GtkAspell * gtkaspell,
2454 gint start, gint end,
2458 GtkTextView *gtktext;
2459 GtkTextBuffer *buffer;
2460 GtkTextIter startiter, enditer;
2465 gtktext = gtkaspell->gtktext;
2467 buffer = gtk_text_view_get_buffer(gtktext);
2468 gtk_text_buffer_get_iter_at_offset(buffer, &startiter, start);
2469 gtk_text_buffer_get_iter_at_offset(buffer, &enditer, end);
2471 gtk_text_buffer_apply_tag_by_name(buffer, "misspelled",
2472 &startiter, &enditer);
2474 gtk_text_iter_forward_char(&enditer);
2475 gtk_text_buffer_remove_tag_by_name(buffer, "misspelled",
2476 &startiter, &enditer);
2480 /* convert_to_aspell_encoding () - converts ISO-8859-* strings to iso8859-*
2481 * as needed by aspell. Returns an allocated string.
2484 static gchar *convert_to_aspell_encoding (const gchar *encoding)
2486 gchar * aspell_encoding;
2488 if (strstr2(encoding, "ISO-8859-")) {
2489 aspell_encoding = g_strdup_printf("iso8859%s", encoding+8);
2492 if (!strcmp2(encoding, "US-ASCII"))
2493 aspell_encoding = g_strdup("iso8859-1");
2495 aspell_encoding = g_strdup(encoding);
2498 return aspell_encoding;
2501 /* compare_dict () - compare 2 dict names */
2502 static gint compare_dict(Dictionary *a, Dictionary *b)
2504 guint aparts = 0, bparts = 0;
2507 for (i=0; i < strlen(a->dictname); i++)
2508 if (a->dictname[i] == '-')
2510 for (i=0; i < strlen(b->dictname); i++)
2511 if (b->dictname[i] == '-')
2514 if (aparts != bparts)
2515 return (aparts < bparts) ? -1 : +1;
2518 compare = strcmp2(a->dictname, b->dictname);
2520 compare = strcmp2(a->fullname, b->fullname);
2525 static void dictionary_delete(Dictionary *dict)
2527 g_free(dict->fullname);
2528 g_free(dict->encoding);
2532 static Dictionary *dictionary_dup(const Dictionary *dict)
2536 dict2 = g_new(Dictionary, 1);
2538 dict2->fullname = g_strdup(dict->fullname);
2539 dict2->dictname = dict->dictname - dict->fullname + dict2->fullname;
2540 dict2->encoding = g_strdup(dict->encoding);
2545 static void free_suggestions_list(GtkAspell *gtkaspell)
2549 for (list = gtkaspell->suggestions_list; list != NULL;
2553 g_list_free(gtkaspell->suggestions_list);
2555 gtkaspell->max_sug = -1;
2556 gtkaspell->suggestions_list = NULL;
2559 static void reset_theword_data(GtkAspell *gtkaspell)
2561 gtkaspell->start_pos = 0;
2562 gtkaspell->end_pos = 0;
2563 gtkaspell->theword[0] = 0;
2564 gtkaspell->max_sug = -1;
2566 free_suggestions_list(gtkaspell);
2569 static void free_checkers(gpointer elt, gpointer data)
2571 GtkAspeller *gtkaspeller = elt;
2573 g_return_if_fail(gtkaspeller);
2575 gtkaspeller_real_delete(gtkaspeller);
2578 static gint find_gtkaspeller(gconstpointer aa, gconstpointer bb)
2580 Dictionary *a = ((GtkAspeller *) aa)->dictionary;
2581 Dictionary *b = ((GtkAspeller *) bb)->dictionary;
2583 if (a && b && a->fullname && b->fullname &&
2584 strcmp(a->fullname, b->fullname) == 0 &&
2585 a->encoding && b->encoding)
2586 return strcmp(a->encoding, b->encoding);
2591 gchar *gtkaspell_get_default_dictionary(GtkAspell *gtkaspell)
2593 if (gtkaspell && gtkaspell->gtkaspeller &&
2594 gtkaspell->gtkaspeller->dictionary)
2595 return gtkaspell->gtkaspeller->dictionary->dictname;