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: <https://gitlab.gnome.org/GNOME/mutter/-/merge_requests/4089>
This commit is contained in:
Bilal Elmoussaoui 2024-10-17 11:41:28 +02:00 committed by Marge Bot
parent a211ed553b
commit 9d72f658af
8 changed files with 147 additions and 127 deletions

View File

@ -356,54 +356,34 @@ 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;
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);
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 (actor_state)
combined_state = atk_state_set_or_sets (parent_state,
actor_state);
else
combined_state = g_steal_pointer (&parent_state);
}
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);
}
}
return state_set;
return combined_state;
}
static gint

View File

@ -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

View File

@ -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_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);
}

View File

@ -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);

View File

@ -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;
}

View File

@ -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);

View File

@ -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

View File

@ -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);
}
}