changeset 22195:6419554aadd9

merge of '7a2d6f31a70791d5e6e8af6fae041e30d5a1c48f' and 'a0479b7ebd8713b6442fb2ecb8238b7e71be61b5'
author Sean Egan <seanegan@gmail.com>
date Fri, 25 Jan 2008 00:51:06 +0000
parents 17e21fa1db57 (diff) 252b96b6a32c (current diff)
children 6c7cf4654d10
files ChangeLog.API libpurple/protocols/myspace/myspace.c libpurple/protocols/oscar/flap_connection.c
diffstat 24 files changed, 1090 insertions(+), 322 deletions(-) [+]
line wrap: on
line diff
--- a/ChangeLog	Wed Jan 23 23:28:38 2008 +0000
+++ b/ChangeLog	Fri Jan 25 00:51:06 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	Wed Jan 23 23:28:38 2008 +0000
+++ b/ChangeLog.API	Fri Jan 25 00:51:06 2008 +0000
@@ -30,8 +30,11 @@
 			* purple_attention_type_get_outgoing_desc
 			* purple_attention_type_get_icon_name
 			* purple_attention_type_get_unlocalized_name
-		* last_received to PurpleAccount, the time_t of the last
-		  received packet
+		* Add some PurpleBuddyListNode accessor functions:
+			* purple_blist_node_get_parent
+			* purple_blist_node_get_first_child
+			* purple_blist_node_get_sibling_next
+			* purple_chat_get_account
 
 	Pidgin:
 		Added:
@@ -67,6 +70,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/finch/gntblist.c	Wed Jan 23 23:28:38 2008 +0000
+++ b/finch/gntblist.c	Fri Jan 25 00:51:06 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,6 +146,168 @@
 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 = node->ui_data;
+		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 */
+	} 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(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);
+		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)
 {
@@ -216,12 +387,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 = node->ui_data;
+		if (PURPLE_BUDDY_IS_ONLINE((PurpleBuddy*)node) ||
+				(fnode && fnode->signed_timer))
 			return TRUE;
 	}
 	return FALSE;
@@ -231,7 +406,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 +416,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 (node->ui_data)
+		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 +440,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)
 {
@@ -271,6 +461,7 @@
 node_remove(PurpleBuddyList *list, PurpleBlistNode *node)
 {
 	FinchBlist *ggblist = list->ui_data;
+	PurpleBlistNode *parent;
 
 	if (ggblist == NULL || node->ui_data == NULL)
 		return;
@@ -280,23 +471,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);
@@ -321,34 +505,25 @@
 				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, list->ui_data);
+		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, list->ui_data);
 	} 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 (node->ui_data == 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, list->ui_data);
 		}
 	} 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);
@@ -363,6 +538,9 @@
 
 	ggblist = g_new0(FinchBlist, 1);
 	list->ui_data = 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 +577,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);
@@ -578,11 +758,14 @@
 static void
 add_group(PurpleGroup *group, FinchBlist *ggblist)
 {
+	gpointer parent;
 	PurpleBlistNode *node = (PurpleBlistNode *)group;
 	if (node->ui_data)
 		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"));
 }
@@ -648,25 +831,24 @@
 static void
 add_chat(PurpleChat *chat, FinchBlist *ggblist)
 {
-	PurpleGroup *group;
+	gpointer parent;
 	PurpleBlistNode *node = (PurpleBlistNode *)chat;
 	if (node->ui_data)
 		return;
 	if (!purple_account_is_connected(chat->account))
 		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;
 
@@ -677,12 +859,11 @@
 	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 +871,19 @@
 static void
 add_buddy(PurpleBuddy *buddy, FinchBlist *ggblist)
 {
+	gpointer parent;
+	PurpleBlistNode *node = (PurpleBlistNode *)buddy;
 	PurpleContact *contact;
-	PurpleBlistNode *node = (PurpleBlistNode *)buddy;
 
 	if (node->ui_data)
 		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))
@@ -1035,8 +1212,8 @@
 	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);
