]> Pileus Git - ~andy/gtk/blobdiff - gtk/gtklabel.c
Deprecate flag macros for toplevel, state, no window and composite child
[~andy/gtk] / gtk / gtklabel.c
index 22fc6fb6383b689bf94d28cf5148af1f54a0afa0..98c77b04e9f31a75e8863a56aa0492852796de0a 100644 (file)
@@ -58,6 +58,8 @@ typedef struct
   gint wrap_width;
   gint width_chars;
   gint max_width_chars;
+
+  gboolean mnemonics_visible;
 } GtkLabelPrivate;
 
 /* Notes about the handling of links:
@@ -124,6 +126,7 @@ enum {
   COPY_CLIPBOARD,
   POPULATE_POPUP,
   ACTIVATE_LINK,
+  ACTIVATE_CURRENT_LINK,
   LAST_SIGNAL
 };
 
@@ -146,7 +149,8 @@ enum {
   PROP_WIDTH_CHARS,
   PROP_SINGLE_LINE_MODE,
   PROP_ANGLE,
-  PROP_MAX_WIDTH_CHARS
+  PROP_MAX_WIDTH_CHARS,
+  PROP_TRACK_VISITED_LINKS
 };
 
 static guint signals[LAST_SIGNAL] = { 0 };
@@ -214,7 +218,8 @@ 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);
+                                                 const gchar   *pattern,
+                                                  gboolean       is_mnemonic);
 static void gtk_label_set_markup_internal        (GtkLabel      *label,
                                                  const gchar   *str,
                                                  gboolean       with_uline);
@@ -262,6 +267,13 @@ static void     gtk_label_buildable_custom_finished    (GtkBuildable     *builda
                                                        gpointer          user_data);
 
 
+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,
@@ -279,11 +291,15 @@ static gint gtk_label_move_backward_word (GtkLabel        *label,
 /* 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);
+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 GQuark quark_angle = 0;
 
@@ -440,21 +456,37 @@ gtk_label_class_init (GtkLabelClass *class)
                  GTK_TYPE_MENU);
 
     /**
-     * GtkLabel::activate-link:
-     * @label: The label on which the signal was emitted.
+     * GtkLabel::activate-current-link:
+     * @label: The label on which the signal was emitted
      *
-     * A  <link linkend="keybinding-signals">keybinding signal</link>
+     * A <link linkend="keybinding-signals">keybinding signal</link>
      * which gets emitted when the user activates a link in the label.
      *
-     * Applications may connect to it to override the default behaviour,
-     * which is to call gtk_show_uri(). To obtain the URI that is being
-     * activated, use gtk_label_get_current_uri().
-     *
      * 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
@@ -462,11 +494,11 @@ gtk_label_class_init (GtkLabelClass *class)
     signals[ACTIVATE_LINK] =
       g_signal_new ("activate-link",
                     G_TYPE_FROM_CLASS (object_class),
-                    G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
+                    G_SIGNAL_RUN_LAST,
                     G_STRUCT_OFFSET (GtkLabelClass, activate_link),
                     _gtk_boolean_handled_accumulator, NULL,
-                    _gtk_marshal_BOOLEAN__VOID,
-                    G_TYPE_BOOLEAN, 0);
+                    _gtk_marshal_BOOLEAN__STRING,
+                    G_TYPE_BOOLEAN, 1, G_TYPE_STRING);
 
   g_object_class_install_property (gobject_class,
                                    PROP_LABEL,
@@ -689,6 +721,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
    */
