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