/*
 * ttkLayout.c --
 *
 * Generic layout processing.
 *
 * Copyright (c) 2003 Joe English.  Freely redistributable.
 */

#include "tkInt.h"
#include "ttkThemeInt.h"

#define MAX(a,b) (a > b ? a : b)
#define MIN(a,b) (a < b ? a : b)

/*------------------------------------------------------------------------
 * +++ Ttk_Box and Ttk_Padding utilities:
 */

Ttk_Box
Ttk_MakeBox(int x, int y, int width, int height)
{
    Ttk_Box b;
    b.x = x; b.y = y; b.width = width; b.height = height;
    return b;
}

int
Ttk_BoxContains(Ttk_Box box, int x, int y)
{
    return box.x <= x && x < box.x + box.width
	&& box.y <= y && y < box.y + box.height;
}

Tcl_Obj *
Ttk_NewBoxObj(Ttk_Box box)
{
    Tcl_Obj *result[4];

    result[0] = Tcl_NewIntObj(box.x);
    result[1] = Tcl_NewIntObj(box.y);
    result[2] = Tcl_NewIntObj(box.width);
    result[3] = Tcl_NewIntObj(box.height);

    return Tcl_NewListObj(4, result);
}

/*
 * packTop, packBottom, packLeft, packRight --
 * 	Carve out a parcel of the specified height (resp width)
 * 	from the specified cavity.
 *
 * Returns:
 * 	The new parcel.
 *
 * Side effects:
 * 	Adjust the cavity.
 */

static Ttk_Box packTop(Ttk_Box *cavity, int height)
{
    Ttk_Box parcel;
    height = MIN(height, cavity->height);
    parcel = Ttk_MakeBox(cavity->x, cavity->y, cavity->width, height);
    cavity->y += height;
    cavity->height -= height;
    return parcel;
}

static Ttk_Box packBottom(Ttk_Box *cavity, int height)
{
    height = MIN(height, cavity->height);
    cavity->height -= height;
    return Ttk_MakeBox(
	cavity->x, cavity->y + cavity->height,
	cavity->width, height);
}

static Ttk_Box packLeft(Ttk_Box *cavity, int width)
{
    Ttk_Box parcel;
    width = MIN(width, cavity->width);
    parcel = Ttk_MakeBox(cavity->x, cavity->y, width,cavity->height);
    cavity->x += width;
    cavity->width -= width;
    return parcel;
}

static Ttk_Box packRight(Ttk_Box *cavity, int width)
{
    width = MIN(width, cavity->width);
    cavity->width -= width;
    return Ttk_MakeBox(cavity->x + cavity->width,
	    cavity->y, width, cavity->height);
}

/*
 * Ttk_PackBox --
 * 	Carve out a parcel of the specified size on the specified side
 * 	in the specified cavity.
 *
 * Returns:
 * 	The new parcel.
 *
 * Side effects:
 * 	Adjust the cavity.
 */

Ttk_Box Ttk_PackBox(Ttk_Box *cavity, int width, int height, Ttk_Side side)
{
    switch (side) {
	default:
	case TTK_SIDE_TOP:	return packTop(cavity, height);
	case TTK_SIDE_BOTTOM:	return packBottom(cavity, height);
	case TTK_SIDE_LEFT:	return packLeft(cavity, width);
	case TTK_SIDE_RIGHT:	return packRight(cavity, width);
    }
}

/*
 * Ttk_PadBox --
 * 	Shrink a box by the specified padding amount.
 */
Ttk_Box Ttk_PadBox(Ttk_Box b, Ttk_Padding p)
{
    b.x += p.left;
    b.y += p.top;
    b.width -= (p.left + p.right);
    b.height -= (p.top + p.bottom);
    if (b.width <= 0) b.width = 1;
    if (b.height <= 0) b.height = 1;
    return b;
}

/*
 * Ttk_ExpandBox --
 * 	Grow a box by the specified padding amount.
 */
Ttk_Box Ttk_ExpandBox(Ttk_Box b, Ttk_Padding p)
{
    b.x -= p.left;
    b.y -= p.top;
    b.width += (p.left + p.right);
    b.height += (p.top + p.bottom);
    return b;
}

/*
 * Ttk_StickBox --
 * 	Place a box of size w * h in the specified parcel,
 * 	according to the specified sticky bits.
 */
Ttk_Box Ttk_StickBox(Ttk_Box parcel, int width, int height, unsigned sticky)
{
    int dx, dy;

    if (width > parcel.width) width = parcel.width;
    if (height > parcel.height) height = parcel.height;

    dx = parcel.width - width;
    dy = parcel.height - height;

    /*
     * X coordinate adjustment:
     */
    switch (sticky & (TTK_STICK_W | TTK_STICK_E))
    {
	case TTK_STICK_W | TTK_STICK_E:
	    /* no-op -- use entire parcel width */
	    break;
	case TTK_STICK_W:
	    parcel.width = width;
	    break;
	case TTK_STICK_E:
	    parcel.x += dx;
	    parcel.width = width;
	    break;
	default :
	    parcel.x += dx / 2;
	    parcel.width = width;
	    break;
    }

    /*
     * Y coordinate adjustment:
     */
    switch (sticky & (TTK_STICK_N | TTK_STICK_S))
    {
	case TTK_STICK_N | TTK_STICK_S:
	    /* use entire parcel height */
	    break;
	case TTK_STICK_N:
	    parcel.height = height;
	    break;
	case TTK_STICK_S:
	    parcel.y += dy;
	    parcel.height = height;
	    break;
	default :
	    parcel.y += dy / 2;
	    parcel.height = height;
	    break;
    }

    return parcel;
}

