st-widget: Copy get_focus_chain and navigate_focus from StContainer

We can't get rid of the implementations in StContainer just yet,
as StContainer still keeps its own child list. But this should
lower the amount of code that has to be moved around when we
remove StContainer.

https://bugzilla.gnome.org/show_bug.cgi?id=670034
This commit is contained in:
Jasper St. Pierre 2012-02-13 19:55:30 -05:00
parent fbcea03ab3
commit 3736d81d8f
5 changed files with 300 additions and 35 deletions

View File

@ -163,13 +163,13 @@ shell_generic_container_pick (ClutterActor *actor,
} }
static GList * static GList *
shell_generic_container_get_focus_chain (StContainer *container) shell_generic_container_get_focus_chain (StWidget *widget)
{ {
ShellGenericContainer *self = SHELL_GENERIC_CONTAINER (container); ShellGenericContainer *self = SHELL_GENERIC_CONTAINER (widget);
GList *children, *focus_chain; GList *children, *focus_chain;
focus_chain = NULL; focus_chain = NULL;
for (children = st_container_get_children_list (container); children; children = children->next) for (children = st_container_get_children_list (ST_CONTAINER (widget)); children; children = children->next)
{ {
ClutterActor *child = children->data; ClutterActor *child = children->data;
@ -252,7 +252,7 @@ shell_generic_container_class_init (ShellGenericContainerClass *klass)
{ {
GObjectClass *gobject_class = G_OBJECT_CLASS (klass); GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
ClutterActorClass *actor_class = CLUTTER_ACTOR_CLASS (klass); ClutterActorClass *actor_class = CLUTTER_ACTOR_CLASS (klass);
StContainerClass *container_class = ST_CONTAINER_CLASS (klass); StWidgetClass *widget_class = ST_WIDGET_CLASS (klass);
gobject_class->finalize = shell_generic_container_finalize; gobject_class->finalize = shell_generic_container_finalize;
@ -262,7 +262,7 @@ shell_generic_container_class_init (ShellGenericContainerClass *klass)
actor_class->paint = shell_generic_container_paint; actor_class->paint = shell_generic_container_paint;
actor_class->pick = shell_generic_container_pick; actor_class->pick = shell_generic_container_pick;
container_class->get_focus_chain = shell_generic_container_get_focus_chain; widget_class->get_focus_chain = shell_generic_container_get_focus_chain;
/** /**
* ShellGenericContainer::get-preferred-width: * ShellGenericContainer::get-preferred-width:

View File

@ -183,8 +183,9 @@ st_container_get_children_list (StContainer *container)
} }
static GList * static GList *
st_container_real_get_focus_chain (StContainer *container) st_container_get_focus_chain (StWidget *widget)
{ {
StContainer *container = ST_CONTAINER (widget);
GList *chain, *children; GList *chain, *children;
chain = NULL; chain = NULL;
@ -199,23 +200,6 @@ st_container_real_get_focus_chain (StContainer *container)
return g_list_reverse (chain); 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 static gint
sort_z_order (gconstpointer a, sort_z_order (gconstpointer a,
gconstpointer b) gconstpointer b)
@ -649,7 +633,7 @@ st_container_navigate_focus (StWidget *widget,
* "first" being determined by @direction.) * "first" being determined by @direction.)
*/ */
children = st_container_get_focus_chain (container); children = st_widget_get_focus_chain (ST_WIDGET (container));
if (direction == GTK_DIR_TAB_FORWARD || if (direction == GTK_DIR_TAB_FORWARD ||
direction == GTK_DIR_TAB_BACKWARD) direction == GTK_DIR_TAB_BACKWARD)
{ {
@ -752,7 +736,6 @@ st_container_class_init (StContainerClass *klass)
GObjectClass *object_class = G_OBJECT_CLASS (klass); GObjectClass *object_class = G_OBJECT_CLASS (klass);
ClutterActorClass *actor_class = CLUTTER_ACTOR_CLASS (klass); ClutterActorClass *actor_class = CLUTTER_ACTOR_CLASS (klass);
StWidgetClass *widget_class = ST_WIDGET_CLASS (klass); StWidgetClass *widget_class = ST_WIDGET_CLASS (klass);
StContainerClass *container_class = ST_CONTAINER_CLASS (klass);
g_type_class_add_private (klass, sizeof (StContainerPrivate)); g_type_class_add_private (klass, sizeof (StContainerPrivate));
@ -760,7 +743,6 @@ st_container_class_init (StContainerClass *klass)
actor_class->get_paint_volume = st_container_get_paint_volume; actor_class->get_paint_volume = st_container_get_paint_volume;
widget_class->get_focus_chain = st_container_get_focus_chain;
widget_class->navigate_focus = st_container_navigate_focus; widget_class->navigate_focus = st_container_navigate_focus;
container_class->get_focus_chain = st_container_real_get_focus_chain;
} }

View File

@ -47,16 +47,12 @@ struct _StContainer {
struct _StContainerClass { struct _StContainerClass {
StWidgetClass parent_class; StWidgetClass parent_class;
GList * (*get_focus_chain) (StContainer *container);
}; };
GType st_container_get_type (void) G_GNUC_CONST; GType st_container_get_type (void) G_GNUC_CONST;
void st_container_destroy_children (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 */ /* Only to be used by subclasses of StContainer */
void st_container_move_child (StContainer *container, void st_container_move_child (StContainer *container,
ClutterActor *actor, ClutterActor *actor,

View File

@ -677,6 +677,12 @@ st_widget_get_paint_volume (ClutterActor *self,
return TRUE; return TRUE;
} }
static GList *
st_widget_real_get_focus_chain (StWidget *widget)
{
return clutter_actor_get_children (CLUTTER_ACTOR (widget));
}
static void static void
st_widget_class_init (StWidgetClass *klass) st_widget_class_init (StWidgetClass *klass)
@ -711,6 +717,7 @@ st_widget_class_init (StWidgetClass *klass)
klass->style_changed = st_widget_real_style_changed; klass->style_changed = st_widget_real_style_changed;
klass->navigate_focus = st_widget_real_navigate_focus; klass->navigate_focus = st_widget_real_navigate_focus;
klass->get_accessible_type = st_widget_accessible_get_type; klass->get_accessible_type = st_widget_accessible_get_type;
klass->get_focus_chain = st_widget_real_get_focus_chain;
/** /**
* StWidget:pseudo-class: * StWidget:pseudo-class:
@ -1581,20 +1588,278 @@ st_widget_get_can_focus (StWidget *widget)
return widget->priv->can_focus; return widget->priv->can_focus;
} }
/* 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. To account for floating-
* point imprecision, an actor is "down" (etc.) from an another
* actor even if it overlaps it by up to 0.1 pixels.
*/
switch (direction)
{
case GTK_DIR_UP:
if (cbox.y2 > rbox->y1 + 0.1)
continue;
if (cbox.x1 >= rbox->x2 || cbox.x2 <= rbox->x1)
continue;
break;
case GTK_DIR_DOWN:
if (cbox.y1 < rbox->y2 - 0.1)
continue;
if (cbox.x1 >= rbox->x2 || cbox.x2 <= rbox->x1)
continue;
break;
case GTK_DIR_LEFT:
if (cbox.x2 > rbox->x1 + 0.1)
continue;
if (cbox.y1 >= rbox->y2 || cbox.y2 <= rbox->y1)
continue;
break;
case GTK_DIR_RIGHT:
if (cbox.x1 < rbox->x2 - 0.1)
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;
} StWidgetChildSortData;
static int
sort_by_position (gconstpointer a,
gconstpointer b,
gpointer user_data)
{
ClutterActor *actor_a = (ClutterActor *)a;
ClutterActor *actor_b = (ClutterActor *)b;
StWidgetChildSortData *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 static gboolean
st_widget_real_navigate_focus (StWidget *widget, st_widget_real_navigate_focus (StWidget *widget,
ClutterActor *from, ClutterActor *from,
GtkDirectionType direction) GtkDirectionType direction)
{ {
if (widget->priv->can_focus && ClutterActor *widget_actor, *focus_child;
CLUTTER_ACTOR (widget) != from) GList *children, *l;
widget_actor = CLUTTER_ACTOR (widget);
if (from == widget_actor)
return FALSE;
/* Figure out if @from is a descendant of @widget, and if so,
* set @focus_child to the immediate child of @widget that
* contains (or *is*) @from.
*/
focus_child = from;
while (focus_child && clutter_actor_get_parent (focus_child) != widget_actor)
focus_child = clutter_actor_get_parent (focus_child);
if (widget->priv->can_focus)
{ {
clutter_actor_grab_key_focus (CLUTTER_ACTOR (widget)); if (!focus_child)
return TRUE; {
/* Accept focus from outside */
clutter_actor_grab_key_focus (widget_actor);
return TRUE;
}
else
{
/* Yield focus from within: since @widget itself is
* focusable we don't allow the focus to be navigated
* within @widget.
*/
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
* @widget'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_widget_get_focus_chain (widget);
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 */
{
StWidgetChildSortData sort_data;
/* Compute the allocation box of the previous focused actor, in
* @widget's coordinate space. If there was no previous focus,
* use the coordinates of the appropriate edge of @widget.
*
* Note that all of this code assumes the actors are not
* transformed (or at most, they are all scaled by the same
* amount). If @widget or any of its children is rotated, or
* any child is inconsistently scaled, then the focus chain will
* probably be unpredictable.
*/
if (focus_child)
{
clutter_actor_get_allocation_box (focus_child, &sort_data.box);
}
else
{
clutter_actor_get_allocation_box (CLUTTER_ACTOR (widget), &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; return FALSE;
} }
/** /**
* st_widget_navigate_focus: * st_widget_navigate_focus:
* @widget: the "top level" container * @widget: the "top level" container
@ -2087,3 +2352,20 @@ check_labels (StWidgetAccessible *widget_accessible,
ATK_OBJECT (widget_accessible)); ATK_OBJECT (widget_accessible));
} }
} }
/**
* st_widget_get_focus_chain:
* @widget: An #StWidget
*
* Gets a list of the focusable children of @widget, in "Tab"
* order. By default, this returns all visible
* (as in CLUTTER_ACTOR_IS_VISIBLE()) children of @widget.
*
* Returns: (element-type Clutter.Actor) (transfer container):
* @widget's focusable children
*/
GList *
st_widget_get_focus_chain (StWidget *widget)
{
return ST_WIDGET_GET_CLASS (widget)->get_focus_chain (widget);
}

View File

@ -86,6 +86,8 @@ struct _StWidgetClass
ClutterActor *from, ClutterActor *from,
GtkDirectionType direction); GtkDirectionType direction);
GType (* get_accessible_type) (void); GType (* get_accessible_type) (void);
GList * (* get_focus_chain) (StWidget *widget);
}; };
GType st_widget_get_type (void) G_GNUC_CONST; GType st_widget_get_type (void) G_GNUC_CONST;
@ -151,6 +153,9 @@ void st_widget_style_changed (StWidget *widg
StThemeNode * st_widget_get_theme_node (StWidget *widget); StThemeNode * st_widget_get_theme_node (StWidget *widget);
StThemeNode * st_widget_peek_theme_node (StWidget *widget); StThemeNode * st_widget_peek_theme_node (StWidget *widget);
GList * st_widget_get_focus_chain (StWidget *widget);
/* debug methods */ /* debug methods */
char *st_describe_actor (ClutterActor *actor); char *st_describe_actor (ClutterActor *actor);
void st_set_slow_down_factor (gfloat factor); void st_set_slow_down_factor (gfloat factor);