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