/* 
 * tkPanedWindow.c --
 *
 *	This module implements "paned window" widgets that are object
 *	based.  A "paned window" is a widget that manages the geometry for
 *	some number of other widgets, placing a movable "sash" between them,
 *	which can be used to alter the relative sizes of adjacent widgets.
 *
 * Copyright (c) 1997 Sun Microsystems, Inc.
 * Copyright (c) 2000 Ajuba Solutions.
 *
 * See the file "license.terms" for information on usage and redistribution
 * of this file, and for a DISCLAIMER OF ALL WARRANTIES.
 *
 * RCS: @(#) $Id: tkPanedWindow.c,v 1.13 2003/02/21 11:48:54 dkf Exp $
 */

#include "tkPort.h"
#include "default.h"
#include "tkInt.h"

/* Flag values for "sticky"ness  The 16 combinations subsume the packer's
 * notion of anchor and fill.
 *
 * STICK_NORTH  	This window sticks to the top of its cavity.
 * STICK_EAST		This window sticks to the right edge of its cavity.
 * STICK_SOUTH		This window sticks to the bottom of its cavity.
 * STICK_WEST		This window sticks to the left edge of its cavity.
 */

#define STICK_NORTH		1
#define STICK_EAST		2
#define STICK_SOUTH		4
#define STICK_WEST		8
/*
 * The following table defines the legal values for the -orient option.
 */

static char *orientStrings[] = {
    "horizontal", "vertical", (char *) NULL
};

enum orient { ORIENT_HORIZONTAL, ORIENT_VERTICAL };

typedef struct {
    Tk_OptionTable pwOptions;	/* Token for paned window option table. */
    Tk_OptionTable slaveOpts;	/* Token for slave cget option table. */
} OptionTables;

/*
 * One structure of the following type is kept for each window
 * managed by a paned window widget.
 */

typedef struct Slave {
    Tk_Window tkwin;			/* Window being managed. */
    
    int minSize;			/* Minimum size of this pane, on the
					 * relevant axis, in pixels. */
    int padx;				/* Additional padding requested for
					 * slave, in the x dimension. */
    int pady;				/* Additional padding requested for
					 * slave, in the y dimension. */
    Tcl_Obj *widthPtr, *heightPtr;	/* Tcl_Obj rep's of slave width/height,
					 * to allow for null values. */
    int width;				/* Slave width. */
    int height;				/* Slave height. */
    int sticky;				/* Sticky string. */
    int x, y;				/* Coordinates of the widget. */
    int paneWidth, paneHeight;		/* Pane dimensions (may be different
					 * from slave width/height). */
    int sashx, sashy;			/* Coordinates of the sash of the
					 * right or bottom of this pane. */
    int markx, marky;			/* Coordinates of the last mark set
					 * for the sash. */
    int handlex, handley;		/* Coordinates of the sash handle. */
    struct PanedWindow *masterPtr;	/* Paned window managing the window. */
    Tk_Window after;			/* Placeholder for parsing options. */
    Tk_Window before;			/* Placeholder for parsing options. */
} Slave;

/*
 * A data structure of the following type is kept for each paned window
 * widget managed by this file:
 */

typedef struct PanedWindow {
    Tk_Window tkwin;		/* Window that embodies the paned window. */
    Tk_Window proxywin;		/* Window for the resizing proxy. */
    Display *display;		/* X's token for the window's display. */
    Tcl_Interp *interp;		/* Interpreter associated with widget. */
    Tcl_Command widgetCmd;	/* Token for square's widget command. */
    Tk_OptionTable optionTable;	/* Token representing the configuration
				 * specifications. */
    Tk_OptionTable slaveOpts;	/* Token for slave cget table. */
    Tk_3DBorder background;	/* Background color. */
    int borderWidth;		/* Value of -borderwidth option. */
    int relief;			/* 3D border effect (TK_RELIEF_RAISED, etc) */
    Tcl_Obj *widthPtr;		/* Tcl_Obj rep for width. */
    Tcl_Obj *heightPtr;		/* Tcl_Obj rep for height. */
    int width, height;		/* Width and height of the widget. */
    enum orient orient;		/* Orientation of the widget. */
    Tk_Cursor cursor;		/* Current cursor for window, or None. */
    int resizeOpaque;		/* Boolean indicating whether resize should be
				 * opaque or rubberband style. */
    
    int sashRelief;		/* Relief used to draw sash. */
    int sashWidth;		/* Width of each sash, in pixels. */
    Tcl_Obj *sashWidthPtr;	/* Tcl_Obj rep for sash width. */
    int sashPad;		/* Additional padding around each sash. */
    Tcl_Obj *sashPadPtr;	/* Tcl_Obj rep for sash padding. */
    int showHandle;		/* Boolean indicating whether sash handles
				 * should be drawn. */
    int handleSize;		/* Size of one side of a sash handle (handles
				 * are square), in pixels. */
    int handlePad;		/* Distance from border to draw handle. */
    Tcl_Obj *handleSizePtr;	/* Tcl_Obj rep for handle size. */
    Tk_Cursor sashCursor;	/* Cursor used when mouse is above a sash. */

    GC gc;			/* Graphics context for copying from
				 * off-screen pixmap onto screen. */
    int proxyx, proxyy;		/* Proxy x,y coordinates. */
    Slave **slaves;		/* Pointer to array of Slaves. */
    int numSlaves;		/* Number of slaves. */
    int sizeofSlaves;		/* Number of elements in the slaves array. */
    int flags;			/* Flags for widget; see below. */
} PanedWindow;

/*
 * Flags used for paned windows:
 *
 * REDRAW_PENDING:		Non-zero means a DoWhenIdle handler has
 *				been queued to redraw this window.
 *
 * WIDGET_DELETED:		Non-zero means that the paned window has
 *				been, or is in the process of being, deleted.
 *
 * RESIZE_PENDING:		Non-zero means that the window might need to
 *				change its size (or the size of its panes)
 *				because of a change in the size of one of its
 *				children.
 */

#define REDRAW_PENDING		0x0001
#define WIDGET_DELETED		0x0002
#define REQUESTED_RELAYOUT	0x0004
#define RECOMPUTE_GEOMETRY	0x0008
#define PROXY_REDRAW_PENDING	0x0010
#define RESIZE_PENDING		0x0020

/*
 * Forward declarations for procedures defined later in this file:
 */

int		Tk_PanedWindowObjCmd _ANSI_ARGS_((ClientData clientData,
			Tcl_Interp *interp, int objc, Tcl_Obj * CONST objv[]));
static void	PanedWindowCmdDeletedProc _ANSI_ARGS_((ClientData clientData));
static int	ConfigurePanedWindow _ANSI_ARGS_((Tcl_Interp *interp,
			PanedWindow *pwPtr, int objc, Tcl_Obj *CONST objv[]));
static void	DestroyPanedWindow _ANSI_ARGS_((PanedWindow *pwPtr));
static void	DisplayPanedWindow _ANSI_ARGS_((ClientData clientData));
static void	PanedWindowEventProc _ANSI_ARGS_((ClientData clientData,
			XEvent *eventPtr));
static void	ProxyWindowEventProc _ANSI_ARGS_((ClientData clientData,
			XEvent *eventPtr));
static void	DisplayProxyWindow _ANSI_ARGS_((ClientData clientData));
void		PanedWindowWorldChanged _ANSI_ARGS_((ClientData instanceData));
static int	PanedWindowWidgetObjCmd _ANSI_ARGS_((ClientData clientData,
			Tcl_Interp *, int objc, Tcl_Obj * CONST objv[]));
static void	PanedWindowLostSlaveProc _ANSI_ARGS_((ClientData clientData,
			Tk_Window tkwin));
static void	PanedWindowReqProc _ANSI_ARGS_((ClientData clientData,
			Tk_Window tkwin));
static void	ArrangePanes _ANSI_ARGS_((ClientData clientData));
static void	Unlink _ANSI_ARGS_((Slave *slavePtr));
static Slave *	GetPane _ANSI_ARGS_((PanedWindow *pwPtr, Tk_Window tkwin));
static void	SlaveStructureProc _ANSI_ARGS_((ClientData clientData,
			XEvent *eventPtr));
static int	PanedWindowSashCommand _ANSI_ARGS_((PanedWindow *pwPtr,
			Tcl_Interp *interp, int objc, Tcl_Obj * CONST objv[]));
static int	PanedWindowProxyCommand _ANSI_ARGS_((PanedWindow *pwPtr,
			Tcl_Interp *interp, int objc, Tcl_Obj * CONST objv[]));
static void	ComputeGeometry _ANSI_ARGS_((PanedWindow *pwPtr));
static int	ConfigureSlaves _ANSI_ARGS_((PanedWindow *pwPtr,
			Tcl_Interp *interp, int objc, Tcl_Obj * CONST objv[]));
static void	DestroyOptionTables _ANSI_ARGS_((ClientData clientData,
			Tcl_Interp *interp));
static int	SetSticky _ANSI_ARGS_((ClientData clientData,
			Tcl_Interp *interp, Tk_Window tkwin,
			Tcl_Obj **value, char *recordPtr, int internalOffset,
			char *oldInternalPtr, int flags));
static Tcl_Obj *GetSticky _ANSI_ARGS_((ClientData clientData, Tk_Window tkwin,
			char *recordPtr, int internalOffset));
static void	RestoreSticky _ANSI_ARGS_((ClientData clientData,
			Tk_Window tkwin, char *internalPtr,
			char *oldInternalPtr));
static void	AdjustForSticky _ANSI_ARGS_((int sticky, int cavityWidth,
			int cavityHeight, int *xPtr, int *yPtr,
			int *slaveWidthPtr, int *slaveHeightPtr));
static void	MoveSash _ANSI_ARGS_((PanedWindow *pwPtr, int sash, int diff));
static int	ObjectIsEmpty _ANSI_ARGS_((Tcl_Obj *objPtr));
static char *	ComputeSlotAddress _ANSI_ARGS_((char *recordPtr, int offset));
static int	PanedWindowIdentifyCoords _ANSI_ARGS_((PanedWindow *pwPtr,
			Tcl_Interp *interp, int x, int y));

/*
 * Sashes are between panes only, so there is one less sash than slaves
 */

#define ValidSashIndex(pwPtr, sash) \
	(((sash) >= 0) && ((sash) < ((pwPtr)->numSlaves-1)))

static Tk_GeomMgr panedWindowMgrType = {
    "panedwindow",		/* name */
    PanedWindowReqProc,		/* requestProc */
    PanedWindowLostSlaveProc,	/* lostSlaveProc */
};

/*
 * Information used for objv parsing.
 */

#define GEOMETRY		0x0001

/*
 * The following structure contains pointers to functions used for processing
 * the custom "-sticky" option for slave windows.
 */

static Tk_ObjCustomOption stickyOption = {
    "sticky",				/* name */
    SetSticky,				/* setProc */
    GetSticky,				/* getProc */
    RestoreSticky,			/* restoreProc */
    (Tk_CustomOptionFreeProc *)NULL,	/* freeProc */
    0
};

