/* $Id: paned.c,v 1.12 2005/10/19 20:30:49 jenglish Exp $
 *
 * Copyright (c) 2005, Joe English.  Freely redistributable.
 *
 * Tile Widget Set: ttk::paned widget.
 *
 * TODO: track active/pressed sash.
 */

#include <string.h>
#include <tk.h>
#include "manager.h"
#include "tkTheme.h"
#include "widget.h"

#define MIN_SASH_THICKNESS 5

/*------------------------------------------------------------------------
 * +++ Layout algorithm.
 *
 * (pos=x/y, size=width/height, depending on -orient=horizontal/vertical)
 *
 * Each pane carries two pieces of state: the request size and the
 * position of the following sash.  (The final pane has no sash,
 * its sash position is used as a sentinel value).
 *
 * Pane geometry is determined by the sash positions.
 * When resizing, sash positions are computed from the request sizes,
 * the available space, and pane weights (see ComputePositions()).
 * This ensures continuous resize behavior (that is: changing
 * the size by X pixels then changing the size by Y pixels
 * gives the same result as changing the size by X+Y pixels
 * in one step).
 *
 * The request size is initially set to the slave window's requested size.
 * When the user drags a sash, each pane's request size is set to its
 * actual size.  This ensures that panes "stay put" on the next resize.
 *
 * If reqSize == 0, use 0 for the weight as well.  This ensures that
 * "collapsed" panes stay collapsed during a resize, regardless of
 * their nominal -weight.
 *
 * +++ Invariants.
 *
 * #sash 		=  #pane - 1
 * pos(pane[0]) 	=  0
 * pos(sash[i]) 	=  pos(pane[i]) + size(pane[i]), 0 <= i <= #sash
 * pos(pane[i+1]) 	=  pos(sash[i]) + size(sash[i]), 0 <= i <  #sash
 * pos(sash[#sash])	=  size(pw)   // sentinel value, constraint
 *
 * size(pw) 		=  sum(size(pane(0..#pane))) + sum(size(sash(0..#sash)))
 * size(pane[i]) 	>= 0,  for 0 <= i < #pane
 * size(sash[i]) 	>= 0,  for 0 <= i < #sash
 * ==> pos(pane[i]) <= pos(sash[i]) <= pos(pane[i+1]), for 0 <= i < #sash
 *
 * Assumption: all sashes are the same size.
 */

/*------------------------------------------------------------------------
 * +++ Widget record.
 */

typedef struct {
    Tcl_Obj 	*orientObj;
    int 	orient;
    Ttk_Manager	*mgr;
    Ttk_Layout	sashLayout;
    int 	sashThickness;
} PanedPart;

typedef struct {
    WidgetCore	core;
    PanedPart	paned;
} Paned;

/* @@@ NOTE: -orient is readonly 'cause dynamic oriention changes NYI
 */
static Tk_OptionSpec PanedOptionSpecs[] = {
    {TK_OPTION_STRING_TABLE, "-orient", "orient", "Orient", "vertical",
	Tk_Offset(Paned,paned.orientObj), Tk_Offset(Paned,paned.orient),
	0,(ClientData)TTKOrientStrings,READONLY_OPTION|STYLE_CHANGED },

    WIDGET_INHERIT_OPTIONS(CoreOptionSpecs)
};

/*------------------------------------------------------------------------
 * +++ Slave pane record.
 */
typedef struct {
    int 	reqSize;		/* Pane request size */
    int 	sashPos;		/* Folowing sash position */
    int 	weight; 		/* Pane -weight, for resizing */
} Pane;

static Tk_OptionSpec PaneOptionSpecs[] = {
    {TK_OPTION_INT, "-weight", "weight", "Weight", "0",
	-1,Tk_Offset(Pane,weight), 0,0,GEOMETRY_CHANGED },
    {TK_OPTION_END, 0,0,0, NULL, -1,-1, 0,0,0}
};

/*------------------------------------------------------------------------
 * +++ Layout algorithm.
 */

/* ShoveUp --
 * 	Place sash i at specified position, recursively shoving
 * 	previous sashes upwards as needed, until hitting the top
 * 	of the window.  If that happens, shove back down.
 *
 * 	Returns: final position of sash i.
 */

