/*
 * tkTreeElem.c --
 *
 *	This module implements elements for treectrl widgets.
 *
 * Copyright (c) 2002-2011 Tim Baker
 */

#include "tkTreeCtrl.h"
#include "tkTreeElem.h"

/* When a column header is in the pressed state, any bitmap, image, and text
 * elements are offset by 1,1. */
#define HEADER_OFFSET_HACK 1

/*
 *----------------------------------------------------------------------
 *
 * DO_BooleanForState --
 * DO_ColorForState --
 * DO_FontForState --
 *
 *	Returns the value of a per-state option for an element.
 *
 * Results:
 *	If the element has the dynamic option allocated, then the
 *	per-state info is checked for a match. If an exact match for
 *	the given state is not found, and if the element is an instance
 *	(not a master), then the master element is checked.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

static int
DO_BooleanForState(
    TreeCtrl *tree,		/* Widget info. */
    TreeElement elem,		/* Element to examine. */
    int id,			/* Unique id of dynamic option. */
    int state			/* STATE_xxx flags. */
    )
{
    int result = -1;
    PerStateInfo *psi;
    int match = MATCH_NONE;

    psi = DynamicOption_FindData(elem->options, id);
    if (psi != NULL)
	result = PerStateBoolean_ForState(tree, psi, state, &match);
    if ((match != MATCH_EXACT) && (elem->master != NULL)) {
	PerStateInfo *psi = DynamicOption_FindData(elem->master->options, id);
	if (psi != NULL) {
	    int matchM;
	    int resultM = PerStateBoolean_ForState(tree, psi, state, &matchM);
	    if (matchM > match)
		result = resultM;
	}
    }
    return result;
}

static TreeColor *
DO_ColorForState(
    TreeCtrl *tree,
    TreeElement elem,
    int id,
    int state
    )
{
    TreeColor *result = NULL;
    PerStateInfo *psi;
    int match = MATCH_NONE;

    psi = DynamicOption_FindData(elem->options, id);
    if (psi != NULL)
	result = PerStateColor_ForState(tree, psi, state, &match);
    if ((match != MATCH_EXACT) && (elem->master != NULL)) {
	PerStateInfo *psi = DynamicOption_FindData(elem->master->options, id);
	if (psi != NULL) {
	    int matchM;
	    TreeColor *resultM = PerStateColor_ForState(tree, psi, state, &matchM);
	    if (matchM > match)
		result = resultM;
	}
    }
    return result;
}

static Tk_Font
DO_FontForState(
    TreeCtrl *tree,
    TreeElement elem,
    int id,
    int state
    )
{
    Tk_Font result = NULL;
    PerStateInfo *psi;
    int match = MATCH_NONE;

    psi = DynamicOption_FindData(elem->options, id);
    if (psi != NULL)
	result = PerStateFont_ForState(tree, psi, state, &match);
    if ((match != MATCH_EXACT) && (elem->master != NULL)) {
	PerStateInfo *psi = DynamicOption_FindData(elem->master->options, id);
	if (psi != NULL) {
	    int matchM;
	    Tk_Font resultM = PerStateFont_ForState(tree, psi, state, &matchM);
	    if (matchM > match)
		result = resultM;
	}
    }
    return result;
}

/*
 *----------------------------------------------------------------------
 *
 * DO_ObjectForState --
 *
 *	Returns the object representation of a per-state option
 *	for an element.
 *
 * Results:
 *	If the element has the dynamic option allocated, then the
 *	per-state info is checked for a match. If an exact match for
 *	the given state is not found, and if the element is an instance
 *	(not a master), then the master element is checked.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

static Tcl_Obj *
DO_ObjectForState(
    TreeCtrl *tree,		/* Widget info. */
    PerStateType *typePtr,	/* Type-specific functions and values. */
    TreeElement elem,		/* Element to examine. */
    int id,			/* Unique id of dynamic option. */
    int state			/* STATE_xxx flags. */
    )
{
    Tcl_Obj *result = NULL;
    PerStateInfo *psi;
    int match = MATCH_NONE;

    psi = DynamicOption_FindData(elem->options, id);
    if (psi != NULL)
	result = PerStateInfo_ObjForState(tree, typePtr, psi, state, &match);
    if ((match != MATCH_EXACT) && (elem->master != NULL)) {
	PerStateInfo *psi = DynamicOption_FindData(elem->master->options, id);
	if (psi != NULL) {
	    int matchM;
	    Tcl_Obj *resultM = PerStateInfo_ObjForState(tree, typePtr, psi, state, &matchM);
	    if (matchM > match)
		result = resultM;
	}
    }
    return result;
}

/* BEGIN custom "boolean" option */

/* Just like TK_OPTION_BOOLEAN but supports TK_OPTION_NULL_OK */
/* Internal value is -1 for no-such-value */

static int BooleanSet(
    ClientData clientData,
    Tcl_Interp *interp,
    Tk_Window tkwin,
    Tcl_Obj **value,
    char *recordPtr,
    int internalOffset,
    char *saveInternalPtr,
    int flags)
{
    int objEmpty;
    int new, *internalPtr;

    if (internalOffset >= 0)
	internalPtr = (int *) (recordPtr + internalOffset);
    else
	internalPtr = NULL;

    objEmpty = ObjectIsEmpty((*value));

    if ((flags & TK_OPTION_NULL_OK) && objEmpty)
	(*value) = NULL;
    else {
	if (Tcl_GetBooleanFromObj(interp, (*value), &new) != TCL_OK)
	    return TCL_ERROR;
    }
    if (internalPtr != NULL) {
	if ((*value) == NULL)
	    new = -1;
	*((int *) saveInternalPtr) = *internalPtr;
	*internalPtr = new;
    }

    return TCL_OK;
}

static Tcl_Obj *BooleanGet(
    ClientData clientData,
    Tk_Window tkwin,
    char *recordPtr,
    int internalOffset)
{
    int value = *(int *) (recordPtr + internalOffset);
    if (value == -1)
	return NULL;
    return Tcl_NewBooleanObj(value);
}

static void BooleanRestore(
    ClientData clientData,
    Tk_Window tkwin,
    char *internalPtr,
    char *saveInternalPtr)
{
    *(int *) internalPtr = *(int *) saveInternalPtr;
}

static Tk_ObjCustomOption booleanCO =
{
    "boolean",
    BooleanSet,
    BooleanGet,
    BooleanRestore,
    NULL,
    (ClientData) NULL
};

static void
DynamicOptionInitBoolean(
    void *data
    )
{
    *((int *) data) = -1;
}

/* END custom "boolean" option */

/* BEGIN custom "integer" option */

/* Just like TK_OPTION_INT but supports TK_OPTION_NULL_OK and bounds checking */

typedef struct IntegerClientData
{
    int min;
    int max;
    int empty; /* internal form if empty */
    int flags; /* 0x01 - use min, 0x02 - use max */
} IntegerClientData;

static int IntegerSet(
    ClientData clientData,
    Tcl_Interp *interp,
    Tk_Window tkwin,
    Tcl_Obj **value,
    char *recordPtr,
    int internalOffset,
    char *saveInternalPtr,
    int flags)
{
    IntegerClientData *cd = clientData;
    int objEmpty;
    int new, *internalPtr;

    if (internalOffset >= 0)
	internalPtr = (int *) (recordPtr + internalOffset);
    else
	internalPtr = NULL;

    objEmpty = ObjectIsEmpty((*value));

    if ((flags & TK_OPTION_NULL_OK) && objEmpty)
	(*value) = NULL;
    else {
	if (Tcl_GetIntFromObj(interp, (*value), &new) != TCL_OK)
	    return TCL_ERROR;
	if ((cd->flags & 0x01) && (new < cd->min)) {
	    FormatResult(interp,
		    "bad integer value \"%d\": must be >= %d",
		    new, cd->min);
	    return TCL_ERROR;
	}
	if ((cd->flags & 0x02) && (new > cd->max)) {
	    FormatResult(interp,
		    "bad integer value \"%d\": must be <= %d",
		    new, cd->max);
	    return TCL_ERROR;
	}
    }
    if (internalPtr != NULL) {
	if ((*value) == NULL)
	    new = cd->empty;
	*((int *) saveInternalPtr) = *internalPtr;
	*internalPtr = new;
    }

    return TCL_OK;
}

static Tcl_Obj *IntegerGet(
    ClientData clientData,
    Tk_Window tkwin,
    char *recordPtr,
    int internalOffset)
{
    IntegerClientData *cd = clientData;
    int value = *(int *) (recordPtr + internalOffset);
    if (value == cd->empty)
	return NULL;
    return Tcl_NewIntObj(value);
}

static void IntegerRestore(
    ClientData clientData,
    Tk_Window tkwin,
    char *internalPtr,
    char *saveInternalPtr)
{
    *(int *) internalPtr = *(int *) saveInternalPtr;
}

/* END custom "integer" option */

/*****/

/* BEGIN custom "stringtable" option */

/* Just like TK_OPTION_STRING_TABLE but supports TK_OPTION_NULL_OK */
/* The integer rep is -1 if empty string specified */

typedef struct StringTableClientData
{
    CONST char **tablePtr; /* NULL-termintated list of strings */
    CONST char *msg; /* Tcl_GetIndexFromObj() message */
} StringTableClientData;

static int StringTableSet(
    ClientData clientData,
    Tcl_Interp *interp,
    Tk_Window tkwin,
    Tcl_Obj **value,
    char *recordPtr,
    int internalOffset,
    char *saveInternalPtr,
    int flags)
{
    StringTableClientData *cd = clientData;
    int objEmpty;
    int new, *internalPtr;

    if (internalOffset >= 0)
	internalPtr = (int *) (recordPtr + internalOffset);
    else
	internalPtr = NULL;

    objEmpty = ObjectIsEmpty((*value));

    if ((flags & TK_OPTION_NULL_OK) && objEmpty)
	(*value) = NULL;
    else {
	if (Tcl_GetIndexFromObj(interp, (*value), cd->tablePtr,
		    cd->msg, 0, &new) != TCL_OK)
	    return TCL_ERROR;
    }
    if (internalPtr != NULL) {
	if ((*value) == NULL)
	    new = -1;
	*((int *) saveInternalPtr) = *internalPtr;
	*internalPtr = new;
    }

    return TCL_OK;
}

static Tcl_Obj *StringTableGet(
    ClientData clientData,
    Tk_Window tkwin,
    char *recordPtr,
    int internalOffset)
{
    StringTableClientData *cd = clientData;
    int index = *(int *) (recordPtr + internalOffset);

    if (index == -1)
	return NULL;
    return Tcl_NewStringObj(cd->tablePtr[index], -1);
}

static void StringTableRestore(
    ClientData clientData,
    Tk_Window tkwin,
    char *internalPtr,
    char *saveInternalPtr)
{
    *(int *) internalPtr = *(int *) saveInternalPtr;
}

/* END custom "stringtable" option */

static int
BooleanCO_Init(
    Tk_OptionSpec *optionTable,
    CONST char *optionName)
{
    Tk_OptionSpec *specPtr;

    specPtr = Tree_FindOptionSpec(optionTable, optionName);
    specPtr->clientData = &booleanCO;
    return TCL_OK;
}

static Tk_ObjCustomOption *
IntegerCO_Alloc(
    CONST char *optionName,
    int min,
    int max,
    int empty,
    int flags
    )
{
    IntegerClientData *cd;
    Tk_ObjCustomOption *co;

    /* ClientData for the Tk custom option record */
    cd = (IntegerClientData *) ckalloc(sizeof(IntegerClientData));
    cd->min = min;
    cd->max = max;
    cd->empty = empty;
    cd->flags = flags;

    /* The Tk custom option record */
    co = (Tk_ObjCustomOption *) ckalloc(sizeof(Tk_ObjCustomOption));
    co->name = (char *) optionName + 1;
    co->setProc = IntegerSet;
    co->getProc = IntegerGet;
    co->restoreProc = IntegerRestore;
    co->freeProc = NULL;
    co->clientData = (ClientData) cd;

    return co;
}

#if 0 /* UNUSED, keep but shutup compiler warning */
static int
IntegerCO_Init(
    Tk_OptionSpec *optionTable,
    CONST char *optionName,
    int min,
    int max,
    int empty,
    int flags
    )
{
    Tk_OptionSpec *specPtr;

    specPtr = Tree_FindOptionSpec(optionTable, optionName);
    if (specPtr->type != TK_OPTION_CUSTOM)
	panic("IntegerCO_Init: %s is not TK_OPTION_CUSTOM", optionName);
    if (specPtr->clientData != NULL)
	return TCL_OK;

    specPtr->clientData = IntegerCO_Alloc(optionName, min, max, empty, flags);

    return TCL_OK;
}
#endif

static Tk_ObjCustomOption *
StringTableCO_Alloc(
    CONST char *optionName,
    CONST char **tablePtr
    )
{
    StringTableClientData *cd;
    Tk_ObjCustomOption *co;

    /* ClientData for the Tk custom option record */
    cd = (StringTableClientData *) ckalloc(sizeof(StringTableClientData));
    cd->tablePtr = tablePtr;
    cd->msg = optionName + 1;

    /* The Tk custom option record */
    co = (Tk_ObjCustomOption *) ckalloc(sizeof(Tk_ObjCustomOption));
    co->name = (char *) optionName + 1;
    co->setProc = StringTableSet;
    co->getProc = StringTableGet;
    co->restoreProc = StringTableRestore;
    co->freeProc = NULL;
    co->clientData = (ClientData) cd;

    return co;
}

int StringTableCO_Init(Tk_OptionSpec *optionTable, CONST char *optionName, CONST char **tablePtr)
{
    Tk_OptionSpec *specPtr;

    specPtr = Tree_FindOptionSpec(optionTable, optionName);
    if (specPtr->type != TK_OPTION_CUSTOM)
	panic("StringTableCO_Init: %s is not TK_OPTION_CUSTOM", optionName);
    if (specPtr->clientData != NULL)
	return TCL_OK;

    specPtr->clientData = StringTableCO_Alloc(optionName, tablePtr);

    return TCL_OK;
}

/*****/

int TreeStateFromObj(TreeCtrl *tree, int domain, Tcl_Obj *obj, int *stateOff, int *stateOn)
{
    int states[3];

    states[STATE_OP_ON] = states[STATE_OP_OFF] = states[STATE_OP_TOGGLE] = 0;
    if (Tree_StateFromObj(tree, domain, obj, states, NULL, SFO_NOT_TOGGLE) != TCL_OK)
	return TCL_ERROR;

    (*stateOn) |= states[STATE_OP_ON];
    (*stateOff) |= states[STATE_OP_OFF];
    return TCL_OK;
}

static void AdjustForSticky(int sticky, int cavityWidth, int cavityHeight,
    int expandX, int expandY,
    int *xPtr, int *yPtr, int *widthPtr, int *heightPtr)
{
    int dx = 0;
    int dy = 0;

    if (cavityWidth > *widthPtr) {
	dx = cavityWidth - *widthPtr;
    }

    if (cavityHeight > *heightPtr) {
	dy = cavityHeight - *heightPtr;
    }

    if ((sticky & STICKY_W) && (sticky & STICKY_E)) {
	if (expandX)
	    *widthPtr += dx;
	else
	    sticky &= ~(STICKY_W | STICKY_E);
    }
    if ((sticky & STICKY_N) && (sticky & STICKY_S)) {
	if (expandY)
	    *heightPtr += dy;
	else
	    sticky &= ~(STICKY_N | STICKY_S);
    }
    if (!(sticky & STICKY_W)) {
	*xPtr += (sticky & STICKY_E) ? dx : dx / 2;
    }
    if (!(sticky & STICKY_N)) {
	*yPtr += (sticky & STICKY_S) ? dy : dy / 2;
    }
}

#define DO_COLOR_FOR_STATE(xVAR,xID,xSTATE) \
    { \
	TreeColor *tc = DO_ColorForState(tree, elem, xID, xSTATE); \
	xVAR = (tc != NULL) ? tc->color : NULL; \
    }

/* This macro gets the value of a per-state option for an element, then
 * looks for a better match from the master element if it exists */
#define OPTION_FOR_STATE(xFUNC,xTYPE,xVAR,xFIELD,xSTATE) \
    xVAR = xFUNC(tree, &elemX->xFIELD, xSTATE, &match); \
    if ((match != MATCH_EXACT) && (masterX != NULL)) { \
	xTYPE varM = xFUNC(tree, &masterX->xFIELD, xSTATE, &match2); \
	if (match2 > match) \
	    xVAR = varM; \
    }
#define BITMAP_FOR_STATE(xVAR,xFIELD,xSTATE) \
    OPTION_FOR_STATE(PerStateBitmap_ForState,Pixmap,xVAR,xFIELD,xSTATE)
#define BOOLEAN_FOR_STATE(xVAR,xFIELD,xSTATE) \
    OPTION_FOR_STATE(PerStateBoolean_ForState,int,xVAR,xFIELD,xSTATE)
#define BORDER_FOR_STATE(xVAR,xFIELD,xSTATE) \
    OPTION_FOR_STATE(PerStateBorder_ForState,Tk_3DBorder,xVAR,xFIELD,xSTATE)
#define COLOR_FOR_STATE(xVAR,xFIELD,xSTATE) \
    { \
	TreeColor *tc; \
	OPTION_FOR_STATE(PerStateColor_ForState,TreeColor*,tc,xFIELD,xSTATE) \
	xVAR = (tc != NULL) ? tc->color : NULL; \
    }
#define TREECOLOR_FOR_STATE(xVAR,xFIELD,xSTATE) \
    OPTION_FOR_STATE(PerStateColor_ForState,TreeColor*,xVAR,xFIELD,xSTATE)
#define FLAGS_FOR_STATE(xVAR,xFIELD,xSTATE) \
    OPTION_FOR_STATE(PerStateFlags_ForState,int,xVAR,xFIELD,xSTATE)
#define FONT_FOR_STATE(xVAR,xFIELD,xSTATE) \
    OPTION_FOR_STATE(PerStateFont_ForState,Tk_Font,xVAR,xFIELD,xSTATE)
#define GRADIENT_FOR_STATE(xVAR,xFIELD,xSTATE) \
    OPTION_FOR_STATE(PerStateGradient_ForState,TreeGradient,xVAR,xFIELD,xSTATE)
#define IMAGE_FOR_STATE(xVAR,xFIELD,xSTATE) \
    OPTION_FOR_STATE(PerStateImage_ForState,Tk_Image,xVAR,xFIELD,xSTATE)
#define RELIEF_FOR_STATE(xVAR,xFIELD,xSTATE) \
    OPTION_FOR_STATE(PerStateRelief_ForState,int,xVAR,xFIELD,xSTATE)

/* This macro gets the object for a per-state option for an element, then
 * looks for a better match from the master element if it exists */
#define OBJECT_FOR_STATE(xVAR,xTYPE,xFIELD,xSTATE) \
    xVAR = PerStateInfo_ObjForState(tree, &xTYPE, &elemX->xFIELD, xSTATE, &match); \
    if ((match != MATCH_EXACT) && (masterX != NULL)) { \
	Tcl_Obj *objM = PerStateInfo_ObjForState(tree, &xTYPE, &masterX->xFIELD, xSTATE, &matchM); \
	if (matchM > match) \
	    xVAR = objM; \
    }

/*****/

typedef struct ElementBitmap ElementBitmap;

struct ElementBitmap
{
    TreeElement_ header;
#ifdef DEPRECATED
    PerStateInfo draw;
#endif
    PerStateInfo bitmap;
    PerStateInfo fg;
    PerStateInfo bg;
};

#define BITMAP_CONF_BITMAP 0x0001
#define BITMAP_CONF_FG 0x0002
#define BITMAP_CONF_BG 0x0004
#ifdef DEPRECATED
#define BITMAP_CONF_DRAW 0x0008
#endif

static Tk_OptionSpec bitmapOptionSpecs[] = {
    {TK_OPTION_CUSTOM, "-background", (char *) NULL, (char *) NULL,
     (char *) NULL,
     Tk_Offset(ElementBitmap, bg.obj), Tk_Offset(ElementBitmap, bg),
     TK_OPTION_NULL_OK, (ClientData) NULL, BITMAP_CONF_BG},
    {TK_OPTION_CUSTOM, "-bitmap", (char *) NULL, (char *) NULL,
     (char *) NULL,
     Tk_Offset(ElementBitmap, bitmap.obj), Tk_Offset(ElementBitmap, bitmap),
     TK_OPTION_NULL_OK, (ClientData) NULL, BITMAP_CONF_BITMAP},
#ifdef DEPRECATED
    {TK_OPTION_CUSTOM, "-draw", (char *) NULL, (char *) NULL,
     (char *) NULL,
     Tk_Offset(ElementBitmap, draw.obj), Tk_Offset(ElementBitmap, draw),
     TK_OPTION_NULL_OK, (ClientData) NULL, BITMAP_CONF_DRAW},
#endif
    {TK_OPTION_CUSTOM, "-foreground", (char *) NULL, (char *) NULL,
     (char *) NULL,
     Tk_Offset(ElementBitmap, fg.obj), Tk_Offset(ElementBitmap, fg),
     TK_OPTION_NULL_OK, (ClientData) NULL, BITMAP_CONF_FG},
    {TK_OPTION_END, (char *) NULL, (char *) NULL, (char *) NULL,
     (char *) NULL, 0, -1, 0, (ClientData) NULL, 0}
};

static void DeleteProcBitmap(TreeElementArgs *args)
{
/*    TreeCtrl *tree = args->tree;
    TreeElement elem = args->elem;
    ElementBitmap *elemX = (ElementBitmap *) elem;*/
}

static int WorldChangedProcBitmap(TreeElementArgs *args)
{
    int flagM = args->change.flagMaster;
    int flagS = args->change.flagSelf;
    int mask = 0;

    if ((flagS | flagM) & BITMAP_CONF_BITMAP)
	mask |= CS_DISPLAY | CS_LAYOUT;

    if ((flagS | flagM) & (
#ifdef DEPRECATED
	    BITMAP_CONF_DRAW |
#endif
	    BITMAP_CONF_FG | BITMAP_CONF_BG))
	mask |= CS_DISPLAY;

    return mask;
}

static int ConfigProcBitmap(TreeElementArgs *args)
{
    TreeCtrl *tree = args->tree;
    TreeElement elem = args->elem;
    ElementBitmap *elemX = (ElementBitmap *) elem;
    Tk_SavedOptions savedOptions;
    int error;
    Tcl_Obj *errorResult = NULL;

    for (error = 0; error <= 1; error++) {
	if (error == 0) {
	    if (Tree_SetOptions(tree, elem->stateDomain, elemX,
			elem->typePtr->optionTable,
			args->config.objc, args->config.objv,
			&savedOptions, &args->config.flagSelf) != TCL_OK) {
		args->config.flagSelf = 0;
		continue;
	    }

	    Tk_FreeSavedOptions(&savedOptions);
	    break;
	} else {
	    errorResult = Tcl_GetObjResult(tree->interp);
	    Tcl_IncrRefCount(errorResult);
	    Tk_RestoreSavedOptions(&savedOptions);

	    Tcl_SetObjResult(tree->interp, errorResult);
	    Tcl_DecrRefCount(errorResult);
	    return TCL_ERROR;
	}
    }

    return TCL_OK;
}

static int CreateProcBitmap(TreeElementArgs *args)
{
    return TCL_OK;
}

static void DisplayProcBitmap(TreeElementArgs *args)
{
    TreeCtrl *tree = args->tree;
    TreeElement elem = args->elem;
    ElementBitmap *elemX = (ElementBitmap *) elem;
    ElementBitmap *masterX = (ElementBitmap *) elem->master;
    int state = args->state;
    int x = args->display.x, y = args->display.y;
    int width, height;
    int match, match2;
#ifdef DEPRECATED
    int draw;
#endif
    Pixmap bitmap;
    XColor *fg, *bg;
    int imgW, imgH;
    int inHeader = elem->stateDomain == STATE_DOMAIN_HEADER;
    int columnState = COLUMN_STATE_NORMAL;

#ifdef DEPRECATED
    BOOLEAN_FOR_STATE(draw, draw, state)
    if (!draw)
	return;
#endif

    BITMAP_FOR_STATE(bitmap, bitmap, state)
    if (bitmap == None)
	return;

    COLOR_FOR_STATE(fg, fg, state)
    COLOR_FOR_STATE(bg, bg, state)

    Tk_SizeOfBitmap(tree->display, bitmap, &imgW, &imgH);
    width = imgW, height = imgH;
    AdjustForSticky(args->display.sticky,
	args->display.width, args->display.height,
	FALSE, FALSE,
	&x, &y, &width, &height);
#if HEADER_OFFSET_HACK == 1
    if (inHeader) {
	if (state & STATE_HEADER_ACTIVE)
	    columnState = COLUMN_STATE_ACTIVE;
	else if (state & STATE_HEADER_PRESSED)
	    columnState = COLUMN_STATE_PRESSED;
    }
    if (inHeader && columnState == COLUMN_STATE_PRESSED) {
	/* If this bitmap fills the whole header, don't offset it. */
	/* FIXME: should have a layout option to control this. */
	if (imgW < args->display.spanBbox.width ||
		imgH < args->display.spanBbox.height) {
	    x += 1, y += 1;
	}
    }
#endif
    if (imgW > args->display.width)
	imgW = args->display.width;
    if (imgH > args->display.height)
	imgH = args->display.height;
    Tree_DrawBitmap(tree, bitmap, args->display.drawable, fg, bg,
	0, 0, (unsigned int) imgW, (unsigned int) imgH,
	x, y);
}

static void NeededProcBitmap(TreeElementArgs *args)
{
    TreeCtrl *tree = args->tree;
    TreeElement elem = args->elem;
    ElementBitmap *elemX = (ElementBitmap *) elem;
    ElementBitmap *masterX = (ElementBitmap *) elem->master;
    int state = args->state;
    int width = 0, height = 0;
    int match, match2;
    Pixmap bitmap;

    BITMAP_FOR_STATE(bitmap, bitmap, state)

    if (bitmap != None)
	Tk_SizeOfBitmap(tree->display, bitmap, &width, &height);

    args->needed.width = width;
    args->needed.height = height;
}

