view talkfilters/talkfilters.c @ 984:5af0906311d6 org.guifications.plugins

Added support for 'or' dependencies
author grim@guifications.org
date Wed, 10 Dec 2008 04:08:40 -0500
parents 6a2cc270822f
children
line wrap: on
line source

/*
 * A neat little Purple plugin to integrate with GNU Talk Filters.
 * http://www.hyperrealm.com/talkfilters/talkfilters.html
 *
 * Mark Lindner <markl@gnu.org> 1/6/04
 * Updates for the purple plugin pack (C) 2005 by
 *    Peter Lawler <bleeter from users.sf.net>
 */

/* TODO:
-- slash commands (allowing it to be a one liner)
-- allow saving different filters for different buddies (or accounts)
*/

/* If you can't figure out what this line is for, DON'T TOUCH IT. */
#include "../common/pp_internal.h"

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


#include <debug.h>
#include <signals.h>
#include <util.h>

#include <gtkplugin.h>
#include <gtkutils.h>

#include <talkfilters.h>

#define	PREF_PREFIX     "/plugins/gtk/bleeter/talkfilters"
#define	PREF_ENABLED    PREF_PREFIX "/enabled"
#define	PREF_FILTER     PREF_PREFIX "/filter"

#define PROP_FILTER     "talkfilter::filter"

static const gtf_filter_t *current_filter = NULL;
static const gtf_filter_t *filter_list = NULL;
static int filter_count = 0;

static void
translate_message(char **message, const gtf_filter_t *filter) {
	if (message == NULL || *message == NULL) {
		purple_debug_info("talkfilters","Null message\n");
		return;
	}

	if(filter != NULL) {
		gchar *tmp;

		size_t len = strlen(*message);
		if(len < 40)
			len += 40;
		else
			len *= 2;

		/* XXX: Is it always true, or are we just hoping it is? */
		tmp = (gchar *)g_malloc(len);

		filter->filter(*message, tmp, len);
		g_free(*message);
		*message = tmp;
	} else {
		purple_debug_info("talkfilters","No filter set\n");
	}
}

static void translate_message_im(PurpleAccount *account, char *who,
								  char **message, gpointer dontcare) {
	PurpleConversation *conv;
	PidginConversation *gtkconv;

	conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, who, account);
	if (!conv)
		return;
	gtkconv = PIDGIN_CONVERSATION(conv);
	if (!gtkconv)
		return;

	translate_message(message, g_object_get_data(G_OBJECT(gtkconv->imhtml), PROP_FILTER));
}

static void translate_message_chat(PurpleAccount *account, char **message,
									int id, gpointer dontcare) {
	PurpleConversation *conv;
	PidginConversation *gtkconv;
   
	conv = purple_find_chat(account->gc, id);
	if (!conv)
		return;

	gtkconv = PIDGIN_CONVERSATION(conv);
	if (!gtkconv)
		return;

	translate_message(message, g_object_get_data(G_OBJECT(gtkconv->imhtml), PROP_FILTER));
}

static void
update_selected_filter() {
	const gtf_filter_t *filter;
	gint ct;
	const char *val = purple_prefs_get_string(PREF_FILTER);

	current_filter = NULL;
	ct = filter_count;
	for(filter = filter_list; ct; filter++, ct--) {
		/* XXX: Is this overkill? Is strcmp enough? */
		if (g_utf8_collate(val, filter->name) == 0) {
			current_filter = filter;
			purple_debug_info("talkfilters", "found default filter \"%s\"\n", filter->name);
			break;
		}
	}
}

static void
filter_changed_cb(const char *name, PurplePrefType type, gconstpointer val, gpointer dontcare) {
	update_selected_filter();
}

static gboolean writing_im_msg(PurpleAccount *account, const char *who, char **message,
				PurpleConversation *conv, PurpleMessageFlags flags, gpointer dontcare) {
	if (flags & PURPLE_MESSAGE_SEND)
	{
		PidginConversation *gtkconv = PIDGIN_CONVERSATION(conv);
		if (!gtkconv)
			return FALSE;

		translate_message(message, g_object_get_data(G_OBJECT(gtkconv->imhtml), PROP_FILTER));
	}
	return FALSE;
}

static void
menu_filter_changed_cb(GtkWidget *w, PidginWindow *win)
{
	if (gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(w)))
	{
		PidginConversation *gtkconv = pidgin_conv_window_get_active_gtkconv(win);
		g_object_set_data(G_OBJECT(gtkconv->imhtml), PROP_FILTER,
				g_object_get_data(G_OBJECT(w), PROP_FILTER));
	}
}

