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