static int StateProcBitmap(TreeElementArgs *args)
{
    TreeCtrl *tree = args->tree;
    TreeElement elem = args->elem;
    ElementBitmap *elemX = (ElementBitmap *) elem;
    ElementBitmap *masterX = (ElementBitmap *) elem->master;
    int match, match2;
#ifdef DEPRECATED
    int draw1, draw2;
#endif
    Pixmap bitmap1, bitmap2;
    XColor *fg1, *fg2;
    XColor *bg1, *bg2;

    if (!args->states.visible2)
	return 0;

    BITMAP_FOR_STATE(bitmap1, bitmap, args->states.state1)
    BITMAP_FOR_STATE(bitmap2, bitmap, args->states.state2)
    if (bitmap1 != bitmap2) {
	if ((bitmap1 != None) && (bitmap2 != None)) {
	    int w1, h1, w2, h2;
	    Tk_SizeOfBitmap(tree->display, bitmap1, &w1, &h1);
	    Tk_SizeOfBitmap(tree->display, bitmap2, &w2, &h2);
	    if ((w1 != w2) || (h1 != h2))
		return CS_DISPLAY | CS_LAYOUT;
	    return CS_DISPLAY;
	}
	return CS_DISPLAY | CS_LAYOUT;
    }

    /* Layout hasn't changed, and -draw layout option is false. */
    if (!args->states.draw2)
	return 0;
#ifdef DEPRECATED
    BOOLEAN_FOR_STATE(draw1, draw, args->states.state1)
    BOOLEAN_FOR_STATE(draw2, draw, args->states.state2)
    if ((draw1 != 0) != (draw2 != 0))
	return CS_DISPLAY;
    if (draw2 == 0)
	return 0;
#endif

    COLOR_FOR_STATE(fg1, fg, args->states.state1)
    COLOR_FOR_STATE(fg2, fg, args->states.state2)
    if (fg1 != fg2)
	return CS_DISPLAY;

    COLOR_FOR_STATE(bg1, bg, args->states.state1)
    COLOR_FOR_STATE(bg2, bg, args->states.state2)
    if (bg1 != bg2)
	return CS_DISPLAY;

    return 0;
}

static int UndefProcBitmap(TreeElementArgs *args)
{
    TreeCtrl *tree = args->tree;
    TreeElement elem = args->elem;
    ElementBitmap *elemX = (ElementBitmap *) elem;
    int modified = 0;

#ifdef DEPRECATED
    modified |= PerStateInfo_Undefine(tree, &pstBoolean, &elemX->draw, elem->stateDomain, args->state);
#endif
    modified |= PerStateInfo_Undefine(tree, &pstColor, &elemX->fg, elem->stateDomain, args->state);
    modified |= PerStateInfo_Undefine(tree, &pstColor, &elemX->bg, elem->stateDomain, args->state);
    modified |= PerStateInfo_Undefine(tree, &pstBitmap, &elemX->bitmap, elem->stateDomain, args->state);
    return modified;
}

static int ActualProcBitmap(TreeElementArgs *args)
{
    TreeCtrl *tree = args->tree;
    ElementBitmap *elemX = (ElementBitmap *) args->elem;
    ElementBitmap *masterX = (ElementBitmap *) args->elem->master;
    static CONST char *optionName[] = {
	"-background", "-bitmap",
#ifdef DEPRECATED
	"-draw",
#endif
	"-foreground",
	(char *) NULL };
    int index, match, matchM;
    Tcl_Obj *obj = NULL;

    if (Tcl_GetIndexFromObj(tree->interp, args->actual.obj, optionName,
		"option", 0, &index) != TCL_OK)
	return TCL_ERROR;

    switch (index) {
	case 0: {
	    OBJECT_FOR_STATE(obj, pstColor, bg, args->state)
	    break;
	}
	case 1: {
	    OBJECT_FOR_STATE(obj, pstBitmap, bitmap, args->state)
	    break;
	}
#ifdef DEPRECATED
	case 2: {
	    OBJECT_FOR_STATE(obj, pstBoolean, draw, args->state)
	    break;
	}
	case 3: {
	    OBJECT_FOR_STATE(obj, pstColor, fg, args->state)
	    break;
	}
#else
	case 2: {
	    OBJECT_FOR_STATE(obj, pstColor, fg, args->state)
	    break;
	}
#endif
    }
    if (obj != NULL)
	Tcl_SetObjResult(tree->interp, obj);
    return TCL_OK;
}

TreeElementType treeElemTypeBitmap = {
    "bitmap",
    sizeof(ElementBitmap),
    bitmapOptionSpecs,
    NULL,
    CreateProcBitmap,
    DeleteProcBitmap,
    ConfigProcBitmap,
    DisplayProcBitmap,
    NeededProcBitmap,
    NULL, /* heightProc */
    WorldChangedProcBitmap,
    StateProcBitmap,
    UndefProcBitmap,
    ActualProcBitmap,
    NULL /* onScreenProc */
};

/*****/

typedef struct ElementBorder ElementBorder;

struct ElementBorder
{
    TreeElement_ header; /* Must be first */
#ifdef DEPRECATED
    PerStateInfo draw;
#endif
    PerStateInfo border;
    PerStateInfo relief;
    int thickness;
    Tcl_Obj *thicknessObj;
    int width;
    Tcl_Obj *widthObj;
    int height;
    Tcl_Obj *heightObj;
    int filled;
};

#define BORDER_CONF_BG 0x0001
#define BORDER_CONF_RELIEF 0x0002
#define BORDER_CONF_SIZE 0x0004
#define BORDER_CONF_THICKNESS 0x0008
#define BORDER_CONF_FILLED 0x0010
#ifdef DEPRECATED
#define BORDER_CONF_DRAW 0x0020
#endif

static Tk_OptionSpec borderOptionSpecs[] = {
    {TK_OPTION_CUSTOM, "-background", (char *) NULL, (char *) NULL,
     (char *) NULL,
     Tk_Offset(ElementBorder, border.obj), Tk_Offset(ElementBorder, border),
     TK_OPTION_NULL_OK, (ClientData) NULL, BORDER_CONF_BG},
#ifdef DEPRECATED
    {TK_OPTION_CUSTOM, "-draw", (char *) NULL, (char *) NULL,
     (char *) NULL,
     Tk_Offset(ElementBorder, draw.obj), Tk_Offset(ElementBorder, draw),
     TK_OPTION_NULL_OK, (ClientData) NULL, BORDER_CONF_DRAW},
#endif
    {TK_OPTION_CUSTOM, "-filled", (char *) NULL, (char *) NULL,
     (char *) NULL, -1, Tk_Offset(ElementBorder, filled),
     TK_OPTION_NULL_OK, (ClientData) &booleanCO, BORDER_CONF_FILLED},
    {TK_OPTION_PIXELS, "-height", (char *) NULL, (char *) NULL,
     (char *) NULL, Tk_Offset(ElementBorder, heightObj),
     Tk_Offset(ElementBorder, height),
     TK_OPTION_NULL_OK, (ClientData) NULL, BORDER_CONF_SIZE},
    {TK_OPTION_CUSTOM, "-relief", (char *) NULL, (char *) NULL,
     (char *) NULL,
     Tk_Offset(ElementBorder, relief.obj), Tk_Offset(ElementBorder, relief),
     TK_OPTION_NULL_OK, (ClientData) NULL, BORDER_CONF_RELIEF},
    {TK_OPTION_PIXELS, "-thickness", (char *) NULL, (char *) NULL,
     (char *) NULL, Tk_Offset(ElementBorder, thicknessObj),
     Tk_Offset(ElementBorder, thickness),
     TK_OPTION_NULL_OK, (ClientData) NULL, BORDER_CONF_THICKNESS},
    {TK_OPTION_PIXELS, "-width", (char *) NULL, (char *) NULL,
     (char *) NULL, Tk_Offset(ElementBorder, widthObj),
     Tk_Offset(ElementBorder, width),
     TK_OPTION_NULL_OK, (ClientData) NULL, BORDER_CONF_SIZE},
    {TK_OPTION_END, (char *) NULL, (char *) NULL, (char *) NULL,
     (char *) NULL, 0, -1, 0, (ClientData) NULL, 0}
};

static void DeleteProcBorder(TreeElementArgs *args)
{
/*    TreeCtrl *tree = args->tree;
    TreeElement elem = args->elem;
    ElementBorder *elemX = (ElementBorder *) elem;*/
}

static int WorldChangedProcBorder(TreeElementArgs *args)
{
    int flagM = args->change.flagMaster;
    int flagS = args->change.flagSelf;
    int mask = 0;

    if ((flagS | flagM) & BORDER_CONF_SIZE)
	mask |= CS_DISPLAY | CS_LAYOUT;

    if ((flagS | flagM) & (
#ifdef DEPRECATED
	    BORDER_CONF_DRAW |
#endif
	    BORDER_CONF_BG | BORDER_CONF_RELIEF | BORDER_CONF_THICKNESS |
	    BORDER_CONF_FILLED))
	mask |= CS_DISPLAY;

    return mask;
}

static int ConfigProcBorder(TreeElementArgs *args)
{
    TreeCtrl *tree = args->tree;
    TreeElement elem = args->elem;
    ElementBorder *elemX = (ElementBorder *) elem;
    Tk_SavedOptions savedOptions;
    int error;
    Tcl_Obj *errorResult = NULL;

    for (error = 0; error <= 1; error++) {
	if (error == 0) {
	    if (Tree_SetOptions(tree, elem->stateDomain, elemX,
			elem->typePtr->optionTable,
			args->config.objc, args->config.objv,
			&savedOptions, &args->config.flagSelf) != TCL_OK) {
		args->config.flagSelf = 0;
		continue;
	    }

	    Tk_FreeSavedOptions(&savedOptions);
	    break;
	} else {
	    errorResult = Tcl_GetObjResult(tree->interp);
	    Tcl_IncrRefCount(errorResult);
	    Tk_RestoreSavedOptions(&savedOptions);

	    Tcl_SetObjResult(tree->interp, errorResult);
	    Tcl_DecrRefCount(errorResult);
	    return TCL_ERROR;
	}
    }

    return TCL_OK;
}

static int CreateProcBorder(TreeElementArgs *args)
{
    TreeElement elem = args->elem;
    ElementBorder *elemX = (ElementBorder *) elem;

    elemX->filled = -1;
    return TCL_OK;
}

static void DisplayProcBorder(TreeElementArgs *args)
{
    TreeCtrl *tree = args->tree;
    TreeElement elem = args->elem;
    ElementBorder *elemX = (ElementBorder *) elem;
    ElementBorder *masterX = (ElementBorder *) elem->master;
    int state = args->state;
    int x = args->display.x, y = args->display.y;
    int width = args->display.width, height = args->display.height;
    int match, match2;
#ifdef DEPRECATED
    int draw;
#endif
    Tk_3DBorder border;
    int relief, filled = FALSE;
    int thickness = 0;
#if USE_ITEM_PIXMAP == 0
    TreeClip clip;
#endif
    TreeRectangle tr1, tr2;

#ifdef DEPRECATED
    BOOLEAN_FOR_STATE(draw, draw, state)
    if (!draw)
	return;
#endif

    BORDER_FOR_STATE(border, border, state)
    if (border == NULL)
	return;

    RELIEF_FOR_STATE(relief, relief, state)
    if (relief == TK_RELIEF_NULL)
	relief = TK_RELIEF_FLAT;

    if (elemX->thicknessObj)
	thickness = elemX->thickness;
    else if ((masterX != NULL) && (masterX->thicknessObj != NULL))
	thickness = masterX->thickness;

    if (elemX->filled != -1)
	filled = elemX->filled;
    else if ((masterX != NULL) && (masterX->filled != -1))
	filled = masterX->filled;

    if (elemX->widthObj != NULL)
	width = elemX->width;
    else if ((masterX != NULL) && (masterX->widthObj != NULL))
	width = masterX->width;

    if (elemX->heightObj != NULL)
	height = elemX->height;
    else if ((masterX != NULL) && (masterX->heightObj != NULL))
	height = masterX->height;

    AdjustForSticky(args->display.sticky,
	args->display.width, args->display.height,
	TRUE, TRUE,
	&x, &y, &width, &height);

    /* X11 coordinates are 16-bit. */
    TreeRect_SetXYWH(tr1, x, y, width, height);
    TreeRect_SetXYWH(tr2, -thickness, -thickness,
	args->display.td.width + thickness * 2,
	args->display.td.height + thickness * 2);
    TreeRect_Intersect(&tr1, &tr1, &tr2);

#if USE_ITEM_PIXMAP == 0
    /* Using a region instead of a rect because of how Tree_Fill3DRectangle
     * and Tree_Draw3DRectangle are implemented. */
    clip.type = TREE_CLIP_REGION;
    clip.region = Tree_GetRectRegion(tree, &args->display.bounds);

    if (filled) {
	Tree_Fill3DRectangle(tree, args->display.td, &clip, border,
		tr1.x, tr1.y, tr1.width, tr1.height, thickness, relief);
    } else if (thickness > 0) {
	Tree_Draw3DRectangle(tree, args->display.td, &clip, border,
		tr1.x, tr1.y, tr1.width, tr1.height, thickness, relief);
    }

    Tree_FreeRegion(tree, clip.region);
#else
    if (filled) {
	Tk_Fill3DRectangle(tree->tkwin, args->display.drawable, border,
		tr1.x, tr1.y, tr1.width, tr1.height, thickness, relief);
    } else if (thickness > 0) {
	Tk_Draw3DRectangle(tree->tkwin, args->display.drawable, border,
		tr1.x, tr1.y, tr1.width, tr1.height, thickness, relief);
    }
#endif
}

static void NeededProcBorder(TreeElementArgs *args)
{
    TreeElement elem = args->elem;
    ElementBorder *elemX = (ElementBorder *) elem;
    ElementBorder *masterX = (ElementBorder *) elem->master;
    int width = 0, height = 0;

    if (elemX->widthObj != NULL)
	width = elemX->width;
    else if ((masterX != NULL) && (masterX->widthObj != NULL))
	width = masterX->width;

    if (elemX->heightObj != NULL)
	height = elemX->height;
    else if ((masterX != NULL) && (masterX->heightObj != NULL))
	height = masterX->height;

    args->needed.width = width;
    args->needed.height = height;
}

static int StateProcBorder(TreeElementArgs *args)
{
    TreeCtrl *tree = args->tree;
    TreeElement elem = args->elem;
    ElementBorder *elemX = (ElementBorder *) elem;
    ElementBorder *masterX = (ElementBorder *) elem->master;
    int match, match2;
#ifdef DEPRECATED
    int draw1, draw2;
#endif
    Tk_3DBorder border1, border2;
    int relief1, relief2;

    if (!args->states.visible2 || !args->states.draw2)
	return 0;

#ifdef DEPRECATED
    BOOLEAN_FOR_STATE(draw1, draw, args->states.state1)
    BOOLEAN_FOR_STATE(draw2, draw, args->states.state2)
    if ((draw1 != 0) != (draw2 != 0))
	return CS_DISPLAY;
    if (draw2 == 0)
	return 0;
#endif

    BORDER_FOR_STATE(border1, border, args->states.state1)
    BORDER_FOR_STATE(border2, border, args->states.state2)
    if (border1 != border2)
	return CS_DISPLAY;

    RELIEF_FOR_STATE(relief1, relief, args->states.state1)
    RELIEF_FOR_STATE(relief2, relief, args->states.state2)
    if (relief1 != relief2)
	return CS_DISPLAY;

    return 0;
}

static int UndefProcBorder(TreeElementArgs *args)
{
    TreeCtrl *tree = args->tree;
    TreeElement elem = args->elem;
    ElementBorder *elemX = (ElementBorder *) elem;
    int modified = 0;

#ifdef DEPRECATED
    modified |= PerStateInfo_Undefine(tree, &pstBoolean, &elemX->draw, elem->stateDomain, args->state);
#endif
    modified |= PerStateInfo_Undefine(tree, &pstBorder, &elemX->border, elem->stateDomain, args->state);
    modified |= PerStateInfo_Undefine(tree, &pstRelief, &elemX->relief, elem->stateDomain, args->state);
    return modified;
}

static int ActualProcBorder(TreeElementArgs *args)
{
    TreeCtrl *tree = args->tree;
    ElementBorder *elemX = (ElementBorder *) args->elem;
    ElementBorder *masterX = (ElementBorder *) args->elem->master;
    static CONST char *optionName[] = {
	"-background",
#ifdef DEPRECATED
	"-draw",
#endif
	"-relief",
	(char *) NULL };
    int index, match, matchM;
    Tcl_Obj *obj = NULL;

    if (Tcl_GetIndexFromObj(tree->interp, args->actual.obj, optionName,
		"option", 0, &index) != TCL_OK)
	return TCL_ERROR;

    switch (index) {
	case 0: {
	    OBJECT_FOR_STATE(obj, pstBorder, border, args->state)
	    break;
	}
#ifdef DEPRECATED
	case 1: {
	    OBJECT_FOR_STATE(obj, pstBoolean, draw, args->state)
	    break;
	}
	case 2: {
	    OBJECT_FOR_STATE(obj, pstRelief, relief, args->state)
	    break;
	}
#else
	case 1: {
	    OBJECT_FOR_STATE(obj, pstRelief, relief, args->state)
	    break;
	}
#endif
    }
    if (obj != NULL)
	Tcl_SetObjResult(tree->interp, obj);
    return TCL_OK;
}

TreeElementType treeElemTypeBorder = {
    "border",
    sizeof(ElementBorder),
    borderOptionSpecs,
    NULL,
    CreateProcBorder,
    DeleteProcBorder,
    ConfigProcBorder,
    DisplayProcBorder,
    NeededProcBorder,
    NULL, /* heightProc */
    WorldChangedProcBorder,
    StateProcBorder,
    UndefProcBorder,
    ActualProcBorder,
    NULL /* onScreenProc */
};

/*****/
#if 0

static CONST char *chkbutStateST[] = {
    "checked", "mixed", "normal", "active", "pressed", "disabled", (char *) NULL
};

typedef struct ElementCheckButton ElementCheckButton;

struct ElementCheckButton
{
    TreeElement_ header;
    PerStateInfo image;
    int state;
};

#define CHKBUT_CONF_IMAGE 0x0001
#define CHKBUT_CONF_STATE 0x0002

static Tk_OptionSpec chkbutOptionSpecs[] = {
    {TK_OPTION_STRING, "-image", (char *) NULL, (char *) NULL,
     (char *) NULL, Tk_Offset(ElementCheckButton, image.obj), -1,
     TK_OPTION_NULL_OK, (ClientData) NULL, CHKBUT_CONF_IMAGE},
    {TK_OPTION_STRING_TABLE, "-state", (char *) NULL, (char *) NULL,
     "normal", -1, Tk_Offset(ElementCheckButton, state),
     0, (ClientData) chkbutStateST, CHKBUT_CONF_STATE},
    {TK_OPTION_END, (char *) NULL, (char *) NULL, (char *) NULL,
     (char *) NULL, 0, -1, 0, (ClientData) NULL, 0}
};

static void DeleteProcCheckButton(TreeElementArgs *args)
{
    TreeCtrl *tree = args->tree;
    TreeElement elem = args->elem;
    ElementCheckButton *elemX = (ElementCheckButton *) elem;

    PerStateInfo_Free(tree, &pstImage, &elemX->image);
}

static int WorldChangedProcCheckButton(TreeElementArgs *args)
{
    int flagM = args->change.flagMaster;
    int flagS = args->change.flagSelf;
    int mask = 0;

    if ((flagS | flagM) & (CHKBUT_CONF_IMAGE | CHKBUT_CONF_STATE))
	mask |= CS_DISPLAY | CS_LAYOUT;

    return mask;
}

static int ChkButStateFromObj(TreeCtrl *tree, Tcl_Obj *obj, int *stateOff, int *stateOn)
{
    Tcl_Interp *interp = tree->interp;
    int i, op = STATE_OP_ON, op2, op3, length, state = 0;
    char ch0, *string;
    int states[3];

    states[STATE_OP_ON] = 0;
    states[STATE_OP_OFF] = 0;
    states[STATE_OP_TOGGLE] = 0;

    string = Tcl_GetStringFromObj(obj, &length);
    if (length == 0)
	goto unknown;
    ch0 = string[0];
    if (ch0 == '!') {
	op = STATE_OP_OFF;
	++string;
	ch0 = string[0];
    } else if (ch0 == '~') {
	if (1) {
	    FormatResult(interp, "can't specify '~' for this command");
	    return TCL_ERROR;
	}
	op = STATE_OP_TOGGLE;
	++string;
	ch0 = string[0];
    }
    for (i = 0; chkbutStateST[i] != NULL; i++) {
	if ((ch0 == chkbutStateST[i][0]) && !strcmp(string, chkbutStateST[i])) {
	    state = 1L << i;
	    break;
	}
    }
    if (state == 0)
	goto unknown;

    if (op == STATE_OP_ON) {
	op2 = STATE_OP_OFF;
	op3 = STATE_OP_TOGGLE;
    }
    else if (op == STATE_OP_OFF) {
	op2 = STATE_OP_ON;
	op3 = STATE_OP_TOGGLE;
    } else {
	op2 = STATE_OP_ON;
	op3 = STATE_OP_OFF;
    }
    states[op2] &= ~state;
    states[op3] &= ~state;
    states[op] |= state;

    *stateOn |= states[STATE_OP_ON];
    *stateOff |= states[STATE_OP_OFF];

    return TCL_OK;

unknown:
    FormatResult(interp, "unknown state \"%s\"", string);
    return TCL_ERROR;
}

static int ConfigProcCheckButton(TreeElementArgs *args)
{
    TreeCtrl *tree = args->tree;
    TreeElement elem = args->elem;
    ElementCheckButton *elemX = (ElementCheckButton *) elem;
    ElementCheckButton savedX;
    Tk_SavedOptions savedOptions;
    int error;
    Tcl_Obj *errorResult = NULL;

    for (error = 0; error <= 1; error++) {
	if (error == 0) {
	    if (Tree_SetOptions(tree, tree, elem->stateDomain,
			elem->typePtr->optionTable,
			args->config.objc, args->config.objv,
			&savedOptions, &args->config.flagSelf) != TCL_OK) {
		args->config.flagSelf = 0;
		continue;
	    }

	    if (args->config.flagSelf & CHKBUT_CONF_IMAGE)
		PSTSave(&elemX->image, &savedX.image);

	    if (args->config.flagSelf & CHKBUT_CONF_IMAGE) {
		if (PerStateInfo_FromObj(tree, ChkButStateFromObj, &pstImage, &elemX->image) != TCL_OK)
		    continue;
	    }

	    if (args->config.flagSelf & CHKBUT_CONF_IMAGE)
		PerStateInfo_Free(tree, &pstImage, &savedX.image);
	    Tk_FreeSavedOptions(&savedOptions);
	    break;
	} else {
	    errorResult = Tcl_GetObjResult(tree->interp);
	    Tcl_IncrRefCount(errorResult);
	    Tk_RestoreSavedOptions(&savedOptions);

	    if (args->config.flagSelf & CHKBUT_CONF_IMAGE)
		PSTRestore(tree, &pstImage, &elemX->image, &savedX.image);

	    Tcl_SetObjResult(tree->interp, errorResult);
	    Tcl_DecrRefCount(errorResult);
	    return TCL_ERROR;
	}
    }

    return TCL_OK;
}

static int CreateProcCheckButton(TreeElementArgs *args)
{
    return TCL_OK;
}

static void DisplayProcCheckButton(TreeElementArgs *args)
{
    TreeCtrl *tree = args->tree;
    TreeElement elem = args->elem;
    ElementCheckButton *elemX = (ElementCheckButton *) elem;
    ElementCheckButton *masterX = (ElementCheckButton *) elem->master;
    int state = args->state;
    int match, matchM;
    Tk_Image image;
    int imgW, imgH;
    int dx = 0, dy = 0;

    image = PerStateImage_ForState(tree, &elemX->image, state, &match);
    if ((match != MATCH_EXACT) && (masterX != NULL)) {
	Tk_Image imageM = PerStateImage_ForState(tree, &masterX->image,
		state, &matchM);
	if (matchM > match)
	    image = imageM;
    }

    if (image != NULL) {
	Tk_SizeOfImage(image, &imgW, &imgH);
	if (imgW < args->display.width)
	    dx = (args->display.width - imgW) / 2;
	else if (imgW > args->display.width)
	    imgW = args->display.width;
	if (imgH < args->display.height)
	    dy = (args->display.height - imgH) / 2;
	else if (imgH > args->display.height)
	    imgH = args->display.height;
	Tk_RedrawImage(image, 0, 0, imgW, imgH, args->display.drawable,
		args->display.x + dx,
		args->display.y + dy);
    }
}

static void NeededProcCheckButton(TreeElementArgs *args)
{
    TreeCtrl *tree = args->tree;
    TreeElement elem = args->elem;
    ElementCheckButton *elemX = (ElementCheckButton *) elem;
    ElementCheckButton *masterX = (ElementCheckButton *) elem->master;
    int state = args->state;
    int match, match2;
    Tk_Image image;
    int width = 0, height = 0;

    image = PerStateImage_ForState(tree, &elemX->image, state, &match);
    if ((match != MATCH_EXACT) && (masterX != NULL)) {
	Tk_Image image2 = PerStateImage_ForState(tree, &masterX->image,
		state, &match2);
	if (match2 > match)
	    image = image2;
    }

    if (image != NULL)
	Tk_SizeOfImage(image, &width, &height);

    args->layout.width = width;
    args->layout.height = height;
}

