changeset 5234:890b29f00b68

[gaim-migrate @ 5604] Chats in the buddy list! You can now put chat rooms in your buddy list, and double-click them to join them, instead of having to do all that typing. I'm eventually gonna add auto-join support, so that ugly hack involving pouncing can go away. Someone should make some new artwork so we don't have 2 + icons next to each other in the menus. This also has some fixes to let gaim compile again, after the renaming of the buddy list files. This also fixes the problem with offline buddies not showing up in the list sometimes for accounts that didn't log in at startup. This probably fixes other stuff, but I can't remember any of it off the top of my head. I'm going to stop typing and let people play with this now. committer: Tailor Script <tailor@pidgin.im>
author Nathan Walp <nwalp@pidgin.im>
date Sat, 26 Apr 2003 20:30:43 +0000
parents 202105dbed8f
children 1dc233cc3349
files plugins/docklet/docklet.c plugins/ticker/ticker.c src/blist.c src/blist.h src/buddy_chat.c src/core.h src/dialogs.c src/gaim.h src/gtkblist.c src/gtkblist.h src/multi.c src/multi.h src/protocols/irc/irc.c src/protocols/jabber/jabber.c src/protocols/napster/napster.c src/protocols/oscar/oscar.c src/protocols/toc/toc.c src/protocols/zephyr/zephyr.c src/prpl.h src/server.c src/ui.h src/util.c
diffstat 22 files changed, 1171 insertions(+), 328 deletions(-) [+]
line wrap: on
line diff
--- a/plugins/docklet/docklet.c	Sat Apr 26 20:05:01 2003 +0000
+++ b/plugins/docklet/docklet.c	Sat Apr 26 20:30:43 2003 +0000
@@ -32,7 +32,7 @@
 #include "gaim.h"
 #include "sound.h"
 #include "eggtrayicon.h"
-#include "gtklist.h"
+#include "gtkblist.h"
 
 #define DOCKLET_PLUGIN_ID "gtk-docklet"
 
--- a/plugins/ticker/ticker.c	Sat Apr 26 20:05:01 2003 +0000
+++ b/plugins/ticker/ticker.c	Sat Apr 26 20:30:43 2003 +0000
@@ -30,8 +30,8 @@
 #include "gaim.h"
 #include "prpl.h"
 #include "gtkplugin.h"
-#include "list.h"
-#include "gtklist.h"
+#include "blist.h"
+#include "gtkblist.h"
 #ifdef _WIN32
 #include "win32dep.h"
 #endif
@@ -111,7 +111,8 @@
 	if(!td->icon)
 		td->icon = gtk_image_new();
 
-	pixbuf = gaim_gtk_blist_get_status_icon(b, GAIM_STATUS_ICON_SMALL);
+	pixbuf = gaim_gtk_blist_get_status_icon((GaimBlistNode*)b,
+			GAIM_STATUS_ICON_SMALL);
 	gtk_image_set_from_pixbuf(GTK_IMAGE(td->icon), pixbuf);
 	g_object_unref(G_OBJECT(pixbuf));
 }
--- a/src/blist.c	Sat Apr 26 20:05:01 2003 +0000
+++ b/src/blist.c	Sat Apr 26 20:30:43 2003 +0000
@@ -198,6 +198,20 @@
 	if (ops)
 		ops->update(gaimbuddylist, (GaimBlistNode*)buddy);
 }
+
+void gaim_blist_alias_chat(struct chat *chat, const char *alias)
+{
+	struct gaim_blist_ui_ops *ops = gaimbuddylist->ui_ops;
+
+	if(!alias || !strlen(alias))
+		return;
+
+	g_free(chat->alias);
+	chat->alias = g_strdup(alias);
+	if(ops)
+		ops->update(gaimbuddylist, (GaimBlistNode*)chat);
+}
+
 void  gaim_blist_alias_buddy (struct buddy *buddy, const char *alias)
 {
 	struct gaim_blist_ui_ops *ops = gaimbuddylist->ui_ops;
@@ -227,6 +241,30 @@
 	if (ops)
 		ops->update(gaimbuddylist, (GaimBlistNode*)group);
 }
+
+struct chat *gaim_chat_new(struct gaim_account *account, const char *alias, GHashTable *components)
+{
+	struct chat *chat;
+	struct gaim_blist_ui_ops *ops;
+
+	if(!alias || !strlen(alias) || !components)
+		return NULL;
+
+	chat = g_new0(struct chat, 1);
+	chat->account = account;
+	chat->alias = g_strdup(alias);
+	chat->components = components;
+
+	((GaimBlistNode*)chat)->type = GAIM_BLIST_CHAT_NODE;
+
+	ops = gaim_get_blist_ui_ops();
+
+	if (ops != NULL && ops->new_node != NULL)
+		ops->new_node((GaimBlistNode *)chat);
+
+	return chat;
+}
+
 struct buddy *gaim_buddy_new(struct gaim_account *account, const char *screenname, const char *alias)
 {
 	struct buddy *b;
@@ -246,6 +284,62 @@
 
 	return b;
 }
+void gaim_blist_add_chat(struct chat *chat, struct group *group, GaimBlistNode *node)
+{
+	GaimBlistNode *n = node, *cnode = (GaimBlistNode*)chat;
+	struct group *g = group;
+	struct gaim_blist_ui_ops *ops = gaimbuddylist->ui_ops;
+	gboolean save = FALSE;
+
+	if (!n) {
+		if (!g) {
+			g = gaim_group_new(_("Chats"));
+			gaim_blist_add_group(g, NULL);
+		}
+		n = gaim_blist_get_last_child((GaimBlistNode*)g);
+	} else {
+		g = (struct group*)n->parent;
+	}
+
+	/* if we're moving to overtop of ourselves, do nothing */
+	if(cnode == n)
+		return;
+
+	if (cnode->parent) {
+		/* This chat was already in the list and is
+		 * being moved.
+		 */
+		if(cnode->next)
+			cnode->next->prev = cnode->prev;
+		if(cnode->prev)
+			cnode->prev->next = cnode->next;
+		if(cnode->parent->child == cnode)
+			cnode->parent->child = cnode->next;
+
+		ops->remove(gaimbuddylist, cnode);
+
+		save = TRUE;
+	}
+
+	if (n) {
+		if(n->next)
+			n->next->prev = cnode;
+		cnode->next = n->next;
+		cnode->prev = n;
+		cnode->parent = n->parent;
+		n->next = cnode;
+	} else {
+		((GaimBlistNode*)g)->child = cnode;
+		cnode->next = cnode->prev = NULL;
+		cnode->parent = (GaimBlistNode*)g;
+	}
+
+	if (ops)
+		ops->update(gaimbuddylist, (GaimBlistNode*)cnode);
+	if (save)
+		gaim_blist_save();
+}
+
 void  gaim_blist_add_buddy (struct buddy *buddy, struct group *group, GaimBlistNode *node)
 {
 	GaimBlistNode *n = node, *bnode = (GaimBlistNode*)buddy;
@@ -402,6 +496,29 @@
 	g_free(buddy);
 }
 
