[egg-list-box] flow-box: add basic keynav



commit d9f158678df31d2ac80484c059768f1f0d4d643a
Author: William Jon McCann <jmccann redhat com>
Date:   Sun Feb 10 07:52:25 2013 -0500

    flow-box: add basic keynav

 egg-flow-box.c  |  658 ++++++++++++++++++++++++++++++++++++++++++++++++++++++-
 egg-flow-box.h  |    5 +
 test-flow-box.c |    3 +
 3 files changed, 658 insertions(+), 8 deletions(-)
---
diff --git a/egg-flow-box.c b/egg-flow-box.c
index ff30c49..abcf0dd 100644
--- a/egg-flow-box.c
+++ b/egg-flow-box.c
@@ -42,6 +42,33 @@
 
 #include <gtk/gtk.h>
 #include "egg-flow-box.h"
+/* This already exists in gtk as _gtk_marshal_VOID__ENUM_INT, inline it here for now
+   to avoid separate marshallers file */
+static void
+_egg_marshal_VOID__ENUM_INT (GClosure * closure,
+                             GValue * return_value,
+                             guint n_param_values,
+                             const GValue * param_values,
+                             gpointer invocation_hint,
+                             gpointer marshal_data)
+{
+  typedef void (*GMarshalFunc_VOID__ENUM_INT) (gpointer data1, gint arg_1, gint arg_2, gpointer data2);
+  register GMarshalFunc_VOID__ENUM_INT callback;
+  register GCClosure * cc;
+  register gpointer data1;
+  register gpointer data2;
+  cc = (GCClosure *) closure;
+  g_return_if_fail (n_param_values == 3);
+  if (G_CCLOSURE_SWAP_DATA (closure)) {
+    data1 = closure->data;
+    data2 = param_values->data[0].v_pointer;
+  } else {
+    data1 = param_values->data[0].v_pointer;
+    data2 = closure->data;
+  }
+  callback = (GMarshalFunc_VOID__ENUM_INT) (marshal_data ? marshal_data : cc->callback);
+  callback (data1, g_value_get_enum (param_values + 1), g_value_get_int (param_values + 2), data2);
+}
 
 #define P_(msgid) (msgid)
 
@@ -50,6 +77,9 @@
 enum {
   CHILD_ACTIVATED,
   SELECTED_CHILDREN_CHANGED,
+  ACTIVATE_CURSOR_CHILD,
+  TOGGLE_CURSOR_CHILD,
+  MOVE_CURSOR,
   LAST_SIGNAL
 };
 
