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