fd146df6a4597510bec37fb213c53203e55135ab
[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 *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         thelabel = g_strdup_printf(_("<span weight=\"bold\" "
1553                                         "size=\"larger\">Replace \"%s\" with: </span>"), 
1554                                    gtkaspell->theword);
1555         /* for title label */
1556         w_hbox = gtk_hbox_new(FALSE, 0);
1557         
1558         icon = gtk_image_new_from_stock(GTK_STOCK_DIALOG_QUESTION,
1559                                         GTK_ICON_SIZE_DIALOG); 
1560         gtk_misc_set_alignment (GTK_MISC (icon), 0.5, 0.0);
1561         gtk_box_pack_start (GTK_BOX (hbox), icon, FALSE, FALSE, 0);
1562         
1563         vbox = gtk_vbox_new (FALSE, 12);
1564         gtk_box_pack_start (GTK_BOX (hbox), vbox, TRUE, TRUE, 0);
1565         gtk_widget_show (vbox);
1566         
1567         label = gtk_label_new(thelabel);
1568         gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
1569         gtk_label_set_justify(GTK_LABEL(label), GTK_JUSTIFY_LEFT);
1570         gtk_label_set_use_markup (GTK_LABEL (label), TRUE);
1571         gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 0);
1572         gtk_label_set_line_wrap(GTK_LABEL(label), TRUE);
1573         if (!font_desc) {
1574                 gint size;
1575
1576                 size = pango_font_description_get_size
1577                         (label->style->font_desc);
1578                 font_desc = pango_font_description_new();
1579                 pango_font_description_set_weight
1580                         (font_desc, PANGO_WEIGHT_BOLD);
1581                 pango_font_description_set_size
1582                         (font_desc, size * PANGO_SCALE_LARGE);
1583         }
1584         if (font_desc)
1585                 gtk_widget_modify_font(label, font_desc);
1586         g_free(thelabel);
1587         
1588         entry = gtk_entry_new();
1589         gtkaspell->replace_entry = entry;
1590         gtk_entry_set_text(GTK_ENTRY(entry), gtkaspell->theword);
1591         gtk_editable_select_region(GTK_EDITABLE(entry), 0, -1);
1592         g_signal_connect(G_OBJECT(dialog),
1593                         "key_press_event",
1594                         G_CALLBACK(replace_key_pressed), gtkaspell);
1595         gtk_box_pack_start(GTK_BOX(vbox), entry, FALSE, FALSE, 0);
1596
1597         label = gtk_label_new(_("Holding down Control key while pressing "
1598                                 "Enter\nwill learn from mistake.\n"));
1599         gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
1600         gtk_label_set_justify(GTK_LABEL(label), GTK_JUSTIFY_LEFT);
1601         gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 0);
1602         gtk_widget_show(label);
1603
1604         hbox = gtk_hbox_new(TRUE, 0);
1605
1606         gtkut_stock_button_set_create(&confirm_area,
1607                                       &cancel_button, GTK_STOCK_CANCEL,
1608                                       &ok_button, GTK_STOCK_OK,
1609                                       NULL, NULL);
1610
1611         gtk_box_pack_end(GTK_BOX(GTK_DIALOG(dialog)->action_area),
1612                          confirm_area, FALSE, FALSE, 0);
1613         gtk_container_set_border_width(GTK_CONTAINER(confirm_area), 5);
1614
1615         g_signal_connect(G_OBJECT(ok_button), "clicked",
1616                          G_CALLBACK(replace_with_supplied_word_cb), 
1617                          gtkaspell);
1618         g_signal_connect_swapped(G_OBJECT(ok_button), "clicked",
1619                                    G_CALLBACK(gtk_widget_destroy), 
1620                                    G_OBJECT(dialog));
1621
1622         g_signal_connect_swapped(G_OBJECT(cancel_button), "clicked",
1623                                  G_CALLBACK(gtk_widget_destroy), 
1624                                  G_OBJECT(dialog));
1625
1626         gtk_widget_grab_focus(entry);
1627
1628         gtk_window_set_modal(GTK_WINDOW(dialog), TRUE);
1629
1630         gtk_widget_show_all(dialog);
1631 }
1632
1633 static void gtkaspell_uncheck_all(GtkAspell * gtkaspell) 
1634 {
1635         GtkTextView *gtktext;
1636         GtkTextBuffer *buffer;
1637         GtkTextIter startiter, enditer;
1638         
1639         gtktext = gtkaspell->gtktext;
1640
1641         buffer = gtk_text_view_get_buffer(gtktext);
1642         gtk_text_buffer_get_iter_at_offset(buffer, &startiter, 0);
1643         gtk_text_buffer_get_iter_at_offset(buffer, &enditer,
1644                                    get_textview_buffer_charcount(gtktext)-1);
1645         gtk_text_buffer_remove_tag_by_name(buffer, "misspelled",
1646                                            &startiter, &enditer);
1647 }
1648
1649 static void toggle_check_while_typing_cb(GtkWidget *w, gpointer data)
1650 {
1651         GtkAspell *gtkaspell = (GtkAspell *) data;
1652
1653         gtkaspell->check_while_typing = gtkaspell->check_while_typing == FALSE;
1654
1655         if (!gtkaspell->check_while_typing)
1656                 gtkaspell_uncheck_all(gtkaspell);
1657         if (gtkaspell->menu_changed_cb)
1658                 gtkaspell->menu_changed_cb(gtkaspell->menu_changed_data);
1659 }
1660
1661 static GSList *create_empty_dictionary_list(void)
1662 {
1663         GSList *list = NULL;
1664         Dictionary *dict;
1665
1666         dict = g_new0(Dictionary, 1);
1667         dict->fullname = g_strdup(_("None"));
1668         dict->dictname = dict->fullname;
1669         dict->encoding = NULL;
1670
1671         return g_slist_append(list, dict);
1672 }
1673
1674 /* gtkaspell_get_dictionary_list() - returns list of dictionary names */
1675 static GSList *gtkaspell_get_dictionary_list(const gchar *aspell_path, gint refresh)
1676 {
1677         GSList *list;
1678         Dictionary *dict;
1679         AspellConfig *config;
1680         AspellDictInfoList *dlist;
1681         AspellDictInfoEnumeration *dels;
1682         const AspellDictInfo *entry;
1683
1684         if (!gtkaspellcheckers)
1685                 gtkaspell_checkers_init();
1686
1687         if (gtkaspellcheckers->dictionary_list && !refresh)
1688                 return gtkaspellcheckers->dictionary_list;
1689         else
1690                 gtkaspell_free_dictionary_list(
1691                                 gtkaspellcheckers->dictionary_list);
1692         list = NULL;
1693
1694         config = new_aspell_config();
1695
1696         aspell_config_replace(config, "dict-dir", aspell_path);
1697         if (aspell_config_error_number(config) != 0) {
1698                 gtkaspellcheckers->error_message = g_strdup(
1699                                 aspell_config_error_message(config));
1700                 gtkaspellcheckers->dictionary_list =
1701                         create_empty_dictionary_list();
1702
1703                 return gtkaspellcheckers->dictionary_list; 
1704         }
1705
1706         dlist = get_aspell_dict_info_list(config);
1707         delete_aspell_config(config);
1708
1709         debug_print("Aspell: checking for dictionaries in %s\n", aspell_path?aspell_path:"(null)");
1710         dels = aspell_dict_info_list_elements(dlist);
1711         while ( (entry = aspell_dict_info_enumeration_next(dels)) != 0) 
1712         {
1713                 dict = g_new0(Dictionary, 1);
1714                 dict->fullname = g_strdup_printf("%s%s", aspell_path, 
1715                                 entry->name);
1716                 dict->dictname = dict->fullname + strlen(aspell_path);
1717                 dict->encoding = g_strdup(entry->code);
1718                 
1719                 if (g_slist_find_custom(list, dict, 
1720                                 (GCompareFunc) compare_dict) != NULL) {
1721                         dictionary_delete(dict);
1722                         continue;       
1723                 }
1724                 
1725                 debug_print("Aspell: found dictionary %s %s %s\n", dict->fullname,
1726                                 dict->dictname, dict->encoding);
1727                 list = g_slist_insert_sorted(list, dict,
1728                                 (GCompareFunc) compare_dict);
1729         }
1730
1731         delete_aspell_dict_info_enumeration(dels);
1732         
1733         if(list==NULL){
1734                 
1735                 debug_print("Aspell: error when searching for dictionaries: "
1736                               "No dictionary found.\n");
1737                 list = create_empty_dictionary_list();
1738         }
1739
1740         gtkaspellcheckers->dictionary_list = list;
1741
1742         return list;
1743 }
1744
1745 static void gtkaspell_free_dictionary_list(GSList *list)
1746 {
1747         Dictionary *dict;
1748         GSList *walk;
1749         for (walk = list; walk != NULL; walk = g_slist_next(walk))
1750                 if (walk->data) {
1751                         dict = (Dictionary *) walk->data;
1752                         dictionary_delete(dict);
1753                 }                               
1754         g_slist_free(list);
1755 }
1756
1757 GtkTreeModel *gtkaspell_dictionary_store_new_with_refresh(const gchar *aspell_path,
1758                                                              gboolean refresh)
1759 {
1760         GSList *dict_list, *tmp;
1761         GtkListStore *store;
1762         GtkTreeIter iter;
1763         Dictionary *dict;
1764
1765         dict_list = gtkaspell_get_dictionary_list(aspell_path, refresh);
1766         g_return_val_if_fail(dict_list, NULL);
1767
1768         store = gtk_list_store_new(SET_GTKASPELL_SIZE,
1769                                    G_TYPE_STRING,
1770                                    G_TYPE_STRING,
1771                                    -1);
1772         
1773         for (tmp = dict_list; tmp != NULL; tmp = g_slist_next(tmp)) {
1774                 dict = (Dictionary *) tmp->data;
1775                 
1776                 gtk_list_store_append(store, &iter);
1777                 gtk_list_store_set(store, &iter,
1778                                    SET_GTKASPELL_NAME, dict->dictname,
1779                                    SET_GTKASPELL_FULLNAME, dict->fullname,
1780                                    -1);
1781         }
1782
1783         return GTK_TREE_MODEL(store);
1784 }
1785
1786 GtkTreeModel *gtkaspell_dictionary_store_new(const gchar *aspell_path)
1787 {
1788         return gtkaspell_dictionary_store_new_with_refresh
1789                 (aspell_path, TRUE);
1790 }
1791
1792 GtkWidget *gtkaspell_dictionary_combo_new(const gchar *aspell_path,
1793                                           const gboolean refresh)
1794 {
1795         GtkWidget *combo;
1796         GtkCellRenderer *renderer;
1797
1798         combo = gtk_combo_box_new_with_model(
1799                         gtkaspell_dictionary_store_new_with_refresh(aspell_path, refresh));
1800         gtk_combo_box_set_active(GTK_COMBO_BOX(combo), 0);   
1801         gtk_widget_show(combo);
1802         
1803         renderer = gtk_cell_renderer_text_new();
1804         gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(combo), renderer, TRUE);
1805         gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(combo),renderer,
1806                                         "text", SET_GTKASPELL_NAME, NULL);
1807         
1808         return combo;
1809
1810
1811 gchar *gtkaspell_get_dictionary_menu_active_item(GtkComboBox *combo)
1812 {
1813         GtkTreeIter iter;
1814         GtkTreeModel *model;
1815         gchar *dict_fullname = NULL;
1816         
1817         g_return_val_if_fail(GTK_IS_COMBO_BOX(combo), NULL);
1818         g_return_val_if_fail(gtk_combo_box_get_active_iter(combo, &iter), NULL);
1819         
1820         model = gtk_combo_box_get_model(combo);
1821         if(model == NULL)
1822                 return NULL;
1823         
1824         gtk_tree_model_get(model, &iter,
1825                            SET_GTKASPELL_FULLNAME, &dict_fullname,
1826                            -1);
1827
1828         return dict_fullname;
1829 }
1830
1831 gint gtkaspell_set_dictionary_menu_active_item(GtkComboBox *combo,
1832                                                const gchar *dictionary)
1833 {
1834         GtkTreeModel *model;
1835         GtkTreeIter iter;
1836         gchar *dict_name = NULL;
1837         
1838         g_return_val_if_fail(combo != NULL, 0);
1839         g_return_val_if_fail(dictionary != NULL, 0);
1840         g_return_val_if_fail(GTK_IS_COMBO_BOX(combo), 0);
1841
1842         if((model = gtk_combo_box_get_model(combo)) == NULL)
1843                 return 0;
1844         if((gtk_tree_model_get_iter_first(model, &iter)) == FALSE)
1845                 return 0;
1846         
1847         do {
1848                 gtk_tree_model_get(model, &iter,
1849                                    SET_GTKASPELL_FULLNAME, &dict_name,
1850                                    -1);
1851                 
1852                 if ((dict_name != NULL) && !strcmp2(dict_name, dictionary)) {
1853                         gtk_combo_box_set_active_iter(combo, &iter);
1854                         g_free(dict_name);
1855                         return 1;
1856                 }
1857                 
1858                 g_free(dict_name);
1859                 
1860         } while ((gtk_tree_model_iter_next(model, &iter)) == TRUE);
1861         
1862         return 0;
1863 }
1864
1865 GtkWidget *gtkaspell_sugmode_combo_new(gint sugmode)
1866 {
1867         GtkWidget *combo = gtkut_sc_combobox_create(NULL, FALSE);
1868         GtkListStore *store = GTK_LIST_STORE(gtk_combo_box_get_model(
1869                                                 GTK_COMBO_BOX(combo)));
1870         GtkTreeIter iter;
1871         
1872         g_return_val_if_fail(store != NULL, NULL);
1873         
1874         COMBOBOX_ADD(store, _("Fast Mode"), ASPELL_FASTMODE);
1875         COMBOBOX_ADD(store, _("Normal Mode"), ASPELL_NORMALMODE);
1876         COMBOBOX_ADD(store, _("Bad Spellers Mode"), ASPELL_BADSPELLERMODE);
1877
1878         gtk_combo_box_set_active(GTK_COMBO_BOX(combo), sugmode - 1);
1879         gtk_widget_show(combo);
1880         
1881         return combo;
1882 }
1883
1884 static void use_alternate_dict(GtkAspell *gtkaspell)
1885 {
1886         GtkAspeller *tmp;
1887
1888         tmp = gtkaspell->gtkaspeller;
1889         gtkaspell->gtkaspeller = gtkaspell->alternate_speller;
1890         gtkaspell->alternate_speller = tmp;
1891 }
1892
1893 static void destroy_menu(GtkWidget *widget,
1894                              gpointer user_data) {
1895
1896         GtkAspell *gtkaspell = (GtkAspell *)user_data;
1897
1898         if (gtkaspell->accel_group) {
1899                 gtk_window_remove_accel_group(GTK_WINDOW(gtkaspell->parent_window), 
1900                                 gtkaspell->accel_group);
1901                 gtkaspell->accel_group = NULL;
1902         }
1903 }
1904
1905 static gboolean aspell_key_pressed(GtkWidget *widget,
1906                                    GdkEventKey *event,
1907                                    GtkAspell *gtkaspell)
1908 {
1909         if (event && (isascii(event->keyval) || event->keyval == GDK_Return)) {
1910                 gtk_accel_groups_activate(
1911                                 G_OBJECT(gtkaspell->parent_window),
1912                                 event->keyval, event->state);
1913         } else if (event && event->keyval == GDK_Escape) {
1914                 destroy_menu(NULL, gtkaspell);
1915         }
1916         return FALSE;
1917 }
1918
1919 /* make_sug_menu() - Add menus to accept this word for this session 
1920  * and to add it to personal dictionary 
1921  */
1922 static GSList *make_sug_menu(GtkAspell *gtkaspell) 
1923 {
1924         GtkWidget       *item;
1925         char    *caption;
1926         GtkTextView     *gtktext;
1927         GtkAccelGroup   *accel;
1928         GList           *l = gtkaspell->suggestions_list;
1929         gchar           *utf8buf;
1930         GSList *list = NULL;
1931         gtktext = gtkaspell->gtktext;
1932
1933         accel = gtk_accel_group_new();
1934
1935         if (gtkaspell->accel_group) {
1936                 gtk_window_remove_accel_group(GTK_WINDOW(gtkaspell->parent_window), 
1937                                 gtkaspell->accel_group);
1938                 gtkaspell->accel_group = NULL;
1939         }
1940
1941         utf8buf  = conv_codeset_strdup((char*)l->data,
1942                                 conv_get_locale_charset_str(),
1943                                 CS_UTF_8);
1944         caption = g_strdup_printf(_("\"%s\" unknown in %s"), 
1945                                   utf8buf, 
1946                                   gtkaspell->gtkaspeller->dictionary->dictname);
1947         item = gtk_menu_item_new_with_label(caption);
1948         g_free(utf8buf);
1949         gtk_widget_show(item);
1950         list = g_slist_append(list, item);
1951         gtk_misc_set_alignment(GTK_MISC(GTK_BIN(item)->child), 0.5, 0.5);
1952         g_free(caption);
1953
1954         item = gtk_menu_item_new();
1955         gtk_widget_show(item);
1956         list = g_slist_append(list, item);
1957
1958         item = gtk_menu_item_new_with_label(_("Accept in this session"));
1959         gtk_widget_show(item);
1960         list = g_slist_append(list, item);
1961         g_signal_connect(G_OBJECT(item), "activate",
1962                          G_CALLBACK(add_word_to_session_cb), 
1963                          gtkaspell);
1964         gtk_widget_add_accelerator(item, "activate", accel, GDK_space,
1965                                    GDK_CONTROL_MASK,
1966                                    GTK_ACCEL_LOCKED | GTK_ACCEL_VISIBLE);
1967
1968         item = gtk_menu_item_new_with_label(_("Add to personal dictionary"));
1969         gtk_widget_show(item);
1970         list = g_slist_append(list, item);
1971         g_signal_connect(G_OBJECT(item), "activate",
1972                          G_CALLBACK(add_word_to_personal_cb), 
1973                          gtkaspell);
1974         gtk_widget_add_accelerator(item, "activate", accel, GDK_Return,
1975                                    GDK_CONTROL_MASK,
1976                                    GTK_ACCEL_LOCKED | GTK_ACCEL_VISIBLE);
1977
1978         item = gtk_menu_item_new_with_label(_("Replace with..."));
1979         gtk_widget_show(item);
1980         list = g_slist_append(list, item);
1981         g_signal_connect(G_OBJECT(item), "activate",
1982                          G_CALLBACK(replace_with_create_dialog_cb), 
1983                          gtkaspell);
1984         gtk_widget_add_accelerator(item, "activate", accel, GDK_R, 0,
1985                                    GTK_ACCEL_LOCKED | GTK_ACCEL_VISIBLE);
1986         gtk_widget_add_accelerator(item, "activate", accel, GDK_R, 
1987                                    GDK_CONTROL_MASK,
1988                                    GTK_ACCEL_LOCKED);
1989
1990         if (gtkaspell->use_alternate && gtkaspell->alternate_speller) {
1991                 caption = g_strdup_printf(_("Check with %s"), 
1992                         gtkaspell->alternate_speller->dictionary->dictname);
1993                 item = gtk_menu_item_new_with_label(caption);
1994                 g_free(caption);
1995                 gtk_widget_show(item);
1996                 list = g_slist_append(list, item);
1997                 g_signal_connect(G_OBJECT(item), "activate",
1998                                  G_CALLBACK(check_with_alternate_cb),
1999                                  gtkaspell);
2000                 gtk_widget_add_accelerator(item, "activate", accel, GDK_X, 0,
2001                                            GTK_ACCEL_LOCKED | GTK_ACCEL_VISIBLE);
2002                 gtk_widget_add_accelerator(item, "activate", accel, GDK_X, 
2003                                            GDK_CONTROL_MASK,
2004                                            GTK_ACCEL_LOCKED);
2005         }
2006
2007         item = gtk_menu_item_new();
2008         gtk_widget_show(item);
2009         list = g_slist_append(list, item);
2010
2011         l = l->next;
2012         if (l == NULL) {
2013                 item = gtk_menu_item_new_with_label(_("(no suggestions)"));
2014                 gtk_widget_show(item);
2015                 list = g_slist_append(list, item);
2016         } else {
2017                 GtkWidget *curmenu = NULL;
2018                 gint count = 0;
2019                 
2020                 do {
2021                         if (count == MENUCOUNT) {
2022                                 count -= MENUCOUNT;
2023
2024                                 item = gtk_menu_item_new_with_label(_("More..."));
2025                                 gtk_widget_show(item);
2026                                 if (curmenu)
2027                                         gtk_menu_append(GTK_MENU(curmenu), item);
2028                                 else 
2029                                         list = g_slist_append(list, item);
2030
2031                                 curmenu = gtk_menu_new();
2032                                 gtk_menu_item_set_submenu(GTK_MENU_ITEM(item),
2033                                                           curmenu);
2034                         }
2035
2036                         utf8buf  = conv_codeset_strdup((char*)l->data,
2037                                                         conv_get_locale_charset_str(),
2038                                                         CS_UTF_8);
2039                         item = gtk_menu_item_new_with_label(utf8buf);
2040                         g_free(utf8buf);
2041                         gtk_widget_show(item);
2042                         if (curmenu == NULL) {
2043                                 list = g_slist_append(list, item);
2044                         } else {
2045                                 gtk_menu_append(GTK_MENU(curmenu), item);
2046                         }
2047                         g_signal_connect(G_OBJECT(item), "activate",
2048                                          G_CALLBACK(replace_word_cb),
2049                                          gtkaspell);
2050
2051                         if (curmenu == NULL && count < MENUCOUNT) {
2052                                 gtk_widget_add_accelerator(item, "activate",
2053                                                            accel,
2054                                                            GDK_A + count, 0,
2055                                                            GTK_ACCEL_LOCKED | 
2056                                                            GTK_ACCEL_VISIBLE);
2057                                 gtk_widget_add_accelerator(item, "activate", 
2058                                                            accel,
2059                                                            GDK_A + count, 
2060                                                            GDK_CONTROL_MASK,
2061                                                            GTK_ACCEL_LOCKED);
2062                                 }
2063
2064                         count++;
2065
2066                 } while ((l = l->next) != NULL);
2067         }
2068
2069         gtk_window_add_accel_group
2070                 (GTK_WINDOW(gtkaspell->parent_window),
2071                  accel);
2072         gtkaspell->accel_group = accel;
2073
2074         return list;
2075 }
2076
2077 static GSList *populate_submenu(GtkAspell *gtkaspell)
2078 {
2079         GtkWidget *item, *submenu;
2080         gchar *dictname;
2081         GtkAspeller *gtkaspeller = NULL;
2082         GSList *list = NULL;
2083
2084         if (!gtkaspell)
2085                 return NULL;
2086
2087         gtkaspeller = gtkaspell->gtkaspeller;
2088         dictname = g_strdup_printf(_("Dictionary: %s"),
2089                                    gtkaspeller->dictionary->dictname);
2090         item = gtk_menu_item_new_with_label(dictname);
2091         gtk_misc_set_alignment(GTK_MISC(GTK_BIN(item)->child), 0.5, 0.5);
2092         g_free(dictname);
2093         gtk_widget_show(item);
2094         list = g_slist_append(list, item);
2095
2096         item = gtk_menu_item_new();
2097         gtk_widget_show(item);
2098         list = g_slist_append(list, item);
2099                 
2100         if (gtkaspell->use_alternate && gtkaspell->alternate_speller) {
2101                 dictname = g_strdup_printf(_("Use alternate (%s)"), 
2102                                 gtkaspell->alternate_speller->dictionary->dictname);
2103                 item = gtk_menu_item_new_with_label(dictname);
2104                 g_free(dictname);
2105                 g_signal_connect(G_OBJECT(item), "activate",
2106                                  G_CALLBACK(switch_to_alternate_cb),
2107                                  gtkaspell);
2108                 gtk_widget_show(item);
2109                 list = g_slist_append(list, item);
2110         }
2111
2112         item = gtk_check_menu_item_new_with_label(_("Use both dictionaries"));
2113         if (gtkaspell->use_both_dicts) {
2114                 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(item), TRUE);
2115         } 
2116         g_signal_connect(G_OBJECT(item), "activate",
2117                          G_CALLBACK(set_use_both_cb),
2118                          gtkaspell);
2119         gtk_widget_show(item);
2120         list = g_slist_append(list, item);
2121         
2122         item = gtk_menu_item_new();
2123         gtk_widget_show(item);
2124         list = g_slist_append(list, item);
2125         
2126         item = gtk_check_menu_item_new_with_label(_("Fast Mode"));
2127         gtk_check_menu_item_set_draw_as_radio(GTK_CHECK_MENU_ITEM(item), TRUE);
2128         if (gtkaspell->gtkaspeller->sug_mode == ASPELL_FASTMODE) {
2129                 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(item),TRUE);
2130                 gtk_widget_set_sensitive(GTK_WIDGET(item),FALSE);
2131         } else
2132                 g_signal_connect(G_OBJECT(item), "activate",
2133                                  G_CALLBACK(set_sug_mode_cb),
2134                                  gtkaspell);
2135         gtk_widget_show(item);
2136         list = g_slist_append(list, item);
2137
2138         item = gtk_check_menu_item_new_with_label(_("Normal Mode"));
2139         gtk_check_menu_item_set_draw_as_radio(GTK_CHECK_MENU_ITEM(item), TRUE);
2140         if (gtkaspell->gtkaspeller->sug_mode == ASPELL_NORMALMODE) {
2141                 gtk_widget_set_sensitive(GTK_WIDGET(item), FALSE);
2142                 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(item), TRUE);
2143         } else
2144                 g_signal_connect(G_OBJECT(item), "activate",
2145                                  G_CALLBACK(set_sug_mode_cb),
2146                                  gtkaspell);
2147         gtk_widget_show(item);
2148         list = g_slist_append(list, item);
2149
2150         item = gtk_check_menu_item_new_with_label(_("Bad Spellers Mode"));
2151         gtk_check_menu_item_set_draw_as_radio(GTK_CHECK_MENU_ITEM(item), TRUE);
2152         if (gtkaspell->gtkaspeller->sug_mode == ASPELL_BADSPELLERMODE) {
2153                 gtk_widget_set_sensitive(GTK_WIDGET(item), FALSE);
2154                 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(item), TRUE);
2155         } else
2156                 g_signal_connect(G_OBJECT(item), "activate",
2157                                  G_CALLBACK(set_sug_mode_cb),
2158                                  gtkaspell);
2159         gtk_widget_show(item);
2160         list = g_slist_append(list, item);
2161         
2162         item = gtk_menu_item_new();
2163         gtk_widget_show(item);
2164         list = g_slist_append(list, item);
2165         
2166         item = gtk_check_menu_item_new_with_label(_("Check while typing"));
2167         if (gtkaspell->check_while_typing)
2168                 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(item), TRUE);
2169         else    
2170                 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(item), FALSE);
2171         g_signal_connect(G_OBJECT(item), "activate",
2172                          G_CALLBACK(toggle_check_while_typing_cb),
2173                          gtkaspell);
2174         gtk_widget_show(item);
2175         list = g_slist_append(list, item);
2176
2177         item = gtk_menu_item_new();
2178         gtk_widget_show(item);
2179         list = g_slist_append(list, item);
2180
2181         submenu = gtk_menu_new();
2182         item = gtk_menu_item_new_with_label(_("Change dictionary"));
2183         gtk_menu_item_set_submenu(GTK_MENU_ITEM(item),submenu);
2184         gtk_widget_show(item);
2185         list = g_slist_append(list, item);
2186
2187         /* Dict list */
2188         if (gtkaspellcheckers->dictionary_list == NULL)
2189                 gtkaspell_get_dictionary_list(gtkaspell->dictionary_path, FALSE);
2190         {
2191                 GtkWidget * curmenu = submenu;
2192                 int count = 0;
2193                 Dictionary *dict;
2194                 GSList *tmp;
2195                 tmp = gtkaspellcheckers->dictionary_list;
2196                 
2197                 for (tmp = gtkaspellcheckers->dictionary_list; tmp != NULL; 
2198                                 tmp = g_slist_next(tmp)) {
2199                         if (count == MENUCOUNT) {
2200                                 GtkWidget *newmenu;
2201                                 
2202                                 newmenu = gtk_menu_new();
2203                                 item = gtk_menu_item_new_with_label(_("More..."));
2204                                 gtk_menu_item_set_submenu(GTK_MENU_ITEM(item), 
2205                                                           newmenu);
2206                                 
2207                                 gtk_menu_append(GTK_MENU(curmenu), item);
2208                                 gtk_widget_show(item);
2209                                 curmenu = newmenu;
2210                                 count = 0;
2211                         }
2212                         dict = (Dictionary *) tmp->data;
2213                         item = gtk_check_menu_item_new_with_label(dict->dictname);
2214                         g_object_set_data(G_OBJECT(item), "dict_name",
2215                                           dict->fullname); 
2216                         if (strcmp2(dict->fullname,
2217                             gtkaspell->gtkaspeller->dictionary->fullname))
2218                                 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(item), FALSE);
2219                         else {
2220                                 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(item), TRUE);
2221                                 gtk_widget_set_sensitive(GTK_WIDGET(item),
2222                                                          FALSE);
2223                         }
2224                         g_signal_connect(G_OBJECT(item), "activate",
2225                                          G_CALLBACK(change_dict_cb),
2226                                          gtkaspell);
2227                         gtk_widget_show(item);
2228                         gtk_menu_append(GTK_MENU(curmenu), item);
2229                         
2230                         count++;
2231                 }
2232         }  
2233         return list;
2234 }
2235
2236 GSList *gtkaspell_make_config_menu(GtkAspell *gtkaspell)
2237 {
2238         return populate_submenu(gtkaspell);
2239 }
2240
2241 static void set_menu_pos(GtkMenu *menu, gint *x, gint *y, 
2242                          gboolean *push_in, gpointer data)
2243 {
2244         GtkAspell       *gtkaspell = (GtkAspell *) data;
2245         gint             xx = 0, yy = 0;
2246         gint             sx,     sy;
2247         gint             wx,     wy;
2248         GtkTextView     *text = GTK_TEXT_VIEW(gtkaspell->gtktext);
2249         GtkTextBuffer   *textbuf;
2250         GtkTextIter      iter;
2251         GdkRectangle     rect;
2252         GtkRequisition   r;
2253
2254         textbuf = gtk_text_view_get_buffer(gtkaspell->gtktext);
2255         gtk_text_buffer_get_iter_at_mark(textbuf, &iter,
2256                                          gtk_text_buffer_get_insert(textbuf));
2257         gtk_text_view_get_iter_location(gtkaspell->gtktext, &iter, &rect);
2258         gtk_text_view_buffer_to_window_coords(text, GTK_TEXT_WINDOW_TEXT,
2259                                               rect.x, rect.y, 
2260                                               &rect.x, &rect.y);
2261
2262         gdk_window_get_origin(GTK_WIDGET(gtkaspell->gtktext)->window, &xx, &yy);
2263
2264         sx = gdk_screen_width();
2265         sy = gdk_screen_height();
2266
2267         gtk_widget_get_child_requisition(GTK_WIDGET(menu), &r);
2268
2269         wx =  r.width;
2270         wy =  r.height;
2271
2272         *x = rect.x + xx +
2273              gdk_char_width(gtk_style_get_font(GTK_WIDGET(text)->style), ' ');
2274
2275         *y = rect.y + rect.height + yy;
2276
2277         if (*x + wx > sx)
2278                 *x = sx - wx;
2279         if (*y + wy > sy)
2280                 *y = *y - wy -
2281                      gdk_string_height(gtk_style_get_font(
2282                                                 GTK_WIDGET(text)->style),
2283                                        gtkaspell->theword);
2284 }
2285
2286 /* change the current dictionary of gtkaspell
2287    - if always_set_alt_dict is set, the alternate dict is unconditionally set to the former
2288      current dictionary (common use: from menu callbacks)
2289    - if always_set_alt_dict is NOT set, the alternate dict will be set to the former
2290      current dictionary only if there is no alternate dictionary already set
2291      (this is when we need to set the current dictionary then the alternate one
2292      when creating a compose window, from the account and folder settings)
2293 */
2294 gboolean gtkaspell_change_dict(GtkAspell *gtkaspell, const gchar *dictionary,
2295                                                          gboolean always_set_alt_dict)
2296 {
2297         Dictionary      *dict;       
2298         GtkAspeller     *gtkaspeller;
2299         gint             sug_mode;
2300
2301         g_return_val_if_fail(gtkaspell, FALSE);
2302         g_return_val_if_fail(dictionary, FALSE);
2303   
2304         sug_mode  = gtkaspell->default_sug_mode;
2305
2306         dict = g_new0(Dictionary, 1);
2307         dict->fullname = g_strdup(dictionary);
2308         dict->encoding = g_strdup(gtkaspell->gtkaspeller->dictionary->encoding);
2309
2310         gtkaspeller = gtkaspeller_new(dict);
2311
2312         if (!gtkaspeller) {
2313                 alertpanel_warning(_("The spell checker could not change dictionary.\n%s"), 
2314                                           gtkaspellcheckers->error_message);
2315         } else {
2316                 if (gtkaspell->use_alternate) {
2317                         if (gtkaspell->alternate_speller) {
2318                                 if (always_set_alt_dict) {
2319                                         gtkaspeller_delete(gtkaspell->alternate_speller);
2320                                         gtkaspell->alternate_speller = gtkaspell->gtkaspeller;
2321                                 } else
2322                                         gtkaspeller_delete(gtkaspell->gtkaspeller);
2323                         } else
2324                                 /* should never be reached as the dicts are always set
2325                                    to a default value */
2326                                 gtkaspell->alternate_speller = gtkaspell->gtkaspeller;
2327                 } else
2328                         gtkaspeller_delete(gtkaspell->gtkaspeller);
2329
2330                 gtkaspell->gtkaspeller = gtkaspeller;
2331                 gtkaspell_set_sug_mode(gtkaspell, sug_mode);
2332         }
2333         
2334         dictionary_delete(dict);
2335
2336         return TRUE;    
2337 }
2338
2339 /* change the alternate dictionary of gtkaspell (doesn't affect the default dictionary) */
2340 gboolean gtkaspell_change_alt_dict(GtkAspell *gtkaspell, const gchar *alt_dictionary)
2341 {
2342         Dictionary      *dict;       
2343         GtkAspeller     *gtkaspeller;
2344
2345         g_return_val_if_fail(gtkaspell, FALSE);
2346         g_return_val_if_fail(alt_dictionary, FALSE);
2347   
2348         dict = g_new0(Dictionary, 1);
2349         dict->fullname = g_strdup(alt_dictionary);
2350         dict->encoding = g_strdup(gtkaspell->gtkaspeller->dictionary->encoding);
2351
2352         gtkaspeller = gtkaspeller_new(dict);
2353
2354         if (!gtkaspeller) {
2355                 alertpanel_warning(_("The spell checker could not change the alternate dictionary.\n%s"), 
2356                                           gtkaspellcheckers->error_message);
2357         } else {
2358                 if (gtkaspell->alternate_speller)
2359                         gtkaspeller_delete(gtkaspell->alternate_speller);
2360                 gtkaspell->alternate_speller = gtkaspeller;
2361         }
2362         
2363         dictionary_delete(dict);
2364
2365         return TRUE;    
2366 }
2367
2368 /* Menu call backs */
2369
2370 /* change_dict_cb() - Menu callback : change dict */
2371 static void change_dict_cb(GtkWidget *w, GtkAspell *gtkaspell)
2372 {
2373         gchar           *fullname;
2374   
2375         fullname = (gchar *) g_object_get_data(G_OBJECT(w), "dict_name");
2376         
2377         if (!strcmp2(fullname, _("None")))
2378                 return;
2379
2380         gtkaspell_change_dict(gtkaspell, fullname, TRUE);
2381         if (gtkaspell->recheck_when_changing_dict) {
2382                 gtkaspell_highlight_all(gtkaspell);
2383         }
2384         if (gtkaspell->menu_changed_cb)
2385                 gtkaspell->menu_changed_cb(gtkaspell->menu_changed_data);
2386 }
2387
2388 static void switch_to_alternate_cb(GtkWidget *w,
2389                                    gpointer data)
2390 {
2391         GtkAspell *gtkaspell = (GtkAspell *) data;
2392         use_alternate_dict(gtkaspell);
2393         if (gtkaspell->recheck_when_changing_dict) {
2394                 gtkaspell_highlight_all(gtkaspell);
2395         }
2396         if (gtkaspell->menu_changed_cb)
2397                 gtkaspell->menu_changed_cb(gtkaspell->menu_changed_data);
2398 }
2399
2400 /* Misc. helper functions */
2401
2402 static void set_point_continue(GtkAspell *gtkaspell)
2403 {
2404         GtkTextView  *gtktext;
2405
2406         gtktext = gtkaspell->gtktext;
2407
2408         set_textview_buffer_offset(gtktext, gtkaspell->orig_pos);
2409
2410         if (gtkaspell->continue_check)
2411                 gtkaspell->continue_check((gpointer *) gtkaspell);
2412 }
2413
2414 static void allocate_color(GtkAspell *gtkaspell, gint rgbvalue)
2415 {
2416         GtkTextBuffer *buffer = gtk_text_view_get_buffer(gtkaspell->gtktext);
2417         GdkColor *color = &(gtkaspell->highlight);
2418
2419         /* Shameless copy from Sylpheed's gtkutils.c */
2420         color->pixel = 0L;
2421         color->red   = (int) (((gdouble)((rgbvalue & 0xff0000) >> 16) / 255.0)
2422                         * 65535.0);
2423         color->green = (int) (((gdouble)((rgbvalue & 0x00ff00) >>  8) / 255.0)
2424                         * 65535.0);
2425         color->blue  = (int) (((gdouble) (rgbvalue & 0x0000ff)        / 255.0)
2426                         * 65535.0);
2427
2428         if (rgbvalue != 0)
2429                 gtk_text_buffer_create_tag(buffer, "misspelled",
2430                                    "foreground-gdk", color, NULL);
2431         else
2432                 gtk_text_buffer_create_tag(buffer, "misspelled",
2433                                    "underline", PANGO_UNDERLINE_ERROR, NULL);
2434
2435 }
2436
2437 static void change_color(GtkAspell * gtkaspell, 
2438                          gint start, gint end,
2439                          gchar *newtext,
2440                          GdkColor *color) 
2441 {
2442         GtkTextView *gtktext;
2443         GtkTextBuffer *buffer;
2444         GtkTextIter startiter, enditer;
2445
2446         if (start > end)
2447                 return;
2448     
2449         gtktext = gtkaspell->gtktext;
2450     
2451         buffer = gtk_text_view_get_buffer(gtktext);
2452         gtk_text_buffer_get_iter_at_offset(buffer, &startiter, start);
2453         gtk_text_buffer_get_iter_at_offset(buffer, &enditer, end);
2454         if (color)
2455                 gtk_text_buffer_apply_tag_by_name(buffer, "misspelled",
2456                                                   &startiter, &enditer);
2457         else {
2458                 gtk_text_iter_forward_char(&enditer);
2459                 gtk_text_buffer_remove_tag_by_name(buffer, "misspelled",
2460                                                    &startiter, &enditer);
2461         }
2462 }
2463
2464 /* convert_to_aspell_encoding () - converts ISO-8859-* strings to iso8859-* 
2465  * as needed by aspell. Returns an allocated string.
2466  */
2467
2468 static gchar *convert_to_aspell_encoding (const gchar *encoding)
2469 {
2470         gchar * aspell_encoding;
2471
2472         if (strstr2(encoding, "ISO-8859-")) {
2473                 aspell_encoding = g_strdup_printf("iso8859%s", encoding+8);
2474         }
2475         else {
2476                 if (!strcmp2(encoding, "US-ASCII"))
2477                         aspell_encoding = g_strdup("iso8859-1");
2478                 else
2479                         aspell_encoding = g_strdup(encoding);
2480         }
2481
2482         return aspell_encoding;
2483 }
2484
2485 /* compare_dict () - compare 2 dict names */
2486 static gint compare_dict(Dictionary *a, Dictionary *b)
2487 {
2488         guint   aparts = 0,  bparts = 0;
2489         guint   i;
2490
2491         for (i=0; i < strlen(a->dictname); i++)
2492                 if (a->dictname[i] == '-')
2493                         aparts++;
2494         for (i=0; i < strlen(b->dictname); i++)
2495                 if (b->dictname[i] == '-')
2496                         bparts++;
2497
2498         if (aparts != bparts) 
2499                 return (aparts < bparts) ? -1 : +1;
2500         else {
2501                 gint compare;
2502                 compare = strcmp2(a->dictname, b->dictname);
2503                 if (!compare)
2504                         compare = strcmp2(a->fullname, b->fullname);
2505                 return compare;
2506         }
2507 }
2508
2509 static void dictionary_delete(Dictionary *dict)
2510 {
2511         g_free(dict->fullname);
2512         g_free(dict->encoding);
2513         g_free(dict);
2514 }
2515
2516 static Dictionary *dictionary_dup(const Dictionary *dict)
2517 {
2518         Dictionary *dict2;
2519
2520         dict2 = g_new(Dictionary, 1); 
2521
2522         dict2->fullname = g_strdup(dict->fullname);
2523         dict2->dictname = dict->dictname - dict->fullname + dict2->fullname;
2524         dict2->encoding = g_strdup(dict->encoding);
2525
2526         return dict2;
2527 }
2528
2529 static void free_suggestions_list(GtkAspell *gtkaspell)
2530 {
2531         GList *list;
2532
2533         for (list = gtkaspell->suggestions_list; list != NULL;
2534              list = list->next)
2535                 g_free(list->data);
2536
2537         g_list_free(gtkaspell->suggestions_list);
2538         
2539         gtkaspell->max_sug          = -1;
2540         gtkaspell->suggestions_list = NULL;
2541 }
2542
2543 static void reset_theword_data(GtkAspell *gtkaspell)
2544 {
2545         gtkaspell->start_pos     =  0;
2546         gtkaspell->end_pos       =  0;
2547         gtkaspell->theword[0]    =  0;
2548         gtkaspell->max_sug       = -1;
2549
2550         free_suggestions_list(gtkaspell);
2551 }
2552
2553 static void free_checkers(gpointer elt, gpointer data)
2554 {
2555         GtkAspeller *gtkaspeller = elt;
2556
2557         g_return_if_fail(gtkaspeller);
2558
2559         gtkaspeller_real_delete(gtkaspeller);
2560 }
2561
2562 static gint find_gtkaspeller(gconstpointer aa, gconstpointer bb)
2563 {
2564         Dictionary *a = ((GtkAspeller *) aa)->dictionary;
2565         Dictionary *b = ((GtkAspeller *) bb)->dictionary;
2566
2567         if (a && b && a->fullname && b->fullname  &&
2568             strcmp(a->fullname, b->fullname) == 0 &&
2569             a->encoding && b->encoding)
2570                 return strcmp(a->encoding, b->encoding);
2571
2572         return 1;
2573 }
2574
2575 gchar *gtkaspell_get_default_dictionary(GtkAspell *gtkaspell)
2576 {
2577         if (gtkaspell && gtkaspell->gtkaspeller &&
2578                         gtkaspell->gtkaspeller->dictionary)
2579                 return gtkaspell->gtkaspeller->dictionary->dictname;
2580         else
2581                 return NULL;
2582 }
2583 #endif