changeset 32576:7c346c5a05d4

propagate from branch 'im.pidgin.pidgin' (head 1bcd0bb175fd417fd165aa7b52e219c97b0e30c6) to branch 'im.pidgin.soc.2009.webkitmessageview' (head 75ffceeee02a5b5f52aac4008a35ac04f5f5a5f4)
author tdrhq@soc.pidgin.im
date Wed, 14 Oct 2009 02:41:11 +0000
parents 5550823608cb (current diff) 4b53d875529f (diff)
children 994e8d214754
files configure.ac pidgin/Makefile.am pidgin/gtkconv.c pidgin/gtkdialogs.c pidgin/plugins/Makefile.am
diffstat 20 files changed, 2335 insertions(+), 554 deletions(-) [+]
line wrap: on
line diff
--- a/configure.ac	Tue Oct 13 19:52:35 2009 +0000
+++ b/configure.ac	Wed Oct 14 02:41:11 2009 +0000
@@ -703,6 +703,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 #######################################################################
@@ -2500,6 +2502,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	Tue Oct 13 19:52:35 2009 +0000
+++ b/pidgin/Makefile.am	Wed Oct 14 02:41:11 2009 +0000
@@ -125,9 +125,11 @@
 	gtkstatusbox.c \
 	gtkthemes.c \
 	gtkutils.c \
+	gtkwebview.c \
 	gtkwhiteboard.c \
 	minidialog.c \
-	pidgintooltip.c
+	pidgintooltip.c \
+	smileyparser.c
 
 pidgin_headers = \
 	eggtrayicon.h \
@@ -183,10 +185,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 = \
@@ -206,6 +210,7 @@
 	$(GTKSPELL_LIBS) \
 	$(STARTUP_NOTIFICATION_LIBS) \
 	$(LIBXML_LIBS) \
+	$(WEBKIT_LIBS) \
 	$(GTK_LIBS) \
 	$(top_builddir)/libpurple/libpurple.la
 
@@ -230,5 +235,6 @@
 	$(GTKSPELL_CFLAGS) \
 	$(STARTUP_NOTIFICATION_CFLAGS) \
 	$(LIBXML_CFLAGS) \
+	$(WEBKIT_CFLAGS) \
 	$(INTGG_CFLAGS)
 endif  # ENABLE_GTK
--- a/pidgin/gtkconv.c	Tue Oct 13 19:52:35 2009 +0000
+++ b/pidgin/gtkconv.c	Wed Oct 14 02:41:11 2009 +0000
@@ -69,6 +69,7 @@
 #include "gtkprivacy.h"
 #include "gtkthemes.h"
 #include "gtkutils.h"
+#include "gtkwebview.h"
 #include "pidginstock.h"
 #include "pidgintooltip.h"
 
@@ -164,7 +165,6 @@
 gboolean pidgin_conv_has_focus(PurpleConversation *conv);
 static GdkColor* generate_nick_colors(guint *numcolors, GdkColor background);
 static gboolean color_is_visible(GdkColor foreground, GdkColor background, int color_contrast, int brightness_contrast);
-static GtkTextTag *get_buddy_tag(PurpleConversation *conv, const char *who, PurpleMessageFlags flag, gboolean create);
 static void pidgin_conv_update_fields(PurpleConversation *conv, PidginConvFields fields);
 static void focus_out_from_menubar(GtkWidget *wid, PidginWindow *win);
 static void pidgin_conv_tab_pack(PidginWindow *win, PidginConversation *gtkconv);
@@ -178,7 +178,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];
@@ -373,7 +373,7 @@
 
 	gtkconv = PIDGIN_CONVERSATION(conv);
 
-	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);
 }
