1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3  * This file is part of the LibreOffice project.
4  *
5  * This Source Code Form is subject to the terms of the Mozilla Public
6  * License, v. 2.0. If a copy of the MPL was not distributed with this
7  * file, You can obtain one at http://mozilla.org/MPL/2.0/.
8  *
9  * This file incorporates work covered by the following license notice:
10  *
11  *   Licensed to the Apache Software Foundation (ASF) under one or more
12  *   contributor license agreements. See the NOTICE file distributed
13  *   with this work for additional information regarding copyright
14  *   ownership. The ASF licenses this file to you under the Apache
15  *   License, Version 2.0 (the "License"); you may not use this file
16  *   except in compliance with the License. You may obtain a copy of
17  *   the License at http://www.apache.org/licenses/LICENSE-2.0 .
18  */
19 
20 #include "atkwrapper.hxx"
21 #include "atktextattributes.hxx"
22 #include <algorithm>
23 
24 #include <osl/diagnose.h>
25 
26 #include <com/sun/star/accessibility/AccessibleTextType.hpp>
27 #include <com/sun/star/accessibility/TextSegment.hpp>
28 #include <com/sun/star/accessibility/XAccessibleMultiLineText.hpp>
29 #include <com/sun/star/accessibility/XAccessibleText.hpp>
30 #include <com/sun/star/accessibility/XAccessibleTextAttributes.hpp>
31 #include <com/sun/star/accessibility/XAccessibleTextMarkup.hpp>
32 #include <com/sun/star/text/TextMarkupType.hpp>
33 
34 using namespace ::com::sun::star;
35 
36 static sal_Int16
text_type_from_boundary(AtkTextBoundary boundary_type)37 text_type_from_boundary(AtkTextBoundary boundary_type)
38 {
39     switch(boundary_type)
40     {
41         case ATK_TEXT_BOUNDARY_CHAR:
42             return accessibility::AccessibleTextType::CHARACTER;
43         case ATK_TEXT_BOUNDARY_WORD_START:
44         case ATK_TEXT_BOUNDARY_WORD_END:
45             return accessibility::AccessibleTextType::WORD;
46         case ATK_TEXT_BOUNDARY_SENTENCE_START:
47         case ATK_TEXT_BOUNDARY_SENTENCE_END:
48             return accessibility::AccessibleTextType::SENTENCE;
49         case ATK_TEXT_BOUNDARY_LINE_START:
50         case ATK_TEXT_BOUNDARY_LINE_END:
51             return accessibility::AccessibleTextType::LINE;
52         default:
53             return -1;
54     }
55 }
56 
57 /*****************************************************************************/
58 
59 static gchar *
adjust_boundaries(css::uno::Reference<css::accessibility::XAccessibleText> const & pText,accessibility::TextSegment const & rTextSegment,AtkTextBoundary boundary_type,gint * start_offset,gint * end_offset)60 adjust_boundaries( css::uno::Reference<css::accessibility::XAccessibleText> const & pText,
61                    accessibility::TextSegment const & rTextSegment,
62                    AtkTextBoundary  boundary_type,
63                    gint * start_offset, gint * end_offset )
64 {
65     accessibility::TextSegment aTextSegment;
66     OUString aString;
67     gint start = 0, end = 0;
68 
69     if( !rTextSegment.SegmentText.isEmpty() )
70     {
71         switch(boundary_type)
72         {
73         case ATK_TEXT_BOUNDARY_CHAR:
74         case ATK_TEXT_BOUNDARY_LINE_START:
75         case ATK_TEXT_BOUNDARY_LINE_END:
76         case ATK_TEXT_BOUNDARY_SENTENCE_START:
77             start = rTextSegment.SegmentStart;
78             end = rTextSegment.SegmentEnd;
79             aString = rTextSegment.SegmentText;
80             break;
81 
82         // the OOo break iterator behaves as SENTENCE_START
83         case ATK_TEXT_BOUNDARY_SENTENCE_END:
84             start = rTextSegment.SegmentStart;
85             end = rTextSegment.SegmentEnd;
86 
87             if( start > 0 )
88                 --start;
89             if( end > 0 && end < pText->getCharacterCount() - 1 )
90                 --end;
91 
92             aString = pText->getTextRange(start, end);
93             break;
94 
95         case ATK_TEXT_BOUNDARY_WORD_START:
96             start = rTextSegment.SegmentStart;
97 
98             // Determine the start index of the next segment
99             aTextSegment = pText->getTextBehindIndex(rTextSegment.SegmentEnd,
100                                                      text_type_from_boundary(boundary_type));
101             if( !aTextSegment.SegmentText.isEmpty() )
102                 end = aTextSegment.SegmentStart;
103             else
104                 end = pText->getCharacterCount();
105 
106             aString = pText->getTextRange(start, end);
107             break;
108 
109         case ATK_TEXT_BOUNDARY_WORD_END:
110             end = rTextSegment.SegmentEnd;
111 
112             // Determine the end index of the previous segment
113             aTextSegment = pText->getTextBeforeIndex(rTextSegment.SegmentStart,
114                                                      text_type_from_boundary(boundary_type));
115             if( !aTextSegment.SegmentText.isEmpty() )
116                 start = aTextSegment.SegmentEnd;
117             else
118                 start = 0;
119 
120             aString = pText->getTextRange(start, end);
121             break;
122 
123         default:
124             return nullptr;
125         }
126     }
127 
128     *start_offset = start;
129     *end_offset   = end;
130 
131     return OUStringToGChar(aString);
132 }
133 
134 /*****************************************************************************/
135 
136 /// @throws uno::RuntimeException
137 static css::uno::Reference<css::accessibility::XAccessibleText>
getText(AtkText * pText)138     getText( AtkText *pText )
139 {
140     AtkObjectWrapper *pWrap = ATK_OBJECT_WRAPPER( pText );
141     if( pWrap )
142     {
143         if( !pWrap->mpText.is() )
144         {
145             pWrap->mpText.set(pWrap->mpContext, css::uno::UNO_QUERY);
146         }
147 
148         return pWrap->mpText;
149     }
150 
151     return css::uno::Reference<css::accessibility::XAccessibleText>();
152 }
153 
154 /*****************************************************************************/
155 
156 /// @throws uno::RuntimeException
157 static css::uno::Reference<css::accessibility::XAccessibleTextMarkup>
getTextMarkup(AtkText * pText)158     getTextMarkup( AtkText *pText )
159 {
160     AtkObjectWrapper *pWrap = ATK_OBJECT_WRAPPER( pText );
161     if( pWrap )
162     {
163         if( !pWrap->mpTextMarkup.is() )
164         {
165             pWrap->mpTextMarkup.set(pWrap->mpContext, css::uno::UNO_QUERY);
166         }
167 
168         return pWrap->mpTextMarkup;
169     }
170 
171     return css::uno::Reference<css::accessibility::XAccessibleTextMarkup>();
172 }
173 
174 /*****************************************************************************/
175 
176 /// @throws uno::RuntimeException
177 static css::uno::Reference<css::accessibility::XAccessibleTextAttributes>
getTextAttributes(AtkText * pText)178     getTextAttributes( AtkText *pText )
179 {
180     AtkObjectWrapper *pWrap = ATK_OBJECT_WRAPPER( pText );
181     if( pWrap )
182     {
183         if( !pWrap->mpTextAttributes.is() )
184         {
185             pWrap->mpTextAttributes.set(pWrap->mpContext, css::uno::UNO_QUERY);
186         }
187 
188         return pWrap->mpTextAttributes;
189     }
190 
191     return css::uno::Reference<css::accessibility::XAccessibleTextAttributes>();
192 }
193 
194 /*****************************************************************************/
195 
196 /// @throws uno::RuntimeException
197 static css::uno::Reference<css::accessibility::XAccessibleMultiLineText>
getMultiLineText(AtkText * pText)198     getMultiLineText( AtkText *pText )
199 {
200     AtkObjectWrapper *pWrap = ATK_OBJECT_WRAPPER( pText );
201     if( pWrap )
202     {
203         if( !pWrap->mpMultiLineText.is() )
204         {
205             pWrap->mpMultiLineText.set(pWrap->mpContext, css::uno::UNO_QUERY);
206         }
207 
208         return pWrap->mpMultiLineText;
209     }
210 
211     return css::uno::Reference<css::accessibility::XAccessibleMultiLineText>();
212 }
213 
214 /*****************************************************************************/
215 
216 extern "C" {
217 
218 static gchar *
text_wrapper_get_text(AtkText * text,gint start_offset,gint end_offset)219 text_wrapper_get_text (AtkText *text,
220                        gint     start_offset,
221                        gint     end_offset)
222 {
223     gchar * ret = nullptr;
224 
225     g_return_val_if_fail( (end_offset == -1) || (end_offset >= start_offset), nullptr );
226 
227     /* at-spi expects the delete event to be send before the deletion happened
228      * so we save the deleted string object in the UNO event notification and
229      * fool libatk-bridge.so here ..
230      */
231     void * pData = g_object_get_data( G_OBJECT(text), "ooo::text_changed::delete" );
232     if( pData != nullptr )
233     {
234         accessibility::TextSegment * pTextSegment =
235             static_cast <accessibility::TextSegment *> (pData);
236 
237         if( pTextSegment->SegmentStart == start_offset &&
238             pTextSegment->SegmentEnd == end_offset )
239         {
240             OString aUtf8 = OUStringToOString( pTextSegment->SegmentText, RTL_TEXTENCODING_UTF8 );
241             return g_strdup( aUtf8.getStr() );
242         }
243     }
244 
245     try {
246         css::uno::Reference<css::accessibility::XAccessibleText> pText
247             = getText( text );
248         if( pText.is() )
249         {
250             OUString aText;
251             sal_Int32 n = pText->getCharacterCount();
252 
253             if( -1 == end_offset )
254                 aText = pText->getText();
255             else if( start_offset < n )
256                 aText = pText->getTextRange(start_offset, end_offset);
257 
258             ret = g_strdup( OUStringToOString(aText, RTL_TEXTENCODING_UTF8 ).getStr() );
259         }
260     }
261     catch(const uno::Exception&) {
262         g_warning( "Exception in getText()" );
263     }
264 
265     return ret;
266 }
267 
268 static gchar *
text_wrapper_get_text_after_offset(AtkText * text,gint offset,AtkTextBoundary boundary_type,gint * start_offset,gint * end_offset)269 text_wrapper_get_text_after_offset (AtkText          *text,
270                                     gint             offset,
271                                     AtkTextBoundary  boundary_type,
272                                     gint             *start_offset,
273                                     gint             *end_offset)
274 {
275     try {
276         css::uno::Reference<css::accessibility::XAccessibleText> pText
277             = getText( text );
278         if( pText.is() )
279         {
280             accessibility::TextSegment aTextSegment = pText->getTextBehindIndex(offset, text_type_from_boundary(boundary_type));
281             return adjust_boundaries(pText, aTextSegment, boundary_type, start_offset, end_offset);
282         }
283     }
284     catch(const uno::Exception&) {
285         g_warning( "Exception in get_text_after_offset()" );
286     }
287 
288     return nullptr;
289 }
290 
291 static gchar *
text_wrapper_get_text_at_offset(AtkText * text,gint offset,AtkTextBoundary boundary_type,gint * start_offset,gint * end_offset)292 text_wrapper_get_text_at_offset (AtkText          *text,
293                                  gint             offset,
294                                  AtkTextBoundary  boundary_type,
295                                  gint             *start_offset,
296                                  gint             *end_offset)
297 {
298     try {
299         css::uno::Reference<css::accessibility::XAccessibleText> pText
300             = getText( text );
301         if( pText.is() )
302         {
303             /* If the user presses the 'End' key, the caret will be placed behind the last character,
304              * which is the same index as the first character of the next line. In atk the magic offset
305              * '-2' is used to cover this special case.
306              */
307             if (
308                  -2 == offset &&
309                      (ATK_TEXT_BOUNDARY_LINE_START == boundary_type ||
310                       ATK_TEXT_BOUNDARY_LINE_END == boundary_type)
311                )
312             {
313                 css::uno::Reference<
314                     css::accessibility::XAccessibleMultiLineText> pMultiLineText
315                         = getMultiLineText( text );
316                 if( pMultiLineText.is() )
317                 {
318                     accessibility::TextSegment aTextSegment = pMultiLineText->getTextAtLineWithCaret();
319                     return adjust_boundaries(pText, aTextSegment, boundary_type, start_offset, end_offset);
320                 }
321             }
322 
323             accessibility::TextSegment aTextSegment = pText->getTextAtIndex(offset, text_type_from_boundary(boundary_type));
324             return adjust_boundaries(pText, aTextSegment, boundary_type, start_offset, end_offset);
325         }
326     }
327     catch(const uno::Exception&) {
328         g_warning( "Exception in get_text_at_offset()" );
329     }
330 
331     return nullptr;
332 }
333 
334 static gunichar
text_wrapper_get_character_at_offset(AtkText * text,gint offset)335 text_wrapper_get_character_at_offset (AtkText          *text,
336                                       gint             offset)
337 {
338     gint start, end;
339     gunichar uc = 0;
340 
341     gchar * char_as_string =
342         text_wrapper_get_text_at_offset(text, offset, ATK_TEXT_BOUNDARY_CHAR,
343                                         &start, &end);
344     if( char_as_string )
345     {
346         uc = g_utf8_get_char( char_as_string );
347         g_free( char_as_string );
348     }
349 
350     return uc;
351 }
352 
353 static gchar *
text_wrapper_get_text_before_offset(AtkText * text,gint offset,AtkTextBoundary boundary_type,gint * start_offset,gint * end_offset)354 text_wrapper_get_text_before_offset (AtkText          *text,
355                                      gint             offset,
356                                      AtkTextBoundary  boundary_type,
357                                      gint             *start_offset,
358                                      gint             *end_offset)
359 {
360     try {
361         css::uno::Reference<css::accessibility::XAccessibleText> pText
362             = getText( text );
363         if( pText.is() )
364         {
365             accessibility::TextSegment aTextSegment = pText->getTextBeforeIndex(offset, text_type_from_boundary(boundary_type));
366             return adjust_boundaries(pText, aTextSegment, boundary_type, start_offset, end_offset);
367         }
368     }
369     catch(const uno::Exception&) {
370         g_warning( "Exception in text_before_offset()" );
371     }
372 
373     return nullptr;
374 }
375 
376 static gint
text_wrapper_get_caret_offset(AtkText * text)377 text_wrapper_get_caret_offset (AtkText          *text)
378 {
379     gint offset = -1;
380 
381     try {
382         css::uno::Reference<css::accessibility::XAccessibleText> pText
383             = getText( text );
384         if( pText.is() )
385             offset = pText->getCaretPosition();
386     }
387     catch(const uno::Exception&) {
388         g_warning( "Exception in getCaretPosition()" );
389     }
390 
391     return offset;
392 }
393 
394 static gboolean
text_wrapper_set_caret_offset(AtkText * text,gint offset)395 text_wrapper_set_caret_offset (AtkText *text,
396                                gint     offset)
397 {
398     try {
399         css::uno::Reference<css::accessibility::XAccessibleText> pText
400             = getText( text );
401         if( pText.is() )
402             return pText->setCaretPosition( offset );
403     }
404     catch(const uno::Exception&) {
405         g_warning( "Exception in setCaretPosition()" );
406     }
407 
408     return FALSE;
409 }
410 
411 // #i92232#
412 static AtkAttributeSet*
handle_text_markup_as_run_attribute(css::uno::Reference<css::accessibility::XAccessibleTextMarkup> const & pTextMarkup,const gint nTextMarkupType,const gint offset,AtkAttributeSet * pSet,gint * start_offset,gint * end_offset)413 handle_text_markup_as_run_attribute( css::uno::Reference<css::accessibility::XAccessibleTextMarkup> const & pTextMarkup,
414                                      const gint nTextMarkupType,
415                                      const gint offset,
416                                      AtkAttributeSet* pSet,
417                                      gint *start_offset,
418                                      gint *end_offset )
419 {
420     const gint nTextMarkupCount( pTextMarkup->getTextMarkupCount( nTextMarkupType ) );
421     if ( nTextMarkupCount > 0 )
422     {
423         for ( gint nTextMarkupIndex = 0;
424               nTextMarkupIndex < nTextMarkupCount;
425               ++nTextMarkupIndex )
426         {
427             accessibility::TextSegment aTextSegment =
428                 pTextMarkup->getTextMarkup( nTextMarkupIndex, nTextMarkupType );
429             const gint nStartOffsetTextMarkup = aTextSegment.SegmentStart;
430             const gint nEndOffsetTextMarkup = aTextSegment.SegmentEnd;
431             if ( nStartOffsetTextMarkup <= offset )
432             {
433                 if ( offset < nEndOffsetTextMarkup )
434                 {
435                     // text markup at <offset>
436                     *start_offset = ::std::max( *start_offset,
437                                                 nStartOffsetTextMarkup );
438                     *end_offset = ::std::min( *end_offset,
439                                               nEndOffsetTextMarkup );
440                     switch ( nTextMarkupType )
441                     {
442                         case css::text::TextMarkupType::SPELLCHECK:
443                         {
444                             pSet = attribute_set_prepend_misspelled( pSet );
445                         }
446                         break;
447                         case css::text::TextMarkupType::TRACK_CHANGE_INSERTION:
448                         {
449                             pSet = attribute_set_prepend_tracked_change_insertion( pSet );
450                         }
451                         break;
452                         case css::text::TextMarkupType::TRACK_CHANGE_DELETION:
453                         {
454                             pSet = attribute_set_prepend_tracked_change_deletion( pSet );
455                         }
456                         break;
457                         case css::text::TextMarkupType::TRACK_CHANGE_FORMATCHANGE:
458                         {
459                             pSet = attribute_set_prepend_tracked_change_formatchange( pSet );
460                         }
461                         break;
462                         default:
463                         {
464                             OSL_ASSERT( false );
465                         }
466                     }
467                     break; // no further iteration needed.
468                 }
469                 else
470                 {
471                     *start_offset = ::std::max( *start_offset,
472                                                 nEndOffsetTextMarkup );
473                     // continue iteration.
474                 }
475             }
476             else
477             {
478                 *end_offset = ::std::min( *end_offset,
479                                           nStartOffsetTextMarkup );
480                 break; // no further iteration.
481             }
482         } // eof iteration over text markups
483     }
484 
485     return pSet;
486 }
487 
488 static AtkAttributeSet *
text_wrapper_get_run_attributes(AtkText * text,gint offset,gint * start_offset,gint * end_offset)489 text_wrapper_get_run_attributes( AtkText        *text,
490                                  gint           offset,
491                                  gint           *start_offset,
492                                  gint           *end_offset)
493 {
494     AtkAttributeSet *pSet = nullptr;
495 
496     try {
497         bool bOffsetsAreValid = false;
498 
499         css::uno::Reference<css::accessibility::XAccessibleText> pText
500             = getText( text );
501         if( pText.is())
502         {
503             uno::Sequence< beans::PropertyValue > aAttributeList;
504 
505             css::uno::Reference<css::accessibility::XAccessibleTextAttributes>
506                 pTextAttributes = getTextAttributes( text );
507             if(pTextAttributes.is()) // Text attributes are available for paragraphs only
508             {
509                 aAttributeList = pTextAttributes->getRunAttributes( offset, uno::Sequence< OUString > () );
510             }
511             else // For other text objects use character attributes
512             {
513                 aAttributeList = pText->getCharacterAttributes( offset, uno::Sequence< OUString > () );
514             }
515 
516             pSet = attribute_set_new_from_property_values( aAttributeList, true, text );
517             //  #i100938#
518             // - always provide start_offset and end_offset
519             {
520                 accessibility::TextSegment aTextSegment =
521                     pText->getTextAtIndex(offset, accessibility::AccessibleTextType::ATTRIBUTE_RUN);
522 
523                 *start_offset = aTextSegment.SegmentStart;
524                 // #i100938#
525                 // Do _not_ increment the end_offset provide by <accessibility::TextSegment> instance
526                 *end_offset = aTextSegment.SegmentEnd;
527                 bOffsetsAreValid = true;
528             }
529         }
530 
531         // Special handling for misspelled text
532         // #i92232#
533         // - add special handling for tracked changes and refactor the
534         //   corresponding code for handling misspelled text.
535         css::uno::Reference<css::accessibility::XAccessibleTextMarkup>
536             pTextMarkup = getTextMarkup( text );
537         if( pTextMarkup.is() )
538         {
539             // Get attribute run here if it hasn't been done before
540             if (!bOffsetsAreValid && pText.is())
541             {
542                 accessibility::TextSegment aAttributeTextSegment =
543                     pText->getTextAtIndex(offset, accessibility::AccessibleTextType::ATTRIBUTE_RUN);
544                 *start_offset = aAttributeTextSegment.SegmentStart;
545                 *end_offset = aAttributeTextSegment.SegmentEnd;
546             }
547             // handle misspelled text
548             pSet = handle_text_markup_as_run_attribute(
549                     pTextMarkup,
550                     css::text::TextMarkupType::SPELLCHECK,
551                     offset, pSet, start_offset, end_offset );
552             // handle tracked changes
553             pSet = handle_text_markup_as_run_attribute(
554                     pTextMarkup,
555                     css::text::TextMarkupType::TRACK_CHANGE_INSERTION,
556                     offset, pSet, start_offset, end_offset );
557             pSet = handle_text_markup_as_run_attribute(
558                     pTextMarkup,
559                     css::text::TextMarkupType::TRACK_CHANGE_DELETION,
560                     offset, pSet, start_offset, end_offset );
561             pSet = handle_text_markup_as_run_attribute(
562                     pTextMarkup,
563                     css::text::TextMarkupType::TRACK_CHANGE_FORMATCHANGE,
564                     offset, pSet, start_offset, end_offset );
565         }
566     }
567     catch(const uno::Exception&){
568 
569         g_warning( "Exception in get_run_attributes()" );
570 
571         if( pSet )
572         {
573             atk_attribute_set_free( pSet );
574             pSet = nullptr;
575         }
576     }
577 
578     return pSet;
579 }
580 
581 /*****************************************************************************/
582 
583 static AtkAttributeSet *
text_wrapper_get_default_attributes(AtkText * text)584 text_wrapper_get_default_attributes( AtkText *text )
585 {
586     AtkAttributeSet *pSet = nullptr;
587 
588     try {
589         css::uno::Reference<css::accessibility::XAccessibleTextAttributes>
590             pTextAttributes = getTextAttributes( text );
591         if( pTextAttributes.is() )
592         {
593             uno::Sequence< beans::PropertyValue > aAttributeList =
594                 pTextAttributes->getDefaultAttributes( uno::Sequence< OUString > () );
595 
596             pSet = attribute_set_new_from_property_values( aAttributeList, false, text );
597         }
598     }
599     catch(const uno::Exception&) {
600 
601         g_warning( "Exception in get_default_attributes()" );
602 
603         if( pSet )
604         {
605             atk_attribute_set_free( pSet );
606             pSet = nullptr;
607         }
608     }
609 
610     return pSet;
611 }
612 
613 /*****************************************************************************/
614 
615 static void
text_wrapper_get_character_extents(AtkText * text,gint offset,gint * x,gint * y,gint * width,gint * height,AtkCoordType coords)616 text_wrapper_get_character_extents( AtkText          *text,
617                                     gint             offset,
618                                     gint             *x,
619                                     gint             *y,
620                                     gint             *width,
621                                     gint             *height,
622                                     AtkCoordType      coords )
623 {
624     *x = *y = *width = *height = -1;
625 
626     try {
627         css::uno::Reference<css::accessibility::XAccessibleText> pText
628             = getText( text );
629         if( pText.is() )
630         {
631             awt::Rectangle aRect = pText->getCharacterBounds( offset );
632 
633             gint origin_x = 0;
634             gint origin_y = 0;
635 
636             if( coords == ATK_XY_SCREEN )
637             {
638                 g_return_if_fail( ATK_IS_COMPONENT( text ) );
639                 SAL_WNODEPRECATED_DECLARATIONS_PUSH
640                 atk_component_get_position( ATK_COMPONENT( text ), &origin_x, &origin_y, coords);
641                 SAL_WNODEPRECATED_DECLARATIONS_POP
642             }
643 
644             *x = aRect.X + origin_x;
645             *y = aRect.Y + origin_y;
646             *width = aRect.Width;
647             *height = aRect.Height;
648         }
649     }
650     catch(const uno::Exception&) {
651         g_warning( "Exception in getCharacterBounds" );
652     }
653 }
654 
655 static gint
text_wrapper_get_character_count(AtkText * text)656 text_wrapper_get_character_count (AtkText *text)
657 {
658     gint rv = 0;
659 
660     try {
661         css::uno::Reference<css::accessibility::XAccessibleText> pText
662             = getText( text );
663         if( pText.is() )
664             rv = pText->getCharacterCount();
665     }
666     catch(const uno::Exception&) {
667         g_warning( "Exception in getCharacterCount" );
668     }
669 
670     return rv;
671 }
672 
673 static gint
text_wrapper_get_offset_at_point(AtkText * text,gint x,gint y,AtkCoordType coords)674 text_wrapper_get_offset_at_point (AtkText     *text,
675                                   gint         x,
676                                   gint         y,
677                                   AtkCoordType coords)
678 {
679     try {
680         css::uno::Reference<css::accessibility::XAccessibleText> pText
681             = getText( text );
682         if( pText.is() )
683         {
684             gint origin_x = 0;
685             gint origin_y = 0;
686 
687             if( coords == ATK_XY_SCREEN )
688             {
689                 g_return_val_if_fail( ATK_IS_COMPONENT( text ), -1 );
690                 SAL_WNODEPRECATED_DECLARATIONS_PUSH
691                 atk_component_get_position( ATK_COMPONENT( text ), &origin_x, &origin_y, coords);
692                 SAL_WNODEPRECATED_DECLARATIONS_POP
693             }
694 
695             return pText->getIndexAtPoint( awt::Point(x - origin_x, y - origin_y) );
696         }
697     }
698     catch(const uno::Exception&) {
699         g_warning( "Exception in getIndexAtPoint" );
700     }
701 
702     return -1;
703 }
704 
705 // FIXME: the whole series of selections API is problematic ...
706 
707 static gint
text_wrapper_get_n_selections(AtkText * text)708 text_wrapper_get_n_selections (AtkText *text)
709 {
710     gint rv = 0;
711 
712     try {
713         css::uno::Reference<css::accessibility::XAccessibleText> pText
714             = getText( text );
715         if( pText.is() )
716             rv = ( pText->getSelectionEnd() > pText->getSelectionStart() ) ? 1 : 0;
717     }
718     catch(const uno::Exception&) {
719         g_warning( "Exception in getSelectionEnd() or getSelectionStart()" );
720     }
721 
722     return rv;
723 }
724 
725 static gchar *
text_wrapper_get_selection(AtkText * text,gint selection_num,gint * start_offset,gint * end_offset)726 text_wrapper_get_selection (AtkText *text,
727                             gint     selection_num,
728                             gint    *start_offset,
729                             gint    *end_offset)
730 {
731     g_return_val_if_fail( selection_num == 0, FALSE );
732 
733     try {
734         css::uno::Reference<css::accessibility::XAccessibleText> pText
735             = getText( text );
736         if( pText.is() )
737         {
738             *start_offset = pText->getSelectionStart();
739             *end_offset   = pText->getSelectionEnd();
740 
741             return OUStringToGChar( pText->getSelectedText() );
742         }
743     }
744     catch(const uno::Exception&) {
745         g_warning( "Exception in getSelectionEnd(), getSelectionStart() or getSelectedText()" );
746     }
747 
748     return nullptr;
749 }
750 
751 static gboolean
text_wrapper_add_selection(AtkText * text,gint start_offset,gint end_offset)752 text_wrapper_add_selection (AtkText *text,
753                             gint     start_offset,
754                             gint     end_offset)
755 {
756     // FIXME: can we try to be more compatible by expanding an
757     //        existing adjacent selection ?
758 
759     try {
760         css::uno::Reference<css::accessibility::XAccessibleText> pText
761             = getText( text );
762         if( pText.is() )
763             return pText->setSelection( start_offset, end_offset ); // ?
764     }
765     catch(const uno::Exception&) {
766         g_warning( "Exception in setSelection()" );
767     }
768 
769     return FALSE;
770 }
771 
772 static gboolean
text_wrapper_remove_selection(AtkText * text,gint selection_num)773 text_wrapper_remove_selection (AtkText *text,
774                                gint     selection_num)
775 {
776     g_return_val_if_fail( selection_num == 0, FALSE );
777 
778     try {
779         css::uno::Reference<css::accessibility::XAccessibleText> pText
780             = getText( text );
781         if( pText.is() )
782             return pText->setSelection( 0, 0 ); // ?
783     }
784     catch(const uno::Exception&) {
785         g_warning( "Exception in setSelection()" );
786     }
787 
788     return FALSE;
789 }
790 
791 static gboolean
text_wrapper_set_selection(AtkText * text,gint selection_num,gint start_offset,gint end_offset)792 text_wrapper_set_selection (AtkText *text,
793                             gint     selection_num,
794                             gint     start_offset,
795                             gint     end_offset)
796 {
797     g_return_val_if_fail( selection_num == 0, FALSE );
798 
799     try {
800         css::uno::Reference<css::accessibility::XAccessibleText> pText
801             = getText( text );
802         if( pText.is() )
803             return pText->setSelection( start_offset, end_offset );
804     }
805     catch(const uno::Exception&) {
806         g_warning( "Exception in setSelection()" );
807     }
808 
809     return FALSE;
810 }
811 
812 } // extern "C"
813 
814 void
textIfaceInit(AtkTextIface * iface)815 textIfaceInit (AtkTextIface *iface)
816 {
817   g_return_if_fail (iface != nullptr);
818 
819   iface->get_text = text_wrapper_get_text;
820   iface->get_character_at_offset = text_wrapper_get_character_at_offset;
821   iface->get_text_before_offset = text_wrapper_get_text_before_offset;
822   iface->get_text_at_offset = text_wrapper_get_text_at_offset;
823   iface->get_text_after_offset = text_wrapper_get_text_after_offset;
824   iface->get_caret_offset = text_wrapper_get_caret_offset;
825   iface->set_caret_offset = text_wrapper_set_caret_offset;
826   iface->get_character_count = text_wrapper_get_character_count;
827   iface->get_n_selections = text_wrapper_get_n_selections;
828   iface->get_selection = text_wrapper_get_selection;
829   iface->add_selection = text_wrapper_add_selection;
830   iface->remove_selection = text_wrapper_remove_selection;
831   iface->set_selection = text_wrapper_set_selection;
832   iface->get_run_attributes = text_wrapper_get_run_attributes;
833   iface->get_default_attributes = text_wrapper_get_default_attributes;
834   iface->get_character_extents = text_wrapper_get_character_extents;
835   iface->get_offset_at_point = text_wrapper_get_offset_at_point;
836 }
837 
838 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
839