2011-11-01 [colin] 3.7.10cvs67
[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 = gtkaspell->gtktext;
1194         g_signal_handlers_block_by_func(G_OBJECT(gtktext),
1195                                          G_CALLBACK(entry_insert_cb),
1196                                          gtkaspell);
1197         g_signal_handlers_block_by_func(G_OBJECT(gtktext),
1198                                          G_CALLBACK(entry_delete_cb),
1199                                          gtkaspell);
1200 }
1201
1202 void gtkaspell_unblock_check(GtkAspell *gtkaspell)
1203 {
1204         GtkTextView *gtktext = gtkaspell->gtktext;
1205         g_signal_handlers_unblock_by_func(G_OBJECT(gtktext),
1206                                          G_CALLBACK(entry_insert_cb),
1207                                          gtkaspell);
1208         g_signal_handlers_unblock_by_func(G_OBJECT(gtktext),
1209                                          G_CALLBACK(entry_delete_cb),
1210                                          gtkaspell);
1211 }
1212
1213 static void replace_real_word(GtkAspell *gtkaspell, const gchar *newword)
1214 {
1215         int             oldlen, newlen, wordlen;
1216         gint            origpos;
1217         gint            pos;
1218         GtkTextView     *gtktext;
1219         GtkTextBuffer   *textbuf;
1220         GtkTextIter     startiter, enditer;
1221     
1222         if (!newword) return;
1223
1224         gtktext = gtkaspell->gtktext;
1225         textbuf = gtk_text_view_get_buffer(gtktext);
1226
1227         origpos = gtkaspell->orig_pos;
1228         pos     = origpos;
1229         oldlen  = gtkaspell->end_pos - gtkaspell->start_pos;
1230         wordlen = strlen(gtkaspell->theword);
1231
1232         newlen = strlen(newword); /* FIXME: multybyte characters? */
1233
1234         g_signal_handlers_block_by_func(G_OBJECT(gtktext),
1235                                          G_CALLBACK(entry_insert_cb),
1236                                          gtkaspell);
1237         g_signal_handlers_block_by_func(G_OBJECT(gtktext),
1238                                          G_CALLBACK(entry_delete_cb),
1239                                          gtkaspell);
1240
1241         gtk_text_buffer_get_iter_at_offset(textbuf, &startiter,
1242                                            gtkaspell->start_pos);
1243         gtk_text_buffer_get_iter_at_offset(textbuf, &enditer,
1244                                            gtkaspell->end_pos);
1245         g_signal_emit_by_name(G_OBJECT(textbuf), "delete-range",
1246                               &startiter, &enditer, gtkaspell);
1247         g_signal_emit_by_name(G_OBJECT(textbuf), "insert-text",
1248                               &startiter, newword, newlen, gtkaspell);
1249
1250         g_signal_handlers_unblock_by_func(G_OBJECT(gtktext),
1251                                            G_CALLBACK(entry_insert_cb),
1252                                            gtkaspell);
1253         g_signal_handlers_unblock_by_func(G_OBJECT(gtktext),
1254                                            G_CALLBACK(entry_delete_cb),
1255                                            gtkaspell);
1256
1257         /* Put the point and the position where we clicked with the mouse
1258          * It seems to be a hack, as I must thaw,freeze,thaw the widget
1259          * to let it update correctly the word insertion and then the
1260          * point & position position. If not, SEGV after the first replacement
1261          * If the new word ends before point, put the point at its end.
1262          */
1263
1264         if (origpos - gtkaspell->start_pos < oldlen &&
1265             origpos - gtkaspell->start_pos >= 0) {
1266                 /* Original point was in the word.
1267                  * Let it there unless point is going to be outside of the word
1268                  */
1269                 if (origpos - gtkaspell->start_pos >= newlen) {
1270                         pos = gtkaspell->start_pos + newlen;
1271                 }
1272         }
1273         else if (origpos >= gtkaspell->end_pos) {
1274                 /* move the position according to the change of length */
1275                 pos = origpos + newlen - oldlen;
1276         }
1277
1278         gtkaspell->end_pos = gtkaspell->start_pos + strlen(newword); /* FIXME: multibyte characters? */
1279
1280         if (get_textview_buffer_charcount(gtktext) < pos)
1281                 pos = get_textview_buffer_charcount(gtktext);
1282         gtkaspell->orig_pos = pos;
1283
1284         set_textview_buffer_offset(gtktext, gtkaspell->orig_pos);
1285 }
1286
1287 static void replace_real_word_cb(gpointer data, const gchar *newword)
1288 {
1289         replace_real_word((GtkAspell *)data, newword);
1290 }
1291
1292 /* Accept this word for this session */
1293 static void add_word_to_session_cb(GtkWidget *w, gpointer data)
1294 {
1295         GtkTextView *gtktext;
1296         GtkAspell *gtkaspell = (GtkAspell *) data; 
1297         gtktext = gtkaspell->gtktext;
1298
1299
1300         enchant_dict_add_to_session(gtkaspell->gtkaspeller->speller, gtkaspell->theword, strlen(gtkaspell->theword));
1301
1302         gtkaspell->ctx.check_word(gtkaspell->ctx.data);
1303         gtkaspell_dict_changed(gtkaspell);
1304
1305         gtk_menu_shell_deactivate(GTK_MENU_SHELL(GTK_WIDGET(w)->parent));
1306
1307         set_point_continue(gtkaspell);
1308 }
1309
1310 /* add_word_to_personal_cb() - add word to personal dict. */
1311 static void add_word_to_personal_cb(GtkWidget *w, gpointer data)
1312 {
1313         GtkAspell *gtkaspell = (GtkAspell *) data; 
1314
1315         enchant_dict_add_to_pwl(gtkaspell->gtkaspeller->speller, gtkaspell->theword, strlen(gtkaspell->theword));
1316
1317         gtkaspell->ctx.check_word(gtkaspell->ctx.data);
1318         gtkaspell_dict_changed(gtkaspell);
1319         
1320         gtk_menu_shell_deactivate(GTK_MENU_SHELL(GTK_WIDGET(w)->parent));
1321         set_point_continue(gtkaspell);
1322 }
1323
1324 static void check_with_alternate_cb(GtkWidget *w, gpointer data)
1325 {
1326         GtkAspell *gtkaspell = (GtkAspell *)data;
1327         gint misspelled;
1328
1329         gtk_menu_shell_deactivate(GTK_MENU_SHELL(GTK_WIDGET(w)->parent));
1330
1331         gtkaspell_use_alternate_dict(gtkaspell);
1332         misspelled = gtkaspell->ctx.check_word(gtkaspell->ctx.data);
1333
1334         if (!gtkaspell->continue_check) {
1335
1336                 gtkaspell->misspelled = misspelled;
1337
1338                 if (gtkaspell->misspelled) {
1339                         GtkWidget *menu;
1340                         GSList *list, *cur;
1341                         misspelled_suggest(gtkaspell, gtkaspell->theword);
1342
1343                         gtkaspell->ctx.set_position(gtkaspell->ctx.data,
1344                                             gtkaspell->end_pos);
1345
1346                         list = make_sug_menu(gtkaspell);
1347                         menu = gtk_menu_new();
1348                         for (cur = list; cur; cur = cur->next)
1349                                 gtk_menu_shell_append(GTK_MENU_SHELL(menu), GTK_WIDGET(cur->data));
1350                         g_slist_free(list);
1351                         gtk_menu_popup(GTK_MENU(menu), NULL, NULL,
1352                                        gtkaspell->ctx.set_menu_pos,
1353                                        gtkaspell->ctx.data, 0,
1354                                        GDK_CURRENT_TIME);
1355                         g_signal_connect(G_OBJECT(menu), "deactivate",
1356                                          G_CALLBACK(destroy_menu),
1357                                          gtkaspell);
1358                         g_signal_connect(G_OBJECT(menu),
1359                                 "key_press_event",
1360                                 G_CALLBACK(aspell_key_pressed),
1361                                 gtkaspell);
1362                         return;
1363                 }
1364         } else
1365                 gtkaspell->orig_pos = gtkaspell->start_pos;
1366
1367         set_point_continue(gtkaspell);
1368 }
1369         
1370 static gboolean replace_key_pressed(GtkWidget *widget,
1371                                    GdkEventKey *event,
1372                                    GtkAspell *gtkaspell)
1373 {
1374         if (event && event->keyval == GDK_KEY_Escape) {
1375                 gtk_widget_destroy(widget);
1376                 return TRUE;
1377         } else if (event && event->keyval == GDK_KEY_Return) {
1378                 replace_with_supplied_word_cb(widget, gtkaspell);
1379                 return TRUE;
1380         }
1381         return FALSE;
1382 }
1383         
1384 static void replace_with_create_dialog_cb(GtkWidget *w, gpointer data)
1385 {
1386         static PangoFontDescription *font_desc;
1387         GtkWidget *dialog;
1388         GtkWidget *label;
1389         GtkWidget *w_hbox;
1390         GtkWidget *hbox;
1391         GtkWidget *vbox;
1392         GtkWidget *entry;
1393         GtkWidget *ok_button;
1394         GtkWidget *cancel_button;
1395         GtkWidget *confirm_area;
1396         GtkWidget *icon;
1397         gchar *utf8buf, *thelabel;
1398         gint xx, yy;
1399         GtkAspell *gtkaspell = (GtkAspell *) data;
1400
1401         gdk_window_get_origin((GTK_WIDGET(w)->parent)->window, &xx, &yy);
1402
1403         gtk_menu_shell_deactivate(GTK_MENU_SHELL(GTK_WIDGET(w)->parent));
1404
1405         dialog = gtk_dialog_new();
1406
1407         gtk_window_set_resizable(GTK_WINDOW(dialog), FALSE);
1408         gtk_window_set_title(GTK_WINDOW(dialog),_("Replace unknown word"));
1409         gtk_window_move(GTK_WINDOW(dialog), xx, yy);
1410
1411         g_signal_connect_swapped(G_OBJECT(dialog), "destroy",
1412                                  G_CALLBACK(gtk_widget_destroy), 
1413                                  G_OBJECT(dialog));
1414
1415         gtk_box_set_spacing (GTK_BOX (GTK_DIALOG (dialog)->vbox), 14);
1416         hbox = gtk_hbox_new (FALSE, 12);
1417         gtk_container_set_border_width (GTK_CONTAINER (hbox), 5);
1418         gtk_widget_show (hbox);
1419         gtk_box_pack_start (GTK_BOX (GTK_DIALOG (dialog)->vbox), hbox,
1420                             FALSE, FALSE, 0);
1421
1422         utf8buf  = g_strdup(gtkaspell->theword);
1423
1424         thelabel = g_strdup_printf(_("<span weight=\"bold\" "
1425                                         "size=\"larger\">Replace \"%s\" with: </span>"), 
1426                                    utf8buf);
1427         /* for title label */
1428         w_hbox = gtk_hbox_new(FALSE, 0);
1429         
1430         icon = gtk_image_new_from_stock(GTK_STOCK_DIALOG_QUESTION,
1431                                         GTK_ICON_SIZE_DIALOG); 
1432         gtk_misc_set_alignment (GTK_MISC (icon), 0.5, 0.0);
1433         gtk_box_pack_start (GTK_BOX (hbox), icon, FALSE, FALSE, 0);
1434         
1435         vbox = gtk_vbox_new (FALSE, 12);
1436         gtk_box_pack_start (GTK_BOX (hbox), vbox, TRUE, TRUE, 0);
1437         gtk_widget_show (vbox);
1438         
1439         label = gtk_label_new(thelabel);
1440         gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
1441         gtk_label_set_justify(GTK_LABEL(label), GTK_JUSTIFY_LEFT);
1442         gtk_label_set_use_markup (GTK_LABEL (label), TRUE);
1443         gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 0);
1444         gtk_label_set_line_wrap(GTK_LABEL(label), TRUE);
1445         if (!font_desc) {
1446                 gint size;
1447
1448                 size = pango_font_description_get_size
1449                         (label->style->font_desc);
1450                 font_desc = pango_font_description_new();
1451                 pango_font_description_set_weight
1452                         (font_desc, PANGO_WEIGHT_BOLD);
1453                 pango_font_description_set_size
1454                         (font_desc, size * PANGO_SCALE_LARGE);
1455         }
1456         if (font_desc)
1457                 gtk_widget_modify_font(label, font_desc);
1458         g_free(thelabel);
1459         
1460         entry = gtk_entry_new();
1461         gtkaspell->replace_entry = entry;
1462         gtk_entry_set_text(GTK_ENTRY(entry), utf8buf);
1463         gtk_editable_select_region(GTK_EDITABLE(entry), 0, -1);
1464         g_signal_connect(G_OBJECT(dialog),
1465                         "key_press_event",
1466                         G_CALLBACK(replace_key_pressed), gtkaspell);
1467         gtk_box_pack_start(GTK_BOX(vbox), entry, FALSE, FALSE, 0);
1468         g_free(utf8buf);  
1469
1470         label = gtk_label_new(_("Holding down Control key while pressing "
1471                                 "Enter\nwill learn from mistake.\n"));
1472         gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
1473         gtk_label_set_justify(GTK_LABEL(label), GTK_JUSTIFY_LEFT);
1474         gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 0);
1475         gtk_widget_show(label);
1476
1477         hbox = gtk_hbox_new(TRUE, 0);
1478
1479         gtkut_stock_button_set_create(&confirm_area,
1480                                       &cancel_button, GTK_STOCK_CANCEL,
1481                                       &ok_button, GTK_STOCK_OK,
1482                                       NULL, NULL);
1483
1484         gtk_box_pack_end(GTK_BOX(GTK_DIALOG(dialog)->action_area),
1485                          confirm_area, FALSE, FALSE, 0);
1486         gtk_container_set_border_width(GTK_CONTAINER(confirm_area), 5);
1487
1488         g_signal_connect(G_OBJECT(ok_button), "clicked",
1489                          G_CALLBACK(replace_with_supplied_word_cb), 
1490                          gtkaspell);
1491         g_signal_connect_swapped(G_OBJECT(ok_button), "clicked",
1492                                    G_CALLBACK(gtk_widget_destroy), 
1493                                    G_OBJECT(dialog));
1494
1495         g_signal_connect_swapped(G_OBJECT(cancel_button), "clicked",
1496                                  G_CALLBACK(gtk_widget_destroy), 
1497                                  G_OBJECT(dialog));
1498
1499         gtk_widget_grab_focus(entry);
1500
1501         gtk_window_set_modal(GTK_WINDOW(dialog), TRUE);
1502
1503         gtk_widget_show_all(dialog);
1504 }
1505
1506 static void gtkaspell_uncheck_all(GtkAspell * gtkaspell) 
1507 {
1508         GtkTextView *gtktext;
1509         GtkTextBuffer *buffer;
1510         GtkTextIter startiter, enditer;
1511         
1512         gtktext = gtkaspell->gtktext;
1513
1514         buffer = gtk_text_view_get_buffer(gtktext);
1515         gtk_text_buffer_get_iter_at_offset(buffer, &startiter, 0);
1516         gtk_text_buffer_get_iter_at_offset(buffer, &enditer,
1517                                    get_textview_buffer_charcount(gtktext)-1);
1518         gtk_text_buffer_remove_tag_by_name(buffer, "misspelled",
1519                                            &startiter, &enditer);
1520 }
1521
1522 static void toggle_check_while_typing_cb(GtkWidget *w, gpointer data)
1523 {
1524         GtkAspell *gtkaspell = (GtkAspell *) data;
1525
1526         gtkaspell->check_while_typing = gtkaspell->check_while_typing == FALSE;
1527
1528         if (!gtkaspell->check_while_typing)
1529                 gtkaspell_uncheck_all(gtkaspell);
1530         if (gtkaspell->menu_changed_cb)
1531                 gtkaspell->menu_changed_cb(gtkaspell->menu_changed_data);
1532 }
1533
1534 static GSList *create_empty_dictionary_list(void)
1535 {
1536         GSList *list = NULL;
1537         Dictionary *dict;
1538
1539         dict = g_new0(Dictionary, 1);
1540         dict->fullname = g_strdup(_("None"));
1541         dict->dictname = NULL;
1542
1543         return g_slist_append(list, dict);
1544 }
1545
1546 static void list_dict_cb(const char * const lang_tag,
1547                  const char * const provider_name,
1548                  const char * const provider_desc,
1549                  const char * const provider_file,
1550                  void * data)
1551 {
1552         GSList **list = (GSList **)data;
1553         Dictionary *dict = g_new0(Dictionary, 1);
1554         dict->fullname = g_strdup(lang_tag);
1555         dict->dictname = g_strdup(lang_tag);
1556
1557         if (g_slist_find_custom(*list, dict, 
1558                         (GCompareFunc) compare_dict) == NULL) {
1559                 debug_print("Aspell: found dictionary %s %s\n", dict->fullname,
1560                                 dict->dictname);
1561                 *list = g_slist_insert_sorted(*list, dict,
1562                                 (GCompareFunc) compare_dict);
1563         } else {
1564                 dictionary_delete(dict);
1565         }
1566 }
1567
1568 /* gtkaspell_get_dictionary_list() - returns list of dictionary names */
1569 static GSList *gtkaspell_get_dictionary_list(gint refresh)
1570 {
1571         GSList *list;
1572         EnchantBroker *broker;
1573
1574         if (!gtkaspellcheckers)
1575                 gtkaspell_checkers_init();
1576
1577         if (gtkaspellcheckers->dictionary_list && !refresh)
1578                 return gtkaspellcheckers->dictionary_list;
1579         else
1580                 gtkaspell_free_dictionary_list(
1581                                 gtkaspellcheckers->dictionary_list);
1582         list = NULL;
1583
1584         broker = enchant_broker_init();
1585
1586         enchant_broker_list_dicts(broker, list_dict_cb, &list);
1587
1588         enchant_broker_free(broker);
1589
1590         if (list == NULL){
1591                 
1592                 debug_print("Aspell: error when searching for dictionaries: "
1593                               "No dictionary found.\n");
1594                 list = create_empty_dictionary_list();
1595         }
1596
1597         gtkaspellcheckers->dictionary_list = list;
1598
1599         return list;
1600 }
1601
1602 static void gtkaspell_free_dictionary_list(GSList *list)
1603 {
1604         Dictionary *dict;
1605         GSList *walk;
1606         for (walk = list; walk != NULL; walk = g_slist_next(walk))
1607                 if (walk->data) {
1608                         dict = (Dictionary *) walk->data;
1609                         dictionary_delete(dict);
1610                 }                               
1611         g_slist_free(list);
1612 }
1613
1614 GtkTreeModel *gtkaspell_dictionary_store_new_with_refresh(gboolean refresh)
1615 {
1616         GSList *dict_list, *tmp;
1617         GtkListStore *store;
1618         GtkTreeIter iter;
1619         Dictionary *dict;
1620
1621         dict_list = gtkaspell_get_dictionary_list(refresh);
1622         cm_return_val_if_fail(dict_list, NULL);
1623
1624         store = gtk_list_store_new(SET_GTKASPELL_SIZE,
1625                                    G_TYPE_STRING,
1626                                    G_TYPE_STRING,
1627                                    -1);
1628         
1629         for (tmp = dict_list; tmp != NULL; tmp = g_slist_next(tmp)) {
1630                 dict = (Dictionary *) tmp->data;
1631                 
1632                 gtk_list_store_append(store, &iter);
1633                 gtk_list_store_set(store, &iter,
1634                                    SET_GTKASPELL_NAME, dict->dictname,
1635                                    SET_GTKASPELL_FULLNAME, dict->fullname,
1636                                    -1);
1637         }
1638
1639         return GTK_TREE_MODEL(store);
1640 }
1641
1642 GtkTreeModel *gtkaspell_dictionary_store_new(void)
1643 {
1644         return gtkaspell_dictionary_store_new_with_refresh
1645                 (TRUE);
1646 }
1647
1648 GtkWidget *gtkaspell_dictionary_combo_new(const gboolean refresh)
1649 {
1650         GtkWidget *combo;
1651         GtkCellRenderer *renderer;
1652
1653         combo = gtk_combo_box_new_with_model(
1654                         gtkaspell_dictionary_store_new_with_refresh(refresh));
1655         gtk_combo_box_set_active(GTK_COMBO_BOX(combo), 0);   
1656         gtk_widget_show(combo);
1657         
1658         renderer = gtk_cell_renderer_text_new();
1659         gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(combo), renderer, TRUE);
1660         gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(combo),renderer,
1661                                         "text", SET_GTKASPELL_NAME, NULL);
1662         
1663         return combo;
1664
1665
1666 gchar *gtkaspell_get_dictionary_menu_active_item(GtkComboBox *combo)
1667 {
1668         GtkTreeIter iter;
1669         GtkTreeModel *model;
1670         gchar *dict_fullname = NULL;
1671         
1672         cm_return_val_if_fail(GTK_IS_COMBO_BOX(combo), NULL);
1673         cm_return_val_if_fail(gtk_combo_box_get_active_iter(combo, &iter), NULL);
1674         
1675         model = gtk_combo_box_get_model(combo);
1676         if(model == NULL)
1677                 return NULL;
1678         
1679         gtk_tree_model_get(model, &iter,
1680                            SET_GTKASPELL_FULLNAME, &dict_fullname,
1681                            -1);
1682
1683         return dict_fullname;
1684 }
1685
1686 gint gtkaspell_set_dictionary_menu_active_item(GtkComboBox *combo,
1687                                                const gchar *dictionary)
1688 {
1689         GtkTreeModel *model;
1690         GtkTreeIter iter;
1691         gchar *dict_name = NULL;
1692         
1693         cm_return_val_if_fail(combo != NULL, 0);
1694         cm_return_val_if_fail(dictionary != NULL, 0);
1695         cm_return_val_if_fail(GTK_IS_COMBO_BOX(combo), 0);
1696
1697         if((model = gtk_combo_box_get_model(combo)) == NULL)
1698                 return 0;
1699         if((gtk_tree_model_get_iter_first(model, &iter)) == FALSE)
1700                 return 0;
1701         
1702         do {
1703                 gtk_tree_model_get(model, &iter,
1704                                    SET_GTKASPELL_FULLNAME, &dict_name,
1705                                    -1);
1706                 
1707                 if ((dict_name != NULL) && !strcmp2(dict_name, dictionary)) {
1708                         gtk_combo_box_set_active_iter(combo, &iter);
1709                         g_free(dict_name);
1710                         return 1;
1711                 }
1712                 
1713                 g_free(dict_name);
1714                 
1715         } while ((gtk_tree_model_iter_next(model, &iter)) == TRUE);
1716         
1717         return 0;
1718 }
1719
1720 void gtkaspell_use_alternate_dict(GtkAspell *gtkaspell)
1721 {
1722         GtkAspeller *tmp;
1723
1724         tmp = gtkaspell->gtkaspeller;
1725         gtkaspell->gtkaspeller = gtkaspell->alternate_speller;
1726         gtkaspell->alternate_speller = tmp;
1727 }
1728
1729 static void destroy_menu(GtkWidget *widget,
1730                              gpointer user_data) {
1731         GtkAspell *gtkaspell = (GtkAspell *)user_data;
1732
1733         if (gtkaspell->accel_group) {
1734                 gtk_window_remove_accel_group(GTK_WINDOW(gtkaspell->parent_window), 
1735                                 gtkaspell->accel_group);
1736                 gtkaspell->accel_group = NULL;
1737         }
1738 }
1739
1740 static gboolean aspell_key_pressed(GtkWidget *widget,
1741                                    GdkEventKey *event,
1742                                    GtkAspell *gtkaspell)
1743 {
1744         if (event && (isascii(event->keyval) || event->keyval == GDK_KEY_Return)) {
1745                 gtk_accel_groups_activate(
1746                                 G_OBJECT(gtkaspell->parent_window),
1747                                 event->keyval, event->state);
1748         } else if (event && event->keyval == GDK_KEY_Escape) {
1749                 destroy_menu(NULL, gtkaspell);
1750         }
1751         return FALSE;
1752 }
1753
1754 /* make_sug_menu() - Add menus to accept this word for this session 
1755  * and to add it to personal dictionary 
1756  */
1757 static GSList *make_sug_menu(GtkAspell *gtkaspell) 
1758 {
1759         GtkWidget       *item;
1760         char    *caption;
1761         GtkTextView     *gtktext;
1762         GtkAccelGroup   *accel;
1763         GList           *l = gtkaspell->suggestions_list;
1764         gchar           *utf8buf;
1765         GSList *list = NULL;
1766         gtktext = gtkaspell->gtktext;
1767
1768         if (l == NULL)
1769                 return NULL;
1770
1771         accel = gtk_accel_group_new();
1772
1773         if (gtkaspell->accel_group) {
1774                 gtk_window_remove_accel_group(GTK_WINDOW(gtkaspell->parent_window), 
1775                                 gtkaspell->accel_group);
1776                 gtkaspell->accel_group = NULL;
1777         }
1778
1779         utf8buf  = g_strdup(l->data);
1780         caption = g_strdup_printf(_("\"%s\" unknown in %s"), 
1781                                   utf8buf, 
1782                                   gtkaspell->gtkaspeller->dictionary->dictname);
1783         item = gtk_menu_item_new_with_label(caption);
1784         g_free(utf8buf);
1785         gtk_widget_show(item);
1786         list = g_slist_append(list, item);
1787         gtk_misc_set_alignment(GTK_MISC(gtk_bin_get_child(GTK_BIN((item)))), 0.5, 0.5);
1788         g_free(caption);
1789
1790         item = gtk_menu_item_new();
1791         gtk_widget_show(item);
1792         list = g_slist_append(list, item);
1793
1794         item = gtk_menu_item_new_with_label(_("Accept in this session"));
1795         gtk_widget_show(item);
1796         list = g_slist_append(list, item);
1797         g_signal_connect(G_OBJECT(item), "activate",
1798                          G_CALLBACK(add_word_to_session_cb), 
1799                          gtkaspell);
1800         gtk_widget_add_accelerator(item, "activate", accel, GDK_KEY_space,
1801                                    GDK_CONTROL_MASK,
1802                                    GTK_ACCEL_LOCKED | GTK_ACCEL_VISIBLE);
1803
1804         item = gtk_menu_item_new_with_label(_("Add to personal dictionary"));
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_personal_cb), 
1809                          gtkaspell);
1810         gtk_widget_add_accelerator(item, "activate", accel, GDK_KEY_Return,
1811                                    GDK_CONTROL_MASK,
1812                                    GTK_ACCEL_LOCKED | GTK_ACCEL_VISIBLE);
1813
1814         item = gtk_menu_item_new_with_label(_("Replace with..."));
1815         gtk_widget_show(item);
1816         list = g_slist_append(list, item);
1817         g_signal_connect(G_OBJECT(item), "activate",
1818                          G_CALLBACK(replace_with_create_dialog_cb), 
1819                          gtkaspell);
1820         gtk_widget_add_accelerator(item, "activate", accel, GDK_KEY_R, 0,
1821                                    GTK_ACCEL_LOCKED | GTK_ACCEL_VISIBLE);
1822         gtk_widget_add_accelerator(item, "activate", accel, GDK_KEY_R, 
1823                                    GDK_CONTROL_MASK,
1824                                    GTK_ACCEL_LOCKED);
1825
1826         if (gtkaspell->use_alternate && gtkaspell->alternate_speller) {
1827                 caption = g_strdup_printf(_("Check with %s"), 
1828                         gtkaspell->alternate_speller->dictionary->dictname);
1829                 item = gtk_menu_item_new_with_label(caption);
1830                 g_free(caption);
1831                 gtk_widget_show(item);
1832                 list = g_slist_append(list, item);
1833                 g_signal_connect(G_OBJECT(item), "activate",
1834                                  G_CALLBACK(check_with_alternate_cb),
1835                                  gtkaspell);
1836                 gtk_widget_add_accelerator(item, "activate", accel, GDK_KEY_X, 0,
1837                                            GTK_ACCEL_LOCKED | GTK_ACCEL_VISIBLE);
1838                 gtk_widget_add_accelerator(item, "activate", accel, GDK_KEY_X, 
1839                                            GDK_CONTROL_MASK,
1840                                            GTK_ACCEL_LOCKED);
1841         }
1842
1843         item = gtk_menu_item_new();
1844         gtk_widget_show(item);
1845         list = g_slist_append(list, item);
1846
1847         l = l->next;
1848         if (l == NULL) {
1849                 item = gtk_menu_item_new_with_label(_("(no suggestions)"));
1850                 gtk_widget_show(item);
1851                 list = g_slist_append(list, item);
1852         } else {
1853                 GtkWidget *curmenu = NULL;
1854                 gint count = 0;
1855                 
1856                 do {
1857                         if (count == MENUCOUNT) {
1858                                 count -= MENUCOUNT;
1859
1860                                 item = gtk_menu_item_new_with_label(_("More..."));
1861                                 gtk_widget_show(item);
1862                                 if (curmenu)
1863                                         gtk_menu_shell_append(GTK_MENU_SHELL(curmenu), item);
1864                                 else 
1865                                         list = g_slist_append(list, item);
1866
1867                                 curmenu = gtk_menu_new();
1868                                 gtk_menu_item_set_submenu(GTK_MENU_ITEM(item),
1869                                                           curmenu);
1870                         }
1871
1872                         utf8buf  = g_strdup(l->data);
1873
1874                         item = gtk_menu_item_new_with_label(utf8buf);
1875                         g_free(utf8buf);
1876                         gtk_widget_show(item);
1877                         if (curmenu == NULL) {
1878                                 list = g_slist_append(list, item);
1879                         } else {
1880                                 gtk_menu_shell_append(GTK_MENU_SHELL(curmenu), item);
1881                         }
1882                         g_signal_connect(G_OBJECT(item), "activate",
1883                                          G_CALLBACK(replace_word_cb),
1884                                          gtkaspell);
1885
1886                         if (curmenu == NULL && count < MENUCOUNT) {
1887                                 gtk_widget_add_accelerator(item, "activate",
1888                                                            accel,
1889                                                            GDK_KEY_A + count, 0,
1890                                                            GTK_ACCEL_LOCKED | 
1891                                                            GTK_ACCEL_VISIBLE);
1892                                 gtk_widget_add_accelerator(item, "activate", 
1893                                                            accel,
1894                                                            GDK_KEY_A + count, 
1895                                                            GDK_CONTROL_MASK,
1896                                                            GTK_ACCEL_LOCKED);
1897                                 }
1898
1899                         count++;
1900
1901                 } while ((l = l->next) != NULL);
1902         }
1903
1904         gtk_window_add_accel_group
1905                 (GTK_WINDOW(gtkaspell->parent_window),
1906                  accel);
1907         gtkaspell->accel_group = accel;
1908
1909         return list;
1910 }
1911
1912 static GSList *populate_submenu(GtkAspell *gtkaspell)
1913 {
1914         GtkWidget *item, *submenu;
1915         gchar *dictname;
1916         GtkAspeller *gtkaspeller = NULL;
1917         GSList *list = NULL;
1918
1919         if (!gtkaspell)
1920                 return NULL;
1921
1922         gtkaspeller = gtkaspell->gtkaspeller;
1923         dictname = g_strdup_printf(_("Dictionary: %s"),
1924                                    gtkaspeller->dictionary->dictname);
1925         item = gtk_menu_item_new_with_label(dictname);
1926         gtk_misc_set_alignment(GTK_MISC(gtk_bin_get_child(GTK_BIN((item)))), 0.5, 0.5);
1927         g_free(dictname);
1928         gtk_widget_show(item);
1929         list = g_slist_append(list, item);
1930
1931         item = gtk_menu_item_new();
1932         gtk_widget_show(item);
1933         list = g_slist_append(list, item);
1934                 
1935         if (gtkaspell->use_alternate && gtkaspell->alternate_speller) {
1936                 dictname = g_strdup_printf(_("Use alternate (%s)"), 
1937                                 gtkaspell->alternate_speller->dictionary->dictname);
1938                 item = gtk_menu_item_new_with_label(dictname);
1939                 g_free(dictname);
1940                 g_signal_connect(G_OBJECT(item), "activate",
1941                                  G_CALLBACK(switch_to_alternate_cb),
1942                                  gtkaspell);
1943                 gtk_widget_show(item);
1944                 list = g_slist_append(list, item);
1945         }
1946
1947         item = gtk_check_menu_item_new_with_label(_("Use both dictionaries"));
1948         if (gtkaspell->use_both_dicts) {
1949                 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(item), TRUE);
1950         } 
1951         g_signal_connect(G_OBJECT(item), "activate",
1952                          G_CALLBACK(set_use_both_cb),
1953                          gtkaspell);
1954         gtk_widget_show(item);
1955         list = g_slist_append(list, item);
1956         
1957         item = gtk_menu_item_new();
1958         gtk_widget_show(item);
1959         list = g_slist_append(list, item);
1960         
1961         item = gtk_check_menu_item_new_with_label(_("Check while typing"));
1962         if (gtkaspell->check_while_typing)
1963                 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(item), TRUE);
1964         else    
1965                 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(item), FALSE);
1966         g_signal_connect(G_OBJECT(item), "activate",
1967                          G_CALLBACK(toggle_check_while_typing_cb),
1968                          gtkaspell);
1969         gtk_widget_show(item);
1970         list = g_slist_append(list, item);
1971
1972         item = gtk_menu_item_new();
1973         gtk_widget_show(item);
1974         list = g_slist_append(list, item);
1975
1976         submenu = gtk_menu_new();
1977         item = gtk_menu_item_new_with_label(_("Change dictionary"));
1978         gtk_menu_item_set_submenu(GTK_MENU_ITEM(item),submenu);
1979         gtk_widget_show(item);
1980         list = g_slist_append(list, item);
1981
1982         /* Dict list */
1983         if (gtkaspellcheckers->dictionary_list == NULL)
1984                 gtkaspell_get_dictionary_list(FALSE);
1985         {
1986                 GtkWidget * curmenu = submenu;
1987                 int count = 0;
1988                 Dictionary *dict;
1989                 GSList *tmp;
1990                 tmp = gtkaspellcheckers->dictionary_list;
1991                 
1992                 for (tmp = gtkaspellcheckers->dictionary_list; tmp != NULL; 
1993                                 tmp = g_slist_next(tmp)) {
1994                         if (count == MENUCOUNT) {
1995                                 GtkWidget *newmenu;
1996                                 
1997                                 newmenu = gtk_menu_new();
1998                                 item = gtk_menu_item_new_with_label(_("More..."));
1999                                 gtk_menu_item_set_submenu(GTK_MENU_ITEM(item), 
2000                                                           newmenu);
2001                                 
2002                                 gtk_menu_shell_append(GTK_MENU_SHELL(curmenu), item);
2003                                 gtk_widget_show(item);
2004                                 curmenu = newmenu;
2005                                 count = 0;
2006                         }
2007                         dict = (Dictionary *) tmp->data;
2008                         item = gtk_check_menu_item_new_with_label(dict->dictname);
2009                         g_object_set_data(G_OBJECT(item), "dict_name",
2010                                           dict->fullname); 
2011                         if (strcmp2(dict->fullname,
2012                             gtkaspell->gtkaspeller->dictionary->fullname))
2013                                 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(item), FALSE);
2014                         else {
2015                                 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(item), TRUE);
2016                                 gtk_widget_set_sensitive(GTK_WIDGET(item),
2017                                                          FALSE);
2018                         }
2019                         g_signal_connect(G_OBJECT(item), "activate",
2020                                          G_CALLBACK(change_dict_cb),
2021                                          gtkaspell);
2022                         gtk_widget_show(item);
2023                         gtk_menu_shell_append(GTK_MENU_SHELL(curmenu), item);
2024                         
2025                         count++;
2026                 }
2027         }  
2028         return list;
2029 }
2030
2031 GSList *gtkaspell_make_config_menu(GtkAspell *gtkaspell)
2032 {
2033         return populate_submenu(gtkaspell);
2034 }
2035
2036 static void set_menu_pos(GtkMenu *menu, gint *x, gint *y, 
2037                          gboolean *push_in, gpointer data)
2038 {
2039         GtkAspell       *gtkaspell = (GtkAspell *) data;
2040         gint             xx = 0, yy = 0;
2041         gint             sx,     sy;
2042         gint             wx,     wy;
2043         GtkTextView     *text = GTK_TEXT_VIEW(gtkaspell->gtktext);
2044         GtkTextBuffer   *textbuf;
2045         GtkTextIter      iter;
2046         GdkRectangle     rect;
2047         GtkRequisition   r;
2048
2049         textbuf = gtk_text_view_get_buffer(gtkaspell->gtktext);
2050         gtk_text_buffer_get_iter_at_mark(textbuf, &iter,
2051                                          gtk_text_buffer_get_insert(textbuf));
2052         gtk_text_view_get_iter_location(gtkaspell->gtktext, &iter, &rect);
2053         gtk_text_view_buffer_to_window_coords(text, GTK_TEXT_WINDOW_TEXT,
2054                                               rect.x, rect.y, 
2055                                               &rect.x, &rect.y);
2056
2057         gdk_window_get_origin(GTK_WIDGET(gtkaspell->gtktext)->window, &xx, &yy);
2058
2059         sx = gdk_screen_width();
2060         sy = gdk_screen_height();
2061
2062         gtk_widget_get_child_requisition(GTK_WIDGET(menu), &r);
2063
2064         wx =  r.width;
2065         wy =  r.height;
2066
2067         *x = rect.x + xx + 8;
2068
2069         *y = rect.y + rect.height + yy;
2070
2071         if (*x + wx > sx)
2072                 *x = sx - wx;
2073         if (*y + wy > sy)
2074                 *y = *y - wy - 10;
2075 }
2076
2077 /* change the current dictionary of gtkaspell
2078    - if always_set_alt_dict is set, the alternate dict is unconditionally set to the former
2079      current dictionary (common use: from menu callbacks)
2080    - if always_set_alt_dict is NOT set, the alternate dict will be set to the former
2081      current dictionary only if there is no alternate dictionary already set
2082      (this is when we need to set the current dictionary then the alternate one
2083      when creating a compose window, from the account and folder settings)
2084 */
2085 gboolean gtkaspell_change_dict(GtkAspell *gtkaspell, const gchar *dictionary,
2086                                                          gboolean always_set_alt_dict)
2087 {
2088         Dictionary      *dict;       
2089         GtkAspeller     *gtkaspeller;
2090
2091         cm_return_val_if_fail(gtkaspell, FALSE);
2092         cm_return_val_if_fail(dictionary, FALSE);
2093   
2094         dict = g_new0(Dictionary, 1);
2095         
2096         if (strrchr(dictionary, '/')) {
2097                 dict->fullname = g_strdup(strrchr(dictionary, '/')+1);
2098                 dict->dictname = g_strdup(strrchr(dictionary, '/')+1);
2099         } else {
2100                 dict->fullname = g_strdup(dictionary);
2101                 dict->dictname = g_strdup(dictionary);
2102         }
2103
2104         if (dict->fullname && strchr(dict->fullname, '-')) {
2105                 *(strchr(dict->fullname, '-')) = '\0';
2106                 *(strchr(dict->dictname, '-')) = '\0';
2107         }
2108
2109         if (!dict->fullname || !(*dict->fullname)) {
2110                 dictionary_delete(dict);
2111                 return FALSE;
2112         }
2113         gtkaspeller = gtkaspeller_new(dict);
2114
2115         if (!gtkaspeller) {
2116                 alertpanel_warning(_("The spell checker could not change dictionary.\n%s"), 
2117                                           gtkaspellcheckers->error_message);
2118         } else {
2119                 if (gtkaspell->use_alternate) {
2120                         if (gtkaspell->alternate_speller) {
2121                                 if (always_set_alt_dict) {
2122                                         gtkaspeller_delete(gtkaspell->alternate_speller);
2123                                         gtkaspell->alternate_speller = gtkaspell->gtkaspeller;
2124                                 } else
2125                                         gtkaspeller_delete(gtkaspell->gtkaspeller);
2126                         } else
2127                                 /* should never be reached as the dicts are always set
2128                                    to a default value */
2129                                 gtkaspell->alternate_speller = gtkaspell->gtkaspeller;
2130                 } else
2131                         gtkaspeller_delete(gtkaspell->gtkaspeller);
2132
2133                 gtkaspell->gtkaspeller = gtkaspeller;
2134         }
2135         
2136         dictionary_delete(dict);
2137
2138         return TRUE;    
2139 }
2140
2141 /* change the alternate dictionary of gtkaspell (doesn't affect the default dictionary) */
2142 gboolean gtkaspell_change_alt_dict(GtkAspell *gtkaspell, const gchar *alt_dictionary)
2143 {
2144         Dictionary      *dict;       
2145         GtkAspeller     *gtkaspeller;
2146
2147         cm_return_val_if_fail(gtkaspell, FALSE);
2148         cm_return_val_if_fail(alt_dictionary, FALSE);
2149   
2150         dict = g_new0(Dictionary, 1);
2151         if (strrchr(alt_dictionary, '/')) {
2152                 dict->fullname = g_strdup(strrchr(alt_dictionary, '/')+1);
2153                 dict->dictname = g_strdup(strrchr(alt_dictionary, '/')+1);
2154         } else {
2155                 dict->fullname = g_strdup(alt_dictionary);
2156                 dict->dictname = g_strdup(alt_dictionary);
2157         }
2158
2159         if (dict->fullname && strchr(dict->fullname, '-')) {
2160                 *(strchr(dict->fullname, '-')) = '\0';
2161                 *(strchr(dict->dictname, '-')) = '\0';
2162         }
2163
2164         if (!dict->fullname || !(*dict->fullname)) {
2165                 dictionary_delete(dict);
2166                 return FALSE;
2167         }
2168
2169         gtkaspeller = gtkaspeller_new(dict);
2170
2171         if (!gtkaspeller) {
2172                 alertpanel_warning(_("The spell checker could not change the alternate dictionary.\n%s"), 
2173                                           gtkaspellcheckers->error_message);
2174         } else {
2175                 if (gtkaspell->alternate_speller)
2176                         gtkaspeller_delete(gtkaspell->alternate_speller);
2177                 gtkaspell->alternate_speller = gtkaspeller;
2178         }
2179         
2180         dictionary_delete(dict);
2181
2182         return TRUE;    
2183 }
2184
2185 /* Menu call backs */
2186
2187 /* change_dict_cb() - Menu callback : change dict */
2188 static void change_dict_cb(GtkWidget *w, GtkAspell *gtkaspell)
2189 {
2190         gchar           *fullname;
2191   
2192         fullname = (gchar *) g_object_get_data(G_OBJECT(w), "dict_name");
2193         
2194         if (!strcmp2(fullname, _("None")))
2195                 return;
2196
2197         gtkaspell_change_dict(gtkaspell, fullname, TRUE);
2198         gtkaspell_dict_changed(gtkaspell);
2199
2200         if (gtkaspell->menu_changed_cb)
2201                 gtkaspell->menu_changed_cb(gtkaspell->menu_changed_data);
2202 }
2203
2204 static void switch_to_alternate_cb(GtkWidget *w,
2205                                    gpointer data)
2206 {
2207         GtkAspell *gtkaspell = (GtkAspell *) data;
2208         gtkaspell_use_alternate_dict(gtkaspell);
2209         gtkaspell_dict_changed(gtkaspell);
2210         
2211         if (gtkaspell->menu_changed_cb)
2212                 gtkaspell->menu_changed_cb(gtkaspell->menu_changed_data);
2213 }
2214
2215 /* Misc. helper functions */
2216
2217 static void set_point_continue(GtkAspell *gtkaspell)
2218 {
2219         GtkTextView  *gtktext;
2220
2221         gtktext = gtkaspell->gtktext;
2222
2223         gtkaspell->ctx.set_position(gtkaspell->ctx.data, gtkaspell->orig_pos);
2224
2225         if (gtkaspell->continue_check)
2226                 gtkaspell->continue_check((gpointer *) gtkaspell->ctx.data);
2227 }
2228
2229 static void allocate_color(GtkAspell *gtkaspell, gint rgbvalue)
2230 {
2231         GtkTextBuffer *buffer = gtk_text_view_get_buffer(gtkaspell->gtktext);
2232         GdkColor *color = &(gtkaspell->highlight);
2233
2234         /* Shameless copy from Sylpheed's gtkutils.c */
2235         color->pixel = 0L;
2236         color->red   = (int) (((gdouble)((rgbvalue & 0xff0000) >> 16) / 255.0)
2237                         * 65535.0);
2238         color->green = (int) (((gdouble)((rgbvalue & 0x00ff00) >>  8) / 255.0)
2239                         * 65535.0);
2240         color->blue  = (int) (((gdouble) (rgbvalue & 0x0000ff)        / 255.0)
2241                         * 65535.0);
2242
2243         if (rgbvalue != 0)
2244                 gtk_text_buffer_create_tag(buffer, "misspelled",
2245                                    "foreground-gdk", color, NULL);
2246         else
2247                 gtk_text_buffer_create_tag(buffer, "misspelled",
2248                                    "underline", PANGO_UNDERLINE_ERROR, NULL);
2249
2250 }
2251
2252 static void change_color(GtkAspell * gtkaspell, 
2253                          gint start, gint end,
2254                          gchar *newtext,
2255                          GdkColor *color) 
2256 {
2257         GtkTextView *gtktext;
2258         GtkTextBuffer *buffer;
2259         GtkTextIter startiter, enditer;
2260
2261         if (start > end)
2262                 return;
2263     
2264         gtktext = gtkaspell->gtktext;
2265     
2266         buffer = gtk_text_view_get_buffer(gtktext);
2267         gtk_text_buffer_get_iter_at_offset(buffer, &startiter, start);
2268         gtk_text_buffer_get_iter_at_offset(buffer, &enditer, end);
2269         if (color)
2270                 gtk_text_buffer_apply_tag_by_name(buffer, "misspelled",
2271                                                   &startiter, &enditer);
2272         else {
2273                 gtk_text_iter_forward_char(&enditer);
2274                 gtk_text_buffer_remove_tag_by_name(buffer, "misspelled",
2275                                                    &startiter, &enditer);
2276         }
2277 }
2278
2279 /* compare_dict () - compare 2 dict names */
2280 static gint compare_dict(Dictionary *a, Dictionary *b)
2281 {
2282         guint   aparts = 0,  bparts = 0;
2283         guint   i;
2284
2285         for (i=0; i < strlen(a->dictname); i++)
2286                 if (a->dictname[i] == '-')
2287                         aparts++;
2288         for (i=0; i < strlen(b->dictname); i++)
2289                 if (b->dictname[i] == '-')
2290                         bparts++;
2291
2292         if (aparts != bparts) 
2293                 return (aparts < bparts) ? -1 : +1;
2294         else {
2295                 gint compare;
2296                 compare = strcmp2(a->dictname, b->dictname);
2297                 if (!compare)
2298                         compare = strcmp2(a->fullname, b->fullname);
2299                 return compare;
2300         }
2301 }
2302
2303 static void dictionary_delete(Dictionary *dict)
2304 {
2305         g_free(dict->fullname);
2306         g_free(dict->dictname);
2307         g_free(dict);
2308 }
2309
2310 static Dictionary *dictionary_dup(const Dictionary *dict)
2311 {
2312         Dictionary *dict2;
2313
2314         dict2 = g_new(Dictionary, 1); 
2315
2316         dict2->fullname = g_strdup(dict->fullname);
2317         dict2->dictname = g_strdup(dict->dictname);
2318
2319         return dict2;
2320 }
2321
2322 void gtkaspell_free_suggestions_list(GtkAspell *gtkaspell)
2323 {
2324         GList *list;
2325
2326         for (list = gtkaspell->suggestions_list; list != NULL;
2327              list = list->next)
2328                 g_free(list->data);
2329
2330         g_list_free(gtkaspell->suggestions_list);
2331         
2332         gtkaspell->max_sug          = -1;
2333         gtkaspell->suggestions_list = NULL;
2334 }
2335
2336 static void reset_theword_data(GtkAspell *gtkaspell)
2337 {
2338         gtkaspell->start_pos     =  0;
2339         gtkaspell->end_pos       =  0;
2340         gtkaspell->theword[0]    =  0;
2341         gtkaspell->max_sug       = -1;
2342
2343         gtkaspell_free_suggestions_list(gtkaspell);
2344 }
2345
2346 static void free_checkers(gpointer elt, gpointer data)
2347 {
2348         GtkAspeller *gtkaspeller = elt;
2349
2350         cm_return_if_fail(gtkaspeller);
2351
2352         gtkaspeller_real_delete(gtkaspeller);
2353 }
2354
2355 static gint find_gtkaspeller(gconstpointer aa, gconstpointer bb)
2356 {
2357         Dictionary *a = ((GtkAspeller *) aa)->dictionary;
2358         Dictionary *b = ((GtkAspeller *) bb)->dictionary;
2359
2360         if (a && b && a->fullname && b->fullname)
2361                 return strcmp(a->fullname, b->fullname);
2362
2363         return 1;
2364 }
2365
2366 gchar *gtkaspell_get_default_dictionary(GtkAspell *gtkaspell)
2367 {
2368         if (gtkaspell && gtkaspell->gtkaspeller &&
2369                         gtkaspell->gtkaspeller->dictionary)
2370                 return gtkaspell->gtkaspeller->dictionary->dictname;
2371         else
2372                 return NULL;
2373 }
2374 #endif