changeset 22874:02eda4bd2b22

Apply the custom smiley patches from #1187, from Jorge Villaseo (Masca) and Mauro Srgio Ferreira Brasil. I have not applied the bits on MSN yet. I will have to look at it later, but I would rather someone else more familiar with the MSN code look at it first. I changed some bits of the applied patch (whitespacing, camelcasing etc.), and the bit that required a major version bump (in gtkthemes.h). There are a few more things that need to be done for this to be merged back to i.p.p.
author Sadrul Habib Chowdhury <imadil@gmail.com>
date Tue, 19 Feb 2008 19:41:56 +0000
parents f463d54e606b
children f1475ca224a8
files libpurple/Makefile.am libpurple/Makefile.mingw libpurple/core.c libpurple/smiley.c libpurple/smiley.h libpurple/util.c libpurple/util.h pidgin/Makefile.am pidgin/Makefile.mingw pidgin/gtkblist.c pidgin/gtkconv.c pidgin/gtkimhtmltoolbar.c pidgin/gtkmain.c pidgin/gtksmiley.c pidgin/gtksmiley.h pidgin/gtkthemes.c pidgin/gtkthemes.h pidgin/gtkutils.c
diffstat 18 files changed, 1897 insertions(+), 21 deletions(-) [+]
line wrap: on
line diff
--- a/libpurple/Makefile.am	Mon Feb 18 19:22:39 2008 +0000
+++ b/libpurple/Makefile.am	Tue Feb 19 19:41:56 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 \
--- a/libpurple/Makefile.mingw	Mon Feb 18 19:22:39 2008 +0000
+++ b/libpurple/Makefile.mingw	Tue Feb 19 19:41:56 2008 +0000
@@ -65,6 +65,7 @@
 			savedstatuses.c \
 			server.c \
 			signals.c \
+			smiley.c \
 			sound.c \
 			sslconn.c \
 			status.c \
--- a/libpurple/core.c	Mon Feb 18 19:22:39 2008 +0000
+++ b/libpurple/core.c	Tue Feb 19 19:41:56 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"
@@ -162,6 +163,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
@@ -190,6 +192,7 @@
 	purple_connections_disconnect_all();
 
 	/* Save .xml files, remove signals, etc. */
