[gtk+] label: Use gestures for event management



commit 8dcdec58ebf149b295d03226dca5103d668d30dd
Author: Carlos Garnacho <carlosg gnome org>
Date:   Thu Jun 5 15:36:15 2014 +0200

    label: Use gestures for event management
    
    A multipress gesture takes care of link handling, and char/word/all
    selection mode on selectable labels. A drag gesture is used for both
    text selection and DnD checks on selectable labels.

 gtk/gtklabel.c |  403 +++++++++++++++++++++++++++++++++-----------------------
 1 files changed, 239 insertions(+), 164 deletions(-)
---
diff --git a/gtk/gtklabel.c b/gtk/gtklabel.c
index b3ed083..1617578 100644
--- a/gtk/gtklabel.c
+++ b/gtk/gtklabel.c
@@ -229,6 +229,9 @@ struct _GtkLabelPrivate
   PangoAttrList *markup_attrs;
   PangoLayout   *layout;
 
+  GtkGesture    *drag_gesture;
+  GtkGesture    *multipress_gesture;
+
   gchar   *label;
   gchar   *text;
 
@@ -375,10 +378,6 @@ static void gtk_label_unrealize         (GtkWidget        *widget);
 static void gtk_label_map               (GtkWidget        *widget);
 static void gtk_label_unmap             (GtkWidget        *widget);
 
-static gboolean gtk_label_button_press      (GtkWidget        *widget,
-                                            GdkEventButton   *event);
-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,
@@ -482,6 +481,26 @@ static GtkLabelLink *gtk_label_get_current_link (GtkLabel  *label);
 static void          emit_activate_link         (GtkLabel     *label,
                                                  GtkLabelLink *link);
 
+/* Event controller callbacks */
+static void   gtk_label_multipress_gesture_pressed  (GtkGestureMultiPress *gesture,
+                                                     gint                  n_press,
+                                                     gdouble               x,
+                                                     gdouble               y,
+                                                     GtkLabel             *label);
+static void   gtk_label_multipress_gesture_released (GtkGestureMultiPress *gesture,
+                                                     gint                  n_press,
+                                                     gdouble               x,
+                                                     gdouble               y,
+                                                     GtkLabel             *label);
+static void   gtk_label_drag_gesture_begin          (GtkGestureDrag *gesture,
+                                                     gdouble         start_x,
+                                                     gdouble         start_y,
+                                                     GtkLabel       *label);
+static void   gtk_label_drag_gesture_update         (GtkGestureDrag *gesture,
+                                                     gdouble         offset_x,
+                                                     gdouble         offset_y,
+                                                     GtkLabel       *label);
+
 static GtkSizeRequestMode gtk_label_get_request_mode                (GtkWidget           *widget);
 static void               gtk_label_get_preferred_width             (GtkWidget           *widget,
                                                                      gint                *minimum_size,
@@ -557,8 +576,6 @@ gtk_label_class_init (GtkLabelClass *class)
   widget_class->unrealize = gtk_label_unrealize;
   widget_class->map = gtk_label_map;
   widget_class->unmap = gtk_label_unmap;
-  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;
@@ -1258,6 +1275,27 @@ gtk_label_init (GtkLabel *label)
   priv->mnemonics_visible = TRUE;
 
   gtk_label_set_text (label, "");
+
+  priv->drag_gesture = gtk_gesture_drag_new (GTK_WIDGET (label));
+  g_signal_connect (priv->drag_gesture, "drag-begin",
+                    G_CALLBACK (gtk_label_drag_gesture_begin), label);
+  g_signal_connect (priv->drag_gesture, "drag-update",
+                    G_CALLBACK (gtk_label_drag_gesture_update), label);
+  gtk_gesture_single_set_touch_only (GTK_GESTURE_SINGLE (priv->drag_gesture), FALSE);
+  gtk_gesture_single_set_exclusive (GTK_GESTURE_SINGLE (priv->drag_gesture), TRUE);
+  gtk_gesture_single_set_button (GTK_GESTURE_SINGLE (priv->drag_gesture), GDK_BUTTON_PRIMARY);
+  gtk_event_controller_set_propagation_phase (GTK_EVENT_CONTROLLER (priv->drag_gesture),
+                                              GTK_PHASE_BUBBLE);
+
+  priv->multipress_gesture = gtk_gesture_multi_press_new (GTK_WIDGET (label));
+  g_signal_connect (priv->multipress_gesture, "pressed",
+                    G_CALLBACK (gtk_label_multipress_gesture_pressed), label);
+  g_signal_connect (priv->multipress_gesture, "released",
+                    G_CALLBACK (gtk_label_multipress_gesture_released), label);
+  gtk_gesture_single_set_touch_only (GTK_GESTURE_SINGLE (priv->multipress_gesture), FALSE);
+  gtk_gesture_single_set_exclusive (GTK_GESTURE_SINGLE (priv->multipress_gesture), TRUE);
+  gtk_event_controller_set_propagation_phase (GTK_EVENT_CONTROLLER (priv->multipress_gesture),
+                                              GTK_PHASE_BUBBLE);
 }
 
 
