# HG changeset patch # User Sadrul Habib Chowdhury # Date 1206997491 0 # Node ID b30f54a720313d29acbea9bc3a9138c8bf25d997 # Parent 0998ae2e483883f14897cf59345690598a168a70# Parent 48e33e3673e245d2b40a2bb00332fa5213498af2 propagate from branch 'im.pidgin.pidgin' (head 8ab7bcf180c3d8cc09b9d82060417bcf3fcfff95) to branch 'im.pidgin.pidgin.custom_smiley' (head aeaf4e99a407c48978426a7866b921b380471dad) diff -r 0998ae2e4838 -r b30f54a72031 ChangeLog.API --- a/ChangeLog.API Mon Mar 31 03:38:50 2008 +0000 +++ b/ChangeLog.API Mon Mar 31 21:04:51 2008 +0000 @@ -1,5 +1,11 @@ Pidgin and Finch: The Pimpin' Penguin IM Clients That're Good for the Soul +version 2.5.0 (??/??/2008): + pidgin: + Added: + * gtk_imhtml_smiley_create, gtk_imhtml_smiley_reload and + gtk_imhtml_smiley_destroy to deal with GtkIMHtmlSmiley's. + version 2.4.0 (02/29/2008): libpurple: Added: diff -r 0998ae2e4838 -r b30f54a72031 libpurple/Makefile.am --- a/libpurple/Makefile.am Mon Mar 31 03:38:50 2008 +0000 +++ b/libpurple/Makefile.am Mon Mar 31 21:04:51 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 0998ae2e4838 -r b30f54a72031 libpurple/Makefile.mingw --- a/libpurple/Makefile.mingw Mon Mar 31 03:38:50 2008 +0000 +++ b/libpurple/Makefile.mingw Mon Mar 31 21:04:51 2008 +0000 @@ -65,6 +65,7 @@ savedstatuses.c \ server.c \ signals.c \ + smiley.c \ sound.c \ sslconn.c \ status.c \ diff -r 0998ae2e4838 -r b30f54a72031 libpurple/core.c --- a/libpurple/core.c Mon Mar 31 03:38:50 2008 +0000 +++ b/libpurple/core.c Mon Mar 31 21:04:51 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" @@ -164,6 +165,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 @@ -192,6 +194,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 0998ae2e4838 -r b30f54a72031 libpurple/imgstore.c --- a/libpurple/imgstore.c Mon Mar 31 03:38:50 2008 +0000 +++ b/libpurple/imgstore.c Mon Mar 31 21:04:51 2008 +0000 @@ -70,6 +70,22 @@ return img; } +PurpleStoredImage * +purple_imgstore_new_from_file(const char *path) +{ + gchar *data = NULL; + size_t len; + GError *err = NULL; + + if (!g_file_get_contents(path, &data, &len, &err)) { + purple_debug_error("imgstore", "Error reading %s: %s\n", + path, err->message); + g_error_free(err); + return NULL; + } + return purple_imgstore_add(data, len, path); +} + int purple_imgstore_add_with_id(gpointer data, size_t size, const char *filename) { diff -r 0998ae2e4838 -r b30f54a72031 libpurple/imgstore.h --- a/libpurple/imgstore.h Mon Mar 31 03:38:50 2008 +0000 +++ b/libpurple/imgstore.h Mon Mar 31 21:04:51 2008 +0000 @@ -59,6 +59,17 @@ purple_imgstore_add(gpointer data, size_t size, const char *filename); /** + * Create an image and add it to the store. + * + * @param path The path to the image. + * + * @return The stored image. + * @since 2.X.X + */ +PurpleStoredImage * +purple_imgstore_new_from_file(const char *path); + +/** * Add an image to the store, allocating an ID. * * The caller owns a reference to the image in the store, and must dereference diff -r 0998ae2e4838 -r b30f54a72031 libpurple/protocols/msn/msn.c --- a/libpurple/protocols/msn/msn.c Mon Mar 31 03:38:50 2008 +0000 +++ b/libpurple/protocols/msn/msn.c Mon Mar 31 21:04:51 2008 +0000 @@ -32,6 +32,7 @@ #include "pluginpref.h" #include "prefs.h" #include "session.h" +#include "smiley.h" #include "state.h" #include "util.h" #include "cmds.h" @@ -82,6 +83,12 @@ time_t when; } MsnIMData; +typedef struct +{ + char *smile; + MsnObject *obj; +} MsnEmoticon; + static const char * msn_normalize(const PurpleAccount *account, const char *str) { @@ -115,6 +122,7 @@ return FALSE; msn_switchboard_send_msg(swboard, msg, TRUE); + msn_message_destroy(msg); return TRUE; } @@ -925,6 +933,97 @@ return FALSE; } +static GString* +msn_msg_emoticon_add(GString *current, MsnEmoticon *emoticon) +{ + MsnObject *obj; + char *strobj; + + if (emoticon == NULL) + return current; + + obj = emoticon->obj; + + if (!obj) + return current; + + strobj = msn_object_to_string(obj); + + if (current) + g_string_append_printf(current, "\t%s\t%s", + emoticon->smile, strobj); + else { + current = g_string_new(""); + g_string_printf(current,"%s\t%s", + emoticon->smile, strobj); + } + + g_free(strobj); + + return current; +} + +static void +msn_send_emoticons(MsnSwitchBoard *swboard, GString *body) +{ + MsnMessage *msg; + + g_return_if_fail(body != NULL); + + msg = msn_message_new(MSN_MSG_SLP); + msn_message_set_content_type(msg, "text/x-mms-emoticon"); + msn_message_set_flag(msg, 'N'); + msn_message_set_bin_data(msg, body->str, body->len); + + msn_switchboard_send_msg(swboard, msg, TRUE); + msn_message_destroy(msg); +} + +static void msn_emoticon_destroy(MsnEmoticon *emoticon) +{ + if (emoticon->obj) + msn_object_destroy(emoticon->obj); + g_free(emoticon->smile); + g_free(emoticon); +} + +static GSList* msn_msg_grab_emoticons(const char *msg, const char *username) +{ + GSList *list; + GList *smileys; + PurpleSmiley *smiley; + PurpleStoredImage *img; + char *ptr; + MsnEmoticon *emoticon; + int length; + + list = NULL; + smileys = purple_smileys_get_all(); + length = strlen(msg); + + for (; smileys; smileys = g_list_delete_link(smileys, smileys)) { + smiley = smileys->data; + + ptr = g_strstr_len(msg, length, purple_smiley_get_shortcut(smiley)); + + if (!ptr) + continue; + + img = purple_smiley_get_stored_image(smiley); + + emoticon = g_new0(MsnEmoticon, 1); + emoticon->smile = g_strdup(purple_smiley_get_shortcut(smiley)); + emoticon->obj = msn_object_new_from_image(img, + purple_imgstore_get_filename(img), + username, MSN_OBJECT_EMOTICON); + + purple_imgstore_unref(img); + list = g_slist_prepend(list, emoticon); + } + + return list; +} + static int msn_send_im(PurpleConnection *gc, const char *who, const char *message, PurpleMessageFlags flags) @@ -934,9 +1033,11 @@ MsnMessage *msg; char *msgformat; char *msgtext; + const char *username; purple_debug_info("MSNP14","send IM {%s} to %s\n",message,who); account = purple_connection_get_account(gc); + username = purple_account_get_username(account); if (buddy) { PurplePresence *p = purple_buddy_get_presence(buddy); @@ -969,10 +1070,13 @@ g_free(msgtext); purple_debug_info("MSNP14","prepare to send online Message\n"); - if (g_ascii_strcasecmp(who, purple_account_get_username(account))) + if (g_ascii_strcasecmp(who, username)) { MsnSession *session; MsnSwitchBoard *swboard; + MsnEmoticon *smile; + GSList *smileys; + GString *emoticons = NULL; session = gc->proto_data; if(msn_user_is_yahoo(account,who)){ @@ -982,6 +1086,19 @@ }else{ purple_debug_info("MSNP14","send via switchboard\n"); swboard = msn_session_get_swboard(session, who, MSN_SB_FLAG_IM); + smileys = msn_msg_grab_emoticons(message, username); + while (smileys) { + smile = (MsnEmoticon*)smileys->data; + emoticons = msn_msg_emoticon_add(emoticons, smile); + msn_emoticon_destroy(smile); + smileys = g_slist_delete_link(smileys, smileys); + } + + if (emoticons) { + msn_send_emoticons(swboard, emoticons); + g_string_free(emoticons, TRUE); + } + msn_switchboard_send_msg(swboard, msg, TRUE); } } diff -r 0998ae2e4838 -r b30f54a72031 libpurple/protocols/msn/object.c --- a/libpurple/protocols/msn/object.c Mon Mar 31 03:38:50 2008 +0000 +++ b/libpurple/protocols/msn/object.c Mon Mar 31 21:04:51 2008 +0000 @@ -23,6 +23,10 @@ */ #include "object.h" #include "debug.h" +/* Sha1 stuff */ +#include "cipher.h" +/* Base64 stuff */ +#include "util.h" #define GET_STRING_TAG(field, id) \ if ((tag = strstr(str, id "=\"")) != NULL) \ @@ -104,6 +108,74 @@ return obj; } +MsnObject* +msn_object_new_from_image(PurpleStoredImage *img, const char *location, + const char *creator, MsnObjectType type) +{ + MsnObject *msnobj; + + PurpleCipherContext *ctx; + char *buf; + gconstpointer data; + size_t size; + char *base64; + unsigned char digest[20]; + + msnobj = NULL; + + if (img == NULL) + return msnobj; + + size = purple_imgstore_get_size(img); + data = purple_imgstore_get_data(img); + + /* New object */ + msnobj = msn_object_new(); + msn_object_set_local(msnobj); + msn_object_set_type(msnobj, type); + msn_object_set_location(msnobj, location); + msn_object_set_creator(msnobj, creator); + + msn_object_set_image(msnobj, img); + + /* Compute the SHA1D field. */ + memset(digest, 0, sizeof(digest)); + + ctx = purple_cipher_context_new_by_name("sha1", NULL); + purple_cipher_context_append(ctx, data, size); + purple_cipher_context_digest(ctx, sizeof(digest), digest, NULL); + + base64 = purple_base64_encode(digest, sizeof(digest)); + msn_object_set_sha1d(msnobj, base64); + g_free(base64); + + msn_object_set_size(msnobj, size); + + /* Compute the SHA1C field. */ + buf = g_strdup_printf( + "Creator%sSize%dType%dLocation%sFriendly%sSHA1D%s", + msn_object_get_creator(msnobj), + msn_object_get_size(msnobj), + msn_object_get_type(msnobj), + msn_object_get_location(msnobj), + msn_object_get_friendly(msnobj), + msn_object_get_sha1d(msnobj)); + + memset(digest, 0, sizeof(digest)); + + purple_cipher_context_reset(ctx, NULL); + purple_cipher_context_append(ctx, (const guchar *)buf, strlen(buf)); + purple_cipher_context_digest(ctx, sizeof(digest), digest, NULL); + purple_cipher_context_destroy(ctx); + g_free(buf); + + base64 = purple_base64_encode(digest, sizeof(digest)); + msn_object_set_sha1c(msnobj, base64); + g_free(base64); + + return msnobj; +} + void msn_object_destroy(MsnObject *obj) { diff -r 0998ae2e4838 -r b30f54a72031 libpurple/protocols/msn/object.h --- a/libpurple/protocols/msn/object.h Mon Mar 31 03:38:50 2008 +0000 +++ b/libpurple/protocols/msn/object.h Mon Mar 31 21:04:51 2008 +0000 @@ -71,6 +71,19 @@ MsnObject *msn_object_new_from_string(const char *str); /** + * Creates a MsnObject structure from a stored image + * + * @param img The image associated to object + * @param location The object location as stored in MsnObject + * @param creator The creator of the object + * @param type The type of the object + * + * @return A new MsnObject structure + */ +MsnObject *msn_object_new_from_image(PurpleStoredImage *img, + const char *location, const char *creator, MsnObjectType type); + +/** * Destroys an MsnObject structure. * * @param obj The object structure. diff -r 0998ae2e4838 -r b30f54a72031 libpurple/protocols/msn/slp.c --- a/libpurple/protocols/msn/slp.c Mon Mar 31 03:38:50 2008 +0000 +++ b/libpurple/protocols/msn/slp.c Mon Mar 31 21:04:51 2008 +0000 @@ -31,6 +31,8 @@ #include "user.h" #include "switchboard.h" +#include "smiley.h" + /* ms to delay between sending buddy icon requests to the server. */ #define BUDDY_ICON_DELAY 20000 /*debug SLP*/ @@ -278,23 +280,32 @@ type = msn_object_get_type(obj); g_free(msnobj_data); - if (!(type == MSN_OBJECT_USERTILE)) + if ((type != MSN_OBJECT_USERTILE) && (type != MSN_OBJECT_EMOTICON)) { purple_debug_error("msn", "Wrong object?\n"); msn_object_destroy(obj); g_return_if_reached(); } - img = msn_object_get_image(obj); + if (type == MSN_OBJECT_EMOTICON) { + char *path; + path = g_build_filename(purple_smileys_get_storing_dir(), + obj->location, NULL); + img = purple_imgstore_new_from_file(path); + g_free(path); + } else { + img = msn_object_get_image(obj); + if (img) + purple_imgstore_ref(img); + } + msn_object_destroy(obj); + if (img == NULL) { purple_debug_error("msn", "Wrong object.\n"); - msn_object_destroy(obj); g_return_if_reached(); } - msn_object_destroy(obj); - slpsession = msn_slplink_find_slp_session(slplink, slpcall->session_id); @@ -319,6 +330,7 @@ #endif msn_slpmsg_set_image(slpmsg, img); msn_slplink_queue_slpmsg(slplink, slpmsg); + purple_imgstore_unref(img); } else if (!strcmp(euf_guid, "5D3E02AB-6190-11D3-BBBB-00C04F795683")) { diff -r 0998ae2e4838 -r b30f54a72031 libpurple/protocols/msn/user.c --- a/libpurple/protocols/msn/user.c Mon Mar 31 03:38:50 2008 +0000 +++ b/libpurple/protocols/msn/user.c Mon Mar 31 21:04:51 2008 +0000 @@ -246,69 +246,17 @@ void msn_user_set_buddy_icon(MsnUser *user, PurpleStoredImage *img) { - MsnObject *msnobj = msn_user_get_object(user); + MsnObject *msnobj; g_return_if_fail(user != NULL); - if (img == NULL) - msn_user_set_object(user, NULL); - else - { - PurpleCipherContext *ctx; - char *buf; - gconstpointer data = purple_imgstore_get_data(img); - size_t size = purple_imgstore_get_size(img); - char *base64; - unsigned char digest[20]; - - if (msnobj == NULL) - { - msnobj = msn_object_new(); - msn_object_set_local(msnobj); - msn_object_set_type(msnobj, MSN_OBJECT_USERTILE); - msn_object_set_location(msnobj, "TFR2C2.tmp"); - msn_object_set_creator(msnobj, msn_user_get_passport(user)); - - msn_user_set_object(user, msnobj); - } - - msn_object_set_image(msnobj, img); - - /* Compute the SHA1D field. */ - memset(digest, 0, sizeof(digest)); + msnobj = msn_object_new_from_image(img, "TFR2C2.tmp", + user->passport, MSN_OBJECT_USERTILE); - ctx = purple_cipher_context_new_by_name("sha1", NULL); - purple_cipher_context_append(ctx, data, size); - purple_cipher_context_digest(ctx, sizeof(digest), digest, NULL); - - base64 = purple_base64_encode(digest, sizeof(digest)); - msn_object_set_sha1d(msnobj, base64); - g_free(base64); - - msn_object_set_size(msnobj, size); + if (!msnobj) + purple_debug_error("msn", "Unable to open buddy icon from %s!\n", user->passport); - /* Compute the SHA1C field. */ - buf = g_strdup_printf( - "Creator%sSize%dType%dLocation%sFriendly%sSHA1D%s", - msn_object_get_creator(msnobj), - msn_object_get_size(msnobj), - msn_object_get_type(msnobj), - msn_object_get_location(msnobj), - msn_object_get_friendly(msnobj), - msn_object_get_sha1d(msnobj)); - - memset(digest, 0, sizeof(digest)); - - purple_cipher_context_reset(ctx, NULL); - purple_cipher_context_append(ctx, (const guchar *)buf, strlen(buf)); - purple_cipher_context_digest(ctx, sizeof(digest), digest, NULL); - purple_cipher_context_destroy(ctx); - g_free(buf); - - base64 = purple_base64_encode(digest, sizeof(digest)); - msn_object_set_sha1c(msnobj, base64); - g_free(base64); - } + msn_user_set_object(user, msnobj); } /*add group id to User object*/ diff -r 0998ae2e4838 -r b30f54a72031 libpurple/smiley.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/smiley.c Mon Mar 31 21:04:51 2008 +0000 @@ -0,0 +1,782 @@ +/** + * @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 */ +/**************************************************************************/ + +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. */ +}; + +static GHashTable *smiley_shortcut_index = NULL; /* shortcut (char *) => smiley (PurpleSmiley*) */ +static GHashTable *smiley_checksum_index = NULL; /* checksum (char *) => smiley (PurpleSmiley*) */ + +static guint save_timer = 0; +static gboolean smileys_loaded = FALSE; +static char *smileys_dir = NULL; + +#define SMILEYS_DEFAULT_FOLDER "custom_smiley" +#define SMILEYS_LOG_ID "smileys" + +#define XML_FILE_NAME "smileys.xml" + +#define XML_ROOT_TAG "smileys" +#define XML_PROFILE_TAG "profile" +#define XML_PROFILE_NAME_ATTRIB_TAG "name" +#define XML_ACCOUNT_TAG "account" +#define XML_ACCOUNT_USERID_ATTRIB_TAG "userid" +#define XML_SMILEY_SET_TAG "smiley_set" +#define XML_SMILEY_TAG "smiley" +#define XML_SHORTCUT_ATTRIB_TAG "shortcut" +#define XML_CHECKSUM_ATRIB_TAG "checksum" +#define XML_FILENAME_ATRIB_TAG "filename" + + +/****************************************************************************** + * XML descriptor file layout * + ****************************************************************************** + * + * Althought we are creating the profile XML structure here, now we + * won't handle it. + * So, we just add one profile named "default" that has no associated + * account elements, and have only the smiley_set that will contain + * all existent custom smiley. + * + * It's our "Highlander Profile" :-) + * + ****************************************************************************** + * + * + * + * + * + * + * + * + * + * + * + * + *****************************************************************************/ + + +/********************************************************************* + * Forward declarations * + *********************************************************************/ + +static gboolean read_smiley_file(const char *path, guchar **data, size_t *len); + +static char *get_file_full_path(const char *filename); + +static PurpleSmiley *purple_smiley_create(const char *shortcut); + +static PurpleSmiley *purple_smiley_load_file(const char *shortcut, const char *checksum, + const char *filename); + +static void +purple_smiley_set_data_impl(PurpleSmiley *smiley, guchar *smiley_data, + size_t smiley_data_len, const char *filename); + + +/********************************************************************* + * Writing to disk * + *********************************************************************/ + +static xmlnode * +smiley_to_xmlnode(PurpleSmiley *smiley) +{ + xmlnode *smiley_node = NULL; + + smiley_node = xmlnode_new(XML_SMILEY_TAG); + + if (!smiley_node) + return NULL; + + xmlnode_set_attrib(smiley_node, XML_SHORTCUT_ATTRIB_TAG, + smiley->shortcut); + + xmlnode_set_attrib(smiley_node, XML_CHECKSUM_ATRIB_TAG, + smiley->checksum); + + xmlnode_set_attrib(smiley_node, XML_FILENAME_ATRIB_TAG, + purple_imgstore_get_filename(smiley->img)); + + return smiley_node; +} + +static void +add_smiley_to_main_node(gpointer key, gpointer value, gpointer user_data) +{ + xmlnode *child_node; + + child_node = smiley_to_xmlnode(value); + xmlnode_insert_child((xmlnode*)user_data, child_node); +} + +static xmlnode * +smileys_to_xmlnode() +{ + xmlnode *root_node, *profile_node, *smileyset_node; + + root_node = xmlnode_new(XML_ROOT_TAG); + xmlnode_set_attrib(root_node, "version", "1.0"); + + /* See the top comment's above to understand why initial tag elements + * are not being considered by now. */ + profile_node = xmlnode_new(XML_PROFILE_TAG); + if (profile_node) { + xmlnode_set_attrib(profile_node, XML_PROFILE_NAME_ATTRIB_TAG, "Default"); + xmlnode_insert_child(root_node, profile_node); + + smileyset_node = xmlnode_new(XML_SMILEY_SET_TAG); + if (smileyset_node) { + xmlnode_insert_child(profile_node, smileyset_node); + g_hash_table_foreach(smiley_shortcut_index, add_smiley_to_main_node, smileyset_node); + } + } + + return root_node; +} + +static void +sync_smileys() +{ + xmlnode *root_node; + char *data; + + if (!smileys_loaded) { + purple_debug_error(SMILEYS_LOG_ID, "Attempted to save smileys before it " + "was read!\n"); + return; + } + + root_node = smileys_to_xmlnode(); + data = xmlnode_to_formatted_str(root_node, NULL); + purple_util_write_data_to_file(XML_FILE_NAME, data, -1); + + g_free(data); + xmlnode_free(root_node); +} + +static gboolean +save_smileys_cb(gpointer data) +{ + sync_smileys(); + save_timer = 0; + return FALSE; +} + +static void +purple_smileys_save() +{ + if (save_timer == 0) + save_timer = purple_timeout_add_seconds(5, save_smileys_cb, NULL); +} + + +/********************************************************************* + * Reading from disk * + *********************************************************************/ + +static PurpleSmiley * +parse_smiley(xmlnode *smiley_node) +{ + PurpleSmiley *smiley; + const char *shortcut = NULL; + const char *checksum = NULL; + const char *filename = NULL; + + shortcut = xmlnode_get_attrib(smiley_node, XML_SHORTCUT_ATTRIB_TAG); + checksum = xmlnode_get_attrib(smiley_node, XML_CHECKSUM_ATRIB_TAG); + filename = xmlnode_get_attrib(smiley_node, XML_FILENAME_ATRIB_TAG); + + if ((shortcut == NULL) || (checksum == NULL) || (filename == NULL)) + return NULL; + + smiley = purple_smiley_load_file(shortcut, checksum, filename); + + return smiley; +} + +static void +purple_smileys_load() +{ + xmlnode *root_node, *profile_node; + xmlnode *smileyset_node = NULL; + xmlnode *smiley_node; + + smileys_loaded = TRUE; + + root_node = purple_util_read_xml_from_file(XML_FILE_NAME, + _(SMILEYS_LOG_ID)); + + if (root_node == NULL) + return; + + /* See the top comment's above to understand why initial tag elements + * are not being considered by now. */ + profile_node = xmlnode_get_child(root_node, XML_PROFILE_TAG); + if (profile_node) + smileyset_node = xmlnode_get_child(profile_node, XML_SMILEY_SET_TAG); + + if (smileyset_node) { + smiley_node = xmlnode_get_child(smileyset_node, XML_SMILEY_TAG); + for (; smiley_node != NULL; + smiley_node = xmlnode_get_next_twin(smiley_node)) { + PurpleSmiley *smiley; + + smiley = parse_smiley(smiley_node); + + 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; +} + +static PurpleSmiley * +purple_smiley_load_file(const char *shortcut, const char *checksum, const char *filename) +{ + PurpleSmiley *smiley = NULL; + guchar *smiley_data; + size_t smiley_data_len; + char *fullpath = NULL; + + g_return_val_if_fail(shortcut != NULL, NULL); + g_return_val_if_fail(checksum != NULL, NULL); + g_return_val_if_fail(filename != NULL, NULL); + + fullpath = get_file_full_path(filename); + if (!fullpath) + return NULL; + + smiley = purple_smiley_create(shortcut); + if (!smiley) { + g_free(fullpath); + return NULL; + } + + smiley->checksum = g_strdup(checksum); + + if (read_smiley_file(fullpath, &smiley_data, &smiley_data_len)) + purple_smiley_set_data_impl(smiley, smiley_data, + smiley_data_len, filename); + else + purple_smiley_delete(smiley); + + g_free(fullpath); + + return smiley; +} + +static void +purple_smiley_data_store(PurpleStoredImage *stored_img) +{ + const char *dirname; + char *path; + FILE *file = NULL; + + g_return_if_fail(stored_img != NULL); + + if (!smileys_loaded) + return; + + dirname = purple_smileys_get_storing_dir(); + path = g_build_filename(dirname, purple_imgstore_get_filename(stored_img), NULL); + + if (!g_file_test(dirname, G_FILE_TEST_IS_DIR)) { + purple_debug_info(SMILEYS_LOG_ID, "Creating smileys directory.\n"); + + if (g_mkdir(dirname, S_IRUSR | S_IWUSR | S_IXUSR) < 0) { + purple_debug_error(SMILEYS_LOG_ID, + "Unable to create directory %s: %s\n", + dirname, g_strerror(errno)); + } + } + + if ((file = g_fopen(path, "wb")) != NULL) { + if (!fwrite(purple_imgstore_get_data(stored_img), + purple_imgstore_get_size(stored_img), 1, file)) { + purple_debug_error(SMILEYS_LOG_ID, "Error writing %s: %s\n", + path, g_strerror(errno)); + } else { + purple_debug_info(SMILEYS_LOG_ID, "Wrote cache file: %s\n", path); + } + + fclose(file); + } else { + purple_debug_error(SMILEYS_LOG_ID, "Unable to create file %s: %s\n", + path, g_strerror(errno)); + g_free(path); + + return; + } + + g_free(path); +} + +static void +purple_smiley_data_unstore(const char *filename) +{ + const char *dirname; + char *path; + + g_return_if_fail(filename != NULL); + + dirname = purple_smileys_get_storing_dir(); + path = g_build_filename(dirname, filename, NULL); + + if (g_file_test(path, G_FILE_TEST_EXISTS)) { + if (g_unlink(path)) + purple_debug_error(SMILEYS_LOG_ID, "Failed to delete %s: %s\n", + path, g_strerror(errno)); + else + purple_debug_info(SMILEYS_LOG_ID, "Deleted cache file: %s\n", path); + } + + g_free(path); +} + +static gboolean +read_smiley_file(const char *path, guchar **data, size_t *len) +{ + GError *err = NULL; + + if (!g_file_get_contents(path, (gchar **)data, len, &err)) { + purple_debug_error(SMILEYS_LOG_ID, "Error reading %s: %s\n", + path, err->message); + g_error_free(err); + + return FALSE; + } + + return TRUE; +} + +static PurpleStoredImage * +purple_smiley_data_new(guchar *smiley_data, size_t smiley_data_len) +{ + char *filename; + PurpleStoredImage *stored_img; + + g_return_val_if_fail(smiley_data != NULL, NULL); + g_return_val_if_fail(smiley_data_len > 0, NULL); + + filename = purple_util_get_image_filename(smiley_data, smiley_data_len); + + if (filename == NULL) { + g_free(smiley_data); + return NULL; + } + + stored_img = purple_imgstore_add(smiley_data, smiley_data_len, filename); + + g_free(filename); + + return stored_img; +} + +static void +purple_smiley_set_data_impl(PurpleSmiley *smiley, guchar *smiley_data, + size_t smiley_data_len, const char *filename) +{ + PurpleStoredImage *old_img; + 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) +{ + g_return_if_fail(smiley != 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) +{ + g_return_val_if_fail(smiley != NULL, FALSE); + g_return_val_if_fail(shortcut != NULL, FALSE); + + /* Check out whether the new shortcut is already being used. */ + if (g_hash_table_lookup(smiley_shortcut_index, shortcut)) + return FALSE; + + /* Remove the old shortcut. */ + g_hash_table_remove(smiley_shortcut_index, smiley->shortcut); + + /* Insert the new shortcut. */ + g_hash_table_insert(smiley_shortcut_index, g_strdup(shortcut), smiley); + + g_free(smiley->shortcut); + smiley->shortcut = g_strdup(shortcut); + + purple_smileys_save(); + + return TRUE; +} + +void +purple_smiley_set_data(PurpleSmiley *smiley, guchar *smiley_data, + size_t smiley_data_len, gboolean keepfilename) +{ + g_return_if_fail(smiley != NULL); + g_return_if_fail(smiley_data != NULL); + g_return_if_fail(smiley_data_len > 0); + + /* Remove the previous entry */ + g_hash_table_remove(smiley_checksum_index, smiley->checksum); + + /* Update the file data. This also updates the checksum. */ + if ((keepfilename) && (smiley->img) && + (purple_imgstore_get_filename(smiley->img))) + purple_smiley_set_data_impl(smiley, smiley_data, + smiley_data_len, + purple_imgstore_get_filename(smiley->img)); + else + purple_smiley_set_data_impl(smiley, smiley_data, + smiley_data_len, NULL); + + /* Reinsert the index item. */ + g_hash_table_insert(smiley_checksum_index, g_strdup(smiley->checksum), smiley); + + purple_smileys_save(); +} + +PurpleStoredImage * +purple_smiley_get_stored_image(const PurpleSmiley *smiley) +{ + return purple_imgstore_ref(smiley->img); +} + +const char *purple_smiley_get_shortcut(const PurpleSmiley *smiley) +{ + g_return_val_if_fail(smiley != NULL, NULL); + + return smiley->shortcut; +} + +const char * +purple_smiley_get_checksum(const PurpleSmiley *smiley) +{ + g_return_val_if_fail(smiley != NULL, NULL); + + return smiley->checksum; +} + +gconstpointer +purple_smiley_get_data(const PurpleSmiley *smiley, size_t *len) +{ + g_return_val_if_fail(smiley != NULL, NULL); + + if (smiley->img) { + if (len != NULL) + *len = purple_imgstore_get_size(smiley->img); + + return purple_imgstore_get_data(smiley->img); + } + + return NULL; +} + +const char * +purple_smiley_get_extension(const PurpleSmiley *smiley) +{ + if (smiley->img != NULL) + return purple_imgstore_get_extension(smiley->img); + + return NULL; +} + +char *purple_smiley_get_full_path(PurpleSmiley *smiley) +{ + g_return_val_if_fail(smiley != NULL, NULL); + + if (smiley->img == NULL) + return NULL; + + return get_file_full_path(purple_imgstore_get_filename(smiley->img)); +} + +static void add_smiley_to_list(gpointer key, gpointer value, gpointer user_data) +{ + GList** returninglist = (GList**)user_data; + + *returninglist = g_list_append(*returninglist, value); +} + +GList * +purple_smileys_get_all(void) +{ + GList *returninglist = NULL; + + g_hash_table_foreach(smiley_shortcut_index, add_smiley_to_list, &returninglist); + + return returninglist; +} + +void +purple_smileys_add(PurpleSmiley *smiley) +{ + g_return_if_fail(smiley != NULL); + + if (!g_hash_table_lookup(smiley_shortcut_index, smiley->shortcut)) { + g_hash_table_insert(smiley_shortcut_index, g_strdup(smiley->shortcut), smiley); + g_hash_table_insert(smiley_checksum_index, g_strdup(smiley->checksum), smiley); + + purple_smileys_save(); + } +} + +void +purple_smileys_remove(PurpleSmiley *smiley) +{ + g_return_if_fail(smiley != NULL); + + if (g_hash_table_lookup(smiley_shortcut_index, smiley->shortcut)) { + g_hash_table_remove(smiley_shortcut_index, smiley->shortcut); + g_hash_table_remove(smiley_checksum_index, smiley->checksum); + + purple_smileys_save(); + } +} + +PurpleSmiley * +purple_smileys_find_by_shortcut(const char *shortcut) +{ + g_return_val_if_fail(shortcut != NULL, NULL); + + return g_hash_table_lookup(smiley_shortcut_index, shortcut); +} + +PurpleSmiley * +purple_smileys_find_by_checksum(const char *checksum) +{ + g_return_val_if_fail(checksum != NULL, NULL); + + return g_hash_table_lookup(smiley_checksum_index, checksum); +} + +const char * +purple_smileys_get_storing_dir(void) +{ + return smileys_dir; +} + +void +purple_smileys_init() +{ + smiley_shortcut_index = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL); + smiley_checksum_index = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL); + + smileys_dir = g_build_filename(purple_user_dir(), SMILEYS_DEFAULT_FOLDER, NULL); + + purple_smileys_load(); +} + +void +purple_smileys_uninit() +{ + if (save_timer != 0) { + purple_timeout_remove(save_timer); + save_timer = 0; + sync_smileys(); + } + + g_hash_table_destroy(smiley_shortcut_index); + g_hash_table_destroy(smiley_checksum_index); + g_free(smileys_dir); +} + diff -r 0998ae2e4838 -r b30f54a72031 libpurple/smiley.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/smiley.h Mon Mar 31 21:04:51 2008 +0000 @@ -0,0 +1,287 @@ +/** + * @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 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. The caller should free the list. + * + * @return 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 0998ae2e4838 -r b30f54a72031 libpurple/util.c --- a/libpurple/util.c Mon Mar 31 03:38:50 2008 +0000 +++ b/libpurple/util.c Mon Mar 31 21:04:51 2008 +0000 @@ -945,7 +945,8 @@ else if(IS_ENTITY("'")) pln = "\'"; else if(*(text+1) == '#' && - (sscanf(text, "&#%u%1[;]", £, temp) == 2 || sscanf(text, "&#x%x%1[;]", £, temp) == 2) && + (sscanf(text, "&#%u%1[;]", £, temp) == 2 || + sscanf(text, "&#x%x%1[;]", £, temp) == 2) && pound != 0) { static char buf[7]; int buflen = g_unichar_to_utf8((gunichar)pound, buf); @@ -2889,7 +2890,7 @@ } char * -purple_util_get_image_filename(gconstpointer image_data, size_t image_len) +purple_util_get_image_checksum(gconstpointer image_data, size_t image_len) { PurpleCipherContext *context; gchar digest[41]; @@ -2910,9 +2911,18 @@ } purple_cipher_context_destroy(context); + return g_strdup(digest); +} + +char * +purple_util_get_image_filename(gconstpointer image_data, size_t image_len) +{ /* Return the filename */ - return g_strdup_printf("%s.%s", digest, + char *checksum = purple_util_get_image_checksum(image_data, image_len); + char *filename = g_strdup_printf("%s.%s", checksum, purple_util_get_image_extension(image_data, image_len)); + g_free(checksum); + return filename; } gboolean diff -r 0998ae2e4838 -r b30f54a72031 libpurple/util.h --- a/libpurple/util.h Mon Mar 31 03:38:50 2008 +0000 +++ b/libpurple/util.h Mon Mar 31 21:04:51 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 0998ae2e4838 -r b30f54a72031 pidgin/Makefile.am --- a/pidgin/Makefile.am Mon Mar 31 03:38:50 2008 +0000 +++ b/pidgin/Makefile.am Mon Mar 31 21:04:51 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 0998ae2e4838 -r b30f54a72031 pidgin/Makefile.mingw --- a/pidgin/Makefile.mingw Mon Mar 31 03:38:50 2008 +0000 +++ b/pidgin/Makefile.mingw Mon Mar 31 21:04:51 2008 +0000 @@ -86,6 +86,7 @@ gtkroomlist.c \ gtksavedstatuses.c \ gtkscrollbook.c \ + gtksmiley.c \ gtksound.c \ gtksourceiter.c \ gtksourceundomanager.c \ diff -r 0998ae2e4838 -r b30f54a72031 pidgin/gtkblist.c --- a/pidgin/gtkblist.c Mon Mar 31 03:38:50 2008 +0000 +++ b/pidgin/gtkblist.c Mon Mar 31 21:04:51 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" @@ -3101,6 +3102,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 0998ae2e4838 -r b30f54a72031 pidgin/gtkconv.c --- a/pidgin/gtkconv.c Mon Mar 31 03:38:50 2008 +0000 +++ b/pidgin/gtkconv.c Mon Mar 31 21:04:51 2008 +0000 @@ -157,8 +157,6 @@ static void update_typing_message(PidginConversation *gtkconv, const char *message); static const char *item_factory_translate_func (const char *path, gpointer func_data); gboolean pidgin_conv_has_focus(PurpleConversation *conv); -static void pidgin_conv_custom_smiley_allocated(GdkPixbufLoader *loader, gpointer user_data); -static void pidgin_conv_custom_smiley_closed(GdkPixbufLoader *loader, gpointer user_data); static GdkColor* generate_nick_colors(guint *numcolors, GdkColor background); static gboolean color_is_visible(GdkColor foreground, GdkColor background, int color_contrast, int brightness_contrast); static void pidgin_conv_update_fields(PurpleConversation *conv, PidginConvFields fields); @@ -5032,6 +5030,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 @@ -5338,8 +5342,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); @@ -5498,14 +5500,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 */ @@ -5751,8 +5747,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(), @@ -5980,119 +5975,24 @@ return FALSE; } -static void pidgin_conv_custom_smiley_allocated(GdkPixbufLoader *loader, gpointer user_data) -{ - GtkIMHtmlSmiley *smiley; - - smiley = (GtkIMHtmlSmiley *)user_data; - smiley->icon = gdk_pixbuf_loader_get_animation(loader); - - if (smiley->icon) - g_object_ref(G_OBJECT(smiley->icon)); -#ifdef DEBUG_CUSTOM_SMILEY - purple_debug_info("custom-smiley", "pidgin_conv_custom_smiley_allocated(): got GdkPixbufAnimation %p for smiley '%s'\n", smiley->icon, smiley->smile); -#endif -} - -static void pidgin_conv_custom_smiley_closed(GdkPixbufLoader *loader, gpointer user_data) -{ - GtkIMHtmlSmiley *smiley; - GtkWidget *icon = NULL; - GtkTextChildAnchor *anchor = NULL; - GSList *current = NULL; - - smiley = (GtkIMHtmlSmiley *)user_data; - if (!smiley->imhtml) { -#ifdef DEBUG_CUSTOM_SMILEY - purple_debug_error("custom-smiley", "pidgin_conv_custom_smiley_closed(): orphan smiley found: %p\n", smiley); -#endif - g_object_unref(G_OBJECT(loader)); - smiley->loader = NULL; - return; - } - - for (current = smiley->anchors; current; current = g_slist_next(current)) { - - icon = gtk_image_new_from_animation(smiley->icon); - -#ifdef DEBUG_CUSTOM_SMILEY - purple_debug_info("custom-smiley", "pidgin_conv_custom_smiley_closed(): got GtkImage %p from GtkPixbufAnimation %p for smiley '%s'\n", - icon, smiley->icon, smiley->smile); -#endif - if (icon) { - GList *wids; - gtk_widget_show(icon); - - anchor = GTK_TEXT_CHILD_ANCHOR(current->data); - wids = gtk_text_child_anchor_get_widgets(anchor); - - g_object_set_data_full(G_OBJECT(anchor), "gtkimhtml_plaintext", purple_unescape_html(smiley->smile), g_free); - g_object_set_data_full(G_OBJECT(anchor), "gtkimhtml_htmltext", g_strdup(smiley->smile), g_free); - - if (smiley->imhtml) { - if (wids) { - GList *children = gtk_container_get_children(GTK_CONTAINER(wids->data)); - g_list_foreach(children, (GFunc)gtk_widget_destroy, NULL); - g_list_free(children); - gtk_container_add(GTK_CONTAINER(wids->data), icon); - } else - gtk_text_view_add_child_at_anchor(GTK_TEXT_VIEW(smiley->imhtml), icon, anchor); - } - g_list_free(wids); - } - - } - - g_slist_free(smiley->anchors); - smiley->anchors = NULL; - - g_object_unref(G_OBJECT(loader)); - smiley->loader = NULL; -} - static gboolean add_custom_smiley_for_imhtml(GtkIMHtml *imhtml, const char *sml, const char *smile) { GtkIMHtmlSmiley *smiley; - GdkPixbufLoader *loader; smiley = gtk_imhtml_smiley_get(imhtml, sml, smile); if (smiley) { - if (!(smiley->flags & GTK_IMHTML_SMILEY_CUSTOM)) { return FALSE; } - - /* Close the old GdkPixbufAnimation, then create a new one for - * the smiley we are about to receive */ - g_object_unref(G_OBJECT(smiley->icon)); - - /* XXX: Is it necessary to _unref the loader first? */ - smiley->loader = gdk_pixbuf_loader_new(); - smiley->icon = NULL; - - g_signal_connect(smiley->loader, "area_prepared", G_CALLBACK(pidgin_conv_custom_smiley_allocated), smiley); - g_signal_connect(smiley->loader, "closed", G_CALLBACK(pidgin_conv_custom_smiley_closed), smiley); - + gtk_imhtml_smiley_reload(smiley); return TRUE; } - loader = gdk_pixbuf_loader_new(); - - /* this is wrong, this file ought not call g_new on GtkIMHtmlSmiley */ - /* Let gtk_imhtml have a gtk_imhtml_smiley_new function, and let - GtkIMHtmlSmiley by opaque */ - smiley = g_new0(GtkIMHtmlSmiley, 1); - smiley->file = NULL; - smiley->smile = g_strdup(smile); - smiley->loader = loader; - smiley->flags = smiley->flags | GTK_IMHTML_SMILEY_CUSTOM; - - g_signal_connect(smiley->loader, "area_prepared", G_CALLBACK(pidgin_conv_custom_smiley_allocated), smiley); - g_signal_connect(smiley->loader, "closed", G_CALLBACK(pidgin_conv_custom_smiley_closed), smiley); - + smiley = gtk_imhtml_smiley_create(NULL, smile, FALSE, GTK_IMHTML_SMILEY_CUSTOM); gtk_imhtml_associate_smiley(imhtml, sml, smiley); + g_signal_connect_swapped(imhtml, "destroy", G_CALLBACK(gtk_imhtml_smiley_destroy), smiley); return TRUE; } diff -r 0998ae2e4838 -r b30f54a72031 pidgin/gtkimhtml.c --- a/pidgin/gtkimhtml.c Mon Mar 31 03:38:50 2008 +0000 +++ b/pidgin/gtkimhtml.c Mon Mar 31 21:04:51 2008 +0000 @@ -5200,4 +5200,123 @@ gtk_imhtml_set_format_functions(imhtml, buttons); } - +/******* + * GtkIMHtmlSmiley functions + *******/ +static void gtk_custom_smiley_allocated(GdkPixbufLoader *loader, gpointer user_data) +{ + GtkIMHtmlSmiley *smiley; + + smiley = (GtkIMHtmlSmiley *)user_data; + smiley->icon = gdk_pixbuf_loader_get_animation(loader); + + if (smiley->icon) + g_object_ref(G_OBJECT(smiley->icon)); +#ifdef DEBUG_CUSTOM_SMILEY + purple_debug_info("custom-smiley", "gtk_custom_smiley_allocated(): got GdkPixbufAnimation %p for smiley '%s'\n", smiley->icon, smiley->smile); +#endif +} + +static void gtk_custom_smiley_closed(GdkPixbufLoader *loader, gpointer user_data) +{ + GtkIMHtmlSmiley *smiley; + GtkWidget *icon = NULL; + GtkTextChildAnchor *anchor = NULL; + GSList *current = NULL; + + smiley = (GtkIMHtmlSmiley *)user_data; + if (!smiley->imhtml) { +#ifdef DEBUG_CUSTOM_SMILEY + purple_debug_error("custom-smiley", "gtk_custom_smiley_closed(): orphan smiley found: %p\n", smiley); +#endif + g_object_unref(G_OBJECT(loader)); + smiley->loader = NULL; + return; + } + + for (current = smiley->anchors; current; current = g_slist_next(current)) { + + icon = gtk_image_new_from_animation(smiley->icon); + +#ifdef DEBUG_CUSTOM_SMILEY + purple_debug_info("custom-smiley", "gtk_custom_smiley_closed(): got GtkImage %p from GtkPixbufAnimation %p for smiley '%s'\n", + icon, smiley->icon, smiley->smile); +#endif + if (icon) { + GList *wids; + gtk_widget_show(icon); + + anchor = GTK_TEXT_CHILD_ANCHOR(current->data); + wids = gtk_text_child_anchor_get_widgets(anchor); + + g_object_set_data_full(G_OBJECT(anchor), "gtkimhtml_plaintext", purple_unescape_html(smiley->smile), g_free); + g_object_set_data_full(G_OBJECT(anchor), "gtkimhtml_htmltext", g_strdup(smiley->smile), g_free); + + if (smiley->imhtml) { + if (wids) { + GList *children = gtk_container_get_children(GTK_CONTAINER(wids->data)); + g_list_foreach(children, (GFunc)gtk_widget_destroy, NULL); + g_list_free(children); + gtk_container_add(GTK_CONTAINER(wids->data), icon); + } else + gtk_text_view_add_child_at_anchor(GTK_TEXT_VIEW(smiley->imhtml), icon, anchor); + } + g_list_free(wids); + } + + } + + g_slist_free(smiley->anchors); + smiley->anchors = NULL; + + g_object_unref(G_OBJECT(loader)); + smiley->loader = NULL; +} + +void +gtk_imhtml_smiley_reload(GtkIMHtmlSmiley *smiley) +{ + if (smiley->icon) + g_object_unref(smiley->icon); + if (smiley->loader) + g_object_unref(smiley->loader); /* XXX: does this crash? */ + + smiley->icon = NULL; + smiley->loader = NULL; + + if (smiley->file) { + /* We do not use the pixbuf loader for a smiley that can be loaded + * from a file. (e.g., local custom smileys) + */ + return; + } + + smiley->loader = gdk_pixbuf_loader_new(); + + g_signal_connect(smiley->loader, "area_prepared", G_CALLBACK(gtk_custom_smiley_allocated), smiley); + g_signal_connect(smiley->loader, "closed", G_CALLBACK(gtk_custom_smiley_closed), smiley); +} + +GtkIMHtmlSmiley *gtk_imhtml_smiley_create(const char *file, const char *shortcut, gboolean hide, + GtkIMHtmlSmileyFlags flags) +{ + GtkIMHtmlSmiley *smiley = g_new0(GtkIMHtmlSmiley, 1); + smiley->file = g_strdup(file); + smiley->smile = g_strdup(shortcut); + smiley->hidden = hide; + smiley->flags = flags; + gtk_imhtml_smiley_reload(smiley); + return smiley; +} + +void gtk_imhtml_smiley_destroy(GtkIMHtmlSmiley *smiley) +{ + g_free(smiley->smile); + g_free(smiley->file); + if (smiley->icon) + g_object_unref(smiley->icon); + if (smiley->loader) + g_object_unref(smiley->loader); + g_free(smiley); +} + diff -r 0998ae2e4838 -r b30f54a72031 pidgin/gtkimhtml.h --- a/pidgin/gtkimhtml.h Mon Mar 31 03:38:50 2008 +0000 +++ b/pidgin/gtkimhtml.h Mon Mar 31 21:04:51 2008 +0000 @@ -852,6 +852,12 @@ */ void gtk_imhtml_setup_entry(GtkIMHtml *imhtml, PurpleConnectionFlags flags); +GtkIMHtmlSmiley *gtk_imhtml_smiley_create(const char *file, const char *shortcut, gboolean hide, + GtkIMHtmlSmileyFlags flags); + +void gtk_imhtml_smiley_reload(GtkIMHtmlSmiley *smiley); + +void gtk_imhtml_smiley_destroy(GtkIMHtmlSmiley *smiley); /*@}*/ #ifdef __cplusplus diff -r 0998ae2e4838 -r b30f54a72031 pidgin/gtkimhtmltoolbar.c --- a/pidgin/gtkimhtmltoolbar.c Mon Mar 31 03:38:50 2008 +0000 +++ b/pidgin/gtkimhtmltoolbar.c Mon Mar 31 21:04:51 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 0998ae2e4838 -r b30f54a72031 pidgin/gtkmain.c --- a/pidgin/gtkmain.c Mon Mar 31 03:38:50 2008 +0000 +++ b/pidgin/gtkmain.c Mon Mar 31 21:04:51 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" @@ -314,6 +315,7 @@ pidgin_roomlist_init(); pidgin_log_init(); pidgin_docklet_init(); + pidgin_smileys_init(); } static GHashTable *ui_info = NULL; @@ -330,6 +332,7 @@ pidgin_plugins_save(); /* Uninit */ + pidgin_smileys_uninit(); pidgin_conversations_uninit(); pidgin_status_uninit(); pidgin_docklet_uninit(); diff -r 0998ae2e4838 -r b30f54a72031 pidgin/gtksmiley.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/pidgin/gtksmiley.c Mon Mar 31 21:04:51 2008 +0000 @@ -0,0 +1,556 @@ +/** + * @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); +} + +/* Perhaps this should be in gtkimhtml.c instead. -- sad */ +static GtkIMHtmlSmiley *smiley_purple_to_gtkimhtml(PurpleSmiley *smiley) +{ + GtkIMHtmlSmiley *gtksmiley; + gchar *filename; + const gchar *file; + + file = purple_imgstore_get_filename(purple_smiley_get_stored_image(smiley)); + + filename = g_build_filename(purple_smileys_get_storing_dir(),file, NULL); + + gtksmiley = gtk_imhtml_smiley_create(filename, purple_smiley_get_shortcut(smiley), + FALSE, GTK_IMHTML_SMILEY_CUSTOM); + g_free(filename); + + return gtksmiley; +} + +void pidgin_smiley_add_to_list(PurpleSmiley *smiley) +{ + GtkIMHtmlSmiley *gtksmiley; + + gtksmiley = smiley_purple_to_gtkimhtml(smiley); + add_gtkimhtml_to_list(gtksmiley); +} + +void pidgin_smiley_del_from_list(PurpleSmiley *smiley) +{ + GSList *list = NULL; + GtkIMHtmlSmiley *gtksmiley; + + if (gtk_smileys == NULL) + return; + + list = gtk_smileys; + + for (; list; list = list->next) { + gtksmiley = (GtkIMHtmlSmiley*)list->data; + + if (strcmp(gtksmiley->smile, purple_smiley_get_shortcut(smiley))) + continue; + + gtk_imhtml_smiley_destroy(gtksmiley); + break; + } + + if (list) + gtk_smileys = g_slist_delete_link(gtk_smileys, list); +} + +void pidgin_smileys_init(void) +{ + GList *smileys; + PurpleSmiley *smiley; + + if (gtk_smileys != NULL) + return; + + smileys = purple_smileys_get_all(); + + for (; smileys; smileys = g_list_delete_link(smileys, smileys)) { + smiley = (PurpleSmiley*)smileys->data; + + pidgin_smiley_add_to_list(smiley); + } +} + +void pidgin_smileys_uninit(void) +{ + GSList *list; + GtkIMHtmlSmiley *gtksmiley; + + list = gtk_smileys; + + if (list == NULL) + return; + + for (; list; list = g_slist_delete_link(list, list)) { + gtksmiley = (GtkIMHtmlSmiley*)list->data; + gtk_imhtml_smiley_destroy(gtksmiley); + } + + gtk_smileys = NULL; +} + +GSList *pidgin_smileys_get_all(void) +{ + return gtk_smileys; +} + +/****************************************************************************** + * Manager stuff + *****************************************************************************/ + +static void refresh_list(void); + +/****************************************************************************** + * The Add dialog + ******************************************************************************/ + +static void do_add(GtkWidget *widget, PidginSmiley *s) +{ + const gchar *entry; + PurpleSmiley *emoticon; + 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"), + s->filename ? _("Please provide a shortcut to associate with the smiley.") + : _("Please select an image for the smiley.")); + return; + } + + purple_debug_info("gtksmiley", "adding a new smiley\n"); + 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, purple_smiley_get_shortcut(smiley), + 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 = g_list_delete_link(list, list)) { + emoticon = (PurpleSmiley*)list->data; + + store_smiley_add(emoticon); + } +} + +static void smile_selected_cb(GtkTreeSelection *sel, SmileyManager *dialog) +{ + gint selected; + + selected = gtk_tree_selection_count_selected_rows(sel); + + gtk_dialog_set_response_sensitive(GTK_DIALOG(dialog->window), + GTK_RESPONSE_NO, selected > 0); +} + +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 0998ae2e4838 -r b30f54a72031 pidgin/gtksmiley.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/pidgin/gtksmiley.h Mon Mar 31 21:04:51 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 0998ae2e4838 -r b30f54a72031 pidgin/gtkthemes.c --- a/pidgin/gtkthemes.c Mon Mar 31 03:38:50 2008 +0000 +++ b/pidgin/gtkthemes.c Mon Mar 31 21:04:51 2008 +0000 @@ -31,6 +31,7 @@ #include "gtkconv.h" #include "gtkdialogs.h" #include "gtkimhtml.h" +#include "gtksmiley.h" #include "gtkthemes.h" GSList *smiley_themes = NULL; @@ -119,7 +120,7 @@ g_free(theme_dir); } -void pidgin_themes_smiley_themeize(GtkWidget *imhtml) +static void _pidgin_themes_smiley_themeize(GtkWidget *imhtml, gboolean custom) { struct smiley_list *list; if (!current_smiley_theme) @@ -134,10 +135,30 @@ gtk_imhtml_associate_smiley(GTK_IMHTML(imhtml), sml, icons->data); icons = icons->next; } + + if (custom == TRUE) { + icons = pidgin_smileys_get_all(); + + while (icons) { + gtk_imhtml_associate_smiley(GTK_IMHTML(imhtml), sml, icons->data); + icons = icons->next; + } + } + list = list->next; } } +void pidgin_themes_smiley_themeize(GtkWidget *imhtml) +{ + _pidgin_themes_smiley_themeize(imhtml, FALSE); +} + +void pidgin_themes_smiley_themeize_custom(GtkWidget *imhtml) +{ + _pidgin_themes_smiley_themeize(imhtml, TRUE); +} + static void pidgin_themes_destroy_smiley_theme_smileys(struct smiley_theme *theme) { @@ -274,7 +295,6 @@ } else if (load && list) { gboolean hidden = FALSE; char *sfile = NULL; - gboolean have_used_sfile = FALSE; if (*i == '!' && *(i + 1) == ' ') { hidden = TRUE; @@ -288,17 +308,12 @@ i++; l[li++] = *(i++); } + l[li] = 0; if (!sfile) { - l[li] = 0; sfile = g_build_filename(dirname, l, NULL); } else { - GtkIMHtmlSmiley *smiley = g_new0(GtkIMHtmlSmiley, 1); - l[li] = 0; - smiley->file = sfile; - smiley->smile = g_strdup(l); - smiley->hidden = hidden; + GtkIMHtmlSmiley *smiley = gtk_imhtml_smiley_create(sfile, l, hidden, 0); list->smileys = g_slist_prepend(list->smileys, smiley); - have_used_sfile = TRUE; } while (isspace(*i)) i++; @@ -306,8 +321,7 @@ } - if (!have_used_sfile) - g_free(sfile); + g_free(sfile); } } @@ -340,8 +354,9 @@ PurpleConversation *conv = cnv->data; if (PIDGIN_IS_PIDGIN_CONVERSATION(conv)) { + /* We want to see our custom smileys on our entry if we write the shortcut */ pidgin_themes_smiley_themeize(PIDGIN_CONVERSATION(conv)->imhtml); - pidgin_themes_smiley_themeize(PIDGIN_CONVERSATION(conv)->entry); + pidgin_themes_smiley_themeize_custom(PIDGIN_CONVERSATION(conv)->entry); } } } diff -r 0998ae2e4838 -r b30f54a72031 pidgin/gtkthemes.h --- a/pidgin/gtkthemes.h Mon Mar 31 03:38:50 2008 +0000 +++ b/pidgin/gtkthemes.h Mon Mar 31 21:04:51 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 0998ae2e4838 -r b30f54a72031 pidgin/gtkutils.c --- a/pidgin/gtkutils.c Mon Mar 31 03:38:50 2008 +0000 +++ b/pidgin/gtkutils.c Mon Mar 31 21:04:51 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); diff -r 0998ae2e4838 -r b30f54a72031 po/POTFILES.in --- a/po/POTFILES.in Mon Mar 31 03:38:50 2008 +0000 +++ b/po/POTFILES.in Mon Mar 31 21:04:51 2008 +0000 @@ -180,6 +180,7 @@ libpurple/request.h libpurple/savedstatuses.c libpurple/server.c +libpurple/smiley.c libpurple/sslconn.c libpurple/status.c libpurple/util.c @@ -209,6 +210,7 @@ pidgin/gtkrequest.c pidgin/gtkroomlist.c pidgin/gtksavedstatuses.c +pidgin/gtksmiley.c pidgin/gtksound.c pidgin/gtkstatusbox.c pidgin/gtkutils.c