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