+	purple_smileys_uninit();
 	purple_idle_uninit();
 	purple_ssl_uninit();
 	purple_pounces_uninit();
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/smiley.c	Tue Feb 19 19:41:56 2008 +0000
@@ -0,0 +1,858 @@
+/**
+ * @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 "xmlnode.h"
+#include "debug.h"
+#include "imgstore.h"
+#include "smiley.h"
+#include "util.h"
+
+
+/**************************************************************************/
+/* Main structures, members and constants                                 */
+/**************************************************************************/
+
+static GHashTable *smiley_data_index = NULL;
+static GHashTable *smiley_shortcut_index = NULL;
+static GHashTable *smiley_checksum_index = NULL;
+
+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);
+
+PurpleSmiley *purple_smiley_load_file(const char *shortcut, const char *checksum,
+		const char *filename);
+
+void
+purple_smiley_set_data_impl(PurpleSmiley *smiley, guchar *smiley_data,
+		size_t smiley_data_len, 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_data_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);
+
+			purple_smileys_add(smiley);
+		}
+	}
+
+	xmlnode_free(root_node);
+}
+
+
+/*********************************************************************
+ * 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;
+}
+
+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)
+		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);
+
+	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;
+}
+
+void
+purple_smiley_set_data_impl(PurpleSmiley *smiley, guchar *smiley_data,
+				size_t smiley_data_len, const char *filename)
+{
+	PurpleStoredImage *old_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;
+	smiley->img = NULL;
+
+	if (filename)
+		smiley->img = purple_imgstore_add(smiley_data,
+				smiley_data_len, filename);
+	else
+		smiley->img = purple_smiley_data_new(smiley_data, smiley_data_len);
+
+	g_free(smiley->checksum);
+	smiley->checksum = purple_util_get_image_checksum(
+			smiley_data, smiley_data_len);
+
+	if (!old_img)
+		return;
+
+	/* If the old and new image files have different names we need
+	 * to unstore old image file. */
+	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 = g_slice_new0(PurpleSmiley);
+	if (!smiley)
+		return NULL;
+
+	smiley->shortcut = g_strdup(shortcut);
+
+	return smiley;
+}
+
+static void
+purple_smiley_destroy(PurpleSmiley *smiley)
+{
+	g_return_if_fail(smiley != NULL);
+
+	g_free(smiley->shortcut);
+	g_free(smiley->checksum);
+	purple_imgstore_unref(smiley->img);
+
+	g_slice_free(PurpleSmiley, 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;
+
+	smiley->checksum = purple_util_get_image_checksum(
+				purple_imgstore_get_data(img),
+				purple_imgstore_get_size(img));
+
+	smiley->img = img;
+	purple_smiley_data_store(img);
+
+	return smiley;
+}
+
+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, const char *filename)
+{
+	PurpleSmiley *smiley = NULL;
+	guchar *smiley_data;
+	size_t smiley_data_len;
+
+	g_return_val_if_fail(shortcut  != NULL,  NULL);
+	g_return_val_if_fail(filepath != NULL,  NULL);
+
+	if (read_smiley_file(filepath, &smiley_data, &smiley_data_len))
+		smiley = purple_smiley_new_from_stream(shortcut, smiley_data,
+				smiley_data_len, filename);
+
+	return smiley;
+}
+
+void
+purple_smiley_delete(PurpleSmiley *smiley)
+{
+	guchar *filename = NULL;
+
+	g_return_if_fail(smiley != NULL);
+
+	filename = g_hash_table_lookup(smiley_shortcut_index, smiley->shortcut);
+
+	if (filename != NULL)
+		purple_smileys_remove(smiley);
+
+	if (smiley->img)
+		purple_smiley_data_unstore(purple_imgstore_get_filename(smiley->img));
+
+	purple_smiley_destroy(smiley);
+}
+
+gboolean
+purple_smiley_set_shortcut(PurpleSmiley *smiley, const char *shortcut)
+{
+	char *filename = NULL;
+
+	g_return_val_if_fail(smiley  != NULL, FALSE);
+	g_return_val_if_fail(shortcut != NULL, FALSE);
+
+	/* Check out whether the shortcut is already being used. */
+	filename = g_hash_table_lookup(smiley_shortcut_index, shortcut);
+
+	if (filename)
+		return FALSE;
+
+	/* Save filename associated with the current shortcut. */
+	filename = g_strdup(g_hash_table_lookup(
+			smiley_shortcut_index, smiley->shortcut));
+
+	/* If the updating smiley was already inserted to internal indexes, the
+	 * shortcut index will need update.
+	 * So we remove the old element first, and add the new one right after.
+	 */
+	if (filename) {
+		g_hash_table_remove(smiley_shortcut_index, smiley->shortcut);
+		g_hash_table_insert(smiley_shortcut_index,
+			g_strdup(shortcut), filename);
+	}
+
+	g_free(smiley->shortcut);
+	smiley->shortcut = g_strdup(shortcut);
+
+	purple_smileys_save();
+
+	return TRUE;
+}
+
+void
+purple_smiley_set_data(PurpleSmiley *smiley, guchar *smiley_data,
+			   size_t smiley_data_len, gboolean keepfilename)
+{
+	gboolean updateindex = FALSE;
+	const char *filename = NULL;
+
+	g_return_if_fail(smiley     != NULL);
+	g_return_if_fail(smiley_data != NULL);
+	g_return_if_fail(smiley_data_len > 0);
+
+	/* If the updating smiley was already inserted to internal indexes, the
+	 * checksum index will need update. And, considering that the filename
+	 * could be regenerated using the file's checksum, the filename index
+	 * will be updated too.
+	 * So we remove it here, and add it again after the information is
+	 * updated by "purple_smiley_set_data_impl" method. */
+	filename = g_hash_table_lookup(smiley_checksum_index, smiley->checksum);
+	if (filename) {
+		g_hash_table_remove(smiley_checksum_index, smiley->checksum);
+		g_hash_table_remove(smiley_data_index, filename);
+
+		updateindex = TRUE;
+	}
+
+	/* Updates the file data. */
+	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);
+
+	/* Reinserts the index item. */
+	filename = purple_imgstore_get_filename(smiley->img);
+	if ((updateindex) && (filename)) {
+		g_hash_table_insert(smiley_checksum_index,
+				g_strdup(smiley->checksum), g_strdup(filename));
+		g_hash_table_insert(smiley_data_index, g_strdup(filename), 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** preturninglist = (GList**)user_data;
+	GList *returninglist = *preturninglist;
+
+	returninglist = g_list_append(returninglist, value);
+
+	*preturninglist = returninglist;
+}
+
+GList *
+purple_smileys_get_all(void)
+{
+	GList *returninglist = NULL;
+
+	g_hash_table_foreach(smiley_data_index, add_smiley_to_list, &returninglist);
+
+	return returninglist;
+}
+
+void
+purple_smileys_add(PurpleSmiley *smiley)
+{
+	const char *filename = NULL;
+
+	g_return_if_fail(smiley != NULL);
+
+	if (!g_hash_table_lookup(smiley_shortcut_index, smiley->shortcut)) {
+		filename = purple_imgstore_get_filename(smiley->img);
+
+		g_hash_table_insert(smiley_data_index, g_strdup(filename), smiley);
+		g_hash_table_insert(smiley_shortcut_index, g_strdup(smiley->shortcut), g_strdup(filename));
+		g_hash_table_insert(smiley_checksum_index, g_strdup(smiley->checksum), g_strdup(filename));
+
+		purple_smileys_save();
+	}
+}
+
+void
+purple_smileys_remove(PurpleSmiley *smiley)
+{
+	const char *filename = NULL;
+
+	g_return_if_fail(smiley != NULL);
+
+	filename = purple_imgstore_get_filename(smiley->img);
+
+	if (g_hash_table_lookup(smiley_data_index, filename)) {
+		g_hash_table_remove(smiley_data_index, filename);
+		g_hash_table_remove(smiley_shortcut_index, smiley->shortcut);
+		g_hash_table_remove(smiley_checksum_index, smiley->checksum);
+
+		purple_smileys_save();
+	}
+}
+
+PurpleSmiley *
+purple_smileys_find_by_shortcut(const char *shortcut)
+{
+	PurpleSmiley *smiley = NULL;
+	guchar *filename = NULL;
+
+	g_return_val_if_fail(shortcut != NULL, NULL);
+
+	filename = g_hash_table_lookup(smiley_shortcut_index, shortcut);
+
+	if (filename == NULL)
+		return NULL;
+
+	smiley = g_hash_table_lookup(smiley_data_index, filename);
+
+	if (!smiley)
+		return NULL;
+
+	return smiley;
+}
+
+PurpleSmiley *
+purple_smileys_find_by_checksum(const char *checksum)
+{
+	PurpleSmiley *smiley = NULL;
+	guchar *filename = NULL;
+
+	g_return_val_if_fail(checksum != NULL, NULL);
+
+	filename = g_hash_table_lookup(smiley_checksum_index, checksum);
+
+	if (filename == NULL)
+		return NULL;
+
+	smiley = g_hash_table_lookup(smiley_data_index, filename);
+
+	if (!smiley)
+		return NULL;
+
+	return smiley;
+}
+
+const char *
+purple_smileys_get_storing_dir(void)
+{
+	return smileys_dir;
+}
+
+void
+purple_smileys_init()
+{
+	smiley_data_index = g_hash_table_new_full(g_str_hash, g_str_equal,
+			g_free, NULL);
+	smiley_shortcut_index = g_hash_table_new_full(g_str_hash, g_str_equal,
+			g_free, g_free);
+	smiley_checksum_index = g_hash_table_new_full(g_str_hash, g_str_equal,
+			g_free, g_free);
+
+	smileys_dir = g_build_filename(purple_user_dir(), SMILEYS_DEFAULT_FOLDER, NULL);
+
+	purple_smileys_load();
+}
+
+static gboolean
+free_smiley_data(gpointer key, gpointer value, gpointer user_data)
+{
+	purple_smiley_destroy(value);
+
+	return TRUE;
+}
+
+void
+purple_smileys_uninit()
+{
+	if (save_timer != 0) {
+		purple_timeout_remove(save_timer);
+		save_timer = 0;
+		sync_smileys();
+	}
+
+	g_hash_table_foreach_remove(smiley_data_index, free_smiley_data, NULL);
+	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	Tue Feb 19 19:41:56 2008 +0000
@@ -0,0 +1,294 @@
+/**
+ * @file Smiley.h Smiley 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
+ *
+ */
+
+#ifndef _PURPLE_SMILEY_H_
+#define _PURPLE_SMILEY_H_
+
+#include "imgstore.h"
+#include "util.h"
+
+/**
+ * A custom smiley.
+ * This contains everything Purple will ever need to know about a custom smiley.
+ * Everything.
+ */
+typedef struct _PurpleSmiley {
+        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.        */
+} PurpleSmiley;
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**************************************************************************/
+/** @name Custom Smiley API                                               */
+/**************************************************************************/
+/*@{*/
+
+/**
+ * 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.
+ *
+ * If a custom smiley with the informed shortcut already exist, it
+ * will be automaticaly returned.
+ *
+ * @param shortcut           The custom smiley associated shortcut.
+ * @param smiley_data        The custom smiley data.
+ * @param smiley_data_len    The custom smiley data length.
+ *
+ * @return The custom smiley structure filled up.
+ */
+PurpleSmiley *
+purple_smiley_new_from_stream(const char *shortcut, guchar *smiley_data,
+                        size_t smiley_data_len, const char *filename);
+
+/**
+ * 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,
+                        const char *filename);
+
+/**
+ * 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 Subsistem API                                     */
+/**************************************************************************/
+/*@{*/
+
+/**
+ * Returns a list of all custom smileys.
+ *
+ * @constreturn A list of all custom smileys.
+ */
+GList *
+purple_smileys_get_all(void);
+
+/**
+ * Adds the custom smiley to the library persistence.
+ *
+ * @param smiley   The custom smiley.
+ *
+ */
+void
+purple_smileys_add(PurpleSmiley *smiley);
+
+/**
+ * Remove the custom smiley from persistence.
+ *
+ * @param smiley   The custom smiley.
+ *
+ */
+void
+purple_smileys_remove(PurpleSmiley *smiley);
+
+/**
+ * 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	Mon Feb 18 19:22:39 2008 +0000
+++ b/libpurple/util.c	Tue Feb 19 19:41:56 2008 +0000
@@ -921,7 +921,6 @@
 {
 	const char *pln;
 	int len, pound;
-	char temp[2];
 
 	if (!text || *text != '&')
 		return NULL;
@@ -944,9 +943,8 @@
 		pln = "\302\256";      /* or use g_unichar_to_utf8(0xae); */
 	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) &&
-			pound != 0) {
+	else if(*(text+1) == '#' && (sscanf(text, "&#%u;", &pound) == 1) &&
+			pound != 0 && *(text+3+(gint)log10(pound)) == ';') {
 		static char buf[7];
 		int buflen = g_unichar_to_utf8((gunichar)pound, buf);
 		buf[buflen] = '\0';
@@ -2889,7 +2887,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,8 +2908,15 @@
 	}
 	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,
+	return g_strdup_printf("%s.%s", 
+	                       purple_util_get_image_checksum(image_data, image_len),
 	                       purple_util_get_image_extension(image_data, image_len));
 }
 