static int StateProcCheckButton(TreeElementArgs *args)
{
    TreeCtrl *tree = args->tree;
    TreeElement elem = args->elem;
    ElementCheckButton *elemX = (ElementCheckButton *) elem;
    ElementCheckButton *masterX = (ElementCheckButton *) elem->master;
    int match, match2;
    Tk_Image image1, image2;
    int mask = 0;

    image1 = PerStateImage_ForState(tree, &elemX->image,
	    args->states.state1, &match);
    if ((match != MATCH_EXACT) && (masterX != NULL)) {
	Tk_Image image = PerStateImage_ForState(tree, &masterX->image, args->states.state1, &match2);
	if (match2 > match)
	    image1 = image;
    }

    image2 = PerStateImage_ForState(tree, &elemX->image,
	    args->states.state2, &match);
    if ((match != MATCH_EXACT) && (masterX != NULL)) {
	Tk_Image image = PerStateImage_ForState(tree, &masterX->image,
		args->states.state2, &match2);
	if (match2 > match)
	    image2 = image;
    }

    if (image1 != image2) {
	mask |= CS_DISPLAY;
	if ((image1 != NULL) && (image2 != NULL)) {
	    int w1, h1, w2, h2;
	    Tk_SizeOfImage(image1, &w1, &h1);
	    Tk_SizeOfImage(image2, &w2, &h2);
	    if ((w1 != w2) || (h1 != h2))
		mask |= CS_LAYOUT;
	} else
	    mask |= CS_LAYOUT;
    }

    return mask;
}

static int UndefProcCheckButton(TreeElementArgs *args)
{
    TreeCtrl *tree = args->tree;
    ElementCheckButton *elemX = (ElementCheckButton *) args->elem;

    return PerStateInfo_Undefine(tree, &pstImage, &elemX->image, args->state);
}

static int ActualProcCheckButton(TreeElementArgs *args)
{
    TreeCtrl *tree = args->tree;
    ElementCheckButton *elemX = (ElementCheckButton *) args->elem;
    ElementCheckButton *masterX = (ElementCheckButton *) args->elem->master;
    static CONST char *optionName[] = {
	"-image",
	(char *) NULL };
    int index, match, matchM;
    Tcl_Obj *obj = NULL;

    if (Tcl_GetIndexFromObj(tree->interp, args->actual.obj, optionName,
		"option", 0, &index) != TCL_OK)
	return TCL_ERROR;

    switch (index) {
	case 0: {
	    obj = PerStateInfo_ObjForState(tree, &pstImage,
		    &elemX->image, args->state, &match);
	    if ((match != MATCH_EXACT) && (masterX != NULL)) {
		objM = PerStateInfo_ObjForState(tree, &pstImage,
			&masterX->image, args->state, &matchM);
		if (matchM > match)
		    obj = objM;
	    }
	    break;
	}
    }
    if (obj != NULL)
	Tcl_SetObjResult(tree->interp, obj);
    return TCL_OK;
}

TreeElementType treeElemTypeCheckButton = {
    "checkbutton",
    sizeof(ElementCheckButton),
    chkbutOptionSpecs,
    NULL,
    CreateProcCheckButton,
    DeleteProcCheckButton,
    ConfigProcCheckButton,
    DisplayProcCheckButton,
    NeededProcCheckButton,
    NULL, /* heightProc */
    WorldChangedProcCheckButton,
    StateProcCheckButton,
    UndefProcCheckButton,
    ActualProcCheckButton,
    NULL /* onScreenProc */
};

#endif

/*****/

typedef struct ElementHeader ElementHeader;

struct ElementHeader
{
    TreeElement_ header; /* Must be first */
    PerStateInfo border;	/* -background */
    int borderWidth;		/* -borderwidth */
    Tcl_Obj *borderWidthObj;	/* -borderwidth */
    PerStateInfo arrowBitmap;	/* -arrowbitmap */
    PerStateInfo arrowImage;	/* -arrowimage */
    Tcl_Obj *arrowPadXObj;	/* -arrowpadx */
    int *arrowPadX;		/* -arrowpadx */
    Tcl_Obj *arrowPadYObj;	/* -arrowpady */
    int *arrowPadY;		/* -arrowpady */
    int arrow;			/* -arrow */
#define SIDE_LEFT 0
#define SIDE_RIGHT 1
    int arrowSide;		/* -arrowside */
    int arrowGravity;		/* -arrowgravity */
    int state;			/* -state */
};

static CONST char *headerStateST[] = { "normal", "active", "pressed", (char *) NULL };
static CONST char *headerArrowST[] = { "none", "up", "down", (char *) NULL };
static CONST char *headerArrowSideST[] = { "left", "right", (char *) NULL };

#define HEADER_CONF_SIZE 0x0001
#define HEADER_CONF_DISPLAY 0x0002

static Tk_OptionSpec headerOptionSpecs[] = {
    {TK_OPTION_CUSTOM, "-arrow", (char *) NULL, (char *) NULL,
     (char *) NULL, -1, Tk_Offset(ElementHeader, arrow), TK_OPTION_NULL_OK,
     (ClientData) NULL, HEADER_CONF_DISPLAY | HEADER_CONF_SIZE},
    {TK_OPTION_CUSTOM, "-arrowbitmap", (char *) NULL, (char *) NULL,
     (char *) NULL,
     Tk_Offset(ElementHeader, arrowBitmap.obj), Tk_Offset(ElementHeader, arrowBitmap),
     TK_OPTION_NULL_OK, (ClientData) NULL, HEADER_CONF_DISPLAY | HEADER_CONF_SIZE},
    {TK_OPTION_CUSTOM, "-arrowgravity", (char *) NULL, (char *) NULL,
     (char *) NULL, -1, Tk_Offset(ElementHeader, arrowGravity), TK_OPTION_NULL_OK,
     (ClientData) NULL, HEADER_CONF_DISPLAY | HEADER_CONF_SIZE},
    {TK_OPTION_CUSTOM, "-arrowimage", (char *) NULL, (char *) NULL,
     (char *) NULL,
     Tk_Offset(ElementHeader, arrowImage.obj), Tk_Offset(ElementHeader, arrowImage),
     TK_OPTION_NULL_OK, (ClientData) NULL, HEADER_CONF_DISPLAY | HEADER_CONF_SIZE},
    {TK_OPTION_CUSTOM, "-arrowpadx", (char *) NULL, (char *) NULL,
     (char *) NULL, Tk_Offset(ElementHeader, arrowPadXObj), Tk_Offset(ElementHeader, arrowPadX),
     TK_OPTION_NULL_OK, (ClientData) &TreeCtrlCO_pad, HEADER_CONF_DISPLAY | HEADER_CONF_SIZE},
    {TK_OPTION_CUSTOM, "-arrowpady", (char *) NULL, (char *) NULL,
     (char *) NULL, Tk_Offset(ElementHeader, arrowPadYObj), Tk_Offset(ElementHeader, arrowPadY),
     TK_OPTION_NULL_OK, (ClientData) &TreeCtrlCO_pad, HEADER_CONF_DISPLAY | HEADER_CONF_SIZE},
    {TK_OPTION_CUSTOM, "-arrowside", (char *) NULL, (char *) NULL,
     (char *) NULL, -1, Tk_Offset(ElementHeader, arrowSide), TK_OPTION_NULL_OK,
     (ClientData) NULL, HEADER_CONF_DISPLAY | HEADER_CONF_SIZE},
    {TK_OPTION_CUSTOM, "-background", (char *) NULL, (char *) NULL,
     (char *) NULL,
     Tk_Offset(ElementHeader, border.obj), Tk_Offset(ElementHeader, border),
     TK_OPTION_NULL_OK, (ClientData) NULL, HEADER_CONF_DISPLAY},
    {TK_OPTION_PIXELS, "-borderwidth", (char *) NULL, (char *) NULL,
     (char *) NULL, Tk_Offset(ElementHeader, borderWidthObj),
     Tk_Offset(ElementHeader, borderWidth),
     TK_OPTION_NULL_OK, (ClientData) NULL, HEADER_CONF_SIZE | HEADER_CONF_DISPLAY},
    {TK_OPTION_CUSTOM, "-state", (char *) NULL, (char *) NULL,
     (char *) NULL, -1, Tk_Offset(ElementHeader, state), TK_OPTION_NULL_OK,
     (ClientData) NULL, HEADER_CONF_DISPLAY},
    {TK_OPTION_END, (char *) NULL, (char *) NULL, (char *) NULL,
     (char *) NULL, 0, -1, 0, (ClientData) NULL, 0}
};

static void DeleteProcHeader(TreeElementArgs *args)
{
/*    TreeCtrl *tree = args->tree;
    TreeElement elem = args->elem;
    ElementHeader *elemX = (ElementHeader *) elem;*/
}

static int WorldChangedProcHeader(TreeElementArgs *args)
{
    int flagT = args->change.flagTree;
    int flagM = args->change.flagMaster;
    int flagS = args->change.flagSelf;
    int mask = 0;

    if (flagT & TREE_CONF_THEME)
	mask |= CS_DISPLAY | CS_LAYOUT;
    else if ((flagS | flagM) & HEADER_CONF_SIZE)
	mask |= CS_DISPLAY | CS_LAYOUT;
    else if ((flagS | flagM) & HEADER_CONF_DISPLAY)
	mask |= CS_DISPLAY;

    return mask;
}

static int ConfigProcHeader(TreeElementArgs *args)
{
    TreeCtrl *tree = args->tree;
    TreeElement elem = args->elem;
    ElementHeader *elemX = (ElementHeader *) elem;
    Tk_SavedOptions savedOptions;
    int error;
    Tcl_Obj *errorResult = NULL;

    for (error = 0; error <= 1; error++) {
	if (error == 0) {
	    if (Tree_SetOptions(tree, elem->stateDomain, elemX,
			elem->typePtr->optionTable,
			args->config.objc, args->config.objv,
			&savedOptions, &args->config.flagSelf) != TCL_OK) {
		args->config.flagSelf = 0;
		continue;
	    }

	    Tk_FreeSavedOptions(&savedOptions);
	    break;
	} else {
	    errorResult = Tcl_GetObjResult(tree->interp);
	    Tcl_IncrRefCount(errorResult);
	    Tk_RestoreSavedOptions(&savedOptions);

	    Tcl_SetObjResult(tree->interp, errorResult);
	    Tcl_DecrRefCount(errorResult);
	    return TCL_ERROR;
	}
    }

    return TCL_OK;
}

static int CreateProcHeader(TreeElementArgs *args)
{
    TreeElement elem = args->elem;
    ElementHeader *elemX = (ElementHeader *) elem;

    elemX->arrow = -1;
    elemX->arrowGravity = -1;
    elemX->arrowSide = -1;
    elemX->state = -1;

    return TCL_OK;
}

struct HeaderParams
{
    int state;		/* COLUMN_STATE_XXX */
    int arrow;		/* COLUMN_ARROW_XXX */
    int borderWidth;
    int margins[4];	/* content margins, not including arrow */
    int elemState;	/* STATE_XXX */
    int eUnionBbox[4];
    int iUnionBbox[4];
};

static void
HeaderGetParams(
    TreeCtrl *tree,
    ElementHeader *elemX,
    int state,
    struct HeaderParams *params
    )
{
    ElementHeader *masterX = (ElementHeader *) elemX->header.master;
    int i;

    params->elemState = state;

    params->state = COLUMN_STATE_NORMAL;
    if (elemX->state != -1)
	params->state = elemX->state;
    else if (masterX != NULL && masterX->state != -1)
	params->state = masterX->state;
    else if (elemX->header.stateDomain == STATE_DOMAIN_HEADER) {
	if (state & STATE_HEADER_ACTIVE) params->state = COLUMN_STATE_ACTIVE;
	if (state & STATE_HEADER_PRESSED) params->state = COLUMN_STATE_PRESSED;
    }

    params->arrow = COLUMN_ARROW_NONE;
    if (elemX->arrow != -1)
	params->arrow = elemX->arrow;
    else if (masterX != NULL && masterX->arrow != -1)
	params->arrow = masterX->arrow;
    else if (elemX->header.stateDomain == STATE_DOMAIN_HEADER) {
	if (state & STATE_HEADER_SORT_UP) params->arrow = COLUMN_ARROW_UP;
	if (state & STATE_HEADER_SORT_DOWN) params->arrow = COLUMN_ARROW_DOWN;
    }

    if (elemX->borderWidthObj)
	params->borderWidth = elemX->borderWidth;
    else if ((masterX != NULL) && (masterX->borderWidthObj != NULL))
	params->borderWidth = masterX->borderWidth;
    else
	params->borderWidth = 2; /* Column header -borderwidth defaults to 2. */
    if (params->borderWidth < 0)
	params->borderWidth = 2;

    if (tree->useTheme &&
	    (TreeTheme_GetHeaderContentMargins(tree, params->state,
	    params->arrow, params->margins) == TCL_OK)) {
#ifdef WIN32
	/* I'm hacking these margins since the default XP theme does not give
	 * reasonable ContentMargins for HP_HEADERITEM */
	int bw = MAX(params->borderWidth, 3);
	params->margins[1] = MAX(params->margins[1], bw);
	params->margins[3] = MAX(params->margins[3], bw);
#endif /* WIN32 */
    } else {
	params->margins[0] = params->margins[2] = 0;
	params->margins[1] = params->margins[3] = params->borderWidth;
    }

    for (i = 0; i < 4; i++)
	params->eUnionBbox[i] = params->iUnionBbox[i] = -1;
}

struct ArrowLayout {
    int arrow;
    int arrowSide;
    int x;
    int y;
    int width;
    int height;
    int padX[2];
    int padY[2];
};

static void
HeaderLayoutArrow(
    TreeCtrl *tree,
    ElementHeader *elemX,
    struct HeaderParams *params,
    int x, int y, int width, int height, /* bounds of whole element */
    int indent,
    struct ArrowLayout *layout
    )
{
    ElementHeader *masterX = (ElementHeader *) elemX->header.master;
    int state = params->elemState;
    int arrowSide, arrowWidth = -1, arrowGravity, arrowHeight;
    Tk_Image image;
    Pixmap bitmap;
    int defPadX[2] = {6, 6}, defPadY[2] = {0, 0}, *arrowPadX, *arrowPadY;
    int match, match2;
    int minX, maxX, padX[2];
#ifdef MAC_OSX_TK
    int margins[4];
#endif

    layout->arrow = params->arrow;
    if (layout->arrow == COLUMN_ARROW_NONE) {
	return;
    }

    arrowSide = elemX->arrowSide;
    if (arrowSide == -1 && masterX != NULL)
	arrowSide = masterX->arrowSide;
    if (arrowSide == -1)
	arrowSide = SIDE_RIGHT;

    arrowGravity = elemX->arrowGravity;
    if (arrowGravity == -1 && masterX != NULL)
	arrowGravity = masterX->arrowGravity;
    if (arrowGravity == -1)
	arrowGravity = SIDE_LEFT;

    arrowPadX = elemX->arrowPadX;
    if (arrowPadX == NULL && masterX != NULL) {
	arrowPadX = masterX->arrowPadX;
    }
    if (arrowPadX == NULL) {
	arrowPadX = defPadX;
    }

    arrowPadY = elemX->arrowPadY;
    if (arrowPadY == NULL && masterX != NULL) {
	arrowPadY = masterX->arrowPadY;
    }
    if (arrowPadY == NULL) {
	arrowPadY = defPadY;
    }

#ifdef MAC_OSX_TK
    /* Under Aqua, the Appearance Manager draws the sort arrow as part of
    * the header background. */
    if (tree->useTheme && TreeTheme_GetHeaderContentMargins(tree,
	    params->state, params->arrow, margins) == TCL_OK) {
	layout->arrowSide = SIDE_RIGHT;
	layout->width = margins[2];
	layout->x = width - layout->width;
	layout->y = 0;
	layout->height = 1; /* bogus value */
	/* The content margins do not include padding on the left of the
	 * sort arrow. */
	layout->padX[PAD_TOP_LEFT] = arrowPadX[PAD_TOP_LEFT];
	layout->padX[PAD_BOTTOM_RIGHT] = 0;
	layout->padY[PAD_TOP_LEFT] = layout->padY[PAD_BOTTOM_RIGHT] = 0;
	return;
    }
#endif

    if (arrowWidth == -1) {
	IMAGE_FOR_STATE(image, arrowImage, state);
	if (image != NULL) {
	    Tk_SizeOfImage(image, &arrowWidth, &arrowHeight);
	}
    }

    if (arrowWidth == -1) {
	BITMAP_FOR_STATE(bitmap, arrowBitmap, state);
	if (bitmap != None) {
	    Tk_SizeOfBitmap(tree->display, bitmap, &arrowWidth, &arrowHeight);
	}
    }

    if ((arrowWidth == -1) && tree->useTheme &&
	    TreeTheme_GetArrowSize(tree, Tk_WindowId(tree->tkwin),
	    params->arrow == COLUMN_ARROW_UP, &arrowWidth, &arrowHeight) == TCL_OK) {
	/* nothing */
    }

    if (arrowWidth == -1) {
#if 0
	/* This is the original calculation of arrow size. */
	Tk_Font tkfont = column->tkfont ? column->tkfont : tree->tkfont;
	Tk_FontMetrics fm;
	Tk_GetFontMetrics(tkfont, &fm);
	arrowWidth = (fm.linespace + column->textPadY[PAD_TOP_LEFT] +
	    column->textPadY[PAD_BOTTOM_RIGHT] + column->borderWidth * 2) / 2;
	if (!(arrowWidth & 1))
	    arrowWidth--;
	arrowHeight = arrowWidth;
#endif
	arrowWidth = 9; /* FIXME: -sortarrowwidth */
	arrowHeight = arrowWidth;
    }

    minX = x + indent;
    maxX = x + width;
    padX[PAD_TOP_LEFT] = padX[PAD_BOTTOM_RIGHT] = 0;
    if (arrowSide == SIDE_LEFT) {
	if (params->iUnionBbox[0] != -1) {
	    maxX = x + params->iUnionBbox[0];
	    padX[PAD_TOP_LEFT] = params->iUnionBbox[0] - params->eUnionBbox[0];
	}
    } else {
	if (params->iUnionBbox[2] != -1) {
	    minX = x + params->iUnionBbox[2];
	    padX[PAD_BOTTOM_RIGHT] = params->eUnionBbox[2] - params->iUnionBbox[2];
	}
    }

    if (arrowGravity == SIDE_LEFT) {
	layout->x = minX + MAX(padX[PAD_BOTTOM_RIGHT], arrowPadX[PAD_TOP_LEFT]);
	layout->x = MIN(layout->x, (x + width) - arrowPadX[PAD_BOTTOM_RIGHT] - arrowWidth);
    } else {
	layout->x = maxX - MAX(padX[PAD_TOP_LEFT], arrowPadX[PAD_BOTTOM_RIGHT]) - arrowWidth;
    }

    /* Don't let the arrow go too far left when the column is very narrow. */
    layout->x = MAX(layout->x, x + indent + arrowPadX[PAD_TOP_LEFT]);

    layout->width = arrowWidth;

    layout->y = y + (height - (arrowHeight + arrowPadY[PAD_TOP_LEFT] +
	arrowPadY[PAD_BOTTOM_RIGHT])) / 2 + arrowPadY[PAD_TOP_LEFT];
    layout->height = arrowHeight;

    layout->arrowSide = arrowSide;
    layout->padX[0] = arrowPadX[0];
    layout->padX[1] = arrowPadX[1];
    layout->padY[0] = arrowPadY[0];
    layout->padY[1] = arrowPadY[1];
}

static void
HeaderDrawArrow(
    TreeElementArgs *args,
    struct HeaderParams *params,
    int x, int y, int width, int height, /* bounds of whole element */
    int indent
    )
{
    TreeCtrl *tree = args->tree;
    TreeElement elem = args->elem;
    ElementHeader *elemX = (ElementHeader *) elem;
    ElementHeader *masterX = (ElementHeader *) elem->master;
    Tk_Image image;
    Pixmap bitmap;
    Tk_3DBorder border;
    int state = params->elemState;
    int sunken = params->state == COLUMN_STATE_PRESSED;
    struct ArrowLayout layout;
    int match, match2;

    if (params->arrow == COLUMN_ARROW_NONE)
	return;

    HeaderLayoutArrow(tree, elemX, params, x, y, width, height, indent, &layout);

    IMAGE_FOR_STATE(image, arrowImage, state);
    if (image != NULL) {
	Tree_RedrawImage(image, 0, 0, layout.width, layout.height,
	    args->display.td,
	    layout.x + sunken,
	    layout.y + sunken);
	return;
    }

    BITMAP_FOR_STATE(bitmap, arrowBitmap, state);
    if (bitmap != None) {
	int bx, by;
	bx = layout.x + sunken;
	by = layout.y + sunken;
	Tree_DrawBitmap(tree, bitmap, args->display.drawable, NULL, NULL,
		0, 0,
		(unsigned int) layout.width, (unsigned int) layout.height,
		bx, by);
	return;
    }

    if (tree->useTheme) {
	if (TreeTheme_DrawHeaderArrow(tree, args->display.td, params->state,
	    layout.arrow == COLUMN_ARROW_UP, layout.x + sunken,
	    layout.y + sunken,
	    layout.width, layout.height) == TCL_OK)
	    return;
    }

    if (1) {
	int arrowWidth = layout.width;
	int arrowHeight = layout.height;
	int arrowBottom = layout.y + arrowHeight;
	XPoint points[5];
	int color1 = 0, color2 = 0;
	int i;

	switch (layout.arrow) {
	    case COLUMN_ARROW_UP:
		points[0].x = layout.x;
		points[0].y = arrowBottom - 1;
		points[1].x = layout.x + arrowWidth / 2;
		points[1].y = layout.y - 1;
		color1 = TK_3D_DARK_GC;
		points[4].x = layout.x + arrowWidth / 2;
		points[4].y = layout.y - 1;
		points[3].x = layout.x + arrowWidth - 1;
		points[3].y = arrowBottom - 1;
		points[2].x = layout.x;
		points[2].y = arrowBottom - 1;
		color2 = TK_3D_LIGHT_GC;
		break;
	    case COLUMN_ARROW_DOWN:
		points[0].x = layout.x + arrowWidth - 1;
		points[0].y = layout.y;
		points[1].x = layout.x + arrowWidth / 2;
		points[1].y = arrowBottom;
		color1 = TK_3D_LIGHT_GC;
		points[2].x = layout.x + arrowWidth - 1;
		points[2].y = layout.y;
		points[3].x = layout.x;
		points[3].y = layout.y;
		points[4].x = layout.x + arrowWidth / 2;
		points[4].y = arrowBottom;
		color2 = TK_3D_DARK_GC;
		break;
	}
	for (i = 0; i < 5; i++) {
	    points[i].x += sunken;
	    points[i].y += sunken;
	}

	BORDER_FOR_STATE(border, border, state)
	if (border == NULL) {
	    Tk_Uid colorName = Tk_GetUid(DEF_BUTTON_BG_COLOR);
	    if (params->state == COLUMN_STATE_ACTIVE)
		colorName = Tk_GetUid(DEF_BUTTON_ACTIVE_BG_COLOR);
	    border = Tk_Get3DBorder(tree->interp, tree->tkwin,
		colorName); /* FIXME: cache it! */
	    if (border == NULL)
		border = tree->border;
	}
	XDrawLines(tree->display, args->display.drawable,
		Tk_3DBorderGC(tree->tkwin, border, color2),
		points + 2, 3, CoordModeOrigin);
	XDrawLines(tree->display, args->display.drawable,
		Tk_3DBorderGC(tree->tkwin, border, color1),
		points, 2, CoordModeOrigin);
    }
}

static void DisplayProcHeader(TreeElementArgs *args)
{
    TreeCtrl *tree = args->tree;
    TreeElement elem = args->elem;
    ElementHeader *elemX = (ElementHeader *) elem;
    ElementHeader *masterX = (ElementHeader *) elem->master;
/*    int state = args->state;*/
    int x = args->display.x, y = args->display.y;
    int width = args->display.width, height = args->display.height;
    int arrowIndent = 0;
    Tk_3DBorder border, borderDefault = NULL;
    int i, relief;
    int match, match2;
    struct HeaderParams params;
    TreeRectangle tr1, tr2;

    if (tree->useTheme && (tree->themeHeaderHeight > 0)) {
	height = tree->themeHeaderHeight;
    }

    AdjustForSticky(args->display.sticky,
	args->display.width, args->display.height,
	TRUE, TRUE,
	&x, &y, &width, &height);

    /* We want the right side of the header to match the right edge of the
     * column and not be clipped when the column width is less than the needed
     * width of the style. */
    width = MIN(width, TreeRect_Right(args->display.spanBbox) - x);

    /* Don't draw the sort arrow in the canvasPadX space. */
    if (x < args->display.spanBbox.x + args->display.indent) {
	arrowIndent = args->display.indent;
    }

    HeaderGetParams(tree, elemX, args->state, &params);

    for (i = 0; i < 4; i++) {
	params.eUnionBbox[i] = args->display.eUnionBbox[i] - (x - args->display.spanBbox.x);
	params.iUnionBbox[i] = args->display.iUnionBbox[i] - (x - args->display.spanBbox.x);
    }

    if (tree->useTheme && TreeTheme_DrawHeaderItem(tree, args->display.td,
	    params.state, params.arrow, args->display.spanIndex, x, y, width, height)
	    == TCL_OK) {

#if !defined(MAC_OSX_TK)
	/* Under Aqua, the Appearance Manager draws the sort arrow as part of
	 * the header background. */
	HeaderDrawArrow(args, &params, x, y, width, height, arrowIndent);
#endif
	return;
    }

    BORDER_FOR_STATE(border, border, params.elemState)
    if (border == NULL) {
	Tk_Uid colorName = Tk_GetUid(DEF_BUTTON_BG_COLOR);
	if (params.state != COLUMN_STATE_NORMAL)
	    colorName = Tk_GetUid(DEF_BUTTON_ACTIVE_BG_COLOR);
	borderDefault = Tk_Get3DBorder(tree->interp, tree->tkwin,
	    colorName); /* FIXME: cache it! */
	if (borderDefault == NULL)
	    return;
	border = borderDefault;
    }

    /* X11 coordinates are 16-bit. */
    TreeRect_SetXYWH(tr1, x, y, width, height);
    TreeRect_SetXYWH(tr2, -params.borderWidth, -params.borderWidth,
	args->display.td.width + params.borderWidth * 2,
	args->display.td.height + params.borderWidth * 2);
    TreeRect_Intersect(&tr1, &tr1, &tr2);

    Tk_Fill3DRectangle(tree->tkwin, args->display.drawable, border,
	    tr1.x, tr1.y, tr1.width, tr1.height, params.borderWidth, TK_RELIEF_FLAT);

    HeaderDrawArrow(args, &params, x, y, width, height, arrowIndent);

    relief = (params.state == COLUMN_STATE_PRESSED) ?
	TK_RELIEF_SUNKEN : TK_RELIEF_RAISED;
    Tk_Draw3DRectangle(tree->tkwin, args->display.drawable, border,
	   tr1. x, tr1.y, tr1.width, tr1.height, params.borderWidth, relief);

    if (borderDefault != NULL)
	Tk_Free3DBorder(borderDefault);
}

