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