changeset 23170:388e96b79206

propagate from branch 'im.pidgin.pidgin.next.minor' (head 71320664d16c85cf4292830959527b4509e2182b) to branch 'im.pidgin.pidgin' (head 75ea267822832fccf8671b593d79999e32b6c694)
author Sadrul Habib Chowdhury <imadil@gmail.com>
date Fri, 23 May 2008 16:47:59 +0000
parents ff8ec3d58367 (diff) dcb26bb0be73 (current diff)
children c767bcb40b36
files ChangeLog libpurple/protocols/bonjour/bonjour.c pidgin/gtkaccount.c
diffstat 80 files changed, 3921 insertions(+), 627 deletions(-) [+]
line wrap: on
line diff
--- a/COPYRIGHT	Fri May 23 16:46:19 2008 +0000
+++ b/COPYRIGHT	Fri May 23 16:47:59 2008 +0000
@@ -233,6 +233,7 @@
 Shlomi Loubaton
 Uli Luckas
 Matthew Luckie
+Marcus Lundblad
 Mike Lundy
 Jason Lynch
 Iain MacDonnell
@@ -289,6 +290,7 @@
 John Oyler
 Matt Pandina
 Laszlo Pandy
+Giulio 'Twain28' Pascali
 Ricardo Fernandez Pascual
 Riley Patterson
 Havoc Pennington
--- a/ChangeLog	Fri May 23 16:46:19 2008 +0000
+++ b/ChangeLog	Fri May 23 16:47:59 2008 +0000
@@ -1,10 +1,23 @@
 Pidgin and Finch: The Pimpin' Penguin IM Clients That're Good for the Soul
 
-version 2.4.3 (??/??/2008):
+version 2.5.0 (??/??/2008):
 	libpurple:
+	* Ability to create custom smileys (currently only the MSN protocol
+	  utilizes the feature). (Thanks to Mauro Sérgio Ferreira Brasil,
+	  Marcus Lundblad, Jorge Villaseñor and other contributors)
 	* Yahoo! Japan now uses UTF-8, matching the behavior of official clients
 	  and restoring compatibility with the web messenger (Yusuke Odate)
 
+	Pidgin:
+	* Custom buddy icons can now be added and removed to buddy list
+	  entries via the buddy list entry right-click menu.
+	* Resize large incoming custom smileys to a maximum of 96px on either
+	  side.
+
+	General:
+	* Group and Chat buddy list entries can now be given custom buddy
+	  icons.
+
 version 2.4.2 (05/17/2008):
 	libpurple:
 	* In MySpaceIM, messages from spambots are discarded (Justin Williams)
--- a/ChangeLog.API	Fri May 23 16:46:19 2008 +0000
+++ b/ChangeLog.API	Fri May 23 16:47:59 2008 +0000
@@ -1,6 +1,40 @@
 Pidgin and Finch: The Pimpin' Penguin IM Clients That're Good for the Soul
 
-version 2.4.2 (05/17/2008):
+version 2.5.0 (??/??/2008):
+	libpurple:
+		Added:
+		* Connection flag PURPLE_CONNECTION_ALLOW_CUSTOM_SMILEY to indicate
+		  that the connection supports sending and receiving custom smileys.
+		* PurpleSmiley and the Smiley API.
+		* purple_serv_got_join_chat_failed
+		* chat-join-failed signal (see conversation-signals.dox)
+		* chat-invite-blocked and blocked-im-msg signals (see
+		  converation-signals.dox) (Thanks to Stefan Ott)
+		* purple_blist_update_node_icon
+		* purple_buddy_icons_node_has_custom_icon
+		* purple_buddy_icons_node_find_custom_icon
+		* purple_buddy_icons_node_set_custom_icon
+		* purple_buddy_icons_node_set_custom_icon_from_file
+
+		Deprecated:
+		* purple_blist_update_buddy_icon
+		* purple_buddy_icons_has_custom_icon
+		* purple_buddy_icons_find_custom_icon
+		* purple_buddy_icons_set_custom_icon
+		* pidgin_set_custom_buddy_icon
+
+	pidgin:
+		Added:
+		* gtk_imhtml_smiley_create, gtk_imhtml_smiley_reload and
+		  gtk_imhtml_smiley_destroy to deal with GtkIMHtmlSmiley's.
+		* pidgin_pixbuf_from_imgstore to create a GdkPixbuf from a
+		  PurpleStoredImage.
+		* pidgin_themes_smiley_themeize_custom to associate custom smileys to
+		  a GtkIMHtml widget.
+		* GTK_IMHTML_CUSTOM_SMILEY flag for GtkIMHtml.
+		* GTK+ Custom Smiley API.
+
+version 2.4.2:
 	perl:
 		Added:
 		* Purple::Prefs::get_children_names.
--- a/Doxyfile.in	Fri May 23 16:46:19 2008 +0000
+++ b/Doxyfile.in	Fri May 23 16:47:59 2008 +0000
@@ -485,11 +485,11 @@
 # directories like "/usr/src/myproject". Separate the files or directories 
 # with spaces.
 
-INPUT                  = libpurple \
-                         finch \
-                         finch/libgnt \
-                         pidgin \
-                         doc
+INPUT                  = @top_srcdir@/libpurple \
+                         @top_srcdir@/finch \
+                         @top_srcdir@/finch/libgnt \
+                         @top_srcdir@/pidgin \
+                         @top_srcdir@/doc
 
 # This tag can be used to specify the character encoding of the source files that 
 # doxygen parses. Internally doxygen uses the UTF-8 encoding, which is also the default 
--- a/configure.ac	Fri May 23 16:46:19 2008 +0000
+++ b/configure.ac	Fri May 23 16:47:59 2008 +0000
@@ -43,19 +43,19 @@
 #
 # Make sure to update finch/libgnt/configure.ac with libgnt version changes.
 #
-m4_define([purple_lt_current], [4])
+m4_define([purple_lt_current], [5])
 m4_define([purple_major_version], [2])
-m4_define([purple_minor_version], [4])
-m4_define([purple_micro_version], [3])
+m4_define([purple_minor_version], [5])
+m4_define([purple_micro_version], [0])
 m4_define([purple_version_suffix], [devel])
 m4_define([purple_version],
           [purple_major_version.purple_minor_version.purple_micro_version])
 m4_define([purple_display_version], purple_version[]m4_ifdef([purple_version_suffix],[purple_version_suffix]))
 
-m4_define([gnt_lt_current], [4])
+m4_define([gnt_lt_current], [5])
 m4_define([gnt_major_version], [2])
-m4_define([gnt_minor_version], [4])
-m4_define([gnt_micro_version], [3])
+m4_define([gnt_minor_version], [5])
+m4_define([gnt_micro_version], [0])
 m4_define([gnt_version_suffix], [devel])
 m4_define([gnt_version],
           [gnt_major_version.gnt_minor_version.gnt_micro_version])
--- a/doc/conversation-signals.dox	Fri May 23 16:46:19 2008 +0000
+++ b/doc/conversation-signals.dox	Fri May 23 16:47:59 2008 +0000
@@ -7,6 +7,7 @@
   @signal sent-im-msg
   @signal receiving-im-msg
   @signal received-im-msg
+  @signal blocked-im-msg
   @signal writing-chat-msg
   @signal wrote-chat-msg
   @signal sending-chat-msg
@@ -26,7 +27,9 @@
   @signal chat-inviting-user
   @signal chat-invited-user
   @signal chat-invited
+  @signal chat-invite-blocked
   @signal chat-joined
+  @signal chat-join-failed
   @signal chat-left
   @signal chat-topic-changed
   @signal conversation-extended-menu
@@ -131,6 +134,21 @@
   @param flags   The IM message flags.
  @endsignaldef
 
+ @signaldef blocked-im-msg
+  @signalproto
+void (*blocked_im_msg)(PurpleAccount *account, const char *sender,
+        const char *message, PurpleMessageFlags flags, time_t when);
+  @endsignalproto
+  @signaldesc
+   Emitted after an IM is blocked due to privacy settings.
+  @param account The account the message was received on.
+  @param sender  The username of the sender.
+  @param message The message that was blocked.
+  @param flags   The IM message flags.
+  @param when    The time the message was sent.
+  @since 2.5.0
+ @endsignaldef
+
  @signaldef writing-chat-msg
   @signalproto
 gboolean (*writing_chat_msg)(PurpleAccount *account, const char *who,
@@ -302,6 +320,18 @@
   @param new_arrival If the buddy is a new arrival.
  @endsignaldef
 
+ @signaldef chat-join-failed
+  @signalproto
+void (*chat_join_failed)(PurpleConnection *gc, GHashTable *components);
+  @endsignalproto
+  @signaldesc
+   Emitted when an account fails to join a chat room
+  @param gc The PurpleConnection of the account which failed to join the chat.
+  @param data    The components passed to serv_join_chat() originally.
+                 The hash function should be g_str_hash() and the equal
+                 function should be g_str_equal().
+ @endsignaldef
+
  @signaldef chat-buddy-flags
   @signalproto
 void (*chat_buddy_flags)(PurpleConversation *conv, const char *name,
@@ -391,6 +421,21 @@
           default behavior will be maintained: the user will be prompted.
  @endsignaldef
 
+ @signaldef chat-invite-blocked
+  @signalproto
+void (*chat_invite_blocked)(PurpleAccount *account, const char *inviter,
+                   const char *name, const char *message, GHashTable *data);
+  @endsignalproto
+  @signaldesc
+   Emitted when an invitation to join a chat is blocked.
+  @param account  The account the invitation was sent to.
+  @param inviter  The name of the person sending the invitation.
+  @param name     The name of the chat invited to.
+  @param message  The invitation message sent.
+  @param data     Hashtable containing data about the invited chat.
+  @since 2.5.0
+ @endsignaldef
+
  @signaldef chat-joined
   @signalproto
 void (*chat_joined)(PurpleConversation *conv);
--- a/finch/libgnt/configure.ac	Fri May 23 16:46:19 2008 +0000
+++ b/finch/libgnt/configure.ac	Fri May 23 16:47:59 2008 +0000
@@ -24,10 +24,10 @@
 # Make sure to update ../../configure.ac with libgnt version changes.
 #
 
-m4_define([gnt_lt_current], [4])
+m4_define([gnt_lt_current], [5])
 m4_define([gnt_major_version], [2])
-m4_define([gnt_minor_version], [4])
-m4_define([gnt_micro_version], [2])
+m4_define([gnt_minor_version], [5])
+m4_define([gnt_micro_version], [0])
 m4_define([gnt_version_suffix], [])
 m4_define([gnt_version],
           [gnt_major_version.gnt_minor_version.gnt_micro_version])
--- a/libpurple/Makefile.am	Fri May 23 16:46:19 2008 +0000
+++ b/libpurple/Makefile.am	Fri May 23 16:47:59 2008 +0000
@@ -68,6 +68,7 @@
 	savedstatuses.c \
 	server.c \
 	signals.c \
+	smiley.c \
 	dnsquery.c \
 	dnssrv.c\
 	status.c \
@@ -120,6 +121,7 @@
 	savedstatuses.h \
 	server.h \
 	signals.h \
+	smiley.h \
 	dnsquery.h \
 	dnssrv.h \
 	status.h \
@@ -154,7 +156,7 @@
 
 dbus_exported = dbus-useful.h dbus-define-api.h account.h blist.h buddyicon.h \
                 connection.h conversation.h core.h ft.h log.h notify.h prefs.h roomlist.h \
-                savedstatuses.h status.h server.h util.h xmlnode.h prpl.h
+                savedstatuses.h smiley.h status.h server.h util.h xmlnode.h prpl.h
 
 purple_build_coreheaders = $(addprefix $(srcdir)/, $(purple_coreheaders)) \
 		$(purple_builtheaders)
--- a/libpurple/Makefile.mingw	Fri May 23 16:46:19 2008 +0000
+++ b/libpurple/Makefile.mingw	Fri May 23 16:47:59 2008 +0000
@@ -65,6 +65,7 @@
 			savedstatuses.c \
 			server.c \
 			signals.c \
+			smiley.c \
 			sound.c \
 			sslconn.c \
 			status.c \
--- a/libpurple/blist.c	Fri May 23 16:46:19 2008 +0000
+++ b/libpurple/blist.c	Fri May 23 16:47:59 2008 +0000
@@ -824,16 +824,25 @@
 		ops->update(purplebuddylist, (PurpleBlistNode *)buddy);
 }
 
-void purple_blist_update_buddy_icon(PurpleBuddy *buddy)
+void
+purple_blist_update_node_icon(PurpleBlistNode *node)
 {
 	PurpleBlistUiOps *ops = purple_blist_get_ui_ops();
 
-	g_return_if_fail(buddy != NULL);
+	g_return_if_fail(node != NULL);
 
 	if (ops && ops->update)
-		ops->update(purplebuddylist, (PurpleBlistNode *)buddy);
+		ops->update(purplebuddylist, node);
 }
 
+#ifndef PURPLE_DISABLE_DEPRECATED
+void
+purple_blist_update_buddy_icon(PurpleBuddy *buddy)
+{
+	purple_blist_update_node_icon((PurpleBlistNode *)buddy);
+}
+#endif
+
 /*
  * TODO: Maybe remove the call to this from server.c and call it
  * from oscar.c and toc.c instead?
@@ -1197,7 +1206,7 @@
 
 	purple_signal_emit(purple_blist_get_handle(), "buddy-icon-changed", buddy);
 
-	purple_blist_update_buddy_icon(buddy);
+	purple_blist_update_node_icon((PurpleBlistNode*)buddy);
 }
 
 PurpleAccount *
@@ -2189,7 +2198,7 @@
 	g_return_val_if_fail((name != NULL) && (*name != '\0'), NULL);
 
 	for (node = purplebuddylist->root; node != NULL; node = node->next) {
-		if (!strcmp(((PurpleGroup *)node)->name, name))
+		if (!purple_utf8_strcasecmp(((PurpleGroup *)node)->name, name))
 			return (PurpleGroup *)node;
 	}
 
--- a/libpurple/blist.h	Fri May 23 16:46:19 2008 +0000
+++ b/libpurple/blist.h	Fri May 23 16:47:59 2008 +0000
@@ -317,11 +317,22 @@
 void purple_blist_update_buddy_status(PurpleBuddy *buddy, PurpleStatus *old_status);
 
 /**
+ * Updates a node's custom icon.
+ *
+ * @param node  The PurpleBlistNode whose custom icon has changed.
+ * @since 2.5.0
+ */
+void purple_blist_update_node_icon(PurpleBlistNode *node);
+
+#ifndef PURPLE_DISABLE_DEPRECATED
+/**
  * Updates a buddy's icon.
  *
  * @param buddy  The buddy whose buddy icon has changed
+ * @deprecated Use purple_blist_update_node_icon() instead.
  */
 void purple_blist_update_buddy_icon(PurpleBuddy *buddy);
+#endif
 
 /**
  * Renames a buddy in the buddy list.
--- a/libpurple/buddyicon.c	Fri May 23 16:46:19 2008 +0000
+++ b/libpurple/buddyicon.c	Fri May 23 16:47:59 2008 +0000
@@ -92,8 +92,10 @@
  */
 static GHashTable *icon_file_cache = NULL;
 
-/* This one is used for both custom buddy icons
- * on PurpleContacts and account icons. */
+/**
+ * This hash table is used for both custom buddy icons on PurpleBlistNodes and
+ * account icons.
+ */
 static GHashTable *pointer_icon_cache = NULL;
 
 static char       *cache_dir     = NULL;
@@ -684,14 +686,6 @@
 	return (icon ? purple_buddy_icon_ref(icon) : NULL);
 }
 
-gboolean
-purple_buddy_icons_has_custom_icon(PurpleContact *contact)
-{
-	g_return_val_if_fail(contact != NULL, FALSE);
-
-	return (purple_blist_node_get_string((PurpleBlistNode*)contact, "custom_buddy_icon") != NULL);
-}
-
 PurpleStoredImage *
 purple_buddy_icons_find_account_icon(PurpleAccount *account)
 {
@@ -807,24 +801,32 @@
 	return ret;
 }
 
-PurpleStoredImage *
-purple_buddy_icons_find_custom_icon(PurpleContact *contact)
+gboolean
+purple_buddy_icons_node_has_custom_icon(PurpleBlistNode *node)
 {
-	PurpleStoredImage *img;
-	const char *custom_icon_file;
-	const char *dirname;
+	g_return_val_if_fail(node != NULL, FALSE);
+
+	return (purple_blist_node_get_string(node, "custom_buddy_icon") != NULL);
+}
+
+PurpleStoredImage *
+purple_buddy_icons_node_find_custom_icon(PurpleBlistNode *node)
+{
 	char *path;
+	size_t len;
 	guchar *data;
-	size_t len;
+	PurpleStoredImage *img;
+	const char *custom_icon_file, *dirname;
 
-	g_return_val_if_fail(contact != NULL, NULL);
+	g_return_val_if_fail(node != NULL, NULL);
 
-	if ((img = g_hash_table_lookup(pointer_icon_cache, contact)))
+	if ((img = g_hash_table_lookup(pointer_icon_cache, node)))
 	{
 		return purple_imgstore_ref(img);
 	}
 
-	custom_icon_file = purple_blist_node_get_string((PurpleBlistNode*)contact, "custom_buddy_icon");
+	custom_icon_file = purple_blist_node_get_string(node,
+	                                                "custom_buddy_icon");
 
 	if (custom_icon_file == NULL)
 		return NULL;
@@ -836,7 +838,7 @@
 	{
 		g_free(path);
 		img = purple_buddy_icon_data_new(data, len, custom_icon_file);
-		g_hash_table_insert(pointer_icon_cache, contact, img);
+		g_hash_table_insert(pointer_icon_cache, node, img);
 		return img;
 	}
 	g_free(path);
@@ -845,66 +847,79 @@
 }
 
 PurpleStoredImage *
-purple_buddy_icons_set_custom_icon(PurpleContact *contact,
-                                   guchar *icon_data, size_t icon_len)
+purple_buddy_icons_node_set_custom_icon(PurpleBlistNode *node,
+                                        guchar *icon_data, size_t icon_len)
 {
+	char *old_icon;
 	PurpleStoredImage *old_img;
 	PurpleStoredImage *img = NULL;
-	char *old_icon;
-	PurpleBlistNode *child;
+
+	g_return_val_if_fail(node != NULL, NULL);
 
-	old_img = g_hash_table_lookup(pointer_icon_cache, contact);
+	if (!PURPLE_BLIST_NODE_IS_CONTACT(node) &&
+	    !PURPLE_BLIST_NODE_IS_CHAT(node) &&
+	    !PURPLE_BLIST_NODE_IS_GROUP(node)) {
+		return NULL;
+	}
 
-	if (icon_data != NULL && icon_len > 0)
-	{
+	old_img = g_hash_table_lookup(pointer_icon_cache, node);
+
+	if (icon_data != NULL && icon_len > 0) {
 		img = purple_buddy_icon_data_new(icon_data, icon_len, NULL);
 	}
 
-	old_icon = g_strdup(purple_blist_node_get_string((PurpleBlistNode *)contact,
+	old_icon = g_strdup(purple_blist_node_get_string(node,
 	                                                 "custom_buddy_icon"));
-	if (img && purple_buddy_icons_is_caching())
-	{
+	if (img && purple_buddy_icons_is_caching()) {
 		const char *filename = purple_imgstore_get_filename(img);
-		purple_blist_node_set_string((PurpleBlistNode *)contact,
-		                             "custom_buddy_icon",
+		purple_blist_node_set_string(node, "custom_buddy_icon",
 		                             filename);
 		ref_filename(filename);
-	}
-	else
-	{
-		purple_blist_node_remove_setting((PurpleBlistNode *)contact,
-		                                 "custom_buddy_icon");
+	} else {
+		purple_blist_node_remove_setting(node, "custom_buddy_icon");
 	}
 	unref_filename(old_icon);
 
 	if (img)
-		g_hash_table_insert(pointer_icon_cache, contact, img);
+		g_hash_table_insert(pointer_icon_cache, node, img);
 	else
-		g_hash_table_remove(pointer_icon_cache, contact);
+		g_hash_table_remove(pointer_icon_cache, node);
 
-	for (child = contact->node.child ; child ; child = child->next)
-	{
-		PurpleBuddy *buddy;
-		PurpleConversation *conv;
+	if (PURPLE_BLIST_NODE_IS_CONTACT(node)) {
+		PurpleBlistNode *child;
+		for (child = node->child ; child ; child = child->next)
+		{
+			PurpleBuddy *buddy;
+			PurpleConversation *conv;
+
+			if (!PURPLE_BLIST_NODE_IS_BUDDY(child))
+				continue;
+
+			buddy = (PurpleBuddy *)child;
 
-		if (!PURPLE_BLIST_NODE_IS_BUDDY(child))
-			continue;
-
-		buddy = (PurpleBuddy *)child;
+			conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, purple_buddy_get_name(buddy), purple_buddy_get_account(buddy));
+			if (conv)
+				purple_conversation_update(conv, PURPLE_CONV_UPDATE_ICON);
 
-		conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM,
-		                                             purple_buddy_get_name(buddy),
-		                                             purple_buddy_get_account(buddy));
-		if (conv)
+			/* Is this call necessary anymore? Can the buddies
+			 * themselves need updating when the custom buddy
+			 * icon changes? */
+			purple_blist_update_node_icon((PurpleBlistNode*)buddy);
+		}
+	} else if (PURPLE_BLIST_NODE_IS_CHAT(node)) {
+		PurpleConversation *conv = NULL;
+
+		conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_CHAT, purple_chat_get_name((PurpleChat*)node), purple_chat_get_account((PurpleChat*)node));
+		if (conv) {
 			purple_conversation_update(conv, PURPLE_CONV_UPDATE_ICON);
-
-		purple_blist_update_buddy_icon(buddy);
+		}
 	}
 
-	if (old_img)
+	purple_blist_update_node_icon(node);
+
+	if (old_img) {
 		purple_imgstore_unref(old_img);
-	else if (old_icon)
-	{
+	} else if (old_icon) {
 		/* The old icon may not have been loaded into memory.  In that
 		 * case, we'll need to uncache the filename.  The filenames
 		 * are ref-counted, so this is safe. */
@@ -915,6 +930,49 @@
 	return img;
 }
 
+PurpleStoredImage *
+purple_buddy_icons_node_set_custom_icon_from_file(PurpleBlistNode *node,
+                                                  const gchar *filename)
+{
+	size_t len;
+	guchar *data;
+
+	g_return_val_if_fail(node != NULL, NULL);
+
+	if (!PURPLE_BLIST_NODE_IS_CONTACT(node) &&
+	    !PURPLE_BLIST_NODE_IS_CHAT(node) &&
+	    !PURPLE_BLIST_NODE_IS_GROUP(node)) {
+		return NULL;
+	}
+
+	if (!read_icon_file(filename, &data, &len)) {
+		return NULL;
+	}
+
+	return purple_buddy_icons_node_set_custom_icon(node, data, len);
+}
+
+#ifndef PURPLE_DISABLE_DEPRECATED
+gboolean
+purple_buddy_icons_has_custom_icon(PurpleContact *contact)
+{
+	return purple_buddy_icons_node_has_custom_icon((PurpleBlistNode*)contact);
+}
+
+PurpleStoredImage *
+purple_buddy_icons_find_custom_icon(PurpleContact *contact)
+{
+	return purple_buddy_icons_node_find_custom_icon((PurpleBlistNode*)contact);
+}
+
+PurpleStoredImage *
+purple_buddy_icons_set_custom_icon(PurpleContact *contact, guchar *icon_data,
+                                   size_t icon_len)
+{
+	return purple_buddy_icons_node_set_custom_icon((PurpleBlistNode*)contact, icon_data, icon_len);
+}
+#endif
+
 void
 _purple_buddy_icon_set_old_icons_dir(const char *dirname)
 {
@@ -1138,7 +1196,9 @@
 				}
 			}
 		}
-		else if (PURPLE_BLIST_NODE_IS_CONTACT(node))
+		else if (PURPLE_BLIST_NODE_IS_CONTACT(node) ||
+		         PURPLE_BLIST_NODE_IS_CHAT(node) ||
+		         PURPLE_BLIST_NODE_IS_GROUP(node))
 		{
 			const char *filename;
 
--- a/libpurple/buddyicon.h	Fri May 23 16:46:19 2008 +0000
+++ b/libpurple/buddyicon.h	Fri May 23 16:47:59 2008 +0000
@@ -218,16 +218,6 @@
 purple_buddy_icons_find(PurpleAccount *account, const char *username);
 
 /**
- * Returns a boolean indicating if a given contact has a custom buddy icon.
- *
- * @param contact The contact
- *
- * @return A boolean indicating if @a contact has a custom buddy icon.
- */
-gboolean
-purple_buddy_icons_has_custom_icon(PurpleContact *contact);
-
-/**
  * Returns the buddy icon image for an account.
  *
  * The caller owns a reference to the image in the store, and must dereference
@@ -277,7 +267,18 @@
 purple_buddy_icons_get_account_icon_timestamp(PurpleAccount *account);
 
 /**
- * Returns the custom buddy icon image for a contact.
+ * Returns a boolean indicating if a given blist node has a custom buddy icon.
+ *
+ * @param node The blist node.
+ *
+ * @return A boolean indicating if @a node has a custom buddy icon.
+ * @since 2.5.0
+ */
+gboolean
+purple_buddy_icons_node_has_custom_icon(PurpleBlistNode *node);
+
+/**
+ * Returns the custom buddy icon image for a blist node.
  *
  * The caller owns a reference to the image in the store, and must dereference
  * the image with purple_imgstore_unref() for it to be freed.
@@ -286,31 +287,82 @@
  * needed, so it should be called in any case where you want the
  * appropriate icon.
  *
- * @param contact The contact
+ * @param node The node.
+ *
+ * @return The custom buddy icon.
+ * @since 2.5.0
+ */
+PurpleStoredImage *
+purple_buddy_icons_node_find_custom_icon(PurpleBlistNode *node);
+
+/**
+ * Sets a custom buddy icon for a blist node.
+ *
+ * This function will deal with saving a record of the icon, caching the data,
+ * etc.
+ *
+ * @param node      The blist node for which to set a custom icon.
+ * @param icon_data The image data of the icon, which the buddy icon code will
+ *                  free.
+ * @param icon_len  The length of the data in @a icon_data.
+ *
+ * @return The icon that was set. The caller does NOT own a reference to this,
+ *         and must call purple_imgstore_ref() if it wants one.
+ * @since 2.5.0
+ */
+PurpleStoredImage *
+purple_buddy_icons_node_set_custom_icon(PurpleBlistNode *node,
+                                        guchar *icon_data, size_t icon_len);
+
+/**
+ * Sets a custom buddy icon for a blist node.
  *
- * @return The custom buddy icon image.
+ * Convenience wrapper around purple_buddy_icons_node_set_custom_icon.
+ * @see purple_buddy_icons_node_set_custom_icon()
+ *
+ * @param node      The blist node for which to set a custom icon.
+ * @param filename  The path to the icon to set for the blist node.
+ *
+ * @return The icon that was set. The caller does NOT own a reference to this,
+ *         and must call purple_imgstore_ref() if it wants one.
+ * @since 2.5.0
+ */
+PurpleStoredImage *
+purple_buddy_icons_node_set_custom_icon_from_file(PurpleBlistNode *node,
+                                                  const gchar *filename);
+
+#ifndef PURPLE_DISABLE_DEPRECATED
+/**
+ * PurpleContact version of purple_buddy_icons_node_has_custom_icon.
+ *
+ * @copydoc purple_buddy_icons_node_has_custom_icon()
+ *
+ * @deprecated Use purple_buddy_icons_node_has_custom_icon instead.
+ */
+gboolean
+purple_buddy_icons_has_custom_icon(PurpleContact *contact);
+
+/**
+ * PurpleContact version of purple_buddy_icons_node_find_custom_icon.
+ *
+ * @copydoc purple_buddy_icons_node_find_custom_icon()
+ *
+ * @deprecated Use purple_buddy_icons_node_find_custom_icon instead.
  */
 PurpleStoredImage *
 purple_buddy_icons_find_custom_icon(PurpleContact *contact);
 
 /**
- * Sets a custom buddy icon for a user.
- *
- * This function will deal with saving a record of the icon,
- * caching the data, etc.
+ * PurpleContact version of purple_buddy_icons_node_set_custom_icon.
  *
- * @param contact   The contact for which to set a custom icon.
- * @param icon_data The image data of the icon, which the
- *                  buddy icon code will free.
- * @param icon_len  The length of the data in @a icon_data.
+ * @copydoc purple_buddy_icons_node_set_custom_icon()
  *
- * @return The icon that was set.  The caller does NOT own
- *         a reference to this, and must call purple_imgstore_ref()
- *         if it wants one.
+ * @deprecated Use purple_buddy_icons_node_set_custom_icon instead.
  */
 PurpleStoredImage *
 purple_buddy_icons_set_custom_icon(PurpleContact *contact,
                                    guchar *icon_data, size_t icon_len);
+#endif
 
 /**
  * Sets whether or not buddy icon caching is enabled.
--- a/libpurple/connection.h	Fri May 23 16:46:19 2008 +0000
+++ b/libpurple/connection.h	Fri May 23 16:47:59 2008 +0000
@@ -43,6 +43,7 @@
 	PURPLE_CONNECTION_NO_FONTSIZE = 0x0020, /**< Connection does not send/receive font sizes */
 	PURPLE_CONNECTION_NO_URLDESC = 0x0040,  /**< Connection does not support descriptions with links */ 
 	PURPLE_CONNECTION_NO_IMAGES = 0x0080,  /**< Connection does not support sending of images */
+	PURPLE_CONNECTION_ALLOW_CUSTOM_SMILEY = 0x0100, /**< Connection supports sending and receiving custom smileys */
 
 } PurpleConnectionFlags;
 
--- a/libpurple/conversation.c	Fri May 23 16:46:19 2008 +0000
+++ b/libpurple/conversation.c	Fri May 23 16:47:59 2008 +0000
@@ -2221,6 +2221,16 @@
 										PURPLE_SUBTYPE_CONVERSATION),
 						 purple_value_new(PURPLE_TYPE_UINT));
 
+	purple_signal_register(handle, "blocked-im-msg",
+						 purple_marshal_VOID__POINTER_POINTER_POINTER_UINT_UINT,
+						 NULL, 5,
+						 purple_value_new(PURPLE_TYPE_SUBTYPE,
+							 PURPLE_SUBTYPE_ACCOUNT),
+						 purple_value_new(PURPLE_TYPE_STRING),
+						 purple_value_new(PURPLE_TYPE_STRING),
+						 purple_value_new(PURPLE_TYPE_UINT),
+						 purple_value_new(PURPLE_TYPE_UINT));
+
 	purple_signal_register(handle, "writing-chat-msg",
 						 purple_marshal_BOOLEAN__POINTER_POINTER_POINTER_POINTER_UINT,
 						 purple_value_new(PURPLE_TYPE_BOOLEAN), 5,
@@ -2376,11 +2386,27 @@
 						 purple_value_new(PURPLE_TYPE_STRING),
 						 purple_value_new(PURPLE_TYPE_POINTER));
 
+	purple_signal_register(handle, "chat-invite-blocked",
+						 purple_marshal_VOID__POINTER_POINTER_POINTER_POINTER_POINTER,
+						 NULL, 5,
+						 purple_value_new(PURPLE_TYPE_SUBTYPE,
+							 PURPLE_SUBTYPE_ACCOUNT),
+						 purple_value_new(PURPLE_TYPE_STRING),
+						 purple_value_new(PURPLE_TYPE_STRING),
+						 purple_value_new(PURPLE_TYPE_STRING),
+						 purple_value_new(PURPLE_TYPE_BOXED, "GHashTable *"));
+
 	purple_signal_register(handle, "chat-joined",
 						 purple_marshal_VOID__POINTER, NULL, 1,
 						 purple_value_new(PURPLE_TYPE_SUBTYPE,
 										PURPLE_SUBTYPE_CONVERSATION));
 
+	purple_signal_register(handle, "chat-join-failed",
+						   purple_marshal_VOID__POINTER_POINTER, NULL, 2,
+						   purple_value_new(PURPLE_TYPE_SUBTYPE,
+										PURPLE_SUBTYPE_CONNECTION),
+						   purple_value_new(PURPLE_TYPE_POINTER));
+
 	purple_signal_register(handle, "chat-left",
 						 purple_marshal_VOID__POINTER, NULL, 1,
 						 purple_value_new(PURPLE_TYPE_SUBTYPE,
--- a/libpurple/core.c	Fri May 23 16:46:19 2008 +0000
+++ b/libpurple/core.c	Fri May 23 16:47:59 2008 +0000
@@ -43,6 +43,7 @@
 #include "proxy.h"
 #include "savedstatuses.h"
 #include "signals.h"
+#include "smiley.h"
 #include "sound.h"
 #include "sslconn.h"
 #include "status.h"
@@ -166,6 +167,7 @@
 	purple_stun_init();
 	purple_xfers_init();
 	purple_idle_init();
+	purple_smileys_init();
 
 	/*
 	 * Call this early on to try to auto-detect our IP address and
@@ -194,6 +196,7 @@
 	purple_connections_disconnect_all();
 
 	/* Save .xml files, remove signals, etc. */
+	purple_smileys_uninit();
 	purple_idle_uninit();
 	purple_ssl_uninit();
 	purple_pounces_uninit();
--- a/libpurple/dbus-analyze-functions.py	Fri May 23 16:46:19 2008 +0000
+++ b/libpurple/dbus-analyze-functions.py	Fri May 23 16:47:59 2008 +0000
@@ -483,6 +483,7 @@
         self.inputiter = iter(inputfile)
         self.functionregexp = \
              re.compile("^%s(\w[^()]*)\(([^()]*)\)\s*;\s*$" % fprefix)    
+        self.typeregexp = re.compile("^\w+\s*\*?\s*$")
 
 
                 
@@ -501,7 +502,7 @@
             # accumulate lines until the parentheses are balance or an
             # empty line has been encountered
             myline = line.strip()
-            while myline.count("(") > myline.count(")"):
+            while (myline.count("(") > myline.count(")")) or self.typeregexp.match(myline):
                 newline = self.inputiter.next().strip()
                 if len(newline) == 0:
                     break
--- a/libpurple/dbus-server.c	Fri May 23 16:46:19 2008 +0000
+++ b/libpurple/dbus-server.c	Fri May 23 16:47:59 2008 +0000
@@ -40,6 +40,7 @@
 #include "core.h"
 #include "internal.h"
 #include "savedstatuses.h"
+#include "smiley.h"
 #include "util.h"
 #include "value.h"
 #include "xmlnode.h"
--- a/libpurple/imgstore.c	Fri May 23 16:46:19 2008 +0000
+++ b/libpurple/imgstore.c	Fri May 23 16:47:59 2008 +0000
@@ -68,6 +68,22 @@
 	return img;
 }
 
+PurpleStoredImage *
+purple_imgstore_new_from_file(const char *path)
+{
+	gchar *data = NULL;
+	size_t len;
+	GError *err = NULL;
+
+	if (!g_file_get_contents(path, &data, &len, &err)) {
+		purple_debug_error("imgstore", "Error reading %s: %s\n",
+				path, err->message);
+		g_error_free(err);
+		return NULL;
+	}
+	return purple_imgstore_add(data, len, path);
+}
+
 int
 purple_imgstore_add_with_id(gpointer data, size_t size, const char *filename)
 {
--- a/libpurple/imgstore.h	Fri May 23 16:46:19 2008 +0000
+++ b/libpurple/imgstore.h	Fri May 23 16:47:59 2008 +0000
@@ -63,6 +63,17 @@
 purple_imgstore_add(gpointer data, size_t size, const char *filename);
 
 /**
+ * Create an image and add it to the store.
+ *
+ * @param path  The path to the image.
+ *
+ * @return  The stored image.
+ * @since 2.X.X
+ */
+PurpleStoredImage *
+purple_imgstore_new_from_file(const char *path);
+
+/**
  * Add an image to the store, allocating an ID.
  *
  * The caller owns a reference to the image in the store, and must dereference
--- a/libpurple/plugins/perl/Makefile.am	Fri May 23 16:46:19 2008 +0000
+++ b/libpurple/plugins/perl/Makefile.am	Fri May 23 16:47:59 2008 +0000
@@ -67,6 +67,7 @@
 	common/SavedStatuses.xs \
 	common/Server.xs \
 	common/Signal.xs \
+	common/Smiley.xs \
 	common/Sound.xs \
 	common/Status.xs \
 	common/Stringref.xs \
--- a/libpurple/plugins/perl/common/MANIFEST	Fri May 23 16:46:19 2008 +0000
+++ b/libpurple/plugins/perl/common/MANIFEST	Fri May 23 16:47:59 2008 +0000
@@ -28,6 +28,7 @@
 SavedStatuses.xs
 Server.xs
 Signal.xs
+Smiley.xs
 Sound.xs
 Status.xs
 Stringref.xs
--- a/libpurple/plugins/perl/common/Makefile.mingw	Fri May 23 16:46:19 2008 +0000
+++ b/libpurple/plugins/perl/common/Makefile.mingw	Fri May 23 16:47:59 2008 +0000
@@ -61,8 +61,9 @@
 				Roomlist.xs \
 				SSLConn.xs \
 				SavedStatuses.xs \
+				Server.xs \
 				Signal.xs \
-				Server.xs \
+				Smiley.xs \
 				Sound.xs \
 				Status.xs \
 				Stringref.xs \
--- a/libpurple/plugins/perl/common/Purple.xs	Fri May 23 16:46:19 2008 +0000
+++ b/libpurple/plugins/perl/common/Purple.xs	Fri May 23 16:47:59 2008 +0000
@@ -30,6 +30,7 @@
 PURPLE_PERL_BOOT_PROTO(SavedStatus);
 PURPLE_PERL_BOOT_PROTO(Serv);
 PURPLE_PERL_BOOT_PROTO(Signal);
+PURPLE_PERL_BOOT_PROTO(Smiley);
 PURPLE_PERL_BOOT_PROTO(Sound);
 PURPLE_PERL_BOOT_PROTO(Status);
 PURPLE_PERL_BOOT_PROTO(Stringref);
@@ -68,6 +69,7 @@
 	PURPLE_PERL_BOOT(SavedStatus);
 	PURPLE_PERL_BOOT(Serv);
 	PURPLE_PERL_BOOT(Signal);
+	PURPLE_PERL_BOOT(Smiley);
 	PURPLE_PERL_BOOT(Sound);
 	PURPLE_PERL_BOOT(Status);
 	PURPLE_PERL_BOOT(Stringref);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/plugins/perl/common/Smiley.xs	Fri May 23 16:47:59 2008 +0000
@@ -0,0 +1,81 @@
+#include "module.h"
+
+MODULE = Purple::Smiley  PACKAGE = Purple::Smiley  PREFIX = purple_smiley_
+PROTOTYPES: ENABLE
+
+Purple::Smiley
+purple_smiley_new(img, shortcut)
+	Purple::StoredImage img
+	const char * shortcut
+
+Purple::Smiley
+purple_smiley_new_from_file(shortcut, filepath)
+	const char * shortcut
+	const char * filepath
+
+void
+purple_smiley_delete(smiley)
+	Purple::Smiley smiley
+
+gboolean
+purple_smiley_set_shortcut(smiley, shortcut)
+	Purple::Smiley smiley
+	const char * shortcut
+
+void
+purple_smiley_set_data(smiley, data, data_len, keepfilename)
+	Purple::Smiley  smiley
+	guchar * data
+	size_t  data_len
+	gboolean keepfilename
+
+const char *
+purple_smiley_get_shortcut(smiley)
+	Purple::Smiley smiley
+
+const char *
+purple_smiley_get_checksum(smiley)
+	Purple::Smiley smiley
+
+Purple::StoredImage
+purple_smiley_get_stored_image(smiley)
+	Purple::Smiley smiley
+
+gconstpointer
+purple_smiley_get_data(smiley, len)
+	Purple::Smiley smiley
+	size_t * len
+
+const char *
+purple_smiley_get_extension(smiley)
+	Purple::Smiley smiley
+
+
+gchar_own *
+purple_smiley_get_full_path(smiley)
+	Purple::Smiley smiley
+
+
+MODULE = Purple::Smiley  PACKAGE = Purple::Smileys  PREFIX = purple_smileys_
+PROTOTYPES: ENABLE
+
+void
+purple_smileys_get_all()
+PREINIT:
+    GList *l;
+PPCODE:
+    for (l = purple_smileys_get_all(); l != NULL; l = g_list_delete_link(l, l)) {
+        XPUSHs(sv_2mortal(purple_perl_bless_object(l->data, "Purple::Smiley")));
+    }
+
+Purple::Smiley
+purple_smileys_find_by_shortcut(shortcut)
+	const char * shortcut
+
+Purple::Smiley
+purple_smileys_find_by_checksum(checksum)
+	const char * checksum
+
+const char *
+purple_smileys_get_storing_dir()
+
--- a/libpurple/plugins/perl/common/module.h	Fri May 23 16:46:19 2008 +0000
+++ b/libpurple/plugins/perl/common/module.h	Fri May 23 16:47:59 2008 +0000
@@ -51,6 +51,7 @@
 #include "savedstatuses.h"
 #include "server.h"
 #include "signals.h"
+#include "smiley.h"
 #include "sound.h"
 #include "sslconn.h"
 #include "status.h"
@@ -240,6 +241,9 @@
 typedef PurpleSavedStatus *		Purple__SavedStatus;
 typedef PurpleSavedStatusSub *		Purple__SavedStatus__Sub;
 
+/* smiley.h */
+typedef PurpleSmiley *		Purple__Smiley;
+
 /* sound.h */
 typedef PurpleSoundEventID		Purple__SoundEventID;
 typedef PurpleSoundUiOps *		Purple__Sound__UiOps;
--- a/libpurple/plugins/perl/common/typemap	Fri May 23 16:46:19 2008 +0000
+++ b/libpurple/plugins/perl/common/typemap	Fri May 23 16:47:59 2008 +0000
@@ -151,6 +151,7 @@
 
 Purple::Presence				T_PurpleObj
 Purple::PresenceContext			T_IV
+Purple::Smiley				T_PurpleObj
 Purple::Status				T_PurpleObj
 Purple::StatusAttr			T_PurpleObj
 Purple::StatusPrimitive			T_IV
--- a/libpurple/protocols/bonjour/bonjour.c	Fri May 23 16:46:19 2008 +0000
+++ b/libpurple/protocols/bonjour/bonjour.c	Fri May 23 16:47:59 2008 +0000
@@ -504,6 +504,7 @@
 	NULL,
 	NULL,
 	NULL,
+	sizeof(PurplePluginProtocolInfo),       /* struct_size */
 	NULL
 };
 
--- a/libpurple/protocols/gg/gg.c	Fri May 23 16:46:19 2008 +0000
+++ b/libpurple/protocols/gg/gg.c	Fri May 23 16:47:59 2008 +0000
@@ -1998,7 +1998,7 @@
 
 	serv_got_chat_in(gc, id,
 			 purple_account_get_username(purple_connection_get_account(gc)),
-			 0, message, time(NULL));
+			 flags, message, time(NULL));
 
 	return 0;
 }
