changeset 5032:cb700c07ee07

[gaim-migrate @ 5375] Rewrote the buddy pounce code. It's now core/UI split, and may allow for more advanced stuff later. Pounce actions are now a UI thing, and the backend logic for registering, unregistering, and activating pouncs is now in core. Also, the buddy pounce dialog was redesigned. Oh, and there are new pounce types. You can now choose from: * Sign on * Sign off * Away * Return from away * Idle * Return from idle * Buddy starts typing * Buddy stops typing Should work. I've been using it for some time. If you find a bug, though, let me know. committer: Tailor Script <tailor@pidgin.im>
author Christian Hammond <chipx86@chipx86.com>
date Sat, 05 Apr 2003 10:14:21 +0000
parents bc494c4a3991
children 14bbf22917d9
files ChangeLog src/Makefile.am src/buddy.c src/core.h src/gaim.h src/gaimrc.c src/gtklist.h src/gtkpounce.c src/gtkpounce.h src/main.c src/pounce.c src/pounce.h src/server.c src/ui.h
diffstat 14 files changed, 1426 insertions(+), 630 deletions(-) [+]
line wrap: on
line diff
--- a/ChangeLog	Sat Apr 05 08:03:10 2003 +0000
+++ b/ChangeLog	Sat Apr 05 10:14:21 2003 +0000
@@ -1,5 +1,8 @@
 Gaim: The Pimpin' Penguin IM Clone that's good for the soul!
 
+version 0.61:
+	* Split the buddy pounce core and UI, and rewrote the UI for it.
+
 version 0.60 (04/04/2003): 
 	Core:
 	* Auto-loading protocol plugins.
--- a/src/Makefile.am	Sat Apr 05 08:03:10 2003 +0000
+++ b/src/Makefile.am	Sat Apr 05 10:14:21 2003 +0000
@@ -34,6 +34,8 @@
 		gtkimhtml.c \
 		gtkimhtml.h \
 		gtklist.h \
+		gtkpounce.c \
+		gtkpounce.h \
 		gtkutils.c \
 		gtkutils.h \
 		html.c \
--- a/src/buddy.c	Sat Apr 05 08:03:10 2003 +0000
+++ b/src/buddy.c	Sat Apr 05 10:14:21 2003 +0000
@@ -149,8 +149,7 @@
 
 static void gtk_blist_menu_bp_cb(GtkWidget *w, struct buddy *b)
 {
-       show_new_bp(b->name, b->account->gc, b->idle,
-                               b->uc & UC_UNAVAILABLE, NULL);
+	gaim_gtkpounce_dialog_show(b, NULL);
 }
 
 static void gtk_blist_menu_showlog_cb(GtkWidget *w, struct buddy *b)
@@ -1167,8 +1166,8 @@
 	awaymenu = gtk_item_factory_get_widget(ift, N_("/Tools/Away"));
 	do_away_menu();
 
-	bpmenu = gtk_item_factory_get_widget(ift, N_("/Tools/Buddy Pounce"));
-	do_bp_menu();
+	gtkblist->bpmenu = gtk_item_factory_get_widget(ift, N_("/Tools/Buddy Pounce"));
+	gaim_gtkpounce_menu_build(gtkblist->bpmenu);
 
 	protomenu = gtk_item_factory_get_widget(ift, N_("/Tools/Protocol Actions"));
 	do_proto_menu();
@@ -1597,7 +1596,6 @@
 			}
 		}
 
-
 		gtk_tree_store_set(gtkblist->treemodel, &iter,
 				   STATUS_ICON_COLUMN, status,
 				   NAME_COLUMN, mark,
@@ -1658,7 +1656,6 @@
 	gtkblist->bbox = gtkblist->tipwindow = NULL;
 	protomenu = NULL;
 	awaymenu = NULL;
-	bpmenu = NULL;
 }
 
 static void gaim_gtk_blist_set_visible(struct gaim_buddy_list *list, gboolean show)
--- a/src/core.h	Sat Apr 05 08:03:10 2003 +0000
+++ b/src/core.h	Sat Apr 05 10:14:21 2003 +0000
@@ -179,6 +179,7 @@
 
 /* Functions in gaimrc.c */
 extern void load_prefs();
+extern void load_pounces();
 extern void save_prefs();
 
 /* Functions in perl.c */
--- a/src/gaim.h	Sat Apr 05 08:03:10 2003 +0000
+++ b/src/gaim.h	Sat Apr 05 10:14:21 2003 +0000
@@ -196,7 +196,6 @@
 #define BUF_LONG BUF_LEN * 2
 
 /* Globals in aim.c */
-extern GList *buddy_pounces;
 extern int opt_away;
 extern char *opt_away_arg;
 extern char *opt_rcfile_arg;
--- a/src/gaimrc.c	Sat Apr 05 08:03:10 2003 +0000
+++ b/src/gaimrc.c	Sat Apr 05 10:14:21 2003 +0000
@@ -39,6 +39,8 @@
 #include "prpl.h"
 #include "proxy.h"
 #include "sound.h"
+#include "pounce.h"
+#include "gtkpounce.h"
 
 #ifdef _WIN32
 #include "win32dep.h"
@@ -79,6 +81,28 @@
 	char value[MAX_VALUES][4096];
 };
 
+/*
+ * This is absolutely necessary, unfortunately. It is used to grab
+ * the information on the pounce, so that we can then later register
+ * them. The reason we do this (well, one of them) is because the buddy
+ * list isn't processed yet.
+ *
+ *  -- ChipX86
+ */
+struct pounce_placeholder
+{
+	char name[80];
+	char message[2048];
+	char command[2048];
+	char sound[2048];
+	char pouncer[80];
+
+	int protocol;
+	int options;
+};
+
+static GList *buddy_pounces = NULL;
+
 static struct parse *parse_line(char *line, struct parse *p)
 {
 	char *c = line;
@@ -302,12 +326,111 @@
 	fprintf(f, "}\n");
 }
 
-static void gaimrc_read_pounce(FILE *f)
+/*
+ * This is temporary, and we're using it to translate the new event
+ * and action values into the old ones. We're also adding entries for
+ * new types, but if you go and use an older gaim, these will be nuked.
+ * When we have a better prefs system, this can go away.
+ *
+ *   -- ChipX86
+ */
+static int pounce_evt_trans_table[] =
+{
+	0x010, GAIM_POUNCE_SIGNON,
+	0x020, GAIM_POUNCE_AWAY_RETURN,
+	0x040, GAIM_POUNCE_IDLE_RETURN,
+	0x080, GAIM_POUNCE_TYPING,
+	/* 0x100, save, is handled separately. */
+	0x400, GAIM_POUNCE_SIGNOFF,
+	0x800, GAIM_POUNCE_AWAY,
+	0x1000, GAIM_POUNCE_IDLE,
+	0x2000, GAIM_POUNCE_TYPING_STOPPED
+};
+
+static int pounce_act_trans_table[] =
+{
+	0x001, GAIM_GTKPOUNCE_OPEN_WIN,
+	0x002, GAIM_GTKPOUNCE_SEND_MSG,
+	0x004, GAIM_GTKPOUNCE_EXEC_CMD,
+	0x008, GAIM_GTKPOUNCE_PLAY_SOUND,
+	/* 0x100, save, is handled separately. */
+	0x200, GAIM_GTKPOUNCE_POPUP
+};
+
+static int pounce_evt_trans_table_size =
+	(sizeof(pounce_evt_trans_table) / sizeof(*pounce_evt_trans_table));
+
+static int pounce_act_trans_table_size =
+	(sizeof(pounce_act_trans_table) / sizeof(*pounce_act_trans_table));
+
+static int
+new_pounce_opts_to_old(struct gaim_pounce *pounce)
+{
+	struct gaim_gtkpounce_data *gtkpounce;
+
+	int opts = 0;
+	int i;
+
+	gtkpounce = GAIM_GTKPOUNCE(pounce);
+
+	/* First, convert events */
+	for (i = 0; i < pounce_evt_trans_table_size; i += 2)
+	{
+		GaimPounceEvent evt = pounce_evt_trans_table[i + 1];
+
+		if ((gaim_pounce_get_events(pounce) & evt) == evt)
+			opts |= pounce_evt_trans_table[i];
+	}
+
+	for (i = 0; i < pounce_act_trans_table_size; i += 2)
+	{
+		GaimGtkPounceAction act = pounce_act_trans_table[i + 1];
+
+		if ((gtkpounce->actions & act) == act)
+			opts |= pounce_act_trans_table[i];
+	}
+
+	if (gtkpounce->save)
+		opts |= 0x100;
+
+	return opts;
+}
+
+static void
+old_pounce_opts_to_new(int opts, GaimPounceEvent *events,
+					   GaimGtkPounceAction *actions)
+{
+	int i;
+
+	*events = 0;
+	*actions = 0;
+
+	/* First, convert events */
+	for (i = 0; i < pounce_evt_trans_table_size; i += 2)
+	{
+		int evt = pounce_evt_trans_table[i];
+
+		if ((opts & evt) == evt)
+			*events |= pounce_evt_trans_table[i + 1];
+	}
+
+	for (i = 0; i < pounce_act_trans_table_size; i += 2)
+	{
+		int act = pounce_act_trans_table[i];
+
+		if ((opts & act) == act)
+			*actions |= pounce_act_trans_table[i + 1];
+	
+	}
+}
+
+static void
+gaimrc_read_pounce(FILE *f)
 {
 	struct parse parse_buffer;
 	struct parse *p;
 	char buf[4096];
-	struct buddy_pounce *b;
+	struct pounce_placeholder *b;
 
 	buf[0] = 0;
 
@@ -320,7 +443,7 @@
 
 		p = parse_line(buf, &parse_buffer);
 		if (!strcmp(p->option, "entry")) {
-			b = g_new0(struct buddy_pounce, 1);
+			b = g_new0(struct pounce_placeholder, 1);
 
 			g_snprintf(b->name, sizeof(b->name), "%s", p->value[0]);
 			g_snprintf(b->message, sizeof(b->message), "%s", p->value[1]);
@@ -338,48 +461,72 @@
 	}
 }
 
-static void gaimrc_write_pounce(FILE *f)
+static void
+gaimrc_write_pounce(FILE *f)
 {
-	GList *pnc = buddy_pounces;
-	struct buddy_pounce *b;
+	GList *pnc;
+	struct gaim_pounce *pounce;
+	struct gaim_gtkpounce_data *pounce_data;
+
+	debug_printf("*** Writing pounces.\n");
 
 	fprintf(f, "pounce {\n");
 
-	while (pnc) {
+	for (pnc = gaim_get_pounces(); pnc != NULL; pnc = pnc->next) {
 		char *str1, *str2, *str3, *str4;
+		struct gaim_account *account;
+
+		pounce      = (struct gaim_pounce *)pnc->data;
+		pounce_data = GAIM_GTKPOUNCE(pounce);
+		account     = gaim_pounce_get_pouncer(pounce);
+
+		/* Pouncee name */
+		str1 = escape_text2(gaim_pounce_get_pouncee(pounce));
 
-		b = (struct buddy_pounce *)pnc->data;
+		if (pounce_data == NULL)
+		{
+			fprintf(f, "\tentry { %s } {  } {  } { %d } { %s } { %d } {  }\n",
+					str1, new_pounce_opts_to_old(pounce),
+					account->username, account->protocol);
 
-		str1 = escape_text2(b->name);
-		if (strlen(b->message))
-			str2 = escape_text2(b->message);
+			free(str1);
+
+			continue;
+		}
+
+		/* Message */
+		if (pounce_data->message != NULL)
+			str2 = escape_text2(pounce_data->message);
 		else {
 			str2 = malloc(1);
-			str2[0] = 0;
+			*str2 = '\0';
 		}
-		if (strlen(b->command))
-			str3 = escape_text2(b->command);
+
+		/* Command */
+		if (pounce_data->command != NULL)
+			str3 = escape_text2(pounce_data->command);
 		else {
 			str3 = malloc(1);
-			str3[0] = 0;
+			*str3 = '\0';
 		}
-		if (strlen(b->sound))
-			str4 = escape_text2(b->sound);
+
+		/* Sound file */
+		if (pounce_data->sound != NULL)
+			str4 = escape_text2(pounce_data->sound);
 		else {
 			str4 = malloc(1);
-			str4[0] = 0;
+			*str4 = '\0';
 		}
 
 		fprintf(f, "\tentry { %s } { %s } { %s } { %d } { %s } { %d } { %s }\n",
-			str1, str2, str3, b->options, b->pouncer, b->protocol, str4);
+				str1, str2, str3, new_pounce_opts_to_old(pounce),
+				account->username, account->protocol, str4);
 
 		/* escape_text2 uses malloc(), so we don't want to g_free these */
 		free(str1);
 		free(str2);
 		free(str3);
 		free(str4);
-
-		pnc = pnc->next;
 	}
 
 	fprintf(f, "}\n");
@@ -1463,3 +1610,41 @@
 	return (strcmp(msg_a->name, msg_b->name));
 
 }