/*
 * AnchorToSticky --
 * 	Convert a Tk_Anchor enum to a TTK_STICKY bitmask.
 */
static Ttk_Sticky AnchorToSticky(Tk_Anchor anchor)
{
    switch (anchor)
    {
	case TK_ANCHOR_N:	return TTK_STICK_N;
	case TK_ANCHOR_NE:	return TTK_STICK_N | TTK_STICK_E;
	case TK_ANCHOR_E:	return TTK_STICK_E;
	case TK_ANCHOR_SE:	return TTK_STICK_S | TTK_STICK_E;
	case TK_ANCHOR_S:	return TTK_STICK_S;
	case TK_ANCHOR_SW:	return TTK_STICK_S | TTK_STICK_W;
	case TK_ANCHOR_W:	return TTK_STICK_W;
	case TK_ANCHOR_NW:	return TTK_STICK_N | TTK_STICK_W;
	default:
	case TK_ANCHOR_CENTER:	return 0;
    }
}

/*
 * Ttk_AnchorBox --
 * 	Place a box of size w * h in the specified parcel,
 * 	according to the specified anchor.
 */
Ttk_Box Ttk_AnchorBox(Ttk_Box parcel, int width, int height, Tk_Anchor anchor)
{
    return Ttk_StickBox(parcel, width, height, AnchorToSticky(anchor));
}

/*
 * Ttk_PlaceBox --
 * 	Combine Ttk_PackBox() and Ttk_StickBox().
 */
Ttk_Box Ttk_PlaceBox(
    Ttk_Box *cavity, int width, int height, Ttk_Side side, unsigned sticky)
{
    return Ttk_StickBox(
	    Ttk_PackBox(cavity, width, height, side), width, height, sticky);
}

/*
 * Ttk_PositionBox --
 * 	Pack and stick a box according to PositionSpec flags.
 */
MODULE_SCOPE Ttk_Box
Ttk_PositionBox(Ttk_Box *cavity, int width, int height, Ttk_PositionSpec flags)
{
    Ttk_Box parcel;

	 if (flags & TTK_EXPAND)	parcel = *cavity;
    else if (flags & TTK_PACK_TOP)	parcel = packTop(cavity, height);
    else if (flags & TTK_PACK_LEFT)	parcel = packLeft(cavity, width);
    else if (flags & TTK_PACK_BOTTOM)	parcel = packBottom(cavity, height);
    else if (flags & TTK_PACK_RIGHT)	parcel = packRight(cavity, width);
    else				parcel = *cavity;

    return Ttk_StickBox(parcel, width, height, flags);
}

/*
 * TTKInitPadding --
 * 	Common factor of Ttk_GetPaddingFromObj and Ttk_GetBorderFromObj.
 * 	Initializes Ttk_Padding record, supplying default values
 * 	for missing entries.
 */
static void TTKInitPadding(int padc, int pixels[4], Ttk_Padding *pad)
{
    switch (padc)
    {
	case 0: pixels[0] = 0; /*FALLTHRU*/
	case 1:	pixels[1] = pixels[0]; /*FALLTHRU*/
	case 2:	pixels[2] = pixels[0]; /*FALLTHRU*/
	case 3:	pixels[3] = pixels[1]; /*FALLTHRU*/
    }

    pad->left	= (short)pixels[0];
    pad->top	= (short)pixels[1];
    pad->right	= (short)pixels[2];
    pad->bottom	= (short)pixels[3];
}

/*
 * Ttk_GetPaddingFromObj --
 *
 * 	Extract a padding specification from a Tcl_Obj * scaled
 * 	to work with a particular Tk_Window.
 *
 * 	The string representation of a Ttk_Padding is a list
 * 	of one to four Tk_Pixel specifications, corresponding
 * 	to the left, top, right, and bottom padding.
 *
 * 	If the 'bottom' (fourth) element is missing, it defaults to 'top'.
 * 	If the 'right' (third) element is missing, it defaults to 'left'.
 * 	If the 'top' (second) element is missing, it defaults to 'left'.
 *
 * 	The internal representation is a Tcl_ListObj containing
 * 	one to four Tk_PixelObj objects.
 *
 * Returns:
 * 	TCL_OK or TCL_ERROR.  In the latter case an error message is
 * 	left in 'interp' and '*paddingPtr' is set to all-zeros.
 * 	Otherwise, *paddingPtr is filled in with the padding specification.
 *
 */