static int ShoveUp(Paned *pw, int i, int pos)
{
    Pane *pane = Ttk_SlaveData(pw->paned.mgr, i);
    int sashThickness = pw->paned.sashThickness;

    if (i == 0) {
	if (pos < 0)
	    pos = 0;
    } else {
	Pane *prevPane = Ttk_SlaveData(pw->paned.mgr, i-1);
	if (pos < prevPane->sashPos + sashThickness)
	    pos = ShoveUp(pw, i-1, pos - sashThickness) + sashThickness;
    }
    return pane->sashPos = pos;
}

/* ShoveDown --
 * 	Same as ShoveUp, but going in the opposite direction
 * 	and stopping at the sentinel sash.
 */
static int ShoveDown(Paned *pw, int i, int pos)
{
    Pane *pane = Ttk_SlaveData(pw->paned.mgr,i);
    int sashThickness = pw->paned.sashThickness;

    if (i == pw->paned.mgr->nSlaves - 1) {
	pos = pane->sashPos; /* Sentinel value == master window size */
    } else {
	Pane *nextPane = Ttk_SlaveData(pw->paned.mgr,i+1);
	if (pos + sashThickness > nextPane->sashPos)
	    pos = ShoveDown(pw, i+1, pos + sashThickness) - sashThickness;
    }
    return pane->sashPos = pos;
}

/* PanedSize --
 * 	Compute the requested size of the paned widget.
 * 	Used as both the WidgetSpec sizeProc and the ManagerSpec sizeProc.
 */
static int PanedSize(void *recordPtr, int *widthPtr, int *heightPtr)
{
    Paned *pw = recordPtr;
    int nPanes = Ttk_NumberSlaves(pw->paned.mgr);
    int nSashes = nPanes - 1;
    int sashThickness = pw->paned.sashThickness;
    int width = 0, height = 0;
    int index;

    if (pw->paned.orient == TTK_ORIENT_HORIZONTAL) {
	for (index = 0; index < nPanes; ++index) {
	    Pane *pane = Ttk_SlaveData(pw->paned.mgr, index);
	    Tk_Window slaveWindow = Ttk_SlaveWindow(pw->paned.mgr, index);

	    if (height < Tk_ReqHeight(slaveWindow))
		height = Tk_ReqHeight(slaveWindow);
	    width += pane->reqSize;
	}
	width += nSashes * sashThickness;
    } else {
	for (index = 0; index < nPanes; ++index) {
	    Pane *pane = Ttk_SlaveData(pw->paned.mgr, index);
	    Tk_Window slaveWindow = Ttk_SlaveWindow(pw->paned.mgr, index);

	    if (width < Tk_ReqWidth(slaveWindow))
		width = Tk_ReqWidth(slaveWindow);
	    height += pane->reqSize;
	}
	height += nSashes * sashThickness;
    }

    *widthPtr = width;
    *heightPtr = height;
    return 1;
}

/* AdjustPanes --
 * 	Set pane request sizes from sash positions.
 *
 * NOTE:
 * 	AdjustPanes followed by ComputePositions (called during relayout)
 * 	will leave the sashes in the same place, as long as available size
 * 	remains contant.
 */
static void AdjustPanes(Paned *pw)
{
    int sashThickness = pw->paned.sashThickness;
    int pos = 0;
    int index;

    for (index = 0; index < Ttk_NumberSlaves(pw->paned.mgr); ++index) {
	Pane *pane = Ttk_SlaveData(pw->paned.mgr, index);
	int size = pane->sashPos - pos;
	pane->reqSize = size >= 0 ? size : 0;
	pos = pane->sashPos + sashThickness;
    }
}

/* ComputePositions --
 *	Set sash positions from pane request sizes and available space.
 *
 *	Allocate pane->reqSize pixels to each pane, and distribute
 *	the difference = available size - requested size according
 *	to pane->weight.
 *
 *	If there's still some left over, squeeze panes from the bottom up
 *	(This can happen if all weights are zero, or if one or more panes
 *	are too small to absorb the required shrinkage).
 *
 * Notes:
 * 	This doesn't distribute the remainder pixels as evenly as it could
 * 	when more than one pane has weight > 1.
 */
