# HG changeset patch # User Etan Reisner # Date 1211054008 0 # Node ID da5d1ecc5c20ce00b137c6afad45718f42020176 # Parent bc58e2d9f0f5419741764af428a71a57aeb390c7 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. diff -r bc58e2d9f0f5 -r da5d1ecc5c20 ChangeLog --- 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) diff -r bc58e2d9f0f5 -r da5d1ecc5c20 pidgin/gtkblist.c --- 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,