mutter/clutter/cally/cally-text.c
2010-07-05 16:45:43 +01:00

1262 lines
43 KiB
C

/* CALLY - The Clutter Accessibility Implementation Library
*
* Copyright (C) 2009 Igalia, S.L.
*
* Author: Alejandro Piñeiro Iglesias <apinheiro@igalia.com>
*
* Some parts are based on GailLabel, GailEntry, GailTextView 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-text
* @short_description: Implementation of the ATK interfaces for a #ClutterText
* @see_also: #ClutterText
*
* #CallyText implements the required ATK interfaces of
* #ClutterText, #AtkText and #AtkEditableText
*
*
*/
/*
* IMPLEMENTATION NOTES:
*
* * AtkText: There are still some methods not implemented yet:
* atk_text_get_default_attributes
* atk_text_get_character_extents
* atk_text_get_offset_at_point
*
* See details on bug CB#1733
*
* * AtkEditableText: some methods will not be implemented
*
* * atk_editable_text_set_run_attributes: ClutterText has some
* properties equivalent to the AtkAttributte, but it doesn't
* allow you to define it by
*
* * atk_editable_text_copy: Clutter has no Clipboard support
*
* * atk_editable_text_paste: Clutter has no Clipboard support
*
* * atk_editable_text_cut: Clutter has no Clipboard support. In
* this case, as cut is basically a copy&delete combination,
* we could have implemented it using the delete, but IMHO,
* it would be weird to cut a text, get the text removed and
* then not be able to paste the text
*
* See details on bug CB#1734
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "cally-text.h"
#include "cally-actor-private.h"
#include "clutter-main.h"
static void cally_text_class_init (CallyTextClass *klass);
static void cally_text_init (CallyText *cally_text);
static void cally_text_finalize (GObject *obj);
/* AtkObject */
static void cally_text_real_initialize (AtkObject *obj,
gpointer data);
static G_CONST_RETURN gchar * cally_text_get_name (AtkObject *obj);
static AtkStateSet* cally_text_ref_state_set (AtkObject *obj);
/* atkaction */
static void _cally_text_activate_action (CallyActor *cally_actor);
static void _check_activate_action (CallyText *cally_text,
ClutterText *clutter_text);
/* AtkText */
static void cally_text_text_interface_init (AtkTextIface *iface);
static gchar* cally_text_get_text (AtkText *text,
gint start_offset,
gint end_offset);
static gunichar cally_text_get_character_at_offset (AtkText *text,
gint offset);
static gchar* cally_text_get_text_before_offset (AtkText *text,
gint offset,
AtkTextBoundary boundary_type,
gint *start_offset,
gint *end_offset);
static gchar* cally_text_get_text_at_offset (AtkText *text,
gint offset,
AtkTextBoundary boundary_type,
gint *start_offset,
gint *end_offset);
static gchar* cally_text_get_text_after_offset (AtkText *text,
gint offset,
AtkTextBoundary boundary_type,
gint *start_offset,
gint *end_offset);
static gint cally_text_get_caret_offset (AtkText *text);
static gboolean cally_text_set_caret_offset (AtkText *text,
gint offset);
static gint cally_text_get_character_count (AtkText *text);
static gint cally_text_get_n_selections (AtkText *text);
static gchar* cally_text_get_selection (AtkText *text,
gint selection_num,
gint *start_offset,
gint *end_offset);
static gboolean cally_text_add_selection (AtkText *text,
gint start_offset,
gint end_offset);
static gboolean cally_text_remove_selection (AtkText *text,
gint selection_num);
static gboolean cally_text_set_selection (AtkText *text,
gint selection_num,
gint start_offset,
gint end_offset);
static AtkAttributeSet* cally_text_get_run_attributes (AtkText *text,
gint offset,
gint *start_offset,
gint *end_offset);
static void _cally_text_get_selection_bounds (ClutterText *clutter_text,
gint *start_offset,
gint *end_offset);
static void _cally_text_text_changed_cb (ClutterText *clutter_text,
gpointer data);
static void _cally_text_insert_text_cb (ClutterText *clutter_text,
gchar *new_text,
gint new_text_length,
gint *position,
gpointer data);
static void _cally_text_delete_text_cb (ClutterText *clutter_text,
gint start_pos,
gint end_pos,
gpointer data);
static gboolean _idle_notify_insert (gpointer data);
static void _notify_insert (CallyText *cally_text);
static void _notify_delete (CallyText *cally_text);
/* AtkEditableText */
static void cally_text_editable_text_interface_init (AtkEditableTextIface *iface);
static void cally_text_set_text_contents (AtkEditableText *text,
const gchar *string);
static void cally_text_insert_text (AtkEditableText *text,
const gchar *string,
gint length,
gint *position);
static void cally_text_delete_text (AtkEditableText *text,
gint start_pos,
gint end_pos);
/* CallyActor */
static void cally_text_notify_clutter (GObject *obj,
GParamSpec *pspec);
static gboolean _check_for_selection_change (CallyText *cally_text,
ClutterText *clutter_text);
/* Misc functions */
static AtkAttributeSet* _cally_misc_add_attribute (AtkAttributeSet *attrib_set,
AtkTextAttribute attr,
gchar *value);
static AtkAttributeSet* _cally_misc_layout_get_run_attributes (AtkAttributeSet *attrib_set,
PangoLayout *layout,
gchar *text,
gint offset,
gint *start_offset,
gint *end_offset);
G_DEFINE_TYPE_WITH_CODE (CallyText,
cally_text,
CALLY_TYPE_ACTOR,
G_IMPLEMENT_INTERFACE (ATK_TYPE_TEXT,
cally_text_text_interface_init)
G_IMPLEMENT_INTERFACE (ATK_TYPE_EDITABLE_TEXT,
cally_text_editable_text_interface_init));
#define CALLY_TEXT_GET_PRIVATE(obj) \
(G_TYPE_INSTANCE_GET_PRIVATE ((obj), CALLY_TYPE_TEXT, CallyTextPrivate))
struct _CallyTextPrivate
{
/* Cached ClutterText values*/
gint cursor_position;
gint selection_bound;
/* text_changed::insert stuff */
gchar *signal_name_insert;
gint position_insert;
gint length_insert;
guint insert_idle_handler;
/* text_changed::delete stuff */
gchar *signal_name_delete;
gint position_delete;
gint length_delete;
/* action */
guint activate_action_id;
};
static void
cally_text_class_init (CallyTextClass *klass)
{
GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
AtkObjectClass *class = ATK_OBJECT_CLASS (klass);
CallyActorClass *cally_class = CALLY_ACTOR_CLASS (klass);
gobject_class->finalize = cally_text_finalize;
class->initialize = cally_text_real_initialize;
class->get_name = cally_text_get_name;
class->ref_state_set = cally_text_ref_state_set;
cally_class->notify_clutter = cally_text_notify_clutter;
g_type_class_add_private (gobject_class, sizeof (CallyTextPrivate));
}
static void
cally_text_init (CallyText *cally_text)
{
CallyTextPrivate *priv = CALLY_TEXT_GET_PRIVATE (cally_text);
cally_text->priv = priv;
priv->cursor_position = 0;
priv->selection_bound = 0;
priv->signal_name_insert = NULL;
priv->position_insert = -1;
priv->length_insert = -1;
priv->insert_idle_handler = 0;
priv->signal_name_delete = NULL;
priv->position_delete = -1;
priv->length_delete = -1;
priv->activate_action_id = 0;
}
static void
cally_text_finalize (GObject *obj)
{
CallyText *cally_text = CALLY_TEXT (obj);
/* g_object_unref (cally_text->priv->textutil); */
/* cally_text->priv->textutil = NULL; */
if (cally_text->priv->insert_idle_handler)
{
g_source_remove (cally_text->priv->insert_idle_handler);
cally_text->priv->insert_idle_handler = 0;
}
G_OBJECT_CLASS (cally_text_parent_class)->finalize (obj);
}
AtkObject*
cally_text_new (ClutterActor *actor)
{
GObject *object = NULL;
AtkObject *accessible = NULL;
g_return_val_if_fail (CLUTTER_IS_TEXT (actor), NULL);
object = g_object_new (CALLY_TYPE_TEXT, NULL);
accessible = ATK_OBJECT (object);
atk_object_initialize (accessible, actor);
return accessible;
}
/* atkobject.h */
static void
cally_text_real_initialize(AtkObject *obj,
gpointer data)
{
ClutterText *clutter_text = NULL;
CallyText *cally_text = NULL;
ATK_OBJECT_CLASS (cally_text_parent_class)->initialize (obj, data);
g_return_if_fail (CLUTTER_TEXT (data));
cally_text = CALLY_TEXT (obj);
clutter_text = CLUTTER_TEXT (data);
cally_text->priv->cursor_position = clutter_text_get_cursor_position (clutter_text);
cally_text->priv->selection_bound = clutter_text_get_selection_bound (clutter_text);
g_signal_connect (clutter_text, "text-changed",
G_CALLBACK (_cally_text_text_changed_cb),
cally_text);
g_signal_connect (clutter_text, "insert-text",
G_CALLBACK (_cally_text_insert_text_cb),
cally_text);
g_signal_connect (clutter_text, "delete-text",
G_CALLBACK (_cally_text_delete_text_cb),
cally_text);
_check_activate_action (cally_text, clutter_text);
obj->role = ATK_ROLE_TEXT;
}
static G_CONST_RETURN gchar *
cally_text_get_name (AtkObject *obj)
{
G_CONST_RETURN gchar *name;
g_return_val_if_fail (CALLY_IS_ACTOR (obj), NULL);
name = ATK_OBJECT_CLASS (cally_text_parent_class)->get_name (obj);
if (name == NULL)
{
ClutterActor *actor = NULL;
actor = CALLY_GET_CLUTTER_ACTOR (obj);
if (actor == NULL) /* State is defunct */
name = NULL;
else
name = clutter_text_get_text (CLUTTER_TEXT (actor));
}
return name;
}
static AtkStateSet*
cally_text_ref_state_set (AtkObject *obj)
{
AtkStateSet *result = NULL;
ClutterActor *actor = NULL;
result = ATK_OBJECT_CLASS (cally_text_parent_class)->ref_state_set (obj);
actor = CALLY_GET_CLUTTER_ACTOR (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;
}
/***** atktext.h ******/
static void
cally_text_text_interface_init (AtkTextIface *iface)
{
g_return_if_fail (iface != NULL);
iface->get_text = cally_text_get_text;
iface->get_character_at_offset = cally_text_get_character_at_offset;
iface->get_text_before_offset = cally_text_get_text_before_offset;
iface->get_text_at_offset = cally_text_get_text_at_offset;
iface->get_text_after_offset = cally_text_get_text_after_offset;
iface->get_character_count = cally_text_get_character_count;
iface->get_caret_offset = cally_text_get_caret_offset;
iface->set_caret_offset = cally_text_set_caret_offset;
iface->get_n_selections = cally_text_get_n_selections;
iface->get_selection = cally_text_get_selection;
iface->add_selection = cally_text_add_selection;
iface->remove_selection = cally_text_remove_selection;
iface->set_selection = cally_text_set_selection;
iface->get_run_attributes = cally_text_get_run_attributes;
/* iface->get_default_attributes = cally_text_get_default_attributes; */
/* iface->get_character_extents = */
/* iface->get_offset_at_point = */
}
static gchar*
cally_text_get_text (AtkText *text,
gint start_offset,
gint end_offset)
{
ClutterActor *actor = NULL;
g_return_val_if_fail (CALLY_IS_TEXT (text), NULL);
actor = CALLY_GET_CLUTTER_ACTOR (text);
if (actor == NULL) /* Object is defunct */
return NULL;
return clutter_text_get_chars (CLUTTER_TEXT (actor),
start_offset, end_offset);
}
static gunichar
cally_text_get_character_at_offset (AtkText *text,
gint offset)
{
ClutterActor *actor = NULL;
CallyText *cally_text = NULL;
gchar *string = NULL;
gchar *index = NULL;
gunichar unichar;
actor = CALLY_GET_CLUTTER_ACTOR (text);
if (actor == NULL) /* State is defunct */
return '\0';
cally_text = CALLY_TEXT (text);
string = clutter_text_get_chars (CLUTTER_TEXT (actor), 0, -1);
if (offset >= g_utf8_strlen (string, -1))
{
unichar = '\0';
}
else
{
index = g_utf8_offset_to_pointer (string, offset);
unichar = g_utf8_get_char(index);
}
g_free(string);
return unichar;
}
static gchar*
cally_text_get_text_before_offset (AtkText *text,
gint offset,
AtkTextBoundary boundary_type,
gint *start_offset,
gint *end_offset)
{
ClutterActor *actor = NULL;
ClutterText *clutter_text = NULL;
CallyText *cally_text = NULL;
actor = CALLY_GET_CLUTTER_ACTOR (text);
if (actor == NULL) /* State is defunct */
return NULL;
clutter_text = CLUTTER_TEXT (actor);
cally_text = CALLY_TEXT (text);
/* return gail_text_util_get_text (cally_text->priv->textutil, */
/* clutter_text_get_layout (clutter_text), */
/* GAIL_BEFORE_OFFSET, boundary_type, */
/* offset, start_offset, end_offset); */
return NULL;
}
static gchar*
cally_text_get_text_at_offset (AtkText *text,
gint offset,
AtkTextBoundary boundary_type,
gint *start_offset,
gint *end_offset)
{
ClutterActor *actor = NULL;
ClutterText *clutter_text = NULL;
CallyText *cally_text = NULL;
actor = CALLY_GET_CLUTTER_ACTOR (text);
if (actor == NULL) /* State is defunct */
return NULL;
clutter_text = CLUTTER_TEXT (actor);
cally_text = CALLY_TEXT (text);
/* return gail_text_util_get_text (cally_text->priv->textutil, */
/* clutter_text_get_layout (clutter_text), GAIL_AT_OFFSET, */
/* boundary_type, offset, start_offset, end_offset); */
return NULL;
}
static gchar*
cally_text_get_text_after_offset (AtkText *text,
gint offset,
AtkTextBoundary boundary_type,
gint *start_offset,
gint *end_offset)
{
ClutterActor *actor = NULL;
ClutterText *clutter_text = NULL;
CallyText *cally_text = NULL;
actor = CALLY_GET_CLUTTER_ACTOR (text);
if (actor == NULL) /* State is defunct */
return NULL;
clutter_text = CLUTTER_TEXT (actor);
cally_text = CALLY_TEXT (text);
/* return gail_text_util_get_text (cally_text->priv->textutil, */
/* clutter_text_get_layout (clutter_text), GAIL_AFTER_OFFSET, */
/* boundary_type, offset, start_offset, end_offset); */
return NULL;
}
static gint
cally_text_get_caret_offset (AtkText *text)
{
ClutterActor *actor = NULL;
actor = CALLY_GET_CLUTTER_ACTOR (text);
if (actor == NULL) /* State is defunct */
return -1;
return clutter_text_get_cursor_position (CLUTTER_TEXT (actor));
}
static gboolean
cally_text_set_caret_offset (AtkText *text,
gint offset)
{
ClutterActor *actor = NULL;
actor = CALLY_GET_CLUTTER_ACTOR (text);
if (actor == NULL) /* State is defunct */
return FALSE;
clutter_text_set_cursor_position (CLUTTER_TEXT (actor), offset);
/* like in gailentry, we suppose that this always works, as clutter text
doesn't return anything */
return TRUE;
}
static gint
cally_text_get_character_count (AtkText *text)
{
ClutterActor *actor = NULL;
ClutterText *clutter_text = NULL;
actor = CALLY_GET_CLUTTER_ACTOR (text);
if (actor == NULL) /* State is defunct */
return 0;
clutter_text = CLUTTER_TEXT (actor);
return g_utf8_strlen (clutter_text_get_text (clutter_text), -1);
}
static gint
cally_text_get_n_selections (AtkText *text)
{
ClutterActor *actor = NULL;
gint selection_bound = -1;
actor = CALLY_GET_CLUTTER_ACTOR (text);
if (actor == NULL) /* State is defunct */
return 0;
if (!clutter_text_get_selectable (CLUTTER_TEXT (actor)))
return 0;
selection_bound = clutter_text_get_selection_bound (CLUTTER_TEXT (actor));
if (selection_bound > 0)
return 1;
else
return 0;
}
static gchar*
cally_text_get_selection (AtkText *text,
gint selection_num,
gint *start_offset,
gint *end_offset)
{
ClutterActor *actor = NULL;
actor = CALLY_GET_CLUTTER_ACTOR (text);
if (actor == NULL) /* State is defunct */
return NULL;
/* As in gailentry, only let the user get the selection if one is set, and if
* the selection_num is 0.
*/
if (selection_num != 0)
return NULL;
_cally_text_get_selection_bounds (CLUTTER_TEXT (actor), start_offset, end_offset);
if (*start_offset != *end_offset)
return clutter_text_get_selection (CLUTTER_TEXT (actor));
else
return NULL;
}
/* ClutterText only allows one selection. So this method will set the selection
if no selection exists, but as in gailentry, it will not change the current
selection */
static gboolean
cally_text_add_selection (AtkText *text,
gint start_offset,
gint end_offset)
{
ClutterActor *actor;
gint select_start, select_end;
actor = CALLY_GET_CLUTTER_ACTOR (text);
if (actor == NULL) /* State is defunct */
return FALSE;
_cally_text_get_selection_bounds (CLUTTER_TEXT (actor),
&select_start, &select_end);
/* Like in gailentry, if there is already a selection, then don't allow another
* to be added, since ClutterText only supports one selected region.
*/
if (select_start == select_end)
{
clutter_text_set_selection (CLUTTER_TEXT (actor),
start_offset, end_offset);
return TRUE;
}
else
return FALSE;
}
static gboolean
cally_text_remove_selection (AtkText *text,
gint selection_num)
{
ClutterActor *actor = NULL;
gint caret_pos = -1;
gint select_start = -1;
gint select_end = -1;
actor = CALLY_GET_CLUTTER_ACTOR (text);
if (actor == NULL) /* State is defunct */
return FALSE;
/* only one selection is allowed */
if (selection_num != 0)
return FALSE;
_cally_text_get_selection_bounds (CLUTTER_TEXT (actor),
&select_start, &select_end);
if (select_start != select_end)
{
/* Setting the start & end of the selected region to the caret position
* turns off the selection.
*/
caret_pos = clutter_text_get_cursor_position (CLUTTER_TEXT (actor));
clutter_text_set_selection (CLUTTER_TEXT (actor),
caret_pos, caret_pos);
return TRUE;
}
else
return FALSE;
}
static gboolean
cally_text_set_selection (AtkText *text,
gint selection_num,
gint start_offset,
gint end_offset)
{
ClutterActor *actor = NULL;
gint select_start = -1;
gint select_end = -1;
actor = CALLY_GET_CLUTTER_ACTOR (text);
if (actor == NULL) /* State is defunct */
return FALSE;
/* Like in gailentry, only let the user move the selection if one is set,
* and if the selection_num is 0
*/
if (selection_num != 0)
return FALSE;
_cally_text_get_selection_bounds (CLUTTER_TEXT (actor),
&select_start, &select_end);
if (select_start != select_end)
{
clutter_text_set_selection (CLUTTER_TEXT (actor),
start_offset, end_offset);
return TRUE;
}
else
return FALSE;
}
static AtkAttributeSet*
cally_text_get_run_attributes (AtkText *text,
gint offset,
gint *start_offset,
gint *end_offset)
{
ClutterActor *actor = NULL;
ClutterText *clutter_text = NULL;
AtkAttributeSet *at_set = NULL;
actor = CALLY_GET_CLUTTER_ACTOR (text);
if (actor == NULL) /* State is defunct */
return NULL;
/* Clutter don't have any reference to the direction*/
clutter_text = CLUTTER_TEXT (actor);
at_set = _cally_misc_layout_get_run_attributes (at_set,
clutter_text_get_layout (clutter_text),
(gchar*)clutter_text_get_text (clutter_text),
offset,
start_offset,
end_offset);
return at_set;
}
/******** Auxiliar private methods ******/
/* ClutterText only maintains the current cursor position and a extra selection
bound, but this could be before or after the cursor. This method returns
the start and end positions in a proper order (so start<=end). This is
similar to the function gtk_editable_get_selection_bounds */
static void
_cally_text_get_selection_bounds (ClutterText *clutter_text,
gint *start_offset,
gint *end_offset)
{
gint pos = -1;
gint selection_bound = -1;
pos = clutter_text_get_cursor_position (clutter_text);
selection_bound = clutter_text_get_selection_bound (clutter_text);
if (pos < selection_bound)
{
*start_offset = pos;
*end_offset = selection_bound;
}
else
{
*start_offset = selection_bound;
*end_offset = pos;
}
}
static void
_cally_text_text_changed_cb (ClutterText *clutter_text,
gpointer data)
{
CallyText *cally_text = NULL;
g_return_if_fail (CALLY_IS_TEXT (data));
cally_text = CALLY_TEXT (data);
}
static void
_cally_text_delete_text_cb (ClutterText *clutter_text,
gint start_pos,
gint end_pos,
gpointer data)
{
CallyText *cally_text = NULL;
g_return_if_fail (CALLY_IS_TEXT (data));
/* Ignore zero lengh deletions */
if (end_pos - start_pos == 0)
return;
cally_text = CALLY_TEXT (data);
if (!cally_text->priv->signal_name_delete)
{
cally_text->priv->signal_name_delete = "text_changed::delete";
cally_text->priv->position_delete = start_pos;
cally_text->priv->length_delete = end_pos - start_pos;
}
_notify_delete (cally_text);
}
static void
_cally_text_insert_text_cb (ClutterText *clutter_text,
gchar *new_text,
gint new_text_length,
gint *position,
gpointer data)
{
CallyText *cally_text = NULL;
g_return_if_fail (CALLY_IS_TEXT (data));
cally_text = CALLY_TEXT (data);
if (!cally_text->priv->signal_name_insert)
{
cally_text->priv->signal_name_insert = "text_changed::insert";
cally_text->priv->position_insert = *position;
cally_text->priv->length_insert = g_utf8_strlen (new_text, new_text_length);
}
/*
* The signal will be emitted when the cursor position is updated,
* or in an idle handler if it not updated.
*/
if (cally_text->priv->insert_idle_handler == 0)
cally_text->priv->insert_idle_handler = clutter_threads_add_idle (_idle_notify_insert,
cally_text);
}
/***** atkeditabletext.h ******/
static void
cally_text_editable_text_interface_init (AtkEditableTextIface *iface)
{
g_return_if_fail (iface != NULL);
iface->set_text_contents = cally_text_set_text_contents;
iface->insert_text = cally_text_insert_text;
iface->delete_text = cally_text_delete_text;
/* Not implemented, see IMPLEMENTATION NOTES*/
iface->set_run_attributes = NULL;
iface->copy_text = NULL;
iface->cut_text = NULL;
iface->paste_text = NULL;
}
static void
cally_text_set_text_contents (AtkEditableText *text,
const gchar *string)
{
ClutterActor *actor = NULL;
actor = CALLY_GET_CLUTTER_ACTOR (text);
if (actor == NULL)
return;
if (!clutter_text_get_editable (CLUTTER_TEXT (actor)))
return;
clutter_text_set_text (CLUTTER_TEXT (actor),
string);
}
static void
cally_text_insert_text (AtkEditableText *text,
const gchar *string,
gint length,
gint *position)
{
ClutterActor *actor = NULL;
actor = CALLY_GET_CLUTTER_ACTOR (text);
if (actor == NULL)
return;
if (!clutter_text_get_editable (CLUTTER_TEXT (actor)))
return;
if (length < 0)
length = g_utf8_strlen (string, -1);
clutter_text_insert_text (CLUTTER_TEXT (actor),
string, *position);
/* we suppose that the text insertion will be succesful,
clutter-text doesn't warn about it. A option would be search for
the text, but it seems not really required */
*position += length;
}
static void cally_text_delete_text (AtkEditableText *text,
gint start_pos,
gint end_pos)
{
ClutterActor *actor = NULL;
actor = CALLY_GET_CLUTTER_ACTOR (text);
if (actor == NULL)
return;
if (!clutter_text_get_editable (CLUTTER_TEXT (actor)))
return;
clutter_text_delete_text (CLUTTER_TEXT (actor),
start_pos, end_pos);
}
/* CallyActor */
static void
cally_text_notify_clutter (GObject *obj,
GParamSpec *pspec)
{
ClutterText *clutter_text = NULL;
CallyText *cally_text = NULL;
AtkObject *atk_obj = NULL;
clutter_text = CLUTTER_TEXT (obj);
atk_obj = clutter_actor_get_accessible (CLUTTER_ACTOR (obj));
cally_text = CALLY_TEXT (atk_obj);
if (g_strcmp0 (pspec->name, "position") == 0)
{
/* the selection can change also for the cursor position */
if (_check_for_selection_change (cally_text, clutter_text))
g_signal_emit_by_name (atk_obj, "text_selection_changed");
g_signal_emit_by_name (atk_obj, "text_caret_moved",
clutter_text_get_cursor_position (clutter_text));
}
else if (g_strcmp0 (pspec->name, "selection-bound") == 0)
{
if (_check_for_selection_change (cally_text, clutter_text))
g_signal_emit_by_name (atk_obj, "text_selection_changed");
}
else if (g_strcmp0 (pspec->name, "editable") == 0)
{
atk_object_notify_state_change (atk_obj, ATK_STATE_EDITABLE,
clutter_text_get_editable (clutter_text));
}
else if (g_strcmp0 (pspec->name, "activatable") == 0)
{
_check_activate_action (cally_text, clutter_text);
}
else
{
CALLY_ACTOR_CLASS (cally_text_parent_class)->notify_clutter (obj, pspec);
}
}
static gboolean
_check_for_selection_change (CallyText *cally_text,
ClutterText *clutter_text)
{
gboolean ret_val = FALSE;
gint clutter_pos = -1;
gint clutter_bound = -1;
clutter_pos = clutter_text_get_cursor_position (clutter_text);
clutter_bound = clutter_text_get_selection_bound (clutter_text);
if (clutter_pos != clutter_bound)
{
if (clutter_pos != cally_text->priv->cursor_position ||
clutter_bound != cally_text->priv->selection_bound)
/*
* This check is here as this function can be called for
* notification of selection_bound and current_pos. The
* values of current_pos and selection_bound may be the same
* for both notifications and we only want to generate one
* text_selection_changed signal.
*/
ret_val = TRUE;
}
else
{
/* We had a selection */
ret_val = (cally_text->priv->cursor_position != cally_text->priv->selection_bound);
}
cally_text->priv->cursor_position = clutter_pos;
cally_text->priv->selection_bound = clutter_bound;
return ret_val;
}
static gboolean
_idle_notify_insert (gpointer data)
{
CallyText *cally_text = NULL;
cally_text = CALLY_TEXT (data);
cally_text->priv->insert_idle_handler = 0;
_notify_insert (cally_text);
return FALSE;
}
static void
_notify_insert (CallyText *cally_text)
{
if (cally_text->priv->signal_name_insert)
{
g_signal_emit_by_name (cally_text,
cally_text->priv->signal_name_insert,
cally_text->priv->position_insert,
cally_text->priv->length_insert);
cally_text->priv->signal_name_insert = NULL;
}
}
static void
_notify_delete (CallyText *cally_text)
{
if (cally_text->priv->signal_name_delete)
{
g_signal_emit_by_name (cally_text,
cally_text->priv->signal_name_delete,
cally_text->priv->position_delete,
cally_text->priv->length_delete);
cally_text->priv->signal_name_delete = NULL;
}
}
/* atkaction */
static void
_cally_text_activate_action (CallyActor *cally_actor)
{
ClutterActor *actor = NULL;
actor = CALLY_GET_CLUTTER_ACTOR (cally_actor);
clutter_text_activate (CLUTTER_TEXT (actor));
}
static void
_check_activate_action (CallyText *cally_text,
ClutterText *clutter_text)
{
if (clutter_text_get_activatable (clutter_text))
{
if (cally_text->priv->activate_action_id != 0)
return;
cally_text->priv->activate_action_id = cally_actor_add_action (CALLY_ACTOR (cally_text),
"activate", NULL, NULL,
_cally_text_activate_action);
}
else
{
if (cally_text->priv->activate_action_id == 0)
return;
if (cally_actor_remove_action (CALLY_ACTOR (cally_text),
cally_text->priv->activate_action_id))
{
cally_text->priv->activate_action_id = 0;
}
}
}
/* GailTextUtil/GailMisc reimplementation methods */
/**
* _cally_misc_add_attribute:
*
* Reimplementation of gail_misc_layout_get_run_attributes (check this
* function for more documentation).
*
* Returns: A pointer to the new #AtkAttributeSet.
**/
static AtkAttributeSet*
_cally_misc_add_attribute (AtkAttributeSet *attrib_set,
AtkTextAttribute attr,
gchar *value)
{
AtkAttributeSet *return_set;
AtkAttribute *at = g_malloc (sizeof (AtkAttribute));
at->name = g_strdup (atk_text_attribute_get_name (attr));
at->value = value;
return_set = g_slist_prepend(attrib_set, at);
return return_set;
}
/**
* _cally_misc_layout_get_run_attributes:
*
* Reimplementation of gail_misc_layout_get_run_attributes (check this
* function for more documentation).
*
* Returns: A pointer to the #AtkAttributeSet.
**/
static AtkAttributeSet*
_cally_misc_layout_get_run_attributes (AtkAttributeSet *attrib_set,
PangoLayout *layout,
gchar *text,
gint offset,
gint *start_offset,
gint *end_offset)
{
PangoAttrIterator *iter;
PangoAttrList *attr;
PangoAttrString *pango_string;
PangoAttrInt *pango_int;
PangoAttrColor *pango_color;
PangoAttrLanguage *pango_lang;
PangoAttrFloat *pango_float;
gint index, start_index, end_index;
gboolean is_next = TRUE;
gchar *value = NULL;
glong len;
len = g_utf8_strlen (text, -1);
/* Grab the attributes of the PangoLayout, if any */
if ((attr = pango_layout_get_attributes (layout)) == NULL)
{
*start_offset = 0;
*end_offset = len;
return attrib_set;
}
iter = pango_attr_list_get_iterator (attr);
/* Get invariant range offsets */
/* If offset out of range, set offset in range */
if (offset > len)
offset = len;
else if (offset < 0)
offset = 0;
index = g_utf8_offset_to_pointer (text, offset) - text;
pango_attr_iterator_range (iter, &start_index, &end_index);
while (is_next)
{
if (index >= start_index && index < end_index)
{
*start_offset = g_utf8_pointer_to_offset (text,
text + start_index);
if (end_index == G_MAXINT)
/* Last iterator */
end_index = len;
*end_offset = g_utf8_pointer_to_offset (text,
text + end_index);
break;
}
is_next = pango_attr_iterator_next (iter);
pango_attr_iterator_range (iter, &start_index, &end_index);
}
/* Get attributes */
if ((pango_string = (PangoAttrString*) pango_attr_iterator_get (iter,
PANGO_ATTR_FAMILY)) != NULL)
{
value = g_strdup_printf("%s", pango_string->value);
attrib_set = _cally_misc_add_attribute (attrib_set,
ATK_TEXT_ATTR_FAMILY_NAME,
value);
}
if ((pango_int = (PangoAttrInt*) pango_attr_iterator_get (iter,
PANGO_ATTR_STYLE)) != NULL)
{
attrib_set = _cally_misc_add_attribute (attrib_set,
ATK_TEXT_ATTR_STYLE,
g_strdup (atk_text_attribute_get_value (ATK_TEXT_ATTR_STYLE, pango_int->value)));
}
if ((pango_int = (PangoAttrInt*) pango_attr_iterator_get (iter,
PANGO_ATTR_WEIGHT)) != NULL)
{
value = g_strdup_printf("%i", pango_int->value);
attrib_set = _cally_misc_add_attribute (attrib_set,
ATK_TEXT_ATTR_WEIGHT,
value);
}
if ((pango_int = (PangoAttrInt*) pango_attr_iterator_get (iter,
PANGO_ATTR_VARIANT)) != NULL)
{
attrib_set = _cally_misc_add_attribute (attrib_set,
ATK_TEXT_ATTR_VARIANT,
g_strdup (atk_text_attribute_get_value (ATK_TEXT_ATTR_VARIANT, pango_int->value)));
}
if ((pango_int = (PangoAttrInt*) pango_attr_iterator_get (iter,
PANGO_ATTR_STRETCH)) != NULL)
{
attrib_set = _cally_misc_add_attribute (attrib_set,
ATK_TEXT_ATTR_STRETCH,
g_strdup (atk_text_attribute_get_value (ATK_TEXT_ATTR_STRETCH, pango_int->value)));
}
if ((pango_int = (PangoAttrInt*) pango_attr_iterator_get (iter,
PANGO_ATTR_SIZE)) != NULL)
{
value = g_strdup_printf("%i", pango_int->value / PANGO_SCALE);
attrib_set = _cally_misc_add_attribute (attrib_set,
ATK_TEXT_ATTR_SIZE,
value);
}
if ((pango_int = (PangoAttrInt*) pango_attr_iterator_get (iter,
PANGO_ATTR_UNDERLINE)) != NULL)
{
attrib_set = _cally_misc_add_attribute (attrib_set,
ATK_TEXT_ATTR_UNDERLINE,
g_strdup (atk_text_attribute_get_value (ATK_TEXT_ATTR_UNDERLINE, pango_int->value)));
}
if ((pango_int = (PangoAttrInt*) pango_attr_iterator_get (iter,
PANGO_ATTR_STRIKETHROUGH)) != NULL)
{
attrib_set = _cally_misc_add_attribute (attrib_set,
ATK_TEXT_ATTR_STRIKETHROUGH,
g_strdup (atk_text_attribute_get_value (ATK_TEXT_ATTR_STRIKETHROUGH, pango_int->value)));
}
if ((pango_int = (PangoAttrInt*) pango_attr_iterator_get (iter,
PANGO_ATTR_RISE)) != NULL)
{
value = g_strdup_printf("%i", pango_int->value);
attrib_set = _cally_misc_add_attribute (attrib_set,
ATK_TEXT_ATTR_RISE,
value);
}
if ((pango_lang = (PangoAttrLanguage*) pango_attr_iterator_get (iter,
PANGO_ATTR_LANGUAGE)) != NULL)
{
value = g_strdup( pango_language_to_string( pango_lang->value));
attrib_set = _cally_misc_add_attribute (attrib_set,
ATK_TEXT_ATTR_LANGUAGE,
value);
}
if ((pango_float = (PangoAttrFloat*) pango_attr_iterator_get (iter,
PANGO_ATTR_SCALE)) != NULL)
{
value = g_strdup_printf("%g", pango_float->value);
attrib_set = _cally_misc_add_attribute (attrib_set,
ATK_TEXT_ATTR_SCALE,
value);
}
if ((pango_color = (PangoAttrColor*) pango_attr_iterator_get (iter,
PANGO_ATTR_FOREGROUND)) != NULL)
{
value = g_strdup_printf ("%u,%u,%u",
pango_color->color.red,
pango_color->color.green,
pango_color->color.blue);
attrib_set = _cally_misc_add_attribute (attrib_set,
ATK_TEXT_ATTR_FG_COLOR,
value);
}
if ((pango_color = (PangoAttrColor*) pango_attr_iterator_get (iter,
PANGO_ATTR_BACKGROUND)) != NULL)
{
value = g_strdup_printf ("%u,%u,%u",
pango_color->color.red,
pango_color->color.green,
pango_color->color.blue);
attrib_set = _cally_misc_add_attribute (attrib_set,
ATK_TEXT_ATTR_BG_COLOR,
value);
}
pango_attr_iterator_destroy (iter);
return attrib_set;
}