int Ttk_GetPaddingFromObj(
    Tcl_Interp *interp,
    Tk_Window tkwin,
    Tcl_Obj *objPtr,
    Ttk_Padding *pad)
{
    Tcl_Obj **padv;
    int i, padc, pixels[4];

    if (TCL_OK != Tcl_ListObjGetElements(interp, objPtr, &padc, &padv)) {
	goto error;
    }

    if (padc > 4) {
	if (interp) {
	    Tcl_SetObjResult(interp, Tcl_NewStringObj(
		    "Wrong #elements in padding spec", -1));
	    Tcl_SetErrorCode(interp, "TTK", "VALUE", "PADDING", NULL);
	}
	goto error;
    }

    for (i=0; i < padc; ++i) {
	if (Tk_GetPixelsFromObj(interp, tkwin, padv[i], &pixels[i]) != TCL_OK) {
	    goto error;
	}
    }

    TTKInitPadding(padc, pixels, pad);
    return TCL_OK;

error:
    pad->left = pad->top = pad->right = pad->bottom = 0;
    return TCL_ERROR;
}

/* Ttk_GetBorderFromObj --
 * 	Same as Ttk_GetPaddingFromObj, except padding is a list of integers
 * 	instead of Tk_Pixel specifications.  Does not require a Tk_Window
 * 	parameter.
 *
 */
int Ttk_GetBorderFromObj(Tcl_Interp *interp, Tcl_Obj *objPtr, Ttk_Padding *pad)
{
    Tcl_Obj **padv;
    int i, padc, pixels[4];

    if (TCL_OK != Tcl_ListObjGetElements(interp, objPtr, &padc, &padv)) {
	goto error;
    }

    if (padc > 4) {
	if (interp) {
	    Tcl_SetObjResult(interp, Tcl_NewStringObj(
		    "Wrong #elements in padding spec", -1));
	    Tcl_SetErrorCode(interp, "TTK", "VALUE", "BORDER", NULL);
	}
	goto error;
    }

    for (i=0; i < padc; ++i) {
	if (Tcl_GetIntFromObj(interp, padv[i], &pixels[i]) != TCL_OK) {
	    goto error;
	}
    }

    TTKInitPadding(padc, pixels, pad);
    return TCL_OK;

error:
    pad->left = pad->top = pad->right = pad->bottom = 0;
    return TCL_ERROR;
}

/*
 * Ttk_MakePadding --
 *	Return an initialized Ttk_Padding structure.
 */
Ttk_Padding Ttk_MakePadding(short left, short top, short right, short bottom)
{
    Ttk_Padding pad;
    pad.left = left;
    pad.top = top;
    pad.right = right;
    pad.bottom = bottom;
    return pad;
}

/*
 * Ttk_UniformPadding --
 * 	Returns a uniform Ttk_Padding structure, with the same
 * 	border width on all sides.
 */
Ttk_Padding Ttk_UniformPadding(short borderWidth)
{
    Ttk_Padding pad;
    pad.left = pad.top = pad.right = pad.bottom = borderWidth;
    return pad;
}

/*
 * Ttk_AddPadding --
 *	Combine two padding records.
 */
Ttk_Padding Ttk_AddPadding(Ttk_Padding p1, Ttk_Padding p2)
{
    p1.left += p2.left;
    p1.top += p2.top;
    p1.right += p2.right;
    p1.bottom += p2.bottom;
    return p1;
}

/* Ttk_RelievePadding --
 * 	Add an extra n pixels of padding according to specified relief.
 * 	This may be used in element geometry procedures to simulate
 * 	a "pressed-in" look for pushbuttons.
 */
Ttk_Padding Ttk_RelievePadding(Ttk_Padding padding, int relief, int n)
{
    switch (relief)
    {
	case TK_RELIEF_RAISED:
	    padding.right += n;
	    padding.bottom += n;
	    break;
	case TK_RELIEF_SUNKEN:	/* shift */
	    padding.left += n;
	    padding.top += n;
	    break;
	default:
	{
	    int h1 = n/2, h2 = h1 + n % 2;
	    padding.left += h1;
	    padding.top += h1;
	    padding.right += h2;
	    padding.bottom += h2;
	    break;
	}
    }
    return padding;
}

/*
 * Ttk_GetStickyFromObj --
 * 	Returns a stickiness specification from the specified Tcl_Obj*,
 * 	consisting of any combination of n, s, e, and w.
 *
 * Returns: TCL_OK if objPtr holds a valid stickiness specification,
 *	otherwise TCL_ERROR.  interp is used for error reporting if non-NULL.
 *
 */
