[gnome-shell] St: add keyboard focus navigation support



commit d2b968a7df5c7c565e125774f2cd2886fa4fbc9e
Author: Dan Winship <danw gnome org>
Date:   Tue Jun 15 12:11:39 2010 -0400

    St: add keyboard focus navigation support
    
    Add StWidget:can-focus, st_widget_navigate_focus(), and
    st_container_get_focus_chain(), and implement as needed to allow
    keyboard navigation of widgets.
    
    https://bugzilla.gnome.org/show_bug.cgi?id=621671

 src/shell-generic-container.c |   22 +++
 src/st/st-bin.c               |   25 +++
 src/st/st-container.c         |  325 +++++++++++++++++++++++++++++++++++++++++
 src/st/st-container.h         |    6 +-
 src/st/st-entry.c             |   24 +++
 src/st/st-overflow-box.c      |   65 +++++++--
 src/st/st-widget.c            |  155 +++++++++++++++++++-
 src/st/st-widget.h            |   13 ++-
 8 files changed, 622 insertions(+), 13 deletions(-)
---
diff --git a/src/shell-generic-container.c b/src/shell-generic-container.c
index 5b43963..e378d72 100644
--- a/src/shell-generic-container.c
+++ b/src/shell-generic-container.c
@@ -162,6 +162,25 @@ shell_generic_container_pick (ClutterActor        *actor,
     }
 }
 
+static GList *
+shell_generic_container_get_focus_chain (StContainer *container)
+{
+  ShellGenericContainer *self = SHELL_GENERIC_CONTAINER (container);
+  GList *children, *focus_chain;
+
+  focus_chain = NULL;
+  for (children = st_container_get_children_list (container); children; children = children->next)
+    {
+      ClutterActor *child = children->data;
+
+      if (CLUTTER_ACTOR_IS_VISIBLE (child) &&
+          !shell_generic_container_get_skip_paint (self, child))
+        focus_chain = g_list_prepend (focus_chain, child);
+    }
+
+  return g_list_reverse (focus_chain);
+}
+
 /**
  * shell_generic_container_get_n_skip_paint:
  * @self:  A #ShellGenericContainer
@@ -231,6 +250,7 @@ shell_generic_container_class_init (ShellGenericContainerClass *klass)
 {
   GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
   ClutterActorClass *actor_class = CLUTTER_ACTOR_CLASS (klass);
+  StContainerClass *container_class = ST_CONTAINER_CLASS (klass);
 
   gobject_class->finalize = shell_generic_container_finalize;
 
@@ -240,6 +260,8 @@ shell_generic_container_class_init (ShellGenericContainerClass *klass)
   actor_class->paint = shell_generic_container_paint;
   actor_class->pick = shell_generic_container_pick;
 
+  container_class->get_focus_chain = shell_generic_container_get_focus_chain;
+
   shell_generic_container_signals[GET_PREFERRED_WIDTH] =
     g_signal_new ("get-preferred-width",
                   G_TYPE_FROM_CLASS (klass),
diff --git a/src/st/st-bin.c b/src/st/st-bin.c
index 3039297..d622395 100644
--- a/src/st/st-bin.c
+++ b/src/st/st-bin.c
@@ -226,6 +226,28 @@ st_bin_dispose (GObject *gobject)
   G_OBJECT_CLASS (st_bin_parent_class)->dispose (gobject);
 }
 
+static gboolean
+st_bin_navigate_focus (StWidget         *widget,
+                       ClutterActor     *from,
+                       GtkDirectionType  direction)
+{
+  StBinPrivate *priv = ST_BIN (widget)->priv;
+  ClutterActor *bin_actor = CLUTTER_ACTOR (widget);
+
+  if (st_widget_get_can_focus (widget))
+    {
+      if (from && clutter_actor_contains (bin_actor, from))
+        return FALSE;
+
+      clutter_actor_grab_key_focus (bin_actor);
+      return TRUE;
+    }
+  else if (priv->child && ST_IS_WIDGET (priv->child))
+    return st_widget_navigate_focus (ST_WIDGET (priv->child), from, direction, FALSE);
+  else
+    return FALSE;
+}
+
 static void
 st_bin_set_property (GObject      *gobject,
                      guint         prop_id,
@@ -309,6 +331,7 @@ st_bin_class_init (StBinClass *klass)
 {
   GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
   ClutterActorClass *actor_class = CLUTTER_ACTOR_CLASS (klass);
+  StWidgetClass *widget_class = ST_WIDGET_CLASS (klass);
   GParamSpec *pspec;
 
   g_type_class_add_private (klass, sizeof (StBinPrivate));
@@ -323,6 +346,8 @@ st_bin_class_init (StBinClass *klass)
   actor_class->paint = st_bin_paint;
   actor_class->pick = st_bin_pick;
 
+  widget_class->navigate_focus = st_bin_navigate_focus;
+
   /**
    * StBin:child:
    *
diff --git a/src/st/st-container.c b/src/st/st-container.c
index 7a8dee2..2c82849 100644
--- a/src/st/st-container.c
+++ b/src/st/st-container.c
@@ -28,6 +28,8 @@
 #include "config.h"
 #endif
 
+#include <stdlib.h>
+
 #include "st-container.h"
 
 #define ST_CONTAINER_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj),ST_TYPE_CONTAINER, StContainerPrivate))
@@ -178,6 +180,40 @@ st_container_get_children_list (StContainer *container)
   return container->priv->children;
 }
 
+static GList *
+st_container_real_get_focus_chain (StContainer *container)
+{
+  GList *chain, *children;
+
+  chain = NULL;
+  for (children = container->priv->children; children; children = children->next)
+    {
+      ClutterActor *child = children->data;
+
+      if (CLUTTER_ACTOR_IS_VISIBLE (child))
+        chain = g_list_prepend (chain, child);
+    }
+
+  return g_list_reverse (chain);
+}
+
+/**
+ * st_container_get_focus_chain:
+ * @container: An #StContainer
+ *
+ * Gets a list of the focusable children of @container, in "Tab"
+ * order. By default, this returns all visible
+ * (as in CLUTTER_ACTOR_IS_VISIBLE()) children of @container.
+ *
+ * Returns: (element-type Clutter.Actor) (transfer container):
+ *   @container's focusable children
+ */
+GList *
+st_container_get_focus_chain (StContainer *container)
+{
+  return ST_CONTAINER_GET_CLASS (container)->get_focus_chain (container);
+}
+
 static gint
 sort_z_order (gconstpointer a,
               gconstpointer b)
