9e032f149ff0ec7605e5dccf8577bddad1cbe3d2
[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         origpos = gtk_editable_get_position(GTK_EDITABLE(gtktext));
1107
1108 /*      gtk_editable_set_position(GTK_EDITABLE(gtktext), 0);*/
1109
1110         while (pos < len) {
1111                 while (pos < len && 
1112                        iswordsep(get_text_index_whar(gtkpspell, pos)))
1113                         pos++;
1114                 while (pos < len &&
1115                        !iswordsep(get_text_index_whar(gtkpspell, pos)))
1116                         pos++;
1117                 if (pos > 0)
1118                         check_at(gtkpspell, pos - 1);
1119         }
1120         gtk_editable_set_position(GTK_EDITABLE(gtktext), origpos);
1121         gtk_stext_set_point(GTK_STEXT(gtktext), origpos);
1122         gtk_adjustment_set_value(gtktext->vadj, adj_value);
1123 /*      gtk_editable_select_region(GTK_EDITABLE(gtktext), origpos, origpos);*/
1124 }
1125
1126 static void replace_with_supplied_word_cb(GtkWidget *w, GtkPspell *gtkpspell) 
1127 {
1128         unsigned char *newword;
1129         GdkEvent *e= (GdkEvent *) gtk_get_current_event();
1130         
1131         newword = gtk_editable_get_chars(GTK_EDITABLE(gtkpspell->replace_entry),
1132                                          0, -1);
1133         
1134         if (strcmp(newword, gtkpspell->theword)) {
1135                 replace_real_word(gtkpspell, newword);
1136
1137                 if ((e->type == GDK_KEY_PRESS && 
1138                     ((GdkEventKey *) e)->state & GDK_MOD1_MASK)) {
1139                         pspell_manager_store_replacement(gtkpspell->gtkpspeller->checker, 
1140                                                          gtkpspell->theword, -1,
1141                                                          newword, -1);
1142                 }
1143                 gtkpspell->replace_entry = NULL;
1144         }
1145
1146         g_free(newword);
1147
1148         set_point_continue(gtkpspell);
1149 }
1150
1151
1152 static void replace_word_cb(GtkWidget *w, gpointer data)
1153 {
1154         unsigned char *newword;
1155         GtkPspell *gtkpspell = (GtkPspell *) data;
1156         GdkEvent *e= (GdkEvent *) gtk_get_current_event();
1157
1158         gtk_label_get(GTK_LABEL(GTK_BIN(w)->child), (gchar**) &newword);
1159
1160         replace_real_word(gtkpspell, newword);
1161
1162         if ((e->type == GDK_KEY_PRESS && 
1163             ((GdkEventKey *) e)->state & GDK_MOD1_MASK) ||
1164             (e->type == GDK_BUTTON_RELEASE && 
1165              ((GdkEventButton *) e)->state & GDK_MOD1_MASK)) {
1166                 pspell_manager_store_replacement(gtkpspell->gtkpspeller->checker, 
1167                                                  gtkpspell->theword, -1, 
1168                                                  newword, -1);
1169         }
1170
1171         gtk_menu_shell_deactivate(GTK_MENU_SHELL(w->parent));
1172
1173         set_point_continue(gtkpspell);
1174 }
1175
1176 static void replace_real_word(GtkPspell *gtkpspell, gchar *newword)
1177 {
1178         int             oldlen, newlen, wordlen;
1179         gint            origpos;
1180         gint            pos;
1181         gint            start = gtkpspell->start_pos;
1182         GtkSText       *gtktext;
1183     
1184         if (!newword) return;
1185
1186         gtktext = gtkpspell->gtktext;
1187
1188         gtk_stext_freeze(GTK_STEXT(gtktext));
1189         origpos = gtkpspell->orig_pos;
1190         pos     = origpos;
1191         oldlen  = gtkpspell->end_pos - gtkpspell->start_pos;
1192         wordlen = strlen(gtkpspell->theword);
1193
1194         newlen = strlen(newword); /* FIXME: multybyte characters? */
1195
1196         gtk_signal_handler_block_by_func(GTK_OBJECT(gtktext),
1197                                          GTK_SIGNAL_FUNC(entry_insert_cb), 
1198                                          gtkpspell);
1199         gtk_signal_handler_block_by_func(GTK_OBJECT(gtktext),
1200                                          GTK_SIGNAL_FUNC(entry_delete_cb), 
1201                                          gtkpspell);
1202
1203         gtk_signal_emit_by_name(GTK_OBJECT(gtktext), "delete-text", 
1204                                 gtkpspell->start_pos, gtkpspell->end_pos);
1205         gtk_signal_emit_by_name(GTK_OBJECT(gtktext), "insert-text", 
1206                                 newword, newlen, &start);
1207         
1208         gtk_signal_handler_unblock_by_func(GTK_OBJECT(gtktext),
1209                                            GTK_SIGNAL_FUNC(entry_insert_cb), 
1210                                            gtkpspell);
1211         gtk_signal_handler_unblock_by_func(GTK_OBJECT(gtktext),
1212                                            GTK_SIGNAL_FUNC(entry_delete_cb), 
1213                                            gtkpspell);
1214         
1215         /* Put the point and the position where we clicked with the mouse
1216          * It seems to be a hack, as I must thaw,freeze,thaw the widget
1217          * to let it update correctly the word insertion and then the
1218          * point & position position. If not, SEGV after the first replacement
1219          * If the new word ends before point, put the point at its end.
1220          */
1221     
1222         if (origpos - gtkpspell->start_pos < oldlen && 
1223             origpos - gtkpspell->start_pos >= 0) {
1224                 /* Original point was in the word.
1225                  * Let it there unless point is going to be outside of the word
1226                  */
1227                 if (origpos - gtkpspell->start_pos >= newlen) {
1228                         pos = gtkpspell->start_pos + newlen;
1229                 }
1230         }
1231         else if (origpos >= gtkpspell->end_pos) {
1232                 /* move the position according to the change of length */
1233                 pos = origpos + newlen - oldlen;
1234         }
1235         
1236         gtkpspell->end_pos = gtkpspell->start_pos + strlen(newword); /* FIXME: multibyte characters? */
1237         
1238         gtk_stext_thaw(GTK_STEXT(gtktext));
1239         gtk_stext_freeze(GTK_STEXT(gtktext));
1240
1241         if (GTK_STEXT(gtktext)->text_len < pos)
1242                 pos = gtk_stext_get_length(GTK_STEXT(gtktext));
1243
1244         gtkpspell->orig_pos = pos;
1245
1246         gtk_editable_set_position(GTK_EDITABLE(gtktext), gtkpspell->orig_pos);
1247         gtk_stext_set_point(GTK_STEXT(gtktext), 
1248                             gtk_editable_get_position(GTK_EDITABLE(gtktext)));
1249
1250         gtk_stext_thaw(GTK_STEXT(gtktext));
1251 }
1252
1253 /* Accept this word for this session */
1254 static void add_word_to_session_cb(GtkWidget *w, gpointer data)
1255 {
1256         guint     pos;
1257         GtkSText *gtktext;
1258         GtkPspell *gtkpspell = (GtkPspell *) data; 
1259         gtktext = gtkpspell->gtktext;
1260
1261         gtk_stext_freeze(GTK_STEXT(gtktext));
1262
1263         pos = gtk_editable_get_position(GTK_EDITABLE(gtktext));
1264     
1265         pspell_manager_add_to_session(gtkpspell->gtkpspeller->checker,
1266                                       gtkpspell->theword, 
1267                                       strlen(gtkpspell->theword));
1268
1269         check_at(gtkpspell, gtkpspell->start_pos);
1270
1271         gtk_stext_thaw(gtkpspell->gtktext);
1272
1273         gtk_menu_shell_deactivate(GTK_MENU_SHELL(GTK_WIDGET(w)->parent));
1274
1275         set_point_continue(gtkpspell);
1276 }
1277
1278 /* add_word_to_personal_cb() - add word to personal dict. */
1279 static void add_word_to_personal_cb(GtkWidget *w, gpointer data)
1280 {
1281         GtkPspell *gtkpspell = (GtkPspell *) data; 
1282         GtkSText *gtktext    = gtkpspell->gtktext;
1283
1284         gtk_stext_freeze(GTK_STEXT(gtktext));
1285     
1286         pspell_manager_add_to_personal(gtkpspell->gtkpspeller->checker,
1287                                        gtkpspell->theword,
1288                                        strlen(gtkpspell->theword));
1289     
1290         check_at(gtkpspell, gtkpspell->start_pos);
1291
1292         gtk_stext_thaw(gtkpspell->gtktext);
1293
1294         gtk_menu_shell_deactivate(GTK_MENU_SHELL(GTK_WIDGET(w)->parent));
1295         set_point_continue(gtkpspell);
1296 }
1297
1298 static void check_with_alternate_cb(GtkWidget *w, gpointer data)
1299 {
1300         GtkPspell *gtkpspell = (GtkPspell *) data;
1301         gint misspelled;
1302
1303         gtk_menu_shell_deactivate(GTK_MENU_SHELL(GTK_WIDGET(w)->parent));
1304
1305         use_alternate_dict(gtkpspell);
1306         misspelled = check_at(gtkpspell, gtkpspell->start_pos);
1307
1308         if (!gtkpspell->continue_check) {
1309
1310                 gtkpspell->misspelled = misspelled;
1311
1312                 if (gtkpspell->misspelled) {
1313
1314                         misspelled_suggest(gtkpspell, gtkpspell->theword);
1315
1316                         gtk_stext_set_point(GTK_STEXT(gtkpspell->gtktext),
1317                                             gtkpspell->end_pos);
1318                         gtk_editable_set_position(GTK_EDITABLE(gtkpspell->gtktext),
1319                                                   gtkpspell->end_pos);
1320
1321                         gtk_menu_popup(make_sug_menu(gtkpspell), NULL, NULL, 
1322                                        set_menu_pos, gtkpspell, 0, 
1323                                        GDK_CURRENT_TIME);
1324                         return;
1325                 }
1326         } else
1327                 gtkpspell->orig_pos = gtkpspell->start_pos;
1328
1329         set_point_continue(gtkpspell);
1330 }
1331         
1332 static void replace_with_create_dialog_cb(GtkWidget *w, gpointer data)
1333 {
1334         GtkWidget *dialog;
1335         GtkWidget *label;
1336         GtkWidget *hbox;
1337         GtkWidget *entry;
1338         GtkWidget *ok_button;
1339         GtkWidget *cancel_button;
1340         gchar *thelabel;
1341         gint xx, yy;
1342         GtkPspell *gtkpspell = (GtkPspell *) data;
1343
1344         gdk_window_get_origin((GTK_WIDGET(w)->parent)->window, &xx, &yy);
1345
1346         gtk_menu_shell_deactivate(GTK_MENU_SHELL(GTK_WIDGET(w)->parent));
1347
1348         dialog = gtk_dialog_new();
1349
1350         gtk_window_set_policy(GTK_WINDOW(dialog), FALSE, FALSE, FALSE);
1351         gtk_window_set_title(GTK_WINDOW(dialog),_("Replace unknown word"));
1352         gtk_widget_set_uposition(dialog, xx, yy);
1353
1354         gtk_signal_connect_object(GTK_OBJECT(dialog), "destroy",
1355                                   GTK_SIGNAL_FUNC(gtk_widget_destroy), 
1356                                   GTK_OBJECT(dialog));
1357
1358         hbox = gtk_hbox_new(FALSE, 0);
1359         gtk_container_set_border_width(GTK_CONTAINER(hbox), 8);
1360
1361         thelabel = g_strdup_printf(_("Replace \"%s\" with: "), 
1362                                    gtkpspell->theword);
1363         label = gtk_label_new(thelabel);
1364         g_free(thelabel);
1365         gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 0);
1366
1367         entry = gtk_entry_new();
1368         gtkpspell->replace_entry = entry;
1369         gtk_entry_set_text(GTK_ENTRY(entry), gtkpspell->theword);
1370         gtk_editable_select_region(GTK_EDITABLE(entry), 0, -1);
1371         gtk_signal_connect(GTK_OBJECT(entry), "activate",
1372                            GTK_SIGNAL_FUNC(replace_with_supplied_word_cb), 
1373                            gtkpspell);
1374         gtk_signal_connect_object(GTK_OBJECT(entry), "activate",
1375                            GTK_SIGNAL_FUNC(gtk_widget_destroy), 
1376                            GTK_OBJECT(dialog));
1377         gtk_box_pack_start(GTK_BOX(hbox), entry, TRUE, TRUE, 0);
1378
1379         gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->vbox), hbox, TRUE, 
1380                            TRUE, 0);
1381         if (!gtkpspell->gtkpspeller->ispell) {
1382                 label = gtk_label_new(_("Holding down MOD1 key while pressing "
1383                                         "Enter\nwill learn from mistake.\n"));
1384                 gtk_label_set_justify(GTK_LABEL(label), GTK_JUSTIFY_LEFT);
1385                 gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.0);
1386                 gtk_misc_set_padding(GTK_MISC(label), 8, 0);
1387                 gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->vbox), label, 
1388                                    TRUE, TRUE, 0);
1389         }
1390
1391         hbox = gtk_hbox_new(TRUE, 0);
1392
1393         ok_button = gtk_button_new_with_label(_("OK"));
1394         gtk_box_pack_start(GTK_BOX(hbox), ok_button, TRUE, TRUE, 8);
1395         gtk_signal_connect(GTK_OBJECT(ok_button), "clicked",
1396                            GTK_SIGNAL_FUNC(replace_with_supplied_word_cb), 
1397                            gtkpspell);
1398         gtk_signal_connect_object(GTK_OBJECT(ok_button), "clicked",
1399                                   GTK_SIGNAL_FUNC(gtk_widget_destroy), 
1400                                   GTK_OBJECT(dialog));
1401
1402         cancel_button = gtk_button_new_with_label(_("Cancel"));
1403         gtk_box_pack_start(GTK_BOX(hbox), cancel_button, TRUE, TRUE, 8);
1404         gtk_signal_connect_object(GTK_OBJECT(cancel_button), "clicked",
1405                                   GTK_SIGNAL_FUNC(gtk_widget_destroy), 
1406                                   GTK_OBJECT(dialog));
1407
1408         gtk_container_add(GTK_CONTAINER(GTK_DIALOG(dialog)->action_area), hbox);
1409
1410         gtk_widget_grab_focus(entry);
1411
1412         gtk_window_set_modal(GTK_WINDOW(dialog), TRUE);
1413
1414         gtk_widget_show_all(dialog);
1415 }
1416
1417 void gtkpspell_uncheck_all(GtkPspell * gtkpspell) 
1418 {
1419         gint      origpos;
1420         gchar    *text;
1421         gfloat    adj_value;
1422         GtkSText *gtktext;
1423         
1424         gtktext = gtkpspell->gtktext;
1425
1426         adj_value = gtktext->vadj->value;
1427
1428         gtk_stext_freeze(gtktext);
1429
1430         origpos = gtk_editable_get_position(GTK_EDITABLE(gtktext));
1431
1432         text = gtk_editable_get_chars(GTK_EDITABLE(gtktext), 0, -1);
1433
1434         gtk_stext_set_point(gtktext, 0);
1435         gtk_stext_forward_delete(gtktext, gtk_stext_get_length(gtktext));
1436         gtk_stext_insert(gtktext, NULL, NULL, NULL, text, strlen(text));
1437
1438         gtk_stext_thaw(gtktext);
1439
1440         gtk_editable_set_position(GTK_EDITABLE(gtktext), origpos);
1441         gtk_stext_set_point(gtktext, origpos);
1442         gtk_adjustment_set_value(gtktext->vadj, adj_value);
1443
1444         g_free(text);
1445
1446 }
1447
1448 static void toggle_check_while_typing_cb(GtkWidget *w, gpointer data)
1449 {
1450         GtkPspell *gtkpspell = (GtkPspell *) data;
1451
1452         gtkpspell->check_while_typing = gtkpspell->check_while_typing == FALSE;
1453
1454         if (!gtkpspell->check_while_typing)
1455                 gtkpspell_uncheck_all(gtkpspell);
1456
1457         if (gtkpspell->config_menu)
1458                 populate_submenu(gtkpspell, gtkpspell->config_menu);
1459 }
1460
1461 static GSList *create_empty_dictionary_list(void)
1462 {
1463         GSList *list = NULL;
1464         Dictionary *dict;
1465
1466         dict = g_new0(Dictionary, 1);
1467         dict->fullname = g_strdup(_("None"));
1468         dict->dictname = dict->fullname;
1469         dict->encoding = NULL;
1470
1471         return g_slist_append(list, dict);
1472 }
1473
1474 /* gtkpspell_get_dictionary_list() - returns list of dictionary names */
1475 GSList *gtkpspell_get_dictionary_list(const gchar *pspell_path, gint refresh)
1476 {
1477         GSList *list;
1478         gchar *dict_path, *tmp, *prevdir;
1479         gchar tmpname[BUFSIZE];
1480         Dictionary *dict;
1481         DIR *dir;
1482         struct dirent *ent;
1483
1484         if (!gtkpspellcheckers)
1485                 gtkpspellcheckers = gtkpspell_checkers_new();
1486
1487         if (gtkpspellcheckers->dictionary_list && !refresh)
1488                 return gtkpspellcheckers->dictionary_list;
1489         else
1490                 gtkpspell_free_dictionary_list(gtkpspellcheckers->dictionary_list);
1491
1492         list = NULL;
1493
1494 #ifdef USE_THREADS
1495 #warning TODO: no directory change
1496 #endif
1497         dict_path = g_strdup(pspell_path);
1498         prevdir   = g_get_current_dir();
1499         if (chdir(dict_path) <0) {
1500                 debug_print(_("Pspell: error when searching for dictionaries:\n%s\n"),
1501                             g_strerror(errno));
1502                 g_free(prevdir);
1503                 g_free(dict_path);
1504                 gtkpspellcheckers->dictionary_list =
1505                         create_empty_dictionary_list();
1506
1507                 return gtkpspellcheckers->dictionary_list; 
1508         }
1509
1510         debug_print(_("Pspell: checking for dictionaries in %s\n"), dict_path);
1511
1512         if (NULL != (dir = opendir("."))) {
1513                 while (NULL != (ent = readdir(dir))) {
1514                         /* search for pwli */
1515                         if ((NULL != (tmp = strstr2(ent->d_name, ".pwli"))) && 
1516                             (tmp[5] == 0x00)) {
1517                                 g_snprintf(tmpname, BUFSIZE, "%s%s", 
1518                                            G_DIR_SEPARATOR_S, ent->d_name);
1519                                 tmpname[MIN(tmp - ent->d_name + 1, BUFSIZE-1)] =
1520                                         0x00;
1521                                 dict = g_new0(Dictionary, 1);
1522                                 dict->fullname = g_strdup_printf("%s%s", 
1523                                                                  dict_path, 
1524                                                                  tmpname);
1525                                 dict->dictname = strrchr(dict->fullname, 
1526                                                          G_DIR_SEPARATOR) + 1;
1527                                 dict->encoding = NULL;
1528                                 debug_print(_("Pspell: found dictionary %s %s\n"),
1529                                             dict->fullname, dict->dictname);
1530                                 list = g_slist_insert_sorted(list, dict,
1531                                                 (GCompareFunc) compare_dict);
1532                         }
1533                 }                       
1534                 closedir(dir);
1535         }
1536         else {
1537                 debug_print(_("Pspell: error when searching for dictionaries.\nNo dictionary found.\n(%s)"), 
1538                             g_strerror(errno));
1539                 list = create_empty_dictionary_list();
1540         }
1541         if(list==NULL){
1542                 
1543                 debug_print(_("Pspell: error when searching for dictionaries.\nNo dictionary found.\n"));
1544                 list = create_empty_dictionary_list();
1545         }
1546
1547         chdir(prevdir);
1548         g_free(dict_path);
1549         g_free(prevdir);
1550         gtkpspellcheckers->dictionary_list = list;
1551
1552         return list;
1553 }
1554
1555 void gtkpspell_free_dictionary_list(GSList *list)
1556 {
1557         Dictionary *dict;
1558         GSList *walk;
1559         for (walk = list; walk != NULL; walk = g_slist_next(walk))
1560                 if (walk->data) {
1561                         dict = (Dictionary *) walk->data;
1562                         dictionary_delete(dict);
1563                 }                               
1564         g_slist_free(list);
1565 }
1566
1567 GtkWidget *gtkpspell_dictionary_option_menu_new(const gchar *pspell_path)
1568 {
1569         GSList *dict_list, *tmp;
1570         GtkWidget *item;
1571         GtkWidget *menu;
1572         Dictionary *dict;
1573
1574         dict_list = gtkpspell_get_dictionary_list(pspell_path, TRUE);
1575         g_return_val_if_fail(dict_list, NULL);
1576
1577         menu = gtk_menu_new();
1578         
1579         for (tmp = dict_list; tmp != NULL; tmp = g_slist_next(tmp)) {
1580                 dict = (Dictionary *) tmp->data;
1581                 item = gtk_menu_item_new_with_label(dict->dictname);
1582                 gtk_object_set_data(GTK_OBJECT(item), "dict_name",
1583                                     dict->fullname); 
1584                                          
1585                 gtk_menu_append(GTK_MENU(menu), item);                                   
1586                 gtk_widget_show(item);
1587         }
1588
1589         gtk_widget_show(menu);
1590
1591         return menu;
1592 }
1593
1594 gchar *gtkpspell_get_dictionary_menu_active_item(GtkWidget *menu)
1595 {
1596         GtkWidget *menuitem;
1597         gchar *dict_fullname;
1598         gchar *label;
1599
1600         g_return_val_if_fail(GTK_IS_MENU(menu), NULL);
1601
1602         menuitem = gtk_menu_get_active(GTK_MENU(menu));
1603         dict_fullname = (gchar *) gtk_object_get_data(GTK_OBJECT(menuitem), 
1604                                                       "dict_name");
1605         g_return_val_if_fail(dict_fullname, NULL);
1606
1607         label = g_strdup(dict_fullname);
1608
1609         return label;
1610   
1611 }
1612
1613 GtkWidget *gtkpspell_sugmode_option_menu_new(gint sugmode)
1614 {
1615         GtkWidget *menu;
1616         GtkWidget *item;
1617
1618         menu = gtk_menu_new();
1619         gtk_widget_show(menu);
1620
1621         item = gtk_menu_item_new_with_label(_("Fast Mode"));
1622         gtk_widget_show(item);
1623         gtk_menu_append(GTK_MENU(menu), item);
1624         gtk_object_set_data(GTK_OBJECT(item), "sugmode",
1625                             GINT_TO_POINTER(PSPELL_FASTMODE));
1626
1627         item = gtk_menu_item_new_with_label(_("Normal Mode"));
1628         gtk_widget_show(item);
1629         gtk_menu_append(GTK_MENU(menu), item);
1630         gtk_object_set_data(GTK_OBJECT(item), "sugmode",
1631                             GINT_TO_POINTER(PSPELL_NORMALMODE));
1632         
1633         item = gtk_menu_item_new_with_label(_("Bad Spellers Mode"));
1634         gtk_widget_show(item);
1635         gtk_menu_append(GTK_MENU(menu), item);
1636         gtk_object_set_data(GTK_OBJECT(item), "sugmode",
1637                             GINT_TO_POINTER(PSPELL_BADSPELLERMODE));
1638
1639         return menu;
1640 }
1641         
1642 void gtkpspell_sugmode_option_menu_set(GtkOptionMenu *optmenu, gint sugmode)
1643 {
1644         g_return_if_fail(GTK_IS_OPTION_MENU(optmenu));
1645
1646         g_return_if_fail(sugmode == PSPELL_FASTMODE ||
1647                          sugmode == PSPELL_NORMALMODE ||
1648                          sugmode == PSPELL_BADSPELLERMODE);
1649
1650         gtk_option_menu_set_history(GTK_OPTION_MENU(optmenu), sugmode - 1);
1651 }
1652
1653 gint gtkpspell_get_sugmode_from_option_menu(GtkOptionMenu *optmenu)
1654 {
1655         gint sugmode;
1656         GtkWidget *item;
1657         
1658         g_return_val_if_fail(GTK_IS_OPTION_MENU(optmenu), -1);
1659
1660         item = gtk_menu_get_active(GTK_MENU(gtk_option_menu_get_menu(optmenu)));
1661         
1662         sugmode = GPOINTER_TO_INT(gtk_object_get_data(GTK_OBJECT(item),
1663                                                       "sugmode"));
1664
1665         return sugmode;
1666 }
1667
1668 static void use_alternate_dict(GtkPspell *gtkpspell)
1669 {
1670         GtkPspeller *tmp;
1671
1672         tmp = gtkpspell->gtkpspeller;
1673         gtkpspell->gtkpspeller = gtkpspell->alternate_speller;
1674         gtkpspell->alternate_speller = tmp;
1675
1676         if (gtkpspell->config_menu)
1677                 populate_submenu(gtkpspell, gtkpspell->config_menu);
1678 }
1679
1680 static void popup_menu(GtkPspell *gtkpspell, GdkEventButton *eb) 
1681 {
1682         GtkSText * gtktext;
1683         
1684         gtktext = gtkpspell->gtktext;
1685         gtkpspell->orig_pos = gtk_editable_get_position(GTK_EDITABLE(gtktext));
1686
1687         if (!(eb->state & GDK_SHIFT_MASK)) {
1688                 if (check_at(gtkpspell, gtkpspell->orig_pos)) {
1689
1690                         gtk_editable_set_position(GTK_EDITABLE(gtktext), 
1691                                                   gtkpspell->orig_pos);
1692                         gtk_stext_set_point(gtktext, gtkpspell->orig_pos);
1693
1694                         if (misspelled_suggest(gtkpspell, gtkpspell->theword)) {
1695                                 gtk_menu_popup(make_sug_menu(gtkpspell), 
1696                                                NULL, NULL, NULL, NULL,
1697                                                eb->button, GDK_CURRENT_TIME);
1698                                 
1699                                 return;
1700                         }
1701                 } else {
1702                         gtk_editable_set_position(GTK_EDITABLE(gtktext), 
1703                                                   gtkpspell->orig_pos);
1704                         gtk_stext_set_point(gtktext, gtkpspell->orig_pos);
1705                 }
1706         }
1707
1708         gtk_menu_popup(make_config_menu(gtkpspell), NULL, NULL, NULL, NULL,
1709                        eb->button, GDK_CURRENT_TIME);
1710 }
1711
1712 /* make_sug_menu() - Add menus to accept this word for this session 
1713  * and to add it to personal dictionary 
1714  */
1715 static GtkMenu *make_sug_menu(GtkPspell *gtkpspell) 
1716 {
1717         GtkWidget       *menu, *item;
1718         unsigned char   *caption;
1719         GtkSText        *gtktext;
1720         GtkAccelGroup   *accel;
1721         GList           *l = gtkpspell->suggestions_list;
1722
1723         gtktext = gtkpspell->gtktext;
1724
1725         accel = gtk_accel_group_new();
1726         menu = gtk_menu_new(); 
1727
1728         if (gtkpspell->sug_menu)
1729                 gtk_widget_destroy(gtkpspell->sug_menu);
1730
1731         gtkpspell->sug_menu = menu;     
1732
1733         gtk_signal_connect(GTK_OBJECT(menu), "cancel",
1734                 GTK_SIGNAL_FUNC(cancel_menu_cb), gtkpspell);
1735
1736         caption = g_strdup_printf(_("\"%s\" unknown in %s"), 
1737                                   (unsigned char*) l->data, 
1738                                   gtkpspell->gtkpspeller->dictionary->dictname);
1739         item = gtk_menu_item_new_with_label(caption);
1740         gtk_widget_show(item);
1741         gtk_menu_append(GTK_MENU(menu), item);
1742         gtk_misc_set_alignment(GTK_MISC(GTK_BIN(item)->child), 0.5, 0.5);
1743         g_free(caption);
1744
1745         item = gtk_menu_item_new();
1746         gtk_widget_show(item);
1747         gtk_menu_append(GTK_MENU(menu), item);
1748
1749         item = gtk_menu_item_new_with_label(_("Accept in this session"));
1750         gtk_widget_show(item);
1751         gtk_menu_append(GTK_MENU(menu), item);
1752         gtk_signal_connect(GTK_OBJECT(item), "activate",
1753                            GTK_SIGNAL_FUNC(add_word_to_session_cb), 
1754                            gtkpspell);
1755         gtk_widget_add_accelerator(item, "activate", accel, GDK_space,
1756                                    GDK_MOD1_MASK,
1757                                    GTK_ACCEL_LOCKED | GTK_ACCEL_VISIBLE);
1758
1759         item = gtk_menu_item_new_with_label(_("Add to personal dictionary"));
1760         gtk_widget_show(item);
1761         gtk_menu_append(GTK_MENU(menu), item);
1762         gtk_signal_connect(GTK_OBJECT(item), "activate",
1763                            GTK_SIGNAL_FUNC(add_word_to_personal_cb), 
1764                            gtkpspell);
1765         gtk_widget_add_accelerator(item, "activate", accel, GDK_Return,
1766                                    GDK_MOD1_MASK,
1767                                    GTK_ACCEL_LOCKED | GTK_ACCEL_VISIBLE);
1768
1769         item = gtk_menu_item_new_with_label(_("Replace with..."));
1770         gtk_widget_show(item);
1771         gtk_menu_append(GTK_MENU(menu), item);
1772         gtk_signal_connect(GTK_OBJECT(item), "activate",
1773                            GTK_SIGNAL_FUNC(replace_with_create_dialog_cb), 
1774                            gtkpspell);
1775         gtk_widget_add_accelerator(item, "activate", accel, GDK_R, 0,
1776                                    GTK_ACCEL_LOCKED | GTK_ACCEL_VISIBLE);
1777
1778         if (gtkpspell->use_alternate && gtkpspell->alternate_speller) {
1779                 caption = g_strdup_printf(_("Check with %s"), 
1780                         gtkpspell->alternate_speller->dictionary->dictname);
1781                 item = gtk_menu_item_new_with_label(caption);
1782                 g_free(caption);
1783                 gtk_widget_show(item);
1784                 gtk_menu_append(GTK_MENU(menu), item);
1785                 gtk_signal_connect(GTK_OBJECT(item), "activate",
1786                                 GTK_SIGNAL_FUNC(check_with_alternate_cb),
1787                                 gtkpspell);
1788                 gtk_widget_add_accelerator(item, "activate", accel, GDK_X, 0,
1789                                            GTK_ACCEL_LOCKED | GTK_ACCEL_VISIBLE);
1790         }
1791
1792         item = gtk_menu_item_new();
1793         gtk_widget_show(item);
1794         gtk_menu_append(GTK_MENU(menu), item);
1795
1796         l = l->next;
1797         if (l == NULL) {
1798                 item = gtk_menu_item_new_with_label(_("(no suggestions)"));
1799                 gtk_widget_show(item);
1800                 gtk_menu_append(GTK_MENU(menu), item);
1801         } else {
1802                 GtkWidget *curmenu = menu;
1803                 gint count = 0;
1804                 
1805                 do {
1806                         if (l->data == NULL && l->next != NULL) {
1807                                 count = 0;
1808                                 curmenu = gtk_menu_new();
1809                                 item = gtk_menu_item_new_with_label(_("Others..."));
1810                                 gtk_widget_show(item);
1811                                 gtk_menu_append(GTK_MENU(curmenu), item);
1812                                 gtk_menu_item_set_submenu(GTK_MENU_ITEM(item),
1813                                                           curmenu);
1814
1815                                 l = l->next;
1816                         } else if (count > MENUCOUNT) {
1817                                 count -= MENUCOUNT;
1818
1819                                 item = gtk_menu_item_new_with_label(_("More..."));
1820                                 gtk_widget_show(item);
1821                                 gtk_menu_append(GTK_MENU(curmenu), item);
1822
1823                                 curmenu = gtk_menu_new();
1824                                 gtk_menu_item_set_submenu(GTK_MENU_ITEM(item),
1825                                                           curmenu);
1826                         }
1827
1828                         item = gtk_menu_item_new_with_label((unsigned char*)l->data);
1829                         gtk_widget_show(item);
1830                         gtk_menu_append(GTK_MENU(curmenu), item);
1831                         gtk_signal_connect(GTK_OBJECT(item), "activate",
1832                                            GTK_SIGNAL_FUNC(replace_word_cb),
1833                                            gtkpspell);
1834
1835                         if (curmenu == menu && count < MENUCOUNT) {
1836                                 gtk_widget_add_accelerator(item, "activate",
1837                                                            accel,
1838                                                            GDK_A + count, 0,
1839                                                            GTK_ACCEL_LOCKED | 
1840                                                            GTK_ACCEL_VISIBLE);
1841                                 gtk_widget_add_accelerator(item, "activate", 
1842                                                            accel,
1843                                                            GDK_A + count, 
1844                                                            GDK_MOD1_MASK,
1845                                                            GTK_ACCEL_LOCKED);
1846                                 }
1847
1848                         count++;
1849
1850                 } while ((l = l->next) != NULL);
1851         }
1852
1853         gtk_accel_group_attach(accel, GTK_OBJECT(menu));
1854         gtk_accel_group_unref(accel);
1855         
1856         return GTK_MENU(menu);
1857 }
1858
1859 static void populate_submenu(GtkPspell *gtkpspell, GtkWidget *menu)
1860 {
1861         GtkWidget *item, *submenu;
1862         gchar *dictname;
1863         GtkPspeller *gtkpspeller = gtkpspell->gtkpspeller;
1864         gint ispell = gtkpspeller->ispell;
1865
1866         if (GTK_MENU_SHELL(menu)->children) {
1867                 GList *amenu, *alist;
1868                 for (amenu = (GTK_MENU_SHELL(menu)->children); amenu; ) {
1869                         alist = amenu->next;
1870                         gtk_widget_destroy(GTK_WIDGET(amenu->data));
1871                         amenu = alist;
1872                 }
1873         }
1874         
1875         dictname = g_strdup_printf(_("Dictionary: %s"),
1876                                    gtkpspeller->dictionary->dictname);
1877         item = gtk_menu_item_new_with_label(dictname);
1878         gtk_misc_set_alignment(GTK_MISC(GTK_BIN(item)->child), 0.5, 0.5);
1879         g_free(dictname);
1880         gtk_widget_show(item);
1881         gtk_menu_append(GTK_MENU(menu), item);
1882
1883         item = gtk_menu_item_new();
1884         gtk_widget_show(item);
1885         gtk_menu_append(GTK_MENU(menu), item);
1886                 
1887         if (gtkpspell->use_alternate && gtkpspell->alternate_speller) {
1888                 dictname = g_strdup_printf(_("Use alternate (%s)"), 
1889                                 gtkpspell->alternate_speller->dictionary->dictname);
1890                 item = gtk_menu_item_new_with_label(dictname);
1891                 g_free(dictname);
1892                 gtk_signal_connect(GTK_OBJECT(item), "activate",
1893                                    GTK_SIGNAL_FUNC(switch_to_alternate_cb),
1894                                    gtkpspell);
1895                 gtk_widget_show(item);
1896                 gtk_menu_append(GTK_MENU(menu), item);
1897                 item = gtk_menu_item_new();
1898                 gtk_widget_show(item);
1899                 gtk_menu_append(GTK_MENU(menu), item);
1900         }
1901
1902         item = gtk_check_menu_item_new_with_label(_("Fast Mode"));
1903         if (ispell || gtkpspell->gtkpspeller->sug_mode == PSPELL_FASTMODE)
1904                 gtk_widget_set_sensitive(GTK_WIDGET(item),FALSE);
1905         if (!ispell && gtkpspell->gtkpspeller->sug_mode == PSPELL_FASTMODE)
1906                 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(item),TRUE);
1907         else
1908                 gtk_signal_connect(GTK_OBJECT(item), "activate",
1909                                    GTK_SIGNAL_FUNC(set_sug_mode_cb),
1910                                    gtkpspell);
1911         gtk_widget_show(item);
1912         gtk_menu_append(GTK_MENU(menu), item);
1913
1914         item = gtk_check_menu_item_new_with_label(_("Normal Mode"));
1915         if (ispell || gtkpspell->gtkpspeller->sug_mode == PSPELL_NORMALMODE)
1916                 gtk_widget_set_sensitive(GTK_WIDGET(item), FALSE);
1917         if (!ispell && gtkpspell->gtkpspeller->sug_mode == PSPELL_NORMALMODE) 
1918                 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(item), TRUE);
1919         else
1920                 gtk_signal_connect(GTK_OBJECT(item), "activate",
1921                                    GTK_SIGNAL_FUNC(set_sug_mode_cb),
1922                                    gtkpspell);
1923         gtk_widget_show(item);
1924         gtk_menu_append(GTK_MENU(menu),item);
1925
1926         item = gtk_check_menu_item_new_with_label(_("Bad Spellers Mode"));
1927         if (ispell || gtkpspell->gtkpspeller->sug_mode == PSPELL_BADSPELLERMODE)
1928                 gtk_widget_set_sensitive(GTK_WIDGET(item), FALSE);
1929         if (!ispell && gtkpspell->gtkpspeller->sug_mode == PSPELL_BADSPELLERMODE)
1930                 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(item), TRUE);
1931         else
1932                 gtk_signal_connect(GTK_OBJECT(item), "activate",
1933                                    GTK_SIGNAL_FUNC(set_sug_mode_cb),
1934                                    gtkpspell);
1935         gtk_widget_show(item);
1936         gtk_menu_append(GTK_MENU(menu), item);
1937         
1938         item = gtk_menu_item_new();
1939         gtk_widget_show(item);
1940         gtk_menu_append(GTK_MENU(menu), item);
1941         
1942         item = gtk_check_menu_item_new_with_label(_("Check while typing"));
1943         if (gtkpspell->check_while_typing)
1944                 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(item), TRUE);
1945         else    
1946                 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(item), FALSE);
1947         gtk_signal_connect(GTK_OBJECT(item), "activate",
1948                            GTK_SIGNAL_FUNC(toggle_check_while_typing_cb),
1949                            gtkpspell);
1950         gtk_widget_show(item);
1951         gtk_menu_append(GTK_MENU(menu), item);
1952
1953         item = gtk_menu_item_new();
1954         gtk_widget_show(item);
1955         gtk_menu_append(GTK_MENU(menu), item);
1956
1957         submenu = gtk_menu_new();
1958         item = gtk_menu_item_new_with_label(_("Change dictionary"));
1959         gtk_menu_item_set_submenu(GTK_MENU_ITEM(item),submenu);
1960         gtk_widget_show(item);
1961         gtk_menu_append(GTK_MENU(menu), item);
1962
1963         /* Dict list */
1964         if (gtkpspellcheckers->dictionary_list == NULL)
1965                 gtkpspell_get_dictionary_list(prefs_common.pspell_path, FALSE);
1966         {
1967                 GtkWidget * curmenu = submenu;
1968                 int count = 0;
1969                 Dictionary *dict;
1970                 GSList *tmp;
1971                 tmp = gtkpspellcheckers->dictionary_list;
1972                 
1973                 for (tmp = gtkpspellcheckers->dictionary_list; tmp != NULL; 
1974                                 tmp = g_slist_next(tmp)) {
1975                         dict = (Dictionary *) tmp->data;
1976                         item = gtk_check_menu_item_new_with_label(dict->dictname);
1977                         gtk_object_set_data(GTK_OBJECT(item), "dict_name",
1978                                             dict->fullname); 
1979                         if (strcmp2(dict->fullname,
1980                             gtkpspell->gtkpspeller->dictionary->fullname))
1981                                 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(item), FALSE);
1982                         else {
1983                                 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(item), TRUE);
1984                                 gtk_widget_set_sensitive(GTK_WIDGET(item),
1985                                                          FALSE);
1986                         }
1987                         gtk_signal_connect(GTK_OBJECT(item), "activate",
1988                                            GTK_SIGNAL_FUNC(change_dict_cb),
1989                                            gtkpspell);
1990                         gtk_widget_show(item);
1991                         gtk_menu_append(GTK_MENU(curmenu), item);
1992                         
1993                         count++;
1994                         
1995                         if (count == MENUCOUNT) {
1996                                 GtkWidget *newmenu;
1997                                 
1998                                 newmenu = gtk_menu_new();
1999                                 item = gtk_menu_item_new_with_label(_("More..."));
2000                                 gtk_menu_item_set_submenu(GTK_MENU_ITEM(item), 
2001                                                           newmenu);
2002                                 
2003                                 gtk_menu_append(GTK_MENU(curmenu), item);
2004                                 gtk_widget_show(item);
2005                                 curmenu = newmenu;
2006                                 count = 0;
2007                         }
2008                 }
2009         }  
2010 }
2011
2012 static GtkMenu *make_config_menu(GtkPspell *gtkpspell)
2013 {
2014         if (!gtkpspell->popup_config_menu)
2015                 gtkpspell->popup_config_menu = gtk_menu_new();
2016
2017         debug_print("Pspell: creating/using popup_config_menu %0x\n", 
2018                         (guint) gtkpspell->popup_config_menu);
2019         populate_submenu(gtkpspell, gtkpspell->popup_config_menu);
2020
2021         return GTK_MENU(gtkpspell->popup_config_menu);
2022 }
2023
2024 void gtkpspell_populate_submenu(GtkPspell *gtkpspell, GtkWidget *menuitem)
2025 {
2026         GtkWidget *menu;
2027
2028         menu = GTK_WIDGET(GTK_MENU_ITEM(menuitem)->submenu);
2029         
2030         debug_print("Pspell: using config menu %0x\n", 
2031                         (guint) gtkpspell->popup_config_menu);
2032         populate_submenu(gtkpspell, menu);
2033         
2034         gtkpspell->config_menu = menu;
2035         
2036 }
2037
2038 static void set_menu_pos(GtkMenu *menu, gint *x, gint *y, gpointer data)
2039 {
2040         GtkPspell       *gtkpspell = (GtkPspell *) data;
2041         gint             xx = 0, yy = 0;
2042         gint             sx,     sy;
2043         gint             wx,     wy;
2044         GtkSText        *text = GTK_STEXT(gtkpspell->gtktext);
2045         GtkRequisition   r;
2046
2047         gdk_window_get_origin(GTK_WIDGET(gtkpspell->gtktext)->window, &xx, &yy);
2048         
2049         sx = gdk_screen_width();
2050         sy = gdk_screen_height();
2051         
2052         gtk_widget_get_child_requisition(GTK_WIDGET(menu), &r);
2053         
2054         wx =  r.width;
2055         wy =  r.height;
2056         
2057         *x = gtkpspell->gtktext->cursor_pos_x + xx +
2058              gdk_char_width(GTK_WIDGET(text)->style->font, ' ');
2059         *y = gtkpspell->gtktext->cursor_pos_y + yy;
2060
2061         if (*x + wx > sx)
2062                 *x = sx - wx;
2063         if (*y + wy > sy)
2064                 *y = *y - wy - 
2065                      gdk_string_height((GTK_WIDGET(gtkpspell->gtktext))->style->font, 
2066                                        gtkpspell->theword);
2067
2068 }
2069
2070 /* Menu call backs */
2071
2072 static gboolean cancel_menu_cb(GtkMenuShell *w, gpointer data)
2073 {
2074         GtkPspell *gtkpspell = (GtkPspell *) data;
2075
2076         gtkpspell->continue_check = NULL;
2077         set_point_continue(gtkpspell);
2078
2079         return FALSE;
2080         
2081 }
2082
2083 /* change_dict_cb() - Menu callback : change dict */
2084 static void change_dict_cb(GtkWidget *w, GtkPspell *gtkpspell)
2085 {
2086         Dictionary      *dict;       
2087         gchar           *fullname;
2088         GtkPspeller     *gtkpspeller;
2089         gint             sug_mode;
2090   
2091         fullname = (gchar *) gtk_object_get_data(GTK_OBJECT(w), "dict_name");
2092         
2093         if (!strcmp2(fullname, _("None")))
2094                 return;
2095
2096         sug_mode  = gtkpspell->default_sug_mode;
2097
2098         dict = g_new0(Dictionary, 1);
2099         dict->fullname = g_strdup(fullname);
2100         dict->encoding = g_strdup(gtkpspell->gtkpspeller->dictionary->encoding);
2101
2102         if (gtkpspell->use_alternate && gtkpspell->alternate_speller &&
2103             dict == gtkpspell->alternate_speller->dictionary) {
2104                 use_alternate_dict(gtkpspell);
2105                 dictionary_delete(dict);
2106                 return;
2107         }
2108         
2109         gtkpspeller = gtkpspeller_new(dict);
2110
2111         if (!gtkpspeller) {
2112                 gchar *message;
2113                 message = g_strdup_printf(_("The spell checker could not change dictionary.\n%s"), 
2114                                           gtkpspellcheckers->error_message);
2115
2116                 gtkpspell_alert_dialog(message); 
2117                 g_free(message);
2118         } else {
2119                 if (gtkpspell->use_alternate) {
2120                         if (gtkpspell->alternate_speller)
2121                                 gtkpspeller_delete(gtkpspell->alternate_speller);
2122                         gtkpspell->alternate_speller = gtkpspell->gtkpspeller;
2123                 } else
2124                         gtkpspeller_delete(gtkpspell->gtkpspeller);
2125
2126                 gtkpspell->gtkpspeller = gtkpspeller;
2127                 gtkpspell_set_sug_mode(gtkpspell, sug_mode);
2128         }
2129         
2130         dictionary_delete(dict);
2131
2132         if (gtkpspell->config_menu)
2133                 populate_submenu(gtkpspell, gtkpspell->config_menu);
2134 }
2135
2136 static void switch_to_alternate_cb(GtkWidget *w,
2137                                    gpointer data)
2138 {
2139         GtkPspell *gtkpspell = (GtkPspell *) data;
2140         use_alternate_dict(gtkpspell);
2141 }
2142
2143 /* Misc. helper functions */
2144
2145 static void set_point_continue(GtkPspell *gtkpspell)
2146 {
2147         GtkSText  *gtktext;
2148
2149         gtktext = gtkpspell->gtktext;
2150
2151         gtk_stext_freeze(gtktext);
2152         gtk_editable_set_position(GTK_EDITABLE(gtktext),gtkpspell->orig_pos);
2153         gtk_stext_set_point(gtktext, gtkpspell->orig_pos);
2154         gtk_stext_thaw(gtktext);
2155
2156         if (gtkpspell->continue_check)
2157                 gtkpspell->continue_check((gpointer *) gtkpspell);
2158 }
2159
2160 static void allocate_color(GtkPspell *gtkpspell, gint rgbvalue)
2161 {
2162         GdkColormap *gc;
2163         GdkColor *color = &(gtkpspell->highlight);
2164
2165         gc = gtk_widget_get_colormap(GTK_WIDGET(gtkpspell->gtktext));
2166
2167         if (gtkpspell->highlight.pixel)
2168                 gdk_colormap_free_colors(gc, &(gtkpspell->highlight), 1);
2169
2170         /* Shameless copy from Sylpheed's gtkutils.c */
2171         color->pixel = 0L;
2172         color->red   = (int) (((gdouble)((rgbvalue & 0xff0000) >> 16) / 255.0)
2173                         * 65535.0);
2174         color->green = (int) (((gdouble)((rgbvalue & 0x00ff00) >>  8) / 255.0)
2175                         * 65535.0);
2176         color->blue  = (int) (((gdouble) (rgbvalue & 0x0000ff)        / 255.0)
2177                         * 65535.0);
2178
2179         gdk_colormap_alloc_color(gc, &(gtkpspell->highlight), FALSE, TRUE);
2180 }
2181
2182 static void change_color(GtkPspell * gtkpspell, 
2183                          gint start, gint end,
2184                          gchar *newtext,
2185                          GdkColor *color) 
2186 {
2187         GtkSText *gtktext;
2188
2189         g_return_if_fail(start < end);
2190     
2191         gtktext = gtkpspell->gtktext;
2192     
2193         gtk_stext_freeze(gtktext);
2194         if (newtext) {
2195                 gtk_stext_set_point(gtktext, start);
2196                 gtk_stext_forward_delete(gtktext, end - start);
2197                 gtk_stext_insert(gtktext, NULL, color, NULL, newtext,
2198                                  end - start);
2199         }
2200         gtk_stext_thaw(gtktext);
2201 }
2202
2203 /* convert_to_pspell_encoding () - converts ISO-8859-* strings to iso8859-* 
2204  * as needed by pspell. Returns an allocated string.
2205  */
2206
2207 static guchar *convert_to_pspell_encoding (const guchar *encoding)
2208 {
2209         guchar * pspell_encoding;
2210
2211         if (strstr2(encoding, "ISO-8859-")) {
2212                 pspell_encoding = g_strdup_printf("iso8859%s", encoding + 8);
2213         }
2214         else {
2215                 if (!strcmp2(encoding, "US-ASCII"))
2216                         pspell_encoding = g_strdup("iso8859-1");
2217                 else
2218                         pspell_encoding = g_strdup(encoding);
2219         }
2220
2221         return pspell_encoding;
2222 }
2223
2224 /* compare_dict () - compare 2 dict names */
2225 static gint compare_dict(Dictionary *a, Dictionary *b)
2226 {
2227         guint   aparts = 0,  bparts = 0;
2228         guint   i;
2229
2230         for (i=0; i < strlen(a->dictname); i++)
2231                 if (a->dictname[i] == '-')
2232                         aparts++;
2233         for (i=0; i < strlen(b->dictname); i++)
2234                 if (b->dictname[i] == '-')
2235                         bparts++;
2236
2237         if (aparts != bparts) 
2238                 return (aparts < bparts) ? -1 : +1;
2239         else {
2240                 gint compare;
2241                 compare = strcmp2(a->dictname, b->dictname);
2242                 if (!compare)
2243                         compare = strcmp2(a->fullname, b->fullname);
2244                 return compare;
2245         }
2246 }
2247
2248
2249 static void dictionary_delete(Dictionary *dict)
2250 {
2251         g_free(dict->fullname);
2252         g_free(dict->encoding);
2253         g_free(dict);
2254 }
2255
2256 static Dictionary *dictionary_dup(const Dictionary *dict)
2257 {
2258         Dictionary *dict2;
2259
2260         dict2 = g_new(Dictionary, 1); 
2261
2262         dict2->fullname = g_strdup(dict->fullname);
2263         dict2->dictname = dict->dictname - dict->fullname + dict2->fullname;
2264         dict2->encoding = g_strdup(dict->encoding);
2265
2266         return dict2;
2267 }
2268
2269 static void free_suggestions_list(GtkPspell *gtkpspell)
2270 {
2271         GList *list;
2272
2273         for (list = gtkpspell->suggestions_list; list != NULL;
2274              list = list->next)
2275                 g_free(list->data);
2276
2277         g_list_free(list);
2278         
2279         gtkpspell->max_sug          = -1;
2280         gtkpspell->suggestions_list = NULL;
2281 }
2282
2283 static void reset_theword_data(GtkPspell *gtkpspell)
2284 {
2285         gtkpspell->start_pos     =  0;
2286         gtkpspell->end_pos       =  0;
2287         gtkpspell->theword[0]    =  0;
2288         gtkpspell->max_sug       = -1;
2289
2290         free_suggestions_list(gtkpspell);
2291 }
2292
2293 static void free_checkers(gpointer elt, gpointer data)
2294 {
2295         GtkPspeller *gtkpspeller = elt;
2296
2297         g_return_if_fail(gtkpspeller);
2298
2299         gtkpspeller_real_delete(gtkpspeller);
2300 }
2301
2302 static gint find_gtkpspeller(gconstpointer aa, gconstpointer bb)
2303 {
2304         Dictionary *a = ((GtkPspeller *) aa)->dictionary;
2305         Dictionary *b = ((GtkPspeller *) bb)->dictionary;
2306
2307         if (a && b && a->fullname && b->fullname  &&
2308             strcmp(a->fullname, b->fullname) == 0 &&
2309             a->encoding && b->encoding)
2310                 return strcmp(a->encoding, b->encoding);
2311
2312         return 1;
2313 }
2314
2315 static void gtkpspell_alert_dialog(gchar *message)
2316 {
2317         GtkWidget *dialog;
2318         GtkWidget *hbox;
2319         GtkWidget *label;
2320         GtkWidget *ok_button;
2321
2322         dialog = gtk_dialog_new();
2323         gtk_window_set_policy(GTK_WINDOW(dialog), FALSE, FALSE, FALSE);
2324         gtk_window_set_position(GTK_WINDOW(dialog),GTK_WIN_POS_MOUSE);
2325         gtk_signal_connect_object(GTK_OBJECT(dialog), "destroy",
2326                                    GTK_SIGNAL_FUNC(gtk_widget_destroy), 
2327                                    GTK_OBJECT(dialog));
2328
2329         label  = gtk_label_new(message);
2330         gtk_misc_set_padding(GTK_MISC(label), 8, 8);
2331
2332         gtk_container_add(GTK_CONTAINER(GTK_DIALOG(dialog)->vbox), label);
2333         
2334         hbox = gtk_hbox_new(FALSE, 0);
2335
2336         ok_button = gtk_button_new_with_label(_("OK"));
2337         GTK_WIDGET_SET_FLAGS(ok_button, GTK_CAN_DEFAULT);
2338         gtk_box_pack_start(GTK_BOX(hbox), ok_button, TRUE, TRUE, 8);    
2339
2340         gtk_signal_connect_object(GTK_OBJECT(ok_button), "clicked",
2341                                    GTK_SIGNAL_FUNC(gtk_widget_destroy), 
2342                                    GTK_OBJECT(dialog));
2343         gtk_container_add(GTK_CONTAINER(GTK_DIALOG(dialog)->action_area), hbox);
2344                         
2345         gtk_widget_grab_default(ok_button);
2346         gtk_widget_grab_focus(ok_button);
2347         gtk_window_set_modal(GTK_WINDOW(dialog), TRUE);
2348
2349         gtk_widget_show_all(dialog);
2350 }
2351 #endif