@@ -987,32 +987,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. */
 }
 
 /*
@@ -1142,12 +1117,16 @@
 
 				if (gtkconv != gtk_active_conv)
 				{
-					gtk_imhtml_search_clear(GTK_IMHTML(gtkconv->imhtml));
+					webkit_web_view_unmark_text_matches (WEBKIT_WEB_VIEW (gtkconv->webview));
 				}
 				else
 				{
-					gtk_imhtml_search_find(GTK_IMHTML(gtk_active_conv->imhtml),
-					                       gtk_entry_get_text(GTK_ENTRY(s->entry)));
+					webkit_web_view_mark_text_matches (WEBKIT_WEB_VIEW (gtkconv->webview), 
+									   gtk_entry_get_text (GTK_ENTRY(s->entry)), TRUE, 0);
+					webkit_web_view_set_highlight_text_matches (WEBKIT_WEB_VIEW (gtkconv->webview),
+										    true);
+					webkit_web_view_search_text (WEBKIT_WEB_VIEW (gtkconv->webview),
+								     gtk_entry_get_text (GTK_ENTRY(s->entry)), FALSE, TRUE, FALSE);
 				}
 			}
 			break;
@@ -1158,7 +1137,7 @@
 			for (iter = pidgin_conv_window_get_gtkconvs(s->gtkwin); iter; iter=iter->next)
 			{
 				PidginConversation *gconv = iter->data;
-				gtk_imhtml_search_clear(GTK_IMHTML(gconv->imhtml));
+				webkit_web_view_unmark_text_matches (WEBKIT_WEB_VIEW(gconv->webview));
 			}
 
 			gtk_widget_destroy(s->gtkwin->dialogs.search);
@@ -1661,30 +1640,11 @@
 	gtk_widget_grab_focus(PIDGIN_CONVERSATION(conv)->entry);
 }
 
-static GtkTextMark *
-get_mark_for_user(PidginConversation *gtkconv, const char *who)
-{
-	GtkTextBuffer *buf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(gtkconv->imhtml));
-	char *tmp = g_strconcat("user:", who, NULL);
-	GtkTextMark *mark = gtk_text_buffer_get_mark(buf, tmp);
-
-	g_free(tmp);
-	return mark;
-}
-
 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 *
@@ -1799,8 +1759,6 @@
 	button = pidgin_new_item_from_stock(menu, _("Last Said"), GTK_STOCK_INDEX,
 						G_CALLBACK(menu_last_said_cb), PIDGIN_CONVERSATION(conv), 0, 0, NULL);
 	g_object_set_data_full(G_OBJECT(button), "user_data", g_strdup(who), g_free);
-	if (!get_mark_for_user(PIDGIN_CONVERSATION(conv), who))
-		gtk_widget_set_sensitive(button, FALSE);
 
 	if (buddy != NULL)
 	{
@@ -1890,10 +1848,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,
@@ -1969,8 +1927,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;
@@ -2226,12 +2184,12 @@
 			break;
 
 		case GDK_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:
-			gtk_imhtml_page_down(GTK_IMHTML(gtkconv->imhtml));
+			//gtk_imhtml_page_down(GTK_IMHTML(gtkconv->imhtml));
 			return TRUE;
 			break;
 
@@ -2334,7 +2292,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));
@@ -3278,11 +3236,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));
@@ -3300,7 +3258,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 {
@@ -3312,15 +3270,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);
 			}
 		}
@@ -3728,38 +3686,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
@@ -4076,7 +4003,6 @@
 	gboolean is_buddy;
 	gchar *tmp, *alias_key, *name, *alias;
 	int flags;
-	GdkColor *color = NULL;
 
 	alias = cb->alias;
 	name  = cb->name;
@@ -4103,20 +4029,6 @@
 	alias_key = g_utf8_collate_key(tmp, -1);
 	g_free(tmp);
 
-	if (is_me) {
-		GtkTextTag *tag = gtk_text_tag_table_lookup(
-				gtk_text_buffer_get_tag_table(GTK_IMHTML(gtkconv->imhtml)->text_buffer),
-				"send-name");
-		g_object_get(tag, "foreground-gdk", &color, NULL);
-	} else {
-		GtkTextTag *tag;
-		if ((tag = get_buddy_tag(conv, name, 0, FALSE)))
-			g_object_set(G_OBJECT(tag), "style", PANGO_STYLE_NORMAL, NULL);
-		if ((tag = get_buddy_tag(conv, name, PURPLE_MESSAGE_NICK, FALSE)))
-			g_object_set(G_OBJECT(tag), "style", PANGO_STYLE_NORMAL, NULL);
-		color = (GdkColor*)get_nick_color(gtkconv, name);
-	}
-
 #if GTK_CHECK_VERSION(2,6,0)
 	gtk_list_store_insert_with_values(ls, &iter,
 /*
@@ -4132,7 +4044,6 @@
 			CHAT_USERS_ALIAS_KEY_COLUMN, alias_key,
 			CHAT_USERS_NAME_COLUMN,  name,
 			CHAT_USERS_FLAGS_COLUMN, flags,
-			CHAT_USERS_COLOR_COLUMN, color,
 			CHAT_USERS_WEIGHT_COLUMN, is_buddy ? PANGO_WEIGHT_BOLD : PANGO_WEIGHT_NORMAL,
 			-1);
 #else
@@ -4143,13 +4054,10 @@
 			CHAT_USERS_ALIAS_KEY_COLUMN, alias_key,
 			CHAT_USERS_NAME_COLUMN,  name,
 			CHAT_USERS_FLAGS_COLUMN, flags,
-			CHAT_USERS_COLOR_COLUMN, color,
 			CHAT_USERS_WEIGHT_COLUMN, is_buddy ? PANGO_WEIGHT_BOLD : PANGO_WEIGHT_NORMAL,
 			-1);
 #endif
 
-	if (is_me && color)
-		gdk_color_free(color);
 	g_free(alias_key);
 }
 
@@ -4547,7 +4455,6 @@
 	GtkTreeModel *model;
 	char *normalized_name;
 	GtkTreeIter iter;
-	GtkTextTag *texttag;
 	int f;
 
 	g_return_if_fail(buddy != NULL);
@@ -4586,10 +4493,6 @@
 
 	blist_node_aliased_cb((PurpleBlistNode *)buddy, NULL, conv);
 
-	texttag = get_buddy_tag(conv, purple_buddy_get_name(buddy), 0, FALSE); /* XXX: do we want the normalized name? */
-	if (texttag) {
-		g_object_set(texttag, "weight", is_buddy ? PANGO_WEIGHT_BOLD : PANGO_WEIGHT_NORMAL, NULL);
-	}
 }
 
 static void