@@ -389,6 +425,289 @@ st_container_dispose (GObject *object)
   G_OBJECT_CLASS (st_container_parent_class)->dispose (object);
 }
 
+/* filter @children to contain only only actors that overlap @rbox
+ * when moving in @direction. (Assuming no transformations.)
+ */
+static GList *
+filter_by_position (GList            *children,
+                    ClutterActorBox  *rbox,
+                    GtkDirectionType  direction)
+{
+  ClutterActorBox cbox;
+  GList *l, *ret;
+  ClutterActor *child;
+
+  for (l = children, ret = NULL; l; l = l->next)
+    {
+      child = l->data;
+      clutter_actor_get_allocation_box (child, &cbox);
+
+      /* Filter out children if they are in the wrong direction from
+       * @rbox, or if they don't overlap it.
+       */
+      switch (direction)
+        {
+        case GTK_DIR_UP:
+          if (cbox.y2 > rbox->y1)
+            continue;
+          if (cbox.x1 >= rbox->x2 || cbox.x2 <= rbox->x1)
+            continue;
+          break;
+
+        case GTK_DIR_DOWN:
+          if (cbox.y1 < rbox->y2)
+            continue;
+          if (cbox.x1 >= rbox->x2 || cbox.x2 <= rbox->x1)
+            continue;
+          break;
+
+        case GTK_DIR_LEFT:
+          if (cbox.x2 > rbox->x1)
+            continue;
+          if (cbox.y1 >= rbox->y2 || cbox.y2 <= rbox->y1)
+            continue;
+          break;
+
+        case GTK_DIR_RIGHT:
+          if (cbox.x1 < rbox->x2)
+            continue;
+          if (cbox.y1 >= rbox->y2 || cbox.y2 <= rbox->y1)
+            continue;
+          break;
+
+        default:
+          g_return_val_if_reached (NULL);
+        }
+
+      ret = g_list_prepend (ret, child);
+    }
+
+  g_list_free (children);
+  return ret;
+}
+
+typedef struct {
+  GtkDirectionType direction;
+  ClutterActorBox box;
+} StContainerChildSortData;
+
+static int
+sort_by_position (gconstpointer  a,
+                  gconstpointer  b,
+                  gpointer       user_data)
+{
+  ClutterActor *actor_a = (ClutterActor *)a;
+  ClutterActor *actor_b = (ClutterActor *)b;
+  StContainerChildSortData *sort_data = user_data;
+  GtkDirectionType direction = sort_data->direction;
+  ClutterActorBox abox, bbox;
+  int ax, ay, bx, by;
+  int cmp, fmid;
+
+  /* Determine the relationship, relative to motion in @direction, of
+   * the center points of the two actors. Eg, for %GTK_DIR_UP, we
+   * return a negative number if @actor_a's center is below @actor_b's
+   * center, and postive if vice versa, which will result in an
+   * overall list sorted bottom-to-top.
+   */
+
+  clutter_actor_get_allocation_box (actor_a, &abox);
+  ax = (int)(abox.x1 + abox.x2) / 2;
+  ay = (int)(abox.y1 + abox.y2) / 2;
+  clutter_actor_get_allocation_box (actor_b, &bbox);
+  bx = (int)(bbox.x1 + bbox.x2) / 2;
+  by = (int)(bbox.y1 + bbox.y2) / 2;
+
+  switch (direction)
+    {
+    case GTK_DIR_UP:
+      cmp = by - ay;
+      break;
+    case GTK_DIR_DOWN:
+      cmp = ay - by;
+      break;
+    case GTK_DIR_LEFT:
+      cmp = bx - ax;
+      break;
+    case GTK_DIR_RIGHT:
+      cmp = ax - bx;
+      break;
+    default:
+      g_return_val_if_reached (0);
+    }
+
+  if (cmp)
+    return cmp;
+
+  /* If two actors have the same center on the axis being sorted,
+   * prefer the one that is closer to the center of the current focus
+   * actor on the other axis. Eg, for %GTK_DIR_UP, prefer whichever
+   * of @actor_a and @actor_b has a horizontal center closest to the
+   * current focus actor's horizontal center.
+   *
+   * (This matches GTK's behavior.)
+   */
+  switch (direction)
+    {
+    case GTK_DIR_UP:
+    case GTK_DIR_DOWN:
+      fmid = (int)(sort_data->box.x1 + sort_data->box.x2) / 2;
+      return abs (ax - fmid) - abs (bx - fmid);
+    case GTK_DIR_LEFT:
+    case GTK_DIR_RIGHT:
+      fmid = (int)(sort_data->box.y1 + sort_data->box.y2) / 2;
+      return abs (ay - fmid) - abs (by - fmid);
+    default:
+      g_return_val_if_reached (0);
+    }
+}
+
+static gboolean
+st_container_navigate_focus (StWidget         *widget,
+                             ClutterActor     *from,
+                             GtkDirectionType  direction)
+{
+  StContainer *container = ST_CONTAINER (widget);
+  ClutterActor *container_actor, *focus_child;
+  GList *children, *l;
+
+  container_actor = CLUTTER_ACTOR (widget);
+  if (from == container_actor)
+    return FALSE;
+
+  /* Figure out if @from is a descendant of @container, and if so,
+   * set @focus_child to the immediate child of @container that
+   * contains (or *is*) @from.
+   */
+  focus_child = from;
+  while (focus_child && clutter_actor_get_parent (focus_child) != container_actor)
+    focus_child = clutter_actor_get_parent (focus_child);
+
+  if (st_widget_get_can_focus (widget))
+    {
+      if (!focus_child)
+        {
+          /* Accept focus from outside */
+          clutter_actor_grab_key_focus (container_actor);
+          return TRUE;
+        }
+      else
+        {
+          /* Yield focus from within: since @container itself is
+           * focusable we don't allow the focus to be navigated
+           * within @container.
+           */
+          return FALSE;
+        }
+    }
+
+  /* See if we can navigate within @focus_child */
+  if (focus_child && ST_IS_WIDGET (focus_child))
+    {
+      if (st_widget_navigate_focus (ST_WIDGET (focus_child), from, direction, FALSE))
+        return TRUE;
+    }
+
+  /* At this point we know that we want to navigate focus to one of
+   * @container's immediate children; the next one after @focus_child,
+   * or the first one if @focus_child is %NULL. (With "next" and
+   * "first" being determined by @direction.)
+   */
+
+  children = st_container_get_focus_chain (container);
+  if (direction == GTK_DIR_TAB_FORWARD ||
+      direction == GTK_DIR_TAB_BACKWARD)
+    {
+      if (direction == GTK_DIR_TAB_BACKWARD)
+        children = g_list_reverse (children);
+
+      if (focus_child)
+        {
+          /* Remove focus_child and any earlier children */
+          while (children && children->data != focus_child)
+            children = g_list_delete_link (children, children);
+          if (children)
+            children = g_list_delete_link (children, children);
+        }
+    }
+  else /* direction is an arrow key, not tab */
+    {
+      StContainerChildSortData sort_data;
+
+      /* Compute the allocation box of the previous focused actor, in
+       * @container's coordinate space. If there was no previous focus,
+       * use the coordinates of the appropriate edge of @container.
+       *
+       * Note that all of this code assumes the actors are not
+       * transformed (or at most, they are all scaled by the same
+       * amount). If @container or any of its children is rotated, or
+       * any child is inconsistently scaled, then the focus chain will
+       * probably be unpredictable.
+       */
+      if (from)
+        {
+          if (from == focus_child)
+            clutter_actor_get_allocation_box (focus_child, &sort_data.box);
+          else
+            {
+              float cx, cy, fx, fy, fw, fh;
+
+              clutter_actor_get_transformed_position (CLUTTER_ACTOR (container), &cx, &cy);
+              clutter_actor_get_transformed_position (from, &fx, &fy);
+              clutter_actor_get_transformed_size (from, &fw, &fh);
+
+              sort_data.box.x1 = fx - cx;
+              sort_data.box.x2 = fx - cx + fw;
+              sort_data.box.y1 = fy - cy;
+              sort_data.box.y2 = fy - cy + fh;
+            }
+        }
+      else
+        {
+          clutter_actor_get_allocation_box (CLUTTER_ACTOR (container), &sort_data.box);
+          switch (direction)
+            {
+            case GTK_DIR_UP:
+              sort_data.box.y1 = sort_data.box.y2;
+              break;
+            case GTK_DIR_DOWN:
+              sort_data.box.y2 = sort_data.box.y1;
+              break;
+            case GTK_DIR_LEFT:
+              sort_data.box.x1 = sort_data.box.x2;
+              break;
+            case GTK_DIR_RIGHT:
+              sort_data.box.x2 = sort_data.box.x1;
+              break;
+            default:
+              g_warn_if_reached ();
+            }
+        }
+      sort_data.direction = direction;
+
+      if (focus_child)
+        children = filter_by_position (children, &sort_data.box, direction);
+      if (children)
+        children = g_list_sort_with_data (children, sort_by_position, &sort_data);
+    }
+
+  /* Now try each child in turn */
+  for (l = children; l; l = l->next)
+    {
+      if (ST_IS_WIDGET (l->data))
+        {
+          if (st_widget_navigate_focus (l->data, from, direction, FALSE))
+            {
+              g_list_free (children);
+              return TRUE;
+            }
+        }
+    }
+
+  g_list_free (children);
+  return FALSE;
+}
+
 static void
 clutter_container_iface_init (ClutterContainerIface *iface)
 {
@@ -410,8 +729,14 @@ static void
 st_container_class_init (StContainerClass *klass)
 {
   GObjectClass *object_class = G_OBJECT_CLASS (klass);
+  StWidgetClass *widget_class = ST_WIDGET_CLASS (klass);
+  StContainerClass *container_class = ST_CONTAINER_CLASS (klass);
 
   g_type_class_add_private (klass, sizeof (StContainerPrivate));
 
   object_class->dispose = st_container_dispose;
+
+  widget_class->navigate_focus = st_container_navigate_focus;
+
+  container_class->get_focus_chain = st_container_real_get_focus_chain;
 }
diff --git a/src/st/st-container.h b/src/st/st-container.h
index ac9ef2c..a19a742 100644
--- a/src/st/st-container.h
+++ b/src/st/st-container.h
@@ -50,6 +50,8 @@ struct _StContainer {
 
 struct _StContainerClass {
   StWidgetClass parent_class;
+
+  GList * (*get_focus_chain) (StContainer *container);
 };
 
 GType   st_container_get_type             (void) G_GNUC_CONST;
@@ -57,11 +59,13 @@ GType   st_container_get_type             (void) G_GNUC_CONST;
 void    st_container_remove_all           (StContainer *container);
 void    st_container_destroy_children     (StContainer *container);
 
+GList * st_container_get_focus_chain      (StContainer *container);
+
 /* Only to be used by subclasses of StContainer */
 void    st_container_move_child           (StContainer  *container,
                                            ClutterActor *actor,
                                            int           pos);
-GList *st_container_get_children_list     (StContainer *container);
+GList * st_container_get_children_list    (StContainer *container);
 
 G_END_DECLS
 
diff --git a/src/st/st-entry.c b/src/st/st-entry.c
index ecb8ba7..b82da8a 100644
--- a/src/st/st-entry.c
+++ b/src/st/st-entry.c
@@ -219,6 +219,29 @@ st_entry_style_changed (StWidget *self)
   ST_WIDGET_CLASS (st_entry_parent_class)->style_changed (self);
 }
 
+static gboolean
+st_entry_navigate_focus (StWidget         *widget,
+                         ClutterActor     *from,
+                         GtkDirectionType  direction)
+{
+  StEntryPrivate *priv = ST_ENTRY_PRIV (widget);
+
+  /* This is basically the same as st_widget_real_navigate_focus(),
+   * except that widget is behaving as a proxy for priv->entry (which
+   * isn't an StWidget and so has no can-focus flag of its own).
+   */
+
+  if (from == priv->entry)
+    return FALSE;
+  else if (st_widget_get_can_focus (widget))
+    {
+      clutter_actor_grab_key_focus (priv->entry);
+      return TRUE;
+    }
+  else
+    return FALSE;
+}
+
 static void
 st_entry_get_preferred_width (ClutterActor *actor,
                               gfloat        for_height,
@@ -632,6 +655,7 @@ st_entry_class_init (StEntryClass *klass)
   actor_class->leave_event = st_entry_leave_event;
 
   widget_class->style_changed = st_entry_style_changed;
+  widget_class->navigate_focus = st_entry_navigate_focus;
 
   pspec = g_param_spec_object ("clutter-text",
 			       "Clutter Text",
diff --git a/src/st/st-overflow-box.c b/src/st/st-overflow-box.c
index 6d40049..23e1153 100644
--- a/src/st/st-overflow-box.c
+++ b/src/st/st-overflow-box.c
@@ -314,27 +314,36 @@ st_overflow_box_allocate (ClutterActor          *actor,
 }
 
 static void
-st_overflow_box_internal_paint (StOverflowBox *box)
+visible_children_iter_init (StOverflowBox  *box,
+                            GList         **iter,
+                            int            *n)
+{
+  *iter = st_container_get_children_list (ST_CONTAINER (box));
+  *n = 0;
+}
+
+static ClutterActor *
+visible_children_iter (StOverflowBox  *box,
+                       GList         **iter,
+                       int            *n)
 {
   StOverflowBoxPrivate *priv = box->priv;
-  GList *l, *children;
-  int i;
+  GList *l;
 
-  i = 0;
-  children = st_container_get_children_list (ST_CONTAINER (box));
-  for (l = children; i < priv->n_visible && l; l = l->next)
+  for (l = *iter; *n < priv->n_visible && l; l = l->next)
     {
       ClutterActor *child = (ClutterActor*) l->data;
 
       if (!CLUTTER_ACTOR_IS_VISIBLE (child))
         continue;
       if (!clutter_actor_get_fixed_position_set (child))
-        i++;
+        (*n)++;
 
-      clutter_actor_paint (child);
+      *iter = l->next;
+      return child;
     }
 
-  for (;l; l = l->next)
+  for (; l; l = l->next)
     {
       ClutterActor *child = (ClutterActor*) l->data;
 
@@ -342,8 +351,25 @@ st_overflow_box_internal_paint (StOverflowBox *box)
         continue;
 
       if (clutter_actor_get_fixed_position_set (child))
-        clutter_actor_paint (child);
+        {
+          *iter = l->next;
+          return child;
+        }
     }
+
+  return NULL;
+}
+
+static void
+st_overflow_box_internal_paint (StOverflowBox *box)
+{
+  ClutterActor *child;
+  GList *children;
+  int n;
+
+  visible_children_iter_init (box, &children, &n);
+  while ((child = visible_children_iter (box, &children, &n)))
+    clutter_actor_paint (child);
 }
 
 static void
@@ -379,12 +405,29 @@ st_overflow_box_style_changed (StWidget *self)
   ST_WIDGET_CLASS (st_overflow_box_parent_class)->style_changed (self);
 }
 
+static GList *
+st_overflow_box_get_focus_chain (StContainer *container)
+{
+  StOverflowBox *box = ST_OVERFLOW_BOX (container);
+  ClutterActor *child;
+  GList *children, *focus_chain;
+  int n;
+
+  focus_chain = NULL;
+  visible_children_iter_init (box, &children, &n);
+  while ((child = visible_children_iter (box, &children, &n)))
+    focus_chain = g_list_prepend (focus_chain, child);
+
+  return g_list_reverse (focus_chain);
+}
+
 static void
 st_overflow_box_class_init (StOverflowBoxClass *klass)
 {
   GObjectClass *object_class = G_OBJECT_CLASS (klass);
   ClutterActorClass *actor_class = CLUTTER_ACTOR_CLASS (klass);
   StWidgetClass *widget_class = ST_WIDGET_CLASS (klass);
+  StContainerClass *container_class = ST_CONTAINER_CLASS (klass);
   GParamSpec *pspec;
 
   g_type_class_add_private (klass, sizeof (StOverflowBoxPrivate));
@@ -400,6 +443,8 @@ st_overflow_box_class_init (StOverflowBoxClass *klass)
 
   widget_class->style_changed = st_overflow_box_style_changed;
 
+  container_class->get_focus_chain = st_overflow_box_get_focus_chain;
+
   pspec = g_param_spec_uint ("min-children",
                              "Min Children",
                              "The actor will request a minimum size large enough to include this many children",
diff --git a/src/st/st-widget.c b/src/st/st-widget.c
index 48c7a3d..32a5d23 100644
--- a/src/st/st-widget.c
+++ b/src/st/st-widget.c
@@ -64,6 +64,7 @@ struct _StWidgetPrivate
   gboolean      draw_border_internal : 1;
   gboolean      track_hover : 1;
   gboolean      hover : 1;
+  gboolean      can_focus : 1;
 
   StTooltip    *tooltip;
 
@@ -93,7 +94,8 @@ enum
   PROP_HAS_TOOLTIP,
   PROP_TOOLTIP_TEXT,
   PROP_TRACK_HOVER,
-  PROP_HOVER
+  PROP_HOVER,
+  PROP_CAN_FOCUS
 };
 
 enum
@@ -113,6 +115,9 @@ G_DEFINE_ABSTRACT_TYPE (StWidget, st_widget, CLUTTER_TYPE_ACTOR);
 
 static void st_widget_recompute_style (StWidget    *widget,
                                        StThemeNode *old_theme_node);
+static gboolean st_widget_real_navigate_focus (StWidget         *widget,
+                                               ClutterActor     *from,
+                                               GtkDirectionType  direction);
 
 static void
 st_widget_set_property (GObject      *gobject,
@@ -164,6 +169,10 @@ st_widget_set_property (GObject      *gobject,
       st_widget_set_hover (actor, g_value_get_boolean (value));
       break;
 
+    case PROP_CAN_FOCUS:
+      st_widget_set_can_focus (actor, g_value_get_boolean (value));
+      break;
+
     default:
       G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
       break;
@@ -217,6 +226,10 @@ st_widget_get_property (GObject    *gobject,
       g_value_set_boolean (value, priv->hover);
       break;
 
+    case PROP_CAN_FOCUS:
+      g_value_set_boolean (value, priv->can_focus);
+      break;
+
     default:
       G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
       break;
@@ -605,6 +618,22 @@ st_widget_leave (ClutterActor         *actor,
 }
 
 static void
+st_widget_key_focus_in (ClutterActor *actor)
+{
+  StWidget *widget = ST_WIDGET (actor);
+
+  st_widget_add_style_pseudo_class (widget, "focus");
+}
+
+static void
+st_widget_key_focus_out (ClutterActor *actor)
+{
+  StWidget *widget = ST_WIDGET (actor);
+
+  st_widget_remove_style_pseudo_class (widget, "focus");
+}
+
+static void
 st_widget_hide (ClutterActor *actor)
 {
   StWidget *widget = (StWidget *) actor;
@@ -642,9 +671,12 @@ st_widget_class_init (StWidgetClass *klass)
 
   actor_class->enter_event = st_widget_enter;
   actor_class->leave_event = st_widget_leave;
+  actor_class->key_focus_in = st_widget_key_focus_in;
+  actor_class->key_focus_out = st_widget_key_focus_out;
   actor_class->hide = st_widget_hide;
 
   klass->style_changed = st_widget_real_style_changed;
+  klass->navigate_focus = st_widget_real_navigate_focus;
 
   /**
    * StWidget:pseudo-class:
@@ -778,6 +810,20 @@ st_widget_class_init (StWidgetClass *klass)
                                    pspec);
 
   /**
+   * StWidget:can-focus:
+   *
+   * Whether or not the widget can be focused via keyboard navigation.
+   */
+  pspec = g_param_spec_boolean ("can-focus",
+                                "Can focus",
+                                "Whether the widget can be focused via keyboard navigation",
+                                FALSE,
+                                ST_PARAM_READWRITE);
+  g_object_class_install_property (gobject_class,
+                                   PROP_CAN_FOCUS,
+                                   pspec);
+
+  /**
    * StWidget::style-changed:
    *
    * Emitted when the style information that the widget derives from the
@@ -1624,6 +1670,113 @@ st_widget_get_hover (StWidget *widget)
   return widget->priv->hover;
 }
 
+/**
+ * st_widget_set_can_focus:
+ * @widget: A #StWidget
+ * @can_focus: %TRUE if the widget can receive keyboard focus
+ *   via keyboard navigation
+ *
+ * Marks @widget as being able to receive keyboard focus via
+ * keyboard navigation.
+ */
+void
+st_widget_set_can_focus (StWidget *widget,
+                         gboolean  can_focus)
+{
+  StWidgetPrivate *priv;
+
+  g_return_if_fail (ST_IS_WIDGET (widget));
+
+  priv = widget->priv;
+
+  if (priv->can_focus != can_focus)
+    {
+      priv->can_focus = can_focus;
+      g_object_notify (G_OBJECT (widget), "can-focus");
+    }
+}
+
+/**
+ * st_widget_get_can_focus:
+ * @widget: A #StWidget
+ *
+ * Returns the current value of the can-focus property. See
+ * st_widget_set_can_focus() for more information.
+ *
+ * Returns: current value of can-focus on @widget
+ */
+gboolean
+st_widget_get_can_focus (StWidget *widget)
+{
+  g_return_val_if_fail (ST_IS_WIDGET (widget), FALSE);
+
+  return widget->priv->can_focus;
+}
+
+static gboolean
+st_widget_real_navigate_focus (StWidget         *widget,
+                               ClutterActor     *from,
+                               GtkDirectionType  direction)
+{
+  if (widget->priv->can_focus &&
+      CLUTTER_ACTOR (widget) != from)
+    {
+      clutter_actor_grab_key_focus (CLUTTER_ACTOR (widget));
+      return TRUE;
+    }
+  return FALSE;
+}
+
+/**
+ * st_widget_navigate_focus:
+ * @widget: the "top level" container
+ * @from: (allow-none): the actor that the focus is coming from
+ * @direction: the direction focus is moving in
+ * @wrap_around: whether focus should wrap around
+ *
+ * Tries to update the keyboard focus within @widget in response to a
+ * keyboard event.
+ *
+ * If @from is a descendant of @widget, this attempts to move the
+ * keyboard focus to the next descendant of @widget (in the order
+ * implied by @direction) that has the #StWidget:can-focus property
+ * set. If @from is %NULL, or outside of @widget, this attempts to
+ * focus either @widget itself, or its first descendant in the order
+ * implied by @direction.
+ *
+ * If a container type is marked #StWidget:can-focus, the expected
+ * behavior is that it will only take up a single slot on the focus
+ * chain as a whole, rather than allowing navigation between its child
+ * actors (or having a distinction between itself being focused and
+ * one of its children being focused).
+ *
+ * Some widget classes might have slightly different behavior from the
+ * above, where that would make more sense.
+ *
+ * If @wrap_around is %TRUE and @from is a child of @widget, but the
+ * widget has no further children that can accept the focus in the
+ * given direction, then st_widget_navigate_focus() will try a second
+ * time, using a %NULL @from, which should cause it to reset the focus
+ * to the first available widget in the given direction.
+ *
+ * Return value: %TRUE if clutter_actor_grab_key_focus() has been
+ * called on an actor. %FALSE if not.
+ */
+gboolean
+st_widget_navigate_focus (StWidget         *widget,
+                          ClutterActor     *from,
+                          GtkDirectionType  direction,
+                          gboolean          wrap_around)
+{
+  g_return_val_if_fail (ST_IS_WIDGET (widget), FALSE);
+
+  if (ST_WIDGET_GET_CLASS (widget)->navigate_focus (widget, from, direction))
+    return TRUE;
+  if (wrap_around && from && clutter_actor_contains (CLUTTER_ACTOR (widget), from))
+    return ST_WIDGET_GET_CLASS (widget)->navigate_focus (widget, NULL, direction);
+  return FALSE;
+}
+
 static gboolean
 append_actor_text (GString      *desc,
                    ClutterActor *actor)
diff --git a/src/st/st-widget.h b/src/st/st-widget.h
index 2a65f8f..3f0e879 100644
--- a/src/st/st-widget.h
+++ b/src/st/st-widget.h
@@ -78,7 +78,10 @@ struct _StWidgetClass
   ClutterActorClass parent_class;
 
   /* vfuncs */
-  void (* style_changed)   (StWidget *self);
+  void     (* style_changed)   (StWidget         *self);
+  gboolean (* navigate_focus)  (StWidget         *self,
+                                ClutterActor     *from,
+                                GtkDirectionType  direction);
 };
 
 GType st_widget_get_type (void) G_GNUC_CONST;
@@ -137,6 +140,14 @@ StTextDirection       st_widget_get_direction             (StWidget        *self
 void                  st_widget_set_direction             (StWidget        *self,
                                                            StTextDirection  dir);
 
+void                  st_widget_set_can_focus             (StWidget        *widget,
+                                                           gboolean         can_focus);
+gboolean              st_widget_get_can_focus             (StWidget        *widget);
+gboolean              st_widget_navigate_focus            (StWidget        *widget,
+                                                           ClutterActor    *from,
+                                                           GtkDirectionType direction,
+                                                           gboolean         wrap_around);
+
 /* Only to be used by sub-classes of StWidget */
 void                  st_widget_style_changed             (StWidget        *widget);
 StThemeNode *         st_widget_get_theme_node            (StWidget        *widget);



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