a8154f47074680c12f6e12414c97e9a3793e05bc
[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 /* change the current dictionary of gtkaspell
2316    - if always_set_alt_dict is set, the alternate dict is unconditionally set to the former
2317      current dictionary (common use: from menu callbacks)
2318    - if always_set_alt_dict is NOT set, the alternate dict will be set to the former
2319      current dictionary only if there is no alternate dictionary already set
2320      (this is when we need to set the current dictionary then the alternate one
2321      when creating a compose window, from the account and folder settings)
2322 */
2323 gboolean gtkaspell_change_dict(GtkAspell *gtkaspell, const gchar *dictionary,
2324                                                          gboolean always_set_alt_dict)
2325 {
2326         Dictionary      *dict;       
2327         GtkAspeller     *gtkaspeller;
2328         gint             sug_mode;
2329
2330         g_return_val_if_fail(gtkaspell, FALSE);
2331         g_return_val_if_fail(dictionary, FALSE);
2332   
2333         sug_mode  = gtkaspell->default_sug_mode;
2334
2335         dict = g_new0(Dictionary, 1);
2336         dict->fullname = g_strdup(dictionary);
2337         dict->encoding = g_strdup(gtkaspell->gtkaspeller->dictionary->encoding);
2338
2339         gtkaspeller = gtkaspeller_new(dict);
2340
2341         if (!gtkaspeller) {
2342                 gchar *message;
2343                 message = g_strdup_printf(_("The spell checker could not change dictionary.\n%s"), 
2344                                           gtkaspellcheckers->error_message);
2345
2346                 alertpanel_warning(message); 
2347                 g_free(message);
2348         } else {
2349                 if (gtkaspell->use_alternate) {
2350                         if (gtkaspell->alternate_speller) {
2351                                 if (always_set_alt_dict) {
2352                                         gtkaspeller_delete(gtkaspell->alternate_speller);
2353                                         gtkaspell->alternate_speller = gtkaspell->gtkaspeller;
2354                                 } else
2355                                         gtkaspeller_delete(gtkaspell->gtkaspeller);
2356                         } else
2357                                 /* should never be reached as the dicts are always set
2358                                    to a default value */
2359                                 gtkaspell->alternate_speller = gtkaspell->gtkaspeller;
2360                 } else
2361                         gtkaspeller_delete(gtkaspell->gtkaspeller);
2362
2363                 gtkaspell->gtkaspeller = gtkaspeller;
2364                 gtkaspell_set_sug_mode(gtkaspell, sug_mode);
2365         }
2366         
2367         dictionary_delete(dict);
2368
2369         return TRUE;    
2370 }
2371
2372 /* change the alternate dictionary of gtkaspell (doesn't affect the default dictionary) */
2373 gboolean gtkaspell_change_alt_dict(GtkAspell *gtkaspell, const gchar *alt_dictionary)
2374 {
2375         Dictionary      *dict;       
2376         GtkAspeller     *gtkaspeller;
2377
2378         g_return_val_if_fail(gtkaspell, FALSE);
2379         g_return_val_if_fail(alt_dictionary, FALSE);
2380   
2381         dict = g_new0(Dictionary, 1);
2382         dict->fullname = g_strdup(alt_dictionary);
2383         dict->encoding = g_strdup(gtkaspell->gtkaspeller->dictionary->encoding);
2384
2385         gtkaspeller = gtkaspeller_new(dict);
2386
2387         if (!gtkaspeller) {
2388                 gchar *message;
2389                 message = g_strdup_printf(_("The spell checker could not change the alternate dictionary.\n%s"), 
2390                                           gtkaspellcheckers->error_message);
2391
2392                 alertpanel_warning(message); 
2393                 g_free(message);
2394         } else {
2395                 if (gtkaspell->alternate_speller)
2396                         gtkaspeller_delete(gtkaspell->alternate_speller);
2397                 gtkaspell->alternate_speller = gtkaspeller;
2398         }
2399         
2400         dictionary_delete(dict);
2401
2402         return TRUE;    
2403 }
2404
2405 /* Menu call backs */
2406
2407 /* change_dict_cb() - Menu callback : change dict */
2408 static void change_dict_cb(GtkWidget *w, GtkAspell *gtkaspell)
2409 {
2410         gchar           *fullname;
2411   
2412         fullname = (gchar *) g_object_get_data(G_OBJECT(w), "dict_name");
2413         
2414         if (!strcmp2(fullname, _("None")))
2415                 return;
2416
2417         gtkaspell_change_dict(gtkaspell, fullname, TRUE);
2418         if (gtkaspell->recheck_when_changing_dict) {
2419                 gtkaspell_highlight_all(gtkaspell);
2420         }
2421         if (gtkaspell->menu_changed_cb)
2422                 gtkaspell->menu_changed_cb(gtkaspell->menu_changed_data);
2423 }
2424
2425 static void switch_to_alternate_cb(GtkWidget *w,
2426                                    gpointer data)
2427 {
2428         GtkAspell *gtkaspell = (GtkAspell *) data;
2429         use_alternate_dict(gtkaspell);
2430         if (gtkaspell->recheck_when_changing_dict) {
2431                 gtkaspell_highlight_all(gtkaspell);
2432         }
2433         if (gtkaspell->menu_changed_cb)
2434                 gtkaspell->menu_changed_cb(gtkaspell->menu_changed_data);
2435 }
2436
2437 /* Misc. helper functions */
2438
2439 static void set_point_continue(GtkAspell *gtkaspell)
2440 {
2441         GtkTextView  *gtktext;
2442
2443         gtktext = gtkaspell->gtktext;
2444
2445         set_textview_buffer_offset(gtktext, gtkaspell->orig_pos);
2446
2447         if (gtkaspell->continue_check)
2448                 gtkaspell->continue_check((gpointer *) gtkaspell);
2449 }
2450
2451 static void allocate_color(GtkAspell *gtkaspell, gint rgbvalue)
2452 {
2453         GtkTextBuffer *buffer = gtk_text_view_get_buffer(gtkaspell->gtktext);
2454         GdkColor *color = &(gtkaspell->highlight);
2455
2456         /* Shameless copy from Sylpheed's gtkutils.c */
2457         color->pixel = 0L;
2458         color->red   = (int) (((gdouble)((rgbvalue & 0xff0000) >> 16) / 255.0)
2459                         * 65535.0);
2460         color->green = (int) (((gdouble)((rgbvalue & 0x00ff00) >>  8) / 255.0)
2461                         * 65535.0);
2462         color->blue  = (int) (((gdouble) (rgbvalue & 0x0000ff)        / 255.0)
2463                         * 65535.0);
2464
2465         if (rgbvalue != 0)
2466                 gtk_text_buffer_create_tag(buffer, "misspelled",
2467                                    "foreground-gdk", color, NULL);
2468         else
2469                 gtk_text_buffer_create_tag(buffer, "misspelled",
2470                                    "underline", PANGO_UNDERLINE_ERROR, NULL);
2471
2472 }
2473
2474 static void change_color(GtkAspell * gtkaspell, 
2475                          gint start, gint end,
2476                          gchar *newtext,
2477                          GdkColor *color) 
2478 {
2479         GtkTextView *gtktext;
2480         GtkTextBuffer *buffer;
2481         GtkTextIter startiter, enditer;
2482
2483         if (start > end)
2484                 return;
2485     
2486         gtktext = gtkaspell->gtktext;
2487     
2488         buffer = gtk_text_view_get_buffer(gtktext);
2489         gtk_text_buffer_get_iter_at_offset(buffer, &startiter, start);
2490         gtk_text_buffer_get_iter_at_offset(buffer, &enditer, end);
2491         if (color)
2492                 gtk_text_buffer_apply_tag_by_name(buffer, "misspelled",
2493                                                   &startiter, &enditer);
2494         else {
2495                 gtk_text_iter_forward_char(&enditer);
2496                 gtk_text_buffer_remove_tag_by_name(buffer, "misspelled",
2497                                                    &startiter, &enditer);
2498         }
2499 }
2500
2501 /* convert_to_aspell_encoding () - converts ISO-8859-* strings to iso8859-* 
2502  * as needed by aspell. Returns an allocated string.
2503  */
2504
2505 static gchar *convert_to_aspell_encoding (const gchar *encoding)
2506 {
2507         gchar * aspell_encoding;
2508
2509         if (strstr2(encoding, "ISO-8859-")) {
2510                 aspell_encoding = g_strdup_printf("iso8859%s", encoding+8);
2511         }
2512         else {
2513                 if (!strcmp2(encoding, "US-ASCII"))
2514                         aspell_encoding = g_strdup("iso8859-1");
2515                 else
2516                         aspell_encoding = g_strdup(encoding);
2517         }
2518
2519         return aspell_encoding;
2520 }
2521
2522 /* compare_dict () - compare 2 dict names */
2523 static gint compare_dict(Dictionary *a, Dictionary *b)
2524 {
2525         guint   aparts = 0,  bparts = 0;
2526         guint   i;
2527
2528         for (i=0; i < strlen(a->dictname); i++)
2529                 if (a->dictname[i] == '-')
2530                         aparts++;
2531         for (i=0; i < strlen(b->dictname); i++)
2532                 if (b->dictname[i] == '-')
2533                         bparts++;
2534
2535         if (aparts != bparts) 
2536                 return (aparts < bparts) ? -1 : +1;
2537         else {
2538                 gint compare;
2539                 compare = strcmp2(a->dictname, b->dictname);
2540                 if (!compare)
2541                         compare = strcmp2(a->fullname, b->fullname);
2542                 return compare;
2543         }
2544 }
2545
2546 static void dictionary_delete(Dictionary *dict)
2547 {
2548         g_free(dict->fullname);
2549         g_free(dict->encoding);
2550         g_free(dict);
2551 }
2552
2553 static Dictionary *dictionary_dup(const Dictionary *dict)
2554 {
2555         Dictionary *dict2;
2556
2557         dict2 = g_new(Dictionary, 1); 
2558
2559         dict2->fullname = g_strdup(dict->fullname);
2560         dict2->dictname = dict->dictname - dict->fullname + dict2->fullname;
2561         dict2->encoding = g_strdup(dict->encoding);
2562
2563         return dict2;
2564 }
2565
2566 static void free_suggestions_list(GtkAspell *gtkaspell)
2567 {
2568         GList *list;
2569
2570         for (list = gtkaspell->suggestions_list; list != NULL;
2571              list = list->next)
2572                 g_free(list->data);
2573
2574         g_list_free(gtkaspell->suggestions_list);
2575         
2576         gtkaspell->max_sug          = -1;
2577         gtkaspell->suggestions_list = NULL;
2578 }
2579
2580 static void reset_theword_data(GtkAspell *gtkaspell)
2581 {
2582         gtkaspell->start_pos     =  0;
2583         gtkaspell->end_pos       =  0;
2584         gtkaspell->theword[0]    =  0;
2585         gtkaspell->max_sug       = -1;
2586
2587         free_suggestions_list(gtkaspell);
2588 }
2589
2590 static void free_checkers(gpointer elt, gpointer data)
2591 {
2592         GtkAspeller *gtkaspeller = elt;
2593
2594         g_return_if_fail(gtkaspeller);
2595
2596         gtkaspeller_real_delete(gtkaspeller);
2597 }
2598
2599 static gint find_gtkaspeller(gconstpointer aa, gconstpointer bb)
2600 {
2601         Dictionary *a = ((GtkAspeller *) aa)->dictionary;
2602         Dictionary *b = ((GtkAspeller *) bb)->dictionary;
2603
2604         if (a && b && a->fullname && b->fullname  &&
2605             strcmp(a->fullname, b->fullname) == 0 &&
2606             a->encoding && b->encoding)
2607                 return strcmp(a->encoding, b->encoding);
2608
2609         return 1;
2610 }
2611 #endif