view plugins/spellchk.c @ 8672:142108e89399

[gaim-migrate @ 9424] "This patch contains two fixes. The first is rather trivial, just removes the declaration of extra pointer used for deleting a message and deletes the message directly. The second is the real bug that needed fixing. Short Explanation: The old code only fixed the message that was being displayed, and still sent the original unchecked message to the receiving party. All I did was add a handler to the load_plugin function to make it fix the message being sent as well. Longer Explanation: In my cursory look through the gaim code (warning, <15 minutes) it appears the message pointer send to all the various *-im-msg and *-chat-msg handlers is a copy of the original string each time. This means if you fix the string at one stage (displaying), you have to fix it again at the next (sending). This seems rather inefficient, but hey, it's your client, maybe it was a design feature. Anyway, the spellchk plugin was only fixing the displayed message, and not the message being sent. So I thought my messages were being fixed while those receiving them new I was screwing up. I added substitute_somethingorother handler for the sending-im-msg callback in the load_plugin function. Other comments: I didn't look to deeply into it since it's so late, but the "displaying-chat-msg" and "sending-chat-msg" callbacks appear to be broken. The spellchk.c currently has a handler registered for the first callback, but the message displayed on my screen still contains the original typos. If I try to register a handler for the second callback listed, Gaim crashes when sending the message to a chat window. As I said, I didn't look into why it was crashing, I just removed my attempt to handle it and built this patch." --Matt Brenneke committer: Tailor Script <tailor@pidgin.im>
author Luke Schierer <lschiere@pidgin.im>
date Fri, 16 Apr 2004 14:39:34 +0000
parents 56360561af5e
children 5891a1a6fc7e
line wrap: on
line source

/*
 * A lot of this code (especially the config code) was taken directly
 * or nearly directly from xchat, version 1.4.2 by Peter Zelezny and others.
 */
#include "internal.h"

#include "debug.h"
#include "signals.h"
#include "util.h"

#include "gtkplugin.h"
#include "gtkutils.h"

#include <stdio.h>
#include <string.h>
#ifndef _WIN32
#include <strings.h>
#endif
#include <sys/types.h>
#include <sys/stat.h>

#define SPELLCHECK_PLUGIN_ID "gtk-spellcheck"

enum {
	BAD_COLUMN,
	GOOD_COLUMN,
	N_COLUMNS
};

static int num_words(const char *);
static int get_word(char *, int);
static char *have_word(char *, int);
static void substitute(char **, int, int, const char *);
static GtkListStore *model;

static gboolean
substitute_words(GaimAccount *account, GaimConversation *conv,
				 char **message, void *data)
{
	int i, l;
	int word;
	char *tmp;

	if (message == NULL || *message == NULL)
		return FALSE;

	l = num_words(*message);
	for (i = 0; i < l; i++) {
		GtkTreeIter iter;
		word = get_word(*message, i);
		if(gtk_tree_model_get_iter_first(GTK_TREE_MODEL(model), &iter)) {
			do {
				GValue val0, val1;
				const char *bad, *good;
				memset(&val0, 0, sizeof(val0));
				memset(&val1, 0, sizeof(val1));
				gtk_tree_model_get_value(GTK_TREE_MODEL(model), &iter, 0, &val0);
				gtk_tree_model_get_value(GTK_TREE_MODEL(model), &iter, 1, &val1);
				tmp = have_word(*message, word);
				bad = g_value_get_string(&val0);
				good = g_value_get_string(&val1);
				if (!strcmp(tmp, bad)) {
					substitute(message, word, strlen(bad),
							good);
					l += num_words(good) - num_words(bad);
					i += num_words(good) - num_words(bad);
				}
				free(tmp);
				g_value_unset(&val0);
				g_value_unset(&val1);
			} while(gtk_tree_model_iter_next(GTK_TREE_MODEL(model), &iter));
		}
	}

	return FALSE;
}

