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