changeset 1117:f3e0f41beddb

[gaim-migrate @ 1127] Removed the old, crappy spellchecker code; replaced to use gtkspell. works much better. got gtkspell.[ch] out of gtkspell's cvs repository. committer: Tailor Script <tailor@pidgin.im>
author Eric Warmenhoven <eric@warmenhoven.org>
date Tue, 21 Nov 2000 09:50:31 +0000
parents 35476475f794
children e1078ee286ba
files ChangeLog src/Makefile.am src/about.c src/aim.c src/buddy.c src/buddy_chat.c src/conversation.c src/gaim.h src/gtkhtml.c src/gtkspell.c src/gtkspell.h src/prefs.c src/util.c
diffstat 13 files changed, 772 insertions(+), 106 deletions(-) [+]
line wrap: on
line diff
--- a/ChangeLog	Tue Nov 21 01:11:27 2000 +0000
+++ b/ChangeLog	Tue Nov 21 09:50:31 2000 +0000
@@ -11,6 +11,7 @@
 	  (Thanks Ryan C. Gordon)
 	* Plugin system uses GModule now (improves portability, adds features)
 	* Perl got updates (reread plugins/PERL-HOWTO)
+	* Spell checker now uses gtkspell
 
 version 0.10.3 (10/09/2000):
 	* Segfault when viewing user info fixed
--- a/src/Makefile.am	Tue Nov 21 01:11:27 2000 +0000
+++ b/src/Makefile.am	Tue Nov 21 09:50:31 2000 +0000
@@ -11,6 +11,7 @@
 			gaimrc.c \
 			gnome_applet_mgr.c \
 			gtkhtml.c \
+			gtkspell.c \
 			gtkticker.c \
 			html.c \
 			idle.c \
@@ -42,6 +43,7 @@
 		gaimrc.c \
 		gnome_applet_mgr.c \
 		gtkhtml.c \
+		gtkspell.c \
 		gtkticker.c \
 		html.c \
 		idle.c \
@@ -71,6 +73,7 @@
 		getopt.h \
 		getopt1.c \
 		gtkhtml.h \
+		gtkspell.h \
 		gtkticker.h \
 		gnome_applet_mgr.h \
 		multi.h \
--- a/src/about.c	Tue Nov 21 01:11:27 2000 +0000
+++ b/src/about.c	Tue Nov 21 09:50:31 2000 +0000
@@ -43,7 +43,7 @@
 
 static void version_exit()
 {
-	exit(0);
+	gtk_main_quit();
 }
 
 
--- a/src/aim.c	Tue Nov 21 01:11:27 2000 +0000
+++ b/src/aim.c	Tue Nov 21 09:50:31 2000 +0000
@@ -53,6 +53,7 @@
 #endif
 #include "locale.h"
 #include "gtkticker.h"
+#include "gtkspell.h"
 #ifndef USE_GNOME
 #include <getopt.h>
 #endif
@@ -101,7 +102,7 @@
 	perl_end();
 #endif
 
-	exit(0);
+	gtk_main_quit();
 #endif /* USE_APPLET */
 }
 
@@ -572,6 +573,7 @@
 	if (general_options & OPT_GEN_DEBUG)
 		show_debug(NULL);
 
+	gtkspell_start(NULL, ispell_cmd);
 #ifdef USE_PERL
 	perl_init();
 	perl_autoload();
@@ -622,6 +624,8 @@
         
 #endif /* USE_APPLET */
         
+	gtkspell_stop();
+
 	return 0;
 	
 }
--- a/src/buddy.c	Tue Nov 21 01:11:27 2000 +0000
+++ b/src/buddy.c	Tue Nov 21 09:50:31 2000 +0000
@@ -1037,7 +1037,7 @@
 	perl_end();
 #endif
 
-	exit(0);
+	gtk_main_quit();
 }
 
 void add_buddy_callback(GtkWidget *widget, void *dummy)
--- a/src/buddy_chat.c	Tue Nov 21 01:11:27 2000 +0000
+++ b/src/buddy_chat.c	Tue Nov 21 09:50:31 2000 +0000
@@ -29,6 +29,7 @@
 #include <stdlib.h>
 #include <gtk/gtk.h>
 #include "gtkhtml.h"
+#include "gtkspell.h"
 #include <gdk/gdkkeysyms.h>
 
 #include "convo.h"
@@ -692,8 +693,9 @@
 	gtk_window_set_focus(GTK_WINDOW(win), chatentry);
 
 	gtk_signal_connect(GTK_OBJECT(win), "destroy", GTK_SIGNAL_FUNC(close_callback),b);
-	gtk_signal_connect(GTK_OBJECT(chatentry), "insert-text", GTK_SIGNAL_FUNC(check_spelling), chatentry);
 	gtk_signal_connect(GTK_OBJECT(chatentry), "key_press_event", GTK_SIGNAL_FUNC(entry_key_pressed), chatentry);