@@ -76,6 +106,7 @@ struct _EggFlowBoxPrivate {
   guint          homogeneous : 1;
   guint          activate_on_single_click : 1;
   GtkSelectionMode selection_mode;
+  GtkAdjustment *adjustment;
 
   guint          row_spacing;
   guint          column_spacing;
@@ -83,9 +114,12 @@ struct _EggFlowBoxPrivate {
   gboolean       active_child_active;
   EggFlowBoxChildInfo *active_child;
   EggFlowBoxChildInfo *prelight_child;
+  EggFlowBoxChildInfo *cursor_child;
+  EggFlowBoxChildInfo *selected_child;
 
   guint16        min_children_per_line;
   guint16        max_children_per_line;
+  guint16        cur_children_per_line;
 
   GSequence     *children;
   GHashTable    *child_hash;
@@ -140,6 +174,21 @@ egg_flow_box_lookup_info (EggFlowBox *flow_box, GtkWidget* child)
   return g_hash_table_lookup (priv->child_hash, child);
 }
 
+void
+egg_flow_box_set_adjustment (EggFlowBox    *box,
+                             GtkAdjustment *adjustment)
+{
+  EggFlowBoxPrivate *priv = box->priv;
+
+  g_return_if_fail (box != NULL);
+
+  g_object_ref (adjustment);
+  g_clear_object (&priv->adjustment);
+  priv->adjustment = adjustment;
+  gtk_container_set_focus_vadjustment (GTK_CONTAINER (box),
+                                       adjustment);
+}
+
 /**
  * egg_flow_box_get_homogeneous:
  * @box: a #EggFlowBox
@@ -185,6 +234,14 @@ egg_flow_box_set_homogeneous (EggFlowBox *box,
     }
 }
 
+/* Children are visible if they are shown by the app (visible)
+   and not filtered out (child_visible) by the box */
+static gboolean
+child_is_visible (GtkWidget *child)
+{
+  return gtk_widget_get_visible (child) && gtk_widget_get_child_visible (child);
+}
+
 static gint
 get_visible_children (EggFlowBox *box)
 {
@@ -202,7 +259,7 @@ get_visible_children (EggFlowBox *box)
       child_info = g_sequence_get (iter);
       child = child_info->widget;
 
-      if (!gtk_widget_get_visible (child))
+      if (!child_is_visible (child))
         continue;
 
       i++;
@@ -236,7 +293,7 @@ get_average_item_size (EggFlowBox    *box,
       child_info = g_sequence_get (iter);
       child = child_info->widget;
 
-      if (!gtk_widget_get_visible (child))
+      if (!child_is_visible (child))
         continue;
 
       if (orientation == GTK_ORIENTATION_HORIZONTAL)
@@ -281,7 +338,7 @@ get_largest_size_for_opposing_orientation (EggFlowBox    *box,
       child_info = g_sequence_get (iter);
       child = child_info->widget;
 
-      if (!gtk_widget_get_visible (child))
+      if (!child_is_visible (child))
         continue;
 
       if (orientation == GTK_ORIENTATION_HORIZONTAL)
@@ -335,7 +392,7 @@ get_largest_size_for_line_in_opposing_orientation (EggFlowBox       *box,
       child_info = g_sequence_get (iter);
       child = child_info->widget;
 
-      if (!gtk_widget_get_visible (child))
+      if (!child_is_visible (child))
         continue;
 
       /* Distribute the extra pixels to the first children in the line
@@ -402,7 +459,7 @@ gather_aligned_item_requests (EggFlowBox       *box,
       child_info = g_sequence_get (iter);
       child = child_info->widget;
 
-      if (!gtk_widget_get_visible (child))
+      if (!child_is_visible (child))
         continue;
 
       if (orientation == GTK_ORIENTATION_HORIZONTAL)
@@ -715,6 +772,8 @@ egg_flow_box_real_size_allocate (GtkWidget     *widget,
    * go on to distribute expand space if needed.
    */
 
+  priv->cur_children_per_line = line_length;
+
   /* FIXME: This portion needs to consider which columns
    * and rows asked for expand space and distribute those
    * accordingly for the case of ALIGNED allocation.
@@ -777,7 +836,7 @@ egg_flow_box_real_size_allocate (GtkWidget     *widget,
       child_info = g_sequence_get (iter);
       child = child_info->widget;
 
-      if (!gtk_widget_get_visible (child))
+      if (!child_is_visible (child))
         {
           child_info->area.x = child_allocation.x;
           child_info->area.y = child_allocation.y;
@@ -933,7 +992,7 @@ egg_flow_box_real_remove (GtkContainer *container,
 
   g_return_if_fail (child != NULL);
 
-  was_visible = gtk_widget_get_visible (child);
+  was_visible = child_is_visible (child);
 
   child_info = egg_flow_box_lookup_info (box, child);
   if (child_info == NULL)
@@ -948,6 +1007,8 @@ egg_flow_box_real_remove (GtkContainer *container,
     priv->prelight_child = NULL;
   if (child_info == priv->active_child)
     priv->active_child = NULL;
+  if (child_info == priv->selected_child)
+    priv->selected_child = NULL;
 
   gtk_widget_unparent (child);
   g_hash_table_remove (priv->child_hash, child);
@@ -1033,7 +1094,7 @@ get_largest_aligned_line_length (EggFlowBox     *box,
       child_info = g_sequence_get (iter);
       child = child_info->widget;
 
-      if (!gtk_widget_get_visible (child))
+      if (!child_is_visible (child))
         continue;
 
       if (orientation == GTK_ORIENTATION_HORIZONTAL)
@@ -2160,6 +2221,46 @@ egg_flow_box_unselect_all_internal (EggFlowBox *box)
 }
 
 static void
+egg_flow_box_unselect_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);
+  else
+    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_update_cursor (EggFlowBox          *box,
+                            EggFlowBoxChildInfo *child_info)
+{
+  EggFlowBoxPrivate *priv = box->priv;
+
+  priv->cursor_child = child_info;
+  gtk_widget_grab_focus (GTK_WIDGET (box));
+  gtk_widget_queue_draw (GTK_WIDGET (box));
+
+  if (child_info != NULL && priv->adjustment != NULL)
+    {
+      GtkAllocation allocation;
+
+      gtk_widget_get_allocation (GTK_WIDGET (box), &allocation);
+      gtk_adjustment_clamp_page (priv->adjustment,
+                                 priv->cursor_child->area.y + allocation.y,
+                                 priv->cursor_child->area.y + allocation.y + priv->cursor_child->area.height);
+  }
+}
+
+static void
 egg_flow_box_select_child_info (EggFlowBox          *box,
                                 EggFlowBoxChildInfo *child_info)
 {
@@ -2172,10 +2273,13 @@ egg_flow_box_select_child_info (EggFlowBox          *box,
     egg_flow_box_unselect_all_internal (box);
 
   child_info->selected = TRUE;
+  box->priv->selected_child = child_info;
 
   g_signal_emit (box, signals[SELECTED_CHILDREN_CHANGED], 0);
 
   egg_flow_box_queue_draw_child (box, child_info);
+
+  egg_flow_box_update_cursor (box, child_info);
 }
 
 static void
@@ -2218,6 +2322,285 @@ egg_flow_box_real_button_release_event (GtkWidget      *widget,
   return FALSE;
 }
 
+static EggFlowBoxChildInfo *
+egg_flow_box_get_first_visible (EggFlowBox *box)
+{
+  EggFlowBoxPrivate *priv = box->priv;
+  EggFlowBoxChildInfo *child_info;
+  GSequenceIter *iter;
+
+  for (iter = g_sequence_get_begin_iter (priv->children);
+       !g_sequence_iter_is_end (iter);
+       iter = g_sequence_iter_next (iter))
+    {
+        child_info = g_sequence_get (iter);
+        if (child_is_visible (child_info->widget))
+          return child_info;
+    }
+
+  return NULL;
+}
+
+static EggFlowBoxChildInfo *
+egg_flow_box_get_last_visible (EggFlowBox *box)
+{
+  EggFlowBoxPrivate *priv = box->priv;
+  EggFlowBoxChildInfo *child_info;
+  GSequenceIter *iter;
+
+  iter = g_sequence_get_end_iter (priv->children);
+  while (!g_sequence_iter_is_begin (iter))
+    {
+      iter = g_sequence_iter_prev (iter);
+      child_info = g_sequence_get (iter);
+      if (child_is_visible (child_info->widget))
+        return child_info;
+    }
+
+  return NULL;
+}
+
+static GSequenceIter *
+egg_flow_box_get_previous_visible (EggFlowBox    *box,
+                                   GSequenceIter *iter)
+{
+  EggFlowBoxChildInfo *child_info;
+
+  if (g_sequence_iter_is_begin (iter))
+    return NULL;
+
+  do
+    {
+      iter = g_sequence_iter_prev (iter);
+      child_info = g_sequence_get (iter);
+      if (child_is_visible (child_info->widget))
+        return iter;
+    }
+  while (!g_sequence_iter_is_begin (iter));
+
+  return NULL;
+}
+
+static GSequenceIter *
+egg_flow_box_get_next_visible (EggFlowBox    *box,
+                               GSequenceIter *iter)
+{
+  EggFlowBoxChildInfo *child_info;
+
+  if (g_sequence_iter_is_end (iter))
+    return iter;
+
+  do
+    {
+      iter = g_sequence_iter_next (iter);
+      if (!g_sequence_iter_is_end (iter))
+        {
+        child_info = g_sequence_get (iter);
+        if (child_is_visible (child_info->widget))
+          return iter;
+        }
+    }
+  while (!g_sequence_iter_is_end (iter));
+
+  return iter;
+}
+
+static GSequenceIter *
+egg_flow_box_get_above_visible (EggFlowBox    *box,
+                                GSequenceIter *iter)
+{
+  EggFlowBoxChildInfo *child_info;
+  GSequenceIter *ret = NULL;
+  gint i;
+
+  if (g_sequence_iter_is_begin (iter))
+    return NULL;
+
+  i = 0;
+  do
+    {
+      iter = g_sequence_iter_prev (iter);
+      child_info = g_sequence_get (iter);
+      if (child_is_visible (child_info->widget))
+        i++;
+    }
+  while (!g_sequence_iter_is_begin (iter)
+         && i < box->priv->cur_children_per_line);
+
+  if (i == box->priv->cur_children_per_line)
+    ret = iter;
+
+  return ret;
+}
+
+static GSequenceIter *
+egg_flow_box_get_below_visible (EggFlowBox    *box,
+                                GSequenceIter *iter)
+{
+  EggFlowBoxChildInfo *child_info;
+  GSequenceIter *ret = NULL;
+  gint i;
+
+  if (g_sequence_iter_is_end (iter))
+    return iter;
+
+  i = 0;
+  do
+    {
+      iter = g_sequence_iter_next (iter);
+      if (!g_sequence_iter_is_end (iter))
+        {
+          child_info = g_sequence_get (iter);
+          if (child_is_visible (child_info->widget))
+            i++;
+        }
+    }
+  while (!g_sequence_iter_is_end (iter)
+         && i < box->priv->cur_children_per_line);
+
+  if (i == box->priv->cur_children_per_line)
+    ret = iter;
+
+  return ret;
+}
+
+static gboolean
+egg_flow_box_real_focus (GtkWidget       *widget,
+                         GtkDirectionType direction)
+{
+  EggFlowBox *box = EGG_FLOW_BOX (widget);
+  EggFlowBoxPrivate *priv = box->priv;
+  gboolean had_focus = FALSE;
+  GtkWidget *recurse_into;
+  EggFlowBoxChildInfo *current_focus_child;
+  EggFlowBoxChildInfo *next_focus_child;
+  gboolean modify_selection_pressed;
+  GdkModifierType state = 0;
+
+  recurse_into = NULL;
+
+  g_object_get (GTK_WIDGET (box), "has-focus", &had_focus, NULL);
+  current_focus_child = NULL;
+  next_focus_child = NULL;
+
+  if (had_focus)
+    {
+      /* If on row, going right, enter into possible container */
+      if (direction == GTK_DIR_RIGHT || direction == GTK_DIR_TAB_FORWARD)
+        {
+          if (priv->cursor_child != NULL)
+            recurse_into = priv->cursor_child->widget;
+        }
+      current_focus_child = priv->cursor_child;
+    }
+  else if (gtk_container_get_focus_child ((GtkContainer *) box) != NULL)
+    {
+      /* There is a focus child, always navigate inside it first */
+      recurse_into = gtk_container_get_focus_child ((GtkContainer *) box);
+      current_focus_child = egg_flow_box_lookup_info (box, recurse_into);
+
+      /* If exiting child container to the left, select row or out */
+      if (direction == GTK_DIR_LEFT || direction == GTK_DIR_TAB_BACKWARD)
+        next_focus_child = current_focus_child;
+    }
+  else
+    {
+      /* If coming from the left, enter into possible container */
+      if (direction == GTK_DIR_LEFT || direction == GTK_DIR_TAB_BACKWARD)
+        {
+          if (priv->selected_child != NULL)
+            recurse_into = priv->selected_child->widget;
+        }
+    }
+
+  if (recurse_into != NULL)
+    {
+      if (gtk_widget_child_focus (recurse_into, direction))
+        return TRUE;
+    }
+
+  if (next_focus_child == NULL)
+    {
+      if (current_focus_child != NULL)
+        {
+          GSequenceIter *i;
+
+          if (direction == GTK_DIR_LEFT)
+            {
+              i = egg_flow_box_get_previous_visible (box, current_focus_child->iter);
+              if (i != NULL)
+                next_focus_child = g_sequence_get (i);
+            }
+          else if (direction == GTK_DIR_RIGHT)
+            {
+              i = egg_flow_box_get_next_visible (box, current_focus_child->iter);
+              if (i != NULL && !g_sequence_iter_is_end (i))
+                next_focus_child = g_sequence_get (i);
+            }
+          else if (direction == GTK_DIR_UP)
+            {
+              i = egg_flow_box_get_above_visible (box, current_focus_child->iter);
+              if (i != NULL && !g_sequence_iter_is_end (i))
+                next_focus_child = g_sequence_get (i);
+            }
+          else if (direction == GTK_DIR_DOWN)
+            {
+              i = egg_flow_box_get_below_visible (box, current_focus_child->iter);
+              if (i != NULL && !g_sequence_iter_is_end (i))
+                next_focus_child = g_sequence_get (i);
+            }
+        }
+      else
+        {
+          switch (direction)
+            {
+            case GTK_DIR_UP:
+            case GTK_DIR_TAB_BACKWARD:
+              next_focus_child = priv->selected_child;
+              if (next_focus_child == NULL)
+                next_focus_child = egg_flow_box_get_last_visible (box);
+              break;
+            default:
+              next_focus_child = priv->selected_child;
+              if (next_focus_child == NULL)
+                next_focus_child =
+                  egg_flow_box_get_first_visible (box);
+              break;
+            }
+        }
+    }
+
+  if (next_focus_child == NULL)
+    {
+      if (direction == GTK_DIR_UP || direction == GTK_DIR_DOWN
+          || direction == GTK_DIR_LEFT || direction == GTK_DIR_RIGHT)
+        {
+          if (gtk_widget_keynav_failed (GTK_WIDGET (box), direction))
+            return TRUE;
+        }
+
+      return FALSE;
+    }
+
+  modify_selection_pressed = FALSE;
+  if (gtk_get_current_event_state (&state))
+    {
+      GdkModifierType modify_mod_mask;
+
+      modify_mod_mask = gtk_widget_get_modifier_mask (GTK_WIDGET (box),
+                                                      GDK_MODIFIER_INTENT_MODIFY_SELECTION);
+      if ((state & modify_mod_mask) == modify_mod_mask)
+        modify_selection_pressed = TRUE;
+    }
+
+  if (modify_selection_pressed)
+    egg_flow_box_update_cursor (box, next_focus_child);
+  else
+    egg_flow_box_select_child_info (box, next_focus_child);
+
+  return TRUE;
+}
+
 typedef struct {
   EggFlowBoxChildInfo *child;
   GtkStateFlags state;
@@ -2242,6 +2625,204 @@ child_flags_find_or_add (ChildFlags          *array,
   return &array[*array_length - 1];
 }
 
+static void
+egg_flow_box_add_move_binding (GtkBindingSet  *binding_set,
+                               guint           keyval,
+                               GdkModifierType modmask,
+                               GtkMovementStep step,
+                               gint            count)
+{
+  gtk_binding_entry_add_signal (binding_set,
+                                keyval,
+                                modmask,
+                                "move-cursor",
+                                (guint) 2,
+                                GTK_TYPE_MOVEMENT_STEP,
+                                step,
+                                G_TYPE_INT,
+                                count,
+                                NULL);
+
+  if ((modmask & GDK_CONTROL_MASK) == GDK_CONTROL_MASK)
+    return;
+
+  gtk_binding_entry_add_signal (binding_set,
+                                keyval,
+                                GDK_CONTROL_MASK,
+                                "move-cursor",
+                                (guint) 2,
+                                GTK_TYPE_MOVEMENT_STEP,
+                                step,
+                                G_TYPE_INT,
+                                count,
+                                NULL);
+}
+
+static void
+egg_flow_box_real_activate_cursor_child (EggFlowBox *box)
+{
+  EggFlowBoxPrivate *priv = box->priv;
+
+  egg_flow_box_select_and_activate (box, priv->cursor_child);
+}
+
+static void
+egg_flow_box_real_toggle_cursor_child (EggFlowBox *box)
+{
+  EggFlowBoxPrivate *priv = box->priv;
+
+  if (priv->cursor_child == NULL)
+    return;
+
+  if (priv->selection_mode == GTK_SELECTION_SINGLE &&
+      priv->cursor_child->selected)
+    egg_flow_box_unselect_child_info (box, priv->cursor_child);
+  else
+    egg_flow_box_select_and_activate (box, priv->cursor_child);
+}
+
+static void
+egg_flow_box_real_move_cursor (EggFlowBox     *box,
+                               GtkMovementStep step,
+                               gint            count)
+{
+  EggFlowBoxPrivate *priv = box->priv;
+  GdkModifierType state;
+  gboolean modify_selection_pressed;
+  EggFlowBoxChildInfo *child;
+  GdkModifierType modify_mod_mask;
+  EggFlowBoxChildInfo *prev;
+  EggFlowBoxChildInfo *next;
+  gint page_size;
+  GSequenceIter *iter;
+  gint start_y;
+  gint end_y;
+
+  modify_selection_pressed = FALSE;
+
+  if (gtk_get_current_event_state (&state))
+    {
+      modify_mod_mask = gtk_widget_get_modifier_mask (GTK_WIDGET (box),
+                                                      GDK_MODIFIER_INTENT_MODIFY_SELECTION);
+      if ((state & modify_mod_mask) == modify_mod_mask)
+        modify_selection_pressed = TRUE;
+    }
+
+  child = NULL;
+  switch (step)
+    {
+    case GTK_MOVEMENT_BUFFER_ENDS:
+      if (count < 0)
+        child = egg_flow_box_get_first_visible (box);
+      else
+        child = egg_flow_box_get_last_visible (box);
+      break;
+    case GTK_MOVEMENT_DISPLAY_LINES:
+      if (priv->cursor_child != NULL)
+        {
+          iter = priv->cursor_child->iter;
+
+          while (count < 0  && iter != NULL)
+            {
+              iter = egg_flow_box_get_previous_visible (box, iter);
+              count = count + 1;
+            }
+          while (count > 0  && iter != NULL)
+            {
+              iter = egg_flow_box_get_next_visible (box, iter);
+              count = count - 1;
+            }
+
+          if (iter != NULL && !g_sequence_iter_is_end (iter))
+            child = g_sequence_get (iter);
+        }
+      break;
+    case GTK_MOVEMENT_PAGES:
+      page_size = 100;
+      if (priv->adjustment != NULL)
+        page_size = gtk_adjustment_get_page_increment (priv->adjustment);
+
+      if (priv->cursor_child != NULL)
+        {
+          start_y = priv->cursor_child->area.y;
+          end_y = start_y;
+          iter = priv->cursor_child->iter;
+
+          child = priv->cursor_child;
+          if (count < 0)
+            {
+              gint i = 0;
+
+              /* Up */
+              while (iter != NULL && !g_sequence_iter_is_begin (iter))
+                {
+                  iter = egg_flow_box_get_previous_visible (box, iter);
+                  if (iter == NULL)
+                    break;
+
+                  prev = g_sequence_get (iter);
+
+                  /* go up an even number of rows */
+                  if (i % priv->cur_children_per_line == 0
+                      && prev->area.y < start_y - page_size)
+                    break;
+
+                  child = prev;
+                  i++;
+                }
+            }
+          else
+            {
+              gint i = 0;
+
+              /* Down */
+              while (iter != NULL && !g_sequence_iter_is_end (iter))
+                {
+                  iter = egg_flow_box_get_next_visible (box, iter);
+                  if (g_sequence_iter_is_end (iter))
+                    break;
+
+                  next = g_sequence_get (iter);
+
+                  if (i % priv->cur_children_per_line == 0
+                      && next->area.y > start_y + page_size)
+                    break;
+
+                  child = next;
+                  i++;
+                }
+            }
+          end_y = child->area.y;
+        }
+      break;
+    default:
+      return;
+    }
+
+  if (child == NULL || child == priv->cursor_child)
+    {
+      GtkDirectionType direction = count < 0 ? GTK_DIR_UP : GTK_DIR_DOWN;
+
+      if (!gtk_widget_keynav_failed (GTK_WIDGET (box), direction))
+        {
+          GtkWidget *toplevel = gtk_widget_get_toplevel (GTK_WIDGET (box));
+
+          if (toplevel)
+            gtk_widget_child_focus (toplevel,
+                                    direction == GTK_DIR_UP ?
+                                    GTK_DIR_TAB_BACKWARD :
+                                    GTK_DIR_TAB_FORWARD);
+
+        }
+
+      return;
+    }
+
+  egg_flow_box_update_cursor (box, child);
+  if (!modify_selection_pressed)
+    egg_flow_box_select_child_info (box, child);
+}
+
 static gboolean
 egg_flow_box_real_draw (GtkWidget *widget,
                         cairo_t   *cr)
@@ -2343,6 +2924,7 @@ egg_flow_box_finalize (GObject *obj)
 
   g_sequence_free (priv->children);
   g_hash_table_unref (priv->child_hash);
+  g_clear_object (&priv->adjustment);
 
   G_OBJECT_CLASS (egg_flow_box_parent_class)->finalize (obj);
 }
@@ -2353,6 +2935,7 @@ egg_flow_box_class_init (EggFlowBoxClass *class)
   GObjectClass      *object_class = G_OBJECT_CLASS (class);
   GtkWidgetClass    *widget_class = GTK_WIDGET_CLASS (class);
   GtkContainerClass *container_class = GTK_CONTAINER_CLASS (class);
+  GtkBindingSet     *binding_set;
 
   object_class->finalize = egg_flow_box_finalize;
   object_class->get_property = egg_flow_box_get_property;
@@ -2363,6 +2946,7 @@ egg_flow_box_class_init (EggFlowBoxClass *class)
   widget_class->motion_notify_event = egg_flow_box_real_motion_notify_event;
   widget_class->size_allocate = egg_flow_box_real_size_allocate;
   widget_class->realize = egg_flow_box_real_realize;
+  widget_class->focus = egg_flow_box_real_focus;
   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;
@@ -2378,6 +2962,10 @@ 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);
 
+  class->activate_cursor_child = egg_flow_box_real_activate_cursor_child;
+  class->toggle_cursor_child = egg_flow_box_real_toggle_cursor_child;
+  class->move_cursor = egg_flow_box_real_move_cursor;
+
   g_object_class_override_property (object_class, PROP_ORIENTATION, "orientation");
 
   g_object_class_install_property (object_class,
@@ -2521,6 +3109,59 @@ egg_flow_box_class_init (EggFlowBoxClass *class)
                                                      NULL, NULL,
                                                      g_cclosure_marshal_VOID__VOID,
                                                      G_TYPE_NONE, 0);
+  signals[ACTIVATE_CURSOR_CHILD] = g_signal_new ("activate-cursor-child",
+                                                 EGG_TYPE_FLOW_BOX,
+                                                 G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
+                                                 G_STRUCT_OFFSET (EggFlowBoxClass, activate_cursor_child),
+                                                 NULL, NULL,
+                                                 g_cclosure_marshal_VOID__VOID,
+                                                 G_TYPE_NONE, 0);
+  signals[TOGGLE_CURSOR_CHILD] = g_signal_new ("toggle-cursor-child",
+                                               EGG_TYPE_FLOW_BOX,
+                                               G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
+                                               G_STRUCT_OFFSET (EggFlowBoxClass, toggle_cursor_child),
+                                               NULL, NULL,
+                                               g_cclosure_marshal_VOID__VOID,
+                                               G_TYPE_NONE, 0);
+  signals[MOVE_CURSOR] = g_signal_new ("move-cursor",
+                                       EGG_TYPE_FLOW_BOX,
+                                       G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
+                                       G_STRUCT_OFFSET (EggFlowBoxClass, move_cursor),
+                                       NULL, NULL,
+                                       _egg_marshal_VOID__ENUM_INT,
+                                       G_TYPE_NONE, 2,
+                                       GTK_TYPE_MOVEMENT_STEP, G_TYPE_INT);
+
+  widget_class->activate_signal = signals[ACTIVATE_CURSOR_CHILD];
+
+  binding_set = gtk_binding_set_by_class (class);
+  egg_flow_box_add_move_binding (binding_set, GDK_KEY_Home, 0,
+                                 GTK_MOVEMENT_BUFFER_ENDS, -1);
+  egg_flow_box_add_move_binding (binding_set, GDK_KEY_KP_Home, 0,
+                                 GTK_MOVEMENT_BUFFER_ENDS, -1);
+  egg_flow_box_add_move_binding (binding_set, GDK_KEY_End, 0,
+                                 GTK_MOVEMENT_BUFFER_ENDS, 1);
+  egg_flow_box_add_move_binding (binding_set, GDK_KEY_KP_End, 0,
+                                 GTK_MOVEMENT_BUFFER_ENDS, 1);
+  egg_flow_box_add_move_binding (binding_set, GDK_KEY_Up, GDK_CONTROL_MASK,
+                                 GTK_MOVEMENT_DISPLAY_LINES, -1);
+  egg_flow_box_add_move_binding (binding_set, GDK_KEY_KP_Up, GDK_CONTROL_MASK,
+                                 GTK_MOVEMENT_DISPLAY_LINES, -1);
+  egg_flow_box_add_move_binding (binding_set, GDK_KEY_Down, GDK_CONTROL_MASK,
+                                 GTK_MOVEMENT_DISPLAY_LINES, 1);
+  egg_flow_box_add_move_binding (binding_set, GDK_KEY_KP_Down, GDK_CONTROL_MASK,
+                                 GTK_MOVEMENT_DISPLAY_LINES, 1);
+  egg_flow_box_add_move_binding (binding_set, GDK_KEY_Page_Up, 0,
+                                 GTK_MOVEMENT_PAGES, -1);
+  egg_flow_box_add_move_binding (binding_set, GDK_KEY_KP_Page_Up, 0,
+                                 GTK_MOVEMENT_PAGES, -1);
+  egg_flow_box_add_move_binding (binding_set, GDK_KEY_Page_Down, 0,
+                                 GTK_MOVEMENT_PAGES, 1);
+  egg_flow_box_add_move_binding (binding_set, GDK_KEY_KP_Page_Down, 0,
+                                 GTK_MOVEMENT_PAGES, 1);
+
+  gtk_binding_entry_add_signal (binding_set, GDK_KEY_space, GDK_CONTROL_MASK,
+                                "toggle-cursor-child", 0, NULL);
 
   g_type_class_add_private (class, sizeof (EggFlowBoxPrivate));
 }
@@ -2543,6 +3184,7 @@ egg_flow_box_init (EggFlowBox *box)
   priv->child_hash = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, NULL);
   priv->activate_on_single_click = TRUE;
 
