# HG changeset patch # User Sadrul Habib Chowdhury # Date 1210634268 0 # Node ID dea8b856466eeb04492188a0ec1a3bce020718cb # Parent d53f72735830adfe2a8a4808084b782f3e48a654# Parent d0ee799828d1a52ae46c50121901f450b88e9c87 propagate from branch 'im.pidgin.pidgin.custom_smiley' (head c134ff23eba5faac09c13e731e792fa612c91a9a) to branch 'im.pidgin.pidgin.next.minor' (head 4d2d20241c7dac5915e142f0aa9811c9eab40111) diff -r d53f72735830 -r dea8b856466e COPYRIGHT --- a/COPYRIGHT Mon May 12 02:19:06 2008 +0000 +++ b/COPYRIGHT Mon May 12 23:17:48 2008 +0000 @@ -287,6 +287,7 @@ John Oyler Matt Pandina Laszlo Pandy +Giulio 'Twain28' Pascali Ricardo Fernandez Pascual Riley Patterson Havoc Pennington diff -r d53f72735830 -r dea8b856466e ChangeLog.API --- a/ChangeLog.API Mon May 12 02:19:06 2008 +0000 +++ b/ChangeLog.API Mon May 12 23:17:48 2008 +0000 @@ -1,5 +1,23 @@ Pidgin and Finch: The Pimpin' Penguin IM Clients That're Good for the Soul +version 2.5.0 (??/??/2008): + libpurple: + Added: + * Connection flag PURPLE_CONNECTION_ALLOW_CUSTOM_SMILEY to indicate + that the connection supports sending and receiving custom smileys. + * PurpleSmiley and the Smiley API. + + pidgin: + Added: + * gtk_imhtml_smiley_create, gtk_imhtml_smiley_reload and + gtk_imhtml_smiley_destroy to deal with GtkIMHtmlSmiley's. + * pidgin_pixbuf_from_imgstore to create a GdkPixbuf from a + PurpleStoredImage. + * pidgin_themes_smiley_themeize_custom to associate custom smileys to + a GtkIMHtml widget. + * GTK_IMHTML_CUSTOM_SMILEY flag for GtkIMHtml. + * GTK+ Custom Smiley API. + version 2.5.0: libpurple: Added: diff -r d53f72735830 -r dea8b856466e libpurple/Makefile.am --- a/libpurple/Makefile.am Mon May 12 02:19:06 2008 +0000 +++ b/libpurple/Makefile.am Mon May 12 23:17:48 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 d53f72735830 -r dea8b856466e libpurple/Makefile.mingw --- a/libpurple/Makefile.mingw Mon May 12 02:19:06 2008 +0000 +++ b/libpurple/Makefile.mingw Mon May 12 23:17:48 2008 +0000 @@ -65,6 +65,7 @@ savedstatuses.c \ server.c \ signals.c \ + smiley.c \ sound.c \ sslconn.c \ status.c \ diff -r d53f72735830 -r dea8b856466e libpurple/connection.h --- a/libpurple/connection.h Mon May 12 02:19:06 2008 +0000 +++ b/libpurple/connection.h Mon May 12 23:17:48 2008 +0000 @@ -43,6 +43,7 @@ PURPLE_CONNECTION_NO_FONTSIZE = 0x0020, /**< Connection does not send/receive font sizes */ PURPLE_CONNECTION_NO_URLDESC = 0x0040, /**< Connection does not support descriptions with links */ PURPLE_CONNECTION_NO_IMAGES = 0x0080, /**< Connection does not support sending of images */ + PURPLE_CONNECTION_ALLOW_CUSTOM_SMILEY = 0x0100, /**< Connection supports sending and receiving custom smileys */ } PurpleConnectionFlags; diff -r d53f72735830 -r dea8b856466e libpurple/core.c --- a/libpurple/core.c Mon May 12 02:19:06 2008 +0000 +++ b/libpurple/core.c Mon May 12 23:17:48 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 d53f72735830 -r dea8b856466e libpurple/imgstore.c --- a/libpurple/imgstore.c Mon May 12 02:19:06 2008 +0000 +++ b/libpurple/imgstore.c Mon May 12 23:17:48 2008 +0000 @@ -68,6 +68,22 @@ return img; } +PurpleStoredImage * +purple_imgstore_new_from_file(const char *path) +{ + gchar *data = NULL; + size_t len; + GError *err = NULL; + + if (!g_file_get_contents(path, &data, &len, &err)) { + purple_debug_error("imgstore", "Error reading %s: %s\n", + path, err->message); + g_error_free(err); + return NULL; + } + return purple_imgstore_add(data, len, path); +} + int purple_imgstore_add_with_id(gpointer data, size_t size, const char *filename) { diff -r d53f72735830 -r dea8b856466e libpurple/imgstore.h --- a/libpurple/imgstore.h Mon May 12 02:19:06 2008 +0000 +++ b/libpurple/imgstore.h Mon May 12 23:17:48 2008 +0000 @@ -63,6 +63,17 @@ purple_imgstore_add(gpointer data, size_t size, const char *filename); /** + * Create an image and add it to the store. + * + * @param path The path to the image. + * + * @return The stored image. + * @since 2.X.X + */ +PurpleStoredImage * +purple_imgstore_new_from_file(const char *path); + +/** * Add an image to the store, allocating an ID. * * The caller owns a reference to the image in the store, and must dereference diff -r d53f72735830 -r dea8b856466e libpurple/protocols/msn/msn.c --- a/libpurple/protocols/msn/msn.c Mon May 12 02:19:06 2008 +0000 +++ b/libpurple/protocols/msn/msn.c Mon May 12 23:17:48 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; } @@ -895,7 +903,8 @@ session = msn_session_new(account); gc->proto_data = session; - gc->flags |= PURPLE_CONNECTION_HTML | PURPLE_CONNECTION_FORMATTING_WBFO | PURPLE_CONNECTION_NO_BGCOLOR | PURPLE_CONNECTION_NO_FONTSIZE | PURPLE_CONNECTION_NO_URLDESC; + gc->flags |= PURPLE_CONNECTION_HTML | PURPLE_CONNECTION_FORMATTING_WBFO | PURPLE_CONNECTION_NO_BGCOLOR | + PURPLE_CONNECTION_NO_FONTSIZE | PURPLE_CONNECTION_NO_URLDESC | PURPLE_CONNECTION_ALLOW_CUSTOM_SMILEY; msn_session_set_login_step(session, MSN_LOGIN_STEP_START); @@ -936,6 +945,97 @@ return FALSE; } +static GString* +msn_msg_emoticon_add(GString *current, MsnEmoticon *emoticon) +{ + MsnObject *obj; + char *strobj; + + if (emoticon == NULL) + return current; + + obj = emoticon->obj; + + if (!obj) + return current; + + strobj = msn_object_to_string(obj); + + if (current) + g_string_append_printf(current, "\t%s\t%s", + emoticon->smile, strobj); + else { + current = g_string_new(""); + g_string_printf(current,"%s\t%s", + emoticon->smile, strobj); + } + + g_free(strobj); + + return current; +} + +static void +msn_send_emoticons(MsnSwitchBoard *swboard, GString *body) +{ + MsnMessage *msg; + + g_return_if_fail(body != NULL); + + msg = msn_message_new(MSN_MSG_SLP); + msn_message_set_content_type(msg, "text/x-mms-emoticon"); + msn_message_set_flag(msg, 'N'); + msn_message_set_bin_data(msg, body->str, body->len); + + msn_switchboard_send_msg(swboard, msg, TRUE); + msn_message_destroy(msg); +} + +static void msn_emoticon_destroy(MsnEmoticon *emoticon) +{ + if (emoticon->obj) + msn_object_destroy(emoticon->obj); + g_free(emoticon->smile); + g_free(emoticon); +} + +static GSList* msn_msg_grab_emoticons(const char *msg, const char *username) +{ + GSList *list; + GList *smileys; + PurpleSmiley *smiley; + PurpleStoredImage *img; + char *ptr; + MsnEmoticon *emoticon; + int length; + + list = NULL; + smileys = purple_smileys_get_all(); + length = strlen(msg); + + for (; smileys; smileys = g_list_delete_link(smileys, smileys)) { + smiley = smileys->data; + + ptr = g_strstr_len(msg, length, purple_smiley_get_shortcut(smiley)); + + if (!ptr) + continue; + + img = purple_smiley_get_stored_image(smiley); + + emoticon = g_new0(MsnEmoticon, 1); + emoticon->smile = g_strdup(purple_smiley_get_shortcut(smiley)); + emoticon->obj = msn_object_new_from_image(img, + purple_imgstore_get_filename(img), + username, MSN_OBJECT_EMOTICON); + + purple_imgstore_unref(img); + list = g_slist_prepend(list, emoticon); + } + + return list; +} + static int msn_send_im(PurpleConnection *gc, const char *who, const char *message, PurpleMessageFlags flags) @@ -945,9 +1045,11 @@ MsnMessage *msg; char *msgformat; char *msgtext; + const char *username; purple_debug_info("MSNP14","send IM {%s} to %s\n",message,who); account = purple_connection_get_account(gc); + username = purple_account_get_username(account); if (buddy) { PurplePresence *p = purple_buddy_get_presence(buddy); @@ -980,10 +1082,13 @@ g_free(msgtext); purple_debug_info("MSNP14","prepare to send online Message\n"); - if (g_ascii_strcasecmp(who, purple_account_get_username(account))) + if (g_ascii_strcasecmp(who, username)) { MsnSession *session; MsnSwitchBoard *swboard; + MsnEmoticon *smile; + GSList *smileys; + GString *emoticons = NULL; session = gc->proto_data; if(msn_user_is_yahoo(account,who)){ @@ -993,6 +1098,19 @@ }else{ purple_debug_info("MSNP14","send via switchboard\n"); swboard = msn_session_get_swboard(session, who, MSN_SB_FLAG_IM); + smileys = msn_msg_grab_emoticons(message, username); + while (smileys) { + smile = (MsnEmoticon*)smileys->data; + emoticons = msn_msg_emoticon_add(emoticons, smile); + msn_emoticon_destroy(smile); + smileys = g_slist_delete_link(smileys, smileys); + } + + if (emoticons) { + msn_send_emoticons(swboard, emoticons); + g_string_free(emoticons, TRUE); + } + msn_switchboard_send_msg(swboard, msg, TRUE); } } diff -r d53f72735830 -r dea8b856466e libpurple/protocols/msn/object.c --- a/libpurple/protocols/msn/object.c Mon May 12 02:19:06 2008 +0000 +++ b/libpurple/protocols/msn/object.c Mon May 12 23:17:48 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 d53f72735830 -r dea8b856466e libpurple/protocols/msn/object.h --- a/libpurple/protocols/msn/object.h Mon May 12 02:19:06 2008 +0000 +++ b/libpurple/protocols/msn/object.h Mon May 12 23:17:48 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 d53f72735830 -r dea8b856466e libpurple/protocols/msn/slp.c --- a/libpurple/protocols/msn/slp.c Mon May 12 02:19:06 2008 +0000 +++ b/libpurple/protocols/msn/slp.c Mon May 12 23:17:48 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 d53f72735830 -r dea8b856466e libpurple/protocols/msn/user.c --- a/libpurple/protocols/msn/user.c Mon May 12 02:19:06 2008 +0000 +++ b/libpurple/protocols/msn/user.c Mon May 12 23:17:48 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 d53f72735830 -r dea8b856466e libpurple/protocols/msnp9/msn.c --- a/libpurple/protocols/msnp9/msn.c Mon May 12 02:19:06 2008 +0000 +++ b/libpurple/protocols/msnp9/msn.c Mon May 12 23:17:48 2008 +0000 @@ -33,6 +33,7 @@ #include "pluginpref.h" #include "prefs.h" #include "session.h" +#include "smiley.h" #include "state.h" #include "util.h" #include "cmds.h" @@ -83,6 +84,12 @@ time_t when; } MsnIMData; +typedef struct +{ + char *smile; + MsnObject *obj; +} MsnEmoticon; + static const char * msn_normalize(const PurpleAccount *account, const char *str) { @@ -116,6 +123,7 @@ return FALSE; msn_switchboard_send_msg(swboard, msg, TRUE); + msn_message_destroy(msg); return TRUE; } @@ -766,7 +774,8 @@ session = msn_session_new(account); gc->proto_data = session; - gc->flags |= PURPLE_CONNECTION_HTML | PURPLE_CONNECTION_FORMATTING_WBFO | PURPLE_CONNECTION_NO_BGCOLOR | PURPLE_CONNECTION_NO_FONTSIZE | PURPLE_CONNECTION_NO_URLDESC; + gc->flags |= PURPLE_CONNECTION_HTML | PURPLE_CONNECTION_FORMATTING_WBFO | PURPLE_CONNECTION_NO_BGCOLOR | + PURPLE_CONNECTION_NO_FONTSIZE | PURPLE_CONNECTION_NO_URLDESC | PURPLE_CONNECTION_ALLOW_CUSTOM_SMILEY; msn_session_set_login_step(session, MSN_LOGIN_STEP_START); @@ -807,6 +816,97 @@ return FALSE; } +static GString* +msn_msg_emoticon_add(GString *current, MsnEmoticon *emoticon) +{ + MsnObject *obj; + char *strobj; + + if (emoticon == NULL) + return current; + + obj = emoticon->obj; + + if (!obj) + return current; + + strobj = msn_object_to_string(obj); + + if (current) + g_string_append_printf(current, "\t%s\t%s", + emoticon->smile, strobj); + else { + current = g_string_new(""); + g_string_printf(current,"%s\t%s", + emoticon->smile, strobj); + } + + g_free(strobj); + + return current; +} + +static void +msn_send_emoticons(MsnSwitchBoard *swboard, GString *body) +{ + MsnMessage *msg; + + g_return_if_fail(body != NULL); + + msg = msn_message_new(MSN_MSG_SLP); + msn_message_set_content_type(msg, "text/x-mms-emoticon"); + msn_message_set_flag(msg, 'N'); + msn_message_set_bin_data(msg, body->str, body->len); + + msn_switchboard_send_msg(swboard, msg, TRUE); + msn_message_destroy(msg); +} + +static void msn_emoticon_destroy(MsnEmoticon *emoticon) +{ + if (emoticon->obj) + msn_object_destroy(emoticon->obj); + g_free(emoticon->smile); + g_free(emoticon); +} + +static GSList* msn_msg_grab_emoticons(const char *msg, const char *username) +{ + GSList *list; + GList *smileys; + PurpleSmiley *smiley; + PurpleStoredImage *img; + char *ptr; + MsnEmoticon *emoticon; + int length; + + list = NULL; + smileys = purple_smileys_get_all(); + length = strlen(msg); + + for (; smileys; smileys = g_list_delete_link(smileys, smileys)) { + smiley = (PurpleSmiley*)smileys->data; + + ptr = g_strstr_len(msg, length, purple_smiley_get_shortcut(smiley)); + + if (!ptr) + continue; + + img = purple_smiley_get_stored_image(smiley); + + emoticon = g_new0(MsnEmoticon, 1); + emoticon->smile = g_strdup(purple_smiley_get_shortcut(smiley)); + emoticon->obj = msn_object_new_from_image(img, + purple_imgstore_get_filename(img), + username, MSN_OBJECT_EMOTICON); + + purple_imgstore_unref(img); + list = g_slist_prepend(list, emoticon); + } + + return list; +} + static int msn_send_im(PurpleConnection *gc, const char *who, const char *message, PurpleMessageFlags flags) @@ -816,8 +916,10 @@ MsnMessage *msg; char *msgformat; char *msgtext; + const char *username; account = purple_connection_get_account(gc); + username = purple_account_get_username(account); if (buddy) { PurplePresence *p = purple_buddy_get_presence(buddy); @@ -845,13 +947,29 @@ g_free(msgformat); g_free(msgtext); - if (g_ascii_strcasecmp(who, purple_account_get_username(account))) + if (g_ascii_strcasecmp(who, username)) { MsnSession *session; MsnSwitchBoard *swboard; + MsnEmoticon *smile; + GSList *smileys; + GString *emoticons = NULL; session = gc->proto_data; swboard = msn_session_get_swboard(session, who, MSN_SB_FLAG_IM); + smileys = msn_msg_grab_emoticons(message, username); + + while (smileys) { + smile = (MsnEmoticon*)smileys->data; + emoticons = msn_msg_emoticon_add(emoticons,smile); + msn_emoticon_destroy(smile); + smileys = g_slist_delete_link(smileys, smileys); + } + + if (emoticons) { + msn_send_emoticons(swboard, emoticons); + g_string_free(emoticons, TRUE); + } msn_switchboard_send_msg(swboard, msg, TRUE); } diff -r d53f72735830 -r dea8b856466e libpurple/protocols/msnp9/object.c --- a/libpurple/protocols/msnp9/object.c Mon May 12 02:19:06 2008 +0000 +++ b/libpurple/protocols/msnp9/object.c Mon May 12 23:17:48 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 d53f72735830 -r dea8b856466e libpurple/protocols/msnp9/object.h --- a/libpurple/protocols/msnp9/object.h Mon May 12 02:19:06 2008 +0000 +++ b/libpurple/protocols/msnp9/object.h Mon May 12 23:17:48 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 d53f72735830 -r dea8b856466e libpurple/protocols/msnp9/slp.c --- a/libpurple/protocols/msnp9/slp.c Mon May 12 02:19:06 2008 +0000 +++ b/libpurple/protocols/msnp9/slp.c Mon May 12 23:17:48 2008 +0000 @@ -31,6 +31,8 @@ #include "user.h" #include "switchboard.h" +#include "smiley.h" + /* ms to delay between sending buddy icon requests to the server. */ #define BUDDY_ICON_DELAY 20000 @@ -276,23 +278,32 @@ type = msn_object_get_type(obj); g_free(msnobj_data); - if (!(type == MSN_OBJECT_USERTILE)) + if ((type != MSN_OBJECT_USERTILE) && (type != MSN_OBJECT_EMOTICON)) { purple_debug_error("msn", "Wrong object?\n"); msn_object_destroy(obj); g_return_if_reached(); } - img = msn_object_get_image(obj); + if (type == MSN_OBJECT_EMOTICON) { + char *path; + path = g_build_filename(purple_smileys_get_storing_dir(), + obj->location, NULL); + img = purple_imgstore_new_from_file(path); + g_free(path); + } else { + img = msn_object_get_image(obj); + if (img) + purple_imgstore_ref(img); + } + msn_object_destroy(obj); + if (img == NULL) { purple_debug_error("msn", "Wrong object.\n"); - msn_object_destroy(obj); g_return_if_reached(); } - msn_object_destroy(obj); - slpsession = msn_slplink_find_slp_session(slplink, slpcall->session_id); @@ -317,6 +328,7 @@ #endif msn_slpmsg_set_image(slpmsg, img); msn_slplink_queue_slpmsg(slplink, slpmsg); + purple_imgstore_unref(img); } else if (!strcmp(euf_guid, "5D3E02AB-6190-11D3-BBBB-00C04F795683")) { diff -r d53f72735830 -r dea8b856466e libpurple/protocols/msnp9/user.c --- a/libpurple/protocols/msnp9/user.c Mon May 12 02:19:06 2008 +0000 +++ b/libpurple/protocols/msnp9/user.c Mon May 12 23:17:48 2008 +0000 @@ -153,69 +153,17 @@ void msn_user_set_buddy_icon(MsnUser *user, PurpleStoredImage *img) { - MsnObject *msnobj = msn_user_get_object(user); + MsnObject *msnobj = NULL; g_return_if_fail(user != NULL); - if (img == NULL) - msn_user_set_object(user, NULL); - else - { - PurpleCipherContext *ctx; - char *buf; - gconstpointer data = purple_imgstore_get_data(img); - size_t size = purple_imgstore_get_size(img); - char *base64; - unsigned char digest[20]; - - if (msnobj == NULL) - { - msnobj = msn_object_new(); - msn_object_set_local(msnobj); - msn_object_set_type(msnobj, MSN_OBJECT_USERTILE); - msn_object_set_location(msnobj, "TFR2C2.tmp"); - msn_object_set_creator(msnobj, msn_user_get_passport(user)); - - msn_user_set_object(user, msnobj); - } - - msn_object_set_image(msnobj, img); - - /* Compute the SHA1D field. */ - memset(digest, 0, sizeof(digest)); + msnobj = msn_object_new_from_image(img, "TFR2C2.tmp", + user->passport, MSN_OBJECT_USERTILE); - ctx = purple_cipher_context_new_by_name("sha1", NULL); - purple_cipher_context_append(ctx, data, size); - purple_cipher_context_digest(ctx, sizeof(digest), digest, NULL); - - base64 = purple_base64_encode(digest, sizeof(digest)); - msn_object_set_sha1d(msnobj, base64); - g_free(base64); - - msn_object_set_size(msnobj, size); + if(!msnobj) + purple_debug_error("msn", "Unable to open buddy icon from %s!\n", user->passport); - /* Compute the SHA1C field. */ - buf = g_strdup_printf( - "Creator%sSize%dType%dLocation%sFriendly%sSHA1D%s", - msn_object_get_creator(msnobj), - msn_object_get_size(msnobj), - msn_object_get_type(msnobj), - msn_object_get_location(msnobj), - msn_object_get_friendly(msnobj), - msn_object_get_sha1d(msnobj)); - - memset(digest, 0, sizeof(digest)); - - purple_cipher_context_reset(ctx, NULL); - purple_cipher_context_append(ctx, (const guchar *)buf, strlen(buf)); - purple_cipher_context_digest(ctx, sizeof(digest), digest, NULL); - purple_cipher_context_destroy(ctx); - g_free(buf); - - base64 = purple_base64_encode(digest, sizeof(digest)); - msn_object_set_sha1c(msnobj, base64); - g_free(base64); - } + msn_user_set_object(user, msnobj); } void diff -r d53f72735830 -r dea8b856466e libpurple/smiley.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/smiley.c Mon May 12 23:17:48 2008 +0000 @@ -0,0 +1,910 @@ +/** + * @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 +{ + GObject parent; + PurpleStoredImage *img; /**< The id of the stored image with the + the smiley data. */ + char *shortcut; /**< Shortcut associated with the custom + smiley. This field will work as a + unique key by this API. */ + char *checksum; /**< The smiley checksum. */ +}; + +struct _PurpleSmileyClass +{ + GObjectClass parent_class; +}; + +static GHashTable *smiley_shortcut_index = NULL; /* shortcut (char *) => smiley (PurpleSmiley*) */ +static GHashTable *smiley_checksum_index = NULL; /* checksum (char *) => smiley (PurpleSmiley*) */ + +static guint save_timer = 0; +static gboolean smileys_loaded = FALSE; +static char *smileys_dir = NULL; + +#define SMILEYS_DEFAULT_FOLDER "custom_smiley" +#define SMILEYS_LOG_ID "smileys" + +#define XML_FILE_NAME "smileys.xml" + +#define XML_ROOT_TAG "smileys" +#define XML_PROFILE_TAG "profile" +#define XML_PROFILE_NAME_ATTRIB_TAG "name" +#define XML_ACCOUNT_TAG "account" +#define XML_ACCOUNT_USERID_ATTRIB_TAG "userid" +#define XML_SMILEY_SET_TAG "smiley_set" +#define XML_SMILEY_TAG "smiley" +#define XML_SHORTCUT_ATTRIB_TAG "shortcut" +#define XML_CHECKSUM_ATRIB_TAG "checksum" +#define XML_FILENAME_ATRIB_TAG "filename" + + +/****************************************************************************** + * XML descriptor file layout * + ****************************************************************************** + * + * Althought we are creating the profile XML structure here, now we + * won't handle it. + * So, we just add one profile named "default" that has no associated + * account elements, and have only the smiley_set that will contain + * all existent custom smiley. + * + * It's our "Highlander Profile" :-) + * + ****************************************************************************** + * + * + * + * + * + * + * + * + * + * + * + * + *****************************************************************************/ + + +/********************************************************************* + * Forward declarations * + *********************************************************************/ + +static gboolean read_smiley_file(const char *path, guchar **data, size_t *len); + +static char *get_file_full_path(const char *filename); + +static PurpleSmiley *purple_smiley_create(const char *shortcut); + +static PurpleSmiley *purple_smiley_load_file(const char *shortcut, const char *checksum, + const char *filename); + +static void +purple_smiley_set_data_impl(PurpleSmiley *smiley, guchar *smiley_data, + size_t smiley_data_len, const char *filename); + +static void +purple_smiley_data_store(PurpleStoredImage *stored_img); + +static void +purple_smiley_data_unstore(const char *filename); + +/********************************************************************* + * Writing to disk * + *********************************************************************/ + +static xmlnode * +smiley_to_xmlnode(PurpleSmiley *smiley) +{ + xmlnode *smiley_node = NULL; + + smiley_node = xmlnode_new(XML_SMILEY_TAG); + + if (!smiley_node) + return NULL; + + xmlnode_set_attrib(smiley_node, XML_SHORTCUT_ATTRIB_TAG, + smiley->shortcut); + + xmlnode_set_attrib(smiley_node, XML_CHECKSUM_ATRIB_TAG, + smiley->checksum); + + xmlnode_set_attrib(smiley_node, XML_FILENAME_ATRIB_TAG, + purple_imgstore_get_filename(smiley->img)); + + return smiley_node; +} + +static void +add_smiley_to_main_node(gpointer key, gpointer value, gpointer user_data) +{ + xmlnode *child_node; + + child_node = smiley_to_xmlnode(value); + xmlnode_insert_child((xmlnode*)user_data, child_node); +} + +static xmlnode * +smileys_to_xmlnode() +{ + xmlnode *root_node, *profile_node, *smileyset_node; + + root_node = xmlnode_new(XML_ROOT_TAG); + xmlnode_set_attrib(root_node, "version", "1.0"); + + /* See the top comment's above to understand why initial tag elements + * are not being considered by now. */ + profile_node = xmlnode_new(XML_PROFILE_TAG); + if (profile_node) { + xmlnode_set_attrib(profile_node, XML_PROFILE_NAME_ATTRIB_TAG, "Default"); + xmlnode_insert_child(root_node, profile_node); + + smileyset_node = xmlnode_new(XML_SMILEY_SET_TAG); + if (smileyset_node) { + xmlnode_insert_child(profile_node, smileyset_node); + g_hash_table_foreach(smiley_shortcut_index, add_smiley_to_main_node, smileyset_node); + } + } + + return root_node; +} + +static void +sync_smileys() +{ + xmlnode *root_node; + char *data; + + if (!smileys_loaded) { + purple_debug_error(SMILEYS_LOG_ID, "Attempted to save smileys before it " + "was read!\n"); + return; + } + + root_node = smileys_to_xmlnode(); + data = xmlnode_to_formatted_str(root_node, NULL); + purple_util_write_data_to_file(XML_FILE_NAME, data, -1); + + g_free(data); + xmlnode_free(root_node); +} + +static gboolean +save_smileys_cb(gpointer data) +{ + sync_smileys(); + save_timer = 0; + return FALSE; +} + +static void +purple_smileys_save() +{ + if (save_timer == 0) + save_timer = purple_timeout_add_seconds(5, save_smileys_cb, NULL); +} + + +/********************************************************************* + * Reading from disk * + *********************************************************************/ + +static PurpleSmiley * +parse_smiley(xmlnode *smiley_node) +{ + PurpleSmiley *smiley; + const char *shortcut = NULL; + const char *checksum = NULL; + const char *filename = NULL; + + shortcut = xmlnode_get_attrib(smiley_node, XML_SHORTCUT_ATTRIB_TAG); + checksum = xmlnode_get_attrib(smiley_node, XML_CHECKSUM_ATRIB_TAG); + filename = xmlnode_get_attrib(smiley_node, XML_FILENAME_ATRIB_TAG); + + if ((shortcut == NULL) || (checksum == NULL) || (filename == NULL)) + return NULL; + + smiley = purple_smiley_load_file(shortcut, checksum, filename); + + return smiley; +} + +static void +purple_smileys_load() +{ + xmlnode *root_node, *profile_node; + xmlnode *smileyset_node = NULL; + xmlnode *smiley_node; + + smileys_loaded = TRUE; + + root_node = purple_util_read_xml_from_file(XML_FILE_NAME, + _(SMILEYS_LOG_ID)); + + if (root_node == NULL) + return; + + /* See the top comment's above to understand why initial tag elements + * are not being considered by now. */ + profile_node = xmlnode_get_child(root_node, XML_PROFILE_TAG); + if (profile_node) + smileyset_node = xmlnode_get_child(profile_node, XML_SMILEY_SET_TAG); + + if (smileyset_node) { + smiley_node = xmlnode_get_child(smileyset_node, XML_SMILEY_TAG); + for (; smiley_node != NULL; + smiley_node = xmlnode_get_next_twin(smiley_node)) { + PurpleSmiley *smiley; + + smiley = parse_smiley(smiley_node); + } + } + + xmlnode_free(root_node); +} + +/********************************************************************* + * GObject Stuff * + *********************************************************************/ +enum +{ + PROP_0, + PROP_SHORTCUT, + PROP_IMGSTORE, +}; + +#define PROP_SHORTCUT_S "shortcut" +#define PROP_IMGSTORE_S "image" + +enum +{ + SIG_DESTROY, + SIG_LAST +}; + +static guint signals[SIG_LAST]; +static GObjectClass *parent_class; + +static void +purple_smiley_init(GTypeInstance *instance, gpointer klass) +{ +} + +static void +purple_smiley_get_property(GObject *object, guint param_id, GValue *value, + GParamSpec *spec) +{ + PurpleSmiley *smiley = PURPLE_SMILEY(object); + switch (param_id) { + case PROP_SHORTCUT: + g_value_set_string(value, smiley->shortcut); + break; + case PROP_IMGSTORE: + g_value_set_pointer(value, smiley->img); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, param_id, spec); + break; + } +} + +static void +purple_smiley_set_property(GObject *object, guint param_id, const GValue *value, + GParamSpec *spec) +{ + PurpleSmiley *smiley = PURPLE_SMILEY(object); + switch (param_id) { + case PROP_SHORTCUT: + { + const char *shortcut = g_value_get_string(value); + purple_smiley_set_shortcut(smiley, shortcut); + } + break; + case PROP_IMGSTORE: + { + PurpleStoredImage *img = g_value_get_pointer(value); + + purple_imgstore_unref(smiley->img); + g_free(smiley->checksum); + + smiley->img = img; + if (img) { + smiley->checksum = purple_util_get_image_checksum( + purple_imgstore_get_data(img), + purple_imgstore_get_size(img)); + purple_smiley_data_store(img); + } else { + smiley->checksum = NULL; + } + + g_object_notify(object, PROP_IMGSTORE_S); + } + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, param_id, spec); + break; + } +} + +static void +purple_smiley_finalize(GObject *obj) +{ + PurpleSmiley *smiley = PURPLE_SMILEY(obj); + + if (g_hash_table_lookup(smiley_shortcut_index, smiley->shortcut)) { + g_hash_table_remove(smiley_shortcut_index, smiley->shortcut); + g_hash_table_remove(smiley_checksum_index, smiley->checksum); + } + + g_free(smiley->shortcut); + g_free(smiley->checksum); + if (smiley->img) + purple_smiley_data_unstore(purple_imgstore_get_filename(smiley->img)); + purple_imgstore_unref(smiley->img); + + purple_smileys_save(); +} + +static void +purple_smiley_dispose(GObject *gobj) +{ + g_signal_emit(gobj, signals[SIG_DESTROY], 0); + parent_class->dispose(gobj); +} + +static void +purple_smiley_class_init(PurpleSmileyClass *klass) +{ + GObjectClass *gobj_class = G_OBJECT_CLASS(klass); + GParamSpec *pspec; + + parent_class = g_type_class_peek_parent(klass); + + gobj_class->get_property = purple_smiley_get_property; + gobj_class->set_property = purple_smiley_set_property; + gobj_class->finalize = purple_smiley_finalize; + gobj_class->dispose = purple_smiley_dispose; + + /* Shortcut */ + pspec = g_param_spec_string(PROP_SHORTCUT_S, _("Shortcut"), + _("The text-shortcut for the smiley"), + NULL, + G_PARAM_READWRITE); + g_object_class_install_property(gobj_class, PROP_SHORTCUT, pspec); + + /* Stored Image */ + pspec = g_param_spec_pointer(PROP_IMGSTORE_S, _("Stored Image"), + _("Stored Image. (that'll have to do for now)"), + G_PARAM_READWRITE); + g_object_class_install_property(gobj_class, PROP_IMGSTORE, pspec); + + signals[SIG_DESTROY] = g_signal_new("destroy", + G_OBJECT_CLASS_TYPE(klass), + G_SIGNAL_RUN_LAST, + 0, NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); +} + +GType +purple_smiley_get_type(void) +{ + static GType type = 0; + + if(type == 0) { + static const GTypeInfo info = { + sizeof(PurpleSmileyClass), + NULL, + NULL, + (GClassInitFunc)purple_smiley_class_init, + NULL, + NULL, + sizeof(PurpleSmiley), + 0, + purple_smiley_init, + NULL, + }; + + type = g_type_register_static(G_TYPE_OBJECT, + "PurpleSmiley", + &info, 0); + } + + return type; +} + +/********************************************************************* + * Other Stuff * + *********************************************************************/ + +static char *get_file_full_path(const char *filename) +{ + char *path; + + path = g_build_filename(purple_smileys_get_storing_dir(), filename, NULL); + + if (!g_file_test(path, G_FILE_TEST_EXISTS)) { + g_free(path); + return NULL; + } + + return path; +} + +static PurpleSmiley * +purple_smiley_load_file(const char *shortcut, const char *checksum, const char *filename) +{ + PurpleSmiley *smiley = NULL; + guchar *smiley_data; + size_t smiley_data_len; + char *fullpath = NULL; + + g_return_val_if_fail(shortcut != NULL, NULL); + g_return_val_if_fail(checksum != NULL, NULL); + g_return_val_if_fail(filename != NULL, NULL); + + fullpath = get_file_full_path(filename); + if (!fullpath) + return NULL; + + smiley = purple_smiley_create(shortcut); + if (!smiley) { + g_free(fullpath); + return NULL; + } + + smiley->checksum = g_strdup(checksum); + + if (read_smiley_file(fullpath, &smiley_data, &smiley_data_len)) + purple_smiley_set_data_impl(smiley, smiley_data, + smiley_data_len, filename); + else + purple_smiley_delete(smiley); + + g_free(fullpath); + + return smiley; +} + +static void +purple_smiley_data_store(PurpleStoredImage *stored_img) +{ + const char *dirname; + char *path; + FILE *file = NULL; + + g_return_if_fail(stored_img != NULL); + + if (!smileys_loaded) + return; + + dirname = purple_smileys_get_storing_dir(); + path = g_build_filename(dirname, purple_imgstore_get_filename(stored_img), NULL); + + if (!g_file_test(dirname, G_FILE_TEST_IS_DIR)) { + purple_debug_info(SMILEYS_LOG_ID, "Creating smileys directory.\n"); + + if (g_mkdir(dirname, S_IRUSR | S_IWUSR | S_IXUSR) < 0) { + purple_debug_error(SMILEYS_LOG_ID, + "Unable to create directory %s: %s\n", + dirname, g_strerror(errno)); + } + } + + if ((file = g_fopen(path, "wb")) != NULL) { + if (!fwrite(purple_imgstore_get_data(stored_img), + purple_imgstore_get_size(stored_img), 1, file)) { + purple_debug_error(SMILEYS_LOG_ID, "Error writing %s: %s\n", + path, g_strerror(errno)); + } else { + purple_debug_info(SMILEYS_LOG_ID, "Wrote cache file: %s\n", path); + } + + fclose(file); + } else { + purple_debug_error(SMILEYS_LOG_ID, "Unable to create file %s: %s\n", + path, g_strerror(errno)); + g_free(path); + + return; + } + + g_free(path); +} + +static void +purple_smiley_data_unstore(const char *filename) +{ + const char *dirname; + char *path; + + g_return_if_fail(filename != NULL); + + dirname = purple_smileys_get_storing_dir(); + path = g_build_filename(dirname, filename, NULL); + + if (g_file_test(path, G_FILE_TEST_EXISTS)) { + if (g_unlink(path)) + purple_debug_error(SMILEYS_LOG_ID, "Failed to delete %s: %s\n", + path, g_strerror(errno)); + else + purple_debug_info(SMILEYS_LOG_ID, "Deleted cache file: %s\n", path); + } + + g_free(path); +} + +static gboolean +read_smiley_file(const char *path, guchar **data, size_t *len) +{ + GError *err = NULL; + + if (!g_file_get_contents(path, (gchar **)data, len, &err)) { + purple_debug_error(SMILEYS_LOG_ID, "Error reading %s: %s\n", + path, err->message); + g_error_free(err); + + return FALSE; + } + + return TRUE; +} + +static PurpleStoredImage * +purple_smiley_data_new(guchar *smiley_data, size_t smiley_data_len) +{ + char *filename; + PurpleStoredImage *stored_img; + + g_return_val_if_fail(smiley_data != NULL, NULL); + g_return_val_if_fail(smiley_data_len > 0, NULL); + + filename = purple_util_get_image_filename(smiley_data, smiley_data_len); + + if (filename == NULL) { + g_free(smiley_data); + return NULL; + } + + stored_img = purple_imgstore_add(smiley_data, smiley_data_len, filename); + + g_free(filename); + + return stored_img; +} + +static void +purple_smiley_set_data_impl(PurpleSmiley *smiley, guchar *smiley_data, + size_t smiley_data_len, const char *filename) +{ + PurpleStoredImage *old_img, *new_img; + const char *old_filename = NULL; + const char *new_filename = NULL; + + g_return_if_fail(smiley != NULL); + g_return_if_fail(smiley_data != NULL); + g_return_if_fail(smiley_data_len > 0); + + old_img = smiley->img; + + if (filename) + new_img = purple_imgstore_add(smiley_data, smiley_data_len, filename); + else + new_img = purple_smiley_data_new(smiley_data, smiley_data_len); + + g_object_set(G_OBJECT(smiley), PROP_IMGSTORE_S, new_img, NULL); + + /* If the old and new image files have different names we need + * to unstore old image file. */ + if (!old_img) + return; + + old_filename = purple_imgstore_get_filename(old_img); + new_filename = purple_imgstore_get_filename(smiley->img); + + if (g_ascii_strcasecmp(old_filename, new_filename)) { + purple_smiley_data_unstore(old_filename); + purple_imgstore_unref(old_img); + } +} + + +/***************************************************************************** + * Public API functions * + *****************************************************************************/ + +static PurpleSmiley * +purple_smiley_create(const char *shortcut) +{ + PurpleSmiley *smiley; + + smiley = PURPLE_SMILEY(g_object_new(PURPLE_TYPE_SMILEY, PROP_SHORTCUT_S, shortcut, NULL)); + + return smiley; +} + +PurpleSmiley * +purple_smiley_new(PurpleStoredImage *img, const char *shortcut) +{ + PurpleSmiley *smiley = NULL; + + g_return_val_if_fail(shortcut != NULL, NULL); + g_return_val_if_fail(img != NULL, NULL); + + smiley = purple_smileys_find_by_shortcut(shortcut); + if (smiley) + return smiley; + + smiley = purple_smiley_create(shortcut); + if (!smiley) + return NULL; + + g_object_set(G_OBJECT(smiley), PROP_IMGSTORE_S, img, NULL); + + return smiley; +} + +static PurpleSmiley * +purple_smiley_new_from_stream(const char *shortcut, guchar *smiley_data, + size_t smiley_data_len, const char *filename) +{ + PurpleSmiley *smiley; + + g_return_val_if_fail(shortcut != NULL, NULL); + g_return_val_if_fail(smiley_data != NULL, NULL); + g_return_val_if_fail(smiley_data_len > 0, NULL); + + smiley = purple_smileys_find_by_shortcut(shortcut); + if (smiley) + return smiley; + + /* purple_smiley_create() sets shortcut */ + smiley = purple_smiley_create(shortcut); + if (!smiley) + return NULL; + + purple_smiley_set_data_impl(smiley, smiley_data, smiley_data_len, filename); + + purple_smiley_data_store(smiley->img); + + return smiley; +} + +PurpleSmiley * +purple_smiley_new_from_file(const char *shortcut, const char *filepath) +{ + PurpleSmiley *smiley = NULL; + guchar *smiley_data; + size_t smiley_data_len; + char *filename; + + g_return_val_if_fail(shortcut != NULL, NULL); + g_return_val_if_fail(filepath != NULL, NULL); + + filename = g_path_get_basename(filepath); + if (read_smiley_file(filepath, &smiley_data, &smiley_data_len)) + smiley = purple_smiley_new_from_stream(shortcut, smiley_data, + smiley_data_len, filename); + g_free(filename); + + return smiley; +} + +void +purple_smiley_delete(PurpleSmiley *smiley) +{ + g_return_if_fail(smiley != NULL); + + g_object_unref(smiley); +} + +gboolean +purple_smiley_set_shortcut(PurpleSmiley *smiley, const char *shortcut) +{ + g_return_val_if_fail(smiley != NULL, FALSE); + g_return_val_if_fail(shortcut != NULL, FALSE); + + /* Check out whether the new shortcut is already being used. */ + if (g_hash_table_lookup(smiley_shortcut_index, shortcut)) + return FALSE; + + /* Remove the old shortcut. */ + if (smiley->shortcut) + g_hash_table_remove(smiley_shortcut_index, smiley->shortcut); + + /* Insert the new shortcut. */ + g_hash_table_insert(smiley_shortcut_index, g_strdup(shortcut), smiley); + + g_free(smiley->shortcut); + smiley->shortcut = g_strdup(shortcut); + + g_object_notify(G_OBJECT(smiley), PROP_SHORTCUT_S); + + purple_smileys_save(); + + return TRUE; +} + +void +purple_smiley_set_data(PurpleSmiley *smiley, guchar *smiley_data, + size_t smiley_data_len, gboolean keepfilename) +{ + g_return_if_fail(smiley != NULL); + g_return_if_fail(smiley_data != NULL); + g_return_if_fail(smiley_data_len > 0); + + /* Remove the previous entry */ + g_hash_table_remove(smiley_checksum_index, smiley->checksum); + + /* Update the file data. This also updates the checksum. */ + if ((keepfilename) && (smiley->img) && + (purple_imgstore_get_filename(smiley->img))) + purple_smiley_set_data_impl(smiley, smiley_data, + smiley_data_len, + purple_imgstore_get_filename(smiley->img)); + else + purple_smiley_set_data_impl(smiley, smiley_data, + smiley_data_len, NULL); + + /* Reinsert the index item. */ + g_hash_table_insert(smiley_checksum_index, g_strdup(smiley->checksum), smiley); + + purple_smileys_save(); +} + +PurpleStoredImage * +purple_smiley_get_stored_image(const PurpleSmiley *smiley) +{ + return purple_imgstore_ref(smiley->img); +} + +const char *purple_smiley_get_shortcut(const PurpleSmiley *smiley) +{ + g_return_val_if_fail(smiley != NULL, NULL); + + return smiley->shortcut; +} + +const char * +purple_smiley_get_checksum(const PurpleSmiley *smiley) +{ + g_return_val_if_fail(smiley != NULL, NULL); + + return smiley->checksum; +} + +gconstpointer +purple_smiley_get_data(const PurpleSmiley *smiley, size_t *len) +{ + g_return_val_if_fail(smiley != NULL, NULL); + + if (smiley->img) { + if (len != NULL) + *len = purple_imgstore_get_size(smiley->img); + + return purple_imgstore_get_data(smiley->img); + } + + return NULL; +} + +const char * +purple_smiley_get_extension(const PurpleSmiley *smiley) +{ + if (smiley->img != NULL) + return purple_imgstore_get_extension(smiley->img); + + return NULL; +} + +char *purple_smiley_get_full_path(PurpleSmiley *smiley) +{ + g_return_val_if_fail(smiley != NULL, NULL); + + if (smiley->img == NULL) + return NULL; + + return get_file_full_path(purple_imgstore_get_filename(smiley->img)); +} + +static void add_smiley_to_list(gpointer key, gpointer value, gpointer user_data) +{ + GList** returninglist = (GList**)user_data; + + *returninglist = g_list_append(*returninglist, value); +} + +GList * +purple_smileys_get_all(void) +{ + GList *returninglist = NULL; + + g_hash_table_foreach(smiley_shortcut_index, add_smiley_to_list, &returninglist); + + return returninglist; +} + +PurpleSmiley * +purple_smileys_find_by_shortcut(const char *shortcut) +{ + g_return_val_if_fail(shortcut != NULL, NULL); + + return g_hash_table_lookup(smiley_shortcut_index, shortcut); +} + +PurpleSmiley * +purple_smileys_find_by_checksum(const char *checksum) +{ + g_return_val_if_fail(checksum != NULL, NULL); + + return g_hash_table_lookup(smiley_checksum_index, checksum); +} + +const char * +purple_smileys_get_storing_dir(void) +{ + return smileys_dir; +} + +void +purple_smileys_init() +{ + smiley_shortcut_index = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL); + smiley_checksum_index = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL); + + smileys_dir = g_build_filename(purple_user_dir(), SMILEYS_DEFAULT_FOLDER, NULL); + + purple_smileys_load(); +} + +void +purple_smileys_uninit() +{ + if (save_timer != 0) { + purple_timeout_remove(save_timer); + save_timer = 0; + sync_smileys(); + } + + g_hash_table_destroy(smiley_shortcut_index); + g_hash_table_destroy(smiley_checksum_index); + g_free(smileys_dir); +} + diff -r d53f72735830 -r dea8b856466e libpurple/smiley.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/smiley.h Mon May 12 23:17:48 2008 +0000 @@ -0,0 +1,270 @@ +/** + * @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 + +#include "imgstore.h" +#include "util.h" + +/** + * A custom smiley. + * This contains everything Purple will ever need to know about a custom smiley. + * Everything. + * + * PurpleSmiley is a GObject. + */ +typedef struct _PurpleSmiley PurpleSmiley; +typedef struct _PurpleSmileyClass PurpleSmileyClass; + +#define PURPLE_TYPE_SMILEY (purple_smiley_get_type ()) +#define PURPLE_SMILEY(smiley) (G_TYPE_CHECK_INSTANCE_CAST ((smiley), PURPLE_TYPE_SMILEY, PurpleSmiley)) +#define PURPLE_SMILEY_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), PURPLE_TYPE_SMILEY, PurpleSmileyClass)) +#define PURPLE_IS_SMILEY(smiley) (G_TYPE_CHECK_INSTANCE_TYPE ((smiley), PURPLE_TYPE_SMILEY)) +#define PURPLE_IS_SMILEY_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), PURPLE_TYPE_SMILEY)) +#define PURPLE_SMILEY_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), PURPLE_TYPE_SMILEY, PurpleSmileyClass)) + +#ifdef __cplusplus +extern "C" { +#endif + +/**************************************************************************/ +/** @name Custom Smiley API */ +/**************************************************************************/ +/*@{*/ + +/** + * GObject foo. + * @internal. + */ +GType purple_smiley_get_type(void); + +/** + * Creates a new custom smiley structure and populates it. + * + * If a custom smiley with the informed shortcut already exist, it + * will be automaticaly returned. + * + * @param img The image associated with the smiley. + * @param shortcut The custom smiley associated shortcut. + * + * @return The custom smiley structure filled up. + */ +PurpleSmiley * +purple_smiley_new(PurpleStoredImage *img, const char *shortcut); + +/** + * Creates a new custom smiley structure and populates it. + * + * The data is retrieved from an already existent file. + * + * If a custom smiley with the informed shortcut already exist, it + * will be automaticaly returned. + * + * @param shortcut The custom smiley associated shortcut. + * @param filepath The image file to be imported to a + * new custom smiley. + * + * @return The custom smiley structure filled up. + */ +PurpleSmiley * +purple_smiley_new_from_file(const char *shortcut, const char *filepath); + +/** + * Destroy the custom smiley and release the associated resources. + * + * @param smiley The custom smiley. + */ +void +purple_smiley_delete(PurpleSmiley *smiley); + +/** + * Changes the custom smiley's shortcut. + * + * @param smiley The custom smiley. + * @param shortcut The custom smiley associated shortcut. + * + * @return TRUE whether the shortcut is not associated with another + * custom smiley and the parameters are valid. FALSE otherwise. + */ +gboolean +purple_smiley_set_shortcut(PurpleSmiley *smiley, const char *shortcut); + +/** + * Changes the custom smiley's data. + * + * When the filename controling is made outside this API, the param + * #keepfilename must be TRUE. + * Otherwise, the file and filename will be regenerated, and the + * old one will be removed. + * + * @param smiley The custom smiley. + * @param smiley_data The custom smiley data. + * @param smiley_data_len The custom smiley data length. + * @param keepfilename The current custom smiley's filename must be + * kept. + */ +void +purple_smiley_set_data(PurpleSmiley *smiley, guchar *smiley_data, + size_t smiley_data_len, gboolean keepfilename); + +/** + * Returns the custom smiley's associated shortcut. + * + * @param smiley The custom smiley. + * + * @return The shortcut. + */ +const char *purple_smiley_get_shortcut(const PurpleSmiley *smiley); + +/** + * Returns the custom smiley data's checksum. + * + * @param smiley The custom smiley. + * + * @return The checksum. + */ +const char *purple_smiley_get_checksum(const PurpleSmiley *smiley); + +/** + * Returns the PurpleStoredImage with the reference counter incremented. + * + * The returned PurpleStoredImage reference counter must be decremented + * after use. + * + * @param smiley The custom smiley. + * + * @return A PurpleStoredImage reference. + */ +PurpleStoredImage *purple_smiley_get_stored_image(const PurpleSmiley *smiley); + +/** + * Returns the custom smiley's data. + * + * @param smiley The custom smiley. + * @param len If not @c NULL, the length of the icon data returned + * will be set in the location pointed to by this. + * + * @return A pointer to the custom smiley data. + */ +gconstpointer purple_smiley_get_data(const PurpleSmiley *smiley, size_t *len); + +/** + * Returns an extension corresponding to the custom smiley's file type. + * + * @param smiley The custom smiley. + * + * @return The custom smiley's extension, "icon" if unknown, or @c NULL if + * the image data has disappeared. + */ +const char *purple_smiley_get_extension(const PurpleSmiley *smiley); + +/** + * Returns a full path to an custom smiley. + * + * If the custom smiley has data and the file exists in the cache, this + * will return a full path to the cached file. + * + * In general, it is not appropriate to be poking in the file cached + * directly. If you find yourself wanting to use this function, think + * very long and hard about it, and then don't. + * + * @param smiley The custom smiley. + * + * @return A full path to the file, or @c NULL under various conditions. + * The caller should use #g_free to free the returned string. + */ +char *purple_smiley_get_full_path(PurpleSmiley *smiley); + +/*@}*/ + + +/**************************************************************************/ +/** @name Custom Smiley Subsystem API */ +/**************************************************************************/ +/*@{*/ + +/** + * Returns a list of all custom smileys. The caller should free the list. + * + * @return A list of all custom smileys. + */ +GList * +purple_smileys_get_all(void); + +/** + * Returns the custom smiley given it's shortcut. + * + * @param shortcut The custom smiley's shortcut. + * + * @return The custom smiley (with a reference for the caller) if found, + * or @c NULL if not found. + */ +PurpleSmiley * +purple_smileys_find_by_shortcut(const char *shortcut); + +/** + * Returns the custom smiley given it's checksum. + * + * @param checksum The custom smiley's checksum. + * + * @return The custom smiley (with a reference for the caller) if found, + * or @c NULL if not found. + */ +PurpleSmiley * +purple_smileys_find_by_checksum(const char *checksum); + +/** + * Returns the directory used to store custom smiley cached files. + * + * The default directory is PURPLEDIR/smileys, unless otherwise specified + * by purple_buddy_icons_set_cache_dir(). + * + * @return The directory to store custom smyles cached files to. + */ +const char *purple_smileys_get_storing_dir(void); + +/** + * Initializes the custom smiley subsystem. + */ +void purple_smileys_init(void); + +/** + * Uninitializes the custom smiley subsystem. + */ +void purple_smileys_uninit(void); + +/*@}*/ + +#ifdef __cplusplus +} +#endif + +#endif /* _PURPLE_SMILEY_H_ */ + diff -r d53f72735830 -r dea8b856466e libpurple/util.c --- a/libpurple/util.c Mon May 12 02:19:06 2008 +0000 +++ b/libpurple/util.c Mon May 12 23:17:48 2008 +0000 @@ -939,7 +939,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 d53f72735830 -r dea8b856466e libpurple/util.h --- a/libpurple/util.h Mon May 12 02:19:06 2008 +0000 +++ b/libpurple/util.h Mon May 12 23:17:48 2008 +0000 @@ -706,6 +706,11 @@ purple_util_get_image_extension(gconstpointer data, size_t len); /** + * Returns a SHA-1 hash string of the data passed in. + */ +char *purple_util_get_image_checksum(gconstpointer image_data, size_t image_len); + +/** * @return A hex encoded version of the SHA-1 hash of the data passed * in with the correct file extention appended. The file * extension is determined by calling diff -r d53f72735830 -r dea8b856466e pidgin/Makefile.am --- a/pidgin/Makefile.am Mon May 12 02:19:06 2008 +0000 +++ b/pidgin/Makefile.am Mon May 12 23:17:48 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 d53f72735830 -r dea8b856466e pidgin/Makefile.mingw --- a/pidgin/Makefile.mingw Mon May 12 02:19:06 2008 +0000 +++ b/pidgin/Makefile.mingw Mon May 12 23:17:48 2008 +0000 @@ -86,6 +86,7 @@ gtkroomlist.c \ gtksavedstatuses.c \ gtkscrollbook.c \ + gtksmiley.c \ gtksound.c \ gtksourceiter.c \ gtksourceundomanager.c \ diff -r d53f72735830 -r dea8b856466e pidgin/gtkaccount.c --- a/pidgin/gtkaccount.c Mon May 12 02:19:06 2008 +0000 +++ b/pidgin/gtkaccount.c Mon May 12 23:17:48 2008 +0000 @@ -176,14 +176,7 @@ } if (dialog->icon_img != NULL) { - GdkPixbufLoader *loader = gdk_pixbuf_loader_new(); - gdk_pixbuf_loader_write(loader, purple_imgstore_get_data(dialog->icon_img), - purple_imgstore_get_size(dialog->icon_img), NULL); - gdk_pixbuf_loader_close(loader, NULL); - pixbuf = gdk_pixbuf_loader_get_pixbuf(loader); - if (pixbuf) - g_object_ref(pixbuf); - g_object_unref(loader); + pixbuf = pidgin_pixbuf_from_imgstore(dialog->icon_img); } if (pixbuf && dialog->prpl_info && @@ -2040,21 +2033,14 @@ } if (img != NULL) { - GdkPixbufLoader *loader = gdk_pixbuf_loader_new(); GdkPixbuf *buddyicon_pixbuf; - - gdk_pixbuf_loader_write(loader, purple_imgstore_get_data(img), - purple_imgstore_get_size(img), NULL); - gdk_pixbuf_loader_close(loader, NULL); - buddyicon_pixbuf = gdk_pixbuf_loader_get_pixbuf(loader); - + buddyicon_pixbuf = pidgin_pixbuf_from_imgstore(img); purple_imgstore_unref(img); if (buddyicon_pixbuf != NULL) { buddyicon = gdk_pixbuf_scale_simple(buddyicon_pixbuf, 22, 22, GDK_INTERP_HYPER); + g_object_unref(G_OBJECT(buddyicon_pixbuf)); } - - g_object_unref(loader); } gtk_list_store_set(store, iter, diff -r d53f72735830 -r dea8b856466e pidgin/gtkblist.c --- a/pidgin/gtkblist.c Mon May 12 02:19:06 2008 +0000 +++ b/pidgin/gtkblist.c Mon May 12 23:17:48 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" @@ -3178,6 +3179,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 d53f72735830 -r dea8b856466e pidgin/gtkconv.c --- a/pidgin/gtkconv.c Mon May 12 02:19:06 2008 +0000 +++ b/pidgin/gtkconv.c Mon May 12 23:17:48 2008 +0000 @@ -159,8 +159,6 @@ static void update_typing_message(PidginConversation *gtkconv, const char *message); static const char *item_factory_translate_func (const char *path, gpointer func_data); gboolean pidgin_conv_has_focus(PurpleConversation *conv); -static void pidgin_conv_custom_smiley_allocated(GdkPixbufLoader *loader, gpointer user_data); -static void pidgin_conv_custom_smiley_closed(GdkPixbufLoader *loader, gpointer user_data); static GdkColor* generate_nick_colors(guint *numcolors, GdkColor background); static gboolean color_is_visible(GdkColor foreground, GdkColor background, int color_contrast, int brightness_contrast); static void pidgin_conv_update_fields(PurpleConversation *conv, PidginConvFields fields); @@ -5180,6 +5178,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 @@ -5486,8 +5490,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); @@ -5646,14 +5648,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 */ @@ -5899,8 +5895,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(), @@ -6128,119 +6123,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; } @@ -6465,6 +6365,11 @@ if(conv->features & PURPLE_CONNECTION_NO_IMAGES) buttons &= ~GTK_IMHTML_IMAGE; + if (conv->features & PURPLE_CONNECTION_ALLOW_CUSTOM_SMILEY) + buttons |= GTK_IMHTML_CUSTOM_SMILEY; + else + buttons &= ~GTK_IMHTML_CUSTOM_SMILEY; + gtk_imhtml_set_format_functions(GTK_IMHTML(gtkconv->entry), buttons); if (account != NULL) gtk_imhtmltoolbar_associate_smileys(GTK_IMHTMLTOOLBAR(gtkconv->toolbar), purple_account_get_protocol_id(account)); diff -r d53f72735830 -r dea8b856466e pidgin/gtkimhtml.c --- a/pidgin/gtkimhtml.c Mon May 12 02:19:06 2008 +0000 +++ b/pidgin/gtkimhtml.c Mon May 12 23:17:48 2008 +0000 @@ -5263,7 +5263,150 @@ if (flags & PURPLE_CONNECTION_NO_IMAGES) buttons &= ~GTK_IMHTML_IMAGE; + if (flags & PURPLE_CONNECTION_ALLOW_CUSTOM_SMILEY) + buttons |= GTK_IMHTML_CUSTOM_SMILEY; + else + buttons &= ~GTK_IMHTML_CUSTOM_SMILEY; + gtk_imhtml_set_format_functions(imhtml, buttons); } - +/******* + * GtkIMHtmlSmiley functions + *******/ +static void gtk_custom_smiley_allocated(GdkPixbufLoader *loader, gpointer user_data) +{ + GtkIMHtmlSmiley *smiley; + + smiley = (GtkIMHtmlSmiley *)user_data; + smiley->icon = gdk_pixbuf_loader_get_animation(loader); + + if (smiley->icon) + g_object_ref(G_OBJECT(smiley->icon)); +#ifdef DEBUG_CUSTOM_SMILEY + purple_debug_info("custom-smiley", "gtk_custom_smiley_allocated(): got GdkPixbufAnimation %p for smiley '%s'\n", smiley->icon, smiley->smile); +#endif +} + +static void gtk_custom_smiley_closed(GdkPixbufLoader *loader, gpointer user_data) +{ + GtkIMHtmlSmiley *smiley; + GtkWidget *icon = NULL; + GtkTextChildAnchor *anchor = NULL; + GSList *current = NULL; + + smiley = (GtkIMHtmlSmiley *)user_data; + if (!smiley->imhtml) { +#ifdef DEBUG_CUSTOM_SMILEY + purple_debug_error("custom-smiley", "gtk_custom_smiley_closed(): orphan smiley found: %p\n", smiley); +#endif + g_object_unref(G_OBJECT(loader)); + smiley->loader = NULL; + return; + } + + for (current = smiley->anchors; current; current = g_slist_next(current)) { + + icon = gtk_image_new_from_animation(smiley->icon); + +#ifdef DEBUG_CUSTOM_SMILEY + purple_debug_info("custom-smiley", "gtk_custom_smiley_closed(): got GtkImage %p from GtkPixbufAnimation %p for smiley '%s'\n", + icon, smiley->icon, smiley->smile); +#endif + if (icon) { + GList *wids; + gtk_widget_show(icon); + + anchor = GTK_TEXT_CHILD_ANCHOR(current->data); + wids = gtk_text_child_anchor_get_widgets(anchor); + + g_object_set_data_full(G_OBJECT(anchor), "gtkimhtml_plaintext", purple_unescape_html(smiley->smile), g_free); + g_object_set_data_full(G_OBJECT(anchor), "gtkimhtml_htmltext", g_strdup(smiley->smile), g_free); + + if (smiley->imhtml) { + if (wids) { + GList *children = gtk_container_get_children(GTK_CONTAINER(wids->data)); + g_list_foreach(children, (GFunc)gtk_widget_destroy, NULL); + g_list_free(children); + gtk_container_add(GTK_CONTAINER(wids->data), icon); + } else + gtk_text_view_add_child_at_anchor(GTK_TEXT_VIEW(smiley->imhtml), icon, anchor); + } + g_list_free(wids); + } + + } + + g_slist_free(smiley->anchors); + smiley->anchors = NULL; + + g_object_unref(G_OBJECT(loader)); + smiley->loader = NULL; +} + +static void +gtk_custom_smiley_size_prepared(GdkPixbufLoader *loader, gint width, gint height, gpointer data) +{ +#define CUSTOM_SMILEY_SIZE 96 /* XXX: Should this be a theme setting? */ + if (width <= CUSTOM_SMILEY_SIZE && height <= CUSTOM_SMILEY_SIZE) + return; + + if (width >= height) { + height = height * CUSTOM_SMILEY_SIZE / width; + width = CUSTOM_SMILEY_SIZE; + } else { + width = width * CUSTOM_SMILEY_SIZE / height; + height = CUSTOM_SMILEY_SIZE; + } + + gdk_pixbuf_loader_set_size(loader, width, height); +} + +void +gtk_imhtml_smiley_reload(GtkIMHtmlSmiley *smiley) +{ + if (smiley->icon) + g_object_unref(smiley->icon); + if (smiley->loader) + g_object_unref(smiley->loader); /* XXX: does this crash? */ + + smiley->icon = NULL; + smiley->loader = NULL; + + if (smiley->file) { + /* We do not use the pixbuf loader for a smiley that can be loaded + * from a file. (e.g., local custom smileys) + */ + return; + } + + smiley->loader = gdk_pixbuf_loader_new(); + + g_signal_connect(smiley->loader, "area_prepared", G_CALLBACK(gtk_custom_smiley_allocated), smiley); + g_signal_connect(smiley->loader, "closed", G_CALLBACK(gtk_custom_smiley_closed), smiley); + g_signal_connect(smiley->loader, "size_prepared", G_CALLBACK(gtk_custom_smiley_size_prepared), smiley); +} + +GtkIMHtmlSmiley *gtk_imhtml_smiley_create(const char *file, const char *shortcut, gboolean hide, + GtkIMHtmlSmileyFlags flags) +{ + GtkIMHtmlSmiley *smiley = g_new0(GtkIMHtmlSmiley, 1); + smiley->file = g_strdup(file); + smiley->smile = g_strdup(shortcut); + smiley->hidden = hide; + smiley->flags = flags; + gtk_imhtml_smiley_reload(smiley); + return smiley; +} + +void gtk_imhtml_smiley_destroy(GtkIMHtmlSmiley *smiley) +{ + g_free(smiley->smile); + g_free(smiley->file); + if (smiley->icon) + g_object_unref(smiley->icon); + if (smiley->loader) + g_object_unref(smiley->loader); + g_free(smiley); +} + diff -r d53f72735830 -r dea8b856466e pidgin/gtkimhtml.h --- a/pidgin/gtkimhtml.h Mon May 12 02:19:06 2008 +0000 +++ b/pidgin/gtkimhtml.h Mon May 12 23:17:48 2008 +0000 @@ -76,6 +76,7 @@ GTK_IMHTML_SMILEY = 1 << 11, GTK_IMHTML_LINKDESC = 1 << 12, GTK_IMHTML_STRIKE = 1 << 13, + GTK_IMHTML_CUSTOM_SMILEY = 1 << 14, GTK_IMHTML_ALL = -1 } GtkIMHtmlButtons; @@ -852,6 +853,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 d53f72735830 -r dea8b856466e pidgin/gtkimhtmltoolbar.c --- a/pidgin/gtkimhtmltoolbar.c Mon May 12 02:19:06 2008 +0000 +++ b/pidgin/gtkimhtmltoolbar.c Mon May 12 23:17:48 2008 +0000 @@ -36,6 +36,7 @@ #include "gtkdialogs.h" #include "gtkimhtmltoolbar.h" +#include "gtksmiley.h" #include "gtkthemes.h" #include "gtkutils.h" @@ -579,8 +580,7 @@ } static gboolean -close_smiley_dialog(GtkWidget *widget, GdkEvent *event, - GtkIMHtmlToolbar *toolbar) +close_smiley_dialog(GtkIMHtmlToolbar *toolbar) { gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(toolbar->smiley), FALSE); return FALSE; @@ -601,24 +601,30 @@ g_free(escaped_smiley); - close_smiley_dialog(NULL, NULL, toolbar); + close_smiley_dialog(toolbar); } /* smiley buttons list */ struct smiley_button_list { int width, height; GtkWidget *button; + const GtkIMHtmlSmiley *smiley; struct smiley_button_list *next; }; static struct smiley_button_list * -sort_smileys(struct smiley_button_list *ls, GtkIMHtmlToolbar *toolbar, int *width, char *filename, char *face) +sort_smileys(struct smiley_button_list *ls, GtkIMHtmlToolbar *toolbar, + int *width, const GtkIMHtmlSmiley *smiley, + const GSList *custom_smileys) { GtkWidget *image; GtkWidget *button; GtkRequisition size; struct smiley_button_list *cur; struct smiley_button_list *it, *it_last; + const gchar *filename = smiley->file; + gchar *face = smiley->smile; + PurpleSmiley *psmiley = NULL; cur = g_new0(struct smiley_button_list, 1); it = ls; @@ -626,6 +632,35 @@ image = gtk_image_new_from_file(filename); gtk_widget_size_request(image, &size); + + if (size.width > 24 && + smiley->flags & GTK_IMHTML_SMILEY_CUSTOM) { /* This is a custom smiley, let's scale it */ + GdkPixbuf *pixbuf = NULL; + GtkImageType type; + + type = gtk_image_get_storage_type(GTK_IMAGE(image)); + + if (type == GTK_IMAGE_PIXBUF) { + pixbuf = gtk_image_get_pixbuf(GTK_IMAGE(image)); + } else if (type == GTK_IMAGE_ANIMATION) { + GdkPixbufAnimation *animation; + + animation = gtk_image_get_animation(GTK_IMAGE(image)); + + pixbuf = gdk_pixbuf_animation_get_static_image(animation); + } + + if (pixbuf != NULL) { + GdkPixbuf *resized; + resized = gdk_pixbuf_scale_simple(pixbuf, 24, 24, + GDK_INTERP_HYPER); + + gtk_image_set_from_pixbuf(GTK_IMAGE(image), resized); /* This unrefs pixbuf */ + gtk_widget_size_request(image, &size); + g_object_unref(G_OBJECT(resized)); + } + } + (*width) += size.width; button = gtk_button_new(); @@ -639,10 +674,26 @@ /* these look really weird with borders */ gtk_button_set_relief(GTK_BUTTON(button), GTK_RELIEF_NONE); + psmiley = purple_smileys_find_by_shortcut(smiley->smile); + /* If this is a "non-custom" smiley, check to see if its shortcut is + "shadowed" by any custom smiley. This can only happen if the connection + is custom smiley-enabled */ + if (psmiley && !(smiley->flags & GTK_IMHTML_SMILEY_CUSTOM)) { + gtk_tooltips_set_tip(toolbar->tooltips, button, + _("This smiley is disabled because a custom smiley exists for this shortcut."), + NULL); + gtk_widget_set_sensitive(button, FALSE); + } else if (psmiley) { + /* Remove the button if the smiley is destroyed */ + g_signal_connect_object(G_OBJECT(psmiley), "destroy", G_CALLBACK(gtk_widget_destroy), + button, G_CONNECT_SWAPPED); + } + /* set current element to add */ cur->height = size.height; cur->width = size.width; cur->button = button; + cur->smiley = smiley; cur->next = ls; /* check where to insert by height */ @@ -661,7 +712,7 @@ smiley_is_unique(GSList *list, GtkIMHtmlSmiley *smiley) { while (list) { - GtkIMHtmlSmiley *cur = list->data; + GtkIMHtmlSmiley *cur = (GtkIMHtmlSmiley *) list->data; if (!strcmp(cur->file, smiley->file)) return FALSE; list = list->next; @@ -675,7 +726,7 @@ if ((event->type == GDK_KEY_PRESS && event->key.keyval == GDK_Escape) || (event->type == GDK_BUTTON_PRESS && event->button.button == 1)) { - close_smiley_dialog(NULL, NULL, toolbar); + close_smiley_dialog(toolbar); return TRUE; } @@ -683,11 +734,43 @@ } static void +add_smiley_list(GtkWidget *container, struct smiley_button_list *list, + int max_width, gboolean custom) +{ + GtkWidget *line; + int line_width = 0; + + if (!list) + return; + + line = gtk_hbox_new(FALSE, 0); + gtk_box_pack_start(GTK_BOX(container), line, FALSE, FALSE, 0); + for (; list; list = list->next) { + if (custom != !!(list->smiley->flags & GTK_IMHTML_SMILEY_CUSTOM)) + continue; + gtk_box_pack_start(GTK_BOX(line), list->button, FALSE, FALSE, 0); + gtk_widget_show(list->button); + line_width += list->width; + if (line_width >= max_width) { + if (list->next) { + line = gtk_hbox_new(FALSE, 0); + gtk_box_pack_start(GTK_BOX(container), line, FALSE, FALSE, 0); + } + line_width = 0; + } + } +} + +static void insert_smiley_cb(GtkWidget *smiley, GtkIMHtmlToolbar *toolbar) { GtkWidget *dialog; GtkWidget *smiley_table = NULL; GSList *smileys, *unique_smileys = NULL; + const GSList *custom_smileys = NULL; + gboolean supports_custom = FALSE; + GtkRequisition req; + GtkWidget *scrolled, *viewport; if (!gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(smiley))) { destroy_smiley_dialog(toolbar); @@ -701,61 +784,73 @@ smileys = pidgin_themes_get_proto_smileys(NULL); while(smileys) { - GtkIMHtmlSmiley *smiley = smileys->data; + GtkIMHtmlSmiley *smiley = (GtkIMHtmlSmiley *) smileys->data; if(!smiley->hidden) { - if(smiley_is_unique(unique_smileys, smiley)) + if(smiley_is_unique(unique_smileys, smiley)) { unique_smileys = g_slist_append(unique_smileys, smiley); + } } smileys = smileys->next; } + supports_custom = (gtk_imhtml_get_format_functions(GTK_IMHTML(toolbar->imhtml)) & GTK_IMHTML_CUSTOM_SMILEY); + if (toolbar->imhtml && supports_custom) { + const GSList *iterator = NULL; + custom_smileys = pidgin_smileys_get_all(); + + for (iterator = custom_smileys ; iterator ; + iterator = g_slist_next(iterator)) { + GtkIMHtmlSmiley *smiley = (GtkIMHtmlSmiley *) iterator->data; + unique_smileys = g_slist_append(unique_smileys, smiley); + } + } dialog = pidgin_create_dialog(_("Smile!"), 0, "smiley_dialog", FALSE); - gtk_window_set_position(GTK_WINDOW(dialog), GTK_WIN_POS_MOUSE); if (unique_smileys != NULL) { - struct smiley_button_list *ls, *it, *it_tmp; - GtkWidget *line; - int line_width = 0; - int max_line_width, num_lines; - int col=0; + struct smiley_button_list *ls; + int max_line_width, num_lines, button_width = 0; /* We use hboxes packed in a vbox */ ls = NULL; - line = gtk_hbox_new(FALSE, 0); - line_width = 0; max_line_width = 0; num_lines = floor(sqrt(g_slist_length(unique_smileys))); smiley_table = gtk_vbox_new(FALSE, 0); + if (supports_custom) { + GtkWidget *manage = gtk_button_new_with_mnemonic(_("_Manage custom smileys")); + GtkRequisition req; + g_signal_connect(G_OBJECT(manage), "clicked", + G_CALLBACK(pidgin_smiley_manager_show), NULL); + g_signal_connect_swapped(G_OBJECT(manage), "clicked", + G_CALLBACK(gtk_widget_destroy), dialog); + gtk_box_pack_end(GTK_BOX(smiley_table), manage, TRUE, FALSE, 0); + gtk_widget_size_request(manage, &req); + button_width = req.width; + } + /* create list of smileys sorted by height */ while (unique_smileys) { - GtkIMHtmlSmiley *smiley = unique_smileys->data; + GtkIMHtmlSmiley *smiley = (GtkIMHtmlSmiley *) unique_smileys->data; if (!smiley->hidden) { - ls = sort_smileys(ls, toolbar, &max_line_width, smiley->file, smiley->smile); + ls = sort_smileys(ls, toolbar, &max_line_width, smiley, custom_smileys); } unique_smileys = g_slist_delete_link(unique_smileys, unique_smileys); } + /* The window will be at least as wide as the 'Manage ..' button */ + max_line_width = MAX(button_width, max_line_width / num_lines); + /* pack buttons of the list */ - max_line_width = max_line_width / num_lines; - it = ls; - while (it != NULL) - { - it_tmp = it; - gtk_box_pack_start(GTK_BOX(line), it->button, FALSE, FALSE, 0); - gtk_widget_show(it->button); - line_width += it->width; - if (line_width >= max_line_width) { - gtk_box_pack_start(GTK_BOX(smiley_table), line, FALSE, FALSE, 0); - line = gtk_hbox_new(FALSE, 0); - line_width = 0; - col = 0; - } - col++; - it = it->next; - g_free(it_tmp); + add_smiley_list(smiley_table, ls, max_line_width, FALSE); + if (supports_custom) { + gtk_box_pack_start(GTK_BOX(smiley_table), gtk_hseparator_new(), TRUE, FALSE, 0); + add_smiley_list(smiley_table, ls, max_line_width, TRUE); } - gtk_box_pack_start(GTK_BOX(smiley_table), line, FALSE, TRUE, 0); + while (ls) { + struct smiley_button_list *tmp = ls->next; + g_free(ls); + ls = tmp; + } gtk_widget_add_events(dialog, GDK_KEY_PRESS_MASK); } @@ -765,19 +860,41 @@ g_signal_connect(G_OBJECT(dialog), "button-press-event", (GCallback)smiley_dialog_input_cb, toolbar); } - g_signal_connect(G_OBJECT(dialog), "key-press-event", (GCallback)smiley_dialog_input_cb, toolbar); - gtk_container_add(GTK_CONTAINER(pidgin_dialog_get_vbox(GTK_DIALOG(dialog))), smiley_table); + scrolled = gtk_scrolled_window_new (NULL, NULL); + gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW (scrolled), GTK_SHADOW_NONE); + gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW (scrolled), + GTK_POLICY_NEVER, GTK_POLICY_NEVER); + gtk_container_add(GTK_CONTAINER(pidgin_dialog_get_vbox(GTK_DIALOG(dialog))), scrolled); + gtk_widget_show(scrolled); + gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(scrolled), smiley_table); gtk_widget_show(smiley_table); + viewport = gtk_widget_get_parent(smiley_table); + gtk_viewport_set_shadow_type(GTK_VIEWPORT(viewport), GTK_SHADOW_NONE); + /* connect signals */ - g_signal_connect(G_OBJECT(dialog), "delete_event", - G_CALLBACK(close_smiley_dialog), toolbar); + g_signal_connect_swapped(G_OBJECT(dialog), "destroy", G_CALLBACK(close_smiley_dialog), toolbar); + g_signal_connect(G_OBJECT(dialog), "key-press-event", G_CALLBACK(smiley_dialog_input_cb), toolbar); + + gtk_window_set_transient_for(GTK_WINDOW(dialog), + GTK_WINDOW(gtk_widget_get_toplevel(GTK_WIDGET(toolbar)))); /* show everything */ gtk_widget_show_all(dialog); - gtk_window_set_transient_for(GTK_WINDOW(dialog), - GTK_WINDOW(gtk_widget_get_toplevel(GTK_WIDGET(toolbar)))); + + gtk_widget_size_request(viewport, &req); + gtk_widget_set_size_request(scrolled, req.width, req.height); + + /* The window has to be made resizable, and the scrollbars in the scrolled window + * enabled only after setting the desired size of the window. If we do either of + * these tasks before now, GTK+ miscalculates the required size, and erronously + * makes one or both scrollbars visible (sometimes). + * I too think this hack is gross. But I couldn't find a better way -- sadrul */ + gtk_window_set_resizable(GTK_WINDOW(dialog), TRUE); + gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW (scrolled), + GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC); + #ifdef _WIN32 winpidgin_ensure_onscreen(dialog); #endif diff -r d53f72735830 -r dea8b856466e pidgin/gtkmain.c --- a/pidgin/gtkmain.c Mon May 12 02:19:06 2008 +0000 +++ b/pidgin/gtkmain.c Mon May 12 23:17:48 2008 +0000 @@ -62,6 +62,7 @@ #include "gtkroomlist.h" #include "gtksavedstatuses.h" #include "gtksession.h" +#include "gtksmiley.h" #include "gtksound.h" #include "gtkthemes.h" #include "gtkutils.h" @@ -315,6 +316,7 @@ pidgin_roomlist_init(); pidgin_log_init(); pidgin_docklet_init(); + pidgin_smileys_init(); } static GHashTable *ui_info = NULL; @@ -331,6 +333,7 @@ pidgin_plugins_save(); /* Uninit */ + pidgin_smileys_uninit(); pidgin_conversations_uninit(); pidgin_status_uninit(); pidgin_docklet_uninit(); diff -r d53f72735830 -r dea8b856466e pidgin/gtksmiley.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/pidgin/gtksmiley.c Mon May 12 23:17:48 2008 +0000 @@ -0,0 +1,667 @@ +/** + * @file gtksmiley.c GTK+ Smiley Manager API + * @ingroup pidgin + */ + +/* + * pidgin + * + * Pidgin is the legal property of its developers, whose names are too numerous + * to list here. Please refer to the COPYRIGHT file distributed with this + * source distribution. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA + */ + +#include "internal.h" +#include "pidgin.h" + +#include "debug.h" +#include "notify.h" +#include "smiley.h" + +#include "gtkimhtml.h" +#include "gtksmiley.h" +#include "gtkutils.h" +#include "pidginstock.h" + +#define PIDGIN_RESPONSE_EDIT 1000 + +typedef struct +{ + PurpleSmiley *smiley; + GtkWidget *parent; + GtkWidget *smile; + GtkWidget *smiley_image; + gchar *filename; +} PidginSmiley; + +typedef struct +{ + GtkWidget *window; + + GtkWidget *treeview; + GtkListStore *model; +} SmileyManager; + +enum +{ + ICON, + SHORTCUT, + SMILEY, + N_COL +}; + +static SmileyManager *smiley_manager = NULL; +static GSList *gtk_smileys = NULL; + +static void +pidgin_smiley_destroy(PidginSmiley *smiley) +{ + gtk_widget_destroy(smiley->parent); + g_free(smiley->filename); + g_free(smiley); +} + +/****************************************************************************** + * GtkIMHtmlSmileys stuff + *****************************************************************************/ +/* Perhaps these should be in gtkimhtml.c instead. -- sadrul */ +static void add_gtkimhtml_to_list(GtkIMHtmlSmiley *gtksmiley) +{ + gtk_smileys = g_slist_prepend(gtk_smileys, gtksmiley); + + purple_debug_info("gtksmiley", "adding %s to gtk_smileys\n", gtksmiley->smile); +} + +static void +shortcut_changed_cb(PurpleSmiley *smiley, gpointer dontcare, GtkIMHtmlSmiley *gtksmiley) +{ + g_free(gtksmiley->smile); + gtksmiley->smile = g_strdup(purple_smiley_get_shortcut(smiley)); +} + +static GtkIMHtmlSmiley *smiley_purple_to_gtkimhtml(PurpleSmiley *smiley) +{ + GtkIMHtmlSmiley *gtksmiley; + gchar *filename; + const gchar *file; + + file = purple_imgstore_get_filename(purple_smiley_get_stored_image(smiley)); + + filename = g_build_filename(purple_smileys_get_storing_dir(), file, NULL); + + gtksmiley = gtk_imhtml_smiley_create(filename, purple_smiley_get_shortcut(smiley), + FALSE, GTK_IMHTML_SMILEY_CUSTOM); + g_free(filename); + + /* Make sure the shortcut for the GtkIMHtmlSmiley is updated with the PurpleSmiley */ + g_signal_connect(G_OBJECT(smiley), "notify::shortcut", + G_CALLBACK(shortcut_changed_cb), gtksmiley); + + return gtksmiley; +} + +void pidgin_smiley_del_from_list(PurpleSmiley *smiley) +{ + GSList *list = NULL; + GtkIMHtmlSmiley *gtksmiley; + + if (gtk_smileys == NULL) + return; + + list = gtk_smileys; + + for (; list; list = list->next) { + gtksmiley = (GtkIMHtmlSmiley*)list->data; + + if (strcmp(gtksmiley->smile, purple_smiley_get_shortcut(smiley))) + continue; + + gtk_imhtml_smiley_destroy(gtksmiley); + g_signal_handlers_disconnect_matched(G_OBJECT(smiley), G_SIGNAL_MATCH_DATA, + 0, 0, NULL, NULL, gtksmiley); + break; + } + + if (list) + gtk_smileys = g_slist_delete_link(gtk_smileys, list); +} + +void pidgin_smiley_add_to_list(PurpleSmiley *smiley) +{ + GtkIMHtmlSmiley *gtksmiley; + + gtksmiley = smiley_purple_to_gtkimhtml(smiley); + add_gtkimhtml_to_list(gtksmiley); + g_signal_connect(G_OBJECT(smiley), "destroy", G_CALLBACK(pidgin_smiley_del_from_list), NULL); +} + +void pidgin_smileys_init(void) +{ + GList *smileys; + PurpleSmiley *smiley; + + if (gtk_smileys != NULL) + return; + + smileys = purple_smileys_get_all(); + + for (; smileys; smileys = g_list_delete_link(smileys, smileys)) { + smiley = (PurpleSmiley*)smileys->data; + + pidgin_smiley_add_to_list(smiley); + } +} + +void pidgin_smileys_uninit(void) +{ + GSList *list; + GtkIMHtmlSmiley *gtksmiley; + + list = gtk_smileys; + + if (list == NULL) + return; + + for (; list; list = g_slist_delete_link(list, list)) { + gtksmiley = (GtkIMHtmlSmiley*)list->data; + gtk_imhtml_smiley_destroy(gtksmiley); + } + + gtk_smileys = NULL; +} + +GSList *pidgin_smileys_get_all(void) +{ + return gtk_smileys; +} + +/****************************************************************************** + * Manager stuff + *****************************************************************************/ + +static void refresh_list(void); + +/****************************************************************************** + * The Add dialog + ******************************************************************************/ + +static void do_add(GtkWidget *widget, PidginSmiley *s) +{ + const gchar *entry; + PurpleSmiley *emoticon; + + entry = gtk_entry_get_text(GTK_ENTRY(s->smile)); + emoticon = purple_smileys_find_by_shortcut(entry); + if (emoticon && emoticon != s->smiley) { + purple_notify_error(s->parent, _("Custom Smiley"), + _("Duplicate Shortcut"), + _("A custom smiley for the selected shortcut already exists. Please specify a different shortcut.")); + return; + } + + if (s->smiley) { + if (s->filename) { + gchar *data = NULL; + size_t len; + GError *err = NULL; + + if (!g_file_get_contents(s->filename, &data, &len, &err)) { + purple_debug_error("gtksmiley", "Error reading %s: %s\n", + s->filename, err->message); + g_error_free(err); + + return; + } + purple_smiley_set_data(s->smiley, (guchar*)data, len, FALSE); + } + purple_smiley_set_shortcut(s->smiley, entry); + } else { + if ((s->filename == NULL || *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"); + emoticon = purple_smiley_new_from_file(entry, s->filename); + pidgin_smiley_add_to_list(emoticon); + } + + if (smiley_manager != NULL) + refresh_list(); + + gtk_widget_destroy(s->parent); +} + +static void do_add_select_cb(GtkWidget *widget, gint resp, PidginSmiley *s) +{ + switch (resp) { + case GTK_RESPONSE_ACCEPT: + do_add(widget, s); + break; + case GTK_RESPONSE_DELETE_EVENT: + case GTK_RESPONSE_CANCEL: + gtk_widget_destroy(s->parent); + break; + default: + purple_debug_error("gtksmiley", "no valid response\n"); + break; + } +} + +static void do_add_file_cb(const char *filename, gpointer data) +{ + PidginSmiley *s = data; + GdkPixbuf *pixbuf; + + if (!filename) + return; + + g_free(s->filename); + s->filename = g_strdup(filename); + pixbuf = gdk_pixbuf_new_from_file_at_scale(filename, 64, 64, FALSE, NULL); + gtk_image_set_from_pixbuf(GTK_IMAGE(s->smiley_image), pixbuf); + if (pixbuf) + gdk_pixbuf_unref(pixbuf); + gtk_widget_grab_focus(s->smile); +} + +static void +open_image_selector(GtkWidget *widget, PidginSmiley *psmiley) +{ + GtkWidget *file_chooser; + file_chooser = pidgin_buddy_icon_chooser_new(GTK_WINDOW(gtk_widget_get_toplevel(widget)), + do_add_file_cb, psmiley); + gtk_window_set_title(GTK_WINDOW(file_chooser), _("Custom Smiley")); + gtk_window_set_role(GTK_WINDOW(file_chooser), "file-selector-custom-smiley"); + gtk_widget_show_all(file_chooser); +} + +void pidgin_smiley_edit(GtkWidget *widget, PurpleSmiley *smiley) +{ + GtkWidget *vbox; + GtkWidget *hbox; + GtkWidget *label; + GtkWidget *filech; + GtkWidget *window; + GdkPixbuf *pixbuf = NULL; + PurpleStoredImage *stored_img; + + PidginSmiley *s = g_new0(PidginSmiley, 1); + s->smiley = smiley; + + window = gtk_dialog_new_with_buttons(smiley ? _("Edit Smiley") : _("Add Smiley"), + GTK_WINDOW(widget), + GTK_DIALOG_DESTROY_WITH_PARENT | GTK_DIALOG_NO_SEPARATOR, + smiley ? GTK_STOCK_SAVE : GTK_STOCK_ADD, GTK_RESPONSE_ACCEPT, + GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, + NULL); + s->parent = window; + + gtk_container_set_border_width(GTK_CONTAINER(window), PIDGIN_HIG_BORDER); + + g_signal_connect(window, "response", G_CALLBACK(do_add_select_cb), s); + + /* The vbox */ + vbox = gtk_vbox_new(FALSE, PIDGIN_HIG_BORDER); + gtk_container_add(GTK_CONTAINER(GTK_DIALOG(window)->vbox), vbox); + gtk_widget_show(vbox); + + /* The hbox */ + hbox = gtk_hbox_new(FALSE, PIDGIN_HIG_BORDER); + gtk_container_add(GTK_CONTAINER(GTK_VBOX(vbox)), hbox); + + label = gtk_label_new_with_mnemonic(_("Smiley _Image")); + gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 0); + gtk_widget_show(label); + + filech = gtk_button_new(); + gtk_box_pack_end(GTK_BOX(hbox), filech, FALSE, FALSE, 0); + pidgin_set_accessible_label(filech, label); + + s->smiley_image = gtk_image_new(); + gtk_container_add(GTK_CONTAINER(filech), s->smiley_image); + if (smiley && (stored_img = purple_smiley_get_stored_image(smiley))) { + pixbuf = pidgin_pixbuf_from_imgstore(stored_img); + purple_imgstore_unref(stored_img); + } else { + GtkIconSize icon_size = gtk_icon_size_from_name(PIDGIN_ICON_SIZE_TANGO_SMALL); + pixbuf = gtk_widget_render_icon(window, PIDGIN_STOCK_TOOLBAR_SELECT_AVATAR, + icon_size, "PidginSmiley"); + } + + gtk_image_set_from_pixbuf(GTK_IMAGE(s->smiley_image), pixbuf); + if (pixbuf != NULL) + g_object_unref(G_OBJECT(pixbuf)); + g_signal_connect(G_OBJECT(filech), "clicked", G_CALLBACK(open_image_selector), s); + + gtk_widget_show_all(hbox); + + /* info */ + hbox = gtk_hbox_new(FALSE, PIDGIN_HIG_BORDER); + gtk_container_add(GTK_CONTAINER(GTK_VBOX(vbox)),hbox); + + /* Smiley shortcut */ + label = gtk_label_new_with_mnemonic(_("Smiley S_hortcut")); + gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 0); + gtk_widget_show(label); + + s->smile = gtk_entry_new(); + gtk_entry_set_activates_default(GTK_ENTRY(s->smile), TRUE); + pidgin_set_accessible_label(s->smile, label); + if (smiley) + gtk_entry_set_text(GTK_ENTRY(s->smile), purple_smiley_get_shortcut(smiley)); + + g_signal_connect(s->smile, "activate", G_CALLBACK(do_add), s); + + gtk_box_pack_end(GTK_BOX(hbox), s->smile, FALSE, FALSE, 0); + gtk_widget_show(s->smile); + + gtk_widget_show(hbox); + + gtk_widget_show(GTK_WIDGET(window)); + g_signal_connect_swapped(G_OBJECT(window), "destroy", G_CALLBACK(pidgin_smiley_destroy), s); + g_signal_connect(G_OBJECT(window), "destroy", G_CALLBACK(purple_notify_close_with_handle), s); +} + +/****************************************************************************** + * Delete smiley + *****************************************************************************/ +static void delete_foreach(GtkTreeModel *model, GtkTreePath *path, + GtkTreeIter *iter, gpointer data) +{ + PurpleSmiley *smiley = NULL; + SmileyManager *dialog; + + dialog = (SmileyManager*)data; + + gtk_tree_model_get(model, iter, + SMILEY, &smiley, + -1); + + if(smiley != NULL) { + g_object_unref(G_OBJECT(smiley)); + pidgin_smiley_del_from_list(smiley); + purple_smiley_delete(smiley); + } +} + +static void append_to_list(GtkTreeModel *model, GtkTreePath *path, + GtkTreeIter *iter, gpointer data) +{ + GList **list = data; + *list = g_list_prepend(*list, gtk_tree_path_copy(path)); +} + +static void smiley_delete(SmileyManager *dialog) +{ + GtkTreeSelection *selection; + GList *list = NULL; + + selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(dialog->treeview)); + gtk_tree_selection_selected_foreach(selection, delete_foreach, dialog); + gtk_tree_selection_selected_foreach(selection, append_to_list, &list); + + while (list) { + GtkTreeIter iter; + if (gtk_tree_model_get_iter(GTK_TREE_MODEL(dialog->model), &iter, list->data)) + gtk_list_store_remove(GTK_LIST_STORE(dialog->model), &iter); + gtk_tree_path_free(list->data); + list = g_list_delete_link(list, list); + } +} +/****************************************************************************** + * The Smiley Manager + *****************************************************************************/ +static void add_columns(GtkWidget *treeview, SmileyManager *dialog) +{ + GtkCellRenderer *rend; + GtkTreeViewColumn *column; + + /* Icon */ + column = gtk_tree_view_column_new(); + gtk_tree_view_column_set_title(column, _("Smiley")); + gtk_tree_view_column_set_resizable(column, TRUE); + gtk_tree_view_append_column(GTK_TREE_VIEW(treeview), column); + + rend = gtk_cell_renderer_pixbuf_new(); + gtk_tree_view_column_pack_start(column, rend, FALSE); + gtk_tree_view_column_add_attribute(column, rend, "pixbuf", ICON); + + /* Shortcut */ + column = gtk_tree_view_column_new(); + gtk_tree_view_column_set_title(column, _("Shortcut")); + gtk_tree_view_column_set_resizable(column, TRUE); + gtk_tree_view_append_column(GTK_TREE_VIEW(treeview), column); + + rend = gtk_cell_renderer_text_new(); + gtk_tree_view_column_pack_start(column, rend, TRUE); + gtk_tree_view_column_add_attribute(column, rend, "text", SHORTCUT); +} + +static void store_smiley_add(PurpleSmiley *smiley) +{ + GtkTreeIter iter; + PurpleStoredImage *img; + GdkPixbuf *sized_smiley = NULL; + + if (smiley_manager == NULL) + return; + + img = purple_smiley_get_stored_image(smiley); + + if (img != NULL) { + GdkPixbuf *smiley_image = pidgin_pixbuf_from_imgstore(img); + purple_imgstore_unref(img); + + if (smiley_image != NULL) + sized_smiley = gdk_pixbuf_scale_simple(smiley_image, + 22, 22, GDK_INTERP_HYPER); + g_object_unref(G_OBJECT(smiley_image)); + } + + + gtk_list_store_append(smiley_manager->model, &iter); + + gtk_list_store_set(smiley_manager->model, &iter, + ICON, sized_smiley, + SHORTCUT, purple_smiley_get_shortcut(smiley), + SMILEY, smiley, + -1); + + if (sized_smiley != NULL) + g_object_unref(G_OBJECT(sized_smiley)); +} + +static void populate_smiley_list(SmileyManager *dialog) +{ + GList *list; + PurpleSmiley *emoticon; + + gtk_list_store_clear(dialog->model); + + for(list = purple_smileys_get_all(); list != NULL; + list = g_list_delete_link(list, list)) { + emoticon = (PurpleSmiley*)list->data; + + store_smiley_add(emoticon); + } +} + +static void smile_selected_cb(GtkTreeSelection *sel, SmileyManager *dialog) +{ + gint selected; + + selected = gtk_tree_selection_count_selected_rows(sel); + + gtk_dialog_set_response_sensitive(GTK_DIALOG(dialog->window), + GTK_RESPONSE_NO, selected > 0); + + gtk_dialog_set_response_sensitive(GTK_DIALOG(dialog->window), + PIDGIN_RESPONSE_EDIT, selected > 0); +} + +static void +smiley_edit_iter(SmileyManager *dialog, GtkTreeIter *iter) +{ + PurpleSmiley *smiley = NULL; + gtk_tree_model_get(GTK_TREE_MODEL(dialog->model), iter, SMILEY, &smiley, -1); + pidgin_smiley_edit(gtk_widget_get_toplevel(GTK_WIDGET(dialog->treeview)), smiley); + g_object_unref(G_OBJECT(smiley)); +} + +static void smiley_edit_cb(GtkTreeView *treeview, GtkTreePath *path, GtkTreeViewColumn *col, gpointer data) +{ + GtkTreeIter iter; + SmileyManager *dialog = data; + + gtk_tree_model_get_iter(GTK_TREE_MODEL(dialog->model), &iter, path); + smiley_edit_iter(dialog, &iter); +} + +static void +edit_selected_cb(GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter, gpointer data) +{ + smiley_edit_iter(data, iter); +} + +static GtkWidget *smiley_list_create(SmileyManager *dialog) +{ + GtkWidget *sw; + GtkWidget *treeview; + GtkTreeSelection *sel; + + sw = gtk_scrolled_window_new(NULL, NULL); + gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(sw), + GTK_POLICY_AUTOMATIC, + GTK_POLICY_AUTOMATIC); + gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(sw), + GTK_SHADOW_IN); + gtk_widget_show(sw); + + /* Create the list model */ + dialog->model = gtk_list_store_new(N_COL, + GDK_TYPE_PIXBUF, /* ICON */ + G_TYPE_STRING, /* SHORTCUT */ + G_TYPE_OBJECT /* SMILEY */ + ); + + /* the actual treeview */ + treeview = gtk_tree_view_new_with_model(GTK_TREE_MODEL(dialog->model)); + dialog->treeview = treeview; + gtk_tree_view_set_rules_hint(GTK_TREE_VIEW(treeview), TRUE); + gtk_tree_sortable_set_sort_column_id(GTK_TREE_SORTABLE(dialog->model), SHORTCUT, GTK_SORT_ASCENDING); + g_object_unref(G_OBJECT(dialog->model)); + + sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(treeview)); + gtk_tree_selection_set_mode(sel, GTK_SELECTION_MULTIPLE); + gtk_container_add(GTK_CONTAINER(sw), treeview); + + g_signal_connect(G_OBJECT(sel), "changed", G_CALLBACK(smile_selected_cb), dialog); + g_signal_connect(G_OBJECT(treeview), "row_activated", G_CALLBACK(smiley_edit_cb), dialog); + + gtk_widget_show(treeview); + + add_columns(treeview, dialog); + populate_smiley_list(dialog); + + return sw; +} + +static void refresh_list() +{ + populate_smiley_list(smiley_manager); +} + +static void smiley_manager_select_cb(GtkWidget *widget, gint resp, SmileyManager *dialog) +{ + GtkTreeSelection *selection = NULL; + + switch (resp) { + case GTK_RESPONSE_YES: + pidgin_smiley_edit(dialog->window, NULL); + break; + case GTK_RESPONSE_NO: + smiley_delete(dialog); + break; + case GTK_RESPONSE_DELETE_EVENT: + case GTK_RESPONSE_CLOSE: + gtk_widget_destroy(dialog->window); + g_free(smiley_manager); + smiley_manager = NULL; + break; + case PIDGIN_RESPONSE_EDIT: + /* Find smiley of selection... */ + selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(dialog->treeview)); + gtk_tree_selection_selected_foreach(selection, edit_selected_cb, dialog); + break; + default: + purple_debug_info("gtksmiley", "No valid selection\n"); + break; + } +} + +void pidgin_smiley_manager_show(void) +{ + SmileyManager *dialog; + GtkWidget *win; + GtkWidget *sw; + GtkWidget *vbox; + + if (smiley_manager) { + gtk_window_present(GTK_WINDOW(smiley_manager->window)); + return; + } + + dialog = g_new0(SmileyManager, 1); + smiley_manager = dialog; + + dialog->window = win = gtk_dialog_new_with_buttons( + _("Custom Smiley Manager"), + NULL, + GTK_DIALOG_DESTROY_WITH_PARENT, + GTK_STOCK_ADD, GTK_RESPONSE_YES, + GTK_STOCK_EDIT, PIDGIN_RESPONSE_EDIT, + GTK_STOCK_DELETE, GTK_RESPONSE_NO, + GTK_STOCK_CLOSE, GTK_RESPONSE_CLOSE, + NULL); + + gtk_window_set_default_size(GTK_WINDOW(win), 50, 400); + gtk_window_set_role(GTK_WINDOW(win), "custom_smiley_manager"); + gtk_container_set_border_width(GTK_CONTAINER(win),PIDGIN_HIG_BORDER); + gtk_dialog_set_response_sensitive(GTK_DIALOG(win), GTK_RESPONSE_NO, FALSE); + gtk_dialog_set_response_sensitive(GTK_DIALOG(win), PIDGIN_RESPONSE_EDIT, + FALSE); + + g_signal_connect(win, "response", G_CALLBACK(smiley_manager_select_cb), + dialog); + + /* The vbox */ + vbox = gtk_vbox_new(FALSE, PIDGIN_HIG_BORDER); + gtk_container_add(GTK_CONTAINER(GTK_DIALOG(win)->vbox), vbox); + gtk_widget_show(vbox); + + /* get the scrolled window with all stuff */ + sw = smiley_list_create(dialog); + gtk_box_pack_start(GTK_BOX(vbox), sw, TRUE, TRUE, 0); + gtk_widget_show(sw); + + gtk_widget_show(win); +} + diff -r d53f72735830 -r dea8b856466e pidgin/gtksmiley.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/pidgin/gtksmiley.h Mon May 12 23:17:48 2008 +0000 @@ -0,0 +1,80 @@ +/** + * @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); + +/** + * Shows an editor for a smiley. + * + * @param widget The parent widget to be linked or @c NULL + * @param smiley The PurpleSmiley to be edited, or @c NULL for a new smiley + */ +void pidgin_smiley_edit(GtkWidget *widget, PurpleSmiley *smiley); + +#endif /* _PIDGIN_GTKSMILEY_H_*/ diff -r d53f72735830 -r dea8b856466e pidgin/gtkthemes.c --- a/pidgin/gtkthemes.c Mon May 12 02:19:06 2008 +0000 +++ b/pidgin/gtkthemes.c Mon May 12 23:17:48 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 d53f72735830 -r dea8b856466e pidgin/gtkthemes.h --- a/pidgin/gtkthemes.h Mon May 12 02:19:06 2008 +0000 +++ b/pidgin/gtkthemes.h Mon May 12 23:17:48 2008 +0000 @@ -51,6 +51,8 @@ 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); diff -r d53f72735830 -r dea8b856466e pidgin/gtkutils.c --- a/pidgin/gtkutils.c Mon May 12 02:19:06 2008 +0000 +++ b/pidgin/gtkutils.c Mon May 12 23:17:48 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); @@ -3469,3 +3469,17 @@ #endif } +GdkPixbuf * pidgin_pixbuf_from_imgstore(PurpleStoredImage *image) +{ + GdkPixbuf *pixbuf; + GdkPixbufLoader *loader = gdk_pixbuf_loader_new(); + gdk_pixbuf_loader_write(loader, purple_imgstore_get_data(image), + purple_imgstore_get_size(image), NULL); + gdk_pixbuf_loader_close(loader, NULL); + pixbuf = gdk_pixbuf_loader_get_pixbuf(loader); + if (pixbuf) + g_object_ref(pixbuf); + g_object_unref(loader); + return pixbuf; +} + diff -r d53f72735830 -r dea8b856466e pidgin/gtkutils.h --- a/pidgin/gtkutils.h Mon May 12 02:19:06 2008 +0000 +++ b/pidgin/gtkutils.h Mon May 12 23:17:48 2008 +0000 @@ -689,7 +689,7 @@ */ GtkWidget *pidgin_make_mini_dialog(PurpleConnection *handle, const char* stock_id, const char *primary, const char *secondary, - void *user_data, ...); + void *user_data, ...) G_GNUC_NULL_TERMINATED; /** * This is a callback function to be used for Ctrl+F searching in treeviews. @@ -809,5 +809,15 @@ */ GtkWidget *pidgin_add_widget_to_vbox(GtkBox *vbox, const char *widget_label, GtkSizeGroup *sg, GtkWidget *widget, gboolean expand, GtkWidget **p_label); +/** + * Create a GdkPixbuf from a PurpleStoredImage. + * + * @param image A PurpleStoredImage. + * + * @return A GdkPixbuf created from the stored image. + * @since 2.5.0 + */ +GdkPixbuf * pidgin_pixbuf_from_imgstore(PurpleStoredImage *image); + #endif /* _PIDGINUTILS_H_ */ diff -r d53f72735830 -r dea8b856466e po/POTFILES.in --- a/po/POTFILES.in Mon May 12 02:19:06 2008 +0000 +++ b/po/POTFILES.in Mon May 12 23:17:48 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