static int buf_get_line(char *ibuf, char **buf, int *position, int len) {
	int pos = *position, spos = pos;

	if (pos == len)
		return 0;

	while (ibuf[pos++] != '\n') {
		if (pos == len)
			return 0;
	}
	pos--;
	ibuf[pos] = 0;
	*buf = &ibuf[spos];
	pos++;
	*position = pos;
	return 1;
}

static void load_conf() {
	const char * const defaultconf = "BAD r\nGOOD are\n\n"
			"BAD u\nGOOD you\n\n"
			"BAD teh\nGOOD the\n\n";
	gchar *buf, *ibuf;
	char name[82];
	char cmd[256];
	int pnt = 0;
	gsize size;

	model = gtk_list_store_new((gint)N_COLUMNS, G_TYPE_STRING, G_TYPE_STRING);

	buf = g_build_filename(gaim_user_dir(), "dict", NULL);
	g_file_get_contents(buf, &ibuf, &size, NULL);
	g_free(buf);
	if(!ibuf) {
		ibuf = g_strdup(defaultconf);
		size = strlen(defaultconf);
	}

	cmd[0] = 0;
	name[0] = 0;

	while(buf_get_line(ibuf, &buf, &pnt, size)) {
		if (*buf != '#') {
			if (!strncasecmp(buf, "BAD ", 4))
				strncpy(name, buf + 4, 81);
			if (!strncasecmp(buf, "GOOD ", 5)) {
				strncpy(cmd, buf + 5, 255);
				if (*name) {
					GtkTreeIter iter;
					gtk_list_store_append(model, &iter);
					gtk_list_store_set(model, &iter,
						0, g_strdup(name),
						1, g_strdup(cmd),
						-1);
				}
			}
		}
	}
	g_free(ibuf);
}



static int num_words(const char *m) {
	int count = 0;
	guint pos;
	int state = 0;

	for (pos = 0; pos < strlen(m); pos++) {
		switch (state) {
		case 0: /* expecting word */
			if (!g_ascii_isspace(m[pos]) && !g_ascii_ispunct(m[pos])) {
				count++;
				state = 1;
			} else if (m[pos] == '<')
				state = 2;
			break;
		case 1: /* inside word */
			if (m[pos] == '<')
				state = 2;
			else if (g_ascii_isspace(m[pos]) || g_ascii_ispunct(m[pos]))
				state = 0;
			break;
		case 2: /* inside HTML tag */
			if (m[pos] == '>')
				state = 0;
			break;
		}
	}
	return count;
}

static int get_word(char *m, int word) {
	int count = 0;
	guint pos = 0;
	int state = 0;

	for (pos = 0; pos < strlen(m) && count <= word; pos++) {
		switch (state) {
		case 0:
			if (!g_ascii_isspace(m[pos]) && !g_ascii_ispunct(m[pos])) {
				count++;
				state = 1;
			} else if (m[pos] == '<')
				state = 2;
			break;
		case 1:
			if (m[pos] == '<')
				state = 2;
			else if (g_ascii_isspace(m[pos]) || g_ascii_ispunct(m[pos]))
				state = 0;
			break;
		case 2:
			if (m[pos] == '>')
				state = 0;
			break;
		}
	}
	return pos - 1;
}

static char *have_word(char *m, int pos) {
	char *tmp = strpbrk(&m[pos], "' \t\f\r\n\"><.?!-,/");
	int len = (int)(tmp - &m[pos]);

	if (tmp == NULL) {
		tmp = strdup(&m[pos]);
		return tmp;
	}

	tmp = malloc(len + 1);
	tmp[0] = 0;
	strncat(tmp, &m[pos], len);

	return tmp;
}

static void substitute(char **mes, int pos, int m, const char *text) {
	char *new = g_malloc(strlen(*mes) + strlen(text) + 1);
	new[0] = 0;

	strncat(new, *mes, pos);
	strcat(new, text);

	strcat(new, &(*mes)[pos + m]);
	g_free(*mes);
	*mes = new;
}

static GtkWidget *tree;
static GtkWidget *bad_entry;
static GtkWidget *good_entry;

static void save_list();

