2 * AT-SPI - Assistive Technology Service Provider Interface
3 * (Gnome Accessibility Project; http://developer.gnome.org/projects/gap)
5 * Copyright 2001 Sun Microsystems Inc.
7 * This library is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU Library General Public
9 * License as published by the Free Software Foundation; either
10 * version 2 of the License, or (at your option) any later version.
12 * This library is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * Library General Public License for more details.
17 * You should have received a copy of the GNU Library General Public
18 * License along with this library; if not, write to the
19 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
20 * Boston, MA 02111-1307, USA.
24 #include "../cspi/spi-private.h"
29 * Screen Review Algorithm Demonstration, Benchmark, and Test-bed.
33 * * bounds now fail to include window border decoration
34 * (which we can't really know about yet).
36 * * brute-force algorithm uses no client-side cache; performance mediocre.
38 * * we don't have good ordering information for the toplevel windows,
39 * and our current heuristic is not guaranteed if
40 * the active window is not always on top (i.e. autoraise is
43 * * text-bearing objects that don't implement AccessibleText (such as buttons)
44 * don't get their text clipped since we don't know the character
47 * * can't know about "inaccessible" objects that may be obscuring the
48 * accessible windows (inherent to API-based approach).
50 * * this implementation doesn't worry about text column-alignment, it generates
51 * review lines of more-or-less arbitrary length. The x-coordinate
52 * info is preserved in the reviewBuffers list structures, but the
53 * "buffer-to-string" algorithm (currently) ignores it.
58 #undef CHUNK_LIST_DEBUG /* define to get list of text chunks before clip */
60 #define BOUNDS_CONTAIN_X_BOUNDS(b, p) ( ((p).x>=(b).x) &&\
61 (((p).x + (p).width) <= \
62 ((b).x + (b).width)) && \
63 ((b).width > 0) && ((b).height > 0))
65 #define BOUNDS_CONTAIN_Y(b, p) (((p)>=(b)->y) && ((p)<=((b)->y + (b)->height))\
66 && ((b)->width > 0) && ((b)->height > 0))
68 #define CHUNK_BOUNDS_BEFORE_START(c, i) ((i) && ((c)->clip_bounds.x < \
71 #define CHUNK_BOUNDS_END_BEFORE_START(c, i) ((i) && \
72 (((c)->clip_bounds.x + \
73 (c)->clip_bounds.width) < \
76 #define CHUNK_BOUNDS_AFTER_END(c, i) ((i) && ((c)->clip_bounds.x >= \
77 ((i)->clip_bounds.x + \
78 (i)->clip_bounds.width)))
80 #define CHUNK_BOUNDS_SPANS_END(c, i) ((i) && (((c)->clip_bounds.x + \
81 (c)->clip_bounds.width) >= \
82 ((i)->clip_bounds.x + \
83 (i)->clip_bounds.width)))
85 * #define CHUNK_BOUNDS_WITHIN(c, i) ((i) && ((c)->clip_bounds.x >= \
86 * (i)->text_bounds.x) && \
87 * (((c)->clip_bounds.x + (c)->clip_bounds.width) \
88 * <= ((i)->text_bounds.x + (i)->text_bounds.width)))
91 #define IS_CLIPPING_CONTAINER(a) ((a) != SPI_ROLE_PAGE_TAB)
93 static void report_screen_review_line (const AccessibleEvent *event, void *user_data);
95 static gint n_elements_traversed = 0;
96 static AccessibleEventListener *mouseclick_listener;
98 typedef struct _BoundaryRect {
103 AccessibleRole role; /* role of last clipping element */
108 typedef struct _TextChunk {
110 AccessibleText *source;
113 BoundaryRect clip_bounds;
114 BoundaryRect text_bounds;
115 BoundaryRect start_char_bounds;
116 BoundaryRect end_char_bounds;
119 typedef struct _ScreenReviewBuffer { /* TODO: implement */
121 } ScreenReviewBuffer;
123 static gboolean isJava = FALSE;
126 main (int argc, char **argv)
133 gdouble elapsed_time;
135 Accessible *application;
140 mouseclick_listener = SPI_createAccessibleEventListener (
141 report_screen_review_line, NULL);
143 SPI_registerGlobalEventListener (mouseclick_listener,
144 "Gtk:GtkWidget:button-press-event");
145 #define JAVA_TEST_HACK
146 #ifdef JAVA_TEST_HACK /* Only use this to test Java apps */
147 SPI_registerGlobalEventListener (mouseclick_listener,
148 "object:text-caret-moved");
153 putenv ("AT_BRIDGE_SHUTDOWN=1");
156 * TODO: Add a key event listener that calls test_exit, to
157 * deregister and cleanup appropriately.
163 static inline gboolean
164 chunk_bounds_within (TextChunk *chunk, TextChunk *test_chunk)
166 int x1, x2, tx1, tx2;
169 x1 = chunk->clip_bounds.x;
170 x2 = x1 + chunk->clip_bounds.width;
171 tx1 = test_chunk->clip_bounds.x;
172 tx2 = tx1 + test_chunk->clip_bounds.width;
173 gtx1 = (chunk->clip_bounds.x >= test_chunk->clip_bounds.x);
174 ltx2 = (chunk->clip_bounds.x + chunk->clip_bounds.width
175 <= test_chunk->clip_bounds.x + test_chunk->clip_bounds.width);
179 #define CHUNK_BOUNDS_WITHIN(a, b) chunk_bounds_within(a, b)
181 static BoundaryRect **
182 clip_bounds_clone (BoundaryRect *bounds[])
185 BoundaryRect **bounds_clone;
186 bounds_clone = (BoundaryRect **)
187 g_new0 (gpointer, SPI_LAYER_LAST_DEFINED);
188 for (i = 0; i < SPI_LAYER_LAST_DEFINED; ++i) {
189 bounds_clone[i] = g_new0 (BoundaryRect, 1);
190 *bounds_clone[i] = *bounds[i];
196 boundary_clip (BoundaryRect *bounds, BoundaryRect *clipBounds)
198 int x2 = bounds->x + bounds->width;
199 int y2 = bounds->y + bounds->height;
201 fprintf (stderr, "bclip %d-%d, %d-%d; ",
203 clipBounds->x, clipBounds->x+clipBounds->width);
205 bounds->x = MAX (bounds->x, clipBounds->x);
206 bounds->y = MAX (bounds->y, clipBounds->y);
207 x2 = MIN (x2, clipBounds->x + clipBounds->width);
208 y2 = MIN (y2, clipBounds->y + clipBounds->height);
209 bounds->width = MAX (x2 - bounds->x, 0);
210 bounds->height = MAX (y2 - bounds->y, 0);
211 if (!bounds->width || !bounds->height)
212 bounds->isEmpty = TRUE;
213 if (IS_CLIPPING_CONTAINER (bounds->role)) {
214 *clipBounds = *bounds;
217 fprintf (stderr, "%d-%d\n",
218 bounds->x, bounds->x+bounds->width);
223 boundary_xclip_head (BoundaryRect *bounds, BoundaryRect *clipBounds)
226 int cx2 = clipBounds->x + clipBounds->width;
227 if (cx2 < bounds->x) return;
228 x2 = bounds->x + bounds->width;
229 if (cx2 < x2) bounds->x = cx2;
230 bounds->width = MAX (0, x2 - cx2);
234 boundary_xclip_tail (BoundaryRect *bounds, BoundaryRect *clipBounds)
236 int x2 = bounds->x + bounds->width;
237 if (clipBounds->x > x2) return;
238 bounds->width = MAX (0, clipBounds->x - bounds->x);
242 text_chunk_copy (TextChunk *chunk)
244 TextChunk *copy = g_new0 (TextChunk, 1);
246 if (chunk->string) copy->string = g_strdup (chunk->string);
247 if (copy->source) AccessibleText_ref (copy->source);
252 text_chunk_tail_clip (TextChunk *bottom, TextChunk *top)
255 fprintf (stderr, "bottom %d-%d, top %d-%d;",
256 bottom->clip_bounds.x,
257 bottom->clip_bounds.x + bottom->clip_bounds.width,
259 top->clip_bounds.x + top->clip_bounds.width);
261 boundary_xclip_tail (&bottom->text_bounds, &top->clip_bounds);
262 boundary_xclip_tail (&bottom->clip_bounds, &top->clip_bounds);
263 bottom->text_bounds.isClipped = TRUE;
264 bottom->clip_bounds.isClipped = TRUE;
266 fprintf (stderr, "result %d-%d\n",
267 bottom->clip_bounds.x,
268 bottom->clip_bounds.x + bottom->clip_bounds.width);
273 text_chunk_head_clip (TextChunk *bottom, TextChunk *top)
276 fprintf (stderr, "bottom %d-%d, top %d-%d;",
277 bottom->clip_bounds.x,
278 bottom->clip_bounds.x + bottom->clip_bounds.width,
280 top->clip_bounds.x + top->clip_bounds.width);
282 boundary_xclip_head (&bottom->text_bounds, &top->clip_bounds);
283 boundary_xclip_head (&bottom->clip_bounds, &top->clip_bounds);
284 bottom->text_bounds.isClipped = TRUE;
285 bottom->clip_bounds.isClipped = TRUE;
287 fprintf (stderr, "result %d-%d\n",
288 bottom->clip_bounds.x,
289 bottom->clip_bounds.x + bottom->clip_bounds.width);
294 text_chunk_split_insert (GList *chunk_list, GList *iter, TextChunk *chunk)
296 TextChunk *iter_chunk = iter->data;
297 TextChunk *iter_copy = text_chunk_copy (iter_chunk);
298 /* TODO: FIXME something is wrong here */
300 fprintf (stderr, "***clip insert of %s into %s\n",
301 chunk->string ? chunk->string : "<null>",
302 iter_chunk->string ? iter_chunk->string : "<null>");
304 chunk_list = g_list_insert_before (chunk_list, iter, iter_copy);
305 text_chunk_tail_clip (iter_copy, chunk);
306 chunk_list = g_list_insert_before (chunk_list, iter, chunk);
307 text_chunk_head_clip (iter_chunk, chunk);
311 /* #define PRINT_CHUNK_DEBUG(a, b, c, d) print_chunk_debug(a, b, c, d) */
313 #define PRINT_CHUNK_DEBUG(a, b, c, d)
315 #ifdef PRINT_CHUNK_DEBUG
317 print_chunk_debug (TextChunk *chunk, char *opname, GList *prev, GList *next)
319 fprintf (stderr, "%sing chunk %s between %s and %s; %d-%d\n",
322 (prev ? ((TextChunk *) prev->data)->string : "<null>"),
323 (next ? ((TextChunk *) next->data)->string : "<null>"),
324 chunk->clip_bounds.x,
325 chunk->text_bounds.x + chunk->text_bounds.width);
330 text_chunk_list_head_clip (GList *text_chunk_list,
334 GList *target, *iter = next, *prev;
336 // if (chunk->string && strlen (chunk->string)) {
338 g_list_insert_before (text_chunk_list, next, chunk);
341 if (CHUNK_BOUNDS_SPANS_END (chunk, (TextChunk *)iter->data)) {
343 fprintf (stderr, "deleting %s\n",
344 ((TextChunk *)iter->data)->string);
349 g_list_delete_link (text_chunk_list, target);
351 if (!CHUNK_BOUNDS_END_BEFORE_START (chunk,
352 (TextChunk *)iter->data)) {
353 text_chunk_head_clip ((TextChunk *)iter->data,
357 !CHUNK_BOUNDS_AFTER_END (
359 (TextChunk *)prev->data)) {
360 text_chunk_tail_clip (
361 (TextChunk *)prev->data,
368 return text_chunk_list;
372 text_chunk_list_clip_and_insert (GList *text_chunk_list,
379 fprintf (stderr, "clip-and-insert for %s, between %s and %s\n",
381 (prev && ((TextChunk *)prev->data)->string ? ((TextChunk *)prev->data)->string : "<null>"),
382 (next && ((TextChunk *)next->data)->string ? ((TextChunk *)next->data)->string : "<null>"));
385 if (!prev && !next) { /* first element in, no clip needed */
386 // if (chunk->string && strlen (chunk->string)) {
388 g_list_append (text_chunk_list, chunk);
389 PRINT_CHUNK_DEBUG (chunk, "append",
393 } else { /* check for clip with prev */
394 /* if we split the prev */
396 CHUNK_BOUNDS_WITHIN (chunk, (TextChunk *) prev->data)) {
398 text_chunk_split_insert (
402 /* we split the 'next' element */
403 if (CHUNK_BOUNDS_WITHIN (chunk, (TextChunk *)next->data)) {
405 text_chunk_split_insert (text_chunk_list,
408 /* do an insert + head clip */
410 text_chunk_list_head_clip (
416 if (!CHUNK_BOUNDS_AFTER_END (chunk,
417 (TextChunk *) prev->data)) {
418 text_chunk_tail_clip (prev->data, chunk);
420 // if (chunk->string && strlen (chunk->string)) {
422 g_list_append (text_chunk_list, chunk);
426 return text_chunk_list;
430 text_chunk_list_insert_chunk (GList *text_chunk_list, TextChunk *chunk)
432 GList *iter = g_list_first (text_chunk_list);
433 TextChunk *iter_chunk = NULL;
435 if (iter) iter_chunk = (TextChunk *) iter->data;
436 /* if we're ahead of the current element */
439 text_chunk_list_clip_and_insert (text_chunk_list,
444 } else if (CHUNK_BOUNDS_BEFORE_START (chunk, iter_chunk)) {
446 text_chunk_list_clip_and_insert (text_chunk_list,
451 } else if (!iter->next ) {
453 text_chunk_list_clip_and_insert (text_chunk_list,
459 if (iter) iter = iter->next;
461 return text_chunk_list;
465 review_buffer_get_text_chunk (ScreenReviewBuffer *reviewBuffer,
466 Accessible *accessible, BoundaryRect *bounds,
467 int screen_x, int screen_y)
469 AccessibleText *text = NULL;
471 TextChunk *text_chunk;
472 BoundaryRect start_char_bounds, end_char_bounds;
478 role = Accessible_getRole (accessible);
479 text_chunk = g_new0 (TextChunk, 1);
480 text_chunk->clip_bounds = *bounds;
481 if (Accessible_isText (accessible)) {
482 text = Accessible_getText (accessible);
483 offset = AccessibleText_getOffsetAtPoint (text,
486 SPI_COORD_TYPE_SCREEN);
487 s = AccessibleText_getTextAtOffset (text, offset,
488 SPI_TEXT_BOUNDARY_LINE_START,
491 AccessibleText_getCharacterExtents (
493 &text_chunk->start_char_bounds.x,
494 &text_chunk->start_char_bounds.y,
495 &text_chunk->start_char_bounds.width,
496 &text_chunk->start_char_bounds.height,
497 SPI_COORD_TYPE_SCREEN);
499 fprintf (stderr, "%s: start char (%d) x, width %d %d; ",
502 text_chunk->start_char_bounds.x,
503 text_chunk->start_char_bounds.width);
505 if (s && strlen (s) && s[strlen (s) - 1] == '\n')
507 AccessibleText_getCharacterExtents (
509 &text_chunk->end_char_bounds.x,
510 &text_chunk->end_char_bounds.y,
511 &text_chunk->end_char_bounds.width,
512 &text_chunk->end_char_bounds.height,
513 SPI_COORD_TYPE_SCREEN);
515 fprintf (stderr, "end char (%d) x, width %d %d\n",
517 text_chunk->end_char_bounds.x,
518 text_chunk->end_char_bounds.width);
521 text_chunk->text_bounds.x = MIN (text_chunk->start_char_bounds.x,
522 text_chunk->end_char_bounds.x);
523 text_chunk->text_bounds.y = MIN (text_chunk->start_char_bounds.y,
524 text_chunk->end_char_bounds.y);
525 x2 = MAX (text_chunk->start_char_bounds.x +
526 text_chunk->start_char_bounds.width,
527 text_chunk->end_char_bounds.x +
528 text_chunk->end_char_bounds.width);
529 text_chunk->text_bounds.width = x2 - text_chunk->text_bounds.x;
530 y2 = MAX (text_chunk->start_char_bounds.y +
531 text_chunk->start_char_bounds.height,
532 text_chunk->end_char_bounds.y +
533 text_chunk->end_char_bounds.height);
534 text_chunk->text_bounds.height = y2 - text_chunk->text_bounds.y;
535 text_chunk->start_offset = start;
536 text_chunk->end_offset = end;
537 if (text_chunk->text_bounds.x < text_chunk->clip_bounds.x) {
538 text_chunk->text_bounds.x = text_chunk->clip_bounds.x;
539 text_chunk->text_bounds.isClipped = TRUE;
541 if ((text_chunk->text_bounds.x +
542 text_chunk->text_bounds.width)
543 > (text_chunk->clip_bounds.x +
544 text_chunk->clip_bounds.width)) {
545 text_chunk->text_bounds.width =
546 MAX (0, (text_chunk->clip_bounds.x +
547 text_chunk->clip_bounds.width) -
548 text_chunk->text_bounds.x);
549 text_chunk->text_bounds.isClipped = TRUE;
551 if (!BOUNDS_CONTAIN_Y (&text_chunk->text_bounds,
554 fprintf (stderr, "%s out of bounds (%d-%d)\n", s,
555 text_chunk->text_bounds.y,
556 text_chunk->text_bounds.y +
557 text_chunk->text_bounds.height);
562 if (role == SPI_ROLE_PUSH_BUTTON ||
563 role == SPI_ROLE_CHECK_BOX ||
564 role == SPI_ROLE_LABEL ||
565 role == SPI_ROLE_MENU ||
566 role == SPI_ROLE_MENU_ITEM) { /* don't like this
567 special casing :-( */
568 s = Accessible_getName (accessible);
569 /* use name instead */
570 text_chunk->text_bounds = text_chunk->clip_bounds;
571 text_chunk->start_offset = 0;
572 text_chunk->end_offset = strlen (s);
575 if (s && strlen (s)) {
576 if (s[strlen(s)-1] == '\n') s[strlen(s)-1] = ' ';
577 /* XXX: if last char is newline, aren't its bounds wrong now? */
578 text_chunk->string = s;
579 text_chunk->source = text;
580 if (text) AccessibleText_ref (text);
582 fprintf (stderr, "%s, bounds %d-%d; clip %d-%d\n",
584 text_chunk->text_bounds.x,
585 text_chunk->text_bounds.x+text_chunk->text_bounds.width,
586 text_chunk->clip_bounds.x,
587 text_chunk->clip_bounds.x+text_chunk->clip_bounds.width);
590 text_chunk->string = NULL;
591 text_chunk->source = NULL;
593 if (text) AccessibleText_unref (text);
598 debug_chunk_list (GList *iter)
602 chunk = (TextChunk *)iter->data;
603 fprintf (stderr, "Chunk %s, clip %d-%d, text %d-%d\n",
605 chunk->clip_bounds.x,
606 chunk->clip_bounds.x + chunk->clip_bounds.width,
607 chunk->text_bounds.x,
608 chunk->text_bounds.x + chunk->text_bounds.width);
614 clip_into_buffers (Accessible *accessible, BoundaryRect* parentClipBounds[],
615 ScreenReviewBuffer *reviewBuffers[],
616 int screen_x, int screen_y)
618 int n_children, child_n;
621 BoundaryRect** clip_bounds;
622 TextChunk *text_chunk;
623 AccessibleComponent *component;
627 clip_bounds = clip_bounds_clone (parentClipBounds);
628 if (Accessible_isComponent (accessible)) {
629 role = Accessible_getRole (accessible);
630 component = Accessible_getComponent (accessible);
631 layer = AccessibleComponent_getLayer (component);
632 bounds = *clip_bounds[layer];
633 if (!bounds.isEmpty) {
634 AccessibleComponent_getExtents (component,
639 SPI_COORD_TYPE_SCREEN);
641 if (clip_bounds[layer])
642 boundary_clip (&bounds, clip_bounds[layer]);
643 if (BOUNDS_CONTAIN_Y (&bounds, screen_y)) {
644 text_chunk = review_buffer_get_text_chunk (
645 reviewBuffers[layer], accessible, &bounds,
647 reviewBuffers[layer]->text_chunks =
648 text_chunk_list_insert_chunk (
649 reviewBuffers[layer]->text_chunks,
653 IS_CLIPPING_CONTAINER (bounds.role);
656 Accessible_unref (component);
659 * we always descend into children in case they are in a higher layer
660 * this can of course be optimized for the topmost layer...
661 * but nobody uses that one! (SPI_LAYER_OVERLAY)
663 n_children = Accessible_getChildCount (accessible);
664 for (child_n = 0; child_n < n_children; ++child_n) {
665 child = Accessible_getChildAtIndex (accessible, child_n);
666 clip_into_buffers (child, clip_bounds, reviewBuffers, screen_x, screen_y);
667 Accessible_unref (child);
669 /* TODO: free the parent clip bounds */
672 #undef CHARACTER_CLIP_DEBUG
675 text_chunk_get_clipped_substring_by_char (TextChunk *chunk, int start, int end)
677 BoundaryRect char_bounds;
679 GString *string = g_string_new ("");
681 for (i = start; i < end; ++i) {
682 AccessibleText_getCharacterExtents (chunk->source,
688 SPI_COORD_TYPE_SCREEN);
689 #ifdef CHARACTER_CLIP_DEBUG
690 fprintf (stderr, "testing %d-%d against %d-%d\n",
691 char_bounds.x, char_bounds.x+char_bounds.width,
692 chunk->text_bounds.x,
693 chunk->text_bounds.x + chunk->text_bounds.width);
695 if (BOUNDS_CONTAIN_X_BOUNDS (chunk->text_bounds,
697 c = AccessibleText_getCharacterAtOffset (
700 fprintf (stderr, "[%c]", c);
702 g_string_append_unichar (string, c);
706 g_string_free (string, FALSE);
712 * Note: this routine shouldn't have to do as much as it currently does,
713 * but at the moment it works around a pango?/gail? bug which
714 * causes WORD boundary type queries to return incorrect strings.
717 string_strip_newlines (char *s, long offset, long *start_offset, long *end_offset)
720 char *word_start = s;
721 /* FIXME: potential memory leak here */
722 for (i=0; s && s[i]; ++i)
724 if (s [i] == '\n' && i > (offset - *start_offset) ) {
726 *end_offset = *start_offset + i;
728 } else if (s [i] == '\n') {
729 word_start = &s[i + 1];
736 text_chunk_get_clipped_string (TextChunk *chunk)
738 char *s, *string = "";
740 long start = chunk->start_offset, end = chunk->end_offset;
741 long word_start, word_end, range_end;
742 BoundaryRect start_bounds, end_bounds;
743 gboolean start_inside, end_inside;
744 if (!chunk->text_bounds.isClipped)
745 string = chunk->string;
746 else if (chunk->source) {
748 fprintf (stderr, "clipping %s\n", chunk->string);
750 /* while words at offset lie within the bounds, add them */
752 s = AccessibleText_getTextAtOffset (chunk->source,
754 SPI_TEXT_BOUNDARY_WORD_START,
757 range_end = word_end;
758 s = string_strip_newlines (s, start, &word_start, &word_end);
759 AccessibleText_getCharacterExtents (chunk->source,
764 &start_bounds.height,
765 SPI_COORD_TYPE_SCREEN);
766 AccessibleText_getCharacterExtents (chunk->source,
772 SPI_COORD_TYPE_SCREEN);
773 start_inside = BOUNDS_CONTAIN_X_BOUNDS (chunk->text_bounds,
775 end_inside = BOUNDS_CONTAIN_X_BOUNDS (chunk->text_bounds,
777 if (start_inside && end_inside) {
778 /* word is contained in bounds */
779 string = g_strconcat (string, s, NULL);
780 } else if (start_inside || end_inside) {
781 /* one end of word is in */
782 if (word_end > end) word_end = end;
783 s = text_chunk_get_clipped_substring_by_char (
785 MAX (word_start, chunk->start_offset),
786 MIN (word_end, chunk->end_offset));
787 string = g_strconcat (string, s, NULL);
791 } while (start < chunk->end_offset);
792 } else { /* we're clipped, but don't implement AccessibleText :-( */
793 /* punt for now, maybe we can do betterc someday */
794 string = chunk->string;
800 text_chunk_list_to_string (GList *iter)
804 TextChunk *chunk = NULL;
806 chunk = (TextChunk *)iter->data;
807 if (chunk /* && chunk->string */) {
808 string = text_chunk_get_clipped_string (chunk);
810 s = g_strconcat (s, "|", string, NULL);
812 s = g_strconcat (s, ":", NULL);
816 s = g_strconcat (s, "|", NULL);
821 review_buffer_composite (ScreenReviewBuffer *buffers[])
824 GList *chunk_list, *iter;
826 #ifdef NEED_TO_FIX_THIS
827 chunk_list = buffers[0]->text_chunks;
828 for (i = 1; i < SPI_LAYER_LAST_DEFINED; ++i) {
829 iter = buffers[i]->text_chunks;
831 fprintf (stderr, "layer %d has %d chunks\n",
832 i, g_list_length (iter));
835 chunk = (TextChunk *) iter->data;
838 fprintf (stderr, "inserting chunk <%s>\n",
839 chunk->string ? chunk->string : "<null>");
842 text_chunk_list_insert_chunk (chunk_list,
849 chunk_list = buffers[SPI_LAYER_WIDGET]->text_chunks;
850 return text_chunk_list_to_string (chunk_list);
854 get_screen_review_line_at (int x, int y)
857 Accessible *desktop, *app, *toplevel, *child;
858 AccessibleComponent *component;
859 AccessibleStateSet *states;
860 GList *toplevels = NULL, *actives = NULL, *iter;
861 ScreenReviewBuffer* reviewBuffers[SPI_LAYER_LAST_DEFINED];
862 BoundaryRect* clip_bounds[SPI_LAYER_LAST_DEFINED];
863 BoundaryRect toplevel_bounds;
864 int n_apps, n_toplevels, n_children, app_n, toplevel_n, child_n;
865 GTimer *timer = g_timer_new ();
868 for (i = 0; i < SPI_LAYER_LAST_DEFINED; ++i) {
869 reviewBuffers[i] = g_new0 (ScreenReviewBuffer, 1);
870 clip_bounds[i] = g_new0 (BoundaryRect, 1);
871 clip_bounds[i]->isClipped = FALSE;
872 clip_bounds[i]->isEmpty = FALSE;
875 /* how do we decide which desktop ? */
876 desktop = SPI_getDesktop (0);
879 n_apps = Accessible_getChildCount (desktop);
880 for (app_n = 0; app_n < n_apps; ++app_n) {
881 /* for each toplevel in app */
882 app = Accessible_getChildAtIndex (desktop, app_n);
883 n_toplevels = Accessible_getChildCount (app);
884 for (toplevel_n = 0; toplevel_n < n_toplevels; ++toplevel_n) {
885 Accessible *toplevel = Accessible_getChildAtIndex (app, toplevel_n);
886 if (Accessible_isComponent (toplevel))
887 toplevels = g_list_prepend (toplevels, toplevel);
889 Accessible_unref (toplevel);
890 fprintf (stderr, "warning, app toplevel not a component.\n");
895 /* sort: at the moment we don't have a good way to sort except to put actives on top */
896 for (iter = g_list_first (toplevels); iter; iter = iter->next) {
897 Accessible *toplevel =
898 (Accessible *) iter->data;
899 if (AccessibleStateSet_contains (Accessible_getStateSet (toplevel),
901 actives = g_list_prepend (actives, toplevel);
905 for (iter = g_list_first (actives); iter; iter = actives->next) {
906 toplevels = g_list_remove (toplevels, iter->data); /* place at end */
907 toplevels = g_list_append (toplevels, iter->data);
909 g_list_free (actives);
911 /* for each toplevel, ending with the active one(s),
912 * clip against children, putting results into appropriate charBuffer.
914 for (iter = g_list_first (toplevels); iter; iter = iter->next) {
915 toplevel = (Accessible *) iter->data;
916 if (Accessible_isComponent (toplevel)) {
917 /* make sure toplevel is visible and not iconified or shaded */
918 states = Accessible_getStateSet (toplevel);
919 if (AccessibleStateSet_contains (states, SPI_STATE_VISIBLE)
920 && !AccessibleStateSet_contains (states, SPI_STATE_ICONIFIED)
921 || isJava) { /* isJava hack! */
922 component = Accessible_getComponent (toplevel);
923 AccessibleComponent_getExtents (component,
926 &toplevel_bounds.width,
927 &toplevel_bounds.height,
928 SPI_COORD_TYPE_SCREEN);
929 toplevel_bounds.isEmpty = FALSE;
930 for (i = 0; i < SPI_LAYER_LAST_DEFINED; ++i) {
931 *clip_bounds[i] = toplevel_bounds;
933 clip_into_buffers (toplevel, clip_bounds,
934 reviewBuffers, x, y);
935 #ifdef CHUNK_LIST_DEBUG
936 fprintf (stderr, "toplevel clip done\n");
937 debug_chunk_list (reviewBuffers[SPI_LAYER_WIDGET]->text_chunks);
941 Accessible_unref (toplevel);
944 string = review_buffer_composite (reviewBuffers);
946 /* SIMPLE SINGLE-PASS ALGORITHM:*/
947 /* traverse the tree:
948 * keep a pointer to outermost instance of each layer
949 * clip against outermost in same layer
950 * when this clip occurs, store outermost clipped string in 2d string buffer.
951 * string buffer may have attributes to mark component bounds, line art,
952 * or attributes of text being reviewed.
953 * composite the layers, ignoring NULL chars in the string buffers.
956 * sibling clip not correct, text may overwrite if siblings intersect onscreen
957 * length of resulting text buffer may vary!
960 * no API for ordering toplevels yet, other than knowing which is ACTIVE.
961 * not much implementation for the LAYER API yet, other than menus.
963 g_timer_stop (timer);
964 fprintf (stderr, "elapsed time = %f s\n", g_timer_elapsed (timer, NULL));
970 report_screen_review_line (const AccessibleEvent *event, void *user_data)
972 static Display *display = NULL;
973 int x, y, win_x, win_y;
974 Window root_return, child_return;
975 unsigned int mask_return;
977 if (!display) display = XOpenDisplay (getenv ("DISPLAY"));
979 * we would prefer to get the x,y info in the above event.
980 * At the moment we don't get detail params for "toolkit" events,
981 * so for testing purposes we use XQueryPointer. Actual apps
982 * probably shouldn't do this.
984 XQueryPointer (display,
985 DefaultRootWindow (display),
986 &root_return, &child_return,
991 fprintf (stderr, "screen review event %s at %d, %d\n", event->type,
993 fprintf (stderr, "[%s]\n",
994 get_screen_review_line_at (x, y));
1000 SPI_deregisterGlobalEventListenerAll (mouseclick_listener);
1001 AccessibleEventListener_unref (mouseclick_listener);