+void  gaim_blist_remove_chat (struct chat *chat)
+{
+	struct gaim_blist_ui_ops *ops = gaimbuddylist->ui_ops;
+
+	GaimBlistNode *gnode, *node = (GaimBlistNode*)chat;
+	struct group *group;
+
+	gnode = node->parent;
+	group = (struct group *)gnode;
+
+	if(gnode->child == node)
+		gnode->child = node->next;
+	if (node->prev)
+		node->prev->next = node->next;
+	if (node->next)
+		node->next->prev = node->prev;
+
+	ops->remove(gaimbuddylist, node);
+	g_hash_table_destroy(chat->components);
+	g_free(chat->alias);
+	g_free(chat);
+}
+
 void  gaim_blist_remove_group (struct group *group)
 {
 	struct gaim_blist_ui_ops *ops = gaimbuddylist->ui_ops;
@@ -470,9 +587,11 @@
 	while (group) {
 		buddy = group->child;
 		while (buddy) {
-			if (!gaim_utf8_strcasecmp(normalize(((struct buddy*)buddy)->name), norm_name) && account == ((struct buddy*)buddy)->account) {
-				g_free(norm_name);
-				return (struct buddy*)buddy;
+			if(GAIM_BLIST_NODE_IS_BUDDY(buddy)) {
+				if (!gaim_utf8_strcasecmp(normalize(((struct buddy*)buddy)->name), norm_name) && account == ((struct buddy*)buddy)->account) {
+					g_free(norm_name);
+					return (struct buddy*)buddy;
+				}
 			}
 			buddy = buddy->next;
 		}
@@ -515,24 +634,58 @@
 	return l;
 }
 
+void gaim_blist_add_account(struct gaim_account *account)
+{
+	struct gaim_blist_ui_ops *ops = gaimbuddylist->ui_ops;
+	GaimBlistNode *group, *buddy;
+
+	if(!gaimbuddylist)
+		return;
+
+	for(group = gaimbuddylist->root; group; group = group->next) {
+		if(!GAIM_BLIST_NODE_IS_GROUP(group))
+			continue;
+		for(buddy = group->child; buddy; buddy = buddy->next) {
+			if(GAIM_BLIST_NODE_IS_BUDDY(buddy)) {
+				if (account == ((struct buddy*)buddy)->account) {
+					if(ops)
+						ops->update(gaimbuddylist, buddy);
+				}
+			} else if(GAIM_BLIST_NODE_IS_CHAT(buddy)) {
+				if (account == ((struct chat*)buddy)->account) {
+					if(ops)
+						ops->update(gaimbuddylist, buddy);
+				}
+			}
+		}
+	}
+}
+
 void gaim_blist_remove_account(struct gaim_account *account)
 {
 	struct gaim_blist_ui_ops *ops = gaimbuddylist->ui_ops;
-	GaimBlistNode *group = gaimbuddylist->root;
-	GaimBlistNode *buddy;
+	GaimBlistNode *group, *buddy;
+
 	if (!gaimbuddylist)
 		return;
-	while (group) {
-		buddy = group->child;
-		while (buddy) {
-			if (account == ((struct buddy*)buddy)->account) {
-				((struct buddy*)buddy)->present = GAIM_BUDDY_OFFLINE;
-				if(ops)
-					ops->remove(gaimbuddylist, buddy);
+
+	for(group = gaimbuddylist->root; group; group = group->next) {
+		if(!GAIM_BLIST_NODE_IS_GROUP(group))
+			continue;
+		for(buddy = group->child; buddy; buddy = buddy->next) {
+			if(GAIM_BLIST_NODE_IS_BUDDY(buddy)) {
+				if (account == ((struct buddy*)buddy)->account) {
+					((struct buddy*)buddy)->present = GAIM_BUDDY_OFFLINE;
+					if(ops)
+						ops->remove(gaimbuddylist, buddy);
+				}
+			} else if(GAIM_BLIST_NODE_IS_CHAT(buddy)) {
+				if (account == ((struct chat*)buddy)->account) {
+					if(ops)
+						ops->remove(gaimbuddylist, buddy);
+				}
 			}
-			buddy = buddy->next;
 		}
-		group = group->next;
 	}
 }
 
@@ -918,18 +1071,24 @@
 static char *blist_parser_person_name = NULL;
 static char *blist_parser_account_name = NULL;
 static int blist_parser_account_protocol = 0;
+static char *blist_parser_chat_alias = NULL;
+static char *blist_parser_component_name = NULL;
+static char *blist_parser_component_value = NULL;
 static char *blist_parser_buddy_name = NULL;
 static char *blist_parser_buddy_alias = NULL;
 static char *blist_parser_setting_name = NULL;
 static char *blist_parser_setting_value = NULL;
 static GHashTable *blist_parser_buddy_settings = NULL;
 static GHashTable *blist_parser_group_settings = NULL;
+static GHashTable *blist_parser_chat_components = NULL;
 static int blist_parser_privacy_mode = 0;
 static GList *tag_stack = NULL;
 enum {
 	BLIST_TAG_GAIM,
 	BLIST_TAG_BLIST,
 	BLIST_TAG_GROUP,
+	BLIST_TAG_CHAT,
+	BLIST_TAG_COMPONENT,
 	BLIST_TAG_PERSON,
 	BLIST_TAG_BUDDY,
 	BLIST_TAG_NAME,
@@ -967,6 +1126,16 @@
 			struct group *g = gaim_group_new(blist_parser_group_name);
 			gaim_blist_add_group(g,NULL);
 		}
+	} else if(!strcmp(element_name, "chat")) {
+		tag_stack = g_list_prepend(tag_stack, GINT_TO_POINTER(BLIST_TAG_CHAT));
+		for(i=0; attribute_names[i]; i++) {
+			if(!strcmp(attribute_names[i], "account")) {
+				g_free(blist_parser_account_name);
+				blist_parser_account_name = g_strdup(attribute_values[i]);
+			} else if(!strcmp(attribute_names[i], "protocol")) {
+				blist_parser_account_protocol = atoi(attribute_values[i]);
+			}
+		}
 	} else if(!strcmp(element_name, "person")) {
 		tag_stack = g_list_prepend(tag_stack, GINT_TO_POINTER(BLIST_TAG_PERSON));
 		for(i=0; attribute_names[i]; i++) {
@@ -997,6 +1166,15 @@
 				blist_parser_setting_name = g_strdup(attribute_values[i]);
 			}
 		}
+	} else if(!strcmp(element_name, "component")) {
+		tag_stack = g_list_prepend(tag_stack, GINT_TO_POINTER(BLIST_TAG_COMPONENT));
+		for(i=0; attribute_names[i]; i++) {
+			if(!strcmp(attribute_names[i], "name")) {
+				g_free(blist_parser_component_name);
+				blist_parser_component_name = g_strdup(attribute_values[i]);
+			}
+		}
+
 	} else if(!strcmp(element_name, "privacy")) {
 		tag_stack = g_list_prepend(tag_stack, GINT_TO_POINTER(BLIST_TAG_PRIVACY));
 	} else if(!strcmp(element_name, "account")) {
@@ -1034,6 +1212,20 @@
 		}
 		tag_stack = g_list_delete_link(tag_stack, tag_stack);
 		blist_parser_group_settings = NULL;
+	} else if(!strcmp(element_name, "chat")) {
+		struct gaim_account *account = gaim_account_find(blist_parser_account_name,
+				blist_parser_account_protocol);
+		if(account && blist_parser_chat_alias) {
+			struct chat *chat = gaim_chat_new(account, blist_parser_chat_alias, blist_parser_chat_components);
+			struct group *g = gaim_find_group(blist_parser_group_name);
+			gaim_blist_add_chat(chat,g,NULL);
+		}
+		g_free(blist_parser_chat_alias);
+		blist_parser_chat_alias = NULL;
+		g_free(blist_parser_account_name);
+		blist_parser_account_name = NULL;
+		blist_parser_chat_components = NULL;
+		tag_stack = g_list_delete_link(tag_stack, tag_stack);
 	} else if(!strcmp(element_name, "person")) {
 		g_free(blist_parser_person_name);
 		blist_parser_person_name = NULL;
@@ -1062,6 +1254,20 @@
 		tag_stack = g_list_delete_link(tag_stack, tag_stack);
 	} else if(!strcmp(element_name, "alias")) {
 		tag_stack = g_list_delete_link(tag_stack, tag_stack);
+	} else if(!strcmp(element_name, "component")) {
+		if(!blist_parser_chat_components)
+			blist_parser_chat_components = g_hash_table_new_full(g_str_hash,
+					g_str_equal, g_free, g_free);
+		if(blist_parser_component_name && blist_parser_component_value) {
+			g_hash_table_replace(blist_parser_chat_components,
+					g_strdup(blist_parser_component_name),
+					g_strdup(blist_parser_component_value));
+		}
+		g_free(blist_parser_component_name);
+		g_free(blist_parser_component_value);
+		blist_parser_component_name = NULL;
+		blist_parser_component_value = NULL;
+		tag_stack = g_list_delete_link(tag_stack, tag_stack);
 	} else if(!strcmp(element_name, "setting")) {
 		if(GPOINTER_TO_INT(tag_stack->next->data) == BLIST_TAG_BUDDY) {
 			if(!blist_parser_buddy_settings)
@@ -1129,13 +1335,21 @@
 			blist_parser_buddy_name = g_strndup(text, text_len);
 			break;
 		case BLIST_TAG_ALIAS:
-			blist_parser_buddy_alias = g_strndup(text, text_len);
+			if(tag_stack->next &&
+					GPOINTER_TO_INT(tag_stack->next->data) == BLIST_TAG_BUDDY)
+				blist_parser_buddy_alias = g_strndup(text, text_len);
+			else if(tag_stack->next &&
+					GPOINTER_TO_INT(tag_stack->next->data) == BLIST_TAG_CHAT)
+				blist_parser_chat_alias = g_strndup(text, text_len);
 			break;
 		case BLIST_TAG_PERMIT:
 		case BLIST_TAG_BLOCK:
 		case BLIST_TAG_IGNORE:
 			blist_parser_buddy_name = g_strndup(text, text_len);
 			break;
+		case BLIST_TAG_COMPONENT:
+			blist_parser_component_value = g_strndup(text, text_len);
+			break;
 		case BLIST_TAG_SETTING:
 			blist_parser_setting_value = g_strndup(text, text_len);
 			break;
@@ -1281,6 +1495,24 @@
 	g_free(data_val);
 }
 
+static void blist_print_chat_components(gpointer key, gpointer data,
+		gpointer user_data) {
+	char *key_val;
+	char *data_val;
+	FILE *file = user_data;
+
+	if(!key || !data)
+		return;
+
+	key_val = g_markup_escape_text(key, -1);
+	data_val = g_markup_escape_text(data, -1);
+
+	fprintf(file, "\t\t\t\t<component name=\"%s\">%s</component>\n", key_val,
+			data_val);
+	g_free(key_val);
+	g_free(data_val);
+}
+
 static void gaim_blist_write(FILE *file, struct gaim_account *exp_acct) {
 	GSList *accounts, *buds;
 	GaimBlistNode *gnode,*bnode;
@@ -1299,32 +1531,43 @@
 			fprintf(file, "\t\t<group name=\"%s\">\n", group_name);
 			g_hash_table_foreach(group->settings, blist_print_group_settings, file);
 			for(bnode = gnode->child; bnode; bnode = bnode->next) {
-				if(!GAIM_BLIST_NODE_IS_BUDDY(bnode))
-					continue;
-				bud = (struct buddy *)bnode;
-				if(!exp_acct || bud->account == exp_acct) {
-					char *bud_name = g_markup_escape_text(bud->name, -1);
-					char *bud_alias = NULL;
-					char *acct_name = g_markup_escape_text(bud->account->username, -1);
-					if(bud->alias)
-						bud_alias= g_markup_escape_text(bud->alias, -1);
-					fprintf(file, "\t\t\t<person name=\"%s\">\n",
-							bud_alias ? bud_alias : bud_name);
-					fprintf(file, "\t\t\t\t<buddy protocol=\"%d\" "
-							"account=\"%s\">\n", bud->account->protocol,
-							acct_name);
-					fprintf(file, "\t\t\t\t\t<name>%s</name>\n", bud_name);
-					if(bud_alias) {
-						fprintf(file, "\t\t\t\t\t<alias>%s</alias>\n",
-								bud_alias);
+				if(GAIM_BLIST_NODE_IS_BUDDY(bnode)) {
+					bud = (struct buddy *)bnode;
+					if(!exp_acct || bud->account == exp_acct) {
+						char *bud_name = g_markup_escape_text(bud->name, -1);
+						char *bud_alias = NULL;
+						char *acct_name = g_markup_escape_text(bud->account->username, -1);
+						if(bud->alias)
+							bud_alias= g_markup_escape_text(bud->alias, -1);
+						fprintf(file, "\t\t\t<person name=\"%s\">\n",
+								bud_alias ? bud_alias : bud_name);
+						fprintf(file, "\t\t\t\t<buddy protocol=\"%d\" "
+								"account=\"%s\">\n", bud->account->protocol,
+								acct_name);
+						fprintf(file, "\t\t\t\t\t<name>%s</name>\n", bud_name);
+						if(bud_alias) {
+							fprintf(file, "\t\t\t\t\t<alias>%s</alias>\n",
+									bud_alias);
+						}
+						g_hash_table_foreach(bud->settings,
+								blist_print_buddy_settings, file);
+						fprintf(file, "\t\t\t\t</buddy>\n");
+						fprintf(file, "\t\t\t</person>\n");
+						g_free(bud_name);
+						g_free(bud_alias);
+						g_free(acct_name);
 					}
-					g_hash_table_foreach(bud->settings,
-							blist_print_buddy_settings, file);
-					fprintf(file, "\t\t\t\t</buddy>\n");
-					fprintf(file, "\t\t\t</person>\n");
-					g_free(bud_name);
-					g_free(bud_alias);
-					g_free(acct_name);
+				} else if(GAIM_BLIST_NODE_IS_CHAT(bnode)) {
+					struct chat *chat = (struct chat *)bnode;
+					if(!exp_acct || chat->account == exp_acct) {
+						char *chat_alias = g_markup_escape_text(chat->alias, -1);
+						char *acct_name = g_markup_escape_text(chat->account->username, -1);
+						fprintf(file, "\t\t\t<chat protocol=\"%d\" account=\"%s\">\n", chat->account->protocol, acct_name);
+						fprintf(file, "\t\t\t\t<alias>%s</alias>\n", chat_alias);
+						g_hash_table_foreach(chat->components,
+								blist_print_chat_components, file);
+						fprintf(file, "\t\t\t</chat>\n");
+					}
 				}
 			}
 			fprintf(file, "\t\t</group>\n");
@@ -1519,6 +1762,10 @@
 			struct buddy *b = (struct buddy *)node;
 			if(b->account->gc || offline)
 				count++;
+		} else if(GAIM_BLIST_NODE_IS_CHAT(node)) {
+			struct chat *chat = (struct chat *)node;
+			if(chat->account->gc || offline)
+				count++;
 		}
 	}
 
@@ -1537,6 +1784,8 @@
 			struct buddy *b = (struct buddy *)node;
 			if(GAIM_BUDDY_IS_ONLINE(b))
 				count++;
+		} else if(GAIM_BLIST_NODE_IS_CHAT(node)) {
+			count++;
 		}
 	}
 
--- a/src/blist.h	Sat Apr 26 20:05:01 2003 +0000
+++ b/src/blist.h	Sat Apr 26 20:30:43 2003 +0000
@@ -1,5 +1,5 @@
 /**
- * @file blist.h Buddy List API
+ * @file list.h Buddy List API
  * @ingroup core
  *
  * gaim
@@ -34,9 +34,11 @@
 enum gaim_blist_node_type {
 	GAIM_BLIST_GROUP_NODE,
 	GAIM_BLIST_BUDDY_NODE,
+	GAIM_BLIST_CHAT_NODE,
 	GAIM_BLIST_OTHER_NODE,
 };
 
+#define GAIM_BLIST_NODE_IS_CHAT(n) ((n)->type == GAIM_BLIST_CHAT_NODE)
 #define GAIM_BLIST_NODE_IS_BUDDY(n) ((n)->type == GAIM_BLIST_BUDDY_NODE)
 #define GAIM_BLIST_NODE_IS_GROUP(n) ((n)->type == GAIM_BLIST_GROUP_NODE)
 
@@ -97,6 +99,17 @@
 	GHashTable *settings;                  /**< per-group settings from the XML buddy list, set by plugins and the likes. */
 };
 
+/**
+ * A group.  This contains everything Gaim needs to put a chat room in the
+ * buddy list.
+ */
+struct chat {
+	GaimBlistNode node;      /**< The node that this chat inherits from */
+	char *alias;             /**< The display name of this chat. */
+	GHashTable *components;  /**< the stuff the protocol needs to know to join the chat */
+	struct gaim_account *account; /**< The account this chat is attached to */
+};
+
 
 /**
  * The Buddy List
@@ -174,7 +187,7 @@
 
 /**
  * Updates a buddy's status.
- * 
+ *
  * This needs to not take an int.
  *
  * @param buddy   The buddy whose status has changed
@@ -235,6 +248,13 @@
  */
 void gaim_blist_alias_buddy(struct buddy *buddy, const char *alias);
 
+/**
+ * Aliases a chat in the buddy list.
+ *
+ * @param chat  The chat whose alias will be changed.
+ * @param alias The chat's new alias.
+ */
+void gaim_blist_alias_chat(struct chat *chat, const char *alias);
 
 /**
  * Renames a group
@@ -244,6 +264,28 @@
  */
 void gaim_blist_rename_group(struct group *group, const char *name);
 
+/**
+ * Creates a new chat for the buddy list
+ *
+ * @param account    The account this chat will get added to
+ * @param alias      The alias of the new chat
+ * @param components The info the prpl needs to join the chat
+ * @return           A newly allocated chat
+ */
+struct chat *gaim_chat_new(struct gaim_account *account, const char *alias, GHashTable *components);
+
+/**
+ * Adds a new chat to the buddy list.
+ *
+ * The chat will be inserted right after node or appended to the end
+ * of group if node is NULL.  If both are NULL, the buddy will be added to
+ * the "Chats" group.
+ *
+ * @param chat  The new chat who gets added
+ * @param group  The group to add the new chat to.
+ * @param node   The insertion point
+ */
+void gaim_blist_add_chat(struct chat *chat, struct group *group, GaimBlistNode *node);
 
 /**
  * Creates a new buddy
@@ -298,6 +340,13 @@
 void gaim_blist_remove_buddy(struct buddy *buddy);
 
 /**
+ * Removes a chat from the buddy list and frees the memory allocated to it.
+ *
+ * @param chat   The chat to be removed
+ */
+void gaim_blist_remove_chat(struct chat *chat);
+
+/**
  * Removes a group from the buddy list and frees the memory allocated to it and to
  * its children
  *
@@ -365,6 +414,15 @@
 gboolean gaim_group_on_account(struct group *g, struct gaim_account *account);
 
 /**
+ * Called when an account gets signed on.  Tells the UI to update all the
+ * buddies.
+ *
+ * @param account   The account
+ */
+void gaim_blist_add_account(struct gaim_account *account);
+
+
+/**
  * Called when an account gets signed off.  Sets the presence of all the buddies to 0
  * and tells the UI to update them.
  *
--- a/src/buddy_chat.c	Sat Apr 26 20:05:01 2003 +0000
+++ b/src/buddy_chat.c	Sat Apr 26 20:30:43 2003 +0000
@@ -30,10 +30,6 @@
 #include <stdlib.h>
 #include <gtk/gtk.h>
 #include "gtkimhtml.h"
-#ifdef USE_GTKSPELL
-#include <gtkspell/gtkspell.h>
-#endif
-#include <gdk/gdkkeysyms.h>
 
 #include "prpl.h"
 
@@ -50,29 +46,27 @@
 do_join_chat()
 {
 	if (joinchat) {
-		GList *data = NULL;
+		GHashTable *components = g_hash_table_new_full(g_str_hash, g_str_equal,
+				g_free, g_free);
 		GList *tmp;
-		int *ival;
-		char *sval;
 
 		for (tmp = chatentries; tmp != NULL; tmp = tmp->next) {
 			if (g_object_get_data(tmp->data, "is_spin")) {
-				ival = g_new0(int, 1);
-				*ival = gtk_spin_button_get_value_as_int(tmp->data);
-				data = g_list_append(data, ival);
+				g_hash_table_replace(components,
+						g_strdup(g_object_get_data(tmp->data, "identifier")),
+						g_strdup_printf("%d",
+							gtk_spin_button_get_value_as_int(tmp->data)));
 			}
 			else {
-				sval = g_strdup(gtk_entry_get_text(tmp->data));
-				data = g_list_append(data, sval);
+				g_hash_table_replace(components,
+						g_strdup(g_object_get_data(tmp->data, "identifier")),
+						g_strdup(gtk_entry_get_text(tmp->data)));
 			}
 		}
 
-		serv_join_chat(joinchatgc, data);
+		serv_join_chat(joinchatgc, components);
 
-		for (tmp = data; tmp != NULL; tmp = tmp->next)
-			g_free(tmp->data);
-
-		g_list_free(data);
+		g_hash_table_destroy(components);
 
 		gtk_widget_destroy(joinchat);
 
@@ -126,6 +120,7 @@
 										pce->max, 1, 10, 10);
 			spin = gtk_spin_button_new(GTK_ADJUSTMENT(adjust), 1, 0);
 			g_object_set_data(G_OBJECT(spin), "is_spin", GINT_TO_POINTER(TRUE));
+			g_object_set_data(G_OBJECT(spin), "identifier", pce->identifier);
 			chatentries = g_list_append(chatentries, spin);
 			gtk_widget_set_size_request(spin, 50, -1);
 			gtk_box_pack_end(GTK_BOX(rowbox), spin, FALSE, FALSE, 0);
@@ -149,6 +144,7 @@
 
 			g_signal_connect(G_OBJECT(entry), "activate",
 							 G_CALLBACK(do_join_chat), NULL);
+			g_object_set_data(G_OBJECT(entry), "identifier", pce->identifier);
 
 			gtk_widget_show(entry);
 		}
@@ -165,9 +161,12 @@
 	if (joinchatgc == g)
 		return;
 
-	joinchatgc = g;
-
-	rebuild_jc();
+	if(joinchatgc->account->protocol == g->account->protocol) {
+		joinchatgc = g;
+	} else {
+		joinchatgc = g;
+		rebuild_jc();
+	}
 }
 
 static void
--- a/src/core.h	Sat Apr 26 20:05:01 2003 +0000
+++ b/src/core.h	Sat Apr 26 20:30:43 2003 +0000
@@ -117,7 +117,7 @@
 extern void serv_got_typing(struct gaim_connection *, char *, int, int);
 extern void serv_got_typing_stopped(struct gaim_connection *, char *);
 extern void serv_got_eviled(struct gaim_connection *, char *, int);
-extern void serv_got_chat_invite(struct gaim_connection *, char *, char *, char *, GList *);
+extern void serv_got_chat_invite(struct gaim_connection *, char *, char *, char *, GHashTable *);
 extern struct gaim_conversation *serv_got_joined_chat(struct gaim_connection *, int, char *);
 extern void serv_got_chat_left(struct gaim_connection *, int);
 extern void serv_got_chat_in(struct gaim_connection *, int, char *, int, char *, time_t);
--- a/src/dialogs.c	Sat Apr 26 20:05:01 2003 +0000
+++ b/src/dialogs.c	Sat Apr 26 20:30:43 2003 +0000
@@ -403,6 +403,12 @@
 	gtk_widget_show_all(w->window);
 }
 
+void do_remove_chat(struct chat *chat)
+{
+	gaim_blist_remove_chat(chat);
+	gaim_blist_save();
+}
+
 void do_remove_buddy(struct buddy *b)
 {
 	struct group *g;
@@ -433,14 +439,16 @@
 {
 	GaimBlistNode *b = ((GaimBlistNode*)g)->child;
 	while (b) {
-		struct buddy *bd = (struct buddy *)b;
-		struct gaim_conversation *c = gaim_find_conversation(bd->name);
-		if(bd->account->gc) {
-			serv_remove_buddy(bd->account->gc, bd->name, g->name);
-			gaim_blist_remove_buddy(bd);
-
-			if (c != NULL)
-				gaim_conversation_update(c, GAIM_CONV_UPDATE_REMOVE);
+		if(GAIM_BLIST_NODE_IS_BUDDY(b)) {
+			struct buddy *bd = (struct buddy *)b;
+			struct gaim_conversation *c = gaim_find_conversation(bd->name);
+			if(bd->account->gc) {
+				serv_remove_buddy(bd->account->gc, bd->name, g->name);
+				gaim_blist_remove_buddy(bd);
+
+				if (c != NULL)
+					gaim_conversation_update(c, GAIM_CONV_UPDATE_REMOVE);
+			}
 		}
 		b = b->next;
 	}
@@ -460,6 +468,13 @@
 	g_free(text);
 }
 
+void show_confirm_del_chat(struct chat *chat)
+{
+	char *text = g_strdup_printf(_("You are about to remove the chat %s from your buddy list.  Do you want to continue?"), chat->alias);
+	do_ask_dialog(_("Remove Chat"), text, chat, _("Remove Chat"), do_remove_chat, _("Cancel"), NULL, NULL, FALSE);
+	g_free(text);
+}
+
 void show_confirm_del_group(struct group *g)
 {
      	char *text = g_strdup_printf(_("You are about to remove the group %s and all its members from your buddy list.  Do you want to continue?"), 
@@ -1162,7 +1177,7 @@
 	gtk_table_attach_defaults(GTK_TABLE(table), a->account, 1, 2, 3, 4);
 
 	create_online_user_names(a);
-	
+
 	/* End of account box */
 
 	g_signal_connect(G_OBJECT(a->window), "response", G_CALLBACK(do_add_buddy), a);
@@ -1173,6 +1188,278 @@
 		gtk_entry_set_text(GTK_ENTRY(GTK_COMBO(a->combo)->entry), group);
 }
 
