changeset 23158:da5d1ecc5c20

Add support for setting and removing custom buddy icons to contacts, chats, and groups via their respective right-click menus in the buddy list. Also display the buddy icons for chats and groups in the buddy list.
author Etan Reisner <pidgin@unreliablesource.net>
date Sat, 17 May 2008 19:53:28 +0000
parents bc58e2d9f0f5
children e206ff2e5efa
files ChangeLog pidgin/gtkblist.c
diffstat 2 files changed, 168 insertions(+), 75 deletions(-) [+]
line wrap: on
line diff
--- a/ChangeLog	Fri May 16 03:18:27 2008 +0000
+++ b/ChangeLog	Sat May 17 19:53:28 2008 +0000
@@ -1,5 +1,14 @@
 Pidgin and Finch: The Pimpin' Penguin IM Clients That're Good for the Soul
 
+version 2.5.0:
+	Pidgin:
+	* Custom buddy icons can now be added and removed to buddy list
+	  entries via the buddy list entry right-click menu.
+
+	General:
+	* Group and Chat buddy list entries can now be given custom buddy
+	  icons.
+
 version 2.x.x:
 	libpurple:
 	* In MySpaceIM, messages from spambots are discarded (Justin Williams)
--- a/pidgin/gtkblist.c	Fri May 16 03:18:27 2008 +0000
+++ b/pidgin/gtkblist.c	Sat May 17 19:53:28 2008 +0000
@@ -1529,6 +1529,44 @@
 	return FALSE;
 }
 
+static void
+set_node_custom_icon_cb(const gchar *filename, gpointer data)
+{
+	if (filename) {
+		PurpleBlistNode *node = (PurpleBlistNode*)data;
+
+		purple_buddy_icons_node_set_custom_icon_from_file(node,
+		                                                  filename);
+	}
+}
+
+static void
+set_node_custom_icon(GtkWidget *w, PurpleBlistNode *node)
+{
+	/* This doesn't keep track of the returned dialog (so that successive
+	 * calls could be made to re-display that dialog). Do we want that? */
+	GtkWidget *win = pidgin_buddy_icon_chooser_new(NULL, set_node_custom_icon_cb, node);
+	gtk_widget_show_all(win);
+}
+
+static void
+remove_node_custom_icon(GtkWidget *w, PurpleBlistNode *node)
+{
+	purple_buddy_icons_node_set_custom_icon(node, NULL, 0);
+}
+
+static void
+add_buddy_icon_menu_items(GtkWidget *menu, PurpleBlistNode *node)
+{
+	pidgin_new_item_from_stock(menu, _("Set Custom Icon"), NULL,
+	                           G_CALLBACK(set_node_custom_icon), node, 0,
+	                           0, NULL);
+
+	pidgin_new_item_from_stock(menu, _("Remove Custom Icon"), NULL,
+	                           G_CALLBACK(remove_node_custom_icon), node,
+	                           0, 0, NULL);
+}
+
 static GtkWidget *
 create_group_menu (PurpleBlistNode *node, PurpleGroup *g)
 {
@@ -1552,12 +1590,13 @@
 				NULL, G_CALLBACK(gtk_blist_menu_showoffline_cb), node, 0, 0, NULL);
 	}
 
+	add_buddy_icon_menu_items(menu, node);
+
 	pidgin_append_blist_node_extended_menu(menu, node);
 
 	return menu;
 }
 
-
 static GtkWidget *
 create_chat_menu(PurpleBlistNode *node, PurpleChat *c)
 {
@@ -1590,6 +1629,8 @@
 	pidgin_new_item_from_stock(menu, _("_Remove"), GTK_STOCK_REMOVE,
 				 G_CALLBACK(pidgin_blist_remove_cb), node, 0, 0, NULL);
 
+	add_buddy_icon_menu_items(menu, node);
+
 	return menu;
 }
 
@@ -1611,6 +1652,8 @@
 	pidgin_new_item_from_stock(menu, _("_Remove"), GTK_STOCK_REMOVE,
 				 G_CALLBACK(pidgin_blist_remove_cb), node, 0, 0, NULL);
 
