policykit-gnome r61 - in trunk: . src tools



Author: davidz
Date: Wed Aug 20 01:33:15 2008
New Revision: 61
URL: http://svn.gnome.org/viewvc/policykit-gnome?rev=61&view=rev

Log:
don't link against libsexy; use local copies instead

Added:
   trunk/src/sexy-url-label.c
   trunk/src/sexy-url-label.h
   trunk/tools/sexy-url-label.c
   trunk/tools/sexy-url-label.h
Modified:
   trunk/configure.ac
   trunk/src/Makefile.am
   trunk/tools/Makefile.am
   trunk/tools/polkit-gnome-authorization.c

Modified: trunk/configure.ac
==============================================================================
--- trunk/configure.ac	(original)
+++ trunk/configure.ac	Wed Aug 20 01:33:15 2008
@@ -123,7 +123,6 @@
 # ***************************
 
 GTK_REQUIRED=2.12.0
-SEXY_REQUIRED=0.1.11
 GNOME_VFS_REQUIRED=2.4
 DBUS_GLIB_REQUIRED=0.71
 POLKIT_DBUS_REQUIRED=0.7
@@ -133,10 +132,6 @@
 AC_SUBST(GTK_CFLAGS)
 AC_SUBST(GTK_LIBS)
 
-PKG_CHECK_MODULES(SEXY, libsexy >= $SEXY_REQUIRED)
-AC_SUBST(SEXY_CFLAGS)
-AC_SUBST(SEXY_LIBS)
-
 PKG_CHECK_MODULES(GNOME_VFS, gnome-vfs-2.0 >= $GNOME_VFS_REQUIRED)
 AC_SUBST(GNOME_VFS_CFLAGS)
 AC_SUBST(GNOME_VFS_LIBS)

Modified: trunk/src/Makefile.am
==============================================================================
--- trunk/src/Makefile.am	(original)
+++ trunk/src/Makefile.am	Wed Aug 20 01:33:15 2008
@@ -10,6 +10,7 @@
 polkit_gnome_manager_SOURCES = 						\
 	polkit-gnome-manager.h		polkit-gnome-manager.c		\
 	polkit-gnome-auth-dialog.h	polkit-gnome-auth-dialog.c	\
+	sexy-url-label.h 		sexy-url-label.c		\
 	main.c								\
 	$(BUILT_SOURCES)
 
@@ -23,7 +24,6 @@
 
 polkit_gnome_manager_CFLAGS = 				\
 	$(GTK_CFLAGS)					\
-	$(SEXY_CFLAGS)					\
 	$(GNOME_VFS_CFLAGS)				\
 	$(DBUS_GLIB_CFLAGS)				\
 	$(POLKIT_DBUS_CFLAGS)				\
@@ -36,7 +36,6 @@
 
 polkit_gnome_manager_LDADD = 				\
 	$(GTK_LIBS)					\
-	$(SEXY_LIBS)					\
 	$(GNOME_VFS_LIBS)				\
 	$(DBUS_GLIB_LIBS)				\
 	$(POLKIT_DBUS_LIBS)				\