+struct addchat {
+    struct gaim_account *account;
+    GtkWidget *window;
+	GtkWidget *account_menu;
+	GtkWidget *alias_entry;
+	GtkWidget *group_combo;
+	GtkWidget *entries_box;
+	GtkSizeGroup *sg;
+	GList *entries;
+};
+
+static void do_add_chat(GtkWidget *w, struct addchat *ac) {
+	GHashTable *components = g_hash_table_new_full(g_str_hash, g_str_equal,
+			g_free, g_free);
+	GList *tmp;
+
+	struct chat *chat;
+	struct group *group;
+	const char *group_name;
+
+	for(tmp = ac->entries; tmp; tmp = tmp->next) {
+		if(g_object_get_data(tmp->data, "is_spin")) {
+			g_hash_table_replace(components,
+					g_strdup(g_object_get_data(tmp->data, "identifier")),
+					g_strdup_printf("%d",
+						gtk_spin_button_get_value_as_int(tmp->data)));
+		} else {
+			g_hash_table_replace(components,
+					g_strdup(g_object_get_data(tmp->data, "identifier")),
+					g_strdup(gtk_entry_get_text(tmp->data)));
+		}
+	}
+
+	chat = gaim_chat_new(ac->account, gtk_entry_get_text(GTK_ENTRY(ac->alias_entry)), components);
+
+	group_name = gtk_entry_get_text(GTK_ENTRY(GTK_COMBO(ac->group_combo)->entry));
+	if (!(group = gaim_find_group(group_name))) {
+		group = gaim_group_new(group_name);
+		gaim_blist_add_group(group, NULL);
+	}
+
+	gaim_blist_add_chat(chat, group, NULL);
+	gaim_blist_save();
+
+	gtk_widget_destroy(ac->window);
+	g_list_free(ac->entries);
+
+	g_free(ac);
+}
+
+static void do_add_chat_resp(GtkWidget *w, int resp, struct addchat *ac) {
+	if(resp == GTK_RESPONSE_OK) {
+		do_add_chat(NULL, ac);
+	} else {
+		gtk_widget_destroy(ac->window);
+		g_list_free(ac->entries);
+		g_free(ac);
+	}
+}
+
+
+static void rebuild_addchat_entries(struct addchat *ac) {
+	GList *list, *tmp;
+	struct proto_chat_entry *pce;
+
+	while(GTK_BOX(ac->entries_box)->children)
+		gtk_container_remove(GTK_CONTAINER(ac->entries_box),
+				((GtkBoxChild *)GTK_BOX(ac->entries_box)->children->data)->widget);
+
+	if(ac->entries)
+		g_list_free(ac->entries);
+
+	ac->entries = NULL;
+
+	list = GAIM_PLUGIN_PROTOCOL_INFO(ac->account->gc->prpl)->chat_info(ac->account->gc);
+
+	for(tmp = list; tmp; tmp = tmp->next) {
+		GtkWidget *label;
+		GtkWidget *rowbox;
+		pce = tmp->data;
+
+		rowbox = gtk_hbox_new(FALSE, 5);
+		gtk_box_pack_start(GTK_BOX(ac->entries_box), rowbox, FALSE, FALSE, 0);
+
+		label = gtk_label_new(pce->label);
+		gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
+		gtk_size_group_add_widget(ac->sg, label);
+		gtk_box_pack_start(GTK_BOX(rowbox), label, FALSE, FALSE, 0);
+
+		if(pce->is_int) {
+			GtkObject *adjust;
+			GtkWidget *spin;
+			adjust = gtk_adjustment_new(pce->min, pce->min, pce->max,
+					1, 10, 10);
+			spin = gtk_spin_button_new(GTK_ADJUSTMENT(adjust), 1, 0);
+			g_object_set_data(G_OBJECT(spin), "is_spin", GINT_TO_POINTER(TRUE));
+			g_object_set_data(G_OBJECT(spin), "identifier", pce->identifier);
+			ac->entries = g_list_append(ac->entries, spin);
+			gtk_widget_set_size_request(spin, 50, -1);
+			gtk_box_pack_end(GTK_BOX(rowbox), spin, FALSE, FALSE, 0);
+		} else {
+			GtkWidget *entry = gtk_entry_new();
+			g_object_set_data(G_OBJECT(entry), "identifier", pce->identifier);
+			ac->entries = g_list_append(ac->entries, entry);
+
+			if(pce->def)
+				gtk_entry_set_text(GTK_ENTRY(entry), pce->def);
+
+			gtk_box_pack_end(GTK_BOX(rowbox), entry, TRUE, TRUE, 0);
+
+			g_signal_connect(G_OBJECT(entry), "activate",
+					G_CALLBACK(do_add_chat), ac);
+		}
+	}
+
+	gtk_widget_show_all(ac->entries_box);
+}
+
+static void addchat_select_account(GObject *w, struct gaim_connection *gc)
+{
+	struct addchat *ac = g_object_get_data(w, "addchat");
+
+	if(ac->account->protocol == gc->account->protocol) {
+		ac->account = gc->account;
+	} else {
+		ac->account = gc->account;
+		rebuild_addchat_entries(ac);
+	}
+}
+
+static void create_online_account_menu_for_add_chat(struct addchat *ac)
+{
+	char buf[2048]; /* Never hurts to be safe ;-) */
+	GSList *g = connections;
+	struct gaim_connection *c;
+	GtkWidget *menu, *opt;
+	int count = 0;
+	int place = 0;
+
+	menu = gtk_menu_new();
+
+	while (g) {
+		c = (struct gaim_connection *)g->data;
+		g_snprintf(buf, sizeof(buf), "%s (%s)", 
+				c->username, c->prpl->info->name);
+		opt = gtk_menu_item_new_with_label(buf);
+		g_object_set_data(G_OBJECT(opt), "addchat", ac);
+		g_signal_connect(GTK_OBJECT(opt), "activate",
+				G_CALLBACK(addchat_select_account),
+				c);
+		gtk_widget_show(opt);
+		gtk_menu_shell_append(GTK_MENU_SHELL(menu), opt);
+
+		/* Now check to see if it's our current menu */
+		if (c->account == ac->account) {
+			place = count;
+			gtk_menu_item_activate(GTK_MENU_ITEM(opt));
+			gtk_option_menu_set_history(GTK_OPTION_MENU(ac->account_menu), count);
+
+			/* Do the cha cha cha */
+		}
+
+		count++;
+
+		g = g->next;
+	}
+
+	gtk_option_menu_remove_menu(GTK_OPTION_MENU(ac->account_menu));
+	gtk_option_menu_set_menu(GTK_OPTION_MENU(ac->account_menu), menu);
+	gtk_option_menu_set_history(GTK_OPTION_MENU(ac->account_menu), place);
+}
+
+void show_add_chat(struct gaim_account *account, struct group *group) {
+    struct addchat *ac = g_new0(struct addchat, 1);
+    struct gaim_gtk_buddy_list *gtkblist;
+
+	GtkWidget *label;
+	GtkWidget *rowbox;
+	GtkWidget *hbox;
+	GtkWidget *vbox;
+	GtkWidget *img = gtk_image_new_from_stock(GAIM_STOCK_DIALOG_QUESTION,
+			GTK_ICON_SIZE_DIALOG);
+
+    gtkblist = GAIM_GTK_BLIST(gaim_get_blist());
+
+    ac->account = account ? account :
+		((struct gaim_connection *)connections->data)->account;
+
+	ac->sg = gtk_size_group_new(GTK_SIZE_GROUP_HORIZONTAL);
+
+    ac->window = gtk_dialog_new_with_buttons(_("Add Chat"),
+            GTK_WINDOW(gtkblist->window), 0,
+            GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
+            GTK_STOCK_ADD, GTK_RESPONSE_OK,
+            NULL);
+
+    gtk_dialog_set_default_response(GTK_DIALOG(ac->window), GTK_RESPONSE_OK);
+    gtk_container_set_border_width(GTK_CONTAINER(ac->window), 6);
+    gtk_window_set_resizable(GTK_WINDOW(ac->window), FALSE);
+    gtk_dialog_set_has_separator(GTK_DIALOG(ac->window), FALSE);
+    gtk_box_set_spacing(GTK_BOX(GTK_DIALOG(ac->window)->vbox), 12);
+    gtk_container_set_border_width(GTK_CONTAINER(GTK_DIALOG(ac->window)->vbox),
+            6);
+	gtk_window_set_role(GTK_WINDOW(ac->window), "add_chat");
+
+	hbox = gtk_hbox_new(FALSE, 12);
+	gtk_container_add(GTK_CONTAINER(GTK_DIALOG(ac->window)->vbox), hbox);
+	gtk_box_pack_start(GTK_BOX(hbox), img, FALSE, FALSE, 0);
+	gtk_misc_set_alignment(GTK_MISC(img), 0, 0);
+
+	vbox = gtk_vbox_new(FALSE, 5);
+	gtk_container_add(GTK_CONTAINER(hbox), vbox);
+
+	label = gtk_label_new(_("Please enter an alias, and the appropriate information about the chat you would like to add to your buddy list.\n"));
+	gtk_widget_set_size_request(label, 400, -1);
+	gtk_label_set_line_wrap(GTK_LABEL(label), TRUE);
+	gtk_misc_set_alignment(GTK_MISC(label), 0, 0);
+	gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 0);
+
+	rowbox = gtk_hbox_new(FALSE, 5);
+	gtk_box_pack_start(GTK_BOX(vbox), rowbox, FALSE, FALSE, 0);
+
+	label = gtk_label_new(_("Account:"));
+	gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
+	gtk_size_group_add_widget(ac->sg, label);
+	gtk_box_pack_start(GTK_BOX(rowbox), label, FALSE, FALSE, 0);
+
+	ac->account_menu = gtk_option_menu_new();
+	gtk_box_pack_end(GTK_BOX(rowbox), ac->account_menu, TRUE, TRUE, 0);
+
+	create_online_account_menu_for_add_chat(ac);
+
+	rowbox = gtk_hbox_new(FALSE, 5);
+	gtk_box_pack_start(GTK_BOX(vbox), rowbox, FALSE, FALSE, 0);
+
+	label = gtk_label_new(_("Alias:"));
+	gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
+	gtk_size_group_add_widget(ac->sg, label);
+	gtk_box_pack_start(GTK_BOX(rowbox), label, FALSE, FALSE, 0);
+
+	ac->alias_entry = gtk_entry_new();
+	gtk_box_pack_end(GTK_BOX(rowbox), ac->alias_entry, TRUE, TRUE, 0);
+
+	ac->entries_box = gtk_vbox_new(FALSE, 5);
+    gtk_container_set_border_width(GTK_CONTAINER(ac->entries_box), 0);
+	gtk_box_pack_start(GTK_BOX(vbox), ac->entries_box, TRUE, TRUE, 0);
+
+	rebuild_addchat_entries(ac);
+
+	rowbox = gtk_hbox_new(FALSE, 5);
+	gtk_box_pack_start(GTK_BOX(vbox), rowbox, FALSE, FALSE, 0);
+
+    label = gtk_label_new(_("Group:"));
+	gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
+	gtk_size_group_add_widget(ac->sg, label);
+	gtk_box_pack_start(GTK_BOX(rowbox), label, FALSE, FALSE, 0);
+
+    ac->group_combo = gtk_combo_new();
+    gtk_combo_set_popdown_strings(GTK_COMBO(ac->group_combo), groups_tree());
+	gtk_box_pack_end(GTK_BOX(rowbox), ac->group_combo, TRUE, TRUE, 0);
+
+	if (group)
+		gtk_entry_set_text(GTK_ENTRY(GTK_COMBO(ac->group_combo)->entry), group->name);
+
+	g_signal_connect(G_OBJECT(ac->window), "response", G_CALLBACK(do_add_chat_resp), ac);
+
+	gtk_widget_grab_focus(ac->alias_entry);
+
+	gtk_widget_show_all(ac->window);
+}
+
+
 
 /*------------------------------------------------------------------------*
  *  Privacy Settings                                                      *
@@ -3277,6 +3564,19 @@
 	return;
 }
 
+static void do_alias_chat(GtkWidget *w, int resp, struct chat *chat)
+{
+	if(resp == GTK_RESPONSE_OK) {
+		GtkWidget *entry = g_object_get_data(G_OBJECT(w), "alias_entry");
+		const char *text = gtk_entry_get_text(GTK_ENTRY(entry));
+		if(text && strlen(text)) {
+			gaim_blist_alias_chat(chat, text);
+			gaim_blist_save();
+		}
+	}
+	gtk_widget_destroy(w);
+}
+
 static void
 do_alias_buddy(GtkWidget *w, int resp, struct alias_dialog_info *info)
 {
@@ -3296,6 +3596,75 @@
 	g_free(info);
 }
 
+void alias_dialog_chat(struct chat *chat) {
+	GtkWidget *dialog;
+	GtkWidget *hbox;
+	GtkWidget *img;
+	GtkWidget *vbox;
+	GtkWidget *label;
+	GtkWidget *alias_entry;
+
+	dialog = gtk_dialog_new_with_buttons(_("Alias Buddy"), NULL,
+			GTK_DIALOG_NO_SEPARATOR,
+			GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
+			GTK_STOCK_OK, GTK_RESPONSE_OK,
+			NULL);
+	gtk_dialog_set_default_response(GTK_DIALOG(dialog),
+										GTK_RESPONSE_OK);
+
+	gtk_container_set_border_width(GTK_CONTAINER(dialog), 6);
+	gtk_window_set_resizable(GTK_WINDOW(dialog), FALSE);
+	gtk_box_set_spacing(GTK_BOX(GTK_DIALOG(dialog)->vbox), 12);
+	gtk_container_set_border_width(
+			GTK_CONTAINER(GTK_DIALOG(dialog)->vbox), 6);
+
+	/* The main hbox container. */
+	hbox = gtk_hbox_new(FALSE, 12);
+	gtk_container_add(GTK_CONTAINER(GTK_DIALOG(dialog)->vbox), hbox);
+
+	/* The dialog image. */
+	img = gtk_image_new_from_stock(GAIM_STOCK_DIALOG_QUESTION,
+			GTK_ICON_SIZE_DIALOG);
+	gtk_box_pack_start(GTK_BOX(hbox), img, FALSE, FALSE, 0);
+	gtk_misc_set_alignment(GTK_MISC(img), 0, 0);
+
+	/* The main vbox container. */
+	vbox = gtk_vbox_new(FALSE, 0);
+	gtk_container_add(GTK_CONTAINER(hbox), vbox);
+
+	/* Setup the label containing the description. */
+	label = gtk_label_new(_("Please enter an aliased name for this chat.\n"));
+	gtk_widget_set_size_request(GTK_WIDGET(label), 350, -1);
+
+	gtk_label_set_line_wrap(GTK_LABEL(label), TRUE);
+	gtk_misc_set_alignment(GTK_MISC(label), 0, 0);
+	gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 0);
+
+	hbox = gtk_hbox_new(FALSE, 6);
+	gtk_container_add(GTK_CONTAINER(vbox), hbox);
+
+	/* The "Alias:" label. */
+	label = gtk_label_new(NULL);
+	gtk_label_set_markup_with_mnemonic(GTK_LABEL(label), _("_Alias:"));
+	gtk_misc_set_alignment(GTK_MISC(label), 0, 0);
+	gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 0);
+
+	/* The alias entry field. */
+	alias_entry = gtk_entry_new();
+	gtk_box_pack_start(GTK_BOX(hbox), alias_entry, FALSE, FALSE, 0);
+	gtk_entry_set_activates_default(GTK_ENTRY(alias_entry), TRUE);
+	gtk_label_set_mnemonic_widget(GTK_LABEL(label), alias_entry);
+
+	gtk_entry_set_text(GTK_ENTRY(alias_entry), chat->alias);
+
+	g_object_set_data(G_OBJECT(dialog), "alias_entry", alias_entry);
+
+	g_signal_connect(G_OBJECT(dialog), "response",
+			G_CALLBACK(do_alias_chat), chat);
+
+	gtk_widget_show_all(dialog);
+}
+
 void
 alias_dialog_bud(struct buddy *b)
 {
--- a/src/gaim.h	Sat Apr 26 20:05:01 2003 +0000
+++ b/src/gaim.h	Sat Apr 26 20:30:43 2003 +0000
@@ -358,6 +358,7 @@
 extern void show_set_info(struct gaim_connection *);
 extern void show_confirm_del(struct gaim_connection *, gchar *);
 extern void show_confirm_del_group(struct group *);
+extern void show_confirm_del_chat(struct chat *);
 
 /* Functions in gaimrc.c */
 extern gint sort_awaymsg_list(gconstpointer, gconstpointer);
@@ -403,7 +404,7 @@
 extern void serv_warn(struct gaim_connection *, char *, int);
 extern void serv_set_dir(struct gaim_connection *, const char *, const char *, const char *, const char *, const char *, const char *, const char *, int);
 extern void serv_dir_search(struct gaim_connection *, const char *, const char *, const char *, const char *, const char *, const char *, const char *, const char *);
-extern void serv_join_chat(struct gaim_connection *, GList *);
+extern void serv_join_chat(struct gaim_connection *, GHashTable *);
 extern void serv_chat_invite(struct gaim_connection *, int, const char *, const char *);
 extern void serv_chat_leave(struct gaim_connection *, int);
 extern void serv_chat_whisper(struct gaim_connection *, int, char *, char *);
--- a/src/gtkblist.c	Sat Apr 26 20:05:01 2003 +0000
+++ b/src/gtkblist.c	Sat Apr 26 20:30:43 2003 +0000
@@ -50,6 +50,7 @@
 #include "gtkblist.h"
 #include "gtkpounce.h"
 #include "gtkft.h"
+#include "gtkdebug.h"
 
 #ifdef _WIN32
 #include "win32dep.h"
@@ -62,7 +63,7 @@
 
 static void gaim_gtk_blist_selection_changed(GtkTreeSelection *selection, gpointer data);
 static void gaim_gtk_blist_update(struct gaim_buddy_list *list, GaimBlistNode *node);
-static char *gaim_get_tooltip_text(struct buddy *b);
+static char *gaim_get_tooltip_text(GaimBlistNode *node);
 static char *item_factory_translate_func (const char *path, gpointer func_data);
 
 /***************************************************
@@ -139,12 +140,20 @@
 
 static void gtk_blist_menu_im_cb(GtkWidget *w, struct buddy *b)
 {
-       gaim_conversation_new(GAIM_CONV_IM, b->account, b->name);
+	gaim_conversation_new(GAIM_CONV_IM, b->account, b->name);
 }
 
-static void gtk_blist_menu_alias_cb(GtkWidget *w, struct buddy *b)
+static void gtk_blist_menu_join_cb(GtkWidget *w, struct chat *chat)
 {
-       alias_dialog_bud(b);
+	serv_join_chat(chat->account->gc, chat->components);
+}
+
+static void gtk_blist_menu_alias_cb(GtkWidget *w, GaimBlistNode *node)
+{
+	if(GAIM_BLIST_NODE_IS_BUDDY(node))
+		alias_dialog_bud((struct buddy*)node);
+	else if(GAIM_BLIST_NODE_IS_CHAT(node))
+		alias_dialog_chat((struct chat*)node);
 }
 
 static void gtk_blist_menu_bp_cb(GtkWidget *w, struct buddy *b)
@@ -154,17 +163,17 @@
 
 static void gtk_blist_menu_showlog_cb(GtkWidget *w, struct buddy *b)
 {
-       show_log(b->name);
+	show_log(b->name);
 }
 
 static void gtk_blist_show_systemlog_cb()
 {
-       show_log(NULL);
+	show_log(NULL);
 }
 
 static void gtk_blist_show_onlinehelp_cb()
 {
-       open_url(NULL, WEBSITE "documentation.php");
+	open_url(NULL, WEBSITE "documentation.php");
 }
 
 static void gtk_blist_button_im_cb(GtkWidget *w, GtkTreeView *tv)
@@ -203,9 +212,21 @@
 	show_info_dialog();
 }
 
-static void gtk_blist_button_chat_cb(GtkWidget *w, gpointer data)
+static void gtk_blist_button_chat_cb(GtkWidget *w, GtkTreeView *tv)
 {
-	/* FIXME: someday, we can check to see if we've selected a chat node */
+	GtkTreeIter iter;
+	GtkTreeModel *model = gtk_tree_view_get_model(tv);
+	GtkTreeSelection *sel = gtk_tree_view_get_selection(tv);
+
+	if(gtk_tree_selection_get_selected(sel, &model, &iter)){
+		GaimBlistNode *node;
+
+		gtk_tree_model_get(GTK_TREE_MODEL(gtkblist->treemodel), &iter, NODE_COLUMN, &node, -1);
+		if (GAIM_BLIST_NODE_IS_CHAT(node)) {
+			serv_join_chat(((struct chat *)node)->account->gc, ((struct chat*)node)->components);
+			return;
+		}
+	}
 	join_chat();
 }
 
