diff -Npur pidgin-2.6.6-old/pidgin/gtkutils.c pidgin-2.6.6/pidgin/gtkutils.c
--- pidgin-2.6.6-old/pidgin/gtkutils.c	2010-02-16 17:34:06.000000000 +0800
+++ pidgin-2.6.6/pidgin/gtkutils.c	2014-03-14 15:20:46.252205865 +0800
@@ -3578,40 +3578,36 @@ copy_email_address(GtkIMHtml *imhtml, Gt
 }
 
 static void
-file_open_uri(GtkIMHtml *imhtml, const char *uri)
+open_file(GtkIMHtml *imhtml, const char *filename)
 {
 	/* Copied from gtkft.c:open_button_cb */
 #ifdef _WIN32
 	/* If using Win32... */
 	int code;
-	if (G_WIN32_HAVE_WIDECHAR_API()) {
-		wchar_t *wc_filename = g_utf8_to_utf16(
-				uri, -1, NULL, NULL, NULL);
-
-		code = (int)ShellExecuteW(NULL, NULL, wc_filename, NULL, NULL,
-				SW_SHOW);
-
-		g_free(wc_filename);
-	} else {
-		char *l_filename = g_locale_from_utf8(
-				uri, -1, NULL, NULL, NULL);
-
-		code = (int)ShellExecuteA(NULL, NULL, l_filename, NULL, NULL,
-				SW_SHOW);
-
-		g_free(l_filename);
-	}
+	/* Escape URI by replacing double-quote with 2 double-quotes. */
+	gchar *escaped = purple_strreplace(filename, "\"", "\"\"");
+	gchar *param = g_strconcat("/select,\"", escaped, "\"", NULL);
+	wchar_t *wc_param = g_utf8_to_utf16(param, -1, NULL, NULL, NULL);
+
+	/* TODO: Better to use SHOpenFolderAndSelectItems()? */
+	code = (int)ShellExecuteW(NULL, L"OPEN", L"explorer.exe", wc_param, NULL, SW_NORMAL);
+
+	g_free(wc_param);
+	g_free(param);
+	g_free(escaped);
 
 	if (code == SE_ERR_ASSOCINCOMPLETE || code == SE_ERR_NOASSOC)
 	{
-		purple_notify_error(imhtml, NULL,
-				_("There is no application configured to open this type of file."), NULL);
+		purple_notify_error(webview, NULL,
+				_("There is no application configured to open this type of file."),
+				NULL, NULL);
 	}
 	else if (code < 32)
 	{
-		purple_notify_error(imhtml, NULL,
-				_("An error occurred while opening the file."), NULL);
-		purple_debug_warning("gtkutils", "filename: %s; code: %d\n", uri, code);
+		purple_notify_error(webview, NULL,
+				_("An error occurred while opening the file."), NULL, NULL);
+		purple_debug_warning("gtkutils", "filename: %s; code: %d\n",
+				filename, code);
 	}
 #else
 	char *command = NULL;
@@ -3620,15 +3616,15 @@ file_open_uri(GtkIMHtml *imhtml, const c
 
 	if (purple_running_gnome())
 	{
-		char *escaped = g_shell_quote(uri);
+		char *escaped = g_shell_quote(filename);
 		command = g_strdup_printf("gnome-open %s", escaped);
 		g_free(escaped);
 	}
 	else if (purple_running_kde())
 	{
-		char *escaped = g_shell_quote(uri);
+		char *escaped = g_shell_quote(filename);
 
-		if (purple_str_has_suffix(uri, ".desktop"))
+		if (purple_str_has_suffix(filename, ".desktop"))
 			command = g_strdup_printf("kfmclient openURL %s 'text/plain'", escaped);
 		else
 			command = g_strdup_printf("kfmclient openURL %s", escaped);
@@ -3636,7 +3632,7 @@ file_open_uri(GtkIMHtml *imhtml, const c
 	}
 	else
 	{
-		purple_notify_uri(NULL, uri);
+		purple_notify_uri(NULL, filename);
 		return;
 	}
 
@@ -3646,7 +3642,7 @@ file_open_uri(GtkIMHtml *imhtml, const c
 		if (!g_spawn_command_line_sync(command, NULL, NULL, &exit_status, &error))
 		{
 			tmp = g_strdup_printf(_("Error launching %s: %s"),
-							uri, error->message);
+							filename, error->message);
 			purple_notify_error(imhtml, NULL, _("Unable to open file."), tmp);
 			g_free(tmp);
 			g_error_free(error);
@@ -3667,8 +3663,9 @@ file_open_uri(GtkIMHtml *imhtml, const c
 static gboolean
 file_clicked_cb(GtkIMHtml *imhtml, GtkIMHtmlLink *link)
 {
-	const char *uri = gtk_imhtml_link_get_url(link) + FILELINKSIZE;
-	file_open_uri(imhtml, uri);
+	/* Strip "file://" from the URI. */
+	const char *filename = gtk_imhtml_link_get_url(link) + FILELINKSIZE;
+	open_file(imhtml, filename);
 	return TRUE;
 }
 
@@ -3676,7 +3673,7 @@ static gboolean
 open_containing_cb(GtkIMHtml *imhtml, const char *url)
 {
 	char *dir = g_path_get_dirname(url + FILELINKSIZE);
-	file_open_uri(imhtml, dir);
+	open_file(imhtml, dir);
 	g_free(dir);
 	return TRUE;
 }
diff -Npur pidgin-2.6.6-old/pidgin/gtkutils.c.orig pidgin-2.6.6/pidgin/gtkutils.c.orig
--- pidgin-2.6.6-old/pidgin/gtkutils.c.orig	1970-01-01 08:00:00.000000000 +0800
+++ pidgin-2.6.6/pidgin/gtkutils.c.orig	2010-02-16 17:34:06.000000000 +0800
@@ -0,0 +1,3978 @@
+/**
+ * @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
+ */
+#define _PIDGIN_GTKUTILS_C_
+
+#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 "sound.h"
+#include "util.h"
+
+#include "gtkaccount.h"
+#include "gtkprefs.h"
+
+#include "gtkconv.h"
+#include "gtkdialogs.h"
+#include "gtkimhtml.h"
+#include "gtkimhtmltoolbar.h"
+#include "pidginstock.h"
+#include "gtkthemes.h"
+#include "gtkutils.h"
+#include "pidgin/minidialog.h"
+
+typedef struct {
+	GtkWidget *menu;
+	gint default_item;
+} AopMenu;
+
+static guint accels_save_timer = 0;
+static GSList *registered_url_handlers = NULL;
+
+static gboolean
+url_clicked_idle_cb(gpointer data)
+{
+	purple_notify_uri(NULL, data);
+	g_free(data);
+	return FALSE;
+}
+
+static gboolean
+url_clicked_cb(GtkIMHtml *unused, GtkIMHtmlLink *link)
+{
+	const char *uri = gtk_imhtml_link_get_url(link);
+	g_idle_add(url_clicked_idle_cb, g_strdup(uri));
+	return TRUE;
+}
+
+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)
+{
+	g_return_if_fail(imhtml != NULL);
+	g_return_if_fail(GTK_IS_IMHTML(imhtml));
+
+	pidgin_themes_smiley_themeize(imhtml);
+
+	gtk_imhtml_set_funcs(GTK_IMHTML(imhtml), &gtkimhtml_cbs);
+
+#ifdef _WIN32
+	if (!purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/conversations/use_theme_font")) {
+		PangoFontDescription *desc;
+		const char *font = purple_prefs_get_string(PIDGIN_PREFS_ROOT "/conversations/custom_font");
+		desc = pango_font_description_from_string(font);
+		if (desc) {
+			gtk_widget_modify_font(imhtml, desc);
+			pango_font_description_free(desc);
+		}
+	}
+#endif
+
+}
+
+static
+void pidgin_window_init(GtkWindow *wnd, const char *title, guint border_width, const char *role, gboolean resizable)
+{
+	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);
+}
+
+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));
+	pidgin_window_init(wnd, title, border_width, role, resizable);
+
+	return GTK_WIDGET(wnd);
+}
+
+GtkWidget *
+pidgin_create_dialog(const char *title, guint border_width, const char *role, gboolean resizable)
+{
+	GtkWindow *wnd = NULL;
+
+	wnd = GTK_WINDOW(gtk_dialog_new());
+	pidgin_window_init(wnd, title, border_width, role, resizable);
+	g_object_set(G_OBJECT(wnd), "has-separator", FALSE, NULL);
+
+	return GTK_WIDGET(wnd);
+}
+
+GtkWidget *
+pidgin_dialog_get_vbox_with_properties(GtkDialog *dialog, gboolean homogeneous, gint spacing)
+{
+	GtkBox *vbox = GTK_BOX(GTK_DIALOG(dialog)->vbox);
+	gtk_box_set_homogeneous(vbox, homogeneous);
+	gtk_box_set_spacing(vbox, spacing);
+	return GTK_WIDGET(vbox);
+}
+
+GtkWidget *pidgin_dialog_get_vbox(GtkDialog *dialog)
+{
+	return GTK_DIALOG(dialog)->vbox;
+}
+
+GtkWidget *pidgin_dialog_get_action_area(GtkDialog *dialog)
+{
+	return GTK_DIALOG(dialog)->action_area;
+}
+
+GtkWidget *pidgin_dialog_add_button(GtkDialog *dialog, const char *label,
+		GCallback callback, gpointer callbackdata)
+{
+	GtkWidget *button = gtk_button_new_from_stock(label);
+	GtkWidget *bbox = pidgin_dialog_get_action_area(dialog);
+	gtk_box_pack_start(GTK_BOX(bbox), button, FALSE, FALSE, 0);
+	if (callback)
+		g_signal_connect(G_OBJECT(button), "clicked", callback, callbackdata);
+	gtk_widget_show(button);
+	return button;
+}
+
+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,
+		GCallback cb, 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 (cb)
+		g_signal_connect(G_OBJECT(menuitem), "activate", cb, 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, GCallback cb, 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 (cb)
+		g_signal_connect(G_OBJECT(menuitem), "activate", cb, 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 item ? g_object_get_data(G_OBJECT(item), "aop_per_item_data") : NULL;
+}
+
+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 = NULL;
+
+	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);
+		}
+		gtk_file_selection_set_filename(filesel, (dirname != NULL) ? dirname : path);
+		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 = purple_timeout_add_seconds(5, 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_real_name)
+		who = prpl_info->get_cb_real_name(conn, chat, name);
+	if (prpl_info == NULL || prpl_info->get_cb_info == NULL) {
+		pidgin_retrieve_user_info(conn, who ? who : name);
+		g_free(who);
+		return;
+	}
+
+	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);
+	g_object_unref(set);
+
+	/* 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);
+	g_object_unref(set);
+}
+
+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)
+{
+	gchar *filedata;
+	size_t size;
+	struct stat st;
+	GError *err = NULL;
+	PurpleConversation *conv;
+	PidginConversation *gtkconv;
+	GtkTextIter iter;
+	int id;
+	PurpleBuddy *buddy;
+	PurpleContact *contact;
+	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, g_strerror(errno));
+			purple_notify_error(NULL, NULL,
+					  _("Failed to load image"),
+					  str);
+			g_free(str);
+
+			break;
+		}
+
+		buddy = purple_find_buddy(data->account, data->who);
+		if (!buddy) {
+			purple_debug_info("custom-icon", "You can only set custom icons for people on your buddylist.\n");
+			break;
+		}
+		contact = purple_buddy_get_contact(buddy);
+		purple_buddy_icons_node_set_custom_icon_from_file((PurpleBlistNode*)contact, 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);
+
+			break;
+		}
+		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;
+	}
+	g_free(data->filename);
+	g_free(data->who);
+	g_free(data);
+}
+
+static void dnd_image_cancel_callback(_DndData *data, int choice)
+{
+	g_free(data->filename);
+	g_free(data->who);
+	g_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)
+{
+	g_free(data->filename);
+	g_free(data->who);
+	g_free(data);
+}
+
+void
+pidgin_dnd_file_manage(GtkSelectionData *sd, PurpleAccount *account, const char *who)
+{
+	GdkPixbuf *pb;
+	GList *files = purple_uri_list_extract_filenames((const gchar *)sd->data);
+	PurpleConnection *gc = purple_account_get_connection(account);
+	PurplePluginProtocolInfo *prpl_info = NULL;
+#ifndef _WIN32
+	PurpleDesktopItem *item;
+#endif
+	gchar *filename = NULL;
+	gchar *basename = NULL;
+
+	g_return_if_fail(account != NULL);
+	g_return_if_fail(who != NULL);
+
+	for ( ; files; files = g_list_delete_link(files, files)) {
+		g_free(filename);
+		g_free(basename);
+
+		filename = files->data;
+		basename = g_path_get_basename(filename);
+
+		/* 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);
+			else if (prpl_info && prpl_info->send_file)
+				ft = TRUE;
+
+			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?"),
+							PURPLE_DEFAULT_ACTION_NONE,
+							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);
+			g_object_unref(G_OBJECT(pb));
+
+			g_free(basename);
+			while (files) {
+				g_free(files->data);
+				files = g_list_delete_link(files, files);
+			}
+			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 the target "
+				                      "of this launcher instead of this "
+				                      "launcher itself."));
+				break;
+			}
+			purple_desktop_item_unref(item);
+			g_free(basename);
+			while (files) {
+				g_free(files->data);
+				files = g_list_delete_link(files, files);
+			}
+			return;
+		}
+#endif /* _WIN32 */
+
+		/* Everything is fine, let's send */
+		serv_send_file(gc, who, filename);
+	}
+
+	g_free(filename);
+	g_free(basename);
+}
+
+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;
+	const char *stock = pidgin_stock_id_from_status_primitive(prim);
+
+	pixbuf = gtk_widget_render_icon (w, stock ? stock : PIDGIN_STOCK_STATUS_AVAILABLE,
+			icon_size, "GtkWidget");
+	return pixbuf;
+}
+
+static const char *
+stock_id_from_status_primitive_idle(PurpleStatusPrimitive prim, gboolean idle)
+{
+	const char *stock = NULL;
+	switch (prim) {
+		case PURPLE_STATUS_UNSET:
+			stock = NULL;
+			break;
+		case PURPLE_STATUS_UNAVAILABLE:
+			stock = idle ? PIDGIN_STOCK_STATUS_BUSY_I : PIDGIN_STOCK_STATUS_BUSY;
+			break;
+		case PURPLE_STATUS_AWAY:
+			stock = idle ? PIDGIN_STOCK_STATUS_AWAY_I : PIDGIN_STOCK_STATUS_AWAY;
+			break;
+		case PURPLE_STATUS_EXTENDED_AWAY:
+			stock = idle ? PIDGIN_STOCK_STATUS_XA_I : PIDGIN_STOCK_STATUS_XA;
+			break;
+		case PURPLE_STATUS_INVISIBLE:
+			stock = PIDGIN_STOCK_STATUS_INVISIBLE;
+			break;
+		case PURPLE_STATUS_OFFLINE:
+			stock = idle ? PIDGIN_STOCK_STATUS_OFFLINE_I : PIDGIN_STOCK_STATUS_OFFLINE;
+			break;
+		default:
+			stock = idle ? PIDGIN_STOCK_STATUS_AVAILABLE_I : PIDGIN_STOCK_STATUS_AVAILABLE;
+			break;
+	}
+	return stock;
+}
+
+const char *
+pidgin_stock_id_from_status_primitive(PurpleStatusPrimitive prim)
+{
+	return stock_id_from_status_primitive_idle(prim, FALSE);
+}
+
+const char *
+pidgin_stock_id_from_presence(PurplePresence *presence)
+{
+	PurpleStatus *status;
+	PurpleStatusType *type;
+	PurpleStatusPrimitive prim;
+	gboolean idle;
+
+	g_return_val_if_fail(presence, NULL);
+
+	status = purple_presence_get_active_status(presence);
+	type = purple_status_get_type(status);
+	prim = purple_status_type_get_primitive(type);
+
+	idle = purple_presence_is_idle(presence);
+
+	return stock_id_from_status_primitive_idle(prim, idle);
+}
+
+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 buddyname_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 buddyname_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_buddyname_autocomplete_entry(GtkListStore *store, const char *buddy_alias, const char *contact_alias,
+								  const PurpleAccount *account, const char *buddyname)
+{
+	GtkTreeIter iter;
+	gboolean completion_added = FALSE;
+	gchar *normalized_buddyname;
+	gchar *tmp;
+
+	tmp = g_utf8_normalize(buddyname, -1, G_NORMALIZE_DEFAULT);
+	normalized_buddyname = g_utf8_casefold(tmp, -1);
+	g_free(tmp);
+
+	/* There's no sense listing things like: 'xxx "xxx"'
+	   when the name and buddy alias match. */
+	if (buddy_alias && strcmp(buddy_alias, buddyname)) {
+		char *completion_entry = g_strdup_printf("%s \"%s\"", buddyname, 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, buddyname,
+				2, normalized_buddyname,
+				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 name and contact alias match. */
+	if (contact_alias && strcmp(contact_alias, buddyname)) {
+		/* 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\"",
+							buddyname, 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, buddyname,
+					2, normalized_buddyname,
+					3, tmp,
+					4, account,
+					-1);
+			g_free(completion_entry);
+			g_free(tmp);
+			completion_added = TRUE;
+		}
+	}
+
+	if (completion_added == FALSE) {
+		/* Add the buddy's name. */
+		gtk_list_store_append(store, &iter);
+		gtk_list_store_set(store, &iter,
+				0, buddyname,
+				1, buddyname,
+				2, normalized_buddyname,
+				3, NULL,
+				4, account,
+				-1);
+	}
+
+	g_free(normalized_buddyname);
+}
+#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_buddyname_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_buddyname_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
+buddyname_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 buddy name, the UTF-8
+	 * normalized & casefolded buddy name, 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 buddy name */
+	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, buddyname_completion_match_func, NULL, NULL);
+
+	g_signal_connect(G_OBJECT(completion), "match-selected",
+		G_CALLBACK(buddyname_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(buddyname_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(cursor_type);
+	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 0 /* mismatched curly braces */
+	}
+#endif
+	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;
+
+	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 */
+
+#ifdef _WIN32
+	g_signal_connect(G_OBJECT(dialog->icon_filesel), "show",
+		G_CALLBACK(winpidgin_ensure_onscreen), dialog->icon_filesel);
+#endif
+
+	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");
+			g_free(filename);
+			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;
+
+	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);
+	purple_buddy_icons_node_set_custom_icon_from_file((PurpleBlistNode*)contact, filename);
+}
+
+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);
+#elif defined GDK_WINDOWING_X11
+	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);
+#else
+	/* do something else? */
+#endif
+}
+
+static GSList *minidialogs = NULL;
+
+static void *
+pidgin_utils_get_handle(void)
+{
+	static int handle;
+
+	return &handle;
+}
+
+static void connection_signed_off_cb(PurpleConnection *gc)
+{
+	GSList *list, *l_next;
+	for (list = minidialogs; list; list = l_next) {
+		l_next = 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);
+}
+
+struct _old_button_clicked_cb_data
+{
+	PidginUtilMiniDialogCallback cb;
+	gpointer data;
+};
+
+static void
+old_mini_dialog_button_clicked_cb(PidginMiniDialog *mini_dialog,
+                                  GtkButton *button,
+                                  gpointer user_data)
+{
+	struct _old_button_clicked_cb_data *data = user_data;
+	data->cb(data->data, button);
+}
+
+static void
+old_mini_dialog_destroy_cb(GtkWidget *dialog,
+                           GList *cb_datas)
+{
+	while (cb_datas != NULL)
+	{
+		g_free(cb_datas->data);
+		cb_datas = g_list_delete_link(cb_datas, cb_datas);
+	}
+}
+
+GtkWidget *
+pidgin_make_mini_dialog(PurpleConnection *gc,
+                        const char *icon_name,
+                        const char *primary,
+                        const char *secondary,
+                        void *user_data,
+                        ...)
+{
+	PidginMiniDialog *mini_dialog;
+	const char *button_text;
+	GList *cb_datas = NULL;
+	va_list args;
+	static gboolean first_call = TRUE;
+
+	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);
+	}
+
+	mini_dialog = pidgin_mini_dialog_new(primary, secondary, icon_name);
+	g_object_set_data(G_OBJECT(mini_dialog), "gc" ,gc);
+	g_signal_connect(G_OBJECT(mini_dialog), "destroy",
+		G_CALLBACK(alert_killed_cb), NULL);
+
+	va_start(args, user_data);
+	while ((button_text = va_arg(args, char*))) {
+		struct _old_button_clicked_cb_data *data = NULL;
+		PidginMiniDialogCallback wrapper_cb = NULL;
+		PidginUtilMiniDialogCallback callback =
+			va_arg(args, PidginUtilMiniDialogCallback);
+
+		if (callback != NULL) {
+			data = g_new0(struct _old_button_clicked_cb_data, 1);
+			data->cb = callback;
+			data->data = user_data;
+			wrapper_cb = old_mini_dialog_button_clicked_cb;
+		}
+		pidgin_mini_dialog_add_button(mini_dialog, button_text,
+			wrapper_cb, data);
+		cb_datas = g_list_append(cb_datas, data);
+	}
+	va_end(args);
+
+	g_signal_connect(G_OBJECT(mini_dialog), "destroy",
+		G_CALLBACK(old_mini_dialog_destroy_cb), cb_datas);
+
+	return GTK_WIDGET(mini_dialog);
+}
+
+/*
+ * "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));
+}
+
+GtkWidget *
+pidgin_add_widget_to_vbox(GtkBox *vbox, const char *widget_label, GtkSizeGroup *sg, GtkWidget *widget, gboolean expand, GtkWidget **p_label)
+{
+	GtkWidget *hbox;
+	GtkWidget *label = NULL;
+
+	if (widget_label) {
+		hbox = gtk_hbox_new(FALSE, 5);
+		gtk_widget_show(hbox);
+		gtk_box_pack_start(vbox, hbox, FALSE, FALSE, 0);
+
+		label = gtk_label_new_with_mnemonic(widget_label);
+		gtk_widget_show(label);
+		if (sg) {
+			gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
+			gtk_size_group_add_widget(sg, label);
+		}
+		gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 0);
+	} else {
+		hbox = GTK_WIDGET(vbox);
+	}
+
+	gtk_widget_show(widget);
+	gtk_box_pack_start(GTK_BOX(hbox), widget, expand, TRUE, 0);
+	if (label) {
+		gtk_label_set_mnemonic_widget(GTK_LABEL(label), widget);
+		pidgin_set_accessible_label (widget, label);
+	}
+
+	if (p_label)
+		(*p_label) = label;
+	return hbox;
+}
+
+gboolean pidgin_auto_parent_window(GtkWidget *widget)
+{
+#if 0
+	/* This looks at the most recent window that received focus, and makes
+	 * that the parent window. */
+#ifndef _WIN32
+	static GdkAtom _WindowTime = GDK_NONE;
+	static GdkAtom _Cardinal = GDK_NONE;
+	GList *windows = NULL;
+	GtkWidget *parent = NULL;
+	time_t window_time = 0;
+
+	windows = gtk_window_list_toplevels();
+
+	if (_WindowTime == GDK_NONE) {
+		_WindowTime = gdk_x11_xatom_to_atom(gdk_x11_get_xatom_by_name("_NET_WM_USER_TIME"));
+	}
+	if (_Cardinal == GDK_NONE) {
+		_Cardinal = gdk_atom_intern("CARDINAL", FALSE);
+	}
+
+	while (windows) {
+		GtkWidget *window = windows->data;
+		guchar *data = NULL;
+		int al = 0;
+		time_t value;
+
+		windows = g_list_delete_link(windows, windows);
+
+		if (window == widget ||
+				!GTK_WIDGET_VISIBLE(window))
+			continue;
+
+		if (!gdk_property_get(window->window, _WindowTime, _Cardinal, 0, sizeof(time_t), FALSE,
+				NULL, NULL, &al, &data))
+			continue;
+		value = *(time_t *)data;
+		if (window_time < value) {
+			window_time = value;
+			parent = window;
+		}
+		g_free(data);
+	}
+	if (windows)
+		g_list_free(windows);
+	if (parent) {
+		if (!gtk_get_current_event() && gtk_window_has_toplevel_focus(GTK_WINDOW(parent))) {
+			/* The window is in focus, and the new window was not triggered by a keypress/click
+			 * event. So do not set it transient, to avoid focus stealing and all that.
+			 */
+			return FALSE;
+		}
+		gtk_window_set_transient_for(GTK_WINDOW(widget), GTK_WINDOW(parent));
+		return TRUE;
+	}
+	return FALSE;
+#endif
+#else
+#if GTK_CHECK_VERSION(2,4,0)
+	/* This finds the currently active window and makes that the parent window. */
+	GList *windows = NULL;
+	GtkWidget *parent = NULL;
+	GdkEvent *event = gtk_get_current_event();
+	GdkWindow *menu = NULL;
+
+	if (event == NULL)
+		/* The window was not triggered by a user action. */
+		return FALSE;
+
+	/* We need to special case events from a popup menu. */
+	if (event->type == GDK_BUTTON_RELEASE) {
+		/* XXX: Neither of the following works:
+			menu = event->button.window;
+			menu = gdk_window_get_parent(event->button.window);
+			menu = gdk_window_get_toplevel(event->button.window);
+		*/
+	} else if (event->type == GDK_KEY_PRESS)
+		menu = event->key.window;
+
+	windows = gtk_window_list_toplevels();
+	while (windows) {
+		GtkWidget *window = windows->data;
+		windows = g_list_delete_link(windows, windows);
+
+		if (window == widget ||
+				!GTK_WIDGET_VISIBLE(window)) {
+			continue;
+		}
+
+		if (gtk_window_has_toplevel_focus(GTK_WINDOW(window)) ||
+				(menu && menu == window->window)) {
+			parent = window;
+			break;
+		}
+	}
+	if (windows)
+		g_list_free(windows);
+	if (parent) {
+		gtk_window_set_transient_for(GTK_WINDOW(widget), GTK_WINDOW(parent));
+		return TRUE;
+	}
+#endif
+	return FALSE;
+#endif
+}
+
+GdkPixbuf * pidgin_pixbuf_from_imgstore(PurpleStoredImage *image)
+{
+	GdkPixbuf *pixbuf;
+	GdkPixbufLoader *loader = gdk_pixbuf_loader_new();
+	gdk_pixbuf_loader_write(loader, purple_imgstore_get_data(image),
+			purple_imgstore_get_size(image), NULL);
+	gdk_pixbuf_loader_close(loader, NULL);
+	pixbuf = gdk_pixbuf_loader_get_pixbuf(loader);
+	if (pixbuf)
+		g_object_ref(pixbuf);
+	g_object_unref(loader);
+	return pixbuf;
+}
+
+static void url_copy(GtkWidget *w, gchar *url)
+{
+	GtkClipboard *clipboard;
+
+	clipboard = gtk_widget_get_clipboard(w, GDK_SELECTION_PRIMARY);
+	gtk_clipboard_set_text(clipboard, url, -1);
+
+	clipboard = gtk_widget_get_clipboard(w, GDK_SELECTION_CLIPBOARD);
+	gtk_clipboard_set_text(clipboard, url, -1);
+}
+
+static gboolean
+link_context_menu(GtkIMHtml *imhtml, GtkIMHtmlLink *link, GtkWidget *menu)
+{
+	GtkWidget *img, *item;
+	const char *url;
+
+	url = gtk_imhtml_link_get_url(link);
+
+	/* Open Link */
+	img = gtk_image_new_from_stock(GTK_STOCK_JUMP_TO, GTK_ICON_SIZE_MENU);
+	item = gtk_image_menu_item_new_with_mnemonic(_("_Open Link"));
+	gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(item), img);
+	g_signal_connect_swapped(G_OBJECT(item), "activate", G_CALLBACK(gtk_imhtml_link_activate), link);
+	gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
+
+	/* Copy Link Location */
+	img = gtk_image_new_from_stock(GTK_STOCK_COPY, GTK_ICON_SIZE_MENU);
+	item = gtk_image_menu_item_new_with_mnemonic(_("_Copy Link Location"));
+	gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(item), img);
+	g_signal_connect(G_OBJECT(item), "activate", G_CALLBACK(url_copy), (gpointer)url);
+	gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
+
+	return TRUE;
+}
+
+static gboolean
+copy_email_address(GtkIMHtml *imhtml, GtkIMHtmlLink *link, GtkWidget *menu)
+{
+	GtkWidget *img, *item;
+	const char *text;
+	char *address;
+#define MAILTOSIZE  (sizeof("mailto:") - 1)
+
+	text = gtk_imhtml_link_get_url(link);
+	g_return_val_if_fail(text && strlen(text) > MAILTOSIZE, FALSE);
+	address = (char*)text + MAILTOSIZE;
+
+	/* Copy Email Address */
+	img = gtk_image_new_from_stock(GTK_STOCK_COPY, GTK_ICON_SIZE_MENU);
+	item = gtk_image_menu_item_new_with_mnemonic(_("_Copy Email Address"));
+	gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(item), img);
+	g_signal_connect(G_OBJECT(item), "activate", G_CALLBACK(url_copy), address);
+	gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
+
+	return TRUE;
+}
+
+static void
+file_open_uri(GtkIMHtml *imhtml, const char *uri)
+{
+	/* Copied from gtkft.c:open_button_cb */
+#ifdef _WIN32
+	/* If using Win32... */
+	int code;
+	if (G_WIN32_HAVE_WIDECHAR_API()) {
+		wchar_t *wc_filename = g_utf8_to_utf16(
+				uri, -1, NULL, NULL, NULL);
+
+		code = (int)ShellExecuteW(NULL, NULL, wc_filename, NULL, NULL,
+				SW_SHOW);
+
+		g_free(wc_filename);
+	} else {
+		char *l_filename = g_locale_from_utf8(
+				uri, -1, NULL, NULL, NULL);
+
+		code = (int)ShellExecuteA(NULL, NULL, l_filename, NULL, NULL,
+				SW_SHOW);
+
+		g_free(l_filename);
+	}
+
+	if (code == SE_ERR_ASSOCINCOMPLETE || code == SE_ERR_NOASSOC)
+	{
+		purple_notify_error(imhtml, NULL,
+				_("There is no application configured to open this type of file."), NULL);
+	}
+	else if (code < 32)
+	{
+		purple_notify_error(imhtml, NULL,
+				_("An error occurred while opening the file."), NULL);
+		purple_debug_warning("gtkutils", "filename: %s; code: %d\n", uri, code);
+	}
+#else
+	char *command = NULL;
+	char *tmp = NULL;
+	GError *error = NULL;
+
+	if (purple_running_gnome())
+	{
+		char *escaped = g_shell_quote(uri);
+		command = g_strdup_printf("gnome-open %s", escaped);
+		g_free(escaped);
+	}
+	else if (purple_running_kde())
+	{
+		char *escaped = g_shell_quote(uri);
+
+		if (purple_str_has_suffix(uri, ".desktop"))
+			command = g_strdup_printf("kfmclient openURL %s 'text/plain'", escaped);
+		else
+			command = g_strdup_printf("kfmclient openURL %s", escaped);
+		g_free(escaped);
+	}
+	else
+	{
+		purple_notify_uri(NULL, uri);
+		return;
+	}
+
+	if (purple_program_is_valid(command))
+	{
+		gint exit_status;
+		if (!g_spawn_command_line_sync(command, NULL, NULL, &exit_status, &error))
+		{
+			tmp = g_strdup_printf(_("Error launching %s: %s"),
+							uri, error->message);
+			purple_notify_error(imhtml, NULL, _("Unable to open file."), tmp);
+			g_free(tmp);
+			g_error_free(error);
+		}
+		if (exit_status != 0)
+		{
+			char *primary = g_strdup_printf(_("Error running %s"), command);
+			char *secondary = g_strdup_printf(_("Process returned error code %d"),
+									exit_status);
+			purple_notify_error(imhtml, NULL, primary, secondary);
+			g_free(tmp);
+		}
+	}
+#endif
+}
+
+#define FILELINKSIZE  (sizeof("file://") - 1)
+static gboolean
+file_clicked_cb(GtkIMHtml *imhtml, GtkIMHtmlLink *link)
+{
+	const char *uri = gtk_imhtml_link_get_url(link) + FILELINKSIZE;
+	file_open_uri(imhtml, uri);
+	return TRUE;
+}
+
+static gboolean
+open_containing_cb(GtkIMHtml *imhtml, const char *url)
+{
+	char *dir = g_path_get_dirname(url + FILELINKSIZE);
+	file_open_uri(imhtml, dir);
+	g_free(dir);
+	return TRUE;
+}
+
+static gboolean
+file_context_menu(GtkIMHtml *imhtml, GtkIMHtmlLink *link, GtkWidget *menu)
+{
+	GtkWidget *img, *item;
+	const char *url;
+
+	url = gtk_imhtml_link_get_url(link);
+
+	/* Open File */
+	img = gtk_image_new_from_stock(GTK_STOCK_JUMP_TO, GTK_ICON_SIZE_MENU);
+	item = gtk_image_menu_item_new_with_mnemonic(_("_Open File"));
+	gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(item), img);
+	g_signal_connect_swapped(G_OBJECT(item), "activate", G_CALLBACK(gtk_imhtml_link_activate), link);
+	gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
+
+	/* Open Containing Directory */
+#if GTK_CHECK_VERSION(2,6,0)
+	img = gtk_image_new_from_stock(GTK_STOCK_DIRECTORY, GTK_ICON_SIZE_MENU);
+	item = gtk_image_menu_item_new_with_mnemonic(_("Open _Containing Directory"));
+	gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(item), img);
+#else
+	item = gtk_menu_item_new_with_mnemonic(_("Open _Containing Directory"));
+#endif
+	g_signal_connect(G_OBJECT(item), "activate", G_CALLBACK(open_containing_cb), (gpointer)url);
+	gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
+
+	return TRUE;
+}
+
+#define AUDIOLINKSIZE  (sizeof("audio://") - 1)
+static gboolean
+audio_clicked_cb(GtkIMHtml *imhtml, GtkIMHtmlLink *link)
+{
+	const char *uri;
+	PidginConversation *conv = g_object_get_data(G_OBJECT(imhtml), "gtkconv");
+	if (!conv) /* no playback in debug window */
+		return TRUE;
+	uri = gtk_imhtml_link_get_url(link) + AUDIOLINKSIZE;
+	purple_sound_play_file(uri, NULL);
+	return TRUE;
+}
+
+static void
+savefile_write_cb(gpointer user_data, char *file)
+{
+	char *temp_file = user_data;
+	gchar *contents;
+	gsize length;
+	GError *error = NULL;
+
+	if (!g_file_get_contents(temp_file, &contents, &length, &error)) {
+		purple_debug_error("gtkutils", "Unable to read contents of %s: %s\n",
+		                   temp_file, error->message);
+		g_error_free(error);
+		return;
+	}
+
+	if (!purple_util_write_data_to_file_absolute(file, contents, length)) {
+		purple_debug_error("gtkutils", "Unable to write contents to %s\n",
+		                   file);
+	}
+}
+
+static gboolean
+save_file_cb(GtkWidget *item, const char *url)
+{
+	PidginConversation *conv = g_object_get_data(G_OBJECT(item), "gtkconv");
+	if (!conv)
+		return TRUE;
+	purple_request_file(conv->active_conv, _("Save File"), NULL, TRUE,
+	                    G_CALLBACK(savefile_write_cb), NULL,
+	                    conv->active_conv->account, NULL, conv->active_conv,
+	                    (void *)url);
+	return TRUE;
+}
+
+static gboolean
+audio_context_menu(GtkIMHtml *imhtml, GtkIMHtmlLink *link, GtkWidget *menu)
+{
+	GtkWidget *img, *item;
+	const char *url;
+	PidginConversation *conv = g_object_get_data(G_OBJECT(imhtml), "gtkconv");
+	if (!conv) /* No menu in debug window */
+		return TRUE;
+
+	url = gtk_imhtml_link_get_url(link);
+
+	/* Play Sound */
+#if GTK_CHECK_VERSION(2,6,0)
+	img = gtk_image_new_from_stock(GTK_STOCK_MEDIA_PLAY, GTK_ICON_SIZE_MENU);
+	item = gtk_image_menu_item_new_with_mnemonic(_("_Play Sound"));
+	gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(item), img);
+#else
+	item = gtk_menu_item_new_with_mnemonic(_("_Play Sound"));
+#endif
+	g_signal_connect_swapped(G_OBJECT(item), "activate", G_CALLBACK(gtk_imhtml_link_activate), link);
+	gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
+
+	/* Save File */
+	img = gtk_image_new_from_stock(GTK_STOCK_SAVE, GTK_ICON_SIZE_MENU);
+	item = gtk_image_menu_item_new_with_mnemonic(_("_Save File"));
+	gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(item), img);
+	g_signal_connect(G_OBJECT(item), "activate", G_CALLBACK(save_file_cb), (gpointer)(url+AUDIOLINKSIZE));
+	g_object_set_data(G_OBJECT(item), "gtkconv", conv);
+	gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
+
+	return TRUE;
+}
+
+/* XXX: The following two functions are for demonstration purposes only! */
+static gboolean
+open_dialog(GtkIMHtml *imhtml, GtkIMHtmlLink *link)
+{
+	const char *url;
+	const char *str;
+
+	url = gtk_imhtml_link_get_url(link);
+	if (!url || strlen(url) < sizeof("open://"))
+		return FALSE;
+
+	str = url + sizeof("open://") - 1;
+
+	if (strcmp(str, "accounts") == 0)
+		pidgin_accounts_window_show();
+	else if (strcmp(str, "prefs") == 0)
+		pidgin_prefs_show();
+	else
+		return FALSE;
+	return TRUE;
+}
+
+static gboolean
+dummy(GtkIMHtml *imhtml, GtkIMHtmlLink *link, GtkWidget *menu)
+{
+	return TRUE;
+}
+
+static gboolean
+register_gnome_url_handlers(void)
+{
+	char *tmp;
+	char *err;
+	char *c;
+	char *start;
+
+	tmp = g_find_program_in_path("gconftool-2");
+	if (tmp == NULL)
+		return FALSE;
+
+	g_free(tmp);
+	tmp = NULL;
+
+	if (!g_spawn_command_line_sync("gconftool-2 --all-dirs /desktop/gnome/url-handlers",
+	                               &tmp, &err, NULL, NULL))
+	{
+		g_free(tmp);
+		g_free(err);
+		g_return_val_if_reached(FALSE);
+	}
+	g_free(err);
+	err = NULL;
+
+	for (c = start = tmp ; *c ; c++)
+	{
+		/* Skip leading spaces. */
+		if (c == start && *c == ' ')
+			start = c + 1;
+		else if (*c == '\n')
+		{
+			*c = '\0';
+			if (g_str_has_prefix(start, "/desktop/gnome/url-handlers/"))
+			{
+				char *cmd;
+				char *tmp2 = NULL;
+				char *protocol;
+
+				/* If there is an enabled boolean, honor it. */
+				cmd = g_strdup_printf("gconftool-2 -g %s/enabled", start);
+				if (g_spawn_command_line_sync(cmd, &tmp2, &err, NULL, NULL))
+				{
+					g_free(err);
+					err = NULL;
+					if (!strcmp(tmp2, "false\n"))
+					{
+						g_free(tmp2);
+						g_free(cmd);
+						start = c + 1;
+						continue;
+					}
+				}
+				g_free(cmd);
+				g_free(tmp2);
+
+				start += sizeof("/desktop/gnome/url-handlers/") - 1;
+
+				protocol = g_strdup_printf("%s:", start);
+				registered_url_handlers = g_slist_prepend(registered_url_handlers, protocol);
+				gtk_imhtml_class_register_protocol(protocol, url_clicked_cb, link_context_menu);
+			}
+			start = c + 1;
+		}
+	}
+	g_free(tmp);
+
+	return (registered_url_handlers != NULL);
+}
+
+#ifdef _WIN32
+static void
+winpidgin_register_win32_url_handlers(void)
+{
+	int idx = 0;
+	LONG ret = ERROR_SUCCESS;
+
+	do {
+		DWORD nameSize = 256;
+		char start[256];
+		/* I don't think we need to worry about non-ASCII protocol names */
+		ret = RegEnumKeyExA(HKEY_CLASSES_ROOT, idx++, start, &nameSize,
+							NULL, NULL, NULL, NULL);
+		if (ret == ERROR_SUCCESS) {
+			HKEY reg_key = NULL;
+			ret = RegOpenKeyExA(HKEY_CLASSES_ROOT, start, 0, KEY_READ, &reg_key);
+			if (ret == ERROR_SUCCESS) {
+				ret = RegQueryValueExA(reg_key, "URL Protocol", NULL, NULL, NULL, NULL);
+				if (ret == ERROR_SUCCESS) {
+					gchar *protocol = g_strdup_printf("%s:", start);
+					registered_url_handlers = g_slist_prepend(registered_url_handlers, protocol);
+					/* We still pass everything to the "http" "open" handler for security reasons */
+					gtk_imhtml_class_register_protocol(protocol, url_clicked_cb, link_context_menu);
+				}
+				RegCloseKey(reg_key);
+			}
+			ret = ERROR_SUCCESS;
+		}
+	} while (ret == ERROR_SUCCESS);
+
+	if (ret != ERROR_NO_MORE_ITEMS)
+		purple_debug_error("winpidgin", "Error iterating HKEY_CLASSES_ROOT subkeys: %ld\n",
+						   ret);
+}
+#endif
+
+void pidgin_utils_init(void)
+{
+	gtk_imhtml_class_register_protocol("http://", url_clicked_cb, link_context_menu);
+	gtk_imhtml_class_register_protocol("https://", url_clicked_cb, link_context_menu);
+	gtk_imhtml_class_register_protocol("ftp://", url_clicked_cb, link_context_menu);
+	gtk_imhtml_class_register_protocol("gopher://", url_clicked_cb, link_context_menu);
+	gtk_imhtml_class_register_protocol("mailto:", url_clicked_cb, copy_email_address);
+
+	gtk_imhtml_class_register_protocol("file://", file_clicked_cb, file_context_menu);
+	gtk_imhtml_class_register_protocol("audio://", audio_clicked_cb, audio_context_menu);
+
+	/* Example custom URL handler. */
+	gtk_imhtml_class_register_protocol("open://", open_dialog, dummy);
+
+	/* If we're under GNOME, try registering the system URL handlers. */
+	if (purple_running_gnome())
+		register_gnome_url_handlers();
+
+#ifdef _WIN32
+	winpidgin_register_win32_url_handlers();
+#endif
+
+}
+
+void pidgin_utils_uninit(void)
+{
+	gtk_imhtml_class_register_protocol("open://", NULL, NULL);
+
+	/* If we have GNOME handlers registered, unregister them. */
+	if (registered_url_handlers)
+	{
+		GSList *l;
+		for (l = registered_url_handlers; l; l = l->next)
+		{
+			gtk_imhtml_class_register_protocol((char *)l->data, NULL, NULL);
+			g_free(l->data);
+		}
+		g_slist_free(registered_url_handlers);
+		registered_url_handlers = NULL;
+		return;
+	}
+
+	gtk_imhtml_class_register_protocol("audio://", NULL, NULL);
+	gtk_imhtml_class_register_protocol("file://", NULL, NULL);
+
+	gtk_imhtml_class_register_protocol("http://", NULL, NULL);
+	gtk_imhtml_class_register_protocol("https://", NULL, NULL);
+	gtk_imhtml_class_register_protocol("ftp://", NULL, NULL);
+	gtk_imhtml_class_register_protocol("mailto:", NULL, NULL);
+	gtk_imhtml_class_register_protocol("gopher://", NULL, NULL);
+}
+
