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