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