@@ -1104,32 +1281,32 @@
 {
 	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)) {
 					buddy = (PurpleBuddy*)bnode;
-					bnode = bnode->next;
+					bnode = purple_blist_node_get_sibling_next(bnode);
 					if (purple_account_is_connected(buddy->account)) {
 						purple_account_remove_buddy(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;
+			cnode = purple_blist_node_get_sibling_next(cnode);
 			if (purple_account_is_connected(chat->account))
 				purple_blist_remove_chat(chat);
 		} else {
-			cnode = cnode->next;
+			cnode = purple_blist_node_get_sibling_next(cnode);
 		}
 	}
 
@@ -1216,18 +1393,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 +1478,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);
@@ -1393,7 +1573,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 +1622,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 +1645,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);
@@ -1707,6 +1829,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 +1919,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 +1980,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 +1988,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;
 }
 
@@ -2082,7 +2235,7 @@
 	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);
 	}
@@ -2166,18 +2319,16 @@
 {
 	PurpleBlistNode *node = data;
 	FinchBlistNode *fnode = node->ui_data;
-	PurpleBuddy *buddy = (PurpleBuddy*)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;
@@ -2195,8 +2346,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,7 +2411,7 @@
 		PurpleAccount *account = iter->data;
 		PurpleConnection *gc = purple_account_get_connection(account);
 		PurplePlugin *prpl;
-		
+
 		if (!gc || !PURPLE_CONNECTION_IS_CONNECTED(gc))
 			continue;
 		prpl = gc->prpl;
@@ -2273,6 +2424,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)
 {
@@ -2439,6 +2614,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 +2661,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 +2695,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 +2865,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	Wed Jan 23 23:28:38 2008 +0000
+++ b/finch/gntblist.h	Fri Jan 25 00:51:06 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/libgnt/gnttree.c	Wed Jan 23 23:28:38 2008 +0000
+++ b/finch/libgnt/gnttree.c	Fri Jan 25 00:51:06 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	Wed Jan 23 23:28:38 2008 +0000
+++ b/finch/libgnt/gnttree.h	Fri Jan 25 00:51:06 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	Wed Jan 23 23:28:38 2008 +0000
+++ b/finch/plugins/Makefile.am	Fri Jan 25 00:51:06 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
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/finch/plugins/grouping.c	Fri Jan 25 00:51:06 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	Wed Jan 23 23:28:38 2008 +0000
+++ b/libpurple/accountopt.h	Fri Jan 25 00:51:06 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	Wed Jan 23 23:28:38 2008 +0000
+++ b/libpurple/blist.c	Fri Jan 25 00:51:06 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,21 @@
 	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;
+}
+
 void
 purple_blist_update_buddy_status(PurpleBuddy *buddy, PurpleStatus *old_status)
 {
@@ -2232,6 +2247,14 @@
 	return (PurpleGroup *)(((PurpleBlistNode *)chat)->parent);
 }
 
+PurpleAccount *
+purple_chat_get_account(PurpleChat *chat)
+{
+	g_return_val_if_fail(chat != NULL, NULL);
+
+	return chat->account;
+}
+
 PurpleContact *purple_buddy_get_contact(PurpleBuddy *buddy)
 {
 	g_return_val_if_fail(buddy != NULL, NULL);
--- a/libpurple/blist.h	Wed Jan 23 23:28:38 2008 +0000
+++ b/libpurple/blist.h	Fri Jan 25 00:51:06 2008 +0000
@@ -231,10 +231,49 @@
  * @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
  */
 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_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_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_next
+ */
+PurpleBlistNode *purple_blist_node_get_sibling_next(PurpleBlistNode *node);
+
+/**
  * Shows the buddy list, creating a new one if necessary.
  */
 void purple_blist_show(void);
@@ -667,6 +706,16 @@
 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);
+
+/**
  * Returns the group of which the buddy is a member.
  *
  * @param buddy   The buddy
--- a/libpurple/buddyicon.c	Wed Jan 23 23:28:38 2008 +0000
+++ b/libpurple/buddyicon.c	Fri Jan 25 00:51:06 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/example/Makefile.am	Wed Jan 23 23:28:38 2008 +0000
+++ b/libpurple/example/Makefile.am	Fri Jan 25 00:51:06 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/example/nullclient.c	Wed Jan 23 23:28:38 2008 +0000
+++ b/libpurple/example/nullclient.c	Fri Jan 25 00:51:06 2008 +0000
@@ -21,28 +21,11 @@
  *
  */
 
