1 /*
2  * tkGeometry.c --
3  *
4  *	This file contains generic Tk code for geometry management
5  *	(stuff that's used by all geometry managers).
6  *
7  * Copyright (c) 1990-1994 The Regents of the University of California.
8  * Copyright (c) 1994-1995 Sun Microsystems, Inc.
9  *
10  * See the file "license.terms" for information on usage and redistribution
11  * of this file, and for a DISCLAIMER OF ALL WARRANTIES.
12  *
13  * SCCS: @(#) tkGeometry.c 1.31 96/02/15 18:53:32
14  */
15 
16 #include "tkInt.h"
17 
18 /*
19  * Data structures of the following type are used by Tk_MaintainGeometry.
20  * For each slave managed by Tk_MaintainGeometry, there is one of these
21  * structures associated with its master.
22  */
23 
24 typedef struct MaintainSlave {
25     Tk_Window slave;		/* The slave window being positioned. */
26     Tk_Window master;		/* The master that determines slave's
27 				 * position; it must be a descendant of
28 				 * slave's parent. */
29     int x, y;			/* Desired position of slave relative to
30 				 * master. */
31     int width, height;		/* Desired dimensions of slave. */
32     struct MaintainSlave *nextPtr;
33 				/* Next in list of Maintains associated
34 				 * with master. */
35 } MaintainSlave;
36 
37 /*
38  * For each window that has been specified as a master to
39  * Tk_MaintainGeometry, there is a structure of the following type:
40  */
41 
42 typedef struct MaintainMaster {
43     Tk_Window ancestor;		/* The lowest ancestor of this window
44 				 * for which we have *not* created a
45 				 * StructureNotify handler.  May be the
46 				 * same as the window itself. */
47     int checkScheduled;		/* Non-zero means that there is already a
48 				 * call to MaintainCheckProc scheduled as
49 				 * an idle handler. */
50     MaintainSlave *slavePtr;	/* First in list of all slaves associated
51 				 * with this master. */
52 } MaintainMaster;
53 
54 /*
55  * Hash table that maps from a master's Tk_Window token to a list of
56  * Maintains for that master:
57  */
58 
59 static Tcl_HashTable maintainHashTable;
60 
61 /*
62  * Has maintainHashTable been initialized yet?
63  */
64 
65 static int initialized = 0;
66 
67 /*
68  * Prototypes for static procedures in this file:
69  */
70 
71 static void		MaintainCheckProc _ANSI_ARGS_((ClientData clientData));
72 static void		MaintainMasterProc _ANSI_ARGS_((ClientData clientData,
73 			    XEvent *eventPtr));
74 static void		MaintainSlaveProc _ANSI_ARGS_((ClientData clientData,
75 			    XEvent *eventPtr));
76 
77 /*
78  *--------------------------------------------------------------
79  *
80  * Tk_ManageGeometry --
81  *
82  *	Arrange for a particular procedure to manage the geometry
83  *	of a given slave window.
84  *
85  * Results:
86  *	None.
87  *
88  * Side effects:
89  *	Proc becomes the new geometry manager for tkwin, replacing
90  *	any previous geometry manager.  The geometry manager will
91  *	be notified (by calling procedures in *mgrPtr) when interesting
92  *	things happen in the future.  If there was an existing geometry
93  *	manager for tkwin different from the new one, it is notified
94  *	by calling its lostSlaveProc.
95  *
96  *--------------------------------------------------------------
97  */
98 
99 void
Tk_ManageGeometry(tkwin,mgrPtr,clientData)100 Tk_ManageGeometry(tkwin, mgrPtr, clientData)
101     Tk_Window tkwin;		/* Window whose geometry is to
102 				 * be managed by proc.  */
103     Tk_GeomMgr *mgrPtr;		/* Static structure describing the
104 				 * geometry manager.  This structure
105 				 * must never go away. */
106     ClientData clientData;	/* Arbitrary one-word argument to
107 				 * pass to geometry manager procedures. */
108 {
109     register TkWindow *winPtr = (TkWindow *) tkwin;
110 
111     if ((winPtr->geomMgrPtr != NULL) && (mgrPtr != NULL)
112 	    && ((winPtr->geomMgrPtr != mgrPtr)
113 		|| (winPtr->geomData != clientData))
114 	    && (winPtr->geomMgrPtr->lostSlaveProc != NULL)) {
115 	(*winPtr->geomMgrPtr->lostSlaveProc)(winPtr->geomData, tkwin);
116     }
117 
118     winPtr->geomMgrPtr = mgrPtr;
119     winPtr->geomData = clientData;
120 }
121 
122 /*
123  *--------------------------------------------------------------
124  *
125  * Tk_GeometryRequest --
126  *
127  *	This procedure is invoked by widget code to indicate
128  *	its preferences about the size of a window it manages.
129  *	In general, widget code should call this procedure
130  *	rather than Tk_ResizeWindow.
131  *
132  * Results:
133  *	None.
134  *
135  * Side effects:
136  *	The geometry manager for tkwin (if any) is invoked to
137  *	handle the request.  If possible, it will reconfigure
138  *	tkwin and/or other windows to satisfy the request.  The
139  *	caller gets no indication of success or failure, but it
140  *	will get X events if the window size was actually
141  *	changed.
142  *
143  *--------------------------------------------------------------
144  */
145 
146 void
Tk_GeometryRequest(tkwin,reqWidth,reqHeight)147 Tk_GeometryRequest(tkwin, reqWidth, reqHeight)
148     Tk_Window tkwin;		/* Window that geometry information
149 				 * pertains to. */
150     int reqWidth, reqHeight;	/* Minimum desired dimensions for
151 				 * window, in pixels. */
152 {
153     register TkWindow *winPtr = (TkWindow *) tkwin;
154 
155     /*
156      * X gets very upset if a window requests a width or height of
157      * zero, so rounds requested sizes up to at least 1.
158      */
159 
160     if (reqWidth <= 0) {
161 	reqWidth = 1;
162     }
163     if (reqHeight <= 0) {
164 	reqHeight = 1;
165     }
166     if ((reqWidth == winPtr->reqWidth) && (reqHeight == winPtr->reqHeight)) {
167 	return;
168     }
169     winPtr->reqWidth = reqWidth;
170     winPtr->reqHeight = reqHeight;
171     if ((winPtr->geomMgrPtr != NULL)
172 	    && (winPtr->geomMgrPtr->requestProc != NULL)) {
173 	(*winPtr->geomMgrPtr->requestProc)(winPtr->geomData, tkwin);
174     }
175 }
176 
177 /*
178  *----------------------------------------------------------------------
179  *
180  * Tk_SetInternalBorder --
181  *
182  *	Notify relevant geometry managers that a window has an internal
183  *	border of a given width and that child windows should not be
184  *	placed on that border.
185  *
186  * Results:
187  *	None.
188  *
189  * Side effects:
190  *	The border width is recorded for the window, and all geometry
191  *	managers of all children are notified so that can re-layout, if
192  *	necessary.
193  *
194  *----------------------------------------------------------------------
195  */
196 
197 void
Tk_SetInternalBorder(tkwin,width)198 Tk_SetInternalBorder(tkwin, width)
199     Tk_Window tkwin;		/* Window that will have internal border. */
200     int width;			/* Width of internal border, in pixels. */
201 {
202     register TkWindow *winPtr = (TkWindow *) tkwin;
203 
204     if (width == winPtr->internalBorderWidth) {
205 	return;
206     }
207     if (width < 0) {
208 	width = 0;
209     }
210     winPtr->internalBorderWidth = width;
211 
212     /*
213      * All the slaves for which this is the master window must now be
214      * repositioned to take account of the new internal border width.
215      * To signal all the geometry managers to do this, just resize the
216      * window to its current size.  The ConfigureNotify event will
217      * cause geometry managers to recompute everything.
218      */
219 
220     Tk_ResizeWindow(tkwin, Tk_Width(tkwin), Tk_Height(tkwin));
221 }
222 
223 /*
224  *----------------------------------------------------------------------
225  *
226  * Tk_MaintainGeometry --
227  *
228  *	This procedure is invoked by geometry managers to handle slaves
229  *	whose master's are not their parents.  It translates the desired
230  *	geometry for the slave into the coordinate system of the parent
231  *	and respositions the slave if it isn't already at the right place.
232  *	Furthermore, it sets up event handlers so that if the master (or
233  *	any of its ancestors up to the slave's parent) is mapped, unmapped,
234  *	or moved, then the slave will be adjusted to match.
235  *
236  * Results:
237  *	None.
238  *
239  * Side effects:
240  *	Event handlers are created and state is allocated to keep track
241  *	of slave.  Note:  if slave was already managed for master by
242  *	Tk_MaintainGeometry, then the previous information is replaced
243  *	with the new information.  The caller must eventually call
244  *	Tk_UnmaintainGeometry to eliminate the correspondence (or, the
245  *	state is automatically freed when either window is destroyed).
246  *
247  *----------------------------------------------------------------------
248  */
249 
250 void
Tk_MaintainGeometry(slave,master,x,y,width,height)251 Tk_MaintainGeometry(slave, master, x, y, width, height)
252     Tk_Window slave;		/* Slave for geometry management. */
253     Tk_Window master;		/* Master for slave; must be a descendant
254 				 * of slave's parent. */
255     int x, y;			/* Desired position of slave within master. */
256     int width, height;		/* Desired dimensions for slave. */
257 {
258     Tcl_HashEntry *hPtr;
259     MaintainMaster *masterPtr;
260     register MaintainSlave *slavePtr;
261     int new, map;
262     Tk_Window ancestor, parent;
263 
264     if (!initialized) {
265 	initialized = 1;
266 	Tcl_InitHashTable(&maintainHashTable, TCL_ONE_WORD_KEYS);
267     }
268 
269     /*
270      * See if there is already a MaintainMaster structure for the master;
271      * if not, then create one.
272      */
273 
274     parent = Tk_Parent(slave);
275     hPtr = Tcl_CreateHashEntry(&maintainHashTable, (char *) master, &new);
276     if (!new) {
277 	masterPtr = (MaintainMaster *) Tcl_GetHashValue(hPtr);
278     } else {
279 	masterPtr = (MaintainMaster *) ckalloc(sizeof(MaintainMaster));
280 	masterPtr->ancestor = master;
281 	masterPtr->checkScheduled = 0;
282 	masterPtr->slavePtr = NULL;
283 	Tcl_SetHashValue(hPtr, masterPtr);
284     }
285 
286     /*
287      * Create a MaintainSlave structure for the slave if there isn't
288      * already one.
289      */
290 
291     for (slavePtr = masterPtr->slavePtr; slavePtr != NULL;
292 	    slavePtr = slavePtr->nextPtr) {
293 	if (slavePtr->slave == slave) {
294 	    goto gotSlave;
295 	}
296     }
297     slavePtr = (MaintainSlave *) ckalloc(sizeof(MaintainSlave));
298     slavePtr->slave = slave;
299     slavePtr->master = master;
300     slavePtr->nextPtr = masterPtr->slavePtr;
301     masterPtr->slavePtr = slavePtr;
302     Tk_CreateEventHandler(slave, StructureNotifyMask, MaintainSlaveProc,
303 	    (ClientData) slavePtr);
304 
305     /*
306      * Make sure that there are event handlers registered for all
307      * the windows between master and slave's parent (including master
308      * but not slave's parent).  There may already be handlers for master
309      * and some of its ancestors (masterPtr->ancestor tells how many).
310      */
311 
312     for (ancestor = master; ancestor != parent;
313 	    ancestor = Tk_Parent(ancestor)) {
314 	if (ancestor == masterPtr->ancestor) {
315 	    Tk_CreateEventHandler(ancestor, StructureNotifyMask,
316 		    MaintainMasterProc, (ClientData) masterPtr);
317 	    masterPtr->ancestor = Tk_Parent(ancestor);
318 	}
319     }
320 
321     /*
322      * Fill in up-to-date information in the structure, then update the
323      * window if it's not currently in the right place or state.
324      */
325 
326     gotSlave:
327     slavePtr->x = x;
328     slavePtr->y = y;
329     slavePtr->width = width;
330     slavePtr->height = height;
331     map = 1;
332     for (ancestor = slavePtr->master; ; ancestor = Tk_Parent(ancestor)) {
333 	if (!Tk_IsMapped(ancestor) && (ancestor != parent)) {
334 	    map = 0;
335 	}
336 	if (ancestor == parent) {
337 	    if ((x != Tk_X(slavePtr->slave))
338 		    || (y != Tk_Y(slavePtr->slave))
339 		    || (width != Tk_Width(slavePtr->slave))
340 		    || (height != Tk_Height(slavePtr->slave))) {
341 		Tk_MoveResizeWindow(slavePtr->slave, x, y, width, height);
342 	    }
343 	    if (map) {
344 		Tk_MapWindow(slavePtr->slave);
345 	    } else {
346 		Tk_UnmapWindow(slavePtr->slave);
347 	    }
348 	    break;
349 	}
350 	x += Tk_X(ancestor) + Tk_Changes(ancestor)->border_width;
351 	y += Tk_Y(ancestor) + Tk_Changes(ancestor)->border_width;
352     }
353 }
354 
355 /*
356  *----------------------------------------------------------------------
357  *
358  * Tk_UnmaintainGeometry --
359  *
360  *	This procedure cancels a previous Tk_MaintainGeometry call,
361  *	so that the relationship between slave and master is no longer
362  *	maintained.
363  *
364  * Results:
365  *	None.
366  *
367  * Side effects:
368  *	The slave is unmapped and state is released, so that slave won't
369  *	track master any more.  If we weren't previously managing slave
370  *	relative to master, then this procedure has no effect.
371  *
372  *----------------------------------------------------------------------
373  */
374 
375 void
Tk_UnmaintainGeometry(slave,master)376 Tk_UnmaintainGeometry(slave, master)
377     Tk_Window slave;		/* Slave for geometry management. */
378     Tk_Window master;		/* Master for slave; must be a descendant
379 				 * of slave's parent. */
380 {
381     Tcl_HashEntry *hPtr;
382     MaintainMaster *masterPtr;
383     register MaintainSlave *slavePtr, *prevPtr;
384     Tk_Window ancestor;
385 
386     if (!initialized) {
387 	initialized = 1;
388 	Tcl_InitHashTable(&maintainHashTable, TCL_ONE_WORD_KEYS);
389     }
390 
391     if (!(((TkWindow *) slave)->flags & TK_ALREADY_DEAD)) {
392 	Tk_UnmapWindow(slave);
393     }
394     hPtr = Tcl_FindHashEntry(&maintainHashTable, (char *) master);
395     if (hPtr == NULL) {
396 	return;
397     }
398     masterPtr = (MaintainMaster *) Tcl_GetHashValue(hPtr);
399     slavePtr = masterPtr->slavePtr;
400     if (slavePtr->slave == slave) {
401 	masterPtr->slavePtr = slavePtr->nextPtr;
402     } else {
403 	for (prevPtr = slavePtr, slavePtr = slavePtr->nextPtr; ;
404 		prevPtr = slavePtr, slavePtr = slavePtr->nextPtr) {
405 	    if (slavePtr == NULL) {
406 		return;
407 	    }
408 	    if (slavePtr->slave == slave) {
409 		prevPtr->nextPtr = slavePtr->nextPtr;
410 		break;
411 	    }
412 	}
413     }
414     Tk_DeleteEventHandler(slavePtr->slave, StructureNotifyMask,
415 	    MaintainSlaveProc, (ClientData) slavePtr);
416     ckfree((char *) slavePtr);
417     if (masterPtr->slavePtr == NULL) {
418 	if (masterPtr->ancestor != NULL) {
419 	    for (ancestor = master; ; ancestor = Tk_Parent(ancestor)) {
420 		Tk_DeleteEventHandler(ancestor, StructureNotifyMask,
421 			MaintainMasterProc, (ClientData) masterPtr);
422 		if (ancestor == masterPtr->ancestor) {
423 		    break;
424 		}
425 	    }
426 	}
427 	if (masterPtr->checkScheduled) {
428 	    Tcl_CancelIdleCall(MaintainCheckProc, (ClientData) masterPtr);
429 	}
430 	Tcl_DeleteHashEntry(hPtr);
431 	ckfree((char *) masterPtr);
432     }
433 }
434 
435 /*
436  *----------------------------------------------------------------------
437  *
438  * MaintainMasterProc --
439  *
440  *	This procedure is invoked by the Tk event dispatcher in
441  *	response to StructureNotify events on the master or one
442  *	of its ancestors, on behalf of Tk_MaintainGeometry.
443  *
444  * Results:
445  *	None.
446  *
447  * Side effects:
448  *	It schedules a call to MaintainCheckProc, which will eventually
449  *	caused the postions and mapped states to be recalculated for all
450  *	the maintained slaves of the master.  Or, if the master window is
451  *	being deleted then state is cleaned up.
452  *
453  *----------------------------------------------------------------------
454  */
455 
456 static void
MaintainMasterProc(clientData,eventPtr)457 MaintainMasterProc(clientData, eventPtr)
458     ClientData clientData;		/* Pointer to MaintainMaster structure
459 					 * for the master window. */
460     XEvent *eventPtr;			/* Describes what just happened. */
461 {
462     MaintainMaster *masterPtr = (MaintainMaster *) clientData;
463     MaintainSlave *slavePtr;
464     int done;
465 
466     if ((eventPtr->type == ConfigureNotify)
467 	    || (eventPtr->type == MapNotify)
468 	    || (eventPtr->type == UnmapNotify)) {
469 	if (!masterPtr->checkScheduled) {
470 	    masterPtr->checkScheduled = 1;
471 	    Tcl_DoWhenIdle(MaintainCheckProc, (ClientData) masterPtr);
472 	}
473     } else if (eventPtr->type == DestroyNotify) {
474 	/*
475 	 * Delete all of the state associated with this master, but
476 	 * be careful not to use masterPtr after the last slave is
477 	 * deleted, since its memory will have been freed.
478 	 */
479 
480 	done = 0;
481 	do {
482 	    slavePtr = masterPtr->slavePtr;
483 	    if (slavePtr->nextPtr == NULL) {
484 		done = 1;
485 	    }
486 	    Tk_UnmaintainGeometry(slavePtr->slave, slavePtr->master);
487 	} while (!done);
488     }
489 }
490 
491 /*
492  *----------------------------------------------------------------------
493  *
494  * MaintainSlaveProc --
495  *
496  *	This procedure is invoked by the Tk event dispatcher in
497  *	response to StructureNotify events on a slave being managed
498  *	by Tk_MaintainGeometry.
499  *
500  * Results:
501  *	None.
502  *
503  * Side effects:
504  *	If the event is a DestroyNotify event then the Maintain state
505  *	and event handlers for this slave are deleted.
506  *
507  *----------------------------------------------------------------------
508  */
509 
510 static void
MaintainSlaveProc(clientData,eventPtr)511 MaintainSlaveProc(clientData, eventPtr)
512     ClientData clientData;		/* Pointer to MaintainSlave structure
513 					 * for master-slave pair. */
514     XEvent *eventPtr;			/* Describes what just happened. */
515 {
516     MaintainSlave *slavePtr = (MaintainSlave *) clientData;
517 
518     if (eventPtr->type == DestroyNotify) {
519 	Tk_UnmaintainGeometry(slavePtr->slave, slavePtr->master);
520     }
521 }
522 
523 /*
524  *----------------------------------------------------------------------
525  *
526  * MaintainCheckProc --
527  *
528  *	This procedure is invoked by the Tk event dispatcher as an
529  *	idle handler, when a master or one of its ancestors has been
530  *	reconfigured, mapped, or unmapped.  Its job is to scan all of
531  *	the slaves for the master and reposition them, map them, or
532  *	unmap them as needed to maintain their geometry relative to
533  *	the master.
534  *
535  * Results:
536  *	None.
537  *
538  * Side effects:
539  *	Slaves can get repositioned, mapped, or unmapped.
540  *
541  *----------------------------------------------------------------------
542  */
543 
544 static void
MaintainCheckProc(clientData)545 MaintainCheckProc(clientData)
546     ClientData clientData;		/* Pointer to MaintainMaster structure
547 					 * for the master window. */
548 {
549     MaintainMaster *masterPtr = (MaintainMaster *) clientData;
550     MaintainSlave *slavePtr;
551     Tk_Window ancestor, parent;
552     int x, y, map;
553 
554     masterPtr->checkScheduled = 0;
555     for (slavePtr = masterPtr->slavePtr; slavePtr != NULL;
556 	    slavePtr = slavePtr->nextPtr) {
557 	parent = Tk_Parent(slavePtr->slave);
558 	x = slavePtr->x;
559 	y = slavePtr->y;
560 	map = 1;
561 	for (ancestor = slavePtr->master; ; ancestor = Tk_Parent(ancestor)) {
562 	    if (!Tk_IsMapped(ancestor) && (ancestor != parent)) {
563 		map = 0;
564 	    }
565 	    if (ancestor == parent) {
566 		if ((x != Tk_X(slavePtr->slave))
567 			|| (y != Tk_Y(slavePtr->slave))) {
568 		    Tk_MoveWindow(slavePtr->slave, x, y);
569 		}
570 		if (map) {
571 		    Tk_MapWindow(slavePtr->slave);
572 		} else {
573 		    Tk_UnmapWindow(slavePtr->slave);
574 		}
575 		break;
576 	    }
577 	    x += Tk_X(ancestor) + Tk_Changes(ancestor)->border_width;
578 	    y += Tk_Y(ancestor) + Tk_Changes(ancestor)->border_width;
579 	}
580     }
581 }
582