view plugins/spellchk.c @ 6686:0b286eace44c

[gaim-migrate @ 7212] Tim Ringenbach (marv_sf) writes: " Changed an isprint to a g_ascii_isprint in disabled debug code, this used to cause a crash back when it was enabled (invalid utf8). Made it only show buddies away when they really are (instead of thinking they're away just because they have a status message). Made it notice when people log on using the java chat client. Made it show how long idle buddies have been idle, and also notice that idle buddies with status messages are in fact idle. Eliminated duplicate code in yahoo_process_contact (which fixes some bugs, such as not showing the buddies status message when you just added them), and also made it show a message when a buddy denied your add (basicly deleting themselves from your server-side buddy list). We still don't do anything about this, however (should probably delete the buddy, or show him as offline or unathorized or something)." sean rocks. just in case you weren't sure, now you know. committer: Tailor Script <tailor@pidgin.im>
author Luke Schierer <lschiere@pidgin.im>
date Tue, 02 Sep 2003 02:08:52 +0000
parents 70d5122bc3ff
children b0913ab92893
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"

#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 void
substitute_words(GaimAccount *account, GaimConversation *conv,
				 char **message, void *data)
{
	int i, l;
	int word;
	char *tmp;

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

	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));
		}
	}
}

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";
	char *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_home_dir(), ".gaim", "dict", NULL);
	g_file_get_contents(buf, &ibuf, &size, NULL);
	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);
				}
			}
		}
	}
	free(ibuf);
}



static int num_words(const char *m) {
	int count = 0;
	int 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;
	int 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);
	char *tmp;
	new[0] = 0;

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

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

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_home_dir(), ".gaim", "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, "sending-im-msg",
						plugin, GAIM_CALLBACK(substitute_words), NULL);
	gaim_signal_connect(conv_handle, "sending-chat-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)