@@ -799,11 +848,11 @@ gtk_label_class_init (GtkLabelClass *class)
                                "copy-clipboard", 0);
 
   gtk_binding_entry_add_signal (binding_set, GDK_Return, 0,
-                               "activate-link", 0);
+                               "activate-current-link", 0);
   gtk_binding_entry_add_signal (binding_set, GDK_ISO_Enter, 0,
-                               "activate-link", 0);
+                               "activate-current-link", 0);
   gtk_binding_entry_add_signal (binding_set, GDK_KP_Enter, 0,
-                               "activate-link", 0);
+                               "activate-current-link", 0);
 
   gtk_settings_install_property (g_param_spec_boolean ("gtk-label-select-on-focus",
                                                       P_("Select on focus"),
@@ -872,6 +921,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;
@@ -955,7 +1007,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;
@@ -983,7 +1037,8 @@ gtk_label_init (GtkLabel *label)
   label->use_underline = FALSE;
   label->use_markup = FALSE;
   label->pattern_set = FALSE;
-  
+  label->track_links = TRUE;
+
   label->mnemonic_keyval = GDK_VoidSymbol;
   label->layout = NULL;
   label->text = NULL;
@@ -991,7 +1046,9 @@ gtk_label_init (GtkLabel *label)
 
   label->mnemonic_widget = NULL;
   label->mnemonic_window = NULL;
-  
+
+  priv->mnemonics_visible = TRUE;
+
   gtk_label_set_text (label, "");
 }
 
@@ -1441,10 +1498,12 @@ gtk_label_setup_mnemonic (GtkLabel *label,
     }
   
   if (label->mnemonic_keyval == GDK_VoidSymbol)
-    goto done;
+      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;
       
@@ -1519,6 +1578,62 @@ label_shortcut_setting_changed (GtkSettings *settings)
   g_list_free (list);
 }
 
