# HG changeset patch # User Sadrul Habib Chowdhury # Date 1203450116 0 # Node ID 02eda4bd2b222829554d863822e9db08de0f0492 # Parent f463d54e606b58fba28ab8af7989b0f1019a94e6 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. diff -r f463d54e606b -r 02eda4bd2b22 libpurple/Makefile.am --- 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 \ diff -r f463d54e606b -r 02eda4bd2b22 libpurple/Makefile.mingw --- 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 \ diff -r f463d54e606b -r 02eda4bd2b22 libpurple/core.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(); diff -r f463d54e606b -r 02eda4bd2b22 libpurple/smiley.c --- /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" :-) + * + ****************************************************************************** + * + * + * + * + * + * + * + * + * + * + * + * + *****************************************************************************/ + + +/********************************************************************* + * 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); +} + diff -r f463d54e606b -r 02eda4bd2b22 libpurple/smiley.h --- /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_ */ + diff -r f463d54e606b -r 02eda4bd2b22 libpurple/util.c --- 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("'")) pln = "\'"; - else if(*(text+1) == '#' && - (sscanf(text, "&#%u%1[;]", £, temp) == 2 || sscanf(text, "&#x%x%1[;]", £, temp) == 2) && - pound != 0) { + else if(*(text+1) == '#' && (sscanf(text, "&#%u;", £) == 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)); } diff -r f463d54e606b -r 02eda4bd2b22 libpurple/util.h --- 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. */ diff -r f463d54e606b -r 02eda4bd2b22 pidgin/Makefile.am --- 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 \ diff -r f463d54e606b -r 02eda4bd2b22 pidgin/Makefile.mingw --- 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 \ diff -r f463d54e606b -r 02eda4bd2b22 pidgin/gtkblist.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, "", NULL }, { N_("/Tools/Buddy _Pounces"), NULL, pidgin_pounces_manager_show, 1, "", NULL }, { N_("/Tools/_Certificates"), NULL, pidgin_certmgr_show, 0, "", NULL }, + { N_("/Tools/Smile_y"), "Y", pidgin_smiley_manager_show, 0, "", PIDGIN_STOCK_TOOLBAR_SMILEY }, { N_("/Tools/Plu_gins"), "U", pidgin_plugin_dialog_show, 2, "", PIDGIN_STOCK_TOOLBAR_PLUGINS }, { N_("/Tools/Pr_eferences"), "P", pidgin_prefs_show, 0, "", GTK_STOCK_PREFERENCES }, { N_("/Tools/Pr_ivacy"), NULL, pidgin_privacy_dialog_show, 0, "", NULL }, diff -r f463d54e606b -r 02eda4bd2b22 pidgin/gtkconv.c --- 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(), diff -r f463d54e606b -r 02eda4bd2b22 pidgin/gtkimhtmltoolbar.c --- 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); diff -r f463d54e606b -r 02eda4bd2b22 pidgin/gtkmain.c --- 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(); diff -r f463d54e606b -r 02eda4bd2b22 pidgin/gtksmiley.c --- /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); +} + diff -r f463d54e606b -r 02eda4bd2b22 pidgin/gtksmiley.h --- /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_*/ diff -r f463d54e606b -r 02eda4bd2b22 pidgin/gtkthemes.c --- 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); } } } diff -r f463d54e606b -r 02eda4bd2b22 pidgin/gtkthemes.h --- 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); diff -r f463d54e606b -r 02eda4bd2b22 pidgin/gtkutils.c --- 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), >kimhtml_cbs);