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