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