--- a/libpurple/util.h	Mon Feb 18 19:22:39 2008 +0000
+++ b/libpurple/util.h	Tue Feb 19 19:41:56 2008 +0000
@@ -702,6 +702,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);
+
+/**
  * Returns a SHA-1 hash string of the data passed in with the correct file
  * extention appended.
  */
--- a/pidgin/Makefile.am	Mon Feb 18 19:22:39 2008 +0000
+++ b/pidgin/Makefile.am	Tue Feb 19 19:41:56 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	Mon Feb 18 19:22:39 2008 +0000
+++ b/pidgin/Makefile.mingw	Tue Feb 19 19:41:56 2008 +0000
@@ -86,6 +86,7 @@
 			gtkroomlist.c \
 			gtksavedstatuses.c \
 			gtkscrollbook.c \
+			gtksmiley.c \
 			gtksound.c \
 			gtksourceiter.c \
 			gtksourceundomanager.c \
--- a/pidgin/gtkblist.c	Mon Feb 18 19:22:39 2008 +0000
+++ b/pidgin/gtkblist.c	Tue Feb 19 19:41:56 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"
@@ -3078,6 +3079,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 },
--- a/pidgin/gtkconv.c	Mon Feb 18 19:22:39 2008 +0000
+++ b/pidgin/gtkconv.c	Tue Feb 19 19:41:56 2008 +0000
@@ -5044,6 +5044,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
@@ -5350,8 +5356,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);
@@ -5510,14 +5514,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 */
@@ -5763,8 +5761,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(),
--- a/pidgin/gtkimhtmltoolbar.c	Mon Feb 18 19:22:39 2008 +0000
+++ b/pidgin/gtkimhtmltoolbar.c	Tue Feb 19 19:41:56 2008 +0000
@@ -36,6 +36,7 @@
 
 #include "gtkdialogs.h"
 #include "gtkimhtmltoolbar.h"
