# HG changeset patch # User Evan Schoenberg # Date 1211372967 0 # Node ID e0e57b83b359a6efa8bdb9a2adf933c0eb6dd861 # Parent 02ef6c5c66503b5ac6b1fc9288f70adf270e8246# Parent 25161f5ea34799b380d049f7fb4fa71cc6c1d1f0 propagate from branch 'im.pidgin.pidgin' (head a9b83251335ed42daf54164bf8c5120166f0e589) to branch 'im.pidgin.pidgin.next.minor' (head d98aba6eab4b0cb6e15e1a1dc7ca8163c6d03f52) diff -r 02ef6c5c6650 -r e0e57b83b359 COPYRIGHT --- a/COPYRIGHT Wed May 21 05:07:25 2008 +0000 +++ b/COPYRIGHT Wed May 21 12:29:27 2008 +0000 @@ -233,6 +233,7 @@ Shlomi Loubaton Uli Luckas Matthew Luckie +Marcus Lundblad Mike Lundy Jason Lynch Iain MacDonnell @@ -289,6 +290,7 @@ John Oyler Matt Pandina Laszlo Pandy +Giulio 'Twain28' Pascali Ricardo Fernandez Pascual Riley Patterson Havoc Pennington diff -r 02ef6c5c6650 -r e0e57b83b359 ChangeLog --- a/ChangeLog Wed May 21 05:07:25 2008 +0000 +++ b/ChangeLog Wed May 21 12:29:27 2008 +0000 @@ -1,5 +1,14 @@ Pidgin and Finch: The Pimpin' Penguin IM Clients That're Good for the Soul +version 2.5.0: + Pidgin: + * Custom buddy icons can now be added and removed to buddy list + entries via the buddy list entry right-click menu. + + General: + * Group and Chat buddy list entries can now be given custom buddy + icons. + version 2.4.3 (??/??/2008): version 2.4.2 (05/17/2008): diff -r 02ef6c5c6650 -r e0e57b83b359 ChangeLog.API --- a/ChangeLog.API Wed May 21 05:07:25 2008 +0000 +++ b/ChangeLog.API Wed May 21 12:29:27 2008 +0000 @@ -1,6 +1,41 @@ Pidgin and Finch: The Pimpin' Penguin IM Clients That're Good for the Soul -version 2.4.2 (05/17/2008): +version 2.5.0 (??/??/2008): + libpurple: + Added: + * Connection flag PURPLE_CONNECTION_ALLOW_CUSTOM_SMILEY to indicate + that the connection supports sending and receiving custom smileys. + * PurpleSmiley and the Smiley API. + + 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: + * purple_serv_got_join_chat_failed + * chat-join-failed signal (see conversation-signals.dox) + * purple_blist_update_node_icon + * purple_buddy_icons_node_has_custom_icon + * purple_buddy_icons_node_find_custom_icon + * purple_buddy_icons_node_set_custom_icon + * purple_buddy_icons_node_set_custom_icon_from_file + + Deprecated: + * purple_blist_update_buddy_icon + * purple_buddy_icons_has_custom_icon + * purple_buddy_icons_find_custom_icon + * purple_buddy_icons_set_custom_icon + * pidgin_set_custom_buddy_icon + perl: Added: * Purple::Prefs::get_children_names. diff -r 02ef6c5c6650 -r e0e57b83b359 configure.ac --- a/configure.ac Wed May 21 05:07:25 2008 +0000 +++ b/configure.ac Wed May 21 12:29:27 2008 +0000 @@ -43,19 +43,19 @@ # # Make sure to update finch/libgnt/configure.ac with libgnt version changes. # -m4_define([purple_lt_current], [4]) +m4_define([purple_lt_current], [5]) m4_define([purple_major_version], [2]) -m4_define([purple_minor_version], [4]) -m4_define([purple_micro_version], [3]) +m4_define([purple_minor_version], [5]) +m4_define([purple_micro_version], [0]) m4_define([purple_version_suffix], [devel]) m4_define([purple_version], [purple_major_version.purple_minor_version.purple_micro_version]) m4_define([purple_display_version], purple_version[]m4_ifdef([purple_version_suffix],[purple_version_suffix])) -m4_define([gnt_lt_current], [4]) +m4_define([gnt_lt_current], [5]) m4_define([gnt_major_version], [2]) -m4_define([gnt_minor_version], [4]) -m4_define([gnt_micro_version], [3]) +m4_define([gnt_minor_version], [5]) +m4_define([gnt_micro_version], [0]) m4_define([gnt_version_suffix], [devel]) m4_define([gnt_version], [gnt_major_version.gnt_minor_version.gnt_micro_version]) diff -r 02ef6c5c6650 -r e0e57b83b359 doc/conversation-signals.dox --- a/doc/conversation-signals.dox Wed May 21 05:07:25 2008 +0000 +++ b/doc/conversation-signals.dox Wed May 21 12:29:27 2008 +0000 @@ -27,6 +27,7 @@ @signal chat-invited-user @signal chat-invited @signal chat-joined + @signal chat-join-failed @signal chat-left @signal chat-topic-changed @signal conversation-extended-menu @@ -302,6 +303,18 @@ @param new_arrival If the buddy is a new arrival. @endsignaldef + @signaldef chat-join-failed + @signalproto +void (*chat_join_failed)(PurpleConnection *gc, GHashTable *components); + @endsignalproto + @signaldesc + Emitted when an account fails to join a chat room + @param gc The PurpleConnection of the account which failed to join the chat. + @param data The components passed to serv_join_chat() originally. + The hash function should be g_str_hash() and the equal + function should be g_str_equal(). + @endsignaldef + @signaldef chat-buddy-flags @signalproto void (*chat_buddy_flags)(PurpleConversation *conv, const char *name, diff -r 02ef6c5c6650 -r e0e57b83b359 libpurple/Makefile.am --- a/libpurple/Makefile.am Wed May 21 05:07:25 2008 +0000 +++ b/libpurple/Makefile.am Wed May 21 12:29:27 2008 +0000 @@ -68,6 +68,7 @@ savedstatuses.c \ server.c \ signals.c \ + smiley.c \ dnsquery.c \ dnssrv.c\ status.c \ @@ -120,6 +121,7 @@ savedstatuses.h \ server.h \ signals.h \ + smiley.h \ dnsquery.h \ dnssrv.h \ status.h \ @@ -154,7 +156,7 @@ dbus_exported = dbus-useful.h dbus-define-api.h account.h blist.h buddyicon.h \ connection.h conversation.h core.h ft.h log.h notify.h prefs.h roomlist.h \ - savedstatuses.h status.h server.h util.h xmlnode.h prpl.h + savedstatuses.h smiley.h status.h server.h util.h xmlnode.h prpl.h purple_build_coreheaders = $(addprefix $(srcdir)/, $(purple_coreheaders)) \ $(purple_builtheaders) diff -r 02ef6c5c6650 -r e0e57b83b359 libpurple/Makefile.mingw --- a/libpurple/Makefile.mingw Wed May 21 05:07:25 2008 +0000 +++ b/libpurple/Makefile.mingw Wed May 21 12:29:27 2008 +0000 @@ -65,6 +65,7 @@ savedstatuses.c \ server.c \ signals.c \ + smiley.c \ sound.c \ sslconn.c \ status.c \ diff -r 02ef6c5c6650 -r e0e57b83b359 libpurple/blist.c --- a/libpurple/blist.c Wed May 21 05:07:25 2008 +0000 +++ b/libpurple/blist.c Wed May 21 12:29:27 2008 +0000 @@ -824,16 +824,25 @@ ops->update(purplebuddylist, (PurpleBlistNode *)buddy); } -void purple_blist_update_buddy_icon(PurpleBuddy *buddy) +void +purple_blist_update_node_icon(PurpleBlistNode *node) { PurpleBlistUiOps *ops = purple_blist_get_ui_ops(); - g_return_if_fail(buddy != NULL); + g_return_if_fail(node != NULL); if (ops && ops->update) - ops->update(purplebuddylist, (PurpleBlistNode *)buddy); + ops->update(purplebuddylist, node); } +#ifndef PURPLE_DISABLE_DEPRECATED +void +purple_blist_update_buddy_icon(PurpleBuddy *buddy) +{ + purple_blist_update_node_icon((PurpleBlistNode *)buddy); +} +#endif + /* * TODO: Maybe remove the call to this from server.c and call it * from oscar.c and toc.c instead? @@ -1197,7 +1206,7 @@ purple_signal_emit(purple_blist_get_handle(), "buddy-icon-changed", buddy); - purple_blist_update_buddy_icon(buddy); + purple_blist_update_node_icon((PurpleBlistNode*)buddy); } PurpleAccount * @@ -2189,7 +2198,7 @@ g_return_val_if_fail((name != NULL) && (*name != '\0'), NULL); for (node = purplebuddylist->root; node != NULL; node = node->next) { - if (!strcmp(((PurpleGroup *)node)->name, name)) + if (!purple_utf8_strcasecmp(((PurpleGroup *)node)->name, name)) return (PurpleGroup *)node; } diff -r 02ef6c5c6650 -r e0e57b83b359 libpurple/blist.h --- a/libpurple/blist.h Wed May 21 05:07:25 2008 +0000 +++ b/libpurple/blist.h Wed May 21 12:29:27 2008 +0000 @@ -317,11 +317,22 @@ void purple_blist_update_buddy_status(PurpleBuddy *buddy, PurpleStatus *old_status); /** + * Updates a node's custom icon. + * + * @param node The PurpleBlistNode whose custom icon has changed. + * @since 2.5.0 + */ +void purple_blist_update_node_icon(PurpleBlistNode *node); + +#ifndef PURPLE_DISABLE_DEPRECATED +/** * Updates a buddy's icon. * * @param buddy The buddy whose buddy icon has changed + * @deprecated Use purple_blist_update_node_icon() instead. */ void purple_blist_update_buddy_icon(PurpleBuddy *buddy); +#endif /** * Renames a buddy in the buddy list. diff -r 02ef6c5c6650 -r e0e57b83b359 libpurple/buddyicon.c --- a/libpurple/buddyicon.c Wed May 21 05:07:25 2008 +0000 +++ b/libpurple/buddyicon.c Wed May 21 12:29:27 2008 +0000 @@ -92,8 +92,10 @@ */ static GHashTable *icon_file_cache = NULL; -/* This one is used for both custom buddy icons - * on PurpleContacts and account icons. */ +/** + * This hash table is used for both custom buddy icons on PurpleBlistNodes and + * account icons. + */ static GHashTable *pointer_icon_cache = NULL; static char *cache_dir = NULL; @@ -684,14 +686,6 @@ return (icon ? purple_buddy_icon_ref(icon) : NULL); } -gboolean -purple_buddy_icons_has_custom_icon(PurpleContact *contact) -{ - g_return_val_if_fail(contact != NULL, FALSE); - - return (purple_blist_node_get_string((PurpleBlistNode*)contact, "custom_buddy_icon") != NULL); -} - PurpleStoredImage * purple_buddy_icons_find_account_icon(PurpleAccount *account) { @@ -807,24 +801,32 @@ return ret; } -PurpleStoredImage * -purple_buddy_icons_find_custom_icon(PurpleContact *contact) +gboolean +purple_buddy_icons_node_has_custom_icon(PurpleBlistNode *node) { - PurpleStoredImage *img; - const char *custom_icon_file; - const char *dirname; + g_return_val_if_fail(node != NULL, FALSE); + + return (purple_blist_node_get_string(node, "custom_buddy_icon") != NULL); +} + +PurpleStoredImage * +purple_buddy_icons_node_find_custom_icon(PurpleBlistNode *node) +{ char *path; + size_t len; guchar *data; - size_t len; + PurpleStoredImage *img; + const char *custom_icon_file, *dirname; - g_return_val_if_fail(contact != NULL, NULL); + g_return_val_if_fail(node != NULL, NULL); - if ((img = g_hash_table_lookup(pointer_icon_cache, contact))) + if ((img = g_hash_table_lookup(pointer_icon_cache, node))) { return purple_imgstore_ref(img); } - custom_icon_file = purple_blist_node_get_string((PurpleBlistNode*)contact, "custom_buddy_icon"); + custom_icon_file = purple_blist_node_get_string(node, + "custom_buddy_icon"); if (custom_icon_file == NULL) return NULL; @@ -836,7 +838,7 @@ { g_free(path); img = purple_buddy_icon_data_new(data, len, custom_icon_file); - g_hash_table_insert(pointer_icon_cache, contact, img); + g_hash_table_insert(pointer_icon_cache, node, img); return img; } g_free(path); @@ -845,66 +847,79 @@ } PurpleStoredImage * -purple_buddy_icons_set_custom_icon(PurpleContact *contact, - guchar *icon_data, size_t icon_len) +purple_buddy_icons_node_set_custom_icon(PurpleBlistNode *node, + guchar *icon_data, size_t icon_len) { + char *old_icon; PurpleStoredImage *old_img; PurpleStoredImage *img = NULL; - char *old_icon; - PurpleBlistNode *child; + + g_return_val_if_fail(node != NULL, NULL); - old_img = g_hash_table_lookup(pointer_icon_cache, contact); + if (!PURPLE_BLIST_NODE_IS_CONTACT(node) && + !PURPLE_BLIST_NODE_IS_CHAT(node) && + !PURPLE_BLIST_NODE_IS_GROUP(node)) { + return NULL; + } - if (icon_data != NULL && icon_len > 0) - { + old_img = g_hash_table_lookup(pointer_icon_cache, node); + + if (icon_data != NULL && icon_len > 0) { img = purple_buddy_icon_data_new(icon_data, icon_len, NULL); } - old_icon = g_strdup(purple_blist_node_get_string((PurpleBlistNode *)contact, + old_icon = g_strdup(purple_blist_node_get_string(node, "custom_buddy_icon")); - if (img && purple_buddy_icons_is_caching()) - { + if (img && purple_buddy_icons_is_caching()) { const char *filename = purple_imgstore_get_filename(img); - purple_blist_node_set_string((PurpleBlistNode *)contact, - "custom_buddy_icon", + purple_blist_node_set_string(node, "custom_buddy_icon", filename); ref_filename(filename); - } - else - { - purple_blist_node_remove_setting((PurpleBlistNode *)contact, - "custom_buddy_icon"); + } else { + purple_blist_node_remove_setting(node, "custom_buddy_icon"); } unref_filename(old_icon); if (img) - g_hash_table_insert(pointer_icon_cache, contact, img); + g_hash_table_insert(pointer_icon_cache, node, img); else - g_hash_table_remove(pointer_icon_cache, contact); + g_hash_table_remove(pointer_icon_cache, node); - for (child = contact->node.child ; child ; child = child->next) - { - PurpleBuddy *buddy; - PurpleConversation *conv; + if (PURPLE_BLIST_NODE_IS_CONTACT(node)) { + PurpleBlistNode *child; + for (child = node->child ; child ; child = child->next) + { + PurpleBuddy *buddy; + PurpleConversation *conv; + + if (!PURPLE_BLIST_NODE_IS_BUDDY(child)) + continue; + + buddy = (PurpleBuddy *)child; - if (!PURPLE_BLIST_NODE_IS_BUDDY(child)) - continue; - - buddy = (PurpleBuddy *)child; + conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, purple_buddy_get_name(buddy), purple_buddy_get_account(buddy)); + if (conv) + purple_conversation_update(conv, PURPLE_CONV_UPDATE_ICON); - conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, - purple_buddy_get_name(buddy), - purple_buddy_get_account(buddy)); - if (conv) + /* Is this call necessary anymore? Can the buddies + * themselves need updating when the custom buddy + * icon changes? */ + purple_blist_update_node_icon((PurpleBlistNode*)buddy); + } + } else if (PURPLE_BLIST_NODE_IS_CHAT(node)) { + PurpleConversation *conv = NULL; + + conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_CHAT, purple_chat_get_name((PurpleChat*)node), purple_chat_get_account((PurpleChat*)node)); + if (conv) { purple_conversation_update(conv, PURPLE_CONV_UPDATE_ICON); - - purple_blist_update_buddy_icon(buddy); + } } - if (old_img) + purple_blist_update_node_icon(node); + + if (old_img) { purple_imgstore_unref(old_img); - else if (old_icon) - { + } else if (old_icon) { /* The old icon may not have been loaded into memory. In that * case, we'll need to uncache the filename. The filenames * are ref-counted, so this is safe. */ @@ -915,6 +930,49 @@ return img; } +PurpleStoredImage * +purple_buddy_icons_node_set_custom_icon_from_file(PurpleBlistNode *node, + const gchar *filename) +{ + size_t len; + guchar *data; + + g_return_val_if_fail(node != NULL, NULL); + + if (!PURPLE_BLIST_NODE_IS_CONTACT(node) && + !PURPLE_BLIST_NODE_IS_CHAT(node) && + !PURPLE_BLIST_NODE_IS_GROUP(node)) { + return NULL; + } + + if (!read_icon_file(filename, &data, &len)) { + return NULL; + } + + return purple_buddy_icons_node_set_custom_icon(node, data, len); +} + +#ifndef PURPLE_DISABLE_DEPRECATED +gboolean +purple_buddy_icons_has_custom_icon(PurpleContact *contact) +{ + return purple_buddy_icons_node_has_custom_icon((PurpleBlistNode*)contact); +} + +PurpleStoredImage * +purple_buddy_icons_find_custom_icon(PurpleContact *contact) +{ + return purple_buddy_icons_node_find_custom_icon((PurpleBlistNode*)contact); +} + +PurpleStoredImage * +purple_buddy_icons_set_custom_icon(PurpleContact *contact, guchar *icon_data, + size_t icon_len) +{ + return purple_buddy_icons_node_set_custom_icon((PurpleBlistNode*)contact, icon_data, icon_len); +} +#endif + void _purple_buddy_icon_set_old_icons_dir(const char *dirname) { @@ -1138,7 +1196,9 @@ } } } - else if (PURPLE_BLIST_NODE_IS_CONTACT(node)) + else if (PURPLE_BLIST_NODE_IS_CONTACT(node) || + PURPLE_BLIST_NODE_IS_CHAT(node) || + PURPLE_BLIST_NODE_IS_GROUP(node)) { const char *filename; diff -r 02ef6c5c6650 -r e0e57b83b359 libpurple/buddyicon.h --- a/libpurple/buddyicon.h Wed May 21 05:07:25 2008 +0000 +++ b/libpurple/buddyicon.h Wed May 21 12:29:27 2008 +0000 @@ -218,16 +218,6 @@ purple_buddy_icons_find(PurpleAccount *account, const char *username); /** - * Returns a boolean indicating if a given contact has a custom buddy icon. - * - * @param contact The contact - * - * @return A boolean indicating if @a contact has a custom buddy icon. - */ -gboolean -purple_buddy_icons_has_custom_icon(PurpleContact *contact); - -/** * Returns the buddy icon image for an account. * * The caller owns a reference to the image in the store, and must dereference @@ -277,7 +267,18 @@ purple_buddy_icons_get_account_icon_timestamp(PurpleAccount *account); /** - * Returns the custom buddy icon image for a contact. + * Returns a boolean indicating if a given blist node has a custom buddy icon. + * + * @param node The blist node. + * + * @return A boolean indicating if @a node has a custom buddy icon. + * @since 2.5.0 + */ +gboolean +purple_buddy_icons_node_has_custom_icon(PurpleBlistNode *node); + +/** + * Returns the custom buddy icon image for a blist node. * * The caller owns a reference to the image in the store, and must dereference * the image with purple_imgstore_unref() for it to be freed. @@ -286,31 +287,82 @@ * needed, so it should be called in any case where you want the * appropriate icon. * - * @param contact The contact + * @param node The node. + * + * @return The custom buddy icon. + * @since 2.5.0 + */ +PurpleStoredImage * +purple_buddy_icons_node_find_custom_icon(PurpleBlistNode *node); + +/** + * Sets a custom buddy icon for a blist node. + * + * This function will deal with saving a record of the icon, caching the data, + * etc. + * + * @param node The blist node for which to set a custom icon. + * @param icon_data The image data of the icon, which the buddy icon code will + * free. + * @param icon_len The length of the data in @a icon_data. + * + * @return The icon that was set. The caller does NOT own a reference to this, + * and must call purple_imgstore_ref() if it wants one. + * @since 2.5.0 + */ +PurpleStoredImage * +purple_buddy_icons_node_set_custom_icon(PurpleBlistNode *node, + guchar *icon_data, size_t icon_len); + +/** + * Sets a custom buddy icon for a blist node. * - * @return The custom buddy icon image. + * Convenience wrapper around purple_buddy_icons_node_set_custom_icon. + * @see purple_buddy_icons_node_set_custom_icon() + * + * @param node The blist node for which to set a custom icon. + * @param filename The path to the icon to set for the blist node. + * + * @return The icon that was set. The caller does NOT own a reference to this, + * and must call purple_imgstore_ref() if it wants one. + * @since 2.5.0 + */ +PurpleStoredImage * +purple_buddy_icons_node_set_custom_icon_from_file(PurpleBlistNode *node, + const gchar *filename); + +#ifndef PURPLE_DISABLE_DEPRECATED +/** + * PurpleContact version of purple_buddy_icons_node_has_custom_icon. + * + * @copydoc purple_buddy_icons_node_has_custom_icon() + * + * @deprecated Use purple_buddy_icons_node_has_custom_icon instead. + */ +gboolean +purple_buddy_icons_has_custom_icon(PurpleContact *contact); + +/** + * PurpleContact version of purple_buddy_icons_node_find_custom_icon. + * + * @copydoc purple_buddy_icons_node_find_custom_icon() + * + * @deprecated Use purple_buddy_icons_node_find_custom_icon instead. */ PurpleStoredImage * purple_buddy_icons_find_custom_icon(PurpleContact *contact); /** - * Sets a custom buddy icon for a user. - * - * This function will deal with saving a record of the icon, - * caching the data, etc. + * PurpleContact version of purple_buddy_icons_node_set_custom_icon. * - * @param contact The contact for which to set a custom icon. - * @param icon_data The image data of the icon, which the - * buddy icon code will free. - * @param icon_len The length of the data in @a icon_data. + * @copydoc purple_buddy_icons_node_set_custom_icon() * - * @return The icon that was set. The caller does NOT own - * a reference to this, and must call purple_imgstore_ref() - * if it wants one. + * @deprecated Use purple_buddy_icons_node_set_custom_icon instead. */ PurpleStoredImage * purple_buddy_icons_set_custom_icon(PurpleContact *contact, guchar *icon_data, size_t icon_len); +#endif /** * Sets whether or not buddy icon caching is enabled. diff -r 02ef6c5c6650 -r e0e57b83b359 libpurple/connection.h --- a/libpurple/connection.h Wed May 21 05:07:25 2008 +0000 +++ b/libpurple/connection.h Wed May 21 12:29:27 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 02ef6c5c6650 -r e0e57b83b359 libpurple/conversation.c --- a/libpurple/conversation.c Wed May 21 05:07:25 2008 +0000 +++ b/libpurple/conversation.c Wed May 21 12:29:27 2008 +0000 @@ -2381,6 +2381,12 @@ purple_value_new(PURPLE_TYPE_SUBTYPE, PURPLE_SUBTYPE_CONVERSATION)); + purple_signal_register(handle, "chat-join-failed", + purple_marshal_VOID__POINTER_POINTER, NULL, 2, + purple_value_new(PURPLE_TYPE_SUBTYPE, + PURPLE_SUBTYPE_CONNECTION), + purple_value_new(PURPLE_TYPE_POINTER)); + purple_signal_register(handle, "chat-left", purple_marshal_VOID__POINTER, NULL, 1, purple_value_new(PURPLE_TYPE_SUBTYPE, diff -r 02ef6c5c6650 -r e0e57b83b359 libpurple/core.c --- a/libpurple/core.c Wed May 21 05:07:25 2008 +0000 +++ b/libpurple/core.c Wed May 21 12:29:27 2008 +0000 @@ -43,6 +43,7 @@ #include "proxy.h" #include "savedstatuses.h" #include "signals.h" +#include "smiley.h" #include "sound.h" #include "sslconn.h" #include "status.h" @@ -166,6 +167,7 @@ purple_stun_init(); purple_xfers_init(); purple_idle_init(); + purple_smileys_init(); /* * Call this early on to try to auto-detect our IP address and @@ -194,6 +196,7 @@ purple_connections_disconnect_all(); /* Save .xml files, remove signals, etc. */ + purple_smileys_uninit(); purple_idle_uninit(); purple_ssl_uninit(); purple_pounces_uninit(); diff -r 02ef6c5c6650 -r e0e57b83b359 libpurple/dbus-analyze-functions.py --- a/libpurple/dbus-analyze-functions.py Wed May 21 05:07:25 2008 +0000 +++ b/libpurple/dbus-analyze-functions.py Wed May 21 12:29:27 2008 +0000 @@ -483,6 +483,7 @@ self.inputiter = iter(inputfile) self.functionregexp = \ re.compile("^%s(\w[^()]*)\(([^()]*)\)\s*;\s*$" % fprefix) + self.typeregexp = re.compile("^\w+\s*\*?\s*$") @@ -501,7 +502,7 @@ # accumulate lines until the parentheses are balance or an # empty line has been encountered myline = line.strip() - while myline.count("(") > myline.count(")"): + while (myline.count("(") > myline.count(")")) or self.typeregexp.match(myline): newline = self.inputiter.next().strip() if len(newline) == 0: break diff -r 02ef6c5c6650 -r e0e57b83b359 libpurple/dbus-server.c --- a/libpurple/dbus-server.c Wed May 21 05:07:25 2008 +0000 +++ b/libpurple/dbus-server.c Wed May 21 12:29:27 2008 +0000 @@ -40,6 +40,7 @@ #include "core.h" #include "internal.h" #include "savedstatuses.h" +#include "smiley.h" #include "util.h" #include "value.h" #include "xmlnode.h" diff -r 02ef6c5c6650 -r e0e57b83b359 libpurple/imgstore.c --- a/libpurple/imgstore.c Wed May 21 05:07:25 2008 +0000 +++ b/libpurple/imgstore.c Wed May 21 12:29:27 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 02ef6c5c6650 -r e0e57b83b359 libpurple/imgstore.h --- a/libpurple/imgstore.h Wed May 21 05:07:25 2008 +0000 +++ b/libpurple/imgstore.h Wed May 21 12:29:27 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 02ef6c5c6650 -r e0e57b83b359 libpurple/plugins/perl/Makefile.am --- a/libpurple/plugins/perl/Makefile.am Wed May 21 05:07:25 2008 +0000 +++ b/libpurple/plugins/perl/Makefile.am Wed May 21 12:29:27 2008 +0000 @@ -67,6 +67,7 @@ common/SavedStatuses.xs \ common/Server.xs \ common/Signal.xs \ + common/Smiley.xs \ common/Sound.xs \ common/Status.xs \ common/Stringref.xs \ diff -r 02ef6c5c6650 -r e0e57b83b359 libpurple/plugins/perl/common/MANIFEST --- a/libpurple/plugins/perl/common/MANIFEST Wed May 21 05:07:25 2008 +0000 +++ b/libpurple/plugins/perl/common/MANIFEST Wed May 21 12:29:27 2008 +0000 @@ -28,6 +28,7 @@ SavedStatuses.xs Server.xs Signal.xs +Smiley.xs Sound.xs Status.xs Stringref.xs diff -r 02ef6c5c6650 -r e0e57b83b359 libpurple/plugins/perl/common/Makefile.mingw --- a/libpurple/plugins/perl/common/Makefile.mingw Wed May 21 05:07:25 2008 +0000 +++ b/libpurple/plugins/perl/common/Makefile.mingw Wed May 21 12:29:27 2008 +0000 @@ -61,8 +61,9 @@ Roomlist.xs \ SSLConn.xs \ SavedStatuses.xs \ + Server.xs \ Signal.xs \ - Server.xs \ + Smiley.xs \ Sound.xs \ Status.xs \ Stringref.xs \ diff -r 02ef6c5c6650 -r e0e57b83b359 libpurple/plugins/perl/common/Purple.xs --- a/libpurple/plugins/perl/common/Purple.xs Wed May 21 05:07:25 2008 +0000 +++ b/libpurple/plugins/perl/common/Purple.xs Wed May 21 12:29:27 2008 +0000 @@ -30,6 +30,7 @@ PURPLE_PERL_BOOT_PROTO(SavedStatus); PURPLE_PERL_BOOT_PROTO(Serv); PURPLE_PERL_BOOT_PROTO(Signal); +PURPLE_PERL_BOOT_PROTO(Smiley); PURPLE_PERL_BOOT_PROTO(Sound); PURPLE_PERL_BOOT_PROTO(Status); PURPLE_PERL_BOOT_PROTO(Stringref); @@ -68,6 +69,7 @@ PURPLE_PERL_BOOT(SavedStatus); PURPLE_PERL_BOOT(Serv); PURPLE_PERL_BOOT(Signal); + PURPLE_PERL_BOOT(Smiley); PURPLE_PERL_BOOT(Sound); PURPLE_PERL_BOOT(Status); PURPLE_PERL_BOOT(Stringref); diff -r 02ef6c5c6650 -r e0e57b83b359 libpurple/plugins/perl/common/Smiley.xs --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/plugins/perl/common/Smiley.xs Wed May 21 12:29:27 2008 +0000 @@ -0,0 +1,81 @@ +#include "module.h" + +MODULE = Purple::Smiley PACKAGE = Purple::Smiley PREFIX = purple_smiley_ +PROTOTYPES: ENABLE + +Purple::Smiley +purple_smiley_new(img, shortcut) + Purple::StoredImage img + const char * shortcut + +Purple::Smiley +purple_smiley_new_from_file(shortcut, filepath) + const char * shortcut + const char * filepath + +void +purple_smiley_delete(smiley) + Purple::Smiley smiley + +gboolean +purple_smiley_set_shortcut(smiley, shortcut) + Purple::Smiley smiley + const char * shortcut + +void +purple_smiley_set_data(smiley, data, data_len, keepfilename) + Purple::Smiley smiley + guchar * data + size_t data_len + gboolean keepfilename + +const char * +purple_smiley_get_shortcut(smiley) + Purple::Smiley smiley + +const char * +purple_smiley_get_checksum(smiley) + Purple::Smiley smiley + +Purple::StoredImage +purple_smiley_get_stored_image(smiley) + Purple::Smiley smiley + +gconstpointer +purple_smiley_get_data(smiley, len) + Purple::Smiley smiley + size_t * len + +const char * +purple_smiley_get_extension(smiley) + Purple::Smiley smiley + + +gchar_own * +purple_smiley_get_full_path(smiley) + Purple::Smiley smiley + + +MODULE = Purple::Smiley PACKAGE = Purple::Smileys PREFIX = purple_smileys_ +PROTOTYPES: ENABLE + +void +purple_smileys_get_all() +PREINIT: + GList *l; +PPCODE: + for (l = purple_smileys_get_all(); l != NULL; l = g_list_delete_link(l, l)) { + XPUSHs(sv_2mortal(purple_perl_bless_object(l->data, "Purple::Smiley"))); + } + +Purple::Smiley +purple_smileys_find_by_shortcut(shortcut) + const char * shortcut + +Purple::Smiley +purple_smileys_find_by_checksum(checksum) + const char * checksum + +const char * +purple_smileys_get_storing_dir() + diff -r 02ef6c5c6650 -r e0e57b83b359 libpurple/plugins/perl/common/module.h --- a/libpurple/plugins/perl/common/module.h Wed May 21 05:07:25 2008 +0000 +++ b/libpurple/plugins/perl/common/module.h Wed May 21 12:29:27 2008 +0000 @@ -51,6 +51,7 @@ #include "savedstatuses.h" #include "server.h" #include "signals.h" +#include "smiley.h" #include "sound.h" #include "sslconn.h" #include "status.h" @@ -240,6 +241,9 @@ typedef PurpleSavedStatus * Purple__SavedStatus; typedef PurpleSavedStatusSub * Purple__SavedStatus__Sub; +/* smiley.h */ +typedef PurpleSmiley * Purple__Smiley; + /* sound.h */ typedef PurpleSoundEventID Purple__SoundEventID; typedef PurpleSoundUiOps * Purple__Sound__UiOps; diff -r 02ef6c5c6650 -r e0e57b83b359 libpurple/plugins/perl/common/typemap --- a/libpurple/plugins/perl/common/typemap Wed May 21 05:07:25 2008 +0000 +++ b/libpurple/plugins/perl/common/typemap Wed May 21 12:29:27 2008 +0000 @@ -151,6 +151,7 @@ Purple::Presence T_PurpleObj Purple::PresenceContext T_IV +Purple::Smiley T_PurpleObj Purple::Status T_PurpleObj Purple::StatusAttr T_PurpleObj Purple::StatusPrimitive T_IV diff -r 02ef6c5c6650 -r e0e57b83b359 libpurple/protocols/bonjour/bonjour.c --- a/libpurple/protocols/bonjour/bonjour.c Wed May 21 05:07:25 2008 +0000 +++ b/libpurple/protocols/bonjour/bonjour.c Wed May 21 12:29:27 2008 +0000 @@ -495,6 +495,7 @@ NULL, NULL, NULL, + sizeof(PurplePluginProtocolInfo), /* struct_size */ NULL }; diff -r 02ef6c5c6650 -r e0e57b83b359 libpurple/protocols/gg/gg.c --- a/libpurple/protocols/gg/gg.c Wed May 21 05:07:25 2008 +0000 +++ b/libpurple/protocols/gg/gg.c Wed May 21 12:29:27 2008 +0000 @@ -1998,7 +1998,7 @@ serv_got_chat_in(gc, id, purple_account_get_username(purple_connection_get_account(gc)), - 0, message, time(NULL)); + flags, message, time(NULL)); return 0; } @@ -2152,6 +2152,7 @@ NULL, NULL, NULL, + sizeof(PurplePluginProtocolInfo), /* struct_size */ NULL }; /* }}} */ diff -r 02ef6c5c6650 -r e0e57b83b359 libpurple/protocols/irc/irc.c --- a/libpurple/protocols/irc/irc.c Wed May 21 05:07:25 2008 +0000 +++ b/libpurple/protocols/irc/irc.c Wed May 21 12:29:27 2008 +0000 @@ -737,7 +737,7 @@ irc_cmd_privmsg(irc, "msg", NULL, args); - serv_got_chat_in(gc, id, purple_connection_get_display_name(gc), 0, what, time(NULL)); + serv_got_chat_in(gc, id, purple_connection_get_display_name(gc), flags, what, time(NULL)); g_free(tmp); return 0; } @@ -909,6 +909,7 @@ NULL, NULL, NULL, + sizeof(PurplePluginProtocolInfo), /* struct_size */ NULL }; diff -r 02ef6c5c6650 -r e0e57b83b359 libpurple/protocols/jabber/chat.c --- a/libpurple/protocols/jabber/chat.c Wed May 21 05:07:25 2008 +0000 +++ b/libpurple/protocols/jabber/chat.c Wed May 21 12:29:27 2008 +0000 @@ -197,6 +197,12 @@ return chat_name; } +static void insert_in_hash_table(gpointer key, gpointer value, gpointer user_data) +{ + GHashTable *hash_table = (GHashTable *)user_data; + g_hash_table_insert(hash_table, g_strdup(key), g_strdup(value)); +} + void jabber_chat_join(PurpleConnection *gc, GHashTable *data) { JabberChat *chat; @@ -225,18 +231,21 @@ char *buf = g_strdup_printf(_("%s is not a valid room name"), room); purple_notify_error(gc, _("Invalid Room Name"), _("Invalid Room Name"), buf); + purple_serv_got_join_chat_failed(gc, data); g_free(buf); return; } else if(!jabber_nameprep_validate(server)) { char *buf = g_strdup_printf(_("%s is not a valid server name"), server); purple_notify_error(gc, _("Invalid Server Name"), _("Invalid Server Name"), buf); + purple_serv_got_join_chat_failed(gc, data); g_free(buf); return; } else if(!jabber_resourceprep_validate(handle)) { char *buf = g_strdup_printf(_("%s is not a valid room handle"), handle); purple_notify_error(gc, _("Invalid Room Handle"), _("Invalid Room Handle"), buf); + purple_serv_got_join_chat_failed(gc, data); g_free(buf); return; } @@ -255,6 +264,11 @@ chat->server = g_strdup(server); chat->handle = g_strdup(handle); + /* Copy the data hash table to chat->components */ + chat->components = g_hash_table_new_full(g_str_hash, g_str_equal, + g_free, g_free); + g_hash_table_foreach(data, insert_in_hash_table, chat->components); + chat->members = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, (GDestroyNotify)jabber_chat_member_free); @@ -315,6 +329,7 @@ g_free(chat->server); g_free(chat->handle); g_hash_table_destroy(chat->members); + g_hash_table_destroy(chat->components); g_free(chat); } diff -r 02ef6c5c6650 -r e0e57b83b359 libpurple/protocols/jabber/chat.h --- a/libpurple/protocols/jabber/chat.h Wed May 21 05:07:25 2008 +0000 +++ b/libpurple/protocols/jabber/chat.h Wed May 21 12:29:27 2008 +0000 @@ -41,6 +41,7 @@ char *room; char *server; char *handle; + GHashTable *components; int id; PurpleConversation *conv; gboolean muc; diff -r 02ef6c5c6650 -r e0e57b83b359 libpurple/protocols/jabber/libxmpp.c --- a/libpurple/protocols/jabber/libxmpp.c Wed May 21 05:07:25 2008 +0000 +++ b/libpurple/protocols/jabber/libxmpp.c Wed May 21 12:29:27 2008 +0000 @@ -116,7 +116,7 @@ jabber_send_attention, /* send_attention */ jabber_attention_types, /* attention_types */ - /* padding */ + sizeof(PurplePluginProtocolInfo), /* struct_size */ NULL }; diff -r 02ef6c5c6650 -r e0e57b83b359 libpurple/protocols/jabber/presence.c --- a/libpurple/protocols/jabber/presence.c Wed May 21 05:07:25 2008 +0000 +++ b/libpurple/protocols/jabber/presence.c Wed May 21 12:29:27 2008 +0000 @@ -587,6 +587,7 @@ serv_got_chat_left(js->gc, chat->id); } else { title = g_strdup_printf(_("Error joining chat %s"), from); + purple_serv_got_join_chat_failed(js->gc, chat->components); } purple_notify_error(js->gc, title, title, msg); g_free(title); diff -r 02ef6c5c6650 -r e0e57b83b359 libpurple/protocols/msn/msn.c --- a/libpurple/protocols/msn/msn.c Wed May 21 05:07:25 2008 +0000 +++ b/libpurple/protocols/msn/msn.c Wed May 21 12:29:27 2008 +0000 @@ -32,6 +32,7 @@ #include "pluginpref.h" #include "prefs.h" #include "session.h" +#include "smiley.h" #include "state.h" #include "util.h" #include "cmds.h" @@ -82,6 +83,12 @@ time_t when; } MsnIMData; +typedef struct +{ + char *smile; + MsnObject *obj; +} MsnEmoticon; + static const char * msn_normalize(const PurpleAccount *account, const char *str) { @@ -115,6 +122,7 @@ return FALSE; msn_switchboard_send_msg(swboard, msg, TRUE); + msn_message_destroy(msg); return TRUE; } @@ -132,6 +140,17 @@ return list; } +static GHashTable * +msn_get_account_text_table(PurpleAccount *unused) +{ + GHashTable *table; + + table = g_hash_table_new(g_str_hash, g_str_equal); + + g_hash_table_insert(table, "login_label", (gpointer)_("E-mail Address...")); + + return table; +} static PurpleCmdRet msn_cmd_nudge(PurpleConversation *conv, const gchar *cmd, gchar **args, gchar **error, void *data) @@ -884,7 +903,8 @@ session = msn_session_new(account); gc->proto_data = session; - gc->flags |= PURPLE_CONNECTION_HTML | PURPLE_CONNECTION_FORMATTING_WBFO | PURPLE_CONNECTION_NO_BGCOLOR | PURPLE_CONNECTION_NO_FONTSIZE | PURPLE_CONNECTION_NO_URLDESC; + gc->flags |= PURPLE_CONNECTION_HTML | PURPLE_CONNECTION_FORMATTING_WBFO | PURPLE_CONNECTION_NO_BGCOLOR | + PURPLE_CONNECTION_NO_FONTSIZE | PURPLE_CONNECTION_NO_URLDESC | PURPLE_CONNECTION_ALLOW_CUSTOM_SMILEY; msn_session_set_login_step(session, MSN_LOGIN_STEP_START); @@ -925,6 +945,97 @@ return FALSE; } +static GString* +msn_msg_emoticon_add(GString *current, MsnEmoticon *emoticon) +{ + MsnObject *obj; + char *strobj; + + if (emoticon == NULL) + return current; + + obj = emoticon->obj; + + if (!obj) + return current; + + strobj = msn_object_to_string(obj); + + if (current) + g_string_append_printf(current, "\t%s\t%s", + emoticon->smile, strobj); + else { + current = g_string_new(""); + g_string_printf(current,"%s\t%s", + emoticon->smile, strobj); + } + + g_free(strobj); + + return current; +} + +static void +msn_send_emoticons(MsnSwitchBoard *swboard, GString *body) +{ + MsnMessage *msg; + + g_return_if_fail(body != NULL); + + msg = msn_message_new(MSN_MSG_SLP); + msn_message_set_content_type(msg, "text/x-mms-emoticon"); + msn_message_set_flag(msg, 'N'); + msn_message_set_bin_data(msg, body->str, body->len); + + msn_switchboard_send_msg(swboard, msg, TRUE); + msn_message_destroy(msg); +} + +static void msn_emoticon_destroy(MsnEmoticon *emoticon) +{ + if (emoticon->obj) + msn_object_destroy(emoticon->obj); + g_free(emoticon->smile); + g_free(emoticon); +} + +static GSList* msn_msg_grab_emoticons(const char *msg, const char *username) +{ + GSList *list; + GList *smileys; + PurpleSmiley *smiley; + PurpleStoredImage *img; + char *ptr; + MsnEmoticon *emoticon; + int length; + + list = NULL; + smileys = purple_smileys_get_all(); + length = strlen(msg); + + for (; smileys; smileys = g_list_delete_link(smileys, smileys)) { + smiley = smileys->data; + + ptr = g_strstr_len(msg, length, purple_smiley_get_shortcut(smiley)); + + if (!ptr) + continue; + + img = purple_smiley_get_stored_image(smiley); + + emoticon = g_new0(MsnEmoticon, 1); + emoticon->smile = g_strdup(purple_smiley_get_shortcut(smiley)); + emoticon->obj = msn_object_new_from_image(img, + purple_imgstore_get_filename(img), + username, MSN_OBJECT_EMOTICON); + + purple_imgstore_unref(img); + list = g_slist_prepend(list, emoticon); + } + + return list; +} + static int msn_send_im(PurpleConnection *gc, const char *who, const char *message, PurpleMessageFlags flags) @@ -934,9 +1045,11 @@ MsnMessage *msg; char *msgformat; char *msgtext; + const char *username; purple_debug_info("MSNP14","send IM {%s} to %s\n",message,who); account = purple_connection_get_account(gc); + username = purple_account_get_username(account); if (buddy) { PurplePresence *p = purple_buddy_get_presence(buddy); @@ -969,10 +1082,13 @@ g_free(msgtext); purple_debug_info("MSNP14","prepare to send online Message\n"); - if (g_ascii_strcasecmp(who, purple_account_get_username(account))) + if (g_ascii_strcasecmp(who, username)) { MsnSession *session; MsnSwitchBoard *swboard; + MsnEmoticon *smile; + GSList *smileys; + GString *emoticons = NULL; session = gc->proto_data; if(msn_user_is_yahoo(account,who)){ @@ -982,6 +1098,19 @@ }else{ purple_debug_info("MSNP14","send via switchboard\n"); swboard = msn_session_get_swboard(session, who, MSN_SB_FLAG_IM); + smileys = msn_msg_grab_emoticons(message, username); + while (smileys) { + smile = (MsnEmoticon*)smileys->data; + emoticons = msn_msg_emoticon_add(emoticons, smile); + msn_emoticon_destroy(smile); + smileys = g_slist_delete_link(smileys, smileys); + } + + if (emoticons) { + msn_send_emoticons(swboard, emoticons); + g_string_free(emoticons, TRUE); + } + msn_switchboard_send_msg(swboard, msg, TRUE); } } @@ -1422,7 +1551,7 @@ g_free(msgformat); g_free(msgtext); - serv_got_chat_in(gc, id, purple_account_get_username(account), 0, + serv_got_chat_in(gc, id, purple_account_get_username(account), flags, message, time(NULL)); return 0; @@ -2307,8 +2436,8 @@ msn_send_attention, /* send_attention */ msn_attention_types, /* attention_types */ - /* padding */ - NULL + sizeof(PurplePluginProtocolInfo), /* struct_size */ + msn_get_account_text_table, /* get_account_text_table */ }; static PurplePluginInfo info = diff -r 02ef6c5c6650 -r e0e57b83b359 libpurple/protocols/msn/object.c --- a/libpurple/protocols/msn/object.c Wed May 21 05:07:25 2008 +0000 +++ b/libpurple/protocols/msn/object.c Wed May 21 12:29:27 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 02ef6c5c6650 -r e0e57b83b359 libpurple/protocols/msn/object.h --- a/libpurple/protocols/msn/object.h Wed May 21 05:07:25 2008 +0000 +++ b/libpurple/protocols/msn/object.h Wed May 21 12:29:27 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 02ef6c5c6650 -r e0e57b83b359 libpurple/protocols/msn/slp.c --- a/libpurple/protocols/msn/slp.c Wed May 21 05:07:25 2008 +0000 +++ b/libpurple/protocols/msn/slp.c Wed May 21 12:29:27 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 02ef6c5c6650 -r e0e57b83b359 libpurple/protocols/msn/user.c --- a/libpurple/protocols/msn/user.c Wed May 21 05:07:25 2008 +0000 +++ b/libpurple/protocols/msn/user.c Wed May 21 12:29:27 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 02ef6c5c6650 -r e0e57b83b359 libpurple/protocols/msnp9/msn.c --- a/libpurple/protocols/msnp9/msn.c Wed May 21 05:07:25 2008 +0000 +++ b/libpurple/protocols/msnp9/msn.c Wed May 21 12:29:27 2008 +0000 @@ -33,6 +33,7 @@ #include "pluginpref.h" #include "prefs.h" #include "session.h" +#include "smiley.h" #include "state.h" #include "util.h" #include "cmds.h" @@ -83,6 +84,12 @@ time_t when; } MsnIMData; +typedef struct +{ + char *smile; + MsnObject *obj; +} MsnEmoticon; + static const char * msn_normalize(const PurpleAccount *account, const char *str) { @@ -116,6 +123,7 @@ return FALSE; msn_switchboard_send_msg(swboard, msg, TRUE); + msn_message_destroy(msg); return TRUE; } @@ -133,6 +141,17 @@ return list; } +static GHashTable * +msn_get_account_text_table(PurpleAccount *unused) +{ + GHashTable *table; + + table = g_hash_table_new(g_str_hash, g_str_equal); + + g_hash_table_insert(table, "login_label", (gpointer)_("E-mail Address...")); + + return table; +} static PurpleCmdRet msn_cmd_nudge(PurpleConversation *conv, const gchar *cmd, gchar **args, gchar **error, void *data) @@ -755,7 +774,8 @@ session = msn_session_new(account); gc->proto_data = session; - gc->flags |= PURPLE_CONNECTION_HTML | PURPLE_CONNECTION_FORMATTING_WBFO | PURPLE_CONNECTION_NO_BGCOLOR | PURPLE_CONNECTION_NO_FONTSIZE | PURPLE_CONNECTION_NO_URLDESC; + gc->flags |= PURPLE_CONNECTION_HTML | PURPLE_CONNECTION_FORMATTING_WBFO | PURPLE_CONNECTION_NO_BGCOLOR | + PURPLE_CONNECTION_NO_FONTSIZE | PURPLE_CONNECTION_NO_URLDESC | PURPLE_CONNECTION_ALLOW_CUSTOM_SMILEY; msn_session_set_login_step(session, MSN_LOGIN_STEP_START); @@ -796,6 +816,97 @@ return FALSE; } +static GString* +msn_msg_emoticon_add(GString *current, MsnEmoticon *emoticon) +{ + MsnObject *obj; + char *strobj; + + if (emoticon == NULL) + return current; + + obj = emoticon->obj; + + if (!obj) + return current; + + strobj = msn_object_to_string(obj); + + if (current) + g_string_append_printf(current, "\t%s\t%s", + emoticon->smile, strobj); + else { + current = g_string_new(""); + g_string_printf(current,"%s\t%s", + emoticon->smile, strobj); + } + + g_free(strobj); + + return current; +} + +static void +msn_send_emoticons(MsnSwitchBoard *swboard, GString *body) +{ + MsnMessage *msg; + + g_return_if_fail(body != NULL); + + msg = msn_message_new(MSN_MSG_SLP); + msn_message_set_content_type(msg, "text/x-mms-emoticon"); + msn_message_set_flag(msg, 'N'); + msn_message_set_bin_data(msg, body->str, body->len); + + msn_switchboard_send_msg(swboard, msg, TRUE); + msn_message_destroy(msg); +} + +static void msn_emoticon_destroy(MsnEmoticon *emoticon) +{ + if (emoticon->obj) + msn_object_destroy(emoticon->obj); + g_free(emoticon->smile); + g_free(emoticon); +} + +static GSList* msn_msg_grab_emoticons(const char *msg, const char *username) +{ + GSList *list; + GList *smileys; + PurpleSmiley *smiley; + PurpleStoredImage *img; + char *ptr; + MsnEmoticon *emoticon; + int length; + + list = NULL; + smileys = purple_smileys_get_all(); + length = strlen(msg); + + for (; smileys; smileys = g_list_delete_link(smileys, smileys)) { + smiley = (PurpleSmiley*)smileys->data; + + ptr = g_strstr_len(msg, length, purple_smiley_get_shortcut(smiley)); + + if (!ptr) + continue; + + img = purple_smiley_get_stored_image(smiley); + + emoticon = g_new0(MsnEmoticon, 1); + emoticon->smile = g_strdup(purple_smiley_get_shortcut(smiley)); + emoticon->obj = msn_object_new_from_image(img, + purple_imgstore_get_filename(img), + username, MSN_OBJECT_EMOTICON); + + purple_imgstore_unref(img); + list = g_slist_prepend(list, emoticon); + } + + return list; +} + static int msn_send_im(PurpleConnection *gc, const char *who, const char *message, PurpleMessageFlags flags) @@ -805,8 +916,10 @@ MsnMessage *msg; char *msgformat; char *msgtext; + const char *username; account = purple_connection_get_account(gc); + username = purple_account_get_username(account); if (buddy) { PurplePresence *p = purple_buddy_get_presence(buddy); @@ -834,13 +947,29 @@ g_free(msgformat); g_free(msgtext); - if (g_ascii_strcasecmp(who, purple_account_get_username(account))) + if (g_ascii_strcasecmp(who, username)) { MsnSession *session; MsnSwitchBoard *swboard; + MsnEmoticon *smile; + GSList *smileys; + GString *emoticons = NULL; session = gc->proto_data; swboard = msn_session_get_swboard(session, who, MSN_SB_FLAG_IM); + smileys = msn_msg_grab_emoticons(message, username); + + while (smileys) { + smile = (MsnEmoticon*)smileys->data; + emoticons = msn_msg_emoticon_add(emoticons,smile); + msn_emoticon_destroy(smile); + smileys = g_slist_delete_link(smileys, smileys); + } + + if (emoticons) { + msn_send_emoticons(swboard, emoticons); + g_string_free(emoticons, TRUE); + } msn_switchboard_send_msg(swboard, msg, TRUE); } @@ -1263,7 +1392,7 @@ g_free(msgformat); g_free(msgtext); - serv_got_chat_in(gc, id, purple_account_get_username(account), 0, + serv_got_chat_in(gc, id, purple_account_get_username(account), flags, message, time(NULL)); return 0; @@ -2148,8 +2277,8 @@ msn_send_attention, /* send_attention */ msn_attention_types, /* attention_types */ - /* padding */ - NULL + sizeof(PurplePluginProtocolInfo), /* struct_size */ + msn_get_account_text_table, /* get_account_text_table */ }; static PurplePluginInfo info = diff -r 02ef6c5c6650 -r e0e57b83b359 libpurple/protocols/msnp9/object.c --- a/libpurple/protocols/msnp9/object.c Wed May 21 05:07:25 2008 +0000 +++ b/libpurple/protocols/msnp9/object.c Wed May 21 12:29:27 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 02ef6c5c6650 -r e0e57b83b359 libpurple/protocols/msnp9/object.h --- a/libpurple/protocols/msnp9/object.h Wed May 21 05:07:25 2008 +0000 +++ b/libpurple/protocols/msnp9/object.h Wed May 21 12:29:27 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 02ef6c5c6650 -r e0e57b83b359 libpurple/protocols/msnp9/slp.c --- a/libpurple/protocols/msnp9/slp.c Wed May 21 05:07:25 2008 +0000 +++ b/libpurple/protocols/msnp9/slp.c Wed May 21 12:29:27 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 02ef6c5c6650 -r e0e57b83b359 libpurple/protocols/msnp9/user.c --- a/libpurple/protocols/msnp9/user.c Wed May 21 05:07:25 2008 +0000 +++ b/libpurple/protocols/msnp9/user.c Wed May 21 12:29:27 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 02ef6c5c6650 -r e0e57b83b359 libpurple/protocols/myspace/myspace.c --- a/libpurple/protocols/myspace/myspace.c Wed May 21 05:07:25 2008 +0000 +++ b/libpurple/protocols/myspace/myspace.c Wed May 21 12:29:27 2008 +0000 @@ -2449,6 +2449,18 @@ return normalized; } +static GHashTable * +msim_get_account_text_table(PurpleAccount *unused) +{ + GHashTable *table; + + table = g_hash_table_new(g_str_hash, g_str_equal); + + g_hash_table_insert(table, "login_label", (gpointer)_("E-mail Address...")); + + return table; +} + /** Return whether the buddy can be messaged while offline. * * The protocol supports offline messages in just the same way as online @@ -3128,14 +3140,16 @@ NULL, /* unregister_user */ msim_send_attention, /* send_attention */ msim_attention_types, /* attention_types */ - NULL /* _purple_reserved4 */ + + sizeof(PurplePluginProtocolInfo), /* struct_size */ + msim_get_account_text_table, /* get_account_text_table */ }; /** Based on MSN's plugin info comments. */ static PurplePluginInfo info = { - PURPLE_PLUGIN_MAGIC, + PURPLE_PLUGIN_MAGIC, PURPLE_MAJOR_VERSION, PURPLE_MINOR_VERSION, PURPLE_PLUGIN_PROTOCOL, /**< type */ diff -r 02ef6c5c6650 -r e0e57b83b359 libpurple/protocols/novell/novell.c --- a/libpurple/protocols/novell/novell.c Wed May 21 05:07:25 2008 +0000 +++ b/libpurple/protocols/novell/novell.c Wed May 21 12:29:27 2008 +0000 @@ -2506,7 +2506,7 @@ } } - serv_got_chat_in(gc, id, name, 0, text, time(NULL)); + serv_got_chat_in(gc, id, name, flags, text, time(NULL)); return 0; } else return -1; @@ -3517,6 +3517,7 @@ NULL, NULL, NULL, + sizeof(PurplePluginProtocolInfo), /* struct_size */ NULL }; diff -r 02ef6c5c6650 -r e0e57b83b359 libpurple/protocols/oscar/libaim.c --- a/libpurple/protocols/oscar/libaim.c Wed May 21 05:07:25 2008 +0000 +++ b/libpurple/protocols/oscar/libaim.c Wed May 21 12:29:27 2008 +0000 @@ -96,7 +96,7 @@ NULL, /* send_attention */ NULL, /* get_attention_types */ - /* padding */ + sizeof(PurplePluginProtocolInfo), /* struct_size */ NULL }; diff -r 02ef6c5c6650 -r e0e57b83b359 libpurple/protocols/oscar/libicq.c --- a/libpurple/protocols/oscar/libicq.c Wed May 21 05:07:25 2008 +0000 +++ b/libpurple/protocols/oscar/libicq.c Wed May 21 12:29:27 2008 +0000 @@ -96,7 +96,7 @@ NULL, /* send_attention */ NULL, /* get_attention_types */ - /* padding */ + sizeof(PurplePluginProtocolInfo), /* struct_size */ NULL }; diff -r 02ef6c5c6650 -r e0e57b83b359 libpurple/protocols/qq/qq.c --- a/libpurple/protocols/qq/qq.c Wed May 21 05:07:25 2008 +0000 +++ b/libpurple/protocols/qq/qq.c Wed May 21 12:29:27 2008 +0000 @@ -706,6 +706,7 @@ NULL, NULL, NULL, + sizeof(PurplePluginProtocolInfo), /* struct_size */ NULL }; diff -r 02ef6c5c6650 -r e0e57b83b359 libpurple/protocols/silc/chat.c --- a/libpurple/protocols/silc/chat.c Wed May 21 05:07:25 2008 +0000 +++ b/libpurple/protocols/silc/chat.c Wed May 21 12:29:27 2008 +0000 @@ -1315,7 +1315,7 @@ g_free(tmp); if (ret) - serv_got_chat_in(gc, id, purple_connection_get_display_name(gc), 0, msg, time(NULL)); + serv_got_chat_in(gc, id, purple_connection_get_display_name(gc), msgflags, msg, time(NULL)); return ret; } } @@ -1326,7 +1326,7 @@ (unsigned char *)msg2, strlen(msg2)); if (ret) { - serv_got_chat_in(gc, id, purple_connection_get_display_name(gc), 0, msg, + serv_got_chat_in(gc, id, purple_connection_get_display_name(gc), msgflags, msg, time(NULL)); } g_free(tmp); diff -r 02ef6c5c6650 -r e0e57b83b359 libpurple/protocols/silc/silc.c --- a/libpurple/protocols/silc/silc.c Wed May 21 05:07:25 2008 +0000 +++ b/libpurple/protocols/silc/silc.c Wed May 21 12:29:27 2008 +0000 @@ -2071,6 +2071,7 @@ NULL, NULL, NULL, + sizeof(PurplePluginProtocolInfo), /* struct_size */ NULL }; diff -r 02ef6c5c6650 -r e0e57b83b359 libpurple/protocols/silc10/chat.c --- a/libpurple/protocols/silc10/chat.c Wed May 21 05:07:25 2008 +0000 +++ b/libpurple/protocols/silc10/chat.c Wed May 21 12:29:27 2008 +0000 @@ -1351,7 +1351,7 @@ flags, (unsigned char *)msg2, strlen(msg2), TRUE); if (ret) { - serv_got_chat_in(gc, id, purple_connection_get_display_name(gc), 0, msg, + serv_got_chat_in(gc, id, purple_connection_get_display_name(gc), msgflags, msg, time(NULL)); } g_free(tmp); diff -r 02ef6c5c6650 -r e0e57b83b359 libpurple/protocols/silc10/silc.c --- a/libpurple/protocols/silc10/silc.c Wed May 21 05:07:25 2008 +0000 +++ b/libpurple/protocols/silc10/silc.c Wed May 21 12:29:27 2008 +0000 @@ -1800,10 +1800,10 @@ NULL, /* send_raw */ NULL, /* roomlist_room_serialize */ - /* padding */ NULL, NULL, NULL, + sizeof(PurplePluginProtocolInfo), /* struct_size */ NULL }; diff -r 02ef6c5c6650 -r e0e57b83b359 libpurple/protocols/simple/simple.c --- a/libpurple/protocols/simple/simple.c Wed May 21 05:07:25 2008 +0000 +++ b/libpurple/protocols/simple/simple.c Wed May 21 12:29:27 2008 +0000 @@ -2068,6 +2068,7 @@ NULL, NULL, NULL, + sizeof(PurplePluginProtocolInfo), /* struct_size */ NULL }; diff -r 02ef6c5c6650 -r e0e57b83b359 libpurple/protocols/yahoo/yahoo.c --- a/libpurple/protocols/yahoo/yahoo.c Wed May 21 05:07:25 2008 +0000 +++ b/libpurple/protocols/yahoo/yahoo.c Wed May 21 12:29:27 2008 +0000 @@ -4383,7 +4383,7 @@ yahoo_send_attention, yahoo_attention_types, - /* padding */ + sizeof(PurplePluginProtocolInfo), /* struct_size */ NULL }; diff -r 02ef6c5c6650 -r e0e57b83b359 libpurple/protocols/yahoo/yahoochat.c --- a/libpurple/protocols/yahoo/yahoochat.c Wed May 21 05:07:25 2008 +0000 +++ b/libpurple/protocols/yahoo/yahoochat.c Wed May 21 12:29:27 2008 +0000 @@ -1043,7 +1043,7 @@ purple_conversation_get_name(c), what, flags); if (!ret) serv_got_chat_in(gc, purple_conv_chat_get_id(PURPLE_CONV_CHAT(c)), - purple_connection_get_display_name(gc), 0, what, time(NULL)); + purple_connection_get_display_name(gc), flags, what, time(NULL)); } return ret; } diff -r 02ef6c5c6650 -r e0e57b83b359 libpurple/protocols/zephyr/zephyr.c --- a/libpurple/protocols/zephyr/zephyr.c Wed May 21 05:07:25 2008 +0000 +++ b/libpurple/protocols/zephyr/zephyr.c Wed May 21 12:29:27 2008 +0000 @@ -2907,10 +2907,10 @@ NULL, /* send_raw */ NULL, /* roomlist_room_serialize */ - /* padding */ NULL, NULL, NULL, + sizeof(PurplePluginProtocolInfo), /* struct_size */ NULL }; diff -r 02ef6c5c6650 -r e0e57b83b359 libpurple/prpl.h --- a/libpurple/prpl.h Wed May 21 05:07:25 2008 +0000 +++ b/libpurple/prpl.h Wed May 21 12:29:27 2008 +0000 @@ -399,9 +399,45 @@ gboolean (*send_attention)(PurpleConnection *gc, const char *username, guint type); GList *(*get_attention_types)(PurpleAccount *acct); - void (*_purple_reserved4)(void); + /** + * The size of the PurplePluginProtocolInfo. This should always be sizeof(PurplePluginProtocolInfo). + * This allows adding more functions to this struct without requiring a major version bump. + */ + unsigned long struct_size; + + /* NOTE: + * If more functions are added, they should accessed using the following syntax: + * + * if (PURPLE_PROTOCOL_PLUGIN_HAS_FUNC(prpl, new_function)) + * prpl->new_function(...); + * + * instead of + * + * if (prpl->new_function != NULL) + * prpl->new_function(...); + * + * The PURPLE_PROTOCOL_PLUGIN_HAS_FUNC macro can be used for the older member + * functions (e.g. login, send_im etc.) too. + */ + + /** This allows protocols to specify additional strings to be used for + * various purposes. The idea is to stuff a bunch of strings in this hash + * table instead of expanding the struct for every addition. This hash + * table is allocated every call and MUST be unrefed by the caller. + * + * @param account The account to specify. This can be NULL. + * @return The protocol's string hash table. The hash table should be + * destroyed by the caller when it's no longer needed. + */ + GHashTable *(*get_account_text_table)(PurpleAccount *account); }; +#define PURPLE_PROTOCOL_PLUGIN_HAS_FUNC(prpl, member) \ + (((G_STRUCT_OFFSET(PurplePluginProtocolInfo, member) < G_STRUCT_OFFSET(PurplePluginProtocolInfo, struct_size)) \ + || (G_STRUCT_OFFSET(PurplePluginProtocolInfo, member) < prpl->struct_size)) && \ + prpl->member != NULL) + + #define PURPLE_IS_PROTOCOL_PLUGIN(plugin) \ ((plugin)->info->type == PURPLE_PLUGIN_PROTOCOL) diff -r 02ef6c5c6650 -r e0e57b83b359 libpurple/server.c --- a/libpurple/server.c Wed May 21 05:07:25 2008 +0000 +++ b/libpurple/server.c Wed May 21 12:29:27 2008 +0000 @@ -967,6 +967,12 @@ purple_signal_emit(purple_conversations_get_handle(), "chat-left", conv); } +void purple_serv_got_join_chat_failed(PurpleConnection *gc, GHashTable *data) +{ + purple_signal_emit(purple_conversations_get_handle(), "chat-join-failed", + gc, data); +} + void serv_got_chat_in(PurpleConnection *g, int id, const char *who, PurpleMessageFlags flags, const char *message, time_t mtime) { diff -r 02ef6c5c6650 -r e0e57b83b359 libpurple/server.h --- a/libpurple/server.h Wed May 21 05:07:25 2008 +0000 +++ b/libpurple/server.h Wed May 21 12:29:27 2008 +0000 @@ -166,6 +166,17 @@ PurpleConversation *serv_got_joined_chat(PurpleConnection *gc, int id, const char *name); +/** + * Called by a prpl when an attempt to join a chat via serv_join_chat() + * fails. + * + * @param gc The connection on which chat joining failed + * @param data The components passed to serv_join_chat() originally. + * The hash function should be g_str_hash() and the equal + * function should be g_str_equal(). + */ +void purple_serv_got_join_chat_failed(PurpleConnection *gc, GHashTable *data); + void serv_got_chat_left(PurpleConnection *g, int id); void serv_got_chat_in(PurpleConnection *g, int id, const char *who, PurpleMessageFlags flags, const char *message, time_t mtime); diff -r 02ef6c5c6650 -r e0e57b83b359 libpurple/smiley.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/smiley.c Wed May 21 12:29:27 2008 +0000 @@ -0,0 +1,915 @@ +/** + * @file smiley.c Simley API + * @ingroup core + */ + +/* purple + * + * Purple is the legal property of its developers, whose names are too numerous + * to list here. Please refer to the COPYRIGHT file distributed with this + * source distribution. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA + */ + +#include "internal.h" +#include "dbus-maybe.h" +#include "debug.h" +#include "imgstore.h" +#include "smiley.h" +#include "util.h" +#include "xmlnode.h" + +/**************************************************************************/ +/* Main structures, members and constants */ +/**************************************************************************/ + +struct _PurpleSmiley +{ + GObject parent; + PurpleStoredImage *img; /**< The id of the stored image with the + the smiley data. */ + char *shortcut; /**< Shortcut associated with the custom + smiley. This field will work as a + unique key by this API. */ + char *checksum; /**< The smiley checksum. */ +}; + +struct _PurpleSmileyClass +{ + GObjectClass parent_class; +}; + +static GHashTable *smiley_shortcut_index = NULL; /* shortcut (char *) => smiley (PurpleSmiley*) */ +static GHashTable *smiley_checksum_index = NULL; /* checksum (char *) => smiley (PurpleSmiley*) */ + +static guint save_timer = 0; +static gboolean smileys_loaded = FALSE; +static char *smileys_dir = NULL; + +#define SMILEYS_DEFAULT_FOLDER "custom_smiley" +#define SMILEYS_LOG_ID "smileys" + +#define XML_FILE_NAME "smileys.xml" + +#define XML_ROOT_TAG "smileys" +#define XML_PROFILE_TAG "profile" +#define XML_PROFILE_NAME_ATTRIB_TAG "name" +#define XML_ACCOUNT_TAG "account" +#define XML_ACCOUNT_USERID_ATTRIB_TAG "userid" +#define XML_SMILEY_SET_TAG "smiley_set" +#define XML_SMILEY_TAG "smiley" +#define XML_SHORTCUT_ATTRIB_TAG "shortcut" +#define XML_CHECKSUM_ATRIB_TAG "checksum" +#define XML_FILENAME_ATRIB_TAG "filename" + + +/****************************************************************************** + * XML descriptor file layout * + ****************************************************************************** + * + * Althought we are creating the profile XML structure here, now we + * won't handle it. + * So, we just add one profile named "default" that has no associated + * account elements, and have only the smiley_set that will contain + * all existent custom smiley. + * + * It's our "Highlander Profile" :-) + * + ****************************************************************************** + * + * + * + * + * + * + * + * + * + * + * + * + *****************************************************************************/ + + +/********************************************************************* + * Forward declarations * + *********************************************************************/ + +static gboolean read_smiley_file(const char *path, guchar **data, size_t *len); + +static char *get_file_full_path(const char *filename); + +static PurpleSmiley *purple_smiley_create(const char *shortcut); + +static PurpleSmiley *purple_smiley_load_file(const char *shortcut, const char *checksum, + const char *filename); + +static void +purple_smiley_set_data_impl(PurpleSmiley *smiley, guchar *smiley_data, + size_t smiley_data_len, const char *filename); + +static void +purple_smiley_data_store(PurpleStoredImage *stored_img); + +static void +purple_smiley_data_unstore(const char *filename); + +/********************************************************************* + * Writing to disk * + *********************************************************************/ + +static xmlnode * +smiley_to_xmlnode(PurpleSmiley *smiley) +{ + xmlnode *smiley_node = NULL; + + smiley_node = xmlnode_new(XML_SMILEY_TAG); + + if (!smiley_node) + return NULL; + + xmlnode_set_attrib(smiley_node, XML_SHORTCUT_ATTRIB_TAG, + smiley->shortcut); + + xmlnode_set_attrib(smiley_node, XML_CHECKSUM_ATRIB_TAG, + smiley->checksum); + + xmlnode_set_attrib(smiley_node, XML_FILENAME_ATRIB_TAG, + purple_imgstore_get_filename(smiley->img)); + + return smiley_node; +} + +static void +add_smiley_to_main_node(gpointer key, gpointer value, gpointer user_data) +{ + xmlnode *child_node; + + child_node = smiley_to_xmlnode(value); + xmlnode_insert_child((xmlnode*)user_data, child_node); +} + +static xmlnode * +smileys_to_xmlnode() +{ + xmlnode *root_node, *profile_node, *smileyset_node; + + root_node = xmlnode_new(XML_ROOT_TAG); + xmlnode_set_attrib(root_node, "version", "1.0"); + + /* See the top comment's above to understand why initial tag elements + * are not being considered by now. */ + profile_node = xmlnode_new(XML_PROFILE_TAG); + if (profile_node) { + xmlnode_set_attrib(profile_node, XML_PROFILE_NAME_ATTRIB_TAG, "Default"); + xmlnode_insert_child(root_node, profile_node); + + smileyset_node = xmlnode_new(XML_SMILEY_SET_TAG); + if (smileyset_node) { + xmlnode_insert_child(profile_node, smileyset_node); + g_hash_table_foreach(smiley_shortcut_index, add_smiley_to_main_node, smileyset_node); + } + } + + return root_node; +} + +static void +sync_smileys() +{ + xmlnode *root_node; + char *data; + + if (!smileys_loaded) { + purple_debug_error(SMILEYS_LOG_ID, "Attempted to save smileys before it " + "was read!\n"); + return; + } + + root_node = smileys_to_xmlnode(); + data = xmlnode_to_formatted_str(root_node, NULL); + purple_util_write_data_to_file(XML_FILE_NAME, data, -1); + + g_free(data); + xmlnode_free(root_node); +} + +static gboolean +save_smileys_cb(gpointer data) +{ + sync_smileys(); + save_timer = 0; + return FALSE; +} + +static void +purple_smileys_save() +{ + if (save_timer == 0) + save_timer = purple_timeout_add_seconds(5, save_smileys_cb, NULL); +} + + +/********************************************************************* + * Reading from disk * + *********************************************************************/ + +static PurpleSmiley * +parse_smiley(xmlnode *smiley_node) +{ + PurpleSmiley *smiley; + const char *shortcut = NULL; + const char *checksum = NULL; + const char *filename = NULL; + + shortcut = xmlnode_get_attrib(smiley_node, XML_SHORTCUT_ATTRIB_TAG); + checksum = xmlnode_get_attrib(smiley_node, XML_CHECKSUM_ATRIB_TAG); + filename = xmlnode_get_attrib(smiley_node, XML_FILENAME_ATRIB_TAG); + + if ((shortcut == NULL) || (checksum == NULL) || (filename == NULL)) + return NULL; + + smiley = purple_smiley_load_file(shortcut, checksum, filename); + + return smiley; +} + +static void +purple_smileys_load() +{ + xmlnode *root_node, *profile_node; + xmlnode *smileyset_node = NULL; + xmlnode *smiley_node; + + smileys_loaded = TRUE; + + root_node = purple_util_read_xml_from_file(XML_FILE_NAME, + _(SMILEYS_LOG_ID)); + + if (root_node == NULL) + return; + + /* See the top comment's above to understand why initial tag elements + * are not being considered by now. */ + profile_node = xmlnode_get_child(root_node, XML_PROFILE_TAG); + if (profile_node) + smileyset_node = xmlnode_get_child(profile_node, XML_SMILEY_SET_TAG); + + if (smileyset_node) { + smiley_node = xmlnode_get_child(smileyset_node, XML_SMILEY_TAG); + for (; smiley_node != NULL; + smiley_node = xmlnode_get_next_twin(smiley_node)) { + PurpleSmiley *smiley; + + smiley = parse_smiley(smiley_node); + } + } + + xmlnode_free(root_node); +} + +/********************************************************************* + * GObject Stuff * + *********************************************************************/ +enum +{ + PROP_0, + PROP_SHORTCUT, + PROP_IMGSTORE, +}; + +#define PROP_SHORTCUT_S "shortcut" +#define PROP_IMGSTORE_S "image" + +enum +{ + SIG_DESTROY, + SIG_LAST +}; + +static guint signals[SIG_LAST]; +static GObjectClass *parent_class; + +static void +purple_smiley_init(GTypeInstance *instance, gpointer klass) +{ + PurpleSmiley *smiley = PURPLE_SMILEY(instance); + PURPLE_DBUS_REGISTER_POINTER(smiley, PurpleSmiley); +} + +static void +purple_smiley_get_property(GObject *object, guint param_id, GValue *value, + GParamSpec *spec) +{ + PurpleSmiley *smiley = PURPLE_SMILEY(object); + switch (param_id) { + case PROP_SHORTCUT: + g_value_set_string(value, smiley->shortcut); + break; + case PROP_IMGSTORE: + g_value_set_pointer(value, smiley->img); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, param_id, spec); + break; + } +} + +static void +purple_smiley_set_property(GObject *object, guint param_id, const GValue *value, + GParamSpec *spec) +{ + PurpleSmiley *smiley = PURPLE_SMILEY(object); + switch (param_id) { + case PROP_SHORTCUT: + { + const char *shortcut = g_value_get_string(value); + purple_smiley_set_shortcut(smiley, shortcut); + } + break; + case PROP_IMGSTORE: + { + PurpleStoredImage *img = g_value_get_pointer(value); + + purple_imgstore_unref(smiley->img); + g_free(smiley->checksum); + + smiley->img = img; + if (img) { + smiley->checksum = purple_util_get_image_checksum( + purple_imgstore_get_data(img), + purple_imgstore_get_size(img)); + purple_smiley_data_store(img); + } else { + smiley->checksum = NULL; + } + + g_object_notify(object, PROP_IMGSTORE_S); + } + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, param_id, spec); + break; + } +} + +static void +purple_smiley_finalize(GObject *obj) +{ + PurpleSmiley *smiley = PURPLE_SMILEY(obj); + + if (g_hash_table_lookup(smiley_shortcut_index, smiley->shortcut)) { + g_hash_table_remove(smiley_shortcut_index, smiley->shortcut); + g_hash_table_remove(smiley_checksum_index, smiley->checksum); + } + + g_free(smiley->shortcut); + g_free(smiley->checksum); + if (smiley->img) + purple_smiley_data_unstore(purple_imgstore_get_filename(smiley->img)); + purple_imgstore_unref(smiley->img); + + PURPLE_DBUS_UNREGISTER_POINTER(smiley); + + purple_smileys_save(); +} + +static void +purple_smiley_dispose(GObject *gobj) +{ + g_signal_emit(gobj, signals[SIG_DESTROY], 0); + parent_class->dispose(gobj); +} + +static void +purple_smiley_class_init(PurpleSmileyClass *klass) +{ + GObjectClass *gobj_class = G_OBJECT_CLASS(klass); + GParamSpec *pspec; + + parent_class = g_type_class_peek_parent(klass); + + gobj_class->get_property = purple_smiley_get_property; + gobj_class->set_property = purple_smiley_set_property; + gobj_class->finalize = purple_smiley_finalize; + gobj_class->dispose = purple_smiley_dispose; + + /* Shortcut */ + pspec = g_param_spec_string(PROP_SHORTCUT_S, _("Shortcut"), + _("The text-shortcut for the smiley"), + NULL, + G_PARAM_READWRITE); + g_object_class_install_property(gobj_class, PROP_SHORTCUT, pspec); + + /* Stored Image */ + pspec = g_param_spec_pointer(PROP_IMGSTORE_S, _("Stored Image"), + _("Stored Image. (that'll have to do for now)"), + G_PARAM_READWRITE); + g_object_class_install_property(gobj_class, PROP_IMGSTORE, pspec); + + signals[SIG_DESTROY] = g_signal_new("destroy", + G_OBJECT_CLASS_TYPE(klass), + G_SIGNAL_RUN_LAST, + 0, NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); +} + +GType +purple_smiley_get_type(void) +{ + static GType type = 0; + + if(type == 0) { + static const GTypeInfo info = { + sizeof(PurpleSmileyClass), + NULL, + NULL, + (GClassInitFunc)purple_smiley_class_init, + NULL, + NULL, + sizeof(PurpleSmiley), + 0, + purple_smiley_init, + NULL, + }; + + type = g_type_register_static(G_TYPE_OBJECT, + "PurpleSmiley", + &info, 0); + } + + return type; +} + +/********************************************************************* + * Other Stuff * + *********************************************************************/ + +static char *get_file_full_path(const char *filename) +{ + char *path; + + path = g_build_filename(purple_smileys_get_storing_dir(), filename, NULL); + + if (!g_file_test(path, G_FILE_TEST_EXISTS)) { + g_free(path); + return NULL; + } + + return path; +} + +static PurpleSmiley * +purple_smiley_load_file(const char *shortcut, const char *checksum, const char *filename) +{ + PurpleSmiley *smiley = NULL; + guchar *smiley_data; + size_t smiley_data_len; + char *fullpath = NULL; + + g_return_val_if_fail(shortcut != NULL, NULL); + g_return_val_if_fail(checksum != NULL, NULL); + g_return_val_if_fail(filename != NULL, NULL); + + fullpath = get_file_full_path(filename); + if (!fullpath) + return NULL; + + smiley = purple_smiley_create(shortcut); + if (!smiley) { + g_free(fullpath); + return NULL; + } + + smiley->checksum = g_strdup(checksum); + + if (read_smiley_file(fullpath, &smiley_data, &smiley_data_len)) + purple_smiley_set_data_impl(smiley, smiley_data, + smiley_data_len, filename); + else + purple_smiley_delete(smiley); + + g_free(fullpath); + + return smiley; +} + +static void +purple_smiley_data_store(PurpleStoredImage *stored_img) +{ + const char *dirname; + char *path; + FILE *file = NULL; + + g_return_if_fail(stored_img != NULL); + + if (!smileys_loaded) + return; + + dirname = purple_smileys_get_storing_dir(); + path = g_build_filename(dirname, purple_imgstore_get_filename(stored_img), NULL); + + if (!g_file_test(dirname, G_FILE_TEST_IS_DIR)) { + purple_debug_info(SMILEYS_LOG_ID, "Creating smileys directory.\n"); + + if (g_mkdir(dirname, S_IRUSR | S_IWUSR | S_IXUSR) < 0) { + purple_debug_error(SMILEYS_LOG_ID, + "Unable to create directory %s: %s\n", + dirname, g_strerror(errno)); + } + } + + if ((file = g_fopen(path, "wb")) != NULL) { + if (!fwrite(purple_imgstore_get_data(stored_img), + purple_imgstore_get_size(stored_img), 1, file)) { + purple_debug_error(SMILEYS_LOG_ID, "Error writing %s: %s\n", + path, g_strerror(errno)); + } else { + purple_debug_info(SMILEYS_LOG_ID, "Wrote cache file: %s\n", path); + } + + fclose(file); + } else { + purple_debug_error(SMILEYS_LOG_ID, "Unable to create file %s: %s\n", + path, g_strerror(errno)); + g_free(path); + + return; + } + + g_free(path); +} + +static void +purple_smiley_data_unstore(const char *filename) +{ + const char *dirname; + char *path; + + g_return_if_fail(filename != NULL); + + dirname = purple_smileys_get_storing_dir(); + path = g_build_filename(dirname, filename, NULL); + + if (g_file_test(path, G_FILE_TEST_EXISTS)) { + if (g_unlink(path)) + purple_debug_error(SMILEYS_LOG_ID, "Failed to delete %s: %s\n", + path, g_strerror(errno)); + else + purple_debug_info(SMILEYS_LOG_ID, "Deleted cache file: %s\n", path); + } + + g_free(path); +} + +static gboolean +read_smiley_file(const char *path, guchar **data, size_t *len) +{ + GError *err = NULL; + + if (!g_file_get_contents(path, (gchar **)data, len, &err)) { + purple_debug_error(SMILEYS_LOG_ID, "Error reading %s: %s\n", + path, err->message); + g_error_free(err); + + return FALSE; + } + + return TRUE; +} + +static PurpleStoredImage * +purple_smiley_data_new(guchar *smiley_data, size_t smiley_data_len) +{ + char *filename; + PurpleStoredImage *stored_img; + + g_return_val_if_fail(smiley_data != NULL, NULL); + g_return_val_if_fail(smiley_data_len > 0, NULL); + + filename = purple_util_get_image_filename(smiley_data, smiley_data_len); + + if (filename == NULL) { + g_free(smiley_data); + return NULL; + } + + stored_img = purple_imgstore_add(smiley_data, smiley_data_len, filename); + + g_free(filename); + + return stored_img; +} + +static void +purple_smiley_set_data_impl(PurpleSmiley *smiley, guchar *smiley_data, + size_t smiley_data_len, const char *filename) +{ + PurpleStoredImage *old_img, *new_img; + const char *old_filename = NULL; + const char *new_filename = NULL; + + g_return_if_fail(smiley != NULL); + g_return_if_fail(smiley_data != NULL); + g_return_if_fail(smiley_data_len > 0); + + old_img = smiley->img; + + if (filename) + new_img = purple_imgstore_add(smiley_data, smiley_data_len, filename); + else + new_img = purple_smiley_data_new(smiley_data, smiley_data_len); + + g_object_set(G_OBJECT(smiley), PROP_IMGSTORE_S, new_img, NULL); + + /* If the old and new image files have different names we need + * to unstore old image file. */ + if (!old_img) + return; + + old_filename = purple_imgstore_get_filename(old_img); + new_filename = purple_imgstore_get_filename(smiley->img); + + if (g_ascii_strcasecmp(old_filename, new_filename)) { + purple_smiley_data_unstore(old_filename); + purple_imgstore_unref(old_img); + } +} + + +/***************************************************************************** + * Public API functions * + *****************************************************************************/ + +static PurpleSmiley * +purple_smiley_create(const char *shortcut) +{ + PurpleSmiley *smiley; + + smiley = PURPLE_SMILEY(g_object_new(PURPLE_TYPE_SMILEY, PROP_SHORTCUT_S, shortcut, NULL)); + + return smiley; +} + +PurpleSmiley * +purple_smiley_new(PurpleStoredImage *img, const char *shortcut) +{ + PurpleSmiley *smiley = NULL; + + g_return_val_if_fail(shortcut != NULL, NULL); + g_return_val_if_fail(img != NULL, NULL); + + smiley = purple_smileys_find_by_shortcut(shortcut); + if (smiley) + return smiley; + + smiley = purple_smiley_create(shortcut); + if (!smiley) + return NULL; + + g_object_set(G_OBJECT(smiley), PROP_IMGSTORE_S, img, NULL); + + return smiley; +} + +static PurpleSmiley * +purple_smiley_new_from_stream(const char *shortcut, guchar *smiley_data, + size_t smiley_data_len, const char *filename) +{ + PurpleSmiley *smiley; + + g_return_val_if_fail(shortcut != NULL, NULL); + g_return_val_if_fail(smiley_data != NULL, NULL); + g_return_val_if_fail(smiley_data_len > 0, NULL); + + smiley = purple_smileys_find_by_shortcut(shortcut); + if (smiley) + return smiley; + + /* purple_smiley_create() sets shortcut */ + smiley = purple_smiley_create(shortcut); + if (!smiley) + return NULL; + + purple_smiley_set_data_impl(smiley, smiley_data, smiley_data_len, filename); + + purple_smiley_data_store(smiley->img); + + return smiley; +} + +PurpleSmiley * +purple_smiley_new_from_file(const char *shortcut, const char *filepath) +{ + PurpleSmiley *smiley = NULL; + guchar *smiley_data; + size_t smiley_data_len; + char *filename; + + g_return_val_if_fail(shortcut != NULL, NULL); + g_return_val_if_fail(filepath != NULL, NULL); + + filename = g_path_get_basename(filepath); + if (read_smiley_file(filepath, &smiley_data, &smiley_data_len)) + smiley = purple_smiley_new_from_stream(shortcut, smiley_data, + smiley_data_len, filename); + g_free(filename); + + return smiley; +} + +void +purple_smiley_delete(PurpleSmiley *smiley) +{ + g_return_if_fail(smiley != NULL); + + g_object_unref(smiley); +} + +gboolean +purple_smiley_set_shortcut(PurpleSmiley *smiley, const char *shortcut) +{ + g_return_val_if_fail(smiley != NULL, FALSE); + g_return_val_if_fail(shortcut != NULL, FALSE); + + /* Check out whether the new shortcut is already being used. */ + if (g_hash_table_lookup(smiley_shortcut_index, shortcut)) + return FALSE; + + /* Remove the old shortcut. */ + if (smiley->shortcut) + g_hash_table_remove(smiley_shortcut_index, smiley->shortcut); + + /* Insert the new shortcut. */ + g_hash_table_insert(smiley_shortcut_index, g_strdup(shortcut), smiley); + + g_free(smiley->shortcut); + smiley->shortcut = g_strdup(shortcut); + + g_object_notify(G_OBJECT(smiley), PROP_SHORTCUT_S); + + purple_smileys_save(); + + return TRUE; +} + +void +purple_smiley_set_data(PurpleSmiley *smiley, guchar *smiley_data, + size_t smiley_data_len, gboolean keepfilename) +{ + g_return_if_fail(smiley != NULL); + g_return_if_fail(smiley_data != NULL); + g_return_if_fail(smiley_data_len > 0); + + /* Remove the previous entry */ + g_hash_table_remove(smiley_checksum_index, smiley->checksum); + + /* Update the file data. This also updates the checksum. */ + if ((keepfilename) && (smiley->img) && + (purple_imgstore_get_filename(smiley->img))) + purple_smiley_set_data_impl(smiley, smiley_data, + smiley_data_len, + purple_imgstore_get_filename(smiley->img)); + else + purple_smiley_set_data_impl(smiley, smiley_data, + smiley_data_len, NULL); + + /* Reinsert the index item. */ + g_hash_table_insert(smiley_checksum_index, g_strdup(smiley->checksum), smiley); + + purple_smileys_save(); +} + +PurpleStoredImage * +purple_smiley_get_stored_image(const PurpleSmiley *smiley) +{ + return purple_imgstore_ref(smiley->img); +} + +const char *purple_smiley_get_shortcut(const PurpleSmiley *smiley) +{ + g_return_val_if_fail(smiley != NULL, NULL); + + return smiley->shortcut; +} + +const char * +purple_smiley_get_checksum(const PurpleSmiley *smiley) +{ + g_return_val_if_fail(smiley != NULL, NULL); + + return smiley->checksum; +} + +gconstpointer +purple_smiley_get_data(const PurpleSmiley *smiley, size_t *len) +{ + g_return_val_if_fail(smiley != NULL, NULL); + + if (smiley->img) { + if (len != NULL) + *len = purple_imgstore_get_size(smiley->img); + + return purple_imgstore_get_data(smiley->img); + } + + return NULL; +} + +const char * +purple_smiley_get_extension(const PurpleSmiley *smiley) +{ + if (smiley->img != NULL) + return purple_imgstore_get_extension(smiley->img); + + return NULL; +} + +char *purple_smiley_get_full_path(PurpleSmiley *smiley) +{ + g_return_val_if_fail(smiley != NULL, NULL); + + if (smiley->img == NULL) + return NULL; + + return get_file_full_path(purple_imgstore_get_filename(smiley->img)); +} + +static void add_smiley_to_list(gpointer key, gpointer value, gpointer user_data) +{ + GList** returninglist = (GList**)user_data; + + *returninglist = g_list_append(*returninglist, value); +} + +GList * +purple_smileys_get_all(void) +{ + GList *returninglist = NULL; + + g_hash_table_foreach(smiley_shortcut_index, add_smiley_to_list, &returninglist); + + return returninglist; +} + +PurpleSmiley * +purple_smileys_find_by_shortcut(const char *shortcut) +{ + g_return_val_if_fail(shortcut != NULL, NULL); + + return g_hash_table_lookup(smiley_shortcut_index, shortcut); +} + +PurpleSmiley * +purple_smileys_find_by_checksum(const char *checksum) +{ + g_return_val_if_fail(checksum != NULL, NULL); + + return g_hash_table_lookup(smiley_checksum_index, checksum); +} + +const char * +purple_smileys_get_storing_dir(void) +{ + return smileys_dir; +} + +void +purple_smileys_init() +{ + smiley_shortcut_index = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL); + smiley_checksum_index = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL); + + smileys_dir = g_build_filename(purple_user_dir(), SMILEYS_DEFAULT_FOLDER, NULL); + + purple_smileys_load(); +} + +void +purple_smileys_uninit() +{ + if (save_timer != 0) { + purple_timeout_remove(save_timer); + save_timer = 0; + sync_smileys(); + } + + g_hash_table_destroy(smiley_shortcut_index); + g_hash_table_destroy(smiley_checksum_index); + g_free(smileys_dir); +} + diff -r 02ef6c5c6650 -r e0e57b83b359 libpurple/smiley.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/smiley.h Wed May 21 12:29:27 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 02ef6c5c6650 -r e0e57b83b359 libpurple/util.c --- a/libpurple/util.c Wed May 21 05:07:25 2008 +0000 +++ b/libpurple/util.c Wed May 21 12:29:27 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 02ef6c5c6650 -r e0e57b83b359 libpurple/util.h --- a/libpurple/util.h Wed May 21 05:07:25 2008 +0000 +++ b/libpurple/util.h Wed May 21 12:29:27 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 02ef6c5c6650 -r e0e57b83b359 pidgin/Makefile.am --- a/pidgin/Makefile.am Wed May 21 05:07:25 2008 +0000 +++ b/pidgin/Makefile.am Wed May 21 12:29:27 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 02ef6c5c6650 -r e0e57b83b359 pidgin/Makefile.mingw --- a/pidgin/Makefile.mingw Wed May 21 05:07:25 2008 +0000 +++ b/pidgin/Makefile.mingw Wed May 21 12:29:27 2008 +0000 @@ -84,6 +84,7 @@ gtkroomlist.c \ gtksavedstatuses.c \ gtkscrollbook.c \ + gtksmiley.c \ gtksound.c \ gtksourceiter.c \ gtksourceundomanager.c \ diff -r 02ef6c5c6650 -r e0e57b83b359 pidgin/gtkaccount.c --- a/pidgin/gtkaccount.c Wed May 21 05:07:25 2008 +0000 +++ b/pidgin/gtkaccount.c Wed May 21 12:29:27 2008 +0000 @@ -176,14 +176,7 @@ } if (dialog->icon_img != NULL) { - GdkPixbufLoader *loader = gdk_pixbuf_loader_new(); - gdk_pixbuf_loader_write(loader, purple_imgstore_get_data(dialog->icon_img), - purple_imgstore_get_size(dialog->icon_img), NULL); - gdk_pixbuf_loader_close(loader, NULL); - pixbuf = gdk_pixbuf_loader_get_pixbuf(loader); - if (pixbuf) - g_object_ref(pixbuf); - g_object_unref(loader); + pixbuf = pidgin_pixbuf_from_imgstore(dialog->icon_img); } if (pixbuf && dialog->prpl_info && @@ -255,6 +248,25 @@ } } +static gboolean +screenname_focus_cb(GtkWidget *widget, GdkEventFocus *event, AccountPrefsDialog *dialog) +{ + GHashTable *table; + const char *label; + + table = dialog->prpl_info->get_account_text_table(NULL); + label = g_hash_table_lookup(table, "login_label"); + + if(!strcmp(gtk_entry_get_text(GTK_ENTRY(widget)), label)) { + gtk_entry_set_text(GTK_ENTRY(widget), ""); + gtk_widget_modify_text(widget, GTK_STATE_NORMAL,NULL); + } + + g_hash_table_destroy(table); + + return FALSE; +} + static void screenname_changed_cb(GtkEntry *entry, AccountPrefsDialog *dialog) { @@ -270,6 +282,32 @@ } } +static gboolean +screenname_nofocus_cb(GtkWidget *widget, GdkEventFocus *event, AccountPrefsDialog *dialog) +{ + GdkColor color = {0, 34952, 35466, 34181}; + GHashTable *table; + const char *label; + + table = dialog->prpl_info->get_account_text_table(NULL); + label = g_hash_table_lookup(table, "login_label"); + + if (*gtk_entry_get_text(GTK_ENTRY(widget)) == '\0') { + /* We have to avoid hitting the screenname_changed_cb function + * because it enables buttons we don't want enabled yet ;) + */ + g_signal_handlers_block_by_func(widget, G_CALLBACK(screenname_changed_cb), dialog); + gtk_entry_set_text(GTK_ENTRY(widget), label); + /* Make sure we can hit it again */ + g_signal_handlers_unblock_by_func(widget, G_CALLBACK(screenname_changed_cb), dialog); + gtk_widget_modify_text(widget, GTK_STATE_NORMAL, &color); + } + + g_hash_table_destroy(table); + + return FALSE; +} + static void icon_filesel_choose_cb(const char *filename, gpointer data) { @@ -410,6 +448,25 @@ add_pref_box(dialog, vbox, _("_Username:"), dialog->screenname_entry); + if (dialog->account != NULL) + username = g_strdup(purple_account_get_username(dialog->account)); + + if (!username && PURPLE_PROTOCOL_PLUGIN_HAS_FUNC(dialog->prpl_info, get_account_text_table)) { + GdkColor color = {0, 34952, 35466, 34181}; + GHashTable *table; + const char *label; + table = dialog->prpl_info->get_account_text_table(NULL); + label = g_hash_table_lookup(table, "login_label"); + + gtk_entry_set_text(GTK_ENTRY(dialog->screenname_entry), label); + g_signal_connect(G_OBJECT(dialog->screenname_entry), "focus-in-event", + G_CALLBACK(screenname_focus_cb), dialog); + g_signal_connect(G_OBJECT(dialog->screenname_entry), "focus-out-event", + G_CALLBACK(screenname_nofocus_cb), dialog); + gtk_widget_modify_text(dialog->screenname_entry, GTK_STATE_NORMAL, &color); + g_hash_table_destroy(table); + } + g_signal_connect(G_OBJECT(dialog->screenname_entry), "changed", G_CALLBACK(screenname_changed_cb), dialog); @@ -419,9 +476,6 @@ else user_splits = dialog->prpl_info->user_splits; - if (dialog->account != NULL) - username = g_strdup(purple_account_get_username(dialog->account)); - if (dialog->user_split_entries != NULL) { g_list_free(dialog->user_split_entries); dialog->user_split_entries = NULL; @@ -1513,6 +1567,8 @@ /* Show the window. */ gtk_widget_show(win); + if (!account) + gtk_widget_grab_focus(dialog->protocol_menu); } /************************************************************************** @@ -1753,39 +1809,6 @@ return FALSE; } -static gboolean -configure_cb(GtkWidget *w, GdkEventConfigure *event, AccountsWindow *dialog) -{ - if (GTK_WIDGET_VISIBLE(w)) { - int old_width = purple_prefs_get_int(PIDGIN_PREFS_ROOT "/accounts/dialog/width"); - int col_width; - int difference; - - purple_prefs_set_int(PIDGIN_PREFS_ROOT "/accounts/dialog/width", event->width); - purple_prefs_set_int(PIDGIN_PREFS_ROOT "/accounts/dialog/height", event->height); - - col_width = gtk_tree_view_column_get_width(dialog->screenname_col); - - if (col_width == 0) - return FALSE; - - difference = (MAX(old_width, event->width) - - MIN(old_width, event->width)); - - if (difference == 0) - return FALSE; - - if (old_width < event->width) - gtk_tree_view_column_set_min_width(dialog->screenname_col, - col_width + difference); - else - gtk_tree_view_column_set_max_width(dialog->screenname_col, - col_width - difference); - } - - return FALSE; -} - static void add_account_cb(GtkWidget *w, AccountsWindow *dialog) { @@ -1908,14 +1931,14 @@ column = gtk_tree_view_column_new_with_attributes(_("Enabled"), renderer, "active", COLUMN_ENABLED, NULL); - gtk_tree_view_insert_column(GTK_TREE_VIEW(treeview), column, -1); - gtk_tree_view_column_set_resizable(column, TRUE); + gtk_tree_view_column_set_resizable(column, FALSE); + gtk_tree_view_append_column(GTK_TREE_VIEW(treeview), column); /* Screen Name column */ column = gtk_tree_view_column_new(); gtk_tree_view_column_set_title(column, _("Username")); - gtk_tree_view_insert_column(GTK_TREE_VIEW(treeview), column, -1); gtk_tree_view_column_set_resizable(column, TRUE); + gtk_tree_view_append_column(GTK_TREE_VIEW(treeview), column); /* Buddy Icon */ renderer = gtk_cell_renderer_pixbuf_new(); @@ -1934,8 +1957,8 @@ /* Protocol name */ column = gtk_tree_view_column_new(); gtk_tree_view_column_set_title(column, _("Protocol")); - gtk_tree_view_insert_column(GTK_TREE_VIEW(treeview), column, -1); - gtk_tree_view_column_set_resizable(column, TRUE); + gtk_tree_view_column_set_resizable(column, FALSE); + gtk_tree_view_append_column(GTK_TREE_VIEW(treeview), column); /* Icon */ renderer = gtk_cell_renderer_pixbuf_new(); @@ -1977,21 +2000,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, @@ -2196,6 +2212,7 @@ gtk_container_add(GTK_CONTAINER(sw), treeview); add_columns(treeview, dialog); + gtk_tree_view_columns_autosize(GTK_TREE_VIEW(treeview)); if (populate_accounts_list(dialog)) gtk_notebook_set_current_page(GTK_NOTEBOOK(accounts_window->notebook), 1); @@ -2265,8 +2282,6 @@ g_signal_connect(G_OBJECT(win), "delete_event", G_CALLBACK(accedit_win_destroy_cb), accounts_window); - g_signal_connect(G_OBJECT(win), "configure_event", - G_CALLBACK(configure_cb), accounts_window); /* Setup the vbox */ vbox = pidgin_dialog_get_vbox_with_properties(GTK_DIALOG(win), FALSE, PIDGIN_HIG_BORDER); diff -r 02ef6c5c6650 -r e0e57b83b359 pidgin/gtkblist.c --- a/pidgin/gtkblist.c Wed May 21 05:07:25 2008 +0000 +++ b/pidgin/gtkblist.c Wed May 21 12:29:27 2008 +0000 @@ -57,6 +57,7 @@ #include "gtkroomlist.h" #include "gtkstatusbox.h" #include "gtkscrollbook.h" +#include "gtksmiley.h" #include "gtkutils.h" #include "pidgin/minidialog.h" #include "pidgin/pidgintooltip.h" @@ -1528,6 +1529,44 @@ return FALSE; } +static void +set_node_custom_icon_cb(const gchar *filename, gpointer data) +{ + if (filename) { + PurpleBlistNode *node = (PurpleBlistNode*)data; + + purple_buddy_icons_node_set_custom_icon_from_file(node, + filename); + } +} + +static void +set_node_custom_icon(GtkWidget *w, PurpleBlistNode *node) +{ + /* This doesn't keep track of the returned dialog (so that successive + * calls could be made to re-display that dialog). Do we want that? */ + GtkWidget *win = pidgin_buddy_icon_chooser_new(NULL, set_node_custom_icon_cb, node); + gtk_widget_show_all(win); +} + +static void +remove_node_custom_icon(GtkWidget *w, PurpleBlistNode *node) +{ + purple_buddy_icons_node_set_custom_icon(node, NULL, 0); +} + +static void +add_buddy_icon_menu_items(GtkWidget *menu, PurpleBlistNode *node) +{ + pidgin_new_item_from_stock(menu, _("Set Custom Icon"), NULL, + G_CALLBACK(set_node_custom_icon), node, 0, + 0, NULL); + + pidgin_new_item_from_stock(menu, _("Remove Custom Icon"), NULL, + G_CALLBACK(remove_node_custom_icon), node, + 0, 0, NULL); +} + static GtkWidget * create_group_menu (PurpleBlistNode *node, PurpleGroup *g) { @@ -1551,12 +1590,13 @@ NULL, G_CALLBACK(gtk_blist_menu_showoffline_cb), node, 0, 0, NULL); } + add_buddy_icon_menu_items(menu, node); + pidgin_append_blist_node_extended_menu(menu, node); return menu; } - static GtkWidget * create_chat_menu(PurpleBlistNode *node, PurpleChat *c) { @@ -1589,6 +1629,8 @@ pidgin_new_item_from_stock(menu, _("_Remove"), GTK_STOCK_REMOVE, G_CALLBACK(pidgin_blist_remove_cb), node, 0, 0, NULL); + add_buddy_icon_menu_items(menu, node); + return menu; } @@ -1610,6 +1652,8 @@ pidgin_new_item_from_stock(menu, _("_Remove"), GTK_STOCK_REMOVE, G_CALLBACK(pidgin_blist_remove_cb), node, 0, 0, NULL); + add_buddy_icon_menu_items(menu, node); + pidgin_separator(menu); pidgin_new_item_from_stock(menu, _("_Collapse"), GTK_STOCK_ZOOM_OUT, @@ -2493,51 +2537,71 @@ static GdkPixbuf *pidgin_blist_get_buddy_icon(PurpleBlistNode *node, - gboolean scaled, gboolean greyed) -{ - GdkPixbuf *buf, *ret = NULL; + gboolean scaled, gboolean greyed) +{ + gsize len; GdkPixbufLoader *loader; - PurpleBuddyIcon *icon = NULL; - const guchar *data = NULL; - gsize len; PurpleBuddy *buddy = NULL; + PurpleGroup *group = NULL; + const guchar *data = NULL; + GdkPixbuf *buf, *ret = NULL; + PurpleBuddyIcon *icon = NULL; PurpleAccount *account = NULL; - PurplePluginProtocolInfo *prpl_info = NULL; + PurpleContact *contact = NULL; PurpleStoredImage *custom_img; - - if(PURPLE_BLIST_NODE_IS_CONTACT(node)) { + PurplePluginProtocolInfo *prpl_info = NULL; + gint orig_width, orig_height, scale_width, scale_height; + + if (PURPLE_BLIST_NODE_IS_CONTACT(node)) { buddy = purple_contact_get_priority_buddy((PurpleContact*)node); - } else if(PURPLE_BLIST_NODE_IS_BUDDY(node)) { + contact = (PurpleContact*)node; + } else if (PURPLE_BLIST_NODE_IS_BUDDY(node)) { buddy = (PurpleBuddy*)node; + contact = purple_buddy_get_contact(buddy); + } else if (PURPLE_BLIST_NODE_IS_GROUP(node)) { + group = (PurpleGroup*)node; + } else if (PURPLE_BLIST_NODE_IS_CHAT(node)) { + /* We don't need to do anything here. We just need to not fall + * into the else block and return. */ } else { return NULL; } - if(buddy == NULL) - return NULL; - - account = purple_buddy_get_account(buddy); - - if(account && account->gc) + if (buddy) { + account = purple_buddy_get_account(buddy); + } + + if(account && account->gc) { prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(account->gc->prpl); + } #if 0 if (!purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/blist/show_buddy_icons")) return NULL; #endif - custom_img = purple_buddy_icons_find_custom_icon(purple_buddy_get_contact(buddy)); - if (custom_img) - { + /* If we have a contact then this is either a contact or a buddy and + * we want to fetch the custom icon for the contact. If we don't have + * a contact then this is a group or some other type of node and we + * want to use that directly. */ + if (contact) { + custom_img = purple_buddy_icons_node_find_custom_icon((PurpleBlistNode*)contact); + } else { + custom_img = purple_buddy_icons_node_find_custom_icon(node); + } + + if (custom_img) { data = purple_imgstore_get_data(custom_img); len = purple_imgstore_get_size(custom_img); } if (data == NULL) { - /* Not sure I like this...*/ - if (!(icon = purple_buddy_icons_find(buddy->account, buddy->name))) - return NULL; - data = purple_buddy_icon_get_data(icon, &len); + if (buddy) { + /* Not sure I like this...*/ + if (!(icon = purple_buddy_icons_find(buddy->account, buddy->name))) + return NULL; + data = purple_buddy_icon_get_data(icon, &len); + } if(data == NULL) return NULL; @@ -2555,56 +2619,68 @@ g_object_ref(G_OBJECT(buf)); g_object_unref(G_OBJECT(loader)); - if (buf) { - int orig_width, orig_height; - int scale_width, scale_height; - - if (greyed) { + if (!buf) { + return NULL; + } + + if (greyed) { + gboolean offline = FALSE, idle = FALSE; + + if (buddy) { PurplePresence *presence = purple_buddy_get_presence(buddy); if (!PURPLE_BUDDY_IS_ONLINE(buddy)) - gdk_pixbuf_saturate_and_pixelate(buf, buf, 0.0, FALSE); + offline = TRUE; if (purple_presence_is_idle(presence)) - gdk_pixbuf_saturate_and_pixelate(buf, buf, 0.25, FALSE); + idle = TRUE; + } else if (group) { + if (purple_blist_get_group_online_count(group) == 0) + offline = TRUE; } - /* i'd use the pidgin_buddy_icon_get_scale_size() thing, - * but it won't tell me the original size, which I need for scaling - * purposes */ - scale_width = orig_width = gdk_pixbuf_get_width(buf); - scale_height = orig_height = gdk_pixbuf_get_height(buf); - - if (prpl_info && prpl_info->icon_spec.scale_rules & PURPLE_ICON_SCALE_DISPLAY) - purple_buddy_icon_get_scale_size(&prpl_info->icon_spec, &scale_width, &scale_height); - - if (scaled || scale_height > 200 || scale_width > 200) { - GdkPixbuf *tmpbuf; - float scale_size = scaled ? 32.0 : 200.0; - if(scale_height > scale_width) { - scale_width = scale_size * (double)scale_width / (double)scale_height; - scale_height = scale_size; - } else { - scale_height = scale_size * (double)scale_height / (double)scale_width; - scale_width = scale_size; - } - /* scale & round before making square, so rectangular (but non-square) - * images get rounded corners too */ - tmpbuf = gdk_pixbuf_new(GDK_COLORSPACE_RGB, TRUE, 8, scale_width, scale_height); - gdk_pixbuf_fill(tmpbuf, 0x00000000); - gdk_pixbuf_scale(buf, tmpbuf, 0, 0, scale_width, scale_height, 0, 0, (double)scale_width/(double)orig_width, (double)scale_height/(double)orig_height, GDK_INTERP_BILINEAR); - if (pidgin_gdk_pixbuf_is_opaque(tmpbuf)) - pidgin_gdk_pixbuf_make_round(tmpbuf); - ret = gdk_pixbuf_new(GDK_COLORSPACE_RGB, TRUE, 8, scale_size, scale_size); - gdk_pixbuf_fill(ret, 0x00000000); - gdk_pixbuf_copy_area(tmpbuf, 0, 0, scale_width, scale_height, ret, (scale_size-scale_width)/2, (scale_size-scale_height)/2); - g_object_unref(G_OBJECT(tmpbuf)); + if (offline) + gdk_pixbuf_saturate_and_pixelate(buf, buf, 0.0, FALSE); + + if (idle) + gdk_pixbuf_saturate_and_pixelate(buf, buf, 0.25, FALSE); + } + + /* I'd use the pidgin_buddy_icon_get_scale_size() thing, but it won't + * tell me the original size, which I need for scaling purposes. */ + scale_width = orig_width = gdk_pixbuf_get_width(buf); + scale_height = orig_height = gdk_pixbuf_get_height(buf); + + if (prpl_info && prpl_info->icon_spec.scale_rules & PURPLE_ICON_SCALE_DISPLAY) + purple_buddy_icon_get_scale_size(&prpl_info->icon_spec, &scale_width, &scale_height); + + if (scaled || scale_height > 200 || scale_width > 200) { + GdkPixbuf *tmpbuf; + float scale_size = scaled ? 32.0 : 200.0; + if(scale_height > scale_width) { + scale_width = scale_size * (double)scale_width / (double)scale_height; + scale_height = scale_size; } else { - ret = gdk_pixbuf_scale_simple(buf,scale_width,scale_height, GDK_INTERP_BILINEAR); + scale_height = scale_size * (double)scale_height / (double)scale_width; + scale_width = scale_size; } - g_object_unref(G_OBJECT(buf)); - } + /* Scale & round before making square, so rectangular (but + * non-square) images get rounded corners too. */ + tmpbuf = gdk_pixbuf_new(GDK_COLORSPACE_RGB, TRUE, 8, scale_width, scale_height); + gdk_pixbuf_fill(tmpbuf, 0x00000000); + gdk_pixbuf_scale(buf, tmpbuf, 0, 0, scale_width, scale_height, 0, 0, (double)scale_width/(double)orig_width, (double)scale_height/(double)orig_height, GDK_INTERP_BILINEAR); + if (pidgin_gdk_pixbuf_is_opaque(tmpbuf)) + pidgin_gdk_pixbuf_make_round(tmpbuf); + ret = gdk_pixbuf_new(GDK_COLORSPACE_RGB, TRUE, 8, scale_size, scale_size); + gdk_pixbuf_fill(ret, 0x00000000); + gdk_pixbuf_copy_area(tmpbuf, 0, 0, scale_width, scale_height, ret, (scale_size-scale_width)/2, (scale_size-scale_height)/2); + g_object_unref(G_OBJECT(tmpbuf)); + } else { + ret = gdk_pixbuf_scale_simple(buf,scale_width,scale_height, GDK_INTERP_BILINEAR); + } + g_object_unref(G_OBJECT(buf)); return ret; } + /* # - Status Icon * P - Protocol Icon * A - Buddy Icon @@ -3183,6 +3259,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 }, @@ -5822,14 +5899,16 @@ return FALSE; } -/*This version of pidgin_blist_update_group can take the original buddy -or a group, but has much better algorithmic performance with a pre-known buddy*/ -static void pidgin_blist_update_group(PurpleBuddyList *list, PurpleBlistNode *node) -{ +/* This version of pidgin_blist_update_group can take the original buddy or a + * group, but has much better algorithmic performance with a pre-known buddy. + */ +static void pidgin_blist_update_group(PurpleBuddyList *list, + PurpleBlistNode *node) +{ + gint count; PurpleGroup *group; - int count; + PurpleBlistNode* gnode; gboolean show = FALSE, show_offline = FALSE; - PurpleBlistNode* gnode; g_return_if_fail(node != NULL); @@ -5864,12 +5943,13 @@ } if (show) { + gchar *title; + gboolean biglist; GtkTreeIter iter; GtkTreePath *path; gboolean expanded; GdkColor bgcolor; - char *title; - + GdkPixbuf *avatar = NULL; if(!insert_node(list, gnode, &iter)) return; @@ -5881,17 +5961,23 @@ gtk_tree_path_free(path); title = pidgin_get_group_title(gnode, expanded); + biglist = purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/blist/show_buddy_icons"); + + if (biglist) { + avatar = pidgin_blist_get_buddy_icon(gnode, TRUE, TRUE); + } gtk_tree_store_set(gtkblist->treemodel, &iter, STATUS_ICON_VISIBLE_COLUMN, FALSE, STATUS_ICON_COLUMN, NULL, NAME_COLUMN, title, NODE_COLUMN, gnode, - /* BGCOLOR_COLUMN, &bgcolor, */ + /* BGCOLOR_COLUMN, &bgcolor, */ GROUP_EXPANDER_COLUMN, TRUE, GROUP_EXPANDER_VISIBLE_COLUMN, TRUE, CONTACT_EXPANDER_VISIBLE_COLUMN, FALSE, - BUDDY_ICON_VISIBLE_COLUMN, FALSE, + BUDDY_ICON_COLUMN, avatar, + BUDDY_ICON_VISIBLE_COLUMN, biglist, IDLE_VISIBLE_COLUMN, FALSE, EMBLEM_VISIBLE_COLUMN, FALSE, -1); @@ -6181,7 +6267,7 @@ STATUS_ICON_COLUMN, status, STATUS_ICON_VISIBLE_COLUMN, TRUE, BUDDY_ICON_COLUMN, avatar ? avatar : gtkblist->empty_avatar, - BUDDY_ICON_VISIBLE_COLUMN, purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/blist/show_buddy_icons"), + BUDDY_ICON_VISIBLE_COLUMN, showicons, EMBLEM_COLUMN, emblem, EMBLEM_VISIBLE_COLUMN, emblem != NULL, PROTOCOL_ICON_COLUMN, prpl_icon, diff -r 02ef6c5c6650 -r e0e57b83b359 pidgin/gtkconv.c --- a/pidgin/gtkconv.c Wed May 21 05:07:25 2008 +0000 +++ b/pidgin/gtkconv.c Wed May 21 12:29:27 2008 +0000 @@ -159,8 +159,6 @@ static void update_typing_message(PidginConversation *gtkconv, const char *message); static const char *item_factory_translate_func (const char *path, gpointer func_data); gboolean pidgin_conv_has_focus(PurpleConversation *conv); -static void pidgin_conv_custom_smiley_allocated(GdkPixbufLoader *loader, gpointer user_data); -static void pidgin_conv_custom_smiley_closed(GdkPixbufLoader *loader, gpointer user_data); static GdkColor* generate_nick_colors(guint *numcolors, GdkColor background); static gboolean color_is_visible(GdkColor foreground, GdkColor background, int color_contrast, int brightness_contrast); static GtkTextTag *get_buddy_tag(PurpleConversation *conv, const char *who, gboolean create); @@ -2514,10 +2512,6 @@ PurpleBuddy *b = purple_find_buddy(conv->account, conv->name); if (b) emblem = pidgin_blist_get_emblem((PurpleBlistNode*)b); - } else { - PurpleChat *c = purple_blist_find_chat(conv->account, conv->name); - if (c) - emblem = pidgin_blist_get_emblem((PurpleBlistNode*)c); } g_return_if_fail(status != NULL); @@ -2725,10 +2719,22 @@ custom_icon_sel_cb(const char *filename, gpointer data) { if (filename) { + const gchar *name; + PurpleBuddy *buddy; + PurpleContact *contact; PidginConversation *gtkconv = data; PurpleConversation *conv = gtkconv->active_conv; PurpleAccount *account = purple_conversation_get_account(conv); - pidgin_set_custom_buddy_icon(account, purple_conversation_get_name(conv), filename); + + name = purple_conversation_get_name(conv); + buddy = purple_find_buddy(account, name); + if (!buddy) { + purple_debug_info("custom-icon", "You can only set custom icons for people on your buddylist.\n"); + return; + } + contact = purple_buddy_get_contact(buddy); + + purple_buddy_icons_node_set_custom_icon_from_file((PurpleBlistNode*)contact, filename); } } @@ -2770,12 +2776,21 @@ static void remove_custom_icon_cb(GtkWidget *widget, PidginConversation *gtkconv) { - PurpleConversation *conv; + const gchar *name; + PurpleBuddy *buddy; PurpleAccount *account; - - conv = gtkconv->active_conv; + PurpleContact *contact; + PurpleConversation *conv = gtkconv->active_conv; + account = purple_conversation_get_account(conv); - pidgin_set_custom_buddy_icon(account, purple_conversation_get_name(conv), NULL); + name = purple_conversation_get_name(conv); + buddy = purple_find_buddy(account, name); + if (!buddy) { + return; + } + contact = purple_buddy_get_contact(buddy); + + purple_buddy_icons_node_set_custom_icon_from_file((PurpleBlistNode*)contact, NULL); } static void @@ -2876,7 +2891,7 @@ if (buddy) { PurpleContact *contact = purple_buddy_get_contact(buddy); - if (contact && purple_buddy_icons_has_custom_icon(contact)) + if (contact && purple_buddy_icons_node_has_custom_icon((PurpleBlistNode*)contact)) { pidgin_new_item_from_stock(menu, _("Remove Custom Icon"), NULL, G_CALLBACK(remove_custom_icon_cb), gtkconv, @@ -5167,6 +5182,12 @@ nbr_nick_colors = NUM_NICK_COLORS; nick_colors = generate_nick_colors(&nbr_nick_colors, gtk_widget_get_style(gtkconv->imhtml)->base[GTK_STATE_NORMAL]); } + + /* We don't want to see the custom smileys if our buddy send us the + * defined shortcut. */ + pidgin_themes_smiley_themeize(gtkconv->imhtml); + /* We want to see our smileys in the entry */ + pidgin_themes_smiley_themeize_custom(gtkconv->entry); } static void @@ -5476,8 +5497,6 @@ char *bracket; int tag_count = 0; gboolean is_rtl_message = FALSE; - GtkSmileyTree *tree = NULL; - GHashTable *smiley_data = NULL; g_return_if_fail(conv != NULL); gtkconv = PIDGIN_CONVERSATION(conv); @@ -5636,14 +5655,8 @@ if (!(flags & PURPLE_MESSAGE_RECV)) { - /* Temporarily revert to the original smiley-data to avoid showing up - * custom smileys of the buddy when sending message - */ - tree = GTK_IMHTML(gtkconv->imhtml)->default_smilies; - GTK_IMHTML(gtkconv->imhtml)->default_smilies = - GTK_IMHTML(gtkconv->entry)->default_smilies; - smiley_data = GTK_IMHTML(gtkconv->imhtml)->smiley_data; - GTK_IMHTML(gtkconv->imhtml)->smiley_data = GTK_IMHTML(gtkconv->entry)->smiley_data; + /* We want to see our own smileys. Need to revert it after send*/ + pidgin_themes_smiley_themeize_custom(gtkconv->imhtml); } /* TODO: These colors should not be hardcoded so log.c can use them */ @@ -5814,8 +5827,7 @@ if (!(flags & PURPLE_MESSAGE_RECV)) { /* Restore the smiley-data */ - GTK_IMHTML(gtkconv->imhtml)->default_smilies = tree; - GTK_IMHTML(gtkconv->imhtml)->smiley_data = smiley_data; + pidgin_themes_smiley_themeize(gtkconv->imhtml); } purple_signal_emit(pidgin_conversations_get_handle(), @@ -6043,119 +6055,24 @@ return FALSE; } -static void pidgin_conv_custom_smiley_allocated(GdkPixbufLoader *loader, gpointer user_data) -{ - GtkIMHtmlSmiley *smiley; - - smiley = (GtkIMHtmlSmiley *)user_data; - smiley->icon = gdk_pixbuf_loader_get_animation(loader); - - if (smiley->icon) - g_object_ref(G_OBJECT(smiley->icon)); -#ifdef DEBUG_CUSTOM_SMILEY - purple_debug_info("custom-smiley", "pidgin_conv_custom_smiley_allocated(): got GdkPixbufAnimation %p for smiley '%s'\n", smiley->icon, smiley->smile); -#endif -} - -static void pidgin_conv_custom_smiley_closed(GdkPixbufLoader *loader, gpointer user_data) -{ - GtkIMHtmlSmiley *smiley; - GtkWidget *icon = NULL; - GtkTextChildAnchor *anchor = NULL; - GSList *current = NULL; - - smiley = (GtkIMHtmlSmiley *)user_data; - if (!smiley->imhtml) { -#ifdef DEBUG_CUSTOM_SMILEY - purple_debug_error("custom-smiley", "pidgin_conv_custom_smiley_closed(): orphan smiley found: %p\n", smiley); -#endif - g_object_unref(G_OBJECT(loader)); - smiley->loader = NULL; - return; - } - - for (current = smiley->anchors; current; current = g_slist_next(current)) { - - icon = gtk_image_new_from_animation(smiley->icon); - -#ifdef DEBUG_CUSTOM_SMILEY - purple_debug_info("custom-smiley", "pidgin_conv_custom_smiley_closed(): got GtkImage %p from GtkPixbufAnimation %p for smiley '%s'\n", - icon, smiley->icon, smiley->smile); -#endif - if (icon) { - GList *wids; - gtk_widget_show(icon); - - anchor = GTK_TEXT_CHILD_ANCHOR(current->data); - wids = gtk_text_child_anchor_get_widgets(anchor); - - g_object_set_data_full(G_OBJECT(anchor), "gtkimhtml_plaintext", purple_unescape_html(smiley->smile), g_free); - g_object_set_data_full(G_OBJECT(anchor), "gtkimhtml_htmltext", g_strdup(smiley->smile), g_free); - - if (smiley->imhtml) { - if (wids) { - GList *children = gtk_container_get_children(GTK_CONTAINER(wids->data)); - g_list_foreach(children, (GFunc)gtk_widget_destroy, NULL); - g_list_free(children); - gtk_container_add(GTK_CONTAINER(wids->data), icon); - } else - gtk_text_view_add_child_at_anchor(GTK_TEXT_VIEW(smiley->imhtml), icon, anchor); - } - g_list_free(wids); - } - - } - - g_slist_free(smiley->anchors); - smiley->anchors = NULL; - - g_object_unref(G_OBJECT(loader)); - smiley->loader = NULL; -} - static gboolean add_custom_smiley_for_imhtml(GtkIMHtml *imhtml, const char *sml, const char *smile) { GtkIMHtmlSmiley *smiley; - GdkPixbufLoader *loader; smiley = gtk_imhtml_smiley_get(imhtml, sml, smile); if (smiley) { - if (!(smiley->flags & GTK_IMHTML_SMILEY_CUSTOM)) { return FALSE; } - - /* Close the old GdkPixbufAnimation, then create a new one for - * the smiley we are about to receive */ - g_object_unref(G_OBJECT(smiley->icon)); - - /* XXX: Is it necessary to _unref the loader first? */ - smiley->loader = gdk_pixbuf_loader_new(); - smiley->icon = NULL; - - g_signal_connect(smiley->loader, "area_prepared", G_CALLBACK(pidgin_conv_custom_smiley_allocated), smiley); - g_signal_connect(smiley->loader, "closed", G_CALLBACK(pidgin_conv_custom_smiley_closed), smiley); - + gtk_imhtml_smiley_reload(smiley); return TRUE; } - loader = gdk_pixbuf_loader_new(); - - /* this is wrong, this file ought not call g_new on GtkIMHtmlSmiley */ - /* Let gtk_imhtml have a gtk_imhtml_smiley_new function, and let - GtkIMHtmlSmiley by opaque */ - smiley = g_new0(GtkIMHtmlSmiley, 1); - smiley->file = NULL; - smiley->smile = g_strdup(smile); - smiley->loader = loader; - smiley->flags = smiley->flags | GTK_IMHTML_SMILEY_CUSTOM; - - g_signal_connect(smiley->loader, "area_prepared", G_CALLBACK(pidgin_conv_custom_smiley_allocated), smiley); - g_signal_connect(smiley->loader, "closed", G_CALLBACK(pidgin_conv_custom_smiley_closed), smiley); - + smiley = gtk_imhtml_smiley_create(NULL, smile, FALSE, GTK_IMHTML_SMILEY_CUSTOM); gtk_imhtml_associate_smiley(imhtml, sml, smiley); + g_signal_connect_swapped(imhtml, "destroy", G_CALLBACK(gtk_imhtml_smiley_destroy), smiley); return TRUE; } @@ -6380,6 +6297,11 @@ if(conv->features & PURPLE_CONNECTION_NO_IMAGES) buttons &= ~GTK_IMHTML_IMAGE; + if (conv->features & PURPLE_CONNECTION_ALLOW_CUSTOM_SMILEY) + buttons |= GTK_IMHTML_CUSTOM_SMILEY; + else + buttons &= ~GTK_IMHTML_CUSTOM_SMILEY; + gtk_imhtml_set_format_functions(GTK_IMHTML(gtkconv->entry), buttons); if (account != NULL) gtk_imhtmltoolbar_associate_smileys(GTK_IMHTMLTOOLBAR(gtkconv->toolbar), purple_account_get_protocol_id(account)); @@ -6827,7 +6749,7 @@ { PurpleContact *contact = purple_buddy_get_contact(buddy); if (contact) { - custom_img = purple_buddy_icons_find_custom_icon(contact); + custom_img = purple_buddy_icons_node_find_custom_icon((PurpleBlistNode*)contact); if (custom_img) { /* There is a custom icon for this user */ data = purple_imgstore_get_data(custom_img); diff -r 02ef6c5c6650 -r e0e57b83b359 pidgin/gtkimhtml.c --- a/pidgin/gtkimhtml.c Wed May 21 05:07:25 2008 +0000 +++ b/pidgin/gtkimhtml.c Wed May 21 12:29:27 2008 +0000 @@ -5329,7 +5329,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 02ef6c5c6650 -r e0e57b83b359 pidgin/gtkimhtml.h --- a/pidgin/gtkimhtml.h Wed May 21 05:07:25 2008 +0000 +++ b/pidgin/gtkimhtml.h Wed May 21 12:29:27 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 02ef6c5c6650 -r e0e57b83b359 pidgin/gtkimhtmltoolbar.c --- a/pidgin/gtkimhtmltoolbar.c Wed May 21 05:07:25 2008 +0000 +++ b/pidgin/gtkimhtmltoolbar.c Wed May 21 12:29:27 2008 +0000 @@ -36,6 +36,7 @@ #include "gtkdialogs.h" #include "gtkimhtmltoolbar.h" +#include "gtksmiley.h" #include "gtkthemes.h" #include "gtkutils.h" @@ -579,8 +580,7 @@ } static gboolean -close_smiley_dialog(GtkWidget *widget, GdkEvent *event, - GtkIMHtmlToolbar *toolbar) +close_smiley_dialog(GtkIMHtmlToolbar *toolbar) { gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(toolbar->smiley), FALSE); return FALSE; @@ -601,24 +601,30 @@ g_free(escaped_smiley); - close_smiley_dialog(NULL, NULL, toolbar); + close_smiley_dialog(toolbar); } /* smiley buttons list */ struct smiley_button_list { int width, height; GtkWidget *button; + const GtkIMHtmlSmiley *smiley; struct smiley_button_list *next; }; static struct smiley_button_list * -sort_smileys(struct smiley_button_list *ls, GtkIMHtmlToolbar *toolbar, int *width, char *filename, char *face) +sort_smileys(struct smiley_button_list *ls, GtkIMHtmlToolbar *toolbar, + int *width, const GtkIMHtmlSmiley *smiley, + const GSList *custom_smileys) { GtkWidget *image; GtkWidget *button; GtkRequisition size; struct smiley_button_list *cur; struct smiley_button_list *it, *it_last; + const gchar *filename = smiley->file; + gchar *face = smiley->smile; + PurpleSmiley *psmiley = NULL; cur = g_new0(struct smiley_button_list, 1); it = ls; @@ -626,6 +632,35 @@ image = gtk_image_new_from_file(filename); gtk_widget_size_request(image, &size); + + if (size.width > 24 && + smiley->flags & GTK_IMHTML_SMILEY_CUSTOM) { /* This is a custom smiley, let's scale it */ + GdkPixbuf *pixbuf = NULL; + GtkImageType type; + + type = gtk_image_get_storage_type(GTK_IMAGE(image)); + + if (type == GTK_IMAGE_PIXBUF) { + pixbuf = gtk_image_get_pixbuf(GTK_IMAGE(image)); + } else if (type == GTK_IMAGE_ANIMATION) { + GdkPixbufAnimation *animation; + + animation = gtk_image_get_animation(GTK_IMAGE(image)); + + pixbuf = gdk_pixbuf_animation_get_static_image(animation); + } + + if (pixbuf != NULL) { + GdkPixbuf *resized; + resized = gdk_pixbuf_scale_simple(pixbuf, 24, 24, + GDK_INTERP_HYPER); + + gtk_image_set_from_pixbuf(GTK_IMAGE(image), resized); /* This unrefs pixbuf */ + gtk_widget_size_request(image, &size); + g_object_unref(G_OBJECT(resized)); + } + } + (*width) += size.width; button = gtk_button_new(); @@ -639,10 +674,26 @@ /* these look really weird with borders */ gtk_button_set_relief(GTK_BUTTON(button), GTK_RELIEF_NONE); + psmiley = purple_smileys_find_by_shortcut(smiley->smile); + /* If this is a "non-custom" smiley, check to see if its shortcut is + "shadowed" by any custom smiley. This can only happen if the connection + is custom smiley-enabled */ + if (psmiley && !(smiley->flags & GTK_IMHTML_SMILEY_CUSTOM)) { + gtk_tooltips_set_tip(toolbar->tooltips, button, + _("This smiley is disabled because a custom smiley exists for this shortcut."), + NULL); + gtk_widget_set_sensitive(button, FALSE); + } else if (psmiley) { + /* Remove the button if the smiley is destroyed */ + g_signal_connect_object(G_OBJECT(psmiley), "destroy", G_CALLBACK(gtk_widget_destroy), + button, G_CONNECT_SWAPPED); + } + /* set current element to add */ cur->height = size.height; cur->width = size.width; cur->button = button; + cur->smiley = smiley; cur->next = ls; /* check where to insert by height */ @@ -661,7 +712,7 @@ smiley_is_unique(GSList *list, GtkIMHtmlSmiley *smiley) { while (list) { - GtkIMHtmlSmiley *cur = list->data; + GtkIMHtmlSmiley *cur = (GtkIMHtmlSmiley *) list->data; if (!strcmp(cur->file, smiley->file)) return FALSE; list = list->next; @@ -675,7 +726,7 @@ if ((event->type == GDK_KEY_PRESS && event->key.keyval == GDK_Escape) || (event->type == GDK_BUTTON_PRESS && event->button.button == 1)) { - close_smiley_dialog(NULL, NULL, toolbar); + close_smiley_dialog(toolbar); return TRUE; } @@ -683,11 +734,43 @@ } static void +add_smiley_list(GtkWidget *container, struct smiley_button_list *list, + int max_width, gboolean custom) +{ + GtkWidget *line; + int line_width = 0; + + if (!list) + return; + + line = gtk_hbox_new(FALSE, 0); + gtk_box_pack_start(GTK_BOX(container), line, FALSE, FALSE, 0); + for (; list; list = list->next) { + if (custom != !!(list->smiley->flags & GTK_IMHTML_SMILEY_CUSTOM)) + continue; + gtk_box_pack_start(GTK_BOX(line), list->button, FALSE, FALSE, 0); + gtk_widget_show(list->button); + line_width += list->width; + if (line_width >= max_width) { + if (list->next) { + line = gtk_hbox_new(FALSE, 0); + gtk_box_pack_start(GTK_BOX(container), line, FALSE, FALSE, 0); + } + line_width = 0; + } + } +} + +static void insert_smiley_cb(GtkWidget *smiley, GtkIMHtmlToolbar *toolbar) { - GtkWidget *dialog; + GtkWidget *dialog, *vbox; GtkWidget *smiley_table = NULL; GSList *smileys, *unique_smileys = NULL; + const GSList *custom_smileys = NULL; + gboolean supports_custom = FALSE; + GtkRequisition req; + GtkWidget *scrolled, *viewport; if (!gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(smiley))) { destroy_smiley_dialog(toolbar); @@ -701,61 +784,74 @@ smileys = pidgin_themes_get_proto_smileys(NULL); while(smileys) { - GtkIMHtmlSmiley *smiley = smileys->data; + GtkIMHtmlSmiley *smiley = (GtkIMHtmlSmiley *) smileys->data; if(!smiley->hidden) { - if(smiley_is_unique(unique_smileys, smiley)) + if(smiley_is_unique(unique_smileys, smiley)) { unique_smileys = g_slist_append(unique_smileys, smiley); + } } smileys = smileys->next; } + supports_custom = (gtk_imhtml_get_format_functions(GTK_IMHTML(toolbar->imhtml)) & GTK_IMHTML_CUSTOM_SMILEY); + if (toolbar->imhtml && supports_custom) { + const GSList *iterator = NULL; + custom_smileys = pidgin_smileys_get_all(); + + for (iterator = custom_smileys ; iterator ; + iterator = g_slist_next(iterator)) { + GtkIMHtmlSmiley *smiley = (GtkIMHtmlSmiley *) iterator->data; + unique_smileys = g_slist_append(unique_smileys, smiley); + } + } dialog = pidgin_create_dialog(_("Smile!"), 0, "smiley_dialog", FALSE); - gtk_window_set_position(GTK_WINDOW(dialog), GTK_WIN_POS_MOUSE); + vbox = pidgin_dialog_get_vbox_with_properties(GTK_DIALOG(dialog), FALSE, 0); if (unique_smileys != NULL) { - struct smiley_button_list *ls, *it, *it_tmp; - GtkWidget *line; - int line_width = 0; - int max_line_width, num_lines; - int col=0; + struct smiley_button_list *ls; + int max_line_width, num_lines, button_width = 0; /* We use hboxes packed in a vbox */ ls = NULL; - line = gtk_hbox_new(FALSE, 0); - line_width = 0; max_line_width = 0; num_lines = floor(sqrt(g_slist_length(unique_smileys))); smiley_table = gtk_vbox_new(FALSE, 0); + if (supports_custom) { + GtkWidget *manage = gtk_button_new_with_mnemonic(_("_Manage custom smileys")); + GtkRequisition req; + g_signal_connect(G_OBJECT(manage), "clicked", + G_CALLBACK(pidgin_smiley_manager_show), NULL); + g_signal_connect_swapped(G_OBJECT(manage), "clicked", + G_CALLBACK(gtk_widget_destroy), dialog); + gtk_box_pack_end(GTK_BOX(vbox), manage, FALSE, TRUE, 0); + gtk_widget_size_request(manage, &req); + button_width = req.width; + } + /* create list of smileys sorted by height */ while (unique_smileys) { - GtkIMHtmlSmiley *smiley = unique_smileys->data; + GtkIMHtmlSmiley *smiley = (GtkIMHtmlSmiley *) unique_smileys->data; if (!smiley->hidden) { - ls = sort_smileys(ls, toolbar, &max_line_width, smiley->file, smiley->smile); + ls = sort_smileys(ls, toolbar, &max_line_width, smiley, custom_smileys); } unique_smileys = g_slist_delete_link(unique_smileys, unique_smileys); } + /* The window will be at least as wide as the 'Manage ..' button */ + max_line_width = MAX(button_width, max_line_width / num_lines); + /* pack buttons of the list */ - max_line_width = max_line_width / num_lines; - it = ls; - while (it != NULL) - { - it_tmp = it; - gtk_box_pack_start(GTK_BOX(line), it->button, FALSE, FALSE, 0); - gtk_widget_show(it->button); - line_width += it->width; - if (line_width >= max_line_width) { - gtk_box_pack_start(GTK_BOX(smiley_table), line, FALSE, FALSE, 0); - line = gtk_hbox_new(FALSE, 0); - line_width = 0; - col = 0; - } - col++; - it = it->next; - g_free(it_tmp); + add_smiley_list(smiley_table, ls, max_line_width, FALSE); + if (supports_custom) { + gtk_box_pack_start(GTK_BOX(smiley_table), gtk_hseparator_new(), TRUE, FALSE, 0); + add_smiley_list(smiley_table, ls, max_line_width, TRUE); } - gtk_box_pack_start(GTK_BOX(smiley_table), line, FALSE, TRUE, 0); + while (ls) { + struct smiley_button_list *tmp = ls->next; + g_free(ls); + ls = tmp; + } gtk_widget_add_events(dialog, GDK_KEY_PRESS_MASK); } @@ -765,19 +861,41 @@ g_signal_connect(G_OBJECT(dialog), "button-press-event", (GCallback)smiley_dialog_input_cb, toolbar); } - g_signal_connect(G_OBJECT(dialog), "key-press-event", (GCallback)smiley_dialog_input_cb, toolbar); - gtk_container_add(GTK_CONTAINER(pidgin_dialog_get_vbox(GTK_DIALOG(dialog))), smiley_table); + scrolled = gtk_scrolled_window_new (NULL, NULL); + gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW (scrolled), GTK_SHADOW_NONE); + gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW (scrolled), + GTK_POLICY_NEVER, GTK_POLICY_NEVER); + gtk_box_pack_start(GTK_BOX(vbox), scrolled, TRUE, TRUE, 0); + gtk_widget_show(scrolled); + gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(scrolled), smiley_table); gtk_widget_show(smiley_table); + viewport = gtk_widget_get_parent(smiley_table); + gtk_viewport_set_shadow_type(GTK_VIEWPORT(viewport), GTK_SHADOW_NONE); + /* connect signals */ - g_signal_connect(G_OBJECT(dialog), "delete_event", - G_CALLBACK(close_smiley_dialog), toolbar); + g_signal_connect_swapped(G_OBJECT(dialog), "destroy", G_CALLBACK(close_smiley_dialog), toolbar); + g_signal_connect(G_OBJECT(dialog), "key-press-event", G_CALLBACK(smiley_dialog_input_cb), toolbar); + + gtk_window_set_transient_for(GTK_WINDOW(dialog), + GTK_WINDOW(gtk_widget_get_toplevel(GTK_WIDGET(toolbar)))); /* show everything */ gtk_widget_show_all(dialog); - gtk_window_set_transient_for(GTK_WINDOW(dialog), - GTK_WINDOW(gtk_widget_get_toplevel(GTK_WIDGET(toolbar)))); + + gtk_widget_size_request(viewport, &req); + gtk_widget_set_size_request(scrolled, MIN(300, req.width), MIN(290, req.height)); + + /* The window has to be made resizable, and the scrollbars in the scrolled window + * enabled only after setting the desired size of the window. If we do either of + * these tasks before now, GTK+ miscalculates the required size, and erronously + * makes one or both scrollbars visible (sometimes). + * I too think this hack is gross. But I couldn't find a better way -- sadrul */ + gtk_window_set_resizable(GTK_WINDOW(dialog), TRUE); + gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW (scrolled), + GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC); + #ifdef _WIN32 winpidgin_ensure_onscreen(dialog); #endif diff -r 02ef6c5c6650 -r e0e57b83b359 pidgin/gtkmain.c --- a/pidgin/gtkmain.c Wed May 21 05:07:25 2008 +0000 +++ b/pidgin/gtkmain.c Wed May 21 12:29:27 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 02ef6c5c6650 -r e0e57b83b359 pidgin/gtksmiley.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/pidgin/gtksmiley.c Wed May 21 12:29:27 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 02ef6c5c6650 -r e0e57b83b359 pidgin/gtksmiley.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/pidgin/gtksmiley.h Wed May 21 12:29:27 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 02ef6c5c6650 -r e0e57b83b359 pidgin/gtkthemes.c --- a/pidgin/gtkthemes.c Wed May 21 05:07:25 2008 +0000 +++ b/pidgin/gtkthemes.c Wed May 21 12:29:27 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 02ef6c5c6650 -r e0e57b83b359 pidgin/gtkthemes.h --- a/pidgin/gtkthemes.h Wed May 21 05:07:25 2008 +0000 +++ b/pidgin/gtkthemes.h Wed May 21 12:29:27 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 02ef6c5c6650 -r e0e57b83b359 pidgin/gtkutils.c --- a/pidgin/gtkutils.c Wed May 21 05:07:25 2008 +0000 +++ b/pidgin/gtkutils.c Wed May 21 12:29:27 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); @@ -1436,7 +1436,7 @@ static void dnd_image_ok_callback(_DndData *data, int choice) { - char *filedata; + gchar *filedata; size_t size; struct stat st; GError *err = NULL; @@ -1444,6 +1444,8 @@ PidginConversation *gtkconv; GtkTextIter iter; int id; + PurpleBuddy *buddy; + PurpleContact *contact; switch (choice) { case DND_BUDDY_ICON: if (g_stat(data->filename, &st)) { @@ -1459,7 +1461,13 @@ return; } - pidgin_set_custom_buddy_icon(data->account, data->who, data->filename); + buddy = purple_find_buddy(data->account, data->who); + if (!buddy) { + purple_debug_info("custom-icon", "You can only set custom icons for people on your buddylist.\n"); + break; + } + contact = purple_buddy_get_contact(buddy); + purple_buddy_icons_node_set_custom_icon_from_file((PurpleBlistNode*)contact, data->filename); break; case DND_FILE_TRANSFER: serv_send_file(purple_account_get_connection(data->account), data->who, data->filename); @@ -2869,12 +2877,11 @@ } #endif /* ! Gtk 2.6.0 */ +#ifndef PURPLE_DISABLE_DEPRECATED void pidgin_set_custom_buddy_icon(PurpleAccount *account, const char *who, const char *filename) { PurpleBuddy *buddy; PurpleContact *contact; - gpointer data = NULL; - size_t len = 0; buddy = purple_find_buddy(account, who); if (!buddy) { @@ -2883,20 +2890,9 @@ } contact = purple_buddy_get_contact(buddy); - - if (filename) { - const char *prpl_id = purple_account_get_protocol_id(account); - PurplePlugin *prpl = purple_find_prpl(prpl_id); - - data = pidgin_convert_buddy_icon(prpl, filename, &len); - - /* We don't want to delete the old icon if the new one didn't load. */ - if (data == NULL) - return; - } - - purple_buddy_icons_set_custom_icon(contact, data, len); + purple_buddy_icons_node_set_custom_icon_from_file((PurpleBlistNode*)contact, filename); } +#endif char *pidgin_make_pretty_arrows(const char *str) { @@ -3469,3 +3465,17 @@ #endif } +GdkPixbuf * pidgin_pixbuf_from_imgstore(PurpleStoredImage *image) +{ + GdkPixbuf *pixbuf; + GdkPixbufLoader *loader = gdk_pixbuf_loader_new(); + gdk_pixbuf_loader_write(loader, purple_imgstore_get_data(image), + purple_imgstore_get_size(image), NULL); + gdk_pixbuf_loader_close(loader, NULL); + pixbuf = gdk_pixbuf_loader_get_pixbuf(loader); + if (pixbuf) + g_object_ref(pixbuf); + g_object_unref(loader); + return pixbuf; +} + diff -r 02ef6c5c6650 -r e0e57b83b359 pidgin/gtkutils.h --- a/pidgin/gtkutils.h Wed May 21 05:07:25 2008 +0000 +++ b/pidgin/gtkutils.h Wed May 21 12:29:27 2008 +0000 @@ -638,6 +638,7 @@ GError **error); #endif +#ifndef PURPLE_DISABLE_DEPRECATED /** * Set or unset a custom buddyicon for a user. * @@ -645,8 +646,10 @@ * @param who The name of the user. * @param filename The path of the custom icon. If this is @c NULL, then any * previously set custom buddy icon for the user is removed. + * @deprecated See purple_buddy_icons_node_set_custom_icon_from_file() */ void pidgin_set_custom_buddy_icon(PurpleAccount *account, const char *who, const char *filename); +#endif /** * Converts "->" and "<-" in strings to Unicode arrow characters, for use in referencing @@ -689,7 +692,7 @@ */ GtkWidget *pidgin_make_mini_dialog(PurpleConnection *handle, const char* stock_id, const char *primary, const char *secondary, - void *user_data, ...); + void *user_data, ...) G_GNUC_NULL_TERMINATED; /** * This is a callback function to be used for Ctrl+F searching in treeviews. @@ -809,5 +812,15 @@ */ GtkWidget *pidgin_add_widget_to_vbox(GtkBox *vbox, const char *widget_label, GtkSizeGroup *sg, GtkWidget *widget, gboolean expand, GtkWidget **p_label); +/** + * Create a GdkPixbuf from a PurpleStoredImage. + * + * @param image A PurpleStoredImage. + * + * @return A GdkPixbuf created from the stored image. + * @since 2.5.0 + */ +GdkPixbuf * pidgin_pixbuf_from_imgstore(PurpleStoredImage *image); + #endif /* _PIDGINUTILS_H_ */ diff -r 02ef6c5c6650 -r e0e57b83b359 po/POTFILES.in --- a/po/POTFILES.in Wed May 21 05:07:25 2008 +0000 +++ b/po/POTFILES.in Wed May 21 12:29:27 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