-/* XXX: we probably shouldn't include internal.h in examples */
-#include "internal.h"
-
-#include "account.h"
-#include "conversation.h"
-#include "core.h"
-#include "debug.h"
-#include "eventloop.h"
-#include "ft.h"
-#include "log.h"
-#include "notify.h"
-#include "prefs.h"
-#include "prpl.h"
-#include "pounce.h"
-#include "savedstatuses.h"
-#include "sound.h"
-#include "status.h"
-#include "util.h"
-#include "whiteboard.h"
+#include "purple.h"
 
 #include <glib.h>
 
+#include <signal.h>
 #include <string.h>
 #include <unistd.h>
 
--- a/libpurple/protocols/myspace/myspace.c	Wed Jan 23 23:28:38 2008 +0000
+++ b/libpurple/protocols/myspace/myspace.c	Fri Jan 25 00:51:06 2008 +0000
@@ -117,7 +117,7 @@
 	}
 	return TRUE;
 }
-
+    
 /**
  * Get possible user status types. Based on mockprpl.
  *
@@ -552,10 +552,8 @@
 		 * return 1 even if the message could not be sent, since I don't know if
 		 * it has failed yet--because the IM is only sent after the userid is
 		 * retrieved from the server (which happens after this function returns).
+                 * If an error does occur, it should be logged to the IM window.
 		 */
-		/* TODO: maybe if message is delayed, don't echo to conv window,
-		 * but do echo it to conv window manually once it is actually
-		 * sent? Would be complicated. */
 		rc = 1;
 	} else {
 		rc = -1;
@@ -563,19 +561,6 @@
 
 	g_free(message_msim);
 
-	/*
-	 * In MySpace, you login with your email address, but don't talk to other
-	 * users using their email address. So there is currently an asymmetry in the 
-	 * IM windows when using this plugin:
-	 *
-	 * you@example.com: hello
-	 * some_other_user: what's going on?
-	 * you@example.com: just coding a prpl
-	 *
-	 * TODO: Make the sent IM's appear as from the user's username, instead of
-	 * their email address. Purple uses the login (in MSIM, the email)--change this.
-	 */
-
 	return rc;
 }
 
@@ -745,7 +730,10 @@
 
 	/* TODO: dump unknown msgs to file, so user can send them to me
 	 * if they wish, to help add support for new messages (inspired
-	 * by Alexandr Shutko, who maintains OSCAR protocol documentation). */
+	 * by Alexandr Shutko, who maintains OSCAR protocol documentation). 
+	 *
+	 * Filed enhancement ticket for libpurple as #4688.
+	 */
 
 	purple_debug_info("msim", "Unrecognized data on account for %s\n", 
 			(session && session->account && session->account->username) ? 
@@ -787,9 +775,6 @@
 			msg_text, username);
 
 	if (g_str_equal(msg_text, "%typing%")) {
-		/* TODO: find out if msim repeatedly sends typing messages, so we can 
-		 * give it a timeout. Right now, there does seem to be an inordinately 
-		 * amount of time between typing stopped-typing notifications. */
 		serv_got_typing(session->gc, username, 0, PURPLE_TYPING);
 		rc = TRUE;
 	} else if (g_str_equal(msg_text, "%stoptyping%")) {
@@ -959,6 +944,7 @@
 
 	if (!user) {
 		/* User isn't on blist, create a temporary user to store info. */
+		/* TODO: is this legit, or is it somehow responsible for #3444? */
 		PurpleBuddy *buddy;
 
 		user = g_new0(MsimUser, 1);
@@ -1052,6 +1038,7 @@
 	guint status_code;
 	const gchar *message;
 	gchar *stripped;
+	gchar *unrecognized_msg;
 
 	session = (MsimSession *)account->gc->proto_data;
 
@@ -1083,6 +1070,12 @@
 			purple_debug_info("msim", "msim_set_status: unknown "
 					"status interpreting as online");
 			status_code = MSIM_STATUS_CODE_ONLINE;
+
+			unrecognized_msg = g_strdup_printf("msim_set_status, unrecognized status type: %d\n", 
+					purple_status_type_get_primitive(type));
+			msim_unrecognized(session, NULL, unrecognized_msg);
+			g_free(unrecognized_msg);
+
 			break;
 	}
 
@@ -1203,7 +1196,7 @@
 	msim_process(session, msg);
 
 	/* TODO: Free copy cloned from  msim_preprocess_incoming(). */
-	//XXX msim_msg_free(msg);
+	/* msim_msg_free(msg); */
 	msim_msg_free(body);
 }
 
