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