Added new, more efficient screen review API to AccessibleText.
[platform/core/uifw/at-spi2-atk.git] / libspi / text.c
1 /*
2  * AT-SPI - Assistive Technology Service Provider Interface
3  * (Gnome Accessibility Project; http://developer.gnome.org/projects/gap)
4  *
5  * Copyright 2001, 2002 Sun Microsystems Inc.,
6  * Copyright 2001, 2002 Ximian, Inc.
7  *
8  * This library is free software; you can redistribute it and/or
9  * modify it under the terms of the GNU Library General Public
10  * License as published by the Free Software Foundation; either
11  * version 2 of the License, or (at your option) any later version.
12  *
13  * This library is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16  * Library General Public License for more details.
17  *
18  * You should have received a copy of the GNU Library General Public
19  * License along with this library; if not, write to the
20  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
21  * Boston, MA 02111-1307, USA.
22  */
23
24 /* text.c : implements the Text interface */
25
26 #include <config.h>
27 #include <stdio.h>
28 #include <stdlib.h>
29 #include <atk/atktext.h>
30 #include <libspi/text.h>
31
32 /* Our parent Gtk object type */
33 #define PARENT_TYPE SPI_TYPE_BASE
34
35 typedef struct {
36   gint x;
37   gint y;
38   gint w;
39   gint h;
40 } SpiTextRect;
41
42 static SpiTextRect *
43 _spi_text_rect_union (SpiTextRect *aggregate, SpiTextRect *subrect)
44 {
45   if (subrect != NULL)
46     {
47       /* 'normalize' subrect */
48       if (subrect->w < 0)
49         {
50           subrect->x += subrect->w;
51           subrect->w *= -1;
52         }
53       if (subrect->h < 0)
54         {
55           subrect->y += subrect->h;
56           subrect->h *= -1;
57         }
58       if (aggregate == NULL)
59         {
60           aggregate = g_new (SpiTextRect, 1);
61           memcpy (aggregate, subrect, sizeof (SpiTextRect));
62         }
63       else
64         {
65           gint ax2 = aggregate->x + aggregate->w;
66           gint ay2 = aggregate->y + aggregate->h; 
67           gint sx2 = subrect->x + subrect->w; 
68           gint sy2 = subrect->y + subrect->h;
69           if (subrect->x < aggregate->x)
70             {
71               aggregate->w += (aggregate->x - subrect->x);
72               aggregate->x = subrect->x;
73             }
74           if (sx2 > ax2)
75             {
76               aggregate->w += (sx2 - ax2);
77             }
78           if (subrect->y < aggregate->y)
79             {
80               aggregate->h += (aggregate->y - subrect->y);
81               aggregate->y = subrect->y;
82             }
83           if (sy2 > ay2)
84             {
85               aggregate->h += (sy2 - ay2);
86             }
87         }
88     }
89   return aggregate;
90 }
91
92 static AtkText *
93 get_text_from_servant (PortableServer_Servant servant)
94 {
95   SpiBase *object = SPI_BASE (bonobo_object_from_servant (servant));
96
97   g_return_val_if_fail (object, NULL);
98   g_return_val_if_fail (ATK_IS_OBJECT(object->gobj), NULL);
99   return ATK_TEXT (object->gobj);
100 }
101
102 static CORBA_string
103 impl_getText (PortableServer_Servant servant,
104               const CORBA_long       startOffset,
105               const CORBA_long       endOffset,
106               CORBA_Environment     *ev)
107 {
108   gchar *txt;
109   CORBA_string rv;
110   AtkText *text = get_text_from_servant (servant);
111
112   g_return_val_if_fail (text != NULL, CORBA_string_dup (""));
113   
114   txt = atk_text_get_text (text, startOffset, endOffset);
115   if (txt)
116     {
117       rv = CORBA_string_dup (txt);
118       g_free (txt);
119     }
120   else
121     rv = CORBA_string_dup ("");
122
123   return rv;
124 }
125
126
127 CORBA_string
128 impl_getTextAfterOffset (PortableServer_Servant servant,
129                          const CORBA_long offset,
130                          const
131                          Accessibility_TEXT_BOUNDARY_TYPE
132                          type, CORBA_long * startOffset,
133                          CORBA_long * endOffset,
134                          CORBA_Environment *ev)
135 {
136   gchar *txt;
137   CORBA_char *rv;
138   gint intStartOffset, intEndOffset;
139   AtkText *text = get_text_from_servant (servant);
140
141   g_return_val_if_fail (text != NULL, CORBA_string_dup (""));
142
143   txt = atk_text_get_text_after_offset (text,
144                                         offset, (AtkTextBoundary) type,
145                                         &intStartOffset, &intEndOffset);
146   *startOffset = intStartOffset;
147   *endOffset = intEndOffset;
148
149   if (txt)
150     {
151       rv = CORBA_string_dup (txt);
152       g_free (txt);
153     }
154   else
155     rv = CORBA_string_dup ("");
156
157   return rv;
158 }
159
160
161 static CORBA_string
162 impl_getTextAtOffset (PortableServer_Servant servant,
163                       const CORBA_long offset,
164                       const Accessibility_TEXT_BOUNDARY_TYPE type,
165                       CORBA_long * startOffset,
166                       CORBA_long * endOffset,
167                       CORBA_Environment *ev)
168 {
169   gchar *txt;
170   CORBA_char *rv;
171   gint intStartOffset, intEndOffset;
172   AtkText *text = get_text_from_servant (servant);
173
174   g_return_val_if_fail (text != NULL, CORBA_string_dup (""));
175
176   txt = atk_text_get_text_at_offset (
177           text,
178           offset, (AtkTextBoundary) type,
179           &intStartOffset, &intEndOffset);
180
181   *startOffset = intStartOffset;
182   *endOffset = intEndOffset;
183
184   if (txt)
185     {
186       rv = CORBA_string_dup (txt);
187       g_free (txt);
188     }
189   else
190     rv = CORBA_string_dup ("");
191
192   return rv;
193 }
194
195
196 static CORBA_unsigned_long
197 impl_getCharacterAtOffset (PortableServer_Servant servant,
198                            const CORBA_long offset,
199                            CORBA_Environment *ev)
200 {
201   AtkText *text = get_text_from_servant (servant);
202
203   g_return_val_if_fail (text != NULL, 0);
204
205   return atk_text_get_character_at_offset (text, offset);
206 }
207
208
209 static CORBA_string
210 impl_getTextBeforeOffset (PortableServer_Servant servant,
211                           const CORBA_long offset,
212                           const
213                           Accessibility_TEXT_BOUNDARY_TYPE
214                           type, CORBA_long * startOffset,
215                           CORBA_long * endOffset,
216                           CORBA_Environment *ev)
217 {
218   gchar *txt;
219   CORBA_char *rv;
220   gint intStartOffset, intEndOffset;
221   AtkText *text = get_text_from_servant (servant);
222
223   g_return_val_if_fail (text != NULL, CORBA_string_dup (""));
224
225   txt = atk_text_get_text_before_offset (text,
226                                          offset, (AtkTextBoundary) type,
227                                          &intStartOffset, &intEndOffset);
228
229   *startOffset = intStartOffset;
230   *endOffset = intEndOffset;
231
232   if (txt)
233     {
234       rv = CORBA_string_dup (txt);
235       g_free (txt);
236     }
237   else
238     rv = CORBA_string_dup ("");
239
240   return rv;
241 }
242
243
244 static CORBA_long
245 impl__get_caretOffset (PortableServer_Servant servant,
246                      CORBA_Environment *ev)
247 {
248   AtkText *text = get_text_from_servant (servant);
249
250   g_return_val_if_fail (text != NULL, -1);
251
252   return atk_text_get_caret_offset (text);
253 }
254
255
256 static CORBA_char *
257 _string_from_attribute_set (AtkAttributeSet *set)
258 {
259   gchar *attributes, *tmp, *tmp2;
260   CORBA_char *rv;
261   GSList *cur_attr;
262   AtkAttribute *at;
263   
264   attributes = g_strdup ("");
265   cur_attr = (GSList *) set;
266   while (cur_attr)
267     {
268       at = (AtkAttribute *) cur_attr->data;
269       tmp = g_strdup_printf ("%s%s:%s%s",
270                              ((GSList *)(set) == cur_attr) ? "" : " ",
271                              at->name, at->value,
272                              (cur_attr->next) ? ";" : "");
273       tmp2 = g_strconcat (attributes, tmp, NULL);
274       g_free (tmp);
275       g_free (attributes);
276       attributes = tmp2;
277       cur_attr = cur_attr->next;
278     }
279   rv = CORBA_string_dup (attributes);
280   g_free (attributes);
281   return rv;
282 }
283
284
285
286 static CORBA_string
287 impl_getAttributes (PortableServer_Servant servant,
288                     const CORBA_long offset,
289                     CORBA_long * startOffset,
290                     CORBA_long * endOffset,
291                     CORBA_Environment *ev)
292 {
293   AtkAttributeSet *set;
294   gint intstart_offset, intend_offset;
295   CORBA_char *rv;
296   AtkText *text = get_text_from_servant (servant);
297
298   g_return_val_if_fail (text != NULL, CORBA_string_dup (""));
299
300   set = atk_text_get_run_attributes (text, offset,
301                                      &intstart_offset, &intend_offset);
302   *startOffset = intstart_offset;
303   *endOffset = intend_offset;
304   rv = _string_from_attribute_set (set);
305   atk_attribute_set_free (set);
306   return rv;  
307 }
308
309
310 static void 
311 impl_getCharacterExtents (PortableServer_Servant servant,
312                           const CORBA_long offset, CORBA_long * x,
313                           CORBA_long * y, CORBA_long * width,
314                           CORBA_long * height,
315                           const CORBA_short coordType,
316                           CORBA_Environment *ev)
317 {
318   AtkText *text = get_text_from_servant (servant);
319   gint ix, iy, iw, ih;
320
321   g_return_if_fail (text != NULL);
322
323   atk_text_get_character_extents (
324           text, offset,
325           &ix, &iy, &iw, &ih,
326           (AtkCoordType) coordType);
327   *x = ix;
328   *y = iy;
329   *width = iw;
330   *height = ih;
331 }
332
333
334 static CORBA_long
335 impl__get_characterCount (PortableServer_Servant servant,
336                           CORBA_Environment    *ev)
337 {
338   AtkText *text = get_text_from_servant (servant);
339
340   g_return_val_if_fail (text != NULL, 0);
341
342   return atk_text_get_character_count (text);
343 }
344
345
346 static CORBA_long
347 impl_getOffsetAtPoint (PortableServer_Servant servant,
348                        const CORBA_long x, const CORBA_long y,
349                        const CORBA_short coordType,
350                        CORBA_Environment *ev)
351 {
352   AtkText *text = get_text_from_servant (servant);
353
354   g_return_val_if_fail (text != NULL, -1);
355
356   return atk_text_get_offset_at_point (text,
357                                   x, y,
358                                   (AtkCoordType) coordType);
359 }
360
361
362 static CORBA_long
363 impl_getNSelections (PortableServer_Servant servant,
364                      CORBA_Environment *ev)
365 {
366   AtkText *text = get_text_from_servant (servant);
367
368   g_return_val_if_fail (text != NULL, 0);
369
370   return atk_text_get_n_selections (text);
371 }
372
373
374 static void 
375 impl_getSelection (PortableServer_Servant servant,
376                    const CORBA_long selectionNum,
377                    CORBA_long * startOffset, CORBA_long * endOffset,
378                    CORBA_Environment *ev)
379 {
380   AtkText *text = get_text_from_servant (servant);
381   gint intStartOffset, intEndOffset;
382   
383   g_return_if_fail (text != NULL);
384
385   /* atk_text_get_selection returns gchar* which we discard */
386   g_free (atk_text_get_selection (text, selectionNum,
387                                   &intStartOffset, &intEndOffset));
388   
389   *startOffset = intStartOffset;
390   *endOffset = intEndOffset;
391 }
392
393
394 static CORBA_boolean
395 impl_addSelection (PortableServer_Servant servant,
396                    const CORBA_long startOffset,
397                    const CORBA_long endOffset,
398                    CORBA_Environment *ev)
399 {
400   AtkText *text = get_text_from_servant (servant);
401
402   g_return_val_if_fail (text != NULL, FALSE);
403
404   return atk_text_add_selection (text,
405                             startOffset, endOffset);
406 }
407
408
409 static CORBA_boolean
410 impl_removeSelection (PortableServer_Servant servant,
411                       const CORBA_long selectionNum,
412                       CORBA_Environment *ev)
413 {
414   AtkText *text = get_text_from_servant (servant);
415
416   g_return_val_if_fail (text != NULL, FALSE);
417
418   return atk_text_remove_selection (text, selectionNum);
419 }
420
421
422 static CORBA_boolean
423 impl_setSelection (PortableServer_Servant servant,
424                    const CORBA_long selectionNum,
425                    const CORBA_long startOffset,
426                    const CORBA_long endOffset,
427                    CORBA_Environment *ev)
428 {
429   AtkText *text = get_text_from_servant (servant);
430
431   g_return_val_if_fail (text != NULL, FALSE);
432
433   return atk_text_set_selection (text,
434                             selectionNum, startOffset, endOffset);
435 }
436
437
438 static CORBA_boolean
439 impl_setCaretOffset (PortableServer_Servant servant,
440                      const CORBA_long value,
441                      CORBA_Environment *ev)
442 {
443   AtkText *text = get_text_from_servant (servant);
444
445   g_return_val_if_fail (text != NULL, FALSE);
446
447   return atk_text_set_caret_offset (text, value);
448 }
449
450 #define SPI_TEXT_MIN_RANGE_FOR_LINE_CHECK 6
451
452 static void
453 impl_getRangeExtents(PortableServer_Servant servant,
454                      const CORBA_long startOffset,
455                      const CORBA_long endOffset,
456                      CORBA_long * x, CORBA_long * y,
457                      CORBA_long * width,
458                      CORBA_long * height,
459                      const CORBA_short coordType,
460                      CORBA_Environment * ev)
461 {
462   AtkText *text = get_text_from_servant (servant);
463   SpiTextRect cbounds, bounds;
464   int i;
465
466   g_return_if_fail (text != NULL);
467   
468   /* no equivalent ATK API yet, must do the hard way. :-( */
469   for (i = startOffset; i > endOffset; i++) 
470     {
471       atk_text_get_character_extents (text, i,
472                                       &cbounds.x, &cbounds.y, &cbounds.w, &cbounds.h,
473                                       (AtkCoordType) coordType);
474       _spi_text_rect_union (&bounds, &cbounds);
475     }
476
477   *x = bounds.x;
478   *y = bounds.y;
479   *width = bounds.w;
480   *height = bounds.h;
481 }
482
483 static Accessibility_Text_RangeList *
484 _spi_text_range_seq_from_gslist (GSList *range_list) 
485
486   Accessibility_Text_RangeList *rangeList = 
487     Accessibility_Text_RangeList__alloc ();
488   int i, len = g_slist_length (range_list);
489   GSList *list = range_list;
490
491   rangeList->_length = len;
492   rangeList->_buffer = Accessibility_Text_RangeList_allocbuf (len);
493   for (i = 0; i < len; ++i) 
494     {
495       memcpy (&rangeList->_buffer[i], list->data, sizeof (Accessibility_Text_Range));
496       spi_init_any_nil (&rangeList->_buffer[i].data);
497       g_free (list->data);
498       list = g_slist_next (range_list);
499     }
500   g_slist_free (range_list);
501
502   return rangeList;
503 }
504
505 static gboolean
506 _spi_bounds_contain (SpiTextRect *clip, SpiTextRect *cbounds, 
507                      Accessibility_TEXT_CLIP_TYPE xClipType, 
508                      Accessibility_TEXT_CLIP_TYPE yClipType)
509 {
510   gint clipx2 = clip->x + clip->w;
511   gint clipy2 = clip->y + clip->h;
512   gint charx2 = cbounds->x + cbounds->w;
513   gint chary2 = cbounds->y + cbounds->h;
514   gboolean x_min_ok, y_min_ok, x_max_ok, y_max_ok;
515
516   x_min_ok = (cbounds->x >= clip->x) || 
517     ((charx2 >= clip->x) && 
518      ((xClipType == Accessibility_TEXT_CLIP_NONE) || 
519       (xClipType == Accessibility_TEXT_CLIP_MAX)));
520   x_max_ok = (charx2 <= clipx2) || 
521     ((cbounds->x <= clipx2) && 
522      ((xClipType == Accessibility_TEXT_CLIP_NONE) || 
523       (xClipType == Accessibility_TEXT_CLIP_MIN)));
524   y_min_ok = (cbounds->y >= clip->y) || 
525     ((chary2 >= clip->y) && 
526      ((yClipType == Accessibility_TEXT_CLIP_NONE) || 
527       (yClipType == Accessibility_TEXT_CLIP_MAX)));
528   y_max_ok = (chary2 <= clipy2) || 
529     ((cbounds->y <= clipy2) && 
530      ((yClipType == Accessibility_TEXT_CLIP_NONE) || 
531       (yClipType == Accessibility_TEXT_CLIP_MIN)));
532   
533   if (x_min_ok && y_min_ok && x_max_ok && y_max_ok)
534     return TRUE;
535   else 
536     return FALSE;
537 }
538
539 Accessibility_Text_RangeList *
540 impl_getBoundedRanges(PortableServer_Servant servant,
541                       const CORBA_long x,
542                       const CORBA_long y,
543                       const CORBA_long width,
544                       const CORBA_long height,
545                       const CORBA_short coordType,
546                       const Accessibility_TEXT_CLIP_TYPE xClipType,
547                       const Accessibility_TEXT_CLIP_TYPE yClipType, 
548                       CORBA_Environment * ev)
549 {
550   AtkText *text = get_text_from_servant (servant);
551   GSList *range_list = NULL;
552   SpiTextRect clip;
553   int startOffset = 0, endOffset = atk_text_get_character_count (text);
554   int curr_offset;
555   gint minLineStart, minLineEnd, maxLineStart, maxLineEnd;
556   long bounds_min_offset;
557
558   clip.x = x;
559   clip.y = y;
560   clip.w = width;
561   clip.h = height;
562
563   /* for horizontal text layouts, at least, the following check helps. */
564   bounds_min_offset =  atk_text_get_offset_at_point (text, x, y, 
565                                                      (AtkCoordType) coordType);
566   atk_text_get_text_at_offset (text, bounds_min_offset, 
567                                ATK_TEXT_BOUNDARY_LINE_START,
568                                &minLineStart, &minLineEnd);
569   atk_text_get_text_at_offset (text, bounds_min_offset, 
570                                ATK_TEXT_BOUNDARY_LINE_START,
571                                &maxLineStart, &maxLineEnd);
572   startOffset = MIN (minLineStart, maxLineStart);
573   endOffset  = MIN (minLineEnd, maxLineEnd);
574
575   curr_offset = startOffset;
576
577   while (curr_offset < endOffset) 
578     {
579       int offset = startOffset;
580       SpiTextRect cbounds;
581       while (curr_offset < endOffset) 
582         {
583           atk_text_get_character_extents (text, curr_offset, 
584                                           &cbounds.x, &cbounds.y, 
585                                           &cbounds.w, &cbounds.h, 
586                                           (AtkCoordType) coordType);
587           if (!_spi_bounds_contain (&clip, &cbounds, xClipType, yClipType))
588             break;
589           curr_offset++;
590         }
591       /* add the range to our list */
592       if (curr_offset > offset) 
593         {
594           Accessibility_Text_Range *range = g_malloc (sizeof (Accessibility_Text_Range));
595           char *s;
596           range->startOffset = offset;
597           range->endOffset = curr_offset;
598           s = atk_text_get_text (text, offset, curr_offset);
599           range->content = CORBA_string_dup (s ? s : "");
600           range_list = g_slist_append (range_list, range);
601           offset = curr_offset;
602         }
603       offset++;
604     }  
605   return _spi_text_range_seq_from_gslist (range_list); /* frees the GSList too */
606 }
607
608
609 static void
610 spi_text_class_init (SpiTextClass *klass)
611 {
612   POA_Accessibility_Text__epv *epv = &klass->epv;
613
614   /* Initialize epv table */
615
616   epv->getText = impl_getText;
617   epv->getTextAfterOffset = impl_getTextAfterOffset;
618   epv->getCharacterAtOffset = impl_getCharacterAtOffset;
619   epv->getTextAtOffset = impl_getTextAtOffset;
620   epv->getTextBeforeOffset = impl_getTextBeforeOffset;
621   epv->_get_caretOffset = impl__get_caretOffset;
622   epv->getAttributes = impl_getAttributes;
623   epv->getCharacterExtents = impl_getCharacterExtents;
624   epv->_get_characterCount = impl__get_characterCount;
625   epv->getOffsetAtPoint = impl_getOffsetAtPoint;
626   epv->getNSelections = impl_getNSelections;
627   epv->getSelection = impl_getSelection;
628   epv->addSelection = impl_addSelection;
629   epv->removeSelection = impl_removeSelection;
630   epv->setSelection = impl_setSelection;
631   epv->setCaretOffset = impl_setCaretOffset;
632   epv->getRangeExtents = impl_getRangeExtents;
633   epv->getBoundedRanges = impl_getBoundedRanges;
634 }
635
636 static void
637 spi_text_init (SpiText *text)
638 {
639 }
640
641 BONOBO_TYPE_FUNC_FULL (SpiText,
642                        Accessibility_Text,
643                        PARENT_TYPE,
644                        spi_text);
645
646 void
647 spi_text_construct (SpiText *text, AtkObject *obj)
648 {
649   spi_base_construct (SPI_BASE (text), G_OBJECT(obj));
650 }
651
652
653 SpiText *
654 spi_text_interface_new (AtkObject *obj)
655 {
656   SpiText *retval;
657
658   g_return_val_if_fail (ATK_IS_TEXT (obj), NULL);
659
660   retval = g_object_new (SPI_TEXT_TYPE, NULL);
661
662   spi_text_construct (retval, obj);
663
664   return retval;
665 }