pidgin/pidgin

Add disconnection reasons to oscar.
cpw.resiak.disconnectreason
2007-10-01, Will Thompson
a9fc6198b5c6
Add disconnection reasons to oscar.
/**
* @file gtkutils.c GTK+ utility functions
* @ingroup pidgin
*/
/* pidgin
*
* Pidgin is the legal property of its developers, whose names are too numerous
* to list here. Please refer to the COPYRIGHT file distributed with this
* source distribution.
*
* 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 "internal.h"
#include "pidgin.h"
#ifndef _WIN32
# include <X11/Xlib.h>
#else
# ifdef small
# undef small
# endif
#endif /*_WIN32*/
#ifdef USE_GTKSPELL
# include <gtkspell/gtkspell.h>
# ifdef _WIN32
# include "wspell.h"
# endif
#endif
#include <gdk/gdkkeysyms.h>
#include "conversation.h"
#include "debug.h"
#include "desktopitem.h"
#include "imgstore.h"
#include "notify.h"
#include "prefs.h"
#include "prpl.h"
#include "request.h"
#include "signals.h"
#include "util.h"
#include "gtkconv.h"
#include "gtkdialogs.h"
#include "gtkimhtml.h"
#include "gtkimhtmltoolbar.h"
#include "pidginstock.h"
#include "gtkthemes.h"
#include "gtkutils.h"
typedef struct {
GtkWidget *menu;
gint default_item;
} AopMenu;
static guint accels_save_timer = 0;
static gboolean
url_clicked_idle_cb(gpointer data)
{
purple_notify_uri(NULL, data);
g_free(data);
return FALSE;
}
static void
url_clicked_cb(GtkWidget *w, const char *uri)
{
g_idle_add(url_clicked_idle_cb, g_strdup(uri));
}
static GtkIMHtmlFuncs gtkimhtml_cbs = {
(GtkIMHtmlGetImageFunc)purple_imgstore_find_by_id,
(GtkIMHtmlGetImageDataFunc)purple_imgstore_get_data,
(GtkIMHtmlGetImageSizeFunc)purple_imgstore_get_size,
(GtkIMHtmlGetImageFilenameFunc)purple_imgstore_get_filename,
purple_imgstore_ref_by_id,
purple_imgstore_unref_by_id,
};
void
pidgin_setup_imhtml(GtkWidget *imhtml)
{
PangoFontDescription *desc = NULL;
g_return_if_fail(imhtml != NULL);
g_return_if_fail(GTK_IS_IMHTML(imhtml));
g_signal_connect(G_OBJECT(imhtml), "url_clicked",
G_CALLBACK(url_clicked_cb), NULL);
pidgin_themes_smiley_themeize(imhtml);
gtk_imhtml_set_funcs(GTK_IMHTML(imhtml), &gtkimhtml_cbs);
if (!purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/conversations/use_theme_font")) {
const char *font = purple_prefs_get_string(PIDGIN_PREFS_ROOT "/conversations/custom_font");
desc = pango_font_description_from_string(font);
} else if (purple_running_gnome()) {
/* Use the GNOME "document" font, if applicable */
char *path, *font;
if ((path = g_find_program_in_path("gconftool-2"))) {
g_free(path);
if (!g_spawn_command_line_sync(
"gconftool-2 -g /desktop/gnome/interface/document_font_name",
&font, NULL, NULL, NULL))
return;
}
desc = pango_font_description_from_string(font);
g_free(font);
}
if (desc) {
gtk_widget_modify_font(imhtml, desc);
pango_font_description_free(desc);
}
}
GtkWidget *
pidgin_create_window(const char *title, guint border_width, const char *role, gboolean resizable)
{
GtkWindow *wnd = NULL;
wnd = GTK_WINDOW(gtk_window_new(GTK_WINDOW_TOPLEVEL));
if (title)
gtk_window_set_title(wnd, title);
#ifdef _WIN32
else
gtk_window_set_title(wnd, PIDGIN_ALERT_TITLE);
#endif
gtk_container_set_border_width(GTK_CONTAINER(wnd), border_width);
if (role)
gtk_window_set_role(wnd, role);
gtk_window_set_resizable(wnd, resizable);
return GTK_WIDGET(wnd);
}
GtkWidget *
pidgin_create_imhtml(gboolean editable, GtkWidget **imhtml_ret, GtkWidget **toolbar_ret, GtkWidget **sw_ret)
{
GtkWidget *frame;
GtkWidget *imhtml;
GtkWidget *sep;
GtkWidget *sw;
GtkWidget *toolbar = NULL;
GtkWidget *vbox;
frame = gtk_frame_new(NULL);
gtk_frame_set_shadow_type(GTK_FRAME(frame), GTK_SHADOW_IN);
vbox = gtk_vbox_new(FALSE, 0);
gtk_container_add(GTK_CONTAINER(frame), vbox);
gtk_widget_show(vbox);
if (editable) {
toolbar = gtk_imhtmltoolbar_new();
gtk_box_pack_start(GTK_BOX(vbox), toolbar, FALSE, FALSE, 0);
gtk_widget_show(toolbar);
sep = gtk_hseparator_new();
gtk_box_pack_start(GTK_BOX(vbox), sep, FALSE, FALSE, 0);
g_signal_connect_swapped(G_OBJECT(toolbar), "show", G_CALLBACK(gtk_widget_show), sep);
g_signal_connect_swapped(G_OBJECT(toolbar), "hide", G_CALLBACK(gtk_widget_hide), sep);
gtk_widget_show(sep);
}
sw = gtk_scrolled_window_new(NULL, NULL);
gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(sw),
GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
gtk_box_pack_start(GTK_BOX(vbox), sw, TRUE, TRUE, 0);
gtk_widget_show(sw);
imhtml = gtk_imhtml_new(NULL, NULL);
gtk_imhtml_set_editable(GTK_IMHTML(imhtml), editable);
gtk_imhtml_set_format_functions(GTK_IMHTML(imhtml), GTK_IMHTML_ALL ^ GTK_IMHTML_IMAGE);
gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(imhtml), GTK_WRAP_WORD_CHAR);
#ifdef USE_GTKSPELL
if (editable && purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/conversations/spellcheck"))
pidgin_setup_gtkspell(GTK_TEXT_VIEW(imhtml));
#endif
gtk_widget_show(imhtml);
if (editable) {
gtk_imhtmltoolbar_attach(GTK_IMHTMLTOOLBAR(toolbar), imhtml);
gtk_imhtmltoolbar_associate_smileys(GTK_IMHTMLTOOLBAR(toolbar), "default");
}
pidgin_setup_imhtml(imhtml);
gtk_container_add(GTK_CONTAINER(sw), imhtml);
if (imhtml_ret != NULL)
*imhtml_ret = imhtml;
if (editable && (toolbar_ret != NULL))
*toolbar_ret = toolbar;
if (sw_ret != NULL)
*sw_ret = sw;
return frame;
}
void
pidgin_set_sensitive_if_input(GtkWidget *entry, GtkWidget *dialog)
{
const char *text = gtk_entry_get_text(GTK_ENTRY(entry));
gtk_dialog_set_response_sensitive(GTK_DIALOG(dialog), GTK_RESPONSE_OK,
(*text != '\0'));
}
void
pidgin_toggle_sensitive(GtkWidget *widget, GtkWidget *to_toggle)
{
gboolean sensitivity;
if (to_toggle == NULL)
return;
sensitivity = GTK_WIDGET_IS_SENSITIVE(to_toggle);
gtk_widget_set_sensitive(to_toggle, !sensitivity);
}
void
pidgin_toggle_sensitive_array(GtkWidget *w, GPtrArray *data)
{
gboolean sensitivity;
gpointer element;
int i;
for (i=0; i < data->len; i++) {
element = g_ptr_array_index(data,i);
if (element == NULL)
continue;
sensitivity = GTK_WIDGET_IS_SENSITIVE(element);
gtk_widget_set_sensitive(element, !sensitivity);
}
}
void
pidgin_toggle_showhide(GtkWidget *widget, GtkWidget *to_toggle)
{
if (to_toggle == NULL)
return;
if (GTK_WIDGET_VISIBLE(to_toggle))
gtk_widget_hide(to_toggle);
else
gtk_widget_show(to_toggle);
}
GtkWidget *pidgin_separator(GtkWidget *menu)
{
GtkWidget *menuitem;
menuitem = gtk_separator_menu_item_new();
gtk_widget_show(menuitem);
gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
return menuitem;
}
GtkWidget *pidgin_new_item(GtkWidget *menu, const char *str)
{
GtkWidget *menuitem;
GtkWidget *label;
menuitem = gtk_menu_item_new();
if (menu)
gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
gtk_widget_show(menuitem);
label = gtk_label_new(str);
gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
gtk_label_set_pattern(GTK_LABEL(label), "_");
gtk_container_add(GTK_CONTAINER(menuitem), label);
gtk_widget_show(label);
/* FIXME: Go back and fix this
gtk_widget_add_accelerator(menuitem, "activate", accel, str[0],
GDK_MOD1_MASK, GTK_ACCEL_LOCKED);
*/
pidgin_set_accessible_label (menuitem, label);
return menuitem;
}
GtkWidget *pidgin_new_check_item(GtkWidget *menu, const char *str,
GtkSignalFunc sf, gpointer data, gboolean checked)
{
GtkWidget *menuitem;
menuitem = gtk_check_menu_item_new_with_mnemonic(str);
if (menu)
gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem), checked);
if (sf)
g_signal_connect(G_OBJECT(menuitem), "activate", sf, data);
gtk_widget_show_all(menuitem);
return menuitem;
}
GtkWidget *
pidgin_pixbuf_toolbar_button_from_stock(const char *icon)
{
GtkWidget *button, *image, *bbox;
button = gtk_toggle_button_new();
gtk_button_set_relief(GTK_BUTTON(button), GTK_RELIEF_NONE);
bbox = gtk_vbox_new(FALSE, 0);
gtk_container_add (GTK_CONTAINER(button), bbox);
image = gtk_image_new_from_stock(icon, gtk_icon_size_from_name(PIDGIN_ICON_SIZE_TANGO_EXTRA_SMALL));
gtk_box_pack_start(GTK_BOX(bbox), image, FALSE, FALSE, 0);
gtk_widget_show_all(bbox);
return button;
}
GtkWidget *
pidgin_pixbuf_button_from_stock(const char *text, const char *icon,
PidginButtonOrientation style)
{
GtkWidget *button, *image, *label, *bbox, *ibox, *lbox = NULL;
button = gtk_button_new();
if (style == PIDGIN_BUTTON_HORIZONTAL) {
bbox = gtk_hbox_new(FALSE, 0);
ibox = gtk_hbox_new(FALSE, 0);
if (text)
lbox = gtk_hbox_new(FALSE, 0);
} else {
bbox = gtk_vbox_new(FALSE, 0);
ibox = gtk_vbox_new(FALSE, 0);
if (text)
lbox = gtk_vbox_new(FALSE, 0);
}
gtk_container_add(GTK_CONTAINER(button), bbox);
if (icon) {
gtk_box_pack_start_defaults(GTK_BOX(bbox), ibox);
image = gtk_image_new_from_stock(icon, GTK_ICON_SIZE_BUTTON);
gtk_box_pack_end(GTK_BOX(ibox), image, FALSE, TRUE, 0);
}
if (text) {
gtk_box_pack_start_defaults(GTK_BOX(bbox), lbox);
label = gtk_label_new(NULL);
gtk_label_set_text_with_mnemonic(GTK_LABEL(label), text);
gtk_label_set_mnemonic_widget(GTK_LABEL(label), button);
gtk_box_pack_start(GTK_BOX(lbox), label, FALSE, TRUE, 0);
pidgin_set_accessible_label (button, label);
}
gtk_widget_show_all(bbox);
return button;
}
GtkWidget *pidgin_new_item_from_stock(GtkWidget *menu, const char *str, const char *icon, GtkSignalFunc sf, gpointer data, guint accel_key, guint accel_mods, char *mod)
{
GtkWidget *menuitem;
/*
GtkWidget *hbox;
GtkWidget *label;
*/
GtkWidget *image;
if (icon == NULL)
menuitem = gtk_menu_item_new_with_mnemonic(str);
else
menuitem = gtk_image_menu_item_new_with_mnemonic(str);
if (menu)
gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
if (sf)
g_signal_connect(G_OBJECT(menuitem), "activate", sf, data);
if (icon != NULL) {
image = gtk_image_new_from_stock(icon, gtk_icon_size_from_name(PIDGIN_ICON_SIZE_TANGO_EXTRA_SMALL));
gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(menuitem), image);
}
/* FIXME: this isn't right
if (mod) {
label = gtk_label_new(mod);
gtk_box_pack_end(GTK_BOX(hbox), label, FALSE, FALSE, 2);
gtk_widget_show(label);
}
*/
/*
if (accel_key) {
gtk_widget_add_accelerator(menuitem, "activate", accel, accel_key,
accel_mods, GTK_ACCEL_LOCKED);
}
*/
gtk_widget_show_all(menuitem);
return menuitem;
}
GtkWidget *
pidgin_make_frame(GtkWidget *parent, const char *title)
{
GtkWidget *vbox, *label, *hbox;
char *labeltitle;
vbox = gtk_vbox_new(FALSE, PIDGIN_HIG_BOX_SPACE);
gtk_box_pack_start(GTK_BOX(parent), vbox, FALSE, FALSE, 0);
gtk_widget_show(vbox);
label = gtk_label_new(NULL);
labeltitle = g_strdup_printf("<span weight=\"bold\">%s</span>", title);
gtk_label_set_markup(GTK_LABEL(label), labeltitle);
g_free(labeltitle);
gtk_misc_set_alignment(GTK_MISC(label), 0, 0);
gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 0);
gtk_widget_show(label);
pidgin_set_accessible_label (vbox, label);
hbox = gtk_hbox_new(FALSE, PIDGIN_HIG_BOX_SPACE);
gtk_box_pack_start (GTK_BOX (vbox), hbox, FALSE, FALSE, 0);
gtk_widget_show(hbox);
label = gtk_label_new(" ");
gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 0);
gtk_widget_show(label);
vbox = gtk_vbox_new(FALSE, PIDGIN_HIG_BOX_SPACE);
gtk_box_pack_start(GTK_BOX(hbox), vbox, FALSE, FALSE, 0);
gtk_widget_show(vbox);
return vbox;
}
static gpointer
aop_option_menu_get_selected(GtkWidget *optmenu, GtkWidget **p_item)
{
GtkWidget *menu = gtk_option_menu_get_menu(GTK_OPTION_MENU(optmenu));
GtkWidget *item = gtk_menu_get_active(GTK_MENU(menu));
if (p_item)
(*p_item) = item;
return g_object_get_data(G_OBJECT(item), "aop_per_item_data");
}
static void
aop_menu_cb(GtkWidget *optmenu, GCallback cb)
{
GtkWidget *item;
gpointer per_item_data;
per_item_data = aop_option_menu_get_selected(optmenu, &item);
if (cb != NULL) {
((void (*)(GtkWidget *, gpointer, gpointer))cb)(item, per_item_data, g_object_get_data(G_OBJECT(optmenu), "user_data"));
}
}
static GtkWidget *
aop_menu_item_new(GtkSizeGroup *sg, GdkPixbuf *pixbuf, const char *lbl, gpointer per_item_data, const char *data)
{
GtkWidget *item;
GtkWidget *hbox;
GtkWidget *image;
GtkWidget *label;
item = gtk_menu_item_new();
gtk_widget_show(item);
hbox = gtk_hbox_new(FALSE, 4);
gtk_widget_show(hbox);
/* Create the image */
if (pixbuf == NULL)
image = gtk_image_new();
else
image = gtk_image_new_from_pixbuf(pixbuf);
gtk_widget_show(image);
if (sg)
gtk_size_group_add_widget(sg, image);
/* Create the label */
label = gtk_label_new (lbl);
gtk_widget_show (label);
gtk_label_set_justify(GTK_LABEL(label), GTK_JUSTIFY_LEFT);
gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.5);
gtk_container_add(GTK_CONTAINER(item), hbox);
gtk_box_pack_start(GTK_BOX(hbox), image, FALSE, FALSE, 0);
gtk_box_pack_start(GTK_BOX(hbox), label, TRUE, TRUE, 0);
g_object_set_data(G_OBJECT (item), data, per_item_data);
g_object_set_data(G_OBJECT (item), "aop_per_item_data", per_item_data);
pidgin_set_accessible_label(item, label);
return item;
}
static GdkPixbuf *
pidgin_create_prpl_icon_from_prpl(PurplePlugin *prpl, PidginPrplIconSize size, PurpleAccount *account)
{
PurplePluginProtocolInfo *prpl_info;
const char *protoname = NULL;
char *tmp;
char *filename = NULL;
GdkPixbuf *pixbuf;
prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(prpl);
if (prpl_info->list_icon == NULL)
return NULL;
protoname = prpl_info->list_icon(account, NULL);
if (protoname == NULL)
return NULL;
/*
* Status icons will be themeable too, and then it will look up
* protoname from the theme
*/
tmp = g_strconcat(protoname, ".png", NULL);
filename = g_build_filename(DATADIR, "pixmaps", "pidgin", "protocols",
size == PIDGIN_PRPL_ICON_SMALL ? "16" :
size == PIDGIN_PRPL_ICON_MEDIUM ? "22" : "48",
tmp, NULL);
g_free(tmp);
pixbuf = gdk_pixbuf_new_from_file(filename, NULL);
g_free(filename);
return pixbuf;
}
static GtkWidget *
aop_option_menu_new(AopMenu *aop_menu, GCallback cb, gpointer user_data)
{
GtkWidget *optmenu;
optmenu = gtk_option_menu_new();
gtk_widget_show(optmenu);
gtk_option_menu_set_menu(GTK_OPTION_MENU(optmenu), aop_menu->menu);
if (aop_menu->default_item != -1)
gtk_option_menu_set_history(GTK_OPTION_MENU(optmenu), aop_menu->default_item);
g_object_set_data_full(G_OBJECT(optmenu), "aop_menu", aop_menu, (GDestroyNotify)g_free);
g_object_set_data(G_OBJECT(optmenu), "user_data", user_data);
g_signal_connect(G_OBJECT(optmenu), "changed", G_CALLBACK(aop_menu_cb), cb);
return optmenu;
}
static void
aop_option_menu_replace_menu(GtkWidget *optmenu, AopMenu *new_aop_menu)
{
if (gtk_option_menu_get_menu(GTK_OPTION_MENU(optmenu)))
gtk_option_menu_remove_menu(GTK_OPTION_MENU(optmenu));
gtk_option_menu_set_menu(GTK_OPTION_MENU(optmenu), new_aop_menu->menu);
if (new_aop_menu->default_item != -1)
gtk_option_menu_set_history(GTK_OPTION_MENU(optmenu), new_aop_menu->default_item);
g_object_set_data_full(G_OBJECT(optmenu), "aop_menu", new_aop_menu, (GDestroyNotify)g_free);
}
static void
aop_option_menu_select_by_data(GtkWidget *optmenu, gpointer data)
{
guint idx;
GList *llItr = NULL;
for (idx = 0, llItr = GTK_MENU_SHELL(gtk_option_menu_get_menu(GTK_OPTION_MENU(optmenu)))->children;
llItr != NULL;
llItr = llItr->next, idx++) {
if (data == g_object_get_data(G_OBJECT(llItr->data), "aop_per_item_data")) {
gtk_option_menu_set_history(GTK_OPTION_MENU(optmenu), idx);
break;
}
}
}
static AopMenu *
create_protocols_menu(const char *default_proto_id)
{
AopMenu *aop_menu = NULL;
PurplePluginProtocolInfo *prpl_info;
PurplePlugin *plugin;
GdkPixbuf *pixbuf = NULL;
GtkSizeGroup *sg;
GList *p;
const char *gtalk_name = NULL;
int i;
aop_menu = g_malloc0(sizeof(AopMenu));
aop_menu->default_item = -1;
aop_menu->menu = gtk_menu_new();
gtk_widget_show(aop_menu->menu);
sg = gtk_size_group_new(GTK_SIZE_GROUP_HORIZONTAL);
if (purple_find_prpl("prpl-jabber"))
gtalk_name = _("Google Talk");
for (p = purple_plugins_get_protocols(), i = 0;
p != NULL;
p = p->next, i++) {
plugin = (PurplePlugin *)p->data;
prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(plugin);
if (gtalk_name && strcmp(gtalk_name, plugin->info->name) < 0) {
char *filename = g_build_filename(DATADIR, "pixmaps", "pidgin", "protocols",
"16", "google-talk.png", NULL);
GtkWidget *item;
pixbuf = gdk_pixbuf_new_from_file(filename, NULL);
g_free(filename);
gtk_menu_shell_append(GTK_MENU_SHELL(aop_menu->menu),
item = aop_menu_item_new(sg, pixbuf, gtalk_name, "prpl-jabber", "protocol"));
g_object_set_data(G_OBJECT(item), "fake", GINT_TO_POINTER(1));
if (pixbuf)
g_object_unref(pixbuf);
gtalk_name = NULL;
i++;
}
pixbuf = pidgin_create_prpl_icon_from_prpl(plugin, PIDGIN_PRPL_ICON_SMALL, NULL);
gtk_menu_shell_append(GTK_MENU_SHELL(aop_menu->menu),
aop_menu_item_new(sg, pixbuf, plugin->info->name, plugin->info->id, "protocol"));
if (pixbuf)
g_object_unref(pixbuf);
if (default_proto_id != NULL && !strcmp(plugin->info->id, default_proto_id))
aop_menu->default_item = i;
}
g_object_unref(sg);
return aop_menu;
}
GtkWidget *
pidgin_protocol_option_menu_new(const char *id, GCallback cb,
gpointer user_data)
{
return aop_option_menu_new(create_protocols_menu(id), cb, user_data);
}
const char *
pidgin_protocol_option_menu_get_selected(GtkWidget *optmenu)
{
return (const char *)aop_option_menu_get_selected(optmenu, NULL);
}
PurpleAccount *
pidgin_account_option_menu_get_selected(GtkWidget *optmenu)
{
return (PurpleAccount *)aop_option_menu_get_selected(optmenu, NULL);
}
static AopMenu *
create_account_menu(PurpleAccount *default_account,
PurpleFilterAccountFunc filter_func, gboolean show_all)
{
AopMenu *aop_menu = NULL;
PurpleAccount *account;
GdkPixbuf *pixbuf = NULL;
GList *list;
GList *p;
GtkSizeGroup *sg;
int i;
char buf[256];
if (show_all)
list = purple_accounts_get_all();
else
list = purple_connections_get_all();
aop_menu = g_malloc0(sizeof(AopMenu));
aop_menu->default_item = -1;
aop_menu->menu = gtk_menu_new();
gtk_widget_show(aop_menu->menu);
sg = gtk_size_group_new(GTK_SIZE_GROUP_HORIZONTAL);
for (p = list, i = 0; p != NULL; p = p->next, i++) {
PurplePlugin *plugin;
if (show_all)
account = (PurpleAccount *)p->data;
else {
PurpleConnection *gc = (PurpleConnection *)p->data;
account = purple_connection_get_account(gc);
}
if (filter_func && !filter_func(account)) {
i--;
continue;
}
plugin = purple_find_prpl(purple_account_get_protocol_id(account));
pixbuf = pidgin_create_prpl_icon(account, PIDGIN_PRPL_ICON_SMALL);
if (pixbuf) {
if (purple_account_is_disconnected(account) && show_all &&
purple_connections_get_all())
gdk_pixbuf_saturate_and_pixelate(pixbuf, pixbuf, 0.0, FALSE);
}
if (purple_account_get_alias(account)) {
g_snprintf(buf, sizeof(buf), "%s (%s) (%s)",
purple_account_get_username(account),
purple_account_get_alias(account),
purple_account_get_protocol_name(account));
} else {
g_snprintf(buf, sizeof(buf), "%s (%s)",
purple_account_get_username(account),
purple_account_get_protocol_name(account));
}
gtk_menu_shell_append(GTK_MENU_SHELL(aop_menu->menu),
aop_menu_item_new(sg, pixbuf, buf, account, "account"));
if (pixbuf)
g_object_unref(pixbuf);
if (default_account && account == default_account)
aop_menu->default_item = i;
}
g_object_unref(sg);
return aop_menu;
}
static void
regenerate_account_menu(GtkWidget *optmenu)
{
gboolean show_all;
PurpleAccount *account;
PurpleFilterAccountFunc filter_func;
account = (PurpleAccount *)aop_option_menu_get_selected(optmenu, NULL);
show_all = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(optmenu), "show_all"));
filter_func = g_object_get_data(G_OBJECT(optmenu), "filter_func");
aop_option_menu_replace_menu(optmenu, create_account_menu(account, filter_func, show_all));
}
static void
account_menu_sign_on_off_cb(PurpleConnection *gc, GtkWidget *optmenu)
{
regenerate_account_menu(optmenu);
}
static void
account_menu_added_removed_cb(PurpleAccount *account, GtkWidget *optmenu)
{
regenerate_account_menu(optmenu);
}
static gboolean
account_menu_destroyed_cb(GtkWidget *optmenu, GdkEvent *event,
void *user_data)
{
purple_signals_disconnect_by_handle(optmenu);
return FALSE;
}
void
pidgin_account_option_menu_set_selected(GtkWidget *optmenu, PurpleAccount *account)
{
aop_option_menu_select_by_data(optmenu, account);
}
GtkWidget *
pidgin_account_option_menu_new(PurpleAccount *default_account,
gboolean show_all, GCallback cb,
PurpleFilterAccountFunc filter_func,
gpointer user_data)
{
GtkWidget *optmenu;
/* Create the option menu */
optmenu = aop_option_menu_new(create_account_menu(default_account, filter_func, show_all), cb, user_data);
g_signal_connect(G_OBJECT(optmenu), "destroy",
G_CALLBACK(account_menu_destroyed_cb), NULL);
/* Register the purple sign on/off event callbacks. */
purple_signal_connect(purple_connections_get_handle(), "signed-on",
optmenu, PURPLE_CALLBACK(account_menu_sign_on_off_cb),
optmenu);
purple_signal_connect(purple_connections_get_handle(), "signed-off",
optmenu, PURPLE_CALLBACK(account_menu_sign_on_off_cb),
optmenu);
purple_signal_connect(purple_accounts_get_handle(), "account-added",
optmenu, PURPLE_CALLBACK(account_menu_added_removed_cb),
optmenu);
purple_signal_connect(purple_accounts_get_handle(), "account-removed",
optmenu, PURPLE_CALLBACK(account_menu_added_removed_cb),
optmenu);
/* Set some data. */
g_object_set_data(G_OBJECT(optmenu), "user_data", user_data);
g_object_set_data(G_OBJECT(optmenu), "show_all", GINT_TO_POINTER(show_all));
g_object_set_data(G_OBJECT(optmenu), "filter_func", filter_func);
return optmenu;
}
gboolean
pidgin_check_if_dir(const char *path, GtkFileSelection *filesel)
{
char *dirname;
if (g_file_test(path, G_FILE_TEST_IS_DIR)) {
/* append a / if needed */
if (path[strlen(path) - 1] != G_DIR_SEPARATOR) {
dirname = g_strconcat(path, G_DIR_SEPARATOR_S, NULL);
} else {
dirname = g_strdup(path);
}
gtk_file_selection_set_filename(filesel, dirname);
g_free(dirname);
return TRUE;
}
return FALSE;
}
void
pidgin_setup_gtkspell(GtkTextView *textview)
{
#ifdef USE_GTKSPELL
GError *error = NULL;
char *locale = NULL;
g_return_if_fail(textview != NULL);
g_return_if_fail(GTK_IS_TEXT_VIEW(textview));
if (gtkspell_new_attach(textview, locale, &error) == NULL && error)
{
purple_debug_warning("gtkspell", "Failed to setup GtkSpell: %s\n",
error->message);
g_error_free(error);
}
#endif /* USE_GTKSPELL */
}
void
pidgin_save_accels_cb(GtkAccelGroup *accel_group, guint arg1,
GdkModifierType arg2, GClosure *arg3,
gpointer data)
{
purple_debug(PURPLE_DEBUG_MISC, "accels",
"accel changed, scheduling save.\n");
if (!accels_save_timer)
accels_save_timer = g_timeout_add(5000, pidgin_save_accels,
NULL);
}
gboolean
pidgin_save_accels(gpointer data)
{
char *filename = NULL;
filename = g_build_filename(purple_user_dir(), G_DIR_SEPARATOR_S,
"accels", NULL);
purple_debug(PURPLE_DEBUG_MISC, "accels", "saving accels to %s\n", filename);
gtk_accel_map_save(filename);
g_free(filename);
accels_save_timer = 0;
return FALSE;
}
void
pidgin_load_accels()
{
char *filename = NULL;
filename = g_build_filename(purple_user_dir(), G_DIR_SEPARATOR_S,
"accels", NULL);
gtk_accel_map_load(filename);
g_free(filename);
}
static void
show_retrieveing_info(PurpleConnection *conn, const char *name)
{
PurpleNotifyUserInfo *info = purple_notify_user_info_new();
purple_notify_user_info_add_pair(info, _("Information"), _("Retrieving..."));
purple_notify_userinfo(conn, name, info, NULL, NULL);
purple_notify_user_info_destroy(info);
}
void pidgin_retrieve_user_info(PurpleConnection *conn, const char *name)
{
show_retrieveing_info(conn, name);
serv_get_info(conn, name);
}
void pidgin_retrieve_user_info_in_chat(PurpleConnection *conn, const char *name, int chat)
{
char *who = NULL;
PurplePluginProtocolInfo *prpl_info = NULL;
if (chat < 0) {
pidgin_retrieve_user_info(conn, name);
return;
}
prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(conn->prpl);
if (prpl_info == NULL || prpl_info->get_cb_info == NULL) {
pidgin_retrieve_user_info(conn, name);
return;
}
if (prpl_info->get_cb_real_name)
who = prpl_info->get_cb_real_name(conn, chat, name);
show_retrieveing_info(conn, who ? who : name);
prpl_info->get_cb_info(conn, chat, name);
g_free(who);
}
gboolean
pidgin_parse_x_im_contact(const char *msg, gboolean all_accounts,
PurpleAccount **ret_account, char **ret_protocol,
char **ret_username, char **ret_alias)
{
char *protocol = NULL;
char *username = NULL;
char *alias = NULL;
char *str;
char *c, *s;
gboolean valid;
g_return_val_if_fail(msg != NULL, FALSE);
g_return_val_if_fail(ret_protocol != NULL, FALSE);
g_return_val_if_fail(ret_username != NULL, FALSE);
s = str = g_strdup(msg);
while (*s != '\r' && *s != '\n' && *s != '\0')
{
char *key, *value;
key = s;
/* Grab the key */
while (*s != '\r' && *s != '\n' && *s != '\0' && *s != ' ')
s++;
if (*s == '\r') s++;
if (*s == '\n')
{
s++;
continue;
}
if (*s != '\0') *s++ = '\0';
/* Clear past any whitespace */
while (*s != '\0' && *s == ' ')
s++;
/* Now let's grab until the end of the line. */
value = s;
while (*s != '\r' && *s != '\n' && *s != '\0')
s++;
if (*s == '\r') *s++ = '\0';
if (*s == '\n') *s++ = '\0';
if ((c = strchr(key, ':')) != NULL)
{
if (!g_ascii_strcasecmp(key, "X-IM-Username:"))
username = g_strdup(value);
else if (!g_ascii_strcasecmp(key, "X-IM-Protocol:"))
protocol = g_strdup(value);
else if (!g_ascii_strcasecmp(key, "X-IM-Alias:"))
alias = g_strdup(value);
}
}
if (username != NULL && protocol != NULL)
{
valid = TRUE;
*ret_username = username;
*ret_protocol = protocol;
if (ret_alias != NULL)
*ret_alias = alias;
/* Check for a compatible account. */
if (ret_account != NULL)
{
GList *list;
PurpleAccount *account = NULL;
GList *l;
const char *protoname;
if (all_accounts)
list = purple_accounts_get_all();
else
list = purple_connections_get_all();
for (l = list; l != NULL; l = l->next)
{
PurpleConnection *gc;
PurplePluginProtocolInfo *prpl_info = NULL;
PurplePlugin *plugin;
if (all_accounts)
{
account = (PurpleAccount *)l->data;
plugin = purple_plugins_find_with_id(
purple_account_get_protocol_id(account));
if (plugin == NULL)
{
account = NULL;
continue;
}
prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(plugin);
}
else
{
gc = (PurpleConnection *)l->data;
account = purple_connection_get_account(gc);
prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(gc->prpl);
}
protoname = prpl_info->list_icon(account, NULL);
if (!strcmp(protoname, protocol))
break;
account = NULL;
}
/* Special case for AIM and ICQ */
if (account == NULL && (!strcmp(protocol, "aim") ||
!strcmp(protocol, "icq")))
{
for (l = list; l != NULL; l = l->next)
{
PurpleConnection *gc;
PurplePluginProtocolInfo *prpl_info = NULL;
PurplePlugin *plugin;
if (all_accounts)
{
account = (PurpleAccount *)l->data;
plugin = purple_plugins_find_with_id(
purple_account_get_protocol_id(account));
if (plugin == NULL)
{
account = NULL;
continue;
}
prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(plugin);
}
else
{
gc = (PurpleConnection *)l->data;
account = purple_connection_get_account(gc);
prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(gc->prpl);
}
protoname = prpl_info->list_icon(account, NULL);
if (!strcmp(protoname, "aim") || !strcmp(protoname, "icq"))
break;
account = NULL;
}
}
*ret_account = account;
}
}
else
{
valid = FALSE;
g_free(username);
g_free(protocol);
g_free(alias);
}
g_free(str);
return valid;
}
void
pidgin_set_accessible_label (GtkWidget *w, GtkWidget *l)
{
AtkObject *acc;
const gchar *label_text;
const gchar *existing_name;
acc = gtk_widget_get_accessible (w);
/* If this object has no name, set it's name with the label text */
existing_name = atk_object_get_name (acc);
if (!existing_name) {
label_text = gtk_label_get_text (GTK_LABEL(l));
if (label_text)
atk_object_set_name (acc, label_text);
}
pidgin_set_accessible_relations(w, l);
}
void
pidgin_set_accessible_relations (GtkWidget *w, GtkWidget *l)
{
AtkObject *acc, *label;
AtkObject *rel_obj[1];
AtkRelationSet *set;
AtkRelation *relation;
acc = gtk_widget_get_accessible (w);
label = gtk_widget_get_accessible (l);
/* Make sure mnemonics work */
gtk_label_set_mnemonic_widget(GTK_LABEL(l), w);
/* Create the labeled-by relation */
set = atk_object_ref_relation_set (acc);
rel_obj[0] = label;
relation = atk_relation_new (rel_obj, 1, ATK_RELATION_LABELLED_BY);
atk_relation_set_add (set, relation);
g_object_unref (relation);
/* Create the label-for relation */
set = atk_object_ref_relation_set (label);
rel_obj[0] = acc;
relation = atk_relation_new (rel_obj, 1, ATK_RELATION_LABEL_FOR);
atk_relation_set_add (set, relation);
g_object_unref (relation);
}
void
pidgin_menu_position_func_helper(GtkMenu *menu,
gint *x,
gint *y,
gboolean *push_in,
gpointer data)
{
#if GTK_CHECK_VERSION(2,2,0)
GtkWidget *widget;
GtkRequisition requisition;
GdkScreen *screen;
GdkRectangle monitor;
gint monitor_num;
gint space_left, space_right, space_above, space_below;
gint needed_width;
gint needed_height;
gint xthickness;
gint ythickness;
gboolean rtl;
g_return_if_fail(GTK_IS_MENU(menu));
widget = GTK_WIDGET(menu);
screen = gtk_widget_get_screen(widget);
xthickness = widget->style->xthickness;
ythickness = widget->style->ythickness;
rtl = (gtk_widget_get_direction(widget) == GTK_TEXT_DIR_RTL);
/*
* We need the requisition to figure out the right place to
* popup the menu. In fact, we always need to ask here, since
* if a size_request was queued while we weren't popped up,
* the requisition won't have been recomputed yet.
*/
gtk_widget_size_request (widget, &requisition);
monitor_num = gdk_screen_get_monitor_at_point (screen, *x, *y);
push_in = FALSE;
/*
* The placement of popup menus horizontally works like this (with
* RTL in parentheses)
*
* - If there is enough room to the right (left) of the mouse cursor,
* position the menu there.
*
* - Otherwise, if if there is enough room to the left (right) of the
* mouse cursor, position the menu there.
*
* - Otherwise if the menu is smaller than the monitor, position it
* on the side of the mouse cursor that has the most space available
*
* - Otherwise (if there is simply not enough room for the menu on the
* monitor), position it as far left (right) as possible.
*
* Positioning in the vertical direction is similar: first try below
* mouse cursor, then above.
*/
gdk_screen_get_monitor_geometry (screen, monitor_num, &monitor);
space_left = *x - monitor.x;
space_right = monitor.x + monitor.width - *x - 1;
space_above = *y - monitor.y;
space_below = monitor.y + monitor.height - *y - 1;
/* position horizontally */
/* the amount of space we need to position the menu. Note the
* menu is offset "xthickness" pixels
*/
needed_width = requisition.width - xthickness;
if (needed_width <= space_left ||
needed_width <= space_right)
{
if ((rtl && needed_width <= space_left) ||
(!rtl && needed_width > space_right))
{
/* position left */
*x = *x + xthickness - requisition.width + 1;
}
else
{
/* position right */
*x = *x - xthickness;
}
/* x is clamped on-screen further down */
}
else if (requisition.width <= monitor.width)
{
/* the menu is too big to fit on either side of the mouse
* cursor, but smaller than the monitor. Position it on
* the side that has the most space
*/
if (space_left > space_right)
{
/* left justify */
*x = monitor.x;
}
else
{
/* right justify */
*x = monitor.x + monitor.width - requisition.width;
}
}
else /* menu is simply too big for the monitor */
{
if (rtl)
{
/* right justify */
*x = monitor.x + monitor.width - requisition.width;
}
else
{
/* left justify */
*x = monitor.x;
}
}
/* Position vertically. The algorithm is the same as above, but
* simpler because we don't have to take RTL into account.
*/
needed_height = requisition.height - ythickness;
if (needed_height <= space_above ||
needed_height <= space_below)
{
if (needed_height <= space_below)
*y = *y - ythickness;
else
*y = *y + ythickness - requisition.height + 1;
*y = CLAMP (*y, monitor.y,
monitor.y + monitor.height - requisition.height);
}
else if (needed_height > space_below && needed_height > space_above)
{
if (space_below >= space_above)
*y = monitor.y + monitor.height - requisition.height;
else
*y = monitor.y;
}
else
{
*y = monitor.y;
}
#endif
}
void
pidgin_treeview_popup_menu_position_func(GtkMenu *menu,
gint *x,
gint *y,
gboolean *push_in,
gpointer data)
{
GtkWidget *widget = GTK_WIDGET(data);
GtkTreeView *tv = GTK_TREE_VIEW(data);
GtkTreePath *path;
GtkTreeViewColumn *col;
GdkRectangle rect;
gint ythickness = GTK_WIDGET(menu)->style->ythickness;
gdk_window_get_origin (widget->window, x, y);
gtk_tree_view_get_cursor (tv, &path, &col);
gtk_tree_view_get_cell_area (tv, path, col, &rect);
*x += rect.x+rect.width;
*y += rect.y+rect.height+ythickness;
pidgin_menu_position_func_helper(menu, x, y, push_in, data);
}
enum {
DND_FILE_TRANSFER,
DND_IM_IMAGE,
DND_BUDDY_ICON
};
typedef struct {
char *filename;
PurpleAccount *account;
char *who;
} _DndData;
static void dnd_image_ok_callback(_DndData *data, int choice)
{
char *filedata;
size_t size;
struct stat st;
GError *err = NULL;
PurpleConversation *conv;
PidginConversation *gtkconv;
GtkTextIter iter;
int id;
switch (choice) {
case DND_BUDDY_ICON:
if (g_stat(data->filename, &st)) {
char *str;
str = g_strdup_printf(_("The following error has occurred loading %s: %s"),
data->filename, strerror(errno));
purple_notify_error(NULL, NULL,
_("Failed to load image"),
str);
g_free(str);
return;
}
pidgin_set_custom_buddy_icon(data->account, data->who, data->filename);
break;
case DND_FILE_TRANSFER:
serv_send_file(purple_account_get_connection(data->account), data->who, data->filename);
break;
case DND_IM_IMAGE:
conv = purple_conversation_new(PURPLE_CONV_TYPE_IM, data->account, data->who);
gtkconv = PIDGIN_CONVERSATION(conv);
if (!g_file_get_contents(data->filename, &filedata, &size,
&err)) {
char *str;
str = g_strdup_printf(_("The following error has occurred loading %s: %s"), data->filename, err->message);
purple_notify_error(NULL, NULL,
_("Failed to load image"),
str);
g_error_free(err);
g_free(str);
return;
}
id = purple_imgstore_add_with_id(filedata, size, data->filename);
gtk_text_buffer_get_iter_at_mark(GTK_IMHTML(gtkconv->entry)->text_buffer, &iter,
gtk_text_buffer_get_insert(GTK_IMHTML(gtkconv->entry)->text_buffer));
gtk_imhtml_insert_image_at_iter(GTK_IMHTML(gtkconv->entry), id, &iter);
purple_imgstore_unref_by_id(id);
break;
}
free(data->filename);
free(data->who);
free(data);
}
static void dnd_image_cancel_callback(_DndData *data, int choice)
{
free(data->filename);
free(data->who);
free(data);
}
static void dnd_set_icon_ok_cb(_DndData *data)
{
dnd_image_ok_callback(data, DND_BUDDY_ICON);
}
static void dnd_set_icon_cancel_cb(_DndData *data)
{
free(data->filename);
free(data->who);
free(data);
}
void
pidgin_dnd_file_manage(GtkSelectionData *sd, PurpleAccount *account, const char *who)
{
GList *tmp;
GdkPixbuf *pb;
GList *files = purple_uri_list_extract_filenames((const gchar *)sd->data);
PurpleConnection *gc = purple_account_get_connection(account);
PurplePluginProtocolInfo *prpl_info = NULL;
gboolean file_send_ok = FALSE;
#ifndef _WIN32
PurpleDesktopItem *item;
#endif
g_return_if_fail(account != NULL);
g_return_if_fail(who != NULL);
for(tmp = files; tmp != NULL ; tmp = g_list_next(tmp)) {
gchar *filename = tmp->data;
gchar *basename = g_path_get_basename(filename);
/* Set the default action: don't send anything */
file_send_ok = FALSE;
/* XXX - Make ft API support creating a transfer with more than one file */
if (!g_file_test(filename, G_FILE_TEST_EXISTS)) {
continue;
}
/* XXX - make ft api suupport sending a directory */
/* Are we dealing with a directory? */
if (g_file_test(filename, G_FILE_TEST_IS_DIR)) {
char *str, *str2;
str = g_strdup_printf(_("Cannot send folder %s."), basename);
str2 = g_strdup_printf(_("%s cannot transfer a folder. You will need to send the files within individually."), PIDGIN_NAME);
purple_notify_error(NULL, NULL,
str, str2);
g_free(str);
g_free(str2);
continue;
}
/* Are we dealing with an image? */
pb = gdk_pixbuf_new_from_file(filename, NULL);
if (pb) {
_DndData *data = g_malloc(sizeof(_DndData));
gboolean ft = FALSE, im = FALSE;
data->who = g_strdup(who);
data->filename = g_strdup(filename);
data->account = account;
if (gc)
prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(gc->prpl);
if (prpl_info && prpl_info->options & OPT_PROTO_IM_IMAGE)
im = TRUE;
if (prpl_info && prpl_info->can_receive_file)
ft = prpl_info->can_receive_file(gc, who);
if (im && ft)
purple_request_choice(NULL, NULL,
_("You have dragged an image"),
_("You can send this image as a file transfer, "
"embed it into this message, or use it as the buddy icon for this user."),
DND_FILE_TRANSFER, "OK", (GCallback)dnd_image_ok_callback,
"Cancel", (GCallback)dnd_image_cancel_callback,
account, who, NULL,
data,
_("Set as buddy icon"), DND_BUDDY_ICON,
_("Send image file"), DND_FILE_TRANSFER,
_("Insert in message"), DND_IM_IMAGE,
NULL);
else if (!(im || ft))
purple_request_yes_no(NULL, NULL, _("You have dragged an image"),
_("Would you like to set it as the buddy icon for this user?"),
0,
account, who, NULL,
data, (GCallback)dnd_set_icon_ok_cb, (GCallback)dnd_set_icon_cancel_cb);
else
purple_request_choice(NULL, NULL,
_("You have dragged an image"),
(ft ? _("You can send this image as a file transfer, or use it as the buddy icon for this user.") :
_("You can insert this image into this message, or use it as the buddy icon for this user")),
(ft ? DND_FILE_TRANSFER : DND_IM_IMAGE),
"OK", (GCallback)dnd_image_ok_callback,
"Cancel", (GCallback)dnd_image_cancel_callback,
account, who, NULL,
data,
_("Set as buddy icon"), DND_BUDDY_ICON,
(ft ? _("Send image file") : _("Insert in message")), (ft ? DND_FILE_TRANSFER : DND_IM_IMAGE),
NULL);
return;
}
#ifndef _WIN32
/* Are we trying to send a .desktop file? */
else if (purple_str_has_suffix(basename, ".desktop") && (item = purple_desktop_item_new_from_file(filename))) {
PurpleDesktopItemType dtype;
char key[64];
const char *itemname = NULL;
#if GTK_CHECK_VERSION(2,6,0)
const char * const *langs;
int i;
langs = g_get_language_names();
for (i = 0; langs[i]; i++) {
g_snprintf(key, sizeof(key), "Name[%s]", langs[i]);
itemname = purple_desktop_item_get_string(item, key);
break;
}
#else
const char *lang = g_getenv("LANG");
char *dot;
dot = strchr(lang, '.');
if (dot)
*dot = '\0';
g_snprintf(key, sizeof(key), "Name[%s]", lang);
itemname = purple_desktop_item_get_string(item, key);
#endif
if (!itemname)
itemname = purple_desktop_item_get_string(item, "Name");
dtype = purple_desktop_item_get_entry_type(item);
switch (dtype) {
PurpleConversation *conv;
PidginConversation *gtkconv;
case PURPLE_DESKTOP_ITEM_TYPE_LINK:
conv = purple_conversation_new(PURPLE_CONV_TYPE_IM, account, who);
gtkconv = PIDGIN_CONVERSATION(conv);
gtk_imhtml_insert_link(GTK_IMHTML(gtkconv->entry),
gtk_text_buffer_get_insert(GTK_IMHTML(gtkconv->entry)->text_buffer),
purple_desktop_item_get_string(item, "URL"), itemname);
break;
default:
/* I don't know if we really want to do anything here. Most of the desktop item types are crap like
* "MIME Type" (I have no clue how that would be a desktop item) and "Comment"... nothing we can really
* send. The only logical one is "Application," but do we really want to send a binary and nothing else?
* Probably not. I'll just give an error and return. */
/* The original patch sent the icon used by the launcher. That's probably wrong */
purple_notify_error(NULL, NULL, _("Cannot send launcher"), _("You dragged a desktop launcher. "
"Most likely you wanted to send whatever this launcher points to instead of this launcher"
" itself."));
break;
}
purple_desktop_item_unref(item);
return;
}
#endif /* _WIN32 */
/* Everything is fine, let's send */
serv_send_file(gc, who, filename);
g_free(filename);
}
g_list_free(files);
}
void pidgin_buddy_icon_get_scale_size(GdkPixbuf *buf, PurpleBuddyIconSpec *spec, PurpleIconScaleRules rules, int *width, int *height)
{
*width = gdk_pixbuf_get_width(buf);
*height = gdk_pixbuf_get_height(buf);
if ((spec == NULL) || !(spec->scale_rules & rules))
return;
purple_buddy_icon_get_scale_size(spec, width, height);
/* and now for some arbitrary sanity checks */
if(*width > 100)
*width = 100;
if(*height > 100)
*height = 100;
}
GdkPixbuf * pidgin_create_status_icon(PurpleStatusPrimitive prim, GtkWidget *w, const char *size)
{
GtkIconSize icon_size = gtk_icon_size_from_name(size);
GdkPixbuf *pixbuf = NULL;
if (prim == PURPLE_STATUS_UNAVAILABLE)
pixbuf = gtk_widget_render_icon (w, PIDGIN_STOCK_STATUS_BUSY,
icon_size, "GtkWidget");
else if (prim == PURPLE_STATUS_AWAY)
pixbuf = gtk_widget_render_icon (w, PIDGIN_STOCK_STATUS_AWAY,
icon_size, "GtkWidget");
else if (prim == PURPLE_STATUS_EXTENDED_AWAY)
pixbuf = gtk_widget_render_icon (w, PIDGIN_STOCK_STATUS_XA,
icon_size, "GtkWidget");
else if (prim == PURPLE_STATUS_INVISIBLE)
pixbuf = gtk_widget_render_icon (w, PIDGIN_STOCK_STATUS_INVISIBLE,
icon_size, "GtkWidget");
else if (prim == PURPLE_STATUS_OFFLINE)
pixbuf = gtk_widget_render_icon (w, PIDGIN_STOCK_STATUS_OFFLINE,
icon_size, "GtkWidget");
else
pixbuf = gtk_widget_render_icon (w, PIDGIN_STOCK_STATUS_AVAILABLE,
icon_size, "GtkWidget");
return pixbuf;
}
GdkPixbuf *
pidgin_create_prpl_icon(PurpleAccount *account, PidginPrplIconSize size)
{
PurplePlugin *prpl;
g_return_val_if_fail(account != NULL, NULL);
prpl = purple_find_prpl(purple_account_get_protocol_id(account));
if (prpl == NULL)
return NULL;
return pidgin_create_prpl_icon_from_prpl(prpl, size, account);
}
static void
menu_action_cb(GtkMenuItem *item, gpointer object)
{
gpointer data;
void (*callback)(gpointer, gpointer);
callback = g_object_get_data(G_OBJECT(item), "purplecallback");
data = g_object_get_data(G_OBJECT(item), "purplecallbackdata");
if (callback)
callback(object, data);
}
GtkWidget *
pidgin_append_menu_action(GtkWidget *menu, PurpleMenuAction *act,
gpointer object)
{
GtkWidget *menuitem;
if (act == NULL) {
return pidgin_separator(menu);
}
if (act->children == NULL) {
menuitem = gtk_menu_item_new_with_mnemonic(act->label);
if (act->callback != NULL) {
g_object_set_data(G_OBJECT(menuitem),
"purplecallback",
act->callback);
g_object_set_data(G_OBJECT(menuitem),
"purplecallbackdata",
act->data);
g_signal_connect(G_OBJECT(menuitem), "activate",
G_CALLBACK(menu_action_cb),
object);
} else {
gtk_widget_set_sensitive(menuitem, FALSE);
}
gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
} else {
GList *l = NULL;
GtkWidget *submenu = NULL;
GtkAccelGroup *group;
menuitem = gtk_menu_item_new_with_mnemonic(act->label);
gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
submenu = gtk_menu_new();
gtk_menu_item_set_submenu(GTK_MENU_ITEM(menuitem), submenu);
group = gtk_menu_get_accel_group(GTK_MENU(menu));
if (group) {
char *path = g_strdup_printf("%s/%s", GTK_MENU_ITEM(menuitem)->accel_path, act->label);
gtk_menu_set_accel_path(GTK_MENU(submenu), path);
g_free(path);
gtk_menu_set_accel_group(GTK_MENU(submenu), group);
}
for (l = act->children; l; l = l->next) {
PurpleMenuAction *act = (PurpleMenuAction *)l->data;
pidgin_append_menu_action(submenu, act, object);
}
g_list_free(act->children);
act->children = NULL;
}
purple_menu_action_free(act);
return menuitem;
}
#if GTK_CHECK_VERSION(2,3,0)
# define NEW_STYLE_COMPLETION
#endif
typedef struct
{
GtkWidget *entry;
GtkWidget *accountopt;
PidginFilterBuddyCompletionEntryFunc filter_func;
gpointer filter_func_user_data;
#ifdef NEW_STYLE_COMPLETION
GtkListStore *store;
#else
GCompletion *completion;
gboolean completion_started;
GList *log_items;
#endif /* NEW_STYLE_COMPLETION */
} PidginCompletionData;
#ifndef NEW_STYLE_COMPLETION
static gboolean
completion_entry_event(GtkEditable *entry, GdkEventKey *event,
PidginCompletionData *data)
{
int pos, end_pos;
if (event->type == GDK_KEY_PRESS && event->keyval == GDK_Tab)
{
gtk_editable_get_selection_bounds(entry, &pos, &end_pos);
if (data->completion_started &&
pos != end_pos && pos > 1 &&
end_pos == strlen(gtk_entry_get_text(GTK_ENTRY(entry))))
{
gtk_editable_select_region(entry, 0, 0);
gtk_editable_set_position(entry, -1);
return TRUE;
}
}
else if (event->type == GDK_KEY_PRESS && event->length > 0)
{
char *prefix, *nprefix;
gtk_editable_get_selection_bounds(entry, &pos, &end_pos);
if (data->completion_started &&
pos != end_pos && pos > 1 &&
end_pos == strlen(gtk_entry_get_text(GTK_ENTRY(entry))))
{
char *temp;
temp = gtk_editable_get_chars(entry, 0, pos);
prefix = g_strconcat(temp, event->string, NULL);
g_free(temp);
}
else if (pos == end_pos && pos > 1 &&
end_pos == strlen(gtk_entry_get_text(GTK_ENTRY(entry))))
{
prefix = g_strconcat(gtk_entry_get_text(GTK_ENTRY(entry)),
event->string, NULL);
}
else
return FALSE;
pos = strlen(prefix);
nprefix = NULL;
g_completion_complete(data->completion, prefix, &nprefix);
if (nprefix != NULL)
{
gtk_entry_set_text(GTK_ENTRY(entry), nprefix);
gtk_editable_set_position(entry, pos);
gtk_editable_select_region(entry, pos, -1);
data->completion_started = TRUE;
g_free(nprefix);
g_free(prefix);
return TRUE;
}
g_free(prefix);
}
return FALSE;
}
static void
destroy_completion_data(GtkWidget *w, PidginCompletionData *data)
{
g_list_foreach(data->completion->items, (GFunc)g_free, NULL);
g_completion_free(data->completion);
g_free(data);
}
#endif /* !NEW_STYLE_COMPLETION */
#ifdef NEW_STYLE_COMPLETION
static gboolean screenname_completion_match_func(GtkEntryCompletion *completion,
const gchar *key, GtkTreeIter *iter, gpointer user_data)
{
GtkTreeModel *model;
GValue val1;
GValue val2;
const char *tmp;
model = gtk_entry_completion_get_model (completion);
val1.g_type = 0;
gtk_tree_model_get_value(model, iter, 2, &val1);
tmp = g_value_get_string(&val1);
if (tmp != NULL && purple_str_has_prefix(tmp, key))
{
g_value_unset(&val1);
return TRUE;
}
g_value_unset(&val1);
val2.g_type = 0;
gtk_tree_model_get_value(model, iter, 3, &val2);
tmp = g_value_get_string(&val2);
if (tmp != NULL && purple_str_has_prefix(tmp, key))
{
g_value_unset(&val2);
return TRUE;
}
g_value_unset(&val2);
return FALSE;
}
static gboolean screenname_completion_match_selected_cb(GtkEntryCompletion *completion,
GtkTreeModel *model, GtkTreeIter *iter, PidginCompletionData *data)
{
GValue val;
GtkWidget *optmenu = data->accountopt;
PurpleAccount *account;
val.g_type = 0;
gtk_tree_model_get_value(model, iter, 1, &val);
gtk_entry_set_text(GTK_ENTRY(data->entry), g_value_get_string(&val));
g_value_unset(&val);
gtk_tree_model_get_value(model, iter, 4, &val);
account = g_value_get_pointer(&val);
g_value_unset(&val);
if (account == NULL)
return TRUE;
if (optmenu != NULL)
aop_option_menu_select_by_data(optmenu, account);
return TRUE;
}
static void
add_screenname_autocomplete_entry(GtkListStore *store, const char *buddy_alias, const char *contact_alias,
const PurpleAccount *account, const char *screenname)
{
GtkTreeIter iter;
gboolean completion_added = FALSE;
gchar *normalized_screenname;
gchar *tmp;
tmp = g_utf8_normalize(screenname, -1, G_NORMALIZE_DEFAULT);
normalized_screenname = g_utf8_casefold(tmp, -1);
g_free(tmp);
/* There's no sense listing things like: 'xxx "xxx"'
when the screenname and buddy alias match. */
if (buddy_alias && strcmp(buddy_alias, screenname)) {
char *completion_entry = g_strdup_printf("%s \"%s\"", screenname, buddy_alias);
char *tmp2 = g_utf8_normalize(buddy_alias, -1, G_NORMALIZE_DEFAULT);
tmp = g_utf8_casefold(tmp2, -1);
g_free(tmp2);
gtk_list_store_append(store, &iter);
gtk_list_store_set(store, &iter,
0, completion_entry,
1, screenname,
2, normalized_screenname,
3, tmp,
4, account,
-1);
g_free(completion_entry);
g_free(tmp);
completion_added = TRUE;
}
/* There's no sense listing things like: 'xxx "xxx"'
when the screenname and contact alias match. */
if (contact_alias && strcmp(contact_alias, screenname)) {
/* We don't want duplicates when the contact and buddy alias match. */
if (!buddy_alias || strcmp(contact_alias, buddy_alias)) {
char *completion_entry = g_strdup_printf("%s \"%s\"",
screenname, contact_alias);
char *tmp2 = g_utf8_normalize(contact_alias, -1, G_NORMALIZE_DEFAULT);
tmp = g_utf8_casefold(tmp2, -1);
g_free(tmp2);
gtk_list_store_append(store, &iter);
gtk_list_store_set(store, &iter,
0, completion_entry,
1, screenname,
2, normalized_screenname,
3, tmp,
4, account,
-1);
g_free(completion_entry);
g_free(tmp);
completion_added = TRUE;
}
}
if (completion_added == FALSE) {
/* Add the buddy's screenname. */
gtk_list_store_append(store, &iter);
gtk_list_store_set(store, &iter,
0, screenname,
1, screenname,
2, normalized_screenname,
3, NULL,
4, account,
-1);
}
g_free(normalized_screenname);
}
#endif /* NEW_STYLE_COMPLETION */
static void get_log_set_name(PurpleLogSet *set, gpointer value, PidginCompletionData *data)
{
PidginFilterBuddyCompletionEntryFunc filter_func = data->filter_func;
gpointer user_data = data->filter_func_user_data;
/* 1. Don't show buddies because we will have gotten them already.
* 2. The boxes that use this autocomplete code handle only IMs. */
if (!set->buddy && set->type == PURPLE_LOG_IM) {
PidginBuddyCompletionEntry entry;
entry.is_buddy = FALSE;
entry.entry.logged_buddy = set;
if (filter_func(&entry, user_data)) {
#ifdef NEW_STYLE_COMPLETION
add_screenname_autocomplete_entry(data->store,
NULL, NULL, set->account, set->name);
#else
/* Steal the name for the GCompletion. */
data->log_items = g_list_append(data->log_items, set->name);
set->name = set->normalized_name = NULL;
#endif /* NEW_STYLE_COMPLETION */
}
}
}
static void
add_completion_list(PidginCompletionData *data)
{
PurpleBlistNode *gnode, *cnode, *bnode;
PidginFilterBuddyCompletionEntryFunc filter_func = data->filter_func;
gpointer user_data = data->filter_func_user_data;
GHashTable *sets;
#ifdef NEW_STYLE_COMPLETION
gtk_list_store_clear(data->store);
#else
GList *item = g_list_append(NULL, NULL);
g_list_foreach(data->completion->items, (GFunc)g_free, NULL);
g_completion_clear_items(data->completion);
#endif /* NEW_STYLE_COMPLETION */
for (gnode = purple_get_blist()->root; gnode != NULL; gnode = gnode->next)
{
if (!PURPLE_BLIST_NODE_IS_GROUP(gnode))
continue;
for (cnode = gnode->child; cnode != NULL; cnode = cnode->next)
{
if (!PURPLE_BLIST_NODE_IS_CONTACT(cnode))
continue;
for (bnode = cnode->child; bnode != NULL; bnode = bnode->next)
{
PidginBuddyCompletionEntry entry;
entry.is_buddy = TRUE;
entry.entry.buddy = (PurpleBuddy *) bnode;
if (filter_func(&entry, user_data)) {
#ifdef NEW_STYLE_COMPLETION
add_screenname_autocomplete_entry(data->store,
((PurpleContact *)cnode)->alias,
purple_buddy_get_contact_alias(entry.entry.buddy),
entry.entry.buddy->account,
entry.entry.buddy->name
);
#else
item->data = g_strdup(entry.entry.buddy->name);
g_completion_add_items(data->completion, item);
#endif /* NEW_STYLE_COMPLETION */
}
}
}
}
#ifndef NEW_STYLE_COMPLETION
g_list_free(item);
data->log_items = NULL;
#endif /* NEW_STYLE_COMPLETION */
sets = purple_log_get_log_sets();
g_hash_table_foreach(sets, (GHFunc)get_log_set_name, data);
g_hash_table_destroy(sets);
#ifndef NEW_STYLE_COMPLETION
g_completion_add_items(data->completion, data->log_items);
g_list_free(data->log_items);
#endif /* NEW_STYLE_COMPLETION */
}
static void
screenname_autocomplete_destroyed_cb(GtkWidget *widget, gpointer data)
{
g_free(data);
purple_signals_disconnect_by_handle(widget);
}
static void
repopulate_autocomplete(gpointer something, gpointer data)
{
add_completion_list(data);
}
void
pidgin_setup_screenname_autocomplete_with_filter(GtkWidget *entry, GtkWidget *accountopt, PidginFilterBuddyCompletionEntryFunc filter_func, gpointer user_data)
{
PidginCompletionData *data;
#ifdef NEW_STYLE_COMPLETION
/* Store the displayed completion value, the screenname, the UTF-8 normalized & casefolded screenname,
* the UTF-8 normalized & casefolded value for comparison, and the account. */
GtkListStore *store;
GtkEntryCompletion *completion;
data = g_new0(PidginCompletionData, 1);
store = gtk_list_store_new(5, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_POINTER);
data->entry = entry;
data->accountopt = accountopt;
if (filter_func == NULL) {
data->filter_func = pidgin_screenname_autocomplete_default_filter;
data->filter_func_user_data = NULL;
} else {
data->filter_func = filter_func;
data->filter_func_user_data = user_data;
}
data->store = store;
add_completion_list(data);
/* Sort the completion list by screenname. */
gtk_tree_sortable_set_sort_column_id(GTK_TREE_SORTABLE(store),
1, GTK_SORT_ASCENDING);
completion = gtk_entry_completion_new();
gtk_entry_completion_set_match_func(completion, screenname_completion_match_func, NULL, NULL);
g_signal_connect(G_OBJECT(completion), "match-selected",
G_CALLBACK(screenname_completion_match_selected_cb), data);
gtk_entry_set_completion(GTK_ENTRY(entry), completion);
g_object_unref(completion);
gtk_entry_completion_set_model(completion, GTK_TREE_MODEL(store));
g_object_unref(store);
gtk_entry_completion_set_text_column(completion, 0);
#else /* !NEW_STYLE_COMPLETION */
data = g_new0(PidginCompletionData, 1);
data->entry = entry;
data->accountopt = accountopt;
if (filter_func == NULL) {
data->filter_func = pidgin_screenname_autocomplete_default_filter;
data->filter_func_user_data = NULL;
} else {
data->filter_func = filter_func;
data->filter_func_user_data = user_data;
}
data->completion = g_completion_new(NULL);
data->completion_started = FALSE;
add_completion_list(data);
g_completion_set_compare(data->completion, g_ascii_strncasecmp);
g_signal_connect(G_OBJECT(entry), "event",
G_CALLBACK(completion_entry_event), data);
g_signal_connect(G_OBJECT(entry), "destroy",
G_CALLBACK(destroy_completion_data), data);
#endif /* !NEW_STYLE_COMPLETION */
purple_signal_connect(purple_connections_get_handle(), "signed-on", entry,
PURPLE_CALLBACK(repopulate_autocomplete), data);
purple_signal_connect(purple_connections_get_handle(), "signed-off", entry,
PURPLE_CALLBACK(repopulate_autocomplete), data);
purple_signal_connect(purple_accounts_get_handle(), "account-added", entry,
PURPLE_CALLBACK(repopulate_autocomplete), data);
purple_signal_connect(purple_accounts_get_handle(), "account-removed", entry,
PURPLE_CALLBACK(repopulate_autocomplete), data);
g_signal_connect(G_OBJECT(entry), "destroy", G_CALLBACK(screenname_autocomplete_destroyed_cb), data);
}
gboolean
pidgin_screenname_autocomplete_default_filter(const PidginBuddyCompletionEntry *completion_entry, gpointer all_accounts) {
gboolean all = GPOINTER_TO_INT(all_accounts);
if (completion_entry->is_buddy) {
return all || purple_account_is_connected(completion_entry->entry.buddy->account);
} else {
return all || (completion_entry->entry.logged_buddy->account != NULL && purple_account_is_connected(completion_entry->entry.logged_buddy->account));
}
}
void
pidgin_setup_screenname_autocomplete(GtkWidget *entry, GtkWidget *accountopt, gboolean all) {
pidgin_setup_screenname_autocomplete_with_filter(entry, accountopt, pidgin_screenname_autocomplete_default_filter, GINT_TO_POINTER(all));
}
void pidgin_set_cursor(GtkWidget *widget, GdkCursorType cursor_type)
{
GdkCursor *cursor;
g_return_if_fail(widget != NULL);
if (widget->window == NULL)
return;
cursor = gdk_cursor_new(GDK_WATCH);
gdk_window_set_cursor(widget->window, cursor);
gdk_cursor_unref(cursor);
#if GTK_CHECK_VERSION(2,4,0)
gdk_display_flush(gdk_drawable_get_display(GDK_DRAWABLE(widget->window)));
#else
gdk_flush();
#endif
}
void pidgin_clear_cursor(GtkWidget *widget)
{
g_return_if_fail(widget != NULL);
if (widget->window == NULL)
return;
gdk_window_set_cursor(widget->window, NULL);
}
struct _icon_chooser {
GtkWidget *icon_filesel;
GtkWidget *icon_preview;
GtkWidget *icon_text;
void (*callback)(const char*,gpointer);
gpointer data;
};
#if !GTK_CHECK_VERSION(2,4,0) /* FILECHOOSER */
static void
icon_filesel_delete_cb(GtkWidget *w, struct _icon_chooser *dialog)
{
if (dialog->icon_filesel != NULL)
gtk_widget_destroy(dialog->icon_filesel);
if (dialog->callback)
dialog->callback(NULL, dialog->data);
g_free(dialog);
}
#endif /* FILECHOOSER */
#if GTK_CHECK_VERSION(2,4,0) /* FILECHOOSER */
static void
icon_filesel_choose_cb(GtkWidget *widget, gint response, struct _icon_chooser *dialog)
{
char *filename, *current_folder;
if (response != GTK_RESPONSE_ACCEPT) {
if (response == GTK_RESPONSE_CANCEL) {
gtk_widget_destroy(dialog->icon_filesel);
}
dialog->icon_filesel = NULL;
if (dialog->callback)
dialog->callback(NULL, dialog->data);
g_free(dialog);
return;
}
filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog->icon_filesel));
current_folder = gtk_file_chooser_get_current_folder(GTK_FILE_CHOOSER(dialog->icon_filesel));
if (current_folder != NULL) {
purple_prefs_set_path(PIDGIN_PREFS_ROOT "/filelocations/last_icon_folder", current_folder);
g_free(current_folder);
}
#else /* FILECHOOSER */
static void
icon_filesel_choose_cb(GtkWidget *w, struct _icon_chooser *dialog)
{
char *filename, *current_folder;
filename = g_strdup(gtk_file_selection_get_filename(
GTK_FILE_SELECTION(dialog->icon_filesel)));
/* If they typed in a directory, change there */
if (pidgin_check_if_dir(filename,
GTK_FILE_SELECTION(dialog->icon_filesel)))
{
g_free(filename);
return;
}
current_folder = g_path_get_dirname(filename);
if (current_folder != NULL) {
purple_prefs_set_path(PIDGIN_PREFS_ROOT "/filelocations/last_icon_folder", current_folder);
g_free(current_folder);
}
#endif /* FILECHOOSER */
if (dialog->callback)
dialog->callback(filename, dialog->data);
gtk_widget_destroy(dialog->icon_filesel);
g_free(filename);
g_free(dialog);
}
static void
#if GTK_CHECK_VERSION(2,4,0) /* FILECHOOSER */
icon_preview_change_cb(GtkFileChooser *widget, struct _icon_chooser *dialog)
#else /* FILECHOOSER */
icon_preview_change_cb(GtkTreeSelection *sel, struct _icon_chooser *dialog)
#endif /* FILECHOOSER */
{
GdkPixbuf *pixbuf, *scale;
int height, width;
char *basename, *markup, *size;
struct stat st;
char *filename;
#if GTK_CHECK_VERSION(2,4,0) /* FILECHOOSER */
filename = gtk_file_chooser_get_preview_filename(
GTK_FILE_CHOOSER(dialog->icon_filesel));
#else /* FILECHOOSER */
filename = g_strdup(gtk_file_selection_get_filename(
GTK_FILE_SELECTION(dialog->icon_filesel)));
#endif /* FILECHOOSER */
if (!filename || g_stat(filename, &st) || !(pixbuf = gdk_pixbuf_new_from_file(filename, NULL)))
{
gtk_image_set_from_pixbuf(GTK_IMAGE(dialog->icon_preview), NULL);
gtk_label_set_markup(GTK_LABEL(dialog->icon_text), "");
g_free(filename);
return;
}
width = gdk_pixbuf_get_width(pixbuf);
height = gdk_pixbuf_get_height(pixbuf);
basename = g_path_get_basename(filename);
size = purple_str_size_to_units(st.st_size);
markup = g_strdup_printf(_("<b>File:</b> %s\n"
"<b>File size:</b> %s\n"
"<b>Image size:</b> %dx%d"),
basename, size, width, height);
scale = gdk_pixbuf_scale_simple(pixbuf, width * 50 / height,
50, GDK_INTERP_BILINEAR);
gtk_image_set_from_pixbuf(GTK_IMAGE(dialog->icon_preview), scale);
gtk_label_set_markup(GTK_LABEL(dialog->icon_text), markup);
g_object_unref(G_OBJECT(pixbuf));
g_object_unref(G_OBJECT(scale));
g_free(filename);
g_free(basename);
g_free(size);
g_free(markup);
}
GtkWidget *pidgin_buddy_icon_chooser_new(GtkWindow *parent, void(*callback)(const char *, gpointer), gpointer data) {
struct _icon_chooser *dialog = g_new0(struct _icon_chooser, 1);
#if GTK_CHECK_VERSION(2,4,0) /* FILECHOOSER */
GtkWidget *vbox;
#else
GtkWidget *hbox;
GtkWidget *tv;
GtkTreeSelection *sel;
#endif /* FILECHOOSER */
const char *current_folder;
dialog->callback = callback;
dialog->data = data;
if (dialog->icon_filesel != NULL) {
gtk_window_present(GTK_WINDOW(dialog->icon_filesel));
return NULL;
}
current_folder = purple_prefs_get_path(PIDGIN_PREFS_ROOT "/filelocations/last_icon_folder");
#if GTK_CHECK_VERSION(2,4,0) /* FILECHOOSER */
dialog->icon_filesel = gtk_file_chooser_dialog_new(_("Buddy Icon"),
parent,
GTK_FILE_CHOOSER_ACTION_OPEN,
GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT,
NULL);
gtk_dialog_set_default_response(GTK_DIALOG(dialog->icon_filesel), GTK_RESPONSE_ACCEPT);
if ((current_folder != NULL) && (*current_folder != '\0'))
gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(dialog->icon_filesel),
current_folder);
dialog->icon_preview = gtk_image_new();
dialog->icon_text = gtk_label_new(NULL);
vbox = gtk_vbox_new(FALSE, PIDGIN_HIG_BOX_SPACE);
gtk_widget_set_size_request(GTK_WIDGET(vbox), -1, 50);
gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(dialog->icon_preview), TRUE, FALSE, 0);
gtk_box_pack_end(GTK_BOX(vbox), GTK_WIDGET(dialog->icon_text), FALSE, FALSE, 0);
gtk_widget_show_all(vbox);
gtk_file_chooser_set_preview_widget(GTK_FILE_CHOOSER(dialog->icon_filesel), vbox);
gtk_file_chooser_set_preview_widget_active(GTK_FILE_CHOOSER(dialog->icon_filesel), TRUE);
gtk_file_chooser_set_use_preview_label(GTK_FILE_CHOOSER(dialog->icon_filesel), FALSE);
g_signal_connect(G_OBJECT(dialog->icon_filesel), "update-preview",
G_CALLBACK(icon_preview_change_cb), dialog);
g_signal_connect(G_OBJECT(dialog->icon_filesel), "response",
G_CALLBACK(icon_filesel_choose_cb), dialog);
icon_preview_change_cb(NULL, dialog);
#else /* FILECHOOSER */
dialog->icon_filesel = gtk_file_selection_new(_("Buddy Icon"));
dialog->icon_preview = gtk_image_new();
dialog->icon_text = gtk_label_new(NULL);
if ((current_folder != NULL) && (*current_folder != '\0'))
gtk_file_selection_set_filename(GTK_FILE_SELECTION(dialog->icon_filesel),
current_folder);
gtk_widget_set_size_request(GTK_WIDGET(dialog->icon_preview),-1, 50);
hbox = gtk_hbox_new(FALSE, PIDGIN_HIG_BOX_SPACE);
gtk_box_pack_start(
GTK_BOX(GTK_FILE_SELECTION(dialog->icon_filesel)->main_vbox),
hbox, FALSE, FALSE, 0);
gtk_box_pack_end(GTK_BOX(hbox), dialog->icon_preview,
FALSE, FALSE, 0);
gtk_box_pack_end(GTK_BOX(hbox), dialog->icon_text, FALSE, FALSE, 0);
tv = GTK_FILE_SELECTION(dialog->icon_filesel)->file_list;
sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(tv));
g_signal_connect(G_OBJECT(sel), "changed",
G_CALLBACK(icon_preview_change_cb), dialog);
g_signal_connect(
G_OBJECT(GTK_FILE_SELECTION(dialog->icon_filesel)->ok_button),
"clicked",
G_CALLBACK(icon_filesel_choose_cb), dialog);
g_signal_connect(
G_OBJECT(GTK_FILE_SELECTION(dialog->icon_filesel)->cancel_button),
"clicked",
G_CALLBACK(icon_filesel_delete_cb), dialog);
g_signal_connect(G_OBJECT(dialog->icon_filesel), "destroy",
G_CALLBACK(icon_filesel_delete_cb), dialog);
#endif /* FILECHOOSER */
return dialog->icon_filesel;
}
#if GTK_CHECK_VERSION(2,2,0)
static gboolean
str_array_match(char **a, char **b)
{
int i, j;
if (!a || !b)
return FALSE;
for (i = 0; a[i] != NULL; i++)
for (j = 0; b[j] != NULL; j++)
if (!g_ascii_strcasecmp(a[i], b[j]))
return TRUE;
return FALSE;
}
#endif
gpointer
pidgin_convert_buddy_icon(PurplePlugin *plugin, const char *path, size_t *len)
{
PurplePluginProtocolInfo *prpl_info;
#if GTK_CHECK_VERSION(2,2,0)
char **prpl_formats;
int width, height;
char **pixbuf_formats = NULL;
GdkPixbufFormat *format;
GdkPixbuf *pixbuf;
#if !GTK_CHECK_VERSION(2,4,0)
GdkPixbufLoader *loader;
#endif
#endif
gchar *contents;
gsize length;
prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(plugin);
g_return_val_if_fail(prpl_info->icon_spec.format != NULL, NULL);
#if GTK_CHECK_VERSION(2,2,0)
#if GTK_CHECK_VERSION(2,4,0)
format = gdk_pixbuf_get_file_info(path, &width, &height);
#else
loader = gdk_pixbuf_loader_new();
if (g_file_get_contents(path, &contents, &length, NULL)) {
gdk_pixbuf_loader_write(loader, contents, length, NULL);
g_free(contents);
}
gdk_pixbuf_loader_close(loader, NULL);
pixbuf = gdk_pixbuf_loader_get_pixbuf(loader);
width = gdk_pixbuf_get_width(pixbuf);
height = gdk_pixbuf_get_height(pixbuf);
format = gdk_pixbuf_loader_get_format(loader);
g_object_unref(G_OBJECT(loader));
#endif
if (format == NULL)
return NULL;
pixbuf_formats = gdk_pixbuf_format_get_extensions(format);
prpl_formats = g_strsplit(prpl_info->icon_spec.format,",",0);
if (str_array_match(pixbuf_formats, prpl_formats) && /* This is an acceptable format AND */
(!(prpl_info->icon_spec.scale_rules & PURPLE_ICON_SCALE_SEND) || /* The prpl doesn't scale before it sends OR */
(prpl_info->icon_spec.min_width <= width &&
prpl_info->icon_spec.max_width >= width &&
prpl_info->icon_spec.min_height <= height &&
prpl_info->icon_spec.max_height >= height))) /* The icon is the correct size */
#endif
{
#if GTK_CHECK_VERSION(2,2,0)
g_strfreev(prpl_formats);
g_strfreev(pixbuf_formats);
#endif
/* We don't need to scale the image. */
contents = NULL;
if (!g_file_get_contents(path, &contents, &length, NULL))
{
g_free(contents);
#if GTK_CHECK_VERSION(2,2,0) && !GTK_CHECK_VERSION(2,4,0)
g_object_unref(G_OBJECT(pixbuf));
#endif
return NULL;
}
}
#if GTK_CHECK_VERSION(2,2,0)
else
{
int i;
GError *error = NULL;
GdkPixbuf *scale;
gboolean success = FALSE;
char *filename = NULL;
g_strfreev(pixbuf_formats);
pixbuf = gdk_pixbuf_new_from_file(path, &error);
if (error) {
purple_debug_error("buddyicon", "Could not open icon for conversion: %s\n", error->message);
g_error_free(error);
g_strfreev(prpl_formats);
return NULL;
}
if ((prpl_info->icon_spec.scale_rules & PURPLE_ICON_SCALE_SEND) &&
(width < prpl_info->icon_spec.min_width ||
width > prpl_info->icon_spec.max_width ||
height < prpl_info->icon_spec.min_height ||
height > prpl_info->icon_spec.max_height))
{
int new_width = width;
int new_height = height;
purple_buddy_icon_get_scale_size(&prpl_info->icon_spec, &new_width, &new_height);
scale = gdk_pixbuf_scale_simple(pixbuf, new_width, new_height,
GDK_INTERP_HYPER);
g_object_unref(G_OBJECT(pixbuf));
pixbuf = scale;
}
for (i = 0; prpl_formats[i]; i++) {
FILE *fp;
g_free(filename);
fp = purple_mkstemp(&filename, TRUE);
if (!fp)
{
g_free(filename);
return NULL;
}
fclose(fp);
purple_debug_info("buddyicon", "Converting buddy icon to %s as %s\n", prpl_formats[i], filename);
/* The "compression" param wasn't supported until gdk-pixbuf 2.8.
* Using it in previous versions causes the save to fail (and an assert message). */
if ((gdk_pixbuf_major_version > 2 || (gdk_pixbuf_major_version == 2
&& gdk_pixbuf_minor_version >= 8))
&& strcmp(prpl_formats[i], "png") == 0) {
if (gdk_pixbuf_save(pixbuf, filename, prpl_formats[i],
&error, "compression", "9", NULL)) {
success = TRUE;
break;
}
} else if (gdk_pixbuf_save(pixbuf, filename, prpl_formats[i],
&error, NULL)) {
success = TRUE;
break;
}
/* The NULL checking is necessary due to this bug:
* http://bugzilla.gnome.org/show_bug.cgi?id=405539 */
purple_debug_warning("buddyicon", "Could not convert to %s: %s\n", prpl_formats[i],
(error && error->message) ? error->message : "Unknown error");
g_error_free(error);
error = NULL;
}
g_strfreev(prpl_formats);
g_object_unref(G_OBJECT(pixbuf));
if (!success) {
purple_debug_error("buddyicon", "Could not convert icon to usable format.\n");
return NULL;
}
contents = NULL;
if (!g_file_get_contents(filename, &contents, &length, NULL))
{
purple_debug_error("buddyicon",
"Could not read '%s', which we just wrote to disk.\n",
filename);
g_free(contents);
g_free(filename);
return NULL;
}
g_unlink(filename);
g_free(filename);
}
/* Check the image size */
/*
* TODO: If the file is too big, it would be cool if we checked if
* the prpl supported jpeg, and then we could convert to that
* and use a lower quality setting.
*/
if ((prpl_info->icon_spec.max_filesize != 0) &&
(length > prpl_info->icon_spec.max_filesize))
{
gchar *tmp;
tmp = g_strdup_printf(_("The file '%s' is too large for %s. Please try a smaller image.\n"),
path, plugin->info->name);
purple_notify_error(NULL, _("Icon Error"),
_("Could not set icon"), tmp);
purple_debug_info("buddyicon",
"'%s' was converted to an image which is %" G_GSIZE_FORMAT
" bytes, but the maximum icon size for %s is %" G_GSIZE_FORMAT
" bytes\n", path, length, plugin->info->name,
prpl_info->icon_spec.max_filesize);
g_free(tmp);
return NULL;
}
if (len)
*len = length;
return contents;
#else
/*
* The chosen icon wasn't the right size, and we're using
* GTK+ 2.0 so we can't scale it.
*/
return NULL;
#endif
}
#if !GTK_CHECK_VERSION(2,6,0)
static void
_gdk_file_scale_size_prepared_cb (GdkPixbufLoader *loader,
int width,
int height,
gpointer data)
{
struct {
gint width;
gint height;
gboolean preserve_aspect_ratio;
} *info = data;
g_return_if_fail (width > 0 && height > 0);
if (info->preserve_aspect_ratio &&
(info->width > 0 || info->height > 0)) {
if (info->width < 0)
{
width = width * (double)info->height/(double)height;
height = info->height;
}
else if (info->height < 0)
{
height = height * (double)info->width/(double)width;
width = info->width;
}
else if ((double)height * (double)info->width >
(double)width * (double)info->height) {
width = 0.5 + (double)width * (double)info->height / (double)height;
height = info->height;
} else {
height = 0.5 + (double)height * (double)info->width / (double)width;
width = info->width;
}
} else {
if (info->width > 0)
width = info->width;
if (info->height > 0)
height = info->height;
}
#if GTK_CHECK_VERSION(2,2,0) /* 2.0 users are going to have very strangely sized things */
gdk_pixbuf_loader_set_size (loader, width, height);
#else
#warning nosnilmot could not be bothered to fix this properly for you
#warning ... good luck ... your images may end up strange sizes
#endif
}
GdkPixbuf *
gdk_pixbuf_new_from_file_at_scale(const char *filename, int width, int height,
gboolean preserve_aspect_ratio,
GError **error)
{
GdkPixbufLoader *loader;
GdkPixbuf *pixbuf;
guchar buffer [4096];
int length;
FILE *f;
struct {
gint width;
gint height;
gboolean preserve_aspect_ratio;
} info;
GdkPixbufAnimation *animation;
GdkPixbufAnimationIter *iter;
gboolean has_frame;
g_return_val_if_fail (filename != NULL, NULL);
g_return_val_if_fail (width > 0 || width == -1, NULL);
g_return_val_if_fail (height > 0 || height == -1, NULL);
f = g_fopen (filename, "rb");
if (!f) {
gint save_errno = errno;
gchar *display_name = g_filename_to_utf8 (filename, -1, NULL, NULL, NULL);
g_set_error (error, G_FILE_ERROR, g_file_error_from_errno (save_errno),
_("Failed to open file '%s': %s"),
display_name ? display_name : "(unknown)",
g_strerror (save_errno));
g_free (display_name);
return NULL;
}
loader = gdk_pixbuf_loader_new ();
info.width = width;
info.height = height;
info.preserve_aspect_ratio = preserve_aspect_ratio;
g_signal_connect (loader, "size-prepared", G_CALLBACK (_gdk_file_scale_size_prepared_cb), &info);
has_frame = FALSE;
while (!has_frame && !feof (f) && !ferror (f)) {
length = fread (buffer, 1, sizeof (buffer), f);
if (length > 0)
if (!gdk_pixbuf_loader_write (loader, buffer, length, error)) {
gdk_pixbuf_loader_close (loader, NULL);
fclose (f);
g_object_unref (loader);
return NULL;
}
animation = gdk_pixbuf_loader_get_animation (loader);
if (animation) {
iter = gdk_pixbuf_animation_get_iter (animation, 0);
if (!gdk_pixbuf_animation_iter_on_currently_loading_frame (iter)) {
has_frame = TRUE;
}
g_object_unref (iter);
}
}
fclose (f);
if (!gdk_pixbuf_loader_close (loader, error) && !has_frame) {
g_object_unref (loader);
return NULL;
}
pixbuf = gdk_pixbuf_loader_get_pixbuf (loader);
if (!pixbuf) {
gchar *display_name = g_filename_to_utf8 (filename, -1, NULL, NULL, NULL);
g_object_unref (loader);
g_set_error (error, GDK_PIXBUF_ERROR, GDK_PIXBUF_ERROR_FAILED,
_("Failed to load image '%s': reason not known, probably a corrupt image file"),
display_name ? display_name : "(unknown)");
g_free (display_name);
return NULL;
}
g_object_ref (pixbuf);
g_object_unref (loader);
return pixbuf;
}
#endif /* ! Gtk 2.6.0 */
void pidgin_set_custom_buddy_icon(PurpleAccount *account, const char *who, const char *filename)
{
PurpleBuddy *buddy;
PurpleContact *contact;
gpointer data = NULL;
size_t len = 0;
buddy = purple_find_buddy(account, who);
if (!buddy) {
purple_debug_info("custom-icon", "You can only set custom icon for someone in your buddylist.\n");
return;
}
contact = purple_buddy_get_contact(buddy);
if (filename) {
const char *prpl_id = purple_account_get_protocol_id(account);
PurplePlugin *prpl = purple_find_prpl(prpl_id);
data = pidgin_convert_buddy_icon(prpl, filename, &len);
/* We don't want to delete the old icon if the new one didn't load. */
if (data == NULL)
return;
}
purple_buddy_icons_set_custom_icon(contact, data, len);
}
char *pidgin_make_pretty_arrows(const char *str)
{
char *ret;
char **split = g_strsplit(str, "->", -1);
ret = g_strjoinv("\342\207\250", split);
g_strfreev(split);
split = g_strsplit(ret, "<-", -1);
g_free(ret);
ret = g_strjoinv("\342\207\246", split);
g_strfreev(split);
return ret;
}
void pidgin_set_urgent(GtkWindow *window, gboolean urgent)
{
#if GTK_CHECK_VERSION(2,8,0)
gtk_window_set_urgency_hint(window, urgent);
#elif defined _WIN32
winpidgin_window_flash(window, urgent);
#else
GdkWindow *gdkwin;
XWMHints *hints;
g_return_if_fail(window != NULL);
gdkwin = GTK_WIDGET(window)->window;
g_return_if_fail(gdkwin != NULL);
hints = XGetWMHints(GDK_WINDOW_XDISPLAY(gdkwin),
GDK_WINDOW_XWINDOW(gdkwin));
if(!hints)
hints = XAllocWMHints();
if (urgent)
hints->flags |= XUrgencyHint;
else
hints->flags &= ~XUrgencyHint;
XSetWMHints(GDK_WINDOW_XDISPLAY(gdkwin),
GDK_WINDOW_XWINDOW(gdkwin), hints);
XFree(hints);
#endif
}
GSList *minidialogs = NULL;
static void *
pidgin_utils_get_handle()
{
static int handle;
return &handle;
}
static void connection_signed_off_cb(PurpleConnection *gc)
{
GSList *list;
for (list = minidialogs; list; list = list->next) {
if (g_object_get_data(G_OBJECT(list->data), "gc") == gc) {
gtk_widget_destroy(GTK_WIDGET(list->data));
}
}
}
static void alert_killed_cb(GtkWidget *widget)
{
minidialogs = g_slist_remove(minidialogs, widget);
}
void *pidgin_make_mini_dialog(PurpleConnection *gc, const char *icon_name,
const char *primary, const char *secondary,
void *user_data, ...)
{
GtkWidget *vbox;
GtkWidget *hbox;
GtkWidget *hbox2;
GtkWidget *label;
GtkWidget *button;
GtkWidget *img = NULL;
GtkSizeGroup *sg = gtk_size_group_new(GTK_SIZE_GROUP_BOTH);
char label_text[2048];
const char *button_text;
GCallback callback;
char *primary_esc, *secondary_esc = NULL;
va_list args;
static gboolean first_call = TRUE;
img = gtk_image_new_from_stock(icon_name, gtk_icon_size_from_name(PIDGIN_ICON_SIZE_TANGO_EXTRA_SMALL));
gtk_misc_set_alignment(GTK_MISC(img), 0, 0);
vbox = gtk_vbox_new(FALSE,0);
gtk_container_set_border_width(GTK_CONTAINER(vbox), PIDGIN_HIG_BOX_SPACE);
g_object_set_data(G_OBJECT(vbox), "gc" ,gc);
minidialogs = g_slist_prepend(minidialogs, vbox);
g_signal_connect(G_OBJECT(vbox), "destroy", G_CALLBACK(alert_killed_cb), NULL);
if (first_call) {
first_call = FALSE;
purple_signal_connect(purple_connections_get_handle(), "signed-off",
pidgin_utils_get_handle(),
PURPLE_CALLBACK(connection_signed_off_cb), NULL);
}
hbox = gtk_hbox_new(FALSE, 0);
gtk_container_add(GTK_CONTAINER(vbox), hbox);
if (img != NULL)
gtk_box_pack_start(GTK_BOX(hbox), img, FALSE, FALSE, 0);
primary_esc = g_markup_escape_text(primary, -1);
if (secondary)
secondary_esc = g_markup_escape_text(secondary, -1);
g_snprintf(label_text, sizeof(label_text),
"<span weight=\"bold\" size=\"smaller\">%s</span>%s<span size=\"smaller\">%s</span>",
primary_esc, secondary ? "\n" : "", secondary_esc ? secondary_esc : "");
g_free(primary_esc);
g_free(secondary_esc);
label = gtk_label_new(NULL);
gtk_widget_set_size_request(label, purple_prefs_get_int(PIDGIN_PREFS_ROOT "/blist/width")-25,-1);
gtk_label_set_markup(GTK_LABEL(label), label_text);
gtk_label_set_line_wrap(GTK_LABEL(label), TRUE);
gtk_misc_set_alignment(GTK_MISC(label), 0, 0);
gtk_box_pack_start(GTK_BOX(hbox), label, TRUE, TRUE, 0);
hbox2 = gtk_hbox_new(FALSE, PIDGIN_HIG_BOX_SPACE);
gtk_box_pack_start(GTK_BOX(vbox), hbox2, FALSE, FALSE, 0);
va_start(args, user_data);
while ((button_text = va_arg(args, char*))) {
callback = va_arg(args, GCallback);
button = gtk_button_new();
if (callback)
g_signal_connect_swapped(G_OBJECT(button), "clicked", callback, user_data);
g_signal_connect_swapped(G_OBJECT(button), "clicked", G_CALLBACK(gtk_widget_destroy), vbox);
hbox = gtk_hbox_new(FALSE, 0);
gtk_container_add(GTK_CONTAINER(button), hbox);
gtk_container_set_border_width(GTK_CONTAINER(hbox), 3);
g_snprintf(label_text, sizeof(label_text),
"<span size=\"smaller\">%s</span>", button_text);
label = gtk_label_new(NULL);
gtk_label_set_markup_with_mnemonic(GTK_LABEL(label), label_text);
gtk_misc_set_alignment(GTK_MISC(label), 0.5, 0.5);
gtk_box_pack_start(GTK_BOX(hbox), label, TRUE, TRUE, 0);
gtk_box_pack_end(GTK_BOX(hbox2), button, FALSE, FALSE, 0);
gtk_size_group_add_widget(sg, button);
}
va_end(args);
return vbox;
}
/*
* "This is so dead sexy."
* "Two thumbs up."
* "Best movie of the year."
*
* This is the function that handles CTRL+F searching in the buddy list.
* It finds the top-most buddy/group/chat/whatever containing the
* entered string.
*
* It's somewhat ineffecient, because we strip all the HTML from the
* "name" column of the buddy list (because the GtkTreeModel does not
* contain the screen name in a non-markedup format). But the alternative
* is to add an extra column to the GtkTreeModel. And this function is
* used rarely, so it shouldn't matter TOO much.
*/
gboolean pidgin_tree_view_search_equal_func(GtkTreeModel *model, gint column,
const gchar *key, GtkTreeIter *iter, gpointer data)
{
gchar *enteredstring;
gchar *tmp;
gchar *withmarkup;
gchar *nomarkup;
gchar *normalized;
gboolean result;
size_t i;
size_t len;
PangoLogAttr *log_attrs;
gchar *word;
if (g_ascii_strcasecmp(key, "Global Thermonuclear War") == 0)
{
purple_notify_info(NULL, "WOPR",
"Wouldn't you prefer a nice game of chess?", NULL);
return FALSE;
}
gtk_tree_model_get(model, iter, column, &withmarkup, -1);
if (withmarkup == NULL) /* This is probably a separator */
return TRUE;
tmp = g_utf8_normalize(key, -1, G_NORMALIZE_DEFAULT);
enteredstring = g_utf8_casefold(tmp, -1);
g_free(tmp);
nomarkup = purple_markup_strip_html(withmarkup);
tmp = g_utf8_normalize(nomarkup, -1, G_NORMALIZE_DEFAULT);
g_free(nomarkup);
normalized = g_utf8_casefold(tmp, -1);
g_free(tmp);
if (purple_str_has_prefix(normalized, enteredstring))
{
g_free(withmarkup);
g_free(enteredstring);
g_free(normalized);
return FALSE;
}
/* Use Pango to separate by words. */
len = g_utf8_strlen(normalized, -1);
log_attrs = g_new(PangoLogAttr, len + 1);
pango_get_log_attrs(normalized, strlen(normalized), -1, NULL, log_attrs, len + 1);
word = normalized;
result = TRUE;
for (i = 0; i < (len - 1) ; i++)
{
if (log_attrs[i].is_word_start &&
purple_str_has_prefix(word, enteredstring))
{
result = FALSE;
break;
}
word = g_utf8_next_char(word);
}
g_free(log_attrs);
/* The non-Pango version. */
#if 0
word = normalized;
result = TRUE;
while (word[0] != '\0')
{
gunichar c = g_utf8_get_char(word);
if (!g_unichar_isalnum(c))
{
word = g_utf8_find_next_char(word, NULL);
if (purple_str_has_prefix(word, enteredstring))
{
result = FALSE;
break;
}
}
else
word = g_utf8_find_next_char(word, NULL);
}
#endif
g_free(withmarkup);
g_free(enteredstring);
g_free(normalized);
return result;
}
gboolean pidgin_gdk_pixbuf_is_opaque(GdkPixbuf *pixbuf) {
int width, height, rowstride, i;
unsigned char *pixels;
unsigned char *row;
if (!gdk_pixbuf_get_has_alpha(pixbuf))
return TRUE;
width = gdk_pixbuf_get_width (pixbuf);
height = gdk_pixbuf_get_height (pixbuf);
rowstride = gdk_pixbuf_get_rowstride (pixbuf);
pixels = gdk_pixbuf_get_pixels (pixbuf);
row = pixels;
for (i = 3; i < rowstride; i+=4) {
if (row[i] < 0xfe)
return FALSE;
}
for (i = 1; i < height - 1; i++) {
row = pixels + (i*rowstride);
if (row[3] < 0xfe || row[rowstride-1] < 0xfe) {
return FALSE;
}
}
row = pixels + ((height-1) * rowstride);
for (i = 3; i < rowstride; i+=4) {
if (row[i] < 0xfe)
return FALSE;
}
return TRUE;
}
void pidgin_gdk_pixbuf_make_round(GdkPixbuf *pixbuf) {
int width, height, rowstride;
guchar *pixels;
if (!gdk_pixbuf_get_has_alpha(pixbuf))
return;
width = gdk_pixbuf_get_width(pixbuf);
height = gdk_pixbuf_get_height(pixbuf);
rowstride = gdk_pixbuf_get_rowstride(pixbuf);
pixels = gdk_pixbuf_get_pixels(pixbuf);
if (width < 6 || height < 6)
return;
/* Top left */
pixels[3] = 0;
pixels[7] = 0x80;
pixels[11] = 0xC0;
pixels[rowstride + 3] = 0x80;
pixels[rowstride * 2 + 3] = 0xC0;
/* Top right */
pixels[width * 4 - 1] = 0;
pixels[width * 4 - 5] = 0x80;
pixels[width * 4 - 9] = 0xC0;
pixels[rowstride + (width * 4) - 1] = 0x80;
pixels[(2 * rowstride) + (width * 4) - 1] = 0xC0;
/* Bottom left */
pixels[(height - 1) * rowstride + 3] = 0;
pixels[(height - 1) * rowstride + 7] = 0x80;
pixels[(height - 1) * rowstride + 11] = 0xC0;
pixels[(height - 2) * rowstride + 3] = 0x80;
pixels[(height - 3) * rowstride + 3] = 0xC0;
/* Bottom right */
pixels[height * rowstride - 1] = 0;
pixels[(height - 1) * rowstride - 1] = 0x80;
pixels[(height - 2) * rowstride - 1] = 0xC0;
pixels[height * rowstride - 5] = 0x80;
pixels[height * rowstride - 9] = 0xC0;
}
const char *pidgin_get_dim_grey_string(GtkWidget *widget) {
static char dim_grey_string[8] = "";
GtkStyle *style;
if (!widget)
return "dim grey";
style = gtk_widget_get_style(widget);
if (!style)
return "dim grey";
snprintf(dim_grey_string, sizeof(dim_grey_string), "#%02x%02x%02x",
style->text_aa[GTK_STATE_NORMAL].red >> 8,
style->text_aa[GTK_STATE_NORMAL].green >> 8,
style->text_aa[GTK_STATE_NORMAL].blue >> 8);
return dim_grey_string;
}
#if !GTK_CHECK_VERSION(2,2,0)
GtkTreePath *
gtk_tree_path_new_from_indices (gint first_index, ...)
{
int arg;
va_list args;
GtkTreePath *path;
path = gtk_tree_path_new ();
va_start (args, first_index);
arg = first_index;
while (arg != -1)
{
gtk_tree_path_append_index (path, arg);
arg = va_arg (args, gint);
}
va_end (args);
return path;
}
#endif
static void
combo_box_changed_cb(GtkComboBox *combo_box, GtkEntry *entry)
{
char *text = gtk_combo_box_get_active_text(combo_box);
gtk_entry_set_text(entry, text ? text : "");
g_free(text);
}
static gboolean
entry_key_pressed_cb(GtkWidget *entry, GdkEventKey *key, GtkComboBox *combo)
{
if (key->keyval == GDK_Down || key->keyval == GDK_Up) {
gtk_combo_box_popup(combo);
return TRUE;
}
return FALSE;
}
GtkWidget *
pidgin_text_combo_box_entry_new(const char *default_item, GList *items)
{
GtkComboBox *ret = NULL;
GtkWidget *the_entry = NULL;
ret = GTK_COMBO_BOX(gtk_combo_box_new_text());
the_entry = gtk_entry_new();
gtk_container_add(GTK_CONTAINER(ret), the_entry);
if (default_item)
gtk_entry_set_text(GTK_ENTRY(the_entry), default_item);
for (; items != NULL ; items = items->next) {
char *text = items->data;
if (text && *text)
gtk_combo_box_append_text(ret, text);
}
g_signal_connect(G_OBJECT(ret), "changed", (GCallback)combo_box_changed_cb, the_entry);
g_signal_connect_after(G_OBJECT(the_entry), "key-press-event", G_CALLBACK(entry_key_pressed_cb), ret);
return GTK_WIDGET(ret);
}
const char *pidgin_text_combo_box_entry_get_text(GtkWidget *widget)
{
return gtk_entry_get_text(GTK_ENTRY(GTK_BIN((widget))->child));
}
void pidgin_text_combo_box_entry_set_text(GtkWidget *widget, const char *text)
{
gtk_entry_set_text(GTK_ENTRY(GTK_BIN((widget))->child), (text));
}