static void NeededProcHeader(TreeElementArgs *args)
{
    TreeCtrl *tree = args->tree;
    TreeElement elem = args->elem;
    ElementHeader *elemX = (ElementHeader *) elem;
/*    ElementHeader *masterX = (ElementHeader *) elem->master;*/
    struct HeaderParams params;
    struct ArrowLayout layout;
    TreeRectangle bounds = {0, 0, 100, 24};
    int width = 0, height = 0, fixedHeight = -1;

    if (args->tree->useTheme && (tree->themeHeaderHeight > 0)) {
	fixedHeight = tree->themeHeaderHeight;
    }

    HeaderGetParams(tree, elemX, args->state, &params);

    HeaderLayoutArrow(tree, elemX, &params, TreeRect_Left(bounds),
	TreeRect_Top(bounds), TreeRect_Width(bounds),
	TreeRect_Height(bounds), 0, &layout);

    if (layout.arrow != COLUMN_ARROW_NONE) {
	width = layout.padX[0] + layout.width + layout.padX[1];
	height = layout.padY[0] + layout.height + layout.padY[1];
    }

#if 0
     /* Original header code never considered borderWidth or
      * TreeTheme_GetHeaderContentMargins when calculating needed width. */
    width += params.margins[0] + params.margins[1];
#endif
    height += params.margins[1] + params.margins[3];

    args->needed.width = width;
    args->needed.height = (fixedHeight > 0) ? fixedHeight : height;
}

static int StateProcHeader(TreeElementArgs *args)
{
    TreeCtrl *tree = args->tree;
    TreeElement elem = args->elem;
    ElementHeader *elemX = (ElementHeader *) elem;
    ElementHeader *masterX = (ElementHeader *) elem->master;
    int match, match2;
    Tk_Image image1 = NULL, image2 = NULL;
    Pixmap bitmap1 = None, bitmap2 = None;
    Tk_3DBorder border1, border2;
    struct HeaderParams params1, params2;

    if (!args->states.visible2)
	return 0;

    HeaderGetParams(tree, elemX, args->states.state1, &params1);
    HeaderGetParams(tree, elemX, args->states.state2, &params2);

    /* Check for -arrow changing to/from "none" */
    if ((params1.arrow != COLUMN_ARROW_NONE) ^
	    (params2.arrow != COLUMN_ARROW_NONE))
	return CS_DISPLAY | CS_LAYOUT;

    if (params1.arrow != COLUMN_ARROW_NONE) {
	IMAGE_FOR_STATE(image1, arrowImage, args->states.state1)
    }
    if (params2.arrow != COLUMN_ARROW_NONE) {
	IMAGE_FOR_STATE(image2, arrowImage, args->states.state2)
    }
    if (image1 != image2) {
	if ((image1 != NULL) && (image2 != NULL)) {
	    int w1, h1, w2, h2;
	    Tk_SizeOfImage(image1, &w1, &h1);
	    Tk_SizeOfImage(image2, &w2, &h2);
	    if ((w1 != w2) || (h1 != h2))
		return CS_DISPLAY | CS_LAYOUT;
	    return CS_DISPLAY;
	}
	return CS_DISPLAY | CS_LAYOUT;
    }

    if (params1.arrow != COLUMN_ARROW_NONE) {
	BITMAP_FOR_STATE(bitmap1, arrowBitmap, args->states.state1)
    }
    if (params2.arrow != COLUMN_ARROW_NONE) {
	BITMAP_FOR_STATE(bitmap2, arrowBitmap, args->states.state2)
    }
    if (bitmap1 != bitmap2) {
	if ((bitmap1 != None) && (bitmap2 != None)) {
	    int w1, h1, w2, h2;
	    Tk_SizeOfBitmap(tree->display, bitmap1, &w1, &h1);
	    Tk_SizeOfBitmap(tree->display, bitmap2, &w2, &h2);
	    if ((w1 != w2) || (h1 != h2))
		return CS_DISPLAY | CS_LAYOUT;
	    return CS_DISPLAY;
	}
	return CS_DISPLAY | CS_LAYOUT;
    }

    if (!args->states.draw2)
	return 0;

    /* Headers look different on MacOSX in the background. */
    if ((args->states.state1 & STATE_HEADER_BG) ^
	    (args->states.state2 & STATE_HEADER_BG))
	return CS_DISPLAY;

    if (params1.state != params2.state)
	return CS_DISPLAY;

    if (params1.arrow != params2.arrow)
	return CS_DISPLAY;

    BORDER_FOR_STATE(border1, border, args->states.state1)
    BORDER_FOR_STATE(border2, border, args->states.state2)
    if (border1 != border2)
	return CS_DISPLAY;

    return 0;
}

static int UndefProcHeader(TreeElementArgs *args)
{
    TreeCtrl *tree = args->tree;
    TreeElement elem = args->elem;
    ElementHeader *elemX = (ElementHeader *) elem;
    int modified = 0;

    modified |= PerStateInfo_Undefine(tree, &pstBitmap, &elemX->arrowBitmap, elem->stateDomain, args->state);
    modified |= PerStateInfo_Undefine(tree, &pstImage, &elemX->arrowImage, elem->stateDomain, args->state);
    modified |= PerStateInfo_Undefine(tree, &pstBorder, &elemX->border, elem->stateDomain, args->state);
    return modified;
}

static int ActualProcHeader(TreeElementArgs *args)
{
    TreeCtrl *tree = args->tree;
    ElementHeader *elemX = (ElementHeader *) args->elem;
    ElementHeader *masterX = (ElementHeader *) args->elem->master;
    static CONST char *optionName[] = {
	"-arrowbitmap",
	"-arrowimage",
	"-background",
	(char *) NULL };
    int index, match, matchM;
    Tcl_Obj *obj = NULL;

    if (Tcl_GetIndexFromObj(tree->interp, args->actual.obj, optionName,
		"option", 0, &index) != TCL_OK)
	return TCL_ERROR;

    switch (index) {
	case 0: {
	    OBJECT_FOR_STATE(obj, pstBitmap, arrowBitmap, args->state)
	    break;
	}
	case 1: {
	    OBJECT_FOR_STATE(obj, pstImage, arrowImage, args->state)
	    break;
	}
	case 2: {
	    OBJECT_FOR_STATE(obj, pstBorder, border, args->state)
	    break;
	}
    }
    if (obj != NULL)
	Tcl_SetObjResult(tree->interp, obj);
    return TCL_OK;
}

TreeElementType treeElemTypeHeader = {
    "header",
    sizeof(ElementHeader),
    headerOptionSpecs,
    NULL,
    CreateProcHeader,
    DeleteProcHeader,
    ConfigProcHeader,
    DisplayProcHeader,
    NeededProcHeader,
    NULL, /* heightProc */
    WorldChangedProcHeader,
    StateProcHeader,
    UndefProcHeader,
    ActualProcHeader,
    NULL /* onScreenProc */
};

void
TreeElement_GetContentMargins(
    TreeCtrl *tree,
    TreeElement elem,
    int state,
    int eMargins[4],
    int uMargins[4],
    int *arrowHeight
    )
{
    eMargins[0] = eMargins[1] = eMargins[2] = eMargins[3] = 0;
    uMargins[0] = uMargins[1] = uMargins[2] = uMargins[3] = 0;
    *arrowHeight = 0;

    if (ELEMENT_TYPE_MATCHES(elem->typePtr, &treeElemTypeHeader)) {
	ElementHeader *elemX = (ElementHeader *) elem;
	struct HeaderParams params;
	struct ArrowLayout layout;
	TreeRectangle bounds = {0, 0, 100, 24};

	HeaderGetParams(tree, elemX, state, &params);

	/* Added to ePadY of elements in the -union */
	eMargins[1] = params.margins[1];
	eMargins[3] = params.margins[3];

	/* Added to iPadY of this element */
	uMargins[1] = params.margins[1];
	uMargins[3] = params.margins[3];

	if (params.arrow == COLUMN_ARROW_NONE)
	    return;

	HeaderLayoutArrow(tree, elemX, &params, TreeRect_Left(bounds),
	    TreeRect_Top(bounds), TreeRect_Width(bounds),
	    TreeRect_Height(bounds), 0, &layout);

	if (layout.arrowSide == SIDE_LEFT) {
	    uMargins[0] = layout.padX[PAD_TOP_LEFT] + layout.width + layout.padX[PAD_BOTTOM_RIGHT];
	    eMargins[0] = layout.padX[PAD_TOP_LEFT] + layout.width;
	} else {
	    uMargins[2] = layout.padX[PAD_TOP_LEFT] + layout.width + layout.padX[PAD_BOTTOM_RIGHT];
	    eMargins[2] =                             layout.width + layout.padX[PAD_BOTTOM_RIGHT];
	}

	*arrowHeight = layout.padY[0] + layout.height + layout.padY[1];
    }
}

/*****/

typedef struct ElementImage ElementImage;

struct ElementImage
{
    TreeElement_ header;
    PerStateInfo image;
};

typedef struct ElementImageSize
{
    int width;
    Tcl_Obj *widthObj;
    int height;
    Tcl_Obj *heightObj;
} ElementImageSize;

#define IMAGE_CONF_IMAGE 0x0001
#define IMAGE_CONF_SIZE 0x0002
#define IMAGE_CONF_DISPLAY 0x0004
#ifdef DEPRECATED
#define IMAGE_CONF_DRAW 0x0008
#endif

static Tk_OptionSpec imageOptionSpecs[] = {
#ifdef DEPRECATED
    {TK_OPTION_CUSTOM, "-draw", (char *) NULL, (char *) NULL,
     (char *) NULL, -1, Tk_Offset(TreeElement_, options),
     TK_OPTION_NULL_OK, (ClientData) NULL, IMAGE_CONF_DRAW},
#endif
    {TK_OPTION_CUSTOM, "-height", (char *) NULL, (char *) NULL,
     (char *) NULL, -1, Tk_Offset(TreeElement_, options),
     TK_OPTION_NULL_OK, (ClientData) NULL, IMAGE_CONF_SIZE},
    {TK_OPTION_CUSTOM, "-image", (char *) NULL, (char *) NULL,
     (char *) NULL,
     Tk_Offset(ElementImage, image.obj), Tk_Offset(ElementImage, image),
     TK_OPTION_NULL_OK, (ClientData) NULL, IMAGE_CONF_IMAGE},
    {TK_OPTION_CUSTOM, "-tiled", (char *) NULL, (char *) NULL,
     (char *) NULL, -1, Tk_Offset(TreeElement_, options),
     TK_OPTION_NULL_OK, (ClientData) NULL, IMAGE_CONF_DISPLAY},
    {TK_OPTION_CUSTOM, "-width", (char *) NULL, (char *) NULL,
     (char *) NULL, -1, Tk_Offset(TreeElement_, options),
     TK_OPTION_NULL_OK, (ClientData) NULL, IMAGE_CONF_SIZE},
    {TK_OPTION_END, (char *) NULL, (char *) NULL, (char *) NULL,
     (char *) NULL, 0, -1, 0, (ClientData) NULL, 0}
};

static void DeleteProcImage(TreeElementArgs *args)
{
/*    TreeCtrl *tree = args->tree;
    TreeElement elem = args->elem;
    ElementImage *elemX = (ElementImage *) elem;*/
}

static int WorldChangedProcImage(TreeElementArgs *args)
{
    int flagM = args->change.flagMaster;
    int flagS = args->change.flagSelf;
    int mask = 0;

    if ((flagS | flagM) & (
#ifdef DEPRECATED
	    IMAGE_CONF_DRAW |
#endif
	    IMAGE_CONF_IMAGE | IMAGE_CONF_SIZE))
	mask |= CS_DISPLAY | CS_LAYOUT;
    if ((flagS | flagM) & IMAGE_CONF_DISPLAY)
	mask |= CS_DISPLAY;

    return mask;
}

static int ConfigProcImage(TreeElementArgs *args)
{
    TreeCtrl *tree = args->tree;
    TreeElement elem = args->elem;
    ElementImage *elemX = (ElementImage *) elem;
    Tk_SavedOptions savedOptions;
    int error;
    Tcl_Obj *errorResult = NULL;

    for (error = 0; error <= 1; error++) {
	if (error == 0) {
	    if (Tree_SetOptions(tree, elem->stateDomain, elemX,
			elem->typePtr->optionTable,
			args->config.objc, args->config.objv,
			&savedOptions, &args->config.flagSelf) != TCL_OK) {
		args->config.flagSelf = 0;
		continue;
	    }

	    Tk_FreeSavedOptions(&savedOptions);
	    break;
	} else {
	    errorResult = Tcl_GetObjResult(tree->interp);
	    Tcl_IncrRefCount(errorResult);
	    Tk_RestoreSavedOptions(&savedOptions);

	    Tcl_SetObjResult(tree->interp, errorResult);
	    Tcl_DecrRefCount(errorResult);
	    return TCL_ERROR;
	}
    }

    return TCL_OK;
}

static int CreateProcImage(TreeElementArgs *args)
{
/*    ElementImage *elemX = (ElementImage *) args->elem;*/

    return TCL_OK;
}

static void DisplayProcImage(TreeElementArgs *args)
{
    TreeCtrl *tree = args->tree;
    TreeElement elem = args->elem;
    ElementImage *elemX = (ElementImage *) elem;
    ElementImage *masterX = (ElementImage *) elem->master;
    int state = args->state;
    int x = args->display.x, y = args->display.y;
    int width, height;
    int match, match2;
#ifdef DEPRECATED
    int draw;
#endif
    Tk_Image image;
    int imgW, imgH;
    int tiled = 0, *eit, *eitM = NULL;
    int inHeader = elem->stateDomain == STATE_DOMAIN_HEADER;
    int columnState = COLUMN_STATE_NORMAL;

#ifdef DEPRECATED
    draw = DO_BooleanForState(tree, elem, 1002, state);
    if (!draw)
	return;
#endif

    IMAGE_FOR_STATE(image, image, state)
    if (image == NULL)
	return;

    eit = DynamicOption_FindData(elem->options, 1003);
    if (masterX != NULL)
	eitM = DynamicOption_FindData(elem->master->options, 1003);

    if (eit != NULL && *eit != -1)
	tiled = *eit;
    else if ((eitM != NULL) && (*eitM != -1))
	tiled = *eitM;
    if (tiled) {
	TreeRectangle tr;
	tr.x = x, tr.y = y;
	tr.width = args->display.width, tr.height = args->display.height;
	Tree_DrawTiledImage(tree, args->display.td, image, tr,
		x, y, TRUE, TRUE);
	return;
    }

    Tk_SizeOfImage(image, &imgW, &imgH);
    width = imgW, height = imgH;
    AdjustForSticky(args->display.sticky,
	args->display.width, args->display.height,
	FALSE, FALSE,
	&x, &y, &width, &height);
#if HEADER_OFFSET_HACK == 1
    if (inHeader) {
	if (state & STATE_HEADER_ACTIVE)
	    columnState = COLUMN_STATE_ACTIVE;
	else if (state & STATE_HEADER_PRESSED)
	    columnState = COLUMN_STATE_PRESSED;
    }
    if (inHeader && columnState == COLUMN_STATE_PRESSED) {
	/* If this image fills the whole header, don't offset it. */
	/* FIXME: should have a layout option to control this. */
	if (imgW < args->display.spanBbox.width ||
		imgH < args->display.spanBbox.height) {
	    x += 1, y += 1;
	}
    }
#endif
    if (imgW > args->display.width)
	imgW = args->display.width;
    if (imgH > args->display.height)
	imgH = args->display.height;
    Tree_RedrawImage(image, 0, 0, imgW, imgH, args->display.td, x, y);
}

static void NeededProcImage(TreeElementArgs *args)
{
    TreeCtrl *tree = args->tree;
    TreeElement elem = args->elem;
    ElementImage *elemX = (ElementImage *) elem;
    ElementImage *masterX = (ElementImage *) elem->master;
    int state = args->state;
    int width = 0, height = 0;
    int match, match2;
    Tk_Image image;
    ElementImageSize *eis, *eisM = NULL;

    IMAGE_FOR_STATE(image, image, state)

    if (image != NULL)
	Tk_SizeOfImage(image, &width, &height);

    eis = DynamicOption_FindData(elem->options, 1001);
    if (masterX != NULL)
	eisM = DynamicOption_FindData(elem->master->options, 1001);

    if (eis != NULL && eis->widthObj != NULL)
	width = eis->width;
    else if ((eisM != NULL) && (eisM->widthObj != NULL))
	width = eisM->width;

    if (eis != NULL && eis->heightObj != NULL)
	height = eis->height;
    else if ((eisM != NULL) && (eisM->heightObj != NULL))
	height = eisM->height;

    args->needed.width = width;
    args->needed.height = height;
}

static int StateProcImage(TreeElementArgs *args)
{
    TreeCtrl *tree = args->tree;
    TreeElement elem = args->elem;
    ElementImage *elemX = (ElementImage *) elem;
    ElementImage *masterX = (ElementImage *) elem->master;
    int match, match2;
#ifdef DEPRECATED
    int draw1, draw2;
#endif
    Tk_Image image1, image2;

    if (!args->states.visible2)
	return 0;

    IMAGE_FOR_STATE(image1, image, args->states.state1)
    IMAGE_FOR_STATE(image2, image, args->states.state2)

    if (image1 != image2) {
	if ((image1 != NULL) && (image2 != NULL)) {
	    int w1, h1, w2, h2;
	    Tk_SizeOfImage(image1, &w1, &h1);
	    Tk_SizeOfImage(image2, &w2, &h2);
	    if ((w1 != w2) || (h1 != h2))
		return CS_DISPLAY | CS_LAYOUT;
	    return CS_DISPLAY;
	}
	return CS_DISPLAY | CS_LAYOUT;
    }

    if (!args->states.draw2)
	return 0;
#ifdef DEPRECATED
    draw1 = DO_BooleanForState(tree, elem, 1002, args->states.state1);
    draw2 = DO_BooleanForState(tree, elem, 1002, args->states.state2);
    if ((draw1 != 0) != (draw2 != 0))
	return CS_DISPLAY;
#endif

    return 0;
}

static int UndefProcImage(TreeElementArgs *args)
{
    TreeCtrl *tree = args->tree;
    TreeElement elem = args->elem;
    ElementImage *elemX = (ElementImage *) elem;
    int modified = 0;
#ifdef DEPRECATED
    PerStateInfo *psi;
#endif

#ifdef DEPRECATED
    if ((psi = DynamicOption_FindData(elem->options, 1002)) != NULL)
	modified |= PerStateInfo_Undefine(tree, &pstBoolean, psi, elem->stateDomain, args->state);
#endif
    modified |= PerStateInfo_Undefine(tree, &pstImage, &elemX->image, elem->stateDomain, args->state);
    return modified;
}

static int ActualProcImage(TreeElementArgs *args)
{
    TreeCtrl *tree = args->tree;
    ElementImage *elemX = (ElementImage *) args->elem;
    ElementImage *masterX = (ElementImage *) args->elem->master;
    static CONST char *optionName[] = {
#ifdef DEPRECATED
	"-draw",
#endif
	"-image",
	(char *) NULL };
    int index, match, matchM;
    Tcl_Obj *obj = NULL;

    if (Tcl_GetIndexFromObj(tree->interp, args->actual.obj, optionName,
		"option", 0, &index) != TCL_OK)
	return TCL_ERROR;

    switch (index) {
#ifdef DEPRECATED
	case 0: {
	    obj = DO_ObjectForState(tree, &pstBoolean, args->elem, 1002, args->state);
	    break;
	}
	case 1: {
	    OBJECT_FOR_STATE(obj, pstImage, image, args->state)
	    break;
	}
#else
	case 0: {
	    OBJECT_FOR_STATE(obj, pstImage, image, args->state)
	    break;
	}
#endif
    }
    if (obj != NULL)
	Tcl_SetObjResult(tree->interp, obj);
    return TCL_OK;
}

TreeElementType treeElemTypeImage = {
    "image",
    sizeof(ElementImage),
    imageOptionSpecs,
    NULL,
    CreateProcImage,
    DeleteProcImage,
    ConfigProcImage,
    DisplayProcImage,
    NeededProcImage,
    NULL, /* heightProc */
    WorldChangedProcImage,
    StateProcImage,
    UndefProcImage,
    ActualProcImage,
    NULL /* onScreenProc */
};

/*****/

typedef struct ElementRect ElementRect;

struct ElementRect
{
    TreeElement_ header;
#ifdef DEPRECATED
    PerStateInfo draw;
#endif
    int width;
    Tcl_Obj *widthObj;
    int height;
    Tcl_Obj *heightObj;
    PerStateInfo fill;
    PerStateInfo outline;
    int outlineWidth;
    Tcl_Obj *outlineWidthObj;
    PerStateInfo open;
    int showFocus;
    int rx;
    Tcl_Obj *rxObj;
    int ry;
    Tcl_Obj *ryObj;
};

#define RECT_CONF_FILL 0x0001
#define RECT_CONF_OUTLINE 0x0002
#define RECT_CONF_OUTWIDTH 0x0004
#define RECT_CONF_OPEN 0x0008
#define RECT_CONF_SIZE 0x0010
#define RECT_CONF_FOCUS 0x0020
#define RECT_CONF_RADIUS 0x0040
#ifdef DEPRECATED
#define RECT_CONF_DRAW 0x0080
#endif

static Tk_OptionSpec rectOptionSpecs[] = {
#ifdef DEPRECATED
    {TK_OPTION_CUSTOM, "-draw", (char *) NULL, (char *) NULL,
     (char *) NULL,
     Tk_Offset(ElementRect, draw.obj), Tk_Offset(ElementRect, draw),
     TK_OPTION_NULL_OK, (ClientData) NULL, RECT_CONF_DRAW},
#endif
    {TK_OPTION_CUSTOM, "-fill", (char *) NULL, (char *) NULL,
     (char *) NULL,
     Tk_Offset(ElementRect, fill.obj), Tk_Offset(ElementRect, fill),
     TK_OPTION_NULL_OK, (ClientData) NULL, RECT_CONF_FILL},
    {TK_OPTION_PIXELS, "-height", (char *) NULL, (char *) NULL,
     (char *) NULL, Tk_Offset(ElementRect, heightObj),
     Tk_Offset(ElementRect, height),
     TK_OPTION_NULL_OK, (ClientData) NULL, RECT_CONF_SIZE},
    {TK_OPTION_CUSTOM, "-open", (char *) NULL, (char *) NULL, (char *) NULL,
     Tk_Offset(ElementRect, open.obj), Tk_Offset(ElementRect, open),
     TK_OPTION_NULL_OK, (ClientData) NULL, RECT_CONF_OPEN},
    {TK_OPTION_CUSTOM, "-outline", (char *) NULL, (char *) NULL,
     (char *) NULL,
     Tk_Offset(ElementRect, outline.obj), Tk_Offset(ElementRect, outline),
     TK_OPTION_NULL_OK, (ClientData) NULL, RECT_CONF_OUTLINE},
    {TK_OPTION_PIXELS, "-outlinewidth", (char *) NULL, (char *) NULL,
     (char *) NULL, Tk_Offset(ElementRect, outlineWidthObj),
     Tk_Offset(ElementRect, outlineWidth),
     TK_OPTION_NULL_OK, (ClientData) NULL, RECT_CONF_OUTWIDTH},
    {TK_OPTION_PIXELS, "-rx", (char *) NULL, (char *) NULL,
     (char *) NULL, Tk_Offset(ElementRect, rxObj),
     Tk_Offset(ElementRect, rx),
     TK_OPTION_NULL_OK, (ClientData) NULL, RECT_CONF_RADIUS},
    {TK_OPTION_PIXELS, "-ry", (char *) NULL, (char *) NULL,
     (char *) NULL, Tk_Offset(ElementRect, ryObj),
     Tk_Offset(ElementRect, ry),
     TK_OPTION_NULL_OK, (ClientData) NULL, RECT_CONF_RADIUS},
    {TK_OPTION_CUSTOM, "-showfocus", (char *) NULL, (char *) NULL,
     (char *) NULL, -1, Tk_Offset(ElementRect, showFocus),
     TK_OPTION_NULL_OK, (ClientData) &booleanCO, RECT_CONF_FOCUS},
    {TK_OPTION_PIXELS, "-width", (char *) NULL, (char *) NULL,
     (char *) NULL, Tk_Offset(ElementRect, widthObj),
     Tk_Offset(ElementRect, width),
     TK_OPTION_NULL_OK, (ClientData) NULL, RECT_CONF_SIZE},
    {TK_OPTION_END, (char *) NULL, (char *) NULL, (char *) NULL,
     (char *) NULL, 0, -1, 0, (ClientData) NULL, 0}
};

static void DeleteProcRect(TreeElementArgs *args)
{
/*    TreeCtrl *tree = args->tree;
    ElementRect *elemX = (ElementRect *) args->elem;*/
}

static int WorldChangedProcRect(TreeElementArgs *args)
{
    int flagM = args->change.flagMaster;
    int flagS = args->change.flagSelf;
    int mask = 0;

    if ((flagS | flagM) & (RECT_CONF_SIZE | RECT_CONF_OUTWIDTH))
	mask |= CS_DISPLAY | CS_LAYOUT;

    if ((flagS | flagM) & (
#ifdef DEPRECATED
	    RECT_CONF_DRAW |
#endif
	    RECT_CONF_FILL | RECT_CONF_OUTLINE | RECT_CONF_OPEN |
	    RECT_CONF_FOCUS | RECT_CONF_RADIUS))
	mask |= CS_DISPLAY;

    return mask;
}

