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