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