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