added support to view text/enriched
[claws.git] / src / gtkspell.c
1 /* gtkpspell - a spell-checking addon for GtkText
2  * Copyright (c) 2000 Evan Martin.
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Lesser General Public
6  * License as published by the Free Software Foundation; either
7  * version 2 of the License, or (at your option) any later version.
8  *
9  * This library is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * Lesser General Public License for more details.
13  *
14  * You should have received a copy of the GNU Lesser General Public
15  * License along with this library; if not, write to the Free Software
16  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307  USA
17  */
18
19 /*
20  * Stuphead: (C) 2000,2001 Grigroy Bakunov, Sergey Pinaev
21  * Adapted for Sylpheed (Claws) (c) 2001 by Hiroyuki Yamamoto & 
22  * The Sylpheed Claws Team.
23  * Adapted for pspell (c) 2001 Melvin Hadasht
24  */
25  
26 #if defined(HAVE_CONFIG_H)
27 #include "config.h"
28 #endif
29
30 #if USE_PSPELL
31 #include "intl.h"
32
33 #include <gtk/gtk.h>
34 #include <sys/types.h>
35 #include <sys/wait.h>
36 #include <unistd.h>
37 #include <stdio.h>
38 #include <signal.h>
39 #include <ctype.h>
40 #include <string.h>
41 #include <stdlib.h>
42 #include <errno.h>
43 #include <stdio.h>
44 #include <sys/time.h>
45 #include <fcntl.h>
46 #include <time.h>
47 #include <prefs_common.h>
48 #include <utils.h>
49
50 #include <dirent.h>
51
52 #include <gtk/gtkoptionmenu.h>
53 #include <gtk/gtkmenu.h>
54 #include <gtk/gtkmenuitem.h>
55
56 #include "gtkxtext.h"
57
58 #include "gtkspell.h"
59
60 #include <pspell/pspell.h>
61 /* size of the text buffer used in various word-processing routines. */
62 #define BUFSIZE 1024
63
64 /* number of suggestions to display on each menu. */
65 #define MENUCOUNT 15
66
67 /******************************************************************************/
68
69 /* Function called from menus */
70
71 static void add_word_to_session         (GtkWidget *w, GtkPspell *d);
72 static void add_word_to_personal        (GtkWidget *w, GtkPspell *d);
73 static void set_sug_mode                (GtkWidget *w, GtkPspell *gtkpspell);
74 static void set_learn_mode              (GtkWidget *w, GtkPspell *gtkpspell);
75 static void check_all                   (GtkWidget *w, GtkPspell *gtkpspell);
76 static void menu_change_dict            (GtkWidget *w, GtkPspell *gtkpspell);
77 static void entry_insert_cb             (GtkXText *gtktext, gchar *newtext, 
78                                          guint len, guint *ppos, 
79                                          GtkPspell *gtkpspell);
80 static gint compare_dict                (Dictionary *a, Dictionary *b);
81 guchar *convert_to_pspell_encoding      (const guchar *encoding);
82
83
84 /* gtkspellconfig - only one config per session */
85 GtkPspellConfig * gtkpspellconfig;
86
87 /* TODO: configurable */
88 static GdkColor highlight = { 0, 255 * 256, 0, 0 };
89
90 /******************************************************************************/
91
92 /* gtkspell_init() - run the first pspell_config from which every
93  * new config is cloned 
94  */
95 GtkPspellConfig * gtkpspell_init()
96 {
97         return new_pspell_config();
98 }
99
100 /* gtkspell_finished() - Finish all. No more spelling. Called when the 
101  * program ends 
102  */
103 void gtkpspell_finished(GtkPspellConfig *gtkpspellconfig)
104 {
105         if (gtkpspellconfig) {
106                 delete_pspell_config(gtkpspellconfig);
107                 gtkpspellconfig = NULL;
108         }
109 }
110
111 /* gtkspell_running - Test if there is a manager running 
112  */
113 int gtkpspell_running(GtkPspell * gtkpspell) 
114 {
115         return (gtkpspell->config!=NULL);
116 }
117
118 /* gtkspell_new() - creates a new session if a gtkpspellconfig exists. The 
119  * settings are defaults.  If no path/dict is set afterwards, the default 
120  * one is used  
121  */
122 GtkPspell * gtkpspell_new(GtkPspellConfig *gtkpspellconfig)
123 {
124         GtkPspell *gtkpspell;
125
126         if (gtkpspellconfig == NULL) {
127                 gtkpspellconfig = gtkpspell_init();
128                 if (gtkpspellconfig == NULL) {
129                         debug_print(_("Pspell could not be started."));
130                         prefs_common.enable_pspell=FALSE;
131                         return NULL;
132                 }
133         }
134
135         gtkpspell               = g_new(GtkPspell ,1);
136         gtkpspell->config       = gtkpspellconfig;
137         gtkpspell->possible_err = new_pspell_manager(gtkpspell->config);
138         gtkpspell->checker      = NULL;
139
140         if (pspell_error_number(gtkpspell->possible_err) != 0) {
141                 debug_print(_("Pspell error : %s\n"), pspell_error_message(gtkpspell->possible_err));
142                 delete_pspell_can_have_error( gtkpspell->possible_err );
143         }
144         else {
145                 gtkpspell->checker = to_pspell_manager(gtkpspell->possible_err);
146         }
147  
148         gtkpspell->dictionary_list = NULL;
149         gtkpspell->path            = NULL;
150         gtkpspell->dict            = NULL;
151         gtkpspell->mode            = PSPELL_FASTMODE;
152         gtkpspell->learn           = TRUE;
153         gtkpspell->gtktext         = NULL;
154         return gtkpspell;
155 }
156
157 /* gtkspell_new_with_config() - Creates a new session and set path/dic 
158  */
159 GtkPspell *gtkpspell_new_with_config(GtkPspellConfig *gtkpspellconfig, 
160                                      guchar *path, guchar *dict, 
161                                      guint mode, const guchar *encoding)
162 {
163         GtkPspell *gtkpspell;
164
165         if (gtkpspellconfig == NULL) {
166                 gtkpspellconfig=gtkpspell_init();
167                 if (gtkpspellconfig == NULL) {
168                         debug_print(_("Pspell could not be started."));
169                         prefs_common.enable_pspell = FALSE;
170                         return NULL;
171                 }
172         }
173
174         gtkpspell                  = g_new( GtkPspell ,1);
175         gtkpspell->path            = NULL;
176         gtkpspell->dict            = NULL;
177         gtkpspell->dictionary_list = NULL;
178         gtkpspell->gtktext         = NULL;
179   
180         gtkpspell->config          = pspell_config_clone(gtkpspellconfig);
181         gtkpspell->mode            = PSPELL_FASTMODE;
182         gtkpspell->learn           = TRUE;
183         
184         if (!set_path_and_dict(gtkpspell, gtkpspell->config, path, dict)) {
185                 debug_print(_("Pspell could not be configured."));
186                 gtkpspell = gtkpspell_delete(gtkpspell);
187                 return gtkpspell;
188         }
189         
190         if (encoding) {
191                 char *pspell_encoding;
192                 pspell_encoding = convert_to_pspell_encoding (encoding);
193                 debug_print(_("Pspell encoding: asked: %s changed to: %s\n"), encoding, pspell_encoding);
194                 pspell_config_replace(gtkpspell->config, "encoding", (const char *)pspell_encoding);
195                 if (pspell_config_error_number(gtkpspell->config) !=0 ) {
196                         debug_print(_("Pspell encoding error: %s\nSwitching to iso8859-1 (sorry)\n"), 
197                                     pspell_config_error_message(gtkpspell->config));
198                         pspell_config_replace(gtkpspell->config, "encoding", "iso8859-1");
199                 }
200                 g_free(pspell_encoding);
201         }
202
203         gtkpspell->possible_err = new_pspell_manager(gtkpspell->config);
204         gtkpspell->checker      = NULL;
205
206         if (pspell_error_number(gtkpspell->possible_err) != 0) {
207                 debug_print(_("Pspell error : %s\n"), pspell_error_message(gtkpspell->possible_err));
208                 delete_pspell_can_have_error(gtkpspell->possible_err);
209                 gtkpspell = gtkpspell_delete(gtkpspell);
210         }
211         else {
212                 gtkpspell->checker = to_pspell_manager( gtkpspell->possible_err );
213         }
214         return gtkpspell;
215 }
216
217 /* gtkspell_delete() - Finishes a session 
218  */
219 GtkPspell *gtkpspell_delete( GtkPspell *gtkpspell )
220 {
221         if ( gtkpspell->checker ) {
222                 /* First save all word lists */
223                 pspell_manager_save_all_word_lists(gtkpspell->checker);
224                 delete_pspell_manager(gtkpspell->checker);
225                 gtkpspell->checker      = NULL;
226                 gtkpspell->possible_err = NULL; /* checker is a cast from possible_err */
227         }
228
229         if (gtkpspell->dictionary_list)
230                 gtkpspell_free_dictionary_list(gtkpspell->dictionary_list);
231
232         g_free(gtkpspell->path);
233         g_free(gtkpspell->dict);
234         gtkpspell->path = NULL;
235         gtkpspell->dict = NULL;
236
237         g_free(gtkpspell);
238         return NULL;
239 }
240   
241 int set_path_and_dict(GtkPspell *gtkpspell, PspellConfig *config,
242                       guchar *path, guchar * dict)
243 {
244         guchar *module   = NULL;
245         guchar *language = NULL;
246         guchar *spelling = NULL;
247         guchar *jargon   = NULL;
248         guchar  buf[BUFSIZE];
249         guchar *end;
250         guchar *temppath;
251         guchar *tempdict;
252
253         /* Change nothing if any of path/dict/config is NULL */
254         g_return_val_if_fail(path, 0);
255         g_return_val_if_fail(dict, 0);
256         g_return_val_if_fail(config, 0);
257         
258         /* This is done, so we can free gtkpspell->path, even if it was
259          * given as an argument of the function */
260         temppath = g_strdup(path);
261         g_free(gtkpspell->path);
262   
263         /* pspell dict name format :                         */
264         /*   <lang>[[-<spelling>[-<jargon>]]-<module>.pwli   */
265         /* Strip off path                                    */
266         
267         if (!strrchr(dict,G_DIR_SEPARATOR)) {
268                 /* plain dict name */
269                 strncpy(buf,dict,BUFSIZE-1);
270         }
271         else { 
272                 /* strip path */
273                 strncpy(buf, strrchr(dict, G_DIR_SEPARATOR)+1, BUFSIZE-1);
274         }
275         
276         g_free(gtkpspell->dict);
277
278         /* Ensure no buffers overflows if the dict is to long */
279         buf[BUFSIZE-1] = 0x00;
280
281         language = buf;
282         if ((module = strrchr(buf, '-')) != NULL) {
283                 module++;
284                 if ((end = strrchr(module, '.')) != NULL)
285                         end[0] = 0x00;
286         }
287
288         /* In tempdict, we have only the dict name, without path nor
289            extension. Useful in the popup menus */
290
291         tempdict = g_strdup(buf);
292
293         /* Probably I am too paranoied... */
294         if (!(language[0] != 0x00 && language[1] != 0x00))
295                 language = NULL;
296         else {
297                 spelling = strchr(language, '-');
298                 if (spelling != NULL) {
299                         spelling[0] = 0x00;
300                         spelling++;
301                 }
302                 if (spelling != module) {
303                         if ((end = strchr(spelling, '-')) != NULL) {
304                                 end[0] = 0x00;
305                                 jargon = end + 1;
306                                 if (jargon != module)
307                                         if ((end = strchr(jargon, '-')) != NULL)
308                                                 end[0] = 0x00;
309                                         else
310                                                 jargon = NULL;
311                                 else
312                                         jargon = NULL;
313                         }
314                         else
315                                 spelling = NULL;
316                 }
317                 else 
318                         spelling = NULL;
319         }
320
321         debug_print(_("Language : %s\nSpelling: %s\nJargon: %s\nModule: %s\n"),
322                     language, spelling, jargon, module);
323         
324         if (temppath[strlen(temppath)-1] == G_DIR_SEPARATOR) 
325                 temppath[strlen(temppath)-1]= 0;
326         if (temppath) {
327                 pspell_config_replace(config, "add-word-list-path", temppath);
328                 debug_print(_("Pspell config: added path %s\n"), pspell_config_retrieve(config, "word-list-path"));
329                 if (pspell_config_error_number(config))
330                         debug_print(_("Pspell config: %s\n"), pspell_config_error_message(config));
331         }
332         if (language) 
333                 pspell_config_replace(config, "language-tag", language);
334         if (spelling) 
335                 pspell_config_replace(config, "spelling", spelling);
336         if (jargon)
337                 pspell_config_replace(config, "jargon", jargon);
338         if (module)
339                 pspell_config_replace(config, "module", module);
340
341         switch(gtkpspell->mode) {
342         case PSPELL_FASTMODE: 
343                 pspell_config_replace(config, "sug-mode", "fast");
344                 break;
345         case PSPELL_NORMALMODE: 
346                 pspell_config_replace(config, "sug-mode", "normal");
347                 break;
348         case PSPELL_BADSPELLERMODE: 
349                 pspell_config_replace(config, "sug-mode", "bad-spellers");
350                 break;
351         }
352   
353         gtkpspell->path = g_strdup(temppath);
354         gtkpspell->dict = g_strdup(tempdict);
355         g_free(temppath);
356         g_free(tempdict);
357
358         return TRUE;
359 }
360
361
362 /* gtkpspell_set_path_and_dict() - Set path and dict. The session is 
363  * resetted. 
364  * FALSE on error, TRUE on success */
365 int gtkpspell_set_path_and_dict(GtkPspell * gtkpspell, guchar * path, 
366                                 guchar * dict)
367 {
368         PspellConfig * config2;
369
370         /* It seems changing an already running config is not the way to go
371          */
372
373         config2 = pspell_config_clone(gtkpspell->config);
374
375         if (gtkpspell->checker) {
376                 pspell_manager_save_all_word_lists(gtkpspell->checker);
377                 delete_pspell_manager(gtkpspell->checker);
378         }
379         
380         gtkpspell->checker      = NULL;
381         gtkpspell->possible_err = NULL;
382
383         if (set_path_and_dict(gtkpspell,config2,path,dict) == 0) {
384                 debug_print(_("Pspell set_path_and_dict error."));
385                 return FALSE;
386         }
387   
388         gtkpspell->possible_err = new_pspell_manager(config2);
389
390         delete_pspell_config(config2);
391         config2 = NULL;
392
393         if (pspell_error_number(gtkpspell->possible_err) != 0) {
394                 debug_print(_("Pspell path & dict. error %s\n"),
395                             pspell_error_message(gtkpspell->possible_err));
396                 delete_pspell_can_have_error(gtkpspell->possible_err);
397                 gtkpspell->possible_err = NULL;
398                 return FALSE;
399         }
400
401         gtkpspell->checker=to_pspell_manager(gtkpspell->possible_err);
402
403         return TRUE;
404 }
405   
406 /* gtkpspell_get_dict() - What dict are we using ? language-spelling-jargon-module format */
407 /* Actually, this function is not used and hence not tested. */
408 /* Returns an allocated string */  
409 guchar *gtkpspell_get_dict(GtkPspell *gtkpspell)
410 {
411 /* Number of dashes in a dictionary name + 1 (for trailing null)
412  * needed when rebuilding dictionary name 
413  */
414 #define DASHES_IN_DICTNAME 3+1
415
416         guchar *dict;
417         guchar *language;
418         guchar *spelling;
419         guchar *jargon;
420         guint   len;
421
422         g_return_val_if_fail(gtkpspell->config, NULL);
423   
424         language = g_strdup(pspell_config_retrieve(gtkpspell->config, "language"));
425         spelling = g_strdup(pspell_config_retrieve(gtkpspell->config, "spelling"));
426         jargon   = g_strdup(pspell_config_retrieve(gtkpspell->config, "jargon"  ));
427         len      = strlen(language) + strlen(spelling) + strlen(jargon);
428
429         if (len + DASHES_IN_DICTNAME < BUFSIZE) {
430                 dict = g_new(char,len + DASHES_IN_DICTNAME);
431                 strcpy(dict, language);
432                 if (spelling) {
433                         strcat(dict, "-");
434                         strcat(dict, spelling);
435                         if (jargon) {
436                                 strcat(dict, "-");
437                                 strcat(dict,jargon);
438                         }
439                 }
440         }
441         g_free(language);
442         g_free(spelling);
443         g_free(jargon);
444   
445         return dict;
446 }
447   
448 /* gtkpspell_get_path() - Return the dict path as an allocated string */
449 /* Not used = not tested */
450 guchar *gtkpspell_get_path(GtkPspell *gtkpspell)
451 {
452         guchar * path;
453
454
455         g_return_val_if_fail(gtkpspell->config, NULL);
456
457         path = g_strdup(pspell_config_retrieve(gtkpspell->config,"word-list-path"));
458
459         return path;
460 }
461
462 /* menu_change_dict() - Menu callback : change dict */
463 static void menu_change_dict(GtkWidget *w, GtkPspell *gtkpspell)
464 {
465         guchar *thedict,
466                *thelabel;
467   
468         /* Dict is simply the menu label */
469
470         gtk_label_get(GTK_LABEL(GTK_BIN(w)->child), (gchar **) &thelabel);
471
472         if (!strcmp2(thelabel, _("None")))
473                         return;
474
475         thedict = g_strdup(thelabel);
476
477         /* Set path, dict, (and sug_mode ?) */
478         if(!gtkpspell_set_path_and_dict(gtkpspell, gtkpspell->path, thedict)) {
479                 /* FIXME : try to handle this very special case */
480                 debug_print("Pspell: Attempt to change to a non existant dict. I will crash after closing compose window.\n");
481                 gtkpspell_detach(gtkpspell);
482                 gtkpspell = gtkpspell_delete(gtkpspell);
483         }
484         g_free(thedict);
485 }
486
487 /* set_sug_mode() - Menu callback : Set the suggestion mode */
488 static void set_sug_mode(GtkWidget *w, GtkPspell *gtkpspell)
489 {
490         unsigned char *themode;
491
492         gtk_label_get(GTK_LABEL(GTK_BIN(w)->child), (gchar **) &themode);
493
494         if (!strcmp(themode, _("Fast Mode"))) {
495                 gtkpspell_set_sug_mode(gtkpspell, "fast");
496                 gtkpspell->mode = PSPELL_FASTMODE;
497         }
498         if (!strcmp(themode,_("Normal Mode"))) {
499                 gtkpspell_set_sug_mode(gtkpspell, "normal");
500                 gtkpspell->mode = PSPELL_NORMALMODE;
501         }
502         if (!strcmp( themode,_("Bad Spellers Mode"))) {
503                 gtkpspell_set_sug_mode(gtkpspell, "bad-spellers");
504                 gtkpspell->mode = PSPELL_BADSPELLERMODE;
505         }
506 }
507   
508 /* gtkpspell_set_sug_mode() - Set the suggestion mode */
509 /* Actually, the session is resetted and everything is reset. */
510 /* We take the used path/dict pair and create a new config with them */
511 int gtkpspell_set_sug_mode(GtkPspell *gtkpspell, gchar *themode)
512 {
513         PspellConfig *config2;
514         guchar       *path;
515         guchar       *dict;
516
517         pspell_manager_save_all_word_lists(gtkpspell->checker);
518         delete_pspell_manager(gtkpspell->checker);
519
520         gtkpspell->checker = NULL;
521
522         config2 = pspell_config_clone(gtkpspell->config);
523
524         if (!set_path_and_dict(gtkpspell, config2, gtkpspell->path, gtkpspell->dict)) {
525                 debug_print(_("Pspell set_sug_mod could not reset path & dict\n"));
526                 return FALSE;
527         }
528
529         pspell_config_replace(config2, "sug-mode", themode);
530
531         gtkpspell->possible_err = new_pspell_manager(config2);
532         delete_pspell_config(config2);
533         config2 = NULL;
534
535         if (pspell_error_number(gtkpspell->possible_err) != 0) {
536                 debug_print(_("Pspell set sug-mode error %s\n"),
537                             pspell_error_message(gtkpspell->possible_err));
538                 delete_pspell_can_have_error(gtkpspell->possible_err);
539                 gtkpspell->possible_err = NULL;
540                 return FALSE;
541         }
542         gtkpspell->checker = to_pspell_manager(gtkpspell->possible_err);
543         return TRUE;
544 }
545
546 /* set_learn_mode() - menu callback to toggle learn mode */
547 static void set_learn_mode (GtkWidget *w, GtkPspell *gtkpspell)
548 {
549         gtkpspell->learn = gtkpspell->learn == FALSE ;
550 }
551   
552 /* misspelled_suggest() - Create a suggestion list for  word  */
553 static GList *misspelled_suggest(GtkPspell *gtkpspell, guchar *word) 
554 {
555         const guchar          *newword;
556         GList                 *list = NULL;
557         int                    count;
558         const PspellWordList  *suggestions;
559         PspellStringEmulation *elements;
560
561         g_return_val_if_fail(word, NULL);
562
563         if (!pspell_manager_check(gtkpspell->checker, word, -1)) {
564                 suggestions = pspell_manager_suggest(gtkpspell->checker, (const char *)word, -1);
565                 elements    = pspell_word_list_elements(suggestions);
566                 /* First one must be the misspelled (why this ?) */
567                 list        = g_list_append(list, g_strdup(word)); 
568                 
569                 while ((newword = pspell_string_emulation_next(elements)) != NULL)
570                         list = g_list_append(list, g_strdup(newword));
571                 return list;
572         }
573         return NULL;
574 }
575
576 /* misspelled_test() - Just test if word is correctly spelled */  
577 static int misspelled_test(GtkPspell *gtkpspell, unsigned char *word) 
578 {
579         return pspell_manager_check(gtkpspell->checker, word, -1) ? 0 : 1; 
580 }
581
582
583 static gboolean iswordsep(unsigned char c) 
584 {
585         return !isalpha(c) && c != '\'';
586 }
587
588 static guchar get_text_index_whar(GtkPspell *gtkpspell, int pos) 
589 {
590         guchar a;
591         gchar *text;
592         
593         text = gtk_editable_get_chars(GTK_EDITABLE(gtkpspell->gtktext), pos, pos + 1);
594         if (text == NULL) 
595                 return 0;
596         a = (guchar) *text;
597         g_free(text);
598         return a;
599 }
600
601 /* get_word_from_pos () - return the word pointed to. */
602 /* Handles correctly the quotes. */
603 static gboolean get_word_from_pos(GtkPspell *gtkpspell, int pos, 
604                                   unsigned char* buf,
605                                   int *pstart, int *pend) 
606 {
607
608         /* TODO : when correcting a word into quotes, change the color of */
609         /* the quotes too, as may be they were highlighted before. To do  */
610         /* so, we can use two others pointers that points to the whole    */
611         /* word including quotes. */
612
613         gint      start, 
614                   end;
615         guchar    c;
616         GtkXText *gtktext;
617         
618         gtktext = gtkpspell->gtktext;
619         if (iswordsep(get_text_index_whar(gtkpspell, pos))) 
620                 return FALSE;
621         
622         /* The apostrophe character is somtimes used for quotes 
623          * So include it in the word only if it is not surrounded 
624          * by other characters. 
625          */
626          
627         for (start = pos; start >= 0; --start) {
628                 c = get_text_index_whar(gtkpspell, start);
629                 if (c == '\'') {
630                         if (start > 0) {
631                                 if (!isalpha(get_text_index_whar(gtkpspell, start - 1))) {
632                                         /* start_quote = TRUE; */
633                                         break;
634                                 }
635                         }
636                         else {
637                                 /* start_quote = TRUE; */
638                                 break;
639                         }
640                 }
641                 else
642                         if (!isalpha(c))
643                                 break;
644         }
645         start++;
646
647         for (end = pos; end < gtk_xtext_get_length(gtktext); end++) {
648                 c = get_text_index_whar(gtkpspell, end); 
649                 if (c == '\'') {
650                         if (end < gtk_xtext_get_length(gtktext)) {
651                                 if (!isalpha(get_text_index_whar(gtkpspell, end + 1))) {
652                                         /* end_quote = TRUE; */
653                                         break;
654                                 }
655                         }
656                         else {
657                                 /* end_quote = TRUE; */
658                                 break;
659                         }
660                 }
661                 else
662                         if(!isalpha(c))
663                                 break;
664         }
665                                                 
666         if (buf) {
667                 for (pos = start; pos < end; pos++) 
668                         buf[pos - start] = get_text_index_whar(gtkpspell, pos);
669                 buf[pos - start] = 0;
670         }
671
672         if (pstart) 
673                 *pstart = start;
674         if (pend) 
675                 *pend = end;
676
677         return TRUE;
678 }
679
680 static gboolean get_curword(GtkPspell *gtkpspell, unsigned char* buf,
681                             int *pstart, int *pend) 
682 {
683         int pos = gtk_editable_get_position(GTK_EDITABLE(gtkpspell->gtktext));
684         return get_word_from_pos(gtkpspell, pos, buf, pstart, pend);
685 }
686
687 static void change_color(GtkPspell * gtkpspell, 
688                          int start, int end, 
689                          GdkColor *color) 
690 {
691         char     *newtext;
692         GtkXText *gtktext;
693
694         g_return_if_fail(start < end);
695     
696         gtktext = gtkpspell->gtktext;
697     
698         gtk_xtext_freeze(gtktext);
699         newtext = gtk_editable_get_chars(GTK_EDITABLE(gtktext), start, end);
700         if (newtext) {
701                 gtk_signal_handler_block_by_func(GTK_OBJECT(gtktext),
702                                                  GTK_SIGNAL_FUNC(entry_insert_cb), 
703                                                  gtkpspell);
704                 gtk_xtext_set_point(gtktext, start);
705                 gtk_xtext_forward_delete(gtktext, end - start);
706
707                 gtk_xtext_insert(gtktext, NULL, color, NULL, newtext, end - start);
708                 gtk_signal_handler_unblock_by_func(GTK_OBJECT(gtktext),
709                                                    GTK_SIGNAL_FUNC(entry_insert_cb), 
710                                                    gtkpspell);
711                 g_free(newtext);
712         }
713         gtk_xtext_thaw(gtktext);
714 }
715
716 static gboolean check_at(GtkPspell *gtkpspell, int from_pos) 
717 {
718         int           start, end;
719         unsigned char buf[BUFSIZE];
720         GtkXText     *gtktext;
721
722         g_return_val_if_fail(from_pos >= 0, FALSE);
723     
724         gtktext = gtkpspell->gtktext;
725
726         if (!get_word_from_pos(gtkpspell, from_pos, buf, &start, &end))
727                 return FALSE;
728
729         strncpy(gtkpspell->theword, buf, BUFSIZE - 1);
730         gtkpspell->theword[BUFSIZE - 1] = 0;
731
732         if (misspelled_test(gtkpspell, buf)) {
733                 if (highlight.pixel == 0) {
734                         /* add an entry for the highlight in the color map. */
735                         GdkColormap *gc = gtk_widget_get_colormap(GTK_WIDGET(gtktext));
736                         gdk_colormap_alloc_color(gc, &highlight, FALSE, TRUE);
737                 }
738                 change_color(gtkpspell, start, end, &highlight);
739                 return TRUE;
740         } else {
741                 change_color(gtkpspell, start, end,
742                              &(GTK_WIDGET(gtktext)->style->fg[0]));
743                 return FALSE;
744         }
745 }
746
747
748 static void check_all(GtkWidget *w, GtkPspell *gtkpspell)
749 {
750         gtkpspell_check_all(gtkpspell);
751 }
752
753
754 void gtkpspell_check_all(GtkPspell *gtkpspell) 
755 {
756         guint     origpos;
757         guint     pos = 0;
758         guint     len;
759         float     adj_value;
760         GtkXText *gtktext;
761
762         
763         if (!gtkpspell_running(gtkpspell)) return ;
764         gtktext = gtkpspell->gtktext;
765
766         len = gtk_xtext_get_length(gtktext);
767
768         adj_value = gtktext->vadj->value;
769         gtk_xtext_freeze(gtktext);
770         origpos = gtk_editable_get_position(GTK_EDITABLE(gtktext));
771         gtk_editable_set_position(GTK_EDITABLE(gtktext),0);
772         while (pos < len) {
773                 while (pos < len && iswordsep(get_text_index_whar(gtkpspell, pos)))
774                         pos++;
775                 while (pos < len && !iswordsep(get_text_index_whar(gtkpspell, pos)))
776                         pos++;
777                 if (pos > 0)
778                         check_at(gtkpspell, pos - 1);
779         }
780         gtk_xtext_thaw(gtktext);
781         gtk_editable_set_position(GTK_EDITABLE(gtktext), origpos);
782         gtk_editable_select_region(GTK_EDITABLE(gtktext), origpos, origpos);
783 }
784
785 static void entry_insert_cb(GtkXText *gtktext, gchar *newtext, 
786                             guint len, guint *ppos, 
787                             GtkPspell * gtkpspell) 
788 {
789         guint origpos;
790         if (!gtkpspell_running(gtkpspell)) 
791                 return;
792
793         /* We must insert ourself the character to impose the */
794         /* color of the inserted character to be default */
795         /* Never mess with set_insertion when frozen */
796         gtk_xtext_freeze(gtktext);
797         gtk_xtext_backward_delete(GTK_XTEXT(gtktext), len);
798         gtk_xtext_insert(GTK_XTEXT(gtktext), NULL,
799                         &(GTK_WIDGET(gtktext)->style->fg[0]), NULL, newtext, len);
800         *ppos = gtk_xtext_get_point(GTK_XTEXT(gtktext));
801                
802         if (iswordsep(newtext[0])) {
803                 /* did we just end a word? */
804                 if (*ppos >= 2) 
805                         check_at(gtkpspell, *ppos - 2);
806
807                 /* did we just split a word? */
808                 if (*ppos < gtk_xtext_get_length(gtktext))
809                         check_at(gtkpspell, *ppos + 1);
810         } else {
811                 /* check as they type, *except* if they're typing at the end (the most
812                  * common case.
813                  */
814                 if (*ppos < gtk_xtext_get_length(gtktext) 
815                 &&  !iswordsep(get_text_index_whar(gtkpspell, *ppos)))
816                         check_at(gtkpspell, *ppos - 1);
817                 }
818         gtk_xtext_thaw(gtktext);
819         gtk_editable_set_position(GTK_EDITABLE(gtktext), *ppos);
820 }
821
822 static void entry_delete_cb(GtkXText *gtktext, gint start, gint end, 
823                             GtkPspell *gtkpspell) 
824 {
825         int origpos;
826     
827         if (!gtkpspell_running(gtkpspell)) 
828                 return;
829
830         origpos = gtk_editable_get_position(GTK_EDITABLE(gtktext));
831         if (start) {
832                 check_at(gtkpspell, start - 1);
833                 check_at(gtkpspell, start);
834         }
835
836         gtk_editable_set_position(GTK_EDITABLE(gtktext), origpos);
837         gtk_xtext_set_point(gtktext, origpos);
838         gtk_editable_select_region(GTK_EDITABLE(gtktext), origpos, origpos);
839         /* this is to *UNDO* the selection, in case they were holding shift
840          * while hitting backspace. */
841 }
842
843 static void replace_word(GtkWidget *w, GtkPspell *gtkpspell) 
844 {
845         int             start, end,oldlen, newlen;
846         guint           origpos;
847         unsigned char  *newword;
848         unsigned char   buf[BUFSIZE];
849         guint           pos;
850         GtkXText       *gtktext;
851     
852         gtktext = gtkpspell->gtktext;
853
854         gtk_xtext_freeze(GTK_XTEXT(gtktext));
855         origpos = gtkpspell->orig_pos;
856         pos     = origpos;
857
858         gtk_label_get(GTK_LABEL(GTK_BIN(w)->child), (gchar**) &newword);
859         newlen = strlen(newword);
860
861         get_curword(gtkpspell, buf, &start, &end);
862         oldlen = end - start;
863
864         gtk_xtext_set_point(GTK_XTEXT(gtktext), end);
865         gtk_xtext_backward_delete(GTK_XTEXT(gtktext), end - start);
866         gtk_xtext_insert(GTK_XTEXT(gtktext), NULL, NULL, NULL, newword, strlen(newword));
867     
868         if (end-start > 0 && gtkpspell->learn) { 
869                 /* Just be sure the buffer ends somewhere... */
870                 buf[end-start] = 0; 
871                 /* Learn from common misspellings */
872                 pspell_manager_store_replacement(gtkpspell->checker, buf, 
873                                                  end-start, newword, 
874                                                  strlen(newword));
875         }
876
877         /* Put the point and the position where we clicked with the mouse */
878         /* It seems to be a hack, as I must thaw,freeze,thaw the widget   */
879         /* to let it update correctly the word insertion and then the     */
880         /* point & position position. If not, SEGV after the first replacement */
881         /* If the new word ends before point, put the point at its end*/
882     
883         if (origpos-start <= oldlen && origpos-start >= 0) {
884                 /* Original point was in the word. */
885                 /* Put the insertion point in the same location */
886                 /* with respect to the new length */
887                 /* If the original position is still within the word, */
888                 /* then keep the original position. If not, move to the */
889                 /* end of the word */
890                 if (origpos-start > newlen)
891                         pos = start + newlen;
892         }
893         else if (origpos > end) {
894                 /* move the position according to the change of length */
895                 pos = origpos + newlen - oldlen;
896         }
897         gtk_xtext_thaw(GTK_XTEXT(gtktext));
898         gtk_xtext_freeze(GTK_XTEXT(gtktext));
899         if (GTK_XTEXT(gtktext)->text_len < pos)
900                 pos = gtk_xtext_get_length(GTK_XTEXT(gtktext));
901         gtkpspell->orig_pos = pos;
902         gtk_editable_set_position(GTK_EDITABLE(gtktext), gtkpspell->orig_pos);
903         gtk_xtext_set_point(GTK_XTEXT(gtktext), 
904                             gtk_editable_get_position(GTK_EDITABLE(gtktext)));
905         gtk_xtext_thaw(GTK_XTEXT(gtktext));
906 }
907
908 /* Accept this word for this session */
909
910 static void add_word_to_session(GtkWidget *w, GtkPspell *gtkpspell)
911 {
912         guint     pos;
913         GtkXText *gtkxtext;
914     
915         gtkxtext = gtkpspell->gtktext;
916         gtk_xtext_freeze(GTK_XTEXT(gtkxtext));
917         pos = gtk_editable_get_position(GTK_EDITABLE(gtkxtext));
918     
919         pspell_manager_add_to_session(gtkpspell->checker,gtkpspell->theword, strlen(gtkpspell->theword));
920
921         check_at(gtkpspell,gtk_editable_get_position(GTK_EDITABLE(gtkxtext)));
922
923         gtk_xtext_thaw(GTK_XTEXT(gtkxtext));
924         gtk_xtext_freeze(GTK_XTEXT(gtkxtext));
925         gtk_editable_set_position(GTK_EDITABLE(gtkxtext),pos);
926         gtk_xtext_set_point(GTK_XTEXT(gtkxtext), gtk_editable_get_position(GTK_EDITABLE(gtkxtext)));
927         gtk_xtext_thaw(GTK_XTEXT(gtkxtext));
928 }
929
930 /* add_word_to_personal() - add word to personal dict. */
931
932 static void add_word_to_personal(GtkWidget *w, GtkPspell *gtkpspell)
933 {
934         guint     pos;
935         GtkXText *gtkxtext;
936     
937         gtkxtext = gtkpspell->gtktext;
938
939         gtk_xtext_freeze(GTK_XTEXT(gtkxtext));
940         pos = gtk_editable_get_position(GTK_EDITABLE(gtkxtext));
941     
942         pspell_manager_add_to_personal(gtkpspell->checker,gtkpspell->theword,strlen(gtkpspell->theword));
943     
944         check_at(gtkpspell,gtk_editable_get_position(GTK_EDITABLE(gtkxtext)));
945         gtk_xtext_thaw(GTK_XTEXT(gtkxtext));
946         gtk_xtext_freeze(GTK_XTEXT(gtkxtext));
947         gtk_editable_set_position(GTK_EDITABLE(gtkxtext),pos);
948         gtk_xtext_set_point(GTK_XTEXT(gtkxtext), gtk_editable_get_position(GTK_EDITABLE(gtkxtext)));
949         gtk_xtext_thaw(GTK_XTEXT(gtkxtext));
950 }
951
952        
953 static GtkMenu *make_menu_config(GtkPspell *gtkpspell)
954 {
955         GtkWidget *menu, *item, *submenu;
956
957   
958         menu = gtk_menu_new();
959
960         item = gtk_menu_item_new_with_label(_("Spell check all"));
961         gtk_widget_show(item);
962         gtk_menu_append(GTK_MENU(menu), item);
963         gtk_signal_connect(GTK_OBJECT(item),"activate",
964                            GTK_SIGNAL_FUNC(check_all), 
965                            gtkpspell);
966
967
968         item = gtk_menu_item_new();
969         gtk_widget_show(item);
970         gtk_menu_append(GTK_MENU(menu),item);
971
972         submenu = gtk_menu_new();
973         item = gtk_menu_item_new_with_label(_("Change dictionary"));
974         gtk_widget_show(item);
975         gtk_menu_item_set_submenu(GTK_MENU_ITEM(item),submenu);
976         gtk_menu_append(GTK_MENU(menu), item);
977
978         /* Dict list */
979         if (gtkpspell->dictionary_list==NULL)
980                 gtkpspell->dictionary_list = gtkpspell_get_dictionary_list(gtkpspell->path);
981        
982         {
983                 GtkWidget * curmenu = submenu;
984                 int count = 0;
985                 Dictionary *dict;
986                 GSList *tmp;
987                 
988                 tmp = gtkpspell->dictionary_list;
989                 for (tmp = gtkpspell->dictionary_list; tmp != NULL; tmp =g_slist_next(tmp)) {
990                         dict = (Dictionary *) tmp->data;
991                         item = gtk_check_menu_item_new_with_label(dict->name);
992                         if (strcmp2(dict->name, gtkpspell->dict))
993                                 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(item), FALSE);
994                         else {
995                                 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(item), TRUE);
996                                 gtk_widget_set_sensitive(GTK_WIDGET(item), FALSE);
997                         }
998                         gtk_menu_append(GTK_MENU(curmenu), item);
999                         gtk_signal_connect(GTK_OBJECT(item), "activate",
1000                                            GTK_SIGNAL_FUNC(menu_change_dict),
1001                                            gtkpspell);
1002                         gtk_widget_show(item);
1003                         count++;
1004                         if (count == MENUCOUNT) {
1005                                 GtkWidget *newmenu;
1006                                 newmenu = gtk_menu_new();
1007                                 item = gtk_menu_item_new_with_label(_("More..."));
1008                                 gtk_menu_item_set_submenu(GTK_MENU_ITEM(item), newmenu);
1009                                 gtk_menu_append(GTK_MENU(curmenu), item);
1010                                 gtk_widget_show(item);
1011                                 curmenu = newmenu;
1012                                 count = 0;
1013                         }
1014                 }
1015         }  
1016
1017         item = gtk_menu_item_new();
1018         gtk_widget_show(item);
1019         gtk_menu_append(GTK_MENU(menu), item);
1020
1021         item = gtk_check_menu_item_new_with_label(_("Fast Mode"));
1022         gtk_widget_show(item);
1023         gtk_menu_append(GTK_MENU(menu), item);
1024         if (gtkpspell->mode == PSPELL_FASTMODE) {
1025                 gtk_widget_set_sensitive(GTK_WIDGET(item),FALSE);
1026                 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(item),TRUE);
1027         }
1028         else
1029                 gtk_signal_connect(GTK_OBJECT(item), "activate",
1030                                    GTK_SIGNAL_FUNC(set_sug_mode),
1031                                    gtkpspell);
1032
1033         item = gtk_check_menu_item_new_with_label(_("Normal Mode"));
1034         gtk_widget_show(item);
1035         gtk_menu_append(GTK_MENU(menu),item);
1036         if (gtkpspell->mode == PSPELL_NORMALMODE) {
1037                 gtk_widget_set_sensitive(GTK_WIDGET(item), FALSE);
1038                 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(item), TRUE);
1039         }
1040         else
1041                 gtk_signal_connect(GTK_OBJECT(item), "activate",
1042                                    GTK_SIGNAL_FUNC(set_sug_mode),
1043                                    gtkpspell);
1044         
1045         item = gtk_check_menu_item_new_with_label(_("Bad Spellers Mode"));
1046         gtk_widget_show(item);
1047         gtk_menu_append(GTK_MENU(menu), item);
1048         if (gtkpspell->mode==PSPELL_BADSPELLERMODE) {
1049                 gtk_widget_set_sensitive(GTK_WIDGET(item), FALSE);
1050                 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(item), TRUE);
1051         }
1052         else
1053                 gtk_signal_connect(GTK_OBJECT(item), "activate",
1054                                    GTK_SIGNAL_FUNC(set_sug_mode),
1055                                    gtkpspell);
1056         item = gtk_menu_item_new();
1057         gtk_widget_show(item);
1058         gtk_menu_append(GTK_MENU(menu), item);
1059
1060         item = gtk_check_menu_item_new_with_label(_("Learn from mistakes"));
1061         gtk_widget_show(item);
1062         gtk_menu_append(GTK_MENU(menu), item);
1063         gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(item), 
1064                                        gtkpspell->learn);
1065         gtk_signal_connect(GTK_OBJECT(item), "activate", 
1066                            GTK_SIGNAL_FUNC(set_learn_mode), gtkpspell);
1067                         
1068         
1069         return GTK_MENU(menu);
1070 }
1071   
1072 /* make_menu() - Add menus to accept this word for this session and to add it to 
1073  * personal dictionary */
1074 static GtkMenu *make_menu(GList *l, GtkPspell *gtkpspell) 
1075 {
1076         GtkWidget *menu, *item;
1077         unsigned char *caption;
1078         GtkXText * gtktext;
1079         
1080         gtktext = gtkpspell->gtktext;
1081
1082         menu = gtk_menu_new(); 
1083         caption = g_strdup_printf(_("Accept `%s' for this session"), (unsigned char*)l->data);
1084         item = gtk_menu_item_new_with_label(caption);
1085         g_free(caption);
1086         gtk_widget_show(item);
1087         gtk_menu_append(GTK_MENU(menu), item);
1088
1089         gtk_signal_connect(GTK_OBJECT(item), "activate",
1090                            GTK_SIGNAL_FUNC(add_word_to_session), 
1091                            gtkpspell);
1092
1093         caption = g_strdup_printf(_("Add `%s' to personal dictionary"), (char*)l->data);
1094         item = gtk_menu_item_new_with_label(caption);
1095         g_free(caption);
1096         gtk_widget_show(item);
1097         gtk_menu_append(GTK_MENU(menu), item);
1098
1099         gtk_signal_connect(GTK_OBJECT(item), "activate",
1100                            GTK_SIGNAL_FUNC(add_word_to_personal), 
1101                            gtkpspell);
1102          
1103         item = gtk_menu_item_new();
1104         gtk_widget_show(item);
1105         gtk_menu_append(GTK_MENU(menu), item);
1106
1107         l = l->next;
1108         if (l == NULL) {
1109                 item = gtk_menu_item_new_with_label(_("(no suggestions)"));
1110                 gtk_widget_show(item);
1111                 gtk_menu_append(GTK_MENU(menu), item);
1112         } else {
1113                 GtkWidget *curmenu = menu;
1114                 int count = 0;
1115                 
1116                 do {
1117                         if (l->data == NULL && l->next != NULL) {
1118                                 count = 0;
1119                                 curmenu = gtk_menu_new();
1120                                 item = gtk_menu_item_new_with_label(_("Others..."));
1121                                 gtk_widget_show(item);
1122                                 gtk_menu_item_set_submenu(GTK_MENU_ITEM(item), curmenu);
1123                                 gtk_menu_append(GTK_MENU(curmenu), item);
1124                                 l = l->next;
1125                         } else if (count > MENUCOUNT) {
1126                                 count -= MENUCOUNT;
1127                                 item = gtk_menu_item_new_with_label(_("More..."));
1128                                 gtk_widget_show(item);
1129                                 gtk_menu_append(GTK_MENU(curmenu), item);
1130                                 curmenu = gtk_menu_new();
1131                                 gtk_menu_item_set_submenu(GTK_MENU_ITEM(item), curmenu);
1132                         }
1133                         item = gtk_menu_item_new_with_label((unsigned char*)l->data);
1134                         gtk_signal_connect(GTK_OBJECT(item), "activate",
1135                                            GTK_SIGNAL_FUNC(replace_word), gtkpspell);
1136                         gtk_widget_show(item);
1137                         gtk_menu_append(GTK_MENU(curmenu), item);
1138                         count++;
1139                 } while ((l = l->next) != NULL);
1140         }
1141         return GTK_MENU(menu);
1142 }
1143
1144 static void popup_menu(GtkPspell *gtkpspell, GdkEventButton *eb) 
1145 {
1146         unsigned char buf[BUFSIZE];
1147         GList *list, *l;
1148         GtkXText * gtktext;
1149         
1150         gtktext = gtkpspell->gtktext;
1151         gtkpspell->orig_pos = gtk_editable_get_position(GTK_EDITABLE(gtktext));
1152
1153         if (!(eb->state & GDK_SHIFT_MASK))
1154                 if (get_curword(gtkpspell, buf, NULL, NULL)) {
1155                         if (buf != NULL) {
1156                                 strncpy(gtkpspell->theword, buf, BUFSIZE - 1);
1157                                 gtkpspell->theword[BUFSIZE - 1] = 0;
1158                                 list = misspelled_suggest(gtkpspell, buf);
1159                                 if (list != NULL) {
1160                                         gtk_menu_popup(make_menu(list, gtkpspell), NULL, NULL, NULL, NULL,
1161                                                        eb->button, eb->time);
1162                                         for (l = list; l != NULL; l = l->next)
1163                                                 g_free(l->data);
1164                                         g_list_free(list);
1165                                         return;
1166                                 }
1167                         }
1168                 }
1169         gtk_menu_popup(make_menu_config(gtkpspell),NULL,NULL,NULL,NULL,
1170         eb->button,eb->time);
1171 }
1172
1173 /* ok, this is pretty wacky:
1174  * we need to let the right-mouse-click go through, so it moves the cursor,
1175  * but we *can't* let it go through, because GtkText interprets rightclicks as
1176  * weird selection modifiers.
1177  *
1178  * so what do we do?  forge rightclicks as leftclicks, then popup the menu.
1179  * HACK HACK HACK.
1180  */
1181 static gint button_press_intercept_cb(GtkXText *gtktext, GdkEvent *e, GtkPspell *gtkpspell) 
1182 {
1183         GdkEventButton *eb;
1184         gboolean retval;
1185
1186         if (!gtkpspell_running(gtkpspell)) 
1187                 return FALSE;
1188
1189         if (e->type != GDK_BUTTON_PRESS) 
1190                 return FALSE;
1191         eb = (GdkEventButton*) e;
1192
1193         if (eb->button != 3) 
1194                 return FALSE;
1195
1196         /* forge the leftclick */
1197         eb->button = 1;
1198
1199         gtk_signal_handler_block_by_func(GTK_OBJECT(gtktext),
1200                                          GTK_SIGNAL_FUNC(button_press_intercept_cb), 
1201                                          gtkpspell);
1202         gtk_signal_emit_by_name(GTK_OBJECT(gtktext), "button-press-event",
1203                                 e, &retval);
1204         gtk_signal_handler_unblock_by_func(GTK_OBJECT(gtktext),
1205                                            GTK_SIGNAL_FUNC(button_press_intercept_cb), 
1206                                            gtkpspell);
1207         gtk_signal_emit_stop_by_name(GTK_OBJECT(gtktext), "button-press-event");
1208     
1209         /* now do the menu wackiness */
1210         popup_menu(gtkpspell, eb);
1211         gtk_grab_remove(GTK_WIDGET(gtktext));
1212         return TRUE;
1213 }
1214
1215 void gtkpspell_uncheck_all(GtkPspell * gtkpspell) 
1216 {
1217         int origpos;
1218         unsigned char *text;
1219         float adj_value;
1220         GtkXText *gtktext;
1221         
1222         gtktext=gtkpspell->gtktext;
1223
1224         adj_value = gtktext->vadj->value;
1225         gtk_xtext_freeze(gtktext);
1226         origpos = gtk_editable_get_position(GTK_EDITABLE(gtktext));
1227         text = gtk_editable_get_chars(GTK_EDITABLE(gtktext), 0, -1);
1228         gtk_xtext_set_point(gtktext, 0);
1229         gtk_xtext_forward_delete(gtktext, gtk_xtext_get_length(gtktext));
1230         gtk_xtext_insert(gtktext, NULL, NULL, NULL, text, strlen(text));
1231         gtk_xtext_thaw(gtktext);
1232
1233         gtk_editable_set_position(GTK_EDITABLE(gtktext), origpos);
1234         gtk_adjustment_set_value(gtktext->vadj, adj_value);
1235 }
1236
1237 void gtkpspell_attach(GtkPspell *gtkpspell, GtkXText *gtktext) 
1238 {
1239         gtkpspell->gtktext=gtktext;
1240         gtk_signal_connect_after(GTK_OBJECT(gtktext), "insert-text",
1241                            GTK_SIGNAL_FUNC(entry_insert_cb), gtkpspell);
1242         gtk_signal_connect_after(GTK_OBJECT(gtktext), "delete-text",
1243                                  GTK_SIGNAL_FUNC(entry_delete_cb), gtkpspell);
1244         gtk_signal_connect(GTK_OBJECT(gtktext), "button-press-event",
1245                            GTK_SIGNAL_FUNC(button_press_intercept_cb), gtkpspell);
1246
1247 }
1248
1249 void gtkpspell_detach(GtkPspell * gtkpspell) 
1250 {
1251         GtkXText * gtktext;
1252         
1253         gtktext =gtkpspell->gtktext;
1254 /*    if (prefs_common.auto_makepspell) { */
1255         gtk_signal_disconnect_by_func(GTK_OBJECT(gtktext),
1256                                       GTK_SIGNAL_FUNC(entry_insert_cb), gtkpspell);
1257         gtk_signal_disconnect_by_func(GTK_OBJECT(gtktext),
1258                                       GTK_SIGNAL_FUNC(entry_delete_cb), gtkpspell);
1259 /*    }; */
1260         gtk_signal_disconnect_by_func(GTK_OBJECT(gtktext),
1261                                       GTK_SIGNAL_FUNC(button_press_intercept_cb), gtkpspell);
1262
1263         gtkpspell_uncheck_all(gtkpspell);
1264 }
1265
1266 /*** Sylpheed (Claws) ***/
1267
1268 static GSList *create_empty_dictionary_list(void)
1269 {
1270         GSList *list = NULL;
1271         Dictionary *dict;
1272         
1273         dict = g_new0(Dictionary, 1);
1274         dict->name = g_strdup(_("None"));
1275         return g_slist_append(list, dict);
1276 }
1277
1278 /* gtkpspell_get_dictionary_list() - returns list of dictionary names */
1279 GSList *gtkpspell_get_dictionary_list(const gchar *pspell_path)
1280 {
1281         GSList *list;
1282         gchar *dict_path, *tmp, *prevdir;
1283         GSList *walk;
1284         Dictionary *dict;
1285         DIR *dir;
1286         struct dirent *ent;
1287
1288         list = NULL;
1289
1290 #ifdef USE_THREADS
1291 #warning TODO: no directory change
1292 #endif
1293         dict_path=g_strdup(pspell_path);
1294         prevdir = g_get_current_dir();
1295         if (chdir(dict_path) <0) {
1296                 FILE_OP_ERROR(dict_path, "chdir");
1297                 g_free(prevdir);
1298                 g_free(dict_path);
1299                 return create_empty_dictionary_list();
1300         }
1301
1302         debug_print(_("Checking for dictionaries in %s\n"), dict_path);
1303
1304         if (NULL != (dir = opendir("."))) {
1305                 while (NULL != (ent = readdir(dir))) {
1306                         /* search for pwli */
1307                         if (NULL != (tmp = strstr(ent->d_name, ".pwli"))) {
1308                                 dict = g_new0(Dictionary, 1);
1309                                 dict->name = g_strndup(ent->d_name, tmp - ent->d_name);
1310                                 debug_print(_("Found dictionary %s\n"), dict->name);
1311                                 list = g_slist_insert_sorted(list, dict, (GCompareFunc) compare_dict);
1312                         }
1313                 }                       
1314                 closedir(dir);
1315         }
1316         else {
1317                 FILE_OP_ERROR(dict_path, "opendir");
1318                 debug_print(_("No dictionary found\n"));
1319                 list = create_empty_dictionary_list();
1320         }
1321         if(list==NULL){
1322           debug_print(_("No dictionary found\n"));
1323           list = create_empty_dictionary_list();
1324         }
1325         chdir(prevdir);
1326         g_free(dict_path);
1327         g_free(prevdir);
1328         return list;
1329 }
1330
1331 /* compare_dict () - compare 2 dict names */
1332
1333 static gint compare_dict(Dictionary *a, Dictionary *b)
1334 {
1335         guchar *alanguage, *blanguage,
1336                *aspelling, *bspelling,
1337                *ajargon  , *bjargon  ,
1338                *amodule  , *bmodule  ;
1339         guint aparts = 0, bparts = 0, i;
1340
1341         for (i=0; i < strlen(a->name) ; i++)
1342                 if (a->name[i]=='-')
1343                         aparts++;
1344         for (i=0; i < strlen(b->name) ; i++)
1345                 if (b->name[i]=='-')
1346                         bparts++;
1347
1348         if (aparts != bparts) 
1349                 return (aparts < bparts) ? -1 : +1 ;
1350         else 
1351                 return strcmp2(a->name, b->name);
1352 }
1353 void gtkpspell_free_dictionary_list(GSList *list)
1354 {
1355         Dictionary *dict;
1356         GSList *walk;
1357         for (walk = list; walk != NULL; walk = g_slist_next(walk))
1358                 if (walk->data) {
1359                         dict = (Dictionary *) walk->data;
1360                         if (dict->name)
1361                                 g_free(dict->name);
1362                         g_free(dict);
1363                 }                               
1364         g_slist_free(list);
1365 }
1366
1367 static void dictionary_option_menu_item_data_destroy(gpointer data)
1368 {
1369         gchar *str = (gchar *) data;
1370
1371         if (str)
1372                 g_free(str);
1373 }
1374
1375 GtkWidget *gtkpspell_dictionary_option_menu_new(const gchar *pspell_path)
1376 {
1377         GSList *dict_list, *tmp;
1378         GtkWidget *item;
1379         GtkWidget *menu;
1380         Dictionary *dict;
1381
1382         dict_list = gtkpspell_get_dictionary_list(pspell_path);
1383         g_return_val_if_fail(dict_list, NULL);
1384
1385         menu = gtk_menu_new();
1386         
1387         for (tmp = dict_list; tmp != NULL; tmp = g_slist_next(tmp)) {
1388                 dict = (Dictionary *) tmp->data;
1389                 item = gtk_menu_item_new_with_label(dict->name);
1390                 if (dict->name)
1391                         gtk_object_set_data_full(GTK_OBJECT(item), "dict_name",
1392                                          g_strdup(dict->name), 
1393                                          dictionary_option_menu_item_data_destroy);
1394                 gtk_menu_append(GTK_MENU(menu), item);                                   
1395                 gtk_widget_show(item);
1396         }
1397
1398         gtk_widget_show(menu);
1399
1400         gtkpspell_free_dictionary_list(dict_list);
1401
1402         return menu;
1403 }
1404
1405
1406
1407 gchar *gtkpspell_get_dictionary_menu_active_item(GtkWidget *menu)
1408 {
1409         GtkWidget *menuitem;
1410         gchar *result;
1411
1412         g_return_val_if_fail(GTK_IS_MENU(menu), NULL);
1413         menuitem = gtk_menu_get_active(GTK_MENU(menu));
1414         result = gtk_object_get_data(GTK_OBJECT(menuitem), "dict_name");
1415         g_return_val_if_fail(result, NULL);
1416         return g_strdup(result);
1417   
1418 }
1419
1420 /* convert_to_pspell_encoding () - converts ISO-8859-* strings to iso8859-* 
1421  * as needed by pspell. Returns an allocated string.
1422  */
1423
1424 guchar *convert_to_pspell_encoding (const guchar *encoding)
1425 {
1426         guchar * pspell_encoding;
1427
1428         if (strstr2(encoding, "ISO-8859-")) {
1429                 pspell_encoding = g_strdup_printf("iso8859%s", encoding+8);
1430         }
1431         else
1432                 if (!strcmp2(encoding, "US-ASCII"))
1433                         pspell_encoding = g_strdup("iso8859-1");
1434                 else
1435                         pspell_encoding = g_strdup(encoding);
1436         return pspell_encoding;
1437         
1438 }
1439
1440                 
1441                 
1442 #endif