int Ttk_GetStickyFromObj(
    Tcl_Interp *interp, Tcl_Obj *objPtr, Ttk_Sticky *result)
{
    const char *string = Tcl_GetString(objPtr);
    Ttk_Sticky sticky = 0;
    char c;

    while ((c = *string++) != '\0') {
	switch (c) {
	    case 'w': case 'W': sticky |= TTK_STICK_W; break;
	    case 'e': case 'E': sticky |= TTK_STICK_E; break;
	    case 'n': case 'N': sticky |= TTK_STICK_N; break;
	    case 's': case 'S': sticky |= TTK_STICK_S; break;
	    default:
	    	if (interp) {
		    Tcl_SetObjResult(interp, Tcl_ObjPrintf(
			"Bad -sticky specification %s",
			Tcl_GetString(objPtr)));
		    Tcl_SetErrorCode(interp, "TTK", "VALUE", "STICKY", NULL);
		}
		return TCL_ERROR;
	}
    }

    *result = sticky;
    return TCL_OK;
}

/* Ttk_NewStickyObj --
 * 	Construct a new Tcl_Obj * containing a stickiness specification.
 */
Tcl_Obj *Ttk_NewStickyObj(Ttk_Sticky sticky)
{
    char buf[5];
    char *p = buf;

    if (sticky & TTK_STICK_N)	*p++ = 'n';
    if (sticky & TTK_STICK_S)	*p++ = 's';
    if (sticky & TTK_STICK_W)	*p++ = 'w';
    if (sticky & TTK_STICK_E)	*p++ = 'e';

    *p = '\0';
    return Tcl_NewStringObj(buf, p - buf);
}

/*------------------------------------------------------------------------
 * +++ Layout nodes.
 */

typedef struct Ttk_LayoutNode_ Ttk_LayoutNode;
struct Ttk_LayoutNode_
{
    unsigned		flags;		/* Packing and sticky flags */
    Ttk_ElementClass 	*eclass;	/* Class record */
    Ttk_State 	 	state;		/* Current state */
    Ttk_Box 		parcel;		/* allocated parcel */
    Ttk_LayoutNode	*next, *child;
};

static Ttk_LayoutNode *Ttk_NewLayoutNode(
    unsigned flags, Ttk_ElementClass *elementClass)
{
    Ttk_LayoutNode *node = ckalloc(sizeof(*node));

    node->flags = flags;
    node->eclass = elementClass;
    node->state = 0u;
    node->next = node->child = 0;
    node->parcel = Ttk_MakeBox(0,0,0,0);

    return node;
}

static void Ttk_FreeLayoutNode(Ttk_LayoutNode *node)
{
    while (node) {
	Ttk_LayoutNode *next = node->next;
	Ttk_FreeLayoutNode(node->child);
	ckfree(node);
	node = next;
    }
}

/*------------------------------------------------------------------------
 * +++ Layout templates.
 */

struct Ttk_TemplateNode_ {
    char *name;
    unsigned flags;
    struct Ttk_TemplateNode_ *next, *child;
};

static Ttk_TemplateNode *Ttk_NewTemplateNode(const char *name, unsigned flags)
{
    Ttk_TemplateNode *op = ckalloc(sizeof(*op));
    op->name = ckalloc(strlen(name) + 1); strcpy(op->name, name);
    op->flags = flags;
    op->next = op->child = 0;
    return op;
}

void Ttk_FreeLayoutTemplate(Ttk_LayoutTemplate op)
{
    while (op) {
	Ttk_LayoutTemplate next = op->next;
	Ttk_FreeLayoutTemplate(op->child);
	ckfree(op->name);
	ckfree(op);
	op = next;
    }
}

/* InstantiateLayout --
 *	Create a layout tree from a template.
 */
static Ttk_LayoutNode *
Ttk_InstantiateLayout(Ttk_Theme theme, Ttk_TemplateNode *op)
{
    Ttk_ElementClass *elementClass = Ttk_GetElement(theme, op->name);
    Ttk_LayoutNode *node = Ttk_NewLayoutNode(op->flags, elementClass);

    if (op->next) {
	node->next = Ttk_InstantiateLayout(theme,op->next);
    }
    if (op->child) {
	node->child = Ttk_InstantiateLayout(theme,op->child);
    }

    return node;
}

/*
 * Ttk_ParseLayoutTemplate --
 *	Convert a Tcl list into a layout template.
 *
 * Syntax:
 * 	layoutSpec ::= { elementName ?-option value ...? }+
 */

/* NB: This must match bit definitions TTK_PACK_LEFT etc. */
static const char *packSideStrings[] =
    { "left", "right", "top", "bottom", NULL };

