changeset 22222:433233d3db10

merge of '2e288b76540410af9b0ca21669ffb7e085633d59' and '5cfc19787cb16932e5e50c4034a7c46699fe1c51'
author Mark Doliner <mark@kingant.net>
date Sun, 27 Jan 2008 10:03:26 +0000
parents ef0e82b8bc2e (diff) c0ad8b41ce09 (current diff)
children 19ace070f0e5
files libpurple/protocols/myspace/myspace.c
diffstat 54 files changed, 1438 insertions(+), 492 deletions(-) [+]
line wrap: on
line diff
--- a/ChangeLog	Sun Jan 27 04:26:38 2008 +0000
+++ b/ChangeLog	Sun Jan 27 10:03:26 2008 +0000
@@ -35,6 +35,9 @@
 	* Recently signed on (or off) buddies blink in the buddy list.
 	* New action 'Room List' in the action list can be used to get the list of
 	  available chat rooms for an online account.
+	* The 'Grouping' plugin can be used for alternate grouping in the
+	  buddylist. The current options are 'Group Online/Offline' and 'No
+	  Group'.
 
 version 2.3.1 (12/7/2007):
 	http://developer.pidgin.im/query?status=closed&milestone=2.3.1
--- a/ChangeLog.API	Sun Jan 27 04:26:38 2008 +0000
+++ b/ChangeLog.API	Sun Jan 27 10:03:26 2008 +0000
@@ -30,6 +30,19 @@
 			* purple_attention_type_get_outgoing_desc
 			* purple_attention_type_get_icon_name
 			* purple_attention_type_get_unlocalized_name
+		* Add some PurpleBuddyListNode accessor functions:
+			* purple_blist_node_get_parent
+			* purple_blist_node_get_first_child
+			* purple_blist_node_get_sibling_next
+			* purple_blist_node_get_sibling_prev
+		* Added last_received to PurpleConnection, the time_t of the
+		  last received packet.
+		* Added some more accessor functions:
+			* purple_chat_get_account
+			* purple_chat_get_components
+			* purple_connection_get_prpl
+			* purple_xfer_get_start_time
+			* purple_xfer_get_end_time
 
 	Pidgin:
 		Added:
@@ -56,6 +69,12 @@
 		* finch_roomlist_get_ui_ops and finch_roomlist_show_all
 		* finch_request_field_get_widget to get the widget for a request
 		  field.
+		* finch_blist_get_tree to get the GntTree widget representing the
+		  buddy list.
+		* FinchBlistManager structure to manage the buddylist view, and some
+		  util functions finch_blist_install_manager,
+		  finch_blist_uninstall_manager, finch_blist_manager_find and
+		  finch_blist_manager_add_node.
 
 		libgnt:
 		* Added gnt_tree_set_row_color to set the color for a row in a tree.
@@ -65,6 +84,7 @@
 		  string.
 		* Added gnt_style_get_color to get a color pair from an entry in
 		  ~/.gntrc
+		* Added gnt_tree_get_parent_key to get the key for the parent row.
 
 version 2.3.0 (11/24/2007):
 	libpurple:
--- a/autogen.sh	Sun Jan 27 04:26:38 2008 +0000
+++ b/autogen.sh	Sun Jan 27 10:03:26 2008 +0000
@@ -9,42 +9,42 @@
 	echo;
 	echo "You must have glib-gettextize installed to compile Pidgin.";
 	echo;
-	exit;
+	exit 1;
 }
 
 (intltoolize --version) < /dev/null > /dev/null 2>&1 || {
 	echo;
 	echo "You must have intltool installed to compile Pidgin.";
 	echo;
-	exit;
+	exit 1;
 }
 
 (libtoolize --version) < /dev/null > /dev/null 2>&1 || {
 	echo;
 	echo "You must have libtool installed to compile Pidgin.";
 	echo;
-	exit;
+	exit 1;
 }
 
 (automake --version) < /dev/null > /dev/null 2>&1 || {
 	echo;
 	echo "You must have automake installed to compile Pidgin.";
 	echo;
-	exit;
+	exit 1;
 }
 
 (autoconf --version) < /dev/null > /dev/null 2>&1 || {
 	echo;
 	echo "You must have autoconf installed to compile Pidgin.";
 	echo;
-	exit;
+	exit 1;
 }
 
 echo "Generating configuration files for Pidgin, please wait...."
 echo;
 
 echo "Running libtoolize, please ignore non-fatal messages...."
-echo n | libtoolize --copy --force || exit;
+echo n | libtoolize --copy --force || exit 1;
 
 # Add other directories to this list if people continue to experience
 # brokennesses ...  Obviously the real answer is for them to fix it
@@ -60,11 +60,11 @@
 libtoolize -c -f --automake
 glib-gettextize --force --copy
 intltoolize --force --copy
-aclocal $ACLOCAL_FLAGS || exit;
-autoheader || exit;
+aclocal $ACLOCAL_FLAGS || exit 1;
+autoheader || exit 1;
 automake --add-missing --copy;
-autoconf || exit;
-automake || exit;
+autoconf || exit 1;
+automake || exit 1;
 
 echo;
 echo "Running ./configure ${CONFIGURE_ARGS} $@"
--- a/finch/finch.h	Sun Jan 27 04:26:38 2008 +0000
+++ b/finch/finch.h	Sun Jan 27 10:03:26 2008 +0000
@@ -29,3 +29,7 @@
 #define FINCH_UI "gnt-purple"
 
 #define FINCH_PREFS_ROOT "/finch"
+
+#define FINCH_GET_DATA(obj)        (obj)->ui_data
+#define FINCH_SET_DATA(obj, data)  (obj)->ui_data = data
+
--- a/finch/gntaccount.c	Sun Jan 27 04:26:38 2008 +0000
+++ b/finch/gntaccount.c	Sun Jan 27 10:03:26 2008 +0000
@@ -686,7 +686,7 @@
 	}
 
 	g_signal_connect(G_OBJECT(accounts.tree), "toggled", G_CALLBACK(account_toggled), NULL);
-	
+
 	gnt_tree_set_col_width(GNT_TREE(accounts.tree), 0, 40);
 	gnt_tree_set_col_width(GNT_TREE(accounts.tree), 1, 10);
 	gnt_box_add_widget(GNT_BOX(accounts.window), accounts.tree);
@@ -708,11 +708,11 @@
 	gnt_box_add_widget(GNT_BOX(box), button);
 	gnt_util_set_trigger_widget(GNT_WIDGET(accounts.tree), GNT_KEY_DEL, button);
 	g_signal_connect(G_OBJECT(button), "activate", G_CALLBACK(delete_account_cb), accounts.tree);
-	
+
 	gnt_box_add_widget(GNT_BOX(accounts.window), box);
 
 	g_signal_connect(G_OBJECT(accounts.window), "destroy", G_CALLBACK(reset_accounts_win), NULL);
-	
+
 	gnt_widget_show(accounts.window);
 }
 
@@ -981,7 +981,7 @@
 
 		gnt_box_add_widget(GNT_BOX(uihandle), gnt_hline_new());
 
-		widget = finch_retrieve_user_info(account->gc, remote_user);
+		widget = finch_retrieve_user_info(purple_account_get_connection(account), remote_user);
 		for (iter = GNT_BOX(widget)->list; iter; iter = iter->next) {
 			if (GNT_IS_BUTTON(iter->data)) {
 				gnt_widget_destroy(iter->data);
--- a/finch/gntblist.c	Sun Jan 27 04:26:38 2008 +0000
+++ b/finch/gntblist.c	Sun Jan 27 10:03:26 2008 +0000
@@ -81,6 +81,9 @@
 	/* These are the menuitems that get regenerated */
 	GntMenuItem *accounts;
 	GntMenuItem *plugins;
+	GntMenuItem *grouping;
+
+	FinchBlistManager *manager;
 } FinchBlist;
 
 typedef struct
@@ -115,7 +118,12 @@
 static void add_chat(PurpleChat *chat, FinchBlist *ggblist);
 static void add_node(PurpleBlistNode *node, FinchBlist *ggblist);
 static void node_update(PurpleBuddyList *list, PurpleBlistNode *node);
+#if 0
+static gboolean is_contact_online(PurpleContact *contact);
+static gboolean is_group_online(PurpleGroup *group);
+#endif
 static void draw_tooltip(FinchBlist *ggblist);
+static void tooltip_for_buddy(PurpleBuddy *buddy, GString *str, gboolean full);
 static gboolean remove_typing_cb(gpointer null);
 static void remove_peripherals(FinchBlist *ggblist);
 static const char * get_display_name(PurpleBlistNode *node);
@@ -125,6 +133,7 @@
 static void update_buddy_display(PurpleBuddy *buddy, FinchBlist *ggblist);
 static void account_signed_on_cb(PurpleConnection *pc, gpointer null);
 static void finch_request_add_buddy(PurpleAccount *account, const char *username, const char *grp, const char *alias);
+static void menu_group_set_cb(GntMenuItem *item, gpointer null);
 
 /* Sort functions */
 static int blist_node_compare_position(PurpleBlistNode *n1, PurpleBlistNode *n2);
@@ -137,14 +146,178 @@
 static int color_offline;
 static int color_idle;
 
+/**
+ * Buddy List Manager functions.
+ */
+
+static gboolean default_can_add_node(PurpleBlistNode *node)
+{
+	gboolean offline = purple_prefs_get_bool(PREF_ROOT "/showoffline");
+
+	if (PURPLE_BLIST_NODE_IS_BUDDY(node)) {
+		PurpleBuddy *buddy = (PurpleBuddy*)node;
+		FinchBlistNode *fnode = FINCH_GET_DATA(node);
+		if (!purple_buddy_get_contact(buddy))
+			return FALSE; /* When a new buddy is added and show-offline is set */
+		if (PURPLE_BUDDY_IS_ONLINE(buddy))
+			return TRUE;  /* The buddy is online */
+		if (!purple_account_is_connected(purple_buddy_get_account(buddy)))
+			return FALSE; /* The account is disconnected. Do not show */
+		if (offline)
+			return TRUE;  /* We want to see offline buddies too */
+		if (fnode && fnode->signed_timer)
+			return TRUE;  /* Show if the buddy just signed off */
+		if (purple_blist_node_get_bool(node, "show_offline"))
+			return TRUE;
+	} else if (PURPLE_BLIST_NODE_IS_CONTACT(node)) {
+		PurpleBlistNode *nd;
+		for (nd = purple_blist_node_get_first_child(node);
+				nd; nd = purple_blist_node_get_sibling_next(nd)) {
+			if (default_can_add_node(nd))
+				return TRUE;
+		}
+	} else if (PURPLE_BLIST_NODE_IS_CHAT(node)) {
+		PurpleChat *chat = (PurpleChat*)node;
+		if (purple_account_is_connected(purple_chat_get_account(chat)))
+			return TRUE;  /* Show whenever the account is online */
+	} else if (PURPLE_BLIST_NODE_IS_GROUP(node)) {
+		PurpleBlistNode *nd;
+		gboolean empty = purple_prefs_get_bool(PREF_ROOT "/emptygroups");
+		if (empty)
+			return TRUE;  /* If we want to see empty groups, we can show any group */
+
+		for (nd = purple_blist_node_get_first_child(node);
+				nd; nd = purple_blist_node_get_sibling_next(nd)) {
+			if (default_can_add_node(nd))
+				return TRUE;
+		}
+	}
+
+	return FALSE;
+}
+
+static gpointer default_find_parent(PurpleBlistNode *node)
+{
+	gpointer ret = NULL;
+	switch (purple_blist_node_get_type(node)) {
+		case PURPLE_BLIST_BUDDY_NODE:
+		case PURPLE_BLIST_CONTACT_NODE:
+		case PURPLE_BLIST_CHAT_NODE:
+			ret = purple_blist_node_get_parent(node);
+			break;
+		default:
+			break;
+	}
+	if (ret)
+		add_node(ret, ggblist);
+	return ret;
+}
+
+static gboolean default_create_tooltip(gpointer selected_row, GString **body, char **tool_title)
+{
+	GString *str;
+	PurpleBlistNode *node = selected_row;
+	int lastseen = 0;
+	char *title;
+
+	if (!node ||
+			purple_blist_node_get_type(node) == PURPLE_BLIST_OTHER_NODE)
+		return FALSE;
+
+	str = g_string_new("");
+
+	if (PURPLE_BLIST_NODE_IS_CONTACT(node)) {
+		PurpleBuddy *pr = purple_contact_get_priority_buddy((PurpleContact*)node);
+		gboolean offline = !PURPLE_BUDDY_IS_ONLINE(pr);
+		gboolean showoffline = purple_prefs_get_bool(PREF_ROOT "/showoffline");
+		const char *name = purple_buddy_get_name(pr);
+
+		title = g_strdup(name);
+		tooltip_for_buddy(pr, str, TRUE);
+		for (node = purple_blist_node_get_first_child(node); node; node = purple_blist_node_get_sibling_next(node)) {
+			PurpleBuddy *buddy = (PurpleBuddy*)node;
+			if (offline) {
+				int value = purple_blist_node_get_int(node, "last_seen");
+				if (value > lastseen)
+					lastseen = value;
+			}
+			if (node == (PurpleBlistNode*)pr)
+				continue;
+			if (!purple_account_is_connected(purple_buddy_get_account(buddy)))
+				continue;
+			if (!showoffline && !PURPLE_BUDDY_IS_ONLINE(buddy))
+				continue;
+			str = g_string_append(str, "\n----------\n");
+			tooltip_for_buddy(buddy, str, FALSE);
+		}
+	} else if (PURPLE_BLIST_NODE_IS_BUDDY(node)) {
+		PurpleBuddy *buddy = (PurpleBuddy *)node;
+		tooltip_for_buddy(buddy, str, TRUE);
+		title = g_strdup(purple_buddy_get_name(buddy));
+		if (!PURPLE_BUDDY_IS_ONLINE((PurpleBuddy*)node))
+			lastseen = purple_blist_node_get_int(node, "last_seen");
+	} else if (PURPLE_BLIST_NODE_IS_GROUP(node)) {
+		PurpleGroup *group = (PurpleGroup *)node;
+
+		g_string_append_printf(str, _("Online: %d\nTotal: %d"),
+						purple_blist_get_group_online_count(group),
+						purple_blist_get_group_size(group, FALSE));
+
+		title = g_strdup(purple_group_get_name(group));
+	} else if (PURPLE_BLIST_NODE_IS_CHAT(node)) {
+		PurpleChat *chat = (PurpleChat *)node;
+		PurpleAccount *account = purple_chat_get_account(chat);
+
+		g_string_append_printf(str, _("Account: %s (%s)"),
+				purple_account_get_username(account),
+				purple_account_get_protocol_name(account));
+
+		title = g_strdup(purple_chat_get_name(chat));
+	} else {
+		g_string_free(str, TRUE);
+		return FALSE;
+	}
+
+	if (lastseen > 0) {
+		char *tmp = purple_str_seconds_to_string(time(NULL) - lastseen);
+		g_string_append_printf(str, _("\nLast Seen: %s ago"), tmp);
+		g_free(tmp);
+	}
+
+	if (tool_title)
+		*tool_title = title;
+	else
+		g_free(title);
+
+	if (body)
+		*body = str;
+	else
+		g_string_free(str, TRUE);
+
+	return TRUE;
+}
+
+static FinchBlistManager default_manager =
+{
+	"default",
+	N_("Default"),
+	NULL,
+	NULL,
+	default_can_add_node,
+	default_find_parent,
+	default_create_tooltip,
+	{NULL, NULL, NULL, NULL}
+};
+static GList *managers;
+
 static FinchBlistNode *
 create_finch_blist_node(PurpleBlistNode *node, gpointer row)
 {
-	FinchBlistNode *fnode = node->ui_data;
+	FinchBlistNode *fnode = FINCH_GET_DATA(node);
 	if (!fnode) {
 		fnode = g_new0(FinchBlistNode, 1);
 		fnode->signed_timer = 0;
-		node->ui_data = fnode;
+		FINCH_SET_DATA(node, fnode);
 	}
 	fnode->row = row;
 	return fnode;
@@ -153,13 +326,13 @@
 static void
 reset_blist_node_ui_data(PurpleBlistNode *node)
 {
-	FinchBlistNode *fnode = node->ui_data;
+	FinchBlistNode *fnode = FINCH_GET_DATA(node);
 	if (fnode == NULL)
 		return;
 	if (fnode->signed_timer)
 		purple_timeout_remove(fnode->signed_timer);
 	g_free(fnode);
-	node->ui_data = NULL;
+	FINCH_SET_DATA(node, NULL);
 }
 
 static int
@@ -192,7 +365,7 @@
 get_blist_node_flag(PurpleBlistNode *node)
 {
 	GntTextFormatFlags flag = 0;
-	FinchBlistNode *fnode = node->ui_data;
+	FinchBlistNode *fnode = FINCH_GET_DATA(node);
 
 	if (ggblist->tagged && g_list_find(ggblist->tagged, node))
 		flag |= GNT_TEXT_FLAG_BOLD;
@@ -201,7 +374,7 @@
 		flag |= GNT_TEXT_FLAG_BLINK;
 	else if (PURPLE_BLIST_NODE_IS_CONTACT(node)) {
 		node = (PurpleBlistNode*)purple_contact_get_priority_buddy((PurpleContact *)node);
-		fnode = node->ui_data;
+		fnode = FINCH_GET_DATA(node);
 		if (fnode && fnode->signed_timer)
 			flag |= GNT_TEXT_FLAG_BLINK;
 	}
@@ -216,12 +389,16 @@
 	gnt_tree_set_row_color(GNT_TREE(ggblist->tree), node, get_display_color(node));
 }
 
+#if 0
 static gboolean
 is_contact_online(PurpleContact *contact)
 {
 	PurpleBlistNode *node;
-	for (node = ((PurpleBlistNode*)contact)->child; node; node = node->next) {
-		if (PURPLE_BUDDY_IS_ONLINE((PurpleBuddy*)node))
+	for (node = purple_blist_node_get_first_child(((PurpleBlistNode*)contact)); node;
+			node = purple_blist_node_get_sibling_next(node)) {
+		FinchBlistNode *fnode = FINCH_GET_DATA(node);
+		if (PURPLE_BUDDY_IS_ONLINE((PurpleBuddy*)node) ||
+				(fnode && fnode->signed_timer))
 			return TRUE;
 	}
 	return FALSE;
@@ -231,7 +408,8 @@
 is_group_online(PurpleGroup *group)
 {
 	PurpleBlistNode *node;
-	for (node = ((PurpleBlistNode*)group)->child; node; node = node->next) {
+	for (node = purple_blist_node_get_first_child(((PurpleBlistNode*)group)); node;
+			node = purple_blist_node_get_sibling_next(node)) {
 		if (PURPLE_BLIST_NODE_IS_CHAT(node) &&
 				purple_account_is_connected(((PurpleChat *)node)->account))
 			return TRUE;
@@ -240,14 +418,22 @@
 	}
 	return FALSE;
 }
+#endif
 
 static void
 new_node(PurpleBlistNode *node)
 {
 }
 
-static void add_node(PurpleBlistNode *node, FinchBlist *ggblist)
+static void
+add_node(PurpleBlistNode *node, FinchBlist *ggblist)
 {
+	if (FINCH_GET_DATA(node))
+		return;
+
+	if (!ggblist->manager->can_add_node(node))
+		return;
+
 	if (PURPLE_BLIST_NODE_IS_BUDDY(node))
 		add_buddy((PurpleBuddy*)node, ggblist);
 	else if (PURPLE_BLIST_NODE_IS_CONTACT(node))
@@ -256,9 +442,15 @@
 		add_group((PurpleGroup*)node, ggblist);
 	else if (PURPLE_BLIST_NODE_IS_CHAT(node))
 		add_chat((PurpleChat *)node, ggblist);
+
 	draw_tooltip(ggblist);
 }
 
+void finch_blist_manager_add_node(PurpleBlistNode *node)
+{
+	add_node(node, ggblist);
+}
+
 static void
 remove_tooltip(FinchBlist *ggblist)
 {
@@ -270,9 +462,10 @@
 static void
 node_remove(PurpleBuddyList *list, PurpleBlistNode *node)
 {
-	FinchBlist *ggblist = list->ui_data;
-
-	if (ggblist == NULL || node->ui_data == NULL)
+	FinchBlist *ggblist = FINCH_GET_DATA(list);
+	PurpleBlistNode *parent;
+
+	if (ggblist == NULL || FINCH_GET_DATA(node)== NULL)
 		return;
 
 	gnt_tree_remove(GNT_TREE(ggblist->tree), node);
@@ -280,23 +473,16 @@
 	if (ggblist->tagged)
 		ggblist->tagged = g_list_remove(ggblist->tagged, node);
 
-	if (PURPLE_BLIST_NODE_IS_BUDDY(node)) {
-		PurpleContact *contact = (PurpleContact*)node->parent;
-		if ((!purple_prefs_get_bool(PREF_ROOT "/showoffline") && !is_contact_online(contact)) ||
-				contact->currentsize < 1)
-			node_remove(list, (PurpleBlistNode*)contact);
+	parent = purple_blist_node_get_parent(node);
+	for (node = purple_blist_node_get_first_child(node); node;
+			node = purple_blist_node_get_sibling_next(node))
+		node_remove(list, node);
+
+	if (parent) {
+		if (!ggblist->manager->can_add_node(parent))
+			node_remove(list, parent);
 		else
-			node_update(list, (PurpleBlistNode*)contact);
-	} else if (!PURPLE_BLIST_NODE_IS_GROUP(node)) {
-		PurpleGroup *group = (PurpleGroup*)node->parent;
-		if ((group->currentsize < 1 && !purple_prefs_get_bool(PREF_ROOT "/emptygroups")) ||
-				(!purple_prefs_get_bool(PREF_ROOT "/showoffline") && !is_group_online(group)))
-			node_remove(list, node->parent);
-		for (node = node->child; node; node = node->next)
-			reset_blist_node_ui_data(node);
-	} else {
-		for (node = node->child; node; node = node->next)
-			node_remove(list, node);
+			node_update(list, parent);
 	}
 
 	draw_tooltip(ggblist);
@@ -310,48 +496,39 @@
            happens, so maybe someone will figure it out. */
 	g_return_if_fail(node != NULL);
 
-	if (list->ui_data == NULL)
+	if (FINCH_GET_DATA(list)== NULL)
 		return;   /* XXX: this is probably the place to auto-join chats */
 
 	if (ggblist->window == NULL)
 		return;
 
-	if (node->ui_data != NULL) {
+	if (FINCH_GET_DATA(node)!= NULL) {
 		gnt_tree_change_text(GNT_TREE(ggblist->tree), node,
 				0, get_display_name(node));
 		gnt_tree_sort_row(GNT_TREE(ggblist->tree), node);
 		blist_update_row_flags(node);
+		if (gnt_tree_get_parent_key(GNT_TREE(ggblist->tree), node) !=
+				ggblist->manager->find_parent(node))
+			node_remove(list, node);
 	}
 
 	if (PURPLE_BLIST_NODE_IS_BUDDY(node)) {
 		PurpleBuddy *buddy = (PurpleBuddy*)node;
-		if (purple_account_is_connected(buddy->account) &&
-				(PURPLE_BUDDY_IS_ONLINE(buddy) || purple_prefs_get_bool(PREF_ROOT "/showoffline")))
-			add_node((PurpleBlistNode*)buddy, list->ui_data);
-
-		node_update(list, node->parent);
+		add_node((PurpleBlistNode*)buddy, FINCH_GET_DATA(list));
+		node_update(list, purple_blist_node_get_parent(node));
 	} else if (PURPLE_BLIST_NODE_IS_CHAT(node)) {
-		add_chat((PurpleChat *)node, list->ui_data);
+		add_node(node, FINCH_GET_DATA(list));
 	} else if (PURPLE_BLIST_NODE_IS_CONTACT(node)) {
-		PurpleContact *contact = (PurpleContact*)node;
-		if ((!purple_prefs_get_bool(PREF_ROOT "/showoffline") && !is_contact_online(contact)) ||
-				contact->currentsize < 1)
-			/* nothing */;
-		else {
-			if (node->ui_data == NULL) {
-				/* The core seems to expect the UI to add the buddies. */
-				for (node = node->child; node; node = node->next)
-					add_node(node, list->ui_data);
-			}
+		if (FINCH_GET_DATA(node)== NULL) {
+			/* The core seems to expect the UI to add the buddies. */
+			for (node = purple_blist_node_get_first_child(node); node; node = purple_blist_node_get_sibling_next(node))
+				add_node(node, FINCH_GET_DATA(list));
 		}
 	} else if (PURPLE_BLIST_NODE_IS_GROUP(node)) {
-		PurpleGroup *group = (PurpleGroup*)node;
-		if (!purple_prefs_get_bool(PREF_ROOT "/emptygroups") &&
-				((!purple_prefs_get_bool(PREF_ROOT "/showoffline") && !is_group_online(group)) ||
-				 group->currentsize < 1))
+		if (!ggblist->manager->can_add_node(node))
 			node_remove(list, node);
 		else
-			add_node(node, list->ui_data);
+			add_node(node, FINCH_GET_DATA(list));
 	}
 }
 
@@ -362,7 +539,10 @@
 		return;
 
 	ggblist = g_new0(FinchBlist, 1);
-	list->ui_data = ggblist;
+	FINCH_SET_DATA(list, ggblist);
+	ggblist->manager = finch_blist_manager_find(purple_prefs_get_string(PREF_ROOT "/grouping"));
+	if (!ggblist->manager)
+		ggblist->manager = &default_manager;
 }
 
 static void
@@ -399,6 +579,8 @@
 		purple_blist_add_group(grp, NULL);
 	}
 
+	/* XXX: Ask if there's already the same buddy in the same group (#4553) */
+
 	buddy = purple_buddy_new(account, username, alias);
 	purple_blist_add_buddy(buddy, NULL, grp, NULL);
 	purple_account_add_buddy(account, buddy);
@@ -447,6 +629,7 @@
 	GHashTable *hash = NULL;
 	PurpleConnection *gc;
 	gboolean autojoin;
+	PurplePluginProtocolInfo *info;
 
 	account = purple_request_fields_get_account(allfields, "account");
 	name = purple_request_fields_get_string(allfields, "name");
@@ -456,15 +639,15 @@
 
 	if (!purple_account_is_connected(account) || !name || !*name)
 		return;
-	
+
 	if (!group || !*group)
 		group = _("Chats");
 
 	gc = purple_account_get_connection(account);
-
-	if (PURPLE_PLUGIN_PROTOCOL_INFO(gc->prpl)->chat_info_defaults != NULL)
-		hash = PURPLE_PLUGIN_PROTOCOL_INFO(gc->prpl)->chat_info_defaults(gc, name);
-	
+	info = PURPLE_PLUGIN_PROTOCOL_INFO(purple_connection_get_prpl(gc));
+	if (info->chat_info_defaults != NULL)
+		hash = info->chat_info_defaults(gc, name);
+
 	chat = purple_chat_new(account, name, hash);
 
 	if (chat != NULL) {
@@ -476,7 +659,7 @@
 		purple_blist_alias_chat(chat, alias);
 		purple_blist_node_set_bool((PurpleBlistNode*)chat, "gnt-autojoin", autojoin);
 		if (autojoin)
-			serv_join_chat(chat->account->gc, chat->components);
+			serv_join_chat(purple_account_get_connection(purple_chat_get_account(chat)), purple_chat_get_components(chat));
 	}
 }
 
@@ -501,7 +684,7 @@
 	field = purple_request_field_string_new("alias", _("Alias"), alias, FALSE);
 	purple_request_field_group_add_field(group, field);
 
-	field = purple_request_field_string_new("group", _("Group"), grp ? grp->name : NULL, FALSE);
+	field = purple_request_field_string_new("group", _("Group"), grp ? purple_group_get_name(grp) : NULL, FALSE);
 	purple_request_field_group_add_field(group, field);
 
 	field = purple_request_field_bool_new("autojoin", _("Auto-join"), FALSE);
@@ -578,11 +761,14 @@
 static void
 add_group(PurpleGroup *group, FinchBlist *ggblist)
 {
+	gpointer parent;
 	PurpleBlistNode *node = (PurpleBlistNode *)group;
-	if (node->ui_data)
+	if (FINCH_GET_DATA(node))
 		return;
+	parent = ggblist->manager->find_parent((PurpleBlistNode*)group);
 	create_finch_blist_node(node, gnt_tree_add_row_after(GNT_TREE(ggblist->tree), group,
-			gnt_tree_create_row(GNT_TREE(ggblist->tree), get_display_name(node)), NULL, NULL));
+			gnt_tree_create_row(GNT_TREE(ggblist->tree), get_display_name(node)),
+			parent, NULL));
 	gnt_tree_set_expanded(GNT_TREE(ggblist->tree), node,
 		!purple_blist_node_get_bool(node, "collapsed"));
 }
@@ -638,7 +824,7 @@
 		strncpy(status, "~", sizeof(status) - 1);
 	}
 	else if (PURPLE_BLIST_NODE_IS_GROUP(node))
-		return ((PurpleGroup*)node)->name;
+		return purple_group_get_name((PurpleGroup*)node);
 
 	snprintf(text, sizeof(text) - 1, "%s %s", status, name);
 
@@ -648,41 +834,39 @@
 static void
 add_chat(PurpleChat *chat, FinchBlist *ggblist)
 {
-	PurpleGroup *group;
+	gpointer parent;
 	PurpleBlistNode *node = (PurpleBlistNode *)chat;
-	if (node->ui_data)
+	if (FINCH_GET_DATA(node))
 		return;
-	if (!purple_account_is_connected(chat->account))
+	if (!purple_account_is_connected(purple_chat_get_account(chat)))
 		return;
 
-	group = purple_chat_get_group(chat);
-	add_node((PurpleBlistNode*)group, ggblist);
+	parent = ggblist->manager->find_parent((PurpleBlistNode*)chat);
 
 	create_finch_blist_node(node, gnt_tree_add_row_after(GNT_TREE(ggblist->tree), chat,
 				gnt_tree_create_row(GNT_TREE(ggblist->tree), get_display_name(node)),
-				group, NULL));
+				parent, NULL));
 }
 
 static void
 add_contact(PurpleContact *contact, FinchBlist *ggblist)
 {
-	PurpleGroup *group;
+	gpointer parent;
 	PurpleBlistNode *node = (PurpleBlistNode*)contact;
 	const char *name;
 
-	if (node->ui_data)
+	if (FINCH_GET_DATA(node))
 		return;
 
 	name = get_display_name(node);
 	if (name == NULL)
 		return;
 
-	group = (PurpleGroup*)node->parent;
-	add_node((PurpleBlistNode*)group, ggblist);
+	parent = ggblist->manager->find_parent((PurpleBlistNode*)contact);
 
 	create_finch_blist_node(node, gnt_tree_add_row_after(GNT_TREE(ggblist->tree), contact,
 				gnt_tree_create_row(GNT_TREE(ggblist->tree), name),
-				group, NULL));
+				parent, NULL));
 
 	gnt_tree_set_expanded(GNT_TREE(ggblist->tree), contact, FALSE);
 }
