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