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