static void on_edited(GtkCellRendererText *cellrenderertext,
					  gchar *path, gchar *arg2, gpointer data)
{
	GtkTreeIter iter;
	GValue val;
	if(arg2[0] == '\0') {
		gdk_beep();
		return;
	}
	memset(&val, 0, sizeof(val));
	g_return_if_fail(gtk_tree_model_get_iter_from_string(GTK_TREE_MODEL(model), &iter, path));
	gtk_tree_model_get_value(GTK_TREE_MODEL(model), &iter, GPOINTER_TO_INT(data), &val);
	if(strcmp(arg2, g_value_get_string(&val))) {
		gtk_list_store_set(model, &iter, GPOINTER_TO_INT(data), arg2, -1);
		save_list();
	}
	g_value_unset(&val);
}

static void list_add_new()
{
	GtkTreeIter iter;

	gtk_list_store_append(model, &iter);
	gtk_list_store_set(model, &iter,
		0, gtk_entry_get_text(GTK_ENTRY(bad_entry)),
		1, gtk_entry_get_text(GTK_ENTRY(good_entry)),
		-1);
	gtk_editable_select_region(GTK_EDITABLE(bad_entry), 0, -1);
	gtk_widget_grab_focus(bad_entry);

	save_list();
}

static void add_selected_row_to_list(GtkTreeModel *model, GtkTreePath *path,
	GtkTreeIter *iter, gpointer data)
{
	GSList **list = (GSList **)data;
	*list = g_slist_append(*list, gtk_tree_path_copy(path) );
}


static void remove_row(void *data1, gpointer data2)
{
	GtkTreePath *path = (GtkTreePath*)data1;
	GtkTreeIter iter;
	gtk_tree_model_get_iter(GTK_TREE_MODEL(model), &iter, path);
	gtk_list_store_remove(model, &iter);
	gtk_tree_path_free(path);
}

static void list_delete()
{
	GtkTreeSelection *sel;
	GSList *list = NULL;

	sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(tree));
	gtk_tree_selection_selected_foreach(sel, add_selected_row_to_list, &list);

	g_slist_foreach(list, remove_row, NULL);
	g_slist_free(list);

	save_list();
}

static void save_list()
{
	FILE *f;
	char *name;
	GtkTreeIter iter;
	char tempfilename[BUF_LONG];
	int fd;

	name = g_build_filename(gaim_user_dir(), "dict", NULL);
	strcpy(tempfilename, name);
	strcat(tempfilename,".XXXXXX");
	fd = g_mkstemp(tempfilename);
	if(fd<0) {
		perror(tempfilename);
		g_free(name);
		return;
	}
	if (!(f = fdopen(fd, "w"))) {
		perror("fdopen");
		close(fd);
		g_free(name);
		return;
	}

	fchmod(fd, S_IRUSR | S_IWUSR);

	if(gtk_tree_model_get_iter_first(GTK_TREE_MODEL(model), &iter)) {
		do {
			GValue val0, val1;
			memset(&val0, 0, sizeof(val0));
			memset(&val1, 0, sizeof(val1));
			gtk_tree_model_get_value(GTK_TREE_MODEL(model), &iter, 0, &val0);
			gtk_tree_model_get_value(GTK_TREE_MODEL(model), &iter, 1, &val1);
			fprintf(f, "BAD %s\nGOOD %s\n\n", g_value_get_string(&val0), g_value_get_string(&val1));
			g_value_unset(&val0);
			g_value_unset(&val1);
		} while(gtk_tree_model_iter_next(GTK_TREE_MODEL(model), &iter));
	}
	if(fclose(f)) {
		gaim_debug(GAIM_DEBUG_ERROR, "spellchk",
				   "Error writing to %s: %m\n", tempfilename);
		unlink(tempfilename);
		g_free(name);
		return;
	}
	rename(tempfilename, name);
	g_free(name);
}

static void
check_if_something_is_selected(GtkTreeModel *model,
	GtkTreePath *path, GtkTreeIter *iter, gpointer data)
{
	*((gboolean*)data) = TRUE;
}