@@ -2152,6 +2152,7 @@
 	NULL,
 	NULL,
 	NULL,
+	sizeof(PurplePluginProtocolInfo),       /* struct_size */
 	NULL
 };
 /* }}} */
--- a/libpurple/protocols/irc/irc.c	Fri May 23 16:46:19 2008 +0000
+++ b/libpurple/protocols/irc/irc.c	Fri May 23 16:47:59 2008 +0000
@@ -737,7 +737,7 @@
 
 	irc_cmd_privmsg(irc, "msg", NULL, args);
 
-	serv_got_chat_in(gc, id, purple_connection_get_display_name(gc), 0, what, time(NULL));
+	serv_got_chat_in(gc, id, purple_connection_get_display_name(gc), flags, what, time(NULL));
 	g_free(tmp);
 	return 0;
 }
@@ -909,6 +909,7 @@
 	NULL,
 	NULL,
 	NULL,
+	sizeof(PurplePluginProtocolInfo),       /* struct_size */
 	NULL
 };
 
--- a/libpurple/protocols/jabber/chat.c	Fri May 23 16:46:19 2008 +0000
+++ b/libpurple/protocols/jabber/chat.c	Fri May 23 16:47:59 2008 +0000
@@ -197,6 +197,12 @@
 	return chat_name;
 }
 
+static void insert_in_hash_table(gpointer key, gpointer value, gpointer user_data)
+{
+	GHashTable *hash_table = (GHashTable *)user_data;
+	g_hash_table_insert(hash_table, g_strdup(key), g_strdup(value));
+}
+
 void jabber_chat_join(PurpleConnection *gc, GHashTable *data)
 {
 	JabberChat *chat;
@@ -225,18 +231,21 @@
 		char *buf = g_strdup_printf(_("%s is not a valid room name"), room);
 		purple_notify_error(gc, _("Invalid Room Name"), _("Invalid Room Name"),
 				buf);
+		purple_serv_got_join_chat_failed(gc, data);
 		g_free(buf);
 		return;
 	} else if(!jabber_nameprep_validate(server)) {
 		char *buf = g_strdup_printf(_("%s is not a valid server name"), server);
 		purple_notify_error(gc, _("Invalid Server Name"),
 				_("Invalid Server Name"), buf);
+		purple_serv_got_join_chat_failed(gc, data);
 		g_free(buf);
 		return;
 	} else if(!jabber_resourceprep_validate(handle)) {
 		char *buf = g_strdup_printf(_("%s is not a valid room handle"), handle);
 		purple_notify_error(gc, _("Invalid Room Handle"),
 				_("Invalid Room Handle"), buf);
+		purple_serv_got_join_chat_failed(gc, data);
 		g_free(buf);
 		return;
 	}
@@ -255,6 +264,11 @@
 	chat->server = g_strdup(server);
 	chat->handle = g_strdup(handle);
 
+	/* Copy the data hash table to chat->components */
+	chat->components = g_hash_table_new_full(g_str_hash, g_str_equal,
+			g_free, g_free);
+	g_hash_table_foreach(data, insert_in_hash_table, chat->components);
+
 	chat->members = g_hash_table_new_full(g_str_hash, g_str_equal, NULL,
 			(GDestroyNotify)jabber_chat_member_free);
 
@@ -315,6 +329,7 @@
 	g_free(chat->server);
 	g_free(chat->handle);
 	g_hash_table_destroy(chat->members);
+	g_hash_table_destroy(chat->components);
 	g_free(chat);
 }
 
--- a/libpurple/protocols/jabber/chat.h	Fri May 23 16:46:19 2008 +0000
+++ b/libpurple/protocols/jabber/chat.h	Fri May 23 16:47:59 2008 +0000
@@ -41,6 +41,7 @@
 	char *room;
 	char *server;
 	char *handle;
+	GHashTable *components;
 	int id;
 	PurpleConversation *conv;
 	gboolean muc;
--- a/libpurple/protocols/jabber/libxmpp.c	Fri May 23 16:46:19 2008 +0000
+++ b/libpurple/protocols/jabber/libxmpp.c	Fri May 23 16:47:59 2008 +0000
@@ -116,7 +116,7 @@
 	jabber_send_attention,			/* send_attention */
 	jabber_attention_types,			/* attention_types */
 
-	/* padding */
+	sizeof(PurplePluginProtocolInfo),       /* struct_size */
 	NULL
 };
 
--- a/libpurple/protocols/jabber/presence.c	Fri May 23 16:46:19 2008 +0000
+++ b/libpurple/protocols/jabber/presence.c	Fri May 23 16:47:59 2008 +0000
@@ -587,6 +587,7 @@
 					serv_got_chat_left(js->gc, chat->id);
 			} else {
 				title = g_strdup_printf(_("Error joining chat %s"), from);
+				purple_serv_got_join_chat_failed(js->gc, chat->components);
 			}
 			purple_notify_error(js->gc, title, title, msg);
 			g_free(title);
--- a/libpurple/protocols/msn/msn.c	Fri May 23 16:46:19 2008 +0000
+++ b/libpurple/protocols/msn/msn.c	Fri May 23 16:47:59 2008 +0000
@@ -32,6 +32,7 @@
 #include "pluginpref.h"
 #include "prefs.h"
 #include "session.h"
+#include "smiley.h"
 #include "state.h"
 #include "util.h"
 #include "cmds.h"
@@ -82,6 +83,12 @@
 	time_t when;
 } MsnIMData;
 
+typedef struct
+{
+	char *smile;
+	MsnObject *obj;
+} MsnEmoticon;
+
 static const char *
 msn_normalize(const PurpleAccount *account, const char *str)
 {
@@ -115,6 +122,7 @@
 		return FALSE;
 
 	msn_switchboard_send_msg(swboard, msg, TRUE);
+	msn_message_destroy(msg);
 
 	return TRUE;
 }
@@ -132,6 +140,17 @@
 	return list;
 }
 
+static GHashTable *
+msn_get_account_text_table(PurpleAccount *unused)
+{
+	GHashTable *table;
+
+	table = g_hash_table_new(g_str_hash, g_str_equal);
+
+	g_hash_table_insert(table, "login_label", (gpointer)_("E-mail Address..."));
+
+	return table;
+}
 
 static PurpleCmdRet
 msn_cmd_nudge(PurpleConversation *conv, const gchar *cmd, gchar **args, gchar **error, void *data)
@@ -884,7 +903,8 @@
 	session = msn_session_new(account);
 
 	gc->proto_data = session;
-	gc->flags |= PURPLE_CONNECTION_HTML | PURPLE_CONNECTION_FORMATTING_WBFO | PURPLE_CONNECTION_NO_BGCOLOR | PURPLE_CONNECTION_NO_FONTSIZE | PURPLE_CONNECTION_NO_URLDESC;
+	gc->flags |= PURPLE_CONNECTION_HTML | PURPLE_CONNECTION_FORMATTING_WBFO | PURPLE_CONNECTION_NO_BGCOLOR |
+		PURPLE_CONNECTION_NO_FONTSIZE | PURPLE_CONNECTION_NO_URLDESC | PURPLE_CONNECTION_ALLOW_CUSTOM_SMILEY;
 
 	msn_session_set_login_step(session, MSN_LOGIN_STEP_START);
 
@@ -925,6 +945,97 @@
 	return FALSE;
 }
 
+static GString*
+msn_msg_emoticon_add(GString *current, MsnEmoticon *emoticon)
+{
+	MsnObject *obj;
+	char *strobj;
+
+	if (emoticon == NULL)
+		return current;
+
+	obj = emoticon->obj;
+
+	if (!obj)
+		return current;
+
+	strobj = msn_object_to_string(obj);
+
+	if (current)
+		g_string_append_printf(current, "\t%s\t%s",
+				emoticon->smile, strobj);
+	else {
+		current = g_string_new("");
+		g_string_printf(current,"%s\t%s",
+					emoticon->smile, strobj);
+	}
+
+	g_free(strobj);
+
+	return current;
+}
+
+static void
+msn_send_emoticons(MsnSwitchBoard *swboard, GString *body)
+{
+	MsnMessage *msg;
+
+	g_return_if_fail(body != NULL);
+
+	msg = msn_message_new(MSN_MSG_SLP);
+	msn_message_set_content_type(msg, "text/x-mms-emoticon");
+	msn_message_set_flag(msg, 'N');
+	msn_message_set_bin_data(msg, body->str, body->len);
+
+	msn_switchboard_send_msg(swboard, msg, TRUE);
+	msn_message_destroy(msg);
+}
+
+static void msn_emoticon_destroy(MsnEmoticon *emoticon)
+{
+	if (emoticon->obj)
+		msn_object_destroy(emoticon->obj);
+	g_free(emoticon->smile);
+	g_free(emoticon);
+}
+
+static GSList* msn_msg_grab_emoticons(const char *msg, const char *username)
+{
+	GSList *list;
+	GList *smileys;
+	PurpleSmiley *smiley;
+	PurpleStoredImage *img;
+	char *ptr;
+	MsnEmoticon *emoticon;
+	int length;
+
+	list = NULL;
+	smileys = purple_smileys_get_all();
+	length = strlen(msg);
+
+	for (; smileys; smileys = g_list_delete_link(smileys, smileys)) {
+		smiley = smileys->data;
+
+		ptr = g_strstr_len(msg, length, purple_smiley_get_shortcut(smiley));
+
+		if (!ptr)
+			continue;
+
+		img = purple_smiley_get_stored_image(smiley);
+
+		emoticon = g_new0(MsnEmoticon, 1);
+		emoticon->smile = g_strdup(purple_smiley_get_shortcut(smiley));
+		emoticon->obj = msn_object_new_from_image(img,
+				purple_imgstore_get_filename(img),
+				username, MSN_OBJECT_EMOTICON);
+
+		purple_imgstore_unref(img);
+		list = g_slist_prepend(list, emoticon);
+	}
+
+	return list;
+}
+
 static int
 msn_send_im(PurpleConnection *gc, const char *who, const char *message,
 			PurpleMessageFlags flags)
@@ -934,9 +1045,11 @@
 	MsnMessage *msg;
 	char *msgformat;
 	char *msgtext;
+	const char *username;
 
 	purple_debug_info("MSNP14","send IM {%s} to %s\n",message,who);
 	account = purple_connection_get_account(gc);
+	username = purple_account_get_username(account);
 
 	if (buddy) {
 		PurplePresence *p = purple_buddy_get_presence(buddy);
@@ -969,10 +1082,13 @@
 		g_free(msgtext);
 
 		purple_debug_info("MSNP14","prepare to send online Message\n");
-		if (g_ascii_strcasecmp(who, purple_account_get_username(account)))
+		if (g_ascii_strcasecmp(who, username))
 		{
 			MsnSession *session;
 			MsnSwitchBoard *swboard;
+			MsnEmoticon *smile;
+			GSList *smileys;
+			GString *emoticons = NULL;
 
 			session = gc->proto_data;
 			if(msn_user_is_yahoo(account,who)){
@@ -982,6 +1098,19 @@
 			}else{
 				purple_debug_info("MSNP14","send via switchboard\n");
 				swboard = msn_session_get_swboard(session, who, MSN_SB_FLAG_IM);
+				smileys = msn_msg_grab_emoticons(message, username);
+				while (smileys) {
+					smile = (MsnEmoticon*)smileys->data;
+					emoticons = msn_msg_emoticon_add(emoticons, smile);
+					msn_emoticon_destroy(smile);
+					smileys = g_slist_delete_link(smileys, smileys);
+				}
+
+				if (emoticons) {
+					msn_send_emoticons(swboard, emoticons);
+					g_string_free(emoticons, TRUE);
+				}
+
 				msn_switchboard_send_msg(swboard, msg, TRUE);
 			}
 		}
@@ -1422,7 +1551,7 @@
 	g_free(msgformat);
 	g_free(msgtext);
 
-	serv_got_chat_in(gc, id, purple_account_get_username(account), 0,
+	serv_got_chat_in(gc, id, purple_account_get_username(account), flags,
 					 message, time(NULL));
 
 	return 0;
@@ -2307,8 +2436,8 @@
 	msn_send_attention,                     /* send_attention */
 	msn_attention_types,                    /* attention_types */
 
-	/* padding */
-	NULL
+	sizeof(PurplePluginProtocolInfo),       /* struct_size */
+	msn_get_account_text_table,             /* get_account_text_table */
 };
 
 static PurplePluginInfo info =
--- a/libpurple/protocols/msn/object.c	Fri May 23 16:46:19 2008 +0000
+++ b/libpurple/protocols/msn/object.c	Fri May 23 16:47:59 2008 +0000
@@ -23,6 +23,10 @@
  */
 #include "object.h"
 #include "debug.h"
+/* Sha1 stuff */
+#include "cipher.h"
+/* Base64 stuff */
+#include "util.h"
 
 #define GET_STRING_TAG(field, id) \
 	if ((tag = strstr(str, id "=\"")) != NULL) \
@@ -104,6 +108,74 @@
 	return obj;
 }
 
+MsnObject*
+msn_object_new_from_image(PurpleStoredImage *img, const char *location,
+		const char *creator, MsnObjectType type)
+{
+	MsnObject *msnobj;
+
+	PurpleCipherContext *ctx;
+	char *buf;
+	gconstpointer data;
+	size_t size;
+	char *base64;
+	unsigned char digest[20];
+
+	msnobj = NULL;
+
+	if (img == NULL)
+		return msnobj;
+
+	size = purple_imgstore_get_size(img);
+	data = purple_imgstore_get_data(img);
+
+	/* New object */
+	msnobj = msn_object_new();
+	msn_object_set_local(msnobj);
+	msn_object_set_type(msnobj, type);
+	msn_object_set_location(msnobj, location);
+	msn_object_set_creator(msnobj, creator);
+
+	msn_object_set_image(msnobj, img);
+
+	/* Compute the SHA1D field. */
+	memset(digest, 0, sizeof(digest));
+
+	ctx = purple_cipher_context_new_by_name("sha1", NULL);
+	purple_cipher_context_append(ctx, data, size);
+	purple_cipher_context_digest(ctx, sizeof(digest), digest, NULL);
+
+	base64 = purple_base64_encode(digest, sizeof(digest));
+	msn_object_set_sha1d(msnobj, base64);
+	g_free(base64);
+
+	msn_object_set_size(msnobj, size);
+
+	/* Compute the SHA1C field. */
+	buf = g_strdup_printf(
+		"Creator%sSize%dType%dLocation%sFriendly%sSHA1D%s",
+		msn_object_get_creator(msnobj),
+		msn_object_get_size(msnobj),
+		msn_object_get_type(msnobj),
+		msn_object_get_location(msnobj),
+		msn_object_get_friendly(msnobj),
+		msn_object_get_sha1d(msnobj));
+
+	memset(digest, 0, sizeof(digest));
+
+	purple_cipher_context_reset(ctx, NULL);
+	purple_cipher_context_append(ctx, (const guchar *)buf, strlen(buf));
+	purple_cipher_context_digest(ctx, sizeof(digest), digest, NULL);
+	purple_cipher_context_destroy(ctx);
+	g_free(buf);
+
+	base64 = purple_base64_encode(digest, sizeof(digest));
+	msn_object_set_sha1c(msnobj, base64);
+	g_free(base64);
+	
+	return msnobj;
+}
+
 void
 msn_object_destroy(MsnObject *obj)
 {
--- a/libpurple/protocols/msn/object.h	Fri May 23 16:46:19 2008 +0000
+++ b/libpurple/protocols/msn/object.h	Fri May 23 16:47:59 2008 +0000
@@ -71,6 +71,19 @@
 MsnObject *msn_object_new_from_string(const char *str);
 
 /**
+ * Creates a MsnObject structure from a stored image
+ *
+ * @param img		The image associated to object
+ * @param location	The object location as stored in MsnObject
+ * @param creator	The creator of the object
+ * @param type		The type of the object
+ *
+ * @return A new MsnObject structure
+ */
+MsnObject *msn_object_new_from_image(PurpleStoredImage *img,
+		const char *location, const char *creator, MsnObjectType type);
+
+/**
  * Destroys an MsnObject structure.
  *
  * @param obj The object structure.
--- a/libpurple/protocols/msn/slp.c	Fri May 23 16:46:19 2008 +0000
+++ b/libpurple/protocols/msn/slp.c	Fri May 23 16:47:59 2008 +0000
@@ -31,6 +31,8 @@
 #include "user.h"
 #include "switchboard.h"
 
+#include "smiley.h"
+
 /* ms to delay between sending buddy icon requests to the server. */
 #define BUDDY_ICON_DELAY 20000
 /*debug SLP*/
@@ -278,23 +280,32 @@
 		type = msn_object_get_type(obj);
 		g_free(msnobj_data);
 
-		if (!(type == MSN_OBJECT_USERTILE))
+		if ((type != MSN_OBJECT_USERTILE) && (type != MSN_OBJECT_EMOTICON))
 		{
 			purple_debug_error("msn", "Wrong object?\n");
 			msn_object_destroy(obj);
 			g_return_if_reached();
 		}
 
-		img = msn_object_get_image(obj);
+		if (type == MSN_OBJECT_EMOTICON) {
+			char *path;
+			path = g_build_filename(purple_smileys_get_storing_dir(),
+					obj->location, NULL);
+			img = purple_imgstore_new_from_file(path);
+			g_free(path);
+		} else {
+			img = msn_object_get_image(obj);
+			if (img)
+				purple_imgstore_ref(img);
+		}
+		msn_object_destroy(obj);
+
 		if (img == NULL)
 		{
 			purple_debug_error("msn", "Wrong object.\n");
-			msn_object_destroy(obj);
 			g_return_if_reached();
 		}
 
-		msn_object_destroy(obj);
-
 		slpsession = msn_slplink_find_slp_session(slplink,
 												  slpcall->session_id);
 
@@ -319,6 +330,7 @@
 #endif
 		msn_slpmsg_set_image(slpmsg, img);
 		msn_slplink_queue_slpmsg(slplink, slpmsg);
+		purple_imgstore_unref(img);
 	}
 	else if (!strcmp(euf_guid, "5D3E02AB-6190-11D3-BBBB-00C04F795683"))
 	{
--- a/libpurple/protocols/msn/user.c	Fri May 23 16:46:19 2008 +0000
+++ b/libpurple/protocols/msn/user.c	Fri May 23 16:47:59 2008 +0000
@@ -246,69 +246,17 @@
 void
 msn_user_set_buddy_icon(MsnUser *user, PurpleStoredImage *img)
 {
-	MsnObject *msnobj = msn_user_get_object(user);
+	MsnObject *msnobj;
 
 	g_return_if_fail(user != NULL);
 
-	if (img == NULL)
-		msn_user_set_object(user, NULL);
-	else
-	{
-		PurpleCipherContext *ctx;
-		char *buf;
-		gconstpointer data = purple_imgstore_get_data(img);
-		size_t size = purple_imgstore_get_size(img);
-		char *base64;
-		unsigned char digest[20];
-
-		if (msnobj == NULL)
-		{
-			msnobj = msn_object_new();
-			msn_object_set_local(msnobj);
-			msn_object_set_type(msnobj, MSN_OBJECT_USERTILE);
-			msn_object_set_location(msnobj, "TFR2C2.tmp");
-			msn_object_set_creator(msnobj, msn_user_get_passport(user));
-
-			msn_user_set_object(user, msnobj);
-		}
-
-		msn_object_set_image(msnobj, img);
-
-		/* Compute the SHA1D field. */
-		memset(digest, 0, sizeof(digest));
+	msnobj = msn_object_new_from_image(img, "TFR2C2.tmp",
+			user->passport, MSN_OBJECT_USERTILE);
 
-		ctx = purple_cipher_context_new_by_name("sha1", NULL);
-		purple_cipher_context_append(ctx, data, size);
-		purple_cipher_context_digest(ctx, sizeof(digest), digest, NULL);
-
-		base64 = purple_base64_encode(digest, sizeof(digest));
-		msn_object_set_sha1d(msnobj, base64);
-		g_free(base64);
-
-		msn_object_set_size(msnobj, size);
+	if (!msnobj)
+		purple_debug_error("msn", "Unable to open buddy icon from %s!\n", user->passport);
 
-		/* Compute the SHA1C field. */
-		buf = g_strdup_printf(
-			"Creator%sSize%dType%dLocation%sFriendly%sSHA1D%s",
-			msn_object_get_creator(msnobj),
-			msn_object_get_size(msnobj),
-			msn_object_get_type(msnobj),
-			msn_object_get_location(msnobj),
-			msn_object_get_friendly(msnobj),
-			msn_object_get_sha1d(msnobj));
-
-		memset(digest, 0, sizeof(digest));
-
-		purple_cipher_context_reset(ctx, NULL);
-		purple_cipher_context_append(ctx, (const guchar *)buf, strlen(buf));
-		purple_cipher_context_digest(ctx, sizeof(digest), digest, NULL);
-		purple_cipher_context_destroy(ctx);
-		g_free(buf);
-
-		base64 = purple_base64_encode(digest, sizeof(digest));
-		msn_object_set_sha1c(msnobj, base64);
-		g_free(base64);
-	}
+	msn_user_set_object(user, msnobj);
 }
 
 /*add group id to User object*/
--- a/libpurple/protocols/msnp9/msn.c	Fri May 23 16:46:19 2008 +0000
+++ b/libpurple/protocols/msnp9/msn.c	Fri May 23 16:47:59 2008 +0000
@@ -33,6 +33,7 @@
 #include "pluginpref.h"
 #include "prefs.h"
 #include "session.h"
+#include "smiley.h"
 #include "state.h"
 #include "util.h"
 #include "cmds.h"
@@ -83,6 +84,12 @@
 	time_t when;
 } MsnIMData;
 
