propagate from branch 'im.pidgin.pidgin' (head 1a50ce0394d22b9a5cac75d0f39003ec12e4fec6) cpw.masca.webkit

Mon, 05 Sep 2011 02:14:18 +0000

author
Jorge Villaseñor <masca@cpw.pidgin.im>
date
Mon, 05 Sep 2011 02:14:18 +0000
branch
cpw.masca.webkit
changeset 32507
5d345f996c80
parent 32324
1a50ce0394d2 (current diff)
parent 32506
74d884939a2c (diff)
child 32508
14e521a11bc3

propagate from branch 'im.pidgin.pidgin' (head 1a50ce0394d22b9a5cac75d0f39003ec12e4fec6)
to branch 'im.pidgin.cpw.masca.webkit' (head 74d884939a2c7277fc3ccd73f761da57a6456e10)

configure.ac file | annotate | diff | comparison | revisions
gaim-uninstalled.pc.in file | annotate | diff | comparison | revisions
gaim.pc.in file | annotate | diff | comparison | revisions
libpurple/gaim-compat.h file | annotate | diff | comparison | revisions
libpurple/protocols/silc10/Makefile.am file | annotate | diff | comparison | revisions
libpurple/protocols/silc10/Makefile.mingw file | annotate | diff | comparison | revisions
libpurple/protocols/silc10/README file | annotate | diff | comparison | revisions
libpurple/protocols/silc10/TODO file | annotate | diff | comparison | revisions
libpurple/protocols/silc10/buddy.c file | annotate | diff | comparison | revisions
libpurple/protocols/silc10/chat.c file | annotate | diff | comparison | revisions
libpurple/protocols/silc10/ft.c file | annotate | diff | comparison | revisions
libpurple/protocols/silc10/ops.c file | annotate | diff | comparison | revisions
libpurple/protocols/silc10/pk.c file | annotate | diff | comparison | revisions
libpurple/protocols/silc10/silc.c file | annotate | diff | comparison | revisions
libpurple/protocols/silc10/silcpurple.h file | annotate | diff | comparison | revisions
libpurple/protocols/silc10/util.c file | annotate | diff | comparison | revisions
libpurple/protocols/silc10/wb.c file | annotate | diff | comparison | revisions
libpurple/protocols/silc10/wb.h file | annotate | diff | comparison | revisions
libpurple/purple-2-uninstalled.pc.in file | annotate | diff | comparison | revisions
libpurple/purple-2.pc.in file | annotate | diff | comparison | revisions
libpurple/purple-3-uninstalled.pc.in file | annotate | diff | comparison | revisions
libpurple/purple-3.pc.in file | annotate | diff | comparison | revisions
libpurple/purple-uninstalled.pc.in file | annotate | diff | comparison | revisions
libpurple/purple.pc.in file | annotate | diff | comparison | revisions
pidgin/Makefile.am file | annotate | diff | comparison | revisions
pidgin/gtkconv.c file | annotate | diff | comparison | revisions
pidgin/gtkconv.h file | annotate | diff | comparison | revisions
pidgin/gtkdialogs.c file | annotate | diff | comparison | revisions
pidgin/gtkdocklet-gtk.c file | annotate | diff | comparison | revisions
pidgin/gtkgaim-compat.h file | annotate | diff | comparison | revisions
pidgin/gtknotify.c file | annotate | diff | comparison | revisions
pidgin/pidgin-2-uninstalled.pc.in file | annotate | diff | comparison | revisions
pidgin/pidgin-2.pc.in file | annotate | diff | comparison | revisions
pidgin/pidgin-3-uninstalled.pc.in file | annotate | diff | comparison | revisions
pidgin/pidgin-3.pc.in file | annotate | diff | comparison | revisions
pidgin/pidgin-uninstalled.pc.in file | annotate | diff | comparison | revisions
pidgin/pidgin.pc.in file | annotate | diff | comparison | revisions
--- a/configure.ac	Sun Sep 04 21:11:24 2011 +0000
+++ b/configure.ac	Mon Sep 05 02:14:18 2011 +0000
@@ -710,6 +710,8 @@
 
 #AC_CHECK_FUNC(wcwidth, [AC_DEFINE([HAVE_WCWIDTH], [1], [Define to 1 if you have wcwidth function.])])
 
