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