+
+void
+load_pounces()
+{
+	GList *l;
+	struct pounce_placeholder *ph;
+	struct gaim_pounce *pounce;
+	struct gaim_gtkpounce_data *pounce_data;
+	struct gaim_account *account;
+
+	debug_printf("*** Loading pounces...\n");
+
+	for (l = buddy_pounces; l != NULL; l = l->next) {
+		ph = (struct pounce_placeholder *)l->data;
+		GaimPounceEvent events = GAIM_POUNCE_NONE;
+		GaimGtkPounceAction actions = GAIM_GTKPOUNCE_NONE;
+
+		account = gaim_account_find(ph->pouncer, ph->protocol);
+
+		old_pounce_opts_to_new(ph->options, &events, &actions);
+
+		pounce = gaim_gtkpounce_new(account, ph->name, events, actions,
+									ph->message, ph->command, ph->sound,
+									(ph->options & 0x100));
+
+		g_free(ph);
+	}
+
+	g_list_free(buddy_pounces);
+	buddy_pounces = NULL;
+
+	/* 
+	 * < ChipX86|Coding> why do we save prefs just after reading them?
+	 * <      faceprint> ChipX86|Coding: because we're cool like that
+	 * <SeanEgan|Coding> damn straight
+	 */
+	save_prefs();
+}
--- a/src/gtklist.h	Sat Apr 05 08:03:10 2003 +0000
+++ b/src/gtklist.h	Sat Apr 05 10:14:21 2003 +0000
@@ -55,6 +55,8 @@
 		*warning_column, 
 		*buddy_icon_column;
 
+	GtkWidget *bpmenu;              /**< The buddy pounce menu. */
+
 	GtkWidget *bbox;                /**< A Button Box. */
 	GtkTooltips *tooltips;           /**< Tooltips for the buttons. */
 
@@ -79,6 +81,8 @@
 
 #define GAIM_GTK_BLIST_NODE(node) ((struct gaim_gtk_blist_node *)(node)->ui_data)
 #define GAIM_GTK_BLIST(list) ((struct gaim_gtk_buddy_list *)(list)->ui_data)
