add global setting stuff for spelling checker; 'nother 20% done
[claws.git] / src / gtkspell.c
1 /* gtkspell - 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 #if defined(HAVE_CONFIG_H)
28 #include "config.h"
29 #endif
30
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 /* size of the text buffer used in various word-processing routines. */
61 #define BUFSIZE 1024
62
63 /* number of suggestions to display on each menu. */
64 #define MENUCOUNT 15
65
66 /* because we keep only one copy of the spell program running,
67  * all ispell-related variables can be static.
68  */
69 volatile pid_t spell_pid = -1;
70 static int sp_fd_write[2], sp_fd_read[2];
71 static int signal_set_up = 0;
72
73 static GdkColor highlight = { 0, 255 * 256, 0, 0 };
74
75 static void entry_insert_cb(GtkXText *gtktext,
76                             gchar *newtext, guint len, guint *ppos, gpointer d);
77 static void set_up_signal();
78
79 int gtkspell_running() 
80 {
81     return (spell_pid > 0);
82 }
83
84 /* functions to interface with pipe */
85 static void writetext(unsigned char *text_ccc) 
86 {
87     write(sp_fd_write[1], text_ccc, strlen(text_ccc));
88 }
89
90 static int readpipe(unsigned char *buf, int bufsize) 
91 {
92     int len;
93     len = read(sp_fd_read[0], buf, bufsize - 1);
94     if (len < 0) {
95         debug_print(_("*** readpipe: read: %s\n"), strerror(errno));
96         return -1;
97     } else if (len == 0) {
98         debug_print(_("*** readpipe: pipe closed.\n"));
99         return -1;
100     } else if (len == bufsize - 1) {
101         debug_print(_("*** readpipe: buffer overflowed?\n"));
102     }
103
104     buf[len] = 0;
105     return len;
106 }
107
108 static int readline(unsigned char *buf) 
109 {
110     return readpipe(buf, BUFSIZE);
111 }
112
113 static int readresponse(unsigned char *buf) 
114 {
115     int len;
116     len = readpipe(buf, BUFSIZE);
117
118     /* all ispell responses of any reasonable length should end in \n\n.
119      * depending on the speed of the spell checker, this may require more
120      * reading. */
121     if (len >= 2 && (buf[len - 1] != '\n' || buf[len - 2] != '\n')) {
122         len += readpipe(buf + len, BUFSIZE - len);
123     }
124
125     /* now we can remove all of the the trailing newlines. */
126     while (len > 0 && buf[len - 1] == '\n')
127         buf[--len] = 0;
128
129     return len;
130 }
131
132 void gtkspell_stop() 
133 {
134     if (gtkspell_running()) {
135         kill(spell_pid, SIGTERM);
136         debug_print(_("*** Kill pid[%i] returned: %s\n"), spell_pid, strerror(errno));
137         while (spell_pid != -1);
138     }
139 }
140
141 static int poller(int buffer) 
142 {
143     int len;
144     fd_set rfds;
145     struct timeval tv;
146     int retval;
147
148     FD_ZERO(&rfds);
149     FD_SET(buffer, &rfds);
150     memset(&tv, 0, sizeof(tv));
151     tv.tv_sec = 2;
152     tv.tv_usec = 0;
153     return select(buffer + 1, &rfds, NULL, NULL, &tv);
154 }
155
156 int gtkspell_start(unsigned char *path, char * args[]) 
157 {
158     int fd_error[2];
159     FILE *sav_stdin, *sav_stdout, *sav_stderr;
160     char buf[BUFSIZE];
161     int retncode;
162
163     if (gtkspell_running()) {
164         debug_print(_("*** gtkspell_start called while already running.\n"));
165         gtkspell_stop();
166     }
167
168     if (!signal_set_up) {
169         set_up_signal();
170         signal_set_up = 1;
171     }
172
173     pipe(sp_fd_write);
174     pipe(sp_fd_read);
175     pipe(fd_error);
176
177     spell_pid = fork();
178     if (spell_pid < 0) {
179         debug_print(_("*** fork: %s\n"), strerror(errno));
180         return -1;
181     } else if (spell_pid == 0) {
182         sav_stdin = fdopen(dup(fileno(stdin)), "r");
183         sav_stdout = fdopen(dup(fileno(stdout)), "w");
184         sav_stderr = fdopen(dup(fileno(stderr)), "w");
185         dup2(sp_fd_write[0], 0);
186         dup2(sp_fd_read[1], 1);
187         dup2(fd_error[1], 2);
188         close(sp_fd_read[0]);
189         close(fd_error[0]);
190         close(sp_fd_write[1]);
191
192         if (path == NULL) {
193             if (execvp(args[0], args) < 0)
194                 //DONT call debug_print here, because stdout is closed at this moment
195                 fprintf(sav_stderr, _("*** execvp('%s'): %s\n"), args[0], strerror(errno));
196         } else {
197             if (execv(path, args) < 0)
198                 //DONT call debug_print here, because stdout is closed at this moment
199                 fprintf(sav_stderr, _("*** execv('%s'): %s\n"), path, strerror(errno));
200         }
201         /* if we get here, we failed.
202          * send some text on the pipe to indicate status.
203          */
204         write(sp_fd_read[1], "!", 1);
205
206         _exit(0);
207     } else {
208         retncode = poller(sp_fd_read[1]);
209         if (retncode < 0) {
210             debug_print(_("*** Spell comand failed: %s.\n"), strerror(errno));
211             gtkspell_stop();
212             return -1;
213         }
214         readline(buf);
215         /* ispell should print something like this:
216          * @(#) International Ispell Version 3.1.20 10/10/95
217          * if it doesn't, it's an error. */
218         if (buf[0] != '@') {
219             debug_print(_("*** ispell didnt print '@'\n"));
220             gtkspell_stop();
221             return -1;
222         }
223     }
224
225     /* put ispell into terse mode.
226      * this makes it not respond on correctly spelled words. */
227     sprintf(buf, "!\n");
228     writetext(buf);
229     return 0;
230 }
231
232 static GList* misspelled_suggest(unsigned char *word) 
233 {
234     unsigned char buf[BUFSIZE];
235     unsigned char *newword;
236     GList *l = NULL;
237     int count;
238     sprintf(buf, "^%s\n", word);  /* guard against ispell control chars */
239     writetext(buf);
240     readresponse(buf);
241     switch (buf[0]) { /* first char is ispell command. */
242     case 0:  /* no response: word is ok. */
243         return NULL;
244     case 10:  /* just enter word is ok.  */
245         return NULL;
246     case '&':  /* misspelled, with suggestions */
247         /* & <orig> <count> <ofs>: <miss>, <miss>, <guess>, ... */
248         strtok(buf, " ");  /* & */
249         newword = strtok(NULL, " ");  /* orig */
250         l = g_list_append(l, g_strdup(newword));
251         newword = strtok(NULL, " ");  /* count */
252         count = atoi(newword);
253         strtok(NULL, " ");  /* ofs: */
254
255         while ((newword = strtok(NULL, ",")) != NULL) {
256             int len = strlen(newword);
257             if (newword[len - 1] == ' ' || newword[len - 1] == '\n')
258                 newword[len - 1] = 0;
259             if (count == 0) {
260                 g_list_append(l, NULL);  /* signal the "suggestions" */
261             }
262             /* add it to the list, skipping the initial space. */
263             l = g_list_append(l,
264                               g_strdup(newword[0] == ' ' ? newword + 1 : newword));
265
266             count--;
267         }
268         return l;
269     case '?':  /* ispell is guessing. */
270     case '#':  /* misspelled, no suggestions */
271         /* # <orig> <ofs> */
272         strtok(buf, " ");  /* & */
273         newword = strtok(NULL, " ");  /* orig */
274         l = g_list_append(l, g_strdup(newword));
275         return l;
276     default:
277         debug_print(_("*** Unsupported spell command '%c'.\n"), buf[0]);
278     }
279     return NULL;
280 }
281
282 static int misspelled_test(unsigned char *word) 
283 {
284     unsigned char buf[BUFSIZE];
285     sprintf(buf, "^%s\n", word);  /* guard against ispell control chars */
286     writetext(buf);
287     readresponse(buf);
288
289     if (buf[0] == 0) {
290         return 0;
291     } else if (buf[0] == '&' || buf[0] == '#' || buf[0] == '?') {
292         return 1;
293     }
294
295     debug_print(_("*** Unsupported spell command '%c'.\n"), buf[0]);
296
297     return -1;
298 }
299
300 static gboolean iswordsep(unsigned char c) 
301 {
302     return !isalpha(c) && c != '\'';
303 }
304
305 static guchar get_text_index_whar(GtkXText *gtktext, int pos) 
306 {
307     guchar a;
308     gchar *text;
309     text = gtk_editable_get_chars(GTK_EDITABLE(gtktext), pos, pos + 1);
310     if (text == NULL) return 0;
311     a = (guchar) * text;
312     g_free(text);
313     return a;
314 }
315
316 static gboolean get_word_from_pos(GtkXText *gtktext, int pos, unsigned char* buf,
317                                   int *pstart, int *pend) 
318 {
319     gint start, end;
320     if (iswordsep(get_text_index_whar(gtktext, pos))) return FALSE;
321
322     for (start = pos; start >= 0; --start) {
323         if (iswordsep(get_text_index_whar(gtktext, start))) break;
324     }
325     start++;
326
327     for (end = pos; end < gtk_xtext_get_length(gtktext); end++) {
328         if (iswordsep(get_text_index_whar(gtktext, end))) break;
329     }
330
331     if (buf) {
332         for (pos = start; pos < end; pos++) buf[pos - start] = get_text_index_whar(gtktext, pos);
333         buf[pos - start] = 0;
334     }
335
336     if (pstart) *pstart = start;
337     if (pend) *pend = end;
338
339     return TRUE;
340 }
341
342 static gboolean get_curword(GtkXText *gtktext, unsigned char* buf,
343                             int *pstart, int *pend) 
344 {
345     int pos = gtk_editable_get_position(GTK_EDITABLE(gtktext));
346     return get_word_from_pos(gtktext, pos, buf, pstart, pend);
347 }
348
349 static void change_color(GtkXText *gtktext, int start, int end, GdkColor *color) 
350 {
351     char *newtext;
352     if (start >= end) {
353         return ;
354     };
355     gtk_xtext_freeze(gtktext);
356     newtext = gtk_editable_get_chars(GTK_EDITABLE(gtktext), start, end);
357 //    if (prefs_common.auto_makeispell) {
358         gtk_signal_handler_block_by_func(GTK_OBJECT(gtktext),
359                                          GTK_SIGNAL_FUNC(entry_insert_cb), NULL);
360 //    }
361     gtk_xtext_set_point(gtktext, start);
362     gtk_xtext_forward_delete(gtktext, end - start);
363
364     if (newtext && end - start > 0)
365         gtk_xtext_insert(gtktext, NULL, color, NULL, newtext, end - start);
366   //  if (prefs_common.auto_makeispell) {
367         gtk_signal_handler_unblock_by_func(GTK_OBJECT(gtktext),
368                                            GTK_SIGNAL_FUNC(entry_insert_cb), NULL);
369    // }
370     gtk_xtext_thaw(gtktext);
371 }
372
373 static gboolean check_at(GtkXText *gtktext, int from_pos) 
374 {
375     int start, end;
376     unsigned char buf[BUFSIZE];
377     if (from_pos < 0) return FALSE;
378     if (!get_word_from_pos(gtktext, from_pos, buf, &start, &end)) {
379         return FALSE;
380     };
381     if (misspelled_test(buf)) {
382         if (highlight.pixel == 0) {
383             /* add an entry for the highlight in the color map. */
384             GdkColormap *gc = gtk_widget_get_colormap(GTK_WIDGET(gtktext));
385             gdk_colormap_alloc_color(gc, &highlight, FALSE, TRUE);
386             ;
387         }
388         change_color(gtktext, start, end, &highlight);
389         return TRUE;
390     } else {
391         change_color(gtktext, start, end,
392                      &(GTK_WIDGET(gtktext)->style->fg[0]));
393         return FALSE;
394     }
395 }
396
397 void gtkspell_check_all(GtkXText *gtktext) 
398 {
399     guint origpos;
400     guint pos = 0;
401     guint len;
402     float adj_value;
403
404     if (!gtkspell_running()) return ;
405
406     len = gtk_xtext_get_length(gtktext);
407
408     adj_value = gtktext->vadj->value;
409     gtk_xtext_freeze(gtktext);
410     origpos = gtk_editable_get_position(GTK_EDITABLE(gtktext));
411     while (pos < len) {
412         while (pos < len && iswordsep(get_text_index_whar(gtktext, pos)))
413             pos++;
414         while (pos < len && !iswordsep(get_text_index_whar(gtktext, pos)))
415             pos++;
416         if (pos > 0)
417             check_at(gtktext, pos - 1);
418     }
419     gtk_xtext_thaw(gtktext);
420     gtk_editable_set_position(GTK_EDITABLE(gtktext), origpos);
421     gtk_editable_select_region(GTK_EDITABLE(gtktext), origpos, origpos);
422
423 }
424
425 static void entry_insert_cb(GtkXText *gtktext,
426                             gchar *newtext, guint len, guint *ppos, gpointer d) 
427 {
428     int origpos;
429     if (!gtkspell_running()) return ;
430
431     gtk_signal_handler_block_by_func(GTK_OBJECT(gtktext),
432                                      GTK_SIGNAL_FUNC(entry_insert_cb),
433                                      NULL);
434     gtk_xtext_insert(GTK_XTEXT(gtktext), NULL,
435                     &(GTK_WIDGET(gtktext)->style->fg[0]), NULL, newtext, len);
436
437     gtk_signal_handler_unblock_by_func(GTK_OBJECT(gtktext),
438                                        GTK_SIGNAL_FUNC(entry_insert_cb),
439                                        NULL);
440     gtk_signal_emit_stop_by_name(GTK_OBJECT(gtktext), "insert-text");
441     *ppos += len;
442     origpos = gtk_editable_get_position(GTK_EDITABLE(gtktext));
443
444     if (iswordsep(newtext[0])) {
445         /* did we just end a word? */
446         if (*ppos >= 2) check_at(gtktext, *ppos - 2);
447
448         /* did we just split a word? */
449         if (*ppos < gtk_xtext_get_length(gtktext))
450             check_at(gtktext, *ppos + 1);
451     } else {
452         /* check as they type, *except* if they're typing at the end (the most
453          * common case.
454          */
455         if (*ppos < gtk_xtext_get_length(gtktext) &&
456                 !iswordsep(get_text_index_whar(gtktext, *ppos)))
457             check_at(gtktext, *ppos - 1);
458     }
459
460     gtk_editable_set_position(GTK_EDITABLE(gtktext), origpos);
461 }
462
463 static void entry_delete_cb(GtkXText *gtktext,
464                             gint start, gint end, gpointer d) 
465 {
466     int origpos;
467     if (!gtkspell_running()) return ;
468
469     origpos = gtk_editable_get_position(GTK_EDITABLE(gtktext));
470     check_at(gtktext, start - 1);
471     gtk_editable_set_position(GTK_EDITABLE(gtktext), origpos);
472     gtk_editable_select_region(GTK_EDITABLE(gtktext), origpos, origpos);
473     /* this is to *UNDO* the selection, in case they were holding shift
474      * while hitting backspace. */
475 }
476
477 static void replace_word(GtkWidget *w, gpointer d) 
478 {
479     int start, end;
480     unsigned char *newword;
481     unsigned char buf[BUFSIZE];
482     gtk_xtext_freeze(GTK_XTEXT(d));
483
484     gtk_label_get(GTK_LABEL(GTK_BIN(w)->child), (gchar**) &newword);
485     get_curword(GTK_XTEXT(d), buf, &start, &end);
486
487     gtk_xtext_set_point(GTK_XTEXT(d), end);
488     gtk_xtext_backward_delete(GTK_XTEXT(d), end - start);
489     gtk_xtext_insert(GTK_XTEXT(d), NULL, NULL, NULL, newword, strlen(newword));
490
491     gtk_xtext_thaw(GTK_XTEXT(d));
492 }
493
494 static GtkMenu *make_menu(GList *l, GtkXText *gtktext) 
495 {
496     GtkWidget *menu, *item;
497     unsigned char *caption;
498     menu = gtk_menu_new(); 
499     
500         caption = g_strdup_printf(_("Not in dictionary: %s"), (unsigned char*)l->data);
501         item = gtk_menu_item_new_with_label(caption);
502         /* I'd like to make it so this item is never selectable, like
503          * the menu titles in the GNOME panel... unfortunately, the GNOME
504          * panel creates their own custom widget to do this! */
505         gtk_widget_show(item);
506         gtk_menu_append(GTK_MENU(menu), item);
507
508         item = gtk_menu_item_new();
509         gtk_widget_show(item);
510         gtk_menu_append(GTK_MENU(menu), item);
511
512         l = l->next;
513         if (l == NULL) {
514             item = gtk_menu_item_new_with_label(_("(no suggestions)"));
515             gtk_widget_show(item);
516             gtk_menu_append(GTK_MENU(menu), item);
517         } else {
518             GtkWidget *curmenu = menu;
519             int count = 0;
520             do {
521                 if (l->data == NULL && l->next != NULL) {
522                     count = 0;
523                     curmenu = gtk_menu_new();
524                     item = gtk_menu_item_new_with_label(_("Others..."));
525                     gtk_widget_show(item);
526                     gtk_menu_item_set_submenu(GTK_MENU_ITEM(item), curmenu);
527                     gtk_menu_append(GTK_MENU(curmenu), item);
528                     l = l->next;
529                 } else if (count > MENUCOUNT) {
530                     count -= MENUCOUNT;
531                     item = gtk_menu_item_new_with_label(_("More..."));
532                     gtk_widget_show(item);
533                     gtk_menu_append(GTK_MENU(curmenu), item);
534                     curmenu = gtk_menu_new();
535                     gtk_menu_item_set_submenu(GTK_MENU_ITEM(item), curmenu);
536                 }
537                 item = gtk_menu_item_new_with_label((unsigned char*)l->data);
538                 gtk_signal_connect(GTK_OBJECT(item), "activate",
539                                    GTK_SIGNAL_FUNC(replace_word), gtktext);
540                 gtk_widget_show(item);
541                 gtk_menu_append(GTK_MENU(curmenu), item);
542                 count++;
543             } while ((l = l->next) != NULL);
544         }
545     return GTK_MENU(menu);
546 }
547
548 static void popup_menu(GtkXText *gtktext, GdkEventButton *eb) 
549 {
550     unsigned char buf[BUFSIZE];
551     GList *list, *l;
552     if (!get_curword(gtktext, buf, NULL, NULL)) return ;
553     if (buf == NULL) return ;
554     list = misspelled_suggest(buf);
555     if (list != NULL) {
556         gtk_menu_popup(make_menu(list, gtktext), NULL, NULL, NULL, NULL,
557                        eb->button, eb->time);
558         for (l = list; l != NULL; l = l->next)
559             g_free(l->data);
560         g_list_free(list);
561     }
562 }
563
564 /* ok, this is pretty wacky:
565  * we need to let the right-mouse-click go through, so it moves the cursor,
566  * but we *can't* let it go through, because GtkText interprets rightclicks as
567  * weird selection modifiers.
568  *
569  * so what do we do?  forge rightclicks as leftclicks, then popup the menu.
570  * HACK HACK HACK.
571  */
572 static gint button_press_intercept_cb(GtkXText *gtktext, GdkEvent *e, gpointer d) 
573 {
574     GdkEventButton *eb;
575     gboolean retval;
576
577     if (!gtkspell_running()) return FALSE;
578
579     if (e->type != GDK_BUTTON_PRESS) return FALSE;
580     eb = (GdkEventButton*) e;
581
582     if (eb->button != 3) return FALSE;
583
584     /* forge the leftclick */
585     eb->button = 1;
586
587     gtk_signal_handler_block_by_func(GTK_OBJECT(gtktext),
588                                      GTK_SIGNAL_FUNC(button_press_intercept_cb), d);
589     gtk_signal_emit_by_name(GTK_OBJECT(gtktext), "button-press-event",
590                             e, &retval);
591     gtk_signal_handler_unblock_by_func(GTK_OBJECT(gtktext),
592                                        GTK_SIGNAL_FUNC(button_press_intercept_cb), d);
593     gtk_signal_emit_stop_by_name(GTK_OBJECT(gtktext), "button-press-event");
594
595     /* now do the menu wackiness */
596     popup_menu(gtktext, eb);
597     return TRUE;
598 }
599
600 void gtkspell_uncheck_all(GtkXText *gtktext) 
601 {
602     int origpos;
603     unsigned char *text;
604     float adj_value;
605     adj_value = gtktext->vadj->value;
606     gtk_xtext_freeze(gtktext);
607     origpos = gtk_editable_get_position(GTK_EDITABLE(gtktext));
608     text = gtk_editable_get_chars(GTK_EDITABLE(gtktext), 0, -1);
609     gtk_xtext_set_point(gtktext, 0);
610     gtk_xtext_forward_delete(gtktext, gtk_xtext_get_length(gtktext));
611     gtk_xtext_insert(gtktext, NULL, NULL, NULL, text, strlen(text));
612     gtk_xtext_thaw(gtktext);
613
614     gtk_editable_set_position(GTK_EDITABLE(gtktext), origpos);
615     gtk_adjustment_set_value(gtktext->vadj, adj_value);
616 }
617
618 void gtkspell_attach(GtkXText *gtktext) 
619 {
620 //    if (prefs_common.auto_makeispell) {
621         gtk_signal_connect(GTK_OBJECT(gtktext), "insert-text",
622                            GTK_SIGNAL_FUNC(entry_insert_cb), NULL);
623         gtk_signal_connect_after(GTK_OBJECT(gtktext), "delete-text",
624                                  GTK_SIGNAL_FUNC(entry_delete_cb), NULL);
625 //    };
626     gtk_signal_connect(GTK_OBJECT(gtktext), "button-press-event",
627                        GTK_SIGNAL_FUNC(button_press_intercept_cb), NULL);
628
629 }
630
631 void gtkspell_detach(GtkXText *gtktext) 
632 {
633 //    if (prefs_common.auto_makeispell) {
634         gtk_signal_disconnect_by_func(GTK_OBJECT(gtktext),
635                                       GTK_SIGNAL_FUNC(entry_insert_cb), NULL);
636         gtk_signal_disconnect_by_func(GTK_OBJECT(gtktext),
637                                       GTK_SIGNAL_FUNC(entry_delete_cb), NULL);
638 //    };
639     gtk_signal_disconnect_by_func(GTK_OBJECT(gtktext),
640                                   GTK_SIGNAL_FUNC(button_press_intercept_cb), NULL);
641
642     gtkspell_uncheck_all(gtktext);
643 }
644
645 static void sigchld(int param) 
646 {
647     int retstat, retval;
648
649     debug_print(_("*** SIGCHLD called. (ispell pid: %i)\n"), spell_pid);
650     while ((retval = waitpid( -1, &retstat, WNOHANG)) > 0) {
651         if (retval == spell_pid) {
652             debug_print(_("*** SIGCHLD called for ispell. (pid: %i)\n"), spell_pid);
653             spell_pid = -1;
654         }
655     }
656 }
657
658 static void set_up_signal() 
659 {
660     /* RETSIGTYPE is found in autoconf's config.h */
661 #ifdef RETSIGTYPE
662     typedef RETSIGTYPE (*sighandler)(int);
663     signal(SIGCHLD, (sighandler)sigchld);
664 #else
665     /* otherwise, just hope it works */
666     signal(SIGCHLD, sigchld);
667 #endif
668 }
669
670 /*** Sylpheed (Claws) ***/
671
672 static GSList *create_empty_dictionary_list(void)
673 {
674         GSList *list = NULL;
675         Dictionary *dict;
676         
677         dict = g_new0(Dictionary, 1);
678         dict->name = g_strdup(_("None"));
679         dict->path = NULL;
680         return g_slist_append(list, dict);
681 }
682
683 /* gtkspell_get_dictionary_list() - returns list of dictionary names and the full 
684  * path of file names. */
685 GSList *gtkspell_get_dictionary_list(const gchar *ispell_path)
686 {
687         GSList *list;
688         gchar *dict_path, *tmp, *prevdir;
689         GSList *walk;
690         Dictionary *dict;
691         DIR *dir;
692         struct dirent *ent;
693
694         list = NULL;
695
696         /* ASSUME: ispell_path is full path */
697         dict_path = strstr(ispell_path + 1, G_DIR_SEPARATOR_S);
698         tmp = g_strndup(ispell_path, dict_path - ispell_path);
699         
700         /* ASSUME: ispell dictionaries in PREFIX/lib/ispell */
701         dict_path = g_strconcat(tmp, G_DIR_SEPARATOR_S, "lib", G_DIR_SEPARATOR_S, "ispell", NULL);
702         g_free(tmp);
703
704 #ifdef USE_THREADS
705 #warning TODO: no directory change
706 #endif
707         
708         prevdir = g_get_current_dir();
709         if (chdir(dict_path) <0) {
710                 FILE_OP_ERROR(dict_path, "chdir");
711                 g_free(prevdir);
712                 g_free(dict_path);
713                 return create_empty_dictionary_list();
714         }
715
716         debug_print(_("Checking for dictionaries in %s\n"), dict_path);
717
718         if (NULL != (dir = opendir("."))) {
719                 while (NULL != (ent = readdir(dir))) {
720                         /* search for hash table */
721                         if (NULL != (tmp = strstr(ent->d_name, ".hash"))) {
722                                 dict = g_new0(Dictionary, 1);
723                                 dict->name = g_strndup(ent->d_name, tmp - ent->d_name);
724                                 dict->path = g_strconcat(dict_path, G_DIR_SEPARATOR_S, ent->d_name, NULL);
725                                 debug_print(_("Found dictionary %s\n"), dict->path);
726                                 list = g_slist_append(list, dict);
727                         }
728                 }                       
729                 closedir(dir);
730         }
731         else {
732                 FILE_OP_ERROR(dict_path, "opendir");
733                 debug_print(_("No dictionary found\n"));
734                 list = create_empty_dictionary_list();
735         }
736         chdir(prevdir);
737         g_free(dict_path);
738         g_free(prevdir);
739         return list;
740 }
741
742 void gtkspell_free_dictionary_list(GSList *list)
743 {
744         Dictionary *dict;
745         GSList *walk;
746         for (walk = list; walk != NULL; walk = g_slist_next(walk))
747                 if (walk->data) {
748                         dict = (Dictionary *) walk->data;
749                         if (dict->name)
750                                 g_free(dict->name);
751                         if (dict->path)
752                                 g_free(dict->path);
753                         g_free(dict);
754                 }                               
755         g_slist_free(list);
756 }
757
758 static void dictionary_option_menu_item_data_destroy(gpointer data)
759 {
760         gchar *str = (gchar *) data;
761
762         if (str)
763                 g_free(str);
764 }
765
766 GtkWidget *gtkspell_dictionary_option_menu_new(const gchar *ispell_path)
767 {
768         GSList *dict_list, *tmp;
769         GtkWidget *item;
770         GtkWidget *menu;
771         Dictionary *dict;
772
773         dict_list = gtkspell_get_dictionary_list(ispell_path);
774         g_return_val_if_fail(dict_list, NULL);
775
776         menu = gtk_menu_new();
777         
778         for (tmp = dict_list; tmp != NULL; tmp = g_slist_next(tmp)) {
779                 dict = (Dictionary *) tmp->data;
780                 item = gtk_menu_item_new_with_label(dict->name);
781                 if (dict->path)
782                         gtk_object_set_data_full(GTK_OBJECT(item), "full_path",
783                                          g_strdup(dict->path), 
784                                          dictionary_option_menu_item_data_destroy);
785                 gtk_menu_append(GTK_MENU(menu), item);                                   
786                 gtk_widget_show(item);
787         }
788
789         gtk_widget_show(menu);
790
791         gtkspell_free_dictionary_list(dict_list);
792
793         return menu;
794 }
795
796 gchar *gtkspell_get_dictionary_menu_active_item(GtkWidget *menu)
797 {
798         GtkWidget *menuitem;
799         gchar *result;
800
801         g_return_val_if_fail(GTK_IS_MENU(menu), NULL);
802         menuitem = gtk_menu_get_active(GTK_MENU(menu));
803         
804         result = gtk_object_get_data(GTK_OBJECT(menuitem), "full_path");
805         g_return_val_if_fail(result, NULL);
806
807         return g_strdup(result);
808 }
809