static Tk_OptionSpec optionSpecs[] = {
    {TK_OPTION_BORDER, "-background", "background", "Background",
	 DEF_PANEDWINDOW_BG_COLOR, -1, Tk_Offset(PanedWindow, background), 0,
	 (ClientData) DEF_PANEDWINDOW_BG_MONO},
    {TK_OPTION_SYNONYM, "-bd", (char *) NULL, (char *) NULL,
	 (char *) NULL, 0, -1, 0, (ClientData) "-borderwidth"},
    {TK_OPTION_SYNONYM, "-bg", (char *) NULL, (char *) NULL,
	 (char *) NULL, 0, -1, 0, (ClientData) "-background"},
    {TK_OPTION_PIXELS, "-borderwidth", "borderWidth", "BorderWidth",
	 DEF_PANEDWINDOW_BORDERWIDTH, -1, Tk_Offset(PanedWindow, borderWidth),
         0, 0, GEOMETRY},
    {TK_OPTION_CURSOR, "-cursor", "cursor", "Cursor",
	 DEF_PANEDWINDOW_CURSOR, -1, Tk_Offset(PanedWindow, cursor),
	 TK_OPTION_NULL_OK, 0, 0},
    {TK_OPTION_PIXELS, "-handlepad", "handlePad", "HandlePad",
	 DEF_PANEDWINDOW_HANDLEPAD, -1, Tk_Offset(PanedWindow, handlePad),
         0, 0},
    {TK_OPTION_PIXELS, "-handlesize", "handleSize", "HandleSize",
	 DEF_PANEDWINDOW_HANDLESIZE, Tk_Offset(PanedWindow, handleSizePtr),
	 Tk_Offset(PanedWindow, handleSize), 0, 0, GEOMETRY},
    {TK_OPTION_PIXELS, "-height", "height", "Height",
	 DEF_PANEDWINDOW_HEIGHT, Tk_Offset(PanedWindow, heightPtr),
	 Tk_Offset(PanedWindow, height), TK_OPTION_NULL_OK, 0, GEOMETRY},
    {TK_OPTION_BOOLEAN, "-opaqueresize", "opaqueResize", "OpaqueResize",
	 DEF_PANEDWINDOW_OPAQUERESIZE, -1,
         Tk_Offset(PanedWindow, resizeOpaque), 0, 0, 0},
    {TK_OPTION_STRING_TABLE, "-orient", "orient", "Orient",
	 DEF_PANEDWINDOW_ORIENT, -1, Tk_Offset(PanedWindow, orient), 
	 0, (ClientData) orientStrings, GEOMETRY},
    {TK_OPTION_RELIEF, "-relief", "relief", "Relief",
	 DEF_PANEDWINDOW_RELIEF, -1, Tk_Offset(PanedWindow, relief), 0, 0, 0},
    {TK_OPTION_CURSOR, "-sashcursor", "sashCursor", "Cursor",
	 DEF_PANEDWINDOW_SASHCURSOR, -1, Tk_Offset(PanedWindow, sashCursor),
	 TK_OPTION_NULL_OK, 0, 0},
    {TK_OPTION_PIXELS, "-sashpad", "sashPad", "SashPad",
	 DEF_PANEDWINDOW_SASHPAD, -1, Tk_Offset(PanedWindow, sashPad),
         0, 0, GEOMETRY},
    {TK_OPTION_RELIEF, "-sashrelief", "sashRelief", "Relief",
	 DEF_PANEDWINDOW_SASHRELIEF, -1, Tk_Offset(PanedWindow, sashRelief),
         0, 0, 0},
    {TK_OPTION_PIXELS, "-sashwidth", "sashWidth", "Width",
	 DEF_PANEDWINDOW_SASHWIDTH, Tk_Offset(PanedWindow, sashWidthPtr),
	 Tk_Offset(PanedWindow, sashWidth), 0, 0, GEOMETRY},
    {TK_OPTION_BOOLEAN, "-showhandle", "showHandle", "ShowHandle",
	 DEF_PANEDWINDOW_SHOWHANDLE, -1, Tk_Offset(PanedWindow, showHandle),
         0, 0, GEOMETRY},
    {TK_OPTION_PIXELS, "-width", "width", "Width",
	 DEF_PANEDWINDOW_WIDTH, Tk_Offset(PanedWindow, widthPtr),
	 Tk_Offset(PanedWindow, width), TK_OPTION_NULL_OK, 0, GEOMETRY},
    {TK_OPTION_END}
};

static Tk_OptionSpec slaveOptionSpecs[] = {
    {TK_OPTION_WINDOW, "-after", (char *) NULL, (char *) NULL,
	 DEF_PANEDWINDOW_PANE_AFTER, -1, Tk_Offset(Slave, after),
         TK_OPTION_NULL_OK, 0, 0},
    {TK_OPTION_WINDOW, "-before", (char *) NULL, (char *) NULL,
         DEF_PANEDWINDOW_PANE_BEFORE, -1, Tk_Offset(Slave, before),
         TK_OPTION_NULL_OK, 0, 0},
    {TK_OPTION_PIXELS, "-height", (char *) NULL, (char *) NULL,
	 DEF_PANEDWINDOW_PANE_HEIGHT, Tk_Offset(Slave, heightPtr),
         Tk_Offset(Slave, height), TK_OPTION_NULL_OK, 0, 0},
    {TK_OPTION_PIXELS, "-minsize", (char *) NULL, (char *) NULL,
	 DEF_PANEDWINDOW_PANE_MINSIZE, -1, Tk_Offset(Slave, minSize), 0, 0, 0},
    {TK_OPTION_PIXELS, "-padx", (char *) NULL, (char *) NULL,
	 DEF_PANEDWINDOW_PANE_PADX, -1, Tk_Offset(Slave, padx), 0, 0, 0},
    {TK_OPTION_PIXELS, "-pady", (char *) NULL, (char *) NULL,
	 DEF_PANEDWINDOW_PANE_PADY, -1, Tk_Offset(Slave, pady), 0, 0, 0},
    {TK_OPTION_CUSTOM, "-sticky", (char *) NULL, (char *) NULL,
	 DEF_PANEDWINDOW_PANE_STICKY, -1, Tk_Offset(Slave, sticky), 0,
         (ClientData) &stickyOption, 0},
    {TK_OPTION_PIXELS, "-width", (char *) NULL, (char *) NULL,
	 DEF_PANEDWINDOW_PANE_WIDTH, Tk_Offset(Slave, widthPtr),
         Tk_Offset(Slave, width), TK_OPTION_NULL_OK, 0, 0},
    {TK_OPTION_END}
};
    

/*
 *--------------------------------------------------------------
 *
 * Tk_PanedWindowObjCmd --
 *
 *	This procedure is invoked to process the "panedwindow" Tcl
 *	command.  It creates a new "panedwindow" widget.
 *
 * Results:
 *	A standard Tcl result.
 *
 * Side effects:
 *	A new widget is created and configured.
 *
 *--------------------------------------------------------------
 */

int
Tk_PanedWindowObjCmd(clientData, interp, objc, objv)
    ClientData clientData;	/* NULL. */
    Tcl_Interp *interp;		/* Current interpreter. */
    int objc;			/* Number of arguments. */
    Tcl_Obj * CONST objv[];	/* Argument objects. */
{
    PanedWindow *pwPtr;
    Tk_Window tkwin, parent;
    OptionTables *pwOpts;

    if (objc < 2) {
	Tcl_WrongNumArgs(interp, 1, objv, "pathName ?options?");
	return TCL_ERROR;
    }

    tkwin = Tk_CreateWindowFromPath(interp, Tk_MainWindow(interp), 
	    Tcl_GetStringFromObj(objv[1], NULL), (char *) NULL);
    if (tkwin == NULL) {
	return TCL_ERROR;
    }

    pwOpts = (OptionTables *)
	Tcl_GetAssocData(interp, "PanedWindowOptionTables", NULL);
    if (pwOpts == NULL) {
	/*
	 * The first time this procedure is invoked, the option tables will
	 * be NULL.  We then create the option tables from the templates
	 * and store a pointer to the tables as the command's clinical so
	 * we'll have easy access to it in the future.
	 */
	pwOpts = (OptionTables *) ckalloc(sizeof(OptionTables));
	/* Set up an exit handler to free the optionTables struct */
	Tcl_SetAssocData(interp, "PanedWindowOptionTables",
		DestroyOptionTables, (ClientData) pwOpts);

	/* Create the paned window option tables. */
	pwOpts->pwOptions = Tk_CreateOptionTable(interp, optionSpecs);
	pwOpts->slaveOpts = Tk_CreateOptionTable(interp, slaveOptionSpecs);
    }

    Tk_SetClass(tkwin, "Panedwindow");

    /*
     * Allocate and initialize the widget record.
     */

    pwPtr = (PanedWindow *) ckalloc(sizeof(PanedWindow));
    memset((void *)pwPtr, 0, (sizeof(PanedWindow)));
    pwPtr->tkwin	= tkwin;
    pwPtr->display	= Tk_Display(tkwin);
    pwPtr->interp	= interp;
    pwPtr->widgetCmd	= Tcl_CreateObjCommand(interp,
	    Tk_PathName(pwPtr->tkwin), PanedWindowWidgetObjCmd,
	    (ClientData) pwPtr, PanedWindowCmdDeletedProc);
    pwPtr->optionTable	= pwOpts->pwOptions;
    pwPtr->slaveOpts	= pwOpts->slaveOpts;
    pwPtr->relief	= TK_RELIEF_RAISED;
    pwPtr->gc		= None;
    pwPtr->cursor	= None;
    pwPtr->sashCursor	= None;

    /*
     * Keep a hold of the associated tkwin until we destroy the widget,
     * otherwise Tk might free it while we still need it.
     */

    Tcl_Preserve((ClientData) pwPtr->tkwin);

    if (Tk_InitOptions(interp, (char *) pwPtr, pwOpts->pwOptions,
	    tkwin) != TCL_OK) {
	Tk_DestroyWindow(pwPtr->tkwin);
	return TCL_ERROR;
    }

    Tk_CreateEventHandler(pwPtr->tkwin, ExposureMask|StructureNotifyMask,
	    PanedWindowEventProc, (ClientData) pwPtr);

    /*
     * Find the toplevel ancestor of the panedwindow, and make a proxy
     * win as a child of that window; this way the proxy can always float
     * above slaves in the panedwindow.
     */
    parent = Tk_Parent(pwPtr->tkwin);
    while (!(Tk_IsTopLevel(parent))) {
	parent = Tk_Parent(parent);
	if (parent == NULL) {
	    parent = pwPtr->tkwin;
	    break;
	}
    }

    pwPtr->proxywin = Tk_CreateAnonymousWindow(interp, parent, (char *) NULL);
    Tk_CreateEventHandler(pwPtr->proxywin, ExposureMask, ProxyWindowEventProc,
	    (ClientData) pwPtr);

    if (ConfigurePanedWindow(interp, pwPtr, objc - 2, objv + 2) != TCL_OK) {
	Tk_DestroyWindow(pwPtr->proxywin);
	Tk_DestroyWindow(pwPtr->tkwin);
	return TCL_ERROR;
    }

    Tcl_SetStringObj(Tcl_GetObjResult(interp), Tk_PathName(pwPtr->tkwin), -1);
    return TCL_OK;
}

/*
 *--------------------------------------------------------------
 *
 * PanedWindowWidgetObjCmd --
 *
 *	This procedure is invoked to process the Tcl command
 *	that corresponds to a widget managed by this module.
 *	See the user documentation for details on what it does.
 *
 * Results:
 *	A standard Tcl result.
 *
 * Side effects:
 *	See the user documentation.
 *
 *--------------------------------------------------------------
 */