static void ComputePositions(Paned *pw)
{
    Ttk_Manager *mgr = pw->paned.mgr;
    int nPanes = Ttk_NumberSlaves(mgr);
    int sashThickness = pw->paned.sashThickness;
    int available
	= pw->paned.orient == TTK_ORIENT_HORIZONTAL
	? Tk_Width(pw->core.tkwin) : Tk_Height(pw->core.tkwin);
    int reqSize = 0, totalWeight = 0;
    int difference, delta, remainder, pos, i;

    if (nPanes == 0)
	return;

    /* Compute total required size and total available weight:
     */
    for (i = 0; i < nPanes; ++i) {
	Pane *pane = Ttk_SlaveData(mgr, i);
	reqSize += pane->reqSize;
	totalWeight += pane->weight * (pane->reqSize != 0);
    }

    /* Compute difference to be redistributed:
     */
    difference = available - reqSize - sashThickness*(nPanes-1);
    if (totalWeight != 0) {
	delta = difference / totalWeight;
	remainder = difference % totalWeight;
	if (remainder < 0) {
	    --delta;
	    remainder += totalWeight;
	}
    } else {
	delta = remainder = 0;
    }
    /* ASSERT: 0 <= remainder < totalWeight */

    /* Place sashes:
     */
    pos = 0;
    for (i = 0; i < nPanes; ++i) {
	Pane *pane = Ttk_SlaveData(mgr, i);
	int weight = pane->weight * (pane->reqSize != 0);
	int size = pane->reqSize + delta * weight;

	if (weight > remainder)
	    weight = remainder;
	remainder -= weight;
	size += weight;

	if (size < 0)
	    size = 0;

	pane->sashPos = (pos += size);
	pos += sashThickness;
    }

    /* Handle emergency shrink/emergency stretch:
     * Set sentinel sash position to end of widget,
     * shove preceding sashes up.
     */
    ShoveUp(pw, nPanes - 1, available);
}

/* PlacePanes --
 *	Places slave panes based on sash positions.
 */
static void PlacePanes(Paned *pw)
{
    int horizontal = pw->paned.orient == TTK_ORIENT_HORIZONTAL;
    int width = Tk_Width(pw->core.tkwin), height = Tk_Height(pw->core.tkwin);
    int sashThickness = pw->paned.sashThickness;
    int pos = 0;
    int index;

    for (index = 0; index < Ttk_NumberSlaves(pw->paned.mgr); ++index) {
	Pane *pane = Ttk_SlaveData(pw->paned.mgr, index);
	int size = pane->sashPos - pos;

	if (size > 0) {
	    if (horizontal) {
		Ttk_PlaceSlave(pw->paned.mgr, index, pos, 0, size, height);
	    } else {
		Ttk_PlaceSlave(pw->paned.mgr, index, 0, pos, width, size);
	    }
	} else {
	    Ttk_UnmapSlave(pw->paned.mgr, index);
	}

	pos = pane->sashPos + sashThickness;
    }
}

/*------------------------------------------------------------------------
 * +++ Manager specification.
 */

static void PanedPlaceSlaves(void *managerData)
{
    Paned *pw = managerData;
    ComputePositions(pw);
    PlacePanes(pw);
}

static void PaneAdded(Ttk_Manager *mgr, int index)
{
    Pane *pane = Ttk_SlaveData(mgr, index);
    Tk_Window slaveWindow = Ttk_SlaveWindow(mgr, index);
    Paned *pw = mgr->managerData;

    /* See also: PanedGeometryRequestProc */
    pane->reqSize
	= pw->paned.orient == TTK_ORIENT_HORIZONTAL
	? Tk_ReqWidth(slaveWindow) : Tk_ReqHeight(slaveWindow);
}

static void PaneRemoved(Ttk_Manager *mgr, int i) { /*no-op*/ }

static int PaneConfigured(
    Tcl_Interp *interp, Ttk_Manager *mgr, Ttk_Slave *slave, unsigned mask)
{
    Pane *pane = slave->slaveData;
    if (pane->weight < 0) {
	Tcl_AppendResult(interp, "-weight must be nonnegative", NULL);
	pane->weight = 0;
	return TCL_ERROR;
    }
    return TCL_OK;
}