@@ -3132,6 +3170,9 @@ gtk_label_finalize (GObject *object)
   gtk_label_clear_links (label);
   g_free (priv->select_info);
 
+  g_object_unref (priv->drag_gesture);
+  g_object_unref (priv->multipress_gesture);
+
   G_OBJECT_CLASS (gtk_label_parent_class)->finalize (object);
 }
 
@@ -4665,49 +4706,61 @@ out:
   return FALSE;
 }
 
-static gboolean
-gtk_label_button_press (GtkWidget      *widget,
-                        GdkEventButton *event)
+static void
+gtk_label_multipress_gesture_pressed (GtkGestureMultiPress *gesture,
+                                      gint                  n_press,
+                                      gdouble               widget_x,
+                                      gdouble               widget_y,
+                                      GtkLabel             *label)
 {
-  GtkLabel *label = GTK_LABEL (widget);
   GtkLabelPrivate *priv = label->priv;
   GtkLabelSelectionInfo *info = priv->select_info;
-  gint index = 0;
-  gint min, max;
+  GtkWidget *widget = GTK_WIDGET (label);
+  GdkEventSequence *sequence;
+  const GdkEvent *event;
+  guint button;
 
   if (info == NULL)
-    return FALSE;
+    {
+      gtk_gesture_set_state (GTK_GESTURE (gesture), GTK_EVENT_SEQUENCE_DENIED);
+      return;
+    }
+
+  button = gtk_gesture_single_get_current_button (GTK_GESTURE_SINGLE (gesture));
+  sequence = gtk_gesture_single_get_current_sequence (GTK_GESTURE_SINGLE (gesture));
+  event = gtk_gesture_get_last_event (GTK_GESTURE (gesture), sequence);
+
+  gtk_gesture_set_state (GTK_GESTURE (gesture), GTK_EVENT_SEQUENCE_CLAIMED);
 
   if (info->active_link)
     {
-      if (gdk_event_triggers_context_menu ((GdkEvent *) event))
+      if (gdk_event_triggers_context_menu (event))
         {
           info->link_clicked = 1;
-          gtk_label_do_popup (label, event);
-          return TRUE;
+          gtk_label_do_popup (label, (GdkEventButton*) event);
+          return;
         }
-      else if (event->button == GDK_BUTTON_PRIMARY)
+      else if (button == GDK_BUTTON_PRIMARY)
         {
           info->link_clicked = 1;
           gtk_widget_queue_draw (widget);
           if (!info->selectable)
-            return TRUE;
+            return;
         }
     }
 
   if (!info->selectable)
-    return FALSE;
+    {
+      gtk_gesture_set_state (GTK_GESTURE (gesture), GTK_EVENT_SEQUENCE_DENIED);
+      return;
+    }
 
   info->in_drag = FALSE;
   info->select_words = FALSE;
 
   if (gdk_event_triggers_context_menu ((GdkEvent *) event))
-    {
-      gtk_label_do_popup (label, event);
-
-      return TRUE;
-    }
-  else if (event->button == GDK_BUTTON_PRIMARY)
+    gtk_label_do_popup (label, (GdkEventButton*) event);
+  else if (button == GDK_BUTTON_PRIMARY)
     {
       if (!gtk_widget_has_focus (widget))
         {
@@ -4716,118 +4769,55 @@ gtk_label_button_press (GtkWidget      *widget,
           priv->in_click = FALSE;
         }
 
-      if (event->type == GDK_3BUTTON_PRESS)
-        {
-          gtk_label_select_region_index (label, 0, strlen (priv->text));
-          return TRUE;
-        }
-
-      if (event->type == GDK_2BUTTON_PRESS)
+      if (n_press == 3)
+        gtk_label_select_region_index (label, 0, strlen (priv->text));
+      else if (n_press == 2)
         {
           info->select_words = TRUE;
           gtk_label_select_word (label);
-          return TRUE;
-        }
-
-      get_layout_index (label, event->x, event->y, &index);
-
-      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))
-        {
-          if (index > min && index < max)
-            {
-              /* truncate selection, but keep it as big as possible */
-              if (index - min > max - index)
-                max = index;
-              else
-                min = index;
-            }
-          else
-            {
-              /* extend (same as motion) */
-              min = MIN (min, index);
-              max = MAX (max, index);
-            }
-
-          /* ensure the anchor is opposite index */
-          if (index == min)
-            {
-              gint tmp = min;
-              min = max;
-              max = tmp;
-            }
-
-          gtk_label_select_region_index (label, min, max);
-        }
-      else
-        {
-          if (event->type == GDK_3BUTTON_PRESS)
-            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)
-            {
-              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;
     }
 
-  return FALSE;
+  if (n_press >= 3)
+    gtk_event_controller_reset (GTK_EVENT_CONTROLLER (gesture));
 }
 
