1/*
2 * tkPack.c --
3 *
4 *	This file contains code to implement the "packer"
5 *	geometry manager for Tk.
6 *
7 * Copyright (c) 1990-1994 The Regents of the University of California.
8 * Copyright (c) 1994-1997 Sun Microsystems, Inc.
9 *
10 * See the file "license.terms" for information on usage and redistribution
11 * of this file, and for a DISCLAIMER OF ALL WARRANTIES.
12 *
13 * RCS: @(#) $Id: tkPack.c,v 1.16.2.3 2005/08/11 12:17:09 dkf Exp $
14 */
15
16#include "tkPort.h"
17#include "tkInt.h"
18
19typedef enum {TOP, BOTTOM, LEFT, RIGHT} Side;
20static CONST char *sideNames[] = {
21    "top", "bottom", "left", "right", (char *) NULL
22};
23
24/* For each window that the packer cares about (either because
25 * the window is managed by the packer or because the window
26 * has slaves that are managed by the packer), there is a
27 * structure of the following type:
28 */
29
30typedef struct Packer {
31    Tk_Window tkwin;		/* Tk token for window.  NULL means that
32				 * the window has been deleted, but the
33				 * packet hasn't had a chance to clean up
34				 * yet because the structure is still in
35				 * use. */
36    struct Packer *masterPtr;	/* Master window within which this window
37				 * is packed (NULL means this window
38				 * isn't managed by the packer). */
39    struct Packer *nextPtr;	/* Next window packed within same
40				 * parent.  List is priority-ordered:
41				 * first on list gets packed first. */
42    struct Packer *slavePtr;	/* First in list of slaves packed
43				 * inside this window (NULL means
44				 * no packed slaves). */
45    Side side;			/* Side of parent against which
46				 * this window is packed. */
47    Tk_Anchor anchor;		/* If frame allocated for window is larger
48				 * than window needs, this indicates how
49				 * where to position window in frame. */
50    int padX, padY;		/* Total additional pixels to leave around the
51				 * window.  Some is of this space is on each
52				 * side.  This is space *outside* the window:
53				 * we'll allocate extra space in frame but
54				 * won't enlarge window). */
55    int padLeft, padTop;	/* The part of padX or padY to use on the
56				 * left or top of the widget, respectively.
57				 * By default, this is half of padX or padY. */
58    int iPadX, iPadY;		/* Total extra pixels to allocate inside the
59				 * window (half of this amount will appear on
60				 * each side). */
61    int doubleBw;		/* Twice the window's last known border
62				 * width.  If this changes, the window
63				 * must be repacked within its parent. */
64    int *abortPtr;		/* If non-NULL, it means that there is a nested
65				 * call to ArrangePacking already working on
66				 * this window.  *abortPtr may be set to 1 to
67				 * abort that nested call.  This happens, for
68				 * example, if tkwin or any of its slaves
69				 * is deleted. */
70    int flags;			/* Miscellaneous flags;  see below
71				 * for definitions. */
72} Packer;
73
74/*
75 * Flag values for Packer structures:
76 *
77 * REQUESTED_REPACK:		1 means a Tcl_DoWhenIdle request
78 *				has already been made to repack
79 *				all the slaves of this window.
80 * FILLX:			1 means if frame allocated for window
81 *				is wider than window needs, expand window
82 *				to fill frame.  0 means don't make window
83 *				any larger than needed.
84 * FILLY:			Same as FILLX, except for height.
85 * EXPAND:			1 means this window's frame will absorb any
86 *				extra space in the parent window.
87 * OLD_STYLE:			1 means this window is being managed with
88 *				the old-style packer algorithms (before
89 *				Tk version 3.3).  The main difference is
90 *				that padding and filling are done differently.
91 * DONT_PROPAGATE:		1 means don't set this window's requested
92 *				size.  0 means if this window is a master
93 *				then Tk will set its requested size to fit
94 *				the needs of its slaves.
95 */
96
97#define REQUESTED_REPACK	1
98#define FILLX			2
99#define FILLY			4
100#define EXPAND			8
101#define OLD_STYLE		16
102#define DONT_PROPAGATE		32
103
104/*
105 * The following structure is the official type record for the
106 * packer:
107 */
108
109static void		PackReqProc _ANSI_ARGS_((ClientData clientData,
110			    Tk_Window tkwin));
111static void		PackLostSlaveProc _ANSI_ARGS_((ClientData clientData,
112			    Tk_Window tkwin));
113
114static Tk_GeomMgr packerType = {
115    "pack",			/* name */
116    PackReqProc,		/* requestProc */
117    PackLostSlaveProc,		/* lostSlaveProc */
118};
119
120/*
121 * Forward declarations for procedures defined later in this file:
122 */
123
124static void		ArrangePacking _ANSI_ARGS_((ClientData clientData));
125static int		ConfigureSlaves _ANSI_ARGS_((Tcl_Interp *interp,
126			    Tk_Window tkwin, int objc, Tcl_Obj *CONST objv[]));
127static void             DestroyPacker _ANSI_ARGS_((char *memPtr));
128static Packer *		GetPacker _ANSI_ARGS_((Tk_Window tkwin));
129static int		PackAfter _ANSI_ARGS_((Tcl_Interp *interp,
130			    Packer *prevPtr, Packer *masterPtr, int objc,
131			    Tcl_Obj *CONST objv[]));
132static void		PackReqProc _ANSI_ARGS_((ClientData clientData,
133			    Tk_Window tkwin));
134static void		PackStructureProc _ANSI_ARGS_((ClientData clientData,
135			    XEvent *eventPtr));
136static void		Unlink _ANSI_ARGS_((Packer *packPtr));
137static int		XExpansion _ANSI_ARGS_((Packer *slavePtr,
138			    int cavityWidth));
139static int		YExpansion _ANSI_ARGS_((Packer *slavePtr,
140			    int cavityHeight));
141
142/*
143 *--------------------------------------------------------------
144 *
145 * TkPrintPadAmount --
146 *
147 *	This procedure generates a text value that describes one
148 *	of the -padx, -pady, -ipadx, or -ipady configuration options.
149 *	The text value generated is appended to the interpreter
150 *	result.
151 *
152 * Results:
153 *	None.
154 *
155 * Side effects:
156 *	None.
157 *
158 *--------------------------------------------------------------
159 */
160void
161TkPrintPadAmount(interp, switchName, halfSpace, allSpace)
162    Tcl_Interp *interp;		/* The interpreter into which the result
163				 * is written. */
164    char *switchName;		/* One of "padx", "pady", "ipadx" or "ipady" */
165    int halfSpace;		/* The left or top padding amount */
166    int allSpace;		/* The total amount of padding */
167{
168    char buffer[60 + 2*TCL_INTEGER_SPACE];
169    if (halfSpace*2 == allSpace) {
170	sprintf(buffer, " -%.10s %d", switchName, halfSpace);
171    } else {
172	sprintf(buffer, " -%.10s {%d %d}", switchName, halfSpace,
173		allSpace - halfSpace);
174    }
175    Tcl_AppendResult(interp, buffer, (char *)NULL);
176}
177
178
179/*
180 *--------------------------------------------------------------
181 *
182 * Tk_PackCmd --
183 *
184 *	This procedure is invoked to process the "pack" Tcl command.
185 *	See the user documentation for details on what it does.
186 *
187 * Results:
188 *	A standard Tcl result.
189 *
190 * Side effects:
191 *	See the user documentation.
192 *
193 *--------------------------------------------------------------
194 */
195
196int
197Tk_PackObjCmd(clientData, interp, objc, objv)
198    ClientData clientData;	/* Main window associated with
199				 * interpreter. */
200    Tcl_Interp *interp;		/* Current interpreter. */
201    int objc;			/* Number of arguments. */
202    Tcl_Obj *CONST objv[];	/* Argument objects. */
203{
204    Tk_Window tkwin = (Tk_Window) clientData;
205    char *argv2;
206    static CONST char *optionStrings[] = {
207	/* after, append, before and unpack are deprecated */
208	"after", "append", "before", "unpack",
209	"configure", "forget", "info", "propagate", "slaves", (char *) NULL };
210    enum options {
211	PACK_AFTER, PACK_APPEND, PACK_BEFORE, PACK_UNPACK,
212	PACK_CONFIGURE, PACK_FORGET, PACK_INFO, PACK_PROPAGATE, PACK_SLAVES };
213    int index;
214
215    if (objc >= 2) {
216	char *string = Tcl_GetString(objv[1]);
217	if (string[0] == '.') {
218	    return ConfigureSlaves(interp, tkwin, objc-1, objv+1);
219	}
220    }
221    if (objc < 3) {
222	Tcl_WrongNumArgs(interp, 1, objv, "option arg ?arg ...?");
223	return TCL_ERROR;
224    }
225
226    if (Tcl_GetIndexFromObj(interp, objv[1], optionStrings, "option", 0,
227	    &index) != TCL_OK) {
228	/*
229	 * Call it again without the deprecated ones to get a proper
230	 * error message.
231	 * This works well since there can't be any ambiguity between
232	 * deprecated and new options.
233	 */
234
235	Tcl_ResetResult(interp);
236	Tcl_GetIndexFromObj(interp, objv[1], &optionStrings[4], "option", 0,
237		&index);
238	return TCL_ERROR;
239    }
240
241    argv2 = Tcl_GetString(objv[2]);
242    if (index == PACK_AFTER) {
243	Packer *prevPtr;
244	Tk_Window tkwin2;
245
246	if (TkGetWindowFromObj(interp, tkwin, objv[2], &tkwin2) != TCL_OK) {
247	    return TCL_ERROR;
248	}
249	prevPtr = GetPacker(tkwin2);
250	if (prevPtr->masterPtr == NULL) {
251	    Tcl_AppendResult(interp, "window \"", argv2,
252		    "\" isn't packed", (char *) NULL);
253	    return TCL_ERROR;
254	}
255	return PackAfter(interp, prevPtr, prevPtr->masterPtr, objc-3, objv+3);
256    } else if (index == PACK_APPEND) {
257	Packer *masterPtr;
258	register Packer *prevPtr;
259	Tk_Window tkwin2;
260
261	if (TkGetWindowFromObj(interp, tkwin, objv[2], &tkwin2) != TCL_OK) {
262	    return TCL_ERROR;
263	}
264	masterPtr = GetPacker(tkwin2);
265	prevPtr = masterPtr->slavePtr;
266	if (prevPtr != NULL) {
267	    while (prevPtr->nextPtr != NULL) {
268		prevPtr = prevPtr->nextPtr;
269	    }
270	}
271	return PackAfter(interp, prevPtr, masterPtr, objc-3, objv+3);
272    } else if (index == PACK_BEFORE) {
273	Packer *packPtr, *masterPtr;
274	register Packer *prevPtr;
275	Tk_Window tkwin2;
276
277	if (TkGetWindowFromObj(interp, tkwin, objv[2], &tkwin2) != TCL_OK) {
278	    return TCL_ERROR;
279	}
280	packPtr = GetPacker(tkwin2);
281	if (packPtr->masterPtr == NULL) {
282	    Tcl_AppendResult(interp, "window \"", argv2,
283		    "\" isn't packed", (char *) NULL);
284	    return TCL_ERROR;
285	}
286	masterPtr = packPtr->masterPtr;
287	prevPtr = masterPtr->slavePtr;
288	if (prevPtr == packPtr) {
289	    prevPtr = NULL;
290	} else {
291	    for ( ; ; prevPtr = prevPtr->nextPtr) {
292		if (prevPtr == NULL) {
293		    panic("\"pack before\" couldn't find predecessor");
294		}
295		if (prevPtr->nextPtr == packPtr) {
296		    break;
297		}
298	    }
299	}
300	return PackAfter(interp, prevPtr, masterPtr, objc-3, objv+3);
301    } else if (index == PACK_CONFIGURE) {
302	if (argv2[0] != '.') {
303	    Tcl_AppendResult(interp, "bad argument \"", argv2,
304		    "\": must be name of window", (char *) NULL);
305	    return TCL_ERROR;
306	}
307	return ConfigureSlaves(interp, tkwin, objc-2, objv+2);
308    } else if (index == PACK_FORGET) {
309	Tk_Window slave;
310	Packer *slavePtr;
311	int i;
312
313	for (i = 2; i < objc; i++) {
314	    if (TkGetWindowFromObj(interp, tkwin, objv[i], &slave) != TCL_OK) {
315		continue;
316	    }
317	    slavePtr = GetPacker(slave);
318	    if ((slavePtr != NULL) && (slavePtr->masterPtr != NULL)) {
319		Tk_ManageGeometry(slave, (Tk_GeomMgr *) NULL,
320			(ClientData) NULL);
321		if (slavePtr->masterPtr->tkwin != Tk_Parent(slavePtr->tkwin)) {
322		    Tk_UnmaintainGeometry(slavePtr->tkwin,
323			    slavePtr->masterPtr->tkwin);
324		}
325		Unlink(slavePtr);
326		Tk_UnmapWindow(slavePtr->tkwin);
327	    }
328	}
329    } else if (index == PACK_INFO) {
330	register Packer *slavePtr;
331	Tk_Window slave;
332
333	if (objc != 3) {
334	    Tcl_WrongNumArgs(interp, 2, objv, "window");
335	    return TCL_ERROR;
336	}
337	if (TkGetWindowFromObj(interp, tkwin, objv[2], &slave) != TCL_OK) {
338	    return TCL_ERROR;
339	}
340	slavePtr = GetPacker(slave);
341	if (slavePtr->masterPtr == NULL) {
342	    Tcl_AppendResult(interp, "window \"", argv2,
343		    "\" isn't packed", (char *) NULL);
344	    return TCL_ERROR;
345	}
346	Tcl_AppendElement(interp, "-in");
347	Tcl_AppendElement(interp, Tk_PathName(slavePtr->masterPtr->tkwin));
348	Tcl_AppendElement(interp, "-anchor");
349	Tcl_AppendElement(interp, Tk_NameOfAnchor(slavePtr->anchor));
350	Tcl_AppendResult(interp, " -expand ",
351		(slavePtr->flags & EXPAND) ? "1" : "0", " -fill ",
352		(char *) NULL);
353	switch (slavePtr->flags & (FILLX|FILLY)) {
354	    case 0:
355		Tcl_AppendResult(interp, "none", (char *) NULL);
356		break;
357	    case FILLX:
358		Tcl_AppendResult(interp, "x", (char *) NULL);
359		break;
360	    case FILLY:
361		Tcl_AppendResult(interp, "y", (char *) NULL);
362		break;
363	    case FILLX|FILLY:
364		Tcl_AppendResult(interp, "both", (char *) NULL);
365		break;
366	}
367        TkPrintPadAmount(interp, "ipadx", slavePtr->iPadX/2, slavePtr->iPadX);
368        TkPrintPadAmount(interp, "ipady", slavePtr->iPadY/2, slavePtr->iPadY);
369        TkPrintPadAmount(interp, "padx", slavePtr->padLeft, slavePtr->padX);
370        TkPrintPadAmount(interp, "pady", slavePtr->padTop, slavePtr->padY);
371	Tcl_AppendResult(interp, " -side ", sideNames[slavePtr->side],
372		(char *) NULL);
373    } else if (index == PACK_PROPAGATE) {
374	Tk_Window master;
375	Packer *masterPtr;
376	int propagate;
377
378	if (objc > 4) {
379	    Tcl_WrongNumArgs(interp, 2, objv, "window ?boolean?");
380	    return TCL_ERROR;
381	}
382	if (TkGetWindowFromObj(interp, tkwin, objv[2], &master) != TCL_OK) {
383	    return TCL_ERROR;
384	}
385	masterPtr = GetPacker(master);
386	if (objc == 3) {
387	    Tcl_SetObjResult(interp,
388		    Tcl_NewBooleanObj(!(masterPtr->flags & DONT_PROPAGATE)));
389	    return TCL_OK;
390	}
391	if (Tcl_GetBooleanFromObj(interp, objv[3], &propagate) != TCL_OK) {
392	    return TCL_ERROR;
393	}
394	if (propagate) {
395	    masterPtr->flags &= ~DONT_PROPAGATE;
396
397	    /*
398	     * Repack the master to allow new geometry information to
399	     * propagate upwards to the master's master.
400	     */
401
402	    if (masterPtr->abortPtr != NULL) {
403		*masterPtr->abortPtr = 1;
404	    }
405	    if (!(masterPtr->flags & REQUESTED_REPACK)) {
406		masterPtr->flags |= REQUESTED_REPACK;
407		Tcl_DoWhenIdle(ArrangePacking, (ClientData) masterPtr);
408	    }
409	} else {
410	    masterPtr->flags |= DONT_PROPAGATE;
411	}
412    } else if (index == PACK_SLAVES) {
413	Tk_Window master;
414	Packer *masterPtr, *slavePtr;
415
416	if (objc != 3) {
417	    Tcl_WrongNumArgs(interp, 2, objv, "window");
418	    return TCL_ERROR;
419	}
420	if (TkGetWindowFromObj(interp, tkwin, objv[2], &master) != TCL_OK) {
421	    return TCL_ERROR;
422	}
423	masterPtr = GetPacker(master);
424	for (slavePtr = masterPtr->slavePtr; slavePtr != NULL;
425		slavePtr = slavePtr->nextPtr) {
426	    Tcl_AppendElement(interp, Tk_PathName(slavePtr->tkwin));
427	}
428    } else if (index == PACK_UNPACK) {
429	Tk_Window tkwin2;
430	Packer *packPtr;
431
432	if (objc != 3) {
433	    Tcl_WrongNumArgs(interp, 2, objv, "window");
434	    return TCL_ERROR;
435	}
436	if (TkGetWindowFromObj(interp, tkwin, objv[2], &tkwin2) != TCL_OK) {
437	    return TCL_ERROR;
438	}
439	packPtr = GetPacker(tkwin2);
440	if ((packPtr != NULL) && (packPtr->masterPtr != NULL)) {
441	    Tk_ManageGeometry(tkwin2, (Tk_GeomMgr *) NULL,
442		    (ClientData) NULL);
443	    if (packPtr->masterPtr->tkwin != Tk_Parent(packPtr->tkwin)) {
444		Tk_UnmaintainGeometry(packPtr->tkwin,
445			packPtr->masterPtr->tkwin);
446	    }
447	    Unlink(packPtr);
448	    Tk_UnmapWindow(packPtr->tkwin);
449	}
450    }
451
452    return TCL_OK;
453}
454
455/*
456 *--------------------------------------------------------------
457 *
458 * PackReqProc --
459 *
460 *	This procedure is invoked by Tk_GeometryRequest for
461 *	windows managed by the packer.
462 *
463 * Results:
464 *	None.
465 *
466 * Side effects:
467 *	Arranges for tkwin, and all its managed siblings, to
468 *	be re-packed at the next idle point.
469 *
470 *--------------------------------------------------------------
471 */
472
473	/* ARGSUSED */
474static void
475PackReqProc(clientData, tkwin)
476    ClientData clientData;	/* Packer's information about
477				 * window that got new preferred
478				 * geometry.  */
479    Tk_Window tkwin;		/* Other Tk-related information
480				 * about the window. */
481{
482    register Packer *packPtr = (Packer *) clientData;
483
484    packPtr = packPtr->masterPtr;
485    if (!(packPtr->flags & REQUESTED_REPACK)) {
486	packPtr->flags |= REQUESTED_REPACK;
487	Tcl_DoWhenIdle(ArrangePacking, (ClientData) packPtr);
488    }
489}
490
491/*
492 *--------------------------------------------------------------
493 *
494 * PackLostSlaveProc --
495 *
496 *	This procedure is invoked by Tk whenever some other geometry
497 *	claims control over a slave that used to be managed by us.
498 *
499 * Results:
500 *	None.
501 *
502 * Side effects:
503 *	Forgets all packer-related information about the slave.
504 *
505 *--------------------------------------------------------------
506 */
507
508	/* ARGSUSED */
509static void
510PackLostSlaveProc(clientData, tkwin)
511    ClientData clientData;	/* Packer structure for slave window that
512				 * was stolen away. */
513    Tk_Window tkwin;		/* Tk's handle for the slave window. */
514{
515    register Packer *slavePtr = (Packer *) clientData;
516
517    if (slavePtr->masterPtr->tkwin != Tk_Parent(slavePtr->tkwin)) {
518	Tk_UnmaintainGeometry(slavePtr->tkwin, slavePtr->masterPtr->tkwin);
519    }
520    Unlink(slavePtr);
521    Tk_UnmapWindow(slavePtr->tkwin);
522}
523
524/*
525 *--------------------------------------------------------------
526 *
527 * ArrangePacking --
528 *
529 *	This procedure is invoked (using the Tcl_DoWhenIdle
530 *	mechanism) to re-layout a set of windows managed by
531 *	the packer.  It is invoked at idle time so that a
532 *	series of packer requests can be merged into a single
533 *	layout operation.
534 *
535 * Results:
536 *	None.
537 *
538 * Side effects:
539 *	The packed slaves of masterPtr may get resized or
540 *	moved.
541 *
542 *--------------------------------------------------------------
543 */
544
545static void
546ArrangePacking(clientData)
547    ClientData clientData;	/* Structure describing parent whose slaves
548				 * are to be re-layed out. */
549{
550    register Packer *masterPtr = (Packer *) clientData;
551    register Packer *slavePtr;
552    int cavityX, cavityY, cavityWidth, cavityHeight;
553				/* These variables keep track of the
554				 * as-yet-unallocated space remaining in
555				 * the middle of the parent window. */
556    int frameX, frameY, frameWidth, frameHeight;
557				/* These variables keep track of the frame
558				 * allocated to the current window. */
559    int x, y, width, height;	/* These variables are used to hold the
560				 * actual geometry of the current window. */
561    int abort;			/* May get set to non-zero to abort this
562				 * repacking operation. */
563    int borderX, borderY;
564    int borderTop, borderBtm;
565    int borderLeft, borderRight;
566    int maxWidth, maxHeight, tmp;
567
568    masterPtr->flags &= ~REQUESTED_REPACK;
569
570    /*
571     * If the parent has no slaves anymore, then don't do anything
572     * at all:  just leave the parent's size as-is.
573     */
574
575    if (masterPtr->slavePtr == NULL) {
576	return;
577    }
578
579    /*
580     * Abort any nested call to ArrangePacking for this window, since
581     * we'll do everything necessary here, and set up so this call
582     * can be aborted if necessary.
583     */
584
585    if (masterPtr->abortPtr != NULL) {
586	*masterPtr->abortPtr = 1;
587    }
588    masterPtr->abortPtr = &abort;
589    abort = 0;
590    Tcl_Preserve((ClientData) masterPtr);
591
592    /*
593     * Pass #1: scan all the slaves to figure out the total amount
594     * of space needed.  Two separate width and height values are
595     * computed:
596     *
597     * width -		Holds the sum of the widths (plus padding) of
598     *			all the slaves seen so far that were packed LEFT
599     *			or RIGHT.
600     * height -		Holds the sum of the heights (plus padding) of
601     *			all the slaves seen so far that were packed TOP
602     *			or BOTTOM.
603     *
604     * maxWidth -	Gradually builds up the width needed by the master
605     *			to just barely satisfy all the slave's needs.  For
606     *			each slave, the code computes the width needed for
607     *			all the slaves so far and updates maxWidth if the
608     *			new value is greater.
609     * maxHeight -	Same as maxWidth, except keeps height info.
610     */
611
612    width = maxWidth = Tk_InternalBorderLeft(masterPtr->tkwin) +
613	    Tk_InternalBorderRight(masterPtr->tkwin);
614    height = maxHeight = Tk_InternalBorderTop(masterPtr->tkwin) +
615	    Tk_InternalBorderBottom(masterPtr->tkwin);
616    for (slavePtr = masterPtr->slavePtr; slavePtr != NULL;
617	    slavePtr = slavePtr->nextPtr) {
618	if ((slavePtr->side == TOP) || (slavePtr->side == BOTTOM)) {
619	    tmp = Tk_ReqWidth(slavePtr->tkwin) + slavePtr->doubleBw
620		    + slavePtr->padX + slavePtr->iPadX + width;
621	    if (tmp > maxWidth) {
622		maxWidth = tmp;
623	    }
624	    height += Tk_ReqHeight(slavePtr->tkwin) + slavePtr->doubleBw
625		    + slavePtr->padY + slavePtr->iPadY;
626	} else {
627	    tmp = Tk_ReqHeight(slavePtr->tkwin) + slavePtr->doubleBw
628		    + slavePtr->padY + slavePtr->iPadY + height;
629	    if (tmp > maxHeight) {
630		maxHeight = tmp;
631	    }
632	    width += Tk_ReqWidth(slavePtr->tkwin) + slavePtr->doubleBw
633		    + slavePtr->padX + slavePtr->iPadX;
634	}
635    }
636    if (width > maxWidth) {
637	maxWidth = width;
638    }
639    if (height > maxHeight) {
640	maxHeight = height;
641    }
642
643    if (maxWidth < Tk_MinReqWidth(masterPtr->tkwin)) {
644	maxWidth = Tk_MinReqWidth(masterPtr->tkwin);
645    }
646    if (maxHeight < Tk_MinReqHeight(masterPtr->tkwin)) {
647	maxHeight = Tk_MinReqHeight(masterPtr->tkwin);
648    }
649
650    /*
651     * If the total amount of space needed in the parent window has
652     * changed, and if we're propagating geometry information, then
653     * notify the next geometry manager up and requeue ourselves to
654     * start again after the parent has had a chance to
655     * resize us.
656     */
657
658    if (((maxWidth != Tk_ReqWidth(masterPtr->tkwin))
659	    || (maxHeight != Tk_ReqHeight(masterPtr->tkwin)))
660	    && !(masterPtr->flags & DONT_PROPAGATE)) {
661	Tk_GeometryRequest(masterPtr->tkwin, maxWidth, maxHeight);
662	masterPtr->flags |= REQUESTED_REPACK;
663	Tcl_DoWhenIdle(ArrangePacking, (ClientData) masterPtr);
664	goto done;
665    }
666
667    /*
668     * Pass #2: scan the slaves a second time assigning
669     * new sizes.  The "cavity" variables keep track of the
670     * unclaimed space in the cavity of the window;  this
671     * shrinks inward as we allocate windows around the
672     * edges.  The "frame" variables keep track of the space
673     * allocated to the current window and its frame.  The
674     * current window is then placed somewhere inside the
675     * frame, depending on anchor.
676     */
677
678    cavityX = x = Tk_InternalBorderLeft(masterPtr->tkwin);
679    cavityY = y = Tk_InternalBorderTop(masterPtr->tkwin);
680    cavityWidth = Tk_Width(masterPtr->tkwin) -
681	    Tk_InternalBorderLeft(masterPtr->tkwin) -
682	    Tk_InternalBorderRight(masterPtr->tkwin);
683    cavityHeight = Tk_Height(masterPtr->tkwin) -
684	    Tk_InternalBorderTop(masterPtr->tkwin) -
685	    Tk_InternalBorderBottom(masterPtr->tkwin);
686    for (slavePtr = masterPtr->slavePtr; slavePtr != NULL;
687	    slavePtr = slavePtr->nextPtr) {
688	if ((slavePtr->side == TOP) || (slavePtr->side == BOTTOM)) {
689	    frameWidth = cavityWidth;
690	    frameHeight = Tk_ReqHeight(slavePtr->tkwin) + slavePtr->doubleBw
691		    + slavePtr->padY + slavePtr->iPadY;
692	    if (slavePtr->flags & EXPAND) {
693		frameHeight += YExpansion(slavePtr, cavityHeight);
694	    }
695	    cavityHeight -= frameHeight;
696	    if (cavityHeight < 0) {
697		frameHeight += cavityHeight;
698		cavityHeight = 0;
699	    }
700	    frameX = cavityX;
701	    if (slavePtr->side == TOP) {
702		frameY = cavityY;
703		cavityY += frameHeight;
704	    } else {
705		frameY = cavityY + cavityHeight;
706	    }
707	} else {
708	    frameHeight = cavityHeight;
709	    frameWidth = Tk_ReqWidth(slavePtr->tkwin) + slavePtr->doubleBw
710		    + slavePtr->padX + slavePtr->iPadX;
711	    if (slavePtr->flags & EXPAND) {
712		frameWidth += XExpansion(slavePtr, cavityWidth);
713	    }
714	    cavityWidth -= frameWidth;
715	    if (cavityWidth < 0) {
716		frameWidth += cavityWidth;
717		cavityWidth = 0;
718	    }
719	    frameY = cavityY;
720	    if (slavePtr->side == LEFT) {
721		frameX = cavityX;
722		cavityX += frameWidth;
723	    } else {
724		frameX = cavityX + cavityWidth;
725	    }
726	}
727
728	/*
729	 * Now that we've got the size of the frame for the window,
730	 * compute the window's actual size and location using the
731	 * fill, padding, and frame factors.  The variables "borderX"
732	 * and "borderY" are used to handle the differences between
733	 * old-style packing and the new style (in old-style, iPadX
734	 * and iPadY are always zero and padding is completely ignored
735	 * except when computing frame size).
736	 */
737
738	if (slavePtr->flags & OLD_STYLE) {
739	    borderX = borderY = 0;
740	    borderTop = borderBtm = 0;
741	    borderLeft = borderRight = 0;
742	} else {
743	    borderX = slavePtr->padX;
744	    borderY = slavePtr->padY;
745	    borderLeft = slavePtr->padLeft;
746	    borderRight = borderX - borderLeft;
747	    borderTop = slavePtr->padTop;
748	    borderBtm = borderY - borderTop;
749	}
750	width = Tk_ReqWidth(slavePtr->tkwin) + slavePtr->doubleBw
751		+ slavePtr->iPadX;
752	if ((slavePtr->flags & FILLX)
753		|| (width > (frameWidth - borderX))) {
754	    width = frameWidth - borderX;
755	}
756	height = Tk_ReqHeight(slavePtr->tkwin) + slavePtr->doubleBw
757		+ slavePtr->iPadY;
758	if ((slavePtr->flags & FILLY)
759		|| (height > (frameHeight - borderY))) {
760	    height = frameHeight - borderY;
761	}
762	switch (slavePtr->anchor) {
763	    case TK_ANCHOR_N:
764		x = frameX + (borderLeft + frameWidth - width - borderRight)/2;
765		y = frameY + borderTop;
766		break;
767	    case TK_ANCHOR_NE:
768		x = frameX + frameWidth - width - borderRight;
769		y = frameY + borderTop;
770		break;
771	    case TK_ANCHOR_E:
772		x = frameX + frameWidth - width - borderRight;
773		y = frameY + (borderTop + frameHeight - height - borderBtm)/2;
774		break;
775	    case TK_ANCHOR_SE:
776		x = frameX + frameWidth - width - borderRight;
777		y = frameY + frameHeight - height - borderBtm;
778		break;
779	    case TK_ANCHOR_S:
780		x = frameX + (borderLeft + frameWidth - width - borderRight)/2;
781		y = frameY + frameHeight - height - borderBtm;
782		break;
783	    case TK_ANCHOR_SW:
784		x = frameX + borderLeft;
785		y = frameY + frameHeight - height - borderBtm;
786		break;
787	    case TK_ANCHOR_W:
788		x = frameX + borderLeft;
789		y = frameY + (borderTop + frameHeight - height - borderBtm)/2;
790		break;
791	    case TK_ANCHOR_NW:
792		x = frameX + borderLeft;
793		y = frameY + borderTop;
794		break;
795	    case TK_ANCHOR_CENTER:
796		x = frameX + (borderLeft + frameWidth - width - borderRight)/2;
797		y = frameY + (borderTop + frameHeight - height - borderBtm)/2;
798		break;
799	    default:
800		panic("bad frame factor in ArrangePacking");
801	}
802	width -= slavePtr->doubleBw;
803	height -= slavePtr->doubleBw;
804
805	/*
806	 * The final step is to set the position, size, and mapped/unmapped
807	 * state of the slave.  If the slave is a child of the master, then
808	 * do this here.  Otherwise let Tk_MaintainGeometry do the work.
809	 */
810
811	if (masterPtr->tkwin == Tk_Parent(slavePtr->tkwin)) {
812	    if ((width <= 0) || (height <= 0)) {
813		Tk_UnmapWindow(slavePtr->tkwin);
814	    } else {
815		if ((x != Tk_X(slavePtr->tkwin))
816			|| (y != Tk_Y(slavePtr->tkwin))
817			|| (width != Tk_Width(slavePtr->tkwin))
818			|| (height != Tk_Height(slavePtr->tkwin))) {
819		    Tk_MoveResizeWindow(slavePtr->tkwin, x, y, width, height);
820		}
821		if (abort) {
822		    goto done;
823		}
824
825		/*
826		 * Don't map the slave if the master isn't mapped: wait
827		 * until the master gets mapped later.
828		 */
829
830		if (Tk_IsMapped(masterPtr->tkwin)) {
831		    Tk_MapWindow(slavePtr->tkwin);
832		}
833	    }
834	} else {
835	    if ((width <= 0) || (height <= 0)) {
836		Tk_UnmaintainGeometry(slavePtr->tkwin, masterPtr->tkwin);
837		Tk_UnmapWindow(slavePtr->tkwin);
838	    } else {
839		Tk_MaintainGeometry(slavePtr->tkwin, masterPtr->tkwin,
840			x, y, width, height);
841	    }
842	}
843
844	/*
845	 * Changes to the window's structure could cause almost anything
846	 * to happen, including deleting the parent or child.  If this
847	 * happens, we'll be told to abort.
848	 */
849
850	if (abort) {
851	    goto done;
852	}
853    }
854
855    done:
856    masterPtr->abortPtr = NULL;
857    Tcl_Release((ClientData) masterPtr);
858}
859
860/*
861 *----------------------------------------------------------------------
862 *
863 * XExpansion --
864 *
865 *	Given a list of packed slaves, the first of which is packed
866 *	on the left or right and is expandable, compute how much to
867 *	expand the child.
868 *
869 * Results:
870 *	The return value is the number of additional pixels to give to
871 *	the child.
872 *
873 * Side effects:
874 *	None.
875 *
876 *----------------------------------------------------------------------
877 */
878
879static int
880XExpansion(slavePtr, cavityWidth)
881    register Packer *slavePtr;		/* First in list of remaining
882					 * slaves. */
883    int cavityWidth;			/* Horizontal space left for all
884					 * remaining slaves. */
885{
886    int numExpand, minExpand, curExpand;
887    int childWidth;
888
889    /*
890     * This procedure is tricky because windows packed top or bottom can
891     * be interspersed among expandable windows packed left or right.
892     * Scan through the list, keeping a running sum of the widths of
893     * all left and right windows (actually, count the cavity space not
894     * allocated) and a running count of all expandable left and right
895     * windows.  At each top or bottom window, and at the end of the
896     * list, compute the expansion factor that seems reasonable at that
897     * point.  Return the smallest factor seen at any of these points.
898     */
899
900    minExpand = cavityWidth;
901    numExpand = 0;
902    for ( ; slavePtr != NULL; slavePtr = slavePtr->nextPtr) {
903	childWidth = Tk_ReqWidth(slavePtr->tkwin) + slavePtr->doubleBw
904		+ slavePtr->padX + slavePtr->iPadX;
905	if ((slavePtr->side == TOP) || (slavePtr->side == BOTTOM)) {
906	    curExpand = (cavityWidth - childWidth)/numExpand;
907	    if (curExpand < minExpand) {
908		minExpand = curExpand;
909	    }
910	} else {
911	    cavityWidth -= childWidth;
912	    if (slavePtr->flags & EXPAND) {
913		numExpand++;
914	    }
915	}
916    }
917    curExpand = cavityWidth/numExpand;
918    if (curExpand < minExpand) {
919	minExpand = curExpand;
920    }
921    return (minExpand < 0) ? 0 : minExpand;
922}
923
924/*
925 *----------------------------------------------------------------------
926 *
927 * YExpansion --
928 *
929 *	Given a list of packed slaves, the first of which is packed
930 *	on the top or bottom and is expandable, compute how much to
931 *	expand the child.
932 *
933 * Results:
934 *	The return value is the number of additional pixels to give to
935 *	the child.
936 *
937 * Side effects:
938 *	None.
939 *
940 *----------------------------------------------------------------------
941 */
942
943static int
944YExpansion(slavePtr, cavityHeight)
945    register Packer *slavePtr;		/* First in list of remaining
946					 * slaves. */
947    int cavityHeight;			/* Vertical space left for all
948					 * remaining slaves. */
949{
950    int numExpand, minExpand, curExpand;
951    int childHeight;
952
953    /*
954     * See comments for XExpansion.
955     */
956
957    minExpand = cavityHeight;
958    numExpand = 0;
959    for ( ; slavePtr != NULL; slavePtr = slavePtr->nextPtr) {
960	childHeight = Tk_ReqHeight(slavePtr->tkwin) + slavePtr->doubleBw
961		+ slavePtr->padY + slavePtr->iPadY;
962	if ((slavePtr->side == LEFT) || (slavePtr->side == RIGHT)) {
963	    curExpand = (cavityHeight - childHeight)/numExpand;
964	    if (curExpand < minExpand) {
965		minExpand = curExpand;
966	    }
967	} else {
968	    cavityHeight -= childHeight;
969	    if (slavePtr->flags & EXPAND) {
970		numExpand++;
971	    }
972	}
973    }
974    curExpand = cavityHeight/numExpand;
975    if (curExpand < minExpand) {
976	minExpand = curExpand;
977    }
978    return (minExpand < 0) ? 0 : minExpand;
979}
980
981/*
982 *--------------------------------------------------------------
983 *
984 * GetPacker --
985 *
986 *	This internal procedure is used to locate a Packer
987 *	structure for a given window, creating one if one
988 *	doesn't exist already.
989 *
990 * Results:
991 *	The return value is a pointer to the Packer structure
992 *	corresponding to tkwin.
993 *
994 * Side effects:
995 *	A new packer structure may be created.  If so, then
996 *	a callback is set up to clean things up when the
997 *	window is deleted.
998 *
999 *--------------------------------------------------------------
1000 */
1001
1002static Packer *
1003GetPacker(tkwin)
1004    Tk_Window tkwin;		/* Token for window for which
1005				 * packer structure is desired. */
1006{
1007    register Packer *packPtr;
1008    Tcl_HashEntry *hPtr;
1009    int new;
1010    TkDisplay *dispPtr = ((TkWindow *) tkwin)->dispPtr;
1011
1012    if (!dispPtr->packInit) {
1013	dispPtr->packInit = 1;
1014	Tcl_InitHashTable(&dispPtr->packerHashTable, TCL_ONE_WORD_KEYS);
1015    }
1016
1017    /*
1018     * See if there's already packer for this window.  If not,
1019     * then create a new one.
1020     */
1021
1022    hPtr = Tcl_CreateHashEntry(&dispPtr->packerHashTable, (char *) tkwin,
1023            &new);
1024    if (!new) {
1025	return (Packer *) Tcl_GetHashValue(hPtr);
1026    }
1027    packPtr = (Packer *) ckalloc(sizeof(Packer));
1028    packPtr->tkwin = tkwin;
1029    packPtr->masterPtr = NULL;
1030    packPtr->nextPtr = NULL;
1031    packPtr->slavePtr = NULL;
1032    packPtr->side = TOP;
1033    packPtr->anchor = TK_ANCHOR_CENTER;
1034    packPtr->padX = packPtr->padY = 0;
1035    packPtr->padLeft = packPtr->padTop = 0;
1036    packPtr->iPadX = packPtr->iPadY = 0;
1037    packPtr->doubleBw = 2*Tk_Changes(tkwin)->border_width;
1038    packPtr->abortPtr = NULL;
1039    packPtr->flags = 0;
1040    Tcl_SetHashValue(hPtr, packPtr);
1041    Tk_CreateEventHandler(tkwin, StructureNotifyMask,
1042	    PackStructureProc, (ClientData) packPtr);
1043    return packPtr;
1044}
1045
1046/*
1047 *--------------------------------------------------------------
1048 *
1049 * PackAfter --
1050 *
1051 *	This procedure does most of the real work of adding
1052 *	one or more windows into the packing order for its parent.
1053 *
1054 * Results:
1055 *	A standard Tcl return value.
1056 *
1057 * Side effects:
1058 *	The geometry of the specified windows may change, both now and
1059 *	again in the future.
1060 *
1061 *--------------------------------------------------------------
1062 */
1063
1064static int
1065PackAfter(interp, prevPtr, masterPtr, objc, objv)
1066    Tcl_Interp *interp;		/* Interpreter for error reporting. */
1067    Packer *prevPtr;		/* Pack windows in argv just after this
1068				 * window;  NULL means pack as first
1069				 * child of masterPtr. */
1070    Packer *masterPtr;		/* Master in which to pack windows. */
1071    int objc;			/* Number of elements in objv. */
1072    Tcl_Obj *CONST objv[];	/* Array of lists, each containing 2
1073				 * elements:  window name and side
1074				 * against which to pack. */
1075{
1076    register Packer *packPtr;
1077    Tk_Window tkwin, ancestor, parent;
1078    int length;
1079    Tcl_Obj **options;
1080    int index, optionCount, c;
1081
1082    /*
1083     * Iterate over all of the window specifiers, each consisting of
1084     * two arguments.  The first argument contains the window name and
1085     * the additional arguments contain options such as "top" or
1086     * "padx 20".
1087     */
1088
1089    for ( ; objc > 0; objc -= 2, objv += 2, prevPtr = packPtr) {
1090	if (objc < 2) {
1091	    Tcl_AppendResult(interp, "wrong # args: window \"",
1092		    Tcl_GetString(objv[0]), "\" should be followed by options",
1093		    (char *) NULL);
1094	    return TCL_ERROR;
1095	}
1096
1097	/*
1098	 * Find the packer for the window to be packed, and make sure
1099	 * that the window in which it will be packed is either its
1100	 * or a descendant of its parent.
1101	 */
1102
1103	if (TkGetWindowFromObj(interp, masterPtr->tkwin, objv[0], &tkwin)
1104		!= TCL_OK) {
1105	    return TCL_ERROR;
1106	}
1107
1108	parent = Tk_Parent(tkwin);
1109	for (ancestor = masterPtr->tkwin; ; ancestor = Tk_Parent(ancestor)) {
1110	    if (ancestor == parent) {
1111		break;
1112	    }
1113	    if (((Tk_FakeWin *) (ancestor))->flags & TK_TOP_HIERARCHY) {
1114		badWindow:
1115		Tcl_AppendResult(interp, "can't pack ", Tcl_GetString(objv[0]),
1116			" inside ", Tk_PathName(masterPtr->tkwin),
1117			(char *) NULL);
1118		return TCL_ERROR;
1119	    }
1120	}
1121	if (((Tk_FakeWin *) (tkwin))->flags & TK_TOP_HIERARCHY) {
1122	    goto badWindow;
1123	}
1124	if (tkwin == masterPtr->tkwin) {
1125	    goto badWindow;
1126	}
1127	packPtr = GetPacker(tkwin);
1128
1129	/*
1130	 * Process options for this window.
1131	 */
1132
1133	if (Tcl_ListObjGetElements(interp, objv[1], &optionCount, &options)
1134		!= TCL_OK) {
1135	    return TCL_ERROR;
1136	}
1137	packPtr->side = TOP;
1138	packPtr->anchor = TK_ANCHOR_CENTER;
1139	packPtr->padX = packPtr->padY = 0;
1140	packPtr->padLeft = packPtr->padTop = 0;
1141	packPtr->iPadX = packPtr->iPadY = 0;
1142	packPtr->flags &= ~(FILLX|FILLY|EXPAND);
1143	packPtr->flags |= OLD_STYLE;
1144	for (index = 0 ; index < optionCount; index++) {
1145	    Tcl_Obj *curOptPtr = options[index];
1146	    char *curOpt = Tcl_GetStringFromObj(curOptPtr, &length);
1147
1148	    c = curOpt[0];
1149
1150	    if ((c == 't')
1151		    && (strncmp(curOpt, "top", (unsigned) length)) == 0) {
1152		packPtr->side = TOP;
1153	    } else if ((c == 'b')
1154		    && (strncmp(curOpt, "bottom", (unsigned) length)) == 0) {
1155		packPtr->side = BOTTOM;
1156	    } else if ((c == 'l')
1157		    && (strncmp(curOpt, "left", (unsigned) length)) == 0) {
1158		packPtr->side = LEFT;
1159	    } else if ((c == 'r')
1160		    && (strncmp(curOpt, "right", (unsigned) length)) == 0) {
1161		packPtr->side = RIGHT;
1162	    } else if ((c == 'e')
1163		    && (strncmp(curOpt, "expand", (unsigned) length)) == 0) {
1164		packPtr->flags |= EXPAND;
1165	    } else if ((c == 'f')
1166		    && (strcmp(curOpt, "fill")) == 0) {
1167		packPtr->flags |= FILLX|FILLY;
1168	    } else if ((length == 5) && (strcmp(curOpt, "fillx")) == 0) {
1169		packPtr->flags |= FILLX;
1170	    } else if ((length == 5) && (strcmp(curOpt, "filly")) == 0) {
1171		packPtr->flags |= FILLY;
1172	    } else if ((c == 'p') && (strcmp(curOpt, "padx")) == 0) {
1173		if (optionCount < (index+2)) {
1174		    missingPad:
1175		    Tcl_AppendResult(interp, "wrong # args: \"", curOpt,
1176			    "\" option must be followed by screen distance",
1177			    (char *) NULL);
1178		    return TCL_ERROR;
1179		}
1180		if (TkParsePadAmount(interp, tkwin, options[index+1],
1181			&packPtr->padLeft, &packPtr->padX) != TCL_OK) {
1182		    return TCL_ERROR;
1183		}
1184		packPtr->padX /= 2;
1185		packPtr->padLeft /= 2;
1186		packPtr->iPadX = 0;
1187		index++;
1188	    } else if ((c == 'p') && (strcmp(curOpt, "pady")) == 0) {
1189		if (optionCount < (index+2)) {
1190		    goto missingPad;
1191		}
1192		if (TkParsePadAmount(interp, tkwin, options[index+1],
1193			&packPtr->padTop, &packPtr->padY) != TCL_OK) {
1194		    return TCL_ERROR;
1195		}
1196		packPtr->padY /= 2;
1197		packPtr->padTop /= 2;
1198		packPtr->iPadY = 0;
1199		index++;
1200	    } else if ((c == 'f') && (length > 1)
1201		    && (strncmp(curOpt, "frame", (unsigned) length) == 0)) {
1202		if (optionCount < (index+2)) {
1203		    Tcl_AppendResult(interp, "wrong # args: \"frame\" ",
1204			    "option must be followed by anchor point",
1205			    (char *) NULL);
1206		    return TCL_ERROR;
1207		}
1208		if (Tk_GetAnchorFromObj(interp, options[index+1],
1209			&packPtr->anchor) != TCL_OK) {
1210		    return TCL_ERROR;
1211		}
1212		index++;
1213	    } else {
1214		Tcl_AppendResult(interp, "bad option \"", curOpt,
1215			"\": should be top, bottom, left, right, ",
1216			"expand, fill, fillx, filly, padx, pady, or frame",
1217			(char *) NULL);
1218		return TCL_ERROR;
1219	    }
1220	}
1221
1222	if (packPtr != prevPtr) {
1223
1224	    /*
1225	     * Unpack this window if it's currently packed.
1226	     */
1227
1228	    if (packPtr->masterPtr != NULL) {
1229		if ((packPtr->masterPtr != masterPtr) &&
1230			(packPtr->masterPtr->tkwin
1231			!= Tk_Parent(packPtr->tkwin))) {
1232		    Tk_UnmaintainGeometry(packPtr->tkwin,
1233			    packPtr->masterPtr->tkwin);
1234		}
1235		Unlink(packPtr);
1236	    }
1237
1238	    /*
1239	     * Add the window in the correct place in its parent's
1240	     * packing order, then make sure that the window is
1241	     * managed by us.
1242	     */
1243
1244	    packPtr->masterPtr = masterPtr;
1245	    if (prevPtr == NULL) {
1246		packPtr->nextPtr = masterPtr->slavePtr;
1247		masterPtr->slavePtr = packPtr;
1248	    } else {
1249		packPtr->nextPtr = prevPtr->nextPtr;
1250		prevPtr->nextPtr = packPtr;
1251	    }
1252	    Tk_ManageGeometry(tkwin, &packerType, (ClientData) packPtr);
1253	}
1254    }
1255
1256    /*
1257     * Arrange for the parent to be re-packed at the first
1258     * idle moment.
1259     */
1260
1261    if (masterPtr->abortPtr != NULL) {
1262	*masterPtr->abortPtr = 1;
1263    }
1264    if (!(masterPtr->flags & REQUESTED_REPACK)) {
1265	masterPtr->flags |= REQUESTED_REPACK;
1266	Tcl_DoWhenIdle(ArrangePacking, (ClientData) masterPtr);
1267    }
1268    return TCL_OK;
1269}
1270
1271/*
1272 *----------------------------------------------------------------------
1273 *
1274 * Unlink --
1275 *
1276 *	Remove a packer from its parent's list of slaves.
1277 *
1278 * Results:
1279 *	None.
1280 *
1281 * Side effects:
1282 *	The parent will be scheduled for repacking.
1283 *
1284 *----------------------------------------------------------------------
1285 */
1286
1287static void
1288Unlink(packPtr)
1289    register Packer *packPtr;		/* Window to unlink. */
1290{
1291    register Packer *masterPtr, *packPtr2;
1292
1293    masterPtr = packPtr->masterPtr;
1294    if (masterPtr == NULL) {
1295	return;
1296    }
1297    if (masterPtr->slavePtr == packPtr) {
1298	masterPtr->slavePtr = packPtr->nextPtr;
1299    } else {
1300	for (packPtr2 = masterPtr->slavePtr; ; packPtr2 = packPtr2->nextPtr) {
1301	    if (packPtr2 == NULL) {
1302		panic("Unlink couldn't find previous window");
1303	    }
1304	    if (packPtr2->nextPtr == packPtr) {
1305		packPtr2->nextPtr = packPtr->nextPtr;
1306		break;
1307	    }
1308	}
1309    }
1310    if (!(masterPtr->flags & REQUESTED_REPACK)) {
1311	masterPtr->flags |= REQUESTED_REPACK;
1312	Tcl_DoWhenIdle(ArrangePacking, (ClientData) masterPtr);
1313    }
1314    if (masterPtr->abortPtr != NULL) {
1315	*masterPtr->abortPtr = 1;
1316    }
1317
1318    packPtr->masterPtr = NULL;
1319}
1320
1321/*
1322 *----------------------------------------------------------------------
1323 *
1324 * DestroyPacker --
1325 *
1326 *	This procedure is invoked by Tcl_EventuallyFree or Tcl_Release
1327 *	to clean up the internal structure of a packer at a safe time
1328 *	(when no-one is using it anymore).
1329 *
1330 * Results:
1331 *	None.
1332 *
1333 * Side effects:
1334 *	Everything associated with the packer is freed up.
1335 *
1336 *----------------------------------------------------------------------
1337 */
1338
1339static void
1340DestroyPacker(memPtr)
1341    char *memPtr;		/* Info about packed window that
1342				 * is now dead. */
1343{
1344    register Packer *packPtr = (Packer *) memPtr;
1345    ckfree((char *) packPtr);
1346}
1347
1348/*
1349 *----------------------------------------------------------------------
1350 *
1351 * PackStructureProc --
1352 *
1353 *	This procedure is invoked by the Tk event dispatcher in response
1354 *	to StructureNotify events.
1355 *
1356 * Results:
1357 *	None.
1358 *
1359 * Side effects:
1360 *	If a window was just deleted, clean up all its packer-related
1361 *	information.  If it was just resized, repack its slaves, if
1362 *	any.
1363 *
1364 *----------------------------------------------------------------------
1365 */
1366
1367static void
1368PackStructureProc(clientData, eventPtr)
1369    ClientData clientData;		/* Our information about window
1370					 * referred to by eventPtr. */
1371    XEvent *eventPtr;			/* Describes what just happened. */
1372{
1373    register Packer *packPtr = (Packer *) clientData;
1374
1375    if (eventPtr->type == ConfigureNotify) {
1376	if ((packPtr->slavePtr != NULL)
1377		&& !(packPtr->flags & REQUESTED_REPACK)) {
1378	    packPtr->flags |= REQUESTED_REPACK;
1379	    Tcl_DoWhenIdle(ArrangePacking, (ClientData) packPtr);
1380	}
1381	if (packPtr->doubleBw != 2*Tk_Changes(packPtr->tkwin)->border_width) {
1382	    if ((packPtr->masterPtr != NULL)
1383		    && !(packPtr->masterPtr->flags & REQUESTED_REPACK)) {
1384		packPtr->doubleBw = 2*Tk_Changes(packPtr->tkwin)->border_width;
1385		packPtr->masterPtr->flags |= REQUESTED_REPACK;
1386		Tcl_DoWhenIdle(ArrangePacking, (ClientData) packPtr->masterPtr);
1387	    }
1388	}
1389    } else if (eventPtr->type == DestroyNotify) {
1390	register Packer *slavePtr, *nextPtr;
1391
1392	if (packPtr->masterPtr != NULL) {
1393	    Unlink(packPtr);
1394	}
1395	for (slavePtr = packPtr->slavePtr; slavePtr != NULL;
1396		slavePtr = nextPtr) {
1397	    Tk_ManageGeometry(slavePtr->tkwin, (Tk_GeomMgr *) NULL,
1398		    (ClientData) NULL);
1399	    Tk_UnmapWindow(slavePtr->tkwin);
1400	    slavePtr->masterPtr = NULL;
1401	    nextPtr = slavePtr->nextPtr;
1402	    slavePtr->nextPtr = NULL;
1403	}
1404	if (packPtr->tkwin != NULL) {
1405	    TkDisplay *dispPtr = ((TkWindow *) packPtr->tkwin)->dispPtr;
1406            Tcl_DeleteHashEntry(Tcl_FindHashEntry(&dispPtr->packerHashTable,
1407		    (char *) packPtr->tkwin));
1408	}
1409	if (packPtr->flags & REQUESTED_REPACK) {
1410	    Tcl_CancelIdleCall(ArrangePacking, (ClientData) packPtr);
1411	}
1412	packPtr->tkwin = NULL;
1413	Tcl_EventuallyFree((ClientData) packPtr, DestroyPacker);
1414    } else if (eventPtr->type == MapNotify) {
1415	/*
1416	 * When a master gets mapped, must redo the geometry computation
1417	 * so that all of its slaves get remapped.
1418	 */
1419
1420	if ((packPtr->slavePtr != NULL)
1421		&& !(packPtr->flags & REQUESTED_REPACK)) {
1422	    packPtr->flags |= REQUESTED_REPACK;
1423	    Tcl_DoWhenIdle(ArrangePacking, (ClientData) packPtr);
1424	}
1425    } else if (eventPtr->type == UnmapNotify) {
1426	register Packer *packPtr2;
1427
1428	/*
1429	 * Unmap all of the slaves when the master gets unmapped,
1430	 * so that they don't bother to keep redisplaying
1431	 * themselves.
1432	 */
1433	for (packPtr2 = packPtr->slavePtr; packPtr2 != NULL;
1434	     packPtr2 = packPtr2->nextPtr) {
1435	    Tk_UnmapWindow(packPtr2->tkwin);
1436	}
1437    }
1438}
1439
1440/*
1441 *----------------------------------------------------------------------
1442 *
1443 * ConfigureSlaves --
1444 *
1445 *	This implements the guts of the "pack configure" command.  Given
1446 *	a list of slaves and configuration options, it arranges for the
1447 *	packer to manage the slaves and sets the specified options.
1448 *
1449 * Results:
1450 *	TCL_OK is returned if all went well.  Otherwise, TCL_ERROR is
1451 *	returned and the interp's result is set to contain an error message.
1452 *
1453 * Side effects:
1454 *	Slave windows get taken over by the packer.
1455 *
1456 *----------------------------------------------------------------------
1457 */
1458
1459static int
1460ConfigureSlaves(interp, tkwin, objc, objv)
1461    Tcl_Interp *interp;		/* Interpreter for error reporting. */
1462    Tk_Window tkwin;		/* Any window in application containing
1463				 * slaves.  Used to look up slave names. */
1464    int objc;			/* Number of elements in argv. */
1465    Tcl_Obj *CONST objv[];	/* Argument objects:  contains one or more
1466				 * window names followed by any number
1467				 * of "option value" pairs.  Caller must
1468				 * make sure that there is at least one
1469				 * window name. */
1470{
1471    Packer *masterPtr, *slavePtr, *prevPtr, *otherPtr;
1472    Tk_Window other, slave, parent, ancestor;
1473    int i, j, numWindows, tmp, positionGiven;
1474    char *string;
1475    static CONST char *optionStrings[] = {
1476	"-after", "-anchor", "-before", "-expand", "-fill",
1477	"-in", "-ipadx", "-ipady", "-padx", "-pady", "-side", (char *) NULL };
1478    enum options {
1479	CONF_AFTER, CONF_ANCHOR, CONF_BEFORE, CONF_EXPAND, CONF_FILL,
1480	CONF_IN, CONF_IPADX, CONF_IPADY, CONF_PADX, CONF_PADY, CONF_SIDE };
1481    int index, side;
1482
1483    /*
1484     * Find out how many windows are specified.
1485     */
1486
1487    for (numWindows = 0; numWindows < objc; numWindows++) {
1488	string = Tcl_GetString(objv[numWindows]);
1489	if (string[0] != '.') {
1490	    break;
1491	}
1492    }
1493
1494    /*
1495     * Iterate over all of the slave windows, parsing the configuration
1496     * options for each slave.  It's a bit wasteful to re-parse the
1497     * options for each slave, but things get too messy if we try to
1498     * parse the arguments just once at the beginning.  For example,
1499     * if a slave already is packed we want to just change a few
1500     * existing values without resetting everything.  If there are
1501     * multiple windows, the -after, -before, and -in options only
1502     * get processed for the first window.
1503     */
1504
1505    masterPtr = NULL;
1506    prevPtr = NULL;
1507    positionGiven = 0;
1508    for (j = 0; j < numWindows; j++) {
1509	if (TkGetWindowFromObj(interp, tkwin, objv[j], &slave) != TCL_OK) {
1510	    return TCL_ERROR;
1511	}
1512	if (Tk_TopWinHierarchy(slave)) {
1513	    Tcl_AppendResult(interp, "can't pack \"", Tcl_GetString(objv[j]),
1514		    "\": it's a top-level window", (char *) NULL);
1515	    return TCL_ERROR;
1516	}
1517	slavePtr = GetPacker(slave);
1518	slavePtr->flags &= ~OLD_STYLE;
1519
1520	/*
1521	 * If the slave isn't currently packed, reset all of its
1522	 * configuration information to default values (there could
1523	 * be old values left from a previous packing).
1524	 */
1525
1526	if (slavePtr->masterPtr == NULL) {
1527	    slavePtr->side = TOP;
1528	    slavePtr->anchor = TK_ANCHOR_CENTER;
1529	    slavePtr->padX = slavePtr->padY = 0;
1530	    slavePtr->padLeft = slavePtr->padTop = 0;
1531	    slavePtr->iPadX = slavePtr->iPadY = 0;
1532	    slavePtr->flags &= ~(FILLX|FILLY|EXPAND);
1533	}
1534
1535	for (i = numWindows; i < objc; i+=2) {
1536	    if ((i+2) > objc) {
1537		Tcl_AppendResult(interp, "extra option \"",
1538			Tcl_GetString(objv[i]),
1539			"\" (option with no value?)", (char *) NULL);
1540		return TCL_ERROR;
1541	    }
1542	    if (Tcl_GetIndexFromObj(interp, objv[i], optionStrings, "option",
1543		    0, &index) != TCL_OK) {
1544		return TCL_ERROR;
1545	    }
1546	    if (index == CONF_AFTER) {
1547		if (j == 0) {
1548		    if (TkGetWindowFromObj(interp, tkwin, objv[i+1], &other)
1549			    != TCL_OK) {
1550			return TCL_ERROR;
1551		    }
1552		    prevPtr = GetPacker(other);
1553		    if (prevPtr->masterPtr == NULL) {
1554			notPacked:
1555			Tcl_AppendResult(interp, "window \"",
1556				Tcl_GetString(objv[i+1]),
1557				"\" isn't packed", (char *) NULL);
1558			return TCL_ERROR;
1559		    }
1560		    masterPtr = prevPtr->masterPtr;
1561		    positionGiven = 1;
1562		}
1563	    } else if (index == CONF_ANCHOR) {
1564		if (Tk_GetAnchorFromObj(interp, objv[i+1], &slavePtr->anchor)
1565			!= TCL_OK) {
1566		    return TCL_ERROR;
1567		}
1568	    } else if (index == CONF_BEFORE) {
1569		if (j == 0) {
1570		    if (TkGetWindowFromObj(interp, tkwin, objv[i+1], &other)
1571			    != TCL_OK) {
1572			return TCL_ERROR;
1573		    }
1574		    otherPtr = GetPacker(other);
1575		    if (otherPtr->masterPtr == NULL) {
1576			goto notPacked;
1577		    }
1578		    masterPtr = otherPtr->masterPtr;
1579		    prevPtr = masterPtr->slavePtr;
1580		    if (prevPtr == otherPtr) {
1581			prevPtr = NULL;
1582		    } else {
1583			while (prevPtr->nextPtr != otherPtr) {
1584			    prevPtr = prevPtr->nextPtr;
1585			}
1586		    }
1587		    positionGiven = 1;
1588		}
1589	    } else if (index == CONF_EXPAND) {
1590		if (Tcl_GetBooleanFromObj(interp, objv[i+1], &tmp) != TCL_OK) {
1591		    return TCL_ERROR;
1592		}
1593		slavePtr->flags &= ~EXPAND;
1594		if (tmp) {
1595		    slavePtr->flags |= EXPAND;
1596		}
1597	    } else if (index == CONF_FILL) {
1598		string = Tcl_GetString(objv[i+1]);
1599		if (strcmp(string, "none") == 0) {
1600		    slavePtr->flags &= ~(FILLX|FILLY);
1601		} else if (strcmp(string, "x") == 0) {
1602		    slavePtr->flags = (slavePtr->flags & ~FILLY) | FILLX;
1603		} else if (strcmp(string, "y") == 0) {
1604		    slavePtr->flags = (slavePtr->flags & ~FILLX) | FILLY;
1605		} else if (strcmp(string, "both") == 0) {
1606		    slavePtr->flags |= FILLX|FILLY;
1607		} else {
1608		    Tcl_AppendResult(interp, "bad fill style \"", string,
1609			    "\": must be none, x, y, or both", (char *) NULL);
1610		    return TCL_ERROR;
1611		}
1612	    } else if (index == CONF_IN) {
1613		if (j == 0) {
1614		    if (TkGetWindowFromObj(interp, tkwin, objv[i+1], &other)
1615			    != TCL_OK) {
1616			return TCL_ERROR;
1617		    }
1618		    masterPtr = GetPacker(other);
1619		    prevPtr = masterPtr->slavePtr;
1620		    if (prevPtr != NULL) {
1621			while (prevPtr->nextPtr != NULL) {
1622			    prevPtr = prevPtr->nextPtr;
1623			}
1624		    }
1625		    positionGiven = 1;
1626		}
1627	    } else if (index == CONF_IPADX) {
1628		if ((Tk_GetPixelsFromObj(interp, slave, objv[i+1], &tmp)
1629			!= TCL_OK)
1630			|| (tmp < 0)) {
1631		    Tcl_ResetResult(interp);
1632		    Tcl_AppendResult(interp, "bad ipadx value \"",
1633			    Tcl_GetString(objv[i+1]),
1634			    "\": must be positive screen distance",
1635			    (char *) NULL);
1636		    return TCL_ERROR;
1637		}
1638		slavePtr->iPadX = tmp * 2;
1639	    } else if (index == CONF_IPADY) {
1640		if ((Tk_GetPixelsFromObj(interp, slave, objv[i+1], &tmp)
1641			!= TCL_OK)
1642			|| (tmp < 0)) {
1643		    Tcl_ResetResult(interp);
1644		    Tcl_AppendResult(interp, "bad ipady value \"",
1645			    Tcl_GetString(objv[i+1]),
1646			    "\": must be positive screen distance",
1647			    (char *) NULL);
1648		    return TCL_ERROR;
1649		}
1650		slavePtr->iPadY = tmp * 2;
1651	    } else if (index == CONF_PADX) {
1652		if (TkParsePadAmount(interp, slave, objv[i+1],
1653			&slavePtr->padLeft, &slavePtr->padX) != TCL_OK) {
1654		    return TCL_ERROR;
1655		}
1656	    } else if (index == CONF_PADY) {
1657		if (TkParsePadAmount(interp, slave, objv[i+1],
1658			&slavePtr->padTop, &slavePtr->padY) != TCL_OK) {
1659		    return TCL_ERROR;
1660		}
1661	    } else if (index == CONF_SIDE) {
1662		if (Tcl_GetIndexFromObj(interp, objv[i+1], sideNames, "side",
1663			TCL_EXACT, &side) != TCL_OK) {
1664		    return TCL_ERROR;
1665		}
1666		slavePtr->side = (Side) side;
1667	    }
1668	}
1669
1670	/*
1671	 * If no position in a packing list was specified and the slave
1672	 * is already packed, then leave it in its current location in
1673	 * its current packing list.
1674	 */
1675
1676	if (!positionGiven && (slavePtr->masterPtr != NULL)) {
1677	    masterPtr = slavePtr->masterPtr;
1678	    goto scheduleLayout;
1679	}
1680
1681	/*
1682	 * If the slave is going to be put back after itself then
1683	 * skip the whole operation, since it won't work anyway.
1684	 */
1685
1686	if (prevPtr == slavePtr) {
1687	    masterPtr = slavePtr->masterPtr;
1688	    goto scheduleLayout;
1689	}
1690
1691	/*
1692	 * If none of the "-in", "-before", or "-after" options has
1693	 * been specified, arrange for the slave to go at the end of
1694	 * the order for its parent.
1695	 */
1696
1697	if (!positionGiven) {
1698	    masterPtr = GetPacker(Tk_Parent(slave));
1699	    prevPtr = masterPtr->slavePtr;
1700	    if (prevPtr != NULL) {
1701		while (prevPtr->nextPtr != NULL) {
1702		    prevPtr = prevPtr->nextPtr;
1703		}
1704	    }
1705	}
1706
1707	/*
1708	 * Make sure that the slave's parent is either the master or
1709	 * an ancestor of the master, and that the master and slave
1710	 * aren't the same.
1711	 */
1712
1713	parent = Tk_Parent(slave);
1714	for (ancestor = masterPtr->tkwin; ; ancestor = Tk_Parent(ancestor)) {
1715	    if (ancestor == parent) {
1716		break;
1717	    }
1718	    if (Tk_TopWinHierarchy(ancestor)) {
1719		Tcl_AppendResult(interp, "can't pack ", Tcl_GetString(objv[j]),
1720			" inside ", Tk_PathName(masterPtr->tkwin),
1721			(char *) NULL);
1722		return TCL_ERROR;
1723	    }
1724	}
1725	if (slave == masterPtr->tkwin) {
1726	    Tcl_AppendResult(interp, "can't pack ", Tcl_GetString(objv[j]),
1727		    " inside itself", (char *) NULL);
1728	    return TCL_ERROR;
1729	}
1730
1731	/*
1732	 * Unpack the slave if it's currently packed, then position it
1733	 * after prevPtr.
1734	 */
1735
1736	if (slavePtr->masterPtr != NULL) {
1737	    if ((slavePtr->masterPtr != masterPtr) &&
1738		    (slavePtr->masterPtr->tkwin
1739		    != Tk_Parent(slavePtr->tkwin))) {
1740		Tk_UnmaintainGeometry(slavePtr->tkwin,
1741			slavePtr->masterPtr->tkwin);
1742	    }
1743	    Unlink(slavePtr);
1744	}
1745	slavePtr->masterPtr = masterPtr;
1746	if (prevPtr == NULL) {
1747	    slavePtr->nextPtr = masterPtr->slavePtr;
1748	    masterPtr->slavePtr = slavePtr;
1749	} else {
1750	    slavePtr->nextPtr = prevPtr->nextPtr;
1751	    prevPtr->nextPtr = slavePtr;
1752	}
1753	Tk_ManageGeometry(slave, &packerType, (ClientData) slavePtr);
1754	prevPtr = slavePtr;
1755
1756	/*
1757	 * Arrange for the parent to be re-packed at the first
1758	 * idle moment.
1759	 */
1760
1761	scheduleLayout:
1762	if (masterPtr->abortPtr != NULL) {
1763	    *masterPtr->abortPtr = 1;
1764	}
1765	if (!(masterPtr->flags & REQUESTED_REPACK)) {
1766	    masterPtr->flags |= REQUESTED_REPACK;
1767	    Tcl_DoWhenIdle(ArrangePacking, (ClientData) masterPtr);
1768	}
1769    }
1770    return TCL_OK;
1771}
1772