]> Pileus Git - ~andy/gtk/blobdiff - gtk/gtklabel.c
GtkLabel: move public members to private structure
[~andy/gtk] / gtk / gtklabel.c
index 209c3692e8856553e4a6113574db11be39aeea24..ae45c6383d83a108f86745e9cfff102cbd17e78c 100644 (file)
 #include "gtkstock.h"
 #include "gtkbindings.h"
 #include "gtkbuildable.h"
+#include "gtkimage.h"
+#include "gtkshow.h"
+#include "gtktooltip.h"
+#include "gtksizerequest.h"
 #include "gtkprivate.h"
-#include "gtkalias.h"
 
-#define GTK_LABEL_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), GTK_TYPE_LABEL, GtkLabelPrivate))
 
+struct _GtkLabelPriv
+{
+  GtkLabelSelectionInfo *select_info;
+  GtkWidget *mnemonic_widget;
+  GtkWindow *mnemonic_window;
+
+  PangoAttrList *attrs;
+  PangoAttrList *effective_attrs;
+  PangoLayout   *layout;
+
+  gboolean mnemonics_visible;
+
+  gchar   *label;
+  gchar   *text;
+
+  guint    jtype            : 2;
+  guint    wrap             : 1;
+  guint    use_underline    : 1;
+  guint    use_markup       : 1;
+  guint    ellipsize        : 3;
+  guint    single_line_mode : 1;
+  guint    have_transform   : 1;
+  guint    in_click         : 1;
+  guint    wrap_mode        : 3;
+  guint    pattern_set      : 1;
+  guint    track_links      : 1;
+
+  guint    mnemonic_keyval;
+
+  gint     wrap_width;
+  gint     width_chars;
+  gint     max_width_chars;
+};
+
+/* Notes about the handling of links:
+ *
+ * Links share the GtkLabelSelectionInfo struct with selectable labels.
+ * There are some new fields for links. The links field contains the list
+ * of GtkLabelLink structs that describe the links which are embedded in
+ * the label. The active_link field points to the link under the mouse
+ * pointer. For keyboard navigation, the 'focus' link is determined by
+ * finding the link which contains the selection_anchor position.
+ * The link_clicked field is used with button press and release events
+ * to ensure that pressing inside a link and releasing outside of it
+ * does not activate the link.
+ *
+ * Links are rendered with the link-color/visited-link-color colors
+ * that are determined by the style and with an underline. When the mouse
+ * pointer is over a link, the pointer is changed to indicate the link,
+ * and the background behind the link is rendered with the base[PRELIGHT]
+ * color. While a button is pressed over a link, the background is rendered
+ * with the base[ACTIVE] color.
+ *
+ * Labels with links accept keyboard focus, and it is possible to move
+ * the focus between the embedded links using Tab/Shift-Tab. The focus
+ * is indicated by a focus rectangle that is drawn around the link text.
+ * Pressing Enter activates the focussed link, and there is a suitable
+ * context menu for links that can be opened with the Menu key. Pressing
+ * Control-C copies the link URI to the clipboard.
+ *
+ * In selectable labels with links, link functionality is only available
+ * when the selection is empty.
+ */
 typedef struct
 {
-  gint wrap_width;
-  gint width_chars;
-  gint max_width_chars;
-}
-GtkLabelPrivate;
+  gchar *uri;
+  gchar *title;     /* the title attribute, used as tooltip */
+  gboolean visited; /* get set when the link is activated; this flag
+                     * gets preserved over later set_markup() calls
+                     */
+  gint start;       /* position of the link in the PangoLayout */
+  gint end;
+} GtkLabelLink;
 
 struct _GtkLabelSelectionInfo
 {
@@ -64,18 +132,25 @@ struct _GtkLabelSelectionInfo
   gint selection_anchor;
   gint selection_end;
   GtkWidget *popup_menu;
-  
+
+  GList *links;
+  GtkLabelLink *active_link;
+
   gint drag_start_x;
   gint drag_start_y;
 
-  guint in_drag : 1;
+  guint in_drag      : 1;
   guint select_words : 1;
+  guint selectable   : 1;
+  guint link_clicked : 1;
 };
 
 enum {
   MOVE_CURSOR,
   COPY_CLIPBOARD,
   POPULATE_POPUP,
+  ACTIVATE_LINK,
+  ACTIVATE_CURRENT_LINK,
   LAST_SIGNAL
 };
 
@@ -98,11 +173,20 @@ enum {
   PROP_WIDTH_CHARS,
   PROP_SINGLE_LINE_MODE,
   PROP_ANGLE,
-  PROP_MAX_WIDTH_CHARS
+  PROP_MAX_WIDTH_CHARS,
+  PROP_TRACK_VISITED_LINKS
 };
 
+/* When rotating ellipsizable text we want the natural size to request 
+ * more to ensure the label wont ever ellipsize in an allocation of full natural size.
+ * */
+#define ROTATION_ELLIPSIZE_PADDING 2
+
 static guint signals[LAST_SIGNAL] = { 0 };
 