+#define GAIM_IS_GTK_BLIST(list) \
+	((list)->ui_ops == gaim_get_gtk_blist_ui_ops())
 
 /**************************************************************************
  * @name GTK+ Conversation API
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/gtkpounce.c	Sat Apr 05 10:14:21 2003 +0000
@@ -0,0 +1,772 @@
+/**
+ * @file gtkpounce.h GTK+ buddy pounce API
+ *
+ * gaim
+ *
+ * Copyright (C) 2003, Christian Hammond <chipx86@gnupdate.org>
+ * 
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ *
+ */
+#include "gaim.h"
+#include "gtkpounce.h"
+#include "gtklist.h"
+#include "prpl.h"
+#include "sound.h"
+
+struct gaim_gtkpounce_dialog
+{
+	/* Pounce data */
+	struct gaim_pounce  *pounce;
+	struct gaim_account *account;
+
+	/* The window */
+	GtkWidget *window;
+
+	/* Pounce Who */
+	GtkWidget *account_menu;
+	GtkWidget *buddy_entry;
+
+	/* Pounce When */
+	GtkWidget *signon;
+	GtkWidget *signoff;
+	GtkWidget *away;
+	GtkWidget *away_return;
+	GtkWidget *idle;
+	GtkWidget *idle_return;
+	GtkWidget *typing;
+	GtkWidget *stop_typing;
+
+	/* Pounce Action */
+	GtkWidget *open_win;
+	GtkWidget *popup;
+	GtkWidget *send_msg;
+	GtkWidget *send_msg_entry;
+	GtkWidget *exec_cmd;
+	GtkWidget *exec_cmd_entry;
+	GtkWidget *play_sound;
+	GtkWidget *play_sound_entry;
+
+	GtkWidget *save_pounce;
+};
+
+/**************************************************************************
+ * Callbacks
+ **************************************************************************/
+static gint
+delete_win_cb(GtkWidget *w, GdkEventAny *e,
+			  struct gaim_gtkpounce_dialog *dialog)
+{
+	gtk_widget_destroy(dialog->window);
+	g_free(dialog);
+
+	return TRUE;
+}
+
+static void
+cancel_cb(GtkWidget *w, struct gaim_gtkpounce_dialog *dialog)
+{
+	delete_win_cb(NULL, NULL, dialog);
+}
+
+
+static void
+save_pounce_cb(GtkWidget *w, struct gaim_gtkpounce_dialog *dialog)
+{
+	const char *name;
+	const char *message, *command, *sound;
+	struct gaim_buddy_list *blist;
+	struct gaim_gtk_buddy_list *gtkblist;
+	GaimPounceEvent events = GAIM_POUNCE_NONE;
+	GaimGtkPounceAction actions = GAIM_GTKPOUNCE_NONE;
+	gboolean save;
+
+	name = gtk_entry_get_text(GTK_ENTRY(dialog->buddy_entry));
+
+	if (*name == '\0') {
+		do_error_dialog(_("Please enter a buddy to pounce."), NULL, GAIM_ERROR);
+		return;
+	}
+
+	/* Events */
+	if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(dialog->signon)))
+		events |= GAIM_POUNCE_SIGNON;
+
+	if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(dialog->signoff)))
+		events |= GAIM_POUNCE_SIGNOFF;
+
+	if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(dialog->away)))
+		events |= GAIM_POUNCE_AWAY;
+
+	if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(dialog->away_return)))
+		events |= GAIM_POUNCE_AWAY_RETURN;
+
+	if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(dialog->idle)))
+		events |= GAIM_POUNCE_IDLE;
+
+	if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(dialog->idle_return)))
+		events |= GAIM_POUNCE_IDLE_RETURN;
+
+	if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(dialog->typing)))
+		events |= GAIM_POUNCE_TYPING;
+
+	if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(dialog->stop_typing)))
+		events |= GAIM_POUNCE_TYPING_STOPPED;
+
+
+	/* Actions */
+	if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(dialog->open_win)))
+		actions |= GAIM_GTKPOUNCE_OPEN_WIN;
+
+	if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(dialog->popup)))
+		actions |= GAIM_GTKPOUNCE_POPUP;
+
+	if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(dialog->send_msg)))
+		actions |= GAIM_GTKPOUNCE_SEND_MSG;
+
+	if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(dialog->exec_cmd)))
+		actions |= GAIM_GTKPOUNCE_EXEC_CMD;
+
+	if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(dialog->play_sound)))
+		actions |= GAIM_GTKPOUNCE_PLAY_SOUND;
+
+	save = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(dialog->save_pounce));
+
+	/* Data fields */
+	message = gtk_entry_get_text(GTK_ENTRY(dialog->send_msg_entry));
+	command = gtk_entry_get_text(GTK_ENTRY(dialog->exec_cmd_entry));
+	sound   = gtk_entry_get_text(GTK_ENTRY(dialog->play_sound_entry));
+
+	if (*message == '\0') message = NULL;
+	if (*command == '\0') command = NULL;
+	if (*sound   == '\0') sound   = NULL;
+
+	if (dialog->pounce == NULL)
+	{
+		gaim_gtkpounce_new(dialog->account, name, events, actions,
+						   message, command, sound, save);
+	}
+	else
+	{
+		struct gaim_gtkpounce_data *pounce_data;
+
+		gaim_pounce_set_events(dialog->pounce, events);
+		gaim_pounce_set_pouncer(dialog->pounce, dialog->account);
+		gaim_pounce_set_pouncee(dialog->pounce, name);
+
+		pounce_data = GAIM_GTKPOUNCE(dialog->pounce);
+
+		if (pounce_data->message != NULL) g_free(pounce_data->message);
+		if (pounce_data->command != NULL) g_free(pounce_data->command);
+		if (pounce_data->sound   != NULL) g_free(pounce_data->sound);
+
+		pounce_data->message = (message == NULL ? NULL : g_strdup(message));
+		pounce_data->command = (command == NULL ? NULL : g_strdup(command));
+		pounce_data->sound   = (sound   == NULL ? NULL : g_strdup(sound));
+
+		pounce_data->actions = actions;
+		pounce_data->save    = save;
+	}
+
+	delete_win_cb(NULL, NULL, dialog);
+
+	/* Rebuild the pounce menu */
+	blist = gaim_get_blist();
+
+	if (GAIM_IS_GTK_BLIST(blist))
+	{
+		gtkblist = GAIM_GTK_BLIST(blist);
+
+		gaim_gtkpounce_menu_build(gtkblist->bpmenu);
+	}
+
+	save_prefs();
+}
+
+static GtkWidget *
+pounce_choose_cb(GtkWidget *item, struct gaim_gtkpounce_dialog *dialog)
+{
+	dialog->account = g_object_get_data(G_OBJECT(item), "user_data");
+}
+
+static GtkWidget *
+pounce_user_menu(struct gaim_gtkpounce_dialog *dialog)
+{
+	struct gaim_account *account;
+	struct prpl *prpl;
+	GtkWidget *opt_menu;
+	GtkWidget *menu;
+	GtkWidget *item;
+	GSList *l;
+	char buf[2048];
+	int count, place;
+
+	opt_menu = gtk_option_menu_new();
+	menu = gtk_menu_new();
+
+	for (l = gaim_accounts, count = 0; l != NULL; l = l->next, count++) {
+		account = (struct gaim_account *)l->data;
+
+		prpl = (struct prpl *)find_prpl(account->protocol);
+
+		g_snprintf(buf, sizeof(buf), "%s (%s)", account->username,
+				   (prpl && prpl->name) ? prpl->name : _("Unknown"));
+
+		item = gtk_menu_item_new_with_label(buf);
+		g_object_set_data(G_OBJECT(item), "user_data", account);
+
+		g_signal_connect(G_OBJECT(item), "activate",
+						 G_CALLBACK(pounce_choose_cb), dialog);
+
+		gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
+		gtk_widget_show(item);
+
+		if (dialog->account == account) {
+			gtk_menu_item_activate(GTK_MENU_ITEM(item));
+			place = count;
+		}
+	}
+
+	gtk_option_menu_set_menu(GTK_OPTION_MENU(opt_menu), menu);
+	gtk_option_menu_set_history(GTK_OPTION_MENU(opt_menu), place);
+
+	return opt_menu;
+}
+
+static void
+pounce_cb(struct gaim_pounce *pounce, GaimPounceEvent events, void *data)
+{
+	struct gaim_conversation *conv;
+	struct gaim_account *account;
+	struct gaim_gtkpounce_data *pounce_data;
+	const char *pouncee;
+
+	pounce_data = (struct gaim_gtkpounce_data *)data;
+	pouncee     = gaim_pounce_get_pouncee(pounce);
+	account     = gaim_pounce_get_pouncer(pounce);
+
+	if (pounce_data->actions & GAIM_GTKPOUNCE_OPEN_WIN) {
+		conv = gaim_find_conversation(pouncee);
+
+		if (conv == NULL)
+			conv = gaim_conversation_new(GAIM_CONV_IM, account, pouncee);
+	}
+
+	if (pounce_data->actions & GAIM_GTKPOUNCE_POPUP) {
+		char tmp[1024];
+
+		g_snprintf(tmp, sizeof(tmp),
+				   (events & GAIM_POUNCE_TYPING) ? _("%s has started typing to you") :
+				   (events & GAIM_POUNCE_SIGNON) ? _("%s has signed on") :
+				   (events & GAIM_POUNCE_IDLE_RETURN) ? _("%s has returned from being idle") :
+				   (events & GAIM_POUNCE_AWAY_RETURN) ? _("%s has returned from being away") :
+				   (events & GAIM_POUNCE_TYPING_STOPPED) ? _("%s has stopped typing to you") :
+				   (events & GAIM_POUNCE_SIGNOFF) ? _("%s has signed off") :
+				   (events & GAIM_POUNCE_IDLE) ? _("%s has become idle") :
+				   (events & GAIM_POUNCE_AWAY) ? _("%s has gone away.") :
+				   _("Unknown pounce event. Please report this!"),
+				   pouncee);
+
+		do_error_dialog(tmp, NULL, GAIM_INFO);
+	}
+
+	if (pounce_data->actions & GAIM_GTKPOUNCE_SEND_MSG &&
+		*pounce_data->message != '\0') {
+
+		conv = gaim_find_conversation(pouncee);
+
+		if (conv == NULL)
+			conv = gaim_conversation_new(GAIM_CONV_IM, account, pouncee);
+
+		gaim_conversation_write(conv, NULL, pounce_data->message, -1,
+								WFLAG_SEND, time(NULL));
+
+		serv_send_im(account->gc, (char *)pouncee, pounce_data->message, -1, 0);
+	}
+
+	if (pounce_data->actions & GAIM_GTKPOUNCE_EXEC_CMD &&
+		*pounce_data->command != '\0') {
+#ifndef _WIN32
+		int pid = fork();
+
+		if (pid == 0) {
+			char *args[4];
+
+			args[0] = "sh";
+			args[1] = "-c";
+			args[2] = pounce_data->command;
+			args[3] = NULL;
+
+			execvp(args[0], args);
+
+			_exit(0);
+		}
+#endif /* _WIN32 */
+	}
+
+	if (pounce_data->actions & GAIM_GTKPOUNCE_PLAY_SOUND) {
+		if (*pounce_data->sound != '\0')
+			gaim_sound_play_file(pounce_data->sound);
+		else
+			gaim_sound_play_event(GAIM_SOUND_POUNCE_DEFAULT);
+	}
+
+	if (!pounce_data->save)
+		gaim_pounce_destroy(pounce);
+}
+
+static void
+free_pounce(void *data)
+{
+	struct gaim_gtkpounce_data *pounce_data;
+	struct gaim_buddy_list *blist;
+	struct gaim_gtk_buddy_list *gtkblist;
+
+	pounce_data = (struct gaim_gtkpounce_data *)data;
+
+	if (pounce_data->message != NULL) g_free(pounce_data->message);
+	if (pounce_data->command != NULL) g_free(pounce_data->command);
+	if (pounce_data->sound   != NULL) g_free(pounce_data->sound);
+
+	g_free(data);
+
+	/* Rebuild the pounce menu */
+	blist = gaim_get_blist();
+
+	if (GAIM_IS_GTK_BLIST(blist))
+	{
+		gtkblist = GAIM_GTK_BLIST(blist);
+
+		gaim_gtkpounce_menu_build(gtkblist->bpmenu);
+	}
+
+	save_prefs();
+}
+
+struct gaim_pounce *
+gaim_gtkpounce_new(struct gaim_account *pouncer, const char *pouncee,
+				   GaimPounceEvent events, GaimGtkPounceAction actions,
+				   const char *message, const char *command,
+				   const char *sound, gboolean save)
+{
+	struct gaim_gtkpounce_data *data;
+
+	data = g_new0(struct gaim_gtkpounce_data, 1);
+
+	data->actions = actions;
+
+	if (message != NULL) data->message = g_strdup(message);
+	if (command != NULL) data->command = g_strdup(command);
+	if (sound   != NULL) data->sound   = g_strdup(sound);
+
+	data->save = save;
+
+	return gaim_pounce_new(pouncer, pouncee, events, pounce_cb, data,
+						   free_pounce);
+}
+
+void
+gaim_gtkpounce_dialog_show(struct buddy *buddy,
+						   struct gaim_pounce *cur_pounce)
+{
+	struct gaim_gtkpounce_dialog *dialog;
+	GtkWidget *window;
+	GtkWidget *label;
+	GtkWidget *bbox;
+	GtkWidget *vbox1, *vbox2;
+	GtkWidget *hbox;
+	GtkWidget *button;
+	GtkWidget *frame;
+	GtkWidget *table;
+	GtkWidget *optmenu;
+	GtkWidget *sep;
+	GtkSizeGroup *sg;
+
+	dialog = g_new0(struct gaim_gtkpounce_dialog, 1);
+
+	if (cur_pounce != NULL) {
+		dialog->pounce  = cur_pounce;
+		dialog->account = gaim_pounce_get_pouncer(cur_pounce);
+	}
+	else if (buddy != NULL) {
+		dialog->pounce  = NULL;
+		dialog->account = buddy->account;
+	}
+	else {
+		dialog->pounce  = NULL;
+		dialog->account = gaim_accounts->data;
+	}
+
+	sg = gtk_size_group_new(GTK_SIZE_GROUP_HORIZONTAL);
+
+	/* Create the window. */
+	dialog->window = window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
+	gtk_window_set_type_hint(GTK_WINDOW(window), GDK_WINDOW_TYPE_HINT_DIALOG);
+	gtk_window_set_role(GTK_WINDOW(window), "buddy_pounce");
+	gtk_window_set_resizable(GTK_WINDOW(window), FALSE);
+	gtk_window_set_title(GTK_WINDOW(window),
+						 (cur_pounce == NULL
+						  ? _("New Buddy Pounce") : _("Edit Buddy Pounce")));
+
+	gtk_container_set_border_width(GTK_CONTAINER(window), 12);
+	gtk_widget_realize(window);
+
+	g_signal_connect(G_OBJECT(window), "delete_event",
+					 G_CALLBACK(delete_win_cb), dialog);
+
+	/* Create the parent vbox for everything. */
+	vbox1 = gtk_vbox_new(FALSE, 12);
+	gtk_container_add(GTK_CONTAINER(window), vbox1);
+	gtk_widget_show(vbox1);
+
+	/* Create the vbox that will contain all the prefs stuff. */
+	vbox2 = gtk_vbox_new(FALSE, 18);
+	gtk_box_pack_start(GTK_BOX(vbox1), vbox2, TRUE, TRUE, 0);
+
+	/* Create the "Pounce Who" frame. */
+	frame = make_frame(vbox2, _("Pounce Who"));
+
+	/* Account: */
+	hbox = gtk_hbox_new(FALSE, 6);
+	gtk_box_pack_start(GTK_BOX(frame), hbox, FALSE, FALSE, 0);
+	gtk_widget_show(hbox);
+
+	label = gtk_label_new_with_mnemonic(_("_Account:"));
+	gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
+	gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 0);
+	gtk_widget_show(label);
+	gtk_size_group_add_widget(sg, label);
+
+	dialog->account_menu = pounce_user_menu(dialog);
+	gtk_box_pack_start(GTK_BOX(hbox), dialog->account_menu, FALSE, FALSE, 0);
+	gtk_widget_show(dialog->account_menu);
+
+	/* Buddy: */
+	hbox = gtk_hbox_new(FALSE, 6);
+	gtk_box_pack_start(GTK_BOX(frame), hbox, FALSE, FALSE, 0);
+	gtk_widget_show(hbox);
+
+	label = gtk_label_new_with_mnemonic(_("_Buddy Name:"));
+	gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
+	gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 0);
+	gtk_widget_show(label);
+	gtk_size_group_add_widget(sg, label);
+
+	dialog->buddy_entry = gtk_entry_new();
+	gtk_box_pack_start(GTK_BOX(hbox), dialog->buddy_entry, TRUE, TRUE, 0);
+	gtk_widget_show(dialog->buddy_entry);
+
+	if (cur_pounce != NULL) {
+		gtk_entry_set_text(GTK_ENTRY(dialog->buddy_entry),
+						   gaim_pounce_get_pouncee(cur_pounce));
+	}
+	else if (buddy != NULL) {
+		gtk_entry_set_text(GTK_ENTRY(dialog->buddy_entry), buddy->name);
+	}
+
+	/* Create the "Pounce When" frame. */
+	frame = make_frame(vbox2, _("Pounce When"));
+
+	table = gtk_table_new(2, 4, FALSE);
+	gtk_container_add(GTK_CONTAINER(frame), table);
+	gtk_table_set_col_spacings(GTK_TABLE(table), 12);
+	gtk_widget_show(table);
+
+	dialog->signon =
+		gtk_check_button_new_with_label(_("Sign on"));
+	dialog->signoff =
+		gtk_check_button_new_with_label(_("Sign off"));
+	dialog->away =
+		gtk_check_button_new_with_label(_("Away"));
+	dialog->away_return =
+		gtk_check_button_new_with_label(_("Return from away"));
+	dialog->idle =
+		gtk_check_button_new_with_label(_("Idle"));
+	dialog->idle_return =
+		gtk_check_button_new_with_label(_("Return from idle"));
+	dialog->typing =
+		gtk_check_button_new_with_label(_("Buddy starts typing"));
+	dialog->stop_typing =
+		gtk_check_button_new_with_label(_("Buddy stops typing"));
+
+	gtk_table_attach(GTK_TABLE(table), dialog->signon,      0, 1, 0, 1,
+					 GTK_FILL, 0, 0, 0);
+	gtk_table_attach(GTK_TABLE(table), dialog->signoff,     1, 2, 0, 1,
+					 GTK_FILL, 0, 0, 0);
+	gtk_table_attach(GTK_TABLE(table), dialog->away,        0, 1, 1, 2,
+					 GTK_FILL, 0, 0, 0);
+	gtk_table_attach(GTK_TABLE(table), dialog->away_return, 1, 2, 1, 2,
+					 GTK_FILL, 0, 0, 0);
+	gtk_table_attach(GTK_TABLE(table), dialog->idle,        0, 1, 2, 3,
+					 GTK_FILL, 0, 0, 0);
+	gtk_table_attach(GTK_TABLE(table), dialog->idle_return, 1, 2, 2, 3,
+					 GTK_FILL, 0, 0, 0);
+	gtk_table_attach(GTK_TABLE(table), dialog->typing,      0, 1, 3, 4,
+					 GTK_FILL, 0, 0, 0);
+	gtk_table_attach(GTK_TABLE(table), dialog->stop_typing, 1, 2, 3, 4,
+					 GTK_FILL, 0, 0, 0);
+
+	gtk_widget_show(dialog->signon);
+	gtk_widget_show(dialog->signoff);
+	gtk_widget_show(dialog->away);
+	gtk_widget_show(dialog->away_return);
+	gtk_widget_show(dialog->idle);
+	gtk_widget_show(dialog->idle_return);
+	gtk_widget_show(dialog->typing);
+	gtk_widget_show(dialog->stop_typing);
+
+	/* Create the "Pounce Action" frame. */
+	frame = make_frame(vbox2, _("Pounce Action"));
+
+	table = gtk_table_new(2, 5, FALSE);
+	gtk_container_add(GTK_CONTAINER(frame), table);
+	gtk_table_set_col_spacings(GTK_TABLE(table), 12);
+	gtk_widget_show(table);
+
+	dialog->open_win = gtk_check_button_new_with_label(_("Open an IM window"));
+	dialog->popup = gtk_check_button_new_with_label(_("Popup notification"));
+	dialog->send_msg = gtk_check_button_new_with_label(_("Send a message"));
+	dialog->exec_cmd = gtk_check_button_new_with_label(_("Execute a command"));
+	dialog->play_sound = gtk_check_button_new_with_label(_("Play a Sound"));
+
+	dialog->send_msg_entry   = gtk_entry_new();
+	dialog->exec_cmd_entry   = gtk_entry_new();
+	dialog->play_sound_entry = gtk_entry_new();
+
+	gtk_widget_set_sensitive(dialog->send_msg_entry,   FALSE);
+	gtk_widget_set_sensitive(dialog->exec_cmd_entry,   FALSE);
+	gtk_widget_set_sensitive(dialog->play_sound_entry, FALSE);
+
+	gtk_table_attach(GTK_TABLE(table), dialog->open_win,         0, 1, 0, 1,
+					 GTK_FILL, 0, 0, 0);
+	gtk_table_attach(GTK_TABLE(table), dialog->popup,            0, 1, 1, 2,
+					 GTK_FILL, 0, 0, 0);
+	gtk_table_attach(GTK_TABLE(table), dialog->send_msg,         0, 1, 2, 3,
+					 GTK_FILL, 0, 0, 0);
+	gtk_table_attach(GTK_TABLE(table), dialog->send_msg_entry,   1, 2, 2, 3,
+					 GTK_FILL, 0, 0, 0);
+	gtk_table_attach(GTK_TABLE(table), dialog->exec_cmd,         0, 1, 3, 4,
+					 GTK_FILL, 0, 0, 0);
+	gtk_table_attach(GTK_TABLE(table), dialog->exec_cmd_entry,   1, 2, 3, 4,
+					 GTK_FILL, 0, 0, 0);
+	gtk_table_attach(GTK_TABLE(table), dialog->play_sound,       0, 1, 4, 5,
+					 GTK_FILL, 0, 0, 0);
+	gtk_table_attach(GTK_TABLE(table), dialog->play_sound_entry, 1, 2, 4, 5,
+					 GTK_FILL, 0, 0, 0);
+
+	gtk_widget_show(dialog->open_win);
+	gtk_widget_show(dialog->popup);
+	gtk_widget_show(dialog->send_msg);
+	gtk_widget_show(dialog->send_msg_entry);
+	gtk_widget_show(dialog->exec_cmd);
+	gtk_widget_show(dialog->exec_cmd_entry);
+	gtk_widget_show(dialog->play_sound);
+	gtk_widget_show(dialog->play_sound_entry);
+
+	g_signal_connect(G_OBJECT(dialog->send_msg), "clicked",
+					 G_CALLBACK(gaim_gtk_toggle_sensitive),
+					 dialog->send_msg_entry);
+	g_signal_connect(G_OBJECT(dialog->exec_cmd), "clicked",
+					 G_CALLBACK(gaim_gtk_toggle_sensitive),
+					 dialog->exec_cmd_entry);
+	g_signal_connect(G_OBJECT(dialog->play_sound), "clicked",
+					 G_CALLBACK(gaim_gtk_toggle_sensitive),
+					 dialog->play_sound_entry);
+
+	g_signal_connect(G_OBJECT(dialog->send_msg_entry), "activate",
+					 G_CALLBACK(save_pounce_cb), dialog);
+	g_signal_connect(G_OBJECT(dialog->exec_cmd_entry), "activate",
+					 G_CALLBACK(save_pounce_cb), dialog);
+	g_signal_connect(G_OBJECT(dialog->play_sound_entry), "activate",
+					 G_CALLBACK(save_pounce_cb), dialog);
+
+	/* Now the last part, where we have the Save checkbox */
+	dialog->save_pounce = gtk_check_button_new_with_mnemonic(
+		_("_Save this pounce after activation"));
+
+	gtk_box_pack_start(GTK_BOX(vbox2), dialog->save_pounce, FALSE, FALSE, 0);
+
+	/* Separator... */
+	sep = gtk_hseparator_new();
+	gtk_box_pack_start(GTK_BOX(vbox1), sep, FALSE, FALSE, 0);
+	gtk_widget_show(sep);
+
+	/* Now the button box! */
+	bbox = gtk_hbutton_box_new();
+	gtk_box_set_spacing(GTK_BOX(bbox), 6);
+	gtk_button_box_set_layout(GTK_BUTTON_BOX(bbox), GTK_BUTTONBOX_END);
+	gtk_box_pack_end(GTK_BOX(vbox1), bbox, FALSE, FALSE, 0);
+	gtk_widget_show(bbox);
+
+	/* Cancel button */
+	button = gtk_button_new_from_stock(GTK_STOCK_CANCEL);
+	gtk_box_pack_start(GTK_BOX(bbox), button, FALSE, FALSE, 0);
+	gtk_widget_show(button);
+
+	g_signal_connect(G_OBJECT(button), "clicked",
+					 G_CALLBACK(cancel_cb), dialog);
+
+	/* OK button */
+	button = gtk_button_new_from_stock(GTK_STOCK_OK);
+	gtk_box_pack_start(GTK_BOX(bbox), button, FALSE, FALSE, 0);
+	gtk_widget_show(button);
+	
+	g_signal_connect(G_OBJECT(button), "clicked",
+					 G_CALLBACK(save_pounce_cb), dialog);
+
+	/* Set the values of stuff. */
+	if (cur_pounce != NULL) {
+		GaimPounceEvent events;
+		GaimGtkPounceAction actions;
+		struct gaim_gtkpounce_data *pounce_data;
+
+		pounce_data = GAIM_GTKPOUNCE(cur_pounce);
+		events      = gaim_pounce_get_events(cur_pounce);
+		actions     = pounce_data->actions;
+
+		gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(dialog->signon),
+									(events & GAIM_POUNCE_SIGNON));
+		gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(dialog->signoff),
+									(events & GAIM_POUNCE_SIGNOFF));
+		gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(dialog->away),
+									(events & GAIM_POUNCE_AWAY));
+		gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(dialog->away_return),
+									(events & GAIM_POUNCE_AWAY_RETURN));
+		gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(dialog->idle),
+									(events & GAIM_POUNCE_IDLE));
+		gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(dialog->idle_return),
+									(events & GAIM_POUNCE_IDLE_RETURN));
+		gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(dialog->typing),
+									(events & GAIM_POUNCE_TYPING));
+		gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(dialog->stop_typing),
+									(events & GAIM_POUNCE_TYPING_STOPPED));
+
+		gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(dialog->open_win),
+									(actions & GAIM_GTKPOUNCE_OPEN_WIN));
+		gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(dialog->popup),
+									(actions & GAIM_GTKPOUNCE_POPUP));
+		gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(dialog->send_msg),
+									(actions & GAIM_GTKPOUNCE_SEND_MSG));
+		gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(dialog->exec_cmd),
+									(actions & GAIM_GTKPOUNCE_EXEC_CMD));
+		gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(dialog->play_sound),
+									(actions & GAIM_GTKPOUNCE_PLAY_SOUND));
+
+		gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(dialog->save_pounce),
+									 pounce_data->save);
+
+		gtk_entry_set_text(GTK_ENTRY(dialog->send_msg_entry),
+						   pounce_data->message);
+		gtk_entry_set_text(GTK_ENTRY(dialog->exec_cmd_entry),
+						   pounce_data->command);
+		gtk_entry_set_text(GTK_ENTRY(dialog->play_sound_entry),
+						   pounce_data->sound);
+	}
+	else {
+		/* Set some defaults */
+		gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(dialog->send_msg), TRUE);
+		gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(dialog->signon), TRUE);
+	}
+
+	gtk_widget_show_all(vbox2);
+	gtk_widget_show(window);
+}
+
+static void
+new_pounce_cb(GtkWidget *w, struct buddy *b)
+{
+	gaim_gtkpounce_dialog_show(b, NULL);
+}
+
+static void
+edit_pounce_cb(GtkWidget *w, struct gaim_pounce *pounce)
+{
+	struct buddy *buddy;
+
+	buddy = gaim_find_buddy(gaim_pounce_get_pouncer(pounce),
+							gaim_pounce_get_pouncee(pounce));
+
+	gaim_gtkpounce_dialog_show(buddy, pounce);
+}
+
+void
+gaim_gtkpounce_menu_build(GtkWidget *menu)
+{
+	GtkWidget *remmenu, *submenu, *item;
+	GList *l;
+	GList *bp;
+	struct gaim_pounce *pounce;
+	const char *buddy;
+
+	for (l = gtk_container_get_children(GTK_CONTAINER(menu));
+		 l != NULL;
+		 l = l->next) {
+
+		gtk_widget_destroy(GTK_WIDGET(l->data));
+	}
+
+	/* "New Buddy Pounce" */
+	item = gtk_menu_item_new_with_label(_("New Buddy Pounce"));
+	gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
+	gtk_widget_show(item);
+	g_signal_connect(G_OBJECT(item), "activate",
+					 G_CALLBACK(new_pounce_cb), NULL);
+
+	/* "Remove Buddy Pounce" */
+	item = gtk_menu_item_new_with_label(_("Remove Buddy Pounce"));
+	gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
+
+	/* "Remove Buddy Pounce" menu */
+	remmenu = gtk_menu_new();
+
+	for (bp = gaim_get_pounces(); bp != NULL; bp = bp->next) {
+		pounce = (struct gaim_pounce *)bp->data;
+		buddy = gaim_pounce_get_pouncee(pounce);
+
+		item = gtk_menu_item_new_with_label(buddy);
+		gtk_menu_shell_append(GTK_MENU_SHELL(remmenu), item);
+		gtk_widget_show(item);
+
+		g_signal_connect(G_OBJECT(item), "activate",
+						 G_CALLBACK(edit_pounce_cb), pounce);
+	}
+
+	gtk_menu_item_set_submenu(GTK_MENU_ITEM(item), remmenu);
+	gtk_widget_show(remmenu);
+	gtk_widget_show(item);
+
+	/* Separator */
+	item = gtk_separator_menu_item_new();
+	gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
+	gtk_widget_show(item);
+
+	/* Pounces */
+	for (bp = gaim_get_pounces(); bp != NULL; bp = bp->next) {
+		struct gaim_gtkpounce_data *pounce_data;
+
+		pounce = (struct gaim_pounce *)bp->data;
+		buddy = gaim_pounce_get_pouncee(pounce);
+
+		pounce_data = GAIM_GTKPOUNCE(pounce);
+
+		item = gtk_menu_item_new_with_label(buddy);
+		gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
+		gtk_widget_show(item);
+
+		g_signal_connect(G_OBJECT(item), "activate",
+						 G_CALLBACK(edit_pounce_cb), pounce);
+	}
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/gtkpounce.h	Sat Apr 05 10:14:21 2003 +0000
@@ -0,0 +1,110 @@
+/**
+ * @file gtkpounce.h GTK+ buddy pounce API
+ *
+ * gaim
+ *
+ * Copyright (C) 2003, Christian Hammond <chipx86@gnupdate.org>
+ * 
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ *
+ */
+#ifndef _GAIM_GTK_POUNCE_H_
+#define _GAIM_GTK_POUNCE_H_
+
+#include "pounce.h"
+
+/**
+ * Types of actions that gaim's GTK+ interface supports.
+ */
+typedef enum
+{
+	GAIM_GTKPOUNCE_NONE       = 0x00, /**< No action.          */
+	GAIM_GTKPOUNCE_OPEN_WIN   = 0x01, /**< Open an IM window.  */
+	GAIM_GTKPOUNCE_POPUP      = 0x02, /**< Popup notification. */
+	GAIM_GTKPOUNCE_SEND_MSG   = 0x04, /**< Send a message.     */
+	GAIM_GTKPOUNCE_EXEC_CMD   = 0x08, /**< Execute a command.  */
+	GAIM_GTKPOUNCE_PLAY_SOUND = 0x10  /**< Play a sound.       */
+
+} GaimGtkPounceAction;
+
+/**
+ * GTK+ pounce-specific data.
+ */
+struct gaim_gtkpounce_data
+{
+	GaimGtkPounceAction actions;  /**< The action(s) for this pounce.      */
+
+	char *message;          /**< The message to send, if
+	                             GAIM_GTKPOUNCE_SEND_MSG is in actions.    */
+	char *command;          /**< The command to execute, if
+	                             GAIM_GTKPOUNCE_EXEC_CMD is in actions.    */
+	char *sound;            /**< The sound file to play, if
+	                             GAIM_GTKPOUNCE_PLAY_SOUND is in actions.  */
+
+	gboolean save;          /**< If TRUE, the pounce should be saved after
+	                             activation.                               */
+};
+
+#define GAIM_GTKPOUNCE(pounce) \
+	((struct gaim_gtkpounce_data *)gaim_pounce_get_data(pounce))
+
+/**
+ * The pounce dialog.
+ *
+ * The structure is opaque, as nobody should be touching anything inside of
+ * it.
+ */
+struct gaim_gtkpounce_dialog;
+
+/**
+ * Creates a GTK-specific pounce.
+ *
+ * @param pouncer The account that will pounce.
+ * @param pouncee The buddy to pounce on.
+ * @param events  The event(s) to pounce on.
+ * @param actions The actions.
+ * @param message The optional message to send.
+ * @param command The optional command to execute.
+ * @param sound   The optional sound to play.
+ * @param save    Whether or not to save the data.
+ *
+ * @return The new buddy pounce.
+ */
+struct gaim_pounce *gaim_gtkpounce_new(struct gaim_account *pouncer,
+									   const char *pouncee,
+									   GaimPounceEvent events,
+									   GaimGtkPounceAction actions,
+									   const char *message,
+									   const char *command,
+									   const char *sound,
+									   gboolean save);
+
+/**
+ * Displays a New Buddy Pounce or Edit Buddy Pounce dialog.
+ *
+ * @param buddy      The optional buddy to pounce on.
+ * @param cur_pounce The current buddy pounce, if editting an existing one.
+ */
+void gaim_gtkpounce_dialog_show(struct buddy *buddy,
+								struct gaim_pounce *cur_pounce);
+
+/**
+ * Displays all registered buddy pounces in a menu.
+ *
+ * @param menu The menu to add to.
+ */
+void gaim_gtkpounce_menu_build(GtkWidget *menu);
+
+#endif /* _GAIM_GTK_POUNCE_H_ */
--- a/src/main.c	Sat Apr 05 08:03:10 2003 +0000
+++ b/src/main.c	Sat Apr 05 10:14:21 2003 +0000
@@ -65,7 +65,6 @@
 static GtkWidget *pass;
 
 GList *log_conversations = NULL;
-GList *buddy_pounces = NULL;
 GSList *away_messages = NULL;
 GSList *message_queue = NULL;
 GSList *unread_message_queue = NULL;
@@ -872,6 +871,7 @@
 
 	load_prefs();
 	core_main();
+	load_pounces();
 	ui_main();
 
 #ifdef USE_SM
--- a/src/pounce.c	Sat Apr 05 08:03:10 2003 +0000
+++ b/src/pounce.c	Sat Apr 05 10:14:21 2003 +0000
@@ -1,8 +1,10 @@
-/*
+/**
+ * @file pounce.h Buddy pounce API
+ *
  * gaim
  *
- * Copyright (C) 1998-2003, Mark Spencer <markster@marko.net>
- *
+ * Copyright (C) 2003, Christian Hammond <chipx86@gnupdate.org>
+ * 
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
  * the Free Software Foundation; either version 2 of the License, or
@@ -18,598 +20,168 @@
  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
  *
  */
-
-#include <stdlib.h>
-#include <string.h>
-#include <sys/types.h>
-#include <unistd.h>
-
 #include "gaim.h"
-#include "prpl.h"
-#include "pounce.h"
-
-GtkWidget *bpmenu = NULL;
 
-void rem_bp(GtkWidget *w, struct buddy_pounce *b)
-{
-	buddy_pounces = g_list_remove(buddy_pounces, b);
-	do_bp_menu();
-	save_prefs();
-}
-
-void do_pounce(struct gaim_connection *gc, char *name, int when)
-{
-	char *who;
-
-	struct buddy_pounce *b;
-	struct gaim_conversation *c;
-	struct gaim_account *account;
-
-	GList *bp = buddy_pounces;
+static GList *pounces = NULL;
 
-	who = g_strdup(normalize (name));
-
-	while (bp) {
-		b = (struct buddy_pounce *)bp->data;
-		bp = bp->next;	/* increment the list here because rem_bp can make our handle bad */
-
-		if (!(b->options & when))
-			continue;
-
-		account = gaim_account_find(b->pouncer, b->protocol);	/* find our user */
-		if (account == NULL)
-			continue;
-
-		/* check and see if we're signed on as the pouncer */
-		if (account->gc != gc)
-			continue;
-
-		if (!gaim_utf8_strcasecmp(who, normalize (b->name))) {	/* find someone to pounce */
-			if (b->options & OPT_POUNCE_POPUP) {
-				c = gaim_find_conversation(name);
-
-				if (c == NULL)
-					c = gaim_conversation_new(GAIM_CONV_IM, account, name);
-				else
-					gaim_conversation_set_account(c, account);
-			}
-			if (b->options & OPT_POUNCE_NOTIFY) {
-				char tmp[1024];
-
-				/* I know the line below is really ugly. I only did it this way
-				 * because I thought it'd be funny :-) */
+struct gaim_pounce *
+gaim_pounce_new(struct gaim_account *pouncer, const char *pouncee,
+				GaimPounceEvent event, gaim_pounce_cb cb,
+				void *data, void (*free)(void *))
+{
+	struct gaim_pounce *pounce;
 
-				g_snprintf(tmp, sizeof(tmp), 
-					   (when & OPT_POUNCE_TYPING) ? _("%s has started typing to you") :
-					   (when & OPT_POUNCE_SIGNON) ? _("%s has signed on") : 
-					   (when & OPT_POUNCE_UNIDLE) ? _("%s has returned from being idle") : 
-					   _("%s has returned from being away"), name);
-				
-				do_error_dialog(tmp, NULL, GAIM_INFO);
-			}
-			if (b->options & OPT_POUNCE_SEND_IM) {
-				if (strlen(b->message) > 0) {
-					c = gaim_find_conversation(name);
+	if (pouncer == NULL || pouncee == NULL || event == 0 || cb == NULL)
+		return NULL;
 
-					if (c == NULL)
-						c = gaim_conversation_new(GAIM_CONV_IM, account, name);
-					else
-						gaim_conversation_set_account(c, account);
-
-					gaim_conversation_write(c, NULL, b->message, -1,
-											WFLAG_SEND, time(NULL));
-
-					serv_send_im(account->gc, name, b->message, -1, 0);
-				}
-			}
-			if (b->options & OPT_POUNCE_COMMAND) {
-#ifndef _WIN32
-				int pid = fork();
+	pounce = g_new0(struct gaim_pounce, 1);
 
-				if (pid == 0) {
-					char *args[4];
-					args[0] = "sh";
-					args[1] = "-c";
-					args[2] = b->command;
-					args[3] = NULL;
-					execvp(args[0], args);
-					_exit(0);
-				}
-#else
-				STARTUPINFO si;
-				PROCESS_INFORMATION pi;
-
-				ZeroMemory( &si, sizeof(si) );
-				si.cb = sizeof(si);
-				ZeroMemory( &pi, sizeof(pi) );
+	pounce->pouncer  = pouncer;
+	pounce->pouncee  = g_strdup(pouncee);
+	pounce->events   = event;
+	pounce->callback = cb;
+	pounce->data     = data;
+	pounce->free     = free;
 
-				CreateProcess( NULL, b->command, NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi );
-				CloseHandle( pi.hProcess );
-				CloseHandle( pi.hThread );
-#endif /*_WIN32*/
-			}
-			/*	if (b->options & OPT_POUNCE_SOUND) {
-				if (strlen(b->sound))
-					play_file(b->sound);
-				else
-					play_sound(SND_POUNCE_DEFAULT);
-					}*/
-
-			if (!(b->options & OPT_POUNCE_SAVE))
-				rem_bp(NULL, b);
+	pounces = g_list_append(pounces, pounce);
 
-		}
-	}
-	g_free(who);
-}
-
-static void new_bp_callback(GtkWidget *w, struct buddy *b)
-{
-	if (b)
-		show_new_bp(b->name, b->account->gc, b->idle, b->uc & UC_UNAVAILABLE, NULL);
-	else
-		show_new_bp(NULL, NULL, 0, 0, NULL);
-}
-
-static void edit_bp_callback(GtkWidget *w, struct buddy_pounce *b)
-{
-  show_new_bp(NULL, NULL, 0, 0, b);
+	return pounce;
 }
 
-static GtkTooltips *bp_tooltip = NULL;
-void do_bp_menu()
+void
+gaim_pounce_destroy(struct gaim_pounce *pounce)
 {
-	GtkWidget *menuitem, *mess, *messmenu;
-	static GtkWidget *remmenu;
-	GtkWidget *remitem;
-	GtkWidget *sep;
-	GList *l;
-	struct buddy_pounce *b;
-	GList *bp = buddy_pounces;
-
-	/* Tooltip for editing bp's */
-	if(!bp_tooltip)
-		bp_tooltip = gtk_tooltips_new();
-
-	l = gtk_container_get_children(GTK_CONTAINER(bpmenu));
-
-	while (l) {
-		gtk_widget_destroy(GTK_WIDGET(l->data));
-		l = l->next;
-	}
-
-	remmenu = gtk_menu_new();
-
-	menuitem = gtk_menu_item_new_with_label(_("New Buddy Pounce"));
-	gtk_menu_shell_append(GTK_MENU_SHELL(bpmenu), menuitem);
-	gtk_widget_show(menuitem);
-	g_signal_connect(GTK_OBJECT(menuitem), "activate", G_CALLBACK(new_bp_callback), NULL);
-
-
-	while (bp) {
-
-		b = (struct buddy_pounce *)bp->data;
-		remitem = gtk_menu_item_new_with_label(b->name);
-		gtk_menu_shell_append(GTK_MENU_SHELL(remmenu), remitem);
-		gtk_widget_show(remitem);
-		g_signal_connect(GTK_OBJECT(remitem), "activate", G_CALLBACK(rem_bp), b);
-
-		bp = bp->next;
+	if (pounce == NULL)
+		return;
 
-	}
-
-	menuitem = gtk_menu_item_new_with_label(_("Remove Buddy Pounce"));
-	gtk_menu_shell_append(GTK_MENU_SHELL(bpmenu), menuitem);
-	gtk_widget_show(menuitem);
-	gtk_menu_item_set_submenu(GTK_MENU_ITEM(menuitem), remmenu);
-	gtk_widget_show(remmenu);
+	if (pounce->pouncee != NULL)
+		g_free(pounce->pouncee);
 
-	sep = gtk_hseparator_new();
-	menuitem = gtk_menu_item_new();
-	gtk_menu_shell_append(GTK_MENU_SHELL(bpmenu), menuitem);
-	gtk_container_add(GTK_CONTAINER(menuitem), sep);
-	gtk_widget_set_sensitive(menuitem, FALSE);
-	gtk_widget_show(menuitem);
-	gtk_widget_show(sep);
-
-	bp = buddy_pounces;
-
-	while (bp) {
+	pounces = g_list_remove(pounces, pounce);
 
-		b = (struct buddy_pounce *)bp->data;
-
-		menuitem = gtk_menu_item_new_with_label(b->name);
-		gtk_menu_shell_append(GTK_MENU_SHELL(bpmenu), menuitem);
-		messmenu = gtk_menu_new();
-		gtk_menu_item_set_submenu(GTK_MENU_ITEM(menuitem), messmenu);
-		gtk_widget_show(menuitem);
+	if (pounce->free != NULL && pounce->data != NULL)
+		pounce->free(pounce->data);
 
-		if (strlen(b->message))
-			mess = gtk_menu_item_new_with_label(b->message);
-		else
-			mess = gtk_menu_item_new_with_label(_("[no message]"));
-		gtk_menu_shell_append(GTK_MENU_SHELL(messmenu), mess);
-		gtk_tooltips_set_tip(bp_tooltip, GTK_WIDGET(mess), _("[Click to edit]"), NULL);
-		gtk_widget_show(mess);
-		g_signal_connect(GTK_OBJECT(mess), "activate", G_CALLBACK(edit_bp_callback), b);
-		bp = bp->next;
-
-	}
-
+	g_free(pounce);
 }
 
-/*------------------------------------------------------------------------*/
-/*  The dialog for new buddy pounces                                      */
-/*------------------------------------------------------------------------*/
-
-
-void do_new_bp(GtkWidget *w, struct addbp *b)
+void
+gaim_pounce_set_events(struct gaim_pounce *pounce, GaimPounceEvent events)
 {
-	struct buddy_pounce *bp;
-	
-	if (strlen(gtk_entry_get_text(GTK_ENTRY(b->nameentry))) == 0) {
-		do_error_dialog(_("Please enter a buddy to pounce."), NULL, GAIM_ERROR);
+	if (pounce == NULL || events == GAIM_POUNCE_NONE)
 		return;
-	}
-
-        if(!b->buddy_pounce)
-		bp = g_new0(struct buddy_pounce, 1);
-	else
-		bp = b->buddy_pounce;
-
-	
-	g_snprintf(bp->name, 80, "%s", gtk_entry_get_text(GTK_ENTRY(b->nameentry)));
-	g_snprintf(bp->message, 2048, "%s", gtk_entry_get_text(GTK_ENTRY(b->messentry)));
-	g_snprintf(bp->command, 2048, "%s", gtk_entry_get_text(GTK_ENTRY(b->commentry)));
-	g_snprintf(bp->sound, 2048, "%s", gtk_entry_get_text(GTK_ENTRY(b->soundentry)));
-	g_snprintf(bp->pouncer, 80, "%s", b->account->username);
-
-	bp->protocol = b->account->protocol;
-
-	bp->options = 0;
-
-	if (GTK_TOGGLE_BUTTON(b->popupnotify)->active)
-		bp->options |= OPT_POUNCE_NOTIFY;
 
-	if (GTK_TOGGLE_BUTTON(b->openwindow)->active)
-		bp->options |= OPT_POUNCE_POPUP;
-
-	if (GTK_TOGGLE_BUTTON(b->sendim)->active)
-		bp->options |= OPT_POUNCE_SEND_IM;
-
-	if (GTK_TOGGLE_BUTTON(b->command)->active)
-		bp->options |= OPT_POUNCE_COMMAND;
-
-	if (GTK_TOGGLE_BUTTON(b->sound)->active)
-		bp->options |= OPT_POUNCE_SOUND;
-
-	if (GTK_TOGGLE_BUTTON(b->p_signon)->active)
-		bp->options |= OPT_POUNCE_SIGNON;
-
-	if (GTK_TOGGLE_BUTTON(b->p_unaway)->active)
-		bp->options |= OPT_POUNCE_UNAWAY;
-
-	if (GTK_TOGGLE_BUTTON(b->p_unidle)->active)
-		bp->options |= OPT_POUNCE_UNIDLE;
-	
-	if (GTK_TOGGLE_BUTTON(b->p_typing)->active)
-		bp->options |= OPT_POUNCE_TYPING;
-
-	if (GTK_TOGGLE_BUTTON(b->save)->active)
-		bp->options |= OPT_POUNCE_SAVE;
-
-	if(!b->buddy_pounce)
-		buddy_pounces = g_list_append(buddy_pounces, bp);
-
-	do_bp_menu();
-
-	gtk_widget_destroy(b->window);
-
-	save_prefs();
-	g_free(b);
+	pounce->events = events;
 }
 
-static void pounce_choose(GtkWidget *opt, struct addbp *b)
+void
+gaim_pounce_set_pouncer(struct gaim_pounce *pounce,
+						struct gaim_account *pouncer)
 {
-	struct gaim_account *account = g_object_get_data(G_OBJECT(opt), "gaim_account");
-	b->account = account;
+	if (pounce == NULL || pouncer == NULL)
+		return;
+
+	pounce->pouncer = pouncer;
+}
+
+void
+gaim_pounce_set_pouncee(struct gaim_pounce *pounce, const char *pouncee)
+{
+	if (pounce == NULL || pouncee == NULL)
+		return;
+
+	if (pounce->pouncee != NULL)
+		g_free(pounce->pouncee);
+
+	pounce->pouncee = (pouncee == NULL ? NULL : g_strdup(pouncee));
 }
 
-static GtkWidget *pounce_user_menu(struct addbp *b, struct gaim_connection *gc)
+void
+gaim_pounce_set_data(struct gaim_pounce *pounce, void *data)
 {
-	GtkWidget *optmenu;
-	GtkWidget *menu;
-	GtkWidget *opt;
-	GSList *u = gaim_accounts;
-	struct gaim_account *account;
-	struct prpl *p;
-	int count = 0;
-	int place = 0;
-	char buf[2048];
+	if (pounce == NULL)
+		return;
+
+	pounce->data = data;
+}
 
-
-	optmenu = gtk_option_menu_new();
-
-	menu = gtk_menu_new();
+GaimPounceEvent
+gaim_pounce_get_events(const struct gaim_pounce *pounce)
+{
+	if (pounce == NULL)
+		return GAIM_POUNCE_NONE;
 
-	while (u) {
-		account = (struct gaim_account *)u->data;
-		p = (struct prpl *)find_prpl(account->protocol);
-		g_snprintf(buf, sizeof buf, "%s (%s)", account->username, (p && p->name)?p->name:_("Unknown"));
-		opt = gtk_menu_item_new_with_label(buf);
-		g_object_set_data(G_OBJECT(opt), "gaim_account", account);
-		g_signal_connect(GTK_OBJECT(opt), "activate", G_CALLBACK(pounce_choose), b);
-		gtk_menu_shell_append(GTK_MENU_SHELL(menu), opt);
-		gtk_widget_show(opt);
+	return pounce->events;
+}
+
+struct gaim_account *
+gaim_pounce_get_pouncer(const struct gaim_pounce *pounce)
+{
+	if (pounce == NULL)
+		return NULL;
 
-		if (b->account == account) {
-			gtk_menu_item_activate(GTK_MENU_ITEM(opt));
-			place = count;
-		}
-
-		count++;
+	return pounce->pouncer;
+}
 
-		u = u->next;
-	}
+const char *
+gaim_pounce_get_pouncee(const struct gaim_pounce *pounce)
+{
+	if (pounce == NULL)
+		return NULL;
 
-	gtk_option_menu_set_menu(GTK_OPTION_MENU(optmenu), menu);
-	gtk_option_menu_set_history(GTK_OPTION_MENU(optmenu), place);
-
-	b->menu = optmenu;
-
-	return optmenu;
+	return pounce->pouncee;
 }
 
+void *
+gaim_pounce_get_data(const struct gaim_pounce *pounce)
+{
+	if (pounce == NULL)
+		return NULL;
 
-void show_new_bp(char *name, struct gaim_connection *gc, int idle, int away, struct buddy_pounce *edit_bp)
+	return pounce->data;
+}
+
+void
+gaim_pounce_execute(const struct gaim_account *pouncer,
+					const char *pouncee, GaimPounceEvent events)
 {
-	GtkWidget *label;
-	GtkWidget *bbox;
-	GtkWidget *vbox;
-	GtkWidget *button;
-	GtkWidget *frame;
-	GtkWidget *table;
-	GtkWidget *optmenu;
-	GtkWidget *sep;
-	GtkSizeGroup *sg;
+	struct gaim_pounce *pounce;
+
+	if (events == GAIM_POUNCE_NONE || pouncer == NULL || pouncee == NULL)
+		return;
+
+	pounce = gaim_find_pounce(pouncer, pouncee, events);
+
+	if (pounce != NULL && pounce->callback != NULL)
+		pounce->callback(pounce, events, gaim_pounce_get_data(pounce));
+}
 
-	struct addbp *b = g_new0(struct addbp, 1);
-	
-	if(edit_bp) {
-		b->buddy_pounce = edit_bp;
-		b->account = gaim_account_find(edit_bp->pouncer, edit_bp->protocol);
-	} else {
-		b->account = gc ? gc->account : gaim_accounts->data;
-		b->buddy_pounce = NULL;
+struct gaim_pounce *
+gaim_find_pounce(const struct gaim_account *pouncer,
+				 const char *pouncee, GaimPounceEvent events)
+{
+	struct gaim_pounce *pounce;
+	GList *l;
+
+	if (events == GAIM_POUNCE_NONE || pouncer == NULL || pouncee == NULL)
+		return NULL;
+
+	for (l = gaim_get_pounces(); l != NULL; l = l->next) {
+		pounce = (struct gaim_pounce *)l->data;
+
+		if ((gaim_pounce_get_events(pounce) & events) &&
+			(gaim_pounce_get_pouncer(pounce) == pouncer) &&
+			!strcmp(gaim_pounce_get_pouncee(pounce), pouncee)) {
+
+			return pounce;
+		}
 	}
 
-	GAIM_DIALOG(b->window);
-	gtk_window_set_resizable(GTK_WINDOW(b->window), TRUE);
-	gtk_window_set_role(GTK_WINDOW(b->window), "new_bp");
-	gtk_window_set_title(GTK_WINDOW(b->window), _("New Buddy Pounce"));
-	g_signal_connect(GTK_OBJECT(b->window), "destroy", G_CALLBACK(gtk_widget_destroy), b->window);
-	gtk_widget_realize(b->window);
-
-	vbox = gtk_vbox_new(FALSE, 5);
-	gtk_container_set_border_width(GTK_CONTAINER(vbox), 5);
-	gtk_container_add(GTK_CONTAINER(b->window), vbox);
-	gtk_widget_show(vbox);
-
-	/* <pounce type="who"> */
-	frame = gtk_frame_new(_("Pounce Who"));
-	gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 0);
-	gtk_widget_show(GTK_WIDGET(frame));
-
-	table = gtk_table_new(2, 2, FALSE);
-	gtk_container_add(GTK_CONTAINER(frame), table);
-	gtk_container_set_border_width(GTK_CONTAINER(table), 5);
-	gtk_table_set_col_spacings(GTK_TABLE(table), 5);
-	gtk_table_set_row_spacings(GTK_TABLE(table), 5);
-	gtk_widget_show(table);
-
-	label = gtk_label_new(_("Account"));
-	gtk_misc_set_alignment(GTK_MISC(label), 0, .5);
-	gtk_table_attach(GTK_TABLE(table), label, 0, 1, 0, 1, GTK_FILL, 0, 0, 0);
-	gtk_widget_show(label);
-
-	optmenu = pounce_user_menu(b, gc);
-	gtk_table_attach(GTK_TABLE(table), optmenu, 1, 2, 0, 1, GTK_FILL | GTK_EXPAND, 0, 0, 0);
-	gtk_widget_show(optmenu);
-
-	label = gtk_label_new(_("Buddy"));
-	gtk_misc_set_alignment(GTK_MISC(label), 0, .5);
-	gtk_table_attach(GTK_TABLE(table), label, 0, 1, 1, 2, GTK_FILL, 0, 0, 0);
-	gtk_widget_show(label);
-
-	b->nameentry = gtk_entry_new();
-	gtk_table_attach(GTK_TABLE(table), b->nameentry, 1, 2, 1, 2, GTK_FILL | GTK_EXPAND, 0, 0, 0);
-	if (name !=NULL)
-		gtk_entry_set_text(GTK_ENTRY(b->nameentry), name);
-	else if(edit_bp)
-		gtk_entry_set_text(GTK_ENTRY(b->nameentry), edit_bp->name);
-	gtk_window_set_focus(GTK_WINDOW(b->window), b->nameentry);
-	gtk_widget_show(b->nameentry);
-	/* </pounce type="who"> */
-
-
-	/* <pounce type="when"> */
-	frame = gtk_frame_new(_("Pounce When"));
-	gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 0);
-	gtk_widget_show(GTK_WIDGET(frame));
-
-	table = gtk_table_new(2, 2, FALSE);
-	gtk_container_add(GTK_CONTAINER(frame), table);
-	gtk_container_set_border_width(GTK_CONTAINER(table), 5);
-	gtk_table_set_col_spacings(GTK_TABLE(table), 5);
-	gtk_widget_show(table);
-	
-	b->p_signon = gtk_check_button_new_with_label(_("Pounce on sign on"));
-	if(edit_bp)
-		gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(b->p_signon), 
-		                           (edit_bp->options & OPT_POUNCE_SIGNON) ? TRUE : FALSE);
-	else
-		gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(b->p_signon), TRUE);
-	gtk_table_attach(GTK_TABLE(table), b->p_signon, 0, 1, 0, 1, GTK_FILL, 0, 0, 0);
-	gtk_widget_show(b->p_signon);
-
-	b->p_unaway = gtk_check_button_new_with_label(_("Pounce on return from away"));
-	if (away)
-		gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(b->p_unaway), TRUE);
-	else if(edit_bp)
-		gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(b->p_unaway), 
-					   (edit_bp->options & OPT_POUNCE_UNAWAY) ? TRUE : FALSE);
-	gtk_table_attach(GTK_TABLE(table), b->p_unaway, 1, 2, 0, 1, GTK_FILL | GTK_EXPAND, 0, 0, 0);
-	gtk_widget_show(b->p_unaway);
-
-	b->p_unidle = gtk_check_button_new_with_label(_("Pounce on return from idle"));
-	if (idle)
-		gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(b->p_unidle), TRUE);
-	else if(edit_bp)
-		gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(b->p_unidle), 
-				           (edit_bp->options & OPT_POUNCE_UNIDLE) ? TRUE : FALSE);
-	gtk_table_attach(GTK_TABLE(table), b->p_unidle, 0, 1, 1, 2, GTK_FILL, 0, 0, 0);
-	gtk_widget_show(b->p_unidle);
-	
-	b->p_typing = gtk_check_button_new_with_label(_("Pounce when buddy is typing to you"));
-	if (edit_bp)		
-		gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(b->p_typing),
-					   (edit_bp->options & OPT_POUNCE_TYPING) ? TRUE : FALSE);
-	gtk_table_attach(GTK_TABLE(table), b->p_typing,1,2,1,2, GTK_FILL | GTK_EXPAND, 0, 0, 0);
-	gtk_widget_show(b->p_typing);
-
-	/* </pounce type="when"> */
-	
-	/* <pounce type="action"> */
-	frame = gtk_frame_new(_("Pounce Action"));
-	gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 0);
-	gtk_widget_show(GTK_WIDGET(frame));
-
-	table = gtk_table_new(4, 2, FALSE);
-	gtk_container_add(GTK_CONTAINER(frame), table);
-	gtk_container_set_border_width(GTK_CONTAINER(table), 5);
-	gtk_table_set_col_spacings(GTK_TABLE(table), 5);
-	gtk_table_set_row_spacings(GTK_TABLE(table), 5);
-	gtk_widget_show(table);
-	
-	b->openwindow = gtk_check_button_new_with_label(_("Open IM Window"));
-	if(edit_bp)
-		gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(b->openwindow), 
-			                   (edit_bp->options & OPT_POUNCE_POPUP) ? TRUE : FALSE);
-	else
-		gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(b->openwindow), FALSE);
-	gtk_table_attach(GTK_TABLE(table), b->openwindow, 0, 1, 0, 1, GTK_FILL, 0, 0, 0);
-	gtk_widget_show(b->openwindow);
-	
-	b->popupnotify = gtk_check_button_new_with_label(_("Popup Notification"));
-	if(edit_bp)
-		gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(b->popupnotify), 
-					   (edit_bp->options & OPT_POUNCE_NOTIFY) ? TRUE : FALSE);
-	else
-		gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(b->popupnotify), FALSE);
-	gtk_table_attach(GTK_TABLE(table), b->popupnotify, 1, 2, 0, 1, GTK_FILL, 0, 0, 0);
-	gtk_widget_show(b->popupnotify);
-
-	b->sendim = gtk_check_button_new_with_label(_("Send Message"));
-	if(edit_bp)
-		gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(b->sendim), 
-					   (edit_bp->options & OPT_POUNCE_SEND_IM) ? TRUE : FALSE);
-	else
-		gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(b->sendim), TRUE);
-	gtk_table_attach(GTK_TABLE(table), b->sendim, 0, 1, 1, 2, GTK_FILL, 0, 0, 0);
-	gtk_widget_show(b->sendim);
-
-	b->messentry = gtk_entry_new();
-	gtk_table_attach(GTK_TABLE(table), b->messentry, 1, 2, 1, 2, GTK_FILL | GTK_EXPAND, 0, 0, 0);
-	g_signal_connect(GTK_OBJECT(b->messentry), "activate", G_CALLBACK(do_new_bp), b);
-	if(edit_bp) {
-		gtk_widget_set_sensitive(GTK_WIDGET(b->messentry), 
-					(edit_bp->options & OPT_POUNCE_SEND_IM) ? TRUE : FALSE);
-		gtk_entry_set_text(GTK_ENTRY(b->messentry), edit_bp->message);
-	}
-	gtk_widget_show(b->messentry);
-
-	g_signal_connect(GTK_OBJECT(b->sendim), "clicked",
-					 G_CALLBACK(gaim_gtk_toggle_sensitive), b->messentry);
-
-	b->command = gtk_check_button_new_with_label(_("Execute command on pounce"));
-	gtk_table_attach(GTK_TABLE(table), b->command, 0, 1, 2, 3, GTK_FILL, 0, 0, 0);
-	if(edit_bp)
-		gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(b->command),
-					   (edit_bp->options & OPT_POUNCE_COMMAND) ? TRUE : FALSE);
-	else
-		gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(b->command), FALSE);
-	gtk_widget_show(b->command);
-
-	b->commentry = gtk_entry_new();
-	gtk_table_attach(GTK_TABLE(table), b->commentry, 1, 2, 2, 3, GTK_FILL | GTK_EXPAND, 0, 0, 0);
-	g_signal_connect(GTK_OBJECT(b->commentry), "activate", G_CALLBACK(do_new_bp), b);
-	if(edit_bp) {
-		gtk_widget_set_sensitive(GTK_WIDGET(b->commentry), 
-					(edit_bp->options & OPT_POUNCE_COMMAND) ? TRUE : FALSE);
-		gtk_entry_set_text(GTK_ENTRY(b->commentry), edit_bp->command);
-	}
-	else
-		gtk_widget_set_sensitive(GTK_WIDGET(b->commentry), FALSE);
-	gtk_widget_show(b->commentry);
-	g_signal_connect(GTK_OBJECT(b->command), "clicked",
-					 G_CALLBACK(gaim_gtk_toggle_sensitive), b->commentry);
-	
-	b->sound = gtk_check_button_new_with_label(_("Play sound on pounce"));
-	if(edit_bp)
-		gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(b->sound), 
-					   (edit_bp->options & OPT_POUNCE_SOUND) ? TRUE : FALSE);
-	else
-		gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(b->sound), FALSE);
-	gtk_table_attach(GTK_TABLE(table), b->sound, 0, 1, 3, 4, GTK_FILL, 0, 0, 0);
-	gtk_widget_show(b->sound);
-
-	b->soundentry = gtk_entry_new();
-	gtk_table_attach(GTK_TABLE(table), b->soundentry, 1, 2, 3, 4, GTK_FILL | GTK_EXPAND, 0, 0, 0);
-	g_signal_connect(GTK_OBJECT(b->soundentry), "activate", G_CALLBACK(do_new_bp), b);
-	if(edit_bp) {
-		gtk_widget_set_sensitive(GTK_WIDGET(b->soundentry), 
-					(edit_bp->options & OPT_POUNCE_SOUND) ? TRUE : FALSE);
-		gtk_entry_set_text(GTK_ENTRY(b->soundentry), edit_bp->sound);
-	} else 
-		gtk_widget_set_sensitive(GTK_WIDGET(b->soundentry), FALSE);
-	gtk_widget_show(b->soundentry);
-	g_signal_connect(GTK_OBJECT(b->sound), "clicked",
-					 G_CALLBACK(gaim_gtk_toggle_sensitive), b->soundentry);
-	/* </pounce type="action"> */
-
-	b->save = gtk_check_button_new_with_label(_("Save this pounce after activation"));
-	gtk_container_set_border_width(GTK_CONTAINER(b->save), 7);
-	if(edit_bp)
-		gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(b->save),
-					   (edit_bp->options & OPT_POUNCE_SAVE) ? TRUE : FALSE);
-	else
-		gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(b->save), FALSE);
-	gtk_box_pack_start(GTK_BOX(vbox), b->save, FALSE, FALSE, 0);
-	gtk_widget_show(b->save);
-
-	sep = gtk_hseparator_new();
-	gtk_box_pack_start(GTK_BOX(vbox), sep, FALSE, FALSE, 0);
-	gtk_widget_show(sep);
-	
-	bbox = gtk_hbox_new(FALSE, 5);
-	gtk_box_pack_start(GTK_BOX(vbox), bbox, FALSE, FALSE, 0);
-	gtk_widget_show(bbox);
-
-	sg = gtk_size_group_new(GTK_SIZE_GROUP_HORIZONTAL);
-
-	button = gaim_pixbuf_button_from_stock(_("_Save"), "gtk-execute", GAIM_BUTTON_HORIZONTAL);
-	gtk_size_group_add_widget(sg, button);
-	g_signal_connect(GTK_OBJECT(button), "clicked", G_CALLBACK(do_new_bp), b);
-	gtk_box_pack_end(GTK_BOX(bbox), button, FALSE, FALSE, 0);
-	gtk_widget_show(button);
-
-	button = gaim_pixbuf_button_from_stock(_("C_ancel"), "gtk-cancel", GAIM_BUTTON_HORIZONTAL);
-	gtk_size_group_add_widget(sg, button);
-	g_signal_connect_swapped(GTK_OBJECT(button), "clicked", G_CALLBACK(gtk_widget_destroy), b->window);
-	gtk_box_pack_end(GTK_BOX(bbox), button, FALSE, FALSE, 0);
-	gtk_widget_show(button);
-
-
-	gtk_widget_show(b->window);
+	return NULL;
 }
 
