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