/* CALLY - The Clutter Accessibility Implementation Library * * Copyright (C) 2008 Igalia, S.L. * * Author: Alejandro PiƱeiro Iglesias * * 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 #ifdef HAVE_CLUTTER_X11 #include #endif #include #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 (g_strcmp0 (pspec->name, "visible") == 0) { state = ATK_STATE_VISIBLE; value = CLUTTER_ACTOR_IS_VISIBLE (actor); } else if (g_strcmp0 (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); }