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