Added: trunk/src/sexy-url-label.c
==============================================================================
--- (empty file)
+++ trunk/src/sexy-url-label.c	Wed Aug 20 01:33:15 2008
@@ -0,0 +1,868 @@
+/*
+ * @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 <libsexy/sexy-url-label.h>
+#include <gtk/gtk.h>
+#include <string.h>
+#include <stdio.h>
+
+#define SEXY_URL_LABEL_GET_PRIVATE(obj) \
+	(G_TYPE_INSTANCE_GET_PRIVATE((obj), SEXY_TYPE_URL_LABEL, \
+								 SexyUrlLabelPrivate))
+
+typedef struct
+{
+	int start;
+	int end;
+	const gchar *url;
+
+} SexyUrlLabelLink;
+
+typedef struct
+{
+	GList *links;
+	GList *urls;
+	SexyUrlLabelLink *active_link;
+	GtkWidget *popup_menu;
+	GdkWindow *event_window;
+
+	int layout_x;
+	int layout_y;
+
+	size_t wrap_width;
+
+	GString *temp_markup_result;
+
+} SexyUrlLabelPrivate;
+
+/*
+ * 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 sexy_url_label_finalize(GObject *obj);
+static void sexy_url_label_realize(GtkWidget *widget);
+static void sexy_url_label_unrealize(GtkWidget *widget);
+static void sexy_url_label_map(GtkWidget *widget);
+static void sexy_url_label_unmap(GtkWidget *widget);
+static void sexy_url_label_size_allocate(GtkWidget *widget,
+										 GtkAllocation *allocation);
+static gboolean sexy_url_label_motion_notify_event(GtkWidget *widget,
+												   GdkEventMotion *event);
+static gboolean sexy_url_label_leave_notify_event(GtkWidget *widget,
+												  GdkEventCrossing *event);
+static gboolean sexy_url_label_button_press_event(GtkWidget *widget,
+												  GdkEventButton *event);
+
+static void open_link_activate_cb(GtkMenuItem *menu_item,
+								  SexyUrlLabel *url_label);
+static void copy_link_activate_cb(GtkMenuItem *menu_item,
+								  SexyUrlLabel *url_label);
+
+static void sexy_url_label_clear_links(SexyUrlLabel *url_label);
+static void sexy_url_label_clear_urls(SexyUrlLabel *url_label);
+static void sexy_url_label_rescan_label(SexyUrlLabel *url_label);
+
+static GtkLabelClass *parent_class = NULL;
+static guint signals[LAST_SIGNAL] = {0};
+
+G_DEFINE_TYPE(SexyUrlLabel, sexy_url_label, GTK_TYPE_LABEL);
+
+static void
+sexy_url_label_class_init(SexyUrlLabelClass *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 = sexy_url_label_finalize;
+
+	widget_class->realize             = sexy_url_label_realize;
+	widget_class->unrealize           = sexy_url_label_unrealize;
+	widget_class->map                 = sexy_url_label_map;
+	widget_class->unmap               = sexy_url_label_unmap;
+	widget_class->size_allocate       = sexy_url_label_size_allocate;
+	widget_class->motion_notify_event = sexy_url_label_motion_notify_event;
+	widget_class->leave_notify_event  = sexy_url_label_leave_notify_event;
+	widget_class->button_press_event  = sexy_url_label_button_press_event;
+
+	g_type_class_add_private(klass, sizeof(SexyUrlLabelPrivate));
+
+	/**
+	 * SexyUrlLabel::url-activated:
+	 * @url_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(SexyUrlLabelClass, url_activated),
+					 NULL, NULL,
+					 g_cclosure_marshal_VOID__STRING,
+					 G_TYPE_NONE, 1,
+					 G_TYPE_STRING);
+}
+
+static void
+selectable_changed_cb(SexyUrlLabel *url_label)
+{
+	SexyUrlLabelPrivate *priv = SEXY_URL_LABEL_GET_PRIVATE(url_label);
+
+	if (priv->event_window != NULL)
+	{
+		GdkCursor *cursor = NULL;
+
+		if (gtk_label_get_selectable(GTK_LABEL(url_label)) &&
+			priv->active_link == NULL)
+			cursor = gdk_cursor_new_for_display(
+				gtk_widget_get_display(GTK_WIDGET(url_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
+sexy_url_label_init(SexyUrlLabel *url_label)
+{
+	SexyUrlLabelPrivate *priv = SEXY_URL_LABEL_GET_PRIVATE(url_label);
+	GtkWidget *item;
+	GtkWidget *image;
+
+	priv->links        = NULL;
+	priv->active_link  = NULL;
+	priv->event_window = NULL;
+
+	g_signal_connect(G_OBJECT(url_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), url_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), url_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
+sexy_url_label_finalize(GObject *obj)
+{
+	SexyUrlLabel *url_label = SEXY_URL_LABEL(obj);
+
+	sexy_url_label_clear_links(url_label);
+	sexy_url_label_clear_urls(url_label);
+
+	if (G_OBJECT_CLASS(parent_class)->finalize != NULL)
+		G_OBJECT_CLASS(parent_class)->finalize(obj);
+}
+
+static gboolean
+sexy_url_label_motion_notify_event(GtkWidget *widget, GdkEventMotion *event)
+{
+	SexyUrlLabel *url_label = (SexyUrlLabel *)widget;
+	SexyUrlLabelPrivate *priv = SEXY_URL_LABEL_GET_PRIVATE(url_label);
+	PangoLayout *layout = gtk_label_get_layout(GTK_LABEL(url_label));
+	GdkModifierType state;
+	gboolean found = FALSE;
+	GList *l;
+	int index, trailing;
+	int x, y;
+	SexyUrlLabelLink *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 = (SexyUrlLabelLink *)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(url_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
+sexy_url_label_leave_notify_event(GtkWidget *widget, GdkEventCrossing *event)
+{
+	SexyUrlLabel *url_label = (SexyUrlLabel *)widget;
+	SexyUrlLabelPrivate *priv = SEXY_URL_LABEL_GET_PRIVATE(url_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
+sexy_url_label_button_press_event(GtkWidget *widget, GdkEventButton *event)
+{
+	SexyUrlLabel *url_label = (SexyUrlLabel *)widget;
+	SexyUrlLabelPrivate *priv = SEXY_URL_LABEL_GET_PRIVATE(url_label);
+
+	if (priv->active_link == NULL)
+	{
+		return GTK_WIDGET_CLASS(parent_class)->button_press_event(widget,
+																  event);
+	}
+
+	if (event->button == 1)
+	{
+		g_signal_emit(url_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(SexyUrlLabel *url_label, size_t wrap_width)
+{
+	SexyUrlLabelPrivate *priv = SEXY_URL_LABEL_GET_PRIVATE(url_label);
+	LabelWrapWidth *wrap_width_data;
+	GtkStyle *style;
+
+	if (wrap_width == 0 || !gtk_label_get_line_wrap(GTK_LABEL(url_label)))
+		return;
+
+#if 0
+	pango_layout_set_width(gtk_label_get_layout(GTK_LABEL(url_label)),
+						   wrap_width * PANGO_SCALE);
+#endif
+	style = GTK_WIDGET(url_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(url_label)->layout);
+		GTK_LABEL(url_label)->layout = NULL;
+		gtk_label_get_layout(GTK_LABEL(url_label));
+		gtk_widget_queue_resize(GTK_WIDGET(url_label));
+	}
+}
+
+static void
+sexy_url_label_realize(GtkWidget *widget)
+{
+	SexyUrlLabel *url_label = (SexyUrlLabel *)widget;
+	SexyUrlLabelPrivate *priv = SEXY_URL_LABEL_GET_PRIVATE(url_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
+sexy_url_label_unrealize(GtkWidget *widget)
+{
+	SexyUrlLabel *url_label = (SexyUrlLabel *)widget;
+	SexyUrlLabelPrivate *priv = SEXY_URL_LABEL_GET_PRIVATE(url_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
+sexy_url_label_map(GtkWidget *widget)
+{
+	SexyUrlLabel *url_label = (SexyUrlLabel *)widget;
+	SexyUrlLabelPrivate *priv = SEXY_URL_LABEL_GET_PRIVATE(url_label);
+
+	GTK_WIDGET_CLASS(parent_class)->map(widget);
+
+	if (priv->event_window != NULL)
+		gdk_window_show(priv->event_window);
+}
+
+static void
+sexy_url_label_unmap(GtkWidget *widget)
+{
+	SexyUrlLabel *url_label = (SexyUrlLabel *)widget;
+	SexyUrlLabelPrivate *priv = SEXY_URL_LABEL_GET_PRIVATE(url_label);
+
+	if (priv->event_window != NULL)
+		gdk_window_hide(priv->event_window);
+
+	GTK_WIDGET_CLASS(parent_class)->unmap(widget);
+}
+
+static void
+sexy_url_label_size_allocate(GtkWidget *widget, GtkAllocation *allocation)
+{
+	SexyUrlLabel *url_label = (SexyUrlLabel *)widget;
+	SexyUrlLabelPrivate *priv = SEXY_URL_LABEL_GET_PRIVATE(url_label);
+
+#if 0
+	{
+		LabelWrapWidth *wrap_width_data;
+		GtkStyle *style;
+		style = GTK_WIDGET(url_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(url_label, allocation->width);
+	GTK_WIDGET_CLASS(parent_class)->size_allocate(widget, allocation);
+	pango_layout_set_width(gtk_label_get_layout(GTK_LABEL(url_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);
+	}
+
+	sexy_url_label_rescan_label(url_label);
+}
+
+static void
+open_link_activate_cb(GtkMenuItem *menu_item, SexyUrlLabel *url_label)
+{
+	SexyUrlLabelPrivate *priv = SEXY_URL_LABEL_GET_PRIVATE(url_label);
+
+	if (priv->active_link == NULL)
+		return;
+
+	g_signal_emit(url_label, signals[URL_ACTIVATED], 0, priv->active_link->url);
+}
+
+static void
+copy_link_activate_cb(GtkMenuItem *menu_item, SexyUrlLabel *url_label)
+{
+	SexyUrlLabelPrivate *priv = SEXY_URL_LABEL_GET_PRIVATE(url_label);
+	GtkClipboard *clipboard;
+
+	if (priv->active_link == NULL)
+		return;
+
+	clipboard = gtk_widget_get_clipboard(GTK_WIDGET(url_label),
+										 GDK_SELECTION_PRIMARY);
+
+	gtk_clipboard_set_text(clipboard, priv->active_link->url,
+						   strlen(priv->active_link->url));
+}
+
+/**
+ * sexy_url_label_new
+ *
+ * Creates a new SexyUrlLabel widget.
+ *
+ * Returns: a new #SexyUrlLabel.
+ */
+GtkWidget *
+sexy_url_label_new(void)
+{
+	return g_object_new(SEXY_TYPE_URL_LABEL, NULL);
+}
+
+static void
+sexy_url_label_clear_links(SexyUrlLabel *url_label)
+{
+	SexyUrlLabelPrivate *priv = SEXY_URL_LABEL_GET_PRIVATE(url_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
+sexy_url_label_clear_urls(SexyUrlLabel *url_label)
+{
+	SexyUrlLabelPrivate *priv = SEXY_URL_LABEL_GET_PRIVATE(url_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
+sexy_url_label_rescan_label(SexyUrlLabel *url_label)
+{
+	SexyUrlLabelPrivate *priv = SEXY_URL_LABEL_GET_PRIVATE(url_label);
+	PangoLayout *layout = gtk_label_get_layout(GTK_LABEL(url_label));
+	PangoAttrList *list = pango_layout_get_attributes(layout);
+	PangoAttrIterator *iter;
+	GList *url_list;
+
+	sexy_url_label_clear_links(url_label);
+
+	if (list == NULL)
+		return;
+
+	iter = pango_attr_list_get_iterator(list);
+
+	gtk_label_get_layout_offsets(GTK_LABEL(url_label),
+								 &priv->layout_x, &priv->layout_y);
+
+	priv->layout_x -= GTK_WIDGET(url_label)->allocation.x;
+	priv->layout_y -= GTK_WIDGET(url_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;
+			SexyUrlLabelLink *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(SexyUrlLabelLink, 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)
+{
+	SexyUrlLabel *url_label   = SEXY_URL_LABEL(user_data);
+	SexyUrlLabelPrivate *priv = SEXY_URL_LABEL_GET_PRIVATE(url_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)
+{
+	SexyUrlLabel *url_label   = SEXY_URL_LABEL(user_data);
+	SexyUrlLabelPrivate *priv = SEXY_URL_LABEL_GET_PRIVATE(url_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)
+{
+	SexyUrlLabel *url_label   = SEXY_URL_LABEL(user_data);
+	SexyUrlLabelPrivate *priv = SEXY_URL_LABEL_GET_PRIVATE(url_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(SexyUrlLabel *url_label, const gchar *markup,
+					gchar **ret_markup)
+{
+	GMarkupParseContext *context = NULL;
+	SexyUrlLabelPrivate *priv = SEXY_URL_LABEL_GET_PRIVATE(url_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, url_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;
+}
+
+/**
+ * sexy_url_label_set_markup
+ * @url_label: A #SexyUrlLabel.
+ * @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
+sexy_url_label_set_markup(SexyUrlLabel *url_label, const gchar *markup)
+{
+	SexyUrlLabelPrivate *priv;
+	gchar *new_markup;
+
+	g_return_if_fail(SEXY_IS_URL_LABEL(url_label));
+
+	priv = SEXY_URL_LABEL_GET_PRIVATE(url_label);
+
+	sexy_url_label_clear_links(url_label);
+	sexy_url_label_clear_urls(url_label);
+
+	if (markup == NULL || *markup == '\0')
+	{
+		gtk_label_set_markup(GTK_LABEL(url_label), "");
+		return;
+	}
+
+	if (parse_custom_markup(url_label, markup, &new_markup))
+	{
+		gtk_label_set_markup(GTK_LABEL(url_label), new_markup);
+		g_free(new_markup);
+	}
+	else
+	{
+		gtk_label_set_markup(GTK_LABEL(url_label), "");
+	}
+
+	sexy_url_label_rescan_label(url_label);
+
+	update_wrap_width(url_label, priv->wrap_width);
+}
+
+// vim:ts=4 sw=4

Added: trunk/src/sexy-url-label.h
==============================================================================
--- (empty file)
+++ trunk/src/sexy-url-label.h	Wed Aug 20 01:33:15 2008
@@ -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 _SEXY_URL_LABEL_H_
+#define _SEXY_URL_LABEL_H_
+
+typedef struct _SexyUrlLabel      SexyUrlLabel;
+typedef struct _SexyUrlLabelClass SexyUrlLabelClass;
+
+#include <gtk/gtklabel.h>
+
+#define SEXY_TYPE_URL_LABEL (sexy_url_label_get_type())
+#define SEXY_URL_LABEL(obj) \
+		(G_TYPE_CHECK_INSTANCE_CAST((obj), SEXY_TYPE_URL_LABEL, SexyUrlLabel))
+#define SEXY_URL_LABEL_CLASS(klass) \
+		(G_TYPE_CHECK_CLASS_CAST((klass), SEXY_TYPE_URL_LABEL, SexyUrlLabelClass))
+#define SEXY_IS_URL_LABEL(obj) \
+		(G_TYPE_CHECK_INSTANCE_TYPE((obj), SEXY_TYPE_URL_LABEL))
+#define SEXY_IS_URL_LABEL_CLASS(klass) \
+		(G_TYPE_CHECK_CLASS_TYPE((klass), SEXY_TYPE_URL_LABEL))
+#define SEXY_URL_LABEL_GET_CLASS(obj) \
+		(G_TYPE_INSTANCE_GET_CLASS ((obj), SEXY_TYPE_URL_LABEL, SexyUrlLabelClass))
+
+struct _SexyUrlLabel
+{
+	GtkLabel parent_object;
+
+	void (*gtk_reserved1)(void);
+	void (*gtk_reserved2)(void);
+	void (*gtk_reserved3)(void);
+	void (*gtk_reserved4)(void);
+};
+
+struct _SexyUrlLabelClass
+{
+	GtkLabelClass parent_class;
+
+	/* Signals */
+	void (*url_activated)(SexyUrlLabel *url_label, const gchar *url);
+
+	void (*gtk_reserved1)(void);
+	void (*gtk_reserved2)(void);
+	void (*gtk_reserved3)(void);
+	void (*gtk_reserved4)(void);
+};
+
+G_BEGIN_DECLS
+
+GType sexy_url_label_get_type(void);
+
+GtkWidget *sexy_url_label_new(void);
+void sexy_url_label_set_markup(SexyUrlLabel *url_label, const gchar *markup);
+
+G_END_DECLS
+
+#endif /* _SEXY_URL_LABEL_H_ */

Modified: trunk/tools/Makefile.am
==============================================================================
--- trunk/tools/Makefile.am	(original)
+++ trunk/tools/Makefile.am	Wed Aug 20 01:33:15 2008
@@ -1,8 +1,9 @@
 
 bin_PROGRAMS = polkit-gnome-authorization
 
-polkit_gnome_authorization_SOURCES = 			\
-	polkit-gnome-authorization.c
+polkit_gnome_authorization_SOURCES = 						\
+						polkit-gnome-authorization.c	\
+	sexy-url-label.h 			sexy-url-label.c
 
 polkit_gnome_authorization_CPPFLAGS = 			\
 	-I$(top_srcdir)					\
@@ -14,7 +15,6 @@
 
 polkit_gnome_authorization_CFLAGS = 			\
 	$(GTK_CFLAGS)					\
-	$(SEXY_CFLAGS)					\
 	$(GNOME_VFS_CFLAGS)				\
 	$(DBUS_GLIB_CFLAGS)				\
 	$(POLKIT_DBUS_CFLAGS)				\
@@ -27,7 +27,6 @@
 
 polkit_gnome_authorization_LDADD = 			\
 	$(GTK_LIBS)					\
-	$(SEXY_LIBS)					\
 	$(GNOME_VFS_LIBS)				\
 	$(DBUS_GLIB_LIBS)				\
 	$(POLKIT_DBUS_LIBS)				\

Modified: trunk/tools/polkit-gnome-authorization.c
==============================================================================
--- trunk/tools/polkit-gnome-authorization.c	(original)
+++ trunk/tools/polkit-gnome-authorization.c	Wed Aug 20 01:33:15 2008
@@ -36,9 +36,10 @@
 #include <dbus/dbus-glib.h>
 #include <dbus/dbus-glib-lowlevel.h>
 #include <polkit-gnome/polkit-gnome.h>
-#include <libsexy/sexy.h>
 #include <libgnomevfs/gnome-vfs-utils.h>
 
+#include "sexy-url-label.h"
+
 static GType boxed_pfe_type;
 
 enum

Added: trunk/tools/sexy-url-label.c
==============================================================================
--- (empty file)
+++ trunk/tools/sexy-url-label.c	Wed Aug 20 01:33:15 2008
@@ -0,0 +1,868 @@
+/*
+ * @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 <libsexy/sexy-url-label.h>
+#include <gtk/gtk.h>
+#include <string.h>
+#include <stdio.h>
+
+#define SEXY_URL_LABEL_GET_PRIVATE(obj) \
+	(G_TYPE_INSTANCE_GET_PRIVATE((obj), SEXY_TYPE_URL_LABEL, \
+								 SexyUrlLabelPrivate))
+
+typedef struct
+{
+	int start;
+	int end;
+	const gchar *url;
+
+} SexyUrlLabelLink;
+
+typedef struct
+{
+	GList *links;
+	GList *urls;
+	SexyUrlLabelLink *active_link;
+	GtkWidget *popup_menu;
+	GdkWindow *event_window;
+
+	int layout_x;
+	int layout_y;
+
+	size_t wrap_width;
+
+	GString *temp_markup_result;
+
+} SexyUrlLabelPrivate;
+
+/*
+ * 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 sexy_url_label_finalize(GObject *obj);
+static void sexy_url_label_realize(GtkWidget *widget);
+static void sexy_url_label_unrealize(GtkWidget *widget);
+static void sexy_url_label_map(GtkWidget *widget);
+static void sexy_url_label_unmap(GtkWidget *widget);
+static void sexy_url_label_size_allocate(GtkWidget *widget,
+										 GtkAllocation *allocation);
+static gboolean sexy_url_label_motion_notify_event(GtkWidget *widget,
+												   GdkEventMotion *event);
+static gboolean sexy_url_label_leave_notify_event(GtkWidget *widget,
+												  GdkEventCrossing *event);
+static gboolean sexy_url_label_button_press_event(GtkWidget *widget,
+												  GdkEventButton *event);
+
+static void open_link_activate_cb(GtkMenuItem *menu_item,
+								  SexyUrlLabel *url_label);
+static void copy_link_activate_cb(GtkMenuItem *menu_item,
+								  SexyUrlLabel *url_label);
+
+static void sexy_url_label_clear_links(SexyUrlLabel *url_label);
+static void sexy_url_label_clear_urls(SexyUrlLabel *url_label);
+static void sexy_url_label_rescan_label(SexyUrlLabel *url_label);
+
+static GtkLabelClass *parent_class = NULL;
+static guint signals[LAST_SIGNAL] = {0};
+
+G_DEFINE_TYPE(SexyUrlLabel, sexy_url_label, GTK_TYPE_LABEL);
+
+static void
+sexy_url_label_class_init(SexyUrlLabelClass *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 = sexy_url_label_finalize;
+
+	widget_class->realize             = sexy_url_label_realize;
+	widget_class->unrealize           = sexy_url_label_unrealize;
+	widget_class->map                 = sexy_url_label_map;
+	widget_class->unmap               = sexy_url_label_unmap;
+	widget_class->size_allocate       = sexy_url_label_size_allocate;
+	widget_class->motion_notify_event = sexy_url_label_motion_notify_event;
+	widget_class->leave_notify_event  = sexy_url_label_leave_notify_event;
+	widget_class->button_press_event  = sexy_url_label_button_press_event;
+
+	g_type_class_add_private(klass, sizeof(SexyUrlLabelPrivate));
+
+	/**
+	 * SexyUrlLabel::url-activated:
+	 * @url_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(SexyUrlLabelClass, url_activated),
+					 NULL, NULL,
+					 g_cclosure_marshal_VOID__STRING,
+					 G_TYPE_NONE, 1,
+					 G_TYPE_STRING);
+}
+
+static void
+selectable_changed_cb(SexyUrlLabel *url_label)
+{
+	SexyUrlLabelPrivate *priv = SEXY_URL_LABEL_GET_PRIVATE(url_label);
+
+	if (priv->event_window != NULL)
+	{
+		GdkCursor *cursor = NULL;
+
+		if (gtk_label_get_selectable(GTK_LABEL(url_label)) &&
+			priv->active_link == NULL)
+			cursor = gdk_cursor_new_for_display(
+				gtk_widget_get_display(GTK_WIDGET(url_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
+sexy_url_label_init(SexyUrlLabel *url_label)
+{
+	SexyUrlLabelPrivate *priv = SEXY_URL_LABEL_GET_PRIVATE(url_label);
+	GtkWidget *item;
+	GtkWidget *image;
+
+	priv->links        = NULL;
+	priv->active_link  = NULL;
+	priv->event_window = NULL;
+
+	g_signal_connect(G_OBJECT(url_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), url_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), url_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
+sexy_url_label_finalize(GObject *obj)
+{
+	SexyUrlLabel *url_label = SEXY_URL_LABEL(obj);
+
+	sexy_url_label_clear_links(url_label);
+	sexy_url_label_clear_urls(url_label);
+
+	if (G_OBJECT_CLASS(parent_class)->finalize != NULL)
+		G_OBJECT_CLASS(parent_class)->finalize(obj);
+}
+
+static gboolean
+sexy_url_label_motion_notify_event(GtkWidget *widget, GdkEventMotion *event)
+{
+	SexyUrlLabel *url_label = (SexyUrlLabel *)widget;
+	SexyUrlLabelPrivate *priv = SEXY_URL_LABEL_GET_PRIVATE(url_label);
+	PangoLayout *layout = gtk_label_get_layout(GTK_LABEL(url_label));
+	GdkModifierType state;
+	gboolean found = FALSE;
+	GList *l;
+	int index, trailing;
+	int x, y;
+	SexyUrlLabelLink *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 = (SexyUrlLabelLink *)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(url_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
+sexy_url_label_leave_notify_event(GtkWidget *widget, GdkEventCrossing *event)
+{
+	SexyUrlLabel *url_label = (SexyUrlLabel *)widget;
+	SexyUrlLabelPrivate *priv = SEXY_URL_LABEL_GET_PRIVATE(url_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
+sexy_url_label_button_press_event(GtkWidget *widget, GdkEventButton *event)
+{
+	SexyUrlLabel *url_label = (SexyUrlLabel *)widget;
+	SexyUrlLabelPrivate *priv = SEXY_URL_LABEL_GET_PRIVATE(url_label);
+
+	if (priv->active_link == NULL)
+	{
+		return GTK_WIDGET_CLASS(parent_class)->button_press_event(widget,
+																  event);
+	}
+
+	if (event->button == 1)
+	{
+		g_signal_emit(url_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(SexyUrlLabel *url_label, size_t wrap_width)
+{
+	SexyUrlLabelPrivate *priv = SEXY_URL_LABEL_GET_PRIVATE(url_label);
+	LabelWrapWidth *wrap_width_data;
+	GtkStyle *style;
+
+	if (wrap_width == 0 || !gtk_label_get_line_wrap(GTK_LABEL(url_label)))
+		return;
+
+#if 0
+	pango_layout_set_width(gtk_label_get_layout(GTK_LABEL(url_label)),
+						   wrap_width * PANGO_SCALE);
+#endif
+	style = GTK_WIDGET(url_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(url_label)->layout);
+		GTK_LABEL(url_label)->layout = NULL;
+		gtk_label_get_layout(GTK_LABEL(url_label));
+		gtk_widget_queue_resize(GTK_WIDGET(url_label));
+	}
+}
+
+static void
+sexy_url_label_realize(GtkWidget *widget)
+{
+	SexyUrlLabel *url_label = (SexyUrlLabel *)widget;
+	SexyUrlLabelPrivate *priv = SEXY_URL_LABEL_GET_PRIVATE(url_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
+sexy_url_label_unrealize(GtkWidget *widget)
+{
+	SexyUrlLabel *url_label = (SexyUrlLabel *)widget;
+	SexyUrlLabelPrivate *priv = SEXY_URL_LABEL_GET_PRIVATE(url_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
+sexy_url_label_map(GtkWidget *widget)
+{
+	SexyUrlLabel *url_label = (SexyUrlLabel *)widget;
+	SexyUrlLabelPrivate *priv = SEXY_URL_LABEL_GET_PRIVATE(url_label);
+
+	GTK_WIDGET_CLASS(parent_class)->map(widget);
+
+	if (priv->event_window != NULL)
+		gdk_window_show(priv->event_window);
+}
+
+static void
+sexy_url_label_unmap(GtkWidget *widget)
+{
+	SexyUrlLabel *url_label = (SexyUrlLabel *)widget;
+	SexyUrlLabelPrivate *priv = SEXY_URL_LABEL_GET_PRIVATE(url_label);
+
+	if (priv->event_window != NULL)
+		gdk_window_hide(priv->event_window);
+
+	GTK_WIDGET_CLASS(parent_class)->unmap(widget);
+}
+
+static void
+sexy_url_label_size_allocate(GtkWidget *widget, GtkAllocation *allocation)
+{
+	SexyUrlLabel *url_label = (SexyUrlLabel *)widget;
+	SexyUrlLabelPrivate *priv = SEXY_URL_LABEL_GET_PRIVATE(url_label);
+
+#if 0
+	{
+		LabelWrapWidth *wrap_width_data;
+		GtkStyle *style;
+		style = GTK_WIDGET(url_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(url_label, allocation->width);
+	GTK_WIDGET_CLASS(parent_class)->size_allocate(widget, allocation);
+	pango_layout_set_width(gtk_label_get_layout(GTK_LABEL(url_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);
+	}
+
+	sexy_url_label_rescan_label(url_label);
+}
+
+static void
+open_link_activate_cb(GtkMenuItem *menu_item, SexyUrlLabel *url_label)
+{
+	SexyUrlLabelPrivate *priv = SEXY_URL_LABEL_GET_PRIVATE(url_label);
+
+	if (priv->active_link == NULL)
+		return;
+
+	g_signal_emit(url_label, signals[URL_ACTIVATED], 0, priv->active_link->url);
+}
+
+static void
+copy_link_activate_cb(GtkMenuItem *menu_item, SexyUrlLabel *url_label)
+{
+	SexyUrlLabelPrivate *priv = SEXY_URL_LABEL_GET_PRIVATE(url_label);
+	GtkClipboard *clipboard;
+
+	if (priv->active_link == NULL)
+		return;
+
+	clipboard = gtk_widget_get_clipboard(GTK_WIDGET(url_label),
+										 GDK_SELECTION_PRIMARY);
+
+	gtk_clipboard_set_text(clipboard, priv->active_link->url,
+						   strlen(priv->active_link->url));
+}
+
+/**
+ * sexy_url_label_new
+ *
+ * Creates a new SexyUrlLabel widget.
+ *
+ * Returns: a new #SexyUrlLabel.
+ */
+GtkWidget *
+sexy_url_label_new(void)
+{
+	return g_object_new(SEXY_TYPE_URL_LABEL, NULL);
+}
+
+static void
+sexy_url_label_clear_links(SexyUrlLabel *url_label)
+{
+	SexyUrlLabelPrivate *priv = SEXY_URL_LABEL_GET_PRIVATE(url_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
+sexy_url_label_clear_urls(SexyUrlLabel *url_label)
+{
+	SexyUrlLabelPrivate *priv = SEXY_URL_LABEL_GET_PRIVATE(url_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
+sexy_url_label_rescan_label(SexyUrlLabel *url_label)
+{
+	SexyUrlLabelPrivate *priv = SEXY_URL_LABEL_GET_PRIVATE(url_label);
+	PangoLayout *layout = gtk_label_get_layout(GTK_LABEL(url_label));
+	PangoAttrList *list = pango_layout_get_attributes(layout);
+	PangoAttrIterator *iter;
+	GList *url_list;
+
+	sexy_url_label_clear_links(url_label);
+
+	if (list == NULL)
+		return;
+
+	iter = pango_attr_list_get_iterator(list);
+
+	gtk_label_get_layout_offsets(GTK_LABEL(url_label),
+								 &priv->layout_x, &priv->layout_y);
+
+	priv->layout_x -= GTK_WIDGET(url_label)->allocation.x;
+	priv->layout_y -= GTK_WIDGET(url_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;
+			SexyUrlLabelLink *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(SexyUrlLabelLink, 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)
+{
+	SexyUrlLabel *url_label   = SEXY_URL_LABEL(user_data);
+	SexyUrlLabelPrivate *priv = SEXY_URL_LABEL_GET_PRIVATE(url_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)
+{
+	SexyUrlLabel *url_label   = SEXY_URL_LABEL(user_data);
+	SexyUrlLabelPrivate *priv = SEXY_URL_LABEL_GET_PRIVATE(url_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)
+{
+	SexyUrlLabel *url_label   = SEXY_URL_LABEL(user_data);
+	SexyUrlLabelPrivate *priv = SEXY_URL_LABEL_GET_PRIVATE(url_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(SexyUrlLabel *url_label, const gchar *markup,
+					gchar **ret_markup)
+{
+	GMarkupParseContext *context = NULL;
+	SexyUrlLabelPrivate *priv = SEXY_URL_LABEL_GET_PRIVATE(url_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, url_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;
+}
+
+/**
+ * sexy_url_label_set_markup
+ * @url_label: A #SexyUrlLabel.
+ * @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
+sexy_url_label_set_markup(SexyUrlLabel *url_label, const gchar *markup)
+{
+	SexyUrlLabelPrivate *priv;
+	gchar *new_markup;
+
+	g_return_if_fail(SEXY_IS_URL_LABEL(url_label));
+
+	priv = SEXY_URL_LABEL_GET_PRIVATE(url_label);
+
+	sexy_url_label_clear_links(url_label);
+	sexy_url_label_clear_urls(url_label);
+
+	if (markup == NULL || *markup == '\0')
+	{
+		gtk_label_set_markup(GTK_LABEL(url_label), "");
+		return;
+	}
+
+	if (parse_custom_markup(url_label, markup, &new_markup))
+	{
+		gtk_label_set_markup(GTK_LABEL(url_label), new_markup);
+		g_free(new_markup);
+	}
+	else
+	{
+		gtk_label_set_markup(GTK_LABEL(url_label), "");
+	}
+
+	sexy_url_label_rescan_label(url_label);
+
+	update_wrap_width(url_label, priv->wrap_width);
+}
+
+// vim:ts=4 sw=4

Added: trunk/tools/sexy-url-label.h
==============================================================================
--- (empty file)
+++ trunk/tools/sexy-url-label.h	Wed Aug 20 01:33:15 2008
@@ -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 _SEXY_URL_LABEL_H_
+#define _SEXY_URL_LABEL_H_
+
+typedef struct _SexyUrlLabel      SexyUrlLabel;
+typedef struct _SexyUrlLabelClass SexyUrlLabelClass;
+
+#include <gtk/gtklabel.h>
+
+#define SEXY_TYPE_URL_LABEL (sexy_url_label_get_type())
+#define SEXY_URL_LABEL(obj) \
+		(G_TYPE_CHECK_INSTANCE_CAST((obj), SEXY_TYPE_URL_LABEL, SexyUrlLabel))
+#define SEXY_URL_LABEL_CLASS(klass) \
+		(G_TYPE_CHECK_CLASS_CAST((klass), SEXY_TYPE_URL_LABEL, SexyUrlLabelClass))
+#define SEXY_IS_URL_LABEL(obj) \
+		(G_TYPE_CHECK_INSTANCE_TYPE((obj), SEXY_TYPE_URL_LABEL))
+#define SEXY_IS_URL_LABEL_CLASS(klass) \
+		(G_TYPE_CHECK_CLASS_TYPE((klass), SEXY_TYPE_URL_LABEL))
+#define SEXY_URL_LABEL_GET_CLASS(obj) \
+		(G_TYPE_INSTANCE_GET_CLASS ((obj), SEXY_TYPE_URL_LABEL, SexyUrlLabelClass))
+
+struct _SexyUrlLabel
+{
+	GtkLabel parent_object;
+
+	void (*gtk_reserved1)(void);
+	void (*gtk_reserved2)(void);
+	void (*gtk_reserved3)(void);
+	void (*gtk_reserved4)(void);
+};
+
+struct _SexyUrlLabelClass
+{
+	GtkLabelClass parent_class;
+
+	/* Signals */
+	void (*url_activated)(SexyUrlLabel *url_label, const gchar *url);
+
+	void (*gtk_reserved1)(void);
+	void (*gtk_reserved2)(void);
+	void (*gtk_reserved3)(void);
+	void (*gtk_reserved4)(void);
+};
+
+G_BEGIN_DECLS
+
+GType sexy_url_label_get_type(void);
+
+GtkWidget *sexy_url_label_new(void);
+void sexy_url_label_set_markup(SexyUrlLabel *url_label, const gchar *markup);
+
+G_END_DECLS
+
+#endif /* _SEXY_URL_LABEL_H_ */



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]