1 /*
2  * tkTreeUtils.c --
3  *
4  *	This module implements misc routines for treectrl widgets.
5  *
6  * Copyright (c) 2002-2011 Tim Baker
7  */
8 
9 #include "tkTreeCtrl.h"
10 
11 struct dbwinterps {
12     int count;
13 #define DBWIN_MAX_INTERPS 16
14     Tcl_Interp *interps[DBWIN_MAX_INTERPS];
15 };
16 
17 static Tcl_ThreadDataKey dbwinTDK;
18 static CONST char *DBWIN_VAR_NAME = "dbwin";
19 
dbwin_forget_interp(ClientData clientData,Tcl_Interp * interp)20 static void dbwin_forget_interp(ClientData clientData, Tcl_Interp *interp)
21 {
22     struct dbwinterps *dbwinterps =
23 	    Tcl_GetThreadData(&dbwinTDK, sizeof(struct dbwinterps));
24     int i;
25 
26     for (i = 0; i < dbwinterps->count; i++) {
27 	if (dbwinterps->interps[i] == interp) {
28 	    for (; i < dbwinterps->count - 1; i++) {
29 		dbwinterps->interps[i] = dbwinterps->interps[i + 1];
30 	    }
31 	    dbwinterps->count--;
32 	    break;
33 	}
34     }
35 }
36 
dbwin_add_interp(Tcl_Interp * interp)37 void dbwin_add_interp(Tcl_Interp *interp)
38 {
39     struct dbwinterps *dbwinterps =
40 	    Tcl_GetThreadData(&dbwinTDK, sizeof(struct dbwinterps));
41 
42     if (dbwinterps->count < DBWIN_MAX_INTERPS) {
43 	dbwinterps->interps[dbwinterps->count++] = interp;
44 
45 	Tcl_SetAssocData(interp,
46 	    DBWIN_VAR_NAME,
47 	    dbwin_forget_interp,
48 	    NULL);
49     }
50 }
51 
dbwin(char * fmt,...)52 void dbwin(char *fmt, ...)
53 {
54     struct dbwinterps *dbwinterps =
55 	    Tcl_GetThreadData(&dbwinTDK, sizeof(struct dbwinterps));
56     char buf[512];
57     va_list args;
58     int i;
59 
60     if (dbwinterps->count <= 0)
61 	return;
62 
63     va_start(args, fmt);
64     vsnprintf(buf, 512, fmt, args);
65     va_end(args);
66 
67     buf[511] = '\0';
68 
69     for (i = 0; i < dbwinterps->count; i++) {
70 	/* All sorts of nasty stuff could happen here. */
71 	Tcl_SetVar2(dbwinterps->interps[i],
72 	    DBWIN_VAR_NAME,
73 	    NULL,
74 	    buf,
75 	    TCL_GLOBAL_ONLY);
76     }
77 }
78 
79 #if defined(TREECTRL_DEBUG)
80 #ifdef WIN32
81 #include <windows.h>
82 #endif
83 /*
84  *----------------------------------------------------------------------
85  *
86  * TreeCtrl_BreakIntoDebugger --
87  *
88  *	If running in a debugger, call DebugBreak(), otherwise
89  *	panic().
90  *
91  * Results:
92  *	None.
93  *
94  * Side effects:
95  *	None.
96  *
97  *----------------------------------------------------------------------
98  */
99 
100 void
TreeCtrl_BreakIntoDebugger(const char * file,int line)101 TreeCtrl_BreakIntoDebugger(
102     const char *file,
103     int line
104     )
105 {
106 #if defined(WIN32)
107     if (IsDebuggerPresent())
108 	DebugBreak();
109     else
110 #endif
111     panic("Debugger call at %s:%d", file, line);
112 }
113 #endif /* TREECTRL_DEBUG */
114 
115 /*
116  * Forward declarations for procedures defined later in this file:
117  */
118 
119 static int	PadAmountOptionSet _ANSI_ARGS_((ClientData clientData,
120 		Tcl_Interp *interp, Tk_Window tkwin,
121 		Tcl_Obj **value, char *recordPtr, int internalOffset,
122 		char *saveInternalPtr, int flags));
123 static Tcl_Obj *PadAmountOptionGet _ANSI_ARGS_((ClientData clientData,
124 		Tk_Window tkwin, char *recordPtr, int internalOffset));
125 static void	PadAmountOptionRestore _ANSI_ARGS_((ClientData clientData,
126 		Tk_Window tkwin, char *internalPtr,
127 		char *saveInternalPtr));
128 static void	PadAmountOptionFree _ANSI_ARGS_((ClientData clientData,
129 		Tk_Window tkwin, char *internalPtr));
130 
131 /*
132  * The following Tk_ObjCustomOption structure can be used as clientData entry
133  * of a Tk_OptionSpec record with a TK_OPTION_CUSTOM type in the form
134  * "(ClientData) &TreeCtrlCO_pad"; the option will then parse list with
135  * one or two screen distances.
136  */
137 
138 Tk_ObjCustomOption TreeCtrlCO_pad = {
139     "pad amount",
140     PadAmountOptionSet,
141     PadAmountOptionGet,
142     PadAmountOptionRestore,
143     PadAmountOptionFree
144 };
145 
146 /*
147  *----------------------------------------------------------------------
148  *
149  * FormatResult --
150  *
151  *	Set the interpreter's result to a formatted string.
152  *
153  * Results:
154  *	None.
155  *
156  * Side effects:
157  *	Interpreter's result is modified.
158  *
159  *----------------------------------------------------------------------
160  */
161 
162 void
FormatResult(Tcl_Interp * interp,char * fmt,...)163 FormatResult(
164     Tcl_Interp *interp,		/* Current interpreter. */
165     char *fmt, ...		/* Format string and varargs. */
166     )
167 {
168     va_list ap;
169     char buf[256];
170 
171     va_start(ap, fmt);
172     vsprintf(buf, fmt, ap);
173     va_end(ap);
174     Tcl_SetResult(interp, buf, TCL_VOLATILE);
175 }
176 
177 /*
178  *----------------------------------------------------------------------
179  *
180  * DStringAppendf --
181  *
182  *	Format a string and append it to a Tcl_DString.
183  *
184  * Results:
185  *	None.
186  *
187  * Side effects:
188  *	None.
189  *
190  *----------------------------------------------------------------------
191  */
192 
193 void
DStringAppendf(Tcl_DString * dString,char * fmt,...)194 DStringAppendf(
195     Tcl_DString *dString,	/* Initialized dynamic string. */
196     char *fmt, ...		/* Format string and varargs. */
197     )
198 {
199     va_list ap;
200     char buf[256];
201 
202     va_start(ap, fmt);
203     vsprintf(buf, fmt, ap);
204     va_end(ap);
205     Tcl_DStringAppend(dString, buf, -1);
206 }
207 
208 /*
209  *----------------------------------------------------------------------
210  *
211  * Tree_Ellipsis --
212  *
213  *	Determine the number of bytes from the string that will fit
214  *	in the given horizontal span. If the entire string does not
215  *	fit then determine the largest number of bytes of a substring
216  *	with an ellipsis "..." appended that will fit.
217  *
218  * Results:
219  *	When the return value is equal to numBytes the caller should
220  *	not add the ellipsis to the string (unless force is TRUE). In
221  *	this case maxPixels contains the number of pixels for the entire
222  *	string (plus ellipsis if force is TRUE).
223  *
224  *	When the return value is less than numBytes the caller should add
225  *	the ellipsis because only a substring fits. In this case
226  *	maxPixels contains the number of pixels for the substring
227  *	plus ellipsis. The substring has a minimum of one character.
228  *
229  * Side effects:
230  *	None.
231  *
232  *----------------------------------------------------------------------
233  */
234 
235 int
Tree_Ellipsis(Tk_Font tkfont,char * string,int numBytes,int * maxPixels,char * ellipsis,int force)236 Tree_Ellipsis(
237     Tk_Font tkfont,		/* The font used to display the string. */
238     char *string,		/* UTF-8 string, need not be NULL-terminated. */
239     int numBytes,		/* Number of bytes to consider. */
240     int *maxPixels,		/* In: maximum line length allowed.
241 				 * Out: length of string that fits (with
242 				 * ellipsis added if needed). */
243     char *ellipsis,		/* NULL-terminated "..." */
244     int force			/* TRUE if ellipsis should always be added
245 				 * even if the whole string fits in
246 				 * maxPixels. */
247     )
248 {
249     char staticStr[256], *tmpStr = staticStr;
250     int pixels, pixelsTest, bytesThatFit, bytesTest;
251     int ellipsisNumBytes = (int) strlen(ellipsis);
252     int bytesInFirstCh;
253     Tcl_UniChar uniCh;
254 
255     bytesThatFit = Tk_MeasureChars(tkfont, string, numBytes, *maxPixels, 0,
256 	&pixels);
257 
258     /* The whole string fits. No ellipsis needed (unless forced) */
259     if ((bytesThatFit == numBytes) && !force) {
260 	(*maxPixels) = pixels;
261 	return numBytes;
262     }
263 
264     bytesInFirstCh = Tcl_UtfToUniChar(string, &uniCh);
265     if (bytesThatFit <= bytesInFirstCh) {
266 	goto singleChar;
267     }
268 
269     /* Strip off one character at a time, adding ellipsis, until it fits */
270     if (force)
271 	bytesTest = bytesThatFit;
272     else
273 	bytesTest = (int) (Tcl_UtfPrev(string + bytesThatFit, string) - string);
274     if (bytesTest + ellipsisNumBytes > sizeof(staticStr))
275 	tmpStr = ckalloc(bytesTest + ellipsisNumBytes);
276     memcpy(tmpStr, string, bytesTest);
277     while (bytesTest > 0) {
278 	memcpy(tmpStr + bytesTest, ellipsis, ellipsisNumBytes);
279 	numBytes = Tk_MeasureChars(tkfont, tmpStr,
280 	    bytesTest + ellipsisNumBytes,
281 	    *maxPixels, 0, &pixelsTest);
282 	if (numBytes == bytesTest + ellipsisNumBytes) {
283 	    (*maxPixels) = pixelsTest;
284 	    if (tmpStr != staticStr)
285 		ckfree(tmpStr);
286 	    return bytesTest;
287 	}
288 	bytesTest = (int) (Tcl_UtfPrev(string + bytesTest, string) - string);
289     }
290 
291     singleChar:
292     /* No single char + ellipsis fits. Return the number of bytes for
293      * the first character. The returned pixel width is the width of the
294      * first character plus ellipsis. */
295     bytesThatFit = bytesInFirstCh;
296     memcpy(tmpStr, string, bytesThatFit);
297     memcpy(tmpStr + bytesThatFit, ellipsis, ellipsisNumBytes);
298     (void) Tk_MeasureChars(tkfont, tmpStr, bytesThatFit + ellipsisNumBytes,
299 	-1, 0, &pixels);
300     (*maxPixels) = pixels;
301     if (tmpStr != staticStr)
302 	ckfree(tmpStr);
303     return bytesThatFit;
304 }
305 
306 /*
307  *----------------------------------------------------------------------
308  *
309  * Tree_GetRegion --
310  *
311  *	Return a pre-allocated TkRegion or create a new one.
312  *
313  * Results:
314  *	None.
315  *
316  * Side effects:
317  *	None.
318  *
319  *----------------------------------------------------------------------
320  */
321 
322 TkRegion
Tree_GetRegion(TreeCtrl * tree)323 Tree_GetRegion(
324     TreeCtrl *tree		/* Widget info. */
325     )
326 {
327     TkRegion region;
328 
329     if (tree->regionStackLen == 0) {
330 	return TkCreateRegion();
331     }
332     region = tree->regionStack[--tree->regionStackLen];
333     Tree_SetEmptyRegion(region);
334     return region;
335 }
336 
337 /*
338  *----------------------------------------------------------------------
339  *
340  * Tree_FreeRegion --
341  *
342  *	Push a region onto the free stack.
343  *
344  * Results:
345  *	None.
346  *
347  * Side effects:
348  *	None.
349  *
350  *----------------------------------------------------------------------
351  */
352 
353 void
Tree_FreeRegion(TreeCtrl * tree,TkRegion region)354 Tree_FreeRegion(
355     TreeCtrl *tree,		/* Widget info. */
356     TkRegion region		/* Region being released. */
357     )
358 {
359     if (tree->regionStackLen == sizeof(tree->regionStack) / sizeof(TkRegion))
360 	panic("Tree_FreeRegion: the stack is full");
361     tree->regionStack[tree->regionStackLen++] = region;
362 }
363 
364 /*
365  *----------------------------------------------------------------------
366  *
367  * Tree_SetEmptyRegion --
368  *
369  *	Set a region to empty.
370  *
371  * Results:
372  *	None.
373  *
374  * Side effects:
375  *	None.
376  *
377  *----------------------------------------------------------------------
378  */
379 
380 void
Tree_SetEmptyRegion(TkRegion region)381 Tree_SetEmptyRegion(
382     TkRegion region		/* Region to modify. */
383     )
384 {
385     TkSubtractRegion(region, region, region);
386 }
387 
388 /*
389  *----------------------------------------------------------------------
390  *
391  * Tree_GetRectRegion --
392  *
393  *	Allocate a region and set it to a single rectangle.
394  *
395  * Results:
396  *	Changes a region.
397  *
398  * Side effects:
399  *	None.
400  *
401  *----------------------------------------------------------------------
402  */
403 
404 TkRegion
Tree_GetRectRegion(TreeCtrl * tree,const TreeRectangle * rect)405 Tree_GetRectRegion(
406     TreeCtrl *tree,		/* Widget info. */
407     const TreeRectangle *rect	/* Rectangle */
408     )
409 {
410     XRectangle xr;
411     TkRegion region;
412 
413     region = Tree_GetRegion(tree);
414     TreeRect_ToXRect(*rect, &xr);
415     TkUnionRectWithRegion(&xr, region, region);
416     return region;
417 }
418 
419 /*
420  *----------------------------------------------------------------------
421  *
422  * Tree_SetRectRegion --
423  *
424  *	Set a region to a single rectangle.
425  *
426  * Results:
427  *	Changes a region.
428  *
429  * Side effects:
430  *	None.
431  *
432  *----------------------------------------------------------------------
433  */
434 
435 void
Tree_SetRectRegion(TkRegion region,const TreeRectangle * rect)436 Tree_SetRectRegion(
437     TkRegion region,		/* Region to modify. */
438     const TreeRectangle *rect	/* Rectangle */
439     )
440 {
441     XRectangle xr;
442     Tree_SetEmptyRegion(region);
443     TreeRect_ToXRect(*rect, &xr);
444     TkUnionRectWithRegion(&xr, region, region);
445 }
446 
447 /*
448  *----------------------------------------------------------------------
449  *
450  * Tree_GetRegionBounds --
451  *
452  *	Return the bounding rectangle of a region.
453  *
454  * Results:
455  *	Result rect is filled in with the bounds of the given region.
456  *
457  * Side effects:
458  *	None.
459  *
460  *----------------------------------------------------------------------
461  */
462 
463 void
Tree_GetRegionBounds(TkRegion region,TreeRectangle * rect)464 Tree_GetRegionBounds(
465     TkRegion region,		/* Region to modify. */
466     TreeRectangle *rect		/* Rectangle */
467     )
468 {
469     XRectangle xr;
470     TkClipBox(region, &xr);
471     TreeRect_FromXRect(xr, rect);
472 }
473 
474 /*
475  *----------------------------------------------------------------------
476  *
477  * Tree_RedrawImage --
478  *
479  *	Wrapper around Tk_RedrawImage to clip the drawing to the actual
480  *	area of the drawable. If you try to draw a transparent photo
481  *	image outside the bounds of a drawable, X11 will silently fail
482  *	and nothing will be drawn. See tkImgPhoto.c:ImgPhotoDisplay.
483  *
484  * Results:
485  *	None.
486  *
487  * Side effects:
488  *	Stuff is drawn.
489  *
490  *----------------------------------------------------------------------
491  */
Tree_RedrawImage(Tk_Image image,int imageX,int imageY,int width,int height,TreeDrawable td,int drawableX,int drawableY)492 void Tree_RedrawImage(
493     Tk_Image image,
494     int imageX,
495     int imageY,
496     int width,
497     int height,
498     TreeDrawable td,
499     int drawableX,
500     int drawableY
501     )
502 {
503 #if 0
504     int ix = imageX, iy = imageY, iw = width, ih = height;
505 #endif
506     if (drawableX < 0) {
507 	imageX = 0 - drawableX;
508 	width -= imageX;
509 	drawableX = 0;
510     }
511     if (drawableX + width > td.width) {
512 	width -= (drawableX + width) - td.width;
513     }
514     if (drawableY < 0) {
515 	imageY = 0 - drawableY;
516 	height -= imageY;
517 	drawableY = 0;
518     }
519     if (drawableY + height > td.height) {
520 	height -= (drawableY + height) - td.height;
521     }
522 #if 0
523     if (ix != imageX || iy != imageY || iw != width || ih != height)
524 	dbwin("Tree_RedrawImage clipped %d,%d,%d,%d -> %d,%d,%d,%d\n", ix,iy,iw,ih, imageX, imageY, width, height);
525 #endif
526     if (width > 0 && height > 0) {
527 	Tk_RedrawImage(image, imageX, imageY, width, height, td.drawable,
528 		drawableX, drawableY);
529     }
530 }
531 
532 /*
533  *----------------------------------------------------------------------
534  *
535  * Tree_DrawBitmap --
536  *
537  *	Draw part of a bitmap.
538  *
539  * Results:
540  *	None.
541  *
542  * Side effects:
543  *	Stuff is drawn.
544  *
545  *----------------------------------------------------------------------
546  */
547 
548 void
Tree_DrawBitmap(TreeCtrl * tree,Pixmap bitmap,Drawable drawable,XColor * fg,XColor * bg,int src_x,int src_y,int width,int height,int dest_x,int dest_y)549 Tree_DrawBitmap(
550     TreeCtrl *tree,		/* Widget info. */
551     Pixmap bitmap,		/* Bitmap to draw. */
552     Drawable drawable,		/* Where to draw. */
553     XColor *fg, XColor *bg,	/* Foreground and background colors.
554 				 * May be NULL. */
555     int src_x, int src_y,	/* Left and top of part of bitmap to copy. */
556     int width, int height,	/* Width and height of part of bitmap to
557 				 * copy. */
558     int dest_x, int dest_y	/* Left and top coordinates to copy part of
559 				 * the bitmap to. */
560     )
561 {
562     XGCValues gcValues;
563     GC gc;
564     unsigned long mask = 0;
565 
566     if (fg != NULL) {
567 	gcValues.foreground = fg->pixel;
568 	mask |= GCForeground;
569     }
570     if (bg != NULL) {
571 	gcValues.background = bg->pixel;
572 	mask |= GCBackground;
573     } else {
574 	gcValues.clip_mask = bitmap;
575 	mask |= GCClipMask;
576     }
577     gcValues.graphics_exposures = False;
578     mask |= GCGraphicsExposures;
579     gc = Tk_GetGC(tree->tkwin, mask, &gcValues);
580     Tree_DrawBitmapWithGC(tree, bitmap, drawable, gc,
581 	src_x, src_y, width, height, dest_x, dest_y);
582     Tk_FreeGC(tree->display, gc);
583 }
584 
585 /*
586  * Replacement for Tk_TextLayout stuff. Allows the caller to break lines
587  * on character boundaries (as well as word boundaries). Allows the caller
588  * to specify the maximum number of lines to display. Will add ellipsis "..."
589  * to the end of text that is too long to fit (when max lines specified).
590  */
591 
592 #define TEXTLAYOUT_ELLIPSIS
593 
594 typedef struct LayoutChunk
595 {
596     CONST char *start;		/* Pointer to simple string to be displayed.
597 				 * * This is a pointer into the TkTextLayout's
598 				 * * string. */
599     int numBytes;		/* The number of bytes in this chunk. */
600     int numChars;		/* The number of characters in this chunk. */
601     int numDisplayChars;	/* The number of characters to display when
602 				 * * this chunk is displayed.  Can be less than
603 				 * * numChars if extra space characters were
604 				 * * absorbed by the end of the chunk.  This
605 				 * * will be < 0 if this is a chunk that is
606 				 * * holding a tab or newline. */
607     int x, y;			/* The origin of the first character in this
608 				 * * chunk with respect to the upper-left hand
609 				 * * corner of the TextLayout. */
610     int totalWidth;		/* Width in pixels of this chunk.  Used
611 				 * * when hit testing the invisible spaces at
612 				 * * the end of a chunk. */
613     int displayWidth;		/* Width in pixels of the displayable
614 				 * * characters in this chunk.  Can be less than
615 				 * * width if extra space characters were
616 				 * * absorbed by the end of the chunk. */
617 #ifdef TEXTLAYOUT_ELLIPSIS
618     int ellipsis;		/* TRUE if adding "..." */
619 #endif
620 } LayoutChunk;
621 
622 typedef struct LayoutInfo
623 {
624     Tk_Font tkfont;		/* The font used when laying out the text. */
625     CONST char *string;		/* The string that was layed out. */
626     int numLines;		/* Number of lines */
627     int width;			/* The maximum width of all lines in the
628 				 * * text layout. */
629     int height;
630     int numChunks;		/* Number of chunks actually used in
631 				 * * following array. */
632     int totalWidth;
633 #define TEXTLAYOUT_ALLOCHAX
634 #ifdef TEXTLAYOUT_ALLOCHAX
635     int maxChunks;
636     struct LayoutInfo *nextFree;
637 #endif
638     LayoutChunk chunks[1];	/* Array of chunks.  The actual size will
639 				 * * be maxChunks.  THIS FIELD MUST BE THE LAST
640 				 * * IN THE STRUCTURE. */
641 } LayoutInfo;
642 
643 #ifdef TEXTLAYOUT_ALLOCHAX
644 TCL_DECLARE_MUTEX(textLayoutMutex)
645 /* FIXME: memory leak, list is never freed. */
646 static LayoutInfo *freeLayoutInfo = NULL;
647 #endif
648 
649 #ifdef TEXTLAYOUT_ALLOCHAX
NewChunk(LayoutInfo ** layoutPtrPtr,CONST char * start,int numBytes,int curX,int newX,int y)650 static LayoutChunk *NewChunk(LayoutInfo **layoutPtrPtr,
651 #else
652 static LayoutChunk *NewChunk(LayoutInfo **layoutPtrPtr, int *maxPtr,
653 #endif
654     CONST char *start, int numBytes, int curX, int newX, int y)
655 {
656     LayoutInfo *layoutPtr;
657     LayoutChunk *chunkPtr;
658 #ifdef TEXTLAYOUT_ALLOCHAX
659     int numChars;
660 #else
661     int maxChunks, numChars;
662 #endif
663     size_t s;
664 
665     layoutPtr = *layoutPtrPtr;
666 #ifdef TEXTLAYOUT_ALLOCHAX
667     if (layoutPtr->numChunks == layoutPtr->maxChunks) {
668 	layoutPtr->maxChunks *= 2;
669 	s = sizeof(LayoutInfo) + ((layoutPtr->maxChunks - 1) * sizeof(LayoutChunk));
670 	layoutPtr = (LayoutInfo *) ckrealloc((char *) layoutPtr, (int) s);
671 
672 	*layoutPtrPtr = layoutPtr;
673     }
674 #else
675     maxChunks = *maxPtr;
676     if (layoutPtr->numChunks == maxChunks) {
677 	maxChunks *= 2;
678 	s = sizeof(LayoutInfo) + ((maxChunks - 1) * sizeof(LayoutChunk));
679 	layoutPtr = (LayoutInfo *) ckrealloc((char *) layoutPtr, s);
680 
681 	*layoutPtrPtr = layoutPtr;
682 	*maxPtr = maxChunks;
683     }
684 #endif
685     numChars = Tcl_NumUtfChars(start, numBytes);
686     chunkPtr = &layoutPtr->chunks[layoutPtr->numChunks];
687     chunkPtr->start = start;
688     chunkPtr->numBytes = numBytes;
689     chunkPtr->numChars = numChars;
690     chunkPtr->numDisplayChars = numChars;
691     chunkPtr->x = curX;
692     chunkPtr->y = y;
693     chunkPtr->totalWidth = newX - curX;
694     chunkPtr->displayWidth = newX - curX;
695     chunkPtr->ellipsis = FALSE;
696     layoutPtr->numChunks++;
697 
698     return chunkPtr;
699 }
700 
TextLayout_Compute(Tk_Font tkfont,CONST char * string,int numChars,int wrapLength,Tk_Justify justify,int maxLines,int lMargin1,int lMargin2,int flags)701 TextLayout TextLayout_Compute(
702     Tk_Font tkfont,		/* Font that will be used to display text. */
703     CONST char *string,		/* String whose dimensions are to be
704 				** computed. */
705     int numChars,		/* Number of characters to consider from
706 				** string, or < 0 for strlen(). */
707     int wrapLength,		/* Longest permissible line length, in
708 				** pixels.  <= 0 means no automatic wrapping:
709 				** just let lines get as long as needed. */
710     Tk_Justify justify,		/* How to justify lines. */
711     int maxLines,
712     int lMargin1, int lMargin2, /* Extra indentation or zero */
713     int flags	/* Flag bits OR-ed together.
714 		 ** TK_IGNORE_TABS means that tab characters
715 		 ** should not be expanded.  TK_IGNORE_NEWLINES
716 		 ** means that newline characters should not
717 		 ** cause a line break. */
718     )
719 {
720     CONST char *start, *end, *special;
721     int n, y, bytesThisChunk, maxChunks;
722     int baseline, height, curX, newX, maxWidth;
723     LayoutInfo *layoutPtr;
724     LayoutChunk *chunkPtr;
725     Tk_FontMetrics fm;
726     Tcl_DString lineBuffer;
727     int *lineLengths;
728     int curLine;
729     int tabWidth = 20; /* FIXME */
730 
731     Tcl_DStringInit(&lineBuffer);
732 
733     Tk_GetFontMetrics(tkfont, &fm);
734     height = fm.ascent + fm.descent;
735 
736     if (numChars < 0)
737 	numChars = Tcl_NumUtfChars(string, -1);
738     if (wrapLength == 0)
739 	wrapLength = -1;
740 
741 #ifdef TEXTLAYOUT_ALLOCHAX
742     Tcl_MutexLock(&textLayoutMutex);
743     if (freeLayoutInfo != NULL) {
744 	layoutPtr = freeLayoutInfo;
745 	freeLayoutInfo = layoutPtr->nextFree;
746     } else {
747 	maxChunks = 1;
748 	layoutPtr = (LayoutInfo *) ckalloc(sizeof(LayoutInfo) +
749 	    (maxChunks - 1) * sizeof(LayoutChunk));
750 	layoutPtr->maxChunks = maxChunks;
751     }
752     Tcl_MutexUnlock(&textLayoutMutex);
753 #else
754     maxChunks = 1;
755 
756     layoutPtr = (LayoutInfo *) ckalloc(sizeof(LayoutInfo) + (maxChunks -
757 	    1) * sizeof(LayoutChunk));
758 #endif
759     layoutPtr->tkfont = tkfont;
760     layoutPtr->string = string;
761     layoutPtr->numChunks = 0;
762     layoutPtr->numLines = 0;
763 
764     baseline = fm.ascent;
765     maxWidth = 0;
766 
767     curX = lMargin1;
768     end = Tcl_UtfAtIndex(string, numChars);
769     special = string;
770 
771     flags &= TK_WHOLE_WORDS | TK_IGNORE_TABS | TK_IGNORE_NEWLINES;
772     flags |= TK_AT_LEAST_ONE;
773     for (start = string; start < end;) {
774 	if (start >= special) {
775 	    for (special = start; special < end; special++) {
776 		if (!(flags & TK_IGNORE_NEWLINES)) {
777 		    if ((*special == '\n') || (*special == '\r'))
778 			break;
779 		}
780 		if (!(flags & TK_IGNORE_TABS)) {
781 		    if (*special == '\t')
782 			break;
783 		}
784 	    }
785 	}
786 
787 	chunkPtr = NULL;
788 	if (start < special) {
789 	    bytesThisChunk = Tk_MeasureChars(tkfont, start,
790 		(int) (special - start),
791 		wrapLength - curX, flags, &newX);
792 	    newX += curX;
793 	    flags &= ~TK_AT_LEAST_ONE;
794 	    if (bytesThisChunk > 0) {
795 #ifdef TEXTLAYOUT_ALLOCHAX
796 		chunkPtr = NewChunk(&layoutPtr, start,
797 #else
798 		chunkPtr = NewChunk(&layoutPtr, &maxChunks, start,
799 #endif
800 		    bytesThisChunk, curX, newX, baseline);
801 		start += bytesThisChunk;
802 		curX = newX;
803 	    }
804 	}
805 
806 	if ((start == special) && (special < end)) {
807 	    chunkPtr = NULL;
808 	    if (*special == '\t') {
809 		newX = curX + tabWidth;
810 		newX -= newX % tabWidth;
811 #ifdef TEXTLAYOUT_ALLOCHAX
812 		NewChunk(&layoutPtr, start, 1, curX, newX,
813 #else
814 		NewChunk(&layoutPtr, &maxChunks, start, 1, curX, newX,
815 #endif
816 		    baseline)->numDisplayChars = -1;
817 		start++;
818 		if ((start < end) && ((wrapLength <= 0) ||
819 		    (newX <= wrapLength))) {
820 		    curX = newX;
821 		    flags &= ~TK_AT_LEAST_ONE;
822 		    continue;
823 		}
824 	    } else {
825 #ifdef TEXTLAYOUT_ALLOCHAX
826 		NewChunk(&layoutPtr, start, 1, curX, curX,
827 #else
828 		NewChunk(&layoutPtr, &maxChunks, start, 1, curX, curX,
829 #endif
830 		    baseline)->numDisplayChars = -1;
831 		start++;
832 		goto wrapLine;
833 	    }
834 	}
835 
836 	while ((start < end) && isspace(UCHAR(*start))) {
837 	    if (!(flags & TK_IGNORE_NEWLINES)) {
838 		if ((*start == '\n') || (*start == '\r'))
839 		    break;
840 	    }
841 	    if (!(flags & TK_IGNORE_TABS)) {
842 		if (*start == '\t')
843 		    break;
844 	    }
845 	    start++;
846 	}
847 	if (chunkPtr != NULL) {
848 	    CONST char *end;
849 
850 	    end = chunkPtr->start + chunkPtr->numBytes;
851 	    bytesThisChunk = (int) (start - end);
852 	    if (bytesThisChunk > 0) {
853 		bytesThisChunk =
854 		    Tk_MeasureChars(tkfont, end, bytesThisChunk, -1, 0,
855 		    &chunkPtr->totalWidth);
856 		chunkPtr->numBytes += bytesThisChunk;
857 		chunkPtr->numChars += Tcl_NumUtfChars(end, bytesThisChunk);
858 		chunkPtr->totalWidth += curX;
859 	    }
860 	}
861 
862 wrapLine:
863 	flags |= TK_AT_LEAST_ONE;
864 
865 	if (curX > maxWidth)
866 	    maxWidth = curX;
867 
868 	Tcl_DStringAppend(&lineBuffer, (char *) &curX, sizeof(curX));
869 
870 	chunkPtr = layoutPtr->numChunks ?
871 	    &layoutPtr->chunks[layoutPtr->numChunks - 1] : NULL;
872 	if ((chunkPtr != NULL) && !(flags & TK_IGNORE_NEWLINES) &&
873 		(chunkPtr->start[0] == '\n'))
874 	    curX = lMargin1;
875 	else
876 	    curX = lMargin2;
877 
878 	baseline += height;
879 	layoutPtr->numLines++;
880 
881 	if ((maxLines > 0) && (layoutPtr->numLines >= maxLines))
882 	    break;
883     }
884 
885     if ((start >= end) && (layoutPtr->numChunks > 0) &&
886 	    !(flags & TK_IGNORE_NEWLINES)) {
887 	if (layoutPtr->chunks[layoutPtr->numChunks - 1].start[0] == '\n') {
888 	    chunkPtr =
889 #ifdef TEXTLAYOUT_ALLOCHAX
890 		NewChunk(&layoutPtr, start, 0, curX, curX,
891 #else
892 		NewChunk(&layoutPtr, &maxChunks, start, 0, curX, curX,
893 #endif
894 		baseline);
895 	    chunkPtr->numDisplayChars = -1;
896 	    Tcl_DStringAppend(&lineBuffer, (char *) &curX, sizeof(curX));
897 	    baseline += height;
898 	}
899     }
900 
901 #ifdef TEXTLAYOUT_ELLIPSIS
902     /* Fiddle with chunks on the last line to add ellipsis if there is some
903      * text remaining */
904     if ((start < end) && (layoutPtr->numChunks > 0)) {
905 	char *ellipsis = "...";
906 	int ellipsisLen = (int) strlen(ellipsis);
907 	char staticStr[256], *buf = staticStr;
908 	int pixelsForText;
909 
910 	chunkPtr = &layoutPtr->chunks[layoutPtr->numChunks - 1];
911 	if (wrapLength > 0) {
912 	    y = chunkPtr->y;
913 	    for (n = layoutPtr->numChunks - 1; n >= 0; n--) {
914 		chunkPtr = &layoutPtr->chunks[n];
915 
916 		/* Only consider the last line */
917 		if (chunkPtr->y != y)
918 		    break;
919 
920 		if (chunkPtr->start[0] == '\n')
921 		    continue;
922 
923 		if (chunkPtr->x + chunkPtr->totalWidth < wrapLength)
924 		    pixelsForText = wrapLength - chunkPtr->x;
925 		else
926 		    pixelsForText = chunkPtr->totalWidth - 1;
927 		bytesThisChunk = Tree_Ellipsis(tkfont,
928 			(char *) chunkPtr->start, chunkPtr->numBytes,
929 			&pixelsForText, ellipsis, TRUE);
930 		if (pixelsForText > wrapLength - chunkPtr->x)
931 		    pixelsForText = wrapLength - chunkPtr->x;
932 		if (bytesThisChunk > 0) {
933 		    chunkPtr->numBytes = bytesThisChunk;
934 		    chunkPtr->numChars = Tcl_NumUtfChars(chunkPtr->start, bytesThisChunk);
935 		    chunkPtr->numDisplayChars = chunkPtr->numChars;
936 		    chunkPtr->ellipsis = TRUE;
937 		    chunkPtr->displayWidth = pixelsForText;
938 		    chunkPtr->totalWidth = pixelsForText;
939 		    lineLengths = (int *) Tcl_DStringValue(&lineBuffer);
940 		    lineLengths[layoutPtr->numLines - 1] = chunkPtr->x + pixelsForText;
941 		    if (chunkPtr->x + pixelsForText > maxWidth)
942 			maxWidth = chunkPtr->x + pixelsForText;
943 		    break;
944 		}
945 	    }
946 	} else {
947 	    if (chunkPtr->start[0] == '\n') {
948 		if (layoutPtr->numChunks == 1)
949 		    goto finish;
950 		if (layoutPtr->chunks[layoutPtr->numChunks - 2].y != chunkPtr->y)
951 		    goto finish;
952 		chunkPtr = &layoutPtr->chunks[layoutPtr->numChunks - 2];
953 	    }
954 
955 	    if (chunkPtr->numBytes + ellipsisLen > sizeof(staticStr))
956 		buf = ckalloc(chunkPtr->numBytes + ellipsisLen);
957 	    memcpy(buf, chunkPtr->start, chunkPtr->numBytes);
958 	    memcpy(buf + chunkPtr->numBytes, ellipsis, ellipsisLen);
959 	    Tk_MeasureChars(tkfont, buf,
960 		chunkPtr->numBytes + ellipsisLen, -1, 0,
961 		&chunkPtr->displayWidth);
962 	    chunkPtr->totalWidth = chunkPtr->displayWidth;
963 	    chunkPtr->ellipsis = TRUE;
964 	    lineLengths = (int *) Tcl_DStringValue(&lineBuffer);
965 	    lineLengths[layoutPtr->numLines - 1] = chunkPtr->x + chunkPtr->displayWidth;
966 	    if (chunkPtr->x + chunkPtr->displayWidth > maxWidth)
967 		maxWidth = chunkPtr->x + chunkPtr->displayWidth;
968 	    if (buf != staticStr)
969 		ckfree(buf);
970 	}
971     }
972 finish:
973 #endif
974 
975     layoutPtr->width = maxWidth;
976     layoutPtr->height = baseline - fm.ascent;
977     layoutPtr->totalWidth = 0;
978     if (layoutPtr->numChunks == 0) {
979 	layoutPtr->height = height;
980 
981 	layoutPtr->numChunks = 1;
982 	layoutPtr->chunks[0].start = string;
983 	layoutPtr->chunks[0].numBytes = 0;
984 	layoutPtr->chunks[0].numChars = 0;
985 	layoutPtr->chunks[0].numDisplayChars = -1;
986 	layoutPtr->chunks[0].x = 0;
987 	layoutPtr->chunks[0].y = fm.ascent;
988 	layoutPtr->chunks[0].totalWidth = 0;
989 	layoutPtr->chunks[0].displayWidth = 0;
990     } else {
991 	curLine = 0;
992 	chunkPtr = layoutPtr->chunks;
993 	y = chunkPtr->y;
994 	lineLengths = (int *) Tcl_DStringValue(&lineBuffer);
995 	for (n = 0; n < layoutPtr->numChunks; n++) {
996 	    int extra;
997 
998 	    if (chunkPtr->y != y) {
999 		curLine++;
1000 		y = chunkPtr->y;
1001 	    }
1002 	    extra = maxWidth - lineLengths[curLine];
1003 	    if (justify == TK_JUSTIFY_CENTER) {
1004 		chunkPtr->x += extra / 2;
1005 	    }
1006 	    else if (justify == TK_JUSTIFY_RIGHT) {
1007 		chunkPtr->x += extra;
1008 	    }
1009 	    if (chunkPtr->x + chunkPtr->totalWidth > layoutPtr->totalWidth)
1010 		layoutPtr->totalWidth = chunkPtr->x + chunkPtr->totalWidth;
1011 	    chunkPtr++;
1012 	}
1013 /* dbwin("totalWidth %d displayWidth %d\n", layoutPtr->totalWidth, maxWidth); */
1014     }
1015 
1016     Tcl_DStringFree(&lineBuffer);
1017 
1018     /* We don't want single-line text layouts for text elements, but it happens for column titles */
1019 /*	if (layoutPtr->numLines == 1)
1020 	dbwin("WARNING: single-line TextLayout created\n"); */
1021 
1022     return (TextLayout) layoutPtr;
1023 }
1024 
TextLayout_Free(TextLayout textLayout)1025 void TextLayout_Free(TextLayout textLayout)
1026 {
1027     LayoutInfo *layoutPtr = (LayoutInfo *) textLayout;
1028 
1029 #ifdef TEXTLAYOUT_ALLOCHAX
1030     Tcl_MutexLock(&textLayoutMutex);
1031     layoutPtr->nextFree = freeLayoutInfo;
1032     freeLayoutInfo = layoutPtr;
1033     Tcl_MutexUnlock(&textLayoutMutex);
1034 #else
1035     ckfree((char *) layoutPtr);
1036 #endif
1037 }
1038 
TextLayout_Size(TextLayout textLayout,int * widthPtr,int * heightPtr)1039 void TextLayout_Size(TextLayout textLayout, int *widthPtr, int *heightPtr)
1040 {
1041     LayoutInfo *layoutPtr = (LayoutInfo *) textLayout;
1042 
1043     if (widthPtr != NULL)
1044 	(*widthPtr) = layoutPtr->width;
1045     if (heightPtr != NULL)
1046 	(*heightPtr) = layoutPtr->height;
1047 }
1048 
TextLayout_TotalWidth(TextLayout textLayout)1049 int TextLayout_TotalWidth(TextLayout textLayout)
1050 {
1051     LayoutInfo *layoutPtr = (LayoutInfo *) textLayout;
1052 
1053     return layoutPtr->totalWidth;
1054 }
1055 
TextLayout_Draw(Display * display,Drawable drawable,GC gc,TextLayout layout,int x,int y,int firstChar,int lastChar,int underline)1056 void TextLayout_Draw(
1057     Display *display,		/* Display on which to draw. */
1058     Drawable drawable,		/* Window or pixmap in which to draw. */
1059     GC gc,			/* Graphics context to use for drawing text. */
1060     TextLayout layout,		/* Layout information, from a previous call
1061 				 * * to Tk_ComputeTextLayout(). */
1062     int x, int y,		/* Upper-left hand corner of rectangle in
1063 				 * * which to draw (pixels). */
1064     int firstChar,		/* The index of the first character to draw
1065 				 * * from the given text item.  0 specfies the
1066 				 * * beginning. */
1067     int lastChar,		/* The index just after the last character
1068 				 * * to draw from the given text item.  A number
1069 				 * * < 0 means to draw all characters. */
1070     int underline		/* Character index to underline, or < 0 for
1071 				 * no underline. */
1072 )
1073 {
1074     LayoutInfo *layoutPtr = (LayoutInfo *) layout;
1075     int i, numDisplayChars, drawX;
1076     CONST char *firstByte;
1077     CONST char *lastByte;
1078     LayoutChunk *chunkPtr;
1079 
1080     if (lastChar < 0)
1081 	lastChar = 100000000;
1082     chunkPtr = layoutPtr->chunks;
1083     for (i = 0; i < layoutPtr->numChunks; i++) {
1084 	numDisplayChars = chunkPtr->numDisplayChars;
1085 	if ((numDisplayChars > 0) && (firstChar < numDisplayChars)) {
1086 	    if (firstChar <= 0) {
1087 		drawX = 0;
1088 		firstChar = 0;
1089 		firstByte = chunkPtr->start;
1090 	    } else {
1091 		firstByte = Tcl_UtfAtIndex(chunkPtr->start, firstChar);
1092 		Tk_MeasureChars(layoutPtr->tkfont, chunkPtr->start,
1093 		    (int) (firstByte - chunkPtr->start), -1, 0, &drawX);
1094 	    }
1095 	    if (lastChar < numDisplayChars)
1096 		numDisplayChars = lastChar;
1097 	    lastByte = Tcl_UtfAtIndex(chunkPtr->start, numDisplayChars);
1098 #ifdef TEXTLAYOUT_ELLIPSIS
1099 	    if (chunkPtr->ellipsis) {
1100 		char staticStr[256], *buf = staticStr;
1101 		char *ellipsis = "...";
1102 		int ellipsisLen = (int) strlen(ellipsis);
1103 
1104 		if ((lastByte - firstByte) + ellipsisLen > sizeof(staticStr))
1105 		    buf = ckalloc((int) (lastByte - firstByte) + ellipsisLen);
1106 		memcpy(buf, firstByte, (lastByte - firstByte));
1107 		memcpy(buf + (lastByte - firstByte), ellipsis, ellipsisLen);
1108 		Tk_DrawChars(display, drawable, gc, layoutPtr->tkfont,
1109 		    buf, (int) (lastByte - firstByte) + ellipsisLen,
1110 		    x + chunkPtr->x + drawX, y + chunkPtr->y);
1111 		if (buf != staticStr)
1112 		    ckfree(buf);
1113 	    } else
1114 #endif
1115 	    Tk_DrawChars(display, drawable, gc, layoutPtr->tkfont,
1116 		firstByte, (int) (lastByte - firstByte),
1117 		x + chunkPtr->x + drawX, y + chunkPtr->y);
1118 
1119 	    if (underline >= firstChar && underline < numDisplayChars) {
1120 		CONST char *fstBytePtr = Tcl_UtfAtIndex(chunkPtr->start, underline);
1121 		CONST char *sndBytePtr = Tcl_UtfNext(fstBytePtr);
1122 		Tk_UnderlineChars(display, drawable, gc,
1123 			layoutPtr->tkfont, firstByte,
1124 			x + chunkPtr->x + drawX, y + chunkPtr->y,
1125 			(int) (fstBytePtr - chunkPtr->start),
1126 			(int) (sndBytePtr - chunkPtr->start));
1127 	    }
1128 	}
1129 	firstChar -= chunkPtr->numChars;
1130 	lastChar -= chunkPtr->numChars;
1131 	underline -= chunkPtr->numChars;
1132 
1133 	if (lastChar <= 0)
1134 	    break;
1135 	chunkPtr++;
1136     }
1137 }
1138 
1139 /*
1140  *----------------------------------------------------------------------
1141  *
1142  * TreeCtrl_GetPadAmountFromObj --
1143  *
1144  *	Parse a pad amount configuration option.
1145  *	A pad amount (typically the value of an option -XXXpadx or
1146  *	-XXXpady, where XXX may be a possibly empty string) can
1147  *	be either a single pixel width, or a list of two pixel widths.
1148  *	If a single pixel width, the amount specified is used for
1149  *	padding on both sides.  If two amounts are specified, then
1150  *	they specify the left/right or top/bottom padding.
1151  *
1152  * Results:
1153  *	Standard Tcl Result.
1154  *
1155  * Side effects:
1156  *	Sets internal representation of the object. In case of an error
1157  *	the result of the interpreter is modified.
1158  *
1159  *----------------------------------------------------------------------
1160  */
1161 
1162 int
TreeCtrl_GetPadAmountFromObj(Tcl_Interp * interp,Tk_Window tkwin,Tcl_Obj * padObj,int * topLeftPtr,int * bottomRightPtr)1163 TreeCtrl_GetPadAmountFromObj(
1164     Tcl_Interp *interp,		/* Interpreter for error reporting, or NULL,
1165 				 * if no error message is wanted. */
1166     Tk_Window tkwin,		/* A window.  Needed by Tk_GetPixels() */
1167     Tcl_Obj *padObj,		/* Object containing a pad amount. */
1168     int *topLeftPtr,		/* Pointer to the location, where to store the
1169 				   first component of the padding. */
1170     int *bottomRightPtr		/* Pointer to the location, where to store the
1171 				   second component of the padding. */
1172     )
1173 {
1174     int padc;			/* Number of element objects in padv. */
1175     Tcl_Obj **padv;		/* Pointer to the element objects of the
1176 				 * parsed pad amount value. */
1177 	int topLeft, bottomRight;
1178 
1179     if (Tcl_ListObjGetElements(interp, padObj, &padc, &padv) != TCL_OK) {
1180 	return TCL_ERROR;
1181     }
1182 
1183     /*
1184      * The value specifies a non empty string.
1185      * Check that this string is indeed a valid pad amount.
1186      */
1187 
1188     if (padc < 1 || padc > 2) {
1189 	if (interp != NULL) {
1190 	error:
1191 	    Tcl_ResetResult(interp);
1192 	    Tcl_AppendResult(interp, "bad pad amount \"",
1193 		Tcl_GetString(padObj), "\": must be a list of ",
1194 		"1 or 2 positive screen distances", (char *) NULL);
1195 	}
1196 	return TCL_ERROR;
1197     }
1198     if ((Tk_GetPixelsFromObj(interp, tkwin, padv[0], &topLeft)
1199 	     != TCL_OK) || (topLeft < 0)) {
1200 	goto error;
1201     }
1202     if (padc == 2) {
1203 	if ((Tk_GetPixelsFromObj(interp, tkwin, padv[1], &bottomRight)
1204 		!= TCL_OK) || (bottomRight < 0)) {
1205 	    goto error;
1206 	}
1207     } else {
1208 	bottomRight = topLeft;
1209     }
1210     (*topLeftPtr) = topLeft;
1211     (*bottomRightPtr) = bottomRight;
1212     return TCL_OK;
1213 }
1214 
1215 /*
1216  *----------------------------------------------------------------------
1217  *
1218  * TreeCtrl_NewPadAmountObj --
1219  *
1220  *	Create a Tcl object with an internal representation, that
1221  *	corresponds to a pad amount, i.e. an integer Tcl_Obj or a
1222  *	list Tcl_Obj with 2 elements.
1223  *
1224  * Results:
1225  *	The created object.
1226  *
1227  * Side effects:
1228  *	None.
1229  *
1230  *----------------------------------------------------------------------
1231  */
1232 
1233 Tcl_Obj *
TreeCtrl_NewPadAmountObj(int * padAmounts)1234 TreeCtrl_NewPadAmountObj(
1235     int *padAmounts		/* Internal form of a pad amount. */
1236     )
1237 {
1238     Tcl_Obj *newObj;
1239 
1240     /*
1241      * If both values are the same, create a list with one value,
1242      * otherwise create a two element list with the top/left value
1243      * first followed by the bottom/right value.
1244      */
1245 
1246     if (padAmounts[PAD_TOP_LEFT] == padAmounts[PAD_BOTTOM_RIGHT]) {
1247 	newObj = Tcl_NewIntObj(padAmounts[PAD_TOP_LEFT]);
1248     } else {
1249 	newObj = Tcl_NewObj();
1250 	Tcl_ListObjAppendElement((Tcl_Interp *) NULL, newObj,
1251 	    Tcl_NewIntObj(padAmounts[PAD_TOP_LEFT]));
1252 	Tcl_ListObjAppendElement((Tcl_Interp *) NULL, newObj,
1253 	    Tcl_NewIntObj(padAmounts[PAD_BOTTOM_RIGHT]));
1254     }
1255     return newObj;
1256 }
1257 
1258 /*
1259  *----------------------------------------------------------------------
1260  *
1261  * PadAmountOptionSet --
1262  * PadAmountOptionGet --
1263  * PadAmountOptionRestore --
1264  * PadAmountOptionFree --
1265  *
1266  *	Handlers for object-based pad amount configuration options.
1267  *	A pad amount (typically the value of an option -XXXpadx or
1268  *	-XXXpady, where XXX may be a possibly empty string) can
1269  *	be either a single pixel width, or a list of two pixel widths.
1270  *	If a single pixel width, the amount specified is used for
1271  *	padding on both sides.  If two amounts are specified, then
1272  *	they specify the left/right or top/bottom padding.
1273  *
1274  * Results:
1275  *	See user documentation for expected results from these functions.
1276  *		PadAmountOptionSet	Standard Tcl Result.
1277  *		PadAmountOptionGet	Tcl_Obj * containing a valid internal
1278  *					representation of the pad amount.
1279  *		PadAmountOptionRestore	None.
1280  *		PadAmountOptionFree	None.
1281  *
1282  * Side effects:
1283  *	Depends on the function.
1284  *		PadAmountOptionSet	Sets option value to new setting,
1285  *					allocating a new integer array.
1286  *		PadAmountOptionGet	Creates a new Tcl_Obj.
1287  *		PadAmountOptionRestore	Resets option value to original value.
1288  *		PadAmountOptionFree	Free storage for internal rep.
1289  *
1290  *----------------------------------------------------------------------
1291  */
1292 
1293 static int
PadAmountOptionSet(ClientData clientData,Tcl_Interp * interp,Tk_Window tkwin,Tcl_Obj ** valuePtr,char * recordPtr,int internalOffset,char * saveInternalPtr,int flags)1294 PadAmountOptionSet(
1295     ClientData clientData,	/* unused. */
1296     Tcl_Interp *interp,		/* Interpreter for error reporting, or NULL,
1297 				 * if no error message is wanted. */
1298     Tk_Window tkwin,		/* A window.  Needed by Tk_GetPixels() */
1299     Tcl_Obj **valuePtr,		/* The argument to "-padx", "-pady", "-ipadx",
1300 				 * or "-ipady".  The thing to be parsed. */
1301     char *recordPtr,		/* Pointer to start of widget record. */
1302     int internalOffset,		/* Offset of internal representation or
1303 				 * -1, if no internal repr is wanted. */
1304     char *saveInternalPtr,	/* Pointer to the place, where the saved
1305 				 * internal form (of type "int *") resides. */
1306     int flags			/* Flags as specified in Tk_OptionSpec. */
1307     )
1308 {
1309     int objEmpty;
1310     int topLeft, bottomRight;	/* The two components of the padding. */
1311     int *new;			/* Pointer to the allocated array of integers
1312 				 * containing the parsed pad amounts. */
1313     int **internalPtr;		/* Pointer to the place, where the internal
1314 				 * form (of type "int *") resides. */
1315 
1316     objEmpty = ObjectIsEmpty((*valuePtr));
1317 
1318     if ((flags & TK_OPTION_NULL_OK) && objEmpty)
1319 	(*valuePtr) = NULL;
1320     else {
1321 	/*
1322 	* Check that the given object indeed specifies a valid pad amount.
1323 	*/
1324 
1325 	if (TreeCtrl_GetPadAmountFromObj(interp, tkwin, *valuePtr,
1326 		&topLeft, &bottomRight) != TCL_OK) {
1327 	    return TCL_ERROR;
1328 	}
1329     }
1330 
1331     /*
1332      * Store a pointer to an allocated array of the two padding values
1333      * into the widget record at the specified offset.
1334      */
1335 
1336     if (internalOffset >= 0) {
1337 	internalPtr = (int **) (recordPtr + internalOffset);
1338 	*(int **) saveInternalPtr = *internalPtr;
1339 	if (*valuePtr == NULL)
1340 	    new = NULL;
1341 	else {
1342 	    new = (int *) ckalloc(2 * sizeof(int));
1343 	    new[PAD_TOP_LEFT]     = topLeft;
1344 	    new[PAD_BOTTOM_RIGHT] = bottomRight;
1345 	}
1346 	*internalPtr = new;
1347     }
1348     return TCL_OK;
1349 }
1350 
1351 static Tcl_Obj *
PadAmountOptionGet(ClientData clientData,Tk_Window tkwin,char * recordPtr,int internalOffset)1352 PadAmountOptionGet(
1353     ClientData clientData,	/* unused. */
1354     Tk_Window tkwin,		/* A window; unused. */
1355     char *recordPtr,		/* Pointer to start of widget record. */
1356     int internalOffset		/* Offset of internal representation. */
1357     )
1358 {
1359     int *padAmounts = *(int **)(recordPtr + internalOffset);
1360 
1361     if (padAmounts == NULL)
1362 	return NULL;
1363     return TreeCtrl_NewPadAmountObj(padAmounts);
1364 }
1365 
1366 static void
PadAmountOptionRestore(ClientData clientData,Tk_Window tkwin,char * internalPtr,char * saveInternalPtr)1367 PadAmountOptionRestore(
1368     ClientData clientData,	/* unused. */
1369     Tk_Window tkwin,		/* A window; unused. */
1370     char *internalPtr,		/* Pointer to the place, where the internal
1371 				 * form (of type "int *") resides. */
1372     char *saveInternalPtr	/* Pointer to the place, where the saved
1373 				 * internal form (of type "int *") resides. */
1374     )
1375 {
1376     *(int **) internalPtr = *(int **) saveInternalPtr;
1377 }
1378 
1379 static void
PadAmountOptionFree(ClientData clientData,Tk_Window tkwin,char * internalPtr)1380 PadAmountOptionFree(
1381     ClientData clientData,	/* unused. */
1382     Tk_Window tkwin,		/* A window; unused */
1383     char *internalPtr		/* Pointer to the place, where the internal
1384 				 * form (of type "int *") resides. */
1385     )
1386 {
1387     if (*(int **)internalPtr != NULL) {
1388 	ckfree((char *) *(int **)internalPtr);
1389     }
1390 }
1391 
1392 /*
1393  *----------------------------------------------------------------------
1394  *
1395  * ObjectIsEmpty --
1396  *
1397  *	This procedure tests whether the string value of an object is
1398  *	empty.
1399  *
1400  * Results:
1401  *	The return value is 1 if the string value of objPtr has length
1402  *	zero, and 0 otherwise.
1403  *
1404  * Side effects:
1405  *	None.
1406  *
1407  *----------------------------------------------------------------------
1408  */
1409 
1410 int
ObjectIsEmpty(Tcl_Obj * obj)1411 ObjectIsEmpty(
1412     Tcl_Obj *obj		/* Object to test.  May be NULL. */
1413     )
1414 {
1415     int length;
1416 
1417     if (obj == NULL)
1418 	return 1;
1419     if (obj->bytes != NULL)
1420 	return (obj->length == 0);
1421     Tcl_GetStringFromObj(obj, &length);
1422     return (length == 0);
1423 }
1424 
1425 #define PERSTATE_ROUNDUP 5
1426 
1427 /*
1428  *----------------------------------------------------------------------
1429  *
1430  * PerStateInfo_Free --
1431  *
1432  *	Frees memory and resources associated with a single per-state
1433  *	option. pInfo is set to an empty state ready to be used again.
1434  *
1435  * Results:
1436  *	None.
1437  *
1438  * Side effects:
1439  *	Memory is deallocated. Colors, etc are freed.
1440  *
1441  *----------------------------------------------------------------------
1442  */
1443 
1444 void
PerStateInfo_Free(TreeCtrl * tree,PerStateType * typePtr,PerStateInfo * pInfo)1445 PerStateInfo_Free(
1446     TreeCtrl *tree,		/* Widget info. */
1447     PerStateType *typePtr,	/* Type-specific functions and values. */
1448     PerStateInfo *pInfo		/* Per-state info to free. */
1449     )
1450 {
1451     PerStateData *pData = pInfo->data;
1452     int i;
1453 
1454     if (pInfo->data == NULL)
1455 	return;
1456 
1457 #ifdef TREECTRL_DEBUG
1458     if (pInfo->type != typePtr)
1459 	panic("PerStateInfo_Free type mismatch: got %s expected %s",
1460 		pInfo->type ? pInfo->type->name : "NULL", typePtr->name);
1461 #endif
1462     for (i = 0; i < pInfo->count; i++) {
1463 	(*typePtr->freeProc)(tree, pData);
1464 	pData = (PerStateData *) (((char *) pData) + typePtr->size);
1465     }
1466 #ifdef ALLOC_HAX
1467     TreeAlloc_CFree(tree->allocData, typePtr->name, (char *) pInfo->data,
1468 	typePtr->size, pInfo->count, PERSTATE_ROUNDUP);
1469 #else
1470     WIPEFREE(pInfo->data, typePtr->size * pInfo->count);
1471 #endif
1472     pInfo->data = NULL;
1473     pInfo->count = 0;
1474 }
1475 
1476 /*
1477  *----------------------------------------------------------------------
1478  *
1479  * PerStateInfo_FromObj --
1480  *
1481  *	Parse a Tcl_Obj to an array of PerStateData. The current data
1482  *	is freed (if any). If the Tcl_Obj is NULL then pInfo is left in
1483  *	an empty state ready to be used again.
1484  *
1485  * Results:
1486  *	A standard Tcl result.
1487  *
1488  * Side effects:
1489  *	Memory is allocated/deallocated.
1490  *
1491  *----------------------------------------------------------------------
1492  */
1493 
1494 int
PerStateInfo_FromObj(TreeCtrl * tree,int domain,StateFromObjProc proc,PerStateType * typePtr,PerStateInfo * pInfo)1495 PerStateInfo_FromObj(
1496     TreeCtrl *tree,		/* Widget info. */
1497     int domain,			/* STATE_DOMAIN_XXX index. */
1498     StateFromObjProc proc,	/* Procedure used to turn a Tcl_Obj into
1499 				 * a state bit-flag. */
1500     PerStateType *typePtr,	/* Type-specific functions and values. */
1501     PerStateInfo *pInfo		/* Per-state info to return. pInfo->obj
1502 				 * must be NULL or point to a valid Tcl_Obj. */
1503     )
1504 {
1505     int i, j;
1506     int objc, objc2;
1507     Tcl_Obj **objv, **objv2;
1508     PerStateData *pData;
1509 
1510 #ifdef TREECTRL_DEBUG
1511     pInfo->type = typePtr;
1512 #endif
1513 
1514     PerStateInfo_Free(tree, typePtr, pInfo);
1515 
1516     if (pInfo->obj == NULL)
1517 	return TCL_OK;
1518 
1519     if (Tcl_ListObjGetElements(tree->interp, pInfo->obj, &objc, &objv) != TCL_OK)
1520 	return TCL_ERROR;
1521 
1522     if (objc == 0)
1523 	return TCL_OK;
1524 
1525     if (objc == 1) {
1526 #ifdef ALLOC_HAX
1527 	pData = (PerStateData *) TreeAlloc_CAlloc(tree->allocData,
1528 	    typePtr->name, typePtr->size, 1, PERSTATE_ROUNDUP);
1529 #else
1530 	pData = (PerStateData *) ckalloc(typePtr->size);
1531 #endif
1532 	pData->stateOff = pData->stateOn = 0; /* all states */
1533 	if ((*typePtr->fromObjProc)(tree, objv[0], pData) != TCL_OK) {
1534 #ifdef ALLOC_HAX
1535 	    TreeAlloc_CFree(tree->allocData, typePtr->name, (char *) pData,
1536 		typePtr->size, 1, PERSTATE_ROUNDUP);
1537 #else
1538 	    WIPEFREE(pData, typePtr->size);
1539 #endif
1540 	    return TCL_ERROR;
1541 	}
1542 	pInfo->data = pData;
1543 	pInfo->count = 1;
1544 	return TCL_OK;
1545     }
1546 
1547     if (objc & 1) {
1548 	FormatResult(tree->interp, "list must have even number of elements");
1549 	return TCL_ERROR;
1550     }
1551 
1552 #ifdef ALLOC_HAX
1553     pData = (PerStateData *) TreeAlloc_CAlloc(tree->allocData,
1554 	typePtr->name, typePtr->size, objc / 2, PERSTATE_ROUNDUP);
1555 #else
1556     pData = (PerStateData *) ckalloc(typePtr->size * (objc / 2));
1557 #endif
1558     pInfo->data = pData;
1559     for (i = 0; i < objc; i += 2) {
1560 	if ((*typePtr->fromObjProc)(tree, objv[i], pData) != TCL_OK) {
1561 	    goto freeIt;
1562 	}
1563 	pInfo->count++;
1564 	if (Tcl_ListObjGetElements(tree->interp, objv[i + 1], &objc2, &objv2) != TCL_OK) {
1565 	    goto freeIt;
1566 	}
1567 	pData->stateOff = pData->stateOn = 0; /* all states */
1568 	for (j = 0; j < objc2; j++) {
1569 	    if (proc(tree, domain, objv2[j], &pData->stateOff, &pData->stateOn) != TCL_OK) {
1570 		goto freeIt;
1571 	    }
1572 	}
1573 	pData = (PerStateData *) (((char *) pData) + typePtr->size);
1574     }
1575     return TCL_OK;
1576 
1577 freeIt:
1578     pData = pInfo->data;
1579     for (i = 0; i < pInfo->count; i++) {
1580 	(*typePtr->freeProc)(tree, pData);
1581 	pData = (PerStateData *) (((char *) pData) + typePtr->size);
1582     }
1583 #ifdef ALLOC_HAX
1584     TreeAlloc_CFree(tree->allocData, typePtr->name, (char *) pInfo->data,
1585 	typePtr->size, objc / 2, PERSTATE_ROUNDUP);
1586 #else
1587     WIPEFREE(pInfo->data, typePtr->size * (objc / 2));
1588 #endif
1589     pInfo->data = NULL;
1590     pInfo->count = 0;
1591     return TCL_ERROR;
1592 }
1593 
1594 /*
1595  *----------------------------------------------------------------------
1596  *
1597  * PerStateInfo_ForState --
1598  *
1599  *	Return the best-matching PerStateData for a given state.
1600  *
1601  * Results:
1602  *	The return value is a pointer to the best-matching PerStateData.
1603  *	*match is set to a MATCH_xxx constant. NULL is returned if
1604  *	no appropriate PerStateData was found.
1605  *
1606  * Side effects:
1607  *	None.
1608  *
1609  *----------------------------------------------------------------------
1610  */
1611 
1612 PerStateData *
PerStateInfo_ForState(TreeCtrl * tree,PerStateType * typePtr,PerStateInfo * pInfo,int state,int * match)1613 PerStateInfo_ForState(
1614     TreeCtrl *tree,		/* Widget info. */
1615     PerStateType *typePtr,	/* Type-specific functions and values. */
1616     PerStateInfo *pInfo,	/* Per-state info to search. */
1617     int state,			/* State bit-flags to compare. */
1618     int *match			/* Returned MATCH_xxx constant. */
1619     )
1620 {
1621     PerStateData *pData = pInfo->data;
1622     int stateOff = ~state, stateOn = state;
1623     int i;
1624 
1625 #ifdef TREECTRL_DEBUG
1626     if ((pInfo->data != NULL) && (pInfo->type != typePtr)) {
1627 	panic("PerStateInfo_ForState type mismatch: got %s expected %s",
1628 		pInfo->type ? pInfo->type->name : "NULL", typePtr->name);
1629     }
1630 #endif
1631 
1632     for (i = 0; i < pInfo->count; i++) {
1633 	/* Any state */
1634 	if ((pData->stateOff == 0) &&
1635 		(pData->stateOn == 0)) {
1636 	    if (match) (*match) = MATCH_ANY;
1637 	    return pData;
1638 	}
1639 
1640 	/* Exact match */
1641 	if ((pData->stateOff == stateOff) &&
1642 		(pData->stateOn == stateOn)) {
1643 	    if (match) (*match) = MATCH_EXACT;
1644 	    return pData;
1645 	}
1646 
1647 	/* Partial match */
1648 	if (((pData->stateOff & stateOff) == pData->stateOff) &&
1649 		((pData->stateOn & stateOn) == pData->stateOn)) {
1650 	    if (match) (*match) = MATCH_PARTIAL;
1651 	    return pData;
1652 	}
1653 
1654 	pData = (PerStateData *) (((char *) pData) + typePtr->size);
1655     }
1656 
1657     if (match) (*match) = MATCH_NONE;
1658     return NULL;
1659 }
1660 
1661 /*
1662  *----------------------------------------------------------------------
1663  *
1664  * PerStateInfo_ObjForState --
1665  *
1666  *	Return a Tcl_Obj from the list object that was parsed by
1667  *	PerStateInfo_FromObj(). pInfo is searched for the best-matching
1668  *	PerStateData for the given state and the object used to
1669  *	create that PerStateData is returned.
1670  *
1671  * Results:
1672  *	*match is set to a MATCH_xxx constant. NULL is returned if
1673  *	no appropriate PerStateData was found. The object should not
1674  *	be modified by the caller.
1675  *
1676  * Side effects:
1677  *	None.
1678  *
1679  *----------------------------------------------------------------------
1680  */
1681 
1682 Tcl_Obj *
PerStateInfo_ObjForState(TreeCtrl * tree,PerStateType * typePtr,PerStateInfo * pInfo,int state,int * match)1683 PerStateInfo_ObjForState(
1684     TreeCtrl *tree,		/* Widget info. */
1685     PerStateType *typePtr,	/* Type-specific functions and values. */
1686     PerStateInfo *pInfo,	/* Per-state info to search. */
1687     int state,			/* State bit-flags to compare. */
1688     int *match			/* Returned MATCH_xxx constant. */
1689     )
1690 {
1691     PerStateData *pData;
1692     Tcl_Obj *obj;
1693     int i;
1694 
1695 #ifdef TREECTRL_DEBUG
1696     if ((pInfo->data != NULL) && (pInfo->type != typePtr))
1697 	panic("PerStateInfo_ObjForState type mismatch: got %s expected %s",
1698 		pInfo->type ? pInfo->type->name : "NULL", typePtr->name);
1699 #endif
1700 
1701     pData = PerStateInfo_ForState(tree, typePtr, pInfo, state, match);
1702     if (pData != NULL) {
1703 	i = (int) ((char *) pData - (char *) pInfo->data) / typePtr->size;
1704 	Tcl_ListObjIndex(tree->interp, pInfo->obj, i * 2, &obj);
1705 	return obj;
1706     }
1707 
1708     return NULL;
1709 }
1710 
1711 static Tcl_Obj *
DuplicateListObj(Tcl_Obj * objPtr)1712 DuplicateListObj(
1713     Tcl_Obj *objPtr
1714     )
1715 {
1716     int objc;
1717     Tcl_Obj **objv;
1718     int result;
1719 
1720     /*
1721      * Comment from TclLsetFlat:
1722      * ... A plain Tcl_DuplicateObj
1723      * will just increase the intrep's refCount without upping the sublists'
1724      * refCount, so that their true shared status cannot be determined from
1725      * their refCount.
1726      */
1727 
1728     result = Tcl_ListObjGetElements(NULL, objPtr, &objc, &objv);
1729     return Tcl_NewListObj(objc, objv);
1730 }
1731 
1732 /*
1733  *----------------------------------------------------------------------
1734  *
1735  * PerStateInfo_Undefine --
1736  *
1737  *	Called when a user-defined state flag is undefined. The state
1738  *	flag is cleared from every PerStateData using that flag. The
1739  *	list object that was parsed by PerStateInfo_FromObj() is modified
1740  *	by removing any reference to the undefined state.
1741  *
1742  * Results:
1743  *	The return value is a boolean indicating whether or not pInfo
1744  *	was modified.
1745  *
1746  * Side effects:
1747  *	The list object pointed to by pInfo->obj may be recreated.
1748  *
1749  *----------------------------------------------------------------------
1750  */
1751 
1752 int
PerStateInfo_Undefine(TreeCtrl * tree,PerStateType * typePtr,PerStateInfo * pInfo,int domain,int state)1753 PerStateInfo_Undefine(
1754     TreeCtrl *tree,		/* Widget info. */
1755     PerStateType *typePtr,	/* Type-specific functions and values. */
1756     PerStateInfo *pInfo,	/* Per-state info to modify. */
1757     int domain,			/* STATE_DOMAIN_XXX index. */
1758     int state			/* State bit-flag that was undefined. */
1759     )
1760 {
1761     PerStateData *pData = pInfo->data;
1762     int i, j, numStates, stateOff, stateOn;
1763     Tcl_Obj *configObj = pInfo->obj, *listObj, *stateObj;
1764     int modified = 0;
1765 
1766 #ifdef TREECTRL_DEBUG
1767     if ((pInfo->data != NULL) && (pInfo->type != typePtr))
1768 	panic("PerStateInfo_Undefine type mismatch: got %s expected %s",
1769 		pInfo->type ? pInfo->type->name : "NULL", typePtr->name);
1770 #endif
1771 
1772     for (i = 0; i < pInfo->count; i++) {
1773 	if ((pData->stateOff | pData->stateOn) & state) {
1774 	    pData->stateOff &= ~state;
1775 	    pData->stateOn &= ~state;
1776 	    if (Tcl_IsShared(configObj)) {
1777 		configObj = DuplicateListObj(configObj);
1778 		Tcl_DecrRefCount(pInfo->obj);
1779 		Tcl_IncrRefCount(configObj);
1780 		pInfo->obj = configObj;
1781 	    }
1782 	    Tcl_ListObjIndex(tree->interp, configObj, i * 2 + 1, &listObj);
1783 	    if (Tcl_IsShared(listObj)) {
1784 		listObj = DuplicateListObj(listObj);
1785 		Tcl_ListObjReplace(tree->interp, configObj, i * 2 + 1, 1, 1, &listObj);
1786 	    }
1787 	    Tcl_ListObjLength(tree->interp, listObj, &numStates);
1788 	    for (j = 0; j < numStates; ) {
1789 		Tcl_ListObjIndex(tree->interp, listObj, j, &stateObj);
1790 		stateOff = stateOn = 0;
1791 		TreeStateFromObj(tree, domain, stateObj, &stateOff, &stateOn); /* FIXME: why this proc? */
1792 		if ((stateOff | stateOn) & state) {
1793 		    Tcl_ListObjReplace(tree->interp, listObj, j, 1, 0, NULL);
1794 		    numStates--;
1795 		} else
1796 		    j++;
1797 	    }
1798 	    /* Given {bitmap {state1 state2 state3}}, we just invalidated
1799 	     * the string rep of the sublist {state1 state2 state3}, but not
1800 	     * the parent list. */
1801 	    Tcl_InvalidateStringRep(configObj);
1802 	    modified = 1;
1803 	}
1804 	pData = (PerStateData *) (((char *) pData) + typePtr->size);
1805     }
1806 
1807     return modified;
1808 }
1809 
1810 /*****/
1811 
1812 GC
Tree_GetGC(TreeCtrl * tree,unsigned long mask,XGCValues * gcValues)1813 Tree_GetGC(
1814     TreeCtrl *tree,
1815     unsigned long mask,
1816     XGCValues *gcValues)
1817 {
1818     GCCache *pGC;
1819     unsigned long valid = GCBackground | GCDashList | GCDashOffset | GCFont |
1820 	    GCForeground | GCFunction | GCGraphicsExposures | GCLineStyle;
1821 
1822     if ((mask | valid) != valid)
1823 	panic("Tree_GetGC: unsupported mask");
1824 
1825     for (pGC = tree->gcCache; pGC != NULL; pGC = pGC->next) {
1826 	if (mask != pGC->mask)
1827 	    continue;
1828 	if ((mask & GCBackground) &&
1829 		(pGC->gcValues.background != gcValues->background))
1830 	    continue;
1831 	if ((mask & GCDashList) &&
1832 		(pGC->gcValues.dashes != gcValues->dashes)) /* FIXME: single value */
1833 	    continue;
1834 	if ((mask & GCDashOffset) &&
1835 		(pGC->gcValues.dash_offset != gcValues->dash_offset))
1836 	    continue;
1837 	if ((mask & GCFont) &&
1838 		(pGC->gcValues.font != gcValues->font))
1839 	    continue;
1840 	if ((mask & GCForeground) &&
1841 		(pGC->gcValues.foreground != gcValues->foreground))
1842 	    continue;
1843 	if ((mask & GCFunction) &&
1844 		(pGC->gcValues.function != gcValues->function))
1845 	    continue;
1846 	if ((mask & GCGraphicsExposures) &&
1847 		(pGC->gcValues.graphics_exposures != gcValues->graphics_exposures))
1848 	    continue;
1849 	return pGC->gc;
1850     }
1851 
1852     pGC = (GCCache *) ckalloc(sizeof(*pGC));
1853     pGC->gcValues = (*gcValues);
1854     pGC->mask = mask;
1855     pGC->gc = Tk_GetGC(tree->tkwin, mask, gcValues);
1856     pGC->next = tree->gcCache;
1857     tree->gcCache = pGC;
1858 
1859     return pGC->gc;
1860 }
1861 
1862 void
Tree_FreeAllGC(TreeCtrl * tree)1863 Tree_FreeAllGC(
1864     TreeCtrl *tree)
1865 {
1866     GCCache *pGC = tree->gcCache, *next;
1867 
1868     while (pGC != NULL) {
1869 	next = pGC->next;
1870 	Tk_FreeGC(tree->display, pGC->gc);
1871 	WFREE(pGC, GCCache);
1872 	pGC = next;
1873     }
1874     tree->gcCache = NULL;
1875 }
1876 
1877 /*****/
1878 
1879 typedef struct PerStateDataBitmap PerStateDataBitmap;
1880 struct PerStateDataBitmap
1881 {
1882     PerStateData header;
1883     Pixmap bitmap;
1884 };
1885 
1886 static int
PSDBitmapFromObj(TreeCtrl * tree,Tcl_Obj * obj,PerStateDataBitmap * pBitmap)1887 PSDBitmapFromObj(
1888     TreeCtrl *tree,
1889     Tcl_Obj *obj,
1890     PerStateDataBitmap *pBitmap)
1891 {
1892     if (ObjectIsEmpty(obj)) {
1893 	/* Specify empty string to override masterX */
1894 	pBitmap->bitmap = None;
1895     } else {
1896 	pBitmap->bitmap = Tk_AllocBitmapFromObj(tree->interp, tree->tkwin, obj);
1897 	if (pBitmap->bitmap == None)
1898 	    return TCL_ERROR;
1899     }
1900     return TCL_OK;
1901 }
1902 
1903 static void
PSDBitmapFree(TreeCtrl * tree,PerStateDataBitmap * pBitmap)1904 PSDBitmapFree(
1905     TreeCtrl *tree,
1906     PerStateDataBitmap *pBitmap)
1907 {
1908     if (pBitmap->bitmap != None)
1909 	Tk_FreeBitmap(tree->display, pBitmap->bitmap);
1910 }
1911 
1912 PerStateType pstBitmap =
1913 {
1914     "pstBitmap",
1915     sizeof(PerStateDataBitmap),
1916     (PerStateType_FromObjProc) PSDBitmapFromObj,
1917     (PerStateType_FreeProc) PSDBitmapFree
1918 };
1919 
1920 Pixmap
PerStateBitmap_ForState(TreeCtrl * tree,PerStateInfo * pInfo,int state,int * match)1921 PerStateBitmap_ForState(
1922     TreeCtrl *tree,
1923     PerStateInfo *pInfo,
1924     int state,
1925     int *match)
1926 {
1927     PerStateDataBitmap *pData;
1928 
1929     pData = (PerStateDataBitmap *) PerStateInfo_ForState(tree, &pstBitmap, pInfo, state, match);
1930     if (pData != NULL)
1931 	return pData->bitmap;
1932     return None;
1933 }
1934 
1935 void
PerStateBitmap_MaxSize(TreeCtrl * tree,PerStateInfo * pInfo,int * widthPtr,int * heightPtr)1936 PerStateBitmap_MaxSize(
1937     TreeCtrl *tree,
1938     PerStateInfo *pInfo,
1939     int *widthPtr,
1940     int *heightPtr)
1941 {
1942     PerStateDataBitmap *pData = (PerStateDataBitmap *) pInfo->data;
1943     int i, width, height, w, h;
1944 
1945     width = height = 0;
1946 
1947     for (i = 0; i < pInfo->count; i++, ++pData) {
1948 	if (pData->bitmap == None)
1949 	    continue;
1950 	Tk_SizeOfBitmap(tree->display, pData->bitmap, &w, &h);
1951 	width = MAX(width, w);
1952 	height = MAX(height, h);
1953     }
1954 
1955     (*widthPtr) = width;
1956     (*heightPtr) = height;
1957 }
1958 
1959 /*****/
1960 
1961 typedef struct PerStateDataBoolean PerStateDataBoolean;
1962 struct PerStateDataBoolean
1963 {
1964     PerStateData header;
1965     int value;
1966 };
1967 
1968 static int
PSDBooleanFromObj(TreeCtrl * tree,Tcl_Obj * obj,PerStateDataBoolean * pBoolean)1969 PSDBooleanFromObj(
1970     TreeCtrl *tree,
1971     Tcl_Obj *obj,
1972     PerStateDataBoolean *pBoolean)
1973 {
1974     if (ObjectIsEmpty(obj)) {
1975 	pBoolean->value = -1;
1976     } else {
1977 	if (Tcl_GetBooleanFromObj(tree->interp, obj, &pBoolean->value) != TCL_OK)
1978 	    return TCL_ERROR;
1979     }
1980     return TCL_OK;
1981 }
1982 
1983 static void
PSDBooleanFree(TreeCtrl * tree,PerStateDataBoolean * pBoolean)1984 PSDBooleanFree(
1985     TreeCtrl *tree,
1986     PerStateDataBoolean *pBoolean)
1987 {
1988 }
1989 
1990 PerStateType pstBoolean =
1991 {
1992     "pstBoolean",
1993     sizeof(PerStateDataBoolean),
1994     (PerStateType_FromObjProc) PSDBooleanFromObj,
1995     (PerStateType_FreeProc) PSDBooleanFree
1996 };
1997 
1998 int
PerStateBoolean_ForState(TreeCtrl * tree,PerStateInfo * pInfo,int state,int * match)1999 PerStateBoolean_ForState(
2000     TreeCtrl *tree,
2001     PerStateInfo *pInfo,
2002     int state,
2003     int *match)
2004 {
2005     PerStateDataBoolean *pData;
2006 
2007     pData = (PerStateDataBoolean *) PerStateInfo_ForState(tree, &pstBoolean, pInfo, state, match);
2008     if (pData != NULL)
2009 	return pData->value;
2010     return -1;
2011 }
2012 
2013 /*****/
2014 
2015 typedef struct PerStateDataBorder PerStateDataBorder;
2016 struct PerStateDataBorder
2017 {
2018     PerStateData header;
2019     Tk_3DBorder border;
2020 };
2021 
2022 static int
PSDBorderFromObj(TreeCtrl * tree,Tcl_Obj * obj,PerStateDataBorder * pBorder)2023 PSDBorderFromObj(
2024     TreeCtrl *tree,
2025     Tcl_Obj *obj,
2026     PerStateDataBorder *pBorder)
2027 {
2028     if (ObjectIsEmpty(obj)) {
2029 	/* Specify empty string to override masterX */
2030 	pBorder->border = NULL;
2031     } else {
2032 	pBorder->border = Tk_Alloc3DBorderFromObj(tree->interp, tree->tkwin, obj);
2033 	if (pBorder->border == NULL)
2034 	    return TCL_ERROR;
2035     }
2036     return TCL_OK;
2037 }
2038 
2039 static void
PSDBorderFree(TreeCtrl * tree,PerStateDataBorder * pBorder)2040 PSDBorderFree(
2041     TreeCtrl *tree,
2042     PerStateDataBorder *pBorder)
2043 {
2044     if (pBorder->border != NULL)
2045 	Tk_Free3DBorder(pBorder->border);
2046 }
2047 
2048 PerStateType pstBorder =
2049 {
2050     "pstBorder",
2051     sizeof(PerStateDataBorder),
2052     (PerStateType_FromObjProc) PSDBorderFromObj,
2053     (PerStateType_FreeProc) PSDBorderFree
2054 };
2055 
2056 Tk_3DBorder
PerStateBorder_ForState(TreeCtrl * tree,PerStateInfo * pInfo,int state,int * match)2057 PerStateBorder_ForState(
2058     TreeCtrl *tree,
2059     PerStateInfo *pInfo,
2060     int state,
2061     int *match)
2062 {
2063     PerStateDataBorder *pData;
2064 
2065     pData = (PerStateDataBorder *) PerStateInfo_ForState(tree, &pstBorder, pInfo, state, match);
2066     if (pData != NULL)
2067 	return pData->border;
2068     return NULL;
2069 }
2070 
2071 /*****/
2072 
2073 typedef struct PerStateDataColor PerStateDataColor;
2074 struct PerStateDataColor
2075 {
2076     PerStateData header;
2077     TreeColor *color;
2078 };
2079 
2080 static int
PSDColorFromObj(TreeCtrl * tree,Tcl_Obj * obj,PerStateDataColor * pColor)2081 PSDColorFromObj(
2082     TreeCtrl *tree,
2083     Tcl_Obj *obj,
2084     PerStateDataColor *pColor)
2085 {
2086     if (ObjectIsEmpty(obj)) {
2087 	/* Specify empty string to override masterX */
2088 	pColor->color = NULL;
2089     } else {
2090 	pColor->color = Tree_AllocColorFromObj(tree, obj);
2091 	if (pColor->color == NULL)
2092 	    return TCL_ERROR;
2093     }
2094     return TCL_OK;
2095 }
2096 
2097 static void
PSDColorFree(TreeCtrl * tree,PerStateDataColor * pColor)2098 PSDColorFree(
2099     TreeCtrl *tree,
2100     PerStateDataColor *pColor)
2101 {
2102     if (pColor->color != NULL)
2103 	Tree_FreeColor(tree, pColor->color);
2104 }
2105 
2106 PerStateType pstColor =
2107 {
2108     "pstColor",
2109     sizeof(PerStateDataColor),
2110     (PerStateType_FromObjProc) PSDColorFromObj,
2111     (PerStateType_FreeProc) PSDColorFree
2112 };
2113 
2114 TreeColor *
PerStateColor_ForState(TreeCtrl * tree,PerStateInfo * pInfo,int state,int * match)2115 PerStateColor_ForState(
2116     TreeCtrl *tree,
2117     PerStateInfo *pInfo,
2118     int state,
2119     int *match)
2120 {
2121     PerStateDataColor *pData;
2122 
2123     pData = (PerStateDataColor *) PerStateInfo_ForState(tree, &pstColor, pInfo, state, match);
2124     if (pData != NULL)
2125 	return pData->color;
2126     return NULL;
2127 }
2128 
2129 /*****/
2130 
2131 typedef struct PerStateDataFont PerStateDataFont;
2132 struct PerStateDataFont
2133 {
2134     PerStateData header;
2135     Tk_Font tkfont;
2136 };
2137 
2138 static int
PSDFontFromObj(TreeCtrl * tree,Tcl_Obj * obj,PerStateDataFont * pFont)2139 PSDFontFromObj(
2140     TreeCtrl *tree,
2141     Tcl_Obj *obj,
2142     PerStateDataFont *pFont)
2143 {
2144     if (ObjectIsEmpty(obj)) {
2145 	/* Specify empty string to override masterX */
2146 	pFont->tkfont = NULL;
2147     } else {
2148 	pFont->tkfont = Tk_AllocFontFromObj(tree->interp, tree->tkwin, obj);
2149 	if (pFont->tkfont == NULL)
2150 	    return TCL_ERROR;
2151     }
2152     return TCL_OK;
2153 }
2154 
2155 static void
PSDFontFree(TreeCtrl * tree,PerStateDataFont * pFont)2156 PSDFontFree(
2157     TreeCtrl *tree,
2158     PerStateDataFont *pFont)
2159 {
2160     if (pFont->tkfont != NULL)
2161 	Tk_FreeFont(pFont->tkfont);
2162 }
2163 
2164 PerStateType pstFont =
2165 {
2166     "pstFont",
2167     sizeof(PerStateDataFont),
2168     (PerStateType_FromObjProc) PSDFontFromObj,
2169     (PerStateType_FreeProc) PSDFontFree
2170 };
2171 
2172 Tk_Font
PerStateFont_ForState(TreeCtrl * tree,PerStateInfo * pInfo,int state,int * match)2173 PerStateFont_ForState(
2174     TreeCtrl *tree,
2175     PerStateInfo *pInfo,
2176     int state,
2177     int *match)
2178 {
2179     PerStateDataFont *pData;
2180 
2181     pData = (PerStateDataFont *) PerStateInfo_ForState(tree, &pstFont, pInfo, state, match);
2182     if (pData != NULL)
2183 	return pData->tkfont;
2184     return NULL;
2185 }
2186 
2187 /*****/
2188 
2189 typedef struct PerStateDataImage PerStateDataImage;
2190 struct PerStateDataImage
2191 {
2192     PerStateData header;
2193     Tk_Image image;
2194     char *string;
2195 };
2196 
2197 static int
PSDImageFromObj(TreeCtrl * tree,Tcl_Obj * obj,PerStateDataImage * pImage)2198 PSDImageFromObj(
2199     TreeCtrl *tree,
2200     Tcl_Obj *obj,
2201     PerStateDataImage *pImage)
2202 {
2203     int length;
2204     char *string;
2205 
2206     if (ObjectIsEmpty(obj)) {
2207 	/* Specify empty string to override masterX */
2208 	pImage->image = NULL;
2209 	pImage->string = NULL;
2210     } else {
2211 	string = Tcl_GetStringFromObj(obj, &length);
2212 	pImage->image = Tree_GetImage(tree, string);
2213 	if (pImage->image == NULL)
2214 	    return TCL_ERROR;
2215 	pImage->string = ckalloc(length + 1);
2216 	strcpy(pImage->string, string);
2217     }
2218     return TCL_OK;
2219 }
2220 
2221 static void
PSDImageFree(TreeCtrl * tree,PerStateDataImage * pImage)2222 PSDImageFree(
2223     TreeCtrl *tree,
2224     PerStateDataImage *pImage)
2225 {
2226     if (pImage->string != NULL)
2227 	ckfree(pImage->string);
2228     if (pImage->image != NULL)
2229 	Tree_FreeImage(tree, pImage->image);
2230 }
2231 
2232 PerStateType pstImage =
2233 {
2234     "pstImage",
2235     sizeof(PerStateDataImage),
2236     (PerStateType_FromObjProc) PSDImageFromObj,
2237     (PerStateType_FreeProc) PSDImageFree
2238 };
2239 
2240 Tk_Image
PerStateImage_ForState(TreeCtrl * tree,PerStateInfo * pInfo,int state,int * match)2241 PerStateImage_ForState(
2242     TreeCtrl *tree,
2243     PerStateInfo *pInfo,
2244     int state,
2245     int *match)
2246 {
2247     PerStateDataImage *pData;
2248 
2249     pData = (PerStateDataImage *) PerStateInfo_ForState(tree, &pstImage, pInfo, state, match);
2250     if (pData != NULL)
2251 	return pData->image;
2252     return NULL;
2253 }
2254 
2255 void
PerStateImage_MaxSize(TreeCtrl * tree,PerStateInfo * pInfo,int * widthPtr,int * heightPtr)2256 PerStateImage_MaxSize(
2257     TreeCtrl *tree,
2258     PerStateInfo *pInfo,
2259     int *widthPtr,
2260     int *heightPtr)
2261 {
2262     PerStateDataImage *pData = (PerStateDataImage *) pInfo->data;
2263     int i, width, height, w, h;
2264 
2265     width = height = 0;
2266 
2267     for (i = 0; i < pInfo->count; i++, ++pData) {
2268 	if (pData->image == None)
2269 	    continue;
2270 	Tk_SizeOfImage(pData->image, &w, &h);
2271 	width = MAX(width, w);
2272 	height = MAX(height, h);
2273     }
2274 
2275     (*widthPtr) = width;
2276     (*heightPtr) = height;
2277 }
2278 
2279 /*****/
2280 
2281 typedef struct PerStateDataRelief PerStateDataRelief;
2282 struct PerStateDataRelief
2283 {
2284     PerStateData header;
2285     int relief;
2286 };
2287 
2288 static int
PSDReliefFromObj(TreeCtrl * tree,Tcl_Obj * obj,PerStateDataRelief * pRelief)2289 PSDReliefFromObj(
2290     TreeCtrl *tree,
2291     Tcl_Obj *obj,
2292     PerStateDataRelief *pRelief)
2293 {
2294     if (ObjectIsEmpty(obj)) {
2295 	/* Specify empty string to override masterX */
2296 	pRelief->relief = TK_RELIEF_NULL;
2297     } else {
2298 	if (Tk_GetReliefFromObj(tree->interp, obj, &pRelief->relief) != TCL_OK)
2299 	    return TCL_ERROR;
2300     }
2301     return TCL_OK;
2302 }
2303 
2304 static void
PSDReliefFree(TreeCtrl * tree,PerStateDataRelief * pRelief)2305 PSDReliefFree(
2306     TreeCtrl *tree,
2307     PerStateDataRelief *pRelief)
2308 {
2309 }
2310 
2311 PerStateType pstRelief =
2312 {
2313     "pstRelief",
2314     sizeof(PerStateDataRelief),
2315     (PerStateType_FromObjProc) PSDReliefFromObj,
2316     (PerStateType_FreeProc) PSDReliefFree
2317 };
2318 
2319 int
PerStateRelief_ForState(TreeCtrl * tree,PerStateInfo * pInfo,int state,int * match)2320 PerStateRelief_ForState(
2321     TreeCtrl *tree,
2322     PerStateInfo *pInfo,
2323     int state,
2324     int *match)
2325 {
2326     PerStateDataRelief *pData;
2327 
2328     pData = (PerStateDataRelief *) PerStateInfo_ForState(tree, &pstRelief, pInfo, state, match);
2329     if (pData != NULL)
2330 	return pData->relief;
2331     return TK_RELIEF_NULL;
2332 }
2333 
2334 /*****/
2335 
2336 int
Tree_GetFlagsFromString(TreeCtrl * tree,const char * string,int length,const char * typeStr,const CharFlag flags[],int * flagsPtr)2337 Tree_GetFlagsFromString(
2338     TreeCtrl *tree,
2339     const char *string,
2340     int length,
2341     const char *typeStr,
2342     const CharFlag flags[],
2343     int *flagsPtr
2344     )
2345 {
2346     int i, j, bits = 0, allBits = 0, numFlags = 0;
2347 
2348     for (j = 0; flags[j].flagChar != '\0'; j++) {
2349 	allBits |= flags[j].flagBit;
2350 	numFlags++;
2351     }
2352 
2353     for (i = 0; i < length; i++) {
2354 	for (j = 0; flags[j].flagChar != '\0'; j++) {
2355 	    if (string[i] == flags[j].flagChar
2356 		    || string[i] == toupper(flags[j].flagChar)) {
2357 		bits |= flags[j].flagBit;
2358 		break;
2359 	    }
2360 	}
2361 	if (flags[j].flagChar == '\0') {
2362 	    Tcl_ResetResult(tree->interp);
2363 	    Tcl_AppendResult(tree->interp, "bad ", typeStr, " \"",
2364 		    string, "\": must be a string ",
2365 		    "containing zero or more of ",
2366 		    (char *) NULL);
2367 	    for (j = 0; flags[j].flagChar != '\0'; j++) {
2368 		char buf[8];
2369 		if (flags[j+1].flagChar != '\0')
2370 		    (void) sprintf(buf, "%c%s ", flags[j].flagChar,
2371 			(numFlags > 2) ? "," : "");
2372 		else
2373 		    (void) sprintf(buf, "and %c", flags[j].flagChar);
2374 		Tcl_AppendResult(tree->interp, buf, (char *) NULL);
2375 	    }
2376 	    return TCL_ERROR;
2377 	}
2378     }
2379 
2380     (*flagsPtr) &= ~allBits;
2381     (*flagsPtr) |= bits;
2382 
2383     return TCL_OK;
2384 }
2385 
2386 int
Tree_GetFlagsFromObj(TreeCtrl * tree,Tcl_Obj * obj,const char * typeStr,const CharFlag flags[],int * flagsPtr)2387 Tree_GetFlagsFromObj(
2388     TreeCtrl *tree,
2389     Tcl_Obj *obj,
2390     const char *typeStr,
2391     const CharFlag flags[],
2392     int *flagsPtr
2393     )
2394 {
2395     int length;
2396     char *string;
2397 
2398     string = Tcl_GetStringFromObj(obj, &length);
2399     return Tree_GetFlagsFromString(tree, string, length, typeStr, flags,
2400 	    flagsPtr);
2401 }
2402 
2403 /*****/
2404 
2405 /* The rect element's -open option */
2406 typedef struct PerStateDataFlags PerStateDataFlags;
2407 struct PerStateDataFlags
2408 {
2409     PerStateData header;
2410     int flags;
2411 };
2412 
2413 static int
PSDFlagsFromObj(TreeCtrl * tree,Tcl_Obj * obj,PerStateDataFlags * pFlags)2414 PSDFlagsFromObj(
2415     TreeCtrl *tree,
2416     Tcl_Obj *obj,
2417     PerStateDataFlags *pFlags)
2418 {
2419     if (ObjectIsEmpty(obj)) {
2420 	pFlags->flags = 0xFFFFFFFF;
2421     } else {
2422 	static const CharFlag openFlags[] = {
2423 	    { 'n', RECT_OPEN_N },
2424 	    { 'e', RECT_OPEN_E },
2425 	    { 's', RECT_OPEN_S },
2426 	    { 'w', RECT_OPEN_W },
2427 	    { 0, 0 }
2428 	};
2429 	pFlags->flags = 0;
2430 	if (Tree_GetFlagsFromObj(tree, obj, "open value", openFlags,
2431 		&pFlags->flags) != TCL_OK) {
2432 	    return TCL_ERROR;
2433 	}
2434     }
2435     return TCL_OK;
2436 }
2437 
2438 static void
PSDFlagsFree(TreeCtrl * tree,PerStateDataFlags * pFlags)2439 PSDFlagsFree(
2440     TreeCtrl *tree,
2441     PerStateDataFlags *pFlags)
2442 {
2443 }
2444 
2445 PerStateType pstFlags =
2446 {
2447     "pstFlags",
2448     sizeof(PerStateDataFlags),
2449     (PerStateType_FromObjProc) PSDFlagsFromObj,
2450     (PerStateType_FreeProc) PSDFlagsFree
2451 };
2452 
2453 int
PerStateFlags_ForState(TreeCtrl * tree,PerStateInfo * pInfo,int state,int * match)2454 PerStateFlags_ForState(
2455     TreeCtrl *tree,
2456     PerStateInfo *pInfo,
2457     int state,
2458     int *match)
2459 {
2460     PerStateDataFlags *pData;
2461 
2462     pData = (PerStateDataFlags *) PerStateInfo_ForState(tree, &pstFlags, pInfo, state, match);
2463     if (pData != NULL)
2464 	return pData->flags;
2465     return 0xFFFFFFFF;
2466 }
2467 
2468 /*****/
2469 
2470 void
PSTSave(PerStateInfo * pInfo,PerStateInfo * pSave)2471 PSTSave(
2472     PerStateInfo *pInfo,
2473     PerStateInfo *pSave)
2474 {
2475 #ifdef TREECTRL_DEBUG
2476     pSave->type = pInfo->type; /* could be NULL */
2477 #endif
2478     pSave->data = pInfo->data;
2479     pSave->count = pInfo->count;
2480     pInfo->data = NULL;
2481     pInfo->count = 0;
2482 }
2483 
2484 void
PSTRestore(TreeCtrl * tree,PerStateType * typePtr,PerStateInfo * pInfo,PerStateInfo * pSave)2485 PSTRestore(
2486     TreeCtrl *tree,
2487     PerStateType *typePtr,
2488     PerStateInfo *pInfo,
2489     PerStateInfo *pSave)
2490 {
2491     PerStateInfo_Free(tree, typePtr, pInfo);
2492     pInfo->data = pSave->data;
2493     pInfo->count = pSave->count;
2494 }
2495 
2496 #ifdef ALLOC_HAX
2497 
2498 /*
2499  * The following TreeAlloc_xxx calls implement a mini memory allocator that
2500  * allocates blocks of same-sized chunks, and holds on to those chunks when
2501  * they are freed so they can be reused quickly. If you don't want to use it
2502  * just comment out #define ALLOC_HAX in tkTreeCtrl.h.
2503  */
2504 
2505 typedef struct AllocElem AllocElem;
2506 typedef struct AllocBlock AllocBlock;
2507 typedef struct AllocList AllocList;
2508 typedef struct AllocData AllocData;
2509 
2510 #ifdef TREECTRL_DEBUG
2511 #define ALLOC_STATS
2512 #endif
2513 
2514 #ifdef ALLOC_STATS
2515 typedef struct AllocStats AllocStats;
2516 #endif
2517 
2518 /*
2519  * One of the following structures exists for each client piece of memory.
2520  * These structures are allocated in arrays (blocks).
2521  */
2522 struct AllocElem
2523 {
2524     AllocElem *next;
2525 #ifdef TREECTRL_DEBUG
2526     char dbug[4];	/* "DBUG" */
2527     int free;		/* 1 if elem is available for reuse. */
2528     int size;		/* Number of bytes in body[]. */
2529 #endif
2530     char body[1];	/* First byte of client's space.  Actual
2531 			 * size of this field will be larger than
2532 			 * one. */
2533 };
2534 
2535 struct AllocBlock
2536 {
2537     int count;		/* Size of .elem[] */
2538     AllocBlock *next;	/* Next block with same-sized elems. */
2539     AllocElem elem[1];	/* Actual size will be larger than one. */
2540 };
2541 
2542 /*
2543  * One of the following structures maintains an array of blocks of AllocElems
2544  * of the same size.
2545  */
2546 struct AllocList
2547 {
2548     int size;		/* Size of every AllocElem.body[] */
2549     AllocElem *head;	/* Top of stack of unused pieces of memory. */
2550     AllocBlock *blocks;	/* Linked list of allocated blocks. The blocks
2551 			 * may contain a different number of elements. */
2552     int blockSize;	/* The number of AllocElems per block to allocate.
2553 			 * Starts at 16 and gets doubled up to 1024. */
2554     AllocList *next;	/* Points to an AllocList with a different .size */
2555 };
2556 
2557 /*
2558  * A pointer to one of the following structures is stored in each TreeCtrl.
2559  */
2560 struct AllocData
2561 {
2562     AllocList *freeLists;	/* Linked list. */
2563 #ifdef ALLOC_STATS
2564     AllocStats *stats;		/* For memory-usage reporting. */
2565 #endif
2566 };
2567 
2568 #ifdef ALLOC_STATS
2569 struct AllocStats {
2570     Tk_Uid id;			/* Name for reporting results. */
2571     unsigned count;		/* Number of allocations. */
2572     unsigned size;		/* Total allocated bytes. */
2573     AllocStats *next;		/* Linked list. */
2574 };
2575 #endif
2576 
2577 /*
2578  * The following macro computes the offset of the "body" field within
2579  * AllocElem.  It is used to get back to the header pointer from the
2580  * body pointer that's used by clients.
2581  */
2582 
2583 #ifdef offsetofXXX
2584 #define BODY_OFFSET ((size_t) offsetof(AllocElem, body))
2585 #else
2586 #define BODY_OFFSET ((size_t) (&((AllocElem *) 0)->body))
2587 #endif
2588 
2589 #ifdef ALLOC_STATS
2590 
2591 static AllocStats *
AllocStats_Get(ClientData _data,Tk_Uid id)2592 AllocStats_Get(
2593     ClientData _data,
2594     Tk_Uid id
2595     )
2596 {
2597     AllocData *data = (AllocData *) _data;
2598     AllocStats *stats = data->stats;
2599 
2600     while (stats != NULL) {
2601 	if (stats->id == id)
2602 	    break;
2603 	stats = stats->next;
2604     }
2605     if (stats == NULL) {
2606 	stats = (AllocStats *) ckalloc(sizeof(AllocStats));
2607 	stats->id = id;
2608 	stats->count = 0;
2609 	stats->size = 0;
2610 	stats->next = data->stats;
2611 	data->stats = stats;
2612     }
2613     return stats;
2614 }
2615 
2616 void
TreeAlloc_Stats(Tcl_Interp * interp,ClientData _data)2617 TreeAlloc_Stats(
2618     Tcl_Interp *interp,
2619     ClientData _data
2620     )
2621 {
2622     AllocData *data = (AllocData *) _data;
2623     AllocStats *stats = data->stats;
2624     int numElems = 0;
2625     Tcl_DString dString;
2626 
2627     Tcl_DStringInit(&dString);
2628     while (stats != NULL) {
2629 	DStringAppendf(&dString, "%-20s: %8d : %8d B %5d KB\n",
2630 		stats->id, stats->count,
2631 		stats->size, (stats->size + 1023) / 1024);
2632 	numElems += stats->count;
2633 	stats = stats->next;
2634     }
2635     DStringAppendf(&dString, "%-31s: %8d B %5d KB\n", "AllocElem overhead",
2636 	    numElems * BODY_OFFSET, (numElems * BODY_OFFSET) / 1024);
2637     Tcl_DStringResult(interp, &dString);
2638 }
2639 
2640 #endif /* ALLOC_STATS */
2641 
2642 /*
2643  *----------------------------------------------------------------------
2644  *
2645  * TreeAlloc_Alloc --
2646  *
2647  *	Return storage for a piece of data of the given size.
2648  *
2649  * Results:
2650  *	The return value is a pointer to memory for the caller's
2651  *	use.
2652  *
2653  * Side effects:
2654  *	Memory may be allocated.
2655  *
2656  *----------------------------------------------------------------------
2657  */
2658 
2659 char *
TreeAlloc_Alloc(ClientData _data,Tk_Uid id,int size)2660 TreeAlloc_Alloc(
2661     ClientData _data,		/* Token returned by TreeAlloc_Init(). */
2662     Tk_Uid id,			/* ID for memory-usage reporting. */
2663     int size			/* Number of bytes needed. */
2664     )
2665 {
2666     AllocData *data = (AllocData *) _data;
2667     AllocList *freeLists = data->freeLists;
2668     AllocList *freeList = freeLists;
2669     AllocBlock *block;
2670     AllocElem *elem, *result;
2671 #ifdef ALLOC_STATS
2672     AllocStats *stats = AllocStats_Get(_data, id);
2673 #endif
2674     int i;
2675 
2676 #ifdef ALLOC_STATS
2677     stats->count++;
2678     stats->size += size;
2679 #endif
2680 
2681     while ((freeList != NULL) && (freeList->size != size))
2682 	freeList = freeList->next;
2683 
2684     if (freeList == NULL) {
2685 	freeList = (AllocList *) ckalloc(sizeof(AllocList));
2686 	freeList->size = size;
2687 	freeList->head = NULL;
2688 	freeList->next = freeLists;
2689 	freeList->blocks = NULL;
2690 	freeList->blockSize = 16;
2691 	freeLists = freeList;
2692 	((AllocData *) data)->freeLists = freeLists;
2693     }
2694 
2695     if (freeList->head == NULL) {
2696 	unsigned elemSize = TCL_ALIGN(BODY_OFFSET + size);
2697 
2698 	block = (AllocBlock *) ckalloc(Tk_Offset(AllocBlock, elem) +
2699 		elemSize * freeList->blockSize);
2700 	block->count = freeList->blockSize;
2701 	block->next = freeList->blocks;
2702 
2703 /* dbwin("TreeAlloc_Alloc alloc %d of size %d\n", freeList->blockSize, size); */
2704 	freeList->blocks = block;
2705 	if (freeList->blockSize < 1024)
2706 	    freeList->blockSize *= 2;
2707 
2708 	freeList->head = block->elem;
2709 	elem = freeList->head;
2710 	for (i = 1; i < block->count - 1; i++) {
2711 #ifdef TREECTRL_DEBUG
2712 	    strncpy(elem->dbug, "DBUG", 4);
2713 	    elem->free = 1;
2714 	    elem->size = size;
2715 #endif
2716 	    elem->next = (AllocElem *) (((char *) freeList->head) +
2717 		elemSize * i);
2718 	    elem = elem->next;
2719 	}
2720 	elem->next = NULL;
2721 #ifdef TREECTRL_DEBUG
2722 	strncpy(elem->dbug, "DBUG", 4);
2723 	elem->free = 1;
2724 	elem->size = size;
2725 #endif
2726     }
2727 
2728     result = freeList->head;
2729     freeList->head = result->next;
2730 #ifdef TREECTRL_DEBUG
2731     if (!result->free)
2732 	panic("TreeAlloc_Alloc: element not marked free");
2733     result->free = 0;
2734 #endif
2735     return result->body;
2736 }
2737 
2738 /*
2739  *----------------------------------------------------------------------
2740  *
2741  * TreeAlloc_Realloc --
2742  *
2743  *	Realloc.
2744  *
2745  * Results:
2746  *	The return value is a pointer to memory for the caller's
2747  *	use.
2748  *
2749  * Side effects:
2750  *	Memory may be allocated.
2751  *
2752  *----------------------------------------------------------------------
2753  */
2754 
2755 char *
TreeAlloc_Realloc(ClientData data,Tk_Uid id,char * ptr,int size1,int size2)2756 TreeAlloc_Realloc(
2757     ClientData data,		/* Token returned by TreeAlloc_Init(). */
2758     Tk_Uid id,			/* ID for memory-usage reporting. */
2759     char *ptr,
2760     int size1,			/* Number of bytes in ptr. */
2761     int size2			/* Number of bytes needed. */
2762     )
2763 {
2764     char *ptr2;
2765 
2766     ptr2 = TreeAlloc_Alloc(data, id, size2);
2767     memcpy(ptr2, ptr, MIN(size1, size2));
2768     TreeAlloc_Free(data, id, ptr, size1);
2769     return ptr2;
2770 }
2771 
2772 /*
2773  *----------------------------------------------------------------------
2774  *
2775  * TreeAlloc_Free --
2776  *
2777  *	Mark a piece of memory as free for reuse.
2778  *
2779  * Results:
2780  *	The piece of memory is added to a list of free pieces of the
2781  *	same size.
2782  *
2783  * Side effects:
2784  *	None.
2785  *
2786  *----------------------------------------------------------------------
2787  */
2788 
2789 void
TreeAlloc_Free(ClientData _data,Tk_Uid id,char * ptr,int size)2790 TreeAlloc_Free(
2791     ClientData _data,		/* Token returned by TreeAlloc_Init(). */
2792     Tk_Uid id,			/* ID for memory-usage reporting. */
2793     char *ptr,			/* Memory to mark for reuse. Must have
2794 				 * been allocated by TreeAlloc_Alloc(). */
2795     int size			/* Number of bytes. Must match the size
2796 				 * passed to TreeAlloc_CAlloc(). */
2797     )
2798 {
2799     AllocData *data = (AllocData *) _data;
2800     AllocList *freeLists = data->freeLists;
2801     AllocList *freeList = freeLists;
2802     AllocElem *elem;
2803 #ifdef ALLOC_STATS
2804     AllocStats *stats = AllocStats_Get(_data, id);
2805 #endif
2806 
2807 #ifdef ALLOC_STATS
2808     stats->count--;
2809     stats->size -= size;
2810 #endif
2811 
2812     /* Comment from Tcl_DbCkfree: */
2813     /*
2814      * The following cast is *very* tricky.  Must convert the pointer
2815      * to an integer before doing arithmetic on it, because otherwise
2816      * the arithmetic will be done differently (and incorrectly) on
2817      * word-addressed machines such as Crays (will subtract only bytes,
2818      * even though BODY_OFFSET is in words on these machines).
2819      */
2820     /* Note: The Tcl source used to do "(unsigned long) ptr" but that
2821      * results in pointer truncation on 64-bit Windows builds. */
2822     elem = (AllocElem *) (((size_t) ptr) - BODY_OFFSET);
2823 
2824 #ifdef TREECTRL_DEBUG
2825     if (strncmp(elem->dbug, "DBUG", 4) != 0)
2826 	panic("TreeAlloc_Free: element header != DBUG");
2827     if (elem->free)
2828 	panic("TreeAlloc_Free: element already marked free");
2829     if (elem->size != size)
2830 	panic("TreeAlloc_Free: element size %d != size %d", elem->size, size);
2831 #endif
2832     while (freeList != NULL && freeList->size != size)
2833 	freeList = freeList->next;
2834     if (freeList == NULL)
2835 	panic("TreeAlloc_Free: can't find free list for size %d", size);
2836 
2837     WIPE(elem->body, size);
2838     elem->next = freeList->head;
2839 #ifdef TREECTRL_DEBUG
2840     elem->free = 1;
2841 #endif
2842     freeList->head = elem;
2843 }
2844 
2845 /*
2846  *----------------------------------------------------------------------
2847  *
2848  * TreeAlloc_CAlloc --
2849  *
2850  *	Return storage for an array of pieces of memory.
2851  *
2852  * Results:
2853  *	Pointer to the available memory.
2854  *
2855  * Side effects:
2856  *	Memory may be allocated.
2857  *
2858  *----------------------------------------------------------------------
2859  */
2860 
2861 char *
TreeAlloc_CAlloc(ClientData data,Tk_Uid id,int size,int count,int roundUp)2862 TreeAlloc_CAlloc(
2863     ClientData data,		/* Token returned by TreeAlloc_Init(). */
2864     Tk_Uid id,			/* ID for memory-usage reporting. */
2865     int size,			/* Number of bytes needed for each piece
2866 				 * of memory. */
2867     int count,			/* Number of pieces of memory needed. */
2868     int roundUp			/* Positive number used to reduce the number
2869 				 * of lists of memory pieces of different
2870 				 * size. */
2871     )
2872 {
2873     int n = (count / roundUp) * roundUp + ((count % roundUp) ? roundUp : 0);
2874 #ifdef ALLOC_STATS
2875     AllocStats *stats = AllocStats_Get(data, id);
2876 #endif
2877 
2878 #ifdef ALLOC_STATS
2879     stats->count += count - 1;
2880 #endif
2881     return TreeAlloc_Alloc(data, id, size * n);
2882 }
2883 
2884 /*
2885  *----------------------------------------------------------------------
2886  *
2887  * TreeAlloc_CFree --
2888  *
2889  *	Mark a piece of memory as free for reuse.
2890  *
2891  * Results:
2892  *	The piece of memory is added to a list of free pieces of the
2893  *	same size.
2894  *
2895  * Side effects:
2896  *	None.
2897  *
2898  *----------------------------------------------------------------------
2899  */
2900 
2901 void
TreeAlloc_CFree(ClientData data,Tk_Uid id,char * ptr,int size,int count,int roundUp)2902 TreeAlloc_CFree(
2903     ClientData data,		/* Token returned by TreeAlloc_Init(). */
2904     Tk_Uid id,			/* ID for memory-usage reporting. */
2905     char *ptr,			/* Memory to mark for reuse. Must have
2906 				 * been allocated by TreeAlloc_CAlloc(). */
2907     int size,			/* Same arg to TreeAlloc_CAlloc(). */
2908     int count,			/* Same arg to TreeAlloc_CAlloc(). */
2909     int roundUp			/* Same arg to TreeAlloc_CAlloc(). */
2910     )
2911 {
2912     int n = (count / roundUp) * roundUp + ((count % roundUp) ? roundUp : 0);
2913 #ifdef ALLOC_STATS
2914     AllocStats *stats = AllocStats_Get(data, id);
2915 #endif
2916 
2917     TreeAlloc_Free(data, id, ptr, size * n);
2918 #ifdef ALLOC_STATS
2919     stats->count -= count - 1;
2920 #endif
2921 }
2922 
2923 /*
2924  *----------------------------------------------------------------------
2925  *
2926  * TreeAlloc_Init --
2927  *
2928  *	Allocate and initialize a new memory-manager record.
2929  *
2930  * Results:
2931  *	Pointer to memory-manager record.
2932  *
2933  * Side effects:
2934  *	Memory is allocated.
2935  *
2936  *----------------------------------------------------------------------
2937  */
2938 
2939 ClientData
TreeAlloc_Init(void)2940 TreeAlloc_Init(void)
2941 {
2942     AllocData *data = (AllocData *) ckalloc(sizeof(AllocData));
2943     data->freeLists = NULL;
2944 #ifdef ALLOC_STATS
2945     data->stats = NULL;
2946 #endif
2947     return data;
2948 }
2949 
2950 /*
2951  *----------------------------------------------------------------------
2952  *
2953  * TreeAlloc_Finalize --
2954  *
2955  *	Free all the memory associated with a memory-manager record.
2956  *
2957  * Results:
2958  *	Pointer to memory-manager record.
2959  *
2960  * Side effects:
2961  *	Memory is deallocated.
2962  *
2963  *----------------------------------------------------------------------
2964  */
2965 
2966 void
TreeAlloc_Finalize(ClientData _data)2967 TreeAlloc_Finalize(
2968     ClientData _data		/* Pointer to AllocData created by
2969 				 * TreeAlloc_Init(). */
2970     )
2971 {
2972     AllocData *data = (AllocData *) _data;
2973     AllocList *freeList = data->freeLists;
2974 #ifdef ALLOC_STATS
2975     AllocStats *stats = data->stats;
2976 #endif
2977 
2978     while (freeList != NULL) {
2979 	AllocList *nextList = freeList->next;
2980 	AllocBlock *block = freeList->blocks;
2981 	while (block != NULL) {
2982 	    AllocBlock *nextBlock = block->next;
2983 	    ckfree((char *) block);
2984 	    block = nextBlock;
2985 	}
2986 	ckfree((char *) freeList);
2987 	freeList = nextList;
2988     }
2989 
2990 #ifdef ALLOC_STATS
2991     while (stats != NULL) {
2992 	AllocStats *next = stats->next;
2993 	ckfree((char *) stats);
2994 	stats = next;
2995     }
2996 #endif
2997 
2998     ckfree((char *) data);
2999 }
3000 
3001 #endif /* ALLOC_HAX */
3002 
3003 /*
3004  *----------------------------------------------------------------------
3005  *
3006  * TreePtrList_Init --
3007  *
3008  *	Initializes an pointer list, discarding any previous contents
3009  *	of the pointer list (TreePtrList_Free should have been called already
3010  *	if the pointer list was previously in use).
3011  *
3012  * Results:
3013  *	None.
3014  *
3015  * Side effects:
3016  *	The pointer list is initialized to be empty.
3017  *
3018  *----------------------------------------------------------------------
3019  */
3020 
3021 void
TreePtrList_Init(TreeCtrl * tree,TreePtrList * tplPtr,int count)3022 TreePtrList_Init(
3023     TreeCtrl *tree,		/* Widget info. */
3024     TreePtrList *tplPtr,	/* Structure describing pointer list. */
3025     int count			/* Number of pointers the list should hold.
3026 				 * 0 for default. */
3027     )
3028 {
3029 #ifdef TREECTRL_DEBUG
3030     strncpy(tplPtr->magic, "MAGC", 4);
3031 #endif
3032     tplPtr->tree = tree;
3033     tplPtr->pointers = tplPtr->pointerSpace;
3034     tplPtr->count = 0;
3035     tplPtr->space = TIL_STATIC_SPACE;
3036 
3037     if (count + 1 > TIL_STATIC_SPACE) {
3038 	tplPtr->space = count + 1;
3039 	tplPtr->pointers = (ClientData *) ckalloc(tplPtr->space * sizeof(ClientData));
3040     }
3041 
3042     tplPtr->pointers[0] = NULL;
3043 }
3044 
3045 /*
3046  *----------------------------------------------------------------------
3047  *
3048  * TreePtrList_Grow --
3049  *
3050  *	Increase the available space in an pointer list.
3051  *
3052  * Results:
3053  *	The pointers[] array is resized if needed.
3054  *
3055  * Side effects:
3056  *	Memory gets reallocated if needed.
3057  *
3058  *----------------------------------------------------------------------
3059  */
3060 
3061 void
TreePtrList_Grow(TreePtrList * tplPtr,int count)3062 TreePtrList_Grow(
3063     TreePtrList *tplPtr,	/* Structure describing pointer list. */
3064     int count			/* Number of pointers the list should hold. */
3065     )
3066 {
3067 #ifdef TREECTRL_DEBUG
3068     if (strncmp(tplPtr->magic, "MAGC", 4) != 0)
3069 	panic("TreePtrList_Grow: using uninitialized list");
3070 #endif
3071     if (tplPtr->space >= count + 1)
3072 	return;
3073     while (tplPtr->space < count + 1)
3074 	tplPtr->space *= 2;
3075     if (tplPtr->pointers == tplPtr->pointerSpace) {
3076 	ClientData *pointers;
3077 	pointers = (ClientData *) ckalloc(tplPtr->space * sizeof(ClientData));
3078 	memcpy(pointers, tplPtr->pointers, (tplPtr->count + 1) * sizeof(ClientData));
3079 	tplPtr->pointers = pointers;
3080     } else {
3081 	tplPtr->pointers = (ClientData *) ckrealloc((char *) tplPtr->pointers,
3082 		tplPtr->space * sizeof(ClientData));
3083     }
3084 }
3085 
3086 /*
3087  *----------------------------------------------------------------------
3088  *
3089  * TreePtrList_Append --
3090  *
3091  *	Append an pointer to an pointer list.
3092  *
3093  * Results:
3094  *	The return value is a pointer to the list of pointers.
3095  *
3096  * Side effects:
3097  *	Memory gets reallocated if needed.
3098  *
3099  *----------------------------------------------------------------------
3100  */
3101 
3102 ClientData *
TreePtrList_Append(TreePtrList * tplPtr,ClientData pointer)3103 TreePtrList_Append(
3104     TreePtrList *tplPtr,	/* Structure describing pointer list. */
3105     ClientData pointer		/* Item to append. */
3106     )
3107 {
3108 #ifdef TREECTRL_DEBUG
3109     if (strncmp(tplPtr->magic, "MAGC", 4) != 0)
3110 	panic("TreePtrList_Append: using uninitialized list");
3111 #endif
3112     TreePtrList_Grow(tplPtr, tplPtr->count + 1);
3113     tplPtr->pointers[tplPtr->count] = pointer;
3114     tplPtr->count++;
3115     tplPtr->pointers[tplPtr->count] = NULL;
3116     return tplPtr->pointers;
3117 }
3118 
3119 /*
3120  *----------------------------------------------------------------------
3121  *
3122  * TreePtrList_Concat --
3123  *
3124  *	Join two pointer lists.
3125  *
3126  * Results:
3127  *	The return value is a pointer to the list of pointers.
3128  *
3129  * Side effects:
3130  *	Memory gets reallocated if needed.
3131  *
3132  *----------------------------------------------------------------------
3133  */
3134 
3135 ClientData *
TreePtrList_Concat(TreePtrList * tplPtr,TreePtrList * tpl2Ptr)3136 TreePtrList_Concat(
3137     TreePtrList *tplPtr,	/* Structure describing pointer list. */
3138     TreePtrList *tpl2Ptr	/* Item list to append. */
3139     )
3140 {
3141 #ifdef TREECTRL_DEBUG
3142     if (strncmp(tplPtr->magic, "MAGC", 4) != 0)
3143 	panic("TreePtrList_Concat: using uninitialized list");
3144 #endif
3145     TreePtrList_Grow(tplPtr, tplPtr->count + tpl2Ptr->count);
3146     memcpy(tplPtr->pointers + tplPtr->count, tpl2Ptr->pointers,
3147 	tpl2Ptr->count * sizeof(ClientData));
3148     tplPtr->count += tpl2Ptr->count;
3149     tplPtr->pointers[tplPtr->count] = NULL;
3150     return tplPtr->pointers;
3151 }
3152 
3153 /*
3154  *----------------------------------------------------------------------
3155  *
3156  * TreePtrList_Free --
3157  *
3158  *	Frees up any memory allocated for the pointer list and
3159  *	reinitializes the pointer list to an empty state.
3160  *
3161  * Results:
3162  *	None.
3163  *
3164  * Side effects:
3165  *	The previous contents of the pointer list are lost.
3166  *
3167  *---------------------------------------------------------------------- */
3168 
3169 void
TreePtrList_Free(TreePtrList * tplPtr)3170 TreePtrList_Free(
3171     TreePtrList *tplPtr		/* Structure describing pointer list. */
3172     )
3173 {
3174 #ifdef TREECTRL_DEBUG
3175     if (strncmp(tplPtr->magic, "MAGC", 4) != 0)
3176 	panic("TreePtrList_Free: using uninitialized list");
3177 #endif
3178     if (tplPtr->pointers != tplPtr->pointerSpace) {
3179 	ckfree((char *) tplPtr->pointers);
3180     }
3181     tplPtr->pointers = tplPtr->pointerSpace;
3182     tplPtr->count = 0;
3183     tplPtr->space = TIL_STATIC_SPACE;
3184     tplPtr->pointers[0] = NULL;
3185 }
3186 
3187 #define TAG_INFO_SIZE(tagSpace) \
3188     (Tk_Offset(TagInfo, tagPtr) + ((tagSpace) * sizeof(Tk_Uid)))
3189 
3190 static CONST char *TagInfoUid = "TagInfo";
3191 
3192 /*
3193  *----------------------------------------------------------------------
3194  *
3195  * TagInfo_Add --
3196  *
3197  *	Adds tags to a list of tags.
3198  *
3199  * Results:
3200  *	Non-duplicate tags are added.
3201  *
3202  * Side effects:
3203  *	Memory may be (re)allocated.
3204  *
3205  *----------------------------------------------------------------------
3206  */
3207 
3208 TagInfo *
TagInfo_Add(TreeCtrl * tree,TagInfo * tagInfo,Tk_Uid tags[],int numTags)3209 TagInfo_Add(
3210     TreeCtrl *tree,		/* Widget info. */
3211     TagInfo *tagInfo,		/* Tag list. May be NULL. */
3212     Tk_Uid tags[],
3213     int numTags
3214     )
3215 {
3216     int i, j;
3217 
3218     if (tagInfo == NULL) {
3219 	if (numTags <= TREE_TAG_SPACE) {
3220 #ifdef ALLOC_HAX
3221 	    tagInfo = (TagInfo *) TreeAlloc_Alloc(tree->allocData, TagInfoUid,
3222 		    sizeof(TagInfo));
3223 #else
3224 	    tagInfo = (TagInfo *) ckalloc(sizeof(TagInfo));
3225 #endif
3226 	    tagInfo->tagSpace = TREE_TAG_SPACE;
3227 	} else {
3228 	    int tagSpace = (numTags / TREE_TAG_SPACE) * TREE_TAG_SPACE +
3229 		((numTags % TREE_TAG_SPACE) ? TREE_TAG_SPACE : 0);
3230 if (tagSpace % TREE_TAG_SPACE) panic("TagInfo_Add miscalc");
3231 #ifdef ALLOC_HAX
3232 	    tagInfo = (TagInfo *) TreeAlloc_Alloc(tree->allocData, TagInfoUid,
3233 		TAG_INFO_SIZE(tagSpace));
3234 #else
3235 	    tagInfo = (TagInfo *) ckalloc(TAG_INFO_SIZE(tagSpace));
3236 #endif
3237 	    tagInfo->tagSpace = tagSpace;
3238 	}
3239 	tagInfo->numTags = 0;
3240     }
3241     for (i = 0; i < numTags; i++) {
3242 	for (j = 0; j < tagInfo->numTags; j++) {
3243 	    if (tagInfo->tagPtr[j] == tags[i])
3244 		break;
3245 	}
3246 	if (j >= tagInfo->numTags) {
3247 	    /* Resize existing storage if needed. */
3248 	    if (tagInfo->tagSpace == tagInfo->numTags) {
3249 		tagInfo->tagSpace += TREE_TAG_SPACE;
3250 #ifdef ALLOC_HAX
3251 		tagInfo = (TagInfo *) TreeAlloc_Realloc(tree->allocData,
3252 		    TagInfoUid, (char *) tagInfo,
3253 		    TAG_INFO_SIZE(tagInfo->tagSpace - TREE_TAG_SPACE),
3254 		    TAG_INFO_SIZE(tagInfo->tagSpace));
3255 #else
3256 		tagInfo = (TagInfo *) ckrealloc((char *) tagInfo,
3257 		    TAG_INFO_SIZE(tagInfo->tagSpace));
3258 #endif
3259 	    }
3260 	    tagInfo->tagPtr[tagInfo->numTags++] = tags[i];
3261 	}
3262     }
3263     return tagInfo;
3264 }
3265 
3266 /*
3267  *----------------------------------------------------------------------
3268  *
3269  * TagInfo_Remove --
3270  *
3271  *	Removes tags from a list of tags.
3272  *
3273  * Results:
3274  *	Existing tags are removed.
3275  *
3276  * Side effects:
3277  *	Memory may be reallocated.
3278  *
3279  *----------------------------------------------------------------------
3280  */
3281 
3282 TagInfo *
TagInfo_Remove(TreeCtrl * tree,TagInfo * tagInfo,Tk_Uid tags[],int numTags)3283 TagInfo_Remove(
3284     TreeCtrl *tree,		/* Widget info. */
3285     TagInfo *tagInfo,		/* Tag list. May be NULL. */
3286     Tk_Uid tags[],
3287     int numTags
3288     )
3289 {
3290     int i, j;
3291 
3292     if (tagInfo == NULL)
3293 	return tagInfo;
3294     for (i = 0; i < numTags; i++) {
3295 	for (j = 0; j < tagInfo->numTags; j++) {
3296 	    if (tagInfo->tagPtr[j] == tags[i]) {
3297 		tagInfo->tagPtr[j] =
3298 		    tagInfo->tagPtr[tagInfo->numTags - 1];
3299 		tagInfo->numTags--;
3300 		break;
3301 	    }
3302 	}
3303     }
3304     if (tagInfo->numTags == 0) {
3305 	TagInfo_Free(tree, tagInfo);
3306 	tagInfo = NULL;
3307     }
3308     return tagInfo;
3309 }
3310 
3311 /*
3312  *----------------------------------------------------------------------
3313  *
3314  * TagInfo_Names --
3315  *
3316  *	Build a list of unique tag names.
3317  *
3318  * Results:
3319  *	Unique tags are added to a dynamically-allocated list.
3320  *
3321  * Side effects:
3322  *	Memory may be (re)allocated.
3323  *
3324  *----------------------------------------------------------------------
3325  */
3326 
3327 Tk_Uid *
TagInfo_Names(TreeCtrl * tree,TagInfo * tagInfo,Tk_Uid * tags,int * numTagsPtr,int * tagSpacePtr)3328 TagInfo_Names(
3329     TreeCtrl *tree,		/* Widget info. */
3330     TagInfo *tagInfo,		/* Tag list. May be NULL. */
3331     Tk_Uid *tags,		/* Current list, may be NULL. */
3332     int *numTagsPtr,		/* Number of tags in tags[]. */
3333     int *tagSpacePtr		/* Size of tags[]. */
3334     )
3335 {
3336     int numTags = *numTagsPtr, tagSpace = *tagSpacePtr;
3337     int i, j;
3338 
3339     if (tagInfo == NULL)
3340 	return tags;
3341     for (i = 0; i < tagInfo->numTags; i++) {
3342 	Tk_Uid tag = tagInfo->tagPtr[i];
3343 	for (j = 0; j < numTags; j++) {
3344 	    if (tag == tags[j])
3345 		break;
3346 	}
3347 	if (j < numTags)
3348 	    continue;
3349 	if ((tags == NULL) || (numTags == tagSpace)) {
3350 	    if (tags == NULL) {
3351 		tagSpace = 32;
3352 		tags = (Tk_Uid *) ckalloc(sizeof(Tk_Uid) * tagSpace);
3353 	    }
3354 	    else {
3355 		tagSpace *= 2;
3356 		tags = (Tk_Uid *) ckrealloc((char *) tags,
3357 		    sizeof(Tk_Uid) * tagSpace);
3358 	    }
3359 	}
3360 	tags[numTags++] = tag;
3361     }
3362     *numTagsPtr = numTags;
3363     *tagSpacePtr = tagSpace;
3364     return tags;
3365 }
3366 
3367 /*
3368  *----------------------------------------------------------------------
3369  *
3370  * TagInfo_Copy --
3371  *
3372  *	Copy a list of tags.
3373  *
3374  * Results:
3375  *	Allocates a new TagInfo if given one is not NULL.
3376  *
3377  * Side effects:
3378  *	Memory may be allocated.
3379  *
3380  *----------------------------------------------------------------------
3381  */
3382 
3383 TagInfo *
TagInfo_Copy(TreeCtrl * tree,TagInfo * tagInfo)3384 TagInfo_Copy(
3385     TreeCtrl *tree,		/* Widget info. */
3386     TagInfo *tagInfo		/* Tag list. May be NULL. */
3387     )
3388 {
3389     TagInfo *copy = NULL;
3390 
3391     if (tagInfo != NULL) {
3392 	int tagSpace = tagInfo->tagSpace;
3393 #ifdef ALLOC_HAX
3394 	copy = (TagInfo *) TreeAlloc_Alloc(tree->allocData, TagInfoUid,
3395 		TAG_INFO_SIZE(tagSpace));
3396 #else
3397 	copy = (TagInfo *) ckalloc(TAG_INFO_SIZE(tagSpace));
3398 #endif
3399 	memcpy((void *) copy->tagPtr, tagInfo->tagPtr, tagInfo->numTags * sizeof(Tk_Uid));
3400 	copy->numTags = tagInfo->numTags;
3401 	copy->tagSpace = tagSpace;
3402     }
3403     return copy;
3404 }
3405 
3406 /*
3407  *----------------------------------------------------------------------
3408  *
3409  * TagInfo_Free --
3410  *
3411  *	Free a list of tags.
3412  *
3413  * Results:
3414  *	TagInfo struct is freed if non-NULL.
3415  *
3416  * Side effects:
3417  *	Memory may be freed.
3418  *
3419  *----------------------------------------------------------------------
3420  */
3421 
3422 void
TagInfo_Free(TreeCtrl * tree,TagInfo * tagInfo)3423 TagInfo_Free(
3424     TreeCtrl *tree,		/* Widget info. */
3425     TagInfo *tagInfo		/* Tag list. May be NULL. */
3426     )
3427 {
3428     if (tagInfo != NULL)
3429 #ifdef ALLOC_HAX
3430 	TreeAlloc_Free(tree->allocData, TagInfoUid, (char *) tagInfo,
3431 	    TAG_INFO_SIZE(tagInfo->tagSpace));
3432 #else
3433 	ckfree((char *) tagInfo);
3434 #endif
3435 }
3436 
3437 int
TagInfo_FromObj(TreeCtrl * tree,Tcl_Obj * objPtr,TagInfo ** tagInfoPtr)3438 TagInfo_FromObj(
3439     TreeCtrl *tree,		/* Widget info. */
3440     Tcl_Obj *objPtr,
3441     TagInfo **tagInfoPtr
3442     )
3443 {
3444     int i, numTags;
3445     Tcl_Obj **listObjv;
3446     TagInfo *tagInfo = NULL;
3447 
3448     if (Tcl_ListObjGetElements(tree->interp, objPtr, &numTags, &listObjv) != TCL_OK) {
3449 	return TCL_ERROR;
3450     }
3451     if (numTags == 0) {
3452 	*tagInfoPtr = NULL;
3453 	return TCL_OK;
3454     }
3455     for (i = 0; i < numTags; i++) {
3456 	Tk_Uid tag = Tk_GetUid(Tcl_GetString(listObjv[i]));
3457 	tagInfo = TagInfo_Add(tree, tagInfo, &tag, 1);
3458     }
3459     *tagInfoPtr = tagInfo;
3460     return TCL_OK;
3461 }
3462 
3463 static Tcl_Obj *
TagInfo_ToObj(TreeCtrl * tree,TagInfo * tagInfo)3464 TagInfo_ToObj(
3465     TreeCtrl *tree,		/* Widget info. */
3466     TagInfo *tagInfo
3467     )
3468 {
3469     Tcl_Obj *listObj;
3470     int i;
3471 
3472     if (tagInfo == NULL)
3473 	return NULL;
3474 
3475     listObj = Tcl_NewListObj(0, NULL);
3476     for (i = 0; i < tagInfo->numTags; i++) {
3477 	Tcl_ListObjAppendElement(NULL, listObj,
3478 		Tcl_NewStringObj((char *) tagInfo->tagPtr[i], -1));
3479     }
3480     return listObj;
3481 }
3482 
3483 /*
3484  *----------------------------------------------------------------------
3485  *
3486  * TagInfoCO_Set --
3487  * TagInfoCO_Get --
3488  * TagInfoCO_Restore --
3489  * TagInfoCO_Free --
3490  *
3491  *	These procedures implement a TK_OPTION_CUSTOM where the custom
3492  *	option is a TagInfo record.
3493  *
3494  * Results:
3495  *	None.
3496  *
3497  * Side effects:
3498  *	None.
3499  *
3500  *----------------------------------------------------------------------
3501  */
3502 
3503 static int
TagInfoCO_Set(ClientData clientData,Tcl_Interp * interp,Tk_Window tkwin,Tcl_Obj ** value,char * recordPtr,int internalOffset,char * saveInternalPtr,int flags)3504 TagInfoCO_Set(
3505     ClientData clientData,
3506     Tcl_Interp *interp,
3507     Tk_Window tkwin,
3508     Tcl_Obj **value,
3509     char *recordPtr,
3510     int internalOffset,
3511     char *saveInternalPtr,
3512     int flags
3513     )
3514 {
3515     TreeCtrl *tree = (TreeCtrl *) ((TkWindow *) tkwin)->instanceData;
3516     int objEmpty;
3517     TagInfo *new, **internalPtr;
3518 
3519     if (internalOffset >= 0)
3520 	internalPtr = (TagInfo **) (recordPtr + internalOffset);
3521     else
3522 	internalPtr = NULL;
3523 
3524     objEmpty = ObjectIsEmpty((*value));
3525 
3526     if ((flags & TK_OPTION_NULL_OK) && objEmpty)
3527 	(*value) = NULL;
3528     else {
3529 	if (TagInfo_FromObj(tree, (*value), &new) != TCL_OK)
3530 	    return TCL_ERROR;
3531     }
3532     if (internalPtr != NULL) {
3533 	if ((*value) == NULL)
3534 	    new = NULL;
3535 	*((TagInfo **) saveInternalPtr) = *internalPtr;
3536 	*internalPtr = new;
3537     }
3538 
3539     return TCL_OK;
3540 }
3541 
3542 static Tcl_Obj *
TagInfoCO_Get(ClientData clientData,Tk_Window tkwin,char * recordPtr,int internalOffset)3543 TagInfoCO_Get(
3544     ClientData clientData,
3545     Tk_Window tkwin,
3546     char *recordPtr,
3547     int internalOffset
3548     )
3549 {
3550     TreeCtrl *tree = (TreeCtrl *) ((TkWindow *) tkwin)->instanceData;
3551     TagInfo *value = *(TagInfo **) (recordPtr + internalOffset);
3552     return TagInfo_ToObj(tree, value);
3553 }
3554 
3555 static void
TagInfoCO_Restore(ClientData clientData,Tk_Window tkwin,char * internalPtr,char * saveInternalPtr)3556 TagInfoCO_Restore(
3557     ClientData clientData,
3558     Tk_Window tkwin,
3559     char *internalPtr,
3560     char *saveInternalPtr
3561     )
3562 {
3563     *(TagInfo **) internalPtr = *(TagInfo **) saveInternalPtr;
3564 }
3565 
3566 static void
TagInfoCO_Free(ClientData clientData,Tk_Window tkwin,char * internalPtr)3567 TagInfoCO_Free(
3568     ClientData clientData,	/* unused. */
3569     Tk_Window tkwin,		/* A window; unused */
3570     char *internalPtr		/* Pointer to the place, where the internal
3571 				 * form resides. */
3572     )
3573 {
3574     TreeCtrl *tree = (TreeCtrl *) ((TkWindow *) tkwin)->instanceData;
3575 
3576     TagInfo_Free(tree, *(TagInfo **)internalPtr);
3577 }
3578 
3579 Tk_ObjCustomOption TreeCtrlCO_tagInfo =
3580 {
3581     "tag list",
3582     TagInfoCO_Set,
3583     TagInfoCO_Get,
3584     TagInfoCO_Restore,
3585     TagInfoCO_Free,
3586     (ClientData) NULL
3587 };
3588 
3589 /*
3590  *----------------------------------------------------------------------
3591  *
3592  * TagExpr_Init --
3593  *
3594  *	This procedure initializes a TagExpr struct by parsing a Tcl_Obj
3595  *	string representation of a tag expression.
3596  *
3597  * Results:
3598  *	A standard Tcl result.
3599  *
3600  * Side effects:
3601  *	Memory may be allocated.
3602  *
3603  *----------------------------------------------------------------------
3604  */
3605 
3606 int
TagExpr_Init(TreeCtrl * tree,Tcl_Obj * exprObj,TagExpr * expr)3607 TagExpr_Init(
3608     TreeCtrl *tree,		/* Widget info. */
3609     Tcl_Obj *exprObj,		/* Tag expression string. */
3610     TagExpr *expr		/* Struct to initialize. */
3611     )
3612 {
3613     int i;
3614     char *tag;
3615 
3616     expr->tree = tree;
3617     expr->index = 0;
3618     expr->length = 0;
3619     expr->uid = NULL;
3620     expr->allocated = sizeof(expr->staticUids) / sizeof(Tk_Uid);
3621     expr->uids = expr->staticUids;
3622     expr->simple = TRUE;
3623     expr->rewritebuffer = expr->staticRWB;
3624 
3625     tag = Tcl_GetStringFromObj(exprObj, &expr->stringLength);
3626 
3627     /* short circuit impossible searches for null tags */
3628     if (expr->stringLength == 0) {
3629 	return TCL_OK;
3630     }
3631 
3632     /*
3633      * Pre-scan tag for at least one unquoted "&&" "||" "^" "!"
3634      *   if not found then use string as simple tag
3635      */
3636     for (i = 0; i < expr->stringLength ; i++) {
3637 	if (tag[i] == '"') {
3638 	    i++;
3639 	    for ( ; i < expr->stringLength; i++) {
3640 		if (tag[i] == '\\') {
3641 		    i++;
3642 		    continue;
3643 		}
3644 		if (tag[i] == '"') {
3645 		    break;
3646 		}
3647 	    }
3648 	} else {
3649 	    if ((tag[i] == '&' && tag[i+1] == '&')
3650 	     || (tag[i] == '|' && tag[i+1] == '|')
3651 	     || (tag[i] == '^')
3652 	     || (tag[i] == '!')) {
3653 		expr->simple = FALSE;
3654 		break;
3655 	    }
3656 	}
3657     }
3658 
3659     if (expr->simple) {
3660 	expr->uid = Tk_GetUid(tag);
3661 	return TCL_OK;
3662     }
3663 
3664     expr->string = tag;
3665     expr->stringIndex = 0;
3666 
3667     /* Allocate buffer for rewritten tags (after de-escaping) */
3668     if (expr->stringLength >= sizeof(expr->staticRWB))
3669 	expr->rewritebuffer = ckalloc(expr->stringLength + 1);
3670 
3671     if (TagExpr_Scan(expr) != TCL_OK) {
3672 	TagExpr_Free(expr);
3673 	return TCL_ERROR;
3674     }
3675     expr->length = expr->index;
3676 
3677     return TCL_OK;
3678 }
3679 
3680 /*
3681  * Uids for operands in compiled tag expressions.
3682  * Initialization is done by GetStaticUids().
3683  */
3684 typedef struct {
3685     Tk_Uid andUid;
3686     Tk_Uid orUid;
3687     Tk_Uid xorUid;
3688     Tk_Uid parenUid;
3689     Tk_Uid negparenUid;
3690     Tk_Uid endparenUid;
3691     Tk_Uid tagvalUid;
3692     Tk_Uid negtagvalUid;
3693 } SearchUids;
3694 
3695 static Tcl_ThreadDataKey searchUidTDK;
3696 
3697 /*
3698  *----------------------------------------------------------------------
3699  *
3700  * GetStaticUids --
3701  *
3702  *	This procedure is invoked to return a structure filled with
3703  *	the Uids used when doing tag searching. If it was never before
3704  *	called in the current thread, it initializes the structure for
3705  *	that thread (uids are only ever local to one thread [Bug
3706  *	1114977]).
3707  *
3708  * Results:
3709  *	None.
3710  *
3711  * Side effects:
3712  *	None.
3713  *
3714  *----------------------------------------------------------------------
3715  */
3716 
3717 static SearchUids *
GetStaticUids()3718 GetStaticUids()
3719 {
3720     SearchUids *searchUids = (SearchUids *)
3721 	    Tcl_GetThreadData(&searchUidTDK, sizeof(SearchUids));
3722 
3723     if (searchUids->andUid == NULL) {
3724 	searchUids->andUid       = Tk_GetUid("&&");
3725 	searchUids->orUid        = Tk_GetUid("||");
3726 	searchUids->xorUid       = Tk_GetUid("^");
3727 	searchUids->parenUid     = Tk_GetUid("(");
3728 	searchUids->endparenUid  = Tk_GetUid(")");
3729 	searchUids->negparenUid  = Tk_GetUid("!(");
3730 	searchUids->tagvalUid    = Tk_GetUid("!!");
3731 	searchUids->negtagvalUid = Tk_GetUid("!");
3732     }
3733     return searchUids;
3734 }
3735 
3736 /*
3737  *----------------------------------------------------------------------
3738  *
3739  * TagExpr_Scan --
3740  *
3741  *	This procedure recursively parses a string representation of a
3742  *	tag expression into an array of Tk_Uids.
3743  *
3744  * Results:
3745  *	The return value indicates if the tag expression
3746  *	was successfully scanned (syntax).
3747  *
3748  * Side effects:
3749  *	Memory may be allocated.
3750  *
3751  *----------------------------------------------------------------------
3752  */
3753 
3754 int
TagExpr_Scan(TagExpr * expr)3755 TagExpr_Scan(
3756     TagExpr *expr		/* Info about a tag expression. */
3757     )
3758 {
3759     Tcl_Interp *interp = expr->tree->interp;
3760     int looking_for_tag;        /* When true, scanner expects
3761 				 * next char(s) to be a tag,
3762 				 * else operand expected */
3763     int found_tag;              /* One or more tags found */
3764     int found_endquote;         /* For quoted tag string parsing */
3765     int negate_result;          /* Pending negation of next tag value */
3766     char *tag;                  /* tag from tag expression string */
3767     SearchUids *searchUids;	/* Collection of uids for basic search
3768 				 * expression terms. */
3769     char c;
3770 
3771     searchUids = GetStaticUids();
3772     negate_result = 0;
3773     found_tag = 0;
3774     looking_for_tag = 1;
3775     while (expr->stringIndex < expr->stringLength) {
3776 	c = expr->string[expr->stringIndex++];
3777 
3778 	if (expr->allocated == expr->index) {
3779 	    expr->allocated += 15;
3780 	    if (expr->uids != expr->staticUids) {
3781 		expr->uids =
3782 		    (Tk_Uid *) ckrealloc((char *)(expr->uids),
3783 		    (expr->allocated)*sizeof(Tk_Uid));
3784 	    } else {
3785 		expr->uids =
3786 		    (Tk_Uid *) ckalloc((expr->allocated)*sizeof(Tk_Uid));
3787 		memcpy((void *) expr->uids, expr->staticUids, sizeof(expr->staticUids));
3788 	    }
3789 	}
3790 
3791 	if (looking_for_tag) {
3792 
3793 	    switch (c) {
3794 		case ' '  :	/* ignore unquoted whitespace */
3795 		case '\t' :
3796 		case '\n' :
3797 		case '\r' :
3798 		    break;
3799 
3800 		case '!'  :	/* negate next tag or subexpr */
3801 		    if (looking_for_tag > 1) {
3802 			Tcl_AppendResult(interp,
3803 				"Too many '!' in tag search expression",
3804 				(char *) NULL);
3805 			return TCL_ERROR;
3806 		    }
3807 		    looking_for_tag++;
3808 		    negate_result = 1;
3809 		    break;
3810 
3811 		case '('  :	/* scan (negated) subexpr recursively */
3812 		    if (negate_result) {
3813 			expr->uids[expr->index++] = searchUids->negparenUid;
3814 			negate_result = 0;
3815 		    } else {
3816 			expr->uids[expr->index++] = searchUids->parenUid;
3817 		    }
3818 		    if (TagExpr_Scan(expr) != TCL_OK) {
3819 			/* Result string should be already set
3820 			 * by nested call to tag_expr_scan() */
3821 			return TCL_ERROR;
3822 		    }
3823 		    looking_for_tag = 0;
3824 		    found_tag = 1;
3825 		    break;
3826 
3827 		case '"'  :	/* quoted tag string */
3828 		    if (negate_result) {
3829 			expr->uids[expr->index++] = searchUids->negtagvalUid;
3830 			negate_result = 0;
3831 		    } else {
3832 			expr->uids[expr->index++] = searchUids->tagvalUid;
3833 		    }
3834 		    tag = expr->rewritebuffer;
3835 		    found_endquote = 0;
3836 		    while (expr->stringIndex < expr->stringLength) {
3837 			c = expr->string[expr->stringIndex++];
3838 			if (c == '\\') {
3839 			    c = expr->string[expr->stringIndex++];
3840 			}
3841 			if (c == '"') {
3842 			    found_endquote = 1;
3843 			    break;
3844 			}
3845 			*tag++ = c;
3846 		    }
3847 		    if (! found_endquote) {
3848 			Tcl_AppendResult(interp,
3849 				"Missing endquote in tag search expression",
3850 				(char *) NULL);
3851 			return TCL_ERROR;
3852 		    }
3853 		    if (! (tag - expr->rewritebuffer)) {
3854 			Tcl_AppendResult(interp,
3855 			    "Null quoted tag string in tag search expression",
3856 			    (char *) NULL);
3857 			return TCL_ERROR;
3858 		    }
3859 		    *tag++ = '\0';
3860 		    expr->uids[expr->index++] =
3861 			    Tk_GetUid(expr->rewritebuffer);
3862 		    looking_for_tag = 0;
3863 		    found_tag = 1;
3864 		    break;
3865 
3866 		case '&'  :	/* illegal chars when looking for tag */
3867 		case '|'  :
3868 		case '^'  :
3869 		case ')'  :
3870 		    Tcl_AppendResult(interp,
3871 			    "Unexpected operator in tag search expression",
3872 			    (char *) NULL);
3873 		    return TCL_ERROR;
3874 
3875 		default :	/* unquoted tag string */
3876 		    if (negate_result) {
3877 			expr->uids[expr->index++] = searchUids->negtagvalUid;
3878 			negate_result = 0;
3879 		    } else {
3880 			expr->uids[expr->index++] = searchUids->tagvalUid;
3881 		    }
3882 		    tag = expr->rewritebuffer;
3883 		    *tag++ = c;
3884 		    /* copy rest of tag, including any embedded whitespace */
3885 		    while (expr->stringIndex < expr->stringLength) {
3886 			c = expr->string[expr->stringIndex];
3887 			if (c == '!' || c == '&' || c == '|' || c == '^'
3888 				|| c == '(' || c == ')' || c == '"') {
3889 			    break;
3890 			}
3891 			*tag++ = c;
3892 			expr->stringIndex++;
3893 		    }
3894 		    /* remove trailing whitespace */
3895 		    while (1) {
3896 			c = *--tag;
3897 			/* there must have been one non-whitespace char,
3898 			 *  so this will terminate */
3899 			if (c != ' ' && c != '\t' && c != '\n' && c != '\r') {
3900 			    break;
3901 			}
3902 		    }
3903 		    *++tag = '\0';
3904 		    expr->uids[expr->index++] =
3905 			    Tk_GetUid(expr->rewritebuffer);
3906 		    looking_for_tag = 0;
3907 		    found_tag = 1;
3908 	    }
3909 
3910 	} else {    /* ! looking_for_tag */
3911 
3912 	    switch (c) {
3913 		case ' '  :	/* ignore whitespace */
3914 		case '\t' :
3915 		case '\n' :
3916 		case '\r' :
3917 		    break;
3918 
3919 		case '&'  :	/* AND operator */
3920 		    c = expr->string[expr->stringIndex++];
3921 		    if (c != '&') {
3922 			Tcl_AppendResult(interp,
3923 				"Singleton '&' in tag search expression",
3924 				(char *) NULL);
3925 			return TCL_ERROR;
3926 		    }
3927 		    expr->uids[expr->index++] = searchUids->andUid;
3928 		    looking_for_tag = 1;
3929 		    break;
3930 
3931 		case '|'  :	/* OR operator */
3932 		    c = expr->string[expr->stringIndex++];
3933 		    if (c != '|') {
3934 			Tcl_AppendResult(interp,
3935 				"Singleton '|' in tag search expression",
3936 				(char *) NULL);
3937 			return TCL_ERROR;
3938 		    }
3939 		    expr->uids[expr->index++] = searchUids->orUid;
3940 		    looking_for_tag = 1;
3941 		    break;
3942 
3943 		case '^'  :	/* XOR operator */
3944 		    expr->uids[expr->index++] = searchUids->xorUid;
3945 		    looking_for_tag = 1;
3946 		    break;
3947 
3948 		case ')'  :	/* end subexpression */
3949 		    expr->uids[expr->index++] = searchUids->endparenUid;
3950 		    goto breakwhile;
3951 
3952 		default   :	/* syntax error */
3953 		    Tcl_AppendResult(interp,
3954 			    "Invalid boolean operator in tag search expression",
3955 			    (char *) NULL);
3956 		    return TCL_ERROR;
3957 	    }
3958 	}
3959     }
3960 breakwhile:
3961     if (found_tag && ! looking_for_tag) {
3962 	return TCL_OK;
3963     }
3964     Tcl_AppendResult(interp, "Missing tag in tag search expression",
3965 	    (char *) NULL);
3966     return TCL_ERROR;
3967 }
3968 
3969 /*
3970  *----------------------------------------------------------------------
3971  *
3972  * TagExpr_Eval --
3973  *
3974  *	This procedure recursively evaluates a compiled tag expression.
3975  *
3976  * Results:
3977  *	The return value indicates if the tag expression
3978  *	successfully matched the tags of the given item.
3979  *
3980  * Side effects:
3981  *	None.
3982  *
3983  *----------------------------------------------------------------------
3984  */
3985 
3986 static int
_TagExpr_Eval(TagExpr * expr,TagInfo * tagInfo)3987 _TagExpr_Eval(
3988     TagExpr *expr,		/* Info about a tag expression. */
3989     TagInfo *tagInfo		/* Tags to test. */
3990     )
3991 {
3992     int looking_for_tag;        /* When true, scanner expects
3993 				 * next char(s) to be a tag,
3994 				 * else operand expected */
3995     int negate_result;          /* Pending negation of next tag value */
3996     Tk_Uid uid;
3997     Tk_Uid *tagPtr;
3998     int count;
3999     int result;                 /* Value of expr so far */
4000     int parendepth;
4001     SearchUids *searchUids;	/* Collection of uids for basic search
4002 				 * expression terms. */
4003     TagInfo dummy;
4004 
4005     if (expr->stringLength == 0) /* empty expression (an error?) */
4006 	return 0;
4007 
4008     /* No tags given. */
4009     if (tagInfo == NULL) {
4010 	dummy.numTags = 0;
4011 	tagInfo = &dummy;
4012     }
4013 
4014     /* A single tag. */
4015     if (expr->simple) {
4016 	for (tagPtr = tagInfo->tagPtr, count = tagInfo->numTags;
4017 	    count > 0; tagPtr++, count--) {
4018 	    if (*tagPtr == expr->uid) {
4019 		return 1;
4020 	    }
4021 	}
4022 	return 0;
4023     }
4024 
4025     searchUids = GetStaticUids();
4026     result = 0;  /* just to keep the compiler quiet */
4027 
4028     negate_result = 0;
4029     looking_for_tag = 1;
4030     while (expr->index < expr->length) {
4031 	uid = expr->uids[expr->index++];
4032 	if (looking_for_tag) {
4033 	    if (uid == searchUids->tagvalUid) {
4034 /*
4035  *              assert(expr->index < expr->length);
4036  */
4037 		uid = expr->uids[expr->index++];
4038 		result = 0;
4039 		/*
4040 		 * set result 1 if tag is found in item's tags
4041 		 */
4042 		for (tagPtr = tagInfo->tagPtr, count = tagInfo->numTags;
4043 		    count > 0; tagPtr++, count--) {
4044 		    if (*tagPtr == uid) {
4045 			result = 1;
4046 			break;
4047 		    }
4048 		}
4049 
4050 	    } else if (uid == searchUids->negtagvalUid) {
4051 		negate_result = ! negate_result;
4052 /*
4053  *              assert(expr->index < expr->length);
4054  */
4055 		uid = expr->uids[expr->index++];
4056 		result = 0;
4057 		/*
4058 		 * set result 1 if tag is found in item's tags
4059 		 */
4060 		for (tagPtr = tagInfo->tagPtr, count = tagInfo->numTags;
4061 		    count > 0; tagPtr++, count--) {
4062 		    if (*tagPtr == uid) {
4063 			result = 1;
4064 			break;
4065 		    }
4066 		}
4067 
4068 	    } else if (uid == searchUids->parenUid) {
4069 		/*
4070 		 * evaluate subexpressions with recursion
4071 		 */
4072 		result = _TagExpr_Eval(expr, tagInfo);
4073 
4074 	    } else if (uid == searchUids->negparenUid) {
4075 		negate_result = ! negate_result;
4076 		/*
4077 		 * evaluate subexpressions with recursion
4078 		 */
4079 		result = _TagExpr_Eval(expr, tagInfo);
4080 /*
4081  *          } else {
4082  *              assert(0);
4083  */
4084 	    }
4085 	    if (negate_result) {
4086 		result = ! result;
4087 		negate_result = 0;
4088 	    }
4089 	    looking_for_tag = 0;
4090 	} else {    /* ! looking_for_tag */
4091 	    if (((uid == searchUids->andUid) && (!result)) ||
4092 		    ((uid == searchUids->orUid) && result)) {
4093 		/*
4094 		 * short circuit expression evaluation
4095 		 *
4096 		 * if result before && is 0, or result before || is 1,
4097 		 *   then the expression is decided and no further
4098 		 *   evaluation is needed.
4099 		 */
4100 
4101 		    parendepth = 0;
4102 		while (expr->index < expr->length) {
4103 		    uid = expr->uids[expr->index++];
4104 		    if (uid == searchUids->tagvalUid ||
4105 			    uid == searchUids->negtagvalUid) {
4106 			expr->index++;
4107 			continue;
4108 		    }
4109 		    if (uid == searchUids->parenUid ||
4110 			    uid == searchUids->negparenUid) {
4111 			parendepth++;
4112 			continue;
4113 		    }
4114 		    if (uid == searchUids->endparenUid) {
4115 			parendepth--;
4116 			if (parendepth < 0) {
4117 			    break;
4118 			}
4119 		    }
4120 		}
4121 		return result;
4122 
4123 	    } else if (uid == searchUids->xorUid) {
4124 		/*
4125 		 * if the previous result was 1
4126 		 *   then negate the next result
4127 		 */
4128 		negate_result = result;
4129 
4130 	    } else if (uid == searchUids->endparenUid) {
4131 		return result;
4132 /*
4133  *          } else {
4134  *               assert(0);
4135  */
4136 	    }
4137 	    looking_for_tag = 1;
4138 	}
4139     }
4140 /*
4141  *  assert(! looking_for_tag);
4142  */
4143     return result;
4144 }
4145 
4146 int
TagExpr_Eval(TagExpr * expr,TagInfo * tagInfo)4147 TagExpr_Eval(
4148     TagExpr *expr,		/* Info about a tag expression. */
4149     TagInfo *tagInfo		/* Tags to test. */
4150     )
4151 {
4152     expr->index = 0;
4153     return _TagExpr_Eval(expr, tagInfo);
4154 }
4155 
4156 /*
4157  *----------------------------------------------------------------------
4158  *
4159  * TagExpr_Free --
4160  *
4161  *	This procedure frees the given struct.
4162  *
4163  * Results:
4164  *	None.
4165  *
4166  * Side effects:
4167  *	None.
4168  *
4169  *----------------------------------------------------------------------
4170  */
4171 
4172 void
TagExpr_Free(TagExpr * expr)4173 TagExpr_Free(
4174     TagExpr *expr
4175     )
4176 {
4177     if (expr->rewritebuffer != expr->staticRWB)
4178 	ckfree(expr->rewritebuffer);
4179     if (expr->uids != expr->staticUids)
4180 	ckfree((char *) expr->uids);
4181 }
4182 
4183 /*
4184  *----------------------------------------------------------------------
4185  *
4186  * OptionHax_Remember --
4187  * OptionHax_Forget --
4188  *
4189  *	These procedures are used to work around a limitation in
4190  *	the Tk_SavedOption structure: the internal form of a configuration
4191  *	option cannot be larger than a double.
4192  *
4193  * Results:
4194  *	None.
4195  *
4196  * Side effects:
4197  *	None.
4198  *
4199  *----------------------------------------------------------------------
4200  */
4201 
4202 static void
OptionHax_Remember(TreeCtrl * tree,char * ptr)4203 OptionHax_Remember(
4204     TreeCtrl *tree,
4205     char *ptr
4206     )
4207 {
4208 #ifdef TREECTRL_DEBUG
4209     int i;
4210     for (i = 0; i < tree->optionHaxCnt; i++) {
4211 	if (ptr == tree->optionHax[i]) {
4212 	    panic("OptionHax_Remember: ptr is not new");
4213 	}
4214     }
4215     if (tree->optionHaxCnt == sizeof(tree->optionHax) / sizeof(tree->optionHax[0]))
4216 	panic("OptionHax_Remember: too many options");
4217 #endif
4218     tree->optionHax[tree->optionHaxCnt++] = ptr;
4219 /*dbwin("OptionHax_Remember %p\n", ptr);*/
4220 }
4221 
4222 static int
OptionHax_Forget(TreeCtrl * tree,char * ptr)4223 OptionHax_Forget(
4224     TreeCtrl *tree,
4225     char *ptr
4226     )
4227 {
4228     int i;
4229 
4230     for (i = 0; i < tree->optionHaxCnt; i++) {
4231 	if (ptr == tree->optionHax[i]) {
4232 	    tree->optionHax[i] = tree->optionHax[--tree->optionHaxCnt];
4233 /*dbwin("OptionHax_Forget %p\n", ptr);*/
4234 	    return 1;
4235 	}
4236     }
4237     return 0;
4238 }
4239 
4240 /*
4241  *----------------------------------------------------------------------
4242  *
4243  * Tree_FindOptionSpec --
4244  *
4245  *	Return a pointer to a name Tk_OptionSpec in a table.
4246  *
4247  * Results:
4248  *	Returns a pointer or panics.
4249  *
4250  * Side effects:
4251  *	None.
4252  *
4253  *----------------------------------------------------------------------
4254  */
4255 
4256 Tk_OptionSpec *
Tree_FindOptionSpec(Tk_OptionSpec * optionTable,CONST char * optionName)4257 Tree_FindOptionSpec(
4258     Tk_OptionSpec *optionTable,
4259     CONST char *optionName
4260     )
4261 {
4262     while (optionTable->type != TK_OPTION_END) {
4263 	if (strcmp(optionTable->optionName, optionName) == 0)
4264 	    return optionTable;
4265 	optionTable++;
4266     }
4267     panic("Tree_FindOptionSpec: can't find %s", optionName);
4268     return NULL;
4269 }
4270 
4271 /*
4272  *----------------------------------------------------------------------
4273  *
4274  * PerStateCO_Set --
4275  * PerStateCO_Get --
4276  * PerStateCO_Restore --
4277  * PerStateCO_Free --
4278  *
4279  *	These procedures implement a TK_OPTION_CUSTOM where the custom
4280  *	option is a PerStateInfo record.
4281  *
4282  * Results:
4283  *	None.
4284  *
4285  * Side effects:
4286  *	None.
4287  *
4288  *----------------------------------------------------------------------
4289  */
4290 
4291 typedef struct PerStateCOClientData
4292 {
4293     PerStateType *typePtr;
4294     StateFromObjProc proc;
4295 } PerStateCOClientData;
4296 
4297 static int
PerStateCO_Set(ClientData clientData,Tcl_Interp * interp,Tk_Window tkwin,Tcl_Obj ** value,char * recordPtr,int internalOffset,char * saveInternalPtr,int flags)4298 PerStateCO_Set(
4299     ClientData clientData,
4300     Tcl_Interp *interp,
4301     Tk_Window tkwin,
4302     Tcl_Obj **value,
4303     char *recordPtr,
4304     int internalOffset,
4305     char *saveInternalPtr,
4306     int flags
4307     )
4308 {
4309     PerStateCOClientData *cd = (PerStateCOClientData *) clientData;
4310     TreeCtrl *tree = (TreeCtrl *) ((TkWindow *) tkwin)->instanceData;
4311     int objEmpty;
4312     PerStateInfo new, *internalPtr, *hax;
4313 
4314     if (internalOffset >= 0)
4315 	internalPtr = (PerStateInfo *) (recordPtr + internalOffset);
4316     else
4317 	internalPtr = NULL;
4318 
4319     objEmpty = ObjectIsEmpty((*value));
4320 
4321     if ((flags & TK_OPTION_NULL_OK) && objEmpty)
4322 	(*value) = NULL;
4323     else {
4324 	new.obj = (*value);
4325 	new.data = NULL;
4326 	new.count = 0;
4327 /*	Tcl_IncrRefCount((*value));*/
4328 	if (tree->configStateDomain == -1)
4329 	    panic("PerStateCO_Set configStateDomain == -1");
4330 	if (PerStateInfo_FromObj(tree, tree->configStateDomain, cd->proc, cd->typePtr, &new) != TCL_OK) {
4331 /*	    Tcl_DecrRefCount((*value));*/
4332 	    return TCL_ERROR;
4333 	}
4334     }
4335     if (internalPtr != NULL) {
4336 	if ((*value) == NULL) {
4337 	    new.obj = NULL;
4338 	    new.data = NULL;
4339 	    new.count = 0;
4340 	}
4341 	OptionHax_Remember(tree, saveInternalPtr);
4342 	if (internalPtr->obj != NULL) {
4343 	    hax = (PerStateInfo *) ckalloc(sizeof(PerStateInfo));
4344 	    *hax = *internalPtr;
4345 	    *((PerStateInfo **) saveInternalPtr) = hax;
4346 	} else {
4347 	    *((PerStateInfo **) saveInternalPtr) = NULL;
4348 	}
4349 	*internalPtr = new;
4350 /*dbwin("PerStateCO_Set %p %s\n", internalPtr, cd->typePtr->name);*/
4351     }
4352 
4353     return TCL_OK;
4354 }
4355 
4356 static Tcl_Obj *
PerStateCO_Get(ClientData clientData,Tk_Window tkwin,char * recordPtr,int internalOffset)4357 PerStateCO_Get(
4358     ClientData clientData,
4359     Tk_Window tkwin,
4360     char *recordPtr,
4361     int internalOffset
4362     )
4363 {
4364     PerStateInfo *value = (PerStateInfo *) (recordPtr + internalOffset);
4365     return value->obj; /* May be NULL. */
4366 }
4367 
4368 static void
PerStateCO_Restore(ClientData clientData,Tk_Window tkwin,char * internalPtr,char * saveInternalPtr)4369 PerStateCO_Restore(
4370     ClientData clientData,
4371     Tk_Window tkwin,
4372     char *internalPtr,
4373     char *saveInternalPtr
4374     )
4375 {
4376     TreeCtrl *tree = (TreeCtrl *) ((TkWindow *) tkwin)->instanceData;
4377     PerStateInfo *psi = (PerStateInfo *) internalPtr;
4378     PerStateInfo *hax = *(PerStateInfo **) saveInternalPtr;
4379 /*dbwin("PerStateCO_Restore\n");*/
4380     if (hax != NULL) {
4381 #ifdef TREECTRL_DEBUG
4382 	psi->type = hax->type;
4383 #endif
4384 	psi->data = hax->data;
4385 	psi->count = hax->count;
4386 	ckfree((char *) hax);
4387     } else {
4388 #ifdef TREECTRL_DEBUG
4389 	psi->type = NULL;
4390 #endif
4391 /*	psi->obj = NULL;*/
4392 	psi->data = NULL;
4393 	psi->count = 0;
4394     }
4395     OptionHax_Forget(tree, saveInternalPtr);
4396 }
4397 
4398 static void
PerStateCO_Free(ClientData clientData,Tk_Window tkwin,char * internalPtr)4399 PerStateCO_Free(
4400     ClientData clientData,	/* unused. */
4401     Tk_Window tkwin,		/* A window; unused */
4402     char *internalPtr		/* Pointer to the place, where the internal
4403 				 * form resides. */
4404     )
4405 {
4406     PerStateCOClientData *cd = (PerStateCOClientData *) clientData;
4407     TreeCtrl *tree = (TreeCtrl *) ((TkWindow *) tkwin)->instanceData;
4408     PerStateInfo *hax;
4409     Tcl_Obj *objPtr = NULL;
4410 
4411     if (OptionHax_Forget(tree, internalPtr)) {
4412 	hax = *(PerStateInfo **) internalPtr;
4413 	if (hax != NULL) {
4414 	    objPtr = hax->obj;
4415 	    PerStateInfo_Free(tree, cd->typePtr, hax);
4416 	    ckfree((char *) hax);
4417 	}
4418     } else {
4419 /*dbwin("PerStateCO_Free %p %s\n", internalPtr, cd->typePtr->name);*/
4420 	objPtr = ((PerStateInfo *) internalPtr)->obj;
4421 	PerStateInfo_Free(tree, cd->typePtr, (PerStateInfo *) internalPtr);
4422     }
4423 /*    if (objPtr != NULL)
4424 	Tcl_DecrRefCount(objPtr);*/
4425 }
4426 
4427 /*
4428  *----------------------------------------------------------------------
4429  *
4430  * PerStateCO_Alloc --
4431  *
4432  *	Allocates a Tk_ObjCustomOption record and clientData.
4433  *
4434  * Results:
4435  *	None.
4436  *
4437  * Side effects:
4438  *	None.
4439  *
4440  *----------------------------------------------------------------------
4441  */
4442 
4443 Tk_ObjCustomOption *
PerStateCO_Alloc(CONST char * optionName,PerStateType * typePtr,StateFromObjProc proc)4444 PerStateCO_Alloc(
4445     CONST char *optionName,
4446     PerStateType *typePtr,
4447     StateFromObjProc proc
4448     )
4449 {
4450     PerStateCOClientData *cd;
4451     Tk_ObjCustomOption *co;
4452 
4453     /* ClientData for the Tk custom option record */
4454     cd = (PerStateCOClientData *) ckalloc(sizeof(PerStateCOClientData));
4455     cd->typePtr = typePtr;
4456     cd->proc = proc;
4457 
4458     /* The Tk custom option record */
4459     co = (Tk_ObjCustomOption *) ckalloc(sizeof(Tk_ObjCustomOption));
4460     co->name = (char *) optionName + 1;
4461     co->setProc = PerStateCO_Set;
4462     co->getProc = PerStateCO_Get;
4463     co->restoreProc = PerStateCO_Restore;
4464     co->freeProc = PerStateCO_Free;
4465     co->clientData = (ClientData) cd;
4466 
4467     return co;
4468 }
4469 
4470 /*
4471  *----------------------------------------------------------------------
4472  *
4473  * PerStateCO_Init --
4474  *
4475  *	Initializes a Tk_OptionSpec.clientData for a custom option.
4476  *
4477  * Results:
4478  *	None.
4479  *
4480  * Side effects:
4481  *	None.
4482  *
4483  *----------------------------------------------------------------------
4484  */
4485 
4486 int
PerStateCO_Init(Tk_OptionSpec * optionTable,CONST char * optionName,PerStateType * typePtr,StateFromObjProc proc)4487 PerStateCO_Init(
4488     Tk_OptionSpec *optionTable,
4489     CONST char *optionName,
4490     PerStateType *typePtr,
4491     StateFromObjProc proc
4492     )
4493 {
4494     Tk_OptionSpec *specPtr;
4495 
4496     specPtr = Tree_FindOptionSpec(optionTable, optionName);
4497     if (specPtr->type != TK_OPTION_CUSTOM)
4498 	panic("PerStateCO_Init: %s is not TK_OPTION_CUSTOM", optionName);
4499     if (specPtr->clientData != NULL)
4500 	return TCL_OK;
4501 
4502     specPtr->clientData = PerStateCO_Alloc(optionName, typePtr, proc);
4503 
4504     return TCL_OK;
4505 }
4506 
4507 #define DEBUG_DYNAMICxxx
4508 
4509 static CONST char *DynamicOptionUid = "DynamicOption";
4510 
4511 /*
4512  *----------------------------------------------------------------------
4513  *
4514  * DynamicOption_Find --
4515  *
4516  *	Returns a pointer to a dynamic-option record or NULL.
4517  *
4518  * Results:
4519  *	None.
4520  *
4521  * Side effects:
4522  *	None.
4523  *
4524  *----------------------------------------------------------------------
4525  */
4526 
4527 static DynamicOption *
DynamicOption_Find(DynamicOption * first,int id)4528 DynamicOption_Find(
4529     DynamicOption *first,	/* Head of linked list. */
4530     int id			/* Unique id. */
4531     )
4532 {
4533     DynamicOption *opt = first;
4534 
4535     while (opt != NULL) {
4536 	if (opt->id == id)
4537 	    return opt;
4538 	opt = opt->next;
4539     }
4540     return NULL;
4541 }
4542 
4543 /*
4544  *----------------------------------------------------------------------
4545  *
4546  * DynamicOption_FindData --
4547  *
4548  *	Returns a pointer to the option-specific data for a
4549  *	dynamic-option record or NULL.
4550  *
4551  * Results:
4552  *	None.
4553  *
4554  * Side effects:
4555  *	None.
4556  *
4557  *----------------------------------------------------------------------
4558  */
4559 
4560 void *
DynamicOption_FindData(DynamicOption * first,int id)4561 DynamicOption_FindData(
4562     DynamicOption *first,	/* Head of linked list. */
4563     int id			/* Unique id. */
4564     )
4565 {
4566     DynamicOption *opt = DynamicOption_Find(first, id);
4567     if (opt != NULL)
4568 	return opt->data;
4569     return NULL;
4570 }
4571 
4572 /*
4573  *----------------------------------------------------------------------
4574  *
4575  * DynamicOption_AllocIfNeeded --
4576  *
4577  *	Returns a pointer to a dynamic-option record.
4578  *
4579  * Results:
4580  *	If the dynamic-option record exists, it is returned. Otherwise
4581  *	a new one is allocated and initialized.
4582  *
4583  * Side effects:
4584  *	Memory may be allocated.
4585  *
4586  *----------------------------------------------------------------------
4587  */
4588 
4589 DynamicOption *
DynamicOption_AllocIfNeeded(TreeCtrl * tree,DynamicOption ** firstPtr,int id,int size,DynamicOptionInitProc * init)4590 DynamicOption_AllocIfNeeded(
4591     TreeCtrl *tree,
4592     DynamicOption **firstPtr,	/* Pointer to the head of linked list.
4593 				 * Will be updated if a new record is
4594 				 * created. */
4595     int id,			/* Unique id. */
4596     int size,			/* Size of option-specific data. */
4597     DynamicOptionInitProc *init	/* Proc to intialize the option-specific
4598 				 * data. May be NULL. */
4599     )
4600 {
4601     DynamicOption *opt = *firstPtr;
4602 
4603     while (opt != NULL) {
4604 	if (opt->id == id)
4605 	    return opt;
4606 	opt = opt->next;
4607     }
4608 #ifdef DEBUG_DYNAMIC
4609 dbwin("DynamicOption_AllocIfNeeded allocated id=%d\n", id);
4610 #endif
4611 #ifdef ALLOC_HAX
4612     opt = (DynamicOption *) TreeAlloc_Alloc(tree->allocData, DynamicOptionUid,
4613 	    Tk_Offset(DynamicOption, data) + size);
4614 #else
4615     opt = (DynamicOption *) ckalloc(Tk_Offset(DynamicOption, data) + size);
4616 #endif
4617     opt->id = id;
4618     memset(opt->data, '\0', size);
4619     if (init != NULL)
4620 	(*init)(opt->data);
4621     opt->next = *firstPtr;
4622     *firstPtr = opt;
4623     return opt;
4624 }
4625 
4626 /*
4627  *----------------------------------------------------------------------
4628  *
4629  * DynamicCO_Set --
4630  * DynamicCO_Get --
4631  * DynamicCO_Restore --
4632  * DynamicCO_Free --
4633  *
4634  *	These procedures implement a TK_OPTION_CUSTOM where the custom
4635  *	option is a DynamicOption record.
4636  *
4637  *	A dynamic option is one for which storage is not allocated until
4638  *	the option is configured for the first time. Dynamic options are
4639  *	saved in a linked list.
4640  *
4641  * Results:
4642  *	None.
4643  *
4644  * Side effects:
4645  *	None.
4646  *
4647  *----------------------------------------------------------------------
4648  */
4649 
4650 /* This is the Tk_OptionSpec.clientData field for a dynamic option. */
4651 typedef struct DynamicCOClientData
4652 {
4653     int id;			/* Unique id. */
4654     int size;			/* Size of client data. */
4655     int objOffset;		/* Offset in the client data to store the
4656 				 * object representation of the option.
4657 				 * May be < 0. */
4658     int internalOffset;		/* Offset in the client data to store the
4659 				 * internal representation of the option.
4660 				 * May be < 0. */
4661     Tk_ObjCustomOption *custom;	/* Table of procedures and clientData for
4662 				 * the actual option. */
4663     DynamicOptionInitProc *init;/* This gets called to initialize the client
4664 				 * data when it is first allocated. May be
4665 				 * NULL. */
4666 } DynamicCOClientData;
4667 
4668 /* This is used to save the current value of an option when a call to
4669  * Tk_SetOptions is in progress. */
4670 typedef struct DynamicCOSave
4671 {
4672     Tcl_Obj *objPtr;		/* The object representation of the option. */
4673     double internalForm;	/* The internal form of the option. */
4674 } DynamicCOSave;
4675 
4676 static int
DynamicCO_Set(ClientData clientData,Tcl_Interp * interp,Tk_Window tkwin,Tcl_Obj ** value,char * recordPtr,int internalOffset,char * saveInternalPtr,int flags)4677 DynamicCO_Set(
4678     ClientData clientData,
4679     Tcl_Interp *interp,
4680     Tk_Window tkwin,
4681     Tcl_Obj **value,
4682     char *recordPtr,
4683     int internalOffset,
4684     char *saveInternalPtr,
4685     int flags
4686     )
4687 {
4688     TreeCtrl *tree = (TreeCtrl *) ((TkWindow *) tkwin)->instanceData;
4689     DynamicCOClientData *cd = clientData;
4690     DynamicOption **firstPtr, *opt;
4691     DynamicCOSave *save;
4692     Tcl_Obj **objPtrPtr = NULL;
4693 
4694     /* Get pointer to the head of the list of dynamic options. */
4695     firstPtr = (DynamicOption **) (recordPtr + internalOffset);
4696 
4697     /* Get the dynamic option record. Create it if needed, and update the
4698      * linked list of dynamic options. */
4699     opt = DynamicOption_AllocIfNeeded(tree, firstPtr, cd->id, cd->size, cd->init);
4700 
4701     if (cd->objOffset >= 0)
4702 	objPtrPtr = (Tcl_Obj **) (opt->data + cd->objOffset);
4703 
4704     save = (DynamicCOSave *) ckalloc(sizeof(DynamicCOSave));
4705 #ifdef DEBUG_DYNAMIC
4706 dbwin("DynamicCO_Set id=%d saveInternalPtr=%p save=%p\n", cd->id, saveInternalPtr, save);
4707 #endif
4708     if (objPtrPtr != NULL) {
4709 	save->objPtr = *objPtrPtr;
4710 #ifdef DEBUG_DYNAMIC
4711 if (save->objPtr)
4712     dbwin("  old object '%s' refCount=%d\n", Tcl_GetString(save->objPtr), save->objPtr->refCount);
4713 else
4714     dbwin("  old object NULL\n");
4715 #endif
4716     }
4717     if (cd->custom->setProc(cd->custom->clientData, interp, tkwin, value,
4718 	    opt->data, cd->internalOffset, (char *) &save->internalForm,
4719 	    flags) != TCL_OK) {
4720 	ckfree((char *) save);
4721 	return TCL_ERROR;
4722     }
4723 
4724     if (objPtrPtr != NULL) {
4725 #ifdef DEBUG_DYNAMIC
4726 if (*value)
4727     dbwin("  new object '%s' refCount=%d\n", Tcl_GetString(*value), (*value)->refCount);
4728 else
4729     dbwin("  new object NULL\n");
4730 #endif
4731 	*objPtrPtr = *value;
4732 	if (*value != NULL)
4733 	    Tcl_IncrRefCount(*value);
4734     }
4735 
4736     *(DynamicCOSave **) saveInternalPtr = save;
4737     OptionHax_Remember(tree, saveInternalPtr);
4738 
4739     return TCL_OK;
4740 }
4741 
4742 static Tcl_Obj *
DynamicCO_Get(ClientData clientData,Tk_Window tkwin,char * recordPtr,int internalOffset)4743 DynamicCO_Get(
4744     ClientData clientData,
4745     Tk_Window tkwin,
4746     char *recordPtr,
4747     int internalOffset
4748     )
4749 {
4750     DynamicCOClientData *cd = clientData;
4751     DynamicOption *first = *(DynamicOption **) (recordPtr + internalOffset);
4752     DynamicOption *opt = DynamicOption_Find(first, cd->id);
4753 
4754 #ifdef DEBUG_DYNAMIC
4755 dbwin("DynamicCO_Get id=%d opt=%p objOffset=%d\n", cd->id, opt, cd->objOffset);
4756 #endif
4757     if (opt == NULL)
4758 	return NULL;
4759 
4760     if (cd->objOffset >= 0) {
4761 #ifdef TREECTRL_DEBUG
4762 Tcl_Obj *objPtr = *(Tcl_Obj **) (opt->data + cd->objOffset);
4763 if (objPtr && objPtr->refCount == 0) panic("DynamicCO_Get refCount=0");
4764 #endif
4765 	return *(Tcl_Obj **) (opt->data + cd->objOffset);
4766     }
4767 
4768     if (cd->custom->getProc != NULL)
4769 	return cd->custom->getProc(cd->custom->clientData, tkwin, opt->data, cd->internalOffset);
4770     return NULL;
4771 }
4772 
4773 static void
DynamicCO_Restore(ClientData clientData,Tk_Window tkwin,char * internalPtr,char * saveInternalPtr)4774 DynamicCO_Restore(
4775     ClientData clientData,
4776     Tk_Window tkwin,
4777     char *internalPtr,
4778     char *saveInternalPtr
4779     )
4780 {
4781     TreeCtrl *tree = (TreeCtrl *) ((TkWindow *) tkwin)->instanceData;
4782     DynamicCOClientData *cd = clientData;
4783     DynamicOption *first = *(DynamicOption **) internalPtr;
4784     DynamicOption *opt = DynamicOption_Find(first, cd->id);
4785     DynamicCOSave *save = *(DynamicCOSave **)saveInternalPtr;
4786     Tcl_Obj **objPtrPtr;
4787 
4788     if (opt == NULL)
4789 	panic("DynamicCO_Restore: opt=NULL");
4790 #ifdef DEBUG_DYNAMIC
4791 dbwin("DynamicCO_Restore id=%d internalOffset=%d save=%p\n", cd->id, cd->internalOffset, save);
4792 #endif
4793     if (cd->internalOffset >= 0 && cd->custom->restoreProc != NULL)
4794 	cd->custom->restoreProc(cd->custom->clientData, tkwin,
4795 	    opt->data + cd->internalOffset, (char *) &save->internalForm);
4796 
4797     if (cd->objOffset >= 0) {
4798 	objPtrPtr = (Tcl_Obj **) (opt->data + cd->objOffset);
4799 #ifdef DEBUG_DYNAMIC
4800 if (*objPtrPtr)
4801     dbwin("DynamicCO_Restore replace object '%s' refCount=%d\n", Tcl_GetString(*objPtrPtr), (*objPtrPtr)->refCount);
4802 else
4803     dbwin("DynamicCO_Restore replace object NULL\n");
4804 if (save->objPtr)
4805     dbwin("DynamicCO_Restore restore object '%s' refCount=%d\n", Tcl_GetString(save->objPtr), save->objPtr->refCount);
4806 else
4807     dbwin("DynamicCO_Restore restore object NULL\n");
4808 #endif
4809 /*	if (*objPtrPtr != NULL)
4810 	    Tcl_DecrRefCount(*objPtrPtr);*/
4811 	*objPtrPtr = save->objPtr;
4812     }
4813 
4814     ckfree((char *) save);
4815     OptionHax_Forget(tree, saveInternalPtr);
4816 }
4817 
4818 static void
DynamicCO_Free(ClientData clientData,Tk_Window tkwin,char * internalPtr)4819 DynamicCO_Free(
4820     ClientData clientData,
4821     Tk_Window tkwin,
4822     char *internalPtr
4823     )
4824 {
4825     TreeCtrl *tree = (TreeCtrl *) ((TkWindow *) tkwin)->instanceData;
4826     DynamicCOClientData *cd = clientData;
4827     Tcl_Obj **objPtrPtr;
4828 
4829     if (OptionHax_Forget(tree, internalPtr)) {
4830 	DynamicCOSave *save = *(DynamicCOSave **) internalPtr;
4831 #ifdef DEBUG_DYNAMIC
4832 dbwin("DynamicCO_Free id=%d internalPtr=%p save=%p\n", cd->id, internalPtr, save);
4833 #endif
4834 	if (cd->internalOffset >= 0 && cd->custom->freeProc != NULL)
4835 	    cd->custom->freeProc(cd->custom->clientData, tkwin,
4836 		    (char *) &save->internalForm);
4837 	if (cd->objOffset >= 0) {
4838 #ifdef DEBUG_DYNAMIC
4839 if (save->objPtr) {
4840     dbwin("DynamicCO_Free free object '%s' refCount=%d-1\n", Tcl_GetString(save->objPtr), (save->objPtr)->refCount);
4841     if (save->objPtr->refCount == 0) panic("DynamicCO_Free refCount=0");
4842 }
4843 else
4844     dbwin("DynamicCO_Free free object NULL\n");
4845 #endif
4846 	    if (save->objPtr) {
4847 		Tcl_DecrRefCount(save->objPtr);
4848 	    }
4849 	}
4850 	ckfree((char *) save);
4851     } else {
4852 	DynamicOption *first = *(DynamicOption **) internalPtr;
4853 	DynamicOption *opt = DynamicOption_Find(first, cd->id);
4854 #ifdef DEBUG_DYNAMIC
4855 dbwin("DynamicCO_Free id=%d internalPtr=%p save=NULL\n", cd->id, internalPtr);
4856 #endif
4857 	if (opt != NULL && cd->internalOffset >= 0 && cd->custom->freeProc != NULL)
4858 	    cd->custom->freeProc(cd->custom->clientData, tkwin,
4859 		opt->data + cd->internalOffset);
4860 	if (opt != NULL && cd->objOffset >= 0) {
4861 	    objPtrPtr = (Tcl_Obj **) (opt->data + cd->objOffset);
4862 #ifdef DEBUG_DYNAMIC
4863 if (*objPtrPtr) {
4864     dbwin("DynamicCO_Free free object '%s' refCount=%d-1\n", Tcl_GetString(*objPtrPtr), (*objPtrPtr)->refCount);
4865     if ((*objPtrPtr)->refCount == 0) panic("DynamicCO_Free refCount=0");
4866 }
4867 else
4868     dbwin("DynamicCO_Free free object NULL\n");
4869 #endif
4870 	    if (*objPtrPtr != NULL) {
4871 		Tcl_DecrRefCount(*objPtrPtr);
4872 	    }
4873 	}
4874     }
4875 }
4876 
4877 /*
4878  *----------------------------------------------------------------------
4879  *
4880  * DynamicOption_Init --
4881  *
4882  *	Initialize a Tk_OptionSpec.clientData field before calling
4883  *	Tk_CreateOptionTable.
4884  *
4885  * Results:
4886  *	None.
4887  *
4888  * Side effects:
4889  *	Memory may be allocated.
4890  *
4891  *----------------------------------------------------------------------
4892  */
4893 
4894 int
DynamicCO_Init(Tk_OptionSpec * optionTable,CONST char * optionName,int id,int size,int objOffset,int internalOffset,Tk_ObjCustomOption * custom,DynamicOptionInitProc * init)4895 DynamicCO_Init(
4896     Tk_OptionSpec *optionTable,	/* Table to search. */
4897     CONST char *optionName,	/* Name of the option. */
4898     int id,			/* Unique id. */
4899     int size,			/* Size of client data. */
4900     int objOffset,		/* Offset in the client data to store the
4901 				 * object representation of the option.
4902 				 * May be < 0. */
4903     int internalOffset,		/* Offset in the client data to store the
4904 				 * internal representation of the option.
4905 				 * May be < 0. */
4906     Tk_ObjCustomOption *custom,	/* Table of procedures and clientData for
4907 				 * the actual option. */
4908     DynamicOptionInitProc *init	/* This gets called to initialize the client
4909 				 * data when it is first allocated. May be
4910 				 * NULL. */
4911     )
4912 {
4913     Tk_OptionSpec *specPtr;
4914     DynamicCOClientData *cd;
4915     Tk_ObjCustomOption *co;
4916 
4917     if (size <= 0)
4918 	panic("DynamicCO_Init: option %s size=%d", optionName, size);
4919 
4920     specPtr = Tree_FindOptionSpec(optionTable, optionName);
4921     if (specPtr->type != TK_OPTION_CUSTOM)
4922 	panic("DynamicCO_Init: %s is not TK_OPTION_CUSTOM", optionName);
4923     if (specPtr->clientData != NULL)
4924 	return TCL_OK;
4925 
4926     /* ClientData for the Tk custom option record */
4927     cd = (DynamicCOClientData *) ckalloc(sizeof(DynamicCOClientData));
4928     cd->id = id;
4929     cd->size = size;
4930     cd->objOffset = objOffset;
4931     cd->internalOffset = internalOffset;
4932     cd->custom = custom;
4933     cd->init = init;
4934 
4935     /* The Tk custom option record */
4936     co = (Tk_ObjCustomOption *) ckalloc(sizeof(Tk_ObjCustomOption));
4937     co->name = (char *) optionName + 1;
4938     co->setProc = DynamicCO_Set;
4939     co->getProc = DynamicCO_Get;
4940     co->restoreProc = DynamicCO_Restore;
4941     co->freeProc = DynamicCO_Free;
4942     co->clientData = (ClientData) cd;
4943 
4944     /* Update the option table */
4945     specPtr->clientData = co;
4946 #ifdef DEBUG_DYNAMIC
4947 dbwin("DynamicCO_Init id=%d size=%d objOffset=%d internalOffset=%d custom->name=%s\n",
4948     id, size, objOffset, internalOffset, custom->name);
4949 #endif
4950     return TCL_OK;
4951 }
4952 
4953 /*
4954  *----------------------------------------------------------------------
4955  *
4956  * DynamicOption_Free --
4957  *
4958  *	Free a linked list of dynamic-option records. This gets called
4959  *	after Tk_FreeConfigOptions.
4960  *
4961  * Results:
4962  *	None.
4963  *
4964  * Side effects:
4965  *	Memory may be freed.
4966  *
4967  *----------------------------------------------------------------------
4968  */
4969 
4970 void
DynamicOption_Free(TreeCtrl * tree,DynamicOption * first,Tk_OptionSpec * optionTable)4971 DynamicOption_Free(
4972     TreeCtrl *tree,
4973     DynamicOption *first,
4974     Tk_OptionSpec *optionTable
4975     )
4976 {
4977     DynamicOption *opt = first;
4978     DynamicCOClientData *cd;
4979     Tk_ObjCustomOption *co;
4980     int i;
4981 
4982     while (opt != NULL) {
4983 	DynamicOption *next = opt->next;
4984 	for (i = 0; optionTable[i].type != TK_OPTION_END; i++) {
4985 
4986 	    if (optionTable[i].type != TK_OPTION_CUSTOM)
4987 		continue;
4988 
4989 	    co = (Tk_ObjCustomOption *) optionTable[i].clientData;
4990 	    if (co->setProc != DynamicCO_Set)
4991 		continue;
4992 
4993 	    cd = (DynamicCOClientData *) co->clientData;
4994 	    if (cd->id != opt->id)
4995 		continue;
4996 
4997 #ifdef ALLOC_HAX
4998 	    TreeAlloc_Free(tree->allocData, DynamicOptionUid, (char *) opt,
4999 		    Tk_Offset(DynamicOption, data) + cd->size);
5000 #else
5001 	    ckfree((char *) opt);
5002 #endif
5003 	    break;
5004 	}
5005 	opt = next;
5006     }
5007 }
5008 
5009 /*
5010  *----------------------------------------------------------------------
5011  *
5012  * DynamicOption_Free1 --
5013  *
5014  *	Free a single dynamic-option record. This is a big hack so that
5015  *	dynamic-option records that aren't associated with a Tk_OptionSpec
5016  *	array can be used.
5017  *
5018  * Results:
5019  *	None.
5020  *
5021  * Side effects:
5022  *	Memory may be freed.
5023  *
5024  *----------------------------------------------------------------------
5025  */
5026 
5027 void
DynamicOption_Free1(TreeCtrl * tree,DynamicOption ** firstPtr,int id,int size)5028 DynamicOption_Free1(
5029     TreeCtrl *tree,
5030     DynamicOption **firstPtr,
5031     int id,
5032     int size
5033     )
5034 {
5035     DynamicOption *opt = *firstPtr, *prev = NULL;
5036 
5037     while (opt != NULL) {
5038 	if (opt->id == id) {
5039 	    if (prev == NULL)
5040 		*firstPtr = opt->next;
5041 	    else
5042 		prev->next = opt->next;
5043 #ifdef ALLOC_HAX
5044 	    TreeAlloc_Free(tree->allocData, DynamicOptionUid, (char *) opt,
5045 		    Tk_Offset(DynamicOption, data) + size);
5046 #else
5047 	    ckfree((char *) opt);
5048 #endif
5049 	    return;
5050 	}
5051 	prev = opt;
5052 	opt = opt->next;
5053     }
5054 }
5055 
5056 /*
5057  *----------------------------------------------------------------------
5058  *
5059  * Tree_InitOptions --
5060  * Tree_SetOptions --
5061  *
5062  *	These procedures are just wrappers around Tk_InitOptions and
5063  *	Tk_SetOptions.  They set tree->configStateDomain so that any
5064  *	per-state options know which state domain to use.
5065  *
5066  * Results:
5067  *	A standard Tcl result.
5068  *
5069  * Side effects:
5070  *	Whatever the wrapped function does.
5071  *
5072  *----------------------------------------------------------------------
5073  */
5074 
5075 int
Tree_InitOptions(TreeCtrl * tree,int domain,void * recordPtr,Tk_OptionTable optionTable)5076 Tree_InitOptions(
5077     TreeCtrl *tree,
5078     int domain,
5079     void *recordPtr,
5080     Tk_OptionTable optionTable
5081     )
5082 {
5083     int result;
5084 
5085     if (tree->configStateDomain != -1)
5086 	panic("Tree_InitOptions configStateDomain != -1");
5087 
5088     tree->configStateDomain = domain;
5089 
5090     result = Tk_InitOptions(tree->interp, recordPtr, optionTable, tree->tkwin);
5091 
5092     tree->configStateDomain = -1;
5093     return result;
5094 }
5095 
5096 int
Tree_SetOptions(TreeCtrl * tree,int domain,void * recordPtr,Tk_OptionTable optionTable,int objc,Tcl_Obj * CONST objv[],Tk_SavedOptions * savePtr,int * maskPtr)5097 Tree_SetOptions(
5098     TreeCtrl *tree,
5099     int domain,
5100     void *recordPtr,
5101     Tk_OptionTable optionTable,
5102     int objc,
5103     Tcl_Obj *CONST objv[],
5104     Tk_SavedOptions *savePtr,
5105     int *maskPtr
5106     )
5107 {
5108     int result;
5109 
5110     if (tree->configStateDomain != -1)
5111 	panic("Tree_SetOptions configStateDomain != -1");
5112 
5113     tree->configStateDomain = domain;
5114 
5115     result = Tk_SetOptions(tree->interp, recordPtr, optionTable, objc, objv,
5116 	tree->tkwin, savePtr, maskPtr);
5117 
5118     tree->configStateDomain = -1;
5119     return result;
5120 }
5121 
5122 /*
5123  *----------------------------------------------------------------------
5124  *
5125  * StringCO_Set --
5126  * StringCO_Get --
5127  * StringCO_Restore --
5128  * StringCO_Free --
5129  *
5130  *	These procedures implement a TK_OPTION_CUSTOM where the custom
5131  *	option is exactly the same as a TK_OPTION_STRING. This is used
5132  *	when storage for the option is dynamically allocated.
5133  *
5134  * Results:
5135  *	None.
5136  *
5137  * Side effects:
5138  *	None.
5139  *
5140  *----------------------------------------------------------------------
5141  */
5142 
5143 static int
StringCO_Set(ClientData clientData,Tcl_Interp * interp,Tk_Window tkwin,Tcl_Obj ** valuePtr,char * recordPtr,int internalOffset,char * saveInternalPtr,int flags)5144 StringCO_Set(
5145     ClientData clientData,
5146     Tcl_Interp *interp,
5147     Tk_Window tkwin,
5148     Tcl_Obj **valuePtr,
5149     char *recordPtr,
5150     int internalOffset,
5151     char *saveInternalPtr,
5152     int flags
5153     )
5154 {
5155     int objEmpty;
5156     char *internalPtr, *new, *value;
5157     int length;
5158 
5159     if (internalOffset >= 0)
5160 	internalPtr = (char *) (recordPtr + internalOffset);
5161     else
5162 	internalPtr = NULL;
5163 
5164     objEmpty = ObjectIsEmpty((*valuePtr));
5165 
5166     if ((flags & TK_OPTION_NULL_OK) && objEmpty)
5167 	(*valuePtr) = NULL;
5168 
5169     if (internalPtr != NULL) {
5170 	if (*valuePtr != NULL) {
5171 	    value = Tcl_GetStringFromObj(*valuePtr, &length);
5172 	    new = ckalloc((unsigned) (length + 1));
5173 	    strcpy(new, value);
5174 	} else {
5175 	    new = NULL;
5176 	}
5177 	*((char **) saveInternalPtr) = *((char **) internalPtr);
5178 	*((char **) internalPtr) = new;
5179     }
5180 
5181     return TCL_OK;
5182 }
5183 
5184 static Tcl_Obj *
StringCO_Get(ClientData clientData,Tk_Window tkwin,char * recordPtr,int internalOffset)5185 StringCO_Get(
5186     ClientData clientData,
5187     Tk_Window tkwin,
5188     char *recordPtr,
5189     int internalOffset
5190     )
5191 {
5192     char **internalPtr = (char **) (recordPtr + internalOffset);
5193 
5194     return Tcl_NewStringObj(*internalPtr, -1);
5195 }
5196 
5197 static void
StringCO_Restore(ClientData clientData,Tk_Window tkwin,char * internalPtr,char * saveInternalPtr)5198 StringCO_Restore(
5199     ClientData clientData,
5200     Tk_Window tkwin,
5201     char *internalPtr,
5202     char *saveInternalPtr
5203     )
5204 {
5205     *(char **) internalPtr = *(char **) saveInternalPtr;
5206 }
5207 
5208 static void
StringCO_Free(ClientData clientData,Tk_Window tkwin,char * internalPtr)5209 StringCO_Free(
5210     ClientData clientData,
5211     Tk_Window tkwin,
5212     char *internalPtr
5213     )
5214 {
5215     if (*((char **) internalPtr) != NULL) {
5216 	ckfree(*((char **) internalPtr));
5217 	*((char **) internalPtr) = NULL;
5218     }
5219 }
5220 
5221 Tk_ObjCustomOption TreeCtrlCO_string =
5222 {
5223     "string",
5224     StringCO_Set,
5225     StringCO_Get,
5226     StringCO_Restore,
5227     StringCO_Free,
5228     (ClientData) NULL
5229 };
5230 
5231 /*
5232  *----------------------------------------------------------------------
5233  *
5234  * PixelsCO_Set --
5235  * PixelsCO_Get --
5236  * PixelsCO_Restore --
5237  *
5238  *	These procedures implement a TK_OPTION_CUSTOM where the custom
5239  *	option is exactly the same as a TK_OPTION_PIXELS. This is used
5240  *	when storage for the option is dynamically allocated.
5241  *
5242  * Results:
5243  *	None.
5244  *
5245  * Side effects:
5246  *	None.
5247  *
5248  *----------------------------------------------------------------------
5249  */
5250 
5251 static int
PixelsCO_Set(ClientData clientData,Tcl_Interp * interp,Tk_Window tkwin,Tcl_Obj ** valuePtr,char * recordPtr,int internalOffset,char * saveInternalPtr,int flags)5252 PixelsCO_Set(
5253     ClientData clientData,
5254     Tcl_Interp *interp,
5255     Tk_Window tkwin,
5256     Tcl_Obj **valuePtr,
5257     char *recordPtr,
5258     int internalOffset,
5259     char *saveInternalPtr,
5260     int flags
5261     )
5262 {
5263     int objEmpty;
5264     int *internalPtr, new;
5265 
5266     if (internalOffset >= 0)
5267 	internalPtr = (int *) (recordPtr + internalOffset);
5268     else
5269 	internalPtr = NULL;
5270 
5271     objEmpty = ObjectIsEmpty((*valuePtr));
5272 
5273     if ((flags & TK_OPTION_NULL_OK) && objEmpty) {
5274 	(*valuePtr) = NULL;
5275 	new = 0;
5276     } else {
5277 	if (Tk_GetPixelsFromObj(interp, tkwin, *valuePtr, &new) != TCL_OK)
5278 	    return TCL_ERROR;
5279     }
5280 
5281     if (internalPtr != NULL) {
5282 	*((int *) saveInternalPtr) = *internalPtr;
5283 	*internalPtr = new;
5284     }
5285 
5286     return TCL_OK;
5287 }
5288 
5289 static Tcl_Obj *
PixelsCO_Get(ClientData clientData,Tk_Window tkwin,char * recordPtr,int internalOffset)5290 PixelsCO_Get(
5291     ClientData clientData,
5292     Tk_Window tkwin,
5293     char *recordPtr,
5294     int internalOffset
5295     )
5296 {
5297     int *internalPtr = (int *) (recordPtr + internalOffset);
5298 
5299     return Tcl_NewIntObj(*internalPtr);
5300 }
5301 
5302 static void
PixelsCO_Restore(ClientData clientData,Tk_Window tkwin,char * internalPtr,char * saveInternalPtr)5303 PixelsCO_Restore(
5304     ClientData clientData,
5305     Tk_Window tkwin,
5306     char *internalPtr,
5307     char *saveInternalPtr
5308     )
5309 {
5310     *(int *) internalPtr = *(int *) saveInternalPtr;
5311 }
5312 
5313 Tk_ObjCustomOption TreeCtrlCO_pixels =
5314 {
5315     "string",
5316     PixelsCO_Set,
5317     PixelsCO_Get,
5318     PixelsCO_Restore,
5319     NULL,
5320     (ClientData) NULL
5321 };
5322 
5323 /*
5324  *----------------------------------------------------------------------
5325  *
5326  * StyleCO_Set --
5327  * StyleCO_Get --
5328  * StyleCO_Restore --
5329  *
5330  *	These procedures implement a TK_OPTION_CUSTOM where the custom
5331  *	option is a TreeStyle.
5332  *
5333  * Results:
5334  *	None.
5335  *
5336  * Side effects:
5337  *	None.
5338  *
5339  *----------------------------------------------------------------------
5340  */
5341 
5342 static int
StyleCO_Set(ClientData clientData,Tcl_Interp * interp,Tk_Window tkwin,Tcl_Obj ** valuePtr,char * recordPtr,int internalOffset,char * saveInternalPtr,int flags)5343 StyleCO_Set(
5344     ClientData clientData,
5345     Tcl_Interp *interp,
5346     Tk_Window tkwin,
5347     Tcl_Obj **valuePtr,
5348     char *recordPtr,
5349     int internalOffset,
5350     char *saveInternalPtr,
5351     int flags
5352     )
5353 {
5354     int domain = PTR2INT(clientData), domainS;
5355     TreeCtrl *tree = (TreeCtrl *) ((TkWindow *) tkwin)->instanceData;
5356     int objEmpty;
5357     TreeStyle *internalPtr, new;
5358 
5359     if (internalOffset >= 0)
5360 	internalPtr = (TreeStyle *) (recordPtr + internalOffset);
5361     else
5362 	internalPtr = NULL;
5363 
5364     objEmpty = ObjectIsEmpty((*valuePtr));
5365 
5366     if ((flags & TK_OPTION_NULL_OK) && objEmpty) {
5367 	(*valuePtr) = NULL;
5368 	new = NULL;
5369     } else {
5370 	if (TreeStyle_FromObj(tree, *valuePtr, &new) != TCL_OK)
5371 	    return TCL_ERROR;
5372 	domainS = TreeStyle_GetStateDomain(tree, new);
5373 	if (domainS != domain) {
5374 	    FormatResult(interp,
5375 		"expected state domain \"%s\" but got \"%s\"",
5376 		tree->stateDomain[domain].name,
5377 		tree->stateDomain[domainS].name);
5378 	    return TCL_ERROR;
5379 	}
5380     }
5381 
5382     if (internalPtr != NULL) {
5383 	*((TreeStyle *) saveInternalPtr) = *internalPtr;
5384 	*internalPtr = new;
5385     }
5386 
5387     return TCL_OK;
5388 }
5389 
5390 static Tcl_Obj *
StyleCO_Get(ClientData clientData,Tk_Window tkwin,char * recordPtr,int internalOffset)5391 StyleCO_Get(
5392     ClientData clientData,
5393     Tk_Window tkwin,
5394     char *recordPtr,
5395     int internalOffset
5396     )
5397 {
5398     TreeStyle *internalPtr = (TreeStyle *) (recordPtr + internalOffset);
5399 
5400     if (*internalPtr == NULL)
5401 	return NULL;
5402     return TreeStyle_ToObj(*internalPtr);
5403 }
5404 
5405 static void
StyleCO_Restore(ClientData clientData,Tk_Window tkwin,char * internalPtr,char * saveInternalPtr)5406 StyleCO_Restore(
5407     ClientData clientData,
5408     Tk_Window tkwin,
5409     char *internalPtr,
5410     char *saveInternalPtr
5411     )
5412 {
5413     *(TreeStyle *) internalPtr = *(TreeStyle *) saveInternalPtr;
5414 }
5415 
5416 Tk_ObjCustomOption TreeCtrlCO_style =
5417 {
5418     "style",
5419     StyleCO_Set,
5420     StyleCO_Get,
5421     StyleCO_Restore,
5422     NULL,
5423     (ClientData) NULL
5424 };
5425 
5426 /*
5427  *----------------------------------------------------------------------
5428  *
5429  * TreeStyleCO_Init --
5430  *
5431  *	Initializes a Tk_OptionSpec.clientData for a custom option.
5432  *
5433  * Results:
5434  *	None.
5435  *
5436  * Side effects:
5437  *	None.
5438  *
5439  *----------------------------------------------------------------------
5440  */
5441 
5442 void
TreeStyleCO_Init(Tk_OptionSpec * optionTable,CONST char * optionName,int domain)5443 TreeStyleCO_Init(
5444     Tk_OptionSpec *optionTable,
5445     CONST char *optionName,
5446     int domain
5447     )
5448 {
5449     Tk_OptionSpec *specPtr;
5450     Tk_ObjCustomOption *co;
5451 
5452     specPtr = Tree_FindOptionSpec(optionTable, optionName);
5453     if (specPtr->type != TK_OPTION_CUSTOM)
5454 	panic("TreeStyleCO_Init: %s is not TK_OPTION_CUSTOM", optionName);
5455     if (specPtr->clientData != NULL)
5456 	return;
5457 
5458     co = (Tk_ObjCustomOption *) ckalloc(sizeof(Tk_ObjCustomOption));
5459     *co = TreeCtrlCO_style;
5460     co->clientData = INT2PTR(domain);
5461 
5462     specPtr->clientData = co;
5463 }
5464 
5465 /*
5466  *----------------------------------------------------------------------
5467  *
5468  * BooleanFlagCO_Set --
5469  * BooleanFlagCO_Get --
5470  * BooleanFlagCO_Restore --
5471  *
5472  *	These procedures implement a TK_OPTION_CUSTOM where the custom
5473  *	option is a boolean value whose internal rep is a single bit of
5474  *	an int rather than the entire int.
5475  *
5476  * Results:
5477  *	None.
5478  *
5479  * Side effects:
5480  *	None.
5481  *
5482  *----------------------------------------------------------------------
5483  */
5484 
5485 static int
BooleanFlagCO_Set(ClientData clientData,Tcl_Interp * interp,Tk_Window tkwin,Tcl_Obj ** value,char * recordPtr,int internalOffset,char * saveInternalPtr,int flags)5486 BooleanFlagCO_Set(
5487     ClientData clientData,
5488     Tcl_Interp *interp,
5489     Tk_Window tkwin,
5490     Tcl_Obj **value,
5491     char *recordPtr,
5492     int internalOffset,
5493     char *saveInternalPtr,
5494     int flags
5495     )
5496 {
5497     int theFlag = PTR2INT(clientData);
5498     int new, *internalPtr;
5499 
5500     if (internalOffset >= 0)
5501 	internalPtr = (int *) (recordPtr + internalOffset);
5502     else
5503 	internalPtr = NULL;
5504 
5505     if (Tcl_GetBooleanFromObj(interp, (*value), &new) != TCL_OK)
5506 	return TCL_ERROR;
5507 
5508     if (internalPtr != NULL) {
5509 	*((int *) saveInternalPtr) = *internalPtr;
5510 	if (new)
5511 	    *internalPtr |= theFlag;
5512 	else
5513 	    *internalPtr &= ~theFlag;
5514     }
5515 
5516     return TCL_OK;
5517 }
5518 
5519 static Tcl_Obj *
BooleanFlagCO_Get(ClientData clientData,Tk_Window tkwin,char * recordPtr,int internalOffset)5520 BooleanFlagCO_Get(
5521     ClientData clientData,
5522     Tk_Window tkwin,
5523     char *recordPtr,
5524     int internalOffset
5525     )
5526 {
5527     int theFlag = PTR2INT(clientData);
5528     int value = *(int *) (recordPtr + internalOffset);
5529 
5530     return Tcl_NewBooleanObj(value & theFlag);
5531 }
5532 
5533 static void
BooleanFlagCO_Restore(ClientData clientData,Tk_Window tkwin,char * internalPtr,char * saveInternalPtr)5534 BooleanFlagCO_Restore(
5535     ClientData clientData,
5536     Tk_Window tkwin,
5537     char *internalPtr,
5538     char *saveInternalPtr
5539     )
5540 {
5541     int theFlag = PTR2INT(clientData);
5542     int value = *(int *) saveInternalPtr;
5543 
5544     if (value & theFlag)
5545 	*((int *) internalPtr) |= theFlag;
5546     else
5547 	*((int *) internalPtr) &= ~theFlag;
5548 }
5549 
5550 int
BooleanFlagCO_Init(Tk_OptionSpec * optionTable,CONST char * optionName,int theFlag)5551 BooleanFlagCO_Init(
5552     Tk_OptionSpec *optionTable,
5553     CONST char *optionName,
5554     int theFlag
5555     )
5556 {
5557     Tk_OptionSpec *specPtr;
5558     Tk_ObjCustomOption *co;
5559 
5560     specPtr = Tree_FindOptionSpec(optionTable, optionName);
5561     if (specPtr->type != TK_OPTION_CUSTOM)
5562 	panic("BooleanFlagCO_Init: %s is not TK_OPTION_CUSTOM", optionName);
5563     if (specPtr->clientData != NULL)
5564 	return TCL_OK;
5565 
5566     /* The Tk custom option record */
5567     co = (Tk_ObjCustomOption *) ckalloc(sizeof(Tk_ObjCustomOption));
5568     co->name = "boolean";
5569     co->setProc = BooleanFlagCO_Set;
5570     co->getProc = BooleanFlagCO_Get;
5571     co->restoreProc = BooleanFlagCO_Restore;
5572     co->freeProc = NULL;
5573     co->clientData = (ClientData) INT2PTR(theFlag);
5574 
5575     specPtr->clientData = co;
5576 
5577     return TCL_OK;
5578 }
5579 
5580 /*
5581  *----------------------------------------------------------------------
5582  *
5583  * ItemButtonCO_Set --
5584  * ItemButtonCO_Get --
5585  * ItemButtonCO_Restore --
5586  *
5587  *	These procedures implement a TK_OPTION_CUSTOM where the custom
5588  *	option is a boolean value or "auto"; the internal rep is two
5589  *	bits of an int: one bit for the boolean, and one for "auto".
5590  *	This is used for the item option -button.
5591  *
5592  * Results:
5593  *	None.
5594  *
5595  * Side effects:
5596  *	None.
5597  *
5598  *----------------------------------------------------------------------
5599  */
5600 
5601 struct ItemButtonCOClientData {
5602     int flag1;		/* Bit to set when object is "true". */
5603     int flag2;		/* Bit to set when object is "auto". */
5604 };
5605 
5606 static int
ItemButtonCO_Set(ClientData clientData,Tcl_Interp * interp,Tk_Window tkwin,Tcl_Obj ** value,char * recordPtr,int internalOffset,char * saveInternalPtr,int flags)5607 ItemButtonCO_Set(
5608     ClientData clientData,
5609     Tcl_Interp *interp,
5610     Tk_Window tkwin,
5611     Tcl_Obj **value,
5612     char *recordPtr,
5613     int internalOffset,
5614     char *saveInternalPtr,
5615     int flags
5616     )
5617 {
5618     struct ItemButtonCOClientData *cd = clientData;
5619     int new, *internalPtr, on, off;
5620     char *s;
5621     int length;
5622 
5623     if (internalOffset >= 0)
5624 	internalPtr = (int *) (recordPtr + internalOffset);
5625     else
5626 	internalPtr = NULL;
5627 
5628     s = Tcl_GetStringFromObj((*value), &length);
5629     if (s[0] == 'a' && strncmp(s, "auto", length) == 0) {
5630 	on = cd->flag2;
5631 	off = cd->flag1;
5632     } else {
5633 	if (Tcl_GetBooleanFromObj(interp, (*value), &new) != TCL_OK) {
5634 	    FormatResult(interp, "expected boolean or auto but got \"%s\"", s);
5635 	    return TCL_ERROR;
5636 	}
5637 	if (new) {
5638 	    on = cd->flag1;
5639 	    off = cd->flag2;
5640 	} else {
5641 	    on = 0;
5642 	    off = cd->flag1 | cd->flag2;
5643 	}
5644     }
5645 
5646     if (internalPtr != NULL) {
5647 	*((int *) saveInternalPtr) = *internalPtr;
5648 	*internalPtr |= on;
5649 	*internalPtr &= ~off;
5650     }
5651 
5652     return TCL_OK;
5653 }
5654 
5655 static Tcl_Obj *
ItemButtonCO_Get(ClientData clientData,Tk_Window tkwin,char * recordPtr,int internalOffset)5656 ItemButtonCO_Get(
5657     ClientData clientData,
5658     Tk_Window tkwin,
5659     char *recordPtr,
5660     int internalOffset
5661     )
5662 {
5663     struct ItemButtonCOClientData *cd = clientData;
5664     int value = *(int *) (recordPtr + internalOffset);
5665 
5666     if (value & cd->flag2)
5667 	return Tcl_NewStringObj("auto", -1);
5668     return Tcl_NewBooleanObj((value & cd->flag1) != 0);
5669 }
5670 
5671 static void
ItemButtonCO_Restore(ClientData clientData,Tk_Window tkwin,char * internalPtr,char * saveInternalPtr)5672 ItemButtonCO_Restore(
5673     ClientData clientData,
5674     Tk_Window tkwin,
5675     char *internalPtr,
5676     char *saveInternalPtr
5677     )
5678 {
5679     struct ItemButtonCOClientData *cd = clientData;
5680     int value = *(int *) saveInternalPtr;
5681 
5682     *((int *) internalPtr) &= ~(cd->flag1 | cd->flag2);
5683     *((int *) internalPtr) |= value & (cd->flag1 | cd->flag2);
5684 }
5685 
5686 int
ItemButtonCO_Init(Tk_OptionSpec * optionTable,CONST char * optionName,int flag1,int flag2)5687 ItemButtonCO_Init(
5688     Tk_OptionSpec *optionTable,
5689     CONST char *optionName,
5690     int flag1,
5691     int flag2
5692     )
5693 {
5694     Tk_OptionSpec *specPtr;
5695     Tk_ObjCustomOption *co;
5696     struct ItemButtonCOClientData *cd;
5697 
5698     specPtr = Tree_FindOptionSpec(optionTable, optionName);
5699     if (specPtr->type != TK_OPTION_CUSTOM)
5700 	panic("BooleanFlagCO_Init: %s is not TK_OPTION_CUSTOM", optionName);
5701     if (specPtr->clientData != NULL)
5702 	return TCL_OK;
5703 
5704     /* ClientData for the Tk custom option record. */
5705     cd = (struct ItemButtonCOClientData *)ckalloc(
5706 	    sizeof(struct ItemButtonCOClientData));
5707     cd->flag1 = flag1;
5708     cd->flag2 = flag2;
5709 
5710     /* The Tk custom option record */
5711     co = (Tk_ObjCustomOption *) ckalloc(sizeof(Tk_ObjCustomOption));
5712     co->name = "button option";
5713     co->setProc = ItemButtonCO_Set;
5714     co->getProc = ItemButtonCO_Get;
5715     co->restoreProc = ItemButtonCO_Restore;
5716     co->freeProc = NULL;
5717     co->clientData = cd;
5718 
5719     specPtr->clientData = co;
5720 
5721     return TCL_OK;
5722 }
5723 
5724 /*
5725  *----------------------------------------------------------------------
5726  *
5727  * Tree_GetIntForIndex --
5728  *
5729  *	This is basically a direct copy of TclGetIntForIndex with one
5730  *	important difference: the caller gets to know whether the index
5731  *	was of the form "end?-offset?".
5732  *
5733  * Results:
5734  *	None.
5735  *
5736  * Side effects:
5737  *	None.
5738  *
5739  *----------------------------------------------------------------------
5740  */
5741 
5742 int
Tree_GetIntForIndex(TreeCtrl * tree,Tcl_Obj * objPtr,int * indexPtr,int * endRelativePtr)5743 Tree_GetIntForIndex(
5744     TreeCtrl *tree,		/* Widget info. */
5745     Tcl_Obj *objPtr,		/* Points to an object containing either "end"
5746 				 * or an integer. */
5747     int *indexPtr,		/* Location filled in with an integer
5748 				 * representing an index. */
5749     int *endRelativePtr		/* Set to 1 if the returned index is relative
5750 				 * to "end". */
5751     )
5752 {
5753     int endValue = 0;
5754     char *bytes;
5755 
5756     if (TclGetIntForIndex(tree->interp, objPtr, endValue, indexPtr) != TCL_OK)
5757 	return TCL_ERROR;
5758 
5759     bytes = Tcl_GetString(objPtr);
5760     if (*bytes == 'e') {
5761 	*endRelativePtr = 1;
5762     } else {
5763 	*endRelativePtr = 0;
5764     }
5765     return TCL_OK;
5766 }
5767 
5768 /*
5769  *----------------------------------------------------------------------
5770  *
5771  * Tree_DrawRoundRectX11 --
5772  *
5773  *	Draw a rounded rectangle with a solid color.
5774  *
5775  * Results:
5776  *	None.
5777  *
5778  * Side effects:
5779  *	Drawing.
5780  *
5781  *----------------------------------------------------------------------
5782  */
5783 
5784 void
Tree_DrawRoundRectX11(TreeCtrl * tree,TreeDrawable td,TreeClip * clip,GC gc,TreeRectangle tr,int outlineWidth,int rx,int ry,int open)5785 Tree_DrawRoundRectX11(
5786     TreeCtrl *tree,		/* Widget info. */
5787     TreeDrawable td,		/* Where to draw. */
5788     TreeClip *clip,		/* Clipping area or NULL. */
5789     GC gc,			/* Graphics context. */
5790     TreeRectangle tr,		/* Where to draw. */
5791     int outlineWidth,
5792     int rx, int ry,		/* Corner radius */
5793     int open			/* RECT_OPEN_x flags */
5794     )
5795 {
5796     int x = tr.x, y = tr.y, width = tr.width, height = tr.height;
5797     TreeRectangle rects[4], *pr = rects;
5798     int nrects = 0;
5799     int drawW = (open & RECT_OPEN_W) == 0;
5800     int drawN = (open & RECT_OPEN_N) == 0;
5801     int drawE = (open & RECT_OPEN_E) == 0;
5802     int drawS = (open & RECT_OPEN_S) == 0;
5803     int i;
5804 
5805     /* Calculate the bounds of each edge that should be drawn */
5806     if (drawW) {
5807 	pr->x = x, pr->y = y, pr->width = outlineWidth, pr->height = height;
5808 	if (drawN)
5809 	    pr->y += ry, pr->height -= ry;
5810 	if (drawS)
5811 	    pr->height -= ry;
5812 	if (pr->width > 0 && pr->height > 0)
5813 	    pr++, nrects++;
5814     }
5815     if (drawN) {
5816 	pr->x = x, pr->y = y, pr->width = width, pr->height = outlineWidth;
5817 	if (drawW)
5818 	    pr->x += rx, pr->width -= rx;
5819 	if (drawE)
5820 	    pr->width -= rx;
5821 	if (pr->width > 0 && pr->height > 0)
5822 	    pr++, nrects++;
5823     }
5824     if (drawE) {
5825 	pr->x = x + width - outlineWidth, pr->y = y, pr->width = outlineWidth, pr->height = height;
5826 	if (drawN)
5827 	    pr->y += ry, pr->height -= ry;
5828 	if (drawS)
5829 	    pr->height -= ry;
5830 	if (pr->width > 0 && pr->height > 0)
5831 	    pr++, nrects++;
5832     }
5833     if (drawS) {
5834 	pr->x = x, pr->y = y + height - outlineWidth, pr->width = width, pr->height = outlineWidth;
5835 	if (drawW)
5836 	    pr->x += rx, pr->width -= rx;
5837 	if (drawE)
5838 	    pr->width -= rx;
5839 	if (pr->width > 0 && pr->height > 0)
5840 	    pr++, nrects++;
5841     }
5842     for (i = 0; i < nrects; i++)
5843 	Tree_FillRectangle(tree, td, clip, gc, rects[i]);
5844 
5845     /* On Win32 the code below works, leaving a 1-pixel hole at each
5846      * corner.  But on X11 there is no hole. */
5847     if (rx == 1 && ry == 1)
5848 	return;
5849 
5850     width -= 1, height -= 1;
5851 
5852     if (drawW && drawN)
5853 	Tree_DrawArc(tree, td, clip, gc, x, y, rx*2, ry*2, 64*90, 64*90); /* top-left */
5854     if (drawW && drawS)
5855 	Tree_DrawArc(tree, td, clip, gc, x, y + height - ry*2, rx*2, ry*2, 64*180, 64*90); /* bottom-left */
5856     if (drawE && drawN)
5857 	Tree_DrawArc(tree, td, clip, gc, x + width - rx*2, y, rx*2, ry*2, 64*0, 64*90); /* top-right */
5858     if (drawE && drawS)
5859 	Tree_DrawArc(tree, td, clip, gc, x + width - rx*2, y + height - ry*2, rx*2, ry*2, 64*270, 64*90); /* bottom-right */
5860 
5861     for (i = 1; i < outlineWidth; i++) {
5862 	x += 1, width -= 2;
5863 	if (drawW && drawN)
5864 	    Tree_DrawArc(tree, td, clip, gc, x, y, rx*2, ry*2, 64*90, 64*90); /* top-left */
5865 	if (drawW && drawS)
5866 	    Tree_DrawArc(tree, td, clip, gc, x, y + height - ry*2, rx*2, ry*2, 64*180, 64*90); /* bottom-left */
5867 	if (drawE && drawN)
5868 	    Tree_DrawArc(tree, td, clip, gc, x + width - rx*2, y, rx*2, ry*2, 64*0, 64*90); /* top-right */
5869 	if (drawE && drawS)
5870 	    Tree_DrawArc(tree, td, clip, gc, x + width - rx*2, y + height - ry*2, rx*2, ry*2, 64*270, 64*90); /* bottom-right */
5871 
5872 	y += 1, height -= 2;
5873 	if (drawW && drawN)
5874 	    Tree_DrawArc(tree, td, clip, gc, x, y, rx*2, ry*2, 64*90, 64*90); /* top-left */
5875 	if (drawW && drawS)
5876 	    Tree_DrawArc(tree, td, clip, gc, x, y + height - ry*2, rx*2, ry*2, 64*180, 64*90); /* bottom-left */
5877 	if (drawE && drawN)
5878 	    Tree_DrawArc(tree, td, clip, gc, x + width - rx*2, y, rx*2, ry*2, 64*0, 64*90); /* top-right */
5879 	if (drawE && drawS)
5880 	    Tree_DrawArc(tree, td, clip, gc, x + width - rx*2, y + height - ry*2, rx*2, ry*2, 64*270, 64*90); /* bottom-right */
5881     }
5882 }
5883 
5884 /*
5885  *----------------------------------------------------------------------
5886  *
5887  * Tree_FillRoundRectX11 --
5888  *
5889  *	Fill a rounded rectangle with a solid color.
5890  *
5891  * Results:
5892  *	None.
5893  *
5894  * Side effects:
5895  *	Drawing.
5896  *
5897  *----------------------------------------------------------------------
5898  */
5899 
5900 void
Tree_FillRoundRectX11(TreeCtrl * tree,TreeDrawable td,TreeClip * clip,GC gc,TreeRectangle tr,int rx,int ry,int open)5901 Tree_FillRoundRectX11(
5902     TreeCtrl *tree,		/* Widget info. */
5903     TreeDrawable td,		/* Where to draw. */
5904     TreeClip *clip,		/* Clipping area or NULL. */
5905     GC gc,			/* Graphics context. */
5906     TreeRectangle tr,		/* Rectangle to paint. */
5907     int rx, int ry,		/* Corner radius */
5908     int open			/* RECT_OPEN_x flags */
5909     )
5910 {
5911     TreeRectangle rects[3], *rectp = rects;
5912     int nrects = 0;
5913     int drawW = (open & RECT_OPEN_W) == 0;
5914     int drawN = (open & RECT_OPEN_N) == 0;
5915     int drawE = (open & RECT_OPEN_E) == 0;
5916     int drawS = (open & RECT_OPEN_S) == 0;
5917     int i;
5918 
5919     tr.height -= 1, tr.width -= 1;
5920     if (drawW && drawN)
5921 	Tree_FillArc(tree, td, clip, gc, tr.x, tr.y, rx*2, ry*2, 64*90, 64*90); /* top-left */
5922     if (drawW && drawS)
5923 	Tree_FillArc(tree, td, clip, gc, tr.x, tr.y + tr.height - ry*2, rx*2, ry*2, 64*180, 64*90); /* bottom-left */
5924     if (drawE && drawN)
5925 	Tree_FillArc(tree, td, clip, gc, tr.x + tr.width - rx*2, tr.y, rx*2, ry*2, 64*0, 64*90); /* top-right */
5926     if (drawE && drawS)
5927 	Tree_FillArc(tree, td, clip, gc, tr.x + tr.width - rx*2, tr.y + tr.height - ry*2, rx*2, ry*2, 64*270, 64*90); /* bottom-right */
5928     tr.height += 1, tr.width += 1;
5929 
5930     /* Everything but left/right edges */
5931     rectp->x = tr.x + rx;
5932     rectp->y = tr.y;
5933     rectp->width = tr.width - rx * 2;
5934     rectp->height = tr.height;
5935     if (rectp->width > 0 && rectp->height > 0) {
5936 	nrects++;
5937 	rectp++;
5938     }
5939 
5940     /* Left edge */
5941     rectp->x = tr.x;
5942     rectp->y = tr.y;
5943     rectp->width = rx;
5944     rectp->height = tr.height;
5945     if (drawW && drawN)
5946 	rectp->y += ry, rectp->height -= ry;
5947     if (drawW && drawS)
5948 	rectp->height -= ry;
5949     if (rectp->width > 0 && rectp->height > 0) {
5950 	nrects++;
5951 	rectp++;
5952     }
5953 
5954     /* Right edge */
5955     rectp->x = tr.x + tr.width - rx;
5956     rectp->y = tr.y;
5957     rectp->width = rx;
5958     rectp->height = tr.height;
5959     if (drawE && drawN)
5960 	rectp->y += ry, rectp->height -= ry;
5961     if (drawE && drawS)
5962 	rectp->height -= ry;
5963     if (rectp->width > 0 && rectp->height > 0) {
5964 	nrects++;
5965 	rectp++;
5966     }
5967 
5968     for (i = 0; i < nrects; i++)
5969 	Tree_FillRectangle(tree, td, clip, gc, rects[i]);
5970 }
5971 
5972 /*****/
5973 
5974 typedef struct PerStateDataGradient PerStateDataGradient;
5975 struct PerStateDataGradient
5976 {
5977     PerStateData header;
5978     TreeGradient gradient;
5979 };
5980 
5981 static int
PSDGradientFromObj(TreeCtrl * tree,Tcl_Obj * obj,PerStateDataGradient * pGradient)5982 PSDGradientFromObj(
5983     TreeCtrl *tree,
5984     Tcl_Obj *obj,
5985     PerStateDataGradient *pGradient)
5986 {
5987     if (ObjectIsEmpty(obj)) {
5988 	/* Specify empty string to override masterX */
5989 	pGradient->gradient = NULL;
5990     } else {
5991 	if (TreeGradient_FromObj(tree, obj, &pGradient->gradient) != TCL_OK)
5992 	    return TCL_ERROR;
5993 	pGradient->gradient->refCount++;
5994     }
5995     return TCL_OK;
5996 }
5997 
5998 static void
PSDGradientFree(TreeCtrl * tree,PerStateDataGradient * pGradient)5999 PSDGradientFree(
6000     TreeCtrl *tree,
6001     PerStateDataGradient *pGradient)
6002 {
6003     if (pGradient->gradient != NULL)
6004 	TreeGradient_Release(tree, pGradient->gradient);
6005 }
6006 
6007 PerStateType pstGradient =
6008 {
6009     "pstGradient",
6010     sizeof(PerStateDataGradient),
6011     (PerStateType_FromObjProc) PSDGradientFromObj,
6012     (PerStateType_FreeProc) PSDGradientFree
6013 };
6014 
6015 TreeGradient
PerStateGradient_ForState(TreeCtrl * tree,PerStateInfo * pInfo,int state,int * match)6016 PerStateGradient_ForState(
6017     TreeCtrl *tree,
6018     PerStateInfo *pInfo,
6019     int state,
6020     int *match)
6021 {
6022     PerStateDataGradient *pData;
6023 
6024     pData = (PerStateDataGradient *) PerStateInfo_ForState(tree, &pstGradient, pInfo, state, match);
6025     if (pData != NULL)
6026 	return pData->gradient;
6027     return NULL;
6028 }
6029 
6030 /*****/
6031 
6032 /*
6033  *----------------------------------------------------------------------
6034  *
6035  * Tree_AllocColorFromObj --
6036  *
6037  *	Allocate a TreeColor structure based on the given object.
6038  *
6039  * Results:
6040  *	A new TreeColor struct or NULL if an error occurred.
6041  *
6042  * Side effects:
6043  *	Memory may be allocated.  If the color refers to a gradient
6044  *	its refCount is incremented.
6045  *
6046  *----------------------------------------------------------------------
6047  */
6048 
6049 TreeColor *
Tree_AllocColorFromObj(TreeCtrl * tree,Tcl_Obj * obj)6050 Tree_AllocColorFromObj(
6051     TreeCtrl *tree,		/* Widget info. */
6052     Tcl_Obj *obj		/* Gradient name or Tk color specification */
6053     )
6054 {
6055     TreeGradient gradient = NULL;
6056     XColor *color = NULL;
6057     TreeColor *tc;
6058 
6059     if (TreeGradient_FromObj(tree, obj, &gradient) == TCL_OK) {
6060 	gradient->refCount++;
6061     } else {
6062 	Tcl_ResetResult(tree->interp);
6063 	color = Tk_AllocColorFromObj(tree->interp, tree->tkwin, obj);
6064 	if (color == NULL) {
6065 	    FormatResult(tree->interp, "unknown color or gradient name \"%s\"",
6066 		Tcl_GetString(obj));
6067 	    return NULL;
6068 	}
6069     }
6070 
6071     tc = (TreeColor *) ckalloc(sizeof(TreeColor));
6072     tc->color = color;
6073     tc->gradient = gradient;
6074 
6075     return tc;
6076 }
6077 
6078 /*
6079  *----------------------------------------------------------------------
6080  *
6081  * Tree_FreeColor --
6082  *
6083  *	Free a TreeColor.
6084  *
6085  * Results:
6086  *	None.
6087  *
6088  * Side effects:
6089  *	Memory may be freed.  If the color refers to a gradient
6090  *	its refCount is decremented.
6091  *
6092  *----------------------------------------------------------------------
6093  */
6094 
6095 void
Tree_FreeColor(TreeCtrl * tree,TreeColor * tc)6096 Tree_FreeColor(
6097     TreeCtrl *tree,		/* Widget info. */
6098     TreeColor *tc		/* Color to free, may be NULL */
6099     )
6100 {
6101     if (tc != NULL) {
6102 	if (tc->color)
6103 	    Tk_FreeColor(tc->color);
6104 	if (tc->gradient)
6105 	   TreeGradient_Release(tree, tc->gradient);
6106 	WFREE(tc, TreeColor);
6107     }
6108 }
6109 
6110 /*
6111  *----------------------------------------------------------------------
6112  *
6113  * TreeColor_ToObj --
6114  *
6115  *	Returns the Tcl_Obj representation of a TreeColor.
6116  *
6117  * Results:
6118  *	The returned representation is bogus, ensure any TreeColor
6119  *	options store the object representation.
6120  *
6121  * Side effects:
6122  *	Creates a new Tcl_Obj.
6123  *
6124  *----------------------------------------------------------------------
6125  */
6126 
6127 Tcl_Obj *
TreeColor_ToObj(TreeCtrl * tree,TreeColor * tc)6128 TreeColor_ToObj(
6129     TreeCtrl *tree,		/* Widget info. */
6130     TreeColor *tc		/* Color to get Tcl_Obj rep of */
6131     )
6132 {
6133     /* FIXME */
6134     return Tcl_NewStringObj("insert tree color rep here", -1);
6135 }
6136 
6137 /*
6138  *----------------------------------------------------------------------
6139  *
6140  * TreeColor_IsOpaque --
6141  *
6142  *	Test whether a tree color would be drawn fully opaque or not.
6143  *
6144  * Results:
6145  *	1 if opaque, 0 otherwise.
6146  *
6147  * Side effects:
6148  *	None.
6149  *
6150  *----------------------------------------------------------------------
6151  */
6152 
6153 int
TreeColor_IsOpaque(TreeCtrl * tree,TreeColor * tc)6154 TreeColor_IsOpaque(
6155     TreeCtrl *tree,		/* Widget info. */
6156     TreeColor *tc		/* Color info. */
6157     )
6158 {
6159     if (tc == NULL)
6160 	return 0;
6161     if (tc->gradient != NULL)
6162 	return TreeGradient_IsOpaque(tree, tc->gradient);
6163     return tc->color != NULL;
6164 }
6165 
6166 /*
6167  *----------------------------------------------------------------------
6168  *
6169  * TreeColorCO_Set --
6170  * TreeColorCO_Get --
6171  * TreeColorCO_Restore --
6172  *
6173  *	These procedures implement a TK_OPTION_CUSTOM where the custom
6174  *	option is a TreeColor.
6175  *
6176  * Results:
6177  *	None.
6178  *
6179  * Side effects:
6180  *	None.
6181  *
6182  *----------------------------------------------------------------------
6183  */
6184 
6185 static int
TreeColorCO_Set(ClientData clientData,Tcl_Interp * interp,Tk_Window tkwin,Tcl_Obj ** valuePtr,char * recordPtr,int internalOffset,char * saveInternalPtr,int flags)6186 TreeColorCO_Set(
6187     ClientData clientData,
6188     Tcl_Interp *interp,
6189     Tk_Window tkwin,
6190     Tcl_Obj **valuePtr,
6191     char *recordPtr,
6192     int internalOffset,
6193     char *saveInternalPtr,
6194     int flags
6195     )
6196 {
6197     TreeCtrl *tree = (TreeCtrl *) ((TkWindow *) tkwin)->instanceData;
6198     int objEmpty;
6199     TreeColor **internalPtr, *new;
6200 
6201     if (internalOffset >= 0)
6202 	internalPtr = (TreeColor **) (recordPtr + internalOffset);
6203     else
6204 	internalPtr = NULL;
6205 
6206     objEmpty = ObjectIsEmpty((*valuePtr));
6207 
6208     if ((flags & TK_OPTION_NULL_OK) && objEmpty) {
6209 	(*valuePtr) = NULL;
6210 	new = 0;
6211     } else {
6212 	new = Tree_AllocColorFromObj(tree, *valuePtr);
6213 	if (new == NULL)
6214 	    return TCL_ERROR;
6215     }
6216 
6217     if (internalPtr != NULL) {
6218 	*((TreeColor **) saveInternalPtr) = *internalPtr;
6219 	*internalPtr = new;
6220     }
6221 
6222     return TCL_OK;
6223 }
6224 
6225 static Tcl_Obj *
TreeColorCO_Get(ClientData clientData,Tk_Window tkwin,char * recordPtr,int internalOffset)6226 TreeColorCO_Get(
6227     ClientData clientData,
6228     Tk_Window tkwin,
6229     char *recordPtr,
6230     int internalOffset
6231     )
6232 {
6233     TreeCtrl *tree = (TreeCtrl *) ((TkWindow *) tkwin)->instanceData;
6234     TreeColor **internalPtr = (TreeColor **) (recordPtr + internalOffset);
6235     return TreeColor_ToObj(tree, *internalPtr);
6236 }
6237 
6238 static void
TreeColorCO_Restore(ClientData clientData,Tk_Window tkwin,char * internalPtr,char * saveInternalPtr)6239 TreeColorCO_Restore(
6240     ClientData clientData,
6241     Tk_Window tkwin,
6242     char *internalPtr,
6243     char *saveInternalPtr
6244     )
6245 {
6246     *(TreeColor **) internalPtr = *(TreeColor **) saveInternalPtr;
6247 }
6248 
6249 static void
TreeColorCO_Free(ClientData clientData,Tk_Window tkwin,char * internalPtr)6250 TreeColorCO_Free(
6251     ClientData clientData,
6252     Tk_Window tkwin,
6253     char *internalPtr
6254     )
6255 {
6256     TreeCtrl *tree = (TreeCtrl *) ((TkWindow *) tkwin)->instanceData;
6257     TreeColor *value = *((TreeColor **) internalPtr);
6258     if (value != NULL) {
6259 	Tree_FreeColor(tree, value);
6260 	*((TreeColor **) internalPtr) = NULL;
6261     }
6262 }
6263 
6264 Tk_ObjCustomOption TreeCtrlCO_treecolor =
6265 {
6266     "tree color",
6267     TreeColorCO_Set,
6268     TreeColorCO_Get,
6269     TreeColorCO_Restore,
6270     TreeColorCO_Free,
6271     (ClientData) NULL
6272 };
6273 
6274 /*****/
6275 
6276 /* *** Borrowed some gradient code from TkPath *** */
6277 
6278 static GradientStop *
NewGradientStop(double offset,XColor * color,double opacity)6279 NewGradientStop(
6280     double offset,
6281     XColor *color,
6282     double opacity
6283     )
6284 {
6285     GradientStop *stopPtr;
6286 
6287     stopPtr = (GradientStop *) ckalloc(sizeof(GradientStop));
6288     memset(stopPtr, '\0', sizeof(GradientStop));
6289     stopPtr->offset = offset;
6290     stopPtr->color = color;
6291     stopPtr->opacity = opacity;
6292     return stopPtr;
6293 }
6294 
6295 static GradientStopArray *
NewGradientStopArray(int nstops)6296 NewGradientStopArray(
6297     int nstops
6298     )
6299 {
6300     GradientStopArray *stopArrPtr;
6301     GradientStop **stops;
6302 
6303     stopArrPtr = (GradientStopArray *) ckalloc(sizeof(GradientStopArray));
6304     memset(stopArrPtr, '\0', sizeof(GradientStopArray));
6305 
6306     /* Array of *pointers* to GradientStop. */
6307     stops = (GradientStop **) ckalloc(nstops*sizeof(GradientStop *));
6308     memset(stops, '\0', nstops*sizeof(GradientStop *));
6309     stopArrPtr->nstops = nstops;
6310     stopArrPtr->stops = stops;
6311     return stopArrPtr;
6312 }
6313 
6314 static void
FreeAllStops(GradientStop ** stops,int nstops)6315 FreeAllStops(
6316     GradientStop **stops,
6317     int nstops
6318     )
6319 {
6320     int i;
6321     for (i = 0; i < nstops; i++) {
6322         if (stops[i] != NULL) {
6323             Tk_FreeColor(stops[i]->color);
6324             WFREE(stops[i], GradientStop);
6325         }
6326     }
6327     WCFREE(stops, GradientStop*, nstops);
6328 }
6329 
6330 static void
FreeStopArray(GradientStopArray * stopArrPtr)6331 FreeStopArray(
6332     GradientStopArray *stopArrPtr
6333     )
6334 {
6335     if (stopArrPtr != NULL) {
6336         FreeAllStops(stopArrPtr->stops, stopArrPtr->nstops);
6337         WFREE(stopArrPtr, GradientStopArray);
6338     }
6339 }
6340 
6341 /*
6342  * The stops are a list of stop lists where each stop list is:
6343  *		{offset color ?opacity?}
6344  */
6345 static int
StopsSet(ClientData clientData,Tcl_Interp * interp,Tk_Window tkwin,Tcl_Obj ** value,char * recordPtr,int internalOffset,char * oldInternalPtr,int flags)6346 StopsSet(
6347     ClientData clientData,
6348     Tcl_Interp *interp,		/* Current interp; may be used for errors. */
6349     Tk_Window tkwin,		/* Window for which option is being set. */
6350     Tcl_Obj **value,		/* Pointer to the pointer to the value object.
6351 				 * We use a pointer to the pointer because
6352 				 * we may need to return a value (NULL). */
6353     char *recordPtr,		/* Pointer to storage for the widget record. */
6354     int internalOffset,		/* Offset within *recordPtr at which the
6355 				 internal value is to be stored. */
6356     char *oldInternalPtr,	/* Pointer to storage for the old value. */
6357     int flags			/* Flags for the option, set Tk_SetOptions. */
6358     )
6359 {
6360     char *internalPtr;
6361     int i, nstops, stopLen;
6362     int objEmpty = 0;
6363     Tcl_Obj *valuePtr;
6364     double offset, lastOffset, opacity;
6365     Tcl_Obj **objv;
6366     Tcl_Obj *stopObj;
6367     Tcl_Obj *obj;
6368     XColor *color;
6369     GradientStopArray *new = NULL;
6370 
6371     valuePtr = *value;
6372     if (internalOffset >= 0)
6373 	internalPtr = recordPtr + internalOffset;
6374     else
6375 	internalPtr = NULL;
6376     objEmpty = ObjectIsEmpty(valuePtr);
6377 
6378     if ((flags & TK_OPTION_NULL_OK) && objEmpty) {
6379         valuePtr = NULL;
6380     } else {
6381 
6382         /* Deal with each stop list in turn. */
6383         if (Tcl_ListObjGetElements(interp, valuePtr, &nstops, &objv) != TCL_OK) {
6384             return TCL_ERROR;
6385         }
6386         if (nstops < 2) {
6387 	    FormatResult(interp, "at least 2 stops required, %d given", nstops);
6388 	    return TCL_ERROR;
6389         }
6390         new = NewGradientStopArray(nstops);
6391         lastOffset = 0.0;
6392 
6393         for (i = 0; i < nstops; i++) {
6394             stopObj = objv[i];
6395             if (Tcl_ListObjLength(interp, stopObj, &stopLen) != TCL_OK) {
6396                 goto error;
6397             }
6398             if ((stopLen < 2) || (stopLen > 3)) {
6399                 Tcl_SetObjResult(interp, Tcl_NewStringObj(
6400                         "stop list not {offset color ?opacity?}", -1));
6401                 goto error;
6402             }
6403 	    Tcl_ListObjIndex(interp, stopObj, 0, &obj);
6404 	    if (Tcl_GetDoubleFromObj(interp, obj, &offset) != TCL_OK) {
6405 		goto error;
6406 	    }
6407 	    if ((offset < 0.0) || (offset > 1.0)) {
6408 		Tcl_SetObjResult(interp, Tcl_NewStringObj(
6409 			"stop offsets must be in the range 0.0 to 1.0", -1));
6410 		goto error;
6411 	    }
6412 	    if ((i == 0) && (offset != 0.0)) {
6413 		FormatResult(interp, "first stop offset must be 0.0, got %.4g", offset);
6414 		goto error;
6415 	    }
6416 	    if ((i == nstops - 1) && (offset != 1.0)) {
6417 		FormatResult(interp, "last stop offset must be 1.0, got %.4g", offset);
6418 		goto error;
6419 	    }
6420 	    if (offset < lastOffset) {
6421 		Tcl_SetObjResult(interp, Tcl_NewStringObj(
6422 			"stop offsets must be ordered", -1));
6423 		goto error;
6424 	    }
6425 	    Tcl_ListObjIndex(interp, stopObj, 1, &obj);
6426 	    color = Tk_AllocColorFromObj(interp, Tk_MainWindow(interp), obj);
6427 	    if (color == NULL)
6428 		goto error;
6429 	    if (stopLen == 3) {
6430 		Tcl_ListObjIndex(interp, stopObj, 2, &obj);
6431 		if (Tcl_GetDoubleFromObj(interp, obj, &opacity) != TCL_OK) {
6432 		    goto error;
6433 		}
6434 	    } else {
6435 		opacity = 1.0;
6436 	    }
6437 
6438 	    /* Make new stop. */
6439 	    new->stops[i] = NewGradientStop(offset, color, opacity);
6440 	    lastOffset = offset;
6441         }
6442     }
6443     if (internalPtr != NULL) {
6444         *((GradientStopArray **) oldInternalPtr) = *((GradientStopArray **) internalPtr);
6445         *((GradientStopArray **) internalPtr) = new;
6446     }
6447     return TCL_OK;
6448 
6449 error:
6450     if (new != NULL) {
6451         FreeStopArray(new);
6452     }
6453     return TCL_ERROR;
6454 }
6455 
6456 static void
StopsRestore(ClientData clientData,Tk_Window tkwin,char * internalPtr,char * oldInternalPtr)6457 StopsRestore(
6458     ClientData clientData,
6459     Tk_Window tkwin,
6460     char *internalPtr,		/* Pointer to storage for value. */
6461     char *oldInternalPtr	/* Pointer to old value. */
6462     )
6463 {
6464     *(GradientStopArray **)internalPtr = *(GradientStopArray **)oldInternalPtr;
6465 }
6466 
6467 static void
StopsFree(ClientData clientData,Tk_Window tkwin,char * internalPtr)6468 StopsFree(
6469     ClientData clientData,
6470     Tk_Window tkwin,
6471     char *internalPtr
6472     )
6473 {
6474     if (*((char **) internalPtr) != NULL) {
6475         FreeStopArray(*(GradientStopArray **)internalPtr);
6476     }
6477 }
6478 
6479 static Tk_ObjCustomOption stopsCO =
6480 {
6481     "stops",
6482     StopsSet,
6483     NULL,
6484     StopsRestore,
6485     StopsFree,
6486     (ClientData) NULL
6487 };
6488 
6489 /*****/
6490 
6491 typedef enum {
6492     GCT_AREA = 0,
6493     GCT_CANVAS,
6494     GCT_COLUMN,
6495     GCT_ITEM
6496 } GradientCoordType;
6497 
6498 static const char *coordTypeNames[] = {
6499     "area", "canvas", "column", "item", NULL
6500 };
6501 
6502 struct GradientCoord {
6503     GradientCoordType type;
6504     float value;
6505     TreeColumn column; /* optional arg to GCT_COLUMN */
6506     TreeItem item; /* optional arg to GCT_ITEM */
6507     int area; /* required arg to GCT_AREA */
6508 };
6509 
6510 static int
GradientCoordSet(ClientData clientData,Tcl_Interp * interp,Tk_Window tkwin,Tcl_Obj ** value,char * recordPtr,int internalOffset,char * oldInternalPtr,int flags)6511 GradientCoordSet(
6512     ClientData clientData,
6513     Tcl_Interp *interp,		/* Current interp; may be used for errors. */
6514     Tk_Window tkwin,		/* Window for which option is being set. */
6515     Tcl_Obj **value,		/* Pointer to the pointer to the value object.
6516 				 * We use a pointer to the pointer because
6517 				 * we may need to return a value (NULL). */
6518     char *recordPtr,		/* Pointer to storage for the widget record. */
6519     int internalOffset,		/* Offset within *recordPtr at which the
6520 				 internal value is to be stored. */
6521     char *oldInternalPtr,	/* Pointer to storage for the old value. */
6522     int flags			/* Flags for the option, set Tk_SetOptions. */
6523     )
6524 {
6525     TreeCtrl *tree = (TreeCtrl *) ((TkWindow *) tkwin)->instanceData;
6526     char *internalPtr;
6527     int objEmpty = 0;
6528     Tcl_Obj *valuePtr;
6529     Tcl_Obj **objv;
6530     int objc;
6531     double coordValue;
6532     GradientCoordType coordType;
6533     GradientCoord *new = NULL;
6534     TreeColumn column = NULL;
6535     TreeItem item = NULL;
6536     int area = TREE_AREA_NONE;
6537 
6538     valuePtr = *value;
6539     if (internalOffset >= 0)
6540 	internalPtr = recordPtr + internalOffset;
6541     else
6542 	internalPtr = NULL;
6543     objEmpty = ObjectIsEmpty(valuePtr);
6544 
6545     if ((flags & TK_OPTION_NULL_OK) && objEmpty) {
6546         valuePtr = NULL;
6547     } else {
6548         if (Tcl_ListObjGetElements(interp, valuePtr, &objc, &objv) != TCL_OK) {
6549             return TCL_ERROR;
6550         }
6551         if (objc < 2) {
6552 	    FormatResult(interp, "expected list {offset coordType ?arg ...?}");
6553 	    return TCL_ERROR;
6554         }
6555         if (Tcl_GetIndexFromObj(interp, objv[1], coordTypeNames,
6556 	    "coordinate type", 0, (int *) &coordType) != TCL_OK) {
6557 	    return TCL_ERROR;
6558 	}
6559 	if (Tcl_GetDoubleFromObj(interp, objv[0], &coordValue) != TCL_OK) {
6560 	    return TCL_ERROR;
6561 	}
6562 	if (coordType == GCT_AREA) {
6563 	    if (objc != 3) {
6564 		FormatResult(interp, "wrong # args after \"area\": must be 1");
6565 		return TCL_ERROR;
6566 	    }
6567 	    if (TreeArea_FromObj(tree, objv[2], &area) != TCL_OK) {
6568 		return TCL_ERROR;
6569 	    }
6570 	}
6571 	if (coordType == GCT_COLUMN && objc > 2) {
6572 	    if (objc > 3) {
6573 		FormatResult(interp, "wrong # args after \"column\": must be 0 or 1");
6574 		return TCL_ERROR;
6575 	    }
6576 	    if (TreeColumn_FromObj(tree, objv[2], &column, CFO_NOT_NULL)
6577 		    != TCL_OK) {
6578 		return TCL_ERROR;
6579 	    }
6580 	}
6581 	if (coordType == GCT_ITEM && objc > 2) {
6582 	    if (objc > 3) {
6583 		FormatResult(interp, "wrong # args after \"item\": must be 0 or 1");
6584 		return TCL_ERROR;
6585 	    }
6586 	    if (TreeItem_FromObj(tree, objv[2], &item, IFO_NOT_NULL)
6587 		    != TCL_OK) {
6588 		return TCL_ERROR;
6589 	    }
6590 	}
6591 	new = (GradientCoord *) ckalloc(sizeof(GradientCoord));
6592 	new->type = coordType;
6593 	new->value = (float) coordValue;
6594 	new->column = column;
6595 	new->item = item;
6596 	new->area = area;
6597     }
6598     if (internalPtr != NULL) {
6599         *((GradientCoord **) oldInternalPtr) = *((GradientCoord **) internalPtr);
6600         *((GradientCoord **) internalPtr) = new;
6601     }
6602     return TCL_OK;
6603 }
6604 
6605 static void
GradientCoordRestore(ClientData clientData,Tk_Window tkwin,char * internalPtr,char * oldInternalPtr)6606 GradientCoordRestore(
6607     ClientData clientData,
6608     Tk_Window tkwin,
6609     char *internalPtr,		/* Pointer to storage for value. */
6610     char *oldInternalPtr)	/* Pointer to old value. */
6611 {
6612     *(GradientCoord **)internalPtr = *(GradientCoord **)oldInternalPtr;
6613 }
6614 
6615 static void
GradientCoordFree(ClientData clientData,Tk_Window tkwin,char * internalPtr)6616 GradientCoordFree(
6617     ClientData clientData,
6618     Tk_Window tkwin,
6619     char *internalPtr)
6620 {
6621     if (*((char **) internalPtr) != NULL) {
6622         ckfree(*(char **)internalPtr);
6623     }
6624 }
6625 
6626 static Tk_ObjCustomOption gradientCoordCO =
6627 {
6628     "coordinate",
6629     GradientCoordSet,
6630     NULL,
6631     GradientCoordRestore,
6632     GradientCoordFree,
6633     (ClientData) NULL
6634 };
6635 
6636 /*
6637  *--------------------------------------------------------------
6638  *
6639  * TreeGradient_ColumnDeleted --
6640  *
6641  *	Removes any reference to a column from the coordinates of
6642  *	all gradients.
6643  *
6644  * Results:
6645  *	None.
6646  *
6647  * Side effects:
6648  *	None.
6649  *
6650  *--------------------------------------------------------------
6651  */
6652 
6653 void
TreeGradient_ColumnDeleted(TreeCtrl * tree,TreeColumn column)6654 TreeGradient_ColumnDeleted(
6655     TreeCtrl *tree,		/* Widget info. */
6656     TreeColumn column		/* Column about to be deleted. */
6657     )
6658 {
6659     Tcl_HashEntry *hPtr;
6660     Tcl_HashSearch search;
6661     TreeGradient gradient;
6662 
6663     hPtr = Tcl_FirstHashEntry(&tree->gradientHash, &search);
6664     while (hPtr != NULL) {
6665 	gradient = (TreeGradient) Tcl_GetHashValue(hPtr);
6666 
6667 #define CHECK_GCOORD(GCRD,OBJ) \
6668 	if ((GCRD != NULL) && (GCRD->column == column)) { \
6669 	    ckfree((char *) GCRD); \
6670 	    Tcl_DecrRefCount(OBJ); \
6671 	    GCRD = NULL; \
6672 	    OBJ = NULL; \
6673 	}
6674 	CHECK_GCOORD(gradient->left,   gradient->leftObj);
6675 	CHECK_GCOORD(gradient->right,  gradient->rightObj);
6676 	CHECK_GCOORD(gradient->top,    gradient->topObj);
6677 	CHECK_GCOORD(gradient->bottom, gradient->bottomObj);
6678 #undef CHECK_GCOORD
6679 
6680 	hPtr = Tcl_NextHashEntry(&search);
6681     }
6682 }
6683 
6684 /*
6685  *--------------------------------------------------------------
6686  *
6687  * TreeGradient_ItemDeleted --
6688  *
6689  *	Removes any reference to an item from the coordinates of
6690  *	all gradients.
6691  *
6692  * Results:
6693  *	None.
6694  *
6695  * Side effects:
6696  *	None.
6697  *
6698  *--------------------------------------------------------------
6699  */
6700 
6701 void
TreeGradient_ItemDeleted(TreeCtrl * tree,TreeItem item)6702 TreeGradient_ItemDeleted(
6703     TreeCtrl *tree,		/* Widget info. */
6704     TreeItem item		/* Item about to be deleted. */
6705     )
6706 {
6707     Tcl_HashEntry *hPtr;
6708     Tcl_HashSearch search;
6709     TreeGradient gradient;
6710 
6711     hPtr = Tcl_FirstHashEntry(&tree->gradientHash, &search);
6712     while (hPtr != NULL) {
6713 	gradient = (TreeGradient) Tcl_GetHashValue(hPtr);
6714 
6715 #define CHECK_GCOORD(GCRD,OBJ) \
6716 	if ((GCRD != NULL) && (GCRD->item == item)) { \
6717 	    ckfree((char *) GCRD); \
6718 	    Tcl_DecrRefCount(OBJ); \
6719 	    GCRD = NULL; \
6720 	    OBJ = NULL; \
6721 	}
6722 	CHECK_GCOORD(gradient->left,   gradient->leftObj);
6723 	CHECK_GCOORD(gradient->right,  gradient->rightObj);
6724 	CHECK_GCOORD(gradient->top,    gradient->topObj);
6725 	CHECK_GCOORD(gradient->bottom, gradient->bottomObj);
6726 #undef CHECK_GCOORD
6727 
6728 	hPtr = Tcl_NextHashEntry(&search);
6729     }
6730 }
6731 
6732 static TreeColumn
FindNthVisibleColumn(TreeCtrl * tree,TreeColumn column,int * n)6733 FindNthVisibleColumn(
6734     TreeCtrl *tree,
6735     TreeColumn column,
6736     int *n
6737     )
6738 {
6739     int index = TreeColumn_Index(column);
6740     TreeColumn column2 = column, column3 = column;
6741 
6742     if ((*n) > 0) {
6743 	while ((*n) > 0 && ++index < tree->columnCount) {
6744 	    column3 = TreeColumn_Next(column3);
6745 	    if (TreeColumn_Visible(column3)) {
6746 		column2 = column3;
6747 		(*n) -= 1;
6748 	    }
6749 	}
6750     } else {
6751 	while ((*n) < 0 && --index >= 0) {
6752 	    column3 = TreeColumn_Prev(column3);
6753 	    if (TreeColumn_Visible(column3)) {
6754 		column2 = column3;
6755 		(*n) += 1;
6756 	    }
6757 	}
6758     }
6759     return column2;
6760 }
6761 
6762 static int
GetGradientBrushCoordX(TreeCtrl * tree,GradientCoord * gcrd,TreeColumn column,TreeItem item,int * xPtr)6763 GetGradientBrushCoordX(
6764     TreeCtrl *tree,			/* Widget Info. */
6765     GradientCoord *gcrd,		/* Left or right coordinate. */
6766     TreeColumn column,			/* Column or NULL. */
6767     TreeItem item,			/* Item or NULL. */
6768     int *xPtr				/* Returned canvas coordinate.
6769 					 * Will remain unchanged if the
6770 					 * gradient coordinate cannot be
6771 					 * resolved. */
6772     )
6773 {
6774     if (gcrd == NULL)
6775 	return 0;
6776 
6777     switch (gcrd->type) {
6778 	case GCT_AREA: {
6779 	    TreeRectangle tr;
6780 	    if (Tree_AreaBbox(tree, gcrd->area, &tr) == TRUE) {
6781 		(*xPtr) = (int) (TreeRect_Left(tr) + TreeRect_Width(tr) * gcrd->value);
6782 		(*xPtr) += tree->xOrigin; /* Window -> Canvas */
6783 		return 1;
6784 	    }
6785 	    break;
6786 	}
6787 
6788 	case GCT_CANVAS: {
6789 	    int canvasWidth = Tree_FakeCanvasWidth(tree);
6790 	    (*xPtr) = (int) (canvasWidth * gcrd->value);
6791 	    return 1;
6792 	}
6793 
6794 /* FIXME: if item != NULL then make column offset relative to item */
6795 	case GCT_COLUMN:
6796 	    if (gcrd->column != NULL)
6797 		column = gcrd->column;
6798 	    if (column != NULL) {
6799 		int x, w;
6800 		if (gcrd->value < 0.0) {
6801 		    int n = (int) ceil(-gcrd->value);
6802 		    n = -n; /* backwards */
6803 		    column = FindNthVisibleColumn(tree, column, &n);
6804 		    if (TreeColumn_Visible(column)) {
6805 			double tmp, frac;
6806 			if ((n < 0) || (frac = modf(-gcrd->value, &tmp)) == 0.0)
6807 			    frac = 1.0;
6808 			x = TreeColumn_Offset(column);
6809 			w = TreeColumn_UseWidth(column);
6810 			(*xPtr) = (int) (x + w * (1.0 - frac));
6811 			return 1;
6812 		    }
6813 		} else if (gcrd->value > 1.0) {
6814 		    int n = (int) ceil(gcrd->value - 1.0);
6815 		    column = FindNthVisibleColumn(tree, column, &n);
6816 		    if (TreeColumn_Visible(column)) {
6817 			double tmp, frac;
6818 			if ((n > 0) || (frac = modf(gcrd->value, &tmp)) == 0.0)
6819 			    frac = 1.0;
6820 			x = TreeColumn_Offset(column);
6821 			w = TreeColumn_UseWidth(column);
6822 			(*xPtr) = (int) (x + w * frac);
6823 			return 1;
6824 		    }
6825 		} else {
6826 		    if (TreeColumn_Visible(column)) {
6827 			x = TreeColumn_Offset(column);
6828 			w = TreeColumn_UseWidth(column);
6829 			(*xPtr) = (int) (x + w * gcrd->value);
6830 			return 1;
6831 		    }
6832 		}
6833 	    }
6834 	    break;
6835 
6836 	case GCT_ITEM:
6837 	    if (gcrd->item != NULL)
6838 		item = gcrd->item;
6839 	    if (item != NULL) {
6840 		TreeRectangle tr;
6841 		int lock;
6842 		if (tree->columnCountVis > 0)
6843 		    lock = COLUMN_LOCK_NONE;
6844 		else if (tree->columnCountVisLeft > 0)
6845 		    lock = COLUMN_LOCK_LEFT;
6846 		else if (tree->columnCountVisRight > 0)
6847 		    lock = COLUMN_LOCK_RIGHT;
6848 		else
6849 		    break; /* not possible else wouldn't be drawing */
6850 		if (gcrd->value < 0.0) {
6851 		    int clip = 0, row, col, row2, col2;
6852 		    if (Tree_ItemToRNC(tree, item, &row, &col) == TCL_OK) {
6853 			int n = (int) ceil(-gcrd->value);
6854 			TreeItem item2 = Tree_RNCToItem(tree, row, col - n);
6855 			(void) Tree_ItemToRNC(tree, item2, &row2, &col2);
6856 			if (row2 == row)
6857 			    item = item2; /* there is an item to the left */
6858 			if (row2 != row || col2 != col - n)
6859 			    clip = 1; /* no item 'n' columns to the left */
6860 		    }
6861 		    if (Tree_ItemBbox(tree, item, lock, &tr) != -1) {
6862 			double tmp, frac;
6863 			if (clip || (frac = modf(-gcrd->value, &tmp)) == 0.0)
6864 			    frac = 1.0;
6865 			(*xPtr) = (int) (tr.x + tr.width * (1.0 - frac));
6866 			return 1;
6867 		    }
6868 		} else if (gcrd->value > 1.0) {
6869 		    int clip = 0, row, col, row2, col2;
6870 		    if (Tree_ItemToRNC(tree, item, &row, &col) == TCL_OK) {
6871 			int n = (int) ceil(gcrd->value - 1.0);
6872 			TreeItem item2 = Tree_RNCToItem(tree, row, col + n);
6873 			(void) Tree_ItemToRNC(tree, item2, &row2, &col2);
6874 			if (row2 == row)
6875 			    item = item2; /* there is an item to the right */
6876 			if (row2 != row || col2 != col + n)
6877 			    clip = 1; /* no item 'n' columns to the right */
6878 		    }
6879 		    if (Tree_ItemBbox(tree, item, lock, &tr) != -1) {
6880 			double tmp, frac;
6881 			if (clip || (frac = modf(gcrd->value, &tmp)) == 0.0)
6882 			    frac = 1.0;
6883 			(*xPtr) = (int) (tr.x + tr.width * frac);
6884 			return 1;
6885 		    }
6886 		} else if (Tree_ItemBbox(tree, item, lock, &tr) != -1) {
6887 		    (*xPtr) = (int) (tr.x + tr.width * gcrd->value);
6888 		    return 1;
6889 		}
6890 	    }
6891 	    break;
6892     }
6893     return 0;
6894 }
6895 
6896 static int
GetGradientBrushCoordY(TreeCtrl * tree,GradientCoord * gcrd,TreeColumn column,TreeItem item,int * yPtr)6897 GetGradientBrushCoordY(
6898     TreeCtrl *tree,			/* Widget Info. */
6899     GradientCoord *gcrd,		/* Top or bottom coordinate. */
6900     TreeColumn column,			/* Column or NULL. */
6901     TreeItem item,			/* Item or NULL. */
6902     int *yPtr				/* Returned canvas coordinate.
6903 					 * Will remain unchanged if the
6904 					 * gradient coordinate cannot be
6905 					 * resolved. */
6906     )
6907 {
6908     if (gcrd == NULL)
6909 	return 0;
6910 
6911     switch (gcrd->type) {
6912 	case GCT_AREA: {
6913 	    TreeRectangle tr;
6914 	    if (Tree_AreaBbox(tree, gcrd->area, &tr) == TRUE) {
6915 		(*yPtr) = (int) (TreeRect_Top(tr) + TreeRect_Height(tr) * gcrd->value);
6916 		(*yPtr) += tree->yOrigin; /* Window -> Canvas */
6917 		return 1;
6918 	    }
6919 	    break;
6920 	}
6921 
6922 	case GCT_CANVAS: {
6923 	    int canvasHeight = Tree_FakeCanvasHeight(tree);
6924 	    (*yPtr) = (int) (canvasHeight * gcrd->value);
6925 	    return 1;
6926 	}
6927 
6928 	case GCT_COLUMN:
6929 	    /* Can't think of any use for this */
6930 	    break;
6931 
6932 	case GCT_ITEM:
6933 	    if (gcrd->item != NULL)
6934 		item = gcrd->item;
6935 	    if (item != NULL) {
6936 		TreeRectangle tr;
6937 		int lock;
6938 		if (tree->columnCountVis > 0)
6939 		    lock = COLUMN_LOCK_NONE;
6940 		else if (tree->columnCountVisLeft > 0)
6941 		    lock = COLUMN_LOCK_LEFT;
6942 		else if (tree->columnCountVisRight > 0)
6943 		    lock = COLUMN_LOCK_RIGHT;
6944 		else
6945 		    break; /* not possible else wouldn't be drawing */
6946 		if (gcrd->value < 0.0) {
6947 		    int clip = 0, row, col, row2, col2;
6948 		    if (Tree_ItemToRNC(tree, item, &row, &col) == TCL_OK) {
6949 			int n = (int) ceil(-gcrd->value);
6950 			TreeItem item2 = Tree_RNCToItem(tree, row - n, col);
6951 			(void) Tree_ItemToRNC(tree, item2, &row2, &col2);
6952 			if (col2 == col)
6953 			    item = item2; /* there is an item above */
6954 			if (row2 != row - n || col2 != col)
6955 			    clip = 1; /* no item 'n' rows above */
6956 		    }
6957 		    if (Tree_ItemBbox(tree, item, lock, &tr) != -1) {
6958 			double tmp, frac;
6959 			if (clip || (frac = modf(-gcrd->value, &tmp)) == 0.0)
6960 			    frac = 1.0;
6961 			(*yPtr) = (int) (tr.y + tr.height * (1.0 - frac));
6962 			return 1;
6963 		    }
6964 		} else if (gcrd->value > 1.0) {
6965 		    int clip = 0, row, col, row2, col2;
6966 		    if (Tree_ItemToRNC(tree, item, &row, &col) == TCL_OK) {
6967 			int n = (int) ceil(gcrd->value - 1.0);
6968 			TreeItem item2 = Tree_RNCToItem(tree, row + n, col);
6969 			(void) Tree_ItemToRNC(tree, item2, &row2, &col2);
6970 			if (col2 == col)
6971 			    item = item2; /* there is an item below */
6972 			if (row2 != row + n || col2 != col)
6973 			    clip = 1; /* no item 'n' rows below */
6974 		    }
6975 		    if (Tree_ItemBbox(tree, item, lock, &tr) != -1) {
6976 			double tmp, frac;
6977 			if (clip || (frac = modf(gcrd->value, &tmp)) == 0.0)
6978 			    frac = 1.0;
6979 			(*yPtr) = (int) (tr.y + tr.height * frac);
6980 			return 1;
6981 		    }
6982 		} else if (Tree_ItemBbox(tree, item, lock, &tr) != -1) {
6983 		    (*yPtr) = (int) (tr.y + tr.height * gcrd->value);
6984 		    return 1;
6985 		}
6986 	    }
6987 	    break;
6988     }
6989     return 0;
6990 }
6991 
6992 /*
6993  *--------------------------------------------------------------
6994  *
6995  * TreeGradient_GetBrushBounds --
6996  *
6997  *	Returns the bounds of the brush that should be used to
6998  *	draw a gradient.
6999  *
7000  * Results:
7001  *	Return 1 if the brush size is non-empty, 0 otherwise.
7002  *
7003  * Side effects:
7004  *	May recalculate item/column layout info if it is out-of-date.
7005  *
7006  *--------------------------------------------------------------
7007  */
7008 
7009 int
TreeGradient_GetBrushBounds(TreeCtrl * tree,TreeGradient gradient,const TreeRectangle * trPaint,TreeRectangle * trBrush,TreeColumn column,TreeItem item)7010 TreeGradient_GetBrushBounds(
7011     TreeCtrl *tree,			/* Widget Info. */
7012     TreeGradient gradient,		/* Gradient token. */
7013     const TreeRectangle *trPaint,	/* Area to paint, canvas coords. */
7014     TreeRectangle *trBrush,		/* Returned brush bounds. */
7015     TreeColumn column,			/* Column or NULL. */
7016     TreeItem item			/* Item or NULL. */
7017     )
7018 {
7019     int x1, y1, x2, y2;
7020 
7021     x1 = trPaint->x;
7022     y1 = trPaint->y;
7023     x2 = trPaint->x + trPaint->width;
7024     y2 = trPaint->y + trPaint->height;
7025 
7026     /* FIXME: If an item's or column's visibility changes, may need to redraw */
7027 
7028    (void) GetGradientBrushCoordX(tree, gradient->left, column, item, &x1);
7029    (void) GetGradientBrushCoordX(tree, gradient->right, column, item, &x2);
7030    (void) GetGradientBrushCoordY(tree, gradient->top, column, item, &y1);
7031    (void) GetGradientBrushCoordY(tree, gradient->bottom, column, item, &y2);
7032 
7033     trBrush->x = x1;
7034     trBrush->y = y1;
7035     trBrush->width = x2 - x1;
7036     trBrush->height = y2 - y1;
7037 
7038     return trBrush->width > 0 && trBrush->height > 0;
7039 }
7040 
7041 /*
7042  *--------------------------------------------------------------
7043  *
7044  * TreeGradient_IsRelativeToCanvas --
7045  *
7046  *	Returns true if the gradient brush is relative to the
7047  *	canvas along the horizontal and vertical axes.
7048  *
7049  * Results:
7050  *	None.
7051  *
7052  * Side effects:
7053  *	None.
7054  *
7055  *--------------------------------------------------------------
7056  */
7057 
7058 void
TreeGradient_IsRelativeToCanvas(TreeGradient gradient,int * relX,int * relY)7059 TreeGradient_IsRelativeToCanvas(
7060     TreeGradient gradient,
7061     int *relX,
7062     int *relY
7063     )
7064 {
7065     (*relX) = (*relY) = 1;
7066 
7067     if (gradient->vertical == 0 &&
7068 	    ((gradient->left != NULL &&
7069 	    gradient->left->type == GCT_AREA) ||
7070 	    (gradient->right != NULL &&
7071 	    gradient->right->type == GCT_AREA)))
7072 	(*relX) = 0;
7073 
7074     if (gradient->vertical==1 &&
7075 	    ((gradient->top != NULL &&
7076 	    gradient->top->type == GCT_AREA) ||
7077 	    (gradient->bottom != NULL &&
7078 	    gradient->bottom->type == GCT_AREA)))
7079 	(*relY) = 0;
7080 }
7081 
7082 /*
7083  *--------------------------------------------------------------
7084  *
7085  * TreeColor_GetBrushBounds --
7086  *
7087  *	Determines the bounds of the gradient brush.
7088  *
7089  * Results:
7090  *	None.
7091  *
7092  * Side effects:
7093  *	May mark an item as needing to be redrawn when scrolling
7094  *	occurs if the gradient brush isn't relative to the canvas.
7095  *
7096  *--------------------------------------------------------------
7097  */
7098 
7099 void
TreeColor_GetBrushBounds(TreeCtrl * tree,TreeColor * tc,TreeRectangle trPaint,int xOrigin,int yOrigin,TreeColumn column,TreeItem item,TreeRectangle * trBrush)7100 TreeColor_GetBrushBounds(
7101     TreeCtrl *tree,		/* Widget info. */
7102     TreeColor *tc,		/* Color or gradient. */
7103     TreeRectangle trPaint,	/* Area to be painted. */
7104     int xOrigin,		/* Offset of trPaint from canvas. */
7105     int yOrigin,
7106     TreeColumn column,		/* Column trPaint is in, or NULL. */
7107     TreeItem item,		/* Item trPaint is in, or NULL. */
7108     TreeRectangle *trBrush	/* Returned brush bounds. */
7109     )
7110 {
7111     int relX, relY;
7112 
7113     if (tc->gradient != NULL) {
7114 
7115 	trPaint.x += xOrigin;
7116 	trPaint.y += yOrigin;
7117 	(void) TreeGradient_GetBrushBounds(tree, tc->gradient, &trPaint,
7118 	    trBrush, column, item);
7119 	trBrush->x -= xOrigin;
7120 	trBrush->y -= yOrigin;
7121 
7122 	if (item != NULL) {
7123 	    TreeGradient_IsRelativeToCanvas(tc->gradient, &relX, &relY);
7124 	    if (relX == 0)
7125 		Tree_InvalidateItemOnScrollX(tree, item);
7126 	    if (relY == 0)
7127 		Tree_InvalidateItemOnScrollY(tree, item);
7128 	}
7129     } else {
7130 	*trBrush = trPaint;
7131     }
7132 }
7133 
7134 /*****/
7135 
7136 #define GRAD_CONF_STOPS 0x0001
7137 #define GRAD_CONF_STEPS 0x0002
7138 
7139 static char *orientStringTable[] = { "horizontal", "vertical", (char *) NULL };
7140 
7141 static Tk_OptionSpec gradientOptionSpecs[] = {
7142     {TK_OPTION_CUSTOM, "-bottom", NULL, NULL, NULL,
7143 	Tk_Offset(TreeGradient_, bottomObj),
7144         Tk_Offset(TreeGradient_, bottom),
7145 	TK_OPTION_NULL_OK, (ClientData) &gradientCoordCO, 0},
7146     {TK_OPTION_CUSTOM, "-left", NULL, NULL, NULL,
7147 	Tk_Offset(TreeGradient_, leftObj),
7148         Tk_Offset(TreeGradient_, left),
7149 	TK_OPTION_NULL_OK, (ClientData) &gradientCoordCO, 0},
7150     {TK_OPTION_CUSTOM, "-right", NULL, NULL, NULL,
7151 	Tk_Offset(TreeGradient_, rightObj),
7152         Tk_Offset(TreeGradient_, right),
7153 	TK_OPTION_NULL_OK, (ClientData) &gradientCoordCO, 0},
7154     {TK_OPTION_CUSTOM, "-top", NULL, NULL, NULL,
7155 	Tk_Offset(TreeGradient_, topObj),
7156         Tk_Offset(TreeGradient_, top),
7157 	TK_OPTION_NULL_OK, (ClientData) &gradientCoordCO, 0},
7158     {TK_OPTION_STRING_TABLE, "-orient", (char *) NULL, (char *) NULL,
7159 	"horizontal", -1, Tk_Offset(TreeGradient_, vertical),
7160 	0, (ClientData) orientStringTable, 0},
7161     {TK_OPTION_INT, "-steps", (char *) NULL, (char *) NULL,
7162 	"1", -1, Tk_Offset(TreeGradient_, steps),
7163 	0, (ClientData) NULL, GRAD_CONF_STEPS},
7164     {TK_OPTION_CUSTOM, "-stops", NULL, NULL, NULL,
7165 	Tk_Offset(TreeGradient_, stopsObj),
7166         Tk_Offset(TreeGradient_, stopArrPtr),
7167 	TK_OPTION_NULL_OK, (ClientData) &stopsCO, GRAD_CONF_STOPS},
7168     {TK_OPTION_END, (char *) NULL, (char *) NULL, (char *) NULL,
7169 	(char *) NULL, 0, -1, 0, (ClientData) NULL, 0}
7170 };
7171 
7172 /*
7173  *----------------------------------------------------------------------
7174  *
7175  * TreeGradient_FromObj --
7176  *
7177  *	Convert a Tcl_Obj to a TreeGradient.
7178  *
7179  * Results:
7180  *	A standard Tcl result.
7181  *
7182  * Side effects:
7183  *	None.
7184  *
7185  *----------------------------------------------------------------------
7186  */
7187 
7188 int
TreeGradient_FromObj(TreeCtrl * tree,Tcl_Obj * obj,TreeGradient * gradientPtr)7189 TreeGradient_FromObj(
7190     TreeCtrl *tree,		/* Widget info. */
7191     Tcl_Obj *obj,		/* Object to convert from. */
7192     TreeGradient *gradientPtr)	/* Returned gradient token. */
7193 {
7194     char *name;
7195     Tcl_HashEntry *hPtr;
7196 
7197     name = Tcl_GetString(obj);
7198     hPtr = Tcl_FindHashEntry(&tree->gradientHash, name);
7199     if (hPtr != NULL) {
7200 	(*gradientPtr) = (TreeGradient) Tcl_GetHashValue(hPtr);
7201     }
7202     if ((hPtr == NULL) || ((*gradientPtr)->deletePending != 0)) {
7203 	Tcl_AppendResult(tree->interp, "gradient \"", name, "\" doesn't exist",
7204 	    NULL);
7205 	return TCL_ERROR;
7206     }
7207     return TCL_OK;
7208 }
7209 
7210 /*
7211  *----------------------------------------------------------------------
7212  *
7213  * Gradient_ToObj --
7214  *
7215  *	Create a new Tcl_Obj representing a gradient.
7216  *
7217  * Results:
7218  *	A Tcl_Obj.
7219  *
7220  * Side effects:
7221  *	Memory is allocated.
7222  *
7223  *----------------------------------------------------------------------
7224  */
7225 
7226 static Tcl_Obj *
Gradient_ToObj(TreeGradient gradient)7227 Gradient_ToObj(
7228     TreeGradient gradient	/* Gradient to create Tcl_Obj from. */
7229     )
7230 {
7231     return Tcl_NewStringObj(gradient->name, -1);
7232 }
7233 
7234 /*
7235  *----------------------------------------------------------------------
7236  *
7237  * CalcStepColors --
7238  *
7239  *	Calculates the colors between (and including) 2 color stops.
7240  *	These colors are only used when native gradients aren't
7241  *	supported.
7242  *
7243  * Results:
7244  *	'stepColors' is filled in with exactly 'step's XColors.
7245  *
7246  * Side effects:
7247  *	Colors are allocated.
7248  *
7249  *----------------------------------------------------------------------
7250  */
7251 
7252 static void
CalcStepColors(TreeCtrl * tree,GradientStop * stop1,GradientStop * stop2,XColor * stepColors[],int steps)7253 CalcStepColors(
7254     TreeCtrl *tree,		/* Widget info. */
7255     GradientStop *stop1,	/* The first color stop. */
7256     GradientStop *stop2,	/* The second color stop. */
7257     XColor *stepColors[],	/* Returned colors. */
7258     int steps			/* Number of colors to allocate. */
7259     )
7260 {
7261     int i;
7262     XColor *c1 = stop1->color, *c2 = stop2->color;
7263 
7264     if (steps == 1) {
7265 	stepColors[0] = Tk_GetColorByValue(tree->tkwin,
7266 		(stop1->offset > 0) ? stop2->color : stop1->color);
7267 	return;
7268     }
7269 
7270 #undef CLAMP
7271 #define CLAMP(a,b,c) (((a)<(b))?(b):(((a)>(c))?(c):(a)))
7272 
7273     for (i = 1; i <= steps; i++) {
7274 	XColor pref;
7275 	int range, increment;
7276 	double factor = (i - 1) * 1.0f / (steps - 1);
7277 
7278 	range = (c2->red - c1->red);
7279 	increment = (int)(range * factor);
7280 	pref.red = CLAMP(c1->red + increment, 0, USHRT_MAX);
7281 
7282 	range = (c2->green - c1->green);
7283 	increment = (int)(range * factor);
7284 	pref.green = CLAMP(c1->green + increment, 0, USHRT_MAX);
7285 
7286 	range = (c2->blue - c1->blue);
7287 	increment = (int)(range * factor);
7288 	pref.blue = CLAMP(c1->blue + increment, 0, USHRT_MAX);
7289 
7290 	stepColors[i - 1] = Tk_GetColorByValue(tree->tkwin, &pref);
7291     }
7292 #undef CLAMP
7293 }
7294 
7295 /*
7296  *----------------------------------------------------------------------
7297  *
7298  * Gradient_CalcStepColors --
7299  *
7300  *	Calculates the entire list of step colors.
7301  *	These colors are only used when native gradients aren't
7302  *	supported.
7303  *
7304  * Results:
7305  *	The TreeGradient.stepColors array is written with exactly
7306  *	#steps X #stops XColors.
7307  *
7308  * Side effects:
7309  *	Colors are allocated.
7310  *
7311  *----------------------------------------------------------------------
7312  */
7313 
7314 #ifdef TREECTRL_DEBUG
sameXColor(XColor * c1,XColor * c2)7315 int sameXColor(XColor *c1, XColor *c2)
7316 {
7317     return c1->red == c2->red && c1->green==c2->green && c1->blue==c2->blue;
7318 }
7319 #endif
7320 
7321 static void
Gradient_CalcStepColors(TreeCtrl * tree,TreeGradient gradient)7322 Gradient_CalcStepColors(
7323     TreeCtrl *tree,		/* Widget info. */
7324     TreeGradient gradient	/* Gradient token. */
7325     )
7326 {
7327     int i, step1, step2;
7328 
7329     for (i = 0; i < gradient->stopArrPtr->nstops - 1; i++) {
7330 	GradientStop *stop1 = gradient->stopArrPtr->stops[i];
7331 	GradientStop *stop2 = gradient->stopArrPtr->stops[i+1];
7332 	step1 = (int)floor(stop1->offset * (gradient->nStepColors));
7333 	step2 = (int)floor(stop2->offset * (gradient->nStepColors))-1;
7334 /*dbwin("CalcStepColors %g -> %g steps=%d-%d\n", stop1->offset,stop2->offset,step1,step2);*/
7335 	CalcStepColors(tree, stop1, stop2, gradient->stepColors + step1,
7336 		step2 - step1 + 1);
7337    }
7338 #ifdef TREECTRL_DEBUG
7339     if (!sameXColor(gradient->stepColors[0], gradient->stopArrPtr->stops[0]->color))
7340 	dbwin("stepColors[0] not same");
7341     if (!sameXColor(gradient->stepColors[gradient->nStepColors - 1], gradient->stopArrPtr->stops[gradient->stopArrPtr->nstops - 1]->color))
7342 	dbwin("stepColors[end-1] not same");
7343 #endif
7344 }
7345 
7346 /*
7347  *----------------------------------------------------------------------
7348  *
7349  * Gradient_Config --
7350  *
7351  *	This procedure is called to process an objc/objv list to set
7352  *	configuration options for a TreeGradient.
7353  *
7354  * Results:
7355  *	The return value is a standard Tcl result.  If TCL_ERROR is
7356  *	returned, then an error message is left in interp's result.
7357  *
7358  * Side effects:
7359  *	Configuration information, such as text string, colors, font,
7360  *	etc. get set for gradient;  old resources get freed, if there
7361  *	were any.  Display changes may occur.
7362  *
7363  *----------------------------------------------------------------------
7364  */
7365 
7366 static int
Gradient_Config(TreeCtrl * tree,TreeGradient gradient,int objc,Tcl_Obj * CONST objv[],int createFlag)7367 Gradient_Config(
7368     TreeCtrl *tree,		/* Widget info. */
7369     TreeGradient gradient,	/* Gradient token. */
7370     int objc,			/* Number of arguments. */
7371     Tcl_Obj *CONST objv[],	/* Argument values. */
7372     int createFlag		/* TRUE if the gradient is being created. */
7373     )
7374 {
7375     TreeGradient_ saved;
7376     Tk_SavedOptions savedOptions;
7377     int error;
7378     Tcl_Obj *errorResult = NULL;
7379     int mask, maskFree = 0;
7380 
7381     saved.nStepColors = 0;
7382     saved.stepColors = NULL;
7383 
7384     for (error = 0; error <= 1; error++) {
7385 	if (error == 0) {
7386 	    if (Tk_SetOptions(tree->interp, (char *) gradient,
7387 			tree->gradientOptionTable,
7388 			objc, objv, tree->tkwin,
7389 			&savedOptions, &mask) != TCL_OK) {
7390 		mask = 0;
7391 		continue;
7392 	    }
7393 
7394 	    /* Wouldn't have to do this if Tk_InitOptions() would return
7395 	     * a mask of configured options like Tk_SetOptions() does. */
7396 	    if (createFlag) {
7397 		mask |= GRAD_CONF_STOPS | GRAD_CONF_STEPS;
7398 	    }
7399 
7400 	    /*
7401 	     * Step 1: Save old values
7402 	     */
7403 
7404 	    if (mask & (GRAD_CONF_STOPS | GRAD_CONF_STEPS)) {
7405 		saved.nStepColors = gradient->nStepColors;
7406 		saved.stepColors = gradient->stepColors;
7407 	    }
7408 
7409 	    /*
7410 	     * Step 2: Process new values
7411 	     */
7412 
7413 	    if (mask & (GRAD_CONF_STOPS | GRAD_CONF_STEPS)) {
7414 		if (gradient->steps < 1 || gradient->steps > 25) {
7415 		    FormatResult(tree->interp, "steps must be >= 1 and <= 25");
7416 		    continue;
7417 		}
7418 		if ((gradient->stopArrPtr != NULL) &&
7419 			(gradient->stopArrPtr->nstops > 0)) {
7420 		    gradient->nStepColors = gradient->stopArrPtr->nstops * gradient->steps;
7421 		    gradient->stepColors = (XColor **)ckalloc(sizeof(XColor*)*gradient->nStepColors);
7422 		    Gradient_CalcStepColors(tree, gradient);
7423 		    maskFree |= GRAD_CONF_STEPS;
7424 		} else {
7425 		    gradient->nStepColors = 0;
7426 		    gradient->stepColors = NULL;
7427 		}
7428 	    }
7429 
7430 	    /*
7431 	     * Step 3: Free saved values
7432 	     */
7433 
7434 	    if (mask & (GRAD_CONF_STOPS | GRAD_CONF_STEPS)) {
7435 		if (saved.stepColors != NULL) { /* will be NULL when createFlag==1 */
7436 		    int i;
7437 		    for (i = 0; i < saved.nStepColors; i++) {
7438 			Tk_FreeColor(saved.stepColors[i]);
7439 		    }
7440 		    WCFREE(saved.stepColors, XColor*, saved.nStepColors);
7441 		}
7442 	    }
7443 
7444 	    Tk_FreeSavedOptions(&savedOptions);
7445 	    break;
7446 	} else {
7447 	    errorResult = Tcl_GetObjResult(tree->interp);
7448 	    Tcl_IncrRefCount(errorResult);
7449 	    Tk_RestoreSavedOptions(&savedOptions);
7450 
7451 	    /*
7452 	     * Free new values.
7453 	     */
7454 
7455 	    if (maskFree & (GRAD_CONF_STOPS | GRAD_CONF_STEPS)) {
7456 		if (gradient->stepColors != NULL) {
7457 		    int i;
7458 		    for (i = 0; i < gradient->nStepColors; i++) {
7459 			Tk_FreeColor(gradient->stepColors[i]);
7460 		    }
7461 		    WCFREE(gradient->stepColors, XColor*, gradient->nStepColors);
7462 		}
7463 	    }
7464 
7465 	    /*
7466 	     * Restore old values.
7467 	     */
7468 	    if (mask & (GRAD_CONF_STOPS | GRAD_CONF_STEPS)) {
7469 		gradient->nStepColors = saved.nStepColors;
7470 		gradient->stepColors = saved.stepColors;
7471 	    }
7472 
7473 	    Tcl_SetObjResult(tree->interp, errorResult);
7474 	    Tcl_DecrRefCount(errorResult);
7475 	    return TCL_ERROR;
7476 	}
7477     }
7478     return TCL_OK;
7479 }
7480 
7481 /*
7482  *----------------------------------------------------------------------
7483  *
7484  * Gradient_FreeResources --
7485  *
7486  *	Free memory etc associated with a TreeGradient.
7487  *
7488  * Results:
7489  *	None.
7490  *
7491  * Side effects:
7492  *	Memory is deallocated.
7493  *
7494  *----------------------------------------------------------------------
7495  */
7496 
7497 static void
Gradient_FreeResources(TreeCtrl * tree,TreeGradient gradient,int deleting)7498 Gradient_FreeResources(
7499     TreeCtrl *tree,		/* Widget info. */
7500     TreeGradient gradient,	/* Gradient token. */
7501     int deleting		/* TRUE if deleting the gradient,
7502 				   FALSE if createing a new gradient to
7503 				   replace a deletePending gradient. */
7504     )
7505 {
7506     Tcl_HashEntry *hPtr;
7507     int i;
7508 
7509     Tk_FreeConfigOptions((char *) gradient,
7510 	tree->gradientOptionTable,
7511 	tree->tkwin);
7512 
7513     if (gradient->stepColors != NULL) {
7514 	for (i = 0; i < gradient->nStepColors; i++)
7515 	    Tk_FreeColor(gradient->stepColors[i]);
7516 	WCFREE(gradient->stepColors, XColor*, gradient->nStepColors);
7517     }
7518 
7519     if (deleting == 0)
7520 	return;
7521 
7522     hPtr = Tcl_FindHashEntry(&tree->gradientHash, gradient->name);
7523     if (hPtr) /* NULL during creation */
7524 	Tcl_DeleteHashEntry(hPtr);
7525 
7526     WFREE(gradient, TreeGradient_);
7527 }
7528 
7529 /*
7530  *----------------------------------------------------------------------
7531  *
7532  * TreeGradient_Release --
7533  *
7534  *	Decrease refCount on a gradient and free it if deletion was
7535  *	pending.
7536  *
7537  * Results:
7538  *	None.
7539  *
7540  * Side effects:
7541  *	Memory may be deallocated.
7542  *
7543  *----------------------------------------------------------------------
7544  */
7545 
7546 void
TreeGradient_Release(TreeCtrl * tree,TreeGradient gradient)7547 TreeGradient_Release(
7548     TreeCtrl *tree,		/* Widget info. */
7549     TreeGradient gradient	/* Gradient token. */
7550     )
7551 {
7552 #ifdef TREECTRL_DEBUG
7553     if (gradient->refCount <= 0)
7554 	panic("TreeGradient_Release: refCount <= 0");
7555 #endif
7556     gradient->refCount--;
7557     if ((gradient->refCount == 0) && (gradient->deletePending != 0)) {
7558 	Gradient_FreeResources(tree, gradient, 1);
7559     }
7560 }
7561 
7562 /*
7563  *----------------------------------------------------------------------
7564  *
7565  * Gradient_CreateAndConfig --
7566  *
7567  *	Allocate and initialize a gradient.
7568  *
7569  * Results:
7570  *	Pointer to the new Gradient, or NULL if an error occurs.
7571  *
7572  * Side effects:
7573  *	Memory is allocated.
7574  *
7575  *----------------------------------------------------------------------
7576  */
7577 
7578 static TreeGradient
Gradient_CreateAndConfig(TreeCtrl * tree,char * name,int objc,Tcl_Obj * CONST objv[])7579 Gradient_CreateAndConfig(
7580     TreeCtrl *tree,		/* Widget info. */
7581     char *name,			/* Name of new gradient. */
7582     int objc,			/* Number of config-option arg-value pairs. */
7583     Tcl_Obj *CONST objv[]	/* Config-option arg-value pairs. */
7584     )
7585 {
7586     TreeGradient gradient;
7587 
7588     gradient = (TreeGradient) ckalloc(sizeof(TreeGradient_));
7589     memset(gradient, '\0', sizeof(TreeGradient_));
7590     gradient->name = Tk_GetUid(name);
7591 
7592     if (Tk_InitOptions(tree->interp, (char *) gradient,
7593 	tree->gradientOptionTable, tree->tkwin) != TCL_OK) {
7594 	WFREE(gradient, TreeGradient_);
7595 	return NULL;
7596     }
7597 
7598     if (Gradient_Config(tree, gradient, objc, objv, TRUE) != TCL_OK) {
7599 	Gradient_FreeResources(tree, gradient, 1);
7600 	return NULL;
7601     }
7602 
7603     return gradient;
7604 }
7605 
7606 static void
Gradient_Changed(TreeCtrl * tree,TreeGradient gradient)7607 Gradient_Changed(
7608     TreeCtrl *tree,		/* Widget info. */
7609     TreeGradient gradient	/* Gradient token. */
7610     )
7611 {
7612     Tree_DInfoChanged(tree, DINFO_INVALIDATE | DINFO_OUT_OF_DATE | DINFO_DRAW_HEADER);
7613 }
7614 
7615 static void
Gradient_Deleted(TreeCtrl * tree,TreeGradient gradient)7616 Gradient_Deleted(
7617     TreeCtrl *tree,		/* Widget info. */
7618     TreeGradient gradient	/* Gradient token. */
7619     )
7620 {
7621 }
7622 
7623 /*
7624  *----------------------------------------------------------------------
7625  *
7626  * TreeGradientCmd --
7627  *
7628  *	This procedure is invoked to process the [gradient]
7629  *	widget command.  See the user documentation for details on what
7630  *	it does.
7631  *
7632  * Results:
7633  *	A standard Tcl result.
7634  *
7635  * Side effects:
7636  *	See the user documentation.
7637  *
7638  *----------------------------------------------------------------------
7639  */
7640 
7641 int
TreeGradientCmd(ClientData clientData,Tcl_Interp * interp,int objc,Tcl_Obj * CONST objv[])7642 TreeGradientCmd(
7643     ClientData clientData,	/* Widget info. */
7644     Tcl_Interp *interp,		/* Current interpreter. */
7645     int objc,			/* Number of arguments. */
7646     Tcl_Obj *CONST objv[]	/* Argument values. */
7647     )
7648 {
7649     TreeCtrl *tree = clientData;
7650     static CONST char *commandNames[] = {
7651 	"cget", "configure", "create", "delete", "names", "native",
7652 	(char *) NULL
7653     };
7654     enum {
7655 	COMMAND_CGET, COMMAND_CONFIGURE, COMMAND_CREATE,
7656 	COMMAND_DELETE, COMMAND_NAMES, COMMAND_NATIVE
7657     };
7658     int index;
7659 
7660     if (objc < 3) {
7661 	Tcl_WrongNumArgs(interp, 2, objv, "command ?arg arg ...?");
7662 	return TCL_ERROR;
7663     }
7664 
7665     if (Tcl_GetIndexFromObj(interp, objv[2], commandNames, "command", 0,
7666 	&index) != TCL_OK) {
7667 	return TCL_ERROR;
7668     }
7669 
7670     switch (index) {
7671 	case COMMAND_CGET: {
7672 	    Tcl_Obj *resultObjPtr = NULL;
7673 	    TreeGradient gradient;
7674 
7675 	    if (objc != 5) {
7676 		Tcl_WrongNumArgs(interp, 3, objv, "name option");
7677 		return TCL_ERROR;
7678 	    }
7679 	    if (TreeGradient_FromObj(tree, objv[3], &gradient) != TCL_OK)
7680 		return TCL_ERROR;
7681 	    resultObjPtr = Tk_GetOptionValue(interp, (char *) gradient,
7682 		tree->gradientOptionTable, objv[4], tree->tkwin);
7683 	    if (resultObjPtr == NULL)
7684 		return TCL_ERROR;
7685 	    Tcl_SetObjResult(interp, resultObjPtr);
7686 	    break;
7687 	}
7688 	case COMMAND_CONFIGURE: {
7689 	    Tcl_Obj *resultObjPtr = NULL;
7690 	    TreeGradient gradient;
7691 
7692 	    if (objc < 4) {
7693 		Tcl_WrongNumArgs(interp, 3, objv, "name ?option? ?value option value ...?");
7694 		return TCL_ERROR;
7695 	    }
7696 	    if (TreeGradient_FromObj(tree, objv[3], &gradient) != TCL_OK)
7697 		return TCL_ERROR;
7698 	    if (objc <= 5) {
7699 		resultObjPtr = Tk_GetOptionInfo(interp, (char *) gradient,
7700 		    tree->gradientOptionTable,
7701 		    (objc == 4) ? (Tcl_Obj *) NULL : objv[4],
7702 		    tree->tkwin);
7703 		if (resultObjPtr == NULL)
7704 		    return TCL_ERROR;
7705 		Tcl_SetObjResult(interp, resultObjPtr);
7706 	    } else {
7707 		if (Gradient_Config(tree, gradient,
7708 		    objc - 4, objv + 4, 0) != TCL_OK)
7709 		    return TCL_ERROR;
7710 		Gradient_Changed(tree, gradient);
7711 	    }
7712 	    break;
7713 	}
7714 	/* T gradient create NAME ?option value ...? */
7715 	case COMMAND_CREATE: {
7716 	    char *name;
7717 	    int len;
7718 	    Tcl_HashEntry *hPtr;
7719 	    int isNew;
7720 	    TreeGradient gradient;
7721 
7722 	    if (objc < 4) {
7723 		Tcl_WrongNumArgs(interp, 3, objv, "name ?option value ...?");
7724 		return TCL_ERROR;
7725 	    }
7726 	    name = Tcl_GetStringFromObj(objv[3], &len);
7727 	    if (!len) {
7728 		FormatResult(interp, "invalid gradient name \"\"");
7729 		return TCL_ERROR;
7730 	    }
7731 	    /* Just like named fonts, if we create a new gradient that has the
7732 	     * same name as one that is awaiting deletion, we copy the new
7733 	     * config to the old gradient and forget it is being deleted. */
7734 	    hPtr = Tcl_FindHashEntry(&tree->gradientHash, name);
7735 	    if (hPtr != NULL) {
7736 		TreeGradient gradient2;
7737 		gradient = (TreeGradient) Tcl_GetHashValue(hPtr);
7738 		if (gradient->deletePending == 0) {
7739 		    FormatResult(interp, "gradient \"%s\" already exists", name);
7740 		    return TCL_ERROR;
7741 		}
7742 		gradient2 = Gradient_CreateAndConfig(tree, name, objc - 4, objv + 4);
7743 		if (gradient2 == NULL)
7744 		    return TCL_ERROR;
7745 		Gradient_FreeResources(tree, gradient, 0);
7746 		gradient->stopArrPtr = gradient2->stopArrPtr;
7747 		gradient->steps = gradient2->steps;
7748 		gradient->nStepColors = gradient2->nStepColors;
7749 		gradient->stepColors = gradient2->stepColors;
7750 		gradient->deletePending = 0;
7751 		WFREE(gradient2, TreeGradient_);
7752 		Gradient_Changed(tree, gradient);
7753 		break;
7754 	    }
7755 	    gradient = Gradient_CreateAndConfig(tree, name, objc - 4, objv + 4);
7756 	    if (gradient == NULL)
7757 		return TCL_ERROR;
7758 	    hPtr = Tcl_CreateHashEntry(&tree->gradientHash, name, &isNew);
7759 	    Tcl_SetHashValue(hPtr, gradient);
7760 	    Tcl_SetObjResult(interp, Gradient_ToObj((TreeGradient) gradient));
7761 	    break;
7762 	}
7763 	/* T gradient delete ?name ...? */
7764 	case COMMAND_DELETE: {
7765 	    int i;
7766 	    TreeGradient gradient;
7767 
7768 	    if (objc < 3) {
7769 		Tcl_WrongNumArgs(interp, 3, objv, "?name ...?");
7770 		return TCL_ERROR;
7771 	    }
7772 	    for (i = 3; i < objc; i++) {
7773 		if (TreeGradient_FromObj(tree, objv[i], &gradient) != TCL_OK)
7774 		    return TCL_ERROR;
7775 		if (gradient->refCount > 0) {
7776 		    gradient->deletePending = 1;
7777 		} else {
7778 		    Gradient_Deleted(tree, gradient);
7779 		    Gradient_FreeResources(tree, gradient, 1);
7780 		}
7781 	    }
7782 	    break;
7783 	}
7784 	/* T gradient names */
7785 	case COMMAND_NAMES: {
7786 	    Tcl_Obj *listObj;
7787 	    Tcl_HashSearch search;
7788 	    Tcl_HashEntry *hPtr;
7789 	    TreeGradient gradient;
7790 
7791 	    if (objc != 3) {
7792 		Tcl_WrongNumArgs(interp, 3, objv, NULL);
7793 		return TCL_ERROR;
7794 	    }
7795 	    listObj = Tcl_NewListObj(0, NULL);
7796 	    hPtr = Tcl_FirstHashEntry(&tree->gradientHash, &search);
7797 	    while (hPtr != NULL) {
7798 		gradient = (TreeGradient) Tcl_GetHashValue(hPtr);
7799 		if (gradient->deletePending == 0) {
7800 		    Tcl_ListObjAppendElement(interp, listObj, Gradient_ToObj(gradient));
7801 		}
7802 		hPtr = Tcl_NextHashEntry(&search);
7803 	    }
7804 	    Tcl_SetObjResult(interp, listObj);
7805 	    break;
7806 	}
7807 	/* T gradient native ?bool? */
7808 	case COMMAND_NATIVE: {
7809 	    int native;
7810 
7811 	    if (objc > 4) {
7812 		Tcl_WrongNumArgs(interp, 3, objv, "?preference?");
7813 		return TCL_ERROR;
7814 	    }
7815 	    if (objc == 4) {
7816 		if (Tcl_GetBooleanFromObj(interp, objv[3], &native) != TCL_OK) {
7817 		    return TCL_ERROR;
7818 		}
7819 		if (native != tree->nativeGradients) {
7820 		    Tree_DInfoChanged(tree, DINFO_INVALIDATE | DINFO_OUT_OF_DATE
7821 			| DINFO_DRAW_HEADER);
7822 		    tree->nativeGradients = native;
7823 		}
7824 	    }
7825 	    native = Tree_HasNativeGradients(tree);
7826 	    Tcl_SetObjResult(interp, Tcl_NewBooleanObj(native));
7827 	    break;
7828 	}
7829     }
7830     return TCL_OK;
7831 }
7832 
7833 /*
7834  *----------------------------------------------------------------------
7835  *
7836  * TreeGradient_IsOpaque --
7837  *
7838  *	Test whether a gradient would be drawn fully opaque or not.
7839  *
7840  * Results:
7841  *	1 if opaque, 0 otherwise.
7842  *
7843  * Side effects:
7844  *	None.
7845  *
7846  *----------------------------------------------------------------------
7847  */
7848 
7849 int
TreeGradient_IsOpaque(TreeCtrl * tree,TreeGradient gradient)7850 TreeGradient_IsOpaque(
7851     TreeCtrl *tree,		/* Widget info. */
7852     TreeGradient gradient	/* Gradient token. */
7853     )
7854 {
7855     GradientStopArray *stopArrPtr = gradient->stopArrPtr;
7856     int i;
7857 
7858     if (stopArrPtr->nstops < 2)
7859 	return 0; /* no colors == fully transparent */
7860 
7861     if (!tree->nativeGradients || !Tree_HasNativeGradients(tree))
7862 	return 1;
7863 
7864     for (i = 0; i < stopArrPtr->nstops; i++) {
7865 	GradientStop *stop = stopArrPtr->stops[i];
7866 	if (stop->opacity < 1.0f)
7867 	    return 0;
7868     }
7869 
7870     return 1;
7871 }
7872 
7873 /*
7874  *----------------------------------------------------------------------
7875  *
7876  * TreeGradient_InitWidget --
7877  *
7878  *	Gradient-related package initialization.
7879  *
7880  * Results:
7881  *	A standard Tcl result.
7882  *
7883  * Side effects:
7884  *	None.
7885  *
7886  *----------------------------------------------------------------------
7887  */
7888 
7889 void
TreeGradient_InitWidget(TreeCtrl * tree)7890 TreeGradient_InitWidget(
7891     TreeCtrl *tree		/* Widget info. */
7892     )
7893 {
7894     tree->gradientOptionTable = Tk_CreateOptionTable(tree->interp,
7895 	gradientOptionSpecs);
7896     tree->nativeGradients = 1; /* Preference, not availability */
7897 }
7898 
7899 /*
7900  *----------------------------------------------------------------------
7901  *
7902  * TreeGradient_FreeWidget --
7903  *
7904  *	Free gradient-related resources for a deleted TreeCtrl.
7905  *
7906  * Results:
7907  *	None.
7908  *
7909  * Side effects:
7910  *	Memory is freed.
7911  *
7912  *----------------------------------------------------------------------
7913  */
7914 
7915 void
TreeGradient_FreeWidget(TreeCtrl * tree)7916 TreeGradient_FreeWidget(
7917     TreeCtrl *tree		/* Widget info. */
7918     )
7919 {
7920     Tcl_HashEntry *hPtr;
7921     Tcl_HashSearch search;
7922     TreeGradient gradient;
7923 
7924     while (1) {
7925 	hPtr = Tcl_FirstHashEntry(&tree->gradientHash, &search);
7926 	if (hPtr == NULL)
7927 	    break;
7928 	gradient = (TreeGradient) Tcl_GetHashValue(hPtr);
7929 	if (gradient->refCount != 0) {
7930 	    panic("TreeGradient_Free: one or more gradients still being used");
7931 	}
7932 	Gradient_FreeResources(tree, gradient, 1);
7933     }
7934 
7935     Tcl_DeleteHashTable(&tree->gradientHash);
7936 }
7937 
7938 /*
7939  *----------------------------------------------------------------------
7940  *
7941  * TreeGradient_FillRoundRectX11 --
7942  *
7943  *	Paint a rectangle with a gradient using XFillRectangle.
7944  *	We can't paint a rounded rectangle with a gradient on X11.
7945  *	If I could clip drawing to arc's then I could do it.
7946  *
7947  * Results:
7948  *	None.
7949  *
7950  * Side effects:
7951  *	Drawing.
7952  *
7953  *----------------------------------------------------------------------
7954  */
7955 
7956 void
TreeGradient_FillRoundRectX11(TreeCtrl * tree,TreeDrawable td,TreeClip * clip,TreeGradient gradient,TreeRectangle trBrush,TreeRectangle tr,int rx,int ry,int open)7957 TreeGradient_FillRoundRectX11(
7958     TreeCtrl *tree,		/* Widget info. */
7959     TreeDrawable td,		/* Where to draw. */
7960     TreeClip *clip,		/* Clipping area or NULL. */
7961     TreeGradient gradient,	/* Gradient token. */
7962     TreeRectangle trBrush,	/* Brush bounds. */
7963     TreeRectangle tr,		/* Rectangle to paint. */
7964     int rx, int ry,		/* Corner radius */
7965     int open			/* RECT_OPEN_x flags */
7966     )
7967 {
7968     if (tr.height <= 0 || tr.width <= 0 || gradient->nStepColors <= 0)
7969 	return;
7970 
7971     /* Use the first stop color */
7972     Tree_FillRoundRect(tree, td, clip, gradient->stopArrPtr->stops[0]->color,
7973 	tr, rx, ry, open);
7974 }
7975 
7976 /*
7977  *----------------------------------------------------------------------
7978  *
7979  * TreeGradient_FillRectX11 --
7980  *
7981  *	Paint a rectangle with a gradient using XFillRectangle.
7982  *
7983  * Results:
7984  *	None.
7985  *
7986  * Side effects:
7987  *	Drawing.
7988  *
7989  *----------------------------------------------------------------------
7990  */
7991 
7992 void
_TreeGradient_FillRectX11(TreeCtrl * tree,TreeDrawable td,TreeClip * clip,TreeGradient gradient,TreeRectangle trBrush,TreeRectangle tr)7993 _TreeGradient_FillRectX11(
7994     TreeCtrl *tree,		/* Widget info. */
7995     TreeDrawable td,		/* Where to draw. */
7996     TreeClip *clip,		/* Clipping area or NULL. */
7997     TreeGradient gradient,	/* Gradient token. */
7998     TreeRectangle trBrush,	/* Brush bounds. */
7999     TreeRectangle tr		/* Rectangle to paint. */
8000     )
8001 {
8002     TreeRectangle trSub, trPaint;
8003     int i;
8004     float delta;
8005     GC gc;
8006 
8007     if (tr.height <= 0 || tr.width <= 0 || gradient->nStepColors <= 0)
8008 	return;
8009 
8010     trSub = trBrush;
8011 
8012     if (gradient->vertical) {
8013 	delta = ((float)trBrush.height) / gradient->nStepColors;
8014 	for (i = 0; i < gradient->nStepColors; i++) {
8015 	    float y1 = trBrush.y + i * delta;
8016 	    float y2 = trBrush.y + (i+1) * delta;
8017 	    trSub.y = (int)y1;
8018 	    trSub.height = (int)(ceil(y2) - floor(y1));
8019 	    if (TreeRect_Intersect(&trPaint, &trSub, &tr)) {
8020 		gc = Tk_GCForColor(gradient->stepColors[i], Tk_WindowId(tree->tkwin));
8021 		Tree_FillRectangle(tree, td, clip, gc, trPaint);
8022 	    }
8023 	}
8024     } else {
8025 	delta = ((float)trBrush.width) / gradient->nStepColors;
8026 	for (i = 0; i < gradient->nStepColors; i++) {
8027 	    float x1 = trBrush.x + i * delta;
8028 	    float x2 = trBrush.x + (i+1) * delta;
8029 	    trSub.x = (int)x1;
8030 	    trSub.width = (int)(ceil(x2) - floor(x1));
8031 	    if (TreeRect_Intersect(&trPaint, &trSub, &tr)) {
8032 		gc = Tk_GCForColor(gradient->stepColors[i], Tk_WindowId(tree->tkwin));
8033 		Tree_FillRectangle(tree, td, clip, gc, trPaint);
8034 	    }
8035 	}
8036     }
8037 }
8038 
8039 void
TreeGradient_FillRectX11(TreeCtrl * tree,TreeDrawable td,TreeClip * clip,TreeGradient gradient,TreeRectangle trBrush,TreeRectangle tr)8040 TreeGradient_FillRectX11(
8041     TreeCtrl *tree,		/* Widget info. */
8042     TreeDrawable td,		/* Where to draw. */
8043     TreeClip *clip,		/* Clipping area or NULL. */
8044     TreeGradient gradient,	/* Gradient token. */
8045     TreeRectangle trBrush,	/* Brush bounds. */
8046     TreeRectangle tr		/* Rectangle to paint. */
8047     )
8048 {
8049     TreeRectangle trSub;
8050     int oldX = trBrush.x, oldY = trBrush.y;
8051 
8052     if (trBrush.height < 1 || trBrush.width < 1) return;
8053     if (tr.height < 1 || tr.width < 1) return;
8054 
8055     /* This implements tiling of the gradient brush */
8056     while (tr.x < trBrush.x)
8057 	trBrush.x -= trBrush.width;
8058     while (tr.x >= trBrush.x + trBrush.width)
8059 	trBrush.x += trBrush.width;
8060     while (tr.y < trBrush.y)
8061 	trBrush.y -= trBrush.height;
8062     while (tr.y >= trBrush.y + trBrush.height)
8063 	trBrush.y += trBrush.height;
8064     oldX = trBrush.x, oldY = trBrush.y;
8065     while (trBrush.x < tr.x + tr.width) {
8066 	trBrush.y = oldY;
8067 	while (trBrush.y < tr.y + tr.height) {
8068 	    TreeRect_Intersect(&trSub, &trBrush, &tr);
8069 	    _TreeGradient_FillRectX11(tree, td, clip, gradient,
8070 		trBrush, trSub);
8071 	    trBrush.y += trBrush.height;
8072 	}
8073 	trBrush.x += trBrush.width;
8074     }
8075 }
8076 
8077 void
TreeGradient_DrawRectX11(TreeCtrl * tree,TreeDrawable td,TreeClip * clip,TreeGradient gradient,TreeRectangle trBrush,TreeRectangle tr,int outlineWidth,int open)8078 TreeGradient_DrawRectX11(
8079     TreeCtrl *tree,		/* Widget info. */
8080     TreeDrawable td,		/* Where to draw. */
8081     TreeClip *clip,		/* Clipping area or NULL. */
8082     TreeGradient gradient,	/* Gradient token. */
8083     TreeRectangle trBrush,	/* Brush bounds. */
8084     TreeRectangle tr,		/* Rectangle to outline. */
8085     int outlineWidth,		/* Width of outline. */
8086     int open			/* RECT_OPEN_x flags */
8087     )
8088 {
8089     TreeRectangle trEdge;
8090 
8091     if (!(open & RECT_OPEN_W)) {
8092 	trEdge.x = tr.x, trEdge.y = tr.y,
8093 	    trEdge.width = outlineWidth, trEdge.height = tr.height;
8094 	TreeGradient_FillRectX11(tree, td, clip, gradient, trBrush, trEdge);
8095     }
8096     if (!(open & RECT_OPEN_N)) {
8097 	trEdge.x = tr.x, trEdge.y = tr.y,
8098 	    trEdge.width = tr.width, trEdge.height = outlineWidth;
8099 	TreeGradient_FillRectX11(tree, td, clip, gradient, trBrush, trEdge);
8100     }
8101     if (!(open & RECT_OPEN_E)) {
8102 	trEdge.x = tr.x + tr.width - outlineWidth, trEdge.y = tr.y,
8103 	    trEdge.width = outlineWidth, trEdge.height = tr.height;
8104 	TreeGradient_FillRectX11(tree, td, clip, gradient, trBrush, trEdge);
8105     }
8106     if (!(open & RECT_OPEN_S)) {
8107 	trEdge.x = tr.x, trEdge.y = tr.y + tr.height - outlineWidth,
8108 	    trEdge.width = tr.width, trEdge.height = outlineWidth;
8109 	TreeGradient_FillRectX11(tree, td, clip, gradient, trBrush, trEdge);
8110     }
8111 }
8112 
8113 /*
8114  *----------------------------------------------------------------------
8115  *
8116  * TreeColor_DrawRect --
8117  *
8118  *	Draw a rectangle with a gradient or solid color.
8119  *
8120  * Results:
8121  *	If the gradient has <2 stops then nothing is drawn.
8122  *
8123  * Side effects:
8124  *	Drawing.
8125  *
8126  *----------------------------------------------------------------------
8127  */
8128 
8129 void
TreeColor_DrawRect(TreeCtrl * tree,TreeDrawable td,TreeClip * clip,TreeColor * tc,TreeRectangle trBrush,TreeRectangle tr,int outlineWidth,int open)8130 TreeColor_DrawRect(
8131     TreeCtrl *tree,		/* Widget info. */
8132     TreeDrawable td,		/* Where to draw. */
8133     TreeClip *clip,		/* Clipping area or NULL. */
8134     TreeColor *tc,		/* Color info. */
8135     TreeRectangle trBrush,	/* Brush bounds. */
8136     TreeRectangle tr,		/* Rectangle to outline. */
8137     int outlineWidth,		/* Width of outline. */
8138     int open			/* RECT_OPEN_x flags */
8139     )
8140 {
8141     if (tc == NULL || outlineWidth < 1 || open == RECT_OPEN_WNES)
8142 	return;
8143     if (tc->gradient != NULL) {
8144 	TreeGradient_DrawRect(tree, td, clip, tc->gradient, trBrush, tr,
8145 	    outlineWidth, open);
8146     }
8147     if (tc->color != NULL) {
8148 	GC gc = Tk_GCForColor(tc->color, td.drawable);
8149 	TreeRectangle trEdge;
8150 	if (!(open & RECT_OPEN_W)) {
8151 	    trEdge.x = tr.x, trEdge.y = tr.y,
8152 		trEdge.width = outlineWidth, trEdge.height = tr.height;
8153 	    Tree_FillRectangle(tree, td, clip, gc, trEdge);
8154 	}
8155 	if (!(open & RECT_OPEN_N)) {
8156 	    trEdge.x = tr.x, trEdge.y = tr.y,
8157 		trEdge.width = tr.width, trEdge.height = outlineWidth;
8158 	    Tree_FillRectangle(tree, td, clip, gc, trEdge);
8159 	}
8160 	if (!(open & RECT_OPEN_E)) {
8161 	    trEdge.x = tr.x + tr.width - outlineWidth, trEdge.y = tr.y,
8162 		trEdge.width = outlineWidth, trEdge.height = tr.height;
8163 	    Tree_FillRectangle(tree, td, clip, gc, trEdge);
8164 	}
8165 	if (!(open & RECT_OPEN_S)) {
8166 	    trEdge.x = tr.x, trEdge.y = tr.y + tr.height - outlineWidth,
8167 		trEdge.width = tr.width, trEdge.height = outlineWidth;
8168 	    Tree_FillRectangle(tree, td, clip, gc, trEdge);
8169 	}
8170     }
8171 }
8172 
8173 /*
8174  *----------------------------------------------------------------------
8175  *
8176  * TreeColor_FillRect --
8177  *
8178  *	Paint a rectangle with a gradient or solid color.
8179  *
8180  * Results:
8181  *	If the gradient has <2 stops then nothing is drawn.
8182  *
8183  * Side effects:
8184  *	Drawing.
8185  *
8186  *----------------------------------------------------------------------
8187  */
8188 
8189 void
TreeColor_FillRect(TreeCtrl * tree,TreeDrawable td,TreeClip * clip,TreeColor * tc,TreeRectangle trBrush,TreeRectangle tr)8190 TreeColor_FillRect(
8191     TreeCtrl *tree,		/* Widget info. */
8192     TreeDrawable td,		/* Where to draw. */
8193     TreeClip *clip,		/* Clipping area or NULL. */
8194     TreeColor *tc,		/* Color info. */
8195     TreeRectangle trBrush,	/* Brush bounds. */
8196     TreeRectangle tr		/* Rectangle to paint. */
8197     )
8198 {
8199     if (tc == NULL)
8200 	return;
8201     if (tc->gradient != NULL) {
8202 	TreeGradient_FillRect(tree, td, clip, tc->gradient, trBrush, tr);
8203     }
8204     if (tc->color != NULL) {
8205 	GC gc = Tk_GCForColor(tc->color, td.drawable);
8206 	Tree_FillRectangle(tree, td, clip, gc, tr);
8207     }
8208 }
8209 
8210 /*
8211  *----------------------------------------------------------------------
8212  *
8213  * TreeColor_DrawRoundRect --
8214  *
8215  *	Draw a rounded rectangle with a gradient or solid color.
8216  *
8217  * Results:
8218  *	If the gradient has <2 stops then nothing is drawn.
8219  *
8220  * Side effects:
8221  *	Drawing.
8222  *
8223  *----------------------------------------------------------------------
8224  */
8225 
8226 void
TreeColor_DrawRoundRect(TreeCtrl * tree,TreeDrawable td,TreeClip * clip,TreeColor * tc,TreeRectangle trBrush,TreeRectangle tr,int outlineWidth,int rx,int ry,int open)8227 TreeColor_DrawRoundRect(
8228     TreeCtrl *tree,		/* Widget info. */
8229     TreeDrawable td,		/* Where to draw. */
8230     TreeClip *clip,		/* Clipping area or NULL. */
8231     TreeColor *tc,		/* Color info. */
8232     TreeRectangle trBrush,	/* Brush bounds. */
8233     TreeRectangle tr,		/* Rectangle to outline. */
8234     int outlineWidth,		/* Width of outline. */
8235     int rx, int ry,		/* Corner radius */
8236     int open			/* RECT_OPEN_x flags */
8237     )
8238 {
8239     if (tc == NULL)
8240 	return;
8241     if (tc->gradient != NULL) {
8242 	TreeGradient_DrawRoundRect(tree, td, clip, tc->gradient, trBrush, tr,
8243 	    outlineWidth, rx, ry, open);
8244     }
8245     if (tc->color != NULL) {
8246 	Tree_DrawRoundRect(tree, td, clip, tc->color, tr, outlineWidth,
8247 	    rx, ry, open);
8248     }
8249 }
8250 
8251 /*
8252  *----------------------------------------------------------------------
8253  *
8254  * TreeColor_FillRoundRect --
8255  *
8256  *	Paint a rounded rectangle with a gradient or solid color.
8257  *
8258  * Results:
8259  *	If the gradient has <2 stops then nothing is drawn.
8260  *
8261  * Side effects:
8262  *	Drawing.
8263  *
8264  *----------------------------------------------------------------------
8265  */
8266 
8267 void
TreeColor_FillRoundRect(TreeCtrl * tree,TreeDrawable td,TreeClip * clip,TreeColor * tc,TreeRectangle trBrush,TreeRectangle tr,int rx,int ry,int open)8268 TreeColor_FillRoundRect(
8269     TreeCtrl *tree,		/* Widget info. */
8270     TreeDrawable td,		/* Where to draw. */
8271     TreeClip *clip,		/* Clipping area or NULL. */
8272     TreeColor *tc,		/* Color info. */
8273     TreeRectangle trBrush,	/* Brush bounds. */
8274     TreeRectangle tr,		/* Rectangle to paint. */
8275     int rx, int ry,		/* Corner radius */
8276     int open			/* RECT_OPEN_x flags */
8277     )
8278 {
8279     if (tc == NULL)
8280 	return;
8281     if (tc->gradient != NULL) {
8282 	TreeGradient_FillRoundRect(tree, td, clip, tc->gradient, trBrush, tr,
8283 	    rx, ry, open);
8284     }
8285     if (tc->color != NULL) {
8286 	Tree_FillRoundRect(tree, td, clip, tc->color, tr, rx, ry, open);
8287     }
8288 }
8289 
8290