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