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