1/*
2 * tkPanedWindow.c --
3 *
4 *	This module implements "paned window" widgets that are object based. A
5 *	"paned window" is a widget that manages the geometry for some number
6 *	of other widgets, placing a movable "sash" between them, which can be
7 *	used to alter the relative sizes of adjacent widgets.
8 *
9 * Copyright (c) 1997 Sun Microsystems, Inc.
10 * Copyright (c) 2000 Ajuba Solutions.
11 *
12 * See the file "license.terms" for information on usage and redistribution of
13 * this file, and for a DISCLAIMER OF ALL WARRANTIES.
14 *
15 * RCS: @(#) $Id$
16 */
17
18#include "default.h"
19#include "tkInt.h"
20
21/*
22 * Flag values for "sticky"ness. The 16 combinations subsume the packer's
23 * notion of anchor and fill.
24 *
25 * STICK_NORTH  	This window sticks to the top of its cavity.
26 * STICK_EAST		This window sticks to the right edge of its cavity.
27 * STICK_SOUTH		This window sticks to the bottom of its cavity.
28 * STICK_WEST		This window sticks to the left edge of its cavity.
29 */
30
31#define STICK_NORTH		1
32#define STICK_EAST		2
33#define STICK_SOUTH		4
34#define STICK_WEST		8
35
36/*
37 * The following table defines the legal values for the -orient option.
38 */
39
40static char *orientStrings[] = {
41    "horizontal", "vertical", NULL
42};
43
44enum orient { ORIENT_HORIZONTAL, ORIENT_VERTICAL };
45
46/*
47 * The following table defines the legal values for the -stretch option.
48 */
49
50static char *stretchStrings[] = {
51    "always", "first", "last", "middle", "never", NULL
52};
53
54enum stretch {
55    STRETCH_ALWAYS,		/* Always give extra space to this pane. */
56    STRETCH_FIRST,		/* Give extra space to pane if it is first. */
57    STRETCH_LAST,		/* Give extra space to pane if it is last. */
58    STRETCH_MIDDLE,		/* Give extra space to pane only if it is
59				 * neither first nor last. */
60    STRETCH_NEVER		/* Never give extra space to this pane. */
61};
62
63/*
64 * Codify the stretchiness rule in one place.
65 */
66
67#define IsStretchable(stretch,index,first,last)			\
68    (((stretch) == STRETCH_ALWAYS) ||				\
69     ((stretch) == STRETCH_FIRST && (index) == (first)) ||	\
70     ((stretch) == STRETCH_LAST && (index) == (last)) ||	\
71     ((stretch) == STRETCH_MIDDLE && (index) != (first) && (index) != (last)))
72
73typedef struct {
74    Tk_OptionTable pwOptions;	/* Token for paned window option table. */
75    Tk_OptionTable slaveOpts;	/* Token for slave cget option table. */
76} OptionTables;
77
78/*
79 * One structure of the following type is kept for each window
80 * managed by a paned window widget.
81 */
82
83typedef struct Slave {
84    Tk_Window tkwin;		/* Window being managed. */
85    int minSize;		/* Minimum size of this pane, on the relevant
86				 * axis, in pixels. */
87    int padx;			/* Additional padding requested for slave, in
88				 * the x dimension. */
89    int pady;			/* Additional padding requested for slave, in
90				 * the y dimension. */
91    Tcl_Obj *widthPtr, *heightPtr;
92				/* Tcl_Obj rep's of slave width/height, to
93				 * allow for null values. */
94    int width;			/* Slave width. */
95    int height;			/* Slave height. */
96    int sticky;			/* Sticky string. */
97    int x, y;			/* Coordinates of the widget. */
98    int paneWidth, paneHeight;	/* Pane dimensions (may be different from
99				 * slave width/height). */
100    int sashx, sashy;		/* Coordinates of the sash of the right or
101				 * bottom of this pane. */
102    int markx, marky;		/* Coordinates of the last mark set for the
103				 * sash. */
104    int handlex, handley;	/* Coordinates of the sash handle. */
105    enum stretch stretch;	/* Controls how slave grows/shrinks */
106    int hide;			/* Controls visibility of pane */
107    struct PanedWindow *masterPtr;
108				/* Paned window managing the window. */
109    Tk_Window after;		/* Placeholder for parsing options. */
110    Tk_Window before;		/* Placeholder for parsing options. */
111} Slave;
112
113/*
114 * A data structure of the following type is kept for each paned window widget
115 * managed by this file:
116 */
117
118typedef struct PanedWindow {
119    Tk_Window tkwin;		/* Window that embodies the paned window. */
120    Tk_Window proxywin;		/* Window for the resizing proxy. */
121    Display *display;		/* X's token for the window's display. */
122    Tcl_Interp *interp;		/* Interpreter associated with widget. */
123    Tcl_Command widgetCmd;	/* Token for square's widget command. */
124    Tk_OptionTable optionTable;	/* Token representing the configuration
125				 * specifications. */
126    Tk_OptionTable slaveOpts;	/* Token for slave cget table. */
127    Tk_3DBorder background;	/* Background color. */
128    int borderWidth;		/* Value of -borderwidth option. */
129    int relief;			/* 3D border effect (TK_RELIEF_RAISED, etc) */
130    Tcl_Obj *widthPtr;		/* Tcl_Obj rep for width. */
131    Tcl_Obj *heightPtr;		/* Tcl_Obj rep for height. */
132    int width, height;		/* Width and height of the widget. */
133    enum orient orient;		/* Orientation of the widget. */
134    Tk_Cursor cursor;		/* Current cursor for window, or None. */
135    int resizeOpaque;		/* Boolean indicating whether resize should be
136				 * opaque or rubberband style. */
137    int sashRelief;		/* Relief used to draw sash. */
138    int sashWidth;		/* Width of each sash, in pixels. */
139    Tcl_Obj *sashWidthPtr;	/* Tcl_Obj rep for sash width. */
140    int sashPad;		/* Additional padding around each sash. */
141    Tcl_Obj *sashPadPtr;	/* Tcl_Obj rep for sash padding. */
142    int showHandle;		/* Boolean indicating whether sash handles
143				 * should be drawn. */
144    int handleSize;		/* Size of one side of a sash handle (handles
145				 * are square), in pixels. */
146    int handlePad;		/* Distance from border to draw handle. */
147    Tcl_Obj *handleSizePtr;	/* Tcl_Obj rep for handle size. */
148    Tk_Cursor sashCursor;	/* Cursor used when mouse is above a sash. */
149    GC gc;			/* Graphics context for copying from
150				 * off-screen pixmap onto screen. */
151    int proxyx, proxyy;		/* Proxy x,y coordinates. */
152    Slave **slaves;		/* Pointer to array of Slaves. */
153    int numSlaves;		/* Number of slaves. */
154    int sizeofSlaves;		/* Number of elements in the slaves array. */
155    int flags;			/* Flags for widget; see below. */
156} PanedWindow;
157
158/*
159 * Flags used for paned windows:
160 *
161 * REDRAW_PENDING:		Non-zero means a DoWhenIdle handler has been
162 *				queued to redraw this window.
163 *
164 * WIDGET_DELETED:		Non-zero means that the paned window has been,
165 *				or is in the process of being, deleted.
166 *
167 * RESIZE_PENDING:		Non-zero means that the window might need to
168 *				change its size (or the size of its panes)
169 *				because of a change in the size of one of its
170 *				children.
171 */
172
173#define REDRAW_PENDING		0x0001
174#define WIDGET_DELETED		0x0002
175#define REQUESTED_RELAYOUT	0x0004
176#define RECOMPUTE_GEOMETRY	0x0008
177#define PROXY_REDRAW_PENDING	0x0010
178#define RESIZE_PENDING		0x0020
179
180/*
181 * Forward declarations for functions defined later in this file:
182 */
183
184int			Tk_PanedWindowObjCmd(ClientData clientData,
185			    Tcl_Interp *interp, int objc,
186			    Tcl_Obj *CONST objv[]);
187static void		PanedWindowCmdDeletedProc(ClientData clientData);
188static int		ConfigurePanedWindow(Tcl_Interp *interp,
189			    PanedWindow *pwPtr, int objc,
190			    Tcl_Obj *CONST objv[]);
191static void		DestroyPanedWindow(PanedWindow *pwPtr);
192static void		DisplayPanedWindow(ClientData clientData);
193static void		PanedWindowEventProc(ClientData clientData,
194			    XEvent *eventPtr);
195static void		ProxyWindowEventProc(ClientData clientData,
196			    XEvent *eventPtr);
197static void		DisplayProxyWindow(ClientData clientData);
198static void		PanedWindowWorldChanged(ClientData instanceData);
199static int		PanedWindowWidgetObjCmd(ClientData clientData,
200			    Tcl_Interp *, int objc, Tcl_Obj * CONST objv[]);
201static void		PanedWindowLostSlaveProc(ClientData clientData,
202			    Tk_Window tkwin);
203static void		PanedWindowReqProc(ClientData clientData,
204			    Tk_Window tkwin);
205static void		ArrangePanes(ClientData clientData);
206static void		Unlink(Slave *slavePtr);
207static Slave *		GetPane(PanedWindow *pwPtr, Tk_Window tkwin);
208static void		SlaveStructureProc(ClientData clientData,
209			    XEvent *eventPtr);
210static int		PanedWindowSashCommand(PanedWindow *pwPtr,
211			    Tcl_Interp *interp, int objc,
212			    Tcl_Obj * CONST objv[]);
213static int		PanedWindowProxyCommand(PanedWindow *pwPtr,
214			    Tcl_Interp *interp, int objc,
215			    Tcl_Obj * CONST objv[]);
216static void		ComputeGeometry(PanedWindow *pwPtr);
217static int		ConfigureSlaves(PanedWindow *pwPtr,
218			    Tcl_Interp *interp, int objc,
219			    Tcl_Obj * CONST objv[]);
220static void		DestroyOptionTables(ClientData clientData,
221			    Tcl_Interp *interp);
222static int		SetSticky(ClientData clientData, Tcl_Interp *interp,
223			    Tk_Window tkwin, Tcl_Obj **value, char *recordPtr,
224			    int internalOffset, char *oldInternalPtr,
225			    int flags);
226static Tcl_Obj *	GetSticky(ClientData clientData, Tk_Window tkwin,
227			    char *recordPtr, int internalOffset);
228static void		RestoreSticky(ClientData clientData, Tk_Window tkwin,
229			    char *internalPtr, char *oldInternalPtr);
230static void		AdjustForSticky(int sticky, int cavityWidth,
231			    int cavityHeight, int *xPtr, int *yPtr,
232			    int *slaveWidthPtr, int *slaveHeightPtr);
233static void		MoveSash(PanedWindow *pwPtr, int sash, int diff);
234static int		ObjectIsEmpty(Tcl_Obj *objPtr);
235static char *		ComputeSlotAddress(char *recordPtr, int offset);
236static int		PanedWindowIdentifyCoords(PanedWindow *pwPtr,
237			    Tcl_Interp *interp, int x, int y);
238
239/*
240 * Sashes are between panes only, so there is one less sash than slaves
241 */
242
243#define ValidSashIndex(pwPtr, sash) \
244	(((sash) >= 0) && ((sash) < ((pwPtr)->numSlaves-1)))
245
246static const Tk_GeomMgr panedWindowMgrType = {
247    "panedwindow",		/* name */
248    PanedWindowReqProc,		/* requestProc */
249    PanedWindowLostSlaveProc,	/* lostSlaveProc */
250};
251
252/*
253 * Information used for objv parsing.
254 */
255
256#define GEOMETRY		0x0001
257
258/*
259 * The following structure contains pointers to functions used for processing
260 * the custom "-sticky" option for slave windows.
261 */
262
263static Tk_ObjCustomOption stickyOption = {
264    "sticky",			/* name */
265    SetSticky,			/* setProc */
266    GetSticky,			/* getProc */
267    RestoreSticky,		/* restoreProc */
268    NULL,			/* freeProc */
269    0
270};
271
272static const Tk_OptionSpec optionSpecs[] = {
273    {TK_OPTION_BORDER, "-background", "background", "Background",
274	 DEF_PANEDWINDOW_BG_COLOR, -1, Tk_Offset(PanedWindow, background), 0,
275	 (ClientData) DEF_PANEDWINDOW_BG_MONO},
276    {TK_OPTION_SYNONYM, "-bd", NULL, NULL,
277	 NULL, 0, -1, 0, (ClientData) "-borderwidth"},
278    {TK_OPTION_SYNONYM, "-bg", NULL, NULL,
279	 NULL, 0, -1, 0, (ClientData) "-background"},
280    {TK_OPTION_PIXELS, "-borderwidth", "borderWidth", "BorderWidth",
281	 DEF_PANEDWINDOW_BORDERWIDTH, -1, Tk_Offset(PanedWindow, borderWidth),
282	 0, 0, GEOMETRY},
283    {TK_OPTION_CURSOR, "-cursor", "cursor", "Cursor",
284	 DEF_PANEDWINDOW_CURSOR, -1, Tk_Offset(PanedWindow, cursor),
285	 TK_OPTION_NULL_OK, 0, 0},
286    {TK_OPTION_PIXELS, "-handlepad", "handlePad", "HandlePad",
287	 DEF_PANEDWINDOW_HANDLEPAD, -1, Tk_Offset(PanedWindow, handlePad),
288	 0, 0, GEOMETRY},
289    {TK_OPTION_PIXELS, "-handlesize", "handleSize", "HandleSize",
290	 DEF_PANEDWINDOW_HANDLESIZE, Tk_Offset(PanedWindow, handleSizePtr),
291	 Tk_Offset(PanedWindow, handleSize), 0, 0, GEOMETRY},
292    {TK_OPTION_PIXELS, "-height", "height", "Height",
293	 DEF_PANEDWINDOW_HEIGHT, Tk_Offset(PanedWindow, heightPtr),
294	 Tk_Offset(PanedWindow, height), TK_OPTION_NULL_OK, 0, GEOMETRY},
295    {TK_OPTION_BOOLEAN, "-opaqueresize", "opaqueResize", "OpaqueResize",
296	 DEF_PANEDWINDOW_OPAQUERESIZE, -1,
297	 Tk_Offset(PanedWindow, resizeOpaque), 0, 0, 0},
298    {TK_OPTION_STRING_TABLE, "-orient", "orient", "Orient",
299	 DEF_PANEDWINDOW_ORIENT, -1, Tk_Offset(PanedWindow, orient),
300	 0, (ClientData) orientStrings, GEOMETRY},
301    {TK_OPTION_RELIEF, "-relief", "relief", "Relief",
302	 DEF_PANEDWINDOW_RELIEF, -1, Tk_Offset(PanedWindow, relief), 0, 0, 0},
303    {TK_OPTION_CURSOR, "-sashcursor", "sashCursor", "Cursor",
304	 DEF_PANEDWINDOW_SASHCURSOR, -1, Tk_Offset(PanedWindow, sashCursor),
305	 TK_OPTION_NULL_OK, 0, 0},
306    {TK_OPTION_PIXELS, "-sashpad", "sashPad", "SashPad",
307	 DEF_PANEDWINDOW_SASHPAD, -1, Tk_Offset(PanedWindow, sashPad),
308	 0, 0, GEOMETRY},
309    {TK_OPTION_RELIEF, "-sashrelief", "sashRelief", "Relief",
310	 DEF_PANEDWINDOW_SASHRELIEF, -1, Tk_Offset(PanedWindow, sashRelief),
311	 0, 0, 0},
312    {TK_OPTION_PIXELS, "-sashwidth", "sashWidth", "Width",
313	 DEF_PANEDWINDOW_SASHWIDTH, Tk_Offset(PanedWindow, sashWidthPtr),
314	 Tk_Offset(PanedWindow, sashWidth), 0, 0, GEOMETRY},
315    {TK_OPTION_BOOLEAN, "-showhandle", "showHandle", "ShowHandle",
316	 DEF_PANEDWINDOW_SHOWHANDLE, -1, Tk_Offset(PanedWindow, showHandle),
317	 0, 0, GEOMETRY},
318    {TK_OPTION_PIXELS, "-width", "width", "Width",
319	 DEF_PANEDWINDOW_WIDTH, Tk_Offset(PanedWindow, widthPtr),
320	 Tk_Offset(PanedWindow, width), TK_OPTION_NULL_OK, 0, GEOMETRY},
321    {TK_OPTION_END}
322};
323
324static const Tk_OptionSpec slaveOptionSpecs[] = {
325    {TK_OPTION_WINDOW, "-after", NULL, NULL,
326	 DEF_PANEDWINDOW_PANE_AFTER, -1, Tk_Offset(Slave, after),
327	 TK_OPTION_NULL_OK, 0, 0},
328    {TK_OPTION_WINDOW, "-before", NULL, NULL,
329	 DEF_PANEDWINDOW_PANE_BEFORE, -1, Tk_Offset(Slave, before),
330	 TK_OPTION_NULL_OK, 0, 0},
331    {TK_OPTION_PIXELS, "-height", NULL, NULL,
332	 DEF_PANEDWINDOW_PANE_HEIGHT, Tk_Offset(Slave, heightPtr),
333	 Tk_Offset(Slave, height), TK_OPTION_NULL_OK, 0, 0},
334    {TK_OPTION_BOOLEAN, "-hide", "hide", "Hide",
335	 DEF_PANEDWINDOW_PANE_HIDE, -1, Tk_Offset(Slave, hide), 0,0,GEOMETRY},
336    {TK_OPTION_PIXELS, "-minsize", NULL, NULL,
337	 DEF_PANEDWINDOW_PANE_MINSIZE, -1, Tk_Offset(Slave, minSize), 0, 0, 0},
338    {TK_OPTION_PIXELS, "-padx", NULL, NULL,
339	 DEF_PANEDWINDOW_PANE_PADX, -1, Tk_Offset(Slave, padx), 0, 0, 0},
340    {TK_OPTION_PIXELS, "-pady", NULL, NULL,
341	 DEF_PANEDWINDOW_PANE_PADY, -1, Tk_Offset(Slave, pady), 0, 0, 0},
342    {TK_OPTION_CUSTOM, "-sticky", NULL, NULL,
343	 DEF_PANEDWINDOW_PANE_STICKY, -1, Tk_Offset(Slave, sticky), 0,
344	 (ClientData) &stickyOption, 0},
345    {TK_OPTION_STRING_TABLE, "-stretch", "stretch", "Stretch",
346	DEF_PANEDWINDOW_PANE_STRETCH, -1, Tk_Offset(Slave, stretch), 0,
347	(ClientData) stretchStrings, 0},
348    {TK_OPTION_PIXELS, "-width", NULL, NULL,
349	 DEF_PANEDWINDOW_PANE_WIDTH, Tk_Offset(Slave, widthPtr),
350	 Tk_Offset(Slave, width), TK_OPTION_NULL_OK, 0, 0},
351    {TK_OPTION_END}
352};
353
354/*
355 *--------------------------------------------------------------
356 *
357 * Tk_PanedWindowObjCmd --
358 *
359 *	This function is invoked to process the "panedwindow" Tcl command. It
360 *	creates a new "panedwindow" widget.
361 *
362 * Results:
363 *	A standard Tcl result.
364 *
365 * Side effects:
366 *	A new widget is created and configured.
367 *
368 *--------------------------------------------------------------
369 */
370
371int
372Tk_PanedWindowObjCmd(
373    ClientData clientData,	/* NULL. */
374    Tcl_Interp *interp,		/* Current interpreter. */
375    int objc,			/* Number of arguments. */
376    Tcl_Obj * CONST objv[])	/* Argument objects. */
377{
378    PanedWindow *pwPtr;
379    Tk_Window tkwin, parent;
380    OptionTables *pwOpts;
381    XSetWindowAttributes atts;
382
383    if (objc < 2) {
384	Tcl_WrongNumArgs(interp, 1, objv, "pathName ?options?");
385	return TCL_ERROR;
386    }
387
388    tkwin = Tk_CreateWindowFromPath(interp, Tk_MainWindow(interp),
389	    Tcl_GetStringFromObj(objv[1], NULL), NULL);
390    if (tkwin == NULL) {
391	return TCL_ERROR;
392    }
393
394    pwOpts = (OptionTables *)
395	    Tcl_GetAssocData(interp, "PanedWindowOptionTables", NULL);
396    if (pwOpts == NULL) {
397	/*
398	 * The first time this function is invoked, the option tables will be
399	 * NULL. We then create the option tables from the templates and store
400	 * a pointer to the tables as the command's clinical so we'll have
401	 * easy access to it in the future.
402	 */
403
404	pwOpts = (OptionTables *) ckalloc(sizeof(OptionTables));
405
406	/*
407	 * Set up an exit handler to free the optionTables struct.
408	 */
409
410	Tcl_SetAssocData(interp, "PanedWindowOptionTables",
411		DestroyOptionTables, (ClientData) pwOpts);
412
413	/*
414	 * Create the paned window option tables.
415	 */
416
417	pwOpts->pwOptions = Tk_CreateOptionTable(interp, optionSpecs);
418	pwOpts->slaveOpts = Tk_CreateOptionTable(interp, slaveOptionSpecs);
419    }
420
421    Tk_SetClass(tkwin, "Panedwindow");
422
423    /*
424     * Allocate and initialize the widget record.
425     */
426
427    pwPtr = (PanedWindow *) ckalloc(sizeof(PanedWindow));
428    memset((void *)pwPtr, 0, (sizeof(PanedWindow)));
429    pwPtr->tkwin = tkwin;
430    pwPtr->display = Tk_Display(tkwin);
431    pwPtr->interp = interp;
432    pwPtr->widgetCmd = Tcl_CreateObjCommand(interp,
433	    Tk_PathName(pwPtr->tkwin), PanedWindowWidgetObjCmd,
434	    (ClientData) pwPtr, PanedWindowCmdDeletedProc);
435    pwPtr->optionTable = pwOpts->pwOptions;
436    pwPtr->slaveOpts = pwOpts->slaveOpts;
437    pwPtr->relief = TK_RELIEF_RAISED;
438    pwPtr->gc = None;
439    pwPtr->cursor = None;
440    pwPtr->sashCursor = None;
441
442    /*
443     * Keep a hold of the associated tkwin until we destroy the widget,
444     * otherwise Tk might free it while we still need it.
445     */
446
447    Tcl_Preserve((ClientData) pwPtr->tkwin);
448
449    if (Tk_InitOptions(interp, (char *) pwPtr, pwOpts->pwOptions,
450	    tkwin) != TCL_OK) {
451	Tk_DestroyWindow(pwPtr->tkwin);
452	return TCL_ERROR;
453    }
454
455    Tk_CreateEventHandler(pwPtr->tkwin, ExposureMask|StructureNotifyMask,
456	    PanedWindowEventProc, (ClientData) pwPtr);
457
458    /*
459     * Find the toplevel ancestor of the panedwindow, and make a proxy win as
460     * a child of that window; this way the proxy can always float above
461     * slaves in the panedwindow.
462     */
463
464    parent = Tk_Parent(pwPtr->tkwin);
465    while (!(Tk_IsTopLevel(parent))) {
466	parent = Tk_Parent(parent);
467	if (parent == NULL) {
468	    parent = pwPtr->tkwin;
469	    break;
470	}
471    }
472
473    pwPtr->proxywin = Tk_CreateAnonymousWindow(interp, parent, NULL);
474
475    /*
476     * The proxy window has to be able to share GCs with the main panedwindow
477     * despite being children of windows with potentially different
478     * characteristics, and it looks better that way too. [Bug 702230] Also
479     * set the X window save under attribute to avoid expose events as the
480     * proxy sash is dragged across the panes. [Bug 1036963]
481     */
482
483    Tk_SetWindowVisual(pwPtr->proxywin,
484	    Tk_Visual(tkwin), Tk_Depth(tkwin), Tk_Colormap(tkwin));
485    Tk_CreateEventHandler(pwPtr->proxywin, ExposureMask, ProxyWindowEventProc,
486	    (ClientData) pwPtr);
487    atts.save_under = True;
488    Tk_ChangeWindowAttributes(pwPtr->proxywin, CWSaveUnder, &atts);
489
490    if (ConfigurePanedWindow(interp, pwPtr, objc - 2, objv + 2) != TCL_OK) {
491	Tk_DestroyWindow(pwPtr->proxywin);
492	Tk_DestroyWindow(pwPtr->tkwin);
493	return TCL_ERROR;
494    }
495
496    Tcl_SetStringObj(Tcl_GetObjResult(interp), Tk_PathName(pwPtr->tkwin), -1);
497    return TCL_OK;
498}
499
500/*
501 *--------------------------------------------------------------
502 *
503 * PanedWindowWidgetObjCmd --
504 *
505 *	This function is invoked to process the Tcl command that corresponds
506 *	to a widget managed by this module. See the user documentation for
507 *	details on what it does.
508 *
509 * Results:
510 *	A standard Tcl result.
511 *
512 * Side effects:
513 *	See the user documentation.
514 *
515 *--------------------------------------------------------------
516 */
517
518static int
519PanedWindowWidgetObjCmd(
520    ClientData clientData,	/* Information about square widget. */
521    Tcl_Interp *interp,		/* Current interpreter. */
522    int objc,			/* Number of arguments. */
523    Tcl_Obj * CONST objv[])	/* Argument objects. */
524{
525    PanedWindow *pwPtr = (PanedWindow *) clientData;
526    int result = TCL_OK;
527    static CONST char *optionStrings[] = {
528	"add", "cget", "configure", "forget", "identify", "panecget",
529	"paneconfigure", "panes", "proxy", "sash", NULL
530    };
531    enum options {
532	PW_ADD, PW_CGET, PW_CONFIGURE, PW_FORGET, PW_IDENTIFY, PW_PANECGET,
533	PW_PANECONFIGURE, PW_PANES, PW_PROXY, PW_SASH
534    };
535    Tcl_Obj *resultObj;
536    int index, count, i, x, y;
537    Tk_Window tkwin;
538    Slave *slavePtr;
539
540    if (objc < 2) {
541	Tcl_WrongNumArgs(interp, 1, objv, "option ?arg arg...?");
542	return TCL_ERROR;
543    }
544
545    if (Tcl_GetIndexFromObj(interp, objv[1], optionStrings, "command",
546	    0, &index) != TCL_OK) {
547	return TCL_ERROR;
548    }
549
550    Tcl_Preserve((ClientData) pwPtr);
551
552    switch ((enum options) index) {
553    case PW_ADD:
554	if (objc < 3) {
555	    Tcl_WrongNumArgs(interp, 2, objv, "widget ?widget ...?");
556	    result = TCL_ERROR;
557	    break;
558	}
559	result = ConfigureSlaves(pwPtr, interp, objc, objv);
560	break;
561
562    case PW_CGET:
563	if (objc != 3) {
564	    Tcl_WrongNumArgs(interp, 2, objv, "option");
565	    result = TCL_ERROR;
566	    break;
567	}
568	resultObj = Tk_GetOptionValue(interp, (char *) pwPtr,
569		pwPtr->optionTable, objv[2], pwPtr->tkwin);
570	if (resultObj == NULL) {
571	    result = TCL_ERROR;
572	} else {
573	    Tcl_SetObjResult(interp, resultObj);
574	}
575	break;
576
577    case PW_CONFIGURE:
578	resultObj = NULL;
579	if (objc <= 3) {
580	    resultObj = Tk_GetOptionInfo(interp, (char *) pwPtr,
581		    pwPtr->optionTable,
582		    (objc == 3) ? objv[2] : NULL, pwPtr->tkwin);
583	    if (resultObj == NULL) {
584		result = TCL_ERROR;
585	    } else {
586		Tcl_SetObjResult(interp, resultObj);
587	    }
588	} else {
589	    result = ConfigurePanedWindow(interp, pwPtr, objc - 2, objv + 2);
590	}
591	break;
592
593    case PW_FORGET: {
594	int i;
595
596	if (objc < 3) {
597	    Tcl_WrongNumArgs(interp, 2, objv, "widget ?widget ...?");
598	    result = TCL_ERROR;
599	    break;
600	}
601
602	/*
603	 * Clean up each window named in the arg list.
604	 */
605	for (count = 0, i = 2; i < objc; i++) {
606	    Tk_Window slave = Tk_NameToWindow(interp, Tcl_GetString(objv[i]),
607		    pwPtr->tkwin);
608	    if (slave == NULL) {
609		continue;
610	    }
611	    slavePtr = GetPane(pwPtr, slave);
612	    if ((slavePtr != NULL) && (slavePtr->masterPtr != NULL)) {
613		count++;
614		Tk_ManageGeometry(slave, NULL, (ClientData)NULL);
615		Tk_UnmaintainGeometry(slavePtr->tkwin, pwPtr->tkwin);
616		Tk_DeleteEventHandler(slavePtr->tkwin, StructureNotifyMask,
617			SlaveStructureProc, (ClientData) slavePtr);
618		Tk_UnmapWindow(slavePtr->tkwin);
619		Unlink(slavePtr);
620	    }
621	    if (count != 0) {
622		ComputeGeometry(pwPtr);
623	    }
624	}
625	break;
626    }
627
628    case PW_IDENTIFY:
629	if (objc != 4) {
630	    Tcl_WrongNumArgs(interp, 2, objv, "x y");
631	    result = TCL_ERROR;
632	    break;
633	}
634
635	if ((Tcl_GetIntFromObj(interp, objv[2], &x) != TCL_OK)
636		|| (Tcl_GetIntFromObj(interp, objv[3], &y) != TCL_OK)) {
637	    result = TCL_ERROR;
638	    break;
639	}
640	result = PanedWindowIdentifyCoords(pwPtr, interp, x, y);
641	break;
642
643    case PW_PANECGET:
644	if (objc != 4) {
645	    Tcl_WrongNumArgs(interp, 2, objv, "pane option");
646	    result = TCL_ERROR;
647	    break;
648	}
649	tkwin = Tk_NameToWindow(interp, Tcl_GetString(objv[2]), pwPtr->tkwin);
650	if (tkwin == NULL) {
651	    result = TCL_ERROR;
652	    break;
653	}
654	resultObj = NULL;
655	for (i = 0; i < pwPtr->numSlaves; i++) {
656	    if (pwPtr->slaves[i]->tkwin == tkwin) {
657		resultObj = Tk_GetOptionValue(interp,
658			(char *) pwPtr->slaves[i], pwPtr->slaveOpts,
659			objv[3], tkwin);
660	    }
661	}
662	if (i == pwPtr->numSlaves) {
663	    Tcl_SetResult(interp, "not managed by this window", TCL_STATIC);
664	}
665	if (resultObj == NULL) {
666	    result = TCL_ERROR;
667	} else {
668	    Tcl_SetObjResult(interp, resultObj);
669	}
670	break;
671
672    case PW_PANECONFIGURE:
673	if (objc < 3) {
674	    Tcl_WrongNumArgs(interp, 2, objv,
675		    "pane ?option? ?value option value ...?");
676	    result = TCL_ERROR;
677	    break;
678	}
679	resultObj = NULL;
680	if (objc <= 4) {
681	    tkwin = Tk_NameToWindow(interp, Tcl_GetString(objv[2]),
682		    pwPtr->tkwin);
683	    for (i = 0; i < pwPtr->numSlaves; i++) {
684		if (pwPtr->slaves[i]->tkwin == tkwin) {
685		    resultObj = Tk_GetOptionInfo(interp,
686			    (char *) pwPtr->slaves[i], pwPtr->slaveOpts,
687			    (objc == 4) ? objv[3] : NULL,
688			    pwPtr->tkwin);
689		    if (resultObj == NULL) {
690			result = TCL_ERROR;
691		    } else {
692			Tcl_SetObjResult(interp, resultObj);
693		    }
694		    break;
695		}
696	    }
697	} else {
698	    result = ConfigureSlaves(pwPtr, interp, objc, objv);
699	}
700	break;
701
702    case PW_PANES:
703	resultObj = Tcl_NewObj();
704
705	Tcl_IncrRefCount(resultObj);
706
707	for (i = 0; i < pwPtr->numSlaves; i++) {
708	    Tcl_ListObjAppendElement(interp, resultObj,
709		    Tcl_NewStringObj(Tk_PathName(pwPtr->slaves[i]->tkwin),-1));
710	}
711	Tcl_SetObjResult(interp, resultObj);
712	Tcl_DecrRefCount(resultObj);
713	break;
714
715    case PW_PROXY:
716	result = PanedWindowProxyCommand(pwPtr, interp, objc, objv);
717	break;
718
719    case PW_SASH:
720	result = PanedWindowSashCommand(pwPtr, interp, objc, objv);
721	break;
722    }
723    Tcl_Release((ClientData) pwPtr);
724    return result;
725}
726
727/*
728 *----------------------------------------------------------------------
729 *
730 * ConfigureSlaves --
731 *
732 *	Add or alter the configuration options of a slave in a paned window.
733 *
734 * Results:
735 *	Standard Tcl result.
736 *
737 * Side effects:
738 *	Depends on options; may add a slave to the paned window, may alter the
739 *	geometry management options of a slave.
740 *
741 *----------------------------------------------------------------------
742 */
743
744static int
745ConfigureSlaves(
746    PanedWindow *pwPtr,		/* Information about paned window. */
747    Tcl_Interp *interp,		/* Current interpreter. */
748    int objc,			/* Number of arguments. */
749    Tcl_Obj *CONST objv[])	/* Argument objects. */
750{
751    int i, firstOptionArg, j, found, doubleBw, index, numNewSlaves, haveLoc;
752    int insertIndex;
753    Tk_Window tkwin = NULL, ancestor, parent;
754    Slave *slavePtr, **inserts, **newSlaves;
755    Slave options;
756    char *arg;
757
758    /*
759     * Find the non-window name arguments; these are the configure options for
760     * the slaves. Also validate that the window names given are legitimate
761     * (ie, they are real windows, they are not the panedwindow itself, etc.).
762     */
763
764    for (i = 2; i < objc; i++) {
765	arg = Tcl_GetString(objv[i]);
766	if (arg[0] == '-') {
767	    break;
768	} else {
769	    tkwin = Tk_NameToWindow(interp, arg, pwPtr->tkwin);
770	    if (tkwin == NULL) {
771		/*
772		 * Just a plain old bad window; Tk_NameToWindow filled in an
773		 * error message for us.
774		 */
775
776		return TCL_ERROR;
777	    } else if (tkwin == pwPtr->tkwin) {
778		/*
779		 * A panedwindow cannot manage itself.
780		 */
781
782		Tcl_ResetResult(interp);
783		Tcl_AppendResult(interp, "can't add ", arg, " to itself",
784			NULL);
785		return TCL_ERROR;
786	    } else if (Tk_IsTopLevel(tkwin)) {
787		/*
788		 * A panedwindow cannot manage a toplevel.
789		 */
790
791		Tcl_ResetResult(interp);
792		Tcl_AppendResult(interp, "can't add toplevel ", arg, " to ",
793			Tk_PathName(pwPtr->tkwin), NULL);
794		return TCL_ERROR;
795	    } else {
796		/*
797		 * Make sure the panedwindow is the parent of the slave,
798		 * or a descendant of the slave's parent.
799		 */
800
801		parent = Tk_Parent(tkwin);
802		for (ancestor = pwPtr->tkwin;;ancestor = Tk_Parent(ancestor)) {
803		    if (ancestor == parent) {
804			break;
805		    }
806		    if (Tk_IsTopLevel(ancestor)) {
807			Tcl_ResetResult(interp);
808			Tcl_AppendResult(interp, "can't add ", arg, " to ",
809				Tk_PathName(pwPtr->tkwin), NULL);
810			return TCL_ERROR;
811		    }
812		}
813	    }
814	}
815    }
816    firstOptionArg = i;
817
818    /*
819     * Pre-parse the configuration options, to get the before/after specifiers
820     * into an easy-to-find location (a local variable). Also, check the
821     * return from Tk_SetOptions once, here, so we can save a little bit of
822     * extra testing in the for loop below.
823     */
824
825    memset((void *)&options, 0, sizeof(Slave));
826    if (Tk_SetOptions(interp, (char *) &options, pwPtr->slaveOpts,
827	    objc - firstOptionArg, objv + firstOptionArg,
828	    pwPtr->tkwin, NULL, NULL) != TCL_OK) {
829	return TCL_ERROR;
830    }
831
832    /*
833     * If either -after or -before was given, find the numerical index that
834     * corresponds to the given window. If both -after and -before are given,
835     * the option precedence is: -after, then -before.
836     */
837
838    index = -1;
839    haveLoc = 0;
840    if (options.after != None) {
841	tkwin = options.after;
842	haveLoc = 1;
843	for (i = 0; i < pwPtr->numSlaves; i++) {
844	    if (options.after == pwPtr->slaves[i]->tkwin) {
845		index = i + 1;
846		break;
847	    }
848	}
849    } else if (options.before != None) {
850	tkwin = options.before;
851	haveLoc = 1;
852	for (i = 0; i < pwPtr->numSlaves; i++) {
853	    if (options.before == pwPtr->slaves[i]->tkwin) {
854		index = i;
855		break;
856	    }
857	}
858    }
859
860    /*
861     * If a window was given for -after/-before, but it's not a window managed
862     * by the panedwindow, throw an error
863     */
864
865    if (haveLoc && index == -1) {
866	Tcl_ResetResult(interp);
867	Tcl_AppendResult(interp, "window \"", Tk_PathName(tkwin),
868		"\" is not managed by ", Tk_PathName(pwPtr->tkwin), NULL);
869	Tk_FreeConfigOptions((char *) &options, pwPtr->slaveOpts,
870		pwPtr->tkwin);
871	return TCL_ERROR;
872    }
873
874    /*
875     * Allocate an array to hold, in order, the pointers to the slave
876     * structures corresponding to the windows specified. Some of those
877     * structures may already have existed, some may be new.
878     */
879
880    inserts = (Slave **)ckalloc(sizeof(Slave *) * (firstOptionArg - 2));
881    insertIndex = 0;
882
883    /*
884     * Populate the inserts array, creating new slave structures as necessary,
885     * applying the options to each structure as we go, and, if necessary,
886     * marking the spot in the original slaves array as empty (for
887     * pre-existing slave structures).
888     */
889
890    for (i = 0, numNewSlaves = 0; i < firstOptionArg - 2; i++) {
891	/*
892	 * We don't check that tkwin is NULL here, because the pre-pass above
893	 * guarantees that the input at this stage is good.
894	 */
895
896	tkwin = Tk_NameToWindow(interp, Tcl_GetString(objv[i + 2]),
897		pwPtr->tkwin);
898
899	found = 0;
900	for (j = 0; j < pwPtr->numSlaves; j++) {
901	    if (pwPtr->slaves[j] != NULL && pwPtr->slaves[j]->tkwin == tkwin) {
902		Tk_SetOptions(interp, (char *) pwPtr->slaves[j],
903			pwPtr->slaveOpts, objc - firstOptionArg,
904			objv + firstOptionArg, pwPtr->tkwin, NULL, NULL);
905		if (pwPtr->slaves[j]->minSize < 0) {
906		    pwPtr->slaves[j]->minSize = 0;
907		}
908		found = 1;
909
910		/*
911		 * If the slave is supposed to move, add it to the inserts
912		 * array now; otherwise, leave it where it is.
913		 */
914
915		if (index != -1) {
916		    inserts[insertIndex++] = pwPtr->slaves[j];
917		    pwPtr->slaves[j] = NULL;
918		}
919		break;
920	    }
921	}
922
923	if (found) {
924	    continue;
925	}
926
927	/*
928	 * Make sure this slave wasn't already put into the inserts array,
929	 * i.e., when the user specifies the same window multiple times in a
930	 * single add commaned.
931	 */
932	for (j = 0; j < insertIndex; j++) {
933	    if (inserts[j]->tkwin == tkwin) {
934		found = 1;
935		break;
936	    }
937	}
938	if (found) {
939	    continue;
940	}
941
942	/*
943	 * Create a new slave structure and initialize it. All slaves start
944	 * out with their "natural" dimensions.
945	 */
946
947	slavePtr = (Slave *) ckalloc(sizeof(Slave));
948	memset(slavePtr, 0, sizeof(Slave));
949	Tk_InitOptions(interp, (char *)slavePtr, pwPtr->slaveOpts,
950		pwPtr->tkwin);
951	Tk_SetOptions(interp, (char *)slavePtr, pwPtr->slaveOpts,
952		objc - firstOptionArg, objv + firstOptionArg,
953		pwPtr->tkwin, NULL, NULL);
954	slavePtr->tkwin = tkwin;
955	slavePtr->masterPtr = pwPtr;
956	doubleBw = 2 * Tk_Changes(slavePtr->tkwin)->border_width;
957	if (slavePtr->width > 0) {
958	    slavePtr->paneWidth = slavePtr->width;
959	} else {
960	    slavePtr->paneWidth = Tk_ReqWidth(tkwin) + doubleBw;
961	}
962	if (slavePtr->height > 0) {
963	    slavePtr->paneHeight = slavePtr->height;
964	} else {
965	    slavePtr->paneHeight = Tk_ReqHeight(tkwin) + doubleBw;
966	}
967	if (slavePtr->minSize < 0) {
968	    slavePtr->minSize = 0;
969	}
970
971	/*
972	 * Set up the geometry management callbacks for this slave.
973	 */
974
975	Tk_CreateEventHandler(slavePtr->tkwin, StructureNotifyMask,
976		SlaveStructureProc, (ClientData) slavePtr);
977	Tk_ManageGeometry(slavePtr->tkwin, &panedWindowMgrType,
978		(ClientData) slavePtr);
979	inserts[insertIndex++] = slavePtr;
980	numNewSlaves++;
981    }
982
983    /*
984     * Allocate the new slaves array, then copy the slaves into it, in order.
985     */
986
987    i = sizeof(Slave *) * (pwPtr->numSlaves+numNewSlaves);
988    newSlaves = (Slave **)ckalloc((unsigned) i);
989    memset(newSlaves, 0, (size_t) i);
990    if (index == -1) {
991	/*
992	 * If none of the existing slaves have to be moved, just copy the old
993	 * and append the new.
994	 */
995	memcpy((void *)&(newSlaves[0]), pwPtr->slaves,
996		sizeof(Slave *) * pwPtr->numSlaves);
997	memcpy((void *)&(newSlaves[pwPtr->numSlaves]), inserts,
998		sizeof(Slave *) * numNewSlaves);
999    } else {
1000	/*
1001	 * If some of the existing slaves were moved, the old slaves array
1002	 * will be partially populated, with some valid and some invalid
1003	 * entries. Walk through it, copying valid entries to the new slaves
1004	 * array as we go; when we get to the insert location for the new
1005	 * slaves, copy the inserts array over, then finish off the old slaves
1006	 * array.
1007	 */
1008
1009	for (i = 0, j = 0; i < index; i++) {
1010	    if (pwPtr->slaves[i] != NULL) {
1011		newSlaves[j] = pwPtr->slaves[i];
1012		j++;
1013	    }
1014	}
1015
1016	memcpy((void *)&(newSlaves[j]), inserts, sizeof(Slave *)*insertIndex);
1017	j += firstOptionArg - 2;
1018
1019	for (i = index; i < pwPtr->numSlaves; i++) {
1020	    if (pwPtr->slaves[i] != NULL) {
1021		newSlaves[j] = pwPtr->slaves[i];
1022		j++;
1023	    }
1024	}
1025    }
1026
1027    /*
1028     * Make the new slaves array the paned window's slave array, and clean up.
1029     */
1030
1031    ckfree((void *)pwPtr->slaves);
1032    ckfree((void *)inserts);
1033    pwPtr->slaves = newSlaves;
1034
1035    /*
1036     * Set the paned window's slave count to the new value.
1037     */
1038
1039    pwPtr->numSlaves += numNewSlaves;
1040
1041    Tk_FreeConfigOptions((char *) &options, pwPtr->slaveOpts, pwPtr->tkwin);
1042
1043    ComputeGeometry(pwPtr);
1044    return TCL_OK;
1045}
1046
1047/*
1048 *----------------------------------------------------------------------
1049 *
1050 * PanedWindowSashCommand --
1051 *
1052 *	Implementation of the panedwindow sash subcommand. See the user
1053 *	documentation for details on what it does.
1054 *
1055 * Results:
1056 *	Standard Tcl result.
1057 *
1058 * Side effects:
1059 *	Depends on the arguments.
1060 *
1061 *----------------------------------------------------------------------
1062 */
1063
1064static int
1065PanedWindowSashCommand(
1066    PanedWindow *pwPtr,		/* Pointer to paned window information. */
1067    Tcl_Interp *interp,		/* Current interpreter. */
1068    int objc,			/* Number of arguments. */
1069    Tcl_Obj *CONST objv[])	/* Argument objects. */
1070{
1071    static CONST char *sashOptionStrings[] = {
1072	"coord", "dragto", "mark", "place", NULL
1073    };
1074    enum sashOptions {
1075	SASH_COORD, SASH_DRAGTO, SASH_MARK, SASH_PLACE
1076    };
1077    int index, sash, x, y, diff;
1078    Tcl_Obj *coords[2];
1079    Slave *slavePtr;
1080
1081    if (objc < 3) {
1082	Tcl_WrongNumArgs(interp, 2, objv, "option ?arg ...?");
1083	return TCL_ERROR;
1084    }
1085
1086    if (Tcl_GetIndexFromObj(interp, objv[2], sashOptionStrings, "option", 0,
1087	    &index) != TCL_OK) {
1088	return TCL_ERROR;
1089    }
1090
1091    Tcl_ResetResult(interp);
1092    switch ((enum sashOptions) index) {
1093    case SASH_COORD:
1094	if (objc != 4) {
1095	    Tcl_WrongNumArgs(interp, 3, objv, "index");
1096	    return TCL_ERROR;
1097	}
1098
1099	if (Tcl_GetIntFromObj(interp, objv[3], &sash) != TCL_OK) {
1100	    return TCL_ERROR;
1101	}
1102
1103	if (!ValidSashIndex(pwPtr, sash)) {
1104	    Tcl_ResetResult(interp);
1105	    Tcl_SetResult(interp, "invalid sash index", TCL_STATIC);
1106	    return TCL_ERROR;
1107	}
1108	slavePtr = pwPtr->slaves[sash];
1109
1110	coords[0] = Tcl_NewIntObj(slavePtr->sashx);
1111	coords[1] = Tcl_NewIntObj(slavePtr->sashy);
1112	Tcl_SetListObj(Tcl_GetObjResult(interp), 2, coords);
1113	break;
1114
1115    case SASH_MARK:
1116	if (objc != 6 && objc != 4) {
1117	    Tcl_WrongNumArgs(interp, 3, objv, "index ?x y?");
1118	    return TCL_ERROR;
1119	}
1120
1121	if (Tcl_GetIntFromObj(interp, objv[3], &sash) != TCL_OK) {
1122	    return TCL_ERROR;
1123	}
1124
1125	if (!ValidSashIndex(pwPtr, sash)) {
1126	    Tcl_ResetResult(interp);
1127	    Tcl_SetResult(interp, "invalid sash index", TCL_STATIC);
1128	    return TCL_ERROR;
1129	}
1130
1131	if (objc == 6) {
1132	    if (Tcl_GetIntFromObj(interp, objv[4], &x) != TCL_OK) {
1133		return TCL_ERROR;
1134	    }
1135
1136	    if (Tcl_GetIntFromObj(interp, objv[5], &y) != TCL_OK) {
1137		return TCL_ERROR;
1138	    }
1139
1140	    pwPtr->slaves[sash]->markx = x;
1141	    pwPtr->slaves[sash]->marky = y;
1142	} else {
1143	    coords[0] = Tcl_NewIntObj(pwPtr->slaves[sash]->markx);
1144	    coords[1] = Tcl_NewIntObj(pwPtr->slaves[sash]->marky);
1145	    Tcl_SetListObj(Tcl_GetObjResult(interp), 2, coords);
1146	}
1147	break;
1148
1149    case SASH_DRAGTO:
1150    case SASH_PLACE:
1151	if (objc != 6) {
1152	    Tcl_WrongNumArgs(interp, 3, objv, "index x y");
1153	    return TCL_ERROR;
1154	}
1155
1156	if (Tcl_GetIntFromObj(interp, objv[3], &sash) != TCL_OK) {
1157	    return TCL_ERROR;
1158	}
1159
1160	if (!ValidSashIndex(pwPtr, sash)) {
1161	    Tcl_ResetResult(interp);
1162	    Tcl_SetResult(interp, "invalid sash index", TCL_STATIC);
1163	    return TCL_ERROR;
1164	}
1165
1166	if (Tcl_GetIntFromObj(interp, objv[4], &x) != TCL_OK) {
1167	    return TCL_ERROR;
1168	}
1169
1170	if (Tcl_GetIntFromObj(interp, objv[5], &y) != TCL_OK) {
1171	    return TCL_ERROR;
1172	}
1173
1174	slavePtr = pwPtr->slaves[sash];
1175	if (pwPtr->orient == ORIENT_HORIZONTAL) {
1176	    if (index == SASH_PLACE) {
1177		diff = x - pwPtr->slaves[sash]->sashx;
1178	    } else {
1179		diff = x - pwPtr->slaves[sash]->markx;
1180	    }
1181	} else {
1182	    if (index == SASH_PLACE) {
1183		diff = y - pwPtr->slaves[sash]->sashy;
1184	    } else {
1185		diff = y - pwPtr->slaves[sash]->marky;
1186	    }
1187	}
1188
1189	MoveSash(pwPtr, sash, diff);
1190	ComputeGeometry(pwPtr);
1191    }
1192    return TCL_OK;
1193}
1194
1195/*
1196 *----------------------------------------------------------------------
1197 *
1198 * ConfigurePanedWindow --
1199 *
1200 *	This function is called to process an argv/argc list in conjunction
1201 *	with the Tk option database to configure (or reconfigure) a paned
1202 *	window widget.
1203 *
1204 * Results:
1205 *	The return value is a standard Tcl result. If TCL_ERROR is returned,
1206 *	then the interp's result contains an error message.
1207 *
1208 * Side effects:
1209 *	Configuration information, such as colors, border width, etc. get set
1210 *	for pwPtr; old resources get freed, if there were any.
1211 *
1212 *----------------------------------------------------------------------
1213 */
1214
1215static int
1216ConfigurePanedWindow(
1217    Tcl_Interp *interp,		/* Used for error reporting. */
1218    PanedWindow *pwPtr,		/* Information about widget. */
1219    int objc,			/* Number of arguments. */
1220    Tcl_Obj *CONST objv[])	/* Argument values. */
1221{
1222    Tk_SavedOptions savedOptions;
1223    int typemask = 0;
1224
1225    if (Tk_SetOptions(interp, (char *) pwPtr, pwPtr->optionTable, objc, objv,
1226	    pwPtr->tkwin, &savedOptions, &typemask) != TCL_OK) {
1227	Tk_RestoreSavedOptions(&savedOptions);
1228	return TCL_ERROR;
1229    }
1230
1231    Tk_FreeSavedOptions(&savedOptions);
1232
1233    PanedWindowWorldChanged((ClientData) pwPtr);
1234
1235    /*
1236     * If an option that affects geometry has changed, make a re-layout
1237     * request.
1238     */
1239
1240    if (typemask & GEOMETRY) {
1241	ComputeGeometry(pwPtr);
1242    }
1243
1244    return TCL_OK;
1245}
1246
1247/*
1248 *----------------------------------------------------------------------
1249 *
1250 * PanedWindowWorldChanged --
1251 *
1252 *	This function is invoked anytime a paned window's world has changed in
1253 *	some way that causes the widget to have to recompute graphics contexts
1254 *	and geometry.
1255 *
1256 * Results:
1257 *	None.
1258 *
1259 * Side effects:
1260 *	Paned window will be relayed out and redisplayed.
1261 *
1262 *----------------------------------------------------------------------
1263 */
1264
1265static void
1266PanedWindowWorldChanged(
1267    ClientData instanceData)	/* Information about the paned window. */
1268{
1269    XGCValues gcValues;
1270    GC newGC;
1271    PanedWindow *pwPtr = (PanedWindow *) instanceData;
1272
1273    /*
1274     * Allocated a graphics context for drawing the paned window widget
1275     * elements (background, sashes, etc.) and set the window background.
1276     */
1277
1278    gcValues.background = Tk_3DBorderColor(pwPtr->background)->pixel;
1279    newGC = Tk_GetGC(pwPtr->tkwin, GCBackground, &gcValues);
1280    if (pwPtr->gc != None) {
1281	Tk_FreeGC(pwPtr->display, pwPtr->gc);
1282    }
1283    pwPtr->gc = newGC;
1284    Tk_SetWindowBackground(pwPtr->tkwin, gcValues.background);
1285
1286    /*
1287     * Issue geometry size requests to Tk.
1288     */
1289
1290    Tk_SetInternalBorder(pwPtr->tkwin, pwPtr->borderWidth);
1291    if (pwPtr->width > 0 && pwPtr->height > 0) {
1292	Tk_GeometryRequest(pwPtr->tkwin, pwPtr->width, pwPtr->height);
1293    }
1294
1295    /*
1296     * Arrange for the window to be redrawn, if neccessary.
1297     */
1298
1299    if (Tk_IsMapped(pwPtr->tkwin) && !(pwPtr->flags & REDRAW_PENDING)) {
1300	Tcl_DoWhenIdle(DisplayPanedWindow, (ClientData) pwPtr);
1301	pwPtr->flags |= REDRAW_PENDING;
1302    }
1303}
1304
1305/*
1306 *--------------------------------------------------------------
1307 *
1308 * PanedWindowEventProc --
1309 *
1310 *	This function is invoked by the Tk dispatcher for various events on
1311 *	paned windows.
1312 *
1313 * Results:
1314 *	None.
1315 *
1316 * Side effects:
1317 *	When the window gets deleted, internal structures get cleaned up. When
1318 *	it gets exposed, it is redisplayed.
1319 *
1320 *--------------------------------------------------------------
1321 */
1322
1323static void
1324PanedWindowEventProc(
1325    ClientData clientData,	/* Information about window. */
1326    XEvent *eventPtr)		/* Information about event. */
1327{
1328    PanedWindow *pwPtr = (PanedWindow *) clientData;
1329
1330    if (eventPtr->type == Expose) {
1331	if (pwPtr->tkwin != NULL && !(pwPtr->flags & REDRAW_PENDING)) {
1332	    Tcl_DoWhenIdle(DisplayPanedWindow, (ClientData) pwPtr);
1333	    pwPtr->flags |= REDRAW_PENDING;
1334	}
1335    } else if (eventPtr->type == ConfigureNotify) {
1336	pwPtr->flags |= REQUESTED_RELAYOUT;
1337	if (pwPtr->tkwin != NULL && !(pwPtr->flags & REDRAW_PENDING)) {
1338	    Tcl_DoWhenIdle(DisplayPanedWindow, (ClientData) pwPtr);
1339	    pwPtr->flags |= REDRAW_PENDING;
1340	}
1341    } else if (eventPtr->type == DestroyNotify) {
1342	DestroyPanedWindow(pwPtr);
1343    }
1344}
1345
1346/*
1347 *----------------------------------------------------------------------
1348 *
1349 * PanedWindowCmdDeletedProc --
1350 *
1351 *	This function is invoked when a widget command is deleted. If the
1352 *	widget isn't already in the process of being destroyed, this command
1353 *	destroys it.
1354 *
1355 * Results:
1356 *	None.
1357 *
1358 * Side effects:
1359 *	The widget is destroyed.
1360 *
1361 *----------------------------------------------------------------------
1362 */
1363
1364static void
1365PanedWindowCmdDeletedProc(
1366    ClientData clientData)	/* Pointer to widget record for widget. */
1367{
1368    PanedWindow *pwPtr = (PanedWindow *) clientData;
1369
1370    /*
1371     * This function could be invoked either because the window was destroyed
1372     * and the command was then deleted or because the command was deleted,
1373     * and then this function destroys the widget. The WIDGET_DELETED flag
1374     * distinguishes these cases.
1375     */
1376
1377    if (!(pwPtr->flags & WIDGET_DELETED)) {
1378	Tk_DestroyWindow(pwPtr->proxywin);
1379	Tk_DestroyWindow(pwPtr->tkwin);
1380    }
1381}
1382
1383/*
1384 *--------------------------------------------------------------
1385 *
1386 * DisplayPanedWindow --
1387 *
1388 *	This function redraws the contents of a paned window widget. It is
1389 *	invoked as a do-when-idle handler, so it only runs when there's
1390 *	nothing else for the application to do.
1391 *
1392 * Results:
1393 *	None.
1394 *
1395 * Side effects:
1396 *	Information appears on the screen.
1397 *
1398 *--------------------------------------------------------------
1399 */
1400
1401static void
1402DisplayPanedWindow(
1403    ClientData clientData)	/* Information about window. */
1404{
1405    PanedWindow *pwPtr = (PanedWindow *) clientData;
1406    Slave *slavePtr;
1407    Pixmap pixmap;
1408    Tk_Window tkwin = pwPtr->tkwin;
1409    int i, sashWidth, sashHeight;
1410    const int horizontal = (pwPtr->orient == ORIENT_HORIZONTAL);
1411
1412    pwPtr->flags &= ~REDRAW_PENDING;
1413    if ((pwPtr->tkwin == NULL) || !Tk_IsMapped(tkwin)) {
1414	return;
1415    }
1416
1417    if (pwPtr->flags & REQUESTED_RELAYOUT) {
1418	ArrangePanes(clientData);
1419    }
1420
1421#ifndef TK_NO_DOUBLE_BUFFERING
1422    /*
1423     * Create a pixmap for double-buffering, if necessary.
1424     */
1425
1426    pixmap = Tk_GetPixmap(Tk_Display(tkwin), Tk_WindowId(tkwin),
1427	    Tk_Width(tkwin), Tk_Height(tkwin), Tk_Depth(tkwin));
1428#else
1429    pixmap = Tk_WindowId(tkwin);
1430#endif /* TK_NO_DOUBLE_BUFFERING */
1431
1432    /*
1433     * Redraw the widget's background and border.
1434     */
1435
1436    Tk_Fill3DRectangle(tkwin, pixmap, pwPtr->background, 0, 0,
1437	    Tk_Width(tkwin), Tk_Height(tkwin), pwPtr->borderWidth,
1438	    pwPtr->relief);
1439
1440    /*
1441     * Set up boilerplate geometry values for sashes (width, height, common
1442     * coordinates).
1443     */
1444
1445    if (horizontal) {
1446	sashHeight = Tk_Height(tkwin) - (2 * Tk_InternalBorderWidth(tkwin));
1447	sashWidth = pwPtr->sashWidth;
1448    } else {
1449	sashWidth = Tk_Width(tkwin) - (2 * Tk_InternalBorderWidth(tkwin));
1450	sashHeight = pwPtr->sashWidth;
1451    }
1452
1453    /*
1454     * Draw the sashes.
1455     */
1456
1457    for (i = 0; i < pwPtr->numSlaves - 1; i++) {
1458	slavePtr = pwPtr->slaves[i];
1459	if (slavePtr->hide) {
1460	    continue;
1461	}
1462	if (sashWidth > 0 && sashHeight > 0) {
1463	    Tk_Fill3DRectangle(tkwin, pixmap, pwPtr->background,
1464		    slavePtr->sashx, slavePtr->sashy, sashWidth, sashHeight,
1465		    1, pwPtr->sashRelief);
1466	}
1467	if (pwPtr->showHandle) {
1468	    Tk_Fill3DRectangle(tkwin, pixmap, pwPtr->background,
1469		    slavePtr->handlex, slavePtr->handley,
1470		    pwPtr->handleSize, pwPtr->handleSize, 1,
1471		    TK_RELIEF_RAISED);
1472	}
1473    }
1474
1475#ifndef TK_NO_DOUBLE_BUFFERING
1476    /*
1477     * Copy the information from the off-screen pixmap onto the screen, then
1478     * delete the pixmap.
1479     */
1480
1481    XCopyArea(Tk_Display(tkwin), pixmap, Tk_WindowId(tkwin), pwPtr->gc, 0, 0,
1482	    (unsigned) Tk_Width(tkwin), (unsigned) Tk_Height(tkwin), 0, 0);
1483    Tk_FreePixmap(Tk_Display(tkwin), pixmap);
1484#endif /* TK_NO_DOUBLE_BUFFERING */
1485}
1486
1487/*
1488 *----------------------------------------------------------------------
1489 *
1490 * DestroyPanedWindow --
1491 *
1492 *	This function is invoked by PanedWindowEventProc to free the internal
1493 *	structure of a paned window.
1494 *
1495 * Results:
1496 *	None.
1497 *
1498 * Side effects:
1499 *	Everything associated with the paned window is freed up.
1500 *
1501 *----------------------------------------------------------------------
1502 */
1503
1504static void
1505DestroyPanedWindow(
1506    PanedWindow *pwPtr)		/* Info about paned window widget. */
1507{
1508    int i;
1509
1510    /*
1511     * First mark the widget as in the process of being deleted, so that any
1512     * code that causes calls to other paned window functions will abort.
1513     */
1514
1515    pwPtr->flags |= WIDGET_DELETED;
1516
1517    /*
1518     * Cancel idle callbacks for redrawing the widget and for rearranging the
1519     * panes.
1520     */
1521
1522    if (pwPtr->flags & REDRAW_PENDING) {
1523	Tcl_CancelIdleCall(DisplayPanedWindow, (ClientData) pwPtr);
1524    }
1525    if (pwPtr->flags & RESIZE_PENDING) {
1526	Tcl_CancelIdleCall(ArrangePanes, (ClientData) pwPtr);
1527    }
1528
1529    /*
1530     * Clean up the slave list; foreach slave:
1531     *  o  Cancel the slave's structure notification callback
1532     *  o  Cancel geometry management for the slave.
1533     *  o  Free memory for the slave
1534     */
1535
1536    for (i = 0; i < pwPtr->numSlaves; i++) {
1537	Tk_DeleteEventHandler(pwPtr->slaves[i]->tkwin, StructureNotifyMask,
1538		SlaveStructureProc, (ClientData) pwPtr->slaves[i]);
1539	Tk_ManageGeometry(pwPtr->slaves[i]->tkwin, NULL, NULL);
1540	Tk_FreeConfigOptions((char *)pwPtr->slaves[i], pwPtr->slaveOpts,
1541		pwPtr->tkwin);
1542	ckfree((void *)pwPtr->slaves[i]);
1543	pwPtr->slaves[i] = NULL;
1544    }
1545    if (pwPtr->slaves) {
1546	ckfree((char *) pwPtr->slaves);
1547    }
1548
1549    /*
1550     * Remove the widget command from the interpreter.
1551     */
1552
1553    Tcl_DeleteCommandFromToken(pwPtr->interp, pwPtr->widgetCmd);
1554
1555    /*
1556     * Let Tk_FreeConfigOptions clean up the rest.
1557     */
1558
1559    Tk_FreeConfigOptions((char *) pwPtr, pwPtr->optionTable, pwPtr->tkwin);
1560    Tcl_Release((ClientData) pwPtr->tkwin);
1561    pwPtr->tkwin = NULL;
1562
1563    Tcl_EventuallyFree((ClientData) pwPtr, TCL_DYNAMIC);
1564}
1565
1566/*
1567 *--------------------------------------------------------------
1568 *
1569 * PanedWindowReqProc --
1570 *
1571 *	This function is invoked by Tk_GeometryRequest for windows managed by
1572 *	a paned window.
1573 *
1574 * Results:
1575 *	None.
1576 *
1577 * Side effects:
1578 *	Arranges for tkwin, and all its managed siblings, to be re-arranged at
1579 *	the next idle point.
1580 *
1581 *--------------------------------------------------------------
1582 */
1583
1584static void
1585PanedWindowReqProc(
1586    ClientData clientData,	/* Paned window's information about window
1587				 * that got new preferred geometry. */
1588    Tk_Window tkwin)		/* Other Tk-related information about the
1589				 * window. */
1590{
1591    Slave *slavePtr = (Slave *) clientData;
1592    PanedWindow *pwPtr = (PanedWindow *) (slavePtr->masterPtr);
1593    if (Tk_IsMapped(pwPtr->tkwin)) {
1594	if (!(pwPtr->flags & RESIZE_PENDING)) {
1595	    pwPtr->flags |= RESIZE_PENDING;
1596	    Tcl_DoWhenIdle(ArrangePanes, (ClientData) pwPtr);
1597	}
1598    } else {
1599	int doubleBw = 2 * Tk_Changes(slavePtr->tkwin)->border_width;
1600
1601	if (slavePtr->width <= 0) {
1602	    slavePtr->paneWidth = Tk_ReqWidth(slavePtr->tkwin) + doubleBw;
1603	}
1604	if (slavePtr->height <= 0) {
1605	    slavePtr->paneHeight = Tk_ReqHeight(slavePtr->tkwin) + doubleBw;
1606	}
1607	ComputeGeometry(pwPtr);
1608    }
1609}
1610
1611/*
1612 *--------------------------------------------------------------
1613 *
1614 * PanedWindowLostSlaveProc --
1615 *
1616 *	This function is invoked by Tk whenever some other geometry claims
1617 *	control over a slave that used to be managed by us.
1618 *
1619 * Results:
1620 *	None.
1621 *
1622 * Side effects:
1623 *	Forgets all information about the slave. Causes geometry to be
1624 *	recomputed for the panedwindow.
1625 *
1626 *--------------------------------------------------------------
1627 */
1628
1629static void
1630PanedWindowLostSlaveProc(
1631    ClientData clientData,	/* Grid structure for slave window that was
1632				 * stolen away. */
1633    Tk_Window tkwin)		/* Tk's handle for the slave window. */
1634{
1635    register Slave *slavePtr = (Slave *) clientData;
1636    PanedWindow *pwPtr = (PanedWindow *) (slavePtr->masterPtr);
1637
1638    if (pwPtr->tkwin != Tk_Parent(slavePtr->tkwin)) {
1639	Tk_UnmaintainGeometry(slavePtr->tkwin, pwPtr->tkwin);
1640    }
1641    Unlink(slavePtr);
1642    Tk_DeleteEventHandler(slavePtr->tkwin, StructureNotifyMask,
1643	    SlaveStructureProc, (ClientData) slavePtr);
1644    Tk_UnmapWindow(slavePtr->tkwin);
1645    slavePtr->tkwin = NULL;
1646    ckfree((void *)slavePtr);
1647    ComputeGeometry(pwPtr);
1648}
1649
1650/*
1651 *--------------------------------------------------------------
1652 *
1653 * ArrangePanes --
1654 *
1655 *	This function is invoked (using the Tcl_DoWhenIdle mechanism) to
1656 *	re-layout a set of windows managed by a paned window. It is invoked at
1657 *	idle time so that a series of pane requests can be merged into a
1658 *	single layout operation.
1659 *
1660 * Results:
1661 *	None.
1662 *
1663 * Side effects:
1664 *	The slaves of masterPtr may get resized or moved.
1665 *
1666 *--------------------------------------------------------------
1667 */
1668
1669static void
1670ArrangePanes(
1671    ClientData clientData)	/* Structure describing parent whose slaves
1672				 * are to be re-layed out. */
1673{
1674    register PanedWindow *pwPtr = (PanedWindow *) clientData;
1675    register Slave *slavePtr;
1676    int i, slaveWidth, slaveHeight, slaveX, slaveY;
1677    int paneWidth, paneHeight, paneSize, paneMinSize;
1678    int doubleBw;
1679    int x, y;
1680    int sashWidth, sashOffset, sashCount, handleOffset;
1681    int sashReserve, sxReserve, syReserve;
1682    int internalBW;
1683    int paneDynSize, paneDynMinSize, pwHeight, pwWidth, pwSize;
1684    int first, last;
1685    int stretchReserve, stretchAmount;
1686    const int horizontal = (pwPtr->orient == ORIENT_HORIZONTAL);
1687
1688    pwPtr->flags &= ~(REQUESTED_RELAYOUT|RESIZE_PENDING);
1689
1690    /*
1691     * If the parent has no slaves anymore, then don't do anything at all:
1692     * just leave the parent's size as-is. Otherwise there is no way to
1693     * "relinquish" control over the parent so another geometry manager can
1694     * take over.
1695     */
1696
1697    if (pwPtr->numSlaves == 0) {
1698	return;
1699    }
1700
1701    Tcl_Preserve((ClientData) pwPtr);
1702
1703    /*
1704     * Find index of last visible pane.
1705     */
1706
1707    for (i = 0, last = 0, first = -1; i < pwPtr->numSlaves; i++) {
1708	if (pwPtr->slaves[i]->hide == 0) {
1709	    if (first < 0) {
1710		first = i;
1711	    }
1712	    last = i;
1713	}
1714    }
1715
1716    /*
1717     * First pass; compute sizes
1718     */
1719
1720    paneDynSize = paneDynMinSize = 0;
1721    internalBW = Tk_InternalBorderWidth(pwPtr->tkwin);
1722    pwHeight = Tk_Height(pwPtr->tkwin) - (2 * internalBW);
1723    pwWidth = Tk_Width(pwPtr->tkwin) - (2 * internalBW);
1724    x = y = internalBW;
1725    stretchReserve = (horizontal ? pwWidth : pwHeight);
1726
1727    /*
1728     * Calculate the sash width, including handle and padding, and the sash
1729     * and handle offsets.
1730     */
1731
1732    sashOffset = handleOffset = pwPtr->sashPad;
1733    if (pwPtr->showHandle && pwPtr->handleSize > pwPtr->sashWidth) {
1734	sashWidth = (2 * pwPtr->sashPad) + pwPtr->handleSize;
1735	sashOffset = ((pwPtr->handleSize - pwPtr->sashWidth) / 2)
1736		+ pwPtr->sashPad;
1737    } else {
1738	sashWidth = (2 * pwPtr->sashPad) + pwPtr->sashWidth;
1739	handleOffset = ((pwPtr->sashWidth - pwPtr->handleSize) / 2)
1740		+ pwPtr->sashPad;
1741    }
1742
1743    for (i = sashCount = 0; i < pwPtr->numSlaves; i++) {
1744	slavePtr = pwPtr->slaves[i];
1745
1746	if (slavePtr->hide) {
1747	    continue;
1748	}
1749
1750	/*
1751	 * Compute the total size needed by all the slaves and the left-over,
1752	 * or shortage of space available.
1753	 */
1754
1755	if (horizontal) {
1756	    paneSize = slavePtr->paneWidth;
1757	    stretchReserve -= paneSize + (2 * slavePtr->padx);
1758	} else {
1759	    paneSize = slavePtr->paneHeight;
1760	    stretchReserve -= paneSize + (2 * slavePtr->pady);
1761	}
1762	if (IsStretchable(slavePtr->stretch,i,first,last)
1763		&& Tk_IsMapped(pwPtr->tkwin)) {
1764	    paneDynSize += paneSize;
1765	    paneDynMinSize += slavePtr->minSize;
1766	}
1767	if (i != last) {
1768	    stretchReserve -= sashWidth;
1769	    sashCount++;
1770	}
1771    }
1772
1773    /*
1774     * Second pass; adjust/arrange panes.
1775     */
1776
1777    for (i = 0; i < pwPtr->numSlaves; i++) {
1778	slavePtr = pwPtr->slaves[i];
1779
1780	if (slavePtr->hide) {
1781	    Tk_UnmaintainGeometry(slavePtr->tkwin, pwPtr->tkwin);
1782	    Tk_UnmapWindow(slavePtr->tkwin);
1783	    continue;
1784	}
1785
1786	/*
1787	 * Compute the size of this slave. The algorithm (assuming a
1788	 * horizontal paned window) is:
1789	 *
1790	 * 1.  Get "base" dimensions. If a width or height is specified for
1791	 *     this slave, use those values; else use the ReqWidth/ReqHeight.
1792	 * 2.  Using base dimensions, pane dimensions, and sticky values,
1793	 *     determine the x and y, and actual width and height of the
1794	 *     widget.
1795	 */
1796
1797	doubleBw = 2 * Tk_Changes(slavePtr->tkwin)->border_width;
1798	slaveWidth = (slavePtr->width > 0 ? slavePtr->width :
1799		Tk_ReqWidth(slavePtr->tkwin) + doubleBw);
1800	slaveHeight = (slavePtr->height > 0 ? slavePtr->height :
1801		Tk_ReqHeight(slavePtr->tkwin) + doubleBw);
1802	paneMinSize = slavePtr->minSize;
1803
1804	/*
1805	 * Calculate pane width and height.
1806	 */
1807
1808	if (horizontal) {
1809	    paneSize = slavePtr->paneWidth;
1810	    pwSize = pwWidth;
1811	} else {
1812	    paneSize = slavePtr->paneHeight;
1813	    pwSize = pwHeight;
1814	}
1815	if (IsStretchable(slavePtr->stretch, i, first, last)) {
1816	    double frac;
1817
1818	    if (paneDynSize > 0) {
1819		frac = (double)paneSize / (double)paneDynSize;
1820	    } else {
1821		frac = (double)paneSize / (double)pwSize;
1822	    }
1823
1824	    paneDynSize -= paneSize;
1825	    paneDynMinSize -= slavePtr->minSize;
1826	    stretchAmount = (int) (frac * stretchReserve);
1827	    if (paneSize + stretchAmount >= paneMinSize) {
1828		stretchReserve -= stretchAmount;
1829		paneSize += stretchAmount;
1830	    } else {
1831		stretchReserve += paneSize - paneMinSize;
1832		paneSize = paneMinSize;
1833	    }
1834	    if (i == last && stretchReserve > 0) {
1835		paneSize += stretchReserve;
1836		stretchReserve = 0;
1837	    }
1838	} else if (paneDynSize - paneDynMinSize + stretchReserve < 0) {
1839	    if (paneSize + paneDynSize - paneDynMinSize + stretchReserve
1840		    <= paneMinSize) {
1841		stretchReserve += paneSize - paneMinSize;
1842		paneSize = paneMinSize;
1843	    } else {
1844		paneSize += paneDynSize - paneDynMinSize + stretchReserve;
1845		stretchReserve = paneDynMinSize - paneDynSize;
1846	    }
1847	}
1848	if (horizontal) {
1849	    paneWidth = paneSize;
1850	    paneHeight = pwHeight - (2 * slavePtr->pady);
1851	} else {
1852	    paneWidth = pwWidth - (2 * slavePtr->padx);
1853	    paneHeight = paneSize;
1854	}
1855
1856	/*
1857	 * Adjust for area reserved for sashes.
1858	 */
1859
1860	if (sashCount) {
1861	    sashReserve = sashWidth * sashCount;
1862	    if (horizontal) {
1863		sxReserve = sashReserve;
1864		syReserve = 0;
1865	    } else {
1866		sxReserve = 0;
1867		syReserve = sashReserve;
1868	    }
1869	} else {
1870	    sxReserve = syReserve = 0;
1871	}
1872
1873	if (pwWidth - sxReserve < x + paneWidth - internalBW) {
1874	    paneWidth = pwWidth - sxReserve - x + internalBW;
1875	}
1876	if (pwHeight - syReserve < y + paneHeight - internalBW) {
1877	    paneHeight = pwHeight - syReserve - y + internalBW;
1878	}
1879
1880	if (slaveWidth > paneWidth) {
1881	    slaveWidth = paneWidth;
1882	}
1883	if (slaveHeight > paneHeight) {
1884	    slaveHeight = paneHeight;
1885	}
1886
1887	slavePtr->x = x;
1888	slavePtr->y = y;
1889
1890	/*
1891	 * Compute the location of the sash at the right or bottom of the
1892	 * parcel and the location of the next parcel.
1893	 */
1894
1895	if (horizontal) {
1896	    x += paneWidth + (2 * slavePtr->padx);
1897	    if (x < internalBW) {
1898		x = internalBW;
1899	    }
1900	    slavePtr->sashx = x + sashOffset;
1901	    slavePtr->sashy = y;
1902	    slavePtr->handlex = x + handleOffset;
1903	    slavePtr->handley = y + pwPtr->handlePad;
1904	    x += sashWidth;
1905	} else {
1906	    y += paneHeight + (2 * slavePtr->pady);
1907	    if (y < internalBW) {
1908		y = internalBW;
1909	    }
1910	    slavePtr->sashx = x;
1911	    slavePtr->sashy = y + sashOffset;
1912	    slavePtr->handlex = x + pwPtr->handlePad;
1913	    slavePtr->handley = y + handleOffset;
1914	    y += sashWidth;
1915	}
1916
1917	/*
1918	 * Compute the actual dimensions of the slave in the pane.
1919	 */
1920
1921	slaveX = slavePtr->x;
1922	slaveY = slavePtr->y;
1923	AdjustForSticky(slavePtr->sticky, paneWidth, paneHeight,
1924		&slaveX, &slaveY, &slaveWidth, &slaveHeight);
1925
1926	slaveX += slavePtr->padx;
1927	slaveY += slavePtr->pady;
1928
1929	/*
1930	 * Now put the window in the proper spot.
1931	 */
1932
1933	if (slaveWidth <= 0 || slaveHeight <= 0 ||
1934		(horizontal ? slaveX - internalBW > pwWidth :
1935		slaveY - internalBW > pwHeight)) {
1936	    Tk_UnmaintainGeometry(slavePtr->tkwin, pwPtr->tkwin);
1937	    Tk_UnmapWindow(slavePtr->tkwin);
1938	} else {
1939	    Tk_MaintainGeometry(slavePtr->tkwin, pwPtr->tkwin,
1940		    slaveX, slaveY, slaveWidth, slaveHeight);
1941	}
1942	sashCount--;
1943    }
1944    Tcl_Release((ClientData) pwPtr);
1945}
1946
1947/*
1948 *----------------------------------------------------------------------
1949 *
1950 * Unlink --
1951 *
1952 *	Remove a slave from a paned window.
1953 *
1954 * Results:
1955 *	None.
1956 *
1957 * Side effects:
1958 *	The paned window will be scheduled for re-arranging and redrawing.
1959 *
1960 *----------------------------------------------------------------------
1961 */
1962
1963static void
1964Unlink(
1965    register Slave *slavePtr)		/* Window to unlink. */
1966{
1967    register PanedWindow *masterPtr;
1968    int i, j;
1969
1970    masterPtr = slavePtr->masterPtr;
1971    if (masterPtr == NULL) {
1972	return;
1973    }
1974
1975    /*
1976     * Find the specified slave in the panedwindow's list of slaves, then
1977     * remove it from that list.
1978     */
1979
1980    for (i = 0; i < masterPtr->numSlaves; i++) {
1981	if (masterPtr->slaves[i] == slavePtr) {
1982	    for (j = i; j < masterPtr->numSlaves - 1; j++) {
1983		masterPtr->slaves[j] = masterPtr->slaves[j + 1];
1984	    }
1985	    break;
1986	}
1987    }
1988
1989    /*
1990     * Clean out any -after or -before references to this slave
1991     */
1992
1993    for (i = 0; i < masterPtr->numSlaves; i++) {
1994	if (masterPtr->slaves[i]->before == slavePtr->tkwin) {
1995	    masterPtr->slaves[i]->before = None;
1996	}
1997	if (masterPtr->slaves[i]->after == slavePtr->tkwin) {
1998	    masterPtr->slaves[i]->after = None;
1999	}
2000    }
2001
2002    masterPtr->flags |= REQUESTED_RELAYOUT;
2003    if (!(masterPtr->flags & REDRAW_PENDING)) {
2004	masterPtr->flags |= REDRAW_PENDING;
2005	Tcl_DoWhenIdle(DisplayPanedWindow, (ClientData) masterPtr);
2006    }
2007
2008    /*
2009     * Set the slave's masterPtr to NULL, so that we can tell that the slave
2010     * is no longer attached to any panedwindow.
2011     */
2012
2013    slavePtr->masterPtr = NULL;
2014
2015    masterPtr->numSlaves--;
2016}
2017
2018/*
2019 *----------------------------------------------------------------------
2020 *
2021 * GetPane --
2022 *
2023 *	Given a token to a Tk window, find the pane that corresponds to that
2024 *	token in a given paned window.
2025 *
2026 * Results:
2027 *	Pointer to the slave structure, or NULL if the window is not managed
2028 *	by this paned window.
2029 *
2030 * Side effects:
2031 *	None.
2032 *
2033 *----------------------------------------------------------------------
2034 */
2035
2036static Slave *
2037GetPane(
2038    PanedWindow *pwPtr,		/* Pointer to the paned window info. */
2039    Tk_Window tkwin)		/* Window to search for. */
2040{
2041    int i;
2042
2043    for (i = 0; i < pwPtr->numSlaves; i++) {
2044	if (pwPtr->slaves[i]->tkwin == tkwin) {
2045	    return pwPtr->slaves[i];
2046	}
2047    }
2048    return NULL;
2049}
2050
2051/*
2052 *--------------------------------------------------------------
2053 *
2054 * SlaveStructureProc --
2055 *
2056 *	This function is invoked whenever StructureNotify events occur for a
2057 *	window that's managed by a paned window. This function's only purpose
2058 *	is to clean up when windows are deleted.
2059 *
2060 * Results:
2061 *	None.
2062 *
2063 * Side effects:
2064 *	The paned window slave structure associated with the window
2065 *	is freed, and the slave is disassociated from the paned
2066 *	window which managed it.
2067 *
2068 *--------------------------------------------------------------
2069 */
2070
2071static void
2072SlaveStructureProc(
2073    ClientData clientData,	/* Pointer to record describing window item. */
2074    XEvent *eventPtr)		/* Describes what just happened. */
2075{
2076    Slave *slavePtr = (Slave *) clientData;
2077    PanedWindow *pwPtr = slavePtr->masterPtr;
2078
2079    if (eventPtr->type == DestroyNotify) {
2080	Unlink(slavePtr);
2081	slavePtr->tkwin = NULL;
2082	ckfree((void *)slavePtr);
2083	ComputeGeometry(pwPtr);
2084    }
2085}
2086
2087/*
2088 *----------------------------------------------------------------------
2089 *
2090 * ComputeGeometry --
2091 *
2092 *	Compute geometry for the paned window, including coordinates of all
2093 *	slave windows and each sash.
2094 *
2095 * Results:
2096 *	None.
2097 *
2098 * Side effects:
2099 *	Recomputes geometry information for a paned window.
2100 *
2101 *----------------------------------------------------------------------
2102 */
2103
2104static void
2105ComputeGeometry(
2106    PanedWindow *pwPtr)		/* Pointer to the Paned Window structure. */
2107{
2108    int i, x, y, doubleBw, internalBw;
2109    int sashWidth, sashOffset, handleOffset;
2110    int reqWidth, reqHeight, dim;
2111    Slave *slavePtr;
2112    const int horizontal = (pwPtr->orient == ORIENT_HORIZONTAL);
2113
2114    pwPtr->flags |= REQUESTED_RELAYOUT;
2115
2116    x = y = internalBw = Tk_InternalBorderWidth(pwPtr->tkwin);
2117    reqWidth = reqHeight = 0;
2118
2119    /*
2120     * Sashes and handles share space on the display. To simplify processing
2121     * below, precompute the x and y offsets of the handles and sashes within
2122     * the space occupied by their combination; later, just add those offsets
2123     * blindly (avoiding the extra showHandle, etc, checks).
2124     */
2125
2126    sashOffset = handleOffset = pwPtr->sashPad;
2127    if (pwPtr->showHandle && pwPtr->handleSize > pwPtr->sashWidth) {
2128	sashWidth = (2 * pwPtr->sashPad) + pwPtr->handleSize;
2129	sashOffset = ((pwPtr->handleSize - pwPtr->sashWidth) / 2)
2130		+ pwPtr->sashPad;
2131    } else {
2132	sashWidth = (2 * pwPtr->sashPad) + pwPtr->sashWidth;
2133	handleOffset = ((pwPtr->sashWidth - pwPtr->handleSize) / 2)
2134		+ pwPtr->sashPad;
2135    }
2136
2137    for (i = 0; i < pwPtr->numSlaves; i++) {
2138	slavePtr = pwPtr->slaves[i];
2139
2140	if (slavePtr->hide) {
2141	    continue;
2142	}
2143
2144	/*
2145	 * First set the coordinates for the top left corner of the slave's
2146	 * parcel.
2147	 */
2148
2149	slavePtr->x = x;
2150	slavePtr->y = y;
2151
2152	/*
2153	 * Make sure the pane's paned dimension is at least minsize. This
2154	 * check may be redundant, since the only way to change a pane's size
2155	 * is by moving a sash, and that code checks the minsize.
2156	 */
2157
2158	if (horizontal) {
2159	    if (slavePtr->paneWidth < slavePtr->minSize) {
2160		slavePtr->paneWidth = slavePtr->minSize;
2161	    }
2162	} else {
2163	    if (slavePtr->paneHeight < slavePtr->minSize) {
2164		slavePtr->paneHeight = slavePtr->minSize;
2165	    }
2166	}
2167
2168	/*
2169	 * Compute the location of the sash at the right or bottom of the
2170	 * parcel.
2171	 */
2172
2173	if (horizontal) {
2174	    x += slavePtr->paneWidth + (2 * slavePtr->padx);
2175	    slavePtr->sashx = x + sashOffset;
2176	    slavePtr->sashy = y;
2177	    slavePtr->handlex = x + handleOffset;
2178	    slavePtr->handley = y + pwPtr->handlePad;
2179	    x += sashWidth;
2180	} else {
2181	    y += slavePtr->paneHeight + (2 * slavePtr->pady);
2182	    slavePtr->sashx = x;
2183	    slavePtr->sashy = y + sashOffset;
2184	    slavePtr->handlex = x + pwPtr->handlePad;
2185	    slavePtr->handley = y + handleOffset;
2186	    y += sashWidth;
2187	}
2188
2189	/*
2190	 * Find the maximum height/width of the slaves, for computing the
2191	 * requested height/width of the paned window.
2192	 */
2193
2194	if (horizontal) {
2195	    /*
2196	     * If the slave has an explicit height set, use that; otherwise,
2197	     * use the slave's requested height.
2198	     */
2199
2200	    if (slavePtr->height > 0) {
2201		dim = slavePtr->height;
2202	    } else {
2203		doubleBw = 2 * Tk_Changes(slavePtr->tkwin)->border_width;
2204		dim = Tk_ReqHeight(slavePtr->tkwin) + doubleBw;
2205	    }
2206	    dim += 2 * slavePtr->pady;
2207	    if (dim > reqHeight) {
2208		reqHeight = dim;
2209	    }
2210	} else {
2211	    /*
2212	     * If the slave has an explicit width set use that; otherwise, use
2213	     * the slave's requested width.
2214	     */
2215
2216	    if (slavePtr->width > 0) {
2217		dim = slavePtr->width;
2218	    } else {
2219		doubleBw = 2 * Tk_Changes(slavePtr->tkwin)->border_width;
2220		dim = Tk_ReqWidth(slavePtr->tkwin) + doubleBw;
2221	    }
2222	    dim += 2 * slavePtr->padx;
2223	    if (dim > reqWidth) {
2224		reqWidth = dim;
2225	    }
2226	}
2227    }
2228
2229    /*
2230     * The loop above should have left x (or y) equal to the sum of the widths
2231     * (or heights) of the widgets, plus the size of one sash and the sash
2232     * padding for each widget, plus the width of the left (or top) border of
2233     * the paned window.
2234     *
2235     * The requested width (or height) is therefore x (or y) minus the size of
2236     * one sash and padding, plus the width of the right (or bottom) border of
2237     * the paned window.
2238     *
2239     * The height (or width) is equal to the maximum height (or width) of the
2240     * slaves, plus the width of the border of the top and bottom (or left and
2241     * right) of the paned window.
2242     *
2243     * If the panedwindow has an explicit width/height set use that;
2244     * otherwise, use the requested width/height.
2245     */
2246
2247    if (horizontal) {
2248	reqWidth = (pwPtr->width > 0 ?
2249		pwPtr->width : x - sashWidth + internalBw);
2250	reqHeight = (pwPtr->height > 0 ?
2251		pwPtr->height : reqHeight + (2 * internalBw));
2252    } else {
2253	reqWidth = (pwPtr->width > 0 ?
2254		pwPtr->width : reqWidth + (2 * internalBw));
2255	reqHeight = (pwPtr->height > 0 ?
2256		pwPtr->height : y - sashWidth + internalBw);
2257    }
2258    Tk_GeometryRequest(pwPtr->tkwin, reqWidth, reqHeight);
2259    if (Tk_IsMapped(pwPtr->tkwin) && !(pwPtr->flags & REDRAW_PENDING)) {
2260	pwPtr->flags |= REDRAW_PENDING;
2261	Tcl_DoWhenIdle(DisplayPanedWindow, (ClientData) pwPtr);
2262    }
2263}
2264
2265/*
2266 *----------------------------------------------------------------------
2267 *
2268 * DestroyOptionTables --
2269 *
2270 *	This function is registered as an exit callback when the paned window
2271 *	command is first called. It cleans up the OptionTables structure
2272 *	allocated by that command.
2273 *
2274 * Results:
2275 *	None.
2276 *
2277 * Side effects:
2278 *	Frees memory.
2279 *
2280 *----------------------------------------------------------------------
2281 */
2282
2283static void
2284DestroyOptionTables(
2285    ClientData clientData,	/* Pointer to the OptionTables struct */
2286    Tcl_Interp *interp)		/* Pointer to the calling interp */
2287{
2288    ckfree((char *)clientData);
2289    return;
2290}
2291
2292/*
2293 *----------------------------------------------------------------------
2294 *
2295 * GetSticky -
2296 *
2297 *	Converts an internal boolean combination of "sticky" bits into a Tcl
2298 *	string obj containing zero or more of n, s, e, or w.
2299 *
2300 * Results:
2301 *	Tcl_Obj containing the string representation of the sticky value.
2302 *
2303 * Side effects:
2304 *	Creates a new Tcl_Obj.
2305 *
2306 *----------------------------------------------------------------------
2307 */
2308
2309static Tcl_Obj *
2310GetSticky(
2311    ClientData clientData,
2312    Tk_Window tkwin,
2313    char *recordPtr,		/* Pointer to widget record. */
2314    int internalOffset)		/* Offset within *recordPtr containing the
2315				 * sticky value. */
2316{
2317    int sticky = *(int *)(recordPtr + internalOffset);
2318    static char buffer[5];
2319    int count = 0;
2320
2321    if (sticky & STICK_NORTH) {
2322	buffer[count++] = 'n';
2323    }
2324    if (sticky & STICK_EAST) {
2325	buffer[count++] = 'e';
2326    }
2327    if (sticky & STICK_SOUTH) {
2328	buffer[count++] = 's';
2329    }
2330    if (sticky & STICK_WEST) {
2331	buffer[count++] = 'w';
2332    }
2333    buffer[count] = '\0';
2334
2335    return Tcl_NewStringObj(buffer, -1);
2336}
2337
2338/*
2339 *----------------------------------------------------------------------
2340 *
2341 * SetSticky --
2342 *
2343 *	Converts a Tcl_Obj representing a widgets stickyness into an integer
2344 *	value.
2345 *
2346 * Results:
2347 *	Standard Tcl result.
2348 *
2349 * Side effects:
2350 *	May store the integer value into the internal representation pointer.
2351 *	May change the pointer to the Tcl_Obj to NULL to indicate that the
2352 *	specified string was empty and that is acceptable.
2353 *
2354 *----------------------------------------------------------------------
2355 */
2356
2357static int
2358SetSticky(
2359    ClientData clientData,
2360    Tcl_Interp *interp,		/* Current interp; may be used for errors. */
2361    Tk_Window tkwin,		/* Window for which option is being set. */
2362    Tcl_Obj **value,		/* Pointer to the pointer to the value object.
2363				 * We use a pointer to the pointer because we
2364				 * may need to return a value (NULL). */
2365    char *recordPtr,		/* Pointer to storage for the widget record. */
2366    int internalOffset,		/* Offset within *recordPtr at which the
2367				 * internal value is to be stored. */
2368    char *oldInternalPtr,	/* Pointer to storage for the old value. */
2369    int flags)			/* Flags for the option, set Tk_SetOptions. */
2370{
2371    int sticky = 0;
2372    char c, *string, *internalPtr;
2373
2374    internalPtr = ComputeSlotAddress(recordPtr, internalOffset);
2375
2376    if (flags & TK_OPTION_NULL_OK && ObjectIsEmpty(*value)) {
2377	*value = NULL;
2378    } else {
2379	/*
2380	 * Convert the sticky specifier into an integer value.
2381	 */
2382
2383	string = Tcl_GetString(*value);
2384
2385	while ((c = *string++) != '\0') {
2386	    switch (c) {
2387	    case 'n': case 'N':
2388		sticky |= STICK_NORTH;
2389		break;
2390	    case 'e': case 'E':
2391		sticky |= STICK_EAST;
2392		break;
2393	    case 's': case 'S':
2394		sticky |= STICK_SOUTH;
2395		break;
2396	    case 'w': case 'W':
2397		sticky |= STICK_WEST;
2398		break;
2399	    case ' ': case ',': case '\t': case '\r': case '\n':
2400		break;
2401	    default:
2402		Tcl_ResetResult(interp);
2403		Tcl_AppendResult(interp, "bad stickyness value \"",
2404			Tcl_GetString(*value), "\": must be a string ",
2405			"containing zero or more of n, e, s, and w", NULL);
2406		return TCL_ERROR;
2407	    }
2408	}
2409    }
2410
2411    if (internalPtr != NULL) {
2412	*((int *) oldInternalPtr) = *((int *) internalPtr);
2413	*((int *) internalPtr) = sticky;
2414    }
2415    return TCL_OK;
2416}
2417
2418/*
2419 *----------------------------------------------------------------------
2420 *
2421 * RestoreSticky --
2422 *
2423 *	Restore a sticky option value from a saved value.
2424 *
2425 * Results:
2426 *	None.
2427 *
2428 * Side effects:
2429 *	Restores the old value.
2430 *
2431 *----------------------------------------------------------------------
2432 */
2433
2434static void
2435RestoreSticky(
2436    ClientData clientData,
2437    Tk_Window tkwin,
2438    char *internalPtr,		/* Pointer to storage for value. */
2439    char *oldInternalPtr)	/* Pointer to old value. */
2440{
2441    *(int *)internalPtr = *(int *)oldInternalPtr;
2442}
2443
2444/*
2445 *----------------------------------------------------------------------
2446 *
2447 * AdjustForSticky --
2448 *
2449 *	Given the x,y coords of the top-left corner of a pane, the dimensions
2450 *	of that pane, and the dimensions of a slave, compute the x,y coords
2451 *	and actual dimensions of the slave based on the slave's sticky value.
2452 *
2453 * Results:
2454 *	No direct return; sets the x, y, slaveWidth and slaveHeight to correct
2455 *	values.
2456 *
2457 * Side effects:
2458 *	None.
2459 *
2460 *----------------------------------------------------------------------
2461 */
2462
2463static void
2464AdjustForSticky(
2465    int sticky,			/* Sticky value; see top of file for
2466				 * definition. */
2467    int cavityWidth,		/* Width of the cavity. */
2468    int cavityHeight,		/* Height of the cavity. */
2469    int *xPtr, int *yPtr,	/* Initially, coordinates of the top-left
2470				 * corner of cavity; also return values for
2471				 * actual x, y coords of slave. */
2472    int *slaveWidthPtr,		/* Slave width. */
2473    int *slaveHeightPtr)	/* Slave height. */
2474{
2475    int diffx = 0;		/* Cavity width - slave width. */
2476    int diffy = 0;		/* Cavity hight - slave height. */
2477
2478    if (cavityWidth > *slaveWidthPtr) {
2479	diffx = cavityWidth - *slaveWidthPtr;
2480    }
2481
2482    if (cavityHeight > *slaveHeightPtr) {
2483	diffy = cavityHeight - *slaveHeightPtr;
2484    }
2485
2486    if ((sticky & STICK_EAST) && (sticky & STICK_WEST)) {
2487	*slaveWidthPtr += diffx;
2488    }
2489    if ((sticky & STICK_NORTH) && (sticky & STICK_SOUTH)) {
2490	*slaveHeightPtr += diffy;
2491    }
2492    if (!(sticky & STICK_WEST)) {
2493	*xPtr += (sticky & STICK_EAST) ? diffx : diffx/2;
2494    }
2495    if (!(sticky & STICK_NORTH)) {
2496	*yPtr += (sticky & STICK_SOUTH) ? diffy : diffy/2;
2497    }
2498}
2499
2500/*
2501 *----------------------------------------------------------------------
2502 *
2503 * MoveSash --
2504 *
2505 *	Move the sash given by index the amount given.
2506 *
2507 * Results:
2508 *	None.
2509 *
2510 * Side effects:
2511 *	Recomputes the sizes of the panes in a panedwindow.
2512 *
2513 *----------------------------------------------------------------------
2514 */
2515
2516static void
2517MoveSash(
2518    PanedWindow *pwPtr,
2519    int sash,
2520    int diff)
2521{
2522    int i;
2523    int expandPane, reduceFirst, reduceLast, reduceIncr, slaveSize, sashOffset;
2524    Slave *slavePtr;
2525    int stretchReserve = 0;
2526    int nextSash = sash + 1;
2527    const int horizontal = (pwPtr->orient == ORIENT_HORIZONTAL);
2528
2529    if (diff == 0)
2530	return;
2531
2532    /*
2533     * Update the slave sizes with their real sizes.
2534     */
2535
2536    if (pwPtr->showHandle && pwPtr->handleSize > pwPtr->sashWidth) {
2537	sashOffset = ((pwPtr->handleSize - pwPtr->sashWidth) / 2)
2538		+ pwPtr->sashPad;
2539    } else {
2540	sashOffset = pwPtr->sashPad;
2541    }
2542    for (i = 0; i < pwPtr->numSlaves; i++) {
2543	slavePtr = pwPtr->slaves[i];
2544	if (slavePtr->hide) {
2545	    continue;
2546	}
2547	if (horizontal) {
2548	    slavePtr->paneWidth = slavePtr->width = slavePtr->sashx
2549		    - sashOffset - slavePtr->x - (2 * slavePtr->padx);
2550	} else {
2551	    slavePtr->paneWidth = slavePtr->height = slavePtr->sashy
2552		    - sashOffset - slavePtr->y - (2 * slavePtr->pady);
2553	}
2554    }
2555
2556    /*
2557     * There must be a next sash since it is only possible to enter this
2558     * routine when moving an actual sash which implies there exists a visible
2559     * pane to either side of the sash.
2560     */
2561
2562    while (nextSash < pwPtr->numSlaves-1 && pwPtr->slaves[nextSash]->hide) {
2563	nextSash++;
2564    }
2565
2566    /*
2567     * Consolidate +/-diff variables to reduce duplicate code.
2568     */
2569
2570    if (diff > 0) {
2571	expandPane = sash;
2572	reduceFirst = nextSash;
2573	reduceLast = pwPtr->numSlaves;
2574	reduceIncr = 1;
2575    } else {
2576	diff = abs(diff);
2577	expandPane = nextSash;
2578	reduceFirst = sash;
2579	reduceLast = -1;
2580	reduceIncr = -1;
2581    }
2582
2583    /*
2584     * Calculate how much room we have to stretch in and adjust diff value
2585     * accordingly.
2586     */
2587
2588    for (i = reduceFirst; i != reduceLast; i += reduceIncr) {
2589	slavePtr = pwPtr->slaves[i];
2590	if (slavePtr->hide) {
2591	    continue;
2592	}
2593	if (horizontal) {
2594	    stretchReserve += slavePtr->width - slavePtr->minSize;
2595	} else {
2596	    stretchReserve += slavePtr->height - slavePtr->minSize;
2597	}
2598    }
2599    if (stretchReserve <= 0) {
2600	return;
2601    }
2602    if (diff > stretchReserve) {
2603	diff = stretchReserve;
2604    }
2605
2606    /*
2607     * Expand pane by diff amount.
2608     */
2609
2610    slavePtr = pwPtr->slaves[expandPane];
2611    if (horizontal) {
2612	slavePtr->paneWidth = slavePtr->width += diff;
2613    } else {
2614	slavePtr->paneHeight = slavePtr->height += diff;
2615    }
2616
2617    /*
2618     * Reduce panes, respecting minsize, until diff amount has been used.
2619     */
2620
2621    for (i = reduceFirst; i != reduceLast; i += reduceIncr) {
2622	slavePtr = pwPtr->slaves[i];
2623	if (slavePtr->hide) {
2624	    continue;
2625	}
2626	if (horizontal) {
2627	    slaveSize = slavePtr->width;
2628	} else {
2629	    slaveSize = slavePtr->height;
2630	}
2631	if (diff > (slaveSize - slavePtr->minSize)) {
2632	    diff -= slaveSize - slavePtr->minSize;
2633	    slaveSize = slavePtr->minSize;
2634	} else {
2635	    slaveSize -= diff;
2636	    i = reduceLast - reduceIncr;
2637	}
2638	if (horizontal) {
2639	    slavePtr->paneWidth = slavePtr->width = slaveSize;
2640	} else {
2641	    slavePtr->paneHeight = slavePtr->height = slaveSize;
2642	}
2643    }
2644}
2645
2646/*
2647 *----------------------------------------------------------------------
2648 *
2649 * ProxyWindowEventProc --
2650 *
2651 *	This function is invoked by the Tk dispatcher for various events on
2652 *	paned window proxy windows.
2653 *
2654 * Results:
2655 *	None.
2656 *
2657 * Side effects:
2658 *	When the window gets deleted, internal structures get cleaned up. Whena
2659 *	it gets exposed, it is redisplayed.
2660 *
2661 *--------------------------------------------------------------
2662 */
2663
2664static void
2665ProxyWindowEventProc(
2666    ClientData clientData,	/* Information about window. */
2667    XEvent *eventPtr)		/* Information about event. */
2668{
2669    PanedWindow *pwPtr = (PanedWindow *) clientData;
2670
2671    if (eventPtr->type == Expose) {
2672	if (pwPtr->proxywin != NULL &&!(pwPtr->flags & PROXY_REDRAW_PENDING)) {
2673	    Tcl_DoWhenIdle(DisplayProxyWindow, (ClientData) pwPtr);
2674	    pwPtr->flags |= PROXY_REDRAW_PENDING;
2675	}
2676    }
2677}
2678
2679/*
2680 *--------------------------------------------------------------
2681 *
2682 * DisplayProxyWindow --
2683 *
2684 *	This function redraws a paned window proxy window. It is invoked as a
2685 *	do-when-idle handler, so it only runs when there's nothing else for
2686 *	the application to do.
2687 *
2688 * Results:
2689 *	None.
2690 *
2691 * Side effects:
2692 *	Information appears on the screen.
2693 *
2694 *--------------------------------------------------------------
2695 */
2696
2697static void
2698DisplayProxyWindow(
2699    ClientData clientData)	/* Information about window. */
2700{
2701    PanedWindow *pwPtr = (PanedWindow *) clientData;
2702    Pixmap pixmap;
2703    Tk_Window tkwin = pwPtr->proxywin;
2704    pwPtr->flags &= ~PROXY_REDRAW_PENDING;
2705    if ((tkwin == NULL) || !Tk_IsMapped(tkwin)) {
2706	return;
2707    }
2708
2709#ifndef TK_NO_DOUBLE_BUFFERING
2710    /*
2711     * Create a pixmap for double-buffering, if necessary.
2712     */
2713
2714    pixmap = Tk_GetPixmap(Tk_Display(tkwin), Tk_WindowId(tkwin),
2715	    Tk_Width(tkwin), Tk_Height(tkwin), Tk_Depth(tkwin));
2716#else
2717    pixmap = Tk_WindowId(tkwin);
2718#endif /* TK_NO_DOUBLE_BUFFERING */
2719
2720    /*
2721     * Redraw the widget's background and border.
2722     */
2723
2724    Tk_Fill3DRectangle(tkwin, pixmap, pwPtr->background, 0, 0,
2725	    Tk_Width(tkwin), Tk_Height(tkwin), 2, pwPtr->sashRelief);
2726
2727#ifndef TK_NO_DOUBLE_BUFFERING
2728    /*
2729     * Copy the pixmap to the display.
2730     */
2731
2732    XCopyArea(Tk_Display(tkwin), pixmap, Tk_WindowId(tkwin), pwPtr->gc, 0, 0,
2733	    (unsigned) Tk_Width(tkwin), (unsigned) Tk_Height(tkwin), 0, 0);
2734    Tk_FreePixmap(Tk_Display(tkwin), pixmap);
2735#endif /* TK_NO_DOUBLE_BUFFERING */
2736}
2737
2738/*
2739 *----------------------------------------------------------------------
2740 *
2741 * PanedWindowProxyCommand --
2742 *
2743 *	Handles the panedwindow proxy subcommand. See the user documentation
2744 *	for details.
2745 *
2746 * Results:
2747 *	Standard Tcl result.
2748 *
2749 * Side effects:
2750 *	May map or unmap the proxy sash.
2751 *
2752 *----------------------------------------------------------------------
2753 */
2754
2755static int
2756PanedWindowProxyCommand(
2757    PanedWindow *pwPtr,		/* Pointer to paned window information. */
2758    Tcl_Interp *interp,		/* Current interpreter. */
2759    int objc,			/* Number of arguments. */
2760    Tcl_Obj *CONST objv[])	/* Argument objects. */
2761{
2762    static CONST char *optionStrings[] = {
2763	"coord", "forget", "place", NULL
2764    };
2765    enum options {
2766	PROXY_COORD, PROXY_FORGET, PROXY_PLACE
2767    };
2768    int index, x, y, sashWidth, sashHeight;
2769    Tcl_Obj *coords[2];
2770
2771    if (objc < 3) {
2772	Tcl_WrongNumArgs(interp, 2, objv, "option ?arg ...?");
2773	return TCL_ERROR;
2774    }
2775
2776    if (Tcl_GetIndexFromObj(interp, objv[2], optionStrings, "option", 0,
2777	    &index) != TCL_OK) {
2778	return TCL_ERROR;
2779    }
2780
2781    switch ((enum options) index) {
2782    case PROXY_COORD:
2783	if (objc != 3) {
2784	    Tcl_WrongNumArgs(interp, 3, objv, NULL);
2785	    return TCL_ERROR;
2786	}
2787
2788	coords[0] = Tcl_NewIntObj(pwPtr->proxyx);
2789	coords[1] = Tcl_NewIntObj(pwPtr->proxyy);
2790	Tcl_SetListObj(Tcl_GetObjResult(interp), 2, coords);
2791	break;
2792
2793    case PROXY_FORGET:
2794	if (objc != 3) {
2795	    Tcl_WrongNumArgs(interp, 3, objv, NULL);
2796	    return TCL_ERROR;
2797	}
2798	if (Tk_IsMapped(pwPtr->proxywin)) {
2799	    Tk_UnmapWindow(pwPtr->proxywin);
2800	    Tk_UnmaintainGeometry(pwPtr->proxywin, pwPtr->tkwin);
2801	}
2802	break;
2803
2804    case PROXY_PLACE:
2805	if (objc != 5) {
2806	    Tcl_WrongNumArgs(interp, 3, objv, "x y");
2807	    return TCL_ERROR;
2808	}
2809
2810	if (Tcl_GetIntFromObj(interp, objv[3], &x) != TCL_OK) {
2811	    return TCL_ERROR;
2812	}
2813
2814	if (Tcl_GetIntFromObj(interp, objv[4], &y) != TCL_OK) {
2815	    return TCL_ERROR;
2816	}
2817
2818	if (pwPtr->orient == ORIENT_HORIZONTAL) {
2819	    if (x < 0) {
2820		x = 0;
2821	    }
2822	    y = Tk_InternalBorderWidth(pwPtr->tkwin);
2823	    sashWidth = pwPtr->sashWidth;
2824	    sashHeight = Tk_Height(pwPtr->tkwin) -
2825		    (2 * Tk_InternalBorderWidth(pwPtr->tkwin));
2826	} else {
2827	    if (y < 0) {
2828		y = 0;
2829	    }
2830	    x = Tk_InternalBorderWidth(pwPtr->tkwin);
2831	    sashHeight = pwPtr->sashWidth;
2832	    sashWidth = Tk_Width(pwPtr->tkwin) -
2833		    (2 * Tk_InternalBorderWidth(pwPtr->tkwin));
2834	}
2835
2836        if (sashWidth < 1) {
2837            sashWidth = 1;
2838        }
2839        if (sashHeight < 1) {
2840            sashHeight = 1;
2841        }
2842
2843	/*
2844	 * Stash the proxy coordinates for future "proxy coord" calls.
2845	 */
2846
2847	pwPtr->proxyx = x;
2848	pwPtr->proxyy = y;
2849
2850	/*
2851	 * Make sure the proxy window is higher in the stacking order than the
2852	 * slaves, so that it will be visible when drawn. It would be more
2853	 * correct to push the proxy window just high enough to appear above
2854	 * the highest slave, but it's much easier to just force it all the
2855	 * way to the top of the stacking order.
2856	 */
2857
2858	Tk_RestackWindow(pwPtr->proxywin, Above, NULL);
2859
2860	/*
2861	 * Let Tk_MaintainGeometry take care of placing the window at the
2862	 * right coordinates.
2863	 */
2864
2865	Tk_MaintainGeometry(pwPtr->proxywin, pwPtr->tkwin, x, y,
2866		sashWidth, sashHeight);
2867	break;
2868    }
2869
2870    return TCL_OK;
2871}
2872
2873/*
2874 *----------------------------------------------------------------------
2875 *
2876 * ObjectIsEmpty --
2877 *
2878 *	This function tests whether the string value of an object is empty.
2879 *
2880 * Results:
2881 *	The return value is 1 if the string value of objPtr has length zero,
2882 *	and 0 otherwise.
2883 *
2884 * Side effects:
2885 *	May cause object shimmering, since this function can force a
2886 *	conversion to a string object.
2887 *
2888 *----------------------------------------------------------------------
2889 */
2890
2891static int
2892ObjectIsEmpty(
2893    Tcl_Obj *objPtr)		/* Object to test. May be NULL. */
2894{
2895    int length;
2896
2897    if (objPtr == NULL) {
2898	return 1;
2899    }
2900    if (objPtr->bytes != NULL) {
2901	return (objPtr->length == 0);
2902    }
2903    Tcl_GetStringFromObj(objPtr, &length);
2904    return (length == 0);
2905}
2906
2907/*
2908 *----------------------------------------------------------------------
2909 *
2910 * ComputeInternalPointer --
2911 *
2912 *	Given a pointer to the start of a record and the offset of a slot
2913 *	within that record, compute the address of that slot.
2914 *
2915 * Results:
2916 *	If offset is non-negative, returns the computed address; else, returns
2917 *	NULL.
2918 *
2919 * Side effects:
2920 *	None.
2921 *
2922 *----------------------------------------------------------------------
2923 */
2924
2925static char *
2926ComputeSlotAddress(
2927    char *recordPtr,	/* Pointer to the start of a record. */
2928    int offset)		/* Offset of a slot within that record; may be < 0. */
2929{
2930    if (offset >= 0) {
2931	return recordPtr + offset;
2932    } else {
2933	return NULL;
2934    }
2935}
2936
2937/*
2938 *----------------------------------------------------------------------
2939 *
2940 * PanedWindowIdentifyCoords --
2941 *
2942 *	Given a pair of x,y coordinates, identify the panedwindow component at
2943 *	that point, if any.
2944 *
2945 * Results:
2946 *	Standard Tcl result.
2947 *
2948 * Side effects:
2949 *	Modifies the interpreter's result to contain either an empty list, or
2950 *	a two element list of the form {sash n} or {handle n} to indicate that
2951 *	the point lies within the n'th sash or handle.
2952 *
2953 *----------------------------------------------------------------------
2954 */
2955
2956static int
2957PanedWindowIdentifyCoords(
2958    PanedWindow *pwPtr,		/* Information about the widget. */
2959    Tcl_Interp *interp,		/* Interpreter in which to store result. */
2960    int x, int y)		/* Coordinates of the point to identify. */
2961{
2962    Tcl_Obj *list;
2963    int i, sashHeight, sashWidth, thisx, thisy;
2964    int found, isHandle, lpad, rpad, tpad, bpad;
2965    list = Tcl_NewObj();
2966
2967    if (pwPtr->orient == ORIENT_HORIZONTAL) {
2968	if (Tk_IsMapped(pwPtr->tkwin)) {
2969	    sashHeight = Tk_Height(pwPtr->tkwin);
2970	} else {
2971	    sashHeight = Tk_ReqHeight(pwPtr->tkwin);
2972	}
2973	sashHeight -= 2 * Tk_InternalBorderWidth(pwPtr->tkwin);
2974	if (pwPtr->showHandle && pwPtr->handleSize > pwPtr->sashWidth) {
2975	    sashWidth = pwPtr->handleSize;
2976	    lpad = (pwPtr->handleSize - pwPtr->sashWidth) / 2;
2977	    rpad = pwPtr->handleSize - lpad;
2978	    lpad += pwPtr->sashPad;
2979	    rpad += pwPtr->sashPad;
2980	} else {
2981	    sashWidth = pwPtr->sashWidth;
2982	    lpad = rpad = pwPtr->sashPad;
2983	}
2984	tpad = bpad = 0;
2985    } else {
2986	if (pwPtr->showHandle && pwPtr->handleSize > pwPtr->sashWidth) {
2987	    sashHeight = pwPtr->handleSize;
2988	    tpad = (pwPtr->handleSize - pwPtr->sashWidth) / 2;
2989	    bpad = pwPtr->handleSize - tpad;
2990	    tpad += pwPtr->sashPad;
2991	    bpad += pwPtr->sashPad;
2992	} else {
2993	    sashHeight = pwPtr->sashWidth;
2994	    tpad = bpad = pwPtr->sashPad;
2995	}
2996	if (Tk_IsMapped(pwPtr->tkwin)) {
2997	    sashWidth = Tk_Width(pwPtr->tkwin);
2998	} else {
2999	    sashWidth = Tk_ReqWidth(pwPtr->tkwin);
3000	}
3001	sashWidth -= 2 * Tk_InternalBorderWidth(pwPtr->tkwin);
3002	lpad = rpad = 0;
3003    }
3004
3005    isHandle = 0;
3006    found = -1;
3007    for (i = 0; i < pwPtr->numSlaves - 1; i++) {
3008	if (pwPtr->slaves[i]->hide) {
3009	    continue;
3010	}
3011	thisx = pwPtr->slaves[i]->sashx;
3012	thisy = pwPtr->slaves[i]->sashy;
3013
3014	if (((thisx - lpad) <= x && x <= (thisx + rpad + sashWidth)) &&
3015		((thisy - tpad) <= y && y <= (thisy + bpad + sashHeight))) {
3016	    found = i;
3017
3018	    /*
3019	     * Determine if the point is over the handle or the sash.
3020	     */
3021
3022	    if (pwPtr->showHandle) {
3023		thisx = pwPtr->slaves[i]->handlex;
3024		thisy = pwPtr->slaves[i]->handley;
3025		if (pwPtr->orient == ORIENT_HORIZONTAL) {
3026		    if (thisy <= y && y <= (thisy + pwPtr->handleSize)) {
3027			isHandle = 1;
3028		    }
3029		} else {
3030		    if (thisx <= x && x <= (thisx + pwPtr->handleSize)) {
3031			isHandle = 1;
3032		    }
3033		}
3034	    }
3035	    break;
3036	}
3037    }
3038
3039    /*
3040     * Set results.
3041     */
3042
3043    if (found != -1) {
3044	Tcl_ListObjAppendElement(interp, list, Tcl_NewIntObj(found));
3045	Tcl_ListObjAppendElement(interp, list, Tcl_NewStringObj(
3046		(isHandle ? "handle" : "sash"), -1));
3047    }
3048
3049    Tcl_SetObjResult(interp, list);
3050    return TCL_OK;
3051}
3052
3053/*
3054 * Local Variables:
3055 * mode: c
3056 * c-basic-offset: 4
3057 * fill-column: 78
3058 * End:
3059 */
3060