2010-10-17 [colin] 3.7.6cvs56
[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_window_move(GTK_WINDOW(dialog), xx, yy);
1382
1383         g_signal_connect_swapped(G_OBJECT(dialog), "destroy",
1384                                  G_CALLBACK(gtk_widget_destroy), 
1385                                  G_OBJECT(dialog));
1386
1387         gtk_box_set_spacing (GTK_BOX (GTK_DIALOG (dialog)->vbox), 14);
1388         hbox = gtk_hbox_new (FALSE, 12);
1389         gtk_container_set_border_width (GTK_CONTAINER (hbox), 5);
1390         gtk_widget_show (hbox);
1391         gtk_box_pack_start (GTK_BOX (GTK_DIALOG (dialog)->vbox), hbox,
1392                             FALSE, FALSE, 0);
1393
1394         utf8buf  = g_strdup(gtkaspell->theword);
1395
1396         thelabel = g_strdup_printf(_("<span weight=\"bold\" "
1397                                         "size=\"larger\">Replace \"%s\" with: </span>"), 
1398                                    utf8buf);
1399         /* for title label */
1400         w_hbox = gtk_hbox_new(FALSE, 0);
1401         
1402         icon = gtk_image_new_from_stock(GTK_STOCK_DIALOG_QUESTION,
1403                                         GTK_ICON_SIZE_DIALOG); 
1404         gtk_misc_set_alignment (GTK_MISC (icon), 0.5, 0.0);
1405         gtk_box_pack_start (GTK_BOX (hbox), icon, FALSE, FALSE, 0);
1406         
1407         vbox = gtk_vbox_new (FALSE, 12);
1408         gtk_box_pack_start (GTK_BOX (hbox), vbox, TRUE, TRUE, 0);
1409         gtk_widget_show (vbox);
1410         
1411         label = gtk_label_new(thelabel);
1412         gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
1413         gtk_label_set_justify(GTK_LABEL(label), GTK_JUSTIFY_LEFT);
1414         gtk_label_set_use_markup (GTK_LABEL (label), TRUE);
1415         gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 0);
1416         gtk_label_set_line_wrap(GTK_LABEL(label), TRUE);
1417         if (!font_desc) {
1418                 gint size;
1419
1420                 size = pango_font_description_get_size
1421                         (label->style->font_desc);
1422                 font_desc = pango_font_description_new();
1423                 pango_font_description_set_weight
1424                         (font_desc, PANGO_WEIGHT_BOLD);
1425                 pango_font_description_set_size
1426                         (font_desc, size * PANGO_SCALE_LARGE);
1427         }
1428         if (font_desc)
1429                 gtk_widget_modify_font(label, font_desc);
1430         g_free(thelabel);
1431         
1432         entry = gtk_entry_new();
1433         gtkaspell->replace_entry = entry;
1434         gtk_entry_set_text(GTK_ENTRY(entry), utf8buf);
1435         gtk_editable_select_region(GTK_EDITABLE(entry), 0, -1);
1436         g_signal_connect(G_OBJECT(dialog),
1437                         "key_press_event",
1438                         G_CALLBACK(replace_key_pressed), gtkaspell);
1439         gtk_box_pack_start(GTK_BOX(vbox), entry, FALSE, FALSE, 0);
1440         g_free(utf8buf);  
1441
1442         label = gtk_label_new(_("Holding down Control key while pressing "
1443                                 "Enter\nwill learn from mistake.\n"));
1444         gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
1445         gtk_label_set_justify(GTK_LABEL(label), GTK_JUSTIFY_LEFT);
1446         gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 0);
1447         gtk_widget_show(label);
1448
1449         hbox = gtk_hbox_new(TRUE, 0);
1450
1451         gtkut_stock_button_set_create(&confirm_area,
1452                                       &cancel_button, GTK_STOCK_CANCEL,
1453                                       &ok_button, GTK_STOCK_OK,
1454                                       NULL, NULL);
1455
1456         gtk_box_pack_end(GTK_BOX(GTK_DIALOG(dialog)->action_area),
1457                          confirm_area, FALSE, FALSE, 0);
1458         gtk_container_set_border_width(GTK_CONTAINER(confirm_area), 5);
1459
1460         g_signal_connect(G_OBJECT(ok_button), "clicked",
1461                          G_CALLBACK(replace_with_supplied_word_cb), 
1462                          gtkaspell);
1463         g_signal_connect_swapped(G_OBJECT(ok_button), "clicked",
1464                                    G_CALLBACK(gtk_widget_destroy), 
1465                                    G_OBJECT(dialog));
1466
1467         g_signal_connect_swapped(G_OBJECT(cancel_button), "clicked",
1468                                  G_CALLBACK(gtk_widget_destroy), 
1469                                  G_OBJECT(dialog));
1470
1471         gtk_widget_grab_focus(entry);
1472
1473         gtk_window_set_modal(GTK_WINDOW(dialog), TRUE);
1474
1475         gtk_widget_show_all(dialog);
1476 }
1477
1478 static void gtkaspell_uncheck_all(GtkAspell * gtkaspell) 
1479 {
1480         GtkTextView *gtktext;
1481         GtkTextBuffer *buffer;
1482         GtkTextIter startiter, enditer;
1483         
1484         gtktext = gtkaspell->gtktext;
1485
1486         buffer = gtk_text_view_get_buffer(gtktext);
1487         gtk_text_buffer_get_iter_at_offset(buffer, &startiter, 0);
1488         gtk_text_buffer_get_iter_at_offset(buffer, &enditer,
1489                                    get_textview_buffer_charcount(gtktext)-1);
1490         gtk_text_buffer_remove_tag_by_name(buffer, "misspelled",
1491                                            &startiter, &enditer);
1492 }
1493
1494 static void toggle_check_while_typing_cb(GtkWidget *w, gpointer data)
1495 {
1496         GtkAspell *gtkaspell = (GtkAspell *) data;
1497
1498         gtkaspell->check_while_typing = gtkaspell->check_while_typing == FALSE;
1499
1500         if (!gtkaspell->check_while_typing)
1501                 gtkaspell_uncheck_all(gtkaspell);
1502         if (gtkaspell->menu_changed_cb)
1503                 gtkaspell->menu_changed_cb(gtkaspell->menu_changed_data);
1504 }
1505
1506 static GSList *create_empty_dictionary_list(void)
1507 {
1508         GSList *list = NULL;
1509         Dictionary *dict;
1510
1511         dict = g_new0(Dictionary, 1);
1512         dict->fullname = g_strdup(_("None"));
1513         dict->dictname = NULL;
1514
1515         return g_slist_append(list, dict);
1516 }
1517
1518 static void list_dict_cb(const char * const lang_tag,
1519                  const char * const provider_name,
1520                  const char * const provider_desc,
1521                  const char * const provider_file,
1522                  void * data)
1523 {
1524         GSList **list = (GSList **)data;
1525         Dictionary *dict = g_new0(Dictionary, 1);
1526         dict->fullname = g_strdup(lang_tag);
1527         dict->dictname = g_strdup(lang_tag);
1528
1529         if (g_slist_find_custom(*list, dict, 
1530                         (GCompareFunc) compare_dict) == NULL) {
1531                 debug_print("Aspell: found dictionary %s %s\n", dict->fullname,
1532                                 dict->dictname);
1533                 *list = g_slist_insert_sorted(*list, dict,
1534                                 (GCompareFunc) compare_dict);
1535         } else {
1536                 dictionary_delete(dict);
1537         }
1538 }
1539
1540 /* gtkaspell_get_dictionary_list() - returns list of dictionary names */
1541 static GSList *gtkaspell_get_dictionary_list(gint refresh)
1542 {
1543         GSList *list;
1544         EnchantBroker *broker;
1545
1546         if (!gtkaspellcheckers)
1547                 gtkaspell_checkers_init();
1548
1549         if (gtkaspellcheckers->dictionary_list && !refresh)
1550                 return gtkaspellcheckers->dictionary_list;
1551         else
1552                 gtkaspell_free_dictionary_list(
1553                                 gtkaspellcheckers->dictionary_list);
1554         list = NULL;
1555
1556         broker = enchant_broker_init();
1557
1558         enchant_broker_list_dicts(broker, list_dict_cb, &list);
1559
1560         enchant_broker_free(broker);
1561
1562         if (list == NULL){
1563                 
1564                 debug_print("Aspell: error when searching for dictionaries: "
1565                               "No dictionary found.\n");
1566                 list = create_empty_dictionary_list();
1567         }
1568
1569         gtkaspellcheckers->dictionary_list = list;
1570
1571         return list;
1572 }
1573
1574 static void gtkaspell_free_dictionary_list(GSList *list)
1575 {
1576         Dictionary *dict;
1577         GSList *walk;
1578         for (walk = list; walk != NULL; walk = g_slist_next(walk))
1579                 if (walk->data) {
1580                         dict = (Dictionary *) walk->data;
1581                         dictionary_delete(dict);
1582                 }                               
1583         g_slist_free(list);
1584 }
1585
1586 GtkTreeModel *gtkaspell_dictionary_store_new_with_refresh(gboolean refresh)
1587 {
1588         GSList *dict_list, *tmp;
1589         GtkListStore *store;
1590         GtkTreeIter iter;
1591         Dictionary *dict;
1592
1593         dict_list = gtkaspell_get_dictionary_list(refresh);
1594         cm_return_val_if_fail(dict_list, NULL);
1595
1596         store = gtk_list_store_new(SET_GTKASPELL_SIZE,
1597                                    G_TYPE_STRING,
1598                                    G_TYPE_STRING,
1599                                    -1);
1600         
1601         for (tmp = dict_list; tmp != NULL; tmp = g_slist_next(tmp)) {
1602                 dict = (Dictionary *) tmp->data;
1603                 
1604                 gtk_list_store_append(store, &iter);
1605                 gtk_list_store_set(store, &iter,
1606                                    SET_GTKASPELL_NAME, dict->dictname,
1607                                    SET_GTKASPELL_FULLNAME, dict->fullname,
1608                                    -1);
1609         }
1610
1611         return GTK_TREE_MODEL(store);
1612 }
1613
1614 GtkTreeModel *gtkaspell_dictionary_store_new(void)
1615 {
1616         return gtkaspell_dictionary_store_new_with_refresh
1617                 (TRUE);
1618 }
1619
1620 GtkWidget *gtkaspell_dictionary_combo_new(const gboolean refresh)
1621 {
1622         GtkWidget *combo;
1623         GtkCellRenderer *renderer;
1624
1625         combo = gtk_combo_box_new_with_model(
1626                         gtkaspell_dictionary_store_new_with_refresh(refresh));
1627         gtk_combo_box_set_active(GTK_COMBO_BOX(combo), 0);   
1628         gtk_widget_show(combo);
1629         
1630         renderer = gtk_cell_renderer_text_new();
1631         gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(combo), renderer, TRUE);
1632         gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(combo),renderer,
1633                                         "text", SET_GTKASPELL_NAME, NULL);
1634         
1635         return combo;
1636
1637
1638 gchar *gtkaspell_get_dictionary_menu_active_item(GtkComboBox *combo)
1639 {
1640         GtkTreeIter iter;
1641         GtkTreeModel *model;
1642         gchar *dict_fullname = NULL;
1643         
1644         cm_return_val_if_fail(GTK_IS_COMBO_BOX(combo), NULL);
1645         cm_return_val_if_fail(gtk_combo_box_get_active_iter(combo, &iter), NULL);
1646         
1647         model = gtk_combo_box_get_model(combo);
1648         if(model == NULL)
1649                 return NULL;
1650         
1651         gtk_tree_model_get(model, &iter,
1652                            SET_GTKASPELL_FULLNAME, &dict_fullname,
1653                            -1);
1654
1655         return dict_fullname;
1656 }
1657
1658 gint gtkaspell_set_dictionary_menu_active_item(GtkComboBox *combo,
1659                                                const gchar *dictionary)
1660 {
1661         GtkTreeModel *model;
1662         GtkTreeIter iter;
1663         gchar *dict_name = NULL;
1664         
1665         cm_return_val_if_fail(combo != NULL, 0);
1666         cm_return_val_if_fail(dictionary != NULL, 0);
1667         cm_return_val_if_fail(GTK_IS_COMBO_BOX(combo), 0);
1668
1669         if((model = gtk_combo_box_get_model(combo)) == NULL)
1670                 return 0;
1671         if((gtk_tree_model_get_iter_first(model, &iter)) == FALSE)
1672                 return 0;
1673         
1674         do {
1675                 gtk_tree_model_get(model, &iter,
1676                                    SET_GTKASPELL_FULLNAME, &dict_name,
1677                                    -1);
1678                 
1679                 if ((dict_name != NULL) && !strcmp2(dict_name, dictionary)) {
1680                         gtk_combo_box_set_active_iter(combo, &iter);
1681                         g_free(dict_name);
1682                         return 1;
1683                 }
1684                 
1685                 g_free(dict_name);
1686                 
1687         } while ((gtk_tree_model_iter_next(model, &iter)) == TRUE);
1688         
1689         return 0;
1690 }
1691
1692 void gtkaspell_use_alternate_dict(GtkAspell *gtkaspell)
1693 {
1694         GtkAspeller *tmp;
1695
1696         tmp = gtkaspell->gtkaspeller;
1697         gtkaspell->gtkaspeller = gtkaspell->alternate_speller;
1698         gtkaspell->alternate_speller = tmp;
1699 }
1700
1701 static void destroy_menu(GtkWidget *widget,
1702                              gpointer user_data) {
1703         GtkAspell *gtkaspell = (GtkAspell *)user_data;
1704
1705         if (gtkaspell->accel_group) {
1706                 gtk_window_remove_accel_group(GTK_WINDOW(gtkaspell->parent_window), 
1707                                 gtkaspell->accel_group);
1708                 gtkaspell->accel_group = NULL;
1709         }
1710 }
1711
1712 static gboolean aspell_key_pressed(GtkWidget *widget,
1713                                    GdkEventKey *event,
1714                                    GtkAspell *gtkaspell)
1715 {
1716         if (event && (isascii(event->keyval) || event->keyval == GDK_Return)) {
1717                 gtk_accel_groups_activate(
1718                                 G_OBJECT(gtkaspell->parent_window),
1719                                 event->keyval, event->state);
1720         } else if (event && event->keyval == GDK_Escape) {
1721                 destroy_menu(NULL, gtkaspell);
1722         }
1723         return FALSE;
1724 }
1725
1726 /* make_sug_menu() - Add menus to accept this word for this session 
1727  * and to add it to personal dictionary 
1728  */
1729 static GSList *make_sug_menu(GtkAspell *gtkaspell) 
1730 {
1731         GtkWidget       *item;
1732         char    *caption;
1733         GtkTextView     *gtktext;
1734         GtkAccelGroup   *accel;
1735         GList           *l = gtkaspell->suggestions_list;
1736         gchar           *utf8buf;
1737         GSList *list = NULL;
1738         gtktext = gtkaspell->gtktext;
1739
1740         if (l == NULL)
1741                 return NULL;
1742
1743         accel = gtk_accel_group_new();
1744
1745         if (gtkaspell->accel_group) {
1746                 gtk_window_remove_accel_group(GTK_WINDOW(gtkaspell->parent_window), 
1747                                 gtkaspell->accel_group);
1748                 gtkaspell->accel_group = NULL;
1749         }
1750
1751         utf8buf  = g_strdup(l->data);
1752         caption = g_strdup_printf(_("\"%s\" unknown in %s"), 
1753                                   utf8buf, 
1754                                   gtkaspell->gtkaspeller->dictionary->dictname);
1755         item = gtk_menu_item_new_with_label(caption);
1756         g_free(utf8buf);
1757         gtk_widget_show(item);
1758         list = g_slist_append(list, item);
1759         gtk_misc_set_alignment(GTK_MISC(gtk_bin_get_child(GTK_BIN((item)))), 0.5, 0.5);
1760         g_free(caption);
1761
1762         item = gtk_menu_item_new();
1763         gtk_widget_show(item);
1764         list = g_slist_append(list, item);
1765
1766         item = gtk_menu_item_new_with_label(_("Accept in this session"));
1767         gtk_widget_show(item);
1768         list = g_slist_append(list, item);
1769         g_signal_connect(G_OBJECT(item), "activate",
1770                          G_CALLBACK(add_word_to_session_cb), 
1771                          gtkaspell);
1772         gtk_widget_add_accelerator(item, "activate", accel, GDK_space,
1773                                    GDK_CONTROL_MASK,
1774                                    GTK_ACCEL_LOCKED | GTK_ACCEL_VISIBLE);
1775
1776         item = gtk_menu_item_new_with_label(_("Add to personal dictionary"));
1777         gtk_widget_show(item);
1778         list = g_slist_append(list, item);
1779         g_signal_connect(G_OBJECT(item), "activate",
1780                          G_CALLBACK(add_word_to_personal_cb), 
1781                          gtkaspell);
1782         gtk_widget_add_accelerator(item, "activate", accel, GDK_Return,
1783                                    GDK_CONTROL_MASK,
1784                                    GTK_ACCEL_LOCKED | GTK_ACCEL_VISIBLE);
1785
1786         item = gtk_menu_item_new_with_label(_("Replace with..."));
1787         gtk_widget_show(item);
1788         list = g_slist_append(list, item);
1789         g_signal_connect(G_OBJECT(item), "activate",
1790                          G_CALLBACK(replace_with_create_dialog_cb), 
1791                          gtkaspell);
1792         gtk_widget_add_accelerator(item, "activate", accel, GDK_R, 0,
1793                                    GTK_ACCEL_LOCKED | GTK_ACCEL_VISIBLE);
1794         gtk_widget_add_accelerator(item, "activate", accel, GDK_R, 
1795                                    GDK_CONTROL_MASK,
1796                                    GTK_ACCEL_LOCKED);
1797
1798         if (gtkaspell->use_alternate && gtkaspell->alternate_speller) {
1799                 caption = g_strdup_printf(_("Check with %s"), 
1800                         gtkaspell->alternate_speller->dictionary->dictname);
1801                 item = gtk_menu_item_new_with_label(caption);
1802                 g_free(caption);
1803                 gtk_widget_show(item);
1804                 list = g_slist_append(list, item);
1805                 g_signal_connect(G_OBJECT(item), "activate",
1806                                  G_CALLBACK(check_with_alternate_cb),
1807                                  gtkaspell);
1808                 gtk_widget_add_accelerator(item, "activate", accel, GDK_X, 0,
1809                                            GTK_ACCEL_LOCKED | GTK_ACCEL_VISIBLE);
1810                 gtk_widget_add_accelerator(item, "activate", accel, GDK_X, 
1811                                            GDK_CONTROL_MASK,
1812                                            GTK_ACCEL_LOCKED);
1813         }
1814
1815         item = gtk_menu_item_new();
1816         gtk_widget_show(item);
1817         list = g_slist_append(list, item);
1818
1819         l = l->next;
1820         if (l == NULL) {
1821                 item = gtk_menu_item_new_with_label(_("(no suggestions)"));
1822                 gtk_widget_show(item);
1823                 list = g_slist_append(list, item);
1824         } else {
1825                 GtkWidget *curmenu = NULL;
1826                 gint count = 0;
1827                 
1828                 do {
1829                         if (count == MENUCOUNT) {
1830                                 count -= MENUCOUNT;
1831
1832                                 item = gtk_menu_item_new_with_label(_("More..."));
1833                                 gtk_widget_show(item);
1834                                 if (curmenu)
1835                                         gtk_menu_shell_append(GTK_MENU_SHELL(curmenu), item);
1836                                 else 
1837                                         list = g_slist_append(list, item);
1838
1839                                 curmenu = gtk_menu_new();
1840                                 gtk_menu_item_set_submenu(GTK_MENU_ITEM(item),
1841                                                           curmenu);
1842                         }
1843
1844                         utf8buf  = g_strdup(l->data);
1845
1846                         item = gtk_menu_item_new_with_label(utf8buf);
1847                         g_free(utf8buf);
1848                         gtk_widget_show(item);
1849                         if (curmenu == NULL) {
1850                                 list = g_slist_append(list, item);
1851                         } else {
1852                                 gtk_menu_shell_append(GTK_MENU_SHELL(curmenu), item);
1853                         }
1854                         g_signal_connect(G_OBJECT(item), "activate",
1855                                          G_CALLBACK(replace_word_cb),
1856                                          gtkaspell);
1857
1858                         if (curmenu == NULL && count < MENUCOUNT) {
1859                                 gtk_widget_add_accelerator(item, "activate",
1860                                                            accel,
1861                                                            GDK_A + count, 0,
1862                                                            GTK_ACCEL_LOCKED | 
1863                                                            GTK_ACCEL_VISIBLE);
1864                                 gtk_widget_add_accelerator(item, "activate", 
1865                                                            accel,
1866                                                            GDK_A + count, 
1867                                                            GDK_CONTROL_MASK,
1868                                                            GTK_ACCEL_LOCKED);
1869                                 }
1870
1871                         count++;
1872
1873                 } while ((l = l->next) != NULL);
1874         }
1875
1876         gtk_window_add_accel_group
1877                 (GTK_WINDOW(gtkaspell->parent_window),
1878                  accel);
1879         gtkaspell->accel_group = accel;
1880
1881         return list;
1882 }
1883
1884 static GSList *populate_submenu(GtkAspell *gtkaspell)
1885 {
1886         GtkWidget *item, *submenu;
1887         gchar *dictname;
1888         GtkAspeller *gtkaspeller = NULL;
1889         GSList *list = NULL;
1890
1891         if (!gtkaspell)
1892                 return NULL;
1893
1894         gtkaspeller = gtkaspell->gtkaspeller;
1895         dictname = g_strdup_printf(_("Dictionary: %s"),
1896                                    gtkaspeller->dictionary->dictname);
1897         item = gtk_menu_item_new_with_label(dictname);
1898         gtk_misc_set_alignment(GTK_MISC(gtk_bin_get_child(GTK_BIN((item)))), 0.5, 0.5);
1899         g_free(dictname);
1900         gtk_widget_show(item);
1901         list = g_slist_append(list, item);
1902
1903         item = gtk_menu_item_new();
1904         gtk_widget_show(item);
1905         list = g_slist_append(list, item);
1906                 
1907         if (gtkaspell->use_alternate && gtkaspell->alternate_speller) {
1908                 dictname = g_strdup_printf(_("Use alternate (%s)"), 
1909                                 gtkaspell->alternate_speller->dictionary->dictname);
1910                 item = gtk_menu_item_new_with_label(dictname);
1911                 g_free(dictname);
1912                 g_signal_connect(G_OBJECT(item), "activate",
1913                                  G_CALLBACK(switch_to_alternate_cb),
1914                                  gtkaspell);
1915                 gtk_widget_show(item);
1916                 list = g_slist_append(list, item);
1917         }
1918
1919         item = gtk_check_menu_item_new_with_label(_("Use both dictionaries"));
1920         if (gtkaspell->use_both_dicts) {
1921                 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(item), TRUE);
1922         } 
1923         g_signal_connect(G_OBJECT(item), "activate",
1924                          G_CALLBACK(set_use_both_cb),
1925                          gtkaspell);
1926         gtk_widget_show(item);
1927         list = g_slist_append(list, item);
1928         
1929         item = gtk_menu_item_new();
1930         gtk_widget_show(item);
1931         list = g_slist_append(list, item);
1932         
1933         item = gtk_check_menu_item_new_with_label(_("Check while typing"));
1934         if (gtkaspell->check_while_typing)
1935                 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(item), TRUE);
1936         else    
1937                 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(item), FALSE);
1938         g_signal_connect(G_OBJECT(item), "activate",
1939                          G_CALLBACK(toggle_check_while_typing_cb),
1940                          gtkaspell);
1941         gtk_widget_show(item);
1942         list = g_slist_append(list, item);
1943
1944         item = gtk_menu_item_new();
1945         gtk_widget_show(item);
1946         list = g_slist_append(list, item);
1947
1948         submenu = gtk_menu_new();
1949         item = gtk_menu_item_new_with_label(_("Change dictionary"));
1950         gtk_menu_item_set_submenu(GTK_MENU_ITEM(item),submenu);
1951         gtk_widget_show(item);
1952         list = g_slist_append(list, item);
1953
1954         /* Dict list */
1955         if (gtkaspellcheckers->dictionary_list == NULL)
1956                 gtkaspell_get_dictionary_list(FALSE);
1957         {
1958                 GtkWidget * curmenu = submenu;
1959                 int count = 0;
1960                 Dictionary *dict;
1961                 GSList *tmp;
1962                 tmp = gtkaspellcheckers->dictionary_list;
1963                 
1964                 for (tmp = gtkaspellcheckers->dictionary_list; tmp != NULL; 
1965                                 tmp = g_slist_next(tmp)) {
1966                         if (count == MENUCOUNT) {
1967                                 GtkWidget *newmenu;
1968                                 
1969                                 newmenu = gtk_menu_new();
1970                                 item = gtk_menu_item_new_with_label(_("More..."));
1971                                 gtk_menu_item_set_submenu(GTK_MENU_ITEM(item), 
1972                                                           newmenu);
1973                                 
1974                                 gtk_menu_shell_append(GTK_MENU_SHELL(curmenu), item);
1975                                 gtk_widget_show(item);
1976                                 curmenu = newmenu;
1977                                 count = 0;
1978                         }
1979                         dict = (Dictionary *) tmp->data;
1980                         item = gtk_check_menu_item_new_with_label(dict->dictname);
1981                         g_object_set_data(G_OBJECT(item), "dict_name",
1982                                           dict->fullname); 
1983                         if (strcmp2(dict->fullname,
1984                             gtkaspell->gtkaspeller->dictionary->fullname))
1985                                 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(item), FALSE);
1986                         else {
1987                                 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(item), TRUE);
1988                                 gtk_widget_set_sensitive(GTK_WIDGET(item),
1989                                                          FALSE);
1990                         }
1991                         g_signal_connect(G_OBJECT(item), "activate",
1992                                          G_CALLBACK(change_dict_cb),
1993                                          gtkaspell);
1994                         gtk_widget_show(item);
1995                         gtk_menu_shell_append(GTK_MENU_SHELL(curmenu), item);
1996                         
1997                         count++;
1998                 }
1999         }  
2000         return list;
2001 }
2002
2003 GSList *gtkaspell_make_config_menu(GtkAspell *gtkaspell)
2004 {
2005         return populate_submenu(gtkaspell);
2006 }
2007
2008 static void set_menu_pos(GtkMenu *menu, gint *x, gint *y, 
2009                          gboolean *push_in, gpointer data)
2010 {
2011         GtkAspell       *gtkaspell = (GtkAspell *) data;
2012         gint             xx = 0, yy = 0;
2013         gint             sx,     sy;
2014         gint             wx,     wy;
2015         GtkTextView     *text = GTK_TEXT_VIEW(gtkaspell->gtktext);
2016         GtkTextBuffer   *textbuf;
2017         GtkTextIter      iter;
2018         GdkRectangle     rect;
2019         GtkRequisition   r;
2020
2021         textbuf = gtk_text_view_get_buffer(gtkaspell->gtktext);
2022         gtk_text_buffer_get_iter_at_mark(textbuf, &iter,
2023                                          gtk_text_buffer_get_insert(textbuf));
2024         gtk_text_view_get_iter_location(gtkaspell->gtktext, &iter, &rect);
2025         gtk_text_view_buffer_to_window_coords(text, GTK_TEXT_WINDOW_TEXT,
2026                                               rect.x, rect.y, 
2027                                               &rect.x, &rect.y);
2028
2029         gdk_window_get_origin(GTK_WIDGET(gtkaspell->gtktext)->window, &xx, &yy);
2030
2031         sx = gdk_screen_width();
2032         sy = gdk_screen_height();
2033
2034         gtk_widget_get_child_requisition(GTK_WIDGET(menu), &r);
2035
2036         wx =  r.width;
2037         wy =  r.height;
2038
2039         *x = rect.x + xx + 8;
2040
2041         *y = rect.y + rect.height + yy;
2042
2043         if (*x + wx > sx)
2044                 *x = sx - wx;
2045         if (*y + wy > sy)
2046                 *y = *y - wy - 10;
2047 }
2048
2049 /* change the current dictionary of gtkaspell
2050    - if always_set_alt_dict is set, the alternate dict is unconditionally set to the former
2051      current dictionary (common use: from menu callbacks)
2052    - if always_set_alt_dict is NOT set, the alternate dict will be set to the former
2053      current dictionary only if there is no alternate dictionary already set
2054      (this is when we need to set the current dictionary then the alternate one
2055      when creating a compose window, from the account and folder settings)
2056 */
2057 gboolean gtkaspell_change_dict(GtkAspell *gtkaspell, const gchar *dictionary,
2058                                                          gboolean always_set_alt_dict)
2059 {
2060         Dictionary      *dict;       
2061         GtkAspeller     *gtkaspeller;
2062
2063         cm_return_val_if_fail(gtkaspell, FALSE);
2064         cm_return_val_if_fail(dictionary, FALSE);
2065   
2066         dict = g_new0(Dictionary, 1);
2067         
2068         if (strrchr(dictionary, '/')) {
2069                 dict->fullname = g_strdup(strrchr(dictionary, '/')+1);
2070                 dict->dictname = g_strdup(strrchr(dictionary, '/')+1);
2071         } else {
2072                 dict->fullname = g_strdup(dictionary);
2073                 dict->dictname = g_strdup(dictionary);
2074         }
2075
2076         if (dict->fullname && strchr(dict->fullname, '-')) {
2077                 *(strchr(dict->fullname, '-')) = '\0';
2078                 *(strchr(dict->dictname, '-')) = '\0';
2079         }
2080
2081         if (!dict->fullname || !(*dict->fullname)) {
2082                 dictionary_delete(dict);
2083                 return FALSE;
2084         }
2085         gtkaspeller = gtkaspeller_new(dict);
2086
2087         if (!gtkaspeller) {
2088                 alertpanel_warning(_("The spell checker could not change dictionary.\n%s"), 
2089                                           gtkaspellcheckers->error_message);
2090         } else {
2091                 if (gtkaspell->use_alternate) {
2092                         if (gtkaspell->alternate_speller) {
2093                                 if (always_set_alt_dict) {
2094                                         gtkaspeller_delete(gtkaspell->alternate_speller);
2095                                         gtkaspell->alternate_speller = gtkaspell->gtkaspeller;
2096                                 } else
2097                                         gtkaspeller_delete(gtkaspell->gtkaspeller);
2098                         } else
2099                                 /* should never be reached as the dicts are always set
2100                                    to a default value */
2101                                 gtkaspell->alternate_speller = gtkaspell->gtkaspeller;
2102                 } else
2103                         gtkaspeller_delete(gtkaspell->gtkaspeller);
2104
2105                 gtkaspell->gtkaspeller = gtkaspeller;
2106         }
2107         
2108         dictionary_delete(dict);
2109
2110         return TRUE;    
2111 }
2112
2113 /* change the alternate dictionary of gtkaspell (doesn't affect the default dictionary) */
2114 gboolean gtkaspell_change_alt_dict(GtkAspell *gtkaspell, const gchar *alt_dictionary)
2115 {
2116         Dictionary      *dict;       
2117         GtkAspeller     *gtkaspeller;
2118
2119         cm_return_val_if_fail(gtkaspell, FALSE);
2120         cm_return_val_if_fail(alt_dictionary, FALSE);
2121   
2122         dict = g_new0(Dictionary, 1);
2123         if (strrchr(alt_dictionary, '/')) {
2124                 dict->fullname = g_strdup(strrchr(alt_dictionary, '/')+1);
2125                 dict->dictname = g_strdup(strrchr(alt_dictionary, '/')+1);
2126         } else {
2127                 dict->fullname = g_strdup(alt_dictionary);
2128                 dict->dictname = g_strdup(alt_dictionary);
2129         }
2130
2131         if (dict->fullname && strchr(dict->fullname, '-')) {
2132                 *(strchr(dict->fullname, '-')) = '\0';
2133                 *(strchr(dict->dictname, '-')) = '\0';
2134         }
2135
2136         if (!dict->fullname || !(*dict->fullname)) {
2137                 dictionary_delete(dict);
2138                 return FALSE;
2139         }
2140
2141         gtkaspeller = gtkaspeller_new(dict);
2142
2143         if (!gtkaspeller) {
2144                 alertpanel_warning(_("The spell checker could not change the alternate dictionary.\n%s"), 
2145                                           gtkaspellcheckers->error_message);
2146         } else {
2147                 if (gtkaspell->alternate_speller)
2148                         gtkaspeller_delete(gtkaspell->alternate_speller);
2149                 gtkaspell->alternate_speller = gtkaspeller;
2150         }
2151         
2152         dictionary_delete(dict);
2153
2154         return TRUE;    
2155 }
2156
2157 /* Menu call backs */
2158
2159 /* change_dict_cb() - Menu callback : change dict */
2160 static void change_dict_cb(GtkWidget *w, GtkAspell *gtkaspell)
2161 {
2162         gchar           *fullname;
2163   
2164         fullname = (gchar *) g_object_get_data(G_OBJECT(w), "dict_name");
2165         
2166         if (!strcmp2(fullname, _("None")))
2167                 return;
2168
2169         gtkaspell_change_dict(gtkaspell, fullname, TRUE);
2170         gtkaspell_dict_changed(gtkaspell);
2171
2172         if (gtkaspell->menu_changed_cb)
2173                 gtkaspell->menu_changed_cb(gtkaspell->menu_changed_data);
2174 }
2175
2176 static void switch_to_alternate_cb(GtkWidget *w,
2177                                    gpointer data)
2178 {
2179         GtkAspell *gtkaspell = (GtkAspell *) data;
2180         gtkaspell_use_alternate_dict(gtkaspell);
2181         gtkaspell_dict_changed(gtkaspell);
2182         
2183         if (gtkaspell->menu_changed_cb)
2184                 gtkaspell->menu_changed_cb(gtkaspell->menu_changed_data);
2185 }
2186
2187 /* Misc. helper functions */
2188
2189 static void set_point_continue(GtkAspell *gtkaspell)
2190 {
2191         GtkTextView  *gtktext;
2192
2193         gtktext = gtkaspell->gtktext;
2194
2195         gtkaspell->ctx.set_position(gtkaspell->ctx.data, gtkaspell->orig_pos);
2196
2197         if (gtkaspell->continue_check)
2198                 gtkaspell->continue_check((gpointer *) gtkaspell->ctx.data);
2199 }
2200
2201 static void allocate_color(GtkAspell *gtkaspell, gint rgbvalue)
2202 {
2203         GtkTextBuffer *buffer = gtk_text_view_get_buffer(gtkaspell->gtktext);
2204         GdkColor *color = &(gtkaspell->highlight);
2205
2206         /* Shameless copy from Sylpheed's gtkutils.c */
2207         color->pixel = 0L;
2208         color->red   = (int) (((gdouble)((rgbvalue & 0xff0000) >> 16) / 255.0)
2209                         * 65535.0);
2210         color->green = (int) (((gdouble)((rgbvalue & 0x00ff00) >>  8) / 255.0)
2211                         * 65535.0);
2212         color->blue  = (int) (((gdouble) (rgbvalue & 0x0000ff)        / 255.0)
2213                         * 65535.0);
2214
2215         if (rgbvalue != 0)
2216                 gtk_text_buffer_create_tag(buffer, "misspelled",
2217                                    "foreground-gdk", color, NULL);
2218         else
2219                 gtk_text_buffer_create_tag(buffer, "misspelled",
2220                                    "underline", PANGO_UNDERLINE_ERROR, NULL);
2221
2222 }
2223
2224 static void change_color(GtkAspell * gtkaspell, 
2225                          gint start, gint end,
2226                          gchar *newtext,
2227                          GdkColor *color) 
2228 {
2229         GtkTextView *gtktext;
2230         GtkTextBuffer *buffer;
2231         GtkTextIter startiter, enditer;
2232
2233         if (start > end)
2234                 return;
2235     
2236         gtktext = gtkaspell->gtktext;
2237     
2238         buffer = gtk_text_view_get_buffer(gtktext);
2239         gtk_text_buffer_get_iter_at_offset(buffer, &startiter, start);
2240         gtk_text_buffer_get_iter_at_offset(buffer, &enditer, end);
2241         if (color)
2242                 gtk_text_buffer_apply_tag_by_name(buffer, "misspelled",
2243                                                   &startiter, &enditer);
2244         else {
2245                 gtk_text_iter_forward_char(&enditer);
2246                 gtk_text_buffer_remove_tag_by_name(buffer, "misspelled",
2247                                                    &startiter, &enditer);
2248         }
2249 }
2250
2251 /* compare_dict () - compare 2 dict names */
2252 static gint compare_dict(Dictionary *a, Dictionary *b)
2253 {
2254         guint   aparts = 0,  bparts = 0;
2255         guint   i;
2256
2257         for (i=0; i < strlen(a->dictname); i++)
2258                 if (a->dictname[i] == '-')
2259                         aparts++;
2260         for (i=0; i < strlen(b->dictname); i++)
2261                 if (b->dictname[i] == '-')
2262                         bparts++;
2263
2264         if (aparts != bparts) 
2265                 return (aparts < bparts) ? -1 : +1;
2266         else {
2267                 gint compare;
2268                 compare = strcmp2(a->dictname, b->dictname);
2269                 if (!compare)
2270                         compare = strcmp2(a->fullname, b->fullname);
2271                 return compare;
2272         }
2273 }
2274
2275 static void dictionary_delete(Dictionary *dict)
2276 {
2277         g_free(dict->fullname);
2278         g_free(dict->dictname);
2279         g_free(dict);
2280 }
2281
2282 static Dictionary *dictionary_dup(const Dictionary *dict)
2283 {
2284         Dictionary *dict2;
2285
2286         dict2 = g_new(Dictionary, 1); 
2287
2288         dict2->fullname = g_strdup(dict->fullname);
2289         dict2->dictname = g_strdup(dict->dictname);
2290
2291         return dict2;
2292 }
2293
2294 void gtkaspell_free_suggestions_list(GtkAspell *gtkaspell)
2295 {
2296         GList *list;
2297
2298         for (list = gtkaspell->suggestions_list; list != NULL;
2299              list = list->next)
2300                 g_free(list->data);
2301
2302         g_list_free(gtkaspell->suggestions_list);
2303         
2304         gtkaspell->max_sug          = -1;
2305         gtkaspell->suggestions_list = NULL;
2306 }
2307
2308 static void reset_theword_data(GtkAspell *gtkaspell)
2309 {
2310         gtkaspell->start_pos     =  0;
2311         gtkaspell->end_pos       =  0;
2312         gtkaspell->theword[0]    =  0;
2313         gtkaspell->max_sug       = -1;
2314
2315         gtkaspell_free_suggestions_list(gtkaspell);
2316 }
2317
2318 static void free_checkers(gpointer elt, gpointer data)
2319 {
2320         GtkAspeller *gtkaspeller = elt;
2321
2322         cm_return_if_fail(gtkaspeller);
2323
2324         gtkaspeller_real_delete(gtkaspeller);
2325 }
2326
2327 static gint find_gtkaspeller(gconstpointer aa, gconstpointer bb)
2328 {
2329         Dictionary *a = ((GtkAspeller *) aa)->dictionary;
2330         Dictionary *b = ((GtkAspeller *) bb)->dictionary;
2331
2332         if (a && b && a->fullname && b->fullname)
2333                 return strcmp(a->fullname, b->fullname);
2334
2335         return 1;
2336 }
2337
2338 gchar *gtkaspell_get_default_dictionary(GtkAspell *gtkaspell)
2339 {
2340         if (gtkaspell && gtkaspell->gtkaspeller &&
2341                         gtkaspell->gtkaspeller->dictionary)
2342                 return gtkaspell->gtkaspeller->dictionary->dictname;
2343         else
2344                 return NULL;
2345 }
2346 #endif