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