/* PanedGeometryRequestProc --
 * 	Update pane request size, but only if slave is currently unmapped.
 * 	Geometry requests from mapped slaves are not directly honored,
 * 	in order to avoid unexpected pane resizes (esp. while the
 * 	user is dragging a sash [#1325286]).
 */

static void PanedGeometryRequestProc(
    ClientData clientData, Tk_Window slaveWindow)
{
    Ttk_Slave *slave = clientData;
    Pane *pane = slave->slaveData;
    Paned *pw = slave->manager->managerData;

    if (!Tk_IsMapped(slaveWindow)) {
	pane->reqSize
	    = pw->paned.orient == TTK_ORIENT_HORIZONTAL
	    ? Tk_ReqWidth(slaveWindow) : Tk_ReqHeight(slaveWindow);
    }

    /* Continue with default GeometryRequestProc:
     */
    Ttk_GeometryRequestProc(clientData, slaveWindow);
}

static Ttk_ManagerSpec PanedManagerSpec = {
    { "paned", PanedGeometryRequestProc, Ttk_LostSlaveProc },
    PaneOptionSpecs, sizeof(Pane),
    PanedSize,
    PanedPlaceSlaves,
    PaneAdded,
    PaneRemoved,
    PaneConfigured,
};

/*------------------------------------------------------------------------
 * +++ Event handler.
 *
 * Tk does not execute binding scripts for <Leave> events when
 * the pointer crosses from a parent to a child.  This widget
 * needs to know when that happens, though, so it can reset
 * the cursor.
 *
 * This event handler generates an <<EnteredChild>> virtual event
 * on LeaveNotify/NotifyInferior.
 */

static const unsigned PanedEventMask = LeaveWindowMask;
static void PanedEventProc(ClientData clientData, XEvent *eventPtr)
{
    WidgetCore *corePtr = clientData;
    if (   eventPtr->type == LeaveNotify
	&& eventPtr->xcrossing.detail == NotifyInferior)
    {
	SendVirtualEvent(corePtr->tkwin, "EnteredChild");
    }
}

/*------------------------------------------------------------------------
 * +++ Initialization and cleanup hooks.
 */

static int PanedInitialize(Tcl_Interp *interp, void *recordPtr)
{
    Paned *pw = recordPtr;

    Tk_CreateEventHandler(pw->core.tkwin,
	PanedEventMask, PanedEventProc, recordPtr);
    pw->paned.mgr = Ttk_CreateManager(&PanedManagerSpec, pw, pw->core.tkwin);
    pw->paned.sashLayout = 0;
    pw->paned.sashThickness = 1;

    return TCL_OK;
}

static void PanedCleanup(void *recordPtr)
{
    Paned *pw = recordPtr;

    if (pw->paned.sashLayout)
	Ttk_FreeLayout(pw->paned.sashLayout);
    Tk_DeleteEventHandler(pw->core.tkwin,
	PanedEventMask, PanedEventProc, recordPtr);
    Ttk_DeleteManager(pw->paned.mgr);
}

/*------------------------------------------------------------------------
 * +++ Layout management hooks.
 */
static Ttk_Layout PanedGetLayout(
    Tcl_Interp *interp, Ttk_Theme themePtr, void *recordPtr)
{
    Paned *pw = recordPtr;
    int horizontal = pw->paned.orient == TTK_ORIENT_HORIZONTAL;
    const char *layoutName = horizontal ? "Vertical.Sash" : "Horizontal.Sash";
    Ttk_Layout sashLayout = Ttk_CreateSubLayout(interp,themePtr,layoutName,
	recordPtr, pw->core.optionTable, pw->core.tkwin);

    if (sashLayout) {
	int sashWidth, sashHeight;

	if (pw->paned.sashLayout)
	    Ttk_FreeLayout(pw->paned.sashLayout);
	pw->paned.sashLayout = sashLayout;

	Ttk_LayoutSize(sashLayout, 0, &sashWidth, &sashHeight);

	pw->paned.sashThickness = horizontal ? sashWidth : sashHeight;

	/* Sanity-check:
	 */
	if (pw->paned.sashThickness < MIN_SASH_THICKNESS)
	    pw->paned.sashThickness = MIN_SASH_THICKNESS;

	Ttk_ManagerSizeChanged(pw->paned.mgr);
    }

    return WidgetGetLayout(interp, themePtr, recordPtr);
}

