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