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