static int ConfigProcRect(TreeElementArgs *args)
{
    TreeCtrl *tree = args->tree;
    TreeElement elem = args->elem;
    ElementRect *elemX = (ElementRect *) elem;
    Tk_SavedOptions savedOptions;
    int error;
    Tcl_Obj *errorResult = NULL;

    for (error = 0; error <= 1; error++) {
	if (error == 0) {
	    if (Tree_SetOptions(tree, elem->stateDomain, elemX,
			elem->typePtr->optionTable,
			args->config.objc, args->config.objv,
			&savedOptions, &args->config.flagSelf) != TCL_OK) {
		args->config.flagSelf = 0;
		continue;
	    }

	    Tk_FreeSavedOptions(&savedOptions);
	    break;
	} else {
	    errorResult = Tcl_GetObjResult(tree->interp);
	    Tcl_IncrRefCount(errorResult);
	    Tk_RestoreSavedOptions(&savedOptions);

	    Tcl_SetObjResult(tree->interp, errorResult);
	    Tcl_DecrRefCount(errorResult);
	    return TCL_ERROR;
	}
    }

    return TCL_OK;
}

static int CreateProcRect(TreeElementArgs *args)
{
    ElementRect *elemX = (ElementRect *) args->elem;

    elemX->showFocus = -1;
    return TCL_OK;
}

static void DisplayProcRect(TreeElementArgs *args)
{
    TreeCtrl *tree = args->tree;
    TreeElement elem = args->elem;
    ElementRect *elemX = (ElementRect *) elem;
    ElementRect *masterX = (ElementRect *) elem->master;
    int state = args->state;
    int x = args->display.x, y = args->display.y;
    int width = args->display.width, height = args->display.height;
    int match, match2;
#ifdef DEPRECATED
    int draw;
#endif
    TreeColor *tc;
    TreeRectangle tr;
    TreeRectangle trBrush;
    TreeClip clip, *clipPtr = &clip;
    int open = 0;
    int outlineWidth = 0;
    int showFocus = 0;
    int rx = 0, ry = 0;

#ifdef DEPRECATED
    BOOLEAN_FOR_STATE(draw, draw, state)
    if (!draw)
	return;
#endif

    if (elemX->outlineWidthObj != NULL)
	outlineWidth = elemX->outlineWidth;
    else if ((masterX != NULL) && (masterX->outlineWidthObj != NULL))
	outlineWidth = masterX->outlineWidth;

    FLAGS_FOR_STATE(open, open, state)
    if (open == 0xFFFFFFFF) /* unspecified */
	open = 0;

    if (elemX->showFocus != -1)
	showFocus = elemX->showFocus;
    else if ((masterX != NULL) && (masterX->showFocus != -1))
	showFocus = masterX->showFocus;

    if (elemX->widthObj != NULL)
	width = elemX->width;
    else if ((masterX != NULL) && (masterX->widthObj != NULL))
	width = masterX->width;

    if (elemX->heightObj != NULL)
	height = elemX->height;
    else if ((masterX != NULL) && (masterX->heightObj != NULL))
	height = masterX->height;

    if (elemX->rxObj != NULL)
	rx = elemX->rx;
    else if ((masterX != NULL) && (masterX->rxObj != NULL))
	rx = masterX->rx;

    if (elemX->ryObj != NULL)
	ry = elemX->ry;
    else if ((masterX != NULL) && (masterX->ryObj != NULL))
	ry = masterX->ry;

    AdjustForSticky(args->display.sticky,
	args->display.width, args->display.height,
	TRUE, TRUE,
	&x, &y, &width, &height);

    TreeRect_SetXYWH(tr, x, y, width, height);

#if USE_ITEM_PIXMAP == 0
    clip.type = TREE_CLIP_RECT;
    clip.tr = args->display.bounds;
#else
    clipPtr = NULL;
#endif

    if (rx < 1 && ry < 1)
	rx = ry = 0;
    else if (ry < 1)
	ry = rx;
    else if (rx < 1)
	rx = ry;
    rx = MIN(rx, width/2);
    ry = MIN(ry, height/2);
    if (rx >= 1 && ry >= 1) {
	TREECOLOR_FOR_STATE(tc, fill, state)
	if (tc != NULL) {
	    TreeColor_GetBrushBounds(tree, tc, tr,
		    tree->drawableXOrigin, tree->drawableYOrigin,
		    args->display.column, args->display.item, &trBrush);
	    TreeColor_FillRoundRect(tree, args->display.td, clipPtr, tc, trBrush, tr, rx, ry, open);
	}
	TREECOLOR_FOR_STATE(tc, outline, state)
	if ((tc != NULL) && (outlineWidth > 0) && (open != RECT_OPEN_WNES)) {
	    TreeColor_GetBrushBounds(tree, tc, tr,
		    tree->drawableXOrigin, tree->drawableYOrigin,
		    args->display.column, args->display.item, &trBrush);
	    TreeColor_DrawRoundRect(tree, args->display.td, clipPtr, tc,
		trBrush, tr, outlineWidth, rx, ry, open);
	}
	/* FIXME: its not round! */
	if (showFocus && (state & STATE_ITEM_FOCUS) && (state & STATE_ITEM_ACTIVE)) {
	    Tree_DrawActiveOutline(tree, args->display.drawable,
		    args->display.x, args->display.y,
		    args->display.width, args->display.height,
		    open);
	}
	return;
    }

    TREECOLOR_FOR_STATE(tc, fill, state)
    if (tc != NULL) {
	TreeColor_GetBrushBounds(tree, tc, tr,
		tree->drawableXOrigin, tree->drawableYOrigin,
		args->display.column, args->display.item, &trBrush);
	TreeColor_FillRect(tree, args->display.td, clipPtr, tc, trBrush, tr);
    }

    TREECOLOR_FOR_STATE(tc, outline, state)
    if ((tc != NULL) && (outlineWidth > 0) && (open != RECT_OPEN_WNES)) {
	TreeColor_GetBrushBounds(tree, tc, tr,
		tree->drawableXOrigin, tree->drawableYOrigin,
		args->display.column, args->display.item, &trBrush);
	TreeColor_DrawRect(tree, args->display.td, clipPtr, tc, trBrush, tr,
	    outlineWidth, open);
    }

    if (showFocus && (state & STATE_ITEM_FOCUS) && (state & STATE_ITEM_ACTIVE)) {
	Tree_DrawActiveOutline(tree, args->display.drawable,
		args->display.x, args->display.y,
		args->display.width, args->display.height,
		open);
    }
}

static void NeededProcRect(TreeElementArgs *args)
{
    TreeElement elem = args->elem;
    ElementRect *elemX = (ElementRect *) elem;
    ElementRect *masterX = (ElementRect *) elem->master;
    int width = 0, height = 0;
    int outlineWidth = 0;

    if (elemX->outlineWidthObj != NULL)
	outlineWidth = elemX->outlineWidth;
    else if ((masterX != NULL) && (masterX->outlineWidthObj != NULL))
	outlineWidth = masterX->outlineWidth;

    if (elemX->widthObj != NULL)
	width = elemX->width;
    else if ((masterX != NULL) && (masterX->widthObj != NULL))
	width = masterX->width;

    if (elemX->heightObj != NULL)
	height = elemX->height;
    else if ((masterX != NULL) && (masterX->heightObj != NULL))
	height = masterX->height;

    args->needed.width = MAX(width, outlineWidth * 2);
    args->needed.height = MAX(height, outlineWidth * 2);
}

static int StateProcRect(TreeElementArgs *args)
{
    TreeCtrl *tree = args->tree;
    TreeElement elem = args->elem;
    ElementRect *elemX = (ElementRect *) elem;
    ElementRect *masterX = (ElementRect *) elem->master;
    int match, match2;
#ifdef DEPRECATED
    int draw1, draw2;
#endif
    int open1, open2;
    XColor *c1, *c2;
    TreeColor *tc1, *tc2;
    int s1, s2;
    int showFocus = 0;

    /* If either the -draw or -visible layout option is false for the
     * current state, then changes to colors etc don't warrant a redisplay. */
    if (!args->states.visible2 || !args->states.draw2)
	return 0;

#ifdef DEPRECATED
    BOOLEAN_FOR_STATE(draw1, draw, args->states.state1)
    BOOLEAN_FOR_STATE(draw2, draw, args->states.state2)
    if ((draw1 != 0) != (draw2 != 0))
	return CS_DISPLAY;
    /* If the element isn't drawn, then changes to colors etc don't
     * warrant a redisplay. */
    if (draw2 == 0)
	return 0;
#endif

    if (elemX->showFocus != -1)
	showFocus = elemX->showFocus;
    else if ((masterX != NULL) && (masterX->showFocus != -1))
	showFocus = masterX->showFocus;

    s1 = showFocus &&
	(args->states.state1 & STATE_ITEM_FOCUS) &&
	(args->states.state1 & STATE_ITEM_ACTIVE);
    s2 = showFocus &&
	(args->states.state2 & STATE_ITEM_FOCUS) &&
	(args->states.state2 & STATE_ITEM_ACTIVE);
    if (s1 != s2)
	return CS_DISPLAY;

    TREECOLOR_FOR_STATE(tc1, fill, args->states.state1)
    TREECOLOR_FOR_STATE(tc2, fill, args->states.state2)
    if (TREECOLOR_CMP(tc1, tc2))
	return CS_DISPLAY;

    FLAGS_FOR_STATE(open1, open, args->states.state1)
    FLAGS_FOR_STATE(open2, open, args->states.state2)
    if (open1 != open2)
	return CS_DISPLAY;

    COLOR_FOR_STATE(c1, outline, args->states.state1)
    COLOR_FOR_STATE(c2, outline, args->states.state2)
    if (c1 != c2)
	return CS_DISPLAY;

    return 0;
}

static int UndefProcRect(TreeElementArgs *args)
{
    TreeCtrl *tree = args->tree;
    TreeElement elem = args->elem;
    ElementRect *elemX = (ElementRect *) elem;
    int modified = 0;

#ifdef DEPRECATED
    modified |= PerStateInfo_Undefine(tree, &pstBoolean, &elemX->draw, elem->stateDomain, args->state);
#endif
    modified |= PerStateInfo_Undefine(tree, &pstColor, &elemX->fill, elem->stateDomain, args->state);
    modified |= PerStateInfo_Undefine(tree, &pstFlags, &elemX->open, elem->stateDomain, args->state);
    modified |= PerStateInfo_Undefine(tree, &pstColor, &elemX->outline, elem->stateDomain, args->state);
    return modified;
}

static int ActualProcRect(TreeElementArgs *args)
{
    TreeCtrl *tree = args->tree;
    ElementRect *elemX = (ElementRect *) args->elem;
    ElementRect *masterX = (ElementRect *) args->elem->master;
    static CONST char *optionName[] = {
#ifdef DEPRECATED
	"-draw",
#endif
	"-fill",
	 "-open",
	 "-outline",
	(char *) NULL };
    enum optionEnum {
#ifdef DEPRECATED
	OPT_DRAW,
#endif
	OPT_FILL,
	OPT_OPEN,
	OPT_OUTLINE
    };
    int index, match, matchM;
    Tcl_Obj *obj = NULL;

    if (Tcl_GetIndexFromObj(tree->interp, args->actual.obj, optionName,
		"option", 0, &index) != TCL_OK)
	return TCL_ERROR;

    switch ((enum optionEnum) index) {
#ifdef DEPRECATED
	case OPT_DRAW: {
	    OBJECT_FOR_STATE(obj, pstBoolean, draw, args->state)
	    break;
	}
#endif
	case OPT_FILL: {
	    OBJECT_FOR_STATE(obj, pstColor, fill, args->state)
	    break;
	}
	case OPT_OPEN: {
	    OBJECT_FOR_STATE(obj, pstFlags, open, args->state)
	    break;
	}
	case OPT_OUTLINE: {
	    OBJECT_FOR_STATE(obj, pstColor, outline, args->state)
	    break;
	}
    }
    if (obj != NULL)
	Tcl_SetObjResult(tree->interp, obj);
    return TCL_OK;
}

TreeElementType treeElemTypeRect = {
    "rect",
    sizeof(ElementRect),
    rectOptionSpecs,
    NULL,
    CreateProcRect,
    DeleteProcRect,
    ConfigProcRect,
    DisplayProcRect,
    NeededProcRect,
    NULL, /* heightProc */
    WorldChangedProcRect,
    StateProcRect,
    UndefProcRect,
    ActualProcRect,
    NULL /* onScreenProc */
};

/*****/

typedef struct ElementText ElementText;

struct ElementText
{
    TreeElement_ header;
    char *textCfg;		/* -text */
    char *text;			/* This will be the same as textCfg, or it
				 * will be a dynamically allocated string
				 * from any -data or -textvariable. */
#define STRINGREP_INVALID -1
    int textLen;		/* Number of bytes (not characters) in the
				 * UTF-8 string. If -1, it means the string
				 * representation is invalid. */
};
#define TEXTVAR

/* for Tk_SetOptions() */
#define TEXT_CONF_LAYOUT 0x0001
#define TEXT_CONF_DISPLAY 0x0002
#define TEXT_CONF_STRINGREP 0x0040
#ifdef TEXTVAR
#define TEXT_CONF_TEXTVAR 0x0080
#endif

/*
 * Dynamic option ids for the text element.
 */
#define DOID_TEXT_VAR 1001
#define DOID_TEXT_DRAW 1002
#define DOID_TEXT_FILL 1003
#define DOID_TEXT_FONT 1004
#define DOID_TEXT_LAYOUT 1005
#define DOID_TEXT_DATA 1006
#define DOID_TEXT_LAYOUT2 1007
#define DOID_TEXT_STYLE 1008
#define DOID_TEXT_LAYOUT3 1009

typedef struct ElementTextData {
    Tcl_Obj *dataObj;			/* -data */
#define TDT_NULL -1
#define TDT_DOUBLE 0
#define TDT_INTEGER 1
#define TDT_LONG 2
#define TDT_STRING 3
#define TDT_TIME 4
    int dataType;			/* -datatype */
    Tcl_Obj *formatObj;			/* -format */
} ElementTextData;

typedef struct ElementTextLayout {
#define TK_JUSTIFY_NULL -1
    int justify;			/* -justify */
    int lines;				/* -lines */
    Tcl_Obj *widthObj;			/* -width */
    int width;				/* -width */
#define TEXT_WRAP_NULL -1
#define TEXT_WRAP_CHAR 0
#define TEXT_WRAP_NONE 1
#define TEXT_WRAP_WORD 2
    int wrap;				/* -wrap */
    int elidePos;			/* -elidepos */
} ElementTextLayout;

/* This structure doesn't hold any option values, but it is managed by
 * the dynamic-option code. */
typedef struct ElementTextLayout2 {
    TextLayout layout;
    int layoutWidth;
    int neededWidth;
    int totalWidth;
} ElementTextLayout2;

typedef struct ElementTextLayout3 {
    Tcl_Obj *lMargin1Obj;		/* -lmargin1 */
    int lMargin1;			/* -lmargin2 */
    Tcl_Obj *lMargin2Obj;		/* -lmargin1 */
    int lMargin2;			/* -lmargin2 */
} ElementTextLayout3;

#define TEXT_STYLE
#ifdef TEXT_STYLE
typedef struct ElementTextStyle {
    int underline;			/* -underline */
} ElementTextStyle;

/* Called by the dynamic-option code when an ElementTextData is allocated. */
static void
ElementTextStyleInit(
    void *data
    )
{
    ElementTextStyle *ets = data;

#define TEXT_UNDERLINE_EMPTYVAL -100000
    ets->underline = TEXT_UNDERLINE_EMPTYVAL;
}
#endif

#ifdef TEXTVAR
typedef struct ElementTextVar {
    Tcl_Obj *varNameObj;		/* -textvariable */
    TreeCtrl *tree;			/* needed to redisplay */
    TreeItem item;			/* needed to redisplay */
    TreeItemColumn column;		/* needed to redisplay */
} ElementTextVar;
#endif

/* Called by the dynamic-option code when an ElementTextData is allocated. */
static void
ElementTextDataInit(
    void *data
    )
{
    ElementTextData *etd = data;

    etd->dataType = TDT_NULL;
}

/* Called by the dynamic-option code when an ElementTextLayout is allocated. */
static void
ElementTextLayoutInit(
    void *data
    )
{
    ElementTextLayout *etl = data;

    etl->justify = TK_JUSTIFY_NULL;
    etl->lines = -1;
    etl->wrap = TEXT_WRAP_NULL;
    etl->elidePos = -1;
}

static CONST char *textDataTypeST[] = { "double", "integer", "long", "string",
					"time", (char *) NULL };
static CONST char *textElideST[] = { "start", "middle", "end", (char *) NULL };
static CONST char *textJustifyST[] = { "left", "right", "center", (char *) NULL };
static CONST char *textWrapST[] = { "char", "none", "word", (char *) NULL };

static Tk_OptionSpec textOptionSpecs[] = {
    {TK_OPTION_CUSTOM, "-data", (char *) NULL, (char *) NULL,
     (char *) NULL, -1, Tk_Offset(TreeElement_, options),
     TK_OPTION_NULL_OK, (ClientData) NULL, TEXT_CONF_STRINGREP},
    {TK_OPTION_CUSTOM, "-datatype", (char *) NULL, (char *) NULL,
     (char *) NULL, -1, Tk_Offset(TreeElement_, options),
     TK_OPTION_NULL_OK, (ClientData) NULL, TEXT_CONF_STRINGREP},
#ifdef DEPRECATED
    {TK_OPTION_CUSTOM, "-draw", (char *) NULL, (char *) NULL,
     (char *) NULL, -1, Tk_Offset(TreeElement_, options),
     TK_OPTION_NULL_OK, (ClientData) NULL, TEXT_CONF_DISPLAY},
#endif
    {TK_OPTION_CUSTOM, "-elidepos", (char *) NULL, (char *) NULL,
     (char *) NULL, -1, Tk_Offset(TreeElement_, options),
     TK_OPTION_NULL_OK, (ClientData) NULL, TEXT_CONF_LAYOUT},
    {TK_OPTION_CUSTOM, "-fill", (char *) NULL, (char *) NULL,
     (char *) NULL, -1, Tk_Offset(TreeElement_, options),
     TK_OPTION_NULL_OK, (ClientData) NULL,  TEXT_CONF_DISPLAY},
    {TK_OPTION_CUSTOM, "-font", (char *) NULL, (char *) NULL,
     (char *) NULL, -1, Tk_Offset(TreeElement_, options),
     TK_OPTION_NULL_OK, (ClientData) NULL, TEXT_CONF_LAYOUT},
    {TK_OPTION_CUSTOM, "-format", (char *) NULL, (char *) NULL,
     (char *) NULL, -1, Tk_Offset(TreeElement_, options),
     TK_OPTION_NULL_OK, (ClientData) NULL, TEXT_CONF_STRINGREP},
    {TK_OPTION_CUSTOM, "-justify", (char *) NULL, (char *) NULL,
     (char *) NULL, -1, Tk_Offset(TreeElement_, options),
     TK_OPTION_NULL_OK, (ClientData) NULL, TEXT_CONF_LAYOUT},
    {TK_OPTION_CUSTOM, "-lines", (char *) NULL, (char *) NULL,
     (char *) NULL, -1, Tk_Offset(TreeElement_, options),
     TK_OPTION_NULL_OK, (ClientData) NULL, TEXT_CONF_LAYOUT},
    {TK_OPTION_CUSTOM, "-lmargin1", (char *) NULL, (char *) NULL,
     (char *) NULL, -1, Tk_Offset(TreeElement_, options),
     TK_OPTION_NULL_OK, (ClientData) NULL, TEXT_CONF_LAYOUT},
    {TK_OPTION_CUSTOM, "-lmargin2", (char *) NULL, (char *) NULL,
     (char *) NULL, -1, Tk_Offset(TreeElement_, options),
     TK_OPTION_NULL_OK, (ClientData) NULL, TEXT_CONF_LAYOUT},
    {TK_OPTION_STRING, "-text", (char *) NULL, (char *) NULL,
     (char *) NULL, -1, Tk_Offset(ElementText, textCfg),
     TK_OPTION_NULL_OK, (ClientData) NULL, TEXT_CONF_STRINGREP},
#ifdef TEXTVAR
    {TK_OPTION_CUSTOM, "-textvariable", (char *) NULL, (char *) NULL,
     (char *) NULL, -1, Tk_Offset(TreeElement_, options),
     TK_OPTION_NULL_OK, (ClientData) NULL, TEXT_CONF_STRINGREP | TEXT_CONF_TEXTVAR},
#endif
#ifdef TEXT_STYLE
    {TK_OPTION_CUSTOM, "-underline", (char *) NULL, (char *) NULL,
     (char *) NULL, -1, Tk_Offset(TreeElement_, options),
     TK_OPTION_NULL_OK, (ClientData) NULL, TEXT_CONF_DISPLAY},
#endif
    {TK_OPTION_CUSTOM, "-width", (char *) NULL, (char *) NULL,
     (char *) NULL, -1, Tk_Offset(TreeElement_, options),
     TK_OPTION_NULL_OK, (ClientData) NULL, TEXT_CONF_LAYOUT},
    {TK_OPTION_CUSTOM, "-wrap", (char *) NULL, (char *) NULL,
     (char *) NULL, -1, Tk_Offset(TreeElement_, options),
     TK_OPTION_NULL_OK, (ClientData) NULL, TEXT_CONF_LAYOUT},
    {TK_OPTION_END, (char *) NULL, (char *) NULL, (char *) NULL,
     (char *) NULL, 0, -1, 0, 0, 0}
};

static int WorldChangedProcText(TreeElementArgs *args)
{
/*	TreeCtrl *tree = args->tree;*/
    TreeElement elem = args->elem;
    ElementText *elemX = (ElementText *) elem;
/*	ElementText *masterX = (ElementText *) elem->master;*/
    int flagT = args->change.flagTree;
    int flagM = args->change.flagMaster;
    int flagS = args->change.flagSelf;
    int mask = 0;

    if ((flagS | flagM) & TEXT_CONF_STRINGREP) {
	elemX->textLen = STRINGREP_INVALID;
    }

    if ((elemX->textLen == STRINGREP_INVALID) ||
	    ((flagS | flagM) & TEXT_CONF_LAYOUT) ||
	    /* Not needed if this element has its own font. */
	    (flagT & TREE_CONF_FONT)) {
	mask |= CS_DISPLAY | CS_LAYOUT;
    }

    if ((flagS | flagM) & TEXT_CONF_DISPLAY)
	mask |= CS_DISPLAY;

    return mask;
}

static void TextUpdateStringRep(TreeElementArgs *args)
{
    TreeCtrl *tree = args->tree;
    TreeElement elem = args->elem;
    ElementText *elemX = (ElementText *) elem;
    ElementText *masterX = (ElementText *) elem->master;
    Tcl_Obj *dataObj, *formatObj;
    char *text;
    ElementTextData *etd, *etdM = NULL;
#ifdef TEXTVAR
    ElementTextVar *etv;
    Tcl_Obj *varNameObj;
#endif
    int dataType;

    /* Free any string allocated as a result of -data or -textvariable. */
    if ((elemX->text != NULL) && (elemX->text != elemX->textCfg)) {
	ckfree(elemX->text);
    }

    /* Forget any string, and mark the string rep as no-longer invalid. */
    elemX->text = NULL;
    elemX->textLen = 0;

    /* If -text is specified, then -data and -textvariable are ignored. */
    if (elemX->textCfg != NULL) {
	elemX->text = elemX->textCfg;
	elemX->textLen = (int) strlen(elemX->textCfg);
	return;
    }

#ifdef TEXTVAR
    etv = DynamicOption_FindData(elem->options, DOID_TEXT_VAR);
    varNameObj = etv ? etv->varNameObj : NULL;

    if (varNameObj != NULL) {
	Tcl_Obj *valueObj = Tcl_ObjGetVar2(tree->interp, varNameObj, NULL,
		TCL_GLOBAL_ONLY);

	if (valueObj == NULL) {
	    /* not possible I think */
	} else {
	    /* FIXME: do I need to allocate a copy, or can I just point
	     * to the internal rep of the string object? */
	    text = Tcl_GetStringFromObj(valueObj, &elemX->textLen);
	    if (elemX->textLen > 0) {
		elemX->text = ckalloc(elemX->textLen);
		memcpy(elemX->text, text, elemX->textLen);
	    }
	}
	return;
    }
#endif

    etd = DynamicOption_FindData(elem->options, DOID_TEXT_DATA);
    if (masterX != NULL)
	etdM = DynamicOption_FindData(elem->master->options, DOID_TEXT_DATA);

    dataObj = etd ? etd->dataObj : NULL;
    if ((dataObj == NULL) && (etdM != NULL))
	dataObj = etdM->dataObj;

    dataType = etd ? etd->dataType : TDT_NULL;
    if ((dataType == TDT_NULL) && (etdM != NULL))
	dataType = etdM->dataType;

    formatObj = etd ? etd->formatObj : NULL;
    if ((formatObj == NULL) && (etdM != NULL))
	formatObj = etdM->formatObj;

    /* Only create a string rep if elemX (not masterX) has dataObj,
       dataType or formatObj. */
    if ((dataObj != NULL) && (dataType != TDT_NULL) &&
	    ((etd != NULL) && ((etd->dataObj != NULL) ||
		    (etd->dataType != TDT_NULL) ||
		    (etd->formatObj != NULL)))) {
	int objc = 0;
	Tcl_Obj *objv[5], *resultObj = NULL;
	Tcl_ObjCmdProc *clockObjCmd = NULL, *formatObjCmd = NULL;
	ClientData clockClientData = NULL, formatClientData = NULL;
	Tcl_CmdInfo cmdInfo;

	if (Tcl_GetCommandInfo(tree->interp, "::clock", &cmdInfo) == 1) {
	    clockObjCmd = cmdInfo.objProc;
	    clockClientData = cmdInfo.objClientData;
	}
	if (Tcl_GetCommandInfo(tree->interp, "::format", &cmdInfo) == 1) {
	    formatObjCmd = cmdInfo.objProc;
	    formatClientData = cmdInfo.objClientData;
	}

	/* Important to remove any shared result object, otherwise
	 * calls like Tcl_SetStringObj(Tcl_GetObjResult()) fail. */
	Tcl_ResetResult(tree->interp);

	switch (dataType) {
	    case TDT_DOUBLE:
		if (formatObjCmd == NULL)
		    break;
		if (formatObj == NULL) formatObj = tree->formatFloatObj;
		objv[objc++] = tree->stringFormatObj;
		objv[objc++] = formatObj;
		objv[objc++] = dataObj;
		if (formatObjCmd(formatClientData, tree->interp, objc, objv)
			    == TCL_OK)
		    resultObj = Tcl_GetObjResult(tree->interp);
		break;
	    case TDT_INTEGER:
		if (formatObjCmd == NULL)
		    break;
		if (formatObj == NULL) formatObj = tree->formatIntObj;
		objv[objc++] = tree->stringFormatObj;
		objv[objc++] = formatObj;
		objv[objc++] = dataObj;
		if (formatObjCmd(formatClientData, tree->interp, objc, objv)
			    == TCL_OK)
		    resultObj = Tcl_GetObjResult(tree->interp);
		break;
	    case TDT_LONG:
		if (formatObjCmd == NULL)
		    break;
		if (formatObj == NULL) formatObj = tree->formatLongObj;;
		objv[objc++] = tree->stringFormatObj;
		objv[objc++] = formatObj;
		objv[objc++] = dataObj;
		if (formatObjCmd(formatClientData, tree->interp, objc, objv)
			    == TCL_OK)
		    resultObj = Tcl_GetObjResult(tree->interp);
		break;
	    case TDT_STRING:
		if (formatObjCmd == NULL)
		    break;
		if (formatObj == NULL) formatObj = tree->formatStringObj;;
		objv[objc++] = tree->stringFormatObj;
		objv[objc++] = formatObj;
		objv[objc++] = dataObj;
		if (formatObjCmd(formatClientData, tree->interp, objc, objv)
			    == TCL_OK)
		    resultObj = Tcl_GetObjResult(tree->interp);
		break;
	    case TDT_TIME:
		if (clockObjCmd == NULL)
		    break;
		objv[objc++] = tree->stringClockObj;
		objv[objc++] = tree->stringFormatObj;
		objv[objc++] = dataObj;
		if (formatObj != NULL) {
		    objv[objc++] = tree->optionFormatObj;
		    objv[objc++] = formatObj;
		}
		if (clockObjCmd(clockClientData, tree->interp, objc, objv)
			    == TCL_OK)
		    resultObj = Tcl_GetObjResult(tree->interp);
		break;
	    default:
		panic("unknown ElementText dataType");
		break;
	}

	if (resultObj != NULL) {
	    text = Tcl_GetStringFromObj(resultObj, &elemX->textLen);
	    if (elemX->textLen > 0) {
		elemX->text = ckalloc(elemX->textLen);
		memcpy(elemX->text, text, elemX->textLen);
	    }
	}
    }
}

