[gtk/wip/otte/listview: 964/1040] listview: Implement activation



commit 564330168593155d23ae5e59fba69e7f13fe09b0
Author: Benjamin Otte <otte redhat com>
Date:   Tue Oct 15 23:06:36 2019 +0200

    listview: Implement activation
    
    - a GtkListview::activate signal
    - a GtkListItem::activatable property
    - activate list items on double clicks and <Enter> presses

 gtk/gtklistitem.c | 169 +++++++++++++++++++++++++++++++++++++++++++++++++-----
 gtk/gtklistitem.h |  13 +++--
 gtk/gtklistview.c |  60 +++++++++++++++++++
 gtk/gtktypes.h    |   1 +
 4 files changed, 224 insertions(+), 19 deletions(-)
---
diff --git a/gtk/gtklistitem.c b/gtk/gtklistitem.c
index 46d6dc09e0..2673e46efa 100644
--- a/gtk/gtklistitem.c
+++ b/gtk/gtklistitem.c
@@ -21,6 +21,7 @@
 
 #include "gtklistitemprivate.h"
 
+#include "gtkbindings.h"
 #include "gtkbinlayout.h"
 #include "gtkcssnodeprivate.h"
 #include "gtkeventcontrollerkey.h"
@@ -61,6 +62,7 @@ struct _GtkListItem
   GtkWidget *child;
   guint position;
 
+  guint activatable : 1;
   guint selectable : 1;
   guint selected : 1;
 };
@@ -68,11 +70,14 @@ struct _GtkListItem
 struct _GtkListItemClass
 {
   GtkWidgetClass parent_class;
+
+  void          (* activate_signal)                             (GtkListItem            *self);
 };
 
 enum
 {
   PROP_0,
+  PROP_ACTIVATABLE,
   PROP_CHILD,
   PROP_ITEM,
   PROP_POSITION,
@@ -82,9 +87,28 @@ enum
   N_PROPS
 };
 
+enum
+{
+  ACTIVATE_SIGNAL,
+  LAST_SIGNAL
+};
+
 G_DEFINE_TYPE (GtkListItem, gtk_list_item, GTK_TYPE_WIDGET)
 
 static GParamSpec *properties[N_PROPS] = { NULL, };
