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