static ElementTextLayout2 *
TextUpdateLayout(
    char *func,
    TreeElementArgs *args,
    int fixedWidth,
    int maxWidth
    )
{
    TreeCtrl *tree = args->tree;
    TreeElement elem = args->elem;
    ElementText *elemX = (ElementText *) elem;
    ElementText *masterX = (ElementText *) elem->master;
    int state = args->state;
    int inHeader = elem->stateDomain == STATE_DOMAIN_HEADER;
    Tk_Font tkfont;
    char *text = NULL;
    int textLen = 0;
    int justify = TK_JUSTIFY_LEFT;
    int lines = 0;
    int wrap = TEXT_WRAP_WORD;
    int width = 0;
    int flags = 0;
    int i, multiLine = FALSE;
    int textWidth;
    ElementTextLayout *etl, *etlM = NULL;
    ElementTextLayout2 *etl2;
    ElementTextLayout3 *etl3, *etl3M = NULL;
    DynamicOption *opt;
    int lMargin1 = 0, lMargin2 = 0;

    if (tree->debug.enable && tree->debug.textLayout)
	dbwin("TextUpdateLayout: %s %p (%s) %s\n    fixedWidth %d maxWidth %d\n",
	    Tk_PathName(tree->tkwin), elemX, masterX ? "instance" : "master",
	    func, fixedWidth, maxWidth);

    etl2 = DynamicOption_FindData(elem->options, DOID_TEXT_LAYOUT2);
    if (etl2 != NULL && etl2->layout != NULL) {
	if (tree->debug.enable && tree->debug.textLayout)
	    dbwin("    FREE\n");
	TextLayout_Free(etl2->layout);
	etl2->layout = NULL;
    }

    if (elemX->text != NULL) {
	text = elemX->text;
	textLen = elemX->textLen;
    } else if ((masterX != NULL) && (masterX->text != NULL)) {
	text = masterX->text;
	textLen = masterX->textLen;
    }
    if ((text == NULL) || (textLen == 0))
	return etl2;

    etl = DynamicOption_FindData(elem->options, DOID_TEXT_LAYOUT);
    if (masterX != NULL)
	etlM = DynamicOption_FindData(elem->master->options, DOID_TEXT_LAYOUT);

    if (etl != NULL && etl->lines != -1)
	lines = etl->lines;
    else if (etlM != NULL && etlM->lines != -1)
	lines = etlM->lines;
    if (lines == 1)
	return etl2;

    tkfont = DO_FontForState(tree, elem, DOID_TEXT_FONT, state);
    if (tkfont == NULL)
	tkfont = inHeader ? tree->tkfontHeader : tree->tkfont;

    if (etl != NULL && etl->wrap != TEXT_WRAP_NULL)
	wrap = etl->wrap;
    else if (etlM != NULL && etlM->wrap != TEXT_WRAP_NULL)
	wrap = etlM->wrap;

    if (wrap != TEXT_WRAP_NONE) {
	if (fixedWidth >= 0)
	    width = fixedWidth;
	else if (maxWidth >= 0)
	    width = maxWidth;
	if (etl != NULL && etl->widthObj != NULL) {
	    if (!width || (etl->width < width))
		width = etl->width;
	} else if ((etlM != NULL) && (etlM->widthObj != NULL)) {
	    if (!width || (etlM->width < width))
		width = etlM->width;
	}
    }

    for (i = 0; i < textLen; i++) {
	if ((text[i] == '\n') || (text[i] == '\r')) {
	    multiLine = TRUE;
	    break;
	}
    }
    if (tree->debug.enable && tree->debug.textLayout)
	dbwin("    lines %d multiLine %d width %d wrap %s\n",
		lines, multiLine, width, textWrapST[wrap]);
    if (!multiLine) {
	if (width == 0)
	    return etl2;
	textWidth = Tk_TextWidth(tkfont, text, textLen);
if (tree->debug.enable && tree->debug.textLayout) dbwin("    available width %d textWidth %d\n", width, textWidth);
	if (width >= textWidth)
	    return etl2;
    }

    if (etl != NULL && etl->justify != TK_JUSTIFY_NULL)
	justify = etl->justify;
    else if (etlM != NULL && etlM->justify != TK_JUSTIFY_NULL)
	justify = etlM->justify;

    if (wrap == TEXT_WRAP_WORD)
	flags |= TK_WHOLE_WORDS;

    if (etl2 == NULL) {
	opt = (DynamicOption *) DynamicOption_AllocIfNeeded(tree,
	    &elem->options, DOID_TEXT_LAYOUT2, sizeof(ElementTextLayout2), NULL);
	etl2 = (ElementTextLayout2 *) opt->data;
	/* It is possible that the needed size of this element does not
	 * require a TextLayout, in which case neededWidth never gets
	 * set. */
	etl2->neededWidth = -1;
    }

    etl3 = DynamicOption_FindData(elem->options, DOID_TEXT_LAYOUT3);
    if (masterX != NULL)
	etl3M = DynamicOption_FindData(elem->master->options, DOID_TEXT_LAYOUT3);
    if (etl3 != NULL && etl3->lMargin1Obj != NULL)
	lMargin1 = etl3->lMargin1;
    else if (etl3M != NULL && etl3M->lMargin1Obj != NULL)
	lMargin1 = etl3M->lMargin1;
    if (etl3 != NULL && etl3->lMargin2Obj != NULL)
	lMargin2 = etl3->lMargin2;
    else if (etl3M != NULL && etl3M->lMargin2Obj != NULL)
	lMargin2 = etl3M->lMargin2;

    etl2->layout = TextLayout_Compute(tkfont, text,
	    Tcl_NumUtfChars(text, textLen), width, justify, lines,
	    lMargin1, lMargin2, flags);

    if (tree->debug.enable && tree->debug.textLayout)
	dbwin("    ALLOC\n");
    return etl2;
}

#ifdef TEXTVAR
static Tcl_VarTraceProc VarTraceProc_Text;

static void TextTraceSet(Tcl_Interp *interp, ElementText *elemX)
{
    ElementTextVar *etv = DynamicOption_FindData(elemX->header.options,
	DOID_TEXT_VAR);
    Tcl_Obj *varNameObj = etv ? etv->varNameObj : NULL;

    if (varNameObj != NULL) {
	Tcl_TraceVar2(interp, Tcl_GetString(varNameObj),
	    NULL,
	    TCL_GLOBAL_ONLY | TCL_TRACE_WRITES | TCL_TRACE_UNSETS,
	    VarTraceProc_Text, (ClientData) elemX);
    }
}

static void TextTraceUnset(Tcl_Interp *interp, ElementText *elemX)
{
    ElementTextVar *etv = DynamicOption_FindData(elemX->header.options,
	DOID_TEXT_VAR);
    Tcl_Obj *varNameObj = etv ? etv->varNameObj : NULL;

    if (varNameObj != NULL) {
	Tcl_UntraceVar2(interp, Tcl_GetString(varNameObj),
	    NULL,
	    TCL_GLOBAL_ONLY | TCL_TRACE_WRITES | TCL_TRACE_UNSETS,
	    VarTraceProc_Text, (ClientData) elemX);
    }
}

static char *VarTraceProc_Text(ClientData clientData, Tcl_Interp *interp,
    CONST char *name1, CONST char *name2, int flags)
{
    ElementText *elemX = (ElementText *) clientData;
    ElementTextVar *etv = DynamicOption_FindData(elemX->header.options,
	DOID_TEXT_VAR);
    Tcl_Obj *varNameObj = etv ? etv->varNameObj : NULL;
    Tcl_Obj *valueObj;

    /*
     * If the variable is unset, then immediately recreate it unless
     * the whole interpreter is going away.
     */

    if (flags & TCL_TRACE_UNSETS) {
	if ((flags & TCL_TRACE_DESTROYED) && !(flags & TCL_INTERP_DESTROYED)) {
	    /* Use the current string if it is valid. */
	    if (elemX->textLen > 0) {
		valueObj = Tcl_NewStringObj(elemX->text, elemX->textLen);
	    } else {
		valueObj = Tcl_NewStringObj("", 0);
	    }
	    Tcl_IncrRefCount(valueObj);
	    Tcl_ObjSetVar2(interp, varNameObj, NULL, valueObj,
		TCL_GLOBAL_ONLY);
	    Tcl_DecrRefCount(valueObj);
	    TextTraceSet(interp, elemX);
	}
	return (char *) NULL;
    }

    elemX->textLen = STRINGREP_INVALID;
    Tree_ElementChangedItself(etv->tree, etv->item, etv->column,
	(TreeElement) elemX, TEXT_CONF_LAYOUT | TEXT_CONF_TEXTVAR,
	CS_LAYOUT | CS_DISPLAY);
    return (char *) NULL;
}
#endif /* TEXTVAR */

static void DeleteProcText(TreeElementArgs *args)
{
    TreeCtrl *tree = args->tree;
    TreeElement elem = args->elem;
    ElementText *elemX = (ElementText *) elem;
    ElementTextLayout2 *etl2;

    if ((elemX->textCfg == NULL) && (elemX->text != NULL)) {
	ckfree(elemX->text);
	elemX->text = NULL;
    }
    etl2 = DynamicOption_FindData(elem->options, DOID_TEXT_LAYOUT2);
    if (etl2 != NULL && etl2->layout != NULL)
	TextLayout_Free(etl2->layout);
    DynamicOption_Free1(tree, &elem->options, DOID_TEXT_LAYOUT2,
	sizeof(ElementTextLayout2));
#ifdef TEXTVAR
    TextTraceUnset(tree->interp, elemX);
#endif
}

static int ConfigProcText(TreeElementArgs *args)
{
    TreeCtrl *tree = args->tree;
    Tcl_Interp *interp = tree->interp;
    TreeElement elem = args->elem;
    ElementText *elemX = (ElementText *) elem;
    Tk_SavedOptions savedOptions;
    int error;
    Tcl_Obj *errorResult = NULL;
    char *textCfg = elemX->textCfg;
#ifdef TEXTVAR
    ElementTextVar *etv;
    Tcl_Obj *varNameObj;
#endif

#ifdef TEXTVAR
    TextTraceUnset(interp, elemX);
#endif

    for (error = 0; error <= 1; error++) {
	if (error == 0) {
	    if (Tree_SetOptions(tree, elem->stateDomain, elemX,
			elem->typePtr->optionTable,
			args->config.objc, args->config.objv,
			&savedOptions, &args->config.flagSelf) != TCL_OK) {
		args->config.flagSelf = 0;
		continue;
	    }

#ifdef TEXTVAR
	    etv = DynamicOption_FindData(elem->options, DOID_TEXT_VAR);
	    if (etv != NULL) {
		etv->tree = tree;
		etv->item = args->config.item;
		etv->column = args->config.column;
		varNameObj = etv->varNameObj;
	    } else
		varNameObj = NULL;
	    if (varNameObj != NULL) {
		Tcl_Obj *valueObj;
		valueObj = Tcl_ObjGetVar2(interp, varNameObj,
			NULL, TCL_GLOBAL_ONLY);
		if (valueObj == NULL) {
		    valueObj = Tcl_NewStringObj("", 0);
		    Tcl_IncrRefCount(valueObj);
		    /* This validates the variable name. We get an error
		     * if it is the name of an array */
		    if (Tcl_ObjSetVar2(interp, varNameObj, NULL,
			    valueObj, TCL_GLOBAL_ONLY | TCL_LEAVE_ERR_MSG)
			    == NULL) {
			Tcl_DecrRefCount(valueObj);
			continue;
		    }
		    Tcl_DecrRefCount(valueObj);
		}
	    }
#endif

	    Tk_FreeSavedOptions(&savedOptions);
	    break;
	} else {
	    errorResult = Tcl_GetObjResult(interp);
	    Tcl_IncrRefCount(errorResult);
	    Tk_RestoreSavedOptions(&savedOptions);
	}
    }

    /* If -text was specified, then do not try to free the old value in
     * TextUpdateStringRep. */
    if (textCfg != elemX->textCfg && elemX->text == textCfg)
	elemX->text = NULL;

#ifdef TEXTVAR
    TextTraceSet(interp, elemX);
#endif

    if (error) {
	Tcl_SetObjResult(interp, errorResult);
	Tcl_DecrRefCount(errorResult);
	return TCL_ERROR;
    }

    return TCL_OK;
}

static int CreateProcText(TreeElementArgs *args)
{
/*    ElementText *elemX = (ElementText *) args->elem;*/

    return TCL_OK;
}

static ElementTextLayout2 *
TextRedoLayoutIfNeeded(
    char *func,
    TreeElementArgs *args,
    int fixedWidth
    )
{
    TreeElement elem = args->elem;
/*    ElementText *elemX = (ElementText *) elem;*/
    ElementText *masterX = (ElementText *) elem->master;
    int doLayout = 0;
    int wrap = TEXT_WRAP_WORD;
    ElementTextLayout *etl, *etlM = NULL;
    ElementTextLayout2 *etl2;

    etl = DynamicOption_FindData(elem->options, DOID_TEXT_LAYOUT);
    if (masterX != NULL)
	etlM = DynamicOption_FindData(elem->master->options, DOID_TEXT_LAYOUT);

    etl2 = DynamicOption_FindData(elem->options, DOID_TEXT_LAYOUT2);

    /* If text wrapping is disabled, the layout doesn't change */
    if (etl != NULL && etl->wrap != TEXT_WRAP_NULL)
	wrap = etl->wrap;
    else if ((etlM != NULL) && (etlM->wrap != TEXT_WRAP_NULL))
	wrap = etlM->wrap;
    if (wrap == TEXT_WRAP_NONE)
	return etl2;

    if (etl2 != NULL && etl2->layout != NULL) {

	/* See comment in NeededProc about totalWidth */
	if ((etl2->neededWidth != -1) && (fixedWidth >= etl2->neededWidth))
	    fixedWidth = etl2->totalWidth;

	/* Already layed out at this width */
	if (fixedWidth == etl2->layoutWidth)
	    return etl2;
    }

    /* May switch from layout -> no layout or vice versa */
    if (etl2 == NULL || etl2->layout == NULL)
	doLayout = 1;

    /* Width was constrained and we have more space now */
    else if ((etl2->layoutWidth != -1) && (fixedWidth > etl2->layoutWidth))
	doLayout = 1;

    /* Width was unconstrained or we have less space now */
    else {
	int width;
	TextLayout_Size(etl2->layout, &width, NULL);
	/* Redo if we are narrower than the layout */
	if (fixedWidth < width)
	    doLayout = 1;
    }
    if (doLayout)
	etl2 = TextUpdateLayout(func, args, fixedWidth, -1);
    if (etl2 != NULL)
	etl2->layoutWidth = (etl2->layout != NULL) ? fixedWidth : -1;
    return etl2;
}

static void DisplayProcText(TreeElementArgs *args)
{
    TreeCtrl *tree = args->tree;
    TreeElement elem = args->elem;
    ElementText *elemX = (ElementText *) elem;
    ElementText *masterX = (ElementText *) elem->master;
    int state = args->state;
    int x = args->display.x, y = args->display.y;
    int width, height;
#ifdef DEPRECATED
    int draw;
#endif
    XColor *color;
    char *text = elemX->text;
    int textLen = elemX->textLen;
    Tk_Font tkfont;
    TextLayout layout = NULL;
    Tk_FontMetrics fm;
    GC gc;
    int bytesThatFit, pixelsForText;
    char *ellipsis = "...";
    TkRegion clipRgn = NULL;
    ElementTextLayout2 *etl2;
#ifdef TEXT_STYLE
    ElementTextStyle *ets, *etsM = NULL;
    int underline = TEXT_UNDERLINE_EMPTYVAL;
#endif
#if USE_ITEM_PIXMAP == 0
    TreeRectangle trClip, trElem;
#endif
    int inHeader = elem->stateDomain == STATE_DOMAIN_HEADER;
    int columnState = COLUMN_STATE_NORMAL;
    ElementTextLayout *etl, *etlM = NULL;
    Tcl_DString dString;
    enum ElidePosition elidePos = ElideEnd;
    int elideStart, elideEnd;

#ifdef DEPRECATED
    draw = DO_BooleanForState(tree, elem, DOID_TEXT_DRAW, state);
    if (!draw)
	return;
#endif

    if ((text == NULL) && (masterX != NULL)) {
	text = masterX->text;
	textLen = masterX->textLen;
    }

    if (text == NULL) /* always false (or layout sets height/width to zero) */
	return;

    if (inHeader) {
	if (state & STATE_HEADER_ACTIVE)
	    columnState = COLUMN_STATE_ACTIVE;
	else if (state & STATE_HEADER_PRESSED)
	    columnState = COLUMN_STATE_PRESSED;
    }

    DO_COLOR_FOR_STATE(color, DOID_TEXT_FILL, state);

    /* If no color is specified and this element is in a header, get the
     * system theme color.  If no theme color is provided then use the default
     * header text color. */
    if ((color == NULL) && inHeader) {
	if (!tree->useTheme || TreeTheme_GetHeaderTextColor(tree, columnState,
		&color) != TCL_OK) {
	    color = tree->defHeaderTextColor;
	}
	if (color->pixel == tree->defHeaderTextColor->pixel)
	    color = NULL;
    }

    tkfont = DO_FontForState(tree, elem, DOID_TEXT_FONT, state);

    /* FIXME: -font {"" {state...}}*/
    if ((color != NULL) || (tkfont != NULL)) {
	XGCValues gcValues;
	unsigned long gcMask = 0;
	if (color == NULL)
	    color = inHeader ? tree->defHeaderTextColor : tree->fgColorPtr;
	gcValues.foreground = color->pixel;
	gcMask |= GCForeground;
	if (tkfont == NULL)
	    tkfont = inHeader ? tree->tkfontHeader : tree->tkfont;
	gcValues.font = Tk_FontId(tkfont);
	gcMask |= GCFont;
	gcValues.graphics_exposures = False;
	gcMask |= GCGraphicsExposures;
	gc = Tree_GetGC(tree, gcMask, &gcValues);
    } else if (inHeader) {
	tkfont = tree->tkfontHeader;
	gc = tree->headerTextGC;
    } else {
	tkfont = tree->tkfont;
	gc = tree->textGC;
    }

#ifdef TEXT_STYLE
    ets = DynamicOption_FindData(elem->options, DOID_TEXT_STYLE);
    if (ets != NULL && ets->underline != TEXT_UNDERLINE_EMPTYVAL)
	underline = ets->underline;
    else if (masterX != NULL) {
	etsM = DynamicOption_FindData(elem->master->options, DOID_TEXT_STYLE);
	if (etsM != NULL && etsM->underline != TEXT_UNDERLINE_EMPTYVAL)
	    underline = etsM->underline;
    }
#endif

    etl2 = TextRedoLayoutIfNeeded("DisplayProcText", args, args->display.width);
    if (etl2 != NULL && etl2->layout != NULL)
	layout = etl2->layout;

    if (layout != NULL) {
	TextLayout_Size(layout, &width, &height);
	pixelsForText = width;
	/* Hack -- The actual size of the text may be slightly smaller than
	 * the available space when squeezed. If so we don't want to center
	 * the text horizontally */
	if ((etl2->neededWidth == -1) || (etl2->neededWidth > width))
	    width = args->display.width;
	AdjustForSticky(args->display.sticky,
	    args->display.width, args->display.height,
	    FALSE, FALSE,
	    &x, &y, &width, &height);
#if HEADER_OFFSET_HACK == 1
	/* FIXME: should have a layout option to control this. */
	if (inHeader && columnState == COLUMN_STATE_PRESSED)
	    x += 1, y += 1;
#endif
#if USE_ITEM_PIXMAP == 0
	/* Use clipping if text is larger than the display area. */
	trElem.x = x, trElem.y = y, trElem.width = args->display.width,
	    trElem.height = args->display.height;
	if (TreeRect_Intersect(&trClip, &trElem, &args->display.bounds)) {
	    clipRgn = Tree_GetRectRegion(tree, &trClip);
	    TkSetRegion(tree->display, gc, clipRgn);
	} else {
	    return;
	}
#else
	/* Use clipping if text is larger than the display area. */
	if (pixelsForText > args->display.width || height > args->display.height) {
	    XRectangle rect;
	    clipRgn = Tree_GetRegion(tree);
	    rect.x = x;
	    rect.y = y;
	    rect.width = args->display.width;
	    rect.height = args->display.height;
	    TkUnionRectWithRegion(&rect, clipRgn, clipRgn);
	    TkSetRegion(tree->display, gc, clipRgn);
	}
#endif
	TextLayout_Draw(tree->display, args->display.drawable, gc,
		layout, x, y, 0, -1, underline);
	if (clipRgn != NULL) {
	    Tree_UnsetClipMask(tree, args->display.drawable, gc);
	    Tree_FreeRegion(tree, clipRgn);
	}
	return;
    }

    Tk_GetFontMetrics(tkfont, &fm);

    etl = DynamicOption_FindData(elem->options, DOID_TEXT_LAYOUT);
    if (etl != NULL && etl->elidePos != -1)
	elidePos = etl->elidePos;
    else if (masterX != NULL) {
	etlM = DynamicOption_FindData(elem->master->options, DOID_TEXT_LAYOUT);
	if (etlM != NULL && etlM->elidePos != -1)
	    elidePos = etlM->elidePos;
    }

    pixelsForText = args->display.width;
    bytesThatFit = Tree_Ellipsis(tkfont, text, textLen, &pixelsForText,
	    ellipsis, FALSE, elidePos, &elideStart, &elideEnd, &dString);
    width = pixelsForText, height = fm.linespace;
    /* Hack -- The actual size of the text may be slightly smaller than
    * the available space when squeezed. If so we don't want to center
    * the text horizontally */
    if (bytesThatFit != textLen)
	width = args->display.width;
    AdjustForSticky(args->display.sticky,
	args->display.width, args->display.height,
	FALSE, FALSE,
	&x, &y, &width, &height);
#if HEADER_OFFSET_HACK == 1
    /* FIXME: should have a layout option to control this. */
    if (inHeader && columnState == COLUMN_STATE_PRESSED)
	x += 1, y += 1;
#endif
#if USE_ITEM_PIXMAP == 0
    /* Use clipping if text is larger than the display area. */
    trElem.x = x, trElem.y = y, trElem.width = args->display.width,
	trElem.height = args->display.height;
    if (TreeRect_Intersect(&trClip, &trElem, &args->display.bounds)) {
	clipRgn = Tree_GetRectRegion(tree, &trClip);
	TkSetRegion(tree->display, gc, clipRgn);
    } else {
	Tcl_DStringFree(&dString);
	return;
    }
#else
    /* Use clipping if text is larger than the display area. */
    if (pixelsForText > args->display.width || height > args->display.height) {
	XRectangle rect;
	clipRgn = Tree_GetRegion(tree);
	rect.x = x;
	rect.y = y;
	rect.width = args->display.width;
	rect.height = args->display.height;
	TkUnionRectWithRegion(&rect, clipRgn, clipRgn);
	TkSetRegion(tree->display, gc, clipRgn);
    }
#endif
    {
	char *buf = Tcl_DStringValue(&dString);
	int bufLen = Tcl_DStringLength(&dString);
	Tk_DrawChars(tree->display, args->display.drawable, gc,
		tkfont, buf, bufLen, x, y + fm.ascent);
#ifdef TEXT_STYLE
	if (underline >= 0 && underline <= elideStart) {
	    CONST char *fstBytePtr = Tcl_UtfAtIndex(buf, underline);
	    CONST char *sndBytePtr = Tcl_UtfNext(fstBytePtr);
	    Tk_UnderlineChars(tree->display, args->display.drawable, gc,
		    tkfont, buf, x, y + fm.ascent,
		    (int) (fstBytePtr - buf), (int) (sndBytePtr - buf));
	} else if (underline >= 0 && elideEnd >= 0 && underline >= elideEnd && underline < Tcl_NumUtfChars(text, textLen)) {
	    if (elideEnd > 0)
		underline += strlen(ellipsis); /* chars added before elideEnd */
	    underline -= elideEnd - MAX(-1, elideStart) - 1; /* chars removed before elideEnd*/
	    {
		CONST char *fstBytePtr = Tcl_UtfAtIndex(buf, underline);
		CONST char *sndBytePtr = Tcl_UtfNext(fstBytePtr);
		Tk_UnderlineChars(tree->display, args->display.drawable, gc,
			tkfont, buf, x, y + fm.ascent,
			(int) (fstBytePtr - buf), (int) (sndBytePtr - buf));
	    }
	}
#endif
    }
    if (clipRgn != NULL) {
	Tree_UnsetClipMask(tree, args->display.drawable, gc);
	Tree_FreeRegion(tree, clipRgn);
    }
    Tcl_DStringFree(&dString);
}