@@ -690,23 +874,19 @@
 static void
 add_buddy(PurpleBuddy *buddy, FinchBlist *ggblist)
 {
-	PurpleContact *contact;
+	gpointer parent;
 	PurpleBlistNode *node = (PurpleBlistNode *)buddy;
-
-	if (node->ui_data)
+	PurpleContact *contact;
+
+	if (FINCH_GET_DATA(node))
 		return;
 
-	if (!purple_account_is_connected(buddy->account))
-		return;
-
-	contact = (PurpleContact*)node->parent;
-	if (!contact)   /* When a new buddy is added and show-offline is set */
-		return;
-	add_node((PurpleBlistNode*)contact, ggblist);
+	contact = purple_buddy_get_contact(buddy);
+	parent = ggblist->manager->find_parent((PurpleBlistNode*)buddy);
 
 	create_finch_blist_node(node, gnt_tree_add_row_after(GNT_TREE(ggblist->tree), buddy,
 				gnt_tree_create_row(GNT_TREE(ggblist->tree), get_display_name(node)),
-				contact, NULL));
+				parent, NULL));
 
 	blist_update_row_flags((PurpleBlistNode*)buddy);
 	if (buddy == purple_contact_get_priority_buddy(contact))
@@ -756,7 +936,7 @@
 						purple_buddy_get_account(buddy),
 						purple_buddy_get_name(buddy));
 		} else {
-			FinchConv *ggconv = conv->ui_data;
+			FinchConv *ggconv = FINCH_GET_DATA(conv);
 			gnt_window_present(ggconv->window);
 		}
 		finch_conversation_set_active(conv);
@@ -764,7 +944,7 @@
 	else if (PURPLE_BLIST_NODE_IS_CHAT(node))
 	{
 		PurpleChat *chat = (PurpleChat*)node;
-		serv_join_chat(chat->account->gc, chat->components);
+		serv_join_chat(purple_account_get_connection(purple_chat_get_account(chat)), purple_chat_get_components(chat));
 	}
 }
 
@@ -809,7 +989,7 @@
 append_proto_menu(GntMenu *menu, PurpleConnection *gc, PurpleBlistNode *node)
 {
 	GList *list;
-	PurplePluginProtocolInfo *prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(gc->prpl);
+	PurplePluginProtocolInfo *prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(purple_connection_get_prpl(gc));
 
 	if(!prpl_info || !prpl_info->blist_node_menu)
 		return;
@@ -851,7 +1031,7 @@
 			else
 				val = g_strdup(purple_request_field_string_get_value(field));
 
-			g_hash_table_replace(chat->components, g_strdup(id), val);  /* val should not be free'd */
+			g_hash_table_replace(purple_chat_get_components(chat), g_strdup(id), val);  /* val should not be free'd */
 		}
 	}
 }
@@ -864,22 +1044,24 @@
 	PurpleRequestField *field;
 	GList *parts, *iter;
 	struct proto_chat_entry *pce;
+	PurpleConnection *gc;
 
 	purple_request_fields_add_group(fields, group);
 
-	parts = PURPLE_PLUGIN_PROTOCOL_INFO(chat->account->gc->prpl)->chat_info(chat->account->gc);
+	gc = purple_account_get_connection(purple_chat_get_account(chat));
+	parts = PURPLE_PLUGIN_PROTOCOL_INFO(purple_connection_get_prpl(gc))->chat_info(gc);
 
 	for (iter = parts; iter; iter = iter->next) {
 		pce = iter->data;
 		if (pce->is_int) {
 			int val;
-			const char *str = g_hash_table_lookup(chat->components, pce->identifier);
+			const char *str = g_hash_table_lookup(purple_chat_get_components(chat), pce->identifier);
 			if (!str || sscanf(str, "%d", &val) != 1)
 				val = pce->min;
 			field = purple_request_field_int_new(pce->identifier, pce->label, val);
 		} else {
 			field = purple_request_field_string_new(pce->identifier, pce->label,
-					g_hash_table_lookup(chat->components, pce->identifier), FALSE);
+					g_hash_table_lookup(purple_chat_get_components(chat), pce->identifier), FALSE);
 		}
 
 		purple_request_field_group_add_field(group, field);
@@ -920,7 +1102,7 @@
 static void
 finch_add_buddy(PurpleBlistNode *selected, PurpleGroup *grp)
 {
-	purple_blist_request_add_buddy(NULL, NULL, grp ? grp->name : NULL, NULL);
+	purple_blist_request_add_buddy(NULL, NULL, grp ? purple_group_get_name(grp) : NULL, NULL);
 }
 
 static void
@@ -961,13 +1143,13 @@
 static void
 finch_blist_get_buddy_info_cb(PurpleBlistNode *selected, PurpleBuddy *buddy)
 {
-	finch_retrieve_user_info(buddy->account->gc, purple_buddy_get_name(buddy));
+	finch_retrieve_user_info(purple_account_get_connection(purple_buddy_get_account(buddy)), purple_buddy_get_name(buddy));
 }
 
 static void
 finch_blist_menu_send_file_cb(PurpleBlistNode *selected, PurpleBuddy *buddy)
 {
-	serv_send_file(buddy->account->gc, buddy->name, NULL);
+	serv_send_file(purple_account_get_connection(purple_buddy_get_account(buddy)), purple_buddy_get_name(buddy), NULL);
 }
 
 static void
@@ -978,7 +1160,7 @@
 		b = purple_contact_get_priority_buddy((PurpleContact *)node);
 	else
 		b = (PurpleBuddy *)node;
-	finch_pounce_editor_show(b->account, b->name, NULL);
+	finch_pounce_editor_show(purple_buddy_get_account(b), purple_buddy_get_name(b), NULL);
 }
 
 
