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