+typedef struct
+{
+	char *smile;
+	MsnObject *obj;
+} MsnEmoticon;
+
 static const char *
 msn_normalize(const PurpleAccount *account, const char *str)
 {
@@ -116,6 +123,7 @@
 		return FALSE;
 
 	msn_switchboard_send_msg(swboard, msg, TRUE);
+	msn_message_destroy(msg);
 
 	return TRUE;
 }
@@ -133,6 +141,17 @@
 	return list;
 }
 
+static GHashTable *
+msn_get_account_text_table(PurpleAccount *unused)
+{
+	GHashTable *table;
+
+	table = g_hash_table_new(g_str_hash, g_str_equal);
+
+	g_hash_table_insert(table, "login_label", (gpointer)_("E-mail Address..."));
+
+	return table;
+}
 
 static PurpleCmdRet
 msn_cmd_nudge(PurpleConversation *conv, const gchar *cmd, gchar **args, gchar **error, void *data)
@@ -755,7 +774,8 @@
 	session = msn_session_new(account);
 
 	gc->proto_data = session;
-	gc->flags |= PURPLE_CONNECTION_HTML | PURPLE_CONNECTION_FORMATTING_WBFO | PURPLE_CONNECTION_NO_BGCOLOR | PURPLE_CONNECTION_NO_FONTSIZE | PURPLE_CONNECTION_NO_URLDESC;
+	gc->flags |= PURPLE_CONNECTION_HTML | PURPLE_CONNECTION_FORMATTING_WBFO | PURPLE_CONNECTION_NO_BGCOLOR |
+		PURPLE_CONNECTION_NO_FONTSIZE | PURPLE_CONNECTION_NO_URLDESC | PURPLE_CONNECTION_ALLOW_CUSTOM_SMILEY;
 
 	msn_session_set_login_step(session, MSN_LOGIN_STEP_START);
 
@@ -796,6 +816,97 @@
 	return FALSE;
 }
 
+static GString*
+msn_msg_emoticon_add(GString *current, MsnEmoticon *emoticon)
+{
+	MsnObject *obj;
+	char *strobj;
+
+	if (emoticon == NULL)
+		return current;
+
+	obj = emoticon->obj;
+
+	if (!obj)
+		return current;
+
+	strobj = msn_object_to_string(obj);
+
+	if (current)
+		g_string_append_printf(current, "\t%s\t%s",
+				emoticon->smile, strobj);
+	else {
+		current = g_string_new("");
+		g_string_printf(current,"%s\t%s",
+					emoticon->smile, strobj);
+	}
+
+	g_free(strobj);
+
+	return current;
+}
+
+static void
+msn_send_emoticons(MsnSwitchBoard *swboard, GString *body)
+{
+	MsnMessage *msg;
+
+	g_return_if_fail(body != NULL);
+
+	msg = msn_message_new(MSN_MSG_SLP);
+	msn_message_set_content_type(msg, "text/x-mms-emoticon");
+	msn_message_set_flag(msg, 'N');
+	msn_message_set_bin_data(msg, body->str, body->len);
+
+	msn_switchboard_send_msg(swboard, msg, TRUE);
+	msn_message_destroy(msg);
+}
+
+static void msn_emoticon_destroy(MsnEmoticon *emoticon)
+{
+	if (emoticon->obj)
+		msn_object_destroy(emoticon->obj);
+	g_free(emoticon->smile);
+	g_free(emoticon);
+}
+
+static GSList* msn_msg_grab_emoticons(const char *msg, const char *username)
+{
+	GSList *list;
+	GList *smileys;
+	PurpleSmiley *smiley;
+	PurpleStoredImage *img;
+	char *ptr;
+	MsnEmoticon *emoticon;
+	int length;
+
+	list = NULL;
+	smileys = purple_smileys_get_all();
+	length = strlen(msg);
+
+	for (; smileys; smileys = g_list_delete_link(smileys, smileys)) {
+		smiley = (PurpleSmiley*)smileys->data;
+
+		ptr = g_strstr_len(msg, length, purple_smiley_get_shortcut(smiley));
+
+		if (!ptr)
+			continue;
+
+		img = purple_smiley_get_stored_image(smiley);
+
+		emoticon = g_new0(MsnEmoticon, 1);
+		emoticon->smile = g_strdup(purple_smiley_get_shortcut(smiley));
+		emoticon->obj = msn_object_new_from_image(img,
+				purple_imgstore_get_filename(img),
+				username, MSN_OBJECT_EMOTICON);
+
+		purple_imgstore_unref(img);
+		list = g_slist_prepend(list, emoticon);
+	}
+
+	return list;
+}
+
 static int
 msn_send_im(PurpleConnection *gc, const char *who, const char *message,
 			PurpleMessageFlags flags)
@@ -805,8 +916,10 @@
 	MsnMessage *msg;
 	char *msgformat;
 	char *msgtext;
+	const char *username;
 
 	account = purple_connection_get_account(gc);
+	username = purple_account_get_username(account);
 
 	if (buddy) {
 		PurplePresence *p = purple_buddy_get_presence(buddy);
@@ -834,13 +947,29 @@
 	g_free(msgformat);
 	g_free(msgtext);
 
-	if (g_ascii_strcasecmp(who, purple_account_get_username(account)))
+	if (g_ascii_strcasecmp(who, username))
 	{
 		MsnSession *session;
 		MsnSwitchBoard *swboard;
+		MsnEmoticon *smile;
+		GSList *smileys;
+		GString *emoticons = NULL;
 
 		session = gc->proto_data;
 		swboard = msn_session_get_swboard(session, who, MSN_SB_FLAG_IM);
+		smileys = msn_msg_grab_emoticons(message, username);
+
+		while (smileys) {
+			smile = (MsnEmoticon*)smileys->data;
+			emoticons = msn_msg_emoticon_add(emoticons,smile);
+			msn_emoticon_destroy(smile);
+			smileys = g_slist_delete_link(smileys, smileys);
+		}
+
+		if (emoticons) {
+			msn_send_emoticons(swboard, emoticons);
+			g_string_free(emoticons, TRUE);
+		}
 
 		msn_switchboard_send_msg(swboard, msg, TRUE);
 	}
@@ -1263,7 +1392,7 @@
 	g_free(msgformat);
 	g_free(msgtext);
 
-	serv_got_chat_in(gc, id, purple_account_get_username(account), 0,
+	serv_got_chat_in(gc, id, purple_account_get_username(account), flags,
 					 message, time(NULL));
 
 	return 0;
@@ -2148,8 +2277,8 @@
 	msn_send_attention,                     /* send_attention */
 	msn_attention_types,                    /* attention_types */
 
-	/* padding */
-	NULL
+	sizeof(PurplePluginProtocolInfo),       /* struct_size */
+	msn_get_account_text_table,             /* get_account_text_table */
 };
 
 static PurplePluginInfo info =
--- a/libpurple/protocols/msnp9/object.c	Fri May 23 16:46:19 2008 +0000
+++ b/libpurple/protocols/msnp9/object.c	Fri May 23 16:47:59 2008 +0000
@@ -23,6 +23,10 @@
  */
 #include "object.h"
 #include "debug.h"
+/* Sha1 stuff */
+#include "cipher.h"
+/* Base64 stuff */
+#include "util.h"
 
 #define GET_STRING_TAG(field, id) \
 	if ((tag = strstr(str, id "=\"")) != NULL) \
@@ -104,6 +108,74 @@
 	return obj;
 }
 
+MsnObject*
+msn_object_new_from_image(PurpleStoredImage *img, const char *location,
+		const char *creator, MsnObjectType type)
+{
+	MsnObject *msnobj;
+
+	PurpleCipherContext *ctx;
+	char *buf;
+	gconstpointer data;
+	size_t size;
+	char *base64;
+	unsigned char digest[20];
+
+	msnobj = NULL;
+
+	if (img == NULL)
+		return msnobj;
+
+	size = purple_imgstore_get_size(img);
+	data = purple_imgstore_get_data(img);
+
+	/* New object */
+	msnobj = msn_object_new();
+	msn_object_set_local(msnobj);
+	msn_object_set_type(msnobj, type);
+	msn_object_set_location(msnobj, location);
+	msn_object_set_creator(msnobj, creator);
+
+	msn_object_set_image(msnobj, img);
+
+	/* Compute the SHA1D field. */
+	memset(digest, 0, sizeof(digest));
+
+	ctx = purple_cipher_context_new_by_name("sha1", NULL);
+	purple_cipher_context_append(ctx, data, size);
+	purple_cipher_context_digest(ctx, sizeof(digest), digest, NULL);
+
+	base64 = purple_base64_encode(digest, sizeof(digest));
+	msn_object_set_sha1d(msnobj, base64);
+	g_free(base64);
+
+	msn_object_set_size(msnobj, size);
+
+	/* Compute the SHA1C field. */
+	buf = g_strdup_printf(
+		"Creator%sSize%dType%dLocation%sFriendly%sSHA1D%s",
+		msn_object_get_creator(msnobj),
+		msn_object_get_size(msnobj),
+		msn_object_get_type(msnobj),
+		msn_object_get_location(msnobj),
+		msn_object_get_friendly(msnobj),
+		msn_object_get_sha1d(msnobj));
+
+	memset(digest, 0, sizeof(digest));
+
+	purple_cipher_context_reset(ctx, NULL);
+	purple_cipher_context_append(ctx, (const guchar *)buf, strlen(buf));
+	purple_cipher_context_digest(ctx, sizeof(digest), digest, NULL);
+	purple_cipher_context_destroy(ctx);
+	g_free(buf);
+
+	base64 = purple_base64_encode(digest, sizeof(digest));
+	msn_object_set_sha1c(msnobj, base64);
+	g_free(base64);
+
+	return msnobj;
+}
+
 void
 msn_object_destroy(MsnObject *obj)
 {
--- a/libpurple/protocols/msnp9/object.h	Fri May 23 16:46:19 2008 +0000
+++ b/libpurple/protocols/msnp9/object.h	Fri May 23 16:47:59 2008 +0000
@@ -71,6 +71,19 @@
 MsnObject *msn_object_new_from_string(const char *str);
 
 /**
+ * Creates a MsnObject structure from a stored image
+ *
+ * @param img		The image associated to object
+ * @param location	The object location as stored in MsnObject
+ * @param creator	The creator of the object
+ * @param type		The type of the object
+ *
+ * @return A new MsnObject structure
+ */
+MsnObject *msn_object_new_from_image(PurpleStoredImage *img,
+		const char *location, const char *creator, MsnObjectType type);
+
+/**
  * Destroys an MsnObject structure.
  *
  * @param obj The object structure.
--- a/libpurple/protocols/msnp9/slp.c	Fri May 23 16:46:19 2008 +0000
+++ b/libpurple/protocols/msnp9/slp.c	Fri May 23 16:47:59 2008 +0000
@@ -31,6 +31,8 @@
 #include "user.h"
 #include "switchboard.h"
 
+#include "smiley.h"
+
 /* ms to delay between sending buddy icon requests to the server. */
 #define BUDDY_ICON_DELAY 20000
 
@@ -276,23 +278,32 @@
 		type = msn_object_get_type(obj);
 		g_free(msnobj_data);
 
-		if (!(type == MSN_OBJECT_USERTILE))
+		if ((type != MSN_OBJECT_USERTILE) && (type != MSN_OBJECT_EMOTICON))
 		{
 			purple_debug_error("msn", "Wrong object?\n");
 			msn_object_destroy(obj);
 			g_return_if_reached();
 		}
 
-		img = msn_object_get_image(obj);
+		if (type == MSN_OBJECT_EMOTICON) {
+			char *path;
+			path = g_build_filename(purple_smileys_get_storing_dir(),
+					obj->location, NULL);
+			img = purple_imgstore_new_from_file(path);
+			g_free(path);
+		} else {
+			img = msn_object_get_image(obj);
+			if (img)
+				purple_imgstore_ref(img);
+		}
+		msn_object_destroy(obj);
+
 		if (img == NULL)
 		{
 			purple_debug_error("msn", "Wrong object.\n");
-			msn_object_destroy(obj);
 			g_return_if_reached();
 		}
 
-		msn_object_destroy(obj);
-
 		slpsession = msn_slplink_find_slp_session(slplink,
 												  slpcall->session_id);
 
@@ -317,6 +328,7 @@
 #endif
 		msn_slpmsg_set_image(slpmsg, img);
 		msn_slplink_queue_slpmsg(slplink, slpmsg);
+		purple_imgstore_unref(img);
 	}
 	else if (!strcmp(euf_guid, "5D3E02AB-6190-11D3-BBBB-00C04F795683"))
 	{
--- a/libpurple/protocols/msnp9/user.c	Fri May 23 16:46:19 2008 +0000
+++ b/libpurple/protocols/msnp9/user.c	Fri May 23 16:47:59 2008 +0000
@@ -153,69 +153,17 @@
 void
 msn_user_set_buddy_icon(MsnUser *user, PurpleStoredImage *img)
 {
-	MsnObject *msnobj = msn_user_get_object(user);
+	MsnObject *msnobj = NULL;
 
 	g_return_if_fail(user != NULL);
 
-	if (img == NULL)
-		msn_user_set_object(user, NULL);
-	else
-	{
-		PurpleCipherContext *ctx;
-		char *buf;
-		gconstpointer data = purple_imgstore_get_data(img);
-		size_t size = purple_imgstore_get_size(img);
-		char *base64;
-		unsigned char digest[20];
-
-		if (msnobj == NULL)
-		{
-			msnobj = msn_object_new();
-			msn_object_set_local(msnobj);
-			msn_object_set_type(msnobj, MSN_OBJECT_USERTILE);
-			msn_object_set_location(msnobj, "TFR2C2.tmp");
-			msn_object_set_creator(msnobj, msn_user_get_passport(user));
-
-			msn_user_set_object(user, msnobj);
-		}
-
-		msn_object_set_image(msnobj, img);
-
-		/* Compute the SHA1D field. */
-		memset(digest, 0, sizeof(digest));
+	msnobj = msn_object_new_from_image(img, "TFR2C2.tmp",
+			user->passport, MSN_OBJECT_USERTILE);
 
-		ctx = purple_cipher_context_new_by_name("sha1", NULL);
-		purple_cipher_context_append(ctx, data, size);
-		purple_cipher_context_digest(ctx, sizeof(digest), digest, NULL);
-
-		base64 = purple_base64_encode(digest, sizeof(digest));
-		msn_object_set_sha1d(msnobj, base64);
-		g_free(base64);
-
-		msn_object_set_size(msnobj, size);
+	if(!msnobj)
+		purple_debug_error("msn", "Unable to open buddy icon from %s!\n", user->passport);
 
-		/* Compute the SHA1C field. */
-		buf = g_strdup_printf(
-			"Creator%sSize%dType%dLocation%sFriendly%sSHA1D%s",
-			msn_object_get_creator(msnobj),
-			msn_object_get_size(msnobj),
-			msn_object_get_type(msnobj),
-			msn_object_get_location(msnobj),
-			msn_object_get_friendly(msnobj),
-			msn_object_get_sha1d(msnobj));
-
-		memset(digest, 0, sizeof(digest));
-
-		purple_cipher_context_reset(ctx, NULL);
-		purple_cipher_context_append(ctx, (const guchar *)buf, strlen(buf));
-		purple_cipher_context_digest(ctx, sizeof(digest), digest, NULL);
-		purple_cipher_context_destroy(ctx);
-		g_free(buf);
-
-		base64 = purple_base64_encode(digest, sizeof(digest));
-		msn_object_set_sha1c(msnobj, base64);
-		g_free(base64);
-	}
+	msn_user_set_object(user, msnobj);
 }
 
 void
--- a/libpurple/protocols/myspace/myspace.c	Fri May 23 16:46:19 2008 +0000
+++ b/libpurple/protocols/myspace/myspace.c	Fri May 23 16:47:59 2008 +0000
@@ -2449,6 +2449,18 @@
 	return normalized;
 }
 
+static GHashTable *
+msim_get_account_text_table(PurpleAccount *unused)
+{
+	GHashTable *table;
+
+	table = g_hash_table_new(g_str_hash, g_str_equal);
+
+	g_hash_table_insert(table, "login_label", (gpointer)_("E-mail Address..."));
+
+	return table;
+}
+
 /** Return whether the buddy can be messaged while offline.
  *
  * The protocol supports offline messages in just the same way as online
@@ -3128,14 +3140,16 @@
 	NULL,                  /* unregister_user */
 	msim_send_attention,   /* send_attention */
 	msim_attention_types,  /* attention_types */
-	NULL                /* _purple_reserved4 */
+
+	sizeof(PurplePluginProtocolInfo),  /* struct_size */
+	msim_get_account_text_table,              /* get_account_text_table */
 };
 
 
 
 /** Based on MSN's plugin info comments. */
 static PurplePluginInfo info = {
-	PURPLE_PLUGIN_MAGIC,                                
+	PURPLE_PLUGIN_MAGIC,
 	PURPLE_MAJOR_VERSION,
 	PURPLE_MINOR_VERSION,
 	PURPLE_PLUGIN_PROTOCOL,                           /**< type           */
--- a/libpurple/protocols/novell/novell.c	Fri May 23 16:46:19 2008 +0000
+++ b/libpurple/protocols/novell/novell.c	Fri May 23 16:47:59 2008 +0000
@@ -2506,7 +2506,7 @@
 						}
 					}
 
-					serv_got_chat_in(gc, id, name, 0, text, time(NULL));
+					serv_got_chat_in(gc, id, name, flags, text, time(NULL));
 					return 0;
 				} else
 					return -1;
@@ -3517,6 +3517,7 @@
 	NULL,
 	NULL,
 	NULL,
+	sizeof(PurplePluginProtocolInfo),       /* struct_size */
 	NULL
 };
 
--- a/libpurple/protocols/oscar/libaim.c	Fri May 23 16:46:19 2008 +0000
+++ b/libpurple/protocols/oscar/libaim.c	Fri May 23 16:47:59 2008 +0000
@@ -96,7 +96,7 @@
 	NULL,					/* send_attention */
 	NULL,					/* get_attention_types */
 
-	/* padding */
+	sizeof(PurplePluginProtocolInfo),       /* struct_size */
 	NULL
 };
 
--- a/libpurple/protocols/oscar/libicq.c	Fri May 23 16:46:19 2008 +0000
+++ b/libpurple/protocols/oscar/libicq.c	Fri May 23 16:47:59 2008 +0000
@@ -96,7 +96,7 @@
 	NULL,					/* send_attention */
 	NULL,					/* get_attention_types */
 
-	/* padding */
+	sizeof(PurplePluginProtocolInfo),       /* struct_size */
 	NULL
 };
 
--- a/libpurple/protocols/qq/qq.c	Fri May 23 16:46:19 2008 +0000
+++ b/libpurple/protocols/qq/qq.c	Fri May 23 16:47:59 2008 +0000
@@ -706,6 +706,7 @@
 	NULL,
 	NULL,
 	NULL,
+	sizeof(PurplePluginProtocolInfo),       /* struct_size */
 	NULL
 };
 
--- a/libpurple/protocols/silc/chat.c	Fri May 23 16:46:19 2008 +0000
+++ b/libpurple/protocols/silc/chat.c	Fri May 23 16:47:59 2008 +0000
@@ -1315,7 +1315,7 @@
 			g_free(tmp);
 
 			if (ret)
-				  serv_got_chat_in(gc, id, purple_connection_get_display_name(gc), 0, msg, time(NULL));
+				  serv_got_chat_in(gc, id, purple_connection_get_display_name(gc), msgflags, msg, time(NULL));
 			return ret;
 		}
 	}
@@ -1326,7 +1326,7 @@
 					       (unsigned char *)msg2,
 					       strlen(msg2));
 	if (ret) {
-		serv_got_chat_in(gc, id, purple_connection_get_display_name(gc), 0, msg,
+		serv_got_chat_in(gc, id, purple_connection_get_display_name(gc), msgflags, msg,
 				 time(NULL));
 	}
 	g_free(tmp);
--- a/libpurple/protocols/silc/silc.c	Fri May 23 16:46:19 2008 +0000
+++ b/libpurple/protocols/silc/silc.c	Fri May 23 16:47:59 2008 +0000
@@ -2071,6 +2071,7 @@
 	NULL,
 	NULL,
 	NULL,
+	sizeof(PurplePluginProtocolInfo),       /* struct_size */
 	NULL
 };
 
--- a/libpurple/protocols/silc10/chat.c	Fri May 23 16:46:19 2008 +0000
+++ b/libpurple/protocols/silc10/chat.c	Fri May 23 16:47:59 2008 +0000
@@ -1351,7 +1351,7 @@
 					       flags, (unsigned char *)msg2,
 					       strlen(msg2), TRUE);
 	if (ret) {
-		serv_got_chat_in(gc, id, purple_connection_get_display_name(gc), 0, msg,
+		serv_got_chat_in(gc, id, purple_connection_get_display_name(gc), msgflags, msg,
 				 time(NULL));
 	}
 	g_free(tmp);
--- a/libpurple/protocols/silc10/silc.c	Fri May 23 16:46:19 2008 +0000
+++ b/libpurple/protocols/silc10/silc.c	Fri May 23 16:47:59 2008 +0000
@@ -1800,10 +1800,10 @@
 	NULL,                       /* send_raw */
 	NULL,                       /* roomlist_room_serialize */
 
-	/* padding */
 	NULL,
 	NULL,
 	NULL,
+	sizeof(PurplePluginProtocolInfo),       /* struct_size */
 	NULL
 };
 
--- a/libpurple/protocols/simple/simple.c	Fri May 23 16:46:19 2008 +0000
+++ b/libpurple/protocols/simple/simple.c	Fri May 23 16:47:59 2008 +0000
@@ -2068,6 +2068,7 @@
 	NULL,
 	NULL,
 	NULL,
+	sizeof(PurplePluginProtocolInfo),       /* struct_size */
 	NULL
 };
 
--- a/libpurple/protocols/yahoo/yahoo.c	Fri May 23 16:46:19 2008 +0000
+++ b/libpurple/protocols/yahoo/yahoo.c	Fri May 23 16:47:59 2008 +0000
@@ -4383,7 +4383,7 @@
 	yahoo_send_attention,
 	yahoo_attention_types,
 
-	/* padding */
+	sizeof(PurplePluginProtocolInfo),       /* struct_size */
 	NULL
 };
 
--- a/libpurple/protocols/yahoo/yahoochat.c	Fri May 23 16:46:19 2008 +0000
+++ b/libpurple/protocols/yahoo/yahoochat.c	Fri May 23 16:47:59 2008 +0000
@@ -1043,7 +1043,7 @@
 						purple_conversation_get_name(c), what, flags);
 		if (!ret)
 			serv_got_chat_in(gc, purple_conv_chat_get_id(PURPLE_CONV_CHAT(c)),
-					purple_connection_get_display_name(gc), 0, what, time(NULL));
+					purple_connection_get_display_name(gc), flags, what, time(NULL));
 	}
 	return ret;
 }
--- a/libpurple/protocols/zephyr/zephyr.c	Fri May 23 16:46:19 2008 +0000
+++ b/libpurple/protocols/zephyr/zephyr.c	Fri May 23 16:47:59 2008 +0000
@@ -2907,10 +2907,10 @@
 	NULL,					/* send_raw */
 	NULL,					/* roomlist_room_serialize */
 
-	/* padding */
 	NULL,
 	NULL,
 	NULL,
+	sizeof(PurplePluginProtocolInfo),       /* struct_size */
 	NULL
 };
 
--- a/libpurple/prpl.h	Fri May 23 16:46:19 2008 +0000
+++ b/libpurple/prpl.h	Fri May 23 16:47:59 2008 +0000
@@ -399,9 +399,45 @@
 	gboolean (*send_attention)(PurpleConnection *gc, const char *username, guint type);
 	GList *(*get_attention_types)(PurpleAccount *acct);
 
-	void (*_purple_reserved4)(void);
+	/**
+	 * The size of the PurplePluginProtocolInfo. This should always be sizeof(PurplePluginProtocolInfo).
+	 * This allows adding more functions to this struct without requiring a major version bump.
+	 */
+	unsigned long struct_size;
+
+	/* NOTE:
+	 * If more functions are added, they should accessed using the following syntax:
+	 *
+	 *		if (PURPLE_PROTOCOL_PLUGIN_HAS_FUNC(prpl, new_function))
+	 *			prpl->new_function(...);
+	 *
+	 * instead of
+	 *
+	 *		if (prpl->new_function != NULL)
+	 *			prpl->new_function(...);
+	 *
+	 * The PURPLE_PROTOCOL_PLUGIN_HAS_FUNC macro can be used for the older member
+	 * functions (e.g. login, send_im etc.) too.
+	 */
+
+	/** This allows protocols to specify additional strings to be used for
+	 * various purposes.  The idea is to stuff a bunch of strings in this hash
+	 * table instead of expanding the struct for every addition.  This hash
+	 * table is allocated every call and MUST be unrefed by the caller.
+	 *
+	 * @param account The account to specify.  This can be NULL.
+	 * @return The protocol's string hash table. The hash table should be
+	 *         destroyed by the caller when it's no longer needed.
+	 */
+	GHashTable *(*get_account_text_table)(PurpleAccount *account);
 };
 
+#define PURPLE_PROTOCOL_PLUGIN_HAS_FUNC(prpl, member) \
+	(((G_STRUCT_OFFSET(PurplePluginProtocolInfo, member) < G_STRUCT_OFFSET(PurplePluginProtocolInfo, struct_size)) \
+	  || (G_STRUCT_OFFSET(PurplePluginProtocolInfo, member) < prpl->struct_size)) && \
+	 prpl->member != NULL)
+
+
 #define PURPLE_IS_PROTOCOL_PLUGIN(plugin) \
 	((plugin)->info->type == PURPLE_PLUGIN_PROTOCOL)
 
--- a/libpurple/server.c	Fri May 23 16:46:19 2008 +0000
+++ b/libpurple/server.c	Fri May 23 16:47:59 2008 +0000
@@ -659,8 +659,11 @@
 
 	if (PURPLE_PLUGIN_PROTOCOL_INFO(purple_connection_get_prpl(gc))->set_permit_deny == NULL) {
 		/* protocol does not support privacy, handle it ourselves */
-		if (!purple_privacy_check(account, who))
+		if (!purple_privacy_check(account, who)) {
+			purple_signal_emit(purple_conversations_get_handle(), "blocked-im-msg",
+					account, who, msg, flags, (unsigned int)mtime);
 			return;
+		}
 	}
 
 	/*
@@ -879,8 +882,11 @@
 	account = purple_connection_get_account(gc);
 	if (PURPLE_PLUGIN_PROTOCOL_INFO(purple_connection_get_prpl(gc))->set_permit_deny == NULL) {
 		/* protocol does not support privacy, handle it ourselves */
