From 3736d81d8f2556a06d95adc75ed73b06cf0e92b0 Mon Sep 17 00:00:00 2001 From: "Jasper St. Pierre" Date: Mon, 13 Feb 2012 19:55:30 -0500 Subject: [PATCH] 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 --- src/shell-generic-container.c | 10 +- src/st/st-container.c | 26 +-- src/st/st-container.h | 4 - src/st/st-widget.c | 290 +++++++++++++++++++++++++++++++++- src/st/st-widget.h | 5 + 5 files changed, 300 insertions(+), 35 deletions(-) diff --git a/src/shell-generic-container.c b/src/shell-generic-container.c index a3fc1b61c..382e79b7f 100644 --- a/src/shell-generic-container.c +++ b/src/shell-generic-container.c @@ -163,13 +163,13 @@ shell_generic_container_pick (ClutterActor *actor, } 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; 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; @@ -252,7 +252,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); + StWidgetClass *widget_class = ST_WIDGET_CLASS (klass); 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->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: diff --git a/src/st/st-container.c b/src/st/st-container.c index c2494079c..50a9a996e 100644 --- a/src/st/st-container.c +++ b/src/st/st-container.c @@ -183,8 +183,9 @@ st_container_get_children_list (StContainer *container) } 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; chain = NULL; @@ -199,23 +200,6 @@ st_container_real_get_focus_chain (StContainer *container) 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) @@ -649,7 +633,7 @@ st_container_navigate_focus (StWidget *widget, * "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 || direction == GTK_DIR_TAB_BACKWARD) { @@ -752,7 +736,6 @@ st_container_class_init (StContainerClass *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); 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; + widget_class->get_focus_chain = st_container_get_focus_chain; 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 023bef921..418ca431d 100644 --- a/src/st/st-container.h +++ b/src/st/st-container.h @@ -47,16 +47,12 @@ struct _StContainer { struct _StContainerClass { StWidgetClass parent_class; - - GList * (*get_focus_chain) (StContainer *container); }; GType st_container_get_type (void) G_GNUC_CONST; 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, diff --git a/src/st/st-widget.c b/src/st/st-widget.c index b8b61b0b4..2a8142709 100644 --- a/src/st/st-widget.c +++ b/src/st/st-widget.c @@ -677,6 +677,12 @@ st_widget_get_paint_volume (ClutterActor *self, return TRUE; } +static GList * +st_widget_real_get_focus_chain (StWidget *widget) +{ + return clutter_actor_get_children (CLUTTER_ACTOR (widget)); +} + static void 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->navigate_focus = st_widget_real_navigate_focus; klass->get_accessible_type = st_widget_accessible_get_type; + klass->get_focus_chain = st_widget_real_get_focus_chain; /** * StWidget:pseudo-class: @@ -1581,20 +1588,278 @@ st_widget_get_can_focus (StWidget *widget) 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 st_widget_real_navigate_focus (StWidget *widget, ClutterActor *from, GtkDirectionType direction) { - if (widget->priv->can_focus && - CLUTTER_ACTOR (widget) != from) + ClutterActor *widget_actor, *focus_child; + 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)); - return TRUE; + if (!focus_child) + { + /* 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; } + /** * st_widget_navigate_focus: * @widget: the "top level" container @@ -2087,3 +2352,20 @@ check_labels (StWidgetAccessible *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); +} diff --git a/src/st/st-widget.h b/src/st/st-widget.h index 81dc6c665..10becf885 100644 --- a/src/st/st-widget.h +++ b/src/st/st-widget.h @@ -86,6 +86,8 @@ struct _StWidgetClass ClutterActor *from, GtkDirectionType direction); GType (* get_accessible_type) (void); + + GList * (* get_focus_chain) (StWidget *widget); }; 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_peek_theme_node (StWidget *widget); +GList * st_widget_get_focus_chain (StWidget *widget); + + /* debug methods */ char *st_describe_actor (ClutterActor *actor); void st_set_slow_down_factor (gfloat factor);