Updated screen review test program.
[platform/core/uifw/at-spi2-atk.git] / test / screen-review-test.c
1 /*
2  * AT-SPI - Assistive Technology Service Provider Interface
3  * (Gnome Accessibility Project; http://developer.gnome.org/projects/gap)
4  *
5  * Copyright 2001 Sun Microsystems Inc.
6  *
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.
11  *
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.
16  *
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.
21  */
22
23 #include <stdlib.h>
24 #include "../cspi/spi-private.h"
25
26 #undef CLIP_DEBUG
27
28 /*
29  * Screen Review Algorithm Demonstration, Benchmark, and Test-bed.
30  *
31  * Issues:
32  *
33  * * bounds now fail to include window border decoration
34  *         (which we can't really know about).
35  *
36  * * brute-force algorithm uses no client-side cache; performance mediocre.
37  *
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.
41  *
42  * * text-bearing objects that don't implement AccessibleText (such as buttons)
43  *          don't get their text clipped since we don't know the character
44  *          bounding boxes.
45  * * can't know about "inaccessible" objects that may be obscuring the
46  *          accessible windows (inherent to API-based approach).
47  *
48  * * (others).
49  */
50
51
52 #define BOUNDS_CONTAIN_X(b, p)  (((p)>=(b)->x) && ((p)<=((b)->x + (b)->width))\
53             && ((b)->width > 0) && ((b)->height > 0))
54
55 #define BOUNDS_CONTAIN_Y(b, p)  (((p)>=(b)->y) && ((p)<=((b)->y + (b)->height))\
56             && ((b)->width > 0) && ((b)->height > 0))
57
58 #define CHUNK_BOUNDS_BEFORE_START(c, i) ((i) && ((c)->clip_bounds.x < \
59                                          (i)->clip_bounds.x)) 
60
61 #define CHUNK_BOUNDS_END_BEFORE_START(c, i) ((i) && \
62                                          (((c)->clip_bounds.x + \
63                                          (c)->clip_bounds.width) < \
64                                          (i)->clip_bounds.x))
65
66 #define CHUNK_BOUNDS_AFTER_END(c, i) ((i) && ((c)->clip_bounds.x >= \
67                                        ((i)->clip_bounds.x + \
68                                         (i)->clip_bounds.width)))
69
70 #define CHUNK_BOUNDS_SPANS_END(c, i) ((i) && (((c)->clip_bounds.x + \
71                                               (c)->clip_bounds.width) >= \
72                                        ((i)->clip_bounds.x + \
73                                         (i)->clip_bounds.width)))
74 /*
75  * #define CHUNK_BOUNDS_WITHIN(c, i)  ((i) && ((c)->clip_bounds.x >= \
76  *                               (i)->text_bounds.x) && \
77  *                               (((c)->clip_bounds.x + (c)->clip_bounds.width) \
78  *                             <= ((i)->text_bounds.x + (i)->text_bounds.width)))
79  */
80
81 #define IS_CLIPPING_CONTAINER(a) ((a) != SPI_ROLE_PAGE_TAB)
82
83 static void report_screen_review_line  (const AccessibleEvent *event, void *user_data);
84
85 static gint n_elements_traversed = 0;
86 static AccessibleEventListener *mouseclick_listener;
87
88 typedef struct _BoundaryRect {
89         long int x;
90         long int y;
91         long int width;
92         long int height;
93         AccessibleRole role; /* role of last clipping element */
94         gboolean isClipped;
95         gboolean isEmpty;
96 } BoundaryRect;
97
98 typedef struct _TextChunk {
99         char           *string;
100         AccessibleText *source;
101         int             start_offset;
102         int             end_offset;
103         BoundaryRect    clip_bounds;
104         BoundaryRect    text_bounds;
105         BoundaryRect    start_char_bounds;
106         BoundaryRect    end_char_bounds;
107 } TextChunk;
108
109 typedef struct _ScreenReviewBuffer { /* TODO: implement */
110         GList *text_chunks;
111 } ScreenReviewBuffer;
112
113 static gboolean isJava = FALSE;
114
115 int
116 main (int argc, char **argv)
117 {
118   int i, j;
119   int n_desktops;
120   int n_apps;
121   char *s;
122   GTimer *timer;
123   gdouble elapsed_time;
124   Accessible *desktop;
125   Accessible *application;
126   const char *modules;
127
128   SPI_init ();
129
130   mouseclick_listener = SPI_createAccessibleEventListener (
131           report_screen_review_line, NULL); 
132
133   SPI_registerGlobalEventListener (mouseclick_listener,
134                                    "Gtk:GtkWidget:button-press-event");
135 #undef JAVA_TEST_HACK
136 #ifdef JAVA_TEST_HACK /* Only use this to test Java apps */
137   SPI_registerGlobalEventListener (mouseclick_listener,
138                                    "object:text-caret-moved");
139   isJava = TRUE;
140 #endif
141   SPI_event_main ();
142
143   putenv ("AT_BRIDGE_SHUTDOWN=1");
144
145   /*
146    * TODO: Add a key event listener that calls test_exit, to
147    * deregister and cleanup appropriately.
148    */
149
150   return SPI_exit ();
151 }
152
153 static inline gboolean
154 chunk_bounds_within (TextChunk *chunk, TextChunk *test_chunk)
155 {
156         int x1, x2, tx1, tx2;
157         gboolean gtx1, ltx2;
158
159         x1 = chunk->clip_bounds.x;
160         x2 = x1 + chunk->clip_bounds.width;
161         tx1 = test_chunk->clip_bounds.x;
162         tx2 = tx1 + test_chunk->clip_bounds.width;
163         gtx1 = (chunk->clip_bounds.x >= test_chunk->clip_bounds.x);
164         ltx2 = (chunk->clip_bounds.x + chunk->clip_bounds.width
165                 <= test_chunk->clip_bounds.x + test_chunk->clip_bounds.width);
166         return gtx1 && ltx2;
167 }
168
169 #define CHUNK_BOUNDS_WITHIN(a, b) chunk_bounds_within(a, b)
170
171 static BoundaryRect **
172 clip_bounds_clone (BoundaryRect *bounds[])
173 {
174         int i;
175         BoundaryRect **bounds_clone;
176         bounds_clone = (BoundaryRect **)
177                 g_new0 (gpointer, SPI_LAYER_LAST_DEFINED);
178         for (i = 0; i < SPI_LAYER_LAST_DEFINED; ++i) {
179                 bounds_clone[i] = g_new0 (BoundaryRect, 1);
180                 *bounds_clone[i] = *bounds[i];
181         }
182         return bounds_clone;
183 }
184
185 static void
186 boundary_clip (BoundaryRect *bounds, BoundaryRect *clipBounds)
187 {
188         int x2 = bounds->x + bounds->width;
189         int y2 = bounds->y + bounds->height;
190 #ifdef CLIP_DEBUG
191         fprintf (stderr, "bclip %d-%d, %d-%d; ",
192                  bounds->x, x2,
193                  clipBounds->x, clipBounds->x+clipBounds->width);
194 #endif           
195         bounds->x = MAX (bounds->x, clipBounds->x);
196         bounds->y = MAX (bounds->y, clipBounds->y);
197         x2 =  MIN (x2,  clipBounds->x + clipBounds->width);
198         y2 =  MIN (y2, clipBounds->y + clipBounds->height);
199         bounds->width = MAX (x2 - bounds->x, 0);
200         bounds->height = MAX (y2 - bounds->y, 0);
201         if (!bounds->width || !bounds->height)
202                 bounds->isEmpty = TRUE;
203         if (IS_CLIPPING_CONTAINER (bounds->role)) {
204                 *clipBounds = *bounds;
205         }
206 #ifdef CLIP_DEBUG
207         fprintf (stderr, "%d-%d\n",
208                  bounds->x, bounds->x+bounds->width);
209 #endif
210 }
211
212 static void
213 boundary_xclip_head (BoundaryRect *bounds, BoundaryRect *clipBounds)
214 {
215         int x2;
216         int cx2 = clipBounds->x + clipBounds->width;
217         if (cx2 < bounds->x) return;
218         x2 = bounds->x + bounds->width;
219         if (cx2 < x2) bounds->x = cx2;
220         bounds->width = MAX (0, x2 - cx2);
221 }
222
223 static void
224 boundary_xclip_tail (BoundaryRect *bounds, BoundaryRect *clipBounds)
225 {
226         int x2 = bounds->x + bounds->width;
227         if (clipBounds->x > x2) return;
228         bounds->width = MAX (0, clipBounds->x - bounds->x);
229 }
230
231 static TextChunk*
232 text_chunk_copy (TextChunk *chunk)
233 {
234         TextChunk *copy = g_new0 (TextChunk, 1);
235         *copy = *chunk;
236         if (chunk->string) copy->string = g_strdup (chunk->string);
237         if (copy->source) AccessibleText_ref (copy->source);
238         return copy;
239 }
240
241 static void
242 text_chunk_tail_clip (TextChunk *bottom, TextChunk *top)
243 {
244 #ifdef CLIP_DEBUG
245         fprintf (stderr, "bottom %d-%d, top %d-%d;",
246                  bottom->clip_bounds.x,
247                  bottom->clip_bounds.x + bottom->clip_bounds.width,
248                  top->clip_bounds.x,
249                  top->clip_bounds.x + top->clip_bounds.width);
250 #endif
251         boundary_xclip_tail (&bottom->text_bounds, &top->clip_bounds);
252         boundary_xclip_tail (&bottom->clip_bounds, &top->clip_bounds);
253         bottom->text_bounds.isClipped = TRUE;
254         bottom->clip_bounds.isClipped = TRUE;
255 #ifdef CLIP_DEBUG
256         fprintf (stderr, "result %d-%d\n",
257                  bottom->clip_bounds.x,
258                  bottom->clip_bounds.x + bottom->clip_bounds.width);
259 #endif
260 }
261
262 static void
263 text_chunk_head_clip (TextChunk *bottom, TextChunk *top)
264 {
265 #ifdef CLIP_DEBUG
266         fprintf (stderr, "bottom %d-%d, top %d-%d;",
267                  bottom->clip_bounds.x,
268                  bottom->clip_bounds.x + bottom->clip_bounds.width,
269                  top->clip_bounds.x,
270                  top->clip_bounds.x + top->clip_bounds.width);
271 #endif  
272         boundary_xclip_head (&bottom->text_bounds, &top->clip_bounds);
273         boundary_xclip_head (&bottom->clip_bounds, &top->clip_bounds);
274         bottom->text_bounds.isClipped = TRUE;
275         bottom->clip_bounds.isClipped = TRUE;
276 #ifdef CLIP_DEBUG       
277         fprintf (stderr, "result %d-%d\n",
278                  bottom->clip_bounds.x,
279                  bottom->clip_bounds.x + bottom->clip_bounds.width);
280 #endif  
281 }
282
283 static GList *
284 text_chunk_split_insert (GList *chunk_list, GList *iter, TextChunk *chunk)
285 {
286         TextChunk *iter_chunk = iter->data;
287         TextChunk *iter_copy = text_chunk_copy (iter_chunk);
288         /* TODO: FIXME something is wrong here */
289 #ifdef CLIP_DEBUG
290         fprintf (stderr, "***clip insert of %s into %s\n", 
291                  chunk->string ? chunk->string : "<null>",
292                  iter_chunk->string ? iter_chunk->string : "<null>");
293 #endif  
294         chunk_list = g_list_insert_before (chunk_list, iter, iter_copy);
295         text_chunk_tail_clip (iter_copy, chunk);
296         chunk_list = g_list_insert_before (chunk_list, iter, chunk);
297         text_chunk_head_clip (iter_chunk, chunk);
298         return chunk_list;
299 }
300
301 /* #define PRINT_CHUNK_DEBUG(a, b, c, d) print_chunk_debug(a, b, c, d) */
302
303 #define PRINT_CHUNK_DEBUG(a, b, c, d) 
304
305 #ifdef PRINT_CHUNK_DEBUG
306 static void
307 print_chunk_debug (TextChunk *chunk, char *opname, GList *prev, GList *next)
308 {
309         fprintf (stderr, "%sing chunk %s between %s and %s; %d-%d\n",
310                  opname,
311                  chunk->string,
312                  (prev ? ((TextChunk *) prev->data)->string : "<null>"),
313                  (next ? ((TextChunk *) next->data)->string : "<null>"),
314                  chunk->clip_bounds.x,
315                  chunk->text_bounds.x + chunk->text_bounds.width);
316 }
317 #endif
318
319 static GList *
320 text_chunk_list_head_clip (GList *text_chunk_list,
321                            TextChunk *chunk,
322                            GList *next)
323 {
324         GList *target, *iter = next, *prev;
325         prev = iter->prev;
326 //      if (chunk->string && strlen (chunk->string)) { 
327                 text_chunk_list =
328                         g_list_insert_before (text_chunk_list, next, chunk);
329 //      }
330         while (iter) {
331                 if (CHUNK_BOUNDS_SPANS_END (chunk, (TextChunk *)iter->data)) {
332 #ifdef CLIP_DEBUG                       
333                         fprintf (stderr, "deleting %s\n",
334                                  ((TextChunk *)iter->data)->string);
335 #endif                  
336                         target = iter;
337                         iter = iter->next;
338                         text_chunk_list =
339                                 g_list_delete_link (text_chunk_list, target);
340                 } else {
341                         if (!CHUNK_BOUNDS_END_BEFORE_START (chunk,
342                                                       (TextChunk *)iter->data)) {
343                                 text_chunk_head_clip ((TextChunk *)iter->data,
344                                                       chunk);
345                         }
346                         if (prev &&
347                             !CHUNK_BOUNDS_AFTER_END (
348                                     chunk,
349                                     (TextChunk *)prev->data)) {
350                                 text_chunk_tail_clip (
351                                         (TextChunk *)prev->data,
352                                         chunk);
353                         }
354                         break;
355                 }
356         }
357         
358         return text_chunk_list;
359 }
360
361 static GList *
362 text_chunk_list_clip_and_insert (GList *text_chunk_list,
363                                  TextChunk *chunk,
364                                  GList *prev,
365                                  GList *next)
366 {
367 #ifdef CLIP_DEBUG
368         if (chunk->string)
369                 fprintf (stderr, "clip-and-insert for %s, between %s and %s\n",
370                          chunk->string,
371                          (prev && ((TextChunk *)prev->data)->string ? ((TextChunk *)prev->data)->string : "<null>"),
372                          (next && ((TextChunk *)next->data)->string ? ((TextChunk *)next->data)->string : "<null>"));
373 #endif
374         /* cases: */
375         if (!prev && !next) { /* first element in, no clip needed */
376 //              if (chunk->string && strlen (chunk->string)) {
377                         text_chunk_list =
378                                 g_list_append (text_chunk_list, chunk);
379                         PRINT_CHUNK_DEBUG (chunk, "append",
380                                            prev,
381                                            NULL);
382 //              }
383         } else { /* check for clip with prev */
384                 /* if we split the prev */
385                 if (prev &&
386                     CHUNK_BOUNDS_WITHIN (chunk, (TextChunk *) prev->data)) {
387                         text_chunk_list =
388                                 text_chunk_split_insert (
389                                         text_chunk_list,
390                                         prev, chunk);
391                 } else if (next) { 
392                     /* we split the 'next' element */
393                     if (CHUNK_BOUNDS_WITHIN (chunk, (TextChunk *)next->data)) {
394                             text_chunk_list =
395                                     text_chunk_split_insert (text_chunk_list,
396                                                              next, chunk);
397                     } else {
398                             /* do an insert +  head clip */
399                             text_chunk_list =
400                                     text_chunk_list_head_clip (
401                                             text_chunk_list,
402                                             chunk,
403                                             next);
404                     }
405                 } else {
406                         if (!CHUNK_BOUNDS_AFTER_END (chunk,
407                                                      (TextChunk *) prev->data)) {
408                                 text_chunk_tail_clip (prev->data, chunk);
409                         }
410 //                      if (chunk->string && strlen (chunk->string)) {
411                                 text_chunk_list =
412                                         g_list_append (text_chunk_list, chunk);
413 //                      }
414                 }
415         }
416         return text_chunk_list;
417 }
418
419 static GList *
420 text_chunk_list_insert_chunk (GList *text_chunk_list, TextChunk *chunk)
421 {
422         GList *iter = g_list_first (text_chunk_list);
423         TextChunk *iter_chunk = NULL;
424         do {
425                 if (iter) iter_chunk = (TextChunk *) iter->data;
426                 /* if we're ahead of the current element */
427                 if (!iter) {
428                         text_chunk_list =
429                                 text_chunk_list_clip_and_insert (text_chunk_list,
430                                                                  chunk,
431                                                                  iter,
432                                                                  NULL);
433                         break;
434                 } else if (CHUNK_BOUNDS_BEFORE_START (chunk, iter_chunk)) {
435                         text_chunk_list =
436                                 text_chunk_list_clip_and_insert (text_chunk_list,
437                                                                  chunk,
438                                                                  iter->prev,
439                                                                  iter);
440                         break;
441                 } else if (!iter->next ) {
442                         text_chunk_list =
443                                 text_chunk_list_clip_and_insert (text_chunk_list,
444                                                                  chunk,
445                                                                  iter,
446                                                                  NULL);
447                         break;
448                 }
449                 if (iter) iter = iter->next;
450         } while (iter);
451         return text_chunk_list;
452 }
453
454 static TextChunk *
455 review_buffer_get_text_chunk (ScreenReviewBuffer *reviewBuffer,
456                               Accessible *accessible, BoundaryRect *bounds,
457                               int screen_x, int screen_y)
458 {
459         AccessibleText *text = NULL;
460         AccessibleRole role;
461         TextChunk *text_chunk;
462         BoundaryRect start_char_bounds, end_char_bounds;
463         char *s = NULL;
464         int offset;
465         long start, end;
466         long x2, y2;
467
468         role = Accessible_getRole (accessible);
469         text_chunk = g_new0 (TextChunk, 1);
470         text_chunk->clip_bounds = *bounds;
471         if (Accessible_isText (accessible)) {
472                 text = Accessible_getText (accessible);
473                 offset = AccessibleText_getOffsetAtPoint (text,
474                                                           screen_x,
475                                                           screen_y,
476                                                           SPI_COORD_TYPE_SCREEN);
477                 s = AccessibleText_getTextAtOffset (text, offset,
478                                                     SPI_TEXT_BOUNDARY_LINE_START,
479                                                     &start, &end);
480                 if (end > start) {
481                         AccessibleText_getCharacterExtents (
482                                 text, start,
483                                 &text_chunk->start_char_bounds.x,
484                                 &text_chunk->start_char_bounds.y,
485                                 &text_chunk->start_char_bounds.width,
486                                 &text_chunk->start_char_bounds.height,
487                                 SPI_COORD_TYPE_SCREEN);
488 #ifdef CLIP_DEBUG
489                         fprintf (stderr, "%s: start char (%d) x, width %d %d; ",
490                                  s,
491                                  start,
492                                  text_chunk->start_char_bounds.x,
493                                  text_chunk->start_char_bounds.width);
494 #endif
495                         end--; /* XXX: bug workaround */
496                         AccessibleText_getCharacterExtents (
497                                 text, end--,
498                                 &text_chunk->end_char_bounds.x,
499                                 &text_chunk->end_char_bounds.y,
500                                 &text_chunk->end_char_bounds.width,
501                                 &text_chunk->end_char_bounds.height,
502                                 SPI_COORD_TYPE_SCREEN);
503 #ifdef CLIP_DEBUG                       
504                         fprintf (stderr, "end char (%d) x, width %d %d\n",
505                                  end,
506                                  text_chunk->end_char_bounds.x,
507                                  text_chunk->end_char_bounds.width);
508 #endif
509                 }
510                 text_chunk->text_bounds.x = MIN (text_chunk->start_char_bounds.x,
511                                                  text_chunk->end_char_bounds.x);
512                 text_chunk->text_bounds.y = MIN (text_chunk->start_char_bounds.y,
513                                                  text_chunk->end_char_bounds.y);
514                 x2 = MAX (text_chunk->start_char_bounds.x +
515                           text_chunk->start_char_bounds.width,
516                           text_chunk->end_char_bounds.x +
517                           text_chunk->end_char_bounds.width);
518                 text_chunk->text_bounds.width = x2 - text_chunk->text_bounds.x;
519                 y2 = MAX (text_chunk->start_char_bounds.y +
520                           text_chunk->start_char_bounds.height,
521                           text_chunk->end_char_bounds.y + 
522                           text_chunk->end_char_bounds.height);
523                 text_chunk->text_bounds.height = y2 - text_chunk->text_bounds.y;
524                 text_chunk->start_offset = start;
525                 text_chunk->end_offset = end;
526                 if (text_chunk->text_bounds.x < text_chunk->clip_bounds.x) {
527                         text_chunk->text_bounds.x = text_chunk->clip_bounds.x;
528                         text_chunk->text_bounds.isClipped = TRUE;
529                 } 
530                 if ((text_chunk->text_bounds.x +
531                      text_chunk->text_bounds.width)
532                     > (text_chunk->clip_bounds.x +
533                        text_chunk->clip_bounds.width)) {
534                         text_chunk->text_bounds.width =
535                                 MAX (0, (text_chunk->clip_bounds.x +
536                                          text_chunk->clip_bounds.width) -
537                                      text_chunk->text_bounds.x);
538                         text_chunk->text_bounds.isClipped = TRUE;
539                 }
540                 if (!BOUNDS_CONTAIN_Y (&text_chunk->text_bounds,
541                                        screen_y)) {
542 #ifdef CLIP_DEBUG                       
543                         fprintf (stderr, "%s out of bounds (%d-%d)\n", s,
544                                  text_chunk->text_bounds.y,
545                                  text_chunk->text_bounds.y +
546                                  text_chunk->text_bounds.height);
547 #endif                  
548                         s = NULL;
549                 }
550         } else {
551                 if (role == SPI_ROLE_PUSH_BUTTON ||
552                     role == SPI_ROLE_CHECK_BOX ||
553                     role == SPI_ROLE_MENU ||
554                     role == SPI_ROLE_MENU_ITEM) { /* don't like this
555                                                      special casing :-( */
556                         s = Accessible_getName (accessible);
557                         /* use name instead */
558                         text_chunk->text_bounds = text_chunk->clip_bounds;
559                         text_chunk->start_offset = 0;
560                         text_chunk->end_offset = strlen (s);
561                 }
562         }
563         if (s && strlen (s)) {
564                 if (s[strlen(s)-1] == '\n') s[strlen(s)-1] = ' ';
565                 /* XXX: if last char is newline, aren't its bounds wrong now? */
566                 text_chunk->string = s;
567                 text_chunk->source = text;
568                 if (text) AccessibleText_ref (text);
569 #ifdef CLIP_DEBUG
570                 fprintf (stderr, "%s, bounds %d-%d; clip %d-%d\n",
571                          s,
572                          text_chunk->text_bounds.x,
573                          text_chunk->text_bounds.x+text_chunk->text_bounds.width,
574                          text_chunk->clip_bounds.x,
575                          text_chunk->clip_bounds.x+text_chunk->clip_bounds.width);
576 #endif          
577         } else {
578                 text_chunk->string = NULL;
579                 text_chunk->source = NULL;
580         }
581         if (text) AccessibleText_unref (text);
582         return text_chunk;
583 }
584
585 static void
586 debug_chunk_list (GList *iter)
587 {
588         TextChunk *chunk;
589         while (iter) {
590                 chunk = (TextChunk *)iter->data;
591                 fprintf (stderr, "Chunk %s, clip %d-%d, text %d-%d\n",
592                          chunk->string,
593                          chunk->clip_bounds.x,
594                          chunk->clip_bounds.x + chunk->clip_bounds.width,
595                          chunk->text_bounds.x,
596                          chunk->text_bounds.x + chunk->text_bounds.width);
597                 iter = iter->next;
598         }
599 }
600
601 static void
602 clip_into_buffers (Accessible *accessible,  BoundaryRect* parentClipBounds[],
603                    ScreenReviewBuffer *reviewBuffers[],
604                    int screen_x, int screen_y)
605 {
606         int n_children, child_n;
607         Accessible *child;
608         BoundaryRect bounds;
609         BoundaryRect** clip_bounds;
610         TextChunk *text_chunk;
611         AccessibleComponent *component;
612         AccessibleRole role;
613         int layer;
614
615         clip_bounds = clip_bounds_clone (parentClipBounds);
616         if (Accessible_isComponent (accessible)) {
617                 role = Accessible_getRole (accessible);
618                 component = Accessible_getComponent (accessible);
619                 layer = AccessibleComponent_getLayer (component);
620                 bounds = *clip_bounds[layer];
621                 if (!bounds.isEmpty) {
622                         AccessibleComponent_getExtents (component,
623                                                         &bounds.x,
624                                                         &bounds.y,
625                                                         &bounds.width,
626                                                         &bounds.height,
627                                                         SPI_COORD_TYPE_SCREEN);
628                         bounds.role = role;
629                         if (clip_bounds[layer])
630                                 boundary_clip (&bounds, clip_bounds[layer]);
631                         if (BOUNDS_CONTAIN_Y (&bounds, screen_y)) {
632                                 text_chunk = review_buffer_get_text_chunk (
633                                         reviewBuffers[layer], accessible, &bounds,
634                                         screen_x, screen_y);
635                                 reviewBuffers[layer]->text_chunks = 
636                                         text_chunk_list_insert_chunk (
637                                                 reviewBuffers[layer]->text_chunks,
638                                                 text_chunk);
639                         } else {
640                                 bounds.isEmpty =
641                                         IS_CLIPPING_CONTAINER (bounds.role);
642                         }
643                 } 
644                 Accessible_unref (component);
645         }
646         /*
647          * we always descend into children in case they are in a higher layer
648          * this can of course be optimized for the topmost layer...
649          * but nobody uses that one! (SPI_LAYER_OVERLAY)
650          */
651         n_children = Accessible_getChildCount (accessible);
652         for (child_n = 0; child_n < n_children; ++child_n) {
653                 child = Accessible_getChildAtIndex (accessible, child_n);
654                 clip_into_buffers (child, clip_bounds, reviewBuffers, screen_x, screen_y);
655                 Accessible_unref (child);
656         }
657         /* TODO: free the parent clip bounds */
658 }
659
660 #undef CHARACTER_CLIP_DEBUG
661
662 static char*
663 text_chunk_get_clipped_string (TextChunk *chunk)
664 {
665         char *s;
666         int i;
667         int len;
668         gunichar c;
669         GString *string = g_string_new ("");
670         BoundaryRect char_bounds;
671         if (!chunk->text_bounds.isClipped)
672                 s = chunk->string;
673         else if (chunk->source) {
674                 len = chunk->end_offset - chunk->start_offset;
675 #ifdef CHARACTER_CLIP_DEBUG             
676                 fprintf (stderr, "clipping %s\n", chunk->string);
677 #endif          
678                 for (i = chunk->start_offset; i < chunk->end_offset; ++i) {
679                         AccessibleText_getCharacterExtents (chunk->source,
680                                                             i,
681                                                             &char_bounds.x,
682                                                             &char_bounds.y,
683                                                             &char_bounds.width,
684                                                             &char_bounds.height,
685                                                             SPI_COORD_TYPE_SCREEN);
686 #ifdef CHARACTER_CLIP_DEBUG
687                         fprintf (stderr, "testing %d-%d against %d-%d\n",
688                                  char_bounds.x, char_bounds.x+char_bounds.width,
689                                  chunk->text_bounds.x,
690                                  chunk->text_bounds.x + chunk->text_bounds.width);
691 #endif
692                         if (BOUNDS_CONTAIN_X (&chunk->text_bounds,
693                                               char_bounds.x)) {
694                                 c = AccessibleText_getCharacterAtOffset (
695                                         chunk->source, i);
696 #ifdef CLIP_DEBUG                               
697                                 fprintf (stderr, "[%c]", c);
698 #endif                          
699                                 g_string_append_unichar (string, c);
700                         }
701                 }
702                 s = string->str;
703         } else { /* we're clipped, but don't implement AccessibleText :-( */
704                 /* punt for now, maybe we can do betterc someday */
705                 s = chunk->string;
706         }
707         g_string_free (string, FALSE);
708         return s;
709 }
710
711 static char*
712 text_chunk_list_to_string (GList *iter)
713 {
714         char *s = "";
715         char *string;
716         TextChunk *chunk = NULL;
717         while (iter) {
718                 chunk = (TextChunk *)iter->data;
719                 if (chunk /* && chunk->string */) {
720                         string = text_chunk_get_clipped_string (chunk);
721                         if (string)
722                                 s = g_strconcat (s, "|", string, NULL);
723                         else /* XXX test */
724                                 s = g_strconcat (s, ":", NULL);
725                 }
726                 iter = iter->next;
727         }
728         s = g_strconcat (s, "|", NULL);
729         return s;
730 }
731
732 static char*
733 review_buffer_composite (ScreenReviewBuffer *buffers[])
734 {
735         int i;
736         GList *chunk_list, *iter;
737         TextChunk *chunk;
738         chunk_list = buffers[0]->text_chunks;
739 /*      for (i = 1; i < SPI_LAYER_LAST_DEFINED; ++i) {
740                 iter = buffers[i]->text_chunks;
741                 fprintf (stderr, "layer %d has %d chunks\n",
742                          i, g_list_length (iter));
743                 while (iter) {
744                         chunk = (TextChunk *) iter->data;
745                         if (chunk) {
746                                 fprintf (stderr, "inserting chunk <%s>\n",
747                                          chunk->string ? chunk->string : "<null>");
748                                 chunk_list =
749                                         text_chunk_list_insert_chunk (chunk_list,
750                                                                       chunk);
751                         }
752                         iter = iter->next;
753                 }
754         }
755 */      chunk_list = buffers[SPI_LAYER_WIDGET]->text_chunks;
756         return text_chunk_list_to_string (chunk_list);
757 }
758
759 static char *
760 get_screen_review_line_at (int x, int y)
761 {
762   char *string;
763   Accessible *desktop, *app, *toplevel, *child;
764   AccessibleComponent *component;
765   AccessibleStateSet *states;
766   GList *toplevels = NULL, *actives = NULL, *iter;
767   ScreenReviewBuffer* reviewBuffers[SPI_LAYER_LAST_DEFINED];
768   BoundaryRect* clip_bounds[SPI_LAYER_LAST_DEFINED];
769   BoundaryRect toplevel_bounds;
770   int n_apps, n_toplevels, n_children, app_n, toplevel_n, child_n;
771   GTimer *timer = g_timer_new ();
772   int i;
773
774   for (i = 0; i < SPI_LAYER_LAST_DEFINED; ++i) {
775           reviewBuffers[i] = g_new0 (ScreenReviewBuffer, 1);
776           clip_bounds[i] = g_new0 (BoundaryRect, 1);
777           clip_bounds[i]->isClipped = FALSE;
778           clip_bounds[i]->isEmpty = FALSE;
779   }
780   
781   /* how do we decide which desktop ? */
782   desktop = SPI_getDesktop (0);
783   
784   /* for each app */
785   n_apps = Accessible_getChildCount (desktop);
786   for (app_n = 0; app_n < n_apps; ++app_n) {
787           /* for each toplevel in app */
788           app =  Accessible_getChildAtIndex (desktop, app_n);
789           n_toplevels = Accessible_getChildCount (app);
790           for (toplevel_n = 0; toplevel_n < n_toplevels; ++toplevel_n) {
791                   Accessible *toplevel = Accessible_getChildAtIndex (app, toplevel_n);
792                   if (Accessible_isComponent (toplevel))
793                           toplevels = g_list_prepend (toplevels, toplevel);
794                   else {
795                           Accessible_unref (toplevel);
796                           fprintf (stderr, "warning, app toplevel not a component.\n");
797                   }
798           }
799   }
800   
801   /* sort: at the moment we don't have a good way to sort except to put actives on top */
802   for (iter = g_list_first (toplevels); iter; iter = iter->next) {
803           Accessible *toplevel =
804                   (Accessible *) iter->data;
805           if (AccessibleStateSet_contains (Accessible_getStateSet (toplevel),
806                                            SPI_STATE_ACTIVE)) {
807                   actives = g_list_prepend (actives, toplevel);
808           }
809   }
810
811   for (iter = g_list_first (actives); iter; iter = actives->next) {
812           toplevels = g_list_remove (toplevels, iter->data); /* place at end */
813           toplevels = g_list_append (toplevels, iter->data);
814   }
815   g_list_free (actives);
816
817   /* for each toplevel, ending with the active one(s),
818    * clip against children, putting results into appropriate charBuffer.
819    */
820   for (iter = g_list_first (toplevels); iter; iter = iter->next) {
821           toplevel = (Accessible *) iter->data;
822           if (Accessible_isComponent (toplevel)) {
823               /* make sure toplevel is visible and not iconified or shaded */
824               states = Accessible_getStateSet (toplevel);
825               if (AccessibleStateSet_contains (states, SPI_STATE_VISIBLE)
826                   && !AccessibleStateSet_contains (states, SPI_STATE_ICONIFIED)
827                   || isJava) { /* isJava hack! */
828                       component = Accessible_getComponent (toplevel);
829                       AccessibleComponent_getExtents (component,
830                                                       &toplevel_bounds.x,
831                                                       &toplevel_bounds.y,
832                                                       &toplevel_bounds.width,
833                                                       &toplevel_bounds.height,
834                                                       SPI_COORD_TYPE_SCREEN);
835                       toplevel_bounds.isEmpty = FALSE;
836                       for (i = 0; i < SPI_LAYER_LAST_DEFINED; ++i) {
837                               *clip_bounds[i] = toplevel_bounds;
838                       }
839                       clip_into_buffers (toplevel, clip_bounds,
840                                      reviewBuffers, x, y);
841 #ifdef CLIP_DEBUG
842                       fprintf (stderr, "toplevel clip done\n");
843                       debug_chunk_list (reviewBuffers[SPI_LAYER_WIDGET]->text_chunks);
844 #endif                
845               }
846           }
847           Accessible_unref (toplevel);
848   }
849
850   string = review_buffer_composite (reviewBuffers);
851
852   /* SIMPLE SINGLE-PASS ALGORITHM:*/
853   /* traverse the tree:
854    *   keep a pointer to outermost instance of each layer
855    *   clip against outermost in same layer
856    *   when this clip occurs, store outermost clipped string in 2d string buffer.
857    *   string buffer may have attributes to mark component bounds, line art,
858    *      or attributes of text being reviewed.
859    *   composite the layers, ignoring NULL chars in the string buffers.
860    *
861    * Limitations:
862    *   sibling clip not correct, text may overwrite if siblings intersect onscreen
863    *   length of resulting text buffer may vary!
864    *
865    * Technical issues:
866    *   no API for ordering toplevels yet, other than knowing which is ACTIVE.
867    *   not much implementation for the LAYER API yet, other than menus.
868    */
869   g_timer_stop (timer);
870   fprintf (stderr, "elapsed time = %f s\n", g_timer_elapsed (timer, NULL));
871   
872   return string;
873 }
874
875 void
876 report_screen_review_line (const AccessibleEvent *event, void *user_data)
877 {
878   static Display *display = NULL;
879   int x, y, win_x, win_y;
880   Window root_return, child_return;
881   unsigned int mask_return;
882   
883   if (!display) display = XOpenDisplay (getenv ("DISPLAY"));
884   /*
885    *  we would prefer to get the x,y info in the above event.
886    *  At the moment we don't get detail params for "toolkit" events,
887    *  so for testing purposes we use XQueryPointer.  Actual apps
888    *  probably shouldn't do this.
889    */
890   XQueryPointer (display,
891                  DefaultRootWindow (display),
892                  &root_return, &child_return,
893                  &x, &y,
894                  &win_x, &win_y,
895                  &mask_return);
896
897   fprintf (stderr, "screen review event %s at %d, %d\n", event->type,
898            x, y);
899   fprintf (stderr, "[%s]\n", 
900            get_screen_review_line_at (x, y));
901 }
902
903 void
904 test_exit ()
905 {
906   SPI_deregisterGlobalEventListenerAll (mouseclick_listener);
907   AccessibleEventListener_unref (mouseclick_listener);
908 }