-		if (!purple_privacy_check(account, who))
+		if (!purple_privacy_check(account, who)) {
+			purple_signal_emit(purple_conversations_get_handle(), "chat-invite-blocked",
+					account, who, name, message, data);
 			return;
+		}
 	}
 
 	plugin_return = GPOINTER_TO_INT(purple_signal_emit_return_1(
@@ -967,6 +973,12 @@
 	purple_signal_emit(purple_conversations_get_handle(), "chat-left", conv);
 }
 
+void purple_serv_got_join_chat_failed(PurpleConnection *gc, GHashTable *data)
+{
+	purple_signal_emit(purple_conversations_get_handle(), "chat-join-failed",
+					gc, data);
+}
+
 void serv_got_chat_in(PurpleConnection *g, int id, const char *who,
 					  PurpleMessageFlags flags, const char *message, time_t mtime)
 {
--- a/libpurple/server.h	Fri May 23 16:46:19 2008 +0000
+++ b/libpurple/server.h	Fri May 23 16:47:59 2008 +0000
@@ -166,6 +166,17 @@
 
 PurpleConversation *serv_got_joined_chat(PurpleConnection *gc,
 									   int id, const char *name);
+/**
+ * Called by a prpl when an attempt to join a chat via serv_join_chat()
+ * fails.
+ *
+ * @param gc      The connection on which chat joining failed
+ * @param data    The components passed to serv_join_chat() originally.
+ *                The hash function should be g_str_hash() and the equal
+ *                function should be g_str_equal().
+ */
+void purple_serv_got_join_chat_failed(PurpleConnection *gc, GHashTable *data);
+	
 void serv_got_chat_left(PurpleConnection *g, int id);
 void serv_got_chat_in(PurpleConnection *g, int id, const char *who,
 					  PurpleMessageFlags flags, const char *message, time_t mtime);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/smiley.c	Fri May 23 16:47:59 2008 +0000
@@ -0,0 +1,915 @@
+/**
+ * @file smiley.c Simley API
+ * @ingroup core
+ */
+
+/* purple
+ *
+ * Purple is the legal property of its developers, whose names are too numerous
+ * to list here.  Please refer to the COPYRIGHT file distributed with this
+ * source distribution.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
+ */
+
+#include "internal.h"
+#include "dbus-maybe.h"
+#include "debug.h"
+#include "imgstore.h"
+#include "smiley.h"
+#include "util.h"
+#include "xmlnode.h"
+
+/**************************************************************************/
+/* Main structures, members and constants                                 */
+/**************************************************************************/
+
+struct _PurpleSmiley
+{
+	GObject parent;
+	PurpleStoredImage *img;        /**< The id of the stored image with the
+	                                    the smiley data.        */
+	char *shortcut;                /**< Shortcut associated with the custom
+	                                    smiley. This field will work as a
+	                                    unique key by this API. */
+	char *checksum;                /**< The smiley checksum.        */
+};
+
+struct _PurpleSmileyClass
+{
+	GObjectClass parent_class;
+};
+
+static GHashTable *smiley_shortcut_index = NULL; /* shortcut (char *) => smiley (PurpleSmiley*) */
+static GHashTable *smiley_checksum_index = NULL; /* checksum (char *) => smiley (PurpleSmiley*) */
+
+static guint save_timer = 0;
+static gboolean smileys_loaded = FALSE;
+static char *smileys_dir = NULL;
+
+#define SMILEYS_DEFAULT_FOLDER			"custom_smiley"
+#define SMILEYS_LOG_ID				"smileys"
+
+#define XML_FILE_NAME				"smileys.xml"
+
+#define XML_ROOT_TAG				"smileys"
+#define XML_PROFILE_TAG			"profile"
+#define XML_PROFILE_NAME_ATTRIB_TAG		"name"
+#define XML_ACCOUNT_TAG			"account"
+#define XML_ACCOUNT_USERID_ATTRIB_TAG		"userid"
+#define XML_SMILEY_SET_TAG			"smiley_set"
+#define XML_SMILEY_TAG				"smiley"
+#define XML_SHORTCUT_ATTRIB_TAG		"shortcut"
+#define XML_CHECKSUM_ATRIB_TAG			"checksum"
+#define XML_FILENAME_ATRIB_TAG			"filename"
+
+
+/******************************************************************************
+ * XML descriptor file layout                                                 *
+ ******************************************************************************
+ *
+ * Althought we are creating the profile XML structure here, now we
+ * won't handle it.
+ * So, we just add one profile named "default" that has no associated
+ * account elements, and have only the smiley_set that will contain
+ * all existent custom smiley.
+ *
+ * It's our "Highlander Profile" :-)
+ *
+ ******************************************************************************
+ *
+ * <smileys>
+ *   <profile name="john.doe">
+ *     <account userid="john.doe@jabber.org">
+ *     <account userid="john.doe@gmail.com">
+ *     <smiley_set>
+ *       <smiley shortcut="aaa" checksum="xxxxxxxx" filename="file_name1.gif"/>
+ *       <smiley shortcut="bbb" checksum="yyyyyyy" filename="file_name2.gif"/>
+ *     </smiley_set>
+ *   </profile>
+ * </smiley>
+ *
+ *****************************************************************************/
+
+
+/*********************************************************************
+ * Forward declarations                                              *
+ *********************************************************************/
+
+static gboolean read_smiley_file(const char *path, guchar **data, size_t *len);
+
+static char *get_file_full_path(const char *filename);
+
+static PurpleSmiley *purple_smiley_create(const char *shortcut);
+
+static PurpleSmiley *purple_smiley_load_file(const char *shortcut, const char *checksum,
+		const char *filename);
+
+static void
+purple_smiley_set_data_impl(PurpleSmiley *smiley, guchar *smiley_data,
+		size_t smiley_data_len, const char *filename);
+
+static void
+purple_smiley_data_store(PurpleStoredImage *stored_img);
+
+static void
+purple_smiley_data_unstore(const char *filename);
+
+/*********************************************************************
+ * Writing to disk                                                   *
+ *********************************************************************/
+
+static xmlnode *
+smiley_to_xmlnode(PurpleSmiley *smiley)
+{
+	xmlnode *smiley_node = NULL;
+
+	smiley_node = xmlnode_new(XML_SMILEY_TAG);
+
+	if (!smiley_node)
+		return NULL;
+
+	xmlnode_set_attrib(smiley_node, XML_SHORTCUT_ATTRIB_TAG,
+			smiley->shortcut);
+
+	xmlnode_set_attrib(smiley_node, XML_CHECKSUM_ATRIB_TAG,
+			smiley->checksum);
+
+	xmlnode_set_attrib(smiley_node, XML_FILENAME_ATRIB_TAG,
+			purple_imgstore_get_filename(smiley->img));
+
+	return smiley_node;
+}
+
+static void
+add_smiley_to_main_node(gpointer key, gpointer value, gpointer user_data)
+{
+	xmlnode *child_node;
+
+	child_node = smiley_to_xmlnode(value);
+	xmlnode_insert_child((xmlnode*)user_data, child_node);
+}
+
+static xmlnode *
+smileys_to_xmlnode()
+{
+	xmlnode *root_node, *profile_node, *smileyset_node;
+
+	root_node = xmlnode_new(XML_ROOT_TAG);
+	xmlnode_set_attrib(root_node, "version", "1.0");
+
+	/* See the top comment's above to understand why initial tag elements
+	 * are not being considered by now. */
+	profile_node = xmlnode_new(XML_PROFILE_TAG);
+	if (profile_node) {
+		xmlnode_set_attrib(profile_node, XML_PROFILE_NAME_ATTRIB_TAG, "Default");
+		xmlnode_insert_child(root_node, profile_node);
+
+		smileyset_node = xmlnode_new(XML_SMILEY_SET_TAG);
+		if (smileyset_node) {
+			xmlnode_insert_child(profile_node, smileyset_node);
+			g_hash_table_foreach(smiley_shortcut_index, add_smiley_to_main_node, smileyset_node);
+		}
+	}
+
+	return root_node;
+}
+
+static void
+sync_smileys()
+{
+	xmlnode *root_node;
+	char *data;
+
+	if (!smileys_loaded) {
+		purple_debug_error(SMILEYS_LOG_ID, "Attempted to save smileys before it "
+						 "was read!\n");
+		return;
+	}
+
+	root_node = smileys_to_xmlnode();
+	data = xmlnode_to_formatted_str(root_node, NULL);
+	purple_util_write_data_to_file(XML_FILE_NAME, data, -1);
+
+	g_free(data);
+	xmlnode_free(root_node);
+}
+
+static gboolean
+save_smileys_cb(gpointer data)
+{
+	sync_smileys();
+	save_timer = 0;
+	return FALSE;
+}
+
+static void
+purple_smileys_save()
+{
+	if (save_timer == 0)
+		save_timer = purple_timeout_add_seconds(5, save_smileys_cb, NULL);
+}
+
+
+/*********************************************************************
+ * Reading from disk                                                 *
+ *********************************************************************/
+
+static PurpleSmiley *
+parse_smiley(xmlnode *smiley_node)
+{
+	PurpleSmiley *smiley;
+	const char *shortcut = NULL;
+	const char *checksum = NULL;
+	const char *filename = NULL;
+
+	shortcut = xmlnode_get_attrib(smiley_node, XML_SHORTCUT_ATTRIB_TAG);
+	checksum = xmlnode_get_attrib(smiley_node, XML_CHECKSUM_ATRIB_TAG);
+	filename = xmlnode_get_attrib(smiley_node, XML_FILENAME_ATRIB_TAG);
+
+	if ((shortcut == NULL) || (checksum == NULL) || (filename == NULL))
+		return NULL;
+
+	smiley = purple_smiley_load_file(shortcut, checksum, filename);
+
+	return smiley;
+}
+
+static void
+purple_smileys_load()
+{
+	xmlnode *root_node, *profile_node;
+	xmlnode *smileyset_node = NULL;
+	xmlnode *smiley_node;
+
+	smileys_loaded = TRUE;
+
+	root_node = purple_util_read_xml_from_file(XML_FILE_NAME,
+			_(SMILEYS_LOG_ID));
+
+	if (root_node == NULL)
+		return;
+
+	/* See the top comment's above to understand why initial tag elements
+	 * are not being considered by now. */
+	profile_node = xmlnode_get_child(root_node, XML_PROFILE_TAG);
+	if (profile_node)
+		smileyset_node = xmlnode_get_child(profile_node, XML_SMILEY_SET_TAG);
+
+	if (smileyset_node) {
+		smiley_node = xmlnode_get_child(smileyset_node, XML_SMILEY_TAG);
+		for (; smiley_node != NULL;
+				smiley_node = xmlnode_get_next_twin(smiley_node)) {
+			PurpleSmiley *smiley;
+
+			smiley = parse_smiley(smiley_node);
+		}
+	}
+
+	xmlnode_free(root_node);
+}
+
+/*********************************************************************
+ * GObject Stuff                                                     *
+ *********************************************************************/
+enum
+{
+	PROP_0,
+	PROP_SHORTCUT,
+	PROP_IMGSTORE,
+};
+
+#define PROP_SHORTCUT_S "shortcut"
+#define PROP_IMGSTORE_S "image"
+
+enum
+{
+	SIG_DESTROY,
+	SIG_LAST
+};
+
+static guint signals[SIG_LAST];
+static GObjectClass *parent_class;
+
+static void
+purple_smiley_init(GTypeInstance *instance, gpointer klass)
+{
+	PurpleSmiley *smiley = PURPLE_SMILEY(instance);
+	PURPLE_DBUS_REGISTER_POINTER(smiley, PurpleSmiley);
+}
+
+static void
+purple_smiley_get_property(GObject *object, guint param_id, GValue *value,
+		GParamSpec *spec)
+{
+	PurpleSmiley *smiley = PURPLE_SMILEY(object);
+	switch (param_id) {
+		case PROP_SHORTCUT:
+			g_value_set_string(value, smiley->shortcut);
+			break;
+		case PROP_IMGSTORE:
+			g_value_set_pointer(value, smiley->img);
+			break;
+		default:
+			G_OBJECT_WARN_INVALID_PROPERTY_ID(object, param_id, spec);
+			break;
+	}
+}
+
+static void
+purple_smiley_set_property(GObject *object, guint param_id, const GValue *value,
+		GParamSpec *spec)
+{
+	PurpleSmiley *smiley = PURPLE_SMILEY(object);
+	switch (param_id) {
+		case PROP_SHORTCUT:
+			{
+				const char *shortcut = g_value_get_string(value);
+				purple_smiley_set_shortcut(smiley, shortcut);
+			}
+			break;
+		case PROP_IMGSTORE:
+			{
+				PurpleStoredImage *img = g_value_get_pointer(value);
+
+				purple_imgstore_unref(smiley->img);
+				g_free(smiley->checksum);
+
+				smiley->img = img;
+				if (img) {
+					smiley->checksum = purple_util_get_image_checksum(
+							purple_imgstore_get_data(img),
+							purple_imgstore_get_size(img));
+					purple_smiley_data_store(img);
+				} else {
+					smiley->checksum = NULL;
+				}
+
+				g_object_notify(object, PROP_IMGSTORE_S);
+			}
+			break;
+		default:
+			G_OBJECT_WARN_INVALID_PROPERTY_ID(object, param_id, spec);
+			break;
+	}
+}
+
+static void
+purple_smiley_finalize(GObject *obj)
+{
+	PurpleSmiley *smiley = PURPLE_SMILEY(obj);
+
+	if (g_hash_table_lookup(smiley_shortcut_index, smiley->shortcut)) {
+		g_hash_table_remove(smiley_shortcut_index, smiley->shortcut);
+		g_hash_table_remove(smiley_checksum_index, smiley->checksum);
+	}
+
+	g_free(smiley->shortcut);
+	g_free(smiley->checksum);
+	if (smiley->img)
+		purple_smiley_data_unstore(purple_imgstore_get_filename(smiley->img));
+	purple_imgstore_unref(smiley->img);
+
+	PURPLE_DBUS_UNREGISTER_POINTER(smiley);
+
+	purple_smileys_save();
+}
+
+static void
+purple_smiley_dispose(GObject *gobj)
+{
+	g_signal_emit(gobj, signals[SIG_DESTROY], 0);
+	parent_class->dispose(gobj);
+}
+
+static void
+purple_smiley_class_init(PurpleSmileyClass *klass)
+{
+	GObjectClass *gobj_class = G_OBJECT_CLASS(klass);
+	GParamSpec *pspec;
+
+	parent_class = g_type_class_peek_parent(klass);
+
+	gobj_class->get_property = purple_smiley_get_property;
+	gobj_class->set_property = purple_smiley_set_property;
+	gobj_class->finalize = purple_smiley_finalize;
+	gobj_class->dispose = purple_smiley_dispose;
+
+	/* Shortcut */
+	pspec = g_param_spec_string(PROP_SHORTCUT_S, _("Shortcut"),
+			_("The text-shortcut for the smiley"),
+			NULL,
+			G_PARAM_READWRITE);
+	g_object_class_install_property(gobj_class, PROP_SHORTCUT, pspec);
+
+	/* Stored Image */
+	pspec = g_param_spec_pointer(PROP_IMGSTORE_S, _("Stored Image"),
+			_("Stored Image. (that'll have to do for now)"),
+			G_PARAM_READWRITE);
+	g_object_class_install_property(gobj_class, PROP_IMGSTORE, pspec);
+
+	signals[SIG_DESTROY] = g_signal_new("destroy",
+			G_OBJECT_CLASS_TYPE(klass),
+			G_SIGNAL_RUN_LAST,
+			0, NULL, NULL,
+			g_cclosure_marshal_VOID__VOID,
+			G_TYPE_NONE, 0);
+}
+
+GType
+purple_smiley_get_type(void)
+{
+	static GType type = 0;
+
+	if(type == 0) {
+		static const GTypeInfo info = {
+			sizeof(PurpleSmileyClass),
+			NULL,
+			NULL,
+			(GClassInitFunc)purple_smiley_class_init,
+			NULL,
+			NULL,
+			sizeof(PurpleSmiley),
+			0,
+			purple_smiley_init,
+			NULL,
+		};
+
+		type = g_type_register_static(G_TYPE_OBJECT,
+				"PurpleSmiley",
+				&info, 0);
+	}
+
+	return type;
+}
+
+/*********************************************************************
+ * Other Stuff                                                             *
+ *********************************************************************/
+
+static char *get_file_full_path(const char *filename)
+{
+	char *path;
+
+	path = g_build_filename(purple_smileys_get_storing_dir(), filename, NULL);
+
+	if (!g_file_test(path, G_FILE_TEST_EXISTS)) {
+		g_free(path);
+		return NULL;
+	}
+
+	return path;
+}
+
+static PurpleSmiley *
+purple_smiley_load_file(const char *shortcut, const char *checksum, const char *filename)
+{
+	PurpleSmiley *smiley = NULL;
+	guchar *smiley_data;
+	size_t smiley_data_len;
+	char *fullpath = NULL;
+
+	g_return_val_if_fail(shortcut  != NULL, NULL);
+	g_return_val_if_fail(checksum  != NULL, NULL);
+	g_return_val_if_fail(filename != NULL, NULL);
+
+	fullpath = get_file_full_path(filename);
+	if (!fullpath)
+		return NULL;
+
+	smiley = purple_smiley_create(shortcut);
+	if (!smiley) {
+		g_free(fullpath);
+		return NULL;
+	}
+
+	smiley->checksum = g_strdup(checksum);
+
+	if (read_smiley_file(fullpath, &smiley_data, &smiley_data_len))
+		purple_smiley_set_data_impl(smiley, smiley_data,
+				smiley_data_len, filename);
+	else
+		purple_smiley_delete(smiley);
+
+	g_free(fullpath);
+
+	return smiley;
+}
+
+static void
+purple_smiley_data_store(PurpleStoredImage *stored_img)
+{
+	const char *dirname;
+	char *path;
+	FILE *file = NULL;
+
+	g_return_if_fail(stored_img != NULL);
+
+	if (!smileys_loaded)
+		return;
+
+	dirname  = purple_smileys_get_storing_dir();
+	path = g_build_filename(dirname, purple_imgstore_get_filename(stored_img), NULL);
+
+	if (!g_file_test(dirname, G_FILE_TEST_IS_DIR)) {
+		purple_debug_info(SMILEYS_LOG_ID, "Creating smileys directory.\n");
+
+		if (g_mkdir(dirname, S_IRUSR | S_IWUSR | S_IXUSR) < 0) {
+			purple_debug_error(SMILEYS_LOG_ID,
+			                   "Unable to create directory %s: %s\n",
+			                   dirname, g_strerror(errno));
+		}
+	}
+
+	if ((file = g_fopen(path, "wb")) != NULL) {
+		if (!fwrite(purple_imgstore_get_data(stored_img),
+				purple_imgstore_get_size(stored_img), 1, file)) {
+			purple_debug_error(SMILEYS_LOG_ID, "Error writing %s: %s\n",
+			                   path, g_strerror(errno));
+		} else {
+			purple_debug_info(SMILEYS_LOG_ID, "Wrote cache file: %s\n", path);
+		}
+
+		fclose(file);
+	} else {
+		purple_debug_error(SMILEYS_LOG_ID, "Unable to create file %s: %s\n",
+		                   path, g_strerror(errno));
+		g_free(path);
+
+		return;
+	}
+
+	g_free(path);
+}
+
+static void
+purple_smiley_data_unstore(const char *filename)
+{
+	const char *dirname;
+	char *path;
+
+	g_return_if_fail(filename != NULL);
+
+	dirname  = purple_smileys_get_storing_dir();
+	path = g_build_filename(dirname, filename, NULL);
+
+	if (g_file_test(path, G_FILE_TEST_EXISTS)) {
+		if (g_unlink(path))
+			purple_debug_error(SMILEYS_LOG_ID, "Failed to delete %s: %s\n",
+			                   path, g_strerror(errno));
+		else
+			purple_debug_info(SMILEYS_LOG_ID, "Deleted cache file: %s\n", path);
+	}
+
+	g_free(path);
+}
+
+static gboolean
+read_smiley_file(const char *path, guchar **data, size_t *len)
+{
+	GError *err = NULL;
+
+	if (!g_file_get_contents(path, (gchar **)data, len, &err)) {
+		purple_debug_error(SMILEYS_LOG_ID, "Error reading %s: %s\n",
+				path, err->message);
+		g_error_free(err);
+
+		return FALSE;
+	}
+
+	return TRUE;
+}
+
+static PurpleStoredImage *
+purple_smiley_data_new(guchar *smiley_data, size_t smiley_data_len)
+{
+	char *filename;
+	PurpleStoredImage *stored_img;
+
+	g_return_val_if_fail(smiley_data != NULL,   NULL);
+	g_return_val_if_fail(smiley_data_len  > 0,  NULL);
+
+	filename = purple_util_get_image_filename(smiley_data, smiley_data_len);
+
+	if (filename == NULL) {
+		g_free(smiley_data);
+		return NULL;
+	}
+
+	stored_img = purple_imgstore_add(smiley_data, smiley_data_len, filename);
+
+	g_free(filename);
+
+	return stored_img;
+}
+
+static void
+purple_smiley_set_data_impl(PurpleSmiley *smiley, guchar *smiley_data,
+				size_t smiley_data_len, const char *filename)
+{
+	PurpleStoredImage *old_img, *new_img;
+	const char *old_filename = NULL;
+	const char *new_filename = NULL;
+
+	g_return_if_fail(smiley     != NULL);
+	g_return_if_fail(smiley_data != NULL);
+	g_return_if_fail(smiley_data_len > 0);
+
+	old_img = smiley->img;
+
+	if (filename)
+		new_img = purple_imgstore_add(smiley_data, smiley_data_len, filename);
+	else
+		new_img = purple_smiley_data_new(smiley_data, smiley_data_len);
+
+	g_object_set(G_OBJECT(smiley), PROP_IMGSTORE_S, new_img, NULL);
+
+	/* If the old and new image files have different names we need
+	 * to unstore old image file. */
+	if (!old_img)
+		return;
+
+	old_filename = purple_imgstore_get_filename(old_img);
+	new_filename = purple_imgstore_get_filename(smiley->img);
+
+	if (g_ascii_strcasecmp(old_filename, new_filename)) {
+		purple_smiley_data_unstore(old_filename);
+		purple_imgstore_unref(old_img);
+	}
+}
+
+
+/*****************************************************************************
+ * Public API functions                                                      *
+ *****************************************************************************/
+
+static PurpleSmiley *
+purple_smiley_create(const char *shortcut)
+{
+	PurpleSmiley *smiley;
+
+	smiley = PURPLE_SMILEY(g_object_new(PURPLE_TYPE_SMILEY, PROP_SHORTCUT_S, shortcut, NULL));
+
+	return smiley;
+}
+
+PurpleSmiley *
+purple_smiley_new(PurpleStoredImage *img, const char *shortcut)
+{
+	PurpleSmiley *smiley = NULL;
+
+	g_return_val_if_fail(shortcut  != NULL, NULL);
+	g_return_val_if_fail(img       != NULL, NULL);
+
+	smiley = purple_smileys_find_by_shortcut(shortcut);
+	if (smiley)
+		return smiley;
+
+	smiley = purple_smiley_create(shortcut);
+	if (!smiley)
+		return NULL;
+
+	g_object_set(G_OBJECT(smiley), PROP_IMGSTORE_S, img, NULL);
+
+	return smiley;
+}
+
+static PurpleSmiley *
+purple_smiley_new_from_stream(const char *shortcut, guchar *smiley_data,
+			size_t smiley_data_len, const char *filename)
+{
+	PurpleSmiley *smiley;
+
+	g_return_val_if_fail(shortcut  != NULL,    NULL);
+	g_return_val_if_fail(smiley_data != NULL,  NULL);
+	g_return_val_if_fail(smiley_data_len  > 0, NULL);
+
+	smiley = purple_smileys_find_by_shortcut(shortcut);
+	if (smiley)
+		return smiley;
+
+	/* purple_smiley_create() sets shortcut */
+	smiley = purple_smiley_create(shortcut);
+	if (!smiley)
+		return NULL;
+
+	purple_smiley_set_data_impl(smiley, smiley_data, smiley_data_len, filename);
+
+	purple_smiley_data_store(smiley->img);
+
+	return smiley;
+}
+
+PurpleSmiley *
+purple_smiley_new_from_file(const char *shortcut, const char *filepath)
+{
+	PurpleSmiley *smiley = NULL;
+	guchar *smiley_data;
+	size_t smiley_data_len;
+	char *filename;
+
+	g_return_val_if_fail(shortcut  != NULL,  NULL);
+	g_return_val_if_fail(filepath != NULL,  NULL);
+
+	filename = g_path_get_basename(filepath);
+	if (read_smiley_file(filepath, &smiley_data, &smiley_data_len))
+		smiley = purple_smiley_new_from_stream(shortcut, smiley_data,
+				smiley_data_len, filename);
+	g_free(filename);
+
+	return smiley;
+}
+
+void
+purple_smiley_delete(PurpleSmiley *smiley)
+{
+	g_return_if_fail(smiley != NULL);
+
+	g_object_unref(smiley);
+}
+
+gboolean
+purple_smiley_set_shortcut(PurpleSmiley *smiley, const char *shortcut)
+{
+	g_return_val_if_fail(smiley  != NULL, FALSE);
+	g_return_val_if_fail(shortcut != NULL, FALSE);
+
+	/* Check out whether the new shortcut is already being used. */
+	if (g_hash_table_lookup(smiley_shortcut_index, shortcut))
+		return FALSE;
+
+	/* Remove the old shortcut. */
+	if (smiley->shortcut)
+		g_hash_table_remove(smiley_shortcut_index, smiley->shortcut);
+
+	/* Insert the new shortcut. */
+	g_hash_table_insert(smiley_shortcut_index, g_strdup(shortcut), smiley);
+
+	g_free(smiley->shortcut);
+	smiley->shortcut = g_strdup(shortcut);
+
+	g_object_notify(G_OBJECT(smiley), PROP_SHORTCUT_S);
+
+	purple_smileys_save();
+
+	return TRUE;
+}
+
+void
+purple_smiley_set_data(PurpleSmiley *smiley, guchar *smiley_data,
+			   size_t smiley_data_len, gboolean keepfilename)
+{
+	g_return_if_fail(smiley     != NULL);
+	g_return_if_fail(smiley_data != NULL);
+	g_return_if_fail(smiley_data_len > 0);
+
+	/* Remove the previous entry */
+	g_hash_table_remove(smiley_checksum_index, smiley->checksum);
+
+	/* Update the file data. This also updates the checksum. */
+	if ((keepfilename) && (smiley->img) &&
+			(purple_imgstore_get_filename(smiley->img)))
+		purple_smiley_set_data_impl(smiley, smiley_data,
+				smiley_data_len,
+				purple_imgstore_get_filename(smiley->img));
+	else
+		purple_smiley_set_data_impl(smiley, smiley_data,
+				smiley_data_len, NULL);
+
+	/* Reinsert the index item. */
+	g_hash_table_insert(smiley_checksum_index, g_strdup(smiley->checksum), smiley);
+
+	purple_smileys_save();
+}
+
+PurpleStoredImage *
+purple_smiley_get_stored_image(const PurpleSmiley *smiley)
+{
+	return purple_imgstore_ref(smiley->img);
+}
+
+const char *purple_smiley_get_shortcut(const PurpleSmiley *smiley)
+{
+	g_return_val_if_fail(smiley != NULL, NULL);
+
+	return smiley->shortcut;
+}
+
+const char *
+purple_smiley_get_checksum(const PurpleSmiley *smiley)
+{
+	g_return_val_if_fail(smiley != NULL, NULL);
+
+	return smiley->checksum;
+}
+
+gconstpointer
+purple_smiley_get_data(const PurpleSmiley *smiley, size_t *len)
+{
+	g_return_val_if_fail(smiley != NULL, NULL);
+
+	if (smiley->img) {
+		if (len != NULL)
+			*len = purple_imgstore_get_size(smiley->img);
+
+		return purple_imgstore_get_data(smiley->img);
+	}
+
+	return NULL;
+}
+
+const char *
+purple_smiley_get_extension(const PurpleSmiley *smiley)
+{
+	if (smiley->img != NULL)
+		return purple_imgstore_get_extension(smiley->img);
+
+	return NULL;
+}
+
+char *purple_smiley_get_full_path(PurpleSmiley *smiley)
+{
+	g_return_val_if_fail(smiley != NULL, NULL);
+
+	if (smiley->img == NULL)
+		return NULL;
+
+	return get_file_full_path(purple_imgstore_get_filename(smiley->img));
+}
+
+static void add_smiley_to_list(gpointer key, gpointer value, gpointer user_data)
+{
+	GList** returninglist = (GList**)user_data;
+
+	*returninglist = g_list_append(*returninglist, value);
+}
+
+GList *
+purple_smileys_get_all(void)
+{
+	GList *returninglist = NULL;
+
+	g_hash_table_foreach(smiley_shortcut_index, add_smiley_to_list, &returninglist);
+
+	return returninglist;
+}
+
+PurpleSmiley *
+purple_smileys_find_by_shortcut(const char *shortcut)
+{
+	g_return_val_if_fail(shortcut != NULL, NULL);
+
+	return g_hash_table_lookup(smiley_shortcut_index, shortcut);
+}
+
+PurpleSmiley *
+purple_smileys_find_by_checksum(const char *checksum)
+{
+	g_return_val_if_fail(checksum != NULL, NULL);
+
+	return g_hash_table_lookup(smiley_checksum_index, checksum);
+}
+
+const char *
+purple_smileys_get_storing_dir(void)
+{
+	return smileys_dir;
+}
+
+void
+purple_smileys_init()
+{
+	smiley_shortcut_index = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);
+	smiley_checksum_index = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);
+
+	smileys_dir = g_build_filename(purple_user_dir(), SMILEYS_DEFAULT_FOLDER, NULL);
+
+	purple_smileys_load();
+}
+
+void
+purple_smileys_uninit()
+{
+	if (save_timer != 0) {
+		purple_timeout_remove(save_timer);
+		save_timer = 0;
+		sync_smileys();
+	}
+
+	g_hash_table_destroy(smiley_shortcut_index);
+	g_hash_table_destroy(smiley_checksum_index);
+	g_free(smileys_dir);
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/smiley.h	Fri May 23 16:47:59 2008 +0000
@@ -0,0 +1,271 @@
+/**
+ * @file smiley.h Smiley API
+ * @ingroup core
+ * @since 2.5.0
+ */
+
+/* purple
+ *
+ * Purple 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
+ *
+ */
+
+#ifndef _PURPLE_SMILEY_H_
+#define _PURPLE_SMILEY_H_
+
+#include <glib-object.h>
+
+#include "imgstore.h"
+#include "util.h"
+
+/**
+ * A custom smiley.
+ * This contains everything Purple will ever need to know about a custom smiley.
+ * Everything.
+ *
+ * PurpleSmiley is a GObject.
+ */
+typedef struct _PurpleSmiley        PurpleSmiley;
+typedef struct _PurpleSmileyClass   PurpleSmileyClass;
+
+#define PURPLE_TYPE_SMILEY             (purple_smiley_get_type ())
+#define PURPLE_SMILEY(smiley)          (G_TYPE_CHECK_INSTANCE_CAST ((smiley), PURPLE_TYPE_SMILEY, PurpleSmiley))
+#define PURPLE_SMILEY_CLASS(klass)     (G_TYPE_CHECK_CLASS_CAST ((klass), PURPLE_TYPE_SMILEY, PurpleSmileyClass))
+#define PURPLE_IS_SMILEY(smiley)       (G_TYPE_CHECK_INSTANCE_TYPE ((smiley), PURPLE_TYPE_SMILEY))
+#define PURPLE_IS_SMILEY_CLASS(klass)  (G_TYPE_CHECK_CLASS_TYPE ((klass), PURPLE_TYPE_SMILEY))
+#define PURPLE_SMILEY_GET_CLASS(obj)   (G_TYPE_INSTANCE_GET_CLASS ((obj), PURPLE_TYPE_SMILEY, PurpleSmileyClass))
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**************************************************************************/
+/** @name Custom Smiley API                                               */
+/**************************************************************************/
+/*@{*/
+
+/**
+ * GObject foo.
+ * @internal.
+ */
+GType purple_smiley_get_type(void);
+
+/**
+ * Creates a new custom smiley structure and populates it.
+ *
+ * If a custom smiley with the informed shortcut already exist, it
+ * will be automaticaly returned.
+ *
+ * @param img         The image associated with the smiley.
+ * @param shortcut    The custom smiley associated shortcut.
+ *
+ * @return The custom smiley structure filled up.
+ */
+PurpleSmiley *
+purple_smiley_new(PurpleStoredImage *img, const char *shortcut);
+
+/**
+ * Creates a new custom smiley structure and populates it.
+ *
+ * The data is retrieved from an already existent file.
+ *
+ * If a custom smiley with the informed shortcut already exist, it
+ * will be automaticaly returned.
+ *
+ * @param shortcut           The custom smiley associated shortcut.
+ * @param filepath           The image file to be imported to a
+ *                           new custom smiley.
+ *
+ * @return The custom smiley structure filled up.
+ */
+PurpleSmiley *
+purple_smiley_new_from_file(const char *shortcut, const char *filepath);
+
+/**
+ * Destroy the custom smiley and release the associated resources.
+ *
+ * @param smiley    The custom smiley.
+ */
+void
+purple_smiley_delete(PurpleSmiley *smiley);
+
+/**
+ * Changes the custom smiley's shortcut.
+ *
+ * @param smiley    The custom smiley.
+ * @param shortcut  The custom smiley associated shortcut.
+ *
+ * @return TRUE whether the shortcut is not associated with another
+ *         custom smiley and the parameters are valid. FALSE otherwise.
+ */
+gboolean
+purple_smiley_set_shortcut(PurpleSmiley *smiley, const char *shortcut);
+
+/**
+ * Changes the custom smiley's data.
+ *
+ * When the filename controling is made outside this API, the param
+ * #keepfilename must be TRUE.
+ * Otherwise, the file and filename will be regenerated, and the
+ * old one will be removed.
+ *
+ * @param smiley             The custom smiley.
+ * @param smiley_data        The custom smiley data.
+ * @param smiley_data_len    The custom smiley data length.
+ * @param keepfilename      The current custom smiley's filename must be
+ *                           kept.
+ */
+void
+purple_smiley_set_data(PurpleSmiley *smiley, guchar *smiley_data,
+                                           size_t smiley_data_len, gboolean keepfilename);
+
+/**
+ * Returns the custom smiley's associated shortcut.
+ *
+ * @param smiley   The custom smiley.
+ *
+ * @return The shortcut.
+ */
+const char *purple_smiley_get_shortcut(const PurpleSmiley *smiley);
+
+/**
+ * Returns the custom smiley data's checksum.
+ *
+ * @param smiley   The custom smiley.
+ *
+ * @return The checksum.
+ */
+const char *purple_smiley_get_checksum(const PurpleSmiley *smiley);
+
+/**
+ * Returns the PurpleStoredImage with the reference counter incremented.
+ *
+ * The returned PurpleStoredImage reference counter must be decremented
+ * after use.
+ *
+ * @param smiley   The custom smiley.
+ *
+ * @return A PurpleStoredImage reference.
+ */
+PurpleStoredImage *purple_smiley_get_stored_image(const PurpleSmiley *smiley);
+
+/**
+ * Returns the custom smiley's data.
+ *
+ * @param smiley  The custom smiley.
+ * @param len     If not @c NULL, the length of the icon data returned
+ *                will be set in the location pointed to by this.
+ *
+ * @return A pointer to the custom smiley data.
+ */
+gconstpointer purple_smiley_get_data(const PurpleSmiley *smiley, size_t *len);
+
+/**
+ * Returns an extension corresponding to the custom smiley's file type.
+ *
+ * @param smiley  The custom smiley.
+ *
+ * @return The custom smiley's extension, "icon" if unknown, or @c NULL if
+ *         the image data has disappeared.
+ */
+const char *purple_smiley_get_extension(const PurpleSmiley *smiley);
+
+/**
+ * Returns a full path to an custom smiley.
+ *
+ * If the custom smiley has data and the file exists in the cache, this
+ * will return a full path to the cached file.
+ *
+ * In general, it is not appropriate to be poking in the file cached
+ * directly.  If you find yourself wanting to use this function, think
+ * very long and hard about it, and then don't.
+ *
+ * @param smiley  The custom smiley.
+ *
+ * @return A full path to the file, or @c NULL under various conditions.
+ *         The caller should use #g_free to free the returned string.
+ */
+char *purple_smiley_get_full_path(PurpleSmiley *smiley);
+
+/*@}*/
+
+
+/**************************************************************************/
+/** @name Custom Smiley Subsystem API                                     */
+/**************************************************************************/
+/*@{*/
+
+/**
+ * Returns a list of all custom smileys. The caller should free the list.
+ *
+ * @return A list of all custom smileys.
+ */
+GList *
+purple_smileys_get_all(void);
+
+/**
+ * Returns the custom smiley given it's shortcut.
+ *
+ * @param shortcut The custom smiley's shortcut.
+ *
+ * @return The custom smiley (with a reference for the caller) if found,
+ *         or @c NULL if not found.
+ */
+PurpleSmiley *
+purple_smileys_find_by_shortcut(const char *shortcut);
+
+/**
+ * Returns the custom smiley given it's checksum.
+ *
+ * @param checksum The custom smiley's checksum.
+ *
+ * @return The custom smiley (with a reference for the caller) if found,
+ *         or @c NULL if not found.
+ */
+PurpleSmiley *
+purple_smileys_find_by_checksum(const char *checksum);
+
+/**
+ * Returns the directory used to store custom smiley cached files.
+ *
+ * The default directory is PURPLEDIR/smileys, unless otherwise specified
+ * by purple_buddy_icons_set_cache_dir().
+ *
+ * @return The directory to store custom smyles cached files to.
+ */
+const char *purple_smileys_get_storing_dir(void);
+
+/**
+ * Initializes the custom smiley subsystem.
+ */
+void purple_smileys_init(void);
+
+/**
+ * Uninitializes the custom smiley subsystem.
+ */
+void purple_smileys_uninit(void);
+
+/*@}*/
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _PURPLE_SMILEY_H_ */
+
--- a/libpurple/util.c	Fri May 23 16:46:19 2008 +0000
+++ b/libpurple/util.c	Fri May 23 16:47:59 2008 +0000
@@ -939,7 +939,8 @@
 	else if(IS_ENTITY("&apos;"))
 		pln = "\'";
 	else if(*(text+1) == '#' &&
-			(sscanf(text, "&#%u%1[;]", &pound, temp) == 2 || sscanf(text, "&#x%x%1[;]", &pound, temp) == 2) &&
+			(sscanf(text, "&#%u%1[;]", &pound, temp) == 2 ||
+			 sscanf(text, "&#x%x%1[;]", &pound, temp) == 2) &&
 			pound != 0) {
 		static char buf[7];
 		int buflen = g_unichar_to_utf8((gunichar)pound, buf);
@@ -2889,7 +2890,7 @@
 }
 
 char *
-purple_util_get_image_filename(gconstpointer image_data, size_t image_len)
+purple_util_get_image_checksum(gconstpointer image_data, size_t image_len)
 {
 	PurpleCipherContext *context;
 	gchar digest[41];
@@ -2910,9 +2911,18 @@
 	}
 	purple_cipher_context_destroy(context);
 
+	return g_strdup(digest);
+}
+
+char *
+purple_util_get_image_filename(gconstpointer image_data, size_t image_len)
+{
 	/* Return the filename */
-	return g_strdup_printf("%s.%s", digest,
+	char *checksum = purple_util_get_image_checksum(image_data, image_len);
+	char *filename = g_strdup_printf("%s.%s", checksum,
 	                       purple_util_get_image_extension(image_data, image_len));
+	g_free(checksum);
+	return filename;
 }
 
 gboolean
--- a/libpurple/util.h	Fri May 23 16:46:19 2008 +0000
+++ b/libpurple/util.h	Fri May 23 16:47:59 2008 +0000
@@ -706,6 +706,11 @@
 purple_util_get_image_extension(gconstpointer data, size_t len);
 
 /**
+ * Returns a SHA-1 hash string of the data passed in.
+ */
+char *purple_util_get_image_checksum(gconstpointer image_data, size_t image_len);
+
+/**
  * @return A hex encoded version of the SHA-1 hash of the data passed
  *         in with the correct file extention appended.  The file
  *         extension is determined by calling
--- a/pidgin/Makefile.am	Fri May 23 16:46:19 2008 +0000
+++ b/pidgin/Makefile.am	Fri May 23 16:47:59 2008 +0000
@@ -111,6 +111,7 @@
 	gtksavedstatuses.c \
 	gtkscrollbook.c \
 	gtksession.c \
+	gtksmiley.c \
 	gtksound.c \
 	gtksourceiter.c \
 	gtksourceundomanager.c \
@@ -163,6 +164,7 @@
 	gtksavedstatuses.h \
 	gtkscrollbook.h \
 	gtksession.h \
+	gtksmiley.h \
 	gtksound.h \
 	gtksourceiter.h \
 	gtksourceundomanager.h \
--- a/pidgin/Makefile.mingw	Fri May 23 16:46:19 2008 +0000
+++ b/pidgin/Makefile.mingw	Fri May 23 16:47:59 2008 +0000
@@ -84,6 +84,7 @@
 			gtkroomlist.c \
 			gtksavedstatuses.c \
 			gtkscrollbook.c \
+			gtksmiley.c \
 			gtksound.c \
 			gtksourceiter.c \
 			gtksourceundomanager.c \
--- a/pidgin/gtkaccount.c	Fri May 23 16:46:19 2008 +0000
+++ b/pidgin/gtkaccount.c	Fri May 23 16:47:59 2008 +0000
@@ -176,14 +176,7 @@
 	}
 
 	if (dialog->icon_img != NULL) {
-		GdkPixbufLoader *loader = gdk_pixbuf_loader_new();
-		gdk_pixbuf_loader_write(loader, purple_imgstore_get_data(dialog->icon_img),
-		                        purple_imgstore_get_size(dialog->icon_img), NULL);
-		gdk_pixbuf_loader_close(loader, NULL);
-		pixbuf = gdk_pixbuf_loader_get_pixbuf(loader);
-		if (pixbuf)
-			g_object_ref(pixbuf);
-		g_object_unref(loader);
+		pixbuf = pidgin_pixbuf_from_imgstore(dialog->icon_img);
 	}
 
 	if (pixbuf && dialog->prpl_info &&
@@ -255,6 +248,25 @@
 	}
 }
 
+static gboolean
+screenname_focus_cb(GtkWidget *widget, GdkEventFocus *event, AccountPrefsDialog *dialog)
+{
+	GHashTable *table;
+	const char *label;
+
+	table = dialog->prpl_info->get_account_text_table(NULL);
+	label = g_hash_table_lookup(table, "login_label");
+
+	if(!strcmp(gtk_entry_get_text(GTK_ENTRY(widget)), label)) {
+		gtk_entry_set_text(GTK_ENTRY(widget), "");
+		gtk_widget_modify_text(widget, GTK_STATE_NORMAL,NULL);
+	}
+
+	g_hash_table_destroy(table);
+
+	return FALSE;
+}
+
 static void
 screenname_changed_cb(GtkEntry *entry, AccountPrefsDialog *dialog)
 {
@@ -270,6 +282,32 @@
 	}
 }
 
+static gboolean
+screenname_nofocus_cb(GtkWidget *widget, GdkEventFocus *event, AccountPrefsDialog *dialog)
+{
+	GdkColor color = {0, 34952, 35466, 34181};
+	GHashTable *table;
+	const char *label;
+
+	table = dialog->prpl_info->get_account_text_table(NULL);
+	label = g_hash_table_lookup(table, "login_label");
+
+	if (*gtk_entry_get_text(GTK_ENTRY(widget)) == '\0') {
+		/* We have to avoid hitting the screenname_changed_cb function 
+		 * because it enables buttons we don't want enabled yet ;)
+		 */
+		g_signal_handlers_block_by_func(widget, G_CALLBACK(screenname_changed_cb), dialog);
+		gtk_entry_set_text(GTK_ENTRY(widget), label);
+		/* Make sure we can hit it again */
+		g_signal_handlers_unblock_by_func(widget, G_CALLBACK(screenname_changed_cb), dialog);
+		gtk_widget_modify_text(widget, GTK_STATE_NORMAL, &color);
+	}
+
+	g_hash_table_destroy(table);
+
+	return FALSE;
+}
+
 static void
 icon_filesel_choose_cb(const char *filename, gpointer data)
 {
@@ -410,6 +448,25 @@
 
 	add_pref_box(dialog, vbox, _("_Username:"), dialog->screenname_entry);
 
+	if (dialog->account != NULL)
+		username = g_strdup(purple_account_get_username(dialog->account));
+
+	if (!username && PURPLE_PROTOCOL_PLUGIN_HAS_FUNC(dialog->prpl_info, get_account_text_table)) {
+		GdkColor color = {0, 34952, 35466, 34181};
+		GHashTable *table;
+		const char *label;
+		table = dialog->prpl_info->get_account_text_table(NULL);
+		label = g_hash_table_lookup(table, "login_label");
+
+		gtk_entry_set_text(GTK_ENTRY(dialog->screenname_entry), label);
+		g_signal_connect(G_OBJECT(dialog->screenname_entry), "focus-in-event",
+				G_CALLBACK(screenname_focus_cb), dialog);
+		g_signal_connect(G_OBJECT(dialog->screenname_entry), "focus-out-event",
+				G_CALLBACK(screenname_nofocus_cb), dialog);
+		gtk_widget_modify_text(dialog->screenname_entry, GTK_STATE_NORMAL, &color);
+		g_hash_table_destroy(table);
+	}
+
 	g_signal_connect(G_OBJECT(dialog->screenname_entry), "changed",
 					 G_CALLBACK(screenname_changed_cb), dialog);
 
@@ -419,9 +476,6 @@
 	else
 		user_splits = dialog->prpl_info->user_splits;
 
-	if (dialog->account != NULL)
-		username = g_strdup(purple_account_get_username(dialog->account));
-
 	if (dialog->user_split_entries != NULL) {
 		g_list_free(dialog->user_split_entries);
 		dialog->user_split_entries = NULL;
@@ -1514,6 +1568,8 @@
 
 	/* Show the window. */
 	gtk_widget_show(win);
+	if (!account)
+		gtk_widget_grab_focus(dialog->protocol_menu);
 }
 
 /**************************************************************************
@@ -1754,39 +1810,6 @@
 	return FALSE;
 }
 
-static gboolean
-configure_cb(GtkWidget *w, GdkEventConfigure *event, AccountsWindow *dialog)
-{
-	if (GTK_WIDGET_VISIBLE(w)) {
-		int old_width = purple_prefs_get_int(PIDGIN_PREFS_ROOT "/accounts/dialog/width");
-		int col_width;
-		int difference;
-
-		purple_prefs_set_int(PIDGIN_PREFS_ROOT "/accounts/dialog/width",  event->width);
-		purple_prefs_set_int(PIDGIN_PREFS_ROOT "/accounts/dialog/height", event->height);
-
-		col_width = gtk_tree_view_column_get_width(dialog->screenname_col);
-
-		if (col_width == 0)
-			return FALSE;
-
-		difference = (MAX(old_width, event->width) -
-					  MIN(old_width, event->width));
-
-		if (difference == 0)
-			return FALSE;
-
-		if (old_width < event->width)
-			gtk_tree_view_column_set_min_width(dialog->screenname_col,
-					col_width + difference);
-		else
-			gtk_tree_view_column_set_max_width(dialog->screenname_col,
-					col_width - difference);
-	}
-
-	return FALSE;
-}
-
 static void
 add_account_cb(GtkWidget *w, AccountsWindow *dialog)
 {
@@ -1909,14 +1932,14 @@
 	column = gtk_tree_view_column_new_with_attributes(_("Enabled"),
 			 renderer, "active", COLUMN_ENABLED, NULL);
 
-	gtk_tree_view_insert_column(GTK_TREE_VIEW(treeview), column, -1);
-	gtk_tree_view_column_set_resizable(column, TRUE);
+	gtk_tree_view_column_set_resizable(column, FALSE);
+	gtk_tree_view_append_column(GTK_TREE_VIEW(treeview), column);
 
 	/* Screen Name column */
 	column = gtk_tree_view_column_new();
 	gtk_tree_view_column_set_title(column, _("Username"));
-	gtk_tree_view_insert_column(GTK_TREE_VIEW(treeview), column, -1);
 	gtk_tree_view_column_set_resizable(column, TRUE);
+	gtk_tree_view_append_column(GTK_TREE_VIEW(treeview), column);
 
 	/* Buddy Icon */
 	renderer = gtk_cell_renderer_pixbuf_new();
@@ -1935,8 +1958,8 @@
 	/* Protocol name */
 	column = gtk_tree_view_column_new();
 	gtk_tree_view_column_set_title(column, _("Protocol"));
-	gtk_tree_view_insert_column(GTK_TREE_VIEW(treeview), column, -1);
-	gtk_tree_view_column_set_resizable(column, TRUE);
+	gtk_tree_view_column_set_resizable(column, FALSE);
+	gtk_tree_view_append_column(GTK_TREE_VIEW(treeview), column);
 
 	/* Icon */
 	renderer = gtk_cell_renderer_pixbuf_new();
@@ -1978,21 +2001,14 @@
 	}
 
 	if (img != NULL) {
-		GdkPixbufLoader *loader = gdk_pixbuf_loader_new();
 		GdkPixbuf *buddyicon_pixbuf;
-
-		gdk_pixbuf_loader_write(loader, purple_imgstore_get_data(img),
-		                        purple_imgstore_get_size(img), NULL);
-		gdk_pixbuf_loader_close(loader, NULL);
-		buddyicon_pixbuf = gdk_pixbuf_loader_get_pixbuf(loader);
-
+		buddyicon_pixbuf = pidgin_pixbuf_from_imgstore(img);
 		purple_imgstore_unref(img);
 
 		if (buddyicon_pixbuf != NULL) {
 			buddyicon = gdk_pixbuf_scale_simple(buddyicon_pixbuf, 22, 22, GDK_INTERP_HYPER);
+			g_object_unref(G_OBJECT(buddyicon_pixbuf));
 		}
-
-		g_object_unref(loader);
 	}
 
 	gtk_list_store_set(store, iter,
@@ -2197,6 +2213,7 @@
 	gtk_container_add(GTK_CONTAINER(sw), treeview);
 
 	add_columns(treeview, dialog);
+	gtk_tree_view_columns_autosize(GTK_TREE_VIEW(treeview));
 
 	if (populate_accounts_list(dialog))
 		gtk_notebook_set_current_page(GTK_NOTEBOOK(accounts_window->notebook), 1);
@@ -2266,8 +2283,6 @@
 
 	g_signal_connect(G_OBJECT(win), "delete_event",
 					 G_CALLBACK(accedit_win_destroy_cb), accounts_window);
-	g_signal_connect(G_OBJECT(win), "configure_event",
-					 G_CALLBACK(configure_cb), accounts_window);
 
 	/* Setup the vbox */
 	vbox = pidgin_dialog_get_vbox_with_properties(GTK_DIALOG(win), FALSE, PIDGIN_HIG_BORDER);
--- a/pidgin/gtkblist.c	Fri May 23 16:46:19 2008 +0000
+++ b/pidgin/gtkblist.c	Fri May 23 16:47:59 2008 +0000
@@ -57,6 +57,7 @@
 #include "gtkroomlist.h"
 #include "gtkstatusbox.h"
 #include "gtkscrollbook.h"
+#include "gtksmiley.h"
 #include "gtkutils.h"
 #include "pidgin/minidialog.h"
 #include "pidgin/pidgintooltip.h"
@@ -1528,6 +1529,48 @@
 	return FALSE;
 }
 