@@ -4621,7 +4524,7 @@
 }
 
 static void
-entry_popup_menu_cb(GtkIMHtml *imhtml, GtkMenu *menu, gpointer data)
+entry_popup_menu_cb(GtkIMHtml *imhtml /* this is for ->entry, fine! */, GtkMenu *menu, gpointer data)
 {
 	GtkWidget *menuitem;
 	PidginConversation *gtkconv = data;
@@ -4649,7 +4552,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;
@@ -4888,13 +4791,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
 	}
 
@@ -4906,13 +4809,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 imhtml_sw_hscroll;
+	GtkPolicyType webview_sw_hscroll;
 	int buddyicon_size = 0;
 
 	/* Setup the top part of the pane */
@@ -5008,8 +4911,15 @@
 	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);
+	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_container_add (GTK_CONTAINER (webview_sw), gtkconv->webview);
+	
+	gtk_widget_set_size_request(gtkconv->webview, -1, 0);
+
 	if (chat) {
 		GtkWidget *hpaned;
 
@@ -5020,29 +4930,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);
-
-	gtk_scrolled_window_get_policy(GTK_SCROLLED_WINDOW(imhtml_sw),
-	                               &imhtml_sw_hscroll, NULL);
-	gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(imhtml_sw),
-	                               imhtml_sw_hscroll, GTK_POLICY_ALWAYS);
-
-	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);
 
 	gtkconv->lower_hbox = gtk_hbox_new(FALSE, PIDGIN_HIG_BOX_SPACE);