-static gboolean
-gtk_label_button_release (GtkWidget      *widget,
-                          GdkEventButton *event)
-
+static void
+gtk_label_multipress_gesture_released (GtkGestureMultiPress *gesture,
+                                       gint                  n_press,
+                                       gdouble               x,
+                                       gdouble               y,
+                                       GtkLabel             *label)
 {
-  GtkLabel *label = GTK_LABEL (widget);
   GtkLabelPrivate *priv = label->priv;
   GtkLabelSelectionInfo *info = priv->select_info;
+  GdkEventSequence *sequence;
   gint index;
 
   if (info == NULL)
-    return FALSE;
+    return;
+
+  sequence = gtk_gesture_single_get_current_sequence (GTK_GESTURE_SINGLE (gesture));
+
+  if (!gtk_gesture_handles_sequence (GTK_GESTURE (gesture), sequence))
+    return;
+
+  if (n_press != 1)
+    return;
 
   if (info->in_drag)
     {
       info->in_drag = 0;
-
-      get_layout_index (label, event->x, event->y, &index);
+      get_layout_index (label, x, y, &index);
       gtk_label_select_region_index (label, index, index);
-
-      return FALSE;
     }
-
-  if (event->button != GDK_BUTTON_PRIMARY)
-    return FALSE;
-
-  if (info->active_link &&
-      info->selection_anchor == info->selection_end &&
-      info->link_clicked)
+  else 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.
-   */
-  if (info->selectable)
-    return TRUE;
-
-  return FALSE;
 }
 
 static void
@@ -4909,84 +4899,112 @@ drag_begin_cb (GtkWidget      *widget,
     }
 }
 
-static gboolean
-gtk_label_motion (GtkWidget      *widget,
-                  GdkEventMotion *event)
+static void
+gtk_label_drag_gesture_begin (GtkGestureDrag *gesture,
+                              gdouble         start_x,
+                              gdouble         start_y,
+                              GtkLabel       *label)
 {
-  GtkLabel *label = GTK_LABEL (widget);
   GtkLabelPrivate *priv = label->priv;
   GtkLabelSelectionInfo *info = priv->select_info;
-  gint index;
-
-  if (info == NULL)
-    return FALSE;
+  GdkModifierType state_mask;
+  GdkEventSequence *sequence;
+  const GdkEvent *event;
+  gint min, max, index;
 
-  if (info->links && !info->in_drag)
+  if (!info)
     {
-      GList *l;
-      GtkLabelLink *link;
-      gboolean found = FALSE;
+      gtk_gesture_set_state (GTK_GESTURE (gesture), GTK_EVENT_SEQUENCE_DENIED);
+      return;
+    }
 
-      if (info->selection_anchor == info->selection_end)
+  get_layout_index (label, start_x, start_y, &index);
+  min = MIN (info->selection_anchor, info->selection_end);
+  max = MAX (info->selection_anchor, info->selection_end);
+
+  sequence = gtk_gesture_single_get_current_sequence (GTK_GESTURE_SINGLE (gesture));
+  event = gtk_gesture_get_last_event (GTK_GESTURE (gesture), sequence);
+  gdk_event_get_state (event, &state_mask);
+
+  if ((info->selection_anchor != info->selection_end) &&
+      (state_mask & GDK_SHIFT_MASK))
+    {
+      if (index > min && index < max)
         {
-          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;
-                    }
-                }
-            }
+          /* truncate selection, but keep it as big as possible */
+          if (index - min > max - index)
+            max = index;
+          else
+            min = index;
+        }
+      else
+        {
+          /* extend (same as motion) */
+          min = MIN (min, index);
+          max = MAX (max, index);
         }
 