static void NeededProcText(TreeElementArgs *args)
{
    TreeCtrl *tree = args->tree;
    TreeElement elem = args->elem;
    ElementText *elemX = (ElementText *) elem;
    ElementText *masterX = (ElementText *) elem->master;
    int state = args->state;
    char *text = NULL;
    int textLen = 0;
    Tk_Font tkfont;
    Tk_FontMetrics fm;
    int width = 0, height = 0;
    ElementTextLayout *etl, *etlM = NULL;
    ElementTextLayout2 *etl2;
    int inHeader = elem->stateDomain == STATE_DOMAIN_HEADER;

    etl = DynamicOption_FindData(args->elem->options, DOID_TEXT_LAYOUT);
    if (masterX != NULL)
	etlM = DynamicOption_FindData(args->elem->master->options, DOID_TEXT_LAYOUT);

    if ((masterX != NULL) && (masterX->textLen == STRINGREP_INVALID)) {
	args->elem = (TreeElement) masterX;
	TextUpdateStringRep(args);
	args->elem = elem;
    }
    if (elemX->textLen == STRINGREP_INVALID) {
	TextUpdateStringRep(args);
    }

    etl2 = TextUpdateLayout("NeededProcText", args, args->needed.fixedWidth,
	    args->needed.maxWidth);
    if (etl2 != NULL) {
	etl2->layoutWidth = -1;
	etl2->neededWidth = -1;
    }

    if (etl2 != NULL && etl2->layout != NULL) {
	TextLayout_Size(etl2->layout, &width, &height);
	if (args->needed.fixedWidth >= 0)
	    etl2->layoutWidth = args->needed.fixedWidth;
	else if (args->needed.maxWidth >= 0)
	    etl2->layoutWidth = args->needed.maxWidth;
	etl2->neededWidth = width;

	/*
	 * Hack -- If we call TextLayout_Compute() with the same width
	 * returned by TextLayout_Size(), we may get a different layout.
	 * I think this has to do with whitespace at the end of lines.
	 * So if HeightProc or DisplayProc is given neededWidth, I do the
	 * layout at totalWidth, not neededWidth.
	 */
	etl2->totalWidth = TextLayout_TotalWidth(etl2->layout);
    } else {
	if (elemX->text != NULL) {
	    text = elemX->text;
	    textLen = elemX->textLen;
	} else if ((masterX != NULL) && (masterX->text != NULL)) {
	    text = masterX->text;
	    textLen = masterX->textLen;
	}
	if (textLen > 0) {
	    int maxWidth = -1;

	    tkfont = DO_FontForState(tree, elem, DOID_TEXT_FONT, state);
	    if (tkfont == NULL)
		tkfont = inHeader ? tree->tkfontHeader : tree->tkfont;

	    width = Tk_TextWidth(tkfont, text, textLen);
	    if (etl != NULL && etl->widthObj != NULL)
		maxWidth = etl->width;
	    else if ((etlM != NULL) && (etlM->widthObj != NULL))
		maxWidth = etlM->width;
	    if ((maxWidth >= 0) && (maxWidth < width))
		width = maxWidth;

	    Tk_GetFontMetrics(tkfont, &fm);
	    height = fm.linespace;
	}
    }

    args->needed.width = width;
    args->needed.height = height;
}

static void HeightProcText(TreeElementArgs *args)
{
    TreeCtrl *tree = args->tree;
    TreeElement elem = args->elem;
    ElementText *elemX = (ElementText *) elem;
    ElementText *masterX = (ElementText *) elem->master;
    int inHeader = elem->stateDomain == STATE_DOMAIN_HEADER;
    int state = args->state;
    int height = 0;
    int textLen = 0;
    Tk_Font tkfont;
    Tk_FontMetrics fm;
    ElementTextLayout2 *etl2;

    etl2 = TextRedoLayoutIfNeeded("HeightProcText", args, args->height.fixedWidth);

    if (etl2 != NULL && etl2->layout != NULL) {
	TextLayout_Size(etl2->layout, NULL, &height);
    } else {
	if (elemX->text != NULL) {
	    textLen = elemX->textLen;
	} else if ((masterX != NULL) && (masterX->text != NULL)) {
	    textLen = masterX->textLen;
	}
	if (textLen > 0) {
	    tkfont = DO_FontForState(tree, elem, DOID_TEXT_FONT, state);
	    if (tkfont == NULL)
		tkfont = inHeader ? tree->tkfontHeader : tree->tkfont;
	    Tk_GetFontMetrics(tkfont, &fm);
	    height = fm.linespace;
	}
    }

    args->height.height = height;
}

int
TreeElement_GetSortData(
    TreeCtrl *tree,
    TreeElement elem,
    int type,
    long *lv,
    double *dv,
    char **sv)
{
    ElementText *elemX = (ElementText *) elem;
    ElementText *masterX = (ElementText *) elem->master;
    ElementTextData *etd, *etdM = NULL;
    Tcl_Obj *dataObj = NULL;
    int dataType = TDT_NULL;

    etd = DynamicOption_FindData(elem->options, DOID_TEXT_DATA);
    if (etd != NULL) {
	dataObj = etd->dataObj;
	dataType = etd->dataType;
    }
    if (dataType == TDT_NULL && masterX != NULL) {
	etdM = DynamicOption_FindData(elem->master->options, DOID_TEXT_DATA);
	/* FIXME: get dataObj from master? */
	if (etdM != NULL)
	    dataType = etdM->dataType;
    }

    switch (type) {
	case SORT_ASCII:
	case SORT_DICT:
	    if (dataObj != NULL && dataType != TDT_NULL)
		(*sv) = Tcl_GetString(dataObj);
	    else
		(*sv) = elemX->textCfg;
	    break;
	case SORT_DOUBLE:
	    if (dataObj != NULL && dataType == TDT_DOUBLE) {
		if (Tcl_GetDoubleFromObj(tree->interp, dataObj, dv) != TCL_OK)
		    return TCL_ERROR;
		break;
	    }
	    if (elemX->textCfg != NULL) {
		if (Tcl_GetDouble(tree->interp, elemX->textCfg, dv) != TCL_OK)
		    return TCL_ERROR;
		break;
	    }
	    FormatResult(tree->interp, "can't get a double from an empty -text value");
	    return TCL_ERROR;
	case SORT_LONG:
	    if (dataObj != NULL && dataType != TDT_NULL) {
		if (dataType == TDT_LONG || dataType == TDT_TIME) {
		    if (Tcl_GetLongFromObj(tree->interp, dataObj, lv) != TCL_OK)
			return TCL_ERROR;
		    break;
		}
		if (dataType == TDT_INTEGER) {
		    int iv;
		    if (Tcl_GetIntFromObj(tree->interp, dataObj, &iv) != TCL_OK)
			return TCL_ERROR;
		    (*lv) = iv;
		    break;
		}
	    }
	    if (elemX->textCfg != NULL) {
		Tcl_Obj obj;

		obj.refCount = 1;
		obj.bytes = (char *) elemX->textCfg;
		obj.length = (int) strlen(elemX->textCfg);
		obj.typePtr = NULL;

		if (Tcl_GetLongFromObj(tree->interp, &obj, lv) != TCL_OK)
		    return TCL_ERROR;
		break;
	    }
	    FormatResult(tree->interp, "can't get a long from an empty -text value");
	    return TCL_ERROR;
    }
    return TCL_OK;
}

static int StateProcText(TreeElementArgs *args)
{
    TreeCtrl *tree = args->tree;
    TreeElement elem = args->elem;
/*    ElementText *elemX = (ElementText *) elem;
    ElementText *masterX = (ElementText *) elem->master;*/
#ifdef DEPRECATED
    int draw1, draw2;
#endif
    XColor *f1, *f2;
    Tk_Font tkfont1, tkfont2;

    if (!args->states.visible2)
	return 0;

    tkfont1 = DO_FontForState(tree, elem, DOID_TEXT_FONT, args->states.state1);
    tkfont2 = DO_FontForState(tree, elem, DOID_TEXT_FONT, args->states.state2);
    if (tkfont1 != tkfont2)
	return CS_DISPLAY | CS_LAYOUT;

    if (!args->states.draw2)
	return 0;
#ifdef DEPRECATED
    draw1 = DO_BooleanForState(tree, elem, DOID_TEXT_DRAW, args->states.state1);
    draw2 = DO_BooleanForState(tree, elem, DOID_TEXT_DRAW, args->states.state2);
    if ((draw1 != 0) != (draw2 != 0))
	return CS_DISPLAY;
    if (draw2 == 0)
	return 0;
#endif

    DO_COLOR_FOR_STATE(f1, DOID_TEXT_FILL, args->states.state1);
    DO_COLOR_FOR_STATE(f2, DOID_TEXT_FILL, args->states.state2);
    if (f1 != f2)
	return CS_DISPLAY;

    return 0;
}

static int UndefProcText(TreeElementArgs *args)
{
    TreeCtrl *tree = args->tree;
    TreeElement elem = args->elem;
/*    ElementText *elemX = (ElementText *) elem;*/
    int modified = 0;
    PerStateInfo *psi;

    if ((psi = DynamicOption_FindData(args->elem->options, DOID_TEXT_DRAW)) != NULL)
	modified |= PerStateInfo_Undefine(tree, &pstBoolean, psi, elem->stateDomain, args->state);
    if ((psi = DynamicOption_FindData(args->elem->options, DOID_TEXT_FILL)) != NULL)
	modified |= PerStateInfo_Undefine(tree, &pstColor, psi, elem->stateDomain, args->state);
    if ((psi = DynamicOption_FindData(args->elem->options, DOID_TEXT_FONT)) != NULL)
	modified |= PerStateInfo_Undefine(tree, &pstFont, psi, elem->stateDomain, args->state);

    return modified;
}

static int ActualProcText(TreeElementArgs *args)
{
    TreeCtrl *tree = args->tree;
/*    ElementText *elemX = (ElementText *) args->elem;
    ElementText *masterX = (ElementText *) args->elem->master;*/
    static CONST char *optionName[] = {
#ifdef DEPRECATED
	"-draw",
#endif
	"-fill", "-font",
	(char *) NULL };
    int index;
    Tcl_Obj *obj = NULL;

    if (Tcl_GetIndexFromObj(tree->interp, args->actual.obj, optionName,
		"option", 0, &index) != TCL_OK)
	return TCL_ERROR;

    switch (index) {
#ifdef DEPRECATED
	case 0: {
	    obj = DO_ObjectForState(tree, &pstBoolean, args->elem, DOID_TEXT_DRAW, args->state);
	    break;
	}
	case 1: {
	    obj = DO_ObjectForState(tree, &pstColor, args->elem, DOID_TEXT_FILL, args->state);
	    break;
	}
	case 2: {
	    obj = DO_ObjectForState(tree, &pstFont, args->elem, DOID_TEXT_FONT, args->state);
	    break;
	}
#else
	case 0: {
	    obj = DO_ObjectForState(tree, &pstColor, args->elem, DOID_TEXT_FILL, args->state);
	    break;
	}
	case 1: {
	    obj = DO_ObjectForState(tree, &pstFont, args->elem, DOID_TEXT_FONT, args->state);
	    break;
	}
#endif
    }
    if (obj != NULL)
	Tcl_SetObjResult(tree->interp, obj);
    return TCL_OK;
}

TreeElementType treeElemTypeText = {
    "text",
    sizeof(ElementText),
    textOptionSpecs,
    NULL,
    CreateProcText,
    DeleteProcText,
    ConfigProcText,
    DisplayProcText,
    NeededProcText,
    HeightProcText,
    WorldChangedProcText,
    StateProcText,
    UndefProcText,
    ActualProcText,
    NULL /* onScreenProc */
};

/*****/

typedef struct ElementWindow ElementWindow;

struct ElementWindow
{
    TreeElement_ header;
#ifdef DEPRECATED
    PerStateInfo draw;		/* -draw */
#endif
    TreeCtrl *tree;
    TreeItem item; 		/* Needed if window changes size */
    TreeItemColumn column; 	/* Needed if window changes size */
    Tk_Window tkwin;		/* Window associated with item.  NULL means
				 * window has been destroyed. */
    int destroy;		/* Destroy window when element
				 * is deleted */
#define CLIP_WINDOW 1
#ifdef CLIP_WINDOW
    int clip;			/* TRUE if tkwin is a borderless frame
				 * widget whose first child is the actual
				 * window we want displayed. */
    Tk_Window child;		/* The first child of tkwin. tkwin is resized
				 * so that it is never out-of-bounds, and
				 * the child is positioned within tkwin to
				 * provide clipping of the child. */
#endif
};

#define EWIN_CONF_WINDOW 0x0001
#ifdef DEPRECATED
#define EWIN_CONF_DRAW 0x0002
#endif

static Tk_OptionSpec windowOptionSpecs[] = {
#ifdef CLIP_WINDOW
    {TK_OPTION_CUSTOM, "-clip", (char *) NULL, (char *) NULL,
     (char *) NULL, -1, Tk_Offset(ElementWindow, clip),
     TK_OPTION_NULL_OK, (ClientData) &booleanCO, 0},
#endif
    {TK_OPTION_CUSTOM, "-destroy", (char *) NULL, (char *) NULL,
     (char *) NULL, -1, Tk_Offset(ElementWindow, destroy),
     TK_OPTION_NULL_OK, (ClientData) &booleanCO, 0},
#ifdef DEPRECATED
    {TK_OPTION_CUSTOM, "-draw", (char *) NULL, (char *) NULL,
     (char *) NULL,
     Tk_Offset(ElementWindow, draw.obj), Tk_Offset(ElementWindow, draw),
     TK_OPTION_NULL_OK, (ClientData) NULL, EWIN_CONF_DRAW},
#endif
    {TK_OPTION_WINDOW, "-window", (char *) NULL, (char *) NULL,
     (char *) NULL, -1, Tk_Offset(ElementWindow, tkwin),
     TK_OPTION_NULL_OK, (ClientData) NULL, EWIN_CONF_WINDOW},
    {TK_OPTION_END, (char *) NULL, (char *) NULL, (char *) NULL,
     (char *) NULL, 0, -1, 0, (ClientData) NULL, 0}
};

static void
WinItemStructureProc(
    ClientData clientData,	/* Pointer to record describing window elem. */
    XEvent *eventPtr)		/* Describes what just happened. */
{
    ElementWindow *elemX = clientData;

    if (eventPtr->type == DestroyNotify) {
	elemX->tkwin = elemX->child = NULL;
	Tree_ElementChangedItself(elemX->tree, elemX->item, elemX->column,
	    (TreeElement) elemX, EWIN_CONF_WINDOW, CS_LAYOUT | CS_DISPLAY);
    }
}

static void
WinItemRequestProc(
    ClientData clientData,		/* Pointer to record for window item. */
    Tk_Window tkwin)			/* Window that changed its desired
					 * size. */
{
    ElementWindow *elemX = clientData;

#ifdef CLIP_WINDOW
    /* We don't care about size changes for the clip window. */
    if (elemX->child != NULL && tkwin != elemX->child)
	return;
#endif
    Tree_ElementChangedItself(elemX->tree, elemX->item, elemX->column,
	(TreeElement) elemX, EWIN_CONF_WINDOW, CS_LAYOUT | CS_DISPLAY);
}

static void
WinItemLostSlaveProc(
    ClientData clientData,	/* WindowItem structure for slave window that
				 * was stolen away. */
    Tk_Window tkwin)		/* Tk's handle for the slave window. */
{
    ElementWindow *elemX = clientData;
    TreeCtrl *tree = elemX->tree;

#ifdef CLIP_WINDOW
    /* If either window is lost to another geometry manager, forget
     * about both windows. */
    if (elemX->child != NULL) {
	Tk_DeleteEventHandler(elemX->child, StructureNotifyMask,
		WinItemStructureProc, (ClientData) elemX);
	if (tkwin != elemX->child) {
	    Tk_ManageGeometry(elemX->child, (Tk_GeomMgr *) NULL,
		    (ClientData) NULL);
	    Tk_UnmapWindow(elemX->child);
	}
	elemX->child = NULL;
    }
    if (elemX->tkwin != NULL) {
	Tk_DeleteEventHandler(elemX->tkwin, StructureNotifyMask,
		WinItemStructureProc, (ClientData) elemX);
	if (tkwin != elemX->tkwin) {
	    Tk_ManageGeometry(elemX->tkwin, (Tk_GeomMgr *) NULL,
		    (ClientData) NULL);
	    if (tree->tkwin != Tk_Parent(elemX->tkwin)) {
		Tk_UnmaintainGeometry(elemX->tkwin, tree->tkwin);
	    }
	    Tk_UnmapWindow(elemX->tkwin);
	}
	elemX->tkwin = NULL;
    }
#else
    Tk_DeleteEventHandler(elemX->tkwin, StructureNotifyMask,
	    WinItemStructureProc, (ClientData) elemX);
    if (tree->tkwin != Tk_Parent(elemX->tkwin)) {
	Tk_UnmaintainGeometry(elemX->tkwin, tree->tkwin);
    }
    Tk_UnmapWindow(elemX->tkwin);
    elemX->tkwin = NULL;
#endif
    Tree_ElementChangedItself(elemX->tree, elemX->item, elemX->column,
	(TreeElement) elemX, EWIN_CONF_WINDOW, CS_LAYOUT | CS_DISPLAY);
}

static Tk_GeomMgr winElemGeomType = {
    "treectrl",				/* name */
    WinItemRequestProc,			/* requestProc */
    WinItemLostSlaveProc,		/* lostSlaveProc */
};

static void DeleteProcWindow(TreeElementArgs *args)
{
    TreeCtrl *tree = args->tree;
    TreeElement elem = args->elem;
    ElementWindow *elemX = (ElementWindow *) elem;
    ElementWindow *masterX = (ElementWindow *) elem->master;

    if (elemX->tkwin != NULL) {
#ifdef CLIP_WINDOW
	if (elemX->child != NULL) {
	    Tk_DeleteEventHandler(elemX->child, StructureNotifyMask,
		    WinItemStructureProc, (ClientData) elemX);
	    Tk_ManageGeometry(elemX->child, (Tk_GeomMgr *) NULL,
		    (ClientData) NULL);
	    Tk_UnmapWindow(elemX->child);
	    elemX->child = NULL;
	}
#endif
	Tk_DeleteEventHandler(elemX->tkwin, StructureNotifyMask,
		WinItemStructureProc, (ClientData) elemX);
	Tk_ManageGeometry(elemX->tkwin, (Tk_GeomMgr *) NULL,
		(ClientData) NULL);
	if (tree->tkwin != Tk_Parent(elemX->tkwin)) {
	    Tk_UnmaintainGeometry(elemX->tkwin, tree->tkwin);
	}
	Tk_UnmapWindow(elemX->tkwin);

	if ((elemX->destroy == 1) ||
		((masterX != NULL) && (masterX->destroy == 1))) {
	    Tk_DestroyWindow(elemX->tkwin);
	}

	elemX->tkwin = NULL;
    }
}

static int WorldChangedProcWindow(TreeElementArgs *args)
{
    int flagM = args->change.flagMaster;
    int flagS = args->change.flagSelf;
    int mask = 0;

#ifdef DEPRECATED
    if ((flagS | flagM) & (EWIN_CONF_DRAW))
	mask |= CS_DISPLAY;
#endif

    if ((flagS | flagM) & (EWIN_CONF_WINDOW))
	mask |= CS_DISPLAY | CS_LAYOUT;

    return mask;
}

static int ConfigProcWindow(TreeElementArgs *args)
{
    TreeCtrl *tree = args->tree;
    TreeElement elem = args->elem;
    ElementWindow *elemX = (ElementWindow *) elem;
    ElementWindow *masterX = (ElementWindow *) elem->master;
    ElementWindow savedX;
    Tk_SavedOptions savedOptions;
    int error;
    Tcl_Obj *errorResult = NULL;

    savedX.tkwin = elemX->tkwin;

    for (error = 0; error <= 1; error++) {
	if (error == 0) {
	    if (Tree_SetOptions(tree, elem->stateDomain, elemX,
			elem->typePtr->optionTable,
			args->config.objc, args->config.objv,
			&savedOptions, &args->config.flagSelf) != TCL_OK) {
		args->config.flagSelf = 0;
		continue;
	    }

	    if (args->config.flagSelf & EWIN_CONF_WINDOW) {
		if ((elem->master == NULL) && (elemX->tkwin != NULL)){
		    FormatResult(tree->interp, "can't specify -window for a master element");
		    continue;
		}
	    }

	    Tk_FreeSavedOptions(&savedOptions);
	    break;
	} else {
	    errorResult = Tcl_GetObjResult(tree->interp);
	    Tcl_IncrRefCount(errorResult);
	    Tk_RestoreSavedOptions(&savedOptions);

	    Tcl_SetObjResult(tree->interp, errorResult);
	    Tcl_DecrRefCount(errorResult);
	    return TCL_ERROR;
	}
    }

    if (savedX.tkwin != elemX->tkwin) {
	if (savedX.tkwin != NULL) {
#ifdef CLIP_WINDOW
	    if (elemX->child != NULL) {
		Tk_DeleteEventHandler(elemX->child, StructureNotifyMask,
			WinItemStructureProc, (ClientData) elemX);
		Tk_ManageGeometry(elemX->child, (Tk_GeomMgr *) NULL,
			(ClientData) NULL);
		Tk_UnmapWindow(elemX->child);
		elemX->child = NULL;
	    }
#endif
	    Tk_DeleteEventHandler(savedX.tkwin, StructureNotifyMask,
		    WinItemStructureProc, (ClientData) elemX);
	    Tk_ManageGeometry(savedX.tkwin, (Tk_GeomMgr *) NULL,
		    (ClientData) NULL);
	    Tk_UnmaintainGeometry(savedX.tkwin, tree->tkwin);
	    Tk_UnmapWindow(savedX.tkwin);
	}
	if (elemX->tkwin != NULL) {
	    Tk_Window ancestor, parent;

	    /*
	     * Make sure that the treectrl is either the parent of the
	     * window associated with the element or a descendant of that
	     * parent.  Also, don't allow a top-of-hierarchy window to be
	     * managed inside a treectrl.
	     */

	    parent = Tk_Parent(elemX->tkwin);
	    for (ancestor = tree->tkwin; ;
		    ancestor = Tk_Parent(ancestor)) {
		if (ancestor == parent) {
		    break;
		}
		if (((Tk_FakeWin *) (ancestor))->flags & TK_TOP_HIERARCHY) {
		    badWindow:
		    FormatResult(tree->interp,
			    "can't use %s in a window element of %s",
			    Tk_PathName(elemX->tkwin),
			    Tk_PathName(tree->tkwin));
		    elemX->tkwin = NULL;
		    return TCL_ERROR;
		}
	    }
	    if (((Tk_FakeWin *) (elemX->tkwin))->flags & TK_TOP_HIERARCHY) {
		goto badWindow;
	    }
	    if (elemX->tkwin == tree->tkwin) {
		goto badWindow;
	    }
#ifdef CLIP_WINDOW
	    if ((elemX->clip == 1) || ((masterX != NULL) && (masterX->clip == 1))) {
		elemX->child = (Tk_Window) ((TkWindow *) elemX->tkwin)->childList;
		if (elemX->child != NULL) {
		    Tk_CreateEventHandler(elemX->child, StructureNotifyMask,
			    WinItemStructureProc, (ClientData) elemX);
		    Tk_ManageGeometry(elemX->child, &winElemGeomType,
			    (ClientData) elemX);
		}
	    }
#endif
	    Tk_CreateEventHandler(elemX->tkwin, StructureNotifyMask,
		    WinItemStructureProc, (ClientData) elemX);
	    Tk_ManageGeometry(elemX->tkwin, &winElemGeomType,
		    (ClientData) elemX);
	}
    }
#if 0
    if ((elemX->tkwin != NULL)
	    && (itemPtr->state == TK_STATE_HIDDEN)) {
	if (tree->tkwin == Tk_Parent(elemX->tkwin)) {
	    Tk_UnmapWindow(elemX->tkwin);
	} else {
	    Tk_UnmaintainGeometry(elemX->tkwin, tree->tkwin);
	}
    }
#endif
    return TCL_OK;
}

