diff src/gtkimhtml.c @ 1428:00b3d02a2168

[gaim-migrate @ 1438] gtkhtml has gotten replaced by gtkimhtml. committer: Tailor Script <tailor@pidgin.im>
author Eric Warmenhoven <eric@warmenhoven.org>
date Fri, 26 Jan 2001 02:02:36 +0000
parents
children 5df631739769
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/gtkimhtml.c	Fri Jan 26 02:02:36 2001 +0000
@@ -0,0 +1,2918 @@
+/*
+ * GtkIMHtml
+ *
+ * Copyright (C) 2000, Eric Warmenhoven <warmenhoven@yahoo.com>
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ *
+ */
+
+#include "gtkimhtml.h"
+#include <gtk/gtk.h>
+#include <string.h>
+#include <ctype.h>
+#include <stdio.h>
+#include <math.h>
+
+#include "pixmaps/angel.xpm"
+#include "pixmaps/bigsmile.xpm"
+#include "pixmaps/burp.xpm"
+#include "pixmaps/crossedlips.xpm"
+#include "pixmaps/cry.xpm"
+#include "pixmaps/embarrassed.xpm"
+#include "pixmaps/kiss.xpm"
+#include "pixmaps/moneymouth.xpm"
+#include "pixmaps/sad.xpm"
+#include "pixmaps/scream.xpm"
+#include "pixmaps/smile.xpm"
+#include "pixmaps/smile8.xpm"
+#include "pixmaps/think.xpm"
+#include "pixmaps/tongue.xpm"
+#include "pixmaps/wink.xpm"
+#include "pixmaps/yell.xpm"
+
+#define DEFAULT_FONT_NAME "helvetica"
+#define MAX_SIZE 7
+
+gint font_sizes [] = { 80, 100, 120, 140, 200, 300, 400 };
+
+#define BORDER_SIZE 3
+#define MIN_HEIGHT 20
+#define HR_HEIGHT 2
+
+#define TYPE_TEXT     0
+#define TYPE_SMILEY   1
+#define TYPE_IMG      2
+#define TYPE_SEP      3
+#define TYPE_BR       4
+#define TYPE_COMMENT  5
+
+typedef struct _GtkIMHtmlBit GtkIMHtmlBit;
+typedef struct _FontDetail   FontDetail;
+
+struct _GtkIMHtmlBit {
+	gint type;
+
+	gchar *text;
+	GdkPixmap *pm;
+	GdkBitmap *bm;
+
+	GdkFont *font;
+	GdkColor *fore;
+	GdkColor *back;
+	GdkColor *bg;
+	gboolean underline;
+	gboolean strike;
+	gchar *url;
+
+	GList *chunks;
+};
+
+struct _FontDetail {
+	gushort size;
+	gchar *face;
+	GdkColor *fore;
+	GdkColor *back;
+};
+
+struct line_info {
+	gint x;
+	gint y;
+	gint width;
+	gint height;
+	gint ascent;
+
+	gboolean selected;
+	gchar *sel_start;
+	gchar *sel_end;
+
+	gchar *text;
+	GtkIMHtmlBit *bit;
+};
+
+struct url_widget {
+	gint x;
+	gint y;
+	gint width;
+	gint height;
+	gchar *url;
+};
+
+static GtkLayoutClass *parent_class = NULL;
+
+enum {
+	TARGET_STRING,
+	TARGET_TEXT,
+	TARGET_COMPOUND_TEXT
+};
+
+enum {
+	URL_CLICKED,
+	LAST_SIGNAL
+};
+static guint signals [LAST_SIGNAL] = { 0 };
+
+static void      gtk_imhtml_draw_bit            (GtkIMHtml *, GtkIMHtmlBit *);
+static GdkColor *gtk_imhtml_get_color           (const gchar *);
+static gint      gtk_imhtml_motion_notify_event (GtkWidget *, GdkEventMotion *);
+
+static void
+gtk_imhtml_destroy (GtkObject *object)
+{
+	GtkIMHtml *imhtml;
+
+	imhtml = GTK_IMHTML (object);
+
+	while (imhtml->bits) {
+		GtkIMHtmlBit *bit = imhtml->bits->data;
+		imhtml->bits = g_list_remove (imhtml->bits, bit);
+		if (bit->text)
+			g_free (bit->text);
+		if (bit->font)
+			gdk_font_unref (bit->font);
+		if (bit->fore)
+			gdk_color_free (bit->fore);
+		if (bit->back)
+			gdk_color_free (bit->back);
+		if (bit->bg)
+			gdk_color_free (bit->bg);
+		if (bit->url)
+			g_free (bit->url);
+		if (bit->pm)
+			gdk_pixmap_unref (bit->pm);
+		if (bit->bm)
+			gdk_bitmap_unref (bit->bm);
+		while (bit->chunks) {
+			GtkObject *obj = bit->chunks->data;
+			struct line_info *li = gtk_object_get_user_data (obj);
+			if (li->text)
+				g_free (li->text);
+			g_free (li);
+			bit->chunks = g_list_remove (bit->chunks, obj);
+		}
+		g_free (bit);
+	}
+
+	while (imhtml->urls) {
+		g_free (imhtml->urls->data);
+		imhtml->urls = g_list_remove (imhtml->urls, imhtml->urls->data);
+	}
+
+	if (imhtml->selected_text)
+		g_string_free (imhtml->selected_text, TRUE);
+
+	gdk_font_unref (imhtml->default_font);
+	gdk_color_free (imhtml->default_fg_color);
+
+	gdk_cursor_destroy (imhtml->hand_cursor);
+	gdk_cursor_destroy (imhtml->arrow_cursor);
+
+	g_hash_table_destroy (imhtml->smiley_hash);
+
+	if (GTK_OBJECT_CLASS (parent_class)->destroy != NULL)
+		(* GTK_OBJECT_CLASS (parent_class)->destroy) (object);
+}
+
+static void
+gtk_imhtml_realize (GtkWidget *widget)
+{
+	GtkIMHtml *imhtml;
+
+	g_return_if_fail (widget != NULL);
+	g_return_if_fail (GTK_IS_IMHTML (widget));
+
+	imhtml = GTK_IMHTML (widget);
+
+	if (GTK_WIDGET_CLASS (parent_class)->realize)
+		(* GTK_WIDGET_CLASS (parent_class)->realize) (widget);
+
+	widget->style = gtk_style_attach (widget->style, widget->window);
+	gdk_window_set_events (imhtml->layout.bin_window,
+			       (gdk_window_get_events (imhtml->layout.bin_window)
+				| GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK
+				| GDK_POINTER_MOTION_MASK | GDK_EXPOSURE_MASK));
+
+	gdk_window_set_cursor (widget->window, imhtml->arrow_cursor);
+
+	gdk_window_set_background (GTK_LAYOUT (imhtml)->bin_window, &GTK_WIDGET (imhtml)->style->white);
+}
+
+static void
+gtk_imhtml_unrealize (GtkWidget *widget)
+{
+	if (GTK_WIDGET_CLASS (parent_class)->unrealize)
+		(* GTK_WIDGET_CLASS (parent_class)->unrealize) (widget);
+}
+
+static void
+gtk_imhtml_draw (GtkWidget    *widget,
+		 GdkRectangle *area)
+{
+	if (GTK_WIDGET_CLASS (parent_class)->draw)
+		(* GTK_WIDGET_CLASS (parent_class)->draw) (widget, area);
+}
+
+static void
+gtk_imhtml_style_set (GtkWidget *widget,
+		      GtkStyle  *style)
+{
+	GtkIMHtml *imhtml;
+
+	g_return_if_fail (widget != NULL);
+	g_return_if_fail (GTK_IS_IMHTML (widget));
+	if (!GTK_WIDGET_REALIZED (widget))
+		return;
+
+	imhtml = GTK_IMHTML (widget);
+
+	gdk_window_set_background (GTK_LAYOUT (imhtml)->bin_window, &GTK_WIDGET (imhtml)->style->white);
+}
+
+static gint
+gtk_imhtml_expose (GtkWidget      *widget,
+		   GdkEventExpose *event)
+{
+	if (GTK_WIDGET_CLASS (parent_class)->expose_event)
+		(* GTK_WIDGET_CLASS (parent_class)->expose_event) (widget, event);
+
+	return TRUE;
+}
+
+static void
+gtk_imhtml_redraw_all (GtkIMHtml *imhtml)
+{
+	GList *b;
+	GtkIMHtmlBit *bit;
+	GtkAdjustment *vadj;
+	gfloat oldvalue;
+
+	vadj = GTK_LAYOUT (imhtml)->vadjustment;
+	oldvalue = vadj->value / vadj->upper;
+
+	b = imhtml->bits;
+	while (b) {
+		bit = b->data;
+		b = g_list_next (b);
+		while (bit->chunks) {
+			GtkObject *obj = bit->chunks->data;
+			struct line_info *li = gtk_object_get_user_data (obj);
+			if (li->text)
+				g_free (li->text);
+			g_free (li);
+			bit->chunks = g_list_remove (bit->chunks, obj);
+		}
+	}
+
+	g_list_free (imhtml->line);
+	imhtml->line = NULL;
+
+	while (imhtml->urls) {
+		g_free (imhtml->urls->data);
+		imhtml->urls = g_list_remove (imhtml->urls, imhtml->urls->data);
+	}
+
+	while (GTK_LAYOUT (imhtml)->children)
+		gtk_container_remove (GTK_CONTAINER (imhtml),
+				*(GtkWidget **)GTK_LAYOUT (imhtml)->children->data);
+
+	imhtml->x = BORDER_SIZE;
+	imhtml->y = BORDER_SIZE + 10;
+	imhtml->llheight = 0;
+	imhtml->llascent = 0;
+
+	b = imhtml->bits;
+	while (b) {
+		gtk_imhtml_draw_bit (imhtml, b->data);
+		b = g_list_next (b);
+	}
+
+	gtk_adjustment_set_value (vadj, vadj->upper * oldvalue);
+}
+
+static void
+gtk_imhtml_size_allocate (GtkWidget     *widget,
+			  GtkAllocation *allocation)
+{
+	GtkIMHtml *imhtml;
+
+	g_return_if_fail (widget != NULL);
+	g_return_if_fail (GTK_IS_IMHTML (widget));
+	g_return_if_fail (allocation != NULL);
+
+	imhtml = GTK_IMHTML (widget);
+
+	if (GTK_WIDGET_CLASS (parent_class)->size_allocate)
+		( *GTK_WIDGET_CLASS (parent_class)->size_allocate) (widget, allocation);
+
+	if (allocation->width == imhtml->xsize)
+		return;
+
+	imhtml->x = BORDER_SIZE;
+	imhtml->y = BORDER_SIZE + 10;
+	imhtml->llheight = 0;
+	imhtml->llascent = 0;
+
+	imhtml->xsize = allocation->width;
+
+	gtk_imhtml_redraw_all (imhtml);
+}
+
+static void
+gtk_imhtml_select_none (GtkIMHtml *imhtml)
+{
+	GList *bits;
+	GList *chunks;
+	GtkIMHtmlBit *bit;
+	struct line_info *chunk;
+	GtkWidget *darea;
+
+	g_return_if_fail (GTK_IS_IMHTML (imhtml));
+
+	bits = imhtml->bits;
+	while (bits) {
+		bit = bits->data;
+		chunks = bit->chunks;
+
+		while (chunks) {
+			darea = chunks->data;
+			chunk = gtk_object_get_user_data (GTK_OBJECT (darea));
+
+			if (chunk->selected)
+				gtk_widget_queue_draw (darea);
+			chunk->selected = FALSE;
+			chunk->sel_start = chunk->sel_end = NULL;
+
+			chunks = g_list_next (chunks);
+		}
+
+		bits = g_list_next (bits);
+	}
+}
+
+static gchar*
+get_position (struct line_info *chunk,
+	      gint              x,
+	      gboolean          smileys)
+{
+	gint width = x - chunk->x;
+	gchar *text;
+	gchar *pos;
+	guint total = 0;
+
+	switch (chunk->bit->type) {
+	case TYPE_TEXT:
+	case TYPE_COMMENT:
+		text = chunk->text;
+		break;
+	case TYPE_SMILEY:
+		if (smileys)
+			return NULL;
+		else
+			text = chunk->text;
+		break;
+	default:
+		return NULL;
+		break;
+	}
+
+	if (width <= 0)
+		return text;
+
+	for (pos = text; *pos != '\0'; pos++) {
+		gint char_width = gdk_text_width (chunk->bit->font, pos, 1);
+		if ((width > total) && (width <= total + char_width)) {
+			if (width < total + (char_width >> 1))
+				return pos;
+			else
+				return ++pos;
+		}
+		total += char_width;
+	}
+
+	return pos;
+}
+
+static GString*
+append_to_sel (GString          *string,
+	       struct line_info *chunk,
+	       gboolean          smileys)
+{
+	GString *new_string;
+	gchar *buf;
+	gchar *start;
+	gint length;
+
+	switch (chunk->bit->type) {
+	case TYPE_TEXT:
+	case TYPE_COMMENT:
+		start = (chunk->sel_start == NULL) ? chunk->text : chunk->sel_start;
+		length = (chunk->sel_end == NULL) ? strlen (start) : chunk->sel_end - start;
+		if (length <= 0)
+			return string;
+		buf = g_strndup (start, length);
+		break;
+	case TYPE_SMILEY:
+		if (smileys) {
+			start = (chunk->sel_start == NULL) ? chunk->bit->text : chunk->sel_start;
+			length = (chunk->sel_end == NULL) ? strlen (start) : chunk->sel_end - start;
+			if (length <= 0)
+				return string;
+			buf = g_strndup (start, length);
+		} else {
+			start = (chunk->sel_start == NULL) ? chunk->text : chunk->sel_start;
+			length = (chunk->sel_end == NULL) ? strlen (start) : chunk->sel_end - start;
+			if (length <= 0)
+				return string;
+			buf = g_strndup (start, length);
+		}
+		break;
+	case TYPE_BR:
+		buf = g_strdup ("\n");
+		break;
+	default:
+		return string;
+		break;
+	}
+
+	new_string = g_string_append (string, buf);
+	g_free (buf);
+
+	return new_string;
+}
+
+#define COORDS_IN_CHUNK(xx, yy) (((xx) < chunk->x + chunk->width) && \
+				 ((yy) < chunk->y + chunk->height))
+
+static void
+gtk_imhtml_select_bits (GtkIMHtml *imhtml)
+{
+	GList *bits;
+	GList *chunks;
+	GtkIMHtmlBit *bit;
+	struct line_info *chunk;
+	GtkWidget *darea;
+
+	guint startx = imhtml->sel_startx,
+	      starty = imhtml->sel_starty,
+	      endx   = imhtml->sel_endx,
+	      endy   = imhtml->sel_endy;
+	gchar *new_pos;
+	gint selection = 0;
+	gboolean smileys = imhtml->smileys;
+	gboolean redraw = FALSE;
+	gboolean got_start = FALSE;
+	gboolean got_end = FALSE;
+
+	g_return_if_fail (GTK_IS_IMHTML (imhtml));
+
+	if (!imhtml->selection)
+		return;
+
+	if (imhtml->selected_text) {
+		g_string_free (imhtml->selected_text, TRUE);
+		imhtml->selected_text = g_string_new ("");
+	}
+
+	bits = imhtml->bits;
+	while (bits) {
+		bit = bits->data;
+		chunks = bit->chunks;
+
+		while (chunks) {
+			darea = chunks->data;
+			chunk = gtk_object_get_user_data (GTK_OBJECT (darea));
+
+			switch (selection) {
+			case 0:
+				if (COORDS_IN_CHUNK (startx, starty)) {
+					new_pos = get_position (chunk, startx, smileys);
+					if ( !chunk->selected ||
+					    (chunk->sel_start != new_pos) ||
+					    (chunk->sel_end != NULL))
+						redraw = TRUE;
+					chunk->selected = TRUE;
+					chunk->sel_start = new_pos;
+					chunk->sel_end = NULL;
+					selection++;
+					got_start = TRUE;
+				}
+
+				if (COORDS_IN_CHUNK (endx, endy)) {
+					if (got_start) {
+						new_pos = get_position (chunk, endx, smileys);
+						if (chunk->sel_end != new_pos)
+							redraw = TRUE;
+						if (chunk->sel_start > new_pos) {
+							chunk->sel_end = chunk->sel_start;
+							chunk->sel_start = new_pos;
+						} else
+							chunk->sel_end = new_pos;
+						selection = 2;
+						got_end = TRUE;
+					} else {
+						new_pos = get_position (chunk, endx, smileys);
+						if ( !chunk->selected ||
+						    (chunk->sel_start != new_pos) ||
+						    (chunk->sel_end != NULL))
+							redraw = TRUE;
+						chunk->selected = TRUE;
+						chunk->sel_start = new_pos;
+						chunk->sel_end = NULL;
+						selection++;
+						got_end = TRUE;
+					}
+				} else if (!COORDS_IN_CHUNK (startx, starty) && !got_start) {
+					if (chunk->selected)
+						redraw = TRUE;
+					chunk->selected = FALSE;
+					chunk->sel_start = chunk->text;
+					chunk->sel_end = NULL;
+				}
+
+				break;
+			case 1:
+				if (!got_start && COORDS_IN_CHUNK (startx, starty)) {
+					new_pos = get_position (chunk, startx, smileys);
+					if ( !chunk->selected ||
+					    (chunk->sel_end != new_pos) ||
+					    (chunk->sel_start != chunk->text))
+						redraw = TRUE;
+					chunk->selected = TRUE;
+					chunk->sel_start = chunk->text;
+					chunk->sel_end = new_pos;
+					selection++;
+					got_start = TRUE;
+				} else if (!got_end && COORDS_IN_CHUNK (endx, endy)) {
+					new_pos = get_position (chunk, endx, smileys);
+					if ( !chunk->selected ||
+					    (chunk->sel_end != new_pos) ||
+					    (chunk->sel_start != chunk->text))
+						redraw = TRUE;
+					chunk->selected = TRUE;
+					chunk->sel_start = chunk->text;
+					chunk->sel_end = new_pos;
+					selection++;
+					got_end = TRUE;
+				} else {
+					if ( !chunk->selected ||
+					    (chunk->sel_end != new_pos) ||
+					    (chunk->sel_start != NULL))
+						redraw = TRUE;
+					chunk->selected = TRUE;
+					chunk->sel_start = chunk->text;
+					chunk->sel_end = NULL;
+				}
+
+				break;
+			case 2:
+				if ( chunk->selected ||
+				    (chunk->sel_start != chunk->text) ||
+				    (chunk->sel_end != NULL))
+					redraw = TRUE;
+				chunk->selected = FALSE;
+				chunk->sel_start = chunk->text;
+				chunk->sel_end = NULL;
+				break;
+			}
+
+			if (chunk->selected == TRUE)
+				imhtml->selected_text = append_to_sel (imhtml->selected_text,
+								       chunk, smileys);
+
+			if (redraw) {
+				gtk_widget_queue_draw (darea);
+				redraw = FALSE;
+			}
+
+			chunks = g_list_next (chunks);
+		}
+
+		bits = g_list_next (bits);
+	}
+}
+
+static gint
+scroll_timeout (GtkIMHtml *imhtml)
+{
+	GdkEventMotion event;
+	gint x, y;
+	GdkModifierType mask;
+
+	imhtml->scroll_timer = 0;
+
+	gdk_window_get_pointer (imhtml->layout.bin_window, &x, &y, &mask);
+
+	if (mask & GDK_BUTTON1_MASK) {
+		event.is_hint = 0;
+		event.x = x;
+		event.y = y;
+		event.state = mask;
+
+		gtk_imhtml_motion_notify_event (GTK_WIDGET (imhtml), &event);
+	}
+
+	return FALSE;
+}
+
+static gint
+gtk_imhtml_motion_notify_event (GtkWidget      *widget,
+				GdkEventMotion *event)
+{
+	gint x, y;
+	GdkModifierType state;
+	GtkIMHtml *imhtml = GTK_IMHTML (widget);
+	GtkAdjustment *vadj = GTK_LAYOUT (widget)->vadjustment;
+	GtkAdjustment *hadj = GTK_LAYOUT (widget)->hadjustment;
+
+	if (event->is_hint)
+		gdk_window_get_pointer (event->window, &x, &y, &state);
+	else {
+		x = event->x + hadj->value;
+		y = event->y + vadj->value;
+		state = event->state;
+	}
+
+	if (state & GDK_BUTTON1_MASK) {
+		gint diff;
+		gint height = vadj->page_size;
+		gint yy = y - vadj->value;
+
+		if (((yy < 0) || (yy > height)) &&
+		    (imhtml->scroll_timer == 0) &&
+		    (vadj->upper > vadj->page_size)) {
+			imhtml->scroll_timer = gtk_timeout_add (100,
+								(GtkFunction) scroll_timeout,
+								imhtml);
+			diff = (yy < 0) ? (yy >> 1) : ((yy - height) >> 1);
+			gtk_adjustment_set_value (vadj,
+						  MIN (vadj->value + diff, vadj->upper - height + 20));
+		}
+
+		if (imhtml->selection) {
+			imhtml->sel_endx = MAX (x, 0);
+			imhtml->sel_endy = MAX (y, 0);
+			gtk_imhtml_select_bits (imhtml);
+		}
+	} else {
+		GList *urls = imhtml->urls;
+		struct url_widget *uw;
+
+		while (urls) {
+			uw = (struct url_widget *) urls->data;
+			if ((x > uw->x) && (x < uw->x + uw->width) &&
+			    (y > uw->y) && (y < uw->y + uw->height)) {
+				gdk_window_set_cursor (imhtml->layout.bin_window, imhtml->hand_cursor);
+				return TRUE;
+			}
+			urls = g_list_next (urls);
+		}
+	}
+
+	gdk_window_set_cursor (imhtml->layout.bin_window, imhtml->arrow_cursor);
+
+	return TRUE;
+}
+
+static gint
+gtk_imhtml_button_press_event (GtkWidget      *widget,
+			       GdkEventButton *event)
+{
+	GtkIMHtml *imhtml = GTK_IMHTML (widget);
+	GtkAdjustment *vadj = GTK_LAYOUT (widget)->vadjustment;
+	GtkAdjustment *hadj = GTK_LAYOUT (widget)->hadjustment;
+	gint x, y;
+
+	if (event->button == 1) {
+		x = event->x + hadj->value;
+		y = event->y + vadj->value;
+
+		imhtml->sel_startx = x;
+		imhtml->sel_starty = y;
+		imhtml->selection = TRUE;
+		gtk_imhtml_select_none (imhtml);
+	}
+
+	return TRUE;
+}
+
+static gint
+gtk_imhtml_button_release_event (GtkWidget      *widget,
+				 GdkEventButton *event)
+{
+	GtkIMHtml *imhtml = GTK_IMHTML (widget);
+	GtkAdjustment *vadj = GTK_LAYOUT (widget)->vadjustment;
+	GtkAdjustment *hadj = GTK_LAYOUT (widget)->hadjustment;
+	gint x, y;
+
+	if ((event->button == 1) && imhtml->selection) {
+		x = event->x + hadj->value;
+		y = event->y + vadj->value;
+
+		if ((x == imhtml->sel_startx) && (y == imhtml->sel_starty)) {
+			imhtml->sel_startx = imhtml->sel_starty = 0;
+			imhtml->selection = FALSE;
+			gtk_imhtml_select_none (imhtml);
+		} else {
+			imhtml->sel_endx = MAX (x, 0);
+			imhtml->sel_endy = MAX (y, 0);
+			gtk_imhtml_select_bits (imhtml);
+		}
+
+		gtk_selection_owner_set (widget, GDK_SELECTION_PRIMARY, event->time);
+	}
+
+	return TRUE;
+}
+
+static void
+gtk_imhtml_selection_get (GtkWidget        *widget,
+			  GtkSelectionData *sel_data,
+			  guint             sel_info,
+			  guint32           time)
+{
+	GtkIMHtml *imhtml;
+	gchar *string;
+	gint length;
+
+	g_return_if_fail (widget != NULL);
+	g_return_if_fail (GTK_IS_IMHTML (widget));
+	g_return_if_fail (sel_data->selection == GDK_SELECTION_PRIMARY);
+
+	imhtml = GTK_IMHTML (widget);
+
+	g_return_if_fail (imhtml->selected_text != NULL);
+	g_return_if_fail (imhtml->selected_text->str != NULL);
+
+	if (imhtml->selected_text->len <= 0) {
+		string = NULL;
+		length = 0;
+	} else {
+		string = g_strdup (imhtml->selected_text->str);
+		length = strlen (string);
+	}
+
+	if (sel_info == TARGET_STRING) {
+		gtk_selection_data_set (sel_data,
+					GDK_SELECTION_TYPE_STRING,
+					8 * sizeof (gchar),
+					(guchar *) string,
+					length);
+	} else if ((sel_info == TARGET_TEXT) || (sel_info == TARGET_COMPOUND_TEXT)) {
+		guchar *text;
+		GdkAtom encoding;
+		gint format;
+		gint new_length;
+
+		gdk_string_to_compound_text (string, &encoding, &format, &text, &new_length);
+		gtk_selection_data_set (sel_data, encoding, format, text, new_length);
+		gdk_free_compound_text (text);
+	}
+
+	if (string)
+		g_free (string);
+}
+
+static gint
+gtk_imhtml_selection_clear_event (GtkWidget         *widget,
+				  GdkEventSelection *event)
+{
+	GtkIMHtml *imhtml;
+
+	g_return_val_if_fail (widget != NULL, FALSE);
+	g_return_val_if_fail (GTK_IS_IMHTML (widget), FALSE);
+	g_return_val_if_fail (event != NULL, FALSE);
+	g_return_val_if_fail (event->selection == GDK_SELECTION_PRIMARY, TRUE);
+
+	if (!gtk_selection_clear (widget, event))
+		return FALSE;
+
+	imhtml = GTK_IMHTML (widget);
+
+	gtk_imhtml_select_none (imhtml);
+
+	return TRUE;
+}
+
+static void
+gtk_imhtml_set_scroll_adjustments (GtkLayout     *layout,
+				   GtkAdjustment *hadj,
+				   GtkAdjustment *vadj)
+{
+	if (parent_class->set_scroll_adjustments)
+		(* parent_class->set_scroll_adjustments) (layout, hadj, vadj);
+}
+
+static void
+gtk_imhtml_class_init (GtkIMHtmlClass *class)
+{
+	GtkObjectClass *object_class;
+	GtkWidgetClass *widget_class;
+	GtkLayoutClass *layout_class;
+
+	object_class = (GtkObjectClass*) class;
+	widget_class = (GtkWidgetClass*) class;
+	layout_class = (GtkLayoutClass*) class;
+
+	parent_class = gtk_type_class (GTK_TYPE_LAYOUT);
+
+	signals [URL_CLICKED] =
+		gtk_signal_new ("url_clicked",
+				GTK_RUN_FIRST,
+				object_class->type,
+				GTK_SIGNAL_OFFSET (GtkIMHtmlClass, url_clicked),
+				gtk_marshal_NONE__POINTER,
+				GTK_TYPE_NONE, 1,
+				GTK_TYPE_POINTER);
+
+	gtk_object_class_add_signals (object_class, signals, LAST_SIGNAL);
+
+	object_class->destroy = gtk_imhtml_destroy;
+
+	widget_class->realize = gtk_imhtml_realize;
+	widget_class->unrealize = gtk_imhtml_unrealize;
+	widget_class->draw = gtk_imhtml_draw;
+	widget_class->style_set = gtk_imhtml_style_set;
+	widget_class->expose_event  = gtk_imhtml_expose;
+	widget_class->size_allocate = gtk_imhtml_size_allocate;
+	widget_class->motion_notify_event = gtk_imhtml_motion_notify_event;
+	widget_class->button_press_event = gtk_imhtml_button_press_event;
+	widget_class->button_release_event = gtk_imhtml_button_release_event;
+	widget_class->selection_get = gtk_imhtml_selection_get;
+	widget_class->selection_clear_event = gtk_imhtml_selection_clear_event;
+
+	layout_class->set_scroll_adjustments = gtk_imhtml_set_scroll_adjustments;
+}
+
+static GdkFont*
+gtk_imhtml_font_load (GtkIMHtml *imhtml,
+		      gchar     *name,
+		      gboolean   bold,
+		      gboolean   italics,
+		      gint       fontsize)
+{
+	gchar buf [16 * 1024];
+	GdkFont *font;
+	gint size = fontsize ? font_sizes [MIN (fontsize, MAX_SIZE) - 1] : 120;
+
+	g_snprintf (buf, sizeof (buf), "-*-%s-%s-%c-*-*-*-%d-*-*-*-*-*-*",
+		    name ? name : DEFAULT_FONT_NAME,
+		    bold ? "bold" : "medium",
+		    italics ? 'i' : 'r',
+		    size);
+	font = gdk_font_load (buf);
+	if (font)
+		return font;
+
+	if (italics) {
+		g_snprintf (buf, sizeof (buf), "-*-%s-%s-%c-*-*-*-%d-*-*-*-*-*-*",
+			    name ? name : DEFAULT_FONT_NAME,
+			    bold ? "bold" : "medium",
+			    'o',
+			    size);
+		font = gdk_font_load (buf);
+		if (font)
+			return font;
+
+		if (bold) {
+			g_snprintf (buf, sizeof (buf), "-*-%s-%s-%c-*-*-*-%d-*-*-*-*-*-*",
+				    name ? name : DEFAULT_FONT_NAME,
+				    "bold",
+				    'r',
+				    size);
+			font = gdk_font_load (buf);
+			if (font)
+				return font;
+		}
+	}
+
+	if (bold) {
+		g_snprintf (buf, sizeof (buf), "-*-%s-%s-%c-*-*-*-%d-*-*-*-*-*-*",
+			    name ? name : DEFAULT_FONT_NAME,
+			    "medium",
+			    italics ? 'i' : 'r',
+			    size);
+		font = gdk_font_load (buf);
+		if (font)
+			return font;
+
+		if (italics) {
+			g_snprintf (buf, sizeof (buf), "-*-%s-%s-%c-*-*-*-%d-*-*-*-*-*-*",
+				    name ? name : DEFAULT_FONT_NAME,
+				    "medium",
+				    'o',
+				    size);
+			font = gdk_font_load (buf);
+			if (font)
+				return font;
+		}
+	}
+
+	if (!bold && !italics) {
+		g_snprintf (buf, sizeof (buf), "-*-%s-medium-r-*-*-*-%d-*-*-*-*-*-*",
+			    name ? name : DEFAULT_FONT_NAME,
+			    size);
+		font = gdk_font_load (buf);
+		if (font)
+			return font;
+	}
+
+	g_snprintf (buf, sizeof (buf), "-*-%s-medium-r-*-*-*-%d-*-*-*-*-*-*",
+		    DEFAULT_FONT_NAME,
+		    size);
+	font = gdk_font_load (buf);
+	if (font)
+		return font;
+
+	if (imhtml->default_font)
+		return gdk_font_ref (imhtml->default_font);
+
+	return NULL;
+}
+
+static void
+gtk_imhtml_init (GtkIMHtml *imhtml)
+{
+	static const GtkTargetEntry targets [] = {
+		{ "STRING", 0, TARGET_STRING },
+		{ "TEXT", 0, TARGET_TEXT },
+		{ "COMPOUND_TEXT", 0, TARGET_COMPOUND_TEXT }
+	};
+
+	imhtml->default_font = gtk_imhtml_font_load (imhtml, NULL, FALSE, FALSE, 0);
+	if (imhtml->default_font == NULL)
+		g_warning ("GtkIMHtml: Could not load default font!");
+	imhtml->default_fg_color = gdk_color_copy (&GTK_WIDGET (imhtml)->style->black);
+	imhtml->hand_cursor = gdk_cursor_new (GDK_HAND2);
+	imhtml->arrow_cursor = gdk_cursor_new (GDK_LEFT_PTR);
+
+	GTK_WIDGET_SET_FLAGS (GTK_WIDGET (imhtml), GTK_CAN_FOCUS);
+	gtk_selection_add_targets (GTK_WIDGET (imhtml), GDK_SELECTION_PRIMARY, targets, 3);
+}
+
+GtkType
+gtk_imhtml_get_type (void)
+{
+	static GtkType imhtml_type = 0;
+
+	if (!imhtml_type) {
+		static const GtkTypeInfo imhtml_info = {
+			"GtkIMHtml",
+			sizeof (GtkIMHtml),
+			sizeof (GtkIMHtmlClass),
+			(GtkClassInitFunc) gtk_imhtml_class_init,
+			(GtkObjectInitFunc) gtk_imhtml_init,
+			NULL,
+			NULL,
+			NULL
+		};
+
+		imhtml_type = gtk_type_unique (GTK_TYPE_LAYOUT, &imhtml_info);
+	}
+
+	return imhtml_type;
+}
+
+static void
+gtk_imhtml_init_smiley_hash (GtkIMHtml *imhtml)
+{
+	g_return_if_fail (imhtml != NULL);
+	g_return_if_fail (GTK_IS_IMHTML (imhtml));
+
+	imhtml->smiley_hash = g_hash_table_new (g_str_hash, g_str_equal);
+
+	gtk_imhtml_associate_smiley (imhtml, ":)", smile_xpm);
+	gtk_imhtml_associate_smiley (imhtml, ":-)", smile_xpm);
+
+	gtk_imhtml_associate_smiley (imhtml, ":(", sad_xpm);
+	gtk_imhtml_associate_smiley (imhtml, ":-(", sad_xpm);
+
+	gtk_imhtml_associate_smiley (imhtml, ";)", wink_xpm);
+	gtk_imhtml_associate_smiley (imhtml, ";-)", wink_xpm);
+
+	gtk_imhtml_associate_smiley (imhtml, ":-p", tongue_xpm);
+	gtk_imhtml_associate_smiley (imhtml, ":-P", tongue_xpm);
+
+	gtk_imhtml_associate_smiley (imhtml, "=-O", scream_xpm);
+	gtk_imhtml_associate_smiley (imhtml, ":-*", kiss_xpm);
+	gtk_imhtml_associate_smiley (imhtml, ">:o", yell_xpm);
+	gtk_imhtml_associate_smiley (imhtml, "8-)", smile8_xpm);
+	gtk_imhtml_associate_smiley (imhtml, ":-$", moneymouth_xpm);
+	gtk_imhtml_associate_smiley (imhtml, ":-!", burp_xpm);
+	gtk_imhtml_associate_smiley (imhtml, ":-[", embarrassed_xpm);
+	gtk_imhtml_associate_smiley (imhtml, ":'(", cry_xpm);
+
+	gtk_imhtml_associate_smiley (imhtml, ":-/", think_xpm);
+	gtk_imhtml_associate_smiley (imhtml, ":-\\", think_xpm);
+
+	gtk_imhtml_associate_smiley (imhtml, ":-X", crossedlips_xpm);
+	gtk_imhtml_associate_smiley (imhtml, ":-D", bigsmile_xpm);
+	gtk_imhtml_associate_smiley (imhtml, "O:-)", angel_xpm);
+}
+
+GtkWidget*
+gtk_imhtml_new (GtkAdjustment *hadj,
+		GtkAdjustment *vadj)
+{
+	GtkIMHtml *imhtml = gtk_type_new (GTK_TYPE_IMHTML);
+
+	gtk_imhtml_set_adjustments (imhtml, hadj, vadj);
+
+	imhtml->bits = NULL;
+	imhtml->urls = NULL;
+
+	imhtml->x = BORDER_SIZE;
+	imhtml->y = BORDER_SIZE + 10;
+	imhtml->llheight = 0;
+	imhtml->llascent = 0;
+	imhtml->line = NULL;
+
+	imhtml->selected_text = g_string_new ("");
+	imhtml->scroll_timer = 0;
+
+	imhtml->img = NULL;
+
+	imhtml->smileys = TRUE;
+	imhtml->comments = FALSE;
+
+	imhtml->smin = G_MAXINT;
+	imhtml->smax = 0;
+	gtk_imhtml_init_smiley_hash (imhtml);
+
+	return GTK_WIDGET (imhtml);
+}
+
+void
+gtk_imhtml_set_adjustments (GtkIMHtml     *imhtml,
+			    GtkAdjustment *hadj,
+			    GtkAdjustment *vadj)
+{
+	gtk_layout_set_hadjustment (GTK_LAYOUT (imhtml), hadj);
+	gtk_layout_set_vadjustment (GTK_LAYOUT (imhtml), vadj);
+}
+
+void
+gtk_imhtml_set_defaults (GtkIMHtml *imhtml,
+			 GdkFont   *font,
+			 GdkColor  *fg_color)
+{
+	g_return_if_fail (imhtml != NULL);
+	g_return_if_fail (GTK_IS_IMHTML (imhtml));
+
+	if (font) {
+		if (imhtml->default_font)
+			gdk_font_unref (imhtml->default_font);
+		imhtml->default_font = gdk_font_ref (font);
+	}
+
+	if (fg_color) {
+		if (imhtml->default_fg_color)
+			gdk_color_free (imhtml->default_fg_color);
+		imhtml->default_fg_color = gdk_color_copy (fg_color);
+	}
+}
+
+void
+gtk_imhtml_set_img_handler (GtkIMHtml      *imhtml,
+			    GtkIMHtmlImage  handler)
+{
+	g_return_if_fail (imhtml != NULL);
+	g_return_if_fail (GTK_IS_IMHTML (imhtml));
+
+	imhtml->img = handler;
+}
+
+void
+gtk_imhtml_associate_smiley (GtkIMHtml  *imhtml,
+			     gchar      *text,
+			     gchar     **xpm)
+{
+	g_return_if_fail (imhtml != NULL);
+	g_return_if_fail (GTK_IS_IMHTML (imhtml));
+	g_return_if_fail (text != NULL);
+
+	if (strlen (text) < imhtml->smin)
+		imhtml->smin = strlen (text);
+
+	if (strlen (text) > imhtml->smax)
+		imhtml->smax = strlen (text);
+
+	if (xpm == NULL)
+		g_hash_table_remove (imhtml->smiley_hash, text);
+	else
+		g_hash_table_insert (imhtml->smiley_hash, text, xpm);
+}
+
+static gint
+draw_text (GtkWidget *widget,
+	   GdkEvent  *event,
+	   gpointer   data)
+{
+	GtkIMHtmlBit *bit;
+	struct line_info *line;
+	GdkGC *gc;
+	GdkColormap *cmap;
+
+	line = gtk_object_get_user_data (GTK_OBJECT (widget));
+	bit = line->bit;
+	gc = gdk_gc_new (widget->window);
+	cmap = gdk_colormap_new (gdk_visual_get_best (), FALSE);
+
+	if (bit->bg != NULL) {
+		gdk_color_alloc (cmap, bit->bg);
+		gdk_gc_set_foreground (gc, bit->bg);
+	} else
+		gdk_gc_copy (gc, widget->style->white_gc);
+
+	gdk_draw_rectangle (widget->window, gc, TRUE, 0, 0,
+			    widget->allocation.width,
+			    widget->allocation.height);
+
+	if (!line->text) {
+		gdk_colormap_unref (cmap);
+		gdk_gc_unref (gc);
+		return TRUE;
+	}
+
+	if (bit->back != NULL) {
+		gdk_color_alloc (cmap, bit->back);
+		gdk_gc_set_foreground (gc, bit->back);
+		gdk_draw_rectangle (widget->window, gc, TRUE, 0, 0,
+				    gdk_string_width (bit->font, line->text),
+				    widget->allocation.height);
+	}
+
+	if (line->selected) {
+		gint width, x;
+		gchar *start, *end;
+		GdkColor col;
+
+		if ((line->sel_start > line->sel_end) && (line->sel_end != NULL)) {
+			start = line->sel_end;
+			end = line->sel_start;
+		} else {
+			start = line->sel_start;
+			end = line->sel_end;
+		}
+
+		if (start == NULL)
+			x = 0;
+		else
+			x = gdk_text_width (bit->font, line->text, start - line->text);
+
+		if (end == NULL)
+			width = gdk_string_width (bit->font, line->text) - x;
+		else
+			width = gdk_text_width (bit->font, line->text, end - line->text) - x;
+
+		col.red = col.green = col.blue = 0xc000;
+		gdk_color_alloc (cmap, &col);
+		gdk_gc_set_foreground (gc, &col);
+
+		gdk_draw_rectangle (widget->window, gc, TRUE, x, 0,
+				    width, widget->allocation.height);
+	}
+
+	if (bit->url) {
+		GdkColor *tc = gtk_imhtml_get_color ("#0000a0");
+		gdk_color_alloc (cmap, tc);
+		gdk_gc_set_foreground (gc, tc);
+		gdk_color_free (tc);
+	} else if (bit->fore) {
+		gdk_color_alloc (cmap, bit->fore);
+		gdk_gc_set_foreground (gc, bit->fore);
+	} else
+		gdk_gc_copy (gc, widget->style->black_gc);
+
+	gdk_draw_string (widget->window, bit->font, gc,
+			 0, line->ascent, line->text);
+
+	if (bit->underline || bit->url)
+		gdk_draw_rectangle (widget->window, gc, TRUE,
+				    0, line->ascent + 1,
+				    gdk_string_width (bit->font, line->text), 1);
+	if (bit->strike)
+		gdk_draw_rectangle (widget->window, gc, TRUE,
+				    0, line->ascent - (bit->font->ascent >> 1),
+				    gdk_string_width (bit->font, line->text), 1);
+
+	gdk_colormap_unref (cmap);
+	gdk_gc_unref (gc);
+
+	return TRUE;
+}
+
+static gint
+draw_img (GtkWidget *widget,
+	  GdkEvent  *event,
+	  gpointer   data)
+{
+	GtkIMHtmlBit *bit;
+	struct line_info *line;
+	GdkGC *gc;
+	GdkColormap *cmap;
+	gint width, height, hoff;
+
+	line = gtk_object_get_user_data (GTK_OBJECT (widget));
+	bit = line->bit;
+	gdk_window_get_size (bit->pm, &width, &height);
+	hoff = (widget->allocation.height - height) / 2;
+	gc = gdk_gc_new (widget->window);
+	cmap = gdk_colormap_new (gdk_visual_get_best (), FALSE);
+
+	if (bit->bg != NULL) {
+		gdk_color_alloc (cmap, bit->bg);
+		gdk_gc_set_foreground (gc, bit->bg);
+	} else
+		gdk_gc_copy (gc, widget->style->white_gc);
+
+	gdk_draw_rectangle (widget->window, gc, TRUE, 0, 0,
+			    widget->allocation.width,
+			    widget->allocation.height);
+
+	if (bit->back != NULL) {
+		gdk_color_alloc (cmap, bit->back);
+		gdk_gc_set_foreground (gc, bit->back);
+		gdk_draw_rectangle (widget->window, gc, TRUE, 0, 0,
+				    width, widget->allocation.height);
+	}
+
+	gdk_draw_pixmap (widget->window, gc, bit->pm, 0, 0, 0, hoff, -1, -1);
+
+	gdk_colormap_unref (cmap);
+	gdk_gc_unref (gc);
+
+	return TRUE;
+}
+
+static gint
+draw_line (GtkWidget *widget,
+	   GdkEvent  *event,
+	   gpointer   data)
+{
+	GtkIMHtmlBit *bit;
+	GdkDrawable *drawable;
+	GdkColormap *cmap;
+	GdkGC *gc;
+	guint max_width;
+	guint max_height;
+
+	bit = data;
+	drawable = widget->window;
+	cmap = gdk_colormap_new (gdk_visual_get_best (), FALSE);
+	gc = gdk_gc_new (drawable);
+
+	if (bit->bg != NULL) {
+		gdk_color_alloc (cmap, bit->bg);
+		gdk_gc_set_foreground (gc, bit->bg);
+
+		gdk_draw_rectangle (widget->window, gc, TRUE, 0, 0,
+				    widget->allocation.width,
+				    widget->allocation.height);
+	}
+
+	gdk_gc_copy (gc, widget->style->black_gc);
+
+	max_width = widget->allocation.width;
+	max_height = widget->allocation.height / 2;
+
+	gdk_draw_rectangle (drawable, gc,
+			    TRUE,
+			    0, max_height / 2,
+			    max_width, max_height);
+
+	gdk_colormap_unref (cmap);
+	gdk_gc_unref (gc);
+
+	return TRUE;
+}
+
+static gint
+click_event_box (GtkBin         *bin,
+		 GdkEventButton *event,
+		 GtkIMHtml      *imhtml)
+{
+	struct line_info *li = gtk_object_get_user_data (GTK_OBJECT (bin->child));
+
+	if (event->button == 1 && event->type == GDK_BUTTON_PRESS)
+		gtk_signal_emit (GTK_OBJECT (imhtml), signals [URL_CLICKED], li->bit->url);
+
+	return TRUE;
+}
+
+static void
+new_line (GtkIMHtml *imhtml)
+{
+	GList *last = g_list_last (imhtml->line);
+	GtkWidget *widget;
+	struct line_info *li;
+
+	if (last) {
+		widget = last->data;
+		li = gtk_object_get_user_data (GTK_OBJECT (widget));
+		if (li->x + li->width != imhtml->xsize - BORDER_SIZE) {
+			li->width = imhtml->xsize - BORDER_SIZE - li->x;
+			gtk_widget_set_usize (widget, li->width, li->height);
+		}
+	}
+
+	last = imhtml->line;
+	if (last) {
+		widget = last->data;
+		li = gtk_object_get_user_data (GTK_OBJECT (widget));
+		if (li->height < MIN_HEIGHT) {
+			while (last) {
+				gint diff;
+				widget = last->data;
+				li = gtk_object_get_user_data (GTK_OBJECT (widget));
+				diff = MIN_HEIGHT - li->height;
+				li->height = MIN_HEIGHT;
+				gtk_widget_set_usize (widget, li->width, li->height);
+				li->ascent += diff >> 1;
+				last = g_list_next (last);
+			}
+			imhtml->llheight = MIN_HEIGHT;
+		}
+	}
+
+	g_list_free (imhtml->line);
+	imhtml->line = NULL;
+
+	imhtml->x = BORDER_SIZE;
+	imhtml->y += imhtml->llheight;
+}
+
+static void
+backwards_update (GtkIMHtml    *imhtml,
+		  GtkIMHtmlBit *bit,
+		  gint          height,
+		  gint          ascent)
+{
+	gint diff;
+	GList *ls = NULL;
+	struct line_info *li;
+	struct url_widget *uw;
+
+	if (height > imhtml->llheight) {
+		diff = height - imhtml->llheight;
+
+		ls = imhtml->line;
+		while (ls) {
+			GtkWidget *data = ls->data;
+			li = gtk_object_get_user_data (GTK_OBJECT (data));
+			li->height += diff;
+			if (ascent)
+				li->ascent = ascent;
+			else
+				li->ascent += diff >> 1;
+			gtk_widget_set_usize (data, li->width, li->height);
+			ls = g_list_next (ls);
+		}
+
+		ls = imhtml->urls;
+		while (ls) {
+			uw = ls->data;
+			if (uw->y + diff > imhtml->y)
+				uw->y += diff;
+			ls = g_list_next (ls);
+		}
+
+		imhtml->llheight = height;
+		if (ascent)
+			imhtml->llascent = ascent;
+		else
+			imhtml->llascent += diff >> 1;
+	}
+}
+
+static GtkTooltips *tips = NULL;
+
+static void
+add_text_renderer (GtkIMHtml    *imhtml,
+		   GtkIMHtmlBit *bit,
+		   gchar        *text)
+{
+	GtkWidget *darea;
+	GtkWidget *eventbox;
+	struct line_info *li;
+	struct url_widget *uw;
+	gint width;
+
+	if (text)
+		width = gdk_string_width (bit->font, text);
+	else
+		width = 0;
+
+	darea = gtk_drawing_area_new ();
+	gtk_widget_set_usize (darea, width, imhtml->llheight);
+	gtk_signal_connect (GTK_OBJECT (darea), "expose_event", GTK_SIGNAL_FUNC (draw_text), NULL);
+
+	li = g_new0 (struct line_info, 1);
+	li->x = imhtml->x;
+	li->y = imhtml->y;
+	li->width = width;
+	li->height = imhtml->llheight;
+	if (text)
+		li->ascent = MAX (imhtml->llascent, bit->font->ascent);
+	else
+		li->ascent = 0;
+	li->text = text;
+	li->bit = bit;
+
+	gtk_object_set_user_data (GTK_OBJECT (darea), li);
+
+	if (bit->url) {
+		eventbox = gtk_event_box_new ();
+		gtk_layout_put (GTK_LAYOUT (imhtml), eventbox, imhtml->x, imhtml->y);
+		gtk_signal_connect (GTK_OBJECT (eventbox), "button_press_event",
+				    GTK_SIGNAL_FUNC (click_event_box), imhtml);
+		gtk_widget_show (eventbox);
+
+		gtk_container_add (GTK_CONTAINER (eventbox), darea);
+
+		uw = g_new0 (struct url_widget, 1);
+		uw->x = imhtml->x;
+		uw->y = imhtml->y;
+		uw->width = width;
+		uw->height = imhtml->llheight;
+		uw->url = bit->url;
+		imhtml->urls = g_list_append (imhtml->urls, uw);
+
+		if (!tips)
+			tips = gtk_tooltips_new ();
+		gtk_tooltips_set_tip (tips, eventbox, bit->url, "");
+	} else {
+		gtk_layout_put (GTK_LAYOUT (imhtml), darea, imhtml->x, imhtml->y);
+	}
+
+	bit->chunks = g_list_append (bit->chunks, darea);
+	imhtml->line = g_list_append (imhtml->line, darea);
+	gtk_widget_show (darea);
+
+	if (bit->bg) {
+		GdkColormap *cmap;
+
+		cmap = gdk_colormap_new (gdk_visual_get_best (), FALSE);
+		gdk_color_alloc (cmap, bit->bg);
+		gdk_window_set_background (darea->window, bit->bg);
+		gdk_colormap_unref (cmap);
+	} else
+		gdk_window_set_background (darea->window, &darea->style->white);
+}
+
+static void
+add_img_renderer (GtkIMHtml    *imhtml,
+		  GtkIMHtmlBit *bit)
+{
+	GtkWidget *darea;
+	GtkWidget *eventbox;
+	struct line_info *li;
+	struct url_widget *uw;
+	gint width;
+
+	gdk_window_get_size (bit->pm, &width, NULL);
+
+	darea = gtk_drawing_area_new ();
+	gtk_widget_set_usize (darea, width, imhtml->llheight);
+	gtk_signal_connect (GTK_OBJECT (darea), "expose_event", GTK_SIGNAL_FUNC (draw_img), NULL);
+
+	li = g_new0 (struct line_info, 1);
+	li->x = imhtml->x;
+	li->y = imhtml->y;
+	li->width = width;
+	li->height = imhtml->llheight;
+	li->ascent = 0;
+	li->bit = bit;
+
+	gtk_object_set_user_data (GTK_OBJECT (darea), li);
+
+	if (bit->url) {
+		eventbox = gtk_event_box_new ();
+		gtk_layout_put (GTK_LAYOUT (imhtml), eventbox, imhtml->x, imhtml->y);
+		gtk_signal_connect (GTK_OBJECT (eventbox), "button_press_event",
+				    GTK_SIGNAL_FUNC (click_event_box), imhtml);
+		gtk_widget_show (eventbox);
+
+		gtk_container_add (GTK_CONTAINER (eventbox), darea);
+
+		uw = g_new0 (struct url_widget, 1);
+		uw->x = imhtml->x;
+		uw->y = imhtml->y;
+		uw->width = width;
+		uw->height = imhtml->llheight;
+		uw->url = bit->url;
+		imhtml->urls = g_list_append (imhtml->urls, uw);
+
+		if (!tips)
+			tips = gtk_tooltips_new ();
+		gtk_tooltips_set_tip (tips, eventbox, bit->url, "");
+	} else {
+		gtk_layout_put (GTK_LAYOUT (imhtml), darea, imhtml->x, imhtml->y);
+	}
+
+	bit->chunks = g_list_append (bit->chunks, darea);
+	imhtml->line = g_list_append (imhtml->line, darea);
+	gtk_widget_show (darea);
+
+	if (bit->bg) {
+		GdkColormap *cmap;
+
+		cmap = gdk_colormap_new (gdk_visual_get_best (), FALSE);
+		gdk_color_alloc (cmap, bit->bg);
+		gdk_window_set_background (darea->window, bit->bg);
+		gdk_colormap_unref (cmap);
+	} else
+		gdk_window_set_background (darea->window, &darea->style->white);
+
+	imhtml->x += width;
+}
+
+static void
+gtk_imhtml_draw_bit (GtkIMHtml    *imhtml,
+		     GtkIMHtmlBit *bit)
+{
+	gint width, height;
+	GdkWindow *window;
+	GdkGC *gc;
+
+	g_return_if_fail (imhtml != NULL);
+	g_return_if_fail (GTK_IS_IMHTML (imhtml));
+	g_return_if_fail (bit != NULL);
+
+	window = GTK_LAYOUT (imhtml)->bin_window;
+	gc = gdk_gc_new (window);
+
+	if ( (bit->type == TYPE_TEXT) ||
+	    ((bit->type == TYPE_SMILEY) && !imhtml->smileys) ||
+	    ((bit->type == TYPE_COMMENT) && imhtml->comments)) {
+		gchar *copy = g_strdup (bit->text);
+		gint pos = 0;
+		gboolean seenspace = FALSE;
+		gchar *tmp;
+
+		height = bit->font->ascent + bit->font->descent;
+		width = gdk_string_width (bit->font, bit->text);
+
+		if ((imhtml->x != BORDER_SIZE) &&
+				((imhtml->x + width + BORDER_SIZE + BORDER_SIZE + 5) > imhtml->xsize)) {
+			gint remain = imhtml->xsize - imhtml->x - BORDER_SIZE - BORDER_SIZE - 5;
+			while (gdk_text_width (bit->font, copy, pos) < remain) {
+				if (copy [pos] == ' ')
+					seenspace = TRUE;
+				pos++;
+			}
+			if (seenspace) {
+				while (copy [pos - 1] != ' ') pos--;
+
+				tmp = g_strndup (copy, pos);
+
+				backwards_update (imhtml, bit, height, bit->font->ascent);
+				add_text_renderer (imhtml, bit, tmp);
+			} else
+				pos = 0;
+			seenspace = FALSE;
+			new_line (imhtml);
+			imhtml->llheight = 0;
+			imhtml->llascent = 0;
+		}
+
+		backwards_update (imhtml, bit, height, bit->font->ascent);
+
+		while (pos < strlen (bit->text)) {
+			width = gdk_string_width (bit->font, copy + pos);
+			if (imhtml->x + width + BORDER_SIZE + BORDER_SIZE + 5 > imhtml->xsize) {
+				gint newpos = 0;
+				gint remain = imhtml->xsize - imhtml->x - BORDER_SIZE - BORDER_SIZE - 5;
+				while (gdk_text_width (bit->font, copy + pos, newpos) < remain) {
+					if (copy [pos + newpos] == ' ')
+						seenspace = TRUE;
+					newpos++;
+				}
+
+				if (seenspace)
+					while (copy [pos + newpos - 1] != ' ') newpos--;
+
+				if (newpos == 0)
+					break;
+
+				tmp = g_strndup (copy + pos, newpos);
+				pos += newpos;
+
+				add_text_renderer (imhtml, bit, tmp);
+
+				seenspace = FALSE;
+				new_line (imhtml);
+			} else {
+				tmp = g_strdup (copy + pos);
+
+				add_text_renderer (imhtml, bit, tmp);
+
+				pos = strlen (bit->text);
+
+				imhtml->x += width;
+			}
+		}
+
+		g_free (copy);
+	} else if ((bit->type == TYPE_SMILEY) || (bit->type == TYPE_IMG)) {
+		gdk_window_get_size (bit->pm, &width, &height);
+
+		if ((imhtml->x != BORDER_SIZE) &&
+				((imhtml->x + width + BORDER_SIZE + BORDER_SIZE + 5) > imhtml->xsize)) {
+			new_line (imhtml);
+			imhtml->llheight = 0;
+			imhtml->llascent = 0;
+		} else
+			backwards_update (imhtml, bit, height, height * 3 / 4);
+
+		add_img_renderer (imhtml, bit);
+	} else if (bit->type == TYPE_BR) {
+		new_line (imhtml);
+		imhtml->llheight = 0;
+		imhtml->llascent = 0;
+		add_text_renderer (imhtml, bit, NULL);
+	} else if (bit->type == TYPE_SEP) {
+		GtkWidget *darea;
+		if (imhtml->llheight) {
+			new_line (imhtml);
+			imhtml->llheight = 0;
+			imhtml->llascent = 0;
+		}
+		darea = gtk_drawing_area_new ();
+		gtk_widget_set_usize (darea, imhtml->xsize - (BORDER_SIZE * 2), HR_HEIGHT * 2);
+		gtk_layout_put (GTK_LAYOUT (imhtml), darea, imhtml->x, imhtml->y);
+		gtk_signal_connect (GTK_OBJECT (darea), "expose_event",
+				    GTK_SIGNAL_FUNC (draw_line), bit);
+		gtk_widget_show (darea);
+		imhtml->llheight = HR_HEIGHT * 2;
+		new_line (imhtml);
+		imhtml->llheight = 0;
+		imhtml->llascent = 0;
+		add_text_renderer (imhtml, bit, NULL);
+	}
+
+	gtk_layout_set_size (GTK_LAYOUT (imhtml), imhtml->xsize, imhtml->y + 5);
+
+	gdk_gc_destroy (gc);
+}
+
+void
+gtk_imhtml_show_smileys (GtkIMHtml *imhtml,
+			 gboolean   show)
+{
+	g_return_if_fail (imhtml != NULL);
+	g_return_if_fail (GTK_IS_IMHTML (imhtml));
+
+	imhtml->smileys = show;
+
+	if (GTK_WIDGET_VISIBLE (GTK_WIDGET (imhtml)))
+		gtk_imhtml_redraw_all (imhtml);
+}
+
+void
+gtk_imhtml_show_comments (GtkIMHtml *imhtml,
+			  gboolean   show)
+{
+	g_return_if_fail (imhtml != NULL);
+	g_return_if_fail (GTK_IS_IMHTML (imhtml));
+
+	imhtml->comments = show;
+
+	if (GTK_WIDGET_VISIBLE (GTK_WIDGET (imhtml)))
+		gtk_imhtml_redraw_all (imhtml);
+}
+
+static GdkColor *
+gtk_imhtml_get_color (const gchar *color)
+{
+	GdkColor c;
+	gboolean valid = TRUE;
+
+	g_return_val_if_fail (color != NULL, NULL);
+
+	c.red = 0; c.green = 0; c.blue = 0;
+
+	if (!g_strcasecmp (color, "aliceblue")) {
+		c.red = 0xf000; c.green = 0xf800; c.blue = 0xff00;
+	} else if (!g_strcasecmp (color, "antiquewhite")) {
+		c.red = 0xfa00; c.green = 0xeb00; c.blue = 0xd700;
+	} else if (!g_strcasecmp (color, "aqua")) {
+		c.red = 0; c.green = 0xff00; c.blue = 0xff00;
+	} else if (!g_strcasecmp (color, "aquamarine")) {
+		c.red = 0; c.green = 0xff00; c.blue = 0xff00;
+	} else if (!g_strcasecmp (color, "azure")) {
+		c.red = 0xf000; c.green = 0xff00; c.blue = 0xff00;
+	} else if (!g_strcasecmp (color, "beige")) {
+		c.red = 0xf500; c.green = 0xf500; c.blue = 0xdc00;
+	} else if (!g_strcasecmp (color, "bisque")) {
+		c.red = 0xff00; c.green = 0xe400; c.blue = 0xc400;
+	} else if (!g_strcasecmp (color, "black")) {
+		c.red = 0; c.green = 0; c.blue = 0;
+	} else if (!g_strcasecmp (color, "blanchedalmond")) {
+		c.red = 0xff00; c.green = 0xeb00; c.blue = 0xcd00;
+	} else if (!g_strcasecmp (color, "blue")) {
+		c.red = 0; c.green = 0; c.blue = 0xff00;
+	} else if (!g_strcasecmp (color, "blueviolet")) {
+		c.red = 0; c.green = 0; c.blue = 0xff00;
+	} else if (!g_strcasecmp (color, "brown")) {
+		c.red = 0xa500; c.green = 0x2a00; c.blue = 0x2a00;
+	} else if (!g_strcasecmp (color, "burlywood")) {
+		c.red = 0xde00; c.green = 0xb800; c.blue = 0x8700;
+	} else if (!g_strcasecmp (color, "cadetblue")) {
+		c.red = 0x5f00; c.green = 0x9e00; c.blue = 0xa000;
+	} else if (!g_strcasecmp (color, "chartreuse")) {
+		c.red = 0x7f00; c.green = 0xff00; c.blue = 0;
+	} else if (!g_strcasecmp (color, "chocolate")) {
+		c.red = 0xd200; c.green = 0x6900; c.blue = 0x1e00;
+	} else if (!g_strcasecmp (color, "coral")) {
+		c.red = 0xff00; c.green = 0x7f00; c.blue = 0x5000;
+	} else if (!g_strcasecmp (color, "cornflowerblue")) {
+		c.red = 0x6400; c.green = 0x9500; c.blue = 0xed00;
+	} else if (!g_strcasecmp (color, "cornsilk")) {
+		c.red = 0xff00; c.green = 0xf800; c.blue = 0xdc00;
+	} else if (!g_strcasecmp (color, "crimson")) {
+		c.red = 0xdc00; c.green = 0x1400; c.blue = 0x3c00;
+	} else if (!g_strcasecmp (color, "cyan")) {
+		c.red = 0; c.green = 0xff00; c.blue = 0xff00;
+	} else if (!g_strcasecmp (color, "darkblue")) {
+		c.red = 0; c.green = 0; c.blue = 0x8b00;
+	} else if (!g_strcasecmp (color, "darkcyan")) {
+		c.red = 0; c.green = 0x8b00; c.blue = 0x8b00;
+	} else if (!g_strcasecmp (color, "darkgoldenrod")) {
+		c.red = 0xb800; c.green = 0x8600; c.blue = 0x0b00;
+	} else if (!g_strcasecmp (color, "darkgray")) {
+		c.red = 0xa900; c.green = 0xa900; c.blue = 0xa900;
+	} else if (!g_strcasecmp (color, "darkgreen")) {
+		c.red = 0; c.green = 0x6400; c.blue = 0;
+	} else if (!g_strcasecmp (color, "darkkhaki")) {
+		c.red = 0xbd00; c.green = 0xb700; c.blue = 0x6b00;
+	} else if (!g_strcasecmp (color, "darkmagenta")) {
+		c.red = 0x8b00; c.green = 0; c.blue = 0x8b00;
+	} else if (!g_strcasecmp (color, "darkolivegreen")) {
+		c.red = 0x5500; c.green = 0x6b00; c.blue = 0x2f00;
+	} else if (!g_strcasecmp (color, "darkorange")) {
+		c.red = 0xff00; c.green = 0x8c00; c.blue = 0;
+	} else if (!g_strcasecmp (color, "darkorchid")) {
+		c.red = 0x9900; c.green = 0x3200; c.blue = 0xcc00;
+	} else if (!g_strcasecmp (color, "darkred")) {
+		c.red = 0x8b00; c.green = 0; c.blue = 0;
+	} else if (!g_strcasecmp (color, "darksalmon")) {
+		c.red = 0xe900; c.green = 0x9600; c.blue = 0x7a00;
+	} else if (!g_strcasecmp (color, "darkseagreen")) {
+		c.red = 0x8f00; c.green = 0xbc00; c.blue = 0x8f00;
+	} else if (!g_strcasecmp (color, "darkslateblue")) {
+		c.red = 0x4800; c.green = 0x3d00; c.blue = 0x8b00;
+	} else if (!g_strcasecmp (color, "darkslategray")) {
+		c.red = 0x2f00; c.green = 0x4f00; c.blue = 0x4f00;
+	} else if (!g_strcasecmp (color, "darkturquoise")) {
+		c.red = 0; c.green = 0xce00; c.blue = 0xd100;
+	} else if (!g_strcasecmp (color, "darkviolet")) {
+		c.red = 0x9400; c.green = 0; c.blue = 0xd300;
+	} else if (!g_strcasecmp (color, "deeppink")) {
+		c.red = 0xff00; c.green = 0x1400; c.blue = 0x9300;
+	} else if (!g_strcasecmp (color, "deepskyblue")) {
+		c.red = 0; c.green = 0xbf00; c.blue = 0xff00;
+	} else if (!g_strcasecmp (color, "dimgray")) {
+		c.red = 0x6900; c.green = 0x6900; c.blue = 0x6900;
+	} else if (!g_strcasecmp (color, "dodgerblue")) {
+		c.red = 0x1e00; c.green = 0x9000; c.blue = 0xff00;
+	} else if (!g_strcasecmp (color, "firebrick")) {
+		c.red = 0xb200; c.green = 0x2200; c.blue = 0x2200;
+	} else if (!g_strcasecmp (color, "floralwhite")) {
+		c.red = 0xff00; c.green = 0xfa00; c.blue = 0xf000;
+	} else if (!g_strcasecmp (color, "forestgreen")) {
+		c.red = 0x2200; c.green = 0x8b00; c.blue = 0x2200;
+	} else if (!g_strcasecmp (color, "fuchsia")) {
+		c.red = 0xff00; c.green = 0; c.blue = 0xff00;
+	} else if (!g_strcasecmp (color, "gainsboro")) {
+		c.red = 0xdc00; c.green = 0xdc00; c.blue = 0xdc00;
+	} else if (!g_strcasecmp (color, "ghostwhite")) {
+		c.red = 0xf800; c.green = 0xf800; c.blue = 0xff00;
+	} else if (!g_strcasecmp (color, "gold")) {
+		c.red = 0xff00; c.green = 0xd700; c.blue = 0;
+	} else if (!g_strcasecmp (color, "goldenrod")) {
+		c.red = 0xff00; c.green = 0xd700; c.blue = 0;
+	} else if (!g_strcasecmp (color, "gray")) {
+		c.red = 0x8000; c.green = 0x8000; c.blue = 0x8000;
+	} else if (!g_strcasecmp (color, "green")) {
+		c.red = 0; c.green = 0x8000; c.blue = 0;
+	} else if (!g_strcasecmp (color, "greenyellow")) {
+		c.red = 0; c.green = 0x8000; c.blue = 0;
+	} else if (!g_strcasecmp (color, "honeydew")) {
+		c.red = 0xf000; c.green = 0xff00; c.blue = 0xf000;
+	} else if (!g_strcasecmp (color, "hotpink")) {
+		c.red = 0xff00; c.green = 0x6900; c.blue = 0xb400;
+	} else if (!g_strcasecmp (color, "indianred")) {
+		c.red = 0xcd00; c.green = 0x5c00; c.blue = 0x5c00;
+	} else if (!g_strcasecmp (color, "indigo")) {
+		c.red = 0x4b00; c.green = 0; c.blue = 0x8200;
+	} else if (!g_strcasecmp (color, "ivory")) {
+		c.red = 0xff00; c.green = 0xff00; c.blue = 0xf000;
+	} else if (!g_strcasecmp (color, "khaki")) {
+		c.red = 0xf000; c.green = 0xe600; c.blue = 0x8c00;
+	} else if (!g_strcasecmp (color, "lavender")) {
+		c.red = 0xe600; c.green = 0xe600; c.blue = 0xfa00;
+	} else if (!g_strcasecmp (color, "lavenderblush")) {
+		c.red = 0xe600; c.green = 0xe600; c.blue = 0xfa00;
+	} else if (!g_strcasecmp (color, "lawngreen")) {
+		c.red = 0x7c00; c.green = 0xfc00; c.blue = 0;
+	} else if (!g_strcasecmp (color, "lemonchiffon")) {
+		c.red = 0xff00; c.green = 0xfa00; c.blue = 0xcd00;
+	} else if (!g_strcasecmp (color, "lightblue")) {
+		c.red = 0xad00; c.green = 0xd800; c.blue = 0xe600;
+	} else if (!g_strcasecmp (color, "lightcoral")) {
+		c.red = 0xf000; c.green = 0x8000; c.blue = 0x8000;
+	} else if (!g_strcasecmp (color, "lightcyan")) {
+		c.red = 0xe000; c.green = 0xff00; c.blue = 0xff00;
+	} else if (!g_strcasecmp (color, "lightgoldenrodyellow")) {
+		c.red = 0xfa00; c.green = 0xfa00; c.blue = 0xd200;
+	} else if (!g_strcasecmp (color, "lightgreen")) {
+		c.red = 0x9000; c.green = 0xee00; c.blue = 0x9000;
+	} else if (!g_strcasecmp (color, "lightgray")) {
+		c.red = 0xd300; c.green = 0xd300; c.blue = 0xd300;
+	} else if (!g_strcasecmp (color, "lightpink")) {
+		c.red = 0xff00; c.green = 0xb600; c.blue = 0xc100;
+	} else if (!g_strcasecmp (color, "lightsalmon")) {
+		c.red = 0xff00; c.green = 0xa000; c.blue = 0x7a00;
+	} else if (!g_strcasecmp (color, "lightseagreen")) {
+		c.red = 0x2000; c.green = 0xb200; c.blue = 0xaa00;
+	} else if (!g_strcasecmp (color, "lightskyblue")) {
+		c.red = 0x8700; c.green = 0xce00; c.blue = 0xfa00;
+	} else if (!g_strcasecmp (color, "lightslategray")) {
+		c.red = 0x7700; c.green = 0x8800; c.blue = 0x9900;
+	} else if (!g_strcasecmp (color, "lightsteelblue")) {
+		c.red = 0xb000; c.green = 0xc400; c.blue = 0xde00;
+	} else if (!g_strcasecmp (color, "lightyellow")) {
+		c.red = 0xff00; c.green = 0xff00; c.blue = 0xe000;
+	} else if (!g_strcasecmp (color, "lime")) {
+		c.red = 0; c.green = 0xff00; c.blue = 0;
+	} else if (!g_strcasecmp (color, "limegreen")) {
+		c.red = 0; c.green = 0xff00; c.blue = 0;
+	} else if (!g_strcasecmp (color, "linen")) {
+		c.red = 0xfa00; c.green = 0xf000; c.blue = 0xe600;
+	} else if (!g_strcasecmp (color, "magenta")) {
+		c.red = 0xff00; c.green = 0; c.blue = 0xff00;
+	} else if (!g_strcasecmp (color, "maroon")) {
+		c.red = 0x8000; c.green = 0; c.blue = 0;
+	} else if (!g_strcasecmp (color, "mediumaquamarine")) {
+		c.red = 0x6600; c.green = 0xcd00; c.blue = 0xaa00;
+	} else if (!g_strcasecmp (color, "mediumblue")) {
+		c.red = 0; c.green = 0; c.blue = 0xcd00;
+	} else if (!g_strcasecmp (color, "mediumorchid")) {
+		c.red = 0xba00; c.green = 0x5500; c.blue = 0xd300;
+	} else if (!g_strcasecmp (color, "mediumpurple")) {
+		c.red = 0x93; c.green = 0x7000; c.blue = 0xdb00;
+	} else if (!g_strcasecmp (color, "mediumseagreen")) {
+		c.red = 0x3c00; c.green = 0xb300; c.blue = 0x7100;
+	} else if (!g_strcasecmp (color, "mediumslateblue")) {
+		c.red = 0x7b00; c.green = 0x6800; c.blue = 0xee00;
+	} else if (!g_strcasecmp (color, "mediumspringgreen")) {
+		c.red = 0; c.green = 0xfa00; c.blue = 0x9a00;
+	} else if (!g_strcasecmp (color, "mediumturquoise")) {
+		c.red = 0x4800; c.green = 0xd100; c.blue = 0xcc00;
+	} else if (!g_strcasecmp (color, "mediumvioletred")) {
+		c.red = 0xc700; c.green = 0x1500; c.blue = 0x8500;
+	} else if (!g_strcasecmp (color, "midnightblue")) {
+		c.red = 0x1900; c.green = 0x1900; c.blue = 0x7000;
+	} else if (!g_strcasecmp (color, "mintcream")) {
+		c.red = 0xf500; c.green = 0xff00; c.blue = 0xfa00;
+	} else if (!g_strcasecmp (color, "mistyrose")) {
+		c.red = 0xff00; c.green = 0xe400; c.blue = 0xe100;
+	} else if (!g_strcasecmp (color, "moccasin")) {
+		c.red = 0xff00; c.green = 0xe400; c.blue = 0xb500;
+	} else if (!g_strcasecmp (color, "navajowhite")) {
+		c.red = 0xff00; c.green = 0xde00; c.blue = 0xad00;
+	} else if (!g_strcasecmp (color, "navy")) {
+		c.red = 0; c.green = 0x8000; c.blue = 0;
+	} else if (!g_strcasecmp (color, "oldlace")) {
+		c.red = 0xfd00; c.green = 0xf500; c.blue = 0xe600;
+	} else if (!g_strcasecmp (color, "olive")) {
+		c.red = 0x8000; c.green = 0x8000; c.blue = 0;
+	} else if (!g_strcasecmp (color, "olivedrab")) {
+		c.red = 0x8000; c.green = 0x8000; c.blue = 0;
+	} else if (!g_strcasecmp (color, "orange")) {
+		c.red = 0xff00; c.green = 0xa500; c.blue = 0;
+	} else if (!g_strcasecmp (color, "orangered")) {
+		c.red = 0xff00; c.green = 0xa500; c.blue = 0;
+	} else if (!g_strcasecmp (color, "orchid")) {
+		c.red = 0xda00; c.green = 0x7000; c.blue = 0xd600;
+	} else if (!g_strcasecmp (color, "palegoldenrod")) {
+		c.red = 0xee00; c.green = 0xe800; c.blue = 0xaa00;
+	} else if (!g_strcasecmp (color, "palegreen")) {
+		c.red = 0x9800; c.green = 0xfb00; c.blue = 0x9800;
+	} else if (!g_strcasecmp (color, "paleturquoise")) {
+		c.red = 0xaf00; c.green = 0xee00; c.blue = 0xee00;
+	} else if (!g_strcasecmp (color, "palevioletred")) {
+		c.red = 0xdb00; c.green = 0x7000; c.blue = 0x9300;
+	} else if (!g_strcasecmp (color, "papayawhip")) {
+		c.red = 0xff00; c.green = 0xef00; c.blue = 0xd500;
+	} else if (!g_strcasecmp (color, "peachpuff")) {
+		c.red = 0xff00; c.green = 0xda00; c.blue = 0xb900;
+	} else if (!g_strcasecmp (color, "peru")) {
+		c.red = 0xcd00; c.green = 0x8500; c.blue = 0x3f00;
+	} else if (!g_strcasecmp (color, "pink")) {
+		c.red = 0xff00; c.green = 0xc000; c.blue = 0xcb00;
+	} else if (!g_strcasecmp (color, "plum")) {
+		c.red = 0xdd00; c.green = 0xa000; c.blue = 0xdd00;
+	} else if (!g_strcasecmp (color, "powderblue")) {
+		c.red = 0xb000; c.green = 0xe000; c.blue = 0xe600;
+	} else if (!g_strcasecmp (color, "purple")) {
+		c.red = 0x8000; c.green = 0; c.blue = 0x8000;
+	} else if (!g_strcasecmp (color, "red")) {
+		c.red = 0xff00; c.green = 0; c.blue = 0;
+	} else if (!g_strcasecmp (color, "rosybrown")) {
+		c.red = 0xbc00; c.green = 0x8f00; c.blue = 0x8f00;
+	} else if (!g_strcasecmp (color, "royalblue")) {
+		c.red = 0x4100; c.green = 0x6900; c.blue = 0xe100;
+	} else if (!g_strcasecmp (color, "saddlebrown")) {
+		c.red = 0x8b00; c.green = 0x4500; c.blue = 0x1300;
+	} else if (!g_strcasecmp (color, "salmon")) {
+		c.red = 0xfa00; c.green = 0x8000; c.blue = 0x7200;
+	} else if (!g_strcasecmp (color, "sandybrown")) {
+		c.red = 0xf400; c.green = 0xa400; c.blue = 0x6000;
+	} else if (!g_strcasecmp (color, "seagreen")) {
+		c.red = 0x2e00; c.green = 0x8b00; c.blue = 0x5700;
+	} else if (!g_strcasecmp (color, "seashell")) {
+		c.red = 0xff00; c.green = 0xf500; c.blue = 0xee00;
+	} else if (!g_strcasecmp (color, "sienna")) {
+		c.red = 0xa000; c.green = 0x5200; c.blue = 0x2d00;
+	} else if (!g_strcasecmp (color, "silver")) {
+		c.red = 0xc000; c.green = 0xc000; c.blue = 0xc000;
+	} else if (!g_strcasecmp (color, "skyblue")) {
+		c.red = 0x8700; c.green = 0xce00; c.blue = 0xeb00;
+	} else if (!g_strcasecmp (color, "slateblue")) {
+		c.red = 0x6a00; c.green = 0x5a00; c.blue = 0xcd00;
+	} else if (!g_strcasecmp (color, "slategray")) {
+		c.red = 0x7000; c.green = 0x8000; c.blue = 0x9000;
+	} else if (!g_strcasecmp (color, "springgreen")) {
+		c.red = 0; c.green = 0xff00; c.blue = 0x7f00;
+	} else if (!g_strcasecmp (color, "steelblue")) {
+		c.red = 0x4600; c.green = 0x8200; c.blue = 0xb400;
+	} else if (!g_strcasecmp (color, "teal")) {
+		c.red = 0; c.green = 0x8000; c.blue = 0x8000;
+	} else if (!g_strcasecmp (color, "thistle")) {
+		c.red = 0xd800; c.green = 0xbf00; c.blue = 0xd800;
+	} else if (!g_strcasecmp (color, "tomato")) {
+		c.red = 0xff00; c.green = 0x6300; c.blue = 0x4700;
+	} else if (!g_strcasecmp (color, "turquoise")) {
+		c.red = 0x4000; c.green = 0xe000; c.blue = 0xd000;
+	} else if (!g_strcasecmp (color, "violet")) {
+		c.red = 0xee00; c.green = 0x8200; c.blue = 0xee00;
+	} else if (!g_strcasecmp (color, "wheat")) {
+		c.red = 0xf500; c.green = 0xde00; c.blue = 0xb300;
+	} else if (!g_strcasecmp (color, "white")) {
+		c.red = 0xfe00; c.green = 0xfe00; c.blue = 0xfe00;
+	} else if (!g_strcasecmp (color, "whitesmoke")) {
+		c.red = 0xfe00; c.green = 0xfe00; c.blue = 0xfe00;
+	} else if (!g_strcasecmp (color, "yellow")) {
+		c.red = 0xff00; c.green = 0xff00; c.blue = 0;
+	} else if (!g_strcasecmp (color, "yellowgreen")) {
+		c.red = 0xff00; c.green = 0xff00; c.blue = 0;
+	} else {
+		const gchar *hex;
+		guint32 value;
+
+		if (color [0] == '#')
+			hex = color + 1;
+		else
+			hex = color;
+
+		if (strlen (hex) == 6) {
+			gint i = 0;
+			for ( ; i < 6; i++)
+				if (!isxdigit ((gint) hex [i]))
+					break;
+			if (i == 6) {
+				sscanf (hex, "%x", &value);
+				c.red = (value & 0xff0000) >> 8;
+				c.green = value & 0xff00;
+				c.blue = (value & 0xff) << 8;
+			} else {
+				valid = FALSE;
+			}
+		} else {
+			valid = FALSE;
+		}
+	}
+
+	if (valid)
+		return gdk_color_copy (&c);
+
+	return NULL;
+}
+
+static gint
+gtk_imhtml_is_smiley (GtkIMHtml   *imhtml,
+		      const gchar *text)
+{
+	gchar *tmp;
+	gint i;
+
+	g_return_val_if_fail (imhtml != NULL, 0);
+	g_return_val_if_fail (GTK_IS_IMHTML (imhtml), 0);
+	g_return_val_if_fail (text != NULL, 0);
+
+	tmp = g_malloc (imhtml->smax + 1);
+
+	for (i = imhtml->smin; i <= imhtml->smax; i++) {
+		if (strlen (text) < i) {
+			g_free (tmp);
+			return 0;
+		}
+		g_snprintf (tmp, i + 1, "%s", text);
+		if (g_hash_table_lookup (imhtml->smiley_hash, tmp)) {
+			g_free (tmp);
+			return i;
+		}
+	}
+
+	g_free (tmp);
+	return 0;
+}
+
+static GtkIMHtmlBit *
+gtk_imhtml_new_bit (GtkIMHtml  *imhtml,
+		    gint        type,
+		    gchar      *text,
+		    gint        bold,
+		    gint        italics,
+		    gint        underline,
+		    gint        strike,
+		    FontDetail *font,
+		    GdkColor   *bg,
+		    gchar      *url)
+{
+	GtkIMHtmlBit *bit = NULL;
+
+	g_return_val_if_fail (imhtml != NULL, NULL);
+	g_return_val_if_fail (GTK_IS_IMHTML (imhtml), NULL);
+
+	if ((type == TYPE_TEXT) && ((text == NULL) || (strlen (text) == 0)))
+		return NULL;
+
+	bit = g_new0 (GtkIMHtmlBit, 1);
+	bit->type = type;
+
+	if ((text != NULL) && (strlen (text) != 0))
+		bit->text = g_strdup (text);
+
+	if ((font != NULL) || bold || italics) {
+		if (font && (bold || italics || font->size || font->face)) {
+			bit->font = gtk_imhtml_font_load (imhtml, font->face, bold, italics, font->size);
+		} else if (bold || italics) {
+			bit->font = gtk_imhtml_font_load (imhtml, NULL, bold, italics, 0);
+		}
+
+		if (font && (type != TYPE_BR)) {
+			if (font->fore != NULL)
+				bit->fore = gdk_color_copy (font->fore);
+
+			if (font->back != NULL)
+				bit->back = gdk_color_copy (font->back);
+		}
+	}
+
+	if (((bit->type == TYPE_TEXT) || (bit->type == TYPE_SMILEY) || (bit->type == TYPE_COMMENT)) &&
+	    (bit->font == NULL))
+		bit->font = gdk_font_ref (imhtml->default_font);
+
+	if (bg != NULL)
+		bit->bg = gdk_color_copy (bg);
+
+	bit->underline = underline;
+	bit->strike = strike;
+
+	if (url != NULL)
+		bit->url = g_strdup (url);
+
+	if (type == TYPE_SMILEY) {
+		GdkColor *clr;
+
+		if ((font != NULL) && (font->back != NULL))
+			clr = font->back;
+		else
+			clr = (bg != NULL) ? bg : &GTK_WIDGET (imhtml)->style->white;
+
+		bit->pm = gdk_pixmap_create_from_xpm_d (GTK_WIDGET (imhtml)->window,
+							&bit->bm,
+							clr,
+							g_hash_table_lookup (imhtml->smiley_hash, text));
+	}
+
+	return bit;
+}
+
+#define NEW_TEXT_BIT    gtk_imhtml_new_bit (imhtml, TYPE_TEXT, ws, bold, italics, underline, strike, \
+				fonts ? fonts->data : NULL, bg, url)
+#define NEW_SMILEY_BIT  gtk_imhtml_new_bit (imhtml, TYPE_SMILEY, ws, bold, italics, underline, strike, \
+				fonts ? fonts->data : NULL, bg, url)
+#define NEW_SEP_BIT     gtk_imhtml_new_bit (imhtml, TYPE_SEP, NULL, 0, 0, 0, 0, NULL, bg, NULL)
+#define NEW_BR_BIT      gtk_imhtml_new_bit (imhtml, TYPE_BR, NULL, 0, 0, 0, 0, \
+				fonts ? fonts->data : NULL, bg, NULL)
+#define NEW_COMMENT_BIT gtk_imhtml_new_bit (imhtml, TYPE_COMMENT, ws, bold, italics, underline, strike, \
+				fonts ? fonts->data : NULL, bg, url)
+
+#define NEW_BIT(bit) { GtkIMHtmlBit *tmp = bit; if (tmp != NULL) \
+				newbits = g_list_append (newbits, tmp); }
+
+#define UPDATE_BG_COLORS \
+	{ \
+		GdkColormap *cmap; \
+		GList *rev; \
+		cmap = gdk_colormap_new (gdk_visual_get_best (), FALSE); \
+		rev = g_list_last (newbits); \
+		while (rev) { \
+			GtkIMHtmlBit *bit = rev->data; \
+			if (bit->type == TYPE_BR) \
+				break; \
+			if (bit->bg) \
+				gdk_color_free (bit->bg); \
+			bit->bg = gdk_color_copy (bg); \
+			rev = g_list_previous (rev); \
+		} \
+		if (!rev) { \
+			rev = g_list_last (imhtml->bits); \
+			while (rev) { \
+				GList *ln; \
+				GtkIMHtmlBit *bit = rev->data; \
+				if (bit->type == TYPE_BR) \
+					break; \
+				if (bit->bg) \
+					gdk_color_free (bit->bg); \
+				bit->bg = gdk_color_copy (bg); \
+				gdk_color_alloc (cmap, bit->bg); \
+				ln = bit->chunks; \
+				while (ln) { \
+					GtkWidget *widget = ln->data; \
+					gdk_window_set_background (widget->window, bit->bg); \
+					ln = g_list_next (ln); \
+				} \
+				rev = g_list_previous (rev); \
+			} \
+			gdk_colormap_unref (cmap); \
+		} \
+	}
+
+GString*
+gtk_imhtml_append_text (GtkIMHtml        *imhtml,
+			const gchar      *text,
+			GtkIMHtmlOptions  options)
+{
+	const gchar *c;
+	gboolean intag = FALSE;
+	gboolean tagquote = FALSE;
+	gboolean incomment = FALSE;
+	gchar *ws;
+	gchar *tag;
+	gint wpos = 0;
+	gint tpos = 0;
+	int smilelen;
+	GList *newbits = NULL;
+
+	guint	bold = 0,
+		italics = 0,
+		underline = 0,
+		strike = 0,
+		sub = 0,
+		sup = 0,
+		title = 0;
+	GSList *fonts = NULL;
+	GdkColor *bg = NULL;
+	gchar *url = NULL;
+
+	GtkAdjustment *vadj;
+	gboolean scrolldown = TRUE;
+
+	GString *retval = NULL;
+
+	g_return_val_if_fail (imhtml != NULL, NULL);
+	g_return_val_if_fail (GTK_IS_IMHTML (imhtml), NULL);
+	g_return_val_if_fail (text != NULL, NULL);
+
+	if (options & GTK_IMHTML_RETURN_LOG)
+		retval = g_string_new ("");
+
+	vadj = GTK_LAYOUT (imhtml)->vadjustment;
+	if ((vadj->value < imhtml->y + 5 - GTK_WIDGET (imhtml)->allocation.height) &&
+	    (vadj->upper >= GTK_WIDGET (imhtml)->allocation.height))
+		scrolldown = FALSE;
+
+	c = text;
+	ws = g_malloc (strlen (text) + 1);
+	tag = g_malloc (strlen (text) + 1);
+
+	ws [0] = '\0';
+
+	while (*c) {
+		if (*c == '<') {
+			if (intag) {
+				ws [wpos] = 0;
+				tag [tpos] = 0;
+				tpos = 0;
+				strcat (ws, tag);
+				wpos = strlen (ws);
+			}
+
+			if (incomment) {
+				ws [wpos++] = *c++;
+				continue;
+			}
+
+			if (!g_strncasecmp (c, "<!--", strlen ("<!--"))) {
+				if (!(options & GTK_IMHTML_NO_COMMENTS)) {
+					ws [wpos] = 0;
+					wpos = 0;
+					tag [tpos] = 0;
+					strcat (tag, ws);
+					incomment = TRUE;
+					intag = FALSE;
+				}
+				ws [wpos++] = *c++;
+				ws [wpos++] = *c++;
+				ws [wpos++] = *c++;
+				ws [wpos++] = *c++;
+				continue;
+			}
+
+			tag [tpos++] = *c++;
+			intag = TRUE;
+		} else if (incomment && (*c == '-') && !g_strncasecmp (c, "-->", strlen ("-->"))) {
+			gchar *tmp;
+			ws [wpos] = 0;
+			wpos = 0;
+			tmp = g_strdup (ws);
+			ws [wpos] = 0;
+			strcat (ws, tag);
+			NEW_BIT (NEW_TEXT_BIT);
+			ws [wpos] = 0;
+			strcat (ws, tmp + strlen ("<!--"));
+			g_free (tmp);
+			NEW_BIT (NEW_COMMENT_BIT);
+			incomment = FALSE;
+			c += strlen ("-->");
+		} else if (*c == '>' && intag && !tagquote) {
+			gboolean got_tag = FALSE;
+			tag [tpos++] = *c++;
+			tag [tpos] = 0;
+			ws [wpos] = 0;
+
+			if (!g_strcasecmp (tag, "<B>") || !g_strcasecmp (tag, "<BOLD>")) {
+				got_tag = TRUE;
+				NEW_BIT (NEW_TEXT_BIT);
+				bold++;
+			} else if (!g_strcasecmp (tag, "</B>") || !g_strcasecmp (tag, "</BOLD>")) {
+				got_tag = TRUE;
+				if (bold) {
+					NEW_BIT (NEW_TEXT_BIT);
+					bold--;
+				}
+			} else if (!g_strcasecmp (tag, "<I>") || !g_strcasecmp (tag, "<ITALIC>")) {
+				got_tag = TRUE;
+				NEW_BIT (NEW_TEXT_BIT);
+				italics++;
+			} else if (!g_strcasecmp (tag, "</I>") || !g_strcasecmp (tag, "</ITALIC>")) {
+				got_tag = TRUE;
+				if (italics) {
+					NEW_BIT (NEW_TEXT_BIT);
+					italics--;
+				}
+			} else if (!g_strcasecmp (tag, "<U>") || !g_strcasecmp (tag, "<UNDERLINE>")) {
+				got_tag = TRUE;
+				NEW_BIT (NEW_TEXT_BIT);
+				underline++;
+			} else if (!g_strcasecmp (tag, "</U>") || !g_strcasecmp (tag, "</UNDERLINE>")) {
+				got_tag = TRUE;
+				if (underline) {
+					NEW_BIT (NEW_TEXT_BIT);
+					underline--;
+				}
+			} else if (!g_strcasecmp (tag, "<S>") || !g_strcasecmp (tag, "<STRIKE>")) {
+				got_tag = TRUE;
+				NEW_BIT (NEW_TEXT_BIT);
+				strike++;
+			} else if (!g_strcasecmp (tag, "</S>") || !g_strcasecmp (tag, "</STRIKE>")) {
+				got_tag = TRUE;
+				if (strike) {
+					NEW_BIT (NEW_TEXT_BIT);
+					strike--;
+				}
+			} else if (!g_strcasecmp (tag, "<SUB>")) {
+				got_tag = TRUE;
+				sub++;
+			} else if (!g_strcasecmp (tag, "</SUB>")) {
+				got_tag = TRUE;
+				if (sub) {
+					sub--;
+				}
+			} else if (!g_strcasecmp (tag, "<SUP>")) {
+				got_tag = TRUE;
+				sup++;
+			} else if (!g_strcasecmp (tag, "</SUP>")) {
+				got_tag = TRUE;
+				if (sup) {
+					sup--;
+				}
+			} else if (!g_strcasecmp (tag, "<TITLE>")) {
+				if (options & GTK_IMHTML_NO_TITLE) {
+					got_tag = TRUE;
+					title++;
+				} else {
+					intag = FALSE;
+					tpos = 0;
+					continue;
+				}
+			} else if (!g_strcasecmp (tag, "</TITLE>")) {
+				if (title) {
+					got_tag = TRUE;
+					wpos = 0;
+					ws [wpos] = '\0';
+					title--;
+				} else {
+					intag = FALSE;
+					tpos = 0;
+					continue;
+				}
+			} else if (!g_strcasecmp (tag, "<BR>")) {
+				got_tag = TRUE;
+				NEW_BIT (NEW_TEXT_BIT);
+				NEW_BIT (NEW_BR_BIT);
+			} else if (!g_strcasecmp (tag, "<HR>") ||
+				   !g_strncasecmp (tag, "<HR ", strlen ("<HR "))) {
+				got_tag = TRUE;
+				NEW_BIT (NEW_TEXT_BIT);
+				NEW_BIT (NEW_SEP_BIT);
+			} else if (!g_strncasecmp (tag, "<FONT ", strlen ("<FONT "))) {
+				gchar *t, *e, *a, *value;
+				FontDetail *font = NULL;
+				GdkColor *clr;
+				gint saw;
+				gint i;
+
+				t = tag + strlen ("<FONT ");
+
+				while (*t != '\0') {
+					value = NULL;
+					saw = 0;
+
+					while (g_strncasecmp (t, "COLOR=", strlen ("COLOR="))
+					    && g_strncasecmp (t, "BACK=", strlen ("BACK="))
+					    && g_strncasecmp (t, "FACE=", strlen ("FACE="))
+					    && g_strncasecmp (t, "SIZE=", strlen ("SIZE="))) {
+						gboolean quote = FALSE;
+						if (*t == '\0') break;
+						while (*t && !((*t == ' ') && !quote)) {
+							if (*t == '\"')
+								quote = ! quote;
+							t++;
+						}
+						while (*t && (*t == ' ')) t++;
+					}
+
+					if (!g_strncasecmp (t, "COLOR=", strlen ("COLOR="))) {
+						t += strlen ("COLOR=");
+						saw = 1;
+					} else if (!g_strncasecmp (t, "BACK=", strlen ("BACK="))) {
+						t += strlen ("BACK=");
+						saw = 2;
+					} else if (!g_strncasecmp (t, "FACE=", strlen ("FACE="))) {
+						t += strlen ("FACE=");
+						saw = 3;
+					} else if (!g_strncasecmp (t, "SIZE=", strlen ("SIZE="))) {
+						t += strlen ("SIZE=");
+						saw = 4;
+					}
+
+					if (!saw)
+						continue;
+
+					if ((*t == '\"') || (*t == '\'')) {
+						e = a = ++t;
+						while (*e && (*e != *(t - 1))) e++;
+						if (*e != '\0') {
+							*e = '\0';
+							t = e + 1;
+							value = g_strdup (a);
+						} else {
+							*t = '\0';
+						}
+					} else {
+						e = a = t;
+						while (*e && !isspace ((gint) *e)) e++;
+						if (*e == '\0') e--;
+						*e = '\0';
+						t = e + 1;
+						value = g_strdup (a);
+					}
+
+					if (value == NULL)
+						continue;
+
+					if (font == NULL)
+						font = g_new0 (FontDetail, 1);
+
+					switch (saw) {
+					case 1:
+						clr = gtk_imhtml_get_color (value);
+						if (clr != NULL) {
+							if ( (font->fore == NULL) &&
+							    !(options & GTK_IMHTML_NO_COLOURS))
+								font->fore = clr;
+						}
+						break;
+					case 2:
+						clr = gtk_imhtml_get_color (value);
+						if (clr != NULL) {
+							if ( (font->back == NULL) &&
+							    !(options & GTK_IMHTML_NO_COLOURS))
+								font->back = clr;
+						}
+						break;
+					case 3:
+						if ( (font->face == NULL) &&
+						    !(options & GTK_IMHTML_NO_FONTS))
+							font->face = g_strdup (value);
+						break;
+					case 4:
+						if ((font->size != 0) ||
+						    (options & GTK_IMHTML_NO_SIZES))
+							break;
+
+						if (isdigit ((gint) value [0])) {
+							for (i = 0; i < strlen (value); i++)
+								if (!isdigit ((gint) value [i]))
+									break;
+							if (i != strlen (value))
+								break;
+
+							sscanf (value, "%hd", &font->size);
+							break;
+						}
+
+						if ((value [0] == '+') && (value [1] != '\0')) {
+							for (i = 1; i < strlen (value); i++)
+								if (!isdigit ((gint) value [i]))
+									break;
+							if (i != strlen (value))
+								break;
+
+							sscanf (value + 1, "%hd", &font->size);
+							font->size += 3;
+							break;
+						}
+
+						if ((value [0] == '-') && (value [1] != '\0')) {
+							for (i = 1; i < strlen (value); i++)
+								if (!isdigit ((gint) value [i]))
+									break;
+							if (i != strlen (value))
+								break;
+
+							sscanf (value + 1, "%hd", &font->size);
+							font->size = MIN (font->size, 2);
+							font->size = 3 - font->size;
+							break;
+						}
+
+						break;
+					}
+
+					g_free (value);
+				}
+
+				if (!font) {
+					intag = FALSE;
+					tpos = 0;
+					continue;
+				}
+
+				if (!(font->size || font->face || font->fore || font->back)) {
+					g_free (font);
+					intag = FALSE;
+					tpos = 0;
+					continue;
+				}
+
+				NEW_BIT (NEW_TEXT_BIT);
+
+				if (fonts) {
+					FontDetail *oldfont = fonts->data;
+					if (!font->size)
+						font->size = oldfont->size;
+					if (!font->face)
+						font->face = g_strdup (oldfont->face);
+					if (!font->fore && oldfont->fore)
+						font->fore = gdk_color_copy (oldfont->fore);
+					if (!font->back && oldfont->back)
+						font->back = gdk_color_copy (oldfont->back);
+				} else {
+					if (!font->size)
+						font->size = 3;
+					if (!font->face)
+						font->face = g_strdup ("helvetica");
+				}
+
+				fonts = g_slist_prepend (fonts, font);
+				got_tag = TRUE;
+			} else if (!g_strcasecmp (tag, "</FONT>")) {
+				FontDetail *font;
+
+				if (fonts) {
+					got_tag = TRUE;
+					NEW_BIT (NEW_TEXT_BIT);
+					font = fonts->data;
+					fonts = g_slist_remove (fonts, font);
+					g_free (font->face);
+					if (font->fore)
+						gdk_color_free (font->fore);
+					if (font->back)
+						gdk_color_free (font->back);
+					g_free (font);
+				} else {
+					intag = FALSE;
+					tpos = 0;
+					continue;
+				}
+			} else if (!g_strncasecmp (tag, "<BODY ", strlen ("<BODY "))) {
+				gchar *t, *e, *color = NULL;
+				GdkColor *tmp;
+
+				got_tag = TRUE;
+
+				if (!(options & GTK_IMHTML_NO_COLOURS)) {
+					t = tag + strlen ("<BODY");
+					do {
+						gboolean quote = FALSE;
+						if (*t == '\0') break;
+						while (*t && !((*t == ' ') && !quote)) {
+							if (*t == '\"')
+								quote = ! quote;
+							t++;
+						}
+						while (*t && (*t == ' ')) t++;
+					} while (g_strncasecmp (t, "BGCOLOR=", strlen ("BGCOLOR=")));
+
+					if (!g_strncasecmp (t, "BGCOLOR=", strlen ("BGCOLOR="))) {
+						t += strlen ("BGCOLOR=");
+						if ((*t == '\"') || (*t == '\'')) {
+							e = ++t;
+							while (*e && (*e != *(t - 1))) e++;
+							if (*e != '\0') {
+								*e = '\0';
+								color = g_strdup (t);
+							}
+						} else {
+							e = t;
+							while (*e && !isspace ((gint) *e)) e++;
+							if (*e == '\0') e--;
+							*e = '\0';
+							color = g_strdup (t);
+						}
+
+						if (color != NULL) {
+							tmp = gtk_imhtml_get_color (color);
+							g_free (color);
+							if (tmp != NULL) {
+								NEW_BIT (NEW_TEXT_BIT);
+								bg = tmp;
+								UPDATE_BG_COLORS;
+							}
+						}
+					}
+				}
+			} else if (!g_strncasecmp (tag, "<A ", strlen ("<A "))) {
+				gchar *t, *e;
+
+				got_tag = TRUE;
+				NEW_BIT (NEW_TEXT_BIT);
+
+				if (url != NULL)
+					g_free (url);
+				url = NULL;
+
+				t = tag + strlen ("<A");
+				do {
+					gboolean quote = FALSE;
+					if (*t == '\0') break;
+					while (*t && !((*t == ' ') && !quote)) {
+						if (*t == '\"')
+							quote = ! quote;
+						t++;
+					}
+					while (*t && (*t == ' ')) t++;
+				} while (g_strncasecmp (t, "HREF=", strlen ("HREF=")));
+
+				if (!g_strncasecmp (t, "HREF=", strlen ("HREF="))) {
+					t += strlen ("HREF=");
+					if ((*t == '\"') || (*t == '\'')) {
+						e = ++t;
+						while (*e && (*e != *(t - 1))) e++;
+						if (*e != '\0') {
+							*e = '\0';
+							url = g_strdup (t);
+						}
+					} else {
+						e = t;
+						while (*e && !isspace ((gint) *e)) e++;
+						if (*e == '\0') e--;
+						*e = '\0';
+						url = g_strdup (t);
+					}
+				}
+			} else if (!g_strcasecmp (tag, "</A>")) {
+				got_tag = TRUE;
+				if (url != NULL) {
+					NEW_BIT (NEW_TEXT_BIT);
+					g_free (url);
+				}
+				url = NULL;
+			} else if (!g_strncasecmp (tag, "<IMG ", strlen ("<IMG "))) {
+				gchar *t, *e, *src = NULL;
+				gchar *copy = g_strdup (tag);
+				gchar **xpm;
+				GdkColor *clr = NULL;
+				GtkIMHtmlBit *bit;
+
+				intag = FALSE;
+				tpos = 0;
+
+				if (imhtml->img == NULL)
+					continue;
+
+				t = tag + strlen ("<IMG");
+				do {
+					gboolean quote = FALSE;
+					if (*t == '\0') break;
+					while (*t && !((*t == ' ') && !quote)) {
+						if (*t == '\"')
+							quote = ! quote;
+						t++;
+					}
+					while (*t && (*t == ' ')) t++;
+				} while (g_strncasecmp (t, "SRC=", strlen ("SRC=")));
+
+				if (!g_strncasecmp (t, "SRC=", strlen ("SRC="))) {
+					t += strlen ("SRC=");
+					if ((*t == '\"') || (*t == '\'')) {
+						e = ++t;
+						while (*e && (*e != *(t - 1))) e++;
+						if (*e != '\0') {
+							*e = '\0';
+							src = g_strdup (t);
+						}
+					} else {
+						e = t;
+						while (*e && !isspace ((gint) *e)) e++;
+						if (*e == '\0') e--;
+						*e = '\0';
+						src = g_strdup (t);
+					}
+				}
+
+				if (src == NULL) {
+					ws [wpos] = 0;
+					strcat (ws, copy);
+					wpos = strlen (ws);
+					g_free (copy);
+					continue;
+				}
+
+				xpm = (* imhtml->img) (src);
+				if (xpm == NULL) {
+					g_free (src);
+					ws [wpos] = 0;
+					strcat (ws, copy);
+					wpos = strlen (ws);
+					g_free (copy);
+					continue;
+				}
+
+				g_free (copy);
+
+				if (!fonts || ((clr = ((FontDetail *)fonts->data)->back) == NULL))
+					clr = (bg != NULL) ? bg : &GTK_WIDGET (imhtml)->style->white;
+
+				if (!GTK_WIDGET_REALIZED (imhtml))
+					gtk_widget_realize (GTK_WIDGET (imhtml));
+
+				bit = g_new0 (GtkIMHtmlBit, 1);
+				bit->type = TYPE_IMG;
+				bit->pm = gdk_pixmap_create_from_xpm_d (GTK_WIDGET (imhtml)->window,
+									&bit->bm,
+									clr,
+									xpm);
+				if (url)
+					bit->url = g_strdup (url);
+
+				NEW_BIT (bit);
+
+				g_free (src);
+
+				continue;
+			} else if (!g_strcasecmp (tag, "<P>") ||
+				   !g_strcasecmp (tag, "</P>") ||
+				   !g_strncasecmp (tag, "<P ", strlen ("<P ")) ||
+				   !g_strcasecmp (tag, "<PRE>") ||
+				   !g_strcasecmp (tag, "</PRE>") ||
+				   !g_strcasecmp (tag, "<HTML>") ||
+				   !g_strcasecmp (tag, "</HTML>") ||
+				   !g_strcasecmp (tag, "<BODY>") ||
+				   !g_strcasecmp (tag, "</BODY>") ||
+				   !g_strcasecmp (tag, "<FONT>") ||
+				   !g_strcasecmp (tag, "<HEAD>") ||
+				   !g_strcasecmp (tag, "</HEAD>")) {
+				intag = FALSE;
+				tpos = 0;
+				continue;
+			}
+
+			if (!got_tag) {
+				ws [wpos] = 0;
+				strcat (ws, tag);
+				wpos = strlen (ws);
+			} else {
+				wpos = 0;
+			}
+			intag = FALSE;
+			tpos = 0;
+		} else if (*c == '&' && !intag) {
+			if (!g_strncasecmp (c, "&amp;", 5)) {
+				ws [wpos++] = '&';
+				c += 5;
+			} else if (!g_strncasecmp (c, "&lt;", 4)) {
+				ws [wpos++] = '<';
+				c += 4;
+			} else if (!g_strncasecmp (c, "&gt;", 4)) {
+				ws [wpos++] = '>';
+				c += 4;
+			} else if (!g_strncasecmp (c, "&nbsp;", 6)) {
+				ws [wpos++] = ' ';
+				c += 6;
+			} else if (!g_strncasecmp (c, "&copy;", 6)) {
+				ws [wpos++] = '©';
+				c += 6;
+			} else if (!g_strncasecmp (c, "&quot;", 6)) {
+				ws [wpos++] = '\"';
+				c += 6;
+			} else if (!g_strncasecmp (c, "&reg;", 5)) {
+				ws [wpos++] = '®';
+				c += 5;
+			} else if (*(c + 1) == '#') {
+				gint pound = 0;
+				if (sscanf (c, "&#%d;", &pound) == 1) {
+					if (*(c + 3 + (gint)log10 (pound)) != ';') {
+						ws [wpos++] = *c++;
+						continue;
+					}
+					ws [wpos++] = (gchar)pound;
+					c += 2;
+					while (isdigit ((gint) *c)) c++;
+					if (*c == ';') c++;
+				} else {
+					ws [wpos++] = *c++;
+				}
+			} else {
+				ws [wpos++] = *c++;
+			}
+		} else if (intag) {
+			if (*c == '\"')
+				tagquote = !tagquote;
+			tag [tpos++] = *c++;
+		} else if (incomment) {
+			ws [wpos++] = *c++;
+		} else if (((smilelen = gtk_imhtml_is_smiley (imhtml, c)) != 0)) {
+			ws [wpos] = 0;
+			wpos = 0;
+			NEW_BIT (NEW_TEXT_BIT);
+			g_snprintf (ws, smilelen + 1, "%s", c);
+			NEW_BIT (NEW_SMILEY_BIT);
+			c += smilelen;
+		} else if (*c == '\n') {
+			if (!(options & GTK_IMHTML_NO_NEWLINE)) {
+				ws [wpos] = 0;
+				wpos = 0;
+				NEW_BIT (NEW_TEXT_BIT);
+				NEW_BIT (NEW_BR_BIT);
+			}
+			c++;
+		} else {
+			ws [wpos++] = *c++;
+		}
+	}
+
+	if (intag) {
+		tag [tpos] = 0;
+		c = tag;
+		while (*c) {
+			if ((smilelen = gtk_imhtml_is_smiley (imhtml, c)) != 0) {
+				ws [wpos] = 0;
+				wpos = 0;
+				NEW_BIT (NEW_TEXT_BIT);
+				g_snprintf (ws, smilelen + 1, "%s", c);
+				NEW_BIT (NEW_SMILEY_BIT);
+				c += smilelen;
+			} else {
+				ws [wpos++] = *c++;
+			}
+		}
+	} else if (incomment) {
+		ws [wpos] = 0;
+		wpos = 0;
+		strcat (tag, ws);
+		ws [wpos] = 0;
+		c = tag;
+		while (*c) {
+			if ((smilelen = gtk_imhtml_is_smiley (imhtml, c)) != 0) {
+				ws [wpos] = 0;
+				wpos = 0;
+				NEW_BIT (NEW_TEXT_BIT);
+				g_snprintf (ws, smilelen + 1, "%s", c);
+				NEW_BIT (NEW_SMILEY_BIT);
+				c += smilelen;
+			} else {
+				ws [wpos++] = *c++;
+			}
+		}
+	}
+
+	ws [wpos] = 0;
+	NEW_BIT (NEW_TEXT_BIT);
+
+	while (newbits) {
+		GtkIMHtmlBit *bit = newbits->data;
+		imhtml->bits = g_list_append (imhtml->bits, bit);
+		newbits = g_list_remove (newbits, bit);
+		gtk_imhtml_draw_bit (imhtml, bit);
+	}
+
+	gtk_widget_set_usize (GTK_WIDGET (imhtml), -1, imhtml->y + 5);
+
+	if (!(options & GTK_IMHTML_NO_SCROLL) &&
+	    scrolldown &&
+	    (imhtml->y + 5 >= GTK_WIDGET (imhtml)->allocation.height))
+		gtk_adjustment_set_value (vadj, imhtml->y + 5 - GTK_WIDGET (imhtml)->allocation.height);
+
+	if (url) {
+		g_free (url);
+		if (retval)
+			retval = g_string_append (retval, "</A>");
+	}
+	if (bg)
+		gdk_color_free (bg);
+	while (fonts) {
+		FontDetail *font = fonts->data;
+		fonts = g_slist_remove (fonts, font);
+		g_free (font->face);
+		if (font->fore)
+			gdk_color_free (font->fore);
+		if (font->back)
+			gdk_color_free (font->back);
+		g_free (font);
+		if (retval)
+			retval = g_string_append (retval, "</FONT>");
+	}
+	if (retval) {
+		while (bold) {
+			retval = g_string_append (retval, "</B>");
+			bold--;
+		}
+		while (italics) {
+			retval = g_string_append (retval, "</I>");
+			italics--;
+		}
+		while (underline) {
+			retval = g_string_append (retval, "</U>");
+			underline--;
+		}
+		while (strike) {
+			retval = g_string_append (retval, "</S>");
+			strike--;
+		}
+		while (sub) {
+			retval = g_string_append (retval, "</SUB>");
+			sub--;
+		}
+		while (sup) {
+			retval = g_string_append (retval, "</SUP>");
+			sup--;
+		}
+		while (title) {
+			retval = g_string_append (retval, "</TITLE>");
+			title--;
+		}
+	}
+	g_free (ws);
+	g_free (tag);
+
+	return retval;
+}