@@ -986,8 +1168,9 @@
 create_buddy_menu(GntMenu *menu, PurpleBuddy *buddy)
 {
 	PurplePluginProtocolInfo *prpl_info;
-
-	prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(buddy->account->gc->prpl);
+	PurpleConnection *gc = purple_account_get_connection(purple_buddy_get_account(buddy));
+
+	prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(purple_connection_get_prpl(gc));
 	if (prpl_info && prpl_info->get_info)
 	{
 		add_custom_action(menu, _("Get Info"),
@@ -1000,7 +1183,7 @@
 	if (prpl_info && prpl_info->send_file)
 	{
 		if (!prpl_info->can_receive_file ||
-			prpl_info->can_receive_file(buddy->account->gc, buddy->name))
+			prpl_info->can_receive_file(gc, purple_buddy_get_name(buddy)))
 			add_custom_action(menu, _("Send File"),
 					PURPLE_CALLBACK(finch_blist_menu_send_file_cb), buddy);
 	}
@@ -1035,11 +1218,12 @@
 	PurpleGroup *group;
 
 	cnode = (PurpleBlistNode *)contact;
-	group = (PurpleGroup*)cnode->parent;
-	for (bnode = cnode->child; bnode; bnode = bnode->next) {
+	group = (PurpleGroup*)purple_blist_node_get_parent(cnode);
+	for (bnode = purple_blist_node_get_first_child(cnode); bnode; bnode = purple_blist_node_get_sibling_next(bnode)) {
 		PurpleBuddy *buddy = (PurpleBuddy*)bnode;
-		if (purple_account_is_connected(buddy->account))
-			purple_account_remove_buddy(buddy->account, buddy, group);
+		PurpleAccount *account = purple_buddy_get_account(buddy);
+		if (purple_account_is_connected(account))
+			purple_account_remove_buddy(account, buddy, group);
 	}
 	purple_blist_remove_contact(contact);
 }
@@ -1082,7 +1266,7 @@
 	else if (PURPLE_BLIST_NODE_IS_CHAT(node))
 		name = purple_chat_get_name((PurpleChat*)node);
 	else if (PURPLE_BLIST_NODE_IS_GROUP(node))
-		name = ((PurpleGroup*)node)->name;
+		name = purple_group_get_name((PurpleGroup*)node);
 	else
 		g_return_if_reached();
 
@@ -1104,32 +1288,34 @@
 {
 	PurpleBlistNode *cnode, *bnode;
 
-	cnode = ((PurpleBlistNode*)group)->child;
+	cnode = purple_blist_node_get_first_child(((PurpleBlistNode*)group));
 
 	while (cnode) {
 		if (PURPLE_BLIST_NODE_IS_CONTACT(cnode)) {
-			bnode = cnode->child;
-			cnode = cnode->next;
+			bnode = purple_blist_node_get_first_child(cnode);
+			cnode = purple_blist_node_get_sibling_next(cnode);
 			while (bnode) {
 				PurpleBuddy *buddy;
 				if (PURPLE_BLIST_NODE_IS_BUDDY(bnode)) {
+					PurpleAccount *account;
 					buddy = (PurpleBuddy*)bnode;
-					bnode = bnode->next;
-					if (purple_account_is_connected(buddy->account)) {
-						purple_account_remove_buddy(buddy->account, buddy, group);
+					bnode = purple_blist_node_get_sibling_next(bnode);
+					account = purple_buddy_get_account(buddy);
+					if (purple_account_is_connected(account)) {
+						purple_account_remove_buddy(account, buddy, group);
 						purple_blist_remove_buddy(buddy);
 					}
 				} else {
-					bnode = bnode->next;
+					bnode = purple_blist_node_get_sibling_next(bnode);
 				}
 			}
 		} else if (PURPLE_BLIST_NODE_IS_CHAT(cnode)) {
 			PurpleChat *chat = (PurpleChat *)cnode;
-			cnode = cnode->next;
-			if (purple_account_is_connected(chat->account))
+			cnode = purple_blist_node_get_sibling_next(cnode);
+			if (purple_account_is_connected(purple_chat_get_account(chat)))
 				purple_blist_remove_chat(chat);
 		} else {
-			cnode = cnode->next;
+			cnode = purple_blist_node_get_sibling_next(cnode);
 		}
 	}
 
@@ -1172,7 +1358,7 @@
 	} else if (PURPLE_BLIST_NODE_IS_CHAT(node)) {
 		name = purple_chat_get_name((PurpleChat*)node);
 	} else if (PURPLE_BLIST_NODE_IS_GROUP(node)) {
-		name = ((PurpleGroup*)node)->name;
+		name = purple_group_get_name((PurpleGroup*)node);
 		sec = _("Removing this group will also remove all the buddies in the group");
 	}
 	else
@@ -1216,18 +1402,19 @@
 	PurpleGroup *tg = NULL;
 	PurpleContact *tc = NULL;
 
-	if (target == NULL)
+	if (target == NULL ||
+			purple_blist_node_get_type(target) == PURPLE_BLIST_OTHER_NODE)
 		return;
 
 	if (PURPLE_BLIST_NODE_IS_GROUP(target))
 		tg = (PurpleGroup*)target;
 	else if (PURPLE_BLIST_NODE_IS_BUDDY(target)) {
-		tc = (PurpleContact*)target->parent;
-		tg = (PurpleGroup*)target->parent->parent;
+		tc = (PurpleContact*)purple_blist_node_get_parent(target);
+		tg = (PurpleGroup*)purple_blist_node_get_parent((PurpleBlistNode*)tc);
 	} else {
 		if (PURPLE_BLIST_NODE_IS_CONTACT(target))
 			tc = (PurpleContact*)target;
-		tg = (PurpleGroup*)target->parent;
+		tg = (PurpleGroup*)purple_blist_node_get_parent(target);
 	}
 
 	if (ggblist->tagged) {
@@ -1300,6 +1487,8 @@
 	tree = GNT_TREE(ggblist->tree);
 
 	node = gnt_tree_get_selection_data(tree);
+	if (node && purple_blist_node_get_type(node) == PURPLE_BLIST_OTHER_NODE)
+		return;
 
 	if (ggblist->tooltip)
 		remove_tooltip(ggblist);
@@ -1328,7 +1517,7 @@
 	} else if (PURPLE_BLIST_NODE_IS_GROUP(node)) {
 		PurpleGroup *group = (PurpleGroup *)node;
 		create_group_menu(GNT_MENU(context), group);
-		title = g_strdup(group->name);
+		title = g_strdup(purple_group_get_name(group));
 	}
 
 	append_extended_menu(GNT_MENU(context), node);
@@ -1393,7 +1582,7 @@
 			purple_account_get_protocol_name(account));
 	purple_notify_user_info_add_pair(user_info, _("Account"), tmp);
 	g_free(tmp);
-	
+
 	prpl = purple_find_prpl(purple_account_get_protocol_id(account));
 	prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(prpl);
 	if (prpl_info && prpl_info->tooltip_text) {
@@ -1442,16 +1631,15 @@
 {
 	PurpleBlistNode *node;
 	int x, y, top, width, w, h;
-	GString *str;
+	GString *str = NULL;
 	GntTree *tree;
 	GntWidget *widget, *box, *tv;
 	char *title = NULL;
-	int lastseen = 0;
 
 	widget = ggblist->tree;
 	tree = GNT_TREE(widget);
 
-	if (!gnt_widget_has_focus(ggblist->tree) || 
+	if (!gnt_widget_has_focus(ggblist->tree) ||
 			(ggblist->context && !GNT_WIDGET_IS_FLAG_SET(ggblist->context, GNT_WIDGET_INVISIBLE)))
 		return FALSE;
 
@@ -1466,65 +1654,8 @@
 	if (!node)
 		return FALSE;
 
-	str = g_string_new("");
-
-	if (PURPLE_BLIST_NODE_IS_CONTACT(node)) {
-		PurpleBuddy *pr = purple_contact_get_priority_buddy((PurpleContact*)node);
-		gboolean offline = !PURPLE_BUDDY_IS_ONLINE(pr);
-		gboolean showoffline = purple_prefs_get_bool(PREF_ROOT "/showoffline");
-		const char *name = purple_buddy_get_name(pr);
-
-		title = g_strdup(name);
-		tooltip_for_buddy(pr, str, TRUE);
-		for (node = node->child; node; node = node->next) {
-			PurpleBuddy *buddy = (PurpleBuddy*)node;
-			if (offline) {
-				int value = purple_blist_node_get_int(node, "last_seen");
-				if (value > lastseen)
-					lastseen = value;
-			}
-			if (node == (PurpleBlistNode*)pr)
-				continue;
-			if (!purple_account_is_connected(buddy->account))
-				continue;
-			if (!showoffline && !PURPLE_BUDDY_IS_ONLINE(buddy))
-				continue;
-			str = g_string_append(str, "\n----------\n");
-			tooltip_for_buddy(buddy, str, FALSE);
-		}
-	} else if (PURPLE_BLIST_NODE_IS_BUDDY(node)) {
-		PurpleBuddy *buddy = (PurpleBuddy *)node;
-		tooltip_for_buddy(buddy, str, TRUE);
-		title = g_strdup(purple_buddy_get_name(buddy));
-		if (!PURPLE_BUDDY_IS_ONLINE((PurpleBuddy*)node))
-			lastseen = purple_blist_node_get_int(node, "last_seen");
-	} else if (PURPLE_BLIST_NODE_IS_GROUP(node)) {
-		PurpleGroup *group = (PurpleGroup *)node;
-
-		g_string_append_printf(str, _("Online: %d\nTotal: %d"),
-						purple_blist_get_group_online_count(group),
-						purple_blist_get_group_size(group, FALSE));
-
-		title = g_strdup(group->name);
-	} else if (PURPLE_BLIST_NODE_IS_CHAT(node)) {
-		PurpleChat *chat = (PurpleChat *)node;
-		PurpleAccount *account = chat->account;
-
-		g_string_append_printf(str, _("Account: %s (%s)"),
-				purple_account_get_username(account),
-				purple_account_get_protocol_name(account));
-
-		title = g_strdup(purple_chat_get_name(chat));
-	} else {
-		g_string_free(str, TRUE);
+	if (!ggblist->manager->create_tooltip(node, &str, &title))
 		return FALSE;
-	}
-
-	if (lastseen > 0) {
-		char *tmp = purple_str_seconds_to_string(time(NULL) - lastseen);
-		g_string_append_printf(str, _("\nLast Seen: %s ago"), tmp);
-		g_free(tmp);
-	}
 
 	gnt_widget_get_position(widget, &x, &y);
 	gnt_widget_get_size(widget, &width, NULL);
@@ -1684,7 +1815,7 @@
 {
 	PurpleBlistNode *node;
 	purple_signals_disconnect_by_handle(finch_blist_get_handle());
-	purple_get_blist()->ui_data = NULL;
+	FINCH_SET_DATA(purple_get_blist(), NULL);
 
 	node = purple_blist_get_root();
 	while (node) {
@@ -1707,6 +1838,9 @@
 	PurpleBlistNode *node;
 	PurpleBuddyList *list;
 
+	if (ggblist->manager->init)
+		ggblist->manager->init();
+
 	if (strcmp(purple_prefs_get_string(PREF_ROOT "/sort_type"), "text") == 0) {
 		gnt_tree_set_compare_func(GNT_TREE(ggblist->tree),
 			(GCompareFunc)blist_node_compare_text);
@@ -1794,11 +1928,33 @@
 redraw_blist(const char *name, PurplePrefType type, gconstpointer val, gpointer data)
 {
 	PurpleBlistNode *node, *sel;
-	if (ggblist == NULL || ggblist->window == NULL)
+	FinchBlistManager *manager;
+
+	if (ggblist == NULL)
+		return;
+
+	manager = finch_blist_manager_find(purple_prefs_get_string(PREF_ROOT "/grouping"));
+	if (manager == NULL)
+		manager = &default_manager;
+	if (ggblist->manager != manager) {
+		if (ggblist->manager->uninit)
+			ggblist->manager->uninit();
+
+		ggblist->manager = manager;
+		if (manager->can_add_node == NULL)
+			manager->can_add_node = default_can_add_node;
+		if (manager->find_parent == NULL)
+			manager->find_parent = default_find_parent;
+		if (manager->create_tooltip == NULL)
+			manager->create_tooltip = default_create_tooltip;
+	}
+
+	if (ggblist->window == NULL)
 		return;
 
 	sel = gnt_tree_get_selection_data(GNT_TREE(ggblist->tree));
 	gnt_tree_remove_all(GNT_TREE(ggblist->tree));
+
 	node = purple_blist_get_root();
 	for (; node; node = purple_blist_node_next(node, TRUE))
 		reset_blist_node_ui_data(node);
@@ -1833,6 +1989,7 @@
 	purple_prefs_add_bool(PREF_ROOT "/showoffline", FALSE);
 	purple_prefs_add_bool(PREF_ROOT "/emptygroups", FALSE);
 	purple_prefs_add_string(PREF_ROOT "/sort_type", "text");
+	purple_prefs_add_string(PREF_ROOT "/grouping", "default");
 
 	purple_prefs_connect_callback(finch_blist_get_handle(),
 			PREF_ROOT "/emptygroups", redraw_blist, NULL);
@@ -1840,9 +1997,14 @@
 			PREF_ROOT "/showoffline", redraw_blist, NULL);
 	purple_prefs_connect_callback(finch_blist_get_handle(),
 			PREF_ROOT "/sort_type", redraw_blist, NULL);
+	purple_prefs_connect_callback(finch_blist_get_handle(),
+			PREF_ROOT "/grouping", redraw_blist, NULL);
 
 	purple_signal_connect(purple_connections_get_handle(), "signed-on", purple_blist_get_handle(),
 			G_CALLBACK(account_signed_on_cb), NULL);
+
+	finch_blist_install_manager(&default_manager);
+
 	return;
 }
 
@@ -2003,7 +2165,7 @@
 static int
 blist_node_compare_position(PurpleBlistNode *n1, PurpleBlistNode *n2)
 {
-	while ((n1 = n1->prev) != NULL)
+	while ((n1 = purple_blist_node_get_sibling_prev(n1)) != NULL)
 		if (n1 == n2)
 			return 1;
 	return -1;
@@ -2016,10 +2178,10 @@
 	char *us1, *us2;
 	int ret;
 
-	if (n1->type != n2->type)
+	if (purple_blist_node_get_type(n1) != purple_blist_node_get_type(n2))
 		return blist_node_compare_position(n1, n2);
 
-	switch (n1->type)
+	switch (purple_blist_node_get_type(n1))
 	{
 		case PURPLE_BLIST_CHAT_NODE:
 			s1 = purple_chat_get_name((PurpleChat*)n1);
@@ -2051,10 +2213,10 @@
 {
 	int ret;
 
-	if (n1->type != n2->type)
+	if (purple_blist_node_get_type(n1) != purple_blist_node_get_type(n2))
 		return blist_node_compare_position(n1, n2);
 
-	switch (n1->type) {
+	switch (purple_blist_node_get_type(n1)) {
 		case PURPLE_BLIST_CONTACT_NODE:
 			n1 = (PurpleBlistNode*)purple_contact_get_priority_buddy((PurpleContact*)n1);
 			n2 = (PurpleBlistNode*)purple_contact_get_priority_buddy((PurpleContact*)n2);
@@ -2082,9 +2244,10 @@
 	int log = 0;
 	PurpleBlistNode *node;
 
-	for (node = c->child; node; node = node->next) {
+	for (node = purple_blist_node_get_first_child(c); node; node = purple_blist_node_get_sibling_next(node)) {
 		PurpleBuddy *b = (PurpleBuddy*)node;
-		log += purple_log_get_total_size(PURPLE_LOG_IM, b->name, b->account);
+		log += purple_log_get_total_size(PURPLE_LOG_IM, purple_buddy_get_name(b),
+				purple_buddy_get_account(b));
 	}
 
 	return log;
@@ -2096,15 +2259,15 @@
 	int ret;
 	PurpleBuddy *b1, *b2;
 
-	if (n1->type != n2->type)
+	if (purple_blist_node_get_type(n1) != purple_blist_node_get_type(n2))
 		return blist_node_compare_position(n1, n2);
 
-	switch (n1->type) {
+	switch (purple_blist_node_get_type(n1)) {
 		case PURPLE_BLIST_BUDDY_NODE:
 			b1 = (PurpleBuddy*)n1;
 			b2 = (PurpleBuddy*)n2;
-			ret = purple_log_get_total_size(PURPLE_LOG_IM, b2->name, b2->account) - 
-					purple_log_get_total_size(PURPLE_LOG_IM, b1->name, b1->account);
+			ret = purple_log_get_total_size(PURPLE_LOG_IM, purple_buddy_get_name(b2), purple_buddy_get_account(b2)) -
+					purple_log_get_total_size(PURPLE_LOG_IM, purple_buddy_get_name(b1), purple_buddy_get_account(b1));
 			if (ret != 0)
 				return ret;
 			break;
@@ -2165,19 +2328,17 @@
 buddy_recent_signed_on_off(gpointer data)
 {
 	PurpleBlistNode *node = data;
-	FinchBlistNode *fnode = node->ui_data;
-	PurpleBuddy *buddy = (PurpleBuddy*)node;
+	FinchBlistNode *fnode = FINCH_GET_DATA(node);
 
 	purple_timeout_remove(fnode->signed_timer);
 	fnode->signed_timer = 0;
 
-	if (!purple_account_is_connected(buddy->account) ||
-			(!PURPLE_BUDDY_IS_ONLINE(buddy) && !purple_prefs_get_bool(PREF_ROOT "/showoffline"))) {
+	if (!ggblist->manager->can_add_node(node)) {
 		node_remove(purple_get_blist(), node);
 	} else {
 		update_node_display(node, ggblist);
-		if (node->parent && PURPLE_BLIST_NODE_IS_CONTACT(node->parent))
-			update_node_display(node->parent, ggblist);
+		if (purple_blist_node_get_parent(node) && PURPLE_BLIST_NODE_IS_CONTACT(purple_blist_node_get_parent(node)))
+			update_node_display(purple_blist_node_get_parent(node), ggblist);
 	}
 
 	return FALSE;
@@ -2187,7 +2348,7 @@
 buddy_signed_on_off_cb(gpointer data)
 {
 	PurpleBlistNode *node = data;
-	FinchBlistNode *fnode = node->ui_data;
+	FinchBlistNode *fnode = FINCH_GET_DATA(node);
 	if (!ggblist || !fnode)
 		return FALSE;
 
@@ -2195,8 +2356,8 @@
 		purple_timeout_remove(fnode->signed_timer);
 	fnode->signed_timer = purple_timeout_add_seconds(6, (GSourceFunc)buddy_recent_signed_on_off, data);
 	update_node_display(node, ggblist);
-	if (node->parent && PURPLE_BLIST_NODE_IS_CONTACT(node->parent))
-		update_node_display(node->parent, ggblist);
+	if (purple_blist_node_get_parent(node) && PURPLE_BLIST_NODE_IS_CONTACT(purple_blist_node_get_parent(node)))
+		update_node_display(purple_blist_node_get_parent(node), ggblist);
 	return FALSE;
 }
 
@@ -2260,10 +2421,10 @@
 		PurpleAccount *account = iter->data;
 		PurpleConnection *gc = purple_account_get_connection(account);
 		PurplePlugin *prpl;
-		
+
 		if (!gc || !PURPLE_CONNECTION_IS_CONNECTED(gc))
 			continue;
-		prpl = gc->prpl;
+		prpl = purple_connection_get_prpl(gc);
 
 		if (PURPLE_PLUGIN_HAS_ACTIONS(prpl)) {
 			item = gnt_menuitem_new(purple_account_get_username(account));
@@ -2273,6 +2434,30 @@
 	}
 }
 
+static void
+reconstruct_grouping_menu(void)
+{
+	GList *iter;
+	GntWidget *subsub;
+
+	if (!ggblist || !ggblist->grouping)
+		return;
+
+	subsub = gnt_menu_new(GNT_MENU_POPUP);
+	gnt_menuitem_set_submenu(ggblist->grouping, GNT_MENU(subsub));
+
+	for (iter = managers; iter; iter = iter->next) {
+		char menuid[128];
+		FinchBlistManager *manager = iter->data;
+		GntMenuItem *item = gnt_menuitem_new(_(manager->name));
+		snprintf(menuid, sizeof(menuid), "grouping-%s", manager->id);
+		gnt_menuitem_set_id(GNT_MENU_ITEM(item), menuid);
+		gnt_menu_add_item(GNT_MENU(subsub), item);
+		g_object_set_data_full(G_OBJECT(item), "grouping-id", g_strdup(manager->id), g_free);
+		gnt_menuitem_set_callback(item, menu_group_set_cb, NULL);
+	}
+}
+
 static gboolean
 auto_join_chats(gpointer data)
 {
@@ -2284,9 +2469,9 @@
 			node = purple_blist_node_next(node, FALSE)) {
 		if (PURPLE_BLIST_NODE_IS_CHAT(node)) {
 			PurpleChat *chat = (PurpleChat*)node;
-			if (chat->account == account &&
+			if (purple_chat_get_account(chat) == account &&
 					purple_blist_node_get_bool(node, "gnt-autojoin"))
-				serv_join_chat(purple_account_get_connection(chat->account), chat->components);
+				serv_join_chat(purple_account_get_connection(account), purple_chat_get_components(chat));
 		}
 	}
 	return FALSE;
@@ -2372,14 +2557,15 @@
 	if (!purple_account_is_connected(account))
 		return;
 
-	gc = purple_account_get_connection(account);	
+	gc = purple_account_get_connection(account);
 	purple_conversation_new(PURPLE_CONV_TYPE_CHAT, account, name);
 	chat = purple_blist_find_chat(account, name);
 	if (chat == NULL) {
-		if (PURPLE_PLUGIN_PROTOCOL_INFO(gc->prpl)->chat_info_defaults != NULL)
-			hash = PURPLE_PLUGIN_PROTOCOL_INFO(gc->prpl)->chat_info_defaults(gc, name);
+		PurplePluginProtocolInfo *info = PURPLE_PLUGIN_PROTOCOL_INFO(purple_connection_get_prpl(gc));
+		if (info->chat_info_defaults != NULL)
+			hash = info->chat_info_defaults(gc, name);
 	} else {
-		hash = chat->components;
+		hash = purple_chat_get_components(chat);
 	}
 	serv_join_chat(gc, hash);
 	if (chat == NULL && hash != NULL)
@@ -2439,6 +2625,13 @@
 }
 
 static void
+menu_group_set_cb(GntMenuItem *item, gpointer null)
+{
+	const char *id = g_object_get_data(G_OBJECT(item), "grouping-id");
+	purple_prefs_set_string(PREF_ROOT "/grouping", id);
+}
+
+static void
 create_menu(void)
 {
 	GntWidget *menu, *sub, *subsub;
@@ -2479,7 +2672,7 @@
 				purple_prefs_get_bool(PREF_ROOT "/emptygroups"));
 	gnt_menu_add_item(GNT_MENU(subsub), item);
 	gnt_menuitem_set_callback(GNT_MENU_ITEM(item), toggle_pref_cb, PREF_ROOT "/emptygroups");
-	
+
 	item = gnt_menuitem_check_new(_("Offline buddies"));
 	gnt_menuitem_set_id(GNT_MENU_ITEM(item), "show-offline-buddies");
 	gnt_menuitem_check_set_checked(GNT_MENU_ITEM_CHECK(item),
@@ -2513,21 +2706,25 @@
 	subsub = gnt_menu_new(GNT_MENU_POPUP);
 	gnt_menuitem_set_submenu(item, GNT_MENU(subsub));
 
-	item = gnt_menuitem_new("Buddy");
+	item = gnt_menuitem_new(_("Buddy"));
 	gnt_menuitem_set_id(GNT_MENU_ITEM(item), "add-buddy");
 	gnt_menu_add_item(GNT_MENU(subsub), item);
 	gnt_menuitem_set_callback(item, menu_add_buddy_cb, NULL);
 
-	item = gnt_menuitem_new("Chat");
+	item = gnt_menuitem_new(_("Chat"));
 	gnt_menuitem_set_id(GNT_MENU_ITEM(item), "add-chat");
 	gnt_menu_add_item(GNT_MENU(subsub), item);
 	gnt_menuitem_set_callback(item, menu_add_chat_cb, NULL);
 
-	item = gnt_menuitem_new("Group");
+	item = gnt_menuitem_new(_("Group"));
 	gnt_menuitem_set_id(GNT_MENU_ITEM(item), "add-group");
 	gnt_menu_add_item(GNT_MENU(subsub), item);
 	gnt_menuitem_set_callback(item, menu_add_group_cb, NULL);
 
+	ggblist->grouping = item = gnt_menuitem_new(_("Grouping"));
+	gnt_menu_add_item(GNT_MENU(sub), item);
+	reconstruct_grouping_menu();
+
 	reconstruct_accounts_menu();
 	gnt_menu_add_item(GNT_MENU(menu), ggblist->accounts);
 
@@ -2679,3 +2876,43 @@
 {
 	gnt_widget_set_size(ggblist->window, width, height);
 }
+
+void finch_blist_install_manager(const FinchBlistManager *manager)
+{
+	if (!g_list_find(managers, manager)) {
+		managers = g_list_append(managers, (gpointer)manager);
+		reconstruct_grouping_menu();
+		if (strcmp(manager->id, purple_prefs_get_string(PREF_ROOT "/grouping")) == 0)
+			purple_prefs_trigger_callback(PREF_ROOT "/grouping");
+	}
+}
+
+void finch_blist_uninstall_manager(const FinchBlistManager *manager)
+{
+	if (g_list_find(managers, manager)) {
+		managers = g_list_remove(managers, manager);
+		reconstruct_grouping_menu();
+		if (strcmp(manager->id, purple_prefs_get_string(PREF_ROOT "/grouping")) == 0)
+			purple_prefs_trigger_callback(PREF_ROOT "/grouping");
+	}
+}
+
+FinchBlistManager * finch_blist_manager_find(const char *id)
+{
+	GList *iter = managers;
+	if (!id)
+		return NULL;
+
+	for (; iter; iter = iter->next) {
+		FinchBlistManager *m = iter->data;
+		if (strcmp(id, m->id) == 0)
+			return m;
+	}
+	return NULL;
+}
+
+GntTree * finch_blist_get_tree(void)
+{
+	return ggblist ? GNT_TREE(ggblist->tree) : NULL;
+}
+
--- a/finch/gntblist.h	Sun Jan 27 04:26:38 2008 +0000
+++ b/finch/gntblist.h	Sun Jan 27 10:03:26 2008 +0000
@@ -27,12 +27,25 @@
 #define _GNT_BLIST_H
 
 #include "blist.h"
+#include "gnttree.h"
 
 /**********************************************************************
  * @name GNT BuddyList API
  **********************************************************************/
 /*@{*/
 
+typedef struct
+{
+	const char *id;                                    /**< An identifier for the manager. */
+	const char *name;                                  /**< Displayable name for the manager. */
+	gboolean (*init)(void);                            /**< Called right before it's being used. */
+	gboolean (*uninit)(void);                          /**< Called right after it's not being used any more. */
+	gboolean (*can_add_node)(PurpleBlistNode *node);   /**< Whether a node should be added to the view. */
+	gpointer (*find_parent)(PurpleBlistNode *node);    /**< Find the parent row for a node. */
+	gboolean (*create_tooltip)(gpointer selected_row, GString **body, char **title);  /**< Create tooltip for a selected row. */
+	gpointer reserved[4];
+} FinchBlistManager;
+
 /**
  * Get the ui-functions.
  *
@@ -103,6 +116,47 @@
  */
 gpointer finch_retrieve_user_info(PurpleConnection *conn, const char *name);
 
+/**
+ * Get the tree list of the buddy list.
+ * @return  The GntTree widget.
+ * @since 2.4.0
+ */
+GntTree * finch_blist_get_tree(void);
+
+/**
+ * Add an alternate buddy list manager.
+ *
+ * @param manager   The alternate buddylist manager.
+ * @since 2.4.0
+ */
+void finch_blist_install_manager(const FinchBlistManager *manager);
+
+/**
+ * Remove an alternate buddy list manager.
+ *
+ * @param manager   The buddy list manager to remove.
+ * @since 2.4.0
+ */
+void finch_blist_uninstall_manager(const FinchBlistManager *manager);
+
+/**
+ * Find a buddy list manager.
+ *
+ * @param id   The identifier for the desired buddy list manager.
+ *
+ * @return  The manager with the requested identifier, if available. @c NULL otherwise.
+ * @since 2.4.0
+ */
+FinchBlistManager * finch_blist_manager_find(const char *id);
+
+/**
+ * Request the active buddy list manager to add a node.
+ *
+ * @param node  The node to add
+ * @since 2.4.0
+ */
+void finch_blist_manager_add_node(PurpleBlistNode *node);
+
 /*@}*/
 
 #endif
--- a/finch/gntcertmgr.c	Sun Jan 27 04:26:38 2008 +0000
+++ b/finch/gntcertmgr.c	Sun Jan 27 10:03:26 2008 +0000
@@ -25,14 +25,13 @@
  *
  */
 
-#include "internal.h"
+#include "finch.h"
 
 #include "certificate.h"
 #include "debug.h"
 #include "notify.h"
 #include "request.h"
 
-#include "finch.h"
 #include "gntcertmgr.h"
 
 #include "gntbutton.h"
--- a/finch/gntconn.c	Sun Jan 27 04:26:38 2008 +0000
+++ b/finch/gntconn.c	Sun Jan 27 10:03:26 2008 +0000
@@ -101,13 +101,14 @@
 }
 
 static void
-finch_connection_report_disconnect(PurpleConnection *gc, const char *text)
+finch_connection_report_disconnect(PurpleConnection *gc, PurpleConnectionError reason,
+		const char *text)
 {
 	FinchAutoRecon *info;
 	PurpleAccount *account = purple_connection_get_account(gc);
 	GList *list;
 
-	if (!gc->wants_to_die) {
+	if (!purple_connection_error_is_fatal(reason)) {
 		info = g_hash_table_lookup(hash, account);
 
 		if (info == NULL) {
@@ -148,7 +149,7 @@
 	while (list) {
 		PurpleConversation *conv = list->data;
 		list = list->next;
-		if (conv->account != account ||
+		if (purple_conversation_get_account(conv) != account ||
 				purple_conv_chat_has_left(PURPLE_CONV_CHAT(conv)))
 			continue;
 		purple_conversation_set_data(conv, "want-to-rejoin", GINT_TO_POINTER(TRUE));
@@ -179,10 +180,10 @@
 	NULL, /* connected */
 	NULL, /* disconnected */
 	NULL, /* notice */
-	finch_connection_report_disconnect,
+	NULL,
 	NULL, /* network_connected */
 	NULL, /* network_disconnected */
-	NULL,
+	finch_connection_report_disconnect,
 	NULL,
 	NULL,
 	NULL
--- a/finch/gntconv.c	Sun Jan 27 04:26:38 2008 +0000
+++ b/finch/gntconv.c	Sun Jan 27 10:03:26 2008 +0000
@@ -71,6 +71,20 @@
 static int color_message_action;
 static int color_timestamp;
 
+static PurpleBuddy *
+find_buddy_for_conversation(PurpleConversation *conv)
+{
+	return purple_find_buddy(purple_conversation_get_account(conv),
+			purple_conversation_get_name(conv));
+}
+
+static PurpleChat *
+find_chat_for_conversation(PurpleConversation *conv)
+{
+	return purple_blist_find_chat(purple_conversation_get_account(conv),
+			purple_conversation_get_name(conv));
+}
+
 static PurpleBlistNode *
 get_conversation_blist_node(PurpleConversation *conv)
 {
@@ -78,11 +92,11 @@
 
 	switch (purple_conversation_get_type(conv)) {
 		case PURPLE_CONV_TYPE_IM:
-			node = (PurpleBlistNode*)purple_find_buddy(conv->account, conv->name);
-			node = node ? node->parent : NULL;
+			node = (PurpleBlistNode*)find_buddy_for_conversation(conv);
+			node = node ? purple_blist_node_get_parent(node) : NULL;
 			break;
 		case PURPLE_CONV_TYPE_CHAT:
-			node = (PurpleBlistNode*)purple_blist_find_chat(conv->account, conv->name);
+			node = (PurpleBlistNode*)find_chat_for_conversation(conv);
 			break;
 		default:
 			break;
@@ -168,7 +182,7 @@
 		}
 		g_free(error);
 	}
-	else if (!purple_account_is_connected(ggconv->active_conv->account))
+	else if (!purple_account_is_connected(purple_conversation_get_account(ggconv->active_conv)))
 	{
 		purple_conversation_write(ggconv->active_conv, "", _("Message was not sent, because you are not signed on."),
 				PURPLE_MESSAGE_ERROR | PURPLE_MESSAGE_NO_LOG, time(NULL));
@@ -236,11 +250,12 @@
 	if (!buddy)
 		return NULL;
 
-	for (node = ((PurpleBlistNode*)buddy)->parent->child; node; node = node->next) {
+	for (node = purple_blist_node_get_first_child(purple_blist_node_get_parent((PurpleBlistNode*)buddy));
+				node; node = purple_blist_node_get_sibling_next(node)) {
 		if (node == (PurpleBlistNode*)buddy)
 			continue;
 		if ((ret = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM,
-				((PurpleBuddy*)node)->name, ((PurpleBuddy*)node)->account)) != NULL)
+				purple_buddy_get_name((PurpleBuddy*)node), purple_buddy_get_account((PurpleBuddy*)node))) != NULL)
 			break;
 	}
 	return ret;
@@ -267,7 +282,7 @@
 		return;
 
 	im = PURPLE_CONV_IM(conv);
-	ggc = conv->ui_data;
+	ggc = FINCH_GET_DATA(conv);
 
 	if (purple_conv_im_get_typing_state(im) == PURPLE_TYPING) {
 		int scroll;
@@ -303,10 +318,10 @@
 static void
 buddy_signed_on_off(PurpleBuddy *buddy, gpointer null)
 {
-	PurpleConversation *conv = find_conv_with_contact(buddy->account, buddy->name);
+	PurpleConversation *conv = find_conv_with_contact(purple_buddy_get_account(buddy), purple_buddy_get_name(buddy));
 	if (conv == NULL)
 		return;
-	generate_send_to_menu(conv->ui_data);
+	generate_send_to_menu(FINCH_GET_DATA(conv));
 }
 
 static void
@@ -315,9 +330,10 @@
 	GList *list = purple_get_ims();
 	while (list) {
 		PurpleConversation *conv = list->data;
-		PurpleConversation *cc = find_conv_with_contact(conv->account, conv->name);
+		PurpleConversation *cc = find_conv_with_contact(
+				purple_conversation_get_account(conv), purple_conversation_get_name(conv));
 		if (cc)
-			generate_send_to_menu(cc->ui_data);
+			generate_send_to_menu(FINCH_GET_DATA(cc));
 		list = list->next;
 	}
 
@@ -331,16 +347,17 @@
 			GHashTable *comps = NULL;
 
 			list = list->next;
-			if (conv->account != gc->account ||
+			if (purple_conversation_get_account(conv) != purple_connection_get_account(gc) ||
 					!purple_conversation_get_data(conv, "want-to-rejoin"))
 				continue;
 
-			chat = purple_blist_find_chat(conv->account, conv->name);
+			chat = find_chat_for_conversation(conv);
 			if (chat == NULL) {
-				if (PURPLE_PLUGIN_PROTOCOL_INFO(gc->prpl)->chat_info_defaults != NULL)
-					comps = PURPLE_PLUGIN_PROTOCOL_INFO(gc->prpl)->chat_info_defaults(gc, conv->name);
+				PurplePluginProtocolInfo *info = PURPLE_PLUGIN_PROTOCOL_INFO(purple_connection_get_prpl(gc));
+				if (info->chat_info_defaults != NULL)
+					comps = info->chat_info_defaults(gc, purple_conversation_get_name(conv));
 			} else {
-				comps = chat->components;
+				comps = purple_chat_get_components(chat);
 			}
 			serv_join_chat(gc, comps);
 			if (chat == NULL && comps != NULL)
@@ -413,15 +430,11 @@
 
 		purple_conversation_write(conv, NULL,
 				_("Logging started. Future messages in this conversation will be logged."),
-				conv->logs ? (PURPLE_MESSAGE_SYSTEM) :
-				(PURPLE_MESSAGE_SYSTEM | PURPLE_MESSAGE_NO_LOG),
-				time(NULL));
+				PURPLE_MESSAGE_SYSTEM, time(NULL));
 	} else {
 		purple_conversation_write(conv, NULL,
 				_("Logging stopped. Future messages in this conversation will not be logged."),
-				conv->logs ? (PURPLE_MESSAGE_SYSTEM) :
-				(PURPLE_MESSAGE_SYSTEM | PURPLE_MESSAGE_NO_LOG),
-				time(NULL));
+				PURPLE_MESSAGE_SYSTEM, time(NULL));
 
 		/* Disable the logging second, so that the above message can be logged. */
 		purple_conversation_set_logging(conv, FALSE);
@@ -462,7 +475,8 @@
 	GSList *buds;
 	GList *list = NULL;
 
-	buds = purple_find_buddies(ggc->active_conv->account, ggc->active_conv->name);
+	buds = purple_find_buddies(purple_conversation_get_account(ggc->active_conv),
+			purple_conversation_get_name(ggc->active_conv));
 	if (!buds)
 		return;
 
@@ -476,7 +490,8 @@
 
 	for (; buds; buds = g_slist_delete_link(buds, buds)) {
 		PurpleBlistNode *node = (PurpleBlistNode *)purple_buddy_get_contact((PurpleBuddy *)buds->data);
-		for (node = node->child; node != NULL; node = node->next) {
+		for (node = purple_blist_node_get_first_child(node); node != NULL;
+				node = purple_blist_node_get_sibling_next(node)) {
 			PurpleBuddy *buddy = (PurpleBuddy *)node;
 			PurpleAccount *account = purple_buddy_get_account(buddy);
 			if (purple_account_is_connected(account)) {
@@ -529,7 +544,9 @@
 
 	if (purple_conversation_get_type(ggc->active_conv) == PURPLE_CONV_TYPE_IM) {
 		PurpleAccount *account = purple_conversation_get_account(ggc->active_conv);
-		PurplePluginProtocolInfo *pinfo = account->gc ? PURPLE_PLUGIN_PROTOCOL_INFO(account->gc->prpl) : NULL;
+		PurpleConnection *gc = purple_account_get_connection(account);
+		PurplePluginProtocolInfo *pinfo =
+			gc ? PURPLE_PLUGIN_PROTOCOL_INFO(purple_connection_get_prpl(gc)) : NULL;
 
 		if (pinfo && pinfo->get_info) {
 			item = gnt_menuitem_new(_("Get Info"));
@@ -543,7 +560,7 @@
 
 		if (pinfo && pinfo->send_file &&
 				(!pinfo->can_receive_file ||
-				 	pinfo->can_receive_file(account->gc, purple_conversation_get_name(ggc->active_conv)))) {
+					pinfo->can_receive_file(gc, purple_conversation_get_name(ggc->active_conv)))) {
 			item = gnt_menuitem_new(_("Send File"));
 			gnt_menu_add_item(GNT_MENU(sub), item);
 			gnt_menuitem_set_callback(item, send_file_cb, ggc);
@@ -593,7 +610,7 @@
 static void
 finch_create_conversation(PurpleConversation *conv)
 {
-	FinchConv *ggc = conv->ui_data;
+	FinchConv *ggc = FINCH_GET_DATA(conv);
 	char *title;
 	PurpleConversationType type;
 	PurpleConversation *cc;
@@ -603,9 +620,10 @@
 	if (ggc)
 		return;
 
-	cc = find_conv_with_contact(conv->account, conv->name);
-	if (cc && cc->ui_data)
-		ggc = cc->ui_data;
+	account = purple_conversation_get_account(conv);
+	cc = find_conv_with_contact(account, purple_conversation_get_name(conv));
+	if (cc && FINCH_GET_DATA(cc))
+		ggc = FINCH_GET_DATA(cc);
 	else
 		ggc = g_new0(FinchConv, 1);
 
@@ -617,14 +635,13 @@
 
 	ggc->list = g_list_prepend(ggc->list, conv);
 	ggc->active_conv = conv;
-	conv->ui_data = ggc;
+	FINCH_SET_DATA(conv, ggc);
 
-	if (cc && cc->ui_data) {
+	if (cc && FINCH_GET_DATA(cc)) {
 		finch_conversation_set_active(conv);
 		return;
 	}
 
-	account = purple_conversation_get_account(conv);
 	type = purple_conversation_get_type(conv);
 	title = get_conversation_title(conv, account);
 
@@ -633,7 +650,7 @@
 	gnt_box_set_toplevel(GNT_BOX(ggc->window), TRUE);
 	gnt_box_set_pad(GNT_BOX(ggc->window), 0);
 
-	switch(conv->type){
+	switch (purple_conversation_get_type(conv)) {
 		case PURPLE_CONV_TYPE_UNKNOWN:
 			gnt_widget_set_name(ggc->window, "conversation-window-unknown" );
 			break;
@@ -721,7 +738,7 @@
 finch_destroy_conversation(PurpleConversation *conv)
 {
 	/* do stuff here */
-	FinchConv *ggc = conv->ui_data;
+	FinchConv *ggc = FINCH_GET_DATA(conv);
 	ggc->list = g_list_remove(ggc->list, conv);
 	if (ggc->list && conv == ggc->active_conv)
 		ggc->active_conv = ggc->list->data;
@@ -738,7 +755,7 @@
 finch_write_common(PurpleConversation *conv, const char *who, const char *message,
 		PurpleMessageFlags flags, time_t mtime)
 {
-	FinchConv *ggconv = conv->ui_data;
+	FinchConv *ggconv = FINCH_GET_DATA(conv);
 	char *strip, *newline;
 	GntTextFormatFlags fl = 0;
 	int pos;
@@ -891,7 +908,7 @@
 static void
 finch_chat_add_users(PurpleConversation *conv, GList *users, gboolean new_arrivals)
 {
-	FinchConv *ggc = conv->ui_data;
+	FinchConv *ggc = FINCH_GET_DATA(conv);
 	GntEntry *entry = GNT_ENTRY(ggc->entry);
 
 	if (!new_arrivals)
@@ -930,7 +947,7 @@
 finch_chat_rename_user(PurpleConversation *conv, const char *old, const char *new_n, const char *new_a)
 {
 	/* Update the name for string completion */
-	FinchConv *ggc = conv->ui_data;
+	FinchConv *ggc = FINCH_GET_DATA(conv);
 	GntEntry *entry = GNT_ENTRY(ggc->entry);
 	GntTree *tree = GNT_TREE(ggc->u.chat->userlist);
 	PurpleConvChatBuddy *cb = purple_conv_chat_cb_find(PURPLE_CONV_CHAT(conv), new_n);
@@ -948,7 +965,7 @@
 finch_chat_remove_users(PurpleConversation *conv, GList *list)
 {
 	/* Remove the name from string completion */
-	FinchConv *ggc = conv->ui_data;
+	FinchConv *ggc = FINCH_GET_DATA(conv);
 	GntEntry *entry = GNT_ENTRY(ggc->entry);
 	for (; list; list = list->next) {
 		GntTree *tree = GNT_TREE(ggc->u.chat->userlist);
@@ -961,7 +978,7 @@
 finch_chat_update_user(PurpleConversation *conv, const char *user)
 {
 	PurpleConvChatBuddy *cb = purple_conv_chat_cb_find(PURPLE_CONV_CHAT(conv), user);
-	FinchConv *ggc = conv->ui_data;
+	FinchConv *ggc = FINCH_GET_DATA(conv);
 	gnt_tree_change_text(GNT_TREE(ggc->u.chat->userlist), (gpointer)user, 0, chat_flag_text(cb->flags));
 }
 
@@ -1070,7 +1087,7 @@
 clear_command_cb(PurpleConversation *conv,
                  const char *cmd, char **args, char **error, void *data)
 {
-	FinchConv *ggconv = conv->ui_data;
+	FinchConv *ggconv = FINCH_GET_DATA(conv);
 	gnt_text_view_clear(GNT_TEXT_VIEW(ggconv->tv));
 	purple_conversation_clear_message_history(conv);
 	return PURPLE_CMD_STATUS_OK;
@@ -1127,7 +1144,7 @@
 static PurpleCmdRet
 users_command_cb(PurpleConversation *conv, const char *cmd, char **args, char **error, gpointer data)
 {
-	FinchConv *fc = conv->ui_data;
+	FinchConv *fc = FINCH_GET_DATA(conv);
 	FinchConvChat *ch;
 	if (!fc)
 		return PURPLE_CMD_STATUS_FAILED;
@@ -1231,7 +1248,7 @@
 
 void finch_conversation_set_active(PurpleConversation *conv)
 {
-	FinchConv *ggconv = conv->ui_data;
+	FinchConv *ggconv = FINCH_GET_DATA(conv);
 	PurpleAccount *account;
 	char *title;
 
@@ -1247,7 +1264,7 @@
 
 void finch_conversation_set_info_widget(PurpleConversation *conv, GntWidget *widget)
 {
-	FinchConv *fc = conv->ui_data;
+	FinchConv *fc = FINCH_GET_DATA(conv);
 	int height, width;
 
 	gnt_box_remove_all(GNT_BOX(fc->info));
--- a/finch/gntft.c	Sun Jan 27 04:26:38 2008 +0000
+++ b/finch/gntft.c	Sun Jan 27 10:03:26 2008 +0000
@@ -23,13 +23,14 @@
  * along with this program; if not, write to the Free Software
  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
+#include "finch.h"
+
 #include <gnt.h>
 #include <gntbox.h>
 #include <gntbutton.h>
 #include <gntcheckbox.h>
 #include <gntlabel.h>
 #include <gnttree.h>
-#include "internal.h"
 
 #include "debug.h"
 #include "notify.h"
@@ -41,7 +42,7 @@
 #include "prefs.h"
 
 #define FINCHXFER(xfer) \
-	(PurpleGntXferUiData *)(xfer)->ui_data
+	(PurpleGntXferUiData *)FINCH_GET_DATA(xfer)
 
 typedef struct
 {
@@ -168,9 +169,15 @@
 stop_button_cb(GntButton *button)
 {
 	PurpleXfer *selected_xfer = gnt_tree_get_selection_data(GNT_TREE(xfer_dialog->tree));
-	if (selected_xfer && selected_xfer->status != PURPLE_XFER_STATUS_CANCEL_LOCAL &&
-			selected_xfer->status != PURPLE_XFER_STATUS_CANCEL_REMOTE &&
-			selected_xfer->status != PURPLE_XFER_STATUS_DONE)
+	PurpleXferStatusType status;
+
+	if (!selected_xfer)
+		return;
+
+	status = purple_xfer_get_status(selected_xfer);
+	if (status != PURPLE_XFER_STATUS_CANCEL_LOCAL &&
+			status != PURPLE_XFER_STATUS_CANCEL_REMOTE &&
+			status != PURPLE_XFER_STATUS_DONE)
 		purple_xfer_cancel_local(selected_xfer);
 }
 
@@ -397,14 +404,12 @@
 	time_t elapsed, now;
 	char *kbsec;
 
-	if (xfer->end_time != 0)
-		now = xfer->end_time;
-	else
+	if ((now = purple_xfer_get_end_time(xfer)) == 0)
 		now = time(NULL);
 
 	kb_sent = purple_xfer_get_bytes_sent(xfer) / 1024.0;
 	kb_rem  = purple_xfer_get_bytes_remaining(xfer) / 1024.0;
-	elapsed = (xfer->start_time > 0 ? now - xfer->start_time : 0);
+	elapsed = (purple_xfer_get_start_time(xfer) > 0 ? now - purple_xfer_get_start_time(xfer) : 0);
 	kbps    = (elapsed > 0 ? (kb_sent / elapsed) : 0);
 
 	g_return_if_fail(xfer_dialog != NULL);
@@ -463,7 +468,7 @@
 
 	/* This is where we're setting xfer->ui_data for the first time. */
 	data = g_new0(PurpleGntXferUiData, 1);
-	xfer->ui_data = data;
+	FINCH_SET_DATA(xfer, data);
 }
 
 static void
@@ -475,7 +480,7 @@
 	if (data) {
 		g_free(data->name);
 		g_free(data);
-		xfer->ui_data = NULL;
+		FINCH_SET_DATA(xfer, NULL);
 	}
 }
 
--- a/finch/gntidle.c	Sun Jan 27 04:26:38 2008 +0000
+++ b/finch/gntidle.c	Sun Jan 27 10:03:26 2008 +0000
@@ -21,7 +21,7 @@
  *
  */
 
-#include "internal.h"
+#include "finch.h"
 #include "gntidle.h"
 #include "gntwm.h"
 
--- a/finch/gntpounce.c	Sun Jan 27 04:26:38 2008 +0000
+++ b/finch/gntpounce.c	Sun Jan 27 10:03:26 2008 +0000
@@ -35,7 +35,6 @@
 #include <gnttree.h>
 #include <gntutils.h>
 
-#include "internal.h"
 #include "finch.h"
 
 #include "account.h"
@@ -808,39 +807,42 @@
 
 	if (purple_pounce_action_is_enabled(pounce, "popup-notify"))
 	{
-		char *tmp;
+		char *tmp = NULL;
 		const char *name_shown;
 		const char *reason;
+		struct {
+			PurplePounceEvent event;
+			const char *format;
+		} messages[] = {
+			{PURPLE_POUNCE_TYPING, _("%s has started typing to you (%s)")},
+			{PURPLE_POUNCE_TYPED, _("%s has paused while typing to you (%s)")},
+			{PURPLE_POUNCE_SIGNON, _("%s has signed on (%s)")},
+			{PURPLE_POUNCE_IDLE_RETURN, _("%s has returned from being idle (%s)")},
+			{PURPLE_POUNCE_AWAY_RETURN, _("%s has returned from being away (%s)")},
+			{PURPLE_POUNCE_TYPING_STOPPED, _("%s has stopped typing to you (%s)")},
+			{PURPLE_POUNCE_SIGNOFF, _("%s has signed off (%s)")},
+			{PURPLE_POUNCE_IDLE, _("%s has become idle (%s)")},
+			{PURPLE_POUNCE_AWAY, _("%s has gone away. (%s)")},
+			{PURPLE_POUNCE_MESSAGE_RECEIVED, _("%s has sent you a message. (%s)")},
+			{0, NULL}
+		};
+		int i;
 		reason = purple_pounce_action_get_attribute(pounce, "popup-notify",
-														  "reason");
+				"reason");
 
 		/*
 		 * Here we place the protocol name in the pounce dialog to lessen
 		 * confusion about what protocol a pounce is for.
 		 */
-		tmp = g_strdup_printf(
-				   (events & PURPLE_POUNCE_TYPING) ?
-				   _("%s has started typing to you (%s)") :
-				   (events & PURPLE_POUNCE_TYPED) ?
-				   _("%s has paused while typing to you (%s)") :
-				   (events & PURPLE_POUNCE_SIGNON) ?
-				   _("%s has signed on (%s)") :
-				   (events & PURPLE_POUNCE_IDLE_RETURN) ?
-				   _("%s has returned from being idle (%s)") :
-				   (events & PURPLE_POUNCE_AWAY_RETURN) ?
-				   _("%s has returned from being away (%s)") :
-				   (events & PURPLE_POUNCE_TYPING_STOPPED) ?
-				   _("%s has stopped typing to you (%s)") :
-				   (events & PURPLE_POUNCE_SIGNOFF) ?
-				   _("%s has signed off (%s)") :
-				   (events & PURPLE_POUNCE_IDLE) ?
-				   _("%s has become idle (%s)") :
-				   (events & PURPLE_POUNCE_AWAY) ?
-				   _("%s has gone away. (%s)") :
-				   (events & PURPLE_POUNCE_MESSAGE_RECEIVED) ?
-				   _("%s has sent you a message. (%s)") :
-				   _("Unknown pounce event. Please report this!"),
-				   alias, purple_account_get_protocol_name(account));
+		for (i = 0; messages[i].format != NULL; i++) {
+			if (messages[i].event & events) {
+				tmp = g_strdup_printf(messages[i].format, alias,
+						purple_account_get_protocol_name(account));
+				break;
+			}
+		}
+		if (tmp == NULL)
+			tmp = g_strdup(_("Unknown pounce event. Please report this!"));
 
 		/*
 		 * Ok here is where I change the second argument, title, from
@@ -880,7 +882,7 @@
 			purple_conversation_write(conv, NULL, message,
 									PURPLE_MESSAGE_SEND, time(NULL));
 
-			serv_send_im(account->gc, (char *)pouncee, (char *)message, 0);
+			serv_send_im(purple_account_get_connection(account), (char *)pouncee, (char *)message, 0);
 		}
 	}
 
--- a/finch/gntrequest.c	Sun Jan 27 04:26:38 2008 +0000
+++ b/finch/gntrequest.c	Sun Jan 27 10:03:26 2008 +0000
@@ -308,26 +308,26 @@
 			PurpleRequestFieldType type = purple_request_field_get_type(field);
 			if (type == PURPLE_REQUEST_FIELD_BOOLEAN)
 			{
-				GntWidget *check = field->ui_data;
+				GntWidget *check = FINCH_GET_DATA(field);
 				gboolean value = gnt_check_box_get_checked(GNT_CHECK_BOX(check));
 				purple_request_field_bool_set_value(field, value);
 			}
 			else if (type == PURPLE_REQUEST_FIELD_STRING)
 			{
-				GntWidget *entry = field->ui_data;
+				GntWidget *entry = FINCH_GET_DATA(field);
 				const char *text = gnt_entry_get_text(GNT_ENTRY(entry));
 				purple_request_field_string_set_value(field, (text && *text) ? text : NULL);
 			}
 			else if (type == PURPLE_REQUEST_FIELD_INTEGER)
 			{
-				GntWidget *entry = field->ui_data;
+				GntWidget *entry = FINCH_GET_DATA(field);
 				const char *text = gnt_entry_get_text(GNT_ENTRY(entry));
 				int value = (text && *text) ? atoi(text) : 0;
 				purple_request_field_int_set_value(field, value);
 			}
 			else if (type == PURPLE_REQUEST_FIELD_CHOICE)
 			{
-				GntWidget *combo = field->ui_data;
+				GntWidget *combo = FINCH_GET_DATA(field);
 				int id;
 				id = GPOINTER_TO_INT(gnt_combo_box_get_selected_data(GNT_COMBO_BOX(combo)));
 				purple_request_field_choice_set_value(field, id);
@@ -338,7 +338,7 @@
 				if (purple_request_field_list_get_multi_select(field))
 				{
 					GList *iter;
-					GntWidget *tree = field->ui_data;
+					GntWidget *tree = FINCH_GET_DATA(field);
 
 					iter = purple_request_field_list_get_items(field);
 					for (; iter; iter = iter->next)
@@ -351,7 +351,7 @@
 				}
 				else
 				{
-					GntWidget *combo = field->ui_data;
+					GntWidget *combo = FINCH_GET_DATA(field);
 					gpointer data = gnt_combo_box_get_selected_data(GNT_COMBO_BOX(combo));
 					list = g_list_append(list, data);
 				}
@@ -361,7 +361,7 @@
 			}
 			else if (type == PURPLE_REQUEST_FIELD_ACCOUNT)
 			{
-				GntWidget *combo = field->ui_data;
+				GntWidget *combo = FINCH_GET_DATA(field);
 				PurpleAccount *acc = gnt_combo_box_get_selected_data(GNT_COMBO_BOX(combo));
 				purple_request_field_account_set_value(field, acc);
 			}
@@ -424,9 +424,10 @@
 			*screenname = entry;
 	} else if (hint && !strcmp(hint, "group")) {
 		PurpleBlistNode *node;
-		for (node = purple_blist_get_root(); node; node = node->next) {
+		for (node = purple_blist_get_root(); node;
+				node = purple_blist_node_get_sibling_next(node)) {
 			if (PURPLE_BLIST_NODE_IS_GROUP(node))
-				gnt_entry_add_suggest(GNT_ENTRY(entry), ((PurpleGroup *)node)->name);
+				gnt_entry_add_suggest(GNT_ENTRY(entry), purple_group_get_name((PurpleGroup *)node));
 		}
 	}
 	return entry;
@@ -593,35 +594,35 @@
 
 			if (type == PURPLE_REQUEST_FIELD_BOOLEAN)
 			{
-				field->ui_data = create_boolean_field(field);
+				FINCH_SET_DATA(field, create_boolean_field(field));
 			}
 			else if (type == PURPLE_REQUEST_FIELD_STRING)
 			{
-				field->ui_data = create_string_field(field, &screenname);
+				FINCH_SET_DATA(field, create_string_field(field, &screenname));
 			}
 			else if (type == PURPLE_REQUEST_FIELD_INTEGER)
 			{
-				field->ui_data = create_integer_field(field);
+				FINCH_SET_DATA(field, create_integer_field(field));
 			}
 			else if (type == PURPLE_REQUEST_FIELD_CHOICE)
 			{
-				field->ui_data = create_choice_field(field);
+				FINCH_SET_DATA(field, create_choice_field(field));
 			}
 			else if (type == PURPLE_REQUEST_FIELD_LIST)
 			{
-				field->ui_data = create_list_field(field);
+				FINCH_SET_DATA(field, create_list_field(field));
 			}
 			else if (type == PURPLE_REQUEST_FIELD_ACCOUNT)
 			{
-				accountlist = field->ui_data = create_account_field(field);
+				accountlist = FINCH_SET_DATA(field, create_account_field(field));
 			}
 			else
 			{
-				field->ui_data = gnt_label_new_with_format(_("Not implemented yet."),
-						GNT_TEXT_FLAG_BOLD);
+				FINCH_SET_DATA(field, gnt_label_new_with_format(_("Not implemented yet."),
+						GNT_TEXT_FLAG_BOLD));
 			}
 			gnt_box_set_alignment(GNT_BOX(hbox), GNT_ALIGN_MID);
-			gnt_box_add_widget(GNT_BOX(hbox), GNT_WIDGET(field->ui_data));
+			gnt_box_add_widget(GNT_BOX(hbox), GNT_WIDGET(FINCH_GET_DATA(field)));
 		}
 		if (grlist->next)
 			gnt_box_add_widget(GNT_BOX(box), gnt_hline_new());
--- a/finch/gntroomlist.c	Sun Jan 27 04:26:38 2008 +0000
+++ b/finch/gntroomlist.c	Sun Jan 27 10:03:26 2008 +0000
@@ -24,7 +24,7 @@
  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
 
-#include"internal.h"
+#include "finch.h"
 
 #include "gntrequest.h"
 #include "gntroomlist.h"
@@ -116,7 +116,7 @@
 	if (gc == NULL || room == NULL)
 		return;
 
-	prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(gc->prpl);
+	prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(purple_connection_get_prpl(gc));
 
 	if(prpl_info != NULL && prpl_info->roomlist_room_serialize)
 		name = prpl_info->roomlist_room_serialize(room);
@@ -238,7 +238,7 @@
 		PurplePluginProtocolInfo *prpl_info = NULL;
 		PurpleConnection *gc = list->data;
 
-		prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(gc->prpl);
+		prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(purple_connection_get_prpl(gc));
 		if (prpl_info->roomlist_get_list != NULL) {
 			PurpleAccount *account = purple_connection_get_account(gc);
 			char *text = g_strdup_printf("%s (%s)",
@@ -341,7 +341,7 @@
 static void
 fl_create(PurpleRoomlist *list)
 {
-	list->ui_data = &froomlist;
+	FINCH_SET_DATA(list, &froomlist);
 	setup_roomlist(NULL);
 	update_roomlist(list);
 }
--- a/finch/gntsound.c	Sun Jan 27 04:26:38 2008 +0000
+++ b/finch/gntsound.c	Sun Jan 27 10:03:26 2008 +0000
@@ -23,7 +23,6 @@
  * along with this program; if not, write to the Free Software
  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
-#include "internal.h"
 #include "finch.h"
 
 #ifdef _WIN32
@@ -142,13 +141,15 @@
 	char *nick = NULL;
 	char *name = NULL;
 	gboolean ret = FALSE;
+	PurpleAccount *account;
+
 	chat = purple_conversation_get_chat_data(conv);
-
 	if (chat == NULL)
 		return ret;
 
-	nick = g_strdup(purple_normalize(conv->account, chat->nick));
-	name = g_strdup(purple_normalize(conv->account, aname));
+	account = purple_conversation_get_account(conv);
+	nick = g_strdup(purple_normalize(account, chat->nick));
+	name = g_strdup(purple_normalize(account, aname));
 
 	if (g_utf8_collate(nick, name) == 0)
 		ret = TRUE;
--- a/finch/gntui.c	Sun Jan 27 04:26:38 2008 +0000
+++ b/finch/gntui.c	Sun Jan 27 10:03:26 2008 +0000
@@ -19,7 +19,7 @@
  * along with this program; if not, write to the Free Software
  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
  */
-#include "internal.h"
+#include "finch.h"
 
 #include "gntui.h"
 
--- a/finch/libgnt/gnttree.c	Sun Jan 27 04:26:38 2008 +0000
+++ b/finch/libgnt/gnttree.c	Sun Jan 27 10:03:26 2008 +0000
@@ -39,6 +39,7 @@
 {
 	PROP_0,
 	PROP_COLUMNS,
+	PROP_EXPANDER,
 };
 
 enum
@@ -59,6 +60,7 @@
 
 	GCompareFunc compare;
 	int lastvisible;
+	int expander_level;
 };
 
 #define	TAB_SIZE 3
@@ -338,7 +340,7 @@
 						row->isselected ? 'X' : ' ');
 				fl = 4;
 			}
-			else if (row->parent == NULL && row->child)
+			else if (find_depth(row) < tree->priv->expander_level && row->child)
 			{
 				if (row->collapsed)
 				{
@@ -951,6 +953,11 @@
 		case PROP_COLUMNS:
 			_gnt_tree_init_internals(tree, g_value_get_int(value));
 			break;
+		case PROP_EXPANDER:
+			if (tree->priv->expander_level == g_value_get_int(value))
+				break;
+			tree->priv->expander_level = g_value_get_int(value);
+			g_object_notify(obj, "expander-level");
 		default:
 			break;
 	}
@@ -965,6 +972,9 @@
 		case PROP_COLUMNS:
 			g_value_set_int(value, tree->ncol);
 			break;
+		case PROP_EXPANDER:
+			g_value_set_int(value, tree->priv->expander_level);
+			break;
 		default:
 			break;
 	}
@@ -995,6 +1005,14 @@
 				G_PARAM_READWRITE|G_PARAM_STATIC_NAME|G_PARAM_STATIC_NICK|G_PARAM_STATIC_BLURB
 			)
 		);
+	g_object_class_install_property(gclass,
+			PROP_EXPANDER,
+			g_param_spec_int("expander-level", "Expander level",
+				"Number of levels to show expander in the tree.",
+				0, G_MAXINT, 1,
+				G_PARAM_READWRITE|G_PARAM_STATIC_NAME|G_PARAM_STATIC_NICK|G_PARAM_STATIC_BLURB
+			)
+		);
 
 	signals[SIG_SELECTION_CHANGED] = 
 		g_signal_new("selection-changed",
@@ -1618,6 +1636,7 @@
 {
 	GntWidget *widget = g_object_new(GNT_TYPE_TREE,
 			"columns", col,
+			"expander-level", 1,
 			NULL);
 
 	return widget;
@@ -1841,3 +1860,9 @@
 	tree->priv->search_func = func;
 }
 
+gpointer gnt_tree_get_parent_key(GntTree *tree, gpointer key)
+{
+	GntTreeRow *row = g_hash_table_lookup(tree->hash, key);
+	return (row && row->parent) ? row->parent->key : NULL;
+}
+
--- a/finch/libgnt/gnttree.h	Sun Jan 27 04:26:38 2008 +0000
+++ b/finch/libgnt/gnttree.h	Sun Jan 27 10:03:26 2008 +0000
@@ -575,6 +575,17 @@
 void gnt_tree_set_search_function(GntTree *tree,
 		gboolean (*func)(GntTree *tree, gpointer key, const char *search, const char *current));
 
+/**
+ * Get the parent key for a row.
+ *
+ * @param  tree  The tree
+ * @param  key   The key for the row.
+ *
+ * @return The key of the parent row.
+ * @since 2.4.0
+ */
+gpointer gnt_tree_get_parent_key(GntTree *tree, gpointer key);
+
 G_END_DECLS
 
 #endif /* GNT_TREE_H */
--- a/finch/plugins/Makefile.am	Sun Jan 27 04:26:38 2008 +0000
+++ b/finch/plugins/Makefile.am	Sun Jan 27 10:03:26 2008 +0000
@@ -1,7 +1,8 @@
 gntclipboard_la_LDFLAGS = -module -avoid-version
 gntgf_la_LDFLAGS      = -module -avoid-version
 gnthistory_la_LDFLAGS = -module -avoid-version
-gntlastlog_la_LDFLAGS    = -module -avoid-version
+gntlastlog_la_LDFLAGS = -module -avoid-version
+grouping_la_LDFLAGS   = -module -avoid-version
 
 if PLUGINS
 
@@ -9,7 +10,8 @@
 	gntclipboard.la \
 	gntgf.la \
 	gnthistory.la \
-	gntlastlog.la
+	gntlastlog.la \
+	grouping.la
 
 plugindir = $(libdir)/finch
 
@@ -17,6 +19,7 @@
 gntgf_la_SOURCES      = gntgf.c
 gnthistory_la_SOURCES = gnthistory.c
 gntlastlog_la_SOURCES = lastlog.c
+grouping_la_SOURCES   = grouping.c
 
 gntclipboard_la_CFLAGS = $(X11_CFLAGS)
 gntgf_la_CFLAGS = $(X11_CFLAGS)
@@ -25,6 +28,7 @@
 gntgf_la_LIBADD       = $(GLIB_LIBS) $(X11_LIBS) $(top_builddir)/finch/libgnt/libgnt.la
 gnthistory_la_LIBADD  = $(GLIB_LIBS)
 gntlastlog_la_LIBADD  = $(GLIB_LIBS)
+grouping_la_LIBADD    = $(GLIB_LIBS) $(top_builddir)/finch/libgnt/libgnt.la
 
 endif # PLUGINS
 
--- a/finch/plugins/gnthistory.c	Sun Jan 27 04:26:38 2008 +0000
+++ b/finch/plugins/gnthistory.c	Sun Jan 27 10:03:26 2008 +0000
@@ -51,8 +51,7 @@
 	PurpleMessageFlags mflag;
 
 	convtype = purple_conversation_get_type(c);
-	if (convtype == PURPLE_CONV_TYPE_IM)
-	{
+	if (convtype == PURPLE_CONV_TYPE_IM) {
 		GSList *buddies;
 		GSList *cur;
 
@@ -62,17 +61,17 @@
 			return;
 
 		/* Find buddies for this conversation. */
-	        buddies = purple_find_buddies(account, name);
+		buddies = purple_find_buddies(account, name);
 
 		/* If we found at least one buddy, save the first buddy's alias. */
 		if (buddies != NULL)
 			alias = purple_buddy_get_contact_alias((PurpleBuddy *)buddies->data);
 
-	        for (cur = buddies; cur != NULL; cur = cur->next)
-	        {
-	                PurpleBlistNode *node = cur->data;
-	                if ((node != NULL) && ((node->prev != NULL) || (node->next != NULL)))
-	                {
+		for (cur = buddies; cur != NULL; cur = cur->next) {
+			PurpleBlistNode *node = cur->data;
+			if ((node != NULL) &&
+					((purple_blist_node_get_sibling_prev(node) != NULL) ||
+						(purple_blist_node_get_sibling_next(node) != NULL))) {
 				PurpleBlistNode *node2;
 
 				alias = purple_buddy_get_contact_alias((PurpleBuddy *)node);
@@ -80,26 +79,24 @@
 				/* We've found a buddy that matches this conversation.  It's part of a
 				 * PurpleContact with more than one PurpleBuddy.  Loop through the PurpleBuddies
 				 * in the contact and get all the logs. */
-				for (node2 = node->parent->child ; node2 != NULL ; node2 = node2->next)
-				{
+				for (node2 = purple_blist_node_get_first_child(purple_blist_node_get_parent(node));
+						node2 != NULL ; node2 = purple_blist_node_get_sibling_next(node2)) {
 					logs = g_list_concat(
-						purple_log_get_logs(PURPLE_LOG_IM,
-							purple_buddy_get_name((PurpleBuddy *)node2),
-							purple_buddy_get_account((PurpleBuddy *)node2)),
-						logs);
+							purple_log_get_logs(PURPLE_LOG_IM,
+								purple_buddy_get_name((PurpleBuddy *)node2),
+								purple_buddy_get_account((PurpleBuddy *)node2)),
+							logs);
 				}
 				break;
-	                }
-	        }
-	        g_slist_free(buddies);
+			}
+		}
+		g_slist_free(buddies);
 
 		if (logs == NULL)
 			logs = purple_log_get_logs(PURPLE_LOG_IM, name, account);
 		else
 			logs = g_list_sort(logs, purple_log_compare);
-	}
-	else if (convtype == PURPLE_CONV_TYPE_CHAT)
-	{
+	} else if (convtype == PURPLE_CONV_TYPE_CHAT) {
 		/* If we're not logging, don't show anything.
 		 * Otherwise, we might show a very old log. */
 		if (!purple_prefs_get_bool("/purple/logging/log_chats"))
@@ -115,7 +112,7 @@
 	history = purple_log_read((PurpleLog*)logs->data, &flags);
 
 	header = g_strdup_printf(_("<b>Conversation with %s on %s:</b><br>"), alias,
-							 purple_date_format_full(localtime(&((PurpleLog *)logs->data)->time)));
+			purple_date_format_full(localtime(&((PurpleLog *)logs->data)->time)));
 	purple_conversation_write(c, "", header, mflag, time(NULL));
 	g_free(header);
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/finch/plugins/grouping.c	Sun Jan 27 10:03:26 2008 +0000
@@ -0,0 +1,278 @@
+/**
+ * @file grouping.c  Provides different grouping options.
+ *
+ * Copyright (C) 2008 Sadrul Habib Chowdhury <sadrul@users.sourceforge.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
+ */
+
+#define PURPLE_PLUGIN
+
+#include "internal.h"
+#include "purple.h"
+
+#include "gntblist.h"
+#include "gntplugin.h"
+
+#include "gnttree.h"
+
+/**
+ * Online/Offline
+ */
+static PurpleBlistNode online = {.type = PURPLE_BLIST_OTHER_NODE},
+					   offline = {.type = PURPLE_BLIST_OTHER_NODE};
+
+static gboolean on_offline_init()
+{
+	GntTree *tree = finch_blist_get_tree();
+
+	gnt_tree_add_row_after(tree, &online,
+			gnt_tree_create_row(tree, _("Online")), NULL, NULL);
+	gnt_tree_add_row_after(tree, &offline,
+			gnt_tree_create_row(tree, _("Offline")), NULL, &online);
+
+	return TRUE;
+}
+
+static gboolean on_offline_can_add_node(PurpleBlistNode *node)
+{
+	switch (purple_blist_node_get_type(node)) {
+		case PURPLE_BLIST_CONTACT_NODE:
+			{
+				PurpleContact *contact = (PurpleContact*)node;
+				if (contact->currentsize > 0)
+					return TRUE;
+				return FALSE;
+			}
+			break;
+		case PURPLE_BLIST_BUDDY_NODE:
+			{
+				PurpleBuddy *buddy = (PurpleBuddy*)node;
+				if (PURPLE_BUDDY_IS_ONLINE(buddy))
+					return TRUE;
+				if (purple_prefs_get_bool("/finch/blist/showoffline") &&
+						purple_account_is_connected(purple_buddy_get_account(buddy)))
+					return TRUE;
+				return FALSE;
+			}
+			break;
+		case PURPLE_BLIST_CHAT_NODE:
+			{
+				PurpleChat *chat = (PurpleChat*)node;
+				return purple_account_is_connected(purple_chat_get_account(chat));
+			}
+			break;
+		default:
+			return FALSE;
+	}
+}
+
+static gpointer on_offline_find_parent(PurpleBlistNode *node)
+{
+	gpointer ret = NULL;
+
+	switch (purple_blist_node_get_type(node)) {
+		case PURPLE_BLIST_CONTACT_NODE:
+			node = (PurpleBlistNode*)purple_contact_get_priority_buddy((PurpleContact*)node);
+			ret = PURPLE_BUDDY_IS_ONLINE((PurpleBuddy*)node) ? &online : &offline;
+			break;
+		case PURPLE_BLIST_BUDDY_NODE:
+			ret = purple_blist_node_get_parent(node);
+			finch_blist_manager_add_node(ret);
+			break;
+		case PURPLE_BLIST_CHAT_NODE:
+			ret = &online;
+			break;
+		default:
+			break;
+	}
+	return ret;
+}
+
+static gboolean on_offline_create_tooltip(gpointer selected_row, GString **body, char **tool_title)
+{
+	static FinchBlistManager *def = NULL;
+	PurpleBlistNode *node = selected_row;
+
+	if (def == NULL)
+		def = finch_blist_manager_find("default");
+
+	if (purple_blist_node_get_type(node) == PURPLE_BLIST_OTHER_NODE) {
+		/* There should be some easy way of getting the total online count,
+		 * or total number of chats. Doing a loop here will probably be pretty
+		 * expensive. */
+		if (body)
+			*body = g_string_new(node == &online ? _("Online Buddies") : _("Offline Buddies"));
+		return TRUE;
+	} else {
+		return def ? def->create_tooltip(selected_row, body, tool_title) : FALSE;
+	}
+}
+
+static FinchBlistManager on_offline =
+{
+	"on-offline",
+	N_("Online/Offline"),
+	on_offline_init,
+	NULL,
+	on_offline_can_add_node,
+	on_offline_find_parent,
+	on_offline_create_tooltip,
+	{NULL, NULL, NULL, NULL}
+};
+
+/**
+ * Meebo-like Grouping.
+ */
+static PurpleBlistNode meebo = {.type = PURPLE_BLIST_OTHER_NODE};
+static gboolean meebo_init()
+{
+	GntTree *tree = finch_blist_get_tree();
+	if (!g_list_find(gnt_tree_get_rows(tree), &meebo)) {
+		gnt_tree_add_row_last(tree, &meebo,
+				gnt_tree_create_row(tree, _("Offline")), NULL);
+	}
+	return TRUE;
+}
+
+static gpointer meebo_find_parent(PurpleBlistNode *node)
+{
+	static FinchBlistManager *def = NULL;
+	if (def == NULL)
+		def = finch_blist_manager_find("default");
+
+	if (PURPLE_BLIST_NODE_IS_CONTACT(node)) {
+		PurpleBuddy *buddy = purple_contact_get_priority_buddy((PurpleContact*)node);
+		if (buddy && !PURPLE_BUDDY_IS_ONLINE(buddy)) {
+			return &meebo;
+		}
+	}
+	return def->find_parent(node);
+}
+
+static FinchBlistManager meebo_group =
+{
+	"meebo",
+	N_("Meebo"),
+	meebo_init,
+	NULL,
+	NULL,
+	meebo_find_parent,
+	NULL,
+	{NULL, NULL, NULL, NULL}
+};
+
+/**
+ * No Grouping.
+ */
+static gboolean no_group_init()
+{
+	GntTree *tree = finch_blist_get_tree();
+	g_object_set(G_OBJECT(tree), "expander-level", 0, NULL);
+	return TRUE;
+}
+
+static gboolean no_group_uninit()
+{
+	GntTree *tree = finch_blist_get_tree();
+	g_object_set(G_OBJECT(tree), "expander-level", 1, NULL);
+	return TRUE;
+}
+
+static gboolean no_group_can_add_node(PurpleBlistNode *node)
+{
+	return on_offline_can_add_node(node);   /* These happen to be the same */
+}
+
+static gpointer no_group_find_parent(PurpleBlistNode *node)
+{
+	gpointer ret = NULL;
+
+	switch (purple_blist_node_get_type(node)) {
+		case PURPLE_BLIST_BUDDY_NODE:
+			ret = purple_blist_node_get_parent(node);
+			finch_blist_manager_add_node(ret);
+			break;
+		default:
+			break;
+	}
+	return ret;
+}
+
+static FinchBlistManager no_group =
+{
+	"no-group",
+	N_("No Grouping"),
+	no_group_init,
+	no_group_uninit,
+	no_group_can_add_node,
+	no_group_find_parent,
+	NULL,
+	{NULL, NULL, NULL, NULL}
+};
+
+static gboolean
+plugin_load(PurplePlugin *plugin)
+{
+	finch_blist_install_manager(&on_offline);
+	finch_blist_install_manager(&meebo_group);
+	finch_blist_install_manager(&no_group);
+	return TRUE;
+}
+
+static gboolean
+plugin_unload(PurplePlugin *plugin)
+{
+	finch_blist_uninstall_manager(&on_offline);
+	finch_blist_uninstall_manager(&meebo_group);
+	finch_blist_uninstall_manager(&no_group);
+	return TRUE;
+}
+
+static PurplePluginInfo info =
+{
+	PURPLE_PLUGIN_MAGIC,
+	PURPLE_MAJOR_VERSION,
+	PURPLE_MINOR_VERSION,
+	PURPLE_PLUGIN_STANDARD,
+	FINCH_PLUGIN_TYPE,
+	0,
+	NULL,
+	PURPLE_PRIORITY_DEFAULT,
+	"grouping",
+	N_("Grouping"),
+	VERSION,
+	N_("Provides alternate buddylist grouping options."),
+	N_("Provides alternate buddylist grouping options."),
+	"Sadrul H Chowdhury <sadrul@users.sourceforge.net>",
+	PURPLE_WEBSITE,
+	plugin_load,
+	plugin_unload,
+	NULL,
+	NULL,
+	NULL,
+	NULL,
+	NULL,
+	NULL,NULL,NULL,NULL
+};
+
+static void
+init_plugin(PurplePlugin *plugin)
+{
+}
+
+PURPLE_INIT_PLUGIN(ignore, init_plugin, info)
+
+
--- a/libpurple/accountopt.h	Sun Jan 27 04:26:38 2008 +0000
+++ b/libpurple/accountopt.h	Sun Jan 27 10:03:26 2008 +0000
@@ -83,7 +83,10 @@
 /*@{*/
 
 /**
- * Creates a new account option.
+ * Creates a new account option.  If you know what @a type will be in advance,
+ * consider using purple_account_option_bool_new(),
+ * purple_account_option_int_new(), purple_account_option_string_new() or
+ * purple_account_option_list_new() (as appropriate) instead.
  *
  * @param type      The type of option.
  * @param text      The text of the option.
@@ -91,8 +94,8 @@
  *
  * @return The account option.
  */
-PurpleAccountOption *purple_account_option_new(PurplePrefType type, const char *text,
-										   const char *pref_name);
+PurpleAccountOption *purple_account_option_new(PurplePrefType type,
+	const char *text, const char *pref_name);
 
 /**
  * Creates a new boolean account option.
@@ -104,8 +107,7 @@
  * @return The account option.
  */
 PurpleAccountOption *purple_account_option_bool_new(const char *text,
-												const char *pref_name,
-												gboolean default_value);
+	const char *pref_name, gboolean default_value);
 
 /**
  * Creates a new integer account option.
@@ -117,8 +119,7 @@
  * @return The account option.
  */
 PurpleAccountOption *purple_account_option_int_new(const char *text,
-											   const char *pref_name,
-											   int default_value);
+	const char *pref_name, int default_value);
 
 /**
  * Creates a new string account option.
@@ -130,8 +131,7 @@
  * @return The account option.
  */
 PurpleAccountOption *purple_account_option_string_new(const char *text,
-												  const char *pref_name,
-												  const char *default_value);
+	const char *pref_name, const char *default_value);
 
 /**
  * Creates a new list account option.
@@ -140,7 +140,7 @@
  * strings inside will be freed automatically.
  *
  * The list is a list of PurpleKeyValuePair items. The key is the ID stored and
- * used internally, and the value is the label displayed.
+ * used internally, and the <tt>(const char *)</tt> value is the label displayed.
  *
  * @param text      The text of the option.
  * @param pref_name The account preference name for the option.
@@ -149,8 +149,7 @@
  * @return The account option.
  */
 PurpleAccountOption *purple_account_option_list_new(const char *text,
-												const char *pref_name,
-												GList *list);
+	const char *pref_name, GList *list);
 
 /**
  * Destroys an account option.
@@ -240,11 +239,13 @@
 const char *purple_account_option_get_text(const PurpleAccountOption *option);
 
 /**
- * Returns the account setting for an account option.
+ * Returns the name of an account option.  This corresponds to the @c pref_name
+ * parameter supplied to purple_account_option_new() or one of the
+ * type-specific constructors.
  *
  * @param option The account option.
  *
- * @return The account setting.
+ * @return The option's name.
  */
 const char *purple_account_option_get_setting(const PurpleAccountOption *option);
 
--- a/libpurple/blist.c	Sun Jan 27 04:26:38 2008 +0000
+++ b/libpurple/blist.c	Sun Jan 27 10:03:26 2008 +0000
@@ -640,10 +640,10 @@
 
 		if (purple_account_is_connected(buddy->account))
 		{
-			int cmp;
-
-			cmp = purple_presence_compare(purple_buddy_get_presence(new_priority),
-			                            purple_buddy_get_presence(buddy));
+			int cmp = 1;
+			if (purple_account_is_connected(new_priority->account))
+				cmp = purple_presence_compare(purple_buddy_get_presence(new_priority),
+						purple_buddy_get_presence(buddy));
 
 			if (cmp > 0 || (cmp == 0 &&
 			                purple_prefs_get_bool("/purple/contact/last_match")))
@@ -753,6 +753,26 @@
 	return ret;
 }
 
+PurpleBlistNode *purple_blist_node_get_parent(PurpleBlistNode *node)
+{
+	return node ? node->parent : NULL;
+}
+
+PurpleBlistNode *purple_blist_node_get_first_child(PurpleBlistNode *node)
+{
+	return node ? node->child : NULL;
+}
+
+PurpleBlistNode *purple_blist_node_get_sibling_next(PurpleBlistNode *node)
+{
+	return node? node->next : NULL;
+}
+
+PurpleBlistNode *purple_blist_node_get_sibling_prev(PurpleBlistNode *node)
+{
+	return node? node->prev : NULL;
+}
+
 void
 purple_blist_update_buddy_status(PurpleBuddy *buddy, PurpleStatus *old_status)
 {
@@ -2232,6 +2252,22 @@
 	return (PurpleGroup *)(((PurpleBlistNode *)chat)->parent);
 }
 
+PurpleAccount *
+purple_chat_get_account(PurpleChat *chat)
+{
+	g_return_val_if_fail(chat != NULL, NULL);
+
+	return chat->account;
+}
+
+GHashTable *
+purple_chat_get_components(PurpleChat *chat)
+{
+	g_return_val_if_fail(chat != NULL, NULL);
+
+	return chat->components;
+}
+
 PurpleContact *purple_buddy_get_contact(PurpleBuddy *buddy)
 {
 	g_return_val_if_fail(buddy != NULL, NULL);
--- a/libpurple/blist.h	Sun Jan 27 04:26:38 2008 +0000
+++ b/libpurple/blist.h	Sun Jan 27 10:03:26 2008 +0000
@@ -53,13 +53,13 @@
 
 } PurpleBlistNodeType;
 
-#define PURPLE_BLIST_NODE_IS_CHAT(n)    ((n)->type == PURPLE_BLIST_CHAT_NODE)
-#define PURPLE_BLIST_NODE_IS_BUDDY(n)   ((n)->type == PURPLE_BLIST_BUDDY_NODE)
-#define PURPLE_BLIST_NODE_IS_CONTACT(n) ((n)->type == PURPLE_BLIST_CONTACT_NODE)
-#define PURPLE_BLIST_NODE_IS_GROUP(n)   ((n)->type == PURPLE_BLIST_GROUP_NODE)
+#define PURPLE_BLIST_NODE_IS_CHAT(n)    (purple_blist_node_get_type(n) == PURPLE_BLIST_CHAT_NODE)
+#define PURPLE_BLIST_NODE_IS_BUDDY(n)   (purple_blist_node_get_type(n) == PURPLE_BLIST_BUDDY_NODE)
+#define PURPLE_BLIST_NODE_IS_CONTACT(n) (purple_blist_node_get_type(n) == PURPLE_BLIST_CONTACT_NODE)
+#define PURPLE_BLIST_NODE_IS_GROUP(n)   (purple_blist_node_get_type(n) == PURPLE_BLIST_GROUP_NODE)
 
 #define PURPLE_BUDDY_IS_ONLINE(b) \
-	((b) != NULL && purple_account_is_connected((b)->account) && \
+	((b) != NULL && purple_account_is_connected(purple_buddy_get_account(b)) && \
 	 purple_presence_is_online(purple_buddy_get_presence(b)))
 
 typedef enum
@@ -231,10 +231,66 @@
  * @param node		A node.
  * @param offline	Whether to include nodes for offline accounts
  * @return	The next node
+ * @see purple_blist_node_get_parent
+ * @see purple_blist_node_get_first_child
+ * @see purple_blist_node_get_sibling_next
+ * @see purple_blist_node_get_sibling_prev
  */
 PurpleBlistNode *purple_blist_node_next(PurpleBlistNode *node, gboolean offline);
 
 /**
+ * Returns the parent node of a given node.
+ *
+ * @param node A node.
+ * @return  The parent node.
+ * @since 2.4.0
+ * @see purple_blist_node_get_first_child
+ * @see purple_blist_node_get_sibling_next
+ * @see purple_blist_node_get_sibling_prev
+ * @see purple_blist_node_next
+ */
+PurpleBlistNode *purple_blist_node_get_parent(PurpleBlistNode *node);
+
+/**
+ * Returns the the first child node of a given node.
+ *
+ * @param node A node.
+ * @return  The child node.
+ * @since 2.4.0
+ * @see purple_blist_node_get_parent
+ * @see purple_blist_node_get_sibling_next
+ * @see purple_blist_node_get_sibling_prev
+ * @see purple_blist_node_next
+ */
+PurpleBlistNode *purple_blist_node_get_first_child(PurpleBlistNode *node);
+
+/**
+ * Returns the sibling node of a given node.
+ *
+ * @param node A node.
+ * @return  The sibling node.
+ * @since 2.4.0
+ * @see purple_blist_node_get_parent
+ * @see purple_blist_node_get_first_child
+ * @see purple_blist_node_get_sibling_prev
+ * @see purple_blist_node_next
+ */
+PurpleBlistNode *purple_blist_node_get_sibling_next(PurpleBlistNode *node);
+
+/**
+ * Returns the previous sibling node of a given node.
+ *
+ * @param node A node.
+ * @return  The sibling node.
+ * @since 2.4.0
+ * @see purple_blist_node_get_parent
+ * @see purple_blist_node_get_first_child
+ * @see purple_blist_node_get_sibling_next
+ * @see purple_blist_node_next
+ */
+PurpleBlistNode *purple_blist_node_get_sibling_prev(PurpleBlistNode *node);
+
+/**
  * Shows the buddy list, creating a new one if necessary.
  */
 void purple_blist_show(void);
@@ -667,6 +723,26 @@
 PurpleGroup *purple_chat_get_group(PurpleChat *chat);
 
 /**
+ * Returns the account the chat belongs to.
+ *
+ * @param chat  The chat.
+ *
+ * @return  The account the chat belongs to.
+ * @since 2.4.0
+ */
+PurpleAccount *purple_chat_get_account(PurpleChat *chat);
+
+/**
+ * Get a hashtable containing information about a chat.
+ *
+ * @param chat  The chat.
+ *
+ * @constreturn  The hashtable.
+ * @since 2.4.0
+ */
+GHashTable *purple_chat_get_components(PurpleChat *chat);
+
+/**
  * Returns the group of which the buddy is a member.
  *
  * @param buddy   The buddy
--- a/libpurple/buddyicon.c	Sun Jan 27 04:26:38 2008 +0000
+++ b/libpurple/buddyicon.c	Sun Jan 27 10:03:26 2008 +0000
@@ -701,11 +701,6 @@
 	}
 	unref_filename(old_icon);
 
-	if (img)
-		g_hash_table_insert(pointer_icon_cache, account, img);
-	else
-		g_hash_table_remove(pointer_icon_cache, account);
-
 	if (purple_account_is_connected(account))
 	{
 		PurpleConnection *gc;
@@ -729,6 +724,11 @@
 	}
 	g_free(old_icon);
 
+	if (img)
+		g_hash_table_insert(pointer_icon_cache, account, img);
+	else
+		g_hash_table_remove(pointer_icon_cache, account);
+
 	return img;
 }
 
--- a/libpurple/connection.c	Sun Jan 27 04:26:38 2008 +0000
+++ b/libpurple/connection.c	Sun Jan 27 10:03:26 2008 +0000
@@ -52,8 +52,19 @@
 	PurpleConnection *gc = data;
 	PurplePluginProtocolInfo *prpl_info = NULL;
 
-	if (gc != NULL && gc->prpl != NULL)
-		prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(gc->prpl);
+	if (gc == NULL)
+		return TRUE;
+
+	/* Only send keep-alives if we haven't heard from the
+	 * server in a while.
+	 */
+	if ((time(NULL) - gc->last_received) < KEEPALIVE_INTERVAL)
+		return TRUE;
+
+	if (gc->prpl == NULL)
+		return TRUE;
+
+	prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(gc->prpl);
 
 	if (prpl_info && prpl_info->keepalive)
 		prpl_info->keepalive(gc);
@@ -413,6 +424,14 @@
 	return gc->account;
 }
 
+PurplePlugin *
+purple_connection_get_prpl(const PurpleConnection *gc)
+{
+	g_return_val_if_fail(gc != NULL, NULL);
+
+	return gc->prpl;
+}
+
 const char *
 purple_connection_get_password(const PurpleConnection *gc)
 {
--- a/libpurple/connection.h	Sun Jan 27 04:26:38 2008 +0000
+++ b/libpurple/connection.h	Sun Jan 27 10:03:26 2008 +0000
@@ -251,6 +251,8 @@
 	gboolean wants_to_die;
 
 	guint disconnect_timeout;    /**< Timer used for nasty stack tricks  */
+	time_t last_received;        /**< When we last received a packet. Set by the
+					  prpl to avoid sending unneeded keepalives */
 };
 
 #ifdef __cplusplus
@@ -362,7 +364,7 @@
  * @return TRUE if the account is connected, otherwise returns FALSE.
  */
 #define PURPLE_CONNECTION_IS_CONNECTED(gc) \
-	(gc->state == PURPLE_CONNECTED)
+	(purple_connection_get_state(gc) == PURPLE_CONNECTED)
 
 /**
  * Returns the connection's account.
@@ -374,6 +376,16 @@
 PurpleAccount *purple_connection_get_account(const PurpleConnection *gc);
 
 /**
+ * Returns the protocol plugin managing a connection.
+ *
+ * @param gc The connection.
+ *
+ * @return The protocol plugin.
+ * @since 2.4.0
+ */
+PurplePlugin * purple_connection_get_prpl(const PurpleConnection *gc);
+
+/**
  * Returns the connection's password.
  *
  * @param gc The connection.
--- a/libpurple/example/Makefile.am	Sun Jan 27 04:26:38 2008 +0000
+++ b/libpurple/example/Makefile.am	Sun Jan 27 10:03:26 2008 +0000
@@ -17,7 +17,8 @@
 	-DLIBDIR=\"$(libdir)/purple-$(PURPLE_MAJOR_VERSION)/\" \
 	-DLOCALEDIR=\"$(datadir)/locale\" \
 	-DSYSCONFDIR=\"$(sysconfdir)\" \
-	-I$(top_srcdir)/libpurple/ \
+	-I$(top_builddir)/libpurple \
+	-I$(top_srcdir)/libpurple \
 	-I$(top_srcdir) \
 	$(DEBUG_CFLAGS) \
 	$(GLIB_CFLAGS) \
--- a/libpurple/ft.c	Sun Jan 27 04:26:38 2008 +0000
+++ b/libpurple/ft.c	Sun Jan 27 10:03:26 2008 +0000
@@ -669,6 +669,22 @@
 	return xfer->remote_port;
 }
 
+time_t
+purple_xfer_get_start_time(const PurpleXfer *xfer)
+{
+	g_return_val_if_fail(xfer != NULL, 0);
+
+	return xfer->start_time;
+}
+
+time_t
+purple_xfer_get_end_time(const PurpleXfer *xfer)
+{
+	g_return_val_if_fail(xfer != NULL, 0);
+
+	return xfer->end_time;
+}
+
 void
 purple_xfer_set_completed(PurpleXfer *xfer, gboolean completed)
 {
--- a/libpurple/ft.h	Sun Jan 27 04:26:38 2008 +0000
+++ b/libpurple/ft.h	Sun Jan 27 10:03:26 2008 +0000
@@ -358,6 +358,26 @@
 unsigned int purple_xfer_get_remote_port(const PurpleXfer *xfer);
 
 /**
+ * Returns the time the transfer of a file started.
+ *
+ * @param xfer  The file transfer.
+ *
+ * @return The time when the transfer started.
+ * @since 2.4.0
+ */
+time_t purple_xfer_get_start_time(const PurpleXfer *xfer);
+
+/**
+ * Returns the time the transfer of a file ended.
+ *
+ * @param xfer  The file transfer.
+ *
+ * @return The time when the transfer ended.
+ * @since 2.4.0
+ */
+time_t purple_xfer_get_end_time(const PurpleXfer *xfer);
+
+/**
  * Sets the completed state for the file transfer.
  *
  * @param xfer      The file transfer.
--- a/libpurple/protocols/gg/gg.c	Sun Jan 27 04:26:38 2008 +0000
+++ b/libpurple/protocols/gg/gg.c	Sun Jan 27 10:03:26 2008 +0000
@@ -1314,7 +1314,7 @@
 			_("Unable to read socket"));
 		return;
 	}
-
+	gc->last_received = time(NULL);
 	switch (ev->type) {
 		case GG_EVENT_NONE:
 			/* Nothing happened. */
--- a/libpurple/protocols/irc/irc.c	Sun Jan 27 04:26:38 2008 +0000
+++ b/libpurple/protocols/irc/irc.c	Sun Jan 27 10:03:26 2008 +0000
@@ -572,6 +572,7 @@
 {
 	char *cur, *end;
 
+	irc->account->gc->last_received = time(NULL);
 	irc->inbufused += len;
 	irc->inbuf[irc->inbufused] = '\0';
 
--- a/libpurple/protocols/jabber/auth.c	Sun Jan 27 04:26:38 2008 +0000
+++ b/libpurple/protocols/jabber/auth.c	Sun Jan 27 10:03:26 2008 +0000
@@ -344,6 +344,7 @@
 					 * Doing otherwise means that simply compiling with SASL support renders the client unable to connect to servers
 					 * which would connect without issue otherwise. -evands
 					 */
+					js->auth_type = JABBER_AUTH_IQ_AUTH;
 					jabber_auth_start_old(js);
 					return;
 				}
--- a/libpurple/protocols/jabber/jabber.c	Sun Jan 27 04:26:38 2008 +0000
+++ b/libpurple/protocols/jabber/jabber.c	Sun Jan 27 10:03:26 2008 +0000
@@ -432,6 +432,7 @@
 	}
 
 	while((len = purple_ssl_read(gsc, buf, sizeof(buf) - 1)) > 0) {
+		gc->last_received = time(NULL);
 		buf[len] = '\0';
 		purple_debug(PURPLE_DEBUG_INFO, "jabber", "Recv (ssl)(%d): %s\n", len, buf);
 		jabber_parser_process(js, buf, len);
@@ -459,6 +460,7 @@
 		return;
 
 	if((len = read(js->fd, buf, sizeof(buf) - 1)) > 0) {
+		gc->last_received = time(NULL);
 #ifdef HAVE_CYRUS_SASL
 		if (js->sasl_maxbuf>0) {
 			const char *out;
--- a/libpurple/protocols/jabber/si.c	Sun Jan 27 04:26:38 2008 +0000
+++ b/libpurple/protocols/jabber/si.c	Sun Jan 27 10:03:26 2008 +0000
@@ -1089,7 +1089,7 @@
 		} else {
 			/* we've got multiple resources, we need to pick one to send to */
 			GList *l;
-			char *msg = g_strdup_printf(_("Please select which resource of %s you would like to send a file to"), xfer->who);
+			char *msg = g_strdup_printf(_("Please select the resource of %s to which you would like to send a file"), xfer->who);
 			PurpleRequestFields *fields = purple_request_fields_new();
 			PurpleRequestField *field = purple_request_field_choice_new("resource", _("Resource"), 0);
 			PurpleRequestFieldGroup *group = purple_request_field_group_new(NULL);
--- a/libpurple/protocols/msn/servconn.c	Sun Jan 27 04:26:38 2008 +0000
+++ b/libpurple/protocols/msn/servconn.c	Sun Jan 27 10:03:26 2008 +0000
@@ -391,6 +391,7 @@
 	session = servconn->session;
 
 	len = read(servconn->fd, buf, sizeof(buf) - 1);
+	servconn->session->account->gc->last_received = time(NULL);
 
 	if (len <= 0) {
 		switch (errno) {
--- a/libpurple/protocols/msnp9/servconn.c	Sun Jan 27 04:26:38 2008 +0000
+++ b/libpurple/protocols/msnp9/servconn.c	Sun Jan 27 10:03:26 2008 +0000
@@ -387,6 +387,7 @@
 	session = servconn->session;
 
 	len = read(servconn->fd, buf, sizeof(buf) - 1);
+	servconn->session->account->gc->last_received = time(NULL);
 
 	if (len < 0 && errno == EAGAIN)
 		return;
--- a/libpurple/protocols/myspace/myspace.c	Sun Jan 27 04:26:38 2008 +0000
+++ b/libpurple/protocols/myspace/myspace.c	Sun Jan 27 10:03:26 2008 +0000
@@ -649,7 +649,7 @@
 msim_incoming_bm(MsimSession *session, MsimMessage *msg)
 {
 	guint bm;
-	
+
 	bm = msim_msg_get_integer(msg, "bm");
 
 	msim_incoming_bm_record_cv(session, msg);
@@ -780,7 +780,7 @@
 	} else if (g_str_equal(msg_text, "%stoptyping%")) {
 		serv_got_typing_stopped(session->gc, username);
 		rc = TRUE;
-	} else if (strstr(msg_text, "!!!ZAP_SEND!!!=RTE_BTN_ZAPS_")) {
+	} else if (strstr(msg_text, "!!!ZAP_SEND!!!=RTE_BTN_ZAPS_") == msg_text) {
 		rc = msim_incoming_zap(session, msg);
 	} else if (strstr(msg_text, "!!!GroupCount=")) {
 		/* TODO: support group chats. I think the number in msg_text has
@@ -792,14 +792,14 @@
 		/* TODO: support group chats. This one might mean a user
 		 * went offline or exited the chat. */
 		purple_debug_info("msim", "msim_incoming_action: TODO: implement #4691, group chats: %s\n", msg_text);
-		
+
 		rc = TRUE;
 	} else if (msim_msg_get_integer(msg, "aid") != 0) {
 		purple_debug_info("msim", "TODO: implement #4691, group chat from %d on %d: %s\n",
 				msim_msg_get_integer(msg, "aid"),
 				msim_msg_get_integer(msg, "f"),
 				msg_text);
-		
+
 		rc = TRUE;
 	} else {
 		msim_unrecognized(session, msg, 
@@ -1209,7 +1209,7 @@
 	/* Special elements name beginning with '_', we'll use internally within the
 	 * program (did not come directly from the wire). */
 	msg = msim_msg_append(msg, "_username", MSIM_TYPE_STRING, username); /* This makes 'msg' the owner of 'username' */
-	
+
 	/* TODO: attach more useful information, like ImageURL */
 
 	msim_process(session, msg);
--- a/libpurple/protocols/oscar/flap_connection.c	Sun Jan 27 04:26:38 2008 +0000
+++ b/libpurple/protocols/oscar/flap_connection.c	Sun Jan 27 10:03:26 2008 +0000
@@ -360,7 +360,7 @@
 
 	conn = data;
 	od = conn->od;
-	account = (PURPLE_CONNECTION_IS_VALID(od->gc) ? purple_connection_get_account(od->gc) : NULL);
+	account = purple_connection_get_account(od->gc);
 
 	purple_debug_info("oscar", "Destroying oscar connection of "
 			"type 0x%04hx.  Disconnect reason is %d\n",
@@ -375,8 +375,8 @@
 	 * TODO: If we don't have a SNAC_FAMILY_LOCATE connection then
 	 * we should try to request one instead of disconnecting.
 	 */
-	if (account && !account->disconnecting &&
-		((od->oscar_connections == NULL) || (!flap_connection_getbytype(od, SNAC_FAMILY_LOCATE))))
+	if (!account->disconnecting && ((od->oscar_connections == NULL)
+			|| (!flap_connection_getbytype(od, SNAC_FAMILY_LOCATE))))
 	{
 		/* No more FLAP connections!  Sign off this PurpleConnection! */
 		gchar *tmp;
@@ -817,6 +817,7 @@
 						OSCAR_DISCONNECT_LOST_CONNECTION, g_strerror(errno));
 				break;
 			}
+			conn->od->gc->last_received = time(NULL);
 
 			/* If we don't even have a complete FLAP header then do nothing */
 			conn->header_received += read;
--- a/libpurple/protocols/sametime/sametime.c	Sun Jan 27 04:26:38 2008 +0000
+++ b/libpurple/protocols/sametime/sametime.c	Sun Jan 27 10:03:26 2008 +0000
@@ -1695,7 +1695,9 @@
   int len;
 
   len = read(sock, buf, BUF_LEN);
-  if(len > 0) mwSession_recv(session, buf, len);
+  if(len > 0) {
+    mwSession_recv(session, buf, len);
+  }
 
   return len;
 }
--- a/libpurple/protocols/simple/simple.c	Sun Jan 27 04:26:38 2008 +0000
+++ b/libpurple/protocols/simple/simple.c	Sun Jan 27 10:03:26 2008 +0000
@@ -1675,7 +1675,7 @@
 		if(sip->fd == source) sip->fd = -1;
 		return;
 	}
-
+	gc->last_received = time(NULL);
 	conn->inbufused += len;
 	conn->inbuf[conn->inbufused] = '\0';
 
--- a/libpurple/protocols/yahoo/yahoo.c	Sun Jan 27 04:26:38 2008 +0000
+++ b/libpurple/protocols/yahoo/yahoo.c	Sun Jan 27 10:03:26 2008 +0000
@@ -2506,7 +2506,7 @@
 				_("Server closed the connection."));
 		return;
 	}
-
+	gc->last_received = time(NULL);
 	yd->rxqueue = g_realloc(yd->rxqueue, len + yd->rxlen);
 	memcpy(yd->rxqueue + yd->rxlen, buf, len);
 	yd->rxlen += len;
--- a/libpurple/protocols/yahoo/yahoo_filexfer.c	Sun Jan 27 04:26:38 2008 +0000
+++ b/libpurple/protocols/yahoo/yahoo_filexfer.c	Sun Jan 27 10:03:26 2008 +0000
@@ -922,7 +922,7 @@
 	return xfer;
 }
 
-static gchar* yahoo_xfer_new_xfer_id()
+static gchar* yahoo_xfer_new_xfer_id(void)
 {
 	gchar *ans;
 	int i,j;
@@ -937,7 +937,7 @@
 		else if(j < 52)
 			ans[i] = j - 26 + 'A';
 		else
-			ans[i] = j - 52 + '0';     
+			ans[i] = j - 52 + '0';
 	}
 	return ans;
 }
--- a/libpurple/tests/test_util.c	Sun Jan 27 04:26:38 2008 +0000
+++ b/libpurple/tests/test_util.c	Sun Jan 27 10:03:26 2008 +0000
@@ -71,6 +71,14 @@
 START_TEST(test_util_email_is_valid)
 {
 	fail_unless(purple_email_is_valid("purple-devel@lists.sf.net"));
+	fail_if(purple_email_is_valid("purple-devel@@lists.sf.net"));
+	fail_if(purple_email_is_valid("purple@devel@lists.sf.net"));
+	fail_if(purple_email_is_valid("purple-devel@list..sf.net"));
+	fail_if(purple_email_is_valid("purple-devel"));
+	fail_if(purple_email_is_valid("purple-devel@"));
+	fail_if(purple_email_is_valid("@lists.sf.net"));
+	fail_if(purple_email_is_valid(""));
+	fail_if(purple_email_is_valid("totally bogus"));
 }
 END_TEST
 
--- a/libpurple/util.c	Sun Jan 27 04:26:38 2008 +0000
+++ b/libpurple/util.c	Sun Jan 27 10:03:26 2008 +0000
@@ -1445,7 +1445,6 @@
 				ALLOW_TAG("pre");
 				ALLOW_TAG("q");
 				ALLOW_TAG("span");
-				ALLOW_TAG("strong");
 				ALLOW_TAG("ul");
 
 
@@ -1465,9 +1464,14 @@
 						plain = g_string_append_c(plain, '\n');
 					continue;
 				}
-				if(!g_ascii_strncasecmp(c, "<b>", 3) || !g_ascii_strncasecmp(c, "<bold>", strlen("<bold>"))) {
+				if(!g_ascii_strncasecmp(c, "<b>", 3) || !g_ascii_strncasecmp(c, "<bold>", strlen("<bold>")) || !g_ascii_strncasecmp(c, "<strong>", strlen("<strong>"))) {
 					struct purple_parse_tag *pt = g_new0(struct purple_parse_tag, 1);
-					pt->src_tag = *(c+2) == '>' ? "b" : "bold";
+					if (*(c+2) == '>')
+						pt->src_tag = "b";
+					else if (*(c+2) == 'o')
+						pt->src_tag = "bold";
+					else
+						pt->src_tag = "strong";
 					pt->dest_tag = "span";
 					tags = g_list_prepend(tags, pt);
 					c = strchr(c, '>') + 1;
--- a/pidgin/gtkconv.c	Sun Jan 27 04:26:38 2008 +0000
+++ b/pidgin/gtkconv.c	Sun Jan 27 10:03:26 2008 +0000
@@ -169,6 +169,7 @@
 
 static void pidgin_conv_set_position_size(PidginWindow *win, int x, int y,
 		int width, int height);
+static gboolean pidgin_conv_xy_to_right_infopane(PidginWindow *win, int x, int y);
 
 static GdkColor *get_nick_color(PidginConversation *gtkconv, const char *name) {
 	static GdkColor col;
@@ -6885,6 +6886,18 @@
 		gray_stuff_out(PIDGIN_CONVERSATION(conv));
 }
 
+static gboolean
+pidgin_conv_xy_to_right_infopane(PidginWindow *win, int x, int y)
+{
+	gint pane_x, pane_y, x_rel;
+	PidginConversation *gtkconv;
+
+	gdk_window_get_origin(win->notebook->window, &pane_x, &pane_y);
+	x_rel = x - pane_x;
+	gtkconv = pidgin_conv_window_get_active_gtkconv(win);
+	return (x_rel > gtkconv->infopane->allocation.x + gtkconv->infopane->allocation.width / 2);
+}
+
 int
 pidgin_conv_get_tab_at_xy(PidginWindow *win, int x, int y, gboolean *to_right)
 {
@@ -6920,7 +6933,7 @@
 		tab = gtk_notebook_get_tab_label(GTK_NOTEBOOK(notebook), page);
 
 		/* Make sure the tab is not hidden beyond an arrow */
-		if (!GTK_WIDGET_DRAWABLE(tab))
+		if (!GTK_WIDGET_DRAWABLE(tab) && gtk_notebook_get_show_tabs(notebook))
 			continue;
 
 		if (horiz) {
@@ -8179,7 +8192,6 @@
 		GtkWidget *tab;
 		gint page_num;
 		gboolean horiz_tabs = FALSE;
-		PidginConversation *gtkconv;
 		gboolean to_right = FALSE;
 
 		/* Get the window that the cursor is over. */
@@ -8193,20 +8205,27 @@
 
 		dest_notebook = GTK_NOTEBOOK(dest_win->notebook);
 
-		page_num = pidgin_conv_get_tab_at_xy(dest_win,
-		                                      e->x_root, e->y_root, &to_right);
-		to_right = to_right && (win != dest_win);
+		if (gtk_notebook_get_show_tabs(dest_notebook)) {
+			page_num = pidgin_conv_get_tab_at_xy(dest_win,
+			                                      e->x_root, e->y_root, &to_right);
+			to_right = to_right && (win != dest_win);
+			tab = pidgin_conv_window_get_gtkconv_at_index(dest_win, page_num)->tabby;
+		} else {
+			page_num = 0;
+			to_right = pidgin_conv_xy_to_right_infopane(dest_win, e->x_root, e->y_root);
+			tab = pidgin_conv_window_get_gtkconv_at_index(dest_win, page_num)->infopane;
+		}
 
 		if (gtk_notebook_get_tab_pos(dest_notebook) == GTK_POS_TOP ||
 				gtk_notebook_get_tab_pos(dest_notebook) == GTK_POS_BOTTOM) {
 			horiz_tabs = TRUE;
 		}
 
-		gtkconv = pidgin_conv_window_get_gtkconv_at_index(dest_win, page_num);
-		tab = gtkconv->tabby;
-		if (gtk_notebook_get_show_tabs(dest_notebook) == FALSE) {
-				dnd_hints_show_relative(HINT_ARROW_DOWN, gtkconv->infopane, HINT_POSITION_CENTER, HINT_POSITION_TOP);
-				dnd_hints_show_relative(HINT_ARROW_UP, gtkconv->infopane, HINT_POSITION_CENTER, HINT_POSITION_BOTTOM);
+		if (gtk_notebook_get_show_tabs(dest_notebook) == FALSE && win == dest_win)
+		{
+			/* dragging a tab from a single-tabbed window over its own window */
+			dnd_hints_hide_all();
+			return TRUE;
 		} else if (horiz_tabs) {
 			if (((gpointer)win == (gpointer)dest_win && win->drag_tab < page_num) || to_right) {
 				dnd_hints_show_relative(HINT_ARROW_DOWN, tab, HINT_POSITION_RIGHT, HINT_POSITION_TOP);
@@ -8403,6 +8422,7 @@
 notebook_release_cb(GtkWidget *widget, GdkEventButton *e, PidginWindow *win)
 {
 	PidginWindow *dest_win;
+	GtkNotebook *dest_notebook;
 	PurpleConversation *conv;
 	PidginConversation *gtkconv;
 	gint dest_page_num = 0;
@@ -8478,9 +8498,16 @@
 	                 "conversation-dragging", win, dest_win);
 
 	/* Get the destination page number. */
-	if (!new_window)
-		dest_page_num = pidgin_conv_get_tab_at_xy(dest_win,
-		                                           e->x_root, e->y_root, &to_right);
+	if (!new_window) {
+		dest_notebook = GTK_NOTEBOOK(dest_win->notebook);
+		if (gtk_notebook_get_show_tabs(dest_notebook)) {
+			dest_page_num = pidgin_conv_get_tab_at_xy(dest_win,
+			                                           e->x_root, e->y_root, &to_right);
+		} else {
+			dest_page_num = 0;
+			to_right = pidgin_conv_xy_to_right_infopane(dest_win, e->x_root, e->y_root);
+		}
+	}
 
 	gtkconv = pidgin_conv_window_get_gtkconv_at_index(win, win->drag_tab);
 
--- a/pidgin/gtkimhtml.c	Sun Jan 27 04:26:38 2008 +0000
+++ b/pidgin/gtkimhtml.c	Sun Jan 27 10:03:26 2008 +0000
@@ -1455,6 +1455,9 @@
 	gtk_text_buffer_create_tag(imhtml->text_buffer, "SUP", "rise", 5000, NULL);
 	gtk_text_buffer_create_tag(imhtml->text_buffer, "PRE", "family", "Monospace", NULL);
 	gtk_text_buffer_create_tag(imhtml->text_buffer, "search", "background", "#22ff00", "weight", "bold", NULL);
+#if GTK_CHECK_VERSION(2,10,10)
+	gtk_text_buffer_create_tag(imhtml->text_buffer, "comment", "invisible", FALSE, NULL);
+#endif
 
 	/* When hovering over a link, we show the hand cursor--elsewhere we show the plain ol' pointer cursor */
 	imhtml->hand_cursor = gdk_cursor_new (GDK_HAND2);
@@ -2981,10 +2984,15 @@
 
 					gtk_text_buffer_insert(imhtml->text_buffer, iter, ws, wpos);
 
+#if GTK_CHECK_VERSION(2,10,10)
+					wpos = g_snprintf (ws, len, "%s", tag);
+					gtk_text_buffer_insert_with_tags_by_name(imhtml->text_buffer, iter, ws, wpos, "comment", NULL);
+#else
 					if (imhtml->show_comments && !(options & GTK_IMHTML_NO_COMMENTS)) {
 						wpos = g_snprintf (ws, len, "%s", tag);
 						gtk_text_buffer_insert(imhtml->text_buffer, iter, ws, wpos);
 					}
+#endif
 					ws[0] = '\0'; wpos = 0;
 
 					/* NEW_BIT (NEW_COMMENT_BIT); */
@@ -3130,6 +3138,12 @@
 void       gtk_imhtml_show_comments    (GtkIMHtml        *imhtml,
 					gboolean          show)
 {
+#if GTK_CHECK_VERSION(2,10,10)
+	GtkTextTag *tag;
+	tag = gtk_text_tag_table_lookup(gtk_text_buffer_get_tag_table(imhtml->text_buffer), "comment");
+	if (tag)
+		g_object_set(G_OBJECT(tag), "invisible", !show, NULL);
+#endif
 	imhtml->show_comments = show;
 }
 
--- a/pidgin/gtknotify.c	Sun Jan 27 04:26:38 2008 +0000
+++ b/pidgin/gtknotify.c	Sun Jan 27 10:03:26 2008 +0000
@@ -1058,7 +1058,12 @@
 	/* if they are running gnome, use the gnome web browser */
 	if (purple_running_gnome() == TRUE)
 	{
-		command = g_strdup_printf("gnome-open %s", escaped);
+		char *tmp = g_find_program_in_path("xdg-open");
+		if (tmp == NULL)
+			command = g_strdup_printf("gnome-open %s", escaped);
+		else
+			command = g_strdup_printf("xdg-open %s", escaped);
+		g_free(tmp);
 	}
 	else if (purple_running_osx() == TRUE)
 	{
@@ -1074,6 +1079,10 @@
 		else
 			command = g_strdup_printf("%s %s", web_browser, escaped);
 	}
+	else if (!strcmp(web_browser, "xdg-open"))
+	{
+		command = g_strdup_printf("xdg-open %s", escaped);
+	}
 	else if (!strcmp(web_browser, "gnome-open"))
 	{
 		command = g_strdup_printf("gnome-open %s", escaped);
--- a/pidgin/gtkprefs.c	Sun Jan 27 04:26:38 2008 +0000
+++ b/pidgin/gtkprefs.c	Sun Jan 27 10:03:26 2008 +0000
@@ -1369,6 +1369,7 @@
 		{N_("Netscape"), "netscape"},
 		{N_("Mozilla"), "mozilla"},
 		{N_("Konqueror"), "kfmclient"},
+		{N_("Desktop Default"), "xdg-open"},
 		{N_("GNOME Default"), "gnome-open"},
 		{N_("Galeon"), "galeon"},
 		{N_("Firefox"), "firefox"},
@@ -1391,6 +1392,14 @@
 			browsers = g_list_prepend(browsers, (gpointer)_(possible_browsers[i].name));
 			if(browser_setting && !strcmp(possible_browsers[i].command, browser_setting))
 				browser_setting = NULL;
+			/* If xdg-open is valid, prefer it over gnome-open and skip forward */
+			if(!strcmp(possible_browsers[i].command, "xdg-open")) {
+				if (browser_setting && !strcmp("gnome-open", browser_setting)) {
+					purple_prefs_set_string(PIDGIN_PREFS_ROOT "/browsers/browser", possible_browsers[i].command);
+					browser_setting = NULL;
+				}
+				i++;
+			}
 		}
 	}
 
--- a/pidgin/gtkstatusbox.c	Sun Jan 27 04:26:38 2008 +0000
+++ b/pidgin/gtkstatusbox.c	Sun Jan 27 10:03:26 2008 +0000
@@ -511,12 +511,26 @@
 pidgin_status_box_finalize(GObject *obj)
 {
 	PidginStatusBox *statusbox = PIDGIN_STATUS_BOX(obj);
+	int i;
 
 	purple_signals_disconnect_by_handle(statusbox);
 	purple_prefs_disconnect_by_handle(statusbox);
 
 	destroy_icon_box(statusbox);
 
+	if (statusbox->active_row)
+		gtk_tree_row_reference_free(statusbox->active_row);
+
+	for (i = 0; i < G_N_ELEMENTS(statusbox->connecting_pixbufs); i++) {
+		if (statusbox->connecting_pixbufs[i] != NULL)
+			gdk_pixbuf_unref(statusbox->connecting_pixbufs[i]);
+	}
+
+	for (i = 0; i < G_N_ELEMENTS(statusbox->typing_pixbufs); i++) {
+		if (statusbox->typing_pixbufs[i] != NULL)
+			gdk_pixbuf_unref(statusbox->typing_pixbufs[i]);
+	}
+
 	g_object_unref(G_OBJECT(statusbox->store));
 	g_object_unref(G_OBJECT(statusbox->dropdown_store));
 	G_OBJECT_CLASS(parent_class)->finalize(obj);
@@ -1166,18 +1180,15 @@
 cache_pixbufs(PidginStatusBox *status_box)
 {
 	GtkIconSize icon_size;
+	int i;
 
 	g_object_set(G_OBJECT(status_box->icon_rend), "xpad", 3, NULL);
 	icon_size = gtk_icon_size_from_name(PIDGIN_ICON_SIZE_TANGO_EXTRA_SMALL);
 
-	if (status_box->connecting_pixbufs[0] != NULL)
-		gdk_pixbuf_unref(status_box->connecting_pixbufs[0]);
-	if (status_box->connecting_pixbufs[1] != NULL)
-		gdk_pixbuf_unref(status_box->connecting_pixbufs[1]);
-	if (status_box->connecting_pixbufs[2] != NULL)
-		gdk_pixbuf_unref(status_box->connecting_pixbufs[2]);
-	if (status_box->connecting_pixbufs[3] != NULL)
-		gdk_pixbuf_unref(status_box->connecting_pixbufs[3]);
+	for (i = 0; i < G_N_ELEMENTS(status_box->connecting_pixbufs); i++) {
+		if (status_box->connecting_pixbufs[i] != NULL)
+			gdk_pixbuf_unref(status_box->connecting_pixbufs[i]);
+	}
 
 	status_box->connecting_index = 0;
 	status_box->connecting_pixbufs[0] = gtk_widget_render_icon (GTK_WIDGET(status_box->vbox), PIDGIN_STOCK_ANIMATION_CONNECT0,
@@ -1199,14 +1210,10 @@
 	status_box->connecting_pixbufs[8] = gtk_widget_render_icon (GTK_WIDGET(status_box->vbox), PIDGIN_STOCK_ANIMATION_CONNECT8,
 								     icon_size, "PidginStatusBox");
 
-	if (status_box->typing_pixbufs[0] != NULL)
-		gdk_pixbuf_unref(status_box->typing_pixbufs[0]);
-	if (status_box->typing_pixbufs[1] != NULL)
-		gdk_pixbuf_unref(status_box->typing_pixbufs[1]);
-	if (status_box->typing_pixbufs[2] != NULL)
-		gdk_pixbuf_unref(status_box->typing_pixbufs[2]);
-	if (status_box->typing_pixbufs[3] != NULL)
-		gdk_pixbuf_unref(status_box->typing_pixbufs[3]);
+	for (i = 0; i < G_N_ELEMENTS(status_box->typing_pixbufs); i++) {
+		if (status_box->typing_pixbufs[i] != NULL)
+			gdk_pixbuf_unref(status_box->typing_pixbufs[i]);
+	}
 
 	status_box->typing_index = 0;
 	status_box->typing_pixbufs[0] =  gtk_widget_render_icon (GTK_WIDGET(status_box->vbox), PIDGIN_STOCK_ANIMATION_TYPING0,
--- a/pidgin/gtkutils.h	Sun Jan 27 04:26:38 2008 +0000
+++ b/pidgin/gtkutils.h	Sun Jan 27 10:03:26 2008 +0000
@@ -798,11 +798,11 @@
  * Add a labelled widget to a GtkVBox
  *
  * @param vbox         The GtkVBox to add the widget to.
- * @param widget_label The label to give the widget.
- * @param sg           The GtkSizeGroup to add the label to.
- * @param widget       The GtkWidget to add
+ * @param widget_label The label to give the widget, can be @c NULL.
+ * @param sg           The GtkSizeGroup to add the label to, can be @c NULL.
+ * @param widget       The GtkWidget to add.
  * @param expand       Whether to expand the widget horizontally.
- * @param p_label      Place to store a pointer to the GtkLabel, or NULL if you don't care.
+ * @param p_label      Place to store a pointer to the GtkLabel, or @c NULL if you don't care.
  *
  * @return  A GtkHBox already added to the GtkVBox containing the GtkLabel and the GtkWidget.
  * @since 2.4.0
--- a/po/POTFILES.in	Sun Jan 27 04:26:38 2008 +0000
+++ b/po/POTFILES.in	Sun Jan 27 10:03:26 2008 +0000
@@ -33,6 +33,7 @@
 finch/plugins/gntclipboard.c
 finch/plugins/gntgf.c
 finch/plugins/gnthistory.c
+finch/plugins/grouping.c
 finch/plugins/lastlog.c
 libpurple/account.c
 libpurple/blist.c
--- a/po/de.po	Sun Jan 27 04:26:38 2008 +0000
+++ b/po/de.po	Sun Jan 27 10:03:26 2008 +0000
@@ -11,8 +11,8 @@
 msgstr ""
 "Project-Id-Version: de\n"
 "Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2008-01-17 22:50+0100\n"
-"PO-Revision-Date: 2008-01-17 22:50+0100\n"
+"POT-Creation-Date: 2008-01-23 10:20+0100\n"
+"PO-Revision-Date: 2008-01-23 10:19+0100\n"
 "Last-Translator: Jochen Kemnade <jochenkemnade@web.de>\n"
 "Language-Team: Deutsch <de@li.org>\n"
 "MIME-Version: 1.0\n"
@@ -155,6 +155,29 @@
 msgid "Deny"
 msgstr "Ablehnen"
 
+#, c-format
+msgid ""
+"Online: %d\n"
+"Total: %d"
+msgstr ""
+"Online: %d\n"
+"Gesamt: %d"
+
+#, c-format
+msgid "Account: %s (%s)"
+msgstr "Konto: %s (%s)"
+
+#, c-format
+msgid ""
+"\n"
+"Last Seen: %s ago"
+msgstr ""
+"\n"
+"Zuletzt gesehen: vor %s"
+
+msgid "Default"
+msgstr "Standard"
+
 msgid "You must provide a screename for the buddy."
 msgstr "Sie müssen einen Benutzernamen für den Buddy angeben."
 
@@ -303,26 +326,6 @@
 msgid "On Mobile"
 msgstr "Am Handy"
 
-#, c-format
-msgid ""
-"Online: %d\n"
-"Total: %d"
-msgstr ""
-"Online: %d\n"
-"Gesamt: %d"
-
-#, c-format
-msgid "Account: %s (%s)"
-msgstr "Konto: %s (%s)"
-
-#, c-format
-msgid ""
-"\n"
-"Last Seen: %s ago"
-msgstr ""
-"\n"
-"Zuletzt gesehen: vor %s"
-
 msgid "New..."
 msgstr "Neu..."
 
@@ -389,6 +392,15 @@
 msgid "By Log Size"
 msgstr "Nach Größe der Logs"
 
+msgid "Buddy"
+msgstr "Buddy"
+
+msgid "Chat"
+msgstr "Chat"
+
+msgid "Grouping"
+msgstr "Gruppierung"
+
 msgid "Certificate Import"
 msgstr "Zertifikat-Import"
 
@@ -1321,6 +1333,27 @@
 "Wenn eine neue Unterhaltung eröffnet wird, fügt dieses Plugin die letzte "
 "Unterhaltung in die aktuelle Unterhaltung ein."
 
+msgid "Online"
+msgstr "Online"
+
+msgid "Offline"
+msgstr "Offline"
+
+msgid "Online Buddies"
+msgstr "Online-Buddys"
+
+msgid "Offline Buddies"
+msgstr "Offline-Buddys"
+
+msgid "Online/Offline"
+msgstr "Online/Offline"
+
+msgid "No Grouping"
+msgstr "Keine Gruppierung"
+
+msgid "Provides alternate buddylist grouping options."
+msgstr "Bietet alternative Einstellungen für die Kontaktlisten-Gruppierung."
+
 msgid "Lastlog"
 msgstr "Verlauf"
 
@@ -2822,9 +2855,6 @@
 msgid "Add to chat..."
 msgstr "Zum Chat hinzufügen..."
 
-msgid "Offline"
-msgstr "Offline"
-
 msgid "Available"
 msgstr "Verfügbar"
 
@@ -5976,9 +6006,6 @@
 msgid "AIM Direct IM"
 msgstr "AIM direkte Nachricht"
 
-msgid "Chat"
-msgstr "Chat"
-
 msgid "Get File"
 msgstr "Datei abrufen"
 
@@ -6048,9 +6075,6 @@
 msgid "Invisible"
 msgstr "Unsichtbar"
 
-msgid "Online"
-msgstr "Online"
-
 msgid "IP Address"
 msgstr "IP-Adresse"
 
@@ -10081,9 +10105,6 @@
 msgid "Total Buddies"
 msgstr "Buddy-Anzahl"
 
-msgid "Online Buddies"
-msgstr "Online-Buddys"
-
 #, c-format
 msgid "Idle %dd %dh %02dm"
 msgstr "Untätig %dd %dh %02dm"
@@ -11691,9 +11712,6 @@
 msgid "Pounce Target"
 msgstr "Alarm-Ziel"
 
-msgid "Default"
-msgstr "Standard"
-
 msgid "Smiley theme failed to unpack."
 msgstr "Smiley-Thema konnte nicht entpackt werden."