static int CreateProcWindow(TreeElementArgs *args)
{
    TreeCtrl *tree = args->tree;
    TreeElement elem = args->elem;
    ElementWindow *elemX = (ElementWindow *) elem;

    elemX->tree = tree;
    elemX->item = args->create.item;
    elemX->column = args->create.column;
    elemX->destroy = -1;
#ifdef CLIP_WINDOW
    elemX->clip = -1;
#endif
    return TCL_OK;
}

static void DisplayProcWindow(TreeElementArgs *args)
{
    TreeCtrl *tree = args->tree;
    TreeElement elem = args->elem;
    ElementWindow *elemX = (ElementWindow *) elem;
    ElementWindow *masterX = (ElementWindow *) elem->master;
    int state = args->state;
    int x = args->display.x, y = args->display.y;
    int minX, maxX, minY, maxY;
    int width, height;
    int match, match2;
#ifdef DEPRECATED
    int draw;
#endif
    int requests;

#ifdef DEPRECATED
    BOOLEAN_FOR_STATE(draw, draw, state);
    if (!draw)
	goto hideIt;
#endif

    if (elemX->tkwin == NULL)
	return;

#ifdef CLIP_WINDOW
    if (elemX->child != NULL) {
	width = Tk_ReqWidth(elemX->child);
	height = Tk_ReqHeight(elemX->child);
    } else {
	width = Tk_ReqWidth(elemX->tkwin);
	height = Tk_ReqHeight(elemX->tkwin);
    }
    if (width < 1 || height < 1)
	goto hideIt;
#else
    width = Tk_ReqWidth(elemX->tkwin);
    height = Tk_ReqHeight(elemX->tkwin);
#endif
    AdjustForSticky(args->display.sticky,
	args->display.width, args->display.height,
	TRUE, TRUE,
	&x, &y, &width, &height);

    x += tree->drawableXOrigin - tree->xOrigin;
    y += tree->drawableYOrigin - tree->yOrigin;

    /* -squeeze layout may give the element less space than requested. */
    if (width > args->display.width)
	width = args->display.width;
    if (height > args->display.height)
	height = args->display.height;

    TreeRect_XYXY(args->display.bounds, &minX, &minY, &maxX, &maxY);

    /*
     * If the window is completely out of the visible area of the treectrl
     * then unmap it.  The window could suddenly reappear if the treectrl
     * window gets resized.
     */

    if (((x + width) <= minX) || ((y + height) <= minY)
	    || (x >= maxX) || (y >= maxY)) {
hideIt:
	if (tree->tkwin == Tk_Parent(elemX->tkwin)) {
	    Tk_UnmapWindow(elemX->tkwin);
	} else {
	    Tk_UnmaintainGeometry(elemX->tkwin, tree->tkwin);
	}
	return;
    }

    TreeDisplay_GetReadyForTrouble(tree, &requests);

#ifdef CLIP_WINDOW
    if (elemX->child != NULL) {
	int cx = x, cy = y, cw = width, ch = height; /* clip win coords */

	if (cx < minX) {
	    cw -= minX - cx;
	    cx = minX;
	}
	if (cy < minY) {
	    ch -= minY - cy;
	    cy = minY;
	}
	if (cx + cw > maxX)
	    cw = maxX - cx;
	if (cy + ch > maxY)
	    ch = maxY - cy;

	/*
	* Reposition and map the window (but in different ways depending
	* on whether the treectrl is the window's parent).
	*/

	if (tree->tkwin == Tk_Parent(elemX->tkwin)) {
	    if ((cx != Tk_X(elemX->tkwin)) || (cy != Tk_Y(elemX->tkwin))
		    || (cw != Tk_Width(elemX->tkwin))
		    || (ch != Tk_Height(elemX->tkwin))) {
		Tk_MoveResizeWindow(elemX->tkwin, cx, cy, cw, ch);
		if (TreeDisplay_WasThereTrouble(tree, requests))
		    return;
	    }
	    Tk_MapWindow(elemX->tkwin);
	} else {
	    Tk_MaintainGeometry(elemX->tkwin, tree->tkwin, cx, cy, cw, ch);
	}
	if (TreeDisplay_WasThereTrouble(tree, requests))
	    return;

	/*
	 * Position the child window within the clip window.
	 */
	x -= cx;
	y -= cy;
	if ((x != Tk_X(elemX->child)) || (y != Tk_Y(elemX->child))
		|| (width != Tk_Width(elemX->child))
		|| (height != Tk_Height(elemX->child))) {
	    Tk_MoveResizeWindow(elemX->child, x, y, width, height);
	    if (TreeDisplay_WasThereTrouble(tree, requests))
		return;
	}
	Tk_MapWindow(elemX->child);
	return;
    }
#endif /* CLIP_WINDOW */

    /*
     * Reposition and map the window (but in different ways depending
     * on whether the treectrl is the window's parent).
     */

    if (tree->tkwin == Tk_Parent(elemX->tkwin)) {
	if ((x != Tk_X(elemX->tkwin)) || (y != Tk_Y(elemX->tkwin))
		|| (width != Tk_Width(elemX->tkwin))
		|| (height != Tk_Height(elemX->tkwin))) {
	    Tk_MoveResizeWindow(elemX->tkwin, x, y, width, height);
	    if (TreeDisplay_WasThereTrouble(tree, requests))
		return;
	}
	Tk_MapWindow(elemX->tkwin);
    } else {
	Tk_MaintainGeometry(elemX->tkwin, tree->tkwin, x, y,
		width, height);
    }
}

static void NeededProcWindow(TreeElementArgs *args)
{
/*    TreeCtrl *tree = args->tree;*/
    TreeElement elem = args->elem;
    ElementWindow *elemX = (ElementWindow *) elem;
/*    ElementWindow *masterX = (ElementWindow *) elem->master;
    int state = args->state;*/
    int width = 0, height = 0;

#ifdef CLIP_WINDOW
    if (elemX->child != NULL) {
	width = Tk_ReqWidth(elemX->child);
	if (width <= 0) {
	    width = 1;
	}
	height = Tk_ReqHeight(elemX->child);
	if (height <= 0) {
	    height = 1;
	}
    } else
#endif
    if (elemX->tkwin) {
	width = Tk_ReqWidth(elemX->tkwin);
	if (width <= 0) {
	    width = 1;
	}
	height = Tk_ReqHeight(elemX->tkwin);
	if (height <= 0) {
	    height = 1;
	}
    }
    args->needed.width = width;
    args->needed.height = height;
}

static int StateProcWindow(TreeElementArgs *args)
{
#ifdef DEPRECATED
    TreeCtrl *tree = args->tree;
    TreeElement elem = args->elem;
    ElementBitmap *elemX = (ElementBitmap *) elem;
    ElementBitmap *masterX = (ElementBitmap *) elem->master;
    int match, match2;
    int draw1, draw2;

    if (!args->states.visible2 || !args->states.draw2)
	return 0;

    BOOLEAN_FOR_STATE(draw1, draw, args->states.state1)
    BOOLEAN_FOR_STATE(draw2, draw, args->states.state2)
    if ((draw1 != 0) != (draw2 != 0))
	return CS_DISPLAY;
#endif

    return 0;
}

static int UndefProcWindow(TreeElementArgs *args)
{
    TreeCtrl *tree = args->tree;
    TreeElement elem = args->elem;
    ElementWindow *elemX = (ElementWindow *) elem;
    int modified = 0;

#ifdef DEPRECATED
    modified |= PerStateInfo_Undefine(tree, &pstBoolean, &elemX->draw, elem->stateDomain, args->state);
#endif
    return modified;
}

static int ActualProcWindow(TreeElementArgs *args)
{
#ifdef DEPRECATED
    TreeCtrl *tree = args->tree;
    ElementWindow *elemX = (ElementWindow *) args->elem;
    ElementWindow *masterX = (ElementWindow *) args->elem->master;
    static CONST char *optionName[] = {
	"-draw",
	(char *) NULL };
    int index, match, matchM;
    Tcl_Obj *obj = NULL;

    if (Tcl_GetIndexFromObj(tree->interp, args->actual.obj, optionName,
		"option", 0, &index) != TCL_OK)
	return TCL_ERROR;

    switch (index) {
	case 0: {
	    OBJECT_FOR_STATE(obj, pstBoolean, draw, args->state)
	    break;
	}
    }
    if (obj != NULL)
	Tcl_SetObjResult(tree->interp, obj);
#endif
    return TCL_OK;
}

static void OnScreenProcWindow(TreeElementArgs *args)
{
    TreeCtrl *tree = args->tree;
    TreeElement elem = args->elem;
    ElementWindow *elemX = (ElementWindow *) elem;

    if (!args->screen.visible && (elemX->tkwin != NULL)) {
	if (tree->tkwin == Tk_Parent(elemX->tkwin)) {
	    Tk_UnmapWindow(elemX->tkwin);
	} else {
	    Tk_UnmaintainGeometry(elemX->tkwin, tree->tkwin);
	}
    }
}

TreeElementType treeElemTypeWindow = {
    "window",
    sizeof(ElementWindow),
    windowOptionSpecs,
    NULL,
    CreateProcWindow,
    DeleteProcWindow,
    ConfigProcWindow,
    DisplayProcWindow,
    NeededProcWindow,
    NULL, /* heightProc */
    WorldChangedProcWindow,
    StateProcWindow,
    UndefProcWindow,
    ActualProcWindow,
    OnScreenProcWindow
};

/*****/

/* This structure holds the list of element types for an interpreter.
 * Each element type has a Tk_OptionTable that is per-interp. */
typedef struct ElementAssocData ElementAssocData;
struct ElementAssocData
{
    TreeElementType *typeList;
};

int TreeElement_TypeFromObj(TreeCtrl *tree, Tcl_Obj *objPtr, TreeElementType **typePtrPtr)
{
    Tcl_Interp *interp = tree->interp;
    ElementAssocData *assocData;
    char *typeStr;
    int length;
    TreeElementType *typeList;
    TreeElementType *typePtr, *matchPtr = NULL;

    assocData = Tcl_GetAssocData(interp, "TreeCtrlElementTypes", NULL);
    typeList = assocData->typeList;

    typeStr = Tcl_GetStringFromObj(objPtr, &length);
    if (!length) {
	FormatResult(interp, "invalid element type \"\"");
	return TCL_ERROR;
    }
    for (typePtr = typeList;
	typePtr != NULL;
	typePtr = typePtr->next) {
	if ((typeStr[0] == typePtr->name[0]) &&
		!strncmp(typeStr, typePtr->name, length)) {
	    if (matchPtr != NULL) {
		FormatResult(interp,
			"ambiguous element type \"%s\"",
			typeStr);
		return TCL_ERROR;
	    }
	    matchPtr = typePtr;
	}
    }
    if (matchPtr == NULL) {
	FormatResult(interp, "unknown element type \"%s\"", typeStr);
	return TCL_ERROR;
    }
    *typePtrPtr = matchPtr;

    return TCL_OK;
}

int TreeCtrl_RegisterElementType(Tcl_Interp *interp, TreeElementType *newTypePtr)
{
    ElementAssocData *assocData;
    TreeElementType *typeList;
    TreeElementType *prevPtr, *typePtr, *nextPtr;

    assocData = Tcl_GetAssocData(interp, "TreeCtrlElementTypes", NULL);
    typeList = assocData->typeList;

    for (typePtr = typeList, prevPtr = NULL;
	 typePtr != NULL;
	 prevPtr = typePtr, typePtr = nextPtr) {
	nextPtr = typePtr->next;
	/* Remove duplicate type */
	if (!strcmp(typePtr->name, newTypePtr->name)) {
	    if (prevPtr == NULL)
		typeList = typePtr->next;
	    else
		prevPtr->next = typePtr->next;
	    ckfree((char *) typePtr);
	}
    }
    typePtr = (TreeElementType *) ckalloc(sizeof(TreeElementType));
    memcpy(typePtr, newTypePtr, sizeof(TreeElementType));

    typePtr->next = typeList;
    typeList = typePtr;

    typePtr->optionTable = Tk_CreateOptionTable(interp,
	    newTypePtr->optionSpecs);

    assocData->typeList = typeList;

    return TCL_OK;
}

static TreeCtrlStubs stubs = {
#ifdef TREECTRL_DEBUG
    sizeof(TreeCtrl),
    sizeof(TreeCtrlStubs),
    sizeof(TreeElement),
    sizeof(TreeElementArgs),
#endif
    TreeCtrl_RegisterElementType,
    Tree_RedrawElement,
    Tree_ElementIterateBegin,
    Tree_ElementIterateNext,
    Tree_ElementIterateGet,
    Tree_ElementIterateChanged,
    PerStateInfo_Free,
    PerStateInfo_FromObj,
    PerStateInfo_ForState,
    PerStateInfo_ObjForState,
    PerStateInfo_Undefine,
    &pstBoolean,
    PerStateBoolean_ForState,
    PSTSave,
    PSTRestore,
    TreeStateFromObj,
    BooleanCO_Init,
    StringTableCO_Init,
    PerStateCO_Init
};

/*
 *----------------------------------------------------------------------
 *
 * TreeElement_InitWidget --
 *
 *	Element-related initialization when a treectrl is created.
 *
 * Results:
 *	A standard Tcl result.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

int
TreeElement_InitWidget(
    TreeCtrl *tree
    )
{
    /* These options are used by the text element. Originally these were
     * static globals but that isn't thread safe. */
    tree->formatFloatObj = Tcl_NewStringObj("%g", -1);
    tree->formatIntObj = Tcl_NewStringObj("%d", -1);
    tree->formatLongObj = Tcl_NewStringObj("%ld", -1);
    tree->formatStringObj = Tcl_NewStringObj("%s", -1);
    Tcl_IncrRefCount(tree->formatFloatObj);
    Tcl_IncrRefCount(tree->formatIntObj);
    Tcl_IncrRefCount(tree->formatLongObj);
    Tcl_IncrRefCount(tree->formatStringObj);

    tree->stringClockObj = Tcl_NewStringObj("clock", -1);
    tree->stringFormatObj = Tcl_NewStringObj("format", -1);
    tree->optionFormatObj = Tcl_NewStringObj("-format", -1);
    Tcl_IncrRefCount(tree->stringClockObj);
    Tcl_IncrRefCount(tree->stringFormatObj);
    Tcl_IncrRefCount(tree->optionFormatObj);

    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * TreeElement_FreeWidget --
 *
 *	Free element-related resources for a deleted TreeCtrl.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

void
TreeElement_FreeWidget(
    TreeCtrl *tree
    )
{
    Tcl_DecrRefCount(tree->formatFloatObj);
    Tcl_DecrRefCount(tree->formatIntObj);
    Tcl_DecrRefCount(tree->formatLongObj);
    Tcl_DecrRefCount(tree->formatStringObj);

    Tcl_DecrRefCount(tree->stringClockObj);
    Tcl_DecrRefCount(tree->stringFormatObj);
    Tcl_DecrRefCount(tree->optionFormatObj);
}

static void FreeAssocData(ClientData clientData, Tcl_Interp *interp)
{
    ElementAssocData *assocData = clientData;
    TreeElementType *typeList = assocData->typeList;
    TreeElementType *next;

    while (typeList != NULL) {
	next = typeList->next;
	/* The ElementType.optionTables are freed when the interp is deleted */
	ckfree((char *) typeList);
	typeList = next;
    }
    ckfree((char *) assocData);
}

/*
 *----------------------------------------------------------------------
 *
 * TreeElement_InitInterp --
 *
 *	Element-related initialization when the package is loaded.
 *
 * Results:
 *	A standard Tcl result.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

int
TreeElement_InitInterp(
    Tcl_Interp *interp
    )
{
    ElementAssocData *assocData;

    /* FIXME: memory leak with dynamically-allocated ClientData. */

    /*
     * bitmap
     */
    PerStateCO_Init(treeElemTypeBitmap.optionSpecs, "-background",
	&pstColor, TreeStateFromObj);
    PerStateCO_Init(treeElemTypeBitmap.optionSpecs, "-bitmap",
	&pstBitmap, TreeStateFromObj);
#ifdef DEPRECATED
    PerStateCO_Init(treeElemTypeBitmap.optionSpecs, "-draw",
	&pstBoolean, TreeStateFromObj);
#endif
    PerStateCO_Init(treeElemTypeBitmap.optionSpecs, "-foreground",
	&pstColor, TreeStateFromObj);

    /*
     * border
     */
#ifdef DEPRECATED
    PerStateCO_Init(treeElemTypeBorder.optionSpecs, "-draw",
	&pstBoolean, TreeStateFromObj);
#endif
    PerStateCO_Init(treeElemTypeBorder.optionSpecs, "-background",
	&pstBorder, TreeStateFromObj);
    PerStateCO_Init(treeElemTypeBorder.optionSpecs, "-relief",
	&pstRelief, TreeStateFromObj);

    /*
     * header
     */
    StringTableCO_Init(treeElemTypeHeader.optionSpecs, "-arrow",
	headerArrowST);
    PerStateCO_Init(treeElemTypeHeader.optionSpecs, "-arrowbitmap",
	&pstBitmap, TreeStateFromObj);
    StringTableCO_Init(treeElemTypeHeader.optionSpecs, "-arrowgravity",
	headerArrowSideST);
    PerStateCO_Init(treeElemTypeHeader.optionSpecs, "-arrowimage",
	&pstImage, TreeStateFromObj);
    StringTableCO_Init(treeElemTypeHeader.optionSpecs, "-arrowside",
	headerArrowSideST);
    PerStateCO_Init(treeElemTypeHeader.optionSpecs, "-background",
	&pstBorder, TreeStateFromObj);
    StringTableCO_Init(treeElemTypeHeader.optionSpecs, "-state", headerStateST);

    /*
     * image
     */
#ifdef DEPRECATED
    DynamicCO_Init(treeElemTypeImage.optionSpecs, "-draw",
	1002, sizeof(PerStateInfo),
	Tk_Offset(PerStateInfo, obj),
	0, PerStateCO_Alloc("-draw", &pstBoolean, TreeStateFromObj),
	(DynamicOptionInitProc *) NULL);
#endif
    PerStateCO_Init(treeElemTypeImage.optionSpecs, "-image",
	&pstImage, TreeStateFromObj);

    /* 2 options in the same structure. */
    DynamicCO_Init(treeElemTypeImage.optionSpecs, "-height",
	1001, sizeof(ElementImageSize),
	Tk_Offset(ElementImageSize, heightObj),
	Tk_Offset(ElementImageSize, height), &TreeCtrlCO_pixels,
	(DynamicOptionInitProc *) NULL);
    DynamicCO_Init(treeElemTypeImage.optionSpecs, "-width",
	1001, sizeof(ElementImageSize),
	Tk_Offset(ElementImageSize, widthObj),
	Tk_Offset(ElementImageSize, width), &TreeCtrlCO_pixels,
	(DynamicOptionInitProc *) NULL);

    DynamicCO_Init(treeElemTypeImage.optionSpecs, "-tiled",
	1003, sizeof(int),
	-1,
	0, &booleanCO,
	DynamicOptionInitBoolean);

    /*
     * rect
     */
#ifdef DEPRECATED
    PerStateCO_Init(treeElemTypeRect.optionSpecs, "-draw",
	&pstBoolean, TreeStateFromObj);
#endif
    PerStateCO_Init(treeElemTypeRect.optionSpecs, "-fill",
	&pstColor, TreeStateFromObj);
    PerStateCO_Init(treeElemTypeRect.optionSpecs, "-open",
	&pstFlags, TreeStateFromObj);
    PerStateCO_Init(treeElemTypeRect.optionSpecs, "-outline",
	&pstColor, TreeStateFromObj);

    /*
     * text
     */
    /* 3 options in the same structure. */
    DynamicCO_Init(treeElemTypeText.optionSpecs, "-data",
	DOID_TEXT_DATA, sizeof(ElementTextData),
	Tk_Offset(ElementTextData, dataObj),
	-1, &TreeCtrlCO_string,
	ElementTextDataInit);
    DynamicCO_Init(treeElemTypeText.optionSpecs, "-datatype",
	DOID_TEXT_DATA, sizeof(ElementTextData),
	-1,
	Tk_Offset(ElementTextData, dataType),
	StringTableCO_Alloc("-datatype", textDataTypeST),
	ElementTextDataInit);
    DynamicCO_Init(treeElemTypeText.optionSpecs, "-format",
	DOID_TEXT_DATA, sizeof(ElementTextData),
	Tk_Offset(ElementTextData, formatObj),
	-1, &TreeCtrlCO_string,
	ElementTextDataInit);

    /* 5 options in the same structure. */
    DynamicCO_Init(treeElemTypeText.optionSpecs, "-justify",
	DOID_TEXT_LAYOUT, sizeof(ElementTextLayout),
	-1,
	Tk_Offset(ElementTextLayout, justify),
	StringTableCO_Alloc("-justify", textJustifyST),
	ElementTextLayoutInit);
    DynamicCO_Init(treeElemTypeText.optionSpecs, "-lines",
	DOID_TEXT_LAYOUT, sizeof(ElementTextLayout),
	-1,
	Tk_Offset(ElementTextLayout, lines),
	IntegerCO_Alloc("-lines",
	    0,		/* min */
	    0,		/* max (ignored) */
	    -1,		/* empty */
	    0x01),	/* flags: min */
	ElementTextLayoutInit);
    DynamicCO_Init(treeElemTypeText.optionSpecs, "-width",
	DOID_TEXT_LAYOUT, sizeof(ElementTextLayout),
	Tk_Offset(ElementTextLayout, widthObj),
	Tk_Offset(ElementTextLayout, width), &TreeCtrlCO_pixels,
	ElementTextLayoutInit);
    DynamicCO_Init(treeElemTypeText.optionSpecs, "-wrap",
	DOID_TEXT_LAYOUT, sizeof(ElementTextLayout),
	-1,
	Tk_Offset(ElementTextLayout, wrap),
	StringTableCO_Alloc("-wrap", textWrapST),
	ElementTextLayoutInit);
    DynamicCO_Init(treeElemTypeText.optionSpecs, "-elidepos",
	DOID_TEXT_LAYOUT, sizeof(ElementTextLayout),
	-1,
	Tk_Offset(ElementTextLayout, elidePos),
	StringTableCO_Alloc("-elidepos", textElideST),
	ElementTextLayoutInit);

#ifdef DEPRECATED
    DynamicCO_Init(treeElemTypeText.optionSpecs, "-draw",
	DOID_TEXT_DRAW, sizeof(PerStateInfo),
	Tk_Offset(PerStateInfo, obj),
	0, PerStateCO_Alloc("-draw", &pstBoolean, TreeStateFromObj),
	(DynamicOptionInitProc *) NULL);
#endif
    DynamicCO_Init(treeElemTypeText.optionSpecs, "-fill",
	DOID_TEXT_FILL, sizeof(PerStateInfo),
	Tk_Offset(PerStateInfo, obj),
	0, PerStateCO_Alloc("-fill", &pstColor, TreeStateFromObj),
	(DynamicOptionInitProc *) NULL);
    DynamicCO_Init(treeElemTypeText.optionSpecs, "-font",
	DOID_TEXT_FONT, sizeof(PerStateInfo),
	Tk_Offset(PerStateInfo, obj),
	0, PerStateCO_Alloc("-font", &pstFont, TreeStateFromObj),
	(DynamicOptionInitProc *) NULL);
#ifdef TEXTVAR
    DynamicCO_Init(treeElemTypeText.optionSpecs, "-textvariable",
	DOID_TEXT_VAR, sizeof(ElementTextVar),
	Tk_Offset(struct ElementTextVar, varNameObj),
	-1, &TreeCtrlCO_string,
	(DynamicOptionInitProc *) NULL);
#endif

#ifdef TEXT_STYLE
    DynamicCO_Init(treeElemTypeText.optionSpecs, "-underline",
	DOID_TEXT_STYLE, sizeof(ElementTextStyle),
	-1,
	Tk_Offset(ElementTextStyle, underline),
	IntegerCO_Alloc("-underline",
	    0,		/* min (ignored) */
	    0,		/* max (ignored) */
	    TEXT_UNDERLINE_EMPTYVAL,	/* empty */
	    0x00),	/* flags */
	ElementTextStyleInit);
#endif

    /* 2 options in the same structure */
    DynamicCO_Init(treeElemTypeText.optionSpecs, "-lmargin1",
	DOID_TEXT_LAYOUT3, sizeof(ElementTextLayout3),
	Tk_Offset(ElementTextLayout3, lMargin1Obj),
	Tk_Offset(ElementTextLayout3, lMargin1), &TreeCtrlCO_pixels,
	(DynamicOptionInitProc *) NULL);
    DynamicCO_Init(treeElemTypeText.optionSpecs, "-lmargin2",
	DOID_TEXT_LAYOUT3, sizeof(ElementTextLayout3),
	Tk_Offset(ElementTextLayout3, lMargin2Obj),
	Tk_Offset(ElementTextLayout3, lMargin2), &TreeCtrlCO_pixels,
	(DynamicOptionInitProc *) NULL);

    /*
     * window
     */
#ifdef DEPRECATED
    PerStateCO_Init(treeElemTypeWindow.optionSpecs, "-draw",
	&pstBoolean, TreeStateFromObj);
#endif

    assocData = (ElementAssocData *) ckalloc(sizeof(ElementAssocData));
    assocData->typeList = NULL;
    Tcl_SetAssocData(interp, "TreeCtrlElementTypes", FreeAssocData, assocData);

    TreeCtrl_RegisterElementType(interp, &treeElemTypeBitmap);
    TreeCtrl_RegisterElementType(interp, &treeElemTypeBorder);
/*    TreeCtrl_RegisterElementType(interp, &treeElemTypeCheckButton);*/
    TreeCtrl_RegisterElementType(interp, &treeElemTypeHeader);
    TreeCtrl_RegisterElementType(interp, &treeElemTypeImage);
    TreeCtrl_RegisterElementType(interp, &treeElemTypeRect);
    TreeCtrl_RegisterElementType(interp, &treeElemTypeText);
    TreeCtrl_RegisterElementType(interp, &treeElemTypeWindow);

    Tcl_SetAssocData(interp, "TreeCtrlStubs", NULL, &stubs);

    return TCL_OK;
}
