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