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