blob: 6618f8220581a99d1e27b94326d7b3526da8ac4f [file] [log] [blame]
/**************************************************************
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*
*************************************************************/
// MARKER(update_precomp.py): autogen include statement, do not remove
#include "precompiled_vcl.hxx"
#include "atkwrapper.hxx"
#include "atktextattributes.hxx"
#include <algorithm>
#include <com/sun/star/accessibility/AccessibleTextType.hpp>
#include <com/sun/star/accessibility/TextSegment.hpp>
#include <com/sun/star/accessibility/XAccessibleMultiLineText.hpp>
#include <com/sun/star/accessibility/XAccessibleText.hpp>
#include <com/sun/star/accessibility/XAccessibleTextAttributes.hpp>
#include <com/sun/star/accessibility/XAccessibleTextMarkup.hpp>
#include <com/sun/star/text/TextMarkupType.hpp>
// #define ENABLE_TRACING
#ifdef ENABLE_TRACING
#include <stdio.h>
#endif
using namespace ::com::sun::star;
static sal_Int16
text_type_from_boundary(AtkTextBoundary boundary_type)
{
switch(boundary_type)
{
case ATK_TEXT_BOUNDARY_CHAR:
return accessibility::AccessibleTextType::CHARACTER;
case ATK_TEXT_BOUNDARY_WORD_START:
case ATK_TEXT_BOUNDARY_WORD_END:
return accessibility::AccessibleTextType::WORD;
case ATK_TEXT_BOUNDARY_SENTENCE_START:
case ATK_TEXT_BOUNDARY_SENTENCE_END:
return accessibility::AccessibleTextType::SENTENCE;
case ATK_TEXT_BOUNDARY_LINE_START:
case ATK_TEXT_BOUNDARY_LINE_END:
return accessibility::AccessibleTextType::LINE;
default:
return -1;
}
}
/*****************************************************************************/
static gchar *
adjust_boundaries( accessibility::XAccessibleText* pText,
accessibility::TextSegment& rTextSegment,
AtkTextBoundary boundary_type,
gint * start_offset, gint * end_offset )
{
accessibility::TextSegment aTextSegment;
rtl::OUString aString;
gint start = 0, end = 0;
if( rTextSegment.SegmentText.getLength() > 0 )
{
switch(boundary_type)
{
case ATK_TEXT_BOUNDARY_CHAR:
case ATK_TEXT_BOUNDARY_LINE_START:
case ATK_TEXT_BOUNDARY_LINE_END:
case ATK_TEXT_BOUNDARY_SENTENCE_START:
start = rTextSegment.SegmentStart;
end = rTextSegment.SegmentEnd;
aString = rTextSegment.SegmentText;
break;
// the OOo break iterator behaves as SENTENCE_START
case ATK_TEXT_BOUNDARY_SENTENCE_END:
start = rTextSegment.SegmentStart;
end = rTextSegment.SegmentEnd;
if( start > 0 )
--start;
if( end > 0 && end < pText->getCharacterCount() - 1 )
--end;
aString = pText->getTextRange(start, end);
break;
case ATK_TEXT_BOUNDARY_WORD_START:
start = rTextSegment.SegmentStart;
// Determine the start index of the next segment
aTextSegment = pText->getTextBehindIndex(rTextSegment.SegmentEnd,
text_type_from_boundary(boundary_type));
if( aTextSegment.SegmentText.getLength() > 0 )
end = aTextSegment.SegmentStart;
else
end = pText->getCharacterCount();
aString = pText->getTextRange(start, end);
break;
case ATK_TEXT_BOUNDARY_WORD_END:
end = rTextSegment.SegmentEnd;
// Determine the end index of the previous segment
aTextSegment = pText->getTextBeforeIndex(rTextSegment.SegmentStart,
text_type_from_boundary(boundary_type));
if( aTextSegment.SegmentText.getLength() > 0 )
start = aTextSegment.SegmentEnd;
else
start = 0;
aString = pText->getTextRange(start, end);
break;
default:
return NULL;
}
}
*start_offset = start;
*end_offset = end;
#ifdef ENABLE_TRACING
fprintf(stderr, "adjust_boundaries( %d, %d, %d ) returns %d, %d\n",
rTextSegment.SegmentStart, rTextSegment.SegmentEnd, boundary_type,
start, end);
#endif
return OUStringToGChar(aString);
}
/*****************************************************************************/
static accessibility::XAccessibleText*
getText( AtkText *pText ) throw (uno::RuntimeException)
{
AtkObjectWrapper *pWrap = ATK_OBJECT_WRAPPER( pText );
if( pWrap )
{
if( !pWrap->mpText && pWrap->mpContext )
{
uno::Any any = pWrap->mpContext->queryInterface( accessibility::XAccessibleText::static_type(NULL) );
pWrap->mpText = reinterpret_cast< accessibility::XAccessibleText * > (any.pReserved);
pWrap->mpText->acquire();
}
return pWrap->mpText;
}
return NULL;
}
/*****************************************************************************/
static accessibility::XAccessibleTextMarkup*
getTextMarkup( AtkText *pText ) throw (uno::RuntimeException)
{
AtkObjectWrapper *pWrap = ATK_OBJECT_WRAPPER( pText );
if( pWrap )
{
if( !pWrap->mpTextMarkup && pWrap->mpContext )
{
uno::Any any = pWrap->mpContext->queryInterface( accessibility::XAccessibleTextMarkup::static_type(NULL) );
/* Since this not a dedicated interface in Atk and thus has not
* been queried during wrapper initialization, we need to check
* the return value here.
*/
if( typelib_TypeClass_INTERFACE == any.pType->eTypeClass )
{
pWrap->mpTextMarkup = reinterpret_cast< accessibility::XAccessibleTextMarkup * > (any.pReserved);
if( pWrap->mpTextMarkup )
pWrap->mpTextMarkup->acquire();
}
}
return pWrap->mpTextMarkup;
}
return NULL;
}
/*****************************************************************************/
static accessibility::XAccessibleTextAttributes*
getTextAttributes( AtkText *pText ) throw (uno::RuntimeException)
{
AtkObjectWrapper *pWrap = ATK_OBJECT_WRAPPER( pText );
if( pWrap )
{
if( !pWrap->mpTextAttributes && pWrap->mpContext )
{
uno::Any any = pWrap->mpContext->queryInterface( accessibility::XAccessibleTextAttributes::static_type(NULL) );
/* Since this not a dedicated interface in Atk and thus has not
* been queried during wrapper initialization, we need to check
* the return value here.
*/
if( typelib_TypeClass_INTERFACE == any.pType->eTypeClass )
{
pWrap->mpTextAttributes = reinterpret_cast< accessibility::XAccessibleTextAttributes * > (any.pReserved);
pWrap->mpTextAttributes->acquire();
}
}
return pWrap->mpTextAttributes;
}
return NULL;
}
/*****************************************************************************/
static accessibility::XAccessibleMultiLineText*
getMultiLineText( AtkText *pText ) throw (uno::RuntimeException)
{
AtkObjectWrapper *pWrap = ATK_OBJECT_WRAPPER( pText );
if( pWrap )
{
if( !pWrap->mpMultiLineText && pWrap->mpContext )
{
uno::Any any = pWrap->mpContext->queryInterface( accessibility::XAccessibleMultiLineText::static_type(NULL) );
/* Since this not a dedicated interface in Atk and thus has not
* been queried during wrapper initialization, we need to check
* the return value here.
*/
if( typelib_TypeClass_INTERFACE == any.pType->eTypeClass )
{
pWrap->mpMultiLineText = reinterpret_cast< accessibility::XAccessibleMultiLineText * > (any.pReserved);
pWrap->mpMultiLineText->acquire();
}
}
return pWrap->mpMultiLineText;
}
return NULL;
}
/*****************************************************************************/
extern "C" {
static gchar *
text_wrapper_get_text (AtkText *text,
gint start_offset,
gint end_offset)
{
gchar * ret = NULL;
g_return_val_if_fail( (end_offset == -1) || (end_offset >= start_offset), NULL );
/* at-spi expects the delete event to be send before the deletion happened
* so we save the deleted string object in the UNO event notification and
* fool libatk-bridge.so here ..
*/
void * pData = g_object_get_data( G_OBJECT(text), "ooo::text_changed::delete" );
if( pData != NULL )
{
accessibility::TextSegment * pTextSegment =
reinterpret_cast <accessibility::TextSegment *> (pData);
if( pTextSegment->SegmentStart == start_offset &&
pTextSegment->SegmentEnd == end_offset )
{
rtl::OString aUtf8 = rtl::OUStringToOString( pTextSegment->SegmentText, RTL_TEXTENCODING_UTF8 );
return g_strdup( aUtf8.getStr() );
}
}
try {
accessibility::XAccessibleText* pText = getText( text );
if( pText )
{
rtl::OUString aText;
sal_Int32 n = pText->getCharacterCount();
if( -1 == end_offset )
aText = pText->getText();
else if( start_offset < n )
aText = pText->getTextRange(start_offset, end_offset);
ret = g_strdup( rtl::OUStringToOString(aText, RTL_TEXTENCODING_UTF8 ).getStr() );
}
}
catch(const uno::Exception& e) {
g_warning( "Exception in getText()" );
}
#ifdef ENABLE_TRACING
fprintf(stderr, "text_wrapper_get_text( %d,%d ) returns %s\n", start_offset, end_offset, ret ? ret : "null" );
#endif
return ret;
}
static gchar *
text_wrapper_get_text_after_offset (AtkText *text,
gint offset,
AtkTextBoundary boundary_type,
gint *start_offset,
gint *end_offset)
{
try {
accessibility::XAccessibleText* pText = getText( text );
if( pText )
{
accessibility::TextSegment aTextSegment = pText->getTextBehindIndex(offset, text_type_from_boundary(boundary_type));
return adjust_boundaries(pText, aTextSegment, boundary_type, start_offset, end_offset);
}
}
catch(const uno::Exception& e) {
g_warning( "Exception in get_text_after_offset()" );
}
return NULL;
}
static gchar *
text_wrapper_get_text_at_offset (AtkText *text,
gint offset,
AtkTextBoundary boundary_type,
gint *start_offset,
gint *end_offset)
{
try {
accessibility::XAccessibleText* pText = getText( text );
if( pText )
{
/* If the user presses the 'End' key, the caret will be placed behind the last character,
* which is the same index as the first character of the next line. In atk the magic offset
* '-2' is used to cover this special case.
*/
if (
-2 == offset &&
(ATK_TEXT_BOUNDARY_LINE_START == boundary_type ||
ATK_TEXT_BOUNDARY_LINE_END == boundary_type)
)
{
accessibility::XAccessibleMultiLineText* pMultiLineText = getMultiLineText( text );
if( pMultiLineText )
{
accessibility::TextSegment aTextSegment = pMultiLineText->getTextAtLineWithCaret();
return adjust_boundaries(pText, aTextSegment, boundary_type, start_offset, end_offset);
}
}
accessibility::TextSegment aTextSegment = pText->getTextAtIndex(offset, text_type_from_boundary(boundary_type));
return adjust_boundaries(pText, aTextSegment, boundary_type, start_offset, end_offset);
}
}
catch(const uno::Exception& e) {
g_warning( "Exception in get_text_at_offset()" );
}
return NULL;
}
static gunichar
text_wrapper_get_character_at_offset (AtkText *text,
gint offset)
{
gint start, end;
gunichar uc = 0;
gchar * char_as_string =
text_wrapper_get_text_at_offset(text, offset, ATK_TEXT_BOUNDARY_CHAR,
&start, &end);
if( char_as_string )
{
uc = g_utf8_get_char( char_as_string );
g_free( char_as_string );
}
return uc;
}
static gchar *
text_wrapper_get_text_before_offset (AtkText *text,
gint offset,
AtkTextBoundary boundary_type,
gint *start_offset,
gint *end_offset)
{
try {
accessibility::XAccessibleText* pText = getText( text );
if( pText )
{
accessibility::TextSegment aTextSegment = pText->getTextBeforeIndex(offset, text_type_from_boundary(boundary_type));
return adjust_boundaries(pText, aTextSegment, boundary_type, start_offset, end_offset);
}
}
catch(const uno::Exception& e) {
g_warning( "Exception in text_before_offset()" );
}
return NULL;
}
static gint
text_wrapper_get_caret_offset (AtkText *text)
{
gint offset = -1;
try {
accessibility::XAccessibleText* pText = getText( text );
if( pText )
offset = pText->getCaretPosition();
}
catch(const uno::Exception& e) {
g_warning( "Exception in getCaretPosition()" );
}
#ifdef ENABLE_TRACING
fprintf(stderr, "get_caret_offset(%p) returns %d\n", text, offset);
#endif
return offset;
}
static gboolean
text_wrapper_set_caret_offset (AtkText *text,
gint offset)
{
try {
accessibility::XAccessibleText* pText = getText( text );
if( pText )
return pText->setCaretPosition( offset );
}
catch(const uno::Exception& e) {
g_warning( "Exception in setCaretPosition()" );
}
return FALSE;
}
// --> OD 2010-03-04 #i92232#
AtkAttributeSet*
handle_text_markup_as_run_attribute( accessibility::XAccessibleTextMarkup* pTextMarkup,
const gint nTextMarkupType,
const gint offset,
AtkAttributeSet* pSet,
gint *start_offset,
gint *end_offset )
{
const gint nTextMarkupCount( pTextMarkup->getTextMarkupCount( nTextMarkupType ) );
if ( nTextMarkupCount > 0 )
{
for ( gint nTextMarkupIndex = 0;
nTextMarkupIndex < nTextMarkupCount;
++nTextMarkupIndex )
{
accessibility::TextSegment aTextSegment =
pTextMarkup->getTextMarkup( nTextMarkupIndex, nTextMarkupType );
const gint nStartOffsetTextMarkup = aTextSegment.SegmentStart;
const gint nEndOffsetTextMarkup = aTextSegment.SegmentEnd;
if ( nStartOffsetTextMarkup <= offset )
{
if ( offset < nEndOffsetTextMarkup )
{
// text markup at <offset>
*start_offset = ::std::max( *start_offset,
nStartOffsetTextMarkup );
*end_offset = ::std::min( *end_offset,
nEndOffsetTextMarkup );
switch ( nTextMarkupType )
{
case com::sun::star::text::TextMarkupType::SPELLCHECK:
{
pSet = attribute_set_prepend_misspelled( pSet );
}
break;
case com::sun::star::text::TextMarkupType::TRACK_CHANGE_INSERTION:
{
pSet = attribute_set_prepend_tracked_change_insertion( pSet );
}
break;
case com::sun::star::text::TextMarkupType::TRACK_CHANGE_DELETION:
{
pSet = attribute_set_prepend_tracked_change_deletion( pSet );
}
break;
case com::sun::star::text::TextMarkupType::TRACK_CHANGE_FORMATCHANGE:
{
pSet = attribute_set_prepend_tracked_change_formatchange( pSet );
}
break;
default:
{
OSL_ASSERT( false );
}
}
break; // no further iteration needed.
}
else
{
*start_offset = ::std::max( *start_offset,
nEndOffsetTextMarkup );
// continue iteration.
}
}
else
{
*end_offset = ::std::min( *end_offset,
nStartOffsetTextMarkup );
break; // no further iteration.
}
} // eof iteration over text markups
}
return pSet;
}
// <--
static AtkAttributeSet *
text_wrapper_get_run_attributes( AtkText *text,
gint offset,
gint *start_offset,
gint *end_offset)
{
AtkAttributeSet *pSet = NULL;
try {
bool bOffsetsAreValid = false;
accessibility::XAccessibleText* pText = getText( text );
accessibility::XAccessibleTextAttributes* pTextAttributes = getTextAttributes( text );
if( pText && pTextAttributes )
{
uno::Sequence< beans::PropertyValue > aAttributeList =
pTextAttributes->getRunAttributes( offset, uno::Sequence< rtl::OUString > () );
pSet = attribute_set_new_from_property_values( aAttributeList, true, text );
// --> OD 2009-06-22 #i100938#
// - always provide start_offset and end_offset
// if( pSet )
// <--
{
accessibility::TextSegment aTextSegment =
pText->getTextAtIndex(offset, accessibility::AccessibleTextType::ATTRIBUTE_RUN);
*start_offset = aTextSegment.SegmentStart;
// --> OD 2009-06-22 #i100938#
// Do _not_ increment the end_offset provide by <accessibility::TextSegment> instance
// *end_offset = aTextSegment.SegmentEnd + 1; // FIXME: TESTME
*end_offset = aTextSegment.SegmentEnd;
// <--
bOffsetsAreValid = true;
}
}
// Special handling for misspelled text
// --> OD 2010-03-01 #i92232#
// - add special handling for tracked changes and refactor the
// corresponding code for handling misspelled text.
accessibility::XAccessibleTextMarkup* pTextMarkup = getTextMarkup( text );
if( pTextMarkup )
{
// Get attribute run here if it hasn't been done before
if( !bOffsetsAreValid )
{
accessibility::TextSegment aAttributeTextSegment =
pText->getTextAtIndex(offset, accessibility::AccessibleTextType::ATTRIBUTE_RUN);
*start_offset = aAttributeTextSegment.SegmentStart;
*end_offset = aAttributeTextSegment.SegmentEnd;
}
// handle misspelled text
pSet = handle_text_markup_as_run_attribute(
pTextMarkup,
com::sun::star::text::TextMarkupType::SPELLCHECK,
offset, pSet, start_offset, end_offset );
// handle tracked changes
pSet = handle_text_markup_as_run_attribute(
pTextMarkup,
com::sun::star::text::TextMarkupType::TRACK_CHANGE_INSERTION,
offset, pSet, start_offset, end_offset );
pSet = handle_text_markup_as_run_attribute(
pTextMarkup,
com::sun::star::text::TextMarkupType::TRACK_CHANGE_DELETION,
offset, pSet, start_offset, end_offset );
pSet = handle_text_markup_as_run_attribute(
pTextMarkup,
com::sun::star::text::TextMarkupType::TRACK_CHANGE_FORMATCHANGE,
offset, pSet, start_offset, end_offset );
}
// <--
}
catch(const uno::Exception& e){
g_warning( "Exception in get_run_attributes()" );
if( pSet )
{
atk_attribute_set_free( pSet );
pSet = NULL;
}
}
return pSet;
}
/*****************************************************************************/
static AtkAttributeSet *
text_wrapper_get_default_attributes( AtkText *text )
{
AtkAttributeSet *pSet = NULL;
try {
accessibility::XAccessibleTextAttributes* pTextAttributes = getTextAttributes( text );
if( pTextAttributes )
{
uno::Sequence< beans::PropertyValue > aAttributeList =
pTextAttributes->getDefaultAttributes( uno::Sequence< rtl::OUString > () );
pSet = attribute_set_new_from_property_values( aAttributeList, false, text );
}
}
catch(const uno::Exception& e) {
g_warning( "Exception in get_default_attributes()" );
if( pSet )
{
atk_attribute_set_free( pSet );
pSet = NULL;
}
}
return pSet;
}
/*****************************************************************************/
static void
text_wrapper_get_character_extents( AtkText *text,
gint offset,
gint *x,
gint *y,
gint *width,
gint *height,
AtkCoordType coords )
{
try {
accessibility::XAccessibleText* pText = getText( text );
if( pText )
{
*x = *y = *width = *height = 0;
awt::Rectangle aRect = pText->getCharacterBounds( offset );
gint origin_x = 0;
gint origin_y = 0;
if( coords == ATK_XY_SCREEN )
{
g_return_if_fail( ATK_IS_COMPONENT( text ) );
atk_component_get_position( ATK_COMPONENT( text ), &origin_x, &origin_y, coords);
}
*x = aRect.X + origin_x;
*y = aRect.Y + origin_y;
*width = aRect.Width;
*height = aRect.Height;
#ifdef ENABLE_TRACING
fprintf(stderr, "get_character_extents(%d, %d) returns: %d,%d,%d,%d ",
offset, coords, *x, *y, *width, *height);
#endif
}
}
catch(const uno::Exception& e) {
g_warning( "Exception in getCharacterBounds" );
}
}
static gint
text_wrapper_get_character_count (AtkText *text)
{
gint rv = 0;
try {
accessibility::XAccessibleText* pText = getText( text );
if( pText )
rv = pText->getCharacterCount();
}
catch(const uno::Exception& e) {
g_warning( "Exception in getCharacterCount" );
}
#ifdef ENABLE_TRACING
fprintf(stderr, "get_character_count(%p) returns: %d\n", text, rv);
#endif
return rv;
}
static gint
text_wrapper_get_offset_at_point (AtkText *text,
gint x,
gint y,
AtkCoordType coords)
{
try {
accessibility::XAccessibleText* pText = getText( text );
if( pText )
{
gint origin_x = 0;
gint origin_y = 0;
if( coords == ATK_XY_SCREEN )
{
g_return_val_if_fail( ATK_IS_COMPONENT( text ), -1 );
atk_component_get_position( ATK_COMPONENT( text ), &origin_x, &origin_y, coords);
}
return pText->getIndexAtPoint( awt::Point(x - origin_x, y - origin_y) );
}
}
catch(const uno::Exception& e) {
g_warning( "Exception in getIndexAtPoint" );
}
return -1;
}
// FIXME: the whole series of selections API is problematic ...
static gint
text_wrapper_get_n_selections (AtkText *text)
{
gint rv = 0;
try {
accessibility::XAccessibleText* pText = getText( text );
if( pText )
rv = ( pText->getSelectionEnd() > pText->getSelectionStart() ) ? 1 : 0;
}
catch(const uno::Exception& e) {
g_warning( "Exception in getSelectionEnd() or getSelectionStart()" );
}
#ifdef ENABLE_TRACING
fprintf(stderr, "get_n_selections(%p) returns %d\n", text, rv);
#endif
return rv;
}
static gchar *
text_wrapper_get_selection (AtkText *text,
gint selection_num,
gint *start_offset,
gint *end_offset)
{
g_return_val_if_fail( selection_num == 0, FALSE );
try {
accessibility::XAccessibleText* pText = getText( text );
if( pText )
{
*start_offset = pText->getSelectionStart();
*end_offset = pText->getSelectionEnd();
return OUStringToGChar( pText->getSelectedText() );
}
}
catch(const uno::Exception& e) {
g_warning( "Exception in getSelectionEnd(), getSelectionStart() or getSelectedText()" );
}
return NULL;
}
static gboolean
text_wrapper_add_selection (AtkText *text,
gint start_offset,
gint end_offset)
{
// FIXME: can we try to be more compatible by expanding an
// existing adjacent selection ?
try {
accessibility::XAccessibleText* pText = getText( text );
if( pText )
return pText->setSelection( start_offset, end_offset ); // ?
}
catch(const uno::Exception& e) {
g_warning( "Exception in setSelection()" );
}
return FALSE;
}
static gboolean
text_wrapper_remove_selection (AtkText *text,
gint selection_num)
{
g_return_val_if_fail( selection_num == 0, FALSE );
try {
accessibility::XAccessibleText* pText = getText( text );
if( pText )
return pText->setSelection( 0, 0 ); // ?
}
catch(const uno::Exception& e) {
g_warning( "Exception in setSelection()" );
}
return FALSE;
}
static gboolean
text_wrapper_set_selection (AtkText *text,
gint selection_num,
gint start_offset,
gint end_offset)
{
g_return_val_if_fail( selection_num == 0, FALSE );
try {
accessibility::XAccessibleText* pText = getText( text );
if( pText )
return pText->setSelection( start_offset, end_offset );
}
catch(const uno::Exception& e) {
g_warning( "Exception in setSelection()" );
}
return FALSE;
}
} // extern "C"
void
textIfaceInit (AtkTextIface *iface)
{
g_return_if_fail (iface != NULL);
iface->get_text = text_wrapper_get_text;
iface->get_character_at_offset = text_wrapper_get_character_at_offset;
iface->get_text_before_offset = text_wrapper_get_text_before_offset;
iface->get_text_at_offset = text_wrapper_get_text_at_offset;
iface->get_text_after_offset = text_wrapper_get_text_after_offset;
iface->get_caret_offset = text_wrapper_get_caret_offset;
iface->set_caret_offset = text_wrapper_set_caret_offset;
iface->get_character_count = text_wrapper_get_character_count;
iface->get_n_selections = text_wrapper_get_n_selections;
iface->get_selection = text_wrapper_get_selection;
iface->add_selection = text_wrapper_add_selection;
iface->remove_selection = text_wrapper_remove_selection;
iface->set_selection = text_wrapper_set_selection;
iface->get_run_attributes = text_wrapper_get_run_attributes;
iface->get_default_attributes = text_wrapper_get_default_attributes;
iface->get_character_extents = text_wrapper_get_character_extents;
iface->get_offset_at_point = text_wrapper_get_offset_at_point;
}