changeset 22175:ca9a660cc388

merge of '4ca258deda6a50b61f8431bb3a742805c180a583' and 'f61e8366b7d42ee3c6df40b1f37dbdb4a9a1b343'
author Sadrul Habib Chowdhury <imadil@gmail.com>
date Tue, 22 Jan 2008 14:14:59 +0000
parents ce5ced43cd93 (current diff) 18ad08694be4 (diff)
children e94336c4de09
files
diffstat 9 files changed, 681 insertions(+), 126 deletions(-) [+]
line wrap: on
line diff
--- a/ChangeLog.API	Mon Jan 21 07:32:49 2008 +0000
+++ b/ChangeLog.API	Tue Jan 22 14:14:59 2008 +0000
@@ -30,6 +30,11 @@
 			* 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_chat_get_account
 
 	Pidgin:
 		Added:
@@ -65,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	Mon Jan 21 07:32:49 2008 +0000
+++ b/finch/gntblist.c	Tue Jan 22 14:14:59 2008 +0000
@@ -81,6 +81,8 @@
 	/* These are the menuitems that get regenerated */
 	GntMenuItem *accounts;
 	GntMenuItem *plugins;
+
+	FinchBlistManager *manager;
 } FinchBlist;
 
 typedef struct
@@ -115,7 +117,10 @@
 static void add_chat(PurpleChat *chat, FinchBlist *ggblist);
 static void add_node(PurpleBlistNode *node, FinchBlist *ggblist);
 static void node_update(PurpleBuddyList *list, PurpleBlistNode *node);
+static gboolean is_contact_online(PurpleContact *contact);
+static gboolean is_group_online(PurpleGroup *group);
 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);
@@ -137,6 +142,165 @@
 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;
+		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 */
+	} else if (PURPLE_BLIST_NODE_IS_CONTACT(node)) {
+		PurpleContact *contact = (PurpleContact*)node;
+		if (contact->currentsize < 1)
+			return FALSE; /* No online accounts in this contact */
+		if (!offline && !is_contact_online(contact))
+			return FALSE; /* Don't want to see offline buddies */
+		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)) {
+		PurpleGroup *group = (PurpleGroup*)node;
+		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 */
+
+		if (group->currentsize < 1)
+			return FALSE; /* No online accounts for this group */
+
+		if (!offline && !is_group_online(group))
+			return FALSE; /* Do not want to see group only with offline buddies */
+
+		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"),
+	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)
 {
@@ -220,7 +384,8 @@
 is_contact_online(PurpleContact *contact)
 {
 	PurpleBlistNode *node;
-	for (node = ((PurpleBlistNode*)contact)->child; node; node = node->next) {
+	for (node = purple_blist_node_get_first_child(((PurpleBlistNode*)contact)); node;
+			node = purple_blist_node_get_sibling_next(node)) {
 		if (PURPLE_BUDDY_IS_ONLINE((PurpleBuddy*)node))
 			return TRUE;
 	}
@@ -231,7 +396,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;
@@ -246,8 +412,12 @@
 {
 }
 
-static void add_node(PurpleBlistNode *node, FinchBlist *ggblist)
+static void
+add_node(PurpleBlistNode *node, FinchBlist *ggblist)
 {
+	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))
@@ -259,6 +429,11 @@
 	draw_tooltip(ggblist);
 }
 
+void finch_blist_manager_add_node(PurpleBlistNode *node)
+{
+	add_node(node, ggblist);
+}
+
 static void
 remove_tooltip(FinchBlist *ggblist)
 {
@@ -281,21 +456,21 @@
 		ggblist->tagged = g_list_remove(ggblist->tagged, node);
 
 	if (PURPLE_BLIST_NODE_IS_BUDDY(node)) {
-		PurpleContact *contact = (PurpleContact*)node->parent;
+		PurpleContact *contact = (PurpleContact*)purple_blist_node_get_parent(node);
 		if ((!purple_prefs_get_bool(PREF_ROOT "/showoffline") && !is_contact_online(contact)) ||
 				contact->currentsize < 1)
 			node_remove(list, (PurpleBlistNode*)contact);
 		else
 			node_update(list, (PurpleBlistNode*)contact);
 	} else if (!PURPLE_BLIST_NODE_IS_GROUP(node)) {
-		PurpleGroup *group = (PurpleGroup*)node->parent;
+		PurpleGroup *group = (PurpleGroup*)purple_blist_node_get_parent(node);
 		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)
+			node_remove(list, purple_blist_node_get_parent(node));
+		for (node = purple_blist_node_get_first_child(node); node; node = purple_blist_node_get_sibling_next(node))
 			reset_blist_node_ui_data(node);
 	} else {
-		for (node = node->child; node; node = node->next)
+		for (node = purple_blist_node_get_first_child(node); node; node = purple_blist_node_get_sibling_next(node))
 			node_remove(list, node);
 	}
 
@@ -321,28 +496,22 @@
 				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;
@@ -363,6 +532,7 @@
 
 	ggblist = g_new0(FinchBlist, 1);
 	list->ui_data = ggblist;
+	ggblist->manager = &default_manager;
 }
 
 static void