+PKG_CHECK_MODULES(WEBKIT, [webkit-1.0 >= 1.1.1]);
+
 dnl #######################################################################
 dnl # Check for LibXML2 (required)
 dnl #######################################################################
@@ -2486,6 +2488,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/pidgin/Makefile.am	Sun Sep 04 21:11:24 2011 +0000
+++ b/pidgin/Makefile.am	Mon Sep 05 02:14:18 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 04 21:11:24 2011 +0000
+++ b/pidgin/gtkconv.c	Mon Sep 05 02:14:18 2011 +0000
@@ -69,6 +69,7 @@
 #include "gtkprivacy.h"
 #include "gtkthemes.h"
 #include "gtkutils.h"
+#include "gtkwebview.h"
 #include "pidginstock.h"
 #include "pidgintooltip.h"
 
@@ -174,6 +175,8 @@
 static GList *away_list = NULL;
 static GList *busy_list = NULL;
 static GList *xa_list = NULL;
+static GList *login_list = NULL;
+static GList *logout_list = NULL;
 static GList *offline_list = NULL;
 static GHashTable *prpl_lists = NULL;
 
@@ -209,7 +212,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];
@@ -313,15 +316,6 @@
 }
 
 static void
-conversation_entry_clear(PidginConversation *gtkconv)
-{
-	GtkIMHtml *imhtml = GTK_IMHTML(gtkconv->entry);
-	gtk_source_undo_manager_begin_not_undoable_action(imhtml->undo_manager);
-	gtk_imhtml_clear(imhtml);
-	gtk_source_undo_manager_end_not_undoable_action(imhtml->undo_manager);
-}
-
-static void
 clear_formatting_cb(GtkIMHtml *imhtml, PidginConversation *gtkconv)
 {
 	default_formatize(gtkconv);
@@ -430,21 +424,29 @@
 	return PURPLE_CMD_RET_OK;
 }
 
-static void clear_conversation_scrollback_cb(PurpleConversation *conv,
-                                             void *data)
+static void clear_conversation_scrollback(PurpleConversation *conv)
 {
 	PidginConversation *gtkconv = NULL;
+	GList *iter;
 
 	gtkconv = PIDGIN_CONVERSATION(conv);
-	if (gtkconv)
-		gtk_imhtml_clear(GTK_IMHTML(gtkconv->imhtml));
-}
-
+
+	webkit_web_view_load_html_string (WEBKIT_WEB_VIEW (gtkconv->webview), "", "");
+	for (iter = gtkconv->convs; iter; iter = iter->next)
+		purple_conversation_clear_message_history(iter->data);
+}
+
+
+static void clear_conversation_scrollback_cb(PurpleConversation *conv,
+		void *data)
+{
+	clear_conversation_scrollback(conv);
+}
 static PurpleCmdRet
 clear_command_cb(PurpleConversation *conv,
                  const char *cmd, char **args, char **error, void *data)
 {
-	purple_conversation_clear_message_history(conv);
+	clear_conversation_scrollback(conv);
 	return PURPLE_CMD_RET_OK;
 }
 
@@ -452,7 +454,7 @@
 clearall_command_cb(PurpleConversation *conv,
                  const char *cmd, char **args, char **error, void *data)
 {
-	purple_conversation_foreach(purple_conversation_clear_message_history);
+	purple_conversation_foreach(clear_conversation_scrollback);
 	return PURPLE_CMD_RET_OK;
 }
 
@@ -619,7 +621,7 @@
 	account = purple_conversation_get_account(conv);
 
 	if (check_for_and_do_command(conv)) {
-		conversation_entry_clear(gtkconv);
+		gtk_imhtml_clear(GTK_IMHTML(gtkconv->entry));
 		return;
 	}
 
@@ -674,7 +676,7 @@
 	g_free(clean);
 	g_free(buf);
 
-	conversation_entry_clear(gtkconv);
+	gtk_imhtml_clear(GTK_IMHTML(gtkconv->entry));
 	gtkconv_set_unseen(gtkconv, PIDGIN_UNSEEN_NONE);
 }
 