static void
regenerate_talkfilter_menu(PidginConversation *gtkconv)
{
	PidginWindow *win;
	GtkWidget *menu;
	int count;
	const gtf_filter_t *filter;
	const gtf_filter_t *curfilter;
	GtkWidget *mitem, *item;
	GSList *list = NULL;

	if (gtkconv == NULL)
		return;

	win = pidgin_conv_get_window(gtkconv);
	if (win == NULL)
		return;

	mitem = g_object_get_data(G_OBJECT(win->window), PROP_FILTER);
	if (mitem == NULL)
	{
		mitem = gtk_menu_item_new_with_mnemonic(_("_Talkfilters"));	/* XXX: or is it "Talk Filters"? */
		gtk_menu_shell_insert(GTK_MENU_SHELL(win->menu.menubar), mitem, 3);
		g_object_set_data(G_OBJECT(win->window), PROP_FILTER, mitem);
		gtk_widget_show(mitem);
	}
	else
		return;

	menu = gtk_menu_new();
	gtk_menu_item_set_submenu(GTK_MENU_ITEM(mitem), menu);

	curfilter = g_object_get_data(G_OBJECT(gtkconv->imhtml), PROP_FILTER);

	item = gtk_radio_menu_item_new_with_label(list, _("(None)"));
	list = gtk_radio_menu_item_get_group(GTK_RADIO_MENU_ITEM(item));
	gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
	g_signal_connect(G_OBJECT(item), "activate", G_CALLBACK(menu_filter_changed_cb), win);

	for (count = filter_count, filter = filter_list; count; filter++, count--)
	{
		item = gtk_radio_menu_item_new_with_label(list, filter->desc);
		g_object_set_data(G_OBJECT(item), PROP_FILTER, (gpointer)filter);
		list = gtk_radio_menu_item_get_group(GTK_RADIO_MENU_ITEM(item));

		g_signal_connect(G_OBJECT(item), "activate", G_CALLBACK(menu_filter_changed_cb), win);

		gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
	}

	gtk_widget_show_all(menu);
}

static void
update_talkfilter_selection(PidginConversation *gtkconv)
{
	PidginWindow *win;
	GtkWidget *menu;
	GList *item;
	const gtf_filter_t *filter;

	if (gtkconv == NULL)
		return;

	win = pidgin_conv_get_window(gtkconv);
	if (win == NULL)
		return;

	menu = g_object_get_data(G_OBJECT(win->window), PROP_FILTER);
	if (menu == NULL)
		return;
	menu = gtk_menu_item_get_submenu(GTK_MENU_ITEM(menu));

	filter = g_object_get_data(G_OBJECT(gtkconv->imhtml), PROP_FILTER);

	for (item = gtk_container_get_children(GTK_CONTAINER(menu));
				item; item = item->next)
	{
		if (filter == g_object_get_data(G_OBJECT(item->data), PROP_FILTER))
		{
			gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(item->data), TRUE);
			break;
		}		
	}
}

static void
conversation_switched_cb(PurpleConversation *conv)
{
	PidginConversation *gtkconv = PIDGIN_CONVERSATION(conv);
	regenerate_talkfilter_menu(gtkconv);
	update_talkfilter_selection(gtkconv);
}
		
static void
conversation_created_cb(PurpleConversation *conv)
{
	PidginConversation *gtkconv = PIDGIN_CONVERSATION(conv);

	/* hopefully fix a crash related to persistent convs */
	if(gtkconv == NULL)
		return;

	g_object_set_data(G_OBJECT(gtkconv->imhtml), PROP_FILTER, (gpointer)current_filter);
	update_talkfilter_selection(gtkconv);
}

static void attach_talkfilter_menu(gpointer data, gpointer dontcare)
{
	PidginWindow *win = data;
	PidginConversation *gtkconv;

	gtkconv = pidgin_conv_window_get_active_gtkconv(win);
	regenerate_talkfilter_menu(gtkconv);
	update_talkfilter_selection(gtkconv);
}

static gboolean plugin_load(PurplePlugin *plugin)
{
	void *conv_handle = purple_conversations_get_handle();

	filter_list = gtf_filter_list();
	filter_count = gtf_filter_count();
	update_selected_filter();

	purple_signal_connect(conv_handle, "sending-im-msg",
						plugin, PURPLE_CALLBACK(translate_message_im), NULL);
	purple_signal_connect(conv_handle, "sending-chat-msg",
						plugin, PURPLE_CALLBACK(translate_message_chat), NULL);

	/* XXX: This is necessary because the changed message isn't displayed locally.
	 * 		This doesn't always show the exact filtered message that is sent, but
	 * 		I guess it's better than no indication that the message was filtered.
	 * 			-- sadrul
	 */
	purple_signal_connect(conv_handle, "writing-im-msg", plugin,
						PURPLE_CALLBACK(writing_im_msg), NULL);
	
	purple_prefs_connect_callback(plugin, PREF_FILTER,
							filter_changed_cb, NULL);

	/* Add a `Talkfilters' menu in the conversation window */
	purple_signal_connect(conv_handle, "conversation-created", plugin,
						PURPLE_CALLBACK(conversation_created_cb), NULL);
	purple_signal_connect(pidgin_conversations_get_handle(), "conversation-switched",
						plugin, PURPLE_CALLBACK(conversation_switched_cb), NULL);

	g_list_foreach(pidgin_conv_windows_get_list(), attach_talkfilter_menu, NULL);

	return TRUE;
}