+#include "gtksmiley.h"
 #include "gtkthemes.h"
 #include "gtkutils.h"
 
@@ -626,6 +627,34 @@
 	image = gtk_image_new_from_file(filename);
 
 	gtk_widget_size_request(image, &size);
+
+	if (size.width > 24) { /* This is a custom smiley, let's scale it */
+		GdkPixbuf *pixbuf = NULL;
+		GdkPixbuf *resized;
+		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) {
+			resized = gdk_pixbuf_scale_simple(pixbuf, 24, 24,
+					GDK_INTERP_HYPER);
+			image = gtk_image_new_from_pixbuf(resized);
+
+			gtk_widget_size_request(image, &size);
+		}
+	}
+
 	(*width) += size.width;
 
 	button = gtk_button_new();
@@ -688,6 +717,7 @@
 	GtkWidget *dialog;
 	GtkWidget *smiley_table = NULL;
 	GSList *smileys, *unique_smileys = NULL;
+	GSList *custom_smileys = NULL;
 
 	if (!gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(smiley))) {
 		destroy_smiley_dialog(toolbar);
@@ -709,6 +739,15 @@
 		smileys = smileys->next;
 	}
 
+	custom_smileys = pidgin_smileys_get_all();
+
+	while (custom_smileys) {
+		GtkIMHtmlSmiley *smiley = custom_smileys->data;
+		unique_smileys = g_slist_append(unique_smileys, smiley);
+		
+		custom_smileys = custom_smileys->next;
+	}
+
 	dialog = pidgin_create_dialog(_("Smile!"), 0, "smiley_dialog", FALSE);
 
 	gtk_window_set_position(GTK_WINDOW(dialog), GTK_WIN_POS_MOUSE);