Ttk_LayoutTemplate Ttk_ParseLayoutTemplate(Tcl_Interp *interp, Tcl_Obj *objPtr)
{
    enum {  OP_SIDE, OP_STICKY, OP_EXPAND, OP_BORDER, OP_UNIT, OP_CHILDREN };
    static const char *optStrings[] = {
	"-side", "-sticky", "-expand", "-border", "-unit", "-children", 0 };

    int i = 0, objc;
    Tcl_Obj **objv;
    Ttk_TemplateNode *head = 0, *tail = 0;

    if (Tcl_ListObjGetElements(interp, objPtr, &objc, &objv) != TCL_OK)
	return 0;

    while (i < objc) {
	const char *elementName = Tcl_GetString(objv[i]);
	unsigned flags = 0x0, sticky = TTK_FILL_BOTH;
	Tcl_Obj *childSpec = 0;

	/*
	 * Parse options:
	 */
	++i;
	while (i < objc) {
	    const char *optName = Tcl_GetString(objv[i]);
	    int option, value;

	    if (optName[0] != '-')
		break;

	    if (Tcl_GetIndexFromObjStruct(interp, objv[i], optStrings,
		    sizeof(char *), "option", 0, &option)
		!= TCL_OK)
	    {
		goto error;
	    }

	    if (++i >= objc) {
		Tcl_SetObjResult(interp, Tcl_ObjPrintf(
			"Missing value for option %s",
			Tcl_GetString(objv[i-1])));
		Tcl_SetErrorCode(interp, "TTK", "VALUE", "LAYOUT", NULL);
		goto error;
	    }

	    switch (option) {
		case OP_SIDE:	/* <<NOTE-PACKSIDE>> */
		    if (Tcl_GetIndexFromObjStruct(interp, objv[i], packSideStrings,
				sizeof(char *), "side", 0, &value) != TCL_OK)
		    {
			goto error;
		    }
		    flags |= (TTK_PACK_LEFT << value);

		    break;
		case OP_STICKY:
		    if (Ttk_GetStickyFromObj(interp,objv[i],&sticky) != TCL_OK)
			goto error;
		    break;
		case OP_EXPAND:
		    if (Tcl_GetBooleanFromObj(interp,objv[i],&value) != TCL_OK)
			goto error;
		    if (value)
			flags |= TTK_EXPAND;
		    break;
		case OP_BORDER:
		    if (Tcl_GetBooleanFromObj(interp,objv[i],&value) != TCL_OK)
			goto error;
		    if (value)
			flags |= TTK_BORDER;
		    break;
		case OP_UNIT:
		    if (Tcl_GetBooleanFromObj(interp,objv[i],&value) != TCL_OK)
			goto error;
		    if (value)
			flags |= TTK_UNIT;
		    break;
		case OP_CHILDREN:
		    childSpec = objv[i];
		    break;
	    }
	    ++i;
	}

	/*
	 * Build new node:
	 */
	if (tail) {
	    tail->next = Ttk_NewTemplateNode(elementName, flags | sticky);
	    tail = tail->next;
	} else {
	    head = tail = Ttk_NewTemplateNode(elementName, flags | sticky);
	}
	if (childSpec) {
	    tail->child = Ttk_ParseLayoutTemplate(interp, childSpec);
	    if (!tail->child) {
                Tcl_SetObjResult(interp, Tcl_ObjPrintf("Invalid -children value"));
                Tcl_SetErrorCode(interp, "TTK", "VALUE", "CHILDREN", NULL);
		goto error;
	    }
	}
    }

    return head;

error:
    Ttk_FreeLayoutTemplate(head);
    return 0;
}

/* Ttk_BuildLayoutTemplate --
 * 	Build a layout template tree from a statically defined
 * 	Ttk_LayoutSpec array.
 */
Ttk_LayoutTemplate Ttk_BuildLayoutTemplate(Ttk_LayoutSpec spec)
{
    Ttk_TemplateNode *first = 0, *last = 0;

    for ( ; !(spec->opcode & _TTK_LAYOUT_END) ; ++spec) {
	if (spec->elementName) {
	    Ttk_TemplateNode *node =
		Ttk_NewTemplateNode(spec->elementName, spec->opcode);

	    if (last) {
		last->next = node;
	    } else {
		first = node;
	    }
	    last = node;
	}

	if (spec->opcode & _TTK_CHILDREN && last) {
	    int depth = 1;
	    last->child = Ttk_BuildLayoutTemplate(spec+1);

	    /* Skip to end of group:
	     */
	    while (depth) {
		++spec;
		if (spec->opcode & _TTK_CHILDREN) {
		    ++depth;
		}
		if (spec->opcode & _TTK_LAYOUT_END) {
		    --depth;
		}
	    }
	}

    } /* for */

    return first;
}

void Ttk_RegisterLayouts(Ttk_Theme theme, Ttk_LayoutSpec spec)
{
    while (!(spec->opcode & _TTK_LAYOUT_END)) {
	Ttk_LayoutTemplate layoutTemplate = Ttk_BuildLayoutTemplate(spec+1);
	Ttk_RegisterLayoutTemplate(theme, spec->elementName, layoutTemplate);
	do {
	    ++spec;
	} while (!(spec->opcode & _TTK_LAYOUT));
    }
}

