gtk2/gtk/a11y/gtktextcellaccessible.c
Cosimo Cecchi 3473577386 a11y: fix a typo leading to a crash
text can't be NULL here, or we would have failed way earlier.
The original code from gail returned if the renderer text was NULL, and
we have to do that, or we'll end up calling e.g.
g_utf8_offset_to_pointer() on a NULL pointer.

https://bugzilla.redhat.com/show_bug.cgi?id=827930

https://bugzilla.gnome.org/show_bug.cgi?id=677551
2012-06-07 16:51:02 -04:00

745 lines
25 KiB
C

/* GAIL - The GNOME Accessibility Enabling Library
* Copyright 2001 Sun Microsystems Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*/
#include "config.h"
#include <gtk/gtk.h>
#include "../gtkpango.h"
#include "gtktextcellaccessible.h"
#include "gtkcontainercellaccessible.h"
#include "gtkcellaccessibleparent.h"
static const gchar* gtk_text_cell_accessible_get_name (AtkObject *atk_obj);
/* atktext.h */
static gchar* gtk_text_cell_accessible_get_text (AtkText *text,
gint start_pos,
gint end_pos);
static gunichar gtk_text_cell_accessible_get_character_at_offset (AtkText *text,
gint offset);
static gchar* gtk_text_cell_accessible_get_text_before_offset (AtkText *text,
gint offset,
AtkTextBoundary boundary_type,
gint *start_offset,
gint *end_offset);
static gchar* gtk_text_cell_accessible_get_text_at_offset (AtkText *text,
gint offset,
AtkTextBoundary boundary_type,
gint *start_offset,
gint *end_offset);
static gchar* gtk_text_cell_accessible_get_text_after_offset (AtkText *text,
gint offset,
AtkTextBoundary boundary_type,
gint *start_offset,
gint *end_offset);
static gint gtk_text_cell_accessible_get_character_count (AtkText *text);
static gint gtk_text_cell_accessible_get_caret_offset (AtkText *text);
static gboolean gtk_text_cell_accessible_set_caret_offset (AtkText *text,
gint offset);
static void gtk_text_cell_accessible_get_character_extents (AtkText *text,
gint offset,
gint *x,
gint *y,
gint *width,
gint *height,
AtkCoordType coords);
static gint gtk_text_cell_accessible_get_offset_at_point (AtkText *text,
gint x,
gint y,
AtkCoordType coords);
static AtkAttributeSet* gtk_text_cell_accessible_get_run_attributes
(AtkText *text,
gint offset,
gint *start_offset,
gint *end_offset);
static AtkAttributeSet* gtk_text_cell_accessible_get_default_attributes
(AtkText *text);
static GtkWidget* get_widget (GtkTextCellAccessible *cell);
static PangoLayout* create_pango_layout (GtkTextCellAccessible *cell);
static void add_attr (PangoAttrList *attr_list,
PangoAttribute *attr);
/* Misc */
static void gtk_text_cell_accessible_update_cache (GtkCellAccessible *cell);
static void atk_text_interface_init (AtkTextIface *iface);
G_DEFINE_TYPE_WITH_CODE (GtkTextCellAccessible, _gtk_text_cell_accessible, GTK_TYPE_RENDERER_CELL_ACCESSIBLE,
G_IMPLEMENT_INTERFACE (ATK_TYPE_TEXT, atk_text_interface_init))
static AtkStateSet *
gtk_text_cell_accessible_ref_state_set (AtkObject *accessible)
{
AtkStateSet *state_set;
state_set = ATK_OBJECT_CLASS (_gtk_text_cell_accessible_parent_class)->ref_state_set (accessible);
atk_state_set_add_state (state_set, ATK_STATE_SINGLE_LINE);
return state_set;
}
static void
gtk_text_cell_accessible_finalize (GObject *object)
{
GtkTextCellAccessible *text_cell = GTK_TEXT_CELL_ACCESSIBLE (object);
g_free (text_cell->cell_text);
G_OBJECT_CLASS (_gtk_text_cell_accessible_parent_class)->finalize (object);
}
static const gchar *
gtk_text_cell_accessible_get_name (AtkObject *atk_obj)
{
GtkTextCellAccessible *text_cell = GTK_TEXT_CELL_ACCESSIBLE (atk_obj);
if (atk_obj->name)
return atk_obj->name;
return text_cell->cell_text;
}
static void
gtk_text_cell_accessible_update_cache (GtkCellAccessible *cell)
{
GtkTextCellAccessible *text_cell = GTK_TEXT_CELL_ACCESSIBLE (cell);
AtkObject *obj = ATK_OBJECT (cell);
gboolean rv = FALSE;
gint temp_length;
gchar *text;
g_object_get (G_OBJECT (GTK_RENDERER_CELL_ACCESSIBLE (cell)->renderer),
"text", &text,
NULL);
if (text_cell->cell_text)
{
if (text == NULL || g_strcmp0 (text_cell->cell_text, text) != 0)
{
g_free (text_cell->cell_text);
temp_length = text_cell->cell_length;
text_cell->cell_text = NULL;
text_cell->cell_length = 0;
g_signal_emit_by_name (cell, "text-changed::delete", 0, temp_length);
if (obj->name == NULL)
g_object_notify (G_OBJECT (obj), "accessible-name");
if (text)
rv = TRUE;
}
}
else
rv = TRUE;
if (rv)
{
if (text == NULL)
{
text_cell->cell_text = g_strdup ("");
text_cell->cell_length = 0;
}
else
{
text_cell->cell_text = g_strdup (text);
text_cell->cell_length = g_utf8_strlen (text, -1);
}
}
g_free (text);
if (rv)
{
g_signal_emit_by_name (cell, "text-changed::insert",
0, text_cell->cell_length);
if (obj->name == NULL)
g_object_notify (G_OBJECT (obj), "accessible-name");
}
}
static void
_gtk_text_cell_accessible_class_init (GtkTextCellAccessibleClass *klass)
{
GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
AtkObjectClass *atk_object_class = ATK_OBJECT_CLASS (klass);
GtkCellAccessibleClass *cell_class = GTK_CELL_ACCESSIBLE_CLASS (klass);
cell_class->update_cache = gtk_text_cell_accessible_update_cache;
atk_object_class->get_name = gtk_text_cell_accessible_get_name;
atk_object_class->ref_state_set = gtk_text_cell_accessible_ref_state_set;
gobject_class->finalize = gtk_text_cell_accessible_finalize;
}
static void
_gtk_text_cell_accessible_init (GtkTextCellAccessible *text_cell)
{
text_cell->cell_text = NULL;
text_cell->caret_pos = 0;
text_cell->cell_length = 0;
}
static gchar *
gtk_text_cell_accessible_get_text (AtkText *atk_text,
gint start_pos,
gint end_pos)
{
gchar *text;
text = GTK_TEXT_CELL_ACCESSIBLE (atk_text)->cell_text;
if (text)
return g_utf8_substring (text, start_pos, end_pos > -1 ? end_pos : g_utf8_strlen (text, -1));
else
return g_strdup ("");
}
static gchar *
gtk_text_cell_accessible_get_text_before_offset (AtkText *atk_text,
gint offset,
AtkTextBoundary boundary_type,
gint *start_offset,
gint *end_offset)
{
PangoLayout *layout;
gchar *text;
layout = create_pango_layout (GTK_TEXT_CELL_ACCESSIBLE (atk_text));
text = _gtk_pango_get_text_before (layout, boundary_type, offset, start_offset, end_offset);
g_object_unref (layout);
return text;
}
static gchar *
gtk_text_cell_accessible_get_text_at_offset (AtkText *atk_text,
gint offset,
AtkTextBoundary boundary_type,
gint *start_offset,
gint *end_offset)
{
PangoLayout *layout;
gchar *text;
layout = create_pango_layout (GTK_TEXT_CELL_ACCESSIBLE (atk_text));
text = _gtk_pango_get_text_at (layout, boundary_type, offset, start_offset, end_offset);
g_object_unref (layout);
return text;
}
static gchar *
gtk_text_cell_accessible_get_text_after_offset (AtkText *atk_text,
gint offset,
AtkTextBoundary boundary_type,
gint *start_offset,
gint *end_offset)
{
PangoLayout *layout;
gchar *text;
layout = create_pango_layout (GTK_TEXT_CELL_ACCESSIBLE (atk_text));
text = _gtk_pango_get_text_after (layout, boundary_type, offset, start_offset, end_offset);
g_object_unref (layout);
return text;
}
static gint
gtk_text_cell_accessible_get_character_count (AtkText *text)
{
if (GTK_TEXT_CELL_ACCESSIBLE (text)->cell_text != NULL)
return GTK_TEXT_CELL_ACCESSIBLE (text)->cell_length;
else
return 0;
}
static gint
gtk_text_cell_accessible_get_caret_offset (AtkText *text)
{
return GTK_TEXT_CELL_ACCESSIBLE (text)->caret_pos;
}
static gboolean
gtk_text_cell_accessible_set_caret_offset (AtkText *text,
gint offset)
{
GtkTextCellAccessible *text_cell = GTK_TEXT_CELL_ACCESSIBLE (text);
if (text_cell->cell_text == NULL)
return FALSE;
else
{
/* Only set the caret within the bounds and if it is to a new position. */
if (offset >= 0 &&
offset <= text_cell->cell_length &&
offset != text_cell->caret_pos)
{
text_cell->caret_pos = offset;
/* emit the signal */
g_signal_emit_by_name (text, "text-caret-moved", offset);
return TRUE;
}
else
return FALSE;
}
}
static AtkAttributeSet *
gtk_text_cell_accessible_get_run_attributes (AtkText *text,
gint offset,
gint *start_offset,
gint *end_offset)
{
AtkAttributeSet *attrib_set = NULL;
PangoLayout *layout;
layout = create_pango_layout (GTK_TEXT_CELL_ACCESSIBLE (text));
attrib_set = _gtk_pango_get_run_attributes (NULL, layout, offset, start_offset, end_offset);
g_object_unref (G_OBJECT (layout));
return attrib_set;
}
static AtkAttributeSet *
add_attribute (AtkAttributeSet *attributes,
AtkTextAttribute attr,
const gchar *value)
{
AtkAttribute *at;
at = g_new (AtkAttribute, 1);
at->name = g_strdup (atk_text_attribute_get_name (attr));
at->value = g_strdup (value);
return g_slist_prepend (attributes, at);
}
static AtkAttributeSet *
gtk_text_cell_accessible_get_default_attributes (AtkText *text)
{
AtkAttributeSet *attrib_set = NULL;
PangoLayout *layout;
GtkWidget *widget;
layout = create_pango_layout (GTK_TEXT_CELL_ACCESSIBLE (text));
widget = get_widget (GTK_TEXT_CELL_ACCESSIBLE (text));
attrib_set = add_attribute (attrib_set, ATK_TEXT_ATTR_DIRECTION,
atk_text_attribute_get_value (ATK_TEXT_ATTR_DIRECTION,
gtk_widget_get_direction (widget)));
attrib_set = _gtk_pango_get_default_attributes (NULL, layout);
attrib_set = _gtk_style_context_get_attributes (attrib_set,
gtk_widget_get_style_context (widget),
gtk_widget_get_state_flags (widget));
g_object_unref (G_OBJECT (layout));
return attrib_set;
}
GtkWidget *
get_widget (GtkTextCellAccessible *text)
{
AtkObject *parent;
parent = atk_object_get_parent (ATK_OBJECT (text));
if (GTK_IS_CONTAINER_CELL_ACCESSIBLE (parent))
parent = atk_object_get_parent (parent);
return gtk_accessible_get_widget (GTK_ACCESSIBLE (parent));
}
/* This function is used by gtk_text_cell_accessible_get_offset_at_point()
* and gtk_text_cell_accessible_get_character_extents(). There is no
* cached PangoLayout so we must create a temporary one using this function.
*/
static PangoLayout *
create_pango_layout (GtkTextCellAccessible *text)
{
GdkRGBA *foreground_rgba;
PangoAttrList *attr_list, *attributes;
PangoLayout *layout;
PangoUnderline uline, underline;
PangoFontMask mask;
PangoFontDescription *font_desc;
gboolean foreground_set, strikethrough_set, strikethrough;
gboolean scale_set, underline_set, rise_set;
gchar *renderer_text;
gdouble scale;
gint rise;
GtkRendererCellAccessible *gail_renderer;
GtkCellRendererText *gtk_renderer;
gail_renderer = GTK_RENDERER_CELL_ACCESSIBLE (text);
gtk_renderer = GTK_CELL_RENDERER_TEXT (gail_renderer->renderer);
g_object_get (gtk_renderer,
"text", &renderer_text,
"attributes", &attributes,
"foreground-set", &foreground_set,
"foreground-rgba", &foreground_rgba,
"strikethrough-set", &strikethrough_set,
"strikethrough", &strikethrough,
"font-desc", &font_desc,
"scale-set", &scale_set,
"scale", &scale,
"underline-set", &underline_set,
"underline", &underline,
"rise-set", &rise_set,
"rise", &rise,
NULL);
layout = gtk_widget_create_pango_layout (get_widget (text), renderer_text);
if (attributes)
attr_list = pango_attr_list_copy (attributes);
else
attr_list = pango_attr_list_new ();
if (foreground_set)
{
add_attr (attr_list, pango_attr_foreground_new (foreground_rgba->red * 65535,
foreground_rgba->green * 65535,
foreground_rgba->blue * 65535));
}
if (strikethrough_set)
add_attr (attr_list,
pango_attr_strikethrough_new (strikethrough));
mask = pango_font_description_get_set_fields (font_desc);
if (mask & PANGO_FONT_MASK_FAMILY)
add_attr (attr_list,
pango_attr_family_new (pango_font_description_get_family (font_desc)));
if (mask & PANGO_FONT_MASK_STYLE)
add_attr (attr_list, pango_attr_style_new (pango_font_description_get_style (font_desc)));
if (mask & PANGO_FONT_MASK_VARIANT)
add_attr (attr_list, pango_attr_variant_new (pango_font_description_get_variant (font_desc)));
if (mask & PANGO_FONT_MASK_WEIGHT)
add_attr (attr_list, pango_attr_weight_new (pango_font_description_get_weight (font_desc)));
if (mask & PANGO_FONT_MASK_STRETCH)
add_attr (attr_list, pango_attr_stretch_new (pango_font_description_get_stretch (font_desc)));
if (mask & PANGO_FONT_MASK_SIZE)
add_attr (attr_list, pango_attr_size_new (pango_font_description_get_size (font_desc)));
if (scale_set && scale != 1.0)
add_attr (attr_list, pango_attr_scale_new (scale));
if (underline_set)
uline = underline;
else
uline = PANGO_UNDERLINE_NONE;
if (uline != PANGO_UNDERLINE_NONE)
add_attr (attr_list,
pango_attr_underline_new (underline));
if (rise_set)
add_attr (attr_list, pango_attr_rise_new (rise));
pango_layout_set_attributes (layout, attr_list);
pango_layout_set_width (layout, -1);
pango_attr_list_unref (attr_list);
pango_font_description_free (font_desc);
pango_attr_list_unref (attributes);
g_free (renderer_text);
gdk_rgba_free (foreground_rgba);
return layout;
}
static void
add_attr (PangoAttrList *attr_list,
PangoAttribute *attr)
{
attr->start_index = 0;
attr->end_index = G_MAXINT;
pango_attr_list_insert (attr_list, attr);
}
static void
get_origins (GtkWidget *widget,
gint *x_window,
gint *y_window,
gint *x_toplevel,
gint *y_toplevel)
{
GdkWindow *window;
if (GTK_IS_TREE_VIEW (widget))
window = gtk_tree_view_get_bin_window (GTK_TREE_VIEW (widget));
else
window = gtk_widget_get_window (widget);
gdk_window_get_origin (window, x_window, y_window);
window = gdk_window_get_toplevel (gtk_widget_get_window (widget));
gdk_window_get_origin (window, x_toplevel, y_toplevel);
}
static void
gtk_text_cell_accessible_get_character_extents (AtkText *text,
gint offset,
gint *x,
gint *y,
gint *width,
gint *height,
AtkCoordType coords)
{
GtkRendererCellAccessible *gail_renderer;
GtkRequisition min_size;
GtkCellRendererText *gtk_renderer;
GdkRectangle rendered_rect;
GtkWidget *widget;
AtkObject *parent;
PangoRectangle char_rect;
PangoLayout *layout;
gchar *renderer_text;
gfloat xalign, yalign;
gint x_offset, y_offset, index;
gint xpad, ypad;
gint x_window, y_window, x_toplevel, y_toplevel;
if (!GTK_TEXT_CELL_ACCESSIBLE (text)->cell_text)
{
*x = *y = *height = *width = 0;
return;
}
if (offset < 0 || offset >= GTK_TEXT_CELL_ACCESSIBLE (text)->cell_length)
{
*x = *y = *height = *width = 0;
return;
}
gail_renderer = GTK_RENDERER_CELL_ACCESSIBLE (text);
gtk_renderer = GTK_CELL_RENDERER_TEXT (gail_renderer->renderer);
g_object_get (gtk_renderer, "text", &renderer_text, NULL);
if (renderer_text == NULL)
return;
parent = atk_object_get_parent (ATK_OBJECT (text));
if (GTK_IS_CONTAINER_CELL_ACCESSIBLE (parent))
parent = atk_object_get_parent (parent);
widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (parent));
g_return_if_fail (GTK_IS_CELL_ACCESSIBLE_PARENT (parent));
_gtk_cell_accessible_parent_get_cell_area (GTK_CELL_ACCESSIBLE_PARENT (parent),
GTK_CELL_ACCESSIBLE (text),
&rendered_rect);
gtk_cell_renderer_get_preferred_size (GTK_CELL_RENDERER (gtk_renderer),
widget,
&min_size, NULL);
gtk_cell_renderer_get_alignment (GTK_CELL_RENDERER (gtk_renderer), &xalign, &yalign);
if (gtk_widget_get_direction (widget) == GTK_TEXT_DIR_RTL)
xalign = 1.0 - xalign;
x_offset = MAX (0, xalign * (rendered_rect.width - min_size.width));
y_offset = MAX (0, yalign * (rendered_rect.height - min_size.height));
layout = create_pango_layout (GTK_TEXT_CELL_ACCESSIBLE (text));
index = g_utf8_offset_to_pointer (renderer_text, offset) - renderer_text;
pango_layout_index_to_pos (layout, index, &char_rect);
gtk_cell_renderer_get_padding (gail_renderer->renderer, &xpad, &ypad);
get_origins (widget, &x_window, &y_window, &x_toplevel, &y_toplevel);
*x = (char_rect.x / PANGO_SCALE) + x_offset + rendered_rect.x + xpad + x_window;
*y = (char_rect.y / PANGO_SCALE) + y_offset + rendered_rect.y + ypad + y_window;
*height = char_rect.height / PANGO_SCALE;
*width = char_rect.width / PANGO_SCALE;
if (coords == ATK_XY_WINDOW)
{
*x -= x_toplevel;
*y -= y_toplevel;
}
else if (coords != ATK_XY_SCREEN)
{
*x = 0;
*y = 0;
*height = 0;
*width = 0;
}
g_free (renderer_text);
g_object_unref (layout);
}
static gint
gtk_text_cell_accessible_get_offset_at_point (AtkText *text,
gint x,
gint y,
AtkCoordType coords)
{
AtkObject *parent;
GtkRendererCellAccessible *gail_renderer;
GtkCellRendererText *gtk_renderer;
GtkRequisition min_size;
GtkWidget *widget;
GdkRectangle rendered_rect;
PangoLayout *layout;
gchar *renderer_text;
gfloat xalign, yalign;
gint x_offset, y_offset, index;
gint xpad, ypad;
gint x_window, y_window, x_toplevel, y_toplevel;
gint x_temp, y_temp;
gboolean ret;
if (!GTK_TEXT_CELL_ACCESSIBLE (text)->cell_text)
return -1;
gail_renderer = GTK_RENDERER_CELL_ACCESSIBLE (text);
gtk_renderer = GTK_CELL_RENDERER_TEXT (gail_renderer->renderer);
parent = atk_object_get_parent (ATK_OBJECT (text));
g_object_get (gtk_renderer, "text", &renderer_text, NULL);
if (text == NULL)
{
g_free (renderer_text);
return -1;
}
if (GTK_IS_CONTAINER_CELL_ACCESSIBLE (parent))
parent = atk_object_get_parent (parent);
widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (parent));
g_return_val_if_fail (GTK_IS_CELL_ACCESSIBLE_PARENT (parent), -1);
_gtk_cell_accessible_parent_get_cell_area (GTK_CELL_ACCESSIBLE_PARENT (parent),
GTK_CELL_ACCESSIBLE (text),
&rendered_rect);
gtk_cell_renderer_get_preferred_size (GTK_CELL_RENDERER (gtk_renderer),
widget,
&min_size, NULL);
gtk_cell_renderer_get_alignment (GTK_CELL_RENDERER (gtk_renderer), &xalign, &yalign);
if (gtk_widget_get_direction (widget) == GTK_TEXT_DIR_RTL)
xalign = 1.0 - xalign;
x_offset = MAX (0, xalign * (rendered_rect.width - min_size.width));
y_offset = MAX (0, yalign * (rendered_rect.height - min_size.height));
layout = create_pango_layout (GTK_TEXT_CELL_ACCESSIBLE (text));
gtk_cell_renderer_get_padding (gail_renderer->renderer, &xpad, &ypad);
get_origins (widget, &x_window, &y_window, &x_toplevel, &y_toplevel);
x_temp = x - (x_offset + rendered_rect.x + xpad) - x_window;
y_temp = y - (y_offset + rendered_rect.y + ypad) - y_window;
if (coords == ATK_XY_WINDOW)
{
x_temp += x_toplevel;
y_temp += y_toplevel;
}
else if (coords != ATK_XY_SCREEN)
index = -1;
ret = pango_layout_xy_to_index (layout,
x_temp * PANGO_SCALE,
y_temp * PANGO_SCALE,
&index, NULL);
if (!ret)
{
if (x_temp < 0 || y_temp < 0)
index = 0;
else
index = -1;
}
g_object_unref (layout);
if (index == -1)
{
if (coords == ATK_XY_WINDOW || coords == ATK_XY_SCREEN)
{
glong length;
length = g_utf8_strlen (renderer_text, -1);
g_free (renderer_text);
return length;
}
g_free (renderer_text);
return index;
}
else
{
glong offset;
offset = g_utf8_pointer_to_offset (renderer_text,
renderer_text + index);
g_free (renderer_text);
return offset;
}
}
static gunichar
gtk_text_cell_accessible_get_character_at_offset (AtkText *text,
gint offset)
{
gchar *index;
gchar *string;
string = GTK_TEXT_CELL_ACCESSIBLE(text)->cell_text;
if (!string)
return '\0';
if (offset >= g_utf8_strlen (string, -1))
return '\0';
index = g_utf8_offset_to_pointer (string, offset);
return g_utf8_get_char (index);
}
static void
atk_text_interface_init (AtkTextIface *iface)
{
iface->get_text = gtk_text_cell_accessible_get_text;
iface->get_character_at_offset = gtk_text_cell_accessible_get_character_at_offset;
iface->get_text_before_offset = gtk_text_cell_accessible_get_text_before_offset;
iface->get_text_at_offset = gtk_text_cell_accessible_get_text_at_offset;
iface->get_text_after_offset = gtk_text_cell_accessible_get_text_after_offset;
iface->get_character_count = gtk_text_cell_accessible_get_character_count;
iface->get_caret_offset = gtk_text_cell_accessible_get_caret_offset;
iface->set_caret_offset = gtk_text_cell_accessible_set_caret_offset;
iface->get_run_attributes = gtk_text_cell_accessible_get_run_attributes;
iface->get_default_attributes = gtk_text_cell_accessible_get_default_attributes;
iface->get_character_extents = gtk_text_cell_accessible_get_character_extents;
iface->get_offset_at_point = gtk_text_cell_accessible_get_offset_at_point;
}