static void on_selection_changed(GtkTreeSelection *sel,
	gpointer data)
{
	gboolean is = FALSE;
	gtk_tree_selection_selected_foreach(sel, check_if_something_is_selected, &is);
	gtk_widget_set_sensitive((GtkWidget*)data, is);
}

static gboolean non_empty(const char *s)
{
	while(*s && g_ascii_isspace(*s))
		s++;
	return *s;
}

static void on_entry_changed(GtkEditable *editable, gpointer data)
{
	gtk_widget_set_sensitive((GtkWidget*)data,
		non_empty(gtk_entry_get_text(GTK_ENTRY(bad_entry))) &&
		non_empty(gtk_entry_get_text(GTK_ENTRY(good_entry))));
}

/*
 *  EXPORTED FUNCTIONS
 */

static gboolean
plugin_load(GaimPlugin *plugin)
{
	void *conv_handle = gaim_conversations_get_handle();

	load_conf();

	gaim_signal_connect(conv_handle, "writing-im-msg",
						plugin, GAIM_CALLBACK(substitute_words), NULL);
	gaim_signal_connect(conv_handle, "writing-chat-msg",
						plugin, GAIM_CALLBACK(substitute_words), NULL);
	gaim_signal_connect(conv_handle, "sending-im-msg",
						plugin, GAIM_CALLBACK(substitute_words), NULL);

	return TRUE;
}