Tcl_Obj *Ttk_UnparseLayoutTemplate(Ttk_TemplateNode *node)
{
    Tcl_Obj *result = Tcl_NewListObj(0,0);

#   define APPENDOBJ(obj) Tcl_ListObjAppendElement(NULL, result, obj)
#   define APPENDSTR(str) APPENDOBJ(Tcl_NewStringObj(str,-1))

    while (node) {
	unsigned flags = node->flags;

	APPENDSTR(node->name);

	/* Back-compute -side.  <<NOTE-PACKSIDE>>
	 * @@@ NOTES: Ick.
	 */
	if (flags & TTK_EXPAND) {
	    APPENDSTR("-expand");
	    APPENDSTR("1");
	} else {
	    if (flags & _TTK_MASK_PACK) {
		int side = 0;
		unsigned sideFlags = flags & _TTK_MASK_PACK;

		while (!(sideFlags & TTK_PACK_LEFT)) {
		    ++side;
		    sideFlags >>= 1;
		}
		APPENDSTR("-side");
		APPENDSTR(packSideStrings[side]);
	    }
	}

	/*
	 * In Ttk_ParseLayoutTemplate, default -sticky is "nsew", so always
	 * include this even if no sticky bits are set.
	 */

	APPENDSTR("-sticky");
	APPENDOBJ(Ttk_NewStickyObj(flags & _TTK_MASK_STICK));

	/* @@@ Check again: are these necessary? Can't see any effect! */
	if (flags & TTK_BORDER)	{ APPENDSTR("-border"); APPENDSTR("1"); }
	if (flags & TTK_UNIT) 	{ APPENDSTR("-unit"); APPENDSTR("1"); }

	if (node->child) {
	    APPENDSTR("-children");
	    APPENDOBJ(Ttk_UnparseLayoutTemplate(node->child));
	}
	node = node->next;
    }

#   undef APPENDOBJ
#   undef APPENDSTR

    return result;
}

/*------------------------------------------------------------------------
 * +++ Layouts.
 */
struct Ttk_Layout_
{
    Ttk_Style	 	style;
    void 		*recordPtr;
    Tk_OptionTable	optionTable;
    Tk_Window		tkwin;
    Ttk_LayoutNode	*root;
};

static Ttk_Layout TTKNewLayout(
    Ttk_Style style,
    void *recordPtr,Tk_OptionTable optionTable, Tk_Window tkwin,
    Ttk_LayoutNode *root)
{
    Ttk_Layout layout = ckalloc(sizeof(*layout));
    layout->style = style;
    layout->recordPtr = recordPtr;
    layout->optionTable = optionTable;
    layout->tkwin = tkwin;
    layout->root = root;
    return layout;
}

void Ttk_FreeLayout(Ttk_Layout layout)
{
    Ttk_FreeLayoutNode(layout->root);
    ckfree(layout);
}

/*
 * Ttk_CreateLayout --
 *	Create a layout from the specified theme and style name.
 *	Returns: New layout, 0 on error.
 *	Leaves an error message in interp's result if there is an error.
 */
Ttk_Layout Ttk_CreateLayout(
    Tcl_Interp *interp,		/* where to leave error messages */
    Ttk_Theme themePtr,
    const char *styleName,
    void *recordPtr,
    Tk_OptionTable optionTable,
    Tk_Window tkwin)
{
    Ttk_Style style = Ttk_GetStyle(themePtr, styleName);
    Ttk_LayoutTemplate layoutTemplate =
	Ttk_FindLayoutTemplate(themePtr,styleName);
    Ttk_ElementClass *bgelement = Ttk_GetElement(themePtr, "background");
    Ttk_LayoutNode *bgnode;

    if (!layoutTemplate) {
	Tcl_SetObjResult(interp, Tcl_ObjPrintf(
		"Layout %s not found", styleName));
	Tcl_SetErrorCode(interp, "TTK", "LOOKUP", "LAYOUT", styleName, NULL);
	return 0;
    }

    bgnode = Ttk_NewLayoutNode(TTK_FILL_BOTH, bgelement);
    bgnode->next = Ttk_InstantiateLayout(themePtr, layoutTemplate);

    return TTKNewLayout(style, recordPtr, optionTable, tkwin, bgnode);
}

/* Ttk_CreateSublayout --
 * 	Creates a new sublayout.
 *
 * 	Sublayouts are used to draw subparts of a compound widget.
 *	They use the same Tk_Window, but a different option table
 *	and data record.
 */
Ttk_Layout
Ttk_CreateSublayout(
    Tcl_Interp *interp,
    Ttk_Theme themePtr,
    Ttk_Layout parentLayout,
    const char *baseName,
    Tk_OptionTable optionTable)
{
    Tcl_DString buf;
    const char *styleName;
    Ttk_Style style;
    Ttk_LayoutTemplate layoutTemplate;

    Tcl_DStringInit(&buf);
    Tcl_DStringAppend(&buf, Ttk_StyleName(parentLayout->style), -1);
    Tcl_DStringAppend(&buf, baseName, -1);
    styleName = Tcl_DStringValue(&buf);

    style = Ttk_GetStyle(themePtr, styleName);
    layoutTemplate = Ttk_FindLayoutTemplate(themePtr, styleName);

    if (!layoutTemplate) {
	Tcl_SetObjResult(interp, Tcl_ObjPrintf(
		"Layout %s not found", styleName));
	Tcl_SetErrorCode(interp, "TTK", "LOOKUP", "LAYOUT", styleName, NULL);
	return 0;
    }

    Tcl_DStringFree(&buf);

    return TTKNewLayout(
	    style, 0, optionTable, parentLayout->tkwin,
	    Ttk_InstantiateLayout(themePtr, layoutTemplate));
}

