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