+GList *
+gaim_get_pounces(void)
+{
+	return pounces;
+}
--- a/src/pounce.h	Sat Apr 05 08:03:10 2003 +0000
+++ b/src/pounce.h	Sat Apr 05 10:14:21 2003 +0000
@@ -1,8 +1,10 @@
-/*
+/**
+ * @file pounce.h Buddy pounce API
+ *
  * gaim
  *
- * Copyright (C) 1998-2003, Mark Spencer <markster@marko.net>
- *
+ * Copyright (C) 2003, Christian Hammond <chipx86@gnupdate.org>
+ * 
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
  * the Free Software Foundation; either version 2 of the License, or
@@ -18,48 +20,184 @@
  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
  *
  */
+#ifndef _GAIM_POUNCE_H_
+#define _GAIM_POUNCE_H_
 
-#ifndef _POUNCE_H_
-#define _POUNCE_H_
+/**
+ * Events that trigger buddy pounces.
+ */
+typedef enum
+{
+	GAIM_POUNCE_NONE           = 0x00, /**< No events.                    */
+	GAIM_POUNCE_SIGNON         = 0x01, /**< The buddy signed on.          */
+	GAIM_POUNCE_SIGNOFF        = 0x02, /**< The buddy signed off.         */
+	GAIM_POUNCE_AWAY           = 0x04, /**< The buddy went away.          */
+	GAIM_POUNCE_AWAY_RETURN    = 0x08, /**< The buddy returned from away. */
+	GAIM_POUNCE_IDLE           = 0x10, /**< The buddy became idle.        */
+	GAIM_POUNCE_IDLE_RETURN    = 0x20, /**< The buddy is no longer idle.  */
+	GAIM_POUNCE_TYPING         = 0x40, /**< The buddy started typing.     */
+	GAIM_POUNCE_TYPING_STOPPED = 0x80  /**< The buddy stopped typing.     */
+
+} GaimPounceEvent;
+
+struct gaim_pounce;
 
