1/* 2 * tkWinButton.c -- 3 * 4 * This file implements the Windows specific portion of the button 5 * widgets. 6 * 7 * Copyright (c) 1996-1998 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: tkWinButton.c,v 1.20.2.4 2006/12/19 19:50:55 hobbs Exp $ 13 */ 14 15#define OEMRESOURCE 16#include "tkWinInt.h" 17#include "tkButton.h" 18 19/* 20 * These macros define the base style flags for the different button types. 21 */ 22 23#define LABEL_STYLE (BS_OWNERDRAW | WS_CHILD | WS_VISIBLE | WS_CLIPSIBLINGS) 24#define PUSH_STYLE (BS_OWNERDRAW | BS_PUSHBUTTON | WS_CHILD | WS_VISIBLE | WS_CLIPSIBLINGS) 25#define CHECK_STYLE (BS_OWNERDRAW | BS_CHECKBOX | WS_CHILD | WS_VISIBLE | WS_CLIPSIBLINGS) 26#define RADIO_STYLE (BS_OWNERDRAW | BS_RADIOBUTTON | WS_CHILD | WS_VISIBLE | WS_CLIPSIBLINGS) 27 28/* 29 * Declaration of Windows specific button structure. 30 */ 31 32typedef struct WinButton { 33 TkButton info; /* Generic button info. */ 34 WNDPROC oldProc; /* Old window procedure. */ 35 HWND hwnd; /* Current window handle. */ 36 Pixmap pixmap; /* Bitmap for rendering the button. */ 37 DWORD style; /* Window style flags. */ 38} WinButton; 39 40/* 41 * The following macro reverses the order of RGB bytes to convert between 42 * RGBQUAD and COLORREF values. 43 */ 44 45#define FlipColor(rgb) (RGB(GetBValue(rgb),GetGValue(rgb),GetRValue(rgb))) 46 47/* 48 * The following enumeration defines the meaning of the palette entries in the 49 * "buttons" image used to draw checkbox and radiobutton indicators. 50 */ 51 52enum { 53 PAL_CHECK = 0, 54 PAL_TOP_OUTER = 1, 55 PAL_BOTTOM_OUTER = 2, 56 PAL_BOTTOM_INNER = 3, 57 PAL_INTERIOR = 4, 58 PAL_TOP_INNER = 5, 59 PAL_BACKGROUND = 6 60}; 61 62/* 63 * Cached information about the boxes bitmap, and the default border width for 64 * a button in string form for use in Tk_OptionSpec for the various button 65 * widget classes. 66 */ 67 68typedef struct ThreadSpecificData { 69 BITMAPINFOHEADER *boxesPtr; /* Information about the bitmap. */ 70 DWORD *boxesPalette; /* Pointer to color palette. */ 71 LPSTR boxesBits; /* Pointer to bitmap data. */ 72 DWORD boxHeight; /* Height of each sub-image. */ 73 DWORD boxWidth ; /* Width of each sub-image. */ 74} ThreadSpecificData; 75static Tcl_ThreadDataKey dataKey; 76 77/* 78 * Declarations for functions defined in this file. 79 */ 80 81static LRESULT CALLBACK ButtonProc _ANSI_ARGS_((HWND hwnd, UINT message, 82 WPARAM wParam, LPARAM lParam)); 83static Window CreateProc _ANSI_ARGS_((Tk_Window tkwin, 84 Window parent, ClientData instanceData)); 85static void InitBoxes _ANSI_ARGS_((void)); 86 87/* 88 * The class procedure table for the button widgets. 89 */ 90 91Tk_ClassProcs tkpButtonProcs = { 92 sizeof(Tk_ClassProcs), /* size */ 93 TkButtonWorldChanged, /* worldChangedProc */ 94 CreateProc, /* createProc */ 95}; 96 97 98/* 99 *---------------------------------------------------------------------- 100 * 101 * InitBoxes -- 102 * 103 * This function load the Tk 3d button bitmap. "buttons" is a 16 104 * color bitmap that is laid out such that the top row contains 105 * the 4 checkbox images, and the bottom row contains the radio 106 * button images. Note that the bitmap is stored in bottom-up 107 * format. Also, the first seven palette entries are used to 108 * identify the different parts of the bitmaps so we can do the 109 * appropriate color mappings based on the current button colors. 110 * 111 * Results: 112 * None. 113 * 114 * Side effects: 115 * Loads the "buttons" resource. 116 * 117 *---------------------------------------------------------------------- 118 */ 119 120static void 121InitBoxes() 122{ 123 /* 124 * For DLLs like Tk, the HINSTANCE is the same as the HMODULE. 125 */ 126 127 HMODULE module = (HINSTANCE) Tk_GetHINSTANCE(); 128 HRSRC hrsrc; 129 HGLOBAL hblk; 130 LPBITMAPINFOHEADER newBitmap; 131 DWORD size; 132 ThreadSpecificData *tsdPtr = (ThreadSpecificData *) 133 Tcl_GetThreadData(&dataKey, sizeof(ThreadSpecificData)); 134 135 hrsrc = FindResource(module, "buttons", RT_BITMAP); 136 if (hrsrc) { 137 hblk = LoadResource(module, hrsrc); 138 tsdPtr->boxesPtr = (LPBITMAPINFOHEADER)LockResource(hblk); 139 } 140 141 /* 142 * Copy the DIBitmap into writable memory. 143 */ 144 145 if (tsdPtr->boxesPtr != NULL && !(tsdPtr->boxesPtr->biWidth % 4) 146 && !(tsdPtr->boxesPtr->biHeight % 2)) { 147 size = tsdPtr->boxesPtr->biSize + (1 << tsdPtr->boxesPtr->biBitCount) 148 * sizeof(RGBQUAD) + tsdPtr->boxesPtr->biSizeImage; 149 newBitmap = (LPBITMAPINFOHEADER) ckalloc(size); 150 memcpy(newBitmap, tsdPtr->boxesPtr, size); 151 tsdPtr->boxesPtr = newBitmap; 152 tsdPtr->boxWidth = tsdPtr->boxesPtr->biWidth / 4; 153 tsdPtr->boxHeight = tsdPtr->boxesPtr->biHeight / 2; 154 tsdPtr->boxesPalette = (DWORD*) (((LPSTR) tsdPtr->boxesPtr) 155 + tsdPtr->boxesPtr->biSize); 156 tsdPtr->boxesBits = ((LPSTR) tsdPtr->boxesPalette) 157 + ((1 << tsdPtr->boxesPtr->biBitCount) * sizeof(RGBQUAD)); 158 } else { 159 tsdPtr->boxesPtr = NULL; 160 } 161} 162 163/* 164 *---------------------------------------------------------------------- 165 * 166 * TkpButtonSetDefaults -- 167 * 168 * This procedure is invoked before option tables are created for 169 * buttons. It modifies some of the default values to match the 170 * current values defined for this platform. 171 * 172 * Results: 173 * Some of the default values in *specPtr are modified. 174 * 175 * Side effects: 176 * Updates some of. 177 * 178 *---------------------------------------------------------------------- 179 */ 180 181void 182TkpButtonSetDefaults(specPtr) 183 Tk_OptionSpec *specPtr; /* Points to an array of option specs, 184 * terminated by one with type 185 * TK_OPTION_END. */ 186{ 187 int width = GetSystemMetrics(SM_CXEDGE); 188 if (width > 0) { 189 sprintf(tkDefButtonBorderWidth, "%d", width); 190 } 191} 192 193/* 194 *---------------------------------------------------------------------- 195 * 196 * TkpCreateButton -- 197 * 198 * Allocate a new TkButton structure. 199 * 200 * Results: 201 * Returns a newly allocated TkButton structure. 202 * 203 * Side effects: 204 * Registers an event handler for the widget. 205 * 206 *---------------------------------------------------------------------- 207 */ 208 209TkButton * 210TkpCreateButton(tkwin) 211 Tk_Window tkwin; 212{ 213 WinButton *butPtr; 214 215 butPtr = (WinButton *)ckalloc(sizeof(WinButton)); 216 butPtr->hwnd = NULL; 217 return (TkButton *) butPtr; 218} 219 220/* 221 *---------------------------------------------------------------------- 222 * 223 * CreateProc -- 224 * 225 * This function creates a new Button control, subclasses 226 * the instance, and generates a new Window object. 227 * 228 * Results: 229 * Returns the newly allocated Window object, or None on failure. 230 * 231 * Side effects: 232 * Causes a new Button control to come into existence. 233 * 234 *---------------------------------------------------------------------- 235 */ 236 237static Window 238CreateProc(tkwin, parentWin, instanceData) 239 Tk_Window tkwin; /* Token for window. */ 240 Window parentWin; /* Parent of new window. */ 241 ClientData instanceData; /* Button instance data. */ 242{ 243 Window window; 244 HWND parent; 245 char *class; 246 WinButton *butPtr = (WinButton *)instanceData; 247 248 parent = Tk_GetHWND(parentWin); 249 if (butPtr->info.type == TYPE_LABEL) { 250 class = "STATIC"; 251 butPtr->style = SS_OWNERDRAW | WS_CHILD | WS_VISIBLE | WS_CLIPSIBLINGS; 252 } else { 253 class = "BUTTON"; 254 butPtr->style = BS_OWNERDRAW | WS_CHILD | WS_VISIBLE | WS_CLIPSIBLINGS; 255 } 256 butPtr->hwnd = CreateWindow(class, NULL, butPtr->style, 257 Tk_X(tkwin), Tk_Y(tkwin), Tk_Width(tkwin), Tk_Height(tkwin), 258 parent, NULL, Tk_GetHINSTANCE(), NULL); 259 SetWindowPos(butPtr->hwnd, HWND_TOP, 0, 0, 0, 0, 260 SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOSIZE); 261#ifdef _WIN64 262 butPtr->oldProc = (WNDPROC)SetWindowLongPtr(butPtr->hwnd, GWLP_WNDPROC, 263 (LONG_PTR) ButtonProc); 264#else 265 butPtr->oldProc = (WNDPROC)SetWindowLong(butPtr->hwnd, GWL_WNDPROC, 266 (DWORD) ButtonProc); 267#endif 268 269 window = Tk_AttachHWND(tkwin, butPtr->hwnd); 270 return window; 271} 272 273/* 274 *---------------------------------------------------------------------- 275 * 276 * TkpDestroyButton -- 277 * 278 * Free data structures associated with the button control. 279 * 280 * Results: 281 * None. 282 * 283 * Side effects: 284 * Restores the default control state. 285 * 286 *---------------------------------------------------------------------- 287 */ 288 289void 290TkpDestroyButton(butPtr) 291 TkButton *butPtr; 292{ 293 WinButton *winButPtr = (WinButton *)butPtr; 294 HWND hwnd = winButPtr->hwnd; 295 if (hwnd) { 296#ifdef _WIN64 297 SetWindowLongPtr(hwnd, GWLP_WNDPROC, (LONG_PTR) winButPtr->oldProc); 298#else 299 SetWindowLong(hwnd, GWL_WNDPROC, (DWORD) winButPtr->oldProc); 300#endif 301 } 302} 303 304/* 305 *---------------------------------------------------------------------- 306 * 307 * TkpDisplayButton -- 308 * 309 * This procedure is invoked to display a button widget. It is 310 * normally invoked as an idle handler. 311 * 312 * Results: 313 * None. 314 * 315 * Side effects: 316 * Information appears on the screen. The REDRAW_PENDING flag 317 * is cleared. 318 * 319 *---------------------------------------------------------------------- 320 */ 321 322void 323TkpDisplayButton(clientData) 324 ClientData clientData; /* Information about widget. */ 325{ 326 TkWinDCState state; 327 HDC dc; 328 register TkButton *butPtr = (TkButton *) clientData; 329 GC gc; 330 Tk_3DBorder border; 331 Pixmap pixmap; 332 int x = 0; /* Initialization only needed to stop 333 * compiler warning. */ 334 int y, relief; 335 register Tk_Window tkwin = butPtr->tkwin; 336 int width = 0, height = 0, haveImage = 0, haveText = 0, drawRing = 0; 337 RECT rect; 338 int defaultWidth; /* Width of default ring. */ 339 int offset; /* 0 means this is a label widget. 1 means 340 * it is a flavor of button, so we offset 341 * the text to make the button appear to 342 * move up and down as the relief changes. */ 343 int textXOffset = 0, textYOffset = 0; /* text offsets for use with 344 * compound buttons and focus ring */ 345 int imageWidth, imageHeight; 346 int imageXOffset = 0, imageYOffset = 0; /* image information that will 347 * be used to restrict disabled 348 * pixmap as well */ 349 DWORD *boxesPalette; 350 351 ThreadSpecificData *tsdPtr = (ThreadSpecificData *) 352 Tcl_GetThreadData(&dataKey, sizeof(ThreadSpecificData)); 353 354 boxesPalette= tsdPtr->boxesPalette; 355 butPtr->flags &= ~REDRAW_PENDING; 356 if ((butPtr->tkwin == NULL) || !Tk_IsMapped(tkwin)) { 357 return; 358 } 359 360 border = butPtr->normalBorder; 361 if ((butPtr->state == STATE_DISABLED) && (butPtr->disabledFg != NULL)) { 362 gc = butPtr->disabledGC; 363 } else if ((butPtr->state == STATE_ACTIVE) 364 && !Tk_StrictMotif(butPtr->tkwin)) { 365 gc = butPtr->activeTextGC; 366 border = butPtr->activeBorder; 367 } else { 368 gc = butPtr->normalTextGC; 369 } 370 if ((butPtr->flags & SELECTED) && (butPtr->state != STATE_ACTIVE) 371 && (butPtr->selectBorder != NULL) && !butPtr->indicatorOn) { 372 border = butPtr->selectBorder; 373 } 374 375 /* 376 * Override the relief specified for the button if this is a 377 * checkbutton or radiobutton and there's no indicator. The new 378 * relief is as follows: 379 * If the button is select --> "sunken" 380 * If relief==overrelief --> relief 381 * Otherwise --> overrelief 382 * 383 * The effect we are trying to achieve is as follows: 384 * 385 * value mouse-over? --> relief 386 * ------- ------------ -------- 387 * off no flat 388 * off yes raised 389 * on no sunken 390 * on yes sunken 391 * 392 * This is accomplished by configuring the checkbutton or radiobutton 393 * like this: 394 * 395 * -indicatoron 0 -overrelief raised -offrelief flat 396 * 397 * Bindings (see library/button.tcl) will copy the -overrelief into 398 * -relief on mouseover. Hence, we can tell if we are in mouse-over by 399 * comparing relief against overRelief. This is an aweful kludge, but 400 * it gives use the desired behavior while keeping the code backwards 401 * compatible. 402 */ 403 404 relief = butPtr->relief; 405 if ((butPtr->type >= TYPE_CHECK_BUTTON) && !butPtr->indicatorOn) { 406 if (butPtr->flags & SELECTED) { 407 relief = TK_RELIEF_SUNKEN; 408 } else if (butPtr->overRelief != relief) { 409 relief = butPtr->offRelief; 410 } 411 } 412 413 /* 414 * Compute width of default ring and offset for pushed buttons. 415 */ 416 417 if (butPtr->type == TYPE_BUTTON) { 418 defaultWidth = ((butPtr->defaultState == DEFAULT_ACTIVE) 419 ? butPtr->highlightWidth : 0); 420 offset = 1; 421 } else { 422 defaultWidth = 0; 423 if ((butPtr->type >= TYPE_CHECK_BUTTON) && !butPtr->indicatorOn) { 424 offset = 1; 425 } else { 426 offset = 0; 427 } 428 } 429 430 /* 431 * In order to avoid screen flashes, this procedure redraws 432 * the button in a pixmap, then copies the pixmap to the 433 * screen in a single operation. This means that there's no 434 * point in time where the on-sreen image has been cleared. 435 */ 436 437 pixmap = Tk_GetPixmap(butPtr->display, Tk_WindowId(tkwin), 438 Tk_Width(tkwin), Tk_Height(tkwin), Tk_Depth(tkwin)); 439 Tk_Fill3DRectangle(tkwin, pixmap, border, 0, 0, Tk_Width(tkwin), 440 Tk_Height(tkwin), 0, TK_RELIEF_FLAT); 441 442 /* 443 * Display image or bitmap or text for button. 444 */ 445 446 if (butPtr->image != None) { 447 Tk_SizeOfImage(butPtr->image, &width, &height); 448 haveImage = 1; 449 } else if (butPtr->bitmap != None) { 450 Tk_SizeOfBitmap(butPtr->display, butPtr->bitmap, &width, &height); 451 haveImage = 1; 452 } 453 imageWidth = width; 454 imageHeight = height; 455 456 haveText = (butPtr->textWidth != 0 && butPtr->textHeight != 0); 457 458 if (butPtr->compound != COMPOUND_NONE && haveImage && haveText) { 459 int fullWidth = 0, fullHeight = 0; 460 461 switch ((enum compound) butPtr->compound) { 462 case COMPOUND_TOP: 463 case COMPOUND_BOTTOM: { 464 /* Image is above or below text */ 465 if (butPtr->compound == COMPOUND_TOP) { 466 textYOffset = height + butPtr->padY; 467 } else { 468 imageYOffset = butPtr->textHeight + butPtr->padY; 469 } 470 fullHeight = height + butPtr->textHeight + butPtr->padY; 471 fullWidth = (width > butPtr->textWidth ? width : 472 butPtr->textWidth); 473 textXOffset = (fullWidth - butPtr->textWidth)/2; 474 imageXOffset = (fullWidth - width)/2; 475 break; 476 } 477 case COMPOUND_LEFT: 478 case COMPOUND_RIGHT: { 479 /* Image is left or right of text */ 480 if (butPtr->compound == COMPOUND_LEFT) { 481 textXOffset = width + butPtr->padX; 482 } else { 483 imageXOffset = butPtr->textWidth + butPtr->padX; 484 } 485 fullWidth = butPtr->textWidth + butPtr->padX + width; 486 fullHeight = (height > butPtr->textHeight ? height : 487 butPtr->textHeight); 488 textYOffset = (fullHeight - butPtr->textHeight)/2; 489 imageYOffset = (fullHeight - height)/2; 490 break; 491 } 492 case COMPOUND_CENTER: { 493 /* Image and text are superimposed */ 494 fullWidth = (width > butPtr->textWidth ? width : 495 butPtr->textWidth); 496 fullHeight = (height > butPtr->textHeight ? height : 497 butPtr->textHeight); 498 textXOffset = (fullWidth - butPtr->textWidth)/2; 499 imageXOffset = (fullWidth - width)/2; 500 textYOffset = (fullHeight - butPtr->textHeight)/2; 501 imageYOffset = (fullHeight - height)/2; 502 break; 503 } 504 case COMPOUND_NONE: {break;} 505 } 506 TkComputeAnchor(butPtr->anchor, tkwin, butPtr->padX, butPtr->padY, 507 butPtr->indicatorSpace + fullWidth, fullHeight, &x, &y); 508 x += butPtr->indicatorSpace; 509 510 if (relief == TK_RELIEF_SUNKEN) { 511 x += offset; 512 y += offset; 513 } 514 imageXOffset += x; 515 imageYOffset += y; 516 if (butPtr->image != NULL) { 517 if ((butPtr->selectImage != NULL) && (butPtr->flags & SELECTED)) { 518 Tk_RedrawImage(butPtr->selectImage, 0, 0, 519 width, height, pixmap, imageXOffset, imageYOffset); 520 } else { 521 Tk_RedrawImage(butPtr->image, 0, 0, 522 width, height, pixmap, imageXOffset, imageYOffset); 523 } 524 } else { 525 XSetClipOrigin(butPtr->display, gc, imageXOffset, imageYOffset); 526 XCopyPlane(butPtr->display, butPtr->bitmap, pixmap, gc, 527 0, 0, (unsigned int) width, (unsigned int) height, 528 imageXOffset, imageYOffset, 1); 529 XSetClipOrigin(butPtr->display, gc, 0, 0); 530 } 531 532 Tk_DrawTextLayout(butPtr->display, pixmap, gc, 533 butPtr->textLayout, x + textXOffset, y + textYOffset, 0, -1); 534 Tk_UnderlineTextLayout(butPtr->display, pixmap, gc, 535 butPtr->textLayout, x + textXOffset, y + textYOffset, 536 butPtr->underline); 537 height = fullHeight; 538 drawRing = 1; 539 } else { 540 if (haveImage) { 541 TkComputeAnchor(butPtr->anchor, tkwin, 0, 0, 542 butPtr->indicatorSpace + width, height, &x, &y); 543 x += butPtr->indicatorSpace; 544 545 if (relief == TK_RELIEF_SUNKEN) { 546 x += offset; 547 y += offset; 548 } 549 imageXOffset += x; 550 imageYOffset += y; 551 if (butPtr->image != NULL) { 552 if ((butPtr->selectImage != NULL) && 553 (butPtr->flags & SELECTED)) { 554 Tk_RedrawImage(butPtr->selectImage, 0, 0, width, height, 555 pixmap, imageXOffset, imageYOffset); 556 } else { 557 Tk_RedrawImage(butPtr->image, 0, 0, width, height, pixmap, 558 imageXOffset, imageYOffset); 559 } 560 } else { 561 XSetClipOrigin(butPtr->display, gc, x, y); 562 XCopyPlane(butPtr->display, butPtr->bitmap, pixmap, gc, 0, 0, 563 (unsigned int) width, (unsigned int) height, x, y, 1); 564 XSetClipOrigin(butPtr->display, gc, 0, 0); 565 } 566 } else { 567 TkComputeAnchor(butPtr->anchor, tkwin, butPtr->padX, butPtr->padY, 568 butPtr->indicatorSpace + butPtr->textWidth, 569 butPtr->textHeight, &x, &y); 570 571 x += butPtr->indicatorSpace; 572 573 if (relief == TK_RELIEF_SUNKEN) { 574 x += offset; 575 y += offset; 576 } 577 Tk_DrawTextLayout(butPtr->display, pixmap, gc, butPtr->textLayout, 578 x, y, 0, -1); 579 Tk_UnderlineTextLayout(butPtr->display, pixmap, gc, 580 butPtr->textLayout, x, y, butPtr->underline); 581 582 height = butPtr->textHeight; 583 drawRing = 1; 584 } 585 } 586 587 /* 588 * Draw the focus ring. If this is a push button then we need to 589 * put it around the inner edge of the border, otherwise we put it 590 * around the text. The text offsets are only non-zero when this 591 * is a compound button. 592 */ 593 594 if (drawRing && butPtr->flags & GOT_FOCUS && butPtr->type != TYPE_LABEL) { 595 dc = TkWinGetDrawableDC(butPtr->display, pixmap, &state); 596 if (butPtr->type == TYPE_BUTTON || !butPtr->indicatorOn) { 597 rect.top = butPtr->borderWidth + 1 + defaultWidth; 598 rect.left = rect.top; 599 rect.right = Tk_Width(tkwin) - rect.left; 600 rect.bottom = Tk_Height(tkwin) - rect.top; 601 } else { 602 rect.top = y-1 + textYOffset; 603 rect.left = x-1 + textXOffset; 604 rect.right = x+butPtr->textWidth + 1 + textXOffset; 605 rect.bottom = y+butPtr->textHeight + 2 + textYOffset; 606 } 607 SetTextColor(dc, gc->foreground); 608 SetBkColor(dc, gc->background); 609 DrawFocusRect(dc, &rect); 610 TkWinReleaseDrawableDC(pixmap, dc, &state); 611 } 612 613 y += height/2; 614 615 /* 616 * Draw the indicator for check buttons and radio buttons. At this 617 * point x and y refer to the top-left corner of the text or image 618 * or bitmap. 619 */ 620 621 if ((butPtr->type >= TYPE_CHECK_BUTTON) && butPtr->indicatorOn 622 && tsdPtr->boxesPtr) { 623 int xSrc, ySrc; 624 625 x -= butPtr->indicatorSpace; 626 y -= butPtr->indicatorDiameter / 2; 627 628 xSrc = (butPtr->flags & SELECTED) ? tsdPtr->boxWidth : 0; 629 if (butPtr->state == STATE_ACTIVE) { 630 xSrc += tsdPtr->boxWidth*2; 631 } 632 ySrc = (butPtr->type == TYPE_RADIO_BUTTON) ? 0 : tsdPtr->boxHeight; 633 634 /* 635 * Update the palette in the boxes bitmap to reflect the current 636 * button colors. Note that this code relies on the layout of the 637 * bitmap's palette. Also, all of the colors used to draw the 638 * bitmap must be in the palette that is selected into the DC of 639 * the offscreen pixmap. This requires that the static colors 640 * be placed into the palette. 641 */ 642 643 if ((butPtr->state == STATE_DISABLED) 644 && (butPtr->disabledFg == NULL)) { 645 boxesPalette[PAL_CHECK] = FlipColor(TkWinGetBorderPixels(tkwin, 646 border, TK_3D_DARK_GC)); 647 } else { 648 boxesPalette[PAL_CHECK] = FlipColor(gc->foreground); 649 } 650 boxesPalette[PAL_TOP_OUTER] = FlipColor(TkWinGetBorderPixels(tkwin, 651 border, TK_3D_DARK_GC)); 652 boxesPalette[PAL_TOP_INNER] = FlipColor(TkWinGetBorderPixels(tkwin, 653 border, TK_3D_DARK2)); 654 boxesPalette[PAL_BOTTOM_INNER] = FlipColor(TkWinGetBorderPixels(tkwin, 655 border, TK_3D_LIGHT2)); 656 boxesPalette[PAL_BOTTOM_OUTER] = FlipColor(TkWinGetBorderPixels(tkwin, 657 border, TK_3D_LIGHT_GC)); 658 if (butPtr->state == STATE_DISABLED) { 659 boxesPalette[PAL_INTERIOR] = FlipColor(TkWinGetBorderPixels(tkwin, 660 border, TK_3D_LIGHT2)); 661 } else if (butPtr->selectBorder != NULL) { 662 boxesPalette[PAL_INTERIOR] = FlipColor(TkWinGetBorderPixels(tkwin, 663 butPtr->selectBorder, TK_3D_FLAT_GC)); 664 } else { 665 boxesPalette[PAL_INTERIOR] = FlipColor(GetSysColor(COLOR_WINDOW)); 666 } 667 boxesPalette[PAL_BACKGROUND] = FlipColor(TkWinGetBorderPixels(tkwin, 668 border, TK_3D_FLAT_GC)); 669 670 dc = TkWinGetDrawableDC(butPtr->display, pixmap, &state); 671 StretchDIBits(dc, x, y, tsdPtr->boxWidth, tsdPtr->boxHeight, 672 xSrc, ySrc, tsdPtr->boxWidth, tsdPtr->boxHeight, 673 tsdPtr->boxesBits, (LPBITMAPINFO) tsdPtr->boxesPtr, 674 DIB_RGB_COLORS, SRCCOPY); 675 TkWinReleaseDrawableDC(pixmap, dc, &state); 676 } 677 678 /* 679 * If the button is disabled with a stipple rather than a special 680 * foreground color, generate the stippled effect. If the widget 681 * is selected and we use a different background color when selected, 682 * must temporarily modify the GC so the stippling is the right color. 683 */ 684 685 if ((butPtr->state == STATE_DISABLED) 686 && ((butPtr->disabledFg == NULL) || (butPtr->image != NULL))) { 687 if ((butPtr->flags & SELECTED) && !butPtr->indicatorOn 688 && (butPtr->selectBorder != NULL)) { 689 XSetForeground(butPtr->display, butPtr->stippleGC, 690 Tk_3DBorderColor(butPtr->selectBorder)->pixel); 691 } 692 /* 693 * Stipple the whole button if no disabledFg was specified, 694 * otherwise restrict stippling only to displayed image 695 */ 696 if (butPtr->disabledFg == NULL) { 697 XFillRectangle(butPtr->display, pixmap, butPtr->stippleGC, 0, 0, 698 (unsigned) Tk_Width(tkwin), (unsigned) Tk_Height(tkwin)); 699 } else { 700 XFillRectangle(butPtr->display, pixmap, butPtr->stippleGC, 701 imageXOffset, imageYOffset, 702 (unsigned) imageWidth, (unsigned) imageHeight); 703 } 704 if ((butPtr->flags & SELECTED) && !butPtr->indicatorOn 705 && (butPtr->selectBorder != NULL)) { 706 XSetForeground(butPtr->display, butPtr->stippleGC, 707 Tk_3DBorderColor(butPtr->normalBorder)->pixel); 708 } 709 } 710 711 /* 712 * Draw the border and traversal highlight last. This way, if the 713 * button's contents overflow they'll be covered up by the border. 714 */ 715 716 if (relief != TK_RELIEF_FLAT) { 717 Tk_Draw3DRectangle(tkwin, pixmap, border, 718 defaultWidth, defaultWidth, 719 Tk_Width(tkwin) - 2*defaultWidth, 720 Tk_Height(tkwin) - 2*defaultWidth, 721 butPtr->borderWidth, relief); 722 } 723 if (defaultWidth != 0) { 724 dc = TkWinGetDrawableDC(butPtr->display, pixmap, &state); 725 TkWinFillRect(dc, 0, 0, Tk_Width(tkwin), defaultWidth, 726 butPtr->highlightColorPtr->pixel); 727 TkWinFillRect(dc, 0, 0, defaultWidth, Tk_Height(tkwin), 728 butPtr->highlightColorPtr->pixel); 729 TkWinFillRect(dc, 0, Tk_Height(tkwin) - defaultWidth, 730 Tk_Width(tkwin), defaultWidth, 731 butPtr->highlightColorPtr->pixel); 732 TkWinFillRect(dc, Tk_Width(tkwin) - defaultWidth, 0, 733 defaultWidth, Tk_Height(tkwin), 734 butPtr->highlightColorPtr->pixel); 735 TkWinReleaseDrawableDC(pixmap, dc, &state); 736 } 737 738 if (butPtr->flags & GOT_FOCUS) { 739 Tk_SetCaretPos(tkwin, x, y, 0 /* not used */); 740 } 741 742 /* 743 * Copy the information from the off-screen pixmap onto the screen, 744 * then delete the pixmap. 745 */ 746 747 XCopyArea(butPtr->display, pixmap, Tk_WindowId(tkwin), 748 butPtr->copyGC, 0, 0, (unsigned) Tk_Width(tkwin), 749 (unsigned) Tk_Height(tkwin), 0, 0); 750 Tk_FreePixmap(butPtr->display, pixmap); 751} 752 753/* 754 *---------------------------------------------------------------------- 755 * 756 * TkpComputeButtonGeometry -- 757 * 758 * After changes in a button's text or bitmap, this procedure 759 * recomputes the button's geometry and passes this information 760 * along to the geometry manager for the window. 761 * 762 * Results: 763 * None. 764 * 765 * Side effects: 766 * The button's window may change size. 767 * 768 *---------------------------------------------------------------------- 769 */ 770 771void 772TkpComputeButtonGeometry(butPtr) 773 register TkButton *butPtr; /* Button whose geometry may have changed. */ 774{ 775 int txtWidth, txtHeight; /* Width and height of text */ 776 int imgWidth, imgHeight; /* Width and height of image */ 777 int width = 0, height = 0; /* Width and height of button */ 778 int haveImage, haveText; 779 int avgWidth; 780 int minWidth; 781 /* Vertical and horizontal dialog units size in pixels. */ 782 double vDLU, hDLU; 783 Tk_FontMetrics fm; 784 785 ThreadSpecificData *tsdPtr = (ThreadSpecificData *) 786 Tcl_GetThreadData(&dataKey, sizeof(ThreadSpecificData)); 787 788 if (butPtr->highlightWidth < 0) { 789 butPtr->highlightWidth = 0; 790 } 791 butPtr->inset = butPtr->highlightWidth + butPtr->borderWidth; 792 butPtr->indicatorSpace = 0; 793 794 if (!tsdPtr->boxesPtr) { 795 InitBoxes(); 796 } 797 798 /* Figure out image metrics */ 799 if (butPtr->image != NULL) { 800 Tk_SizeOfImage(butPtr->image, &imgWidth, &imgHeight); 801 haveImage = 1; 802 } else if (butPtr->bitmap != None) { 803 Tk_SizeOfBitmap(butPtr->display, butPtr->bitmap, 804 &imgWidth, &imgHeight); 805 haveImage = 1; 806 } else { 807 imgWidth = 0; 808 imgHeight = 0; 809 haveImage = 0; 810 } 811 812 /* 813 * Figure out font metrics (even if we don't have text because we need 814 * DLUs (based on font, not text) for some spacing calculations below). 815 */ 816 Tk_FreeTextLayout(butPtr->textLayout); 817 butPtr->textLayout = Tk_ComputeTextLayout(butPtr->tkfont, 818 Tcl_GetString(butPtr->textPtr), -1, butPtr->wrapLength, 819 butPtr->justify, 0, &butPtr->textWidth, &butPtr->textHeight); 820 821 txtWidth = butPtr->textWidth; 822 txtHeight = butPtr->textHeight; 823 haveText = (*(Tcl_GetString(butPtr->textPtr)) != '\0'); 824 avgWidth = (Tk_TextWidth(butPtr->tkfont, 825 "abcdefghijklmnopqurstuvwzyABCDEFGHIJKLMNOPQURSTUVWZY", 826 52) + 26) / 52; 827 Tk_GetFontMetrics(butPtr->tkfont, &fm); 828 829 /* Compute dialog units for layout calculations. */ 830 hDLU = avgWidth / 4.0; 831 vDLU = fm.linespace / 8.0; 832 833 /* 834 * First, let's try to compute button size "by the book" (See "Microsoft 835 * Windows User Experience" (ISBN 0-7356-0566-1), Chapter 14 - Visual 836 * Design, Section 4 - Layout (page 448)). 837 * 838 * Note, that Tk "buttons" are Microsoft "Command buttons", Tk 839 * "checkbuttons" are Microsoft "check boxes", Tk "radiobuttons" are 840 * Microsoft "option buttons", and Tk "labels" are Microsoft "text 841 * labels". 842 */ 843 844 /* 845 * Set width and height by button type; See User Experience table, p449. 846 * These are text-based measurements, even if the text is "". 847 * If there is an image, height will get set again later. 848 */ 849 switch (butPtr->type) { 850 case TYPE_BUTTON: { 851 /* 852 * First compute the minimum width of the button in 853 * characters. MWUE says that the button should be 854 * 50 DLUs. We allow 6 DLUs padding left and right. 855 * (There is no rule but this is consistent with the 856 * fact that button text is 8 DLUs high and buttons 857 * are 14 DLUs high.) 858 * 859 * The width is specified in characters. A character 860 * is, by definition, 4 DLUs wide. 11 char * 4 DLU 861 * is 44 DLU + 6 DLU padding = 50 DLU. Therefore, 862 * width = -11 -> MWUE compliant buttons. 863 */ 864 if (butPtr->width < 0) { 865 /* Min width in characters */ 866 minWidth = -(butPtr->width); 867 /* Allow for characters */ 868 width = avgWidth * minWidth; 869 /* Add for padding */ 870 width += (int)(0.5 + (6 * hDLU)); 871 } 872 873 /* 874 * If shrink-wrapping was requested (width = 0) or 875 * if the text is wider than the default button width, 876 * adjust the button width up to suit. 877 */ 878 if (butPtr->width == 0 879 || (txtWidth + (int)(0.5 + (6 * hDLU)) > width)) { 880 width = txtWidth + (int)(0.5 + (6 * hDLU)); 881 } 882 883 /* 884 * The User Experience says 14 DLUs. Since text is, by 885 * definition, 8 DLU/line, this allows for multi-line text 886 * while working perfectly for single-line text. 887 */ 888 height = txtHeight + (int)(0.5 + (6 * vDLU)); 889 890 /* 891 * The above includes 6 DLUs of padding which should include 892 * defaults of 1 pixel of highlightwidth, 2 pixels of 893 * borderwidth, 1 pixel of padding and 1 pixel of extra inset 894 * on each side. Those will be added later so reduce width 895 * and height now to compensate. 896 */ 897 width -= 10; 898 height -= 10; 899 900 if (!haveImage) { 901 /* 902 * Extra inset for the focus ring. 903 */ 904 butPtr->inset += 1; 905 } 906 break; 907 } 908 909 case TYPE_LABEL: { 910 /* 911 * The User Experience says, "as wide as needed". 912 */ 913 width = txtWidth; 914 915 /* 916 * The User Experience says, "8 (DLUs) per line of text." 917 * Since text is, by definition, 8 DLU/line, this allows 918 * for multi-line text while working perfectly for single-line 919 * text. 920 */ 921 if (txtHeight) { 922 height = txtHeight; 923 } else { 924 /* 925 * If there's no text, we want the height to be one linespace. 926 */ 927 height = fm.linespace; 928 } 929 break; 930 } 931 932 case TYPE_RADIO_BUTTON: 933 case TYPE_CHECK_BUTTON: { 934 /* See note for TYPE_LABEL */ 935 width = txtWidth; 936 /* 937 * The User Experience says 10 DLUs. (Is that one DLU above 938 * and below for the focus ring?) See note above about 939 * multi-line text and 8 DLU/line. 940 */ 941 height = txtHeight + (int)(0.5 + (2.0 * vDLU)); 942 943 /* 944 * The above includes 2 DLUs of padding which should include 945 * defaults of 1 pixel of highlightwidth, 0 pixels of 946 * borderwidth, and 1 pixel of padding on each side. Those 947 * will be added later so reduce height now to compensate. 948 */ 949 height -= 4; 950 951 /* 952 * Extra inset for the focus ring. 953 */ 954 butPtr->inset += 1; 955 break; 956 } 957 }/* switch */ 958 959 /* 960 * At this point, the width and height are correct for a Tk text 961 * button, excluding padding and inset, but we have to allow for 962 * compound buttons. The image may be above, below, left, or right 963 * of the text. 964 */ 965 966 /* 967 * If the button is compound (i.e., it shows both an image and text), 968 * the new geometry is a combination of the image and text geometry. 969 * We only honor the compound bit if the button has both text and an 970 * image, because otherwise it is not really a compound button. 971 */ 972 if (butPtr->compound != COMPOUND_NONE && haveImage && haveText) { 973 switch ((enum compound) butPtr->compound) { 974 case COMPOUND_TOP: 975 case COMPOUND_BOTTOM: { 976 /* Image is above or below text */ 977 if (imgWidth > width) { 978 width = imgWidth; 979 } 980 height += imgHeight + butPtr->padY; 981 break; 982 } 983 case COMPOUND_LEFT: 984 case COMPOUND_RIGHT: { 985 /* Image is left or right of text */ 986 /* 987 * Only increase width of button if image doesn't fit in 988 * slack space of default button width 989 */ 990 if ((imgWidth + txtWidth + butPtr->padX) > width) { 991 width = imgWidth + txtWidth + butPtr->padX; 992 } 993 994 if (imgHeight > height) { 995 height = imgHeight; 996 } 997 break; 998 } 999 case COMPOUND_CENTER: { 1000 /* Image and text are superimposed */ 1001 if (imgWidth > width) { 1002 width = imgWidth; 1003 } 1004 if (imgHeight > height) { 1005 height = imgHeight; 1006 } 1007 break; 1008 } 1009 } /* switch */ 1010 1011 /* Fix up for minimum width */ 1012 if (butPtr->width < 0) { 1013 /* minWidth in pixels (because there's an image */ 1014 minWidth = -(butPtr->width); 1015 if (width < minWidth) { 1016 width = minWidth; 1017 } 1018 } else if (butPtr->width > 0) { 1019 width = butPtr->width; 1020 } 1021 1022 if (butPtr->height > 0) { 1023 height = butPtr->height; 1024 } 1025 1026 width += 2*butPtr->padX; 1027 height += 2*butPtr->padY; 1028 } else if (haveImage) { 1029 if (butPtr->width > 0) { 1030 width = butPtr->width; 1031 } else { 1032 width = imgWidth; 1033 } 1034 if (butPtr->height > 0) { 1035 height = butPtr->height; 1036 } else { 1037 height = imgHeight; 1038 } 1039 } else { 1040 /* No image. May or may not be text. May or may not be compound. */ 1041 1042 /* 1043 * butPtr->width is in characters. We need to allow for that 1044 * many characters on the face, not in the over-all button width 1045 */ 1046 if (butPtr->width > 0) { 1047 width = butPtr->width * avgWidth; 1048 } 1049 1050 /* 1051 * butPtr->height is in lines of text. We need to allow for 1052 * that many lines on the face, not in the over-all button 1053 * height. 1054 */ 1055 if (butPtr->height > 0) { 1056 height = butPtr->height * fm.linespace; 1057 1058 /* 1059 * Make the same adjustments as above to get same height for 1060 * e.g. a one line text with -height 0 or 1. [Bug #565485] 1061 */ 1062 1063 switch (butPtr->type) { 1064 case TYPE_BUTTON: { 1065 height += (int)(0.5 + (6 * vDLU)) - 10; 1066 break; 1067 } 1068 case TYPE_RADIO_BUTTON: 1069 case TYPE_CHECK_BUTTON: { 1070 height += (int)(0.5 + (2.0 * vDLU)) - 4; 1071 break; 1072 } 1073 } 1074 } 1075 1076 width += 2 * butPtr->padX; 1077 height += 2 * butPtr->padY; 1078 } 1079 1080 /* Fix up width and height for indicator sizing and spacing */ 1081 if (butPtr->type == TYPE_RADIO_BUTTON 1082 || butPtr->type == TYPE_CHECK_BUTTON) { 1083 if (butPtr->indicatorOn) { 1084 butPtr->indicatorDiameter = tsdPtr->boxHeight; 1085 1086 /* 1087 * Make sure we can see the whole indicator, even if the text 1088 * or image is very small. 1089 */ 1090 if (height < butPtr->indicatorDiameter) { 1091 height = butPtr->indicatorDiameter; 1092 } 1093 1094 /* 1095 * There is no rule for space between the indicator and 1096 * the text (the two are atomic on 'Windows) but the User 1097 * Experience page 451 says leave 3 hDLUs between "text 1098 * labels and their associated controls". 1099 */ 1100 butPtr->indicatorSpace = butPtr->indicatorDiameter + 1101 (int)(0.5 + (3.0 * hDLU)); 1102 width += butPtr->indicatorSpace; 1103 } 1104 } 1105 1106 /* 1107 * Inset is always added to the size. 1108 */ 1109 width += 2 * butPtr->inset; 1110 height += 2 * butPtr->inset; 1111 1112 Tk_GeometryRequest(butPtr->tkwin, width, height); 1113 Tk_SetInternalBorder(butPtr->tkwin, butPtr->inset); 1114} 1115 1116/* 1117 *---------------------------------------------------------------------- 1118 * 1119 * ButtonProc -- 1120 * 1121 * This function is call by Windows whenever an event occurs on 1122 * a button control created by Tk. 1123 * 1124 * Results: 1125 * Standard Windows return value. 1126 * 1127 * Side effects: 1128 * May generate events. 1129 * 1130 *---------------------------------------------------------------------- 1131 */ 1132 1133static LRESULT CALLBACK 1134ButtonProc(hwnd, message, wParam, lParam) 1135 HWND hwnd; 1136 UINT message; 1137 WPARAM wParam; 1138 LPARAM lParam; 1139{ 1140 LRESULT result; 1141 WinButton *butPtr; 1142 Tk_Window tkwin = Tk_HWNDToWindow(hwnd); 1143 1144 if (tkwin == NULL) { 1145 panic("ButtonProc called on an invalid HWND"); 1146 } 1147 butPtr = (WinButton *)((TkWindow*)tkwin)->instanceData; 1148 1149 switch(message) { 1150 case WM_ERASEBKGND: 1151 return 0; 1152 1153 case BM_GETCHECK: 1154 if (((butPtr->info.type == TYPE_CHECK_BUTTON) 1155 || (butPtr->info.type == TYPE_RADIO_BUTTON)) 1156 && butPtr->info.indicatorOn) { 1157 return (butPtr->info.flags & SELECTED) 1158 ? BST_CHECKED : BST_UNCHECKED; 1159 } 1160 return 0; 1161 1162 case BM_GETSTATE: { 1163 DWORD state = 0; 1164 if (((butPtr->info.type == TYPE_CHECK_BUTTON) 1165 || (butPtr->info.type == TYPE_RADIO_BUTTON)) 1166 && butPtr->info.indicatorOn) { 1167 state = (butPtr->info.flags & SELECTED) 1168 ? BST_CHECKED : BST_UNCHECKED; 1169 } 1170 if (butPtr->info.flags & GOT_FOCUS) { 1171 state |= BST_FOCUS; 1172 } 1173 return state; 1174 } 1175 case WM_ENABLE: 1176 break; 1177 1178 case WM_PAINT: { 1179 PAINTSTRUCT ps; 1180 BeginPaint(hwnd, &ps); 1181 EndPaint(hwnd, &ps); 1182 TkpDisplayButton((ClientData)butPtr); 1183 1184 /* 1185 * Special note: must cancel any existing idle handler 1186 * for TkpDisplayButton; it's no longer needed, and 1187 * TkpDisplayButton cleared the REDRAW_PENDING flag. 1188 */ 1189 1190 Tcl_CancelIdleCall(TkpDisplayButton, (ClientData)butPtr); 1191 return 0; 1192 } 1193 case BN_CLICKED: { 1194 int code; 1195 Tcl_Interp *interp = butPtr->info.interp; 1196 if (butPtr->info.state != STATE_DISABLED) { 1197 Tcl_Preserve((ClientData)interp); 1198 code = TkInvokeButton((TkButton*)butPtr); 1199 if (code != TCL_OK && code != TCL_CONTINUE 1200 && code != TCL_BREAK) { 1201 Tcl_AddErrorInfo(interp, "\n (button invoke)"); 1202 Tcl_BackgroundError(interp); 1203 } 1204 Tcl_Release((ClientData)interp); 1205 } 1206 Tcl_ServiceAll(); 1207 return 0; 1208 } 1209 1210 default: 1211 if (Tk_TranslateWinEvent(hwnd, message, wParam, lParam, &result)) { 1212 return result; 1213 } 1214 } 1215 return DefWindowProc(hwnd, message, wParam, lParam); 1216} 1217