Fix bug #184
[claws.git] / src / gtk / gtkaspell.c
1 /* gtkaspell - a spell-checking addon for GtkText
2  * Copyright (c) 2000 Evan Martin (original code for ispell).
3  * Copyright (c) 2002 Melvin Hadasht.
4  *
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.
9  *
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.
14  *
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
18  */
19 /*
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
25  */
26  
27 #ifdef HAVE_CONFIG_H
28 #  include "config.h"
29 #endif
30
31 #ifdef USE_ASPELL
32
33 #include <stdio.h>
34 #include <stdlib.h>
35 #include <unistd.h>
36 #include <sys/types.h>
37 #include <sys/wait.h>
38 #include <signal.h>
39 #include <ctype.h>
40 #include <string.h>
41 #include <errno.h>
42 #include <sys/time.h>
43 #include <fcntl.h>
44 #include <time.h>
45 #include <dirent.h>
46
47 #include <gtk/gtk.h>
48 #include <gdk/gdk.h>
49 #include <gtk/gtkoptionmenu.h>
50 #include <gtk/gtkmenu.h>
51 #include <gtk/gtkmenuitem.h>
52 #include <gdk/gdkkeysyms.h>
53
54 #include <aspell.h>
55
56 #include "intl.h"
57 #include "gtkstext.h"
58 #include "utils.h"
59
60 #include "gtkaspell.h"
61 #define ASPELL_FASTMODE       1
62 #define ASPELL_NORMALMODE     2
63 #define ASPELL_BADSPELLERMODE 3
64
65 #define GTKASPELLWORDSIZE 1024
66
67 /* size of the text buffer used in various word-processing routines. */
68 #define BUFSIZE 1024
69
70 /* number of suggestions to display on each menu. */
71 #define MENUCOUNT 15
72
73 /* 'config' must be defined as a 'AspellConfig *' */
74 #define RETURN_FALSE_IF_CONFIG_ERROR() \
75 { \
76         if (aspell_config_error_number(config) != 0) { \
77                 gtkaspellcheckers->error_message = g_strdup(aspell_config_error_message(config)); \
78                 return FALSE; \
79         } \
80 }
81
82 #define CONFIG_REPLACE_RETURN_FALSE_IF_FAIL(option, value) { \
83         aspell_config_replace(config, option, value);        \
84         RETURN_FALSE_IF_CONFIG_ERROR();                      \
85         }
86
87 typedef struct _GtkAspellCheckers {
88         GSList          *checkers;
89         GSList          *dictionary_list;
90         gchar           *error_message;
91 } GtkAspellCheckers;
92
93 typedef struct _Dictionary {
94         gchar *fullname;
95         gchar *dictname;
96         gchar *encoding;
97 } Dictionary;
98
99 typedef struct _GtkAspeller {
100         Dictionary      *dictionary;
101         gint             sug_mode;
102         AspellConfig    *config;
103         AspellSpeller   *checker;
104 } GtkAspeller;
105
106 typedef void (*ContCheckFunc) (gpointer *gtkaspell);
107
108 struct _GtkAspell
109 {
110         GtkAspeller     *gtkaspeller;
111         GtkAspeller     *alternate_speller;
112         gchar           *dictionary_path;
113         gchar            theword[GTKASPELLWORDSIZE];
114         gint             start_pos;
115         gint             end_pos;
116         gint             orig_pos;
117         gint             end_check_pos;
118         gboolean         misspelled;
119         gboolean         check_while_typing;
120         gboolean         use_alternate;
121
122         ContCheckFunc    continue_check; 
123
124         GtkWidget       *config_menu;
125         GtkWidget       *popup_config_menu;
126         GtkWidget       *sug_menu;
127         GtkWidget       *replace_entry;
128
129         gint             default_sug_mode;
130         gint             max_sug;
131         GList           *suggestions_list;
132
133         GtkSText        *gtktext;
134         GdkColor         highlight;
135 };
136
137 typedef AspellConfig GtkAspellConfig;
138
139 /******************************************************************************/
140
141 static GtkAspellCheckers *gtkaspellcheckers;
142
143 /* Error message storage */
144 static void gtkaspell_checkers_error_message    (gchar          *message);
145
146 /* Callbacks */
147 static void entry_insert_cb                     (GtkSText       *gtktext, 
148                                                  gchar          *newtext, 
149                                                  guint           len, 
150                                                  guint          *ppos, 
151                                                  GtkAspell      *gtkaspell);
152 static void entry_delete_cb                     (GtkSText       *gtktext, 
153                                                  gint            start, 
154                                                  gint            end,
155                                                  GtkAspell      *gtkaspell);
156 static gint button_press_intercept_cb           (GtkSText       *gtktext, 
157                                                  GdkEvent       *e, 
158                                                  GtkAspell      *gtkaspell);
159
160 /* Checker creation */
161 static GtkAspeller* gtkaspeller_new             (Dictionary     *dict);
162 static GtkAspeller* gtkaspeller_real_new        (Dictionary     *dict);
163 static GtkAspeller* gtkaspeller_delete          (GtkAspeller    *gtkaspeller);
164 static GtkAspeller* gtkaspeller_real_delete     (GtkAspeller    *gtkaspeller);
165
166 /* Checker configuration */
167 static gint             set_dictionary                  (AspellConfig *config, 
168                                                          Dictionary *dict);
169 static void             set_sug_mode_cb                 (GtkMenuItem *w, 
170                                                          GtkAspell *gtkaspell);
171 static void             set_real_sug_mode               (GtkAspell *gtkaspell, 
172                                                          const char *themode);
173
174 /* Checker actions */
175 static gboolean check_at                        (GtkAspell      *gtkaspell, 
176                                                  int             from_pos);
177 static gboolean check_next_prev                 (GtkAspell      *gtkaspell, 
178                                                  gboolean        forward);
179 static GList* misspelled_suggest                (GtkAspell      *gtkaspell, 
180                                                  guchar         *word);
181 static void add_word_to_session_cb              (GtkWidget      *w, 
182                                                  gpointer        data);
183 static void add_word_to_personal_cb             (GtkWidget      *w, 
184                                                  gpointer        data);
185 static void replace_with_create_dialog_cb       (GtkWidget      *w,
186                                                  gpointer        data);
187 static void replace_with_supplied_word_cb       (GtkWidget      *w, 
188                                                  GtkAspell      *gtkaspell);
189 static void replace_word_cb                     (GtkWidget      *w, 
190                                                  gpointer       data); 
191 static void replace_real_word                   (GtkAspell      *gtkaspell, 
192                                                  gchar          *newword);
193 static void check_with_alternate_cb             (GtkWidget      *w,
194                                                  gpointer        data);
195 static void use_alternate_dict                  (GtkAspell      *gtkaspell);
196 static void toggle_check_while_typing_cb        (GtkWidget      *w, 
197                                                  gpointer        data);
198
199 /* Menu creation */
200 static void popup_menu                          (GtkAspell      *gtkaspell, 
201                                                  GdkEventButton *eb);
202 static GtkMenu* make_sug_menu                   (GtkAspell      *gtkaspell);
203 static void populate_submenu                    (GtkAspell      *gtkaspell, 
204                                                  GtkWidget      *menu);
205 static GtkMenu* make_config_menu                (GtkAspell      *gtkaspell);
206 static void set_menu_pos                        (GtkMenu        *menu, 
207                                                  gint           *x, 
208                                                  gint           *y, 
209                                                  gpointer        data);
210 /* Other menu callbacks */
211 static gboolean cancel_menu_cb                  (GtkMenuShell   *w,
212                                                  gpointer        data);
213 static void change_dict_cb                      (GtkWidget      *w, 
214                                                  GtkAspell      *gtkaspell);
215 static void switch_to_alternate_cb              (GtkWidget      *w, 
216                                                  gpointer        data);
217
218 /* Misc. helper functions */
219 static void             set_point_continue              (GtkAspell *gtkaspell);
220 static void             continue_check                  (gpointer *gtkaspell);
221 static gboolean         iswordsep                       (unsigned char c);
222 static guchar           get_text_index_whar             (GtkAspell *gtkaspell, 
223                                                          int pos);
224 static gboolean         get_word_from_pos               (GtkAspell *gtkaspell, 
225                                                          gint pos, 
226                                                          unsigned char* buf,
227                                                          gint buflen,
228                                                          gint *pstart, 
229                                                          gint *pend);
230 static void             allocate_color                  (GtkAspell *gtkaspell,
231                                                          gint rgbvalue);
232 static void             change_color                    (GtkAspell *gtkaspell, 
233                                                          gint start, 
234                                                          gint end, 
235                                                          gchar *newtext,
236                                                          GdkColor *color);
237 static guchar*          convert_to_aspell_encoding      (const guchar *encoding);
238 static gint             compare_dict                    (Dictionary *a, 
239                                                          Dictionary *b);
240 static void             dictionary_delete               (Dictionary *dict);
241 static Dictionary *     dictionary_dup                  (const Dictionary *dict);
242 static void             free_suggestions_list           (GtkAspell *gtkaspell);
243 static void             reset_theword_data              (GtkAspell *gtkaspell);
244 static void             free_checkers                   (gpointer elt, 
245                                                          gpointer data);
246 static gint             find_gtkaspeller                (gconstpointer aa, 
247                                                          gconstpointer bb);
248 static void             gtkaspell_alert_dialog          (gchar *message);
249 /* gtkspellconfig - only one config per session */
250 GtkAspellConfig * gtkaspellconfig;
251
252 /******************************************************************************/
253
254 void gtkaspell_checkers_init(void)
255 {
256         gtkaspellcheckers                  = g_new(GtkAspellCheckers, 1);
257         gtkaspellcheckers->checkers        = NULL;
258         gtkaspellcheckers->dictionary_list = NULL;
259         gtkaspellcheckers->error_message   = NULL;
260 }
261         
262 void gtkaspell_checkers_quit(void)
263 {
264         GSList *checkers;
265         GSList *dict_list;
266
267         if (gtkaspellcheckers == NULL) 
268                 return;
269
270         if ((checkers  = gtkaspellcheckers->checkers)) {
271                 debug_print("Aspell: number of running checkers to delete %d\n",
272                                 g_slist_length(checkers));
273
274                 g_slist_foreach(checkers, free_checkers, NULL);
275                 g_slist_free(checkers);
276         }
277
278         if ((dict_list = gtkaspellcheckers->dictionary_list)) {
279                 debug_print("Aspell: number of dictionaries to delete %d\n",
280                                 g_slist_length(dict_list));
281
282                 gtkaspell_free_dictionary_list(dict_list);
283                 gtkaspellcheckers->dictionary_list = NULL;
284         }
285
286         g_free(gtkaspellcheckers->error_message);
287
288         return;
289 }
290
291 static void gtkaspell_checkers_error_message (gchar *message)
292 {
293         gchar *tmp;
294         if (gtkaspellcheckers->error_message) {
295                 tmp = g_strdup_printf("%s\n%s", 
296                                       gtkaspellcheckers->error_message, message);
297                 g_free(message);
298                 g_free(gtkaspellcheckers->error_message);
299                 gtkaspellcheckers->error_message = tmp;
300         } else 
301                 gtkaspellcheckers->error_message = message;
302 }
303
304 const char *gtkaspell_checkers_strerror(void)
305 {
306         g_return_if_fail(gtkaspellcheckers);
307         return gtkaspellcheckers->error_message;
308 }
309
310 void gtkaspell_checkers_reset_error(void)
311 {
312         g_return_if_fail(gtkaspellcheckers);
313         
314         g_free(gtkaspellcheckers->error_message);
315         
316         gtkaspellcheckers->error_message = NULL;
317 }
318
319 GtkAspell *gtkaspell_new(const gchar *dictionary_path,
320                          const gchar *dictionary, 
321                          const gchar *encoding,
322                          gint  misspelled_color,
323                          gboolean check_while_typing,
324                          gboolean use_alternate,
325                          GtkSText *gtktext)
326 {
327         Dictionary      *dict;
328         GtkAspell       *gtkaspell;
329         GtkAspeller     *gtkaspeller;
330
331         g_return_val_if_fail(gtktext, NULL);
332         
333         dict           = g_new0(Dictionary, 1);
334         dict->fullname = g_strdup(dictionary);
335         dict->encoding = g_strdup(encoding);
336
337         gtkaspeller    = gtkaspeller_new(dict); 
338         dictionary_delete(dict);
339
340         if (!gtkaspeller)
341                 return NULL;
342         
343         gtkaspell = g_new0(GtkAspell, 1);
344
345         gtkaspell->dictionary_path    = g_strdup(dictionary_path);
346
347         gtkaspell->gtkaspeller        = gtkaspeller;
348         gtkaspell->alternate_speller  = NULL;
349         gtkaspell->theword[0]         = 0x00;
350         gtkaspell->start_pos          = 0;
351         gtkaspell->end_pos            = 0;
352         gtkaspell->orig_pos           = -1;
353         gtkaspell->end_check_pos      = -1;
354         gtkaspell->misspelled         = -1;
355         gtkaspell->check_while_typing = check_while_typing;
356         gtkaspell->continue_check     = NULL;
357         gtkaspell->config_menu        = NULL;
358         gtkaspell->popup_config_menu  = NULL;
359         gtkaspell->sug_menu           = NULL;
360         gtkaspell->replace_entry      = NULL;
361         gtkaspell->gtktext            = gtktext;
362         gtkaspell->default_sug_mode   = ASPELL_FASTMODE;
363         gtkaspell->max_sug            = -1;
364         gtkaspell->suggestions_list   = NULL;
365         gtkaspell->use_alternate      = use_alternate;
366
367         allocate_color(gtkaspell, misspelled_color);
368
369         gtk_signal_connect_after(GTK_OBJECT(gtktext), "insert-text",
370                                  GTK_SIGNAL_FUNC(entry_insert_cb), gtkaspell);
371         gtk_signal_connect_after(GTK_OBJECT(gtktext), "delete-text",
372                                  GTK_SIGNAL_FUNC(entry_delete_cb), gtkaspell);
373         gtk_signal_connect(GTK_OBJECT(gtktext), "button-press-event",
374                            GTK_SIGNAL_FUNC(button_press_intercept_cb), gtkaspell);
375         
376         debug_print("Aspell: created gtkaspell %0x\n", (guint) gtkaspell);
377
378         return gtkaspell;
379 }
380
381 void gtkaspell_delete(GtkAspell * gtkaspell) 
382 {
383         GtkSText *gtktext = gtkaspell->gtktext;
384         
385         gtk_signal_disconnect_by_func(GTK_OBJECT(gtktext),
386                                       GTK_SIGNAL_FUNC(entry_insert_cb),
387                                       gtkaspell);
388         gtk_signal_disconnect_by_func(GTK_OBJECT(gtktext),
389                                       GTK_SIGNAL_FUNC(entry_delete_cb),
390                                       gtkaspell);
391         gtk_signal_disconnect_by_func(GTK_OBJECT(gtktext),
392                                       GTK_SIGNAL_FUNC(button_press_intercept_cb),
393                                       gtkaspell);
394
395         gtkaspell_uncheck_all(gtkaspell);
396         
397         gtkaspeller_delete(gtkaspell->gtkaspeller);
398
399         if (gtkaspell->use_alternate && gtkaspell->alternate_speller)
400                 gtkaspeller_delete(gtkaspell->alternate_speller);
401
402         if (gtkaspell->sug_menu)
403                 gtk_widget_destroy(gtkaspell->sug_menu);
404
405         if (gtkaspell->popup_config_menu)
406                 gtk_widget_destroy(gtkaspell->popup_config_menu);
407
408         if (gtkaspell->config_menu)
409                 gtk_widget_destroy(gtkaspell->config_menu);
410
411         if (gtkaspell->suggestions_list)
412                 free_suggestions_list(gtkaspell);
413
414         g_free((gchar *)gtkaspell->dictionary_path);
415
416         debug_print("Aspell: deleting gtkaspell %0x\n", (guint) gtkaspell);
417
418         g_free(gtkaspell);
419
420         gtkaspell = NULL;
421 }
422
423 static void entry_insert_cb(GtkSText *gtktext,
424                             gchar *newtext, 
425                             guint len,
426                             guint *ppos, 
427                             GtkAspell *gtkaspell) 
428 {
429         size_t wlen;
430
431         g_return_if_fail(gtkaspell->gtkaspeller->checker);
432
433         if (!gtkaspell->check_while_typing)
434                 return;
435         
436         /* We must insert ourselves the character so the
437          * color of the inserted character is the default color.
438          * Never mess with set_insertion when frozen.
439          */
440
441         gtk_stext_freeze(gtktext);
442         if (MB_CUR_MAX > 1) {
443                 gchar *str;
444                 Xstrndup_a(str, newtext, len, return);
445                 wlen = mbstowcs(NULL, str, 0);
446                 if (wlen < 0)
447                         return;
448         } else
449                 wlen = len;
450         
451         gtk_stext_backward_delete(GTK_STEXT(gtktext), wlen);
452         gtk_stext_insert(GTK_STEXT(gtktext), NULL, NULL, NULL, newtext, len);
453         *ppos = gtk_stext_get_point(GTK_STEXT(gtktext));
454                
455         if (iswordsep(newtext[0])) {
456                 /* did we just end a word? */
457                 if (*ppos >= 2) 
458                         check_at(gtkaspell, *ppos - 2);
459
460                 /* did we just split a word? */
461                 if (*ppos < gtk_stext_get_length(gtktext))
462                         check_at(gtkaspell, *ppos + 1);
463         } else {
464                 /* check as they type, *except* if they're typing at the end (the most
465                  * common case).
466                  */
467                 if (*ppos < gtk_stext_get_length(gtktext) &&
468                     !iswordsep(get_text_index_whar(gtkaspell, *ppos))) {
469                         check_at(gtkaspell, *ppos - 1);
470                 }
471         }
472
473         gtk_stext_thaw(gtktext);
474         gtk_editable_set_position(GTK_EDITABLE(gtktext), *ppos);
475 }
476
477 static void entry_delete_cb(GtkSText *gtktext,
478                             gint start, 
479                             gint end, 
480                             GtkAspell *gtkaspell) 
481 {
482         int origpos;
483     
484         g_return_if_fail(gtkaspell->gtkaspeller->checker);
485
486         if (!gtkaspell->check_while_typing)
487                 return;
488
489         origpos = gtk_editable_get_position(GTK_EDITABLE(gtktext));
490         if (start) {
491                 check_at(gtkaspell, start - 1);
492                 check_at(gtkaspell, start);
493         }
494
495         gtk_editable_set_position(GTK_EDITABLE(gtktext), origpos);
496         gtk_stext_set_point(gtktext, origpos);
497         /* this is to *UNDO* the selection, in case they were holding shift
498          * while hitting backspace. */
499         gtk_editable_select_region(GTK_EDITABLE(gtktext), origpos, origpos);
500 }
501
502 /* ok, this is pretty wacky:
503  * we need to let the right-mouse-click go through, so it moves the cursor,
504  * but we *can't* let it go through, because GtkText interprets rightclicks as
505  * weird selection modifiers.
506  *
507  * so what do we do?  forge rightclicks as leftclicks, then popup the menu.
508  * HACK HACK HACK.
509  */
510 static gint button_press_intercept_cb(GtkSText *gtktext, GdkEvent *e, GtkAspell *gtkaspell) 
511 {
512         GdkEventButton *eb;
513         gboolean retval;
514
515         g_return_val_if_fail(gtkaspell->gtkaspeller->checker, FALSE);
516
517         if (e->type != GDK_BUTTON_PRESS) 
518                 return FALSE;
519         eb = (GdkEventButton*) e;
520
521         if (eb->button != 3) 
522                 return FALSE;
523
524         /* forge the leftclick */
525         eb->button = 1;
526
527         gtk_signal_handler_block_by_func(GTK_OBJECT(gtktext),
528                                          GTK_SIGNAL_FUNC(button_press_intercept_cb), 
529                                          gtkaspell);
530         gtk_signal_emit_by_name(GTK_OBJECT(gtktext), "button-press-event",
531                                 e, &retval);
532         gtk_signal_handler_unblock_by_func(GTK_OBJECT(gtktext),
533                                            GTK_SIGNAL_FUNC(button_press_intercept_cb), 
534                                            gtkaspell);
535         gtk_signal_emit_stop_by_name(GTK_OBJECT(gtktext), "button-press-event");
536     
537         /* now do the menu wackiness */
538         popup_menu(gtkaspell, eb);
539         gtk_grab_remove(GTK_WIDGET(gtktext));
540         return TRUE;
541 }
542
543 /* Checker creation */
544 static GtkAspeller *gtkaspeller_new(Dictionary *dictionary)
545 {
546         GSList          *exist;
547         GtkAspeller     *gtkaspeller = NULL;
548         GtkAspeller     *tmp;
549         Dictionary      *dict;
550
551         g_return_val_if_fail(gtkaspellcheckers, NULL);
552
553         g_return_val_if_fail(dictionary, NULL);
554
555         if (dictionary->fullname == NULL)
556                 gtkaspell_checkers_error_message(g_strdup(_("No dictionary selected.")));
557         
558         g_return_val_if_fail(dictionary->fullname, NULL);
559         
560         if (dictionary->dictname == NULL) {
561                 gchar *tmp;
562
563                 tmp = strrchr(dictionary->fullname, G_DIR_SEPARATOR);
564
565                 if (tmp == NULL)
566                         dictionary->dictname = dictionary->fullname;
567                 else
568                         dictionary->dictname = tmp + 1;
569         }
570
571         dict = dictionary_dup(dictionary);
572
573         tmp = g_new0(GtkAspeller, 1);
574         tmp->dictionary = dict;
575
576         exist = g_slist_find_custom(gtkaspellcheckers->checkers, tmp, 
577                                     find_gtkaspeller);
578         
579         g_free(tmp);
580
581         if ((gtkaspeller = gtkaspeller_real_new(dict)) != NULL) {
582                 gtkaspellcheckers->checkers = g_slist_append(
583                                 gtkaspellcheckers->checkers,
584                                 gtkaspeller);
585
586                 debug_print("Aspell: Created a new gtkaspeller %0x\n",
587                                 (gint) gtkaspeller);
588         } else {
589                 dictionary_delete(dict);
590
591                 debug_print("Aspell: Could not create spell checker.\n");
592         }
593
594         debug_print("Aspell: number of existing checkers %d\n", 
595                         g_slist_length(gtkaspellcheckers->checkers));
596
597         return gtkaspeller;
598 }
599
600 static GtkAspeller *gtkaspeller_real_new(Dictionary *dict)
601 {
602         GtkAspeller             *gtkaspeller;
603         AspellConfig            *config;
604         AspellCanHaveError      *ret;
605         
606         g_return_val_if_fail(gtkaspellcheckers, NULL);
607         g_return_val_if_fail(dict, NULL);
608
609         gtkaspeller = g_new(GtkAspeller, 1);
610         
611         gtkaspeller->dictionary = dict;
612         gtkaspeller->sug_mode   = ASPELL_FASTMODE;
613
614         config = new_aspell_config();
615
616         if (!set_dictionary(config, dict))
617                 return NULL;
618         
619         ret = new_aspell_speller(config);
620         delete_aspell_config(config);
621
622         if (aspell_error_number(ret) != 0) {
623                 gtkaspellcheckers->error_message = g_strdup(aspell_error_message(ret));
624                 
625                 delete_aspell_can_have_error(ret);
626                 
627                 return NULL;
628         }
629
630         gtkaspeller->checker = to_aspell_speller(ret);
631         gtkaspeller->config  = aspell_speller_config(gtkaspeller->checker);
632
633         return gtkaspeller;
634 }
635
636 static GtkAspeller *gtkaspeller_delete(GtkAspeller *gtkaspeller)
637 {
638         g_return_val_if_fail(gtkaspellcheckers, NULL);
639         
640         gtkaspellcheckers->checkers = 
641                 g_slist_remove(gtkaspellcheckers->checkers, 
642                                 gtkaspeller);
643
644         debug_print("Aspell: Deleting gtkaspeller %0x.\n", 
645                         (gint) gtkaspeller);
646
647         gtkaspeller_real_delete(gtkaspeller);
648
649         debug_print("Aspell: number of existing checkers %d\n", 
650                         g_slist_length(gtkaspellcheckers->checkers));
651
652         return gtkaspeller;
653 }
654
655 static GtkAspeller *gtkaspeller_real_delete(GtkAspeller *gtkaspeller)
656 {
657         g_return_val_if_fail(gtkaspeller,          NULL);
658         g_return_val_if_fail(gtkaspeller->checker, NULL);
659
660         aspell_speller_save_all_word_lists(gtkaspeller->checker);
661
662         delete_aspell_speller(gtkaspeller->checker);
663
664         dictionary_delete(gtkaspeller->dictionary);
665
666         debug_print("Aspell: gtkaspeller %0x deleted.\n", 
667                     (gint) gtkaspeller);
668
669         g_free(gtkaspeller);
670
671         return NULL;
672 }
673
674 /*****************************************************************************/
675 /* Checker configuration */
676
677 static gboolean set_dictionary(AspellConfig *config, Dictionary *dict)
678 {
679         gchar *language = NULL;
680         gchar *jargon = NULL;
681         gchar *size   = NULL;
682         gchar  buf[BUFSIZE];
683         
684         g_return_val_if_fail(config, FALSE);
685         g_return_val_if_fail(dict,   FALSE);
686
687         strncpy(buf, dict->fullname, BUFSIZE-1);
688         buf[BUFSIZE-1] = 0x00;
689
690         buf[dict->dictname - dict->fullname] = 0x00;
691
692         CONFIG_REPLACE_RETURN_FALSE_IF_FAIL("dict-dir", buf);
693         debug_print("Aspell: looking for dictionaries in path %s.\n", buf);
694
695         strncpy(buf, dict->dictname, BUFSIZE-1);
696         language = buf;
697         
698         if ((size = strrchr(buf, '-')) && isdigit((int) size[1]))
699                 *size++ = 0x00;
700         else
701                 size = NULL;
702                                 
703         if ((jargon = strchr(language, '-')) != NULL) 
704                 *jargon++ = 0x00;
705         
706         if (size != NULL && jargon == size)
707                 jargon = NULL;
708
709         debug_print("Aspell: language: %s, jargon: %s, size: %s\n",
710                     language, jargon, size);
711         
712         if (language)
713                 CONFIG_REPLACE_RETURN_FALSE_IF_FAIL("lang", language);
714         if (jargon)
715                 CONFIG_REPLACE_RETURN_FALSE_IF_FAIL("jargon", jargon);
716         if (size)
717                 CONFIG_REPLACE_RETURN_FALSE_IF_FAIL("size", size);
718         if (dict->encoding) {
719                 gchar *aspell_enc;
720         
721                 aspell_enc = convert_to_aspell_encoding (dict->encoding);
722                 aspell_config_replace(config, "encoding", (const char *) aspell_enc);
723                 g_free(aspell_enc);
724
725                 RETURN_FALSE_IF_CONFIG_ERROR();
726         }
727         
728         return TRUE;
729 }
730
731 guchar *gtkaspell_get_dict(GtkAspell *gtkaspell)
732 {
733
734         g_return_val_if_fail(gtkaspell->gtkaspeller->config,     NULL);
735         g_return_val_if_fail(gtkaspell->gtkaspeller->dictionary, NULL);
736         
737         return g_strdup(gtkaspell->gtkaspeller->dictionary->dictname);
738 }
739   
740 guchar *gtkaspell_get_path(GtkAspell *gtkaspell)
741 {
742         guchar *path;
743         Dictionary *dict;
744
745         g_return_val_if_fail(gtkaspell->gtkaspeller->config, NULL);
746         g_return_val_if_fail(gtkaspell->gtkaspeller->dictionary, NULL);
747
748         dict = gtkaspell->gtkaspeller->dictionary;
749         path = g_strndup(dict->fullname, dict->dictname - dict->fullname);
750
751         return path;
752 }
753
754 /* set_sug_mode_cb() - Menu callback: Set the suggestion mode */
755 static void set_sug_mode_cb(GtkMenuItem *w, GtkAspell *gtkaspell)
756 {
757         char *themode;
758         
759         gtk_label_get(GTK_LABEL(GTK_BIN(w)->child), (gchar **) &themode);
760         
761         set_real_sug_mode(gtkaspell, themode);
762
763         if (gtkaspell->config_menu)
764                 populate_submenu(gtkaspell, gtkaspell->config_menu);
765 }
766
767 static void set_real_sug_mode(GtkAspell *gtkaspell, const char *themode)
768 {
769         gint result;
770         gint mode = ASPELL_FASTMODE;
771         g_return_if_fail(gtkaspell);
772         g_return_if_fail(gtkaspell->gtkaspeller);
773         g_return_if_fail(themode);
774
775         if (!strcmp(themode,_("Normal Mode")))
776                 mode = ASPELL_NORMALMODE;
777         else if (!strcmp( themode,_("Bad Spellers Mode")))
778                 mode = ASPELL_BADSPELLERMODE;
779
780         result = gtkaspell_set_sug_mode(gtkaspell, mode);
781
782         if(!result) {
783                 debug_print("Aspell: error while changing suggestion mode:%s\n",
784                             gtkaspellcheckers->error_message);
785                 gtkaspell_checkers_reset_error();
786         }
787 }
788   
789 /* gtkaspell_set_sug_mode() - Set the suggestion mode */
790 gboolean gtkaspell_set_sug_mode(GtkAspell *gtkaspell, gint themode)
791 {
792         AspellConfig *config;
793
794         g_return_val_if_fail(gtkaspell, FALSE);
795         g_return_val_if_fail(gtkaspell->gtkaspeller, FALSE);
796         g_return_val_if_fail(gtkaspell->gtkaspeller->config, FALSE);
797
798         debug_print("Aspell: setting sug mode of gtkaspeller %0x to %d\n",
799                         (guint) gtkaspell->gtkaspeller, themode);
800
801         config = gtkaspell->gtkaspeller->config;
802
803         switch (themode) {
804                 case ASPELL_FASTMODE: 
805                         CONFIG_REPLACE_RETURN_FALSE_IF_FAIL("sug-mode", "fast");
806                         break;
807                 case ASPELL_NORMALMODE: 
808                         CONFIG_REPLACE_RETURN_FALSE_IF_FAIL("sug-mode", "normal");
809                         break;
810                 case ASPELL_BADSPELLERMODE: 
811                         CONFIG_REPLACE_RETURN_FALSE_IF_FAIL("sug-mode", 
812                                                             "bad-spellers");
813                         break;
814                 default: 
815                         gtkaspellcheckers->error_message = 
816                                 g_strdup(_("Unknown suggestion mode."));
817                         return FALSE;
818                 }
819
820         gtkaspell->gtkaspeller->sug_mode = themode;
821         gtkaspell->default_sug_mode      = themode;
822
823         return TRUE;
824 }
825
826 /* misspelled_suggest() - Create a suggestion list for  word  */
827 static GList *misspelled_suggest(GtkAspell *gtkaspell, guchar *word) 
828 {
829         const guchar          *newword;
830         GList                 *list = NULL;
831         const AspellWordList  *suggestions;
832         AspellStringEnumeration *elements;
833
834         g_return_val_if_fail(word, NULL);
835
836         if (!aspell_speller_check(gtkaspell->gtkaspeller->checker, word, -1)) {
837                 free_suggestions_list(gtkaspell);
838
839                 suggestions = aspell_speller_suggest(gtkaspell->gtkaspeller->checker, 
840                                                      (const char *)word, -1);
841                 elements    = aspell_word_list_elements(suggestions);
842                 list        = g_list_append(list, g_strdup(word)); 
843                 
844                 while ((newword = aspell_string_enumeration_next(elements)) != NULL)
845                         list = g_list_append(list, g_strdup(newword));
846
847                 gtkaspell->max_sug          = g_list_length(list) - 1;
848                 gtkaspell->suggestions_list = list;
849
850                 return list;
851         }
852
853         free_suggestions_list(gtkaspell);
854
855         return NULL;
856 }
857
858 /* misspelled_test() - Just test if word is correctly spelled */  
859 static int misspelled_test(GtkAspell *gtkaspell, unsigned char *word) 
860 {
861         return aspell_speller_check(gtkaspell->gtkaspeller->checker, word, -1) ? 0 : 1; 
862 }
863
864
865 static gboolean iswordsep(unsigned char c) 
866 {
867         return !isalpha(c) && c != '\'';
868 }
869
870 static guchar get_text_index_whar(GtkAspell *gtkaspell, int pos) 
871 {
872         guchar a;
873         gchar *text;
874         
875         text = gtk_editable_get_chars(GTK_EDITABLE(gtkaspell->gtktext), pos, 
876                                       pos + 1);
877         if (text == NULL) 
878                 return 0;
879
880         a = (guchar) *text;
881
882         g_free(text);
883
884         return a;
885 }
886
887 /* get_word_from_pos () - return the word pointed to. */
888 /* Handles correctly the quotes. */
889 static gboolean get_word_from_pos(GtkAspell *gtkaspell, gint pos, 
890                                   unsigned char* buf, gint buflen,
891                                   gint *pstart, gint *pend) 
892 {
893
894         /* TODO : when correcting a word into quotes, change the color of */
895         /* the quotes too, as may be they were highlighted before. To do  */
896         /* this, we can use two others pointers that points to the whole    */
897         /* word including quotes. */
898
899         gint start;
900         gint end;
901                   
902         guchar c;
903         GtkSText *gtktext;
904         
905         gtktext = gtkaspell->gtktext;
906         if (iswordsep(get_text_index_whar(gtkaspell, pos))) 
907                 return FALSE;
908         
909         /* The apostrophe character is somtimes used for quotes 
910          * So include it in the word only if it is not surrounded 
911          * by other characters. 
912          */
913          
914         for (start = pos; start >= 0; --start) {
915                 c = get_text_index_whar(gtkaspell, start);
916                 if (c == '\'') {
917                         if (start > 0) {
918                                 if (!isalpha(get_text_index_whar(gtkaspell,
919                                                                  start - 1))) {
920                                         /* start_quote = TRUE; */
921                                         break;
922                                 }
923                         }
924                         else {
925                                 /* start_quote = TRUE; */
926                                 break;
927                         }
928                 }
929                 else if (!isalpha(c))
930                                 break;
931         }
932
933         start++;
934
935         for (end = pos; end < gtk_stext_get_length(gtktext); end++) {
936                 c = get_text_index_whar(gtkaspell, end); 
937                 if (c == '\'') {
938                         if (end < gtk_stext_get_length(gtktext)) {
939                                 if (!isalpha(get_text_index_whar(gtkaspell,
940                                                                  end + 1))) {
941                                         /* end_quote = TRUE; */
942                                         break;
943                                 }
944                         }
945                         else {
946                                 /* end_quote = TRUE; */
947                                 break;
948                         }
949                 }
950                 else if(!isalpha(c))
951                                 break;
952         }
953                                                 
954         if (pstart) 
955                 *pstart = start;
956         if (pend) 
957                 *pend = end;
958
959         if (buf) {
960                 if (end - start < buflen) {
961                         for (pos = start; pos < end; pos++) 
962                                 buf[pos - start] =
963                                         get_text_index_whar(gtkaspell, pos);
964                         buf[pos - start] = 0;
965                 } else
966                         return FALSE;
967         }
968
969         return TRUE;
970 }
971
972 static gboolean check_at(GtkAspell *gtkaspell, gint from_pos) 
973 {
974         gint          start, end;
975         unsigned char buf[GTKASPELLWORDSIZE];
976         GtkSText     *gtktext;
977
978         g_return_val_if_fail(from_pos >= 0, FALSE);
979     
980         gtktext = gtkaspell->gtktext;
981
982         if (!get_word_from_pos(gtkaspell, from_pos, buf, sizeof(buf), 
983                                &start, &end))
984                 return FALSE;
985
986         if (misspelled_test(gtkaspell, buf)) {
987                 strncpy(gtkaspell->theword, buf, GTKASPELLWORDSIZE - 1);
988                 gtkaspell->theword[GTKASPELLWORDSIZE - 1] = 0;
989                 gtkaspell->start_pos  = start;
990                 gtkaspell->end_pos    = end;
991                 free_suggestions_list(gtkaspell);
992
993                 change_color(gtkaspell, start, end, buf, &(gtkaspell->highlight));
994                 return TRUE;
995         } else {
996                 change_color(gtkaspell, start, end, buf, NULL);
997                 return FALSE;
998         }
999 }
1000
1001 static gboolean check_next_prev(GtkAspell *gtkaspell, gboolean forward)
1002 {
1003         gint pos;
1004         gint minpos;
1005         gint maxpos;
1006         gint direc = -1;
1007         gboolean misspelled;
1008         
1009         minpos = 0;
1010         maxpos = gtkaspell->end_check_pos;
1011
1012         if (forward) {
1013                 minpos = -1;
1014                 direc = 1;
1015                 maxpos--;
1016         } 
1017
1018         pos = gtk_editable_get_position(GTK_EDITABLE(gtkaspell->gtktext));
1019         gtkaspell->orig_pos = pos;
1020         while (iswordsep(get_text_index_whar(gtkaspell, pos)) &&
1021                pos > minpos && pos <= maxpos) 
1022                 pos += direc;
1023         while (!(misspelled = check_at(gtkaspell, pos)) &&
1024                pos > minpos && pos <= maxpos) {
1025
1026                 while (!iswordsep(get_text_index_whar(gtkaspell, pos)) &&
1027                        pos > minpos && pos <= maxpos)
1028                         pos += direc;
1029
1030                 while (iswordsep(get_text_index_whar(gtkaspell, pos)) && 
1031                        pos > minpos && pos <= maxpos) 
1032                         pos += direc;
1033         }
1034         if (misspelled) {
1035                 misspelled_suggest(gtkaspell, gtkaspell->theword);
1036
1037                 if (forward)
1038                         gtkaspell->orig_pos = gtkaspell->end_pos;
1039
1040                 gtk_stext_set_point(GTK_STEXT(gtkaspell->gtktext),
1041                                 gtkaspell->end_pos);
1042                 gtk_editable_set_position(GTK_EDITABLE(gtkaspell->gtktext),
1043                                 gtkaspell->end_pos);
1044                 gtk_menu_popup(make_sug_menu(gtkaspell), NULL, NULL, 
1045                                 set_menu_pos, gtkaspell, 0, GDK_CURRENT_TIME);
1046         } else {
1047                 reset_theword_data(gtkaspell);
1048
1049                 gtkaspell_alert_dialog(_("No misspelled word found."));
1050                 gtk_stext_set_point(GTK_STEXT(gtkaspell->gtktext),
1051                                     gtkaspell->orig_pos);
1052                 gtk_editable_set_position(GTK_EDITABLE(gtkaspell->gtktext),
1053                                           gtkaspell->orig_pos);
1054
1055                 
1056         }
1057         return misspelled;
1058 }
1059
1060 void gtkaspell_check_backwards(GtkAspell *gtkaspell)
1061 {
1062         gtkaspell->continue_check = NULL;
1063         gtkaspell->end_check_pos =
1064                 gtk_stext_get_length(GTK_STEXT(gtkaspell->gtktext));
1065         check_next_prev(gtkaspell, FALSE);
1066 }
1067
1068 void gtkaspell_check_forwards_go(GtkAspell *gtkaspell)
1069 {
1070
1071         gtkaspell->continue_check = NULL;
1072         gtkaspell->end_check_pos
1073                 = gtk_stext_get_length(GTK_STEXT(gtkaspell->gtktext));
1074         check_next_prev(gtkaspell, TRUE);
1075 }
1076
1077 void gtkaspell_check_all(GtkAspell *gtkaspell)
1078 {       
1079         GtkWidget *gtktext;
1080         gint start, end;
1081
1082         g_return_if_fail(gtkaspell);
1083         g_return_if_fail(gtkaspell->gtktext);
1084
1085         gtktext = (GtkWidget *) gtkaspell->gtktext;
1086
1087         start = 0;      
1088         end   = gtk_stext_get_length(GTK_STEXT(gtktext));
1089
1090         if (GTK_EDITABLE(gtktext)->has_selection) {
1091                 start = GTK_EDITABLE(gtktext)->selection_start_pos;
1092                 end   = GTK_EDITABLE(gtktext)->selection_end_pos;
1093         }
1094
1095         if (start > end) {
1096                 gint tmp;
1097
1098                 tmp   = start;
1099                 start = end;
1100                 end   = tmp;
1101         }
1102                 
1103         
1104         gtk_editable_set_position(GTK_EDITABLE(gtktext), start);
1105         gtk_stext_set_point(GTK_STEXT(gtktext), start);
1106
1107         gtkaspell->continue_check = continue_check;
1108         gtkaspell->end_check_pos  = end;
1109
1110         gtkaspell->misspelled = check_next_prev(gtkaspell, TRUE);
1111
1112 }       
1113
1114 static void continue_check(gpointer *data)
1115 {
1116         GtkAspell *gtkaspell = (GtkAspell *) data;
1117         gint pos = gtk_editable_get_position(GTK_EDITABLE(gtkaspell->gtktext));
1118         if (pos < gtkaspell->end_check_pos && gtkaspell->misspelled)
1119                 gtkaspell->misspelled = check_next_prev(gtkaspell, TRUE);
1120         else
1121                 gtkaspell->continue_check = NULL;
1122                 
1123 }
1124
1125 void gtkaspell_highlight_all(GtkAspell *gtkaspell) 
1126 {
1127         guint     origpos;
1128         guint     pos = 0;
1129         guint     len;
1130         GtkSText *gtktext;
1131         gfloat    adj_value;
1132
1133         g_return_if_fail(gtkaspell->gtkaspeller->checker);      
1134
1135         gtktext = gtkaspell->gtktext;
1136
1137         adj_value = gtktext->vadj->value;
1138
1139         len = gtk_stext_get_length(gtktext);
1140
1141         origpos = gtk_editable_get_position(GTK_EDITABLE(gtktext));
1142
1143         while (pos < len) {
1144                 while (pos < len && 
1145                        iswordsep(get_text_index_whar(gtkaspell, pos)))
1146                         pos++;
1147                 while (pos < len &&
1148                        !iswordsep(get_text_index_whar(gtkaspell, pos)))
1149                         pos++;
1150                 if (pos > 0)
1151                         check_at(gtkaspell, pos - 1);
1152         }
1153         gtk_editable_set_position(GTK_EDITABLE(gtktext), origpos);
1154         gtk_stext_set_point(GTK_STEXT(gtktext), origpos);
1155         gtk_adjustment_set_value(gtktext->vadj, adj_value);
1156 }
1157
1158 static void replace_with_supplied_word_cb(GtkWidget *w, GtkAspell *gtkaspell) 
1159 {
1160         unsigned char *newword;
1161         GdkEvent *e= (GdkEvent *) gtk_get_current_event();
1162         
1163         newword = gtk_editable_get_chars(GTK_EDITABLE(gtkaspell->replace_entry),
1164                                          0, -1);
1165         
1166         if (strcmp(newword, gtkaspell->theword)) {
1167                 replace_real_word(gtkaspell, newword);
1168
1169                 if ((e->type == GDK_KEY_PRESS && 
1170                     ((GdkEventKey *) e)->state & GDK_MOD1_MASK)) {
1171                         aspell_speller_store_replacement(gtkaspell->gtkaspeller->checker, 
1172                                                          gtkaspell->theword, -1, 
1173                                                          newword, -1);
1174                 }
1175                 gtkaspell->replace_entry = NULL;
1176         }
1177
1178         g_free(newword);
1179
1180         set_point_continue(gtkaspell);
1181 }
1182
1183
1184 static void replace_word_cb(GtkWidget *w, gpointer data)
1185 {
1186         unsigned char *newword;
1187         GtkAspell *gtkaspell = (GtkAspell *) data;
1188         GdkEvent *e= (GdkEvent *) gtk_get_current_event();
1189
1190         gtk_label_get(GTK_LABEL(GTK_BIN(w)->child), (gchar**) &newword);
1191
1192         replace_real_word(gtkaspell, newword);
1193
1194         if ((e->type == GDK_KEY_PRESS && 
1195             ((GdkEventKey *) e)->state & GDK_MOD1_MASK) ||
1196             (e->type == GDK_BUTTON_RELEASE && 
1197              ((GdkEventButton *) e)->state & GDK_MOD1_MASK)) {
1198                 aspell_speller_store_replacement(gtkaspell->gtkaspeller->checker, 
1199                                                  gtkaspell->theword, -1, 
1200                                                  newword, -1);
1201         }
1202
1203         gtk_menu_shell_deactivate(GTK_MENU_SHELL(w->parent));
1204
1205         set_point_continue(gtkaspell);
1206 }
1207
1208 static void replace_real_word(GtkAspell *gtkaspell, gchar *newword)
1209 {
1210         int             oldlen, newlen, wordlen;
1211         gint            origpos;
1212         gint            pos;
1213         gint            start = gtkaspell->start_pos;
1214         GtkSText       *gtktext;
1215     
1216         if (!newword) return;
1217
1218         gtktext = gtkaspell->gtktext;
1219
1220         gtk_stext_freeze(GTK_STEXT(gtktext));
1221         origpos = gtkaspell->orig_pos;
1222         pos     = origpos;
1223         oldlen  = gtkaspell->end_pos - gtkaspell->start_pos;
1224         wordlen = strlen(gtkaspell->theword);
1225
1226         newlen = strlen(newword); /* FIXME: multybyte characters? */
1227
1228         gtk_signal_handler_block_by_func(GTK_OBJECT(gtktext),
1229                                          GTK_SIGNAL_FUNC(entry_insert_cb), 
1230                                          gtkaspell);
1231         gtk_signal_handler_block_by_func(GTK_OBJECT(gtktext),
1232                                          GTK_SIGNAL_FUNC(entry_delete_cb), 
1233                                          gtkaspell);
1234
1235         gtk_signal_emit_by_name(GTK_OBJECT(gtktext), "delete-text", 
1236                                 gtkaspell->start_pos, gtkaspell->end_pos);
1237         gtk_signal_emit_by_name(GTK_OBJECT(gtktext), "insert-text", 
1238                                 newword, newlen, &start);
1239         
1240         gtk_signal_handler_unblock_by_func(GTK_OBJECT(gtktext),
1241                                            GTK_SIGNAL_FUNC(entry_insert_cb), 
1242                                            gtkaspell);
1243         gtk_signal_handler_unblock_by_func(GTK_OBJECT(gtktext),
1244                                            GTK_SIGNAL_FUNC(entry_delete_cb), 
1245                                            gtkaspell);
1246         
1247         /* Put the point and the position where we clicked with the mouse
1248          * It seems to be a hack, as I must thaw,freeze,thaw the widget
1249          * to let it update correctly the word insertion and then the
1250          * point & position position. If not, SEGV after the first replacement
1251          * If the new word ends before point, put the point at its end.
1252          */
1253     
1254         if (origpos - gtkaspell->start_pos < oldlen && 
1255             origpos - gtkaspell->start_pos >= 0) {
1256                 /* Original point was in the word.
1257                  * Let it there unless point is going to be outside of the word
1258                  */
1259                 if (origpos - gtkaspell->start_pos >= newlen) {
1260                         pos = gtkaspell->start_pos + newlen;
1261                 }
1262         }
1263         else if (origpos >= gtkaspell->end_pos) {
1264                 /* move the position according to the change of length */
1265                 pos = origpos + newlen - oldlen;
1266         }
1267         
1268         gtkaspell->end_pos = gtkaspell->start_pos + strlen(newword); /* FIXME: multibyte characters? */
1269         
1270         gtk_stext_thaw(GTK_STEXT(gtktext));
1271         gtk_stext_freeze(GTK_STEXT(gtktext));
1272
1273         if (GTK_STEXT(gtktext)->text_len < pos)
1274                 pos = gtk_stext_get_length(GTK_STEXT(gtktext));
1275
1276         gtkaspell->orig_pos = pos;
1277
1278         gtk_editable_set_position(GTK_EDITABLE(gtktext), gtkaspell->orig_pos);
1279         gtk_stext_set_point(GTK_STEXT(gtktext), 
1280                             gtk_editable_get_position(GTK_EDITABLE(gtktext)));
1281
1282         gtk_stext_thaw(GTK_STEXT(gtktext));
1283 }
1284
1285 /* Accept this word for this session */
1286 static void add_word_to_session_cb(GtkWidget *w, gpointer data)
1287 {
1288         guint     pos;
1289         GtkSText *gtktext;
1290         GtkAspell *gtkaspell = (GtkAspell *) data; 
1291         gtktext = gtkaspell->gtktext;
1292
1293         gtk_stext_freeze(GTK_STEXT(gtktext));
1294
1295         pos = gtk_editable_get_position(GTK_EDITABLE(gtktext));
1296     
1297         aspell_speller_add_to_session(gtkaspell->gtkaspeller->checker,
1298                                       gtkaspell->theword, 
1299                                       strlen(gtkaspell->theword));
1300
1301         check_at(gtkaspell, gtkaspell->start_pos);
1302
1303         gtk_stext_thaw(gtkaspell->gtktext);
1304
1305         gtk_menu_shell_deactivate(GTK_MENU_SHELL(GTK_WIDGET(w)->parent));
1306
1307         set_point_continue(gtkaspell);
1308 }
1309
1310 /* add_word_to_personal_cb() - add word to personal dict. */
1311 static void add_word_to_personal_cb(GtkWidget *w, gpointer data)
1312 {
1313         GtkAspell *gtkaspell = (GtkAspell *) data; 
1314         GtkSText *gtktext    = gtkaspell->gtktext;
1315
1316         gtk_stext_freeze(GTK_STEXT(gtktext));
1317     
1318         aspell_speller_add_to_personal(gtkaspell->gtkaspeller->checker,
1319                                        gtkaspell->theword,
1320                                        strlen(gtkaspell->theword));
1321     
1322         check_at(gtkaspell, gtkaspell->start_pos);
1323
1324         gtk_stext_thaw(gtkaspell->gtktext);
1325
1326         gtk_menu_shell_deactivate(GTK_MENU_SHELL(GTK_WIDGET(w)->parent));
1327         set_point_continue(gtkaspell);
1328 }
1329
1330 static void check_with_alternate_cb(GtkWidget *w, gpointer data)
1331 {
1332         GtkAspell *gtkaspell = (GtkAspell *) data;
1333         gint misspelled;
1334
1335         gtk_menu_shell_deactivate(GTK_MENU_SHELL(GTK_WIDGET(w)->parent));
1336
1337         use_alternate_dict(gtkaspell);
1338         misspelled = check_at(gtkaspell, gtkaspell->start_pos);
1339
1340         if (!gtkaspell->continue_check) {
1341
1342                 gtkaspell->misspelled = misspelled;
1343
1344                 if (gtkaspell->misspelled) {
1345
1346                         misspelled_suggest(gtkaspell, gtkaspell->theword);
1347
1348                         gtk_stext_set_point(GTK_STEXT(gtkaspell->gtktext),
1349                                             gtkaspell->end_pos);
1350                         gtk_editable_set_position(GTK_EDITABLE(gtkaspell->gtktext),
1351                                                   gtkaspell->end_pos);
1352
1353                         gtk_menu_popup(make_sug_menu(gtkaspell), NULL, NULL, 
1354                                        set_menu_pos, gtkaspell, 0, 
1355                                        GDK_CURRENT_TIME);
1356                         return;
1357                 }
1358         } else
1359                 gtkaspell->orig_pos = gtkaspell->start_pos;
1360
1361         set_point_continue(gtkaspell);
1362 }
1363         
1364 static void replace_with_create_dialog_cb(GtkWidget *w, gpointer data)
1365 {
1366         GtkWidget *dialog;
1367         GtkWidget *label;
1368         GtkWidget *hbox;
1369         GtkWidget *entry;
1370         GtkWidget *ok_button;
1371         GtkWidget *cancel_button;
1372         gchar *thelabel;
1373         gint xx, yy;
1374         GtkAspell *gtkaspell = (GtkAspell *) data;
1375
1376         gdk_window_get_origin((GTK_WIDGET(w)->parent)->window, &xx, &yy);
1377
1378         gtk_menu_shell_deactivate(GTK_MENU_SHELL(GTK_WIDGET(w)->parent));
1379
1380         dialog = gtk_dialog_new();
1381
1382         gtk_window_set_policy(GTK_WINDOW(dialog), FALSE, FALSE, FALSE);
1383         gtk_window_set_title(GTK_WINDOW(dialog),_("Replace unknown word"));
1384         gtk_widget_set_uposition(dialog, xx, yy);
1385
1386         gtk_signal_connect_object(GTK_OBJECT(dialog), "destroy",
1387                                   GTK_SIGNAL_FUNC(gtk_widget_destroy), 
1388                                   GTK_OBJECT(dialog));
1389
1390         hbox = gtk_hbox_new(FALSE, 0);
1391         gtk_container_set_border_width(GTK_CONTAINER(hbox), 8);
1392
1393         thelabel = g_strdup_printf(_("Replace \"%s\" with: "), 
1394                                    gtkaspell->theword);
1395         label = gtk_label_new(thelabel);
1396         g_free(thelabel);
1397         gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 0);
1398
1399         entry = gtk_entry_new();
1400         gtkaspell->replace_entry = entry;
1401         gtk_entry_set_text(GTK_ENTRY(entry), gtkaspell->theword);
1402         gtk_editable_select_region(GTK_EDITABLE(entry), 0, -1);
1403         gtk_signal_connect(GTK_OBJECT(entry), "activate",
1404                            GTK_SIGNAL_FUNC(replace_with_supplied_word_cb), 
1405                            gtkaspell);
1406         gtk_signal_connect_object(GTK_OBJECT(entry), "activate",
1407                            GTK_SIGNAL_FUNC(gtk_widget_destroy), 
1408                            GTK_OBJECT(dialog));
1409         gtk_box_pack_start(GTK_BOX(hbox), entry, TRUE, TRUE, 0);
1410
1411         gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->vbox), hbox, TRUE, 
1412                            TRUE, 0);
1413         label = gtk_label_new(_("Holding down MOD1 key while pressing "
1414                                 "Enter\nwill learn from mistake.\n"));
1415         gtk_label_set_justify(GTK_LABEL(label), GTK_JUSTIFY_LEFT);
1416         gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.0);
1417         gtk_misc_set_padding(GTK_MISC(label), 8, 0);
1418         gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->vbox), label, 
1419                         TRUE, TRUE, 0);
1420
1421         hbox = gtk_hbox_new(TRUE, 0);
1422
1423         ok_button = gtk_button_new_with_label(_("OK"));
1424         gtk_box_pack_start(GTK_BOX(hbox), ok_button, TRUE, TRUE, 8);
1425         gtk_signal_connect(GTK_OBJECT(ok_button), "clicked",
1426                         GTK_SIGNAL_FUNC(replace_with_supplied_word_cb), 
1427                         gtkaspell);
1428         gtk_signal_connect_object(GTK_OBJECT(ok_button), "clicked",
1429                         GTK_SIGNAL_FUNC(gtk_widget_destroy), 
1430                         GTK_OBJECT(dialog));
1431
1432         cancel_button = gtk_button_new_with_label(_("Cancel"));
1433         gtk_box_pack_start(GTK_BOX(hbox), cancel_button, TRUE, TRUE, 8);
1434         gtk_signal_connect_object(GTK_OBJECT(cancel_button), "clicked",
1435                         GTK_SIGNAL_FUNC(gtk_widget_destroy), 
1436                         GTK_OBJECT(dialog));
1437
1438         gtk_container_add(GTK_CONTAINER(GTK_DIALOG(dialog)->action_area), hbox);
1439
1440         gtk_widget_grab_focus(entry);
1441
1442         gtk_window_set_modal(GTK_WINDOW(dialog), TRUE);
1443
1444         gtk_widget_show_all(dialog);
1445 }
1446
1447 void gtkaspell_uncheck_all(GtkAspell * gtkaspell) 
1448 {
1449         gint      origpos;
1450         gchar    *text;
1451         gfloat    adj_value;
1452         GtkSText *gtktext;
1453         
1454         gtktext = gtkaspell->gtktext;
1455
1456         adj_value = gtktext->vadj->value;
1457
1458         gtk_stext_freeze(gtktext);
1459
1460         origpos = gtk_editable_get_position(GTK_EDITABLE(gtktext));
1461
1462         text = gtk_editable_get_chars(GTK_EDITABLE(gtktext), 0, -1);
1463
1464         gtk_stext_set_point(gtktext, 0);
1465         gtk_stext_forward_delete(gtktext, gtk_stext_get_length(gtktext));
1466         gtk_stext_insert(gtktext, NULL, NULL, NULL, text, strlen(text));
1467
1468         gtk_stext_thaw(gtktext);
1469
1470         gtk_editable_set_position(GTK_EDITABLE(gtktext), origpos);
1471         gtk_stext_set_point(gtktext, origpos);
1472         gtk_adjustment_set_value(gtktext->vadj, adj_value);
1473
1474         g_free(text);
1475
1476 }
1477
1478 static void toggle_check_while_typing_cb(GtkWidget *w, gpointer data)
1479 {
1480         GtkAspell *gtkaspell = (GtkAspell *) data;
1481
1482         gtkaspell->check_while_typing = gtkaspell->check_while_typing == FALSE;
1483
1484         if (!gtkaspell->check_while_typing)
1485                 gtkaspell_uncheck_all(gtkaspell);
1486
1487         if (gtkaspell->config_menu)
1488                 populate_submenu(gtkaspell, gtkaspell->config_menu);
1489 }
1490
1491 static GSList *create_empty_dictionary_list(void)
1492 {
1493         GSList *list = NULL;
1494         Dictionary *dict;
1495
1496         dict = g_new0(Dictionary, 1);
1497         dict->fullname = g_strdup(_("None"));
1498         dict->dictname = dict->fullname;
1499         dict->encoding = NULL;
1500
1501         return g_slist_append(list, dict);
1502 }
1503
1504 /* gtkaspell_get_dictionary_list() - returns list of dictionary names */
1505 GSList *gtkaspell_get_dictionary_list(const gchar *aspell_path, gint refresh)
1506 {
1507         GSList *list;
1508         Dictionary *dict;
1509         AspellConfig *config;
1510         AspellDictInfoList *dlist;
1511         AspellDictInfoEnumeration *dels;
1512         const AspellDictInfo *entry;
1513
1514         if (!gtkaspellcheckers)
1515                 gtkaspell_checkers_init();
1516
1517         if (gtkaspellcheckers->dictionary_list && !refresh)
1518                 return gtkaspellcheckers->dictionary_list;
1519         else
1520                 gtkaspell_free_dictionary_list(gtkaspellcheckers->dictionary_list);
1521         list = NULL;
1522
1523         config = new_aspell_config();
1524 #if 0 
1525         aspell_config_replace(config, "rem-all-word-list-path", "");
1526         if (aspell_config_error_number(config) != 0) {
1527                 gtkaspellcheckers->error_message = g_strdup(
1528                                 aspell_config_error_message(config));
1529                 gtkaspellcheckers->dictionary_list =
1530                         create_empty_dictionary_list();
1531
1532                 return gtkaspellcheckers->dictionary_list; 
1533         }
1534 #endif
1535         aspell_config_replace(config, "dict-dir", aspell_path);
1536         if (aspell_config_error_number(config) != 0) {
1537                 gtkaspellcheckers->error_message = g_strdup(
1538                                 aspell_config_error_message(config));
1539                 gtkaspellcheckers->dictionary_list =
1540                         create_empty_dictionary_list();
1541
1542                 return gtkaspellcheckers->dictionary_list; 
1543         }
1544
1545         dlist = get_aspell_dict_info_list(config);
1546         delete_aspell_config(config);
1547
1548         debug_print("Aspell: checking for dictionaries in %s\n", aspell_path);
1549         dels = aspell_dict_info_list_elements(dlist);
1550         while ( (entry = aspell_dict_info_enumeration_next(dels)) != 0) 
1551         {
1552                 dict = g_new0(Dictionary, 1);
1553                 dict->fullname = g_strdup_printf("%s%s", aspell_path, 
1554                                 entry->name);
1555                 dict->dictname = dict->fullname + strlen(aspell_path);
1556                 dict->encoding = g_strdup(entry->code);
1557                 debug_print("Aspell: found dictionary %s %s\n", dict->fullname,
1558                                 dict->dictname);
1559                 list = g_slist_insert_sorted(list, dict,
1560                                 (GCompareFunc) compare_dict);
1561         }
1562
1563         delete_aspell_dict_info_enumeration(dels);
1564         
1565         if(list==NULL){
1566                 
1567                 debug_print("Aspell: error when searching for dictionaries: "
1568                               "No dictionary found.\n");
1569                 list = create_empty_dictionary_list();
1570         }
1571
1572         gtkaspellcheckers->dictionary_list = list;
1573
1574         return list;
1575 }
1576
1577 void gtkaspell_free_dictionary_list(GSList *list)
1578 {
1579         Dictionary *dict;
1580         GSList *walk;
1581         for (walk = list; walk != NULL; walk = g_slist_next(walk))
1582                 if (walk->data) {
1583                         dict = (Dictionary *) walk->data;
1584                         dictionary_delete(dict);
1585                 }                               
1586         g_slist_free(list);
1587 }
1588
1589 GtkWidget *gtkaspell_dictionary_option_menu_new(const gchar *aspell_path)
1590 {
1591         GSList *dict_list, *tmp;
1592         GtkWidget *item;
1593         GtkWidget *menu;
1594         Dictionary *dict;
1595
1596         dict_list = gtkaspell_get_dictionary_list(aspell_path, TRUE);
1597         g_return_val_if_fail(dict_list, NULL);
1598
1599         menu = gtk_menu_new();
1600         
1601         for (tmp = dict_list; tmp != NULL; tmp = g_slist_next(tmp)) {
1602                 dict = (Dictionary *) tmp->data;
1603                 item = gtk_menu_item_new_with_label(dict->dictname);
1604                 gtk_object_set_data(GTK_OBJECT(item), "dict_name",
1605                                     dict->fullname); 
1606                                          
1607                 gtk_menu_append(GTK_MENU(menu), item);                                   
1608                 gtk_widget_show(item);
1609         }
1610
1611         gtk_widget_show(menu);
1612
1613         return menu;
1614 }
1615
1616 gchar *gtkaspell_get_dictionary_menu_active_item(GtkWidget *menu)
1617 {
1618         GtkWidget *menuitem;
1619         gchar *dict_fullname;
1620         gchar *label;
1621
1622         g_return_val_if_fail(GTK_IS_MENU(menu), NULL);
1623
1624         menuitem = gtk_menu_get_active(GTK_MENU(menu));
1625         dict_fullname = (gchar *) gtk_object_get_data(GTK_OBJECT(menuitem), 
1626                                                       "dict_name");
1627         g_return_val_if_fail(dict_fullname, NULL);
1628
1629         label = g_strdup(dict_fullname);
1630
1631         return label;
1632   
1633 }
1634
1635 GtkWidget *gtkaspell_sugmode_option_menu_new(gint sugmode)
1636 {
1637         GtkWidget *menu;
1638         GtkWidget *item;
1639
1640         menu = gtk_menu_new();
1641         gtk_widget_show(menu);
1642
1643         item = gtk_menu_item_new_with_label(_("Fast Mode"));
1644         gtk_widget_show(item);
1645         gtk_menu_append(GTK_MENU(menu), item);
1646         gtk_object_set_data(GTK_OBJECT(item), "sugmode", GINT_TO_POINTER(ASPELL_FASTMODE));
1647
1648         item = gtk_menu_item_new_with_label(_("Normal Mode"));
1649         gtk_widget_show(item);
1650         gtk_menu_append(GTK_MENU(menu), item);
1651         gtk_object_set_data(GTK_OBJECT(item), "sugmode", GINT_TO_POINTER(ASPELL_NORMALMODE));
1652         
1653         item = gtk_menu_item_new_with_label(_("Bad Spellers Mode"));
1654         gtk_widget_show(item);
1655         gtk_menu_append(GTK_MENU(menu), item);
1656         gtk_object_set_data(GTK_OBJECT(item), "sugmode", GINT_TO_POINTER(ASPELL_BADSPELLERMODE));
1657
1658         return menu;
1659 }
1660         
1661 void gtkaspell_sugmode_option_menu_set(GtkOptionMenu *optmenu, gint sugmode)
1662 {
1663         g_return_if_fail(GTK_IS_OPTION_MENU(optmenu));
1664
1665         g_return_if_fail(sugmode == ASPELL_FASTMODE ||
1666                          sugmode == ASPELL_NORMALMODE ||
1667                          sugmode == ASPELL_BADSPELLERMODE);
1668
1669         gtk_option_menu_set_history(GTK_OPTION_MENU(optmenu), sugmode - 1);
1670 }
1671
1672 gint gtkaspell_get_sugmode_from_option_menu(GtkOptionMenu *optmenu)
1673 {
1674         gint sugmode;
1675         GtkWidget *item;
1676         
1677         g_return_val_if_fail(GTK_IS_OPTION_MENU(optmenu), -1);
1678
1679         item = gtk_menu_get_active(GTK_MENU(gtk_option_menu_get_menu(optmenu)));
1680         
1681         sugmode = GPOINTER_TO_INT(gtk_object_get_data(GTK_OBJECT(item),
1682                                                       "sugmode"));
1683
1684         return sugmode;
1685 }
1686
1687 static void use_alternate_dict(GtkAspell *gtkaspell)
1688 {
1689         GtkAspeller *tmp;
1690
1691         tmp = gtkaspell->gtkaspeller;
1692         gtkaspell->gtkaspeller = gtkaspell->alternate_speller;
1693         gtkaspell->alternate_speller = tmp;
1694
1695         if (gtkaspell->config_menu)
1696                 populate_submenu(gtkaspell, gtkaspell->config_menu);
1697 }
1698
1699 static void popup_menu(GtkAspell *gtkaspell, GdkEventButton *eb) 
1700 {
1701         GtkSText * gtktext;
1702         
1703         gtktext = gtkaspell->gtktext;
1704         gtkaspell->orig_pos = gtk_editable_get_position(GTK_EDITABLE(gtktext));
1705
1706         if (!(eb->state & GDK_SHIFT_MASK)) {
1707                 if (check_at(gtkaspell, gtkaspell->orig_pos)) {
1708
1709                         gtk_editable_set_position(GTK_EDITABLE(gtktext), 
1710                                                   gtkaspell->orig_pos);
1711                         gtk_stext_set_point(gtktext, gtkaspell->orig_pos);
1712
1713                         if (misspelled_suggest(gtkaspell, gtkaspell->theword)) {
1714                                 gtk_menu_popup(make_sug_menu(gtkaspell), 
1715                                                NULL, NULL, NULL, NULL,
1716                                                eb->button, GDK_CURRENT_TIME);
1717                                 
1718                                 return;
1719                         }
1720                 } else {
1721                         gtk_editable_set_position(GTK_EDITABLE(gtktext), 
1722                                                   gtkaspell->orig_pos);
1723                         gtk_stext_set_point(gtktext, gtkaspell->orig_pos);
1724                 }
1725         }
1726
1727         gtk_menu_popup(make_config_menu(gtkaspell), NULL, NULL, NULL, NULL,
1728                        eb->button, GDK_CURRENT_TIME);
1729 }
1730
1731 /* make_sug_menu() - Add menus to accept this word for this session 
1732  * and to add it to personal dictionary 
1733  */
1734 static GtkMenu *make_sug_menu(GtkAspell *gtkaspell) 
1735 {
1736         GtkWidget       *menu, *item;
1737         unsigned char   *caption;
1738         GtkSText        *gtktext;
1739         GtkAccelGroup   *accel;
1740         GList           *l = gtkaspell->suggestions_list;
1741
1742         gtktext = gtkaspell->gtktext;
1743
1744         accel = gtk_accel_group_new();
1745         menu = gtk_menu_new(); 
1746
1747         if (gtkaspell->sug_menu)
1748                 gtk_widget_destroy(gtkaspell->sug_menu);
1749
1750         gtkaspell->sug_menu = menu;     
1751
1752         gtk_signal_connect(GTK_OBJECT(menu), "cancel",
1753                 GTK_SIGNAL_FUNC(cancel_menu_cb), gtkaspell);
1754
1755         caption = g_strdup_printf(_("\"%s\" unknown in %s"), 
1756                                   (unsigned char*) l->data, 
1757                                   gtkaspell->gtkaspeller->dictionary->dictname);
1758         item = gtk_menu_item_new_with_label(caption);
1759         gtk_widget_show(item);
1760         gtk_menu_append(GTK_MENU(menu), item);
1761         gtk_misc_set_alignment(GTK_MISC(GTK_BIN(item)->child), 0.5, 0.5);
1762         g_free(caption);
1763
1764         item = gtk_menu_item_new();
1765         gtk_widget_show(item);
1766         gtk_menu_append(GTK_MENU(menu), item);
1767
1768         item = gtk_menu_item_new_with_label(_("Accept in this session"));
1769         gtk_widget_show(item);
1770         gtk_menu_append(GTK_MENU(menu), item);
1771         gtk_signal_connect(GTK_OBJECT(item), "activate",
1772                            GTK_SIGNAL_FUNC(add_word_to_session_cb), 
1773                            gtkaspell);
1774         gtk_widget_add_accelerator(item, "activate", accel, GDK_space,
1775                                    GDK_MOD1_MASK,
1776                                    GTK_ACCEL_LOCKED | GTK_ACCEL_VISIBLE);
1777
1778         item = gtk_menu_item_new_with_label(_("Add to personal dictionary"));
1779         gtk_widget_show(item);
1780         gtk_menu_append(GTK_MENU(menu), item);
1781         gtk_signal_connect(GTK_OBJECT(item), "activate",
1782                            GTK_SIGNAL_FUNC(add_word_to_personal_cb), 
1783                            gtkaspell);
1784         gtk_widget_add_accelerator(item, "activate", accel, GDK_Return,
1785                                    GDK_MOD1_MASK,
1786                                    GTK_ACCEL_LOCKED | GTK_ACCEL_VISIBLE);
1787
1788         item = gtk_menu_item_new_with_label(_("Replace with..."));
1789         gtk_widget_show(item);
1790         gtk_menu_append(GTK_MENU(menu), item);
1791         gtk_signal_connect(GTK_OBJECT(item), "activate",
1792                            GTK_SIGNAL_FUNC(replace_with_create_dialog_cb), 
1793                            gtkaspell);
1794         gtk_widget_add_accelerator(item, "activate", accel, GDK_R, 0,
1795                                    GTK_ACCEL_LOCKED | GTK_ACCEL_VISIBLE);
1796
1797         if (gtkaspell->use_alternate && gtkaspell->alternate_speller) {
1798                 caption = g_strdup_printf(_("Check with %s"), 
1799                         gtkaspell->alternate_speller->dictionary->dictname);
1800                 item = gtk_menu_item_new_with_label(caption);
1801                 g_free(caption);
1802                 gtk_widget_show(item);
1803                 gtk_menu_append(GTK_MENU(menu), item);
1804                 gtk_signal_connect(GTK_OBJECT(item), "activate",
1805                                 GTK_SIGNAL_FUNC(check_with_alternate_cb),
1806                                 gtkaspell);
1807                 gtk_widget_add_accelerator(item, "activate", accel, GDK_X, 0,
1808                                            GTK_ACCEL_LOCKED | GTK_ACCEL_VISIBLE);
1809         }
1810
1811         item = gtk_menu_item_new();
1812         gtk_widget_show(item);
1813         gtk_menu_append(GTK_MENU(menu), item);
1814
1815         l = l->next;
1816         if (l == NULL) {
1817                 item = gtk_menu_item_new_with_label(_("(no suggestions)"));
1818                 gtk_widget_show(item);
1819                 gtk_menu_append(GTK_MENU(menu), item);
1820         } else {
1821                 GtkWidget *curmenu = menu;
1822                 gint count = 0;
1823                 
1824                 do {
1825                         if (count == MENUCOUNT) {
1826                                 count -= MENUCOUNT;
1827
1828                                 item = gtk_menu_item_new_with_label(_("More..."));
1829                                 gtk_widget_show(item);
1830                                 gtk_menu_append(GTK_MENU(curmenu), item);
1831
1832                                 curmenu = gtk_menu_new();
1833                                 gtk_menu_item_set_submenu(GTK_MENU_ITEM(item),
1834                                                           curmenu);
1835                         }
1836
1837                         item = gtk_menu_item_new_with_label((unsigned char*)l->data);
1838                         gtk_widget_show(item);
1839                         gtk_menu_append(GTK_MENU(curmenu), item);
1840                         gtk_signal_connect(GTK_OBJECT(item), "activate",
1841                                            GTK_SIGNAL_FUNC(replace_word_cb),
1842                                            gtkaspell);
1843
1844                         if (curmenu == menu && count < MENUCOUNT) {
1845                                 gtk_widget_add_accelerator(item, "activate",
1846                                                            accel,
1847                                                            GDK_A + count, 0,
1848                                                            GTK_ACCEL_LOCKED | 
1849                                                            GTK_ACCEL_VISIBLE);
1850                                 gtk_widget_add_accelerator(item, "activate", 
1851                                                            accel,
1852                                                            GDK_A + count, 
1853                                                            GDK_MOD1_MASK,
1854                                                            GTK_ACCEL_LOCKED);
1855                                 }
1856
1857                         count++;
1858
1859                 } while ((l = l->next) != NULL);
1860         }
1861
1862         gtk_accel_group_attach(accel, GTK_OBJECT(menu));
1863         gtk_accel_group_unref(accel);
1864         
1865         return GTK_MENU(menu);
1866 }
1867
1868 static void populate_submenu(GtkAspell *gtkaspell, GtkWidget *menu)
1869 {
1870         GtkWidget *item, *submenu;
1871         gchar *dictname;
1872         GtkAspeller *gtkaspeller = gtkaspell->gtkaspeller;
1873
1874         if (GTK_MENU_SHELL(menu)->children) {
1875                 GList *amenu, *alist;
1876                 for (amenu = (GTK_MENU_SHELL(menu)->children); amenu; ) {
1877                         alist = amenu->next;
1878                         gtk_widget_destroy(GTK_WIDGET(amenu->data));
1879                         amenu = alist;
1880                 }
1881         }
1882         
1883         dictname = g_strdup_printf(_("Dictionary: %s"),
1884                                    gtkaspeller->dictionary->dictname);
1885         item = gtk_menu_item_new_with_label(dictname);
1886         gtk_misc_set_alignment(GTK_MISC(GTK_BIN(item)->child), 0.5, 0.5);
1887         g_free(dictname);
1888         gtk_widget_show(item);
1889         gtk_menu_append(GTK_MENU(menu), item);
1890
1891         item = gtk_menu_item_new();
1892         gtk_widget_show(item);
1893         gtk_menu_append(GTK_MENU(menu), item);
1894                 
1895         if (gtkaspell->use_alternate && gtkaspell->alternate_speller) {
1896                 dictname = g_strdup_printf(_("Use alternate (%s)"), 
1897                                 gtkaspell->alternate_speller->dictionary->dictname);
1898                 item = gtk_menu_item_new_with_label(dictname);
1899                 g_free(dictname);
1900                 gtk_signal_connect(GTK_OBJECT(item), "activate",
1901                                    GTK_SIGNAL_FUNC(switch_to_alternate_cb),
1902                                    gtkaspell);
1903                 gtk_widget_show(item);
1904                 gtk_menu_append(GTK_MENU(menu), item);
1905         }
1906
1907         item = gtk_check_menu_item_new_with_label(_("Fast Mode"));
1908         if (gtkaspell->gtkaspeller->sug_mode == ASPELL_FASTMODE) {
1909                 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(item),TRUE);
1910                 gtk_widget_set_sensitive(GTK_WIDGET(item),FALSE);
1911         } else
1912                 gtk_signal_connect(GTK_OBJECT(item), "activate",
1913                                    GTK_SIGNAL_FUNC(set_sug_mode_cb),
1914                                    gtkaspell);
1915         gtk_widget_show(item);
1916         gtk_menu_append(GTK_MENU(menu), item);
1917
1918         item = gtk_check_menu_item_new_with_label(_("Normal Mode"));
1919         if (gtkaspell->gtkaspeller->sug_mode == ASPELL_NORMALMODE) {
1920                 gtk_widget_set_sensitive(GTK_WIDGET(item), FALSE);
1921                 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(item), TRUE);
1922         } else
1923                 gtk_signal_connect(GTK_OBJECT(item), "activate",
1924                                    GTK_SIGNAL_FUNC(set_sug_mode_cb),
1925                                    gtkaspell);
1926         gtk_widget_show(item);
1927         gtk_menu_append(GTK_MENU(menu),item);
1928
1929         item = gtk_check_menu_item_new_with_label(_("Bad Spellers Mode"));
1930         if (gtkaspell->gtkaspeller->sug_mode == ASPELL_BADSPELLERMODE) {
1931                 gtk_widget_set_sensitive(GTK_WIDGET(item), FALSE);
1932                 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(item), TRUE);
1933         } else
1934                 gtk_signal_connect(GTK_OBJECT(item), "activate",
1935                                    GTK_SIGNAL_FUNC(set_sug_mode_cb),
1936                                    gtkaspell);
1937         gtk_widget_show(item);
1938         gtk_menu_append(GTK_MENU(menu), item);
1939         
1940         item = gtk_menu_item_new();
1941         gtk_widget_show(item);
1942         gtk_menu_append(GTK_MENU(menu), item);
1943         
1944         item = gtk_check_menu_item_new_with_label(_("Check while typing"));
1945         if (gtkaspell->check_while_typing)
1946                 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(item), TRUE);
1947         else    
1948                 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(item), FALSE);
1949         gtk_signal_connect(GTK_OBJECT(item), "activate",
1950                            GTK_SIGNAL_FUNC(toggle_check_while_typing_cb),
1951                            gtkaspell);
1952         gtk_widget_show(item);
1953         gtk_menu_append(GTK_MENU(menu), item);
1954
1955         item = gtk_menu_item_new();
1956         gtk_widget_show(item);
1957         gtk_menu_append(GTK_MENU(menu), item);
1958
1959         submenu = gtk_menu_new();
1960         item = gtk_menu_item_new_with_label(_("Change dictionary"));
1961         gtk_menu_item_set_submenu(GTK_MENU_ITEM(item),submenu);
1962         gtk_widget_show(item);
1963         gtk_menu_append(GTK_MENU(menu), item);
1964
1965         /* Dict list */
1966         if (gtkaspellcheckers->dictionary_list == NULL)
1967                 gtkaspell_get_dictionary_list(gtkaspell->dictionary_path, FALSE);
1968         {
1969                 GtkWidget * curmenu = submenu;
1970                 int count = 0;
1971                 Dictionary *dict;
1972                 GSList *tmp;
1973                 tmp = gtkaspellcheckers->dictionary_list;
1974                 
1975                 for (tmp = gtkaspellcheckers->dictionary_list; tmp != NULL; 
1976                                 tmp = g_slist_next(tmp)) {
1977                         if (count == MENUCOUNT) {
1978                                 GtkWidget *newmenu;
1979                                 
1980                                 newmenu = gtk_menu_new();
1981                                 item = gtk_menu_item_new_with_label(_("More..."));
1982                                 gtk_menu_item_set_submenu(GTK_MENU_ITEM(item), 
1983                                                           newmenu);
1984                                 
1985                                 gtk_menu_append(GTK_MENU(curmenu), item);
1986                                 gtk_widget_show(item);
1987                                 curmenu = newmenu;
1988                                 count = 0;
1989                         }
1990                         dict = (Dictionary *) tmp->data;
1991                         item = gtk_check_menu_item_new_with_label(dict->dictname);
1992                         gtk_object_set_data(GTK_OBJECT(item), "dict_name",
1993                                             dict->fullname); 
1994                         if (strcmp2(dict->fullname,
1995                             gtkaspell->gtkaspeller->dictionary->fullname))
1996                                 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(item), FALSE);
1997                         else {
1998                                 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(item), TRUE);
1999                                 gtk_widget_set_sensitive(GTK_WIDGET(item),
2000                                                          FALSE);
2001                         }
2002                         gtk_signal_connect(GTK_OBJECT(item), "activate",
2003                                            GTK_SIGNAL_FUNC(change_dict_cb),
2004                                            gtkaspell);
2005                         gtk_widget_show(item);
2006                         gtk_menu_append(GTK_MENU(curmenu), item);
2007                         
2008                         count++;
2009                 }
2010         }  
2011 }
2012
2013 static GtkMenu *make_config_menu(GtkAspell *gtkaspell)
2014 {
2015         if (!gtkaspell->popup_config_menu)
2016                 gtkaspell->popup_config_menu = gtk_menu_new();
2017
2018         debug_print("Aspell: creating/using popup_config_menu %0x\n", 
2019                         (guint) gtkaspell->popup_config_menu);
2020         populate_submenu(gtkaspell, gtkaspell->popup_config_menu);
2021
2022         return GTK_MENU(gtkaspell->popup_config_menu);
2023 }
2024
2025 void gtkaspell_populate_submenu(GtkAspell *gtkaspell, GtkWidget *menuitem)
2026 {
2027         GtkWidget *menu;
2028
2029         menu = GTK_WIDGET(GTK_MENU_ITEM(menuitem)->submenu);
2030         
2031         debug_print("Aspell: using config menu %0x\n", 
2032                         (guint) gtkaspell->popup_config_menu);
2033         populate_submenu(gtkaspell, menu);
2034         
2035         gtkaspell->config_menu = menu;
2036         
2037 }
2038
2039 static void set_menu_pos(GtkMenu *menu, gint *x, gint *y, gpointer data)
2040 {
2041         GtkAspell       *gtkaspell = (GtkAspell *) data;
2042         gint             xx = 0, yy = 0;
2043         gint             sx,     sy;
2044         gint             wx,     wy;
2045         GtkSText        *text = GTK_STEXT(gtkaspell->gtktext);
2046         GtkRequisition   r;
2047
2048         gdk_window_get_origin(GTK_WIDGET(gtkaspell->gtktext)->window, &xx, &yy);
2049         
2050         sx = gdk_screen_width();
2051         sy = gdk_screen_height();
2052         
2053         gtk_widget_get_child_requisition(GTK_WIDGET(menu), &r);
2054         
2055         wx =  r.width;
2056         wy =  r.height;
2057         
2058         *x = gtkaspell->gtktext->cursor_pos_x + xx +
2059              gdk_char_width(GTK_WIDGET(text)->style->font, ' ');
2060         *y = gtkaspell->gtktext->cursor_pos_y + yy;
2061
2062         if (*x + wx > sx)
2063                 *x = sx - wx;
2064         if (*y + wy > sy)
2065                 *y = *y - wy - 
2066                      gdk_string_height((GTK_WIDGET(gtkaspell->gtktext))->style->font, 
2067                                        gtkaspell->theword);
2068
2069 }
2070
2071 /* Menu call backs */
2072
2073 static gboolean cancel_menu_cb(GtkMenuShell *w, gpointer data)
2074 {
2075         GtkAspell *gtkaspell = (GtkAspell *) data;
2076
2077         gtkaspell->continue_check = NULL;
2078         set_point_continue(gtkaspell);
2079
2080         return FALSE;
2081         
2082 }
2083
2084 /* change_dict_cb() - Menu callback : change dict */
2085 static void change_dict_cb(GtkWidget *w, GtkAspell *gtkaspell)
2086 {
2087         Dictionary      *dict;       
2088         gchar           *fullname;
2089         GtkAspeller     *gtkaspeller;
2090         gint             sug_mode;
2091   
2092         fullname = (gchar *) gtk_object_get_data(GTK_OBJECT(w), "dict_name");
2093         
2094         if (!strcmp2(fullname, _("None")))
2095                 return;
2096
2097         sug_mode  = gtkaspell->default_sug_mode;
2098
2099         dict = g_new0(Dictionary, 1);
2100         dict->fullname = g_strdup(fullname);
2101         dict->encoding = g_strdup(gtkaspell->gtkaspeller->dictionary->encoding);
2102
2103         if (gtkaspell->use_alternate && gtkaspell->alternate_speller &&
2104             dict == gtkaspell->alternate_speller->dictionary) {
2105                 use_alternate_dict(gtkaspell);
2106                 dictionary_delete(dict);
2107                 return;
2108         }
2109         
2110         gtkaspeller = gtkaspeller_new(dict);
2111
2112         if (!gtkaspeller) {
2113                 gchar *message;
2114                 message = g_strdup_printf(_("The spell checker could not change dictionary.\n%s"), 
2115                                           gtkaspellcheckers->error_message);
2116
2117                 gtkaspell_alert_dialog(message); 
2118                 g_free(message);
2119         } else {
2120                 if (gtkaspell->use_alternate) {
2121                         if (gtkaspell->alternate_speller)
2122                                 gtkaspeller_delete(gtkaspell->alternate_speller);
2123                         gtkaspell->alternate_speller = gtkaspell->gtkaspeller;
2124                 } else
2125                         gtkaspeller_delete(gtkaspell->gtkaspeller);
2126
2127                 gtkaspell->gtkaspeller = gtkaspeller;
2128                 gtkaspell_set_sug_mode(gtkaspell, sug_mode);
2129         }
2130         
2131         dictionary_delete(dict);
2132
2133         if (gtkaspell->config_menu)
2134                 populate_submenu(gtkaspell, gtkaspell->config_menu);
2135 }
2136
2137 static void switch_to_alternate_cb(GtkWidget *w,
2138                                    gpointer data)
2139 {
2140         GtkAspell *gtkaspell = (GtkAspell *) data;
2141         use_alternate_dict(gtkaspell);
2142 }
2143
2144 /* Misc. helper functions */
2145
2146 static void set_point_continue(GtkAspell *gtkaspell)
2147 {
2148         GtkSText  *gtktext;
2149
2150         gtktext = gtkaspell->gtktext;
2151
2152         gtk_stext_freeze(gtktext);
2153         gtk_editable_set_position(GTK_EDITABLE(gtktext),gtkaspell->orig_pos);
2154         gtk_stext_set_point(gtktext, gtkaspell->orig_pos);
2155         gtk_stext_thaw(gtktext);
2156
2157         if (gtkaspell->continue_check)
2158                 gtkaspell->continue_check((gpointer *) gtkaspell);
2159 }
2160
2161 static void allocate_color(GtkAspell *gtkaspell, gint rgbvalue)
2162 {
2163         GdkColormap *gc;
2164         GdkColor *color = &(gtkaspell->highlight);
2165
2166         gc = gtk_widget_get_colormap(GTK_WIDGET(gtkaspell->gtktext));
2167
2168         if (gtkaspell->highlight.pixel)
2169                 gdk_colormap_free_colors(gc, &(gtkaspell->highlight), 1);
2170
2171         /* Shameless copy from Sylpheed's gtkutils.c */
2172         color->pixel = 0L;
2173         color->red   = (int) (((gdouble)((rgbvalue & 0xff0000) >> 16) / 255.0)
2174                         * 65535.0);
2175         color->green = (int) (((gdouble)((rgbvalue & 0x00ff00) >>  8) / 255.0)
2176                         * 65535.0);
2177         color->blue  = (int) (((gdouble) (rgbvalue & 0x0000ff)        / 255.0)
2178                         * 65535.0);
2179
2180         gdk_colormap_alloc_color(gc, &(gtkaspell->highlight), FALSE, TRUE);
2181 }
2182
2183 static void change_color(GtkAspell * gtkaspell, 
2184                          gint start, gint end,
2185                          gchar *newtext,
2186                          GdkColor *color) 
2187 {
2188         GtkSText *gtktext;
2189
2190         g_return_if_fail(start < end);
2191     
2192         gtktext = gtkaspell->gtktext;
2193     
2194         gtk_stext_freeze(gtktext);
2195         if (newtext) {
2196                 gtk_stext_set_point(gtktext, start);
2197                 gtk_stext_forward_delete(gtktext, end - start);
2198                 gtk_stext_insert(gtktext, NULL, color, NULL, newtext,
2199                                  end - start);
2200         }
2201         gtk_stext_thaw(gtktext);
2202 }
2203
2204 /* convert_to_aspell_encoding () - converts ISO-8859-* strings to iso8859-* 
2205  * as needed by aspell. Returns an allocated string.
2206  */
2207
2208 static guchar *convert_to_aspell_encoding (const guchar *encoding)
2209 {
2210         guchar * aspell_encoding;
2211
2212         if (strstr2(encoding, "ISO-8859-")) {
2213                 aspell_encoding = g_strdup_printf("iso8859%s", encoding+8);
2214         }
2215         else {
2216                 if (!strcmp2(encoding, "US-ASCII"))
2217                         aspell_encoding = g_strdup("iso8859-1");
2218                 else
2219                         aspell_encoding = g_strdup(encoding);
2220         }
2221
2222         return aspell_encoding;
2223 }
2224
2225 /* compare_dict () - compare 2 dict names */
2226 static gint compare_dict(Dictionary *a, Dictionary *b)
2227 {
2228         guint   aparts = 0,  bparts = 0;
2229         guint   i;
2230
2231         for (i=0; i < strlen(a->dictname); i++)
2232                 if (a->dictname[i] == '-')
2233                         aparts++;
2234         for (i=0; i < strlen(b->dictname); i++)
2235                 if (b->dictname[i] == '-')
2236                         bparts++;
2237
2238         if (aparts != bparts) 
2239                 return (aparts < bparts) ? -1 : +1;
2240         else {
2241                 gint compare;
2242                 compare = strcmp2(a->dictname, b->dictname);
2243                 if (!compare)
2244                         compare = strcmp2(a->fullname, b->fullname);
2245                 return compare;
2246         }
2247 }
2248
2249 static void dictionary_delete(Dictionary *dict)
2250 {
2251         g_free(dict->fullname);
2252         g_free(dict->encoding);
2253         g_free(dict);
2254 }
2255
2256 static Dictionary *dictionary_dup(const Dictionary *dict)
2257 {
2258         Dictionary *dict2;
2259
2260         dict2 = g_new(Dictionary, 1); 
2261
2262         dict2->fullname = g_strdup(dict->fullname);
2263         dict2->dictname = dict->dictname - dict->fullname + dict2->fullname;
2264         dict2->encoding = g_strdup(dict->encoding);
2265
2266         return dict2;
2267 }
2268
2269 static void free_suggestions_list(GtkAspell *gtkaspell)
2270 {
2271         GList *list;
2272
2273         for (list = gtkaspell->suggestions_list; list != NULL;
2274              list = list->next)
2275                 g_free(list->data);
2276
2277         g_list_free(list);
2278         
2279         gtkaspell->max_sug          = -1;
2280         gtkaspell->suggestions_list = NULL;
2281 }
2282
2283 static void reset_theword_data(GtkAspell *gtkaspell)
2284 {
2285         gtkaspell->start_pos     =  0;
2286         gtkaspell->end_pos       =  0;
2287         gtkaspell->theword[0]    =  0;
2288         gtkaspell->max_sug       = -1;
2289
2290         free_suggestions_list(gtkaspell);
2291 }
2292
2293 static void free_checkers(gpointer elt, gpointer data)
2294 {
2295         GtkAspeller *gtkaspeller = elt;
2296
2297         g_return_if_fail(gtkaspeller);
2298
2299         gtkaspeller_real_delete(gtkaspeller);
2300 }
2301
2302 static gint find_gtkaspeller(gconstpointer aa, gconstpointer bb)
2303 {
2304         Dictionary *a = ((GtkAspeller *) aa)->dictionary;
2305         Dictionary *b = ((GtkAspeller *) bb)->dictionary;
2306
2307         if (a && b && a->fullname && b->fullname  &&
2308             strcmp(a->fullname, b->fullname) == 0 &&
2309             a->encoding && b->encoding)
2310                 return strcmp(a->encoding, b->encoding);
2311
2312         return 1;
2313 }
2314
2315 static void gtkaspell_alert_dialog(gchar *message)
2316 {
2317         GtkWidget *dialog;
2318         GtkWidget *hbox;
2319         GtkWidget *label;
2320         GtkWidget *ok_button;
2321
2322         dialog = gtk_dialog_new();
2323         gtk_window_set_policy(GTK_WINDOW(dialog), FALSE, FALSE, FALSE);
2324         gtk_window_set_position(GTK_WINDOW(dialog),GTK_WIN_POS_MOUSE);
2325         gtk_signal_connect_object(GTK_OBJECT(dialog), "destroy",
2326                                    GTK_SIGNAL_FUNC(gtk_widget_destroy), 
2327                                    GTK_OBJECT(dialog));
2328
2329         label  = gtk_label_new(message);
2330         gtk_misc_set_padding(GTK_MISC(label), 8, 8);
2331
2332         gtk_container_add(GTK_CONTAINER(GTK_DIALOG(dialog)->vbox), label);
2333         
2334         hbox = gtk_hbox_new(FALSE, 0);
2335
2336         ok_button = gtk_button_new_with_label(_("OK"));
2337         GTK_WIDGET_SET_FLAGS(ok_button, GTK_CAN_DEFAULT);
2338         gtk_box_pack_start(GTK_BOX(hbox), ok_button, TRUE, TRUE, 8);    
2339
2340         gtk_signal_connect_object(GTK_OBJECT(ok_button), "clicked",
2341                                    GTK_SIGNAL_FUNC(gtk_widget_destroy), 
2342                                    GTK_OBJECT(dialog));
2343         gtk_container_add(GTK_CONTAINER(GTK_DIALOG(dialog)->action_area), hbox);
2344                         
2345         gtk_widget_grab_default(ok_button);
2346         gtk_widget_grab_focus(ok_button);
2347         gtk_window_set_modal(GTK_WINDOW(dialog), TRUE);
2348
2349         gtk_widget_show_all(dialog);
2350 }
2351 #endif