+static const GdkColor default_link_color = { 0, 0, 0, 0xeeee };
+static const GdkColor default_visited_link_color = { 0, 0x5555, 0x1a1a, 0x8b8b };
+
 static void gtk_label_set_property      (GObject          *object,
                                         guint             prop_id,
                                         const GValue     *value,
@@ -113,8 +197,6 @@ static void gtk_label_get_property      (GObject          *object,
                                         GParamSpec       *pspec);
 static void gtk_label_destroy           (GtkObject        *object);
 static void gtk_label_finalize          (GObject          *object);
-static void gtk_label_size_request      (GtkWidget        *widget,
-                                        GtkRequisition   *requisition);
 static void gtk_label_size_allocate     (GtkWidget        *widget,
                                          GtkAllocation    *allocation);
 static void gtk_label_state_changed     (GtkWidget        *widget,
@@ -125,6 +207,8 @@ static void gtk_label_direction_changed (GtkWidget        *widget,
                                         GtkTextDirection  previous_dir);
 static gint gtk_label_expose            (GtkWidget        *widget,
                                         GdkEventExpose   *event);
+static gboolean gtk_label_focus         (GtkWidget         *widget,
+                                         GtkDirectionType   direction);
 
 static void gtk_label_realize           (GtkWidget        *widget);
 static void gtk_label_unrealize         (GtkWidget        *widget);
@@ -137,8 +221,16 @@ static gboolean gtk_label_button_release    (GtkWidget        *widget,
                                             GdkEventButton   *event);
 static gboolean gtk_label_motion            (GtkWidget        *widget,
                                             GdkEventMotion   *event);
+static gboolean gtk_label_leave_notify      (GtkWidget        *widget,
+                                             GdkEventCrossing *event);
+
 static void     gtk_label_grab_focus        (GtkWidget        *widget);
 
+static gboolean gtk_label_query_tooltip     (GtkWidget        *widget,
+                                             gint              x,
+                                             gint              y,
+                                             gboolean          keyboard_tip,
+                                             GtkTooltip       *tooltip);
 
 static void gtk_label_set_text_internal          (GtkLabel      *label,
                                                  gchar         *str);
@@ -153,8 +245,9 @@ static void gtk_label_set_attributes_internal    (GtkLabel      *label,
 static void gtk_label_set_uline_text_internal    (GtkLabel      *label,
                                                  const gchar   *str);
 static void gtk_label_set_pattern_internal       (GtkLabel      *label,
-                                                 const gchar   *pattern);
-static void set_markup                           (GtkLabel      *label,
+                                                 const gchar   *pattern,
+                                                  gboolean       is_mnemonic);
+static void gtk_label_set_markup_internal        (GtkLabel      *label,
                                                  const gchar   *str,
                                                  gboolean       with_uline);
 static void gtk_label_recalculate                (GtkLabel      *label);
@@ -162,11 +255,15 @@ static void gtk_label_hierarchy_changed          (GtkWidget     *widget,
                                                  GtkWidget     *old_toplevel);
 static void gtk_label_screen_changed             (GtkWidget     *widget,
                                                  GdkScreen     *old_screen);
+static gboolean gtk_label_popup_menu             (GtkWidget     *widget);
 
 static void gtk_label_create_window       (GtkLabel *label);
 static void gtk_label_destroy_window      (GtkLabel *label);
+static void gtk_label_ensure_select_info  (GtkLabel *label);
+static void gtk_label_clear_select_info   (GtkLabel *label);
+static void gtk_label_update_cursor       (GtkLabel *label);
 static void gtk_label_clear_layout        (GtkLabel *label);
-static void gtk_label_ensure_layout       (GtkLabel *label);
+static void gtk_label_ensure_layout       (GtkLabel *label, gboolean guess_wrap_width);
 static void gtk_label_invalidate_wrap_width (GtkLabel *label);
 static void gtk_label_select_region_index (GtkLabel *label,
                                            gint      anchor_index,
@@ -197,7 +294,14 @@ static void     gtk_label_buildable_custom_finished    (GtkBuildable     *builda
                                                        gpointer          user_data);
 
 
-/* For selectable lables: */
+static void connect_mnemonics_visible_notify    (GtkLabel   *label);
+static gboolean      separate_uline_pattern     (const gchar  *str,
+                                                 guint        *accel_key,
+                                                 gchar       **new_str,
+                                                 gchar       **pattern);
+
+
+/* For selectable labels: */
 static void gtk_label_move_cursor        (GtkLabel        *label,
                                          GtkMovementStep  step,
                                          gint             count,
@@ -206,19 +310,50 @@ static void gtk_label_copy_clipboard     (GtkLabel        *label);
 static void gtk_label_select_all         (GtkLabel        *label);
 static void gtk_label_do_popup           (GtkLabel        *label,
                                          GdkEventButton  *event);
-
 static gint gtk_label_move_forward_word  (GtkLabel        *label,
                                          gint             start);
 static gint gtk_label_move_backward_word (GtkLabel        *label,
                                          gint             start);
 
+/* For links: */
+static void          gtk_label_rescan_links     (GtkLabel  *label);
+static void          gtk_label_clear_links      (GtkLabel  *label);
+static gboolean      gtk_label_activate_link    (GtkLabel    *label,
+                                                 const gchar *uri);
+static void          gtk_label_activate_current_link (GtkLabel *label);
+static GtkLabelLink *gtk_label_get_current_link (GtkLabel  *label);
+static void          gtk_label_get_link_colors  (GtkWidget  *widget,
+                                                 GdkColor  **link_color,
+                                                 GdkColor  **visited_link_color);
+static void          emit_activate_link         (GtkLabel     *label,
+                                                 GtkLabelLink *link);
+
+static void               gtk_label_size_request_init     (GtkSizeRequestIface *iface);
+static GtkSizeRequestMode gtk_label_get_request_mode      (GtkSizeRequest      *widget);
+static void               gtk_label_get_width             (GtkSizeRequest      *widget,
+                                                          gint                *minimum_size,
+                                                          gint                *natural_size);
+static void               gtk_label_get_height            (GtkSizeRequest      *widget,
+                                                          gint                *minimum_size,
+                                                          gint                *natural_size);
+static void               gtk_label_get_width_for_height  (GtkSizeRequest      *widget,
+                                                          gint                 height,
+                                                          gint                *minimum_width,
+                                                          gint                *natural_width);
+static void               gtk_label_get_height_for_width  (GtkSizeRequest      *widget,
+                                                          gint                 width,
+                                                          gint                *minimum_height,
+                                                          gint                *natural_height);
+
 static GQuark quark_angle = 0;
 
 static GtkBuildableIface *buildable_parent_iface = NULL;
 
 G_DEFINE_TYPE_WITH_CODE (GtkLabel, gtk_label, GTK_TYPE_MISC,
                         G_IMPLEMENT_INTERFACE (GTK_TYPE_BUILDABLE,
-                                               gtk_label_buildable_interface_init));
+                                               gtk_label_buildable_interface_init)
+                         G_IMPLEMENT_INTERFACE (GTK_TYPE_SIZE_REQUEST,
+                                                gtk_label_size_request_init));
 
 static void
 add_move_binding (GtkBindingSet  *binding_set,
@@ -258,11 +393,11 @@ gtk_label_class_init (GtkLabelClass *class)
   gobject_class->finalize = gtk_label_finalize;
 
   object_class->destroy = gtk_label_destroy;
-  
-  widget_class->size_request = gtk_label_size_request;
+
   widget_class->size_allocate = gtk_label_size_allocate;
   widget_class->state_changed = gtk_label_state_changed;
   widget_class->style_set = gtk_label_style_set;
+  widget_class->query_tooltip = gtk_label_query_tooltip;
   widget_class->direction_changed = gtk_label_direction_changed;
   widget_class->expose_event = gtk_label_expose;
   widget_class->realize = gtk_label_realize;
@@ -272,15 +407,19 @@ gtk_label_class_init (GtkLabelClass *class)
   widget_class->button_press_event = gtk_label_button_press;
   widget_class->button_release_event = gtk_label_button_release;
   widget_class->motion_notify_event = gtk_label_motion;
+  widget_class->leave_notify_event = gtk_label_leave_notify;
   widget_class->hierarchy_changed = gtk_label_hierarchy_changed;
   widget_class->screen_changed = gtk_label_screen_changed;
   widget_class->mnemonic_activate = gtk_label_mnemonic_activate;
   widget_class->drag_data_get = gtk_label_drag_data_get;
   widget_class->grab_focus = gtk_label_grab_focus;
+  widget_class->popup_menu = gtk_label_popup_menu;
+  widget_class->focus = gtk_label_focus;
 
   class->move_cursor = gtk_label_move_cursor;
   class->copy_clipboard = gtk_label_copy_clipboard;
-  
+  class->activate_link = gtk_label_activate_link;
+
   /**
    * GtkLabel::move-cursor:
    * @entry: the object which received the signal
@@ -361,6 +500,51 @@ gtk_label_class_init (GtkLabelClass *class)
                  G_TYPE_NONE, 1,
                  GTK_TYPE_MENU);
 
+    /**
+     * GtkLabel::activate-current-link:
+     * @label: The label on which the signal was emitted
+     *
+     * A <link linkend="keybinding-signals">keybinding signal</link>
+     * which gets emitted when the user activates a link in the label.
+     *
+     * Applications may also emit the signal with g_signal_emit_by_name()
+     * if they need to control activation of URIs programmatically.
+     *
+     * The default bindings for this signal are all forms of the Enter key.
+     *
+     * Since: 2.18
+     */
+    signals[ACTIVATE_CURRENT_LINK] =
+      g_signal_new_class_handler ("activate-current-link",
+                                  G_TYPE_FROM_CLASS (object_class),
+                                  G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
+                                  G_CALLBACK (gtk_label_activate_current_link),
+                                  NULL, NULL,
+                                  _gtk_marshal_VOID__VOID,
+                                  G_TYPE_NONE, 0);
+
+    /**
+     * GtkLabel::activate-link:
+     * @label: The label on which the signal was emitted
+     * @uri: the URI that is activated
+     *
+     * The signal which gets emitted to activate a URI.
+     * Applications may connect to it to override the default behaviour,
+     * which is to call gtk_show_uri().
+     *
+     * Returns: %TRUE if the link has been activated
+     *
+     * Since: 2.18
+     */
+    signals[ACTIVATE_LINK] =
+      g_signal_new ("activate-link",
+                    G_TYPE_FROM_CLASS (object_class),
+                    G_SIGNAL_RUN_LAST,
+                    G_STRUCT_OFFSET (GtkLabelClass, activate_link),
+                    _gtk_boolean_handled_accumulator, NULL,
+                    _gtk_marshal_BOOLEAN__STRING,
+                    G_TYPE_BOOLEAN, 1, G_TYPE_STRING);
+
   g_object_class_install_property (gobject_class,
                                    PROP_LABEL,
                                    g_param_spec_string ("label",
@@ -504,12 +688,13 @@ gtk_label_class_init (GtkLabelClass *class)
 
   /**
    * GtkLabel:width-chars:
-   * 
+   *
    * The desired width of the label, in characters. If this property is set to
-   * -1, the width will be calculated automatically, otherwise the label will
-   * request either 3 characters or the property value, whichever is greater.
-   * If the "width-chars" property is set to a positive value, then the 
-   * #GtkLabel:max-width-chars property is ignored. 
+   * -1, the width will be calculated automatically.
+   *
+   * See the section on <link linkend="label-text-layout">text layout</link>
+   * for details of how #GtkLabel:width-chars and #GtkLabel:max-width-chars
+   * determine the width of ellipsized and wrapped labels.
    *
    * Since: 2.6
    **/
@@ -566,11 +751,12 @@ gtk_label_class_init (GtkLabelClass *class)
    * GtkLabel:max-width-chars:
    * 
    * The desired maximum width of the label, in characters. If this property 
-   * is set to -1, the width will be calculated automatically, otherwise the 
-   * label will request space for no more than the requested number of 
-   * characters. If the #GtkLabel:width-chars property is set to a positive 
-   * value, then the "max-width-chars" property is ignored.
-   * 
+   * is set to -1, the width will be calculated automatically.
+   *
+   * See the section on <link linkend="label-text-layout">text layout</link>
+   * for details of how #GtkLabel:width-chars and #GtkLabel:max-width-chars
+   * determine the width of ellipsized and wrapped labels.
+   *
    * Since: 2.6
    **/
   g_object_class_install_property (gobject_class,
@@ -582,6 +768,23 @@ gtk_label_class_init (GtkLabelClass *class)
                                                      G_MAXINT,
                                                      -1,
                                                      GTK_PARAM_READWRITE));
+
+  /**
+   * GtkLabel:track-visited-links:
+   *
+   * Set this property to %TRUE to make the label track which links
+   * have been clicked. It will then apply the ::visited-link-color
+   * color, instead of ::link-color.
+   *
+   * Since: 2.18
+   */
+  g_object_class_install_property (gobject_class,
+                                   PROP_TRACK_VISITED_LINKS,
+                                   g_param_spec_boolean ("track-visited-links",
+                                                         P_("Track visited links"),
+                                                         P_("Whether visited links should be tracked"),
+                                                         TRUE,
+                                                         GTK_PARAM_READWRITE));
   /*
    * Key bindings
    */
@@ -691,14 +894,20 @@ gtk_label_class_init (GtkLabelClass *class)
   gtk_binding_entry_add_signal (binding_set, GDK_c, GDK_CONTROL_MASK,
                                "copy-clipboard", 0);
 
+  gtk_binding_entry_add_signal (binding_set, GDK_Return, 0,
+                               "activate-current-link", 0);
+  gtk_binding_entry_add_signal (binding_set, GDK_ISO_Enter, 0,
+                               "activate-current-link", 0);
+  gtk_binding_entry_add_signal (binding_set, GDK_KP_Enter, 0,
+                               "activate-current-link", 0);
+
   gtk_settings_install_property (g_param_spec_boolean ("gtk-label-select-on-focus",
                                                       P_("Select on focus"),
                                                       P_("Whether to select the contents of a selectable label when it is focused"),
                                                       TRUE,
                                                       GTK_PARAM_READWRITE));
 
-                               
-  g_type_class_add_private (class, sizeof (GtkLabelPrivate));
+  g_type_class_add_private (class, sizeof (GtkLabelPriv));
 }
 
 static void 
@@ -707,10 +916,8 @@ gtk_label_set_property (GObject      *object,
                        const GValue *value,
                        GParamSpec   *pspec)
 {
-  GtkLabel *label;
+  GtkLabel *label = GTK_LABEL (object);
 
-  label = GTK_LABEL (object);
-  
   switch (prop_id)
     {
     case PROP_LABEL:
@@ -758,6 +965,9 @@ gtk_label_set_property (GObject      *object,
     case PROP_MAX_WIDTH_CHARS:
       gtk_label_set_max_width_chars (label, g_value_get_int (value));
       break;
+    case PROP_TRACK_VISITED_LINKS:
+      gtk_label_set_track_visited_links (label, g_value_get_boolean (value));
+      break;
     default:
       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
       break;
@@ -770,64 +980,63 @@ gtk_label_get_property (GObject     *object,
                        GValue      *value,
                        GParamSpec  *pspec)
 {
-  GtkLabel *label;
-  
-  label = GTK_LABEL (object);
-  
+  GtkLabel *label = GTK_LABEL (object);
+  GtkLabelPriv *priv = label->priv;
+
   switch (prop_id)
     {
     case PROP_LABEL:
-      g_value_set_string (value, label->label);
+      g_value_set_string (value, priv->label);
       break;
     case PROP_ATTRIBUTES:
-      g_value_set_boxed (value, label->attrs);
+      g_value_set_boxed (value, priv->attrs);
       break;
     case PROP_USE_MARKUP:
-      g_value_set_boolean (value, label->use_markup);
+      g_value_set_boolean (value, priv->use_markup);
       break;
     case PROP_USE_UNDERLINE:
-      g_value_set_boolean (value, label->use_underline);
+      g_value_set_boolean (value, priv->use_underline);
       break;
     case PROP_JUSTIFY:
-      g_value_set_enum (value, label->jtype);
+      g_value_set_enum (value, priv->jtype);
       break;
     case PROP_WRAP:
-      g_value_set_boolean (value, label->wrap);
+      g_value_set_boolean (value, priv->wrap);
       break;
     case PROP_WRAP_MODE:
-      g_value_set_enum (value, label->wrap_mode);
+      g_value_set_enum (value, priv->wrap_mode);
       break;
     case PROP_SELECTABLE:
       g_value_set_boolean (value, gtk_label_get_selectable (label));
       break;
     case PROP_MNEMONIC_KEYVAL:
-      g_value_set_uint (value, label->mnemonic_keyval);
+      g_value_set_uint (value, priv->mnemonic_keyval);
       break;
     case PROP_MNEMONIC_WIDGET:
-      g_value_set_object (value, (GObject*) label->mnemonic_widget);
+      g_value_set_object (value, (GObject*) priv->mnemonic_widget);
       break;
     case PROP_CURSOR_POSITION:
-      if (label->select_info)
+      if (priv->select_info && priv->select_info->selectable)
        {
-         gint offset = g_utf8_pointer_to_offset (label->text,
-                                                 label->text + label->select_info->selection_end);
+         gint offset = g_utf8_pointer_to_offset (priv->text,
+                                                 priv->text + priv->select_info->selection_end);
          g_value_set_int (value, offset);
        }
       else
        g_value_set_int (value, 0);
       break;
     case PROP_SELECTION_BOUND:
-      if (label->select_info)
+      if (priv->select_info && priv->select_info->selectable)
        {
-         gint offset = g_utf8_pointer_to_offset (label->text,
-                                                 label->text + label->select_info->selection_anchor);
+         gint offset = g_utf8_pointer_to_offset (priv->text,
+                                                 priv->text + priv->select_info->selection_anchor);
          g_value_set_int (value, offset);
        }
       else
        g_value_set_int (value, 0);
       break;
     case PROP_ELLIPSIZE:
-      g_value_set_enum (value, label->ellipsize);
+      g_value_set_enum (value, priv->ellipsize);
       break;
     case PROP_WIDTH_CHARS:
       g_value_set_int (value, gtk_label_get_width_chars (label));
@@ -841,7 +1050,9 @@ gtk_label_get_property (GObject     *object,
     case PROP_MAX_WIDTH_CHARS:
       g_value_set_int (value, gtk_label_get_max_width_chars (label));
       break;
-
+    case PROP_TRACK_VISITED_LINKS:
+      g_value_set_boolean (value, gtk_label_get_track_visited_links (label));
+      break;
     default:
       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
       break;
@@ -851,33 +1062,40 @@ gtk_label_get_property (GObject     *object,
 static void
 gtk_label_init (GtkLabel *label)
 {
-  GtkLabelPrivate *priv;
+  GtkLabelPriv *priv;
 
-  GTK_WIDGET_SET_FLAGS (label, GTK_NO_WINDOW);
+  label->priv = G_TYPE_INSTANCE_GET_PRIVATE (label,
+                                             GTK_TYPE_LABEL,
+                                             GtkLabelPriv);
+  priv = label->priv;
+
+  gtk_widget_set_has_window (GTK_WIDGET (label), FALSE);
 
-  priv = GTK_LABEL_GET_PRIVATE (label);
   priv->width_chars = -1;
   priv->max_width_chars = -1;
   priv->wrap_width = -1;
-  label->label = NULL;
+  priv->label = NULL;
 
-  label->jtype = GTK_JUSTIFY_LEFT;
-  label->wrap = FALSE;
-  label->wrap_mode = PANGO_WRAP_WORD;
-  label->ellipsize = PANGO_ELLIPSIZE_NONE;
+  priv->jtype = GTK_JUSTIFY_LEFT;
+  priv->wrap = FALSE;
+  priv->wrap_mode = PANGO_WRAP_WORD;
+  priv->ellipsize = PANGO_ELLIPSIZE_NONE;
 
-  label->use_underline = FALSE;
-  label->use_markup = FALSE;
-  label->pattern_set = FALSE;
-  
-  label->mnemonic_keyval = GDK_VoidSymbol;
-  label->layout = NULL;
-  label->text = NULL;
-  label->attrs = NULL;
+  priv->use_underline = FALSE;
+  priv->use_markup = FALSE;
+  priv->pattern_set = FALSE;
+  priv->track_links = TRUE;
+
+  priv->mnemonic_keyval = GDK_VoidSymbol;
+  priv->layout = NULL;
+  priv->text = NULL;
+  priv->attrs = NULL;
+
+  priv->mnemonic_widget = NULL;
+  priv->mnemonic_window = NULL;
+
+  priv->mnemonics_visible = TRUE;
 
-  label->mnemonic_widget = NULL;
-  label->mnemonic_window = NULL;
-  
   gtk_label_set_text (label, "");
 }
 
@@ -960,7 +1178,6 @@ attribute_from_text (GtkBuilder   *builder,
                                              value, &val, error))
        attribute = pango_attr_gravity_hint_new (g_value_get_enum (&val));
       break;
-
       /* PangoAttrString */      
     case PANGO_ATTR_FAMILY:
       attribute = pango_attr_family_new (value);
@@ -1268,10 +1485,12 @@ static gboolean
 gtk_label_mnemonic_activate (GtkWidget *widget,
                             gboolean   group_cycling)
 {
+  GtkLabel *label = GTK_LABEL (widget);
+  GtkLabelPriv *priv = label->priv;
   GtkWidget *parent;
 
-  if (GTK_LABEL (widget)->mnemonic_widget)
-    return gtk_widget_mnemonic_activate (GTK_LABEL (widget)->mnemonic_widget, group_cycling);
+  if (priv->mnemonic_widget)
+    return gtk_widget_mnemonic_activate (priv->mnemonic_widget, group_cycling);
 
   /* Try to find the widget to activate by traversing the
    * widget's ancestry.
@@ -1283,7 +1502,7 @@ gtk_label_mnemonic_activate (GtkWidget *widget,
   
   while (parent)
     {
-      if (GTK_WIDGET_CAN_FOCUS (parent) ||
+      if (gtk_widget_get_can_focus (parent) ||
          (!group_cycling && GTK_WIDGET_GET_CLASS (parent)->activate_signal) ||
           GTK_IS_NOTEBOOK (parent->parent) ||
          GTK_IS_MENU_ITEM (parent))
@@ -1302,6 +1521,7 @@ static void
 gtk_label_setup_mnemonic (GtkLabel *label,
                          guint     last_key)
 {
+  GtkLabelPriv *priv = label->priv;
   GtkWidget *widget = GTK_WIDGET (label);
   GtkWidget *toplevel;
   GtkWidget *mnemonic_menu;
@@ -1310,12 +1530,12 @@ gtk_label_setup_mnemonic (GtkLabel *label,
   
   if (last_key != GDK_VoidSymbol)
     {
-      if (label->mnemonic_window)
+      if (priv->mnemonic_window)
        {
-         gtk_window_remove_mnemonic  (label->mnemonic_window,
+         gtk_window_remove_mnemonic  (priv->mnemonic_window,
                                       last_key,
                                       widget);
-         label->mnemonic_window = NULL;
+         priv->mnemonic_window = NULL;
        }
       if (mnemonic_menu)
        {
@@ -1326,11 +1546,13 @@ gtk_label_setup_mnemonic (GtkLabel *label,
        }
     }
   
-  if (label->mnemonic_keyval == GDK_VoidSymbol)
-    goto done;
+  if (priv->mnemonic_keyval == GDK_VoidSymbol)
+      goto done;
+
+  connect_mnemonics_visible_notify (GTK_LABEL (widget));
 
   toplevel = gtk_widget_get_toplevel (widget);
-  if (GTK_WIDGET_TOPLEVEL (toplevel))
+  if (gtk_widget_is_toplevel (toplevel))
     {
       GtkWidget *menu_shell;
       
@@ -1340,7 +1562,7 @@ gtk_label_setup_mnemonic (GtkLabel *label,
       if (menu_shell)
        {
          _gtk_menu_shell_add_mnemonic (GTK_MENU_SHELL (menu_shell),
-                                       label->mnemonic_keyval,
+                                       priv->mnemonic_keyval,
                                        widget);
          mnemonic_menu = menu_shell;
        }
@@ -1348,9 +1570,9 @@ gtk_label_setup_mnemonic (GtkLabel *label,
       if (!GTK_IS_MENU (menu_shell))
        {
          gtk_window_add_mnemonic (GTK_WINDOW (toplevel),
-                                  label->mnemonic_keyval,
+                                  priv->mnemonic_keyval,
                                   widget);
-         label->mnemonic_window = GTK_WINDOW (toplevel);
+         priv->mnemonic_window = GTK_WINDOW (toplevel);
        }
     }
   
@@ -1363,8 +1585,9 @@ gtk_label_hierarchy_changed (GtkWidget *widget,
                             GtkWidget *old_toplevel)
 {
   GtkLabel *label = GTK_LABEL (widget);
+  GtkLabelPriv *priv = label->priv;
 
-  gtk_label_setup_mnemonic (label, label->mnemonic_keyval);
+  gtk_label_setup_mnemonic (label, priv->mnemonic_keyval);
 }
 
 static void
@@ -1405,6 +1628,58 @@ label_shortcut_setting_changed (GtkSettings *settings)
   g_list_free (list);
 }
 
+static void
+mnemonics_visible_apply (GtkWidget *widget,
+                         gboolean   mnemonics_visible)
+{
+  GtkLabel *label = GTK_LABEL (widget);
+  GtkLabelPriv *priv = label->priv;
+
+  mnemonics_visible = mnemonics_visible != FALSE;
+
+  if (priv->mnemonics_visible != mnemonics_visible)
+    {
+      priv->mnemonics_visible = mnemonics_visible;
+
+      gtk_label_recalculate (label);
+    }
+}
+
+static void
+label_mnemonics_visible_traverse_container (GtkWidget *widget,
+                                            gpointer   data)
+{
+  gboolean mnemonics_visible = GPOINTER_TO_INT (data);
+
+  _gtk_label_mnemonics_visible_apply_recursively (widget, mnemonics_visible);
+}
+
+void
+_gtk_label_mnemonics_visible_apply_recursively (GtkWidget *widget,
+                                                gboolean   mnemonics_visible)
+{
+  if (GTK_IS_LABEL (widget))
+    mnemonics_visible_apply (widget, mnemonics_visible);
+  else if (GTK_IS_CONTAINER (widget))
+    gtk_container_forall (GTK_CONTAINER (widget),
+                          label_mnemonics_visible_traverse_container,
+                          GINT_TO_POINTER (mnemonics_visible));
+}
+
+static void
+label_mnemonics_visible_changed (GtkWindow  *window,
+                                 GParamSpec *pspec,
+                                 gpointer    data)
+{
+  gboolean mnemonics_visible;
+
+  g_object_get (window, "mnemonics-visible", &mnemonics_visible, NULL);
+
+  gtk_container_forall (GTK_CONTAINER (window),
+                        label_mnemonics_visible_traverse_container,
+                        GINT_TO_POINTER (mnemonics_visible));
+}
+
 static void
 gtk_label_screen_changed (GtkWidget *widget,
                          GdkScreen *old_screen)
@@ -1443,15 +1718,16 @@ label_mnemonic_widget_weak_notify (gpointer      data,
                                   GObject      *where_the_object_was)
 {
   GtkLabel *label = data;
+  GtkLabelPriv *priv = label->priv;
 
-  label->mnemonic_widget = NULL;
+  priv->mnemonic_widget = NULL;
   g_object_notify (G_OBJECT (label), "mnemonic-widget");
 }
 
 /**
  * gtk_label_set_mnemonic_widget:
  * @label: a #GtkLabel
- * @widget: the target #GtkWidget 
+ * @widget: (allow-none): the target #GtkWidget
  *
  * If the label has been set so that it has an mnemonic key (using
  * i.e. gtk_label_set_markup_with_mnemonic(),
@@ -1472,24 +1748,29 @@ void
 gtk_label_set_mnemonic_widget (GtkLabel  *label,
                               GtkWidget *widget)
 {
+  GtkLabelPriv *priv;
+
   g_return_if_fail (GTK_IS_LABEL (label));
+
+  priv = label->priv;
+
   if (widget)
     g_return_if_fail (GTK_IS_WIDGET (widget));
 
-  if (label->mnemonic_widget)
+  if (priv->mnemonic_widget)
     {
-      gtk_widget_remove_mnemonic_label (label->mnemonic_widget, GTK_WIDGET (label));
-      g_object_weak_unref (G_OBJECT (label->mnemonic_widget),
+      gtk_widget_remove_mnemonic_label (priv->mnemonic_widget, GTK_WIDGET (label));
+      g_object_weak_unref (G_OBJECT (priv->mnemonic_widget),
                           label_mnemonic_widget_weak_notify,
                           label);
     }
-  label->mnemonic_widget = widget;
-  if (label->mnemonic_widget)
+  priv->mnemonic_widget = widget;
+  if (priv->mnemonic_widget)
     {
-      g_object_weak_ref (G_OBJECT (label->mnemonic_widget),
+      g_object_weak_ref (G_OBJECT (priv->mnemonic_widget),
                         label_mnemonic_widget_weak_notify,
                         label);
-      gtk_widget_add_mnemonic_label (label->mnemonic_widget, GTK_WIDGET (label));
+      gtk_widget_add_mnemonic_label (priv->mnemonic_widget, GTK_WIDGET (label));
     }
   
   g_object_notify (G_OBJECT (label), "mnemonic-widget");
@@ -1510,7 +1791,7 @@ gtk_label_get_mnemonic_widget (GtkLabel *label)
 {
   g_return_val_if_fail (GTK_IS_LABEL (label), NULL);
 
-  return label->mnemonic_widget;
+  return label->priv->mnemonic_widget;
 }
 
 /**
@@ -1528,16 +1809,18 @@ gtk_label_get_mnemonic_keyval (GtkLabel *label)
 {
   g_return_val_if_fail (GTK_IS_LABEL (label), GDK_VoidSymbol);
 
-  return label->mnemonic_keyval;
+  return label->priv->mnemonic_keyval;
 }
 
 static void
 gtk_label_set_text_internal (GtkLabel *label,
                             gchar    *str)
 {
-  g_free (label->text);
-  
-  label->text = str;
+  GtkLabelPriv *priv = label->priv;
+
+  g_free (priv->text);
+
+  priv->text = str;
 
   gtk_label_select_region_index (label, 0, 0);
 }
@@ -1546,9 +1829,11 @@ static void
 gtk_label_set_label_internal (GtkLabel *label,
                              gchar    *str)
 {
-  g_free (label->label);
-  
-  label->label = str;
+  GtkLabelPriv *priv = label->priv;
+
+  g_free (priv->label);
+
+  priv->label = str;
 
   g_object_notify (G_OBJECT (label), "label");
 }
@@ -1557,10 +1842,12 @@ static void
 gtk_label_set_use_markup_internal (GtkLabel *label,
                                   gboolean  val)
 {
+  GtkLabelPriv *priv = label->priv;
+
   val = val != FALSE;
-  if (label->use_markup != val)
+  if (priv->use_markup != val)
     {
-      label->use_markup = val;
+      priv->use_markup = val;
 
       g_object_notify (G_OBJECT (label), "use-markup");
     }
@@ -1570,10 +1857,12 @@ static void
 gtk_label_set_use_underline_internal (GtkLabel *label,
                                      gboolean val)
 {
+  GtkLabelPriv *priv = label->priv;
+
   val = val != FALSE;
-  if (label->use_underline != val)
+  if (priv->use_underline != val)
     {
-      label->use_underline = val;
+      priv->use_underline = val;
 
       g_object_notify (G_OBJECT (label), "use-underline");
     }
@@ -1582,30 +1871,34 @@ gtk_label_set_use_underline_internal (GtkLabel *label,
 static void
 gtk_label_compose_effective_attrs (GtkLabel *label)
 {
+  GtkLabelPriv      *priv = label->priv;
   PangoAttrIterator *iter;
   PangoAttribute    *attr;
   GSList            *iter_attrs, *l;
 
-  if (label->attrs)
+  if (priv->attrs)
     {
-      if (label->effective_attrs)
+      if (priv->effective_attrs)
        {
-         if ((iter = pango_attr_list_get_iterator (label->attrs)))
-           do 
-             {
-               iter_attrs = pango_attr_iterator_get_attrs (iter);
-               for (l = iter_attrs; l; l = l->next)
-                 {
-                   attr = l->data;
-                   pango_attr_list_insert (label->effective_attrs, attr);
-                 }
-               g_slist_free (iter_attrs);
-             }
-           while (pango_attr_iterator_next (iter));
+         if ((iter = pango_attr_list_get_iterator (priv->attrs)))
+           {
+             do
+               {
+                 iter_attrs = pango_attr_iterator_get_attrs (iter);
+                 for (l = iter_attrs; l; l = l->next)
+                   {
+                     attr = l->data;
+                     pango_attr_list_insert (priv->effective_attrs, attr);
+                   }
+                 g_slist_free (iter_attrs);
+               }
+             while (pango_attr_iterator_next (iter));
+             pango_attr_iterator_destroy (iter);
+           }
        }
       else
-       label->effective_attrs = 
-         pango_attr_list_ref (label->attrs);
+       priv->effective_attrs =
+         pango_attr_list_ref (priv->attrs);
     }
 }
 
@@ -1613,12 +1906,14 @@ static void
 gtk_label_set_attributes_internal (GtkLabel      *label,
                                   PangoAttrList *attrs)
 {
+  GtkLabelPriv *priv = label->priv;
+
   if (attrs)
     pango_attr_list_ref (attrs);
-  
-  if (label->attrs)
-    pango_attr_list_unref (label->attrs);
-  label->attrs = attrs;
+
+  if (priv->attrs)
+    pango_attr_list_unref (priv->attrs);
+  priv->attrs = attrs;
 
   g_object_notify (G_OBJECT (label), "attributes");
 }
@@ -1630,41 +1925,37 @@ gtk_label_set_attributes_internal (GtkLabel      *label,
 static void
 gtk_label_recalculate (GtkLabel *label)
 {
-  guint keyval = label->mnemonic_keyval;
+  GtkLabelPriv *priv = label->priv;
+  guint keyval = priv->mnemonic_keyval;
 
-  if (label->use_markup)
-    {
-      set_markup (label, label->label, label->use_underline);
-      gtk_label_compose_effective_attrs (label);
-    }
+  if (priv->use_markup)
+    gtk_label_set_markup_internal (label, priv->label, priv->use_underline);
   else
     {
-      if (label->use_underline)
-       {
-         gtk_label_set_uline_text_internal (label, label->label);
-         gtk_label_compose_effective_attrs (label);
-       }
+      if (priv->use_underline)
+       gtk_label_set_uline_text_internal (label, priv->label);
       else
-       {
-         gtk_label_set_text_internal (label, g_strdup (label->label));
-         if (label->attrs)
-           pango_attr_list_ref (label->attrs);
-         if (label->effective_attrs)
-           pango_attr_list_unref (label->effective_attrs);
-         label->effective_attrs = label->attrs;
-       }
+        {
+          if (priv->effective_attrs)
+            pango_attr_list_unref (priv->effective_attrs);
+          priv->effective_attrs = NULL;
+          gtk_label_set_text_internal (label, g_strdup (priv->label));
+        }
     }
 
-  if (!label->use_underline)
-    label->mnemonic_keyval = GDK_VoidSymbol;
+  gtk_label_compose_effective_attrs (label);
 
-  if (keyval != label->mnemonic_keyval)
+  if (!priv->use_underline)
+    priv->mnemonic_keyval = GDK_VoidSymbol;
+
+  if (keyval != priv->mnemonic_keyval)
     {
       gtk_label_setup_mnemonic (label, keyval);
       g_object_notify (G_OBJECT (label), "mnemonic-keyval");
     }
 
-  gtk_label_clear_layout (label);  
+  gtk_label_clear_layout (label);
+  gtk_label_clear_select_info (label);
   gtk_widget_queue_resize (GTK_WIDGET (label));
 }
 
@@ -1720,7 +2011,7 @@ gtk_label_set_attributes (GtkLabel         *label,
 
   gtk_label_recalculate (label);
 
-  gtk_label_clear_layout (label);  
+  gtk_label_clear_layout (label);
   gtk_widget_queue_resize (GTK_WIDGET (label));
 }
 
@@ -1742,7 +2033,7 @@ gtk_label_get_attributes (GtkLabel *label)
 {
   g_return_val_if_fail (GTK_IS_LABEL (label), NULL);
 
-  return label->attrs;
+  return label->priv->attrs;
 }
 
 /**
@@ -1760,11 +2051,10 @@ gtk_label_set_label (GtkLabel    *label,
                     const gchar *str)
 {
   g_return_if_fail (GTK_IS_LABEL (label));
-  g_return_if_fail (str != NULL);
 
   g_object_freeze_notify (G_OBJECT (label));
 
-  gtk_label_set_label_internal (label, g_strdup (str));
+  gtk_label_set_label_internal (label, g_strdup (str ? str : ""));
   gtk_label_recalculate (label);
 
   g_object_thaw_notify (G_OBJECT (label));
@@ -1786,20 +2076,354 @@ gtk_label_get_label (GtkLabel *label)
 {
   g_return_val_if_fail (GTK_IS_LABEL (label), NULL);
 
-  return label->label;
+  return label->priv->label;
+}
+
+typedef struct
+{
+  GtkLabel *label;
+  GList *links;
+  GString *new_str;
+  GdkColor *link_color;
+  GdkColor *visited_link_color;
+} UriParserData;
+
+static void
+start_element_handler (GMarkupParseContext  *context,
+                       const gchar          *element_name,
+                       const gchar         **attribute_names,
+                       const gchar         **attribute_values,
+                       gpointer              user_data,
+                       GError              **error)
+{
+  GtkLabelPriv *priv;
+  UriParserData *pdata = user_data;
+
+  if (strcmp (element_name, "a") == 0)
+    {
+      GtkLabelLink *link;
+      const gchar *uri = NULL;
+      const gchar *title = NULL;
+      gboolean visited = FALSE;
+      gint line_number;
+      gint char_number;
+      gint i;
+      GdkColor *color = NULL;
+
+      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") == 0)
+            uri = attribute_values[i];
+          else if (strcmp (attr, "title") == 0)
+            title = 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",
+                            attr, line_number, char_number);
+              return;
+            }
+        }
+
+      if (uri == 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;
+        }
+
+      visited = FALSE;
+      priv = pdata->label->priv;
+      if (priv->track_links && priv->select_info)
+        {
+          GList *l;
+          for (l = priv->select_info->links; l; l = l->next)
+            {
+              link = l->data;
+              if (strcmp (uri, link->uri) == 0)
+                {
+                  visited = link->visited;
+                  break;
+                }
+            }
+        }
+
+      if (visited)
+        color = pdata->visited_link_color;
+      else
+        color = pdata->link_color;
+
+      g_string_append_printf (pdata->new_str,
+                              "<span color=\"#%04x%04x%04x\" underline=\"single\">",
+                              color->red,
+                              color->green,
+                              color->blue);
+
+      link = g_new0 (GtkLabelLink, 1);
+      link->uri = g_strdup (uri);
+      link->title = g_strdup (title);
+      link->visited = visited;
+      pdata->links = g_list_append (pdata->links, link);
+    }
+  else
+    {
+      gint i;
+
+      g_string_append_c (pdata->new_str, '<');
+      g_string_append (pdata->new_str, element_name);
+
+      for (i = 0; attribute_names[i] != NULL; i++)
+        {
+          const gchar *attr  = attribute_names[i];
+          const gchar *value = attribute_values[i];
+          gchar *newvalue;
+
+          newvalue = g_markup_escape_text (value, -1);
+
+          g_string_append_c (pdata->new_str, ' ');
+          g_string_append (pdata->new_str, attr);
+          g_string_append (pdata->new_str, "=\"");
+          g_string_append (pdata->new_str, newvalue);
+          g_string_append_c (pdata->new_str, '\"');
+
+          g_free (newvalue);
+        }
+      g_string_append_c (pdata->new_str, '>');
+    }
+}
+
+static void
+end_element_handler (GMarkupParseContext  *context,
+                     const gchar          *element_name,
+                     gpointer              user_data,
+                     GError              **error)
+{
+  UriParserData *pdata = user_data;
+
+  if (!strcmp (element_name, "a"))
+    g_string_append (pdata->new_str, "</span>");
+  else
+    {
+      g_string_append (pdata->new_str, "</");
+      g_string_append (pdata->new_str, element_name);
+      g_string_append_c (pdata->new_str, '>');
+    }
+}
+
+static void
+text_handler (GMarkupParseContext  *context,
+              const gchar          *text,
+              gsize                 text_len,
+              gpointer              user_data,
+              GError              **error)
+{
+  UriParserData *pdata = user_data;
+  gchar *newtext;
+
+  newtext = g_markup_escape_text (text, text_len);
+  g_string_append (pdata->new_str, newtext);
+  g_free (newtext);
+}
+
+static const GMarkupParser markup_parser =
+{
+  start_element_handler,
+  end_element_handler,
+  text_handler,
+  NULL,
+  NULL
+};
+
+static gboolean
+xml_isspace (gchar c)
+{
+  return (c == ' ' || c == '\t' || c == '\n' || c == '\r');
+}
+
+static void
+link_free (GtkLabelLink *link)
+{
+  g_free (link->uri);
+  g_free (link->title);
+  g_free (link);
+}
+
+static void
+gtk_label_get_link_colors (GtkWidget  *widget,
+                           GdkColor  **link_color,
+                           GdkColor  **visited_link_color)
+{
+  gtk_widget_ensure_style (widget);
+  gtk_widget_style_get (widget,
+                        "link-color", link_color,
+                        "visited-link-color", visited_link_color,
+                        NULL);
+  if (!*link_color)
+    *link_color = gdk_color_copy (&default_link_color);
+  if (!*visited_link_color)
+    *visited_link_color = gdk_color_copy (&default_visited_link_color);
+}
+
+static gboolean
+parse_uri_markup (GtkLabel     *label,
+                  const gchar  *str,
+                  gchar       **new_str,
+                  GList       **links,
+                  GError      **error)
+{
+  GMarkupParseContext *context = NULL;
+  const gchar *p, *end;
+  gboolean needs_root = TRUE;
+  gsize length;
+  UriParserData pdata;
+
+  length = strlen (str);
+  p = str;
+  end = str + length;
+
+  pdata.label = label;
+  pdata.links = NULL;
+  pdata.new_str = g_string_sized_new (length);
+
+  gtk_label_get_link_colors (GTK_WIDGET (label), &pdata.link_color, &pdata.visited_link_color);
+
+  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, &pdata, NULL);
+
+  if (needs_root)
+    {
+      if (!g_markup_parse_context_parse (context, "<markup>", -1, error))
+        goto failed;
+    }
+
+  if (!g_markup_parse_context_parse (context, str, length, 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;
+
+  g_markup_parse_context_free (context);
+
+  *new_str = g_string_free (pdata.new_str, FALSE);
+  *links = pdata.links;
+
+  gdk_color_free (pdata.link_color);
+  gdk_color_free (pdata.visited_link_color);
+
+  return TRUE;
+
+failed:
+  g_markup_parse_context_free (context);
+  g_string_free (pdata.new_str, TRUE);
+  g_list_foreach (pdata.links, (GFunc)link_free, NULL);
+  g_list_free (pdata.links);
+  gdk_color_free (pdata.link_color);
+  gdk_color_free (pdata.visited_link_color);
+
+  return FALSE;
+}
+
+static void
+gtk_label_ensure_has_tooltip (GtkLabel *label)
+{
+  GtkLabelPriv *priv = label->priv;
+  GList *l;
+  gboolean has_tooltip = FALSE;
+
+  for (l = priv->select_info->links; l; l = l->next)
+    {
+      GtkLabelLink *link = l->data;
+      if (link->title)
+        {
+          has_tooltip = TRUE;
+          break;
+        }
+    }
+
+  gtk_widget_set_has_tooltip (GTK_WIDGET (label), has_tooltip);
 }
 
 static void
-set_markup (GtkLabel    *label,
-            const gchar *str,
-            gboolean     with_uline)
+gtk_label_set_markup_internal (GtkLabel    *label,
+                               const gchar *str,
+                               gboolean     with_uline)
 {
+  GtkLabelPriv *priv = label->priv;
   gchar *text = NULL;
   GError *error = NULL;
   PangoAttrList *attrs = NULL;
   gunichar accel_char = 0;
+  gchar *new_str;
+  GList *links = NULL;
 
-  if (!pango_parse_markup (str,
+  if (!parse_uri_markup (label, str, &new_str, &links, &error))
+    {
+      g_warning ("Failed to set text from markup due to error parsing markup: %s",
+                 error->message);
+      g_error_free (error);
+      return;
+    }
+
+  gtk_label_clear_links (label);
+  if (links)
+    {
+      gtk_label_ensure_select_info (label);
+      priv->select_info->links = links;
+      gtk_label_ensure_has_tooltip (label);
+    }
+
+  if (with_uline)
+    {
+      gboolean enable_mnemonics;
+      gboolean auto_mnemonics;
+
+      g_object_get (gtk_widget_get_settings (GTK_WIDGET (label)),
+                    "gtk-enable-mnemonics", &enable_mnemonics,
+                    "gtk-auto-mnemonics", &auto_mnemonics,
+                    NULL);
+
+      if (!(enable_mnemonics && priv->mnemonics_visible &&
+            (!auto_mnemonics ||
+             (gtk_widget_is_sensitive (GTK_WIDGET (label)) &&
+              (!priv->mnemonic_widget ||
+               gtk_widget_is_sensitive (priv->mnemonic_widget))))))
+        {
+          gchar *tmp;
+          gchar *pattern;
+          guint key;
+
+          if (separate_uline_pattern (new_str, &key, &tmp, &pattern))
+            {
+              g_free (new_str);
+              new_str = tmp;
+              g_free (pattern);
+            }
+        }
+    }
+
+  if (!pango_parse_markup (new_str,
                            -1,
                            with_uline ? '_' : 0,
                            &attrs,
@@ -1809,24 +2433,27 @@ set_markup (GtkLabel    *label,
     {
       g_warning ("Failed to set text from markup due to error parsing markup: %s",
                  error->message);
+      g_free (new_str);
       g_error_free (error);
       return;
     }
 
+  g_free (new_str);
+
   if (text)
     gtk_label_set_text_internal (label, text);
 
   if (attrs)
     {
-      if (label->effective_attrs)
-       pango_attr_list_unref (label->effective_attrs);
-      label->effective_attrs = attrs;
+      if (priv->effective_attrs)
+       pango_attr_list_unref (priv->effective_attrs);
+      priv->effective_attrs = attrs;
     }
 
   if (accel_char != 0)
-    label->mnemonic_keyval = gdk_keyval_to_lower (gdk_unicode_to_keyval (accel_char));
+    priv->mnemonic_keyval = gdk_keyval_to_lower (gdk_unicode_to_keyval (accel_char));
   else
-    label->mnemonic_keyval = GDK_VoidSymbol;
+    priv->mnemonic_keyval = GDK_VoidSymbol;
 }
 
 /**
@@ -1850,9 +2477,13 @@ set_markup (GtkLabel    *label,
 void
 gtk_label_set_markup (GtkLabel    *label,
                       const gchar *str)
-{  
+{
+  GtkLabelPriv *priv;
+
   g_return_if_fail (GTK_IS_LABEL (label));
 
+  priv = label->priv;
+
   g_object_freeze_notify (G_OBJECT (label));
 
   gtk_label_set_label_internal (label, g_strdup (str ? str : ""));
@@ -1881,8 +2512,12 @@ void
 gtk_label_set_markup_with_mnemonic (GtkLabel    *label,
                                    const gchar *str)
 {
+  GtkLabelPriv *priv;
+
   g_return_if_fail (GTK_IS_LABEL (label));
 
+  priv = label->priv;
+
   g_object_freeze_notify (G_OBJECT (label));
 
   gtk_label_set_label_internal (label, g_strdup (str ? str : ""));
@@ -1910,15 +2545,16 @@ gtk_label_get_text (GtkLabel *label)
 {
   g_return_val_if_fail (GTK_IS_LABEL (label), NULL);
 
-  return label->text;
+  return label->priv->text;
 }
 
 static PangoAttrList *
 gtk_label_pattern_to_attrs (GtkLabel      *label,
                            const gchar   *pattern)
 {
+  GtkLabelPriv *priv = label->priv;
   const char *start;
-  const char *p = label->text;
+  const char *p = priv->text;
   const char *q = pattern;
   PangoAttrList *attrs;
 
@@ -1941,8 +2577,8 @@ gtk_label_pattern_to_attrs (GtkLabel      *label,
       if (p > start)
        {
          PangoAttribute *attr = pango_attr_underline_new (PANGO_UNDERLINE_LOW);
-         attr->start_index = start - label->text;
-         attr->end_index = p - label->text;
+         attr->start_index = start - priv->text;
+         attr->end_index = p - priv->text;
          
          pango_attr_list_insert (attrs, attr);
        }
@@ -1955,47 +2591,62 @@ gtk_label_pattern_to_attrs (GtkLabel      *label,
 
 static void
 gtk_label_set_pattern_internal (GtkLabel    *label,
-                               const gchar *pattern)
+                               const gchar *pattern,
+                                gboolean     is_mnemonic)
 {
+  GtkLabelPriv *priv = label->priv;
   PangoAttrList *attrs;
   gboolean enable_mnemonics;
+  gboolean auto_mnemonics;
 
-  g_return_if_fail (GTK_IS_LABEL (label));
-
-  if (label->pattern_set)
+  if (priv->pattern_set)
     return;
 
-  g_object_get (gtk_widget_get_settings (GTK_WIDGET (label)),
-               "gtk-enable-mnemonics", &enable_mnemonics,
-               NULL);
-
-  if (enable_mnemonics && pattern)
-    attrs = gtk_label_pattern_to_attrs (label, pattern);
+  if (is_mnemonic)
+    {
+      g_object_get (gtk_widget_get_settings (GTK_WIDGET (label)),
+                   "gtk-enable-mnemonics", &enable_mnemonics,
+                   "gtk-auto-mnemonics", &auto_mnemonics,
+                   NULL);
+
+      if (enable_mnemonics && priv->mnemonics_visible && pattern &&
+          (!auto_mnemonics ||
+           (gtk_widget_is_sensitive (GTK_WIDGET (label)) &&
+            (!priv->mnemonic_widget ||
+             gtk_widget_is_sensitive (priv->mnemonic_widget)))))
+        attrs = gtk_label_pattern_to_attrs (label, pattern);
+      else
+        attrs = NULL;
+    }
   else
-    attrs = NULL;
+    attrs = gtk_label_pattern_to_attrs (label, pattern);
 
-  if (label->effective_attrs)
-    pango_attr_list_unref (label->effective_attrs);
-  label->effective_attrs = attrs;
+  if (priv->effective_attrs)
+    pango_attr_list_unref (priv->effective_attrs);
+  priv->effective_attrs = attrs;
 }
 
 void
 gtk_label_set_pattern (GtkLabel           *label,
                       const gchar *pattern)
 {
+  GtkLabelPriv *priv = label->priv;
+
   g_return_if_fail (GTK_IS_LABEL (label));
-  
-  label->pattern_set = FALSE;
+
+  priv = label->priv;
+
+  priv->pattern_set = FALSE;
 
   if (pattern)
     {
-      gtk_label_set_pattern_internal (label, pattern);
-      label->pattern_set = TRUE;
+      gtk_label_set_pattern_internal (label, pattern, FALSE);
+      priv->pattern_set = TRUE;
     }
   else
     gtk_label_recalculate (label);
 
-  gtk_label_clear_layout (label);  
+  gtk_label_clear_layout (label);
   gtk_widget_queue_resize (GTK_WIDGET (label));
 }
 
@@ -2016,12 +2667,16 @@ void
 gtk_label_set_justify (GtkLabel        *label,
                       GtkJustification jtype)
 {
+  GtkLabelPriv *priv;
+
   g_return_if_fail (GTK_IS_LABEL (label));
   g_return_if_fail (jtype >= GTK_JUSTIFY_LEFT && jtype <= GTK_JUSTIFY_FILL);
-  
-  if ((GtkJustification) label->jtype != jtype)
+
+  priv = label->priv;
+
+  if ((GtkJustification) priv->jtype != jtype)
     {
-      label->jtype = jtype;
+      priv->jtype = jtype;
 
       /* No real need to be this drastic, but easier than duplicating the code */
       gtk_label_clear_layout (label);
@@ -2044,7 +2699,7 @@ gtk_label_get_justify (GtkLabel *label)
 {
   g_return_val_if_fail (GTK_IS_LABEL (label), 0);
 
-  return label->jtype;
+  return label->priv->jtype;
 }
 
 /**
@@ -2061,12 +2716,16 @@ void
 gtk_label_set_ellipsize (GtkLabel          *label,
                         PangoEllipsizeMode mode)
 {
+  GtkLabelPriv *priv;
+
   g_return_if_fail (GTK_IS_LABEL (label));
   g_return_if_fail (mode >= PANGO_ELLIPSIZE_NONE && mode <= PANGO_ELLIPSIZE_END);
-  
-  if ((PangoEllipsizeMode) label->ellipsize != mode)
+
+  priv = label->priv;
+
+  if ((PangoEllipsizeMode) priv->ellipsize != mode)
     {
-      label->ellipsize = mode;
+      priv->ellipsize = mode;
 
       /* No real need to be this drastic, but easier than duplicating the code */
       gtk_label_clear_layout (label);
@@ -2091,7 +2750,7 @@ gtk_label_get_ellipsize (GtkLabel *label)
 {
   g_return_val_if_fail (GTK_IS_LABEL (label), PANGO_ELLIPSIZE_NONE);
 
-  return label->ellipsize;
+  return label->priv->ellipsize;
 }
 
 /**
@@ -2107,11 +2766,11 @@ void
 gtk_label_set_width_chars (GtkLabel *label,
                           gint      n_chars)
 {
-  GtkLabelPrivate *priv;
+  GtkLabelPriv *priv;
 
   g_return_if_fail (GTK_IS_LABEL (label));
 
-  priv = GTK_LABEL_GET_PRIVATE (label);
+  priv = label->priv;
 
   if (priv->width_chars != n_chars)
     {
@@ -2138,7 +2797,7 @@ gtk_label_get_width_chars (GtkLabel *label)
 {
   g_return_val_if_fail (GTK_IS_LABEL (label), -1);
 
-  return GTK_LABEL_GET_PRIVATE (label)->width_chars;
+  return label->priv->width_chars;
 }
 
 /**
@@ -2154,11 +2813,11 @@ void
 gtk_label_set_max_width_chars (GtkLabel *label,
                               gint      n_chars)
 {
-  GtkLabelPrivate *priv;
+  GtkLabelPriv *priv;
 
   g_return_if_fail (GTK_IS_LABEL (label));
 
-  priv = GTK_LABEL_GET_PRIVATE (label);
+  priv = label->priv;
 
   if (priv->max_width_chars != n_chars)
     {
@@ -2186,7 +2845,7 @@ gtk_label_get_max_width_chars (GtkLabel *label)
 {
   g_return_val_if_fail (GTK_IS_LABEL (label), -1);
 
-  return GTK_LABEL_GET_PRIVATE (label)->max_width_chars;
+  return label->priv->max_width_chars;
 }
 
 /**
@@ -2208,13 +2867,17 @@ void
 gtk_label_set_line_wrap (GtkLabel *label,
                         gboolean  wrap)
 {
+  GtkLabelPriv *priv;
+
   g_return_if_fail (GTK_IS_LABEL (label));
-  
+
+  priv = label->priv;
+
   wrap = wrap != FALSE;
-  
-  if (label->wrap != wrap)
+
+  if (priv->wrap != wrap)
     {
-      label->wrap = wrap;
+      priv->wrap = wrap;
 
       gtk_label_clear_layout (label);
       gtk_widget_queue_resize (GTK_WIDGET (label));
@@ -2236,7 +2899,7 @@ gtk_label_get_line_wrap (GtkLabel *label)
 {
   g_return_val_if_fail (GTK_IS_LABEL (label), FALSE);
 
-  return label->wrap;
+  return label->priv->wrap;
 }
 
 /**
@@ -2254,11 +2917,15 @@ void
 gtk_label_set_line_wrap_mode (GtkLabel *label,
                              PangoWrapMode wrap_mode)
 {
+  GtkLabelPriv *priv;
+
   g_return_if_fail (GTK_IS_LABEL (label));
-  
-  if (label->wrap_mode != wrap_mode)
+
+  priv = label->priv;
+
+  if (priv->wrap_mode != wrap_mode)
     {
-      label->wrap_mode = wrap_mode;
+      priv->wrap_mode = wrap_mode;
       g_object_notify (G_OBJECT (label), "wrap-mode");
       
       gtk_widget_queue_resize (GTK_WIDGET (label));
@@ -2280,18 +2947,7 @@ gtk_label_get_line_wrap_mode (GtkLabel *label)
 {
   g_return_val_if_fail (GTK_IS_LABEL (label), FALSE);
 
-  return label->wrap_mode;
-}
-
-
-void
-gtk_label_get (GtkLabel *label,
-              gchar   **str)
-{
-  g_return_if_fail (GTK_IS_LABEL (label));
-  g_return_if_fail (str != NULL);
-  
-  *str = label->text;
+  return label->priv->wrap_mode;
 }
 
 static void
@@ -2308,20 +2964,22 @@ static void
 gtk_label_finalize (GObject *object)
 {
   GtkLabel *label = GTK_LABEL (object);
+  GtkLabelPriv *priv = label->priv;
 
-  g_free (label->label);
-  g_free (label->text);
+  g_free (priv->label);
+  g_free (priv->text);
 
-  if (label->layout)
-    g_object_unref (label->layout);
+  if (priv->layout)
+    g_object_unref (priv->layout);
 
-  if (label->attrs)
-    pango_attr_list_unref (label->attrs);
+  if (priv->attrs)
+    pango_attr_list_unref (priv->attrs);
 
-  if (label->effective_attrs)
-    pango_attr_list_unref (label->effective_attrs);
+  if (priv->effective_attrs)
+    pango_attr_list_unref (priv->effective_attrs);
 
-  g_free (label->select_info);
+  gtk_label_clear_links (label);
+  g_free (priv->select_info);
 
   G_OBJECT_CLASS (gtk_label_parent_class)->finalize (object);
 }
@@ -2329,24 +2987,36 @@ gtk_label_finalize (GObject *object)
 static void
 gtk_label_clear_layout (GtkLabel *label)
 {
-  if (label->layout)
+  GtkLabelPriv *priv = label->priv;
+
+  if (priv->layout)
     {
-      g_object_unref (label->layout);
-      label->layout = NULL;
+      g_object_unref (priv->layout);
+      priv->layout = NULL;
+
+      //gtk_label_clear_links (label);
     }
 }
 
-static gint
-get_label_char_width (GtkLabel *label)
+
+static void
+get_label_width (GtkLabel *label,
+                gint     *minimum,
+                gint     *natural)
 {
-  GtkLabelPrivate *priv;
-  PangoContext *context;
+  GtkWidgetAuxInfo *aux_info;
+  GtkLabelPriv     *priv;
+  PangoLayout      *layout;
+  PangoContext     *context;
   PangoFontMetrics *metrics;
-  gint char_width, digit_width, char_pixels, w;
-  
-  priv = GTK_LABEL_GET_PRIVATE (label);
-  
-  context = pango_layout_get_context (label->layout);
+  PangoRectangle    rect;
+  gint              char_width, digit_width, char_pixels, text_width, ellipsize_chars, guess_width;
+
+  priv     = label->priv;
+  aux_info = _gtk_widget_get_aux_info (GTK_WIDGET (label), FALSE);
+
+  layout  = pango_layout_copy (priv->layout);
+  context = pango_layout_get_context (layout);
   metrics = pango_context_get_metrics (context, GTK_WIDGET (label)->style->font_desc, 
                                       pango_context_get_language (context));
   
@@ -2354,32 +3024,94 @@ get_label_char_width (GtkLabel *label)
   digit_width = pango_font_metrics_get_approximate_digit_width (metrics);
   char_pixels = MAX (char_width, digit_width);
   pango_font_metrics_unref (metrics);
-  
-  if (priv->width_chars < 0)
-    {
-      PangoRectangle rect;
-      
-      pango_layout_set_width (label->layout, -1);
-      pango_layout_get_extents (label->layout, NULL, &rect);
       
-      w = char_pixels * MAX (priv->max_width_chars, 3);
-      w = MIN (rect.width, w);
+  /* Fetch the length of the complete unwrapped text */
+  pango_layout_set_width (layout, -1);
+  pango_layout_get_extents (layout, NULL, &rect);
+  text_width = rect.width;
+
+  /* Fetch the width that was guessed by gtk_label_ensure_layout() */
+  pango_layout_get_extents (priv->layout, NULL, &rect);
+  guess_width = rect.width;
+
+  /* enforce minimum width for ellipsized labels at ~3 chars */
+  if (priv->ellipsize)
+    ellipsize_chars = 3;
+  else
+    ellipsize_chars = 0;
+
+  /* "width-chars" Hard-coded minimum width: 
+   *    - minimum size should be MAX (width-chars, strlen ("..."));
+   *    - natural size should be MAX (width-chars, strlen (priv->text));
+   *
+   * "max-width-chars" User specified maximum size requisition
+   *    - minimum size should be MAX (width-chars, 0)
+   *    - natural size should be MIN (max-width-chars, strlen (priv->text))
+   *
+   *    For ellipsizing labels; if max-width-chars is specified: either it is used as 
+   *    a minimum size or the label text as a minimum size (natural size still overflows).
+   *
+   *    For wrapping labels; A reasonable minimum size is useful to naturally layout
+   *    interfaces automatically. In this case if no "width-chars" is specified, the minimum
+   *    width will default to the wrap guess that gtk_label_ensure_layout() does.
+   *
+   *    In *any* case the minimum width is completely overridden if an explicit width 
+   *    request was provided.
+   */
+
+  if (priv->ellipsize || priv->wrap)
+    {
+      *minimum = char_pixels * MAX (priv->width_chars, ellipsize_chars);
+
+      /* Default to the minimum width regularly guessed by GTK+ if no minimum
+       * width was specified, only allow unwrapping of these labels.
+       *
+       * Note that when specifying a small width_chars for a long text;
+       * an accordingly large size will be required for the label height.
+       */
+      if (priv->wrap && priv->width_chars <= 0)
+       *minimum = guess_width;
+
+      if (priv->max_width_chars < 0)
+       {
+         *natural = MAX (*minimum, text_width);
+       }
+      else
+       {
+         gint max_char_width = char_pixels * priv->max_width_chars;
+         gint max_width      = MIN (text_width, max_char_width);
+
+         /* With max-char-width specified, we let the minimum widths of 
+          * ellipsized text crawl up to the max-char-width
+          * (note that we dont want to limit the minimum width for wrapping text).
+          */
+         if (priv->ellipsize)
+           *minimum = MIN (text_width, max_width);
+
+         *natural = MAX (*minimum, max_width);
+       }
     }
   else
     {
-      /* enforce minimum width for ellipsized labels at ~3 chars */
-      w = char_pixels * MAX (priv->width_chars, 3);
+      *minimum = text_width;
+      *natural = *minimum;
     }
-  
-  return w;
+
+  /* if a width-request is set, use that as the requested label width */
+  if ((priv->wrap || priv->ellipsize || priv->width_chars > 0 || priv->max_width_chars > 0) &&
+      aux_info && aux_info->width > 0)
+    {
+      *minimum = aux_info->width * PANGO_SCALE;
+      *natural = MAX (*natural, *minimum);
+    }
+
+  g_object_unref (layout);
 }
 
 static void
 gtk_label_invalidate_wrap_width (GtkLabel *label)
 {
-  GtkLabelPrivate *priv;
-
-  priv = GTK_LABEL_GET_PRIVATE (label);
+  GtkLabelPriv *priv = label->priv;
 
   priv->wrap_width = -1;
 }
@@ -2387,20 +3119,42 @@ gtk_label_invalidate_wrap_width (GtkLabel *label)
 static gint
 get_label_wrap_width (GtkLabel *label)
 {
-  GtkLabelPrivate *priv;
+  GtkLabelPriv *priv = label->priv;
 
-  priv = GTK_LABEL_GET_PRIVATE (label);
-  
   if (priv->wrap_width < 0)
     {
-      if (priv->width_chars > 0 || priv->max_width_chars > 0)
-       priv->wrap_width = get_label_char_width (label);
+      if (priv->width_chars > 0)
+       {
+         PangoLayout      *layout;
+         PangoContext     *context;
+         PangoFontMetrics *metrics;
+         PangoRectangle    rect;
+         gint              char_width, digit_width, char_pixels, text_width;
+
+         layout  = pango_layout_copy (priv->layout);
+         context = pango_layout_get_context (layout);
+         metrics = pango_context_get_metrics (context, GTK_WIDGET (label)->style->font_desc, 
+                                              pango_context_get_language (context));
+         
+         char_width = pango_font_metrics_get_approximate_char_width (metrics);
+         digit_width = pango_font_metrics_get_approximate_digit_width (metrics);
+         char_pixels = MAX (char_width, digit_width);
+         pango_font_metrics_unref (metrics);
+         
+         pango_layout_set_width (layout, -1);
+         pango_layout_get_extents (layout, NULL, &rect);
+         g_object_unref (layout);
+
+         text_width = rect.width;
+
+         priv->wrap_width = PANGO_PIXELS (MAX (text_width, char_pixels * priv->width_chars));
+       }
       else
        {
          PangoLayout *layout;
   
          layout = gtk_widget_create_pango_layout (GTK_WIDGET (label), 
-                                                  "This long string gives a good enough length for any line to have.");
+                                                  "This string is just about long enough.");
          pango_layout_get_size (layout, &priv->wrap_width, NULL);
          g_object_unref (layout);
        }
@@ -2410,49 +3164,52 @@ get_label_wrap_width (GtkLabel *label)
 }
 
 static void
-gtk_label_ensure_layout (GtkLabel *label)
+gtk_label_ensure_layout (GtkLabel *label, gboolean guess_wrap_width)
 {
+  GtkLabelPriv *priv = label->priv;
   GtkWidget *widget;
   PangoRectangle logical_rect;
   gboolean rtl;
 
   widget = GTK_WIDGET (label);
 
-  rtl = gtk_widget_get_direction(widget) == GTK_TEXT_DIR_RTL;
+  rtl = gtk_widget_get_direction (widget) == GTK_TEXT_DIR_RTL;
 
-  if (!label->layout)
+  if (!priv->layout)
     {
       PangoAlignment align = PANGO_ALIGN_LEFT; /* Quiet gcc */
       gdouble angle = gtk_label_get_angle (label);
 
-      if (angle != 0.0 && !label->wrap && !label->ellipsize && !label->select_info)
+      if (angle != 0.0 && !priv->select_info)
        {
+          PangoMatrix matrix = PANGO_MATRIX_INIT;
+
          /* We rotate the standard singleton PangoContext for the widget,
           * depending on the fact that it's meant pretty much exclusively
           * for our use.
           */
-         PangoMatrix matrix = PANGO_MATRIX_INIT;
-         
          pango_matrix_rotate (&matrix, angle);
 
          pango_context_set_matrix (gtk_widget_get_pango_context (widget), &matrix);
-         
-         label->have_transform = TRUE;
+
+         priv->have_transform = TRUE;
        }
-      else 
+      else
        {
-         if (label->have_transform)
+         if (priv->have_transform)
            pango_context_set_matrix (gtk_widget_get_pango_context (widget), NULL);
 
-         label->have_transform = FALSE;
+         priv->have_transform = FALSE;
        }
 
-      label->layout = gtk_widget_create_pango_layout (widget, label->text);
+      priv->layout = gtk_widget_create_pango_layout (widget, priv->text);
 
-      if (label->effective_attrs)
-       pango_layout_set_attributes (label->layout, label->effective_attrs);
-      
-      switch (label->jtype)
+      if (priv->effective_attrs)
+       pango_layout_set_attributes (priv->layout, priv->effective_attrs);
+
+      gtk_label_rescan_links (label);
+
+      switch (priv->jtype)
        {
        case GTK_JUSTIFY_LEFT:
          align = rtl ? PANGO_ALIGN_RIGHT : PANGO_ALIGN_LEFT;
@@ -2465,40 +3222,53 @@ gtk_label_ensure_layout (GtkLabel *label)
          break;
        case GTK_JUSTIFY_FILL:
          align = rtl ? PANGO_ALIGN_RIGHT : PANGO_ALIGN_LEFT;
-         pango_layout_set_justify (label->layout, TRUE);
+         pango_layout_set_justify (priv->layout, TRUE);
          break;
        default:
          g_assert_not_reached();
        }
 
-      pango_layout_set_alignment (label->layout, align);
-      pango_layout_set_ellipsize (label->layout, label->ellipsize);
-      pango_layout_set_single_paragraph_mode (label->layout, label->single_line_mode);
+      pango_layout_set_alignment (priv->layout, align);
+      pango_layout_set_ellipsize (priv->layout, priv->ellipsize);
+      pango_layout_set_single_paragraph_mode (priv->layout, priv->single_line_mode);
 
-      if (label->ellipsize)
-       pango_layout_set_width (label->layout, 
-                               widget->allocation.width * PANGO_SCALE);
-      else if (label->wrap)
+      if (priv->ellipsize)
+        pango_layout_set_width (priv->layout,
+                                widget->allocation.width * PANGO_SCALE);
+      else if (priv->wrap)
        {
-         GtkWidgetAuxInfo *aux_info;
+         GtkWidgetAuxInfo *aux_info = _gtk_widget_get_aux_info (widget, FALSE);
          gint longest_paragraph;
          gint width, height;
-
-         pango_layout_set_wrap (label->layout, label->wrap_mode);
-         
-         aux_info = _gtk_widget_get_aux_info (widget, FALSE);
-         if (aux_info && aux_info->width > 0)
-           pango_layout_set_width (label->layout, aux_info->width * PANGO_SCALE);
+         gint aux_width = 0;
+
+         if ((angle == 90 || angle == 270) && aux_info && aux_info->height > 0)
+           aux_width = aux_info->height;
+         else if (aux_info && aux_info->width > 0)
+           aux_width = aux_info->width;
+
+         if (aux_width > 0)
+           pango_layout_set_width (priv->layout, aux_width * PANGO_SCALE);
+         else if (guess_wrap_width == FALSE &&
+                  widget->allocation.width > 1 && widget->allocation.height > 1)
+           {
+             if (angle == 90 || angle == 270)
+               width = widget->allocation.height - label->misc.ypad * 2;
+             else
+               width = widget->allocation.width  - label->misc.xpad * 2;
+
+             pango_layout_set_wrap (priv->layout, priv->wrap_mode);
+             pango_layout_set_width (priv->layout, MAX (width, 1) * PANGO_SCALE);
+           }
          else
            {
              GdkScreen *screen = gtk_widget_get_screen (GTK_WIDGET (label));
              gint wrap_width;
-             
-             pango_layout_set_width (label->layout, -1);
-             pango_layout_get_extents (label->layout, NULL, &logical_rect);
+
+             pango_layout_set_width (priv->layout, -1);
+             pango_layout_get_extents (priv->layout, NULL, &logical_rect);
 
              width = logical_rect.width;
-             
              /* Try to guess a reasonable maximum width */
              longest_paragraph = width;
 
@@ -2506,27 +3276,27 @@ gtk_label_ensure_layout (GtkLabel *label)
              width = MIN (width, wrap_width);
              width = MIN (width,
                           PANGO_SCALE * (gdk_screen_get_width (screen) + 1) / 2);
-             
-             pango_layout_set_width (label->layout, width);
-             pango_layout_get_extents (label->layout, NULL, &logical_rect);
+
+             pango_layout_set_width (priv->layout, width);
+             pango_layout_get_extents (priv->layout, NULL, &logical_rect);
              width = logical_rect.width;
              height = logical_rect.height;
-             
+
              /* Unfortunately, the above may leave us with a very unbalanced looking paragraph,
               * so we try short search for a narrower width that leaves us with the same height
               */
              if (longest_paragraph > 0)
                {
                  gint nlines, perfect_width;
-                 
-                 nlines = pango_layout_get_line_count (label->layout);
+
+                 nlines = pango_layout_get_line_count (priv->layout);
                  perfect_width = (longest_paragraph + nlines - 1) / nlines;
                  
                  if (perfect_width < width)
                    {
-                     pango_layout_set_width (label->layout, perfect_width);
-                     pango_layout_get_extents (label->layout, NULL, &logical_rect);
-                     
+                     pango_layout_set_width (priv->layout, perfect_width);
+                     pango_layout_get_extents (priv->layout, NULL, &logical_rect);
+
                      if (logical_rect.height <= height)
                        width = logical_rect.width;
                      else
@@ -2535,264 +3305,605 @@ gtk_label_ensure_layout (GtkLabel *label)
                          
                          if (mid_width > perfect_width)
                            {
-                             pango_layout_set_width (label->layout, mid_width);
-                             pango_layout_get_extents (label->layout, NULL, &logical_rect);
-                             
+                             pango_layout_set_width (priv->layout, mid_width);
+                             pango_layout_get_extents (priv->layout, NULL, &logical_rect);
+
                              if (logical_rect.height <= height)
                                width = logical_rect.width;
                            }
                        }
                    }
                }
-             pango_layout_set_width (label->layout, width);
+             pango_layout_set_width (priv->layout, width);
            }
        }
-      else /* !label->wrap */
-       pango_layout_set_width (label->layout, -1);
+      else /* !priv->wrap */
+       pango_layout_set_width (priv->layout, -1);
     }
 }
 
-static void
-gtk_label_size_request (GtkWidget      *widget,
-                       GtkRequisition *requisition)
+static gint
+get_single_line_height (GtkWidget   *widget,
+                        PangoLayout *layout)
 {
-  GtkLabel *label = GTK_LABEL (widget);
-  GtkLabelPrivate *priv;
-  gint width, height;
-  PangoRectangle logical_rect;
-  GtkWidgetAuxInfo *aux_info;
+  PangoContext *context;
+  PangoFontMetrics *metrics;
+  gint ascent, descent;
 
-  priv = GTK_LABEL_GET_PRIVATE (widget);
+  context = pango_layout_get_context (layout);
+  metrics = pango_context_get_metrics (context, widget->style->font_desc,
+                                       pango_context_get_language (context));
 
-  /*  
-   * If word wrapping is on, then the height requisition can depend
-   * on:
-   *
-   *   - Any width set on the widget via gtk_widget_set_size_request().
-   *   - The padding of the widget (xpad, set by gtk_misc_set_padding)
-   *
-   * Instead of trying to detect changes to these quantities, if we
-   * are wrapping, we just rewrap for each size request. Since
-   * size requisitions are cached by the GTK+ core, this is not
-   * expensive.
-   */
+  ascent = pango_font_metrics_get_ascent (metrics);
+  descent = pango_font_metrics_get_descent (metrics);
+  pango_font_metrics_unref (metrics);
 
-  if (label->wrap)
-    gtk_label_clear_layout (label);
+  return ascent + descent;
+}
 
-  gtk_label_ensure_layout (label);
+static void
+gtk_label_size_request_init (GtkSizeRequestIface *iface)
+{
+  iface->get_request_mode     = gtk_label_get_request_mode;
+  iface->get_width            = gtk_label_get_width;
+  iface->get_height           = gtk_label_get_height;
+  iface->get_width_for_height = gtk_label_get_width_for_height;
+  iface->get_height_for_width = gtk_label_get_height_for_width;
+}
 
-  width = label->misc.xpad * 2;
-  height = label->misc.ypad * 2;
+static GtkSizeRequestMode
+gtk_label_get_request_mode (GtkSizeRequest *layout)
+{
+  GtkLabel *label = GTK_LABEL (layout);
+  gdouble   angle = gtk_label_get_angle (label);
 
-  aux_info = _gtk_widget_get_aux_info (widget, FALSE);
+  if (angle == 90 || angle == 270)
+    return GTK_SIZE_REQUEST_WIDTH_FOR_HEIGHT;
 
-  if (label->have_transform)
-    {
-      PangoRectangle rect;
-      PangoContext *context = pango_layout_get_context (label->layout);
-      const PangoMatrix *matrix = pango_context_get_matrix (context);
+  return GTK_SIZE_REQUEST_HEIGHT_FOR_WIDTH;
+}
 
-      pango_layout_get_extents (label->layout, NULL, &rect);
-      pango_matrix_transform_rectangle (matrix, &rect);
-      pango_extents_to_pixels (&rect, NULL);
-      
-      requisition->width = width + rect.width;
-      requisition->height = height + rect.height;
+static void
+get_size_for_allocation (GtkLabel        *label,
+                         GtkOrientation   orientation,
+                         gint             allocation,
+                         gint            *minimum_size,
+                         gint            *natural_size)
+{
+  GtkLabelPriv *priv = label->priv;
+  PangoLayout *layout;
+  GtkWidgetAuxInfo *aux_info =
+    _gtk_widget_get_aux_info (GTK_WIDGET (label), FALSE);
+  gint aux_size;
+  gint text_height;
 
-      return;
-    }
-  else
-    pango_layout_get_extents (label->layout, NULL, &logical_rect);
+  gtk_label_ensure_layout (label, FALSE);
+  layout = pango_layout_copy (priv->layout);
 
-  if ((label->wrap || label->ellipsize || 
-       priv->width_chars > 0 || priv->max_width_chars > 0) && 
-      aux_info && aux_info->width > 0)
-    width += aux_info->width;
-  else if (label->ellipsize || priv->width_chars > 0 || priv->max_width_chars > 0)
+  if (aux_info)
     {
-      width += PANGO_PIXELS (get_label_char_width (label));
+      if (orientation == GTK_ORIENTATION_HORIZONTAL)
+        aux_size = aux_info->width;
+      else
+        aux_size = aux_info->height;
     }
   else
-    width += PANGO_PIXELS (logical_rect.width);
+    aux_size = 0;
 
-  if (label->single_line_mode)
-    {
-      PangoContext *context;
-      PangoFontMetrics *metrics;
-      gint ascent, descent;
+  if (aux_size > 0)
+    pango_layout_set_width (layout, aux_size * PANGO_SCALE);
+  else
+    pango_layout_set_width (layout, allocation * PANGO_SCALE);
 
-      context = pango_layout_get_context (label->layout);
-      metrics = pango_context_get_metrics (context, widget->style->font_desc,
-                                           pango_context_get_language (context));
+  pango_layout_get_pixel_size (layout, NULL, &text_height);
 
-      ascent = pango_font_metrics_get_ascent (metrics);
-      descent = pango_font_metrics_get_descent (metrics);
-      pango_font_metrics_unref (metrics);
-    
-      height += PANGO_PIXELS (ascent + descent);
-    }
-  else
-    height += PANGO_PIXELS (logical_rect.height);
+  if (minimum_size)
+    *minimum_size = text_height;
+
+  if (natural_size)
+    *natural_size = text_height;
 
-  requisition->width = width;
-  requisition->height = height;
+  g_object_unref (layout);
 }
 
 static void
-gtk_label_size_allocate (GtkWidget     *widget,
-                         GtkAllocation *allocation)
+gtk_label_get_size (GtkSizeRequest *widget,
+                   GtkOrientation  orientation,
+                   gint           *minimum_size,
+                   gint           *natural_size)
 {
-  GtkLabel *label;
+  GtkLabel      *label = GTK_LABEL (widget);
+  GtkLabelPriv  *priv = label->priv;
+  PangoRectangle required_rect;
+  PangoRectangle natural_rect;
+  gdouble        angle;
+
+  /* "width-chars" Hard-coded minimum width:
+   *    - minimum size should be MAX (width-chars, strlen ("..."));
+   *    - natural size should be MAX (width-chars, strlen (priv->text));
+   *
+   * "max-width-chars" User specified maximum size requisition
+   *    - minimum size should be MAX (width-chars, 0)
+   *    - natural size should be MIN (max-width-chars, strlen (priv->text))
+   *
+   */
 
-  label = GTK_LABEL (widget);
+  /* When calculating ->wrap sometimes we need to invent a size; Ideally we should be doing
+   * that stuff here instead of inside gtk_label_ensure_layout() */
+  if (priv->wrap)
+    gtk_label_clear_layout (label);
+  gtk_label_ensure_layout (label, TRUE);
 
-  GTK_WIDGET_CLASS (gtk_label_parent_class)->size_allocate (widget, allocation);
+  angle = gtk_label_get_angle (label);
 
-  if (label->ellipsize)
-    {
-      if (label->layout)
-       {
-         gint width;
-         PangoRectangle logical;
+  /* Start off with the pixel extents of the rendered layout */
+  pango_layout_get_extents (priv->layout, NULL, &required_rect);
+  required_rect.x = required_rect.y = 0;
 
-         width = (allocation->width - label->misc.xpad * 2) * PANGO_SCALE;
+  if (priv->single_line_mode || priv->wrap)
+    required_rect.height = get_single_line_height (GTK_WIDGET (label), priv->layout);
 
-         pango_layout_set_width (label->layout, -1);
-         pango_layout_get_extents (label->layout, NULL, &logical);
+  natural_rect = required_rect;
 
-         if (logical.width > width)
-           pango_layout_set_width (label->layout, width);
-       }
-    }
+  /* Calculate text width itself based on GtkLabel property rules */
+  get_label_width (label, &required_rect.width, &natural_rect.width);
 
-  if (label->select_info && label->select_info->window)
+  /* Now that we have minimum and natural sizes in pango extents, apply a possible transform */
+  if (priv->have_transform)
     {
-      gdk_window_move_resize (label->select_info->window,
-                              allocation->x,
-                              allocation->y,
-                              allocation->width,
-                              allocation->height);
+      PangoLayout       *layout  = pango_layout_copy (priv->layout);
+      PangoContext      *context = pango_layout_get_context (priv->layout);
+      const PangoMatrix *matrix  = pango_context_get_matrix (context);
+
+      pango_layout_set_width (layout, -1);
+      pango_layout_set_ellipsize (layout, PANGO_ELLIPSIZE_NONE);
+
+      pango_layout_get_extents (layout, NULL, &natural_rect);
+      g_object_unref (layout);
+
+      pango_matrix_transform_rectangle (matrix, &required_rect);
+      pango_matrix_transform_rectangle (matrix, &natural_rect);
+
+      /* Bump the natural size in case of ellipsize to ensure pango has
+       * enough space in the angles (note, we could alternatively set the
+       * layout to not ellipsize when we know we have been allocated our
+       * full natural size, or it may be that pango needs a fix here).
+       */
+      if (priv->ellipsize && angle != 0 && angle != 90 && angle != 180 && angle != 270 && angle != 360)
+        {
+          /* For some reason we only need this at about 110 degrees, and only
+           * when gaining in height
+           */
+          natural_rect.height += ROTATION_ELLIPSIZE_PADDING * 2 * PANGO_SCALE;
+          natural_rect.width  += ROTATION_ELLIPSIZE_PADDING * 2 * PANGO_SCALE;
+        }
     }
-}
 
-static void
-gtk_label_state_changed (GtkWidget   *widget,
-                         GtkStateType prev_state)
-{
-  GtkLabel *label;
-  GdkCursor *cursor;
-  
-  label = GTK_LABEL (widget);
+  required_rect.width  = PANGO_PIXELS_CEIL (required_rect.width);
+  required_rect.height = PANGO_PIXELS_CEIL (required_rect.height);
 
-  if (label->select_info)
-    {
-      gtk_label_select_region (label, 0, 0);
+  natural_rect.width  = PANGO_PIXELS_CEIL (natural_rect.width);
+  natural_rect.height = PANGO_PIXELS_CEIL (natural_rect.height);
 
-      if (GTK_WIDGET_REALIZED (widget))
+  if (orientation == GTK_ORIENTATION_HORIZONTAL)
+    {
+      /* Note, we cant use get_size_for_allocation() when rotating
+       * ellipsized labels.
+       */
+      if (!(priv->ellipsize && priv->have_transform) &&
+          (angle == 90 || angle == 270))
         {
-          if (GTK_WIDGET_IS_SENSITIVE (widget))
-            cursor = gdk_cursor_new_for_display (gtk_widget_get_display (widget), GDK_XTERM);
-          else
-            cursor = NULL;
+          /* Doing a h4w request on a rotated label here, return the
+           * required width for the minimum height.
+           */
+          get_size_for_allocation (label,
+                                   GTK_ORIENTATION_VERTICAL,
+                                   required_rect.height,
+                                   minimum_size, natural_size);
 
-          gdk_window_set_cursor (label->select_info->window, cursor);
+        }
+      else
+        {
+          /* Normal desired width */
+          *minimum_size = required_rect.width;
+          *natural_size = natural_rect.width;
+        }
 
-          if (cursor)
-            gdk_cursor_unref (cursor);
+      *minimum_size += label->misc.xpad * 2;
+      *natural_size += label->misc.xpad * 2;
+    }
+  else /* GTK_ORIENTATION_VERTICAL */
+    {
+      /* Note, we cant use get_size_for_allocation() when rotating
+       * ellipsized labels.
+       */
+      if (!(priv->ellipsize && priv->have_transform) &&
+          (angle == 0 || angle == 180))
+        {
+          /* Doing a w4h request on a label here, return the required
+           * height for the minimum width.
+           */
+          get_size_for_allocation (label,
+                                   GTK_ORIENTATION_HORIZONTAL,
+                                   required_rect.width,
+                                   minimum_size, natural_size);
+        }
+      else
+        {
+          /* A vertically rotated label does w4h, so return the base
+           * desired height (text length)
+           */
+          *minimum_size = required_rect.height;
+          *natural_size = natural_rect.height;
         }
+
+      *minimum_size += label->misc.ypad * 2;
+      *natural_size += label->misc.ypad * 2;
     }
 
-  if (GTK_WIDGET_CLASS (gtk_label_parent_class)->state_changed)
-    GTK_WIDGET_CLASS (gtk_label_parent_class)->state_changed (widget, prev_state);
+  /* Restore real allocated size of layout; sometimes size-requests
+   * are randomly called without a following allocation; for this case
+   * we need to make sure we dont have a mucked up layout because we
+   * went and guessed the wrap-size.
+   */
+  if (priv->wrap)
+    gtk_label_clear_layout (label);
+  gtk_label_ensure_layout (label, FALSE);
+
 }
 
-static void 
-gtk_label_style_set (GtkWidget *widget,
-                    GtkStyle  *previous_style)
-{
-  GtkLabel *label = GTK_LABEL (widget);
 
-  /* We have to clear the layout, fonts etc. may have changed */
-  gtk_label_clear_layout (label);
-  gtk_label_invalidate_wrap_width (label);
+static void
+gtk_label_get_width (GtkSizeRequest *widget,
+                    gint           *minimum_size,
+                    gint           *natural_size)
+{
+  gtk_label_get_size (widget, GTK_ORIENTATION_HORIZONTAL, minimum_size, natural_size);
 }
 
-static void 
-gtk_label_direction_changed (GtkWidget        *widget,
-                            GtkTextDirection previous_dir)
+static void
+gtk_label_get_height (GtkSizeRequest *widget,
+                     gint           *minimum_size,
+                     gint           *natural_size)
 {
-  GtkLabel *label = GTK_LABEL (widget);
-
-  if (label->layout)
-    pango_layout_context_changed (label->layout);
-
-  GTK_WIDGET_CLASS (gtk_label_parent_class)->direction_changed (widget, previous_dir);
+  gtk_label_get_size (widget, GTK_ORIENTATION_VERTICAL, minimum_size, natural_size);
 }
 
 static void
-get_layout_location (GtkLabel  *label,
-                     gint      *xp,
-                     gint      *yp)
+gtk_label_get_width_for_height (GtkSizeRequest *widget,
+                                gint            height,
+                                gint           *minimum_width,
+                                gint           *natural_width)
 {
-  GtkMisc *misc;
-  GtkWidget *widget; 
-  GtkLabelPrivate *priv;
-  gfloat xalign;
-  gint req_width, x, y;
-  PangoRectangle logical;
-  
-  misc = GTK_MISC (label);
-  widget = GTK_WIDGET (label);
-  priv = GTK_LABEL_GET_PRIVATE (label);
-
-  if (gtk_widget_get_direction (widget) == GTK_TEXT_DIR_LTR)
-    xalign = misc->xalign;
-  else
-    xalign = 1.0 - misc->xalign;
-
-  pango_layout_get_pixel_extents (label->layout, NULL, &logical);
+  GtkLabel *label = GTK_LABEL (widget);
+  GtkLabelPriv *priv = label->priv;
+  gdouble angle = gtk_label_get_angle (label);
 
-  if (label->ellipsize || priv->width_chars > 0)
+  if (priv->wrap && (angle == 90 || angle == 270))
     {
-      int width;
+      if (priv->wrap)
+        gtk_label_clear_layout (label);
 
-      width = pango_layout_get_width (label->layout);
+      get_size_for_allocation (label, GTK_ORIENTATION_VERTICAL,
+                               MAX (1, height - (label->misc.ypad * 2)),
+                               minimum_width, natural_width);
 
-      req_width = logical.width;
-      if (width != -1)
-       req_width = MIN(PANGO_PIXELS (width), req_width);
-      req_width += 2 * misc->xpad;
+      if (minimum_width)
+        *minimum_width += label->misc.xpad * 2;
+
+      if (natural_width)
+        *natural_width += label->misc.xpad * 2;
     }
   else
-    req_width = widget->requisition.width;
+    GTK_SIZE_REQUEST_GET_IFACE (widget)->get_width (widget, minimum_width, natural_width);
+}
 
-  x = floor (widget->allocation.x + (gint)misc->xpad +
-             xalign * (widget->allocation.width - req_width));
+static void
+gtk_label_get_height_for_width (GtkSizeRequest *widget,
+                                gint            width,
+                                gint           *minimum_height,
+                                gint           *natural_height)
+{
+  GtkLabel *label = GTK_LABEL (widget);
+  GtkLabelPriv *priv = label->priv;
+  gdouble angle = gtk_label_get_angle (label);
 
-  if (gtk_widget_get_direction (widget) == GTK_TEXT_DIR_LTR)
-    x = MAX (x, widget->allocation.x + misc->xpad);
-  else
-    x = MIN (x, widget->allocation.x + widget->allocation.width - misc->xpad);
-  x -= logical.x;
+  if (priv->wrap && (angle == 0 || angle == 180 || angle == 360))
+    {
+      if (priv->wrap)
+        gtk_label_clear_layout (label);
 
-  y = floor (widget->allocation.y + (gint)misc->ypad 
-             + MAX (((widget->allocation.height - widget->requisition.height) * misc->yalign),
-            0));
+      get_size_for_allocation (label, GTK_ORIENTATION_HORIZONTAL,
+                               MAX (1, width - label->misc.xpad * 2),
+                               minimum_height, natural_height);
 
-  if (xp)
-    *xp = x;
+      if (minimum_height)
+        *minimum_height += label->misc.ypad * 2;
 
-  if (yp)
-    *yp = y;
+      if (natural_height)
+        *natural_height += label->misc.ypad * 2;
+    }
+  else
+    GTK_SIZE_REQUEST_GET_IFACE (widget)->get_height (widget, minimum_height, natural_height);
 }
 
 static void
-draw_insertion_cursor (GtkLabel      *label,
-                      GdkRectangle  *cursor_location,
-                      gboolean       is_primary,
-                      PangoDirection direction,
-                      gboolean       draw_arrow)
+gtk_label_size_allocate (GtkWidget     *widget,
+                         GtkAllocation *allocation)
+{
+  GtkLabel *label = GTK_LABEL (widget);
+  GtkLabelPriv *priv = label->priv;
+
+  GTK_WIDGET_CLASS (gtk_label_parent_class)->size_allocate (widget, allocation);
+
+  /* The layout may have been recently cleared in get_size_for_orientation(),
+   * but the width at that point may not be the same as the allocated width
+   */
+  if (priv->wrap)
+    gtk_label_clear_layout (label);
+
+  gtk_label_ensure_layout (label, FALSE);
+
+  if (priv->ellipsize)
+    {
+      if (priv->layout)
+        {
+          PangoRectangle logical;
+          PangoRectangle bounds;
+
+          bounds.x = bounds.y = 0;
+          bounds.width = allocation->width - label->misc.xpad * 2;
+          bounds.height = allocation->height - label->misc.ypad * 2;
+
+          pango_layout_set_width (priv->layout, -1);
+          pango_layout_get_pixel_extents (priv->layout, NULL, &logical);
+
+          if (priv->have_transform)
+            {
+              PangoContext *context = gtk_widget_get_pango_context (widget);
+              const PangoMatrix *matrix = pango_context_get_matrix (context);
+
+              const gdouble dx = matrix->xx; /* cos (M_PI * angle / 180) */
+              const gdouble dy = matrix->xy; /* sin (M_PI * angle / 180) */
+              if (fabs (dy) < 0.01)
+                {
+                  if (logical.width > bounds.width)
+                    pango_layout_set_width (priv->layout, bounds.width * PANGO_SCALE);
+                }
+              else if (fabs (dx) < 0.01)
+                {
+                  if (logical.width > bounds.height)
+                    pango_layout_set_width (priv->layout, bounds.height * PANGO_SCALE);
+                }
+              else
+                {
+                  gdouble x0, y0, x1, y1, length;
+                  gboolean vertical;
+                  gint cy;
+
+                  x0 = bounds.width / 2;
+                  y0 = dx ? x0 * dy / dx : G_MAXDOUBLE;
+                  vertical = fabs (y0) > bounds.height / 2;
+
+                  if (vertical)
+                    {
+                      y0 = bounds.height/2;
+                      x0 = dy ? y0 * dx / dy : G_MAXDOUBLE;
+                    }
+
+                  length = 2 * sqrt (x0 * x0 + y0 * y0);
+                  pango_layout_set_width (priv->layout, rint (length * PANGO_SCALE));
+                  pango_layout_get_pixel_size (priv->layout, NULL, &cy);
+
+                  x1 = +dy * cy/2;
+                  y1 = -dx * cy/2;
+
+                  if (vertical)
+                    {
+                      y0 = bounds.height/2 + y1 - y0;
+                      x0 = -y0 * dx/dy;
+                    }
+                  else
+                    {
+                      x0 = bounds.width/2 + x1 - x0;
+                      y0 = -x0 * dy/dx;
+                    }
+                  length = length - sqrt (x0 * x0 + y0 * y0) * 2;
+                  pango_layout_set_width (priv->layout, rint (length * PANGO_SCALE));
+                }
+            }
+          else if (logical.width > bounds.width)
+            pango_layout_set_width (priv->layout, bounds.width * PANGO_SCALE);
+        }
+    }
+
+  if (priv->select_info && priv->select_info->window)
+    {
+      gdk_window_move_resize (priv->select_info->window,
+                              allocation->x,
+                              allocation->y,
+                              allocation->width,
+                              allocation->height);
+    }
+}
+
+static void
+gtk_label_update_cursor (GtkLabel *label)
+{
+  GtkLabelPriv *priv = label->priv;
+  GtkWidget *widget;
+
+  if (!priv->select_info)
+    return;
+
+  widget = GTK_WIDGET (label);
+
+  if (gtk_widget_get_realized (widget))
+    {
+      GdkDisplay *display;
+      GdkCursor *cursor;
+
+      if (gtk_widget_is_sensitive (widget))
+        {
+          display = gtk_widget_get_display (widget);
+
+          if (priv->select_info->active_link)
+            cursor = gdk_cursor_new_for_display (display, GDK_HAND2);
+          else if (priv->select_info->selectable)
+            cursor = gdk_cursor_new_for_display (display, GDK_XTERM);
+          else
+            cursor = NULL;
+        }
+      else
+        cursor = NULL;
+
+      gdk_window_set_cursor (priv->select_info->window, cursor);
+
+      if (cursor)
+        gdk_cursor_unref (cursor);
+    }
+}
+
+static void
+gtk_label_state_changed (GtkWidget   *widget,
+                         GtkStateType prev_state)
+{
+  GtkLabel *label = GTK_LABEL (widget);
+  GtkLabelPriv *priv = label->priv;
+
+  if (priv->select_info)
+    {
+      gtk_label_select_region (label, 0, 0);
+      gtk_label_update_cursor (label);
+    }
+
+  if (GTK_WIDGET_CLASS (gtk_label_parent_class)->state_changed)
+    GTK_WIDGET_CLASS (gtk_label_parent_class)->state_changed (widget, prev_state);
+}
+
+static void
+gtk_label_style_set (GtkWidget *widget,
+                    GtkStyle  *previous_style)
+{
+  GtkLabel *label = GTK_LABEL (widget);
+
+  /* We have to clear the layout, fonts etc. may have changed */
+  gtk_label_clear_layout (label);
+  gtk_label_invalidate_wrap_width (label);
+}
+
+static void 
+gtk_label_direction_changed (GtkWidget        *widget,
+                            GtkTextDirection previous_dir)
+{
+  GtkLabel *label = GTK_LABEL (widget);
+  GtkLabelPriv *priv = label->priv;
+
+  if (priv->layout)
+    pango_layout_context_changed (priv->layout);
+
+  GTK_WIDGET_CLASS (gtk_label_parent_class)->direction_changed (widget, previous_dir);
+}
+
+static void
+get_layout_location (GtkLabel  *label,
+                     gint      *xp,
+                     gint      *yp)
+{
+  GtkMisc *misc;
+  GtkWidget *widget;
+  GtkLabelPriv *priv;
+  gfloat xalign;
+  gint req_width, x, y;
+  gint req_height;
+  PangoRectangle logical;
+  gdouble angle;
+
+  misc   = GTK_MISC (label);
+  widget = GTK_WIDGET (label);
+  priv   = label->priv;
+  angle  = gtk_label_get_angle (label);
+
+  if (gtk_widget_get_direction (widget) == GTK_TEXT_DIR_LTR)
+    xalign = misc->xalign;
+  else
+    xalign = 1.0 - misc->xalign;
+
+  pango_layout_get_extents (priv->layout, NULL, &logical);
+
+  /* Do the wrap width delimiting before the transform
+   */
+  if (priv->wrap || priv->ellipsize || priv->width_chars > 0)
+    {
+      int width;
+
+      width = pango_layout_get_width (priv->layout);
+
+      if (width != -1)
+       logical.width = MIN (width, logical.width);
+    }
+
+  if (priv->have_transform)
+    {
+      PangoContext *context = gtk_widget_get_pango_context (widget);
+      const PangoMatrix *matrix = pango_context_get_matrix (context);
+      pango_matrix_transform_rectangle (matrix, &logical);
+    }
+
+  pango_extents_to_pixels (&logical, NULL);
+
+  req_width  = logical.width;
+  req_height = logical.height;
+
+  req_width  += 2 * misc->xpad;
+  req_height += 2 * misc->ypad;
+
+  x = floor (widget->allocation.x + (gint)misc->xpad +
+             xalign * (widget->allocation.width - req_width));
+
+  if (gtk_widget_get_direction (widget) == GTK_TEXT_DIR_LTR)
+    x = MAX (x, widget->allocation.x + misc->xpad);
+  else
+    x = MIN (x, widget->allocation.x + widget->allocation.width - misc->xpad);
+
+
+
+
+  /* bgo#315462 - For single-line labels, *do* align the requisition with
+   * respect to the allocation, even if we are under-allocated.  For multi-line
+   * labels, always show the top of the text when they are under-allocated.  The
+   * rationale is this:
+   *
+   * - Single-line labels appear in GtkButtons, and it is very easy to get them
+   *   to be smaller than their requisition.  The button may clip the label, but
+   *   the label will still be able to show most of itself and the focus
+   *   rectangle.  Also, it is fairly easy to read a single line of clipped text.
+   *
+   * - Multi-line labels should not be clipped to showing "something in the
+   *   middle".  You want to read the first line, at least, to get some context.
+   */
+  if (pango_layout_get_line_count (priv->layout) == 1)
+    y = floor (widget->allocation.y + (gint)misc->ypad 
+              + (widget->allocation.height - req_height) * misc->yalign);
+  else
+    y = floor (widget->allocation.y + (gint)misc->ypad 
+              + MAX (((widget->allocation.height - req_height) * misc->yalign),
+                     0));
+
+  if (xp)
+    *xp = x;
+
+  if (yp)
+    *yp = y;
+}
+
+static void
+draw_insertion_cursor (GtkLabel      *label,
+                      GdkRectangle  *cursor_location,
+                      gboolean       is_primary,
+                      PangoDirection direction,
+                      gboolean       draw_arrow)
 {
   GtkWidget *widget = GTK_WIDGET (label);
   GtkTextDirection text_dir;
@@ -2810,24 +3921,25 @@ draw_insertion_cursor (GtkLabel      *label,
 static PangoDirection
 get_cursor_direction (GtkLabel *label)
 {
+  GtkLabelPriv *priv = label->priv;
   GSList *l;
 
-  g_assert (label->select_info);
+  g_assert (priv->select_info);
 
-  gtk_label_ensure_layout (label);
+  gtk_label_ensure_layout (label, FALSE);
 
-  for (l = pango_layout_get_lines_readonly (label->layout); l; l = l->next)
+  for (l = pango_layout_get_lines_readonly (priv->layout); l; l = l->next)
     {
       PangoLayoutLine *line = l->data;
 
-      /* If label->select_info->selection_end is at the very end of
+      /* If priv->select_info->selection_end is at the very end of
        * the line, we don't know if the cursor is on this line or
        * the next without looking ahead at the next line. (End
        * of paragraph is different from line break.) But it's
        * definitely in this paragraph, which is good enough
        * to figure out the resolved direction.
        */
-       if (line->start_index + line->length >= label->select_info->selection_end)
+       if (line->start_index + line->length >= priv->select_info->selection_end)
        return line->resolved_dir;
     }
 
@@ -2837,13 +3949,16 @@ get_cursor_direction (GtkLabel *label)
 static void
 gtk_label_draw_cursor (GtkLabel  *label, gint xoffset, gint yoffset)
 {
-  if (label->select_info == NULL)
+  GtkLabelPriv *priv = label->priv;
+  GtkWidget *widget;
+
+  if (priv->select_info == NULL)
     return;
+
+  widget = GTK_WIDGET (label);
   
-  if (GTK_WIDGET_DRAWABLE (label))
+  if (gtk_widget_is_drawable (widget))
     {
-      GtkWidget *widget = GTK_WIDGET (label);
-
       PangoDirection keymap_direction;
       PangoDirection cursor_direction;
       PangoRectangle strong_pos, weak_pos;
@@ -2857,9 +3972,9 @@ gtk_label_draw_cursor (GtkLabel  *label, gint xoffset, gint yoffset)
       keymap_direction = gdk_keymap_get_direction (gdk_keymap_get_for_display (gtk_widget_get_display (widget)));
       cursor_direction = get_cursor_direction (label);
 
-      gtk_label_ensure_layout (label);
+      gtk_label_ensure_layout (label, FALSE);
       
-      pango_layout_get_cursor_pos (label->layout, label->select_info->selection_end,
+      pango_layout_get_cursor_pos (priv->layout, priv->select_info->selection_end,
                                   &strong_pos, &weak_pos);
 
       g_object_get (gtk_widget_get_settings (widget),
@@ -2910,46 +4025,65 @@ gtk_label_draw_cursor (GtkLabel  *label, gint xoffset, gint yoffset)
     }
 }
 
+static GtkLabelLink *
+gtk_label_get_focus_link (GtkLabel *label)
+{
+  GtkLabelPriv *priv = label->priv;
+  GtkLabelSelectionInfo *info = priv->select_info;
+  GList *l;
+
+  if (!info)
+    return NULL;
+
+  if (info->selection_anchor != info->selection_end)
+    return NULL;
+
+  for (l = info->links; l; l = l->next)
+    {
+      GtkLabelLink *link = l->data;
+      if (link->start <= info->selection_anchor &&
+          info->selection_anchor <= link->end)
+        return link;
+    }
+
+  return NULL;
+}
 
 static gint
 gtk_label_expose (GtkWidget      *widget,
                  GdkEventExpose *event)
 {
-  GtkLabel *label;
+  GtkLabel *label = GTK_LABEL (widget);
+  GtkLabelPriv *priv = label->priv;
+  GtkLabelSelectionInfo *info = priv->select_info;
   gint x, y;
-  
-  g_return_val_if_fail (GTK_IS_LABEL (widget), FALSE);
-  g_return_val_if_fail (event != NULL, FALSE);
-  
-  label = GTK_LABEL (widget);
 
-  gtk_label_ensure_layout (label);
-  
-  if (GTK_WIDGET_VISIBLE (widget) && GTK_WIDGET_MAPPED (widget) &&
-      label->text && (*label->text != '\0'))
+  gtk_label_ensure_layout (label, FALSE);
+
+  if (gtk_widget_get_visible (widget) && gtk_widget_get_mapped (widget) &&
+      priv->text && (*priv->text != '\0'))
     {
       get_layout_location (label, &x, &y);
 
       gtk_paint_layout (widget->style,
                         widget->window,
-                        GTK_WIDGET_STATE (widget),
+                        gtk_widget_get_state (widget),
                        FALSE,
                         &event->area,
                         widget,
                         "label",
                         x, y,
-                        label->layout);
-      
-      if (label->select_info &&
-          (label->select_info->selection_anchor !=
-           label->select_info->selection_end))
+                        priv->layout);
+
+      if (info &&
+          (info->selection_anchor != info->selection_end))
         {
           gint range[2];
-          GdkRegion *clip;
+          cairo_region_t *clip;
          GtkStateType state;
-         
-          range[0] = label->select_info->selection_anchor;
-          range[1] = label->select_info->selection_end;
+
+          range[0] = info->selection_anchor;
+          range[1] = info->selection_end;
 
           if (range[0] > range[1])
             {
@@ -2958,12 +4092,12 @@ gtk_label_expose (GtkWidget      *widget,
               range[1] = tmp;
             }
 
-          clip = gdk_pango_layout_get_clip_region (label->layout,
+          clip = gdk_pango_layout_get_clip_region (priv->layout,
                                                    x, y,
                                                    range,
                                                    1);
-         gdk_region_intersect (clip, event->region);
+         cairo_region_intersect (clip, event->region);
+
          /* FIXME should use gtk_paint, but it can't use a clip
            * region
            */
@@ -2972,72 +4106,130 @@ gtk_label_expose (GtkWidget      *widget,
 
 
          state = GTK_STATE_SELECTED;
-         if (!GTK_WIDGET_HAS_FOCUS (widget))
+         if (!gtk_widget_has_focus (widget))
            state = GTK_STATE_ACTIVE;
-             
+
           gdk_draw_layout_with_colors (widget->window,
                                        widget->style->black_gc,
                                        x, y,
-                                       label->layout,
+                                       priv->layout,
                                        &widget->style->text[state],
                                        &widget->style->base[state]);
 
           gdk_gc_set_clip_region (widget->style->black_gc, NULL);
-          gdk_region_destroy (clip);
+          cairo_region_destroy (clip);
+        }
+      else if (info)
+        {
+          GtkLabelLink *focus_link;
+          GtkLabelLink *active_link;
+          gint range[2];
+          cairo_region_t *clip;
+          GdkRectangle rect;
+          GdkColor *text_color;
+          GdkColor *base_color;
+          GdkColor *link_color;
+          GdkColor *visited_link_color;
+
+          if (info->selectable && gtk_widget_has_focus (widget))
+           gtk_label_draw_cursor (label, x, y);
+
+          focus_link = gtk_label_get_focus_link (label);
+          active_link = info->active_link;
+
+          if (active_link)
+            {
+              range[0] = active_link->start;
+              range[1] = active_link->end;
+
+              clip = gdk_pango_layout_get_clip_region (priv->layout,
+                                                       x, y,
+                                                       range,
+                                                       1);
+              gdk_gc_set_clip_region (widget->style->black_gc, clip);
+
+              gtk_label_get_link_colors (widget, &link_color, &visited_link_color);
+              if (active_link->visited)
+                text_color = visited_link_color;
+              else
+                text_color = link_color;
+              if (info->link_clicked)
+                base_color = &widget->style->base[GTK_STATE_ACTIVE];
+              else
+                base_color = &widget->style->base[GTK_STATE_PRELIGHT];
+              gdk_draw_layout_with_colors (widget->window,
+                                           widget->style->black_gc,
+                                           x, y,
+                                           priv->layout,
+                                           text_color,
+                                           base_color);
+              gdk_color_free (link_color);
+              gdk_color_free (visited_link_color);
+
+              gdk_gc_set_clip_region (widget->style->black_gc, NULL);
+              cairo_region_destroy (clip);
+            }
+
+          if (focus_link && gtk_widget_has_focus (widget))
+            {
+              range[0] = focus_link->start;
+              range[1] = focus_link->end;
+
+              clip = gdk_pango_layout_get_clip_region (priv->layout,
+                                                       x, y,
+                                                       range,
+                                                       1);
+              cairo_region_get_extents (clip, &rect);
+
+              gtk_paint_focus (widget->style, widget->window, gtk_widget_get_state (widget),
+                               &event->area, widget, "label",
+                               rect.x, rect.y, rect.width, rect.height);
+
+              cairo_region_destroy (clip);
+            }
         }
-      else if (label->select_info && GTK_WIDGET_HAS_FOCUS (widget))
-       gtk_label_draw_cursor (label, x, y);
     }
 
   return FALSE;
 }
 
-static void
-gtk_label_set_uline_text_internal (GtkLabel    *label,
-                                  const gchar *str)
+static gboolean
+separate_uline_pattern (const gchar  *str,
+                        guint        *accel_key,
+                        gchar       **new_str,
+                        gchar       **pattern)
 {
-  guint accel_key = GDK_VoidSymbol;
-
-  gchar *new_str;
-  gchar *pattern;
-  const gchar *src;
-  gchar *dest, *pattern_dest;
   gboolean underscore;
-      
-  g_return_if_fail (GTK_IS_LABEL (label));
-  g_return_if_fail (str != NULL);
+  const gchar *src;
+  gchar *dest;
+  gchar *pattern_dest;
+
+  *accel_key = GDK_VoidSymbol;
+  *new_str = g_new (gchar, strlen (str) + 1);
+  *pattern = g_new (gchar, g_utf8_strlen (str, -1) + 1);
 
-  /* Split text into the base text and a separate pattern
-   * of underscores.
-   */
-  
-  new_str = g_new (gchar, strlen (str) + 1);
-  pattern = g_new (gchar, g_utf8_strlen (str, -1) + 1);
-  
   underscore = FALSE;
 
-  if (str == NULL)
-    str = "";
-  
   src = str;
-  dest = new_str;
-  pattern_dest = pattern;
-  
+  dest = *new_str;
+  pattern_dest = *pattern;
+
   while (*src)
     {
       gunichar c;
-      gchar *next_src;
+      const gchar *next_src;
 
       c = g_utf8_get_char (src);
       if (c == (gunichar)-1)
        {
          g_warning ("Invalid input string");
-         g_free (new_str);
-         g_free (pattern);
-         return;
+         g_free (*new_str);
+         g_free (*pattern);
+
+         return FALSE;
        }
       next_src = g_utf8_next_char (src);
-      
+
       if (underscore)
        {
          if (c == '_')
@@ -3045,13 +4237,13 @@ gtk_label_set_uline_text_internal (GtkLabel    *label,
          else
            {
              *pattern_dest++ = '_';
-             if (accel_key == GDK_VoidSymbol)
-               accel_key = gdk_keyval_to_lower (gdk_unicode_to_keyval (c));
+             if (*accel_key == GDK_VoidSymbol)
+               *accel_key = gdk_keyval_to_lower (gdk_unicode_to_keyval (c));
            }
 
          while (src < next_src)
            *dest++ = *src++;
-         
+
          underscore = FALSE;
        }
       else
@@ -3065,50 +4257,41 @@ gtk_label_set_uline_text_internal (GtkLabel    *label,
            {
              while (src < next_src)
                *dest++ = *src++;
-         
+
              *pattern_dest++ = ' ';
            }
        }
     }
+
   *dest = 0;
   *pattern_dest = 0;
-  
-  gtk_label_set_text_internal (label, new_str);
-  gtk_label_set_pattern_internal (label, pattern);
-  
-  g_free (pattern);
 
-  label->mnemonic_keyval = accel_key;
+  return TRUE;
 }
 
-guint      
-gtk_label_parse_uline (GtkLabel    *label,
-                      const gchar *str)
+static void
+gtk_label_set_uline_text_internal (GtkLabel    *label,
+                                  const gchar *str)
 {
-  guint keyval;
-  
-  g_return_val_if_fail (GTK_IS_LABEL (label), GDK_VoidSymbol);
-  g_return_val_if_fail (str != NULL, GDK_VoidSymbol);
+  GtkLabelPriv *priv = label->priv;
+  guint accel_key = GDK_VoidSymbol;
+  gchar *new_str;
+  gchar *pattern;
 
-  g_object_freeze_notify (G_OBJECT (label));
-  
-  gtk_label_set_label_internal (label, g_strdup (str ? str : ""));
-  gtk_label_set_use_markup_internal (label, FALSE);
-  gtk_label_set_use_underline_internal (label, TRUE);
-  
-  gtk_label_recalculate (label);
+  g_return_if_fail (GTK_IS_LABEL (label));
+  g_return_if_fail (str != NULL);
+
+  /* Split text into the base text and a separate pattern
+   * of underscores.
+   */
+  if (!separate_uline_pattern (str, &accel_key, &new_str, &pattern))
+    return;
 
-  keyval = label->mnemonic_keyval;
-  if (keyval != GDK_VoidSymbol)
-    {
-      label->mnemonic_keyval = GDK_VoidSymbol;
-      gtk_label_setup_mnemonic (label, keyval);
-      g_object_notify (G_OBJECT (label), "mnemonic-keyval");
-    }
-  
-  g_object_thaw_notify (G_OBJECT (label));
+  gtk_label_set_text_internal (label, new_str);
+  gtk_label_set_pattern_internal (label, pattern, TRUE);
+  priv->mnemonic_keyval = accel_key;
 
-  return keyval;
+  g_free (pattern);
 }
 
 /**
@@ -3143,24 +4326,22 @@ gtk_label_set_text_with_mnemonic (GtkLabel    *label,
 static void
 gtk_label_realize (GtkWidget *widget)
 {
-  GtkLabel *label;
-
-  label = GTK_LABEL (widget);
+  GtkLabel *label = GTK_LABEL (widget);
+  GtkLabelPriv *priv = label->priv;
 
   GTK_WIDGET_CLASS (gtk_label_parent_class)->realize (widget);
 
-  if (label->select_info)
+  if (priv->select_info)
     gtk_label_create_window (label);
 }
 
 static void
 gtk_label_unrealize (GtkWidget *widget)
 {
-  GtkLabel *label;
-
-  label = GTK_LABEL (widget);
+  GtkLabel *label = GTK_LABEL (widget);
+  GtkLabelPriv *priv = label->priv;
 
-  if (label->select_info)
+  if (priv->select_info)
     gtk_label_destroy_window (label);
 
   GTK_WIDGET_CLASS (gtk_label_parent_class)->unrealize (widget);
@@ -3169,25 +4350,23 @@ gtk_label_unrealize (GtkWidget *widget)
 static void
 gtk_label_map (GtkWidget *widget)
 {
-  GtkLabel *label;
-
-  label = GTK_LABEL (widget);
+  GtkLabel *label = GTK_LABEL (widget);
+  GtkLabelPriv *priv = label->priv;
 
   GTK_WIDGET_CLASS (gtk_label_parent_class)->map (widget);
 
-  if (label->select_info)
-    gdk_window_show (label->select_info->window);
+  if (priv->select_info)
+    gdk_window_show (priv->select_info->window);
 }
 
 static void
 gtk_label_unmap (GtkWidget *widget)
 {
-  GtkLabel *label;
-
-  label = GTK_LABEL (widget);
+  GtkLabel *label = GTK_LABEL (widget);
+  GtkLabelPriv *priv = label->priv;
 
-  if (label->select_info)
-    gdk_window_hide (label->select_info->window);
+  if (priv->select_info)
+    gdk_window_hide (priv->select_info->window);
 
   GTK_WIDGET_CLASS (gtk_label_parent_class)->unmap (widget);
 }
@@ -3246,31 +4425,32 @@ layout_to_window_coords (GtkLabel *label,
 }
 #endif
 
-static void
+static gboolean
 get_layout_index (GtkLabel *label,
                   gint      x,
                   gint      y,
                   gint     *index)
 {
+  GtkLabelPriv *priv = label->priv;
   gint trailing = 0;
   const gchar *cluster;
   const gchar *cluster_end;
+  gboolean inside;
 
   *index = 0;
-  
-  gtk_label_ensure_layout (label);
-  
+
+  gtk_label_ensure_layout (label, FALSE);
+
   window_to_layout_coords (label, &x, &y);
 
   x *= PANGO_SCALE;
   y *= PANGO_SCALE;
-  
-  pango_layout_xy_to_index (label->layout,
-                            x, y,
-                            index, &trailing);
 
-  
-  cluster = label->text + *index;
+  inside = pango_layout_xy_to_index (priv->layout,
+                                     x, y,
+                                     index, &trailing);
+
+  cluster = priv->text + *index;
   cluster_end = cluster;
   while (trailing)
     {
@@ -3279,20 +4459,23 @@ get_layout_index (GtkLabel *label,
     }
 
   *index += (cluster_end - cluster);
+
+  return inside;
 }
 
 static void
 gtk_label_select_word (GtkLabel *label)
 {
+  GtkLabelPriv *priv = label->priv;
   gint min, max;
-  
-  gint start_index = gtk_label_move_backward_word (label, label->select_info->selection_end);
-  gint end_index = gtk_label_move_forward_word (label, label->select_info->selection_end);
 
-  min = MIN (label->select_info->selection_anchor,
-            label->select_info->selection_end);
-  max = MAX (label->select_info->selection_anchor,
-            label->select_info->selection_end);
+  gint start_index = gtk_label_move_backward_word (label, priv->select_info->selection_end);
+  gint end_index = gtk_label_move_forward_word (label, priv->select_info->selection_end);
+
+  min = MIN (priv->select_info->selection_anchor,
+            priv->select_info->selection_end);
+  max = MAX (priv->select_info->selection_anchor,
+            priv->select_info->selection_end);
 
   min = MIN (min, start_index);
   max = MAX (max, end_index);
@@ -3303,78 +4486,215 @@ gtk_label_select_word (GtkLabel *label)
 static void
 gtk_label_grab_focus (GtkWidget *widget)
 {
-  GtkLabel *label;
+  GtkLabel *label = GTK_LABEL (widget);
+  GtkLabelPriv *priv = label->priv;
   gboolean select_on_focus;
-  
-  label = GTK_LABEL (widget);
+  GtkLabelLink *link;
 
-  if (label->select_info == NULL)
+  if (priv->select_info == NULL)
     return;
 
   GTK_WIDGET_CLASS (gtk_label_parent_class)->grab_focus (widget);
 
-  g_object_get (gtk_widget_get_settings (widget),
-               "gtk-label-select-on-focus",
-               &select_on_focus,
-               NULL);
-  
-  if (select_on_focus && !label->in_click)
-    gtk_label_select_region (label, 0, -1);
+  if (priv->select_info->selectable)
+    {
+      g_object_get (gtk_widget_get_settings (widget),
+                    "gtk-label-select-on-focus",
+                    &select_on_focus,
+                    NULL);
+
+      if (select_on_focus && !priv->in_click)
+        gtk_label_select_region (label, 0, -1);
+    }
+  else
+    {
+      if (priv->select_info->links && !priv->in_click)
+        {
+          link = priv->select_info->links->data;
+          priv->select_info->selection_anchor = link->start;
+          priv->select_info->selection_end = link->start;
+        }
+    }
 }
+
+static gboolean
+gtk_label_focus (GtkWidget        *widget,
+                 GtkDirectionType  direction)
+{
+  GtkLabel *label = GTK_LABEL (widget);
+  GtkLabelPriv *priv = label->priv;
+  GtkLabelSelectionInfo *info = priv->select_info;
+  GtkLabelLink *focus_link;
+  GList *l;
+
+  if (!gtk_widget_is_focus (widget))
+    {
+      gtk_widget_grab_focus (widget);
+      if (info)
+        {
+          focus_link = gtk_label_get_focus_link (label);
+          if (focus_link && direction == GTK_DIR_TAB_BACKWARD)
+            {
+              l = g_list_last (info->links);
+              focus_link = l->data;
+              info->selection_anchor = focus_link->start;
+              info->selection_end = focus_link->start;
+            }
+        }
+
+      return TRUE;
+    }
+
+  if (!info)
+    return FALSE;
+
+  if (info->selectable)
+    {
+      gint index;
+
+      if (info->selection_anchor != info->selection_end)
+        goto out;
+
+      index = info->selection_anchor;
+
+      if (direction == GTK_DIR_TAB_FORWARD)
+        for (l = info->links; l; l = l->next)
+          {
+            GtkLabelLink *link = l->data;
+
+            if (link->start > index)
+              {
+                gtk_label_select_region_index (label, link->start, link->start);
+                return TRUE;
+              }
+          }
+      else if (direction == GTK_DIR_TAB_BACKWARD)
+        for (l = g_list_last (info->links); l; l = l->prev)
+          {
+            GtkLabelLink *link = l->data;
+
+            if (link->end < index)
+              {
+                gtk_label_select_region_index (label, link->start, link->start);
+                return TRUE;
+              }
+          }
+
+      goto out;
+    }
+  else
+    {
+      focus_link = gtk_label_get_focus_link (label);
+      switch (direction)
+        {
+        case GTK_DIR_TAB_FORWARD:
+          if (focus_link)
+            {
+              l = g_list_find (info->links, focus_link);
+              l = l->next;
+            }
+          else
+            l = info->links;
+          break;
+
+        case GTK_DIR_TAB_BACKWARD:
+          if (focus_link)
+            {
+              l = g_list_find (info->links, focus_link);
+              l = l->prev;
+            }
+          else
+            l = g_list_last (info->links);
+          break;
+
+        default:
+          goto out;
+        }
+
+      if (l)
+        {
+          focus_link = l->data;
+          info->selection_anchor = focus_link->start;
+          info->selection_end = focus_link->start;
+          gtk_widget_queue_draw (widget);
+
+          return TRUE;
+        }
+    }
+
+out:
+
+  return FALSE;
+}
+
 static gboolean
 gtk_label_button_press (GtkWidget      *widget,
                         GdkEventButton *event)
 {
-  GtkLabel *label;
+  GtkLabel *label = GTK_LABEL (widget);
+  GtkLabelPriv *priv = label->priv;
+  GtkLabelSelectionInfo *info = priv->select_info;
   gint index = 0;
-  gint min, max;  
-  
-  label = GTK_LABEL (widget);
+  gint min, max;
+
+  if (info == NULL)
+    return FALSE;
+
+  if (info->active_link)
+    {
+      if (event->button == 1)
+        {
+          info->link_clicked = 1;
+          gtk_widget_queue_draw (widget);
+        }
+      else if (event->button == 3 && event->type == GDK_BUTTON_PRESS)
+        {
+          info->link_clicked = 1;
+          gtk_label_do_popup (label, event);
+          return TRUE;
+        }
+    }
 
-  if (label->select_info == NULL)
+  if (!info->selectable)
     return FALSE;
 
-  label->select_info->in_drag = FALSE;
-  label->select_info->select_words = FALSE;
+  info->in_drag = FALSE;
+  info->select_words = FALSE;
 
   if (event->button == 1)
     {
-      if (!GTK_WIDGET_HAS_FOCUS (widget)) 
+      if (!gtk_widget_has_focus (widget))
        {
-         label->in_click = TRUE;
+         priv->in_click = TRUE;
          gtk_widget_grab_focus (widget);
-         label->in_click = FALSE;
+         priv->in_click = FALSE;
        }
 
       if (event->type == GDK_3BUTTON_PRESS)
        {
-         gtk_label_select_region_index (label, 0, strlen (label->text));
+         gtk_label_select_region_index (label, 0, strlen (priv->text));
          return TRUE;
        }
-      
+
       if (event->type == GDK_2BUTTON_PRESS)
        {
-          label->select_info->select_words = TRUE;
+          info->select_words = TRUE;
          gtk_label_select_word (label);
          return TRUE;
        }
-      
+
       get_layout_index (label, event->x, event->y, &index);
-      
-      min = MIN (label->select_info->selection_anchor,
-                label->select_info->selection_end);
-      max = MAX (label->select_info->selection_anchor,
-                label->select_info->selection_end);
-         
-      if ((label->select_info->selection_anchor !=
-          label->select_info->selection_end) &&
+
+      min = MIN (info->selection_anchor, info->selection_end);
+      max = MAX (info->selection_anchor, info->selection_end);
+
+      if ((info->selection_anchor != info->selection_end) &&
          (event->state & GDK_SHIFT_MASK))
        {
          /* extend (same as motion) */
          min = MIN (min, index);
          max = MAX (max, index);
-         
+
          /* ensure the anchor is opposite index */
          if (index == min)
            {
@@ -3382,26 +4702,26 @@ gtk_label_button_press (GtkWidget      *widget,
              min = max;
              max = tmp;
            }
-         
+
          gtk_label_select_region_index (label, min, max);
        }
-      else 
+      else
        {
          if (event->type == GDK_3BUTTON_PRESS)
-           gtk_label_select_region_index (label, 0, strlen (label->text));
+           gtk_label_select_region_index (label, 0, strlen (priv->text));
          else if (event->type == GDK_2BUTTON_PRESS)
            gtk_label_select_word (label);
          else if (min < max && min <= index && index <= max)
            {
-             label->select_info->in_drag = TRUE;
-             label->select_info->drag_start_x = event->x;
-             label->select_info->drag_start_y = event->y;
+             info->in_drag = TRUE;
+             info->drag_start_x = event->x;
+             info->drag_start_y = event->y;
            }
          else
            /* start a replacement */
            gtk_label_select_region_index (label, index, index);
        }
-  
+
       return TRUE;
     }
   else if (event->button == 3 && event->type == GDK_BUTTON_PRESS)
@@ -3409,7 +4729,6 @@ gtk_label_button_press (GtkWidget      *widget,
       gtk_label_do_popup (label, event);
 
       return TRUE;
-      
     }
   return FALSE;
 }
@@ -3420,57 +4739,99 @@ gtk_label_button_release (GtkWidget      *widget,
 
 {
   GtkLabel *label = GTK_LABEL (widget);
+  GtkLabelPriv *priv = label->priv;
+  GtkLabelSelectionInfo *info = priv->select_info;
   gint index;
-  
-  if (label->select_info == NULL)
+
+  if (info == NULL)
     return FALSE;
-  
-  if (label->select_info->in_drag)
+
+  if (info->in_drag)
     {
-      label->select_info->in_drag = 0;
+      info->in_drag = 0;
 
       get_layout_index (label, event->x, event->y, &index);
       gtk_label_select_region_index (label, index, index);
-      
+
       return FALSE;
     }
 
   if (event->button != 1)
     return FALSE;
-  
+
+  if (info->active_link &&
+      info->selection_anchor == info->selection_end &&
+      info->link_clicked)
+    {
+      emit_activate_link (label, info->active_link);
+      info->link_clicked = 0;
+
+      return TRUE;
+    }
+
   /* The goal here is to return TRUE iff we ate the
    * button press to start selecting.
    */
-  
   return TRUE;
 }
 
+static void
+connect_mnemonics_visible_notify (GtkLabel *label)
+{
+  GtkLabelPriv *priv = label->priv;
+  GtkWidget *toplevel;
+  gboolean connected;
+
+  toplevel = gtk_widget_get_toplevel (GTK_WIDGET (label));
+
+  if (!GTK_IS_WINDOW (toplevel))
+    return;
+
+  /* always set up this widgets initial value */
+  priv->mnemonics_visible =
+    gtk_window_get_mnemonics_visible (GTK_WINDOW (toplevel));
+
+  connected =
+    GPOINTER_TO_INT (g_object_get_data (G_OBJECT (toplevel),
+                                        "gtk-label-mnemonics-visible-connected"));
+
+  if (!connected)
+    {
+      g_signal_connect (toplevel,
+                        "notify::mnemonics-visible",
+                        G_CALLBACK (label_mnemonics_visible_changed),
+                        label);
+      g_object_set_data (G_OBJECT (toplevel),
+                         "gtk-label-mnemonics-visible-connected",
+                         GINT_TO_POINTER (1));
+    }
+}
+
 static void
 drag_begin_cb (GtkWidget      *widget,
                GdkDragContext *context,
                gpointer        data)
 {
-  GtkLabel *label;
+  GtkLabel *label = GTK_LABEL (widget);
+  GtkLabelPriv *priv = label->priv;
   GdkPixmap *pixmap = NULL;
 
   g_signal_handlers_disconnect_by_func (widget, drag_begin_cb, NULL);
 
-  label = GTK_LABEL (widget);
-
-  if ((label->select_info->selection_anchor !=
-       label->select_info->selection_end) &&
-      label->text)
+  if ((priv->select_info->selection_anchor !=
+       priv->select_info->selection_end) &&
+      priv->text)
     {
       gint start, end;
       gint len;
-      
-      start = MIN (label->select_info->selection_anchor,
-                   label->select_info->selection_end);
-      end = MAX (label->select_info->selection_anchor,
-                 label->select_info->selection_end);
-      
-      len = strlen (label->text);
-      
+
+      start = MIN (priv->select_info->selection_anchor,
+                   priv->select_info->selection_end);
+      end = MAX (priv->select_info->selection_anchor,
+                 priv->select_info->selection_end);
+
+      len = strlen (priv->text);
+
       if (end > len)
         end = len;
       
@@ -3478,7 +4839,7 @@ drag_begin_cb (GtkWidget      *widget,
         start = len;
       
       pixmap = _gtk_text_util_create_drag_icon (widget, 
-                                               label->text + start,
+                                               priv->text + start,
                                                end - start);
     }
 
@@ -3499,48 +4860,94 @@ static gboolean
 gtk_label_motion (GtkWidget      *widget,
                   GdkEventMotion *event)
 {
-  GtkLabel *label;
+  GtkLabel *label = GTK_LABEL (widget);
+  GtkLabelPriv *priv = label->priv;
+  GtkLabelSelectionInfo *info = priv->select_info;
   gint index;
-  gint x, y;
-  
-  label = GTK_LABEL (widget);
-  
-  if (label->select_info == NULL)
-    return FALSE;  
+
+  if (info == NULL)
+    return FALSE;
+
+  if (info->links && !info->in_drag)
+    {
+      GList *l;
+      GtkLabelLink *link;
+      gboolean found = FALSE;
+
+      if (info->selection_anchor == info->selection_end)
+        {
+          if (get_layout_index (label, event->x, event->y, &index))
+            {
+              for (l = info->links; l != NULL; l = l->next)
+                {
+                  link = l->data;
+                  if (index >= link->start && index <= link->end)
+                    {
+                      found = TRUE;
+                      break;
+                    }
+                }
+            }
+        }
+
+      if (found)
+        {
+          if (info->active_link != link)
+            {
+              info->link_clicked = 0;
+              info->active_link = link;
+              gtk_label_update_cursor (label);
+              gtk_widget_queue_draw (widget);
+            }
+        }
+      else
+        {
+          if (info->active_link != NULL)
+            {
+              info->link_clicked = 0;
+              info->active_link = NULL;
+              gtk_label_update_cursor (label);
+              gtk_widget_queue_draw (widget);
+            }
+        }
+    }
+
+  if (!info->selectable)
+    return FALSE;
 
   if ((event->state & GDK_BUTTON1_MASK) == 0)
     return FALSE;
 
-  gdk_window_get_pointer (label->select_info->window,
-                          &x, &y, NULL);
-  
-  if (label->select_info->in_drag)
+  if (info->in_drag)
     {
       if (gtk_drag_check_threshold (widget,
-                                   label->select_info->drag_start_x, 
-                                   label->select_info->drag_start_y,
+                                   info->drag_start_x,
+                                   info->drag_start_y,
                                    event->x, event->y))
        {
          GtkTargetList *target_list = gtk_target_list_new (NULL, 0);
 
          gtk_target_list_add_text_targets (target_list, 0);
-         
-          g_signal_connect (widget, "drag-begin", 
+
+          g_signal_connect (widget, "drag-begin",
                             G_CALLBACK (drag_begin_cb), NULL);
-         gtk_drag_begin (widget, target_list, 
+         gtk_drag_begin (widget, target_list,
                          GDK_ACTION_COPY,
                          1, (GdkEvent *)event);
-         
-         label->select_info->in_drag = FALSE;
-         
+
+         info->in_drag = FALSE;
+
          gtk_target_list_unref (target_list);
        }
     }
   else
     {
+      gint x, y;
+
+      gdk_window_get_device_position (info->window, event->device, &x, &y, NULL);
       get_layout_index (label, x, y, &index);
-      
-      if (label->select_info->select_words)
+
+      if (info->select_words)
         {
           gint min, max;
           gint old_min, old_max;
@@ -3549,8 +4956,8 @@ gtk_label_motion (GtkWidget      *widget,
           min = gtk_label_move_backward_word (label, index);
           max = gtk_label_move_forward_word (label, index);
 
-          anchor = label->select_info->selection_anchor;
-          end = label->select_info->selection_end;
+          anchor = info->selection_anchor;
+          end = info->selection_end;
 
           old_min = MIN (anchor, end);
           old_max = MAX (anchor, end);
@@ -3579,28 +4986,46 @@ gtk_label_motion (GtkWidget      *widget,
           gtk_label_select_region_index (label, anchor, end);
         }
       else
-        gtk_label_select_region_index (label, 
-                                       label->select_info->selection_anchor,
-                                       index);
+        gtk_label_select_region_index (label, info->selection_anchor, index);
     }
 
   return TRUE;
 }
 
+static gboolean
+gtk_label_leave_notify (GtkWidget        *widget,
+                        GdkEventCrossing *event)
+{
+  GtkLabel *label = GTK_LABEL (widget);
+  GtkLabelPriv *priv = label->priv;
+
+  if (priv->select_info)
+    {
+      priv->select_info->active_link = NULL;
+      gtk_label_update_cursor (label);
+      gtk_widget_queue_draw (widget);
+    }
+
+  if (GTK_WIDGET_CLASS (gtk_label_parent_class)->leave_notify_event)
+    return GTK_WIDGET_CLASS (gtk_label_parent_class)->leave_notify_event (widget, event);
+
+ return FALSE;
+}
+
 static void
 gtk_label_create_window (GtkLabel *label)
 {
+  GtkLabelPriv *priv = label->priv;
   GtkWidget *widget;
   GdkWindowAttr attributes;
   gint attributes_mask;
-  
-  g_assert (label->select_info);
-  g_assert (GTK_WIDGET_REALIZED (label));
-  
-  if (label->select_info->window)
-    return;
-  
+
+  g_assert (priv->select_info);
   widget = GTK_WIDGET (label);
+  g_assert (gtk_widget_get_realized (widget));
+
+  if (priv->select_info->window)
+    return;
 
   attributes.x = widget->allocation.x;
   attributes.y = widget->allocation.y;
@@ -3612,9 +5037,12 @@ gtk_label_create_window (GtkLabel *label)
   attributes.event_mask = gtk_widget_get_events (widget) |
     GDK_BUTTON_PRESS_MASK        |
     GDK_BUTTON_RELEASE_MASK      |
-    GDK_BUTTON_MOTION_MASK;
-  attributes_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_NOREDIR; 
-  if (GTK_WIDGET_IS_SENSITIVE (widget))
+    GDK_LEAVE_NOTIFY_MASK        |
+    GDK_BUTTON_MOTION_MASK       |
+    GDK_POINTER_MOTION_MASK      |
+    GDK_POINTER_MOTION_HINT_MASK;
+  attributes_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_NOREDIR;
+  if (gtk_widget_is_sensitive (widget))
     {
       attributes.cursor = gdk_cursor_new_for_display (gtk_widget_get_display (widget),
                                                      GDK_XTERM);
@@ -3622,9 +5050,9 @@ gtk_label_create_window (GtkLabel *label)
     }
 
 
-  label->select_info->window = gdk_window_new (widget->window,
+  priv->select_info->window = gdk_window_new (widget->window,
                                                &attributes, attributes_mask);
-  gdk_window_set_user_data (label->select_info->window, widget);
+  gdk_window_set_user_data (priv->select_info->window, widget);
 
   if (attributes_mask & GDK_WA_CURSOR)
     gdk_cursor_unref (attributes.cursor);
@@ -3633,14 +5061,54 @@ gtk_label_create_window (GtkLabel *label)
 static void
 gtk_label_destroy_window (GtkLabel *label)
 {
-  g_assert (label->select_info);
+  GtkLabelPriv *priv = label->priv;
 
-  if (label->select_info->window == NULL)
+  g_assert (priv->select_info);
+
+  if (priv->select_info->window == NULL)
     return;
-  
-  gdk_window_set_user_data (label->select_info->window, NULL);
-  gdk_window_destroy (label->select_info->window);
-  label->select_info->window = NULL;
+
+  gdk_window_set_user_data (priv->select_info->window, NULL);
+  gdk_window_destroy (priv->select_info->window);
+  priv->select_info->window = NULL;
+}
+
+static void
+gtk_label_ensure_select_info (GtkLabel *label)
+{
+  GtkLabelPriv *priv = label->priv;
+
+  if (priv->select_info == NULL)
+    {
+      priv->select_info = g_new0 (GtkLabelSelectionInfo, 1);
+
+      gtk_widget_set_can_focus (GTK_WIDGET (label), TRUE);
+
+      if (gtk_widget_get_realized (GTK_WIDGET (label)))
+       gtk_label_create_window (label);
+
+      if (gtk_widget_get_mapped (GTK_WIDGET (label)))
+        gdk_window_show (priv->select_info->window);
+    }
+}
+
+static void
+gtk_label_clear_select_info (GtkLabel *label)
+{
+  GtkLabelPriv *priv = label->priv;
+
+  if (priv->select_info == NULL)
+    return;
+
+  if (!priv->select_info->selectable && !priv->select_info->links)
+    {
+      gtk_label_destroy_window (label);
+
+      g_free (priv->select_info);
+      priv->select_info = NULL;
+
+      gtk_widget_set_can_focus (GTK_WIDGET (label), FALSE);
+    }
 }
 
 /**
@@ -3655,45 +5123,32 @@ void
 gtk_label_set_selectable (GtkLabel *label,
                           gboolean  setting)
 {
+  GtkLabelPriv *priv;
   gboolean old_setting;
-  
+
   g_return_if_fail (GTK_IS_LABEL (label));
-  
+
+  priv = label->priv;
+
   setting = setting != FALSE;
-  old_setting = label->select_info != NULL;
-  
+  old_setting = priv->select_info && priv->select_info->selectable;
+
   if (setting)
     {
-      if (label->select_info == NULL)
-        {
-          label->select_info = g_new0 (GtkLabelSelectionInfo, 1);
-
-         GTK_WIDGET_SET_FLAGS (label, GTK_CAN_FOCUS);
-      
-          if (GTK_WIDGET_REALIZED (label))
-           gtk_label_create_window (label);
-
-          if (GTK_WIDGET_MAPPED (label))
-            gdk_window_show (label->select_info->window);
-        }
+      gtk_label_ensure_select_info (label);
+      priv->select_info->selectable = TRUE;
+      gtk_label_update_cursor (label);
     }
   else
     {
-      if (label->select_info)
+      if (old_setting)
         {
-          /* unselect, to give up the selection */
-          gtk_label_select_region (label, 0, 0);
-          
-          if (label->select_info->window)
-           {
-             gtk_label_destroy_window (label);
-           }
-
-          g_free (label->select_info);
-
-          label->select_info = NULL;
+          /* unselect, to give up the selection */
+          gtk_label_select_region (label, 0, 0);
 
-         GTK_WIDGET_UNSET_FLAGS (label, GTK_CAN_FOCUS);
+          priv->select_info->selectable = FALSE;
+          gtk_label_clear_select_info (label);
+          gtk_label_update_cursor (label);
         }
     }
   if (setting != old_setting)
@@ -3718,9 +5173,13 @@ gtk_label_set_selectable (GtkLabel *label,
 gboolean
 gtk_label_get_selectable (GtkLabel *label)
 {
+  GtkLabelPriv *priv;
+
   g_return_val_if_fail (GTK_IS_LABEL (label), FALSE);
 
-  return label->select_info != NULL;
+  priv = label->priv;
+
+  return priv->select_info && priv->select_info->selectable;
 }
 
 static void
@@ -3808,28 +5267,30 @@ static void
 gtk_label_set_selection_text (GtkLabel         *label,
                              GtkSelectionData *selection_data)
 {
-  if ((label->select_info->selection_anchor !=
-       label->select_info->selection_end) &&
-      label->text)
+  GtkLabelPriv *priv = label->priv;
+
+  if ((priv->select_info->selection_anchor !=
+       priv->select_info->selection_end) &&
+      priv->text)
     {
       gint start, end;
       gint len;
-      
-      start = MIN (label->select_info->selection_anchor,
-                   label->select_info->selection_end);
-      end = MAX (label->select_info->selection_anchor,
-                 label->select_info->selection_end);
-      
-      len = strlen (label->text);
-      
+
+      start = MIN (priv->select_info->selection_anchor,
+                   priv->select_info->selection_end);
+      end = MAX (priv->select_info->selection_anchor,
+                 priv->select_info->selection_end);
+
+      len = strlen (priv->text);
+
       if (end > len)
         end = len;
-      
+
       if (start > len)
         start = len;
-      
+
       gtk_selection_data_set_text (selection_data,
-                                  label->text + start,
+                                  priv->text + start,
                                   end - start);
     }
 }
@@ -3858,13 +5319,15 @@ clear_text_callback (GtkClipboard     *clipboard,
                      gpointer          user_data_or_owner)
 {
   GtkLabel *label;
+  GtkLabelPriv *priv;
 
   label = GTK_LABEL (user_data_or_owner);
+  priv = label->priv;
 
-  if (label->select_info)
+  if (priv->select_info)
     {
-      label->select_info->selection_anchor = label->select_info->selection_end;
-      
+      priv->select_info->selection_anchor = priv->select_info->selection_end;
+
       gtk_widget_queue_draw (GTK_WIDGET (label));
     }
 }
@@ -3874,22 +5337,26 @@ gtk_label_select_region_index (GtkLabel *label,
                                gint      anchor_index,
                                gint      end_index)
 {
+  GtkLabelPriv *priv;
+
   g_return_if_fail (GTK_IS_LABEL (label));
-  
-  if (label->select_info)
+
+  priv = label->priv;
+
+  if (priv->select_info && priv->select_info->selectable)
     {
       GtkClipboard *clipboard;
 
-      if (label->select_info->selection_anchor == anchor_index &&
-         label->select_info->selection_end == end_index)
+      if (priv->select_info->selection_anchor == anchor_index &&
+         priv->select_info->selection_end == end_index)
        return;
 
-      label->select_info->selection_anchor = anchor_index;
-      label->select_info->selection_end = end_index;
+      priv->select_info->selection_anchor = anchor_index;
+      priv->select_info->selection_end = end_index;
 
       clipboard = gtk_widget_get_clipboard (GTK_WIDGET (label),
-                                           GDK_SELECTION_PRIMARY);      
-      
+                                           GDK_SELECTION_PRIMARY);
+
       if (anchor_index != end_index)
         {
           GtkTargetList *list;
@@ -3940,19 +5407,23 @@ gtk_label_select_region  (GtkLabel *label,
                           gint      start_offset,
                           gint      end_offset)
 {
+  GtkLabelPriv *priv;
+
   g_return_if_fail (GTK_IS_LABEL (label));
-  
-  if (label->text && label->select_info)
+
+  priv = label->priv;
+
+  if (priv->text && priv->select_info)
     {
       if (start_offset < 0)
-        start_offset = g_utf8_strlen (label->text, -1);
+        start_offset = g_utf8_strlen (priv->text, -1);
       
       if (end_offset < 0)
-        end_offset = g_utf8_strlen (label->text, -1);
+        end_offset = g_utf8_strlen (priv->text, -1);
       
       gtk_label_select_region_index (label,
-                                     g_utf8_offset_to_pointer (label->text, start_offset) - label->text,
-                                     g_utf8_offset_to_pointer (label->text, end_offset) - label->text);
+                                     g_utf8_offset_to_pointer (priv->text, start_offset) - priv->text,
+                                     g_utf8_offset_to_pointer (priv->text, end_offset) - priv->text);
     }
 }
 
@@ -3972,9 +5443,13 @@ gtk_label_get_selection_bounds (GtkLabel  *label,
                                 gint      *start,
                                 gint      *end)
 {
+  GtkLabelPriv *priv;
+
   g_return_val_if_fail (GTK_IS_LABEL (label), FALSE);
 
-  if (label->select_info == NULL)
+  priv = label->priv;
+
+  if (priv->select_info == NULL)
     {
       /* not a selectable label */
       if (start)
@@ -3990,12 +5465,12 @@ gtk_label_get_selection_bounds (GtkLabel  *label,
       gint start_offset, end_offset;
       gint len;
       
-      start_index = MIN (label->select_info->selection_anchor,
-                   label->select_info->selection_end);
-      end_index = MAX (label->select_info->selection_anchor,
-                 label->select_info->selection_end);
+      start_index = MIN (priv->select_info->selection_anchor,
+                   priv->select_info->selection_end);
+      end_index = MAX (priv->select_info->selection_anchor,
+                 priv->select_info->selection_end);
 
-      len = strlen (label->text);
+      len = strlen (priv->text);
 
       if (end_index > len)
         end_index = len;
@@ -4003,8 +5478,8 @@ gtk_label_get_selection_bounds (GtkLabel  *label,
       if (start_index > len)
         start_index = len;
       
-      start_offset = g_utf8_strlen (label->text, start_index);
-      end_offset = g_utf8_strlen (label->text, end_index);
+      start_offset = g_utf8_strlen (priv->text, start_index);
+      end_offset = g_utf8_strlen (priv->text, end_index);
 
       if (start_offset > end_offset)
         {
@@ -4033,24 +5508,28 @@ gtk_label_get_selection_bounds (GtkLabel  *label,
  * pixel positions, in combination with gtk_label_get_layout_offsets().
  * The returned layout is owned by the label so need not be
  * freed by the caller.
- * 
- * Return value: the #PangoLayout for this label
+ *
+ * Return value: (transfer none): the #PangoLayout for this label
  **/
 PangoLayout*
 gtk_label_get_layout (GtkLabel *label)
 {
+  GtkLabelPriv *priv;
+
   g_return_val_if_fail (GTK_IS_LABEL (label), NULL);
 
-  gtk_label_ensure_layout (label);
+  priv = label->priv;
 
-  return label->layout;
+  gtk_label_ensure_layout (label, FALSE);
+
+  return priv->layout;
 }
 
 /**
  * gtk_label_get_layout_offsets:
  * @label: a #GtkLabel
- * @x: location to store X offset of layout, or %NULL
- * @y: location to store Y offset of layout, or %NULL
+ * @x: (allow-none): location to store X offset of layout, or %NULL
+ * @y: (allow-none): location to store Y offset of layout, or %NULL
  *
  * Obtains the coordinates where the label will draw the #PangoLayout
  * representing the text in the label; useful to convert mouse events
@@ -4068,7 +5547,7 @@ gtk_label_get_layout_offsets (GtkLabel *label,
 {
   g_return_if_fail (GTK_IS_LABEL (label));
 
-  gtk_label_ensure_layout (label);
+  gtk_label_ensure_layout (label, FALSE);
 
   get_layout_location (label, x, y);
 }
@@ -4106,8 +5585,8 @@ gboolean
 gtk_label_get_use_markup (GtkLabel *label)
 {
   g_return_val_if_fail (GTK_IS_LABEL (label), FALSE);
-  
-  return label->use_markup;
+
+  return label->priv->use_markup;
 }
 
 /**
@@ -4142,8 +5621,8 @@ gboolean
 gtk_label_get_use_underline (GtkLabel *label)
 {
   g_return_val_if_fail (GTK_IS_LABEL (label), FALSE);
-  
-  return label->use_underline;
+
+  return label->priv->use_underline;
 }
 
 /**
@@ -4159,13 +5638,17 @@ void
 gtk_label_set_single_line_mode (GtkLabel *label,
                                 gboolean single_line_mode)
 {
+  GtkLabelPriv *priv;
+
   g_return_if_fail (GTK_IS_LABEL (label));
 
+  priv = label->priv;
+
   single_line_mode = single_line_mode != FALSE;
 
-  if (label->single_line_mode != single_line_mode)
+  if (priv->single_line_mode != single_line_mode)
     {
-      label->single_line_mode = single_line_mode;
+      priv->single_line_mode = single_line_mode;
 
       gtk_label_clear_layout (label);
       gtk_widget_queue_resize (GTK_WIDGET (label));
@@ -4189,7 +5672,7 @@ gtk_label_get_single_line_mode  (GtkLabel *label)
 {
   g_return_val_if_fail (GTK_IS_LABEL (label), FALSE);
 
-  return label->single_line_mode;
+  return label->priv->single_line_mode;
 }
 
 /* Compute the X position for an offset that corresponds to the "more important
@@ -4203,6 +5686,7 @@ get_better_cursor (GtkLabel *label,
                   gint      *x,
                   gint      *y)
 {
+  GtkLabelPriv *priv = label->priv;
   GdkKeymap *keymap = gdk_keymap_get_for_display (gtk_widget_get_display (GTK_WIDGET (label)));
   PangoDirection keymap_direction = gdk_keymap_get_direction (keymap);
   PangoDirection cursor_direction = get_cursor_direction (label);
@@ -4213,9 +5697,9 @@ get_better_cursor (GtkLabel *label,
                "gtk-split-cursor", &split_cursor,
                NULL);
 
-  gtk_label_ensure_layout (label);
+  gtk_label_ensure_layout (label, FALSE);
   
-  pango_layout_get_cursor_pos (label->layout, index,
+  pango_layout_get_cursor_pos (priv->layout, index,
                               &strong_pos, &weak_pos);
 
   if (split_cursor)
@@ -4244,20 +5728,21 @@ gtk_label_move_logically (GtkLabel *label,
                          gint      start,
                          gint      count)
 {
-  gint offset = g_utf8_pointer_to_offset (label->text,
-                                         label->text + start);
+  GtkLabelPriv *priv = label->priv;
+  gint offset = g_utf8_pointer_to_offset (priv->text,
+                                         priv->text + start);
 
-  if (label->text)
+  if (priv->text)
     {
       PangoLogAttr *log_attrs;
       gint n_attrs;
       gint length;
 
-      gtk_label_ensure_layout (label);
+      gtk_label_ensure_layout (label, FALSE);
       
-      length = g_utf8_strlen (label->text, -1);
+      length = g_utf8_strlen (priv->text, -1);
 
-      pango_layout_get_log_attrs (label->layout, &log_attrs, &n_attrs);
+      pango_layout_get_log_attrs (priv->layout, &log_attrs, &n_attrs);
 
       while (count > 0 && offset < length)
        {
@@ -4279,7 +5764,7 @@ gtk_label_move_logically (GtkLabel *label,
       g_free (log_attrs);
     }
 
-  return g_utf8_offset_to_pointer (label->text, offset) - label->text;
+  return g_utf8_offset_to_pointer (priv->text, offset) - priv->text;
 }
 
 static gint
@@ -4287,6 +5772,7 @@ gtk_label_move_visually (GtkLabel *label,
                         gint      start,
                         gint      count)
 {
+  GtkLabelPriv *priv = label->priv;
   gint index;
 
   index = start;
@@ -4297,7 +5783,7 @@ gtk_label_move_visually (GtkLabel *label,
       gboolean split_cursor;
       gboolean strong;
 
-      gtk_label_ensure_layout (label);
+      gtk_label_ensure_layout (label, FALSE);
 
       g_object_get (gtk_widget_get_settings (GTK_WIDGET (label)),
                    "gtk-split-cursor", &split_cursor,
@@ -4315,12 +5801,12 @@ gtk_label_move_visually (GtkLabel *label,
       
       if (count > 0)
        {
-         pango_layout_move_cursor_visually (label->layout, strong, index, 0, 1, &new_index, &new_trailing);
+         pango_layout_move_cursor_visually (priv->layout, strong, index, 0, 1, &new_index, &new_trailing);
          count--;
        }
       else
        {
-         pango_layout_move_cursor_visually (label->layout, strong, index, 0, -1, &new_index, &new_trailing);
+         pango_layout_move_cursor_visually (priv->layout, strong, index, 0, -1, &new_index, &new_trailing);
          count++;
        }
 
@@ -4330,7 +5816,7 @@ gtk_label_move_visually (GtkLabel *label,
       index = new_index;
       
       while (new_trailing--)
-       index = g_utf8_next_char (label->text + new_index) - label->text;
+       index = g_utf8_next_char (priv->text + new_index) - priv->text;
     }
   
   return index;
@@ -4340,20 +5826,21 @@ static gint
 gtk_label_move_forward_word (GtkLabel *label,
                             gint      start)
 {
-  gint new_pos = g_utf8_pointer_to_offset (label->text,
-                                          label->text + start);
+  GtkLabelPriv *priv = label->priv;
+  gint new_pos = g_utf8_pointer_to_offset (priv->text,
+                                          priv->text + start);
   gint length;
 
-  length = g_utf8_strlen (label->text, -1);
+  length = g_utf8_strlen (priv->text, -1);
   if (new_pos < length)
     {
       PangoLogAttr *log_attrs;
       gint n_attrs;
 
-      gtk_label_ensure_layout (label);
-      
-      pango_layout_get_log_attrs (label->layout, &log_attrs, &n_attrs);
-      
+      gtk_label_ensure_layout (label, FALSE);
+
+      pango_layout_get_log_attrs (priv->layout, &log_attrs, &n_attrs);
+
       /* Find the next word end */
       new_pos++;
       while (new_pos < n_attrs && !log_attrs[new_pos].is_word_end)
@@ -4362,7 +5849,7 @@ gtk_label_move_forward_word (GtkLabel *label,
       g_free (log_attrs);
     }
 
-  return g_utf8_offset_to_pointer (label->text, new_pos) - label->text;
+  return g_utf8_offset_to_pointer (priv->text, new_pos) - priv->text;
 }
 
 
@@ -4370,18 +5857,19 @@ static gint
 gtk_label_move_backward_word (GtkLabel *label,
                              gint      start)
 {
-  gint new_pos = g_utf8_pointer_to_offset (label->text,
-                                          label->text + start);
+  GtkLabelPriv *priv = label->priv;
+  gint new_pos = g_utf8_pointer_to_offset (priv->text,
+                                          priv->text + start);
 
   if (new_pos > 0)
     {
       PangoLogAttr *log_attrs;
       gint n_attrs;
 
-      gtk_label_ensure_layout (label);
-      
-      pango_layout_get_log_attrs (label->layout, &log_attrs, &n_attrs);
-      
+      gtk_label_ensure_layout (label, FALSE);
+
+      pango_layout_get_log_attrs (priv->layout, &log_attrs, &n_attrs);
+
       new_pos -= 1;
 
       /* Find the previous word beginning */
@@ -4391,7 +5879,7 @@ gtk_label_move_backward_word (GtkLabel *label,
       g_free (log_attrs);
     }
 
-  return g_utf8_offset_to_pointer (label->text, new_pos) - label->text;
+  return g_utf8_offset_to_pointer (priv->text, new_pos) - priv->text;
 }
 
 static void
@@ -4400,15 +5888,16 @@ gtk_label_move_cursor (GtkLabel       *label,
                       gint            count,
                       gboolean        extend_selection)
 {
+  GtkLabelPriv *priv = label->priv;
   gint old_pos;
   gint new_pos;
-  
-  if (label->select_info == NULL)
+
+  if (priv->select_info == NULL)
     return;
 
-  old_pos = new_pos = label->select_info->selection_end;
+  old_pos = new_pos = priv->select_info->selection_end;
 
-  if (label->select_info->selection_end != label->select_info->selection_anchor &&
+  if (priv->select_info->selection_end != priv->select_info->selection_anchor &&
       !extend_selection)
     {
       /* If we have a current selection and aren't extending it, move to the
@@ -4421,30 +5910,30 @@ gtk_label_move_cursor (GtkLabel       *label,
            gint end_x, end_y;
            gint anchor_x, anchor_y;
            gboolean end_is_left;
-           
-           get_better_cursor (label, label->select_info->selection_end, &end_x, &end_y);
-           get_better_cursor (label, label->select_info->selection_anchor, &anchor_x, &anchor_y);
+
+           get_better_cursor (label, priv->select_info->selection_end, &end_x, &end_y);
+           get_better_cursor (label, priv->select_info->selection_anchor, &anchor_x, &anchor_y);
 
            end_is_left = (end_y < anchor_y) || (end_y == anchor_y && end_x < anchor_x);
            
            if (count < 0)
-             new_pos = end_is_left ? label->select_info->selection_end : label->select_info->selection_anchor;
+             new_pos = end_is_left ? priv->select_info->selection_end : priv->select_info->selection_anchor;
            else
-             new_pos = !end_is_left ? label->select_info->selection_end : label->select_info->selection_anchor;
+             new_pos = !end_is_left ? priv->select_info->selection_end : priv->select_info->selection_anchor;
            break;
          }
        case GTK_MOVEMENT_LOGICAL_POSITIONS:
        case GTK_MOVEMENT_WORDS:
          if (count < 0)
-           new_pos = MIN (label->select_info->selection_end, label->select_info->selection_anchor);
+           new_pos = MIN (priv->select_info->selection_end, priv->select_info->selection_anchor);
          else
-           new_pos = MAX (label->select_info->selection_end, label->select_info->selection_anchor);
+           new_pos = MAX (priv->select_info->selection_end, priv->select_info->selection_anchor);
          break;
        case GTK_MOVEMENT_DISPLAY_LINE_ENDS:
        case GTK_MOVEMENT_PARAGRAPH_ENDS:
        case GTK_MOVEMENT_BUFFER_ENDS:
          /* FIXME: Can do better here */
-         new_pos = count < 0 ? 0 : strlen (label->text);
+         new_pos = count < 0 ? 0 : strlen (priv->text);
          break;
        case GTK_MOVEMENT_DISPLAY_LINES:
        case GTK_MOVEMENT_PARAGRAPHS:
@@ -4502,7 +5991,7 @@ gtk_label_move_cursor (GtkLabel       *label,
        case GTK_MOVEMENT_PARAGRAPH_ENDS:
        case GTK_MOVEMENT_BUFFER_ENDS:
          /* FIXME: Can do better here */
-         new_pos = count < 0 ? 0 : strlen (label->text);
+         new_pos = count < 0 ? 0 : strlen (priv->text);
           if (new_pos == old_pos)
             gtk_widget_error_bell (GTK_WIDGET (label));
          break;
@@ -4516,7 +6005,7 @@ gtk_label_move_cursor (GtkLabel       *label,
 
   if (extend_selection)
     gtk_label_select_region_index (label,
-                                  label->select_info->selection_anchor,
+                                  priv->select_info->selection_anchor,
                                   new_pos);
   else
     gtk_label_select_region_index (label, new_pos, new_pos);
@@ -4525,17 +6014,20 @@ gtk_label_move_cursor (GtkLabel       *label,
 static void
 gtk_label_copy_clipboard (GtkLabel *label)
 {
-  if (label->text && label->select_info)
+  GtkLabelPriv *priv = label->priv;
+
+  if (priv->text && priv->select_info)
     {
       gint start, end;
       gint len;
-      
-      start = MIN (label->select_info->selection_anchor,
-                   label->select_info->selection_end);
-      end = MAX (label->select_info->selection_anchor,
-                 label->select_info->selection_end);
+      GtkClipboard *clipboard;
 
-      len = strlen (label->text);
+      start = MIN (priv->select_info->selection_anchor,
+                   priv->select_info->selection_end);
+      end = MAX (priv->select_info->selection_anchor,
+                 priv->select_info->selection_end);
+
+      len = strlen (priv->text);
 
       if (end > len)
         end = len;
@@ -4543,17 +6035,27 @@ gtk_label_copy_clipboard (GtkLabel *label)
       if (start > len)
         start = len;
 
+      clipboard = gtk_widget_get_clipboard (GTK_WIDGET (label), GDK_SELECTION_CLIPBOARD);
+
       if (start != end)
-       gtk_clipboard_set_text (gtk_widget_get_clipboard (GTK_WIDGET (label),
-                                                         GDK_SELECTION_CLIPBOARD),
-                               label->text + start, end - start);
+       gtk_clipboard_set_text (clipboard, priv->text + start, end - start);
+      else
+        {
+          GtkLabelLink *link;
+
+          link = gtk_label_get_focus_link (label);
+          if (link)
+            gtk_clipboard_set_text (clipboard, link->uri, -1);
+        }
     }
 }
 
 static void
 gtk_label_select_all (GtkLabel *label)
 {
-  gtk_label_select_region_index (label, 0, strlen (label->text));
+  GtkLabelPriv *priv = label->priv;
+
+  gtk_label_select_region_index (label, 0, strlen (priv->text));
 }
 
 /* Quick hack of a popup menu
@@ -4589,11 +6091,11 @@ static void
 popup_menu_detach (GtkWidget *attach_widget,
                   GtkMenu   *menu)
 {
-  GtkLabel *label;
-  label = GTK_LABEL (attach_widget);
+  GtkLabel *label = GTK_LABEL (attach_widget);
+  GtkLabelPriv *priv = label->priv;
 
-  if (label->select_info)
-    label->select_info->popup_menu = NULL;
+  if (priv->select_info)
+    priv->select_info->popup_menu = NULL;
 }
 
 static void
@@ -4607,20 +6109,20 @@ popup_position_func (GtkMenu   *menu,
   GtkWidget *widget;
   GtkRequisition req;
   GdkScreen *screen;
-  
-  label = GTK_LABEL (user_data);  
+
+  label = GTK_LABEL (user_data);
   widget = GTK_WIDGET (label);
 
-  if (label->select_info == NULL)
-    return;
-  
-  g_return_if_fail (GTK_WIDGET_REALIZED (label));
-  
+  g_return_if_fail (gtk_widget_get_realized (widget));
+
   screen = gtk_widget_get_screen (widget);
-  gdk_window_get_origin (widget->window, x, y);      
+  gdk_window_get_origin (widget->window, x, y);
+
+  *x += widget->allocation.x;
+  *y += widget->allocation.y;
+
+  gtk_widget_size_request (GTK_WIDGET (menu), &req);
 
-  gtk_widget_size_request (label->select_info->popup_menu, &req);
-  
   *x += widget->allocation.width / 2;
   *y += widget->allocation.height;
 
@@ -4628,66 +6130,418 @@ popup_position_func (GtkMenu   *menu,
   *y = CLAMP (*y, 0, MAX (0, gdk_screen_get_height (screen) - req.height));
 }
 
+static void
+open_link_activate_cb (GtkMenuItem *menu_item,
+                       GtkLabel    *label)
+{
+  GtkLabelLink *link;
+
+  link = gtk_label_get_current_link (label);
+
+  if (link)
+    emit_activate_link (label, link);
+}
+
+static void
+copy_link_activate_cb (GtkMenuItem *menu_item,
+                       GtkLabel    *label)
+{
+  GtkClipboard *clipboard;
+  const gchar *uri;
+
+  uri = gtk_label_get_current_uri (label);
+  if (uri)
+    {
+      clipboard = gtk_widget_get_clipboard (GTK_WIDGET (label), GDK_SELECTION_CLIPBOARD);
+      gtk_clipboard_set_text (clipboard, uri, -1);
+    }
+}
+
+static gboolean
+gtk_label_popup_menu (GtkWidget *widget)
+{
+  gtk_label_do_popup (GTK_LABEL (widget), NULL);
+
+  return TRUE;
+}
 
 static void
 gtk_label_do_popup (GtkLabel       *label,
                     GdkEventButton *event)
 {
+  GtkLabelPriv *priv = label->priv;
   GtkWidget *menuitem;
+  GtkWidget *menu;
+  GtkWidget *image;
   gboolean have_selection;
+  GtkLabelLink *link;
 
-  if (label->select_info == NULL)
+  if (!priv->select_info)
     return;
-    
-  if (label->select_info->popup_menu)
-    gtk_widget_destroy (label->select_info->popup_menu);
-  
-  label->select_info->popup_menu = gtk_menu_new ();
 
-  gtk_menu_attach_to_widget (GTK_MENU (label->select_info->popup_menu),
-                             GTK_WIDGET (label),
-                             popup_menu_detach);
+  if (priv->select_info->popup_menu)
+    gtk_widget_destroy (priv->select_info->popup_menu);
+
+  priv->select_info->popup_menu = menu = gtk_menu_new ();
+
+  gtk_menu_attach_to_widget (GTK_MENU (menu), GTK_WIDGET (label), popup_menu_detach);
 
   have_selection =
-    label->select_info->selection_anchor != label->select_info->selection_end;
+    priv->select_info->selection_anchor != priv->select_info->selection_end;
+
+  if (event)
+    {
+      if (priv->select_info->link_clicked)
+        link = priv->select_info->active_link;
+      else
+        link = NULL;
+    }
+  else
+    link = gtk_label_get_focus_link (label);
 
+  if (!have_selection && link)
+    {
+      /* Open Link */
+      menuitem = gtk_image_menu_item_new_with_mnemonic (_("_Open Link"));
+      gtk_widget_show (menuitem);
+      gtk_menu_shell_append (GTK_MENU_SHELL (menu), menuitem);
 
-  append_action_signal (label, label->select_info->popup_menu, GTK_STOCK_CUT, "cut-clipboard",
-                        FALSE);
-  append_action_signal (label, label->select_info->popup_menu, GTK_STOCK_COPY, "copy-clipboard",
-                        have_selection);
-  append_action_signal (label, label->select_info->popup_menu, GTK_STOCK_PASTE, "paste-clipboard",
-                        FALSE);
-  
-  menuitem = gtk_image_menu_item_new_from_stock (GTK_STOCK_DELETE, NULL);
-  gtk_widget_set_sensitive (menuitem, FALSE);
-  gtk_widget_show (menuitem);
-  gtk_menu_shell_append (GTK_MENU_SHELL (label->select_info->popup_menu), menuitem);
+      g_signal_connect (G_OBJECT (menuitem), "activate",
+                        G_CALLBACK (open_link_activate_cb), label);
 
-  menuitem = gtk_separator_menu_item_new ();
-  gtk_widget_show (menuitem);
-  gtk_menu_shell_append (GTK_MENU_SHELL (label->select_info->popup_menu), menuitem);
-      
-  menuitem = gtk_image_menu_item_new_from_stock (GTK_STOCK_SELECT_ALL, NULL);
-  g_signal_connect_swapped (menuitem, "activate",
-                           G_CALLBACK (gtk_label_select_all), label);
-  gtk_widget_show (menuitem);
-  gtk_menu_shell_append (GTK_MENU_SHELL (label->select_info->popup_menu), menuitem);
+      image = gtk_image_new_from_stock (GTK_STOCK_JUMP_TO, GTK_ICON_SIZE_MENU);
+      gtk_widget_show (image);
+      gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (menuitem), image);
+
+      /* Copy Link Address */
+      menuitem = gtk_image_menu_item_new_with_mnemonic (_("Copy _Link Address"));
+      gtk_widget_show (menuitem);
+      gtk_menu_shell_append (GTK_MENU_SHELL (menu), menuitem);
 
-  g_signal_emit (label,
-                signals[POPULATE_POPUP],
-                0,
-                label->select_info->popup_menu);
+      g_signal_connect (G_OBJECT (menuitem), "activate",
+                        G_CALLBACK (copy_link_activate_cb), label);
+
+      image = gtk_image_new_from_stock (GTK_STOCK_COPY, GTK_ICON_SIZE_MENU);
+      gtk_widget_show (image);
+      gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (menuitem), image);
+    }
+  else
+    {
+      append_action_signal (label, menu, GTK_STOCK_CUT, "cut-clipboard", FALSE);
+      append_action_signal (label, menu, GTK_STOCK_COPY, "copy-clipboard", have_selection);
+      append_action_signal (label, menu, GTK_STOCK_PASTE, "paste-clipboard", FALSE);
   
+      menuitem = gtk_image_menu_item_new_from_stock (GTK_STOCK_DELETE, NULL);
+      gtk_widget_set_sensitive (menuitem, FALSE);
+      gtk_widget_show (menuitem);
+      gtk_menu_shell_append (GTK_MENU_SHELL (menu), menuitem);
+
+      menuitem = gtk_separator_menu_item_new ();
+      gtk_widget_show (menuitem);
+      gtk_menu_shell_append (GTK_MENU_SHELL (menu), menuitem);
+
+      menuitem = gtk_image_menu_item_new_from_stock (GTK_STOCK_SELECT_ALL, NULL);
+      g_signal_connect_swapped (menuitem, "activate",
+                               G_CALLBACK (gtk_label_select_all), label);
+      gtk_widget_show (menuitem);
+      gtk_menu_shell_append (GTK_MENU_SHELL (menu), menuitem);
+    }
+
+  g_signal_emit (label, signals[POPULATE_POPUP], 0, menu);
+
   if (event)
-    gtk_menu_popup (GTK_MENU (label->select_info->popup_menu), NULL, NULL,
+    gtk_menu_popup (GTK_MENU (menu), NULL, NULL,
                     NULL, NULL,
                     event->button, event->time);
   else
-    gtk_menu_popup (GTK_MENU (label->select_info->popup_menu), NULL, NULL,
-                    popup_position_func, label,
-                    0, gtk_get_current_event_time ());
+    {
+      gtk_menu_popup (GTK_MENU (menu), NULL, NULL,
+                      popup_position_func, label,
+                      0, gtk_get_current_event_time ());
+      gtk_menu_shell_select_first (GTK_MENU_SHELL (menu), FALSE);
+    }
+}
+
+static void
+gtk_label_clear_links (GtkLabel *label)
+{
+  GtkLabelPriv *priv = label->priv;
+
+  if (!priv->select_info)
+    return;
+
+  g_list_foreach (priv->select_info->links, (GFunc)link_free, NULL);
+  g_list_free (priv->select_info->links);
+  priv->select_info->links = NULL;
+  priv->select_info->active_link = NULL;
+}
+
+static void
+gtk_label_rescan_links (GtkLabel *label)
+{
+  GtkLabelPriv *priv = label->priv;
+  PangoLayout *layout = priv->layout;
+  PangoAttrList *attlist;
+  PangoAttrIterator *iter;
+  GList *links;
+
+  if (!priv->select_info || !priv->select_info->links)
+    return;
+
+  attlist = pango_layout_get_attributes (layout);
+
+  if (attlist == NULL)
+    return;
+
+  iter = pango_attr_list_get_iterator (attlist);
+
+  links = priv->select_info->links;
+
+  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;
+          GtkLabelLink *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);
+
+          if (links == NULL)
+            {
+              g_warning ("Ran out of links");
+              break;
+            }
+          link = links->data;
+          links = links->next;
+          link->start = start;
+          link->end = end;
+        }
+      } while (pango_attr_iterator_next (iter));
+
+    pango_attr_iterator_destroy (iter);
+}
+
+static gboolean
+gtk_label_activate_link (GtkLabel    *label,
+                         const gchar *uri)
+{
+  GtkWidget *widget = GTK_WIDGET (label);
+  GError *error = NULL;
+
+  if (!gtk_show_uri (gtk_widget_get_screen (widget),
+                     uri, gtk_get_current_event_time (), &error))
+    {
+      g_warning ("Unable to show '%s': %s", uri, error->message);
+      g_error_free (error);
+    }
+
+  return TRUE;
+}
+
+static void
+emit_activate_link (GtkLabel     *label,
+                    GtkLabelLink *link)
+{
+  GtkLabelPriv *priv = label->priv;
+  gboolean handled;
+
+  g_signal_emit (label, signals[ACTIVATE_LINK], 0, link->uri, &handled);
+  if (handled && priv->track_links && !link->visited)
+    {
+      link->visited = TRUE;
+      /* FIXME: shouldn't have to redo everything here */
+      gtk_label_recalculate (label);
+    }
+}
+
+static void
+gtk_label_activate_current_link (GtkLabel *label)
+{
+  GtkLabelLink *link;
+  GtkWidget *widget = GTK_WIDGET (label);
+
+  link = gtk_label_get_focus_link (label);
+
+  if (link)
+    {
+      emit_activate_link (label, link);
+    }
+  else
+    {
+      GtkWidget *toplevel;
+      GtkWindow *window;
+
+      toplevel = gtk_widget_get_toplevel (widget);
+      if (GTK_IS_WINDOW (toplevel))
+        {
+          window = GTK_WINDOW (toplevel);
+
+          if (window &&
+              window->default_widget != widget &&
+              !(widget == window->focus_widget &&
+                (!window->default_widget || !gtk_widget_is_sensitive (window->default_widget))))
+            gtk_window_activate_default (window);
+        }
+    }
+}
+
+static GtkLabelLink *
+gtk_label_get_current_link (GtkLabel *label)
+{
+  GtkLabelPriv *priv = label->priv;
+  GtkLabelLink *link;
+
+  if (!priv->select_info)
+    return NULL;
+
+  if (priv->select_info->link_clicked)
+    link = priv->select_info->active_link;
+  else
+    link = gtk_label_get_focus_link (label);
+
+  return link;
+}
+
+/**
+ * gtk_label_get_current_uri:
+ * @label: a #GtkLabel
+ *
+ * Returns the URI for the currently active link in the label.
+ * The active link is the one under the mouse pointer or, in a
+ * selectable label, the link in which the text cursor is currently
+ * positioned.
+ *
+ * This function is intended for use in a #GtkLabel::activate-link handler
+ * or for use in a #GtkWidget::query-tooltip handler.
+ *
+ * Returns: the currently active URI. The string is owned by GTK+ and must
+ *   not be freed or modified.
+ *
+ * Since: 2.18
+ */
+G_CONST_RETURN gchar *
+gtk_label_get_current_uri (GtkLabel *label)
+{
+  GtkLabelLink *link;
+
+  g_return_val_if_fail (GTK_IS_LABEL (label), NULL);
+
+  link = gtk_label_get_current_link (label);
+
+  if (link)
+    return link->uri;
+
+  return NULL;
+}
+
+/**
+ * gtk_label_set_track_visited_links:
+ * @label: a #GtkLabel
+ * @track_links: %TRUE to track visited links
+ *
+ * Sets whether the label should keep track of clicked
+ * links (and use a different color for them).
+ *
+ * Since: 2.18
+ */
+void
+gtk_label_set_track_visited_links (GtkLabel *label,
+                                   gboolean  track_links)
+{
+  GtkLabelPriv *priv;
+
+  g_return_if_fail (GTK_IS_LABEL (label));
+
+  priv = label->priv;
+
+  track_links = track_links != FALSE;
+
+  if (priv->track_links != track_links)
+    {
+      priv->track_links = track_links;
+
+      /* FIXME: shouldn't have to redo everything here */
+      gtk_label_recalculate (label);
+
+      g_object_notify (G_OBJECT (label), "track-visited-links");
+    }
+}
+
+/**
+ * gtk_label_get_track_visited_links:
+ * @label: a #GtkLabel
+ *
+ * Returns whether the label is currently keeping track
+ * of clicked links.
+ *
+ * Returns: %TRUE if clicked links are remembered
+ *
+ * Since: 2.18
+ */
+gboolean
+gtk_label_get_track_visited_links (GtkLabel *label)
+{
+  g_return_val_if_fail (GTK_IS_LABEL (label), FALSE);
+
+  return label->priv->track_links;
 }
 
-#define __GTK_LABEL_C__
-#include "gtkaliasdef.c"
+static gboolean
+gtk_label_query_tooltip (GtkWidget  *widget,
+                         gint        x,
+                         gint        y,
+                         gboolean    keyboard_tip,
+                         GtkTooltip *tooltip)
+{
+  GtkLabel *label = GTK_LABEL (widget);
+  GtkLabelPriv *priv = label->priv;
+  GtkLabelSelectionInfo *info = priv->select_info;
+  gint index = -1;
+  GList *l;
+
+  if (info && info->links)
+    {
+      if (keyboard_tip)
+        {
+          if (info->selection_anchor == info->selection_end)
+            index = info->selection_anchor;
+        }
+      else
+        {
+          if (!get_layout_index (label, x, y, &index))
+            index = -1;
+        }
+
+      if (index != -1)
+        {
+          for (l = info->links; l != NULL; l = l->next)
+            {
+              GtkLabelLink *link = l->data;
+              if (index >= link->start && index <= link->end)
+                {
+                  if (link->title)
+                    {
+                      gtk_tooltip_set_markup (tooltip, link->title);
+                      return TRUE;
+                    }
+                  break;
+                }
+            }
+        }
+    }
+
+  return GTK_WIDGET_CLASS (gtk_label_parent_class)->query_tooltip (widget,
+                                                                   x, y,
+                                                                   keyboard_tip,
+                                                                   tooltip);
+}