@@ -803,9 +805,9 @@
 do_invite(GtkWidget *w, int resp, InviteBuddyInfo *info)
 {
 	const char *buddy, *message;
-	PurpleConversation *conv;
-
-	conv = info->conv;
+	PidginConversation *gtkconv;
+
+	gtkconv = PIDGIN_CONVERSATION(info->conv);
 
 	if (resp == GTK_RESPONSE_OK) {
 		buddy   = gtk_entry_get_text(GTK_ENTRY(info->entry));
@@ -814,8 +816,8 @@
 		if (!g_ascii_strcasecmp(buddy, ""))
 			return;
 
-		serv_chat_invite(purple_conversation_get_gc(conv),
-						 purple_conv_chat_get_id(PURPLE_CONV_CHAT(conv)),
+		serv_chat_invite(purple_conversation_get_gc(info->conv),
+						 purple_conv_chat_get_id(PURPLE_CONV_CHAT(info->conv)),
 						 message, buddy);
 	}
 
@@ -909,6 +911,7 @@
 	InviteBuddyInfo *info = NULL;
 
 	if (invite_dialog == NULL) {
+		PurpleConnection *gc;
 		PidginWindow *gtkwin;
 		GtkWidget *label;
 		GtkWidget *vbox, *hbox;
@@ -921,6 +924,7 @@
 		info = g_new0(InviteBuddyInfo, 1);
 		info->conv = conv;
 
+		gc        = purple_conversation_get_gc(conv);
 		gtkwin    = pidgin_conv_get_window(gtkconv);
 
 		/* Create the new dialog. */
@@ -1045,32 +1049,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. */
 }
 
 /*
@@ -1168,7 +1147,7 @@
 	PurpleConversation *conv;
 
 	conv = pidgin_conv_window_get_active_conversation(win);
-	purple_conversation_clear_message_history(conv);
+	clear_conversation_scrollback(conv);
 }
 
 static void
@@ -1627,7 +1606,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,16 +1617,8 @@
 static void
 menu_last_said_cb(GtkWidget *w, PidginConversation *gtkconv)
 {
-	GtkTextMark *mark;
-	const char *who;
-
-	who = g_object_get_data(G_OBJECT(w), "user_data");
-	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);
-	else
-		g_return_if_reached();
+	/* I don't know what this is! */
+	return;
 }
 
 static GtkWidget *
@@ -1862,10 +1833,10 @@
 		chat_do_im(gtkconv, who);
 	} else if (event->button == 2 && event->type == GDK_BUTTON_PRESS) {
 		/* Move to user's anchor */
-		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);
+		//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);
 	} 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 +1913,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;
@@ -2200,13 +2171,13 @@
 
 		case GDK_Page_Up:
  		case GDK_KP_Page_Up:
-			gtk_imhtml_page_up(GTK_IMHTML(gtkconv->imhtml));
+			//gtk_imhtml_page_up(GTK_IMHTML(gtkconv->imhtml));
 			return TRUE;
 			break;
 
 		case GDK_Page_Down:
  		case GDK_KP_Page_Down:
-			gtk_imhtml_page_down(GTK_IMHTML(gtkconv->imhtml));
+			//gtk_imhtml_page_down(GTK_IMHTML(gtkconv->imhtml));
 			return TRUE;
 			break;
 
@@ -2316,7 +2287,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);
+	//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));
@@ -2681,6 +2652,7 @@
 	PidginConversation *gtkconv = (PidginConversation *)data;
 	PurpleConversation *conv = gtkconv->active_conv;
 	PurpleAccount *account;
+	PurplePluginProtocolInfo *prpl_info = NULL;
 
 	GdkPixbuf *buf;
 	GdkPixbuf *scale;
@@ -3252,11 +3224,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 +3246,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 +3258,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,38 +3677,7 @@
 static void
 update_typing_message(PidginConversation *gtkconv, const char *message)
 {
-	GtkTextBuffer *buffer;
-	GtkTextMark *stmark, *enmark;
-
-	if (g_object_get_data(G_OBJECT(gtkconv->imhtml), "disable-typing-notification"))
-		return;
-
-	buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(gtkconv->imhtml));
-	stmark = gtk_text_buffer_get_mark(buffer, "typing-notification-start");
-	enmark = gtk_text_buffer_get_mark(buffer, "typing-notification-end");
-	if (stmark && enmark) {
-		GtkTextIter start, end;
-		gtk_text_buffer_get_iter_at_mark(buffer, &start, stmark);
-		gtk_text_buffer_get_iter_at_mark(buffer, &end, enmark);
-		gtk_text_buffer_delete_mark(buffer, stmark);
-		gtk_text_buffer_delete_mark(buffer, enmark);
-		gtk_text_buffer_delete(buffer, &start, &end);
-	} else if (message && *message == '\n' && message[1] == ' ' && message[2] == '\0')
-		message = NULL;
-
-#ifdef RESERVE_LINE
-	if (!message)
-		message = "\n ";   /* The blank space is required to avoid a GTK+/Pango bug */
-#endif
-
-	if (message) {
-		GtkTextIter iter;
-		gtk_text_buffer_get_end_iter(buffer, &iter);
-		gtk_text_buffer_create_mark(buffer, "typing-notification-start", &iter, TRUE);
-		gtk_text_buffer_insert_with_tags_by_name(buffer, &iter, message, -1, "TYPING-NOTIFICATION", NULL);
-		gtk_text_buffer_get_end_iter(buffer, &iter);
-		gtk_text_buffer_create_mark(buffer, "typing-notification-end", &iter, TRUE);
-	}
+	/* this is not handled at all */
 }
 
 static void
