2 * AT-SPI - Assistive Technology Service Provider Interface
3 * (Gnome Accessibility Project; http://developer.gnome.org/projects/gap)
5 * Copyright 2001, 2002 Sun Microsystems Inc.,
6 * Copyright 2001, 2002 Ximian, Inc.
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.
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.
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.
27 #include "../cspi/spi-private.h"
32 * Screen Review Algorithm Demonstration, Benchmark, and Test-bed.
36 * * there are bugs in the compositing code.
38 * * brute-force algorithm uses no client-side cache; performance mediocre.
40 * * we don't have good ordering information for the toplevel windows,
41 * and our current heuristic is not guaranteed if
42 * the active window is not always on top (i.e. autoraise is
45 * * can't know about "inaccessible" objects that may be obscuring the
46 * accessible windows (inherent to API-based approach).
48 * * text column alignment is crude since it relies on a hard-coded
49 * (and arbitrary) ratio of horizontal pixels to character columns.
50 * For small proportional fonts, overruns are likely to result in
51 * occasional review lines that exceed the standard length.
56 #undef CHUNK_LIST_DEBUG /* define to get list of text chunks before clip */
58 #define BOUNDS_CONTAIN_X_BOUNDS(b, p) ( ((p).x>=(b).x) &&\
59 (((p).x + (p).width) <= \
60 ((b).x + (b).width)) && \
61 ((b).width > 0) && ((b).height > 0))
63 #define BOUNDS_CONTAIN_Y(b, p) (((p)>=(b)->y) && ((p)<=((b)->y + (b)->height))\
64 && ((b)->width > 0) && ((b)->height > 0))
66 #define CHUNK_BOUNDS_BEFORE_START(c, i) ((i) && ((c)->clip_bounds.x < \
69 #define CHUNK_BOUNDS_END_BEFORE_START(c, i) ((i) && \
70 (((c)->clip_bounds.x + \
71 (c)->clip_bounds.width) < \
74 #define CHUNK_BOUNDS_AFTER_END(c, i) ((i) && ((c)->clip_bounds.x >= \
75 ((i)->clip_bounds.x + \
76 (i)->clip_bounds.width)))
78 #define CHUNK_BOUNDS_SPANS_END(c, i) ((i) && (((c)->clip_bounds.x + \
79 (c)->clip_bounds.width) >= \
80 ((i)->clip_bounds.x + \
81 (i)->clip_bounds.width)))
83 * #define CHUNK_BOUNDS_WITHIN(c, i) ((i) && ((c)->clip_bounds.x >= \
84 * (i)->text_bounds.x) && \
85 * (((c)->clip_bounds.x + (c)->clip_bounds.width) \
86 * <= ((i)->text_bounds.x + (i)->text_bounds.width)))
89 #define IS_CLIPPING_CONTAINER(a) ((a) != SPI_ROLE_PAGE_TAB)
91 static void report_screen_review_line (const AccessibleEvent *event, void *user_data);
93 static gint n_elements_traversed = 0;
94 static AccessibleEventListener *mouseclick_listener;
96 typedef struct _BoundaryRect {
101 AccessibleRole role; /* role of last clipping element */
106 typedef struct _TextChunk {
111 BoundaryRect clip_bounds;
112 BoundaryRect text_bounds;
113 BoundaryRect start_char_bounds;
114 BoundaryRect end_char_bounds;
117 typedef struct _ScreenReviewBuffer {
119 } ScreenReviewBuffer;
121 static gboolean isJava = FALSE;
124 main (int argc, char **argv)
131 gdouble elapsed_time;
133 Accessible *application;
138 mouseclick_listener = SPI_createAccessibleEventListener (
139 report_screen_review_line, NULL);
141 SPI_registerGlobalEventListener (mouseclick_listener,
142 "Gtk:GtkWidget:button-press-event");
143 #undef JAVA_TEST_HACK
144 #ifdef JAVA_TEST_HACK /* Only use this to test Java apps */
145 SPI_registerGlobalEventListener (mouseclick_listener,
146 "object:text-caret-moved");
151 putenv ("AT_BRIDGE_SHUTDOWN=1");
154 * TODO: Add a key event listener that calls test_exit, to
155 * deregister and cleanup appropriately.
161 static inline gboolean
162 chunk_bounds_within (TextChunk *chunk, TextChunk *test_chunk)
164 int x1, x2, tx1, tx2;
167 x1 = chunk->clip_bounds.x;
168 x2 = x1 + chunk->clip_bounds.width;
169 tx1 = test_chunk->clip_bounds.x;
170 tx2 = tx1 + test_chunk->clip_bounds.width;
171 gtx1 = (chunk->clip_bounds.x >= test_chunk->clip_bounds.x);
172 ltx2 = (chunk->clip_bounds.x + chunk->clip_bounds.width
173 <= test_chunk->clip_bounds.x + test_chunk->clip_bounds.width);
177 #define CHUNK_BOUNDS_WITHIN(a, b) chunk_bounds_within(a, b)
179 static BoundaryRect **
180 clip_bounds_clone (BoundaryRect *bounds[])
183 BoundaryRect **bounds_clone;
184 bounds_clone = (BoundaryRect **)
185 g_new0 (gpointer, SPI_LAYER_LAST_DEFINED);
186 for (i = 1; i < SPI_LAYER_LAST_DEFINED; ++i) {
187 bounds_clone[i] = g_new0 (BoundaryRect, 1);
188 *bounds_clone[i] = *bounds[i];
194 boundary_clip (BoundaryRect *bounds, BoundaryRect *clipBounds)
196 int x2 = bounds->x + bounds->width;
197 int y2 = bounds->y + bounds->height;
199 fprintf (stderr, "bclip %d-%d, %d-%d; ",
201 clipBounds->x, clipBounds->x+clipBounds->width);
203 bounds->x = MAX (bounds->x, clipBounds->x);
204 bounds->y = MAX (bounds->y, clipBounds->y);
205 x2 = MIN (x2, clipBounds->x + clipBounds->width);
206 y2 = MIN (y2, clipBounds->y + clipBounds->height);
207 bounds->width = MAX (x2 - bounds->x, 0);
208 bounds->height = MAX (y2 - bounds->y, 0);
209 if (!bounds->width || !bounds->height)
210 bounds->isEmpty = TRUE;
211 if (IS_CLIPPING_CONTAINER (bounds->role)) {
212 *clipBounds = *bounds;
215 fprintf (stderr, "%d-%d\n",
216 bounds->x, bounds->x+bounds->width);
221 boundary_xclip_head (BoundaryRect *bounds, BoundaryRect *clipBounds)
224 int cx2 = clipBounds->x + clipBounds->width;
225 if (cx2 < bounds->x) return;
226 x2 = bounds->x + bounds->width;
227 if (cx2 <= x2) bounds->x = cx2;
228 bounds->width = MAX (0, x2 - cx2);
232 boundary_xclip_tail (BoundaryRect *bounds, BoundaryRect *clipBounds)
234 int x2 = bounds->x + bounds->width;
235 if (clipBounds->x > x2) return;
236 bounds->width = MAX (0, clipBounds->x - bounds->x);
240 text_chunk_copy (TextChunk *chunk)
242 TextChunk *copy = g_new0 (TextChunk, 1);
244 if (chunk->string) copy->string = g_strdup (chunk->string);
245 if (copy->source) Accessible_ref (copy->source);
250 text_chunk_tail_clip (TextChunk *bottom, TextChunk *top)
253 fprintf (stderr, "bottom %d-%d, top %d-%d;",
254 bottom->clip_bounds.x,
255 bottom->clip_bounds.x + bottom->clip_bounds.width,
257 top->clip_bounds.x + top->clip_bounds.width);
259 boundary_xclip_tail (&bottom->text_bounds, &top->clip_bounds);
260 boundary_xclip_tail (&bottom->clip_bounds, &top->clip_bounds);
261 bottom->text_bounds.isClipped = TRUE;
262 bottom->clip_bounds.isClipped = TRUE;
264 fprintf (stderr, "result %d-%d\n",
265 bottom->clip_bounds.x,
266 bottom->clip_bounds.x + bottom->clip_bounds.width);
271 text_chunk_head_clip (TextChunk *bottom, TextChunk *top)
274 fprintf (stderr, "bottom %d-%d, top %d-%d;",
275 bottom->clip_bounds.x,
276 bottom->clip_bounds.x + bottom->clip_bounds.width,
278 top->clip_bounds.x + top->clip_bounds.width);
280 boundary_xclip_head (&bottom->text_bounds, &top->clip_bounds);
281 boundary_xclip_head (&bottom->clip_bounds, &top->clip_bounds);
282 bottom->text_bounds.isClipped = TRUE;
283 bottom->clip_bounds.isClipped = TRUE;
285 fprintf (stderr, "result %d-%d\n",
286 bottom->clip_bounds.x,
287 bottom->clip_bounds.x + bottom->clip_bounds.width);
292 text_chunk_split_insert (GList *chunk_list, GList *iter, TextChunk *chunk)
294 TextChunk *iter_chunk = iter->data;
295 TextChunk *iter_copy = text_chunk_copy (iter_chunk);
296 /* TODO: FIXME something is wrong here */
298 fprintf (stderr, "***clip insert of %s into %s\n",
299 chunk->string ? chunk->string : "<null>",
300 iter_chunk->string ? iter_chunk->string : "<null>");
302 chunk_list = g_list_insert_before (chunk_list, iter, iter_copy);
303 text_chunk_tail_clip (iter_copy, chunk);
304 chunk_list = g_list_insert_before (chunk_list, iter, chunk);
305 text_chunk_head_clip (iter_chunk, chunk);
309 /* #define PRINT_CHUNK_DEBUG(a, b, c, d) print_chunk_debug(a, b, c, d) */
311 #define PRINT_CHUNK_DEBUG(a, b, c, d)
313 #ifdef PRINT_CHUNK_DEBUG
315 print_chunk_debug (TextChunk *chunk, char *opname, GList *prev, GList *next)
317 fprintf (stderr, "%sing chunk %s between %s and %s; %d-%d\n",
320 (prev ? ((TextChunk *) prev->data)->string : "<null>"),
321 (next ? ((TextChunk *) next->data)->string : "<null>"),
322 chunk->clip_bounds.x,
323 chunk->text_bounds.x + chunk->text_bounds.width);
328 text_chunk_list_head_clip (GList *text_chunk_list,
332 GList *target, *iter = next, *prev;
334 /* if (chunk->string && strlen (chunk->string)) { */
336 g_list_insert_before (text_chunk_list, next, chunk);
338 while (iter && CHUNK_BOUNDS_SPANS_END (chunk, (TextChunk *)iter->data)) {
340 fprintf (stderr, "deleting %s\n",
341 ((TextChunk *)iter->data)->string);
346 g_list_delete_link (text_chunk_list, target);
348 if (iter && !CHUNK_BOUNDS_END_BEFORE_START (chunk,
349 (TextChunk *)iter->data)) {
350 text_chunk_head_clip ((TextChunk *)iter->data,
354 !CHUNK_BOUNDS_AFTER_END (
356 (TextChunk *)prev->data)) {
357 text_chunk_tail_clip (
358 (TextChunk *)prev->data,
362 return text_chunk_list;
366 text_chunk_list_clip_and_insert (GList *text_chunk_list,
372 /* if (chunk->string) */
373 fprintf (stderr, "clip-and-insert for %s, between %s (%d) and %s (%d)\n",
375 (prev && ((TextChunk *)prev->data)->string ? ((TextChunk *)prev->data)->string : "<null>"),
376 (prev ? ((TextChunk *)prev->data)->text_bounds.x : -1),
377 (next && ((TextChunk *)next->data)->string ? ((TextChunk *)next->data)->string : "<null>"),
378 (next ? ((TextChunk *)next->data)->text_bounds.x : -1));
381 if (!prev && !next) { /* first element in, no clip needed */
383 g_list_append (text_chunk_list, chunk);
384 PRINT_CHUNK_DEBUG (chunk, "append",
387 } else { /* check for clip with prev */
388 /* if we split the prev */
390 CHUNK_BOUNDS_WITHIN (chunk, (TextChunk *) prev->data)) {
392 text_chunk_split_insert (
396 /* we split the 'next' element */
397 if (CHUNK_BOUNDS_WITHIN (chunk, (TextChunk *)next->data)) {
399 text_chunk_split_insert (text_chunk_list,
402 /* do an insert + head clip */
404 text_chunk_list_head_clip (
410 if (!CHUNK_BOUNDS_AFTER_END (chunk,
411 (TextChunk *) prev->data)) {
412 text_chunk_tail_clip (prev->data, chunk);
415 g_list_append (text_chunk_list, chunk);
418 return text_chunk_list;
422 text_chunk_list_insert_chunk (GList *text_chunk_list, TextChunk *chunk)
424 GList *iter = g_list_first (text_chunk_list);
425 TextChunk *iter_chunk = NULL;
427 if (iter) iter_chunk = (TextChunk *) iter->data;
428 /* if we're ahead of the current element */
431 text_chunk_list_clip_and_insert (text_chunk_list,
436 } else if (CHUNK_BOUNDS_BEFORE_START (chunk, iter_chunk)) {
438 text_chunk_list_clip_and_insert (text_chunk_list,
443 } else if (!iter->next ) {
445 text_chunk_list_clip_and_insert (text_chunk_list,
451 if (iter) iter = iter->next;
453 return text_chunk_list;
457 review_buffer_get_text_chunk (ScreenReviewBuffer *reviewBuffer,
458 Accessible *accessible, BoundaryRect *bounds,
459 int screen_x, int screen_y)
461 AccessibleText *text = NULL;
463 TextChunk *text_chunk;
464 BoundaryRect start_char_bounds, end_char_bounds;
470 role = Accessible_getRole (accessible);
471 text_chunk = g_new0 (TextChunk, 1);
472 text_chunk->clip_bounds = *bounds;
473 text_chunk->source = accessible;
474 Accessible_ref (accessible);
475 if (Accessible_isText (accessible)) {
476 text = Accessible_getText (accessible);
477 offset = AccessibleText_getOffsetAtPoint (text,
480 SPI_COORD_TYPE_SCREEN);
481 s = AccessibleText_getTextAtOffset (text, offset,
482 SPI_TEXT_BOUNDARY_LINE_START,
485 AccessibleText_getCharacterExtents (
487 &text_chunk->start_char_bounds.x,
488 &text_chunk->start_char_bounds.y,
489 &text_chunk->start_char_bounds.width,
490 &text_chunk->start_char_bounds.height,
491 SPI_COORD_TYPE_SCREEN);
493 fprintf (stderr, "%s: start char (%d) x, width %d %d; ",
496 text_chunk->start_char_bounds.x,
497 text_chunk->start_char_bounds.width);
499 if (s && strlen (s) && s[strlen (s) - 1] == '\n')
501 AccessibleText_getCharacterExtents (
503 &text_chunk->end_char_bounds.x,
504 &text_chunk->end_char_bounds.y,
505 &text_chunk->end_char_bounds.width,
506 &text_chunk->end_char_bounds.height,
507 SPI_COORD_TYPE_SCREEN);
509 fprintf (stderr, "end char (%d) x, width %d %d\n",
511 text_chunk->end_char_bounds.x,
512 text_chunk->end_char_bounds.width);
515 text_chunk->text_bounds.x = MIN (text_chunk->start_char_bounds.x,
516 text_chunk->end_char_bounds.x);
517 text_chunk->text_bounds.y = MIN (text_chunk->start_char_bounds.y,
518 text_chunk->end_char_bounds.y);
519 x2 = MAX (text_chunk->start_char_bounds.x +
520 text_chunk->start_char_bounds.width,
521 text_chunk->end_char_bounds.x +
522 text_chunk->end_char_bounds.width);
523 text_chunk->text_bounds.width = x2 - text_chunk->text_bounds.x;
524 y2 = MAX (text_chunk->start_char_bounds.y +
525 text_chunk->start_char_bounds.height,
526 text_chunk->end_char_bounds.y +
527 text_chunk->end_char_bounds.height);
528 text_chunk->text_bounds.height = y2 - text_chunk->text_bounds.y;
529 text_chunk->start_offset = start;
530 text_chunk->end_offset = end;
531 AccessibleText_unref (text);
533 if (role == SPI_ROLE_PUSH_BUTTON ||
534 role == SPI_ROLE_CHECK_BOX ||
535 role == SPI_ROLE_LABEL ||
536 role == SPI_ROLE_MENU ||
537 role == SPI_ROLE_MENU_ITEM) { /* don't like this
538 special casing :-( */
539 s = Accessible_getName (accessible);
540 /* use name instead */
541 text_chunk->text_bounds = text_chunk->clip_bounds;
542 text_chunk->start_offset = 0;
543 text_chunk->end_offset = strlen (s);
546 if (text_chunk->text_bounds.x < text_chunk->clip_bounds.x) {
547 text_chunk->text_bounds.x = text_chunk->clip_bounds.x;
548 text_chunk->text_bounds.isClipped = TRUE;
550 if ((text_chunk->text_bounds.x +
551 text_chunk->text_bounds.width)
552 > (text_chunk->clip_bounds.x +
553 text_chunk->clip_bounds.width)) {
554 text_chunk->text_bounds.width =
555 MAX (0, (text_chunk->clip_bounds.x +
556 text_chunk->clip_bounds.width) -
557 text_chunk->text_bounds.x);
558 text_chunk->text_bounds.isClipped = TRUE;
560 if (!BOUNDS_CONTAIN_Y (&text_chunk->text_bounds,
563 fprintf (stderr, "%s out of bounds (%d-%d)\n", s,
564 text_chunk->text_bounds.y,
565 text_chunk->text_bounds.y +
566 text_chunk->text_bounds.height);
569 text_chunk->start_offset = offset;
570 text_chunk->end_offset = offset;
572 if (s && strlen (s)) {
573 if (s[strlen(s)-1] == '\n') s[strlen(s)-1] = ' ';
574 /* XXX: if last char is newline, aren't its bounds wrong now? */
575 text_chunk->string = s;
577 fprintf (stderr, "%s, bounds %d-%d; clip %d-%d\n",
579 text_chunk->text_bounds.x,
580 text_chunk->text_bounds.x+text_chunk->text_bounds.width,
581 text_chunk->clip_bounds.x,
582 text_chunk->clip_bounds.x+text_chunk->clip_bounds.width);
585 text_chunk->string = NULL;
591 debug_chunk_list (GList *iter)
595 chunk = (TextChunk *)iter->data;
596 fprintf (stderr, "Chunk %s, clip %d-%d, text %d-%d\n",
598 chunk->clip_bounds.x,
599 chunk->clip_bounds.x + chunk->clip_bounds.width,
600 chunk->text_bounds.x,
601 chunk->text_bounds.x + chunk->text_bounds.width);
607 clip_into_buffers (Accessible *accessible, BoundaryRect* parentClipBounds[],
608 ScreenReviewBuffer *reviewBuffers[],
609 int screen_x, int screen_y)
611 int n_children, child_n;
614 BoundaryRect** clip_bounds;
615 TextChunk *text_chunk;
616 AccessibleComponent *component;
620 clip_bounds = clip_bounds_clone (parentClipBounds);
621 if (Accessible_isComponent (accessible)) {
622 role = Accessible_getRole (accessible);
623 component = Accessible_getComponent (accessible);
624 layer = AccessibleComponent_getLayer (component);
625 bounds = *clip_bounds[layer];
626 if (!bounds.isEmpty) {
627 AccessibleComponent_getExtents (component,
632 SPI_COORD_TYPE_SCREEN);
634 if (clip_bounds[layer])
635 boundary_clip (&bounds, clip_bounds[layer]);
636 if (BOUNDS_CONTAIN_Y (&bounds, screen_y)) {
637 text_chunk = review_buffer_get_text_chunk (
638 reviewBuffers[layer], accessible, &bounds,
640 reviewBuffers[layer]->text_chunks =
641 text_chunk_list_insert_chunk (
642 reviewBuffers[layer]->text_chunks,
646 IS_CLIPPING_CONTAINER (bounds.role);
649 Accessible_unref (component);
652 * we always descend into children in case they are in a higher layer
653 * this can of course be optimized for the topmost layer...
654 * but nobody uses that one! (SPI_LAYER_OVERLAY)
656 n_children = Accessible_getChildCount (accessible);
657 for (child_n = 0; child_n < n_children; ++child_n) {
658 child = Accessible_getChildAtIndex (accessible, child_n);
659 clip_into_buffers (child, clip_bounds, reviewBuffers, screen_x, screen_y);
660 Accessible_unref (child);
662 /* TODO: free the parent clip bounds */
665 #undef CHARACTER_CLIP_DEBUG
668 text_chunk_get_clipped_substring_by_char (TextChunk *chunk, int start, int end)
670 BoundaryRect char_bounds;
673 GString *string = g_string_new ("");
675 AccessibleText *text = Accessible_getText (chunk->source);
676 for (i = start; i < end; ++i) {
677 AccessibleText_getCharacterExtents (text,
683 SPI_COORD_TYPE_SCREEN);
684 #ifdef CHARACTER_CLIP_DEBUG
685 fprintf (stderr, "testing %d-%d against %d-%d\n",
686 char_bounds.x, char_bounds.x+char_bounds.width,
687 chunk->text_bounds.x,
688 chunk->text_bounds.x + chunk->text_bounds.width);
690 if (BOUNDS_CONTAIN_X_BOUNDS (chunk->text_bounds,
692 c = AccessibleText_getCharacterAtOffset (
695 fprintf (stderr, "[%c]", c);
697 g_string_append_unichar (string, c);
700 AccessibleText_unref (text);
702 g_string_free (string, FALSE);
708 * Note: this routine shouldn't have to do as much as it currently does,
709 * but at the moment it works around another bug (probably one in this
713 string_strip_newlines (char *s, long offset, long *start_offset, long *end_offset)
716 char *word_start = s;
717 /* FIXME: potential memory leak here */
718 for (i=0; s && s[i]; ++i)
720 if (s [i] == '\n' && i > (offset - *start_offset) ) {
722 *end_offset = *start_offset + i;
724 } else if (s [i] == '\n') {
725 word_start = &s[i + 1];
732 string_guess_clip (TextChunk *chunk)
735 char *s = NULL, *sp = chunk->string, *ep;
736 long start_offset, end_offset, len;
738 AccessibleComponent *component =
739 Accessible_getComponent (chunk->source);
740 ep = sp + (strlen (sp));
741 len = g_utf8_strlen (chunk->string, -1);
743 AccessibleComponent_getExtents (component,
746 SPI_COORD_TYPE_SCREEN);
747 start_offset = len * (chunk->text_bounds.x - b.x) / b.width;
748 end_offset = len * (chunk->text_bounds.x +
749 chunk->text_bounds.width - b.x) / b.width;
750 fprintf (stderr, "String len %d, clipped to %d-%d\n",
751 len, start_offset, end_offset);
752 len = end_offset - start_offset;
753 sp = g_utf8_offset_to_pointer (chunk->string, start_offset);
754 ep = g_utf8_offset_to_pointer (chunk->string, end_offset);
756 s = g_new0 (char, ep - sp + 1);
757 s = g_utf8_strncpy (s, sp, len);
759 g_assert (g_utf8_validate (s, -1, NULL));
765 text_chunk_get_clipped_string (TextChunk *chunk)
767 char *s, *string = "";
769 long start = chunk->start_offset, end = chunk->end_offset;
770 long word_start, word_end, range_end;
771 BoundaryRect start_bounds, end_bounds;
772 gboolean start_inside, end_inside;
773 if (!chunk->text_bounds.isClipped || !chunk->string)
774 string = chunk->string;
775 else if (chunk->source && Accessible_isText (chunk->source)) {
776 /* while words at offset lie within the bounds, add them */
777 AccessibleText *text = Accessible_getText (chunk->source);
779 fprintf (stderr, "clipping %s\n", chunk->string);
782 s = AccessibleText_getTextAtOffset (text,
784 SPI_TEXT_BOUNDARY_WORD_END,
787 range_end = word_end;
788 s = string_strip_newlines (s, start, &word_start, &word_end);
789 AccessibleText_getCharacterExtents (text,
794 &start_bounds.height,
795 SPI_COORD_TYPE_SCREEN);
796 AccessibleText_getCharacterExtents (text,
802 SPI_COORD_TYPE_SCREEN);
803 start_inside = BOUNDS_CONTAIN_X_BOUNDS (chunk->text_bounds,
805 end_inside = BOUNDS_CONTAIN_X_BOUNDS (chunk->text_bounds,
807 if (start_inside && end_inside) {
808 /* word is contained in bounds */
809 string = g_strconcat (string, s, NULL);
810 } else if (start_inside || end_inside) {
811 /* one end of word is in */
812 if (word_end > end) word_end = end;
813 s = text_chunk_get_clipped_substring_by_char (
815 MAX (word_start, chunk->start_offset),
816 MIN (word_end, chunk->end_offset));
817 string = g_strconcat (string, s, NULL);
821 } while (start < chunk->end_offset);
822 } else { /* we're clipped, but don't implement AccessibleText :-( */
823 /* guess for now, maybe we can do better someday */
824 string = string_guess_clip (chunk);
831 text_chunk_pad_string (TextChunk *chunk, char *string, glong offset, const char *pad_chars)
835 char startbuf[6], padbuf[6], endbuf[6];
836 int pixels_per_column = 6;
837 /* this is an arbitrary pixel-to-textcolumn mapping at present */
840 howmany = g_unichar_to_utf8 (g_utf8_get_char (pad_chars), startbuf);
841 startbuf[howmany] = '\0';
842 g_assert (howmany < 7 && howmany > 0);
843 cp = g_utf8_find_next_char (pad_chars, NULL);
844 howmany = g_unichar_to_utf8 (g_utf8_get_char (cp), padbuf);
845 padbuf[howmany] = '\0';
846 g_assert (howmany < 7 && howmany > 0);
847 cp = g_utf8_find_next_char (cp, NULL);
848 howmany = g_unichar_to_utf8 (g_utf8_get_char (cp), endbuf);
849 endbuf[howmany] = '\0';
850 g_assert (howmany < 7 && howmany > 0);
851 end_padding = chunk->clip_bounds.x / pixels_per_column;
852 while (offset < end_padding - 1) {
853 s = g_strconcat (s, padbuf, NULL); /* could be more efficient */
856 s = g_strconcat (s, startbuf, string, NULL);
857 offset += g_utf8_strlen (string, -1) + 1;
858 end_padding = chunk->text_bounds.x / pixels_per_column;
859 while (offset < end_padding) {
860 s = g_strconcat (s, padbuf, NULL); /* could be more efficient */
863 end_padding = (chunk->clip_bounds.x + chunk->clip_bounds.width) /
865 while (offset < end_padding - 1) {
866 s = g_strconcat (s, padbuf, NULL); /* could be more efficient */
869 s = g_strconcat (s, endbuf, NULL);
874 text_chunk_to_string (TextChunk *chunk, glong offset)
878 s = text_chunk_get_clipped_string (chunk);
879 if (chunk->clip_bounds.role == SPI_ROLE_PUSH_BUTTON) {
880 s = text_chunk_pad_string (chunk, s, offset, "[ ]");
881 } else if (chunk->clip_bounds.role == SPI_ROLE_FRAME) {
882 s = text_chunk_pad_string (chunk, s, offset, "| |");
883 } else if (chunk->clip_bounds.role == SPI_ROLE_TEXT) {
884 s = text_chunk_pad_string (chunk, s, offset, "\" \"");
886 s = text_chunk_pad_string (chunk, s, offset, " ");
893 text_chunk_list_to_string (GList *iter)
897 TextChunk *chunk = NULL;
900 chunk = (TextChunk *)iter->data;
902 string = text_chunk_to_string (chunk, g_utf8_strlen (s, -1));
904 s = g_strconcat (s, string, NULL);
908 s = g_strconcat (s, "|", NULL);
912 #define COMPOSITE_DEBUG
915 toplevel_composite (ScreenReviewBuffer *buffers[])
918 GList *chunk_list, *iter;
921 chunk_list = buffers[SPI_LAYER_CANVAS]->text_chunks;
922 for (i = SPI_LAYER_MDI; i < SPI_LAYER_OVERLAY; ++i) {
923 iter = buffers[i]->text_chunks;
924 #ifdef COMPOSITE_DEBUG
925 fprintf (stderr, "layer %d has %d chunks\n",
926 i, g_list_length (iter));
929 chunk = (TextChunk *) iter->data;
931 #ifdef COMPOSITE_DEBUG
932 fprintf (stderr, "inserting chunk <%s>\n",
933 chunk->string ? chunk->string : "<null>");
936 text_chunk_list_insert_chunk (chunk_list,
945 review_buffer_composite (ScreenReviewBuffer *buffers[])
947 /* TODO: FIXME: something is wrong here, compositing fails */
949 GList *chunk_list, *iter;
951 chunk_list = buffers[SPI_LAYER_BACKGROUND]->text_chunks;
952 for (i = 2; i < SPI_LAYER_LAST_DEFINED; ++i) {
953 if (i == SPI_LAYER_WIDGET) i = SPI_LAYER_OVERLAY;
955 * Q: why skip these layers ?
956 * A: since layers WIDGET, MDI, and POPUP have already been
957 * composited into layer CANVAS for each toplevel before this
960 iter = buffers[i]->text_chunks;
962 fprintf (stderr, "layer %d has %d chunks\n",
963 i, g_list_length (iter));
966 chunk = (TextChunk *) iter->data;
969 fprintf (stderr, "inserting chunk <%s>\n",
970 chunk->string ? chunk->string : "<null>");
973 text_chunk_list_insert_chunk (chunk_list,
980 chunk_list = buffers[SPI_LAYER_WIDGET]->text_chunks;
981 return text_chunk_list_to_string (chunk_list);
985 get_screen_review_line_at (int x, int y)
988 Accessible *desktop, *app, *toplevel, *child;
989 AccessibleComponent *component;
990 AccessibleStateSet *states;
991 GList *toplevels = NULL, *actives = NULL, *iter;
992 ScreenReviewBuffer* reviewBuffers[SPI_LAYER_LAST_DEFINED];
993 BoundaryRect* clip_bounds[SPI_LAYER_LAST_DEFINED];
994 BoundaryRect toplevel_bounds;
995 int n_apps, n_toplevels, n_children, app_n, toplevel_n, child_n;
996 GTimer *timer = g_timer_new ();
999 for (i = 1; i < SPI_LAYER_LAST_DEFINED; ++i) {
1000 reviewBuffers[i] = g_new0 (ScreenReviewBuffer, 1);
1001 clip_bounds[i] = g_new0 (BoundaryRect, 1);
1002 clip_bounds[i]->isClipped = FALSE;
1003 clip_bounds[i]->isEmpty = FALSE;
1006 /* how do we decide which desktop ? */
1007 desktop = SPI_getDesktop (0);
1010 n_apps = Accessible_getChildCount (desktop);
1011 for (app_n = 0; app_n < n_apps; ++app_n) {
1012 /* for each toplevel in app */
1013 app = Accessible_getChildAtIndex (desktop, app_n);
1014 n_toplevels = Accessible_getChildCount (app);
1015 for (toplevel_n = 0; toplevel_n < n_toplevels; ++toplevel_n) {
1016 Accessible *toplevel = Accessible_getChildAtIndex (app, toplevel_n);
1017 if (Accessible_isComponent (toplevel))
1018 toplevels = g_list_prepend (toplevels, toplevel);
1020 Accessible_unref (toplevel);
1021 fprintf (stderr, "warning, app toplevel not a component.\n");
1026 /* sort: at the moment we don't have a good way to sort except to put actives on top */
1027 for (iter = g_list_first (toplevels); iter; iter = iter->next) {
1028 Accessible *toplevel =
1029 (Accessible *) iter->data;
1030 if (AccessibleStateSet_contains (Accessible_getStateSet (toplevel),
1031 SPI_STATE_ACTIVE)) {
1032 actives = g_list_prepend (actives, toplevel);
1036 for (iter = g_list_first (actives); iter; iter = actives->next) {
1037 toplevels = g_list_remove (toplevels, iter->data); /* place at end */
1038 toplevels = g_list_append (toplevels, iter->data);
1040 g_list_free (actives);
1042 /* for each toplevel, ending with the active one(s),
1043 * clip against children, putting results into appropriate charBuffer.
1045 for (iter = g_list_first (toplevels); iter; iter = iter->next) {
1046 toplevel = (Accessible *) iter->data;
1047 if (Accessible_isComponent (toplevel)) {
1048 /* make sure toplevel is visible and not iconified or shaded */
1049 states = Accessible_getStateSet (toplevel);
1050 if (AccessibleStateSet_contains (states, SPI_STATE_VISIBLE)
1051 && !AccessibleStateSet_contains (states, SPI_STATE_ICONIFIED)
1052 || isJava) { /* isJava hack! */
1053 component = Accessible_getComponent (toplevel);
1054 AccessibleComponent_getExtents (component,
1057 &toplevel_bounds.width,
1058 &toplevel_bounds.height,
1059 SPI_COORD_TYPE_SCREEN);
1060 toplevel_bounds.isEmpty = FALSE;
1061 for (i = 1; i < SPI_LAYER_LAST_DEFINED; ++i) {
1062 *clip_bounds[i] = toplevel_bounds;
1064 clip_into_buffers (toplevel, clip_bounds,
1065 reviewBuffers, x, y);
1067 toplevel_composite (reviewBuffers);
1068 #ifdef CHUNK_LIST_DEBUG
1069 fprintf (stderr, "toplevel clip done\n");
1070 debug_chunk_list (reviewBuffers[SPI_LAYER_WIDGET]->text_chunks);
1074 Accessible_unref (toplevel);
1077 string = review_buffer_composite (reviewBuffers);
1079 /* SIMPLE SINGLE-PASS ALGORITHM:*/
1080 /* traverse the tree:
1081 * keep a pointer to outermost instance of each layer
1082 * clip against outermost in same layer
1083 * when this clip occurs, store outermost clipped string in 2d string buffer.
1084 * string buffer may have attributes to mark component bounds, line art,
1085 * or attributes of text being reviewed.
1086 * composite the layers, ignoring NULL chars in the string buffers.
1089 * sibling clip not correct, text may overwrite if siblings intersect onscreen
1090 * length of resulting text buffer may vary!
1093 * no API for ordering toplevels yet, other than knowing which is ACTIVE.
1094 * not much implementation for the LAYER API yet, other than menus.
1096 g_timer_stop (timer);
1097 fprintf (stderr, "elapsed time = %f s\n", g_timer_elapsed (timer, NULL));
1103 report_screen_review_line (const AccessibleEvent *event, void *user_data)
1105 static Display *display = NULL;
1106 int x, y, win_x, win_y;
1107 Window root_return, child_return;
1108 unsigned int mask_return;
1110 if (!display) display = XOpenDisplay (getenv ("DISPLAY"));
1112 * we would prefer to get the x,y info in the above event.
1113 * At the moment we don't get detail params for "toolkit" events,
1114 * so for testing purposes we use XQueryPointer. Actual apps
1115 * probably shouldn't do this.
1117 XQueryPointer (display,
1118 DefaultRootWindow (display),
1119 &root_return, &child_return,
1124 fprintf (stderr, "screen review event %s at %d, %d\n", event->type,
1126 fprintf (stderr, "[%s]\n",
1127 get_screen_review_line_at (x, y));
1133 SPI_deregisterGlobalEventListenerAll (mouseclick_listener);
1134 AccessibleEventListener_unref (mouseclick_listener);