/* Ttk_RebindSublayout --
 * 	Bind sublayout to new data source.
 */
void Ttk_RebindSublayout(Ttk_Layout layout, void *recordPtr)
{
    layout->recordPtr = recordPtr;
}

/*
 * Ttk_QueryOption --
 * 	Look up an option from a layout's associated option.
 */
Tcl_Obj *Ttk_QueryOption(
    Ttk_Layout layout, const char *optionName, Ttk_State state)
{
    return Ttk_QueryStyle(
	layout->style,layout->recordPtr,layout->optionTable,optionName,state);
}

/*
 * Ttk_LayoutStyle --
 * 	Extract Ttk_Style from Ttk_Layout.
 */
Ttk_Style Ttk_LayoutStyle(Ttk_Layout layout)
{
    return layout->style;
}

/*------------------------------------------------------------------------
 * +++ Size computation.
 */
static void Ttk_NodeListSize(
    Ttk_Layout layout, Ttk_LayoutNode *node,
    Ttk_State state, int *widthPtr, int *heightPtr); /* Forward */

static void Ttk_NodeSize(
    Ttk_Layout layout, Ttk_LayoutNode *node, Ttk_State state,
    int *widthPtr, int *heightPtr, Ttk_Padding *paddingPtr)
{
    int elementWidth, elementHeight, subWidth, subHeight;
    Ttk_Padding elementPadding;

    Ttk_ElementSize(node->eclass,
	layout->style, layout->recordPtr,layout->optionTable, layout->tkwin,
	state|node->state,
	&elementWidth, &elementHeight, &elementPadding);

    Ttk_NodeListSize(layout,node->child,state,&subWidth,&subHeight);
    subWidth += Ttk_PaddingWidth(elementPadding);
    subHeight += Ttk_PaddingHeight(elementPadding);

    *widthPtr = MAX(elementWidth, subWidth);
    *heightPtr = MAX(elementHeight, subHeight);
    *paddingPtr = elementPadding;
}

static void Ttk_NodeListSize(
    Ttk_Layout layout, Ttk_LayoutNode *node,
    Ttk_State state, int *widthPtr, int *heightPtr)
{
    if (!node) {
	*widthPtr = *heightPtr = 0;
    } else {
	int width, height, restWidth, restHeight;
	Ttk_Padding unused;

	Ttk_NodeSize(layout, node, state, &width, &height, &unused);
	Ttk_NodeListSize(layout, node->next, state, &restWidth, &restHeight);

	if (node->flags & (TTK_PACK_LEFT|TTK_PACK_RIGHT)) {
	    *widthPtr = width + restWidth;
	} else {
	    *widthPtr = MAX(width, restWidth);
	}

	if (node->flags & (TTK_PACK_TOP|TTK_PACK_BOTTOM)) {
	    *heightPtr = height + restHeight;
	} else {
	    *heightPtr = MAX(height, restHeight);
	}
    }
}

/*
 * Ttk_LayoutNodeInternalPadding --
 * 	Returns the internal padding of a layout node.
 */
Ttk_Padding Ttk_LayoutNodeInternalPadding(
    Ttk_Layout layout, Ttk_LayoutNode *node)
{
    int unused;
    Ttk_Padding padding;
    Ttk_ElementSize(node->eclass,
	layout->style, layout->recordPtr, layout->optionTable, layout->tkwin,
	0/*state*/, &unused, &unused, &padding);
    return padding;
}

/*
 * Ttk_LayoutNodeInternalParcel --
 * 	Returns the inner area of a specified layout node,
 * 	based on current parcel and element's internal padding.
 */
Ttk_Box Ttk_LayoutNodeInternalParcel(Ttk_Layout layout, Ttk_LayoutNode *node)
{
    Ttk_Padding padding = Ttk_LayoutNodeInternalPadding(layout, node);
    return Ttk_PadBox(node->parcel, padding);
}

/* Ttk_LayoutSize --
 * 	Compute requested size of a layout.
 */
void Ttk_LayoutSize(
    Ttk_Layout layout, Ttk_State state, int *widthPtr, int *heightPtr)
{
    Ttk_NodeListSize(layout, layout->root, state, widthPtr, heightPtr);
}

void Ttk_LayoutNodeReqSize(	/* @@@ Rename this */
    Ttk_Layout layout, Ttk_LayoutNode *node, int *widthPtr, int *heightPtr)
{
    Ttk_Padding unused;
    Ttk_NodeSize(layout, node, 0/*state*/, widthPtr, heightPtr, &unused);
}

/*------------------------------------------------------------------------
 * +++ Layout placement.
 */

/* Ttk_PlaceNodeList --
 *	Compute parcel for each node in a layout tree
 *	according to position specification and overall size.
 */
