1/* 2 * tkMacOSXButton.c -- 3 * 4 * This file implements the Macintosh specific portion of the 5 * button widgets. 6 * 7 * Copyright (c) 1996-1997 by Sun Microsystems, Inc. 8 * Copyright 2001-2009, Apple Inc. 9 * Copyright (c) 2006-2009 Daniel A. Steffen <das@users.sourceforge.net> 10 * 11 * See the file "license.terms" for information on usage and redistribution 12 * of this file, and for a DISCLAIMER OF ALL WARRANTIES. 13 * 14 * RCS: @(#) $Id$ 15 */ 16 17#include "tkMacOSXPrivate.h" 18#include "tkButton.h" 19#include "tkMacOSXFont.h" 20#include "tkMacOSXDebug.h" 21 22/* 23#ifdef TK_MAC_DEBUG 24#define TK_MAC_DEBUG_BUTTON 25#endif 26*/ 27 28typedef struct MacButton { 29 TkButton info; 30 NSButton *button; 31 NSImage *image, *selectImage, *tristateImage; 32#if TK_MAC_BUTTON_USE_COMPATIBILITY_METRICS 33 int fix; 34#endif 35} MacButton; 36 37#if TK_MAC_BUTTON_USE_COMPATIBILITY_METRICS 38 39int tkMacOSXUseCompatibilityMetrics = 1; 40 41/* 42 * Use the following heuristic conversion constants to make NSButton-based 43 * widget metrics match up with the old Carbon control buttons (for the 44 * default Lucida Grande 13 font). 45 */ 46 47#define NATIVE_BUTTON_INSET 2 48#define NATIVE_BUTTON_EXTRA_H 2 49 50typedef struct { 51 int trimW, trimH, inset, shrinkH, offsetX, offsetY; 52} BoundsFix; 53 54#define fixForTypeStyle(type, style) ( \ 55 type == NSSwitchButton ? 0 : \ 56 type == NSRadioButton ? 1 : \ 57 style == NSRoundedBezelStyle ? 2 : \ 58 style == NSRegularSquareBezelStyle ? 3 : \ 59 style == NSShadowlessSquareBezelStyle ? 4 : \ 60 INT_MIN) 61 62static const BoundsFix boundsFixes[] = { 63 [fixForTypeStyle(NSSwitchButton,0)] = { 2, 2, -1, 0, 2, 1 }, 64 [fixForTypeStyle(NSRadioButton,0)] = { 0, 2, -1, 0, 1, 1 }, 65 [fixForTypeStyle(0,NSRoundedBezelStyle)] = { 28, 16, -6, 0, 0, 3 }, 66 [fixForTypeStyle(0,NSRegularSquareBezelStyle)] = { 28, 15, -2, -1 }, 67 [fixForTypeStyle(0,NSShadowlessSquareBezelStyle)] = { 2, 2 }, 68}; 69 70#endif 71 72static void DisplayNativeButton(TkButton *butPtr); 73static void ComputeNativeButtonGeometry(TkButton *butPtr); 74static void DisplayUnixButton(TkButton *butPtr); 75static void ComputeUnixButtonGeometry(TkButton *butPtr); 76 77/* 78 * The class procedure table for the button widgets. 79 */ 80 81Tk_ClassProcs tkpButtonProcs = { 82 sizeof(Tk_ClassProcs), /* size */ 83 TkButtonWorldChanged, /* worldChangedProc */ 84}; 85 86 87/* 88 *---------------------------------------------------------------------- 89 * 90 * TkpCreateButton -- 91 * 92 * Allocate a new TkButton structure. 93 * 94 * Results: 95 * Returns a newly allocated TkButton structure. 96 * 97 * Side effects: 98 * Registers an event handler for the widget. 99 * 100 *---------------------------------------------------------------------- 101 */ 102 103TkButton * 104TkpCreateButton( 105 Tk_Window tkwin) 106{ 107 MacButton *macButtonPtr = (MacButton *) ckalloc(sizeof(MacButton)); 108 109 macButtonPtr->button = nil; 110 macButtonPtr->image = nil; 111 macButtonPtr->selectImage = nil; 112 macButtonPtr->tristateImage = nil; 113 114 return (TkButton *) macButtonPtr; 115} 116 117/* 118 *---------------------------------------------------------------------- 119 * 120 * TkpDestroyButton -- 121 * 122 * Free data structures associated with the button control. 123 * 124 * Results: 125 * None. 126 * 127 * Side effects: 128 * Restores the default control state. 129 * 130 *---------------------------------------------------------------------- 131 */ 132 133void 134TkpDestroyButton( 135 TkButton *butPtr) 136{ 137 MacButton *macButtonPtr = (MacButton *) butPtr; 138 139 TkMacOSXMakeCollectableAndRelease(macButtonPtr->button); 140 TkMacOSXMakeCollectableAndRelease(macButtonPtr->selectImage); 141 TkMacOSXMakeCollectableAndRelease(macButtonPtr->selectImage); 142 TkMacOSXMakeCollectableAndRelease(macButtonPtr->tristateImage); 143} 144 145/* 146 *---------------------------------------------------------------------- 147 * 148 * TkpDisplayButton -- 149 * 150 * This procedure is invoked to display a button widget. It is 151 * normally invoked as an idle handler. 152 * 153 * Results: 154 * None. 155 * 156 * Side effects: 157 * Commands are output to X to display the button in its 158 * current mode. The REDRAW_PENDING flag is cleared. 159 * 160 *---------------------------------------------------------------------- 161 */ 162 163void 164TkpDisplayButton( 165 ClientData clientData) /* Information about widget. */ 166{ 167 TkButton *butPtr = (TkButton *) clientData; 168 169 butPtr->flags &= ~REDRAW_PENDING; 170 if (!butPtr->tkwin || !Tk_IsMapped(butPtr->tkwin)) { 171 return; 172 } 173 174 switch (butPtr->type) { 175 case TYPE_LABEL: 176 DisplayUnixButton(butPtr); 177 break; 178 case TYPE_BUTTON: 179 case TYPE_CHECK_BUTTON: 180 case TYPE_RADIO_BUTTON: 181 DisplayNativeButton(butPtr); 182 break; 183 } 184} 185 186/* 187 *---------------------------------------------------------------------- 188 * 189 * TkpComputeButtonGeometry -- 190 * 191 * After changes in a button's text or bitmap, this procedure 192 * recomputes the button's geometry and passes this information 193 * along to the geometry manager for the window. 194 * 195 * Results: 196 * None. 197 * 198 * Side effects: 199 * The button's window may change size. 200 * 201 *---------------------------------------------------------------------- 202 */ 203 204void 205TkpComputeButtonGeometry( 206 register TkButton *butPtr) /* Button whose geometry may have changed. */ 207{ 208 MacButton *macButtonPtr = (MacButton *) butPtr; 209 210 switch (butPtr->type) { 211 case TYPE_LABEL: 212 if (macButtonPtr->button && [macButtonPtr->button superview]) { 213 [macButtonPtr->button removeFromSuperviewWithoutNeedingDisplay]; 214 } 215 ComputeUnixButtonGeometry(butPtr); 216 break; 217 case TYPE_BUTTON: 218 case TYPE_CHECK_BUTTON: 219 case TYPE_RADIO_BUTTON: 220 if (!macButtonPtr->button) { 221 NSButton *button = [[NSButton alloc] initWithFrame:NSZeroRect]; 222 macButtonPtr->button = TkMacOSXMakeUncollectable(button); 223 } 224 ComputeNativeButtonGeometry(butPtr); 225 break; 226 } 227} 228 229/* 230 *---------------------------------------------------------------------- 231 * 232 * TkpButtonSetDefaults -- 233 * 234 * This procedure is invoked before option tables are created for 235 * buttons. It modifies some of the default values to match the current 236 * values defined for this platform. 237 * 238 * Results: 239 * Some of the default values in *specPtr are modified. 240 * 241 * Side effects: 242 * Updates some of. 243 * 244 *---------------------------------------------------------------------- 245 */ 246 247void 248TkpButtonSetDefaults( 249 Tk_OptionSpec *specPtr) /* Points to an array of option specs, 250 * terminated by one with type 251 * TK_OPTION_END. */ 252{ 253#if TK_MAC_BUTTON_USE_COMPATIBILITY_METRICS 254 if (!tkMacOSXUseCompatibilityMetrics) { 255 while (specPtr->type != TK_CONFIG_END) { 256 switch (specPtr->internalOffset) { 257 case Tk_Offset(TkButton, highlightWidth): 258 specPtr->defValue = DEF_BUTTON_HIGHLIGHT_WIDTH_NOCM; 259 break; 260 case Tk_Offset(TkButton, padX): 261 specPtr->defValue = DEF_BUTTON_PADX_NOCM; 262 break; 263 case Tk_Offset(TkButton, padY): 264 specPtr->defValue = DEF_BUTTON_PADY_NOCM; 265 break; 266 } 267 specPtr++; 268 } 269 } 270#endif 271} 272 273#pragma mark - 274#pragma mark Native Buttons: 275 276 277/* 278 *---------------------------------------------------------------------- 279 * 280 * DisplayNativeButton -- 281 * 282 * This procedure is invoked to display a button widget. It is 283 * normally invoked as an idle handler. 284 * 285 * Results: 286 * None. 287 * 288 * Side effects: 289 * Commands are output to X to display the button in its 290 * current mode. The REDRAW_PENDING flag is cleared. 291 * 292 *---------------------------------------------------------------------- 293 */ 294 295static void 296DisplayNativeButton( 297 TkButton *butPtr) 298{ 299 MacButton *macButtonPtr = (MacButton *) butPtr; 300 NSButton *button = macButtonPtr->button; 301 Tk_Window tkwin = butPtr->tkwin; 302 TkWindow *winPtr = (TkWindow *) tkwin; 303 MacDrawable *macWin = (MacDrawable *) winPtr->window; 304 TkMacOSXDrawingContext dc; 305 NSView *view = TkMacOSXDrawableView(macWin); 306 CGFloat viewHeight = [view bounds].size.height; 307 CGAffineTransform t = { .a = 1, .b = 0, .c = 0, .d = -1, .tx = 0, 308 .ty = viewHeight}; 309 NSRect frame; 310 int enabled; 311 NSCellStateValue state; 312 313 if (!view || 314 !TkMacOSXSetupDrawingContext((Drawable) macWin, NULL, 1, &dc)) { 315 return; 316 } 317 CGContextConcatCTM(dc.context, t); 318 319 /* 320 * We cannot change the background color of the button itself, only the 321 * color of the background of its container. 322 * This will be the color that peeks around the rounded corners of the 323 * button. We make this the highlightbackground rather than the background, 324 * because if you color the background of a frame containing a 325 * button, you usually also color the highlightbackground as well, 326 * or you will get a thin grey ring around the button. 327 */ 328 329 Tk_Fill3DRectangle(tkwin, (Pixmap) macWin, butPtr->type == TYPE_BUTTON ? 330 butPtr->highlightBorder : butPtr->normalBorder, 0, 0, 331 Tk_Width(tkwin), Tk_Height(tkwin), 0, TK_RELIEF_FLAT); 332 if ([button superview] != view) { 333 [view addSubview:button]; 334 } 335 if (macButtonPtr->tristateImage) { 336 NSImage *selectImage = macButtonPtr->selectImage ? 337 macButtonPtr->selectImage : macButtonPtr->image; 338 [button setImage:(butPtr->flags & TRISTATED ? 339 selectImage : macButtonPtr->image)]; 340 [button setAlternateImage:(butPtr->flags & TRISTATED ? 341 macButtonPtr->tristateImage : selectImage)]; 342 } 343 if (butPtr->flags & SELECTED) { 344 state = NSOnState; 345 } else if (butPtr->flags & TRISTATED) { 346 state = NSMixedState; 347 } else { 348 state = NSOffState; 349 } 350 [button setState:state]; 351 enabled = !(butPtr->state == STATE_DISABLED); 352 [button setEnabled:enabled]; 353 if (enabled) { 354 //[button highlight:(butPtr->state == STATE_ACTIVE)]; 355 //[cell setHighlighted:(butPtr->state == STATE_ACTIVE)]; 356 } 357 if (butPtr->type == TYPE_BUTTON && butPtr->defaultState == STATE_ACTIVE) { 358 //[[view window] setDefaultButtonCell:cell]; 359 [button setKeyEquivalent:@"\r"]; 360 } else { 361 [button setKeyEquivalent:@""]; 362 } 363 frame = NSMakeRect(macWin->xOff, macWin->yOff, Tk_Width(tkwin), 364 Tk_Height(tkwin)); 365#if TK_MAC_BUTTON_USE_COMPATIBILITY_METRICS 366 if (tkMacOSXUseCompatibilityMetrics) { 367 BoundsFix boundsFix = boundsFixes[macButtonPtr->fix]; 368 frame = NSOffsetRect(frame, boundsFix.offsetX, boundsFix.offsetY); 369 frame.size.height -= boundsFix.shrinkH + NATIVE_BUTTON_EXTRA_H; 370 frame = NSInsetRect(frame, boundsFix.inset + NATIVE_BUTTON_INSET, 371 boundsFix.inset + NATIVE_BUTTON_INSET); 372 } 373#endif 374 frame.origin.y = viewHeight - (frame.origin.y + frame.size.height); 375 if (!NSEqualRects(frame, [button frame])) { 376 [button setFrame:frame]; 377 } 378 [button displayRectIgnoringOpacity:[button bounds]]; 379 TkMacOSXRestoreDrawingContext(&dc); 380#ifdef TK_MAC_DEBUG_BUTTON 381 TKLog(@"button %s frame %@ width %d height %d", 382 ((TkWindow *)butPtr->tkwin)->pathName, NSStringFromRect(frame), 383 Tk_Width(tkwin), Tk_Height(tkwin)); 384#endif 385} 386 387/* 388 *---------------------------------------------------------------------- 389 * 390 * ComputeNativeButtonGeometry -- 391 * 392 * After changes in a button's text or bitmap, this procedure 393 * recomputes the button's geometry and passes this information 394 * along to the geometry manager for the window. 395 * 396 * Results: 397 * None. 398 * 399 * Side effects: 400 * The button's window may change size. 401 * 402 *---------------------------------------------------------------------- 403 */ 404 405static void 406ComputeNativeButtonGeometry( 407 TkButton *butPtr) /* Button whose geometry may have changed. */ 408{ 409 MacButton *macButtonPtr = (MacButton *) butPtr; 410 NSButton *button = macButtonPtr->button; 411 NSButtonCell *cell = [button cell]; 412 NSButtonType type = -1; 413 NSBezelStyle style = 0; 414 NSInteger highlightsBy = 0, showsStateBy = 0; 415 NSFont *font; 416 NSRect bounds = NSZeroRect, titleRect = NSZeroRect; 417 int haveImage = (butPtr->image || butPtr->bitmap != None), haveText = 0; 418 int haveCompound = (butPtr->compound != COMPOUND_NONE); 419 int width, height, border = 0; 420 421 butPtr->indicatorSpace = 0; 422 butPtr->inset = 0; 423 if (butPtr->highlightWidth < 0) { 424 butPtr->highlightWidth = 0; 425 } 426 switch (butPtr->type) { 427 case TYPE_BUTTON: 428 type = NSMomentaryPushInButton; 429 if (!haveImage) { 430 style = NSRoundedBezelStyle; 431 butPtr->inset = butPtr->defaultState != STATE_DISABLED ? 432 butPtr->highlightWidth : 0; 433 [button setImage:nil]; 434 [button setImagePosition:NSNoImage]; 435 } else { 436 style = NSShadowlessSquareBezelStyle; 437 highlightsBy = butPtr->selectImage || butPtr->bitmap ? 438 NSContentsCellMask : 0; 439 border = butPtr->borderWidth; 440 } 441 break; 442 case TYPE_RADIO_BUTTON: 443 case TYPE_CHECK_BUTTON: 444 if (!haveImage /*|| butPtr->indicatorOn*/) { // TODO: indicatorOn 445 type = butPtr->type == TYPE_RADIO_BUTTON ? 446 NSRadioButton : NSSwitchButton; 447 butPtr->inset = /*butPtr->indicatorOn ? 0 :*/ butPtr->borderWidth; 448 } else { 449 type = NSPushOnPushOffButton; 450 style = NSShadowlessSquareBezelStyle; 451 highlightsBy = butPtr->selectImage || butPtr->bitmap ? 452 NSContentsCellMask : 0; 453 showsStateBy = butPtr->selectImage || butPtr->tristateImage ? 454 NSContentsCellMask : 0; 455#if TK_MAC_BUTTON_USE_COMPATIBILITY_METRICS 456 if (tkMacOSXUseCompatibilityMetrics) { 457 border = butPtr->borderWidth > 1 ? butPtr->borderWidth - 1 : 1; 458 } else 459#endif 460 { 461 border = butPtr->borderWidth; 462 } 463 } 464 break; 465 } 466 [button setButtonType:type]; 467 if (style) { 468 [button setBezelStyle:style]; 469 } 470 if (highlightsBy) { 471 [cell setHighlightsBy:highlightsBy|[cell highlightsBy]]; 472 } 473 if (showsStateBy) { 474 [cell setShowsStateBy:showsStateBy|[cell showsStateBy]]; 475 } 476#if 0 477 if (style == NSShadowlessSquareBezelStyle) { 478 NSControlSize controlSize = NSRegularControlSize; 479 480 if (butPtr->borderWidth <= 2) { 481 controlSize = NSMiniControlSize; 482 } else if (butPtr->borderWidth == 3) { 483 controlSize = NSSmallControlSize; 484 } 485 [cell setControlSize:controlSize]; 486 } 487#endif 488 [button setAllowsMixedState:YES]; 489 490 if (!haveImage || haveCompound) { 491 int len; 492 char *text = Tcl_GetStringFromObj(butPtr->textPtr, &len); 493 494 if (len) { 495 NSString *title = [[NSString alloc] initWithBytes:text length:len 496 encoding:NSUTF8StringEncoding]; 497 [button setTitle:title]; 498 [title release]; 499 haveText = 1; 500 } 501 } 502 haveCompound = (haveCompound && haveImage && haveText); 503 if (haveText) { 504 NSTextAlignment alignment = NSNaturalTextAlignment; 505 506 switch (butPtr->justify) { 507 case TK_JUSTIFY_LEFT: 508 alignment = NSLeftTextAlignment; 509 break; 510 case TK_JUSTIFY_RIGHT: 511 alignment = NSRightTextAlignment; 512 break; 513 case TK_JUSTIFY_CENTER: 514 alignment = NSCenterTextAlignment; 515 break; 516 } 517 [button setAlignment:alignment]; 518 } else { 519 [button setTitle:@""]; 520 } 521 font = TkMacOSXNSFontForFont(butPtr->tkfont); 522 if (font) { 523 [button setFont:font]; 524 } 525 TkMacOSXMakeCollectableAndRelease(macButtonPtr->image); 526 TkMacOSXMakeCollectableAndRelease(macButtonPtr->selectImage); 527 TkMacOSXMakeCollectableAndRelease(macButtonPtr->tristateImage); 528 if (haveImage) { 529 int width, height; 530 NSImage *image, *selectImage = nil, *tristateImage = nil; 531 NSCellImagePosition pos = NSImageOnly; 532 533 if (butPtr->image) { 534 Tk_SizeOfImage(butPtr->image, &width, &height); 535 image = TkMacOSXGetNSImageWithTkImage(butPtr->display, 536 butPtr->image, width, height); 537 if (butPtr->selectImage) { 538 selectImage = TkMacOSXGetNSImageWithTkImage(butPtr->display, 539 butPtr->selectImage, width, height); 540 } 541 if (butPtr->tristateImage) { 542 tristateImage = TkMacOSXGetNSImageWithTkImage(butPtr->display, 543 butPtr->tristateImage, width, height); 544 } 545 } else { 546 Tk_SizeOfBitmap(butPtr->display, butPtr->bitmap, &width, &height); 547 image = TkMacOSXGetNSImageWithBitmap(butPtr->display, 548 butPtr->bitmap, butPtr->normalTextGC, width, height); 549 selectImage = TkMacOSXGetNSImageWithBitmap(butPtr->display, 550 butPtr->bitmap, butPtr->activeTextGC, width, height); 551 } 552 [button setImage:image]; 553 if (selectImage) { 554 [button setAlternateImage:selectImage]; 555 } 556 if (tristateImage) { 557 macButtonPtr->image = TkMacOSXMakeUncollectableAndRetain(image); 558 if (selectImage) { 559 macButtonPtr->selectImage = 560 TkMacOSXMakeUncollectableAndRetain(selectImage); 561 } 562 macButtonPtr->tristateImage = 563 TkMacOSXMakeUncollectableAndRetain(tristateImage); 564 } 565 if (haveCompound) { 566 switch ((enum compound) butPtr->compound) { 567 case COMPOUND_TOP: 568 pos = NSImageAbove; 569 break; 570 case COMPOUND_BOTTOM: 571 pos = NSImageBelow; 572 break; 573 case COMPOUND_LEFT: 574 pos = NSImageLeft; 575 break; 576 case COMPOUND_RIGHT: 577 pos = NSImageRight; 578 break; 579 case COMPOUND_CENTER: 580 pos = NSImageOverlaps; 581 break; 582 case COMPOUND_NONE: 583 pos = NSImageOnly; 584 break; 585 } 586 } 587 [button setImagePosition:pos]; 588 } 589 590 bounds.size = [cell cellSize]; 591 if (haveText) { 592 titleRect = [cell titleRectForBounds:bounds]; 593 if (butPtr->wrapLength > 0 && 594 titleRect.size.width > butPtr->wrapLength) { 595 if (style == NSRoundedBezelStyle) { 596 [button setBezelStyle:(style = NSRegularSquareBezelStyle)]; 597 bounds.size = [cell cellSize]; 598 titleRect = [cell titleRectForBounds:bounds]; 599 } 600 bounds.size.width -= titleRect.size.width - butPtr->wrapLength; 601 bounds.size.height = 40000.0; 602 [cell setWraps:YES]; 603 bounds.size = [cell cellSizeForBounds:bounds]; 604#ifdef TK_MAC_DEBUG_BUTTON 605 titleRect = [cell titleRectForBounds:bounds]; 606#endif 607#if TK_MAC_BUTTON_USE_COMPATIBILITY_METRICS 608 if (tkMacOSXUseCompatibilityMetrics) { 609 bounds.size.height += 3; 610 } 611#endif 612 } 613 } 614 width = lround(bounds.size.width); 615 height = lround(bounds.size.height); 616#if TK_MAC_BUTTON_USE_COMPATIBILITY_METRICS 617 if (tkMacOSXUseCompatibilityMetrics) { 618 macButtonPtr->fix = fixForTypeStyle(type, style); 619 width -= boundsFixes[macButtonPtr->fix].trimW; 620 height -= boundsFixes[macButtonPtr->fix].trimH; 621 } 622#endif 623 624 if (haveImage || haveCompound) { 625 if (butPtr->width > 0) { 626 width = butPtr->width; 627 } 628 if (butPtr->height > 0) { 629 height = butPtr->height; 630 } 631 } else { 632 if (butPtr->width > 0) { 633 int avgWidth = Tk_TextWidth(butPtr->tkfont, "0", 1); 634 width = butPtr->width * avgWidth; 635 } 636 if (butPtr->height > 0) { 637 Tk_FontMetrics fm; 638 639 Tk_GetFontMetrics(butPtr->tkfont, &fm); 640 height = butPtr->height * fm.linespace; 641 } 642 } 643 if (!haveImage || haveCompound) { 644 width += 2*butPtr->padX; 645 height += 2*butPtr->padY; 646 } 647 if (haveImage) { 648 width += 2*border; 649 height += 2*border; 650 } 651#if TK_MAC_BUTTON_USE_COMPATIBILITY_METRICS 652 if (tkMacOSXUseCompatibilityMetrics) { 653 width += 2*NATIVE_BUTTON_INSET; 654 height += 2*NATIVE_BUTTON_INSET + NATIVE_BUTTON_EXTRA_H; 655 } 656#endif 657 Tk_GeometryRequest(butPtr->tkwin, width, height); 658 Tk_SetInternalBorder(butPtr->tkwin, butPtr->inset); 659#ifdef TK_MAC_DEBUG_BUTTON 660 TKLog(@"button %s bounds %@ titleRect %@ width %d height %d inset %d borderWidth %d", 661 ((TkWindow *)butPtr->tkwin)->pathName, NSStringFromRect(bounds), 662 NSStringFromRect(titleRect), width, height, butPtr->inset, 663 butPtr->borderWidth); 664#endif 665} 666 667#pragma mark - 668#pragma mark Unix Buttons: 669 670 671/* 672 *---------------------------------------------------------------------- 673 * 674 * DisplayUnixButton -- 675 * 676 * This procedure is invoked to display a button widget. It is 677 * normally invoked as an idle handler. 678 * 679 * Results: 680 * None. 681 * 682 * Side effects: 683 * Commands are output to X to display the button in its 684 * current mode. The REDRAW_PENDING flag is cleared. 685 * 686 *---------------------------------------------------------------------- 687 */ 688 689void 690DisplayUnixButton( 691 TkButton *butPtr) 692{ 693 GC gc; 694 Tk_3DBorder border; 695 Pixmap pixmap; 696 int x = 0; /* Initialization only needed to stop compiler 697 * warning. */ 698 int y, relief; 699 Tk_Window tkwin = butPtr->tkwin; 700 int width = 0, height = 0, fullWidth, fullHeight; 701 int textXOffset, textYOffset; 702 int haveImage = 0, haveText = 0; 703 int imageWidth, imageHeight; 704 int imageXOffset = 0, imageYOffset = 0; 705 /* image information that will be used to 706 * restrict disabled pixmap as well */ 707 708 border = butPtr->normalBorder; 709 if ((butPtr->state == STATE_DISABLED) && (butPtr->disabledFg != NULL)) { 710 gc = butPtr->disabledGC; 711 } else if ((butPtr->state == STATE_ACTIVE) 712 && !Tk_StrictMotif(butPtr->tkwin)) { 713 gc = butPtr->activeTextGC; 714 border = butPtr->activeBorder; 715 } else { 716 gc = butPtr->normalTextGC; 717 } 718 if ((butPtr->flags & SELECTED) && (butPtr->state != STATE_ACTIVE) 719 && (butPtr->selectBorder != NULL) && !butPtr->indicatorOn) { 720 border = butPtr->selectBorder; 721 } 722 723 relief = butPtr->relief; 724 725 pixmap = (Pixmap) Tk_WindowId(tkwin); 726 Tk_Fill3DRectangle(tkwin, pixmap, border, 0, 0, Tk_Width(tkwin), 727 Tk_Height(tkwin), 0, TK_RELIEF_FLAT); 728 729 /* 730 * Display image or bitmap or text for button. 731 */ 732 733 if (butPtr->image != NULL) { 734 Tk_SizeOfImage(butPtr->image, &width, &height); 735 haveImage = 1; 736 } else if (butPtr->bitmap != None) { 737 Tk_SizeOfBitmap(butPtr->display, butPtr->bitmap, &width, &height); 738 haveImage = 1; 739 } 740 imageWidth = width; 741 imageHeight = height; 742 743 haveText = (butPtr->textWidth != 0 && butPtr->textHeight != 0); 744 745 if (butPtr->compound != COMPOUND_NONE && haveImage && haveText) { 746 textXOffset = 0; 747 textYOffset = 0; 748 fullWidth = 0; 749 fullHeight = 0; 750 751 switch ((enum compound) butPtr->compound) { 752 case COMPOUND_TOP: 753 case COMPOUND_BOTTOM: 754 /* 755 * Image is above or below text. 756 */ 757 758 if (butPtr->compound == COMPOUND_TOP) { 759 textYOffset = height + butPtr->padY; 760 } else { 761 imageYOffset = butPtr->textHeight + butPtr->padY; 762 } 763 fullHeight = height + butPtr->textHeight + butPtr->padY; 764 fullWidth = (width > butPtr->textWidth ? width : 765 butPtr->textWidth); 766 textXOffset = (fullWidth - butPtr->textWidth)/2; 767 imageXOffset = (fullWidth - width)/2; 768 break; 769 case COMPOUND_LEFT: 770 case COMPOUND_RIGHT: 771 /* 772 * Image is left or right of text. 773 */ 774 775 if (butPtr->compound == COMPOUND_LEFT) { 776 textXOffset = width + butPtr->padX; 777 } else { 778 imageXOffset = butPtr->textWidth + butPtr->padX; 779 } 780 fullWidth = butPtr->textWidth + butPtr->padX + width; 781 fullHeight = (height > butPtr->textHeight ? height : 782 butPtr->textHeight); 783 textYOffset = (fullHeight - butPtr->textHeight)/2; 784 imageYOffset = (fullHeight - height)/2; 785 break; 786 case COMPOUND_CENTER: 787 /* 788 * Image and text are superimposed. 789 */ 790 791 fullWidth = (width > butPtr->textWidth ? width : 792 butPtr->textWidth); 793 fullHeight = (height > butPtr->textHeight ? height : 794 butPtr->textHeight); 795 textXOffset = (fullWidth - butPtr->textWidth)/2; 796 imageXOffset = (fullWidth - width)/2; 797 textYOffset = (fullHeight - butPtr->textHeight)/2; 798 imageYOffset = (fullHeight - height)/2; 799 break; 800 case COMPOUND_NONE: 801 break; 802 } 803 804 TkComputeAnchor(butPtr->anchor, tkwin, butPtr->padX, butPtr->padY, 805 fullWidth, fullHeight, &x, &y); 806 807 imageXOffset += x; 808 imageYOffset += y; 809 810 if (butPtr->image != NULL) { 811 /* 812 * Do boundary clipping, so that Tk_RedrawImage is passed valid 813 * coordinates. [Bug 979239] 814 */ 815 816 if (imageXOffset < 0) { 817 imageXOffset = 0; 818 } 819 if (imageYOffset < 0) { 820 imageYOffset = 0; 821 } 822 if (width > Tk_Width(tkwin)) { 823 width = Tk_Width(tkwin); 824 } 825 if (height > Tk_Height(tkwin)) { 826 height = Tk_Height(tkwin); 827 } 828 if ((width + imageXOffset) > Tk_Width(tkwin)) { 829 imageXOffset = Tk_Width(tkwin) - width; 830 } 831 if ((height + imageYOffset) > Tk_Height(tkwin)) { 832 imageYOffset = Tk_Height(tkwin) - height; 833 } 834 835 if ((butPtr->selectImage != NULL) && (butPtr->flags & SELECTED)) { 836 Tk_RedrawImage(butPtr->selectImage, 0, 0, 837 width, height, pixmap, imageXOffset, imageYOffset); 838 } else if ((butPtr->tristateImage != NULL) && (butPtr->flags & TRISTATED)) { 839 Tk_RedrawImage(butPtr->tristateImage, 0, 0, 840 width, height, pixmap, imageXOffset, imageYOffset); 841 } else { 842 Tk_RedrawImage(butPtr->image, 0, 0, width, 843 height, pixmap, imageXOffset, imageYOffset); 844 } 845 } else { 846 XSetClipOrigin(butPtr->display, gc, imageXOffset, imageYOffset); 847 XCopyPlane(butPtr->display, butPtr->bitmap, pixmap, gc, 848 0, 0, (unsigned int) width, (unsigned int) height, 849 imageXOffset, imageYOffset, 1); 850 XSetClipOrigin(butPtr->display, gc, 0, 0); 851 } 852 853 Tk_DrawTextLayout(butPtr->display, pixmap, gc, 854 butPtr->textLayout, x + textXOffset, y + textYOffset, 0, -1); 855 Tk_UnderlineTextLayout(butPtr->display, pixmap, gc, 856 butPtr->textLayout, x + textXOffset, y + textYOffset, 857 butPtr->underline); 858 y += fullHeight/2; 859 } else { 860 if (haveImage) { 861 TkComputeAnchor(butPtr->anchor, tkwin, 0, 0, 862 width, height, &x, &y); 863 imageXOffset += x; 864 imageYOffset += y; 865 if (butPtr->image != NULL) { 866 /* 867 * Do boundary clipping, so that Tk_RedrawImage is passed 868 * valid coordinates. [Bug 979239] 869 */ 870 871 if (imageXOffset < 0) { 872 imageXOffset = 0; 873 } 874 if (imageYOffset < 0) { 875 imageYOffset = 0; 876 } 877 if (width > Tk_Width(tkwin)) { 878 width = Tk_Width(tkwin); 879 } 880 if (height > Tk_Height(tkwin)) { 881 height = Tk_Height(tkwin); 882 } 883 if ((width + imageXOffset) > Tk_Width(tkwin)) { 884 imageXOffset = Tk_Width(tkwin) - width; 885 } 886 if ((height + imageYOffset) > Tk_Height(tkwin)) { 887 imageYOffset = Tk_Height(tkwin) - height; 888 } 889 890 if ((butPtr->selectImage != NULL) && 891 (butPtr->flags & SELECTED)) { 892 Tk_RedrawImage(butPtr->selectImage, 0, 0, width, 893 height, pixmap, imageXOffset, imageYOffset); 894 } else if ((butPtr->tristateImage != NULL) && 895 (butPtr->flags & TRISTATED)) { 896 Tk_RedrawImage(butPtr->tristateImage, 0, 0, width, 897 height, pixmap, imageXOffset, imageYOffset); 898 } else { 899 Tk_RedrawImage(butPtr->image, 0, 0, width, height, pixmap, 900 imageXOffset, imageYOffset); 901 } 902 } else { 903 XSetClipOrigin(butPtr->display, gc, x, y); 904 XCopyPlane(butPtr->display, butPtr->bitmap, pixmap, gc, 0, 0, 905 (unsigned int) width, (unsigned int) height, x, y, 1); 906 XSetClipOrigin(butPtr->display, gc, 0, 0); 907 } 908 y += height/2; 909 } else { 910 TkComputeAnchor(butPtr->anchor, tkwin, butPtr->padX, butPtr->padY, 911 butPtr->textWidth, butPtr->textHeight, &x, &y); 912 913 Tk_DrawTextLayout(butPtr->display, pixmap, gc, butPtr->textLayout, 914 x, y, 0, -1); 915 Tk_UnderlineTextLayout(butPtr->display, pixmap, gc, 916 butPtr->textLayout, x, y, butPtr->underline); 917 y += butPtr->textHeight/2; 918 } 919 } 920 921 /* 922 * If the button is disabled with a stipple rather than a special 923 * foreground color, generate the stippled effect. If the widget is 924 * selected and we use a different background color when selected, must 925 * temporarily modify the GC so the stippling is the right color. 926 */ 927 928 if ((butPtr->state == STATE_DISABLED) 929 && ((butPtr->disabledFg == NULL) || (butPtr->image != NULL))) { 930 if ((butPtr->flags & SELECTED) && !butPtr->indicatorOn 931 && (butPtr->selectBorder != NULL)) { 932 XSetForeground(butPtr->display, butPtr->stippleGC, 933 Tk_3DBorderColor(butPtr->selectBorder)->pixel); 934 } 935 936 /* 937 * Stipple the whole button if no disabledFg was specified, otherwise 938 * restrict stippling only to displayed image 939 */ 940 941 if (butPtr->disabledFg == NULL) { 942 XFillRectangle(butPtr->display, pixmap, butPtr->stippleGC, 0, 0, 943 (unsigned) Tk_Width(tkwin), (unsigned) Tk_Height(tkwin)); 944 } else { 945 XFillRectangle(butPtr->display, pixmap, butPtr->stippleGC, 946 imageXOffset, imageYOffset, 947 (unsigned) imageWidth, (unsigned) imageHeight); 948 } 949 if ((butPtr->flags & SELECTED) && !butPtr->indicatorOn 950 && (butPtr->selectBorder != NULL)) { 951 XSetForeground(butPtr->display, butPtr->stippleGC, 952 Tk_3DBorderColor(butPtr->normalBorder)->pixel); 953 } 954 } 955 956 /* 957 * Draw the border and traversal highlight last. This way, if the button's 958 * contents overflow they'll be covered up by the border. This code is 959 * complicated by the possible combinations of focus highlight and default 960 * rings. We draw the focus and highlight rings using the highlight border 961 * and highlight foreground color. 962 */ 963 964 if (relief != TK_RELIEF_FLAT) { 965 int inset = butPtr->highlightWidth; 966 967 if (butPtr->defaultState == DEFAULT_ACTIVE) { 968 /* 969 * Draw the default ring with 2 pixels of space between the 970 * default ring and the button and the default ring and the focus 971 * ring. Note that we need to explicitly draw the space in the 972 * highlightBorder color to ensure that we overwrite any overflow 973 * text and/or a different button background color. 974 */ 975 976 Tk_Draw3DRectangle(tkwin, pixmap, butPtr->highlightBorder, inset, 977 inset, Tk_Width(tkwin) - 2*inset, 978 Tk_Height(tkwin) - 2*inset, 2, TK_RELIEF_FLAT); 979 inset += 2; 980 Tk_Draw3DRectangle(tkwin, pixmap, butPtr->highlightBorder, inset, 981 inset, Tk_Width(tkwin) - 2*inset, 982 Tk_Height(tkwin) - 2*inset, 1, TK_RELIEF_SUNKEN); 983 inset++; 984 Tk_Draw3DRectangle(tkwin, pixmap, butPtr->highlightBorder, inset, 985 inset, Tk_Width(tkwin) - 2*inset, 986 Tk_Height(tkwin) - 2*inset, 2, TK_RELIEF_FLAT); 987 988 inset += 2; 989 } else if (butPtr->defaultState == DEFAULT_NORMAL) { 990 /* 991 * Leave room for the default ring and write over any text or 992 * background color. 993 */ 994 995 Tk_Draw3DRectangle(tkwin, pixmap, butPtr->highlightBorder, 0, 996 0, Tk_Width(tkwin), Tk_Height(tkwin), 5, TK_RELIEF_FLAT); 997 inset += 5; 998 } 999 1000 /* 1001 * Draw the button border. 1002 */ 1003 1004 Tk_Draw3DRectangle(tkwin, pixmap, border, inset, inset, 1005 Tk_Width(tkwin) - 2*inset, Tk_Height(tkwin) - 2*inset, 1006 butPtr->borderWidth, relief); 1007 } 1008 if (butPtr->highlightWidth > 0) { 1009 GC gc; 1010 1011 if (butPtr->flags & GOT_FOCUS) { 1012 gc = Tk_GCForColor(butPtr->highlightColorPtr, pixmap); 1013 } else { 1014 gc = Tk_GCForColor(Tk_3DBorderColor(butPtr->highlightBorder), 1015 pixmap); 1016 } 1017 1018 /* 1019 * Make sure the focus ring shrink-wraps the actual button, not the 1020 * padding space left for a default ring. 1021 */ 1022 1023 if (butPtr->defaultState == DEFAULT_NORMAL) { 1024 TkDrawInsetFocusHighlight(tkwin, gc, butPtr->highlightWidth, 1025 pixmap, 5); 1026 } else { 1027 Tk_DrawFocusHighlight(tkwin, gc, butPtr->highlightWidth, pixmap); 1028 } 1029 } 1030} 1031 1032/* 1033 *---------------------------------------------------------------------- 1034 * 1035 * ComputeUnixButtonGeometry -- 1036 * 1037 * After changes in a button's text or bitmap, this procedure 1038 * recomputes the button's geometry and passes this information 1039 * along to the geometry manager for the window. 1040 * 1041 * Results: 1042 * None. 1043 * 1044 * Side effects: 1045 * The button's window may change size. 1046 * 1047 *---------------------------------------------------------------------- 1048 */ 1049 1050void 1051ComputeUnixButtonGeometry( 1052 register TkButton *butPtr) /* Button whose geometry may have changed. */ 1053{ 1054 int width, height, avgWidth, txtWidth, txtHeight; 1055 int haveImage = 0, haveText = 0; 1056 Tk_FontMetrics fm; 1057 1058 butPtr->inset = butPtr->highlightWidth + butPtr->borderWidth; 1059 1060 /* 1061 * Leave room for the default ring if needed. 1062 */ 1063 1064 if (butPtr->defaultState != DEFAULT_DISABLED) { 1065 butPtr->inset += 5; 1066 } 1067 butPtr->indicatorSpace = 0; 1068 1069 width = 0; 1070 height = 0; 1071 txtWidth = 0; 1072 txtHeight = 0; 1073 avgWidth = 0; 1074 1075 if (butPtr->image != NULL) { 1076 Tk_SizeOfImage(butPtr->image, &width, &height); 1077 haveImage = 1; 1078 } else if (butPtr->bitmap != None) { 1079 Tk_SizeOfBitmap(butPtr->display, butPtr->bitmap, &width, &height); 1080 haveImage = 1; 1081 } 1082 1083 if (haveImage == 0 || butPtr->compound != COMPOUND_NONE) { 1084 Tk_FreeTextLayout(butPtr->textLayout); 1085 1086 butPtr->textLayout = Tk_ComputeTextLayout(butPtr->tkfont, 1087 Tcl_GetString(butPtr->textPtr), -1, butPtr->wrapLength, 1088 butPtr->justify, 0, &butPtr->textWidth, &butPtr->textHeight); 1089 1090 txtWidth = butPtr->textWidth; 1091 txtHeight = butPtr->textHeight; 1092 avgWidth = Tk_TextWidth(butPtr->tkfont, "0", 1); 1093 Tk_GetFontMetrics(butPtr->tkfont, &fm); 1094 haveText = (txtWidth != 0 && txtHeight != 0); 1095 } 1096 1097 /* 1098 * If the button is compound (i.e., it shows both an image and text), the 1099 * new geometry is a combination of the image and text geometry. We only 1100 * honor the compound bit if the button has both text and an image, 1101 * because otherwise it is not really a compound button. 1102 */ 1103 1104 if (butPtr->compound != COMPOUND_NONE && haveImage && haveText) { 1105 switch ((enum compound) butPtr->compound) { 1106 case COMPOUND_TOP: 1107 case COMPOUND_BOTTOM: 1108 /* 1109 * Image is above or below text. 1110 */ 1111 1112 height += txtHeight + butPtr->padY; 1113 width = (width > txtWidth ? width : txtWidth); 1114 break; 1115 case COMPOUND_LEFT: 1116 case COMPOUND_RIGHT: 1117 /* 1118 * Image is left or right of text. 1119 */ 1120 1121 width += txtWidth + butPtr->padX; 1122 height = (height > txtHeight ? height : txtHeight); 1123 break; 1124 case COMPOUND_CENTER: 1125 /* 1126 * Image and text are superimposed. 1127 */ 1128 1129 width = (width > txtWidth ? width : txtWidth); 1130 height = (height > txtHeight ? height : txtHeight); 1131 break; 1132 case COMPOUND_NONE: 1133 break; 1134 } 1135 if (butPtr->width > 0) { 1136 width = butPtr->width; 1137 } 1138 if (butPtr->height > 0) { 1139 height = butPtr->height; 1140 } 1141 1142 width += 2*butPtr->padX; 1143 height += 2*butPtr->padY; 1144 } else { 1145 if (haveImage) { 1146 if (butPtr->width > 0) { 1147 width = butPtr->width; 1148 } 1149 if (butPtr->height > 0) { 1150 height = butPtr->height; 1151 } 1152 } else { 1153 width = txtWidth; 1154 height = txtHeight; 1155 1156 if (butPtr->width > 0) { 1157 width = butPtr->width * avgWidth; 1158 } 1159 if (butPtr->height > 0) { 1160 height = butPtr->height * fm.linespace; 1161 } 1162 } 1163 } 1164 1165 if (!haveImage) { 1166 width += 2*butPtr->padX; 1167 height += 2*butPtr->padY; 1168 } 1169 Tk_GeometryRequest(butPtr->tkwin, (int) (width 1170 + 2*butPtr->inset), (int) (height + 2*butPtr->inset)); 1171 Tk_SetInternalBorder(butPtr->tkwin, butPtr->inset); 1172} 1173 1174/* 1175 * Local Variables: 1176 * mode: c 1177 * c-basic-offset: 4 1178 * fill-column: 79 1179 * coding: utf-8 1180 * End: 1181 */ 1182