]>
Commit | Line | Data |
---|---|---|
6a5fa4e0 MG |
1 | /* |
2 | * tkPack.c -- | |
3 | * | |
4 | * This file contains code to implement the "packer" | |
5 | * geometry manager for Tk. | |
6 | * | |
7 | * Copyright 1990 Regents of the University of California. | |
8 | * Permission to use, copy, modify, and distribute this | |
9 | * software and its documentation for any purpose and without | |
10 | * fee is hereby granted, provided that the above copyright | |
11 | * notice appear in all copies. The University of California | |
12 | * makes no representations about the suitability of this | |
13 | * software for any purpose. It is provided "as is" without | |
14 | * express or implied warranty. | |
15 | */ | |
16 | ||
17 | #ifndef lint | |
18 | static char rcsid[] = "$Header: /user6/ouster/wish/RCS/tkPack.c,v 1.27 92/01/04 15:16:41 ouster Exp $ SPRITE (Berkeley)"; | |
19 | #endif | |
20 | ||
21 | #include "tkconfig.h" | |
22 | #include "tkint.h" | |
23 | ||
24 | typedef enum {TOP, BOTTOM, LEFT, RIGHT} Side; | |
25 | ||
26 | /* For each window that the packer cares about (either because | |
27 | * the window is managed by the packer or because the window | |
28 | * has children that are managed by the packer), there is a | |
29 | * structure of the following type: | |
30 | */ | |
31 | ||
32 | typedef struct Packer { | |
33 | Tk_Window tkwin; /* Tk token for window. NULL means that | |
34 | * the window has been deleted, but the | |
35 | * packet hasn't had a chance to clean up | |
36 | * yet because the structure is still in | |
37 | * use. */ | |
38 | struct Packer *parentPtr; /* Parent within which this window | |
39 | * is packed (NULL means this window | |
40 | * isn't managed by the packer). */ | |
41 | struct Packer *nextPtr; /* Next window packed within same | |
42 | * parent. List is priority-ordered: | |
43 | * first on list gets packed first. */ | |
44 | struct Packer *childPtr; /* First in list of children packed | |
45 | * inside this window (NULL means | |
46 | * no packed children). */ | |
47 | Side side; /* Side of parent against which | |
48 | * this window is packed. */ | |
49 | Tk_Anchor anchorPoint; /* If frame allocated for window is larger | |
50 | * than window needs, this indicates how | |
51 | * where to position window in frame. */ | |
52 | int padX, padY; /* Additional amounts of space to give window | |
53 | * besides what it asked for. */ | |
54 | int doubleBw; /* Twice the window's last known border | |
55 | * width. If this changes, the window | |
56 | * must be repacked within its parent. */ | |
57 | int *abortPtr; /* If non-NULL, it means that there is a nested | |
58 | * call to ArrangePacking already working on | |
59 | * this window. *abortPtr may be set to 1 to | |
60 | * abort that nested call. This happens, for | |
61 | * example, if tkwin or any of its children | |
62 | * is deleted. */ | |
63 | int flags; /* Miscellaneous flags; see below | |
64 | * for definitions. */ | |
65 | } Packer; | |
66 | ||
67 | /* | |
68 | * Flag values for Packer structures: | |
69 | * | |
70 | * REQUESTED_REPACK: 1 means a Tk_DoWhenIdle request | |
71 | * has already been made to repack | |
72 | * all the children of this window. | |
73 | * FILLX: 1 means if frame allocated for window | |
74 | * is wider than window needs, expand window | |
75 | * to fill frame. 0 means don't make window | |
76 | * any larger than needed. | |
77 | * FILLY: Same as FILLX, except for height. | |
78 | * EXPAND: 1 means this window's frame will absorb any | |
79 | * extra space in the parent window. | |
80 | */ | |
81 | ||
82 | #define REQUESTED_REPACK 1 | |
83 | #define FILLX 2 | |
84 | #define FILLY 4 | |
85 | #define EXPAND 8 | |
86 | ||
87 | /* | |
88 | * Hash table used to map from Tk_Window tokens to corresponding | |
89 | * Packer structures: | |
90 | */ | |
91 | ||
92 | static Tcl_HashTable packerHashTable; | |
93 | ||
94 | /* | |
95 | * Have statics in this module been initialized? | |
96 | */ | |
97 | ||
98 | static initialized = 0; | |
99 | ||
100 | /* | |
101 | * Forward declarations for procedures defined later in this file: | |
102 | */ | |
103 | ||
104 | static void ArrangePacking _ANSI_ARGS_((ClientData clientData)); | |
105 | static Packer * GetPacker _ANSI_ARGS_((Tk_Window tkwin)); | |
106 | static int PackAfter _ANSI_ARGS_((Tcl_Interp *interp, | |
107 | Packer *prevPtr, Packer *parentPtr, int argc, | |
108 | char **argv)); | |
109 | static void PackReqProc _ANSI_ARGS_((ClientData clientData, | |
110 | Tk_Window tkwin)); | |
111 | static void PackStructureProc _ANSI_ARGS_((ClientData clientData, | |
112 | XEvent *eventPtr)); | |
113 | static void Unlink _ANSI_ARGS_((Packer *packPtr)); | |
114 | \f | |
115 | /* | |
116 | *-------------------------------------------------------------- | |
117 | * | |
118 | * Tk_PackCmd -- | |
119 | * | |
120 | * This procedure is invoked to process the "pack" Tcl command. | |
121 | * See the user documentation for details on what it does. | |
122 | * | |
123 | * Results: | |
124 | * A standard Tcl result. | |
125 | * | |
126 | * Side effects: | |
127 | * See the user documentation. | |
128 | * | |
129 | *-------------------------------------------------------------- | |
130 | */ | |
131 | ||
132 | int | |
133 | Tk_PackCmd(clientData, interp, argc, argv) | |
134 | ClientData clientData; /* Main window associated with | |
135 | * interpreter. */ | |
136 | Tcl_Interp *interp; /* Current interpreter. */ | |
137 | int argc; /* Number of arguments. */ | |
138 | char **argv; /* Argument strings. */ | |
139 | { | |
140 | Tk_Window tkwin = (Tk_Window) clientData; | |
141 | int length; | |
142 | char c; | |
143 | ||
144 | if (argc < 3) { | |
145 | Tcl_AppendResult(interp, "wrong # args: should be \"", | |
146 | argv[0], " option arg ?arg ...?\"", (char *) NULL); | |
147 | return TCL_ERROR; | |
148 | } | |
149 | c = argv[1][0]; | |
150 | length = strlen(argv[1]); | |
151 | if ((c == 'a') && (length >= 2) | |
152 | && (strncmp(argv[1], "after", length) == 0)) { | |
153 | Packer *prevPtr; | |
154 | Tk_Window tkwin2; | |
155 | ||
156 | tkwin2 = Tk_NameToWindow(interp, argv[2], tkwin); | |
157 | if (tkwin2 == NULL) { | |
158 | return TCL_ERROR; | |
159 | } | |
160 | prevPtr = GetPacker(tkwin2); | |
161 | if (prevPtr->parentPtr == NULL) { | |
162 | Tcl_AppendResult(interp, "window \"", argv[2], | |
163 | "\" isn't packed", (char *) NULL); | |
164 | return TCL_ERROR; | |
165 | } | |
166 | return PackAfter(interp, prevPtr, prevPtr->parentPtr, argc-3, argv+3); | |
167 | } else if ((c == 'a') && (length >= 2) | |
168 | && (strncmp(argv[1], "append", length) == 0)) { | |
169 | Packer *parentPtr; | |
170 | register Packer *prevPtr; | |
171 | Tk_Window tkwin2; | |
172 | ||
173 | tkwin2 = Tk_NameToWindow(interp, argv[2], tkwin); | |
174 | if (tkwin2 == NULL) { | |
175 | return TCL_ERROR; | |
176 | } | |
177 | parentPtr = GetPacker(tkwin2); | |
178 | prevPtr = parentPtr->childPtr; | |
179 | if (prevPtr != NULL) { | |
180 | while (prevPtr->nextPtr != NULL) { | |
181 | prevPtr = prevPtr->nextPtr; | |
182 | } | |
183 | } | |
184 | return PackAfter(interp, prevPtr, parentPtr, argc-3, argv+3); | |
185 | } else if ((c == 'b') && (strncmp(argv[1], "before", length) == 0)) { | |
186 | Packer *packPtr, *parentPtr; | |
187 | register Packer *prevPtr; | |
188 | Tk_Window tkwin2; | |
189 | ||
190 | tkwin2 = Tk_NameToWindow(interp, argv[2], tkwin); | |
191 | if (tkwin2 == NULL) { | |
192 | return TCL_ERROR; | |
193 | } | |
194 | packPtr = GetPacker(tkwin2); | |
195 | if (packPtr->parentPtr == NULL) { | |
196 | Tcl_AppendResult(interp, "window \"", argv[2], | |
197 | "\" isn't packed", (char *) NULL); | |
198 | return TCL_ERROR; | |
199 | } | |
200 | parentPtr = packPtr->parentPtr; | |
201 | prevPtr = parentPtr->childPtr; | |
202 | if (prevPtr == packPtr) { | |
203 | prevPtr = NULL; | |
204 | } else { | |
205 | for ( ; ; prevPtr = prevPtr->nextPtr) { | |
206 | if (prevPtr == NULL) { | |
207 | panic("\"pack before\" couldn't find predecessor"); | |
208 | } | |
209 | if (prevPtr->nextPtr == packPtr) { | |
210 | break; | |
211 | } | |
212 | } | |
213 | } | |
214 | return PackAfter(interp, prevPtr, parentPtr, argc-3, argv+3); | |
215 | } else if ((c == 'i') && (strncmp(argv[1], "info", length) == 0)) { | |
216 | char *prefix; | |
217 | register Packer *packPtr; | |
218 | Tk_Window tkwin2; | |
219 | char tmp[20]; | |
220 | static char *sideNames[] = {"top", "bottom", "left", "right"}; | |
221 | ||
222 | if (argc != 3) { | |
223 | Tcl_AppendResult(interp, "wrong # args: should be \"", | |
224 | argv[0], " info window\"", (char *) NULL); | |
225 | return TCL_ERROR; | |
226 | } | |
227 | tkwin2 = Tk_NameToWindow(interp, argv[2], tkwin); | |
228 | if (tkwin2 == NULL) { | |
229 | return TCL_ERROR; | |
230 | } | |
231 | packPtr = GetPacker(tkwin2); | |
232 | prefix = ""; | |
233 | for (packPtr = packPtr->childPtr; packPtr != NULL; | |
234 | packPtr = packPtr->nextPtr) { | |
235 | Tcl_AppendResult(interp, prefix, Tk_PathName(packPtr->tkwin), | |
236 | " {", sideNames[(int) packPtr->side], | |
237 | " frame ", Tk_NameOfAnchor(packPtr->anchorPoint), | |
238 | (char *) NULL); | |
239 | if (packPtr->padX != 0) { | |
240 | sprintf(tmp, "%d", packPtr->padX); | |
241 | Tcl_AppendResult(interp, " padx ", tmp, (char *) NULL); | |
242 | } | |
243 | if (packPtr->padY != 0) { | |
244 | sprintf(tmp, "%d", packPtr->padY); | |
245 | Tcl_AppendResult(interp, " pady ", tmp, (char *) NULL); | |
246 | } | |
247 | if (packPtr->flags & EXPAND) { | |
248 | Tcl_AppendResult(interp, " expand", (char *) NULL); | |
249 | } | |
250 | if ((packPtr->flags & (FILLX|FILLY)) == (FILLX|FILLY)) { | |
251 | Tcl_AppendResult(interp, " fill", (char *) NULL); | |
252 | } else if (packPtr->flags & FILLX) { | |
253 | Tcl_AppendResult(interp, " fillx", (char *) NULL); | |
254 | } else if (packPtr->flags & FILLY) { | |
255 | Tcl_AppendResult(interp, " filly", (char *) NULL); | |
256 | } | |
257 | Tcl_AppendResult(interp, "}", (char *) NULL); | |
258 | prefix = " "; | |
259 | } | |
260 | return TCL_OK; | |
261 | } else if ((c == 'u') && (strncmp(argv[1], "unpack", length) == 0)) { | |
262 | Tk_Window tkwin2; | |
263 | Packer *packPtr; | |
264 | ||
265 | if (argc != 3) { | |
266 | Tcl_AppendResult(interp, "wrong # args: should be \"", | |
267 | argv[0], " unpack window\"", (char *) NULL); | |
268 | return TCL_ERROR; | |
269 | } | |
270 | tkwin2 = Tk_NameToWindow(interp, argv[2], tkwin); | |
271 | if (tkwin2 == NULL) { | |
272 | return TCL_ERROR; | |
273 | } | |
274 | packPtr = GetPacker(tkwin2); | |
275 | if ((packPtr != NULL) && (packPtr->parentPtr != NULL)) { | |
276 | Tk_ManageGeometry(tkwin2, (Tk_GeometryProc *) NULL, | |
277 | (ClientData) NULL); | |
278 | Unlink(packPtr); | |
279 | Tk_UnmapWindow(packPtr->tkwin); | |
280 | } | |
281 | } else { | |
282 | Tcl_AppendResult(interp, "bad option \"", argv[1], | |
283 | "\": must be after, append, before, or info", (char *) NULL); | |
284 | return TCL_ERROR; | |
285 | } | |
286 | return TCL_OK; | |
287 | } | |
288 | \f | |
289 | /* | |
290 | *-------------------------------------------------------------- | |
291 | * | |
292 | * PackReqProc -- | |
293 | * | |
294 | * This procedure is invoked by Tk_GeometryRequest for | |
295 | * windows managed by the packer. | |
296 | * | |
297 | * Results: | |
298 | * None. | |
299 | * | |
300 | * Side effects: | |
301 | * Arranges for tkwin, and all its managed siblings, to | |
302 | * be re-packed at the next idle point. | |
303 | * | |
304 | *-------------------------------------------------------------- | |
305 | */ | |
306 | ||
307 | /* ARGSUSED */ | |
308 | static void | |
309 | PackReqProc(clientData, tkwin) | |
310 | ClientData clientData; /* Packer's information about | |
311 | * window that got new preferred | |
312 | * geometry. */ | |
313 | Tk_Window tkwin; /* Other Tk-related information | |
314 | * about the window. */ | |
315 | { | |
316 | register Packer *packPtr = (Packer *) clientData; | |
317 | ||
318 | packPtr = packPtr->parentPtr; | |
319 | if (!(packPtr->flags & REQUESTED_REPACK)) { | |
320 | packPtr->flags |= REQUESTED_REPACK; | |
321 | Tk_DoWhenIdle(ArrangePacking, (ClientData) packPtr); | |
322 | } | |
323 | } | |
324 | \f | |
325 | /* | |
326 | *-------------------------------------------------------------- | |
327 | * | |
328 | * ArrangePacking -- | |
329 | * | |
330 | * This procedure is invoked (using the Tk_DoWhenIdle | |
331 | * mechanism) to re-layout a set of windows managed by | |
332 | * the packer. It is invoked at idle time so that a | |
333 | * series of packer requests can be merged into a single | |
334 | * layout operation. | |
335 | * | |
336 | * Results: | |
337 | * None. | |
338 | * | |
339 | * Side effects: | |
340 | * The packed children of parentPtr may get resized or | |
341 | * moved. | |
342 | * | |
343 | *-------------------------------------------------------------- | |
344 | */ | |
345 | ||
346 | static void | |
347 | ArrangePacking(clientData) | |
348 | ClientData clientData; /* Structure describing parent | |
349 | * whose children are to be | |
350 | * re-layed out. */ | |
351 | { | |
352 | register Packer *parentPtr = (Packer *) clientData; | |
353 | register Packer *childPtr; | |
354 | int numExpX, numExpY; /* # of windows that are expandable in | |
355 | * each direction. */ | |
356 | int spareX, spareY; /* Amount of extra space to give to each | |
357 | * expandable window. */ | |
358 | int leftOverX, leftOverY; /* Extra chunk of space to give to last | |
359 | * expandable window. */ | |
360 | int cavityX, cavityY, cavityWidth, cavityHeight; | |
361 | /* These variables keep track of the | |
362 | * as-yet-unallocated space remaining in | |
363 | * the middle of the parent window. */ | |
364 | int frameX, frameY, frameWidth, frameHeight; | |
365 | /* These variables keep track of the frame | |
366 | * allocated to the current window. */ | |
367 | int x, y, width, height; /* These variables are used to hold the | |
368 | * actual geometry of the current window. */ | |
369 | int intBWidth; /* Width of internal border in parent window, | |
370 | * if any. */ | |
371 | int abort; /* May get set to non-zero to abort this | |
372 | * repacking operation. */ | |
373 | int maxWidth, maxHeight, tmp; | |
374 | ||
375 | parentPtr->flags &= ~REQUESTED_REPACK; | |
376 | ||
377 | /* | |
378 | * If the parent has no children anymore, then don't do anything | |
379 | * at all: just leave the parent's size as-is. | |
380 | */ | |
381 | ||
382 | if (parentPtr->childPtr == NULL) { | |
383 | return; | |
384 | } | |
385 | ||
386 | /* | |
387 | * Abort any nested call to ArrangePacking for this window, since | |
388 | * we'll do everything necessary here, and set up so this call | |
389 | * can be aborted if necessary. | |
390 | */ | |
391 | ||
392 | if (parentPtr->abortPtr != NULL) { | |
393 | *parentPtr->abortPtr = 1; | |
394 | } | |
395 | parentPtr->abortPtr = &abort; | |
396 | abort = 0; | |
397 | Tk_Preserve((ClientData) parentPtr); | |
398 | ||
399 | /* | |
400 | * Pass #1: scan all the children to figure out the total amount | |
401 | * of space needed. Two separate widths and heights are computed. | |
402 | * | |
403 | * "Width" and "height" compute the minimum parent size to meet | |
404 | * the needs of each window in the direction "where there is | |
405 | * flexibility". For example, if a child is packed TOP, then | |
406 | * y is the flexible direction: the child's requested height | |
407 | * will determine its size. For this window x is the inflexible | |
408 | * direction: the window's width will be determined by the amount | |
409 | * of space left in the parent's cavity, not by the window's | |
410 | * requested width. "Width" and "height" are needed in order to | |
411 | * compute how much extra space there is, so that it can be divided | |
412 | * among the windows that have the EXPAND flag. | |
413 | * | |
414 | * "MaxWidth" and "maxHeight" compute the minimum parent size to | |
415 | * meet all the needs of every window in both directions, flexible | |
416 | * or inflexible. These values are needed to make geometry requests | |
417 | * of the parent's parent. | |
418 | */ | |
419 | ||
420 | intBWidth = Tk_InternalBorderWidth(parentPtr->tkwin); | |
421 | width = height = maxWidth = maxHeight = 2*intBWidth; | |
422 | numExpX = numExpY = 0; | |
423 | for (childPtr = parentPtr->childPtr; childPtr != NULL; | |
424 | childPtr = childPtr->nextPtr) { | |
425 | if ((childPtr->side == TOP) || (childPtr->side == BOTTOM)) { | |
426 | tmp = Tk_ReqWidth(childPtr->tkwin) + childPtr->doubleBw | |
427 | + childPtr->padX + width; | |
428 | if (tmp > maxWidth) { | |
429 | maxWidth = tmp; | |
430 | } | |
431 | height += Tk_ReqHeight(childPtr->tkwin) + childPtr->doubleBw | |
432 | + childPtr->padY; | |
433 | if (childPtr->flags & EXPAND) { | |
434 | numExpY++; | |
435 | } | |
436 | } else { | |
437 | tmp = Tk_ReqHeight(childPtr->tkwin) + childPtr->doubleBw | |
438 | + childPtr->padY + height; | |
439 | if (tmp > maxHeight) { | |
440 | maxHeight = tmp; | |
441 | } | |
442 | width += Tk_ReqWidth(childPtr->tkwin) + childPtr->doubleBw | |
443 | + childPtr->padX; | |
444 | if (childPtr->flags & EXPAND) { | |
445 | numExpX++; | |
446 | } | |
447 | } | |
448 | } | |
449 | if (width > maxWidth) { | |
450 | maxWidth = width; | |
451 | } | |
452 | if (height > maxHeight) { | |
453 | maxHeight = height; | |
454 | } | |
455 | ||
456 | /* | |
457 | * If the total amount of space needed in the parent window has | |
458 | * changed, then notify the next geometry manager up and requeue | |
459 | * ourselves to start again after the parent has had a chance to | |
460 | * resize us. | |
461 | */ | |
462 | ||
463 | if ((maxWidth != Tk_ReqWidth(parentPtr->tkwin)) | |
464 | || (maxHeight != Tk_ReqHeight(parentPtr->tkwin))) { | |
465 | Tk_GeometryRequest(parentPtr->tkwin, maxWidth, maxHeight); | |
466 | parentPtr->flags |= REQUESTED_REPACK; | |
467 | Tk_DoWhenIdle(ArrangePacking, (ClientData) parentPtr); | |
468 | goto done; | |
469 | } | |
470 | ||
471 | /* | |
472 | * If there is spare space, figure out how much of it goes to | |
473 | * each of the windows that is expandable. | |
474 | */ | |
475 | ||
476 | spareX = Tk_Width(parentPtr->tkwin) - width; | |
477 | spareY = Tk_Height(parentPtr->tkwin) - height; | |
478 | if ((spareX <= 0) || (numExpX == 0)) { | |
479 | leftOverX = 0; | |
480 | spareX = 0; | |
481 | } else { | |
482 | leftOverX = spareX % numExpX; | |
483 | spareX /= numExpX; | |
484 | } | |
485 | if ((spareY <= 0) || (numExpY == 0)) { | |
486 | leftOverY = spareY; | |
487 | spareY = 0; | |
488 | } else { | |
489 | leftOverY = spareY % numExpY; | |
490 | spareY /= numExpY; | |
491 | } | |
492 | ||
493 | /* | |
494 | * Pass #2: scan the children a second time assigning | |
495 | * new sizes. The "cavity" variables keep track of the | |
496 | * unclaimed space in the cavity of the window; this | |
497 | * shrinks inward as we allocate windows around the | |
498 | * edges. The "frame" variables keep track of the space | |
499 | * allocated to the current window and its frame. The | |
500 | * current window is then placed somewhere inside the | |
501 | * frame, depending on anchorPoint. | |
502 | */ | |
503 | ||
504 | cavityX = cavityY = x = y = intBWidth; | |
505 | cavityWidth = Tk_Width(parentPtr->tkwin) - 2*intBWidth; | |
506 | cavityHeight = Tk_Height(parentPtr->tkwin) - 2*intBWidth; | |
507 | for (childPtr = parentPtr->childPtr; childPtr != NULL; | |
508 | childPtr = childPtr->nextPtr) { | |
509 | if ((childPtr->side == TOP) || (childPtr->side == BOTTOM)) { | |
510 | frameWidth = cavityWidth; | |
511 | frameHeight = Tk_ReqHeight(childPtr->tkwin) + childPtr->padY | |
512 | + childPtr->doubleBw; | |
513 | if (childPtr->flags & EXPAND) { | |
514 | frameHeight += spareY; | |
515 | numExpY--; | |
516 | if (numExpY == 0) { | |
517 | frameHeight += leftOverY; | |
518 | } | |
519 | } | |
520 | cavityHeight -= frameHeight; | |
521 | if (cavityHeight < 0) { | |
522 | frameHeight += cavityHeight; | |
523 | cavityHeight = 0; | |
524 | } | |
525 | frameX = cavityX; | |
526 | if (childPtr->side == TOP) { | |
527 | frameY = cavityY; | |
528 | cavityY += frameHeight; | |
529 | } else { | |
530 | frameY = cavityY + cavityHeight; | |
531 | } | |
532 | } else { | |
533 | frameHeight = cavityHeight; | |
534 | frameWidth = Tk_ReqWidth(childPtr->tkwin) + childPtr->padX | |
535 | + childPtr->doubleBw; | |
536 | if (childPtr->flags & EXPAND) { | |
537 | frameWidth += spareX; | |
538 | numExpX--; | |
539 | if (numExpX == 0) { | |
540 | frameWidth += leftOverX; | |
541 | } | |
542 | } | |
543 | cavityWidth -= frameWidth; | |
544 | if (cavityWidth < 0) { | |
545 | frameWidth += cavityWidth; | |
546 | cavityWidth = 0; | |
547 | } | |
548 | frameY = cavityY; | |
549 | if (childPtr->side == LEFT) { | |
550 | frameX = cavityX; | |
551 | cavityX += frameWidth; | |
552 | } else { | |
553 | frameX = cavityX + cavityWidth; | |
554 | } | |
555 | } | |
556 | ||
557 | /* | |
558 | * Now that we've got the size of the frame for the window, | |
559 | * compute the window's actual size and location using the | |
560 | * fill and frame factors. | |
561 | */ | |
562 | ||
563 | width = Tk_ReqWidth(childPtr->tkwin) + childPtr->doubleBw; | |
564 | if ((childPtr->flags & FILLX) || (width > frameWidth)) { | |
565 | width = frameWidth; | |
566 | } | |
567 | height = Tk_ReqHeight(childPtr->tkwin) + childPtr->doubleBw; | |
568 | if ((childPtr->flags & FILLY) || (height > frameHeight)) { | |
569 | height = frameHeight; | |
570 | } | |
571 | switch (childPtr->anchorPoint) { | |
572 | case TK_ANCHOR_N: | |
573 | x = frameX + (frameWidth - width)/2; | |
574 | y = frameY; | |
575 | break; | |
576 | case TK_ANCHOR_NE: | |
577 | x = frameX + frameWidth - width; | |
578 | y = frameY; | |
579 | break; | |
580 | case TK_ANCHOR_E: | |
581 | x = frameX + frameWidth - width; | |
582 | y = frameY + (frameHeight - height)/2; | |
583 | break; | |
584 | case TK_ANCHOR_SE: | |
585 | x = frameX + frameWidth - width; | |
586 | y = frameY + frameHeight - height; | |
587 | break; | |
588 | case TK_ANCHOR_S: | |
589 | x = frameX + (frameWidth - width)/2; | |
590 | y = frameY + frameHeight - height; | |
591 | break; | |
592 | case TK_ANCHOR_SW: | |
593 | x = frameX; | |
594 | y = frameY + frameHeight - height; | |
595 | break; | |
596 | case TK_ANCHOR_W: | |
597 | x = frameX; | |
598 | y = frameY + (frameHeight - height)/2; | |
599 | break; | |
600 | case TK_ANCHOR_NW: | |
601 | x = frameX; | |
602 | y = frameY; | |
603 | break; | |
604 | case TK_ANCHOR_CENTER: | |
605 | x = frameX + (frameWidth - width)/2; | |
606 | y = frameY + (frameHeight - height)/2; | |
607 | break; | |
608 | default: | |
609 | panic("bad frame factor in ArrangePacking"); | |
610 | } | |
611 | width -= childPtr->doubleBw; | |
612 | height -= childPtr->doubleBw; | |
613 | ||
614 | /* | |
615 | * If the window is too small to be interesting then | |
616 | * unmap it. Otherwise configure it and then make sure | |
617 | * it's mapped. | |
618 | */ | |
619 | ||
620 | if ((width <= 0) || (height <= 0)) { | |
621 | Tk_UnmapWindow(childPtr->tkwin); | |
622 | } else { | |
623 | if ((x != Tk_X(childPtr->tkwin)) | |
624 | || (y != Tk_Y(childPtr->tkwin)) | |
625 | || (width != Tk_Width(childPtr->tkwin)) | |
626 | || (height != Tk_Height(childPtr->tkwin))) { | |
627 | Tk_MoveResizeWindow(childPtr->tkwin, x, y, | |
628 | (unsigned int) width, (unsigned int) height); | |
629 | } | |
630 | if (abort) { | |
631 | goto done; | |
632 | } | |
633 | Tk_MapWindow(childPtr->tkwin); | |
634 | } | |
635 | ||
636 | /* | |
637 | * Changes to the window's structure could cause almost anything | |
638 | * to happen, including deleting the parent or child. If this | |
639 | * happens, we'll be told to abort. | |
640 | */ | |
641 | ||
642 | if (abort) { | |
643 | goto done; | |
644 | } | |
645 | } | |
646 | ||
647 | done: | |
648 | parentPtr->abortPtr = NULL; | |
649 | Tk_Release((ClientData) parentPtr); | |
650 | } | |
651 | \f | |
652 | /* | |
653 | *-------------------------------------------------------------- | |
654 | * | |
655 | * GetPacker -- | |
656 | * | |
657 | * This internal procedure is used to locate a Packer | |
658 | * structure for a given window, creating one if one | |
659 | * doesn't exist already. | |
660 | * | |
661 | * Results: | |
662 | * The return value is a pointer to the Packer structure | |
663 | * corresponding to tkwin. | |
664 | * | |
665 | * Side effects: | |
666 | * A new packer structure may be created. If so, then | |
667 | * a callback is set up to clean things up when the | |
668 | * window is deleted. | |
669 | * | |
670 | *-------------------------------------------------------------- | |
671 | */ | |
672 | ||
673 | static Packer * | |
674 | GetPacker(tkwin) | |
675 | Tk_Window tkwin; /* Token for window for which | |
676 | * packer structure is desired. */ | |
677 | { | |
678 | register Packer *packPtr; | |
679 | Tcl_HashEntry *hPtr; | |
680 | int new; | |
681 | ||
682 | if (!initialized) { | |
683 | initialized = 1; | |
684 | Tcl_InitHashTable(&packerHashTable, TCL_ONE_WORD_KEYS); | |
685 | } | |
686 | ||
687 | /* | |
688 | * See if there's already packer for this window. If not, | |
689 | * then create a new one. | |
690 | */ | |
691 | ||
692 | hPtr = Tcl_CreateHashEntry(&packerHashTable, (char *) tkwin, &new); | |
693 | if (!new) { | |
694 | return (Packer *) Tcl_GetHashValue(hPtr); | |
695 | } | |
696 | packPtr = (Packer *) ckalloc(sizeof(Packer)); | |
697 | packPtr->tkwin = tkwin; | |
698 | packPtr->parentPtr = NULL; | |
699 | packPtr->nextPtr = NULL; | |
700 | packPtr->childPtr = NULL; | |
701 | packPtr->side = TOP; | |
702 | packPtr->anchorPoint = TK_ANCHOR_CENTER; | |
703 | packPtr->padX = packPtr->padY = 0; | |
704 | packPtr->doubleBw = 2*Tk_Changes(tkwin)->border_width; | |
705 | packPtr->abortPtr = NULL; | |
706 | packPtr->flags = 0; | |
707 | Tcl_SetHashValue(hPtr, packPtr); | |
708 | Tk_CreateEventHandler(tkwin, StructureNotifyMask, | |
709 | PackStructureProc, (ClientData) packPtr); | |
710 | return packPtr; | |
711 | } | |
712 | \f | |
713 | /* | |
714 | *-------------------------------------------------------------- | |
715 | * | |
716 | * PackAfter -- | |
717 | * | |
718 | * This procedure does most of the real work of adding | |
719 | * one or more windows into the packing order for its parent. | |
720 | * | |
721 | * Results: | |
722 | * A standard Tcl return value. | |
723 | * | |
724 | * Side effects: | |
725 | * The geometry of the specified windows may change, both now and | |
726 | * again in the future. | |
727 | * | |
728 | *-------------------------------------------------------------- | |
729 | */ | |
730 | ||
731 | static int | |
732 | PackAfter(interp, prevPtr, parentPtr, argc, argv) | |
733 | Tcl_Interp *interp; /* Interpreter for error reporting. */ | |
734 | Packer *prevPtr; /* Pack windows in argv just after this | |
735 | * window; NULL means pack as first | |
736 | * child of parentPtr. */ | |
737 | Packer *parentPtr; /* Parent in which to pack windows. */ | |
738 | int argc; /* Number of elements in argv. */ | |
739 | char **argv; /* Array of lists, each containing 2 | |
740 | * elements: window name and side | |
741 | * against which to pack. */ | |
742 | { | |
743 | register Packer *packPtr; | |
744 | Tk_Window tkwin; | |
745 | int length, optionCount; | |
746 | char **options; | |
747 | int index; | |
748 | char c; | |
749 | ||
750 | /* | |
751 | * Iterate over all of the window specifiers, each consisting of | |
752 | * two arguments. The first argument contains the window name and | |
753 | * the additional arguments contain options such as "top" or | |
754 | * "padx 20". | |
755 | */ | |
756 | ||
757 | for ( ; argc > 0; argc -= 2, argv += 2, prevPtr = packPtr) { | |
758 | if (argc < 2) { | |
759 | Tcl_AppendResult(interp, "wrong # args: window \"", | |
760 | argv[0], "\" should be followed by options", | |
761 | (char *) NULL); | |
762 | return TCL_ERROR; | |
763 | } | |
764 | ||
765 | /* | |
766 | * Find the packer for the window to be packed, and make sure | |
767 | * that the window in which it will be packed is its parent. | |
768 | */ | |
769 | ||
770 | tkwin = Tk_NameToWindow(interp, argv[0], parentPtr->tkwin); | |
771 | if (tkwin == NULL) { | |
772 | return TCL_ERROR; | |
773 | } | |
774 | if (Tk_Parent(tkwin) != parentPtr->tkwin) { | |
775 | Tcl_AppendResult(interp, "tried to pack \"", | |
776 | argv[0], "\" in window that isn't its parent", | |
777 | (char *) NULL); | |
778 | return TCL_ERROR; | |
779 | } | |
780 | packPtr = GetPacker(tkwin); | |
781 | ||
782 | /* | |
783 | * Process options for this window. | |
784 | */ | |
785 | ||
786 | if (Tcl_SplitList(interp, argv[1], &optionCount, &options) != TCL_OK) { | |
787 | return TCL_ERROR; | |
788 | } | |
789 | packPtr->side = TOP; | |
790 | packPtr->anchorPoint = TK_ANCHOR_CENTER; | |
791 | packPtr->padX = packPtr->padY = 0; | |
792 | packPtr->flags &= ~(FILLX|FILLY|EXPAND); | |
793 | for (index = 0 ; index < optionCount; index++) { | |
794 | char *curOpt = options[index]; | |
795 | ||
796 | c = curOpt[0]; | |
797 | length = strlen(curOpt); | |
798 | ||
799 | if ((c == 't') | |
800 | && (strncmp(curOpt, "top", length)) == 0) { | |
801 | packPtr->side = TOP; | |
802 | } else if ((c == 'b') | |
803 | && (strncmp(curOpt, "bottom", length)) == 0) { | |
804 | packPtr->side = BOTTOM; | |
805 | } else if ((c == 'l') | |
806 | && (strncmp(curOpt, "left", length)) == 0) { | |
807 | packPtr->side = LEFT; | |
808 | } else if ((c == 'r') | |
809 | && (strncmp(curOpt, "right", length)) == 0) { | |
810 | packPtr->side = RIGHT; | |
811 | } else if ((c == 'e') | |
812 | && (strncmp(curOpt, "expand", length)) == 0) { | |
813 | packPtr->flags |= EXPAND; | |
814 | } else if ((c == 'f') | |
815 | && (strcmp(curOpt, "fill")) == 0) { | |
816 | packPtr->flags |= FILLX|FILLY; | |
817 | } else if ((length == 5) && (strcmp(curOpt, "fillx")) == 0) { | |
818 | packPtr->flags |= FILLX; | |
819 | } else if ((length == 5) && (strcmp(curOpt, "filly")) == 0) { | |
820 | packPtr->flags |= FILLY; | |
821 | } else if ((c == 'p') && (strcmp(curOpt, "padx")) == 0) { | |
822 | if (optionCount < (index+2)) { | |
823 | missingPad: | |
824 | Tcl_AppendResult(interp, "wrong # args: \"", curOpt, | |
825 | "\" option must be followed by count", | |
826 | (char *) NULL); | |
827 | goto error; | |
828 | } | |
829 | if ((Tcl_GetInt(interp, options[index+1], &packPtr->padX) | |
830 | != TCL_OK) || (packPtr->padX < 0)) { | |
831 | badPad: | |
832 | Tcl_AppendResult(interp, "bad pad value \"", | |
833 | options[index+1], "\": must be positive integer", | |
834 | (char *) NULL); | |
835 | goto error; | |
836 | } | |
837 | index++; | |
838 | } else if ((c == 'p') && (strcmp(curOpt, "pady")) == 0) { | |
839 | if (optionCount < (index+2)) { | |
840 | goto missingPad; | |
841 | } | |
842 | if ((Tcl_GetInt(interp, options[index+1], &packPtr->padY) | |
843 | != TCL_OK) || (packPtr->padY < 0)) { | |
844 | goto badPad; | |
845 | } | |
846 | index++; | |
847 | } else if ((c == 'f') && (length > 1) | |
848 | && (strncmp(curOpt, "frame", length) == 0)) { | |
849 | if (optionCount < (index+2)) { | |
850 | Tcl_AppendResult(interp, "wrong # args: \"frame\" ", | |
851 | "option must be followed by anchor point", | |
852 | (char *) NULL); | |
853 | goto error; | |
854 | } | |
855 | if (Tk_GetAnchor(interp, options[index+1], | |
856 | &packPtr->anchorPoint) != TCL_OK) { | |
857 | goto error; | |
858 | } | |
859 | index++; | |
860 | } else { | |
861 | Tcl_AppendResult(interp, "bad option \"", curOpt, | |
862 | "\": should be top, bottom, left, right, ", | |
863 | "expand, fill, fillx, filly, padx, pady, or frame", | |
864 | (char *) NULL); | |
865 | goto error; | |
866 | } | |
867 | } | |
868 | ||
869 | if (packPtr != prevPtr) { | |
870 | ||
871 | /* | |
872 | * Unpack this window if it's currently packed. | |
873 | */ | |
874 | ||
875 | if (packPtr->parentPtr != NULL) { | |
876 | Unlink(packPtr); | |
877 | } | |
878 | ||
879 | /* | |
880 | * Add the window in the correct place in its parent's | |
881 | * packing order, then make sure that the window is | |
882 | * managed by us. | |
883 | */ | |
884 | ||
885 | packPtr->parentPtr = parentPtr; | |
886 | if (prevPtr == NULL) { | |
887 | packPtr->nextPtr = parentPtr->childPtr; | |
888 | parentPtr->childPtr = packPtr; | |
889 | } else { | |
890 | packPtr->nextPtr = prevPtr->nextPtr; | |
891 | prevPtr->nextPtr = packPtr; | |
892 | } | |
893 | Tk_ManageGeometry(tkwin, PackReqProc, (ClientData) packPtr); | |
894 | } | |
895 | ckfree((char *) options); | |
896 | } | |
897 | ||
898 | /* | |
899 | * Arrange for the parent to be re-packed at the first | |
900 | * idle moment. | |
901 | */ | |
902 | ||
903 | if (parentPtr->abortPtr != NULL) { | |
904 | *parentPtr->abortPtr = 1; | |
905 | } | |
906 | if (!(parentPtr->flags & REQUESTED_REPACK)) { | |
907 | parentPtr->flags |= REQUESTED_REPACK; | |
908 | Tk_DoWhenIdle(ArrangePacking, (ClientData) parentPtr); | |
909 | } | |
910 | return TCL_OK; | |
911 | ||
912 | error: | |
913 | ckfree((char *) options); | |
914 | return TCL_ERROR; | |
915 | } | |
916 | \f | |
917 | /* | |
918 | *---------------------------------------------------------------------- | |
919 | * | |
920 | * Unlink -- | |
921 | * | |
922 | * Remove a packer from its parent's list of children. | |
923 | * | |
924 | * Results: | |
925 | * None. | |
926 | * | |
927 | * Side effects: | |
928 | * The parent will be scheduled for repacking. | |
929 | * | |
930 | *---------------------------------------------------------------------- | |
931 | */ | |
932 | ||
933 | static void | |
934 | Unlink(packPtr) | |
935 | register Packer *packPtr; /* Window to unlink. */ | |
936 | { | |
937 | register Packer *parentPtr, *packPtr2; | |
938 | ||
939 | parentPtr = packPtr->parentPtr; | |
940 | if (parentPtr == NULL) { | |
941 | return; | |
942 | } | |
943 | if (parentPtr->childPtr == packPtr) { | |
944 | parentPtr->childPtr = packPtr->nextPtr; | |
945 | } else { | |
946 | for (packPtr2 = parentPtr->childPtr; ; packPtr2 = packPtr2->nextPtr) { | |
947 | if (packPtr2 == NULL) { | |
948 | panic("Unlink couldn't find previous window"); | |
949 | } | |
950 | if (packPtr2->nextPtr == packPtr) { | |
951 | packPtr2->nextPtr = packPtr->nextPtr; | |
952 | break; | |
953 | } | |
954 | } | |
955 | } | |
956 | if (!(parentPtr->flags & REQUESTED_REPACK)) { | |
957 | parentPtr->flags |= REQUESTED_REPACK; | |
958 | Tk_DoWhenIdle(ArrangePacking, (ClientData) parentPtr); | |
959 | } | |
960 | if (parentPtr->abortPtr != NULL) { | |
961 | *parentPtr->abortPtr = 1; | |
962 | } | |
963 | ||
964 | packPtr->parentPtr = NULL; | |
965 | } | |
966 | \f | |
967 | /* | |
968 | *---------------------------------------------------------------------- | |
969 | * | |
970 | * DestroyPacker -- | |
971 | * | |
972 | * This procedure is invoked by Tk_EventuallyFree or Tk_Release | |
973 | * to clean up the internal structure of a packer at a safe time | |
974 | * (when no-one is using it anymore). | |
975 | * | |
976 | * Results: | |
977 | * None. | |
978 | * | |
979 | * Side effects: | |
980 | * Everything associated with the packer is freed up. | |
981 | * | |
982 | *---------------------------------------------------------------------- | |
983 | */ | |
984 | ||
985 | static void | |
986 | DestroyPacker(clientData) | |
987 | ClientData clientData; /* Info about packed window that | |
988 | * is now dead. */ | |
989 | { | |
990 | register Packer *packPtr = (Packer *) clientData; | |
991 | ckfree((char *) packPtr); | |
992 | } | |
993 | \f | |
994 | /* | |
995 | *---------------------------------------------------------------------- | |
996 | * | |
997 | * PackStructureProc -- | |
998 | * | |
999 | * This procedure is invoked by the Tk event dispatcher in response | |
1000 | * to StructureNotify events. | |
1001 | * | |
1002 | * Results: | |
1003 | * None. | |
1004 | * | |
1005 | * Side effects: | |
1006 | * If a window was just deleted, clean up all its packer-related | |
1007 | * information. If it was just resized, repack its children, if | |
1008 | * any. | |
1009 | * | |
1010 | *---------------------------------------------------------------------- | |
1011 | */ | |
1012 | ||
1013 | static void | |
1014 | PackStructureProc(clientData, eventPtr) | |
1015 | ClientData clientData; /* Our information about window | |
1016 | * referred to by eventPtr. */ | |
1017 | XEvent *eventPtr; /* Describes what just happened. */ | |
1018 | { | |
1019 | register Packer *packPtr = (Packer *) clientData; | |
1020 | if (eventPtr->type == ConfigureNotify) { | |
1021 | if ((packPtr->childPtr != NULL) | |
1022 | && !(packPtr->flags & REQUESTED_REPACK)) { | |
1023 | packPtr->flags |= REQUESTED_REPACK; | |
1024 | Tk_DoWhenIdle(ArrangePacking, (ClientData) packPtr); | |
1025 | } | |
1026 | if (packPtr->doubleBw != 2*Tk_Changes(packPtr->tkwin)->border_width) { | |
1027 | if ((packPtr->parentPtr != NULL) | |
1028 | && !(packPtr->parentPtr->flags & REQUESTED_REPACK)) { | |
1029 | packPtr->doubleBw = 2*Tk_Changes(packPtr->tkwin)->border_width; | |
1030 | packPtr->parentPtr->flags |= REQUESTED_REPACK; | |
1031 | Tk_DoWhenIdle(ArrangePacking, (ClientData) packPtr->parentPtr); | |
1032 | } | |
1033 | } | |
1034 | } else if (eventPtr->type == DestroyNotify) { | |
1035 | register Packer *packPtr2; | |
1036 | ||
1037 | if (packPtr->parentPtr != NULL) { | |
1038 | Unlink(packPtr); | |
1039 | } | |
1040 | for (packPtr2 = packPtr->childPtr; packPtr2 != NULL; | |
1041 | packPtr2 = packPtr2->nextPtr) { | |
1042 | packPtr2->parentPtr = NULL; | |
1043 | packPtr2->nextPtr = NULL; | |
1044 | } | |
1045 | Tcl_DeleteHashEntry(Tcl_FindHashEntry(&packerHashTable, | |
1046 | (char *) packPtr->tkwin)); | |
1047 | if (packPtr->flags & REQUESTED_REPACK) { | |
1048 | Tk_CancelIdleCall(ArrangePacking, (ClientData) packPtr); | |
1049 | } | |
1050 | packPtr->tkwin = NULL; | |
1051 | Tk_EventuallyFree((ClientData) packPtr, DestroyPacker); | |
1052 | } | |
1053 | } |