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