+static void
+set_node_custom_icon_cb(const gchar *filename, gpointer data)
+{
+	if (filename) {
+		PurpleBlistNode *node = (PurpleBlistNode*)data;
+
+		purple_buddy_icons_node_set_custom_icon_from_file(node,
+		                                                  filename);
+	}
+}
+
+static void
+set_node_custom_icon(GtkWidget *w, PurpleBlistNode *node)
+{
+	/* This doesn't keep track of the returned dialog (so that successive
+	 * calls could be made to re-display that dialog). Do we want that? */
+	GtkWidget *win = pidgin_buddy_icon_chooser_new(NULL, set_node_custom_icon_cb, node);
+	gtk_widget_show_all(win);
+}
+
+static void
+remove_node_custom_icon(GtkWidget *w, PurpleBlistNode *node)
+{
+	purple_buddy_icons_node_set_custom_icon(node, NULL, 0);
+}
+
+static void
+add_buddy_icon_menu_items(GtkWidget *menu, PurpleBlistNode *node)
+{
+	GtkWidget *item;
+
+	pidgin_new_item_from_stock(menu, _("Set Custom Icon"), NULL,
+	                           G_CALLBACK(set_node_custom_icon), node, 0,
+	                           0, NULL);
+
+	item = pidgin_new_item_from_stock(menu, _("Remove Custom Icon"), NULL,
+	                           G_CALLBACK(remove_node_custom_icon), node,
+	                           0, 0, NULL);
+	if (!purple_buddy_icons_node_has_custom_icon(node))
+		gtk_widget_set_sensitive(item, FALSE);
+}
+
 static GtkWidget *
 create_group_menu (PurpleBlistNode *node, PurpleGroup *g)
 {
@@ -1551,12 +1594,13 @@
 				NULL, G_CALLBACK(gtk_blist_menu_showoffline_cb), node, 0, 0, NULL);
 	}
 
+	add_buddy_icon_menu_items(menu, node);
+
 	pidgin_append_blist_node_extended_menu(menu, node);
 
 	return menu;
 }
 
-
 static GtkWidget *
 create_chat_menu(PurpleBlistNode *node, PurpleChat *c)
 {
@@ -1589,6 +1633,8 @@
 	pidgin_new_item_from_stock(menu, _("_Remove"), GTK_STOCK_REMOVE,
 				 G_CALLBACK(pidgin_blist_remove_cb), node, 0, 0, NULL);
 
+	add_buddy_icon_menu_items(menu, node);
+
 	return menu;
 }
 
@@ -1610,6 +1656,8 @@
 	pidgin_new_item_from_stock(menu, _("_Remove"), GTK_STOCK_REMOVE,
 				 G_CALLBACK(pidgin_blist_remove_cb), node, 0, 0, NULL);
 