+static void
+mnemonics_visible_apply (GtkWidget *widget,
+                         gboolean   mnemonics_visible)
+{
+  GtkLabel *label;
+  GtkLabelPrivate *priv;
+
+  label = GTK_LABEL (widget);
+
+  priv = GTK_LABEL_GET_PRIVATE (label);
+
+  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)
@@ -1565,7 +1680,7 @@ label_mnemonic_widget_weak_notify (gpointer      data,
 /**
  * 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(),
@@ -1705,20 +1820,23 @@ gtk_label_compose_effective_attrs (GtkLabel *label)
       if (label->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));
+           {
+             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));
+             pango_attr_iterator_destroy (iter);
+           }
        }
       else
-       label->effective_attrs = 
+       label->effective_attrs =
          pango_attr_list_ref (label->attrs);
     }
 }
@@ -1761,11 +1879,7 @@ gtk_label_recalculate (GtkLabel *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;
+         gtk_label_compose_effective_attrs (label);
        }
     }
 
@@ -1966,7 +2080,8 @@ start_element_handler (GMarkupParseContext  *context,
           return;
         }
 
-      if (pdata->label->select_info)
+      visited = FALSE;
+      if (pdata->label->track_links && pdata->label->select_info)
         {
           GList *l;
           for (l = pdata->label->select_info->links; l; l = l->next)
@@ -2190,6 +2305,7 @@ gtk_label_set_markup_internal (GtkLabel    *label,
                                const gchar *str,
                                gboolean     with_uline)
 {
+  GtkLabelPrivate *priv = GTK_LABEL_GET_PRIVATE (label);
   gchar *text = NULL;
   GError *error = NULL;
   PangoAttrList *attrs = NULL;
@@ -2213,6 +2329,35 @@ gtk_label_set_markup_internal (GtkLabel    *label,
       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 (label) &&
+              (!label->mnemonic_widget ||
+               GTK_WIDGET_IS_SENSITIVE (label->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,
@@ -2372,24 +2517,37 @@ 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)
 {
+  GtkLabelPrivate *priv = GTK_LABEL_GET_PRIVATE (label);
   PangoAttrList *attrs;
   gboolean enable_mnemonics;
+  gboolean auto_mnemonics;
 
   g_return_if_fail (GTK_IS_LABEL (label));
 
   if (label->pattern_set)
     return;
 
-  g_object_get (gtk_widget_get_settings (GTK_WIDGET (label)),
-               "gtk-enable-mnemonics", &enable_mnemonics,
-               NULL);
+  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 && pattern)
-    attrs = gtk_label_pattern_to_attrs (label, pattern);
+      if (enable_mnemonics && priv->mnemonics_visible && pattern &&
+          (!auto_mnemonics ||
+           (GTK_WIDGET_IS_SENSITIVE (label) &&
+            (!label->mnemonic_widget ||
+             GTK_WIDGET_IS_SENSITIVE (label->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);
@@ -2406,13 +2564,13 @@ gtk_label_set_pattern (GtkLabel    *label,
 
   if (pattern)
     {
-      gtk_label_set_pattern_internal (label, pattern);
+      gtk_label_set_pattern_internal (label, pattern, FALSE);
       label->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));
 }
 
@@ -3215,9 +3373,26 @@ get_layout_location (GtkLabel  *label,
     x = MIN (x, widget->allocation.x + widget->allocation.width - misc->xpad);
   x -= logical.x;
 
-  y = floor (widget->allocation.y + (gint)misc->ypad 
-             + MAX (((widget->allocation.height - widget->requisition.height) * misc->yalign),
-            0));
+  /* 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 (label->layout) == 1)
+    y = floor (widget->allocation.y + (gint)misc->ypad 
+              + (widget->allocation.height - widget->requisition.height) * misc->yalign);
+  else
+    y = floor (widget->allocation.y + (gint)misc->ypad 
+              + MAX (((widget->allocation.height - widget->requisition.height) * misc->yalign),
+                     0));
 
   if (xp)
     *xp = x;
@@ -3515,37 +3690,27 @@ gtk_label_expose (GtkWidget      *widget,
   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);
+  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;
@@ -3555,12 +3720,13 @@ gtk_label_set_uline_text_internal (GtkLabel    *label,
       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 == '_')
@@ -3568,13 +3734,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
@@ -3588,23 +3754,43 @@ 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);
 
+  return TRUE;
+}
+
+static void
+gtk_label_set_uline_text_internal (GtkLabel    *label,
+                                  const gchar *str)
+{
+  guint accel_key = GDK_VoidSymbol;
+  gchar *new_str;
+  gchar *pattern;
+
+  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;
+
+  gtk_label_set_text_internal (label, new_str);
+  gtk_label_set_pattern_internal (label, pattern, TRUE);
   label->mnemonic_keyval = accel_key;
+
+  g_free (pattern);
 }
 
-guint      
+guint
 gtk_label_parse_uline (GtkLabel    *label,
                       const gchar *str)
 {
@@ -3778,6 +3964,7 @@ get_layout_index (GtkLabel *label,
   gint trailing = 0;
   const gchar *cluster;
   const gchar *cluster_end;
+  gboolean inside;
 
   *index = 0;
 
@@ -3788,24 +3975,21 @@ get_layout_index (GtkLabel *label,
   x *= PANGO_SCALE;
   y *= PANGO_SCALE;
 
-  if (pango_layout_xy_to_index (label->layout,
-                                x, y,
-                                index, &trailing))
-    {
-      cluster = label->text + *index;
-      cluster_end = cluster;
-      while (trailing)
-        {
-          cluster_end = g_utf8_next_char (cluster_end);
-          --trailing;
-        }
-
-      *index += (cluster_end - cluster);
+  inside = pango_layout_xy_to_index (label->layout,
+                                     x, y,
+                                     index, &trailing);
 
-      return TRUE;
+  cluster = label->text + *index;
+  cluster_end = cluster;
+  while (trailing)
+    {
+      cluster_end = g_utf8_next_char (cluster_end);
+      --trailing;
     }
 
-  return FALSE;
+  *index += (cluster_end - cluster);
+
+  return inside;
 }
 
 static void
@@ -4105,16 +4289,7 @@ gtk_label_button_release (GtkWidget      *widget,
       info->selection_anchor == info->selection_end &&
       info->link_clicked)
     {
-      gboolean handled;
-
-      g_signal_emit (label, signals[ACTIVATE_LINK], 0, &handled);
-      if (handled && !info->active_link->visited)
-        {
-          info->active_link->visited = TRUE;
-          /* FIXME: shouldn't have to redo everything here */
-          gtk_label_recalculate (label);
-        }
-
+      emit_activate_link (label, info->active_link);
       info->link_clicked = 0;
 
       return TRUE;
