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