fix bugs 74, 'Crash opening editor window with pseudo-color display'
[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_new0(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  buf[BUFSIZE];
606         
607         g_return_val_if_fail(config, FALSE);
608         g_return_val_if_fail(dict,   FALSE);
609
610         strncpy(buf, dict->fullname, BUFSIZE-1);
611         buf[BUFSIZE-1] = 0x00;
612
613         buf[dict->dictname - dict->fullname] = 0x00;
614
615         CONFIG_REPLACE_RETURN_FALSE_IF_FAIL("dict-dir", buf);
616         debug_print("Aspell: looking for dictionaries in path %s.\n", buf);
617
618         strncpy(buf, dict->dictname, BUFSIZE-1);
619         language = buf;
620         
621         if ((size = strrchr(buf, '-')) && isdigit((int) size[1]))
622                 *size++ = 0x00;
623         else
624                 size = NULL;
625                                 
626         if ((jargon = strchr(language, '-')) != NULL) 
627                 *jargon++ = 0x00;
628         
629         if (size != NULL && jargon == size)
630                 jargon = NULL;
631
632         debug_print("Aspell: language: %s, jargon: %s, size: %s\n",
633                     language, jargon, size);
634         
635         if (language)
636                 CONFIG_REPLACE_RETURN_FALSE_IF_FAIL("lang", language);
637         if (jargon)
638                 CONFIG_REPLACE_RETURN_FALSE_IF_FAIL("jargon", jargon);
639         if (size)
640                 CONFIG_REPLACE_RETURN_FALSE_IF_FAIL("size", size);
641         if (dict->encoding) {
642                 gchar *aspell_enc;
643         
644                 aspell_enc = convert_to_aspell_encoding (dict->encoding);
645                 aspell_config_replace(config, "encoding", (const char *) aspell_enc);
646                 g_free(aspell_enc);
647
648                 RETURN_FALSE_IF_CONFIG_ERROR();
649         }
650         
651         return TRUE;
652 }
653
654 guchar *gtkaspell_get_dict(GtkAspell *gtkaspell)
655 {
656
657         g_return_val_if_fail(gtkaspell->gtkaspeller->config,     NULL);
658         g_return_val_if_fail(gtkaspell->gtkaspeller->dictionary, NULL);
659         
660         return g_strdup(gtkaspell->gtkaspeller->dictionary->dictname);
661 }
662   
663 guchar *gtkaspell_get_path(GtkAspell *gtkaspell)
664 {
665         guchar *path;
666         Dictionary *dict;
667
668         g_return_val_if_fail(gtkaspell->gtkaspeller->config, NULL);
669         g_return_val_if_fail(gtkaspell->gtkaspeller->dictionary, NULL);
670
671         dict = gtkaspell->gtkaspeller->dictionary;
672         path = g_strndup(dict->fullname, dict->dictname - dict->fullname);
673
674         return path;
675 }
676
677 /* set_sug_mode_cb() - Menu callback: Set the suggestion mode */
678 static void set_sug_mode_cb(GtkMenuItem *w, GtkAspell *gtkaspell)
679 {
680         char *themode;
681         
682         gtk_label_get(GTK_LABEL(GTK_BIN(w)->child), (gchar **) &themode);
683         
684         set_real_sug_mode(gtkaspell, themode);
685
686         if (gtkaspell->config_menu)
687                 populate_submenu(gtkaspell, gtkaspell->config_menu);
688 }
689
690 static void set_real_sug_mode(GtkAspell *gtkaspell, const char *themode)
691 {
692         gint result;
693         gint mode = ASPELL_FASTMODE;
694         g_return_if_fail(gtkaspell);
695         g_return_if_fail(gtkaspell->gtkaspeller);
696         g_return_if_fail(themode);
697
698         if (!strcmp(themode,_("Normal Mode")))
699                 mode = ASPELL_NORMALMODE;
700         else if (!strcmp( themode,_("Bad Spellers Mode")))
701                 mode = ASPELL_BADSPELLERMODE;
702
703         result = gtkaspell_set_sug_mode(gtkaspell, mode);
704
705         if(!result) {
706                 debug_print("Aspell: error while changing suggestion mode:%s\n",
707                             gtkaspellcheckers->error_message);
708                 gtkaspell_checkers_reset_error();
709         }
710 }
711   
712 /* gtkaspell_set_sug_mode() - Set the suggestion mode */
713 gboolean gtkaspell_set_sug_mode(GtkAspell *gtkaspell, gint themode)
714 {
715         AspellConfig *config;
716
717         g_return_val_if_fail(gtkaspell, FALSE);
718         g_return_val_if_fail(gtkaspell->gtkaspeller, FALSE);
719         g_return_val_if_fail(gtkaspell->gtkaspeller->config, FALSE);
720
721         debug_print("Aspell: setting sug mode of gtkaspeller %0x to %d\n",
722                         (guint) gtkaspell->gtkaspeller, themode);
723
724         config = gtkaspell->gtkaspeller->config;
725
726         switch (themode) {
727                 case ASPELL_FASTMODE: 
728                         CONFIG_REPLACE_RETURN_FALSE_IF_FAIL("sug-mode", "fast");
729                         break;
730                 case ASPELL_NORMALMODE: 
731                         CONFIG_REPLACE_RETURN_FALSE_IF_FAIL("sug-mode", "normal");
732                         break;
733                 case ASPELL_BADSPELLERMODE: 
734                         CONFIG_REPLACE_RETURN_FALSE_IF_FAIL("sug-mode", 
735                                                             "bad-spellers");
736                         break;
737                 default: 
738                         gtkaspellcheckers->error_message = 
739                                 g_strdup(_("Unknown suggestion mode."));
740                         return FALSE;
741                 }
742
743         gtkaspell->gtkaspeller->sug_mode = themode;
744         gtkaspell->default_sug_mode      = themode;
745
746         return TRUE;
747 }
748
749 /* misspelled_suggest() - Create a suggestion list for  word  */
750 static GList *misspelled_suggest(GtkAspell *gtkaspell, guchar *word) 
751 {
752         const guchar          *newword;
753         GList                 *list = NULL;
754         const AspellWordList  *suggestions;
755         AspellStringEnumeration *elements;
756
757         g_return_val_if_fail(word, NULL);
758
759         if (!aspell_speller_check(gtkaspell->gtkaspeller->checker, word, -1)) {
760                 free_suggestions_list(gtkaspell);
761
762                 suggestions = aspell_speller_suggest(gtkaspell->gtkaspeller->checker, 
763                                                      (const char *)word, -1);
764                 elements    = aspell_word_list_elements(suggestions);
765                 list        = g_list_append(list, g_strdup(word)); 
766                 
767                 while ((newword = aspell_string_enumeration_next(elements)) != NULL)
768                         list = g_list_append(list, g_strdup(newword));
769
770                 gtkaspell->max_sug          = g_list_length(list) - 1;
771                 gtkaspell->suggestions_list = list;
772
773                 return list;
774         }
775
776         free_suggestions_list(gtkaspell);
777
778         return NULL;
779 }
780
781 /* misspelled_test() - Just test if word is correctly spelled */  
782 static int misspelled_test(GtkAspell *gtkaspell, unsigned char *word) 
783 {
784         return aspell_speller_check(gtkaspell->gtkaspeller->checker, word, -1) ? 0 : 1; 
785 }
786
787
788 static gboolean iswordsep(unsigned char c) 
789 {
790         return !isalpha(c) && c != '\'';
791 }
792
793 static guchar get_text_index_whar(GtkAspell *gtkaspell, int pos) 
794 {
795         guchar a;
796         gchar *text;
797         
798         text = gtk_editable_get_chars(GTK_EDITABLE(gtkaspell->gtktext), pos, 
799                                       pos + 1);
800         if (text == NULL) 
801                 return 0;
802
803         a = (guchar) *text;
804
805         g_free(text);
806
807         return a;
808 }
809
810 /* get_word_from_pos () - return the word pointed to. */
811 /* Handles correctly the quotes. */
812 static gboolean get_word_from_pos(GtkAspell *gtkaspell, gint pos, 
813                                   unsigned char* buf, gint buflen,
814                                   gint *pstart, gint *pend) 
815 {
816
817         /* TODO : when correcting a word into quotes, change the color of */
818         /* the quotes too, as may be they were highlighted before. To do  */
819         /* this, we can use two others pointers that points to the whole    */
820         /* word including quotes. */
821
822         gint start;
823         gint end;
824                   
825         guchar c;
826         GtkSText *gtktext;
827         
828         gtktext = gtkaspell->gtktext;
829         if (iswordsep(get_text_index_whar(gtkaspell, pos))) 
830                 return FALSE;
831         
832         /* The apostrophe character is somtimes used for quotes 
833          * So include it in the word only if it is not surrounded 
834          * by other characters. 
835          */
836          
837         for (start = pos; start >= 0; --start) {
838                 c = get_text_index_whar(gtkaspell, start);
839                 if (c == '\'') {
840                         if (start > 0) {
841                                 if (!isalpha(get_text_index_whar(gtkaspell,
842                                                                  start - 1))) {
843                                         /* start_quote = TRUE; */
844                                         break;
845                                 }
846                         }
847                         else {
848                                 /* start_quote = TRUE; */
849                                 break;
850                         }
851                 }
852                 else if (!isalpha(c))
853                                 break;
854         }
855
856         start++;
857
858         for (end = pos; end < gtk_stext_get_length(gtktext); end++) {
859                 c = get_text_index_whar(gtkaspell, end); 
860                 if (c == '\'') {
861                         if (end < gtk_stext_get_length(gtktext)) {
862                                 if (!isalpha(get_text_index_whar(gtkaspell,
863                                                                  end + 1))) {
864                                         /* end_quote = TRUE; */
865                                         break;
866                                 }
867                         }
868                         else {
869                                 /* end_quote = TRUE; */
870                                 break;
871                         }
872                 }
873                 else if(!isalpha(c))
874                                 break;
875         }
876                                                 
877         if (pstart) 
878                 *pstart = start;
879         if (pend) 
880                 *pend = end;
881
882         if (buf) {
883                 if (end - start < buflen) {
884                         for (pos = start; pos < end; pos++) 
885                                 buf[pos - start] =
886                                         get_text_index_whar(gtkaspell, pos);
887                         buf[pos - start] = 0;
888                 } else
889                         return FALSE;
890         }
891
892         return TRUE;
893 }
894
895 static gboolean check_at(GtkAspell *gtkaspell, gint from_pos) 
896 {
897         gint          start, end;
898         unsigned char buf[GTKASPELLWORDSIZE];
899         GtkSText     *gtktext;
900
901         g_return_val_if_fail(from_pos >= 0, FALSE);
902     
903         gtktext = gtkaspell->gtktext;
904
905         if (!get_word_from_pos(gtkaspell, from_pos, buf, sizeof(buf), 
906                                &start, &end))
907                 return FALSE;
908
909         if (misspelled_test(gtkaspell, buf)) {
910                 strncpy(gtkaspell->theword, buf, GTKASPELLWORDSIZE - 1);
911                 gtkaspell->theword[GTKASPELLWORDSIZE - 1] = 0;
912                 gtkaspell->start_pos  = start;
913                 gtkaspell->end_pos    = end;
914                 free_suggestions_list(gtkaspell);
915
916                 change_color(gtkaspell, start, end, buf, &(gtkaspell->highlight));
917                 return TRUE;
918         } else {
919                 change_color(gtkaspell, start, end, buf, NULL);
920                 return FALSE;
921         }
922 }
923
924 static gboolean check_next_prev(GtkAspell *gtkaspell, gboolean forward)
925 {
926         gint pos;
927         gint minpos;
928         gint maxpos;
929         gint direc = -1;
930         gboolean misspelled;
931         
932         minpos = 0;
933         maxpos = gtkaspell->end_check_pos;
934
935         if (forward) {
936                 minpos = -1;
937                 direc = 1;
938                 maxpos--;
939         } 
940
941         pos = gtk_editable_get_position(GTK_EDITABLE(gtkaspell->gtktext));
942         gtkaspell->orig_pos = pos;
943         while (iswordsep(get_text_index_whar(gtkaspell, pos)) &&
944                pos > minpos && pos <= maxpos) 
945                 pos += direc;
946         while (!(misspelled = check_at(gtkaspell, pos)) &&
947                pos > minpos && pos <= maxpos) {
948
949                 while (!iswordsep(get_text_index_whar(gtkaspell, pos)) &&
950                        pos > minpos && pos <= maxpos)
951                         pos += direc;
952
953                 while (iswordsep(get_text_index_whar(gtkaspell, pos)) && 
954                        pos > minpos && pos <= maxpos) 
955                         pos += direc;
956         }
957         if (misspelled) {
958                 misspelled_suggest(gtkaspell, gtkaspell->theword);
959
960                 if (forward)
961                         gtkaspell->orig_pos = gtkaspell->end_pos;
962
963                 gtk_stext_set_point(GTK_STEXT(gtkaspell->gtktext),
964                                 gtkaspell->end_pos);
965                 gtk_editable_set_position(GTK_EDITABLE(gtkaspell->gtktext),
966                                 gtkaspell->end_pos);
967                 gtk_menu_popup(make_sug_menu(gtkaspell), NULL, NULL, 
968                                 set_menu_pos, gtkaspell, 0, GDK_CURRENT_TIME);
969         } else {
970                 reset_theword_data(gtkaspell);
971
972                 gtkaspell_alert_dialog(_("No misspelled word found."));
973                 gtk_stext_set_point(GTK_STEXT(gtkaspell->gtktext),
974                                     gtkaspell->orig_pos);
975                 gtk_editable_set_position(GTK_EDITABLE(gtkaspell->gtktext),
976                                           gtkaspell->orig_pos);
977
978                 
979         }
980         return misspelled;
981 }
982
983 void gtkaspell_check_backwards(GtkAspell *gtkaspell)
984 {
985         gtkaspell->continue_check = NULL;
986         gtkaspell->end_check_pos =
987                 gtk_stext_get_length(GTK_STEXT(gtkaspell->gtktext));
988         check_next_prev(gtkaspell, FALSE);
989 }
990
991 void gtkaspell_check_forwards_go(GtkAspell *gtkaspell)
992 {
993
994         gtkaspell->continue_check = NULL;
995         gtkaspell->end_check_pos
996                 = gtk_stext_get_length(GTK_STEXT(gtkaspell->gtktext));
997         check_next_prev(gtkaspell, TRUE);
998 }
999
1000 void gtkaspell_check_all(GtkAspell *gtkaspell)
1001 {       
1002         GtkWidget *gtktext;
1003         gint start, end;
1004
1005         g_return_if_fail(gtkaspell);
1006         g_return_if_fail(gtkaspell->gtktext);
1007
1008         gtktext = (GtkWidget *) gtkaspell->gtktext;
1009
1010         start = 0;      
1011         end   = gtk_stext_get_length(GTK_STEXT(gtktext));
1012
1013         if (GTK_EDITABLE(gtktext)->has_selection) {
1014                 start = GTK_EDITABLE(gtktext)->selection_start_pos;
1015                 end   = GTK_EDITABLE(gtktext)->selection_end_pos;
1016         }
1017
1018         if (start > end) {
1019                 gint tmp;
1020
1021                 tmp   = start;
1022                 start = end;
1023                 end   = tmp;
1024         }
1025                 
1026         
1027         gtk_editable_set_position(GTK_EDITABLE(gtktext), start);
1028         gtk_stext_set_point(GTK_STEXT(gtktext), start);
1029
1030         gtkaspell->continue_check = continue_check;
1031         gtkaspell->end_check_pos  = end;
1032
1033         gtkaspell->misspelled = check_next_prev(gtkaspell, TRUE);
1034
1035 }       
1036
1037 static void continue_check(gpointer *data)
1038 {
1039         GtkAspell *gtkaspell = (GtkAspell *) data;
1040         gint pos = gtk_editable_get_position(GTK_EDITABLE(gtkaspell->gtktext));
1041         if (pos < gtkaspell->end_check_pos && gtkaspell->misspelled)
1042                 gtkaspell->misspelled = check_next_prev(gtkaspell, TRUE);
1043         else
1044                 gtkaspell->continue_check = NULL;
1045                 
1046 }
1047
1048 void gtkaspell_highlight_all(GtkAspell *gtkaspell) 
1049 {
1050         guint     origpos;
1051         guint     pos = 0;
1052         guint     len;
1053         GtkSText *gtktext;
1054         gfloat    adj_value;
1055
1056         g_return_if_fail(gtkaspell->gtkaspeller->checker);      
1057
1058         gtktext = gtkaspell->gtktext;
1059
1060         adj_value = gtktext->vadj->value;
1061
1062         len = gtk_stext_get_length(gtktext);
1063
1064         origpos = gtk_editable_get_position(GTK_EDITABLE(gtktext));
1065
1066         while (pos < len) {
1067                 while (pos < len && 
1068                        iswordsep(get_text_index_whar(gtkaspell, pos)))
1069                         pos++;
1070                 while (pos < len &&
1071                        !iswordsep(get_text_index_whar(gtkaspell, pos)))
1072                         pos++;
1073                 if (pos > 0)
1074                         check_at(gtkaspell, pos - 1);
1075         }
1076         gtk_editable_set_position(GTK_EDITABLE(gtktext), origpos);
1077         gtk_stext_set_point(GTK_STEXT(gtktext), origpos);
1078         gtk_adjustment_set_value(gtktext->vadj, adj_value);
1079 }
1080
1081 static void replace_with_supplied_word_cb(GtkWidget *w, GtkAspell *gtkaspell) 
1082 {
1083         unsigned char *newword;
1084         GdkEvent *e= (GdkEvent *) gtk_get_current_event();
1085         
1086         newword = gtk_editable_get_chars(GTK_EDITABLE(gtkaspell->replace_entry),
1087                                          0, -1);
1088         
1089         if (strcmp(newword, gtkaspell->theword)) {
1090                 replace_real_word(gtkaspell, newword);
1091
1092                 if ((e->type == GDK_KEY_PRESS && 
1093                     ((GdkEventKey *) e)->state & GDK_MOD1_MASK)) {
1094                         aspell_speller_store_replacement(gtkaspell->gtkaspeller->checker, 
1095                                                          gtkaspell->theword, -1, 
1096                                                          newword, -1);
1097                 }
1098                 gtkaspell->replace_entry = NULL;
1099         }
1100
1101         g_free(newword);
1102
1103         set_point_continue(gtkaspell);
1104 }
1105
1106
1107 static void replace_word_cb(GtkWidget *w, gpointer data)
1108 {
1109         unsigned char *newword;
1110         GtkAspell *gtkaspell = (GtkAspell *) data;
1111         GdkEvent *e= (GdkEvent *) gtk_get_current_event();
1112
1113         gtk_label_get(GTK_LABEL(GTK_BIN(w)->child), (gchar**) &newword);
1114
1115         replace_real_word(gtkaspell, newword);
1116
1117         if ((e->type == GDK_KEY_PRESS && 
1118             ((GdkEventKey *) e)->state & GDK_MOD1_MASK) ||
1119             (e->type == GDK_BUTTON_RELEASE && 
1120              ((GdkEventButton *) e)->state & GDK_MOD1_MASK)) {
1121                 aspell_speller_store_replacement(gtkaspell->gtkaspeller->checker, 
1122                                                  gtkaspell->theword, -1, 
1123                                                  newword, -1);
1124         }
1125
1126         gtk_menu_shell_deactivate(GTK_MENU_SHELL(w->parent));
1127
1128         set_point_continue(gtkaspell);
1129 }
1130
1131 static void replace_real_word(GtkAspell *gtkaspell, gchar *newword)
1132 {
1133         int             oldlen, newlen, wordlen;
1134         gint            origpos;
1135         gint            pos;
1136         gint            start = gtkaspell->start_pos;
1137         GtkSText       *gtktext;
1138     
1139         if (!newword) return;
1140
1141         gtktext = gtkaspell->gtktext;
1142
1143         gtk_stext_freeze(GTK_STEXT(gtktext));
1144         origpos = gtkaspell->orig_pos;
1145         pos     = origpos;
1146         oldlen  = gtkaspell->end_pos - gtkaspell->start_pos;
1147         wordlen = strlen(gtkaspell->theword);
1148
1149         newlen = strlen(newword); /* FIXME: multybyte characters? */
1150
1151         gtk_signal_handler_block_by_func(GTK_OBJECT(gtktext),
1152                                          GTK_SIGNAL_FUNC(entry_insert_cb), 
1153                                          gtkaspell);
1154         gtk_signal_handler_block_by_func(GTK_OBJECT(gtktext),
1155                                          GTK_SIGNAL_FUNC(entry_delete_cb), 
1156                                          gtkaspell);
1157
1158         gtk_signal_emit_by_name(GTK_OBJECT(gtktext), "delete-text", 
1159                                 gtkaspell->start_pos, gtkaspell->end_pos);
1160         gtk_signal_emit_by_name(GTK_OBJECT(gtktext), "insert-text", 
1161                                 newword, newlen, &start);
1162         
1163         gtk_signal_handler_unblock_by_func(GTK_OBJECT(gtktext),
1164                                            GTK_SIGNAL_FUNC(entry_insert_cb), 
1165                                            gtkaspell);
1166         gtk_signal_handler_unblock_by_func(GTK_OBJECT(gtktext),
1167                                            GTK_SIGNAL_FUNC(entry_delete_cb), 
1168                                            gtkaspell);
1169         
1170         /* Put the point and the position where we clicked with the mouse
1171          * It seems to be a hack, as I must thaw,freeze,thaw the widget
1172          * to let it update correctly the word insertion and then the
1173          * point & position position. If not, SEGV after the first replacement
1174          * If the new word ends before point, put the point at its end.
1175          */
1176     
1177         if (origpos - gtkaspell->start_pos < oldlen && 
1178             origpos - gtkaspell->start_pos >= 0) {
1179                 /* Original point was in the word.
1180                  * Let it there unless point is going to be outside of the word
1181                  */
1182                 if (origpos - gtkaspell->start_pos >= newlen) {
1183                         pos = gtkaspell->start_pos + newlen;
1184                 }
1185         }
1186         else if (origpos >= gtkaspell->end_pos) {
1187                 /* move the position according to the change of length */
1188                 pos = origpos + newlen - oldlen;
1189         }
1190         
1191         gtkaspell->end_pos = gtkaspell->start_pos + strlen(newword); /* FIXME: multibyte characters? */
1192         
1193         gtk_stext_thaw(GTK_STEXT(gtktext));
1194         gtk_stext_freeze(GTK_STEXT(gtktext));
1195
1196         if (GTK_STEXT(gtktext)->text_len < pos)
1197                 pos = gtk_stext_get_length(GTK_STEXT(gtktext));
1198
1199         gtkaspell->orig_pos = pos;
1200
1201         gtk_editable_set_position(GTK_EDITABLE(gtktext), gtkaspell->orig_pos);
1202         gtk_stext_set_point(GTK_STEXT(gtktext), 
1203                             gtk_editable_get_position(GTK_EDITABLE(gtktext)));
1204
1205         gtk_stext_thaw(GTK_STEXT(gtktext));
1206 }
1207
1208 /* Accept this word for this session */
1209 static void add_word_to_session_cb(GtkWidget *w, gpointer data)
1210 {
1211         guint     pos;
1212         GtkSText *gtktext;
1213         GtkAspell *gtkaspell = (GtkAspell *) data; 
1214         gtktext = gtkaspell->gtktext;
1215
1216         gtk_stext_freeze(GTK_STEXT(gtktext));
1217
1218         pos = gtk_editable_get_position(GTK_EDITABLE(gtktext));
1219     
1220         aspell_speller_add_to_session(gtkaspell->gtkaspeller->checker,
1221                                       gtkaspell->theword, 
1222                                       strlen(gtkaspell->theword));
1223
1224         check_at(gtkaspell, gtkaspell->start_pos);
1225
1226         gtk_stext_thaw(gtkaspell->gtktext);
1227
1228         gtk_menu_shell_deactivate(GTK_MENU_SHELL(GTK_WIDGET(w)->parent));
1229
1230         set_point_continue(gtkaspell);
1231 }
1232
1233 /* add_word_to_personal_cb() - add word to personal dict. */
1234 static void add_word_to_personal_cb(GtkWidget *w, gpointer data)
1235 {
1236         GtkAspell *gtkaspell = (GtkAspell *) data; 
1237         GtkSText *gtktext    = gtkaspell->gtktext;
1238
1239         gtk_stext_freeze(GTK_STEXT(gtktext));
1240     
1241         aspell_speller_add_to_personal(gtkaspell->gtkaspeller->checker,
1242                                        gtkaspell->theword,
1243                                        strlen(gtkaspell->theword));
1244     
1245         check_at(gtkaspell, gtkaspell->start_pos);
1246
1247         gtk_stext_thaw(gtkaspell->gtktext);
1248
1249         gtk_menu_shell_deactivate(GTK_MENU_SHELL(GTK_WIDGET(w)->parent));
1250         set_point_continue(gtkaspell);
1251 }
1252
1253 static void check_with_alternate_cb(GtkWidget *w, gpointer data)
1254 {
1255         GtkAspell *gtkaspell = (GtkAspell *) data;
1256         gint misspelled;
1257
1258         gtk_menu_shell_deactivate(GTK_MENU_SHELL(GTK_WIDGET(w)->parent));
1259
1260         use_alternate_dict(gtkaspell);
1261         misspelled = check_at(gtkaspell, gtkaspell->start_pos);
1262
1263         if (!gtkaspell->continue_check) {
1264
1265                 gtkaspell->misspelled = misspelled;
1266
1267                 if (gtkaspell->misspelled) {
1268
1269                         misspelled_suggest(gtkaspell, gtkaspell->theword);
1270
1271                         gtk_stext_set_point(GTK_STEXT(gtkaspell->gtktext),
1272                                             gtkaspell->end_pos);
1273                         gtk_editable_set_position(GTK_EDITABLE(gtkaspell->gtktext),
1274                                                   gtkaspell->end_pos);
1275
1276                         gtk_menu_popup(make_sug_menu(gtkaspell), NULL, NULL, 
1277                                        set_menu_pos, gtkaspell, 0, 
1278                                        GDK_CURRENT_TIME);
1279                         return;
1280                 }
1281         } else
1282                 gtkaspell->orig_pos = gtkaspell->start_pos;
1283
1284         set_point_continue(gtkaspell);
1285 }
1286         
1287 static void replace_with_create_dialog_cb(GtkWidget *w, gpointer data)
1288 {
1289         GtkWidget *dialog;
1290         GtkWidget *label;
1291         GtkWidget *hbox;
1292         GtkWidget *entry;
1293         GtkWidget *ok_button;
1294         GtkWidget *cancel_button;
1295         gchar *thelabel;
1296         gint xx, yy;
1297         GtkAspell *gtkaspell = (GtkAspell *) data;
1298
1299         gdk_window_get_origin((GTK_WIDGET(w)->parent)->window, &xx, &yy);
1300
1301         gtk_menu_shell_deactivate(GTK_MENU_SHELL(GTK_WIDGET(w)->parent));
1302
1303         dialog = gtk_dialog_new();
1304
1305         gtk_window_set_policy(GTK_WINDOW(dialog), FALSE, FALSE, FALSE);
1306         gtk_window_set_title(GTK_WINDOW(dialog),_("Replace unknown word"));
1307         gtk_widget_set_uposition(dialog, xx, yy);
1308
1309         gtk_signal_connect_object(GTK_OBJECT(dialog), "destroy",
1310                                   GTK_SIGNAL_FUNC(gtk_widget_destroy), 
1311                                   GTK_OBJECT(dialog));
1312
1313         hbox = gtk_hbox_new(FALSE, 0);
1314         gtk_container_set_border_width(GTK_CONTAINER(hbox), 8);
1315
1316         thelabel = g_strdup_printf(_("Replace \"%s\" with: "), 
1317                                    gtkaspell->theword);
1318         label = gtk_label_new(thelabel);
1319         g_free(thelabel);
1320         gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 0);
1321
1322         entry = gtk_entry_new();
1323         gtkaspell->replace_entry = entry;
1324         gtk_entry_set_text(GTK_ENTRY(entry), gtkaspell->theword);
1325         gtk_editable_select_region(GTK_EDITABLE(entry), 0, -1);
1326         gtk_signal_connect(GTK_OBJECT(entry), "activate",
1327                            GTK_SIGNAL_FUNC(replace_with_supplied_word_cb), 
1328                            gtkaspell);
1329         gtk_signal_connect_object(GTK_OBJECT(entry), "activate",
1330                            GTK_SIGNAL_FUNC(gtk_widget_destroy), 
1331                            GTK_OBJECT(dialog));
1332         gtk_box_pack_start(GTK_BOX(hbox), entry, TRUE, TRUE, 0);
1333
1334         gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->vbox), hbox, TRUE, 
1335                            TRUE, 0);
1336         label = gtk_label_new(_("Holding down MOD1 key while pressing "
1337                                 "Enter\nwill learn from mistake.\n"));
1338         gtk_label_set_justify(GTK_LABEL(label), GTK_JUSTIFY_LEFT);
1339         gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.0);
1340         gtk_misc_set_padding(GTK_MISC(label), 8, 0);
1341         gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->vbox), label, 
1342                         TRUE, TRUE, 0);
1343
1344         hbox = gtk_hbox_new(TRUE, 0);
1345
1346         ok_button = gtk_button_new_with_label(_("OK"));
1347         gtk_box_pack_start(GTK_BOX(hbox), ok_button, TRUE, TRUE, 8);
1348         gtk_signal_connect(GTK_OBJECT(ok_button), "clicked",
1349                         GTK_SIGNAL_FUNC(replace_with_supplied_word_cb), 
1350                         gtkaspell);
1351         gtk_signal_connect_object(GTK_OBJECT(ok_button), "clicked",
1352                         GTK_SIGNAL_FUNC(gtk_widget_destroy), 
1353                         GTK_OBJECT(dialog));
1354
1355         cancel_button = gtk_button_new_with_label(_("Cancel"));
1356         gtk_box_pack_start(GTK_BOX(hbox), cancel_button, TRUE, TRUE, 8);
1357         gtk_signal_connect_object(GTK_OBJECT(cancel_button), "clicked",
1358                         GTK_SIGNAL_FUNC(gtk_widget_destroy), 
1359                         GTK_OBJECT(dialog));
1360
1361         gtk_container_add(GTK_CONTAINER(GTK_DIALOG(dialog)->action_area), hbox);
1362
1363         gtk_widget_grab_focus(entry);
1364
1365         gtk_window_set_modal(GTK_WINDOW(dialog), TRUE);
1366
1367         gtk_widget_show_all(dialog);
1368 }
1369
1370 void gtkaspell_uncheck_all(GtkAspell * gtkaspell) 
1371 {
1372         gint      origpos;
1373         gchar    *text;
1374         gfloat    adj_value;
1375         GtkSText *gtktext;
1376         
1377         gtktext = gtkaspell->gtktext;
1378
1379         adj_value = gtktext->vadj->value;
1380
1381         gtk_stext_freeze(gtktext);
1382
1383         origpos = gtk_editable_get_position(GTK_EDITABLE(gtktext));
1384
1385         text = gtk_editable_get_chars(GTK_EDITABLE(gtktext), 0, -1);
1386
1387         gtk_stext_set_point(gtktext, 0);
1388         gtk_stext_forward_delete(gtktext, gtk_stext_get_length(gtktext));
1389         gtk_stext_insert(gtktext, NULL, NULL, NULL, text, strlen(text));
1390
1391         gtk_stext_thaw(gtktext);
1392
1393         gtk_editable_set_position(GTK_EDITABLE(gtktext), origpos);
1394         gtk_stext_set_point(gtktext, origpos);
1395         gtk_adjustment_set_value(gtktext->vadj, adj_value);
1396
1397         g_free(text);
1398
1399 }
1400
1401 static void toggle_check_while_typing_cb(GtkWidget *w, gpointer data)
1402 {
1403         GtkAspell *gtkaspell = (GtkAspell *) data;
1404
1405         gtkaspell->check_while_typing = gtkaspell->check_while_typing == FALSE;
1406
1407         if (!gtkaspell->check_while_typing)
1408                 gtkaspell_uncheck_all(gtkaspell);
1409
1410         if (gtkaspell->config_menu)
1411                 populate_submenu(gtkaspell, gtkaspell->config_menu);
1412 }
1413
1414 static GSList *create_empty_dictionary_list(void)
1415 {
1416         GSList *list = NULL;
1417         Dictionary *dict;
1418
1419         dict = g_new0(Dictionary, 1);
1420         dict->fullname = g_strdup(_("None"));
1421         dict->dictname = dict->fullname;
1422         dict->encoding = NULL;
1423
1424         return g_slist_append(list, dict);
1425 }
1426
1427 /* gtkaspell_get_dictionary_list() - returns list of dictionary names */
1428 GSList *gtkaspell_get_dictionary_list(const gchar *aspell_path, gint refresh)
1429 {
1430         GSList *list;
1431         Dictionary *dict;
1432         AspellConfig *config;
1433         AspellDictInfoList *dlist;
1434         AspellDictInfoEnumeration *dels;
1435         const AspellDictInfo *entry;
1436
1437         if (!gtkaspellcheckers)
1438                 gtkaspellcheckers = gtkaspell_checkers_new();
1439
1440         if (gtkaspellcheckers->dictionary_list && !refresh)
1441                 return gtkaspellcheckers->dictionary_list;
1442         else
1443                 gtkaspell_free_dictionary_list(gtkaspellcheckers->dictionary_list);
1444         list = NULL;
1445
1446         config = new_aspell_config();
1447 #if 0 
1448         aspell_config_replace(config, "rem-all-word-list-path", "");
1449         if (aspell_config_error_number(config) != 0) {
1450                 gtkaspellcheckers->error_message = g_strdup(
1451                                 aspell_config_error_message(config));
1452                 gtkaspellcheckers->dictionary_list =
1453                         create_empty_dictionary_list();
1454
1455                 return gtkaspellcheckers->dictionary_list; 
1456         }
1457 #endif
1458         aspell_config_replace(config, "dict-dir", aspell_path);
1459         if (aspell_config_error_number(config) != 0) {
1460                 gtkaspellcheckers->error_message = g_strdup(
1461                                 aspell_config_error_message(config));
1462                 gtkaspellcheckers->dictionary_list =
1463                         create_empty_dictionary_list();
1464
1465                 return gtkaspellcheckers->dictionary_list; 
1466         }
1467
1468         dlist = get_aspell_dict_info_list(config);
1469         delete_aspell_config(config);
1470
1471         debug_print("Aspell: checking for dictionaries in %s\n", aspell_path);
1472         dels = aspell_dict_info_list_elements(dlist);
1473         while ( (entry = aspell_dict_info_enumeration_next(dels)) != 0) 
1474         {
1475                 dict = g_new0(Dictionary, 1);
1476                 dict->fullname = g_strdup_printf("%s%s", aspell_path, 
1477                                 entry->name);
1478                 dict->dictname = dict->fullname + strlen(aspell_path);
1479                 dict->encoding = g_strdup(entry->code);
1480                 debug_print("Aspell: found dictionary %s %s\n", dict->fullname,
1481                                 dict->dictname);
1482                 list = g_slist_insert_sorted(list, dict,
1483                                 (GCompareFunc) compare_dict);
1484         }
1485
1486         delete_aspell_dict_info_enumeration(dels);
1487         
1488         if(list==NULL){
1489                 
1490                 debug_print("Aspell: error when searching for dictionaries: "
1491                               "No dictionary found.\n");
1492                 list = create_empty_dictionary_list();
1493         }
1494
1495         gtkaspellcheckers->dictionary_list = list;
1496
1497         return list;
1498 }
1499
1500 void gtkaspell_free_dictionary_list(GSList *list)
1501 {
1502         Dictionary *dict;
1503         GSList *walk;
1504         for (walk = list; walk != NULL; walk = g_slist_next(walk))
1505                 if (walk->data) {
1506                         dict = (Dictionary *) walk->data;
1507                         dictionary_delete(dict);
1508                 }                               
1509         g_slist_free(list);
1510 }
1511
1512 GtkWidget *gtkaspell_dictionary_option_menu_new(const gchar *aspell_path)
1513 {
1514         GSList *dict_list, *tmp;
1515         GtkWidget *item;
1516         GtkWidget *menu;
1517         Dictionary *dict;
1518
1519         dict_list = gtkaspell_get_dictionary_list(aspell_path, TRUE);
1520         g_return_val_if_fail(dict_list, NULL);
1521
1522         menu = gtk_menu_new();
1523         
1524         for (tmp = dict_list; tmp != NULL; tmp = g_slist_next(tmp)) {
1525                 dict = (Dictionary *) tmp->data;
1526                 item = gtk_menu_item_new_with_label(dict->dictname);
1527                 gtk_object_set_data(GTK_OBJECT(item), "dict_name",
1528                                     dict->fullname); 
1529                                          
1530                 gtk_menu_append(GTK_MENU(menu), item);                                   
1531                 gtk_widget_show(item);
1532         }
1533
1534         gtk_widget_show(menu);
1535
1536         return menu;
1537 }
1538
1539 gchar *gtkaspell_get_dictionary_menu_active_item(GtkWidget *menu)
1540 {
1541         GtkWidget *menuitem;
1542         gchar *dict_fullname;
1543         gchar *label;
1544
1545         g_return_val_if_fail(GTK_IS_MENU(menu), NULL);
1546
1547         menuitem = gtk_menu_get_active(GTK_MENU(menu));
1548         dict_fullname = (gchar *) gtk_object_get_data(GTK_OBJECT(menuitem), 
1549                                                       "dict_name");
1550         g_return_val_if_fail(dict_fullname, NULL);
1551
1552         label = g_strdup(dict_fullname);
1553
1554         return label;
1555   
1556 }
1557
1558 GtkWidget *gtkaspell_sugmode_option_menu_new(gint sugmode)
1559 {
1560         GtkWidget *menu;
1561         GtkWidget *item;
1562
1563         menu = gtk_menu_new();
1564         gtk_widget_show(menu);
1565
1566         item = gtk_menu_item_new_with_label(_("Fast Mode"));
1567         gtk_widget_show(item);
1568         gtk_menu_append(GTK_MENU(menu), item);
1569         gtk_object_set_data(GTK_OBJECT(item), "sugmode", GINT_TO_POINTER(ASPELL_FASTMODE));
1570
1571         item = gtk_menu_item_new_with_label(_("Normal Mode"));
1572         gtk_widget_show(item);
1573         gtk_menu_append(GTK_MENU(menu), item);
1574         gtk_object_set_data(GTK_OBJECT(item), "sugmode", GINT_TO_POINTER(ASPELL_NORMALMODE));
1575         
1576         item = gtk_menu_item_new_with_label(_("Bad Spellers Mode"));
1577         gtk_widget_show(item);
1578         gtk_menu_append(GTK_MENU(menu), item);
1579         gtk_object_set_data(GTK_OBJECT(item), "sugmode", GINT_TO_POINTER(ASPELL_BADSPELLERMODE));
1580
1581         return menu;
1582 }
1583         
1584 void gtkaspell_sugmode_option_menu_set(GtkOptionMenu *optmenu, gint sugmode)
1585 {
1586         g_return_if_fail(GTK_IS_OPTION_MENU(optmenu));
1587
1588         g_return_if_fail(sugmode == ASPELL_FASTMODE ||
1589                          sugmode == ASPELL_NORMALMODE ||
1590                          sugmode == ASPELL_BADSPELLERMODE);
1591
1592         gtk_option_menu_set_history(GTK_OPTION_MENU(optmenu), sugmode - 1);
1593 }
1594
1595 gint gtkaspell_get_sugmode_from_option_menu(GtkOptionMenu *optmenu)
1596 {
1597         gint sugmode;
1598         GtkWidget *item;
1599         
1600         g_return_val_if_fail(GTK_IS_OPTION_MENU(optmenu), -1);
1601
1602         item = gtk_menu_get_active(GTK_MENU(gtk_option_menu_get_menu(optmenu)));
1603         
1604         sugmode = GPOINTER_TO_INT(gtk_object_get_data(GTK_OBJECT(item),
1605                                                       "sugmode"));
1606
1607         return sugmode;
1608 }
1609
1610 static void use_alternate_dict(GtkAspell *gtkaspell)
1611 {
1612         GtkAspeller *tmp;
1613
1614         tmp = gtkaspell->gtkaspeller;
1615         gtkaspell->gtkaspeller = gtkaspell->alternate_speller;
1616         gtkaspell->alternate_speller = tmp;
1617
1618         if (gtkaspell->config_menu)
1619                 populate_submenu(gtkaspell, gtkaspell->config_menu);
1620 }
1621
1622 static void popup_menu(GtkAspell *gtkaspell, GdkEventButton *eb) 
1623 {
1624         GtkSText * gtktext;
1625         
1626         gtktext = gtkaspell->gtktext;
1627         gtkaspell->orig_pos = gtk_editable_get_position(GTK_EDITABLE(gtktext));
1628
1629         if (!(eb->state & GDK_SHIFT_MASK)) {
1630                 if (check_at(gtkaspell, gtkaspell->orig_pos)) {
1631
1632                         gtk_editable_set_position(GTK_EDITABLE(gtktext), 
1633                                                   gtkaspell->orig_pos);
1634                         gtk_stext_set_point(gtktext, gtkaspell->orig_pos);
1635
1636                         if (misspelled_suggest(gtkaspell, gtkaspell->theword)) {
1637                                 gtk_menu_popup(make_sug_menu(gtkaspell), 
1638                                                NULL, NULL, NULL, NULL,
1639                                                eb->button, GDK_CURRENT_TIME);
1640                                 
1641                                 return;
1642                         }
1643                 } else {
1644                         gtk_editable_set_position(GTK_EDITABLE(gtktext), 
1645                                                   gtkaspell->orig_pos);
1646                         gtk_stext_set_point(gtktext, gtkaspell->orig_pos);
1647                 }
1648         }
1649
1650         gtk_menu_popup(make_config_menu(gtkaspell), NULL, NULL, NULL, NULL,
1651                        eb->button, GDK_CURRENT_TIME);
1652 }
1653
1654 /* make_sug_menu() - Add menus to accept this word for this session 
1655  * and to add it to personal dictionary 
1656  */
1657 static GtkMenu *make_sug_menu(GtkAspell *gtkaspell) 
1658 {
1659         GtkWidget       *menu, *item;
1660         unsigned char   *caption;
1661         GtkSText        *gtktext;
1662         GtkAccelGroup   *accel;
1663         GList           *l = gtkaspell->suggestions_list;
1664
1665         gtktext = gtkaspell->gtktext;
1666
1667         accel = gtk_accel_group_new();
1668         menu = gtk_menu_new(); 
1669
1670         if (gtkaspell->sug_menu)
1671                 gtk_widget_destroy(gtkaspell->sug_menu);
1672
1673         gtkaspell->sug_menu = menu;     
1674
1675         gtk_signal_connect(GTK_OBJECT(menu), "cancel",
1676                 GTK_SIGNAL_FUNC(cancel_menu_cb), gtkaspell);
1677
1678         caption = g_strdup_printf(_("\"%s\" unknown in %s"), 
1679                                   (unsigned char*) l->data, 
1680                                   gtkaspell->gtkaspeller->dictionary->dictname);
1681         item = gtk_menu_item_new_with_label(caption);
1682         gtk_widget_show(item);
1683         gtk_menu_append(GTK_MENU(menu), item);
1684         gtk_misc_set_alignment(GTK_MISC(GTK_BIN(item)->child), 0.5, 0.5);
1685         g_free(caption);
1686
1687         item = gtk_menu_item_new();
1688         gtk_widget_show(item);
1689         gtk_menu_append(GTK_MENU(menu), item);
1690
1691         item = gtk_menu_item_new_with_label(_("Accept in this session"));
1692         gtk_widget_show(item);
1693         gtk_menu_append(GTK_MENU(menu), item);
1694         gtk_signal_connect(GTK_OBJECT(item), "activate",
1695                            GTK_SIGNAL_FUNC(add_word_to_session_cb), 
1696                            gtkaspell);
1697         gtk_widget_add_accelerator(item, "activate", accel, GDK_space,
1698                                    GDK_MOD1_MASK,
1699                                    GTK_ACCEL_LOCKED | GTK_ACCEL_VISIBLE);
1700
1701         item = gtk_menu_item_new_with_label(_("Add to personal dictionary"));
1702         gtk_widget_show(item);
1703         gtk_menu_append(GTK_MENU(menu), item);
1704         gtk_signal_connect(GTK_OBJECT(item), "activate",
1705                            GTK_SIGNAL_FUNC(add_word_to_personal_cb), 
1706                            gtkaspell);
1707         gtk_widget_add_accelerator(item, "activate", accel, GDK_Return,
1708                                    GDK_MOD1_MASK,
1709                                    GTK_ACCEL_LOCKED | GTK_ACCEL_VISIBLE);
1710
1711         item = gtk_menu_item_new_with_label(_("Replace with..."));
1712         gtk_widget_show(item);
1713         gtk_menu_append(GTK_MENU(menu), item);
1714         gtk_signal_connect(GTK_OBJECT(item), "activate",
1715                            GTK_SIGNAL_FUNC(replace_with_create_dialog_cb), 
1716                            gtkaspell);
1717         gtk_widget_add_accelerator(item, "activate", accel, GDK_R, 0,
1718                                    GTK_ACCEL_LOCKED | GTK_ACCEL_VISIBLE);
1719
1720         if (gtkaspell->use_alternate && gtkaspell->alternate_speller) {
1721                 caption = g_strdup_printf(_("Check with %s"), 
1722                         gtkaspell->alternate_speller->dictionary->dictname);
1723                 item = gtk_menu_item_new_with_label(caption);
1724                 g_free(caption);
1725                 gtk_widget_show(item);
1726                 gtk_menu_append(GTK_MENU(menu), item);
1727                 gtk_signal_connect(GTK_OBJECT(item), "activate",
1728                                 GTK_SIGNAL_FUNC(check_with_alternate_cb),
1729                                 gtkaspell);
1730                 gtk_widget_add_accelerator(item, "activate", accel, GDK_X, 0,
1731                                            GTK_ACCEL_LOCKED | GTK_ACCEL_VISIBLE);
1732         }
1733
1734         item = gtk_menu_item_new();
1735         gtk_widget_show(item);
1736         gtk_menu_append(GTK_MENU(menu), item);
1737
1738         l = l->next;
1739         if (l == NULL) {
1740                 item = gtk_menu_item_new_with_label(_("(no suggestions)"));
1741                 gtk_widget_show(item);
1742                 gtk_menu_append(GTK_MENU(menu), item);
1743         } else {
1744                 GtkWidget *curmenu = menu;
1745                 gint count = 0;
1746                 
1747                 do {
1748                         if (count == MENUCOUNT) {
1749                                 count -= MENUCOUNT;
1750
1751                                 item = gtk_menu_item_new_with_label(_("More..."));
1752                                 gtk_widget_show(item);
1753                                 gtk_menu_append(GTK_MENU(curmenu), item);
1754
1755                                 curmenu = gtk_menu_new();
1756                                 gtk_menu_item_set_submenu(GTK_MENU_ITEM(item),
1757                                                           curmenu);
1758                         }
1759
1760                         item = gtk_menu_item_new_with_label((unsigned char*)l->data);
1761                         gtk_widget_show(item);
1762                         gtk_menu_append(GTK_MENU(curmenu), item);
1763                         gtk_signal_connect(GTK_OBJECT(item), "activate",
1764                                            GTK_SIGNAL_FUNC(replace_word_cb),
1765                                            gtkaspell);
1766
1767                         if (curmenu == menu && count < MENUCOUNT) {
1768                                 gtk_widget_add_accelerator(item, "activate",
1769                                                            accel,
1770                                                            GDK_A + count, 0,
1771                                                            GTK_ACCEL_LOCKED | 
1772                                                            GTK_ACCEL_VISIBLE);
1773                                 gtk_widget_add_accelerator(item, "activate", 
1774                                                            accel,
1775                                                            GDK_A + count, 
1776                                                            GDK_MOD1_MASK,
1777                                                            GTK_ACCEL_LOCKED);
1778                                 }
1779
1780                         count++;
1781
1782                 } while ((l = l->next) != NULL);
1783         }
1784
1785         gtk_accel_group_attach(accel, GTK_OBJECT(menu));
1786         gtk_accel_group_unref(accel);
1787         
1788         return GTK_MENU(menu);
1789 }
1790
1791 static void populate_submenu(GtkAspell *gtkaspell, GtkWidget *menu)
1792 {
1793         GtkWidget *item, *submenu;
1794         gchar *dictname;
1795         GtkAspeller *gtkaspeller = gtkaspell->gtkaspeller;
1796
1797         if (GTK_MENU_SHELL(menu)->children) {
1798                 GList *amenu, *alist;
1799                 for (amenu = (GTK_MENU_SHELL(menu)->children); amenu; ) {
1800                         alist = amenu->next;
1801                         gtk_widget_destroy(GTK_WIDGET(amenu->data));
1802                         amenu = alist;
1803                 }
1804 }
1805         
1806         dictname = g_strdup_printf(_("Dictionary: %s"),
1807                                    gtkaspeller->dictionary->dictname);
1808         item = gtk_menu_item_new_with_label(dictname);
1809         gtk_misc_set_alignment(GTK_MISC(GTK_BIN(item)->child), 0.5, 0.5);
1810         g_free(dictname);
1811         gtk_widget_show(item);
1812         gtk_menu_append(GTK_MENU(menu), item);
1813
1814         item = gtk_menu_item_new();
1815         gtk_widget_show(item);
1816         gtk_menu_append(GTK_MENU(menu), item);
1817                 
1818         if (gtkaspell->use_alternate && gtkaspell->alternate_speller) {
1819                 dictname = g_strdup_printf(_("Use alternate (%s)"), 
1820                                 gtkaspell->alternate_speller->dictionary->dictname);
1821                 item = gtk_menu_item_new_with_label(dictname);
1822                 g_free(dictname);
1823                 gtk_signal_connect(GTK_OBJECT(item), "activate",
1824                                    GTK_SIGNAL_FUNC(switch_to_alternate_cb),
1825                                    gtkaspell);
1826                 gtk_widget_show(item);
1827                 gtk_menu_append(GTK_MENU(menu), item);
1828         }
1829
1830         item = gtk_check_menu_item_new_with_label(_("Fast Mode"));
1831         if (gtkaspell->gtkaspeller->sug_mode == ASPELL_FASTMODE) {
1832                 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(item),TRUE);
1833                 gtk_widget_set_sensitive(GTK_WIDGET(item),FALSE);
1834         } else
1835                 gtk_signal_connect(GTK_OBJECT(item), "activate",
1836                                    GTK_SIGNAL_FUNC(set_sug_mode_cb),
1837                                    gtkaspell);
1838         gtk_widget_show(item);
1839         gtk_menu_append(GTK_MENU(menu), item);
1840
1841         item = gtk_check_menu_item_new_with_label(_("Normal Mode"));
1842         if (gtkaspell->gtkaspeller->sug_mode == ASPELL_NORMALMODE) {
1843                 gtk_widget_set_sensitive(GTK_WIDGET(item), FALSE);
1844                 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(item), TRUE);
1845         } else
1846                 gtk_signal_connect(GTK_OBJECT(item), "activate",
1847                                    GTK_SIGNAL_FUNC(set_sug_mode_cb),
1848                                    gtkaspell);
1849         gtk_widget_show(item);
1850         gtk_menu_append(GTK_MENU(menu),item);
1851
1852         item = gtk_check_menu_item_new_with_label(_("Bad Spellers Mode"));
1853         if (gtkaspell->gtkaspeller->sug_mode == ASPELL_BADSPELLERMODE) {
1854                 gtk_widget_set_sensitive(GTK_WIDGET(item), FALSE);
1855                 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(item), TRUE);
1856         } else
1857                 gtk_signal_connect(GTK_OBJECT(item), "activate",
1858                                    GTK_SIGNAL_FUNC(set_sug_mode_cb),
1859                                    gtkaspell);
1860         gtk_widget_show(item);
1861         gtk_menu_append(GTK_MENU(menu), item);
1862         
1863         item = gtk_menu_item_new();
1864         gtk_widget_show(item);
1865         gtk_menu_append(GTK_MENU(menu), item);
1866         
1867         item = gtk_check_menu_item_new_with_label(_("Check while typing"));
1868         if (gtkaspell->check_while_typing)
1869                 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(item), TRUE);
1870         else    
1871                 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(item), FALSE);
1872         gtk_signal_connect(GTK_OBJECT(item), "activate",
1873                            GTK_SIGNAL_FUNC(toggle_check_while_typing_cb),
1874                            gtkaspell);
1875         gtk_widget_show(item);
1876         gtk_menu_append(GTK_MENU(menu), item);
1877
1878         item = gtk_menu_item_new();
1879         gtk_widget_show(item);
1880         gtk_menu_append(GTK_MENU(menu), item);
1881
1882         submenu = gtk_menu_new();
1883         item = gtk_menu_item_new_with_label(_("Change dictionary"));
1884         gtk_menu_item_set_submenu(GTK_MENU_ITEM(item),submenu);
1885         gtk_widget_show(item);
1886         gtk_menu_append(GTK_MENU(menu), item);
1887
1888         /* Dict list */
1889         if (gtkaspellcheckers->dictionary_list == NULL)
1890                 gtkaspell_get_dictionary_list(prefs_common.aspell_path, FALSE);
1891         {
1892                 GtkWidget * curmenu = submenu;
1893                 int count = 0;
1894                 Dictionary *dict;
1895                 GSList *tmp;
1896                 tmp = gtkaspellcheckers->dictionary_list;
1897                 
1898                 for (tmp = gtkaspellcheckers->dictionary_list; tmp != NULL; 
1899                                 tmp = g_slist_next(tmp)) {
1900                         if (count == MENUCOUNT) {
1901                                 GtkWidget *newmenu;
1902                                 
1903                                 newmenu = gtk_menu_new();
1904                                 item = gtk_menu_item_new_with_label(_("More..."));
1905                                 gtk_menu_item_set_submenu(GTK_MENU_ITEM(item), 
1906                                                           newmenu);
1907                                 
1908                                 gtk_menu_append(GTK_MENU(curmenu), item);
1909                                 gtk_widget_show(item);
1910                                 curmenu = newmenu;
1911                                 count = 0;
1912                         }
1913                         dict = (Dictionary *) tmp->data;
1914                         item = gtk_check_menu_item_new_with_label(dict->dictname);
1915                         gtk_object_set_data(GTK_OBJECT(item), "dict_name",
1916                                             dict->fullname); 
1917                         if (strcmp2(dict->fullname,
1918                             gtkaspell->gtkaspeller->dictionary->fullname))
1919                                 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(item), FALSE);
1920                         else {
1921                                 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(item), TRUE);
1922                                 gtk_widget_set_sensitive(GTK_WIDGET(item),
1923                                                          FALSE);
1924                         }
1925                         gtk_signal_connect(GTK_OBJECT(item), "activate",
1926                                            GTK_SIGNAL_FUNC(change_dict_cb),
1927                                            gtkaspell);
1928                         gtk_widget_show(item);
1929                         gtk_menu_append(GTK_MENU(curmenu), item);
1930                         
1931                         count++;
1932                 }
1933         }  
1934 }
1935
1936 static GtkMenu *make_config_menu(GtkAspell *gtkaspell)
1937 {
1938         if (!gtkaspell->popup_config_menu)
1939                 gtkaspell->popup_config_menu = gtk_menu_new();
1940
1941         debug_print("Aspell: creating/using popup_config_menu %0x\n", 
1942                         (guint) gtkaspell->popup_config_menu);
1943         populate_submenu(gtkaspell, gtkaspell->popup_config_menu);
1944
1945         return GTK_MENU(gtkaspell->popup_config_menu);
1946 }
1947
1948 void gtkaspell_populate_submenu(GtkAspell *gtkaspell, GtkWidget *menuitem)
1949 {
1950         GtkWidget *menu;
1951
1952         menu = GTK_WIDGET(GTK_MENU_ITEM(menuitem)->submenu);
1953         
1954         debug_print("Aspell: using config menu %0x\n", 
1955                         (guint) gtkaspell->popup_config_menu);
1956         populate_submenu(gtkaspell, menu);
1957         
1958         gtkaspell->config_menu = menu;
1959         
1960 }
1961
1962 static void set_menu_pos(GtkMenu *menu, gint *x, gint *y, gpointer data)
1963 {
1964         GtkAspell       *gtkaspell = (GtkAspell *) data;
1965         gint             xx = 0, yy = 0;
1966         gint             sx,     sy;
1967         gint             wx,     wy;
1968         GtkSText        *text = GTK_STEXT(gtkaspell->gtktext);
1969         GtkRequisition   r;
1970
1971         gdk_window_get_origin(GTK_WIDGET(gtkaspell->gtktext)->window, &xx, &yy);
1972         
1973         sx = gdk_screen_width();
1974         sy = gdk_screen_height();
1975         
1976         gtk_widget_get_child_requisition(GTK_WIDGET(menu), &r);
1977         
1978         wx =  r.width;
1979         wy =  r.height;
1980         
1981         *x = gtkaspell->gtktext->cursor_pos_x + xx +
1982              gdk_char_width(GTK_WIDGET(text)->style->font, ' ');
1983         *y = gtkaspell->gtktext->cursor_pos_y + yy;
1984
1985         if (*x + wx > sx)
1986                 *x = sx - wx;
1987         if (*y + wy > sy)
1988                 *y = *y - wy - 
1989                      gdk_string_height((GTK_WIDGET(gtkaspell->gtktext))->style->font, 
1990                                        gtkaspell->theword);
1991
1992 }
1993
1994 /* Menu call backs */
1995
1996 static gboolean cancel_menu_cb(GtkMenuShell *w, gpointer data)
1997 {
1998         GtkAspell *gtkaspell = (GtkAspell *) data;
1999
2000         gtkaspell->continue_check = NULL;
2001         set_point_continue(gtkaspell);
2002
2003         return FALSE;
2004         
2005 }
2006
2007 /* change_dict_cb() - Menu callback : change dict */
2008 static void change_dict_cb(GtkWidget *w, GtkAspell *gtkaspell)
2009 {
2010         Dictionary      *dict;       
2011         gchar           *fullname;
2012         GtkAspeller     *gtkaspeller;
2013         gint             sug_mode;
2014   
2015         fullname = (gchar *) gtk_object_get_data(GTK_OBJECT(w), "dict_name");
2016         
2017         if (!strcmp2(fullname, _("None")))
2018                 return;
2019
2020         sug_mode  = gtkaspell->default_sug_mode;
2021
2022         dict = g_new0(Dictionary, 1);
2023         dict->fullname = g_strdup(fullname);
2024         dict->encoding = g_strdup(gtkaspell->gtkaspeller->dictionary->encoding);
2025
2026         if (gtkaspell->use_alternate && gtkaspell->alternate_speller &&
2027             dict == gtkaspell->alternate_speller->dictionary) {
2028                 use_alternate_dict(gtkaspell);
2029                 dictionary_delete(dict);
2030                 return;
2031         }
2032         
2033         gtkaspeller = gtkaspeller_new(dict);
2034
2035         if (!gtkaspeller) {
2036                 gchar *message;
2037                 message = g_strdup_printf(_("The spell checker could not change dictionary.\n%s"), 
2038                                           gtkaspellcheckers->error_message);
2039
2040                 gtkaspell_alert_dialog(message); 
2041                 g_free(message);
2042         } else {
2043                 if (gtkaspell->use_alternate) {
2044                         if (gtkaspell->alternate_speller)
2045                                 gtkaspeller_delete(gtkaspell->alternate_speller);
2046                         gtkaspell->alternate_speller = gtkaspell->gtkaspeller;
2047                 } else
2048                         gtkaspeller_delete(gtkaspell->gtkaspeller);
2049
2050                 gtkaspell->gtkaspeller = gtkaspeller;
2051                 gtkaspell_set_sug_mode(gtkaspell, sug_mode);
2052         }
2053         
2054         dictionary_delete(dict);
2055
2056         if (gtkaspell->config_menu)
2057                 populate_submenu(gtkaspell, gtkaspell->config_menu);
2058 }
2059
2060 static void switch_to_alternate_cb(GtkWidget *w,
2061                                    gpointer data)
2062 {
2063         GtkAspell *gtkaspell = (GtkAspell *) data;
2064         use_alternate_dict(gtkaspell);
2065 }
2066
2067 /* Misc. helper functions */
2068
2069 static void set_point_continue(GtkAspell *gtkaspell)
2070 {
2071         GtkSText  *gtktext;
2072
2073         gtktext = gtkaspell->gtktext;
2074
2075         gtk_stext_freeze(gtktext);
2076         gtk_editable_set_position(GTK_EDITABLE(gtktext),gtkaspell->orig_pos);
2077         gtk_stext_set_point(gtktext, gtkaspell->orig_pos);
2078         gtk_stext_thaw(gtktext);
2079
2080         if (gtkaspell->continue_check)
2081                 gtkaspell->continue_check((gpointer *) gtkaspell);
2082 }
2083
2084 static void allocate_color(GtkAspell *gtkaspell, gint rgbvalue)
2085 {
2086         GdkColormap *gc;
2087         GdkColor *color = &(gtkaspell->highlight);
2088
2089         gc = gtk_widget_get_colormap(GTK_WIDGET(gtkaspell->gtktext));
2090
2091         if (gtkaspell->highlight.pixel)
2092                 gdk_colormap_free_colors(gc, &(gtkaspell->highlight), 1);
2093
2094         /* Shameless copy from Sylpheed's gtkutils.c */
2095         color->pixel = 0L;
2096         color->red   = (int) (((gdouble)((rgbvalue & 0xff0000) >> 16) / 255.0)
2097                         * 65535.0);
2098         color->green = (int) (((gdouble)((rgbvalue & 0x00ff00) >>  8) / 255.0)
2099                         * 65535.0);
2100         color->blue  = (int) (((gdouble) (rgbvalue & 0x0000ff)        / 255.0)
2101                         * 65535.0);
2102
2103         gdk_colormap_alloc_color(gc, &(gtkaspell->highlight), FALSE, TRUE);
2104 }
2105
2106 static void change_color(GtkAspell * gtkaspell, 
2107                          gint start, gint end,
2108                          gchar *newtext,
2109                          GdkColor *color) 
2110 {
2111         GtkSText *gtktext;
2112
2113         g_return_if_fail(start < end);
2114     
2115         gtktext = gtkaspell->gtktext;
2116     
2117         gtk_stext_freeze(gtktext);
2118         if (newtext) {
2119                 gtk_stext_set_point(gtktext, start);
2120                 gtk_stext_forward_delete(gtktext, end - start);
2121                 gtk_stext_insert(gtktext, NULL, color, NULL, newtext,
2122                                  end - start);
2123         }
2124         gtk_stext_thaw(gtktext);
2125 }
2126
2127 /* convert_to_aspell_encoding () - converts ISO-8859-* strings to iso8859-* 
2128  * as needed by aspell. Returns an allocated string.
2129  */
2130
2131 static guchar *convert_to_aspell_encoding (const guchar *encoding)
2132 {
2133         guchar * aspell_encoding;
2134
2135         if (strstr2(encoding, "ISO-8859-")) {
2136                 aspell_encoding = g_strdup_printf("iso8859%s", encoding+8);
2137         }
2138         else {
2139                 if (!strcmp2(encoding, "US-ASCII"))
2140                         aspell_encoding = g_strdup("iso8859-1");
2141                 else
2142                         aspell_encoding = g_strdup(encoding);
2143         }
2144
2145         return aspell_encoding;
2146 }
2147
2148 /* compare_dict () - compare 2 dict names */
2149 static gint compare_dict(Dictionary *a, Dictionary *b)
2150 {
2151         guint   aparts = 0,  bparts = 0;
2152         guint   i;
2153
2154         for (i=0; i < strlen(a->dictname); i++)
2155                 if (a->dictname[i] == '-')
2156                         aparts++;
2157         for (i=0; i < strlen(b->dictname); i++)
2158                 if (b->dictname[i] == '-')
2159                         bparts++;
2160
2161         if (aparts != bparts) 
2162                 return (aparts < bparts) ? -1 : +1;
2163         else {
2164                 gint compare;
2165                 compare = strcmp2(a->dictname, b->dictname);
2166                 if (!compare)
2167                         compare = strcmp2(a->fullname, b->fullname);
2168                 return compare;
2169         }
2170 }
2171
2172 static void dictionary_delete(Dictionary *dict)
2173 {
2174         g_free(dict->fullname);
2175         g_free(dict->encoding);
2176         g_free(dict);
2177 }
2178
2179 static Dictionary *dictionary_dup(const Dictionary *dict)
2180 {
2181         Dictionary *dict2;
2182
2183         dict2 = g_new(Dictionary, 1); 
2184
2185         dict2->fullname = g_strdup(dict->fullname);
2186         dict2->dictname = dict->dictname - dict->fullname + dict2->fullname;
2187         dict2->encoding = g_strdup(dict->encoding);
2188
2189         return dict2;
2190 }
2191
2192 static void free_suggestions_list(GtkAspell *gtkaspell)
2193 {
2194         GList *list;
2195
2196         for (list = gtkaspell->suggestions_list; list != NULL;
2197              list = list->next)
2198                 g_free(list->data);
2199
2200         g_list_free(list);
2201         
2202         gtkaspell->max_sug          = -1;
2203         gtkaspell->suggestions_list = NULL;
2204 }
2205
2206 static void reset_theword_data(GtkAspell *gtkaspell)
2207 {
2208         gtkaspell->start_pos     =  0;
2209         gtkaspell->end_pos       =  0;
2210         gtkaspell->theword[0]    =  0;
2211         gtkaspell->max_sug       = -1;
2212
2213         free_suggestions_list(gtkaspell);
2214 }
2215
2216 static void free_checkers(gpointer elt, gpointer data)
2217 {
2218         GtkAspeller *gtkaspeller = elt;
2219
2220         g_return_if_fail(gtkaspeller);
2221
2222         gtkaspeller_real_delete(gtkaspeller);
2223 }
2224
2225 static gint find_gtkaspeller(gconstpointer aa, gconstpointer bb)
2226 {
2227         Dictionary *a = ((GtkAspeller *) aa)->dictionary;
2228         Dictionary *b = ((GtkAspeller *) bb)->dictionary;
2229
2230         if (a && b && a->fullname && b->fullname  &&
2231             strcmp(a->fullname, b->fullname) == 0 &&
2232             a->encoding && b->encoding)
2233                 return strcmp(a->encoding, b->encoding);
2234
2235         return 1;
2236 }
2237
2238 static void gtkaspell_alert_dialog(gchar *message)
2239 {
2240         GtkWidget *dialog;
2241         GtkWidget *hbox;
2242         GtkWidget *label;
2243         GtkWidget *ok_button;
2244
2245         dialog = gtk_dialog_new();
2246         gtk_window_set_policy(GTK_WINDOW(dialog), FALSE, FALSE, FALSE);
2247         gtk_window_set_position(GTK_WINDOW(dialog),GTK_WIN_POS_MOUSE);
2248         gtk_signal_connect_object(GTK_OBJECT(dialog), "destroy",
2249                                    GTK_SIGNAL_FUNC(gtk_widget_destroy), 
2250                                    GTK_OBJECT(dialog));
2251
2252         label  = gtk_label_new(message);
2253         gtk_misc_set_padding(GTK_MISC(label), 8, 8);
2254
2255         gtk_container_add(GTK_CONTAINER(GTK_DIALOG(dialog)->vbox), label);
2256         
2257         hbox = gtk_hbox_new(FALSE, 0);
2258
2259         ok_button = gtk_button_new_with_label(_("OK"));
2260         GTK_WIDGET_SET_FLAGS(ok_button, GTK_CAN_DEFAULT);
2261         gtk_box_pack_start(GTK_BOX(hbox), ok_button, TRUE, TRUE, 8);    
2262
2263         gtk_signal_connect_object(GTK_OBJECT(ok_button), "clicked",
2264                                    GTK_SIGNAL_FUNC(gtk_widget_destroy), 
2265                                    GTK_OBJECT(dialog));
2266         gtk_container_add(GTK_CONTAINER(GTK_DIALOG(dialog)->action_area), hbox);
2267                         
2268         gtk_widget_grab_default(ok_button);
2269         gtk_widget_grab_focus(ok_button);
2270         gtk_window_set_modal(GTK_WINDOW(dialog), TRUE);
2271
2272         gtk_widget_show_all(dialog);
2273 }
2274 #endif