Tk Source Code

Documentation
Login
/*
 * 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 © 1997 Sun Microsystems, Inc.
 * Copyright © 2000 Ajuba Solutions.
 *
 * See the file "license.terms" for information on usage and redistribution of
 * this file, and for a DISCLAIMER OF ALL WARRANTIES.
 */

#include "tkInt.h"
#include "default.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 const char *const orientStrings[] = {
    "horizontal", "vertical", NULL
};

enum orient { ORIENT_HORIZONTAL, ORIENT_VERTICAL };

/*
 * The following table defines the legal values for the -stretch option.
 */

static const char *const stretchStrings[] = {
    "always", "first", "last", "middle", "never", NULL
};

enum stretch {
    STRETCH_ALWAYS,		/* Always give extra space to this pane. */
    STRETCH_FIRST,		/* Give extra space to pane if it is first. */
    STRETCH_LAST,		/* Give extra space to pane if it is last. */
    STRETCH_MIDDLE,		/* Give extra space to pane only if it is
				 * neither first nor last. */
    STRETCH_NEVER		/* Never give extra space to this pane. */
};

/*
 * Codify the stretchiness rule in one place.
 */

#define IsStretchable(stretch,index,first,last)			\
    (((stretch) == STRETCH_ALWAYS) ||				\
     ((stretch) == STRETCH_FIRST && (index) == (first)) ||	\
     ((stretch) == STRETCH_LAST && (index) == (last)) ||	\
     ((stretch) == STRETCH_MIDDLE && (index) != (first) && (index) != (last)))

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

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

typedef struct Pane {
    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 pane, in
				 * the x dimension. */
    int pady;			/* Additional padding requested for pane, in
				 * the y dimension. */
    Tcl_Obj *widthPtr, *heightPtr;
				/* Tcl_Obj rep's of pane width/height, to
				 * allow for null values. */
    int width;			/* Pane width. */
    int height;			/* Pane height. */
    int sticky;			/* Sticky string. */
    int x, y;			/* Coordinates of the widget. */
    int paneWidth, paneHeight;	/* Pane dimensions (may be different from
				 * pane 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. */
    enum stretch stretch;	/* Controls how pane grows/shrinks */
    int hide;			/* Controls visibility of pane */
    struct PanedWindow *containerPtr;
				/* Paned window managing the window. */
    Tk_Window after;		/* Placeholder for parsing options. */
    Tk_Window before;		/* Placeholder for parsing options. */
} Pane;

/*
 * 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 paneOpts;	/* Token for pane 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. */
    Tk_3DBorder proxyBackground;/* Background color used to draw proxy. If NULL, use background. */
    Tcl_Obj *proxyBorderWidthPtr; /* Tcl_Obj rep for proxyBorderWidth */
    int proxyBorderWidth;	/* Borderwidth used to draw proxy. */
    int proxyRelief;		/* Relief used to draw proxy, if TK_RELIEF_NULL then use relief. */
    Pane **panes;		/* Pointer to array of Panes. */
    int numPanes;		/* Number of panes. */
    int sizeofPanes;		/* Number of elements in the panes 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 functions defined later in this file:
 */

int			Tk_PanedWindowObjCmd(void *clientData,
			    Tcl_Interp *interp, int objc,
			    Tcl_Obj *const objv[]);
static void		PanedWindowCmdDeletedProc(void *clientData);
static int		ConfigurePanedWindow(Tcl_Interp *interp,
			    PanedWindow *pwPtr, int objc,
			    Tcl_Obj *const objv[]);
static void		DestroyPanedWindow(PanedWindow *pwPtr);
static void		DisplayPanedWindow(void *clientData);
static void		PanedWindowEventProc(void *clientData,
			    XEvent *eventPtr);
static void		ProxyWindowEventProc(void *clientData,
			    XEvent *eventPtr);
static void		DisplayProxyWindow(void *clientData);
static void		PanedWindowWorldChanged(void *instanceData);
static int		PanedWindowWidgetObjCmd(void *clientData,
			    Tcl_Interp *, int objc, Tcl_Obj * const objv[]);
static void		PanedWindowLostPaneProc(void *clientData,
			    Tk_Window tkwin);
static void		PanedWindowReqProc(void *clientData,
			    Tk_Window tkwin);
static void		ArrangePanes(void *clientData);
static void		Unlink(Pane *panePtr);
static Pane *		GetPane(PanedWindow *pwPtr, Tk_Window tkwin);
static void		GetFirstLastVisiblePane(PanedWindow *pwPtr,
			    int *firstPtr, int *lastPtr);
static void		PaneStructureProc(void *clientData,
			    XEvent *eventPtr);
static int		PanedWindowSashCommand(PanedWindow *pwPtr,
			    Tcl_Interp *interp, int objc,
			    Tcl_Obj * const objv[]);
static int		PanedWindowProxyCommand(PanedWindow *pwPtr,
			    Tcl_Interp *interp, int objc,
			    Tcl_Obj * const objv[]);
static void		ComputeGeometry(PanedWindow *pwPtr);
static int		ConfigurePanes(PanedWindow *pwPtr,
			    Tcl_Interp *interp, int objc,
			    Tcl_Obj * const objv[]);
static void		DestroyOptionTables(void *clientData,
			    Tcl_Interp *interp);
static int		SetSticky(void *clientData, Tcl_Interp *interp,
			    Tk_Window tkwin, Tcl_Obj **value, char *recordPtr,
			    Tcl_Size internalOffset, char *oldInternalPtr,
			    int flags);
static Tcl_Obj *	GetSticky(void *clientData, Tk_Window tkwin,
			    char *recordPtr, Tcl_Size internalOffset);
static void		RestoreSticky(void *clientData, Tk_Window tkwin,
			    char *internalPtr, char *oldInternalPtr);
static void		AdjustForSticky(int sticky, int cavityWidth,
			    int cavityHeight, int *xPtr, int *yPtr,
			    int *paneWidthPtr, int *paneHeightPtr);
static void		MoveSash(PanedWindow *pwPtr, int sash, int diff);
static int		ObjectIsEmpty(Tcl_Obj *objPtr);
static void *	ComputeSlotAddress(void *recordPtr, Tcl_Size offset);
static int		PanedWindowIdentifyCoords(PanedWindow *pwPtr,
			    Tcl_Interp *interp, int x, int y);

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

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

static const Tk_GeomMgr panedWindowMgrType = {
    "panedwindow",		/* name */
    PanedWindowReqProc,		/* requestProc */
    PanedWindowLostPaneProc,	/* lostPaneProc */
};

/*
 * Information used for objv parsing.
 */

#define GEOMETRY		0x0001

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

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

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