-struct buddy_pounce {
-        char name[80];
-        char message[2048];
-        char command[2048];
-        char sound[2048];
-        
-        char pouncer[80];
-        int protocol;
+/** A pounce callback. */
+typedef void (*gaim_pounce_cb)(struct gaim_pounce *, GaimPounceEvent, void *);
 
-        int options;
+/**
+ * A buddy pounce structure.
+ *
+ * Buddy pounces are actions triggered by a buddy-related event. For
+ * example, a sound can be played or an IM window opened when a buddy
+ * signs on or returns from away. Such responses are handled in the
+ * UI. The events themselves are done in the core.
+ */
+struct gaim_pounce
+{
+	GaimPounceEvent events;       /**< The event(s) to pounce on. */
+	struct gaim_account *pouncer; /**< The user who is pouncing.  */
+
+	char *pouncee;                /**< The buddy to pounce on.    */
+
+	gaim_pounce_cb callback;      /**< The callback function to call when the
+	                                   event is triggered. */
+	void (*free)(void *data);     /**< The data free function. */
+	void *data;                   /**< Pounce-specific data. */
 };
 
-struct addbp {
-	GtkWidget *window;
-	GtkWidget *nameentry;
-	GtkWidget *messentry;
-	GtkWidget *commentry;
-	GtkWidget *command;
-	GtkWidget *sendim;
-	GtkWidget *openwindow;
-	GtkWidget *popupnotify;
-	GtkWidget *p_signon;
-	GtkWidget *p_unaway;
-	GtkWidget *p_unidle;
-	GtkWidget *p_typing;
-	GtkWidget *save;
-	GtkWidget *menu;
-	GtkWidget *sound;
-	GtkWidget *soundentry;
+/**
+ * Creates a new buddy pounce.
+ *
+ * @param pouncer The account that will pounce.
+ * @param pouncee The buddy to pounce on.
+ * @param event   The event(s) to pounce on.
+ * @param cb      The callback function to call when the pounce is triggered.
+ * @param data    Pounce-specific data.
+ * @param free    The function to free the pounce-specific data.
+ *
+ * @return The new buddy pounce structure.
+ */
+struct gaim_pounce *gaim_pounce_new(struct gaim_account *pouncer,
+									const char *pouncee,
+									GaimPounceEvent event,
+									gaim_pounce_cb cb, void *data,
+									void (*free)(void *));
+
+/**
+ * Destroys a buddy pounce.
+ *
+ * @param pounce The buddy pounce.
+ */
+void gaim_pounce_destroy(struct gaim_pounce *pounce);
+
+/**
+ * Sets the events a pounce should watch for.
+ *
+ * @param pounce The buddy pounce.
+ * @param events The events to watch for.
+ */
+void gaim_pounce_set_events(struct gaim_pounce *pounce,
+							GaimPounceEvent events);
+
+/**
+ * Sets the account that will do the pouncing.
+ *
+ * @param pounce  The buddy pounce.
+ * @param pouncer The account that will pounce.
+ */
+void gaim_pounce_set_pouncer(struct gaim_pounce *pounce,
+							 struct gaim_account *pouncer);
+
+/**
+ * Sets the buddy a pounce should pounce on.
+ *
+ * @param pounce  The buddy pounce.
+ * @param pouncee The buddy to pounce on.
+ */
+void gaim_pounce_set_pouncee(struct gaim_pounce *pounce, const char *buddy);
+
+/**
+ * Sets the callback function to call when the pounce event is triggered.
+ *
+ * @param pounce The buddy pounce.
+ * @param cb     The callback function.
+ */
+void gaim_pounce_set_callback(struct gaim_pounce *pounce, gaim_pounce_cb cb);
+
+/**
+ * Sets the pounce-specific data.
+ *
+ * @param pounce The buddy pounce.
+ * @param data   Data specific to the pounce.
+ */
+void gaim_pounce_set_data(struct gaim_pounce *pounce, void *data);
 
-	struct gaim_account *account;
-	struct buddy_pounce *buddy_pounce;
-};
+/**
+ * Returns the events a pounce should watch for.
+ *
+ * @param pounce The buddy pounce.
+ *
+ * @return The events the pounce is watching for.
+ */
+GaimPounceEvent gaim_pounce_get_events(const struct gaim_pounce *pounce);
 
-void rem_bp(GtkWidget *w, struct buddy_pounce *b);
+/**
+ * Returns the account that will do the pouncing.
+ *
+ * @param pounce The buddy pounce.
+ *
+ * @return The account that will pounce.
+ */
+struct gaim_account *gaim_pounce_get_pouncer(const struct gaim_pounce *pounce);
+
+/**
+ * Returns the buddy a pounce should pounce on.
+ *
+ * @param pounce The buddy pounce.
+ *
+ * @return The buddy to pounce on.
+ */
+const char *gaim_pounce_get_pouncee(const struct gaim_pounce *pounce);
 
-void do_pounce(struct gaim_connection *gc, char *name, int when);
+/**
+ * Returns the pounce-specific data.
+ *
+ * @param pounce The buddy pounce.
+ *
+ * @return The data specific to a buddy pounce.
+ */
+void *gaim_pounce_get_data(const struct gaim_pounce *pounce);
+
+/**
+ * Executes a pounce with the specified pouncer, pouncee, and event type.
+ *
+ * @param pouncer The account that will do the pouncing.
+ * @param pouncee The buddy that is being pounced.
+ * @param events  The events that triggered the pounce.
+ */
+void gaim_pounce_execute(const struct gaim_account *pouncer,
+						 const char *pouncee,
+						 GaimPounceEvent events);
 
-void do_bp_menu();
+/**
+ * Finds a pounce with the specified event(s) and buddy.
+ *
+ * @param pouncer The account to match against.
+ * @param buddy   The buddy to match against.
+ * @param events  The event(s) to match against.
+ *
+ * @return The pounce if found, or @c NULL otherwise.
+ */
+struct gaim_pounce *gaim_find_pounce(const struct gaim_account *pouncer,
+									 const char *pouncee,
+									 GaimPounceEvent events);
 
-#endif /* _POUNCE_H_ */
+/**
+ * Returns a list of all registered buddy pounces.
+ *
+ * @return The list of buddy pounces.
+ */
+GList *gaim_get_pounces(void);
+
+#endif /* _GAIM_POUNCE_H_ */
--- a/src/server.c	Sat Apr 05 08:03:10 2003 +0000
+++ b/src/server.c	Sat Apr 05 10:14:21 2003 +0000
@@ -862,11 +862,12 @@
 	}
 
 	if (!b->idle && idle) {
+		gaim_pounce_execute(gc->account, b->name, GAIM_POUNCE_IDLE);
 		plugin_event(event_buddy_idle, gc, b->name);
 		system_log(log_idle, gc, b, OPT_LOG_BUDDY_IDLE);
 	}
 	if (b->idle && !idle) {
-		do_pounce(gc, b->name, OPT_POUNCE_UNIDLE);
+		gaim_pounce_execute(gc->account, b->name, GAIM_POUNCE_IDLE_RETURN);
 		plugin_event(event_buddy_unidle, gc, b->name);
 		system_log(log_unidle, gc, b, OPT_LOG_BUDDY_IDLE);
 	}