@@ -399,6 +569,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 +750,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 +823,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 +851,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 +863,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 +1204,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 +1273,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 +1385,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 +1470,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);
@@ -1442,16 +1614,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 +1637,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);
@@ -1843,6 +1957,9 @@
 
 	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 +2199,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);
 	}
@@ -2176,8 +2293,8 @@
 		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 +2312,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;
 }
 
@@ -2439,11 +2556,33 @@
 }
 
 static void
+menu_group_set_cb(GntMenuItem *item, gpointer null)
+{
+	const char *id = g_object_get_data(G_OBJECT(item), "grouping-id");
+	FinchBlistManager *manager;
+
+	manager = finch_blist_manager_find(id);
+	if (!manager)
+		return;
+
+	ggblist->manager = manager;
+	if (ggblist->manager->can_add_node == NULL)
+		ggblist->manager->can_add_node = default_can_add_node;
+	if (ggblist->manager->find_parent == NULL)
+		ggblist->manager->find_parent = default_find_parent;
+	if (ggblist->manager->create_tooltip == NULL)
+		ggblist->manager->create_tooltip = default_create_tooltip;
+
+	redraw_blist(NULL, 0, NULL, NULL);
+}
+
+static void
 create_menu(void)
 {
 	GntWidget *menu, *sub, *subsub;
 	GntMenuItem *item;
 	GntWindow *window;
+	GList *iter;
 
 	if (!ggblist)
 		return;
@@ -2513,21 +2652,38 @@
 	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);
 
+	item = gnt_menuitem_new(_("Grouping"));
+	gnt_menu_add_item(GNT_MENU(sub), item);
+
+	subsub = gnt_menu_new(GNT_MENU_POPUP);
+	gnt_menuitem_set_submenu(item, GNT_MENU(subsub));
+
+	for (iter = managers; iter; iter = iter->next) {
+		char menuid[128];
+		FinchBlistManager *manager = iter->data;
+		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);
+	}
+
 	reconstruct_accounts_menu();
 	gnt_menu_add_item(GNT_MENU(menu), ggblist->accounts);
 
@@ -2679,3 +2835,34 @@
 {
 	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);
+}
+
+void finch_blist_uninstall_manager(const FinchBlistManager *manager)
+{
+	managers = g_list_remove(managers, manager);
+}
+
+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	Mon Jan 21 07:32:49 2008 +0000
+++ b/finch/gntblist.h	Tue Jan 22 14:14:59 2008 +0000
@@ -27,12 +27,23 @@
 #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 (*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 +114,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	Mon Jan 21 07:32:49 2008 +0000
+++ b/finch/libgnt/gnttree.c	Tue Jan 22 14:14:59 2008 +0000
@@ -1841,3 +1841,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 : NULL;
+}
+
--- a/finch/libgnt/gnttree.h	Mon Jan 21 07:32:49 2008 +0000
+++ b/finch/libgnt/gnttree.h	Tue Jan 22 14:14:59 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	Mon Jan 21 07:32:49 2008 +0000
+++ b/finch/plugins/Makefile.am	Tue Jan 22 14:14:59 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	Tue Jan 22 14:14:59 2008 +0000
@@ -0,0 +1,217 @@
+/**
+ * @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_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;
+	GntTree *tree = finch_blist_get_tree();
+
+	if (!tree)
+		return NULL;
+
+	if (!g_list_find(gnt_tree_get_rows(tree), &online)) {
+		gnt_tree_remove_all(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);
+	}
+
+	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_can_add_node,
+	on_offline_find_parent,
+	on_offline_create_tooltip,
+	{NULL, NULL, NULL, NULL}
+};
+
+/**
+ * No Grouping.
+ */
+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_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(&no_group);
+	return TRUE;
+}
+
+static gboolean
+plugin_unload(PurplePlugin *plugin)
+{
+	finch_blist_uninstall_manager(&on_offline);
+	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/blist.c	Mon Jan 21 07:32:49 2008 +0000
+++ b/libpurple/blist.c	Tue Jan 22 14:14:59 2008 +0000
@@ -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	Mon Jan 21 07:32:49 2008 +0000
+++ b/libpurple/blist.h	Tue Jan 22 14:14:59 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