static int
PanedWindowWidgetObjCmd(clientData, interp, objc, objv)
    ClientData clientData;		/* Information about square widget. */
    Tcl_Interp *interp;			/* Current interpreter. */
    int objc;				/* Number of arguments. */
    Tcl_Obj * CONST objv[];		/* Argument objects. */
{
    PanedWindow *pwPtr = (PanedWindow *) clientData;
    int result = TCL_OK;
    static CONST char *optionStrings[] = {"add", "cget", "configure", "forget",
					"identify", "panecget",
                                        "paneconfigure", "panes",
                                        "proxy", "sash", (char *) NULL};
    enum options { PW_ADD, PW_CGET, PW_CONFIGURE, PW_FORGET, PW_IDENTIFY,
		       PW_PANECGET, PW_PANECONFIGURE, PW_PANES, PW_PROXY,
                       PW_SASH };
    Tcl_Obj *resultObj;
    int index, count, i, x, y;
    Tk_Window tkwin;
    Slave *slavePtr;
    
    if (objc < 2) {
	Tcl_WrongNumArgs(interp, 1, objv, "option ?arg arg...?");
	return TCL_ERROR;
    }

    if (Tcl_GetIndexFromObj(interp, objv[1], optionStrings, "command",
	    0, &index) != TCL_OK) {
	return TCL_ERROR;
    }

    Tcl_Preserve((ClientData) pwPtr);
    
    switch ((enum options) index) {
	case PW_ADD: {
	    if (objc < 3) {
		Tcl_WrongNumArgs(interp, 2, objv, "widget ?widget ...?");
		result = TCL_ERROR;
		break;
	    }
	    
	    result = ConfigureSlaves(pwPtr, interp, objc, objv);
	    break;
	}
	
	case PW_CGET: {
	    if (objc != 3) {
		Tcl_WrongNumArgs(interp, 2, objv, "option");
		result = TCL_ERROR;
		break;
	    }
	    resultObj = Tk_GetOptionValue(interp, (char *) pwPtr,
		    pwPtr->optionTable, objv[2], pwPtr->tkwin);
	    if (resultObj == NULL) {
		result = TCL_ERROR;
	    } else {
		Tcl_SetObjResult(interp, resultObj);
	    }
	    break;
	}
	
	case PW_CONFIGURE: {
	    resultObj = NULL;
	    if (objc <= 3) {
		resultObj = Tk_GetOptionInfo(interp, (char *) pwPtr,
			pwPtr->optionTable,
			(objc == 3) ? objv[2] : (Tcl_Obj *) NULL,
			pwPtr->tkwin);
		if (resultObj == NULL) {
		    result = TCL_ERROR;
		} else {
		    Tcl_SetObjResult(interp, resultObj);
		}
	    } else {
		result = ConfigurePanedWindow(interp, pwPtr, objc - 2,
			objv + 2);
	    }
	    break;
	}
	
	case PW_FORGET: {
	    Tk_Window slave;
	    int i;
	    
	    if (objc < 3) {
		Tcl_WrongNumArgs(interp, 2, objv, "widget ?widget ...?");
		result = TCL_ERROR;
		break;
	    }

	    /*
	     * Clean up each window named in the arg list.
	     */
	    for (count = 0, i = 2; i < objc; i++) {
		slave = Tk_NameToWindow(interp, Tcl_GetString(objv[i]),
			pwPtr->tkwin);
		if (slave == NULL) {
		    continue;
		}
		slavePtr = GetPane(pwPtr, slave);
		if ((slavePtr != NULL) && (slavePtr->masterPtr != NULL)) {
		    count++;
		    Tk_ManageGeometry(slave, (Tk_GeomMgr *) NULL,
			    (ClientData) NULL);
		    Tk_UnmaintainGeometry(slavePtr->tkwin, pwPtr->tkwin);
		    Tk_DeleteEventHandler(slavePtr->tkwin, StructureNotifyMask,
			    SlaveStructureProc, (ClientData) slavePtr);
		    Tk_UnmapWindow(slavePtr->tkwin);
		    Unlink(slavePtr);
		}
		if (count != 0) {
		    ComputeGeometry(pwPtr);
		}
	    }
	    break;
	}

	case PW_IDENTIFY: {
	    if (objc != 4) {
		Tcl_WrongNumArgs(interp, 2, objv, "x y");
		result = TCL_ERROR;
		break;
	    }

	    if ((Tcl_GetIntFromObj(interp, objv[2], &x) != TCL_OK)
		    || (Tcl_GetIntFromObj(interp, objv[3], &y) != TCL_OK)) {
		result = TCL_ERROR;
		break;
	    }
	    
	    result = PanedWindowIdentifyCoords(pwPtr, interp, x, y);
	    break;
	}

	case PW_PANECGET: {
	    if (objc != 4) {
		Tcl_WrongNumArgs(interp, 2, objv, "pane option");
		result = TCL_ERROR;
		break;
	    }
	    tkwin = Tk_NameToWindow(interp, Tcl_GetString(objv[2]),
		    pwPtr->tkwin);
	    if (tkwin == NULL) {
		result = TCL_ERROR;
		break;
	    }
	    resultObj = NULL;
	    for (i = 0; i < pwPtr->numSlaves; i++) {
		if (pwPtr->slaves[i]->tkwin == tkwin) {
		    resultObj = Tk_GetOptionValue(interp,
			    (char *) pwPtr->slaves[i], pwPtr->slaveOpts,
			    objv[3], tkwin);
		}
	    }
	    if (i == pwPtr->numSlaves) {
		Tcl_SetResult(interp, "not managed by this window",
			TCL_STATIC);
	    }
	    if (resultObj == NULL) {
		result = TCL_ERROR;
	    } else {
		Tcl_SetObjResult(interp, resultObj);
	    }
	    break;
	}

	case PW_PANECONFIGURE: {
	    if (objc < 3) {
		Tcl_WrongNumArgs(interp, 2, objv,
			"pane ?option? ?value option value ...?");
		result = TCL_ERROR;
		break;
	    }
	    resultObj = NULL;
	    if (objc <= 4) {
		tkwin = Tk_NameToWindow(interp, Tcl_GetString(objv[2]),
			pwPtr->tkwin);
		for (i = 0; i < pwPtr->numSlaves; i++) {
		    if (pwPtr->slaves[i]->tkwin == tkwin) {
			resultObj = Tk_GetOptionInfo(interp,
				(char *) pwPtr->slaves[i],
				pwPtr->slaveOpts,
				(objc == 4) ? objv[3] : (Tcl_Obj *) NULL,
				pwPtr->tkwin);
			if (resultObj == NULL) {
			    result = TCL_ERROR;
			} else {
			    Tcl_SetObjResult(interp, resultObj);
			}
			break;
		    }
		}
	    } else {
		result = ConfigureSlaves(pwPtr, interp, objc, objv);
	    }
	    break;
	}
	    
	case PW_PANES: {
	    resultObj = Tcl_NewObj();

	    Tcl_IncrRefCount(resultObj);

	    for (i = 0; i < pwPtr->numSlaves; i++) {
		Tcl_ListObjAppendElement(interp, resultObj,
			Tcl_NewStringObj(Tk_PathName(pwPtr->slaves[i]->tkwin),
				-1));
	    }
	    Tcl_SetObjResult(interp, resultObj);
	    Tcl_DecrRefCount(resultObj);
	    break;
	}

	case PW_PROXY: {
	    result = PanedWindowProxyCommand(pwPtr, interp, objc, objv);
	    break;
	}

	case PW_SASH: {
	    result = PanedWindowSashCommand(pwPtr, interp, objc, objv);
	    break;
	}
    }
    Tcl_Release((ClientData) pwPtr);
    return result;
}

/*
 *----------------------------------------------------------------------
 *
 * ConfigureSlaves --
 *
 *	Add or alter the configuration options of a slave in a paned
 *	window.
 *
 * Results:
 *	Standard Tcl result.
 *
 * Side effects:
 *	Depends on options; may add a slave to the paned window, may
 *	alter the geometry management options of a slave.
 *
 *----------------------------------------------------------------------
 */