@@ -3978,7 +3919,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);
@@ -4091,7 +4033,7 @@
 
 	if (is_me) {
 		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);
 	} else {
@@ -4657,7 +4599,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 +4839,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 +4860,7 @@
 {
 	gtk_widget_modify_base(gtkconv->quickfind.entry, GTK_STATE_NORMAL, NULL);
 
-	gtk_imhtml_search_clear(GTK_IMHTML(gtkconv->imhtml));
+	gtk_imhtml_search_clear(GTK_IMHTML(gtkconv->webview));
 	gtk_widget_hide_all(gtkconv->quickfind.container);
 
 	gtk_widget_grab_focus(gtkconv->entry);
@@ -4931,7 +4873,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 (gtk_imhtml_search_find(GTK_IMHTML(gtkconv->webview), gtk_entry_get_text(GTK_ENTRY(entry)))) {
 				gtk_widget_modify_base(gtkconv->quickfind.entry, GTK_STATE_NORMAL, NULL);
 			} else {
 				GdkColor col;
@@ -4984,12 +4926,13 @@
 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;
 	PurpleBuddy *buddy;
 	gboolean chat = (conv->type == PURPLE_CONV_TYPE_CHAT);
+	GtkPolicyType webview_sw_hscroll;
 	int buddyicon_size = 0;
 
 	/* Setup the top part of the pane */
@@ -5081,8 +5024,18 @@
 	g_object_set(rend, "xalign", 0.0, "xpad", 6, "ypad", 0, NULL);
 
 	/* Setup the gtkimhtml widget */
-	frame = pidgin_create_imhtml(FALSE, &gtkconv->imhtml, NULL, &imhtml_sw);
-	gtk_widget_set_size_request(gtkconv->imhtml, -1, 0);
+	/* TODO: create a pidgin_create_webview() function in utils*/
+	webview_sw = gtk_scrolled_window_new (NULL, NULL);
+	gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(webview_sw), GTK_SHADOW_IN);
+        gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (webview_sw), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
+
+	gtkconv->webview = gtk_webview_new ();
+	gtk_webview_set_vadjustment(GTK_WEBVIEW(gtkconv->webview),
+			gtk_scrolled_window_get_vadjustment(GTK_SCROLLED_WINDOW(webview_sw)));
+	gtk_container_add (GTK_CONTAINER (webview_sw), gtkconv->webview);
+	
+	gtk_widget_set_size_request(gtkconv->webview, -1, 0);
+
 	if (chat) {
 		GtkWidget *hpaned;
 
@@ -5093,26 +5046,28 @@
 		hpaned = gtk_hpaned_new();
 		gtk_box_pack_start(GTK_BOX(vbox), hpaned, TRUE, TRUE, 0);
 		gtk_widget_show(hpaned);
-		gtk_paned_pack1(GTK_PANED(hpaned), frame, TRUE, TRUE);
+		gtk_paned_pack1(GTK_PANED(hpaned), webview_sw, TRUE, TRUE);
 
 		/* Now add the userlist */
 		setup_chat_userlist(gtkconv, hpaned);
 	} 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_box_pack_start(GTK_BOX(vbox), webview_sw, TRUE, TRUE, 0);
+	}
+	gtk_widget_show_all(webview_sw);
+
+	gtk_widget_set_name(gtkconv->webview, "pidgin_conv_webview");
+	g_object_set_data(G_OBJECT(gtkconv->webview), "gtkconv", gtkconv);
+
+	gtk_scrolled_window_get_policy(GTK_SCROLLED_WINDOW(webview_sw),
+	                               &webview_sw_hscroll, NULL);
+	gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(webview_sw),
+	                               webview_sw_hscroll, GTK_POLICY_ALWAYS);
+
+	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 +5308,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 +5340,7 @@
 	}
 
 	g_signal_handlers_disconnect_by_func(G_OBJECT(widget), set_typing_font, gtkconv);