@@ -875,10 +876,11 @@
 	gaim_blist_update_buddy_evil(b, evil);
 
 	if ((b->uc & UC_UNAVAILABLE) && !(type & UC_UNAVAILABLE)) {
-		do_pounce(gc, b->name, OPT_POUNCE_UNAWAY);
+		gaim_pounce_execute(gc->account, b->name, GAIM_POUNCE_AWAY_RETURN);
 		plugin_event(event_buddy_back, gc, b->name);
 		system_log(log_back, gc, b, OPT_LOG_BUDDY_AWAY);
 	} else if (!(b->uc & UC_UNAVAILABLE) && (type & UC_UNAVAILABLE)) {
+		gaim_pounce_execute(gc->account, b->name, GAIM_POUNCE_AWAY);
 		plugin_event(event_buddy_away, gc, b->name);
 		system_log(log_away, gc, b, OPT_LOG_BUDDY_AWAY);
 	}
@@ -906,7 +908,7 @@
 				message_queue = g_slist_append(message_queue, qm);
 			}
 			gaim_sound_play_event(GAIM_SOUND_BUDDY_ARRIVE);
-			do_pounce(gc, b->name, OPT_POUNCE_SIGNON);
+			gaim_pounce_execute(gc->account, b->name, GAIM_POUNCE_SIGNON);
 			plugin_event(event_buddy_signon, gc, b->name);
 			system_log(log_signon, gc, b, OPT_LOG_BUDDY_SIGNON);
 		}
