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