static void PanedDisplay(void *recordPtr, Drawable d)
{
    Paned *pw = recordPtr;
    int horizontal = pw->paned.orient == TTK_ORIENT_HORIZONTAL;
    Ttk_Layout sashLayout = pw->paned.sashLayout;
    int sashThickness = pw->paned.sashThickness;
    Ttk_State state = pw->core.state;
    int nPanes = Ttk_NumberSlaves(pw->paned.mgr);
    int i;

    WidgetDisplay(recordPtr, d);

    /* Draw sashes:
     */
    if (horizontal) {
	int height = Tk_Height(pw->core.tkwin);
	for (i = 0; i < nPanes; ++i) {
	    Pane *pane = Ttk_SlaveData(pw->paned.mgr, i);
	    Ttk_PlaceLayout(sashLayout, state,
		Ttk_MakeBox(pane->sashPos, 0, sashThickness, height));
	    Ttk_DrawLayout(sashLayout, state, d);
	}
    } else {
	int width = Tk_Width(pw->core.tkwin);
	for (i = 0; i < nPanes; ++i) {
	    Pane *pane = Ttk_SlaveData(pw->paned.mgr, i);
	    Ttk_PlaceLayout(sashLayout, state,
		Ttk_MakeBox(0, pane->sashPos, width, sashThickness));
	    Ttk_DrawLayout(sashLayout, state, d);
	}
    }
}

/*------------------------------------------------------------------------
 * +++ Widget commands.
 */

/* $pw add window [ options ... ]
 */
static int PanedAddCommand(
    Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[], void *recordPtr)
{
    Paned *pw = recordPtr;
    Tk_Window slaveWindow;

    if (objc < 3) {
	Tcl_WrongNumArgs(interp, 2, objv, "window");
	return TCL_ERROR;
    }

    slaveWindow = Tk_NameToWindow(
	interp, Tcl_GetString(objv[2]), pw->core.tkwin);

    if (!slaveWindow) {
	return TCL_ERROR;
    }

    return Ttk_AddSlave(interp, pw->paned.mgr, slaveWindow,
	    Ttk_NumberSlaves(pw->paned.mgr), objc - 3, objv + 3);
}

/* $pw insert $index $slave ?options...?
 * 	Insert new slave, or move existing one.
 */
static int PanedInsertCommand(
    Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[], void *recordPtr)
{
    Paned *pw = recordPtr;
    int srcIndex, destIndex;
    Tk_Window slaveWindow;

    if (objc < 4) {
	Tcl_WrongNumArgs(interp, 2,objv, "index slave ?options...?");
	return TCL_ERROR;
    }

    slaveWindow = Tk_NameToWindow(
	interp, Tcl_GetString(objv[3]), pw->core.tkwin);
    if (!slaveWindow) {
	return TCL_ERROR;
    }

    if (!strcmp(Tcl_GetString(objv[2]), "end")) {
	destIndex = Ttk_NumberSlaves(pw->paned.mgr);
    } else if (!Ttk_GetSlaveFromObj(interp,pw->paned.mgr,objv[2],&destIndex)) {
	return TCL_ERROR;
    }

    srcIndex = Ttk_SlaveIndex(pw->paned.mgr, slaveWindow);
    if (srcIndex < 0) { /* New slave: */
	return Ttk_AddSlave(interp, pw->paned.mgr, slaveWindow,
		destIndex, objc - 4, objv + 4);
    } /* else -- move existing slave: */

    if (destIndex >= pw->paned.mgr->nSlaves)
	destIndex  = pw->paned.mgr->nSlaves - 1;
    Ttk_ReorderSlave(pw->paned.mgr, srcIndex, destIndex);

    return objc == 4 ? TCL_OK :
	Ttk_ConfigureSlave(interp, pw->paned.mgr,
	     pw->paned.mgr->slaves[destIndex], objc-4,objv+4);
}

