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