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