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