/* $pw forget $pane
 */
static int PanedForgetCommand(
    Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[], void *recordPtr)
{
    Paned *pw = recordPtr;
    int paneIndex;

    if (objc != 3) {
	Tcl_WrongNumArgs(interp, 2,objv, "pane");
	return TCL_ERROR;
    }

    if (!Ttk_GetSlaveFromObj(interp, pw->paned.mgr, objv[2], &paneIndex)) {
	return TCL_ERROR;
    }
    Ttk_ForgetSlave(pw->paned.mgr, paneIndex);

    return TCL_OK;
}

/* $pw identify $x $y
 * @@@ TODO: implement as documented, or change documentation.
 */
static int PanedIdentifyCommand(
    Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[], void *recordPtr)
{
    Paned *pw = recordPtr;
    int sashThickness = pw->paned.sashThickness;
    int x, y, pos;
    int index;

    if (objc != 4) {
	Tcl_WrongNumArgs(interp, 2,objv, "x y");
	return TCL_ERROR;
    }
    if (   Tcl_GetIntFromObj(interp, objv[2], &x) != TCL_OK
	|| Tcl_GetIntFromObj(interp, objv[3], &y) != TCL_OK
    ) {
	return TCL_ERROR;
    }

    pos = pw->paned.orient == TTK_ORIENT_HORIZONTAL ? x : y;
    for (index = 0; index < pw->paned.mgr->nSlaves - 1; ++index) {
	Pane *pane = Ttk_SlaveData(pw->paned.mgr, index);
	if (pane->sashPos <= pos && pos <= pane->sashPos + sashThickness) {
	    Tcl_SetObjResult(interp, Tcl_NewIntObj(index));
	    return TCL_OK;
	}
    }

    return TCL_OK;	/* empty list */
}

/* $pw pane $pane ?-option ?value -option value ...??
 * 	Query/modify pane options.
 */
static int PanedPaneCommand(
    Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[], void *recordPtr)
{
    Paned *pw = recordPtr;
    int paneIndex;
    Ttk_Slave *slave;

    if (objc < 3) {
	Tcl_WrongNumArgs(interp, 2,objv, "pane ?-option value...?");
	return TCL_ERROR;
    }

    slave = Ttk_GetSlaveFromObj(interp,pw->paned.mgr,objv[2],&paneIndex);
    if (!slave) {
	return TCL_ERROR;
    }

    switch (objc) {
	case 3:
	    return EnumerateOptions(interp, slave->slaveData, PaneOptionSpecs,
			pw->paned.mgr->slaveOptionTable, slave->slaveWindow);
	case 4:
	    return GetOptionValue(interp, slave->slaveData,objv[3],
			pw->paned.mgr->slaveOptionTable, slave->slaveWindow);
	default:
	    return Ttk_ConfigureSlave(
		    interp, pw->paned.mgr, slave, objc-3,objv+3);
    }
}

/* $pw sashpos $index ?$newpos?
 * 	Query or modify sash position.
 */
static int PanedSashposCommand(
    Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[], void *recordPtr)
{
    Paned *pw = recordPtr;
    int sashIndex, position = -1;
    Pane *pane;

    if (objc < 3 || objc > 4) {
	Tcl_WrongNumArgs(interp, 2,objv, "index ?newpos?");
	return TCL_ERROR;
    }
    if (Tcl_GetIntFromObj(interp, objv[2], &sashIndex) != TCL_OK) {
	return TCL_ERROR;
    }
    if (sashIndex < 0 || sashIndex >= Ttk_NumberSlaves(pw->paned.mgr) - 1) {
	Tcl_AppendResult(interp,
	    "sash index ", Tcl_GetString(objv[2]), " out of range",
	    NULL);
	return TCL_ERROR;
    }

    pane = Ttk_SlaveData(pw->paned.mgr, sashIndex);

    if (objc == 3) {
	Tcl_SetObjResult(interp, Tcl_NewIntObj(pane->sashPos));
	return TCL_OK;
    }
    /* else -- set new sash position */

    if (Tcl_GetIntFromObj(interp, objv[3], &position) != TCL_OK) {
	return TCL_ERROR;
    }

    if (position < pane->sashPos) {
	ShoveUp(pw, sashIndex, position);
    } else {
	ShoveDown(pw, sashIndex, position);
    }

    AdjustPanes(pw);
    Ttk_ManagerLayoutChanged(pw->paned.mgr);

    Tcl_SetObjResult(interp, Tcl_NewIntObj(pane->sashPos));
    return TCL_OK;
}

