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