static const Tk_OptionSpec paneOptionSpecs[] = {
    {TK_OPTION_WINDOW, "-after", NULL, NULL,
	DEF_PANEDWINDOW_PANE_AFTER, TCL_INDEX_NONE, offsetof(Pane, after),
	TK_OPTION_NULL_OK, 0, 0},
    {TK_OPTION_WINDOW, "-before", NULL, NULL,
	DEF_PANEDWINDOW_PANE_BEFORE, TCL_INDEX_NONE, offsetof(Pane, before),
	TK_OPTION_NULL_OK, 0, 0},
    {TK_OPTION_PIXELS, "-height", NULL, NULL,
	DEF_PANEDWINDOW_PANE_HEIGHT, offsetof(Pane, heightPtr),
	offsetof(Pane, height), TK_OPTION_NULL_OK, 0, 0},
    {TK_OPTION_BOOLEAN, "-hide", "hide", "Hide",
	DEF_PANEDWINDOW_PANE_HIDE, TCL_INDEX_NONE, offsetof(Pane, hide), 0,0,GEOMETRY},
    {TK_OPTION_PIXELS, "-minsize", NULL, NULL,
	DEF_PANEDWINDOW_PANE_MINSIZE, TCL_INDEX_NONE, offsetof(Pane, minSize), 0, 0, 0},
    {TK_OPTION_PIXELS, "-padx", NULL, NULL,
	DEF_PANEDWINDOW_PANE_PADX, TCL_INDEX_NONE, offsetof(Pane, padx), 0, 0, 0},
    {TK_OPTION_PIXELS, "-pady", NULL, NULL,
	DEF_PANEDWINDOW_PANE_PADY, TCL_INDEX_NONE, offsetof(Pane, pady), 0, 0, 0},
    {TK_OPTION_CUSTOM, "-sticky", NULL, NULL,
	DEF_PANEDWINDOW_PANE_STICKY, TCL_INDEX_NONE, offsetof(Pane, sticky),
	0, &stickyOption, 0},
    {TK_OPTION_STRING_TABLE, "-stretch", "stretch", "Stretch",
	DEF_PANEDWINDOW_PANE_STRETCH, TCL_INDEX_NONE, offsetof(Pane, stretch),
	TK_OPTION_ENUM_VAR, stretchStrings, 0},
    {TK_OPTION_PIXELS, "-width", NULL, NULL,
	DEF_PANEDWINDOW_PANE_WIDTH, offsetof(Pane, widthPtr),
	offsetof(Pane, width), TK_OPTION_NULL_OK, 0, 0},
    {TK_OPTION_END, NULL, NULL, NULL, NULL, 0, 0, 0, 0, 0}
};

/*
 *--------------------------------------------------------------
 *
 * Tk_PanedWindowObjCmd --
 *
 *	This function 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(
    TCL_UNUSED(void *),	/* 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;
    XSetWindowAttributes atts;

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

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

    pwOpts = (OptionTables *)
	    Tcl_GetAssocData(interp, "PanedWindowOptionTables", NULL);
    if (pwOpts == NULL) {
	/*
	 * The first time this function 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 clientData 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, pwOpts);

	/*
	 * Create the paned window option tables.
	 */

	pwOpts->pwOptions = Tk_CreateOptionTable(interp, optionSpecs);
	pwOpts->paneOpts = Tk_CreateOptionTable(interp, paneOptionSpecs);
    }

    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, pwPtr,
	    PanedWindowCmdDeletedProc);
    pwPtr->optionTable = pwOpts->pwOptions;
    pwPtr->paneOpts = pwOpts->paneOpts;
    pwPtr->relief = TK_RELIEF_RAISED;
    pwPtr->gc = NULL;
    pwPtr->cursor = NULL;
    pwPtr->sashCursor = NULL;

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

    Tcl_Preserve(pwPtr->tkwin);

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

    Tk_CreateEventHandler(pwPtr->tkwin, ExposureMask|StructureNotifyMask,
	    PanedWindowEventProc, 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
     * panes 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, NULL);

    /*
     * The proxy window has to be able to share GCs with the main panedwindow
     * despite being children of windows with potentially different
     * characteristics, and it looks better that way too. [Bug 702230] Also
     * set the X window save under attribute to avoid expose events as the
     * proxy sash is dragged across the panes. [Bug 1036963]
     */

    Tk_SetWindowVisual(pwPtr->proxywin,
	    Tk_Visual(tkwin), Tk_Depth(tkwin), Tk_Colormap(tkwin));
    Tk_CreateEventHandler(pwPtr->proxywin, ExposureMask, ProxyWindowEventProc,
	    pwPtr);
    atts.save_under = True;
    Tk_ChangeWindowAttributes(pwPtr->proxywin, CWSaveUnder, &atts);

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

    Tcl_SetObjResult(interp, Tk_NewWindowObj(pwPtr->tkwin));
    return TCL_OK;
}

/*
 *--------------------------------------------------------------
 *
 * PanedWindowWidgetObjCmd --
 *
 *	This function 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(
    void *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 *const optionStrings[] = {
	"add", "cget", "configure", "forget", "identify", "panecget",
	"paneconfigure", "panes", "proxy", "sash", 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;
    Pane *panePtr;

    if (objc < 2) {
	Tcl_WrongNumArgs(interp, 1, objv, "option ?arg ...?");
	return TCL_ERROR;
    }

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

    Tcl_Preserve(pwPtr);

    switch ((enum options) index) {
    case PW_ADD:
	if (objc < 3) {
	    Tcl_WrongNumArgs(interp, 2, objv, "widget ?widget ...?");
	    result = TCL_ERROR;
	    break;
	}
	result = ConfigurePanes(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, 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, pwPtr,
		    pwPtr->optionTable,
		    (objc == 3) ? objv[2] : 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: {

	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++) {
	    Tk_Window pane = Tk_NameToWindow(interp, Tcl_GetString(objv[i]),
		    pwPtr->tkwin);

	    if (pane == NULL) {
		continue;
	    }
	    panePtr = GetPane(pwPtr, pane);
	    if ((panePtr != NULL) && (panePtr->containerPtr != NULL)) {
		count++;
		Tk_ManageGeometry(pane, NULL, NULL);
		Tk_UnmaintainGeometry(panePtr->tkwin, pwPtr->tkwin);
		Tk_DeleteEventHandler(panePtr->tkwin, StructureNotifyMask,
			PaneStructureProc, panePtr);
		Tk_UnmapWindow(panePtr->tkwin);
		Unlink(panePtr);
	    }
	    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->numPanes; i++) {
	    if (pwPtr->panes[i]->tkwin == tkwin) {
		resultObj = Tk_GetOptionValue(interp,
			pwPtr->panes[i], pwPtr->paneOpts,
			objv[3], tkwin);
	    }
	}
	if (resultObj == NULL) {
	    if (i == pwPtr->numPanes) {
		Tcl_SetObjResult(interp, Tcl_NewStringObj(
			"not managed by this window", TCL_INDEX_NONE));
		Tcl_SetErrorCode(interp, "TK", "PANEDWINDOW", "UNMANAGED",
			NULL);
	    }
	    result = TCL_ERROR;
	} else {
	    Tcl_SetObjResult(interp, resultObj);
	}
	break;

    case PW_PANECONFIGURE:
	if (objc < 3) {
	    Tcl_WrongNumArgs(interp, 2, objv,
		    "pane ?-option value ...?");
	    result = TCL_ERROR;
	    break;
	}
	resultObj = NULL;
	if (objc <= 4) {
	    tkwin = Tk_NameToWindow(interp, Tcl_GetString(objv[2]),
		    pwPtr->tkwin);
            if (tkwin == NULL) {
                /*
                 * Just a plain old bad window; Tk_NameToWindow filled in an
                 * error message for us.
                 */

                result = TCL_ERROR;
                break;
            }
	    for (i = 0; i < pwPtr->numPanes; i++) {
		if (pwPtr->panes[i]->tkwin == tkwin) {
		    resultObj = Tk_GetOptionInfo(interp,
			    pwPtr->panes[i], pwPtr->paneOpts,
			    (objc == 4) ? objv[3] : NULL,
			    pwPtr->tkwin);
		    if (resultObj == NULL) {
			result = TCL_ERROR;
		    } else {
			Tcl_SetObjResult(interp, resultObj);
		    }
		    break;
		}
	    }
	} else {
	    result = ConfigurePanes(pwPtr, interp, objc, objv);
	}
	break;

    case PW_PANES:
	resultObj = Tcl_NewObj();
	for (i = 0; i < pwPtr->numPanes; i++) {
	    Tcl_ListObjAppendElement(NULL, resultObj,
		    Tk_NewWindowObj(pwPtr->panes[i]->tkwin));
	}
	Tcl_SetObjResult(interp, resultObj);
	break;

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

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

