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