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