This is a temporary move that aims to make GTK's accessibility support independent of external components. ATK has lived outside of GTK since its inception, as it was meant to be a generic API implemented by different toolkits; that mostly failed, and we're now stuck with an abstraction layer that doesn't really abstract anything of value. What it did bring, on the other hand, was maintenance burden, with changes being propagated across different projects—ATK, GTK, and whatever accessibility layer implemented the bridge to ATSPI on Linux. This complexity is completely unwarranted for the benefits it gives us. Ideally, we'd get rid of ATK entirely, and expose the accessility API as part of the GTK namespace, but that is a fairly sizeable work, so we can start from this step. The ATK namespace is maintained, similarly to how we maintain the GDK, GSK, and GTK namespaces. The API is still public, so that GTK and out of tree widgets can consume it. The inclusion of ATK comes with the removal of the deprecated entry points and types, as that's a good chance to begin the cleanup of this mess of an API. Sadly, this also means we have to disable the a11y tests for the time being, as they depend on rando API from 2002. Another side effect of moving ATK in tree is that we cannot link to atk-bridge on X11, as that will link to the out of tree ATK; this means that accessibility is disabled on X11.
1445 lines
43 KiB
C
1445 lines
43 KiB
C
/* ATK - The Accessibility Toolkit for GTK+
|
|
* Copyright 2001 Sun Microsystems Inc.
|
|
*
|
|
* This library is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Library 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
|
|
* Library General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Library 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.
|
|
*/
|
|
|
|
#include "config.h"
|
|
|
|
#include "atk.h"
|
|
#include "atkmarshal.h"
|
|
|
|
#include <string.h>
|
|
|
|
/**
|
|
* SECTION:atktext
|
|
* @Short_description: The ATK interface implemented by components
|
|
* with text content.
|
|
* @Title:AtkText
|
|
*
|
|
* #AtkText should be implemented by #AtkObjects on behalf of widgets
|
|
* that have text content which is either attributed or otherwise
|
|
* non-trivial. #AtkObjects whose text content is simple,
|
|
* unattributed, and very brief may expose that content via
|
|
* #atk_object_get_name instead; however if the text is editable,
|
|
* multi-line, typically longer than three or four words, attributed,
|
|
* selectable, or if the object already uses the 'name' ATK property
|
|
* for other information, the #AtkText interface should be used to
|
|
* expose the text content. In the case of editable text content,
|
|
* #AtkEditableText (a subtype of the #AtkText interface) should be
|
|
* implemented instead.
|
|
*
|
|
* #AtkText provides not only traversal facilities and change
|
|
* notification for text content, but also caret tracking and glyph
|
|
* bounding box calculations. Note that the text strings are exposed
|
|
* as UTF-8, and are therefore potentially multi-byte, and
|
|
* caret-to-byte offset mapping makes no assumptions about the
|
|
* character length; also bounding box glyph-to-offset mapping may be
|
|
* complex for languages which use ligatures.
|
|
*/
|
|
|
|
static GPtrArray *extra_attributes = NULL;
|
|
|
|
enum {
|
|
TEXT_CARET_MOVED,
|
|
TEXT_SELECTION_CHANGED,
|
|
TEXT_ATTRIBUTES_CHANGED,
|
|
TEXT_INSERT,
|
|
TEXT_REMOVE,
|
|
LAST_SIGNAL
|
|
};
|
|
|
|
static const char boolean[] =
|
|
"false\0"
|
|
"true";
|
|
static const guint8 boolean_offsets[] = {
|
|
0, 6
|
|
};
|
|
|
|
static const char style[] =
|
|
"normal\0"
|
|
"oblique\0"
|
|
"italic";
|
|
static const guint8 style_offsets[] = {
|
|
0, 7, 15
|
|
};
|
|
|
|
static const char variant[] =
|
|
"normal\0"
|
|
"small_caps";
|
|
static const guint8 variant_offsets[] = {
|
|
0, 7
|
|
};
|
|
|
|
static const char stretch[] =
|
|
"ultra_condensed\0"
|
|
"extra_condensed\0"
|
|
"condensed\0"
|
|
"semi_condensed\0"
|
|
"normal\0"
|
|
"semi_expanded\0"
|
|
"expanded\0"
|
|
"extra_expanded\0"
|
|
"ultra_expanded";
|
|
static const guint8 stretch_offsets[] = {
|
|
0, 16, 32, 42, 57, 64, 78, 87, 102
|
|
};
|
|
|
|
static const char justification[] =
|
|
"left\0"
|
|
"right\0"
|
|
"center\0"
|
|
"fill";
|
|
static const guint8 justification_offsets[] = {
|
|
0, 5, 11, 18
|
|
};
|
|
|
|
static const char direction[] =
|
|
"none\0"
|
|
"ltr\0"
|
|
"rtl";
|
|
static const guint8 direction_offsets[] = {
|
|
0, 5, 9
|
|
};
|
|
|
|
static const char wrap_mode[] =
|
|
"none\0"
|
|
"char\0"
|
|
"word\0"
|
|
"word_char";
|
|
static const guint8 wrap_mode_offsets[] = {
|
|
0, 5, 10, 15
|
|
};
|
|
|
|
static const char underline[] =
|
|
"none\0"
|
|
"single\0"
|
|
"double\0"
|
|
"low\0"
|
|
"error";
|
|
static const guint8 underline_offsets[] = {
|
|
0, 5, 12, 19, 23
|
|
};
|
|
|
|
static const char text_position[] =
|
|
"baseline\0"
|
|
"super\0"
|
|
"sub\0";
|
|
static const guint8 text_position_offsets[] = {
|
|
0, 9, 15,
|
|
};
|
|
|
|
static void atk_text_base_init (AtkTextIface *class);
|
|
|
|
static void atk_text_real_get_range_extents (AtkText *text,
|
|
gint start_offset,
|
|
gint end_offset,
|
|
AtkCoordType coord_type,
|
|
AtkTextRectangle *rect);
|
|
|
|
static AtkTextRange** atk_text_real_get_bounded_ranges (AtkText *text,
|
|
AtkTextRectangle *rect,
|
|
AtkCoordType coord_type,
|
|
AtkTextClipType x_clip_type,
|
|
AtkTextClipType y_clip_type);
|
|
|
|
static guint atk_text_signals[LAST_SIGNAL] = { 0 };
|
|
|
|
GType
|
|
atk_text_get_type (void)
|
|
{
|
|
static GType type = 0;
|
|
|
|
if (!type)
|
|
{
|
|
static const GTypeInfo tinfo =
|
|
{
|
|
sizeof (AtkTextIface),
|
|
(GBaseInitFunc) atk_text_base_init,
|
|
(GBaseFinalizeFunc) NULL,
|
|
(GClassInitFunc) NULL /* atk_text_interface_init */ ,
|
|
(GClassFinalizeFunc) NULL,
|
|
|
|
};
|
|
|
|
type = g_type_register_static (G_TYPE_INTERFACE, "AtkText", &tinfo, 0);
|
|
}
|
|
|
|
return type;
|
|
}
|
|
|
|
static void
|
|
atk_text_base_init (AtkTextIface *class)
|
|
{
|
|
static gboolean initialized = FALSE;
|
|
|
|
if (! initialized)
|
|
{
|
|
/*
|
|
* Note that text_changed signal supports details "insert", "delete",
|
|
* possibly "replace".
|
|
*/
|
|
|
|
class->get_range_extents = atk_text_real_get_range_extents;
|
|
class->get_bounded_ranges = atk_text_real_get_bounded_ranges;
|
|
|
|
/**
|
|
* AtkText::text-insert:
|
|
* @atktext: the object which received the signal.
|
|
* @arg1: The position (character offset) of the insertion.
|
|
* @arg2: The length (in characters) of text inserted.
|
|
* @arg3: The new text inserted
|
|
*
|
|
* The "text-insert" signal is emitted when a new text is
|
|
* inserted. If the signal was not triggered by the user
|
|
* (e.g. typing or pasting text), the "system" detail should be
|
|
* included.
|
|
*/
|
|
atk_text_signals[TEXT_INSERT] =
|
|
g_signal_new ("text_insert",
|
|
ATK_TYPE_TEXT,
|
|
G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED,
|
|
0,
|
|
(GSignalAccumulator) NULL, NULL,
|
|
atk_marshal_VOID__INT_INT_STRING,
|
|
G_TYPE_NONE,
|
|
3, G_TYPE_INT, G_TYPE_INT, G_TYPE_STRING);
|
|
|
|
/**
|
|
* AtkText::text-remove:
|
|
* @atktext: the object which received the signal.
|
|
* @arg1: The position (character offset) of the removal.
|
|
* @arg2: The length (in characters) of text removed.
|
|
* @arg3: The old text removed
|
|
*
|
|
* The "text-remove" signal is emitted when a new text is
|
|
* removed. If the signal was not triggered by the user
|
|
* (e.g. typing or pasting text), the "system" detail should be
|
|
* included.
|
|
*/
|
|
atk_text_signals[TEXT_REMOVE] =
|
|
g_signal_new ("text_remove",
|
|
ATK_TYPE_TEXT,
|
|
G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED,
|
|
0,
|
|
(GSignalAccumulator) NULL, NULL,
|
|
atk_marshal_VOID__INT_INT_STRING,
|
|
G_TYPE_NONE,
|
|
3, G_TYPE_INT, G_TYPE_INT, G_TYPE_STRING);
|
|
|
|
/**
|
|
* AtkText::text-caret-moved:
|
|
* @atktext: the object which received the signal.
|
|
* @arg1: The new position of the text caret.
|
|
*
|
|
* The "text-caret-moved" signal is emitted when the caret
|
|
* position of the text of an object which implements AtkText
|
|
* changes.
|
|
*/
|
|
atk_text_signals[TEXT_CARET_MOVED] =
|
|
g_signal_new ("text_caret_moved",
|
|
ATK_TYPE_TEXT,
|
|
G_SIGNAL_RUN_LAST,
|
|
G_STRUCT_OFFSET (AtkTextIface, text_caret_moved),
|
|
(GSignalAccumulator) NULL, NULL,
|
|
g_cclosure_marshal_VOID__INT,
|
|
G_TYPE_NONE,
|
|
1, G_TYPE_INT);
|
|
|
|
/**
|
|
* AtkText::text-selection-changed:
|
|
* @atktext: the object which received the signal.
|
|
*
|
|
* The "text-selection-changed" signal is emitted when the
|
|
* selected text of an object which implements AtkText changes.
|
|
*/
|
|
atk_text_signals[TEXT_SELECTION_CHANGED] =
|
|
g_signal_new ("text_selection_changed",
|
|
ATK_TYPE_TEXT,
|
|
G_SIGNAL_RUN_LAST,
|
|
G_STRUCT_OFFSET (AtkTextIface, text_selection_changed),
|
|
(GSignalAccumulator) NULL, NULL,
|
|
g_cclosure_marshal_VOID__VOID,
|
|
G_TYPE_NONE, 0);
|
|
/**
|
|
* AtkText::text-attributes-changed:
|
|
* @atktext: the object which received the signal.
|
|
*
|
|
* The "text-attributes-changed" signal is emitted when the text
|
|
* attributes of the text of an object which implements AtkText
|
|
* changes.
|
|
*/
|
|
atk_text_signals[TEXT_ATTRIBUTES_CHANGED] =
|
|
g_signal_new ("text_attributes_changed",
|
|
ATK_TYPE_TEXT,
|
|
G_SIGNAL_RUN_LAST,
|
|
G_STRUCT_OFFSET (AtkTextIface, text_attributes_changed),
|
|
(GSignalAccumulator) NULL, NULL,
|
|
g_cclosure_marshal_VOID__VOID,
|
|
G_TYPE_NONE, 0);
|
|
|
|
|
|
initialized = TRUE;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* atk_text_get_text:
|
|
* @text: an #AtkText
|
|
* @start_offset: a starting character offset within @text
|
|
* @end_offset: an ending character offset within @text, or -1 for the end of the string.
|
|
*
|
|
* Gets the specified text.
|
|
*
|
|
* Returns: a newly allocated string containing the text from @start_offset up
|
|
* to, but not including @end_offset. Use g_free() to free the returned
|
|
* string.
|
|
**/
|
|
gchar*
|
|
atk_text_get_text (AtkText *text,
|
|
gint start_offset,
|
|
gint end_offset)
|
|
{
|
|
AtkTextIface *iface;
|
|
|
|
g_return_val_if_fail (ATK_IS_TEXT (text), NULL);
|
|
|
|
iface = ATK_TEXT_GET_IFACE (text);
|
|
|
|
if (start_offset < 0 || end_offset < -1 ||
|
|
(end_offset != -1 && end_offset < start_offset))
|
|
return NULL;
|
|
|
|
if (iface->get_text)
|
|
return (*(iface->get_text)) (text, start_offset, end_offset);
|
|
else
|
|
return NULL;
|
|
}
|
|
|
|
/**
|
|
* atk_text_get_character_at_offset:
|
|
* @text: an #AtkText
|
|
* @offset: a character offset within @text
|
|
*
|
|
* Gets the specified text.
|
|
*
|
|
* Returns: the character at @offset or 0 in the case of failure.
|
|
**/
|
|
gunichar
|
|
atk_text_get_character_at_offset (AtkText *text,
|
|
gint offset)
|
|
{
|
|
AtkTextIface *iface;
|
|
|
|
g_return_val_if_fail (ATK_IS_TEXT (text), (gunichar) 0);
|
|
|
|
iface = ATK_TEXT_GET_IFACE (text);
|
|
|
|
if (iface->get_character_at_offset)
|
|
return (*(iface->get_character_at_offset)) (text, offset);
|
|
else
|
|
return (gunichar) 0;
|
|
}
|
|
|
|
/**
|
|
* atk_text_get_string_at_offset:
|
|
* @text: an #AtkText
|
|
* @offset: position
|
|
* @granularity: An #AtkTextGranularity
|
|
* @start_offset: (out): the starting character offset of the returned string, or -1
|
|
* in the case of error (e.g. invalid offset, not implemented)
|
|
* @end_offset: (out): the offset of the first character after the returned string,
|
|
* or -1 in the case of error (e.g. invalid offset, not implemented)
|
|
*
|
|
* Gets a portion of the text exposed through an #AtkText according to a given @offset
|
|
* and a specific @granularity, along with the start and end offsets defining the
|
|
* boundaries of such a portion of text.
|
|
*
|
|
* If @granularity is ATK_TEXT_GRANULARITY_CHAR the character at the
|
|
* offset is returned.
|
|
*
|
|
* If @granularity is ATK_TEXT_GRANULARITY_WORD the returned string
|
|
* is from the word start at or before the offset to the word start after
|
|
* the offset.
|
|
*
|
|
* The returned string will contain the word at the offset if the offset
|
|
* is inside a word and will contain the word before the offset if the
|
|
* offset is not inside a word.
|
|
*
|
|
* If @granularity is ATK_TEXT_GRANULARITY_SENTENCE the returned string
|
|
* is from the sentence start at or before the offset to the sentence
|
|
* start after the offset.
|
|
*
|
|
* The returned string will contain the sentence at the offset if the offset
|
|
* is inside a sentence and will contain the sentence before the offset
|
|
* if the offset is not inside a sentence.
|
|
*
|
|
* If @granularity is ATK_TEXT_GRANULARITY_LINE the returned string
|
|
* is from the line start at or before the offset to the line
|
|
* start after the offset.
|
|
*
|
|
* If @granularity is ATK_TEXT_GRANULARITY_PARAGRAPH the returned string
|
|
* is from the start of the paragraph at or before the offset to the start
|
|
* of the following paragraph after the offset.
|
|
*
|
|
* Since: 2.10
|
|
*
|
|
* Returns: (nullable): a newly allocated string containing the text at
|
|
* the @offset bounded by the specified @granularity. Use g_free()
|
|
* to free the returned string. Returns %NULL if the offset is invalid
|
|
* or no implementation is available.
|
|
**/
|
|
gchar* atk_text_get_string_at_offset (AtkText *text,
|
|
gint offset,
|
|
AtkTextGranularity granularity,
|
|
gint *start_offset,
|
|
gint *end_offset)
|
|
{
|
|
AtkTextIface *iface;
|
|
gint local_start_offset, local_end_offset;
|
|
gint *real_start_offset, *real_end_offset;
|
|
|
|
g_return_val_if_fail (ATK_IS_TEXT (text), NULL);
|
|
|
|
if (start_offset)
|
|
{
|
|
*start_offset = -1;
|
|
real_start_offset = start_offset;
|
|
}
|
|
else
|
|
real_start_offset = &local_start_offset;
|
|
|
|
if (end_offset)
|
|
{
|
|
*end_offset = -1;
|
|
real_end_offset = end_offset;
|
|
}
|
|
else
|
|
real_end_offset = &local_end_offset;
|
|
|
|
if (offset < 0)
|
|
return NULL;
|
|
|
|
iface = ATK_TEXT_GET_IFACE (text);
|
|
|
|
if (iface->get_string_at_offset)
|
|
return (*(iface->get_string_at_offset)) (text, offset, granularity, real_start_offset, real_end_offset);
|
|
else
|
|
return NULL;
|
|
}
|
|
|
|
/**
|
|
* atk_text_get_caret_offset:
|
|
* @text: an #AtkText
|
|
*
|
|
* Gets the offset of the position of the caret (cursor).
|
|
*
|
|
* Returns: the character offset of the position of the caret or -1 if
|
|
* the caret is not located inside the element or in the case of
|
|
* any other failure.
|
|
**/
|
|
gint
|
|
atk_text_get_caret_offset (AtkText *text)
|
|
{
|
|
AtkTextIface *iface;
|
|
|
|
g_return_val_if_fail (ATK_IS_TEXT (text), 0);
|
|
|
|
iface = ATK_TEXT_GET_IFACE (text);
|
|
|
|
if (iface->get_caret_offset)
|
|
return (*(iface->get_caret_offset)) (text);
|
|
else
|
|
return -1;
|
|
}
|
|
|
|
/**
|
|
* atk_text_get_character_extents:
|
|
* @text: an #AtkText
|
|
* @offset: The offset of the text character for which bounding information is required.
|
|
* @x: (out) (optional): Pointer for the x coordinate of the bounding box
|
|
* @y: (out) (optional): Pointer for the y coordinate of the bounding box
|
|
* @width: (out) (optional): Pointer for the width of the bounding box
|
|
* @height: (out) (optional): Pointer for the height of the bounding box
|
|
* @coords: specify whether coordinates are relative to the screen or widget window
|
|
*
|
|
* If the extent can not be obtained (e.g. missing support), all of x, y, width,
|
|
* height are set to -1.
|
|
*
|
|
* Get the bounding box containing the glyph representing the character at
|
|
* a particular text offset.
|
|
**/
|
|
void
|
|
atk_text_get_character_extents (AtkText *text,
|
|
gint offset,
|
|
gint *x,
|
|
gint *y,
|
|
gint *width,
|
|
gint *height,
|
|
AtkCoordType coords)
|
|
{
|
|
AtkTextIface *iface;
|
|
gint local_x, local_y, local_width, local_height;
|
|
gint *real_x, *real_y, *real_width, *real_height;
|
|
|
|
g_return_if_fail (ATK_IS_TEXT (text));
|
|
|
|
if (x)
|
|
real_x = x;
|
|
else
|
|
real_x = &local_x;
|
|
if (y)
|
|
real_y = y;
|
|
else
|
|
real_y = &local_y;
|
|
if (width)
|
|
real_width = width;
|
|
else
|
|
real_width = &local_width;
|
|
if (height)
|
|
real_height = height;
|
|
else
|
|
real_height = &local_height;
|
|
|
|
*real_x = -1;
|
|
*real_y = -1;
|
|
*real_width = -1;
|
|
*real_height = -1;
|
|
|
|
if (offset < 0)
|
|
return;
|
|
|
|
iface = ATK_TEXT_GET_IFACE (text);
|
|
|
|
if (iface->get_character_extents)
|
|
(*(iface->get_character_extents)) (text, offset, real_x, real_y, real_width, real_height, coords);
|
|
|
|
if (*real_width <0)
|
|
{
|
|
*real_x = *real_x + *real_width;
|
|
*real_width *= -1;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* atk_text_get_run_attributes:
|
|
*@text: an #AtkText
|
|
*@offset: the character offset at which to get the attributes, -1 means the offset of
|
|
*the character to be inserted at the caret location.
|
|
*@start_offset: (out): the address to put the start offset of the range
|
|
*@end_offset: (out): the address to put the end offset of the range
|
|
*
|
|
*Creates an #AtkAttributeSet which consists of the attributes explicitly
|
|
*set at the position @offset in the text. @start_offset and @end_offset are
|
|
*set to the start and end of the range around @offset where the attributes are
|
|
*invariant. Note that @end_offset is the offset of the first character
|
|
*after the range. See the enum AtkTextAttribute for types of text
|
|
*attributes that can be returned. Note that other attributes may also be
|
|
*returned.
|
|
*
|
|
*Returns: (transfer full): an #AtkAttributeSet which contains the attributes
|
|
* explicitly set at @offset. This #AtkAttributeSet should be freed by
|
|
* a call to atk_attribute_set_free().
|
|
**/
|
|
AtkAttributeSet*
|
|
atk_text_get_run_attributes (AtkText *text,
|
|
gint offset,
|
|
gint *start_offset,
|
|
gint *end_offset)
|
|
{
|
|
AtkTextIface *iface;
|
|
gint local_start_offset, local_end_offset;
|
|
gint *real_start_offset, *real_end_offset;
|
|
|
|
g_return_val_if_fail (ATK_IS_TEXT (text), NULL);
|
|
|
|
if (start_offset)
|
|
real_start_offset = start_offset;
|
|
else
|
|
real_start_offset = &local_start_offset;
|
|
if (end_offset)
|
|
real_end_offset = end_offset;
|
|
else
|
|
real_end_offset = &local_end_offset;
|
|
|
|
if (offset < -1)
|
|
return NULL;
|
|
|
|
iface = ATK_TEXT_GET_IFACE (text);
|
|
|
|
if (iface->get_run_attributes)
|
|
return (*(iface->get_run_attributes)) (text, offset, real_start_offset, real_end_offset);
|
|
else
|
|
return NULL;
|
|
}
|
|
|
|
/**
|
|
* atk_text_get_default_attributes:
|
|
*@text: an #AtkText
|
|
*
|
|
*Creates an #AtkAttributeSet which consists of the default values of
|
|
*attributes for the text. See the enum AtkTextAttribute for types of text
|
|
*attributes that can be returned. Note that other attributes may also be
|
|
*returned.
|
|
*
|
|
*Returns: (transfer full): an #AtkAttributeSet which contains the default text
|
|
* attributes for this #AtkText. This #AtkAttributeSet should be freed by
|
|
* a call to atk_attribute_set_free().
|
|
*/
|
|
AtkAttributeSet*
|
|
atk_text_get_default_attributes (AtkText *text)
|
|
{
|
|
AtkTextIface *iface;
|
|
|
|
g_return_val_if_fail (ATK_IS_TEXT (text), NULL);
|
|
|
|
iface = ATK_TEXT_GET_IFACE (text);
|
|
|
|
if (iface->get_default_attributes)
|
|
return (*(iface->get_default_attributes)) (text);
|
|
else
|
|
return NULL;
|
|
}
|
|
|
|
/**
|
|
* atk_text_get_character_count:
|
|
* @text: an #AtkText
|
|
*
|
|
* Gets the character count.
|
|
*
|
|
* Returns: the number of characters or -1 in case of failure.
|
|
**/
|
|
gint
|
|
atk_text_get_character_count (AtkText *text)
|
|
{
|
|
AtkTextIface *iface;
|
|
|
|
g_return_val_if_fail (ATK_IS_TEXT (text), -1);
|
|
|
|
iface = ATK_TEXT_GET_IFACE (text);
|
|
|
|
if (iface->get_character_count)
|
|
return (*(iface->get_character_count)) (text);
|
|
else
|
|
return -1;
|
|
}
|
|
|
|
/**
|
|
* atk_text_get_offset_at_point:
|
|
* @text: an #AtkText
|
|
* @x: screen x-position of character
|
|
* @y: screen y-position of character
|
|
* @coords: specify whether coordinates are relative to the screen or
|
|
* widget window
|
|
*
|
|
* Gets the offset of the character located at coordinates @x and @y. @x and @y
|
|
* are interpreted as being relative to the screen or this widget's window
|
|
* depending on @coords.
|
|
*
|
|
* Returns: the offset to the character which is located at the specified
|
|
* @x and @y coordinates of -1 in case of failure.
|
|
**/
|
|
gint
|
|
atk_text_get_offset_at_point (AtkText *text,
|
|
gint x,
|
|
gint y,
|
|
AtkCoordType coords)
|
|
{
|
|
AtkTextIface *iface;
|
|
|
|
g_return_val_if_fail (ATK_IS_TEXT (text), -1);
|
|
|
|
iface = ATK_TEXT_GET_IFACE (text);
|
|
|
|
if (iface->get_offset_at_point)
|
|
return (*(iface->get_offset_at_point)) (text, x, y, coords);
|
|
else
|
|
return -1;
|
|
}
|
|
|
|
/**
|
|
* atk_text_get_n_selections:
|
|
* @text: an #AtkText
|
|
*
|
|
* Gets the number of selected regions.
|
|
*
|
|
* Returns: The number of selected regions, or -1 in the case of failure.
|
|
**/
|
|
gint
|
|
atk_text_get_n_selections (AtkText *text)
|
|
{
|
|
AtkTextIface *iface;
|
|
|
|
g_return_val_if_fail (ATK_IS_TEXT (text), -1);
|
|
|
|
iface = ATK_TEXT_GET_IFACE (text);
|
|
|
|
if (iface->get_n_selections)
|
|
return (*(iface->get_n_selections)) (text);
|
|
else
|
|
return -1;
|
|
}
|
|
|
|
/**
|
|
* atk_text_get_selection:
|
|
* @text: an #AtkText
|
|
* @selection_num: The selection number. The selected regions are
|
|
* assigned numbers that correspond to how far the region is from the
|
|
* start of the text. The selected region closest to the beginning
|
|
* of the text region is assigned the number 0, etc. Note that adding,
|
|
* moving or deleting a selected region can change the numbering.
|
|
* @start_offset: (out): passes back the starting character offset of the selected region
|
|
* @end_offset: (out): passes back the ending character offset (offset immediately past)
|
|
* of the selected region
|
|
*
|
|
* Gets the text from the specified selection.
|
|
*
|
|
* Returns: a newly allocated string containing the selected text. Use g_free()
|
|
* to free the returned string.
|
|
**/
|
|
gchar*
|
|
atk_text_get_selection (AtkText *text,
|
|
gint selection_num,
|
|
gint *start_offset,
|
|
gint *end_offset)
|
|
{
|
|
AtkTextIface *iface;
|
|
gint local_start_offset, local_end_offset;
|
|
gint *real_start_offset, *real_end_offset;
|
|
|
|
g_return_val_if_fail (ATK_IS_TEXT (text), NULL);
|
|
|
|
if (start_offset)
|
|
real_start_offset = start_offset;
|
|
else
|
|
real_start_offset = &local_start_offset;
|
|
if (end_offset)
|
|
real_end_offset = end_offset;
|
|
else
|
|
real_end_offset = &local_end_offset;
|
|
|
|
iface = ATK_TEXT_GET_IFACE (text);
|
|
|
|
if (iface->get_selection)
|
|
{
|
|
return (*(iface->get_selection)) (text, selection_num,
|
|
real_start_offset, real_end_offset);
|
|
}
|
|
else
|
|
return NULL;
|
|
}
|
|
|
|
/**
|
|
* atk_text_add_selection:
|
|
* @text: an #AtkText
|
|
* @start_offset: the starting character offset of the selected region
|
|
* @end_offset: the offset of the first character after the selected region.
|
|
*
|
|
* Adds a selection bounded by the specified offsets.
|
|
*
|
|
* Returns: %TRUE if successful, %FALSE otherwise
|
|
**/
|
|
gboolean
|
|
atk_text_add_selection (AtkText *text,
|
|
gint start_offset,
|
|
gint end_offset)
|
|
{
|
|
AtkTextIface *iface;
|
|
|
|
g_return_val_if_fail (ATK_IS_TEXT (text), FALSE);
|
|
|
|
iface = ATK_TEXT_GET_IFACE (text);
|
|
|
|
if (iface->add_selection)
|
|
return (*(iface->add_selection)) (text, start_offset, end_offset);
|
|
else
|
|
return FALSE;
|
|
}
|
|
|
|
/**
|
|
* atk_text_remove_selection:
|
|
* @text: an #AtkText
|
|
* @selection_num: The selection number. The selected regions are
|
|
* assigned numbers that correspond to how far the region is from the
|
|
* start of the text. The selected region closest to the beginning
|
|
* of the text region is assigned the number 0, etc. Note that adding,
|
|
* moving or deleting a selected region can change the numbering.
|
|
*
|
|
* Removes the specified selection.
|
|
*
|
|
* Returns: %TRUE if successful, %FALSE otherwise
|
|
**/
|
|
gboolean
|
|
atk_text_remove_selection (AtkText *text,
|
|
gint selection_num)
|
|
{
|
|
AtkTextIface *iface;
|
|
|
|
g_return_val_if_fail (ATK_IS_TEXT (text), FALSE);
|
|
|
|
iface = ATK_TEXT_GET_IFACE (text);
|
|
|
|
if (iface->remove_selection)
|
|
return (*(iface->remove_selection)) (text, selection_num);
|
|
else
|
|
return FALSE;
|
|
}
|
|
|
|
/**
|
|
* atk_text_set_selection:
|
|
* @text: an #AtkText
|
|
* @selection_num: The selection number. The selected regions are
|
|
* assigned numbers that correspond to how far the region is from the
|
|
* start of the text. The selected region closest to the beginning
|
|
* of the text region is assigned the number 0, etc. Note that adding,
|
|
* moving or deleting a selected region can change the numbering.
|
|
* @start_offset: the new starting character offset of the selection
|
|
* @end_offset: the new end position of (e.g. offset immediately past)
|
|
* the selection
|
|
*
|
|
* Changes the start and end offset of the specified selection.
|
|
*
|
|
* Returns: %TRUE if successful, %FALSE otherwise
|
|
**/
|
|
gboolean
|
|
atk_text_set_selection (AtkText *text,
|
|
gint selection_num,
|
|
gint start_offset,
|
|
gint end_offset)
|
|
{
|
|
AtkTextIface *iface;
|
|
|
|
g_return_val_if_fail (ATK_IS_TEXT (text), FALSE);
|
|
|
|
iface = ATK_TEXT_GET_IFACE (text);
|
|
|
|
if (iface->set_selection)
|
|
{
|
|
return (*(iface->set_selection)) (text, selection_num,
|
|
start_offset, end_offset);
|
|
}
|
|
else
|
|
return FALSE;
|
|
}
|
|
|
|
/**
|
|
* atk_text_set_caret_offset:
|
|
* @text: an #AtkText
|
|
* @offset: the character offset of the new caret position
|
|
*
|
|
* Sets the caret (cursor) position to the specified @offset.
|
|
*
|
|
* In the case of rich-text content, this method should either grab focus
|
|
* or move the sequential focus navigation starting point (if the application
|
|
* supports this concept) as if the user had clicked on the new caret position.
|
|
* Typically, this means that the target of this operation is the node containing
|
|
* the new caret position or one of its ancestors. In other words, after this
|
|
* method is called, if the user advances focus, it should move to the first
|
|
* focusable node following the new caret position.
|
|
*
|
|
* Calling this method should also scroll the application viewport in a way
|
|
* that matches the behavior of the application's typical caret motion or tab
|
|
* navigation as closely as possible. This also means that if the application's
|
|
* caret motion or focus navigation does not trigger a scroll operation, this
|
|
* method should not trigger one either. If the application does not have a caret
|
|
* motion or focus navigation operation, this method should try to scroll the new
|
|
* caret position into view while minimizing unnecessary scroll motion.
|
|
*
|
|
* Returns: %TRUE if successful, %FALSE otherwise.
|
|
**/
|
|
gboolean
|
|
atk_text_set_caret_offset (AtkText *text,
|
|
gint offset)
|
|
{
|
|
AtkTextIface *iface;
|
|
|
|
g_return_val_if_fail (ATK_IS_TEXT (text), FALSE);
|
|
|
|
iface = ATK_TEXT_GET_IFACE (text);
|
|
|
|
if (iface->set_caret_offset)
|
|
{
|
|
return (*(iface->set_caret_offset)) (text, offset);
|
|
}
|
|
else
|
|
{
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* atk_text_get_range_extents:
|
|
* @text: an #AtkText
|
|
* @start_offset: The offset of the first text character for which boundary
|
|
* information is required.
|
|
* @end_offset: The offset of the text character after the last character
|
|
* for which boundary information is required.
|
|
* @coord_type: Specify whether coordinates are relative to the screen or widget window.
|
|
* @rect: (out): A pointer to a AtkTextRectangle which is filled in by this function.
|
|
*
|
|
* Get the bounding box for text within the specified range.
|
|
*
|
|
* If the extents can not be obtained (e.g. or missing support), the rectangle
|
|
* fields are set to -1.
|
|
*
|
|
* Since: 1.3
|
|
**/
|
|
void
|
|
atk_text_get_range_extents (AtkText *text,
|
|
gint start_offset,
|
|
gint end_offset,
|
|
AtkCoordType coord_type,
|
|
AtkTextRectangle *rect)
|
|
{
|
|
AtkTextIface *iface;
|
|
|
|
g_return_if_fail (ATK_IS_TEXT (text));
|
|
g_return_if_fail (rect);
|
|
g_return_if_fail (start_offset >= 0 && start_offset < end_offset);
|
|
|
|
iface = ATK_TEXT_GET_IFACE (text);
|
|
|
|
if (iface->get_range_extents)
|
|
(*(iface->get_range_extents)) (text, start_offset, end_offset, coord_type, rect);
|
|
else
|
|
{
|
|
rect->x = -1;
|
|
rect->y = -1;
|
|
rect->width = -1;
|
|
rect->height = -1;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* atk_text_get_bounded_ranges: (virtual get_bounded_ranges)
|
|
* @text: an #AtkText
|
|
* @rect: An AtkTextRectangle giving the dimensions of the bounding box.
|
|
* @coord_type: Specify whether coordinates are relative to the screen or widget window.
|
|
* @x_clip_type: Specify the horizontal clip type.
|
|
* @y_clip_type: Specify the vertical clip type.
|
|
*
|
|
* Get the ranges of text in the specified bounding box.
|
|
*
|
|
* Since: 1.3
|
|
*
|
|
* Returns: (array zero-terminated=1): Array of AtkTextRange. The last
|
|
* element of the array returned by this function will be NULL.
|
|
**/
|
|
AtkTextRange**
|
|
atk_text_get_bounded_ranges (AtkText *text,
|
|
AtkTextRectangle *rect,
|
|
AtkCoordType coord_type,
|
|
AtkTextClipType x_clip_type,
|
|
AtkTextClipType y_clip_type)
|
|
{
|
|
AtkTextIface *iface;
|
|
|
|
g_return_val_if_fail (ATK_IS_TEXT (text), NULL);
|
|
g_return_val_if_fail (rect, NULL);
|
|
|
|
iface = ATK_TEXT_GET_IFACE (text);
|
|
|
|
if (iface->get_bounded_ranges)
|
|
return (*(iface->get_bounded_ranges)) (text, rect, coord_type, x_clip_type, y_clip_type);
|
|
else
|
|
return NULL;
|
|
}
|
|
|
|
/**
|
|
* atk_attribute_set_free:
|
|
* @attrib_set: The #AtkAttributeSet to free
|
|
*
|
|
* Frees the memory used by an #AtkAttributeSet, including all its
|
|
* #AtkAttributes.
|
|
**/
|
|
void
|
|
atk_attribute_set_free (AtkAttributeSet *attrib_set)
|
|
{
|
|
GSList *temp;
|
|
|
|
temp = attrib_set;
|
|
|
|
while (temp != NULL)
|
|
{
|
|
AtkAttribute *att;
|
|
|
|
att = temp->data;
|
|
|
|
g_free (att->name);
|
|
g_free (att->value);
|
|
g_free (att);
|
|
temp = temp->next;
|
|
}
|
|
g_slist_free (attrib_set);
|
|
}
|
|
|
|
/**
|
|
* atk_text_attribute_register:
|
|
* @name: a name string
|
|
*
|
|
* Associate @name with a new #AtkTextAttribute
|
|
*
|
|
* Returns: an #AtkTextAttribute associated with @name
|
|
**/
|
|
AtkTextAttribute
|
|
atk_text_attribute_register (const gchar *name)
|
|
{
|
|
g_return_val_if_fail (name, ATK_TEXT_ATTR_INVALID);
|
|
|
|
if (!extra_attributes)
|
|
extra_attributes = g_ptr_array_new ();
|
|
|
|
g_ptr_array_add (extra_attributes, g_strdup (name));
|
|
return extra_attributes->len + ATK_TEXT_ATTR_LAST_DEFINED;
|
|
}
|
|
|
|
/**
|
|
* atk_text_attribute_get_name:
|
|
* @attr: The #AtkTextAttribute whose name is required
|
|
*
|
|
* Gets the name corresponding to the #AtkTextAttribute
|
|
*
|
|
* Returns: a string containing the name; this string should not be freed
|
|
**/
|
|
const gchar*
|
|
atk_text_attribute_get_name (AtkTextAttribute attr)
|
|
{
|
|
GTypeClass *type_class;
|
|
GEnumValue *value;
|
|
const gchar *name = NULL;
|
|
|
|
type_class = g_type_class_ref (ATK_TYPE_TEXT_ATTRIBUTE);
|
|
g_return_val_if_fail (G_IS_ENUM_CLASS (type_class), NULL);
|
|
|
|
value = g_enum_get_value (G_ENUM_CLASS (type_class), attr);
|
|
|
|
if (value)
|
|
{
|
|
name = value->value_nick;
|
|
}
|
|
else
|
|
{
|
|
if (extra_attributes)
|
|
{
|
|
gint n = attr;
|
|
|
|
n -= ATK_TEXT_ATTR_LAST_DEFINED + 1;
|
|
|
|
if (n < extra_attributes->len)
|
|
|
|
name = g_ptr_array_index (extra_attributes, n);
|
|
}
|
|
}
|
|
g_type_class_unref (type_class);
|
|
return name;
|
|
}
|
|
|
|
/**
|
|
* atk_text_attribute_for_name:
|
|
* @name: a string which is the (non-localized) name of an ATK text attribute.
|
|
*
|
|
* Get the #AtkTextAttribute type corresponding to a text attribute name.
|
|
*
|
|
* Returns: the #AtkTextAttribute enumerated type corresponding to the specified
|
|
* name, or #ATK_TEXT_ATTRIBUTE_INVALID if no matching text attribute
|
|
* is found.
|
|
**/
|
|
AtkTextAttribute
|
|
atk_text_attribute_for_name (const gchar *name)
|
|
{
|
|
GTypeClass *type_class;
|
|
GEnumValue *value;
|
|
AtkTextAttribute type = ATK_TEXT_ATTR_INVALID;
|
|
|
|
g_return_val_if_fail (name, ATK_TEXT_ATTR_INVALID);
|
|
|
|
type_class = g_type_class_ref (ATK_TYPE_TEXT_ATTRIBUTE);
|
|
g_return_val_if_fail (G_IS_ENUM_CLASS (type_class), ATK_TEXT_ATTR_INVALID);
|
|
|
|
value = g_enum_get_value_by_nick (G_ENUM_CLASS (type_class), name);
|
|
|
|
if (value)
|
|
{
|
|
type = value->value;
|
|
}
|
|
else
|
|
{
|
|
gint i;
|
|
|
|
if (extra_attributes)
|
|
{
|
|
for (i = 0; i < extra_attributes->len; i++)
|
|
{
|
|
gchar *extra_attribute = (gchar *)g_ptr_array_index (extra_attributes, i);
|
|
|
|
g_return_val_if_fail (extra_attribute, ATK_TEXT_ATTR_INVALID);
|
|
|
|
if (strcmp (name, extra_attribute) == 0)
|
|
{
|
|
type = i + 1 + ATK_TEXT_ATTR_LAST_DEFINED;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
g_type_class_unref (type_class);
|
|
|
|
return type;
|
|
}
|
|
|
|
/**
|
|
* atk_text_attribute_get_value:
|
|
* @attr: The #AtkTextAttribute for which a value is required
|
|
* @index_: The index of the required value
|
|
*
|
|
* Gets the value for the index of the #AtkTextAttribute
|
|
*
|
|
* Returns: (nullable): a string containing the value; this string
|
|
* should not be freed; %NULL is returned if there are no values
|
|
* maintained for the attr value.
|
|
**/
|
|
const gchar*
|
|
atk_text_attribute_get_value (AtkTextAttribute attr,
|
|
gint index)
|
|
{
|
|
switch (attr)
|
|
{
|
|
case ATK_TEXT_ATTR_INVISIBLE:
|
|
case ATK_TEXT_ATTR_EDITABLE:
|
|
case ATK_TEXT_ATTR_BG_FULL_HEIGHT:
|
|
case ATK_TEXT_ATTR_STRIKETHROUGH:
|
|
case ATK_TEXT_ATTR_BG_STIPPLE:
|
|
case ATK_TEXT_ATTR_FG_STIPPLE:
|
|
g_assert (index >= 0 && index < G_N_ELEMENTS (boolean_offsets));
|
|
return boolean + boolean_offsets[index];
|
|
case ATK_TEXT_ATTR_UNDERLINE:
|
|
g_assert (index >= 0 && index < G_N_ELEMENTS (underline_offsets));
|
|
return underline + underline_offsets[index];
|
|
case ATK_TEXT_ATTR_WRAP_MODE:
|
|
g_assert (index >= 0 && index < G_N_ELEMENTS (wrap_mode_offsets));
|
|
return wrap_mode + wrap_mode_offsets[index];
|
|
case ATK_TEXT_ATTR_DIRECTION:
|
|
g_assert (index >= 0 && index < G_N_ELEMENTS (direction_offsets));
|
|
return direction + direction_offsets[index];
|
|
case ATK_TEXT_ATTR_JUSTIFICATION:
|
|
g_assert (index >= 0 && index < G_N_ELEMENTS (justification_offsets));
|
|
return justification + justification_offsets[index];
|
|
case ATK_TEXT_ATTR_STRETCH:
|
|
g_assert (index >= 0 && index < G_N_ELEMENTS (stretch_offsets));
|
|
return stretch + stretch_offsets[index];
|
|
case ATK_TEXT_ATTR_VARIANT:
|
|
g_assert (index >= 0 && index < G_N_ELEMENTS (variant_offsets));
|
|
return variant + variant_offsets[index];
|
|
case ATK_TEXT_ATTR_STYLE:
|
|
g_assert (index >= 0 && index < G_N_ELEMENTS (style_offsets));
|
|
return style + style_offsets[index];
|
|
case ATK_TEXT_ATTR_TEXT_POSITION:
|
|
g_assert (index >= 0 && index < G_N_ELEMENTS (text_position_offsets));
|
|
return text_position + text_position_offsets[index];
|
|
default:
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
static void
|
|
atk_text_rectangle_union (AtkTextRectangle *src1,
|
|
AtkTextRectangle *src2,
|
|
AtkTextRectangle *dest)
|
|
{
|
|
gint dest_x, dest_y;
|
|
|
|
/*
|
|
* Some invocations of e.g. atk_text_get_character_extents
|
|
* may return "-1" rectangles for character positions without support for
|
|
* getting an extent. In that case we have to ignore them instead of using -1
|
|
* values in computations.
|
|
*/
|
|
if (src1->width == -1)
|
|
{
|
|
*dest = *src2;
|
|
return;
|
|
}
|
|
if (src2->width == -1)
|
|
{
|
|
*dest = *src1;
|
|
return;
|
|
}
|
|
|
|
dest_x = MIN (src1->x, src2->x);
|
|
dest_y = MIN (src1->y, src2->y);
|
|
dest->width = MAX (src1->x + src1->width, src2->x + src2->width) - dest_x;
|
|
dest->height = MAX (src1->y + src1->height, src2->y + src2->height) - dest_y;
|
|
dest->x = dest_x;
|
|
dest->y = dest_y;
|
|
}
|
|
|
|
static gboolean
|
|
atk_text_rectangle_contain (AtkTextRectangle *clip,
|
|
AtkTextRectangle *bounds,
|
|
AtkTextClipType x_clip_type,
|
|
AtkTextClipType y_clip_type)
|
|
{
|
|
gboolean x_min_ok, x_max_ok, y_min_ok, y_max_ok;
|
|
|
|
x_min_ok = (bounds->x >= clip->x) ||
|
|
((bounds->x + bounds->width >= clip->x) &&
|
|
((x_clip_type == ATK_TEXT_CLIP_NONE) ||
|
|
(x_clip_type == ATK_TEXT_CLIP_MAX)));
|
|
|
|
x_max_ok = (bounds->x + bounds->width <= clip->x + clip->width) ||
|
|
((bounds->x <= clip->x + clip->width) &&
|
|
((x_clip_type == ATK_TEXT_CLIP_NONE) ||
|
|
(x_clip_type == ATK_TEXT_CLIP_MIN)));
|
|
|
|
y_min_ok = (bounds->y >= clip->y) ||
|
|
((bounds->y + bounds->height >= clip->y) &&
|
|
((y_clip_type == ATK_TEXT_CLIP_NONE) ||
|
|
(y_clip_type == ATK_TEXT_CLIP_MAX)));
|
|
|
|
y_max_ok = (bounds->y + bounds->height <= clip->y + clip->height) ||
|
|
((bounds->y <= clip->y + clip->height) &&
|
|
((y_clip_type == ATK_TEXT_CLIP_NONE) ||
|
|
(y_clip_type == ATK_TEXT_CLIP_MIN)));
|
|
|
|
return (x_min_ok && x_max_ok && y_min_ok && y_max_ok);
|
|
|
|
}
|
|
|
|
/**
|
|
* atk_text_scroll_substring_to:
|
|
* @text: an #AtkText
|
|
* @start_offset: start offset in the @text
|
|
* @end_offset: end offset in the @text, or -1 for the end of the text.
|
|
* @type: specify where the object should be made visible.
|
|
*
|
|
* Makes a substring of @text visible on the screen by scrolling all necessary parents.
|
|
*
|
|
* Since: 2.32
|
|
*
|
|
* Returns: whether scrolling was successful.
|
|
*/
|
|
gboolean
|
|
atk_text_scroll_substring_to (AtkText *text,
|
|
gint start_offset,
|
|
gint end_offset,
|
|
AtkScrollType type)
|
|
{
|
|
AtkTextIface *iface = NULL;
|
|
g_return_val_if_fail (ATK_IS_TEXT (text), FALSE);
|
|
|
|
iface = ATK_TEXT_GET_IFACE (text);
|
|
|
|
if (iface->scroll_substring_to)
|
|
return (iface->scroll_substring_to) (text, start_offset, end_offset, type);
|
|
else
|
|
return FALSE;
|
|
}
|
|
|
|
/**
|
|
* atk_text_scroll_substring_to_point:
|
|
* @text: an #AtkText
|
|
* @start_offset: start offset in the @text
|
|
* @end_offset: end offset in the @text, or -1 for the end of the text.
|
|
* @coords: specify whether coordinates are relative to the screen or to the
|
|
* parent object.
|
|
* @x: x-position where to scroll to
|
|
* @y: y-position where to scroll to
|
|
*
|
|
* Move the top-left of a substring of @text to a given position of the screen
|
|
* by scrolling all necessary parents.
|
|
*
|
|
* Since: 2.32
|
|
*
|
|
* Returns: whether scrolling was successful.
|
|
*/
|
|
gboolean
|
|
atk_text_scroll_substring_to_point (AtkText *text,
|
|
gint start_offset,
|
|
gint end_offset,
|
|
AtkCoordType coords,
|
|
gint x,
|
|
gint y)
|
|
{
|
|
AtkTextIface *iface = NULL;
|
|
g_return_val_if_fail (ATK_IS_TEXT (text), FALSE);
|
|
|
|
iface = ATK_TEXT_GET_IFACE (text);
|
|
|
|
if (iface->scroll_substring_to_point)
|
|
return (iface->scroll_substring_to_point) (text, start_offset, end_offset, coords, x, y);
|
|
else
|
|
return FALSE;
|
|
}
|
|
|
|
static void
|
|
atk_text_real_get_range_extents (AtkText *text,
|
|
gint start_offset,
|
|
gint end_offset,
|
|
AtkCoordType coord_type,
|
|
AtkTextRectangle *rect)
|
|
{
|
|
gint i;
|
|
AtkTextRectangle cbounds, bounds;
|
|
|
|
atk_text_get_character_extents (text, start_offset,
|
|
&bounds.x, &bounds.y,
|
|
&bounds.width, &bounds.height,
|
|
coord_type);
|
|
|
|
for (i = start_offset + 1; i < end_offset; i++)
|
|
{
|
|
atk_text_get_character_extents (text, i,
|
|
&cbounds.x, &cbounds.y,
|
|
&cbounds.width, &cbounds.height,
|
|
coord_type);
|
|
atk_text_rectangle_union (&bounds, &cbounds, &bounds);
|
|
}
|
|
|
|
rect->x = bounds.x;
|
|
rect->y = bounds.y;
|
|
rect->width = bounds.width;
|
|
rect->height = bounds.height;
|
|
}
|
|
|
|
static AtkTextRange**
|
|
atk_text_real_get_bounded_ranges (AtkText *text,
|
|
AtkTextRectangle *rect,
|
|
AtkCoordType coord_type,
|
|
AtkTextClipType x_clip_type,
|
|
AtkTextClipType y_clip_type)
|
|
{
|
|
gint bounds_min_offset, bounds_max_offset;
|
|
gint min_line_start, min_line_end;
|
|
gint max_line_start, max_line_end;
|
|
gchar *line;
|
|
gint curr_offset;
|
|
gint offset;
|
|
gint num_ranges = 0;
|
|
gint range_size = 1;
|
|
AtkTextRectangle cbounds;
|
|
AtkTextRange **range;
|
|
|
|
range = NULL;
|
|
bounds_min_offset = atk_text_get_offset_at_point (text, rect->x, rect->y, coord_type);
|
|
bounds_max_offset = atk_text_get_offset_at_point (text, rect->x + rect->width, rect->y + rect->height, coord_type);
|
|
|
|
if (bounds_min_offset == 0 &&
|
|
bounds_min_offset == bounds_max_offset)
|
|
return NULL;
|
|
|
|
line = atk_text_get_string_at_offset (text, bounds_min_offset,
|
|
ATK_TEXT_BOUNDARY_LINE_START,
|
|
&min_line_start, &min_line_end);
|
|
g_free (line);
|
|
line = atk_text_get_string_at_offset (text, bounds_max_offset,
|
|
ATK_TEXT_BOUNDARY_LINE_START,
|
|
&max_line_start, &max_line_end);
|
|
g_free (line);
|
|
bounds_min_offset = MIN (min_line_start, max_line_start);
|
|
bounds_max_offset = MAX (min_line_end, max_line_end);
|
|
|
|
curr_offset = bounds_min_offset;
|
|
while (curr_offset < bounds_max_offset)
|
|
{
|
|
offset = curr_offset;
|
|
|
|
while (curr_offset < bounds_max_offset)
|
|
{
|
|
atk_text_get_character_extents (text, curr_offset,
|
|
&cbounds.x, &cbounds.y,
|
|
&cbounds.width, &cbounds.height,
|
|
coord_type);
|
|
if (!atk_text_rectangle_contain (rect, &cbounds, x_clip_type, y_clip_type))
|
|
break;
|
|
curr_offset++;
|
|
}
|
|
if (curr_offset > offset)
|
|
{
|
|
AtkTextRange *one_range = g_new (AtkTextRange, 1);
|
|
|
|
one_range->start_offset = offset;
|
|
one_range->end_offset = curr_offset;
|
|
one_range->content = atk_text_get_text (text, offset, curr_offset);
|
|
atk_text_get_range_extents (text, offset, curr_offset, coord_type, &one_range->bounds);
|
|
|
|
if (num_ranges >= range_size - 1)
|
|
{
|
|
range_size *= 2;
|
|
range = g_realloc (range, range_size * sizeof (gpointer));
|
|
}
|
|
range[num_ranges] = one_range;
|
|
num_ranges++;
|
|
}
|
|
curr_offset++;
|
|
if (range)
|
|
range[num_ranges] = NULL;
|
|
}
|
|
return range;
|
|
}
|
|
|
|
/**
|
|
* atk_text_free_ranges:
|
|
* @ranges: (array): A pointer to an array of #AtkTextRange which is
|
|
* to be freed.
|
|
*
|
|
* Frees the memory associated with an array of AtkTextRange. It is assumed
|
|
* that the array was returned by the function atk_text_get_bounded_ranges
|
|
* and is NULL terminated.
|
|
*
|
|
* Since: 1.3
|
|
**/
|
|
void
|
|
atk_text_free_ranges (AtkTextRange **ranges)
|
|
{
|
|
AtkTextRange **first = ranges;
|
|
|
|
if (ranges)
|
|
{
|
|
while (*ranges)
|
|
{
|
|
AtkTextRange *range;
|
|
|
|
range = *ranges;
|
|
ranges++;
|
|
g_free (range->content);
|
|
g_free (range);
|
|
}
|
|
g_free (first);
|
|
}
|
|
}
|
|
|
|
static AtkTextRange *
|
|
atk_text_range_copy (AtkTextRange *src)
|
|
{
|
|
AtkTextRange *dst = g_new0 (AtkTextRange, 1);
|
|
dst->bounds = src->bounds;
|
|
dst->start_offset = src->start_offset;
|
|
dst->end_offset = src->end_offset;
|
|
if (src->content)
|
|
dst->content = g_strdup (src->content);
|
|
return dst;
|
|
}
|
|
|
|
static void
|
|
atk_text_range_free (AtkTextRange *range)
|
|
{
|
|
g_free (range->content);
|
|
g_free (range);
|
|
}
|
|
|
|
G_DEFINE_BOXED_TYPE (AtkTextRange, atk_text_range, atk_text_range_copy,
|
|
atk_text_range_free)
|