+	if (general_options & OPT_GEN_CHECK_SPELLING)
+		gtkspell_attach(GTK_TEXT(chatentry));
 
 	b->font_dialog = NULL;
 	b->fg_color_dialog = NULL;	
--- a/src/conversation.c	Tue Nov 21 01:11:27 2000 +0000
+++ b/src/conversation.c	Tue Nov 21 09:50:31 2000 +0000
@@ -34,6 +34,7 @@
 #include "gtkhtml.h"
 #include <gdk/gdkkeysyms.h>
 #include "convo.h"
+#include "gtkspell.h"
 
 #include "pixmaps/underline.xpm"
 #include "pixmaps/bold.xpm"
@@ -345,6 +346,9 @@
 
 	debug_print("conversation close callback\n");
 
+	if (general_options & OPT_GEN_CHECK_SPELLING)
+		gtkspell_detach(GTK_TEXT(c->entry));
+
 	if (c->window)
 	        gtk_widget_destroy(c->window);
 	c->window = NULL;
@@ -1389,29 +1393,6 @@
 
 
 
-void check_spelling( GtkEditable * editable, gchar * new_text,
-                                                          gint length, gint * position,
-                                                          gpointer data )
-{
-	if (general_options & OPT_GEN_CHECK_SPELLING)
-	{
-        	gtk_signal_handler_block_by_func(GTK_OBJECT(editable),
-        	GTK_SIGNAL_FUNC(check_spelling), data);
-		gtk_text_set_point(GTK_TEXT(editable), *position);
-		gtk_text_insert(GTK_TEXT(editable), NULL, &(GTK_WIDGET(editable)->style->fg[0]), NULL, new_text, length );
-        	if(isspace(new_text[0]))
-        	{
-                	gtk_text_freeze(GTK_TEXT(editable));
-                	spell_checker(GTK_WIDGET(editable));
-                	gtk_text_thaw(GTK_TEXT(editable));
-        	}
-        	gtk_signal_handler_unblock_by_func(GTK_OBJECT(editable),
-				GTK_SIGNAL_FUNC(check_spelling), data);
-        	gtk_signal_emit_stop_by_name(GTK_OBJECT(editable), "insert-text");
-	}
-}
-
-
 GtkWidget *build_conv_toolbar(struct conversation *c) {
         GdkPixmap *strike_i, *small_i, *normal_i, *big_i, *bold_i, *italic_i, *underline_i, *speaker_i, *wood_i, *fgcolor_i, *bgcolor_i, *link_i, *font_i, *smiley_i;
         GtkWidget *strike_p, *small_p, *normal_p, *big_p, *bold_p, *italic_p, *underline_p, *speaker_p, *wood_p, *fgcolor_p, *bgcolor_p, *link_p, *font_p, *smiley_p;
@@ -1890,10 +1871,41 @@
 	gtk_window_set_focus(GTK_WINDOW(win),entry);
 
 	gtk_signal_connect(GTK_OBJECT(win), "delete_event", GTK_SIGNAL_FUNC(delete_event_convo), c);
-	gtk_signal_connect(GTK_OBJECT(entry), "insert-text", GTK_SIGNAL_FUNC(check_spelling), entry);
 	gtk_signal_connect(GTK_OBJECT(entry), "key_press_event", GTK_SIGNAL_FUNC(entry_key_pressed), entry);
+	if (general_options & OPT_GEN_CHECK_SPELLING)
+		gtkspell_attach(GTK_TEXT(c->entry));
 
 	gtk_widget_show(win);
 }
 
 
+void toggle_spellchk() {
+	GList *cnv = conversations;
+	GSList *cht;
+	struct conversation *c;
+	GSList *con = connections;
+	struct gaim_connection *gc;
+
+	while (cnv) {
+		c = (struct conversation *)cnv->data;
+		if (general_options & OPT_GEN_CHECK_SPELLING)
+			gtkspell_attach(GTK_TEXT(c->entry));
+		else
+			gtkspell_detach(GTK_TEXT(c->entry));
+		cnv = cnv->next;
+	}
+
+	while (con) {
+		gc = (struct gaim_connection *)con->data;
+		cht = gc->buddy_chats;
+		while (cht) {
+			c = (struct conversation *)cht->data;
+			if (general_options & OPT_GEN_CHECK_SPELLING)
+				gtkspell_attach(GTK_TEXT(c->entry));
+			else
+				gtkspell_detach(GTK_TEXT(c->entry));
+			cht = cht->next;
+		}
+		con = con->next;
+	}
+}
--- a/src/gaim.h	Tue Nov 21 01:11:27 2000 +0000
+++ b/src/gaim.h	Tue Nov 21 09:50:31 2000 +0000
@@ -532,7 +532,6 @@
 extern struct aim_user *find_user(const char *, int);
 extern char *full_date();
 extern void check_gaim_versions();
-extern void spell_checker(GtkWidget *);
 extern char *away_subs(char *, char *);
 extern GtkWidget *picture_button(GtkWidget *, char *, char **);
 extern GtkWidget *picture_button2(GtkWidget *, char *, char **, short);