+	add_buddy_icon_menu_items(menu, node);
+
 	pidgin_separator(menu);
 
 	pidgin_new_item_from_stock(menu, _("_Collapse"), GTK_STOCK_ZOOM_OUT,
@@ -1634,6 +1682,8 @@
 	if(PURPLE_BLIST_NODE_IS_CONTACT(node)) {
 		pidgin_separator(menu);
 
+		add_buddy_icon_menu_items(menu, node);
+
 		if(gtknode->contact_expanded) {
 			pidgin_new_item_from_stock(menu, _("_Collapse"),
 						 GTK_STOCK_ZOOM_OUT,
@@ -2493,51 +2543,71 @@
 
 
 static GdkPixbuf *pidgin_blist_get_buddy_icon(PurpleBlistNode *node,
-		gboolean scaled, gboolean greyed)
-{
-	GdkPixbuf *buf, *ret = NULL;
+                                              gboolean scaled, gboolean greyed)
+{
+	gsize len;
 	GdkPixbufLoader *loader;
-	PurpleBuddyIcon *icon = NULL;
-	const guchar *data = NULL;
-	gsize len;
 	PurpleBuddy *buddy = NULL;
+	PurpleGroup *group = NULL;
+	const guchar *data = NULL;
+	GdkPixbuf *buf, *ret = NULL;
+	PurpleBuddyIcon *icon = NULL;
 	PurpleAccount *account = NULL;
-	PurplePluginProtocolInfo *prpl_info = NULL;
+	PurpleContact *contact = NULL;
 	PurpleStoredImage *custom_img;
-
-	if(PURPLE_BLIST_NODE_IS_CONTACT(node)) {
+	PurplePluginProtocolInfo *prpl_info = NULL;
+	gint orig_width, orig_height, scale_width, scale_height;
+
+	if (PURPLE_BLIST_NODE_IS_CONTACT(node)) {
 		buddy = purple_contact_get_priority_buddy((PurpleContact*)node);
-	} else if(PURPLE_BLIST_NODE_IS_BUDDY(node)) {
+		contact = (PurpleContact*)node;
+	} else if (PURPLE_BLIST_NODE_IS_BUDDY(node)) {
 		buddy = (PurpleBuddy*)node;
+		contact = purple_buddy_get_contact(buddy);
+	} else if (PURPLE_BLIST_NODE_IS_GROUP(node)) {
+		group = (PurpleGroup*)node;
+	} else if (PURPLE_BLIST_NODE_IS_CHAT(node)) {
+		/* We don't need to do anything here. We just need to not fall
+		 * into the else block and return. */
 	} else {
 		return NULL;
 	}
 
-	if(buddy == NULL)
-		return NULL;
-
-	account = purple_buddy_get_account(buddy);
-
-	if(account && account->gc)
+	if (buddy) {
+		account = purple_buddy_get_account(buddy);
+	}
+
+	if(account && account->gc) {
 		prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(account->gc->prpl);
+	}
 
 #if 0
 	if (!purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/blist/show_buddy_icons"))
 		return NULL;
 #endif
 
-	custom_img = purple_buddy_icons_find_custom_icon(purple_buddy_get_contact(buddy));
-	if (custom_img)
-	{
+	/* If we have a contact then this is either a contact or a buddy and
+	 * we want to fetch the custom icon for the contact. If we don't have
+	 * a contact then this is a group or some other type of node and we
+	 * want to use that directly. */
+	if (contact) {
+		custom_img = purple_buddy_icons_node_find_custom_icon((PurpleBlistNode*)contact);
+	} else {
+		custom_img = purple_buddy_icons_node_find_custom_icon(node);
+	}
+
+	if (custom_img) {
 		data = purple_imgstore_get_data(custom_img);
 		len = purple_imgstore_get_size(custom_img);
 	}
 
 	if (data == NULL) {
-		/* Not sure I like this...*/
-		if (!(icon = purple_buddy_icons_find(buddy->account, buddy->name)))
-			return NULL;
-		data = purple_buddy_icon_get_data(icon, &len);
+		if (buddy) {
+			/* Not sure I like this...*/
+			if (!(icon = purple_buddy_icons_find(buddy->account, buddy->name)))
+				return NULL;
+			data = purple_buddy_icon_get_data(icon, &len);
+		}
 
 		if(data == NULL)
 			return NULL;
@@ -2555,56 +2625,68 @@
 		g_object_ref(G_OBJECT(buf));
 	g_object_unref(G_OBJECT(loader));
 
-	if (buf) {
-		int orig_width, orig_height;
-		int scale_width, scale_height;
-
-		if (greyed) {
+	if (!buf) {
+		return NULL;
+	}
+
+	if (greyed) {
+		gboolean offline = FALSE, idle = FALSE;
+
+		if (buddy) {
 			PurplePresence *presence = purple_buddy_get_presence(buddy);
 			if (!PURPLE_BUDDY_IS_ONLINE(buddy))
-				gdk_pixbuf_saturate_and_pixelate(buf, buf, 0.0, FALSE);
+				offline = TRUE;
 			if (purple_presence_is_idle(presence))
-				gdk_pixbuf_saturate_and_pixelate(buf, buf, 0.25, FALSE);
+				idle = TRUE;
+		} else if (group) {
+			if (purple_blist_get_group_online_count(group) == 0)
+				offline = TRUE;
 		}
 
-		/* i'd use the pidgin_buddy_icon_get_scale_size() thing,
-		 * but it won't tell me the original size, which I need for scaling
-		 * purposes */
-		scale_width = orig_width = gdk_pixbuf_get_width(buf);
-		scale_height = orig_height = gdk_pixbuf_get_height(buf);
-
-		if (prpl_info && prpl_info->icon_spec.scale_rules & PURPLE_ICON_SCALE_DISPLAY)
-			purple_buddy_icon_get_scale_size(&prpl_info->icon_spec, &scale_width, &scale_height);
-
-		if (scaled || scale_height > 200 || scale_width > 200) {
-			GdkPixbuf *tmpbuf;
-			float scale_size = scaled ? 32.0 : 200.0;
-			if(scale_height > scale_width) {
-				scale_width = scale_size * (double)scale_width / (double)scale_height;
-				scale_height = scale_size;
-			} else {
-				scale_height = scale_size * (double)scale_height / (double)scale_width;
-				scale_width = scale_size;
-			}
-			/* scale & round before making square, so rectangular (but non-square)
-			 * images get rounded corners too */
-			tmpbuf = gdk_pixbuf_new(GDK_COLORSPACE_RGB, TRUE, 8, scale_width, scale_height);
-			gdk_pixbuf_fill(tmpbuf, 0x00000000);
-			gdk_pixbuf_scale(buf, tmpbuf, 0, 0, scale_width, scale_height, 0, 0, (double)scale_width/(double)orig_width, (double)scale_height/(double)orig_height, GDK_INTERP_BILINEAR);
-			if (pidgin_gdk_pixbuf_is_opaque(tmpbuf))
-				pidgin_gdk_pixbuf_make_round(tmpbuf);
-			ret = gdk_pixbuf_new(GDK_COLORSPACE_RGB, TRUE, 8, scale_size, scale_size);
-			gdk_pixbuf_fill(ret, 0x00000000);
-			gdk_pixbuf_copy_area(tmpbuf, 0, 0, scale_width, scale_height, ret, (scale_size-scale_width)/2, (scale_size-scale_height)/2);
-			g_object_unref(G_OBJECT(tmpbuf));
+		if (offline)
+			gdk_pixbuf_saturate_and_pixelate(buf, buf, 0.0, FALSE);
+
+		if (idle)
+			gdk_pixbuf_saturate_and_pixelate(buf, buf, 0.25, FALSE);
+	}
+
+	/* I'd use the pidgin_buddy_icon_get_scale_size() thing, but it won't
+	 * tell me the original size, which I need for scaling purposes. */
+	scale_width = orig_width = gdk_pixbuf_get_width(buf);
+	scale_height = orig_height = gdk_pixbuf_get_height(buf);
+
+	if (prpl_info && prpl_info->icon_spec.scale_rules & PURPLE_ICON_SCALE_DISPLAY)
+		purple_buddy_icon_get_scale_size(&prpl_info->icon_spec, &scale_width, &scale_height);
+
+	if (scaled || scale_height > 200 || scale_width > 200) {
+		GdkPixbuf *tmpbuf;
+		float scale_size = scaled ? 32.0 : 200.0;
+		if(scale_height > scale_width) {
+			scale_width = scale_size * (double)scale_width / (double)scale_height;
+			scale_height = scale_size;
 		} else {
-			ret = gdk_pixbuf_scale_simple(buf,scale_width,scale_height, GDK_INTERP_BILINEAR);
+			scale_height = scale_size * (double)scale_height / (double)scale_width;
+			scale_width = scale_size;
 		}
-		g_object_unref(G_OBJECT(buf));
-	}
+		/* Scale & round before making square, so rectangular (but
+		 * non-square) images get rounded corners too. */
+		tmpbuf = gdk_pixbuf_new(GDK_COLORSPACE_RGB, TRUE, 8, scale_width, scale_height);
+		gdk_pixbuf_fill(tmpbuf, 0x00000000);
+		gdk_pixbuf_scale(buf, tmpbuf, 0, 0, scale_width, scale_height, 0, 0, (double)scale_width/(double)orig_width, (double)scale_height/(double)orig_height, GDK_INTERP_BILINEAR);
+		if (pidgin_gdk_pixbuf_is_opaque(tmpbuf))
+			pidgin_gdk_pixbuf_make_round(tmpbuf);
+		ret = gdk_pixbuf_new(GDK_COLORSPACE_RGB, TRUE, 8, scale_size, scale_size);
+		gdk_pixbuf_fill(ret, 0x00000000);
+		gdk_pixbuf_copy_area(tmpbuf, 0, 0, scale_width, scale_height, ret, (scale_size-scale_width)/2, (scale_size-scale_height)/2);
+		g_object_unref(G_OBJECT(tmpbuf));
+	} else {
+		ret = gdk_pixbuf_scale_simple(buf,scale_width,scale_height, GDK_INTERP_BILINEAR);
+	}
+	g_object_unref(G_OBJECT(buf));
 
 	return ret;
 }
+
 /* # - Status Icon
  * P - Protocol Icon
  * A - Buddy Icon
@@ -3183,6 +3265,7 @@
 	{ N_("/_Tools"), NULL, NULL, 0, "<Branch>", NULL },
 	{ N_("/Tools/Buddy _Pounces"), NULL, pidgin_pounces_manager_show, 1, "<Item>", NULL },
 	{ N_("/Tools/_Certificates"), NULL, pidgin_certmgr_show, 0, "<Item>", NULL },
+	{ N_("/Tools/Smile_y"), "<CTL>Y", pidgin_smiley_manager_show, 0, "<StockItem>", PIDGIN_STOCK_TOOLBAR_SMILEY },
 	{ N_("/Tools/Plu_gins"), "<CTL>U", pidgin_plugin_dialog_show, 2, "<StockItem>", PIDGIN_STOCK_TOOLBAR_PLUGINS },
 	{ N_("/Tools/Pr_eferences"), "<CTL>P", pidgin_prefs_show, 0, "<StockItem>", GTK_STOCK_PREFERENCES },
 	{ N_("/Tools/Pr_ivacy"), NULL, pidgin_privacy_dialog_show, 0, "<Item>", NULL },
@@ -5822,14 +5905,16 @@
 	return FALSE;
 }
 
-/*This version of pidgin_blist_update_group can take the original buddy
-or a group, but has much better algorithmic performance with a pre-known buddy*/
-static void pidgin_blist_update_group(PurpleBuddyList *list, PurpleBlistNode *node)
-{
+/* This version of pidgin_blist_update_group can take the original buddy or a
+ * group, but has much better algorithmic performance with a pre-known buddy.
+ */
+static void pidgin_blist_update_group(PurpleBuddyList *list,
+                                      PurpleBlistNode *node)
+{
+	gint count;
 	PurpleGroup *group;
-	int count;
+	PurpleBlistNode* gnode;
 	gboolean show = FALSE, show_offline = FALSE;
-	PurpleBlistNode* gnode;
 
 	g_return_if_fail(node != NULL);
 
@@ -5864,12 +5949,13 @@
 	}
 
 	if (show) {
+		gchar *title;
+		gboolean biglist;
 		GtkTreeIter iter;
 		GtkTreePath *path;
 		gboolean expanded;
 		GdkColor bgcolor;
-		char *title;
-
+		GdkPixbuf *avatar = NULL;
 
 		if(!insert_node(list, gnode, &iter))
 			return;
@@ -5881,17 +5967,23 @@
 		gtk_tree_path_free(path);
 
 		title = pidgin_get_group_title(gnode, expanded);
+		biglist = purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/blist/show_buddy_icons");
+
+		if (biglist) {
+			avatar = pidgin_blist_get_buddy_icon(gnode, TRUE, TRUE);
+		}
 
 		gtk_tree_store_set(gtkblist->treemodel, &iter,
 				   STATUS_ICON_VISIBLE_COLUMN, FALSE,
 				   STATUS_ICON_COLUMN, NULL,
 				   NAME_COLUMN, title,
 				   NODE_COLUMN, gnode,
-	/* 			   BGCOLOR_COLUMN, &bgcolor,     */
+	/*			   BGCOLOR_COLUMN, &bgcolor,     */
 				   GROUP_EXPANDER_COLUMN, TRUE,
 				   GROUP_EXPANDER_VISIBLE_COLUMN, TRUE,
 				   CONTACT_EXPANDER_VISIBLE_COLUMN, FALSE,
-				   BUDDY_ICON_VISIBLE_COLUMN, FALSE,
+				   BUDDY_ICON_COLUMN, avatar,
+				   BUDDY_ICON_VISIBLE_COLUMN, biglist,
 				   IDLE_VISIBLE_COLUMN, FALSE,
 				   EMBLEM_VISIBLE_COLUMN, FALSE,
 				   -1);
@@ -6181,7 +6273,7 @@
 				STATUS_ICON_COLUMN, status,
 				STATUS_ICON_VISIBLE_COLUMN, TRUE,
 				BUDDY_ICON_COLUMN, avatar ? avatar : gtkblist->empty_avatar,
-				BUDDY_ICON_VISIBLE_COLUMN,  purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/blist/show_buddy_icons"),
+				BUDDY_ICON_VISIBLE_COLUMN, showicons,
 				EMBLEM_COLUMN, emblem,
 				EMBLEM_VISIBLE_COLUMN, emblem != NULL,
 				PROTOCOL_ICON_COLUMN, prpl_icon,
--- a/pidgin/gtkconv.c	Fri May 23 16:46:19 2008 +0000
+++ b/pidgin/gtkconv.c	Fri May 23 16:47:59 2008 +0000
@@ -159,8 +159,6 @@
 static void update_typing_message(PidginConversation *gtkconv, const char *message);
 static const char *item_factory_translate_func (const char *path, gpointer func_data);
 gboolean pidgin_conv_has_focus(PurpleConversation *conv);
-static void pidgin_conv_custom_smiley_allocated(GdkPixbufLoader *loader, gpointer user_data);
-static void pidgin_conv_custom_smiley_closed(GdkPixbufLoader *loader, gpointer user_data);
 static GdkColor* generate_nick_colors(guint *numcolors, GdkColor background);
 static gboolean color_is_visible(GdkColor foreground, GdkColor background, int color_contrast, int brightness_contrast);
 static GtkTextTag *get_buddy_tag(PurpleConversation *conv, const char *who, gboolean create);
@@ -2514,10 +2512,6 @@
 		PurpleBuddy *b = purple_find_buddy(conv->account, conv->name);
 		if (b)
 			emblem = pidgin_blist_get_emblem((PurpleBlistNode*)b);
-	} else {
-		PurpleChat *c = purple_blist_find_chat(conv->account, conv->name);
-		if (c)
-			emblem = pidgin_blist_get_emblem((PurpleBlistNode*)c);
 	}
 
 	g_return_if_fail(status != NULL);
@@ -2725,10 +2719,22 @@
 custom_icon_sel_cb(const char *filename, gpointer data)
 {
 	if (filename) {
+		const gchar *name;
+		PurpleBuddy *buddy;
+		PurpleContact *contact;
 		PidginConversation *gtkconv = data;
 		PurpleConversation *conv = gtkconv->active_conv;
 		PurpleAccount *account = purple_conversation_get_account(conv);
-		pidgin_set_custom_buddy_icon(account, purple_conversation_get_name(conv), filename);
+
+		name = purple_conversation_get_name(conv);
+		buddy = purple_find_buddy(account, name);
+		if (!buddy) {
+			purple_debug_info("custom-icon", "You can only set custom icons for people on your buddylist.\n");
+			return;
+		}
+		contact = purple_buddy_get_contact(buddy);
+
+		purple_buddy_icons_node_set_custom_icon_from_file((PurpleBlistNode*)contact, filename);
 	}
 }
 
@@ -2770,12 +2776,21 @@
 static void
 remove_custom_icon_cb(GtkWidget *widget, PidginConversation *gtkconv)
 {
-	PurpleConversation *conv;
+	const gchar *name;
+	PurpleBuddy *buddy;
 	PurpleAccount *account;
-
-	conv = gtkconv->active_conv;
+	PurpleContact *contact;
+	PurpleConversation *conv = gtkconv->active_conv;
+
 	account = purple_conversation_get_account(conv);
-	pidgin_set_custom_buddy_icon(account, purple_conversation_get_name(conv), NULL);
+	name = purple_conversation_get_name(conv);
+	buddy = purple_find_buddy(account, name);
+	if (!buddy) {
+		return;
+	}
+	contact = purple_buddy_get_contact(buddy);
+
+	purple_buddy_icons_node_set_custom_icon_from_file((PurpleBlistNode*)contact, NULL);
 }
 
 static void
@@ -2876,7 +2891,7 @@
 	if (buddy)
 	{
 		PurpleContact *contact = purple_buddy_get_contact(buddy);
-		if (contact && purple_buddy_icons_has_custom_icon(contact))
+		if (contact && purple_buddy_icons_node_has_custom_icon((PurpleBlistNode*)contact))
 		{
 			pidgin_new_item_from_stock(menu, _("Remove Custom Icon"), NULL,
 			                           G_CALLBACK(remove_custom_icon_cb), gtkconv,
@@ -5167,6 +5182,12 @@
 		nbr_nick_colors = NUM_NICK_COLORS;
 		nick_colors = generate_nick_colors(&nbr_nick_colors, gtk_widget_get_style(gtkconv->imhtml)->base[GTK_STATE_NORMAL]);
 	}
+
+	/* We don't want to see the custom smileys if our buddy send us the
+	 * defined shortcut. */
+	pidgin_themes_smiley_themeize(gtkconv->imhtml);
+	/* We want to see our smileys in the entry */
+	pidgin_themes_smiley_themeize_custom(gtkconv->entry);
 }
 
 static void
@@ -5476,8 +5497,6 @@
 	char *bracket;
 	int tag_count = 0;
 	gboolean is_rtl_message = FALSE;
-	GtkSmileyTree *tree = NULL;
-	GHashTable *smiley_data = NULL;
 
 	g_return_if_fail(conv != NULL);
 	gtkconv = PIDGIN_CONVERSATION(conv);
@@ -5636,14 +5655,8 @@
 
 	if (!(flags & PURPLE_MESSAGE_RECV))
 	{
-		/* Temporarily revert to the original smiley-data to avoid showing up
-		 * custom smileys of the buddy when sending message
-		 */
-		tree = GTK_IMHTML(gtkconv->imhtml)->default_smilies;
-		GTK_IMHTML(gtkconv->imhtml)->default_smilies =
-								GTK_IMHTML(gtkconv->entry)->default_smilies;
-		smiley_data = GTK_IMHTML(gtkconv->imhtml)->smiley_data;
-		GTK_IMHTML(gtkconv->imhtml)->smiley_data = GTK_IMHTML(gtkconv->entry)->smiley_data;
+		/* We want to see our own smileys. Need to revert it after send*/
+		pidgin_themes_smiley_themeize_custom(gtkconv->imhtml);
 	}
 
 	/* TODO: These colors should not be hardcoded so log.c can use them */
@@ -5674,10 +5687,15 @@
 		char *alias_escaped = (alias ? g_markup_escape_text(alias, strlen(alias)) : g_strdup(""));
 		/* The initial offset is to deal with
 		 * escaped entities making the string longer */
-		int tag_start_offset = alias ? (strlen(alias_escaped) - strlen(alias)) : 0;
+		int tag_start_offset = 0;
 		int tag_end_offset = 0;
 		const char *tagname = NULL;
 
+		GtkTextIter start, end;
+		GtkTextMark *mark;
+		GtkTextTag *tag;
+		GtkTextBuffer *buffer = GTK_IMHTML(gtkconv->imhtml)->text_buffer;
+
 		/* Enforce direction on alias */
 		if (is_rtl_message)
 			str_embed_direction_chars(&alias_escaped);
@@ -5700,8 +5718,7 @@
 			if (purple_message_meify(new_message, -1)) {
 				if (flags & PURPLE_MESSAGE_AUTO_RESP) {
 					g_snprintf(str, 1024, "%s ***%s", AUTO_RESPONSE, alias_escaped);
-					tag_start_offset += 4
-						+ strlen(AUTO_RESPONSE);
+					tag_start_offset += strlen(AUTO_RESPONSE) - 6 + 4;
 				} else {
 					g_snprintf(str, 1024, "***%s", alias_escaped);
 					tag_start_offset += 3;
@@ -5714,8 +5731,7 @@
 			} else {
 				if (flags & PURPLE_MESSAGE_AUTO_RESP) {
 					g_snprintf(str, 1024, "%s %s", alias_escaped, AUTO_RESPONSE);
-					tag_start_offset += 1
-						+ strlen(AUTO_RESPONSE);
+					tag_start_offset += strlen(AUTO_RESPONSE) - 6 + 1;
 				} else {
 					g_snprintf(str, 1024, "%s:", alias_escaped);
 					tag_end_offset = 1;
@@ -5738,31 +5754,42 @@
 
 		g_free(alias_escaped);
 
-		g_snprintf(buf2, BUF_LONG,
-				"<FONT %s><FONT SIZE=\"2\"><!--%s --></FONT>%s</FONT> ",
-				sml_attrib ? sml_attrib : "", mdate, str);
+		if (tagname)
+			tag = gtk_text_tag_table_lookup(gtk_text_buffer_get_tag_table(buffer), tagname);
+		else
+			tag = get_buddy_tag(conv, name, TRUE);
+
+		if (GTK_IMHTML(gtkconv->imhtml)->show_comments) {
+			/* The color for the timestamp has to be set in the font-tags, unfortunately.
+			 * Applying the nick-tag to timestamps would work, but that can make it
+			 * bold. I thought applying the "comment" tag again, which has "weight" set
+			 * to PANGO_WEIGHT_NORMAL, would remove the boldness. But it doesn't. So
+			 * this will have to do. I don't terribly like it.  -- sadrul */
+			GdkColor *color = NULL;
+			gboolean set = FALSE;
+			char colcode[] = "COLOR=\"#XXXXXX\"";
+			g_object_get(G_OBJECT(tag), "foreground-set", &set, "foreground-gdk", &color, NULL);
+			if (set && color)
+				g_snprintf(colcode, sizeof(colcode), "COLOR=\"#%02x%02x%02x\"",
+						color->red >> 8, color->green >> 8, color->blue >> 8);
+			else
+				colcode[0] = '\0';
+			g_snprintf(buf2, BUF_LONG, "<FONT %s SIZE=\"2\"><!--%s --></FONT>", colcode, mdate);
+			gtk_imhtml_append_text(GTK_IMHTML(gtkconv->imhtml), buf2, gtk_font_options_all | GTK_IMHTML_NO_SCROLL);
+			if (color)
+				gdk_color_free(color);
+		}
+
+		gtk_text_buffer_get_end_iter(buffer, &end);
+		mark = gtk_text_buffer_create_mark(buffer, NULL, &end, TRUE);
+
+		g_snprintf(buf2, BUF_LONG, "<FONT %s>%s</FONT> ", sml_attrib ? sml_attrib : "", str);
 		gtk_imhtml_append_text(GTK_IMHTML(gtkconv->imhtml), buf2, gtk_font_options_all | GTK_IMHTML_NO_SCROLL);
 
-		if (tagname ||
-				(purple_conversation_get_type(conv) == PURPLE_CONV_TYPE_CHAT &&
-				 !(flags & PURPLE_MESSAGE_SEND))) {
-			GtkTextIter start, end;
-			GtkTextTag *tag;
-			GtkTextBuffer *buffer = GTK_IMHTML(gtkconv->imhtml)->text_buffer;
-
-			if (tagname)
-				tag = gtk_text_tag_table_lookup(gtk_text_buffer_get_tag_table(buffer), tagname);
-			else
-				tag = get_buddy_tag(conv, name, TRUE);
-
-			gtk_text_buffer_get_end_iter(buffer, &end);
-			gtk_text_iter_backward_chars(&end, tag_end_offset + 1);
-
-			gtk_text_buffer_get_end_iter(buffer, &start);
-			gtk_text_iter_backward_chars(&start, strlen(str) + 1 - tag_start_offset);
-
-			gtk_text_buffer_apply_tag(buffer, tag, &start, &end);
-		}
+		gtk_text_buffer_get_end_iter(buffer, &end);
+		gtk_text_buffer_get_iter_at_mark(buffer, &start, mark);
+		gtk_text_buffer_apply_tag(buffer, tag, &start, &end);
+		gtk_text_buffer_delete_mark(buffer, mark);
 
 		g_free(str);
 
@@ -5814,8 +5841,7 @@
 	if (!(flags & PURPLE_MESSAGE_RECV))
 	{
 		/* Restore the smiley-data */
-		GTK_IMHTML(gtkconv->imhtml)->default_smilies = tree;
-		GTK_IMHTML(gtkconv->imhtml)->smiley_data = smiley_data;
+		pidgin_themes_smiley_themeize(gtkconv->imhtml);
 	}
 
 	purple_signal_emit(pidgin_conversations_get_handle(),
@@ -6043,119 +6069,24 @@
 	return FALSE;
 }
 
-static void pidgin_conv_custom_smiley_allocated(GdkPixbufLoader *loader, gpointer user_data)
-{
-	GtkIMHtmlSmiley *smiley;
-
-	smiley = (GtkIMHtmlSmiley *)user_data;
-	smiley->icon = gdk_pixbuf_loader_get_animation(loader);
-
-	if (smiley->icon)
-		g_object_ref(G_OBJECT(smiley->icon));
-#ifdef DEBUG_CUSTOM_SMILEY
-	purple_debug_info("custom-smiley", "pidgin_conv_custom_smiley_allocated(): got GdkPixbufAnimation %p for smiley '%s'\n", smiley->icon, smiley->smile);
-#endif
-}
-
-static void pidgin_conv_custom_smiley_closed(GdkPixbufLoader *loader, gpointer user_data)
-{
-	GtkIMHtmlSmiley *smiley;
-	GtkWidget *icon = NULL;
-	GtkTextChildAnchor *anchor = NULL;
-	GSList *current = NULL;
-
-	smiley = (GtkIMHtmlSmiley *)user_data;
-	if (!smiley->imhtml) {
-#ifdef DEBUG_CUSTOM_SMILEY
-		purple_debug_error("custom-smiley", "pidgin_conv_custom_smiley_closed(): orphan smiley found: %p\n", smiley);
-#endif
-		g_object_unref(G_OBJECT(loader));
-		smiley->loader = NULL;
-		return;
-	}
-
-	for (current = smiley->anchors; current; current = g_slist_next(current)) {
-
-		icon = gtk_image_new_from_animation(smiley->icon);
-
-#ifdef DEBUG_CUSTOM_SMILEY
-		purple_debug_info("custom-smiley", "pidgin_conv_custom_smiley_closed(): got GtkImage %p from GtkPixbufAnimation %p for smiley '%s'\n",
-				icon, smiley->icon, smiley->smile);
-#endif
-		if (icon) {
-			GList *wids;
-			gtk_widget_show(icon);
-
-			anchor = GTK_TEXT_CHILD_ANCHOR(current->data);
-			wids = gtk_text_child_anchor_get_widgets(anchor);
-
-			g_object_set_data_full(G_OBJECT(anchor), "gtkimhtml_plaintext", purple_unescape_html(smiley->smile), g_free);
-			g_object_set_data_full(G_OBJECT(anchor), "gtkimhtml_htmltext", g_strdup(smiley->smile), g_free);
-
-			if (smiley->imhtml) {
-				if (wids) {
-					GList *children = gtk_container_get_children(GTK_CONTAINER(wids->data));
-					g_list_foreach(children, (GFunc)gtk_widget_destroy, NULL);
-					g_list_free(children);
-					gtk_container_add(GTK_CONTAINER(wids->data), icon);
-				} else
-					gtk_text_view_add_child_at_anchor(GTK_TEXT_VIEW(smiley->imhtml), icon, anchor);
-			}
-			g_list_free(wids);
-		}
-
-	}
-
-	g_slist_free(smiley->anchors);
-	smiley->anchors = NULL;
-
-	g_object_unref(G_OBJECT(loader));
-	smiley->loader = NULL;
-}
-
 static gboolean
 add_custom_smiley_for_imhtml(GtkIMHtml *imhtml, const char *sml, const char *smile)
 {
 	GtkIMHtmlSmiley *smiley;
-	GdkPixbufLoader *loader;
 
 	smiley = gtk_imhtml_smiley_get(imhtml, sml, smile);
 
 	if (smiley) {
-
 		if (!(smiley->flags & GTK_IMHTML_SMILEY_CUSTOM)) {
 			return FALSE;
 		}
-
-		/* Close the old GdkPixbufAnimation, then create a new one for
-		 * the smiley we are about to receive */
-		g_object_unref(G_OBJECT(smiley->icon));
-
-		/* XXX: Is it necessary to _unref the loader first? */
-		smiley->loader = gdk_pixbuf_loader_new();
-		smiley->icon = NULL;
-
-		g_signal_connect(smiley->loader, "area_prepared", G_CALLBACK(pidgin_conv_custom_smiley_allocated), smiley);
-		g_signal_connect(smiley->loader, "closed", G_CALLBACK(pidgin_conv_custom_smiley_closed), smiley);
-
+		gtk_imhtml_smiley_reload(smiley);
 		return TRUE;
 	}
 
-	loader = gdk_pixbuf_loader_new();
-
-	/* this is wrong, this file ought not call g_new on GtkIMHtmlSmiley */
-	/* Let gtk_imhtml have a gtk_imhtml_smiley_new function, and let
-	   GtkIMHtmlSmiley by opaque */
-	smiley = g_new0(GtkIMHtmlSmiley, 1);
-	smiley->file   = NULL;
-	smiley->smile  = g_strdup(smile);
-	smiley->loader = loader;
-	smiley->flags  = smiley->flags | GTK_IMHTML_SMILEY_CUSTOM;
-
-	g_signal_connect(smiley->loader, "area_prepared", G_CALLBACK(pidgin_conv_custom_smiley_allocated), smiley);
-	g_signal_connect(smiley->loader, "closed", G_CALLBACK(pidgin_conv_custom_smiley_closed), smiley);
-
+	smiley = gtk_imhtml_smiley_create(NULL, smile, FALSE, GTK_IMHTML_SMILEY_CUSTOM);
 	gtk_imhtml_associate_smiley(imhtml, sml, smiley);
+	g_signal_connect_swapped(imhtml, "destroy", G_CALLBACK(gtk_imhtml_smiley_destroy), smiley);
 
 	return TRUE;
 }
@@ -6380,6 +6311,11 @@
 		if(conv->features & PURPLE_CONNECTION_NO_IMAGES)
 			buttons &= ~GTK_IMHTML_IMAGE;
 
+		if (conv->features & PURPLE_CONNECTION_ALLOW_CUSTOM_SMILEY)
+			buttons |= GTK_IMHTML_CUSTOM_SMILEY;
+		else
+			buttons &= ~GTK_IMHTML_CUSTOM_SMILEY;
+
 		gtk_imhtml_set_format_functions(GTK_IMHTML(gtkconv->entry), buttons);
 		if (account != NULL)
 			gtk_imhtmltoolbar_associate_smileys(GTK_IMHTMLTOOLBAR(gtkconv->toolbar), purple_account_get_protocol_id(account));
@@ -6827,7 +6763,7 @@
 	{
 		PurpleContact *contact = purple_buddy_get_contact(buddy);
 		if (contact) {
-			custom_img = purple_buddy_icons_find_custom_icon(contact);
+			custom_img = purple_buddy_icons_node_find_custom_icon((PurpleBlistNode*)contact);
 			if (custom_img) {
 				/* There is a custom icon for this user */
 				data = purple_imgstore_get_data(custom_img);
--- a/pidgin/gtkimhtml.c	Fri May 23 16:46:19 2008 +0000
+++ b/pidgin/gtkimhtml.c	Fri May 23 16:47:59 2008 +0000
@@ -32,10 +32,14 @@
 #include "internal.h"
 #include "pidgin.h"
 #include "pidginstock.h"
+#include "gtkutils.h"
+#include "smiley.h"
+#include "imgstore.h"
 
 #include "debug.h"
 #include "util.h"
 #include "gtkimhtml.h"
+#include "gtksmiley.h"
 #include "gtksourceiter.h"
 #include "gtksourceundomanager.h"
 #include "gtksourceview-marshal.h"
@@ -1570,9 +1574,11 @@
 	gtk_text_buffer_create_tag(imhtml->text_buffer, "SUP", "rise", 5000, NULL);
 	gtk_text_buffer_create_tag(imhtml->text_buffer, "PRE", "family", "Monospace", NULL);
 	gtk_text_buffer_create_tag(imhtml->text_buffer, "search", "background", "#22ff00", "weight", "bold", NULL);
+	gtk_text_buffer_create_tag(imhtml->text_buffer, "comment", "weight", PANGO_WEIGHT_NORMAL,
 #if FALSE && GTK_CHECK_VERSION(2,10,10)
-	gtk_text_buffer_create_tag(imhtml->text_buffer, "comment", "invisible", FALSE, NULL);
+			"invisible", FALSE,
 #endif
+			NULL);
 
 	gtk_text_buffer_create_tag(imhtml->text_buffer, "send-name", "weight", PANGO_WEIGHT_BOLD, NULL);
 	gtk_text_buffer_create_tag(imhtml->text_buffer, "receive-name", "weight", PANGO_WEIGHT_BOLD, NULL);
@@ -3120,7 +3126,7 @@
 #else
 					if (imhtml->show_comments && !(options & GTK_IMHTML_NO_COMMENTS)) {
 						wpos = g_snprintf (ws, len, "%s", tag);
-						gtk_text_buffer_insert(imhtml->text_buffer, iter, ws, wpos);
+						gtk_text_buffer_insert_with_tags_by_name(imhtml->text_buffer, iter, ws, wpos, "comment", NULL);
 					}
 #endif
 					ws[0] = '\0'; wpos = 0;
@@ -3687,6 +3693,15 @@
 	gtk_widget_show(image->filesel);
 }
 
+static void
+gtk_imhtml_custom_smiley_save(GtkWidget *w, GtkIMHtmlImage *image)
+{
+	/* Create an add dialog */
+	PidginSmiley *editor = pidgin_smiley_edit(NULL, NULL);
+	pidgin_smiley_editor_set_shortcut(editor, image->filename);
+	pidgin_smiley_editor_set_image(editor, image->pixbuf);
+}
+
 /*
  * So, um, AIM Direct IM lets you send any file, not just images.  You can
  * just insert a sound or a file or whatever in a conversation.  It's
@@ -3711,6 +3726,19 @@
 			g_signal_connect(G_OBJECT(item), "activate", G_CALLBACK(gtk_imhtml_image_save), image);
 			gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
 
+			/* Add menu item for adding custom smiley to local smileys */
+			/* we only add the menu if the image is of "custom smiley size"
+			  <= 96x96 pixels */
+			if (image->width <= 96 && image->height <= 96) {
+				text = g_strdup_printf(_("_Add Custom Smiley..."));
+				img = gtk_image_new_from_stock(GTK_STOCK_ADD, GTK_ICON_SIZE_MENU);
+				item = gtk_image_menu_item_new_with_mnemonic(text);
+				gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(item), img);
+				g_signal_connect(G_OBJECT(item), "activate",
+								 G_CALLBACK(gtk_imhtml_custom_smiley_save), image);
+				gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
+			}
+
 			gtk_widget_show_all(menu);
 			gtk_menu_popup(GTK_MENU(menu), NULL, NULL, NULL, NULL,
 							event_button->button, event_button->time);
@@ -3732,7 +3760,7 @@
 	GdkPixbufAnimation *anim = NULL;
 	GtkIMHtmlScalable *image = NULL;
 	gboolean ret;
-	
+
 	if (event->type != GDK_BUTTON_RELEASE || ((GdkEventButton*)event)->button != 3)
 		return FALSE;
 
@@ -4968,7 +4996,67 @@
 		g_snprintf(buf, sizeof(buf), "<font size=\"%s\">", &name[10]);
 		return buf;
 	} else {
-		return "";
+		char *str = buf;
+		gboolean isset;
+		int ivalue = 0;
+		GdkColor *color = NULL;
+		GObject *obj = G_OBJECT(tag);
+		gboolean empty = TRUE;
+
+		str += g_snprintf(str, sizeof(buf) - (str - buf), "<span style='");
+
+		/* Weight */
+		g_object_get(obj, "weight-set", &isset, "weight", &ivalue, NULL);
+		if (isset) {
+			const char *weight = "";
+			if (ivalue >= PANGO_WEIGHT_ULTRABOLD)
+				weight = "bolder";
+			else if (ivalue >= PANGO_WEIGHT_BOLD)
+				weight = "bold";
+			else if (ivalue >= PANGO_WEIGHT_NORMAL)
+				weight = "normal";
+			else
+				weight = "lighter";
+
+			str += g_snprintf(str, sizeof(buf) - (str - buf), "font-weight: %s;", weight);
+			empty = FALSE;
+		}
+
+		/* Foreground color */
+		g_object_get(obj, "foreground-set", &isset, "foreground-gdk", &color, NULL);
+		if (isset && color) {
+			str += g_snprintf(str, sizeof(buf) - (str - buf),
+					"color: #%02x%02x%02x;",
+					color->red >> 8, color->green >> 8, color->blue >> 8);
+			gdk_color_free(color);
+			empty = FALSE;
+		}
+
+		/* Background color */
+		g_object_get(obj, "background-set", &isset, "background-gdk", &color, NULL);
+		if (isset && color) {
+			str += g_snprintf(str, sizeof(buf) - (str - buf),
+					"background: #%02x%02x%02x;",
+					color->red >> 8, color->green >> 8, color->blue >> 8);
+			gdk_color_free(color);
+			empty = FALSE;
+		}
+
+		/* Underline */
+		g_object_get(obj, "underline-set", &isset, "underline", &ivalue, NULL);
+		if (isset) {
+			switch (ivalue) {
+				case PANGO_UNDERLINE_NONE:
+				case PANGO_UNDERLINE_ERROR:
+					break;
+				default:
+					str += g_snprintf(str, sizeof(buf) - (str - buf), "text-decoration: underline;");
+			}
+		}
+
+		g_snprintf(str, sizeof(buf) - (str - buf), "'>");
+
+		return (empty ? "" : buf);
 	}
 }
 
@@ -5000,6 +5088,16 @@
 	} else if (strncmp(name, "FONT SIZE ", 10) == 0) {
 		return "</font>";
 	} else {
+		const char *props[] = {"weight-set", "foreground-set", "background-set",
+			"size-set", "underline-set", NULL};
+		int i;
+		for (i = 0; props[i]; i++) {
+			gboolean set = FALSE;
+			g_object_get(G_OBJECT(tag), props[i], &set, NULL);
+			if (set)
+				return "</span>";
+		}
+
 		return "";
 	}
 }
@@ -5329,7 +5427,150 @@
 	if (flags & PURPLE_CONNECTION_NO_IMAGES)
 		buttons &= ~GTK_IMHTML_IMAGE;
 
+	if (flags & PURPLE_CONNECTION_ALLOW_CUSTOM_SMILEY)
+		buttons |= GTK_IMHTML_CUSTOM_SMILEY;
+	else
+		buttons &= ~GTK_IMHTML_CUSTOM_SMILEY;
+
 	gtk_imhtml_set_format_functions(imhtml, buttons);
 }
 
-
+/*******
+ * GtkIMHtmlSmiley functions
+ *******/
+static void gtk_custom_smiley_allocated(GdkPixbufLoader *loader, gpointer user_data)
+{
+	GtkIMHtmlSmiley *smiley;
+
+	smiley = (GtkIMHtmlSmiley *)user_data;
+	smiley->icon = gdk_pixbuf_loader_get_animation(loader);
+
+	if (smiley->icon)
+		g_object_ref(G_OBJECT(smiley->icon));
+#ifdef DEBUG_CUSTOM_SMILEY
+	purple_debug_info("custom-smiley", "gtk_custom_smiley_allocated(): got GdkPixbufAnimation %p for smiley '%s'\n", smiley->icon, smiley->smile);
+#endif
+}
+
+static void gtk_custom_smiley_closed(GdkPixbufLoader *loader, gpointer user_data)
+{
+	GtkIMHtmlSmiley *smiley;
+	GtkWidget *icon = NULL;
+	GtkTextChildAnchor *anchor = NULL;
+	GSList *current = NULL;
+
+	smiley = (GtkIMHtmlSmiley *)user_data;
+	if (!smiley->imhtml) {
+#ifdef DEBUG_CUSTOM_SMILEY
+		purple_debug_error("custom-smiley", "gtk_custom_smiley_closed(): orphan smiley found: %p\n", smiley);
+#endif
+		g_object_unref(G_OBJECT(loader));
+		smiley->loader = NULL;
+		return;
+	}
+
+	for (current = smiley->anchors; current; current = g_slist_next(current)) {
+
+		icon = gtk_image_new_from_animation(smiley->icon);
+
+#ifdef DEBUG_CUSTOM_SMILEY
+		purple_debug_info("custom-smiley", "gtk_custom_smiley_closed(): got GtkImage %p from GtkPixbufAnimation %p for smiley '%s'\n",
+				icon, smiley->icon, smiley->smile);
+#endif
+		if (icon) {
+			GList *wids;
+			gtk_widget_show(icon);
+
+			anchor = GTK_TEXT_CHILD_ANCHOR(current->data);
+			wids = gtk_text_child_anchor_get_widgets(anchor);
+
+			g_object_set_data_full(G_OBJECT(anchor), "gtkimhtml_plaintext", purple_unescape_html(smiley->smile), g_free);
+			g_object_set_data_full(G_OBJECT(anchor), "gtkimhtml_htmltext", g_strdup(smiley->smile), g_free);
+
+			if (smiley->imhtml) {
+				if (wids) {
+					GList *children = gtk_container_get_children(GTK_CONTAINER(wids->data));
+					g_list_foreach(children, (GFunc)gtk_widget_destroy, NULL);
+					g_list_free(children);
+					gtk_container_add(GTK_CONTAINER(wids->data), icon);
+				} else
+					gtk_text_view_add_child_at_anchor(GTK_TEXT_VIEW(smiley->imhtml), icon, anchor);
+			}
+			g_list_free(wids);
+		}
+
+	}
+
+	g_slist_free(smiley->anchors);
+	smiley->anchors = NULL;
+
+	g_object_unref(G_OBJECT(loader));
+	smiley->loader = NULL;
+}
+
+static void
+gtk_custom_smiley_size_prepared(GdkPixbufLoader *loader, gint width, gint height, gpointer data)
+{
+#define CUSTOM_SMILEY_SIZE 96	/* XXX: Should this be a theme setting? */
+	if (width <= CUSTOM_SMILEY_SIZE && height <= CUSTOM_SMILEY_SIZE)
+		return;
+
+	if (width >= height) {
+		height = height * CUSTOM_SMILEY_SIZE / width;
+		width = CUSTOM_SMILEY_SIZE;
+	} else {
+		width = width * CUSTOM_SMILEY_SIZE / height;
+		height = CUSTOM_SMILEY_SIZE;
+	}
+
+	gdk_pixbuf_loader_set_size(loader, width, height);
+}
+
+void
+gtk_imhtml_smiley_reload(GtkIMHtmlSmiley *smiley)
+{
+	if (smiley->icon)
+		g_object_unref(smiley->icon);
+	if (smiley->loader)
+		g_object_unref(smiley->loader);  /* XXX: does this crash? */
+
+	smiley->icon = NULL;
+	smiley->loader = NULL;
+
+	if (smiley->file) {
+		/* We do not use the pixbuf loader for a smiley that can be loaded
+		 * from a file. (e.g., local custom smileys)
+		 */
+		return;
+	}
+
+	smiley->loader = gdk_pixbuf_loader_new();
+
+	g_signal_connect(smiley->loader, "area_prepared", G_CALLBACK(gtk_custom_smiley_allocated), smiley);
+	g_signal_connect(smiley->loader, "closed", G_CALLBACK(gtk_custom_smiley_closed), smiley);
+	g_signal_connect(smiley->loader, "size_prepared", G_CALLBACK(gtk_custom_smiley_size_prepared), smiley);
+}
+
+GtkIMHtmlSmiley *gtk_imhtml_smiley_create(const char *file, const char *shortcut, gboolean hide,
+		GtkIMHtmlSmileyFlags flags)
+{
+	GtkIMHtmlSmiley *smiley = g_new0(GtkIMHtmlSmiley, 1);
+	smiley->file = g_strdup(file);
+	smiley->smile = g_strdup(shortcut);
+	smiley->hidden = hide;
+	smiley->flags = flags;
+	gtk_imhtml_smiley_reload(smiley);
+	return smiley;
+}
+
+void gtk_imhtml_smiley_destroy(GtkIMHtmlSmiley *smiley)
+{
+	g_free(smiley->smile);
+	g_free(smiley->file);
+	if (smiley->icon)
+		g_object_unref(smiley->icon);
+	if (smiley->loader)
+		g_object_unref(smiley->loader);
+	g_free(smiley);
+}
+
--- a/pidgin/gtkimhtml.h	Fri May 23 16:46:19 2008 +0000
+++ b/pidgin/gtkimhtml.h	Fri May 23 16:47:59 2008 +0000
@@ -76,6 +76,8 @@
 	GTK_IMHTML_SMILEY =     1 << 11,
 	GTK_IMHTML_LINKDESC =   1 << 12,
 	GTK_IMHTML_STRIKE =     1 << 13,
+	/** Show custom smileys when appropriate. @since 2.5.0 */
+	GTK_IMHTML_CUSTOM_SMILEY = 1 << 14,
 	GTK_IMHTML_ALL =       -1
 } GtkIMHtmlButtons;
 
@@ -852,6 +854,12 @@
  */
 void gtk_imhtml_setup_entry(GtkIMHtml *imhtml, PurpleConnectionFlags flags);
 
+GtkIMHtmlSmiley *gtk_imhtml_smiley_create(const char *file, const char *shortcut, gboolean hide,
+		GtkIMHtmlSmileyFlags flags);
+
+void gtk_imhtml_smiley_reload(GtkIMHtmlSmiley *smiley);
+
+void gtk_imhtml_smiley_destroy(GtkIMHtmlSmiley *smiley);
 /*@}*/
 
 #ifdef __cplusplus
--- a/pidgin/gtkimhtmltoolbar.c	Fri May 23 16:46:19 2008 +0000
+++ b/pidgin/gtkimhtmltoolbar.c	Fri May 23 16:47:59 2008 +0000
@@ -36,6 +36,7 @@
 
 #include "gtkdialogs.h"
 #include "gtkimhtmltoolbar.h"
+#include "gtksmiley.h"
 #include "gtkthemes.h"
 #include "gtkutils.h"
 
@@ -579,8 +580,7 @@
 }
 
 static gboolean
-close_smiley_dialog(GtkWidget *widget, GdkEvent *event,
-					GtkIMHtmlToolbar *toolbar)
+close_smiley_dialog(GtkIMHtmlToolbar *toolbar)
 {
 	gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(toolbar->smiley), FALSE);
 	return FALSE;
@@ -601,24 +601,30 @@
 
 	g_free(escaped_smiley);
 
-	close_smiley_dialog(NULL, NULL, toolbar);
+	close_smiley_dialog(toolbar);
 }
 
 /* smiley buttons list */
 struct smiley_button_list {
 	int width, height;
 	GtkWidget *button;
+	const GtkIMHtmlSmiley *smiley;
 	struct smiley_button_list *next;
 };
 
 static struct smiley_button_list *
-sort_smileys(struct smiley_button_list *ls, GtkIMHtmlToolbar *toolbar, int *width, char *filename, char *face)
+sort_smileys(struct smiley_button_list *ls, GtkIMHtmlToolbar *toolbar,
+			 int *width, const GtkIMHtmlSmiley *smiley,
+			 const GSList *custom_smileys)
 {
 	GtkWidget *image;
 	GtkWidget *button;
 	GtkRequisition size;
 	struct smiley_button_list *cur;
 	struct smiley_button_list *it, *it_last;
+	const gchar *filename = smiley->file;
+	gchar *face = smiley->smile;
+	PurpleSmiley *psmiley = NULL;
 
 	cur = g_new0(struct smiley_button_list, 1);
 	it = ls;
@@ -626,6 +632,35 @@
 	image = gtk_image_new_from_file(filename);
 
 	gtk_widget_size_request(image, &size);
+
+	if (size.width > 24 &&
+			smiley->flags & GTK_IMHTML_SMILEY_CUSTOM) { /* This is a custom smiley, let's scale it */
+		GdkPixbuf *pixbuf = NULL;
+		GtkImageType type;
+
+		type = gtk_image_get_storage_type(GTK_IMAGE(image));
+
+		if (type == GTK_IMAGE_PIXBUF) {
+			pixbuf = gtk_image_get_pixbuf(GTK_IMAGE(image));
+		} else if (type == GTK_IMAGE_ANIMATION) {
+			GdkPixbufAnimation *animation;
+
+			animation = gtk_image_get_animation(GTK_IMAGE(image));
+
+			pixbuf = gdk_pixbuf_animation_get_static_image(animation);
+		}
+
+		if (pixbuf != NULL) {
+			GdkPixbuf *resized;
+			resized = gdk_pixbuf_scale_simple(pixbuf, 24, 24,
+					GDK_INTERP_HYPER);
+
+			gtk_image_set_from_pixbuf(GTK_IMAGE(image), resized); /* This unrefs pixbuf */
+			gtk_widget_size_request(image, &size);
+			g_object_unref(G_OBJECT(resized));
+		}
+	}
+
 	(*width) += size.width;
 
 	button = gtk_button_new();
@@ -639,10 +674,26 @@
 	/* these look really weird with borders */
 	gtk_button_set_relief(GTK_BUTTON(button), GTK_RELIEF_NONE);
 
+	psmiley = purple_smileys_find_by_shortcut(smiley->smile);
+	/* If this is a "non-custom" smiley, check to see if its shortcut is
+	  "shadowed" by any custom smiley. This can only happen if the connection
+	  is custom smiley-enabled */
+	if (psmiley && !(smiley->flags & GTK_IMHTML_SMILEY_CUSTOM)) {
+		gtk_tooltips_set_tip(toolbar->tooltips, button,
+				_("This smiley is disabled because a custom smiley exists for this shortcut."),
+				NULL);
+		gtk_widget_set_sensitive(button, FALSE);
+	} else if (psmiley) {
+		/* Remove the button if the smiley is destroyed */
+		g_signal_connect_object(G_OBJECT(psmiley), "destroy", G_CALLBACK(gtk_widget_destroy),
+				button, G_CONNECT_SWAPPED);
+	}
+
 	/* set current element to add */
 	cur->height = size.height;
 	cur->width = size.width;
 	cur->button = button;
+	cur->smiley = smiley;
 	cur->next = ls;
 
 	/* check where to insert by height */
@@ -661,7 +712,7 @@
 smiley_is_unique(GSList *list, GtkIMHtmlSmiley *smiley)
 {
 	while (list) {
-		GtkIMHtmlSmiley *cur = list->data;
+		GtkIMHtmlSmiley *cur = (GtkIMHtmlSmiley *) list->data;
 		if (!strcmp(cur->file, smiley->file))
 			return FALSE;
 		list = list->next;
@@ -675,7 +726,7 @@
 	if ((event->type == GDK_KEY_PRESS && event->key.keyval == GDK_Escape) ||
 	    (event->type == GDK_BUTTON_PRESS && event->button.button == 1))
 	{
-		close_smiley_dialog(NULL, NULL, toolbar);
+		close_smiley_dialog(toolbar);
 		return TRUE;
 	}
 
@@ -683,11 +734,43 @@
 }
 
 static void
+add_smiley_list(GtkWidget *container, struct smiley_button_list *list,
+		int max_width, gboolean custom)
+{
+	GtkWidget *line;
+	int line_width = 0;
+
+	if (!list)
+		return;
+
+	line = gtk_hbox_new(FALSE, 0);
+	gtk_box_pack_start(GTK_BOX(container), line, FALSE, FALSE, 0);
+	for (; list; list = list->next) {
+		if (custom != !!(list->smiley->flags & GTK_IMHTML_SMILEY_CUSTOM))
+			continue;
+		gtk_box_pack_start(GTK_BOX(line), list->button, FALSE, FALSE, 0);
+		gtk_widget_show(list->button);
+		line_width += list->width;
+		if (line_width >= max_width) {
+			if (list->next) {
+				line = gtk_hbox_new(FALSE, 0);
+				gtk_box_pack_start(GTK_BOX(container), line, FALSE, FALSE, 0);
+			}
+			line_width = 0;
+		}
+	}
+}
+
+static void
 insert_smiley_cb(GtkWidget *smiley, GtkIMHtmlToolbar *toolbar)
 {
-	GtkWidget *dialog;
+	GtkWidget *dialog, *vbox;
 	GtkWidget *smiley_table = NULL;
 	GSList *smileys, *unique_smileys = NULL;
+	const GSList *custom_smileys = NULL;
+	gboolean supports_custom = FALSE;
+	GtkRequisition req;
+	GtkWidget *scrolled, *viewport;
 
 	if (!gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(smiley))) {
 		destroy_smiley_dialog(toolbar);
@@ -701,61 +784,74 @@
 		smileys = pidgin_themes_get_proto_smileys(NULL);
 
 	while(smileys) {
-		GtkIMHtmlSmiley *smiley = smileys->data;
+		GtkIMHtmlSmiley *smiley = (GtkIMHtmlSmiley *) smileys->data;
 		if(!smiley->hidden) {
-			if(smiley_is_unique(unique_smileys, smiley))
+			if(smiley_is_unique(unique_smileys, smiley)) {
 				unique_smileys = g_slist_append(unique_smileys, smiley);
+			}
 		}
 		smileys = smileys->next;
 	}
+	supports_custom = (gtk_imhtml_get_format_functions(GTK_IMHTML(toolbar->imhtml)) & GTK_IMHTML_CUSTOM_SMILEY);
+	if (toolbar->imhtml && supports_custom) {
+		const GSList *iterator = NULL;
+		custom_smileys = pidgin_smileys_get_all();
+
+		for (iterator = custom_smileys ; iterator ;
+			 iterator = g_slist_next(iterator)) {
+			GtkIMHtmlSmiley *smiley = (GtkIMHtmlSmiley *) iterator->data;
+			unique_smileys = g_slist_append(unique_smileys, smiley);
+		}
+	}
 
 	dialog = pidgin_create_dialog(_("Smile!"), 0, "smiley_dialog", FALSE);
-
 	gtk_window_set_position(GTK_WINDOW(dialog), GTK_WIN_POS_MOUSE);
+	vbox = pidgin_dialog_get_vbox_with_properties(GTK_DIALOG(dialog), FALSE, 0);
 
 	if (unique_smileys != NULL) {
-		struct smiley_button_list *ls, *it, *it_tmp;
-		GtkWidget *line;
-		int line_width = 0;
-		int max_line_width, num_lines;
-		int col=0;
+		struct smiley_button_list *ls;
+		int max_line_width, num_lines, button_width = 0;
 
 		/* We use hboxes packed in a vbox */
 		ls = NULL;
-		line = gtk_hbox_new(FALSE, 0);
-		line_width = 0;
 		max_line_width = 0;
 		num_lines = floor(sqrt(g_slist_length(unique_smileys)));
 		smiley_table = gtk_vbox_new(FALSE, 0);
 
+		if (supports_custom) {
+			GtkWidget *manage = gtk_button_new_with_mnemonic(_("_Manage custom smileys"));
+			GtkRequisition req;
+			g_signal_connect(G_OBJECT(manage), "clicked",
+					G_CALLBACK(pidgin_smiley_manager_show), NULL);
+			g_signal_connect_swapped(G_OBJECT(manage), "clicked",
+					G_CALLBACK(gtk_widget_destroy), dialog);
+			gtk_box_pack_end(GTK_BOX(vbox), manage, FALSE, TRUE, 0);
+			gtk_widget_size_request(manage, &req);
+			button_width = req.width;
+		}
+
 		/* create list of smileys sorted by height */
 		while (unique_smileys) {
-			GtkIMHtmlSmiley *smiley = unique_smileys->data;
+			GtkIMHtmlSmiley *smiley = (GtkIMHtmlSmiley *) unique_smileys->data;
 			if (!smiley->hidden) {
-				ls = sort_smileys(ls, toolbar, &max_line_width, smiley->file, smiley->smile);
+				ls = sort_smileys(ls, toolbar, &max_line_width, smiley, custom_smileys);
 			}
 			unique_smileys = g_slist_delete_link(unique_smileys, unique_smileys);
 		}
+		/* The window will be at least as wide as the 'Manage ..' button */
+		max_line_width = MAX(button_width, max_line_width / num_lines);
+
 		/* pack buttons of the list */
-		max_line_width = max_line_width / num_lines;
-		it = ls;
-		while (it != NULL)
-		{
-			it_tmp = it;
-			gtk_box_pack_start(GTK_BOX(line), it->button, FALSE, FALSE, 0);
-			gtk_widget_show(it->button);
-			line_width += it->width;
-			if (line_width >= max_line_width) {
-				gtk_box_pack_start(GTK_BOX(smiley_table), line, FALSE, FALSE, 0);
-				line = gtk_hbox_new(FALSE, 0);
-				line_width = 0;
-				col = 0;
-			}
-			col++;
-			it = it->next;
-			g_free(it_tmp);
+		add_smiley_list(smiley_table, ls, max_line_width, FALSE);
+		if (supports_custom) {
+			gtk_box_pack_start(GTK_BOX(smiley_table), gtk_hseparator_new(), TRUE, FALSE, 0);
+			add_smiley_list(smiley_table, ls, max_line_width, TRUE);
 		}
-		gtk_box_pack_start(GTK_BOX(smiley_table), line, FALSE, TRUE, 0);
+		while (ls) {
+			struct smiley_button_list *tmp = ls->next;
+			g_free(ls);
+			ls = tmp;
+		}
 
 		gtk_widget_add_events(dialog, GDK_KEY_PRESS_MASK);
 	}
@@ -765,19 +861,41 @@
 		g_signal_connect(G_OBJECT(dialog), "button-press-event", (GCallback)smiley_dialog_input_cb, toolbar);
 	}
 
-	g_signal_connect(G_OBJECT(dialog), "key-press-event", (GCallback)smiley_dialog_input_cb, toolbar);
-	gtk_container_add(GTK_CONTAINER(pidgin_dialog_get_vbox(GTK_DIALOG(dialog))), smiley_table);
+	scrolled = gtk_scrolled_window_new (NULL, NULL);
+	gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW (scrolled), GTK_SHADOW_NONE);
+	gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW (scrolled),
+			GTK_POLICY_NEVER, GTK_POLICY_NEVER);
+	gtk_box_pack_start(GTK_BOX(vbox), scrolled, TRUE, TRUE, 0);
+	gtk_widget_show(scrolled);
 
+	gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(scrolled), smiley_table);
 	gtk_widget_show(smiley_table);
 
+	viewport = gtk_widget_get_parent(smiley_table);
+	gtk_viewport_set_shadow_type(GTK_VIEWPORT(viewport), GTK_SHADOW_NONE);
+
 	/* connect signals */
-	g_signal_connect(G_OBJECT(dialog), "delete_event",
-					 G_CALLBACK(close_smiley_dialog), toolbar);
+	g_signal_connect_swapped(G_OBJECT(dialog), "destroy", G_CALLBACK(close_smiley_dialog), toolbar);
+	g_signal_connect(G_OBJECT(dialog), "key-press-event", G_CALLBACK(smiley_dialog_input_cb), toolbar);
+
+	gtk_window_set_transient_for(GTK_WINDOW(dialog),
+			GTK_WINDOW(gtk_widget_get_toplevel(GTK_WIDGET(toolbar))));
 
 	/* show everything */
 	gtk_widget_show_all(dialog);
-	gtk_window_set_transient_for(GTK_WINDOW(dialog),
-			GTK_WINDOW(gtk_widget_get_toplevel(GTK_WIDGET(toolbar))));
+
+	gtk_widget_size_request(viewport, &req);
+	gtk_widget_set_size_request(scrolled, MIN(300, req.width), MIN(290, req.height));
+
+	/* The window has to be made resizable, and the scrollbars in the scrolled window
+	 * enabled only after setting the desired size of the window. If we do either of
+	 * these tasks before now, GTK+ miscalculates the required size, and erronously
+	 * makes one or both scrollbars visible (sometimes).
+	 * I too think this hack is gross. But I couldn't find a better way -- sadrul */
+	gtk_window_set_resizable(GTK_WINDOW(dialog), TRUE);
+	gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW (scrolled),
+			GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
+
 #ifdef _WIN32
 	winpidgin_ensure_onscreen(dialog);
 #endif
--- a/pidgin/gtkmain.c	Fri May 23 16:46:19 2008 +0000
+++ b/pidgin/gtkmain.c	Fri May 23 16:47:59 2008 +0000
@@ -62,6 +62,7 @@
 #include "gtkroomlist.h"
 #include "gtksavedstatuses.h"
 #include "gtksession.h"
+#include "gtksmiley.h"
 #include "gtksound.h"
 #include "gtkthemes.h"
 #include "gtkutils.h"
@@ -315,6 +316,7 @@
 	pidgin_roomlist_init();
 	pidgin_log_init();
 	pidgin_docklet_init();
+	pidgin_smileys_init();
 }
 
 static GHashTable *ui_info = NULL;
@@ -331,6 +333,7 @@
 	pidgin_plugins_save();
 
 	/* Uninit */
+	pidgin_smileys_uninit();
 	pidgin_conversations_uninit();
 	pidgin_status_uninit();
 	pidgin_docklet_uninit();
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/pidgin/gtksmiley.c	Fri May 23 16:47:59 2008 +0000
@@ -0,0 +1,705 @@
+/**
+ * @file gtksmiley.c GTK+ Smiley Manager API
+ * @ingroup pidgin
+ */
+
+/*
+ * pidgin
+ *
+ * Pidgin is the legal property of its developers, whose names are too numerous
+ * to list here.  Please refer to the COPYRIGHT file distributed with this
+ * source distribution.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
+ */
+
+#include "internal.h"
+#include "pidgin.h"
+
+#include "debug.h"
+#include "notify.h"
+#include "smiley.h"
+
+#include "gtkimhtml.h"
+#include "gtksmiley.h"
+#include "gtkutils.h"
+#include "pidginstock.h"
+
+#define PIDGIN_RESPONSE_EDIT 1000
+
+struct _PidginSmiley
+{
+	PurpleSmiley *smiley;
+	GtkWidget *parent;
+	GtkWidget *smile;
+	GtkWidget *smiley_image;
+	gchar *filename;
+	GdkPixbuf *custom_pixbuf;
+};
+
+typedef struct
+{
+	GtkWidget *window;
+
+	GtkWidget *treeview;
+	GtkListStore *model;
+} SmileyManager;
+
+enum
+{
+	ICON,
+	SHORTCUT,
+	SMILEY,
+	N_COL
+};
+
+static SmileyManager *smiley_manager = NULL;
+static GSList *gtk_smileys = NULL;
+
+static void
+pidgin_smiley_destroy(PidginSmiley *smiley)
+{
+	gtk_widget_destroy(smiley->parent);
+	g_free(smiley->filename);
+	if (smiley->custom_pixbuf)
+		gdk_pixbuf_unref(smiley->custom_pixbuf);
+	g_free(smiley);
+}
+
+/******************************************************************************
+ * GtkIMHtmlSmileys stuff
+ *****************************************************************************/
+/* Perhaps these should be in gtkimhtml.c instead. -- sadrul */
+static void add_gtkimhtml_to_list(GtkIMHtmlSmiley *gtksmiley)
+{
+	gtk_smileys = g_slist_prepend(gtk_smileys, gtksmiley);
+
+	purple_debug_info("gtksmiley", "adding %s to gtk_smileys\n", gtksmiley->smile);
+}
+
+static void
+shortcut_changed_cb(PurpleSmiley *smiley, gpointer dontcare, GtkIMHtmlSmiley *gtksmiley)
+{
+	g_free(gtksmiley->smile);
+	gtksmiley->smile = g_strdup(purple_smiley_get_shortcut(smiley));
+}
+
+static GtkIMHtmlSmiley *smiley_purple_to_gtkimhtml(PurpleSmiley *smiley)
+{
+	GtkIMHtmlSmiley *gtksmiley;
+	gchar *filename;
+	const gchar *file;
+
+	file = purple_imgstore_get_filename(purple_smiley_get_stored_image(smiley));
+
+	filename = g_build_filename(purple_smileys_get_storing_dir(), file, NULL);
+
+	gtksmiley = gtk_imhtml_smiley_create(filename, purple_smiley_get_shortcut(smiley),
+			FALSE, GTK_IMHTML_SMILEY_CUSTOM);
+	g_free(filename);
+
+	/* Make sure the shortcut for the GtkIMHtmlSmiley is updated with the PurpleSmiley */
+	g_signal_connect(G_OBJECT(smiley), "notify::shortcut",
+			G_CALLBACK(shortcut_changed_cb), gtksmiley);
+
+	return gtksmiley;
+}
+
+void pidgin_smiley_del_from_list(PurpleSmiley *smiley)
+{
+	GSList *list = NULL;
+	GtkIMHtmlSmiley *gtksmiley;
+
+	if (gtk_smileys == NULL)
+		return;
+
+	list = gtk_smileys;
+
+	for (; list; list = list->next) {
+		gtksmiley = (GtkIMHtmlSmiley*)list->data;
+
+		if (strcmp(gtksmiley->smile, purple_smiley_get_shortcut(smiley)))
+			continue;
+
+		gtk_imhtml_smiley_destroy(gtksmiley);
+		g_signal_handlers_disconnect_matched(G_OBJECT(smiley), G_SIGNAL_MATCH_DATA,
+				0, 0, NULL, NULL, gtksmiley);
+		break;
+	}
+
+	if (list)
+		gtk_smileys = g_slist_delete_link(gtk_smileys, list);
+}
+
+void pidgin_smiley_add_to_list(PurpleSmiley *smiley)
+{
+	GtkIMHtmlSmiley *gtksmiley;
+
+	gtksmiley = smiley_purple_to_gtkimhtml(smiley);
+	add_gtkimhtml_to_list(gtksmiley);
+	g_signal_connect(G_OBJECT(smiley), "destroy", G_CALLBACK(pidgin_smiley_del_from_list), NULL);
+}
+
+void pidgin_smileys_init(void)
+{
+	GList *smileys;
+	PurpleSmiley *smiley;
+
+	if (gtk_smileys != NULL)
+		return;
+
+	smileys = purple_smileys_get_all();
+
+	for (; smileys; smileys = g_list_delete_link(smileys, smileys)) {
+		smiley = (PurpleSmiley*)smileys->data;
+
+		pidgin_smiley_add_to_list(smiley);
+	}
+}
+
+void pidgin_smileys_uninit(void)
+{
+	GSList *list;
+	GtkIMHtmlSmiley *gtksmiley;
+
+	list = gtk_smileys;
+
+	if (list == NULL)
+		return;
+
+	for (; list; list = g_slist_delete_link(list, list)) {
+		gtksmiley = (GtkIMHtmlSmiley*)list->data;
+		gtk_imhtml_smiley_destroy(gtksmiley);
+	}
+
+	gtk_smileys = NULL;
+}
+
+GSList *pidgin_smileys_get_all(void)
+{
+	return gtk_smileys;
+}
+
+/******************************************************************************
+ * Manager stuff
+ *****************************************************************************/
+
+static void refresh_list(void);
+
+/******************************************************************************
+ * The Add dialog
+ ******************************************************************************/
+
+static void do_add(GtkWidget *widget, PidginSmiley *s)
+{
+	const gchar *entry;
+	PurpleSmiley *emoticon;
+
+	entry = gtk_entry_get_text(GTK_ENTRY(s->smile));
+	emoticon = purple_smileys_find_by_shortcut(entry);
+	if (emoticon && emoticon != s->smiley) {
+		purple_notify_error(s->parent, _("Custom Smiley"),
+				_("Duplicate Shortcut"),
+				_("A custom smiley for the selected shortcut already exists. Please specify a different shortcut."));
+		return;
+	}
+
+	if (s->smiley) {
+		if (s->filename) {
+			gchar *data = NULL;
+			size_t len;
+			GError *err = NULL;
+
+			if (!g_file_get_contents(s->filename, &data, &len, &err)) {
+				purple_debug_error("gtksmiley", "Error reading %s: %s\n",
+						s->filename, err->message);
+				g_error_free(err);
+
+				return;
+			}
+			purple_smiley_set_data(s->smiley, (guchar*)data, len, FALSE);
+		}
+		purple_smiley_set_shortcut(s->smiley, entry);
+	} else {
+		if ((s->filename == NULL && s->custom_pixbuf == NULL)
+				|| *entry == 0) {
+			purple_notify_error(s->parent, _("Custom Smiley"),
+					_("More Data needed"),
+					s->filename ? _("Please provide a shortcut to associate with the smiley.")
+					: _("Please select an image for the smiley."));
+			return;
+		}
+
+		purple_debug_info("gtksmiley", "adding a new smiley\n");
+
+		if (s->filename == NULL) {
+			/* Get the smiley from the custom pixbuf */
+			gchar *buffer = NULL;
+			gsize size = 0;
+			gchar *filename;
+
+			gdk_pixbuf_save_to_bufferv(s->custom_pixbuf, &buffer, &size,
+									   "png", NULL, NULL, NULL);
+			filename = purple_util_get_image_filename(buffer, size);
+			s->filename = g_build_filename(purple_smileys_get_storing_dir(), filename, NULL);
+			purple_util_write_data_to_file_absolute(s->filename, buffer, size);
+			g_free(filename);
+			g_free(buffer);
+		}
+		emoticon = purple_smiley_new_from_file(entry, s->filename);
+		pidgin_smiley_add_to_list(emoticon);
+	}
+
+	if (smiley_manager != NULL)
+		refresh_list();
+
+	gtk_widget_destroy(s->parent);
+}
+
+static void do_add_select_cb(GtkWidget *widget, gint resp, PidginSmiley *s)
+{
+	switch (resp) {
+		case GTK_RESPONSE_ACCEPT:
+			do_add(widget, s);
+			break;
+		case GTK_RESPONSE_DELETE_EVENT:
+		case GTK_RESPONSE_CANCEL:
+			gtk_widget_destroy(s->parent);
+			break;
+		default:
+			purple_debug_error("gtksmiley", "no valid response\n");
+			break;
+	}
+}
+
+static void do_add_file_cb(const char *filename, gpointer data)
+{
+	PidginSmiley *s = data;
+	GdkPixbuf *pixbuf;
+
+	if (!filename)
+		return;
+
+	g_free(s->filename);
+	s->filename = g_strdup(filename);
+	pixbuf = gdk_pixbuf_new_from_file_at_scale(filename, 64, 64, FALSE, NULL);
+	gtk_image_set_from_pixbuf(GTK_IMAGE(s->smiley_image), pixbuf);
+	if (pixbuf)
+		gdk_pixbuf_unref(pixbuf);
+	gtk_widget_grab_focus(s->smile);
+}
+
+static void
+open_image_selector(GtkWidget *widget, PidginSmiley *psmiley)
+{
+	GtkWidget *file_chooser;
+	file_chooser = pidgin_buddy_icon_chooser_new(GTK_WINDOW(gtk_widget_get_toplevel(widget)),
+			do_add_file_cb, psmiley);
+	gtk_window_set_title(GTK_WINDOW(file_chooser), _("Custom Smiley"));
+	gtk_window_set_role(GTK_WINDOW(file_chooser), "file-selector-custom-smiley");
+	gtk_widget_show_all(file_chooser);
+}
+
+PidginSmiley *
+pidgin_smiley_edit(GtkWidget *widget, PurpleSmiley *smiley)
+{
+	GtkWidget *vbox;
+	GtkWidget *hbox;
+	GtkWidget *label;
+	GtkWidget *filech;
+	GtkWidget *window;
+	GdkPixbuf *pixbuf = NULL;
+	PurpleStoredImage *stored_img;
+
+	PidginSmiley *s = g_new0(PidginSmiley, 1);
+	s->smiley = smiley;
+
+	window = gtk_dialog_new_with_buttons(smiley ? _("Edit Smiley") : _("Add Smiley"),
+			widget ? GTK_WINDOW(widget) : NULL,
+			GTK_DIALOG_DESTROY_WITH_PARENT | GTK_DIALOG_NO_SEPARATOR,
+			smiley ? GTK_STOCK_SAVE : GTK_STOCK_ADD, GTK_RESPONSE_ACCEPT,
+			GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
+			NULL);
+	s->parent = window;
+
+	gtk_container_set_border_width(GTK_CONTAINER(window), PIDGIN_HIG_BORDER);
+
+	g_signal_connect(window, "response", G_CALLBACK(do_add_select_cb), s);
+
+	/* The vbox */
+	vbox = gtk_vbox_new(FALSE, PIDGIN_HIG_BORDER);
+	gtk_container_add(GTK_CONTAINER(GTK_DIALOG(window)->vbox), vbox);
+	gtk_widget_show(vbox);
+
+	/* The hbox */
+	hbox = gtk_hbox_new(FALSE, PIDGIN_HIG_BORDER);
+	gtk_container_add(GTK_CONTAINER(GTK_VBOX(vbox)), hbox);
+
+	label = gtk_label_new_with_mnemonic(_("Smiley _Image"));
+	gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 0);
+	gtk_widget_show(label);
+
+	filech = gtk_button_new();
+	gtk_box_pack_end(GTK_BOX(hbox), filech, FALSE, FALSE, 0);
+	pidgin_set_accessible_label(filech, label);
+
+	s->smiley_image = gtk_image_new();
+	gtk_container_add(GTK_CONTAINER(filech), s->smiley_image);
+	if (smiley && (stored_img = purple_smiley_get_stored_image(smiley))) {
+		pixbuf = pidgin_pixbuf_from_imgstore(stored_img);
+		purple_imgstore_unref(stored_img);
+	} else {
+		GtkIconSize icon_size = gtk_icon_size_from_name(PIDGIN_ICON_SIZE_TANGO_SMALL);
+		pixbuf = gtk_widget_render_icon(window, PIDGIN_STOCK_TOOLBAR_SELECT_AVATAR,
+				icon_size, "PidginSmiley");
+	}
+
+	gtk_image_set_from_pixbuf(GTK_IMAGE(s->smiley_image), pixbuf);
+	if (pixbuf != NULL)
+		g_object_unref(G_OBJECT(pixbuf));
+	g_signal_connect(G_OBJECT(filech), "clicked", G_CALLBACK(open_image_selector), s);
+
+	gtk_widget_show_all(hbox);
+
+	/* info */
+	hbox = gtk_hbox_new(FALSE, PIDGIN_HIG_BORDER);
+	gtk_container_add(GTK_CONTAINER(GTK_VBOX(vbox)),hbox);
+
+	/* Smiley shortcut */
+	label = gtk_label_new_with_mnemonic(_("Smiley S_hortcut"));
+	gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 0);
+	gtk_widget_show(label);
+
+	s->smile = gtk_entry_new();
+	gtk_entry_set_activates_default(GTK_ENTRY(s->smile), TRUE);
+	pidgin_set_accessible_label(s->smile, label);
+	if (smiley)
+		gtk_entry_set_text(GTK_ENTRY(s->smile), purple_smiley_get_shortcut(smiley));
+
+	g_signal_connect(s->smile, "activate", G_CALLBACK(do_add), s);
+
+	gtk_box_pack_end(GTK_BOX(hbox), s->smile, FALSE, FALSE, 0);
+	gtk_widget_show(s->smile);
+
+	gtk_widget_show(hbox);
+
+	gtk_widget_show(GTK_WIDGET(window));
+	g_signal_connect_swapped(G_OBJECT(window), "destroy", G_CALLBACK(pidgin_smiley_destroy), s);
+	g_signal_connect(G_OBJECT(window), "destroy", G_CALLBACK(purple_notify_close_with_handle), s);
+
+	return s;
+}
+
+void
+pidgin_smiley_editor_set_shortcut(PidginSmiley *editor, const gchar *shortcut)
+{
+	gtk_entry_set_text(GTK_ENTRY(editor->smile), shortcut ? shortcut : "");
+}
+
+void
+pidgin_smiley_editor_set_image(PidginSmiley *editor, GdkPixbuf *image)
+{
+	if (editor->custom_pixbuf)
+		gdk_pixbuf_unref(editor->custom_pixbuf);
+	editor->custom_pixbuf = image ? gdk_pixbuf_ref(image) : NULL;
+	if (image)
+		gtk_image_set_from_pixbuf(GTK_IMAGE(editor->smiley_image), image);
+}
+
+/******************************************************************************
+ * Delete smiley
+ *****************************************************************************/
+static void delete_foreach(GtkTreeModel *model, GtkTreePath *path,
+		GtkTreeIter *iter, gpointer data)
+{
+	PurpleSmiley *smiley = NULL;
+	SmileyManager *dialog;
+
+	dialog = (SmileyManager*)data;
+
+	gtk_tree_model_get(model, iter,
+			SMILEY, &smiley,
+			-1);
+
+	if(smiley != NULL) {
+		g_object_unref(G_OBJECT(smiley));
+		pidgin_smiley_del_from_list(smiley);
+		purple_smiley_delete(smiley);
+	}
+}
+
+static void append_to_list(GtkTreeModel *model, GtkTreePath *path,
+		GtkTreeIter *iter, gpointer data)
+{
+	GList **list = data;
+	*list = g_list_prepend(*list, gtk_tree_path_copy(path));
+}
+
+static void smiley_delete(SmileyManager *dialog)
+{
+	GtkTreeSelection *selection;
+	GList *list = NULL;
+
+	selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(dialog->treeview));
+	gtk_tree_selection_selected_foreach(selection, delete_foreach, dialog);
+	gtk_tree_selection_selected_foreach(selection, append_to_list, &list);
+
+	while (list) {
+		GtkTreeIter iter;
+		if (gtk_tree_model_get_iter(GTK_TREE_MODEL(dialog->model), &iter, list->data))
+			gtk_list_store_remove(GTK_LIST_STORE(dialog->model), &iter);
+		gtk_tree_path_free(list->data);
+		list = g_list_delete_link(list, list);
+	}
+}
+/******************************************************************************
+ * The Smiley Manager
+ *****************************************************************************/
+static void add_columns(GtkWidget *treeview, SmileyManager *dialog)
+{
+	GtkCellRenderer *rend;
+	GtkTreeViewColumn *column;
+
+	/* Icon */
+	column = gtk_tree_view_column_new();
+	gtk_tree_view_column_set_title(column, _("Smiley"));
+	gtk_tree_view_column_set_resizable(column, TRUE);
+	gtk_tree_view_append_column(GTK_TREE_VIEW(treeview), column);
+
+	rend = gtk_cell_renderer_pixbuf_new();
+	gtk_tree_view_column_pack_start(column, rend, FALSE);
+	gtk_tree_view_column_add_attribute(column, rend, "pixbuf", ICON);
+
+	/* Shortcut */
+	column = gtk_tree_view_column_new();
+	gtk_tree_view_column_set_title(column, _("Shortcut"));
+	gtk_tree_view_column_set_resizable(column, TRUE);
+	gtk_tree_view_append_column(GTK_TREE_VIEW(treeview), column);
+
+	rend = gtk_cell_renderer_text_new();
+	gtk_tree_view_column_pack_start(column, rend, TRUE);
+	gtk_tree_view_column_add_attribute(column, rend, "text", SHORTCUT);
+}
+
+static void store_smiley_add(PurpleSmiley *smiley)
+{
+	GtkTreeIter iter;
+	PurpleStoredImage *img;
+	GdkPixbuf *sized_smiley = NULL;
+
+	if (smiley_manager == NULL)
+		return;
+
+	img = purple_smiley_get_stored_image(smiley);
+
+	if (img != NULL) {
+		GdkPixbuf *smiley_image = pidgin_pixbuf_from_imgstore(img);
+		purple_imgstore_unref(img);
+
+		if (smiley_image != NULL)
+			sized_smiley = gdk_pixbuf_scale_simple(smiley_image,
+					22, 22, GDK_INTERP_HYPER);
+		g_object_unref(G_OBJECT(smiley_image));
+	}
+
+
+	gtk_list_store_append(smiley_manager->model, &iter);
+
+	gtk_list_store_set(smiley_manager->model, &iter,
+			ICON, sized_smiley,
+			SHORTCUT, purple_smiley_get_shortcut(smiley),
+			SMILEY, smiley,
+			-1);
+
+	if (sized_smiley != NULL)
+		g_object_unref(G_OBJECT(sized_smiley));
+}
+
+static void populate_smiley_list(SmileyManager *dialog)
+{
+	GList *list;
+	PurpleSmiley *emoticon;
+
+	gtk_list_store_clear(dialog->model);
+
+	for(list = purple_smileys_get_all(); list != NULL;
+			list = g_list_delete_link(list, list)) {
+		emoticon = (PurpleSmiley*)list->data;
+
+		store_smiley_add(emoticon);
+	}
+}
+
+static void smile_selected_cb(GtkTreeSelection *sel, SmileyManager *dialog)
+{
+	gint selected;
+
+	selected = gtk_tree_selection_count_selected_rows(sel);
+
+	gtk_dialog_set_response_sensitive(GTK_DIALOG(dialog->window),
+			GTK_RESPONSE_NO, selected > 0);
+
+	gtk_dialog_set_response_sensitive(GTK_DIALOG(dialog->window),
+			PIDGIN_RESPONSE_EDIT, selected > 0);
+}
+
+static void
+smiley_edit_iter(SmileyManager *dialog, GtkTreeIter *iter)
+{
+	PurpleSmiley *smiley = NULL;
+	gtk_tree_model_get(GTK_TREE_MODEL(dialog->model), iter, SMILEY, &smiley, -1);
+	pidgin_smiley_edit(gtk_widget_get_toplevel(GTK_WIDGET(dialog->treeview)), smiley);
+	g_object_unref(G_OBJECT(smiley));
+}
+
+static void smiley_edit_cb(GtkTreeView *treeview, GtkTreePath *path, GtkTreeViewColumn *col, gpointer data)
+{
+	GtkTreeIter iter;
+	SmileyManager *dialog = data;
+
+	gtk_tree_model_get_iter(GTK_TREE_MODEL(dialog->model), &iter, path);
+	smiley_edit_iter(dialog, &iter);
+}
+
+static void
+edit_selected_cb(GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter, gpointer data)
+{
+	smiley_edit_iter(data, iter);
+}
+
+static GtkWidget *smiley_list_create(SmileyManager *dialog)
+{
+	GtkWidget *sw;
+	GtkWidget *treeview;
+	GtkTreeSelection *sel;
+
+	sw = gtk_scrolled_window_new(NULL, NULL);
+	gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(sw),
+			GTK_POLICY_AUTOMATIC,
+			GTK_POLICY_AUTOMATIC);
+	gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(sw),
+			GTK_SHADOW_IN);
+	gtk_widget_show(sw);
+
+	/* Create the list model */
+	dialog->model = gtk_list_store_new(N_COL,
+			GDK_TYPE_PIXBUF,	/* ICON */
+			G_TYPE_STRING,		/* SHORTCUT */
+			G_TYPE_OBJECT		/* SMILEY */
+			);
+
+	/* the actual treeview */
+	treeview = gtk_tree_view_new_with_model(GTK_TREE_MODEL(dialog->model));
+	dialog->treeview = treeview;
+	gtk_tree_view_set_rules_hint(GTK_TREE_VIEW(treeview), TRUE);
+	gtk_tree_sortable_set_sort_column_id(GTK_TREE_SORTABLE(dialog->model), SHORTCUT, GTK_SORT_ASCENDING);
+	g_object_unref(G_OBJECT(dialog->model));
+
+	sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(treeview));
+	gtk_tree_selection_set_mode(sel, GTK_SELECTION_MULTIPLE);
+	gtk_container_add(GTK_CONTAINER(sw), treeview);
+
+	g_signal_connect(G_OBJECT(sel), "changed", G_CALLBACK(smile_selected_cb), dialog);
+	g_signal_connect(G_OBJECT(treeview), "row_activated", G_CALLBACK(smiley_edit_cb), dialog);
+
+	gtk_widget_show(treeview);
+
+	add_columns(treeview, dialog);
+	populate_smiley_list(dialog);
+
+	return sw;
+}
+
+static void refresh_list()
+{
+	populate_smiley_list(smiley_manager);
+}
+
+static void smiley_manager_select_cb(GtkWidget *widget, gint resp, SmileyManager *dialog)
+{
+	GtkTreeSelection *selection = NULL;
+
+	switch (resp) {
+		case GTK_RESPONSE_YES:
+			pidgin_smiley_edit(dialog->window, NULL);
+			break;
+		case GTK_RESPONSE_NO:
+			smiley_delete(dialog);
+			break;
+		case GTK_RESPONSE_DELETE_EVENT:
+		case GTK_RESPONSE_CLOSE:
+			gtk_widget_destroy(dialog->window);
+			g_free(smiley_manager);
+			smiley_manager = NULL;
+			break;
+		case PIDGIN_RESPONSE_EDIT:
+			/* Find smiley of selection... */
+			selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(dialog->treeview));
+			gtk_tree_selection_selected_foreach(selection, edit_selected_cb, dialog);
+			break;
+		default:
+			purple_debug_info("gtksmiley", "No valid selection\n");
+			break;
+	}
+}
+
+void pidgin_smiley_manager_show(void)
+{
+	SmileyManager *dialog;
+	GtkWidget *win;
+	GtkWidget *sw;
+	GtkWidget *vbox;
+
+	if (smiley_manager) {
+		gtk_window_present(GTK_WINDOW(smiley_manager->window));
+		return;
+	}
+
+	dialog = g_new0(SmileyManager, 1);
+	smiley_manager = dialog;
+
+	dialog->window = win = gtk_dialog_new_with_buttons(
+			_("Custom Smiley Manager"),
+			NULL,
+			GTK_DIALOG_DESTROY_WITH_PARENT,
+			GTK_STOCK_ADD, GTK_RESPONSE_YES,
+			GTK_STOCK_EDIT, PIDGIN_RESPONSE_EDIT,
+			GTK_STOCK_DELETE, GTK_RESPONSE_NO,
+			GTK_STOCK_CLOSE, GTK_RESPONSE_CLOSE,
+			NULL);
+
+	gtk_window_set_default_size(GTK_WINDOW(win), 50, 400);
+	gtk_window_set_role(GTK_WINDOW(win), "custom_smiley_manager");
+	gtk_container_set_border_width(GTK_CONTAINER(win),PIDGIN_HIG_BORDER);
+	gtk_dialog_set_response_sensitive(GTK_DIALOG(win), GTK_RESPONSE_NO, FALSE);
+	gtk_dialog_set_response_sensitive(GTK_DIALOG(win), PIDGIN_RESPONSE_EDIT,
+									  FALSE);
+
+	g_signal_connect(win, "response", G_CALLBACK(smiley_manager_select_cb),
+			dialog);
+
+	/* The vbox */
+	vbox = gtk_vbox_new(FALSE, PIDGIN_HIG_BORDER);
+	gtk_container_add(GTK_CONTAINER(GTK_DIALOG(win)->vbox), vbox);
+	gtk_widget_show(vbox);
+
+	/* get the scrolled window with all stuff */
+	sw = smiley_list_create(dialog);
+	gtk_box_pack_start(GTK_BOX(vbox), sw, TRUE, TRUE, 0);
+	gtk_widget_show(sw);
+
+	gtk_widget_show(win);
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/pidgin/gtksmiley.h	Fri May 23 16:47:59 2008 +0000
@@ -0,0 +1,103 @@
+/**
+ * @file gtksmiley.h GTK+ Custom Smiley API
+ * @ingroup pidgin
+ * @since 2.5.0
+ */
+
+/* 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
+ */
+
+#ifndef _PIDGIN_GTKSMILEY_H_
+#define _PIDGIN_GTKSMILEY_H_
+
+#include "smiley.h"
+
+typedef struct _PidginSmiley PidginSmiley;
+
+/**
+ * Add a PurpleSmiley to the GtkIMHtmlSmiley's list to be able to use it
+ * in pidgin
+ *
+ * @param smiley	The smiley to be added.
+ */
+void pidgin_smiley_add_to_list(PurpleSmiley *smiley);
+
+/**
+ * Delete a PurpleSmiley from the GtkIMHtmlSmiley's list
+ *
+ * @param smiley	The smiley to be deleted.
+ */
+void pidgin_smiley_del_from_list(PurpleSmiley *smiley);
+
+/**
+ * Load the GtkIMHtml list
+ */
+void pidgin_smileys_init(void);
+
+/**
+ * Uninit the GtkIMHtml list
+ */
+void pidgin_smileys_uninit(void);
+
+/**
+ * Returns a GSList with the GtkIMHtmlSmiley of each custom smiley
+ *
+ * @constreturn A GtkIMHmlSmiley list
+ */
+GSList* pidgin_smileys_get_all(void);
+
+/******************************************************************************
+ * Smiley Manager
+ *****************************************************************************/
+/**
+ * Displays the Smiley Manager Window
+ */
+void pidgin_smiley_manager_show(void);
+
+/**
+ * Shows an editor for a smiley.
+ *
+ * @param widget The parent widget to be linked or @c NULL
+ * @param smiley The PurpleSmiley to be edited, or @c NULL for a new smiley
+ * @return The smiley add dialog
+ *
+ * @see pidgin_smiley_editor_set_shortcut
+ * @see pidgin_smiley_editor_set_image
+ */
+PidginSmiley *pidgin_smiley_edit(GtkWidget *widget, PurpleSmiley *smiley);
+
+/**
+ * Set the shortcut in a smiley add dialog
+ *
+ * @param editor A smiley editor dialog (created by pidgin_smiley_edit)
+ * @param shortcut The shortcut to set
+ */
+void pidgin_smiley_editor_set_shortcut(PidginSmiley *editor, const gchar *shortcut);
+
+/**
+ * Set the image in a smiley add dialog
+ *
+ * @param editor A smiley editor dialog
+ * @param image A GdkPixbuf image
+ */
+void pidgin_smiley_editor_set_image(PidginSmiley *editor, GdkPixbuf *image);
+
+#endif /* _PIDGIN_GTKSMILEY_H_*/
--- a/pidgin/gtkthemes.c	Fri May 23 16:46:19 2008 +0000
+++ b/pidgin/gtkthemes.c	Fri May 23 16:47:59 2008 +0000
@@ -31,6 +31,7 @@
 #include "gtkconv.h"
 #include "gtkdialogs.h"
 #include "gtkimhtml.h"
+#include "gtksmiley.h"
 #include "gtkthemes.h"
 
 GSList *smiley_themes = NULL;
@@ -119,7 +120,7 @@
 	g_free(theme_dir);
 }
 
