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