static int
ConfigureSlaves(pwPtr, interp, objc, objv)
    PanedWindow *pwPtr;			/* Information about paned window. */
    Tcl_Interp *interp;			/* Current interpreter. */
    int objc;				/* Number of arguments. */
    Tcl_Obj * CONST objv[];		/* Argument objects. */
{
    int i, firstOptionArg, j, found, doubleBw, index, numNewSlaves, haveLoc;
    int insertIndex;
    Tk_Window tkwin = NULL, ancestor, parent;
    Slave *slavePtr, **inserts, **new;
    Slave options;
    char *arg;
   
    /*
     * Find the non-window name arguments; these are the configure options
     * for the slaves.  Also validate that the window names given are
     * legitimate (ie, they are real windows, they are not the panedwindow
     * itself, etc.).
     */
    for (i = 2; i < objc; i++) {
	arg = Tcl_GetString(objv[i]);
	if (arg[0] == '-') {
	    break;
	} else {
	    tkwin = Tk_NameToWindow(interp, arg, pwPtr->tkwin);
	    if (tkwin == NULL) {
		/*
		 * Just a plain old bad window; Tk_NameToWindow filled in an
		 * error message for us.
		 */
		return TCL_ERROR;
	    } else if (tkwin == pwPtr->tkwin) {
		/*
		 * A panedwindow cannot manage itself.
		 */
		Tcl_ResetResult(interp);
		Tcl_AppendResult(interp, "can't add ", arg, " to itself",
			(char *) NULL);
		return TCL_ERROR;
	    } else if (Tk_IsTopLevel(tkwin)) {
		/*
		 * A panedwindow cannot manage a toplevel.
		 */
		Tcl_ResetResult(interp);
		Tcl_AppendResult(interp, "can't add toplevel ", arg, " to ",
			Tk_PathName(pwPtr->tkwin), (char *) NULL);
		return TCL_ERROR;
	    } else {
		/*
		 * Make sure the panedwindow is the parent of the slave,
		 * or a descendant of the slave's parent.
		 */
		parent = Tk_Parent(tkwin);
		for (ancestor = pwPtr->tkwin;;ancestor = Tk_Parent(ancestor)) {
		    if (ancestor == parent) {
			break;
		    }
		    if (Tk_IsTopLevel(ancestor)) {
			Tcl_ResetResult(interp);
			Tcl_AppendResult(interp, "can't add ", arg,
				" to ", Tk_PathName(pwPtr->tkwin),
				(char *) NULL);
			return TCL_ERROR;
		    }
		}
	    }
	}
    }
    firstOptionArg = i;

    /*
     * Pre-parse the configuration options, to get the before/after specifiers
     * into an easy-to-find location (a local variable).  Also, check the
     * return from Tk_SetOptions once, here, so we can save a little bit of
     * extra testing in the for loop below.
     */
    memset((void *)&options, 0, sizeof(Slave));
    if (Tk_SetOptions(interp, (char *) &options, pwPtr->slaveOpts,
	    objc - firstOptionArg, objv + firstOptionArg,
	    pwPtr->tkwin, NULL, NULL) != TCL_OK) {
	return TCL_ERROR;
    }

    /*
     * If either -after or -before was given, find the numerical index that
     * corresponds to the given window.  If both -after and -before are
     * given, the option precedence is:  -after, then -before.
     */
    index = -1;
    haveLoc = 0;
    if (options.after != None) {
	tkwin = options.after;
	haveLoc = 1;
	for (i = 0; i < pwPtr->numSlaves; i++) {
	    if (options.after == pwPtr->slaves[i]->tkwin) {
		index = i + 1;
		break;
	    }
	}
    } else if (options.before != None) {
	tkwin = options.before;
	haveLoc = 1;
	for (i = 0; i < pwPtr->numSlaves; i++) {
	    if (options.before == pwPtr->slaves[i]->tkwin) {
		index = i;
		break;
	    }
	}
    }

    /*
     * If a window was given for -after/-before, but it's not a window
     * managed by the panedwindow, throw an error
     */
    if (haveLoc && index == -1) {
	Tcl_ResetResult(interp);
	Tcl_AppendResult(interp, "window \"", Tk_PathName(tkwin),
		"\" is not managed by ", Tk_PathName(pwPtr->tkwin),
		(char *) NULL);
	Tk_FreeConfigOptions((char *) &options, pwPtr->slaveOpts,
		pwPtr->tkwin);
	return TCL_ERROR;
    }

    /*
     * Allocate an array to hold, in order, the pointers to the slave
     * structures corresponding to the windows specified.  Some of those
     * structures may already have existed, some may be new.
     */
    inserts = (Slave **)ckalloc(sizeof(Slave *) * (firstOptionArg - 2));
    insertIndex = 0;
    
    /*
     * Populate the inserts array, creating new slave structures as necessary,
     * applying the options to each structure as we go, and, if necessary,
     * marking the spot in the original slaves array as empty (for pre-existing
     * slave structures).
     */
    for (i = 0, numNewSlaves = 0; i < firstOptionArg - 2; i++) {
	/*
	 * We don't check that tkwin is NULL here, because the pre-pass above
	 * guarantees that the input at this stage is good.
	 */
	tkwin = Tk_NameToWindow(interp, Tcl_GetString(objv[i + 2]),
		pwPtr->tkwin);

	found = 0;
	for (j = 0; j < pwPtr->numSlaves; j++) {
	    if (pwPtr->slaves[j] != NULL && pwPtr->slaves[j]->tkwin == tkwin) {
		Tk_SetOptions(interp, (char *) pwPtr->slaves[j],
			pwPtr->slaveOpts, objc - firstOptionArg,
			objv + firstOptionArg, pwPtr->tkwin, NULL, NULL);
		found = 1;

		/*
		 * If the slave is supposed to move, add it to the inserts
		 * array now; otherwise, leave it where it is.
		 */

		if (index != -1) {
		    inserts[insertIndex++] = pwPtr->slaves[j];
		    pwPtr->slaves[j] = NULL;
		}
		break;
	    }
	}

	if (found) {
	    continue;
	}

	/*
	 * Make sure this slave wasn't already put into the inserts array,
	 * ie, when the user specifies the same window multiple times in
	 * a single add commaned.
	 */
	for (j = 0; j < insertIndex; j++) {
	    if (inserts[j]->tkwin == tkwin) {
		found = 1;
		break;
	    }
	}
	if (found) {
	    continue;
	}
	
	/*
	 * Create a new slave structure and initialize it.  All slaves
	 * start out with their "natural" dimensions.
	 */
	
	slavePtr = (Slave *) ckalloc(sizeof(Slave));
	memset(slavePtr, 0, sizeof(Slave));
	Tk_InitOptions(interp, (char *)slavePtr, pwPtr->slaveOpts,
		pwPtr->tkwin);
	Tk_SetOptions(interp, (char *)slavePtr, pwPtr->slaveOpts,
		objc - firstOptionArg, objv + firstOptionArg,
		pwPtr->tkwin, NULL, NULL);
	slavePtr->tkwin		= tkwin;
	slavePtr->masterPtr	= pwPtr;
	doubleBw = 2 * Tk_Changes(slavePtr->tkwin)->border_width;
	if (slavePtr->width > 0) {
	    slavePtr->paneWidth = slavePtr->width;
	} else {
	    slavePtr->paneWidth = Tk_ReqWidth(tkwin) + doubleBw;
	}
	if (slavePtr->height > 0) {
	    slavePtr->paneHeight = slavePtr->height;
	} else {
	    slavePtr->paneHeight = Tk_ReqHeight(tkwin) + doubleBw;
	}

	/*
	 * Set up the geometry management callbacks for this slave.
	 */
	
	Tk_CreateEventHandler(slavePtr->tkwin, StructureNotifyMask,
		SlaveStructureProc, (ClientData) slavePtr);
	Tk_ManageGeometry(slavePtr->tkwin, &panedWindowMgrType,
		(ClientData) slavePtr);
	inserts[insertIndex++] = slavePtr;
	numNewSlaves++;
    }

    /*
     * Allocate the new slaves array, then copy the slaves into it, in
     * order.
     */
    i = sizeof(Slave *) * (pwPtr->numSlaves+numNewSlaves);
    new = (Slave **)ckalloc((unsigned) i);
    memset(new, 0, (size_t) i);
    if (index == -1) {
	/*
	 * If none of the existing slaves have to be moved, just copy the old
	 * and append the new.
	 */
	memcpy((void *)&(new[0]), pwPtr->slaves,
		sizeof(Slave *) * pwPtr->numSlaves);
	memcpy((void *)&(new[pwPtr->numSlaves]), inserts,
		sizeof(Slave *) * numNewSlaves);
    } else {
	/*
	 * If some of the existing slaves were moved, the old slaves array
	 * will be partially populated, with some valid and some invalid
	 * entries.  Walk through it, copying valid entries to the new slaves
	 * array as we go; when we get to the insert location for the new
	 * slaves, copy the inserts array over, then finish off the old slaves
	 * array.
	 */
	for (i = 0, j = 0; i < index; i++) {
	    if (pwPtr->slaves[i] != NULL) {
		new[j] = pwPtr->slaves[i];
		j++;
	    }
	}
	
	memcpy((void *)&(new[j]), inserts, sizeof(Slave *) * (insertIndex));
	j += firstOptionArg - 2;
	
	for (i = index; i < pwPtr->numSlaves; i++) {
	    if (pwPtr->slaves[i] != NULL) {
		new[j] = pwPtr->slaves[i];
		j++;
	    }
	}
    }

    /*
     * Make the new slaves array the paned window's slave array, and clean up.
     */
    ckfree((void *)pwPtr->slaves);
    ckfree((void *)inserts);
    pwPtr->slaves = new;

    /*
     * Set the paned window's slave count to the new value.
     */
    pwPtr->numSlaves += numNewSlaves;

    Tk_FreeConfigOptions((char *) &options, pwPtr->slaveOpts, pwPtr->tkwin);
    
    ComputeGeometry(pwPtr);
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * PanedWindowSashCommand --
 *
 *	Implementation of the panedwindow sash subcommand.  See the user
 *	documentation for details on what it does.
 *
 * Results:
 *	Standard Tcl result.
 *
 * Side effects:
 *	Depends on the arguments.
 *
 *----------------------------------------------------------------------
 */

static int
PanedWindowSashCommand(pwPtr, interp, objc, objv)
    PanedWindow *pwPtr;		/* Pointer to paned window information. */
    Tcl_Interp *interp;		/* Current interpreter. */
    int objc;			/* Number of arguments. */
    Tcl_Obj * CONST objv[];	/* Argument objects. */
{
    static CONST char *sashOptionStrings[] = { "coord", "dragto", "mark",
					     "place", (char *) NULL };
    enum sashOptions { SASH_COORD, SASH_DRAGTO, SASH_MARK, SASH_PLACE };
    int index, sash, x, y, diff;
    Tcl_Obj *coords[2];
    Slave *slavePtr;
    
    if (objc < 3) {
	Tcl_WrongNumArgs(interp, 2, objv, "option ?arg ...?");
	return TCL_ERROR;
    }

    if (Tcl_GetIndexFromObj(interp, objv[2], sashOptionStrings,
	    "option", 0, &index) != TCL_OK) {
	return TCL_ERROR;
    }

    Tcl_ResetResult(interp);
    switch ((enum sashOptions) index) {
	case SASH_COORD: {
	    if (objc != 4) {
		Tcl_WrongNumArgs(interp, 3, objv, "index");
		return TCL_ERROR;
	    }

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

	    if (!ValidSashIndex(pwPtr, sash)) {
		Tcl_ResetResult(interp);
		Tcl_SetResult(interp, "invalid sash index", TCL_STATIC);
		return TCL_ERROR;
	    }
	    slavePtr = pwPtr->slaves[sash];
	    
	    coords[0] = Tcl_NewIntObj(slavePtr->sashx);
	    coords[1] = Tcl_NewIntObj(slavePtr->sashy);
	    Tcl_SetListObj(Tcl_GetObjResult(interp), 2, coords);
	    break;
	}

	case SASH_MARK: {
	    if (objc != 6 && objc != 4) {
		Tcl_WrongNumArgs(interp, 3, objv, "index ?x y?");
		return TCL_ERROR;
	    }
	    
	    if (Tcl_GetIntFromObj(interp, objv[3], &sash) != TCL_OK) {
		return TCL_ERROR;
	    }

	    if (!ValidSashIndex(pwPtr, sash)) {
		Tcl_ResetResult(interp);
		Tcl_SetResult(interp, "invalid sash index", TCL_STATIC);
		return TCL_ERROR;
	    }

	    if (objc == 6) {
		if (Tcl_GetIntFromObj(interp, objv[4], &x) != TCL_OK) {
		    return TCL_ERROR;
		}
		
		if (Tcl_GetIntFromObj(interp, objv[5], &y) != TCL_OK) {
		    return TCL_ERROR;
		}

		pwPtr->slaves[sash]->markx = x;
		pwPtr->slaves[sash]->marky = y;
	    } else {
		coords[0] = Tcl_NewIntObj(pwPtr->slaves[sash]->markx);
		coords[1] = Tcl_NewIntObj(pwPtr->slaves[sash]->marky);
		Tcl_SetListObj(Tcl_GetObjResult(interp), 2, coords);
	    }

	    break;
	}
	
	case SASH_DRAGTO:
	case SASH_PLACE: {
	    if (objc != 6) {
		Tcl_WrongNumArgs(interp, 3, objv, "index x y");
		return TCL_ERROR;
	    }
	    
	    if (Tcl_GetIntFromObj(interp, objv[3], &sash) != TCL_OK) {
		return TCL_ERROR;
	    }

	    if (!ValidSashIndex(pwPtr, sash)) {
		Tcl_ResetResult(interp);
		Tcl_SetResult(interp, "invalid sash index", TCL_STATIC);
		return TCL_ERROR;
	    }

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

	    if (Tcl_GetIntFromObj(interp, objv[5], &y) != TCL_OK) {
		return TCL_ERROR;
	    }
	    
	    slavePtr = pwPtr->slaves[sash];
	    if (pwPtr->orient == ORIENT_HORIZONTAL) {
		if (index == SASH_PLACE) {
		    diff = x - pwPtr->slaves[sash]->sashx;
		} else {
		    diff = x - pwPtr->slaves[sash]->markx;
		}
	    } else {
		if (index == SASH_PLACE) {
		    diff = y - pwPtr->slaves[sash]->sashy;
		} else {
		    diff = y - pwPtr->slaves[sash]->marky;
		}
	    }

	    MoveSash(pwPtr, sash, diff);
	    ComputeGeometry(pwPtr);
	}
    }
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * ConfigurePanedWindow --
 *
 *	This procedure is called to process an argv/argc list in
 *	conjunction with the Tk option database to configure (or
 *	reconfigure) a paned window widget.
 *
 * Results:
 *	The return value is a standard Tcl result.  If TCL_ERROR is
 *	returned, then the interp's result contains an error message.
 *
 * Side effects:
 *	Configuration information, such as colors, border width,
 *	etc. get set for pwPtr;  old resources get freed,
 *	if there were any.
 *
 *----------------------------------------------------------------------
 */

static int
ConfigurePanedWindow(interp, pwPtr, objc, objv)
    Tcl_Interp *interp;		/* Used for error reporting. */
    PanedWindow *pwPtr;		/* Information about widget. */
    int objc;			/* Number of arguments. */
    Tcl_Obj *CONST objv[];	/* Argument values. */
{
    Tk_SavedOptions savedOptions;
    int typemask = 0;
    
    if (Tk_SetOptions(interp, (char *) pwPtr, pwPtr->optionTable, objc, objv,
	    pwPtr->tkwin, &savedOptions, &typemask) != TCL_OK) {
	Tk_RestoreSavedOptions(&savedOptions);
	return TCL_ERROR;
    }

    Tk_FreeSavedOptions(&savedOptions);

    PanedWindowWorldChanged((ClientData) pwPtr);

    /*
     * If an option that affects geometry has changed, make a relayout
     * request.
     */

    if (typemask & GEOMETRY) {
	ComputeGeometry(pwPtr);
    }
    
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * PanedWindowWorldChanged --
 *
 *	This procedure is invoked anytime a paned window's world has
 *	changed in some way that causes the widget to have to recompute
 *	graphics contexts and geometry.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Paned window will be relayed out and redisplayed.
 *
 *----------------------------------------------------------------------
 */

void
PanedWindowWorldChanged(instanceData)
    ClientData instanceData;	/* Information about the paned window. */
{
    XGCValues gcValues;
    GC newGC;
    PanedWindow *pwPtr = (PanedWindow *) instanceData;

    /*
     * Allocated a graphics context for drawing the paned window widget
     * elements (background, sashes, etc.).
     */
    
    gcValues.background = Tk_3DBorderColor(pwPtr->background)->pixel;
    newGC = Tk_GetGC(pwPtr->tkwin, GCBackground, &gcValues);
    if (pwPtr->gc != None) {
	Tk_FreeGC(pwPtr->display, pwPtr->gc);
    }
    pwPtr->gc = newGC;

    /*
     * Issue geometry size requests to Tk.
     */
    
    Tk_SetInternalBorder(pwPtr->tkwin, pwPtr->borderWidth);
    if (pwPtr->width > 0 || pwPtr->height > 0) {
	Tk_GeometryRequest(pwPtr->tkwin, pwPtr->width, pwPtr->height);
    }

    /*
     * Arrange for the window to be redrawn, if neccessary.
     */

    if (Tk_IsMapped(pwPtr->tkwin) && !(pwPtr->flags & REDRAW_PENDING)) {
	Tcl_DoWhenIdle(DisplayPanedWindow, (ClientData) pwPtr);
	pwPtr->flags |= REDRAW_PENDING;
    }
}

/*
 *--------------------------------------------------------------
 *
 * PanedWindowEventProc --
 *
 *	This procedure is invoked by the Tk dispatcher for various
 *	events on paned windows.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	When the window gets deleted, internal structures get
 *	cleaned up.  When it gets exposed, it is redisplayed.
 *
 *--------------------------------------------------------------
 */

static void
PanedWindowEventProc(clientData, eventPtr)
    ClientData clientData;	/* Information about window. */
    XEvent *eventPtr;		/* Information about event. */
{
    PanedWindow *pwPtr = (PanedWindow *) clientData;

    if (eventPtr->type == Expose) {
	if (pwPtr->tkwin != NULL && !(pwPtr->flags & REDRAW_PENDING)) {
	    Tcl_DoWhenIdle(DisplayPanedWindow, (ClientData) pwPtr);
	    pwPtr->flags |= REDRAW_PENDING;
	}
    } else if (eventPtr->type == ConfigureNotify) {
	pwPtr->flags |= REQUESTED_RELAYOUT;
	if (pwPtr->tkwin != NULL && !(pwPtr->flags & REDRAW_PENDING)) {
	    Tcl_DoWhenIdle(DisplayPanedWindow, (ClientData) pwPtr);
	    pwPtr->flags |= REDRAW_PENDING;
	}
    } else if (eventPtr->type == DestroyNotify) {
	DestroyPanedWindow(pwPtr);
    }
}

/*
 *----------------------------------------------------------------------
 *
 * PanedWindowCmdDeletedProc --
 *
 *	This procedure is invoked when a widget command is deleted.  If
 *	the widget isn't already in the process of being destroyed,
 *	this command destroys it.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	The widget is destroyed.
 *
 *----------------------------------------------------------------------
 */

static void
PanedWindowCmdDeletedProc(clientData)
    ClientData clientData;	/* Pointer to widget record for widget. */
{
    PanedWindow *pwPtr = (PanedWindow *) clientData;

    /*
     * This procedure could be invoked either because the window was
     * destroyed and the command was then deleted or because the command was
     * deleted, and then this procedure destroys the widget.  The
     * WIDGET_DELETED flag distinguishes these cases.
     */

    if (!(pwPtr->flags & WIDGET_DELETED)) {
	Tk_DestroyWindow(pwPtr->proxywin);
	Tk_DestroyWindow(pwPtr->tkwin);
    }
}

/*
 *--------------------------------------------------------------
 *
 * DisplayPanedWindow --
 *
 *	This procedure redraws the contents of a paned window widget.
 *	It is invoked as a do-when-idle handler, so it only runs
 *	when there's nothing else for the application to do.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Information appears on the screen.
 *
 *--------------------------------------------------------------
 */

static void
DisplayPanedWindow(clientData)
    ClientData clientData;	/* Information about window. */
{
    PanedWindow *pwPtr = (PanedWindow *) clientData;
    Pixmap pixmap;
    Tk_Window tkwin = pwPtr->tkwin;
    int i, sashWidth, sashHeight;
    
    pwPtr->flags &= ~REDRAW_PENDING;
    if ((pwPtr->tkwin == NULL) || !Tk_IsMapped(tkwin)) {
	return;
    }
    
    if (pwPtr->flags & REQUESTED_RELAYOUT) {
	ArrangePanes(clientData);
    }

    /*
     * Create a pixmap for double-buffering, if necessary.
     */

    pixmap = Tk_GetPixmap(Tk_Display(tkwin), Tk_WindowId(tkwin),
	    Tk_Width(tkwin), Tk_Height(tkwin), Tk_Depth(tkwin));

    /*
     * Redraw the widget's background and border.
     */
    Tk_Fill3DRectangle(tkwin, pixmap, pwPtr->background, 0, 0,
	    Tk_Width(tkwin), Tk_Height(tkwin), pwPtr->borderWidth,
	    pwPtr->relief);
    
    /*
     * Set up boilerplate geometry values for sashes (width, height, common
     * coordinates).
     */

    if (pwPtr->orient == ORIENT_HORIZONTAL) {
	sashHeight = Tk_Height(tkwin) - (2 * Tk_InternalBorderWidth(tkwin));
	sashWidth = pwPtr->sashWidth;
    } else {
	sashWidth = Tk_Width(tkwin) - (2 * Tk_InternalBorderWidth(tkwin));
	sashHeight = pwPtr->sashWidth;
    }

    /*
     * Draw the sashes.
     */
    for (i = 0; i < pwPtr->numSlaves - 1; i++) {
	Tk_Fill3DRectangle(tkwin, pixmap, pwPtr->background,
		pwPtr->slaves[i]->sashx, pwPtr->slaves[i]->sashy,
		sashWidth, sashHeight, 1, pwPtr->sashRelief);

	if (pwPtr->showHandle) {
	    Tk_Fill3DRectangle(tkwin, pixmap, pwPtr->background,
		    pwPtr->slaves[i]->handlex, pwPtr->slaves[i]->handley,
		    pwPtr->handleSize, pwPtr->handleSize, 1,
		    TK_RELIEF_RAISED);
	}
    }
    
    /*
     * Copy the information from the off-screen pixmap onto the screen,
     * then delete the pixmap.
     */
    
    XCopyArea(Tk_Display(tkwin), pixmap, Tk_WindowId(tkwin), pwPtr->gc,
	    0, 0, (unsigned) Tk_Width(tkwin), (unsigned) Tk_Height(tkwin),
	    0, 0);
    Tk_FreePixmap(Tk_Display(tkwin), pixmap);
}

/*
 *----------------------------------------------------------------------
 *
 * DestroyPanedWindow --
 *
 *	This procedure is invoked by PanedWindowEventProc to free the
 *	internal structure of a paned window.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Everything associated with the paned window is freed up.
 *
 *----------------------------------------------------------------------
 */

static void
DestroyPanedWindow(pwPtr)
    PanedWindow *pwPtr;		/* Info about paned window widget. */
{
    int i;
    
    /*
     * First mark the widget as in the process of being deleted,
     * so that any code that causes calls to other paned window procedures
     * will abort.
     */

    pwPtr->flags |= WIDGET_DELETED;

    /*
     * Cancel idle callbacks for redrawing the widget and for rearranging
     * the panes.
     */
    if (pwPtr->flags & REDRAW_PENDING) {
	Tcl_CancelIdleCall(DisplayPanedWindow, (ClientData) pwPtr);
    }
    if (pwPtr->flags & RESIZE_PENDING) {
	Tcl_CancelIdleCall(ArrangePanes, (ClientData) pwPtr);
    }

    /*
     * Clean up the slave list; foreach slave:
     *  o  Cancel the slave's structure notification callback
     *  o  Cancel geometry management for the slave.
     *  o  Free memory for the slave
     */
    
    for (i = 0; i < pwPtr->numSlaves; i++) {
	Tk_DeleteEventHandler(pwPtr->slaves[i]->tkwin, StructureNotifyMask,
		SlaveStructureProc, (ClientData) pwPtr->slaves[i]);
	Tk_ManageGeometry(pwPtr->slaves[i]->tkwin, NULL, NULL);
	Tk_FreeConfigOptions((char *)pwPtr->slaves[i], pwPtr->slaveOpts,
		pwPtr->tkwin);
	ckfree((void *)pwPtr->slaves[i]);
	pwPtr->slaves[i] = NULL;
    }
    if (pwPtr->slaves) {
	ckfree((char *) pwPtr->slaves);
    }

    /*
     * Remove the widget command from the interpreter.
     */

    Tcl_DeleteCommandFromToken(pwPtr->interp, pwPtr->widgetCmd);

    /*
     * Let Tk_FreeConfigOptions clean up the rest.
     */

    Tk_FreeConfigOptions((char *) pwPtr, pwPtr->optionTable, pwPtr->tkwin);
    Tcl_Release((ClientData) pwPtr->tkwin);
    pwPtr->tkwin = NULL;

    Tcl_EventuallyFree((ClientData) pwPtr, TCL_DYNAMIC);
}

/*
 *--------------------------------------------------------------
 *
 * PanedWindowReqProc --
 *
 *	This procedure is invoked by Tk_GeometryRequest for
 *	windows managed by a paned window.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Arranges for tkwin, and all its managed siblings, to
 *	be re-arranged at the next idle point.
 *
 *--------------------------------------------------------------
 */

static void
PanedWindowReqProc(clientData, tkwin)
    ClientData clientData;	/* Paned window's information about
				 * window that got new preferred
				 * geometry.  */
    Tk_Window tkwin;		/* Other Tk-related information
				 * about the window. */
{
    Slave *panePtr = (Slave *) clientData;
    PanedWindow *pwPtr = (PanedWindow *) (panePtr->masterPtr);
    if (Tk_IsMapped(pwPtr->tkwin)) {
	if (!(pwPtr->flags & RESIZE_PENDING)) {
	    pwPtr->flags |= RESIZE_PENDING;
	    Tcl_DoWhenIdle(ArrangePanes, (ClientData) pwPtr);
	}
    } else {
	ComputeGeometry(pwPtr);
    }
}

/*
 *--------------------------------------------------------------
 *
 * PanedWindowLostSlaveProc --
 *
 *	This procedure is invoked by Tk whenever some other geometry
 *	claims control over a slave that used to be managed by us.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Forgets all information about the slave.  Causes geometry to
 *	be recomputed for the panedwindow.
 *
 *--------------------------------------------------------------
 */

static void
PanedWindowLostSlaveProc(clientData, tkwin)
    ClientData clientData;	/* Grid structure for slave window that
				 * was stolen away. */
    Tk_Window tkwin;		/* Tk's handle for the slave window. */
{
    register Slave *slavePtr = (Slave *) clientData;
    PanedWindow *pwPtr = (PanedWindow *) (slavePtr->masterPtr);
    if (pwPtr->tkwin != Tk_Parent(slavePtr->tkwin)) {
	Tk_UnmaintainGeometry(slavePtr->tkwin, pwPtr->tkwin);
    }
    Unlink(slavePtr);
    Tk_DeleteEventHandler(slavePtr->tkwin, StructureNotifyMask,
	    SlaveStructureProc, (ClientData) slavePtr);
    Tk_UnmapWindow(slavePtr->tkwin);
    slavePtr->tkwin = NULL;
    ckfree((void *)slavePtr);
    ComputeGeometry(pwPtr);
}

/*
 *--------------------------------------------------------------
 *
 * ArrangePanes --
 *
 *	This procedure is invoked (using the Tcl_DoWhenIdle
 *	mechanism) to re-layout a set of windows managed by
 *	a paned window.  It is invoked at idle time so that a
 *	series of pane requests can be merged into a single
 *	layout operation.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	The slaves of masterPtr may get resized or moved.
 *
 *--------------------------------------------------------------
 */

static void
ArrangePanes(clientData)
    ClientData clientData;	/* Structure describing parent whose slaves
				 * are to be re-layed out. */
{
    register PanedWindow *pwPtr = (PanedWindow *) clientData;
    register Slave *slavePtr;	
    int i, slaveWidth, slaveHeight, slaveX, slaveY, paneWidth, paneHeight;
    int doubleBw;
    
    pwPtr->flags &= ~(REQUESTED_RELAYOUT|RESIZE_PENDING);

    /*
     * If the parent has no slaves anymore, then don't do anything
     * at all:  just leave the parent's size as-is.  Otherwise there is
     * no way to "relinquish" control over the parent so another geometry
     * manager can take over.
     */

    if (pwPtr->numSlaves == 0) {
	return;
    }

    Tcl_Preserve((ClientData) pwPtr);
    for (i = 0; i < pwPtr->numSlaves; i++) {
	slavePtr = pwPtr->slaves[i];

	/*
	 * Compute the size of this slave.  The algorithm (assuming a
	 * horizontal paned window) is:
	 *
	 * 1.  Get "base" dimensions.  If a width or height is specified
	 *	for this slave, use those values; else use the
	 *	ReqWidth/ReqHeight.
	 * 2.  Using base dimensions, pane dimensions, and sticky values,
	 *	determine the x and y, and actual width and height of the
	 *	widget.
	 */
	
	doubleBw = 2 * Tk_Changes(slavePtr->tkwin)->border_width;
	slaveWidth = (slavePtr->width > 0 ? slavePtr->width :
		Tk_ReqWidth(slavePtr->tkwin) + doubleBw);
	slaveHeight = (slavePtr->height > 0 ? slavePtr->height :
		Tk_ReqHeight(slavePtr->tkwin) + doubleBw);

	if (pwPtr->orient == ORIENT_HORIZONTAL) {
	    paneWidth = slavePtr->paneWidth;
	    if (i == pwPtr->numSlaves - 1 && Tk_IsMapped(pwPtr->tkwin)) {
		if (Tk_Width(pwPtr->tkwin) > Tk_ReqWidth(pwPtr->tkwin)) {
		    paneWidth += Tk_Width(pwPtr->tkwin) -
			Tk_ReqWidth(pwPtr->tkwin);
		}
	    }
	    paneHeight = Tk_Height(pwPtr->tkwin) - (2 * slavePtr->pady) -
		(2 * Tk_InternalBorderWidth(pwPtr->tkwin));
	} else {
	    paneHeight = slavePtr->paneHeight;
	    if (i == pwPtr->numSlaves - 1 && Tk_IsMapped(pwPtr->tkwin)) {
		if (Tk_Height(pwPtr->tkwin) > Tk_ReqHeight(pwPtr->tkwin)) {
		    paneHeight += Tk_Height(pwPtr->tkwin) -
			Tk_ReqHeight(pwPtr->tkwin);
		}
	    }
	    paneWidth = Tk_Width(pwPtr->tkwin) - (2 * slavePtr->padx) -
		(2 * Tk_InternalBorderWidth(pwPtr->tkwin));
	}
	
	if (slaveWidth > paneWidth) {
	    slaveWidth = paneWidth;
	}
	if (slaveHeight > paneHeight) {
	    slaveHeight = paneHeight;
	}

	slaveX = slavePtr->x;
	slaveY = slavePtr->y;
	AdjustForSticky(slavePtr->sticky, paneWidth, paneHeight,
		&slaveX, &slaveY, &slaveWidth, &slaveHeight);

	slaveX += slavePtr->padx;
	slaveY += slavePtr->pady;

	/*
	 * Now put the window in the proper spot.
	 */
	if ((slaveWidth <= 0) || (slaveHeight <= 0)) {
	    Tk_UnmaintainGeometry(slavePtr->tkwin, pwPtr->tkwin);
	    Tk_UnmapWindow(slavePtr->tkwin);
	} else {
	    Tk_MaintainGeometry(slavePtr->tkwin, pwPtr->tkwin,
		    slaveX, slaveY, slaveWidth, slaveHeight);
	}
    }
    Tcl_Release((ClientData) pwPtr);
}

/*
 *----------------------------------------------------------------------
 *
 * Unlink --
 *
 *	Remove a slave from a paned window.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	The paned window will be scheduled for re-arranging and redrawing.
 *
 *----------------------------------------------------------------------
 */

static void
Unlink(slavePtr)
    register Slave *slavePtr;		/* Window to unlink. */
{
    register PanedWindow *masterPtr;
    int i, j;
    
    masterPtr = slavePtr->masterPtr;
    if (masterPtr == NULL) {
	return;
    }

    /*
     * Find the specified slave in the panedwindow's list of slaves, then
     * remove it from that list.
     */

    for (i = 0; i < masterPtr->numSlaves; i++) {
	if (masterPtr->slaves[i] == slavePtr) {
	    for (j = i; j < masterPtr->numSlaves - 1; j++) {
		masterPtr->slaves[j] = masterPtr->slaves[j + 1];
	    }
	    break;
	}
    }

    masterPtr->flags |= REQUESTED_RELAYOUT;
    if (!(masterPtr->flags & REDRAW_PENDING)) {
	masterPtr->flags |= REDRAW_PENDING;
	Tcl_DoWhenIdle(DisplayPanedWindow, (ClientData) masterPtr);
    }

    /*
     * Set the slave's masterPtr to NULL, so that we can tell that the
     * slave is no longer attached to any panedwindow.
     */
    slavePtr->masterPtr = NULL;

    masterPtr->numSlaves--;
}

/*
 *----------------------------------------------------------------------
 *
 * GetPane --
 *
 *	Given a token to a Tk window, find the pane that corresponds to
 *	that token in a given paned window.
 *
 * Results:
 *	Pointer to the slave structure, or NULL if the window is not
 *	managed by this paned window.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

static Slave *
GetPane(pwPtr, tkwin)
    PanedWindow *pwPtr;		/* Pointer to the paned window info. */
    Tk_Window tkwin;		/* Window to search for. */
{
    int i;
    for (i = 0; i < pwPtr->numSlaves; i++) {
	if (pwPtr->slaves[i]->tkwin == tkwin) {
	    return pwPtr->slaves[i];
	}
    }
    return NULL;
}

/*
 *--------------------------------------------------------------
 *
 * SlaveStructureProc --
 *
 *	This procedure is invoked whenever StructureNotify events
 *	occur for a window that's managed by a paned window.  This
 *	procedure's only purpose is to clean up when windows are
 *	deleted.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	The paned window slave structure associated with the window
 *	is freed, and the slave is disassociated from the paned
 *	window which managed it.
 *
 *--------------------------------------------------------------
 */

static void
SlaveStructureProc(clientData, eventPtr)
    ClientData clientData;	/* Pointer to record describing window item. */
    XEvent *eventPtr;		/* Describes what just happened. */
{
    Slave *slavePtr = (Slave *) clientData;
    PanedWindow *pwPtr = slavePtr->masterPtr;
    
    if (eventPtr->type == DestroyNotify) {
	Unlink(slavePtr);
	slavePtr->tkwin = NULL;
	ckfree((void *)slavePtr);
	ComputeGeometry(pwPtr);
    }
}

/*
 *----------------------------------------------------------------------
 *
 * ComputeGeometry --
 *
 *	Compute geometry for the paned window, including coordinates of
 *	all slave windows and each sash.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Recomputes geometry information for a paned window.
 *
 *----------------------------------------------------------------------
 */

static void
ComputeGeometry(pwPtr)
    PanedWindow *pwPtr;		/* Pointer to the Paned Window structure. */
{
    int i, x, y, doubleBw, internalBw;
    int reqWidth, reqHeight, sashWidth, sxOff, syOff, hxOff, hyOff, dim;
    Slave *slavePtr;

    pwPtr->flags |= REQUESTED_RELAYOUT;

    x = y = internalBw = Tk_InternalBorderWidth(pwPtr->tkwin);
    reqWidth = reqHeight = 0;
    
    /*
     * Sashes and handles share space on the display.  To simplify
     * processing below, precompute the x and y offsets of the handles and
     * sashes within the space occupied by their combination; later, just add
     * those offsets blindly (avoiding the extra showHandle, etc, checks).
     */
    sxOff = syOff = hxOff = hyOff = 0;
    if (pwPtr->showHandle && pwPtr->handleSize > pwPtr->sashWidth) {
	sashWidth = pwPtr->handleSize;
	if (pwPtr->orient == ORIENT_HORIZONTAL) {
	    sxOff = (pwPtr->handleSize - pwPtr->sashWidth) / 2;
	    hyOff = pwPtr->handlePad;
	} else {
	    syOff = (pwPtr->handleSize - pwPtr->sashWidth) / 2;
	    hxOff = pwPtr->handlePad;
	}
    } else {
	sashWidth = pwPtr->sashWidth;
	if (pwPtr->orient == ORIENT_HORIZONTAL) {
	    hxOff = (pwPtr->handleSize - pwPtr->sashWidth) / 2;
	    hyOff = pwPtr->handlePad;
	} else {
	    hyOff = (pwPtr->handleSize - pwPtr->sashWidth) / 2;
	    hxOff = pwPtr->handlePad;
	}
    }
    
    for (i = 0; i < pwPtr->numSlaves; i++) {
	slavePtr = pwPtr->slaves[i];
	/*
	 * First set the coordinates for the top left corner of the slave's
	 * parcel.
	 */
	slavePtr->x = x;
	slavePtr->y = y;

	/*
	 * Make sure the pane's paned dimension is at least minsize.
	 * This check may be redundant, since the only way to change a pane's
	 * size is by moving a sash, and that code checks the minsize.
	 */
	if (pwPtr->orient == ORIENT_HORIZONTAL) {
	    if (slavePtr->paneWidth < slavePtr->minSize) {
		slavePtr->paneWidth = slavePtr->minSize;
	    }
	} else {
	    if (slavePtr->paneHeight < slavePtr->minSize) {
		slavePtr->paneHeight = slavePtr->minSize;
	    }
	}
	
	/*
	 * Compute the location of the sash at the right or bottom of the
	 * parcel.
	 */
	if (pwPtr->orient == ORIENT_HORIZONTAL) {
	    x += slavePtr->paneWidth + (2 * slavePtr->padx) + pwPtr->sashPad;
	} else {
	    y += slavePtr->paneHeight + (2 * slavePtr->pady) +  pwPtr->sashPad;
	}
	slavePtr->sashx		= x + sxOff;
	slavePtr->sashy		= y + syOff;
	slavePtr->handlex	= x + hxOff;
	slavePtr->handley	= y + hyOff;

	/*
	 * Compute the location of the next parcel.
	 */

	if (pwPtr->orient == ORIENT_HORIZONTAL) {
	    x += sashWidth + pwPtr->sashPad;
	} else {
	    y += sashWidth + pwPtr->sashPad;
	}
	
	/*
	 * Find the maximum height/width of the slaves, for computing the
	 * requested height/width of the paned window.
	 */
	if (pwPtr->orient == ORIENT_HORIZONTAL) {
	    /*
	     * If the slave has an explicit height set, use that; otherwise,
	     * use the slave's requested height.
	     */
	    if (slavePtr->height > 0) {
		dim = slavePtr->height;
	    } else {
	    	doubleBw = (2 * Tk_Changes(slavePtr->tkwin)->border_width);
		dim = Tk_ReqHeight(slavePtr->tkwin) + doubleBw;
	    }
	    dim += (2 * slavePtr->pady);
	    if (dim > reqHeight) {
		reqHeight = dim;
	    }
	} else {
	    /*
	     * If the slave has an explicit width set use that; otherwise,
	     * use the slave's requested width.
	     */
	    if (slavePtr->width > 0) {
		dim = slavePtr->width;
	    } else {
	    	doubleBw = (2 * Tk_Changes(slavePtr->tkwin)->border_width);
		dim = Tk_ReqWidth(slavePtr->tkwin) + doubleBw;
	    }
	    dim += (2 * slavePtr->padx);
	    if (dim > reqWidth) {
		reqWidth = dim;
	    }
	}
    }

    /*
     * The loop above should have left x (or y) equal to the sum of the
     * widths (or heights) of the widgets, plus the size of one sash and
     * the sash padding for each widget, plus the width of the left (or top)
     * border of the paned window.
     *
     * The requested width (or height) is therefore x (or y) minus the size of
     * one sash and padding, plus the width of the right (or bottom) border
     * of the paned window.
     *
     * The height (or width) is equal to the maximum height (or width) of
     * the slaves, plus the width of the border of the top and bottom (or left
     * and right) of the paned window.
     */
    if (pwPtr->orient == ORIENT_HORIZONTAL) {
	reqWidth	= x - (sashWidth + (2 * pwPtr->sashPad)) + internalBw;
	reqHeight	+= 2 * internalBw;
    } else {
	reqHeight	= y - (sashWidth + (2 * pwPtr->sashPad)) + internalBw;
	reqWidth	+= 2 * internalBw;
    }
    if (pwPtr->width <= 0 && pwPtr->height <= 0) {
	Tk_GeometryRequest(pwPtr->tkwin, reqWidth, reqHeight);
    }
    if (Tk_IsMapped(pwPtr->tkwin) && !(pwPtr->flags & REDRAW_PENDING)) {
	pwPtr->flags |= REDRAW_PENDING;
	Tcl_DoWhenIdle(DisplayPanedWindow, (ClientData) pwPtr);
    }
}

/*
 *----------------------------------------------------------------------
 *
 * DestroyOptionTables --
 *
 *	This procedure is registered as an exit callback when the paned window
 *	command is first called.  It cleans up the OptionTables structure
 *	allocated by that command.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Frees memory.
 *
 *----------------------------------------------------------------------
 */

static void
DestroyOptionTables(clientData, interp)
    ClientData clientData;	/* Pointer to the OptionTables struct */
    Tcl_Interp *interp;		/* Pointer to the calling interp */
{
    ckfree((char *)clientData);
    return;
}

/*
 *----------------------------------------------------------------------
 *
 * GetSticky -
 *
 *	Converts an internal boolean combination of "sticky" bits into a
 *	a Tcl string obj containing zero or mor of n, s, e, or w.
 *
 * Results:
 *	Tcl_Obj containing the string representation of the sticky value.
 *
 * Side effects:
 *	Creates a new Tcl_Obj.
 *
 *----------------------------------------------------------------------
 */

static Tcl_Obj *
GetSticky(clientData, tkwin, recordPtr, internalOffset)
    ClientData clientData;
    Tk_Window tkwin;
    char *recordPtr;		/* Pointer to widget record. */
    int internalOffset;		/* Offset within *recordPtr containing the
				 * sticky value. */
{
    int sticky = *(int *)(recordPtr + internalOffset);
    static char buffer[5];
    int count = 0;

    if (sticky & STICK_NORTH) {
    	buffer[count++] = 'n';
    }
    if (sticky & STICK_EAST) {
    	buffer[count++] = 'e';
    }
    if (sticky & STICK_SOUTH) {
    	buffer[count++] = 's';
    }
    if (sticky & STICK_WEST) {
    	buffer[count++] = 'w';
    }
    buffer[count] = '\0';

    return Tcl_NewStringObj(buffer, -1);
}

/*
 *----------------------------------------------------------------------
 *
 * SetSticky --
 *
 *	Converts a Tcl_Obj representing a widgets stickyness into an
 *	integer value.
 *
 * Results:
 *	Standard Tcl result.
 *
 * Side effects:
 *	May store the integer value into the internal representation
 *	pointer.  May change the pointer to the Tcl_Obj to NULL to indicate
 *	that the specified string was empty and that is acceptable.
 *
 *----------------------------------------------------------------------
 */

static int
SetSticky(clientData, interp, tkwin, value, recordPtr, internalOffset,
	oldInternalPtr, flags)
    ClientData clientData;
    Tcl_Interp *interp;		/* Current interp; may be used for errors. */
    Tk_Window tkwin;		/* Window for which option is being set. */
    Tcl_Obj **value;		/* Pointer to the pointer to the value object.
				 * We use a pointer to the pointer because
				 * we may need to return a value (NULL). */
    char *recordPtr;		/* Pointer to storage for the widget record. */
    int internalOffset;		/* Offset within *recordPtr at which the
				   internal value is to be stored. */
    char *oldInternalPtr;	/* Pointer to storage for the old value. */
    int flags;			/* Flags for the option, set Tk_SetOptions. */
{
    int sticky = 0;
    char c, *string, *internalPtr;

    internalPtr = ComputeSlotAddress(recordPtr, internalOffset);
    
    if (flags & TK_OPTION_NULL_OK && ObjectIsEmpty(*value)) {
	*value = NULL;
    } else {
	/*
	 * Convert the sticky specifier into an integer value.
	 */
	
	string = Tcl_GetString(*value);
    
	while ((c = *string++) != '\0') {
	    switch (c) {
		case 'n': case 'N': sticky |= STICK_NORTH; break;
		case 'e': case 'E': sticky |= STICK_EAST;  break;
		case 's': case 'S': sticky |= STICK_SOUTH; break;
		case 'w': case 'W': sticky |= STICK_WEST;  break;
		case ' ': case ',': case '\t': case '\r': case '\n': break;
		default: {
		    Tcl_ResetResult(interp);
		    Tcl_AppendResult(interp, "bad stickyness value \"",
			    Tcl_GetString(*value), "\": must be a string ",
			    "containing zero or more of n, e, s, and w",
			    (char *)NULL);
		    return TCL_ERROR;
		}
	    }
	}
    }

    if (internalPtr != NULL) {
	*((int *) oldInternalPtr) = *((int *) internalPtr);
	*((int *) internalPtr) = sticky;
    }
    return TCL_OK;
}		

/*
 *----------------------------------------------------------------------
 *
 * RestoreSticky --
 *
 *	Restore a sticky option value from a saved value.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Restores the old value.
 *
 *----------------------------------------------------------------------
 */

static void
RestoreSticky(clientData, tkwin, internalPtr, oldInternalPtr)
    ClientData clientData;
    Tk_Window tkwin;
    char *internalPtr;		/* Pointer to storage for value. */
    char *oldInternalPtr;	/* Pointer to old value. */
{
    *(int *)internalPtr = *(int *)oldInternalPtr;
}

/*
 *----------------------------------------------------------------------
 *
 * AdjustForSticky --
 *
 *	Given the x,y coords of the top-left corner of a pane, the
 *	dimensions of that pane, and the dimensions of a slave, compute
 *	the x,y coords and actual dimensions of the slave based on the slave's
 *	sticky value.
 *
 * Results:
 *	No direct return; sets the x, y, slaveWidth and slaveHeight to
 *	correct values.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

static void
AdjustForSticky(sticky, cavityWidth, cavityHeight, xPtr, yPtr,
	slaveWidthPtr, slaveHeightPtr)
    int sticky;		/* Sticky value; see top of file for definition. */
    int cavityWidth;	/* Width of the cavity. */
    int cavityHeight;	/* Height of the cavity. */
    int *xPtr, *yPtr;		/* Initially, coordinates of the top-left
				 * corner of cavity; also return values for
				 * actual x, y coords of slave. */
    int *slaveWidthPtr;		/* Slave width. */
    int *slaveHeightPtr;	/* Slave height. */
{
    int diffx=0;	/* Cavity width - slave width. */
    int diffy=0;	/* Cavity hight - slave height. */

    if (cavityWidth > *slaveWidthPtr) {
	diffx = cavityWidth - *slaveWidthPtr;
    }

    if (cavityHeight > *slaveHeightPtr) {
	diffy = cavityHeight - *slaveHeightPtr;
    }

    if ((sticky & STICK_EAST) && (sticky & STICK_WEST)) {
	*slaveWidthPtr += diffx;
    }
    if ((sticky & STICK_NORTH) && (sticky & STICK_SOUTH)) {
	*slaveHeightPtr += diffy;
    }
    if (!(sticky & STICK_WEST)) {
    	*xPtr += (sticky & STICK_EAST) ? diffx : diffx/2;
    }
    if (!(sticky & STICK_NORTH)) {
    	*yPtr += (sticky & STICK_SOUTH) ? diffy : diffy/2;
    }
}

/*
 *----------------------------------------------------------------------
 *
 * MoveSash --
 *
 *	Move the sash given by index the amount given.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Recomputes the sizes of the panes in a panedwindow.
 *
 *----------------------------------------------------------------------
 */

static void
MoveSash(pwPtr, sash, diff)
    PanedWindow *pwPtr;
    int sash;
    int diff;
{
    int diffConsumed = 0, i, extra, maxCoord, currCoord;
    int *lengthPtr, newLength;
    Slave *slave;
    
    if (diff > 0) {
	/*
	 * Growing the pane, at the expense of panes to the right.
	 */

	/*
	 * First check that moving the sash the requested distance will not
	 * leave it off the screen.  If necessary, clip the requested diff
	 * to the maximum possible while remaining visible.
	 */
	if (pwPtr->orient == ORIENT_HORIZONTAL) {
	    if (Tk_IsMapped(pwPtr->tkwin)) {
		maxCoord	= Tk_Width(pwPtr->tkwin);
	    } else {
		maxCoord	= Tk_ReqWidth(pwPtr->tkwin);
	    }
	    extra	= Tk_Width(pwPtr->tkwin) - Tk_ReqWidth(pwPtr->tkwin);
	    currCoord	= pwPtr->slaves[sash]->sashx;
	} else {
	    if (Tk_IsMapped(pwPtr->tkwin)) {
		maxCoord	= Tk_Height(pwPtr->tkwin);
	    } else {
		maxCoord	= Tk_ReqHeight(pwPtr->tkwin);
	    }
	    extra	= Tk_Height(pwPtr->tkwin) - Tk_ReqHeight(pwPtr->tkwin);
	    currCoord	= pwPtr->slaves[sash]->sashy;
	}

	maxCoord -= (pwPtr->borderWidth + pwPtr->sashWidth + pwPtr->sashPad);
	if (currCoord + diff >= maxCoord) {
	    diff = maxCoord - currCoord;
	}

	for (i = sash + 1; i < pwPtr->numSlaves; i++) {
	    if (diffConsumed == diff) {
		break;
	    }
	    slave = pwPtr->slaves[i];

	    if (pwPtr->orient == ORIENT_HORIZONTAL) {
		lengthPtr	= &(slave->paneWidth);
	    } else {
		lengthPtr	= &(slave->paneHeight);
	    }

	    /*
	     * Remove as much space from this pane as possible (constrained
	     * by the minsize value and the visible dimensions of the window).
	     */

	    if (i == pwPtr->numSlaves - 1 && extra > 0) {
		/*
		 * The last pane may have some additional "virtual" space,
		 * if the width (or height) of the paned window is bigger
		 * than the requested width (or height).
		 *
		 * That extra space is not included in the paneWidth
		 * (or paneHeight) value, so we have to handle the last
		 * pane specially.
		 */
		newLength = (*lengthPtr + extra) - (diff - diffConsumed);
		if (newLength < slave->minSize) {
		    newLength = slave->minSize;
		}
		if (newLength < 0) {
		    newLength = 0;
		}
		diffConsumed += (*lengthPtr + extra) - newLength;
		if (newLength < *lengthPtr) {
		    *lengthPtr = newLength;
		}
	    } else {
		newLength = *lengthPtr - (diff - diffConsumed);
		if (newLength < slave->minSize) {
		    newLength = slave->minSize;
		}
		if (newLength < 0) {
		    newLength = 0;
		}
		diffConsumed += *lengthPtr - newLength;
		*lengthPtr = newLength;
	    }
	}
	if (pwPtr->orient == ORIENT_HORIZONTAL) {
	    pwPtr->slaves[sash]->paneWidth += diffConsumed;
	} else {
	    pwPtr->slaves[sash]->paneHeight += diffConsumed;
	}
    } else if (diff < 0) {
	/*
	 * Shrinking the pane; additional space is given to the pane to the
	 * right.
	 */
	for (i = sash; i >= 0; i--) {
	    if (diffConsumed == diff) {
		break;
	    }
	    /*
	     * Remove as much space from this pane as possible.
	     */
	    slave = pwPtr->slaves[i];

	    if (pwPtr->orient == ORIENT_HORIZONTAL) {
		lengthPtr	= &(slave->paneWidth);
	    } else {
		lengthPtr	= &(slave->paneHeight);
	    }
	    
	    newLength = *lengthPtr + (diff - diffConsumed);
	    if (newLength < slave->minSize) {
		newLength = slave->minSize;
	    }
	    if (newLength < 0) {
		newLength = 0;
	    }
	    diffConsumed -= *lengthPtr - newLength;
	    *lengthPtr = newLength;
	}
	if (pwPtr->orient == ORIENT_HORIZONTAL) {
	    pwPtr->slaves[sash + 1]->paneWidth -= diffConsumed;
	} else {
	    pwPtr->slaves[sash + 1]->paneHeight -= diffConsumed;
	}
    }
    
}

/*
 *----------------------------------------------------------------------
 *
 * ProxyWindowEventProc --
 *
 *	This procedure is invoked by the Tk dispatcher for various
 *	events on paned window proxy windows.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	When the window gets deleted, internal structures get
 *	cleaned up.  When it gets exposed, it is redisplayed.
 *
 *--------------------------------------------------------------
 */

static void
ProxyWindowEventProc(clientData, eventPtr)
    ClientData clientData;	/* Information about window. */
    XEvent *eventPtr;		/* Information about event. */
{
    PanedWindow *pwPtr = (PanedWindow *) clientData;

    if (eventPtr->type == Expose) {
	if (pwPtr->proxywin != NULL &&!(pwPtr->flags & PROXY_REDRAW_PENDING)) {
	    Tcl_DoWhenIdle(DisplayProxyWindow, (ClientData) pwPtr);
	    pwPtr->flags |= PROXY_REDRAW_PENDING;
	}
    }
}

/*
 *--------------------------------------------------------------
 *
 * DisplayProxyWindow --
 *
 *	This procedure redraws a paned window proxy window.
 *	It is invoked as a do-when-idle handler, so it only runs
 *	when there's nothing else for the application to do.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Information appears on the screen.
 *
 *--------------------------------------------------------------
 */

static void
DisplayProxyWindow(clientData)
    ClientData clientData;	/* Information about window. */
{
    PanedWindow *pwPtr = (PanedWindow *) clientData;
    Pixmap pixmap;
    Tk_Window tkwin = pwPtr->proxywin;
    pwPtr->flags &= ~PROXY_REDRAW_PENDING;
    if ((tkwin == NULL) || !Tk_IsMapped(tkwin)) {
	return;
    }

    /*
     * Create a pixmap for double-buffering, if necessary.
     */

    pixmap = Tk_GetPixmap(Tk_Display(tkwin), Tk_WindowId(tkwin),
	    Tk_Width(tkwin), Tk_Height(tkwin), Tk_Depth(tkwin));

    /*
     * Redraw the widget's background and border.
     */
    Tk_Fill3DRectangle(tkwin, pixmap, pwPtr->background, 0, 0,
	    Tk_Width(tkwin), Tk_Height(tkwin), 2, pwPtr->sashRelief);
    
    /*
     * Copy the pixmap to the display.
     */
    XCopyArea(Tk_Display(tkwin), pixmap, Tk_WindowId(tkwin), pwPtr->gc,
	    0, 0, (unsigned) Tk_Width(tkwin), (unsigned) Tk_Height(tkwin),
	    0, 0);
    Tk_FreePixmap(Tk_Display(tkwin), pixmap);
}

/*
 *----------------------------------------------------------------------
 *
 * PanedWindowProxyCommand --
 *
 *	Handles the panedwindow proxy subcommand.  See the user
 *	documentation for details.
 *
 * Results:
 *	Standard Tcl result.
 *
 * Side effects:
 *	May map or unmap the proxy sash.
 *
 *----------------------------------------------------------------------
 */

static int
PanedWindowProxyCommand(pwPtr, interp, objc, objv)
    PanedWindow *pwPtr;		/* Pointer to paned window information. */
    Tcl_Interp *interp;		/* Current interpreter. */
    int objc;			/* Number of arguments. */
    Tcl_Obj * CONST objv[];	/* Argument objects. */
{
    static CONST char *optionStrings[] = { "coord", "forget", "place",
					 (char *) NULL };
    enum options { PROXY_COORD, PROXY_FORGET, PROXY_PLACE };
    int index, x, y, sashWidth, sashHeight;
    Tcl_Obj *coords[2];
    
    if (objc < 3) {
	Tcl_WrongNumArgs(interp, 2, objv, "option ?arg ...?");
	return TCL_ERROR;
    }

    if (Tcl_GetIndexFromObj(interp, objv[2], optionStrings, "option", 0,
	    &index) != TCL_OK) {
	return TCL_ERROR;
    }

    switch ((enum options) index) {
	case PROXY_COORD:
	    if (objc != 3) {
		Tcl_WrongNumArgs(interp, 3, objv, NULL);
		return TCL_ERROR;
	    }

	    coords[0] = Tcl_NewIntObj(pwPtr->proxyx);
	    coords[1] = Tcl_NewIntObj(pwPtr->proxyy);
	    Tcl_SetListObj(Tcl_GetObjResult(interp), 2, coords);
	    break;

	case PROXY_FORGET:
	    if (objc != 3) {
		Tcl_WrongNumArgs(interp, 3, objv, NULL);
		return TCL_ERROR;
	    }
	    if (Tk_IsMapped(pwPtr->proxywin)) {
		Tk_UnmapWindow(pwPtr->proxywin);
		Tk_UnmaintainGeometry(pwPtr->proxywin, pwPtr->tkwin);
	    }
	    break;

	case PROXY_PLACE: {
	    if (objc != 5) {
		Tcl_WrongNumArgs(interp, 3, objv, "x y");
		return TCL_ERROR;
	    }

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

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

	    if (pwPtr->orient == ORIENT_HORIZONTAL) {
		if (x < 0) {
		    x = 0;
		}
		y = Tk_InternalBorderWidth(pwPtr->tkwin);
		sashWidth = pwPtr->sashWidth;
		sashHeight = Tk_Height(pwPtr->tkwin) -
		    (2 * Tk_InternalBorderWidth(pwPtr->tkwin));
	    } else {
		if (y < 0) {
		    y = 0;
		}
		x = Tk_InternalBorderWidth(pwPtr->tkwin);
		sashHeight = pwPtr->sashWidth;
		sashWidth = Tk_Width(pwPtr->tkwin) -
		    (2 * Tk_InternalBorderWidth(pwPtr->tkwin));
	    }

	    /*
	     * Stash the proxy coordinates for future "proxy coord" calls.
	     */

	    pwPtr->proxyx = x;
	    pwPtr->proxyy = y;
	    
	    /*
	     * Make sure the proxy window is higher in the stacking order
	     * than the slaves, so that it will be visible when drawn.
	     * It would be more correct to push the proxy window just high
	     * enough to appear above the highest slave, but it's much easier
	     * to just force it all the way to the top of the stacking order.
	     */
	    
	    Tk_RestackWindow(pwPtr->proxywin, Above, NULL);
	    
	    /*
	     * Let Tk_MaintainGeometry take care of placing the window at
	     * the right coordinates.
	     */
	    Tk_MaintainGeometry(pwPtr->proxywin, pwPtr->tkwin,
		    x, y, sashWidth, sashHeight);
	    break;
	}
    }

    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * ObjectIsEmpty --
 *
 *	This procedure tests whether the string value of an object is
 *	empty.
 *
 * Results:
 *	The return value is 1 if the string value of objPtr has length
 *	zero, and 0 otherwise.
 *
 * Side effects:
 *	May cause object shimmering, since this function can force a
 *	conversion to a string object.
 *
 *----------------------------------------------------------------------
 */

static int
ObjectIsEmpty(objPtr)
    Tcl_Obj *objPtr;		/* Object to test.  May be NULL. */
{
    int length;

    if (objPtr == NULL) {
	return 1;
    }
    if (objPtr->bytes != NULL) {
	return (objPtr->length == 0);
    }
    Tcl_GetStringFromObj(objPtr, &length);
    return (length == 0);
}

/*
 *----------------------------------------------------------------------
 *
 * ComputeInternalPointer --
 *
 *	Given a pointer to the start of a record and the offset of a slot
 *	within that record, compute the address of that slot.
 *
 * Results:
 *	If offset is non-negative, returns the computed address; else,
 *	returns NULL.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

static char *
ComputeSlotAddress(recordPtr, offset)
    char *recordPtr;	/* Pointer to the start of a record. */
    int offset;		/* Offset of a slot within that record; may be < 0. */
{
    if (offset >= 0) {
	return recordPtr + offset;
    } else {
	return NULL;
    }
}

/*
 *----------------------------------------------------------------------
 *
 * PanedWindowIdentifyCoords --
 *
 *	Given a pair of x,y coordinates, identify the panedwindow component
 *	at that point, if any.
 *
 * Results:
 *	Standard Tcl result.
 *
 * Side effects:
 *	Modifies the interpreter's result to contain either an empty list,
 *	or a two element list of the form {sash n} or {handle n} to indicate
 *	that the point lies within the n'th sash or handle.
 *
 *----------------------------------------------------------------------
 */

static int
PanedWindowIdentifyCoords(pwPtr, interp, x, y)
    PanedWindow *pwPtr;		/* Information about the widget. */
    Tcl_Interp *interp;		/* Interpreter in which to store result. */
    int x, y;			/* Coordinates of the point to identify. */
{
    Tcl_Obj *list;
    int i, sashHeight, sashWidth, thisx, thisy;
    int found, isHandle, lpad, rpad, tpad, bpad;
    list = Tcl_NewObj();

    if (pwPtr->orient == ORIENT_HORIZONTAL) {
	if (Tk_IsMapped(pwPtr->tkwin)) {
	    sashHeight	= Tk_Height(pwPtr->tkwin);
	} else {
	    sashHeight	= Tk_ReqHeight(pwPtr->tkwin);
	}
	sashHeight	-= 2 * Tk_InternalBorderWidth(pwPtr->tkwin);
	if (pwPtr->showHandle && pwPtr->handleSize > pwPtr->sashWidth) {
	    sashWidth	= pwPtr->handleSize;
	    lpad	= (pwPtr->handleSize - pwPtr->sashWidth) / 2;
	    rpad	= pwPtr->handleSize - lpad;
	    lpad	+= pwPtr->sashPad;
	    rpad	+= pwPtr->sashPad;
	} else {
	    sashWidth	= pwPtr->sashWidth;
	    lpad = rpad = pwPtr->sashPad;
	}
	tpad = bpad	= 0;
    } else {
	if (pwPtr->showHandle && pwPtr->handleSize > pwPtr->sashWidth) {
	    sashHeight	= pwPtr->handleSize;
	    tpad	= (pwPtr->handleSize - pwPtr->sashWidth) / 2;
	    bpad	= pwPtr->handleSize - tpad;
	    tpad	+= pwPtr->sashPad;
	    bpad	+= pwPtr->sashPad;
	} else {
	    sashHeight	= pwPtr->sashWidth;
	    tpad = bpad = pwPtr->sashPad;
	}
	if (Tk_IsMapped(pwPtr->tkwin)) {
	    sashWidth	= Tk_Width(pwPtr->tkwin);
	} else {
	    sashWidth	= Tk_ReqWidth(pwPtr->tkwin);
	}
	sashWidth	-= 2 * Tk_InternalBorderWidth(pwPtr->tkwin);
	lpad = rpad	= 0;
    }
    
    isHandle = 0;
    found = -1;
    for (i = 0; i < pwPtr->numSlaves - 1; i++) {
	thisx = pwPtr->slaves[i]->sashx;
	thisy = pwPtr->slaves[i]->sashy;

	if (((thisx - lpad) <= x && x <= (thisx + rpad + sashWidth)) &&
		((thisy - tpad) <= y && y <= (thisy + bpad + sashHeight))) {
	    found = i;

	    /*
	     * Determine if the point is over the handle or the sash.
	     */
	    if (pwPtr->showHandle) {
		thisx = pwPtr->slaves[i]->handlex;
		thisy = pwPtr->slaves[i]->handley;
		if (pwPtr->orient == ORIENT_HORIZONTAL) {
		    if (thisy <= y && y <= (thisy + pwPtr->handleSize)) {
			isHandle = 1;
		    }
		} else {
		    if (thisx <= x && x <= (thisx + pwPtr->handleSize)) {
			isHandle = 1;
		    }
		}
	    }
	    break;
	}
    }

    /*
     * Set results.
     */
    if (found != -1) {
	Tcl_ListObjAppendElement(interp, list, Tcl_NewIntObj(found));
	if (isHandle) {
	    Tcl_ListObjAppendElement(interp, list,
		    Tcl_NewStringObj("handle", -1));
	} else {
	    Tcl_ListObjAppendElement(interp, list,
		    Tcl_NewStringObj("sash", -1));
	}
    }
    
    Tcl_SetObjResult(interp, list);
    return TCL_OK;
}
