1 /* DBWfeedback.c -
2  *
3  *	This file provides a standard set of procedures for Magic
4  *	commands to use to provide feedback to users.  Feedback
5  *	consists of areas of the screen that are highlighted, along
6  *	with text describing why those particular areas are important.
7  *	Feedback is used for things like displaying CIF, and for errors
8  *	in CIF-generation and routing.
9  *
10  *     *********************************************************************
11  *     * Copyright (C) 1985, 1990 Regents of the University of California. *
12  *     * Permission to use, copy, modify, and distribute this              *
13  *     * software and its documentation for any purpose and without        *
14  *     * fee is hereby granted, provided that the above copyright          *
15  *     * notice appear in all copies.  The University of California        *
16  *     * makes no representations about the suitability of this            *
17  *     * software for any purpose.  It is provided "as is" without         *
18  *     * express or implied warranty.  Export of this software outside     *
19  *     * of the United States of America may require an export license.    *
20  *     *********************************************************************
21  */
22 
23 #ifndef lint
24 static char rcsid[] __attribute__ ((unused)) = "$Header: /usr/cvsroot/magic-8.0/dbwind/DBWfdback.c,v 1.2 2010/06/24 12:37:16 tim Exp $";
25 #endif  /* not lint */
26 
27 #include <stdio.h>
28 #include <string.h>
29 
30 #include "utils/magic.h"
31 #include "utils/geometry.h"
32 #include "tiles/tile.h"
33 #include "utils/hash.h"
34 #include "database/database.h"
35 #include "windows/windows.h"
36 #include "graphics/graphics.h"
37 #include "dbwind/dbwind.h"
38 #include "utils/utils.h"
39 #include "utils/styles.h"
40 #include "utils/malloc.h"
41 #include "utils/signals.h"
42 
43 /* Use a reference-counted character structure for feedback info */
44 
45 typedef struct rcstring
46 {
47     int refcount;
48     char *string;
49 } RCString;
50 
51 
52 /* Each feedback area is stored in a record that looks like this: */
53 
54 typedef struct feedback
55 {
56     Rect fb_area;		/* The area to be highlighted, in coords of
57 				 * fb_rootDef, but prior to scaling by
58 				 * fb_scale.
59 				 */
60     Rect fb_rootArea;		/* The area of the feedback, in Magic coords.
61 				 * of fb_rootDef, scaled up to the next
62 				 * integral Magic unit.
63 				 */
64     RCString *fb_text;		/* Text explanation for the feedback. */
65     CellDef *fb_rootDef;	/* Root definition of windows in which to
66 				 * display this feedback.
67 				 */
68     int fb_scale;		/* Scale factor to use in redisplaying
69 				 * (fb_scale units in fb_area correspond
70 				 * to one unit in fb_rootDef).
71 				 */
72     int fb_style;		/* Display style to use for this feedback. */
73 } Feedback;
74 
75 /* The following stuff describes all the feedback information we know
76  * about.  The feedback is stored in a big array that grows whenever
77  * necessary.
78  */
79 
80 static Feedback *dbwfbArray = NULL;	/* Array holding all feedback info. */
81 global int DBWFeedbackCount = 0;	/* Number of active entries in
82 					 * dbwfbArray.
83 					 */
84 static int dbwfbSize = 0;		/* Size of dbwfbArray, in entries. */
85 static int dbwfbNextToShow = 0;		/* Index of first feedback area that
86 					 * hasn't been displayed yet.  Used by
87 					 * DBWFBShow.
88 					 */
89 static CellDef *dbwfbRootDef;		/* To pass root cell definition from
90 					 * dbwfbGetTransform back up to
91 					 * DBWFeedbackAdd.
92 					 */
93 
94 
95 /*
96  * ----------------------------------------------------------------------------
97  *
98  * DBWFeedbackRedraw --
99  *
100  * 	This procedure is called by the highlight manager to redisplay
101  *	feedback highlights.  The window is locked before entry.
102  *
103  * Results:
104  *	None.
105  *
106  * Side effects:
107  *	Any feedback information that overlaps a non-space tile in
108  *	plane is redrawn.
109  *
110  * Tricky stuff:
111  *	Redisplay is numerically difficult, particularly when feedbacks
112  *	have a large internal scale factor:  the tendency is to get
113  *	integer overflow and get everything goofed up.  Be careful
114  *	when making changes to the code below.
115  *
116  * ----------------------------------------------------------------------------
117  */
118 
119 void
DBWFeedbackRedraw(window,plane)120 DBWFeedbackRedraw(window, plane)
121     MagWindow *window;		/* Window in which to redraw. */
122     Plane *plane;		/* Non-space tiles on this plane mark what
123 				 * needs to be redrawn.
124 				 */
125 {
126     dlong x, y;
127     int i, halfScale, curStyle, newStyle, curScale;
128     CellDef *windowRoot;
129     Rect worldArea, screenArea, tmp;
130     Feedback *fb;
131     extern int dbwFeedbackAlways1();	/* Forward reference. */
132 
133     if (DBWFeedbackCount == 0) return;
134 
135     windowRoot = ((CellUse *) (window->w_surfaceID))->cu_def;
136     curStyle = -1;
137     curScale = -1;
138     for (i = 0, fb = dbwfbArray; i < DBWFeedbackCount; i++, fb++)
139     {
140 	/* We expect that most of the feedbacks will have the same
141 	 * style and scale, so compute information with the current
142 	 * values, and recompute only when the values change.
143 	 */
144 	if (fb->fb_scale != curScale)
145 	{
146 	    curScale = fb->fb_scale;
147 	    halfScale = curScale / 2;
148 	    worldArea.r_xbot = window->w_surfaceArea.r_xbot * curScale;
149 	    worldArea.r_xtop = window->w_surfaceArea.r_xtop * curScale;
150 	    worldArea.r_ybot = window->w_surfaceArea.r_ybot * curScale;
151 	    worldArea.r_ytop = window->w_surfaceArea.r_ytop * curScale;
152 	}
153 
154 	/*
155 	 * Check to make sure this feedback area is relevant.
156 	 * Clip it to TiPlaneRect as a sanity check against
157 	 * callers who provide a feedback area that extends
158 	 * out somewhere in never-never land.
159 	 */
160 	if (fb->fb_rootDef != windowRoot) continue;
161 	tmp = fb->fb_rootArea;
162 	GeoClip(&tmp, &TiPlaneRect);
163 	if (!DBSrPaintArea((Tile *) NULL, plane, &tmp,
164 		&DBAllButSpaceBits, dbwFeedbackAlways1, (ClientData) NULL))
165 	    continue;
166 
167 	/* Transform the feedback area to screen coords.  This is
168 	 * very similar to the code in WindSurfaceToScreen, except
169 	 * that there's additional scaling involved.
170 	 */
171 
172 	tmp = fb->fb_area;
173 
174 	/* Don't clip non-Manhattan tiles, or else the diagonals are	*/
175 	/* clipped to an incorrect position.				*/
176 
177 	if (!(fb->fb_style & TT_DIAGONAL))
178 	GeoClip(&tmp, &worldArea);
179 
180 	/*
181 	 * Ignore areas which have been clipped out of existence
182 	 */
183 	if (tmp.r_xtop < tmp.r_xbot || tmp.r_ytop < tmp.r_ybot)
184 	    continue;
185 
186 	x = ((dlong)(tmp.r_xbot - worldArea.r_xbot) * window->w_scale) + halfScale;
187 	screenArea.r_xbot = (int)((x/curScale + window->w_origin.p_x) >> SUBPIXELBITS);
188 	x = ((dlong)(tmp.r_xtop - worldArea.r_xbot) * window->w_scale) + halfScale;
189 	screenArea.r_xtop = (int)((x/curScale + window->w_origin.p_x) >> SUBPIXELBITS);
190 	y = ((dlong)(tmp.r_ybot - worldArea.r_ybot) * window->w_scale) + halfScale;
191 	screenArea.r_ybot = (int)((y/curScale + window->w_origin.p_y) >> SUBPIXELBITS);
192 	y = ((dlong)(tmp.r_ytop - worldArea.r_ybot) * window->w_scale) + halfScale;
193 	screenArea.r_ytop = (int)((y/curScale + window->w_origin.p_y) >> SUBPIXELBITS);
194 
195 	/*
196 	 * Sanity check on integer bounds
197 	 */
198 	if (screenArea.r_xtop < screenArea.r_xbot ||
199 	    screenArea.r_ytop < screenArea.r_ybot)
200 	{
201 	    TxError("Internal error: Feedback area exceeds integer bounds "
202 		"on screen rectangle!\n");
203 	    continue;
204 	}
205 
206 	newStyle = fb->fb_style & (TT_LEFTMASK | TT_RIGHTMASK);
207 
208 	/* Another little trick:  when the feedback area is very small ("very
209 	 * small" is a hand-tuned constant), change all stippled styles to
210 	 * solid.
211 	 * (The usefulness of this trick is questionable, as it generally
212 	 * creates very bothersome output for the "cif see" function and
213 	 * other places where the feedback is generated from tiles.)
214 	 */
215 /*
216 	if (((GEO_WIDTH(&screenArea) < 18) || (GEO_HEIGHT(&screenArea) < 18))
217 			&& ((newStyle == STYLE_MEDIUMHIGHLIGHTS)
218 			|| (newStyle == STYLE_PALEHIGHLIGHTS)))
219 	    newStyle = STYLE_SOLIDHIGHLIGHTS;
220 */
221 	if (newStyle != curStyle)
222 	{
223 	    curStyle = newStyle;
224 	    GrSetStuff(curStyle);
225 	}
226 
227 	if (fb->fb_style & TT_DIAGONAL)
228 	    GrDiagonal(&screenArea, fb->fb_style);
229 
230 	/* Special case (i.e., "hack")---to get a diagonal line	*/
231 	/* instead of a triangle, set TT_SIDE but not		*/
232 	/* TT_DIAGONAL.	 TT_DIRECTION still indicates / or \	*/
233 
234 	else if (fb->fb_style & TT_SIDE)
235 	{
236 	    if (fb->fb_style & TT_DIRECTION)
237 	        GrClipLine(screenArea.r_xbot, screenArea.r_ytop,
238 			screenArea.r_xtop, screenArea.r_ybot);
239 	    else
240 	        GrClipLine(screenArea.r_xbot, screenArea.r_ybot,
241 			screenArea.r_xtop, screenArea.r_ytop);
242 	}
243 	else
244 	    GrFastBox(&screenArea);
245     }
246 }
247 
248 int
dbwFeedbackAlways1()249 dbwFeedbackAlways1()
250 {
251     return 1;
252 }
253 
254 /*
255  * ----------------------------------------------------------------------------
256  *
257  * DBWFeedbackClear --
258  *
259  * 	This procedure clears all existing feedback information from
260  *	the screen.
261  *
262  * Results:
263  *	None.
264  *
265  * Side effects:
266  *	Any existing feedback information is cleared from the screen
267  *	and from our database.
268  *
269  * ----------------------------------------------------------------------------
270  */
271 
272 void
DBWFeedbackClear(text)273 DBWFeedbackClear(text)
274     char *text;
275 {
276     int i, oldCount;
277     Feedback *fb, *fl, *fe;
278     Rect area;
279     CellDef *currentRoot;
280     RCString *quickptr = NULL;
281 
282     /* Clear out the feedback array and recycle string storage.  Whenever
283      * the root cell changes, make a call to erase from the screen.
284      */
285 
286     currentRoot = (CellDef *) NULL;
287     oldCount = DBWFeedbackCount;
288     DBWFeedbackCount = 0;
289     for (fb = dbwfbArray; fb < dbwfbArray + oldCount; fb++)
290     {
291 	if ((text == NULL) ||
292 		((quickptr != NULL) && (fb->fb_text == quickptr)) ||
293 		(strstr(fb->fb_text->string, text) != NULL))
294 	{
295 	    // Track the last refcount string that matched the
296 	    // text so we can run through lists of matching text
297 	    // without having to call strstr()
298 
299 	    if (text != NULL) quickptr = fb->fb_text;
300 
301 	    if (currentRoot != fb->fb_rootDef)
302 	    {
303 		if (currentRoot != (CellDef *) NULL)
304 		    DBWHLRedraw(currentRoot, &area, TRUE);
305 		area = GeoNullRect;
306 	    }
307 	    (void) GeoInclude(&fb->fb_rootArea, &area);
308 	    currentRoot = fb->fb_rootDef;
309 
310 	    // Decrement refcount and free if zero.
311 	    fb->fb_text->refcount--;
312 	    if (fb->fb_text->refcount == 0)
313 	    {
314 		freeMagic(fb->fb_text->string);
315 		freeMagic(fb->fb_text);
316 	    }
317 	    fb->fb_text = NULL;
318 	}
319     }
320     if (currentRoot != NULL)
321 	DBWHLRedraw(currentRoot, &area, TRUE);
322     dbwfbNextToShow = 0;
323 
324     if (text != NULL)
325     {
326 	/* Close up feedback list around removed text entries */
327 
328 	fl = dbwfbArray;
329 	fe = fl + oldCount;
330 	for (fb = dbwfbArray; fb < fe; fb++)
331 	{
332 	    while ((fb->fb_text == NULL) && (fb < fe)) fb++;
333 	    if (fb < fe) *fl++ = *fb;
334 	}
335 	DBWFeedbackCount = (int)(fl - dbwfbArray);
336 
337 	for (fb = fl; fb < dbwfbArray + oldCount; fb++)
338 	    fb->fb_text = NULL;
339     }
340 
341     /* Free up feedback memory whenever we have removed all feedback entries */
342     if (DBWFeedbackCount == 0)
343     {
344 	if (dbwfbArray != NULL) {
345 	    freeMagic((char *) dbwfbArray);
346 	    dbwfbArray = NULL;
347 	}
348 	dbwfbSize = 0;
349 	return;
350     }
351 }
352 
353 /*
354  * ----------------------------------------------------------------------------
355  *
356  * DBWFeedbackAdd --
357  *
358  * 	Adds a new piece of feedback information to the list we have
359  *	already.
360  *
361  * Results:
362  *	None.
363  *
364  * Side effects:
365  *	CellDef's ancestors are searched until its first root definition
366  *	is found, and the coordinates of area are transformed into the
367  *	root.  Then the feedback area is added to our current list, using
368  *	the style and scalefactor given.  This stuff will be displayed on
369  *	the screen at the end of the current command.
370  * ----------------------------------------------------------------------------
371  */
372 
373 void
DBWFeedbackAdd(area,text,cellDef,scaleFactor,style)374 DBWFeedbackAdd(area, text, cellDef, scaleFactor, style)
375     Rect *area;			/* The area to be highlighted. */
376     char *text;			/* Text associated with the area. */
377     CellDef *cellDef;		/* The cellDef in whose coordinates area
378 				 * is given.
379 				 */
380     int scaleFactor;		/* The coordinates provided for feedback
381 				 * areas are divided by this to produce
382 				 * coordinates in Magic database units.
383 				 * This will probably be 1 most of the time.
384 				 * By making it bigger, say 10, and scaling
385 				 * other coordinates appropriately, it's
386 				 * possible to draw narrow lines on the
387 				 * screen, or to handle CIF, which isn't in
388 				 * exactly the same coordinates as other Magic
389 				 * stuff.
390 				 */
391     int style;			/* A display style to use for the feedback.
392 				 * Use one of:
393 				 * STYLE_OUTLINEHIGHLIGHTS:	solid outlines
394 				 * STYLE_DOTTEDHIGHLIGHTS:	dotted outlines
395 				 * STYLE_SOLIDHIGHLIGHTS:	solid fill
396 				 * STYLE_MEDIUMHIGHLIGHTS:	medium stipple
397 				 * STYLE_PALEHIGHLIGHTS:	pald stipple
398 				 * At very coarse viewing scales, the last
399 				 * two styles are hard to see, so they are
400 				 * turned into STYLE_SOLIDHIGHLIGHTS.
401 				 */
402 {
403     Rect tmp, tmp2, tmp3;
404     Transform transform;
405     int i;
406     Feedback *fb, *fblast;
407     extern int dbwfbGetTransform();	/* Forward declaration. */
408 
409     /* Find a transform from this cell to the root, and use it to
410      * transform the area.  If the root isn't an ancestor, just
411      * return.
412      */
413 
414     if (!DBSrRoots(cellDef, &GeoIdentityTransform,
415 	dbwfbGetTransform, (ClientData) &transform)) return;
416 
417     /* SigInterruptPending screws up DBSrRoots */
418     if (SigInterruptPending)
419 	return;
420 
421     /* Don't get fooled like I did.  The translations for
422      * this transform are in Magic coordinates, not feedback
423      * coordinates.  Scale them into feedback coordinates.
424      */
425 
426     transform.t_c *= scaleFactor;
427     transform.t_f *= scaleFactor;
428     GeoTransRect(&transform, area, &tmp2);
429     area = &tmp2;
430 
431     /* Make sure there's enough space in the current array.  If
432      * not, make a new array, copy the old to the new, then delete
433      * the old array.  Use memcpy() to make sure this happens very fast.
434      */
435 
436     if (DBWFeedbackCount == dbwfbSize)
437     {
438 	Feedback *new;
439 
440 	if (dbwfbSize == 0) dbwfbSize = 32;
441 	else dbwfbSize <<= 1;
442 
443 	new = (Feedback *) mallocMagic(dbwfbSize * sizeof(Feedback));
444 	memcpy((char *)new, (char *)dbwfbArray, DBWFeedbackCount * sizeof(Feedback));
445 
446 	for (i = DBWFeedbackCount; i < dbwfbSize; i++) new[i].fb_text = NULL;
447 
448 	if (dbwfbArray != NULL) freeMagic((char *) dbwfbArray);
449 	dbwfbArray = new;
450     }
451     fb = &dbwfbArray[DBWFeedbackCount];
452     fb->fb_area = *area;
453     fblast = (DBWFeedbackCount == 0) ? NULL : &dbwfbArray[DBWFeedbackCount - 1];
454 
455     if (fblast && (!strcmp(fblast->fb_text->string, text)))
456     {
457 	fb->fb_text = fblast->fb_text;
458 	fb->fb_text->refcount++;
459     }
460     else
461     {
462 	fb->fb_text = (RCString *)mallocMagic(sizeof(RCString));
463 	fb->fb_text->refcount = 1;
464 	fb->fb_text->string = StrDup(NULL, text);
465     }
466 
467     fb->fb_rootDef = dbwfbRootDef;
468     fb->fb_scale = scaleFactor;
469     fb->fb_style = style;
470     DBWFeedbackCount++;
471 
472     /* Round the area up into Magic coords, and save it too. */
473     if (area->r_xtop > 0)
474 	tmp.r_xtop = (area->r_xtop + scaleFactor - 1)/scaleFactor;
475     else tmp.r_xtop = area->r_xtop/scaleFactor;
476     if (area->r_ytop > 0)
477 	tmp.r_ytop = (area->r_ytop + scaleFactor - 1)/scaleFactor;
478     else tmp.r_ytop = area->r_ytop/scaleFactor;
479     if (area->r_xbot > 0) tmp.r_xbot = area->r_xbot/scaleFactor;
480     else tmp.r_xbot = (area->r_xbot - scaleFactor + 1)/scaleFactor;
481     if (area->r_ybot > 0) tmp.r_ybot = area->r_ybot/scaleFactor;
482     else tmp.r_ybot = (area->r_ybot - scaleFactor + 1)/scaleFactor;
483 
484     /* Clip to ensure well within TiPlaneRect */
485     tmp3.r_xbot = TiPlaneRect.r_xbot + 10;
486     tmp3.r_ybot = TiPlaneRect.r_ybot + 10;
487     tmp3.r_xtop = TiPlaneRect.r_xtop - 10;
488     tmp3.r_ytop = TiPlaneRect.r_ytop - 10;
489     GeoClip(&tmp, &tmp3);
490 
491     fb->fb_rootArea = tmp;
492 }
493 
494 /* This utility procedure is invoked by DBSrRoots.  Save the root definition
495  * in dbwfbRootDef, save the transform in the argument, and abort the search.
496  * Make sure that the root we pick is actually displayed in a window
497  * someplace (there could be root cells that are no longer displayed
498  * anywhere).
499  */
500 
501 int
dbwfbGetTransform(use,transform,cdarg)502 dbwfbGetTransform(use, transform, cdarg)
503     CellUse *use;			/* A root use that is an ancestor
504 					 * of cellDef in DBWFeedbackAdd.
505 					 */
506     Transform *transform;		/* Transform up from cellDef to use. */
507     Transform *cdarg;			/* Place to store transform from
508 					 * cellDef to its root def.
509 					 */
510 {
511     extern int dbwfbWindFunc();
512     if (use->cu_def->cd_flags & CDINTERNAL) return 0;
513     if (!WindSearch((ClientData) DBWclientID, (ClientData) use,
514 	    (Rect *) NULL, dbwfbWindFunc, (ClientData) NULL)) return 0;
515     if (SigInterruptPending)
516 	return 0;
517     dbwfbRootDef = use->cu_def;
518     *cdarg = *transform;
519     return 1;
520 }
521 
522 /* This procedure is called if a window is found for the cell in
523  * dbwfbGetTransform above.  It returns 1 to abort the search and
524  * notify dbwfbGetTransform that there was a window for that root
525  * cell.
526  */
527 
528 int
dbwfbWindFunc()529 dbwfbWindFunc()
530 {
531     return 1;
532 }
533 
534 /*
535  * ----------------------------------------------------------------------------
536  *
537  * Client initialization
538  *
539  * ----------------------------------------------------------------------------
540  */
541 
542 void
dbwFeedbackInit()543 dbwFeedbackInit()
544 {
545     DBWHLAddClient(DBWFeedbackRedraw);
546 }
547 
548 
549 /*
550  * ----------------------------------------------------------------------------
551  *
552  * DBWFeedbackShow --
553  *
554  * 	Causes new feedback information actually to be displayed on
555  *	the screen.
556  *
557  * Results:
558  *	None.
559  *
560  * Side effects:
561  *	All new feedback information that has been created since the
562  *	last call to this procedure is added to the display.
563  *
564  * ----------------------------------------------------------------------------
565  */
566 
567 void
DBWFeedbackShow()568 DBWFeedbackShow()
569 {
570     Rect area;
571     CellDef *currentRoot;
572     Feedback *fb;
573     int i;
574 
575     /* Scan through all of the feedback areas starting with dbwfbNextToShow.
576      * Save up the total bounding box until the root definition changes,
577      * then redisplay what's been saved up so far.
578      */
579 
580     currentRoot = NULL;
581     for (i = dbwfbNextToShow, fb = &(dbwfbArray[dbwfbNextToShow]);
582 	i < DBWFeedbackCount; i++, fb++)
583     {
584 	if (currentRoot != fb->fb_rootDef)
585 	{
586 	    if (currentRoot != NULL)
587 		DBWHLRedraw(currentRoot, &area, FALSE);
588 	    area = GeoNullRect;
589 	}
590 	(void) GeoInclude(&fb->fb_rootArea, &area);
591 	currentRoot = fb->fb_rootDef;
592     }
593     if (currentRoot != NULL)
594 	DBWHLRedraw(currentRoot, &area, FALSE);
595     dbwfbNextToShow = DBWFeedbackCount;
596 }
597 
598 /*
599  * ----------------------------------------------------------------------------
600  *
601  * DBWFeedbackNth --
602  *
603  * 	Provides the area and text associated with a particular
604  *	feedback area.
605  *
606  * Results:
607  *	Returns a pointer to the text associated with the Nth feedback
608  *	entry.
609  *
610  * Side effects:
611  *	The parameter "area" is filled with the area of the nth
612  *	feedback, and the text of that feedback is returned. *pRootDef
613  *	is filled in with rootDef for window of feedback area.  *pStyle
614  *	is filled in with the display style for the feedback area.  If
615  *	the particular area doesn't exist (nth >= DBWFeedbackCount),
616  *	area and *pRootDef  and *pStyle are untouched and NULL is
617  *	returned.  NULL	may also be returned if there simply wasn't
618  *	any text associated with the selected feedback.
619  *
620  * ----------------------------------------------------------------------------
621  */
622 
623 char *
DBWFeedbackNth(nth,area,pRootDef,pStyle)624 DBWFeedbackNth(nth, area, pRootDef, pStyle)
625     int nth;			/* Selects which feedback area to return
626 				 * stuff from.  (0 <= nth < DBWFeedbackCount)
627 				 */
628     Rect *area;			/* To be filled in with area of feedback, in
629 				 * rounded-outward Magic coordinates.
630 				 */
631     CellDef **pRootDef;		/* *pRootDef gets filled in with root def for
632 				 * this feedback area.  If pRootDef is NULL,
633 				 * nothing is touched.
634 				 */
635     int *pStyle;		/* *pStyle gets filled in with the display
636 				 * style for this feedback area.  If NULL,
637 				 * nothing is touched.
638 				 */
639 {
640     if (nth >= DBWFeedbackCount) return NULL;
641     *area = dbwfbArray[nth].fb_rootArea;
642     if (pRootDef != NULL) *pRootDef = dbwfbArray[nth].fb_rootDef;
643     if (pStyle != NULL) *pStyle = dbwfbArray[nth].fb_style;
644     return dbwfbArray[nth].fb_text->string;
645 }
646