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