From 9d72f658af1778f557d2e6a0038ae5e2d128aab9 Mon Sep 17 00:00:00 2001 From: Bilal Elmoussaoui Date: Thu, 17 Oct 2024 11:41:28 +0200 Subject: [PATCH] clutter: Add accessible state tracking Currently, this has been living in StWidget, moving that to Clutter allows us to properly track the accessibility state changes in the actors provided inside Clutter as well as simplifying things for a future move from Atk. Part-of: --- clutter/clutter/clutter-actor-accessible.c | 50 ++----- clutter/clutter/clutter-actor-private.h | 2 + clutter/clutter/clutter-actor.c | 136 +++++++++++++----- clutter/clutter/clutter-actor.h | 6 + clutter/clutter/clutter-stage-accessible.c | 26 ---- clutter/clutter/clutter-stage.c | 11 +- .../clutter/pango/clutter-text-accessible.c | 25 ---- clutter/clutter/pango/clutter-text.c | 18 ++- 8 files changed, 147 insertions(+), 127 deletions(-) diff --git a/clutter/clutter/clutter-actor-accessible.c b/clutter/clutter/clutter-actor-accessible.c index e2bb30513..c3d9a8685 100644 --- a/clutter/clutter/clutter-actor-accessible.c +++ b/clutter/clutter/clutter-actor-accessible.c @@ -355,55 +355,35 @@ clutter_actor_accessible_get_index_in_parent (AtkObject *obj) static AtkStateSet* clutter_actor_accessible_ref_state_set (AtkObject *obj) { - ClutterActor *actor = NULL; - AtkStateSet *state_set = NULL; - ClutterStage *stage = NULL; - ClutterActor *focus_actor = NULL; - ClutterActorAccessible * actor_accessible = NULL; + ClutterActor *actor = NULL; + g_autoptr (AtkStateSet) parent_state = NULL; + AtkStateSet *combined_state, *actor_state = NULL; + ClutterActorAccessible *actor_accessible = NULL; g_return_val_if_fail (CLUTTER_IS_ACTOR_ACCESSIBLE (obj), NULL); - actor_accessible = CLUTTER_ACTOR_ACCESSIBLE (obj); + actor_accessible = CLUTTER_ACTOR_ACCESSIBLE (obj); - state_set = ATK_OBJECT_CLASS (clutter_actor_accessible_parent_class)->ref_state_set (obj); + parent_state = ATK_OBJECT_CLASS (clutter_actor_accessible_parent_class)->ref_state_set (obj); actor = CLUTTER_ACTOR_FROM_ACCESSIBLE (actor_accessible); if (actor == NULL) /* Object is defunct */ { - atk_state_set_add_state (state_set, ATK_STATE_DEFUNCT); + atk_state_set_add_state (parent_state, ATK_STATE_DEFUNCT); + combined_state = g_steal_pointer (&parent_state); } else { - if (clutter_actor_get_reactive (actor)) - { - atk_state_set_add_state (state_set, ATK_STATE_SENSITIVE); - atk_state_set_add_state (state_set, ATK_STATE_ENABLED); - } + actor_state = clutter_actor_get_accessible_state (actor); - if (clutter_actor_is_visible (actor)) - { - atk_state_set_add_state (state_set, ATK_STATE_VISIBLE); - - /* It would be good to also check if the actor is on screen, - like the old and removed clutter_actor_is_on_stage*/ - if (clutter_actor_get_paint_visibility (actor)) - atk_state_set_add_state (state_set, ATK_STATE_SHOWING); - - } - - /* See focus section on implementation notes */ - atk_state_set_add_state (state_set, ATK_STATE_FOCUSABLE); - - stage = CLUTTER_STAGE (clutter_actor_get_stage (actor)); - if (stage != NULL) - { - focus_actor = clutter_stage_get_key_focus (stage); - if (focus_actor == actor) - atk_state_set_add_state (state_set, ATK_STATE_FOCUSED); - } + if (actor_state) + combined_state = atk_state_set_or_sets (parent_state, + actor_state); + else + combined_state = g_steal_pointer (&parent_state); } - return state_set; + return combined_state; } static gint diff --git a/clutter/clutter/clutter-actor-private.h b/clutter/clutter/clutter-actor-private.h index 354559245..ce9096b56 100644 --- a/clutter/clutter/clutter-actor-private.h +++ b/clutter/clutter/clutter-actor-private.h @@ -300,4 +300,6 @@ const GList * clutter_actor_peek_actions (ClutterActor *self); void clutter_actor_set_implicitly_grabbed (ClutterActor *actor, gboolean is_implicitly_grabbed); +AtkStateSet * clutter_actor_get_accessible_state (ClutterActor *actor); + G_END_DECLS diff --git a/clutter/clutter/clutter-actor.c b/clutter/clutter/clutter-actor.c index 46549824d..32806c63e 100644 --- a/clutter/clutter/clutter-actor.c +++ b/clutter/clutter/clutter-actor.c @@ -557,6 +557,7 @@ struct _ClutterActorPrivate /* Accessibility */ AtkObject *accessible; gchar *accessible_name; + AtkStateSet *accessible_state; /* request mode */ ClutterRequestMode request_mode; @@ -1431,7 +1432,6 @@ clutter_actor_real_map (ClutterActor *self) { ClutterActorPrivate *priv = self->priv; ClutterActor *iter; - AtkObject *accessible; g_assert (!clutter_actor_is_mapped (self)); @@ -1468,11 +1468,8 @@ clutter_actor_real_map (ClutterActor *self) */ g_object_notify_by_pspec (G_OBJECT (self), obj_props[PROP_MAPPED]); - accessible = clutter_actor_get_accessible (self); - if (accessible && !clutter_actor_is_painting_unmapped (self)) - atk_object_notify_state_change (accessible, - ATK_STATE_SHOWING, - TRUE); + if (!clutter_actor_is_painting_unmapped (self)) + clutter_actor_add_accessible_state (self, ATK_STATE_SHOWING); for (iter = priv->first_child; iter != NULL; @@ -1542,6 +1539,7 @@ maybe_unset_key_focus (ClutterActor *self) return; clutter_stage_set_key_focus (CLUTTER_STAGE (stage), NULL); + clutter_actor_remove_accessible_state (self, ATK_STATE_FOCUSED); } static void @@ -1573,7 +1571,6 @@ clutter_actor_real_unmap (ClutterActor *self) { ClutterActorPrivate *priv = self->priv; ClutterActor *iter; - AtkObject *accessible; g_assert (clutter_actor_is_mapped (self)); @@ -1605,11 +1602,8 @@ clutter_actor_real_unmap (ClutterActor *self) */ g_object_notify_by_pspec (G_OBJECT (self), obj_props[PROP_MAPPED]); - accessible = clutter_actor_get_accessible (self); - if (accessible && !clutter_actor_is_painting_unmapped (self)) - atk_object_notify_state_change (accessible, - ATK_STATE_SHOWING, - FALSE); + if (!clutter_actor_is_painting_unmapped (self)) + clutter_actor_remove_accessible_state (self, ATK_STATE_SHOWING); if (priv->n_pointers > 0) { @@ -1744,7 +1738,6 @@ void clutter_actor_show (ClutterActor *self) { ClutterActorPrivate *priv; - AtkObject *accessible; g_return_if_fail (CLUTTER_IS_ACTOR (self)); @@ -1782,11 +1775,7 @@ clutter_actor_show (ClutterActor *self) g_signal_emit (self, actor_signals[SHOW], 0); g_object_notify_by_pspec (G_OBJECT (self), obj_props[PROP_VISIBLE]); - accessible = clutter_actor_get_accessible (self); - if (accessible) - atk_object_notify_state_change (accessible, - ATK_STATE_VISIBLE, - TRUE); + clutter_actor_add_accessible_state (self, ATK_STATE_VISIBLE); if (priv->parent != NULL) clutter_actor_queue_redraw (self); @@ -1842,7 +1831,6 @@ void clutter_actor_hide (ClutterActor *self) { ClutterActorPrivate *priv; - AtkObject *accessible; g_return_if_fail (CLUTTER_IS_ACTOR (self)); @@ -1880,12 +1868,7 @@ clutter_actor_hide (ClutterActor *self) g_signal_emit (self, actor_signals[HIDE], 0); g_object_notify_by_pspec (G_OBJECT (self), obj_props[PROP_VISIBLE]); - accessible = clutter_actor_get_accessible (self); - if (accessible) - atk_object_notify_state_change (accessible, - ATK_STATE_VISIBLE, - FALSE); - + clutter_actor_remove_accessible_state (self, ATK_STATE_VISIBLE); if (priv->parent != NULL && priv->needs_allocation) clutter_actor_queue_redraw (priv->parent); @@ -5423,6 +5406,7 @@ clutter_actor_finalize (GObject *object) g_free (priv->name); g_free (priv->debug_name); + g_clear_object (&priv->accessible_state); G_OBJECT_CLASS (clutter_actor_parent_class)->finalize (object); } @@ -11878,7 +11862,6 @@ clutter_actor_set_reactive (ClutterActor *actor, gboolean reactive) { ClutterActorPrivate *priv; - AtkObject *accessible; g_return_if_fail (CLUTTER_IS_ACTOR (actor)); @@ -11894,11 +11877,16 @@ clutter_actor_set_reactive (ClutterActor *actor, g_object_notify_by_pspec (G_OBJECT (actor), obj_props[PROP_REACTIVE]); - accessible = clutter_actor_get_accessible (actor); - if (accessible) - atk_object_notify_state_change (accessible, - ATK_STATE_SENSITIVE, - reactive); + if (reactive) + { + clutter_actor_add_accessible_state (actor, ATK_STATE_SENSITIVE); + clutter_actor_add_accessible_state (actor, ATK_STATE_ENABLED); + } + else + { + clutter_actor_remove_accessible_state (actor, ATK_STATE_SENSITIVE); + clutter_actor_remove_accessible_state (actor, ATK_STATE_ENABLED); + } if (!clutter_actor_get_reactive (actor) && priv->n_pointers > 0) { @@ -13003,7 +12991,10 @@ clutter_actor_grab_key_focus (ClutterActor *self) stage = _clutter_actor_get_stage_internal (self); if (stage != NULL) - clutter_stage_set_key_focus (CLUTTER_STAGE (stage), self); + { + clutter_stage_set_key_focus (CLUTTER_STAGE (stage), self); + clutter_actor_add_accessible_state (self, ATK_STATE_FOCUSED); + } } #ifdef HAVE_FONTS @@ -18802,3 +18793,84 @@ clutter_actor_get_accessible_role (ClutterActor *self) return role; } + +AtkStateSet * +clutter_actor_get_accessible_state (ClutterActor *actor) +{ + ClutterActorPrivate *priv; + + g_return_val_if_fail (CLUTTER_IS_ACTOR (actor), NULL); + + priv = clutter_actor_get_instance_private (actor); + + return priv->accessible_state; +} + +/** + * clutter_actor_add_accessible_state: + * @actor: A #ClutterActor + * @state: #AtkStateType state to add + * + * This method adds @state as one of the accessible states for + * @actor. The list of states of an actor describes the current state + * of user interface element @actor and is provided so that assistive + * technologies know how to present @actor to the user. + * + * Usually you will have no need to add accessible states for an + * object, as the accessible object can extract most of the states + * from the object itself. + * This method is only required when one cannot extract the + * information automatically from the object itself (i.e.: a generic + * container used as a toggle menu item will not automatically include + * the toggled state). + */ +void +clutter_actor_add_accessible_state (ClutterActor *actor, + AtkStateType state) +{ + ClutterActorPrivate *priv; + AtkObject *accessible; + + g_return_if_fail (CLUTTER_IS_ACTOR (actor)); + + priv = clutter_actor_get_instance_private (actor); + accessible = clutter_actor_get_accessible (actor); + + if (G_UNLIKELY (priv->accessible_state == NULL)) + { + priv->accessible_state = atk_state_set_new (); + /* Actors are all focusable until we merge focus management from St */ + atk_state_set_add_state (priv->accessible_state, ATK_STATE_FOCUSABLE); + } + + if (atk_state_set_add_state (priv->accessible_state, state) && accessible) + atk_object_notify_state_change (accessible, state, TRUE); +} + +/** + * clutter_actor_remove_accessible_state: + * @actor: A #ClutterActor + * @state: #AtkState state to remove + * + * This method removes @state as on of the accessible states for + * @actor. See [method@Clutter.Actor.add_accessible_state] for more information. + * + */ +void +clutter_actor_remove_accessible_state (ClutterActor *actor, + AtkStateType state) +{ + ClutterActorPrivate *priv; + AtkObject *accessible; + + g_return_if_fail (CLUTTER_IS_ACTOR (actor)); + + priv = clutter_actor_get_instance_private (actor); + accessible = clutter_actor_get_accessible (actor); + + if (G_UNLIKELY (priv->accessible_state == NULL)) + return; + + if (atk_state_set_remove_state (priv->accessible_state, state) && accessible) + atk_object_notify_state_change (accessible, state, FALSE); +} diff --git a/clutter/clutter/clutter-actor.h b/clutter/clutter/clutter-actor.h index e4e9938f8..917ee83e1 100644 --- a/clutter/clutter/clutter-actor.h +++ b/clutter/clutter/clutter-actor.h @@ -323,6 +323,12 @@ CLUTTER_EXPORT void clutter_actor_set_accessible (ClutterActor *self, AtkObject *accessible); CLUTTER_EXPORT +void clutter_actor_add_accessible_state (ClutterActor *actor, + AtkStateType state); +CLUTTER_EXPORT +void clutter_actor_remove_accessible_state (ClutterActor *actor, + AtkStateType state); +CLUTTER_EXPORT gboolean clutter_actor_is_visible (ClutterActor *self); CLUTTER_EXPORT gboolean clutter_actor_is_mapped (ClutterActor *self); diff --git a/clutter/clutter/clutter-stage-accessible.c b/clutter/clutter/clutter-stage-accessible.c index 42d759d9e..82e67d361 100644 --- a/clutter/clutter/clutter-stage-accessible.c +++ b/clutter/clutter/clutter-stage-accessible.c @@ -55,28 +55,6 @@ clutter_stage_accessible_init (ClutterStageAccessible *stage_accessible) { } -static AtkStateSet* -clutter_stage_accessible_ref_state_set (AtkObject *obj) -{ - ClutterStageAccessible *stage_accessible; - AtkStateSet *state_set; - ClutterStage *stage; - - g_return_val_if_fail (CLUTTER_IS_STAGE_ACCESSIBLE (obj), NULL); - stage_accessible = CLUTTER_STAGE_ACCESSIBLE (obj); - - state_set = ATK_OBJECT_CLASS (clutter_stage_accessible_parent_class)->ref_state_set (obj); - stage = CLUTTER_STAGE (CLUTTER_ACTOR_FROM_ACCESSIBLE (stage_accessible)); - - if (stage == NULL) - return state_set; - - if (clutter_stage_is_active (stage)) - atk_state_set_add_state (state_set, ATK_STATE_ACTIVE); - - return state_set; -} - /* AtkWindow */ static void clutter_stage_accessible_window_interface_init (AtkWindowIface *iface) @@ -87,8 +65,4 @@ clutter_stage_accessible_window_interface_init (AtkWindowIface *iface) static void clutter_stage_accessible_class_init (ClutterStageAccessibleClass *klass) { - AtkObjectClass *class = ATK_OBJECT_CLASS (klass); - - /* AtkObject */ - class->ref_state_set = clutter_stage_accessible_ref_state_set; } diff --git a/clutter/clutter/clutter-stage.c b/clutter/clutter/clutter-stage.c index 10d4d76e3..c75e6fe9a 100644 --- a/clutter/clutter/clutter-stage.c +++ b/clutter/clutter/clutter-stage.c @@ -691,12 +691,17 @@ clutter_stage_set_active (ClutterStage *stage, return; priv->is_active = is_active; + + if (is_active) + clutter_actor_add_accessible_state (CLUTTER_ACTOR (stage), + ATK_STATE_ACTIVE); + else + clutter_actor_remove_accessible_state (CLUTTER_ACTOR (stage), + ATK_STATE_ACTIVE); + accessible = clutter_actor_get_accessible (CLUTTER_ACTOR (stage)); if (accessible) { - atk_object_notify_state_change (accessible, - ATK_STATE_ACTIVE, - priv->is_active); /* Emit AtkWindow signals */ if (priv->is_active) g_signal_emit_by_name (accessible, "activate", 0); diff --git a/clutter/clutter/pango/clutter-text-accessible.c b/clutter/clutter/pango/clutter-text-accessible.c index e93da8b54..321282300 100644 --- a/clutter/clutter/pango/clutter-text-accessible.c +++ b/clutter/clutter/pango/clutter-text-accessible.c @@ -48,8 +48,6 @@ static void cally_text_finalize (GObject *obj); /* AtkObject */ static void cally_text_real_initialize (AtkObject *obj, gpointer data); -static AtkStateSet* cally_text_ref_state_set (AtkObject *obj); - /* atkaction */ static void _cally_text_activate_action (ClutterActorAccessible *accessible_actor); @@ -246,7 +244,6 @@ clutter_text_accessible_class_init (ClutterTextAccessibleClass *klass) gobject_class->finalize = cally_text_finalize; class->initialize = cally_text_real_initialize; - class->ref_state_set = cally_text_ref_state_set; } static void @@ -317,28 +314,6 @@ cally_text_real_initialize(AtkObject *obj, atk_object_set_role (obj, ATK_ROLE_TEXT); } -static AtkStateSet* -cally_text_ref_state_set (AtkObject *obj) -{ - AtkStateSet *result = NULL; - ClutterActor *actor = NULL; - - result = ATK_OBJECT_CLASS (clutter_text_accessible_parent_class)->ref_state_set (obj); - - actor = CLUTTER_ACTOR_FROM_ACCESSIBLE (obj); - - if (actor == NULL) - return result; - - if (clutter_text_get_editable (CLUTTER_TEXT (actor))) - atk_state_set_add_state (result, ATK_STATE_EDITABLE); - - if (clutter_text_get_selectable (CLUTTER_TEXT (actor))) - atk_state_set_add_state (result, ATK_STATE_SELECTABLE_TEXT); - - return result; -} - /***** pango stuff **** * * FIXME: all this pango related code used to implement diff --git a/clutter/clutter/pango/clutter-text.c b/clutter/clutter/pango/clutter-text.c index 416fbc6dd..01c389ef9 100644 --- a/clutter/clutter/pango/clutter-text.c +++ b/clutter/clutter/pango/clutter-text.c @@ -4856,7 +4856,6 @@ clutter_text_set_editable (ClutterText *self, ClutterBackend *backend = clutter_context_get_backend (context); ClutterInputMethod *method = clutter_backend_get_input_method (backend); ClutterTextPrivate *priv; - AtkObject *accessible; g_return_if_fail (CLUTTER_IS_TEXT (self)); @@ -4864,7 +4863,6 @@ clutter_text_set_editable (ClutterText *self, if (priv->editable != editable) { - accessible = clutter_actor_get_accessible (CLUTTER_ACTOR (self)); priv->editable = editable; if (method) @@ -4878,10 +4876,12 @@ clutter_text_set_editable (ClutterText *self, clutter_text_queue_redraw (CLUTTER_ACTOR (self)); g_object_notify_by_pspec (G_OBJECT (self), obj_props[PROP_EDITABLE]); - if (accessible) - atk_object_notify_state_change (accessible, - ATK_STATE_EDITABLE, - priv->editable); + if (editable) + clutter_actor_add_accessible_state (CLUTTER_ACTOR (self), + ATK_STATE_EDITABLE); + else + clutter_actor_remove_accessible_state (CLUTTER_ACTOR (self), + ATK_STATE_EDITABLE); } } @@ -4931,6 +4931,12 @@ clutter_text_set_selectable (ClutterText *self, clutter_text_queue_redraw (CLUTTER_ACTOR (self)); g_object_notify_by_pspec (G_OBJECT (self), obj_props[PROP_SELECTABLE]); + if (selectable) + clutter_actor_add_accessible_state (CLUTTER_ACTOR (self), + ATK_STATE_SELECTABLE_TEXT); + else + clutter_actor_remove_accessible_state (CLUTTER_ACTOR (self), + ATK_STATE_SELECTABLE_TEXT); } }