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