@@ -5281,36 +5190,6 @@
 
 static void set_typing_font(GtkWidget *widget, GtkStyle *style, PidginConversation *gtkconv)
 {
-	static PangoFontDescription *font_desc = NULL;
-	static GdkColor *color = NULL;
-	static gboolean enable = TRUE;
-
-	if (font_desc == NULL) {
-		char *string = NULL;
-		gtk_widget_style_get(widget,
-				"typing-notification-font", &string,
-				"typing-notification-color", &color,
-				"typing-notification-enable", &enable,
-				NULL);
-		font_desc = pango_font_description_from_string(string);
-		g_free(string);
-		if (color == NULL) {
-			GdkColor def = {0, 0x8888, 0x8888, 0x8888};
-			color = gdk_color_copy(&def);
-		}
-	}
-
-	gtk_text_buffer_create_tag(GTK_IMHTML(widget)->text_buffer, "TYPING-NOTIFICATION",
-			"foreground-gdk", color,
-			"font-desc", font_desc,
-			NULL);
-
-	if (!enable) {
-		g_object_set_data(G_OBJECT(widget), "disable-typing-notification", GINT_TO_POINTER(TRUE));
-		/* or may be 'gtkconv->disable_typing = TRUE;' instead? */
-	}
-
-	g_signal_handlers_disconnect_by_func(G_OBJECT(widget), set_typing_font, gtkconv);
 }
 
 /**************************************************************************
@@ -5352,9 +5231,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);
@@ -5377,7 +5253,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);
 
@@ -5389,12 +5265,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);
@@ -5424,10 +5300,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),
@@ -5440,7 +5312,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)
@@ -5647,43 +5519,6 @@
 	return FALSE;
 }
 
-static GtkTextTag *get_buddy_tag(PurpleConversation *conv, const char *who, PurpleMessageFlags flag,
-		gboolean create)
-{
-	PidginConversation *gtkconv = PIDGIN_CONVERSATION(conv);
-	GtkTextTag *buddytag;
-	gchar *str;
-	gboolean highlight = (flag & PURPLE_MESSAGE_NICK);
-	GtkTextBuffer *buffer = GTK_IMHTML(gtkconv->imhtml)->text_buffer;
-
-	str = g_strdup_printf(highlight ? "HILIT %s" : "BUDDY %s", who);
-
-	buddytag = gtk_text_tag_table_lookup(
-			gtk_text_buffer_get_tag_table(buffer), str);
-
-	if (buddytag == NULL && create) {
-		if (highlight)
-			buddytag = gtk_text_buffer_create_tag(buffer, str,
-					"foreground", get_text_tag_color(gtk_text_tag_table_lookup(
-							gtk_text_buffer_get_tag_table(buffer), "highlight-name")),
-					"weight", PANGO_WEIGHT_BOLD,
-					NULL);
-		else
-			buddytag = gtk_text_buffer_create_tag(
-					buffer, str,
-					"foreground-gdk", get_nick_color(gtkconv, who),
-					"weight", purple_find_buddy(purple_conversation_get_account(conv), who) ? PANGO_WEIGHT_BOLD : PANGO_WEIGHT_NORMAL,
-					NULL);
-
-		g_signal_connect(G_OBJECT(buddytag), "event",
-				G_CALLBACK(buddytag_event), conv);
-	}
-
-	g_free(str);
-
-	return buddytag;
-}
-
 static void pidgin_conv_calculate_newday(PidginConversation *gtkconv, time_t mtime)
 {
 	struct tm *tm = localtime(&mtime);
@@ -5740,10 +5575,6 @@
 	PurpleConnection *gc;
 	PurpleAccount *account;
 	PurplePluginProtocolInfo *prpl_info;
-	int gtk_font_options = 0;
-	int gtk_font_options_all = 0;
-	int max_scrollback_lines;
-	int line_count;
 	char buf2[BUF_LONG];
 	gboolean show_date;
 	char *mdate;
@@ -5754,8 +5585,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);
@@ -5813,59 +5642,13 @@
 	}
 	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);
-	}
 
 	win = gtkconv->win;
 	prpl_info = gc ? PURPLE_PLUGIN_PROTOCOL_INFO(gc->prpl) : NULL;
 
-	line_count = gtk_text_buffer_get_line_count(
-			gtk_text_view_get_buffer(GTK_TEXT_VIEW(
-				gtkconv->imhtml)));
-
-	max_scrollback_lines = purple_prefs_get_int(
-		PIDGIN_PREFS_ROOT "/conversations/scrollback_lines");
-	/* If we're sitting at more than 100 lines more than the
-	   max scrollback, trim down to max scrollback */
-	if (max_scrollback_lines > 0
-			&& line_count > (max_scrollback_lines + 100)) {
-		GtkTextBuffer *text_buffer = gtk_text_view_get_buffer(
-			GTK_TEXT_VIEW(gtkconv->imhtml));
-		GtkTextIter start, end;
-
-		gtk_text_buffer_get_start_iter(text_buffer, &start);
-		gtk_text_buffer_get_iter_at_line(text_buffer, &end,
-			(line_count - max_scrollback_lines));
-		gtk_imhtml_delete(GTK_IMHTML(gtkconv->imhtml), &start, &end);
-	}
-
-	if (type == PURPLE_CONV_TYPE_CHAT)
-	{
-		/* Create anchor for user */
-		GtkTextIter iter;
-		char *tmp = g_strconcat("user:", name, NULL);
-
-		gtk_text_buffer_get_end_iter(gtk_text_view_get_buffer(GTK_TEXT_VIEW(gtkconv->imhtml)), &iter);
-		gtk_text_buffer_create_mark(gtk_text_view_get_buffer(GTK_TEXT_VIEW(gtkconv->imhtml)),
-								tmp, &iter, TRUE);
-		g_free(tmp);
-	}
-
-	if (purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/conversations/use_smooth_scrolling"))
-		gtk_font_options_all |= GTK_IMHTML_USE_SMOOTHSCROLLING;
-
-	if (gtk_text_buffer_get_char_count(gtk_text_view_get_buffer(GTK_TEXT_VIEW(gtkconv->imhtml))))
-		gtk_imhtml_append_text(GTK_IMHTML(gtkconv->imhtml), "<BR>", gtk_font_options_all | GTK_IMHTML_NO_SCROLL);
+	/* if the buffer is not empty add a <br> */
+	if (!gtk_webview_is_empty (GTK_WEBVIEW(gtkconv->webview)))
+		gtk_webview_append_html (GTK_WEBVIEW(gtkconv->webview), "<br />");
 
 	/* First message in a conversation. */
 	if (gtkconv->newday == 0)
@@ -5901,47 +5684,29 @@
 
 	sml_attrib = g_strdup_printf("sml=\"%s\"", purple_account_get_protocol_name(account));
 
-	gtk_font_options |= GTK_IMHTML_NO_COMMENTS;
-
-	if ((flags & PURPLE_MESSAGE_RECV) &&
-			!purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/conversations/show_incoming_formatting"))
-		gtk_font_options |= GTK_IMHTML_NO_COLOURS | GTK_IMHTML_NO_FONTS | GTK_IMHTML_NO_SIZES | GTK_IMHTML_NO_FORMATTING;
-
-	/* this is gonna crash one day, I can feel it. */
-	if (PURPLE_PLUGIN_PROTOCOL_INFO(purple_find_prpl(purple_account_get_protocol_id(conv->account)))->options &
-	    OPT_PROTO_USE_POINTSIZE) {
-		gtk_font_options |= GTK_IMHTML_USE_POINTSIZE;
-	}
-
-	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);
-	}
-
 	/* 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(""));
@@ -5951,11 +5716,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);
@@ -6016,55 +5776,27 @@
 
 		g_free(alias_escaped);
 
-		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);
-			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);
+		/* timestamp */ 
+		{
+			g_snprintf (buf2, BUF_LONG, "<font size='2'>%s </font>", mdate);
+
+			gtk_webview_append_html (GTK_WEBVIEW(gtkconv->webview), buf2);
+		}
+		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);
@@ -6091,12 +5823,6 @@
 		gtkconv_set_unseen(gtkconv, unseen);
 	}
 