static int
ConfigurePanes(
    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, numNewPanes, haveLoc;
    int insertIndex;
    Tk_Window tkwin = NULL, ancestor, parent;
    Pane *panePtr, **inserts, **newPanes;
    Pane options;
    const char *arg;

    /*
     * Find the non-window name arguments; these are the configure options for
     * the panes. 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_SetObjResult(interp, Tcl_ObjPrintf(
			"can't add %s to itself", arg));
		Tcl_SetErrorCode(interp, "TK", "GEOMETRY", "SELF", NULL);
		return TCL_ERROR;
	    } else if (Tk_IsTopLevel(tkwin)) {
		/*
		 * A panedwindow cannot manage a toplevel.
		 */

		Tcl_SetObjResult(interp, Tcl_ObjPrintf(
			"can't add toplevel %s to %s", arg,
			Tk_PathName(pwPtr->tkwin)));
		Tcl_SetErrorCode(interp, "TK", "GEOMETRY", "TOPLEVEL", NULL);
		return TCL_ERROR;
	    } else {
		/*
		 * Make sure the panedwindow is the parent of the pane,
		 * or a descendant of the pane's parent.
		 */

		parent = Tk_Parent(tkwin);
		for (ancestor = pwPtr->tkwin;;ancestor = Tk_Parent(ancestor)) {
		    if (ancestor == parent) {
			break;
		    }
		    if (Tk_IsTopLevel(ancestor)) {
			Tcl_SetObjResult(interp, Tcl_ObjPrintf(
				"can't add %s to %s", arg,
				Tk_PathName(pwPtr->tkwin)));
			Tcl_SetErrorCode(interp, "TK", "GEOMETRY",
				"HIERARCHY", 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(Pane));
    if (Tk_SetOptions(interp, &options, pwPtr->paneOpts,
	    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 != NULL) {
	tkwin = options.after;
	haveLoc = 1;
	for (i = 0; i < pwPtr->numPanes; i++) {
	    if (options.after == pwPtr->panes[i]->tkwin) {
		index = i + 1;
		break;
	    }
	}
    } else if (options.before != NULL) {
	tkwin = options.before;
	haveLoc = 1;
	for (i = 0; i < pwPtr->numPanes; i++) {
	    if (options.before == pwPtr->panes[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_SetObjResult(interp, Tcl_ObjPrintf(
		"window \"%s\" is not managed by %s",
		Tk_PathName(tkwin), Tk_PathName(pwPtr->tkwin)));
	Tcl_SetErrorCode(interp, "TK", "PANEDWINDOW", "UNMANAGED", NULL);
	Tk_FreeConfigOptions((char *) &options, pwPtr->paneOpts,
		pwPtr->tkwin);
	return TCL_ERROR;
    }

    /*
     * Allocate an array to hold, in order, the pointers to the pane
     * structures corresponding to the windows specified. Some of those
     * structures may already have existed, some may be new.
     */

    inserts = (Pane **)ckalloc(sizeof(Pane *) * (firstOptionArg - 2));
    insertIndex = 0;

    /*
     * Populate the inserts array, creating new pane structures as necessary,
     * applying the options to each structure as we go, and, if necessary,
     * marking the spot in the original panes array as empty (for
     * pre-existing pane structures).
     */

    for (i = 0, numNewPanes = 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->numPanes; j++) {
	    if (pwPtr->panes[j] != NULL && pwPtr->panes[j]->tkwin == tkwin) {
		Tk_SetOptions(interp, pwPtr->panes[j],
			pwPtr->paneOpts, objc - firstOptionArg,
			objv + firstOptionArg, pwPtr->tkwin, NULL, NULL);
		if (pwPtr->panes[j]->minSize < 0) {
		    pwPtr->panes[j]->minSize = 0;
		}
		found = 1;

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

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

	if (found) {
	    continue;
	}

	/*
	 * Make sure this pane wasn't already put into the inserts array,
	 * i.e., 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 pane structure and initialize it. All panes start
	 * out with their "natural" dimensions.
	 */

	panePtr = (Pane *)ckalloc(sizeof(Pane));
	memset(panePtr, 0, sizeof(Pane));
	Tk_InitOptions(interp, panePtr, pwPtr->paneOpts,
		pwPtr->tkwin);
	Tk_SetOptions(interp, panePtr, pwPtr->paneOpts,
		objc - firstOptionArg, objv + firstOptionArg,
		pwPtr->tkwin, NULL, NULL);
	panePtr->tkwin = tkwin;
	panePtr->containerPtr = pwPtr;
	doubleBw = 2 * Tk_Changes(panePtr->tkwin)->border_width;
	if (panePtr->width > 0) {
	    panePtr->paneWidth = panePtr->width;
	} else {
	    panePtr->paneWidth = Tk_ReqWidth(tkwin) + doubleBw;
	}
	if (panePtr->height > 0) {
	    panePtr->paneHeight = panePtr->height;
	} else {
	    panePtr->paneHeight = Tk_ReqHeight(tkwin) + doubleBw;
	}
	if (panePtr->minSize < 0) {
	    panePtr->minSize = 0;
	}

	/*
	 * Set up the geometry management callbacks for this pane.
	 */

	Tk_CreateEventHandler(panePtr->tkwin, StructureNotifyMask,
		PaneStructureProc, panePtr);
	Tk_ManageGeometry(panePtr->tkwin, &panedWindowMgrType, panePtr);
	inserts[insertIndex++] = panePtr;
	numNewPanes++;
    }

    /*
     * Allocate the new panes array, then copy the panes into it, in order.
     */

    i = sizeof(Pane *) * (pwPtr->numPanes + numNewPanes);
    newPanes = (Pane **)ckalloc(i);
    memset(newPanes, 0, i);
    if (index == -1) {
	/*
	 * If none of the existing panes have to be moved, just copy the old
	 * and append the new.
	 * Be careful about the case pwPtr->numPanes == 0 since in this case
	 * pwPtr->panes is NULL, and the memcpy would have undefined behavior.
	 */
	if (pwPtr->numPanes) {
	    memcpy(newPanes, pwPtr->panes,
		    sizeof(Pane *) * pwPtr->numPanes);
	}
	memcpy(&newPanes[pwPtr->numPanes], inserts,
		sizeof(Pane *) * numNewPanes);
    } else {
	/*
	 * If some of the existing panes were moved, the old panes array
	 * will be partially populated, with some valid and some invalid
	 * entries. Walk through it, copying valid entries to the new panes
	 * array as we go; when we get to the insert location for the new
	 * panes, copy the inserts array over, then finish off the old panes
	 * array.
	 */

	for (i = 0, j = 0; i < index; i++) {
	    if (pwPtr->panes[i] != NULL) {
		newPanes[j] = pwPtr->panes[i];
		j++;
	    }
	}

	memcpy(&newPanes[j], inserts, sizeof(Pane *)*insertIndex);
	j += firstOptionArg - 2;

	for (i = index; i < pwPtr->numPanes; i++) {
	    if (pwPtr->panes[i] != NULL) {
		newPanes[j] = pwPtr->panes[i];
		j++;
	    }
	}
    }

    /*
     * Make the new panes array the paned window's pane array, and clean up.
     */

    ckfree(pwPtr->panes);
    ckfree(inserts);
    pwPtr->panes = newPanes;

    /*
     * Set the paned window's pane count to the new value.
     */

    pwPtr->numPanes += numNewPanes;

    Tk_FreeConfigOptions((char *) &options, pwPtr->paneOpts, 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(
    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 *const sashOptionStrings[] = {
	"coord", "dragto", "mark", "place", NULL
    };
    enum sashOptions {
	SASH_COORD, SASH_DRAGTO, SASH_MARK, SASH_PLACE
    };
    int index, sash, x, y, diff;
    Tcl_Obj *coords[2];
    Pane *panePtr;

    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;
    }

    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_SetObjResult(interp, Tcl_NewStringObj(
		    "invalid sash index", TCL_INDEX_NONE));
	    Tcl_SetErrorCode(interp, "TK", "VALUE", "SASH_INDEX", NULL);
	    return TCL_ERROR;
	}
	panePtr = pwPtr->panes[sash];

	coords[0] = Tcl_NewWideIntObj(panePtr->sashx);
	coords[1] = Tcl_NewWideIntObj(panePtr->sashy);
	Tcl_SetObjResult(interp, Tcl_NewListObj(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_SetObjResult(interp, Tcl_NewStringObj(
		    "invalid sash index", TCL_INDEX_NONE));
	    Tcl_SetErrorCode(interp, "TK", "VALUE", "SASH_INDEX", NULL);
	    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->panes[sash]->markx = x;
	    pwPtr->panes[sash]->marky = y;
	} else {
	    coords[0] = Tcl_NewWideIntObj(pwPtr->panes[sash]->markx);
	    coords[1] = Tcl_NewWideIntObj(pwPtr->panes[sash]->marky);
	    Tcl_SetObjResult(interp, Tcl_NewListObj(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_SetObjResult(interp, Tcl_NewStringObj(
		    "invalid sash index", TCL_INDEX_NONE));
	    Tcl_SetErrorCode(interp, "TK", "VALUE", "SASH_INDEX", NULL);
	    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;
	}

	panePtr = pwPtr->panes[sash];
	if (pwPtr->orient == ORIENT_HORIZONTAL) {
	    if (index == SASH_PLACE) {
		diff = x - pwPtr->panes[sash]->sashx;
	    } else {
		diff = x - pwPtr->panes[sash]->markx;
	    }
	} else {
	    if (index == SASH_PLACE) {
		diff = y - pwPtr->panes[sash]->sashy;
	    } else {
		diff = y - pwPtr->panes[sash]->marky;
	    }
	}

	MoveSash(pwPtr, sash, diff);
	ComputeGeometry(pwPtr);
    }
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * ConfigurePanedWindow --
 *
 *	This function 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(
    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, pwPtr, pwPtr->optionTable, objc, objv,
	    pwPtr->tkwin, &savedOptions, &typemask) != TCL_OK) {
	Tk_RestoreSavedOptions(&savedOptions);
	return TCL_ERROR;
    }

    Tk_FreeSavedOptions(&savedOptions);

    PanedWindowWorldChanged(pwPtr);

    /*
     * If an option that affects geometry has changed, make a re-layout
     * request.
     */

    if (typemask & GEOMETRY) {
	ComputeGeometry(pwPtr);
    }

    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * PanedWindowWorldChanged --
 *
 *	This function 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.
 *
 *----------------------------------------------------------------------
 */

static void
PanedWindowWorldChanged(
    void *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.) and set the window background.
     */

    gcValues.background = Tk_3DBorderColor(pwPtr->background)->pixel;
    newGC = Tk_GetGC(pwPtr->tkwin, GCBackground, &gcValues);
    if (pwPtr->gc != NULL) {
	Tk_FreeGC(pwPtr->display, pwPtr->gc);
    }
    pwPtr->gc = newGC;
    Tk_SetWindowBackground(pwPtr->tkwin, gcValues.background);

    /*
     * 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, pwPtr);
	pwPtr->flags |= REDRAW_PENDING;
    }
}

/*
 *--------------------------------------------------------------
 *
 * PanedWindowEventProc --
 *
 *	This function 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(
    void *clientData,	/* Information about window. */
    XEvent *eventPtr)		/* Information about event. */
{
    PanedWindow *pwPtr = (PanedWindow *)clientData;
    int i;

    if (eventPtr->type == Expose) {
	if (pwPtr->tkwin != NULL && !(pwPtr->flags & REDRAW_PENDING)) {
	    Tcl_DoWhenIdle(DisplayPanedWindow, 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, pwPtr);
	    pwPtr->flags |= REDRAW_PENDING;
	}
    } else if (eventPtr->type == DestroyNotify) {
	DestroyPanedWindow(pwPtr);
    } else if (eventPtr->type == UnmapNotify) {
        for (i = 0; i < pwPtr->numPanes; i++) {
            if (!pwPtr->panes[i]->hide) {
                Tk_UnmapWindow(pwPtr->panes[i]->tkwin);
            }
        }
    } else if (eventPtr->type == MapNotify) {
        for (i = 0; i < pwPtr->numPanes; i++) {
            if (!pwPtr->panes[i]->hide) {
                Tk_MapWindow(pwPtr->panes[i]->tkwin);
            }
        }
    }
}

/*
 *----------------------------------------------------------------------
 *
 * PanedWindowCmdDeletedProc --
 *
 *	This function 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(
    void *clientData)	/* Pointer to widget record for widget. */
{
    PanedWindow *pwPtr = (PanedWindow *)clientData;

    /*
     * This function could be invoked either because the window was destroyed
     * and the command was then deleted or because the command was deleted,
     * and then this function 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 function 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(
    void *clientData)	/* Information about window. */
{
    PanedWindow *pwPtr = (PanedWindow *)clientData;
    Pane *panePtr;
    Pixmap pixmap;
    Tk_Window tkwin = pwPtr->tkwin;
    int i, sashWidth, sashHeight;
    const int horizontal = (pwPtr->orient == ORIENT_HORIZONTAL);
    int first, last;

    pwPtr->flags &= ~REDRAW_PENDING;
    if ((pwPtr->tkwin == NULL) || !Tk_IsMapped(tkwin)) {
	return;
    }

    if (pwPtr->flags & REQUESTED_RELAYOUT) {
	ArrangePanes(clientData);
    }

#ifndef TK_NO_DOUBLE_BUFFERING
    /*
     * 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));
#else
    pixmap = Tk_WindowId(tkwin);
#endif /* TK_NO_DOUBLE_BUFFERING */

    /*
     * 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 (horizontal) {
	sashHeight = Tk_Height(tkwin) - (2 * Tk_InternalBorderLeft(tkwin));
	sashWidth = pwPtr->sashWidth;
    } else {
	sashWidth = Tk_Width(tkwin) - (2 * Tk_InternalBorderLeft(tkwin));
	sashHeight = pwPtr->sashWidth;
    }

    /*
     * Draw the sashes.
     */

    GetFirstLastVisiblePane(pwPtr, &first, &last);
    for (i = 0; i < pwPtr->numPanes - 1; i++) {
	panePtr = pwPtr->panes[i];
	if (panePtr->hide || i == last) {
	    continue;
	}
	if (sashWidth > 0 && sashHeight > 0) {
	    Tk_Fill3DRectangle(tkwin, pixmap, pwPtr->background,
		    panePtr->sashx, panePtr->sashy, sashWidth, sashHeight,
		    1, pwPtr->sashRelief);
	}
	if (pwPtr->showHandle) {
	    Tk_Fill3DRectangle(tkwin, pixmap, pwPtr->background,
		    panePtr->handlex, panePtr->handley,
		    pwPtr->handleSize, pwPtr->handleSize, 1,
		    TK_RELIEF_RAISED);
	}
    }

#ifndef TK_NO_DOUBLE_BUFFERING
    /*
     * 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);
#endif /* TK_NO_DOUBLE_BUFFERING */
}

/*
 *----------------------------------------------------------------------
 *
 * DestroyPanedWindow --
 *
 *	This function 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(
    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 functions 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, pwPtr);
    }
    if (pwPtr->flags & RESIZE_PENDING) {
	Tcl_CancelIdleCall(ArrangePanes, pwPtr);
    }

    /*
     * Clean up the pane list; foreach pane:
     *  o  Cancel the pane's structure notification callback
     *  o  Cancel geometry management for the pane.
     *  o  Free memory for the pane
     */

    for (i = 0; i < pwPtr->numPanes; i++) {
	Tk_DeleteEventHandler(pwPtr->panes[i]->tkwin, StructureNotifyMask,
		PaneStructureProc, pwPtr->panes[i]);
	Tk_ManageGeometry(pwPtr->panes[i]->tkwin, NULL, NULL);
	Tk_FreeConfigOptions((char *) pwPtr->panes[i], pwPtr->paneOpts,
		pwPtr->tkwin);
	ckfree(pwPtr->panes[i]);
	pwPtr->panes[i] = NULL;
    }
    if (pwPtr->panes) {
	ckfree(pwPtr->panes);
    }

    /*
     * 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(pwPtr->tkwin);
    pwPtr->tkwin = NULL;

    Tcl_EventuallyFree(pwPtr, TCL_DYNAMIC);
}

/*
 *--------------------------------------------------------------
 *
 * PanedWindowReqProc --
 *
 *	This function 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(
    void *clientData,	/* Paned window's information about window
				 * that got new preferred geometry. */
    TCL_UNUSED(Tk_Window))		/* Other Tk-related information about the
				 * window. */
{
    Pane *panePtr = (Pane *)clientData;
    PanedWindow *pwPtr = (PanedWindow *) panePtr->containerPtr;

    if (Tk_IsMapped(pwPtr->tkwin)) {
	if (!(pwPtr->flags & RESIZE_PENDING)) {
	    pwPtr->flags |= RESIZE_PENDING;
	    Tcl_DoWhenIdle(ArrangePanes, pwPtr);
	}
    } else {
	int doubleBw = 2 * Tk_Changes(panePtr->tkwin)->border_width;

	if (panePtr->width <= 0) {
	    panePtr->paneWidth = Tk_ReqWidth(panePtr->tkwin) + doubleBw;
	}
	if (panePtr->height <= 0) {
	    panePtr->paneHeight = Tk_ReqHeight(panePtr->tkwin) + doubleBw;
	}
	ComputeGeometry(pwPtr);
    }
}

/*
 *--------------------------------------------------------------
 *
 * PanedWindowLostPaneProc --
 *
 *	This function is invoked by Tk whenever some other geometry claims
 *	control over a pane that used to be managed by us.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Forgets all information about the pane. Causes geometry to be
 *	recomputed for the panedwindow.
 *
 *--------------------------------------------------------------
 */

static void
PanedWindowLostPaneProc(
    void *clientData,	/* Grid structure for the pane that was
				 * stolen away. */
    TCL_UNUSED(Tk_Window))		/* Tk's handle for the pane. */
{
    Pane *panePtr = (Pane *)clientData;
    PanedWindow *pwPtr = (PanedWindow *) panePtr->containerPtr;

    if (pwPtr->tkwin != Tk_Parent(panePtr->tkwin)) {
	Tk_UnmaintainGeometry(panePtr->tkwin, pwPtr->tkwin);
    }
    Unlink(panePtr);
    Tk_DeleteEventHandler(panePtr->tkwin, StructureNotifyMask,
	    PaneStructureProc, panePtr);
    Tk_UnmapWindow(panePtr->tkwin);
    panePtr->tkwin = NULL;
    ckfree(panePtr);
    ComputeGeometry(pwPtr);
}

/*
 *--------------------------------------------------------------
 *
 * ArrangePanes --
 *
 *	This function 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 panes of containerPtr may get resized or moved.
 *
 *--------------------------------------------------------------
 */

static void
ArrangePanes(
    void *clientData)	/* Structure describing parent whose panes
				 * are to be re-layed out. */
{
    PanedWindow *pwPtr = (PanedWindow *)clientData;
    Pane *panePtr;
    int i, newPaneWidth, newPaneHeight, paneX, paneY;
    int paneWidth, paneHeight, paneSize, paneMinSize;
    int doubleBw;
    int x, y;
    int sashWidth, sashOffset, sashCount, handleOffset;
    int sashReserve, sxReserve, syReserve;
    int internalBW;
    int paneDynSize, paneDynMinSize, pwHeight, pwWidth, pwSize;
    int first, last;
    int stretchReserve, stretchAmount;
    const int horizontal = (pwPtr->orient == ORIENT_HORIZONTAL);

    pwPtr->flags &= ~(REQUESTED_RELAYOUT|RESIZE_PENDING);

    /*
     * If the parent has no panes 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->numPanes == 0) {
	return;
    }

    Tcl_Preserve(pwPtr);

    /*
     * Find index of first and last visible panes.
     */

    GetFirstLastVisiblePane(pwPtr, &first, &last);

    /*
     * First pass; compute sizes
     */

    paneDynSize = paneDynMinSize = 0;
    internalBW = Tk_InternalBorderLeft(pwPtr->tkwin);
    pwHeight = Tk_Height(pwPtr->tkwin) - (2 * internalBW);
    pwWidth = Tk_Width(pwPtr->tkwin) - (2 * internalBW);
    x = y = internalBW;
    stretchReserve = (horizontal ? pwWidth : pwHeight);

    /*
     * Calculate the sash width, including handle and padding, and the sash
     * and handle offsets.
     */

    sashOffset = handleOffset = pwPtr->sashPad;
    if (pwPtr->showHandle && pwPtr->handleSize > pwPtr->sashWidth) {
	sashWidth = (2 * pwPtr->sashPad) + pwPtr->handleSize;
	sashOffset = ((pwPtr->handleSize - pwPtr->sashWidth) / 2)
		+ pwPtr->sashPad;
    } else {
	sashWidth = (2 * pwPtr->sashPad) + pwPtr->sashWidth;
	handleOffset = ((pwPtr->sashWidth - pwPtr->handleSize) / 2)
		+ pwPtr->sashPad;
    }

    for (i = sashCount = 0; i < pwPtr->numPanes; i++) {
	panePtr = pwPtr->panes[i];

	if (panePtr->hide) {
	    continue;
	}

	/*
	 * Compute the total size needed by all the panes and the left-over,
	 * or shortage of space available.
	 */

	if (horizontal) {
            if (panePtr->width > 0) {
                paneSize = panePtr->width;
            } else {
                paneSize = panePtr->paneWidth;
            }
	    stretchReserve -= paneSize + (2 * panePtr->padx);
	} else {
            if (panePtr->height > 0) {
                paneSize = panePtr->height;
            } else {
                paneSize = panePtr->paneHeight;
            }
	    stretchReserve -= paneSize + (2 * panePtr->pady);
	}
	if (IsStretchable(panePtr->stretch,i,first,last)
		&& Tk_IsMapped(pwPtr->tkwin)) {
	    paneDynSize += paneSize;
	    paneDynMinSize += panePtr->minSize;
	}
	if (i != last) {
	    stretchReserve -= sashWidth;
	    sashCount++;
	}
    }

    /*
     * Second pass; adjust/arrange panes.
     */

    for (i = 0; i < pwPtr->numPanes; i++) {
	panePtr = pwPtr->panes[i];

	if (panePtr->hide) {
	    Tk_UnmaintainGeometry(panePtr->tkwin, pwPtr->tkwin);
	    Tk_UnmapWindow(panePtr->tkwin);
	    continue;
	}

	/*
	 * Compute the size of this pane. The algorithm (assuming a
	 * horizontal paned window) is:
	 *
	 * 1.  Get "base" dimensions. If a width or height is specified for
	 *     this pane, 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(panePtr->tkwin)->border_width;
	newPaneWidth = (panePtr->width > 0 ? panePtr->width :
		Tk_ReqWidth(panePtr->tkwin) + doubleBw);
	newPaneHeight = (panePtr->height > 0 ? panePtr->height :
		Tk_ReqHeight(panePtr->tkwin) + doubleBw);
	paneMinSize = panePtr->minSize;

	/*
	 * Calculate pane width and height.
	 */

	if (horizontal) {
            if (panePtr->width > 0) {
                paneSize = panePtr->width;
            } else {
                paneSize = panePtr->paneWidth;
            }
	    pwSize = pwWidth;
	} else {
            if (panePtr->height > 0) {
                paneSize = panePtr->height;
            } else {
                paneSize = panePtr->paneHeight;
            }
	    pwSize = pwHeight;
	}
	if (IsStretchable(panePtr->stretch, i, first, last)) {
	    double frac;

	    if (paneDynSize > 0) {
		frac = (double)paneSize / (double)paneDynSize;
	    } else {
		frac = (double)paneSize / (double)pwSize;
	    }

	    paneDynSize -= paneSize;
	    paneDynMinSize -= panePtr->minSize;
	    stretchAmount = (int) (frac * stretchReserve);
	    if (paneSize + stretchAmount >= paneMinSize) {
		stretchReserve -= stretchAmount;
		paneSize += stretchAmount;
	    } else {
		stretchReserve += paneSize - paneMinSize;
		paneSize = paneMinSize;
	    }
	    if (i == last && stretchReserve > 0) {
		paneSize += stretchReserve;
		stretchReserve = 0;
	    }
	} else if (paneDynSize - paneDynMinSize + stretchReserve < 0) {
	    if (paneSize + paneDynSize - paneDynMinSize + stretchReserve
		    <= paneMinSize) {
		stretchReserve += paneSize - paneMinSize;
		paneSize = paneMinSize;
	    } else {
		paneSize += paneDynSize - paneDynMinSize + stretchReserve;
		stretchReserve = paneDynMinSize - paneDynSize;
	    }
	}
	if (horizontal) {
	    paneWidth = paneSize;
	    paneHeight = pwHeight - (2 * panePtr->pady);
	} else {
	    paneWidth = pwWidth - (2 * panePtr->padx);
	    paneHeight = paneSize;
	}

	/*
	 * Adjust for area reserved for sashes.
	 */

	if (sashCount) {
	    sashReserve = sashWidth * sashCount;
	    if (horizontal) {
		sxReserve = sashReserve;
		syReserve = 0;
	    } else {
		sxReserve = 0;
		syReserve = sashReserve;
	    }
	} else {
	    sxReserve = syReserve = 0;
	}

	if (pwWidth - sxReserve < x + paneWidth - internalBW) {
	    paneWidth = pwWidth - sxReserve - x + internalBW;
	}
	if (pwHeight - syReserve < y + paneHeight - internalBW) {
	    paneHeight = pwHeight - syReserve - y + internalBW;
	}

	if (newPaneWidth > paneWidth) {
	    newPaneWidth = paneWidth;
	}
	if (newPaneHeight > paneHeight) {
	    newPaneHeight = paneHeight;
	}

	panePtr->x = x;
	panePtr->y = y;

	/*
	 * Compute the location of the sash at the right or bottom of the
	 * parcel and the location of the next parcel.
	 */

	if (horizontal) {
	    x += paneWidth + (2 * panePtr->padx);
	    if (x < internalBW) {
		x = internalBW;
	    }
	    panePtr->sashx = x + sashOffset;
	    panePtr->sashy = y;
	    panePtr->handlex = x + handleOffset;
	    panePtr->handley = y + pwPtr->handlePad;
	    x += sashWidth;
	} else {
	    y += paneHeight + (2 * panePtr->pady);
	    if (y < internalBW) {
		y = internalBW;
	    }
	    panePtr->sashx = x;
	    panePtr->sashy = y + sashOffset;
	    panePtr->handlex = x + pwPtr->handlePad;
	    panePtr->handley = y + handleOffset;
	    y += sashWidth;
	}

	/*
	 * Compute the actual dimensions of the pane in the pane.
	 */

	paneX = panePtr->x;
	paneY = panePtr->y;
	AdjustForSticky(panePtr->sticky, paneWidth, paneHeight,
		&paneX, &paneY, &newPaneWidth, &newPaneHeight);

	paneX += panePtr->padx;
	paneY += panePtr->pady;

	/*
	 * Now put the window in the proper spot.
	 */

	if (newPaneWidth <= 0 || newPaneHeight <= 0 ||
		(horizontal ? paneX - internalBW > pwWidth :
		paneY - internalBW > pwHeight)) {
	    Tk_UnmaintainGeometry(panePtr->tkwin, pwPtr->tkwin);
	    Tk_UnmapWindow(panePtr->tkwin);
	} else {
	    Tk_MaintainGeometry(panePtr->tkwin, pwPtr->tkwin,
		    paneX, paneY, newPaneWidth, newPaneHeight);
	}
	sashCount--;
    }
    Tcl_Release(pwPtr);
}

/*
 *----------------------------------------------------------------------
 *
 * Unlink --
 *
 *	Remove a pane from a paned window.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	The paned window will be scheduled for re-arranging and redrawing.
 *
 *----------------------------------------------------------------------
 */

static void
Unlink(
    Pane *panePtr)		/* Window to unlink. */
{
    PanedWindow *containerPtr;
    int i, j;

    containerPtr = panePtr->containerPtr;
    if (containerPtr == NULL) {
	return;
    }

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

    for (i = 0; i < containerPtr->numPanes; i++) {
	if (containerPtr->panes[i] == panePtr) {
	    for (j = i; j < containerPtr->numPanes - 1; j++) {
		containerPtr->panes[j] = containerPtr->panes[j + 1];
	    }
	    break;
	}
    }

    /*
     * Clean out any -after or -before references to this pane
     */

    for (i = 0; i < containerPtr->numPanes; i++) {
	if (containerPtr->panes[i]->before == panePtr->tkwin) {
	    containerPtr->panes[i]->before = NULL;
	}
	if (containerPtr->panes[i]->after == panePtr->tkwin) {
	    containerPtr->panes[i]->after = NULL;
	}
    }

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

    /*
     * Set the pane's containerPtr to NULL, so that we can tell that the pane
     * is no longer attached to any panedwindow.
     */

    panePtr->containerPtr = NULL;

    containerPtr->numPanes--;
}

/*
 *----------------------------------------------------------------------
 *
 * 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 pane structure, or NULL if the window is not managed
 *	by this paned window.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

static Pane *
GetPane(
    PanedWindow *pwPtr,		/* Pointer to the paned window info. */
    Tk_Window tkwin)		/* Window to search for. */
{
    int i;

    for (i = 0; i < pwPtr->numPanes; i++) {
	if (pwPtr->panes[i]->tkwin == tkwin) {
	    return pwPtr->panes[i];
	}
    }
    return NULL;
}

/*
 *----------------------------------------------------------------------
 *
 * GetFirstLastVisiblePane --
 *
 *	Given panedwindow, find the index of the first and last visible panes
 *	of that paned window.
 *
 * Results:
 *	Index of the first and last visible panes.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

static void
GetFirstLastVisiblePane(
    PanedWindow *pwPtr,		/* Pointer to the paned window info. */
    int *firstPtr, 		/* Returned index for first. */
    int *lastPtr)  		/* Returned index for last. */
{
    int i;

    for (i = 0, *lastPtr = 0, *firstPtr = -1; i < pwPtr->numPanes; i++) {
	if (pwPtr->panes[i]->hide == 0) {
	    if (*firstPtr < 0) {
		*firstPtr = i;
	    }
	    *lastPtr = i;
	}
    }
}

/*
 *--------------------------------------------------------------
 *
 * PaneStructureProc --
 *
 *	This function is invoked whenever StructureNotify events occur for a
 *	window that's managed by a paned window. This function's only purpose
 *	is to clean up when windows are deleted.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	The paned window pane structure associated with the window
 *	is freed, and the pane is disassociated from the paned
 *	window which managed it.
 *
 *--------------------------------------------------------------
 */

static void
PaneStructureProc(
    void *clientData,	/* Pointer to record describing window item. */
    XEvent *eventPtr)		/* Describes what just happened. */
{
    Pane *panePtr = (Pane *)clientData;
    PanedWindow *pwPtr = panePtr->containerPtr;

    if (eventPtr->type == DestroyNotify) {
	Unlink(panePtr);
	panePtr->tkwin = NULL;
	ckfree(panePtr);
	ComputeGeometry(pwPtr);
    }
}

/*
 *----------------------------------------------------------------------
 *
 * ComputeGeometry --
 *
 *	Compute geometry for the paned window, including coordinates of all
 *	panes and each sash.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Recomputes geometry information for a paned window.
 *
 *----------------------------------------------------------------------
 */

static void
ComputeGeometry(
    PanedWindow *pwPtr)		/* Pointer to the Paned Window structure. */
{
    int i, x, y, doubleBw, internalBw;
    int sashWidth, sashOffset, handleOffset;
    int reqWidth, reqHeight, dim;
    Pane *panePtr;
    const int horizontal = (pwPtr->orient == ORIENT_HORIZONTAL);

    pwPtr->flags |= REQUESTED_RELAYOUT;

    x = y = internalBw = Tk_InternalBorderLeft(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).
     */

    sashOffset = handleOffset = pwPtr->sashPad;
    if (pwPtr->showHandle && pwPtr->handleSize > pwPtr->sashWidth) {
	sashWidth = (2 * pwPtr->sashPad) + pwPtr->handleSize;
	sashOffset = ((pwPtr->handleSize - pwPtr->sashWidth) / 2)
		+ pwPtr->sashPad;
    } else {
	sashWidth = (2 * pwPtr->sashPad) + pwPtr->sashWidth;
	handleOffset = ((pwPtr->sashWidth - pwPtr->handleSize) / 2)
		+ pwPtr->sashPad;
    }

    for (i = 0; i < pwPtr->numPanes; i++) {
	panePtr = pwPtr->panes[i];

	if (panePtr->hide) {
	    continue;
	}

	/*
	 * First set the coordinates for the top left corner of the pane's
	 * parcel.
	 */

	panePtr->x = x;
	panePtr->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 (horizontal) {
	    if (panePtr->paneWidth < panePtr->minSize) {
		panePtr->paneWidth = panePtr->minSize;
	    }
	} else {
	    if (panePtr->paneHeight < panePtr->minSize) {
		panePtr->paneHeight = panePtr->minSize;
	    }
	}

	/*
	 * Compute the location of the sash at the right or bottom of the
	 * parcel.
	 */

	if (horizontal) {
	    x += panePtr->paneWidth + (2 * panePtr->padx);
	    panePtr->sashx = x + sashOffset;
	    panePtr->sashy = y;
	    panePtr->handlex = x + handleOffset;
	    panePtr->handley = y + pwPtr->handlePad;
	    x += sashWidth;
	} else {
	    y += panePtr->paneHeight + (2 * panePtr->pady);
	    panePtr->sashx = x;
	    panePtr->sashy = y + sashOffset;
	    panePtr->handlex = x + pwPtr->handlePad;
	    panePtr->handley = y + handleOffset;
	    y += sashWidth;
	}

	/*
	 * Find the maximum height/width of the panes, for computing the
	 * requested height/width of the paned window.
	 */

	if (horizontal) {
	    /*
	     * If the pane has an explicit height set, use that; otherwise,
	     * use the pane's requested height.
	     */

	    if (panePtr->height > 0) {
		dim = panePtr->height;
	    } else {
		doubleBw = 2 * Tk_Changes(panePtr->tkwin)->border_width;
		dim = Tk_ReqHeight(panePtr->tkwin) + doubleBw;
	    }
	    dim += 2 * panePtr->pady;
	    if (dim > reqHeight) {
		reqHeight = dim;
	    }
	} else {
	    /*
	     * If the pane has an explicit width set use that; otherwise, use
	     * the pane's requested width.
	     */

	    if (panePtr->width > 0) {
		dim = panePtr->width;
	    } else {
		doubleBw = 2 * Tk_Changes(panePtr->tkwin)->border_width;
		dim = Tk_ReqWidth(panePtr->tkwin) + doubleBw;
	    }
	    dim += 2 * panePtr->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
     * panes, plus the width of the border of the top and bottom (or left and
     * right) of the paned window.
     *
     * If the panedwindow has an explicit width/height set use that;
     * otherwise, use the requested width/height.
     */

    if (horizontal) {
	reqWidth = (pwPtr->width > 0 ?
		pwPtr->width : x - sashWidth + internalBw);
	reqHeight = (pwPtr->height > 0 ?
		pwPtr->height : reqHeight + (2 * internalBw));
    } else {
	reqWidth = (pwPtr->width > 0 ?
		pwPtr->width : reqWidth + (2 * internalBw));
	reqHeight = (pwPtr->height > 0 ?
		pwPtr->height : y - sashWidth + internalBw);
    }
    Tk_GeometryRequest(pwPtr->tkwin, reqWidth, reqHeight);
    if (Tk_IsMapped(pwPtr->tkwin) && !(pwPtr->flags & REDRAW_PENDING)) {
	pwPtr->flags |= REDRAW_PENDING;
	Tcl_DoWhenIdle(DisplayPanedWindow, pwPtr);
    }
}

/*
 *----------------------------------------------------------------------
 *
 * DestroyOptionTables --
 *
 *	This function 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(
    void *clientData,	/* Pointer to the OptionTables struct */
    TCL_UNUSED(Tcl_Interp *))		/* Pointer to the calling interp */
{
    ckfree(clientData);
}

/*
 *----------------------------------------------------------------------
 *
 * GetSticky -
 *
 *	Converts an internal boolean combination of "sticky" bits into a Tcl
 *	string obj containing zero or more 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(
    TCL_UNUSED(void *),
    TCL_UNUSED(Tk_Window),
    char *recordPtr,		/* Pointer to widget record. */
    Tcl_Size internalOffset)		/* Offset within *recordPtr containing the
				 * sticky value. */
{
    int sticky = *(int *)(recordPtr + internalOffset);
    char buffer[5];
    char *p = &buffer[0];

    if (sticky & STICK_NORTH) {
	*p++ = 'n';
    }
    if (sticky & STICK_EAST) {
	*p++ = 'e';
    }
    if (sticky & STICK_SOUTH) {
	*p++ = 's';
    }
    if (sticky & STICK_WEST) {
	*p++ = 'w';
    }
    *p = '\0';

    return Tcl_NewStringObj(buffer, TCL_INDEX_NONE);
}

/*
 *----------------------------------------------------------------------
 *
 * 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(
    TCL_UNUSED(void *),
    Tcl_Interp *interp,		/* Current interp; may be used for errors. */
    TCL_UNUSED(Tk_Window),	/* 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. */
    Tcl_Size 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;
    void *internalPtr;
    const char *string;

    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_SetObjResult(interp, Tcl_ObjPrintf(
			"bad stickyness value \"%s\": must be a string"
			" containing zero or more of n, e, s, and w",
			Tcl_GetString(*value)));
		Tcl_SetErrorCode(interp, "TK", "VALUE", "STICKY", 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(
    TCL_UNUSED(void *),
    TCL_UNUSED(Tk_Window),
    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 pane, compute the x,y coords
 *	and actual dimensions of the pane based on the pane's sticky value.
 *
 * Results:
 *	No direct return; sets the x, y, paneWidth and paneHeight to correct
 *	values.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

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

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

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

    if ((sticky & STICK_EAST) && (sticky & STICK_WEST)) {
	*paneWidthPtr += diffx;
    }
    if ((sticky & STICK_NORTH) && (sticky & STICK_SOUTH)) {
	*paneHeightPtr += 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(
    PanedWindow *pwPtr,
    int sash,
    int diff)
{
    int i;
    int expandPane, reduceFirst, reduceLast, reduceIncr, paneSize, sashOffset;
    Pane *panePtr;
    int stretchReserve = 0;
    int nextSash = sash + 1;
    const int horizontal = (pwPtr->orient == ORIENT_HORIZONTAL);

    if (diff == 0)
	return;

    /*
     * Update the pane sizes with their real sizes.
     */

    if (pwPtr->showHandle && pwPtr->handleSize > pwPtr->sashWidth) {
	sashOffset = ((pwPtr->handleSize - pwPtr->sashWidth) / 2)
		+ pwPtr->sashPad;
    } else {
	sashOffset = pwPtr->sashPad;
    }
    for (i = 0; i < pwPtr->numPanes; i++) {
	panePtr = pwPtr->panes[i];
	if (panePtr->hide) {
	    continue;
	}
	if (horizontal) {
	    panePtr->paneWidth = panePtr->width = panePtr->sashx
		    - sashOffset - panePtr->x - (2 * panePtr->padx);
	} else {
	    panePtr->paneHeight = panePtr->height = panePtr->sashy
		    - sashOffset - panePtr->y - (2 * panePtr->pady);
	}
    }

    /*
     * There must be a next sash since it is only possible to enter this
     * routine when moving an actual sash which implies there exists a visible
     * pane to either side of the sash.
     */

    while (nextSash < pwPtr->numPanes-1 && pwPtr->panes[nextSash]->hide) {
	nextSash++;
    }

    /*
     * Consolidate +/-diff variables to reduce duplicate code.
     */

    if (diff > 0) {
	expandPane = sash;
	reduceFirst = nextSash;
	reduceLast = pwPtr->numPanes;
	reduceIncr = 1;
    } else {
	diff = abs(diff);
	expandPane = nextSash;
	reduceFirst = sash;
	reduceLast = -1;
	reduceIncr = -1;
    }

    /*
     * Calculate how much room we have to stretch in and adjust diff value
     * accordingly.
     */

    for (i = reduceFirst; i != reduceLast; i += reduceIncr) {
	panePtr = pwPtr->panes[i];
	if (panePtr->hide) {
	    continue;
	}
	if (horizontal) {
	    stretchReserve += panePtr->width - panePtr->minSize;
	} else {
	    stretchReserve += panePtr->height - panePtr->minSize;
	}
    }
    if (stretchReserve <= 0) {
	return;
    }
    if (diff > stretchReserve) {
	diff = stretchReserve;
    }

    /*
     * Expand pane by diff amount.
     */

    panePtr = pwPtr->panes[expandPane];
    if (horizontal) {
	panePtr->paneWidth = panePtr->width += diff;
    } else {
	panePtr->paneHeight = panePtr->height += diff;
    }

    /*
     * Reduce panes, respecting minsize, until diff amount has been used.
     */

    for (i = reduceFirst; i != reduceLast; i += reduceIncr) {
	panePtr = pwPtr->panes[i];
	if (panePtr->hide) {
	    continue;
	}
	if (horizontal) {
	    paneSize = panePtr->width;
	} else {
	    paneSize = panePtr->height;
	}
	if (diff > (paneSize - panePtr->minSize)) {
	    diff -= paneSize - panePtr->minSize;
	    paneSize = panePtr->minSize;
	} else {
	    paneSize -= diff;
	    i = reduceLast - reduceIncr;
	}
	if (horizontal) {
	    panePtr->paneWidth = panePtr->width = paneSize;
	} else {
	    panePtr->paneHeight = panePtr->height = paneSize;
	}
    }
}

/*
 *----------------------------------------------------------------------
 *
 * ProxyWindowEventProc --
 *
 *	This function 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(
    void *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, pwPtr);
	    pwPtr->flags |= PROXY_REDRAW_PENDING;
	}
    }
}

/*
 *--------------------------------------------------------------
 *
 * DisplayProxyWindow --
 *
 *	This function 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(
    void *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;
    }

#ifndef TK_NO_DOUBLE_BUFFERING
    /*
     * 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));
#else
    pixmap = Tk_WindowId(tkwin);
#endif /* TK_NO_DOUBLE_BUFFERING */

    /*
     * Redraw the widget's background and border.
     */

    Tk_Fill3DRectangle(tkwin, pixmap,
	    pwPtr->proxyBackground ? pwPtr->proxyBackground : pwPtr->background,
	    0, 0, Tk_Width(tkwin), Tk_Height(tkwin), pwPtr->proxyBorderWidth,
	    (pwPtr->proxyRelief != TK_RELIEF_NULL) ? pwPtr->proxyRelief : pwPtr->sashRelief);

#ifndef TK_NO_DOUBLE_BUFFERING
    /*
     * 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);
#endif /* TK_NO_DOUBLE_BUFFERING */
}

/*
 *----------------------------------------------------------------------
 *
 * 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(
    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 *const optionStrings[] = {
	"coord", "forget", "place", NULL
    };
    enum options {
	PROXY_COORD, PROXY_FORGET, PROXY_PLACE
    };
    int index, x, y, sashWidth, sashHeight;
    int internalBW, pwWidth, pwHeight;
    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_NewWideIntObj(pwPtr->proxyx);
	coords[1] = Tcl_NewWideIntObj(pwPtr->proxyy);
	Tcl_SetObjResult(interp, Tcl_NewListObj(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;
	}

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

	if (sashWidth < 1) {
	    sashWidth = 1;
	}
	if (sashHeight < 1) {
	    sashHeight = 1;
	}

	/*
	 * 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
	 * panes, 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 pane, 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 function 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(
    Tcl_Obj *objPtr)		/* Object to test. May be NULL. */
{
    if (objPtr == NULL) {
	return 1;
    }
    if (objPtr->bytes == NULL) {
	Tcl_GetString(objPtr);
    }
    return (objPtr->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 void *
ComputeSlotAddress(
    void *recordPtr,	/* Pointer to the start of a record. */
    Tcl_Size offset)		/* Offset of a slot within that record; may be TCL_INDEX_NONE. */
{
    if (offset != TCL_INDEX_NONE) {
	return (char *)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(
    PanedWindow *pwPtr,		/* Information about the widget. */
    Tcl_Interp *interp,		/* Interpreter in which to store result. */
    int x, int y)		/* Coordinates of the point to identify. */
{
    int i, sashHeight, sashWidth, thisx, thisy;
    int found, isHandle, lpad, rpad, tpad, bpad;
    int first, last;

    if (pwPtr->orient == ORIENT_HORIZONTAL) {
	if (Tk_IsMapped(pwPtr->tkwin)) {
	    sashHeight = Tk_Height(pwPtr->tkwin);
	} else {
	    sashHeight = Tk_ReqHeight(pwPtr->tkwin);
	}
	sashHeight -= 2 * Tk_InternalBorderLeft(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_InternalBorderLeft(pwPtr->tkwin);
	lpad = rpad = 0;
    }

    GetFirstLastVisiblePane(pwPtr, &first, &last);
    isHandle = 0;
    found = -1;
    for (i = 0; i < pwPtr->numPanes - 1; i++) {
	if (pwPtr->panes[i]->hide || i == last) {
	    continue;
	}
	thisx = pwPtr->panes[i]->sashx;
	thisy = pwPtr->panes[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->panes[i]->handlex;
		thisy = pwPtr->panes[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. Note that the empty string is the default (this function
     * is called inside the implementation of a command).
     */

    if (found != -1) {
	Tcl_Obj *list[2];

	list[0] = Tcl_NewWideIntObj(found);
	list[1] = Tcl_NewStringObj((isHandle ? "handle" : "sash"), TCL_INDEX_NONE);
	Tcl_SetObjResult(interp, Tcl_NewListObj(2, list));
    }
    return TCL_OK;
}

/*
 * Local Variables:
 * mode: c
 * c-basic-offset: 4
 * fill-column: 78
 * End:
 */