--- a/src/gtkhtml.c	Tue Nov 21 01:11:27 2000 +0000
+++ b/src/gtkhtml.c	Tue Nov 21 09:50:31 2000 +0000
@@ -3481,8 +3481,11 @@
 							if (sscanf(d, "%x", &colorv)
 								&& !(options & HTML_OPTION_NO_COLOURS))
 							{
-								current->bgcol = get_color(colorv, map);
-								current->ownbg = 1;
+								if (colorv != 0xffffff ||
+								    !(display_options & OPT_DISP_IGN_WHITE)) {
+									current->bgcol = get_color(colorv, map);
+									current->ownbg = 1;
+								}
 							}
 							else
 							{
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/gtkspell.c	Tue Nov 21 09:50:31 2000 +0000
@@ -0,0 +1,609 @@
+/* gtkspell - a spell-checking addon for GtkText
+ * Copyright (c) 2000 Evan Martin.
+ * vim: ts=4 sw=4
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ * 
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307  USA
+ */
+
+#include <gtk/gtk.h>
+
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <sys/time.h>
+#include <unistd.h>   
+#include <stdio.h>    
+#include <signal.h>
+#include <ctype.h>
+#include <string.h>
+#include <stdlib.h>
+#include <errno.h>
+
+/* TODO:
+ * handle dictionary changes
+ * asynchronous lookups
+ */
+
+/* size of the text buffer used in various word-processing routines. */
+#define BUFSIZE 1024
+/* number of suggestions to display on each menu. */
+#define MENUCOUNT 10
+#define BUGEMAIL "gtkspell-devel@lists.sourceforge.net"
+
+/* because we keep only one copy of the spell program running, 
+ * all ispell-related variables can be static.  
+ */
+static pid_t spell_pid = -1;
+static int fd_write[2], fd_read[2];
+static int signal_set_up = 0;
+
+/* FIXME? */
+static GdkColor highlight = { 0, 255*256, 0, 0 };
+
+static void entry_insert_cb(GtkText *gtktext, 
+		gchar *newtext, guint len, guint *ppos, gpointer d);
+static void set_up_signal();
+
+int gtkspell_running() {
+	return (spell_pid > 0);
+}
+
+static void error_print(const char *fmt, ...) {
+	va_list ap;
+	va_start(ap, fmt);
+	fprintf(stderr, "gtkspell: ");
+	vfprintf(stderr, fmt, ap);
+	va_end(ap);
+}
+
+/* functions to interface with pipe */
+static void writetext(char *text) {
+	write(fd_write[1], text, strlen(text));
+}
+static int readpipe(char *buf, int bufsize) {
+	int len;
+	len = read(fd_read[0], buf, bufsize-1);
+	if (len < 0) {
+		error_print("read: %s\n", strerror(errno));
+		return -1;
+	} else if (len == 0) {
+		error_print("pipe closed.\n");
+		return -1;
+	} else if (len == bufsize-1) {
+		error_print("buffer overflowed?\n");
+	}
+
+	buf[len] = 0;
+	return len;
+}
+static int readline(char *buf) {
+	return readpipe(buf, BUFSIZE);
+}
+
+static int readresponse(char *buf) {
+	int len;
+	len = readpipe(buf, BUFSIZE);
+
+	/* all ispell responses of any reasonable length should end in \n\n.
+	 * depending on the speed of the spell checker, this may require more
+	 * reading. */
+	if (len >= 2 && (buf[len-1] != '\n' || buf[len-2] != '\n')) {
+		len += readpipe(buf+len, BUFSIZE-len);
+	}
+
+	/* now we can remove all of the the trailing newlines. */
+	while (len > 0 && buf[len-1] == '\n')
+		buf[--len] = 0;
+
+	return len;
+}
+
+
+void gtkspell_stop() {
+	if (gtkspell_running()) {
+		kill(spell_pid, SIGQUIT); /* FIXME: is this the correct signal? */
+	}
+}
+
+int gtkspell_start(char *path, char * args[]) {
+	int fd_error[2];
+	char buf[BUFSIZE];
+
+	if (gtkspell_running()) {
+		error_print("gtkspell_start called while already running.\n");
+		gtkspell_stop();
+	}
+
+	if (!signal_set_up) {
+		set_up_signal();
+		signal_set_up = 1;
+	}
+
+	pipe(fd_write);
+	pipe(fd_read);
+	pipe(fd_error);
+
+	spell_pid = fork();
+	if (spell_pid < 0) {
+		error_print("fork: %s\n", strerror(errno));
+		return -1;
+	} else if (spell_pid == 0) {
+		dup2(fd_write[0], 0);
+		dup2(fd_read[1], 1);
+		dup2(fd_error[1], 2);
+		close(fd_read[0]);
+		close(fd_error[0]);
+		close(fd_write[1]);
+
+		if (path == NULL) {
+			if (execvp(args[0], args) < 0) 
+				error_print("execvp('%s'): %s\n", args[0], strerror(errno));
+		} else {
+			if (execv(path, args) < 0) 
+				error_print("execv('%s'): %s\n", path, strerror(errno));
+		}
+		/* if we get here, we failed.
+		 * send some text on the pipe to indicate status.
+		 */
+		write(fd_read[1], "!", 1);
+
+		_exit(0);
+	} else {
+		/* there are at least two ways to fail:
+		 * - the exec() can fail
+		 * - the exec() can succeed, but the program can dump the help screen
+		 * we must check for both.
+		 */
+		fd_set rfds;
+		struct timeval tv;
+		
+		FD_ZERO(&rfds);
+		FD_SET(fd_error[0], &rfds);
+		FD_SET(fd_read[0], &rfds);
+		tv.tv_sec = 2;
+		tv.tv_usec = 0;
+		if (select(MAX(fd_error[0], fd_read[0])+1, 
+					&rfds, NULL, NULL, &tv) < 0) {
+			/* FIXME: is this needed? */
+			error_print("Timed out waiting for spell command.\n");
+			gtkspell_stop();
+			return -1;
+		}
+
+		if (FD_ISSET(fd_error[0], &rfds)) { /* stderr readable? */
+			error_print("Spell command printed on stderr -- probably failed.\n");
+			gtkspell_stop();
+			return -1;
+		}
+
+		/* otherwise, fd_read[0] is set. */
+		readline(buf);
+
+		/* ispell should print something like this:
+		 * @(#) International Ispell Version 3.1.20 10/10/95
+		 * if it doesn't, it's an error. */
+		if (buf[0] != '@') {
+			gtkspell_stop();
+			return -1;
+		}
+	}
+
+	/* put ispell into terse mode.  
+	 * this makes it not respond on correctly spelled words. */
+	sprintf(buf, "!\n");
+	writetext(buf);
+	return 0;
+}
+
+static GList* misspelled_suggest(char *word) {
+	char buf[BUFSIZE];
+	char *newword;
+	GList *l = NULL;
+	int count;
+
+	sprintf(buf, "^%s\n", word); /* guard against ispell control chars */
+	writetext(buf);
+	readresponse(buf);
+
+	switch (buf[0]) { /* first char is ispell command. */
+		case 0: /* no response: word is ok. */
+			return NULL;
+		case '&': /* misspelled, with suggestions */
+			/* & <orig> <count> <ofs>: <miss>, <miss>, <guess>, ... */
+			strtok(buf, " "); /* & */
+			newword = strtok(NULL, " "); /* orig */
+			l = g_list_append(l, g_strdup(newword));
+			newword = strtok(NULL, " "); /* count */
+			count = atoi(newword);
+			strtok(NULL, " "); /* ofs: */
+
+			while ((newword = strtok(NULL, ",")) != NULL) {
+				int len = strlen(newword);
+				if (newword[len-1] == ' ' || newword[len-1] == '\n') 
+					newword[len-1] = 0;
+				if (count == 0) {
+					g_list_append(l, NULL); /* signal the "suggestions" */
+				}
+				/* add it to the list, skipping the initial space. */
+				l = g_list_append(l, 
+						g_strdup(newword[0] == ' ' ? newword+1 : newword));
+
+				count--;
+			}
+			return l;
+
+		case '#': /* misspelled, no suggestions */
+			/* # <orig> <ofs> */
+			strtok(buf, " "); /* & */
+			newword = strtok(NULL, " "); /* orig */
+			l = g_list_append(l, g_strdup(newword));
+			return l;
+		default:
+			error_print("Unsupported spell command '%c'.\n"
+					"This is a bug; mail " BUGEMAIL " about it.\n", buf[0]);
+	}
+	return NULL;
+}
+
+static int misspelled_test(char *word) {
+	char buf[BUFSIZE];
+	sprintf(buf, "^%s\n", word); /* guard against ispell control chars */
+	writetext(buf);
+	readresponse(buf);
+
+	if (buf[0] == 0) {
+		return 0;
+	} else if (buf[0] == '&' || buf[0] == '#') {
+		return 1;
+	}
+	
+	error_print("Unsupported spell command '%c'.\n"
+			"This is a bug; mail " BUGEMAIL " about it.\n", buf[0]);
+	return -1;
+}
+
+static gboolean iswordsep(char c) {
+	return !isalpha(c) && c != '\'';
+}
+
+static gboolean get_word_from_pos(GtkText* gtktext, int pos, char* buf, 
+		int *pstart, int *pend) {
+	gint start, end;
+
+	if (iswordsep(GTK_TEXT_INDEX(gtktext, pos))) return FALSE;
+
+	for (start = pos; start >= 0; --start) {
+		if (iswordsep(GTK_TEXT_INDEX(gtktext, start))) break;
+	}
+	start++;
+
+	for (end = pos; end <= gtk_text_get_length(gtktext); end++) {
+		if (iswordsep(GTK_TEXT_INDEX(gtktext, end))) break;
+	}
+
+	if (buf) {
+		for (pos = start; pos < end; pos++) 
+			buf[pos-start] = GTK_TEXT_INDEX(gtktext, pos);
+		buf[pos-start] = 0;
+	}
+
+	if (pstart) *pstart = start;
+	if (pend) *pend = end;
+
+	return TRUE;
+}
+
+static gboolean get_curword(GtkText* gtktext, char* buf, 
+		int *pstart, int *pend) {
+	int pos = gtk_editable_get_position(GTK_EDITABLE(gtktext));
+	return get_word_from_pos(gtktext, pos, buf, pstart, pend);
+}
+
+static void change_color(GtkText *gtktext, 
+		int start, int end, GdkColor *color) {
+	char *newtext = gtk_editable_get_chars(GTK_EDITABLE(gtktext), start, end);
+	gtk_text_freeze(gtktext);
+	gtk_signal_handler_block_by_func(GTK_OBJECT(gtktext), 
+			GTK_SIGNAL_FUNC(entry_insert_cb), NULL);
+	
+	gtk_text_set_point(gtktext, start);
+	gtk_text_forward_delete(gtktext, end-start);
+
+	if (newtext && end-start > 0)
+		gtk_text_insert(gtktext, NULL, color, NULL, newtext, end-start);
+
+	gtk_signal_handler_unblock_by_func(GTK_OBJECT(gtktext), 
+			GTK_SIGNAL_FUNC(entry_insert_cb), NULL);
+	gtk_text_thaw(gtktext);
+}
+
+static gboolean check_at(GtkText *gtktext, int from_pos) {
+	int start, end;
+	char buf[BUFSIZE];
+
+	if (!get_word_from_pos(gtktext, from_pos, buf, &start, &end)) {
+		return FALSE;
+	}
+
+	if (misspelled_test(buf)) {
+		if (highlight.pixel == 0) {
+			/* add an entry for the highlight in the color map. */
+			GdkColormap *gc = gtk_widget_get_colormap(GTK_WIDGET(gtktext));
+			gdk_colormap_alloc_color(gc, &highlight, FALSE, TRUE);;
+		}
+		change_color(gtktext, start, end, &highlight);
+		return TRUE;
+	} else { 
+		change_color(gtktext, start, end, 
+				&(GTK_WIDGET(gtktext)->style->fg[0]));
+		return FALSE;
+	}
+}
+
+void gtkspell_check_all(GtkText *gtktext) {
+	guint origpos;
+	guint pos = 0;
+	guint len;
+	float adj_value;
+	
+	if (!gtkspell_running()) return;
+	
+	len = gtk_text_get_length(gtktext);
+
+	adj_value = gtktext->vadj->value;
+	gtk_text_freeze(gtktext);
+	origpos = gtk_editable_get_position(GTK_EDITABLE(gtktext));
+	while (pos < len) {
+		while (pos < len && iswordsep(GTK_TEXT_INDEX(gtktext, pos)))
+			pos++;
+		while (pos < len && !iswordsep(GTK_TEXT_INDEX(gtktext, pos)))
+			pos++;
+		if (pos > 0)
+			check_at(gtktext, pos-1);
+	}
+	gtk_text_thaw(gtktext);
+	gtk_editable_set_position(GTK_EDITABLE(gtktext), origpos);
+}
+
+static void entry_insert_cb(GtkText *gtktext, 
+		gchar *newtext, guint len, guint *ppos, gpointer d) {
+	int origpos;
+
+	if (!gtkspell_running()) return;
+
+	gtk_signal_handler_block_by_func(GTK_OBJECT(gtktext),
+									 GTK_SIGNAL_FUNC(entry_insert_cb),
+									 NULL);
+	gtk_text_insert(GTK_TEXT(gtktext), NULL,
+			&(GTK_WIDGET(gtktext)->style->fg[0]), NULL, newtext, len);
+	gtk_signal_handler_unblock_by_func(GTK_OBJECT(gtktext),
+									 GTK_SIGNAL_FUNC(entry_insert_cb),
+									 NULL);
+	gtk_signal_emit_stop_by_name(GTK_OBJECT(gtktext), "insert-text");
+	*ppos += len;
+
+	origpos = gtk_editable_get_position(GTK_EDITABLE(gtktext));
+
+	if (iswordsep(newtext[0])) {
+		/* did we just end a word? */
+		if (*ppos >= 2) check_at(gtktext, *ppos-2);
+
+		/* did we just split a word? */
+		if (*ppos < gtk_text_get_length(gtktext))
+			check_at(gtktext, *ppos+1);
+	} else {
+		/* check as they type, *except* if they're typing at the end (the most
+		 * common case.
+		 */
+		if (*ppos < gtk_text_get_length(gtktext) && 
+				!iswordsep(GTK_TEXT_INDEX(gtktext, *ppos)))
+			check_at(gtktext, *ppos-1);
+	}
+
+	gtk_editable_set_position(GTK_EDITABLE(gtktext), origpos);
+}
+
+static void entry_delete_cb(GtkText *gtktext,
+		gint start, gint end, gpointer d) {
+	int origpos;
+
+	if (!gtkspell_running()) return;
+
+	origpos = gtk_editable_get_position(GTK_EDITABLE(gtktext));
+	check_at(gtktext, start-1);
+	gtk_editable_set_position(GTK_EDITABLE(gtktext), origpos);
+	gtk_editable_select_region(GTK_EDITABLE(gtktext), origpos, origpos);
+	/* this is to *UNDO* the selection, in case they were holding shift
+	 * while hitting backspace. */
+}
+
+static void replace_word(GtkWidget *w, gpointer d) {
+	int start, end;
+	char *newword;
+	char buf[BUFSIZE];
+
+	/* we don't save their position, 
+	 * because the cursor is moved by the click. */
+
+	gtk_text_freeze(GTK_TEXT(d));
+
+	gtk_label_get(GTK_LABEL(GTK_BIN(w)->child), &newword);
+	get_curword(GTK_TEXT(d), buf, &start, &end);
+
+	gtk_text_set_point(GTK_TEXT(d), end);
+	gtk_text_backward_delete(GTK_TEXT(d), end-start);
+	gtk_text_insert(GTK_TEXT(d), NULL, NULL, NULL, newword, strlen(newword));
+
+	gtk_text_thaw(GTK_TEXT(d));
+}
+
+static GtkMenu *make_menu(GList *l, GtkText *gtktext) {
+	GtkWidget *menu, *item;
+	char *caption;
+	menu = gtk_menu_new(); {
+		caption = g_strdup_printf("Not in dictionary: %s", (char*)l->data);
+		item = gtk_menu_item_new_with_label(caption);
+		/* I'd like to make it so this item is never selectable, like
+		 * the menu titles in the GNOME panel... unfortunately, the GNOME
+		 * panel creates their own custom widget to do this! */
+		gtk_widget_show(item);
+		gtk_menu_append(GTK_MENU(menu), item);
+
+		item = gtk_menu_item_new();
+		gtk_widget_show(item);
+		gtk_menu_append(GTK_MENU(menu), item);
+
+		l = l->next;
+		if (l == NULL) {
+			item = gtk_menu_item_new_with_label("(no suggestions)");
+			gtk_widget_show(item);
+			gtk_menu_append(GTK_MENU(menu), item);
+		} else {
+			GtkWidget *curmenu = menu;
+			int count = 0;
+			do {
+				if (l->data == NULL && l->next != NULL) {
+					count = 0;
+					curmenu = gtk_menu_new();
+					item = gtk_menu_item_new_with_label("Other Possibilities...");
+					gtk_widget_show(item);
+					gtk_menu_item_set_submenu(GTK_MENU_ITEM(item), curmenu);
+					gtk_menu_append(GTK_MENU(curmenu), item);
+					l = l->next;
+				} else if (count > MENUCOUNT) {
+					count -= MENUCOUNT;
+					item = gtk_menu_item_new_with_label("More...");
+					gtk_widget_show(item);
+					gtk_menu_append(GTK_MENU(curmenu), item);
+					curmenu = gtk_menu_new();
+					gtk_menu_item_set_submenu(GTK_MENU_ITEM(item), curmenu);
+				}
+				item = gtk_menu_item_new_with_label((char*)l->data);
+				gtk_signal_connect(GTK_OBJECT(item), "activate",
+						GTK_SIGNAL_FUNC(replace_word), gtktext);
+				gtk_widget_show(item);
+				gtk_menu_append(GTK_MENU(curmenu), item);
+				count++;
+			} while ((l = l->next) != NULL);
+		}
+	}
+	return GTK_MENU(menu);
+}
+
+static void popup_menu(GtkText *gtktext, GdkEventButton *eb) {
+	char buf[BUFSIZE];
+	GList *list, *l;
+
+	get_curword(gtktext, buf, NULL, NULL);
+
+	list = misspelled_suggest(buf);
+	if (list != NULL) {
+		gtk_menu_popup(make_menu(list, gtktext), NULL, NULL, NULL, NULL,
+				eb->button, eb->time);
+		for (l = list; l != NULL; l = l->next)
+			g_free(l->data);
+		g_list_free(list);
+	}
+}
+
+/* ok, this is pretty wacky:
+ * we need to let the right-mouse-click go through, so it moves the cursor, 
+ * but we *can't* let it go through, because GtkText interprets rightclicks as
+ * weird selection modifiers.
+ *
+ * so what do we do?  forge rightclicks as leftclicks, then popup the menu. 
+ * HACK HACK HACK. 
+ */
+static gint button_press_intercept_cb(GtkText *gtktext, GdkEvent *e, gpointer d) {
+	GdkEventButton *eb;
+	gboolean retval;
+
+	if (!gtkspell_running()) return FALSE;
+
+	if (e->type != GDK_BUTTON_PRESS) return FALSE;
+	eb = (GdkEventButton*) e;
+
+	if (eb->button != 3) return FALSE;
+
+	/* forge the leftclick */
+	eb->button = 1;
+
+	gtk_signal_handler_block_by_func(GTK_OBJECT(gtktext), 
+			GTK_SIGNAL_FUNC(button_press_intercept_cb), d);
+	gtk_signal_emit_by_name(GTK_OBJECT(gtktext), "button-press-event",
+			e, &retval);
+	gtk_signal_handler_unblock_by_func(GTK_OBJECT(gtktext), 
+			GTK_SIGNAL_FUNC(button_press_intercept_cb), d);
+	gtk_signal_emit_stop_by_name(GTK_OBJECT(gtktext), "button-press-event");
+
+	/* now do the menu wackiness */
+	popup_menu(gtktext, eb);
+	return TRUE;
+}
+
+void gtkspell_uncheck_all(GtkText *gtktext) {
+	int origpos;
+	char *text;
+	float adj_value;
+
+	adj_value = gtktext->vadj->value;
+	gtk_text_freeze(gtktext);
+	origpos = gtk_editable_get_position(GTK_EDITABLE(gtktext));
+	text = gtk_editable_get_chars(GTK_EDITABLE(gtktext), 0, -1);
+	gtk_text_set_point(gtktext, 0);
+	gtk_text_forward_delete(gtktext, gtk_text_get_length(gtktext));
+	gtk_text_insert(gtktext, NULL, NULL, NULL, text, strlen(text));
+	gtk_text_thaw(gtktext);
+
+	gtk_editable_set_position(GTK_EDITABLE(gtktext), origpos);
+	gtk_adjustment_set_value(gtktext->vadj, adj_value);
+}
+
+void gtkspell_attach(GtkText *gtktext) {
+	gtk_signal_connect(GTK_OBJECT(gtktext), "insert-text",
+		GTK_SIGNAL_FUNC(entry_insert_cb), NULL);
+	gtk_signal_connect_after(GTK_OBJECT(gtktext), "delete-text",
+		GTK_SIGNAL_FUNC(entry_delete_cb), NULL);
+	gtk_signal_connect(GTK_OBJECT(gtktext), "button-press-event",
+			GTK_SIGNAL_FUNC(button_press_intercept_cb), NULL);
+}
+
+void gtkspell_detach(GtkText *gtktext) {
+	gtk_signal_disconnect_by_func(GTK_OBJECT(gtktext),
+		GTK_SIGNAL_FUNC(entry_insert_cb), NULL);
+	gtk_signal_disconnect_by_func(GTK_OBJECT(gtktext),
+		GTK_SIGNAL_FUNC(entry_delete_cb), NULL);
+	gtk_signal_disconnect_by_func(GTK_OBJECT(gtktext), 
+			GTK_SIGNAL_FUNC(button_press_intercept_cb), NULL);
+
+	gtkspell_uncheck_all(gtktext);
+}
+
+static void sigchld(int param) {
+	if (gtkspell_running() &&
+		(waitpid(spell_pid, NULL, WNOHANG) == spell_pid)) {
+		spell_pid = 0;
+	} else {
+		/* a default SIGCHLD handler.
+		 * what else to do here? */
+		waitpid(-1, NULL, WNOHANG);
+	}
+}
+
+static void set_up_signal() {
+	struct sigaction sigact;
+	memset(&sigact, 0, sizeof(struct sigaction));
+
+	sigact.sa_handler = sigchld;
+	sigaction(SIGCHLD, &sigact, NULL);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/gtkspell.h	Tue Nov 21 09:50:31 2000 +0000
@@ -0,0 +1,105 @@
+/* gtkspell - a spell-checking addon for GtkText
+ * Copyright (c) 2000 Evan Martin.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ * 
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307  USA
+ */
+
+#ifndef __gtkspell_h__
+#define __gtkspell_h__
+
+/* PLEASE NOTE that this API is unstable and subject to change. */
+#define GTKSPELL_VERSION "0.3.2"
+
+static gchar *ispell_cmd[] = { "ispell", "-a", NULL };
+
+extern int gtkspell_start(char *path, char *args[]);
+/* Spawns the spell checking program.
+ *
+ * Arguments:
+ *  - "path" should be the full path to the spell checking program, or NULL
+ *    if you want to search the PATH for args[0].
+ *  - "args" should be a array of arguments to the spell checking program.
+ *    The first element should be the name of the program.
+ *    You should give the argument to run the spell checking program in the
+ *    "embedded" mode.  for ispell, this is "-a".
+ *    The last element should be NULL.
+ * Return:
+ *  0 on success, and -1 on error.
+ *
+ * Example:
+ *  char *args[] = { "ispell", "-a", NULL };
+ *  if (gtkspell_start(NULL, args) < 0) {
+ *  	fprintf(stderr, "Unable to start GtkSpell.\n");
+ *  	return -1;
+ *  }
+ *
+ */
+
+
+extern void gtkspell_stop();
+/* Stop the spellchecking program.
+ * This kills the spell checker's process and frees memory.
+ */
+
+extern int gtkspell_running();
+/* Is gtkspell running?
+ *
+ * Returns:
+ * 	nonzero if it running
+ * 	zero if is not running
+ *
+ * Example:
+ *  if (gtkspell_running())
+ *  	printf("gtkspell is running.\n");
+ */
+
+extern void gtkspell_attach(GtkText *text);
+/* Attach GtkSpell to a GtkText Widget.
+ * This enables checking as you type and the popup menu.
+ *
+ * Arguments:
+ *  - "text" is the widget to which GtkSpell should attach.
+ *
+ * Example:
+ *  GtkWidget *text;
+ *  text = gtk_text_new(NULL, NULL); 
+ *  gtk_text_set_editable(GTK_TEXT(text), TRUE);
+ *  gtkspell_attach(GTK_TEXT(text));
+ */  
+
+void gtkspell_detach(GtkText *gtktext);
+/* Detach GtkSpell from a GtkText widget.
+ * 
+ * Arguments:
+ *  - "text" is the widget from which GtkSpell should detach.
+ * 
+ */ 
+
+void gtkspell_check_all(GtkText *gtktext);
+/* Check and highlight the misspelled words.
+ * Note that the popup menu will not work unless you gtkspell_attach().
+ *
+ * Arguments:
+ *  - "text" is the widget to check.
+ */
+
+void gtkspell_uncheck_all(GtkText *gtktext);
+/* Remove all of the highlighting from the widget.
+ *
+ * Arguments:
+ *  - "text" is the widget to check.
+ */
+
+#endif /* __gtkspell_h__ */
--- a/src/prefs.c	Tue Nov 21 01:11:27 2000 +0000
+++ b/src/prefs.c	Tue Nov 21 09:50:31 2000 +0000
@@ -1587,6 +1587,9 @@
        	if ((int)option == OPT_GEN_LOG_ALL)
        		update_log_convs();
 
+	if ((int)option == OPT_GEN_CHECK_SPELLING)
+		toggle_spellchk();
+
 	save_prefs();
 }
 
--- a/src/util.c	Tue Nov 21 01:11:27 2000 +0000
+++ b/src/util.c	Tue Nov 21 09:50:31 2000 +0000
@@ -730,81 +730,6 @@
         return NULL;
 }
 