-	if (!(flags & PURPLE_MESSAGE_RECV) && (conv->features & PURPLE_CONNECTION_ALLOW_CUSTOM_SMILEY))
-	{
-		/* Restore the smiley-data */
-		pidgin_themes_smiley_themeize(gtkconv->imhtml);
-	}
-
 	purple_signal_emit(pidgin_conversations_get_handle(),
 		(type == PURPLE_CONV_TYPE_IM ? "displayed-im-msg" : "displayed-chat-msg"),
 		account, name, displaying, conv, flags);
@@ -6188,10 +5914,6 @@
 		g_free(val);
 	}
 
-	if ((tag = get_buddy_tag(conv, old_name, 0, FALSE)))
-		g_object_set(G_OBJECT(tag), "style", PANGO_STYLE_ITALIC, NULL);
-	if ((tag = get_buddy_tag(conv, old_name, PURPLE_MESSAGE_NICK, FALSE)))
-		g_object_set(G_OBJECT(tag), "style", PANGO_STYLE_ITALIC, NULL);
 
 	if (!purple_conv_chat_find_user(chat, old_name))
 		return;
@@ -6249,10 +5971,7 @@
 			g_free(val);
 		} while (f);
 
-		if ((tag = get_buddy_tag(conv, l->data, 0, FALSE)))
-			g_object_set(G_OBJECT(tag), "style", PANGO_STYLE_ITALIC, NULL);
-		if ((tag = get_buddy_tag(conv, l->data, PURPLE_MESSAGE_NICK, FALSE)))
-			g_object_set(G_OBJECT(tag), "style", PANGO_STYLE_ITALIC, NULL);
+
 	}
 
 	g_snprintf(tmp, sizeof(tmp),
@@ -6356,99 +6075,17 @@
 	return TRUE;
 }
 