+#endif
 }
 
 /**************************************************************************
@@ -5424,9 +5382,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 +5404,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 +5416,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 +5451,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 +5463,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 +5688,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 +5723,8 @@
 	g_free(str);
 
 	return buddytag;
+#endif
+	return NULL;
 }
 
 static void pidgin_conv_calculate_newday(PidginConversation *gtkconv, time_t mtime)
@@ -5826,6 +5781,7 @@
 	PidginConversation *gtkconv;
 	PurpleConnection *gc;
 	PurpleAccount *account;
+	PurplePluginProtocolInfo *prpl_info;
 	int gtk_font_options = 0;
 	int gtk_font_options_all = 0;
 	int max_scrollback_lines;
@@ -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,12 @@
 	}
 	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);
+
+	prpl_info = gc ? PURPLE_PLUGIN_PROTOCOL_INFO(gc->prpl) : NULL;
+
+	/* 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 +5909,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 +5944,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 +6004,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){
 			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 +6068,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(),
@@ -6263,6 +6154,7 @@
 	GtkTreeIter iter;
 	GtkTreeModel *model;
 	GtkTextTag *tag;
+	int f = 1;
 
 	chat    = PURPLE_CONV_CHAT(conv);
 	gtkconv = PIDGIN_CONVERSATION(conv);
@@ -6449,7 +6341,7 @@
 		}
 	}
 
-	if (!add_custom_smiley_for_imhtml(GTK_IMHTML(gtkconv->imhtml), sml, smile))
+	if (!add_custom_smiley_for_imhtml(GTK_IMHTML(gtkconv->webview), sml, smile))
 		return FALSE;
 
 	if (!remote)	/* If it's a local custom smiley, then add it for the entry */
@@ -6463,6 +6355,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 +6391,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 +6435,7 @@
 		g_object_unref(G_OBJECT(smiley->loader));
 		smiley->loader = gdk_pixbuf_loader_new();
 	}
+#endif
 }
 
 static void
@@ -6811,7 +6709,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) ||
@@ -7050,6 +6948,7 @@
 	int size = 0;
 
 	PurpleAccount *account;
+	PurplePluginProtocolInfo *prpl_info = NULL;
 
 	PurpleBuddyIcon *icon;
 
@@ -7066,6 +6965,8 @@
 		return;
 
 	account = purple_conversation_get_account(conv);
+	if(account && account->gc)
+		prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(account->gc->prpl);
 
 	/* Remove the current icon stuff */
 	children = gtk_container_get_children(GTK_CONTAINER(gtkconv->u.im->icon_container));
@@ -7114,6 +7015,7 @@
 
 	if (data == NULL) {
 		icon = purple_conv_im_get_icon(PURPLE_CONV_IM(conv));
+
 		if (icon == NULL)
 		{
 			gtk_widget_set_size_request(gtkconv->u.im->icon_container,
@@ -7122,6 +7024,7 @@
 		}
 
 		data = purple_buddy_icon_get_data(icon, &len);
+
 		if (data == NULL)
 		{
 			gtk_widget_set_size_request(gtkconv->u.im->icon_container,
@@ -7389,7 +7292,7 @@
 		        GTK_CHECK_MENU_ITEM(win->menu.show_timestamps),
 		        (gboolean)GPOINTER_TO_INT(value));
 
-		gtk_imhtml_show_comments(GTK_IMHTML(gtkconv->imhtml),
+		gtk_imhtml_show_comments(GTK_IMHTML(gtkconv->webview),
 			(gboolean)GPOINTER_TO_INT(value));
 	}
 }
@@ -7791,7 +7694,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 +7729,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);
 	}
 
