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