static GtkWidget *
get_config_frame(GaimPlugin *plugin)
{
	GtkWidget *ret, *vbox, *win;
	GtkWidget *hbox, *label;
	GtkWidget *button;
	GtkSizeGroup *sg;
	GtkCellRenderer *renderer;
	GtkTreeViewColumn *column;

	sg = gtk_size_group_new(GTK_SIZE_GROUP_HORIZONTAL);

	ret = gtk_vbox_new(FALSE, 18);
	gtk_container_set_border_width (GTK_CONTAINER (ret), 12);

	vbox = gaim_gtk_make_frame(ret, _("Text Replacements"));
	gtk_container_set_border_width(GTK_CONTAINER(vbox), 4);
	gtk_widget_set_size_request(vbox, 300, -1);
	gtk_widget_show (vbox);

	win = gtk_scrolled_window_new(0, 0);
	gtk_container_add(GTK_CONTAINER(vbox), win);
	gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(win),
										GTK_SHADOW_IN);
	gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW (win),
			GTK_POLICY_AUTOMATIC,
			GTK_POLICY_AUTOMATIC);
	gtk_widget_show(win);

	tree = gtk_tree_view_new_with_model(GTK_TREE_MODEL(model));
	/* gtk_tree_view_set_rules_hint(GTK_TREE_VIEW(tree), TRUE); */
	gtk_widget_set_size_request(tree, 260,200);

	renderer = gtk_cell_renderer_text_new ();
	g_object_set (G_OBJECT (renderer),
		"editable", TRUE,
		NULL);
	g_signal_connect(G_OBJECT(renderer), "edited",
			   G_CALLBACK(on_edited), GINT_TO_POINTER(0));
	column = gtk_tree_view_column_new_with_attributes (_("You type"),
		renderer, "text", BAD_COLUMN, NULL);
	gtk_tree_view_column_set_sizing(column, GTK_TREE_VIEW_COLUMN_FIXED);
	gtk_tree_view_column_set_fixed_width(column, 130);
	/* gtk_tree_view_column_set_resizable(column, TRUE); */
	gtk_tree_view_append_column (GTK_TREE_VIEW (tree), column);
	renderer = gtk_cell_renderer_text_new ();
	g_object_set (G_OBJECT (renderer),
		"editable", TRUE,
		NULL);
	g_signal_connect(G_OBJECT(renderer), "edited",
			   G_CALLBACK(on_edited), GINT_TO_POINTER(1));
	column = gtk_tree_view_column_new_with_attributes (_("You send"),
		renderer, "text", GOOD_COLUMN, NULL);
	gtk_tree_view_column_set_sizing(column, GTK_TREE_VIEW_COLUMN_FIXED);
	gtk_tree_view_column_set_fixed_width(column, 130);
	/* gtk_tree_view_column_set_resizable(column, TRUE); */
	gtk_tree_view_append_column (GTK_TREE_VIEW (tree), column);
	gtk_tree_selection_set_mode(gtk_tree_view_get_selection(GTK_TREE_VIEW(tree)),
		 GTK_SELECTION_MULTIPLE);
	gtk_container_add(GTK_CONTAINER(win), tree);
	gtk_widget_show(tree);

	hbox = gtk_hbutton_box_new();
	gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0);
	gtk_widget_show(hbox);

	button = gtk_button_new_from_stock(GTK_STOCK_DELETE);
	g_signal_connect(G_OBJECT(button), "clicked",
			   G_CALLBACK(list_delete), NULL);
	gtk_box_pack_start(GTK_BOX(hbox), button, FALSE, FALSE, 0);
	gtk_widget_set_sensitive(button, FALSE);

	g_signal_connect(G_OBJECT(gtk_tree_view_get_selection(GTK_TREE_VIEW(tree))),
		"changed", G_CALLBACK(on_selection_changed), button);

	gtk_widget_show(button);

	vbox = gaim_gtk_make_frame(ret, _("Add a new text replacement"));
	gtk_widget_set_size_request(vbox, 300, -1);

	hbox = gtk_hbox_new(FALSE, 2);
	gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0);
	gtk_widget_show(hbox);

	label = gtk_label_new_with_mnemonic(_("You _type:"));
	gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 0);

	bad_entry = gtk_entry_new();
	gtk_entry_set_max_length(GTK_ENTRY(bad_entry), 40);
	gtk_box_pack_start(GTK_BOX(hbox), bad_entry, TRUE, TRUE, 0);
	gtk_size_group_add_widget(sg, bad_entry);
	gtk_label_set_mnemonic_widget(GTK_LABEL(label), bad_entry);
	gtk_widget_show(bad_entry);

	hbox = gtk_hbox_new(FALSE, 2);
	gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0);
	gtk_widget_show(hbox);

	label = gtk_label_new_with_mnemonic(_("You _send:"));
	gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 0);

	good_entry = gtk_entry_new();
	gtk_entry_set_max_length(GTK_ENTRY(good_entry), 255);
	gtk_box_pack_start(GTK_BOX(hbox), good_entry, TRUE, TRUE, 0);
	gtk_size_group_add_widget(sg, good_entry);
	gtk_label_set_mnemonic_widget(GTK_LABEL(label), good_entry);
	gtk_widget_show(good_entry);

	hbox = gtk_hbutton_box_new();
	gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0);
	button = gtk_button_new_from_stock(GTK_STOCK_ADD);
	g_signal_connect(G_OBJECT(button), "clicked",
			   G_CALLBACK(list_add_new), NULL);
	gtk_box_pack_start(GTK_BOX(hbox), button, FALSE, FALSE, 0);
	g_signal_connect(G_OBJECT(bad_entry), "changed", G_CALLBACK(on_entry_changed), button);
	g_signal_connect(G_OBJECT(good_entry), "changed", G_CALLBACK(on_entry_changed), button);
	gtk_widget_set_sensitive(button, FALSE);
	gtk_widget_show(button);


	gtk_widget_show_all(ret);
	return ret;
}

static GaimGtkPluginUiInfo ui_info =
{
	get_config_frame
};

static GaimPluginInfo info =
{
	2,
	GAIM_PLUGIN_STANDARD,
	GAIM_GTK_PLUGIN_TYPE,
	0,
	NULL,
	GAIM_PRIORITY_DEFAULT,
	SPELLCHECK_PLUGIN_ID,
	N_("Text replacement"),
	VERSION,
	N_("Replaces text in outgoing messages according to user-defined rules."),
	N_("Replaces text in outgoing messages according to user-defined rules."),
	"Eric Warmenhoven <eric@warmenhoven.org>",
	GAIM_WEBSITE,
	plugin_load,
	NULL,
	NULL,
	&ui_info,
	NULL
};

static void
init_plugin(GaimPlugin *plugin)
{
}

GAIM_INIT_PLUGIN(spellcheck, init_plugin, info)