view src/buddy.c @ 1106:5bc8fdacd2cb

[gaim-migrate @ 1116] lots of changes. buddy.c: just in general tried to get things to work better. moving things in the edit list window and signing off should be handled better in the main buddy list window (watch out for flashes). gaim.h: removed toc-specific things and moved them to toc.c and rvous.c as needed. gtkhtml.c: possible fix for AOL 6.0 problems (I wasn't able to reproduce the problem before or after the fix, but i fixed what i think might have been causing the problem). multi.c: moved LOGIN_STEPS from gaim.h here and actually use it now oscar.c: moved an oscar-specific struct definition from gaim.h here and also handle problems better perl.c: fix for stupid problem rvous.c: first pass at attempt to be able to remove toc.c and rvous.c (though this will never happen; gaim will support toc as long as aol does) without cruft. gaim is now only dependent on toc.c and rvous.c for toc_build_config and parse_toc_buddy_list, which gaim needs to save and read its buddy list. toc.c: rewrote the signin process so that the read()'s won't block. it's not actually a non-blocking read; it's just that it won't ever get to the read until there's data to be read (thanks to the gdk_input watcher). this means the cancel button should work after it's connected, but it's still not a non-blocking connect. committer: Tailor Script <tailor@pidgin.im>
author Eric Warmenhoven <eric@warmenhoven.org>
date Mon, 20 Nov 2000 07:24:18 +0000
parents 7aabbbaae829
children cb338aa38e78
line wrap: on
line source

/*
 * gaim
 *
 * Copyright (C) 1998-1999, Mark Spencer <markster@marko.net>
 *
 * 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
 *
 */

#ifdef HAVE_CONFIG_H
#include "../config.h"
#endif
#ifdef USE_APPLET
#include <gnome.h>
#include <applet-widget.h>
#include "gnome_applet_mgr.h"
#endif /* USE_APPLET */
#ifdef GAIM_PLUGINS
#include <dlfcn.h>
#endif /* GAIM_PLUGINS */
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <time.h>

#include <gtk/gtk.h>
#include <gdk/gdkx.h>
#include "prpl.h"
#include "gaim.h"
#include "pixmaps/login_icon.xpm"
#include "pixmaps/logout_icon.xpm"
#include "pixmaps/no_icon.xpm"

#include "pixmaps/away_small.xpm"

#include "pixmaps/add_small.xpm"
#include "pixmaps/import_small.xpm"
#include "pixmaps/export_small.xpm"
#if defined(GAIM_PLUGINS) || defined(USE_PERL)
#include "pixmaps/plugins_small.xpm"
#endif
#include "pixmaps/prefs_small.xpm"
#include "pixmaps/search_small.xpm"
#ifdef USE_APPLET
#include "pixmaps/close_small.xpm"
#endif
#include "pixmaps/exit_small.xpm"
#include "pixmaps/pounce_small.xpm"
#include "pixmaps/about_small.xpm"

#include "pixmaps/tmp_send.xpm"
#include "pixmaps/tb_search.xpm"
#include "pixmaps/join.xpm"
#include "pixmaps/gnome_add.xpm"
#include "pixmaps/gnome_remove.xpm"
#include "pixmaps/group.xpm"

static GtkTooltips *tips;
static GtkWidget *editpane;
static GtkWidget *buddypane;
static GtkWidget *imchatbox;
static GtkWidget *edittree;
static GtkWidget *imbutton, *infobutton, *chatbutton;
static GtkWidget *addbutton, *groupbutton, *rembutton;

extern int ticker_prefs;

GtkWidget *blist = NULL;
GtkWidget *bpmenu;
GtkWidget *buddies;

void BuddyTickerLogonTimeout( gpointer data );
void BuddyTickerLogoutTimeout( gpointer data );

/* Predefine some functions */
static void new_bp_callback(GtkWidget *w, char *name);

/* stuff for actual display of buddy list */
struct group_show {
	GtkWidget *item;
	GtkWidget *label;
	GtkWidget *tree;
	GSList *members;
	char *name;
};
static GSList *shows = NULL;

static struct group_show *find_group_show(char *group);
static struct buddy_show *find_buddy_show(struct group_show *gs, char *name);
static int group_number(char *group);
static int buddy_number(char *group, char *buddy);
static struct group_show *new_group_show(char *group);
static struct buddy_show *new_buddy_show(struct group_show *gs, struct buddy *buddy, char **xpm);
static struct group_show *find_gs_by_bs(struct buddy_show *b);
static void redo_buddy_list();

void destroy_buddy()
{
	if (blist)
		gtk_widget_destroy(blist);
	blist=NULL;
	imchatbox = NULL;
}

static void adjust_pic(GtkWidget *button, const char *c, gchar **xpm)
{
        GdkPixmap *pm;
        GdkBitmap *bm;
        GtkWidget *pic;
        GtkWidget *label;

		/*if the user had opted to put pictures on the buttons*/
        if (display_options & OPT_DISP_SHOW_BUTTON_XPM && xpm) {
		pm = gdk_pixmap_create_from_xpm_d(blist->window, &bm, NULL, xpm);
		pic = gtk_pixmap_new(pm, bm);
		gtk_widget_show(pic);
		gdk_pixmap_unref(pm);
		gdk_bitmap_unref(bm);
		label = GTK_BIN(button)->child;
		gtk_container_remove(GTK_CONTAINER(button), label);
		gtk_container_add(GTK_CONTAINER(button), pic);
        } else {
		label = gtk_label_new(c);
		gtk_widget_show(label);
		pic = GTK_BIN(button)->child;
		gtk_container_remove(GTK_CONTAINER(button), pic);
		gtk_container_add(GTK_CONTAINER(button), label);
	}

}


void toggle_show_empty_groups() {
	if (display_options & OPT_DISP_NO_MT_GRP) {
		/* remove any group_shows with empty members */
		GSList *s = shows;
		struct group_show *g;

		while (s) {
			g = (struct group_show *)s->data;
			if (!g_slist_length(g->members)) {
				shows = g_slist_remove(shows, g);
				s = shows;
				gtk_container_remove(GTK_CONTAINER(buddies), g->item);
				g_free(g->name);
				g_free(g);
			} else
				s = g_slist_next(s);
		}

	} else {
		/* put back all groups */
		GSList *c = connections;
		struct gaim_connection *gc;
		GSList *m;
		struct group *g;

		while (c) {
			gc = (struct gaim_connection *)c->data;
			m = gc->groups;
			while (m) {
				g = (struct group *)m->data;
				m = g_slist_next(m);
				if (!find_group_show(g->name))
					new_group_show(g->name);
			}
			c = g_slist_next(c);
		}
		
	}
}

static void update_num_group(struct group_show *gs) {
	GSList *c = connections;
	struct gaim_connection *gc;
	struct group *g;
	struct buddy_show *b;
	int total = 0, on = 0;
	char buf[256];

	if (!g_slist_find(shows, gs)) {
		debug_printf("update_num_group called for unfound group_show %s\n", gs->name);
		return;
	}

	while (c) {
		gc = (struct gaim_connection *)c->data;
		g = find_group(gc, gs->name);
		if (g) {
			total += g_slist_length(g->members);
		}
		c = g_slist_next(c);
	}

	c = gs->members;
	while (c) {
		b = (struct buddy_show *)c->data;
		on += g_slist_length(b->connlist);
		c = g_slist_next(c);
	}

	if (display_options & OPT_DISP_SHOW_GRPNUM)
		g_snprintf(buf, sizeof buf, "%s (%d/%d)", gs->name, on, total);
	else
		g_snprintf(buf, sizeof buf, "%s", gs->name);

	gtk_label_set_text(GTK_LABEL(gs->label), buf);
}

void update_num_groups() {
	GSList *s = shows;
	struct group_show *g;

	while (s) {
		g = (struct group_show *)s->data;
		update_num_group(g);
		s = g_slist_next(s);
	}
}

void update_button_pix()
{

	adjust_pic(addbutton, _("Add"), (gchar **)gnome_add_xpm);
	adjust_pic(groupbutton, _("Group"), (gchar **)group_xpm);
	adjust_pic(rembutton, _("Remove"), (gchar **)gnome_remove_xpm);

	if (!(display_options & OPT_DISP_NO_BUTTONS)) {
		adjust_pic(chatbutton, _("Chat"), (gchar **)join_xpm);
	        adjust_pic(imbutton, _("IM"), (gchar **)tmp_send_xpm);
	        adjust_pic(infobutton, _("Info"), (gchar **)tb_search_xpm);
	}
	gtk_widget_hide(addbutton->parent);
	gtk_widget_show(addbutton->parent);
	if (!(display_options & OPT_DISP_NO_BUTTONS)) {
		gtk_widget_hide(chatbutton->parent);
		gtk_widget_show(chatbutton->parent);
	}
}



#ifdef USE_APPLET
gint applet_destroy_buddy( GtkWidget *widget, GdkEvent *event,gpointer *data ) {
	applet_buddy_show = FALSE;
	gtk_widget_hide(blist);
	return (TRUE);
}

#endif


void signoff_all(GtkWidget *w, gpointer d)
{
	GSList *c = connections;
	struct gaim_connection *g = NULL;

	while (c) {
		g = (struct gaim_connection *)c->data;
		signoff(g);
		c = connections;
	}
}

void signoff(struct gaim_connection *gc)
{
	plugin_event(event_signoff, gc, 0, 0, 0);
	update_keepalive(gc, FALSE);
	serv_close(gc);
	redo_buddy_list();

	if (connections) return;

	{
		GSList *s = shows;
		struct group_show *g;
		GSList *m;
		struct buddy_show *b;
		while (s) {
			g = (struct group_show *)s->data;
			debug_printf("group_show still exists: %s\n", g->name);
			m = g->members;
			while (m) {
				b = (struct buddy_show *)m->data;
				debug_printf("buddy_show still exists: %s\n", b->name);
				m = g_slist_remove(m, b);
				if (b->log_timer > 0)
					gtk_timeout_remove(b->log_timer);
				b->log_timer = 0;
				gtk_container_remove(GTK_CONTAINER(g->tree), b->item);
				g_free(b->show);
				g_free(b->name);
				g_free(b);
			}
			gtk_container_remove(GTK_CONTAINER(buddies), g->item);
			s = g_slist_remove(s, g);
			g_free(g->name);
			g_free(g);
		}
		shows = NULL;
	}

	sprintf(debug_buff, "date: %s\n", full_date());
	debug_print(debug_buff);
        destroy_all_dialogs();
        destroy_buddy();
#ifdef USE_APPLET
	set_user_state(offline);
	applet_buddy_show = FALSE;
        applet_widget_unregister_callback(APPLET_WIDGET(applet),"signoff");
	remove_applet_away();
#else
        show_login();
#endif /* USE_APPLET */
	if ( ticker_prefs & OPT_DISP_SHOW_BUDDYTICKER )
		BuddyTickerSignoff();
}

void handle_click_group(GtkWidget *widget, GdkEventButton *event, gpointer func_data)
{
	if (event->type == GDK_2BUTTON_PRESS) {
		if (GTK_TREE_ITEM(widget)->expanded)
			gtk_tree_item_collapse(GTK_TREE_ITEM(widget));
		else
			gtk_tree_item_expand(GTK_TREE_ITEM(widget));
	} else {
	}
}

void pressed_im(GtkWidget *widget, struct buddy_show *b)
{
	struct conversation *c;

	c = find_conversation(b->name);

	if (c != NULL) {
		gdk_window_show(c->window->window);
	} else {
		c = new_conversation(b->name);
	}
}

void pressed_ticker(char *buddy)
{
	struct conversation *c;

	c = find_conversation(buddy);

	if (c != NULL) {
		gdk_window_show(c->window->window);
	} else {
		c = new_conversation(buddy);
	}
}

void pressed_alias(GtkWidget *widget, struct buddy_show *b)
{
	alias_dialog(b);
}

void handle_click_buddy(GtkWidget *widget, GdkEventButton *event, struct buddy_show *b)
{
	if (!b->connlist) return;
        if (event->type == GDK_2BUTTON_PRESS && event->button == 1) {
                struct conversation *c;

                c = find_conversation(b->name);

                if (c != NULL) {
                        gdk_window_show(c->window->window);
                } else {
                        c = new_conversation(b->name);
                }
	} else if (event->type == GDK_BUTTON_PRESS && event->button == 3) {
		GtkWidget *menu;
		GtkWidget *button;
		GtkWidget *menuitem;
		GtkWidget *conmenu;
		GSList *cn = b->connlist;
		struct gaim_connection *g;
		/* We're gonna make us a menu right here */

		menu = gtk_menu_new();

		button = gtk_menu_item_new_with_label(_("IM"));
		gtk_signal_connect(GTK_OBJECT(button), "activate",
				   GTK_SIGNAL_FUNC(pressed_im), b);
		gtk_menu_append(GTK_MENU(menu), button);
		gtk_widget_show(button);

		button = gtk_menu_item_new_with_label(_("Alias"));
		gtk_signal_connect(GTK_OBJECT(button), "activate",
				   GTK_SIGNAL_FUNC(pressed_alias), b);
		gtk_menu_append(GTK_MENU(menu), button);
		gtk_widget_show(button);

		/* FIXME: for now, we're not going to do buddy pounces from the menu,
		 * since the person is already online. when we do per-connection pounces,
		 * then this'll come back
		button = gtk_menu_item_new_with_label(_("Add Buddy Pounce"));
		gtk_signal_connect(GTK_OBJECT(button), "activate",
				   GTK_SIGNAL_FUNC(new_bp_callback), b->name);
		gtk_menu_append(GTK_MENU(menu), button);
		gtk_widget_show(button);
		*/

		if (g_slist_length(cn) > 1) {
			while (cn) {
				g = (struct gaim_connection *)cn->data;
				if (g->prpl->action_menu) {
					menuitem = gtk_menu_item_new_with_label(g->username);
					gtk_menu_append(GTK_MENU(menu), menuitem);
					gtk_widget_show(menuitem);
					
					conmenu = gtk_menu_new();
					gtk_menu_item_set_submenu(GTK_MENU_ITEM(menuitem), conmenu);
					gtk_widget_show(conmenu);

					(*g->prpl->action_menu)(conmenu, g, b->name);
				}
				cn = g_slist_next(cn);
			}
		} else {
			g = (struct gaim_connection *)cn->data;
			if (g->prpl->action_menu)
				(*g->prpl->action_menu)(menu, g, b->name);
		}
		
		gtk_menu_popup(GTK_MENU(menu), NULL, NULL, NULL, NULL,
			       event->button, event->time);

	} else if (event->type == GDK_3BUTTON_PRESS && event->button == 2) {
		if (!strcasecmp("zilding", normalize(b->name)))
			show_ee_dialog(0);
		else if (!strcasecmp("robflynn", normalize(b->name)))
			show_ee_dialog(1);
		else if (!strcasecmp("flynorange", normalize(b->name)))
			show_ee_dialog(2);
		else if (!strcasecmp("ewarmenhoven", normalize(b->name)))
			show_ee_dialog(3);

	} else {
		
                /* Anything for other buttons? :) */
	}
}



void remove_buddy(struct gaim_connection *gc, struct group *rem_g, struct buddy *rem_b)
{
	GSList *grp;
	GSList *mem;
	struct conversation *c;
	struct group_show *gs;
	struct buddy_show *bs;
	
	struct group *delg;
	struct buddy *delb;

	/* we assume that gc is not NULL and that the buddy exists somewhere within the
	 * gc's buddy list, therefore we can safely remove it. we need to ensure this
	 * via the UI
	 */

	gs = find_group_show(rem_g->name);
	if (gs) {
		bs = find_buddy_show(gs, rem_b->name);
		if (bs) {
			if (g_slist_find(bs->connlist, gc)) {
				bs->connlist = g_slist_remove(bs->connlist, gc);
				if (!g_slist_length(bs->connlist)) {
					gs->members = g_slist_remove(gs->members, bs);
					if (bs->log_timer > 0)
						gtk_timeout_remove(bs->log_timer);
					bs->log_timer = 0;
					gtk_container_remove(GTK_CONTAINER(gs->tree), bs->item);
					g_free(bs->show);
					g_free(bs->name);
					g_free(bs);
					if (!g_slist_length(gs->members) &&
							(display_options & OPT_DISP_NO_MT_GRP)) {
						shows = g_slist_remove(shows, gs);
						gtk_container_remove(GTK_CONTAINER(buddies), gs->item);
						g_free(gs->name);
						g_free(gs);
					} else
						update_num_group(gs);
				} else
					update_num_group(gs);
			}
		}
	}

	grp = g_slist_find(gc->groups, rem_g);
        delg = (struct group *)grp->data;
        mem = delg->members;
	
        mem = g_slist_find(mem, rem_b);
        delb = (struct buddy *)mem->data;
	
        delg->members = g_slist_remove(delg->members, delb);
        serv_remove_buddy(gc, delb->name);
	c = find_conversation(delb->name);
        g_free(delb);
	mem = delg->members;

	if (c)
		update_convo_add_button(c);

	// flush buddy list to cache

	do_export( (GtkWidget *) NULL, 0 );
}

void remove_group(struct gaim_connection *gc, struct group *rem_g)
{
	GSList *grp;
	GSList *mem;
	
	struct group *delg;
	struct buddy *delb;

	/* we assume that the group actually does exist within the gc, and that the gc is not NULL.
	 * the UI is responsible for this */

	grp = g_slist_find(gc->groups, rem_g);
        delg = (struct group *)grp->data;
        mem = delg->members;

	while(delg->members) {
		delb = (struct buddy *)delg->members->data;
		remove_buddy(gc, delg, delb); /* this should take care of removing
						 the group_show if necessary */
                serv_remove_buddy(gc, delb->name);
	}

	gc->groups = g_slist_remove(gc->groups, delg);
	g_free(delg);

        // flush buddy list to cache

        do_export( (GtkWidget *) NULL, 0 );
}





gboolean edit_drag_compare_func (GtkCTree *ctree, GtkCTreeNode *source_node,
				 GtkCTreeNode *new_parent, GtkCTreeNode *new_sibling)
{
        gboolean leaf;
	struct gaim_connection *gc, *pc;
	char *source;
	char *parent;

	gtk_ctree_get_node_info (ctree, source_node, &source,
				 NULL, NULL, NULL, NULL, NULL, &leaf, NULL);

	gc = (struct gaim_connection *)gtk_ctree_node_get_row_data(GTK_CTREE(ctree), source_node);
	
	if (!strcmp(gc->username, source)) {
		if (!new_parent)
			return TRUE;
	} else if (leaf) {
		if (new_parent) {
			gtk_ctree_get_node_info (ctree, new_parent, &parent,
						 NULL, NULL, NULL, NULL, NULL, NULL, NULL);
			pc = (struct gaim_connection *)gtk_ctree_node_get_row_data(GTK_CTREE(ctree),
					new_parent);
			if (strcmp(parent, pc->username))
				return TRUE;
		}
	} else /* group */ {
		if (g_slist_length(connections) > 1 && new_parent) {
			gtk_ctree_get_node_info (ctree, new_parent, &parent,
						 NULL, NULL, NULL, NULL, NULL, NULL, NULL);
			pc = (struct gaim_connection *)gtk_ctree_node_get_row_data(GTK_CTREE(ctree),
					new_parent);
			if (!strcmp(parent, pc->username))
				return TRUE;
		} else if (g_slist_length(connections) == 1 && !new_parent)
			return TRUE;
	}

	return FALSE;
}


static void redo_buddy_list() {
	/* so here we can safely assume that we don't have to add or delete anything, we
	 * just have to go through and reorder everything. remember, nothing is going to
	 * change connections, so we can assume that we don't have to change any user
	 * data or anything. this is just a simple reordering. so calm down. */
	/* note: we only have to do this if we want to strongly enforce order; however,
	 * order doesn't particularly matter to the stability of the program. but, it's
	 * kind of nice to have */
	/* the easy way to implement this is just to go through shows and destroy all the
	 * group_shows, then go through the connections and put everything back. though,
	 * there are slight complications with that; most of them deal with timeouts and
	 * people not seeing the login icon for the full 10 seconds. butt fuck them. */
	GSList *s = shows;
	struct group_show *gs;
	GSList *m;
	struct buddy_show *bs;
	GSList *c = connections;
	struct gaim_connection *gc;
	GSList *gr;
	struct group *g;
	struct buddy *b;

	while (s) {
		gs = (struct group_show *)s->data;
		s = g_slist_remove(s, gs);
		m = gs->members;
		gtk_container_remove(GTK_CONTAINER(buddies), gs->item);
		while (m) {
			bs = (struct buddy_show *)m->data;
			m = g_slist_remove(m, bs);
			if (bs->log_timer > 0)
				gtk_timeout_remove(bs->log_timer);
			g_free(bs->show);
			g_free(bs->name);
			g_free(bs);
		}
		g_free(gs->name);
		g_free(gs);
	}
	shows = NULL;
	while (c) {
		gc = (struct gaim_connection *)c->data;
		c = c->next;
		gr = gc->groups;
		while (gr) {
			g = (struct group *)gr->data;
			gr = gr->next;
			gs = find_group_show(g->name);
			if (!gs && !(display_options & OPT_DISP_NO_MT_GRP))
				gs = new_group_show(g->name);
			m = g->members;
			while (m) {
				b = (struct buddy *)m->data;
				m = m->next;
				if (b->present) {
					if (!gs)
						gs = new_group_show(g->name);
					bs = find_buddy_show(gs, b->name);
					if (!bs) {
						if (gc->prpl->list_icon)
							bs = new_buddy_show(gs, b,
								(*gc->prpl->list_icon)(b->uc));
						else
							bs = new_buddy_show(gs, b, (char **)no_icon_xpm);
					}
					bs->connlist = g_slist_append(bs->connlist, gc);
				}
			}
		}
	}
}

static void edit_tree_move (GtkCTree *ctree, GtkCTreeNode *child, GtkCTreeNode *parent,
                 GtkCTreeNode *sibling, gpointer data)
{
	gboolean leaf;
	char *source = "";
	char *target1 = "";
        char *target2 = "";
	struct gaim_connection *gc, *pc = NULL, *sc = NULL;
	
	gc = (struct gaim_connection *)gtk_ctree_node_get_row_data(GTK_CTREE(ctree), child);
	
	gtk_ctree_get_node_info (ctree, child, &source,
				 NULL, NULL, NULL, NULL, NULL, &leaf, NULL);
	if (parent) {
		gtk_ctree_get_node_info (ctree, parent, &target1,
					 NULL, NULL, NULL, NULL, NULL, NULL, NULL);
		pc = (struct gaim_connection *)gtk_ctree_node_get_row_data(GTK_CTREE(ctree), parent);
	}

	if (sibling) {
		gtk_ctree_get_node_info (ctree, sibling, &target2,
					 NULL, NULL, NULL, NULL, NULL, NULL, NULL);
		sc = (struct gaim_connection *)gtk_ctree_node_get_row_data(GTK_CTREE(ctree), sibling);
	}

	if (!strcmp(source, gc->username)) {
		/* not that it particularly matters which order the connections
		 * are in, but just for debugging sake, i guess.... */
		connections = g_slist_remove(connections, gc);
		if (sibling) {
			int pos = g_slist_index(connections, sc);
			if (pos)
				connections = g_slist_insert(connections, gc, pos);
			else
				connections = g_slist_prepend(connections, gc);
		} else
			connections = g_slist_append(connections, gc);
	} else if (leaf) {
		/* we moved a buddy. hopefully we just changed groups or positions or something.
		 * if we changed connections, we copy the buddy to the new connection. if the new
		 * connection already had the buddy in its buddy list but in a different group,
		 * we change the group that the buddy is in */
		struct group *new_g, *old_g;
		struct buddy *b, *s = NULL;
		int pos;

		if (gc != pc) {
			/* we changed connections */
			struct buddy *a;

			a = find_buddy(pc, source);

			if (a) {
				/* the buddy is in the new connection, so we'll remove it from
				 * its current group and add it to the proper group below */
				struct group *og;
				og = find_group_by_buddy(pc, source);
				og->members = g_slist_remove(og->members, a);
			} else {
				/* we don't have this buddy yet; let's add him */
				serv_add_buddy(pc, source);
			}
		}

		b = find_buddy(gc, source);
		new_g = find_group(pc, target1);
		old_g = find_group_by_buddy(gc, source);

		if (gc == pc) /* this is the same connection, so we'll remove it from its old group */
			old_g->members = g_slist_remove(old_g->members, b);

		if (sibling) {
			s = find_buddy(sc, target2);
			pos = g_slist_index(new_g->members, s);
			if (pos)
				new_g->members = g_slist_insert(new_g->members, b, pos);
			else
				new_g->members = g_slist_prepend(new_g->members, b);
		} else
			new_g->members = g_slist_append(new_g->members, b);

		if (pc != gc)
			build_edit_tree();
	} else /* group */ {
		/* move the group. if moving connections, copy the group, and each buddy in the
		 * group. if the buddy exists in the new connection, leave it where it is. */

		struct group *g, *g2;
		int pos;

		if (g_slist_length(connections) > 1) {
			g = find_group(pc, source);
			if (!g)
				g = add_group(pc, source);

			pc->groups = g_slist_remove(pc->groups, g);

			if (sibling) {
				g2 = find_group(pc, target2);
				pos = g_slist_index(pc->groups, g2);
				if (pos)
					pc->groups = g_slist_insert(pc->groups, g, pos);
				else
					pc->groups = g_slist_prepend(pc->groups, g);
			} else
				pc->groups = g_slist_append(pc->groups, g);

			if (pc != gc) {
				GSList *mem;
				struct buddy *b;
				g2 = find_group(gc, source);

				mem = g2->members;
				while (mem) {
					b = (struct buddy *)mem->data;
					if (!find_buddy(pc, b->name))
						add_buddy(pc, g->name, b->name, b->show);
					mem = mem->next;
				}
				
				build_edit_tree();
			}
		} else {
			g = find_group(gc, source);

			gc->groups = g_slist_remove(gc->groups, g);

			if (sibling) {
				g2 = find_group(gc, target2);
				pos = g_slist_index(gc->groups, g2);
				if (pos)
					gc->groups = g_slist_insert(gc->groups, g, pos);
				else
					gc->groups = g_slist_prepend(gc->groups, g);
			} else
				gc->groups = g_slist_append(gc->groups, g);
		}
	}

	do_export( (GtkWidget *) NULL, 0 );

	redo_buddy_list();
	update_num_groups();
}



void build_edit_tree()
{
        GtkCTreeNode *c = NULL, *p = NULL, *n;
	GSList *con = connections;
	GSList *grp;
	GSList *mem;
	struct gaim_connection *z;
	struct group *g;
	struct buddy *b;
	char *text[1];

	if (!blist) return;

	gtk_clist_freeze(GTK_CLIST(edittree));
	gtk_clist_clear(GTK_CLIST(edittree));
	
        
	while (con) {
		z = (struct gaim_connection *)con->data;

		if (g_slist_length(connections) > 1) {
			text[0] = z->username;

			c = gtk_ctree_insert_node(GTK_CTREE(edittree), NULL,
							NULL, text, 5, NULL, NULL,
							NULL, NULL, 0, 1);

			gtk_ctree_node_set_row_data(GTK_CTREE(edittree), c, z);
		} else
			c = NULL;

		grp = z->groups;

		while(grp) {
			g = (struct group *)grp->data;

			text[0] = g->name;
			
			p = gtk_ctree_insert_node(GTK_CTREE(edittree), c,
						     NULL, text, 5, NULL, NULL,
						     NULL, NULL, 0, 1);

			gtk_ctree_node_set_row_data(GTK_CTREE(edittree), p, z);

			n = NULL;
			
			mem = g->members;

			while(mem) {
				b = (struct buddy *)mem->data;

				text[0] = b->name;

				n = gtk_ctree_insert_node(GTK_CTREE(edittree),
							  p, NULL, text, 5,
							  NULL, NULL,
							  NULL, NULL, 1, 1);

				gtk_ctree_node_set_row_data(GTK_CTREE(edittree), n, z);

				mem = mem->next;
				
			}
			grp = g_slist_next(grp);
		}
		con = g_slist_next(con);
	}

	gtk_clist_thaw(GTK_CLIST(edittree));
	
}

struct buddy *add_buddy(struct gaim_connection *gc, char *group, char *buddy, char *show)
{
	struct buddy *b;
	struct group *g;
	struct group_show *gs = find_group_show(group);

	if ((b = find_buddy(gc, buddy)) != NULL)
                return b;

	g = find_group(gc, group);

	if (g == NULL)
		g = add_group(gc, group);
	
        b = (struct buddy *)g_new0(struct buddy, 1);
        
	if (!b)
		return NULL;

	b->present = 0;

	g_snprintf(b->name, sizeof(b->name), "%s", buddy);
	g_snprintf(b->show, sizeof(b->show), "%s", show ? (show[0] ? show : buddy) : buddy);
		
        g->members = g_slist_append(g->members, b);

        b->idle = 0;
	b->caps = 0;

	if (gs) update_num_group(gs);
			
	return b;
}


struct group *add_group(struct gaim_connection *gc, char *group)
{
	struct group *g = find_group(gc, group);
	if (g)
		return g;
	g = (struct group *)g_new0(struct group, 1);
	if (!g)
		return NULL;

	strncpy(g->name, group, sizeof(g->name));
        gc->groups = g_slist_append(gc->groups, g);

	g->members = NULL;
	
	if (!blist) return g;

	build_edit_tree();
	
	if (!(display_options & OPT_DISP_NO_MT_GRP) && !find_group_show(group))
		new_group_show(group);

	return g;
}


static void do_del_buddy(GtkWidget *w, GtkCTree *ctree)
{
	GtkCTreeNode *node;
        char *bud, *grp;
	struct buddy *b;
	struct group *g;
	struct gaim_connection *gc;
	GList *i;
	
	i = GTK_CLIST(edittree)->selection;
	if (i) {
		node = i->data;
		gc = (struct gaim_connection *)gtk_ctree_node_get_row_data(GTK_CTREE(edittree), node);

		if (GTK_CTREE_ROW(node)->is_leaf) {
                        gtk_ctree_get_node_info (GTK_CTREE(edittree), node, &bud,
						 NULL, NULL, NULL, NULL, NULL, NULL, NULL);

			b = find_buddy(gc, bud);
			g = find_group_by_buddy(gc, bud);
			remove_buddy(gc, g, b);
		} else {
			gtk_ctree_get_node_info (ctree, node, &grp,
						NULL, NULL, NULL, NULL, NULL, NULL, NULL);
			if (strcmp(gc->username, grp)) {
				g = find_group(gc, grp);
				remove_group(gc, g);
			}
                }
                
                build_edit_tree();

        	// flush buddy list to cache

        	do_export( (GtkWidget *) NULL, 0 );

        } else {
                /* Nothing selected. */
        }
}


void import_callback(GtkWidget *widget, void *null)
{
        show_import_dialog();
}

void export_callback(GtkWidget *widget, void *null)
{
        show_export_dialog();
}



void do_quit()
{
#ifdef GAIM_PLUGINS
	GList *c;
	struct gaim_plugin *p;
	void (*gaim_plugin_remove)();

	/* first we tell those who have requested it we're quitting */
	plugin_event(event_quit, 0, 0, 0, 0);

	/* then we remove everyone in a mass suicide */
	c = plugins;
	while (c) {
		p = (struct gaim_plugin *)c->data;
		if (g_module_symbol(p->handle, "gaim_plugin_remove", (gpointer *)&gaim_plugin_remove))
			(*gaim_plugin_remove)();
		/* we don't need to worry about removing callbacks since
		 * there won't be any more chance to call them back :) */
		g_free(p);
		c = c->next;
	}
#endif
#ifdef USE_PERL
	perl_end();
#endif

	exit(0);
}

void add_buddy_callback(GtkWidget *widget, void *dummy)
{
	char *grp = NULL;
	GtkCTreeNode *node;
	GList *i;
	struct gaim_connection *gc = NULL;

	i = GTK_CLIST(edittree)->selection;
	if (i) {
		node = i->data;
		gc = (struct gaim_connection *)gtk_ctree_node_get_row_data(GTK_CTREE(edittree), node);

		if (GTK_CTREE_ROW(node)->is_leaf) {
			node = GTK_CTREE_ROW(node)->parent;
		} else if (gc) {
			gtk_ctree_get_node_info (GTK_CTREE(edittree), node, &grp,
						 NULL, NULL, NULL, NULL, NULL, NULL, NULL);
		}
	}
	show_add_buddy(gc, NULL, grp);

}

void add_group_callback(GtkWidget *widget, void *dummy)
{
	GtkCTreeNode *node;
	GList *i;
	struct gaim_connection *gc = NULL;

	i = GTK_CLIST(edittree)->selection;
	if (i) {
		node = i->data;
		gc = (struct gaim_connection *)gtk_ctree_node_get_row_data(GTK_CTREE(edittree), node);
	}
	show_add_group(gc);
}

static void im_callback(GtkWidget *widget, GtkTree *tree)
{
	GList *i;
	struct buddy_show *b = NULL;
	struct conversation *c;
	i = GTK_TREE_SELECTION(tree);
	if (i) {
		b = gtk_object_get_user_data(GTK_OBJECT(i->data));
        }
	if (!i || !b) {
		show_im_dialog();
		return;
        }
	if (!b->name)
		return;

        c = find_conversation(b->name);
	if (c == NULL) {
		c = new_conversation(b->name);
	} else {
		gdk_window_raise(c->window->window);
	}
}
	

static void info_callback(GtkWidget *widget, GtkTree *tree)
{
	GList *i;
	struct buddy_show *b = NULL;
	i = GTK_TREE_SELECTION(tree);
	if (i) {
		b = gtk_object_get_user_data(GTK_OBJECT(i->data));
        }
	if (!i || !b) {
		show_info_dialog();
		return;
        }
	if (!b->name)
		return;
        serv_get_info(b->connlist->data, b->name);
}


void chat_callback(GtkWidget *widget, GtkTree *tree)
{
	join_chat();
}

struct group *find_group(struct gaim_connection *gc, char *group)
{
	struct group *g;
        GSList *grp;
	GSList *c = connections;
	struct gaim_connection *z;
	char *grpname = g_malloc(strlen(group) + 1);

	strcpy(grpname, normalize(group));
	if (gc) {
		grp = gc->groups;
		while (grp) {
			g = (struct group *)grp->data;
			if (!strcasecmp(normalize(g->name), grpname)) {
					g_free(grpname);
					return g;
			}
			grp = g_slist_next(grp);
		}

		g_free(grpname);
		return NULL;	
	} else {
		while(c) {
			z = (struct gaim_connection *)c->data;
			grp = z->groups;
			while (grp) {
				g = (struct group *)grp->data;
				if (!strcasecmp(normalize(g->name), grpname)) {
						g_free(grpname);
						return g;
				}
				grp = g_slist_next(grp);
			}

			c = c->next;
		}
		g_free(grpname);
		return NULL;
	}
}


struct group *find_group_by_buddy(struct gaim_connection *gc, char *who)
{
	struct group *g;
	struct buddy *b;
	GSList *grp;
	GSList *mem;
        char *whoname = g_malloc(strlen(who) + 1);

	strcpy(whoname, normalize(who));
	
	if (gc) {
		grp = gc->groups;
		while(grp) {
			g = (struct group *)grp->data;

			mem = g->members;
			while(mem) {
				b = (struct buddy *)mem->data;
				if (!strcasecmp(normalize(b->name), whoname)) {
					g_free(whoname);
					return g;
				}
				mem = mem->next;
			}
			grp = g_slist_next(grp);
		}
		g_free(whoname);
		return NULL;
	} else {
		GSList *c = connections;
		struct gaim_connection *z;
		while (c) {
			z = (struct gaim_connection *)c->data;
			grp = z->groups;
			while(grp) {
				g = (struct group *)grp->data;

				mem = g->members;
				while(mem) {
					b = (struct buddy *)mem->data;
					if (!strcasecmp(normalize(b->name), whoname)) {
						g_free(whoname);
						return g;
					}
					mem = mem->next;
				}
				grp = g_slist_next(grp);
			}
			c = c->next;
		}
		g_free(whoname);
		return NULL;
	}
}


struct buddy *find_buddy(struct gaim_connection *gc, char *who)
{
	struct group *g;
	struct buddy *b;
	GSList *grp;
	GSList *c;
	struct gaim_connection *z;
	GSList *mem;
        char *whoname = g_malloc(strlen(who) + 1);

	strcpy(whoname, normalize(who));
	if (gc) {
		grp = gc->groups;
		while(grp) {
			g = (struct group *)grp->data;

			mem = g->members;
			while(mem) {
				b = (struct buddy *)mem->data;
				if (!strcasecmp(normalize(b->name), whoname)) {
					g_free(whoname);
					return b;
				}
				mem = mem->next;
			}
			grp = g_slist_next(grp);
		}
		g_free(whoname);
		return NULL;
	} else {
		c = connections;
		while (c) {
			z = (struct gaim_connection *)c->data;
			grp = z->groups;
			while(grp) {
				g = (struct group *)grp->data;

				mem = g->members;
				while(mem) {
					b = (struct buddy *)mem->data;
					if (!strcasecmp(normalize(b->name), whoname)) {
						g_free(whoname);
						return b;
					}
					mem = mem->next;
				}
				grp = g_slist_next(grp);
			}
			c = c->next;
		}
		g_free(whoname);
		return 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(char *name)
{
        char *who;
        
        struct buddy_pounce *b;
	struct conversation *c;

	GList *bp = buddy_pounces;
        
	who = g_strdup(normalize(name));

	/* FIXME: we should decide somewhere who we're pouncing as */
	while(bp) {
		b = (struct buddy_pounce *)bp->data;;
		bp = bp->next; /* increment the list here because rem_bp can make our handle bad */

                if (!strcasecmp(who, normalize(b->name))) {
			if (b->popup == 1)
			{
				c = find_conversation(name);
				if (c == NULL)
					c = new_conversation(name);
			}
			if (b->sendim == 1)
			{
                        	c = find_conversation(name);
                        	if (c == NULL)
                                	c = new_conversation(name);

                        	write_to_conv(c, b->message, WFLAG_SEND, NULL);

                                serv_send_im(c->gc, name, b->message, 0);
			}
                        
                        rem_bp(NULL, b);
                        
                }
        }
        g_free(who);
}

static void new_bp_callback(GtkWidget *w, char *name)
{
        show_new_bp(name);
}

void do_bp_menu()
{
	GtkWidget *menuitem, *mess, *messmenu;
	static GtkWidget *remmenu;
        GtkWidget *remitem;
        GtkWidget *sep;
	GList *l;
	struct buddy_pounce *b;
	GList *bp = buddy_pounces;
	
	l = gtk_container_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_append(GTK_MENU(bpmenu), menuitem);
	gtk_widget_show(menuitem);
	gtk_signal_connect(GTK_OBJECT(menuitem), "activate", GTK_SIGNAL_FUNC(new_bp_callback), NULL);


	while(bp) {

		b = (struct buddy_pounce *)bp->data;
		remitem = gtk_menu_item_new_with_label(b->name);
		gtk_menu_append(GTK_MENU(remmenu), remitem);
		gtk_widget_show(remitem);
		gtk_signal_connect(GTK_OBJECT(remitem), "activate", GTK_SIGNAL_FUNC(rem_bp), b);

		bp = bp->next;

	}
	
	menuitem = gtk_menu_item_new_with_label(_("Remove Buddy Pounce"));
	gtk_menu_append(GTK_MENU(bpmenu), menuitem);
	gtk_widget_show(menuitem);
	gtk_menu_item_set_submenu(GTK_MENU_ITEM(menuitem), remmenu);
        gtk_widget_show(remmenu);


        sep = gtk_hseparator_new();
	menuitem = gtk_menu_item_new();
	gtk_menu_append(GTK_MENU(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) {

                b = (struct buddy_pounce *)bp->data;
		
		menuitem = gtk_menu_item_new_with_label(b->name);
		gtk_menu_append(GTK_MENU(bpmenu), menuitem);
                messmenu = gtk_menu_new();
                gtk_menu_item_set_submenu(GTK_MENU_ITEM(menuitem), messmenu);
                gtk_widget_show(menuitem);
                


                mess = gtk_menu_item_new_with_label(b->message);
                gtk_menu_append(GTK_MENU(messmenu), mess);
                gtk_widget_show(mess);

                bp = bp->next;

	}

}


static struct group_show *find_group_show(char *group) {
	GSList *m = shows;
	struct group_show *g = NULL;
	char *who = g_strdup(normalize(group));

	while (m) {
		g = (struct group_show *)m->data;
		if (!strcasecmp(normalize(g->name), who))
			break;
		g = NULL;
		m = m->next;
	}
	g_free(who);

	return g;
}

static struct buddy_show *find_buddy_show(struct group_show *gs, char *name) {
	GSList *m = gs->members;
	struct buddy_show *b = NULL;
	char *who = g_strdup(normalize(name));

	while (m) {
		b = (struct buddy_show *)m->data;
		if (!strcasecmp(normalize(b->name), who))
			break;
		b = NULL;
		m = m->next;
	}
	g_free(who);

	return b;
}

static int group_number(char *group) {
	GSList *c = connections;
	struct gaim_connection *g;
	GSList *m;
	struct group *p;
	int pos = 0;

	while (c) {
		g = (struct gaim_connection *)c->data;
		m = g->groups;
		while (m) {
			p = (struct group *)m->data;
			if (!strcmp(p->name, group))
				return pos;
			if (find_group_show(p->name))
				pos++;
			m = m->next;
		}
		c = c->next;
	}
	/* um..... we'll never get here */
	return -1;
}

static int buddy_number(char *group, char *buddy) {
	GSList *c = connections;
	struct gaim_connection *g;
	struct group *p;
	GSList *z;
	struct buddy *b;
	int pos = 0;
	char *tmp1 = g_strdup(normalize(buddy));
	struct group_show *gs = find_group_show(group);

	while (c) {
		g = (struct gaim_connection *)c->data;
		p = find_group(g, group);
		if (!p) {
			c = c->next;
			continue;
		}
		z = p->members;
		while (z) {
			b = (struct buddy *)z->data;
			if (!strcmp(tmp1, normalize(b->name))) {
				g_free(tmp1);
				return pos;
			}
			if (find_buddy_show(gs, b->name))
				pos++;
			z = z->next;
		}
		c = c->next;
	}
	/* we shouldn't ever get here */
	debug_printf("got to bad place in buddy_number\n");
	g_free(tmp1);
	return -1;
}

static struct group_show *new_group_show(char *group) {
	struct group_show *g = g_new0(struct group_show, 1);
	int pos = group_number(group);

	g->name = g_strdup(group);

	g->item = gtk_tree_item_new();
	gtk_tree_insert(GTK_TREE(buddies), g->item, pos);
	gtk_signal_connect(GTK_OBJECT(g->item), "button_press_event",
			   GTK_SIGNAL_FUNC(handle_click_group), NULL);
	gtk_widget_show(g->item);

	g->label = gtk_label_new(group);
	gtk_misc_set_alignment(GTK_MISC(g->label), 0.0, 0.5);
	gtk_container_add(GTK_CONTAINER(g->item), g->label);
	gtk_widget_show(g->label);

	g->tree = gtk_tree_new();
	gtk_tree_item_set_subtree(GTK_TREE_ITEM(g->item), g->tree);
	gtk_tree_item_expand(GTK_TREE_ITEM(g->item));
	gtk_widget_show(g->tree);

	shows = g_slist_insert(shows, g, pos);
	update_num_groups(g);
	return g;
}

static struct buddy_show *new_buddy_show(struct group_show *gs, struct buddy *buddy, char **xpm) {
	struct buddy_show *b = g_new0(struct buddy_show, 1);
	GtkWidget *box;
	GdkPixmap *pm;
	GdkBitmap *bm;
	int pos = buddy_number(gs->name, buddy->name);

	b->name = g_strdup(buddy->name);
	b->show = g_strdup(buddy->show);

	b->item = gtk_tree_item_new();
	gtk_tree_insert(GTK_TREE(gs->tree), b->item, pos);
	gtk_object_set_user_data(GTK_OBJECT(b->item), b);
	gtk_signal_connect(GTK_OBJECT(b->item), "button_press_event",
			   GTK_SIGNAL_FUNC(handle_click_buddy), b);
	gtk_widget_show(b->item);

	box = gtk_hbox_new(FALSE, 1);
	gtk_container_add(GTK_CONTAINER(b->item), box);
	gtk_widget_show(box);

	pm = gdk_pixmap_create_from_xpm_d(blist->window, &bm, NULL, xpm);
	b->pix = gtk_pixmap_new(pm, bm);
	gtk_box_pack_start(GTK_BOX(box), b->pix, FALSE, FALSE, 1);
	gtk_widget_show(b->pix);
	gdk_pixmap_unref(pm);
	gdk_bitmap_unref(bm);

	b->label = gtk_label_new(buddy->show);
	gtk_misc_set_alignment(GTK_MISC(b->label), 0.0, 0.5);
	gtk_box_pack_start(GTK_BOX(box), b->label, TRUE, TRUE, 1);
	gtk_widget_show(b->label);

	b->idle = gtk_label_new("");
	gtk_box_pack_start(GTK_BOX(box), b->idle, FALSE, FALSE, 1);
	gtk_widget_show(b->idle);

	gs->members = g_slist_insert(gs->members, b, pos);
	update_num_group(gs);
	return b;
}

static struct group_show *find_gs_by_bs(struct buddy_show *b) {
	GSList *m, *n;
	struct group_show *g = NULL;
	struct buddy_show *h;

	m = shows;
	while (m) {
		g = (struct group_show *)m->data;
		n = g->members;
		while (n) {
			h = (struct buddy_show *)n->data;
			if (h == b)
				return g;
			n = n->next;
		}
		g = NULL;
		m = m->next;
	}

	return g;
}

static gint log_timeout(struct buddy_show *b) {
	if (!b->connlist) {
		struct group_show *g = find_gs_by_bs(b);
		g->members = g_slist_remove(g->members, b);
		if (blist)
			gtk_container_remove(GTK_CONTAINER(g->tree), b->item);
		else
			debug_printf("log_timeout but buddy list not available\n");
		if ((g->members == NULL) && (display_options & OPT_DISP_NO_MT_GRP)) {
			shows = g_slist_remove(shows, g);
			if (blist)
				gtk_container_remove(GTK_CONTAINER(buddies), g->item);
			g_free(g->name);
			g_free(g);
		}
		gtk_timeout_remove(b->log_timer);
		b->log_timer = 0;
		g_free(b->name);
		g_free(b->show);
		g_free(b);
	} else {
		/* um.... what do we have to do here? just update the pixmap? */
		GdkPixmap *pm;
		GdkBitmap *bm;
		gchar **xpm = NULL;
		struct buddy *light = find_buddy(b->connlist->data, b->name);
		if (((struct gaim_connection *)b->connlist->data)->prpl->list_icon)
			xpm = (*((struct gaim_connection *)b->connlist->data)->prpl->list_icon)(light->uc);
		if (xpm == NULL)
			xpm = (char **)no_icon_xpm;
		pm = gdk_pixmap_create_from_xpm_d(blist->window, &bm, NULL, xpm);
		gtk_widget_hide(b->pix);
		gtk_pixmap_set(GTK_PIXMAP(b->pix), pm, bm);
		gtk_widget_show(b->pix);
		if (ticker_prefs & OPT_DISP_SHOW_BUDDYTICKER)
			BuddyTickerSetPixmap(b->name, pm, bm);
		gdk_pixmap_unref(pm);
		gdk_bitmap_unref(bm);
		gtk_timeout_remove(b->log_timer);
		b->log_timer = 0;
	}
	return 0;
}

static char *caps_string(gushort caps)
{
	static char buf[256], *tmp;
	int count = 0, i = 0;
	gushort bit = 1;
	while (bit <= 0x20) {
		if (bit & caps) {
			switch (bit) {
				case 0x1:
					tmp = _("Buddy Icon");
					break;
				case 0x2:
					tmp = _("Voice");
					break;
				case 0x4:
					tmp = _("IM Image");
					break;
				case 0x8:
					tmp = _("Chat");
					break;
				case 0x10:
					tmp = _("Get File");
					break;
				case 0x20:
					tmp = _("Send File");
					break;
				default:
					tmp = NULL;
					break;
			}
			if (tmp)
				i += g_snprintf(buf+i, sizeof(buf)-i, "%s%s", (count ? ", " : ""), tmp);
			count++;
		}
		bit <<= 1;
	}
	return buf;
}

static void update_idle_time(struct buddy_show *bs) {
	/* this also updates the tooltip since that has idle time in it */
	char idlet[16];
	time_t t;
	int ihrs, imin;
	struct buddy *b;

	char infotip[256];
	char warn[256];
	char caps[256];
	char *sotime, *itime;

	time(&t);
	if (g_slist_length(bs->connlist) == 1) {
		b = find_buddy(bs->connlist->data, bs->name);
		if (!b) return;
		ihrs = (t - b->idle) / 3600; imin = ((t - b->idle) / 60) % 60;

		if (ihrs)
			g_snprintf(idlet, sizeof idlet, "(%d:%02d)", ihrs, imin);
		else
			g_snprintf(idlet, sizeof idlet, "(%d)", imin);

		gtk_widget_hide(bs->idle);
		if (b->idle)
			gtk_label_set(GTK_LABEL(bs->idle), idlet);
		else
			gtk_label_set(GTK_LABEL(bs->idle), "");
		if (display_options & OPT_DISP_SHOW_IDLETIME)
			gtk_widget_show(bs->idle);

		/* now we do the tooltip */
		sotime = sec_to_text(t - b->signon +
				((struct gaim_connection *)bs->connlist->data)->correction_time);

		if (b->idle)
			itime = sec_to_text(t - b->idle);
		else {
			itime = g_malloc(1); itime[0] = 0;
		}

		if (b->evil)
			g_snprintf(warn, sizeof warn, _("Warnings: %d%%\n"), b->evil);
		else
			warn[0] = '\0';

		if (b->caps)
			g_snprintf(caps, sizeof caps, _("Capabilities: %s\n"), caps_string(b->caps));
		else
			caps[0] = '\0';

		g_snprintf(infotip, sizeof infotip, _("Alias: %s               \nScreen Name: %s\n"
							"Logged in: %s\n%s%s%s%s%s"),
							b->show, b->name, sotime, warn,
							(b->idle ? _("Idle: ") : ""), itime,
							(b->idle ? "\n" : ""), caps);

		gtk_tooltips_set_tip(tips, GTK_WIDGET(bs->item), infotip, "");

		g_free(sotime);
		g_free(itime);
	} else {
		/* FIXME */
	}
}

void update_idle_times() {
	GSList *grp = shows;
	GSList *mem;
	struct buddy_show *b;
	struct group_show *g;

	while (grp) {
		g = (struct group_show *)grp->data;
		mem = g->members;
		while (mem) {
			b = (struct buddy_show *)mem->data;
			update_idle_time(b);
			mem = mem->next;
		}
		grp = grp->next;
	}
}

void set_buddy(struct gaim_connection *gc, struct buddy *b)
{
	struct group *g = find_group_by_buddy(gc, b->name);
	struct group_show *gs;
	struct buddy_show *bs;
	GdkPixmap *pm;
	GdkBitmap *bm;
	char **xpm = NULL;

	if (!blist) return;

	if (b->present) {
		if ((gs = find_group_show(g->name)) == NULL)
			gs = new_group_show(g->name);
		if ((bs = find_buddy_show(gs, b->name)) == NULL)
			bs = new_buddy_show(gs, b, (char **)login_icon_xpm);
		if (b->present == 1) {
			play_sound(BUDDY_ARRIVE);
			pm = gdk_pixmap_create_from_xpm_d(blist->window, &bm,
							NULL, (char **)login_icon_xpm);
			gtk_widget_hide(bs->pix);
			gtk_pixmap_set(GTK_PIXMAP(bs->pix), pm, bm);
			gtk_widget_show(bs->pix);
			if (ticker_prefs & OPT_DISP_SHOW_BUDDYTICKER) {
				BuddyTickerAddUser(b->name, pm, bm);
				gtk_timeout_add(10000, (GtkFunction)BuddyTickerLogonTimeout, b->name);
			}
			gdk_pixmap_unref(pm);
			gdk_bitmap_unref(bm);
			b->present = 2;
			if (!g_slist_find(bs->connlist, gc))
				bs->connlist = g_slist_append(bs->connlist, gc);
			else
				debug_printf("already got signon for %s from %s\n", b->name, gc->username);
			if (bs->log_timer > 0)
				gtk_timeout_remove(bs->log_timer);
			bs->log_timer = gtk_timeout_add(10000, (GtkFunction)log_timeout, bs);
			update_num_group(gs);
			if (display_options & OPT_DISP_SHOW_LOGON) {
				struct conversation *c = find_conversation(b->name);
				if (c) {
					char tmp[1024];
					g_snprintf(tmp, sizeof(tmp), _("<HR><B>%s logged in%s%s.</B><BR><HR>"), b->name,
							((display_options & OPT_DISP_SHOW_TIME) ? " @ " : ""),
							((display_options & OPT_DISP_SHOW_TIME) ? date() : ""));
					write_to_conv(c, tmp, WFLAG_SYSTEM, NULL);
				}
			}
		} else if (bs->log_timer == 0) {
			if (gc->prpl->list_icon)
				xpm = (*gc->prpl->list_icon)(b->uc);
			if (xpm == NULL)
				xpm = (char **)no_icon_xpm;
			pm = gdk_pixmap_create_from_xpm_d(blist->window, &bm, NULL, xpm);
			gtk_widget_hide(bs->pix);
			gtk_pixmap_set(GTK_PIXMAP(bs->pix), pm, bm);
			gtk_widget_show(bs->pix);
			if (ticker_prefs & OPT_DISP_SHOW_BUDDYTICKER)
				BuddyTickerSetPixmap(b->name, pm, bm);
			gdk_pixmap_unref(pm);
			gdk_bitmap_unref(bm);
		}
		update_idle_time(bs);
	} else {
		gs = find_group_show(g->name);
		if (!gs) return;
		bs = find_buddy_show(gs, b->name);
		if (!bs) return;
		if (!bs->connlist) return; /* we won't do signoff updates for
					      buddies that have already signed
					      off */
		play_sound(BUDDY_LEAVE);

		bs->connlist = g_slist_remove(bs->connlist, gc);
		if (bs->log_timer > 0)
			gtk_timeout_remove(bs->log_timer);
		bs->log_timer = gtk_timeout_add(10000, (GtkFunction)log_timeout, bs);
		update_num_group(gs);
		pm = gdk_pixmap_create_from_xpm_d(blist->window, &bm, NULL, logout_icon_xpm);
		gtk_widget_hide(bs->pix);
		gtk_pixmap_set(GTK_PIXMAP(bs->pix), pm, bm);
		gtk_widget_show(bs->pix);
		if (ticker_prefs & OPT_DISP_SHOW_BUDDYTICKER) {
			BuddyTickerSetPixmap(b->name, pm, bm);
			gtk_timeout_add(10000, (GtkFunction)BuddyTickerLogoutTimeout, b->name);
		}
		gdk_pixmap_unref(pm);
		gdk_bitmap_unref(bm);
		if (display_options & OPT_DISP_SHOW_LOGON) {
			struct conversation *c = find_conversation(b->name);
			if (c) {
				char tmp[1024];
				g_snprintf(tmp, sizeof(tmp), _("<HR><B>%s logged out%s%s.</B><BR><HR>"), b->name,
						((display_options & OPT_DISP_SHOW_TIME) ? " @ " : ""),
						((display_options & OPT_DISP_SHOW_TIME) ? date() : ""));
				write_to_conv(c, tmp, WFLAG_SYSTEM, NULL);
			}
		}
	}
}


static void move_blist_window(GtkWidget *w, GdkEventConfigure *e, void *dummy)
{
        int x, y, width, height;
        int save = 0;
        gdk_window_get_position(blist->window, &x, &y);
        gdk_window_get_size(blist->window, &width, &height);

        if(e->send_event) { /* Is a position event */
                if (blist_pos.x != x || blist_pos.y != y)
                        save = 1;
                blist_pos.x = x;
                blist_pos.y = y;
        } else { /* Is a size event */
                if (blist_pos.xoff != x || blist_pos.yoff != y ||
                   blist_pos.width != width || blist_pos.width != width)
                        save = 1;

                blist_pos.width = width;
                blist_pos.height = height;
                blist_pos.xoff = x;
                blist_pos.yoff = y;
        }

        if (save)
                save_prefs();

}


/*******************************************************************
 *
 * Helper funs for making the menu
 *
 *******************************************************************/

void gaim_seperator(GtkWidget *menu)
{
	GtkWidget *sep, *menuitem;
	sep = gtk_hseparator_new();
	menuitem = gtk_menu_item_new();
	gtk_menu_append(GTK_MENU(menu), menuitem);
	gtk_container_add(GTK_CONTAINER(menuitem), sep);
	gtk_widget_set_sensitive(menuitem, FALSE);
	gtk_widget_show(menuitem);
	gtk_widget_show(sep);
}

GtkWidget *gaim_new_item(GtkWidget *menu, const char *str, GtkSignalFunc sf)
{
	GtkWidget *menuitem;
	menuitem = gtk_menu_item_new_with_label(str);
        if (menu)
		gtk_menu_append(GTK_MENU(menu), menuitem);
	gtk_widget_show(menuitem);
	if (sf)
		gtk_signal_connect(GTK_OBJECT(menuitem), "activate", sf, NULL);
	return menuitem;
}

GtkWidget *gaim_new_item_with_pixmap(GtkWidget *menu, const char *str, char **xpm, GtkSignalFunc sf)
{
	GtkWidget *menuitem;
	GtkWidget *hbox;
	GtkWidget *label;
	GtkWidget *pixmap;
	GdkPixmap *pm;
	GdkBitmap *mask;

	menuitem = gtk_menu_item_new();
	gtk_widget_show(menuitem);

	/* Create our container */
	hbox = gtk_hbox_new(FALSE, 2);

	/* Create our pixmap and pack it */
	gtk_widget_realize(menu->parent);
	pm = gdk_pixmap_create_from_xpm_d(menu->parent->window, &mask, NULL, xpm);

	pixmap = gtk_pixmap_new(pm, mask);
	gtk_widget_show(pixmap);
	gdk_pixmap_unref(pm);
	gdk_bitmap_unref(mask);

	gtk_box_pack_start(GTK_BOX(hbox), pixmap, FALSE, FALSE, 2);

	/* Create our label and pack it */

	label = gtk_label_new(str);
	gtk_widget_show(label);
	gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 2);

	/* And finally, pack our box within our menu item */

	gtk_container_add(GTK_CONTAINER(menuitem), hbox);
	gtk_widget_show(hbox);
	
        if (menu)
		gtk_menu_append(GTK_MENU(menu), menuitem);

	if (sf)
		gtk_signal_connect(GTK_OBJECT(menuitem), "activate", sf, NULL);
	return menuitem;
}



void build_imchat_box(gboolean on)
{
	if (on) {
		if (imchatbox) return;

		imbutton   = gtk_button_new_with_label(_("IM"));
		infobutton = gtk_button_new_with_label(_("Info"));
		chatbutton = gtk_button_new_with_label(_("Chat"));

		imchatbox  = gtk_hbox_new(TRUE, 10);

		if (display_options & OPT_DISP_COOL_LOOK)
		{
			gtk_button_set_relief(GTK_BUTTON(imbutton), GTK_RELIEF_NONE);
			gtk_button_set_relief(GTK_BUTTON(infobutton), GTK_RELIEF_NONE);
			gtk_button_set_relief(GTK_BUTTON(chatbutton), GTK_RELIEF_NONE);
		}

		/* Put the buttons in the hbox */
		gtk_widget_show(imbutton);
		gtk_widget_show(chatbutton);
		gtk_widget_show(infobutton);

		gtk_box_pack_start(GTK_BOX(imchatbox), imbutton, TRUE, TRUE, 0);
		gtk_box_pack_start(GTK_BOX(imchatbox), infobutton, TRUE, TRUE, 0);
		gtk_box_pack_start(GTK_BOX(imchatbox), chatbutton, TRUE, TRUE, 0);
		gtk_container_border_width(GTK_CONTAINER(imchatbox), 10);

		gtk_signal_connect(GTK_OBJECT(imbutton), "clicked", GTK_SIGNAL_FUNC(im_callback), buddies);
		gtk_signal_connect(GTK_OBJECT(infobutton), "clicked", GTK_SIGNAL_FUNC(info_callback), buddies);
		gtk_signal_connect(GTK_OBJECT(chatbutton), "clicked", GTK_SIGNAL_FUNC(chat_callback), buddies);

		gtk_tooltips_set_tip(tips,infobutton, _("Information on selected Buddy"), "Penguin");
		gtk_tooltips_set_tip(tips,imbutton, _("Send Instant Message"), "Penguin");
		gtk_tooltips_set_tip(tips,chatbutton, _("Start/join a Buddy Chat"), "Penguin");

		gtk_box_pack_start(GTK_BOX(buddypane), imchatbox, FALSE, FALSE, 0);

		gtk_widget_show(imchatbox);
	} else {
		if (imchatbox)
			gtk_widget_destroy(imchatbox);
		imchatbox = NULL;
	}
}



void show_buddy_list()
{
	
	/* Build the buddy list, based on *config */
        
	GtkWidget *sw;
	GtkWidget *menu;
	GtkWidget *findmenu;
#ifdef USE_PERL
	GtkWidget *perlmenu;
#endif
	GtkWidget *setmenu;
	GtkWidget *menubar;
	GtkWidget *vbox;
	GtkWidget *menuitem;
        GtkWidget *notebook;
        GtkWidget *label;
        GtkWidget *bbox;
        GtkWidget *tbox;

	if (blist) {
		gtk_widget_show(blist);
		return;
	}


#ifdef USE_APPLET
        blist = gtk_window_new(GTK_WINDOW_DIALOG);
#else
        blist = gtk_window_new(GTK_WINDOW_TOPLEVEL);
#endif
        
        gtk_window_set_wmclass(GTK_WINDOW(blist), "buddy_list", "Gaim" );

	gtk_widget_realize(blist);
        aol_icon(blist->window);
        
        gtk_window_set_policy(GTK_WINDOW(blist), TRUE, TRUE, TRUE);
        
	menubar = gtk_menu_bar_new();
	
	menu = gtk_menu_new();


	menuitem = gaim_new_item(NULL, _("File"), NULL);
	gtk_menu_item_set_submenu(GTK_MENU_ITEM(menuitem), menu);
	gtk_menu_bar_append(GTK_MENU_BAR(menubar), menuitem);

	// gaim_new_item(menu, _("Add A Buddy"), GTK_SIGNAL_FUNC(add_buddy_callback));
	gaim_new_item_with_pixmap(menu, _("Add A Buddy"), add_small_xpm, GTK_SIGNAL_FUNC(add_buddy_callback));
	gaim_new_item_with_pixmap(menu, _("Join A Chat"), pounce_small_xpm, GTK_SIGNAL_FUNC(chat_callback));
        gaim_seperator(menu);
        gaim_new_item_with_pixmap(menu, _("Import Buddy List"), import_small_xpm, GTK_SIGNAL_FUNC(import_callback));
        gaim_new_item_with_pixmap(menu, _("Export Buddy List"), export_small_xpm,GTK_SIGNAL_FUNC(export_callback));
	gaim_seperator(menu);
	gaim_new_item_with_pixmap(menu, _("Signoff"), logout_icon_xpm, GTK_SIGNAL_FUNC(signoff_all));

#ifndef USE_APPLET
	gaim_new_item_with_pixmap(menu, _("Quit"), exit_small_xpm, GTK_SIGNAL_FUNC(do_quit));
#else
	gaim_new_item_with_pixmap(menu, _("Close"), close_small_xpm, GTK_SIGNAL_FUNC(applet_destroy_buddy));
#endif

	menu = gtk_menu_new();

	menuitem = gaim_new_item(NULL, _("Tools"), NULL);
	gtk_menu_item_set_submenu(GTK_MENU_ITEM(menuitem), menu);
	gtk_menu_bar_append(GTK_MENU_BAR(menubar), menuitem);

	awaymenu = gtk_menu_new();
	menuitem = gaim_new_item_with_pixmap(menu, _("Away"), away_small_xpm, NULL);
	gtk_menu_item_set_submenu(GTK_MENU_ITEM(menuitem), awaymenu);
        do_away_menu();

        bpmenu = gtk_menu_new();
        menuitem = gaim_new_item_with_pixmap(menu, _("Buddy Pounce"), pounce_small_xpm, NULL);
        gtk_menu_item_set_submenu(GTK_MENU_ITEM(menuitem), bpmenu);
        do_bp_menu();

        gaim_seperator(menu);

	findmenu = gtk_menu_new();
	gtk_widget_show(findmenu);
	menuitem = gaim_new_item_with_pixmap(menu, _("Search for Buddy"), search_small_xpm, NULL);
	gtk_menu_item_set_submenu(GTK_MENU_ITEM(menuitem), findmenu);
	gtk_widget_show(menuitem);
	menuitem = gtk_menu_item_new_with_label(_("by Email"));
	gtk_menu_append(GTK_MENU(findmenu), menuitem);
	gtk_signal_connect(GTK_OBJECT(menuitem), "activate", GTK_SIGNAL_FUNC(show_find_email), NULL);
	gtk_widget_show(menuitem);
	menuitem = gtk_menu_item_new_with_label(_("by Dir Info"));
	gtk_menu_append(GTK_MENU(findmenu), menuitem);
	gtk_signal_connect(GTK_OBJECT(menuitem), "activate", GTK_SIGNAL_FUNC(show_find_info), NULL);
 	gtk_widget_show(menuitem);

	setmenu = gtk_menu_new();
	gtk_widget_show(setmenu);
	//menuitem = gaim_new_item(menu, _("Settings"), NULL);
	menuitem = gaim_new_item_with_pixmap(menu, _("Settings"), prefs_small_xpm, NULL);
	gtk_menu_item_set_submenu(GTK_MENU_ITEM(menuitem), setmenu);
	//gtk_widget_show(menuitem);
	menuitem = gtk_menu_item_new_with_label(_("User Info"));
	gtk_menu_append(GTK_MENU(setmenu), menuitem);
	gtk_signal_connect(GTK_OBJECT(menuitem), "activate", GTK_SIGNAL_FUNC(show_set_info), NULL);
	gtk_widget_show(menuitem);
	menuitem = gtk_menu_item_new_with_label(_("Directory Info"));
	gtk_menu_append(GTK_MENU(setmenu), menuitem);
	gtk_signal_connect(GTK_OBJECT(menuitem), "activate", GTK_SIGNAL_FUNC(show_set_dir), NULL);	
	gtk_widget_show(menuitem);
	menuitem = gtk_menu_item_new_with_label(_("Change Password"));
	gtk_menu_append(GTK_MENU(setmenu), menuitem);
	gtk_signal_connect(GTK_OBJECT(menuitem), "activate", GTK_SIGNAL_FUNC(show_change_passwd), NULL);
	gtk_widget_show(menuitem);
#ifndef NO_MULTI
        gaim_new_item_with_pixmap(menu, _("Accounts"), add_small_xpm, GTK_SIGNAL_FUNC(account_editor));
#endif
	gaim_seperator(menu);

        gaim_new_item_with_pixmap(menu, _("Preferences"), prefs_small_xpm, GTK_SIGNAL_FUNC(show_prefs));

#ifdef GAIM_PLUGINS
        gaim_new_item_with_pixmap(menu, _("Plugins"), plugins_small_xpm, GTK_SIGNAL_FUNC(show_plugins));
#endif
#ifdef USE_PERL
	perlmenu = gtk_menu_new();
	gtk_widget_show(perlmenu);
	menuitem = gaim_new_item_with_pixmap(menu, _("Perl"), plugins_small_xpm, NULL);
	gtk_menu_item_set_submenu(GTK_MENU_ITEM(menuitem), perlmenu);
	gtk_widget_show(menuitem);
	menuitem = gtk_menu_item_new_with_label(_("Load Script"));
	gtk_menu_append(GTK_MENU(perlmenu), menuitem);
	gtk_signal_connect(GTK_OBJECT(menuitem), "activate", GTK_SIGNAL_FUNC(load_perl_script), NULL);
	gtk_widget_show(menuitem);
	menuitem = gtk_menu_item_new_with_label(_("Unload All Scripts"));
	gtk_menu_append(GTK_MENU(perlmenu), menuitem);
	gtk_signal_connect(GTK_OBJECT(menuitem), "activate", GTK_SIGNAL_FUNC(unload_perl_scripts), NULL);
	gtk_widget_show(menuitem);
	menuitem = gtk_menu_item_new_with_label(_("List Scripts"));
	gtk_menu_append(GTK_MENU(perlmenu), menuitem);
	gtk_signal_connect(GTK_OBJECT(menuitem), "activate", GTK_SIGNAL_FUNC(list_perl_scripts), NULL);
	gtk_widget_show(menuitem);
#endif

	menu = gtk_menu_new();

	menuitem = gaim_new_item(NULL, _("Help"), NULL);
	gtk_menu_item_set_submenu(GTK_MENU_ITEM(menuitem), menu);
	gtk_menu_item_right_justify(GTK_MENU_ITEM(menuitem));
	gtk_menu_bar_append(GTK_MENU_BAR(menubar), menuitem);
	
	gaim_new_item_with_pixmap(menu, _("About Gaim"), about_small_xpm, show_about);

        gtk_widget_show(menubar);

	vbox       = gtk_vbox_new(FALSE, 5);
        
        notebook = gtk_notebook_new();

 
        

        /* Do buddy list stuff */

        buddypane = gtk_vbox_new(FALSE, 0);
        
	buddies    = gtk_tree_new();
	sw         = gtk_scrolled_window_new(NULL, NULL);
	
	tips = gtk_tooltips_new();
	gtk_object_set_data(GTK_OBJECT(blist), _("Buddy List"), tips);
	
	/* Now the buddy list */
	gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(sw),buddies);
	gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(sw),
				       GTK_POLICY_AUTOMATIC,GTK_POLICY_AUTOMATIC);
	gtk_widget_set_usize(sw,200,200);
	gtk_widget_show(buddies);
	gtk_widget_show(sw);

        gtk_box_pack_start(GTK_BOX(buddypane), sw, TRUE, TRUE, 0);
        gtk_widget_show(buddypane);

	if (!(display_options & OPT_DISP_NO_BUTTONS))
		build_imchat_box(TRUE);


        /* Swing the edit buddy */
        editpane = gtk_vbox_new(FALSE, 0);

        
       	addbutton = gtk_button_new_with_label(_("Add"));
       	groupbutton = gtk_button_new_with_label(_("Group"));
       	rembutton = gtk_button_new_with_label(_("Remove"));
	
	if (display_options & OPT_DISP_COOL_LOOK)
	{
		gtk_button_set_relief(GTK_BUTTON(addbutton), GTK_RELIEF_NONE);
		gtk_button_set_relief(GTK_BUTTON(groupbutton), GTK_RELIEF_NONE);
		gtk_button_set_relief(GTK_BUTTON(rembutton), GTK_RELIEF_NONE);
	}
	
	edittree = gtk_ctree_new(1, 0);
	gtk_ctree_set_line_style (GTK_CTREE(edittree), GTK_CTREE_LINES_SOLID);
        gtk_ctree_set_expander_style(GTK_CTREE(edittree), GTK_CTREE_EXPANDER_SQUARE);
	gtk_clist_set_reorderable(GTK_CLIST(edittree), TRUE);

	gtk_ctree_set_drag_compare_func (GTK_CTREE(edittree),
                                      (GtkCTreeCompareDragFunc)edit_drag_compare_func);

	
	gtk_signal_connect_after (GTK_OBJECT (edittree), "tree_move",
				  GTK_SIGNAL_FUNC (edit_tree_move), NULL);

	
	bbox = gtk_hbox_new(TRUE, 10);
       	tbox = gtk_scrolled_window_new(NULL, NULL);
       	/* Put the buttons in the box */
       	gtk_box_pack_start(GTK_BOX(bbox), addbutton, TRUE, TRUE, 0);
       	gtk_box_pack_start(GTK_BOX(bbox), groupbutton, TRUE, TRUE, 0);
       	gtk_box_pack_start(GTK_BOX(bbox), rembutton, TRUE, TRUE, 0);

	gtk_tooltips_set_tip(tips, addbutton, _("Add a new Buddy"), "Penguin");
	gtk_tooltips_set_tip(tips, groupbutton, _("Add a new Group"), "Penguin");
	gtk_tooltips_set_tip(tips, rembutton, _("Remove selected Buddy"), "Penguin");

       	/* And the boxes in the box */
       	gtk_box_pack_start(GTK_BOX(editpane), tbox, TRUE, TRUE, 5);
       	gtk_box_pack_start(GTK_BOX(editpane), bbox, FALSE, FALSE, 5);

	/* Handle closes right */

	

       	/* Finish up */
       	gtk_widget_show(addbutton);
       	gtk_widget_show(groupbutton);
       	gtk_widget_show(rembutton);
       	gtk_widget_show(edittree);
       	gtk_widget_show(tbox);
       	gtk_widget_show(bbox);
	gtk_widget_show(editpane);


	
	update_button_pix();



        label = gtk_label_new(_("Online"));
        gtk_notebook_append_page(GTK_NOTEBOOK(notebook), buddypane, label);
        label = gtk_label_new(_("Edit Buddies"));
	gtk_notebook_append_page(GTK_NOTEBOOK(notebook), editpane, label);

        gtk_widget_show_all(notebook);

	/* Pack things in the vbox */
        gtk_widget_show(vbox);


        gtk_widget_show(notebook);

        /* Enable buttons */
	
       	gtk_signal_connect(GTK_OBJECT(rembutton), "clicked", GTK_SIGNAL_FUNC(do_del_buddy), edittree);
       	gtk_signal_connect(GTK_OBJECT(addbutton), "clicked", GTK_SIGNAL_FUNC(add_buddy_callback), NULL);
       	gtk_signal_connect(GTK_OBJECT(groupbutton), "clicked", GTK_SIGNAL_FUNC(add_group_callback), NULL);
        gtk_box_pack_start(GTK_BOX(vbox), menubar, FALSE, TRUE, 0);
	gtk_box_pack_start(GTK_BOX(vbox), notebook, TRUE, TRUE, 0);

        gtk_container_add(GTK_CONTAINER(blist), vbox);

#ifndef USE_APPLET
        gtk_signal_connect(GTK_OBJECT(blist), "delete_event", GTK_SIGNAL_FUNC(do_quit), blist);
#else
	gtk_signal_connect(GTK_OBJECT(blist), "delete_event", GTK_SIGNAL_FUNC(applet_destroy_buddy), NULL);
#endif

        gtk_signal_connect(GTK_OBJECT(blist), "configure_event", GTK_SIGNAL_FUNC(move_blist_window), NULL);



        /* The edit tree */
        gtk_container_add(GTK_CONTAINER(tbox), edittree);
       	gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(tbox),
                                       GTK_POLICY_NEVER,GTK_POLICY_AUTOMATIC);


        gtk_window_set_title(GTK_WINDOW(blist), _("Gaim - Buddy List"));

        if (general_options & OPT_GEN_SAVED_WINDOWS) {
                if (blist_pos.width != 0) { /* Sanity check! */
                        gtk_widget_set_uposition(blist, blist_pos.x - blist_pos.xoff, blist_pos.y - blist_pos.yoff);
                        gtk_widget_set_usize(blist, blist_pos.width, blist_pos.height);
                }
        }
}

void refresh_buddy_window()
{
        build_edit_tree();
        
        update_button_pix();
        gtk_widget_show(blist);
}