@@ -261,6 +282,8 @@
 				gaim_conversation_get_window(conv),
 				gaim_conversation_get_index(conv));
 		}
+	} else if (GAIM_BLIST_NODE_IS_CHAT(node)) {
+		serv_join_chat(((struct chat *)node)->account->gc, ((struct chat*)node)->components);
 	} else if (GAIM_BLIST_NODE_IS_GROUP(node)) {
 		if (gtk_tree_view_row_expanded(tv, path))
 			gtk_tree_view_collapse_row(tv, path);
@@ -269,6 +292,24 @@
 	}
 }
 
+static void gaim_gtk_blist_add_chat_cb()
+{
+	GtkTreeSelection *sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(gtkblist->treeview));
+	GtkTreeIter iter;
+	GaimBlistNode *node;
+
+	if(gtk_tree_selection_get_selected(sel, NULL, &iter)){
+		gtk_tree_model_get(GTK_TREE_MODEL(gtkblist->treemodel), &iter, NODE_COLUMN, &node, -1);
+		if (GAIM_BLIST_NODE_IS_BUDDY(node) || GAIM_BLIST_NODE_IS_CHAT(node))
+			show_add_chat(NULL, (struct group*)node->parent);
+		else if (GAIM_BLIST_NODE_IS_GROUP(node))
+			show_add_chat(NULL, (struct group*)node);
+	}
+	else {
+		show_add_chat(NULL, NULL);
+	}
+}
+
 static void gaim_gtk_blist_add_buddy_cb()
 {
 	GtkTreeSelection *sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(gtkblist->treeview));