static WidgetCommandSpec PanedCommands[] =
{
    { "add", 		PanedAddCommand },
    { "configure",	WidgetConfigureCommand },
    { "cget",		WidgetCgetCommand },
    { "forget", 	PanedForgetCommand },
    { "identify", 	PanedIdentifyCommand },
    { "insert", 	PanedInsertCommand },
    { "instate",	WidgetInstateCommand },
    { "pane",   	PanedPaneCommand },
    { "sashpos",  	PanedSashposCommand },
    { "state",  	WidgetStateCommand },
    { 0,0 }
};

/*------------------------------------------------------------------------
 * +++ Widget specification.
 */

static WidgetSpec PanedWidgetSpec =
{
    "TPaned",			/* className */
    sizeof(Paned),		/* recordSize */
    PanedOptionSpecs,		/* optionSpecs */
    PanedCommands,		/* subcommands */
    PanedInitialize,		/* initializeProc */
    PanedCleanup,		/* cleanupProc */
    CoreConfigure,		/* configureProc */
    NullPostConfigure,		/* postConfigureProc */
    PanedGetLayout,		/* getLayoutProc */
    PanedSize, 			/* sizeProc */
    WidgetDoLayout,		/* layoutProc */
    PanedDisplay		/* displayProc */
};

/*------------------------------------------------------------------------
 * +++ Elements and layouts.
 */

typedef struct {
    Tcl_Obj *thicknessObj;
} SashElement;

static Ttk_ElementOptionSpec SashElementOptions[] = {
    { "-sashthickness", TK_OPTION_INT,
	    Tk_Offset(SashElement,thicknessObj), "5" },
    {NULL}
};

static void SashElementSize(
    void *clientData, void *elementRecord, Tk_Window tkwin,
    int *widthPtr, int *heightPtr, Ttk_Padding *paddingPtr)
{
    SashElement *sash = elementRecord;
    int thickness = MIN_SASH_THICKNESS;
    Tcl_GetIntFromObj(NULL, sash->thicknessObj, &thickness);
    *widthPtr = *heightPtr = thickness;
}

static Ttk_ElementSpec SashElementSpec = {
    TK_STYLE_VERSION_2,
    sizeof(SashElement),
    SashElementOptions,
    SashElementSize,
    NullElementDraw
};

TTK_BEGIN_LAYOUT(PanedLayout)
    TTK_NODE("Paned.background", 0)	/* @@@ BUG: empty layouts don't work */
TTK_END_LAYOUT

TTK_BEGIN_LAYOUT(HorizontalSashLayout)
    TTK_NODE("Sash.hsash", TTK_FILL_X)
TTK_END_LAYOUT

TTK_BEGIN_LAYOUT(VerticalSashLayout)
    TTK_NODE("Sash.vsash", TTK_FILL_Y)
TTK_END_LAYOUT

/*------------------------------------------------------------------------
 * +++ Registration routine.
 */
void Paned_Init(Tcl_Interp *interp)
{
    Ttk_Theme themePtr = Ttk_GetDefaultTheme(interp);
    RegisterWidget(interp, "ttk::paned", &PanedWidgetSpec);

    Ttk_RegisterElement(interp, themePtr, "hsash", &SashElementSpec, 0);
    Ttk_RegisterElement(interp, themePtr, "vsash", &SashElementSpec, 0);

    Ttk_RegisterLayout(themePtr, "TPaned", PanedLayout);
    Ttk_RegisterLayout(themePtr, "Horizontal.Sash", HorizontalSashLayout);
    Ttk_RegisterLayout(themePtr, "Vertical.Sash", VerticalSashLayout);
}