-static gboolean
-pidgin_conv_custom_smiley_add(PurpleConversation *conv, const char *smile, gboolean remote)
-{
-	PidginConversation *gtkconv;
-	struct smiley_list *list;
-	const char *sml = NULL, *conv_sml;
-
-	if (!conv || !smile || !*smile) {
-		return FALSE;
-	}
-
-	/* If smileys are off, return false */
-	if (pidgin_themes_smileys_disabled())
-		return FALSE;
-
-	/* If possible add this smiley to the current theme.
-	 * The addition is only temporary: custom smilies aren't saved to disk. */
-	conv_sml = purple_account_get_protocol_name(conv->account);
-	gtkconv = PIDGIN_CONVERSATION(conv);
-
-	for (list = (struct smiley_list *)current_smiley_theme->list; list; list = list->next) {
-		if (!strcmp(list->sml, conv_sml)) {
-			sml = list->sml;
-			break;
-		}
-	}
-
-	if (!add_custom_smiley_for_imhtml(GTK_IMHTML(gtkconv->imhtml), sml, smile))
-		return FALSE;
-
-	if (!remote)	/* If it's a local custom smiley, then add it for the entry */
-		if (!add_custom_smiley_for_imhtml(GTK_IMHTML(gtkconv->entry), sml, smile))
-			return FALSE;
-
-	return TRUE;
-}
-
 static void
 pidgin_conv_custom_smiley_write(PurpleConversation *conv, const char *smile,
                                       const guchar *data, gsize size)
 {
-	PidginConversation *gtkconv;
-	GtkIMHtmlSmiley *smiley;
-	GdkPixbufLoader *loader;
-	const char *sml;
-
-	sml = purple_account_get_protocol_name(conv->account);
-	gtkconv = PIDGIN_CONVERSATION(conv);
-	smiley = gtk_imhtml_smiley_get(GTK_IMHTML(gtkconv->imhtml), sml, smile);
-
-	if (!smiley)
-		return;
-
-	smiley->data = g_realloc(smiley->data, smiley->datasize + size);
-	g_memmove((guchar *)smiley->data + smiley->datasize, data, size);
-	smiley->datasize += size;
-
-	loader = smiley->loader;
-	if (!loader)
-		return;
-
-	gdk_pixbuf_loader_write(loader, data, size, NULL);
+	return;
 }
 
 static void
 pidgin_conv_custom_smiley_close(PurpleConversation *conv, const char *smile)
 {
-	PidginConversation *gtkconv;
-	GtkIMHtmlSmiley *smiley;
-	GdkPixbufLoader *loader;
-	const char *sml;
-
-	g_return_if_fail(conv  != NULL);
-	g_return_if_fail(smile != NULL);
-
-	sml = purple_account_get_protocol_name(conv->account);
-	gtkconv = PIDGIN_CONVERSATION(conv);
-	smiley = gtk_imhtml_smiley_get(GTK_IMHTML(gtkconv->imhtml), sml, smile);
-
-	if (!smiley)
-		return;
-
-	loader = smiley->loader;
-
-	if (!loader)
-		return;
-
-
-
-	purple_debug_info("gtkconv", "About to close the smiley pixbuf\n");
-
-	gdk_pixbuf_loader_close(loader, NULL);
-
+	return;
 }
 
 static void