-void pidgin_themes_smiley_themeize(GtkWidget *imhtml)
+static void _pidgin_themes_smiley_themeize(GtkWidget *imhtml, gboolean custom)
 {
 	struct smiley_list *list;
 	if (!current_smiley_theme)
@@ -134,10 +135,30 @@
 			gtk_imhtml_associate_smiley(GTK_IMHTML(imhtml), sml, icons->data);
 			icons = icons->next;
 		}
+
+		if (custom == TRUE) {
+			icons = pidgin_smileys_get_all();
+
+			while (icons) {
+				gtk_imhtml_associate_smiley(GTK_IMHTML(imhtml), sml, icons->data);
+				icons = icons->next;
+			}
+		}
+
 		list = list->next;
 	}
 }
 
+void pidgin_themes_smiley_themeize(GtkWidget *imhtml)
+{
+	_pidgin_themes_smiley_themeize(imhtml, FALSE);
+}
+
+void pidgin_themes_smiley_themeize_custom(GtkWidget *imhtml)
+{
+	_pidgin_themes_smiley_themeize(imhtml, TRUE);
+}
+
 static void
 pidgin_themes_destroy_smiley_theme_smileys(struct smiley_theme *theme)
 {
@@ -274,7 +295,6 @@
 		} else if (load && list) {
 			gboolean hidden = FALSE;
 			char *sfile = NULL;
-			gboolean have_used_sfile = FALSE;
 
 			if (*i == '!' && *(i + 1) == ' ') {
 				hidden = TRUE;
@@ -288,17 +308,12 @@
 						i++;
 					l[li++] = *(i++);
 				}
+				l[li] = 0;
 				if (!sfile) {
-					l[li] = 0;
 					sfile = g_build_filename(dirname, l, NULL);
 				} else {
-					GtkIMHtmlSmiley *smiley = g_new0(GtkIMHtmlSmiley, 1);
-					l[li] = 0;
-					smiley->file = sfile;
-					smiley->smile = g_strdup(l);
-					smiley->hidden = hidden;
+					GtkIMHtmlSmiley *smiley = gtk_imhtml_smiley_create(sfile, l, hidden, 0);
 					list->smileys = g_slist_prepend(list->smileys, smiley);
-					have_used_sfile = TRUE;
 				}
 				while (isspace(*i))
 					i++;
@@ -306,8 +321,7 @@
 			}
 
 