@@ -277,7 +318,7 @@
 
 	if(gtk_tree_selection_get_selected(sel, NULL, &iter)){
 		gtk_tree_model_get(GTK_TREE_MODEL(gtkblist->treemodel), &iter, NODE_COLUMN, &node, -1);
-		if (GAIM_BLIST_NODE_IS_BUDDY(node)) 
+		if (GAIM_BLIST_NODE_IS_BUDDY(node) || GAIM_BLIST_NODE_IS_CHAT(node))
 			show_add_buddy(NULL, NULL, ((struct group*)node->parent)->name, NULL);
 		else if (GAIM_BLIST_NODE_IS_GROUP(node))
 			show_add_buddy(NULL, NULL, ((struct group*)node)->name, NULL);
@@ -293,6 +334,9 @@
 	if (GAIM_BLIST_NODE_IS_BUDDY(node)) {
 		struct buddy *b = (struct buddy*)node;
 		show_confirm_del(b->account->gc, b->name);
+	} else if (GAIM_BLIST_NODE_IS_CHAT(node)) {
+		struct chat *chat = (struct chat*)node;
+		show_confirm_del_chat(chat);
 	} else if (GAIM_BLIST_NODE_IS_GROUP(node)) {
 		struct group *g = (struct group*)node;
 		show_confirm_del_group(g);
@@ -331,8 +375,13 @@
 
 	if (GAIM_BLIST_NODE_IS_GROUP(node)) {
 		gaim_new_item_from_stock(menu, _("_Add a Buddy"), GTK_STOCK_ADD, G_CALLBACK(gaim_gtk_blist_add_buddy_cb), node, 0, 0, NULL);
+		gaim_new_item_from_stock(menu, _("_Add a Chat"), GTK_STOCK_ADD, G_CALLBACK(gaim_gtk_blist_add_chat_cb), node, 0, 0, NULL);
 		gaim_new_item_from_stock(menu, _("_Delete Group"), GTK_STOCK_REMOVE, G_CALLBACK(gaim_gtk_blist_remove_cb), node, 0, 0, NULL);
 		gaim_new_item_from_stock(menu, _("_Rename"), NULL, G_CALLBACK(show_rename_group), node, 0, 0, NULL);
+	} else if (GAIM_BLIST_NODE_IS_CHAT(node)) {
+		gaim_new_item_from_stock(menu, _("_Join"), GAIM_STOCK_CHAT, G_CALLBACK(gtk_blist_menu_join_cb), node, 0, 0, NULL);
+		gaim_new_item_from_stock(menu, _("_Alias"), GAIM_STOCK_EDIT, G_CALLBACK(gtk_blist_menu_alias_cb), node, 0, 0, NULL);
+		gaim_new_item_from_stock(menu, _("_Remove"), GTK_STOCK_REMOVE, G_CALLBACK(gaim_gtk_blist_remove_cb), node, 0, 0, NULL);
 	} else if (GAIM_BLIST_NODE_IS_BUDDY(node)) {
 		/* Protocol specific options */
 		prpl = gaim_find_prpl(((struct buddy*)node)->account->protocol);
@@ -460,7 +509,8 @@
 
 			if (GAIM_BLIST_NODE_IS_BUDDY(n)) {
 				struct buddy *b = (struct buddy*)n;
-				if (GAIM_BLIST_NODE_IS_BUDDY(node)) {
+				if (GAIM_BLIST_NODE_IS_BUDDY(node) ||
+						GAIM_BLIST_NODE_IS_CHAT(node)) {
 					switch(position) {
 						case GTK_TREE_VIEW_DROP_AFTER:
 						case GTK_TREE_VIEW_DROP_INTO_OR_AFTER:
@@ -474,6 +524,23 @@
 				} else if (GAIM_BLIST_NODE_IS_GROUP(node)) {
 					gaim_blist_add_buddy(b, (struct group*)node, NULL);
 				}
+			} else if (GAIM_BLIST_NODE_IS_CHAT(n)) {
+				struct chat *chat = (struct chat*)n;
+				if (GAIM_BLIST_NODE_IS_BUDDY(node) ||
+						GAIM_BLIST_NODE_IS_CHAT(node)) {
+					switch(position) {
+						case GTK_TREE_VIEW_DROP_AFTER:
+						case GTK_TREE_VIEW_DROP_INTO_OR_AFTER:
+							gaim_blist_add_chat(chat, (struct group*)node->parent, node);
+							break;
+						case GTK_TREE_VIEW_DROP_BEFORE:
+						case GTK_TREE_VIEW_DROP_INTO_OR_BEFORE:
+							gaim_blist_add_chat(chat, (struct group*)node->parent, node->prev);
+							break;
+					}
+				} else if (GAIM_BLIST_NODE_IS_GROUP(node)) {
+					gaim_blist_add_chat(chat, (struct group*)node, NULL);
+				}
 			} else if (GAIM_BLIST_NODE_IS_GROUP(n)) {
 				struct group *g = (struct group*)n;
 				if (GAIM_BLIST_NODE_IS_GROUP(node)) {
@@ -488,7 +555,8 @@
 						break;
 					}
 
-				} else if(GAIM_BLIST_NODE_IS_BUDDY(node)) {
+				} else if(GAIM_BLIST_NODE_IS_BUDDY(node) ||
+						GAIM_BLIST_NODE_IS_CHAT(node)) {
 					gaim_blist_add_group(g, node->parent);
 				}
 
@@ -500,12 +568,12 @@
 	}
 }
 
-static void gaim_gtk_blist_paint_tip(GtkWidget *widget, GdkEventExpose *event, struct buddy *b)
+static void gaim_gtk_blist_paint_tip(GtkWidget *widget, GdkEventExpose *event, GaimBlistNode *node)
 {
 	GtkStyle *style;
-	GdkPixbuf *pixbuf = gaim_gtk_blist_get_status_icon(b, GAIM_STATUS_ICON_LARGE);
+	GdkPixbuf *pixbuf = gaim_gtk_blist_get_status_icon(node, GAIM_STATUS_ICON_LARGE);
 	PangoLayout *layout;
-	char *tooltiptext = gaim_get_tooltip_text(b);
+	char *tooltiptext = gaim_get_tooltip_text(node);
 
 	layout = gtk_widget_create_pango_layout (gtkblist->tipwindow, NULL);
 	pango_layout_set_markup(layout, tooltiptext, strlen(tooltiptext));
@@ -538,63 +606,63 @@
 	GtkTreeIter iter;
 	GaimBlistNode *node;
 	GValue val = {0};
+	int scr_w,scr_h, w, h, x, y;
+	PangoLayout *layout;
+	char *tooltiptext = NULL;
 
 	if (!gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(tv), gtkblist->rect.x, gtkblist->rect.y, &path, NULL, NULL, NULL))
 		return FALSE;
 	gtk_tree_model_get_iter(GTK_TREE_MODEL(gtkblist->treemodel), &iter, path);
 	gtk_tree_model_get_value (GTK_TREE_MODEL(gtkblist->treemodel), &iter, NODE_COLUMN, &val);
 	node = g_value_get_pointer(&val);
-	
-	if (GAIM_BLIST_NODE_IS_BUDDY(node)) {
-		int scr_w,scr_h, w, h, x, y;
-		PangoLayout *layout;
-		struct buddy *buddy = (struct buddy*)node;
-		char *tooltiptext = gaim_get_tooltip_text(buddy);
-		gtkblist->tipwindow = gtk_window_new(GTK_WINDOW_POPUP);
-		gtkblist->tipwindow->parent = tv;
-		gtk_widget_set_app_paintable(gtkblist->tipwindow, TRUE);
-		gtk_window_set_resizable(GTK_WINDOW(gtkblist->tipwindow), FALSE);
-		gtk_widget_set_name(gtkblist->tipwindow, "gtk-tooltips");
-		g_signal_connect(G_OBJECT(gtkblist->tipwindow), "expose_event", 
-				 G_CALLBACK(gaim_gtk_blist_paint_tip), buddy);
-		gtk_widget_ensure_style (gtkblist->tipwindow);
+	if(!GAIM_BLIST_NODE_IS_BUDDY(node) && !GAIM_BLIST_NODE_IS_CHAT(node))
+		return FALSE;
 
-		layout = gtk_widget_create_pango_layout (gtkblist->tipwindow, NULL);
-		pango_layout_set_wrap(layout, PANGO_WRAP_WORD);
-		pango_layout_set_width(layout, 300000);
-		pango_layout_set_markup(layout, tooltiptext, strlen(tooltiptext));
-		scr_w = gdk_screen_width();
-		scr_h = gdk_screen_height();
-		pango_layout_get_size (layout, &w, &h);
-		w = PANGO_PIXELS(w) + 8;
-		h = PANGO_PIXELS(h) + 8;
+	tooltiptext = gaim_get_tooltip_text(node);
+	gtkblist->tipwindow = gtk_window_new(GTK_WINDOW_POPUP);
+	gtkblist->tipwindow->parent = tv;
+	gtk_widget_set_app_paintable(gtkblist->tipwindow, TRUE);
+	gtk_window_set_resizable(GTK_WINDOW(gtkblist->tipwindow), FALSE);
+	gtk_widget_set_name(gtkblist->tipwindow, "gtk-tooltips");
+	g_signal_connect(G_OBJECT(gtkblist->tipwindow), "expose_event",
+			G_CALLBACK(gaim_gtk_blist_paint_tip), node);
+	gtk_widget_ensure_style (gtkblist->tipwindow);
+
+	layout = gtk_widget_create_pango_layout (gtkblist->tipwindow, NULL);
+	pango_layout_set_wrap(layout, PANGO_WRAP_WORD);
+	pango_layout_set_width(layout, 300000);
+	pango_layout_set_markup(layout, tooltiptext, strlen(tooltiptext));
+	scr_w = gdk_screen_width();
+	scr_h = gdk_screen_height();
+	pango_layout_get_size (layout, &w, &h);
+	w = PANGO_PIXELS(w) + 8;
+	h = PANGO_PIXELS(h) + 8;
 
-		/* 38 is the size of a large status icon plus 4 pixels padding on each side.
-		   I should #define this or something */
-		w = w + 38;
-		h = MAX(h, 38);
+	/* 38 is the size of a large status icon plus 4 pixels padding on each side.
+	 *  I should #define this or something */
+	w = w + 38;
+	h = MAX(h, 38);
+
+	gdk_window_get_pointer(NULL, &x, &y, NULL);
+	if (GTK_WIDGET_NO_WINDOW(gtkblist->window))
+		y+=gtkblist->window->allocation.y;
+
+	x -= ((w >> 1) + 4);
 
-		gdk_window_get_pointer(NULL, &x, &y, NULL);
-		if (GTK_WIDGET_NO_WINDOW(gtkblist->window))
-			y+=gtkblist->window->allocation.y;
-	
-		x -= ((w >> 1) + 4);
-		
-		if ((x + w) > scr_w)
-			x -= (x + w) - scr_w;
-		else if (x < 0)
-			x = 0;
+	if ((x + w) > scr_w)
+		x -= (x + w) - scr_w;
+	else if (x < 0)
+		x = 0;
 
-		if ((y + h + 4) > scr_h)
-			y = y - h;
-		else
-			y = y + 6;
-		g_object_unref (layout);
-		g_free(tooltiptext);
-		gtk_widget_set_size_request(gtkblist->tipwindow, w, h);
-		gtk_window_move(GTK_WINDOW(gtkblist->tipwindow), x, y);
-		gtk_widget_show(gtkblist->tipwindow);
-	}
+	if ((y + h + 4) > scr_h)
+		y = y - h;
+	else
+		y = y + 6;
+	g_object_unref (layout);
+	g_free(tooltiptext);
+	gtk_widget_set_size_request(gtkblist->tipwindow, w, h);
+	gtk_window_move(GTK_WINDOW(gtkblist->tipwindow), x, y);
+	gtk_widget_show(gtkblist->tipwindow);
 
 	gtk_tree_path_free(path);
 	return FALSE;
@@ -662,7 +730,8 @@
 	{ "/Buddies/sep1", NULL, NULL, 0, "<Separator>" },
 	{ N_("/Buddies/_Show Offline Buddies"), NULL, gaim_gtk_blist_edit_mode_cb, 1, "<CheckItem>"},
 	{ N_("/Buddies/Show _Empty Groups"), NULL, gaim_gtk_blist_show_empty_groups_cb, 1, "<CheckItem>"},
-	{ N_("/Buddies/_Add a Buddy..."), NULL, gaim_gtk_blist_add_buddy_cb, 0, "<StockItem>", GTK_STOCK_ADD }, 
+	{ N_("/Buddies/_Add a Buddy..."), NULL, gaim_gtk_blist_add_buddy_cb, 0, "<StockItem>", GTK_STOCK_ADD },
+	{ N_("/Buddies/_Add a Chat..."), NULL, gaim_gtk_blist_add_chat_cb, 0, "<StockItem>", GTK_STOCK_ADD },
 	{ N_("/Buddies/Add a _Group..."), NULL, show_add_group, 0, NULL},
 	{ "/Buddies/sep2", NULL, NULL, 0, "<Separator>" },
 	{ N_("/Buddies/_Signoff"), "<CTL>D", signoff_all, 0, "<StockItem>", GAIM_STOCK_SIGN_OFF },
@@ -692,57 +761,64 @@
  * Private Utility functions                             *
  *********************************************************/
 
-static char *gaim_get_tooltip_text(struct buddy *b)
+static char *gaim_get_tooltip_text(GaimBlistNode *node)
 {
-	GaimPlugin *prpl;
-	GaimPluginProtocolInfo *prpl_info = NULL;
 	char *text = NULL;
-	char *statustext = NULL;
-	char *aliastext = NULL, *nicktext = NULL;
-	char *warning = NULL, *idletime = NULL;
-
-	prpl = gaim_find_prpl(b->account->protocol);
-	prpl_info = GAIM_PLUGIN_PROTOCOL_INFO(prpl);
 
-	if (prpl_info->tooltip_text) {
-		const char *end;
-		statustext = prpl_info->tooltip_text(b);
+	if(GAIM_BLIST_NODE_IS_CHAT(node)) {
+		struct chat *chat = (struct chat *)node;
+		text = g_strdup_printf("<span size='larger' weight='bold'>%s</span>",
+				chat->alias);
+	} else if(GAIM_BLIST_NODE_IS_BUDDY(node)) {
+		GaimPlugin *prpl;
+		GaimPluginProtocolInfo *prpl_info = NULL;
+		struct buddy *b = (struct buddy *)node;
+		char *statustext = NULL;
+		char *aliastext = NULL, *nicktext = NULL;
+		char *warning = NULL, *idletime = NULL;
 
-		if(statustext && !g_utf8_validate(statustext, -1, &end)) {
-			char *new = g_strndup(statustext,
-					g_utf8_pointer_to_offset(statustext, end));
-			g_free(statustext);
-			statustext = new;
-		}
-	}
+		prpl = gaim_find_prpl(b->account->protocol);
+		prpl_info = GAIM_PLUGIN_PROTOCOL_INFO(prpl);
+
+		if (prpl_info->tooltip_text) {
+			const char *end;
+			statustext = prpl_info->tooltip_text(b);
 
-	if (!statustext && !GAIM_BUDDY_IS_ONLINE(b))
-		statustext = g_strdup(_("<b>Status:</b> Offline"));
+			if(statustext && !g_utf8_validate(statustext, -1, &end)) {
+				char *new = g_strndup(statustext,
+						g_utf8_pointer_to_offset(statustext, end));
+				g_free(statustext);
+				statustext = new;
+			}
+		}
+
+		if (!statustext && !GAIM_BUDDY_IS_ONLINE(b))
+			statustext = g_strdup(_("<b>Status:</b> Offline"));
 
-	if (b->idle > 0) {
-		int ihrs, imin;
-		time_t t;
-		time(&t);
-		ihrs = (t - b->idle) / 3600;
-		imin = ((t - b->idle) / 60) % 60;
-		if (ihrs)
-			idletime = g_strdup_printf(_("%dh%02dm"), ihrs, imin);
-		else
-			idletime = g_strdup_printf(_("%dm"), imin);
-	}
+		if (b->idle > 0) {
+			int ihrs, imin;
+			time_t t;
+			time(&t);
+			ihrs = (t - b->idle) / 3600;
+			imin = ((t - b->idle) / 60) % 60;
+			if (ihrs)
+				idletime = g_strdup_printf(_("%dh%02dm"), ihrs, imin);
+			else
+				idletime = g_strdup_printf(_("%dm"), imin);
+		}
 
-	if(b->alias && b->alias[0])
-		aliastext = g_markup_escape_text(b->alias, -1);
+		if(b->alias && b->alias[0])
+			aliastext = g_markup_escape_text(b->alias, -1);
 
-	if(b->server_alias)
-		nicktext = g_markup_escape_text(b->server_alias, -1);
+		if(b->server_alias)
+			nicktext = g_markup_escape_text(b->server_alias, -1);
 
-	if (b->evil > 0)
-		warning = g_strdup_printf(_("%d%%"), b->evil);
+		if (b->evil > 0)
+			warning = g_strdup_printf(_("%d%%"), b->evil);
 
-	text = g_strdup_printf("<span size='larger' weight='bold'>%s</span>"
+		text = g_strdup_printf("<span size='larger' weight='bold'>%s</span>"
 			       "%s %s"  /* Alias */
-			       "%s %s"  /* Nickname */
+				   "%s %s"  /* Nickname */
 			       "%s %s"     /* Idle */
 			       "%s %s"     /* Warning */
 			       "%s%s"     /* Status */
@@ -754,23 +830,23 @@
 			       b->evil ? _("\n<b>Warned:</b>") : "", b->evil ? warning : "",
 			       statustext ? "\n" : "", statustext ? statustext : "",
 				   !g_ascii_strcasecmp(b->name, "robflynn") ? _("\n<b>Description:</b> Spooky") : "");
-	
-	if(warning)
-		g_free(warning);
-	if(idletime)
-		g_free(idletime);
-	if(statustext)
-		g_free(statustext);
-	if(nicktext)
-		g_free(nicktext);
-	if(aliastext)
-		g_free(aliastext);
+
+		if(warning)
+			g_free(warning);
+		if(idletime)
+			g_free(idletime);
+		if(statustext)
+			g_free(statustext);
+		if(nicktext)
+			g_free(nicktext);
+		if(aliastext)
+			g_free(aliastext);
+	}
 
 	return text;
-
 }
 
-GdkPixbuf *gaim_gtk_blist_get_status_icon(struct buddy *b, GaimStatusIconSize size)
+GdkPixbuf *gaim_gtk_blist_get_status_icon(GaimBlistNode *node, GaimStatusIconSize size)
 {
 	GdkPixbuf *status = NULL;
 	GdkPixbuf *scale = NULL;
@@ -782,20 +858,32 @@
 
 	int scalesize = 30;
 
-	GaimPlugin *prpl;
+	GaimPlugin *prpl = NULL;
 	GaimPluginProtocolInfo *prpl_info = NULL;
 
-	prpl = gaim_find_prpl(b->account->protocol);
+	if(GAIM_BLIST_NODE_IS_BUDDY(node))
+		prpl = gaim_find_prpl(((struct buddy *)node)->account->protocol);
+	else if(GAIM_BLIST_NODE_IS_CHAT(node))
+		prpl = gaim_find_prpl(((struct chat *)node)->account->protocol);
 
-	if (!prpl) 
+	if (!prpl)
 		return NULL;
 
 	prpl_info = GAIM_PLUGIN_PROTOCOL_INFO(prpl);
 
-	if (prpl_info->list_icon)
-		protoname = prpl_info->list_icon(b->account, b);
-	if (b->present != GAIM_BUDDY_SIGNING_OFF && prpl_info->list_emblems)
-		prpl_info->list_emblems(b, &se, &sw, &nw, &ne);
+	if (prpl_info->list_icon) {
+		if(GAIM_BLIST_NODE_IS_BUDDY(node))
+			protoname = prpl_info->list_icon(((struct buddy*)node)->account,
+					(struct buddy *)node);
+		else if(GAIM_BLIST_NODE_IS_CHAT(node))
+			protoname = prpl_info->list_icon(((struct chat*)node)->account, NULL);
+	}
+
+	if (GAIM_BLIST_NODE_IS_BUDDY(node) &&
+			((struct buddy *)node)->present != GAIM_BUDDY_SIGNING_OFF &&
+			prpl_info->list_emblems) {
+		prpl_info->list_emblems((struct buddy*)node, &se, &sw, &nw, &ne);
+	}
 
 	if (size == GAIM_STATUS_ICON_SMALL) {
 		scalesize = 15;
@@ -803,11 +891,13 @@
 	}
 
 
-	if (b->present == GAIM_BUDDY_SIGNING_ON) {
+	if (GAIM_BLIST_NODE_IS_BUDDY(node) &&
+			((struct buddy*)node)->present == GAIM_BUDDY_SIGNING_ON) {
 		filename = g_build_filename(DATADIR, "pixmaps", "gaim", "status", "default", "login.png", NULL);
 		status = gdk_pixbuf_new_from_file(filename,NULL);
 		g_free(filename);
-	} else if (b->present == GAIM_BUDDY_SIGNING_OFF) {
+	} else if (GAIM_BLIST_NODE_IS_BUDDY(node) &&
+			((struct buddy *)node)->present == GAIM_BUDDY_SIGNING_OFF) {
 		filename = g_build_filename(DATADIR, "pixmaps", "gaim", "status", "default", "logout.png", NULL);
 		status = gdk_pixbuf_new_from_file(filename,NULL);
 		g_free(filename);
@@ -916,9 +1006,12 @@
 
 
 	/* Idle grey buddies affects the whole row.  This converts the status icon to greyscale. */
-	if (b->present == GAIM_BUDDY_OFFLINE)
+	if (GAIM_BLIST_NODE_IS_BUDDY(node) &&
+			((struct buddy *)node)->present == GAIM_BUDDY_OFFLINE)
 		gdk_pixbuf_saturate_and_pixelate(scale, scale, 0.0, FALSE);
-	else if (b->idle && blist_options & OPT_BLIST_GREY_IDLERS)
+	else if (GAIM_BLIST_NODE_IS_BUDDY(node) &&
+			((struct buddy *)node)->idle &&
+			blist_options & OPT_BLIST_GREY_IDLERS)
 		gdk_pixbuf_saturate_and_pixelate(scale, scale, 0.25, FALSE);
 	return scale;
 }
@@ -1091,17 +1184,17 @@
 
 static gboolean gaim_gtk_blist_refresh_timer(struct gaim_buddy_list *list)
 {
-	GaimBlistNode *group = list->root;
-	GaimBlistNode *buddy;
+	GaimBlistNode *group, *buddy;
 
-	while (group) {
-		buddy = group->child;
-		while (buddy) {
+	for(group = list->root; group; group = group->next) {
+		if(!GAIM_BLIST_NODE_IS_GROUP(group))
+			continue;
+		for(buddy = group->child; buddy; buddy = buddy->next) {
+			if(!GAIM_BLIST_NODE_IS_BUDDY(buddy))
+				continue;
 			if (((struct buddy *)buddy)->idle)
 				gaim_gtk_blist_update(list, buddy);
-			buddy = buddy->next;
 		}
-		group = group->next;
 	}
 
 	/* keep on going */
@@ -1314,7 +1407,7 @@
 	gtk_box_pack_start(GTK_BOX(gtkblist->bbox), button, FALSE, FALSE, 0);
 	gtk_button_set_relief(GTK_BUTTON(button), GTK_RELIEF_NONE);
 	gtk_size_group_add_widget(sg, button);
-	g_signal_connect(G_OBJECT(button), "clicked", G_CALLBACK(gtk_blist_button_chat_cb), NULL);
+	g_signal_connect(G_OBJECT(button), "clicked", G_CALLBACK(gtk_blist_button_chat_cb), gtkblist->treeview);
 	gtk_tooltips_set_tip(GTK_TOOLTIPS(gtkblist->tooltips), button, _("Join a chat room"), NULL);
 	gtk_widget_show(button);
 
@@ -1335,17 +1428,15 @@
 
 void gaim_gtk_blist_refresh(struct gaim_buddy_list *list)
 {
-	GaimBlistNode *group = list->root;
-	GaimBlistNode *buddy;
+	GaimBlistNode *group, *buddy;
 
-	while (group) {
-		buddy = group->child;
+	for(group = list->root; group; group = group->next) {
+		if(!GAIM_BLIST_NODE_IS_GROUP(group))
+			continue;
 		gaim_gtk_blist_update(list, group);
-		while (buddy) {
+		for(buddy = group->child; buddy; buddy = buddy->next) {
 			gaim_gtk_blist_update(list, buddy);
-			buddy = buddy->next;
 		}
-		group = group->next;
 	}
 }
 
@@ -1433,7 +1524,7 @@
 
 	if (get_iter_from_node(node, &iter)) {
 		gtk_tree_store_remove(gtkblist->treemodel, &iter);
-		if(GAIM_BLIST_NODE_IS_BUDDY(node)) {
+		if(GAIM_BLIST_NODE_IS_BUDDY(node) || GAIM_BLIST_NODE_IS_CHAT(node)) {
 			gaim_gtk_blist_update(list, node->parent);
 		}
 	}
@@ -1541,8 +1632,32 @@
 				}
 
 			}
-		}
-		else if (GAIM_BLIST_NODE_IS_GROUP(node) &&
+		} else if (GAIM_BLIST_NODE_IS_CHAT(node) &&
+				((struct chat *)node)->account->gc) {
+			GtkTreeIter groupiter;
+			GaimBlistNode *oldersibling;
+			GtkTreeIter oldersiblingiter;
+			char *collapsed = gaim_group_get_setting((struct group *)node->parent, "collapsed");
+
+			if(node->parent &&
+					!get_iter_from_node(node->parent, &groupiter)) {
+				/* This buddy's group has not yet been added.
+				 * We do that here */
+				make_a_group(node->parent, &groupiter);
+			}
+			if(!collapsed)
+				expand = gtk_tree_model_get_path(GTK_TREE_MODEL(gtkblist->treemodel), &groupiter);
+			else
+				g_free(collapsed);
+
+			oldersibling = node->prev;
+			while (oldersibling && !get_iter_from_node(oldersibling, &oldersiblingiter)) {
+				oldersibling = oldersibling->prev;
+			}
+
+			gtk_tree_store_insert_after(gtkblist->treemodel, &iter, &groupiter, oldersibling ? &oldersiblingiter : NULL);
+
+		} else if (GAIM_BLIST_NODE_IS_GROUP(node) &&
 					((blist_options & OPT_BLIST_SHOW_OFFLINE) ||
 					!(blist_options & OPT_BLIST_NO_MT_GRP))) {
 			make_a_group(node, &iter);
@@ -1569,14 +1684,34 @@
 		}
 	}
 
-	if (GAIM_BLIST_NODE_IS_BUDDY(node) && (((struct buddy*)node)->present != GAIM_BUDDY_OFFLINE || ((blist_options & OPT_BLIST_SHOW_OFFLINE) && ((struct buddy*)node)->account->gc))) {
+	if (GAIM_BLIST_NODE_IS_CHAT(node) && ((struct chat*)node)->account->gc) {
+		GdkPixbuf *status;
+		char *mark;
+
+		status = gaim_gtk_blist_get_status_icon(node,
+							blist_options & OPT_BLIST_SHOW_ICONS ? GAIM_STATUS_ICON_LARGE : GAIM_STATUS_ICON_SMALL);
+		mark = g_markup_escape_text(((struct chat*)node)->alias, -1);
+
+		gtk_tree_store_set(gtkblist->treemodel, &iter,
+				   STATUS_ICON_COLUMN, status,
+				   STATUS_ICON_VISIBLE_COLUMN, TRUE,
+				   NAME_COLUMN, mark,
+				   NODE_COLUMN, node,
+				   -1);
+
+		g_free(mark);
+		if (status != NULL)
+			g_object_unref(status);
+	} else if(GAIM_BLIST_NODE_IS_CHAT(node) && !((struct chat *)node)->account->gc) {
+		gaim_gtk_blist_remove(list, node);
+	} else if (GAIM_BLIST_NODE_IS_BUDDY(node) && (((struct buddy*)node)->present != GAIM_BUDDY_OFFLINE || ((blist_options & OPT_BLIST_SHOW_OFFLINE) && ((struct buddy*)node)->account->gc))) {
 		GdkPixbuf *status, *avatar;
 		char *mark;
 		char *warning = NULL, *idle = NULL;
 
 		gboolean selected = (gtkblist->selected_node == node);
 
-		status = gaim_gtk_blist_get_status_icon((struct buddy*)node,
+		status = gaim_gtk_blist_get_status_icon(node,
 							blist_options & OPT_BLIST_SHOW_ICONS ? GAIM_STATUS_ICON_LARGE : GAIM_STATUS_ICON_SMALL);
 		avatar = gaim_gtk_blist_get_buddy_icon((struct buddy*)node);
 		mark   = gaim_gtk_blist_get_name_markup((struct buddy*)node, selected);
@@ -1646,6 +1781,7 @@
 	} else if (GAIM_BLIST_NODE_IS_BUDDY(node) && !new_entry) {
 		gaim_gtk_blist_remove(list, node);
 	}
+
 	gtk_tree_view_columns_autosize(GTK_TREE_VIEW(gtkblist->treeview));
 
 
@@ -1654,7 +1790,7 @@
 		gtk_tree_path_free(expand);
 	}
 
-	if(GAIM_BLIST_NODE_IS_BUDDY(node))
+	if(GAIM_BLIST_NODE_IS_BUDDY(node) || GAIM_BLIST_NODE_IS_CHAT(node))
 		gaim_gtk_blist_update(list, node->parent);
 }
 
--- a/src/gtkblist.h	Sat Apr 26 20:05:01 2003 +0000
+++ b/src/gtkblist.h	Sat Apr 26 20:30:43 2003 +0000
@@ -1,5 +1,5 @@
 /**
- * @file gtkblist.h GTK+ Buddy List API
+ * @file gtklist.h GTK+ Buddy List API
  * @ingroup gtkui
  *
  * gaim
@@ -122,7 +122,7 @@
 /**
  * Useful for the buddy ticker
  */
-GdkPixbuf *gaim_gtk_blist_get_status_icon(struct buddy *b,
+GdkPixbuf *gaim_gtk_blist_get_status_icon(GaimBlistNode *node,
 		GaimStatusIconSize size);
 
 #endif /* _GAIM_GTK_LIST_H_ */
--- a/src/multi.c	Sat Apr 26 20:05:01 2003 +0000
+++ b/src/multi.c	Sat Apr 26 20:30:43 2003 +0000
@@ -167,11 +167,11 @@
 			continue;
 		m = (struct group *)gnode;
 		for(bnode = gnode->child; bnode; bnode = bnode->next) {
-			if(!GAIM_BLIST_NODE_IS_BUDDY(bnode))
-				continue;
-			n = (struct buddy *)bnode;
-			if(n->account == gc->account) {
-				n->present = GAIM_BUDDY_OFFLINE;
+			if(GAIM_BLIST_NODE_IS_BUDDY(bnode)) {
+				n = (struct buddy *)bnode;
+				if(n->account == gc->account) {
+					n->present = GAIM_BUDDY_OFFLINE;
+				}
 			}
 		}
 	}
@@ -1463,11 +1463,14 @@
 		if(!GAIM_BLIST_NODE_IS_GROUP(gnode))
 			continue;
 		for(bnode = gnode->child; bnode; bnode = bnode->next) {
-			struct buddy *b = (struct buddy *)bnode;
-			if(!GAIM_BLIST_NODE_IS_BUDDY(bnode))
-				continue;
-			if(b->account == account) {
-				gaim_blist_remove_buddy(b);
+			if(GAIM_BLIST_NODE_IS_BUDDY(bnode)) {
+				struct buddy *b = (struct buddy *)bnode;
+				if(b->account == account)
+					gaim_blist_remove_buddy(b);
+			} else if(GAIM_BLIST_NODE_IS_CHAT(bnode)) {
+				struct chat *chat = (struct chat *)bnode;
+				if(chat->account == account)
+					gaim_blist_remove_chat(chat);
 			}
 		}
 		if(!gnode->child) {
@@ -1706,6 +1709,8 @@
 	do_away_menu();
 	do_proto_menu();
 
+	gaim_blist_add_account(gc->account);
+
 	/*
 	 * XXX This is a hack! Remove this and replace it with a better event
 	 *     notification system.
@@ -1744,11 +1749,11 @@
 		if(!GAIM_BLIST_NODE_IS_GROUP(gnode))
 			continue;
 		for(bnode = gnode->child; bnode; bnode = bnode->next) {
-			struct buddy *b = (struct buddy *)bnode;
-			if(!GAIM_BLIST_NODE_IS_BUDDY(bnode))
-				continue;
-			if(b->account == gc->account) {
-				add_buds = g_list_append(add_buds, b->name);
+			if(GAIM_BLIST_NODE_IS_BUDDY(bnode)) {
+				struct buddy *b = (struct buddy *)bnode;
+				if(b->account == gc->account) {
+					add_buds = g_list_append(add_buds, b->name);
+				}
 			}
 		}
 	}
--- a/src/multi.h	Sat Apr 26 20:05:01 2003 +0000
+++ b/src/multi.h	Sat Apr 26 20:30:43 2003 +0000
@@ -101,6 +101,7 @@
 
 struct proto_chat_entry {
 	char *label;
+	char *identifier;
 	char *def;
 	gboolean is_int;
 	int min;
--- a/src/protocols/irc/irc.c	Sat Apr 26 20:05:01 2003 +0000
+++ b/src/protocols/irc/irc.c	Sat Apr 26 20:30:43 2003 +0000
@@ -1579,8 +1579,12 @@
 	}
 
 	if (!strcmp(cmd, "INVITE")) {
-		char *chan = g_strdup(word[4]);
-		serv_got_chat_invite(gc, chan + 1, nick, NULL, g_list_append(NULL, chan));
+		GHashTable *components = g_hash_table_new_full(g_str_hash, g_str_equal,
+				g_free, g_free);
+
+		g_hash_table_replace(components, g_strdup("channel"), g_strdup(word[4]));
+
+		serv_got_chat_invite(gc, word[4] + 1, nick, NULL, components);
 	} else if (!strcmp(cmd, "JOIN")) {
 		irc_parse_join(gc, nick, word, word_eol);
 	} else if (!strcmp(cmd, "KICK")) {
@@ -2471,17 +2475,19 @@
 
 	pce = g_new0(struct proto_chat_entry, 1);
 	pce->label = _("Channel:");
+	pce->identifier = "channel";
 	m = g_list_append(m, pce);
 
 	pce = g_new0(struct proto_chat_entry, 1);
 	pce->label = _("Password:");
+	pce->identifier = "password";
 	m = g_list_append(m, pce);
 
 	return m;
 }
 
-static void 
-irc_join_chat(struct gaim_connection *gc, GList *data)
+static void
+irc_join_chat(struct gaim_connection *gc, GHashTable *data)
 {
 	struct irc_data *id = gc->proto_data;
 	char buf[IRC_BUF_LEN];
@@ -2489,9 +2495,10 @@
 
 	if (!data)
 		return;
-	name = data->data;
-	if (data->next) {
-		pass = data->next->data;
+
+	name = g_hash_table_lookup(data, "channel");
+	pass = g_hash_table_lookup(data, "password");
+	if (pass) {
 		g_snprintf(buf, sizeof(buf), "JOIN %s %s\r\n", name, pass);
 	} else
 		g_snprintf(buf, sizeof(buf), "JOIN %s\r\n", name);
--- a/src/protocols/jabber/jabber.c	Sat Apr 26 20:05:01 2003 +0000
+++ b/src/protocols/jabber/jabber.c	Sat Apr 26 20:30:43 2003 +0000
@@ -1300,16 +1300,20 @@
 			return;
 
 		if (conference_room) {
-			GList *m = NULL;
+			GHashTable *components = g_hash_table_new_full(g_str_hash,
+					g_str_equal, g_free, g_free);
 			char **data;
 
 			data = g_strsplit(conference_room, "@", 2);
-			m = g_list_append(m, g_strdup(data[0]));
-			m = g_list_append(m, g_strdup(data[1]));
-			m = g_list_append(m, g_strdup(gjc->user->user));
+			g_hash_table_replace(components, g_strdup("room"),
+					g_strdup(data[0]));
+			g_hash_table_replace(components, g_strdup("server"),
+					g_strdup(data[1]));
+			g_hash_table_replace(components, g_strdup("handle"),
+					g_strdup(gjc->user->user));
 			g_strfreev(data);
 
-			serv_got_chat_invite(GJ_GC(gjc), conference_room, from, msg, m);
+			serv_got_chat_invite(GJ_GC(gjc), conference_room, from, msg, components);
 		} else if (msg) { /* whisper */
 			struct jabber_chat *jc;
 			g_snprintf(m, sizeof(m), "%s", msg);
@@ -2894,35 +2898,43 @@
 
 	pce = g_new0(struct proto_chat_entry, 1);
 	pce->label = _("Room:");
+	pce->identifier = "room";
 	m = g_list_append(m, pce);
 
 	pce = g_new0(struct proto_chat_entry, 1);
 	pce->label = _("Server:");
+	pce->identifier = "server";
 	pce->def = confserv;
 	m = g_list_append(m, pce);
 
 	pce = g_new0(struct proto_chat_entry, 1);
 	pce->label = _("Handle:");
+	pce->identifier = "handle";
 	pce->def = gjc->user->user;
 	m = g_list_append(m, pce);
 
 	return m;
 }
 
-static void jabber_join_chat(struct gaim_connection *gc, GList *data)
+static void jabber_join_chat(struct gaim_connection *gc, GHashTable *data)
 {
 	xmlnode x;
+	char *room, *server, *handle;
 	char *realwho;
 	gjconn gjc = ((struct jabber_data *)gc->proto_data)->gjc;
 	GSList *jcs = ((struct jabber_data *)gc->proto_data)->chats;
 	struct jabber_chat *jc;
 	gaim_jid gjid;
 
-	if (!data || !data->next || !data->next->next)
+	room = g_hash_table_lookup(data, "room");
+	server = g_hash_table_lookup(data, "server");
+	handle = g_hash_table_lookup(data, "handle");
+
+	if (!room || !server || !handle)
 		return;
 
-	realwho = create_valid_jid(data->data, data->next->data, data->next->next->data);
-	gaim_debug(GAIM_DEBUG_INFO, "%s\n", realwho);
+	realwho = create_valid_jid(room, server, handle);
+	gaim_debug(GAIM_DEBUG_INFO, "jabber", "%s\n", realwho);
 
 	if((gjid = gaim_jid_new(realwho)) == NULL) {
 		char *msg = g_strdup_printf("The Jabber I.D. %s is invalid.", realwho);
--- a/src/protocols/napster/napster.c	Sat Apr 26 20:05:01 2003 +0000
+++ b/src/protocols/napster/napster.c	Sat Apr 26 20:30:43 2003 +0000
@@ -458,20 +458,16 @@
 
 	pce = g_new0(struct proto_chat_entry, 1);
 	pce->label = _("Join what group:");
+	pce->identifier = "name";
 	m = g_list_append(m, pce);
 
 	return m;
 }
 
-static void nap_join_chat(struct gaim_connection *gc, GList *data)
+static void nap_join_chat(struct gaim_connection *gc, GHashTable *data)
 {
 	gchar buf[NAP_BUF_LEN];
-	char *name;
-
-	if (!data)
-		return;
-
-	name = data->data;
+	char *name = g_hash_table_lookup(data, "name");
 
 	/* Make sure the name has a # preceeding it */
 	if (name[0] != '#') 
--- a/src/protocols/oscar/oscar.c	Sat Apr 26 20:05:01 2003 +0000
+++ b/src/protocols/oscar/oscar.c	Sat Apr 26 20:30:43 2003 +0000
@@ -2260,21 +2260,20 @@
 
 	if (args->reqclass & AIM_CAPS_CHAT) {
 		char *name;
-		int *exch;
-		GList *m = NULL;
-		
+		GHashTable *components;
+
 		if (!args->info.chat.roominfo.name || !args->info.chat.roominfo.exchange || !args->msg)
 			return 1;
+		components = g_hash_table_new_full(g_str_hash, g_str_equal, g_free,
+				g_free);
 		name = extract_name(args->info.chat.roominfo.name);
-		exch = g_new0(int, 1);
-		m = g_list_append(m, g_strdup(name ? name : args->info.chat.roominfo.name));
-		*exch = args->info.chat.roominfo.exchange;
-		m = g_list_append(m, exch);
+		g_hash_table_replace(components, g_strdup("room"), g_strdup(name ? name : args->info.chat.roominfo.name));
+		g_hash_table_replace(components, g_strdup("exchange"), g_strdup_printf("%d", args->info.chat.roominfo.exchange));
 		serv_got_chat_invite(gc,
 				     name ? name : args->info.chat.roominfo.name,
 				     userinfo->sn,
 				     (char *)args->msg,
-				     m);
+				     components);
 		if (name)
 			g_free(name);
 	} else if (args->reqclass & AIM_CAPS_SENDFILE) {
@@ -4831,8 +4830,12 @@
 		/* Buddies */
 		if ((blist = gaim_get_blist()))
 			for (gnode = blist->root; gnode; gnode = gnode->next) {
+				if(!GAIM_BLIST_NODE_IS_GROUP(gnode))
+					continue;
 				group = (struct group *)gnode;
 				for (bnode = gnode->child; bnode; bnode = bnode->next) {
+					if(!GAIM_BLIST_NODE_IS_BUDDY(bnode))
+						continue;
 					buddy = (struct buddy *)bnode;
 					if (buddy->account == gc->account) {
 						gchar *servernick = gaim_buddy_get_setting(buddy, "servernick");
@@ -5098,10 +5101,12 @@
 
 	pce = g_new0(struct proto_chat_entry, 1);
 	pce->label = _("Join what group:");
+	pce->identifier = "room";
 	m = g_list_append(m, pce);
 
 	pce = g_new0(struct proto_chat_entry, 1);
 	pce->label = _("Exchange:");
+	pce->identifier = "exchange";
 	pce->is_int = TRUE;
 	pce->min = 4;
 	pce->max = 20;
@@ -5110,30 +5115,26 @@
 	return m;
 }
 
-static void oscar_join_chat(struct gaim_connection *g, GList *data) {
+static void oscar_join_chat(struct gaim_connection *g, GHashTable *data) {
 	struct oscar_data *od = (struct oscar_data *)g->proto_data;
 	aim_conn_t *cur;
-	char *name;
-	int *exchange;
-
-	if (!data || !data->next)
-		return;
-
-	name = data->data;
-	exchange = data->next->data;
+	char *name, *exchange;
+
+	name = g_hash_table_lookup(data, "room");
+	exchange = g_hash_table_lookup(data, "exchange");
 
 	gaim_debug(GAIM_DEBUG_INFO, "oscar",
 			   "Attempting to join chat room %s.\n", name);
 	if ((cur = aim_getconn_type(od->sess, AIM_CONN_TYPE_CHATNAV))) {
 		gaim_debug(GAIM_DEBUG_INFO, "oscar",
 				   "chatnav exists, creating room\n");
-		aim_chatnav_createroom(od->sess, cur, name, *exchange);
+		aim_chatnav_createroom(od->sess, cur, name, atoi(exchange));
 	} else {
 		/* this gets tricky */
 		struct create_room *cr = g_new0(struct create_room, 1);
 		gaim_debug(GAIM_DEBUG_INFO, "oscar",
 				   "chatnav does not exist, opening chatnav\n");
-		cr->exchange = *exchange;
+		cr->exchange = atoi(exchange);
 		cr->name = g_strdup(name);
 		od->create_rooms = g_slist_append(od->create_rooms, cr);
 		aim_reqservice(od->sess, od->conn, AIM_CONN_TYPE_CHATNAV);
--- a/src/protocols/toc/toc.c	Sat Apr 26 20:05:01 2003 +0000
+++ b/src/protocols/toc/toc.c	Sat Apr 26 20:30:43 2003 +0000
@@ -818,14 +818,18 @@
 				gaim_chat_remove_user(chat, buddy, NULL);
 	} else if (!g_ascii_strcasecmp(c, "CHAT_INVITE")) {
 		char *name, *who, *message;
-		int *id = g_new0(int, 1);
+		int id;
+		GHashTable *components = g_hash_table_new_full(g_str_hash, g_str_equal,
+				g_free, g_free);
 
 		name = strtok(NULL, ":");
-		sscanf(strtok(NULL, ":"), "%d", id);
+		sscanf(strtok(NULL, ":"), "%d", &id);
 		who = strtok(NULL, ":");
 		message = strtok(NULL, ":");
 
-		serv_got_chat_invite(gc, name, who, message, g_list_append(NULL, id));
+		g_hash_table_replace(components, g_strdup("id"), g_strdup_printf("%d", id));
+
+		serv_got_chat_invite(gc, name, who, message, components);
 	} else if (!g_ascii_strcasecmp(c, "CHAT_LEFT")) {
 		GSList *bcs = gc->buddy_chats;
 		struct gaim_conversation *b = NULL;
@@ -1185,10 +1189,12 @@
 
 	pce = g_new0(struct proto_chat_entry, 1);
 	pce->label = _("Join what group:");
+	pce->identifier = "room";
 	m = g_list_append(m, pce);
 
 	pce = g_new0(struct proto_chat_entry, 1);
 	pce->label = _("Exchange:");
+	pce->identifier = "exchange";
 	pce->is_int = TRUE;
 	pce->min = 4;
 	pce->max = 20;
@@ -1197,23 +1203,20 @@
 	return m;
 }
 
-static void toc_join_chat(struct gaim_connection *g, GList *data)
+static void toc_join_chat(struct gaim_connection *g, GHashTable *data)
 {
 	char buf[BUF_LONG];
-	int *exchange;
-	char *name;
-	int *i;
-
-	if (!data)
-		return;
+	char *name, *exchange;
+	char *id;
 
-	if (!data->next) {
-		i = data->data;
-		g_snprintf(buf, 255, "toc_chat_accept %d", *i);
+	name = g_hash_table_lookup(data, "room");
+	exchange = g_hash_table_lookup(data, "exchange");
+	id = g_hash_table_lookup(data, "id");
+
+	if (id) {
+		g_snprintf(buf, 255, "toc_chat_accept %d", atoi(id));
 	} else {
-		name = data->data;
-		exchange = data->next->data;
-		g_snprintf(buf, sizeof(buf) / 2, "toc_chat_join %d \"%s\"", *exchange, name);
+		g_snprintf(buf, sizeof(buf) / 2, "toc_chat_join %d \"%s\"", atoi(exchange), name);
 	}
 
 	sflap_send(g, buf, -1, TYPE_DATA);
--- a/src/protocols/zephyr/zephyr.c	Sat Apr 26 20:05:01 2003 +0000
+++ b/src/protocols/zephyr/zephyr.c	Sat Apr 26 20:30:43 2003 +0000
@@ -882,20 +882,23 @@
 
 	pce = g_new0(struct proto_chat_entry, 1);
 	pce->label = _("Class:");
+	pce->identifier = "class";
 	m = g_list_append(m, pce);
 
 	pce = g_new0(struct proto_chat_entry, 1);
 	pce->label = _("Instance:");
+	pce->identifier = "instance";
 	m = g_list_append(m, pce);
 
 	pce = g_new0(struct proto_chat_entry, 1);
 	pce->label = _("Recipient:");
+	pce->identifier = "recipient";
 	m = g_list_append(m, pce);
 
 	return m;
 }
 
-static void zephyr_join_chat(struct gaim_connection *gc, GList *data)
+static void zephyr_join_chat(struct gaim_connection *gc, GHashTable *data)
 {
 	ZSubscription_t sub;
 	zephyr_triple *zt1, *zt2;
@@ -903,12 +906,13 @@
 	const char *instname;
 	const char *recip;
 
-	if (!data || !data->next || !data->next->next)
+	classname = g_hash_table_lookup(data, "class");
+	instname = g_hash_table_lookup(data, "instance");
+	recip = g_hash_table_lookup(data, "recipient");
+
+	if (!classname || !instname || !recip)
 		return;
 
-	classname = data->data;
-	instname = data->next->data;
-	recip = data->next->next->data;
 	if (!g_ascii_strcasecmp(recip, "%me%"))
 		recip = ZGetSender();
 
--- a/src/prpl.h	Sat Apr 26 20:05:01 2003 +0000
+++ b/src/prpl.h	Sat Apr 26 20:30:43 2003 +0000
@@ -252,7 +252,7 @@
 	void (*rem_deny)(struct gaim_connection *, const char *name);
 	void (*set_permit_deny)(struct gaim_connection *);
 	void (*warn)(struct gaim_connection *, char *who, int anonymous);
-	void (*join_chat)(struct gaim_connection *, GList *data);
+	void (*join_chat)(struct gaim_connection *, GHashTable *components);
 	void (*chat_invite)(struct gaim_connection *, int id,
 						const char *who, const char *message);
 	void (*chat_leave)(struct gaim_connection *, int id);
--- a/src/server.c	Sat Apr 26 20:05:01 2003 +0000
+++ b/src/server.c	Sat Apr 26 20:30:43 2003 +0000
@@ -498,9 +498,11 @@
 		GaimBlistNode *b = ((GaimBlistNode*)old_group)->child;
 
 		while (b) {
-			struct buddy *bd = (struct buddy *)b;
-			if (bd->account == g->account)
-				tobemoved = g_list_append(tobemoved, bd->name);
+			if(GAIM_BLIST_NODE_IS_BUDDY(b)) {
+				struct buddy *bd = (struct buddy *)b;
+				if (bd->account == g->account)
+					tobemoved = g_list_append(tobemoved, bd->name);
+			}
 			b = b->next;
 		}
 
@@ -601,7 +603,7 @@
 		prpl_info->warn(g, name, anon);
 }
 
-void serv_join_chat(struct gaim_connection *g, GList *data)
+void serv_join_chat(struct gaim_connection *g, GHashTable *data)
 {
 	GaimPluginProtocolInfo *prpl_info = NULL;
 
@@ -1194,25 +1196,19 @@
 
 struct chat_invite_data {
 	struct gaim_connection *gc;
-	GList *str;
+	GHashTable *components;
 };
 
 static void chat_invite_data_free(struct chat_invite_data *cid)
 {
-	GList *tmp = cid->str;
-	while (tmp) {
-		/* this is either a g_malloc'd char* or g_malloc'd int* */
-		g_free(tmp->data);
-		tmp = tmp->next;
-	}
-	if (cid->str)
-		g_list_free(cid->str);
+	if (cid->components)
+		g_hash_table_destroy(cid->components);
 	g_free(cid);
 }
 
 static void chat_invite_accept(struct chat_invite_data *cid)
 {
-	serv_join_chat(cid->gc, cid->str);
+	serv_join_chat(cid->gc, cid->components);
 
 	chat_invite_data_free(cid);
 }
@@ -1220,7 +1216,7 @@
 
 
 void serv_got_chat_invite(struct gaim_connection *gc, char *name,
-						  char *who, char *message, GList *data)
+						  char *who, char *message, GHashTable *data)
 {
 	char buf2[BUF_LONG];
 	struct chat_invite_data *cid = g_new0(struct chat_invite_data, 1);
@@ -1238,7 +1234,7 @@
 				   who, gc->username, name);
 
 	cid->gc = gc;
-	cid->str = data;
+	cid->components = data;
 
 	do_ask_dialog(_("Buddy Chat Invite"), buf2, cid, _("Accept"), chat_invite_accept, _("Cancel"), chat_invite_data_free, NULL, FALSE);
 }
--- a/src/ui.h	Sat Apr 26 20:05:01 2003 +0000
+++ b/src/ui.h	Sat Apr 26 20:30:43 2003 +0000
@@ -245,10 +245,12 @@
 
 /* Functions in dialogs.c */
 extern void alias_dialog_bud(struct buddy *);
+extern void alias_dialog_chat(struct chat *);
 extern void show_warn_dialog(struct gaim_connection *, char *);
 extern void show_im_dialog();
 extern void show_info_dialog();
 extern void show_add_buddy(struct gaim_connection *, char *, char *, char *);
+extern void show_add_chat(struct gaim_account *, struct group *);
 extern void show_add_group(struct gaim_connection *);
 extern void show_add_perm(struct gaim_connection *, char *, gboolean);
 extern void destroy_all_dialogs();
--- a/src/util.c	Sat Apr 26 20:05:01 2003 +0000
+++ b/src/util.c	Sat Apr 26 20:30:43 2003 +0000
@@ -1089,7 +1089,7 @@
 			g_free(group);
 	} else if (!g_ascii_strncasecmp(uri, "aim:gochat?", strlen("aim:gochat?"))) {
 		char *room;
-		GList *chat=NULL;
+		GHashTable *components;
 		int exch = 5;
 		
 		uri = uri + strlen("aim:gochat?");
@@ -1106,11 +1106,14 @@
 		}
 		room = g_strdup(str->str);
 		g_string_free(str, TRUE);
-		chat = g_list_append(NULL, room);
-		chat = g_list_append(chat, &exch);
-		serv_join_chat(gc, chat);
-		g_free(room);
-		g_list_free(chat);
+		components = g_hash_table_new_full(g_str_hash, g_str_equal, g_free,
+				g_free);
+		g_hash_table_replace(components, g_strdup("room"), room);
+		g_hash_table_replace(components, g_strdup("exchange"),
+				g_strdup_printf("%d", exch));
+
+		serv_join_chat(gc, components);
+		g_hash_table_destroy(components);
 	} else {
 		return _("Invalid AIM URI");
 	}