* test/screen-review-test.c: Fix crashes in debug statements
[platform/upstream/at-spi2-core.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", 
273                  chunk->string ? chunk->string : "<null>",
274                  iter_chunk->string ? iter_chunk->string : "<null>");
275 #endif  
276         chunk_list = g_list_insert_before (chunk_list, iter, iter_copy);
277         text_chunk_tail_clip (iter_copy, chunk);
278         chunk_list = g_list_insert_before (chunk_list, iter, chunk);
279         text_chunk_head_clip (iter_chunk, chunk);
280         return chunk_list;
281 }
282
283 /* #define PRINT_CHUNK_DEBUG(a, b, c, d) print_chunk_debug(a, b, c, d) */
284
285 #define PRINT_CHUNK_DEBUG(a, b, c, d) 
286
287 #ifdef PRINT_CHUNK_DEBUG
288 static void
289 print_chunk_debug (TextChunk *chunk, char *opname, GList *prev, GList *next)
290 {
291         fprintf (stderr, "%sing chunk %s between %s and %s; %d-%d\n",
292                  opname,
293                  chunk->string,
294                  (prev ? ((TextChunk *) prev->data)->string : "<null>"),
295                  (next ? ((TextChunk *) next->data)->string : "<null>"),
296                  chunk->clip_bounds.x,
297                  chunk->text_bounds.x + chunk->text_bounds.width);
298 }
299 #endif
300
301 static GList *
302 text_chunk_list_head_clip (GList *text_chunk_list,
303                            TextChunk *chunk,
304                            GList *next)
305 {
306         GList *target, *iter = next, *prev;
307         prev = iter->prev;
308 //      if (chunk->string && strlen (chunk->string)) { 
309                 text_chunk_list =
310                         g_list_insert_before (text_chunk_list, next, chunk);
311 //      }
312         while (iter) {
313                 if (CHUNK_BOUNDS_SPANS_END (chunk, (TextChunk *)iter->data)) {
314 #ifdef CLIP_DEBUG                       
315                         fprintf (stderr, "deleting %s\n",
316                                  ((TextChunk *)iter->data)->string);
317 #endif                  
318                         target = iter;
319                         iter = iter->next;
320                         text_chunk_list =
321                                 g_list_delete_link (text_chunk_list, target);
322                 } else {
323                         if (!CHUNK_BOUNDS_END_BEFORE_START (chunk,
324                                                       (TextChunk *)iter->data)) {
325                                 text_chunk_head_clip ((TextChunk *)iter->data,
326                                                       chunk);
327                         }
328                         if (prev &&
329                             !CHUNK_BOUNDS_AFTER_END (
330                                     chunk,
331                                     (TextChunk *)prev->data)) {
332                                 text_chunk_tail_clip (
333                                         (TextChunk *)prev->data,
334                                         chunk);
335                         }
336                         break;
337                 }
338         }
339         
340         return text_chunk_list;
341 }
342
343 static GList *
344 text_chunk_list_clip_and_insert (GList *text_chunk_list,
345                                  TextChunk *chunk,
346                                  GList *prev,
347                                  GList *next)
348 {
349 #ifdef CLIP_DEBUG
350         if (chunk->string)
351                 fprintf (stderr, "clip-and-insert for %s, between %s and %s\n",
352                          chunk->string,
353                          (prev && ((TextChunk *)prev->data)->string ? ((TextChunk *)prev->data)->string : "<null>"),
354                          (next && ((TextChunk *)next->data)->string ? ((TextChunk *)next->data)->string : "<null>"));
355 #endif
356         /* cases: */
357         if (!prev && !next) { /* first element in, no clip needed */
358 //              if (chunk->string && strlen (chunk->string)) {
359                         text_chunk_list =
360                                 g_list_append (text_chunk_list, chunk);
361                         PRINT_CHUNK_DEBUG (chunk, "append",
362                                            prev,
363                                            NULL);
364 //              }
365         } else { /* check for clip with prev */
366                 /* if we split the prev */
367                 if (prev &&
368                     CHUNK_BOUNDS_WITHIN (chunk, (TextChunk *) prev->data)) {
369                         text_chunk_list =
370                                 text_chunk_split_insert (
371                                         text_chunk_list,
372                                         prev, chunk);
373                 } else if (next) { 
374                     /* we split the 'next' element */
375                     if (CHUNK_BOUNDS_WITHIN (chunk, (TextChunk *)next->data)) {
376                             text_chunk_list =
377                                     text_chunk_split_insert (text_chunk_list,
378                                                              next, chunk);
379                     } else {
380                             /* do an insert +  head clip */
381                             text_chunk_list =
382                                     text_chunk_list_head_clip (
383                                             text_chunk_list,
384                                             chunk,
385                                             next);
386                     }
387                 } else {
388                         if (!CHUNK_BOUNDS_AFTER_END (chunk,
389                                                      (TextChunk *) prev->data)) {
390                                 text_chunk_tail_clip (prev->data, chunk);
391                         }
392 //                      if (chunk->string && strlen (chunk->string)) {
393                                 text_chunk_list =
394                                         g_list_append (text_chunk_list, chunk);
395 //                      }
396                 }
397         }
398         return text_chunk_list;
399 }
400
401 static GList *
402 text_chunk_list_insert_chunk (GList *text_chunk_list, TextChunk *chunk)
403 {
404         GList *iter = g_list_first (text_chunk_list);
405         TextChunk *iter_chunk = NULL;
406         do {
407                 if (iter) iter_chunk = (TextChunk *) iter->data;
408                 /* if we're ahead of the current element */
409                 if (!iter) {
410                         text_chunk_list =
411                                 text_chunk_list_clip_and_insert (text_chunk_list,
412                                                                  chunk,
413                                                                  iter,
414                                                                  NULL);
415                         break;
416                 } else if (CHUNK_BOUNDS_BEFORE_START (chunk, iter_chunk)) {
417                         text_chunk_list =
418                                 text_chunk_list_clip_and_insert (text_chunk_list,
419                                                                  chunk,
420                                                                  iter->prev,
421                                                                  iter);
422                         break;
423                 } else if (!iter->next ) {
424                         text_chunk_list =
425                                 text_chunk_list_clip_and_insert (text_chunk_list,
426                                                                  chunk,
427                                                                  iter,
428                                                                  NULL);
429                         break;
430                 }
431                 if (iter) iter = iter->next;
432         } while (iter);
433         return text_chunk_list;
434 }
435
436 static TextChunk *
437 review_buffer_get_text_chunk (ScreenReviewBuffer *reviewBuffer,
438                               Accessible *accessible, BoundaryRect *bounds,
439                               int screen_x, int screen_y)
440 {
441         AccessibleText *text = NULL;
442         AccessibleRole role;
443         TextChunk *text_chunk;
444         BoundaryRect start_char_bounds, end_char_bounds;
445         char *s = NULL;
446         int offset;
447         long start, end;
448         long x2, y2;
449
450         role = Accessible_getRole (accessible);
451         text_chunk = g_new0 (TextChunk, 1);
452         text_chunk->clip_bounds = *bounds;
453         if (Accessible_isText (accessible)) {
454                 text = Accessible_getText (accessible);
455                 offset = AccessibleText_getOffsetAtPoint (text,
456                                                           screen_x,
457                                                           screen_y,
458                                                           SPI_COORD_TYPE_SCREEN);
459                 s = AccessibleText_getTextAtOffset (text, offset,
460                                                     SPI_TEXT_BOUNDARY_LINE_START,
461                                                     &start, &end);
462                 if (end > start) {
463                         AccessibleText_getCharacterExtents (
464                                 text, start,
465                                 &text_chunk->start_char_bounds.x,
466                                 &text_chunk->start_char_bounds.y,
467                                 &text_chunk->start_char_bounds.width,
468                                 &text_chunk->start_char_bounds.height,
469                                 SPI_COORD_TYPE_SCREEN);
470 #ifdef CLIP_DEBUG
471                         fprintf (stderr, "%s: start char (%d) x, width %d %d;",
472                                  s,
473                                  start,
474                                  text_chunk->start_char_bounds.x,
475                                  text_chunk->start_char_bounds.width);
476 #endif
477                         end--; /* XXX: bug workaround */
478                         AccessibleText_getCharacterExtents (
479                                 text, end--,
480                                 &text_chunk->end_char_bounds.x,
481                                 &text_chunk->end_char_bounds.y,
482                                 &text_chunk->end_char_bounds.width,
483                                 &text_chunk->end_char_bounds.height,
484                                 SPI_COORD_TYPE_SCREEN);
485 #ifdef CLIP_DEBUG                       
486                         fprintf (stderr, "end char (%d) x, width %d %d\n",
487                                  end,
488                                  text_chunk->end_char_bounds.x,
489                                  text_chunk->end_char_bounds.width);
490 #endif
491                 }
492                 text_chunk->text_bounds.x = MIN (text_chunk->start_char_bounds.x,
493                                                  text_chunk->end_char_bounds.x);
494                 text_chunk->text_bounds.y = MIN (text_chunk->start_char_bounds.y,
495                                                  text_chunk->end_char_bounds.y);
496                 x2 = MAX (text_chunk->start_char_bounds.x +
497                           text_chunk->start_char_bounds.width,
498                           text_chunk->end_char_bounds.x +
499                           text_chunk->end_char_bounds.width);
500                 text_chunk->text_bounds.width = x2 - text_chunk->text_bounds.x;
501                 y2 = MAX (text_chunk->start_char_bounds.y +
502                           text_chunk->start_char_bounds.height,
503                           text_chunk->end_char_bounds.y + 
504                           text_chunk->end_char_bounds.height);
505                 text_chunk->text_bounds.height = y2 - text_chunk->text_bounds.y;
506                 text_chunk->start_offset = start;
507                 text_chunk->end_offset = end;
508                 if ((role != SPI_ROLE_TABLE_CELL) /* XXX bug workaround */
509                     && !bounds_contain_y (&text_chunk->text_bounds,
510                                           screen_y)) {
511                         s = NULL;
512                 }
513         } else {
514                 if (role == SPI_ROLE_PUSH_BUTTON ||
515                     role == SPI_ROLE_CHECK_BOX ||
516                     role == SPI_ROLE_MENU ||
517                     role == SPI_ROLE_MENU_ITEM) { /* don't like this
518                                                      special casing :-( */
519                         s = Accessible_getName (accessible);
520                         /* use name instead */
521                         text_chunk->text_bounds = text_chunk->clip_bounds;
522                         text_chunk->start_offset = 0;
523                         text_chunk->end_offset = strlen (s);
524                 }
525         }
526         if (s && strlen (s)) {
527                 if (s[strlen(s)-1] == '\n') s[strlen(s)-1] = ' ';
528                 /* XXX: if last char is newline, aren't its bounds wrong now? */
529                 text_chunk->string = s;
530                 text_chunk->source = text;
531                 if (text) AccessibleText_ref (text);
532 #ifdef CLIP_DEBUG
533                 fprintf (stderr, "%s, bounds %d-%d; clip %d-%d\n",
534                          s,
535                          text_chunk->text_bounds.x,
536                          text_chunk->text_bounds.x+text_chunk->text_bounds.width,
537                          text_chunk->clip_bounds.x,
538                          text_chunk->clip_bounds.x+text_chunk->clip_bounds.width);
539 #endif          
540         } else {
541                 text_chunk->string = NULL;
542                 text_chunk->source = NULL;
543         }
544         if (text) AccessibleText_unref (text);
545         return text_chunk;
546 }
547
548 static void
549 debug_chunk_list (GList *iter)
550 {
551         TextChunk *chunk;
552         while (iter) {
553                 chunk = (TextChunk *)iter->data;
554                 fprintf (stderr, "Chunk %s, clip %d-%d, text %d-%d\n",
555                          chunk->string,
556                          chunk->clip_bounds.x,
557                          chunk->clip_bounds.x + chunk->clip_bounds.width,
558                          chunk->text_bounds.x,
559                          chunk->text_bounds.x + chunk->text_bounds.width);
560                 iter = iter->next;
561         }
562 }
563
564 static void
565 clip_into_buffers (Accessible *accessible,  BoundaryRect* parentClipBounds[],
566                    ScreenReviewBuffer *reviewBuffers[],
567                    int screen_x, int screen_y)
568 {
569         int n_children, child_n;
570         Accessible *child;
571         BoundaryRect bounds;
572         BoundaryRect** clip_bounds;
573         TextChunk *text_chunk;
574         AccessibleComponent *component;
575         int layer;
576
577         clip_bounds = clip_bounds_clone (parentClipBounds);
578         if (Accessible_isComponent (accessible)) {
579                 component = Accessible_getComponent (accessible);
580                 layer = AccessibleComponent_getLayer (component);
581                 bounds = *clip_bounds[layer];
582                 if (!bounds.isEmpty || 1) {
583                         AccessibleComponent_getExtents (component,
584                                                         &bounds.x,
585                                                         &bounds.y,
586                                                         &bounds.width,
587                                                         &bounds.height,
588                                                         SPI_COORD_TYPE_SCREEN);
589                         if (clip_bounds[layer])
590                                 boundary_clip (&bounds, clip_bounds[layer]);
591                         if (bounds_contain_y (&bounds, screen_y)) {
592                                 text_chunk = review_buffer_get_text_chunk (
593                                         reviewBuffers[layer], accessible, &bounds,
594                                         screen_x, screen_y);
595                                 reviewBuffers[layer]->text_chunks = 
596                                         text_chunk_list_insert_chunk (
597                                                 reviewBuffers[layer]->text_chunks,
598                                                 text_chunk);
599                         } else {
600                                 clip_bounds[layer]->isEmpty = TRUE;
601                         }
602                 } 
603                 Accessible_unref (component);
604         }
605         /*
606          * we always descend into children in case they are in a higher layer
607          * this can of course be optimized for the topmost layer...
608          * but nobody uses that one! (SPI_LAYER_OVERLAY)
609          */
610         n_children = Accessible_getChildCount (accessible);
611         for (child_n = 0; child_n < n_children; ++child_n) {
612                 child = Accessible_getChildAtIndex (accessible, child_n);
613                 clip_into_buffers (child, clip_bounds, reviewBuffers, screen_x, screen_y);
614                 Accessible_unref (child);
615         }
616         /* TODO: free the parent clip bounds */
617 }
618
619 static char*
620 text_chunk_get_clipped_string (TextChunk *chunk)
621 {
622         char *s;
623         int i;
624         int len;
625         gunichar c;
626         GString *string = g_string_new ("");
627         BoundaryRect char_bounds;
628         if (!chunk->text_bounds.isClipped)
629                 s = chunk->string;
630         else if (chunk->source) {
631                 len = chunk->end_offset - chunk->start_offset;
632 #ifdef CLIP_DEBUG               
633                 fprintf (stderr, "clipping %s\n", chunk->string);
634 #endif          
635                 for (i = 0; i < len; ++i) {
636                         AccessibleText_getCharacterExtents (chunk->source,
637                                                             i,
638                                                             &char_bounds.x,
639                                                             &char_bounds.y,
640                                                             &char_bounds.width,
641                                                             &char_bounds.height,
642                                                             SPI_COORD_TYPE_SCREEN);
643 #ifdef CHARACTER_CLIP_DEBUG
644                         fprintf (stderr, "testing %d-%d against %d-%d\n",
645                                  char_bounds.x, char_bounds.x+char_bounds.width,
646                                  chunk->text_bounds.x,
647                                  chunk->text_bounds.x + chunk->text_bounds.width);
648 #endif
649                         if (BOUNDS_CONTAIN_X (&chunk->text_bounds,
650                                               char_bounds.x)) {
651                                 c = AccessibleText_getCharacterAtOffset (
652                                         chunk->source, i);
653 #ifdef CLIP_DEBUG                               
654                                 fprintf (stderr, "[%c]", c);
655 #endif                          
656                                 g_string_append_unichar (string, c);
657                         }
658                 }
659                 s = string->str;
660         } else { /* we're clipped, but don't implement AccessibleText :-( */
661                 /* punt for now, maybe we can do betterc someday */
662                 s = chunk->string;
663         }
664         g_string_free (string, FALSE);
665         return s;
666 }
667
668 static char*
669 text_chunk_list_to_string (GList *iter)
670 {
671         char *s = "";
672         char *string;
673         TextChunk *chunk = NULL;
674         while (iter) {
675                 chunk = (TextChunk *)iter->data;
676                 if (chunk /* && chunk->string */) {
677                         string = text_chunk_get_clipped_string (chunk);
678                         if (string)
679                                 s = g_strconcat (s, "|", string, NULL);
680                         else /* XXX test */
681                                 s = g_strconcat (s, ":", NULL);
682                 }
683                 iter = iter->next;
684         }
685         s = g_strconcat (s, "|", NULL);
686         return s;
687 }
688
689 static char*
690 review_buffer_composite (ScreenReviewBuffer *buffers[])
691 {
692         int i;
693         GList *chunk_list, *iter;
694         TextChunk *chunk;
695         chunk_list = buffers[0]->text_chunks;
696         for (i = 1; i < SPI_LAYER_LAST_DEFINED; ++i) {
697                 iter = buffers[i]->text_chunks;
698                 fprintf (stderr, "layer %d has %d chunks\n",
699                          i, g_list_length (iter));
700                 while (iter) {
701                         chunk = (TextChunk *) iter->data;
702                         if (chunk) {
703                                 fprintf (stderr, "inserting chunk <%s>\n",
704                                          chunk->string ? chunk->string : "<null>");
705                                 chunk_list =
706                                         text_chunk_list_insert_chunk (chunk_list,
707                                                                       chunk);
708                         }
709                         iter = iter->next;
710                 }
711         }
712         chunk_list = buffers[SPI_LAYER_WIDGET]->text_chunks;
713         return text_chunk_list_to_string (chunk_list);
714 }
715
716 static char *
717 get_screen_review_line_at (int x, int y)
718 {
719   char *string;
720   Accessible *desktop, *app, *toplevel, *child;
721   AccessibleComponent *component;
722   AccessibleStateSet *states;
723   GList *toplevels = NULL, *actives = NULL, *iter;
724   ScreenReviewBuffer* reviewBuffers[SPI_LAYER_LAST_DEFINED];
725   BoundaryRect* clip_bounds[SPI_LAYER_LAST_DEFINED];
726   BoundaryRect toplevel_bounds;
727   int n_apps, n_toplevels, n_children, app_n, toplevel_n, child_n;
728   GTimer *timer = g_timer_new ();
729   int i;
730
731   for (i = 0; i < SPI_LAYER_LAST_DEFINED; ++i) {
732           reviewBuffers[i] = g_new0 (ScreenReviewBuffer, 1);
733           clip_bounds[i] = g_new0 (BoundaryRect, 1);
734           clip_bounds[i]->isClipped = FALSE;
735           clip_bounds[i]->isEmpty = FALSE;
736   }
737   
738   /* how do we decide which desktop ? */
739   desktop = SPI_getDesktop (0);
740   
741   /* for each app */
742   n_apps = Accessible_getChildCount (desktop);
743   for (app_n = 0; app_n < n_apps; ++app_n) {
744           /* for each toplevel in app */
745           app =  Accessible_getChildAtIndex (desktop, app_n);
746           n_toplevels = Accessible_getChildCount (app);
747           for (toplevel_n = 0; toplevel_n < n_toplevels; ++toplevel_n) {
748                   Accessible *toplevel = Accessible_getChildAtIndex (app, toplevel_n);
749                   if (Accessible_isComponent (toplevel))
750                           toplevels = g_list_prepend (toplevels, toplevel);
751                   else {
752                           Accessible_unref (toplevel);
753                           fprintf (stderr, "warning, app toplevel not a component.\n");
754                   }
755           }
756   }
757   
758   /* sort: at the moment we don't have a good way to sort except to put actives on top */
759   for (iter = g_list_first (toplevels); iter; iter = iter->next) {
760           Accessible *toplevel =
761                   (Accessible *) iter->data;
762           if (AccessibleStateSet_contains (Accessible_getStateSet (toplevel),
763                                            SPI_STATE_ACTIVE)) {
764                   actives = g_list_prepend (actives, toplevel);
765           }
766   }
767
768   for (iter = g_list_first (actives); iter; iter = actives->next) {
769           toplevels = g_list_remove (toplevels, iter->data); /* place at end */
770           toplevels = g_list_append (toplevels, iter->data);
771   }
772   g_list_free (actives);
773
774   /* for each toplevel, ending with the active one(s),
775    * clip against children, putting results into appropriate charBuffer.
776    */
777   for (iter = g_list_first (toplevels); iter; iter = iter->next) {
778           toplevel = (Accessible *) iter->data;
779           if (Accessible_isComponent (toplevel)) {
780               /* make sure toplevel is visible and not iconified or shaded */
781               states = Accessible_getStateSet (toplevel);
782               if (AccessibleStateSet_contains (states, SPI_STATE_VISIBLE)
783                   && !AccessibleStateSet_contains (states, SPI_STATE_ICONIFIED)
784                   || isJava) { /* isJava hack! */
785                       component = Accessible_getComponent (toplevel);
786                       AccessibleComponent_getExtents (component,
787                                                       &toplevel_bounds.x,
788                                                       &toplevel_bounds.y,
789                                                       &toplevel_bounds.width,
790                                                       &toplevel_bounds.height,
791                                                       SPI_COORD_TYPE_SCREEN);
792                       toplevel_bounds.isEmpty = FALSE;
793                       for (i = 0; i < SPI_LAYER_LAST_DEFINED; ++i) {
794                               *clip_bounds[i] = toplevel_bounds;
795                       }
796 #ifdef CLIP_DEBUG                     
797                       fprintf (stderr, "toplevel clip starting\n");
798                       debug_chunk_list (reviewBuffers[SPI_LAYER_WIDGET]->text_chunks);
799 #endif                
800                       clip_into_buffers (toplevel, clip_bounds,
801                                      reviewBuffers, x, y);
802               }
803           }
804           Accessible_unref (toplevel);
805   }
806
807   string = review_buffer_composite (reviewBuffers);
808
809   /* SIMPLE SINGLE-PASS ALGORITHM:*/
810   /* traverse the tree:
811    *   keep a pointer to outermost instance of each layer
812    *   clip against outermost in same layer
813    *   when this clip occurs, store outermost clipped string in 2d string buffer.
814    *   string buffer may have attributes to mark component bounds, line art,
815    *      or attributes of text being reviewed.
816    *   composite the layers, ignoring NULL chars in the string buffers.
817    *
818    * Limitations:
819    *   sibling clip not correct, text may overwrite if siblings intersect onscreen
820    *   length of resulting text buffer may vary!
821    *
822    * Technical issues:
823    *   no API for ordering toplevels yet, other than knowing which is ACTIVE.
824    *   not much implementation for the LAYER API yet, other than menus.
825    */
826   g_timer_stop (timer);
827   fprintf (stderr, "elapsed time = %f s\n", g_timer_elapsed (timer, NULL));
828   
829   return string;
830 }
831
832 void
833 report_screen_review_line (const AccessibleEvent *event, void *user_data)
834 {
835   static Display *display = NULL;
836   int x, y, win_x, win_y;
837   Window root_return, child_return;
838   unsigned int mask_return;
839   
840   if (!display) display = XOpenDisplay (getenv ("DISPLAY"));
841   /*
842    *  we would prefer to get the x,y info in the above event.
843    *  At the moment we don't get detail params for "toolkit" events,
844    *  so for testing purposes we use XQueryPointer.  Actual apps
845    *  probably shouldn't do this.
846    */
847   XQueryPointer (display,
848                  DefaultRootWindow (display),
849                  &root_return, &child_return,
850                  &x, &y,
851                  &win_x, &win_y,
852                  &mask_return);
853
854   fprintf (stderr, "screen review event %s at %d, %d\n", event->type,
855            x, y);
856   fprintf (stderr, "[%s]\n", 
857            get_screen_review_line_at (x, y));
858 }
859
860 void
861 test_exit ()
862 {
863   SPI_deregisterGlobalEventListenerAll (mouseclick_listener);
864   AccessibleEventListener_unref (mouseclick_listener);
865 }