Index: gtk/gtklinklabel.c
===================================================================
--- gtk/gtklinklabel.c	(revision 0)
+++ gtk/gtklinklabel.c	(revision 0)
@@ -0,0 +1,894 @@
+/*
+ * @file libsexy/sexy-url-label.c URL Label
+ *
+ * @Copyright (C) 2005-2006 Christian Hammond
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA  02111-1307, USA.
+ */
+
+#include "config.h"
+
+#include <string.h>
+#include <stdio.h>
+
+#include "gtklinklabel.h"
+#include "gtkimagemenuitem.h"
+#include "gtkimage.h"
+#include "gtkstock.h"
+
+#define GTK_LINK_LABEL_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE((obj), GTK_TYPE_LINK_LABEL, GtkLinkLabelPrivate))
+
+typedef struct
+{
+  int start;
+  int end;
+  const gchar *url;
+} GtkLinkLabelLink;
+
+typedef struct
+{
+  GList *links;
+  GList *urls;
+  GtkLinkLabelLink *active_link;
+  GtkWidget *popup_menu;
+  GdkWindow *event_window;
+
+  int layout_x;
+  int layout_y;
+
+  size_t wrap_width;
+
+  GString *temp_markup_result;
+} GtkLinkLabelPrivate;
+
+/*
+ * NOTE: This *MUST* match the LabelWrapWidth struct in gtklabel.c.
+ */
+typedef struct
+{
+  gint width;
+  PangoFontDescription *font_desc;
+} LabelWrapWidth;
+
+enum
+{
+  URL_ACTIVATED,
+  LAST_SIGNAL
+};
+
+static void gtk_link_label_finalize                  (GObject *obj);
+static void gtk_link_label_realize                   (GtkWidget *widget);
+static void gtk_link_label_unrealize                 (GtkWidget *widget);
+static void gtk_link_label_map                       (GtkWidget *widget);
+static void gtk_link_label_unmap                     (GtkWidget *widget);
+static void gtk_link_label_size_allocate             (GtkWidget *widget,
+						      GtkAllocation *allocation);
+static gboolean gtk_link_label_motion_notify_event   (GtkWidget *widget,
+						      GdkEventMotion *event);
+static gboolean gtk_link_label_leave_notify_event    (GtkWidget *widget,
+						      GdkEventCrossing *event);
+static gboolean gtk_link_label_button_press_event    (GtkWidget *widget,
+						      GdkEventButton *event);
+
+static void open_link_activate_cb                    (GtkMenuItem *menu_item,
+						      GtkLinkLabel *link_label);
+static void copy_link_activate_cb                    (GtkMenuItem *menu_item,
+						      GtkLinkLabel *link_label);
+
+static void gtk_link_label_clear_links               (GtkLinkLabel *link_label);
+static void gtk_link_label_clear_urls                (GtkLinkLabel *link_label);
+static void gtk_link_label_rescan_label              (GtkLinkLabel *link_label);
+
+static GtkLabelClass *parent_class = NULL;
+static guint signals[LAST_SIGNAL] = {0};
+
+static GtkLinkLabelUriFunc uri_func = NULL;
+static gpointer uri_func_data = NULL;
+static GDestroyNotify uri_func_destroy = NULL;
+
+G_DEFINE_TYPE(GtkLinkLabel, gtk_link_label, GTK_TYPE_LABEL);
+
+static void
+gtk_link_label_class_init (GtkLinkLabelClass *klass)
+{
+  GObjectClass   *object_class = G_OBJECT_CLASS (klass);
+  GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+  parent_class = g_type_class_peek_parent (klass);
+
+  object_class->finalize = gtk_link_label_finalize;
+
+  widget_class->realize             = gtk_link_label_realize;
+  widget_class->unrealize           = gtk_link_label_unrealize;
+  widget_class->map                 = gtk_link_label_map;
+  widget_class->unmap               = gtk_link_label_unmap;
+  widget_class->size_allocate       = gtk_link_label_size_allocate;
+  widget_class->motion_notify_event = gtk_link_label_motion_notify_event;
+  widget_class->leave_notify_event  = gtk_link_label_leave_notify_event;
+  widget_class->button_press_event  = gtk_link_label_button_press_event;
+
+  g_type_class_add_private (klass, sizeof (GtkLinkLabelPrivate));
+
+  /**
+   * GtkLinkLabel::url-activated:
+   * @link_label: The label on which the signal was emitted.
+   * @url: The URL which was activated.
+   *
+   * The ::url-activated signal is emitted when a URL in the label was
+   * clicked.
+   */
+  signals[URL_ACTIVATED] =
+    g_signal_new ("url_activated",
+		  G_TYPE_FROM_CLASS (object_class),
+		  G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
+		  G_STRUCT_OFFSET (GtkLinkLabelClass, url_activated),
+		  NULL, NULL,
+		  g_cclosure_marshal_VOID__STRING,
+		  G_TYPE_NONE, 1,
+		  G_TYPE_STRING);
+}
+
+static void
+selectable_changed_cb (GtkLinkLabel *link_label)
+{
+  GtkLinkLabelPrivate *priv = GTK_LINK_LABEL_GET_PRIVATE (link_label);
+
+  if (priv->event_window != NULL)
+    {
+      GdkCursor *cursor = NULL;
+
+      if (gtk_label_get_selectable (GTK_LABEL (link_label)) && priv->active_link == NULL)
+	cursor = gdk_cursor_new_for_display(gtk_widget_get_display (GTK_WIDGET (link_label)), GDK_XTERM);
+
+      gdk_window_set_cursor (priv->event_window, cursor);
+
+      if (cursor)
+	gdk_cursor_unref (cursor);
+
+      /*
+       * GtkLabel recreates its event window when the selectable property
+       * changes, which will cover ours.
+       */
+      gdk_window_raise (priv->event_window);
+    }
+}
+
+static void
+gtk_link_label_init (GtkLinkLabel *link_label)
+{
+  GtkLinkLabelPrivate *priv = GTK_LINK_LABEL_GET_PRIVATE (link_label);
+  GtkWidget *item;
+  GtkWidget *image;
+
+  priv->links        = NULL;
+  priv->active_link  = NULL;
+  priv->event_window = NULL;
+
+  g_signal_connect (G_OBJECT (link_label), "notify::selectable",
+		    G_CALLBACK (selectable_changed_cb), NULL);
+
+  priv->popup_menu = gtk_menu_new ();
+
+  /* Open Link */
+  item = gtk_image_menu_item_new_with_mnemonic ("_Open Link");
+  gtk_widget_show (item);
+  gtk_menu_shell_append (GTK_MENU_SHELL (priv->popup_menu), item);
+
+  g_signal_connect (G_OBJECT(item), "activate",
+		    G_CALLBACK (open_link_activate_cb), link_label);
+
+  image = gtk_image_new_from_stock (GTK_STOCK_JUMP_TO, GTK_ICON_SIZE_MENU);
+  gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
+  gtk_widget_show (image);
+
+  /* Copy Link Address */
+  item = gtk_image_menu_item_new_with_mnemonic ("Copy _Link Address");
+  gtk_widget_show (item);
+  gtk_menu_shell_append (GTK_MENU_SHELL (priv->popup_menu), item);
+
+  g_signal_connect (G_OBJECT (item), "activate",
+		    G_CALLBACK (copy_link_activate_cb), link_label);
+
+  image = gtk_image_new_from_stock (GTK_STOCK_COPY, GTK_ICON_SIZE_MENU);
+  gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM(item), image);
+  gtk_widget_show (image);
+}
+
+static void
+gtk_link_label_finalize (GObject *obj)
+{
+  GtkLinkLabel *link_label = GTK_LINK_LABEL (obj);
+
+  gtk_link_label_clear_links (link_label);
+  gtk_link_label_clear_urls (link_label);
+
+  if (G_OBJECT_CLASS (parent_class)->finalize != NULL)
+    G_OBJECT_CLASS (parent_class)->finalize (obj);
+}
+
+static gboolean
+gtk_link_label_motion_notify_event (GtkWidget *widget, GdkEventMotion *event)
+{
+  GtkLinkLabel *link_label = (GtkLinkLabel *)widget;
+  GtkLinkLabelPrivate *priv = GTK_LINK_LABEL_GET_PRIVATE (link_label);
+  PangoLayout *layout = gtk_label_get_layout(GTK_LABEL (link_label));
+  GdkModifierType state;
+  gboolean found = FALSE;
+  GList *l;
+  int index, trailing;
+  int x, y;
+  GtkLinkLabelLink *link = NULL;
+
+  if (event->is_hint)
+    {
+      gdk_window_get_pointer (event->window, &x, &y, &state);
+    }
+  else
+    {
+      x = event->x;
+      y = event->y;
+      state = event->state;
+    }
+
+  if (pango_layout_xy_to_index (layout,
+				(x - priv->layout_x) * PANGO_SCALE,
+				(y - priv->layout_y) * PANGO_SCALE,
+				&index, &trailing))
+    {
+      for (l = priv->links; l != NULL; l = l->next)
+	{
+	  link = (GtkLinkLabelLink *)l->data;
+
+	  if (index >= link->start && index <= link->end)
+	    {
+	      found = TRUE;
+	      break;
+	    }
+	}
+    }
+
+  if (found)
+    {
+      if (priv->active_link == NULL)
+	{
+	  GdkCursor *cursor;
+
+	  cursor = gdk_cursor_new_for_display (gtk_widget_get_display (widget), GDK_HAND2);
+	  gdk_window_set_cursor (priv->event_window, cursor);
+	  gdk_cursor_unref (cursor);
+
+	  priv->active_link = link;
+	}
+    }
+  else
+    {
+      if (priv->active_link != NULL)
+	{
+	  if (gtk_label_get_selectable (GTK_LABEL (link_label)))
+	    {
+	      GdkCursor *cursor;
+
+	      cursor = gdk_cursor_new_for_display (gtk_widget_get_display (widget), GDK_XTERM);
+	      gdk_window_set_cursor (priv->event_window, cursor);
+	      gdk_cursor_unref (cursor);
+	    }
+	  else
+	    {
+	      gdk_window_set_cursor(priv->event_window, NULL);
+	    }
+
+	  priv->active_link = NULL;
+	}
+    }
+
+  /*
+   * Another beautiful libsexy hack. This one prevents the callback
+   * from going "Oh boy, they clicked and dragged! Let's select more of
+   * the text!"
+   */
+  if (priv->active_link != NULL)
+    event->state = 0;
+
+  GTK_WIDGET_CLASS (parent_class)->motion_notify_event (widget, event);
+
+  return FALSE;
+}
+
+static gboolean
+gtk_link_label_leave_notify_event (GtkWidget *widget, GdkEventCrossing *event)
+{
+  GtkLinkLabel *link_label = (GtkLinkLabel *)widget;
+  GtkLinkLabelPrivate *priv = GTK_LINK_LABEL_GET_PRIVATE (link_label);
+
+  if (GTK_WIDGET_CLASS (parent_class)->leave_notify_event != NULL)
+    GTK_WIDGET_CLASS (parent_class)->leave_notify_event (widget, event);
+
+  if (event->mode == GDK_CROSSING_NORMAL)
+    {
+      GdkCursor *cursor = NULL;
+
+      if (gtk_label_get_selectable (GTK_LABEL (widget)))
+	cursor = gdk_cursor_new_for_display (gtk_widget_get_display(widget), GDK_XTERM);
+
+      gdk_window_set_cursor (priv->event_window, cursor);
+
+      if (cursor)
+	gdk_cursor_unref (cursor);
+
+      priv->active_link = NULL;
+    }
+
+  return FALSE;
+}
+
+static gboolean
+gtk_link_label_button_press_event (GtkWidget *widget, GdkEventButton *event)
+{
+  GtkLinkLabel *link_label = (GtkLinkLabel *)widget;
+  GtkLinkLabelPrivate *priv = GTK_LINK_LABEL_GET_PRIVATE (link_label);
+
+  if (priv->active_link == NULL)
+    {
+      return GTK_WIDGET_CLASS (parent_class)->button_press_event (widget,
+								  event);
+    }
+
+  if (event->button == 1)
+    {
+      g_signal_emit (link_label, signals[URL_ACTIVATED], 0,
+		     priv->active_link->url);
+    }
+  else if (event->button == 3)
+    {
+      gtk_menu_popup (GTK_MENU (priv->popup_menu), NULL, NULL, NULL, NULL,
+		      event->button, event->time);
+    }
+
+  return TRUE;
+}
+
+static void
+update_wrap_width (GtkLinkLabel *link_label, size_t wrap_width)
+{
+  GtkLinkLabelPrivate *priv = GTK_LINK_LABEL_GET_PRIVATE (link_label);
+  LabelWrapWidth *wrap_width_data;
+  GtkStyle *style;
+
+  if (wrap_width == 0 || !gtk_label_get_line_wrap (GTK_LABEL (link_label)))
+    return;
+
+#if 0
+  pango_layout_set_width (gtk_label_get_layout (GTK_LABEL (link_label)),
+			  wrap_width * PANGO_SCALE);
+#endif
+  style = GTK_WIDGET (link_label)->style;
+  wrap_width_data = g_object_get_data (G_OBJECT(style),
+				       "gtk-label-wrap-width");
+
+  if (wrap_width_data != NULL &&
+      wrap_width_data->width != wrap_width * PANGO_SCALE)
+    {
+      wrap_width_data->width = wrap_width * PANGO_SCALE;
+      priv->wrap_width = wrap_width;
+      g_object_unref (GTK_LABEL (link_label)->layout);
+      GTK_LABEL (link_label)->layout = NULL;
+      gtk_label_get_layout (GTK_LABEL (link_label));
+      gtk_widget_queue_resize (GTK_WIDGET (link_label));
+    }
+}
+
+static void
+gtk_link_label_realize (GtkWidget *widget)
+{
+  GtkLinkLabel *link_label = (GtkLinkLabel *)widget;
+  GtkLinkLabelPrivate *priv = GTK_LINK_LABEL_GET_PRIVATE (link_label);
+  GdkWindowAttr attributes;
+  gint attributes_mask;
+
+  GTK_WIDGET_CLASS (parent_class)->realize (widget);
+
+  attributes.window_type = GDK_WINDOW_CHILD;
+  attributes.x = widget->allocation.x;
+  attributes.y = widget->allocation.y;
+  attributes.width = widget->allocation.width;
+  attributes.height = widget->allocation.height;
+  attributes.window_type = GDK_WINDOW_CHILD;
+  attributes.wclass = GDK_INPUT_ONLY;
+  attributes.event_mask = gtk_widget_get_events(widget);
+  attributes.event_mask |= (GDK_BUTTON_PRESS_MASK |
+			    GDK_BUTTON_RELEASE_MASK |
+			    GDK_POINTER_MOTION_MASK |
+			    GDK_POINTER_MOTION_HINT_MASK |
+			    GDK_LEAVE_NOTIFY_MASK |
+			    GDK_LEAVE_NOTIFY_MASK);
+  attributes_mask = GDK_WA_X | GDK_WA_Y;
+
+  if (gtk_label_get_selectable (GTK_LABEL (widget))) {
+    attributes.cursor = gdk_cursor_new_for_display (gtk_widget_get_display (widget), GDK_XTERM);
+    attributes_mask |= GDK_WA_CURSOR;
+  }
+
+  priv->event_window = gdk_window_new (gtk_widget_get_parent_window (widget),
+				       &attributes,
+				       attributes_mask);
+  gdk_window_set_user_data (priv->event_window, widget);
+
+  if (attributes_mask & GDK_WA_CURSOR)
+    gdk_cursor_unref (attributes.cursor);
+}
+
+static void
+gtk_link_label_unrealize (GtkWidget *widget)
+{
+  GtkLinkLabel *link_label = (GtkLinkLabel *)widget;
+  GtkLinkLabelPrivate *priv = GTK_LINK_LABEL_GET_PRIVATE (link_label);
+
+  if (priv->event_window != NULL)
+    {
+      gdk_window_set_user_data (priv->event_window, NULL);
+      gdk_window_destroy (priv->event_window);
+      priv->event_window = NULL;
+    }
+
+  GTK_WIDGET_CLASS (parent_class)->unrealize (widget);
+}
+
+static void
+gtk_link_label_map (GtkWidget *widget)
+{
+  GtkLinkLabel *link_label = (GtkLinkLabel *)widget;
+  GtkLinkLabelPrivate *priv = GTK_LINK_LABEL_GET_PRIVATE (link_label);
+
+  GTK_WIDGET_CLASS (parent_class)->map (widget);
+
+  if (priv->event_window != NULL)
+    gdk_window_show (priv->event_window);
+}
+
+static void
+gtk_link_label_unmap (GtkWidget *widget)
+{
+  GtkLinkLabel *link_label = (GtkLinkLabel *)widget;
+  GtkLinkLabelPrivate *priv = GTK_LINK_LABEL_GET_PRIVATE (link_label);
+
+  if (priv->event_window != NULL)
+    gdk_window_hide (priv->event_window);
+
+  GTK_WIDGET_CLASS (parent_class)->unmap (widget);
+}
+
+static void
+gtk_link_label_size_allocate (GtkWidget *widget, GtkAllocation *allocation)
+{
+  GtkLinkLabel *link_label = (GtkLinkLabel *)widget;
+  GtkLinkLabelPrivate *priv = GTK_LINK_LABEL_GET_PRIVATE (link_label);
+
+#if 0
+  {
+    LabelWrapWidth *wrap_width_data;
+    GtkStyle *style;
+    style = GTK_WIDGET (link_label)->style;
+    wrap_width_data = g_object_get_data (G_OBJECT(style),
+					 "gtk-label-wrap-width");
+    if (wrap_width_data != NULL)
+      printf ("wrap width = %d\n", wrap_width_data->width / PANGO_SCALE);
+  }
+#endif
+
+  update_wrap_width (link_label, allocation->width);
+  GTK_WIDGET_CLASS (parent_class)->size_allocate (widget, allocation);
+  pango_layout_set_width (gtk_label_get_layout (GTK_LABEL (link_label)),
+			  allocation->width * PANGO_SCALE);
+
+  if (GTK_WIDGET_REALIZED (widget))
+    {
+      gdk_window_move_resize (priv->event_window,
+			      allocation->x, allocation->y,
+			      allocation->width, allocation->height);
+    }
+
+  gtk_link_label_rescan_label (link_label);
+}
+
+static void
+open_link_activate_cb (GtkMenuItem *menu_item, GtkLinkLabel *link_label)
+{
+  GtkLinkLabelPrivate *priv = GTK_LINK_LABEL_GET_PRIVATE(link_label);
+
+  if (priv->active_link == NULL)
+    return;
+
+  g_signal_emit (link_label, signals[URL_ACTIVATED], 0, priv->active_link->url);
+}
+
+static void
+copy_link_activate_cb (GtkMenuItem *menu_item, GtkLinkLabel *link_label)
+{
+  GtkLinkLabelPrivate *priv = GTK_LINK_LABEL_GET_PRIVATE(link_label);
+  GtkClipboard *clipboard;
+
+  if (priv->active_link == NULL)
+    return;
+
+  clipboard = gtk_widget_get_clipboard (GTK_WIDGET (link_label),
+					GDK_SELECTION_PRIMARY);
+
+  gtk_clipboard_set_text (clipboard, priv->active_link->url,
+			  strlen (priv->active_link->url));
+}
+
+/**
+ * gtk_link_label_new
+ *
+ * Creates a new GtkLinkLabel widget.
+ *
+ * Returns: a new #GtkLinkLabel.
+ */
+GtkWidget *
+gtk_link_label_new (void)
+{
+  return g_object_new (GTK_TYPE_LINK_LABEL, NULL);
+}
+
+static void
+gtk_link_label_clear_links (GtkLinkLabel *link_label)
+{
+  GtkLinkLabelPrivate *priv = GTK_LINK_LABEL_GET_PRIVATE (link_label);
+
+  if (priv->links == NULL)
+    return;
+
+  g_list_foreach (priv->links, (GFunc)g_free, NULL);
+  g_list_free (priv->links);
+  priv->links = NULL;
+}
+
+static void
+gtk_link_label_clear_urls (GtkLinkLabel *link_label)
+{
+  GtkLinkLabelPrivate *priv = GTK_LINK_LABEL_GET_PRIVATE (link_label);
+
+  if (priv->urls == NULL)
+    return;
+
+  g_list_foreach (priv->urls, (GFunc)g_free, NULL);
+  g_list_free (priv->urls);
+  priv->urls = NULL;
+}
+
+static void
+gtk_link_label_rescan_label (GtkLinkLabel *link_label)
+{
+  GtkLinkLabelPrivate *priv = GTK_LINK_LABEL_GET_PRIVATE (link_label);
+  PangoLayout *layout = gtk_label_get_layout (GTK_LABEL (link_label));
+  PangoAttrList *list = pango_layout_get_attributes (layout);
+  PangoAttrIterator *iter;
+  GList *url_list;
+
+  gtk_link_label_clear_links (link_label);
+
+  if (list == NULL)
+    return;
+
+  iter = pango_attr_list_get_iterator (list);
+
+  gtk_label_get_layout_offsets (GTK_LABEL (link_label),
+				&priv->layout_x, &priv->layout_y);
+
+  priv->layout_x -= GTK_WIDGET (link_label)->allocation.x;
+  priv->layout_y -= GTK_WIDGET (link_label)->allocation.y;
+
+  url_list = priv->urls;
+
+  do
+    {
+      PangoAttribute *underline;
+      PangoAttribute *color;
+
+      underline = pango_attr_iterator_get (iter, PANGO_ATTR_UNDERLINE);
+      color     = pango_attr_iterator_get (iter, PANGO_ATTR_FOREGROUND);
+
+      if (underline != NULL && color != NULL)
+	{
+	  gint start, end;
+	  PangoRectangle start_pos;
+	  PangoRectangle end_pos;
+	  GtkLinkLabelLink *link;
+
+	  pango_attr_iterator_range (iter, &start, &end);
+	  pango_layout_index_to_pos (layout, start, &start_pos);
+	  pango_layout_index_to_pos (layout, end,   &end_pos);
+
+	  link = g_new0 (GtkLinkLabelLink, 1);
+	  link->start = start;
+	  link->end   = end;
+	  link->url   = (const gchar *)url_list->data;
+	  priv->links = g_list_append (priv->links, link);
+
+	  url_list = url_list->next;
+	}
+
+    } while (pango_attr_iterator_next (iter));
+
+  pango_attr_iterator_destroy (iter);
+}
+
+static void
+start_element_handler (GMarkupParseContext *context,
+		       const gchar *element_name,
+		       const gchar **attribute_names,
+		       const gchar **attribute_values,
+		       gpointer user_data,
+		       GError **error)
+{
+  GtkLinkLabel *link_label   = GTK_LINK_LABEL (user_data);
+  GtkLinkLabelPrivate *priv = GTK_LINK_LABEL_GET_PRIVATE (link_label);
+
+  if (!strcmp (element_name, "a"))
+    {
+      const gchar *url = NULL;
+      int line_number;
+      int char_number;
+      int i;
+
+      g_markup_parse_context_get_position (context, &line_number,
+					   &char_number);
+
+      for (i = 0; attribute_names[i] != NULL; i++)
+	{
+	  const gchar *attr = attribute_names[i];
+
+	  if (!strcmp (attr, "href"))
+	    {
+	      if (url != NULL)
+		{
+		  g_set_error (error, G_MARKUP_ERROR,
+			       G_MARKUP_ERROR_INVALID_CONTENT,
+			       "Attribute '%s' occurs twice on <a> tag "
+			       "on line %d char %d, may only occur once",
+			       attribute_names[i], line_number, char_number);
+		  return;
+		}
+
+	      url = attribute_values[i];
+	    }
+	  else
+	    {
+	      g_set_error (error, G_MARKUP_ERROR,
+			   G_MARKUP_ERROR_UNKNOWN_ATTRIBUTE,
+			   "Attribute '%s' is not allowed on the <a> tag "
+			   "on line %d char %d",
+			   attribute_names[i], line_number, char_number);
+	      return;
+	    }
+	}
+
+      if (url == NULL)
+	{
+	  g_set_error (error, G_MARKUP_ERROR,
+		       G_MARKUP_ERROR_INVALID_CONTENT,
+		       "Attribute 'href' was missing on the <a> tag "
+		       "on line %d char %d",
+		       line_number, char_number);
+	  return;
+	}
+
+      g_string_append (priv->temp_markup_result,
+		       "<span color=\"blue\" underline=\"single\">");
+
+      priv->urls = g_list_append (priv->urls, g_strdup(url));
+    }
+  else
+    {
+      int i;
+
+      g_string_append_printf (priv->temp_markup_result,
+			      "<%s", element_name);
+
+      for (i = 0; attribute_names[i] != NULL; i++)
+	{
+	  const gchar *attr  = attribute_names[i];
+	  const gchar *value = attribute_values[i];
+
+	  g_string_append_printf (priv->temp_markup_result,
+				  " %s=\"%s\"",
+				  attr, value);
+	}
+
+      g_string_append_c (priv->temp_markup_result, '>');
+    }
+}
+
+static void
+end_element_handler (GMarkupParseContext *context,
+		     const gchar *element_name,
+		     gpointer user_data,
+		     GError **error)
+{
+  GtkLinkLabel *link_label   = GTK_LINK_LABEL (user_data);
+  GtkLinkLabelPrivate *priv = GTK_LINK_LABEL_GET_PRIVATE (link_label);
+
+  if (!strcmp (element_name, "a"))
+    {
+      g_string_append (priv->temp_markup_result, "</span>");
+    }
+  else
+    {
+      g_string_append_printf (priv->temp_markup_result,
+			      "</%s>", element_name);
+    }
+}
+
+static void
+text_handler (GMarkupParseContext *context,
+	      const gchar *text,
+	      gsize text_len,
+	      gpointer user_data,
+	      GError **error)
+{
+  GtkLinkLabel *link_label   = GTK_LINK_LABEL (user_data);
+  GtkLinkLabelPrivate *priv = GTK_LINK_LABEL_GET_PRIVATE (link_label);
+
+  gchar *newtext = g_markup_escape_text (text, text_len);
+  g_string_append_len (priv->temp_markup_result, newtext, strlen (newtext));
+  g_free (newtext);
+}
+
+static const GMarkupParser markup_parser =
+{
+  start_element_handler,
+  end_element_handler,
+  text_handler,
+  NULL,
+  NULL
+};
+
+static gboolean
+xml_isspace (char c)
+{
+  return (c == ' ' || c == '\t' || c == '\n' || c == '\r');
+}
+
+static gboolean
+parse_custom_markup (GtkLinkLabel *link_label, const gchar *markup,
+		     gchar **ret_markup)
+{
+  GMarkupParseContext *context = NULL;
+  GtkLinkLabelPrivate *priv = GTK_LINK_LABEL_GET_PRIVATE (link_label);
+  GError *error = NULL;
+  const gchar *p, *end;
+  gboolean needs_root = TRUE;
+  gsize length;
+
+  g_return_val_if_fail (markup     != NULL, FALSE);
+  g_return_val_if_fail (ret_markup != NULL, FALSE);
+
+  priv->temp_markup_result = g_string_new (NULL);
+
+  length = strlen(markup);
+  p = markup;
+  end = markup + length;
+
+  while (p != end && xml_isspace (*p))
+    p++;
+
+  if (end - p >= 8 && strncmp (p, "<markup>", 8) == 0)
+    needs_root = FALSE;
+
+  context = g_markup_parse_context_new (&markup_parser, 0, link_label, NULL);
+
+  if (needs_root)
+    {
+      if (!g_markup_parse_context_parse (context, "<markup>", -1, &error))
+	goto failed;
+    }
+
+  if (!g_markup_parse_context_parse (context, markup, strlen (markup), &error))
+    goto failed;
+
+  if (needs_root)
+    {
+      if (!g_markup_parse_context_parse (context, "</markup>", -1, &error))
+	goto failed;
+    }
+
+  if (!g_markup_parse_context_end_parse (context, &error))
+    goto failed;
+
+  if (error != NULL)
+    g_error_free (error);
+
+  g_markup_parse_context_free (context);
+
+  *ret_markup = g_string_free (priv->temp_markup_result, FALSE);
+  priv->temp_markup_result = NULL;
+
+  return TRUE;
+
+failed:
+  fprintf (stderr, "Unable to parse markup: %s\n", error->message);
+  g_error_free (error);
+
+  g_string_free (priv->temp_markup_result, TRUE);
+  priv->temp_markup_result = NULL;
+
+  g_markup_parse_context_free (context);
+  return FALSE;
+}
+
+/**
+ * gtk_link_label_set_markup
+ * @link_label: A #GtkLinkLabel.
+ * @markup: a markup string (see <link linkend="PangoMarkupFormat">Pango markup format</link>)
+ *
+ * Parses @markup which is marked up with the <link
+ * linkend="PangoMarkupFormat">Pango text markup language</link> as well as
+ * HTML-style hyperlinks, setting the label's text and attribute list based
+ * on the parse results.  If the @markup is external data, you may need to
+ * escape it with g_markup_escape_text() or g_markup_printf_escaped()
+ */
+void
+gtk_link_label_set_markup (GtkLinkLabel *link_label, const gchar *markup)
+{
+  GtkLinkLabelPrivate *priv;
+  gchar *new_markup;
+
+  g_return_if_fail (GTK_IS_LINK_LABEL(link_label));
+
+  priv = GTK_LINK_LABEL_GET_PRIVATE (link_label);
+
+  gtk_link_label_clear_links (link_label);
+  gtk_link_label_clear_urls (link_label);
+
+  if (markup == NULL || *markup == '\0')
+    {
+      gtk_label_set_markup (GTK_LABEL(link_label), "");
+      return;
+    }
+
+  if (parse_custom_markup (link_label, markup, &new_markup))
+    {
+      gtk_label_set_markup (GTK_LABEL (link_label), new_markup);
+      g_free (new_markup);
+    }
+  else
+    {
+      gtk_label_set_markup (GTK_LABEL(link_label), "");
+    }
+
+  gtk_link_label_rescan_label (link_label);
+
+  update_wrap_width (link_label, priv->wrap_width);
+}
+
+GtkLinkLabelUriFunc
+gtk_link_label_set_uri_hook (GtkLinkLabelUriFunc func,
+			     gpointer            data,
+			     GDestroyNotify      destroy)
+{
+  GtkLinkLabelUriFunc old_uri_func;
+
+  if (uri_func_destroy)
+    (* uri_func_destroy) (uri_func_data);
+
+  old_uri_func = uri_func;
+
+  uri_func = func;
+  uri_func_data = data;
+  uri_func_destroy = destroy;
+
+  return old_uri_func;
+}
Index: gtk/gtklinklabel.h
===================================================================
--- gtk/gtklinklabel.h	(revision 0)
+++ gtk/gtklinklabel.h	(revision 0)
@@ -0,0 +1,73 @@
+/*
+ * @file libsexy/sexy-url-label.h URL Label
+ *
+ * @Copyright (C) 2005-2006 Christian Hammond
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA  02111-1307, USA.
+ */
+
+#ifndef __GTK_LINK_LABEL_H__
+#define __GTK_LINK_LABEL_H__
+
+#include <gtk/gtklabel.h>
+
+G_BEGIN_DECLS
+
+#define GTK_TYPE_LINK_LABEL            (gtk_link_label_get_type ())
+#define GTK_LINK_LABEL(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), GTK_TYPE_LINK_LABEL, GtkLinkLabel))
+#define GTK_LINK_LABEL_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), GTK_TYPE_LINK_LABEL, GtkLinkLabelClass))
+#define GTK_IS_LINK_LABEL(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GTK_TYPE_LINK_LABEL))
+#define GTK_IS_LINK_LABEL_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GTK_TYPE_LINK_LABEL))
+#define GTK_LINK_LABEL_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), GTK_TYPE_LINK_LABEL, GtkLinkLabelClass))
+
+typedef struct _GtkLinkLabel      GtkLinkLabel;
+typedef struct _GtkLinkLabelClass GtkLinkLabelClass;
+
+typedef void (*GtkLinkLabelUriFunc) (GtkLinkLabel *label,
+				     const gchar  *uri,
+				     gpointer      user_data);
+
+struct _GtkLinkLabel
+{
+	GtkLabel parent_object;
+};
+
+struct _GtkLinkLabelClass
+{
+	GtkLabelClass parent_class;
+
+	/* Signals */
+	void (*url_activated) (GtkLinkLabel *link_label, const gchar *url);
+
+	void (*gtk_reserved1) (void);
+	void (*gtk_reserved2) (void);
+	void (*gtk_reserved3) (void);
+	void (*gtk_reserved4) (void);
+};
+
+GType               gtk_link_label_get_type     (void) G_GNUC_CONST;
+
+GtkWidget*          gtk_link_label_new          (void);
+void                gtk_link_label_set_markup   (GtkLinkLabel *link_label,
+						 const gchar *markup);
+
+GtkLinkLabelUriFunc gtk_link_label_set_uri_hook (GtkLinkLabelUriFunc func,
+						 gpointer            data,
+						 GDestroyNotify      destroy);
+
+G_END_DECLS
+
+#endif /* __GTK_LINK_LABEL_H__ */
Index: gtk/gtk.h
===================================================================
--- gtk/gtk.h	(revision 19352)
+++ gtk/gtk.h	(working copy)
@@ -114,6 +114,7 @@
 #include <gtk/gtklabel.h>
 #include <gtk/gtklayout.h>
 #include <gtk/gtklinkbutton.h>