static void remove_talkfilter_menu(gpointer data, gpointer dontcare)
{
	PidginWindow *win = data;
	GtkWidget *menu;

	menu = g_object_get_data(G_OBJECT(win->window), PROP_FILTER);
	if (menu)
	{
		gtk_widget_destroy(menu);
		g_object_set_data(G_OBJECT(win->window), PROP_FILTER, NULL);
		
		/* XXX: Do we need to set PROP_FILTER data to NULL for each gtkconv->imhtml as well?
		 * It doesn't seem to be necessary right now. The GTF library probably gets loaded
		 * at the very beginning when Purple starts, and not when this plugin is loaded. */
	}
}

static gboolean plugin_unload(PurplePlugin *plugin)
{
	purple_prefs_disconnect_by_handle(plugin);

	g_list_foreach(pidgin_conv_windows_get_list(), remove_talkfilter_menu, NULL);

	return TRUE;
}

static PurplePluginPrefFrame *
get_plugin_pref_frame(PurplePlugin *plugin)
{
	PurplePluginPrefFrame *frame;
	PurplePluginPref *pref;
	const gtf_filter_t *filter;
	gint ct;

	frame = purple_plugin_pref_frame_new();

	pref = purple_plugin_pref_new_with_label(_("Talk Filters"));
	purple_plugin_pref_frame_add(frame, pref);

	pref = purple_plugin_pref_new_with_name_and_label(PREF_FILTER, _("Active filter:"));
	purple_plugin_pref_set_type(pref, PURPLE_PLUGIN_PREF_CHOICE);

	purple_plugin_pref_add_choice(pref, _("(None)"), "");
	ct = filter_count;
	for(filter = filter_list; ct; filter++, ct--)
	{
		purple_plugin_pref_add_choice(pref, filter->desc, (gpointer)filter->name);
	}

	purple_plugin_pref_frame_add(frame, pref);

	return frame;
}

static PurplePluginUiInfo prefs_info = {
	get_plugin_pref_frame,
	0,
	NULL,
	NULL,
	NULL,
	NULL,
	NULL
};

static PurplePluginInfo talkfilters_info = {
	PURPLE_PLUGIN_MAGIC,
	PURPLE_MAJOR_VERSION,
	PURPLE_MINOR_VERSION,
	PURPLE_PLUGIN_STANDARD,								/* type				*/
	PIDGIN_PLUGIN_TYPE,								/* ui requirement	*/
	0,													/* flags			*/
	NULL,												/* dependencies		*/
	PURPLE_PRIORITY_DEFAULT,								/* priority			*/
	"gtk-plugin_pack-talkfilters",						/* id				*/
	NULL,												/* name				*/
	PP_VERSION,
	NULL,												/* summary			*/
	NULL,												/* description		*/
	"Mark Lindner <markl@gnu.org>, "
	"Peter Lawler <bleeter@users.sf.net>",
	PP_WEBSITE,
	plugin_load,
	plugin_unload,
	NULL,

	NULL,
	NULL,
	&prefs_info,
	NULL,
	NULL,
	NULL,
	NULL,
	NULL
};

static void init_plugin(PurplePlugin *plugin) {
	purple_prefs_add_none("/plugins/gtk/bleeter");
	purple_prefs_add_none("/plugins/gtk/bleeter/talkfilters");
	purple_prefs_add_bool("/plugins/gtk/bleeter/talkfilters/enabled", FALSE);
	purple_prefs_add_string("/plugins/gtk/bleeter/talkfilters/filter", "");
	talkfilters_info.name = _("GNU Talk Filters");
	talkfilters_info.summary = 
		_("Translates text in outgoing messages into humorous dialects.");
	talkfilters_info.description = 
		_("The GNU Talk Filters are filter programs that convert ordinary "
		  "English text into text that mimics a stereotyped or otherwise "
		  "humorous dialect. These filters have been in the public domain for "
		  "many years, and have been made available as a single integrated "
		  "package. The filters include austro, b1ff, brooklyn, chef, cockney, "
		  "drawl, dubya, fudd, funetak, jethro, jive, kraut, pansy, pirate, "
		  "postmodern, redneck, valspeak, and warez.");
}

PURPLE_INIT_PLUGIN(talkfilters, init_plugin, talkfilters_info)