@@ -10020,9 +9923,12 @@
 static void
 conv_placement_by_group(PidginConversation *conv)
 {
+	PurpleConversationType type;
 	PurpleGroup *group = NULL;
 	GList *wl, *cl;
 
+	type = purple_conversation_get_type(conv->active_conv);
+
 	group = conv_get_group(conv);
 
 	/* Go through the list of IMs and find one with this group. */
--- a/pidgin/gtkconv.h	Sun Sep 04 21:11:24 2011 +0000
+++ b/pidgin/gtkconv.h	Mon Sep 05 02:14:18 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 04 21:11:24 2011 +0000
+++ b/pidgin/gtkdialogs.c	Mon Sep 05 02:14:18 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;
@@ -452,10 +451,12 @@
 	gtk_box_pack_start(GTK_BOX(vbox), logo, FALSE, FALSE, 0);
 
 	frame = pidgin_create_imhtml(FALSE, &imhtml, 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_webview_append_html(GTK_WEBVIEW(imhtml), string->str);
 	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);
 
--- a/pidgin/gtklog.c	Sun Sep 04 21:11:24 2011 +0000
+++ b/pidgin/gtklog.c	Mon Sep 05 02:14:18 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);
@@ -655,11 +649,18 @@
 	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);
+	/*
+	sw = gtk_scrolled_window_new (NULL, NULL);
+	gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (sw), GTK_SHADOW_IN);
+	gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(sw), GTK_POLICY_NEVER, GTK_POLICY_ALWAYS);
+
+	lv->web_view = gtk_webview_new ();
+	gtk_container_add (GTK_CONTAINER (sw), lv->web_view);
+	*/
+	frame = pidgin_create_imhtml(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);
 
 	/* Search box **********/
 	hbox = gtk_hbox_new(FALSE, PIDGIN_HIG_BOX_SPACE);
--- a/pidgin/gtklog.h	Sun Sep 04 21:11:24 2011 +0000
+++ b/pidgin/gtklog.h	Mon Sep 05 02:14:18 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 04 21:11:24 2011 +0000
+++ b/pidgin/gtknotify.c	Mon Sep 05 02:14:18 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,8 +818,8 @@
 	GtkWidget *vbox;
 	GtkWidget *label;
 	GtkWidget *button;
-	GtkWidget *imhtml;
-	GtkWidget *frame;
+	GtkWidget *web_view;
+	GtkWidget *scrolled_window;
 	char label_text[2048];
 	char *linked_text, *primary_esc, *secondary_esc;
 
@@ -869,14 +854,18 @@
 	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);
-	gtk_box_pack_start(GTK_BOX(vbox), frame, TRUE, TRUE, 0);
-	gtk_widget_show(frame);
+	/* Add the webview */
+	scrolled_window = gtk_scrolled_window_new (NULL, NULL);
+        gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrolled_window), GTK_SHADOW_IN);
+	gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled_window), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
+
+	web_view = gtk_webview_new ();
+	gtk_container_add (GTK_CONTAINER (scrolled_window), web_view);
+
+	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), scrolled_window, TRUE, TRUE, 0);
+	gtk_widget_show_all(scrolled_window);
 
 	/* Add the Close button. */
 	button = gtk_dialog_add_button(GTK_DIALOG(window), GTK_STOCK_CLOSE, GTK_RESPONSE_CLOSE);
@@ -889,10 +878,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 +1136,11 @@
 	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());
+		g_assert (webview);
+		printf ("%s\n", linked_text);
+		gtk_webview_load_html_string_with_imgstore (GTK_WEBVIEW (webview), linked_text);
 		g_free(linked_text);
 		g_free(key);
 		ui_handle = pinfo->window;
