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