@@ -1211,7 +1204,9 @@
  *
  * @param wanted_uid
  *
- * @return Username of wanted_uid, if on blist, or NULL. Static string. 
+ * @return Username of wanted_uid, if on blist, or NULL. Static string.
+ * 	TODO: The username string here is a new string from g_strdup(), not
+ * 	a static string that doesn't need to be fixed. Probably leaks. TODO: fix.
  *
  */
 static const gchar *
@@ -1313,13 +1308,13 @@
 	g_return_val_if_fail(MSIM_SESSION_VALID(session), FALSE);
 
 	delta = time(NULL) - session->last_comm;
-	//purple_debug_info("msim", "msim_check_alive: delta=%d\n", delta);
+	/* purple_debug_info("msim", "msim_check_alive: delta=%d\n", delta); */
 	if (delta >= MSIM_KEEPALIVE_INTERVAL) {
 		errmsg = g_strdup_printf(_("Connection to server lost (no data received within %d seconds)"), (int)delta);
 
 		purple_debug_info("msim", "msim_check_alive: %s > interval of %d, presumed dead\n",
 				errmsg, MSIM_KEEPALIVE_INTERVAL);
-		purple_connection_error_reason (session->gc,
+		purple_connection_error_reason(session->gc,
 				PURPLE_CONNECTION_ERROR_NETWORK_ERROR, errmsg);
 
 		purple_notify_error(session->gc, NULL, errmsg, NULL);
@@ -1741,7 +1736,7 @@
 static gboolean
 msim_web_challenge(MsimSession *session, MsimMessage *msg)
 {
-	/* TODO: web challenge, store token */
+	/* TODO: web challenge, store token. #2659. */
 	return FALSE;
 }
 
@@ -1832,13 +1827,14 @@
 	if (msim_msg_get(msg, "fatal")) {
 		PurpleConnectionError reason = PURPLE_CONNECTION_ERROR_NETWORK_ERROR;
 		purple_debug_info("msim", "fatal error, closing\n");
+
 		switch (err) {
-			case 260: /* Incorrect password */
+			case MSIM_ERROR_INCORRECT_PASSWORD: /* Incorrect password */
 				reason = PURPLE_CONNECTION_ERROR_AUTHENTICATION_FAILED;
 				if (!purple_account_get_remember_password(session->account))
 					purple_account_set_password(session->account, NULL);
 				break;
-			case 6: /* Logged in elsewhere */
+			case MSIM_ERROR_LOGGED_IN_ELSEWHERE: /* Logged in elsewhere */
 				reason = PURPLE_CONNECTION_ERROR_NAME_IN_USE;
 				if (!purple_account_get_remember_password(session->account))
 					purple_account_set_password(session->account, NULL);
@@ -1871,6 +1867,7 @@
 	gchar *status_headline, *status_headline_escaped;
 	gint status_code, purple_status_code;
 	gchar *username;
+	gchar *unrecognized_msg;
 
 	g_return_val_if_fail(MSIM_SESSION_VALID(session), FALSE);
 	g_return_val_if_fail(msg != NULL, FALSE);
@@ -1903,7 +1900,8 @@
 
 	blist = purple_get_blist();
 
-	/* Add buddy if not found */
+	/* Add buddy if not found.
+	 * TODO: Could this be responsible for #3444? */
 	user = msim_find_user(session, username);
 	if (!user) {
 		PurpleBuddy *buddy;
@@ -1915,7 +1913,7 @@
 
 		user = msim_get_user_from_buddy(buddy);
 
-		/* All buddies on list should have 'uid' integer associated with them. */
+		/* All buddies on list should have a UserID integer associated with them. */
 		purple_blist_node_set_int(&buddy->node, "UserID", msim_msg_get_integer(msg, "f"));
 		
 		msim_store_user_info(session, msg, NULL);
@@ -1959,9 +1957,15 @@
 			break;
 
 		default:
-			purple_debug_info("msim", "msim_status for %s, unknown status code %d, treating as available\n",
+			purple_debug_info("msim", "msim_incoming_status for %s, unknown status code %d, treating as available\n",
 						username, status_code);
 			purple_status_code = PURPLE_STATUS_AVAILABLE;
+
+			unrecognized_msg = g_strdup_printf("msim_incoming_status, unrecognized status code: %d\n", 
+					status_code);
+			msim_unrecognized(session, NULL, unrecognized_msg);
+			g_free(unrecognized_msg);
+
 	}
 
 	purple_prpl_got_user_status(session->account, username, purple_primitive_get_id_from_type(purple_status_code), NULL);
@@ -2156,10 +2160,15 @@
 		gchar *msg;
 
 		msg = g_strdup_printf(_("No such user: %s"), username);
-		purple_notify_error(NULL, NULL, _("User lookup"), msg);
+                if (!purple_conv_present_error(username, session->account, msg)) { 
+                    purple_notify_error(NULL, NULL, _("User lookup"), msg); 
+                }
+
 		g_free(msg);
 		g_free(username);
-		//msim_msg_free(msg);
+		/* TODO: free
+		 * msim_msg_free(msg);
+		 */
 		return;
 	}
 
@@ -2180,7 +2189,9 @@
 	g_free(uid_field_name);
 	g_free(uid_before);
 	g_free(username);
-	//msim_msg_free(msg);
+	/* TODO: free 
+	 * msim_msg_free(msg);
+	 */
 }
 
 /** Postprocess and send a message.
@@ -2222,8 +2233,7 @@
 			uid = 0;
 		}
 
-		if (!buddy || !uid)
-		{
+		if (!buddy || !uid) {
 			/* Don't have uid offhand - need to ask for it, and wait until hear back before sending. */
 			purple_debug_info("msim", ">>> msim_postprocess_outgoing: couldn't find username %s in blist\n",
 					username ? username : "(NULL)");
@@ -2244,7 +2254,9 @@
 	
 	rc = msim_msg_send(session, msg);
 
-	//msim_msg_free(msg);
+	/* TODO: free
+	 * msim_msg_free(msg);
+	 */
 
 	return rc;
 }
@@ -2304,7 +2316,7 @@
 			"blocklist", MSIM_TYPE_BOOLEAN, TRUE,
 			"sesskey", MSIM_TYPE_INTEGER, session->sesskey,
 			/* TODO: MsimMessage lists. Currently <uid> isn't replaced in lists. */
-			//"idlist", MSIM_TYPE_STRING, g_strdup("a-|<uid>|b-|<uid>"),
+			/* "idlist", MSIM_TYPE_STRING, g_strdup("a-|<uid>|b-|<uid>"), */
 			"idlist", MSIM_TYPE_LIST, blocklist_updates,
 			NULL);
 
@@ -2317,9 +2329,12 @@
 }
 
 /**
- * Borrowed this code from oscar_normalize. Added checking for "if userid, get name before normalizing"
+ * Returns a string of a username in canonical form. Basically removes all the
+ * spaces, lowercases the string, and looks up user IDs to usernames.
+ * Normalizing tom, TOM, Tom, and 6221 wil all return 'tom'.
  *
- * Basically... Returns a string that has been formated with all the spaces and caps removed.
+ * Borrowed this code from oscar_normalize. Added checking for 
+ * "if userid, get name before normalizing"
  */
 const char *msim_normalize(const PurpleAccount *account, const char *str) {
 	static char normalized[BUF_LEN];
@@ -2345,7 +2360,7 @@
 		id = atol(str);
 		username = msim_uid2username_from_blist(session, id);
 		if (!username) {
-			/* Not in buddy list... scheisse... TODO: Manual Lookup! */
+			/* Not in buddy list... scheisse... TODO: Manual Lookup! Bug #4631 */
 			/* Note: manual lookup using msim_lookup_user() is a problem inside 
 			 * msim_normalize(), because msim_lookup_user() calls a callback function
 			 * when the user information has been looked up, but msim_normalize() expects
@@ -2376,6 +2391,13 @@
 	g_free(tmp2);
 	g_free(tmp1);
 
+	/* TODO: re-add caps and spacing back to what the user wanted.
+	 * User can format their own names, for example 'msimprpl' is shown
+	 * as 'MsIm PrPl' in the official client.
+	 *
+	 * TODO: file a ticket to add this enhancement.
+	 */
+
 	return normalized;
 }
 
@@ -2427,16 +2449,17 @@
 	g_return_if_fail(cond == PURPLE_INPUT_READ);
 	g_return_if_fail(MSIM_SESSION_VALID(session));
 
-	/* Mark down that we got data, so don't timeout. */
+	/* Mark down that we got data, so we don't timeout. */
 	session->last_comm = time(NULL);
 
 	/* Only can handle so much data at once... 
-	 * If this happens, try recompiling with a higher MSIM_READ_BUF_SIZE.
 	 * Should be large enough to hold the largest protocol message.
 	 */
 	if (session->rxoff >= MSIM_READ_BUF_SIZE) {
 		purple_debug_error("msim", 
-				"msim_input_cb: %d-byte read buffer full! rxoff=%d\n",
+				"msim_input_cb: %d-byte read buffer full! rxoff=%d. "
+				"If this happens, try recompiling with a higher "
+				"MSIM_READ_BUF_SIZE.",
 				MSIM_READ_BUF_SIZE, session->rxoff);
 		purple_connection_error_reason (gc,
 				PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
@@ -2535,8 +2558,9 @@
 		memmove(session->rxbuf, end + strlen(MSIM_FINAL_STRING), 
 				MSIM_READ_BUF_SIZE - (end + strlen(MSIM_FINAL_STRING) - session->rxbuf));
 
-		/* Clear end of buffer */
-		//memset(end, 0, MSIM_READ_BUF_SIZE - (end - session->rxbuf));
+		/* Clear end of buffer 
+		 * memset(end, 0, MSIM_READ_BUF_SIZE - (end - session->rxbuf));
+		 */
 	}
 }
 
--- a/libpurple/protocols/myspace/myspace.h	Wed Jan 23 23:28:38 2008 +0000
+++ b/libpurple/protocols/myspace/myspace.h	Fri Jan 25 00:51:06 2008 +0000
@@ -180,6 +180,10 @@
 #define MSIM_CONTACT_LIST_IMPORT_ALL_FRIENDS	1
 #define MSIM_CONTACT_LIST_IMPORT_TOP_FRIENDS	2
 
+/* Error codes */
+#define MSIM_ERROR_INCORRECT_PASSWORD           260
+#define MSIM_ERROR_LOGGED_IN_ELSEWHERE          6
+
 /* Functions */
 gboolean msim_load(PurplePlugin *plugin);
 GList *msim_status_types(PurpleAccount *acct);
--- a/libpurple/protocols/oscar/flap_connection.c	Wed Jan 23 23:28:38 2008 +0000
+++ b/libpurple/protocols/oscar/flap_connection.c	Fri Jan 25 00:51:06 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;
--- a/libpurple/protocols/yahoo/util.c	Wed Jan 23 23:28:38 2008 +0000
+++ b/libpurple/protocols/yahoo/util.c	Fri Jan 25 00:51:06 2008 +0000
@@ -168,11 +168,11 @@
 {
 	GString *gstr = NULL;
 	char *retstr;
-	const char *p;
+	const unsigned char *p;
 
 	gstr = g_string_sized_new(strlen(str) * 6 + 1);
 
-	for (p = str; *p; p++) {
+	for (p = (unsigned char *)str; *p; p++) {
 		g_string_append_printf(gstr, "&#%u;", *p);
 	}
 
--- a/libpurple/util.c	Wed Jan 23 23:28:38 2008 +0000
+++ b/libpurple/util.c	Fri Jan 25 00:51:06 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	Wed Jan 23 23:28:38 2008 +0000
+++ b/pidgin/gtkconv.c	Fri Jan 25 00:51:06 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;
@@ -3428,9 +3429,14 @@
 		gtk_text_buffer_delete_mark(buffer, stmark);
 		gtk_text_buffer_delete_mark(buffer, enmark);
 		gtk_text_buffer_delete(buffer, &start, &end);
-	} else if (message && *message == '\n' && !*(message + 1))
+	} else if (message && *message == '\n' && message[1] == ' ' && message[2] == '\0')
 		message = NULL;
 
+#ifdef RESERVE_LINE
+	if (!message)
+		message = "\n ";   /* The blank space is required to avoid a GTK+/Pango bug */
+#endif
+
 	if (message) {
 		GtkTextIter iter;
 		gtk_text_buffer_get_end_iter(buffer, &iter);
@@ -3458,7 +3464,11 @@
 		return;
 
 	if (purple_conv_im_get_typing_state(im) == PURPLE_NOT_TYPING) {
-		update_typing_message(gtkconv, "\n");
+#ifdef RESERVE_LINE
+		update_typing_message(gtkconv, NULL);
+#else
+		update_typing_message(gtkconv, "\n ");
+#endif
 		return;
 	}
 
@@ -4980,7 +4990,7 @@
 	gtk_text_buffer_create_tag(GTK_IMHTML(gtkconv->imhtml)->text_buffer, "TYPING-NOTIFICATION",
 			"foreground", "#888888",
 			"justification", GTK_JUSTIFY_LEFT,  /* XXX: RTL'ify */
-			"weight", PANGO_WEIGHT_BOLD,
+			"weight", PANGO_WEIGHT_LIGHT,
 			"scale", PANGO_SCALE_SMALL,
 			NULL);
 
@@ -6876,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)
 {
@@ -6911,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) {
@@ -8170,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. */
@@ -8184,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);
@@ -8394,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;
@@ -8469,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/gtkstatusbox.c	Wed Jan 23 23:28:38 2008 +0000
+++ b/pidgin/gtkstatusbox.c	Fri Jan 25 00:51:06 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	Wed Jan 23 23:28:38 2008 +0000
+++ b/pidgin/gtkutils.h	Fri Jan 25 00:51:06 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	Wed Jan 23 23:28:38 2008 +0000
+++ b/po/POTFILES.in	Fri Jan 25 00:51:06 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	Wed Jan 23 23:28:38 2008 +0000
+++ b/po/de.po	Fri Jan 25 00:51:06 2008 +0000
@@ -11,8 +11,8 @@
 msgstr ""
 "Project-Id-Version: de\n"
 "Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2008-01-08 11:16+0100\n"
-"PO-Revision-Date: 2008-01-08 11:15+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"
 
@@ -2688,6 +2721,9 @@
 msgid "Could not listen on socket"
 msgstr "Kann nicht an der Socket hören"
 
+msgid "Error communicating with local mDNSResponder."
+msgstr "Fehler bei der Kommunikation mit lokalem mDNSResponder."
+
 msgid "Invalid proxy settings"
 msgstr "Falsche Proxy-Einstellungen"
 
@@ -2819,9 +2855,6 @@
 msgid "Add to chat..."
 msgstr "Zum Chat hinzufügen..."
 
-msgid "Offline"
-msgstr "Offline"
-
 msgid "Available"
 msgstr "Verfügbar"
 
@@ -4237,6 +4270,8 @@
 msgid "Unable to buzz, because the user %s does not support it."
 msgstr "Kann nicht anklopfen, da der Benutzer %s dies nicht unterstützt."
 
+#. Yahoo only supports one attention command: the 'buzz'.
+#. This is index number YAHOO_BUZZ.
 msgid "Buzz"
 msgstr "Anklopfen"
 
@@ -5971,9 +6006,6 @@
 msgid "AIM Direct IM"
 msgstr "AIM direkte Nachricht"
 
-msgid "Chat"
-msgstr "Chat"
-
 msgid "Get File"
 msgstr "Datei abrufen"
 
@@ -6043,9 +6075,6 @@
 msgid "Invisible"
 msgstr "Unsichtbar"
 
-msgid "Online"
-msgstr "Online"
-
 msgid "IP Address"
 msgstr "IP-Adresse"
 
@@ -9151,6 +9180,10 @@
 msgid "Unable to establish file descriptor."
 msgstr "Konnte Dateibeschreibung nicht erstellen."
 
+#, c-format
+msgid "%s is trying to send you a group of %d files.\n"
+msgstr "%s versucht, Ihnen eine Gruppe von %d Dateien zu senden.\n"
+
 msgid "Write Error"
 msgstr "Schreibfehler"
 
@@ -10072,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"
@@ -11682,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."
 
@@ -12295,6 +12322,9 @@
 msgid "_Open Mail"
 msgstr "Mail ö_ffnen"
 
+msgid "Pidgin Tooltip"
+msgstr "Pidgin-Tooltip"
+
 msgid "Pidgin smileys"
 msgstr "Pidgin-Smileys"