mutter/clutter/clutter/clutter-actor-accessible.c
Bilal Elmoussaoui 4eb46eb3d4 clutter/actor: Remove has_accessible
Instead, the users of the API can check if get_accessible returns NULL
which is more correct and avoids the extra vfunc that is not even used

Part-of: <https://gitlab.gnome.org/GNOME/mutter/-/merge_requests/3917>
2024-08-07 22:46:16 +00:00

570 lines
17 KiB
C

/* Clutter.
*
* Copyright (C) 2008 Igalia, S.L.
*
* Author: Alejandro Piñeiro Iglesias <apinheiro@igalia.com>
*
* Some parts are based on GailWidget from GAIL
* GAIL - The GNOME Accessibility Implementation Library
* Copyright 2001, 2002, 2003 Sun Microsystems Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*/
/**
* ClutterActorAccessible:
*
* Implementation of the ATK interfaces for [class@Clutter.Actor]
*
* #ClutterActorAccessible implements the required ATK interfaces of [class@Clutter.Actor]
* exposing the common elements on each actor (position, extents, etc).
*/
/*
*
* IMPLEMENTATION NOTES:
*
* ####
*
* Focus: clutter hasn't got the focus concept in the same way that GTK, but it
* has a key focus managed by the stage. Basically any actor can be focused using
* clutter_stage_set_key_focus. So, we will use this approach: all actors are
* focusable, and we get the currently focused using clutter_stage_get_key_focus
* This affects focus related stateset and some atk_component focus methods (like
* grab focus).
*
* In the same way, we will manage the focus state change management
* on the cally-stage object. The reason is avoid missing a focus
* state change event if the object is focused just before the
* accessibility object being created.
*
* #AtkAction implementation: on previous releases ClutterActor added
* the actions "press", "release" and "click", as at that time some
* general-purpose actors like textures were directly used as buttons.
*
* But now, new toolkits appeared, providing high-level widgets, like
* buttons. So in this environment, it doesn't make sense to keep
* adding them as default.
*
* Anyway, current implementation of AtkAction is done at ClutterActorAccessible
* providing methods to add and remove actions. This is based on the
* one used at gailcell, and proposed as a change on #AtkAction
* interface:
*
* https://bugzilla.gnome.org/show_bug.cgi?id=649804
*
*/
#include "config.h"
#include <math.h>
#include <atk/atk.h>
#include <glib.h>
#include "clutter/clutter-actor-private.h"
#include "clutter/clutter-actor-accessible.h"
#include "clutter/clutter-stage.h"
/* AtkComponent.h */
static void clutter_actor_accessible_component_interface_init (AtkComponentIface *iface);
struct _ClutterActorAccessiblePrivate
{
GList *children;
};
G_DEFINE_TYPE_WITH_CODE (ClutterActorAccessible,
clutter_actor_accessible,
ATK_TYPE_GOBJECT_ACCESSIBLE,
G_ADD_PRIVATE (ClutterActorAccessible)
G_IMPLEMENT_INTERFACE (ATK_TYPE_COMPONENT,
clutter_actor_accessible_component_interface_init));
/* ClutterContainer */
static gint
clutter_actor_accessible_add_actor (ClutterActor *container,
ClutterActor *actor,
gpointer data)
{
AtkObject *atk_parent = clutter_actor_get_accessible (container);
AtkObject *atk_child = clutter_actor_get_accessible (actor);
ClutterActorAccessiblePrivate *priv = clutter_actor_accessible_get_instance_private (CLUTTER_ACTOR_ACCESSIBLE (atk_parent));
gint index;
g_return_val_if_fail (CLUTTER_IS_ACTOR (container), 0);
g_return_val_if_fail (CLUTTER_IS_ACTOR (actor), 0);
g_object_notify (G_OBJECT (atk_child), "accessible_parent");
g_list_free (priv->children);
priv->children = clutter_actor_get_children (CLUTTER_ACTOR (container));
index = g_list_index (priv->children, actor);
g_signal_emit_by_name (atk_parent, "children_changed::add",
index, atk_child, NULL);
return 1;
}
static gint
clutter_actor_accessible_remove_actor (ClutterActor *container,
ClutterActor *actor,
gpointer data)
{
g_autoptr (AtkObject) atk_child = NULL;
AtkPropertyValues values = { NULL };
AtkObject *atk_parent = NULL;
ClutterActorAccessiblePrivate *priv = NULL;
gint index;
g_return_val_if_fail (CLUTTER_IS_ACTOR (container), 0);
g_return_val_if_fail (CLUTTER_IS_ACTOR (actor), 0);
atk_parent = clutter_actor_get_accessible (container);
atk_child = clutter_actor_get_accessible (actor);
if (atk_child)
{
g_assert (ATK_IS_OBJECT (atk_child));
g_object_ref (atk_child);
g_value_init (&values.old_value, G_TYPE_POINTER);
g_value_set_pointer (&values.old_value, atk_parent);
values.property_name = "accessible-parent";
g_signal_emit_by_name (atk_child,
"property_change::accessible-parent", &values, NULL);
}
priv = clutter_actor_accessible_get_instance_private (CLUTTER_ACTOR_ACCESSIBLE (atk_parent));
index = g_list_index (priv->children, actor);
g_list_free (priv->children);
priv->children = clutter_actor_get_children (CLUTTER_ACTOR (container));
if (index >= 0 && index <= g_list_length (priv->children))
g_signal_emit_by_name (atk_parent, "children_changed::remove",
index, atk_child, NULL);
return 1;
}
static void
clutter_actor_accessible_initialize (AtkObject *obj,
gpointer data)
{
ClutterActorAccessible *self = NULL;
ClutterActorAccessiblePrivate *priv = NULL;
ClutterActor *actor = NULL;
guint handler_id;
ATK_OBJECT_CLASS (clutter_actor_accessible_parent_class)->initialize (obj, data);
self = CLUTTER_ACTOR_ACCESSIBLE (obj);
priv = clutter_actor_accessible_get_instance_private (self);
actor = CLUTTER_ACTOR (data);
g_object_set_data (G_OBJECT (obj), "atk-component-layer",
GINT_TO_POINTER (ATK_LAYER_MDI));
priv->children = clutter_actor_get_children (actor);
/*
* We store the handler ids for these signals in case some objects
* need to remove these handlers.
*/
handler_id = g_signal_connect (actor,
"child-added",
G_CALLBACK (clutter_actor_accessible_add_actor),
obj);
g_object_set_data (G_OBJECT (obj), "cally-add-handler-id",
GUINT_TO_POINTER (handler_id));
handler_id = g_signal_connect (actor,
"child-removed",
G_CALLBACK (clutter_actor_accessible_remove_actor),
obj);
g_object_set_data (G_OBJECT (obj), "cally-remove-handler-id",
GUINT_TO_POINTER (handler_id));
obj->role = ATK_ROLE_PANEL; /* typically objects implementing ClutterContainer
interface would be a panel */
}
/* AtkObject */
static const gchar *
clutter_actor_accessible_get_name (AtkObject *obj)
{
const gchar* name = NULL;
ClutterActor *actor;
g_return_val_if_fail (CLUTTER_IS_ACTOR_ACCESSIBLE (obj), NULL);
actor = CLUTTER_ACTOR_FROM_ACCESSIBLE (obj);
if (actor)
name = clutter_actor_get_accessible_name (actor);
if (!name)
name = ATK_OBJECT_CLASS (clutter_actor_accessible_parent_class)->get_name (obj);
return name;
}
static AtkRole
clutter_actor_accessible_get_role (AtkObject *obj)
{
ClutterActor *actor = NULL;
AtkRole role = ATK_ROLE_INVALID;
g_return_val_if_fail (CLUTTER_IS_ACTOR_ACCESSIBLE (obj), role);
actor = CLUTTER_ACTOR_FROM_ACCESSIBLE (obj);
if (actor == NULL)
return role;
role = actor->accessible_role;
if (role == ATK_ROLE_INVALID)
role = ATK_OBJECT_CLASS (clutter_actor_accessible_parent_class)->get_role (obj);
return role;
}
static void
clutter_actor_accessible_init (ClutterActorAccessible *actor_accessible)
{
ClutterActorAccessiblePrivate *priv = clutter_actor_accessible_get_instance_private (actor_accessible);
priv->children = NULL;
}
static void
clutter_actor_accessible_finalize (GObject *obj)
{
ClutterActorAccessible *actor_accessible =
CLUTTER_ACTOR_ACCESSIBLE (obj);
ClutterActorAccessiblePrivate *priv =
clutter_actor_accessible_get_instance_private (actor_accessible);
if (priv->children)
{
g_list_free (priv->children);
priv->children = NULL;
}
G_OBJECT_CLASS (clutter_actor_accessible_parent_class)->finalize (obj);
}
static AtkObject *
clutter_actor_accessible_get_parent (AtkObject *obj)
{
ClutterActor *parent_actor = NULL;
AtkObject *parent = NULL;
ClutterActor *actor = NULL;
ClutterActorAccessible *actor_accessible = NULL;
g_return_val_if_fail (CLUTTER_IS_ACTOR_ACCESSIBLE (obj), NULL);
/* Check if we have and assigned parent */
if (obj->accessible_parent)
return obj->accessible_parent;
/* Try to get it from the clutter parent */
actor_accessible = CLUTTER_ACTOR_ACCESSIBLE (obj);
actor = CLUTTER_ACTOR_FROM_ACCESSIBLE (actor_accessible);
if (actor == NULL) /* Object is defunct */
return NULL;
parent_actor = clutter_actor_get_parent (actor);
if (parent_actor == NULL)
return NULL;
parent = clutter_actor_get_accessible (parent_actor);
/* FIXME: I need to review the clutter-embed, to check if in this case I
* should get the widget accessible
*/
return parent;
}
static gint
clutter_actor_accessible_get_index_in_parent (AtkObject *obj)
{
ClutterActorAccessible *actor_accessible = NULL;
ClutterActor *actor = NULL;
ClutterActor *parent_actor = NULL;
ClutterActor *iter;
gint index = -1;
g_return_val_if_fail (CLUTTER_IS_ACTOR_ACCESSIBLE (obj), -1);
if (obj->accessible_parent)
{
gint n_children, i;
gboolean found = FALSE;
n_children = atk_object_get_n_accessible_children (obj->accessible_parent);
for (i = 0; i < n_children; i++)
{
AtkObject *child;
child = atk_object_ref_accessible_child (obj->accessible_parent, i);
if (child == obj)
found = TRUE;
g_object_unref (child);
if (found)
return i;
}
return -1;
}
actor_accessible = CLUTTER_ACTOR_ACCESSIBLE (obj);
actor = CLUTTER_ACTOR_FROM_ACCESSIBLE (actor_accessible);
if (actor == NULL) /* Object is defunct */
return -1;
index = 0;
parent_actor = clutter_actor_get_parent (actor);
if (parent_actor == NULL)
return -1;
for (iter = clutter_actor_get_first_child (parent_actor);
iter != NULL && iter != actor;
iter = clutter_actor_get_next_sibling (iter))
{
index += 1;
}
return index;
}
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_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);
actor = CLUTTER_ACTOR_FROM_ACCESSIBLE (actor_accessible);
if (actor == NULL) /* Object is defunct */
{
atk_state_set_add_state (state_set, ATK_STATE_DEFUNCT);
}
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);
}
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;
}
static gint
clutter_actor_accessible_get_n_children (AtkObject *obj)
{
ClutterActor *actor = NULL;
g_return_val_if_fail (CLUTTER_IS_ACTOR_ACCESSIBLE (obj), 0);
actor = CLUTTER_ACTOR_FROM_ACCESSIBLE (obj);
if (actor == NULL) /* State is defunct */
return 0;
g_return_val_if_fail (CLUTTER_IS_ACTOR (actor), 0);
return clutter_actor_get_n_children (actor);
}
static AtkObject*
clutter_actor_accessible_ref_child (AtkObject *obj,
gint i)
{
ClutterActor *actor = NULL;
ClutterActor *child = NULL;
g_return_val_if_fail (CLUTTER_IS_ACTOR_ACCESSIBLE (obj), NULL);
actor = CLUTTER_ACTOR_FROM_ACCESSIBLE (obj);
if (actor == NULL) /* State is defunct */
return NULL;
g_return_val_if_fail (CLUTTER_IS_ACTOR (actor), NULL);
if (i >= clutter_actor_get_n_children (actor))
return NULL;
child = clutter_actor_get_child_at_index (actor, i);
if (child == NULL)
return NULL;
return g_object_ref (clutter_actor_get_accessible (child));
}
static void
clutter_actor_accessible_class_init (ClutterActorAccessibleClass *klass)
{
AtkObjectClass *class = ATK_OBJECT_CLASS (klass);
GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
/* GObject */
gobject_class->finalize = clutter_actor_accessible_finalize;
/* AtkObject */
class->get_role = clutter_actor_accessible_get_role;
class->get_name = clutter_actor_accessible_get_name;
class->get_parent = clutter_actor_accessible_get_parent;
class->get_index_in_parent = clutter_actor_accessible_get_index_in_parent;
class->ref_state_set = clutter_actor_accessible_ref_state_set;
class->initialize = clutter_actor_accessible_initialize;
class->get_n_children = clutter_actor_accessible_get_n_children;
class->ref_child = clutter_actor_accessible_ref_child;
}
/* AtkComponent implementation */
static void
clutter_actor_accessible_get_extents (AtkComponent *component,
gint *x,
gint *y,
gint *width,
gint *height,
AtkCoordType coord_type)
{
ClutterActorAccessible *actor_accessible = NULL;
ClutterActor *actor = NULL;
gfloat f_width, f_height;
graphene_point3d_t verts[4];
ClutterActor *stage = NULL;
g_return_if_fail (CLUTTER_IS_ACTOR_ACCESSIBLE (component));
actor_accessible = CLUTTER_ACTOR_ACCESSIBLE (component);
actor = CLUTTER_ACTOR_FROM_ACCESSIBLE (actor_accessible);
if (actor == NULL) /* actor is defunct */
return;
/* If the actor is not placed in any stage, we can't compute the
* extents */
stage = clutter_actor_get_stage (actor);
if (stage == NULL)
return;
clutter_actor_get_abs_allocation_vertices (actor, verts);
clutter_actor_get_transformed_size (actor, &f_width, &f_height);
*x = (int) verts[0].x;
*y = (int) verts[0].y;
*width = (int) ceilf (f_width);
*height = (int) ceilf (f_height);
}
static gint
clutter_actor_accessible_get_mdi_zorder (AtkComponent *component)
{
ClutterActorAccessible *actor_accessible = NULL;
ClutterActor *actor = NULL;
g_return_val_if_fail (CLUTTER_IS_ACTOR_ACCESSIBLE (component), G_MININT);
actor_accessible = CLUTTER_ACTOR_ACCESSIBLE (component);
actor = CLUTTER_ACTOR_FROM_ACCESSIBLE (actor_accessible);
return (int) clutter_actor_get_z_position (actor);
}
static gboolean
clutter_actor_accessible_grab_focus (AtkComponent *component)
{
ClutterActor *actor = NULL;
ClutterActor *stage = NULL;
ClutterActorAccessible *actor_accessible = NULL;
g_return_val_if_fail (CLUTTER_IS_ACTOR_ACCESSIBLE (component), FALSE);
/* See focus section on implementation notes */
actor_accessible = CLUTTER_ACTOR_ACCESSIBLE (component);
actor = CLUTTER_ACTOR_FROM_ACCESSIBLE (actor_accessible);
stage = clutter_actor_get_stage (actor);
clutter_stage_set_key_focus (CLUTTER_STAGE (stage),
actor);
return TRUE;
}
static double
clutter_actor_accessible_get_alpha (AtkComponent *component)
{
ClutterActor *actor;
g_return_val_if_fail (CLUTTER_IS_ACTOR_ACCESSIBLE (component), 1.0);
actor = CLUTTER_ACTOR_FROM_ACCESSIBLE (component);
if (!actor)
return 1.0;
return clutter_actor_get_opacity (actor) / 255.0;
}
static void
clutter_actor_accessible_component_interface_init (AtkComponentIface *iface)
{
g_return_if_fail (iface != NULL);
iface->get_extents = clutter_actor_accessible_get_extents;
iface->get_mdi_zorder = clutter_actor_accessible_get_mdi_zorder;
iface->get_alpha = clutter_actor_accessible_get_alpha;
/* focus management */
iface->grab_focus = clutter_actor_accessible_grab_focus;
}