@@ -930,6 +932,7 @@
 				message_queue = g_slist_append(message_queue, qm);
 			}
 			gaim_sound_play_event(GAIM_SOUND_BUDDY_LEAVE);
+			gaim_pounce_execute(gc->account, b->name, GAIM_POUNCE_SIGNOFF);
 			plugin_event(event_buddy_signoff, gc, b->name);
 			system_log(log_signoff, gc, b, OPT_LOG_BUDDY_SIGNON);
 		}
@@ -965,6 +968,7 @@
 void serv_got_typing(struct gaim_connection *gc, char *name, int timeout,
 					 int state) {
 
+	struct buddy *b;
 	struct gaim_conversation *cnv = gaim_find_conversation(name);
 	struct gaim_im *im;
 
@@ -977,8 +981,12 @@
 	gaim_im_set_typing_state(im, state);
 	gaim_im_update_typing(im);
 
+	b = gaim_find_buddy(gc->account, name);
+
 	plugin_event(event_got_typing, gc, name);
-	do_pounce(gc, name, OPT_POUNCE_TYPING);
+
+	if (b != NULL)
+		gaim_pounce_execute(gc->account, name, GAIM_POUNCE_TYPING);
 
 	if (timeout > 0)
 		gaim_im_start_typing_timeout(im, timeout);
@@ -988,6 +996,7 @@
 
 	struct gaim_conversation *c = gaim_find_conversation(name);
 	struct gaim_im *im;
+	struct buddy *b;
 
 	if (!c)
 		return;
@@ -997,6 +1006,11 @@
 	gaim_im_stop_typing_timeout(im);
 	gaim_im_set_typing_state(im, NOT_TYPING);
 	gaim_im_update_typing(im);
+
+	b = gaim_find_buddy(gc->account, name);
+
+	if (b != NULL)
+		gaim_pounce_execute(gc->account, name, GAIM_POUNCE_TYPING_STOPPED);
 }
 
 struct chat_invite_data {
--- a/src/ui.h	Sat Apr 05 08:03:10 2003 +0000
+++ b/src/ui.h	Sat Apr 05 10:14:21 2003 +0000
@@ -250,7 +250,6 @@
 extern void destroy_all_dialogs();
 extern void show_import_dialog();
 extern void show_export_dialog();
-extern void show_new_bp(char *, struct gaim_connection *, int, int, struct buddy_pounce *);
 extern void conv_show_log(GtkWidget *, gpointer);
 extern void chat_show_log(GtkWidget *, gpointer);
 extern void show_log(char *);