--- a/pidgin/gtkthemes.c	Sun Sep 04 21:11:24 2011 +0000
+++ b/pidgin/gtkthemes.c	Mon Sep 05 02:14:18 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 04 21:11:24 2011 +0000
+++ b/pidgin/gtkthemes.h	Mon Sep 05 02:14:18 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;
 };
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/pidgin/gtkwebview.c	Mon Sep 05 02:14:18 2011 +0000
@@ -0,0 +1,410 @@
+/*
+ * @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
+ * 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);
+	}
+
+	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);
+	printf ("%s\n", html_imged);
+	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);
+	printf ("script: %s\n", script);
+	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 05 02:14:18 2011 +0000
@@ -0,0 +1,143 @@
+/**
+ * @file gtkwebview.h Wrapper over the Gtk WebKitWebView component
+ * @ingroup 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
+ * 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);
+
+/**
+ * (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 calls 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 smoth   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 04 21:11:24 2011 +0000
+++ b/pidgin/plugins/Makefile.am	Mon Sep 05 02:14:18 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
 
@@ -36,17 +34,12 @@
 extplacement_la_LDFLAGS     = -module -avoid-version
 gtk_signals_test_la_LDFLAGS = -module -avoid-version
 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
 
@@ -56,17 +49,12 @@
 	convcolors.la       \
 	extplacement.la     \
 	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
@@ -82,18 +70,12 @@
 extplacement_la_SOURCES     = extplacement.c
 gtk_signals_test_la_SOURCES = gtk-signals-test.c
 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)
@@ -101,18 +83,12 @@
 extplacement_la_LIBADD      = $(GTK_LIBS)
 gtk_signals_test_la_LIBADD  = $(GTK_LIBS)
 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
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/pidgin/plugins/adiumthemes/Makefile.am	Mon Sep 05 02:14:18 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 05 02:14:18 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 05 02:14:18 2011 +0000
@@ -0,0 +1,448 @@
+/*
+ * Adium Message Styles
+ * Copyright (C) 2009  Arnold Noronha <arnstein87@gmail.com>
+ *
+ * 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., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, 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)
+{
+	GList *first = list;
+	for (; list; list = g_list_next (list)) 
+		g_free (list->data);
+	g_list_free (first);
+}
+
+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;
+}
+
+/**
+ * deallocate any memory used for info.plist options 
+ */
+static void
+pidgin_message_style_unset_info_plist (PidginMessageStyle *style)
+{
+	style->message_view_version = 0;
+	g_free (style->cf_bundle_name);
+	style->cf_bundle_name = NULL;
+
+	g_free (style->cf_bundle_identifier);
+	style->cf_bundle_identifier = NULL;
+
+	g_free (style->cf_bundle_get_info_string);
+	style->cf_bundle_get_info_string = NULL;
+
+	g_free (style->default_font_family);
+	style->default_font_family = NULL;
+
+	style->default_font_size = 0;
+	style->shows_user_icons = TRUE;
+	style->disable_combine_consecutive = FALSE;
+	style->default_background_is_transparent = FALSE;
+	style->disable_custom_background = FALSE;
+
+	g_free (style->default_background_color);
+	style->default_background_color = NULL;
+	
+	style->allow_text_colors = TRUE;
+	
+	g_free (style->image_mask);
+	style->image_mask = NULL;
+	g_free (style->default_variant);
+	style->default_variant = NULL;
+}
+
+
+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->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);
+
+	pidgin_message_style_unset_info_plist (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.
+	 */
+	
+	/* is this style already loaded? */
+	char   *file; /* temporary variable */
+	PidginMessageStyle *style = NULL;
+
+	/* else we need to load it */
+	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)
+{
+	/* it's at times like this that I miss C++ */
+	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 05 02:14:18 2011 +0000
@@ -0,0 +1,59 @@
+
+#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 05 02:14:18 2011 +0000
@@ -0,0 +1,860 @@
+/*
+ * Adium Message Styles
+ * Copyright (C) 2009  Arnold Noronha <arnstein87@gmail.com>
+ * Copyright (C) 2007
+ *
+ * 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., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, 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 = g_get_current_dir (), *ret;
+		ret = g_build_filename (cwd, path, NULL);
+		g_free (cwd);
+		return ret;
+	}
+}
+
+static void
+glist_free_all_string (GList *list)
+{
+	GList *first = list;
+	for (; list; list = g_list_next (list))
+		g_free (list->data);
+	g_list_free (first);
+}
+
+static void webkit_on_webview_destroy (GtkObject* obj, gpointer data);
+
+static void* webkit_plugin_get_handle ()
+{
+	if (handle) return handle;
+	else return (handle = g_malloc (1));
+}
+
+static void webkit_plugin_free_handle ()
+{
+	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;
+	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);
+
+	PidginMessageStyle *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;
+
+	fprintf (stderr, "hmm.. here %s %s\n", name, message);
+	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 ()
+{
+	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 ()
+{
+	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 ()
+{
+	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)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/pidgin/smileyparser.c	Mon Sep 05 02:14:18 2011 +0000
@@ -0,0 +1,144 @@
+
+#include <gtk/gtk.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)) {
+		printf ("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 05 02:14:18 2011 +0000
@@ -0,0 +1,3 @@
+
+char*
+smiley_parse_markup (const char* markup, const char* sml);

mercurial