[gnome-contacts: 19/35] flow-box: add basic selection support



commit 658540b2d71d9ad8b6ce46efa2945842beab843a
Author: William Jon McCann <jmccann redhat com>
Date:   Sat Feb 9 10:02:59 2013 -0500

    flow-box: add basic selection support

 egg-flow-box.c  |  338 ++++++++++++++++++++++++++++++++++++++++++++++++++-----
 egg-flow-box.h  |   24 ++++
 test-flow-box.c |   42 +++++++
 3 files changed, 377 insertions(+), 27 deletions(-)
---
diff --git a/egg-flow-box.c b/egg-flow-box.c
index 4832a78..5a41b93 100644
--- a/egg-flow-box.c
+++ b/egg-flow-box.c
@@ -47,6 +47,7 @@
 
 enum {
   CHILD_ACTIVATED,
+  SELECTED_CHILDREN_CHANGED,
   LAST_SIGNAL
 };
 
@@ -60,6 +61,7 @@ enum {
   PROP_ROW_SPACING,
   PROP_MIN_CHILDREN_PER_LINE,
   PROP_MAX_CHILDREN_PER_LINE,
+  PROP_SELECTION_MODE,
   PROP_ACTIVATE_ON_SINGLE_CLICK
 };
 
@@ -71,6 +73,7 @@ struct _EggFlowBoxPrivate {
   GtkAlign       valign_policy;
   guint          homogeneous : 1;
   guint          activate_on_single_click : 1;
+  GtkSelectionMode selection_mode;
 
   guint          row_spacing;
   guint          column_spacing;
@@ -89,10 +92,8 @@ struct _EggFlowBoxChildInfo
 {
   GSequenceIter *iter;
   GtkWidget *widget;
-  gint x;
-  gint y;
-  gint width;
-  gint height;
+  guint selected : 1;
+  GdkRectangle area;
 };
 
 static guint signals[LAST_SIGNAL] = { 0 };
@@ -772,10 +773,10 @@ egg_flow_box_real_size_allocate (GtkWidget     *widget,
 
       if (!gtk_widget_get_visible (child))
         {
-          child_info->x = child_allocation.x;
-          child_info->y = child_allocation.y;
-          child_info->width = 0;
-          child_info->height = 0;
+          child_info->area.x = child_allocation.x;
+          child_info->area.y = child_allocation.y;
+          child_info->area.width = 0;
+          child_info->area.height = 0;
           continue;
         }
 
@@ -878,10 +879,10 @@ egg_flow_box_real_size_allocate (GtkWidget     *widget,
           child_allocation.height = this_item_size;
         }
 
-      child_info->x = child_allocation.x;
-      child_info->y = child_allocation.y;
-      child_info->width = child_allocation.width;
-      child_info->height = child_allocation.height;
+      child_info->area.x = child_allocation.x;
+      child_info->area.y = child_allocation.y;
+      child_info->area.width = child_allocation.width;
+      child_info->area.height = child_allocation.height;
       gtk_widget_size_allocate (child, &child_allocation);
 
       item_offset += this_item_size;
@@ -921,25 +922,31 @@ egg_flow_box_real_remove (GtkContainer *container,
   EggFlowBox *box = EGG_FLOW_BOX (container);
   EggFlowBoxPrivate *priv = box->priv;
   gboolean was_visible;
-  EggFlowBoxChildInfo *info;
+  gboolean was_selected;
+  EggFlowBoxChildInfo *child_info;
 
   g_return_if_fail (child != NULL);
 
   was_visible = gtk_widget_get_visible (child);
 
-  info = egg_flow_box_lookup_info (box, child);
-  if (info == NULL)
+  child_info = egg_flow_box_lookup_info (box, child);
+  if (child_info == NULL)
     {
       g_warning ("Tried to remove non-child %p\n", child);
       return;
     }
 
+  was_selected = child_info->selected;
+
   gtk_widget_unparent (child);
   g_hash_table_remove (priv->child_hash, child);
-  g_sequence_remove (info->iter);
+  g_sequence_remove (child_info->iter);
 
   if (was_visible && gtk_widget_get_visible (GTK_WIDGET (box)))
     gtk_widget_queue_resize (GTK_WIDGET (box));
+
+  if (was_selected)
+    g_signal_emit (box, signals[SELECTED_CHILDREN_CHANGED], 0);
 }
 
 static void
@@ -1872,6 +1879,9 @@ egg_flow_box_get_property (GObject      *object,
     case PROP_MAX_CHILDREN_PER_LINE:
       g_value_set_uint (value, priv->max_children_per_line);
       break;
+    case PROP_SELECTION_MODE:
+      g_value_set_enum (value, priv->selection_mode);
+      break;
     case PROP_ACTIVATE_ON_SINGLE_CLICK:
       g_value_set_boolean (value, priv->activate_on_single_click);
       break;
@@ -1919,6 +1929,9 @@ egg_flow_box_set_property (GObject      *object,
     case PROP_MAX_CHILDREN_PER_LINE:
       egg_flow_box_set_max_children_per_line (box, g_value_get_uint (value));
       break;
+    case PROP_SELECTION_MODE:
+      egg_flow_box_set_selection_mode (box, g_value_get_enum (value));
+      break;
     case PROP_ACTIVATE_ON_SINGLE_CLICK:
       egg_flow_box_set_activate_on_single_click (box, g_value_get_boolean (value));
       break;
@@ -1944,8 +1957,8 @@ egg_flow_box_find_child_at_pos (EggFlowBox *box,
        iter = g_sequence_iter_next (iter))
     {
       info = (EggFlowBoxChildInfo *) g_sequence_get (iter);
-      if (x >= info->x && x < (info->x + info->width)
-          && y >= info->y && y < (info->y + info->height))
+      if (x >= info->area.x && x < (info->area.x + info->area.width)
+          && y >= info->area.y && y < (info->area.y + info->area.height))
         {
           child_info = info;
           break;
@@ -1964,17 +1977,17 @@ egg_flow_box_real_button_press_event (GtkWidget      *widget,
 
   if (event->button == 1)
     {
-      EggFlowBoxChildInfo *child;
-      child = egg_flow_box_find_child_at_pos (box, event->x, event->y);
-      if (child != NULL)
+      EggFlowBoxChildInfo *child_info;
+      child_info = egg_flow_box_find_child_at_pos (box, event->x, event->y);
+      if (child_info != NULL)
         {
-          priv->active_child = child;
+          priv->active_child = child_info;
           priv->active_child_active = TRUE;
           if (event->type == GDK_2BUTTON_PRESS &&
               !priv->activate_on_single_click)
             g_signal_emit (box,
                            signals[CHILD_ACTIVATED], 0,
-                           child->widget);
+                           child_info->widget);
         }
     }
 
@@ -1982,13 +1995,72 @@ egg_flow_box_real_button_press_event (GtkWidget      *widget,
 }
 
 static void
+egg_flow_box_queue_draw_child (EggFlowBox          *box,
+                               EggFlowBoxChildInfo *child_info)
+{
+  GdkRectangle rect;
+  GdkWindow *window;
+
+  rect = child_info->area;
+
+  window = gtk_widget_get_window (GTK_WIDGET (box));
+  gdk_window_invalidate_rect (window, &rect, TRUE);
+}
+
+static gboolean
+egg_flow_box_unselect_all_internal (EggFlowBox *box)
+{
+  EggFlowBoxChildInfo *child_info;
+  GSequenceIter *iter;
+  gboolean dirty = FALSE;
+
+  if (box->priv->selection_mode == GTK_SELECTION_NONE)
+    return FALSE;
+
+  for (iter = g_sequence_get_begin_iter (box->priv->children);
+       !g_sequence_iter_is_end (iter);
+       iter = g_sequence_iter_next (iter))
+    {
+      child_info = g_sequence_get (iter);
+      if (child_info->selected)
+        {
+          child_info->selected = FALSE;
+          egg_flow_box_queue_draw_child (box, child_info);
+          dirty = TRUE;
+        }
+    }
+
+}
+
+static void
+egg_flow_box_select_child_info (EggFlowBox          *box,
+                                EggFlowBoxChildInfo *child_info)
+{
+  if (child_info->selected)
+    return;
+
+  if (box->priv->selection_mode == GTK_SELECTION_NONE)
+    return;
+  else if (box->priv->selection_mode != GTK_SELECTION_MULTIPLE)
+    egg_flow_box_unselect_all_internal (box);
+
+  child_info->selected = TRUE;
+
+  g_signal_emit (box, signals[SELECTED_CHILDREN_CHANGED], 0);
+
+  egg_flow_box_queue_draw_child (box, child_info);
+}
+
+static void
 egg_flow_box_select_and_activate (EggFlowBox          *box,
-                                  EggFlowBoxChildInfo *child)
+                                  EggFlowBoxChildInfo *child_info)
 {
   GtkWidget *w = NULL;
 
-  if (child != NULL)
-    w = child->widget;
+  if (child_info != NULL)
+    w = child_info->widget;
+
+  egg_flow_box_select_child_info (box, child_info);
 
   if (w != NULL)
     g_signal_emit (box, signals[CHILD_ACTIVATED], 0, w);
@@ -2017,6 +2089,81 @@ egg_flow_box_real_button_release_event (GtkWidget      *widget,
   return FALSE;
 }
 
+typedef struct {
+  EggFlowBoxChildInfo *child;
+  GtkStateFlags state;
+} ChildFlags;
+
+static ChildFlags *
+child_flags_find_or_add (ChildFlags          *array,
+                         int                 *array_length,
+                         EggFlowBoxChildInfo *to_find)
+{
+  gint i;
+
+  for (i = 0; i < *array_length; i++)
+    {
+      if (array[i].child == to_find)
+        return &array[i];
+    }
+
+  *array_length = *array_length + 1;
+  array[*array_length - 1].child = to_find;
+  array[*array_length - 1].state = 0;
+  return &array[*array_length - 1];
+}
+
+static gboolean
+egg_flow_box_real_draw (GtkWidget *widget,
+                        cairo_t   *cr)
+{
+  EggFlowBox *box = EGG_FLOW_BOX (widget);
+  EggFlowBoxPrivate *priv = box->priv;
+  GtkAllocation allocation = {0};
+  GtkStyleContext* context;
+  GtkStateFlags state;
+  gint focus_pad;
+  int i;
+  GSequenceIter *iter;
+
+  gtk_widget_get_allocation (GTK_WIDGET (box), &allocation);
+  context = gtk_widget_get_style_context (GTK_WIDGET (box));
+  state = gtk_widget_get_state_flags (widget);
+  gtk_render_background (context, cr, (gdouble) 0, (gdouble) 0, (gdouble) allocation.width, (gdouble) 
allocation.height);
+
+  for (iter = g_sequence_get_begin_iter (box->priv->children);
+       !g_sequence_iter_is_end (iter);
+       iter = g_sequence_iter_next (iter))
+    {
+      EggFlowBoxChildInfo *child_info;
+      ChildFlags flags[3], *found;
+      gint flags_length;
+
+      child_info = g_sequence_get (iter);
+      if (child_info->selected)
+        {
+          flags_length = 0;
+          found = child_flags_find_or_add (flags, &flags_length, child_info);
+          found->state |= (state | GTK_STATE_FLAG_SELECTED);
+
+          for (i = 0; i < flags_length; i++)
+            {
+              ChildFlags *flag = &flags[i];
+              gtk_style_context_save (context);
+              gtk_style_context_set_state (context, flag->state);
+              gtk_render_background (context, cr,
+                                     flag->child->area.x, flag->child->area.y,
+                                     flag->child->area.width, flag->child->area.height);
+              gtk_style_context_restore (context);
+            }
+        }
+    }
+
+  GTK_WIDGET_CLASS (egg_flow_box_parent_class)->draw ((GtkWidget *) G_TYPE_CHECK_INSTANCE_CAST (box, 
GTK_TYPE_CONTAINER, GtkContainer), cr);
+
+  return TRUE;
+}
+
 static void
 egg_flow_box_real_realize (GtkWidget *widget)
 {
@@ -2070,6 +2217,7 @@ egg_flow_box_class_init (EggFlowBoxClass *class)
 
   widget_class->size_allocate = egg_flow_box_real_size_allocate;
   widget_class->realize = egg_flow_box_real_realize;
+  widget_class->draw = egg_flow_box_real_draw;
   widget_class->button_press_event = egg_flow_box_real_button_press_event;
   widget_class->button_release_event = egg_flow_box_real_button_release_event;
   widget_class->get_request_mode = egg_flow_box_real_get_request_mode;
@@ -2084,10 +2232,18 @@ egg_flow_box_class_init (EggFlowBoxClass *class)
   container_class->child_type = egg_flow_box_real_child_type;
   gtk_container_class_handle_border_width (container_class);
 
-  /* GObjectClass properties */
   g_object_class_override_property (object_class, PROP_ORIENTATION, "orientation");
 
   g_object_class_install_property (object_class,
+                                   PROP_SELECTION_MODE,
+                                   g_param_spec_enum ("selection-mode",
+                                                      P_("Selection mode"),
+                                                      P_("The selection mode"),
+                                                      GTK_TYPE_SELECTION_MODE,
+                                                      GTK_SELECTION_SINGLE,
+                                                      G_PARAM_READWRITE));
+
+  g_object_class_install_property (object_class,
                                    PROP_ACTIVATE_ON_SINGLE_CLICK,
                                    g_param_spec_boolean ("activate-on-single-click",
                                                          P_("Activate on Single Click"),
@@ -2212,6 +2368,14 @@ egg_flow_box_class_init (EggFlowBoxClass *class)
                                            G_TYPE_NONE, 1,
                                            GTK_TYPE_WIDGET);
 
+  signals[SELECTED_CHILDREN_CHANGED] = g_signal_new ("selected-children-changed",
+                                                     EGG_TYPE_FLOW_BOX,
+                                                     G_SIGNAL_RUN_FIRST,
+                                                     G_STRUCT_OFFSET (EggFlowBoxClass, 
selected_children_changed),
+                                                     NULL, NULL,
+                                                     g_cclosure_marshal_VOID__VOID,
+                                                     G_TYPE_NONE, 0);
+
   g_type_class_add_private (class, sizeof (EggFlowBoxPrivate));
 }
 
@@ -2223,6 +2387,7 @@ egg_flow_box_init (EggFlowBox *box)
   box->priv = priv = G_TYPE_INSTANCE_GET_PRIVATE (box, EGG_TYPE_FLOW_BOX, EggFlowBoxPrivate);
 
   priv->orientation = GTK_ORIENTATION_HORIZONTAL;
+  priv->selection_mode = GTK_SELECTION_SINGLE;
   priv->halign_policy = GTK_ALIGN_FILL;
   priv->valign_policy = GTK_ALIGN_START;
   priv->column_spacing = 0;
@@ -2246,3 +2411,122 @@ egg_flow_box_new (void)
 {
   return (GtkWidget *)g_object_new (EGG_TYPE_FLOW_BOX, NULL);
 }
+
+static void
+egg_flow_box_unselect_all (EggFlowBox *box)
+{
+  gboolean dirty = FALSE;
+
+  g_return_if_fail (EGG_IS_FLOW_BOX (box));
+
+  if (box->priv->selection_mode == GTK_SELECTION_BROWSE)
+    return;
+
+  dirty = egg_flow_box_unselect_all_internal (box);
+
+  if (dirty)
+    g_signal_emit (box, signals[SELECTED_CHILDREN_CHANGED], 0);
+}
+
+/**
+ * egg_flow_box_get_selected_children:
+ * @box: An #EggFlowBox.
+ *
+ * Creates a list of all selected children.
+ *
+ * Return value: (element-type GtkWidget) (transfer container): A #GList containing the #GtkWidget for each 
selected child.
+ **/
+GList *
+egg_flow_box_get_selected_children (EggFlowBox *box)
+{
+  EggFlowBoxChildInfo *child_info;
+  GSequenceIter *iter;
+  GList *selected = NULL;
+
+  g_return_if_fail (box != NULL);
+
+  for (iter = g_sequence_get_begin_iter (box->priv->children);
+       !g_sequence_iter_is_end (iter);
+       iter = g_sequence_iter_next (iter))
+    {
+      child_info = g_sequence_get (iter);
+      if (child_info->selected)
+        selected = g_list_prepend (selected, child_info->widget);
+    }
+
+  return g_list_reverse (selected);
+}
+
+void
+egg_flow_box_select_child (EggFlowBox *box,
+                           GtkWidget  *child)
+{
+  EggFlowBoxChildInfo *child_info;
+
+  g_return_if_fail (EGG_IS_FLOW_BOX (box));
+  g_return_if_fail (child != NULL);
+
+  child_info = egg_flow_box_lookup_info (box, child);
+  if (child_info == NULL)
+    {
+      g_warning ("Tried to select non-child %p\n", child);
+      return;
+    }
+
+  egg_flow_box_select_child_info (box, child_info);
+}
+
+/**
+ * egg_flow_box_selected_foreach:
+ * @flow_box: An #EggFlowBox.
+ * @func: (scope call): The function to call for each selected child.
+ * @data: User data to pass to the function.
+ *
+ * Calls a function for each selected child. Note that the
+ * selection cannot be modified from within this function.
+ */
+void
+egg_flow_box_selected_foreach (EggFlowBox           *box,
+                               EggFlowBoxForeachFunc func,
+                               gpointer              data)
+{
+  EggFlowBoxChildInfo *child_info;
+  GSequenceIter *iter;
+
+  g_return_if_fail (EGG_IS_FLOW_BOX (box));
+
+  for (iter = g_sequence_get_begin_iter (box->priv->children);
+       !g_sequence_iter_is_end (iter);
+       iter = g_sequence_iter_next (iter))
+    {
+      child_info = g_sequence_get (iter);
+      if (child_info->selected)
+        (* func) (box, child_info->widget, data);
+    }
+}
+
+void
+egg_flow_box_set_selection_mode (EggFlowBox *box,
+                                 GtkSelectionMode mode)
+{
+  g_return_if_fail (EGG_IS_FLOW_BOX (box));
+
+  if (mode == box->priv->selection_mode)
+    return;
+
+  if (mode == GTK_SELECTION_NONE ||
+      box->priv->selection_mode == GTK_SELECTION_MULTIPLE)
+    egg_flow_box_unselect_all (box);
+
+  box->priv->selection_mode = mode;
+
+  g_object_notify (G_OBJECT (box), "selection-mode");
+}
+
+GtkSelectionMode
+egg_flow_box_get_selection_mode (EggFlowBox *box)
+{
+  g_return_val_if_fail (EGG_IS_FLOW_BOX (box), GTK_SELECTION_SINGLE);
+
+  return box->priv->selection_mode;
+}
diff --git a/egg-flow-box.h b/egg-flow-box.h
index 4418415..c99905e 100644
--- a/egg-flow-box.h
+++ b/egg-flow-box.h
@@ -36,6 +36,19 @@ typedef struct _EggFlowBox            EggFlowBox;
 typedef struct _EggFlowBoxPrivate     EggFlowBoxPrivate;
 typedef struct _EggFlowBoxClass       EggFlowBoxClass;
 
+/**
+ * EggFlowBoxForeachFunc:
+ * @flow_box: an #EggFlowBox
+ * @child: The child #GtkWidget
+ * @data: user data
+ *
+ * A function used by egg_flow_box_selected_foreach() to map all
+ * selected children.  It will be called on every selected child in the box.
+ */
+typedef void (* EggFlowBoxForeachFunc)     (EggFlowBox      *flow_box,
+                                            GtkWidget       *child,
+                                            gpointer         data);
+
 struct _EggFlowBox
 {
   GtkContainer container;
@@ -49,6 +62,7 @@ struct _EggFlowBoxClass
   GtkContainerClass parent_class;
 
   void (* child_activated) (EggFlowBox *self, GtkWidget *child);
+  void (* selected_children_changed) (EggFlowBox *self);
 };
 
 GType                 egg_flow_box_get_type                  (void) G_GNUC_CONST;
@@ -84,6 +98,16 @@ gboolean              egg_flow_box_get_activate_on_single_click (EggFlowBox
 void                  egg_flow_box_set_activate_on_single_click (EggFlowBox        *box,
                                                                  gboolean           single);
 
+GList                *egg_flow_box_get_selected_children        (EggFlowBox        *box);
+void                  egg_flow_box_selected_foreach             (EggFlowBox        *box,
+                                                                 EggFlowBoxForeachFunc func,
+                                                                 gpointer           data);
+void                  egg_flow_box_select_child                 (EggFlowBox        *box,
+                                                                 GtkWidget         *child);
+GtkSelectionMode      egg_flow_box_get_selection_mode           (EggFlowBox        *box);
+void                  egg_flow_box_set_selection_mode           (EggFlowBox        *box,
+                                                                 GtkSelectionMode   mode);
+
 G_END_DECLS
 
 
diff --git a/test-flow-box.c b/test-flow-box.c
index ff7b16c..64587c6 100644
--- a/test-flow-box.c
+++ b/test-flow-box.c
@@ -211,6 +211,15 @@ orientation_changed (GtkComboBox   *box,
 }
 
 static void
+selection_mode_changed (GtkComboBox *box,
+                        EggFlowBox  *flowbox)
+{
+  GtkSelectionMode mode = gtk_combo_box_get_active (box);
+
+  egg_flow_box_set_selection_mode (flowbox, mode);
+}
+
+static void
 line_length_changed (GtkSpinButton *spin,
                      EggFlowBox *flowbox)
 {
@@ -277,6 +286,23 @@ on_child_activated (EggFlowBox *self,
   g_message ("Child activated %p: %s", child, id);
 }
 
+static void
+selection_foreach (EggFlowBox *self,
+                   GtkWidget  *child,
+                   gpointer    data)
+{
+  const char *id;
+  id = g_object_get_data (G_OBJECT (child), "id");
+  g_message ("Child selected %p: %s", child, id);
+}
+
+static void
+on_selected_children_changed (EggFlowBox *self)
+{
+  g_message ("Selection changed");
+  egg_flow_box_selected_foreach (self, selection_foreach, NULL);
+}
+
 static GtkWidget *
 create_window (void)
 {
@@ -313,6 +339,7 @@ create_window (void)
   gtk_container_add (GTK_CONTAINER (swindow), flowbox);
 
   g_signal_connect (flowbox, "child-activated", G_CALLBACK (on_child_activated), NULL);
+  g_signal_connect (flowbox, "selected-children-changed", G_CALLBACK (on_selected_children_changed), NULL);
 
   /* Add Flowbox test control frame */
   expander = gtk_expander_new ("Flow Box controls");
@@ -375,6 +402,21 @@ create_window (void)
   g_signal_connect (G_OBJECT (widget), "changed",
                     G_CALLBACK (orientation_changed), flowbox);
 
+  /* Add selection mode control */
+  widget = gtk_combo_box_text_new ();
+  gtk_combo_box_text_append_text (GTK_COMBO_BOX_TEXT (widget), "None");
+  gtk_combo_box_text_append_text (GTK_COMBO_BOX_TEXT (widget), "Single");
+  gtk_combo_box_text_append_text (GTK_COMBO_BOX_TEXT (widget), "Browse");
+  gtk_combo_box_text_append_text (GTK_COMBO_BOX_TEXT (widget), "Multiple");
+  gtk_combo_box_set_active (GTK_COMBO_BOX (widget), 1);
+  gtk_widget_show (widget);
+
+  gtk_widget_set_tooltip_text (widget, "Set the selection mode");
+  gtk_box_pack_start (GTK_BOX (flowbox_cntl), widget, FALSE, FALSE, 0);
+
+  g_signal_connect (G_OBJECT (widget), "changed",
+                    G_CALLBACK (selection_mode_changed), flowbox);
+
   /* Add minimum line length in items control */
   widget = gtk_spin_button_new_with_range (1, 10, 1);
   gtk_spin_button_set_value (GTK_SPIN_BUTTON (widget), INITIAL_MINIMUM_LENGTH);


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