+static guint signals[LAST_SIGNAL] = { 0 };
+
+static void
+gtk_list_item_activate_signal (GtkListItem *self)
+{
+  if (!self->activatable)
+    return;
+
+  gtk_widget_activate_action (GTK_WIDGET (self),
+                              "list.activate-item",
+                              "u",
+                              self->position);
+}
 
 static gboolean
 gtk_list_item_focus (GtkWidget        *widget,
@@ -152,6 +176,10 @@ gtk_list_item_get_property (GObject    *object,
 
   switch (property_id)
     {
+    case PROP_ACTIVATABLE:
+      g_value_set_boolean (value, self->activatable);
+      break;
+
     case PROP_CHILD:
       g_value_set_object (value, self->child);
       break;
@@ -188,6 +216,10 @@ gtk_list_item_set_property (GObject      *object,
 
   switch (property_id)
     {
+    case PROP_ACTIVATABLE:
+      gtk_list_item_set_activatable (self, g_value_get_boolean (value));
+      break;
+
     case PROP_CHILD:
       gtk_list_item_set_child (self, g_value_get_object (value));
       break;
@@ -207,6 +239,9 @@ gtk_list_item_class_init (GtkListItemClass *klass)
 {
   GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
   GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+  GtkBindingSet *binding_set;
+
+  klass->activate_signal = gtk_list_item_activate_signal;
 
   widget_class->focus = gtk_list_item_focus;
   widget_class->grab_focus = gtk_list_item_grab_focus;
@@ -215,6 +250,18 @@ gtk_list_item_class_init (GtkListItemClass *klass)
   gobject_class->get_property = gtk_list_item_get_property;
   gobject_class->set_property = gtk_list_item_set_property;
 
+  /**
+   * GtkListItem:activatable:
+   *
+   * If the item can be activated by the user
+   */
+  properties[PROP_ACTIVATABLE] =
+    g_param_spec_boolean ("activatable",
+                          P_("Activatable"),
+                          P_("If the item can be activated by the user"),
+                          TRUE,
+                          G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
+
   /**
    * GtkListItem:child:
    *
@@ -277,6 +324,36 @@ gtk_list_item_class_init (GtkListItemClass *klass)
 
   g_object_class_install_properties (gobject_class, N_PROPS, properties);
 
+  /**
+   * GtkListItem::activate-signal:
+   *
+   * This is a keybinding signal, which will cause this row to be activated.
+   *
+   * Do not use it, it is an implementation detail.
+   *
+   * If you want to be notified when the user activates a listitem (by key or not),
+   * look at the list widget this item is contained in.
+   */
+  signals[ACTIVATE_SIGNAL] =
+    g_signal_new (I_("activate-keybinding"),
+                  G_OBJECT_CLASS_TYPE (gobject_class),
+                  G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION,
+                  G_STRUCT_OFFSET (GtkListItemClass, activate_signal),
+                  NULL, NULL,
+                  NULL,
+                  G_TYPE_NONE, 0);
+
+  widget_class->activate_signal = signals[ACTIVATE_SIGNAL];
+
+  binding_set = gtk_binding_set_by_class (klass);
+
+  gtk_binding_entry_add_signal (binding_set, GDK_KEY_Return, 0,
+                                "activate-keybinding", 0);
+  gtk_binding_entry_add_signal (binding_set, GDK_KEY_ISO_Enter, 0,
+                                "activate-keybinding", 0);
+  gtk_binding_entry_add_signal (binding_set, GDK_KEY_KP_Enter, 0,
+                                "activate-keybinding", 0);
+
   /* This gets overwritten by gtk_list_item_new() but better safe than sorry */
   gtk_widget_class_set_css_name (widget_class, I_("row"));
   gtk_widget_class_set_layout_manager_type (widget_class, GTK_TYPE_BIN_LAYOUT);
@@ -290,30 +367,45 @@ gtk_list_item_click_gesture_pressed (GtkGestureClick *gesture,
                                      GtkListItem     *self)
 {
   GtkWidget *widget = GTK_WIDGET (self);
-  GdkModifierType state;
-  GdkModifierType mask;
-  gboolean extend = FALSE, modify = FALSE;
 
-  if (!self->selectable)
+  if (!self->selectable && !self->activatable)
     {
       gtk_gesture_set_state (GTK_GESTURE (gesture), GTK_EVENT_SEQUENCE_DENIED);
       return;
     }
 
-  if (gtk_get_current_event_state (&state))
+  if (self->selectable)
     {
-      mask = gtk_widget_get_modifier_mask (widget, GDK_MODIFIER_INTENT_MODIFY_SELECTION);
-      if ((state & mask) == mask)
-        modify = TRUE;
-      mask = gtk_widget_get_modifier_mask (widget, GDK_MODIFIER_INTENT_EXTEND_SELECTION);
-      if ((state & mask) == mask)
-        extend = TRUE;
+      GdkModifierType state;
+      GdkModifierType mask;
+      gboolean extend = FALSE, modify = FALSE;
+
+      if (gtk_get_current_event_state (&state))
+        {
+          mask = gtk_widget_get_modifier_mask (widget, GDK_MODIFIER_INTENT_MODIFY_SELECTION);
+          if ((state & mask) == mask)
+            modify = TRUE;
+          mask = gtk_widget_get_modifier_mask (widget, GDK_MODIFIER_INTENT_EXTEND_SELECTION);
+          if ((state & mask) == mask)
+            extend = TRUE;
+        }
+
+      gtk_widget_activate_action (GTK_WIDGET (self),
+                                  "list.select-item",
+                                  "(ubb)",
+                                  self->position, modify, extend);
     }
 
-  gtk_widget_activate_action (GTK_WIDGET (self),
-                              "list.select-item",
-                              "(ubb)",
-                              self->position, modify, extend);
+  if (self->activatable)
+    {
+      if (n_press == 2)
+        {
+          gtk_widget_activate_action (GTK_WIDGET (self),
+                                      "list.activate-item",
+                                      "u",
+                                      self->position);
+        }
+    }
 
   gtk_widget_set_state_flags (widget, GTK_STATE_FLAG_ACTIVE, FALSE);
 
@@ -362,6 +454,7 @@ gtk_list_item_init (GtkListItem *self)
   GtkGesture *gesture;
 
   self->selectable = TRUE;
+  self->activatable = TRUE;
   gtk_widget_set_can_focus (GTK_WIDGET (self), TRUE);
 
   gesture = gtk_gesture_click_new ();
@@ -600,3 +693,49 @@ gtk_list_item_set_selectable (GtkListItem *self,
 
   g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_SELECTABLE]);
 }
+
+/**
+ * gtk_list_item_get_activatable:
+ * @self: a #GtkListItem
+ *
+ * Checks if a list item has been set to be activatable via
+ * gtk_list_item_set_activatable().
+ *
+ * Returns: %TRUE if the item is activatable
+ **/
+gboolean
+gtk_list_item_get_activatable (GtkListItem *self)
+{
+  g_return_val_if_fail (GTK_IS_LIST_ITEM (self), FALSE);
+
+  return self->activatable;
+}
+
+/**
+ * gtk_list_item_set_activatable:
+ * @self: a #GtkListItem
+ * @activatable: if the item should be activatable
+ *
+ * Sets @self to be activatable.
+ *
+ * If an item is activatable, double-clicking on the item, using
+ * the <Return> key or calling gtk_widget_activate() will activate
+ * the item. Activating instructs the containing view to handle
+ * activation. #GtkListView for example will be emitting the
+ * GtkListView::activate signal.
+ *
+ * By default, list items are activatable
+ **/
+void
+gtk_list_item_set_activatable (GtkListItem *self,
+                               gboolean     activatable)
+{
+  g_return_if_fail (GTK_IS_LIST_ITEM (self));
+
+  if (self->activatable == activatable)
+    return;
+
+  self->activatable = activatable;
+
+  g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_ACTIVATABLE]);
+}
diff --git a/gtk/gtklistitem.h b/gtk/gtklistitem.h
index aa423218a1..3eb58677b5 100644
--- a/gtk/gtklistitem.h
+++ b/gtk/gtklistitem.h
@@ -38,20 +38,25 @@ G_BEGIN_DECLS
 typedef struct _GtkListItem GtkListItem;
 typedef struct _GtkListItemClass GtkListItemClass;
 
+GDK_AVAILABLE_IN_ALL
 GType           gtk_list_item_get_type                          (void) G_GNUC_CONST;
 
 GDK_AVAILABLE_IN_ALL
 gpointer        gtk_list_item_get_item                          (GtkListItem            *self);
 GDK_AVAILABLE_IN_ALL
-guint           gtk_list_item_get_position                      (GtkListItem            *self);
+guint           gtk_list_item_get_position                      (GtkListItem            *self) G_GNUC_PURE;
 GDK_AVAILABLE_IN_ALL
-gboolean        gtk_list_item_get_selected                      (GtkListItem            *self);
+gboolean        gtk_list_item_get_selected                      (GtkListItem            *self) G_GNUC_PURE;
 GDK_AVAILABLE_IN_ALL
-gboolean        gtk_list_item_get_selectable                    (GtkListItem            *self);
+gboolean        gtk_list_item_get_selectable                    (GtkListItem            *self) G_GNUC_PURE;
 GDK_AVAILABLE_IN_ALL
 void            gtk_list_item_set_selectable                    (GtkListItem            *self,
                                                                  gboolean                selectable);
-
+GDK_AVAILABLE_IN_ALL
+gboolean        gtk_list_item_get_activatable                   (GtkListItem            *self) G_GNUC_PURE;
+GDK_AVAILABLE_IN_ALL
+void            gtk_list_item_set_activatable                   (GtkListItem            *self,
+                                                                 gboolean                activatable);
 
 GDK_AVAILABLE_IN_ALL
 void            gtk_list_item_set_child                         (GtkListItem            *self,
diff --git a/gtk/gtklistview.c b/gtk/gtklistview.c
index 87a80513de..85a4a5744f 100644
--- a/gtk/gtklistview.c
+++ b/gtk/gtklistview.c
@@ -101,11 +101,17 @@ enum
   N_PROPS
 };
 
+enum {
+  ACTIVATE,
+  LAST_SIGNAL
+};
+
 G_DEFINE_TYPE_WITH_CODE (GtkListView, gtk_list_view, GTK_TYPE_WIDGET,
                          G_IMPLEMENT_INTERFACE (GTK_TYPE_ORIENTABLE, NULL)
                          G_IMPLEMENT_INTERFACE (GTK_TYPE_SCROLLABLE, NULL))
 
 static GParamSpec *properties[N_PROPS] = { NULL, };
+static guint signals[LAST_SIGNAL] = { 0 };
 
 static void G_GNUC_UNUSED
 dump (GtkListView *self)
@@ -889,6 +895,24 @@ gtk_list_view_scroll_to_item (GtkWidget  *widget,
     }
 }
 
+static void
+gtk_list_view_activate_item (GtkWidget  *widget,
+                             const char *action_name,
+                             GVariant   *parameter)
+{
+  GtkListView *self = GTK_LIST_VIEW (widget);
+  guint pos;
+
+  if (!g_variant_check_format_string (parameter, "u", FALSE))
+    return;
+
+  g_variant_get (parameter, "u", &pos);
+  if (self->model == NULL || pos >= g_list_model_get_n_items (self->model))
+    return;
+
+  g_signal_emit (widget, signals[ACTIVATE], 0, pos);
+}
+
 static void
 gtk_list_view_class_init (GtkListViewClass *klass)
 {
@@ -971,6 +995,42 @@ gtk_list_view_class_init (GtkListViewClass *klass)
 
   g_object_class_install_properties (gobject_class, N_PROPS, properties);
 
+  /**
+   * GtkListView::activate:
+   * @self: The #GtkListView
+   * @position: position of item to activate
+   *
+   * The ::activate signal is emitted when a row has been activated by the user,
+   * usually via activating the GtkListView|list.activate-item action.
+   *
+   * This allows for a convenient way to handle activation in a listview.
+   * See gtk_list_item_set_activatable() for details on how to use this signal.
+   */
+  signals[ACTIVATE] =
+    g_signal_new (I_("activate"),
+                  G_TYPE_FROM_CLASS (gobject_class),
+                  G_SIGNAL_RUN_LAST,
+                  0,
+                  NULL, NULL,
+                  g_cclosure_marshal_VOID__UINT,
+                  G_TYPE_NONE, 1,
+                  G_TYPE_UINT);
+  g_signal_set_va_marshaller (signals[ACTIVATE],
+                              G_TYPE_FROM_CLASS (gobject_class),
+                              g_cclosure_marshal_VOID__UINTv);
+
+  /**
+   * GtkListView|list.activate-item:
+   * @position: position of item to activate
+   *
+   * Activates the item given in @position by emitting the GtkListView::activate
+   * signal.
+   */
+  gtk_widget_class_install_action (widget_class,
+                                   "list.activate-item",
+                                   "u",
+                                   gtk_list_view_activate_item);
+
   /**
    * GtkListView|list.select-item:
    * @position: position of item to select
diff --git a/gtk/gtktypes.h b/gtk/gtktypes.h
index a56b47e7e6..36aa1f7fc5 100644
--- a/gtk/gtktypes.h
+++ b/gtk/gtktypes.h
@@ -40,6 +40,7 @@ typedef struct _GtkClipboard         GtkClipboard;
 typedef struct _GtkEventController     GtkEventController;
 typedef struct _GtkGesture             GtkGesture;
 typedef struct _GtkLayoutManager       GtkLayoutManager;
+typedef struct _GtkListItem            GtkListItem;
 typedef struct _GtkListItemFactory     GtkListItemFactory;
 typedef struct _GtkNative              GtkNative;
 typedef struct _GtkRequisition        GtkRequisition;


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