--- a/pidgin/gtkmain.c	Mon Feb 18 19:22:39 2008 +0000
+++ b/pidgin/gtkmain.c	Tue Feb 19 19:41:56 2008 +0000
@@ -61,6 +61,7 @@
 #include "gtkroomlist.h"
 #include "gtksavedstatuses.h"
 #include "gtksession.h"
+#include "gtksmiley.h"
 #include "gtksound.h"
 #include "gtkthemes.h"
 #include "gtkutils.h"
@@ -327,6 +328,7 @@
 	pidgin_roomlist_init();
 	pidgin_log_init();
 	pidgin_docklet_init();
+	pidgin_smileys_init();
 }
 
 static GHashTable *ui_info = NULL;
@@ -343,6 +345,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	Tue Feb 19 19:41:56 2008 +0000
@@ -0,0 +1,562 @@
+/**
+ * @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"
+
+typedef struct
+{
+	GtkWidget *parent;
+	GtkWidget *smile;
+	GtkWidget *file_chooser;
+	gchar *filename;
+} PidginSmiley;
+
+typedef struct
+{
+	GtkWidget *window;
+
+	GtkWidget *treeview;
+	GtkListStore *model;
+} SmileyManager;
+
+enum
+{
+	ICON,
+	SHORTCUT,
+	DATA,
+	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);
+	g_free(smiley);
+}
+
+/******************************************************************************
+ * GtkIMHtmlSmileys stuff
+ *****************************************************************************/
+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 GtkIMHtmlSmiley *smiley_purple_to_gtkimhtml(PurpleSmiley *smiley)
+{
+	GtkIMHtmlSmiley *gtksmiley;
+	gchar *filename;
+	const gchar *file;
+
+	file = purple_imgstore_get_filename(smiley->img);
+
+	filename = g_build_filename(purple_smileys_get_storing_dir(),file, NULL);
+
+	gtksmiley = g_new0(GtkIMHtmlSmiley,1);
+	gtksmiley->smile = g_strdup(smiley->shortcut);
+	gtksmiley->hidden = FALSE;
+	gtksmiley->file = filename;
+	gtksmiley->flags = GTK_IMHTML_SMILEY_CUSTOM;
+
+	return gtksmiley;
+}
+
+void pidgin_smiley_add_to_list(PurpleSmiley *smiley)
+{
+	GtkIMHtmlSmiley *gtksmiley;
+
+	gtksmiley = smiley_purple_to_gtkimhtml(smiley);
+	add_gtkimhtml_to_list(gtksmiley);
+}
+
+static void destroy_gtksmiley(GtkIMHtmlSmiley *gtksmiley)
+{
+	purple_debug_info("gtksmiley", "destroying %s\n", gtksmiley->smile);
+	g_free(gtksmiley->smile);
+	g_free(gtksmiley->file);
+	g_free(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, smiley->shortcut))
+			continue;
+
+		destroy_gtksmiley(gtksmiley);
+		list = g_slist_delete_link(list, list);
+		break;
+	}
+
+	gtk_smileys = list;
+}
+
+void pidgin_smileys_init(void)
+{
+	GList *purple_smileys;
+	PurpleSmiley *smiley;
+
+	if (gtk_smileys != NULL)
+		return;
+
+	purple_smileys = purple_smileys_get_all();
+
+	for (; purple_smileys; purple_smileys = purple_smileys->next) {
+		smiley = (PurpleSmiley*)purple_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;
+		destroy_gtksmiley(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;
+	gchar *file;
+
+	entry = gtk_entry_get_text(GTK_ENTRY(s->smile));
+
+	if (s->filename == NULL || *entry == 0) {
+		purple_notify_error(s->parent, _("Custom Smiley"),
+				_("More Data needed"), NULL);
+		return;
+	}
+
+	purple_debug_info("gtksmiley", "adding a new smiley\n");
+	file = g_path_get_basename(s->filename);
+	emoticon = purple_smiley_new_from_file(entry, s->filename, file);
+	purple_smileys_add(emoticon);
+	g_free(file);
+
+	if (gtk_smileys != NULL)
+		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(GtkWidget *widget, gint resp, PidginSmiley *s)
+{
+	if (resp == GTK_RESPONSE_ACCEPT)
+		s->filename = gtk_file_chooser_get_filename(
+				GTK_FILE_CHOOSER(s->file_chooser));
+}
+
+void pidgin_smiley_add(GtkWidget *widget)
+{
+	GtkWidget *vbox;
+	GtkWidget *hbox;
+	GtkWidget *label;
+	GtkWidget *filech;
+	GtkWidget *window;
+
+	PidginSmiley *s = g_new0(PidginSmiley, 1);
+
+	window = gtk_dialog_new_with_buttons(_("Add Smiley"),
+			GTK_WINDOW(widget),
+			GTK_DIALOG_DESTROY_WITH_PARENT | GTK_DIALOG_NO_SEPARATOR,
+			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(_("Select the file:"));
+	gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 0);
+	gtk_widget_show(label);
+
+	s->file_chooser = gtk_file_chooser_dialog_new(_("Custom Smiley"),
+		GTK_WINDOW(window),
+		GTK_FILE_CHOOSER_ACTION_OPEN,
+		GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
+		GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT,
+		NULL);
+
+	g_signal_connect(s->file_chooser, "response", G_CALLBACK(do_add_file_cb), s);
+
+	filech = gtk_file_chooser_button_new_with_dialog(GTK_WIDGET(s->file_chooser));
+
+	gtk_box_pack_end(GTK_BOX(hbox), filech, TRUE, TRUE, 0);
+	gtk_widget_show(filech);
+
+	gtk_widget_show(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(_("Define Smiley's shortcut"));
+	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);
+	gtk_label_set_mnemonic_widget(GTK_LABEL(label), GTK_WIDGET(s->smile));
+
+	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);
+}
+
+/******************************************************************************
+ * Delete smiley
+ *****************************************************************************/
+static void delete_foreach(GtkTreeModel *model, GtkTreePath *path,
+		GtkTreeIter *iter, gpointer data)
+{
+	PurpleSmiley *smiley;
+	char *shortcut;
+	SmileyManager *dialog;
+
+	dialog = (SmileyManager*)data;
+
+	gtk_tree_model_get(model, iter,
+			SHORTCUT, &shortcut,
+			-1);
+
+	purple_debug_info("gtksmiley", "delete_foreach shortcut = %s\n", shortcut);
+
+	smiley = purple_smileys_find_by_shortcut(shortcut);
+
+	if(smiley == NULL)
+		purple_debug_error("gtksmiley", "%s not found\n", shortcut);
+	else {
+		pidgin_smiley_del_from_list(smiley);
+		purple_smiley_delete(smiley);
+	}
+
+	g_free(shortcut);
+}
+
+static void smiley_delete(SmileyManager *dialog)
+{
+	GtkTreeSelection *selection;
+
+	selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(dialog->treeview));
+	gtk_tree_selection_selected_foreach(selection, delete_foreach, dialog);
+}
+/******************************************************************************
+ * 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) {
+		GdkPixbufLoader *loader = gdk_pixbuf_loader_new();
+		GdkPixbuf *smiley_image = NULL;
+
+		gdk_pixbuf_loader_write(loader, purple_imgstore_get_data(img),
+				purple_imgstore_get_size(img), NULL);
+		gdk_pixbuf_loader_close(loader, NULL);
+		smiley_image = gdk_pixbuf_loader_get_pixbuf(loader);
+
+		purple_imgstore_unref(img);
+
+		if (smiley_image != NULL)
+			sized_smiley = gdk_pixbuf_scale_simple(smiley_image,
+					22, 22, GDK_INTERP_HYPER);
+
+		g_object_unref(loader);
+	}
+
+
+	gtk_list_store_append(smiley_manager->model, &iter);
+
+	gtk_list_store_set(smiley_manager->model, &iter,
+			ICON, sized_smiley,
+			SHORTCUT, smiley->shortcut,
+			DATA, NULL,
+			-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 = list->next) {
+		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);
+}
+
+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_NONE);
+	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_POINTER		/* DATA */
+			);
+
+	/* 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);
+
+	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);
+
+	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)
+{
+	switch (resp) {
+		case GTK_RESPONSE_YES:
+			pidgin_smiley_add(dialog->window);
+			break;
+		case GTK_RESPONSE_NO:
+			smiley_delete(dialog);
+			refresh_list();
+			break;
+		case GTK_RESPONSE_DELETE_EVENT:
+		case GTK_RESPONSE_CLOSE:
+			gtk_widget_destroy(dialog->window);
+			g_free(smiley_manager);
+			smiley_manager = NULL;
+			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_DELETE, GTK_RESPONSE_NO,
+			GTK_STOCK_CLOSE, GTK_RESPONSE_CLOSE,
+			NULL);
+
+	gtk_window_set_default_size(GTK_WINDOW(win), 50, 400);
+	gtk_container_set_border_width(GTK_CONTAINER(win),PIDGIN_HIG_BORDER);
+
+	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	Tue Feb 19 19:41:56 2008 +0000
@@ -0,0 +1,79 @@
+/**
+ * @file gtksmiley.h GTK+ Custom Smiley 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
+ */
+
+#ifndef _PIDGIN_GTKSMILEY_H_
+#define _PIDGIN_GTKSMILEY_H_
+
+#include "smiley.h"
+
+/**
+ * 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);
+
+/**
+ * Displays the "Add smiley" Dialog Box
+ *
+ * @param widget	The parent widget to be linked
+ */
+void pidgin_smiley_add(GtkWidget *widget);
+
+#endif /* _PIDGIN_GTKSMILEY_H_*/
--- a/pidgin/gtkthemes.c	Mon Feb 18 19:22:39 2008 +0000
+++ b/pidgin/gtkthemes.c	Tue Feb 19 19:41:56 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)
 {
@@ -340,8 +361,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	Mon Feb 18 19:22:39 2008 +0000
+++ b/pidgin/gtkthemes.h	Tue Feb 19 19:41:56 2008 +0000
@@ -48,6 +48,7 @@
 void pidgin_themes_init(void);
 gboolean pidgin_themes_smileys_disabled(void);
 void pidgin_themes_smiley_themeize(GtkWidget *);
+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);
 void pidgin_themes_remove_smiley_theme(const char *file);
--- a/pidgin/gtkutils.c	Mon Feb 18 19:22:39 2008 +0000
+++ b/pidgin/gtkutils.c	Tue Feb 19 19:41:56 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);