dropped PSPELL_PATH macro
[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     if (from_pos < 0) return FALSE;
621     if (!get_word_from_pos(gtkpspell, from_pos, buf, &start, &end)) {
622         return FALSE;
623     };
624     if (misspelled_test(gtkpspell, buf)) {
625         if (highlight.pixel == 0) {
626             /* add an entry for the highlight in the color map. */
627             GdkColormap *gc = gtk_widget_get_colormap(GTK_WIDGET(gtktext));
628             gdk_colormap_alloc_color(gc, &highlight, FALSE, TRUE);
629             ;
630         }
631         change_color(gtkpspell, start, end, &highlight);
632         return TRUE;
633     } else {
634         change_color(gtkpspell, start, end,
635                      &(GTK_WIDGET(gtktext)->style->fg[0]));
636         return FALSE;
637     }
638 }
639
640
641 static void check_all(GtkWidget *w, GtkPspell * gtkpspell){
642   gtkpspell_check_all(gtkpspell);
643 }
644
645
646 void gtkpspell_check_all(GtkPspell * gtkpspell) 
647 {
648     guint origpos;
649     guint pos = 0;
650     guint len;
651     float adj_value;
652     GtkXText * gtktext;
653     gtktext=gtkpspell->gtktext;
654
655     if (!gtkpspell_running(gtkpspell)) return ;
656
657     len = gtk_xtext_get_length(gtktext);
658
659     adj_value = gtktext->vadj->value;
660     gtk_xtext_freeze(gtktext);
661     origpos = gtk_editable_get_position(GTK_EDITABLE(gtktext));
662     gtk_editable_set_position(GTK_EDITABLE(gtktext),0);
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( !(eb->state & GDK_SHIFT_MASK) )
992       if (get_curword(gtkpspell, buf, NULL, NULL)){
993         if (buf != NULL) {
994           strncpy(gtkpspell->theword,buf,BUFSIZE-1);
995           gtkpspell->theword[BUFSIZE-1]=0x00;
996           list = misspelled_suggest(gtkpspell, buf);
997           if (list != NULL) {
998             gtk_menu_popup(make_menu(list, gtkpspell), NULL, NULL, NULL, NULL,
999                            eb->button, eb->time);
1000             for (l = list; l != NULL; l = l->next)
1001               g_free(l->data);
1002             g_list_free(list);
1003             return;
1004           }
1005         }
1006       }
1007     gtk_menu_popup(make_menu_config(gtkpspell),NULL,NULL,NULL,NULL,
1008           eb->button,eb->time);
1009
1010 }
1011
1012 /* ok, this is pretty wacky:
1013  * we need to let the right-mouse-click go through, so it moves the cursor,
1014  * but we *can't* let it go through, because GtkText interprets rightclicks as
1015  * weird selection modifiers.
1016  *
1017  * so what do we do?  forge rightclicks as leftclicks, then popup the menu.
1018  * HACK HACK HACK.
1019  */
1020 static gint button_press_intercept_cb(GtkXText *gtktext, GdkEvent *e, GtkPspell * gtkpspell) 
1021
1022 {
1023     GdkEventButton *eb;
1024     gboolean retval;
1025
1026     if (!gtkpspell_running(gtkpspell)) return FALSE;
1027
1028     if (e->type != GDK_BUTTON_PRESS) return FALSE;
1029     eb = (GdkEventButton*) e;
1030
1031     if (eb->button != 3) return FALSE;
1032
1033     /* forge the leftclick */
1034     eb->button = 1;
1035
1036         gtk_signal_handler_block_by_func(GTK_OBJECT(gtktext),
1037                                      GTK_SIGNAL_FUNC(button_press_intercept_cb), gtkpspell);
1038     gtk_signal_emit_by_name(GTK_OBJECT(gtktext), "button-press-event",
1039                             e, &retval);
1040     gtk_signal_handler_unblock_by_func(GTK_OBJECT(gtktext),
1041                                        GTK_SIGNAL_FUNC(button_press_intercept_cb), gtkpspell);
1042     gtk_signal_emit_stop_by_name(GTK_OBJECT(gtktext), "button-press-event");
1043     
1044     /* now do the menu wackiness */
1045     popup_menu(gtkpspell, eb);
1046     gtk_grab_remove(GTK_WIDGET(gtktext));
1047     return TRUE;
1048 }
1049
1050 void gtkpspell_uncheck_all(GtkPspell * gtkpspell) 
1051 {
1052     int origpos;
1053     unsigned char *text;
1054     float adj_value;
1055     GtkXText * gtktext;
1056     gtktext=gtkpspell->gtktext;
1057
1058     adj_value = gtktext->vadj->value;
1059     gtk_xtext_freeze(gtktext);
1060     origpos = gtk_editable_get_position(GTK_EDITABLE(gtktext));
1061     text = gtk_editable_get_chars(GTK_EDITABLE(gtktext), 0, -1);
1062     gtk_xtext_set_point(gtktext, 0);
1063     gtk_xtext_forward_delete(gtktext, gtk_xtext_get_length(gtktext));
1064     gtk_xtext_insert(gtktext, NULL, NULL, NULL, text, strlen(text));
1065     gtk_xtext_thaw(gtktext);
1066
1067     gtk_editable_set_position(GTK_EDITABLE(gtktext), origpos);
1068     gtk_adjustment_set_value(gtktext->vadj, adj_value);
1069 }
1070
1071 void gtkpspell_attach(GtkPspell * gtkpspell, GtkXText *gtktext) 
1072 {
1073     gtkpspell->gtktext=gtktext;
1074 //    if (prefs_common.auto_makepspell) {
1075         gtk_signal_connect(GTK_OBJECT(gtktext), "insert-text",
1076                            GTK_SIGNAL_FUNC(entry_insert_cb), gtkpspell);
1077         gtk_signal_connect_after(GTK_OBJECT(gtktext), "delete-text",
1078                                  GTK_SIGNAL_FUNC(entry_delete_cb), gtkpspell);
1079 //    };
1080     gtk_signal_connect(GTK_OBJECT(gtktext), "button-press-event",
1081                        GTK_SIGNAL_FUNC(button_press_intercept_cb), gtkpspell);
1082
1083 }
1084
1085 void gtkpspell_detach(GtkPspell * gtkpspell) 
1086 {
1087     GtkXText * gtktext;
1088     gtktext=gtkpspell->gtktext;
1089 //    if (prefs_common.auto_makepspell) {
1090         gtk_signal_disconnect_by_func(GTK_OBJECT(gtktext),
1091                                       GTK_SIGNAL_FUNC(entry_insert_cb), gtkpspell);
1092         gtk_signal_disconnect_by_func(GTK_OBJECT(gtktext),
1093                                       GTK_SIGNAL_FUNC(entry_delete_cb), gtkpspell);
1094 //    };
1095     gtk_signal_disconnect_by_func(GTK_OBJECT(gtktext),
1096                                   GTK_SIGNAL_FUNC(button_press_intercept_cb), gtkpspell);
1097
1098     gtkpspell_uncheck_all(gtkpspell);
1099 }
1100
1101 /*** Sylpheed (Claws) ***/
1102
1103 static GSList *create_empty_dictionary_list(void)
1104 {
1105         GSList *list = NULL;
1106         Dictionary *dict;
1107         
1108         dict = g_new0(Dictionary, 1);
1109         dict->name = g_strdup(_("None"));
1110         return g_slist_append(list, dict);
1111 }
1112
1113 /* gtkpspell_get_dictionary_list() - returns list of dictionary names */
1114 GSList *gtkpspell_get_dictionary_list(const gchar *pspell_path)
1115 {
1116         GSList *list;
1117         gchar *dict_path, *tmp, *prevdir;
1118         GSList *walk;
1119         Dictionary *dict;
1120         DIR *dir;
1121         struct dirent *ent;
1122
1123         list = NULL;
1124
1125 #ifdef USE_THREADS
1126 #warning TODO: no directory change
1127 #endif
1128         dict_path=g_strdup(pspell_path);
1129         prevdir = g_get_current_dir();
1130         if (chdir(dict_path) <0) {
1131                 FILE_OP_ERROR(dict_path, "chdir");
1132                 g_free(prevdir);
1133                 g_free(dict_path);
1134                 return create_empty_dictionary_list();
1135         }
1136
1137         debug_print(_("Checking for dictionaries in %s\n"), dict_path);
1138
1139         if (NULL != (dir = opendir("."))) {
1140                 while (NULL != (ent = readdir(dir))) {
1141                         /* search for pwli */
1142                         if (NULL != (tmp = strstr(ent->d_name, ".pwli"))) {
1143                                 dict = g_new0(Dictionary, 1);
1144                                 dict->name = g_strndup(ent->d_name, tmp - ent->d_name);
1145                                 debug_print(_("Found dictionary %s\n"), dict->name);
1146                                 list = g_slist_append(list, dict);
1147                         }
1148                 }                       
1149                 closedir(dir);
1150         }
1151         else {
1152                 FILE_OP_ERROR(dict_path, "opendir");
1153                 debug_print(_("No dictionary found\n"));
1154                 list = create_empty_dictionary_list();
1155         }
1156         if(list==NULL){
1157           debug_print(_("No dictionary found"));
1158           list = create_empty_dictionary_list();
1159         }
1160         chdir(prevdir);
1161         g_free(dict_path);
1162         g_free(prevdir);
1163         return list;
1164 }
1165
1166 void gtkpspell_free_dictionary_list(GSList *list)
1167 {
1168         Dictionary *dict;
1169         GSList *walk;
1170         for (walk = list; walk != NULL; walk = g_slist_next(walk))
1171                 if (walk->data) {
1172                         dict = (Dictionary *) walk->data;
1173                         if (dict->name)
1174                                 g_free(dict->name);
1175                         g_free(dict);
1176                 }                               
1177         g_slist_free(list);
1178 }
1179
1180 static void dictionary_option_menu_item_data_destroy(gpointer data)
1181 {
1182         gchar *str = (gchar *) data;
1183
1184         if (str)
1185                 g_free(str);
1186 }
1187
1188 GtkWidget *gtkpspell_dictionary_option_menu_new(const gchar *pspell_path)
1189 {
1190         GSList *dict_list, *tmp;
1191         GtkWidget *item;
1192         GtkWidget *menu;
1193         Dictionary *dict;
1194
1195         dict_list = gtkpspell_get_dictionary_list(pspell_path);
1196         g_return_val_if_fail(dict_list, NULL);
1197
1198         menu = gtk_menu_new();
1199         
1200         for (tmp = dict_list; tmp != NULL; tmp = g_slist_next(tmp)) {
1201                 dict = (Dictionary *) tmp->data;
1202                 item = gtk_menu_item_new_with_label(dict->name);
1203                 if (dict->name)
1204                         gtk_object_set_data_full(GTK_OBJECT(item), "dict_name",
1205                                          g_strdup(dict->name), 
1206                                          dictionary_option_menu_item_data_destroy);
1207                 gtk_menu_append(GTK_MENU(menu), item);                                   
1208                 gtk_widget_show(item);
1209         }
1210
1211         gtk_widget_show(menu);
1212
1213         gtkpspell_free_dictionary_list(dict_list);
1214
1215         return menu;
1216 }
1217
1218
1219
1220 gchar *gtkpspell_get_dictionary_menu_active_item(GtkWidget *menu)
1221 {
1222         GtkWidget *menuitem;
1223         gchar *result;
1224
1225         g_return_val_if_fail(GTK_IS_MENU(menu), NULL);
1226         menuitem = gtk_menu_get_active(GTK_MENU(menu));
1227         result = gtk_object_get_data(GTK_OBJECT(menuitem), "dict_name");
1228         g_return_val_if_fail(result, NULL);
1229         return g_strdup(result);
1230   
1231 }
1232 #endif