-			if (!have_used_sfile)
-				g_free(sfile);
+			g_free(sfile);
 		}
 	}
 
@@ -340,8 +354,9 @@
 			PurpleConversation *conv = cnv->data;
 
 			if (PIDGIN_IS_PIDGIN_CONVERSATION(conv)) {
+				/* We want to see our custom smileys on our entry if we write the shortcut */
 				pidgin_themes_smiley_themeize(PIDGIN_CONVERSATION(conv)->imhtml);
-				pidgin_themes_smiley_themeize(PIDGIN_CONVERSATION(conv)->entry);
+				pidgin_themes_smiley_themeize_custom(PIDGIN_CONVERSATION(conv)->entry);
 			}
 		}
 	}
--- a/pidgin/gtkthemes.h	Fri May 23 16:46:19 2008 +0000
+++ b/pidgin/gtkthemes.h	Fri May 23 16:47:59 2008 +0000
@@ -51,6 +51,11 @@
 
 void pidgin_themes_smiley_themeize(GtkWidget *);
 
+/**
+ * @since 2.5.0
+ */
+void pidgin_themes_smiley_themeize_custom(GtkWidget *);
+
 void pidgin_themes_smiley_theme_probe(void);
 
 void pidgin_themes_load_smiley_theme(const char *file, gboolean load);
--- a/pidgin/gtkutils.c	Fri May 23 16:46:19 2008 +0000
+++ b/pidgin/gtkutils.c	Fri May 23 16:47:59 2008 +0000
@@ -103,7 +103,7 @@
 	g_signal_connect(G_OBJECT(imhtml), "url_clicked",
 					 G_CALLBACK(url_clicked_cb), NULL);
 
-	pidgin_themes_smiley_themeize(imhtml);
+	pidgin_themes_smiley_themeize_custom(imhtml);
 
 	gtk_imhtml_set_funcs(GTK_IMHTML(imhtml), &gtkimhtml_cbs);
 
@@ -1436,7 +1436,7 @@
 
 static void dnd_image_ok_callback(_DndData *data, int choice)
 {
-	char *filedata;
+	gchar *filedata;
 	size_t size;
 	struct stat st;
 	GError *err = NULL;
@@ -1444,6 +1444,8 @@
 	PidginConversation *gtkconv;
 	GtkTextIter iter;
 	int id;
+	PurpleBuddy *buddy;
+	PurpleContact *contact;
 	switch (choice) {
 	case DND_BUDDY_ICON:
 		if (g_stat(data->filename, &st)) {
@@ -1459,7 +1461,13 @@
 			return;
 		}
 
-		pidgin_set_custom_buddy_icon(data->account, data->who, data->filename);
+		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);
@@ -2869,12 +2877,11 @@
 }
 #endif /* ! Gtk 2.6.0 */
 
+#ifndef PURPLE_DISABLE_DEPRECATED
 void pidgin_set_custom_buddy_icon(PurpleAccount *account, const char *who, const char *filename)
 {
 	PurpleBuddy *buddy;
 	PurpleContact *contact;
-	gpointer data = NULL;
-	size_t len = 0;
 
 	buddy = purple_find_buddy(account, who);
 	if (!buddy) {
@@ -2883,20 +2890,9 @@
 	}
 
 	contact = purple_buddy_get_contact(buddy);
-
-	if (filename) {
-		const char *prpl_id = purple_account_get_protocol_id(account);
-		PurplePlugin *prpl = purple_find_prpl(prpl_id);
-
-		data = pidgin_convert_buddy_icon(prpl, filename, &len);
-
-		/* We don't want to delete the old icon if the new one didn't load. */
-		if (data == NULL)
-			return;
-	}
-
-	purple_buddy_icons_set_custom_icon(contact, data, len);
+	purple_buddy_icons_node_set_custom_icon_from_file((PurpleBlistNode*)contact, filename);
 }
+#endif
 
 char *pidgin_make_pretty_arrows(const char *str)
 {
@@ -3469,3 +3465,17 @@
 #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;
+}
+
--- a/pidgin/gtkutils.h	Fri May 23 16:46:19 2008 +0000
+++ b/pidgin/gtkutils.h	Fri May 23 16:47:59 2008 +0000
@@ -638,6 +638,7 @@
 											 GError **error);
 #endif
 
+#ifndef PURPLE_DISABLE_DEPRECATED
 /**
  * Set or unset a custom buddyicon for a user.
  *
@@ -645,8 +646,10 @@
  * @param who       The name of the user.
  * @param filename  The path of the custom icon. If this is @c NULL, then any
  *                  previously set custom buddy icon for the user is removed.
+ * @deprecated See purple_buddy_icons_node_set_custom_icon_from_file()
  */
 void pidgin_set_custom_buddy_icon(PurpleAccount *account, const char *who, const char *filename);
+#endif
 
 /**
  * Converts "->" and "<-" in strings to Unicode arrow characters, for use in referencing
@@ -689,7 +692,7 @@
  */
 GtkWidget *pidgin_make_mini_dialog(PurpleConnection *handle,
 	const char* stock_id, const char *primary, const char *secondary,
-	void *user_data, ...);
+	void *user_data, ...) G_GNUC_NULL_TERMINATED;
 
 /**
  * This is a callback function to be used for Ctrl+F searching in treeviews.
@@ -809,5 +812,15 @@
  */
 GtkWidget *pidgin_add_widget_to_vbox(GtkBox *vbox, const char *widget_label, GtkSizeGroup *sg, GtkWidget *widget, gboolean expand, GtkWidget **p_label);
 
+/**
+ * Create a GdkPixbuf from a PurpleStoredImage.
+ *
+ * @param  image   A PurpleStoredImage.
+ *
+ * @return   A GdkPixbuf created from the stored image.
+ * @since 2.5.0
+ */
+GdkPixbuf * pidgin_pixbuf_from_imgstore(PurpleStoredImage *image);
+
 #endif /* _PIDGINUTILS_H_ */
 
--- a/po/POTFILES.in	Fri May 23 16:46:19 2008 +0000
+++ b/po/POTFILES.in	Fri May 23 16:47:59 2008 +0000
@@ -180,6 +180,7 @@
 libpurple/request.h
 libpurple/savedstatuses.c
 libpurple/server.c
+libpurple/smiley.c
 libpurple/sslconn.c
 libpurple/status.c
 libpurple/util.c
@@ -209,6 +210,7 @@
 pidgin/gtkrequest.c
 pidgin/gtkroomlist.c
 pidgin/gtksavedstatuses.c
+pidgin/gtksmiley.c
 pidgin/gtksound.c
 pidgin/gtkstatusbox.c
 pidgin/gtkutils.c