-      if (found)
+      /* ensure the anchor is opposite index */
+      if (index == min)
         {
-          if (info->active_link != link)
-            {
-              info->link_clicked = 0;
-              info->active_link = link;
-              gtk_label_update_cursor (label);
-              gtk_widget_queue_draw (widget);
-            }
+          gint tmp = min;
+          min = max;
+          max = tmp;
         }
-      else
+
+      gtk_label_select_region_index (label, min, max);
+    }
+  else
+    {
+      if (min < max && min <= index && index <= max)
         {
-          if (info->active_link != NULL)
-            {
-              info->link_clicked = 0;
-              info->active_link = NULL;
-              gtk_label_update_cursor (label);
-              gtk_widget_queue_draw (widget);
-            }
+          info->in_drag = TRUE;
+          info->drag_start_x = start_x;
+          info->drag_start_y = start_y;
         }
+      else
+        /* start a replacement */
+        gtk_label_select_region_index (label, index, index);
     }
+}
 
-  if (!info->selectable)
-    return FALSE;
+static void
+gtk_label_drag_gesture_update (GtkGestureDrag *gesture,
+                               gdouble         offset_x,
+                               gdouble         offset_y,
+                               GtkLabel       *label)
+{
+  GtkLabelPrivate *priv = label->priv;
+  GtkLabelSelectionInfo *info = priv->select_info;
+  GtkWidget *widget = GTK_WIDGET (label);
+  GdkEventSequence *sequence;
+  gdouble x, y;
+  gint index;
 
-  if ((event->state & GDK_BUTTON1_MASK) == 0)
-    return FALSE;
+  if (info == NULL || !info->selectable)
+    return;
+
+  sequence = gtk_gesture_single_get_current_sequence (GTK_GESTURE_SINGLE (gesture));
+  gtk_gesture_get_point (GTK_GESTURE (gesture), sequence, &x, &y);
 
   if (info->in_drag)
     {
       if (gtk_drag_check_threshold (widget,
                                    info->drag_start_x,
                                    info->drag_start_y,
-                                   event->x, event->y))
+                                   x, y))
        {
          GtkTargetList *target_list = gtk_target_list_new (NULL, 0);
+          const GdkEvent *event;
 
+          event = gtk_gesture_get_last_event (GTK_GESTURE (gesture), sequence);
          gtk_target_list_add_text_targets (target_list, 0);
 
           g_signal_connect (widget, "drag-begin",
                             G_CALLBACK (drag_begin_cb), NULL);
          gtk_drag_begin_with_coordinates (widget, target_list,
                                            GDK_ACTION_COPY,
-                                           1, (GdkEvent *)event,
+                                           1, (GdkEvent*) event,
                                            info->drag_start_x,
                                            info->drag_start_y);
 
@@ -4997,11 +5015,11 @@ gtk_label_motion (GtkWidget      *widget,
     }
   else
     {
-      gint x, y;
-
-      gdk_window_get_device_position (info->window, event->device, &x, &y, NULL);
       get_layout_index (label, x, y, &index);
 
+      if (index != info->selection_anchor)
+        gtk_gesture_set_state (GTK_GESTURE (gesture), GTK_EVENT_SEQUENCE_CLAIMED);
+
       if (info->select_words)
         {
           gint min, max;
@@ -5043,8 +5061,65 @@ gtk_label_motion (GtkWidget      *widget,
       else
         gtk_label_select_region_index (label, info->selection_anchor, index);
     }
+}
 
-  return TRUE;
+static gboolean
+gtk_label_motion (GtkWidget      *widget,
+                  GdkEventMotion *event)
+{
+  GtkLabel *label = GTK_LABEL (widget);
+  GtkLabelPrivate *priv = label->priv;
+  GtkLabelSelectionInfo *info = priv->select_info;
+  gint index;
+
+  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);
+            }
+        }
+    }
+
+  return GTK_WIDGET_CLASS (gtk_label_parent_class)->motion_notify_event (widget, event);
 }
 
 static gboolean


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