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