mutter/clutter/cally/cally-actor.c
Alejandro Piñeiro c0f753d108 Add Cally
The Clutter Accessibility Library is an implementation of the ATK,
the Accessibility Toolkit, which exposes Clutter actors to accessibility
tools. This allows not only writing accessible user interfaces, but also
allows testing and verification frameworks based on A11Y technologies to
inspect and test a Clutter scene graph.

http://bugzilla.clutter-project.org/show_bug.cgi?id=2097

Signed-off-by: Emmanuele Bassi <ebassi@linux.intel.com>
2010-07-05 16:45:43 +01:00

1492 lines
44 KiB
C

/* CALLY - The Clutter Accessibility Implementation Library
*
* Copyright (C) 2008 Igalia, S.L.
*
* Author: Alejandro Piñeiro Iglesias <apinheiro@igalia.com>
*
* Some parts are based on GailWidget, GailContaineer, GailCell 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, write to the
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*/
/**
* SECTION:cally-actor
* @short_description: Implementation of the ATK interfaces for #ClutterActor
* @see_also: #ClutterActor
*
* #CallyActor implements the required ATK interfaces of #ClutterActor
* exposing the common elements on each ClutterActor (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_componenet focus methods (like
* grab focus)
*
* ####
*
* #ClutterContainer : cally_actor implements some of his methods based on
* #ClutterContainer interface, although there are the posibility to not
* implement it. Could be strange to do that but:
* * Some methods (like get_n_children and ref_child) are easily implemented using
* this interface so a general implementation can be done
* * #ClutterContainer is a popular interface, so several classes will implement
* that.
* * So we can implement a a11y class similar to GailContainer for each clutter
* object implementing that, and their clutter subclasses will have a proper
* a11y class, but not if they are parallel classes (ie: #ClutterGroup,
* #TinyFrame, #TinyScrollView)
* * So, on all this objects, will be required to reimplement again some methods
* on the objects.
* * A auxiliar object (a kind of a11y specific #ClutterContainer implementation)
* could be used to implement this methods in only a place, anyway, this will
* require some code on each concrete class to manage it.
* * So this implementation is based in that is better to manage a interface
* on the top abstract object, instead that C&P some code, with the minor
* problem that we need to check if we are implementing or not the interface.
*
* This methods can be reimplemented, in concrete cases that we can get ways more
* efficient to implement that. Take a look to #CallyGroup as a example of this.
*
* Anyway, there are several examples of behaviour changes depending of the current
* type of the object you are granting access.
*
* TODO,FIXME: check if an option would be to use a dynamic type, as
* it has been done on the webkit a11y implementation:
* See: https://bugs.webkit.org/show_bug.cgi?id=21546
*
* ###
*
* #AtkAction implementation. As ClutterActor has signals for "press"
* and "release", and most of the general Clutter objects are being
* used as buttons, it has sense to implement #AtkAction on
* #CallyActor, so this actions were added in this level.
*
* So we should search a way to extend #AtkAction on subclasses, to
* add actions. The direct solution would be just extend it normally,
* but we also should have the option to remove actions if required.
*
* So it was used the solution implemented in GailCell: maintain a
* list of actions, and add a _add_action and _remove_action public
* methods.
*
* This is another reason to not isolate CALLY as GAIL (although the
* current idea is to not do that).
*
*/
#include "config.h"
#include <string.h>
#include <clutter/clutter.h>
#ifdef HAVE_CLUTTER_X11
#include <clutter/x11/clutter-x11.h>
#endif
#include <math.h>
#include "cally-actor.h"
#include "cally-actor-private.h"
typedef struct _CallyActorActionInfo CallyActorActionInfo;
/**
* CallyActorActionInfo:
* @name: name of the action
* @description: description of the action
* @keybinding: keybinding related to the action
* @do_action_func: callback
*
* Utility structure to maintain the different actions added to the
* #CallyActor
*/
struct _CallyActorActionInfo
{
gchar *name;
gchar *description;
gchar *keybinding;
CallyActionFunc do_action_func;
};
static void cally_actor_class_init (CallyActorClass *klass);
static void cally_actor_init (CallyActor *cally_actor);
static void cally_actor_initialize (AtkObject *obj,
gpointer data);
static void cally_actor_finalize (GObject *obj);
/* AtkObject.h */
static AtkObject* cally_actor_get_parent (AtkObject *obj);
static gint cally_actor_get_index_in_parent (AtkObject *obj);
static AtkStateSet* cally_actor_ref_state_set (AtkObject *obj);
static G_CONST_RETURN gchar* cally_actor_get_name (AtkObject *obj);
static gint cally_actor_get_n_children (AtkObject *obj);
static AtkObject* cally_actor_ref_child (AtkObject *obj,
gint i);
static gboolean _cally_actor_all_parents_visible (ClutterActor *actor);
/* ClutterContainer */
static gint cally_actor_add_actor (ClutterActor *container,
ClutterActor *actor,
gpointer data);
static gint cally_actor_remove_actor (ClutterActor *container,
ClutterActor *actor,
gpointer data);
static gint cally_actor_real_add_actor (ClutterActor *container,
ClutterActor *actor,
gpointer data);
static gint cally_actor_real_remove_actor (ClutterActor *container,
ClutterActor *actor,
gpointer data);
/* AtkComponent.h */
static void cally_actor_component_interface_init (AtkComponentIface *iface);
static void cally_actor_get_extents (AtkComponent *component,
gint *x,
gint *y,
gint *width,
gint *height,
AtkCoordType coord_type);
static gint cally_actor_get_mdi_zorder (AtkComponent *component);
static gboolean cally_actor_grab_focus (AtkComponent *component);
static guint cally_actor_add_focus_handler (AtkComponent *component,
AtkFocusHandler handler);
static void cally_actor_remove_focus_handler (AtkComponent *component,
guint handler_id);
static void cally_actor_focus_event (AtkObject *obj,
gboolean focus_in);
static gboolean _is_actor_on_screen (ClutterActor *actor);
static void _get_top_level_origin (ClutterActor *actor,
gint *x,
gint *y);
/* AtkAction.h */
static void cally_actor_action_interface_init (AtkActionIface *iface);
static gboolean cally_actor_action_do_action (AtkAction *action,
gint i);
static gboolean idle_do_action (gpointer data);
static gint cally_actor_action_get_n_actions (AtkAction *action);
static G_CONST_RETURN gchar* cally_actor_action_get_description (AtkAction *action,
gint i);
static G_CONST_RETURN gchar* cally_actor_action_get_keybinding (AtkAction *action,
gint i);
static G_CONST_RETURN gchar* cally_actor_action_get_name (AtkAction *action,
gint i);
static gboolean cally_actor_action_set_description (AtkAction *action,
gint i,
const gchar *desc);
static void _cally_actor_press_action (CallyActor *cally_actor);
static void _cally_actor_release_action (CallyActor *cally_actor);
static void _cally_actor_click_action (CallyActor *cally_actor);
static void _cally_actor_destroy_action_info (gpointer action_info,
gpointer user_data);
static void _cally_actor_clean_action_list (CallyActor *cally_actor);
static CallyActorActionInfo* _cally_actor_get_action_info (CallyActor *cally_actor,
gint index);
/* Misc functions */
static void cally_actor_notify_clutter (GObject *obj,
GParamSpec *pspec);
static void cally_actor_real_notify_clutter (GObject *obj,
GParamSpec *pspec);
static gboolean cally_actor_focus_clutter (ClutterActor *actor,
gpointer data);
static gboolean cally_actor_real_focus_clutter (ClutterActor *actor,
gpointer data);
G_DEFINE_TYPE_WITH_CODE (CallyActor,
cally_actor,
ATK_TYPE_GOBJECT_ACCESSIBLE,
G_IMPLEMENT_INTERFACE (ATK_TYPE_COMPONENT,
cally_actor_component_interface_init)
G_IMPLEMENT_INTERFACE (ATK_TYPE_ACTION,
cally_actor_action_interface_init));
#define CALLY_ACTOR_GET_PRIVATE(obj) \
(G_TYPE_INSTANCE_GET_PRIVATE ((obj), CALLY_TYPE_ACTOR, CallyActorPrivate))
struct _CallyActorPrivate
{
GQueue *action_queue;
guint action_idle_handler;
GList *action_list;
GList *children;
};
AtkObject*
cally_actor_new (ClutterActor *actor)
{
gpointer object;
AtkObject *atk_object;
g_return_val_if_fail (CLUTTER_IS_ACTOR (actor), NULL);
object = g_object_new (CALLY_TYPE_ACTOR, NULL);
atk_object = ATK_OBJECT (object);
atk_object_initialize (atk_object, actor);
return atk_object;
}
static void
cally_actor_initialize (AtkObject *obj,
gpointer data)
{
CallyActor *self = NULL;
CallyActorPrivate *priv = NULL;
ClutterActor *actor = NULL;
guint handler_id;
ATK_OBJECT_CLASS (cally_actor_parent_class)->initialize (obj, data);
self = CALLY_ACTOR(obj);
priv = self->priv;
actor = CLUTTER_ACTOR (data);
g_signal_connect_after (actor,
"key-focus-in",
G_CALLBACK (cally_actor_focus_clutter),
GINT_TO_POINTER (TRUE));
g_signal_connect_after (actor,
"key-focus-out",
G_CALLBACK (cally_actor_focus_clutter),
GINT_TO_POINTER (FALSE));
g_signal_connect (actor,
"notify",
G_CALLBACK (cally_actor_notify_clutter),
NULL);
atk_component_add_focus_handler (ATK_COMPONENT (self),
cally_actor_focus_event);
g_object_set_data (G_OBJECT (obj), "atk-component-layer",
GINT_TO_POINTER (ATK_LAYER_MDI));
/* add basic actions */
cally_actor_add_action (self, "press", NULL, NULL,
_cally_actor_press_action);
cally_actor_add_action (self, "release", NULL, NULL,
_cally_actor_release_action);
cally_actor_add_action (self, "click", NULL, NULL,
_cally_actor_click_action);
/* Depends if the object implement ClutterContainer */
if (CLUTTER_IS_CONTAINER(actor))
{
priv->children = clutter_container_get_children (CLUTTER_CONTAINER (actor));
/*
* We store the handler ids for these signals in case some objects
* need to remove these handlers.
*/
handler_id = g_signal_connect (actor,
"actor-added",
G_CALLBACK (cally_actor_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,
"actor-removed",
G_CALLBACK (cally_actor_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 */
}
else
{
priv->children = NULL;
obj->role = ATK_ROLE_UNKNOWN;
}
}
static void
cally_actor_class_init (CallyActorClass *klass)
{
AtkObjectClass *class = ATK_OBJECT_CLASS (klass);
GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
klass->focus_clutter = cally_actor_real_focus_clutter;
klass->notify_clutter = cally_actor_real_notify_clutter;
klass->add_actor = cally_actor_real_add_actor;
klass->remove_actor = cally_actor_real_remove_actor;
/* GObject */
gobject_class->finalize = cally_actor_finalize;
/* AtkObject */
class->get_name = cally_actor_get_name;
class->get_parent = cally_actor_get_parent;
class->get_index_in_parent = cally_actor_get_index_in_parent;
class->ref_state_set = cally_actor_ref_state_set;
class->initialize = cally_actor_initialize;
class->get_n_children = cally_actor_get_n_children;
class->ref_child = cally_actor_ref_child;
g_type_class_add_private (gobject_class, sizeof (CallyActorPrivate));
}
static void
cally_actor_init (CallyActor *cally_actor)
{
CallyActorPrivate *priv = CALLY_ACTOR_GET_PRIVATE (cally_actor);
cally_actor->priv = priv;
priv->action_queue = NULL;
priv->action_idle_handler = 0;
priv->action_list = NULL;
priv->children = NULL;
}
static void
cally_actor_finalize (GObject *obj)
{
CallyActor *cally_actor = NULL;
CallyActorPrivate *priv = NULL;
cally_actor = CALLY_ACTOR (obj);
priv = cally_actor->priv;
_cally_actor_clean_action_list (cally_actor);
if (priv->action_idle_handler)
{
g_source_remove (priv->action_idle_handler);
priv->action_idle_handler = 0;
}
if (priv->action_queue)
{
g_queue_free (priv->action_queue);
}
if (priv->children)
{
g_list_free (priv->children);
priv->children = NULL;
}
G_OBJECT_CLASS (cally_actor_parent_class)->finalize (obj);
}
/* AtkObject */
static G_CONST_RETURN gchar*
cally_actor_get_name (AtkObject *obj)
{
G_CONST_RETURN gchar* name = NULL;
g_return_val_if_fail (CALLY_IS_ACTOR (obj), NULL);
name = ATK_OBJECT_CLASS (cally_actor_parent_class)->get_name (obj);
if (name == NULL)
{
CallyActor *cally_actor = NULL;
ClutterActor *actor = NULL;
cally_actor = CALLY_ACTOR (obj);
actor = CALLY_GET_CLUTTER_ACTOR (cally_actor);
if (actor == NULL) /* State is defunct */
name = NULL;
else
name = clutter_actor_get_name (actor);
}
return name;
}
static AtkObject *
cally_actor_get_parent (AtkObject *obj)
{
ClutterActor *parent_actor = NULL;
AtkObject *parent = NULL;
ClutterActor *actor = NULL;
CallyActor *cally_actor = NULL;
g_return_val_if_fail (CALLY_IS_ACTOR (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 */
cally_actor = CALLY_ACTOR (obj);
actor = CALLY_GET_CLUTTER_ACTOR (cally_actor);
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
cally_actor_get_index_in_parent (AtkObject *obj)
{
CallyActor *cally_actor = NULL;
ClutterActor *actor = NULL;
ClutterActor *parent_actor = NULL;
GList *children = NULL;
gint index = -1;
g_return_val_if_fail (CALLY_IS_ACTOR (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;
}
cally_actor = CALLY_ACTOR (obj);
actor = CALLY_GET_CLUTTER_ACTOR (cally_actor);
if (actor == NULL) /* Object is defunct */
return -1;
parent_actor = clutter_actor_get_parent(actor);
if ((parent_actor == NULL)||(!CLUTTER_IS_CONTAINER(parent_actor)))
return -1;
children = clutter_container_get_children(CLUTTER_CONTAINER(parent_actor));
index = g_list_index (children, actor);
g_list_free (children);
return index;
}
static AtkStateSet*
cally_actor_ref_state_set (AtkObject *obj)
{
ClutterActor *actor = NULL;
AtkStateSet *state_set = NULL;
ClutterStage *stage = NULL;
ClutterActor *focus_actor = NULL;
CallyActor *cally_actor = NULL;
g_return_val_if_fail (CALLY_IS_ACTOR (obj), NULL);
cally_actor = CALLY_ACTOR (obj);
state_set = ATK_OBJECT_CLASS (cally_actor_parent_class)->ref_state_set (obj);
actor = CALLY_GET_CLUTTER_ACTOR (cally_actor);
if (actor == NULL) /* Object is defunct */
{
atk_state_set_add_state (state_set, ATK_STATE_DEFUNCT);
}
else
{
if (CLUTTER_ACTOR_IS_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);
if (_is_actor_on_screen (actor) &&
_cally_actor_all_parents_visible (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 for any reason this actor doesn't have a stage
associated, we try the default one as fallback */
if (stage == NULL)
stage = CLUTTER_STAGE (clutter_stage_get_default ());
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
cally_actor_get_n_children (AtkObject *obj)
{
ClutterActor *actor = NULL;
CallyActorPrivate *priv = NULL;
GList *children = NULL;
gint num = 0;
g_return_val_if_fail (CALLY_IS_ACTOR (obj), 0);
priv = CALLY_ACTOR (obj)->priv;
actor = CALLY_GET_CLUTTER_ACTOR (obj);
if (actor == NULL) /* State is defunct */
return 0;
g_return_val_if_fail (CLUTTER_IS_ACTOR (actor), 0);
if (CLUTTER_IS_CONTAINER (actor))
{
children = clutter_container_get_children (CLUTTER_CONTAINER (actor));
num = g_list_length (children);
g_list_free (children);
}
else
{
num = 0;
}
return num;
}
static AtkObject*
cally_actor_ref_child (AtkObject *obj,
gint i)
{
ClutterActor *actor = NULL;
ClutterActor *child = NULL;
CallyActorPrivate *priv = NULL;
GList *children = NULL;
AtkObject *result = NULL;
g_return_val_if_fail (CALLY_IS_ACTOR (obj), NULL);
priv = CALLY_ACTOR (obj)->priv;
actor = CALLY_GET_CLUTTER_ACTOR (obj);
if (actor == NULL) /* State is defunct */
{
return NULL;
}
g_return_val_if_fail (CLUTTER_IS_ACTOR (actor), NULL);
if (CLUTTER_IS_CONTAINER (actor))
{
children = clutter_container_get_children (CLUTTER_CONTAINER (actor));
child = g_list_nth_data (children, i);
result = clutter_actor_get_accessible (child);
g_object_ref (result);
g_list_free (children);
}
else
{
result = NULL;
}
return result;
}
/* ClutterContainer */
static gint
cally_actor_add_actor (ClutterActor *container,
ClutterActor *actor,
gpointer data)
{
CallyActor *cally_actor = CALLY_ACTOR (data);
CallyActorClass *klass = NULL;
klass = CALLY_ACTOR_GET_CLASS (cally_actor);
if (klass->add_actor)
return klass->add_actor (container, actor, data);
else
return 1;
}
static gint
cally_actor_remove_actor (ClutterActor *container,
ClutterActor *actor,
gpointer data)
{
CallyActor *cally_actor = CALLY_ACTOR (data);
CallyActorClass *klass = NULL;
klass = CALLY_ACTOR_GET_CLASS (cally_actor);
if (klass->remove_actor)
return klass->remove_actor (container, actor, data);
else
return 1;
}
static gint
cally_actor_real_add_actor (ClutterActor *container,
ClutterActor *actor,
gpointer data)
{
AtkObject *atk_parent = ATK_OBJECT (data);
AtkObject *atk_child = clutter_actor_get_accessible (actor);
CallyActor *cally_actor = CALLY_ACTOR (atk_parent);
CallyActorPrivate *priv = cally_actor->priv;
gint index;
g_return_val_if_fail (CLUTTER_IS_CONTAINER (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_container_get_children (CLUTTER_CONTAINER(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
cally_actor_real_remove_actor (ClutterActor *container,
ClutterActor *actor,
gpointer data)
{
AtkPropertyValues values = { NULL };
AtkObject* atk_parent = NULL;
AtkObject *atk_child = NULL;
CallyActorPrivate *priv = NULL;
gint index;
g_return_val_if_fail (CLUTTER_IS_CONTAINER (container), 0);
g_return_val_if_fail (CLUTTER_IS_ACTOR (actor), 0);
atk_parent = ATK_OBJECT (data);
atk_child = clutter_actor_get_accessible (actor);
if (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_object_ref (atk_child);
g_signal_emit_by_name (atk_child,
"property_change::accessible-parent", &values, NULL);
g_object_unref (atk_child);
}
priv = CALLY_ACTOR (atk_parent)->priv;
index = g_list_index (priv->children, actor);
g_list_free (priv->children);
priv->children = clutter_container_get_children (CLUTTER_CONTAINER(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;
}
/* AtkComponent implementation */
static void
cally_actor_component_interface_init (AtkComponentIface *iface)
{
g_return_if_fail (iface != NULL);
iface->get_extents = cally_actor_get_extents;
iface->get_mdi_zorder = cally_actor_get_mdi_zorder;
/* focus management */
iface->grab_focus = cally_actor_grab_focus;
iface->add_focus_handler = cally_actor_add_focus_handler;
iface->remove_focus_handler = cally_actor_remove_focus_handler;
}
static void
cally_actor_get_extents (AtkComponent *component,
gint *x,
gint *y,
gint *width,
gint *height,
AtkCoordType coord_type)
{
CallyActor *cally_actor = NULL;
ClutterActor *actor = NULL;
gint top_level_x, top_level_y;
gfloat f_width, f_height;
ClutterVertex verts[4];
g_return_if_fail (CALLY_IS_ACTOR (component));
cally_actor = CALLY_ACTOR (component);
actor = CALLY_GET_CLUTTER_ACTOR (cally_actor);
if (actor == NULL) /* actor is defunct */
return;
clutter_actor_get_abs_allocation_vertices (actor, verts);
clutter_actor_get_transformed_size (actor, &f_width, &f_height);
*x = verts[0].x;
*y = verts[0].y;
*width = ceilf (f_width);
*height = ceilf (f_height);
/* In the ATK_XY_WINDOW case, we consider the stage as the
* "top-level-window"
*
* http://library.gnome.org/devel/atk/stable/AtkUtil.html#AtkCoordType
*/
if (coord_type == ATK_XY_SCREEN)
{
_get_top_level_origin (actor, &top_level_x, &top_level_y);
*x += top_level_x;
*y += top_level_y;
}
return;
}
static gint
cally_actor_get_mdi_zorder (AtkComponent *component)
{
CallyActor *cally_actor = NULL;
ClutterActor *actor = NULL;
g_return_val_if_fail (CALLY_IS_ACTOR (component), G_MININT);
cally_actor = CALLY_ACTOR(component);
actor = CALLY_GET_CLUTTER_ACTOR (cally_actor);
return clutter_actor_get_depth (actor);
}
static gboolean
cally_actor_grab_focus (AtkComponent *component)
{
ClutterActor *actor = NULL;
ClutterActor *stage = NULL;
CallyActor *cally_actor = NULL;
g_return_val_if_fail (CALLY_IS_ACTOR (component), FALSE);
/* See focus section on implementation notes */
cally_actor = CALLY_ACTOR(component);
actor = CALLY_GET_CLUTTER_ACTOR (cally_actor);
stage = clutter_actor_get_stage (actor);
clutter_stage_set_key_focus (CLUTTER_STAGE (stage),
actor);
return TRUE;
}
/*
* These methods are basically taken from gail, as I don't see any
* reason to modify it. It makes me wonder why it is really required
* to be implemented in the toolkit
*/
static guint
cally_actor_add_focus_handler (AtkComponent *component,
AtkFocusHandler handler)
{
GSignalMatchType match_type;
gulong ret;
guint signal_id;
match_type = G_SIGNAL_MATCH_ID | G_SIGNAL_MATCH_FUNC;
signal_id = g_signal_lookup ("focus-event", ATK_TYPE_OBJECT);
ret = g_signal_handler_find (component, match_type, signal_id, 0, NULL,
(gpointer) handler, NULL);
if (!ret)
{
return g_signal_connect_closure_by_id (component,
signal_id, 0,
g_cclosure_new (G_CALLBACK (handler), NULL,
(GClosureNotify) NULL),
FALSE);
}
else
{
return 0;
}
}
static void
cally_actor_remove_focus_handler (AtkComponent *component,
guint handler_id)
{
g_signal_handler_disconnect (component, handler_id);
}
/* This method should check if the actor is currently on screen */
static gboolean
_is_actor_on_screen (ClutterActor *actor)
{
/* FIXME: FILL ME!!
* You could get some ideas from clutter_actor_is_on_stage, a private clutter
* function (note: it doesn't exists in the last versions of clutter)
* A occlusion check could be a good idea too
*/
return TRUE;
}
/**
*
* This gets the top level origin, it is, the position of the stage in
* the global screen. You can see it as the absolute display position
* of the stage.
*
* FIXME: only the case with x11 is implemented, other backends are
* required
*
**/
static void
_get_top_level_origin (ClutterActor *actor,
gint *x,
gint *y)
{
/* default values */
*x = 0;
*y = 0;
#ifdef HAVE_CLUTTER_X11
ClutterActor *stage = NULL;
Display *display = NULL;
Window root_window;
Window stage_window;
Window child;
gint return_val = 0;
stage = clutter_actor_get_stage (actor);
display = clutter_x11_get_default_display (); /* FIXME: what happens if you use another
display with clutter_backend_x11_set_display ?*/
root_window = clutter_x11_get_root_window ();
stage_window = clutter_x11_get_stage_window (CLUTTER_STAGE (stage));
return_val = XTranslateCoordinates (display, stage_window, root_window,
0, 0, x, y,
&child);
if (!return_val)
g_warning ("[x11] We were not able to get proper absolute position of the stage");
#else
static gboolean yet_warned = FALSE;
if (!yet_warned)
{
yet_warned = TRUE;
g_warning ("Using a clutter backend not supported. "
"atk_component_get_extents using ATK_XY_SCREEN could return a wrong screen position");
}
#endif
}
/* AtkAction implementation */
static void
cally_actor_action_interface_init (AtkActionIface *iface)
{
g_return_if_fail (iface != NULL);
iface->do_action = cally_actor_action_do_action;
iface->get_n_actions = cally_actor_action_get_n_actions;
iface->get_description = cally_actor_action_get_description;
iface->get_keybinding = cally_actor_action_get_keybinding;
iface->get_name = cally_actor_action_get_name;
iface->set_description = cally_actor_action_set_description;
}
static gboolean
cally_actor_action_do_action (AtkAction *action,
gint index)
{
ClutterActor *actor = NULL;
CallyActor *cally_actor = NULL;
AtkStateSet *set = NULL;
CallyActorPrivate *priv = NULL;
CallyActorActionInfo *info = NULL;
cally_actor = CALLY_ACTOR (action);
priv = cally_actor->priv;
actor = CALLY_GET_CLUTTER_ACTOR (cally_actor);
set = atk_object_ref_state_set (ATK_OBJECT (cally_actor));
if (atk_state_set_contains_state (set, ATK_STATE_DEFUNCT))
return FALSE;
if (!atk_state_set_contains_state (set, ATK_STATE_SENSITIVE) ||
!atk_state_set_contains_state (set, ATK_STATE_SHOWING))
return FALSE;
g_object_unref (set);
info = _cally_actor_get_action_info (cally_actor, index);
if (info == NULL)
return FALSE;
if (info->do_action_func == NULL)
return FALSE;
if (!priv->action_queue)
priv->action_queue = g_queue_new ();
g_queue_push_head (priv->action_queue, info);
if (!priv->action_idle_handler)
priv->action_idle_handler = g_idle_add (idle_do_action, cally_actor);
return TRUE;
}
static gboolean
idle_do_action (gpointer data)
{
CallyActor *cally_actor = NULL;
CallyActorPrivate *priv = NULL;
ClutterActor *actor = NULL;
cally_actor = CALLY_ACTOR (data);
priv = cally_actor->priv;
actor = CALLY_GET_CLUTTER_ACTOR (cally_actor);
priv->action_idle_handler = 0;
if (actor == NULL) /* state is defunct*/
return FALSE;
while (!g_queue_is_empty (priv->action_queue))
{
CallyActorActionInfo *info = NULL;
info = (CallyActorActionInfo *) g_queue_pop_head (priv->action_queue);
info->do_action_func (cally_actor);
}
return FALSE;
}
static gint
cally_actor_action_get_n_actions (AtkAction *action)
{
CallyActor *cally_actor = NULL;
CallyActorPrivate *priv = NULL;
g_return_val_if_fail (CALLY_IS_ACTOR (action), 0);
cally_actor = CALLY_ACTOR (action);
priv = cally_actor->priv;
return g_list_length (priv->action_list);
}
static G_CONST_RETURN gchar*
cally_actor_action_get_name (AtkAction *action,
gint i)
{
CallyActor *cally_actor = NULL;
CallyActorActionInfo *info = NULL;
g_return_val_if_fail (CALLY_IS_ACTOR (action), NULL);
cally_actor = CALLY_ACTOR (action);
info = _cally_actor_get_action_info (cally_actor, i);
if (info == NULL)
return NULL;
return info->name;
}
static G_CONST_RETURN gchar*
cally_actor_action_get_description (AtkAction *action,
gint i)
{
CallyActor *cally_actor = NULL;
CallyActorActionInfo *info = NULL;
g_return_val_if_fail (CALLY_IS_ACTOR (action), NULL);
cally_actor = CALLY_ACTOR (action);
info = _cally_actor_get_action_info (cally_actor, i);
if (info == NULL)
return NULL;
return info->description;
}
static gboolean
cally_actor_action_set_description (AtkAction *action,
gint i,
const gchar *desc)
{
CallyActor *cally_actor = NULL;
CallyActorActionInfo *info = NULL;
g_return_val_if_fail (CALLY_IS_ACTOR (action), FALSE);
cally_actor = CALLY_ACTOR (action);
info = _cally_actor_get_action_info (cally_actor, i);
if (info == NULL)
return FALSE;
g_free (info->description);
info->description = g_strdup (desc);
return TRUE;
}
static G_CONST_RETURN gchar*
cally_actor_action_get_keybinding (AtkAction *action,
gint i)
{
CallyActor *cally_actor = NULL;
CallyActorActionInfo *info = NULL;
g_return_val_if_fail (CALLY_IS_ACTOR (action), NULL);
cally_actor = CALLY_ACTOR (action);
info = _cally_actor_get_action_info (cally_actor, i);
if (info == NULL)
return NULL;
return info->keybinding;
}
/* Misc functions */
/**
* Checks if the parent actor, and his parent, etc is all visible
* Used to check the showing property
*
* FIXME: the same functionality is implemented on clutter since version 0.8.4
* by clutter_actor_get_paint_visibility, so we should change this function
* if a clutter version update is made
**/
static gboolean
_cally_actor_all_parents_visible (ClutterActor *actor)
{
ClutterActor *iter_parent = NULL;
gboolean result = TRUE;
ClutterActor *stage = NULL;
stage = clutter_actor_get_stage (actor);
for (iter_parent = clutter_actor_get_parent(actor); iter_parent;
iter_parent = clutter_actor_get_parent(iter_parent))
{
if (!CLUTTER_ACTOR_IS_VISIBLE (iter_parent))
{
/* stage parent */
if (iter_parent != stage)
result = FALSE;
else
result = TRUE;
break;
}
}
return result;
}
/*
* This function is a signal handler for key_focus_in and
* key_focus_out signal which gets emitted on a ClutterActor
*/
static gboolean
cally_actor_focus_clutter (ClutterActor *actor,
gpointer data)
{
CallyActor *cally_actor = NULL;
CallyActorClass *klass = NULL;
cally_actor = CALLY_ACTOR (clutter_actor_get_accessible (actor));
klass = CALLY_ACTOR_GET_CLASS (cally_actor);
if (klass->focus_clutter)
return klass->focus_clutter (actor, data);
else
return FALSE;
}
static gboolean
cally_actor_real_focus_clutter (ClutterActor *actor,
gpointer data)
{
CallyActor *cally_actor = NULL;
gboolean return_val = FALSE;
gboolean in = FALSE;
in = GPOINTER_TO_INT (data);
cally_actor = CALLY_ACTOR (clutter_actor_get_accessible (actor));
g_signal_emit_by_name (cally_actor, "focus_event", in, &return_val);
atk_focus_tracker_notify (ATK_OBJECT (cally_actor));
return FALSE;
}
/*
* This function is a signal handler for notify signal which gets emitted
* when a property changes value on the ClutterActor associated with the object.
*
* It calls a function for the CallyActor type
*/
static void
cally_actor_notify_clutter (GObject *obj,
GParamSpec *pspec)
{
CallyActor *cally_actor = NULL;
CallyActorClass *klass = NULL;
cally_actor = CALLY_ACTOR (clutter_actor_get_accessible (CLUTTER_ACTOR (obj)));
klass = CALLY_ACTOR_GET_CLASS (cally_actor);
if (klass->notify_clutter)
klass->notify_clutter (obj, pspec);
}
/*
* This function is a signal handler for notify signal which gets emitted
* when a property changes value on the ClutterActor associated with a CallyActor
*
* It constructs an AtkPropertyValues structure and emits a "property_changed"
* signal which causes the user specified AtkPropertyChangeHandler
* to be called.
*/
static void
cally_actor_real_notify_clutter (GObject *obj,
GParamSpec *pspec)
{
ClutterActor* actor = CLUTTER_ACTOR (obj);
AtkObject* atk_obj = clutter_actor_get_accessible (CLUTTER_ACTOR(obj));
AtkState state;
gboolean value;
if (strcmp (pspec->name, "visible") == 0)
{
state = ATK_STATE_VISIBLE;
value = CLUTTER_ACTOR_IS_VISIBLE (actor);
}
else if (strcmp (pspec->name, "reactive") == 0)
{
state = ATK_STATE_SENSITIVE;
value = CLUTTER_ACTOR_IS_REACTIVE (actor);
}
else
return;
atk_object_notify_state_change (atk_obj, state, value);
}
static void
cally_actor_focus_event (AtkObject *obj,
gboolean focus_in)
{
atk_object_notify_state_change (obj, ATK_STATE_FOCUSED, focus_in);
}
static void
_cally_actor_clean_action_list (CallyActor *cally_actor)
{
CallyActorPrivate *priv = NULL;
priv = cally_actor->priv;
if (priv->action_list)
{
g_list_foreach (priv->action_list,
(GFunc) _cally_actor_destroy_action_info,
NULL);
g_list_free (priv->action_list);
priv->action_list = NULL;
}
}
static CallyActorActionInfo *
_cally_actor_get_action_info (CallyActor *cally_actor,
gint index)
{
CallyActorPrivate *priv = NULL;
GList *node = NULL;
g_return_val_if_fail (CALLY_IS_ACTOR (cally_actor), NULL);
priv = cally_actor->priv;
if (priv->action_list == NULL)
return NULL;
node = g_list_nth (priv->action_list, index);
if (node == NULL)
return NULL;
return (CallyActorActionInfo *)(node->data);
}
static void
_cally_actor_click_action (CallyActor *cally_actor)
{
ClutterEvent tmp_event;
ClutterActor *stage = NULL;
ClutterActor *actor = NULL;
actor = CALLY_GET_CLUTTER_ACTOR (cally_actor);
stage = clutter_actor_get_stage (actor);
/* press */
tmp_event.button.type = CLUTTER_BUTTON_PRESS;
tmp_event.button.time = CLUTTER_CURRENT_TIME;
tmp_event.button.stage = CLUTTER_STAGE (stage);
tmp_event.button.source = actor;
tmp_event.button.button = 1;
clutter_actor_event (actor, &tmp_event, FALSE);
/* release */
tmp_event.button.type = CLUTTER_BUTTON_RELEASE;
clutter_actor_event (actor, &tmp_event, FALSE);
}
static void
_cally_actor_press_action (CallyActor *cally_actor)
{
ClutterEvent tmp_event;
ClutterActor *stage = NULL;
ClutterActor *actor = NULL;
actor = CALLY_GET_CLUTTER_ACTOR (cally_actor);
stage = clutter_actor_get_stage (actor);
tmp_event.button.type = CLUTTER_BUTTON_PRESS;
tmp_event.button.time = CLUTTER_CURRENT_TIME;
tmp_event.button.stage = CLUTTER_STAGE (stage);
tmp_event.button.source = actor;
tmp_event.button.button = 1;
clutter_actor_event (actor, &tmp_event, FALSE);
}
static void
_cally_actor_release_action (CallyActor *cally_actor)
{
ClutterEvent tmp_event;
ClutterActor *stage = NULL;
ClutterActor *actor = NULL;
actor = CALLY_GET_CLUTTER_ACTOR (cally_actor);
stage = clutter_actor_get_stage (actor);
tmp_event.button.type = CLUTTER_BUTTON_RELEASE;
tmp_event.button.time = CLUTTER_CURRENT_TIME;
tmp_event.button.stage = CLUTTER_STAGE (stage);
tmp_event.button.source = actor;
tmp_event.button.button = 1;
clutter_actor_event (actor, &tmp_event, FALSE);
}
/**
* cally_actor_add_action:
* @action_name: the action name
* @action_description: the action description
* @action_keybinding: the action keybinding
* @action_func: the callback of the action, to be executed with do_action
*
* Adds a new action to be accessed with the AtkAction interface.
*
* Return value: added action id, or 0 if failure
*
* Since: 1.2
*/
guint
cally_actor_add_action (CallyActor *cally_actor,
const gchar *action_name,
const gchar *action_description,
const gchar *action_keybinding,
CallyActionFunc action_func)
{
CallyActorActionInfo *info = NULL;
CallyActorPrivate *priv = NULL;
g_return_val_if_fail (CALLY_IS_ACTOR (cally_actor), -1);
g_return_val_if_fail (action_func != NULL, -1);
priv = cally_actor->priv;
info = g_new (CallyActorActionInfo, 1);
if (action_name != NULL)
info->name = g_strdup (action_name);
else
info->name = NULL;
if (action_description != NULL)
info->description = g_strdup (action_description);
else
info->description = NULL;
if (action_keybinding != NULL)
info->keybinding = g_strdup (action_keybinding);
else
info->keybinding = NULL;
info->do_action_func = action_func;
priv->action_list = g_list_append (priv->action_list, (gpointer) info);
return g_list_length (priv->action_list);
}
/**
* cally_actor_remove_action:
* @action_id: the action id
*
* Removes a action, using the @action_id returned by cally_actor_add_action
*
* Return value: TRUE if the operation was succesful, FALSE otherwise
*
* Since: 1.2
*/
gboolean
cally_actor_remove_action (CallyActor *cally_actor,
gint action_id)
{
GList *list_node = NULL;
CallyActorPrivate *priv = NULL;
g_return_val_if_fail (CALLY_IS_ACTOR (cally_actor), FALSE);
priv = cally_actor->priv;
list_node = g_list_nth (priv->action_list, action_id - 1);
if (!list_node)
return FALSE;
_cally_actor_destroy_action_info (list_node->data, NULL);
priv->action_list = g_list_remove_link (priv->action_list, list_node);
return TRUE;
}
/**
* cally_actor_remove_action:
* @action_name: the name of the action
*
* Removes a action, using the @action_name used when the action was added
* with cally_actor_add_action
*
* Return value: TRUE if the operation was succesful, FALSE otherwise
*
* Since: 1.2
*/
gboolean
cally_actor_remove_action_by_name (CallyActor *cally_actor,
const gchar *action_name)
{
GList *node = NULL;
gboolean action_found = FALSE;
CallyActorPrivate *priv = NULL;
g_return_val_if_fail (CALLY_IS_ACTOR (cally_actor), FALSE);
priv = CALLY_ACTOR (cally_actor)->priv;
for (node = priv->action_list; node && !action_found;
node = node->next)
{
if (!g_strcasecmp (((CallyActorActionInfo *)(node->data))->name, action_name))
{
action_found = TRUE;
break;
}
}
if (!action_found)
return FALSE;
_cally_actor_destroy_action_info (node->data, NULL);
priv->action_list = g_list_remove_link (priv->action_list, node);
return TRUE;
}
static void
_cally_actor_destroy_action_info (gpointer action_info,
gpointer user_data)
{
CallyActorActionInfo *info = (CallyActorActionInfo *)action_info;
g_assert (info != NULL);
g_free (info->name);
g_free (info->description);
g_free (info->keybinding);
g_free (info);
}