pidgin/purple-plugin-pack

Add the turtles target to keep the foot clan at bay
default tip
13 months ago, Gary Kramlich
63ad7e4f10b4
Add the turtles target to keep the foot clan at bay

Testing Done:
Ran `ninja turtles` and verified it worked correctly.

Reviewed at https://reviews.imfreedom.org/r/2409/
/*
* Album (Buddy Icon Archiver)
*
* Copyright (C) 2005-2008, Sadrul Habib Chowdhury <imadil@gmail.com>
* Copyright (C) 2005-2008, Richard Laager <rlaager@pidgin.im>
* Copyright (C) 2006, Jérôme Poulin (TiCPU) <jeromepoulin@gmail.com>
*
* 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., 51 Franklin Street, Fifth Floor, Boston, MA
* 02111-1301, USA.
*/
#include "album-ui.h"
/* We want to use the gstdio functions when possible so that non-ASCII
* filenames are handled properly on Windows. */
#include <glib/gstdio.h>
#include <gtk/gtk.h>
#include <gtk/gtkwidget.h>
#include <sys/stat.h>
#include <string.h>
#include <account.h>
#include <debug.h>
#include <gtkblist.h>
#include <pidgin.h>
#include <plugin.h>
#include <request.h>
#include <util.h>
/* XXX: For DATADIR... There must be a better way. */
#ifdef _WIN32
#include <win32/win32dep.h>
#endif
/* The increment between sizes. */
#ifndef ICON_SIZE_MULTIPLIER
#define ICON_SIZE_MULTIPLIER 32
#endif
/* The size of the icon in the icon viewer's label. */
#ifndef LABEL_ICON_SIZE
#define LABEL_ICON_SIZE 24
#endif
/* How much to pad between two rows. */
#ifndef ROW_PADDING
#define ROW_PADDING 0
#endif
/* Padding around the vbox containing the image and text. */
#ifndef VBOX_BORDER
#define VBOX_BORDER 10
#endif
/* Spacing between children in the vbox containing the image and text. */
#ifndef VBOX_SPACING
#define VBOX_SPACING 5
#endif
/* Padding around the text label for an icon. */
#ifndef LABEL_PADDING
#define LABEL_PADDING 3
#endif
/* Padding around the image. */
#ifndef IMAGE_PADDING
#define IMAGE_PADDING 3
#endif
typedef struct _BuddyIcon BuddyIcon;
typedef struct _BuddyWindow BuddyWindow;
typedef struct _icon_viewer_key icon_viewer_key;
struct _BuddyIcon
{
char *full_path;
time_t timestamp;
/* For suggesting a filename on image save. */
char *buddy_name;
};
struct _BuddyWindow
{
GtkWidget *window;
GtkWidget *vbox;
GtkWidget *iconview;
GtkTextBuffer *text_buffer;
int text_height;
int text_width;
GtkRequisition requisition;
};
/* contact or ((account and screenname) and possibly buddy) will be set.*/
struct _icon_viewer_key
{
PurpleContact *contact;
PurpleBuddy *buddy;
PurpleAccount *account;
char *screenname;
GList *list;
};
static void update_icon_view(icon_viewer_key *key);
static void show_buddy_icon_window(icon_viewer_key *key, const char *name);
void icon_viewer_key_free(void *data)
{
icon_viewer_key *key = (icon_viewer_key *)data;
g_free(key->screenname);
g_free(key);
}
guint icon_viewer_hash(gconstpointer data)
{
const icon_viewer_key *key = data;
if (key->contact != NULL)
return g_direct_hash(key->contact);
return g_str_hash(key->screenname) +
g_str_hash(purple_account_get_username(key->account));
}
gboolean icon_viewer_equal(gconstpointer y, gconstpointer z)
{
const icon_viewer_key *a, *b;
a = y;
b = z;
if (a->contact != NULL)
{
if (b->contact != NULL)
return (a->contact == b->contact);
else
return FALSE;
}
else if (b->contact != NULL)
return FALSE;
if (a->account == b->account)
{
char *normal = g_strdup(purple_normalize(a->account, a->screenname));
if (!strcmp(normal, purple_normalize(b->account, b->screenname)))
{
g_free(normal);
return TRUE;
}
g_free(normal);
}
return FALSE;
}
static void set_window_geometry(BuddyWindow *bw, int buddy_icon_size)
{
GdkGeometry geom;
g_return_if_fail(bw != NULL);
/* Set the window geometry. This controls window resizing. */
geom.base_width = bw->requisition.width + 40; /* XXX: Where is the hardcoded value coming from? */
geom.base_height = bw->requisition.height + 18; /* XXX: Where is the hardcoded value coming from? */
geom.width_inc = MAX(buddy_icon_size, bw->text_width) + 2 * VBOX_BORDER;
geom.height_inc = ROW_PADDING + 2 * VBOX_BORDER + buddy_icon_size + 2 * IMAGE_PADDING + VBOX_SPACING + bw->text_height + 2 * LABEL_PADDING;
geom.min_width = geom.base_width + 3 * geom.width_inc; /* Minimum size: 3 wide */
geom.min_height = geom.base_height + geom.height_inc; /* Minimum size: 1 high */
gtk_window_set_geometry_hints(GTK_WINDOW(bw->window), bw->vbox, &geom,
GDK_HINT_MIN_SIZE | GDK_HINT_RESIZE_INC | GDK_HINT_BASE_SIZE);
}
static gboolean resize_icons(GtkWidget *combo, icon_viewer_key *key)
{
int sel = gtk_combo_box_get_active(GTK_COMBO_BOX(combo));
BuddyWindow *bw;
switch(sel)
{
case 0: /* Small */
case 1: /* Medium */
case 2: /* Large */
purple_prefs_set_int(PREF_ICON_SIZE, sel);
break;
default:
g_return_val_if_reached(FALSE);
}
update_icon_view(key);
bw = g_hash_table_lookup(buddy_windows, key);
g_return_val_if_fail(bw != NULL, FALSE);
set_window_geometry(bw, ICON_SIZE_MULTIPLIER * (sel + 1));
return FALSE;
}
/* Save the size of the window. */
static gboolean update_size(GtkWidget *win, GdkEventConfigure *event, gpointer data)
{
int w;
int h;
gtk_window_get_size(GTK_WINDOW(win), &w, &h);
purple_prefs_set_int(PREF_WINDOW_WIDTH, w);
purple_prefs_set_int(PREF_WINDOW_HEIGHT, h);
/* We want the normal handling to continue. */
return FALSE;
}
static gboolean window_close(GtkWidget *win, gint resp, icon_viewer_key *key)
{
g_hash_table_remove(buddy_windows, key);
gtk_widget_destroy(win);
return TRUE;
}
/* Returns a scroller window which contains widget. */
static GtkWidget *wrap_in_scroller(GtkWidget *widget)
{
GtkWidget *scroller = gtk_scrolled_window_new(NULL, NULL);
gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroller),
GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scroller),
GTK_SHADOW_IN);
gtk_container_add(GTK_CONTAINER(scroller), widget);
return scroller;
}
/* Based on Purple's gtkimhtml.c, image_save_yes_cb(). */
static void convert_image(GtkImage *image, const char *filename)
{
gchar *type = NULL;
GError *error = NULL;
GSList *formats = gdk_pixbuf_get_formats();
while (formats)
{
GdkPixbufFormat *format = formats->data;
gchar **extensions = gdk_pixbuf_format_get_extensions(format);
gpointer p = extensions;
while (gdk_pixbuf_format_is_writable(format) && extensions && extensions[0])
{
gchar *fmt_ext = extensions[0];
const gchar *file_ext = filename + strlen(filename) - strlen(fmt_ext);
if (!strcmp(fmt_ext, file_ext))
{
type = gdk_pixbuf_format_get_name(format);
break;
}
extensions++;
}
g_strfreev(p);
if (type)
break;
formats = formats->next;
}
g_slist_free(formats);
if (!type)
{
GtkWidget *dialog;
/* Present an error dialog. */
dialog = gtk_message_dialog_new_with_markup(NULL, 0, GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE,
_("<span size='larger' weight='bold'>Unrecognized file type</span>\n\nDefaulting to PNG."));
g_signal_connect_swapped(dialog, "response", G_CALLBACK (gtk_widget_destroy), dialog);
gtk_widget_show(dialog);
/* Assume the user wants a PNG. */
type = g_strdup("png");
}
gdk_pixbuf_save(gtk_image_get_pixbuf(image), filename, type, &error, NULL);
if (error)
{
GtkWidget *dialog;
/* Present an error dialog. */
dialog = gtk_message_dialog_new_with_markup(NULL, 0, GTK_MESSAGE_ERROR, GTK_BUTTONS_OK,
_("<span size='larger' weight='bold'>Error saving image</span>\n\n%s"),
error->message);
g_signal_connect_swapped(dialog, "response", G_CALLBACK (gtk_widget_destroy), dialog);
gtk_widget_show(dialog);
g_error_free(error);
}
g_free(type);
}
static void
image_save_cb(GtkWidget *widget, gint response, GtkImage *image)
{
char *filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(widget));
char *image_name = g_object_get_data(G_OBJECT(image), "filename");
gtk_widget_destroy(widget);
if (response != GTK_RESPONSE_ACCEPT)
return;
purple_debug_misc(PLUGIN_STATIC_NAME, "Saving image %s as: %s\n", image_name, filename);
/* Keeping this crud out of this function is a Good Thing (TM). */
convert_image(image, filename);
g_free(filename);
}
static void save_dialog(GtkWidget *widget, GtkImage *image)
{
GtkWidget *dialog;
const char *ext;
char *filename;
dialog = gtk_file_chooser_dialog_new(_("Save Image"),
NULL,
GTK_FILE_CHOOSER_ACTION_SAVE,
GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
GTK_STOCK_SAVE, GTK_RESPONSE_ACCEPT,
NULL);
gtk_dialog_set_default_response(GTK_DIALOG(dialog), GTK_RESPONSE_ACCEPT);
/* Determine the extension. */
ext = g_object_get_data(G_OBJECT(image), "filename");
if (ext)
ext = strrchr(ext, '.');
if (ext == NULL)
ext = "";
filename = g_strdup_printf("%s%s", purple_escape_filename(g_object_get_data(G_OBJECT(image), "buddy_name")), ext);
gtk_file_chooser_set_current_name(GTK_FILE_CHOOSER(dialog), filename);
g_free(filename);
g_signal_connect(G_OBJECT(GTK_FILE_CHOOSER(dialog)), "response",
G_CALLBACK(image_save_cb), image);
gtk_widget_show(dialog);
}
static gboolean save_menu(GtkWidget *event_box, GdkEventButton *event, GtkImage *image)
{
GtkWidget *menu = gtk_menu_new();
GtkWidget *item = gtk_image_menu_item_new_with_mnemonic("_Save Icon");
gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(item),
gtk_image_new_from_stock(GTK_STOCK_SAVE, GTK_ICON_SIZE_MENU));
gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
g_signal_connect(G_OBJECT(item), "activate", G_CALLBACK(save_dialog), image);
gtk_widget_show_all(menu);
gtk_menu_popup(GTK_MENU(menu), NULL, NULL, NULL, event_box, 3, event->time);
return FALSE;
}
static gint buddy_icon_compare(BuddyIcon *b1, BuddyIcon *b2)
{
gint ret;
/* NOTE: This sorts by timestamp DESCENDING. */
if ((ret = b2->timestamp - b1->timestamp) != 0)
return ret;
/* This isn't strictly necessary, but it
* ensures the icon order is stable. */
return strcmp(b1->full_path, b2->full_path);
}
/* Returns an unsorted list of available icons for buddy (as BuddyIcon *).
* The items need to be freed.
*/
static GList *retrieve_icons(PurpleAccount *account, const char *name)
{
char *path;
GDir *dir;
const char *filename;
GList *list = NULL;
path = album_buddy_icon_get_dir(account, name);
if (path == NULL)
{
purple_debug_warning(PLUGIN_STATIC_NAME, "Path for buddy %s not found.\n", name);
return NULL;
}
if (!(dir = g_dir_open(path, 0, NULL)))
{
purple_debug_warning(PLUGIN_STATIC_NAME, "Could not open path: %s\n", path);
g_free(path);
return NULL;
}
while ((filename = g_dir_read_name(dir)))
{
char *full_path = g_build_filename(path, filename, NULL);
struct stat st;
BuddyIcon *icon;
/* We need the file's timestamp. */
if (stat(full_path, &st) != 0)
{
g_free(full_path);
continue;
}
icon = g_new0(BuddyIcon, 1);
icon->full_path = full_path;
icon->timestamp = st.st_mtime;
icon->buddy_name = g_strdup(name);
list = g_list_prepend(list, icon);
}
g_dir_close(dir);
g_free(path);
return list;
}
static gboolean add_icon_from_list_cb(gpointer data)
{
GtkTextIter text_iter;
int buddy_icon_pref = purple_prefs_get_int(PREF_ICON_SIZE);
int buddy_icon_size;
BuddyWindow *bw;
GtkTextBuffer *text_buffer;
GtkTextIter start, end;
GtkWidget *iconview;
icon_viewer_key *key = data;
BuddyIcon *icon;
GdkPixbuf *pixbuf;
int width;
int height;
int xpad = 0;
int ypad = 0;
GdkPixbuf *scaled;
GtkWidget *image;
GtkWidget *event_box;
GtkWidget *alignment;
GtkWidget *widget;
GtkWidget *label;
const char *timestamp;
GtkTextChildAnchor *anchor;
BuddyIcon *prev_icon;
char *prev_icon_filename;
GList *l;
/* If we're out of icons, kill this idle callback. */
if (key->list == NULL)
return FALSE;
bw = g_hash_table_lookup(buddy_windows, key);
g_return_val_if_fail(bw != NULL, FALSE);
text_buffer = bw->text_buffer;
iconview = bw->iconview;
/* Clamp the pref value to the allowable range. */
buddy_icon_pref = CLAMP(buddy_icon_pref, 0, 2);
buddy_icon_size = ICON_SIZE_MULTIPLIER * (buddy_icon_pref + 1);
gtk_text_buffer_get_end_iter(text_buffer, &text_iter);
/* Duplicate Removal */
prev_icon = key->list->data;
/* Technically, this will yield "/filename.ext", but the
* code below will get the same thing, so it'll compare fine. */
prev_icon_filename = strrchr(prev_icon->full_path, '/');
if (prev_icon_filename == NULL)
{
/* This should never happen. */
prev_icon_filename = prev_icon->full_path;
}
for (l = key->list->next ; l != NULL ; l = l->next)
{
BuddyIcon *this_icon = l->data;
char *this_icon_filename = strrchr(this_icon->full_path, '/');
if (this_icon_filename == NULL)
{
/* This should never happen. */
this_icon_filename = this_icon->full_path;
}
/* The files are named by hash, so if they have the
* same basename, we can assume they have the same
* contents. This happens when someone uses the
* same icon on multiple accounts. We only want to
* show each icon once.
*/
if (!strcmp(this_icon_filename, prev_icon_filename))
{
key->list = g_list_delete_link(key->list, l);
}
}
/* Pop off one icon to add. */
icon = key->list->data;
key->list = g_list_delete_link(key->list, key->list);
pixbuf = gdk_pixbuf_new_from_file(icon->full_path, NULL);
if (pixbuf == NULL)
{
purple_debug_warning(PLUGIN_STATIC_NAME, "Invalid image file: %s\n", icon->full_path);
g_free(icon->full_path);
g_free(icon->buddy_name);
g_free(icon);
return TRUE;
}
width = gdk_pixbuf_get_width(pixbuf);
height = gdk_pixbuf_get_height(pixbuf);
/* Never scale the image up. */
if (height > buddy_icon_size || width > buddy_icon_size)
{
/* Scale the image proportionally to fit with a square of size buddy_icon_size. */
if (width > height)
{
int new_height = (int)(buddy_icon_size / (double)width * height);
ypad = buddy_icon_size - new_height;
scaled = gdk_pixbuf_scale_simple(pixbuf, buddy_icon_size, new_height, GDK_INTERP_BILINEAR);
}
else
{
int new_width = (int)(buddy_icon_size / (double)height * width);
xpad = buddy_icon_size - new_width;
scaled = gdk_pixbuf_scale_simple(pixbuf, new_width, buddy_icon_size, GDK_INTERP_BILINEAR);
}
g_object_unref(G_OBJECT(pixbuf));
}
else
{
ypad = buddy_icon_size - height;
xpad = buddy_icon_size - width;
scaled = pixbuf;
}
/* Now we're ready to add the icon. */
/* Create the image and an event box to which to attach the right-click menu. */
image = gtk_image_new_from_pixbuf(scaled);
g_object_unref(G_OBJECT(scaled));
event_box = gtk_event_box_new();
gtk_event_box_set_visible_window(GTK_EVENT_BOX(event_box), FALSE);
gtk_container_add(GTK_CONTAINER(event_box), image);
/* Save the filename and create a right-click handler. */
g_object_set_data_full(G_OBJECT(image), "buddy_name", icon->buddy_name, g_free);
g_object_set_data_full(G_OBJECT(image), "filename", icon->full_path, g_free);
g_signal_connect(G_OBJECT(event_box), "button-press-event",
G_CALLBACK(save_menu), image);
/* Add padding as required. */
alignment = gtk_alignment_new(0.5, 0.5, 0, 0);
/* The + 1 is in case the padding is odd. It ensures that one side will get the extra one pixel so that
* the padding is always correct. Without it, we'd be one pixel short whenever xpad or ypad was odd. */
gtk_alignment_set_padding(GTK_ALIGNMENT(alignment), ypad / 2, (ypad + 1) / 2, xpad / 2, (xpad + 1) / 2);
gtk_container_add(GTK_CONTAINER(alignment), event_box);
widget = gtk_vbox_new(FALSE, VBOX_SPACING);
gtk_container_set_border_width(GTK_CONTAINER(widget), VBOX_BORDER);
gtk_box_pack_start(GTK_BOX(widget), alignment, FALSE, FALSE, IMAGE_PADDING);
/* Label */
timestamp = purple_utf8_strftime(_("%x\n%X"), localtime(&icon->timestamp));
label = gtk_label_new(NULL);
gtk_label_set_text(GTK_LABEL(label), timestamp);
gtk_label_set_justify(GTK_LABEL(label), GTK_JUSTIFY_CENTER);
gtk_box_pack_start(GTK_BOX(widget), label, TRUE, TRUE, LABEL_PADDING);
anchor = gtk_text_buffer_create_child_anchor(text_buffer, &text_iter);
gtk_text_view_add_child_at_anchor(GTK_TEXT_VIEW(iconview), widget, anchor);
gtk_widget_show_all(widget);
gtk_text_buffer_get_bounds(text_buffer, &start, &end);
gtk_text_buffer_apply_tag_by_name(text_buffer, "word_wrap", &start, &end);
g_free(icon);
return TRUE;
}
static void update_icon_view(icon_viewer_key *key)
{
GtkTextIter start;
GtkTextIter end;
GList *list = NULL;
BuddyWindow *bw;
GtkWidget *iconview;
GtkTextBuffer *text_buffer;
gboolean success = FALSE;
bw = g_hash_table_lookup(buddy_windows, key);
g_return_if_fail(bw != NULL);
iconview = bw->iconview;
text_buffer = bw->text_buffer;
gtk_text_buffer_get_bounds(text_buffer, &start, &end);
gtk_text_buffer_delete(text_buffer, &start, &end);
if (key->contact != NULL)
{
PurpleBlistNode *bnode;
/* Get the list of all the accounts of the contact and sort it. */
for (bnode = ((PurpleBlistNode *)key->contact)->child; bnode ; bnode = bnode->next)
{
list = g_list_concat(retrieve_icons(purple_buddy_get_account((PurpleBuddy *)bnode),
purple_buddy_get_name((PurpleBuddy *)bnode)), list);
}
}
else if (key->buddy != NULL)
{
list = retrieve_icons(purple_buddy_get_account(key->buddy),
purple_buddy_get_name(key->buddy));
}
else
{
list = retrieve_icons(key->account, key->screenname);
}
/* Show icons for all the accounts of the contact. */
if (list != NULL)
{
int id;
list = g_list_sort(list, (GCompareFunc)buddy_icon_compare);
success = (list != NULL);
key->list = list;
/* It's possible we already have one of these loops running, but
* having two won't harm anything, so we don't do anything about it. */
id = g_idle_add(add_icon_from_list_cb, key);
g_object_set_data_full(G_OBJECT(iconview), "update-idle-callback",
GINT_TO_POINTER(id), (GDestroyNotify)g_source_remove);
}
if (!success)
{
/* No icons were found. Display an appropriate message. */
GtkWidget *hbox;
char *filename;
GdkPixbuf *pixbuf;
GdkPixbuf *scaled;
GtkWidget *image;
char *str;
GtkWidget *label;
GtkTextIter text_iter;
GtkTextChildAnchor *anchor;
/* Hbox */
/* Reuse spacing information that's used for the vbox which contains the image and text for each icon. */
hbox = gtk_hbox_new(FALSE, VBOX_SPACING);
gtk_container_set_border_width(GTK_CONTAINER(hbox), VBOX_BORDER);
/* Image */
#ifndef _WIN32
filename = g_build_filename(PIXMAPSDIR, "dialogs", "purple_info.png", NULL);
#else
filename = g_build_filename(wpurple_install_dir(), "pixmaps", "pidgin", "dialogs", "purple_info.png", NULL);
#endif
pixbuf = gdk_pixbuf_new_from_file(filename, NULL);
g_free(filename);
scaled = gdk_pixbuf_scale_simple(pixbuf, 48, 48, GDK_INTERP_BILINEAR);
g_object_unref(G_OBJECT(pixbuf));
image = gtk_image_new_from_pixbuf(scaled);
g_object_unref(G_OBJECT(scaled));
gtk_box_pack_start(GTK_BOX(hbox), image, FALSE, FALSE, 0);
/* Label */
str = g_strdup_printf("<span size='larger' weight='bold'>%s</span>", _("No icons were found."));
label = gtk_label_new(NULL);
gtk_label_set_markup(GTK_LABEL(label), str);
g_free(str);
gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 0);
/* Add the hbox to the text view. */
gtk_text_buffer_get_iter_at_offset (text_buffer, &text_iter, 0);
anchor = gtk_text_buffer_create_child_anchor(text_buffer, &text_iter);
gtk_text_view_add_child_at_anchor(GTK_TEXT_VIEW(iconview), hbox, anchor);
}
gtk_widget_show_all(iconview);
gtk_text_view_set_cursor_visible(GTK_TEXT_VIEW(iconview), FALSE);
}
static void update_runtime(icon_viewer_key *key, gpointer value, PurpleBuddy *buddy)
{
PurpleAccount *account = purple_buddy_get_account(buddy);
if (key->contact != NULL)
{
char *name = g_strdup(purple_normalize(account, purple_buddy_get_name(buddy)));
PurpleBlistNode *node;
for (node = ((PurpleBlistNode *)key->contact)->child; node; node = node->next)
{
if (account == purple_buddy_get_account((PurpleBuddy *)node) &&
!strcmp(name, purple_normalize(account, purple_buddy_get_name((PurpleBuddy *)node))))
{
g_free(name);
update_icon_view(key);
return;
}
}
g_free(name);
}
else if (account == key->account &&
!strcmp(key->screenname, purple_normalize(account, purple_buddy_get_name(buddy))))
{
update_icon_view(key);
}
}
void album_update_runtime(PurpleBuddy *buddy, gpointer data)
{
g_hash_table_foreach(buddy_windows, (GHFunc)update_runtime, buddy);
}
static GtkWidget *get_viewer_icon()
{
char *filename = NULL;
GdkPixbuf *pixbuf;
int width;
int height;
GdkPixbuf *scaled;
GtkWidget *image;
if (filename == NULL)
#ifndef _WIN32
filename = g_build_filename(PIXMAPSDIR, "icons", "online.png", NULL);
#else
filename = g_build_filename(wpurple_install_dir(), "pixmaps", "pidgin", "icons", "online.png", NULL);
#endif
pixbuf = gdk_pixbuf_new_from_file(filename, NULL);
g_free(filename);
width = gdk_pixbuf_get_width(pixbuf);
height = gdk_pixbuf_get_height(pixbuf);
/* Never scale the image up. */
if (height > LABEL_ICON_SIZE || width > LABEL_ICON_SIZE)
{
/* Scale the image proportionally to fit with a square of size buddy_icon_size. */
if (width > height)
{
int new_height = (int)(LABEL_ICON_SIZE / (double)width * height);
scaled = gdk_pixbuf_scale_simple(pixbuf, LABEL_ICON_SIZE, new_height, GDK_INTERP_BILINEAR);
}
else
{
int new_width = (int)(LABEL_ICON_SIZE / (double)height * width);
scaled = gdk_pixbuf_scale_simple(pixbuf, new_width, LABEL_ICON_SIZE, GDK_INTERP_BILINEAR);
}
g_object_unref(G_OBJECT(pixbuf));
}
else
{
scaled = pixbuf;
}
image = gtk_image_new_from_pixbuf(scaled);
g_object_unref(G_OBJECT(scaled));
return image;
}
static void view_buddy_icons_cb(PurpleBlistNode *node, gpointer data)
{
gboolean contact_expanded;
icon_viewer_key *key = g_new0(icon_viewer_key, 1);
const char *name;
g_return_if_fail(node != NULL);
if(PURPLE_BLIST_NODE_HAS_FLAG(node, PURPLE_BLIST_NODE_FLAG_NO_SAVE))
return;
contact_expanded = pidgin_blist_node_is_contact_expanded(node);
if (PURPLE_BLIST_NODE_IS_BUDDY(node))
{
if (!contact_expanded)
{
/* Work on the contact, since it's collapsed. */
name = purple_contact_get_alias((PurpleContact*)node->parent);
if (name == NULL)
name = purple_buddy_get_name(((PurpleContact *)node->parent)->priority);
if (node->next != NULL)
{
/* The contact has at least two buddies. */
key->contact = (PurpleContact *)node->parent;
}
else
{
/* Treat one-buddy contacts similar to buddies. */
key->account = purple_buddy_get_account((PurpleBuddy *)node);
key->screenname = g_strdup(purple_normalize(key->account, purple_buddy_get_name((PurpleBuddy *)node)));
key->buddy = (PurpleBuddy *)node;
}
}
else
{
/* The contact is expanded and the user has chosen a buddy. */
key->account = purple_buddy_get_account((PurpleBuddy *)node);
key->screenname = g_strdup(purple_normalize(key->account, purple_buddy_get_name((PurpleBuddy *)node)));
key->buddy = (PurpleBuddy *)node;
name = purple_buddy_get_alias_only((PurpleBuddy *)node);
if (name == NULL)
{
name = purple_buddy_get_name((PurpleBuddy *)node);
}
}
}
else if (PURPLE_BLIST_NODE_IS_CONTACT(node))
{
/* The contact is expanded and the user has chosen the contact. */
if (node->child != NULL && node->child->next != NULL)
{
/* The contact has at least two buddies. */
key->contact = (PurpleContact *)node;
}
else
{
/* Treat one-buddy contacts similar to buddies. */
key->account = purple_buddy_get_account((PurpleBuddy *)node->child);
key->screenname = g_strdup(purple_normalize(key->account, purple_buddy_get_name((PurpleBuddy *)node->child)));
key->buddy = (PurpleBuddy *)node->child;
}
name = purple_contact_get_alias((PurpleContact*)node);
if (name == NULL)
name = purple_buddy_get_name(((PurpleContact *)node)->priority);
}
else
g_return_if_reached();
show_buddy_icon_window(key, name);
}
static gboolean compare_buddy_keys(icon_viewer_key *key1, BuddyWindow *bw, icon_viewer_key *key2)
{
g_return_val_if_fail(key2->contact == NULL, FALSE);
if (key1->contact == NULL)
{
if (key1->account == key2->account)
{
char *normal = g_strdup(purple_normalize(key1->account, key1->screenname));
if (!strcmp(normal, purple_normalize(key2->account, key2->screenname)))
{
g_free(normal);
return TRUE;
}
g_free(normal);
}
}
return FALSE;
}
static void show_buddy_icon_window(icon_viewer_key *key, const char *name)
{
char *title;
GtkWidget *win;
GtkWidget *vbox;
char *str;
GtkTextIter start;
GtkTextIter end;
GtkWidget *title_box;
GtkWidget *label;
GtkWidget *combo;
GtkWidget *iconview;
GtkTextBuffer *text_buffer;
time_t now;
const char *timestamp;
PangoLayout *layout;
int text_width;
int text_height;
int buddy_icon_pref = purple_prefs_get_int(PREF_ICON_SIZE);
int buddy_icon_size;
BuddyWindow *bw;
/* Return if a window is already opened for the buddy. */
if ((bw = g_hash_table_lookup(buddy_windows, key)) != NULL)
{
icon_viewer_key_free(key);
gtk_window_present(GTK_WINDOW(bw->window));
return;
}
/* If it was a contact, it would've matched above.
* Compare screennames...
*/
if (key->contact == NULL &&
(bw = g_hash_table_find(buddy_windows, (GHRFunc)compare_buddy_keys, key)) != NULL)
{
icon_viewer_key_free(key);
gtk_window_present(GTK_WINDOW(bw->window));
return;
}
/* Clamp the pref value to the allowable range. */
buddy_icon_pref = MAX(0, buddy_icon_pref);
buddy_icon_pref = MIN(2, buddy_icon_pref);
buddy_icon_size = ICON_SIZE_MULTIPLIER * (buddy_icon_pref + 1);
title = g_strdup_printf(_("Buddy Icons used by %s"), name);
win = gtk_dialog_new_with_buttons(title, NULL, 0,
GTK_STOCK_CLOSE, GTK_RESPONSE_CLOSE, NULL);
gtk_window_set_role(GTK_WINDOW(win), "buddy_icon_viewer");
gtk_container_set_border_width(GTK_CONTAINER(win), 12);
vbox = gtk_vbox_new(FALSE, 5);
gtk_box_pack_start(GTK_BOX(GTK_DIALOG(win)->vbox), vbox, TRUE, TRUE, 0);
iconview = gtk_text_view_new();
text_buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(iconview));
gtk_text_view_set_editable(GTK_TEXT_VIEW(iconview), FALSE);
#if ROW_PADDING != 0
gtk_text_view_set_pixels_inside_wrap(GTK_TEXT_VIEW(iconview), ROW_PADDING);
#endif
gtk_text_buffer_create_tag (text_buffer, "word_wrap",
"wrap_mode", GTK_WRAP_WORD, NULL);
gtk_text_buffer_get_bounds(text_buffer, &start, &end);
gtk_text_buffer_apply_tag_by_name(text_buffer, "word_wrap", &start, &end);
/* Get the size of the text of a sample timestamp. */
now = time(NULL);
timestamp = purple_utf8_strftime("%x\n%X", localtime(&now));
layout = gtk_widget_create_pango_layout(iconview, timestamp);
pango_layout_get_pixel_size(layout, &text_width, &text_height);
/* Title Box */
title_box = gtk_hbox_new(FALSE, 6);
gtk_container_set_border_width(GTK_CONTAINER(title_box), 6);
gtk_box_pack_start(GTK_BOX(vbox), title_box, FALSE, FALSE, 0);
/* Icon */
gtk_box_pack_start(GTK_BOX(title_box), get_viewer_icon(), FALSE, FALSE, 0);
/* Label */
str = g_strdup_printf("<span size='larger' weight='bold'>%s</span>", title);
g_free(title);
label = gtk_label_new(NULL);
gtk_label_set_markup(GTK_LABEL(label), str);
g_free(str);
gtk_misc_set_alignment(GTK_MISC(label), 0, 0);
gtk_box_pack_start(GTK_BOX(title_box), label, FALSE, FALSE, 0);
/* Icon View */
gtk_box_pack_start(GTK_BOX(vbox), wrap_in_scroller(iconview), TRUE, TRUE, 0);
/* Size Selector */
combo = gtk_combo_box_new_text();
str = g_strdup_printf(_("Small (%1$ux%1$u)"), ICON_SIZE_MULTIPLIER);
gtk_combo_box_append_text(GTK_COMBO_BOX(combo), str);
g_free(str);
str = g_strdup_printf(_("Medium (%1$ux%1$u)"), ICON_SIZE_MULTIPLIER * 2);
gtk_combo_box_append_text(GTK_COMBO_BOX(combo), str);
g_free(str);
str = g_strdup_printf(_("Large (%1$ux%1$u)"), ICON_SIZE_MULTIPLIER * 3);
gtk_combo_box_append_text(GTK_COMBO_BOX(combo), str);
g_free(str);
gtk_combo_box_set_active(GTK_COMBO_BOX(combo), buddy_icon_pref);
gtk_widget_show_all(combo);
gtk_signal_connect(GTK_OBJECT(combo), "changed", GTK_SIGNAL_FUNC(resize_icons), key);
gtk_box_pack_start(GTK_BOX(GTK_DIALOG(win)->action_area), combo, FALSE, FALSE, 0);
gtk_box_reorder_child(GTK_BOX(GTK_DIALOG(win)->action_area), combo, 0);
/* Add the stuff in the hashtable. */
bw = g_new0(BuddyWindow, 1);
bw->window = win;
bw->vbox = vbox;
bw->iconview = iconview;
bw->text_buffer = text_buffer;
bw->text_height = text_height;
bw->text_width = text_width;
g_hash_table_insert(buddy_windows, key, bw);
update_icon_view(key);
gtk_widget_size_request(bw->iconview, &bw->requisition);
set_window_geometry(bw, buddy_icon_size);
gtk_window_set_default_size(GTK_WINDOW(win),
purple_prefs_get_int(PREF_WINDOW_WIDTH),
purple_prefs_get_int(PREF_WINDOW_HEIGHT));
gtk_window_set_policy(GTK_WINDOW(win), FALSE, TRUE, FALSE);
gtk_widget_show_all(win);
#if 0
/* TODO: We need a way to disconnect this signal handler when the window is closed.
* TODO: Otherwise, it segfaults in update_runtime. */
/* Register a signal handler to update the viewer if a new buddy icon is cached. */
purple_signal_connect_priority(purple_buddy_icons_get_handle(), "buddy-icon-cached",
purple_plugins_find_with_id(PLUGIN_ID), PURPLE_CALLBACK(update_runtime),
key, PURPLE_SIGNAL_PRIORITY_DEFAULT + 1);
#endif
gtk_signal_connect(GTK_OBJECT(win), "configure_event",
GTK_SIGNAL_FUNC(update_size), NULL);
g_signal_connect(G_OBJECT(win), "response",
G_CALLBACK(window_close), key);
}
static gboolean has_stored_icons(PurpleBuddy *buddy)
{
char *path = album_buddy_icon_get_dir(purple_buddy_get_account(buddy),
purple_buddy_get_name(buddy));
GDir *dir = g_dir_open(path, 0, NULL);
g_free(path);
if (dir)
{
if (g_dir_read_name(dir))
{
g_dir_close(dir);
return TRUE;
}
g_dir_close(dir);
}
return FALSE;
}
static void
album_select_dialog_cb(gpointer data, PurpleRequestFields *fields)
{
char *username;
PurpleAccount *account;
account = purple_request_fields_get_account(fields, "account");
username = g_strdup(purple_normalize(account,
purple_request_fields_get_string(fields, "screenname")));
if (username != NULL && *username != '\0' && account != NULL )
{
icon_viewer_key *key = g_new0(icon_viewer_key, 1);
key->account = account;
key->screenname = username;
show_buddy_icon_window(key, username);
}
}
/* Based on Pidgin's gtkdialogs.c, pidgindialogs_log(). */
static void album_select_dialog(PurplePluginAction *action)
{
PurpleRequestFields *fields;
PurpleRequestFieldGroup *group;
PurpleRequestField *field;
fields = purple_request_fields_new();
group = purple_request_field_group_new(NULL);
purple_request_fields_add_group(fields, group);
field = purple_request_field_string_new("screenname", _("_Name"), NULL, FALSE);
purple_request_field_set_type_hint(field, "screenname-all");
purple_request_field_set_required(field, TRUE);
purple_request_field_group_add_field(group, field);
field = purple_request_field_account_new("account", _("_Account"), NULL);
purple_request_field_set_type_hint(field, "account");
purple_request_field_account_set_show_all(field, TRUE);
purple_request_field_set_visible(field, (purple_accounts_get_all() != NULL &&
purple_accounts_get_all()->next != NULL));
purple_request_field_set_required(field, TRUE);
purple_request_field_group_add_field(group, field);
purple_request_fields(purple_get_blist(), _("View Buddy Icons..."),
NULL,
_("Please enter the screen name or alias of the person whose icon album you want to view."),
fields,
_("OK"), G_CALLBACK(album_select_dialog_cb),
_("Cancel"), NULL,
NULL, NULL, NULL,
NULL);
}
GList *album_get_plugin_actions(PurplePlugin *plugin, gpointer data)
{
GList *actions = NULL;
actions = g_list_append(actions, purple_plugin_action_new(_("View Buddy Icons"), album_select_dialog));
return actions;
}
void album_blist_node_menu_cb(PurpleBlistNode *node, GList **menu)
{
gboolean contact_expanded;
PurpleMenuAction *action;
void (*callback)() = view_buddy_icons_cb;
if (!(PURPLE_BLIST_NODE_IS_CONTACT(node) || PURPLE_BLIST_NODE_IS_BUDDY(node)))
return;
contact_expanded = pidgin_blist_node_is_contact_expanded(node);
if (PURPLE_BLIST_NODE_IS_BUDDY(node))
{
if (contact_expanded)
{
if (!has_stored_icons((PurpleBuddy *)node))
{
callback = NULL;
}
}
else if (PURPLE_BLIST_NODE_IS_CONTACT(node))
{
/* We don't want to show this option in buddy submenus. */
if ((PurpleBlistNode *)((PurpleContact *)node->parent)->priority != node)
return;
/* Find the contact and fall through to the contact handling code. */
node = node->parent;
}
}
if (PURPLE_BLIST_NODE_IS_CONTACT(node))
{
PurpleBlistNode *bnode;
for (bnode = node->child; bnode; bnode = bnode->next)
{
if (has_stored_icons((PurpleBuddy *)bnode))
break;
}
/* bnode == NULL when the for loop made it all the way through. */
if (bnode == NULL)
{
/* No icons found. */
callback = NULL;
}
}
/* Separator */
(*menu) = g_list_append(*menu, NULL);
action = purple_menu_action_new(_("_View Buddy Icons"), PURPLE_CALLBACK(callback), NULL, NULL);
(*menu) = g_list_append(*menu, action);
}