@@ -6716,8 +6353,6 @@
 		}
 	}
 
-	if (fields & PIDGIN_CONV_SMILEY_THEME)
-		pidgin_themes_smiley_themeize(PIDGIN_CONVERSATION(conv)->imhtml);
 
 	if ((fields & PIDGIN_CONV_COLORIZE_TITLE) ||
 			(fields & PIDGIN_CONV_SET_TITLE) ||
@@ -6942,7 +6577,7 @@
 	pidgin_conv_chat_update_user,     /* chat_update_user     */
 	pidgin_conv_present_conversation, /* present              */
 	pidgin_conv_has_focus,            /* has_focus            */
-	pidgin_conv_custom_smiley_add,    /* custom_smiley_add    */
+	NULL,    /* custom_smiley_add    */
 	pidgin_conv_custom_smiley_write,  /* custom_smiley_write  */
 	pidgin_conv_custom_smiley_close,  /* custom_smiley_close  */
 	pidgin_conv_send_confirm,         /* send_confirm         */
@@ -7349,8 +6984,6 @@
 		        GTK_CHECK_MENU_ITEM(win->menu.show_timestamps),
 		        (gboolean)GPOINTER_TO_INT(value));
 
-		gtk_imhtml_show_comments(GTK_IMHTML(gtkconv->imhtml),
-			(gboolean)GPOINTER_TO_INT(value));
 	}
 }
 
@@ -7745,7 +7378,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);
@@ -7780,7 +7413,7 @@
 			PurpleConvMessage *msg = msgs->data;
 			pidgin_conv_write_conv(msg->conv, msg->who, msg->alias, msg->what, msg->flags, msg->when);
 		}
-		gtk_imhtml_append_text(GTK_IMHTML(gtkconv->imhtml), "<BR><HR>", 0);
+		gtk_webview_append_html(GTK_WEBVIEW(gtkconv->webview), "<BR><HR>");
 		g_object_set_data(G_OBJECT(gtkconv->entry), "attach-start-time", NULL);
 	}
 
--- a/pidgin/gtkconv.h	Tue Oct 13 19:52:35 2009 +0000
+++ b/pidgin/gtkconv.h	Wed Oct 14 02:41:11 2009 +0000
@@ -128,7 +128,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	Tue Oct 13 19:52:35 2009 +0000
+++ b/pidgin/gtkdialogs.c	Wed Oct 14 02:41:11 2009 +0000
@@ -38,12 +38,13 @@
 
 #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;
 
 static GtkWidget *about = NULL;
@@ -402,10 +403,10 @@
 {
 	GtkWidget *vbox;
 	GtkWidget *logo;
-	GtkWidget *frame;
-	GtkWidget *text;
+	GtkWidget *scrolled_window;
 	GtkWidget *button;
-	GtkTextIter iter;
+	GtkWidget *web_view;
+
 	GString *str;
 	AtkObject *obj;
 	char* filename, *tmp;
@@ -443,9 +444,14 @@
 	g_free(tmp);
 	gtk_box_pack_start(GTK_BOX(vbox), logo, FALSE, FALSE, 0);
 
-	frame = pidgin_create_imhtml(FALSE, &text, NULL, NULL);
-	gtk_imhtml_set_format_functions(GTK_IMHTML(text), GTK_IMHTML_ALL ^ GTK_IMHTML_SMILEY);
-	gtk_box_pack_start(GTK_BOX(vbox), frame, TRUE, TRUE, 0);
+	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_box_pack_start(GTK_BOX(vbox), scrolled_window, TRUE, TRUE, 0);
 
 	str = g_string_sized_new(4096);
 
@@ -698,11 +704,9 @@
 
 	/* End of not to be translated section */
 
-	gtk_imhtml_append_text(GTK_IMHTML(text), str->str, GTK_IMHTML_NO_SCROLL);
+	webkit_web_view_load_html_string (WEBKIT_WEB_VIEW(web_view), str->str, "");
 	g_string_free(str, TRUE);
 
-	gtk_text_buffer_get_start_iter(gtk_text_view_get_buffer(GTK_TEXT_VIEW(text)), &iter);
-	gtk_text_buffer_place_cursor(gtk_text_view_get_buffer(GTK_TEXT_VIEW(text)), &iter);
 
 	/* Close Button */
 	button = pidgin_dialog_add_button(GTK_DIALOG(about), GTK_STOCK_CLOSE,
--- a/pidgin/gtklog.c	Tue Oct 13 19:52:35 2009 +0000
+++ b/pidgin/gtklog.c	Wed Oct 14 02:41:11 2009 +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);
@@ -422,7 +422,9 @@
 static gboolean search_find_cb(gpointer data)
 {
 	PidginLogViewer *viewer = data;
-	gtk_imhtml_search_find(GTK_IMHTML(viewer->imhtml), viewer->search);
+	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;
 }
 
@@ -463,19 +465,15 @@
 	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) {
-		gtk_imhtml_search_clear(GTK_IMHTML(viewer->imhtml));
+		webkit_web_view_unmark_text_matches(WEBKIT_WEB_VIEW(viewer->web_view));
 		g_idle_add(search_find_cb, viewer);
 	}
 