+#include <gtk/gtklinklabel.h>
 #include <gtk/gtklist.h>
 #include <gtk/gtklistitem.h>
 #include <gtk/gtkliststore.h>
Index: gtk/Makefile.am
===================================================================
--- gtk/Makefile.am	(revision 19352)
+++ gtk/Makefile.am	(working copy)
@@ -219,6 +219,7 @@
 	gtklabel.h		\
 	gtklayout.h             \
 	gtklinkbutton.h		\
+	gtklinklabel.h		\
 	gtklist.h		\
 	gtklistitem.h		\
 	gtkliststore.h		\
@@ -477,6 +478,7 @@
 	gtkhsv.c		\
 	gtkiconcache.c		\
 	gtkiconcachevalidator.c	\
+	gtkiconentry.c		\
 	gtkiconfactory.c	\
 	gtkicontheme.c		\
 	gtkiconview.c		\
@@ -494,6 +496,7 @@
 	gtklabel.c		\
 	gtklayout.c		\
 	gtklinkbutton.c		\
+	gtklinklabel.c		\
 	gtklist.c		\
 	gtklistitem.c		\
 	gtkliststore.c		\
Index: tests/testiconentry.c
===================================================================
--- tests/testiconentry.c	(revision 0)
+++ tests/testiconentry.c	(revision 0)
@@ -0,0 +1,133 @@
+#include <gtk/gtk.h>
+#include <stdio.h>
+
+int
+main (int argc, char **argv)
+{
+  GtkWidget *window;
+  GtkWidget *table;
+  GtkWidget *label;
+  GtkWidget *icon_entry;
+  GtkWidget *icon;
+
+  gtk_init (&argc, &argv);
+
+  window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
+  gtk_widget_show (window);
+  gtk_window_set_title (GTK_WINDOW (window), "Gtk Icon Entry Test");
+  gtk_container_set_border_width (GTK_CONTAINER (window), 12);
+
+  g_signal_connect (G_OBJECT (window), "destroy",
+		    G_CALLBACK (gtk_main_quit), NULL);
+
+  table = gtk_table_new (2, 4, FALSE);
+  gtk_widget_show (table);
+  gtk_container_add (GTK_CONTAINER (window), table);
+  gtk_table_set_row_spacings (GTK_TABLE (table), 6);
+  gtk_table_set_col_spacings (GTK_TABLE (table), 6);
+
+  /*
+   * Open File
+   */
+  label = gtk_label_new ("Open File:");
+  gtk_widget_show (label);
+  gtk_table_attach (GTK_TABLE (table), label, 0, 1, 0, 1,
+		    GTK_FILL, GTK_FILL, 0, 0);
+  gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
+
+  icon_entry = gtk_icon_entry_new ();
+  gtk_widget_show (icon_entry);
+  gtk_table_attach (GTK_TABLE (table), icon_entry, 1, 2, 0, 1,
+		    GTK_FILL | GTK_EXPAND, GTK_FILL, 0, 0);
+
+  icon = gtk_image_new_from_stock (GTK_STOCK_OPEN, GTK_ICON_SIZE_MENU);
+  gtk_widget_show (icon);
+  gtk_icon_entry_set_icon (GTK_ICON_ENTRY (icon_entry),
+			   GTK_ICON_ENTRY_PRIMARY,
+			   GTK_IMAGE(icon));
+  gtk_icon_entry_set_icon_highlight (GTK_ICON_ENTRY (icon_entry),
+				     GTK_ICON_ENTRY_PRIMARY, TRUE);
+
+  /*
+   * Save File
+   */
+  label = gtk_label_new ("Save File:");
+  gtk_widget_show (label);
+  gtk_table_attach (GTK_TABLE (table), label, 0, 1, 1, 2,
+		    GTK_FILL, GTK_FILL, 0, 0);
+  gtk_misc_set_alignment (GTK_MISC(label), 0.0, 0.5);
+
+  icon_entry = gtk_icon_entry_new ();
+  gtk_widget_show (icon_entry);
+  gtk_table_attach(GTK_TABLE (table), icon_entry, 1, 2, 1, 2,
+		   GTK_FILL | GTK_EXPAND, GTK_FILL, 0, 0);
+  gtk_entry_set_text (GTK_ENTRY (icon_entry), "‏Right-to-left");
+  gtk_widget_set_direction (icon_entry, GTK_TEXT_DIR_RTL);
+
+  icon = gtk_image_new_from_stock (GTK_STOCK_SAVE, GTK_ICON_SIZE_MENU);
+  gtk_widget_show (icon);
+  gtk_icon_entry_set_icon (GTK_ICON_ENTRY (icon_entry),
+			   GTK_ICON_ENTRY_PRIMARY,
+			   GTK_IMAGE (icon));
+  gtk_icon_entry_set_icon_highlight (GTK_ICON_ENTRY (icon_entry),
+				     GTK_ICON_ENTRY_PRIMARY, TRUE);
+
+  /*
+   * Search
+   */
+  label = gtk_label_new ("Search:");
+  gtk_widget_show (label);
+  gtk_table_attach (GTK_TABLE (table), label, 0, 1, 2, 3,
+		    GTK_FILL, GTK_FILL, 0, 0);
+  gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
+
+  icon_entry = gtk_icon_entry_new ();
+  gtk_widget_show (icon_entry);
+  gtk_table_attach (GTK_TABLE (table), icon_entry, 1, 2, 2, 3,
+		    GTK_FILL | GTK_EXPAND, GTK_FILL, 0, 0);
+  gtk_icon_entry_add_clear_button (GTK_ICON_ENTRY (icon_entry));
+
+  icon = gtk_image_new_from_stock (GTK_STOCK_FIND, GTK_ICON_SIZE_MENU);
+  gtk_widget_show (icon);
+  gtk_icon_entry_set_icon (GTK_ICON_ENTRY (icon_entry),
+			   GTK_ICON_ENTRY_PRIMARY,
+			   GTK_IMAGE (icon));
+
+  /*
+   * Password
+   */
+  label = gtk_label_new ("Password:");
+  gtk_widget_show (label);
+  gtk_table_attach (GTK_TABLE (table), label, 0, 1, 3, 4,
+		    GTK_FILL, GTK_FILL, 0, 0);
+  gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
+
+  icon_entry = gtk_icon_entry_new ();
+  gtk_widget_show (icon_entry);
+  gtk_table_attach (GTK_TABLE (table), icon_entry, 1, 2, 3, 4,
+		    GTK_FILL | GTK_EXPAND, GTK_FILL, 0, 0);
+  gtk_entry_set_visibility (GTK_ENTRY (icon_entry), FALSE);
+
+  icon = gtk_image_new_from_stock (GTK_STOCK_DIALOG_AUTHENTICATION,
+				   GTK_ICON_SIZE_MENU);
+  gtk_widget_show (icon);
+  gtk_icon_entry_set_icon (GTK_ICON_ENTRY (icon_entry),
+			   GTK_ICON_ENTRY_PRIMARY,
+			   GTK_IMAGE (icon));
+
+  /* Name */
+  label = gtk_label_new ("Name:");
+  gtk_widget_show (label);
+  gtk_table_attach (GTK_TABLE (table), label, 0, 1, 4, 5,
+		    GTK_FILL, GTK_FILL, 0, 0);
+  gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
+
+  icon_entry = gtk_icon_entry_new ();
+  gtk_widget_show (icon_entry);
+  gtk_table_attach (GTK_TABLE (table), icon_entry, 1, 2, 4, 5,
+		    GTK_FILL | GTK_EXPAND, GTK_FILL, 0, 0);
+
+  gtk_main();
+
+  return 0;
+}
Index: tests/Makefile.am
===================================================================
--- tests/Makefile.am	(revision 19352)
+++ tests/Makefile.am	(working copy)
@@ -69,6 +69,7 @@
 	testicontheme			\
 	testimage			\
 	testinput			\
+	testlinklabel			\
 	testmenus			\
 	testmenubars			\
 	testmultidisplay		\
@@ -143,6 +144,7 @@
 testgtk_DEPENDENCIES = $(TEST_DEPS)
 testinput_DEPENDENCIES = $(TEST_DEPS)
 testimage_DEPENDENCIES = $(TEST_DEPS)
+testlinklabel_DEPENDENCIES = $(TEST_DEPS)
 testmenus_DEPENDENCIES = $(TEST_DEPS)
 testmenubars_DEPENDENCIES = $(TEST_DEPS)
 testmultidisplay_DEPENDENCIES = $(TEST_DEPS)
@@ -197,6 +199,7 @@
 testiconview_LDADD = $(LDADDS)
 testinput_LDADD = $(LDADDS)
 testimage_LDADD = $(LDADDS)
+testlinklabel_LDADD = $(LDADDS)
 testmenus_LDADD = $(LDADDS)
 testmenubars_LDADD = $(LDADDS)
 testmultidisplay_LDADD = $(LDADDS)
