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