-/*  
-
-This function was taken from EveryBuddy and was written by
-Torrey Searle.  tsearle@valhalla.marko.net 
-
-http://www.everybuddy.com
-
-*/
-
-void spell_checker(GtkWidget * text)
-{
-        int start = 0;
-        int end = 0;
-        static GdkColor * color = NULL;
-        int ignore = 0;
-        int point = gtk_editable_get_position(GTK_EDITABLE(text));
-
-        GString * string;
-
-        if( color == NULL )
-        {
-                GdkColormap * gc = gtk_widget_get_colormap( text );
-                color = g_new0(GdkColor, 1);
-                color->red = 255 * 256;
-                gdk_colormap_alloc_color(gc, color, FALSE, TRUE);
-        }
-
-
-
-        end = point-1;
-        for( start = end-1; start >= 0; start-- )
-        {
-                if((isspace(GTK_TEXT_INDEX(GTK_TEXT(text), start)) || start == 0)
-                                && !ignore)
-                {
-                        char * word;
-                        FILE * file;
-                        char buff[1024];
-                        word = gtk_editable_get_chars( GTK_EDITABLE(text), start, end );
-                        g_snprintf(buff, 1024, "echo \"%s\" | ispell -l", word );
-                        file = popen(buff, "r");
-
-                        buff[0] = 0;
-                        fgets(buff, 255, file);
-
-                        if(strlen(buff) > 0 )
-                        {
-                                string = g_string_new(word);
-                                gtk_text_set_point(GTK_TEXT(text), end);
-                                gtk_text_backward_delete(GTK_TEXT(text), end-start);
-                                gtk_text_insert( GTK_TEXT(text), NULL, color, NULL,
-                                                                 string->str, string->len );
-                                g_string_free( string, TRUE );
-                        }
-                        else
-                        {
-                                string = g_string_new(word);
-                                gtk_text_set_point(GTK_TEXT(text), end);
-                                gtk_text_backward_delete(GTK_TEXT(text), end-start);
-                                gtk_text_insert( GTK_TEXT(text), NULL, &(text->style->fg[0]), NULL,
-                                                                 string->str, string->len );
-                                g_string_free( string, TRUE );
-                        }
-                        pclose( file);
-			g_free(word);
-                        break;
-                }
-                else if(!isalpha(GTK_TEXT_INDEX(GTK_TEXT(text), start)))
-                {
-                        ignore = 1;
-                }
-        }
-        gtk_text_set_point(GTK_TEXT(text), point);
-
-}
 
 /* Look for %n, %d, or %t in msg, and replace with the sender's name, date,
    or time */