@@ -658,11 +656,16 @@
 	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);
-	gtk_box_pack_start(GTK_BOX(vbox), frame, TRUE, TRUE, 0);
-	gtk_widget_show(frame);
+	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);
+	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), sw, TRUE, TRUE, 0);
+	gtk_widget_show(sw);
 
 	/* Search box **********/
 	hbox = gtk_hbox_new(FALSE, PIDGIN_HIG_BOX_SPACE);
--- a/pidgin/gtklog.h	Tue Oct 13 19:52:35 2009 +0000
+++ b/pidgin/gtklog.h	Wed Oct 14 02:41:11 2009 +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	Tue Oct 13 19:52:35 2009 +0000
+++ b/pidgin/gtknotify.c	Wed Oct 14 02:41:11 2009 +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
 {
@@ -815,21 +815,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)
@@ -838,8 +823,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;
 
@@ -874,14 +859,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);
@@ -894,10 +883,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);
@@ -1156,10 +1145,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	Tue Oct 13 19:52:35 2009 +0000
+++ b/pidgin/gtkthemes.c	Wed Oct 14 02:41:11 2009 +0000
@@ -271,6 +271,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
@@ -321,6 +323,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++;
@@ -359,7 +362,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	Tue Oct 13 19:52:35 2009 +0000
+++ b/pidgin/gtkthemes.h	Wed Oct 14 02:41:11 2009 +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	Wed Oct 14 02:41:11 2009 +0000
@@ -0,0 +1,336 @@
+/*
+ * @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;
+};
+
+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;
+
+	uri = webkit_network_request_get_uri (request);
+
+	/* 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);
+	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);
+}
+
+
+/* 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;
+	g_free (script);
+	g_free (escaped);
+}
+
+gboolean gtk_webview_is_empty (GtkWebView *view)
+{
+	return view->priv->empty;
+}
+
+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	Wed Oct 14 02:41:11 2009 +0000
@@ -0,0 +1,127 @@
+/**
+ * @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);
+
+/**
+ * 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);
+
+#endif /* _PIDGIN_WEBVIEW_H_ */
--- a/pidgin/plugins/Makefile.am	Tue Oct 13 19:52:35 2009 +0000
+++ b/pidgin/plugins/Makefile.am	Wed Oct 14 02:41:11 2009 +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	Wed Oct 14 02:41:11 2009 +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	Wed Oct 14 02:41:11 2009 +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	Wed Oct 14 02:41:11 2009 +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	Wed Oct 14 02:41:11 2009 +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	Wed Oct 14 02:41:11 2009 +0000
@@ -0,0 +1,852 @@
+/*
+ * 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 <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) 
+		styles = g_list_prepend (styles, g_strdup (stylepath));
+
+	/* 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);;
+
+	/* 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/csspath", "");
+}
+
+PURPLE_INIT_PLUGIN(webkit, init_plugin, info)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/pidgin/smileyparser.c	Wed Oct 14 02:41:11 2009 +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	Wed Oct 14 02:41:11 2009 +0000
@@ -0,0 +1,3 @@
+
+char*
+smiley_parse_markup (const char* markup, const char* sml);