1/* 2 * tkMenuDraw.c -- 3 * 4 * This module implements the platform-independent drawing and 5 * geometry calculations of menu widgets. 6 * 7 * Copyright (c) 1996-1997 by Sun Microsystems, Inc. 8 * 9 * See the file "license.terms" for information on usage and redistribution 10 * of this file, and for a DISCLAIMER OF ALL WARRANTIES. 11 * 12 * RCS: @(#) $Id: tkMenuDraw.c,v 1.3.20.2 2003/11/12 00:04:53 hobbs Exp $ 13 */ 14 15#include "tkMenu.h" 16 17/* 18 * Forward declarations for procedures defined later in this file: 19 */ 20 21static void AdjustMenuCoords _ANSI_ARGS_ ((TkMenu *menuPtr, 22 TkMenuEntry *mePtr, int *xPtr, int *yPtr, 23 char *string)); 24static void ComputeMenuGeometry _ANSI_ARGS_(( 25 ClientData clientData)); 26static void DisplayMenu _ANSI_ARGS_((ClientData clientData)); 27 28/* 29 *---------------------------------------------------------------------- 30 * 31 * TkMenuInitializeDrawingFields -- 32 * 33 * Fills in drawing fields of a new menu. Called when new menu is 34 * created by MenuCmd. 35 * 36 * Results: 37 * None. 38 * 39 * Side effects: 40 * menuPtr fields are initialized. 41 * 42 *---------------------------------------------------------------------- 43 */ 44 45void 46TkMenuInitializeDrawingFields(menuPtr) 47 TkMenu *menuPtr; /* The menu we are initializing. */ 48{ 49 menuPtr->textGC = None; 50 menuPtr->gray = None; 51 menuPtr->disabledGC = None; 52 menuPtr->activeGC = None; 53 menuPtr->indicatorGC = None; 54 menuPtr->disabledImageGC = None; 55 menuPtr->totalWidth = menuPtr->totalHeight = 0; 56} 57 58/* 59 *---------------------------------------------------------------------- 60 * 61 * TkMenuInitializeEntryDrawingFields -- 62 * 63 * Fills in drawing fields of a new menu entry. Called when an 64 * entry is created. 65 * 66 * Results: 67 * None. 68 * 69 * Side effects: 70 * None. 71 * 72 *---------------------------------------------------------------------- 73 */ 74 75void 76TkMenuInitializeEntryDrawingFields(mePtr) 77 TkMenuEntry *mePtr; /* The menu we are initializing. */ 78{ 79 mePtr->width = 0; 80 mePtr->height = 0; 81 mePtr->x = 0; 82 mePtr->y = 0; 83 mePtr->indicatorSpace = 0; 84 mePtr->labelWidth = 0; 85 mePtr->textGC = None; 86 mePtr->activeGC = None; 87 mePtr->disabledGC = None; 88 mePtr->indicatorGC = None; 89} 90 91/* 92 *---------------------------------------------------------------------- 93 * 94 * TkMenuFreeDrawOptions -- 95 * 96 * Frees up any structures allocated for the drawing of a menu. 97 * Called when menu is deleted. 98 * 99 * Results: 100 * None. 101 * 102 * Side effects: 103 * Storage is released. 104 * 105 *---------------------------------------------------------------------- 106 */ 107 108void 109TkMenuFreeDrawOptions(menuPtr) 110 TkMenu *menuPtr; 111{ 112 if (menuPtr->textGC != None) { 113 Tk_FreeGC(menuPtr->display, menuPtr->textGC); 114 } 115 if (menuPtr->disabledImageGC != None) { 116 Tk_FreeGC(menuPtr->display, menuPtr->disabledImageGC); 117 } 118 if (menuPtr->gray != None) { 119 Tk_FreeBitmap(menuPtr->display, menuPtr->gray); 120 } 121 if (menuPtr->disabledGC != None) { 122 Tk_FreeGC(menuPtr->display, menuPtr->disabledGC); 123 } 124 if (menuPtr->activeGC != None) { 125 Tk_FreeGC(menuPtr->display, menuPtr->activeGC); 126 } 127 if (menuPtr->indicatorGC != None) { 128 Tk_FreeGC(menuPtr->display, menuPtr->indicatorGC); 129 } 130} 131 132/* 133 *---------------------------------------------------------------------- 134 * 135 * TkMenuEntryFreeDrawOptions -- 136 * 137 * Frees up drawing structures for a menu entry. Called when 138 * menu entry is freed. 139 * 140 * RESULTS: 141 * None. 142 * 143 * Side effects: 144 * Storage is freed. 145 * 146 *---------------------------------------------------------------------- 147 */ 148 149void 150TkMenuEntryFreeDrawOptions(mePtr) 151 TkMenuEntry *mePtr; 152{ 153 if (mePtr->textGC != None) { 154 Tk_FreeGC(mePtr->menuPtr->display, mePtr->textGC); 155 } 156 if (mePtr->disabledGC != None) { 157 Tk_FreeGC(mePtr->menuPtr->display, mePtr->disabledGC); 158 } 159 if (mePtr->activeGC != None) { 160 Tk_FreeGC(mePtr->menuPtr->display, mePtr->activeGC); 161 } 162 if (mePtr->indicatorGC != None) { 163 Tk_FreeGC(mePtr->menuPtr->display, mePtr->indicatorGC); 164 } 165} 166 167/* 168 *---------------------------------------------------------------------- 169 * 170 * TkMenuConfigureDrawOptions -- 171 * 172 * Sets the menu's drawing attributes in preparation for drawing 173 * the menu. 174 * 175 * RESULTS: 176 * None. 177 * 178 * Side effects: 179 * Storage is allocated. 180 * 181 *---------------------------------------------------------------------- 182 */ 183 184void 185TkMenuConfigureDrawOptions(menuPtr) 186 TkMenu *menuPtr; /* The menu we are configuring. */ 187{ 188 XGCValues gcValues; 189 GC newGC; 190 unsigned long mask; 191 Tk_3DBorder border, activeBorder; 192 Tk_Font tkfont; 193 XColor *fg, *activeFg, *indicatorFg; 194 195 /* 196 * A few options need special processing, such as setting the 197 * background from a 3-D border, or filling in complicated 198 * defaults that couldn't be specified to Tk_ConfigureWidget. 199 */ 200 201 border = Tk_Get3DBorderFromObj(menuPtr->tkwin, menuPtr->borderPtr); 202 Tk_SetBackgroundFromBorder(menuPtr->tkwin, border); 203 204 tkfont = Tk_GetFontFromObj(menuPtr->tkwin, menuPtr->fontPtr); 205 gcValues.font = Tk_FontId(tkfont); 206 fg = Tk_GetColorFromObj(menuPtr->tkwin, menuPtr->fgPtr); 207 gcValues.foreground = fg->pixel; 208 gcValues.background = Tk_3DBorderColor(border)->pixel; 209 newGC = Tk_GetGC(menuPtr->tkwin, GCForeground|GCBackground|GCFont, 210 &gcValues); 211 if (menuPtr->textGC != None) { 212 Tk_FreeGC(menuPtr->display, menuPtr->textGC); 213 } 214 menuPtr->textGC = newGC; 215 216 gcValues.font = Tk_FontId(tkfont); 217 gcValues.background = Tk_3DBorderColor(border)->pixel; 218 if (menuPtr->disabledFgPtr != NULL) { 219 XColor *disabledFg; 220 221 disabledFg = Tk_GetColorFromObj(menuPtr->tkwin, 222 menuPtr->disabledFgPtr); 223 gcValues.foreground = disabledFg->pixel; 224 mask = GCForeground|GCBackground|GCFont; 225 } else { 226 gcValues.foreground = gcValues.background; 227 mask = GCForeground; 228 if (menuPtr->gray == None) { 229 menuPtr->gray = Tk_GetBitmap(menuPtr->interp, menuPtr->tkwin, 230 "gray50"); 231 } 232 if (menuPtr->gray != None) { 233 gcValues.fill_style = FillStippled; 234 gcValues.stipple = menuPtr->gray; 235 mask = GCForeground|GCFillStyle|GCStipple; 236 } 237 } 238 newGC = Tk_GetGC(menuPtr->tkwin, mask, &gcValues); 239 if (menuPtr->disabledGC != None) { 240 Tk_FreeGC(menuPtr->display, menuPtr->disabledGC); 241 } 242 menuPtr->disabledGC = newGC; 243 244 gcValues.foreground = Tk_3DBorderColor(border)->pixel; 245 if (menuPtr->gray == None) { 246 menuPtr->gray = Tk_GetBitmap(menuPtr->interp, menuPtr->tkwin, 247 "gray50"); 248 } 249 if (menuPtr->gray != None) { 250 gcValues.fill_style = FillStippled; 251 gcValues.stipple = menuPtr->gray; 252 newGC = Tk_GetGC(menuPtr->tkwin, 253 GCForeground|GCFillStyle|GCStipple, &gcValues); 254 } 255 if (menuPtr->disabledImageGC != None) { 256 Tk_FreeGC(menuPtr->display, menuPtr->disabledImageGC); 257 } 258 menuPtr->disabledImageGC = newGC; 259 260 gcValues.font = Tk_FontId(tkfont); 261 activeFg = Tk_GetColorFromObj(menuPtr->tkwin, menuPtr->activeFgPtr); 262 gcValues.foreground = activeFg->pixel; 263 activeBorder = Tk_Get3DBorderFromObj(menuPtr->tkwin, 264 menuPtr->activeBorderPtr); 265 gcValues.background = Tk_3DBorderColor(activeBorder)->pixel; 266 newGC = Tk_GetGC(menuPtr->tkwin, GCForeground|GCBackground|GCFont, 267 &gcValues); 268 if (menuPtr->activeGC != None) { 269 Tk_FreeGC(menuPtr->display, menuPtr->activeGC); 270 } 271 menuPtr->activeGC = newGC; 272 273 indicatorFg = Tk_GetColorFromObj(menuPtr->tkwin, 274 menuPtr->indicatorFgPtr); 275 gcValues.foreground = indicatorFg->pixel; 276 gcValues.background = Tk_3DBorderColor(border)->pixel; 277 newGC = Tk_GetGC(menuPtr->tkwin, GCForeground|GCBackground|GCFont, 278 &gcValues); 279 if (menuPtr->indicatorGC != None) { 280 Tk_FreeGC(menuPtr->display, menuPtr->indicatorGC); 281 } 282 menuPtr->indicatorGC = newGC; 283} 284 285/* 286 *---------------------------------------------------------------------- 287 * 288 * TkMenuConfigureEntryDrawOptions -- 289 * 290 * Calculates any entry-specific draw options for the given menu 291 * entry. 292 * 293 * Results: 294 * Returns a standard Tcl error. 295 * 296 * Side effects: 297 * Storage may be allocated. 298 * 299 *---------------------------------------------------------------------- 300 */ 301 302int 303TkMenuConfigureEntryDrawOptions(mePtr, index) 304 TkMenuEntry *mePtr; 305 int index; 306{ 307 308 XGCValues gcValues; 309 GC newGC, newActiveGC, newDisabledGC, newIndicatorGC; 310 unsigned long mask; 311 Tk_Font tkfont; 312 TkMenu *menuPtr = mePtr->menuPtr; 313 314 tkfont = Tk_GetFontFromObj(menuPtr->tkwin, 315 (mePtr->fontPtr != NULL) ? mePtr->fontPtr : menuPtr->fontPtr); 316 317 if (mePtr->state == ENTRY_ACTIVE) { 318 if (index != menuPtr->active) { 319 TkActivateMenuEntry(menuPtr, index); 320 } 321 } else { 322 if (index == menuPtr->active) { 323 TkActivateMenuEntry(menuPtr, -1); 324 } 325 } 326 327 if ((mePtr->fontPtr != NULL) 328 || (mePtr->borderPtr != NULL) 329 || (mePtr->fgPtr != NULL) 330 || (mePtr->activeBorderPtr != NULL) 331 || (mePtr->activeFgPtr != NULL) 332 || (mePtr->indicatorFgPtr != NULL)) { 333 XColor *fg, *indicatorFg, *activeFg; 334 Tk_3DBorder border, activeBorder; 335 336 fg = Tk_GetColorFromObj(menuPtr->tkwin, (mePtr->fgPtr != NULL) 337 ? mePtr->fgPtr : menuPtr->fgPtr); 338 gcValues.foreground = fg->pixel; 339 border = Tk_Get3DBorderFromObj(menuPtr->tkwin, 340 (mePtr->borderPtr != NULL) ? mePtr->borderPtr 341 : menuPtr->borderPtr); 342 gcValues.background = Tk_3DBorderColor(border)->pixel; 343 344 gcValues.font = Tk_FontId(tkfont); 345 346 /* 347 * Note: disable GraphicsExpose events; we know there won't be 348 * obscured areas when copying from an off-screen pixmap to the 349 * screen and this gets rid of unnecessary events. 350 */ 351 352 gcValues.graphics_exposures = False; 353 newGC = Tk_GetGC(menuPtr->tkwin, 354 GCForeground|GCBackground|GCFont|GCGraphicsExposures, 355 &gcValues); 356 357 indicatorFg = Tk_GetColorFromObj(menuPtr->tkwin, 358 (mePtr->indicatorFgPtr != NULL) ? mePtr->indicatorFgPtr 359 : menuPtr->indicatorFgPtr); 360 gcValues.foreground = indicatorFg->pixel; 361 newIndicatorGC = Tk_GetGC(menuPtr->tkwin, 362 GCForeground|GCBackground|GCGraphicsExposures, 363 &gcValues); 364 365 if ((menuPtr->disabledFgPtr != NULL) || (mePtr->image != NULL)) { 366 XColor *disabledFg; 367 368 disabledFg = Tk_GetColorFromObj(menuPtr->tkwin, 369 menuPtr->disabledFgPtr); 370 gcValues.foreground = disabledFg->pixel; 371 mask = GCForeground|GCBackground|GCFont|GCGraphicsExposures; 372 } else { 373 gcValues.foreground = gcValues.background; 374 gcValues.fill_style = FillStippled; 375 gcValues.stipple = menuPtr->gray; 376 mask = GCForeground|GCFillStyle|GCStipple; 377 } 378 newDisabledGC = Tk_GetGC(menuPtr->tkwin, mask, &gcValues); 379 380 activeFg = Tk_GetColorFromObj(menuPtr->tkwin, 381 (mePtr->activeFgPtr != NULL) ? mePtr->activeFgPtr 382 : menuPtr->activeFgPtr); 383 activeBorder = Tk_Get3DBorderFromObj(menuPtr->tkwin, 384 (mePtr->activeBorderPtr != NULL) ? mePtr->activeBorderPtr 385 : menuPtr->activeBorderPtr); 386 387 gcValues.foreground = activeFg->pixel; 388 gcValues.background = Tk_3DBorderColor(activeBorder)->pixel; 389 newActiveGC = Tk_GetGC(menuPtr->tkwin, 390 GCForeground|GCBackground|GCFont|GCGraphicsExposures, 391 &gcValues); 392 } else { 393 newGC = None; 394 newActiveGC = None; 395 newDisabledGC = None; 396 newIndicatorGC = None; 397 } 398 if (mePtr->textGC != None) { 399 Tk_FreeGC(menuPtr->display, mePtr->textGC); 400 } 401 mePtr->textGC = newGC; 402 if (mePtr->activeGC != None) { 403 Tk_FreeGC(menuPtr->display, mePtr->activeGC); 404 } 405 mePtr->activeGC = newActiveGC; 406 if (mePtr->disabledGC != None) { 407 Tk_FreeGC(menuPtr->display, mePtr->disabledGC); 408 } 409 mePtr->disabledGC = newDisabledGC; 410 if (mePtr->indicatorGC != None) { 411 Tk_FreeGC(menuPtr->display, mePtr->indicatorGC); 412 } 413 mePtr->indicatorGC = newIndicatorGC; 414 return TCL_OK; 415} 416 417/* 418 *---------------------------------------------------------------------- 419 * 420 * TkEventuallyRecomputeMenu -- 421 * 422 * Tells Tcl to redo the geometry because this menu has changed. 423 * 424 * Results: 425 * None. 426 * 427 * Side effects: 428 * Menu geometry is recomputed at idle time, and the menu will be 429 * redisplayed. 430 * 431 *---------------------------------------------------------------------- 432 */ 433 434void 435TkEventuallyRecomputeMenu(menuPtr) 436 TkMenu *menuPtr; 437{ 438 if (!(menuPtr->menuFlags & RESIZE_PENDING)) { 439 menuPtr->menuFlags |= RESIZE_PENDING; 440 Tcl_DoWhenIdle(ComputeMenuGeometry, (ClientData) menuPtr); 441 } 442} 443 444/* 445 *---------------------------------------------------------------------- 446 * 447 * TkRecomputeMenu -- 448 * 449 * Tells Tcl to redo the geometry because this menu has changed. 450 * Does it now; removes any ComputeMenuGeometries from the idler. 451 * 452 * Results: 453 * None. 454 * 455 * Side effects: 456 * Menu geometry is immediately reconfigured. 457 * 458 *---------------------------------------------------------------------- 459 */ 460 461void 462TkRecomputeMenu(menuPtr) 463 TkMenu *menuPtr; 464{ 465 if (menuPtr->menuFlags & RESIZE_PENDING) { 466 Tcl_CancelIdleCall(ComputeMenuGeometry, (ClientData) menuPtr); 467 ComputeMenuGeometry((ClientData) menuPtr); 468 } 469} 470 471/* 472 *---------------------------------------------------------------------- 473 * 474 * TkEventuallyRedrawMenu -- 475 * 476 * Arrange for an entry of a menu, or the whole menu, to be 477 * redisplayed at some point in the future. 478 * 479 * Results: 480 * None. 481 * 482 * Side effects: 483 * A when-idle hander is scheduled to do the redisplay, if there 484 * isn't one already scheduled. 485 * 486 *---------------------------------------------------------------------- 487 */ 488 489void 490TkEventuallyRedrawMenu(menuPtr, mePtr) 491 register TkMenu *menuPtr; /* Information about menu to redraw. */ 492 register TkMenuEntry *mePtr;/* Entry to redraw. NULL means redraw 493 * all the entries in the menu. */ 494{ 495 int i; 496 497 if (menuPtr->tkwin == NULL) { 498 return; 499 } 500 if (mePtr != NULL) { 501 mePtr->entryFlags |= ENTRY_NEEDS_REDISPLAY; 502 } else { 503 for (i = 0; i < menuPtr->numEntries; i++) { 504 menuPtr->entries[i]->entryFlags |= ENTRY_NEEDS_REDISPLAY; 505 } 506 } 507 if (!Tk_IsMapped(menuPtr->tkwin) 508 || (menuPtr->menuFlags & REDRAW_PENDING)) { 509 return; 510 } 511 Tcl_DoWhenIdle(DisplayMenu, (ClientData) menuPtr); 512 menuPtr->menuFlags |= REDRAW_PENDING; 513} 514 515/* 516 *-------------------------------------------------------------- 517 * 518 * ComputeMenuGeometry -- 519 * 520 * This procedure is invoked to recompute the size and 521 * layout of a menu. It is called as a when-idle handler so 522 * that it only gets done once, even if a group of changes is 523 * made to the menu. 524 * 525 * Results: 526 * None. 527 * 528 * Side effects: 529 * Fields of menu entries are changed to reflect their 530 * current positions, and the size of the menu window 531 * itself may be changed. 532 * 533 *-------------------------------------------------------------- 534 */ 535 536static void 537ComputeMenuGeometry(clientData) 538 ClientData clientData; /* Structure describing menu. */ 539{ 540 TkMenu *menuPtr = (TkMenu *) clientData; 541 542 if (menuPtr->tkwin == NULL) { 543 return; 544 } 545 546 if (menuPtr->menuType == MENUBAR) { 547 TkpComputeMenubarGeometry(menuPtr); 548 } else { 549 TkpComputeStandardMenuGeometry(menuPtr); 550 } 551 552 if ((menuPtr->totalWidth != Tk_ReqWidth(menuPtr->tkwin)) || 553 (menuPtr->totalHeight != Tk_ReqHeight(menuPtr->tkwin))) { 554 Tk_GeometryRequest(menuPtr->tkwin, menuPtr->totalWidth, 555 menuPtr->totalHeight); 556 } 557 558 /* 559 * Must always force a redisplay here if the window is mapped 560 * (even if the size didn't change, something else might have 561 * changed in the menu, such as a label or accelerator). The 562 * resize will force a redisplay above. 563 */ 564 565 TkEventuallyRedrawMenu(menuPtr, (TkMenuEntry *) NULL); 566 567 menuPtr->menuFlags &= ~RESIZE_PENDING; 568} 569 570/* 571 *---------------------------------------------------------------------- 572 * 573 * TkMenuSelectImageProc -- 574 * 575 * This procedure is invoked by the image code whenever the manager 576 * for an image does something that affects the size of contents 577 * of an image displayed in a menu entry when it is selected. 578 * 579 * Results: 580 * None. 581 * 582 * Side effects: 583 * Arranges for the menu to get redisplayed. 584 * 585 *---------------------------------------------------------------------- 586 */ 587 588void 589TkMenuSelectImageProc(clientData, x, y, width, height, imgWidth, 590 imgHeight) 591 ClientData clientData; /* Pointer to widget record. */ 592 int x, y; /* Upper left pixel (within image) 593 * that must be redisplayed. */ 594 int width, height; /* Dimensions of area to redisplay 595 * (may be <= 0). */ 596 int imgWidth, imgHeight; /* New dimensions of image. */ 597{ 598 register TkMenuEntry *mePtr = (TkMenuEntry *) clientData; 599 600 if ((mePtr->entryFlags & ENTRY_SELECTED) 601 && !(mePtr->menuPtr->menuFlags & 602 REDRAW_PENDING)) { 603 mePtr->menuPtr->menuFlags |= REDRAW_PENDING; 604 Tcl_DoWhenIdle(DisplayMenu, (ClientData) mePtr->menuPtr); 605 } 606} 607 608/* 609 *---------------------------------------------------------------------- 610 * 611 * DisplayMenu -- 612 * 613 * This procedure is invoked to display a menu widget. 614 * 615 * Results: 616 * None. 617 * 618 * Side effects: 619 * Commands are output to X to display the menu in its 620 * current mode. 621 * 622 *---------------------------------------------------------------------- 623 */ 624 625static void 626DisplayMenu(clientData) 627 ClientData clientData; /* Information about widget. */ 628{ 629 register TkMenu *menuPtr = (TkMenu *) clientData; 630 register TkMenuEntry *mePtr; 631 register Tk_Window tkwin = menuPtr->tkwin; 632 int index, strictMotif; 633 Tk_Font tkfont; 634 Tk_FontMetrics menuMetrics; 635 int width; 636 int borderWidth; 637 Tk_3DBorder border; 638 int activeBorderWidth; 639 int relief; 640 641 642 menuPtr->menuFlags &= ~REDRAW_PENDING; 643 if ((menuPtr->tkwin == NULL) || !Tk_IsMapped(tkwin)) { 644 return; 645 } 646 647 Tk_GetPixelsFromObj(NULL, menuPtr->tkwin, menuPtr->borderWidthPtr, 648 &borderWidth); 649 border = Tk_Get3DBorderFromObj(menuPtr->tkwin, menuPtr->borderPtr); 650 Tk_GetPixelsFromObj(NULL, menuPtr->tkwin, 651 menuPtr->activeBorderWidthPtr, &activeBorderWidth); 652 653 if (menuPtr->menuType == MENUBAR) { 654 Tk_Fill3DRectangle(tkwin, Tk_WindowId(tkwin), border, borderWidth, 655 borderWidth, Tk_Width(tkwin) - 2 * borderWidth, 656 Tk_Height(tkwin) - 2 * borderWidth, 0, TK_RELIEF_FLAT); 657 } 658 659 strictMotif = Tk_StrictMotif(menuPtr->tkwin); 660 661 /* 662 * See note in ComputeMenuGeometry. We don't want to be doing font metrics 663 * all of the time. 664 */ 665 666 tkfont = Tk_GetFontFromObj(menuPtr->tkwin, menuPtr->fontPtr); 667 Tk_GetFontMetrics(tkfont, &menuMetrics); 668 669 /* 670 * Loop through all of the entries, drawing them one at a time. 671 */ 672 673 for (index = 0; index < menuPtr->numEntries; index++) { 674 mePtr = menuPtr->entries[index]; 675 if (menuPtr->menuType != MENUBAR) { 676 if (!(mePtr->entryFlags & ENTRY_NEEDS_REDISPLAY)) { 677 continue; 678 } 679 } 680 mePtr->entryFlags &= ~ENTRY_NEEDS_REDISPLAY; 681 682 if (menuPtr->menuType == MENUBAR) { 683 width = mePtr->width; 684 } else { 685 if (mePtr->entryFlags & ENTRY_LAST_COLUMN) { 686 width = Tk_Width(menuPtr->tkwin) - mePtr->x 687 - activeBorderWidth; 688 } else { 689 width = mePtr->width + borderWidth; 690 } 691 } 692 TkpDrawMenuEntry(mePtr, Tk_WindowId(menuPtr->tkwin), tkfont, 693 &menuMetrics, mePtr->x, mePtr->y, width, 694 mePtr->height, strictMotif, 1); 695 if ((index > 0) && (menuPtr->menuType != MENUBAR) 696 && mePtr->columnBreak) { 697 mePtr = menuPtr->entries[index - 1]; 698 Tk_Fill3DRectangle(tkwin, Tk_WindowId(tkwin), border, 699 mePtr->x, mePtr->y + mePtr->height, 700 mePtr->width, 701 Tk_Height(tkwin) - mePtr->y - mePtr->height - 702 activeBorderWidth, 0, 703 TK_RELIEF_FLAT); 704 } 705 } 706 707 if (menuPtr->menuType != MENUBAR) { 708 int x, y, height; 709 710 if (menuPtr->numEntries == 0) { 711 x = y = borderWidth; 712 width = Tk_Width(tkwin) - 2 * activeBorderWidth; 713 height = Tk_Height(tkwin) - 2 * activeBorderWidth; 714 } else { 715 mePtr = menuPtr->entries[menuPtr->numEntries - 1]; 716 Tk_Fill3DRectangle(tkwin, Tk_WindowId(tkwin), 717 border, mePtr->x, mePtr->y + mePtr->height, mePtr->width, 718 Tk_Height(tkwin) - mePtr->y - mePtr->height 719 - activeBorderWidth, 0, 720 TK_RELIEF_FLAT); 721 x = mePtr->x + mePtr->width; 722 y = mePtr->y + mePtr->height; 723 width = Tk_Width(tkwin) - x - activeBorderWidth; 724 height = Tk_Height(tkwin) - y - activeBorderWidth; 725 } 726 Tk_Fill3DRectangle(tkwin, Tk_WindowId(tkwin), border, x, y, 727 width, height, 0, TK_RELIEF_FLAT); 728 } 729 730 Tk_GetReliefFromObj(NULL, menuPtr->reliefPtr, &relief); 731 Tk_Draw3DRectangle(menuPtr->tkwin, Tk_WindowId(tkwin), 732 border, 0, 0, Tk_Width(tkwin), Tk_Height(tkwin), borderWidth, 733 relief); 734} 735 736/* 737 *-------------------------------------------------------------- 738 * 739 * TkMenuEventProc -- 740 * 741 * This procedure is invoked by the Tk dispatcher for various 742 * events on menus. 743 * 744 * Results: 745 * None. 746 * 747 * Side effects: 748 * When the window gets deleted, internal structures get 749 * cleaned up. When it gets exposed, it is redisplayed. 750 * 751 *-------------------------------------------------------------- 752 */ 753 754void 755TkMenuEventProc(clientData, eventPtr) 756 ClientData clientData; /* Information about window. */ 757 XEvent *eventPtr; /* Information about event. */ 758{ 759 TkMenu *menuPtr = (TkMenu *) clientData; 760 761 if ((eventPtr->type == Expose) && (eventPtr->xexpose.count == 0)) { 762 TkEventuallyRedrawMenu(menuPtr, (TkMenuEntry *) NULL); 763 } else if (eventPtr->type == ConfigureNotify) { 764 TkEventuallyRecomputeMenu(menuPtr); 765 TkEventuallyRedrawMenu(menuPtr, (TkMenuEntry *) NULL); 766 } else if (eventPtr->type == ActivateNotify) { 767 if (menuPtr->menuType == TEAROFF_MENU) { 768 TkpSetMainMenubar(menuPtr->interp, menuPtr->tkwin, NULL); 769 } 770 } else if (eventPtr->type == DestroyNotify) { 771 if (menuPtr->tkwin != NULL) { 772 if (!(menuPtr->menuFlags & MENU_DELETION_PENDING)) { 773 TkDestroyMenu(menuPtr); 774 } 775 menuPtr->tkwin = NULL; 776 } 777 if (menuPtr->menuFlags & MENU_WIN_DESTRUCTION_PENDING) { 778 return; 779 } 780 menuPtr->menuFlags |= MENU_WIN_DESTRUCTION_PENDING; 781 if (menuPtr->widgetCmd != NULL) { 782 Tcl_DeleteCommandFromToken(menuPtr->interp, menuPtr->widgetCmd); 783 menuPtr->widgetCmd = NULL; 784 } 785 if (menuPtr->menuFlags & REDRAW_PENDING) { 786 Tcl_CancelIdleCall(DisplayMenu, (ClientData) menuPtr); 787 menuPtr->menuFlags &= ~REDRAW_PENDING; 788 } 789 if (menuPtr->menuFlags & RESIZE_PENDING) { 790 Tcl_CancelIdleCall(ComputeMenuGeometry, (ClientData) menuPtr); 791 menuPtr->menuFlags &= ~RESIZE_PENDING; 792 } 793 Tcl_EventuallyFree((ClientData) menuPtr, TCL_DYNAMIC); 794 } 795} 796 797/* 798 *---------------------------------------------------------------------- 799 * 800 * TkMenuImageProc -- 801 * 802 * This procedure is invoked by the image code whenever the manager 803 * for an image does something that affects the size of contents 804 * of an image displayed in a menu entry. 805 * 806 * Results: 807 * None. 808 * 809 * Side effects: 810 * Arranges for the menu to get redisplayed. 811 * 812 *---------------------------------------------------------------------- 813 */ 814 815void 816TkMenuImageProc(clientData, x, y, width, height, imgWidth, 817 imgHeight) 818 ClientData clientData; /* Pointer to widget record. */ 819 int x, y; /* Upper left pixel (within image) 820 * that must be redisplayed. */ 821 int width, height; /* Dimensions of area to redisplay 822 * (may be <= 0). */ 823 int imgWidth, imgHeight; /* New dimensions of image. */ 824{ 825 register TkMenu *menuPtr = ((TkMenuEntry *)clientData)->menuPtr; 826 827 if ((menuPtr->tkwin != NULL) && !(menuPtr->menuFlags 828 & RESIZE_PENDING)) { 829 menuPtr->menuFlags |= RESIZE_PENDING; 830 Tcl_DoWhenIdle(ComputeMenuGeometry, (ClientData) menuPtr); 831 } 832} 833 834/* 835 *---------------------------------------------------------------------- 836 * 837 * TkPostTearoffMenu -- 838 * 839 * Posts a menu on the screen. Used to post tearoff menus. On Unix, 840 * all menus are posted this way. Adjusts the menu's position 841 * so that it fits on the screen, and maps and raises the menu. 842 * 843 * Results: 844 * Returns a standard Tcl Error. 845 * 846 * Side effects: 847 * The menu is posted. 848 * 849 *---------------------------------------------------------------------- 850 */ 851 852int 853TkPostTearoffMenu(interp, menuPtr, x, y) 854 Tcl_Interp *interp; /* The interpreter of the menu */ 855 TkMenu *menuPtr; /* The menu we are posting */ 856 int x; /* The root X coordinate where we 857 * are posting */ 858 int y; /* The root Y coordinate where we 859 * are posting */ 860{ 861 int vRootX, vRootY, vRootWidth, vRootHeight; 862 int tmp, result; 863 864 TkActivateMenuEntry(menuPtr, -1); 865 TkRecomputeMenu(menuPtr); 866 result = TkPostCommand(menuPtr); 867 if (result != TCL_OK) { 868 return result; 869 } 870 871 /* 872 * The post commands could have deleted the menu, which means 873 * we are dead and should go away. 874 */ 875 876 if (menuPtr->tkwin == NULL) { 877 return TCL_OK; 878 } 879 880 /* 881 * Adjust the position of the menu if necessary to keep it 882 * visible on the screen. There are two special tricks to 883 * make this work right: 884 * 885 * 1. If a virtual root window manager is being used then 886 * the coordinates are in the virtual root window of 887 * menuPtr's parent; since the menu uses override-redirect 888 * mode it will be in the *real* root window for the screen, 889 * so we have to map the coordinates from the virtual root 890 * (if any) to the real root. Can't get the virtual root 891 * from the menu itself (it will never be seen by the wm) 892 * so use its parent instead (it would be better to have an 893 * an option that names a window to use for this...). 894 * 2. The menu may not have been mapped yet, so its current size 895 * might be the default 1x1. To compute how much space it 896 * needs, use its requested size, not its actual size. 897 * 898 * Note that this code assumes square screen regions and all 899 * positive coordinates. This does not work on a Mac with 900 * multiple monitors. But then again, Tk has other problems 901 * with this. 902 */ 903 904 Tk_GetVRootGeometry(Tk_Parent(menuPtr->tkwin), &vRootX, &vRootY, 905 &vRootWidth, &vRootHeight); 906 x += vRootX; 907 y += vRootY; 908 tmp = WidthOfScreen(Tk_Screen(menuPtr->tkwin)) 909 - Tk_ReqWidth(menuPtr->tkwin); 910 if (x > tmp) { 911 x = tmp; 912 } 913 if (x < 0) { 914 x = 0; 915 } 916 tmp = HeightOfScreen(Tk_Screen(menuPtr->tkwin)) 917 - Tk_ReqHeight(menuPtr->tkwin); 918 if (y > tmp) { 919 y = tmp; 920 } 921 if (y < 0) { 922 y = 0; 923 } 924 Tk_MoveToplevelWindow(menuPtr->tkwin, x, y); 925 if (!Tk_IsMapped(menuPtr->tkwin)) { 926 Tk_MapWindow(menuPtr->tkwin); 927 } 928 TkWmRestackToplevel((TkWindow *) menuPtr->tkwin, Above, NULL); 929 return TCL_OK; 930} 931 932/* 933 *-------------------------------------------------------------- 934 * 935 * TkPostSubmenu -- 936 * 937 * This procedure arranges for a particular submenu (i.e. the 938 * menu corresponding to a given cascade entry) to be 939 * posted. 940 * 941 * Results: 942 * A standard Tcl return result. Errors may occur in the 943 * Tcl commands generated to post and unpost submenus. 944 * 945 * Side effects: 946 * If there is already a submenu posted, it is unposted. 947 * The new submenu is then posted. 948 * 949 *-------------------------------------------------------------- 950 */ 951 952int 953TkPostSubmenu(interp, menuPtr, mePtr) 954 Tcl_Interp *interp; /* Used for invoking sub-commands and 955 * reporting errors. */ 956 register TkMenu *menuPtr; /* Information about menu as a whole. */ 957 register TkMenuEntry *mePtr; /* Info about submenu that is to be 958 * posted. NULL means make sure that 959 * no submenu is posted. */ 960{ 961 int result, x, y; 962 963 if (mePtr == menuPtr->postedCascade) { 964 return TCL_OK; 965 } 966 967 if (menuPtr->postedCascade != NULL) { 968 char *name = Tcl_GetStringFromObj(menuPtr->postedCascade->namePtr, 969 NULL); 970 971 /* 972 * Note: when unposting a submenu, we have to redraw the entire 973 * parent menu. This is because of a combination of the following 974 * things: 975 * (a) the submenu partially overlaps the parent. 976 * (b) the submenu specifies "save under", which causes the X 977 * server to make a copy of the information under it when it 978 * is posted. When the submenu is unposted, the X server 979 * copies this data back and doesn't generate any Expose 980 * events for the parent. 981 * (c) the parent may have redisplayed itself after the submenu 982 * was posted, in which case the saved information is no 983 * longer correct. 984 * The simplest solution is just force a complete redisplay of 985 * the parent. 986 */ 987 988 TkEventuallyRedrawMenu(menuPtr, (TkMenuEntry *) NULL); 989 result = Tcl_VarEval(interp, "{", name, "} unpost", (char *) NULL); 990 menuPtr->postedCascade = NULL; 991 if (result != TCL_OK) { 992 return result; 993 } 994 } 995 996 if ((mePtr != NULL) && (mePtr->namePtr != NULL) 997 && Tk_IsMapped(menuPtr->tkwin)) { 998 /* 999 * Position the cascade with its upper left corner slightly 1000 * below and to the left of the upper right corner of the 1001 * menu entry (this is an attempt to match Motif behavior). 1002 * 1003 * The menu has to redrawn so that the entry can change relief. 1004 */ 1005 1006 char string[TCL_INTEGER_SPACE * 2]; 1007 char *name; 1008 1009 name = Tcl_GetStringFromObj(mePtr->namePtr, NULL); 1010 Tk_GetRootCoords(menuPtr->tkwin, &x, &y); 1011 AdjustMenuCoords(menuPtr, mePtr, &x, &y, string); 1012 menuPtr->postedCascade = mePtr; 1013 result = Tcl_VarEval(interp, "{", name, "} post ", string, (char *) NULL); 1014 if (result != TCL_OK) { 1015 menuPtr->postedCascade = NULL; 1016 return result; 1017 } 1018 TkEventuallyRedrawMenu(menuPtr, mePtr); 1019 } 1020 return TCL_OK; 1021} 1022 1023/* 1024 *---------------------------------------------------------------------- 1025 * 1026 * AdjustMenuCoords -- 1027 * 1028 * Adjusts the given coordinates down and the left to give a Motif 1029 * look. 1030 * 1031 * Results: 1032 * None. 1033 * 1034 * Side effects: 1035 * The menu is eventually redrawn if necessary. 1036 * 1037 *---------------------------------------------------------------------- 1038 */ 1039 1040static void 1041AdjustMenuCoords(menuPtr, mePtr, xPtr, yPtr, string) 1042 TkMenu *menuPtr; 1043 TkMenuEntry *mePtr; 1044 int *xPtr; 1045 int *yPtr; 1046 char *string; 1047{ 1048 if (menuPtr->menuType == MENUBAR) { 1049 *xPtr += mePtr->x; 1050 *yPtr += mePtr->y + mePtr->height; 1051 } else { 1052 int borderWidth, activeBorderWidth; 1053 1054 Tk_GetPixelsFromObj(NULL, menuPtr->tkwin, menuPtr->borderWidthPtr, 1055 &borderWidth); 1056 Tk_GetPixelsFromObj(NULL, menuPtr->tkwin, 1057 menuPtr->activeBorderWidthPtr, &activeBorderWidth); 1058 *xPtr += Tk_Width(menuPtr->tkwin) - borderWidth - activeBorderWidth 1059 - 2; 1060 *yPtr += mePtr->y + activeBorderWidth + 2; 1061 } 1062 sprintf(string, "%d %d", *xPtr, *yPtr); 1063} 1064