Mercurial > pidgin
changeset 32150:3d5a0e9654ed
merge of '0acd4d77f5bd8bb02a3a73b6800be683bd7068f2'
and '6bee9aeec299196df3421d8fa5d0c663ae9f9e88'
author | Elliott Sales de Andrade <qulogic@pidgin.im> |
---|---|
date | Mon, 12 Sep 2011 02:42:41 +0000 |
parents | 513b8ec76077 (current diff) 62c34bc73f18 (diff) |
children | 4d64ab35c73b |
files | |
diffstat | 55 files changed, 2649 insertions(+), 506 deletions(-) [+] |
line wrap: on
line diff
--- a/ChangeLog.API Sun Sep 11 04:19:01 2011 +0000 +++ b/ChangeLog.API Mon Sep 12 02:42:41 2011 +0000 @@ -107,10 +107,14 @@ * PidginConversation.sg * purple_account_add_buddies_with_invite * purple_account_add_buddy_with_invite + * purple_blist_update_buddy_icon + * purple_buddy_get_local_alias * purple_buddy_icons_has_custom_icon * purple_buddy_icons_find_custom_icon * purple_buddy_icons_set_custom_icon * purple_connection_error_reason + * purple_contact_set_alias + * purple_conv_chat_set_users * purple_core_migrate * purple_dnsquery_a_account * purple_notify_searchresults_column_get_title @@ -125,6 +129,7 @@ * purple_plugins_unregister_unload_notify_cb * purple_presence_add_status * purple_presence_add_list + * purple_proxy_connect_socks5 * purple_srv_cancel * purple_srv_resolve_account * purple_status_set_attr_boolean @@ -144,6 +149,7 @@ * PurpleConnectionUiOps.report_disconnect_reason * PurplePluginProtocolInfo.add_buddy_with_invite * PurplePluginProtocolInfo.add_buddies_with_invite + * PurplePluginProtocolInfo.get_cb_away * serv_got_attention * serv_send_attention * struct _GtkIMHtmlFontDetail
--- a/configure.ac Sun Sep 11 04:19:01 2011 +0000 +++ b/configure.ac Mon Sep 12 02:42:41 2011 +0000 @@ -434,6 +434,16 @@ PKG_CHECK_MODULES(PANGO, [pango >= 1.4.0], AC_DEFINE(HAVE_PANGO14, 1, [Define if we have Pango 1.4 or newer.]),:) + PKG_CHECK_MODULES(WEBKIT, [webkit-1.0 >= 1.1.1], , [ + AC_MSG_RESULT(no) + AC_MSG_ERROR([ +You must have WebKit 1.1.1 or newer development headers installed to compile +Pidgin. If you want to build only Finch then specify --disable-gtkui when +running configure. +])]) + AC_SUBST(WEBKIT_CFLAGS) + AC_SUBST(WEBKIT_LIBS) + dnl ####################################################################### dnl # Check if we should compile with X support dnl ####################################################################### @@ -2486,6 +2496,7 @@ pidgin/pixmaps/emotes/none/Makefile pidgin/pixmaps/emotes/small/16/Makefile pidgin/plugins/Makefile + pidgin/plugins/adiumthemes/Makefile pidgin/plugins/cap/Makefile pidgin/plugins/disco/Makefile pidgin/plugins/gestures/Makefile
--- a/libpurple/blist.c Sun Sep 11 04:19:01 2011 +0000 +++ b/libpurple/blist.c Mon Sep 12 02:42:41 2011 +0000 @@ -956,12 +956,6 @@ ops->update(purplebuddylist, node); } -void -purple_blist_update_buddy_icon(PurpleBuddy *buddy) -{ - purple_blist_update_node_icon((PurpleBlistNode *)buddy); -} - /* * TODO: Maybe remove the call to this from server.c and call it * from oscar.c and toc.c instead? @@ -1743,11 +1737,6 @@ return (PurpleGroup *)(((PurpleBlistNode *)contact)->parent); } -void purple_contact_set_alias(PurpleContact *contact, const char *alias) -{ - purple_blist_alias_contact(contact,alias); -} - const char *purple_contact_get_alias(PurpleContact* contact) { g_return_val_if_fail(contact != NULL, NULL); @@ -2375,26 +2364,6 @@ return NULL; } -const char *purple_buddy_get_local_alias(PurpleBuddy *buddy) -{ - PurpleContact *c; - - g_return_val_if_fail(buddy != NULL, NULL); - - /* Search for an alias for the buddy. In order of precedence: */ - /* The buddy alias */ - if (buddy->alias != NULL) - return buddy->alias; - - /* The contact alias */ - c = purple_buddy_get_contact(buddy); - if ((c != NULL) && (c->alias != NULL)) - return c->alias; - - /* The buddy's user name (i.e. no alias) */ - return buddy->name; -} - const char *purple_chat_get_name(PurpleChat *chat) { char *ret = NULL;
--- a/libpurple/blist.h Sun Sep 11 04:19:01 2011 +0000 +++ b/libpurple/blist.h Mon Sep 12 02:42:41 2011 +0000 @@ -454,21 +454,9 @@ * 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); -#if !(defined PURPLE_DISABLE_DEPRECATED) || (defined _PURPLE_BLIST_C_) -/** - * 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. * @@ -774,18 +762,6 @@ */ PurpleBuddy *purple_contact_get_priority_buddy(PurpleContact *contact); -#if !(defined PURPLE_DISABLE_DEPRECATED) || (defined _PURPLE_BLIST_C_) -/** - * Sets the alias for a contact. - * - * @param contact The contact - * @param alias The alias to set, or NULL to unset - * - * @deprecated Use purple_blist_alias_contact() instead. - */ -void purple_contact_set_alias(PurpleContact *contact, const char *alias); -#endif - /** * Gets the alias for a contact. * @@ -876,19 +852,6 @@ */ const char *purple_buddy_get_contact_alias(PurpleBuddy *buddy); -#if !(defined PURPLE_DISABLE_DEPRECATED) || (defined _PURPLE_BLIST_C_) -/** - * Returns the correct alias for this user, ignoring server aliases. Used - * when a user-recognizable name is required. In order: buddy's alias; buddy's - * contact alias; buddy's user name. - * - * @param buddy The buddy whose alias will be returned. - * @return The appropriate name or alias. - * @deprecated Try purple_buddy_get_alias(), if server aliases are okay. - */ -const char *purple_buddy_get_local_alias(PurpleBuddy *buddy); -#endif - /** * Returns the correct name to display for a buddy. In order of precedence: * the buddy's alias; the buddy's server alias; the buddy's contact alias;
--- a/libpurple/conversation.c Sun Sep 11 04:19:01 2011 +0000 +++ b/libpurple/conversation.c Mon Sep 12 02:42:41 2011 +0000 @@ -1379,16 +1379,6 @@ } GList * -purple_conv_chat_set_users(PurpleConvChat *chat, GList *users) -{ - g_return_val_if_fail(chat != NULL, NULL); - - chat->in_room = users; - - return users; -} - -GList * purple_conv_chat_get_users(const PurpleConvChat *chat) { g_return_val_if_fail(chat != NULL, NULL);
--- a/libpurple/conversation.h Sun Sep 11 04:19:01 2011 +0000 +++ b/libpurple/conversation.h Mon Sep 12 02:42:41 2011 +0000 @@ -1080,22 +1080,6 @@ PurpleConversation *purple_conv_chat_get_conversation(const PurpleConvChat *chat); /** - * Sets the list of users in the chat room. - * - * @note Calling this function will not update the display of the users. - * Please use purple_conv_chat_add_user(), purple_conv_chat_add_users(), - * purple_conv_chat_remove_user(), and purple_conv_chat_remove_users() instead. - * - * @param chat The chat. - * @param users The list of users. - * - * @return The list passed. - * - * @deprecated This function will be removed in 3.0.0. You shouldn't be using it anyway. - */ -GList *purple_conv_chat_set_users(PurpleConvChat *chat, GList *users); - -/** * Returns a list of users in the chat room. The members of the list * are PurpleConvChatBuddy objects. *
--- a/libpurple/dbus-analyze-functions.py Sun Sep 11 04:19:01 2011 +0000 +++ b/libpurple/dbus-analyze-functions.py Mon Sep 12 02:42:41 2011 +0000 @@ -31,6 +31,11 @@ "purple_account_unregister", "purple_connection_new_unregister", + # Similar to the above, again + "purple_menu_action_new", + "purple_menu_action_set_callback", + "purple_menu_action_get_callback", + # These functions are excluded because they involve setting arbitrary # data via pointers for protocols and UIs. This just won't work. "purple_blist_get_ui_data",
--- a/libpurple/plugins/perl/common/BuddyList.xs Sun Sep 11 04:19:01 2011 +0000 +++ b/libpurple/plugins/perl/common/BuddyList.xs Mon Sep 12 02:42:41 2011 +0000 @@ -82,11 +82,6 @@ purple_contact_get_priority_buddy(contact) Purple::BuddyList::Contact contact -void -purple_contact_set_alias(contact, alias) - Purple::BuddyList::Contact contact - const char * alias - const char * purple_contact_get_alias(contact) Purple::BuddyList::Contact contact @@ -200,10 +195,6 @@ Purple::Status old_status void -purple_blist_update_buddy_icon(buddy) - Purple::BuddyList::Buddy buddy - -void purple_blist_rename_buddy(buddy, name) Purple::BuddyList::Buddy buddy const char * name @@ -430,9 +421,5 @@ Purple::BuddyList::Buddy buddy const char * -purple_buddy_get_local_alias(buddy) - Purple::BuddyList::Buddy buddy - -const char * purple_buddy_get_alias(buddy) Purple::BuddyList::Buddy buddy
--- a/libpurple/plugins/perl/common/Conversation.xs Sun Sep 11 04:19:01 2011 +0000 +++ b/libpurple/plugins/perl/common/Conversation.xs Mon Sep 12 02:42:41 2011 +0000 @@ -338,24 +338,6 @@ Purple::Conversation::Chat chat void -purple_conv_chat_set_users(chat, users) - Purple::Conversation::Chat chat - SV * users -PREINIT: - GList *l, *t_GL; - int i, t_len; -PPCODE: - t_GL = NULL; - t_len = av_len((AV *)SvRV(users)); - - for (i = 0; i <= t_len; i++) - t_GL = g_list_append(t_GL, SvPVutf8_nolen(*av_fetch((AV *)SvRV(users), i, 0))); - - for (l = purple_conv_chat_set_users(chat, t_GL); l != NULL; l = l->next) { - XPUSHs(sv_2mortal(purple_perl_bless_object(l->data, "Purple::ListEntry"))); - } - -void purple_conv_chat_get_users(chat) Purple::Conversation::Chat chat PREINIT:
--- a/libpurple/protocols/bonjour/bonjour.c Sun Sep 11 04:19:01 2011 +0000 +++ b/libpurple/protocols/bonjour/bonjour.c Mon Sep 12 02:42:41 2011 +0000 @@ -525,7 +525,6 @@ NULL, /* keepalive */ NULL, /* register_user */ NULL, /* get_cb_info */ - NULL, /* get_cb_away */ NULL, /* alias_buddy */ bonjour_group_buddy, /* group_buddy */ bonjour_rename_group, /* rename_group */
--- a/libpurple/protocols/gg/gg.c Sun Sep 11 04:19:01 2011 +0000 +++ b/libpurple/protocols/gg/gg.c Mon Sep 12 02:42:41 2011 +0000 @@ -2696,7 +2696,6 @@ ggp_keepalive, /* keepalive */ ggp_register_user, /* register_user */ NULL, /* get_cb_info */ - NULL, /* get_cb_away */ NULL, /* alias_buddy */ NULL, /* group_buddy */ NULL, /* rename_group */
--- a/libpurple/protocols/irc/irc.c Sun Sep 11 04:19:01 2011 +0000 +++ b/libpurple/protocols/irc/irc.c Mon Sep 12 02:42:41 2011 +0000 @@ -959,7 +959,6 @@ irc_keepalive, /* keepalive */ NULL, /* register_user */ NULL, /* get_cb_info */ - NULL, /* get_cb_away */ NULL, /* alias_buddy */ NULL, /* group_buddy */ NULL, /* rename_group */
--- a/libpurple/protocols/jabber/libxmpp.c Sun Sep 11 04:19:01 2011 +0000 +++ b/libpurple/protocols/jabber/libxmpp.c Mon Sep 12 02:42:41 2011 +0000 @@ -98,7 +98,6 @@ jabber_keepalive, /* keepalive */ jabber_register_account, /* register_user */ NULL, /* get_cb_info */ - NULL, /* get_cb_away */ jabber_roster_alias_change, /* alias_buddy */ jabber_roster_group_change, /* group_buddy */ jabber_roster_group_rename, /* rename_group */
--- a/libpurple/protocols/msn/msn.c Sun Sep 11 04:19:01 2011 +0000 +++ b/libpurple/protocols/msn/msn.c Mon Sep 12 02:42:41 2011 +0000 @@ -2971,7 +2971,6 @@ msn_keepalive, /* keepalive */ NULL, /* register_user */ NULL, /* get_cb_info */ - NULL, /* get_cb_away */ msn_alias_buddy, /* alias_buddy */ msn_group_buddy, /* group_buddy */ msn_rename_group, /* rename_group */
--- a/libpurple/protocols/mxit/mxit.c Sun Sep 11 04:19:01 2011 +0000 +++ b/libpurple/protocols/mxit/mxit.c Mon Sep 12 02:42:41 2011 +0000 @@ -716,7 +716,6 @@ mxit_keepalive, /* keepalive */ mxit_register, /* register_user */ NULL, /* get_cb_info */ - NULL, /* get_cb_away */ mxit_buddy_alias, /* alias_buddy [roster.c] */ mxit_buddy_group, /* group_buddy [roster.c] */ mxit_rename_group, /* rename_group [roster.c] */
--- a/libpurple/protocols/myspace/myspace.c Sun Sep 11 04:19:01 2011 +0000 +++ b/libpurple/protocols/myspace/myspace.c Mon Sep 12 02:42:41 2011 +0000 @@ -3052,7 +3052,6 @@ NULL, /* keepalive */ NULL, /* register_user */ NULL, /* get_cb_info */ - NULL, /* get_cb_away */ NULL, /* alias_buddy */ NULL, /* group_buddy */ NULL, /* rename_group */
--- a/libpurple/protocols/novell/novell.c Sun Sep 11 04:19:01 2011 +0000 +++ b/libpurple/protocols/novell/novell.c Mon Sep 12 02:42:41 2011 +0000 @@ -3515,7 +3515,6 @@ novell_keepalive, /* keepalive */ NULL, /* register_user */ NULL, /* get_cb_info */ - NULL, /* get_cb_away */ novell_alias_buddy, /* alias_buddy */ novell_group_buddy, /* group_buddy */ novell_rename_group, /* rename_group */
--- a/libpurple/protocols/null/nullprpl.c Sun Sep 11 04:19:01 2011 +0000 +++ b/libpurple/protocols/null/nullprpl.c Mon Sep 12 02:42:41 2011 +0000 @@ -1101,7 +1101,6 @@ NULL, /* keepalive */ nullprpl_register_user, /* register_user */ nullprpl_get_cb_info, /* get_cb_info */ - NULL, /* get_cb_away */ nullprpl_alias_buddy, /* alias_buddy */ nullprpl_group_buddy, /* group_buddy */ nullprpl_rename_group, /* rename_group */
--- a/libpurple/protocols/oscar/libaim.c Sun Sep 11 04:19:01 2011 +0000 +++ b/libpurple/protocols/oscar/libaim.c Mon Sep 12 02:42:41 2011 +0000 @@ -70,7 +70,6 @@ oscar_keepalive, /* keepalive */ NULL, /* register_user */ NULL, /* get_cb_info */ - NULL, /* get_cb_away */ oscar_alias_buddy, /* alias_buddy */ oscar_move_buddy, /* group_buddy */ oscar_rename_group, /* rename_group */
--- a/libpurple/protocols/oscar/libicq.c Sun Sep 11 04:19:01 2011 +0000 +++ b/libpurple/protocols/oscar/libicq.c Mon Sep 12 02:42:41 2011 +0000 @@ -79,7 +79,6 @@ oscar_keepalive, /* keepalive */ NULL, /* register_user */ NULL, /* get_cb_info */ - NULL, /* get_cb_away */ oscar_alias_buddy, /* alias_buddy */ oscar_move_buddy, /* group_buddy */ oscar_rename_group, /* rename_group */
--- a/libpurple/protocols/sametime/sametime.c Sun Sep 11 04:19:01 2011 +0000 +++ b/libpurple/protocols/sametime/sametime.c Mon Sep 12 02:42:41 2011 +0000 @@ -5195,7 +5195,6 @@ .keepalive = mw_prpl_keepalive, .register_user = NULL, .get_cb_info = NULL, - .get_cb_away = NULL, .alias_buddy = mw_prpl_alias_buddy, .group_buddy = mw_prpl_group_buddy, .rename_group = mw_prpl_rename_group,
--- a/libpurple/protocols/silc/silc.c Sun Sep 11 04:19:01 2011 +0000 +++ b/libpurple/protocols/silc/silc.c Mon Sep 12 02:42:41 2011 +0000 @@ -2093,7 +2093,6 @@ silcpurple_keepalive, /* keepalive */ NULL, /* register_user */ NULL, /* get_cb_info */ - NULL, /* get_cb_away */ NULL, /* alias_buddy */ NULL, /* group_buddy */ NULL, /* rename_group */
--- a/libpurple/protocols/simple/simple.c Sun Sep 11 04:19:01 2011 +0000 +++ b/libpurple/protocols/simple/simple.c Mon Sep 12 02:42:41 2011 +0000 @@ -2087,7 +2087,6 @@ simple_keep_alive, /* keepalive */ NULL, /* register_user */ NULL, /* get_cb_info */ - NULL, /* get_cb_away */ NULL, /* alias_buddy */ NULL, /* group_buddy */ NULL, /* rename_group */
--- a/libpurple/protocols/yahoo/libyahoo.c Sun Sep 11 04:19:01 2011 +0000 +++ b/libpurple/protocols/yahoo/libyahoo.c Mon Sep 12 02:42:41 2011 +0000 @@ -235,7 +235,6 @@ yahoo_keepalive, NULL, /* register_user */ NULL, /* get_cb_info */ - NULL, /* get_cb_away */ yahoo_update_alias, /* alias_buddy */ yahoo_change_buddys_group, yahoo_rename_group,
--- a/libpurple/protocols/yahoo/libyahoojp.c Sun Sep 11 04:19:01 2011 +0000 +++ b/libpurple/protocols/yahoo/libyahoojp.c Mon Sep 12 02:42:41 2011 +0000 @@ -131,7 +131,6 @@ yahoo_keepalive, NULL, /* register_user */ NULL, /* get_cb_info */ - NULL, /* get_cb_away */ yahoo_update_alias, /* alias_buddy */ yahoo_change_buddys_group, yahoo_rename_group,
--- a/libpurple/protocols/zephyr/zephyr.c Sun Sep 11 04:19:01 2011 +0000 +++ b/libpurple/protocols/zephyr/zephyr.c Mon Sep 12 02:42:41 2011 +0000 @@ -2894,7 +2894,6 @@ NULL, /* keepalive -- Not necessary*/ NULL, /* register_user -- Not supported*/ NULL, /* XXX get_cb_info */ - NULL, /* get_cb_away */ NULL, /* alias_buddy */ NULL, /* group_buddy */ NULL, /* rename_group */
--- a/libpurple/proxy.c Sun Sep 11 04:19:01 2011 +0000 +++ b/libpurple/proxy.c Mon Sep 12 02:42:41 2011 +0000 @@ -2408,17 +2408,6 @@ return connect_data; } -PurpleProxyConnectData * -purple_proxy_connect_socks5(void *handle, PurpleProxyInfo *gpi, - const char *host, int port, - PurpleProxyConnectFunction connect_cb, - gpointer data) -{ - return purple_proxy_connect_socks5_account(NULL, handle, gpi, - host, port, connect_cb, data); -} - - /* This is called when we connect to the SOCKS5 proxy server (through any * relevant account proxy) */
--- a/libpurple/proxy.h Sun Sep 11 04:19:01 2011 +0000 +++ b/libpurple/proxy.h Mon Sep 12 02:42:41 2011 +0000 @@ -307,35 +307,6 @@ const char *host, int port, PurpleProxyConnectFunction connect_cb, gpointer data); -#if !(defined PURPLE_DISABLE_DEPRECATED) || (defined _PURPLE_PROXY_C_) -/** - * Makes a connection through a SOCKS5 proxy. - * - * @param handle A handle that should be associated with this - * connection attempt. The handle can be used - * to cancel the connection attempt using the - * purple_proxy_connect_cancel_with_handle() - * function. - * @param gpi The PurpleProxyInfo specifying the proxy settings - * @param host The destination host. - * @param port The destination port. - * @param connect_cb The function to call when the connection is - * established. If the connection failed then - * fd will be -1 and error message will be set - * to something descriptive (hopefully). - * @param data User-defined data. - * - * @return NULL if there was an error, or a reference to an - * opaque data structure that can be used to cancel - * the pending connection, if needed. - * @deprecated Use purple_proxy_connect_socks5_account instead - */ -PurpleProxyConnectData *purple_proxy_connect_socks5(void *handle, - PurpleProxyInfo *gpi, - const char *host, int port, - PurpleProxyConnectFunction connect_cb, gpointer data); -#endif - /** * Cancel an in-progress connection attempt. This should be called * by the PRPL if the user disables an account while it is still
--- a/libpurple/prpl.h Sun Sep 11 04:19:01 2011 +0000 +++ b/libpurple/prpl.h Mon Sep 12 02:42:41 2011 +0000 @@ -448,11 +448,6 @@ * @deprecated Use #PurplePluginProtocolInfo.get_info instead. */ void (*get_cb_info)(PurpleConnection *, int, const char *who); - /** - * @deprecated Use #PurplePluginProtocolInfo.get_cb_real_name and - * #PurplePluginProtocolInfo.status_text instead. - */ - void (*get_cb_away)(PurpleConnection *, int, const char *who); /** save/store buddy's alias on server list/roster */ void (*alias_buddy)(PurpleConnection *, const char *who,
--- a/libpurple/util.c Sun Sep 11 04:19:01 2011 +0000 +++ b/libpurple/util.c Mon Sep 12 02:42:41 2011 +0000 @@ -113,8 +113,7 @@ return act->label; } -PurpleCallback * -purple_menu_action_get_callback(const PurpleMenuAction *act) +PurpleCallback purple_menu_action_get_callback(const PurpleMenuAction *act) { g_return_val_if_fail(act != NULL, NULL);
--- a/libpurple/util.h Sun Sep 11 04:19:01 2011 +0000 +++ b/libpurple/util.h Mon Sep 12 02:42:41 2011 +0000 @@ -103,7 +103,7 @@ * * @return The callback function. */ -PurpleCallback *purple_menu_action_get_callback(const PurpleMenuAction *act); +PurpleCallback purple_menu_action_get_callback(const PurpleMenuAction *act); /** * Returns the data stored in the PurpleMenuAction.
--- a/pidgin/Makefile.am Sun Sep 11 04:19:01 2011 +0000 +++ b/pidgin/Makefile.am Mon Sep 12 02:42:41 2011 +0000 @@ -83,9 +83,11 @@ gtkstatusbox.c \ gtkthemes.c \ gtkutils.c \ + gtkwebview.c \ gtkwhiteboard.c \ minidialog.c \ - pidgintooltip.c + pidgintooltip.c \ + smileyparser.c pidgin_headers = \ gtkaccount.h \ @@ -133,10 +135,12 @@ pidginstock.h \ gtkthemes.h \ gtkutils.h \ + gtkwebview.h \ gtkwhiteboard.h \ minidialog.h \ pidgintooltip.h \ - pidgin.h + pidgin.h \ + smileyparser.h pidginincludedir=$(includedir)/pidgin pidgininclude_HEADERS = \ @@ -155,6 +159,7 @@ $(INTLLIBS) \ $(GTKSPELL_LIBS) \ $(LIBXML_LIBS) \ + $(WEBKIT_LIBS) \ $(GTK_LIBS) \ $(top_builddir)/libpurple/libpurple.la @@ -178,6 +183,7 @@ $(DBUS_CFLAGS) \ $(GTKSPELL_CFLAGS) \ $(LIBXML_CFLAGS) \ + $(WEBKIT_CFLAGS) \ $(INTGG_CFLAGS) endif # ENABLE_GTK
--- a/pidgin/gtkconv.c Sun Sep 11 04:19:01 2011 +0000 +++ b/pidgin/gtkconv.c Mon Sep 12 02:42:41 2011 +0000 @@ -69,6 +69,7 @@ #include "gtkprivacy.h" #include "gtkthemes.h" #include "gtkutils.h" +#include "gtkwebview.h" #include "pidginstock.h" #include "pidgintooltip.h" @@ -209,7 +210,7 @@ static const GdkColor *get_nick_color(PidginConversation *gtkconv, const char *name) { static GdkColor col; - GtkStyle *style = gtk_widget_get_style(gtkconv->imhtml); + GtkStyle *style = gtk_widget_get_style(gtkconv->webview); float scale; col = nick_colors[g_str_hash(name) % nbr_nick_colors]; @@ -436,8 +437,9 @@ PidginConversation *gtkconv = NULL; gtkconv = PIDGIN_CONVERSATION(conv); - if (gtkconv) - gtk_imhtml_clear(GTK_IMHTML(gtkconv->imhtml)); + + if (PIDGIN_CONVERSATION(conv)) + webkit_web_view_load_html_string(WEBKIT_WEB_VIEW(gtkconv->webview), "", ""); } static PurpleCmdRet @@ -1045,32 +1047,7 @@ static void savelog_writefile_cb(void *user_data, const char *filename) { - PurpleConversation *conv = (PurpleConversation *)user_data; - FILE *fp; - const char *name; - char **lines; - gchar *text; - - if ((fp = g_fopen(filename, "w+")) == NULL) { - purple_notify_error(PIDGIN_CONVERSATION(conv), NULL, _("Unable to open file."), NULL); - return; - } - - name = purple_conversation_get_name(conv); - fprintf(fp, "<html>\n<head>\n"); - fprintf(fp, "<meta http-equiv=\"content-type\" content=\"text/html; charset=UTF-8\">\n"); - fprintf(fp, "<title>%s</title>\n</head>\n<body>\n", name); - fprintf(fp, _("<h1>Conversation with %s</h1>\n"), name); - - lines = gtk_imhtml_get_markup_lines( - GTK_IMHTML(PIDGIN_CONVERSATION(conv)->imhtml)); - text = g_strjoinv("<br>\n", lines); - fprintf(fp, "%s", text); - g_free(text); - g_strfreev(lines); - - fprintf(fp, "\n</body>\n</html>\n"); - fclose(fp); + /* TODO: I don't know how to support this using webkit yet. */ } /* @@ -1579,32 +1556,6 @@ } static void -menu_chat_get_away_cb(GtkWidget *w, PidginConversation *gtkconv) -{ - PurpleConversation *conv = gtkconv->active_conv; - PurplePluginProtocolInfo *prpl_info = NULL; - PurpleConnection *gc; - char *who; - - gc = purple_conversation_get_gc(conv); - who = g_object_get_data(G_OBJECT(w), "user_data"); - - if (gc != NULL) { - prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(gc->prpl); - - /* - * May want to expand this to work similarly to menu_info_cb? - */ - - if (prpl_info->get_cb_away != NULL) - { - prpl_info->get_cb_away(gc, - purple_conv_chat_get_id(PURPLE_CONV_CHAT(conv)), who); - } - } -} - -static void menu_chat_add_remove_cb(GtkWidget *w, PidginConversation *gtkconv) { PurpleConversation *conv = gtkconv->active_conv; @@ -1627,7 +1578,7 @@ static GtkTextMark * get_mark_for_user(PidginConversation *gtkconv, const char *who) { - GtkTextBuffer *buf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(gtkconv->imhtml)); + GtkTextBuffer *buf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(gtkconv->webview)); char *tmp = g_strconcat("user:", who, NULL); GtkTextMark *mark = gtk_text_buffer_get_mark(buf, tmp); @@ -1638,6 +1589,8 @@ static void menu_last_said_cb(GtkWidget *w, PidginConversation *gtkconv) { +/* FIXME: This doesn't work yet, of course... */ +#if 0 GtkTextMark *mark; const char *who; @@ -1648,6 +1601,7 @@ gtk_text_view_scroll_to_mark(GTK_TEXT_VIEW(gtkconv->imhtml), mark, 0.1, FALSE, 0, 0); else g_return_if_reached(); +#endif } static GtkWidget * @@ -1735,16 +1689,6 @@ g_object_set_data_full(G_OBJECT(button), "user_data", g_strdup(who), g_free); } - if (prpl_info && prpl_info->get_cb_away) { - button = pidgin_new_item_from_stock(menu, _("Get Away Message"), PIDGIN_STOCK_AWAY, - G_CALLBACK(menu_chat_get_away_cb), PIDGIN_CONVERSATION(conv), 0, 0, NULL); - - if (gc == NULL) - gtk_widget_set_sensitive(button, FALSE); - else - g_object_set_data_full(G_OBJECT(button), "user_data", g_strdup(who), g_free); - } - if (!is_me && prpl_info && !(prpl_info->options & OPT_PROTO_UNIQUE_CHATNAME)) { if ((buddy = purple_find_buddy(conv->account, who)) != NULL) button = pidgin_new_item_from_stock(menu, _("Remove"), GTK_STOCK_REMOVE, @@ -1862,10 +1806,13 @@ chat_do_im(gtkconv, who); } else if (event->button == 2 && event->type == GDK_BUTTON_PRESS) { /* Move to user's anchor */ +/* FIXME: This isn't implemented yet. */ +#if 0 GtkTextMark *mark = get_mark_for_user(gtkconv, who); if(mark != NULL) gtk_text_view_scroll_to_mark(GTK_TEXT_VIEW(gtkconv->imhtml), mark, 0.1, FALSE, 0, 0); +#endif } else if (event->button == 3 && event->type == GDK_BUTTON_PRESS) { GtkWidget *menu = create_chat_menu (conv, who, gc); gtk_menu_popup(GTK_MENU(menu), NULL, NULL, NULL, NULL, @@ -1942,8 +1889,8 @@ GtkWidget *from; GtkWidget *to; } transitions[] = { - {gtkconv->entry, gtkconv->imhtml}, - {gtkconv->imhtml, chat ? gtkconv->u.chat->list : gtkconv->entry}, + {gtkconv->entry, gtkconv->webview}, + {gtkconv->webview, chat ? gtkconv->u.chat->list : gtkconv->entry}, {chat ? gtkconv->u.chat->list : NULL, gtkconv->entry}, {NULL, NULL} }, *ptr; @@ -2199,14 +2146,20 @@ break; case GDK_Page_Up: - case GDK_KP_Page_Up: + case GDK_KP_Page_Up: +/* FIXME: Write this. */ +#if 0 gtk_imhtml_page_up(GTK_IMHTML(gtkconv->imhtml)); +#endif return TRUE; break; case GDK_Page_Down: - case GDK_KP_Page_Down: + case GDK_KP_Page_Down: +/* FIXME: Write this. */ +#if 0 gtk_imhtml_page_down(GTK_IMHTML(gtkconv->imhtml)); +#endif return TRUE; break; @@ -2316,7 +2269,7 @@ entry = GTK_IMHTML(gtkconv->entry); protocol_name = purple_account_get_protocol_name(conv->account); gtk_imhtml_set_protocol_name(entry, protocol_name); - gtk_imhtml_set_protocol_name(GTK_IMHTML(gtkconv->imhtml), protocol_name); + /* FIXME: gtk_imhtml_set_protocol_name(GTK_IMHTML(gtkconv->imhtml), protocol_name); */ if (!(conv->features & PURPLE_CONNECTION_HTML)) gtk_imhtml_clear_formatting(GTK_IMHTML(gtkconv->entry)); @@ -3252,11 +3205,11 @@ if (purple_conversation_get_type(conv) == PURPLE_CONV_TYPE_CHAT) { chat = purple_blist_find_chat(conv->account, conv->name); - if ((chat == NULL) && (gtkconv->imhtml != NULL)) { - chat = g_object_get_data(G_OBJECT(gtkconv->imhtml), "transient_chat"); - } - - if ((chat == NULL) && (gtkconv->imhtml != NULL)) { + if ((chat == NULL) && (gtkconv->webview != NULL)) { + chat = g_object_get_data(G_OBJECT(gtkconv->webview), "transient_chat"); + } + + if ((chat == NULL) && (gtkconv->webview != NULL)) { GHashTable *components; PurpleAccount *account = purple_conversation_get_account(conv); PurplePlugin *prpl = purple_find_prpl(purple_account_get_protocol_id(account)); @@ -3274,7 +3227,7 @@ chat = purple_chat_new(conv->account, NULL, components); purple_blist_node_set_flags((PurpleBlistNode *)chat, PURPLE_BLIST_NODE_FLAG_NO_SAVE); - g_object_set_data_full(G_OBJECT(gtkconv->imhtml), "transient_chat", + g_object_set_data_full(G_OBJECT(gtkconv->webview), "transient_chat", chat, (GDestroyNotify)purple_blist_remove_chat); } } else { @@ -3286,15 +3239,15 @@ /* gotta remain bug-compatible :( libpurple < 2.0.2 didn't handle * removing "isolated" buddy nodes well */ if (purple_version_check(2, 0, 2) == NULL) { - if ((buddy == NULL) && (gtkconv->imhtml != NULL)) { - buddy = g_object_get_data(G_OBJECT(gtkconv->imhtml), "transient_buddy"); + if ((buddy == NULL) && (gtkconv->webview != NULL)) { + buddy = g_object_get_data(G_OBJECT(gtkconv->webview), "transient_buddy"); } - if ((buddy == NULL) && (gtkconv->imhtml != NULL)) { + if ((buddy == NULL) && (gtkconv->webview != NULL)) { buddy = purple_buddy_new(conv->account, conv->name, NULL); purple_blist_node_set_flags((PurpleBlistNode *)buddy, PURPLE_BLIST_NODE_FLAG_NO_SAVE); - g_object_set_data_full(G_OBJECT(gtkconv->imhtml), "transient_buddy", + g_object_set_data_full(G_OBJECT(gtkconv->webview), "transient_buddy", buddy, (GDestroyNotify)purple_buddy_destroy); } } @@ -3705,6 +3658,8 @@ static void update_typing_message(PidginConversation *gtkconv, const char *message) { + /* FIXME: this is not handled at all */ +#if 0 GtkTextBuffer *buffer; GtkTextMark *stmark, *enmark; @@ -3737,6 +3692,7 @@ gtk_text_buffer_get_end_iter(buffer, &iter); gtk_text_buffer_create_mark(buffer, "typing-notification-end", &iter, TRUE); } +#endif } static void @@ -3978,7 +3934,8 @@ continue; account = purple_buddy_get_account(buddy); - if (purple_account_is_connected(account) || account == gtkconv->active_conv->account) + /* FIXME: */ + if (purple_account_is_connected(account) /*|| account == gtkconv->active_conv->account*/) { /* Use the PurplePresence to get unique buddies. */ PurplePresence *presence = purple_buddy_get_presence(buddy); @@ -4090,10 +4047,13 @@ g_free(tmp); if (is_me) { +#if 0 + /* FIXME: No tags in webkit stuff, yet. */ GtkTextTag *tag = gtk_text_tag_table_lookup( - gtk_text_buffer_get_tag_table(GTK_IMHTML(gtkconv->imhtml)->text_buffer), + gtk_text_buffer_get_tag_table(GTK_IMHTML(gtkconv->webview)->text_buffer), "send-name"); g_object_get(tag, "foreground-gdk", &color, NULL); +#endif } else { GtkTextTag *tag; if ((tag = get_buddy_tag(conv, name, 0, FALSE))) @@ -4657,7 +4617,7 @@ GdkRectangle oneline; int height, diff; int pad_top, pad_inside, pad_bottom; - int total_height = (gtkconv->imhtml->allocation.height + gtkconv->entry->allocation.height); + int total_height = (gtkconv->webview->allocation.height + gtkconv->entry->allocation.height); int max_height = total_height / 2; int min_lines = purple_prefs_get_int(PIDGIN_PREFS_ROOT "/conversations/minimum_entry_lines"); int min_height; @@ -4897,13 +4857,13 @@ if (purple_conversation_get_type(conv) == PURPLE_CONV_TYPE_CHAT) { node = (PurpleBlistNode*)(purple_blist_find_chat(conv->account, conv->name)); if (!node) - node = g_object_get_data(G_OBJECT(gtkconv->imhtml), "transient_chat"); + node = g_object_get_data(G_OBJECT(gtkconv->webview), "transient_chat"); } else { node = (PurpleBlistNode*)(purple_find_buddy(conv->account, conv->name)); #if 0 /* Using the transient blist nodes to show the tooltip doesn't quite work yet. */ if (!node) - node = g_object_get_data(G_OBJECT(gtkconv->imhtml), "transient_buddy"); + node = g_object_get_data(G_OBJECT(gtkconv->webview), "transient_buddy"); #endif } @@ -4918,7 +4878,7 @@ { gtk_widget_modify_base(gtkconv->quickfind.entry, GTK_STATE_NORMAL, NULL); - gtk_imhtml_search_clear(GTK_IMHTML(gtkconv->imhtml)); + webkit_web_view_unmark_text_matches(WEBKIT_WEB_VIEW(gtkconv->webview)); gtk_widget_hide_all(gtkconv->quickfind.container); gtk_widget_grab_focus(gtkconv->entry); @@ -4931,7 +4891,7 @@ switch (event->keyval) { case GDK_Return: case GDK_KP_Enter: - if (gtk_imhtml_search_find(GTK_IMHTML(gtkconv->imhtml), gtk_entry_get_text(GTK_ENTRY(entry)))) { + if (webkit_web_view_search_text(WEBKIT_WEB_VIEW(gtkconv->webview), gtk_entry_get_text(GTK_ENTRY(entry)), FALSE, TRUE, TRUE)) { gtk_widget_modify_base(gtkconv->quickfind.entry, GTK_STATE_NORMAL, NULL); } else { GdkColor col; @@ -4984,7 +4944,7 @@ static GtkWidget * setup_common_pane(PidginConversation *gtkconv) { - GtkWidget *vbox, *frame, *imhtml_sw, *event_box; + GtkWidget *vbox, *frame, *webview_sw, *event_box; GtkCellRenderer *rend; GtkTreePath *path; PurpleConversation *conv = gtkconv->active_conv; @@ -5080,9 +5040,10 @@ gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(gtkconv->infopane), rend, "pixbuf", CONV_EMBLEM_COLUMN, NULL); g_object_set(rend, "xalign", 0.0, "xpad", 6, "ypad", 0, NULL); - /* Setup the gtkimhtml widget */ - frame = pidgin_create_imhtml(FALSE, >kconv->imhtml, NULL, &imhtml_sw); - gtk_widget_set_size_request(gtkconv->imhtml, -1, 0); + /* Setup the webkit widget */ + frame = pidgin_create_webview(FALSE, >kconv->webview, NULL, &webview_sw); + gtk_widget_set_size_request(gtkconv->webview, -1, 0); + if (chat) { GtkWidget *hpaned; @@ -5100,19 +5061,16 @@ } else { gtk_box_pack_start(GTK_BOX(vbox), frame, TRUE, TRUE, 0); } - gtk_widget_show(frame); - - gtk_widget_set_name(gtkconv->imhtml, "pidgin_conv_imhtml"); - gtk_imhtml_show_comments(GTK_IMHTML(gtkconv->imhtml),TRUE); - g_object_set_data(G_OBJECT(gtkconv->imhtml), "gtkconv", gtkconv); - - g_object_set(G_OBJECT(imhtml_sw), "vscrollbar-policy", GTK_POLICY_ALWAYS, NULL); - - g_signal_connect_after(G_OBJECT(gtkconv->imhtml), "button_press_event", + gtk_widget_show_all(frame); + + gtk_widget_set_name(gtkconv->webview, "pidgin_conv_webview"); + g_object_set_data(G_OBJECT(gtkconv->webview), "gtkconv", gtkconv); + + g_signal_connect_after(G_OBJECT(gtkconv->webview), "button_press_event", G_CALLBACK(entry_stop_rclick_cb), NULL); - g_signal_connect(G_OBJECT(gtkconv->imhtml), "key_press_event", + g_signal_connect(G_OBJECT(gtkconv->webview), "key_press_event", G_CALLBACK(refocus_entry_cb), gtkconv); - g_signal_connect(G_OBJECT(gtkconv->imhtml), "key_release_event", + g_signal_connect(G_OBJECT(gtkconv->webview), "key_release_event", G_CALLBACK(refocus_entry_cb), gtkconv); pidgin_conv_setup_quickfind(gtkconv, vbox); @@ -5353,6 +5311,8 @@ static void set_typing_font(GtkWidget *widget, GtkStyle *style, PidginConversation *gtkconv) { +/* FIXME */ +#if 0 static PangoFontDescription *font_desc = NULL; static GdkColor *color = NULL; static gboolean enable = TRUE; @@ -5383,6 +5343,7 @@ } g_signal_handlers_disconnect_by_func(G_OBJECT(widget), set_typing_font, gtkconv); +#endif } /************************************************************************** @@ -5424,9 +5385,6 @@ } pane = setup_common_pane(gtkconv); - gtk_imhtml_set_format_functions(GTK_IMHTML(gtkconv->imhtml), - gtk_imhtml_get_format_functions(GTK_IMHTML(gtkconv->imhtml)) | GTK_IMHTML_IMAGE); - if (pane == NULL) { if (conv_type == PURPLE_CONV_TYPE_CHAT) g_free(gtkconv->u.chat); @@ -5449,7 +5407,7 @@ GTK_DEST_DEFAULT_DROP, te, sizeof(te) / sizeof(GtkTargetEntry), GDK_ACTION_COPY); - gtk_drag_dest_set(gtkconv->imhtml, 0, + gtk_drag_dest_set(gtkconv->webview, 0, te, sizeof(te) / sizeof(GtkTargetEntry), GDK_ACTION_COPY); @@ -5461,12 +5419,12 @@ G_CALLBACK(ignore_middle_click), NULL); g_signal_connect(G_OBJECT(pane), "drag_data_received", G_CALLBACK(conv_dnd_recv), gtkconv); - g_signal_connect(G_OBJECT(gtkconv->imhtml), "drag_data_received", + g_signal_connect(G_OBJECT(gtkconv->webview), "drag_data_received", G_CALLBACK(conv_dnd_recv), gtkconv); g_signal_connect(G_OBJECT(gtkconv->entry), "drag_data_received", G_CALLBACK(conv_dnd_recv), gtkconv); - g_signal_connect(gtkconv->imhtml, "style-set", G_CALLBACK(set_typing_font), gtkconv); + g_signal_connect(gtkconv->webview, "style-set", G_CALLBACK(set_typing_font), gtkconv); /* Setup the container for the tab. */ gtkconv->tab_cont = tab_cont = gtk_vbox_new(FALSE, PIDGIN_HIG_BOX_SPACE); @@ -5496,10 +5454,6 @@ else gtk_widget_hide(gtkconv->infopane_hbox); - gtk_imhtml_show_comments(GTK_IMHTML(gtkconv->imhtml), - purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/conversations/show_timestamps")); - gtk_imhtml_set_protocol_name(GTK_IMHTML(gtkconv->imhtml), - purple_account_get_protocol_name(conv->account)); g_signal_connect_swapped(G_OBJECT(pane), "focus", G_CALLBACK(gtk_widget_grab_focus), @@ -5512,7 +5466,7 @@ if (nick_colors == NULL) { nbr_nick_colors = NUM_NICK_COLORS; - nick_colors = generate_nick_colors(&nbr_nick_colors, gtk_widget_get_style(gtkconv->imhtml)->base[GTK_STATE_NORMAL]); + nick_colors = generate_nick_colors(&nbr_nick_colors, gtk_widget_get_style(gtkconv->webview)->base[GTK_STATE_NORMAL]); } if (conv->features & PURPLE_CONNECTION_ALLOW_CUSTOM_SMILEY) @@ -5737,6 +5691,8 @@ static GtkTextTag *get_buddy_tag(PurpleConversation *conv, const char *who, PurpleMessageFlags flag, gboolean create) { +/* FIXME */ +#if 0 PidginConversation *gtkconv = PIDGIN_CONVERSATION(conv); GtkTextTag *buddytag; gchar *str; @@ -5770,6 +5726,8 @@ g_free(str); return buddytag; +#endif + return NULL; } static void pidgin_conv_calculate_newday(PidginConversation *gtkconv, time_t mtime) @@ -5828,8 +5786,6 @@ PurpleAccount *account; int gtk_font_options = 0; int gtk_font_options_all = 0; - int max_scrollback_lines; - int line_count; char buf2[BUF_LONG]; gboolean show_date; char *mdate; @@ -5840,8 +5796,6 @@ PurpleConversationType type; char *displaying; gboolean plugin_return; - char *bracket; - int tag_count = 0; gboolean is_rtl_message = FALSE; g_return_if_fail(conv != NULL); @@ -5899,56 +5853,9 @@ } length = strlen(displaying) + 1; - /* Awful hack to work around GtkIMHtml's inefficient rendering of messages with lots of formatting changes. - * If a message has over 100 '<' characters, strip formatting before appending it. Hopefully nobody actually - * needs that much formatting, anyway. - */ - for (bracket = strchr(displaying, '<'); bracket && *(bracket + 1); bracket = strchr(bracket + 1, '<')) - tag_count++; - - if (tag_count > 100) { - char *tmp = displaying; - displaying = purple_markup_strip_html(tmp); - g_free(tmp); - } - - line_count = gtk_text_buffer_get_line_count( - gtk_text_view_get_buffer(GTK_TEXT_VIEW( - gtkconv->imhtml))); - - max_scrollback_lines = purple_prefs_get_int( - PIDGIN_PREFS_ROOT "/conversations/scrollback_lines"); - /* If we're sitting at more than 100 lines more than the - max scrollback, trim down to max scrollback */ - if (max_scrollback_lines > 0 - && line_count > (max_scrollback_lines + 100)) { - GtkTextBuffer *text_buffer = gtk_text_view_get_buffer( - GTK_TEXT_VIEW(gtkconv->imhtml)); - GtkTextIter start, end; - - gtk_text_buffer_get_start_iter(text_buffer, &start); - gtk_text_buffer_get_iter_at_line(text_buffer, &end, - (line_count - max_scrollback_lines)); - gtk_imhtml_delete(GTK_IMHTML(gtkconv->imhtml), &start, &end); - } - - if (type == PURPLE_CONV_TYPE_CHAT) - { - /* Create anchor for user */ - GtkTextIter iter; - char *tmp = g_strconcat("user:", name, NULL); - - gtk_text_buffer_get_end_iter(gtk_text_view_get_buffer(GTK_TEXT_VIEW(gtkconv->imhtml)), &iter); - gtk_text_buffer_create_mark(gtk_text_view_get_buffer(GTK_TEXT_VIEW(gtkconv->imhtml)), - tmp, &iter, TRUE); - g_free(tmp); - } - - if (purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/conversations/use_smooth_scrolling")) - gtk_font_options_all |= GTK_IMHTML_USE_SMOOTHSCROLLING; - - if (gtk_text_buffer_get_char_count(gtk_text_view_get_buffer(GTK_TEXT_VIEW(gtkconv->imhtml)))) - gtk_imhtml_append_text(GTK_IMHTML(gtkconv->imhtml), "<BR>", gtk_font_options_all | GTK_IMHTML_NO_SCROLL); + /* if the buffer is not empty add a <br> */ + if (!gtk_webview_is_empty(GTK_WEBVIEW(gtkconv->webview))) + gtk_webview_append_html(GTK_WEBVIEW(gtkconv->webview), "<br />"); /* First message in a conversation. */ if (gtkconv->newday == 0) @@ -5999,32 +5906,32 @@ if (!(flags & PURPLE_MESSAGE_RECV) && (conv->features & PURPLE_CONNECTION_ALLOW_CUSTOM_SMILEY)) { /* We want to see our own smileys. Need to revert it after send*/ - pidgin_themes_smiley_themeize_custom(gtkconv->imhtml); + pidgin_themes_smiley_themeize_custom(gtkconv->webview); } /* TODO: These colors should not be hardcoded so log.c can use them */ if (flags & PURPLE_MESSAGE_RAW) { - gtk_imhtml_append_text(GTK_IMHTML(gtkconv->imhtml), message, gtk_font_options_all); + gtk_webview_append_html(GTK_WEBVIEW(gtkconv->webview), message); } else if (flags & PURPLE_MESSAGE_SYSTEM) { g_snprintf(buf2, sizeof(buf2), - "<FONT %s><FONT SIZE=\"2\"><!--%s --></FONT><B>%s</B></FONT>", + "<font %s><font size=\"2\"><span class='timestamp'>%s</span></font><b>%s</b></font>", sml_attrib ? sml_attrib : "", mdate, displaying); - gtk_imhtml_append_text(GTK_IMHTML(gtkconv->imhtml), buf2, gtk_font_options_all); + gtk_webview_append_html(GTK_WEBVIEW(gtkconv->webview), buf2); } else if (flags & PURPLE_MESSAGE_ERROR) { g_snprintf(buf2, sizeof(buf2), - "<FONT COLOR=\"#ff0000\"><FONT %s><FONT SIZE=\"2\"><!--%s --></FONT><B>%s</B></FONT></FONT>", + "<font color=\"#ff0000\"><font %s><font size=\"2\"><span class='timestamp'>%s</span> </font><b>%s</b></font></font>", sml_attrib ? sml_attrib : "", mdate, displaying); - gtk_imhtml_append_text(GTK_IMHTML(gtkconv->imhtml), buf2, gtk_font_options_all); + gtk_webview_append_html(GTK_WEBVIEW(gtkconv->webview), buf2); } else if (flags & PURPLE_MESSAGE_NO_LOG) { g_snprintf(buf2, BUF_LONG, - "<B><FONT %s COLOR=\"#777777\">%s</FONT></B>", + "<b><font %s color=\"#777777\">%s</font></b>", sml_attrib ? sml_attrib : "", displaying); - gtk_imhtml_append_text(GTK_IMHTML(gtkconv->imhtml), buf2, gtk_font_options_all); + gtk_webview_append_html(GTK_WEBVIEW(gtkconv->webview), buf2); } else { char *new_message = g_memdup(displaying, length); char *alias_escaped = (alias ? g_markup_escape_text(alias, strlen(alias)) : g_strdup("")); @@ -6034,11 +5941,6 @@ int tag_end_offset = 0; const char *tagname = NULL; - GtkTextIter start, end; - GtkTextMark *mark; - GtkTextTag *tag; - GtkTextBuffer *buffer = GTK_IMHTML(gtkconv->imhtml)->text_buffer; - /* Enforce direction on alias */ if (is_rtl_message) str_embed_direction_chars(&alias_escaped); @@ -6099,55 +6001,41 @@ g_free(alias_escaped); + /* FIXME: */ +#if 0 if (tagname) tag = gtk_text_tag_table_lookup(gtk_text_buffer_get_tag_table(buffer), tagname); else tag = get_buddy_tag(conv, name, flags, TRUE); if (GTK_IMHTML(gtkconv->imhtml)->show_comments) { + { /* The color for the timestamp has to be set in the font-tags, unfortunately. * Applying the nick-tag to timestamps would work, but that can make it * bold. I thought applying the "comment" tag again, which has "weight" set * to PANGO_WEIGHT_NORMAL, would remove the boldness. But it doesn't. So * this will have to do. I don't terribly like it. -- sadrul */ - const char *color = get_text_tag_color(tag); + /* const char *color = get_text_tag_color(tag); */ g_snprintf(buf2, BUF_LONG, "<FONT %s%s%s SIZE=\"2\"><!--%s --></FONT>", color ? "COLOR=\"" : "", color ? color : "", color ? "\"" : "", mdate); - gtk_imhtml_append_text(GTK_IMHTML(gtkconv->imhtml), buf2, gtk_font_options_all | GTK_IMHTML_NO_SCROLL); - } - - gtk_text_buffer_get_end_iter(buffer, &end); - mark = gtk_text_buffer_create_mark(buffer, NULL, &end, TRUE); - - g_snprintf(buf2, BUF_LONG, "<FONT %s>%s</FONT> ", sml_attrib ? sml_attrib : "", str); - gtk_imhtml_append_text(GTK_IMHTML(gtkconv->imhtml), buf2, gtk_font_options_all | GTK_IMHTML_NO_SCROLL); - - gtk_text_buffer_get_end_iter(buffer, &end); - gtk_text_buffer_get_iter_at_mark(buffer, &start, mark); - gtk_text_buffer_apply_tag(buffer, tag, &start, &end); - gtk_text_buffer_delete_mark(buffer, mark); + gtk_webview_append_html (GTK_WEBVIEW(gtkconv->webview), buf2); + } +#endif + g_snprintf(buf2, BUF_LONG, "<font %s>%s</font> ", sml_attrib ? sml_attrib : "", str); + gtk_webview_append_html(GTK_WEBVIEW(gtkconv->webview), buf2); g_free(str); - if(gc){ + if (gc) { char *pre = g_strdup_printf("<font %s>", sml_attrib ? sml_attrib : ""); char *post = "</font>"; - int pre_len = strlen(pre); - int post_len = strlen(post); - - with_font_tag = g_malloc(length + pre_len + post_len + 1); - - strcpy(with_font_tag, pre); - memcpy(with_font_tag + pre_len, new_message, length); - strcpy(with_font_tag + pre_len + length, post); - - length += pre_len + post_len; + with_font_tag = g_strdup_printf("%s%s%s", pre, new_message, post); g_free(pre); } else with_font_tag = g_memdup(new_message, length); - gtk_imhtml_append_text(GTK_IMHTML(gtkconv->imhtml), - with_font_tag, gtk_font_options | gtk_font_options_all); + gtk_webview_append_html(GTK_WEBVIEW(gtkconv->webview), + with_font_tag); g_free(with_font_tag); g_free(new_message); @@ -6177,7 +6065,7 @@ if (!(flags & PURPLE_MESSAGE_RECV) && (conv->features & PURPLE_CONNECTION_ALLOW_CUSTOM_SMILEY)) { /* Restore the smiley-data */ - pidgin_themes_smiley_themeize(gtkconv->imhtml); + pidgin_themes_smiley_themeize(gtkconv->webview); } purple_signal_emit(pidgin_conversations_get_handle(), @@ -6423,6 +6311,13 @@ } static gboolean +add_custom_smiley_for_webview(GtkWebView *webview, const char *sml, const char *smile) +{ + /* FIXME: Smileys need to be added to webkit stuff */ + return TRUE; +} + +static gboolean pidgin_conv_custom_smiley_add(PurpleConversation *conv, const char *smile, gboolean remote) { PidginConversation *gtkconv; @@ -6449,7 +6344,7 @@ } } - if (!add_custom_smiley_for_imhtml(GTK_IMHTML(gtkconv->imhtml), sml, smile)) + if (!add_custom_smiley_for_webview(GTK_WEBVIEW(gtkconv->webview), sml, smile)) return FALSE; if (!remote) /* If it's a local custom smiley, then add it for the entry */ @@ -6463,6 +6358,8 @@ pidgin_conv_custom_smiley_write(PurpleConversation *conv, const char *smile, const guchar *data, gsize size) { +/* FIXME */ +#if 0 PidginConversation *gtkconv; GtkIMHtmlSmiley *smiley; const char *sml; @@ -6497,11 +6394,14 @@ g_object_unref(G_OBJECT(smiley->loader)); smiley->loader = gdk_pixbuf_loader_new(); } +#endif } static void pidgin_conv_custom_smiley_close(PurpleConversation *conv, const char *smile) { +/* FIXME*/ +#if 0 PidginConversation *gtkconv; GtkIMHtmlSmiley *smiley; const char *sml; @@ -6538,6 +6438,7 @@ g_object_unref(G_OBJECT(smiley->loader)); smiley->loader = gdk_pixbuf_loader_new(); } +#endif } static void @@ -6811,7 +6712,7 @@ } if (fields & PIDGIN_CONV_SMILEY_THEME) - pidgin_themes_smiley_themeize(PIDGIN_CONVERSATION(conv)->imhtml); + pidgin_themes_smiley_themeize(PIDGIN_CONVERSATION(conv)->webview); if ((fields & PIDGIN_CONV_COLORIZE_TITLE) || (fields & PIDGIN_CONV_SET_TITLE) || @@ -7389,8 +7290,11 @@ GTK_CHECK_MENU_ITEM(win->menu.show_timestamps), (gboolean)GPOINTER_TO_INT(value)); +/* FIXME: Use WebKit version of this. */ +#if 0 gtk_imhtml_show_comments(GTK_IMHTML(gtkconv->imhtml), (gboolean)GPOINTER_TO_INT(value)); +#endif } } @@ -7791,7 +7695,7 @@ while (gtkconv->attach.current && count < 100) { /* XXX: 100 is a random value here */ PurpleConvMessage *msg = gtkconv->attach.current->data; if (!im && when && when < msg->when) { - gtk_imhtml_append_text(GTK_IMHTML(gtkconv->imhtml), "<BR><HR>", 0); + gtk_webview_append_html(GTK_WEBVIEW(gtkconv->webview), "<BR><HR>"); g_object_set_data(G_OBJECT(gtkconv->entry), "attach-start-time", NULL); } pidgin_conv_write_conv(msg->conv, msg->who, msg->alias, msg->what, msg->flags, msg->when); @@ -7826,7 +7730,7 @@ PurpleConvMessage *msg = msgs->data; pidgin_conv_write_conv(msg->conv, msg->who, msg->alias, msg->what, msg->flags, msg->when); } - gtk_imhtml_append_text(GTK_IMHTML(gtkconv->imhtml), "<BR><HR>", 0); + gtk_webview_append_html(GTK_WEBVIEW(gtkconv->webview), "<BR><HR>"); g_object_set_data(G_OBJECT(gtkconv->entry), "attach-start-time", NULL); }
--- a/pidgin/gtkconv.h Sun Sep 11 04:19:01 2011 +0000 +++ b/pidgin/gtkconv.h Mon Sep 12 02:42:41 2011 +0000 @@ -95,7 +95,7 @@ GtkWidget *tabby; GtkWidget *menu_tabby; - GtkWidget *imhtml; + GtkWidget *webview; GtkTextBuffer *entry_buffer; GtkWidget *entry; gboolean auto_resize; /* this is set to TRUE if the conversation
--- a/pidgin/gtkdialogs.c Sun Sep 11 04:19:01 2011 +0000 +++ b/pidgin/gtkdialogs.c Mon Sep 12 02:42:41 2011 +0000 @@ -39,10 +39,9 @@ #include "gtkblist.h" #include "gtkdialogs.h" -#include "gtkimhtml.h" -#include "gtkimhtmltoolbar.h" #include "gtklog.h" #include "gtkutils.h" +#include "gtkwebview.h" #include "pidginstock.h" static GList *dialogwindows = NULL; @@ -422,9 +421,8 @@ static GtkWidget * pidgin_build_help_dialog(const char *title, const char *role, GString *string) { - GtkWidget *win, *vbox, *frame, *logo, *imhtml, *button; + GtkWidget *win, *vbox, *frame, *logo, *webview, *button; GdkPixbuf *pixbuf; - GtkTextIter iter; AtkObject *obj; char *filename, *tmp; @@ -451,13 +449,15 @@ g_free(tmp); gtk_box_pack_start(GTK_BOX(vbox), logo, FALSE, FALSE, 0); - frame = pidgin_create_imhtml(FALSE, &imhtml, NULL, NULL); + frame = pidgin_create_webview(FALSE, &webview, NULL, NULL); + /* FIXME: Compile now and fix it later when we have a proper replacement for this function gtk_imhtml_set_format_functions(GTK_IMHTML(imhtml), GTK_IMHTML_ALL ^ GTK_IMHTML_SMILEY); + */ gtk_box_pack_start(GTK_BOX(vbox), frame, TRUE, TRUE, 0); - gtk_imhtml_append_text(GTK_IMHTML(imhtml), string->str, GTK_IMHTML_NO_SCROLL); - gtk_text_buffer_get_start_iter(gtk_text_view_get_buffer(GTK_TEXT_VIEW(imhtml)), &iter); - gtk_text_buffer_place_cursor(gtk_text_view_get_buffer(GTK_TEXT_VIEW(imhtml)), &iter); + gtk_webview_append_html(GTK_WEBVIEW(webview), string->str); + /* FIXME: This doesn't seem to stay at the top. */ + webkit_web_view_move_cursor(WEBKIT_WEB_VIEW(webview), GTK_MOVEMENT_BUFFER_ENDS, -1); button = pidgin_dialog_add_button(GTK_DIALOG(win), GTK_STOCK_CLOSE, G_CALLBACK(destroy_win), win);
--- a/pidgin/gtkdocklet.c Sun Sep 11 04:19:01 2011 +0000 +++ b/pidgin/gtkdocklet.c Mon Sep 12 02:42:41 2011 +0000 @@ -715,6 +715,7 @@ { static GtkWidget *menu = NULL; GtkWidget *menuitem; + GtkMenuPositionFunc pos_func = gtk_status_icon_position_menu; if (menu) { gtk_widget_destroy(menu); @@ -790,10 +791,11 @@ #ifdef _WIN32 g_signal_connect(menu, "leave-notify-event", G_CALLBACK(docklet_menu_leave_enter), NULL); g_signal_connect(menu, "enter-notify-event", G_CALLBACK(docklet_menu_leave_enter), NULL); + pos_func = NULL; #endif gtk_widget_show_all(menu); gtk_menu_popup(GTK_MENU(menu), NULL, NULL, - gtk_status_icon_position_menu, + pos_func, docklet, 0, gtk_get_current_event_time()); } @@ -856,6 +858,7 @@ return FALSE; } +#ifndef _WIN32 static gboolean docklet_gtk_embed_timeout_cb(gpointer data) { @@ -888,6 +891,7 @@ return TRUE; #endif } +#endif #if GTK_CHECK_VERSION(2,12,0) static gboolean @@ -1003,6 +1007,7 @@ */ if (!recreate) { pidgin_docklet_embedded(); +#ifndef _WIN32 #if GTK_CHECK_VERSION(2,12,0) if (purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/docklet/gtk/embedded")) { embed_timeout = purple_timeout_add_seconds(LONG_EMBED_TIMEOUT, docklet_gtk_embed_timeout_cb, NULL); @@ -1012,6 +1017,7 @@ #else embed_timeout = purple_timeout_add_seconds(SHORT_EMBED_TIMEOUT, docklet_gtk_embed_timeout_cb, NULL); #endif +#endif } purple_debug_info("docklet", "GTK+ created\n");
--- a/pidgin/gtklog.c Sun Sep 11 04:19:01 2011 +0000 +++ b/pidgin/gtklog.c Mon Sep 12 02:42:41 2011 +0000 @@ -35,9 +35,9 @@ #include "pidginstock.h" #include "gtkblist.h" -#include "gtkimhtml.h" #include "gtklog.h" #include "gtkutils.h" +#include "gtkwebview.h" static GHashTable *log_viewers = NULL; static void populate_log_tree(PidginLogViewer *lv); @@ -130,7 +130,7 @@ populate_log_tree(lv); g_free(lv->search); lv->search = NULL; - gtk_imhtml_search_clear(GTK_IMHTML(lv->imhtml)); + webkit_web_view_unmark_text_matches(WEBKIT_WEB_VIEW(lv->web_view)); select_first_log(lv); return; } @@ -138,7 +138,7 @@ if (lv->search != NULL && !strcmp(lv->search, search_term)) { /* Searching for the same term acts as "Find Next" */ - gtk_imhtml_search_find(GTK_IMHTML(lv->imhtml), lv->search); + webkit_web_view_search_text(WEBKIT_WEB_VIEW(lv->web_view), lv->search, FALSE, TRUE, TRUE); return; } @@ -148,7 +148,7 @@ lv->search = g_strdup(search_term); gtk_tree_store_clear(lv->treestore); - gtk_imhtml_clear(GTK_IMHTML(lv->imhtml)); + webkit_web_view_open(WEBKIT_WEB_VIEW(lv->web_view), "about:blank"); /* clear the view */ for (logs = lv->logs; logs != NULL; logs = logs->next) { char *read = purple_log_read((PurpleLog*)logs->data, NULL); @@ -419,8 +419,9 @@ static gboolean search_find_cb(gpointer data) { PidginLogViewer *viewer = data; - gtk_imhtml_search_find(GTK_IMHTML(viewer->imhtml), viewer->search); - g_object_steal_data(G_OBJECT(viewer->entry), "search-find-cb"); + webkit_web_view_mark_text_matches(WEBKIT_WEB_VIEW(viewer->web_view), viewer->search, FALSE, 0); + webkit_web_view_set_highlight_text_matches(WEBKIT_WEB_VIEW(viewer->web_view), TRUE); + webkit_web_view_search_text(WEBKIT_WEB_VIEW(viewer->web_view), viewer->search, FALSE, TRUE, TRUE); return FALSE; } @@ -461,23 +462,16 @@ read = purple_log_read(log, &flags); viewer->flags = flags; - gtk_imhtml_clear(GTK_IMHTML(viewer->imhtml)); - gtk_imhtml_set_protocol_name(GTK_IMHTML(viewer->imhtml), - purple_account_get_protocol_name(log->account)); + webkit_web_view_open(WEBKIT_WEB_VIEW(viewer->web_view), "about:blank"); purple_signal_emit(pidgin_log_get_handle(), "log-displaying", viewer, log); - gtk_imhtml_append_text(GTK_IMHTML(viewer->imhtml), read, - GTK_IMHTML_NO_COMMENTS | GTK_IMHTML_NO_TITLE | GTK_IMHTML_NO_SCROLL | - ((flags & PURPLE_LOG_READ_NO_NEWLINE) ? GTK_IMHTML_NO_NEWLINE : 0)); + webkit_web_view_load_html_string(WEBKIT_WEB_VIEW(viewer->web_view), read, ""); g_free(read); if (viewer->search != NULL) { - guint source; - gtk_imhtml_search_clear(GTK_IMHTML(viewer->imhtml)); - source = g_idle_add(search_find_cb, viewer); - g_object_set_data_full(G_OBJECT(viewer->entry), "search-find-cb", - GINT_TO_POINTER(source), (GDestroyNotify)g_source_remove); + webkit_web_view_unmark_text_matches(WEBKIT_WEB_VIEW(viewer->web_view)); + g_idle_add(search_find_cb, viewer); } pidgin_clear_cursor(viewer->window); @@ -620,7 +614,7 @@ col = gtk_tree_view_column_new_with_attributes ("time", rend, "markup", 0, NULL); gtk_tree_view_append_column (GTK_TREE_VIEW(lv->treeview), col); gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (lv->treeview), FALSE); - gtk_paned_add1(GTK_PANED(pane), + gtk_paned_add1(GTK_PANED(pane), pidgin_make_scrollable(lv->treeview, GTK_POLICY_NEVER, GTK_POLICY_ALWAYS, GTK_SHADOW_IN, -1, -1)); populate_log_tree(lv); @@ -655,9 +649,9 @@ gtk_paned_add2(GTK_PANED(pane), vbox); /* Viewer ************/ - frame = pidgin_create_imhtml(FALSE, &lv->imhtml, NULL, NULL); - gtk_widget_set_name(lv->imhtml, "pidgin_log_imhtml"); - gtk_widget_set_size_request(lv->imhtml, 320, 200); + frame = pidgin_create_webview(FALSE, &lv->web_view, NULL, NULL); + gtk_widget_set_name(lv->web_view, "pidgin_log_web_view"); + gtk_widget_set_size_request(lv->web_view, 320, 200); gtk_box_pack_start(GTK_BOX(vbox), frame, TRUE, TRUE, 0); gtk_widget_show(frame);
--- a/pidgin/gtklog.h Sun Sep 11 04:19:01 2011 +0000 +++ b/pidgin/gtklog.h Mon Sep 12 02:42:41 2011 +0000 @@ -43,7 +43,7 @@ GtkWidget *window; /**< The viewer's window */ GtkTreeStore *treestore; /**< The treestore containing said logs */ GtkWidget *treeview; /**< The treeview representing said treestore */ - GtkWidget *imhtml; /**< The imhtml to display said logs */ + GtkWidget *web_view; /**< The webkit web view to display said logs */ GtkWidget *entry; /**< The search entry, in which search terms * are entered */ PurpleLogReadFlags flags; /**< The most recently used log flags */
--- a/pidgin/gtknotify.c Sun Sep 11 04:19:01 2011 +0000 +++ b/pidgin/gtknotify.c Mon Sep 12 02:42:41 2011 +0000 @@ -36,10 +36,10 @@ #include "util.h" #include "gtkblist.h" -#include "gtkimhtml.h" #include "gtknotify.h" #include "gtkpounce.h" #include "gtkutils.h" +#include "gtkwebview.h" typedef struct { @@ -810,21 +810,6 @@ return FALSE; } -static GtkIMHtmlOptions -notify_imhtml_options(void) -{ - GtkIMHtmlOptions options = 0; - - if (!purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/conversations/show_incoming_formatting")) - options |= GTK_IMHTML_NO_COLOURS | GTK_IMHTML_NO_FONTS | GTK_IMHTML_NO_SIZES; - - options |= GTK_IMHTML_NO_COMMENTS; - options |= GTK_IMHTML_NO_TITLE; - options |= GTK_IMHTML_NO_NEWLINE; - options |= GTK_IMHTML_NO_SCROLL; - return options; -} - static void * pidgin_notify_formatted(const char *title, const char *primary, const char *secondary, const char *text) @@ -833,7 +818,7 @@ GtkWidget *vbox; GtkWidget *label; GtkWidget *button; - GtkWidget *imhtml; + GtkWidget *web_view; GtkWidget *frame; char label_text[2048]; char *linked_text, *primary_esc, *secondary_esc; @@ -869,12 +854,10 @@ gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 0); gtk_widget_show(label); - /* Add the imhtml */ - frame = pidgin_create_imhtml(FALSE, &imhtml, NULL, NULL); - gtk_widget_set_name(imhtml, "pidgin_notify_imhtml"); - gtk_imhtml_set_format_functions(GTK_IMHTML(imhtml), - gtk_imhtml_get_format_functions(GTK_IMHTML(imhtml)) | GTK_IMHTML_IMAGE); - gtk_widget_set_size_request(imhtml, 300, 250); + /* Add the webview */ + frame = pidgin_create_webview(FALSE, &web_view, NULL, NULL); + gtk_widget_set_name(web_view, "pidgin_notify_webview"); + gtk_widget_set_size_request(web_view, 300, 250); gtk_box_pack_start(GTK_BOX(vbox), frame, TRUE, TRUE, 0); gtk_widget_show(frame); @@ -889,10 +872,10 @@ /* Make sure URLs are clickable */ linked_text = purple_markup_linkify(text); - gtk_imhtml_append_text(GTK_IMHTML(imhtml), linked_text, notify_imhtml_options()); + webkit_web_view_load_html_string(WEBKIT_WEB_VIEW(web_view), linked_text, ""); g_free(linked_text); - g_object_set_data(G_OBJECT(window), "info-widget", imhtml); + g_object_set_data(G_OBJECT(window), "webview-widget", web_view); /* Show the window */ pidgin_auto_parent_window(window); @@ -1147,10 +1130,9 @@ info = purple_notify_user_info_get_text_with_newline(user_info, "<br />"); pinfo = g_hash_table_lookup(userinfo, key); if (pinfo != NULL) { - GtkIMHtml *imhtml = g_object_get_data(G_OBJECT(pinfo->window), "info-widget"); + GtkWidget *webview = g_object_get_data(G_OBJECT(pinfo->window), "webview-widget"); char *linked_text = purple_markup_linkify(info); - gtk_imhtml_clear(imhtml); - gtk_imhtml_append_text(imhtml, linked_text, notify_imhtml_options()); + gtk_webview_load_html_string_with_imgstore(GTK_WEBVIEW(webview), linked_text); g_free(linked_text); g_free(key); ui_handle = pinfo->window; @@ -1650,7 +1632,7 @@ gtk_label_set_line_wrap(GTK_LABEL(label), TRUE); gtk_misc_set_alignment(GTK_MISC(label), 0, 0); gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 0); - gtk_box_pack_start(GTK_BOX(vbox), + gtk_box_pack_start(GTK_BOX(vbox), pidgin_make_scrollable(spec_dialog->treeview, GTK_POLICY_AUTOMATIC, GTK_POLICY_ALWAYS, GTK_SHADOW_IN, -1, -1), TRUE, TRUE, 2);
--- a/pidgin/gtkthemes.c Sun Sep 11 04:19:01 2011 +0000 +++ b/pidgin/gtkthemes.c Mon Sep 12 02:42:41 2011 +0000 @@ -278,6 +278,8 @@ if (*i == '[' && strchr(i, ']') && load) { struct smiley_list *child = g_new0(struct smiley_list, 1); child->sml = g_strndup(i+1, strchr(i, ']') - i - 1); + child->files = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free); + if (theme->list) list->next = child; else @@ -320,6 +322,7 @@ } else { GtkIMHtmlSmiley *smiley = gtk_imhtml_smiley_create(sfile, l, hidden, 0); list->smileys = g_slist_prepend(list->smileys, smiley); + g_hash_table_insert (list->files, g_strdup(l), g_strdup(sfile)); } while (isspace(*i)) i++; @@ -358,7 +361,6 @@ 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_custom(PIDGIN_CONVERSATION(conv)->entry); } }
--- a/pidgin/gtkthemes.h Sun Sep 11 04:19:01 2011 +0000 +++ b/pidgin/gtkthemes.h Mon Sep 12 02:42:41 2011 +0000 @@ -29,6 +29,7 @@ struct smiley_list { char *sml; GSList *smileys; + GHashTable *files; /**< map from smiley shortcut to filename */ struct smiley_list *next; };
--- a/pidgin/gtkutils.c Sun Sep 11 04:19:01 2011 +0000 +++ b/pidgin/gtkutils.c Mon Sep 12 02:42:41 2011 +0000 @@ -67,6 +67,7 @@ #include "pidginstock.h" #include "gtkthemes.h" #include "gtkutils.h" +#include "gtkwebview.h" #include "pidgin/minidialog.h" typedef struct { @@ -276,6 +277,69 @@ return frame; } +GtkWidget * +pidgin_create_webview(gboolean editable, GtkWidget **webview_ret, GtkWidget **toolbar_ret, GtkWidget **sw_ret) +{ + GtkWidget *frame; + GtkWidget *webview; + GtkWidget *sep; + GtkWidget *sw; + GtkWidget *toolbar = NULL; + GtkWidget *vbox; + + frame = gtk_frame_new(NULL); + gtk_frame_set_shadow_type(GTK_FRAME(frame), GTK_SHADOW_IN); + + vbox = gtk_vbox_new(FALSE, 0); + gtk_container_add(GTK_CONTAINER(frame), vbox); + gtk_widget_show(vbox); + + if (editable) { + toolbar = gtk_imhtmltoolbar_new(); + gtk_box_pack_start(GTK_BOX(vbox), toolbar, FALSE, FALSE, 0); + gtk_widget_show(toolbar); + + sep = gtk_hseparator_new(); + gtk_box_pack_start(GTK_BOX(vbox), sep, FALSE, FALSE, 0); + g_signal_connect_swapped(G_OBJECT(toolbar), "show", G_CALLBACK(gtk_widget_show), sep); + g_signal_connect_swapped(G_OBJECT(toolbar), "hide", G_CALLBACK(gtk_widget_hide), sep); + gtk_widget_show(sep); + } + + webview = gtk_webview_new(); +#if 0 + /* FIXME: Don't have editable webview yet. */ + gtk_webview_set_editable(GTK_WEBVIEW(webview), editable); +#endif +#ifdef USE_GTKSPELL + if (editable && purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/conversations/spellcheck")) + pidgin_setup_gtkspell(GTK_TEXT_VIEW(webview)); +#endif + gtk_widget_show(webview); + + if (editable) { + gtk_imhtmltoolbar_attach(GTK_IMHTMLTOOLBAR(toolbar), webview); + gtk_imhtmltoolbar_associate_smileys(GTK_IMHTMLTOOLBAR(toolbar), "default"); + } + + sw = pidgin_make_scrollable(webview, GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC, GTK_SHADOW_NONE, -1, -1); + gtk_box_pack_start(GTK_BOX(vbox), sw, TRUE, TRUE, 0); + + gtk_webview_set_vadjustment(GTK_WEBVIEW(webview), + gtk_scrolled_window_get_vadjustment(GTK_SCROLLED_WINDOW(sw))); + + if (webview_ret != NULL) + *webview_ret = webview; + + if (editable && (toolbar_ret != NULL)) + *toolbar_ret = toolbar; + + if (sw_ret != NULL) + *sw_ret = sw; + + return frame; +} + void pidgin_set_sensitive_if_input(GtkWidget *entry, GtkWidget *dialog) {
--- a/pidgin/gtkutils.h Sun Sep 11 04:19:01 2011 +0000 +++ b/pidgin/gtkutils.h Mon Sep 12 02:42:41 2011 +0000 @@ -109,6 +109,28 @@ GtkWidget *pidgin_create_imhtml(gboolean editable, GtkWidget **imhtml_ret, GtkWidget **toolbar_ret, GtkWidget **sw_ret); /** + * Create an GtkWebView widget and associated GtkIMHtmlToolbar widget. This + * function puts both widgets in a nice GtkFrame. They're separated by an + * attractive GtkSeparator. + * FIXME: There is no editable GtkWebView yet. + * + * @param editable @c TRUE if this webview should be editable. If this is + * @c FALSE, then the toolbar will NOT be created. If this webview + * should be read-only at first, but may become editable later, then + * pass in @c TRUE here and then manually call gtk_webview_set_editable() + * later. + * @param webview_ret A pointer to a pointer to a GtkWidget. This pointer + * will be set to the webview when this function exits. + * @param toolbar_ret A pointer to a pointer to a GtkWidget. If editable is + * TRUE then this will be set to the toolbar when this function exits. + * Otherwise this will be set to @c NULL. + * @param sw_ret This will be filled with a pointer to the scrolled window + * widget which contains the webview. + * @return The GtkFrame containing the toolbar and webview. + */ +GtkWidget *pidgin_create_webview(gboolean editable, GtkWidget **webview_ret, GtkWidget **toolbar_ret, GtkWidget **sw_ret); + +/** * Creates a small button * * @param image A button image.
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/pidgin/gtkwebview.c Mon Sep 12 02:42:41 2011 +0000 @@ -0,0 +1,419 @@ +/* + * @file gtkwebview.c GTK+ WebKitWebView wrapper class. + * @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 + * + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <ctype.h> +#include <string.h> +#include <glib.h> +#include <glib/gstdio.h> +#include <JavaScriptCore/JavaScript.h> + +#include "util.h" +#include "gtkwebview.h" +#include "imgstore.h" + +static WebKitWebViewClass *parent_class = NULL; + +struct GtkWebViewPriv { + GHashTable *images; /**< a map from id to temporary file for the image */ + gboolean empty; /**< whether anything has been appended **/ + + /* JS execute queue */ + GQueue *js_queue; + gboolean is_loading; + GtkAdjustment *vadj; + guint scroll_src; + GTimer *scroll_time; +}; + +GtkWidget * +gtk_webview_new(void) +{ + GtkWebView* ret = GTK_WEBVIEW(g_object_new(gtk_webview_get_type(), NULL)); + return GTK_WIDGET(ret); +} + +static char * +get_image_filename_from_id(GtkWebView* view, int id) +{ + char *filename = NULL; + FILE *file; + PurpleStoredImage* img; + + if (!view->priv->images) + view->priv->images = g_hash_table_new_full(g_direct_hash, g_direct_equal, NULL, g_free); + + filename = (char *)g_hash_table_lookup(view->priv->images, GINT_TO_POINTER(id)); + if (filename) + return filename; + + /* else get from img store */ + file = purple_mkstemp(&filename, TRUE); + + img = purple_imgstore_find_by_id(id); + + fwrite(purple_imgstore_get_data(img), purple_imgstore_get_size(img), 1, file); + g_hash_table_insert(view->priv->images, GINT_TO_POINTER(id), filename); + fclose(file); + return filename; +} + +static void +clear_single_image(gpointer key, gpointer value, gpointer userdata) +{ + g_unlink((char *)value); +} + +static void +clear_images(GtkWebView *view) +{ + if (!view->priv->images) + return; + g_hash_table_foreach(view->priv->images, clear_single_image, NULL); + g_hash_table_unref(view->priv->images); +} + +/* + * Replace all <img id=""> tags with <img src="">. I hoped to never + * write any HTML parsing code, but I'm forced to do this, until + * purple changes the way it works. + */ +static char * +replace_img_id_with_src(GtkWebView *view, const char *html) +{ + GString *buffer = g_string_sized_new(strlen(html)); + const char* cur = html; + char *id; + int nid; + + while (*cur) { + const char *img = strstr(cur, "<img"); + if (!img) { + g_string_append(buffer, cur); + break; + } else + g_string_append_len(buffer, cur, img - cur); + + cur = strstr(img, "/>"); + if (!cur) + cur = strstr(img, ">"); + + if (!cur) { /* invalid html? */ + g_string_printf(buffer, "%s", html); + break; + } + + if (strstr(img, "src=") || !strstr(img, "id=")) { + g_string_printf(buffer, "%s", html); + break; + } + + /* + * if this is valid HTML, then I can be sure that it + * has an id= and does not have an src=, since + * '=' cannot appear in parameters. + */ + + id = strstr(img, "id=") + 3; + + /* *id can't be \0, since a ">" appears after this */ + if (isdigit(*id)) + nid = atoi(id); + else + nid = atoi(id + 1); + + /* let's dump this, tag and then dump the src information */ + g_string_append_len(buffer, img, cur - img); + + g_string_append_printf(buffer, " src='file://%s' ", get_image_filename_from_id(view, nid)); + } + + return g_string_free(buffer, FALSE); +} + +static void +gtk_webview_finalize(GObject *view) +{ + gpointer temp; + + while ((temp = g_queue_pop_head(GTK_WEBVIEW(view)->priv->js_queue))) + g_free(temp); + g_queue_free(GTK_WEBVIEW(view)->priv->js_queue); + + clear_images(GTK_WEBVIEW(view)); + g_free(GTK_WEBVIEW(view)->priv); + G_OBJECT_CLASS(parent_class)->finalize(G_OBJECT(view)); +} + +static void +gtk_webview_class_init(GtkWebViewClass *klass, gpointer userdata) +{ + parent_class = g_type_class_ref(webkit_web_view_get_type()); + G_OBJECT_CLASS(klass)->finalize = gtk_webview_finalize; +} + +static gboolean +webview_link_clicked(WebKitWebView *view, + WebKitWebFrame *frame, + WebKitNetworkRequest *request, + WebKitWebNavigationAction *navigation_action, + WebKitWebPolicyDecision *policy_decision) +{ + const gchar *uri; + WebKitWebNavigationReason reason; + + uri = webkit_network_request_get_uri(request); + reason = webkit_web_navigation_action_get_reason(navigation_action); + + if (reason == WEBKIT_WEB_NAVIGATION_REASON_LINK_CLICKED) { + /* the gtk imhtml way was to create an idle cb, not sure + * why, so right now just using purple_notify_uri directly */ + purple_notify_uri(NULL, uri); + } else + webkit_web_policy_decision_use(policy_decision); + + return TRUE; +} + +static gboolean +process_js_script_queue(GtkWebView *view) +{ + char *script; + if (view->priv->is_loading) + return FALSE; /* we will be called when loaded */ + if (!view->priv->js_queue || g_queue_is_empty(view->priv->js_queue)) + return FALSE; /* nothing to do! */ + + script = g_queue_pop_head(view->priv->js_queue); + webkit_web_view_execute_script(WEBKIT_WEB_VIEW(view), script); + g_free(script); + + return TRUE; /* there may be more for now */ +} + +static void +webview_load_started(WebKitWebView *view, + WebKitWebFrame *frame, + gpointer userdata) +{ + /* is there a better way to test for is_loading? */ + GTK_WEBVIEW(view)->priv->is_loading = TRUE; +} + +static void +webview_load_finished(WebKitWebView *view, + WebKitWebFrame *frame, + gpointer userdata) +{ + GTK_WEBVIEW(view)->priv->is_loading = FALSE; + g_idle_add((GSourceFunc)process_js_script_queue, view); +} + +void +gtk_webview_safe_execute_script(GtkWebView *view, const char *script) +{ + g_queue_push_tail(view->priv->js_queue, g_strdup(script)); + g_idle_add((GSourceFunc)process_js_script_queue, view); +} + +static void +gtk_webview_init(GtkWebView *view, gpointer userdata) +{ + view->priv = g_new0(struct GtkWebViewPriv, 1); + g_signal_connect(view, "navigation-policy-decision-requested", + G_CALLBACK(webview_link_clicked), + view); + + g_signal_connect(view, "load-started", + G_CALLBACK(webview_load_started), + view); + + g_signal_connect(view, "load-finished", + G_CALLBACK(webview_load_finished), + view); + + view->priv->empty = TRUE; + view->priv->js_queue = g_queue_new(); +} + + +void +gtk_webview_load_html_string_with_imgstore(GtkWebView *view, const char *html) +{ + char *html_imged; + + clear_images(view); + html_imged = replace_img_id_with_src(view, html); + webkit_web_view_load_html_string(WEBKIT_WEB_VIEW(view), html_imged, "file:///"); + g_free(html_imged); +} + +char * +gtk_webview_quote_js_string(const char *text) +{ + GString *str = g_string_new("\""); + const char *cur = text; + + while (cur && *cur) { + switch (*cur) { + case '\\': + g_string_append(str, "\\\\"); + break; + case '\"': + g_string_append(str, "\\\""); + break; + case '\r': + g_string_append(str, "<br/>"); + break; + case '\n': + break; + default: + g_string_append_c(str, *cur); + } + cur++; + } + g_string_append_c(str, '"'); + return g_string_free(str, FALSE); +} + +void +gtk_webview_set_vadjustment(GtkWebView *webview, GtkAdjustment *vadj) +{ + webview->priv->vadj = vadj; +} + +/* this is a "hack", my plan is to eventually handle this + * correctly using a signals and a plugin: the plugin will have + * the information as to what javascript function to call. It seems + * wrong to hardcode that here. + */ +void +gtk_webview_append_html(GtkWebView *view, const char *html) +{ + char *escaped = gtk_webview_quote_js_string(html); + char *script = g_strdup_printf("document.write(%s)", escaped); + webkit_web_view_execute_script(WEBKIT_WEB_VIEW(view), script); + view->priv->empty = FALSE; + gtk_webview_scroll_to_end(view, TRUE); + g_free(script); + g_free(escaped); +} + +gboolean +gtk_webview_is_empty(GtkWebView *view) +{ + return view->priv->empty; +} + +#define MAX_SCROLL_TIME 0.4 /* seconds */ +#define SCROLL_DELAY 33 /* milliseconds */ + +/* + * Smoothly scroll a WebView. + * + * @return TRUE if the window needs to be scrolled further, FALSE if we're at the bottom. + */ +static gboolean +smooth_scroll_cb(gpointer data) +{ + struct GtkWebViewPriv *priv = data; + GtkAdjustment *adj = priv->vadj; + gdouble max_val = adj->upper - adj->page_size; + gdouble scroll_val = gtk_adjustment_get_value(adj) + ((max_val - gtk_adjustment_get_value(adj)) / 3); + + g_return_val_if_fail(priv->scroll_time != NULL, FALSE); + + if (g_timer_elapsed(priv->scroll_time, NULL) > MAX_SCROLL_TIME || scroll_val >= max_val) { + /* time's up. jump to the end and kill the timer */ + gtk_adjustment_set_value(adj, max_val); + g_timer_destroy(priv->scroll_time); + priv->scroll_time = NULL; + g_source_remove(priv->scroll_src); + priv->scroll_src = 0; + return FALSE; + } + + /* scroll by 1/3rd the remaining distance */ + gtk_adjustment_set_value(adj, scroll_val); + return TRUE; +} + +static gboolean +scroll_idle_cb(gpointer data) +{ + struct GtkWebViewPriv *priv = data; + GtkAdjustment *adj = priv->vadj; + if (adj) { + gtk_adjustment_set_value(adj, adj->upper - adj->page_size); + } + priv->scroll_src = 0; + return FALSE; +} + +void +gtk_webview_scroll_to_end(GtkWebView *webview, gboolean smooth) +{ + struct GtkWebViewPriv *priv = webview->priv; + if (priv->scroll_time) + g_timer_destroy(priv->scroll_time); + if (priv->scroll_src) + g_source_remove(priv->scroll_src); + if(smooth) { + priv->scroll_time = g_timer_new(); + priv->scroll_src = g_timeout_add_full(G_PRIORITY_LOW, SCROLL_DELAY, smooth_scroll_cb, priv, NULL); + } else { + priv->scroll_time = NULL; + priv->scroll_src = g_idle_add_full(G_PRIORITY_LOW, scroll_idle_cb, priv, NULL); + } +} + +GType +gtk_webview_get_type(void) +{ + static GType mview_type = 0; + if (G_UNLIKELY(mview_type == 0)) { + static const GTypeInfo mview_info = { + sizeof(GtkWebViewClass), + NULL, + NULL, + (GClassInitFunc) gtk_webview_class_init, + NULL, + NULL, + sizeof(GtkWebView), + 0, + (GInstanceInitFunc) gtk_webview_init, + NULL + }; + mview_type = g_type_register_static(webkit_web_view_get_type(), + "GtkWebView", &mview_info, 0); + } + return mview_type; +} +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/pidgin/gtkwebview.h Mon Sep 12 02:42:41 2011 +0000 @@ -0,0 +1,147 @@ +/** + * @file gtkwebview.h Wrapper over the Gtk WebKitWebView component + * @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_WEBVIEW_H_ +#define _PIDGIN_WEBVIEW_H_ + +#include <glib.h> +#include <gtk/gtk.h> +#include <webkit/webkit.h> + +#include "notify.h" + +#define GTK_TYPE_WEBVIEW (gtk_webview_get_type()) +#define GTK_WEBVIEW(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), GTK_TYPE_WEBVIEW, GtkWebView)) +#define GTK_WEBVIEW_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), GTK_TYPE_WEBVIEW, GtkWebViewClass)) +#define GTK_IS_WEBVIEW(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), GTK_TYPE_WEBVIEW)) +#define GTK_IS_WEBVIEW_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), GTK_TYPE_WEBVIEW)) + + +struct GtkWebViewPriv; + +struct _GtkWebView +{ + WebKitWebView webkit_web_view; + + /*< private >*/ + struct GtkWebViewPriv *priv; +}; + +typedef struct _GtkWebView GtkWebView; + +struct _GtkWebViewClass +{ + WebKitWebViewClass parent; +}; + +typedef struct _GtkWebViewClass GtkWebViewClass; + + +/** + * Returns the GType for a GtkWebView widget + * + * @return the GType for GtkWebView widget + */ +GType gtk_webview_get_type(void); + +/** + * Create a new GtkWebView object + * + * @return a GtkWidget corresponding to the GtkWebView object + */ +GtkWidget *gtk_webview_new(void); + +/** + * Set the vertical adjustment for the GtkWebView. + * + * @param webview The GtkWebView. + * @param vadj The GtkAdjustment that control the webview. + */ +void gtk_webview_set_vadjustment(GtkWebView *webview, GtkAdjustment *vadj); + +/** + * A very basic routine to append html, which can be considered + * equivalent to a "document.write" using JavaScript. + * + * @param webview The GtkWebView object + * @param markup The html markup to append + */ +void gtk_webview_append_html(GtkWebView *webview, const char *markup); + +/** + * Rather than use webkit_webview_load_string, this routine + * parses and displays the <img id=?> tags that make use of the + * Pidgin imgstore. + * + * @param webview The GtkWebView object + * @param html The HTML content to load + */ +void gtk_webview_load_html_string_with_imgstore(GtkWebView *webview, const char *html); + +/** + * FIXME: (To be changed, right now it just tests whether an append has been + * called since the last clear or since the Widget was created. So it + * does not test for load_string's called in between. + * + * @param webview The GtkWebView object + * + * @return gboolean indicating whether the webview is empty. + */ +gboolean gtk_webview_is_empty(GtkWebView *webview); + +/** + * Execute the JavaScript only after the webkit_webview_load_string + * loads completely. We also guarantee that the scripts are executed + * in the order they are called here. This is useful to avoid race + * conditions when calling JS functions immediately after opening the + * page. + * + * @param webview the GtkWebView object + * @param script the script to execute + */ +void gtk_webview_safe_execute_script(GtkWebView *webview, const char *script); + +/** + * A convenience routine to quote a string for use as a JavaScript + * string. For instance, "hello 'world'" becomes "'hello \\'world\\''" + * + * @param str The string to escape and quote + * + * @return the quoted string. + */ +char *gtk_webview_quote_js_string(const char *str); + +/** + * Scrolls the Webview to the end of its contents. + * + * @param webview The GtkWebView. + * @param smooth A boolean indicating if smooth scrolling should be used. + */ +void gtk_webview_scroll_to_end(GtkWebView *webview, gboolean smooth); + +#endif /* _PIDGIN_WEBVIEW_H_ */ +
--- a/pidgin/plugins/Makefile.am Sun Sep 11 04:19:01 2011 +0000 +++ b/pidgin/plugins/Makefile.am Mon Sep 12 02:42:41 2011 +0000 @@ -1,4 +1,4 @@ -DIST_SUBDIRS = cap disco gestures gevolution musicmessaging perl ticker +DIST_SUBDIRS = adiumthemes cap disco gestures gevolution musicmessaging perl ticker if BUILD_GEVOLUTION GEVOLUTION_DIR = gevolution @@ -16,9 +16,6 @@ PERL_DIR = perl endif -if ENABLE_GESTURES -GESTURE_DIR = gestures -endif SUBDIRS = \ $(CAP_DIR) \ @@ -27,7 +24,8 @@ $(MUSICMESSAGING_DIR) \ $(PERL_DIR) \ disco \ - ticker + ticker \ + adiumthemes plugindir = $(libdir)/pidgin @@ -38,15 +36,12 @@ gtkbuddynote_la_LDFLAGS = -module -avoid-version history_la_LDFLAGS = -module -avoid-version iconaway_la_LDFLAGS = -module -avoid-version -markerline_la_LDFLAGS = -module -avoid-version notify_la_LDFLAGS = -module -avoid-version pidginrc_la_LDFLAGS = -module -avoid-version relnot_la_LDFLAGS = -module -avoid-version sendbutton_la_LDFLAGS = -module -avoid-version spellchk_la_LDFLAGS = -module -avoid-version themeedit_la_LDFLAGS = -module -avoid-version -timestamp_la_LDFLAGS = -module -avoid-version -timestamp_format_la_LDFLAGS = -module -avoid-version vvconfig_la_LDFLAGS = -module -avoid-version xmppconsole_la_LDFLAGS = -module -avoid-version @@ -58,15 +53,12 @@ gtkbuddynote.la \ history.la \ iconaway.la \ - markerline.la \ notify.la \ pidginrc.la \ relnot.la \ sendbutton.la \ spellchk.la \ themeedit.la \ - timestamp.la \ - timestamp_format.la \ xmppconsole.la if USE_VV @@ -84,16 +76,12 @@ gtkbuddynote_la_SOURCES = gtkbuddynote.c history_la_SOURCES = history.c iconaway_la_SOURCES = iconaway.c -markerline_la_SOURCES = markerline.c notify_la_SOURCES = notify.c pidginrc_la_SOURCES = pidginrc.c relnot_la_SOURCES = relnot.c sendbutton_la_SOURCES = sendbutton.c spellchk_la_SOURCES = spellchk.c themeedit_la_SOURCES = themeedit.c themeedit-icon.c themeedit-icon.h -timestamp_la_SOURCES = timestamp.c -timestamp_format_la_SOURCES = timestamp_format.c -vvconfig_la_SOURCES = vvconfig.c xmppconsole_la_SOURCES = xmppconsole.c convcolors_la_LIBADD = $(GTK_LIBS) @@ -103,16 +91,12 @@ gtkbuddynote_la_LIBADD = $(GTK_LIBS) history_la_LIBADD = $(GTK_LIBS) iconaway_la_LIBADD = $(GTK_LIBS) -markerline_la_LIBADD = $(GTK_LIBS) notify_la_LIBADD = $(GTK_LIBS) pidginrc_la_LIBADD = $(GTK_LIBS) relnot_la_LIBADD = $(GLIB_LIBS) sendbutton_la_LIBADD = $(GTK_LIBS) spellchk_la_LIBADD = $(GTK_LIBS) themeedit_la_LIBADD = $(GTK_LIBS) -timestamp_la_LIBADD = $(GTK_LIBS) -timestamp_format_la_LIBADD = $(GTK_LIBS) -vvconfig_la_LIBADD = $(GTK_LIBS) $(GSTREAMER_LIBS) xmppconsole_la_LIBADD = $(GTK_LIBS) endif # PLUGINS @@ -136,6 +120,7 @@ -I$(top_srcdir)/pidgin \ $(DEBUG_CFLAGS) \ $(GTK_CFLAGS) \ + $(WEBKIT_CFLAGS) \ $(GSTREAMER_CFLAGS) \ $(PLUGIN_CFLAGS)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/pidgin/plugins/adiumthemes/Makefile.am Mon Sep 12 02:42:41 2011 +0000 @@ -0,0 +1,30 @@ + +webkittemplatedir = $(datadir)/pidgin/webkit +webkittemplate_DATA = Template.html + +webkitdir = $(libdir)/pidgin + +webkit_la_LDFLAGS = -module -avoid-version + +EXTRA_DIST = $(webkittemplate_DATA) + +if PLUGINS + +webkit_LTLIBRARIES = webkit.la + +webkit_la_SOURCES = webkit.c \ + message-style.h \ + message-style.c + +endif + +webkit_la_LIBADD = $(GTK_LIBS) $(WEBKIT_LIBS) + +AM_CPPFLAGS = \ + -DDATADIR=\"$(datadir)\" \ + -I$(top_srcdir)/libpurple \ + -I$(top_builddir)/libpurple \ + -I$(top_srcdir)/pidgin \ + $(DEBUG_CFLAGS) \ + $(GTK_CFLAGS) \ + $(WEBKIT_CFLAGS)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/pidgin/plugins/adiumthemes/Template.html Mon Sep 12 02:42:41 2011 +0000 @@ -0,0 +1,164 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd"> +<html> +<head> + <meta http-equiv="content-type" content="text/html; charset=utf-8" /> + <base href="%@"> + <script type="text/ecmascript" defer="defer"> + + //Appending new content to the message view + function appendMessage(html) { + shouldScroll = nearBottom(); + + //Remove any existing insertion point + insert = document.getElementById("insert"); + if(insert) insert.parentNode.removeChild(insert); + + //Append the new message to the bottom of our chat block + chat = document.getElementById("Chat"); + range = document.createRange(); + range.selectNode(chat); + documentFragment = range.createContextualFragment(html); + chat.appendChild(documentFragment); + + alignChat(shouldScroll); + } + function appendMessageNoScroll(html) { + //Remove any existing insertion point + insert = document.getElementById("insert"); + if(insert) insert.parentNode.removeChild(insert); + + //Append the new message to the bottom of our chat block + chat = document.getElementById("Chat"); + range = document.createRange(); + range.selectNode(chat); + documentFragment = range.createContextualFragment(html); + chat.appendChild(documentFragment); + } + function appendNextMessage(html){ + shouldScroll = nearBottom(); + + //Locate the insertion point + insert = document.getElementById("insert"); + + //make new node + range = document.createRange(); + range.selectNode(insert.parentNode); + newNode = range.createContextualFragment(html); + + //swap + insert.parentNode.replaceChild(newNode,insert); + + alignChat(shouldScroll); + } + function appendNextMessageNoScroll(html){ + //Locate the insertion point + insert = document.getElementById("insert"); + + //make new node + range = document.createRange(); + range.selectNode(insert.parentNode); + newNode = range.createContextualFragment(html); + + //swap + insert.parentNode.replaceChild(newNode,insert); + } + + //Auto-scroll to bottom. Use nearBottom to determine if a scrollToBottom is desired. + function nearBottom() { + return ( document.body.scrollTop >= ( document.body.offsetHeight - ( window.innerHeight * 1.2 ) ) ); + } + function scrollToBottom() { + document.body.scrollTop = document.body.offsetHeight; + } + + //Dynamically exchange the active stylesheet + function setStylesheet( id, url ) { + code = "<style id=\"" + id + "\" type=\"text/css\" media=\"screen,print\">"; + if( url.length ) code += "@import url( \"" + url + "\" );"; + code += "</style>"; + range = document.createRange(); + head = document.getElementsByTagName( "head" ).item(0); + range.selectNode( head ); + documentFragment = range.createContextualFragment( code ); + head.removeChild( document.getElementById( id ) ); + head.appendChild( documentFragment ); + } + + //Swap an image with its alt-tag text on click, or expand/unexpand an attached image + document.onclick = imageCheck; + function imageCheck() { + node = event.target; + if(node.tagName == 'IMG' && !client.zoomImage(node) && node.alt) { + a = document.createElement('a'); + a.setAttribute('onclick', 'imageSwap(this)'); + a.setAttribute('src', node.getAttribute('src')); + a.className = node.className; + text = document.createTextNode(node.alt); + a.appendChild(text); + node.parentNode.replaceChild(a, node); + } + } + + function imageSwap(node) { + shouldScroll = nearBottom(); + + //Swap the image/text + img = document.createElement('img'); + img.setAttribute('src', node.getAttribute('src')); + img.setAttribute('alt', node.firstChild.nodeValue); + img.className = node.className; + node.parentNode.replaceChild(img, node); + + alignChat(shouldScroll); + } + + //Align our chat to the bottom of the window. If true is passed, view will also be scrolled down + function alignChat(shouldScroll) { + var windowHeight = window.innerHeight; + + if (windowHeight > 0) { + var contentElement = document.getElementById('Chat'); + var contentHeight = contentElement.offsetHeight; + if (windowHeight - contentHeight > 0) { + contentElement.style.position = 'relative'; + contentElement.style.top = (windowHeight - contentHeight) + 'px'; + } else { + contentElement.style.position = 'static'; + } + } + + if (shouldScroll) scrollToBottom(); + } + + function windowDidResize(){ + alignChat(true/*nearBottom()*/); //nearBottom buggy with inactive tabs + } + + window.onresize = windowDidResize; + </script> + + <style type="text/css"> + .actionMessageUserName:before { content:"*"; } + .actionMessageBody:after { content:"*"; } + *{ word-wrap:break-word; } + img.scaledToFitImage { height:auto; width:100%; } + </style> + + <!-- This style is shared by all variants. !--> + <style id="baseStyle" type="text/css" media="screen,print"> + %@ + </style> + + <!-- Although we call this mainStyle for legacy reasons, it's actually the variant style !--> + <style id="mainStyle" type="text/css" media="screen,print"> + @import url( "%@" ); + </style> + +</head> +<body onload="alignChat(true);" style="==bodyBackground=="> +%@ +<div id="Chat"> +</div> +%@ +</body> +</html>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/pidgin/plugins/adiumthemes/message-style.c Mon Sep 12 02:42:41 2011 +0000 @@ -0,0 +1,429 @@ +/* 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 "message-style.h" + +#include <string.h> + +#include <glib.h> + +#include <debug.h> +#include <util.h> + +static void +glist_free_all_string(GList *list) +{ + for (; list; list = g_list_delete_link(list, list)) + g_free(list->data); +} + +static PidginMessageStyle * +pidgin_message_style_new(const char *styledir) +{ + PidginMessageStyle *ret = g_new0(PidginMessageStyle, 1); + + ret->ref_counter = 1; + ret->style_dir = g_strdup(styledir); + + return ret; +} + +void +pidgin_message_style_unref(PidginMessageStyle *style) +{ + if (!style) + return; + g_assert (style->ref_counter > 0); + + style->ref_counter--; + if (style->ref_counter) + return; + + g_free(style->cf_bundle_name); + g_free(style->cf_bundle_identifier); + g_free(style->cf_bundle_get_info_string); + g_free(style->default_font_family); + g_free(style->default_background_color); + g_free(style->image_mask); + g_free(style->default_variant); + + g_free(style->style_dir); + g_free(style->template_path); + + g_free(style->template_html); + g_free(style->incoming_content_html); + g_free(style->outgoing_content_html); + g_free(style->outgoing_next_content_html); + g_free(style->status_html); + g_free(style->basestyle_css); + + g_free(style); +} + +void +pidgin_message_style_save_state(const PidginMessageStyle *style) +{ + char *prefname = g_strdup_printf("/plugins/gtk/adiumthemes/%s", style->cf_bundle_identifier); + char *variant = g_strdup_printf("%s/variant", prefname); + + purple_debug_info("webkit", "saving state with variant %s\n", style->variant); + purple_prefs_add_none(prefname); + purple_prefs_add_string(variant, ""); + purple_prefs_set_string(variant, style->variant); + + g_free(prefname); + g_free(variant); +} + +static void +pidgin_message_style_load_state(PidginMessageStyle *style) +{ + char *prefname = g_strdup_printf("/plugins/gtk/adiumthemes/%s", style->cf_bundle_identifier); + char *variant = g_strdup_printf("%s/variant", prefname); + + const char* value = purple_prefs_get_string(variant); + gboolean changed = !style->variant || !g_str_equal(style->variant, value); + + g_free(style->variant); + style->variant = g_strdup(value); + + if (changed) + pidgin_message_style_read_info_plist(style, style->variant); + + g_free(prefname); + g_free(variant); +} + + +static gboolean +parse_info_plist_key_value(xmlnode* key, gpointer destination, const char* expected) +{ + xmlnode *val = key->next; + + for (; val && val->type != XMLNODE_TYPE_TAG; val = val->next) + ; + if (!val) + return FALSE; + + if (expected == NULL || g_str_equal(expected, "string")) { + char **dest = (char **)destination; + if (!g_str_equal(val->name, "string")) + return FALSE; + if (*dest) + g_free(*dest); + *dest = xmlnode_get_data_unescaped(val); + } else if (g_str_equal(expected, "integer")) { + int *dest = (int *)destination; + char *value = xmlnode_get_data_unescaped(val); + + if (!g_str_equal(val->name, "integer")) + return FALSE; + *dest = atoi(value); + g_free(value); + } else if (g_str_equal(expected, "boolean")) { + gboolean *dest = (gboolean *)destination; + if (g_str_equal(val->name, "true")) + *dest = TRUE; + else if (g_str_equal(val->name, "false")) + *dest = FALSE; + else + return FALSE; + } else return FALSE; + + return TRUE; +} + +static gboolean +str_for_key(const char *key, const char *found, const char *variant) +{ + if (g_str_equal(key, found)) + return TRUE; + if (!variant) + return FALSE; + return (g_str_has_prefix(found, key) + && g_str_has_suffix(found, variant) + && strlen(found) == strlen(key) + strlen(variant) + 1); +} + +/** + * Info.plist should be re-read every time the variant changes, this is because + * the keys that take precedence depend on the value of the current variant. + */ +void +pidgin_message_style_read_info_plist(PidginMessageStyle *style, const char *variant) +{ + /* note that if a variant is used the option:VARIANTNAME takes precedence */ + char *contents = g_build_filename(style->style_dir, "Contents", NULL); + xmlnode *plist = xmlnode_from_file(contents, "Info.plist", "Info.plist", "webkit"), *iter; + xmlnode *dict = xmlnode_get_child(plist, "dict"); + + g_assert (dict); + for (iter = xmlnode_get_child(dict, "key"); iter; iter = xmlnode_get_next_twin(iter)) { + char* key = xmlnode_get_data_unescaped(iter); + gboolean pr = TRUE; + + if (g_str_equal("MessageViewVersion", key)) + pr = parse_info_plist_key_value(iter, &style->message_view_version, "integer"); + else if (g_str_equal("CFBundleName", key)) + pr = parse_info_plist_key_value(iter, &style->cf_bundle_name, "string"); + else if (g_str_equal("CFBundleIdentifier", key)) + pr = parse_info_plist_key_value(iter, &style->cf_bundle_identifier, "string"); + else if (g_str_equal("CFBundleGetInfoString", key)) + pr = parse_info_plist_key_value(iter, &style->cf_bundle_get_info_string, "string"); + else if (str_for_key("DefaultFontFamily", key, variant)) + pr = parse_info_plist_key_value(iter, &style->default_font_family, "string"); + else if (str_for_key("DefaultFontSize", key, variant)) + pr = parse_info_plist_key_value(iter, &style->default_font_size, "integer"); + else if (str_for_key("ShowsUserIcons", key, variant)) + pr = parse_info_plist_key_value(iter, &style->shows_user_icons, "boolean"); + else if (str_for_key("DisableCombineConsecutive", key, variant)) + pr = parse_info_plist_key_value(iter, &style->disable_combine_consecutive, "boolean"); + else if (str_for_key("DefaultBackgroundIsTransparent", key, variant)) + pr = parse_info_plist_key_value(iter, &style->default_background_is_transparent, "boolean"); + else if (str_for_key("DisableCustomBackground", key, variant)) + pr = parse_info_plist_key_value(iter, &style->disable_custom_background, "boolean"); + else if (str_for_key("DefaultBackgroundColor", key, variant)) + pr = parse_info_plist_key_value(iter, &style->default_background_color, "string"); + else if (str_for_key("AllowTextColors", key, variant)) + pr = parse_info_plist_key_value(iter, &style->allow_text_colors, "integer"); + else if (str_for_key("ImageMask", key, variant)) + pr = parse_info_plist_key_value(iter, &style->image_mask, "string"); + + if (!pr) + purple_debug_warning("webkit", "Failed to parse key %s\n", key); + g_free(key); + } + + xmlnode_free(plist); +} + +PidginMessageStyle * +pidgin_message_style_load(const char *styledir) +{ + /* + * the loading process described: + * + * First we load all the style .html files, etc. + * The we load any config options that have been stored for + * this variant. + * Then we load the Info.plist, for the currently decided variant. + * At this point, if we find that variants exist, yet + * we don't have a variant selected, we choose DefaultVariant + * and if that does not exist, we choose the first one in the + * directory. + */ + char *file; + PidginMessageStyle *style = NULL; + + style = pidgin_message_style_new(styledir); + + /* load all other files */ + + /* The template path can either come from the theme, or can + * be stock Template.html that comes with the plugin */ + style->template_path = g_build_filename(styledir, "Contents", "Resources", "Template.html", NULL); + + if (!g_file_test(style->template_path, G_FILE_TEST_EXISTS)) { + g_free(style->template_path); + style->template_path = g_build_filename(DATADIR, "pidgin", "webkit", "Template.html", NULL); + } + + if (!g_file_get_contents(style->template_path, &style->template_html, NULL, NULL)) { + purple_debug_error("webkit", "Could not locate a Template.html (%s)\n", style->template_path); + pidgin_message_style_unref(style); + return NULL; + } + + file = g_build_filename(styledir, "Contents", "Resources", "Status.html", NULL); + if (!g_file_get_contents(file, &style->status_html, NULL, NULL)) { + purple_debug_info("webkit", "%s could not find Resources/Status.html", styledir); + pidgin_message_style_unref(style); + g_free(file); + return NULL; + } + g_free(file); + + file = g_build_filename(styledir, "Contents", "Resources", "main.css", NULL); + if (!g_file_get_contents(file, &style->basestyle_css, NULL, NULL)) + style->basestyle_css = g_strdup(""); + g_free(file); + + file = g_build_filename(styledir, "Contents", "Resources", "Header.html", NULL); + if (!g_file_get_contents(file, &style->header_html, NULL, NULL)) + style->header_html = g_strdup(""); + g_free(file); + + file = g_build_filename(styledir, "Contents", "Resources", "Footer.html", NULL); + if (!g_file_get_contents(file, &style->footer_html, NULL, NULL)) + style->footer_html = g_strdup(""); + g_free(file); + + file = g_build_filename(styledir, "Contents", "Resources", "Incoming", "Content.html", NULL); + if (!g_file_get_contents(file, &style->incoming_content_html, NULL, NULL)) { + purple_debug_info("webkit", "%s did not have a Incoming/Content.html\n", styledir); + pidgin_message_style_unref(style); + g_free(file); + return NULL; + } + g_free(file); + + + /* according to the spec, the following are optional files */ + file = g_build_filename(styledir, "Contents", "Resources", "Incoming", "NextContent.html", NULL); + if (!g_file_get_contents(file, &style->incoming_next_content_html, NULL, NULL)) { + style->incoming_next_content_html = g_strdup(style->incoming_content_html); + } + g_free(file); + + file = g_build_filename(styledir, "Contents", "Resources", "Outgoing", "Content.html", NULL); + if (!g_file_get_contents(file, &style->outgoing_content_html, NULL, NULL)) { + style->outgoing_content_html = g_strdup(style->incoming_content_html); + } + g_free(file); + + file = g_build_filename(styledir, "Contents", "Resources", "Outgoing", "NextContent.html", NULL); + if (!g_file_get_contents(file, &style->outgoing_next_content_html, NULL, NULL)) { + style->outgoing_next_content_html = g_strdup(style->outgoing_content_html); + } + + pidgin_message_style_read_info_plist(style, NULL); + pidgin_message_style_load_state(style); + + /* non variant dependent Info.plist checks */ + if (style->message_view_version < 3) { + purple_debug_info("webkit", "%s is a legacy style (version %d) and will not be loaded\n", style->cf_bundle_name, style->message_view_version); + pidgin_message_style_unref(style); + return NULL; + } + + if (!style->variant) + { + GList *variants = pidgin_message_style_get_variants(style); + + if (variants) + pidgin_message_style_set_variant(style, variants->data); + + glist_free_all_string(variants); + } + + return style; +} + +PidginMessageStyle * +pidgin_message_style_copy(const PidginMessageStyle *style) +{ + PidginMessageStyle *ret = pidgin_message_style_new(style->style_dir); + + ret->variant = g_strdup(style->variant); + ret->message_view_version = style->message_view_version; + ret->cf_bundle_name = g_strdup(style->cf_bundle_name); + ret->cf_bundle_identifier = g_strdup(style->cf_bundle_identifier); + ret->cf_bundle_get_info_string = g_strdup(style->cf_bundle_get_info_string); + ret->default_font_family = g_strdup(style->default_font_family); + ret->default_font_size = style->default_font_size; + ret->shows_user_icons = style->shows_user_icons; + ret->disable_combine_consecutive = style->disable_combine_consecutive; + ret->default_background_is_transparent = style->default_background_is_transparent; + ret->disable_custom_background = style->disable_custom_background; + ret->default_background_color = g_strdup(style->default_background_color); + ret->allow_text_colors = style->allow_text_colors; + ret->image_mask = g_strdup(style->image_mask); + ret->default_variant = g_strdup(style->default_variant); + + ret->template_path = g_strdup(style->template_path); + ret->template_html = g_strdup(style->template_html); + ret->header_html = g_strdup(style->header_html); + ret->footer_html = g_strdup(style->footer_html); + ret->incoming_content_html = g_strdup(style->incoming_content_html); + ret->outgoing_content_html = g_strdup(style->outgoing_content_html); + ret->incoming_next_content_html = g_strdup(style->incoming_next_content_html); + ret->outgoing_next_content_html = g_strdup(style->outgoing_next_content_html); + ret->status_html = g_strdup(style->status_html); + ret->basestyle_css = g_strdup(style->basestyle_css); + return ret; +} + +void +pidgin_message_style_set_variant(PidginMessageStyle *style, const char *variant) +{ + /* I'm not going to test whether this variant is valid! */ + g_free(style->variant); + style->variant = g_strdup(variant); + + pidgin_message_style_read_info_plist(style, variant); + + /* todo, the style has "changed". Ideally, I would like to use signals at this point. */ +} + +char * +pidgin_message_style_get_variant(PidginMessageStyle *style) +{ + return g_strdup(style->variant); +} + +/** + * Get a list of variants supported by the style. + */ +GList* +pidgin_message_style_get_variants(PidginMessageStyle *style) +{ + GList *ret = NULL; + GDir *variants; + const char *css_file; + char *css; + char *variant_dir; + + g_assert(style->style_dir); + variant_dir = g_build_filename(style->style_dir, "Contents", "Resources", "Variants", NULL); + + variants = g_dir_open(variant_dir, 0, NULL); + if (!variants) + return NULL; + + while ((css_file = g_dir_read_name(variants)) != NULL) { + if (!g_str_has_suffix(css_file, ".css")) + continue; + + css = g_strndup(css_file, strlen(css_file) - 4); + ret = g_list_append(ret, css); + } + + g_dir_close(variants); + g_free(variant_dir); + + ret = g_list_sort(ret, (GCompareFunc)g_strcmp0); + return ret; +} + +char * +pidgin_message_style_get_css(PidginMessageStyle *style) +{ + if (!style->variant) { + return g_build_filename(style->style_dir, "Contents", "Resources", "main.css", NULL); + } else { + char *file = g_strdup_printf("%s.css", style->variant); + char *ret = g_build_filename(style->style_dir, "Contents", "Resources", "Variants", file, NULL); + g_free(file); + return ret; + } +} +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/pidgin/plugins/adiumthemes/message-style.h Mon Sep 12 02:42:41 2011 +0000 @@ -0,0 +1,81 @@ +/* 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 <glib.h> + +/* + * I'm going to allow a different style for each PidginConversation. + * This way I can do two things: 1) change the theme on the fly and not + * change existing themes, and 2) Use a different theme for IMs and + * chats. + */ +typedef struct _PidginMessageStyle { + int ref_counter; + + /* current config options */ + char *variant; /* allowed to be NULL if there are no variants */ + + /* Info.plist keys that change with Variant */ + + /* Static Info.plist keys */ + int message_view_version; + char *cf_bundle_name; + char *cf_bundle_identifier; + char *cf_bundle_get_info_string; + char *default_font_family; + int default_font_size; + gboolean shows_user_icons; + gboolean disable_combine_consecutive; + gboolean default_background_is_transparent; + gboolean disable_custom_background; + char *default_background_color; + gboolean allow_text_colors; + char *image_mask; + char *default_variant; + + /* paths */ + char *style_dir; + char *template_path; + + /* caches */ + char *template_html; + char *header_html; + char *footer_html; + char *incoming_content_html; + char *outgoing_content_html; + char *incoming_next_content_html; + char *outgoing_next_content_html; + char *status_html; + char *basestyle_css; +} PidginMessageStyle; + +PidginMessageStyle *pidgin_message_style_load(const char *styledir); +PidginMessageStyle *pidgin_message_style_copy(const PidginMessageStyle *style); +void pidgin_message_style_save_state(const PidginMessageStyle *style); +void pidgin_message_style_unref(PidginMessageStyle *style); +void pidgin_message_style_read_info_plist(PidginMessageStyle *style, const char *variant); +char *pidgin_message_style_get_variant(PidginMessageStyle *style); +GList *pidgin_message_style_get_variants(PidginMessageStyle *style); +void pidgin_message_style_set_variant(PidginMessageStyle *style, const char *variant); + +char *pidgin_message_style_get_css(PidginMessageStyle *style); +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/pidgin/plugins/adiumthemes/webkit.c Mon Sep 12 02:42:41 2011 +0000 @@ -0,0 +1,863 @@ +/* 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 + * + */ + +#define PLUGIN_ID "gtk-webview-adium-ims" +#define PLUGIN_NAME "webview-adium-ims" + +/* + * A lot of this was originally written by Sean Egan, but I think I've + * rewrote enough to replace the author for now. + */ +#define PLUGIN_AUTHOR "Arnold Noronha <arnstein87@gmail.com>" +#define PURPLE_PLUGINS "Hell yeah" + +/* System headers */ +#include <string.h> +#include <gdk/gdk.h> +#include <gtk/gtk.h> + +#include <webkit/webkit.h> + +/* Purple headers */ +#include <conversation.h> +#include <debug.h> +#include <internal.h> +#include <notify.h> +#include <util.h> +#include <version.h> + +/* Pidgin headers */ +#include <gtkconv.h> +#include <gtkplugin.h> +#include <gtkwebview.h> +#include <smileyparser.h> + +#include <libxml/xmlreader.h> + +#include "message-style.h" +/* GObject data keys */ +#define MESSAGE_STYLE_KEY "message-style" + +static char *cur_style_dir = NULL; +static void *handle = NULL; + +static inline char * +get_absolute_path(const char *path) +{ + if (g_path_is_absolute(path)) + return g_strdup(path); + else { + char *cwd, *ret; + cwd = g_get_current_dir(); + ret = g_build_filename(cwd, path, NULL); + g_free(cwd); + return ret; + } +} + +static void webkit_on_webview_destroy(GtkObject* obj, gpointer data); + +static void * +webkit_plugin_get_handle(void) +{ + if (handle) + return handle; + else + return (handle = g_malloc(1)); +} + +static void +webkit_plugin_free_handle(void) +{ + purple_signals_disconnect_by_handle(handle); + g_free(handle); +} + +static char * +replace_message_tokens( + const char *text, + gsize len, + PurpleConversation *conv, + const char *name, + const char *alias, + const char *message, + PurpleMessageFlags flags, + time_t mtime) +{ + GString *str = g_string_new_len(NULL, len); + const char *cur = text; + const char *prev = cur; + + while ((cur = strchr(cur, '%'))) { + const char *replace = NULL; + char *fin = NULL; + + if (!strncmp(cur, "%message%", strlen("%message%"))) { + replace = message; + } else if (!strncmp(cur, "%messageClasses%", strlen("%messageClasses%"))) { + replace = flags & PURPLE_MESSAGE_SEND ? "outgoing" : + flags & PURPLE_MESSAGE_RECV ? "incoming" : "event"; + } else if (!strncmp(cur, "%time", strlen("%time"))) { + char *format = NULL; + if (*(cur + strlen("%time")) == '{') { + const char *start = cur + strlen("%time") + 1; + char *end = strstr(start, "}%"); + if (!end) /* Invalid string */ + continue; + format = g_strndup(start, end - start); + fin = end + 1; + } + replace = purple_utf8_strftime(format ? format : "%X", NULL); + g_free(format); + } else if (!strncmp(cur, "%userIconPath%", strlen("%userIconPath%"))) { + if (flags & PURPLE_MESSAGE_SEND) { + if (purple_account_get_bool(conv->account, "use-global-buddyicon", TRUE)) { + replace = purple_prefs_get_path(PIDGIN_PREFS_ROOT "/accounts/buddyicon"); + } else { + PurpleStoredImage *img = purple_buddy_icons_find_account_icon(conv->account); + replace = purple_imgstore_get_filename(img); + } + if (replace == NULL || !g_file_test(replace, G_FILE_TEST_EXISTS)) { + replace = g_build_filename("Outgoing", "buddy_icon.png", NULL); + } + } else if (flags & PURPLE_MESSAGE_RECV) { + PurpleBuddyIcon *icon = purple_conv_im_get_icon(PURPLE_CONV_IM(conv)); + replace = purple_buddy_icon_get_full_path(icon); + if (replace == NULL || !g_file_test(replace, G_FILE_TEST_EXISTS)) { + replace = g_build_filename("Incoming", "buddy_icon.png", NULL); + } + } + + } else if (!strncmp(cur, "%senderScreenName%", strlen("%senderScreenName%"))) { + replace = name; + } else if (!strncmp(cur, "%sender%", strlen("%sender%"))) { + replace = alias; + } else if (!strncmp(cur, "%service%", strlen("%service%"))) { + replace = purple_account_get_protocol_name(conv->account); + } else { + cur++; + continue; + } + + /* Here we have a replacement to make */ + g_string_append_len(str, prev, cur - prev); + g_string_append(str, replace); + + /* And update the pointers */ + if (fin) { + prev = cur = fin + 1; + } else { + prev = cur = strchr(cur + 1, '%') + 1; + } + + } + + /* And wrap it up */ + g_string_append(str, prev); + return g_string_free(str, FALSE); +} + +static char * +replace_header_tokens(char *text, gsize len, PurpleConversation *conv) +{ + GString *str = g_string_new_len(NULL, len); + char *cur = text; + char *prev = cur; + + if (text == NULL) + return NULL; + + while ((cur = strchr(cur, '%'))) { + const char *replace = NULL; + char *fin = NULL; + + if (!strncmp(cur, "%chatName%", strlen("%chatName%"))) { + replace = conv->name; + } else if (!strncmp(cur, "%sourceName%", strlen("%sourceName%"))) { + replace = purple_account_get_alias(conv->account); + if (replace == NULL) + replace = purple_account_get_username(conv->account); + } else if (!strncmp(cur, "%destinationName%", strlen("%destinationName%"))) { + PurpleBuddy *buddy = purple_find_buddy(conv->account, conv->name); + if (buddy) { + replace = purple_buddy_get_alias(buddy); + } else { + replace = conv->name; + } + } else if (!strncmp(cur, "%incomingIconPath%", strlen("%incomingIconPath%"))) { + PurpleBuddyIcon *icon = purple_conv_im_get_icon(PURPLE_CONV_IM(conv)); + replace = purple_buddy_icon_get_full_path(icon); + } else if (!strncmp(cur, "%outgoingIconPath%", strlen("%outgoingIconPath%"))) { + } else if (!strncmp(cur, "%timeOpened", strlen("%timeOpened"))) { + char *format = NULL; + if (*(cur + strlen("%timeOpened")) == '{') { + char *start = cur + strlen("%timeOpened") + 1; + char *end = strstr(start, "}%"); + if (!end) /* Invalid string */ + continue; + format = g_strndup(start, end - start); + fin = end + 1; + } + replace = purple_utf8_strftime(format ? format : "%X", NULL); + g_free(format); + } else { + continue; + } + + /* Here we have a replacement to make */ + g_string_append_len(str, prev, cur - prev); + g_string_append(str, replace); + + /* And update the pointers */ + if (fin) { + prev = cur = fin + 1; + } else { + prev = cur = strchr(cur + 1, '%') + 1; + } + } + + /* And wrap it up */ + g_string_append(str, prev); + return g_string_free(str, FALSE); +} + +static char * +replace_template_tokens(PidginMessageStyle *style, char *text, int len, char *header, char *footer) +{ + GString *str = g_string_new_len(NULL, len); + + char **ms = g_strsplit(text, "%@", 6); + char *base = NULL; + char *csspath = pidgin_message_style_get_css(style); + if (ms[0] == NULL || ms[1] == NULL || ms[2] == NULL || ms[3] == NULL || ms[4] == NULL || ms[5] == NULL) { + g_strfreev(ms); + g_string_free(str, TRUE); + return NULL; + } + + g_string_append(str, ms[0]); + g_string_append(str, "file://"); + base = g_build_filename(style->style_dir, "Contents", "Resources", "Template.html", NULL); + g_string_append(str, base); + g_free(base); + + g_string_append(str, ms[1]); + + g_string_append(str, style->basestyle_css); + + g_string_append(str, ms[2]); + + g_string_append(str, "file://"); + g_string_append(str, csspath); + + g_string_append(str, ms[3]); + if (header) + g_string_append(str, header); + g_string_append(str, ms[4]); + if (footer) + g_string_append(str, footer); + g_string_append(str, ms[5]); + + g_strfreev(ms); + g_free(csspath); + return g_string_free(str, FALSE); +} + +static GtkWidget * +get_webkit(PurpleConversation *conv) +{ + PidginConversation *gtkconv; + gtkconv = PIDGIN_CONVERSATION(conv); + if (!gtkconv) + return NULL; + else + return gtkconv->webview; +} + +static void +set_theme_webkit_settings(WebKitWebView *webview, PidginMessageStyle *style) +{ + WebKitWebSettings *settings; + + g_object_get(G_OBJECT(webview), "settings", &settings, NULL); + if (style->default_font_family) + g_object_set(G_OBJECT(settings), "default-font-family", style->default_font_family, NULL); + + if (style->default_font_size) + g_object_set(G_OBJECT(settings), "default-font-size", GINT_TO_POINTER(style->default_font_size), NULL); + + /* this does not work :( */ + webkit_web_view_set_transparent(webview, style->default_background_is_transparent); +} + +/* + * The style specification says that if the conversation is a group + * chat then the <div id="Chat"> element will be given a class + * 'groupchat'. I can't add another '%@' in Template.html because + * that breaks style-specific Template.html's. I have to either use libxml + * or conveniently play with WebKit's javascript engine. The javascript + * engine should work, but it's not an identical behavior. + */ +static void +webkit_set_groupchat(GtkWebView *webview) +{ + gtk_webview_safe_execute_script(webview, "document.getElementById('Chat').className = 'groupchat'"); +} + + +/** + * Called when either a new PurpleConversation is created + * or when a PidginConversation changes its active PurpleConversation + * This will not change the theme if the theme is already set. + * (This is to prevent accidental theme changes if a new + * PurpleConversation gets added. + * + * FIXME: it's not at all clear to me as to how + * Adium themes handle the case when the PurpleConversation + * changes. + */ +static void +init_theme_for_webkit(PurpleConversation *conv, char *style_dir) +{ + GtkWidget *webkit = PIDGIN_CONVERSATION(conv)->webview; + char *header, *footer; + char *template; + + char* basedir; + char* baseuri; + PidginMessageStyle *style, *oldStyle; + PidginMessageStyle *copy; + + oldStyle = g_object_get_data(G_OBJECT(webkit), MESSAGE_STYLE_KEY); + if (oldStyle) + return; + + purple_debug_info("webkit", "loading %s\n", style_dir); + style = pidgin_message_style_load(style_dir); + g_assert(style); + g_assert(style->template_html); /* debugging test? */ + + basedir = g_build_filename(style->style_dir, "Contents", "Resources", "Template.html", NULL); + baseuri = g_strdup_printf("file://%s", basedir); + header = replace_header_tokens(style->header_html, strlen(style->header_html), conv); + g_assert(style); + footer = replace_header_tokens(style->footer_html, strlen(style->footer_html), conv); + template = replace_template_tokens(style, style->template_html, strlen(style->template_html) + strlen(style->header_html), header, footer); + + g_assert(template); + + purple_debug_info("webkit", "template: %s\n", template); + + set_theme_webkit_settings(WEBKIT_WEB_VIEW(webkit), style); + webkit_web_view_load_string(WEBKIT_WEB_VIEW(webkit), template, "text/html", "UTF-8", baseuri); + + copy = pidgin_message_style_copy(style); + g_object_set_data(G_OBJECT(webkit), MESSAGE_STYLE_KEY, copy); + + pidgin_message_style_unref(style); + /* I need to unref this style when the webkit object destroys */ + g_signal_connect(G_OBJECT(webkit), "destroy", G_CALLBACK(webkit_on_webview_destroy), copy); + + if (purple_conversation_get_type(conv) == PURPLE_CONV_TYPE_CHAT) + webkit_set_groupchat(GTK_WEBVIEW(webkit)); + g_free(basedir); + g_free(baseuri); + g_free(header); + g_free(footer); + g_free(template); +} + + +/* restore the non theme version of the conversation window */ +static void +finalize_theme_for_webkit(PurpleConversation *conv) +{ + GtkWidget *webview = PIDGIN_CONVERSATION(conv)->webview; + PidginMessageStyle *style = g_object_get_data(G_OBJECT(webview), MESSAGE_STYLE_KEY); + + webkit_web_view_load_string(WEBKIT_WEB_VIEW(webview), "", "text/html", "UTF-8", ""); + + g_object_set_data(G_OBJECT(webview), MESSAGE_STYLE_KEY, NULL); + pidgin_message_style_unref(style); +} + +static void +webkit_on_webview_destroy(GtkObject *object, gpointer data) +{ + pidgin_message_style_unref((PidginMessageStyle *)data); + g_object_set_data(G_OBJECT(object), MESSAGE_STYLE_KEY, NULL); +} + +static gboolean +webkit_on_displaying_im_msg(PurpleAccount *account, + const char* name, + char **pmessage, + PurpleConversation *conv, + PurpleMessageFlags flags, + gpointer data) +{ + GtkWidget *webkit; + char *message = *pmessage; + const char *alias = name; /* FIXME: signal doesn't give me alias */ + char *stripped; + char *message_html; + char *msg; + char *escape; + char *script; + char *func = "appendMessage"; + char *smileyed; + time_t mtime = time(NULL); /* FIXME: this should come from the write_conv calback, but the signal doesn't pass this to me */ + + PurpleMessageFlags old_flags = GPOINTER_TO_INT(purple_conversation_get_data(conv, "webkit-lastflags")); + PidginMessageStyle *style; + + webkit = get_webkit(conv); + stripped = g_strdup(message); + + style = g_object_get_data(G_OBJECT(webkit), MESSAGE_STYLE_KEY); + g_assert(style); + + if (flags & PURPLE_MESSAGE_SEND && old_flags & PURPLE_MESSAGE_SEND) { + message_html = style->outgoing_next_content_html; + func = "appendNextMessage"; + } else if (flags & PURPLE_MESSAGE_SEND) { + message_html = style->outgoing_content_html; + } else if (flags & PURPLE_MESSAGE_RECV && old_flags & PURPLE_MESSAGE_RECV) { + message_html = style->incoming_next_content_html; + func = "appendNextMessage"; + } else if (flags & PURPLE_MESSAGE_RECV) { + message_html = style->incoming_content_html; + } else { + message_html = style->status_html; + } + purple_conversation_set_data(conv, "webkit-lastflags", GINT_TO_POINTER(flags)); + + smileyed = smiley_parse_markup(stripped, conv->account->protocol_id); + msg = replace_message_tokens(message_html, 0, conv, name, alias, smileyed, flags, mtime); + escape = gtk_webview_quote_js_string(msg); + script = g_strdup_printf("%s(%s)", func, escape); + + purple_debug_info("webkit", "JS: %s\n", script); + gtk_webview_safe_execute_script(GTK_WEBVIEW(webkit), script); + + g_free(script); + g_free(smileyed); + g_free(msg); + g_free(stripped); + g_free(escape); + + return TRUE; /* GtkConv should not handle this IM */ +} + +static gboolean +webkit_on_displaying_chat_msg(PurpleAccount *account, + const char *who, + char **message, + PurpleConversation *conv, + PurpleMessageFlags flags, + gpointer userdata) +{ + /* handle exactly like an IM message for now */ + return webkit_on_displaying_im_msg(account, who, message, conv, flags, NULL); +} + +static void +webkit_on_conversation_displayed(PidginConversation *gtkconv, gpointer data) +{ + init_theme_for_webkit(gtkconv->active_conv, cur_style_dir); +} + +static void +webkit_on_conversation_switched(PurpleConversation *conv, gpointer data) +{ + init_theme_for_webkit(conv, cur_style_dir); +} + +static void +webkit_on_conversation_hiding(PidginConversation *gtkconv, gpointer data) +{ + /* + * I'm not sure if I need to do anything here, but let's keep + * this anyway. + */ +} + +static GList * +get_dir_dir_list(const char *dirname) +{ + GList *ret = NULL; + GDir *dir = g_dir_open(dirname, 0, NULL); + const char* subdir; + + if (!dir) return NULL; + while ((subdir = g_dir_read_name(dir))) { + ret = g_list_append(ret, g_build_filename(dirname, subdir, NULL)); + } + + g_dir_close(dir); + return ret; +} + +/** + * Get me a list of all the available themes specified by their + * directories. I don't guarrantee that these are valid themes, just + * that they are in the directories for themes. + */ +static GList * +get_style_directory_list(void) +{ + char *user_dir, *user_style_dir, *global_style_dir; + GList *list1, *list2; + + user_dir = get_absolute_path(purple_user_dir()); + + user_style_dir = g_build_filename(user_dir, "styles", NULL); + global_style_dir = g_build_filename(DATADIR, "pidgin", "styles", NULL); + + list1 = get_dir_dir_list(user_style_dir); + list2 = get_dir_dir_list(global_style_dir); + + g_free(global_style_dir); + g_free(user_style_dir); + g_free(user_dir); + + return g_list_concat(list1, list2); +} + +/** + * use heuristics or previous user options to figure out what + * theme to use as default in this Pidgin instance. + */ +static void +style_set_default(void) +{ + GList *styles = get_style_directory_list(), *iter; + const char *stylepath = purple_prefs_get_string("/plugins/gtk/adiumthemes/stylepath"); + g_assert(cur_style_dir == NULL); + + if (stylepath && *stylepath) + styles = g_list_prepend(styles, g_strdup(stylepath)); + else { + purple_notify_error(handle, _("Webkit themes"), + _("Can't find installed styles"), + _("Please install some theme and verify the installation path")); + + } + + /* pick any one that works. Note that we have first preference + * for the one in the userdir */ + for (iter = styles; iter; iter = g_list_next(iter)) { + PidginMessageStyle *style = pidgin_message_style_load(iter->data); + if (style) { + cur_style_dir = (char *)g_strdup(iter->data); + pidgin_message_style_unref(style); + break; + } + purple_debug_info("webkit", "Style %s is invalid\n", (char *)iter->data); + } + + for (iter = styles; iter; iter = g_list_next(iter)) + g_free(iter->data); + g_list_free(styles); +} + +static gboolean +plugin_load(PurplePlugin *plugin) +{ + style_set_default(); + if (!cur_style_dir) + return FALSE; /* couldn't find a style */ + + purple_signal_connect(pidgin_conversations_get_handle(), + "displaying-im-msg", + webkit_plugin_get_handle(), + PURPLE_CALLBACK(webkit_on_displaying_im_msg), + NULL); + + purple_signal_connect(pidgin_conversations_get_handle(), + "displaying-chat-msg", + webkit_plugin_get_handle(), + PURPLE_CALLBACK(webkit_on_displaying_chat_msg), + NULL); + + purple_signal_connect(pidgin_conversations_get_handle(), + "conversation-displayed", + webkit_plugin_get_handle(), + PURPLE_CALLBACK(webkit_on_conversation_displayed), + NULL); + + purple_signal_connect(pidgin_conversations_get_handle(), + "conversation-switched", + webkit_plugin_get_handle(), + PURPLE_CALLBACK(webkit_on_conversation_switched), + NULL); + + purple_signal_connect(pidgin_conversations_get_handle(), + "conversation-hiding", + webkit_plugin_get_handle(), + PURPLE_CALLBACK(webkit_on_conversation_hiding), + NULL); + + /* finally update each of the existing conversation windows */ + { + GList *list = purple_get_conversations(); + for (;list; list = g_list_next(list)) + init_theme_for_webkit(list->data, cur_style_dir); + + } + return TRUE; +} + +static gboolean +plugin_unload(PurplePlugin *plugin) +{ + GList *list; + + webkit_plugin_free_handle(); + cur_style_dir = NULL; + list = purple_get_conversations(); + while (list) { + finalize_theme_for_webkit(list->data); + list = g_list_next(list); + } + + return TRUE; +} + +/* + * UI config code + */ + +static void +style_changed(GtkWidget *combobox, gpointer null) +{ + char *name = gtk_combo_box_get_active_text(GTK_COMBO_BOX(combobox)); + GtkWidget *dialog; + GList *styles = get_style_directory_list(), *iter; + + /* find the full path for this name, I wish I could store this info in the combobox itself. :( */ + for (iter = styles; iter; iter = g_list_next(iter)) { + char *basename = g_path_get_basename(iter->data); + if (g_str_equal(basename, name)) { + g_free(basename); + break; + } + g_free(basename); + } + + g_assert(iter); + g_free(name); + g_free(cur_style_dir); + cur_style_dir = g_strdup(iter->data);; + purple_prefs_set_string("/plugins/gtk/adiumthemes/stylepath", cur_style_dir); + + /* inform the user that existing conversations haven't changed */ + dialog = gtk_message_dialog_new(NULL, 0, GTK_MESSAGE_INFO, GTK_BUTTONS_CLOSE, "The style for existing conversations have not been changed. Please close and re-open the conversation for the changes to take effect."); + g_assert(dialog); + gtk_widget_show(dialog); + g_signal_connect_swapped(dialog, "response", G_CALLBACK(gtk_widget_destroy), dialog); +} + +static GtkWidget * +get_style_config_frame(void) +{ + GtkWidget *combobox = gtk_combo_box_new_text(); + GList *styles = get_style_directory_list(), *iter; + int index = 0, selected = 0; + + for (iter = styles; iter; iter = g_list_next(iter)) { + PidginMessageStyle *style = pidgin_message_style_load(iter->data); + + if (style) { + char *text = g_path_get_basename(iter->data); + gtk_combo_box_append_text(GTK_COMBO_BOX(combobox), text); + g_free(text); + + if (g_str_equal(iter->data, cur_style_dir)) + selected = index; + index++; + pidgin_message_style_unref(style); + } + } + gtk_combo_box_set_active(GTK_COMBO_BOX(combobox), selected); + g_signal_connect_after(G_OBJECT(combobox), "changed", G_CALLBACK(style_changed), NULL); + return combobox; +} + +static void +variant_update_conversation(PurpleConversation *conv) +{ + PidginConversation *gtkconv = PIDGIN_CONVERSATION(conv); + WebKitWebView *webview = WEBKIT_WEB_VIEW(gtkconv->webview); + PidginMessageStyle *style = (PidginMessageStyle *)g_object_get_data(G_OBJECT(webview), MESSAGE_STYLE_KEY); + char *script; + + g_assert(style); + + script = g_strdup_printf("setStylesheet(\"mainStyle\",\"%s\")", pidgin_message_style_get_css(style)); + gtk_webview_safe_execute_script(GTK_WEBVIEW(webview), script); + + set_theme_webkit_settings(WEBKIT_WEB_VIEW(gtkconv->webview), style); + g_free(script); +} + +static void +variant_changed(GtkWidget* combobox, gpointer null) +{ + char *name; + GList *list; + PidginMessageStyle *style = pidgin_message_style_load(cur_style_dir); + + g_assert(style); + name = gtk_combo_box_get_active_text(GTK_COMBO_BOX(combobox)); + pidgin_message_style_set_variant(style, name); + pidgin_message_style_save_state(style); + + /* update conversations */ + list = purple_get_conversations(); + while (list) { + variant_update_conversation(list->data); + list = g_list_next(list); + } + + g_free(name); + pidgin_message_style_unref(style); +} + +static GtkWidget * +get_variant_config_frame() +{ + PidginMessageStyle *style = pidgin_message_style_load(cur_style_dir); + GList *variants = pidgin_message_style_get_variants(style), *iter; + char *cur_variant = pidgin_message_style_get_variant(style); + GtkWidget *combobox = gtk_combo_box_new_text(); + int def = -1, index = 0; + + pidgin_message_style_unref(style); + + for (iter = variants; iter; iter = g_list_next(iter)) { + gtk_combo_box_append_text(GTK_COMBO_BOX(combobox), iter->data); + + if (g_str_equal(cur_variant, iter->data)) + def = index; + index ++; + + } + + gtk_combo_box_set_active(GTK_COMBO_BOX(combobox), def); + g_signal_connect(G_OBJECT(combobox), "changed", G_CALLBACK(variant_changed), NULL); + + return combobox; +} + +static void +style_changed_reset_variants(GtkWidget* combobox, gpointer table) +{ + /* I hate to do this, I swear. But I don't know how to cleanly clean an existing combobox */ + GtkWidget* variants = g_object_get_data(G_OBJECT(table), "variants-cbox"); + gtk_widget_destroy(variants); + variants = get_variant_config_frame(); + gtk_table_attach_defaults(GTK_TABLE(table), variants, 1, 2, 1, 2); + gtk_widget_show_all(GTK_WIDGET(table)); + + g_object_set_data(G_OBJECT(table), "variants-cbox", variants); +} + +static GtkWidget* +get_config_frame(PurplePlugin* plugin) +{ + GtkWidget *table = gtk_table_new(2, 2, FALSE); + GtkWidget *style_config = get_style_config_frame(); + GtkWidget *variant_config = get_variant_config_frame(); + + gtk_table_attach_defaults(GTK_TABLE(table), gtk_label_new("Message Style"), 0, 1, 0, 1); + gtk_table_attach_defaults(GTK_TABLE(table), style_config, 1, 2, 0, 1); + gtk_table_attach_defaults(GTK_TABLE(table), gtk_label_new("Style Variant"), 0, 1, 1, 2); + gtk_table_attach_defaults(GTK_TABLE(table), variant_config, 1, 2, 1, 2); + + g_object_set_data(G_OBJECT(table), "variants-cbox", variant_config); + /* to clarify, this is a second signal connected on style config */ + g_signal_connect_after(G_OBJECT(style_config), "changed", G_CALLBACK(style_changed_reset_variants), table); + + return table; +} + +PidginPluginUiInfo ui_info = +{ + get_config_frame, + 0, /* page_num (Reserved) */ + + /* padding */ + NULL, + NULL, + NULL, + NULL +}; + + +static PurplePluginInfo info = +{ + PURPLE_PLUGIN_MAGIC, /* Magic */ + PURPLE_MAJOR_VERSION, /* Purple Major Version */ + PURPLE_MINOR_VERSION, /* Purple Minor Version */ + PURPLE_PLUGIN_STANDARD, /* plugin type */ + PIDGIN_PLUGIN_TYPE, /* ui requirement */ + 0, /* flags */ + NULL, /* dependencies */ + PURPLE_PRIORITY_DEFAULT, /* priority */ + + PLUGIN_ID, /* plugin id */ + NULL, /* name */ + "0.1", /* version */ + NULL, /* summary */ + NULL, /* description */ + PLUGIN_AUTHOR, /* author */ + "http://pidgin.im", /* website */ + + plugin_load, /* load */ + plugin_unload, /* unload */ + NULL, /* destroy */ + + &ui_info, /* ui_info */ + NULL, /* extra_info */ + NULL, /* prefs_info */ + NULL, /* actions */ + NULL, /* reserved 1 */ + NULL, /* reserved 2 */ + NULL, /* reserved 3 */ + NULL /* reserved 4 */ +}; + +static void +init_plugin(PurplePlugin *plugin) { + info.name = "Adium IMs"; + info.summary = "Adium-like IMs with Pidgin"; + info.description = "You can chat in Pidgin using Adium's WebKit view."; + + purple_prefs_add_none("/plugins"); + purple_prefs_add_none("/plugins/gtk"); + purple_prefs_add_none("/plugins/gtk/adiumthemes"); + purple_prefs_add_string("/plugins/gtk/adiumthemes/stylepath", ""); +} + +PURPLE_INIT_PLUGIN(webkit, init_plugin, info) +
--- a/pidgin/plugins/history.c Sun Sep 11 04:19:01 2011 +0000 +++ b/pidgin/plugins/history.c Mon Sep 12 02:42:41 2011 +0000 @@ -16,16 +16,17 @@ #include "gtkconv.h" #include "gtkimhtml.h" #include "gtkplugin.h" +#include "gtkwebview.h" #define HISTORY_PLUGIN_ID "gtk-history" #define HISTORY_SIZE (4 * 1024) -static gboolean _scroll_imhtml_to_end(gpointer data) +static gboolean _scroll_webview_to_end(gpointer data) { - GtkIMHtml *imhtml = data; - gtk_imhtml_scroll_to_end(GTK_IMHTML(imhtml), FALSE); - g_object_unref(G_OBJECT(imhtml)); + GtkWebView *webview = data; + gtk_webview_scroll_to_end(GTK_WEBVIEW(webview), FALSE); + g_object_unref(G_OBJECT(webview)); return FALSE; } @@ -39,9 +40,15 @@ guint flags; char *history; PidginConversation *gtkconv; +#if 0 + /* FIXME: WebView has no options */ GtkIMHtmlOptions options = GTK_IMHTML_NO_COLOURS; +#endif char *header; +#if 0 + /* FIXME: WebView has no protocol setting */ char *protocol; +#endif char *escaped_alias; const char *header_date; @@ -116,15 +123,21 @@ history = purple_log_read((PurpleLog*)logs->data, &flags); gtkconv = PIDGIN_CONVERSATION(c); +#if 0 + /* FIXME: WebView has no options */ if (flags & PURPLE_LOG_READ_NO_NEWLINE) options |= GTK_IMHTML_NO_NEWLINE; +#endif +#if 0 + /* FIXME: WebView has no protocol setting */ protocol = g_strdup(gtk_imhtml_get_protocol_name(GTK_IMHTML(gtkconv->imhtml))); gtk_imhtml_set_protocol_name(GTK_IMHTML(gtkconv->imhtml), purple_account_get_protocol_name(((PurpleLog*)logs->data)->account)); +#endif - if (gtk_text_buffer_get_char_count(gtk_text_view_get_buffer(GTK_TEXT_VIEW(gtkconv->imhtml)))) - gtk_imhtml_append_text(GTK_IMHTML(gtkconv->imhtml), "<BR>", options); + if (!gtk_webview_is_empty(GTK_WEBVIEW(gtkconv->webview))) + gtk_webview_append_html(GTK_WEBVIEW(gtkconv->webview), "<BR>"); escaped_alias = g_markup_escape_text(alias, -1); @@ -134,21 +147,24 @@ header_date = purple_date_format_full(localtime(&((PurpleLog *)logs->data)->time)); header = g_strdup_printf(_("<b>Conversation with %s on %s:</b><br>"), escaped_alias, header_date); - gtk_imhtml_append_text(GTK_IMHTML(gtkconv->imhtml), header, options); + gtk_webview_append_html(GTK_WEBVIEW(gtkconv->webview), header); g_free(header); g_free(escaped_alias); g_strchomp(history); - gtk_imhtml_append_text(GTK_IMHTML(gtkconv->imhtml), history, options); + gtk_webview_append_html(GTK_WEBVIEW(gtkconv->webview), history); g_free(history); - gtk_imhtml_append_text(GTK_IMHTML(gtkconv->imhtml), "<hr>", options); + gtk_webview_append_html(GTK_WEBVIEW(gtkconv->webview), "<hr>"); +#if 0 + /* FIXME: WebView has no protocol setting */ gtk_imhtml_set_protocol_name(GTK_IMHTML(gtkconv->imhtml), protocol); g_free(protocol); +#endif - g_object_ref(G_OBJECT(gtkconv->imhtml)); - g_idle_add(_scroll_imhtml_to_end, gtkconv->imhtml); + g_object_ref(G_OBJECT(gtkconv->webview)); + g_idle_add(_scroll_webview_to_end, gtkconv->webview); g_list_foreach(logs, (GFunc)purple_log_free, NULL); g_list_free(logs);
--- a/pidgin/plugins/notify.c Sun Sep 11 04:19:01 2011 +0000 +++ b/pidgin/plugins/notify.c Mon Sep 12 02:42:41 2011 +0000 @@ -303,7 +303,7 @@ attach_signals(PurpleConversation *conv) { PidginConversation *gtkconv = NULL; - GSList *imhtml_ids = NULL, *entry_ids = NULL; + GSList *webview_ids = NULL, *entry_ids = NULL; guint id; gtkconv = PIDGIN_CONVERSATION(conv); @@ -322,9 +322,9 @@ G_CALLBACK(unnotify_cb), conv); entry_ids = g_slist_append(entry_ids, GUINT_TO_POINTER(id)); - id = g_signal_connect(G_OBJECT(gtkconv->imhtml), "focus-in-event", + id = g_signal_connect(G_OBJECT(gtkconv->webview), "focus-in-event", G_CALLBACK(unnotify_cb), conv); - imhtml_ids = g_slist_append(imhtml_ids, GUINT_TO_POINTER(id)); + webview_ids = g_slist_append(webview_ids, GUINT_TO_POINTER(id)); } if (purple_prefs_get_bool("/plugins/gtk/X11/notify/notify_click")) { @@ -334,9 +334,9 @@ G_CALLBACK(unnotify_cb), conv); entry_ids = g_slist_append(entry_ids, GUINT_TO_POINTER(id)); - id = g_signal_connect(G_OBJECT(gtkconv->imhtml), "button-press-event", + id = g_signal_connect(G_OBJECT(gtkconv->webview), "button-press-event", G_CALLBACK(unnotify_cb), conv); - imhtml_ids = g_slist_append(imhtml_ids, GUINT_TO_POINTER(id)); + webview_ids = g_slist_append(webview_ids, GUINT_TO_POINTER(id)); } if (purple_prefs_get_bool("/plugins/gtk/X11/notify/notify_type")) { @@ -345,7 +345,7 @@ entry_ids = g_slist_append(entry_ids, GUINT_TO_POINTER(id)); } - purple_conversation_set_data(conv, "notify-imhtml-signals", imhtml_ids); + purple_conversation_set_data(conv, "notify-webview-signals", webview_ids); purple_conversation_set_data(conv, "notify-entry-signals", entry_ids); return 0; @@ -361,9 +361,9 @@ if (!gtkconv) return; - ids = purple_conversation_get_data(conv, "notify-imhtml-signals"); + ids = purple_conversation_get_data(conv, "notify-webview-signals"); for (l = ids; l != NULL; l = l->next) - g_signal_handler_disconnect(gtkconv->imhtml, GPOINTER_TO_INT(l->data)); + g_signal_handler_disconnect(gtkconv->webview, GPOINTER_TO_INT(l->data)); g_slist_free(ids); ids = purple_conversation_get_data(conv, "notify-entry-signals"); @@ -373,7 +373,7 @@ purple_conversation_set_data(conv, "notify-message-count", GINT_TO_POINTER(0)); - purple_conversation_set_data(conv, "notify-imhtml-signals", NULL); + purple_conversation_set_data(conv, "notify-webview-signals", NULL); purple_conversation_set_data(conv, "notify-entry-signals", NULL); }
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/pidgin/smileyparser.c Mon Sep 12 02:42:41 2011 +0000 @@ -0,0 +1,169 @@ +/* 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 <gtk/gtk.h> +#include <debug.h> +#include "smileyparser.h" +#include <smiley.h> +#include <string.h> +#include "gtkthemes.h" + +static char * +get_fullpath(const char *filename) +{ + if (g_path_is_absolute(filename)) + return g_strdup(filename); + else + return g_build_path(g_get_current_dir(), filename, NULL); +} + +static void +parse_for_shortcut_plaintext(const char *text, const char *shortcut, const char *file, GString *ret) +{ + const char *tmp = text; + + for (;*tmp;) { + const char *end = strstr(tmp, shortcut); + char *path; + char *escaped_path; + + if (end == NULL) { + g_string_append(ret, tmp); + break; + } + path = get_fullpath(file); + escaped_path = g_markup_escape_text(path, -1); + + g_string_append_len(ret, tmp, end-tmp); + g_string_append_printf(ret,"<img alt='%s' src='%s' />", + shortcut, escaped_path); + g_free(path); + g_free(escaped_path); + g_assert(strlen(tmp) >= strlen(shortcut)); + tmp = end + strlen(shortcut); + } +} + +static char * +parse_for_shortcut(const char *markup, const char *shortcut, const char *file) +{ + GString* ret = g_string_new(""); + char *local_markup = g_strdup(markup); + char *escaped_shortcut = g_markup_escape_text(shortcut, -1); + + char *temp = local_markup; + + for (;*temp;) { + char *end = strchr(temp, '<'); + char *end_of_tag; + + if (!end) { + parse_for_shortcut_plaintext(temp, escaped_shortcut, file, ret); + break; + } + + *end = 0; + parse_for_shortcut_plaintext(temp, escaped_shortcut, file, ret); + *end = '<'; + + /* if this is well-formed, then there should be no '>' within + * the tag. TODO: handle a comment tag better :( */ + end_of_tag = strchr(end, '>'); + if (!end_of_tag) { + g_string_append(ret, end); + break; + } + + g_string_append_len(ret, end, end_of_tag - end + 1); + + temp = end_of_tag + 1; + } + g_free(local_markup); + g_free(escaped_shortcut); + return g_string_free(ret, FALSE); +} + +static char * +parse_for_purple_smiley(const char *markup, PurpleSmiley *smiley) +{ + char *file = purple_smiley_get_full_path(smiley); + char *ret = parse_for_shortcut(markup, purple_smiley_get_shortcut(smiley), file); + g_free(file); + return ret; +} + +static char * +parse_for_smiley_list(const char *markup, GHashTable *smileys) +{ + GHashTableIter iter; + char *key, *value; + char *ret = g_strdup(markup); + + g_hash_table_iter_init(&iter, smileys); + while (g_hash_table_iter_next(&iter, (gpointer *)&key, (gpointer *)&value)) + { + char *temp = parse_for_shortcut(ret, key, value); + g_free(ret); + ret = temp; + } + + return ret; +} + +char * +smiley_parse_markup(const char *markup, const char *proto_id) +{ + GList *smileys = purple_smileys_get_all(); + char *temp = g_strdup(markup), *temp2; + struct smiley_list *list; + const char *proto_name = "default"; + + if (proto_id != NULL) { + PurplePlugin *proto; + proto = purple_find_prpl(proto_id); + proto_name = proto->info->name; + } + + /* unnecessarily slow, but lets manage for now. */ + for (; smileys; smileys = g_list_next(smileys)) { + temp2 = parse_for_purple_smiley(temp, PURPLE_SMILEY(smileys->data)); + g_free(temp); + temp = temp2; + } + + /* now for each theme smiley, observe that this does look nasty */ + if (!current_smiley_theme || !(current_smiley_theme->list)) { + purple_debug_warning("smiley", "theme does not exist\n"); + return temp; + } + + for (list = current_smiley_theme->list; list; list = list->next) { + if (g_str_equal(list->sml, proto_name)) { + temp2 = parse_for_smiley_list(temp, list->files); + g_free(temp); + temp = temp2; + } + } + + return temp; +} +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/pidgin/smileyparser.h Mon Sep 12 02:42:41 2011 +0000 @@ -0,0 +1,25 @@ +/* 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 + * + */ + +char * +smiley_parse_markup(const char *markup, const char *sml); +