@@ -4126,6 +4301,38 @@ gtk_label_button_release (GtkWidget      *widget,
   return TRUE;
 }
 
+static void
+connect_mnemonics_visible_notify (GtkLabel *label)
+{
+  GtkLabelPrivate *priv = GTK_LABEL_GET_PRIVATE (label);
+  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,
@@ -4798,8 +5005,8 @@ 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)
@@ -5406,20 +5613,11 @@ open_link_activate_cb (GtkMenuItem *menu_item,
                        GtkLabel    *label)
 {
   GtkLabelLink *link;
-  gboolean handled;
 
   link = gtk_label_get_current_link (label);
 
   if (link)
-    {
-      g_signal_emit (label, signals[ACTIVATE_LINK], 0, &handled);
-      if (handled && !link->visited)
-        {
-          link->visited = TRUE;
-          /* FIXME: shouldn't have to redo everything here */
-          gtk_label_recalculate (label);
-        }
-    }
+    emit_activate_link (label, link);
 }
 
 static void
@@ -5561,7 +5759,7 @@ gtk_label_rescan_links (GtkLabel *label)
   PangoAttrIterator *iter;
   GList *links;
 
-  if (!label->select_info)
+  if (!label->select_info || !label->select_info->links)
     return;
 
   attlist = pango_layout_get_attributes (layout);
@@ -5608,25 +5806,48 @@ gtk_label_rescan_links (GtkLabel *label)
 }
 
 static gboolean
-gtk_label_activate_link (GtkLabel *label)
+gtk_label_activate_link (GtkLabel    *label,
+                         const gchar *uri)
 {
   GtkWidget *widget = GTK_WIDGET (label);
-  GtkLabelLink *link;
-  const gchar *uri;
   GError *error = NULL;
 
-  link = gtk_label_get_current_link (label);
+  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);
+    }
 
-  if (link)
+  return TRUE;
+}
+
+static void
+emit_activate_link (GtkLabel     *label,
+                    GtkLabelLink *link)
+{
+  gboolean handled;
+
+  g_signal_emit (label, signals[ACTIVATE_LINK], 0, link->uri, &handled);
+  if (handled && label->track_links && !link->visited)
     {
-      uri = link->uri;
+      link->visited = TRUE;
+      /* FIXME: shouldn't have to redo everything here */
+      gtk_label_recalculate (label);
+    }
+}
 
-      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);
-        }
+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
     {
@@ -5641,12 +5862,10 @@ gtk_label_activate_link (GtkLabel *label)
           if (window &&
               window->default_widget != widget &&
               !(widget == window->focus_widget &&
-                (!window->default_widget || !GTK_WIDGET_SENSITIVE (window->default_widget))))
+                (!window->default_widget || !GTK_WIDGET_IS_SENSITIVE (window->default_widget))))
             gtk_window_activate_default (window);
         }
     }
-
-  return TRUE;
 }
 
 static GtkLabelLink *
@@ -5674,7 +5893,7 @@ gtk_label_get_current_link (GtkLabel *label)
  * selectable label, the link in which the text cursor is currently
  * positioned.
  *
- * This function is intended for use in a #GtkLabel::link-activate handler
+ * 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
@@ -5696,6 +5915,53 @@ gtk_label_get_current_uri (GtkLabel *label)
   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)
+{
+  g_return_if_fail (GTK_IS_LABEL (label));
+
+  track_links = track_links != FALSE;
+
+  if (label->track_links != track_links)
+    {
+      label->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->track_links;
+}
 
 static gboolean
 gtk_label_query_tooltip (GtkWidget  *widget,