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