+	add_buddy_icon_menu_items(menu, node);
+
 	pidgin_separator(menu);
 
 	pidgin_new_item_from_stock(menu, _("_Collapse"), GTK_STOCK_ZOOM_OUT,
@@ -2492,51 +2535,71 @@
 
 
 static GdkPixbuf *pidgin_blist_get_buddy_icon(PurpleBlistNode *node,
-		gboolean scaled, gboolean greyed)
-{
-	GdkPixbuf *buf, *ret = NULL;
+                                              gboolean scaled, gboolean greyed)
+{
+	gsize len;
 	GdkPixbufLoader *loader;
-	PurpleBuddyIcon *icon = NULL;
-	const guchar *data = NULL;
-	gsize len;
 	PurpleBuddy *buddy = NULL;
+	PurpleGroup *group = NULL;
+	const guchar *data = NULL;
+	GdkPixbuf *buf, *ret = NULL;
+	PurpleBuddyIcon *icon = NULL;
 	PurpleAccount *account = NULL;
-	PurplePluginProtocolInfo *prpl_info = NULL;
+	PurpleContact *contact = NULL;
 	PurpleStoredImage *custom_img;
-
-	if(PURPLE_BLIST_NODE_IS_CONTACT(node)) {
+	PurplePluginProtocolInfo *prpl_info = NULL;
+	gint orig_width, orig_height, scale_width, scale_height;
+
+	if (PURPLE_BLIST_NODE_IS_CONTACT(node)) {
 		buddy = purple_contact_get_priority_buddy((PurpleContact*)node);
-	} else if(PURPLE_BLIST_NODE_IS_BUDDY(node)) {
+		contact = (PurpleContact*)node;
+	} else if (PURPLE_BLIST_NODE_IS_BUDDY(node)) {
 		buddy = (PurpleBuddy*)node;
+		contact = purple_buddy_get_contact(buddy);
+	} else if (PURPLE_BLIST_NODE_IS_GROUP(node)) {
+		group = (PurpleGroup*)node;
+	} else if (PURPLE_BLIST_NODE_IS_CHAT(node)) {
+		/* We don't need to do anything here. We just need to not fall
+		 * into the else block and return. */
 	} else {
 		return NULL;
 	}
 
-	if(buddy == NULL)
-		return NULL;
-
-	account = purple_buddy_get_account(buddy);
-
-	if(account && account->gc)
+	if (buddy) {
+		account = purple_buddy_get_account(buddy);
+	}
+
+	if(account && account->gc) {
 		prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(account->gc->prpl);
+	}
 
 #if 0
 	if (!purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/blist/show_buddy_icons"))
 		return NULL;
 #endif
 
-	custom_img = purple_buddy_icons_find_custom_icon(purple_buddy_get_contact(buddy));
-	if (custom_img)
-	{
+	/* If we have a contact then this is either a contact or a buddy and
+	 * we want to fetch the custom icon for the contact. If we don't have
+	 * a contact then this is a group or some other type of node and we
+	 * want to use that directly. */
+	if (contact) {
+		custom_img = purple_buddy_icons_node_find_custom_icon((PurpleBlistNode*)contact);
+	} else {
+		custom_img = purple_buddy_icons_node_find_custom_icon(node);
+	}
+
+	if (custom_img) {
 		data = purple_imgstore_get_data(custom_img);
 		len = purple_imgstore_get_size(custom_img);
 	}
 
 	if (data == NULL) {
-		/* Not sure I like this...*/
-		if (!(icon = purple_buddy_icons_find(buddy->account, buddy->name)))
-			return NULL;
-		data = purple_buddy_icon_get_data(icon, &len);
+		if (buddy) {
+			/* Not sure I like this...*/
+			if (!(icon = purple_buddy_icons_find(buddy->account, buddy->name)))
+				return NULL;
+			data = purple_buddy_icon_get_data(icon, &len);
+		}
 
 		if(data == NULL)
 			return NULL;
@@ -2554,56 +2617,68 @@
 		g_object_ref(G_OBJECT(buf));
 	g_object_unref(G_OBJECT(loader));
 
-	if (buf) {
-		int orig_width, orig_height;
-		int scale_width, scale_height;
-
-		if (greyed) {
+	if (!buf) {
+		return NULL;
+	}
+
+	if (greyed) {
+		gboolean offline = FALSE, idle = TRUE;
+
+		if (buddy) {
 			PurplePresence *presence = purple_buddy_get_presence(buddy);
 			if (!PURPLE_BUDDY_IS_ONLINE(buddy))
-				gdk_pixbuf_saturate_and_pixelate(buf, buf, 0.0, FALSE);
+				offline = TRUE;
 			if (purple_presence_is_idle(presence))
-				gdk_pixbuf_saturate_and_pixelate(buf, buf, 0.25, FALSE);
+				idle = TRUE;
+		} else if (group) {
+			if (purple_blist_get_group_online_count(group) == 0)
+				offline = TRUE;
 		}
 
-		/* i'd use the pidgin_buddy_icon_get_scale_size() thing,
-		 * but it won't tell me the original size, which I need for scaling
-		 * purposes */
-		scale_width = orig_width = gdk_pixbuf_get_width(buf);
-		scale_height = orig_height = gdk_pixbuf_get_height(buf);
-
-		if (prpl_info && prpl_info->icon_spec.scale_rules & PURPLE_ICON_SCALE_DISPLAY)
-			purple_buddy_icon_get_scale_size(&prpl_info->icon_spec, &scale_width, &scale_height);
-
-		if (scaled || scale_height > 200 || scale_width > 200) {
-			GdkPixbuf *tmpbuf;
-			float scale_size = scaled ? 32.0 : 200.0;
-			if(scale_height > scale_width) {
-				scale_width = scale_size * (double)scale_width / (double)scale_height;
-				scale_height = scale_size;
-			} else {
-				scale_height = scale_size * (double)scale_height / (double)scale_width;
-				scale_width = scale_size;
-			}
-			/* scale & round before making square, so rectangular (but non-square)
-			 * images get rounded corners too */
-			tmpbuf = gdk_pixbuf_new(GDK_COLORSPACE_RGB, TRUE, 8, scale_width, scale_height);
-			gdk_pixbuf_fill(tmpbuf, 0x00000000);
-			gdk_pixbuf_scale(buf, tmpbuf, 0, 0, scale_width, scale_height, 0, 0, (double)scale_width/(double)orig_width, (double)scale_height/(double)orig_height, GDK_INTERP_BILINEAR);
-			if (pidgin_gdk_pixbuf_is_opaque(tmpbuf))
-				pidgin_gdk_pixbuf_make_round(tmpbuf);
-			ret = gdk_pixbuf_new(GDK_COLORSPACE_RGB, TRUE, 8, scale_size, scale_size);
-			gdk_pixbuf_fill(ret, 0x00000000);
-			gdk_pixbuf_copy_area(tmpbuf, 0, 0, scale_width, scale_height, ret, (scale_size-scale_width)/2, (scale_size-scale_height)/2);
-			g_object_unref(G_OBJECT(tmpbuf));
+		if (offline)
+			gdk_pixbuf_saturate_and_pixelate(buf, buf, 0.0, FALSE);
+
+		if (idle)
+			gdk_pixbuf_saturate_and_pixelate(buf, buf, 0.25, FALSE);
+	}
+
+	/* I'd use the pidgin_buddy_icon_get_scale_size() thing, but it won't
+	 * tell me the original size, which I need for scaling purposes. */
+	scale_width = orig_width = gdk_pixbuf_get_width(buf);
+	scale_height = orig_height = gdk_pixbuf_get_height(buf);
+
+	if (prpl_info && prpl_info->icon_spec.scale_rules & PURPLE_ICON_SCALE_DISPLAY)
+		purple_buddy_icon_get_scale_size(&prpl_info->icon_spec, &scale_width, &scale_height);
+
+	if (scaled || scale_height > 200 || scale_width > 200) {
+		GdkPixbuf *tmpbuf;
+		float scale_size = scaled ? 32.0 : 200.0;
+		if(scale_height > scale_width) {
+			scale_width = scale_size * (double)scale_width / (double)scale_height;
+			scale_height = scale_size;
 		} else {
-			ret = gdk_pixbuf_scale_simple(buf,scale_width,scale_height, GDK_INTERP_BILINEAR);
+			scale_height = scale_size * (double)scale_height / (double)scale_width;
+			scale_width = scale_size;
 		}
-		g_object_unref(G_OBJECT(buf));
-	}
+		/* Scale & round before making square, so rectangular (but
+		 * non-square) images get rounded corners too. */
+		tmpbuf = gdk_pixbuf_new(GDK_COLORSPACE_RGB, TRUE, 8, scale_width, scale_height);
+		gdk_pixbuf_fill(tmpbuf, 0x00000000);
+		gdk_pixbuf_scale(buf, tmpbuf, 0, 0, scale_width, scale_height, 0, 0, (double)scale_width/(double)orig_width, (double)scale_height/(double)orig_height, GDK_INTERP_BILINEAR);
+		if (pidgin_gdk_pixbuf_is_opaque(tmpbuf))
+			pidgin_gdk_pixbuf_make_round(tmpbuf);
+		ret = gdk_pixbuf_new(GDK_COLORSPACE_RGB, TRUE, 8, scale_size, scale_size);
+		gdk_pixbuf_fill(ret, 0x00000000);
+		gdk_pixbuf_copy_area(tmpbuf, 0, 0, scale_width, scale_height, ret, (scale_size-scale_width)/2, (scale_size-scale_height)/2);
+		g_object_unref(G_OBJECT(tmpbuf));
+	} else {
+		ret = gdk_pixbuf_scale_simple(buf,scale_width,scale_height, GDK_INTERP_BILINEAR);
+	}
+	g_object_unref(G_OBJECT(buf));
 
 	return ret;
 }
+
 /* # - Status Icon
  * P - Protocol Icon
  * A - Buddy Icon
@@ -5819,14 +5894,16 @@
 	return FALSE;
 }
 
-/*This version of pidgin_blist_update_group can take the original buddy
-or a group, but has much better algorithmic performance with a pre-known buddy*/
-static void pidgin_blist_update_group(PurpleBuddyList *list, PurpleBlistNode *node)
-{
+/* This version of pidgin_blist_update_group can take the original buddy or a
+ * group, but has much better algorithmic performance with a pre-known buddy.
+ */
+static void pidgin_blist_update_group(PurpleBuddyList *list,
+                                      PurpleBlistNode *node)
+{
+	gint count;
 	PurpleGroup *group;
-	int count;
+	PurpleBlistNode* gnode;
 	gboolean show = FALSE, show_offline = FALSE;
-	PurpleBlistNode* gnode;
 
 	g_return_if_fail(node != NULL);
 
@@ -5861,12 +5938,13 @@
 	}
 
 	if (show) {
+		gchar *title;
+		gboolean biglist;
 		GtkTreeIter iter;
 		GtkTreePath *path;
 		gboolean expanded;
 		GdkColor bgcolor;
-		char *title;
-
+		GdkPixbuf *avatar = NULL;
 
 		if(!insert_node(list, gnode, &iter))
 			return;
@@ -5878,17 +5956,23 @@
 		gtk_tree_path_free(path);
 
 		title = pidgin_get_group_title(gnode, expanded);
+		biglist = purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/blist/show_buddy_icons");
+
+		if (biglist) {
+			avatar = pidgin_blist_get_buddy_icon(gnode, TRUE, TRUE);
+		}
 
 		gtk_tree_store_set(gtkblist->treemodel, &iter,
 				   STATUS_ICON_VISIBLE_COLUMN, FALSE,
 				   STATUS_ICON_COLUMN, NULL,
 				   NAME_COLUMN, title,
 				   NODE_COLUMN, gnode,
-	/* 			   BGCOLOR_COLUMN, &bgcolor,     */
+	/*			   BGCOLOR_COLUMN, &bgcolor,     */
 				   GROUP_EXPANDER_COLUMN, TRUE,
 				   GROUP_EXPANDER_VISIBLE_COLUMN, TRUE,
 				   CONTACT_EXPANDER_VISIBLE_COLUMN, FALSE,
-				   BUDDY_ICON_VISIBLE_COLUMN, FALSE,
+				   BUDDY_ICON_COLUMN, avatar,
+				   BUDDY_ICON_VISIBLE_COLUMN, biglist,
 				   IDLE_VISIBLE_COLUMN, FALSE,
 				   EMBLEM_VISIBLE_COLUMN, FALSE,
 				   -1);
@@ -6178,7 +6262,7 @@
 				STATUS_ICON_COLUMN, status,
 				STATUS_ICON_VISIBLE_COLUMN, TRUE,
 				BUDDY_ICON_COLUMN, avatar ? avatar : gtkblist->empty_avatar,
-				BUDDY_ICON_VISIBLE_COLUMN,  purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/blist/show_buddy_icons"),
+				BUDDY_ICON_VISIBLE_COLUMN, showicons,
 				EMBLEM_COLUMN, emblem,
 				EMBLEM_VISIBLE_COLUMN, emblem != NULL,
 				PROTOCOL_ICON_COLUMN, prpl_icon,