static void Ttk_PlaceNodeList(
    Ttk_Layout layout, Ttk_LayoutNode *node, Ttk_State state, Ttk_Box cavity)
{
    for (; node; node = node->next)
    {
	int width, height;
	Ttk_Padding padding;

	/* Compute node size: (@@@ cache this instead?)
	 */
	Ttk_NodeSize(layout, node, state, &width, &height, &padding);

	/* Compute parcel:
	 */
	node->parcel = Ttk_PositionBox(&cavity, width, height, node->flags);

	/* Place child nodes:
	 */
	if (node->child) {
	    Ttk_Box childBox = Ttk_PadBox(node->parcel, padding);
	    Ttk_PlaceNodeList(layout,node->child, state, childBox);
	}
    }
}

void Ttk_PlaceLayout(Ttk_Layout layout, Ttk_State state, Ttk_Box b)
{
    Ttk_PlaceNodeList(layout, layout->root, state,  b);
}

/*------------------------------------------------------------------------
 * +++ Layout drawing.
 */

/*
 * Ttk_DrawLayout --
 * 	Draw a layout tree.
 */
static void Ttk_DrawNodeList(
    Ttk_Layout layout, Ttk_State state, Ttk_LayoutNode *node, Drawable d)
{
    for (; node; node = node->next)
    {
	int border = node->flags & TTK_BORDER;
	int substate = state;

	if (node->flags & TTK_UNIT)
	    substate |= node->state;

	if (node->child && border)
	    Ttk_DrawNodeList(layout, substate, node->child, d);

	Ttk_DrawElement(
	    node->eclass,
	    layout->style,layout->recordPtr,layout->optionTable,layout->tkwin,
	    d, node->parcel, state | node->state);

	if (node->child && !border)
	    Ttk_DrawNodeList(layout, substate, node->child, d);
    }
}

void Ttk_DrawLayout(Ttk_Layout layout, Ttk_State state, Drawable d)
{
    Ttk_DrawNodeList(layout, state, layout->root, d);
}

/*------------------------------------------------------------------------
 * +++ Inquiry and modification.
 */

/*
 * Ttk_IdentifyElement --
 * 	Find the element at the specified x,y coordinate.
 */
static Ttk_Element IdentifyNode(Ttk_Element node, int x, int y)
{
    Ttk_Element closest = NULL;

    for (; node; node = node->next) {
	if (Ttk_BoxContains(node->parcel, x, y)) {
	    closest = node;
	    if (node->child && !(node->flags & TTK_UNIT)) {
		Ttk_Element childNode = IdentifyNode(node->child, x,y);
		if (childNode) {
		    closest = childNode;
		}
	    }
	}
    }
    return closest;
}

Ttk_Element Ttk_IdentifyElement(Ttk_Layout layout, int x, int y)
{
    return IdentifyNode(layout->root, x, y);
}

/*
 * tail --
 * 	Return the last component of an element name, e.g.,
 * 	"Scrollbar.thumb" => "thumb"
 */
static const char *tail(const char *elementName)
{
    const char *dot;
    while ((dot=strchr(elementName,'.')) != NULL)
	elementName = dot + 1;
    return elementName;
}

/*
 * Ttk_FindElement --
 * 	Look up an element by name
 */
static Ttk_Element
FindNode(Ttk_Element node, const char *nodeName)
{
    for (; node ; node = node->next) {
	if (!strcmp(tail(Ttk_ElementName(node)), nodeName))
	    return node;

	if (node->child) {
	    Ttk_Element childNode = FindNode(node->child, nodeName);
	    if (childNode)
		return childNode;
	}
    }
    return 0;
}

Ttk_Element Ttk_FindElement(Ttk_Layout layout, const char *nodeName)
{
    return FindNode(layout->root, nodeName);
}

/*
 * Ttk_ClientRegion --
 * 	Find the internal parcel of a named element within a given layout.
 * 	If the element is not present, use the entire window.
 */
Ttk_Box Ttk_ClientRegion(Ttk_Layout layout, const char *elementName)
{
    Ttk_Element element = Ttk_FindElement(layout, elementName);
    return element
	? Ttk_LayoutNodeInternalParcel(layout, element)
	: Ttk_WinBox(layout->tkwin)
	;
}

/*
 * Ttk_ElementName --
 * 	Return the name (class name) of the element.
 */
const char *Ttk_ElementName(Ttk_Element node)
{
    return Ttk_ElementClassName(node->eclass);
}

/*
 * Ttk_ElementParcel --
 * 	Return the element's current parcel.
 */
Ttk_Box Ttk_ElementParcel(Ttk_Element node)
{
    return node->parcel;
}

/*
 * Ttk_PlaceElement --
 * 	Explicitly specify an element's parcel.
 */
void Ttk_PlaceElement(Ttk_Layout layout, Ttk_Element node, Ttk_Box b)
{
    node->parcel = b;
    if (node->child) {
	Ttk_PlaceNodeList(layout, node->child, 0,
	    Ttk_PadBox(b, Ttk_LayoutNodeInternalPadding(layout, node)));
    }
}

/*
 * Ttk_ChangeElementState --
 */
void Ttk_ChangeElementState(Ttk_LayoutNode *node,unsigned set,unsigned clr)
{
    node->state = (node->state | set) & ~clr;
}

/*EOF*/
