diff --git a/src/shell-generic-container.c b/src/shell-generic-container.c index 5b439636c..e378d7211 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 30392972c..d6223956c 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 7a8dee2f0..2c828496f 100644 --- a/src/st/st-container.c +++ b/src/st/st-container.c @@ -28,6 +28,8 @@ #include "config.h" #endif +#include + #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 ac9ef2c06..a19a742b7 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 ecb8ba783..b82da8afe 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 6d40049c3..23e11534a 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 48c7a3dde..32a5d2388 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; @@ -604,6 +617,22 @@ st_widget_leave (ClutterActor *actor, return FALSE; } +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) { @@ -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: @@ -777,6 +809,20 @@ st_widget_class_init (StWidgetClass *klass) PROP_HOVER, 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: * @@ -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 2a65f8fff..3f0e87902 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);