+  gtk_widget_set_can_focus (GTK_WIDGET (box), TRUE);
   gtk_widget_set_has_window (GTK_WIDGET (box), TRUE);
 }
 
diff --git a/egg-flow-box.h b/egg-flow-box.h
index c99905e..9748522 100644
--- a/egg-flow-box.h
+++ b/egg-flow-box.h
@@ -63,6 +63,9 @@ struct _EggFlowBoxClass
 
   void (* child_activated) (EggFlowBox *self, GtkWidget *child);
   void (* selected_children_changed) (EggFlowBox *self);
+  void (*activate_cursor_child) (EggFlowBox *self);
+  void (*toggle_cursor_child) (EggFlowBox *self);
+  void (*move_cursor) (EggFlowBox *self, GtkMovementStep step, gint count);
 };
 
 GType                 egg_flow_box_get_type                  (void) G_GNUC_CONST;
@@ -107,6 +110,8 @@ void                  egg_flow_box_select_child                 (EggFlowBox
 GtkSelectionMode      egg_flow_box_get_selection_mode           (EggFlowBox        *box);
 void                  egg_flow_box_set_selection_mode           (EggFlowBox        *box,
                                                                  GtkSelectionMode   mode);
+void                  egg_flow_box_set_adjustment               (EggFlowBox        *box,
+                                                                 GtkAdjustment     *adjustment);
 
 G_END_DECLS
 
diff --git a/test-flow-box.c b/test-flow-box.c
index 88565e7..ef06eaa 100644
--- a/test-flow-box.c
+++ b/test-flow-box.c
@@ -338,6 +338,9 @@ create_window (void)
   gtk_widget_show (flowbox);
   gtk_container_add (GTK_CONTAINER (swindow), flowbox);
 
+  egg_flow_box_set_adjustment (EGG_FLOW_BOX (flowbox),
+                               gtk_scrolled_window_get_vadjustment (GTK_SCROLLED_WINDOW (swindow)));
+
   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);
 


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