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 2 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, write to the Free Software
17 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
20 * Stuphead: (C) 2000,2001 Grigroy Bakunov, Sergey Pinaev
21 * Adapted for Sylpheed (Claws) (c) 2001-2002 by Hiroyuki Yamamoto &
22 * The Sylpheed Claws Team.
23 * Adapted for pspell (c) 2001-2002 Melvin Hadasht
24 * Adapted for GNU/aspell (c) 2002 Melvin Hadasht
36 #include <sys/types.h>
49 #include <gtk/gtkoptionmenu.h>
50 #include <gtk/gtkmenu.h>
51 #include <gtk/gtkmenuitem.h>
52 #include <gdk/gdkkeysyms.h>
56 #include "prefs_common.h"
58 #include "gtkaspell.h"
60 /* size of the text buffer used in various word-processing routines. */
63 /* number of suggestions to display on each menu. */
66 /* 'config' must be defined as a 'AspellConfig *' */
67 #define RETURN_FALSE_IF_CONFIG_ERROR() \
69 if (aspell_config_error_number(config) != 0) { \
70 gtkaspellcheckers->error_message = g_strdup(aspell_config_error_message(config)); \
75 #define CONFIG_REPLACE_RETURN_FALSE_IF_FAIL(option, value) { \
76 aspell_config_replace(config, option, value); \
77 RETURN_FALSE_IF_CONFIG_ERROR(); \
80 /******************************************************************************/
82 GtkAspellCheckers *gtkaspellcheckers;
84 /* Error message storage */
85 static void gtkaspell_checkers_error_message (gchar *message);
88 static void entry_insert_cb (GtkSText *gtktext,
92 GtkAspell *gtkaspell);
93 static void entry_delete_cb (GtkSText *gtktext,
96 GtkAspell *gtkaspell);
97 static gint button_press_intercept_cb (GtkSText *gtktext,
99 GtkAspell *gtkaspell);
101 /* Checker creation */
102 static GtkAspeller* gtkaspeller_new (Dictionary *dict);
103 static GtkAspeller* gtkaspeller_real_new (Dictionary *dict);
104 static GtkAspeller* gtkaspeller_delete (GtkAspeller *gtkaspeller);
105 static GtkAspeller* gtkaspeller_real_delete (GtkAspeller *gtkaspeller);
107 /* Checker configuration */
108 static gint set_dictionary (AspellConfig *config,
110 static void set_sug_mode_cb (GtkMenuItem *w,
111 GtkAspell *gtkaspell);
112 static void set_real_sug_mode (GtkAspell *gtkaspell,
113 const char *themode);
115 /* Checker actions */
116 static gboolean check_at (GtkAspell *gtkaspell,
118 static gboolean check_next_prev (GtkAspell *gtkaspell,
120 static GList* misspelled_suggest (GtkAspell *gtkaspell,
122 static void add_word_to_session_cb (GtkWidget *w,
124 static void add_word_to_personal_cb (GtkWidget *w,
126 static void replace_with_create_dialog_cb (GtkWidget *w,
128 static void replace_with_supplied_word_cb (GtkWidget *w,
129 GtkAspell *gtkaspell);
130 static void replace_word_cb (GtkWidget *w,
132 static void replace_real_word (GtkAspell *gtkaspell,
134 static void check_with_alternate_cb (GtkWidget *w,
136 static void use_alternate_dict (GtkAspell *gtkaspell);
137 static void toggle_check_while_typing_cb (GtkWidget *w,
141 static void popup_menu (GtkAspell *gtkaspell,
143 static GtkMenu* make_sug_menu (GtkAspell *gtkaspell);
144 static void populate_submenu (GtkAspell *gtkaspell,
146 static GtkMenu* make_config_menu (GtkAspell *gtkaspell);
147 static void set_menu_pos (GtkMenu *menu,
151 /* Other menu callbacks */
152 static gboolean cancel_menu_cb (GtkMenuShell *w,
154 static void change_dict_cb (GtkWidget *w,
155 GtkAspell *gtkaspell);
156 static void switch_to_alternate_cb (GtkWidget *w,
159 /* Misc. helper functions */
160 static void set_point_continue (GtkAspell *gtkaspell);
161 static void continue_check (gpointer *gtkaspell);
162 static gboolean iswordsep (unsigned char c);
163 static guchar get_text_index_whar (GtkAspell *gtkaspell,
165 static gboolean get_word_from_pos (GtkAspell *gtkaspell,
171 static void allocate_color (GtkAspell *gtkaspell,
173 static void change_color (GtkAspell *gtkaspell,
178 static guchar* convert_to_aspell_encoding (const guchar *encoding);
179 static gint compare_dict (Dictionary *a,
181 static void dictionary_delete (Dictionary *dict);
182 static Dictionary * dictionary_dup (const Dictionary *dict);
183 static void free_suggestions_list (GtkAspell *gtkaspell);
184 static void reset_theword_data (GtkAspell *gtkaspell);
185 static void free_checkers (gpointer elt,
187 static gint find_gtkaspeller (gconstpointer aa,
189 static void gtkaspell_alert_dialog (gchar *message);
190 /* gtkspellconfig - only one config per session */
191 GtkAspellConfig * gtkaspellconfig;
193 /******************************************************************************/
195 GtkAspellCheckers *gtkaspell_checkers_new()
197 GtkAspellCheckers *gtkaspellcheckers;
199 gtkaspellcheckers = g_new(GtkAspellCheckers, 1);
200 gtkaspellcheckers->checkers = NULL;
201 gtkaspellcheckers->dictionary_list = NULL;
202 gtkaspellcheckers->error_message = NULL;
204 return gtkaspellcheckers;
207 GtkAspellCheckers *gtkaspell_checkers_delete()
212 if (gtkaspellcheckers == NULL)
215 if ((checkers = gtkaspellcheckers->checkers)) {
216 debug_print("Aspell: number of running checkers to delete %d\n",
217 g_slist_length(checkers));
219 g_slist_foreach(checkers, free_checkers, NULL);
220 g_slist_free(checkers);
223 if ((dict_list = gtkaspellcheckers->dictionary_list)) {
224 debug_print("Aspell: number of dictionaries to delete %d\n",
225 g_slist_length(dict_list));
227 gtkaspell_free_dictionary_list(dict_list);
228 gtkaspellcheckers->dictionary_list = NULL;
231 g_free(gtkaspellcheckers->error_message);
236 static void gtkaspell_checkers_error_message (gchar *message)
239 if (gtkaspellcheckers->error_message) {
240 tmp = g_strdup_printf("%s\n%s",
241 gtkaspellcheckers->error_message, message);
243 g_free(gtkaspellcheckers->error_message);
244 gtkaspellcheckers->error_message = tmp;
246 gtkaspellcheckers->error_message = message;
249 void gtkaspell_checkers_reset_error(void)
251 g_return_if_fail(gtkaspellcheckers);
253 g_free(gtkaspellcheckers->error_message);
255 gtkaspellcheckers->error_message = NULL;
258 GtkAspell *gtkaspell_new(const gchar *dictionary,
259 const gchar *encoding,
260 gint misspelled_color,
261 gboolean check_while_typing,
262 gboolean use_alternate,
266 GtkAspell *gtkaspell;
267 GtkAspeller *gtkaspeller;
269 g_return_val_if_fail(gtktext, NULL);
271 dict = g_new0(Dictionary, 1);
272 dict->fullname = g_strdup(dictionary);
273 dict->encoding = g_strdup(encoding);
275 gtkaspeller = gtkaspeller_new(dict);
276 dictionary_delete(dict);
281 gtkaspell = g_new(GtkAspell, 1);
283 gtkaspell->gtkaspeller = gtkaspeller;
284 gtkaspell->alternate_speller = NULL;
285 gtkaspell->theword[0] = 0x00;
286 gtkaspell->start_pos = 0;
287 gtkaspell->end_pos = 0;
288 gtkaspell->orig_pos = -1;
289 gtkaspell->end_check_pos = -1;
290 gtkaspell->misspelled = -1;
291 gtkaspell->check_while_typing = check_while_typing;
292 gtkaspell->continue_check = NULL;
293 gtkaspell->config_menu = NULL;
294 gtkaspell->popup_config_menu = NULL;
295 gtkaspell->sug_menu = NULL;
296 gtkaspell->replace_entry = NULL;
297 gtkaspell->gtktext = gtktext;
298 gtkaspell->default_sug_mode = ASPELL_FASTMODE;
299 gtkaspell->max_sug = -1;
300 gtkaspell->suggestions_list = NULL;
301 gtkaspell->use_alternate = use_alternate;
303 allocate_color(gtkaspell, misspelled_color);
305 gtk_signal_connect_after(GTK_OBJECT(gtktext), "insert-text",
306 GTK_SIGNAL_FUNC(entry_insert_cb), gtkaspell);
307 gtk_signal_connect_after(GTK_OBJECT(gtktext), "delete-text",
308 GTK_SIGNAL_FUNC(entry_delete_cb), gtkaspell);
309 gtk_signal_connect(GTK_OBJECT(gtktext), "button-press-event",
310 GTK_SIGNAL_FUNC(button_press_intercept_cb), gtkaspell);
312 debug_print("Aspell: created gtkaspell %0x\n", (guint) gtkaspell);
317 void gtkaspell_delete(GtkAspell * gtkaspell)
319 GtkSText *gtktext = gtkaspell->gtktext;
321 gtk_signal_disconnect_by_func(GTK_OBJECT(gtktext),
322 GTK_SIGNAL_FUNC(entry_insert_cb),
324 gtk_signal_disconnect_by_func(GTK_OBJECT(gtktext),
325 GTK_SIGNAL_FUNC(entry_delete_cb),
327 gtk_signal_disconnect_by_func(GTK_OBJECT(gtktext),
328 GTK_SIGNAL_FUNC(button_press_intercept_cb),
331 gtkaspell_uncheck_all(gtkaspell);
333 gtkaspeller_delete(gtkaspell->gtkaspeller);
335 if (gtkaspell->use_alternate && gtkaspell->alternate_speller)
336 gtkaspeller_delete(gtkaspell->alternate_speller);
338 if (gtkaspell->sug_menu)
339 gtk_widget_destroy(gtkaspell->sug_menu);
341 if (gtkaspell->popup_config_menu)
342 gtk_widget_destroy(gtkaspell->popup_config_menu);
344 if (gtkaspell->config_menu)
345 gtk_widget_destroy(gtkaspell->config_menu);
347 if (gtkaspell->suggestions_list)
348 free_suggestions_list(gtkaspell);
350 debug_print("Aspell: deleting gtkaspell %0x\n", (guint) gtkaspell);
357 static void entry_insert_cb(GtkSText *gtktext,
361 GtkAspell *gtkaspell)
363 g_return_if_fail(gtkaspell->gtkaspeller->checker);
365 if (!gtkaspell->check_while_typing)
368 /* We must insert ourselves the character so the
369 * color of the inserted character is the default color.
370 * Never mess with set_insertion when frozen.
373 gtk_stext_freeze(gtktext);
374 gtk_stext_backward_delete(GTK_STEXT(gtktext), len);
375 gtk_stext_insert(GTK_STEXT(gtktext), NULL, NULL, NULL, newtext, len);
376 *ppos = gtk_stext_get_point(GTK_STEXT(gtktext));
378 if (iswordsep(newtext[0])) {
379 /* did we just end a word? */
381 check_at(gtkaspell, *ppos - 2);
383 /* did we just split a word? */
384 if (*ppos < gtk_stext_get_length(gtktext))
385 check_at(gtkaspell, *ppos + 1);
387 /* check as they type, *except* if they're typing at the end (the most
390 if (*ppos < gtk_stext_get_length(gtktext) &&
391 !iswordsep(get_text_index_whar(gtkaspell, *ppos))) {
392 check_at(gtkaspell, *ppos - 1);
396 gtk_stext_thaw(gtktext);
397 gtk_editable_set_position(GTK_EDITABLE(gtktext), *ppos);
400 static void entry_delete_cb(GtkSText *gtktext,
403 GtkAspell *gtkaspell)
407 g_return_if_fail(gtkaspell->gtkaspeller->checker);
409 if (!gtkaspell->check_while_typing)
412 origpos = gtk_editable_get_position(GTK_EDITABLE(gtktext));
414 check_at(gtkaspell, start - 1);
415 check_at(gtkaspell, start);
418 gtk_editable_set_position(GTK_EDITABLE(gtktext), origpos);
419 gtk_stext_set_point(gtktext, origpos);
420 /* this is to *UNDO* the selection, in case they were holding shift
421 * while hitting backspace. */
422 gtk_editable_select_region(GTK_EDITABLE(gtktext), origpos, origpos);
425 /* ok, this is pretty wacky:
426 * we need to let the right-mouse-click go through, so it moves the cursor,
427 * but we *can't* let it go through, because GtkText interprets rightclicks as
428 * weird selection modifiers.
430 * so what do we do? forge rightclicks as leftclicks, then popup the menu.
433 static gint button_press_intercept_cb(GtkSText *gtktext, GdkEvent *e, GtkAspell *gtkaspell)
438 g_return_val_if_fail(gtkaspell->gtkaspeller->checker, FALSE);
440 if (e->type != GDK_BUTTON_PRESS)
442 eb = (GdkEventButton*) e;
447 /* forge the leftclick */
450 gtk_signal_handler_block_by_func(GTK_OBJECT(gtktext),
451 GTK_SIGNAL_FUNC(button_press_intercept_cb),
453 gtk_signal_emit_by_name(GTK_OBJECT(gtktext), "button-press-event",
455 gtk_signal_handler_unblock_by_func(GTK_OBJECT(gtktext),
456 GTK_SIGNAL_FUNC(button_press_intercept_cb),
458 gtk_signal_emit_stop_by_name(GTK_OBJECT(gtktext), "button-press-event");
460 /* now do the menu wackiness */
461 popup_menu(gtkaspell, eb);
462 gtk_grab_remove(GTK_WIDGET(gtktext));
466 /* Checker creation */
467 static GtkAspeller *gtkaspeller_new(Dictionary *dictionary)
470 GtkAspeller *gtkaspeller = NULL;
474 g_return_val_if_fail(gtkaspellcheckers, NULL);
476 g_return_val_if_fail(dictionary, NULL);
478 if (dictionary->fullname == NULL)
479 gtkaspell_checkers_error_message(g_strdup(_("No dictionary selected.")));
481 g_return_val_if_fail(dictionary->fullname, NULL);
483 if (dictionary->dictname == NULL) {
486 tmp = strrchr(dictionary->fullname, G_DIR_SEPARATOR);
489 dictionary->dictname = dictionary->fullname;
491 dictionary->dictname = tmp + 1;
494 dict = dictionary_dup(dictionary);
496 tmp = g_new0(GtkAspeller, 1);
497 tmp->dictionary = dict;
499 exist = g_slist_find_custom(gtkaspellcheckers->checkers, tmp,
504 if ((gtkaspeller = gtkaspeller_real_new(dict)) != NULL) {
505 gtkaspellcheckers->checkers = g_slist_append(
506 gtkaspellcheckers->checkers,
509 debug_print("Aspell: Created a new gtkaspeller %0x\n",
512 dictionary_delete(dict);
514 debug_print("Aspell: Could not create spell checker.\n");
517 debug_print("Aspell: number of existing checkers %d\n",
518 g_slist_length(gtkaspellcheckers->checkers));
523 static GtkAspeller *gtkaspeller_real_new(Dictionary *dict)
525 GtkAspeller *gtkaspeller;
526 AspellConfig *config;
527 AspellCanHaveError *ret;
529 g_return_val_if_fail(gtkaspellcheckers, NULL);
530 g_return_val_if_fail(dict, NULL);
532 gtkaspeller = g_new(GtkAspeller, 1);
534 gtkaspeller->dictionary = dict;
535 gtkaspeller->sug_mode = ASPELL_FASTMODE;
537 config = new_aspell_config();
539 if (!set_dictionary(config, dict))
542 ret = new_aspell_speller(config);
543 delete_aspell_config(config);
545 if (aspell_error_number(ret) != 0) {
546 gtkaspellcheckers->error_message = g_strdup(aspell_error_message(ret));
548 delete_aspell_can_have_error(ret);
553 gtkaspeller->checker = to_aspell_speller(ret);
554 gtkaspeller->config = aspell_speller_config(gtkaspeller->checker);
559 static GtkAspeller *gtkaspeller_delete(GtkAspeller *gtkaspeller)
561 g_return_val_if_fail(gtkaspellcheckers, NULL);
563 gtkaspellcheckers->checkers =
564 g_slist_remove(gtkaspellcheckers->checkers,
567 debug_print("Aspell: Deleting gtkaspeller %0x.\n",
570 gtkaspeller_real_delete(gtkaspeller);
572 debug_print("Aspell: number of existing checkers %d\n",
573 g_slist_length(gtkaspellcheckers->checkers));
578 static GtkAspeller *gtkaspeller_real_delete(GtkAspeller *gtkaspeller)
580 g_return_val_if_fail(gtkaspeller, NULL);
581 g_return_val_if_fail(gtkaspeller->checker, NULL);
583 aspell_speller_save_all_word_lists(gtkaspeller->checker);
585 delete_aspell_speller(gtkaspeller->checker);
587 dictionary_delete(gtkaspeller->dictionary);
589 debug_print("Aspell: gtkaspeller %0x deleted.\n",
597 /*****************************************************************************/
598 /* Checker configuration */
600 static gboolean set_dictionary(AspellConfig *config, Dictionary *dict)
602 gchar *language = NULL;
603 gchar *jargon = NULL;
608 g_return_val_if_fail(config, FALSE);
609 g_return_val_if_fail(dict, FALSE);
611 strncpy(buf, dict->fullname, BUFSIZE-1);
612 buf[BUFSIZE-1] = 0x00;
614 buf[dict->dictname - dict->fullname] = 0x00;
616 CONFIG_REPLACE_RETURN_FALSE_IF_FAIL("dict-dir", buf);
617 debug_print("Aspell: looking for dictionaries in path %s.\n", buf);
619 strncpy(buf, dict->dictname, BUFSIZE-1);
622 if ((size = strrchr(buf, '-')) && isdigit((int) size[1]))
627 if ((jargon = strchr(language, '-')) != NULL)
630 if (size != NULL && jargon == size)
633 debug_print("Aspell: language: %s, jargon: %s, size: %s\n",
634 language, jargon, size);
637 CONFIG_REPLACE_RETURN_FALSE_IF_FAIL("lang", language);
639 CONFIG_REPLACE_RETURN_FALSE_IF_FAIL("jargon", jargon);
641 CONFIG_REPLACE_RETURN_FALSE_IF_FAIL("size", size);
642 if (dict->encoding) {
645 aspell_enc = convert_to_aspell_encoding (dict->encoding);
646 aspell_config_replace(config, "encoding", (const char *) aspell_enc);
649 RETURN_FALSE_IF_CONFIG_ERROR();
655 guchar *gtkaspell_get_dict(GtkAspell *gtkaspell)
658 g_return_val_if_fail(gtkaspell->gtkaspeller->config, NULL);
659 g_return_val_if_fail(gtkaspell->gtkaspeller->dictionary, NULL);
661 return g_strdup(gtkaspell->gtkaspeller->dictionary->dictname);
664 guchar *gtkaspell_get_path(GtkAspell *gtkaspell)
669 g_return_val_if_fail(gtkaspell->gtkaspeller->config, NULL);
670 g_return_val_if_fail(gtkaspell->gtkaspeller->dictionary, NULL);
672 dict = gtkaspell->gtkaspeller->dictionary;
673 path = g_strndup(dict->fullname, dict->dictname - dict->fullname);
678 /* set_sug_mode_cb() - Menu callback: Set the suggestion mode */
679 static void set_sug_mode_cb(GtkMenuItem *w, GtkAspell *gtkaspell)
683 gtk_label_get(GTK_LABEL(GTK_BIN(w)->child), (gchar **) &themode);
685 set_real_sug_mode(gtkaspell, themode);
687 if (gtkaspell->config_menu)
688 populate_submenu(gtkaspell, gtkaspell->config_menu);
691 static void set_real_sug_mode(GtkAspell *gtkaspell, const char *themode)
694 gint mode = ASPELL_FASTMODE;
695 g_return_if_fail(gtkaspell);
696 g_return_if_fail(gtkaspell->gtkaspeller);
697 g_return_if_fail(themode);
699 if (!strcmp(themode,_("Normal Mode")))
700 mode = ASPELL_NORMALMODE;
701 else if (!strcmp( themode,_("Bad Spellers Mode")))
702 mode = ASPELL_BADSPELLERMODE;
704 result = gtkaspell_set_sug_mode(gtkaspell, mode);
707 debug_print("Aspell: error while changing suggestion mode:%s\n",
708 gtkaspellcheckers->error_message);
709 gtkaspell_checkers_reset_error();
713 /* gtkaspell_set_sug_mode() - Set the suggestion mode */
714 gboolean gtkaspell_set_sug_mode(GtkAspell *gtkaspell, gint themode)
716 AspellConfig *config;
718 g_return_val_if_fail(gtkaspell, FALSE);
719 g_return_val_if_fail(gtkaspell->gtkaspeller, FALSE);
720 g_return_val_if_fail(gtkaspell->gtkaspeller->config, FALSE);
722 debug_print("Aspell: setting sug mode of gtkaspeller %0x to %d\n",
723 (guint) gtkaspell->gtkaspeller, themode);
725 config = gtkaspell->gtkaspeller->config;
728 case ASPELL_FASTMODE:
729 CONFIG_REPLACE_RETURN_FALSE_IF_FAIL("sug-mode", "fast");
731 case ASPELL_NORMALMODE:
732 CONFIG_REPLACE_RETURN_FALSE_IF_FAIL("sug-mode", "normal");
734 case ASPELL_BADSPELLERMODE:
735 CONFIG_REPLACE_RETURN_FALSE_IF_FAIL("sug-mode",
739 gtkaspellcheckers->error_message =
740 g_strdup(_("Unknown suggestion mode."));
744 gtkaspell->gtkaspeller->sug_mode = themode;
745 gtkaspell->default_sug_mode = themode;
750 /* misspelled_suggest() - Create a suggestion list for word */
751 static GList *misspelled_suggest(GtkAspell *gtkaspell, guchar *word)
753 const guchar *newword;
755 const AspellWordList *suggestions;
756 AspellStringEnumeration *elements;
758 g_return_val_if_fail(word, NULL);
760 if (!aspell_speller_check(gtkaspell->gtkaspeller->checker, word, -1)) {
761 free_suggestions_list(gtkaspell);
763 suggestions = aspell_speller_suggest(gtkaspell->gtkaspeller->checker,
764 (const char *)word, -1);
765 elements = aspell_word_list_elements(suggestions);
766 list = g_list_append(list, g_strdup(word));
768 while ((newword = aspell_string_enumeration_next(elements)) != NULL)
769 list = g_list_append(list, g_strdup(newword));
771 gtkaspell->max_sug = g_list_length(list) - 1;
772 gtkaspell->suggestions_list = list;
777 free_suggestions_list(gtkaspell);
782 /* misspelled_test() - Just test if word is correctly spelled */
783 static int misspelled_test(GtkAspell *gtkaspell, unsigned char *word)
785 return aspell_speller_check(gtkaspell->gtkaspeller->checker, word, -1) ? 0 : 1;
789 static gboolean iswordsep(unsigned char c)
791 return !isalpha(c) && c != '\'';
794 static guchar get_text_index_whar(GtkAspell *gtkaspell, int pos)
799 text = gtk_editable_get_chars(GTK_EDITABLE(gtkaspell->gtktext), pos,
811 /* get_word_from_pos () - return the word pointed to. */
812 /* Handles correctly the quotes. */
813 static gboolean get_word_from_pos(GtkAspell *gtkaspell, gint pos,
814 unsigned char* buf, gint buflen,
815 gint *pstart, gint *pend)
818 /* TODO : when correcting a word into quotes, change the color of */
819 /* the quotes too, as may be they were highlighted before. To do */
820 /* this, we can use two others pointers that points to the whole */
821 /* word including quotes. */
829 gtktext = gtkaspell->gtktext;
830 if (iswordsep(get_text_index_whar(gtkaspell, pos)))
833 /* The apostrophe character is somtimes used for quotes
834 * So include it in the word only if it is not surrounded
835 * by other characters.
838 for (start = pos; start >= 0; --start) {
839 c = get_text_index_whar(gtkaspell, start);
842 if (!isalpha(get_text_index_whar(gtkaspell,
844 /* start_quote = TRUE; */
849 /* start_quote = TRUE; */
853 else if (!isalpha(c))
859 for (end = pos; end < gtk_stext_get_length(gtktext); end++) {
860 c = get_text_index_whar(gtkaspell, end);
862 if (end < gtk_stext_get_length(gtktext)) {
863 if (!isalpha(get_text_index_whar(gtkaspell,
865 /* end_quote = TRUE; */
870 /* end_quote = TRUE; */
884 if (end - start < buflen) {
885 for (pos = start; pos < end; pos++)
887 get_text_index_whar(gtkaspell, pos);
888 buf[pos - start] = 0;
896 static gboolean check_at(GtkAspell *gtkaspell, gint from_pos)
899 unsigned char buf[GTKASPELLWORDSIZE];
902 g_return_val_if_fail(from_pos >= 0, FALSE);
904 gtktext = gtkaspell->gtktext;
906 if (!get_word_from_pos(gtkaspell, from_pos, buf, sizeof(buf),
910 if (misspelled_test(gtkaspell, buf)) {
911 strncpy(gtkaspell->theword, buf, GTKASPELLWORDSIZE - 1);
912 gtkaspell->theword[GTKASPELLWORDSIZE - 1] = 0;
913 gtkaspell->start_pos = start;
914 gtkaspell->end_pos = end;
915 free_suggestions_list(gtkaspell);
917 change_color(gtkaspell, start, end, buf, &(gtkaspell->highlight));
920 change_color(gtkaspell, start, end, buf, NULL);
925 static gboolean check_next_prev(GtkAspell *gtkaspell, gboolean forward)
934 maxpos = gtkaspell->end_check_pos;
942 pos = gtk_editable_get_position(GTK_EDITABLE(gtkaspell->gtktext));
943 gtkaspell->orig_pos = pos;
944 while (iswordsep(get_text_index_whar(gtkaspell, pos)) &&
945 pos > minpos && pos <= maxpos)
947 while (!(misspelled = check_at(gtkaspell, pos)) &&
948 pos > minpos && pos <= maxpos) {
950 while (!iswordsep(get_text_index_whar(gtkaspell, pos)) &&
951 pos > minpos && pos <= maxpos)
954 while (iswordsep(get_text_index_whar(gtkaspell, pos)) &&
955 pos > minpos && pos <= maxpos)
959 misspelled_suggest(gtkaspell, gtkaspell->theword);
962 gtkaspell->orig_pos = gtkaspell->end_pos;
964 gtk_stext_set_point(GTK_STEXT(gtkaspell->gtktext),
966 gtk_editable_set_position(GTK_EDITABLE(gtkaspell->gtktext),
968 gtk_menu_popup(make_sug_menu(gtkaspell), NULL, NULL,
969 set_menu_pos, gtkaspell, 0, GDK_CURRENT_TIME);
971 reset_theword_data(gtkaspell);
973 gtkaspell_alert_dialog(_("No misspelled word found."));
974 gtk_stext_set_point(GTK_STEXT(gtkaspell->gtktext),
975 gtkaspell->orig_pos);
976 gtk_editable_set_position(GTK_EDITABLE(gtkaspell->gtktext),
977 gtkaspell->orig_pos);
984 void gtkaspell_check_backwards(GtkAspell *gtkaspell)
986 gtkaspell->continue_check = NULL;
987 gtkaspell->end_check_pos =
988 gtk_stext_get_length(GTK_STEXT(gtkaspell->gtktext));
989 check_next_prev(gtkaspell, FALSE);
992 void gtkaspell_check_forwards_go(GtkAspell *gtkaspell)
995 gtkaspell->continue_check = NULL;
996 gtkaspell->end_check_pos
997 = gtk_stext_get_length(GTK_STEXT(gtkaspell->gtktext));
998 check_next_prev(gtkaspell, TRUE);
1001 void gtkaspell_check_all(GtkAspell *gtkaspell)
1006 g_return_if_fail(gtkaspell);
1007 g_return_if_fail(gtkaspell->gtktext);
1009 gtktext = (GtkWidget *) gtkaspell->gtktext;
1012 end = gtk_stext_get_length(GTK_STEXT(gtktext));
1014 if (GTK_EDITABLE(gtktext)->has_selection) {
1015 start = GTK_EDITABLE(gtktext)->selection_start_pos;
1016 end = GTK_EDITABLE(gtktext)->selection_end_pos;
1028 gtk_editable_set_position(GTK_EDITABLE(gtktext), start);
1029 gtk_stext_set_point(GTK_STEXT(gtktext), start);
1031 gtkaspell->continue_check = continue_check;
1032 gtkaspell->end_check_pos = end;
1034 gtkaspell->misspelled = check_next_prev(gtkaspell, TRUE);
1038 static void continue_check(gpointer *data)
1040 GtkAspell *gtkaspell = (GtkAspell *) data;
1041 gint pos = gtk_editable_get_position(GTK_EDITABLE(gtkaspell->gtktext));
1042 if (pos < gtkaspell->end_check_pos && gtkaspell->misspelled)
1043 gtkaspell->misspelled = check_next_prev(gtkaspell, TRUE);
1045 gtkaspell->continue_check = NULL;
1049 void gtkaspell_highlight_all(GtkAspell *gtkaspell)
1057 g_return_if_fail(gtkaspell->gtkaspeller->checker);
1059 gtktext = gtkaspell->gtktext;
1061 adj_value = gtktext->vadj->value;
1063 len = gtk_stext_get_length(gtktext);
1065 origpos = gtk_editable_get_position(GTK_EDITABLE(gtktext));
1069 iswordsep(get_text_index_whar(gtkaspell, pos)))
1072 !iswordsep(get_text_index_whar(gtkaspell, pos)))
1075 check_at(gtkaspell, pos - 1);
1077 gtk_editable_set_position(GTK_EDITABLE(gtktext), origpos);
1078 gtk_stext_set_point(GTK_STEXT(gtktext), origpos);
1079 gtk_adjustment_set_value(gtktext->vadj, adj_value);
1082 static void replace_with_supplied_word_cb(GtkWidget *w, GtkAspell *gtkaspell)
1084 unsigned char *newword;
1085 GdkEvent *e= (GdkEvent *) gtk_get_current_event();
1087 newword = gtk_editable_get_chars(GTK_EDITABLE(gtkaspell->replace_entry),
1090 if (strcmp(newword, gtkaspell->theword)) {
1091 replace_real_word(gtkaspell, newword);
1093 if ((e->type == GDK_KEY_PRESS &&
1094 ((GdkEventKey *) e)->state & GDK_MOD1_MASK)) {
1095 aspell_speller_store_replacement(gtkaspell->gtkaspeller->checker,
1096 gtkaspell->theword, -1,
1099 gtkaspell->replace_entry = NULL;
1104 set_point_continue(gtkaspell);
1108 static void replace_word_cb(GtkWidget *w, gpointer data)
1110 unsigned char *newword;
1111 GtkAspell *gtkaspell = (GtkAspell *) data;
1112 GdkEvent *e= (GdkEvent *) gtk_get_current_event();
1114 gtk_label_get(GTK_LABEL(GTK_BIN(w)->child), (gchar**) &newword);
1116 replace_real_word(gtkaspell, newword);
1118 if ((e->type == GDK_KEY_PRESS &&
1119 ((GdkEventKey *) e)->state & GDK_MOD1_MASK) ||
1120 (e->type == GDK_BUTTON_RELEASE &&
1121 ((GdkEventButton *) e)->state & GDK_MOD1_MASK)) {
1122 aspell_speller_store_replacement(gtkaspell->gtkaspeller->checker,
1123 gtkaspell->theword, -1,
1127 gtk_menu_shell_deactivate(GTK_MENU_SHELL(w->parent));
1129 set_point_continue(gtkaspell);
1132 static void replace_real_word(GtkAspell *gtkaspell, gchar *newword)
1134 int oldlen, newlen, wordlen;
1137 gint start = gtkaspell->start_pos;
1140 if (!newword) return;
1142 gtktext = gtkaspell->gtktext;
1144 gtk_stext_freeze(GTK_STEXT(gtktext));
1145 origpos = gtkaspell->orig_pos;
1147 oldlen = gtkaspell->end_pos - gtkaspell->start_pos;
1148 wordlen = strlen(gtkaspell->theword);
1150 newlen = strlen(newword); /* FIXME: multybyte characters? */
1152 gtk_signal_handler_block_by_func(GTK_OBJECT(gtktext),
1153 GTK_SIGNAL_FUNC(entry_insert_cb),
1155 gtk_signal_handler_block_by_func(GTK_OBJECT(gtktext),
1156 GTK_SIGNAL_FUNC(entry_delete_cb),
1159 gtk_signal_emit_by_name(GTK_OBJECT(gtktext), "delete-text",
1160 gtkaspell->start_pos, gtkaspell->end_pos);
1161 gtk_signal_emit_by_name(GTK_OBJECT(gtktext), "insert-text",
1162 newword, newlen, &start);
1164 gtk_signal_handler_unblock_by_func(GTK_OBJECT(gtktext),
1165 GTK_SIGNAL_FUNC(entry_insert_cb),
1167 gtk_signal_handler_unblock_by_func(GTK_OBJECT(gtktext),
1168 GTK_SIGNAL_FUNC(entry_delete_cb),
1171 /* Put the point and the position where we clicked with the mouse
1172 * It seems to be a hack, as I must thaw,freeze,thaw the widget
1173 * to let it update correctly the word insertion and then the
1174 * point & position position. If not, SEGV after the first replacement
1175 * If the new word ends before point, put the point at its end.
1178 if (origpos - gtkaspell->start_pos < oldlen &&
1179 origpos - gtkaspell->start_pos >= 0) {
1180 /* Original point was in the word.
1181 * Let it there unless point is going to be outside of the word
1183 if (origpos - gtkaspell->start_pos >= newlen) {
1184 pos = gtkaspell->start_pos + newlen;
1187 else if (origpos >= gtkaspell->end_pos) {
1188 /* move the position according to the change of length */
1189 pos = origpos + newlen - oldlen;
1192 gtkaspell->end_pos = gtkaspell->start_pos + strlen(newword); /* FIXME: multibyte characters? */
1194 gtk_stext_thaw(GTK_STEXT(gtktext));
1195 gtk_stext_freeze(GTK_STEXT(gtktext));
1197 if (GTK_STEXT(gtktext)->text_len < pos)
1198 pos = gtk_stext_get_length(GTK_STEXT(gtktext));
1200 gtkaspell->orig_pos = pos;
1202 gtk_editable_set_position(GTK_EDITABLE(gtktext), gtkaspell->orig_pos);
1203 gtk_stext_set_point(GTK_STEXT(gtktext),
1204 gtk_editable_get_position(GTK_EDITABLE(gtktext)));
1206 gtk_stext_thaw(GTK_STEXT(gtktext));
1209 /* Accept this word for this session */
1210 static void add_word_to_session_cb(GtkWidget *w, gpointer data)
1214 GtkAspell *gtkaspell = (GtkAspell *) data;
1215 gtktext = gtkaspell->gtktext;
1217 gtk_stext_freeze(GTK_STEXT(gtktext));
1219 pos = gtk_editable_get_position(GTK_EDITABLE(gtktext));
1221 aspell_speller_add_to_session(gtkaspell->gtkaspeller->checker,
1223 strlen(gtkaspell->theword));
1225 check_at(gtkaspell, gtkaspell->start_pos);
1227 gtk_stext_thaw(gtkaspell->gtktext);
1229 gtk_menu_shell_deactivate(GTK_MENU_SHELL(GTK_WIDGET(w)->parent));
1231 set_point_continue(gtkaspell);
1234 /* add_word_to_personal_cb() - add word to personal dict. */
1235 static void add_word_to_personal_cb(GtkWidget *w, gpointer data)
1237 GtkAspell *gtkaspell = (GtkAspell *) data;
1238 GtkSText *gtktext = gtkaspell->gtktext;
1240 gtk_stext_freeze(GTK_STEXT(gtktext));
1242 aspell_speller_add_to_personal(gtkaspell->gtkaspeller->checker,
1244 strlen(gtkaspell->theword));
1246 check_at(gtkaspell, gtkaspell->start_pos);
1248 gtk_stext_thaw(gtkaspell->gtktext);
1250 gtk_menu_shell_deactivate(GTK_MENU_SHELL(GTK_WIDGET(w)->parent));
1251 set_point_continue(gtkaspell);
1254 static void check_with_alternate_cb(GtkWidget *w, gpointer data)
1256 GtkAspell *gtkaspell = (GtkAspell *) data;
1259 gtk_menu_shell_deactivate(GTK_MENU_SHELL(GTK_WIDGET(w)->parent));
1261 use_alternate_dict(gtkaspell);
1262 misspelled = check_at(gtkaspell, gtkaspell->start_pos);
1264 if (!gtkaspell->continue_check) {
1266 gtkaspell->misspelled = misspelled;
1268 if (gtkaspell->misspelled) {
1270 misspelled_suggest(gtkaspell, gtkaspell->theword);
1272 gtk_stext_set_point(GTK_STEXT(gtkaspell->gtktext),
1273 gtkaspell->end_pos);
1274 gtk_editable_set_position(GTK_EDITABLE(gtkaspell->gtktext),
1275 gtkaspell->end_pos);
1277 gtk_menu_popup(make_sug_menu(gtkaspell), NULL, NULL,
1278 set_menu_pos, gtkaspell, 0,
1283 gtkaspell->orig_pos = gtkaspell->start_pos;
1285 set_point_continue(gtkaspell);
1288 static void replace_with_create_dialog_cb(GtkWidget *w, gpointer data)
1294 GtkWidget *ok_button;
1295 GtkWidget *cancel_button;
1298 GtkAspell *gtkaspell = (GtkAspell *) data;
1300 gdk_window_get_origin((GTK_WIDGET(w)->parent)->window, &xx, &yy);
1302 gtk_menu_shell_deactivate(GTK_MENU_SHELL(GTK_WIDGET(w)->parent));
1304 dialog = gtk_dialog_new();
1306 gtk_window_set_policy(GTK_WINDOW(dialog), FALSE, FALSE, FALSE);
1307 gtk_window_set_title(GTK_WINDOW(dialog),_("Replace unknown word"));
1308 gtk_widget_set_uposition(dialog, xx, yy);
1310 gtk_signal_connect_object(GTK_OBJECT(dialog), "destroy",
1311 GTK_SIGNAL_FUNC(gtk_widget_destroy),
1312 GTK_OBJECT(dialog));
1314 hbox = gtk_hbox_new(FALSE, 0);
1315 gtk_container_set_border_width(GTK_CONTAINER(hbox), 8);
1317 thelabel = g_strdup_printf(_("Replace \"%s\" with: "),
1318 gtkaspell->theword);
1319 label = gtk_label_new(thelabel);
1321 gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 0);
1323 entry = gtk_entry_new();
1324 gtkaspell->replace_entry = entry;
1325 gtk_entry_set_text(GTK_ENTRY(entry), gtkaspell->theword);
1326 gtk_editable_select_region(GTK_EDITABLE(entry), 0, -1);
1327 gtk_signal_connect(GTK_OBJECT(entry), "activate",
1328 GTK_SIGNAL_FUNC(replace_with_supplied_word_cb),
1330 gtk_signal_connect_object(GTK_OBJECT(entry), "activate",
1331 GTK_SIGNAL_FUNC(gtk_widget_destroy),
1332 GTK_OBJECT(dialog));
1333 gtk_box_pack_start(GTK_BOX(hbox), entry, TRUE, TRUE, 0);
1335 gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->vbox), hbox, TRUE,
1337 label = gtk_label_new(_("Holding down MOD1 key while pressing "
1338 "Enter\nwill learn from mistake.\n"));
1339 gtk_label_set_justify(GTK_LABEL(label), GTK_JUSTIFY_LEFT);
1340 gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.0);
1341 gtk_misc_set_padding(GTK_MISC(label), 8, 0);
1342 gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->vbox), label,
1345 hbox = gtk_hbox_new(TRUE, 0);
1347 ok_button = gtk_button_new_with_label(_("OK"));
1348 gtk_box_pack_start(GTK_BOX(hbox), ok_button, TRUE, TRUE, 8);
1349 gtk_signal_connect(GTK_OBJECT(ok_button), "clicked",
1350 GTK_SIGNAL_FUNC(replace_with_supplied_word_cb),
1352 gtk_signal_connect_object(GTK_OBJECT(ok_button), "clicked",
1353 GTK_SIGNAL_FUNC(gtk_widget_destroy),
1354 GTK_OBJECT(dialog));
1356 cancel_button = gtk_button_new_with_label(_("Cancel"));
1357 gtk_box_pack_start(GTK_BOX(hbox), cancel_button, TRUE, TRUE, 8);
1358 gtk_signal_connect_object(GTK_OBJECT(cancel_button), "clicked",
1359 GTK_SIGNAL_FUNC(gtk_widget_destroy),
1360 GTK_OBJECT(dialog));
1362 gtk_container_add(GTK_CONTAINER(GTK_DIALOG(dialog)->action_area), hbox);
1364 gtk_widget_grab_focus(entry);
1366 gtk_window_set_modal(GTK_WINDOW(dialog), TRUE);
1368 gtk_widget_show_all(dialog);
1371 void gtkaspell_uncheck_all(GtkAspell * gtkaspell)
1378 gtktext = gtkaspell->gtktext;
1380 adj_value = gtktext->vadj->value;
1382 gtk_stext_freeze(gtktext);
1384 origpos = gtk_editable_get_position(GTK_EDITABLE(gtktext));
1386 text = gtk_editable_get_chars(GTK_EDITABLE(gtktext), 0, -1);
1388 gtk_stext_set_point(gtktext, 0);
1389 gtk_stext_forward_delete(gtktext, gtk_stext_get_length(gtktext));
1390 gtk_stext_insert(gtktext, NULL, NULL, NULL, text, strlen(text));
1392 gtk_stext_thaw(gtktext);
1394 gtk_editable_set_position(GTK_EDITABLE(gtktext), origpos);
1395 gtk_stext_set_point(gtktext, origpos);
1396 gtk_adjustment_set_value(gtktext->vadj, adj_value);
1402 static void toggle_check_while_typing_cb(GtkWidget *w, gpointer data)
1404 GtkAspell *gtkaspell = (GtkAspell *) data;
1406 gtkaspell->check_while_typing = gtkaspell->check_while_typing == FALSE;
1408 if (!gtkaspell->check_while_typing)
1409 gtkaspell_uncheck_all(gtkaspell);
1411 if (gtkaspell->config_menu)
1412 populate_submenu(gtkaspell, gtkaspell->config_menu);
1415 static GSList *create_empty_dictionary_list(void)
1417 GSList *list = NULL;
1420 dict = g_new0(Dictionary, 1);
1421 dict->fullname = g_strdup(_("None"));
1422 dict->dictname = dict->fullname;
1423 dict->encoding = NULL;
1425 return g_slist_append(list, dict);
1428 /* gtkaspell_get_dictionary_list() - returns list of dictionary names */
1429 GSList *gtkaspell_get_dictionary_list(const gchar *aspell_path, gint refresh)
1432 gchar *dict_path, *tmp, *prevdir;
1433 gchar tmpname[BUFSIZE];
1435 AspellConfig *config;
1436 AspellDictInfoList *dlist;
1437 AspellDictInfoEnumeration *dels;
1438 const AspellDictInfo *entry;
1441 if (!gtkaspellcheckers)
1442 gtkaspellcheckers = gtkaspell_checkers_new();
1444 if (gtkaspellcheckers->dictionary_list && !refresh)
1445 return gtkaspellcheckers->dictionary_list;
1447 gtkaspell_free_dictionary_list(gtkaspellcheckers->dictionary_list);
1450 config = new_aspell_config();
1452 aspell_config_replace(config, "rem-all-word-list-path", "");
1453 if (aspell_config_error_number(config) != 0) {
1454 gtkaspellcheckers->error_message = g_strdup(
1455 aspell_config_error_message(config));
1456 gtkaspellcheckers->dictionary_list =
1457 create_empty_dictionary_list();
1459 return gtkaspellcheckers->dictionary_list;
1462 aspell_config_replace(config, "dict-dir", aspell_path);
1463 if (aspell_config_error_number(config) != 0) {
1464 gtkaspellcheckers->error_message = g_strdup(
1465 aspell_config_error_message(config));
1466 gtkaspellcheckers->dictionary_list =
1467 create_empty_dictionary_list();
1469 return gtkaspellcheckers->dictionary_list;
1472 dlist = get_aspell_dict_info_list(config);
1473 delete_aspell_config(config);
1475 debug_print("Aspell: checking for dictionaries in %s\n", aspell_path);
1476 dels = aspell_dict_info_list_elements(dlist);
1477 while ( (entry = aspell_dict_info_enumeration_next(dels)) != 0)
1479 dict = g_new0(Dictionary, 1);
1480 dict->fullname = g_strdup_printf("%s%s", aspell_path,
1482 dict->dictname = dict->fullname + strlen(aspell_path);
1483 dict->encoding = g_strdup(entry->code);
1484 debug_print("Aspell: found dictionary %s %s\n", dict->fullname,
1486 list = g_slist_insert_sorted(list, dict,
1487 (GCompareFunc) compare_dict);
1490 delete_aspell_dict_info_enumeration(dels);
1494 debug_print("Aspell: error when searching for dictionaries: "
1495 "No dictionary found.\n");
1496 list = create_empty_dictionary_list();
1499 gtkaspellcheckers->dictionary_list = list;
1504 void gtkaspell_free_dictionary_list(GSList *list)
1508 for (walk = list; walk != NULL; walk = g_slist_next(walk))
1510 dict = (Dictionary *) walk->data;
1511 dictionary_delete(dict);
1516 GtkWidget *gtkaspell_dictionary_option_menu_new(const gchar *aspell_path)
1518 GSList *dict_list, *tmp;
1523 dict_list = gtkaspell_get_dictionary_list(aspell_path, TRUE);
1524 g_return_val_if_fail(dict_list, NULL);
1526 menu = gtk_menu_new();
1528 for (tmp = dict_list; tmp != NULL; tmp = g_slist_next(tmp)) {
1529 dict = (Dictionary *) tmp->data;
1530 item = gtk_menu_item_new_with_label(dict->dictname);
1531 gtk_object_set_data(GTK_OBJECT(item), "dict_name",
1534 gtk_menu_append(GTK_MENU(menu), item);
1535 gtk_widget_show(item);
1538 gtk_widget_show(menu);
1543 gchar *gtkaspell_get_dictionary_menu_active_item(GtkWidget *menu)
1545 GtkWidget *menuitem;
1546 gchar *dict_fullname;
1549 g_return_val_if_fail(GTK_IS_MENU(menu), NULL);
1551 menuitem = gtk_menu_get_active(GTK_MENU(menu));
1552 dict_fullname = (gchar *) gtk_object_get_data(GTK_OBJECT(menuitem),
1554 g_return_val_if_fail(dict_fullname, NULL);
1556 label = g_strdup(dict_fullname);
1562 GtkWidget *gtkaspell_sugmode_option_menu_new(gint sugmode)
1567 menu = gtk_menu_new();
1568 gtk_widget_show(menu);
1570 item = gtk_menu_item_new_with_label(_("Fast Mode"));
1571 gtk_widget_show(item);
1572 gtk_menu_append(GTK_MENU(menu), item);
1573 gtk_object_set_data(GTK_OBJECT(item), "sugmode", GINT_TO_POINTER(ASPELL_FASTMODE));
1575 item = gtk_menu_item_new_with_label(_("Normal Mode"));
1576 gtk_widget_show(item);
1577 gtk_menu_append(GTK_MENU(menu), item);
1578 gtk_object_set_data(GTK_OBJECT(item), "sugmode", GINT_TO_POINTER(ASPELL_NORMALMODE));
1580 item = gtk_menu_item_new_with_label(_("Bad Spellers Mode"));
1581 gtk_widget_show(item);
1582 gtk_menu_append(GTK_MENU(menu), item);
1583 gtk_object_set_data(GTK_OBJECT(item), "sugmode", GINT_TO_POINTER(ASPELL_BADSPELLERMODE));
1588 void gtkaspell_sugmode_option_menu_set(GtkOptionMenu *optmenu, gint sugmode)
1590 g_return_if_fail(GTK_IS_OPTION_MENU(optmenu));
1592 g_return_if_fail(sugmode == ASPELL_FASTMODE ||
1593 sugmode == ASPELL_NORMALMODE ||
1594 sugmode == ASPELL_BADSPELLERMODE);
1596 gtk_option_menu_set_history(GTK_OPTION_MENU(optmenu), sugmode - 1);
1599 gint gtkaspell_get_sugmode_from_option_menu(GtkOptionMenu *optmenu)
1604 g_return_val_if_fail(GTK_IS_OPTION_MENU(optmenu), -1);
1606 item = gtk_menu_get_active(GTK_MENU(gtk_option_menu_get_menu(optmenu)));
1608 sugmode = GPOINTER_TO_INT(gtk_object_get_data(GTK_OBJECT(item),
1614 static void use_alternate_dict(GtkAspell *gtkaspell)
1618 tmp = gtkaspell->gtkaspeller;
1619 gtkaspell->gtkaspeller = gtkaspell->alternate_speller;
1620 gtkaspell->alternate_speller = tmp;
1622 if (gtkaspell->config_menu)
1623 populate_submenu(gtkaspell, gtkaspell->config_menu);
1626 static void popup_menu(GtkAspell *gtkaspell, GdkEventButton *eb)
1630 gtktext = gtkaspell->gtktext;
1631 gtkaspell->orig_pos = gtk_editable_get_position(GTK_EDITABLE(gtktext));
1633 if (!(eb->state & GDK_SHIFT_MASK)) {
1634 if (check_at(gtkaspell, gtkaspell->orig_pos)) {
1636 gtk_editable_set_position(GTK_EDITABLE(gtktext),
1637 gtkaspell->orig_pos);
1638 gtk_stext_set_point(gtktext, gtkaspell->orig_pos);
1640 if (misspelled_suggest(gtkaspell, gtkaspell->theword)) {
1641 gtk_menu_popup(make_sug_menu(gtkaspell),
1642 NULL, NULL, NULL, NULL,
1643 eb->button, GDK_CURRENT_TIME);
1648 gtk_editable_set_position(GTK_EDITABLE(gtktext),
1649 gtkaspell->orig_pos);
1650 gtk_stext_set_point(gtktext, gtkaspell->orig_pos);
1654 gtk_menu_popup(make_config_menu(gtkaspell), NULL, NULL, NULL, NULL,
1655 eb->button, GDK_CURRENT_TIME);
1658 /* make_sug_menu() - Add menus to accept this word for this session
1659 * and to add it to personal dictionary
1661 static GtkMenu *make_sug_menu(GtkAspell *gtkaspell)
1663 GtkWidget *menu, *item;
1664 unsigned char *caption;
1666 GtkAccelGroup *accel;
1667 GList *l = gtkaspell->suggestions_list;
1669 gtktext = gtkaspell->gtktext;
1671 accel = gtk_accel_group_new();
1672 menu = gtk_menu_new();
1674 if (gtkaspell->sug_menu)
1675 gtk_widget_destroy(gtkaspell->sug_menu);
1677 gtkaspell->sug_menu = menu;
1679 gtk_signal_connect(GTK_OBJECT(menu), "cancel",
1680 GTK_SIGNAL_FUNC(cancel_menu_cb), gtkaspell);
1682 caption = g_strdup_printf(_("\"%s\" unknown in %s"),
1683 (unsigned char*) l->data,
1684 gtkaspell->gtkaspeller->dictionary->dictname);
1685 item = gtk_menu_item_new_with_label(caption);
1686 gtk_widget_show(item);
1687 gtk_menu_append(GTK_MENU(menu), item);
1688 gtk_misc_set_alignment(GTK_MISC(GTK_BIN(item)->child), 0.5, 0.5);
1691 item = gtk_menu_item_new();
1692 gtk_widget_show(item);
1693 gtk_menu_append(GTK_MENU(menu), item);
1695 item = gtk_menu_item_new_with_label(_("Accept in this session"));
1696 gtk_widget_show(item);
1697 gtk_menu_append(GTK_MENU(menu), item);
1698 gtk_signal_connect(GTK_OBJECT(item), "activate",
1699 GTK_SIGNAL_FUNC(add_word_to_session_cb),
1701 gtk_widget_add_accelerator(item, "activate", accel, GDK_space,
1703 GTK_ACCEL_LOCKED | GTK_ACCEL_VISIBLE);
1705 item = gtk_menu_item_new_with_label(_("Add to personal dictionary"));
1706 gtk_widget_show(item);
1707 gtk_menu_append(GTK_MENU(menu), item);
1708 gtk_signal_connect(GTK_OBJECT(item), "activate",
1709 GTK_SIGNAL_FUNC(add_word_to_personal_cb),
1711 gtk_widget_add_accelerator(item, "activate", accel, GDK_Return,
1713 GTK_ACCEL_LOCKED | GTK_ACCEL_VISIBLE);
1715 item = gtk_menu_item_new_with_label(_("Replace with..."));
1716 gtk_widget_show(item);
1717 gtk_menu_append(GTK_MENU(menu), item);
1718 gtk_signal_connect(GTK_OBJECT(item), "activate",
1719 GTK_SIGNAL_FUNC(replace_with_create_dialog_cb),
1721 gtk_widget_add_accelerator(item, "activate", accel, GDK_R, 0,
1722 GTK_ACCEL_LOCKED | GTK_ACCEL_VISIBLE);
1724 if (gtkaspell->use_alternate && gtkaspell->alternate_speller) {
1725 caption = g_strdup_printf(_("Check with %s"),
1726 gtkaspell->alternate_speller->dictionary->dictname);
1727 item = gtk_menu_item_new_with_label(caption);
1729 gtk_widget_show(item);
1730 gtk_menu_append(GTK_MENU(menu), item);
1731 gtk_signal_connect(GTK_OBJECT(item), "activate",
1732 GTK_SIGNAL_FUNC(check_with_alternate_cb),
1734 gtk_widget_add_accelerator(item, "activate", accel, GDK_X, 0,
1735 GTK_ACCEL_LOCKED | GTK_ACCEL_VISIBLE);
1738 item = gtk_menu_item_new();
1739 gtk_widget_show(item);
1740 gtk_menu_append(GTK_MENU(menu), item);
1744 item = gtk_menu_item_new_with_label(_("(no suggestions)"));
1745 gtk_widget_show(item);
1746 gtk_menu_append(GTK_MENU(menu), item);
1748 GtkWidget *curmenu = menu;
1752 if (count == MENUCOUNT) {
1755 item = gtk_menu_item_new_with_label(_("More..."));
1756 gtk_widget_show(item);
1757 gtk_menu_append(GTK_MENU(curmenu), item);
1759 curmenu = gtk_menu_new();
1760 gtk_menu_item_set_submenu(GTK_MENU_ITEM(item),
1764 item = gtk_menu_item_new_with_label((unsigned char*)l->data);
1765 gtk_widget_show(item);
1766 gtk_menu_append(GTK_MENU(curmenu), item);
1767 gtk_signal_connect(GTK_OBJECT(item), "activate",
1768 GTK_SIGNAL_FUNC(replace_word_cb),
1771 if (curmenu == menu && count < MENUCOUNT) {
1772 gtk_widget_add_accelerator(item, "activate",
1777 gtk_widget_add_accelerator(item, "activate",
1786 } while ((l = l->next) != NULL);
1789 gtk_accel_group_attach(accel, GTK_OBJECT(menu));
1790 gtk_accel_group_unref(accel);
1792 return GTK_MENU(menu);
1795 static void populate_submenu(GtkAspell *gtkaspell, GtkWidget *menu)
1797 GtkWidget *item, *submenu;
1799 GtkAspeller *gtkaspeller = gtkaspell->gtkaspeller;
1801 if (GTK_MENU_SHELL(menu)->children) {
1802 GList *amenu, *alist;
1803 for (amenu = (GTK_MENU_SHELL(menu)->children); amenu; ) {
1804 alist = amenu->next;
1805 gtk_widget_destroy(GTK_WIDGET(amenu->data));
1810 dictname = g_strdup_printf(_("Dictionary: %s"),
1811 gtkaspeller->dictionary->dictname);
1812 item = gtk_menu_item_new_with_label(dictname);
1813 gtk_misc_set_alignment(GTK_MISC(GTK_BIN(item)->child), 0.5, 0.5);
1815 gtk_widget_show(item);
1816 gtk_menu_append(GTK_MENU(menu), item);
1818 item = gtk_menu_item_new();
1819 gtk_widget_show(item);
1820 gtk_menu_append(GTK_MENU(menu), item);
1822 if (gtkaspell->use_alternate && gtkaspell->alternate_speller) {
1823 dictname = g_strdup_printf(_("Use alternate (%s)"),
1824 gtkaspell->alternate_speller->dictionary->dictname);
1825 item = gtk_menu_item_new_with_label(dictname);
1827 gtk_signal_connect(GTK_OBJECT(item), "activate",
1828 GTK_SIGNAL_FUNC(switch_to_alternate_cb),
1830 gtk_widget_show(item);
1831 gtk_menu_append(GTK_MENU(menu), item);
1834 item = gtk_check_menu_item_new_with_label(_("Fast Mode"));
1835 if (gtkaspell->gtkaspeller->sug_mode == ASPELL_FASTMODE) {
1836 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(item),TRUE);
1837 gtk_widget_set_sensitive(GTK_WIDGET(item),FALSE);
1839 gtk_signal_connect(GTK_OBJECT(item), "activate",
1840 GTK_SIGNAL_FUNC(set_sug_mode_cb),
1842 gtk_widget_show(item);
1843 gtk_menu_append(GTK_MENU(menu), item);
1845 item = gtk_check_menu_item_new_with_label(_("Normal Mode"));
1846 if (gtkaspell->gtkaspeller->sug_mode == ASPELL_NORMALMODE) {
1847 gtk_widget_set_sensitive(GTK_WIDGET(item), FALSE);
1848 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(item), TRUE);
1850 gtk_signal_connect(GTK_OBJECT(item), "activate",
1851 GTK_SIGNAL_FUNC(set_sug_mode_cb),
1853 gtk_widget_show(item);
1854 gtk_menu_append(GTK_MENU(menu),item);
1856 item = gtk_check_menu_item_new_with_label(_("Bad Spellers Mode"));
1857 if (gtkaspell->gtkaspeller->sug_mode == ASPELL_BADSPELLERMODE) {
1858 gtk_widget_set_sensitive(GTK_WIDGET(item), FALSE);
1859 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(item), TRUE);
1861 gtk_signal_connect(GTK_OBJECT(item), "activate",
1862 GTK_SIGNAL_FUNC(set_sug_mode_cb),
1864 gtk_widget_show(item);
1865 gtk_menu_append(GTK_MENU(menu), item);
1867 item = gtk_menu_item_new();
1868 gtk_widget_show(item);
1869 gtk_menu_append(GTK_MENU(menu), item);
1871 item = gtk_check_menu_item_new_with_label(_("Check while typing"));
1872 if (gtkaspell->check_while_typing)
1873 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(item), TRUE);
1875 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(item), FALSE);
1876 gtk_signal_connect(GTK_OBJECT(item), "activate",
1877 GTK_SIGNAL_FUNC(toggle_check_while_typing_cb),
1879 gtk_widget_show(item);
1880 gtk_menu_append(GTK_MENU(menu), item);
1882 item = gtk_menu_item_new();
1883 gtk_widget_show(item);
1884 gtk_menu_append(GTK_MENU(menu), item);
1886 submenu = gtk_menu_new();
1887 item = gtk_menu_item_new_with_label(_("Change dictionary"));
1888 gtk_menu_item_set_submenu(GTK_MENU_ITEM(item),submenu);
1889 gtk_widget_show(item);
1890 gtk_menu_append(GTK_MENU(menu), item);
1893 if (gtkaspellcheckers->dictionary_list == NULL)
1894 gtkaspell_get_dictionary_list(prefs_common.aspell_path, FALSE);
1896 GtkWidget * curmenu = submenu;
1900 tmp = gtkaspellcheckers->dictionary_list;
1902 for (tmp = gtkaspellcheckers->dictionary_list; tmp != NULL;
1903 tmp = g_slist_next(tmp)) {
1904 if (count == MENUCOUNT) {
1907 newmenu = gtk_menu_new();
1908 item = gtk_menu_item_new_with_label(_("More..."));
1909 gtk_menu_item_set_submenu(GTK_MENU_ITEM(item),
1912 gtk_menu_append(GTK_MENU(curmenu), item);
1913 gtk_widget_show(item);
1917 dict = (Dictionary *) tmp->data;
1918 item = gtk_check_menu_item_new_with_label(dict->dictname);
1919 gtk_object_set_data(GTK_OBJECT(item), "dict_name",
1921 if (strcmp2(dict->fullname,
1922 gtkaspell->gtkaspeller->dictionary->fullname))
1923 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(item), FALSE);
1925 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(item), TRUE);
1926 gtk_widget_set_sensitive(GTK_WIDGET(item),
1929 gtk_signal_connect(GTK_OBJECT(item), "activate",
1930 GTK_SIGNAL_FUNC(change_dict_cb),
1932 gtk_widget_show(item);
1933 gtk_menu_append(GTK_MENU(curmenu), item);
1940 static GtkMenu *make_config_menu(GtkAspell *gtkaspell)
1942 if (!gtkaspell->popup_config_menu)
1943 gtkaspell->popup_config_menu = gtk_menu_new();
1945 debug_print("Aspell: creating/using popup_config_menu %0x\n",
1946 (guint) gtkaspell->popup_config_menu);
1947 populate_submenu(gtkaspell, gtkaspell->popup_config_menu);
1949 return GTK_MENU(gtkaspell->popup_config_menu);
1952 void gtkaspell_populate_submenu(GtkAspell *gtkaspell, GtkWidget *menuitem)
1956 menu = GTK_WIDGET(GTK_MENU_ITEM(menuitem)->submenu);
1958 debug_print("Aspell: using config menu %0x\n",
1959 (guint) gtkaspell->popup_config_menu);
1960 populate_submenu(gtkaspell, menu);
1962 gtkaspell->config_menu = menu;
1966 static void set_menu_pos(GtkMenu *menu, gint *x, gint *y, gpointer data)
1968 GtkAspell *gtkaspell = (GtkAspell *) data;
1969 gint xx = 0, yy = 0;
1972 GtkSText *text = GTK_STEXT(gtkaspell->gtktext);
1975 gdk_window_get_origin(GTK_WIDGET(gtkaspell->gtktext)->window, &xx, &yy);
1977 sx = gdk_screen_width();
1978 sy = gdk_screen_height();
1980 gtk_widget_get_child_requisition(GTK_WIDGET(menu), &r);
1985 *x = gtkaspell->gtktext->cursor_pos_x + xx +
1986 gdk_char_width(GTK_WIDGET(text)->style->font, ' ');
1987 *y = gtkaspell->gtktext->cursor_pos_y + yy;
1993 gdk_string_height((GTK_WIDGET(gtkaspell->gtktext))->style->font,
1994 gtkaspell->theword);
1998 /* Menu call backs */
2000 static gboolean cancel_menu_cb(GtkMenuShell *w, gpointer data)
2002 GtkAspell *gtkaspell = (GtkAspell *) data;
2004 gtkaspell->continue_check = NULL;
2005 set_point_continue(gtkaspell);
2011 /* change_dict_cb() - Menu callback : change dict */
2012 static void change_dict_cb(GtkWidget *w, GtkAspell *gtkaspell)
2016 GtkAspeller *gtkaspeller;
2019 fullname = (gchar *) gtk_object_get_data(GTK_OBJECT(w), "dict_name");
2021 if (!strcmp2(fullname, _("None")))
2024 sug_mode = gtkaspell->default_sug_mode;
2026 dict = g_new0(Dictionary, 1);
2027 dict->fullname = g_strdup(fullname);
2028 dict->encoding = g_strdup(gtkaspell->gtkaspeller->dictionary->encoding);
2030 if (gtkaspell->use_alternate && gtkaspell->alternate_speller &&
2031 dict == gtkaspell->alternate_speller->dictionary) {
2032 use_alternate_dict(gtkaspell);
2033 dictionary_delete(dict);
2037 gtkaspeller = gtkaspeller_new(dict);
2041 message = g_strdup_printf(_("The spell checker could not change dictionary.\n%s"),
2042 gtkaspellcheckers->error_message);
2044 gtkaspell_alert_dialog(message);
2047 if (gtkaspell->use_alternate) {
2048 if (gtkaspell->alternate_speller)
2049 gtkaspeller_delete(gtkaspell->alternate_speller);
2050 gtkaspell->alternate_speller = gtkaspell->gtkaspeller;
2052 gtkaspeller_delete(gtkaspell->gtkaspeller);
2054 gtkaspell->gtkaspeller = gtkaspeller;
2055 gtkaspell_set_sug_mode(gtkaspell, sug_mode);
2058 dictionary_delete(dict);
2060 if (gtkaspell->config_menu)
2061 populate_submenu(gtkaspell, gtkaspell->config_menu);
2064 static void switch_to_alternate_cb(GtkWidget *w,
2067 GtkAspell *gtkaspell = (GtkAspell *) data;
2068 use_alternate_dict(gtkaspell);
2071 /* Misc. helper functions */
2073 static void set_point_continue(GtkAspell *gtkaspell)
2077 gtktext = gtkaspell->gtktext;
2079 gtk_stext_freeze(gtktext);
2080 gtk_editable_set_position(GTK_EDITABLE(gtktext),gtkaspell->orig_pos);
2081 gtk_stext_set_point(gtktext, gtkaspell->orig_pos);
2082 gtk_stext_thaw(gtktext);
2084 if (gtkaspell->continue_check)
2085 gtkaspell->continue_check((gpointer *) gtkaspell);
2088 static void allocate_color(GtkAspell *gtkaspell, gint rgbvalue)
2091 GdkColor *color = &(gtkaspell->highlight);
2093 gc = gtk_widget_get_colormap(GTK_WIDGET(gtkaspell->gtktext));
2095 if (gtkaspell->highlight.pixel)
2096 gdk_colormap_free_colors(gc, &(gtkaspell->highlight), 1);
2098 /* Shameless copy from Sylpheed's gtkutils.c */
2100 color->red = (int) (((gdouble)((rgbvalue & 0xff0000) >> 16) / 255.0)
2102 color->green = (int) (((gdouble)((rgbvalue & 0x00ff00) >> 8) / 255.0)
2104 color->blue = (int) (((gdouble) (rgbvalue & 0x0000ff) / 255.0)
2107 gdk_colormap_alloc_color(gc, &(gtkaspell->highlight), FALSE, TRUE);
2110 static void change_color(GtkAspell * gtkaspell,
2111 gint start, gint end,
2117 g_return_if_fail(start < end);
2119 gtktext = gtkaspell->gtktext;
2121 gtk_stext_freeze(gtktext);
2123 gtk_stext_set_point(gtktext, start);
2124 gtk_stext_forward_delete(gtktext, end - start);
2125 gtk_stext_insert(gtktext, NULL, color, NULL, newtext,
2128 gtk_stext_thaw(gtktext);
2131 /* convert_to_aspell_encoding () - converts ISO-8859-* strings to iso8859-*
2132 * as needed by aspell. Returns an allocated string.
2135 static guchar *convert_to_aspell_encoding (const guchar *encoding)
2137 guchar * aspell_encoding;
2139 if (strstr2(encoding, "ISO-8859-")) {
2140 aspell_encoding = g_strdup_printf("iso8859%s", encoding+8);
2143 if (!strcmp2(encoding, "US-ASCII"))
2144 aspell_encoding = g_strdup("iso8859-1");
2146 aspell_encoding = g_strdup(encoding);
2149 return aspell_encoding;
2152 /* compare_dict () - compare 2 dict names */
2153 static gint compare_dict(Dictionary *a, Dictionary *b)
2155 guint aparts = 0, bparts = 0;
2158 for (i=0; i < strlen(a->dictname); i++)
2159 if (a->dictname[i] == '-')
2161 for (i=0; i < strlen(b->dictname); i++)
2162 if (b->dictname[i] == '-')
2165 if (aparts != bparts)
2166 return (aparts < bparts) ? -1 : +1;
2169 compare = strcmp2(a->dictname, b->dictname);
2171 compare = strcmp2(a->fullname, b->fullname);
2176 static void dictionary_delete(Dictionary *dict)
2178 g_free(dict->fullname);
2179 g_free(dict->encoding);
2183 static Dictionary *dictionary_dup(const Dictionary *dict)
2187 dict2 = g_new(Dictionary, 1);
2189 dict2->fullname = g_strdup(dict->fullname);
2190 dict2->dictname = dict->dictname - dict->fullname + dict2->fullname;
2191 dict2->encoding = g_strdup(dict->encoding);
2196 static void free_suggestions_list(GtkAspell *gtkaspell)
2200 for (list = gtkaspell->suggestions_list; list != NULL;
2206 gtkaspell->max_sug = -1;
2207 gtkaspell->suggestions_list = NULL;
2210 static void reset_theword_data(GtkAspell *gtkaspell)
2212 gtkaspell->start_pos = 0;
2213 gtkaspell->end_pos = 0;
2214 gtkaspell->theword[0] = 0;
2215 gtkaspell->max_sug = -1;
2217 free_suggestions_list(gtkaspell);
2220 static void free_checkers(gpointer elt, gpointer data)
2222 GtkAspeller *gtkaspeller = elt;
2224 g_return_if_fail(gtkaspeller);
2226 gtkaspeller_real_delete(gtkaspeller);
2229 static gint find_gtkaspeller(gconstpointer aa, gconstpointer bb)
2231 Dictionary *a = ((GtkAspeller *) aa)->dictionary;
2232 Dictionary *b = ((GtkAspeller *) bb)->dictionary;
2234 if (a && b && a->fullname && b->fullname &&
2235 strcmp(a->fullname, b->fullname) == 0 &&
2236 a->encoding && b->encoding)
2237 return strcmp(a->encoding, b->encoding);
2242 static void gtkaspell_alert_dialog(gchar *message)
2247 GtkWidget *ok_button;
2249 dialog = gtk_dialog_new();
2250 gtk_window_set_policy(GTK_WINDOW(dialog), FALSE, FALSE, FALSE);
2251 gtk_window_set_position(GTK_WINDOW(dialog),GTK_WIN_POS_MOUSE);
2252 gtk_signal_connect_object(GTK_OBJECT(dialog), "destroy",
2253 GTK_SIGNAL_FUNC(gtk_widget_destroy),
2254 GTK_OBJECT(dialog));
2256 label = gtk_label_new(message);
2257 gtk_misc_set_padding(GTK_MISC(label), 8, 8);
2259 gtk_container_add(GTK_CONTAINER(GTK_DIALOG(dialog)->vbox), label);
2261 hbox = gtk_hbox_new(FALSE, 0);
2263 ok_button = gtk_button_new_with_label(_("OK"));
2264 GTK_WIDGET_SET_FLAGS(ok_button, GTK_CAN_DEFAULT);
2265 gtk_box_pack_start(GTK_BOX(hbox), ok_button, TRUE, TRUE, 8);
2267 gtk_signal_connect_object(GTK_OBJECT(ok_button), "clicked",
2268 GTK_SIGNAL_FUNC(gtk_widget_destroy),
2269 GTK_OBJECT(dialog));
2270 gtk_container_add(GTK_CONTAINER(GTK_DIALOG(dialog)->action_area), hbox);
2272 gtk_widget_grab_default(ok_button);
2273 gtk_widget_grab_focus(ok_button);
2274 gtk_window_set_modal(GTK_WINDOW(dialog), TRUE);
2276 gtk_widget_show_all(dialog);