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