1/*
2 * tkPlace.c --
3 *
4 *	This file contains code to implement a simple geometry manager for Tk
5 *	based on absolute placement or "rubber-sheet" placement.
6 *
7 * Copyright (c) 1992-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
18/*
19 * Border modes for relative placement:
20 *
21 * BM_INSIDE:		relative distances computed using area inside all
22 *			borders of master window.
23 * BM_OUTSIDE:		relative distances computed using outside area that
24 *			includes all borders of master.
25 * BM_IGNORE:		border issues are ignored: place relative to master's
26 *			actual window size.
27 */
28
29static char *borderModeStrings[] = {
30    "inside", "outside", "ignore", NULL
31};
32
33typedef enum {BM_INSIDE, BM_OUTSIDE, BM_IGNORE} BorderMode;
34
35/*
36 * For each window whose geometry is managed by the placer there is a
37 * structure of the following type:
38 */
39
40typedef struct Slave {
41    Tk_Window tkwin;		/* Tk's token for window. */
42    Tk_Window inTkwin;		/* Token for the -in window. */
43    struct Master *masterPtr;	/* Pointer to information for window relative
44				 * to which tkwin is placed. This isn't
45				 * necessarily the logical parent of tkwin.
46				 * NULL means the master was deleted or never
47				 * assigned. */
48    struct Slave *nextPtr;	/* Next in list of windows placed relative to
49				 * same master (NULL for end of list). */
50    Tk_OptionTable optionTable;	/* Table that defines configuration options
51				 * available for this command. */
52    /*
53     * Geometry information for window; where there are both relative and
54     * absolute values for the same attribute (e.g. x and relX) only one of
55     * them is actually used, depending on flags.
56     */
57
58    int x, y;			/* X and Y pixel coordinates for tkwin. */
59    Tcl_Obj *xPtr, *yPtr;	/* Tcl_Obj rep's of x, y coords, to keep pixel
60				 * spec. information. */
61    double relX, relY;		/* X and Y coordinates relative to size of
62				 * master. */
63    int width, height;		/* Absolute dimensions for tkwin. */
64    Tcl_Obj *widthPtr;		/* Tcl_Obj rep of width, to keep pixel
65				 * spec. */
66    Tcl_Obj *heightPtr;		/* Tcl_Obj rep of height, to keep pixel
67				 * spec. */
68    double relWidth, relHeight;	/* Dimensions for tkwin relative to size of
69				 * master. */
70    Tcl_Obj *relWidthPtr;
71    Tcl_Obj *relHeightPtr;
72    Tk_Anchor anchor;		/* Which point on tkwin is placed at the given
73				 * position. */
74    BorderMode borderMode;	/* How to treat borders of master window. */
75    int flags;			/* Various flags; see below for bit
76				 * definitions. */
77} Slave;
78
79/*
80 * Type masks for options:
81 */
82
83#define IN_MASK		1
84
85static const Tk_OptionSpec optionSpecs[] = {
86    {TK_OPTION_ANCHOR, "-anchor", NULL, NULL, "nw", -1,
87	 Tk_Offset(Slave, anchor), 0, 0, 0},
88    {TK_OPTION_STRING_TABLE, "-bordermode", NULL, NULL, "inside", -1,
89	 Tk_Offset(Slave, borderMode), 0, (ClientData) borderModeStrings, 0},
90    {TK_OPTION_PIXELS, "-height", NULL, NULL, "", Tk_Offset(Slave, heightPtr),
91	 Tk_Offset(Slave, height), TK_OPTION_NULL_OK, 0, 0},
92    {TK_OPTION_WINDOW, "-in", NULL, NULL, "", -1, Tk_Offset(Slave, inTkwin),
93	 0, 0, IN_MASK},
94    {TK_OPTION_DOUBLE, "-relheight", NULL, NULL, "",
95	 Tk_Offset(Slave, relHeightPtr), Tk_Offset(Slave, relHeight),
96	 TK_OPTION_NULL_OK, 0, 0},
97    {TK_OPTION_DOUBLE, "-relwidth", NULL, NULL, "",
98	 Tk_Offset(Slave, relWidthPtr), Tk_Offset(Slave, relWidth),
99	 TK_OPTION_NULL_OK, 0, 0},
100    {TK_OPTION_DOUBLE, "-relx", NULL, NULL, "0", -1,
101	 Tk_Offset(Slave, relX), 0, 0, 0},
102    {TK_OPTION_DOUBLE, "-rely", NULL, NULL, "0", -1,
103	 Tk_Offset(Slave, relY), 0, 0, 0},
104    {TK_OPTION_PIXELS, "-width", NULL, NULL, "", Tk_Offset(Slave, widthPtr),
105	 Tk_Offset(Slave, width), TK_OPTION_NULL_OK, 0, 0},
106    {TK_OPTION_PIXELS, "-x", NULL, NULL, "0", Tk_Offset(Slave, xPtr),
107	 Tk_Offset(Slave, x), TK_OPTION_NULL_OK, 0, 0},
108    {TK_OPTION_PIXELS, "-y", NULL, NULL, "0", Tk_Offset(Slave, yPtr),
109	 Tk_Offset(Slave, y), TK_OPTION_NULL_OK, 0, 0},
110    {TK_OPTION_END, NULL, NULL, NULL, NULL, 0, -1, 0, 0, 0}
111};
112
113/*
114 * Flag definitions for Slave structures:
115 *
116 * CHILD_WIDTH -		1 means -width was specified;
117 * CHILD_REL_WIDTH -		1 means -relwidth was specified.
118 * CHILD_HEIGHT -		1 means -height was specified;
119 * CHILD_REL_HEIGHT -		1 means -relheight was specified.
120 */
121
122#define CHILD_WIDTH		1
123#define CHILD_REL_WIDTH		2
124#define CHILD_HEIGHT		4
125#define CHILD_REL_HEIGHT	8
126
127/*
128 * For each master window that has a slave managed by the placer there is a
129 * structure of the following form:
130 */
131
132typedef struct Master {
133    Tk_Window tkwin;		/* Tk's token for master window. */
134    struct Slave *slavePtr;	/* First in linked list of slaves placed
135				 * relative to this master. */
136    int *abortPtr;		/* If non-NULL, it means that there is a nested
137				 * call to RecomputePlacement already working on
138				 * this window.  *abortPtr may be set to 1 to
139				 * abort that nested call.  This happens, for
140				 * example, if tkwin or any of its slaves
141				 * is deleted. */
142    int flags;			/* See below for bit definitions. */
143} Master;
144
145/*
146 * Flag definitions for masters:
147 *
148 * PARENT_RECONFIG_PENDING -	1 means that a call to RecomputePlacement is
149 *				already pending via a Do_When_Idle handler.
150 */
151
152#define PARENT_RECONFIG_PENDING	1
153
154/*
155 * The following structure is the official type record for the placer:
156 */
157
158static void		PlaceRequestProc(ClientData clientData,
159			    Tk_Window tkwin);
160static void		PlaceLostSlaveProc(ClientData clientData,
161			    Tk_Window tkwin);
162
163static const Tk_GeomMgr placerType = {
164    "place",			/* name */
165    PlaceRequestProc,		/* requestProc */
166    PlaceLostSlaveProc,		/* lostSlaveProc */
167};
168
169/*
170 * Forward declarations for functions defined later in this file:
171 */
172
173static void		SlaveStructureProc(ClientData clientData,
174			    XEvent *eventPtr);
175static int		ConfigureSlave(Tcl_Interp *interp, Tk_Window tkwin,
176			    Tk_OptionTable table, int objc,
177			    Tcl_Obj *CONST objv[]);
178static int		PlaceInfoCommand(Tcl_Interp *interp, Tk_Window tkwin);
179static Slave *		CreateSlave(Tk_Window tkwin, Tk_OptionTable table);
180static void		FreeSlave(Slave *slavePtr);
181static Slave *		FindSlave(Tk_Window tkwin);
182static Master *		CreateMaster(Tk_Window tkwin);
183static Master *		FindMaster(Tk_Window tkwin);
184static void		MasterStructureProc(ClientData clientData,
185			    XEvent *eventPtr);
186static void		RecomputePlacement(ClientData clientData);
187static void		UnlinkSlave(Slave *slavePtr);
188
189/*
190 *--------------------------------------------------------------
191 *
192 * Tk_PlaceObjCmd --
193 *
194 *	This function is invoked to process the "place" Tcl commands. See the
195 *	user documentation for details on what it does.
196 *
197 * Results:
198 *	A standard Tcl result.
199 *
200 * Side effects:
201 *	See the user documentation.
202 *
203 *--------------------------------------------------------------
204 */
205
206int
207Tk_PlaceObjCmd(
208    ClientData clientData,	/* NULL. */
209    Tcl_Interp *interp,		/* Current interpreter. */
210    int objc,			/* Number of arguments. */
211    Tcl_Obj *CONST objv[])	/* Argument objects. */
212{
213    Tk_Window tkwin;
214    Slave *slavePtr;
215    char *string;
216    TkDisplay *dispPtr;
217    Tk_OptionTable optionTable;
218    static CONST char *optionStrings[] = {
219	"configure", "forget", "info", "slaves", NULL
220    };
221    enum options { PLACE_CONFIGURE, PLACE_FORGET, PLACE_INFO, PLACE_SLAVES };
222    int index;
223
224    if (objc < 3) {
225	Tcl_WrongNumArgs(interp, 1, objv, "option|pathName args");
226	return TCL_ERROR;
227    }
228
229    /*
230     * Create the option table for this widget class. If it has already been
231     * created, the cached pointer will be returned.
232     */
233
234     optionTable = Tk_CreateOptionTable(interp, optionSpecs);
235
236    /*
237     * Handle special shortcut where window name is first argument.
238     */
239
240    string = Tcl_GetString(objv[1]);
241    if (string[0] == '.') {
242	tkwin = Tk_NameToWindow(interp, string,	Tk_MainWindow(interp));
243	if (tkwin == NULL) {
244	    return TCL_ERROR;
245	}
246
247	/*
248	 * Initialize, if that hasn't been done yet.
249	 */
250
251	dispPtr = ((TkWindow *) tkwin)->dispPtr;
252	if (!dispPtr->placeInit) {
253	    Tcl_InitHashTable(&dispPtr->masterTable, TCL_ONE_WORD_KEYS);
254	    Tcl_InitHashTable(&dispPtr->slaveTable, TCL_ONE_WORD_KEYS);
255	    dispPtr->placeInit = 1;
256	}
257
258	return ConfigureSlave(interp, tkwin, optionTable, objc-2, objv+2);
259    }
260
261    /*
262     * Handle more general case of option followed by window name followed by
263     * possible additional arguments.
264     */
265
266    tkwin = Tk_NameToWindow(interp, Tcl_GetString(objv[2]),
267	    Tk_MainWindow(interp));
268    if (tkwin == NULL) {
269	return TCL_ERROR;
270    }
271
272    /*
273     * Initialize, if that hasn't been done yet.
274     */
275
276    dispPtr = ((TkWindow *) tkwin)->dispPtr;
277    if (!dispPtr->placeInit) {
278	Tcl_InitHashTable(&dispPtr->masterTable, TCL_ONE_WORD_KEYS);
279	Tcl_InitHashTable(&dispPtr->slaveTable, TCL_ONE_WORD_KEYS);
280	dispPtr->placeInit = 1;
281    }
282
283    if (Tcl_GetIndexFromObj(interp, objv[1], optionStrings, "option", 0,
284	    &index) != TCL_OK) {
285	return TCL_ERROR;
286    }
287
288    switch ((enum options) index) {
289    case PLACE_CONFIGURE:
290	if (objc == 3 || objc == 4) {
291	    Tcl_Obj *objPtr;
292
293	    slavePtr = FindSlave(tkwin);
294	    if (slavePtr == NULL) {
295		return TCL_OK;
296	    }
297	    objPtr = Tk_GetOptionInfo(interp, (char *) slavePtr, optionTable,
298		    (objc == 4) ? objv[3] : NULL, tkwin);
299	    if (objPtr == NULL) {
300		return TCL_ERROR;
301	    }
302	    Tcl_SetObjResult(interp, objPtr);
303	    return TCL_OK;
304	}
305	return ConfigureSlave(interp, tkwin, optionTable, objc-3, objv+3);
306
307    case PLACE_FORGET:
308	if (objc != 3) {
309	    Tcl_WrongNumArgs(interp, 2, objv, "pathName");
310	    return TCL_ERROR;
311	}
312	slavePtr = FindSlave(tkwin);
313	if (slavePtr == NULL) {
314	    return TCL_OK;
315	}
316	if ((slavePtr->masterPtr != NULL) &&
317		(slavePtr->masterPtr->tkwin != Tk_Parent(slavePtr->tkwin))) {
318	    Tk_UnmaintainGeometry(slavePtr->tkwin, slavePtr->masterPtr->tkwin);
319	}
320	UnlinkSlave(slavePtr);
321	Tcl_DeleteHashEntry(Tcl_FindHashEntry(&dispPtr->slaveTable,
322		(char *) tkwin));
323	Tk_DeleteEventHandler(tkwin, StructureNotifyMask, SlaveStructureProc,
324		(ClientData) slavePtr);
325	Tk_ManageGeometry(tkwin, NULL, (ClientData) NULL);
326	Tk_UnmapWindow(tkwin);
327	FreeSlave(slavePtr);
328	break;
329
330    case PLACE_INFO:
331	if (objc != 3) {
332	    Tcl_WrongNumArgs(interp, 2, objv, "pathName");
333	    return TCL_ERROR;
334	}
335	return PlaceInfoCommand(interp, tkwin);
336
337    case PLACE_SLAVES: {
338	Master *masterPtr;
339
340	if (objc != 3) {
341	    Tcl_WrongNumArgs(interp, 2, objv, "pathName");
342	    return TCL_ERROR;
343	}
344	masterPtr = FindMaster(tkwin);
345	if (masterPtr != NULL) {
346	    Tcl_Obj *listPtr = Tcl_NewObj();
347
348	    for (slavePtr = masterPtr->slavePtr; slavePtr != NULL;
349		    slavePtr = slavePtr->nextPtr) {
350		Tcl_ListObjAppendElement(interp, listPtr,
351			Tcl_NewStringObj(Tk_PathName(slavePtr->tkwin),-1));
352	    }
353	    Tcl_SetObjResult(interp, listPtr);
354	}
355	break;
356    }
357    }
358
359    return TCL_OK;
360}
361
362/*
363 *----------------------------------------------------------------------
364 *
365 * CreateSlave --
366 *
367 *	Given a Tk_Window token, find the Slave structure corresponding to
368 *	that token, creating a new one if necessary.
369 *
370 * Results:
371 *	Pointer to the Slave structure.
372 *
373 * Side effects:
374 *	A new Slave structure may be created.
375 *
376 *----------------------------------------------------------------------
377 */
378
379static Slave *
380CreateSlave(
381    Tk_Window tkwin,		/* Token for desired slave. */
382    Tk_OptionTable table)
383{
384    Tcl_HashEntry *hPtr;
385    register Slave *slavePtr;
386    int isNew;
387    TkDisplay *dispPtr = ((TkWindow *) tkwin)->dispPtr;
388
389    hPtr = Tcl_CreateHashEntry(&dispPtr->slaveTable, (char *) tkwin, &isNew);
390    if (!isNew) {
391	return (Slave *) Tcl_GetHashValue(hPtr);
392    }
393
394    /*
395     * No preexisting slave structure for that window, so make a new one and
396     * populate it with some default values.
397     */
398
399    slavePtr = (Slave *) ckalloc(sizeof(Slave));
400    memset(slavePtr, 0, sizeof(Slave));
401    slavePtr->tkwin = tkwin;
402    slavePtr->inTkwin = None;
403    slavePtr->anchor = TK_ANCHOR_NW;
404    slavePtr->borderMode = BM_INSIDE;
405    slavePtr->optionTable = table;
406    Tcl_SetHashValue(hPtr, slavePtr);
407    Tk_CreateEventHandler(tkwin, StructureNotifyMask, SlaveStructureProc,
408	    (ClientData) slavePtr);
409    return slavePtr;
410}
411
412/*
413 *----------------------------------------------------------------------
414 *
415 * FreeSlave --
416 *
417 *	Frees the resources held by a Slave structure.
418 *
419 * Results:
420 *	None
421 *
422 * Side effects:
423 *	Memory are freed.
424 *
425 *----------------------------------------------------------------------
426 */
427
428static void
429FreeSlave(
430    Slave *slavePtr)
431{
432    Tk_FreeConfigOptions((char *) slavePtr, slavePtr->optionTable,
433	    slavePtr->tkwin);
434    ckfree((char *) slavePtr);
435}
436
437/*
438 *----------------------------------------------------------------------
439 *
440 * FindSlave --
441 *
442 *	Given a Tk_Window token, find the Slave structure corresponding to
443 *	that token. This is purely a lookup function; it will not create a
444 *	record if one does not yet exist.
445 *
446 * Results:
447 *	Pointer to Slave structure; NULL if none exists.
448 *
449 * Side effects:
450 *	None.
451 *
452 *----------------------------------------------------------------------
453 */
454
455static Slave *
456FindSlave(
457    Tk_Window tkwin)		/* Token for desired slave. */
458{
459    Tcl_HashEntry *hPtr;
460    register Slave *slavePtr;
461    TkDisplay *dispPtr = ((TkWindow *) tkwin)->dispPtr;
462
463    hPtr = Tcl_FindHashEntry(&dispPtr->slaveTable, (char *) tkwin);
464    if (hPtr == NULL) {
465	return NULL;
466    }
467    slavePtr = (Slave *) Tcl_GetHashValue(hPtr);
468    return slavePtr;
469}
470
471/*
472 *----------------------------------------------------------------------
473 *
474 * UnlinkSlave --
475 *
476 *	This function removes a slave window from the chain of slaves in its
477 *	master.
478 *
479 * Results:
480 *	None.
481 *
482 * Side effects:
483 *	The slave list of slavePtr's master changes.
484 *
485 *----------------------------------------------------------------------
486 */
487
488static void
489UnlinkSlave(
490    Slave *slavePtr)		/* Slave structure to be unlinked. */
491{
492    register Master *masterPtr;
493    register Slave *prevPtr;
494
495    masterPtr = slavePtr->masterPtr;
496    if (masterPtr == NULL) {
497	return;
498    }
499    if (masterPtr->slavePtr == slavePtr) {
500	masterPtr->slavePtr = slavePtr->nextPtr;
501    } else {
502	for (prevPtr = masterPtr->slavePtr; ; prevPtr = prevPtr->nextPtr) {
503	    if (prevPtr == NULL) {
504		Tcl_Panic("UnlinkSlave couldn't find slave to unlink");
505	    }
506	    if (prevPtr->nextPtr == slavePtr) {
507		prevPtr->nextPtr = slavePtr->nextPtr;
508		break;
509	    }
510	}
511    }
512
513    if (masterPtr->abortPtr != NULL) {
514	*masterPtr->abortPtr = 1;
515    }
516    slavePtr->masterPtr = NULL;
517}
518
519/*
520 *----------------------------------------------------------------------
521 *
522 * CreateMaster --
523 *
524 *	Given a Tk_Window token, find the Master structure corresponding to
525 *	that token, creating a new one if necessary.
526 *
527 * Results:
528 *	Pointer to the Master structure.
529 *
530 * Side effects:
531 *	A new Master structure may be created.
532 *
533 *----------------------------------------------------------------------
534 */
535
536static Master *
537CreateMaster(
538    Tk_Window tkwin)		/* Token for desired master. */
539{
540    Tcl_HashEntry *hPtr;
541    register Master *masterPtr;
542    int isNew;
543    TkDisplay *dispPtr = ((TkWindow *) tkwin)->dispPtr;
544
545    hPtr = Tcl_CreateHashEntry(&dispPtr->masterTable, (char *) tkwin, &isNew);
546    if (isNew) {
547	masterPtr = (Master *) ckalloc(sizeof(Master));
548	masterPtr->tkwin = tkwin;
549	masterPtr->slavePtr = NULL;
550	masterPtr->abortPtr = NULL;
551	masterPtr->flags = 0;
552	Tcl_SetHashValue(hPtr, masterPtr);
553	Tk_CreateEventHandler(masterPtr->tkwin, StructureNotifyMask,
554		MasterStructureProc, (ClientData) masterPtr);
555    } else {
556	masterPtr = (Master *) Tcl_GetHashValue(hPtr);
557    }
558    return masterPtr;
559}
560
561/*
562 *----------------------------------------------------------------------
563 *
564 * FindMaster --
565 *
566 *	Given a Tk_Window token, find the Master structure corresponding to
567 *	that token. This is simply a lookup function; a new record will not be
568 *	created if one does not already exist.
569 *
570 * Results:
571 *	Pointer to the Master structure; NULL if one does not exist for the
572 *	given Tk_Window token.
573 *
574 * Side effects:
575 *	None.
576 *
577 *----------------------------------------------------------------------
578 */
579
580static Master *
581FindMaster(
582    Tk_Window tkwin)		/* Token for desired master. */
583{
584    Tcl_HashEntry *hPtr;
585    register Master *masterPtr;
586    TkDisplay *dispPtr = ((TkWindow *) tkwin)->dispPtr;
587
588    hPtr = Tcl_FindHashEntry(&dispPtr->masterTable, (char *) tkwin);
589    if (hPtr == NULL) {
590	return NULL;
591    }
592    masterPtr = (Master *) Tcl_GetHashValue(hPtr);
593    return masterPtr;
594}
595
596/*
597 *----------------------------------------------------------------------
598 *
599 * ConfigureSlave --
600 *
601 *	This function is called to process an argv/argc list to reconfigure
602 *	the placement of a window.
603 *
604 * Results:
605 *	A standard Tcl result. If an error occurs then a message is left in
606 *	the interp's result.
607 *
608 * Side effects:
609 *	Information in slavePtr may change, and slavePtr's master is scheduled
610 *	for reconfiguration.
611 *
612 *----------------------------------------------------------------------
613 */
614
615static int
616ConfigureSlave(
617    Tcl_Interp *interp,		/* Used for error reporting. */
618    Tk_Window tkwin,		/* Token for the window to manipulate. */
619    Tk_OptionTable table,	/* Token for option table. */
620    int objc,			/* Number of config arguments. */
621    Tcl_Obj *CONST objv[])	/* Object values for arguments. */
622{
623    register Master *masterPtr;
624    Tk_SavedOptions savedOptions;
625    int mask;
626    Slave *slavePtr;
627    Tk_Window masterWin = (Tk_Window) NULL;
628
629    if (Tk_TopWinHierarchy(tkwin)) {
630	Tcl_AppendResult(interp, "can't use placer on top-level window \"",
631		Tk_PathName(tkwin), "\"; use wm command instead", NULL);
632	return TCL_ERROR;
633    }
634
635    slavePtr = CreateSlave(tkwin, table);
636
637    if (Tk_SetOptions(interp, (char *) slavePtr, table, objc, objv,
638	    slavePtr->tkwin, &savedOptions, &mask) != TCL_OK) {
639	goto error;
640    }
641
642    /*
643     * Set slave flags. First clear the field, then add bits as needed.
644     */
645
646    slavePtr->flags = 0;
647    if (slavePtr->heightPtr) {
648	slavePtr->flags |= CHILD_HEIGHT;
649    }
650
651    if (slavePtr->relHeightPtr) {
652	slavePtr->flags |= CHILD_REL_HEIGHT;
653    }
654
655    if (slavePtr->relWidthPtr) {
656	slavePtr->flags |= CHILD_REL_WIDTH;
657    }
658
659    if (slavePtr->widthPtr) {
660	slavePtr->flags |= CHILD_WIDTH;
661    }
662
663    if (((mask & IN_MASK) == 0) && (slavePtr->masterPtr != NULL)) {
664	/*
665	 * If no -in option was passed and the slave is already placed then
666	 * just recompute the placement.
667	 */
668
669	masterPtr = slavePtr->masterPtr;
670	goto scheduleLayout;
671    } else if (mask & IN_MASK) {
672	/* -in changed */
673	Tk_Window tkwin;
674	Tk_Window ancestor;
675
676	tkwin = slavePtr->inTkwin;
677
678	/*
679	 * Make sure that the new master is either the logical parent of the
680	 * slave or a descendant of that window, and that the master and slave
681	 * aren't the same.
682	 */
683
684	for (ancestor = tkwin; ; ancestor = Tk_Parent(ancestor)) {
685	    if (ancestor == Tk_Parent(slavePtr->tkwin)) {
686		break;
687	    }
688	    if (Tk_TopWinHierarchy(ancestor)) {
689		Tcl_AppendResult(interp, "can't place ",
690			Tk_PathName(slavePtr->tkwin), " relative to ",
691			Tk_PathName(tkwin), NULL);
692		goto error;
693	    }
694	}
695	if (slavePtr->tkwin == tkwin) {
696	    Tcl_AppendResult(interp, "can't place ",
697		    Tk_PathName(slavePtr->tkwin), " relative to itself",
698		    NULL);
699	    goto error;
700	}
701	if ((slavePtr->masterPtr != NULL)
702		&& (slavePtr->masterPtr->tkwin == tkwin)) {
703	    /*
704	     * Re-using same old master. Nothing to do.
705	     */
706
707	    masterPtr = slavePtr->masterPtr;
708	    goto scheduleLayout;
709	}
710	if ((slavePtr->masterPtr != NULL) &&
711		(slavePtr->masterPtr->tkwin != Tk_Parent(slavePtr->tkwin))) {
712	    Tk_UnmaintainGeometry(slavePtr->tkwin, slavePtr->masterPtr->tkwin);
713	}
714	UnlinkSlave(slavePtr);
715	masterWin = tkwin;
716    }
717
718    /*
719     * If there's no master specified for this slave, use its Tk_Parent.
720     */
721
722    if (masterWin == NULL) {
723	masterWin = Tk_Parent(slavePtr->tkwin);
724	slavePtr->inTkwin = masterWin;
725    }
726
727    /*
728     * Manage the slave window in this master.
729     */
730
731    masterPtr = CreateMaster(masterWin);
732    slavePtr->masterPtr = masterPtr;
733    slavePtr->nextPtr = masterPtr->slavePtr;
734    masterPtr->slavePtr = slavePtr;
735    Tk_ManageGeometry(slavePtr->tkwin, &placerType, (ClientData) slavePtr);
736
737    /*
738     * Arrange for the master to be re-arranged at the first idle moment.
739     */
740
741  scheduleLayout:
742    Tk_FreeSavedOptions(&savedOptions);
743
744    if (!(masterPtr->flags & PARENT_RECONFIG_PENDING)) {
745	masterPtr->flags |= PARENT_RECONFIG_PENDING;
746	Tcl_DoWhenIdle(RecomputePlacement, (ClientData) masterPtr);
747    }
748    return TCL_OK;
749
750    /*
751     * Error while processing some option, cleanup and return.
752     */
753
754  error:
755    Tk_RestoreSavedOptions(&savedOptions);
756    return TCL_ERROR;
757}
758
759/*
760 *----------------------------------------------------------------------
761 *
762 * PlaceInfoCommand --
763 *
764 *	Implementation of the [place info] subcommand. See the user
765 *	documentation for information on what it does.
766 *
767 * Results:
768 *	Standard Tcl result.
769 *
770 * Side effects:
771 *	If the given tkwin is managed by the placer, this function will put
772 *	information about that placement in the interp's result.
773 *
774 *----------------------------------------------------------------------
775 */
776
777static int
778PlaceInfoCommand(
779    Tcl_Interp *interp,		/* Interp into which to place result. */
780    Tk_Window tkwin)		/* Token for the window to get info on. */
781{
782    char buffer[32 + TCL_INTEGER_SPACE];
783    Slave *slavePtr;
784
785    slavePtr = FindSlave(tkwin);
786    if (slavePtr == NULL) {
787	return TCL_OK;
788    }
789    if (slavePtr->masterPtr != NULL) {
790	Tcl_AppendElement(interp, "-in");
791	Tcl_AppendElement(interp, Tk_PathName(slavePtr->masterPtr->tkwin));
792    }
793    sprintf(buffer, " -x %d", slavePtr->x);
794    Tcl_AppendResult(interp, buffer, NULL);
795    sprintf(buffer, " -relx %.4g", slavePtr->relX);
796    Tcl_AppendResult(interp, buffer, NULL);
797    sprintf(buffer, " -y %d", slavePtr->y);
798    Tcl_AppendResult(interp, buffer, NULL);
799    sprintf(buffer, " -rely %.4g", slavePtr->relY);
800    Tcl_AppendResult(interp, buffer, NULL);
801    if (slavePtr->flags & CHILD_WIDTH) {
802	sprintf(buffer, " -width %d", slavePtr->width);
803	Tcl_AppendResult(interp, buffer, NULL);
804    } else {
805	Tcl_AppendResult(interp, " -width {}", NULL);
806    }
807    if (slavePtr->flags & CHILD_REL_WIDTH) {
808	sprintf(buffer, " -relwidth %.4g", slavePtr->relWidth);
809	Tcl_AppendResult(interp, buffer, NULL);
810    } else {
811	Tcl_AppendResult(interp, " -relwidth {}", NULL);
812    }
813    if (slavePtr->flags & CHILD_HEIGHT) {
814	sprintf(buffer, " -height %d", slavePtr->height);
815	Tcl_AppendResult(interp, buffer, NULL);
816    } else {
817	Tcl_AppendResult(interp, " -height {}", NULL);
818    }
819    if (slavePtr->flags & CHILD_REL_HEIGHT) {
820	sprintf(buffer, " -relheight %.4g", slavePtr->relHeight);
821	Tcl_AppendResult(interp, buffer, NULL);
822    } else {
823	Tcl_AppendResult(interp, " -relheight {}", NULL);
824    }
825
826    Tcl_AppendElement(interp, "-anchor");
827    Tcl_AppendElement(interp, Tk_NameOfAnchor(slavePtr->anchor));
828    Tcl_AppendElement(interp, "-bordermode");
829    Tcl_AppendElement(interp, borderModeStrings[slavePtr->borderMode]);
830    return TCL_OK;
831}
832
833/*
834 *----------------------------------------------------------------------
835 *
836 * RecomputePlacement --
837 *
838 *	This function is called as a when-idle handler. It recomputes the
839 *	geometries of all the slaves of a given master.
840 *
841 * Results:
842 *	None.
843 *
844 * Side effects:
845 *	Windows may change size or shape.
846 *
847 *----------------------------------------------------------------------
848 */
849
850static void
851RecomputePlacement(
852    ClientData clientData)	/* Pointer to Master record. */
853{
854    register Master *masterPtr = (Master *) clientData;
855    register Slave *slavePtr;
856    int x, y, width, height, tmp;
857    int masterWidth, masterHeight, masterX, masterY;
858    double x1, y1, x2, y2;
859
860    int abort;			/* May get set to non-zero to abort this
861				 * placement operation. */
862
863    masterPtr->flags &= ~PARENT_RECONFIG_PENDING;
864
865    /*
866     * Abort any nested call to RecomputePlacement for this window, since
867     * we'll do everything necessary here, and set up so this call
868     * can be aborted if necessary.
869     */
870
871    if (masterPtr->abortPtr != NULL) {
872	*masterPtr->abortPtr = 1;
873    }
874    masterPtr->abortPtr = &abort;
875    abort = 0;
876    Tcl_Preserve((ClientData) masterPtr);
877
878    /*
879     * Iterate over all the slaves for the master. Each slave's geometry can
880     * be computed independently of the other slaves. Changes to the window's
881     * structure could cause almost anything to happen, including deleting the
882     * parent or child.  If this happens, we'll be told to abort.
883     */
884
885    for (slavePtr = masterPtr->slavePtr; slavePtr != NULL && !abort;
886	    slavePtr = slavePtr->nextPtr) {
887	/*
888	 * Step 1: compute size and borderwidth of master, taking into account
889	 * desired border mode.
890	 */
891
892	masterX = masterY = 0;
893	masterWidth = Tk_Width(masterPtr->tkwin);
894	masterHeight = Tk_Height(masterPtr->tkwin);
895	if (slavePtr->borderMode == BM_INSIDE) {
896	    masterX = Tk_InternalBorderLeft(masterPtr->tkwin);
897	    masterY = Tk_InternalBorderTop(masterPtr->tkwin);
898	    masterWidth -= masterX + Tk_InternalBorderRight(masterPtr->tkwin);
899	    masterHeight -= masterY +
900		    Tk_InternalBorderBottom(masterPtr->tkwin);
901	} else if (slavePtr->borderMode == BM_OUTSIDE) {
902	    masterX = masterY = -Tk_Changes(masterPtr->tkwin)->border_width;
903	    masterWidth -= 2 * masterX;
904	    masterHeight -= 2 * masterY;
905	}
906
907	/*
908	 * Step 2: compute size of slave (outside dimensions including border)
909	 * and location of anchor point within master.
910	 */
911
912	x1 = slavePtr->x + masterX + (slavePtr->relX*masterWidth);
913	x = (int) (x1 + ((x1 > 0) ? 0.5 : -0.5));
914	y1 = slavePtr->y + masterY + (slavePtr->relY*masterHeight);
915	y = (int) (y1 + ((y1 > 0) ? 0.5 : -0.5));
916	if (slavePtr->flags & (CHILD_WIDTH|CHILD_REL_WIDTH)) {
917	    width = 0;
918	    if (slavePtr->flags & CHILD_WIDTH) {
919		width += slavePtr->width;
920	    }
921	    if (slavePtr->flags & CHILD_REL_WIDTH) {
922		/*
923		 * The code below is a bit tricky. In order to round correctly
924		 * when both relX and relWidth are specified, compute the
925		 * location of the right edge and round that, then compute
926		 * width. If we compute the width and round it, rounding
927		 * errors in relX and relWidth accumulate.
928		 */
929
930		x2 = x1 + (slavePtr->relWidth*masterWidth);
931		tmp = (int) (x2 + ((x2 > 0) ? 0.5 : -0.5));
932		width += tmp - x;
933	    }
934	} else {
935	    width = Tk_ReqWidth(slavePtr->tkwin)
936		    + 2*Tk_Changes(slavePtr->tkwin)->border_width;
937	}
938	if (slavePtr->flags & (CHILD_HEIGHT|CHILD_REL_HEIGHT)) {
939	    height = 0;
940	    if (slavePtr->flags & CHILD_HEIGHT) {
941		height += slavePtr->height;
942	    }
943	    if (slavePtr->flags & CHILD_REL_HEIGHT) {
944		/*
945		 * See note above for rounding errors in width computation.
946		 */
947
948		y2 = y1 + (slavePtr->relHeight*masterHeight);
949		tmp = (int) (y2 + ((y2 > 0) ? 0.5 : -0.5));
950		height += tmp - y;
951	    }
952	} else {
953	    height = Tk_ReqHeight(slavePtr->tkwin)
954		    + 2*Tk_Changes(slavePtr->tkwin)->border_width;
955	}
956
957	/*
958	 * Step 3: adjust the x and y positions so that the desired anchor
959	 * point on the slave appears at that position. Also adjust for the
960	 * border mode and master's border.
961	 */
962
963	switch (slavePtr->anchor) {
964	case TK_ANCHOR_N:
965	    x -= width/2;
966	    break;
967	case TK_ANCHOR_NE:
968	    x -= width;
969	    break;
970	case TK_ANCHOR_E:
971	    x -= width;
972	    y -= height/2;
973	    break;
974	case TK_ANCHOR_SE:
975	    x -= width;
976	    y -= height;
977	    break;
978	case TK_ANCHOR_S:
979	    x -= width/2;
980	    y -= height;
981	    break;
982	case TK_ANCHOR_SW:
983	    y -= height;
984	    break;
985	case TK_ANCHOR_W:
986	    y -= height/2;
987	    break;
988	case TK_ANCHOR_NW:
989	    break;
990	case TK_ANCHOR_CENTER:
991	    x -= width/2;
992	    y -= height/2;
993	    break;
994	}
995
996	/*
997	 * Step 4: adjust width and height again to reflect inside dimensions
998	 * of window rather than outside. Also make sure that the width and
999	 * height aren't zero.
1000	 */
1001
1002	width -= 2*Tk_Changes(slavePtr->tkwin)->border_width;
1003	height -= 2*Tk_Changes(slavePtr->tkwin)->border_width;
1004	if (width <= 0) {
1005	    width = 1;
1006	}
1007	if (height <= 0) {
1008	    height = 1;
1009	}
1010
1011	/*
1012	 * Step 5: reconfigure the window and map it if needed. If the slave
1013	 * is a child of the master, we do this ourselves. If the slave isn't
1014	 * a child of the master, let Tk_MaintainGeometry do the work (it will
1015	 * re-adjust things as relevant windows map, unmap, and move).
1016	 */
1017
1018	if (masterPtr->tkwin == Tk_Parent(slavePtr->tkwin)) {
1019	    if ((x != Tk_X(slavePtr->tkwin))
1020		    || (y != Tk_Y(slavePtr->tkwin))
1021		    || (width != Tk_Width(slavePtr->tkwin))
1022		    || (height != Tk_Height(slavePtr->tkwin))) {
1023		Tk_MoveResizeWindow(slavePtr->tkwin, x, y, width, height);
1024	    }
1025            if (abort) {
1026                break;
1027            }
1028
1029	    /*
1030	     * Don't map the slave unless the master is mapped: the slave will
1031	     * get mapped later, when the master is mapped.
1032	     */
1033
1034	    if (Tk_IsMapped(masterPtr->tkwin)) {
1035		Tk_MapWindow(slavePtr->tkwin);
1036	    }
1037	} else {
1038	    if ((width <= 0) || (height <= 0)) {
1039		Tk_UnmaintainGeometry(slavePtr->tkwin, masterPtr->tkwin);
1040		Tk_UnmapWindow(slavePtr->tkwin);
1041	    } else {
1042		Tk_MaintainGeometry(slavePtr->tkwin, masterPtr->tkwin,
1043			x, y, width, height);
1044	    }
1045	}
1046    }
1047
1048    masterPtr->abortPtr = NULL;
1049    Tcl_Release((ClientData) masterPtr);
1050}
1051
1052/*
1053 *----------------------------------------------------------------------
1054 *
1055 * MasterStructureProc --
1056 *
1057 *	This function is invoked by the Tk event handler when StructureNotify
1058 *	events occur for a master window.
1059 *
1060 * Results:
1061 *	None.
1062 *
1063 * Side effects:
1064 *	Structures get cleaned up if the window was deleted. If the window was
1065 *	resized then slave geometries get recomputed.
1066 *
1067 *----------------------------------------------------------------------
1068 */
1069
1070static void
1071MasterStructureProc(
1072    ClientData clientData,	/* Pointer to Master structure for window
1073				 * referred to by eventPtr. */
1074    XEvent *eventPtr)		/* Describes what just happened. */
1075{
1076    register Master *masterPtr = (Master *) clientData;
1077    register Slave *slavePtr, *nextPtr;
1078    TkDisplay *dispPtr = ((TkWindow *) masterPtr->tkwin)->dispPtr;
1079
1080    if (eventPtr->type == ConfigureNotify) {
1081	if ((masterPtr->slavePtr != NULL)
1082		&& !(masterPtr->flags & PARENT_RECONFIG_PENDING)) {
1083	    masterPtr->flags |= PARENT_RECONFIG_PENDING;
1084	    Tcl_DoWhenIdle(RecomputePlacement, (ClientData) masterPtr);
1085	}
1086    } else if (eventPtr->type == DestroyNotify) {
1087	for (slavePtr = masterPtr->slavePtr; slavePtr != NULL;
1088		slavePtr = nextPtr) {
1089	    slavePtr->masterPtr = NULL;
1090	    nextPtr = slavePtr->nextPtr;
1091	    slavePtr->nextPtr = NULL;
1092	}
1093	Tcl_DeleteHashEntry(Tcl_FindHashEntry(&dispPtr->masterTable,
1094		(char *) masterPtr->tkwin));
1095	if (masterPtr->flags & PARENT_RECONFIG_PENDING) {
1096	    Tcl_CancelIdleCall(RecomputePlacement, (ClientData) masterPtr);
1097	}
1098	masterPtr->tkwin = NULL;
1099	if (masterPtr->abortPtr != NULL) {
1100	    *masterPtr->abortPtr = 1;
1101	}
1102	Tcl_EventuallyFree((ClientData) masterPtr, TCL_DYNAMIC);
1103    } else if (eventPtr->type == MapNotify) {
1104	/*
1105	 * When a master gets mapped, must redo the geometry computation so
1106	 * that all of its slaves get remapped.
1107	 */
1108
1109	if ((masterPtr->slavePtr != NULL)
1110		&& !(masterPtr->flags & PARENT_RECONFIG_PENDING)) {
1111	    masterPtr->flags |= PARENT_RECONFIG_PENDING;
1112	    Tcl_DoWhenIdle(RecomputePlacement, (ClientData) masterPtr);
1113	}
1114    } else if (eventPtr->type == UnmapNotify) {
1115	/*
1116	 * Unmap all of the slaves when the master gets unmapped, so that they
1117	 * don't keep redisplaying themselves.
1118	 */
1119
1120	for (slavePtr = masterPtr->slavePtr; slavePtr != NULL;
1121		slavePtr = slavePtr->nextPtr) {
1122	    Tk_UnmapWindow(slavePtr->tkwin);
1123	}
1124    }
1125}
1126
1127/*
1128 *----------------------------------------------------------------------
1129 *
1130 * SlaveStructureProc --
1131 *
1132 *	This function is invoked by the Tk event handler when StructureNotify
1133 *	events occur for a slave window.
1134 *
1135 * Results:
1136 *	None.
1137 *
1138 * Side effects:
1139 *	Structures get cleaned up if the window was deleted.
1140 *
1141 *----------------------------------------------------------------------
1142 */
1143
1144static void
1145SlaveStructureProc(
1146    ClientData clientData,	/* Pointer to Slave structure for window
1147				 * referred to by eventPtr. */
1148    XEvent *eventPtr)		/* Describes what just happened. */
1149{
1150    register Slave *slavePtr = (Slave *) clientData;
1151    TkDisplay *dispPtr = ((TkWindow *) slavePtr->tkwin)->dispPtr;
1152
1153    if (eventPtr->type == DestroyNotify) {
1154	if (slavePtr->masterPtr != NULL) {
1155	    UnlinkSlave(slavePtr);
1156	}
1157	Tcl_DeleteHashEntry(Tcl_FindHashEntry(&dispPtr->slaveTable,
1158		(char *) slavePtr->tkwin));
1159	FreeSlave(slavePtr);
1160    }
1161}
1162
1163/*
1164 *----------------------------------------------------------------------
1165 *
1166 * PlaceRequestProc --
1167 *
1168 *	This function is invoked by Tk whenever a slave managed by us changes
1169 *	its requested geometry.
1170 *
1171 * Results:
1172 *	None.
1173 *
1174 * Side effects:
1175 *	The window will get relayed out, if its requested size has anything to
1176 *	do with its actual size.
1177 *
1178 *----------------------------------------------------------------------
1179 */
1180
1181	/* ARGSUSED */
1182static void
1183PlaceRequestProc(
1184    ClientData clientData,	/* Pointer to our record for slave. */
1185    Tk_Window tkwin)		/* Window that changed its desired size. */
1186{
1187    Slave *slavePtr = (Slave *) clientData;
1188    Master *masterPtr;
1189
1190    if (((slavePtr->flags & (CHILD_WIDTH|CHILD_REL_WIDTH)) != 0)
1191	    && ((slavePtr->flags & (CHILD_HEIGHT|CHILD_REL_HEIGHT)) != 0)) {
1192	return;
1193    }
1194    masterPtr = slavePtr->masterPtr;
1195    if (masterPtr == NULL) {
1196	return;
1197    }
1198    if (!(masterPtr->flags & PARENT_RECONFIG_PENDING)) {
1199	masterPtr->flags |= PARENT_RECONFIG_PENDING;
1200	Tcl_DoWhenIdle(RecomputePlacement, (ClientData) masterPtr);
1201    }
1202}
1203
1204/*
1205 *--------------------------------------------------------------
1206 *
1207 * PlaceLostSlaveProc --
1208 *
1209 *	This function is invoked by Tk whenever some other geometry claims
1210 *	control over a slave that used to be managed by us.
1211 *
1212 * Results:
1213 *	None.
1214 *
1215 * Side effects:
1216 *	Forgets all placer-related information about the slave.
1217 *
1218 *--------------------------------------------------------------
1219 */
1220
1221	/* ARGSUSED */
1222static void
1223PlaceLostSlaveProc(
1224    ClientData clientData,	/* Slave structure for slave window that was
1225				 * stolen away. */
1226    Tk_Window tkwin)		/* Tk's handle for the slave window. */
1227{
1228    register Slave *slavePtr = (Slave *) clientData;
1229    TkDisplay *dispPtr = ((TkWindow *) slavePtr->tkwin)->dispPtr;
1230
1231    if (slavePtr->masterPtr->tkwin != Tk_Parent(slavePtr->tkwin)) {
1232	Tk_UnmaintainGeometry(slavePtr->tkwin, slavePtr->masterPtr->tkwin);
1233    }
1234    Tk_UnmapWindow(tkwin);
1235    UnlinkSlave(slavePtr);
1236    Tcl_DeleteHashEntry(Tcl_FindHashEntry(&dispPtr->slaveTable,
1237	    (char *) tkwin));
1238    Tk_DeleteEventHandler(tkwin, StructureNotifyMask, SlaveStructureProc,
1239	    (ClientData) slavePtr);
1240    FreeSlave(slavePtr);
1241}
1242
1243/*
1244 * Local Variables:
1245 * mode: c
1246 * c-basic-offset: 4
1247 * fill-column: 78
1248 * End:
1249 */
1250