1/*
2 * tkUnixButton.c --
3 *
4 *	This file implements the Unix specific portion of the button widgets.
5 *
6 * Copyright (c) 1996-1997 by Sun Microsystems, Inc.
7 *
8 * See the file "license.terms" for information on usage and redistribution of
9 * this file, and for a DISCLAIMER OF ALL WARRANTIES.
10 *
11 * RCS: @(#) $Id$
12 */
13
14#include "tkInt.h"
15#include "tkButton.h"
16#include "tk3d.h"
17
18/*
19 * Shared with menu widget.
20 */
21
22MODULE_SCOPE void	TkpDrawCheckIndicator(Tk_Window tkwin,
23			    Display *display, Drawable d, int x, int y,
24			    Tk_3DBorder bgBorder, XColor *indicatorColor,
25			    XColor *selectColor, XColor *disColor, int on,
26			    int disabled, int mode);
27
28/*
29 * Declaration of Unix specific button structure.
30 */
31
32typedef struct UnixButton {
33    TkButton info;		/* Generic button info. */
34} UnixButton;
35
36/*
37 * The class function table for the button widgets.
38 */
39
40Tk_ClassProcs tkpButtonProcs = {
41    sizeof(Tk_ClassProcs),	/* size */
42    TkButtonWorldChanged,	/* worldChangedProc */
43};
44
45/*
46 * The button image.
47 * The header info here is ignored, it's the image that's important. The
48 * colors will be applied as follows:
49 *   A = Background
50 *   B = Background
51 *   C = 3D light
52 *   D = selectColor
53 *   E = 3D dark
54 *   F = Background
55 *   G = Indicator Color
56 *   H = disabled Indicator Color
57 */
58
59/* XPM */
60static char *button_images[] = {
61    /* width height ncolors chars_per_pixel */
62    "52 26 7 1",
63    /* colors */
64    "A c #808000000000",
65    "B c #000080800000",
66    "C c #808080800000",
67    "D c #000000008080",
68    "E c #808000008080",
69    "F c #000080808080",
70    "G c #000000000000",
71    "H c #000080800000",
72    /* pixels */
73    "AAAAAAAAAAAABAAAAAAAAAAAABAAAAAAAAAAAABAAAAAAAAAAAAB",
74    "AEEEEEEEEEECBAEEEEEEEEEECBAEEEEEEEEEECBAEEEEEEEEEECB",
75    "AEDDDDDDDDDCBAEDDDDDDDDDCBAEFFFFFFFFFCBAEFFFFFFFFFCB",
76    "AEDDDDDDDDDCBAEDDDDDDDGDCBAEFFFFFFFFFCBAEFFFFFFFHFCB",
77    "AEDDDDDDDDDCBAEDDDDDDGGDCBAEFFFFFFFFFCBAEFFFFFFHHFCB",
78    "AEDDDDDDDDDCBAEDGDDDGGGDCBAEFFFFFFFFFCBAEFHFFFHHHFCB",
79    "AEDDDDDDDDDCBAEDGGDGGGDDCBAEFFFFFFFFFCBAEFHHFHHHFFCB",
80    "AEDDDDDDDDDCBAEDGGGGGDDDCBAEFFFFFFFFFCBAEFHHHHHFFFCB",
81    "AEDDDDDDDDDCBAEDDGGGDDDDCBAEFFFFFFFFFCBAEFFHHHFFFFCB",
82    "AEDDDDDDDDDCBAEDDDGDDDDDCBAEFFFFFFFFFCBAEFFFHFFFFFCB",
83    "AEDDDDDDDDDCBAEDDDDDDDDDCBAEFFFFFFFFFCBAEFFFFFFFFFCB",
84    "ACCCCCCCCCCCBACCCCCCCCCCCBACCCCCCCCCCCBACCCCCCCCCCCB",
85    "BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB",
86    "FFFFAAAAFFFFFFFFFAAAAFFFFFFFFFAAAAFFFFFFFFFAAAAFFFFF",
87    "FFAAEEEEAAFFFFFAAEEEEAAFFFFFAAEEEEAAFFFFFAAEEEEAAFFF",
88    "FAEEDDDDEEBFFFAEEDDDDEEBFFFAEEFFFFEEBFFFAEEFFFFEEBFF",
89    "FAEDDDDDDCBFFFAEDDDDDDCBFFFAEFFFFFFCBFFFAEFFFFFFCBFF",
90    "AEDDDDDDDDCBFAEDDDGGDDDCBFAEFFFFFFFFCBFAEFFFHHFFFCBF",
91    "AEDDDDDDDDCBFAEDDGGGGDDCBFAEFFFFFFFFCBFAEFFHHHHFFCBF",
92    "AEDDDDDDDDCBFAEDDGGGGDDCBFAEFFFFFFFFCBFAEFFHHHHFFCBF",
93    "AEDDDDDDDDCBFAEDDDGGDDDCBFAEFFFFFFFFCBFAEFFFHHFFFCBF",
94    "FAEDDDDDDCBFFFAEDDDDDDCBFFFAEFFFFFFCBFFFAEFFFFFFCBFF",
95    "FACCDDDDCCBFFFACCDDDDCCBFFFACCFFFFCCBFFFACCFFFFCCBFF",
96    "FFBBCCCCBBFFFFFBBCCCCBBFFFFFBBCCCCBBFFFFFBBCCCCBBFFF",
97    "FFFFBBBBFFFFFFFFFBBBBFFFFFFFFFBBBBFFFFFFFFFBBBBFFFFF",
98    "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF",
99};
100
101/*
102 * Sizes and offsets into above XPM file.
103 */
104
105#define CHECK_BUTTON_DIM    13
106#define CHECK_MENU_DIM       9
107#define CHECK_START          9
108#define CHECK_ON_OFFSET     13
109#define CHECK_OFF_OFFSET     0
110#define CHECK_DISON_OFFSET  39
111#define CHECK_DISOFF_OFFSET 26
112#define RADIO_BUTTON_DIM    12
113#define RADIO_MENU_DIM       6
114#define RADIO_WIDTH         13
115#define RADIO_START         22
116#define RADIO_ON_OFFSET     13
117#define RADIO_OFF_OFFSET     0
118#define RADIO_DISON_OFFSET  39
119#define RADIO_DISOFF_OFFSET 26
120
121/*
122 * Indicator Draw Modes
123 */
124
125#define CHECK_BUTTON 0
126#define CHECK_MENU   1
127#define RADIO_BUTTON 2
128#define RADIO_MENU   3
129
130/*
131 *----------------------------------------------------------------------
132 *
133 * TkpDrawCheckIndicator -
134 *
135 *	Draws the checkbox image in the drawable at the (x,y) location, value,
136 *	and state given. This routine is use by the button and menu widgets
137 *
138 * Results:
139 *	None.
140 *
141 * Side effects:
142 *	An image is drawn in the drawable at the location given.
143 *
144 *----------------------------------------------------------------------
145 */
146
147void
148TkpDrawCheckIndicator(
149    Tk_Window tkwin,		/* handle for resource alloc */
150    Display *display,
151    Drawable d,			/* what to draw on */
152    int x, int y,		/* where to draw */
153    Tk_3DBorder bgBorder,	/* colors of the border */
154    XColor *indicatorColor,	/* color of the indicator */
155    XColor *selectColor,	/* color when selected */
156    XColor *disableColor,	/* color when disabled */
157    int on,			/* are we on? */
158    int disabled,		/* are we disabled? */
159    int mode)			/* kind of indicator to draw */
160{
161    int ix, iy;
162    int dim;
163    int imgsel, imgstart;
164    TkBorder *bg_brdr = (TkBorder*)bgBorder;
165    XGCValues gcValues;
166    GC copyGC;
167    unsigned long imgColors[8];
168    XImage *img;
169    Pixmap pixmap;
170    int depth;
171
172    /*
173     * Sanity check.
174     */
175
176    if (tkwin == NULL || display == None || d == None || bgBorder == NULL
177	    || indicatorColor == NULL) {
178	return;
179    }
180
181    if (disableColor == NULL) {
182	disableColor = bg_brdr->bgColorPtr;
183    }
184
185    if (selectColor == NULL) {
186	selectColor = bg_brdr->bgColorPtr;
187    }
188
189    depth = Tk_Depth(tkwin);
190
191    /*
192     * Compute starting point and dimensions of image inside button_images to
193     * be used.
194     */
195
196    switch (mode) {
197    default:
198    case CHECK_BUTTON:
199	imgsel = on == 2 ? CHECK_DISON_OFFSET :
200		on == 1 ? CHECK_ON_OFFSET : CHECK_OFF_OFFSET;
201	imgsel += disabled && on != 2 ? CHECK_DISOFF_OFFSET : 0;
202	imgstart = CHECK_START;
203	dim = CHECK_BUTTON_DIM;
204	break;
205
206    case CHECK_MENU:
207	imgsel = on == 2 ? CHECK_DISOFF_OFFSET :
208		on == 1 ? CHECK_ON_OFFSET : CHECK_OFF_OFFSET;
209	imgsel += disabled && on != 2 ? CHECK_DISOFF_OFFSET : 0;
210	imgstart = CHECK_START + 2;
211	imgsel += 2;
212	dim = CHECK_MENU_DIM;
213	break;
214
215    case RADIO_BUTTON:
216	imgsel = on == 2 ? RADIO_DISON_OFFSET :
217		on==1 ? RADIO_ON_OFFSET : RADIO_OFF_OFFSET;
218	imgsel += disabled && on != 2 ? RADIO_DISOFF_OFFSET : 0;
219	imgstart = RADIO_START;
220	dim = RADIO_BUTTON_DIM;
221	break;
222
223    case RADIO_MENU:
224	imgsel = on == 2 ? RADIO_DISOFF_OFFSET :
225		on==1 ? RADIO_ON_OFFSET : RADIO_OFF_OFFSET;
226	imgsel += disabled && on != 2 ? RADIO_DISOFF_OFFSET : 0;
227	imgstart = RADIO_START + 3;
228	imgsel += 3;
229	dim = RADIO_MENU_DIM;
230	break;
231    }
232
233    /*
234     * Allocate the drawing areas to use. Note that we use double-buffering
235     * here because not all code paths leading to this function do so.
236     */
237
238    pixmap = Tk_GetPixmap(display, d, dim, dim, depth);
239    if (pixmap == None) {
240	return;
241    }
242
243    x -= dim/2;
244    y -= dim/2;
245
246    img = XGetImage(display, pixmap, 0, 0,
247	    (unsigned int)dim, (unsigned int)dim, AllPlanes, ZPixmap);
248    if (img == NULL) {
249	return;
250    }
251
252    /*
253     * Set up the color mapping table.
254     */
255
256    TkpGetShadows(bg_brdr, tkwin);
257
258    imgColors[0 /*A*/] =
259	    Tk_GetColorByValue(tkwin, bg_brdr->bgColorPtr)->pixel;
260    imgColors[1 /*B*/] =
261	    Tk_GetColorByValue(tkwin, bg_brdr->bgColorPtr)->pixel;
262    imgColors[2 /*C*/] = (bg_brdr->lightColorPtr != NULL) ?
263	    Tk_GetColorByValue(tkwin, bg_brdr->lightColorPtr)->pixel :
264	    WhitePixelOfScreen(bg_brdr->screen);
265    imgColors[3 /*D*/] =
266	    Tk_GetColorByValue(tkwin, selectColor)->pixel;
267    imgColors[4 /*E*/] = (bg_brdr->darkColorPtr != NULL) ?
268	    Tk_GetColorByValue(tkwin, bg_brdr->darkColorPtr)->pixel :
269	    BlackPixelOfScreen(bg_brdr->screen);
270    imgColors[5 /*F*/] =
271	    Tk_GetColorByValue(tkwin, bg_brdr->bgColorPtr)->pixel;
272    imgColors[6 /*G*/] =
273	    Tk_GetColorByValue(tkwin, indicatorColor)->pixel;
274    imgColors[7 /*H*/] =
275	    Tk_GetColorByValue(tkwin, disableColor)->pixel;
276
277    /*
278     * Create the image, painting it into an XImage one pixel at a time.
279     */
280
281    for (iy=0 ; iy<dim ; iy++) {
282	for (ix=0 ; ix<dim ; ix++) {
283	    XPutPixel(img, ix, iy,
284		    imgColors[button_images[imgstart+iy][imgsel+ix] - 'A'] );
285	}
286    }
287
288    /*
289     * Copy onto our target drawable surface.
290     */
291
292    memset(&gcValues, 0, sizeof(gcValues));
293    gcValues.background = bg_brdr->bgColorPtr->pixel;
294    gcValues.graphics_exposures = False;
295    copyGC = Tk_GetGC(tkwin, 0, &gcValues);
296
297    XPutImage(display, pixmap, copyGC, img, 0, 0, 0, 0,
298	    (unsigned int)dim, (unsigned int)dim);
299    XCopyArea(display, pixmap, d, copyGC, 0, 0,
300	    (unsigned int)dim, (unsigned int)dim, x, y);
301
302    /*
303     * Tidy up.
304     */
305
306    Tk_FreeGC(display, copyGC);
307    XDestroyImage(img);
308    Tk_FreePixmap(display, pixmap);
309}
310
311/*
312 *----------------------------------------------------------------------
313 *
314 * TkpCreateButton --
315 *
316 *	Allocate a new TkButton structure.
317 *
318 * Results:
319 *	Returns a newly allocated TkButton structure.
320 *
321 * Side effects:
322 *	Registers an event handler for the widget.
323 *
324 *----------------------------------------------------------------------
325 */
326
327TkButton *
328TkpCreateButton(
329    Tk_Window tkwin)
330{
331    UnixButton *butPtr = (UnixButton *) ckalloc(sizeof(UnixButton));
332    return (TkButton *) butPtr;
333}
334
335/*
336 *----------------------------------------------------------------------
337 *
338 * TkpDisplayButton --
339 *
340 *	This function is invoked to display a button widget. It is normally
341 *	invoked as an idle handler.
342 *
343 * Results:
344 *	None.
345 *
346 * Side effects:
347 *	Commands are output to X to display the button in its current mode.
348 *	The REDRAW_PENDING flag is cleared.
349 *
350 *----------------------------------------------------------------------
351 */
352
353void
354TkpDisplayButton(
355    ClientData clientData)	/* Information about widget. */
356{
357    register TkButton *butPtr = (TkButton *) clientData;
358    GC gc;
359    Tk_3DBorder border;
360    Pixmap pixmap;
361    int x = 0;			/* Initialization only needed to stop compiler
362				 * warning. */
363    int y, relief;
364    Tk_Window tkwin = butPtr->tkwin;
365    int width, height, fullWidth, fullHeight;
366    int textXOffset, textYOffset;
367    int haveImage = 0, haveText = 0;
368    int offset;			/* 1 means this is a button widget, so we
369				 * offset the text to make the button appear
370				 * to move up and down as the relief
371				 * changes. */
372    int imageWidth, imageHeight;
373    int imageXOffset = 0, imageYOffset = 0;
374				/* image information that will be used to
375				 * restrict disabled pixmap as well */
376
377    butPtr->flags &= ~REDRAW_PENDING;
378    if ((butPtr->tkwin == NULL) || !Tk_IsMapped(tkwin)) {
379	return;
380    }
381
382    border = butPtr->normalBorder;
383    if ((butPtr->state == STATE_DISABLED) && (butPtr->disabledFg != NULL)) {
384	gc = butPtr->disabledGC;
385    } else if ((butPtr->state == STATE_ACTIVE)
386	    && !Tk_StrictMotif(butPtr->tkwin)) {
387	gc = butPtr->activeTextGC;
388	border = butPtr->activeBorder;
389    } else {
390	gc = butPtr->normalTextGC;
391    }
392    if ((butPtr->flags & SELECTED) && (butPtr->selectBorder != NULL)
393	    && !butPtr->indicatorOn) {
394	border = butPtr->selectBorder;
395    }
396
397    /*
398     * Override the relief specified for the button if this is a checkbutton
399     * or radiobutton and there's no indicator. The new relief is as follows:
400     *      If the button is select  --> "sunken"
401     *      If relief==overrelief    --> relief
402     *      Otherwise                --> overrelief
403     *
404     * The effect we are trying to achieve is as follows:
405     *
406     *      value    mouse-over?   -->   relief
407     *     -------  ------------        --------
408     *       off        no               flat
409     *       off        yes              raised
410     *       on         no               sunken
411     *       on         yes              sunken
412     *
413     * This is accomplished by configuring the checkbutton or radiobutton like
414     * this:
415     *
416     *     -indicatoron 0 -overrelief raised -offrelief flat
417     *
418     * Bindings (see library/button.tcl) will copy the -overrelief into
419     * -relief on mouseover. Hence, we can tell if we are in mouse-over by
420     * comparing relief against overRelief. This is an aweful kludge, but it
421     * gives use the desired behavior while keeping the code backwards
422     * compatible.
423     */
424
425    relief = butPtr->relief;
426    if ((butPtr->type >= TYPE_CHECK_BUTTON) && !butPtr->indicatorOn) {
427	if (butPtr->flags & SELECTED) {
428	    relief = TK_RELIEF_SUNKEN;
429	} else if (butPtr->overRelief != relief) {
430	    relief = butPtr->offRelief;
431	}
432    }
433
434    offset = (butPtr->type == TYPE_BUTTON) && !Tk_StrictMotif(butPtr->tkwin);
435
436    /*
437     * In order to avoid screen flashes, this function redraws the button in a
438     * pixmap, then copies the pixmap to the screen in a single operation.
439     * This means that there's no point in time where the on-screen image has
440     * been cleared.
441     */
442
443    pixmap = Tk_GetPixmap(butPtr->display, Tk_WindowId(tkwin),
444	    Tk_Width(tkwin), Tk_Height(tkwin), Tk_Depth(tkwin));
445    Tk_Fill3DRectangle(tkwin, pixmap, border, 0, 0, Tk_Width(tkwin),
446	    Tk_Height(tkwin), 0, TK_RELIEF_FLAT);
447
448    /*
449     * Display image or bitmap or text for button.
450     */
451
452    if (butPtr->image != NULL) {
453	Tk_SizeOfImage(butPtr->image, &width, &height);
454	haveImage = 1;
455    } else if (butPtr->bitmap != None) {
456	Tk_SizeOfBitmap(butPtr->display, butPtr->bitmap, &width, &height);
457	haveImage = 1;
458    }
459    imageWidth = width;
460    imageHeight = height;
461
462    haveText = (butPtr->textWidth != 0 && butPtr->textHeight != 0);
463
464    if (butPtr->compound != COMPOUND_NONE && haveImage && haveText) {
465	textXOffset = 0;
466	textYOffset = 0;
467	fullWidth = 0;
468	fullHeight = 0;
469
470	switch ((enum compound) butPtr->compound) {
471	case COMPOUND_TOP:
472	case COMPOUND_BOTTOM:
473	    /*
474	     * Image is above or below text.
475	     */
476
477	    if (butPtr->compound == COMPOUND_TOP) {
478		textYOffset = height + butPtr->padY;
479	    } else {
480		imageYOffset = butPtr->textHeight + butPtr->padY;
481	    }
482	    fullHeight = height + butPtr->textHeight + butPtr->padY;
483	    fullWidth = (width > butPtr->textWidth ? width :
484		    butPtr->textWidth);
485	    textXOffset = (fullWidth - butPtr->textWidth)/2;
486	    imageXOffset = (fullWidth - width)/2;
487	    break;
488	case COMPOUND_LEFT:
489	case COMPOUND_RIGHT:
490	    /*
491	     * Image is left or right of text.
492	     */
493
494	    if (butPtr->compound == COMPOUND_LEFT) {
495		textXOffset = width + butPtr->padX;
496	    } else {
497		imageXOffset = butPtr->textWidth + butPtr->padX;
498	    }
499	    fullWidth = butPtr->textWidth + butPtr->padX + width;
500	    fullHeight = (height > butPtr->textHeight ? height :
501		    butPtr->textHeight);
502	    textYOffset = (fullHeight - butPtr->textHeight)/2;
503	    imageYOffset = (fullHeight - height)/2;
504	    break;
505	case COMPOUND_CENTER:
506	    /*
507	     * Image and text are superimposed.
508	     */
509
510	    fullWidth = (width > butPtr->textWidth ? width :
511		    butPtr->textWidth);
512	    fullHeight = (height > butPtr->textHeight ? height :
513		    butPtr->textHeight);
514	    textXOffset = (fullWidth - butPtr->textWidth)/2;
515	    imageXOffset = (fullWidth - width)/2;
516	    textYOffset = (fullHeight - butPtr->textHeight)/2;
517	    imageYOffset = (fullHeight - height)/2;
518	    break;
519	case COMPOUND_NONE:
520	    break;
521	}
522
523	TkComputeAnchor(butPtr->anchor, tkwin, butPtr->padX, butPtr->padY,
524		butPtr->indicatorSpace + fullWidth, fullHeight, &x, &y);
525
526	x += butPtr->indicatorSpace;
527
528	x += offset;
529	y += offset;
530	if (relief == TK_RELIEF_RAISED) {
531	    x -= offset;
532	    y -= offset;
533	} else if (relief == TK_RELIEF_SUNKEN) {
534	    x += offset;
535	    y += offset;
536	}
537
538	imageXOffset += x;
539	imageYOffset += y;
540
541	if (butPtr->image != NULL) {
542	    /*
543	     * Do boundary clipping, so that Tk_RedrawImage is passed valid
544	     * coordinates. [Bug 979239]
545	     */
546
547	    if (imageXOffset < 0) {
548		imageXOffset = 0;
549	    }
550	    if (imageYOffset < 0) {
551		imageYOffset = 0;
552	    }
553	    if (width > Tk_Width(tkwin)) {
554		width = Tk_Width(tkwin);
555	    }
556	    if (height > Tk_Height(tkwin)) {
557		height = Tk_Height(tkwin);
558	    }
559	    if ((width + imageXOffset) > Tk_Width(tkwin)) {
560		imageXOffset = Tk_Width(tkwin) - width;
561	    }
562	    if ((height + imageYOffset) > Tk_Height(tkwin)) {
563		imageYOffset = Tk_Height(tkwin) - height;
564	    }
565
566	    if ((butPtr->selectImage != NULL) && (butPtr->flags & SELECTED)) {
567		Tk_RedrawImage(butPtr->selectImage, 0, 0,
568			width, height, pixmap, imageXOffset, imageYOffset);
569	    } else if ((butPtr->tristateImage != NULL) && (butPtr->flags & TRISTATED)) {
570		Tk_RedrawImage(butPtr->tristateImage, 0, 0,
571			width, height, pixmap, imageXOffset, imageYOffset);
572	    } else {
573		Tk_RedrawImage(butPtr->image, 0, 0, width,
574			height, pixmap, imageXOffset, imageYOffset);
575	    }
576	} else {
577	    XSetClipOrigin(butPtr->display, gc, imageXOffset, imageYOffset);
578	    XCopyPlane(butPtr->display, butPtr->bitmap, pixmap, gc,
579		    0, 0, (unsigned int) width, (unsigned int) height,
580		    imageXOffset, imageYOffset, 1);
581	    XSetClipOrigin(butPtr->display, gc, 0, 0);
582	}
583
584	Tk_DrawTextLayout(butPtr->display, pixmap, gc,
585		butPtr->textLayout, x + textXOffset, y + textYOffset, 0, -1);
586	Tk_UnderlineTextLayout(butPtr->display, pixmap, gc,
587		butPtr->textLayout, x + textXOffset, y + textYOffset,
588		butPtr->underline);
589	y += fullHeight/2;
590    } else {
591	if (haveImage) {
592	    TkComputeAnchor(butPtr->anchor, tkwin, 0, 0,
593		    butPtr->indicatorSpace + width, height, &x, &y);
594	    x += butPtr->indicatorSpace;
595
596	    x += offset;
597	    y += offset;
598	    if (relief == TK_RELIEF_RAISED) {
599		x -= offset;
600		y -= offset;
601	    } else if (relief == TK_RELIEF_SUNKEN) {
602		x += offset;
603		y += offset;
604	    }
605	    imageXOffset += x;
606	    imageYOffset += y;
607	    if (butPtr->image != NULL) {
608		/*
609		 * Do boundary clipping, so that Tk_RedrawImage is passed
610		 * valid coordinates. [Bug 979239]
611		 */
612
613		if (imageXOffset < 0) {
614		    imageXOffset = 0;
615		}
616		if (imageYOffset < 0) {
617		    imageYOffset = 0;
618		}
619		if (width > Tk_Width(tkwin)) {
620		    width = Tk_Width(tkwin);
621		}
622		if (height > Tk_Height(tkwin)) {
623		    height = Tk_Height(tkwin);
624		}
625		if ((width + imageXOffset) > Tk_Width(tkwin)) {
626		    imageXOffset = Tk_Width(tkwin) - width;
627		}
628		if ((height + imageYOffset) > Tk_Height(tkwin)) {
629		    imageYOffset = Tk_Height(tkwin) - height;
630		}
631
632		if ((butPtr->selectImage != NULL) &&
633			(butPtr->flags & SELECTED)) {
634		    Tk_RedrawImage(butPtr->selectImage, 0, 0, width,
635			    height, pixmap, imageXOffset, imageYOffset);
636		} else if ((butPtr->tristateImage != NULL) &&
637			(butPtr->flags & TRISTATED)) {
638		    Tk_RedrawImage(butPtr->tristateImage, 0, 0, width,
639			    height, pixmap, imageXOffset, imageYOffset);
640		} else {
641		    Tk_RedrawImage(butPtr->image, 0, 0, width, height, pixmap,
642			    imageXOffset, imageYOffset);
643		}
644	    } else {
645		XSetClipOrigin(butPtr->display, gc, x, y);
646		XCopyPlane(butPtr->display, butPtr->bitmap, pixmap, gc, 0, 0,
647			(unsigned int) width, (unsigned int) height, x, y, 1);
648		XSetClipOrigin(butPtr->display, gc, 0, 0);
649	    }
650	    y += height/2;
651	} else {
652 	    TkComputeAnchor(butPtr->anchor, tkwin, butPtr->padX, butPtr->padY,
653		    butPtr->indicatorSpace + butPtr->textWidth,
654		    butPtr->textHeight, &x, &y);
655
656	    x += butPtr->indicatorSpace;
657
658	    x += offset;
659	    y += offset;
660	    if (relief == TK_RELIEF_RAISED) {
661		x -= offset;
662		y -= offset;
663	    } else if (relief == TK_RELIEF_SUNKEN) {
664		x += offset;
665		y += offset;
666	    }
667	    Tk_DrawTextLayout(butPtr->display, pixmap, gc, butPtr->textLayout,
668		    x, y, 0, -1);
669	    Tk_UnderlineTextLayout(butPtr->display, pixmap, gc,
670		    butPtr->textLayout, x, y, butPtr->underline);
671	    y += butPtr->textHeight/2;
672	}
673    }
674
675    /*
676     * Draw the indicator for check buttons and radio buttons. At this point,
677     * x and y refer to the top-left corner of the text or image or bitmap.
678     */
679
680    if ((butPtr->type == TYPE_CHECK_BUTTON) && butPtr->indicatorOn) {
681	if (butPtr->indicatorDiameter > 2*butPtr->borderWidth) {
682	    TkBorder *selBorder = (TkBorder *) butPtr->selectBorder;
683	    XColor *selColor = NULL;
684
685	    if (selBorder != NULL) {
686		selColor = selBorder->bgColorPtr;
687	    }
688	    x -= butPtr->indicatorSpace/2;
689	    y = Tk_Height(tkwin)/2;
690	    TkpDrawCheckIndicator(tkwin, butPtr->display, pixmap, x, y,
691		    border, butPtr->normalFg, selColor, butPtr->disabledFg,
692		    ((butPtr->flags & SELECTED) ? 1 :
693			    (butPtr->flags & TRISTATED) ? 2 : 0),
694		    (butPtr->state == STATE_DISABLED), CHECK_BUTTON);
695	}
696    } else if ((butPtr->type == TYPE_RADIO_BUTTON) && butPtr->indicatorOn) {
697	if (butPtr->indicatorDiameter > 2*butPtr->borderWidth) {
698	    TkBorder *selBorder = (TkBorder *) butPtr->selectBorder;
699	    XColor *selColor = NULL;
700
701	    if (selBorder != NULL) {
702		selColor = selBorder->bgColorPtr;
703	    }
704	    x -= butPtr->indicatorSpace/2;
705	    y = Tk_Height(tkwin)/2;
706	    TkpDrawCheckIndicator(tkwin, butPtr->display, pixmap, x, y,
707		    border, butPtr->normalFg, selColor, butPtr->disabledFg,
708		    ((butPtr->flags & SELECTED) ? 1 :
709			    (butPtr->flags & TRISTATED) ? 2 : 0),
710		    (butPtr->state == STATE_DISABLED), RADIO_BUTTON);
711	}
712    }
713
714    /*
715     * If the button is disabled with a stipple rather than a special
716     * foreground color, generate the stippled effect. If the widget is
717     * selected and we use a different background color when selected, must
718     * temporarily modify the GC so the stippling is the right color.
719     */
720
721    if ((butPtr->state == STATE_DISABLED)
722	    && ((butPtr->disabledFg == NULL) || (butPtr->image != NULL))) {
723	if ((butPtr->flags & SELECTED) && !butPtr->indicatorOn
724		&& (butPtr->selectBorder != NULL)) {
725	    XSetForeground(butPtr->display, butPtr->stippleGC,
726		    Tk_3DBorderColor(butPtr->selectBorder)->pixel);
727	}
728
729	/*
730	 * Stipple the whole button if no disabledFg was specified, otherwise
731	 * restrict stippling only to displayed image
732	 */
733
734	if (butPtr->disabledFg == NULL) {
735	    XFillRectangle(butPtr->display, pixmap, butPtr->stippleGC, 0, 0,
736		    (unsigned) Tk_Width(tkwin), (unsigned) Tk_Height(tkwin));
737	} else {
738	    XFillRectangle(butPtr->display, pixmap, butPtr->stippleGC,
739		    imageXOffset, imageYOffset,
740		    (unsigned) imageWidth, (unsigned) imageHeight);
741	}
742	if ((butPtr->flags & SELECTED) && !butPtr->indicatorOn
743		&& (butPtr->selectBorder != NULL)) {
744	    XSetForeground(butPtr->display, butPtr->stippleGC,
745		    Tk_3DBorderColor(butPtr->normalBorder)->pixel);
746	}
747    }
748
749    /*
750     * Draw the border and traversal highlight last. This way, if the button's
751     * contents overflow they'll be covered up by the border. This code is
752     * complicated by the possible combinations of focus highlight and default
753     * rings. We draw the focus and highlight rings using the highlight border
754     * and highlight foreground color.
755     */
756
757    if (relief != TK_RELIEF_FLAT) {
758	int inset = butPtr->highlightWidth;
759
760	if (butPtr->defaultState == DEFAULT_ACTIVE) {
761	    /*
762	     * Draw the default ring with 2 pixels of space between the
763	     * default ring and the button and the default ring and the focus
764	     * ring. Note that we need to explicitly draw the space in the
765	     * highlightBorder color to ensure that we overwrite any overflow
766	     * text and/or a different button background color.
767	     */
768
769	    Tk_Draw3DRectangle(tkwin, pixmap, butPtr->highlightBorder, inset,
770		    inset, Tk_Width(tkwin) - 2*inset,
771		    Tk_Height(tkwin) - 2*inset, 2, TK_RELIEF_FLAT);
772	    inset += 2;
773	    Tk_Draw3DRectangle(tkwin, pixmap, butPtr->highlightBorder, inset,
774		    inset, Tk_Width(tkwin) - 2*inset,
775		    Tk_Height(tkwin) - 2*inset, 1, TK_RELIEF_SUNKEN);
776	    inset++;
777	    Tk_Draw3DRectangle(tkwin, pixmap, butPtr->highlightBorder, inset,
778		    inset, Tk_Width(tkwin) - 2*inset,
779		    Tk_Height(tkwin) - 2*inset, 2, TK_RELIEF_FLAT);
780
781	    inset += 2;
782	} else if (butPtr->defaultState == DEFAULT_NORMAL) {
783	    /*
784	     * Leave room for the default ring and write over any text or
785	     * background color.
786	     */
787
788	    Tk_Draw3DRectangle(tkwin, pixmap, butPtr->highlightBorder, 0,
789		    0, Tk_Width(tkwin), Tk_Height(tkwin), 5, TK_RELIEF_FLAT);
790	    inset += 5;
791	}
792
793	/*
794	 * Draw the button border.
795	 */
796
797	Tk_Draw3DRectangle(tkwin, pixmap, border, inset, inset,
798		Tk_Width(tkwin) - 2*inset, Tk_Height(tkwin) - 2*inset,
799		butPtr->borderWidth, relief);
800    }
801    if (butPtr->highlightWidth > 0) {
802	GC gc;
803
804	if (butPtr->flags & GOT_FOCUS) {
805	    gc = Tk_GCForColor(butPtr->highlightColorPtr, pixmap);
806	} else {
807	    gc = Tk_GCForColor(Tk_3DBorderColor(butPtr->highlightBorder),
808		    pixmap);
809	}
810
811	/*
812	 * Make sure the focus ring shrink-wraps the actual button, not the
813	 * padding space left for a default ring.
814	 */
815
816	if (butPtr->defaultState == DEFAULT_NORMAL) {
817	    TkDrawInsetFocusHighlight(tkwin, gc, butPtr->highlightWidth,
818		    pixmap, 5);
819	} else {
820	    Tk_DrawFocusHighlight(tkwin, gc, butPtr->highlightWidth, pixmap);
821	}
822    }
823
824    /*
825     * Copy the information from the off-screen pixmap onto the screen, then
826     * delete the pixmap.
827     */
828
829    XCopyArea(butPtr->display, pixmap, Tk_WindowId(tkwin),
830	    butPtr->copyGC, 0, 0, (unsigned) Tk_Width(tkwin),
831	    (unsigned) Tk_Height(tkwin), 0, 0);
832    Tk_FreePixmap(butPtr->display, pixmap);
833}
834
835/*
836 *----------------------------------------------------------------------
837 *
838 * TkpComputeButtonGeometry --
839 *
840 *	After changes in a button's text or bitmap, this function recomputes
841 *	the button's geometry and passes this information along to the
842 *	geometry manager for the window.
843 *
844 * Results:
845 *	None.
846 *
847 * Side effects:
848 *	The button's window may change size.
849 *
850 *----------------------------------------------------------------------
851 */
852
853void
854TkpComputeButtonGeometry(
855    register TkButton *butPtr)	/* Button whose geometry may have changed. */
856{
857    int width, height, avgWidth, txtWidth, txtHeight;
858    int haveImage = 0, haveText = 0;
859    Tk_FontMetrics fm;
860
861    butPtr->inset = butPtr->highlightWidth + butPtr->borderWidth;
862
863    /*
864     * Leave room for the default ring if needed.
865     */
866
867    if (butPtr->defaultState != DEFAULT_DISABLED) {
868	butPtr->inset += 5;
869    }
870    butPtr->indicatorSpace = 0;
871
872    width = 0;
873    height = 0;
874    txtWidth = 0;
875    txtHeight = 0;
876    avgWidth = 0;
877
878    if (butPtr->image != NULL) {
879	Tk_SizeOfImage(butPtr->image, &width, &height);
880	haveImage = 1;
881    } else if (butPtr->bitmap != None) {
882	Tk_SizeOfBitmap(butPtr->display, butPtr->bitmap, &width, &height);
883	haveImage = 1;
884    }
885
886    if (haveImage == 0 || butPtr->compound != COMPOUND_NONE) {
887	Tk_FreeTextLayout(butPtr->textLayout);
888
889	butPtr->textLayout = Tk_ComputeTextLayout(butPtr->tkfont,
890		Tcl_GetString(butPtr->textPtr), -1, butPtr->wrapLength,
891		butPtr->justify, 0, &butPtr->textWidth, &butPtr->textHeight);
892
893	txtWidth = butPtr->textWidth;
894	txtHeight = butPtr->textHeight;
895	avgWidth = Tk_TextWidth(butPtr->tkfont, "0", 1);
896	Tk_GetFontMetrics(butPtr->tkfont, &fm);
897	haveText = (txtWidth != 0 && txtHeight != 0);
898    }
899
900    /*
901     * If the button is compound (i.e., it shows both an image and text), the
902     * new geometry is a combination of the image and text geometry. We only
903     * honor the compound bit if the button has both text and an image,
904     * because otherwise it is not really a compound button.
905     */
906
907    if (butPtr->compound != COMPOUND_NONE && haveImage && haveText) {
908	switch ((enum compound) butPtr->compound) {
909	case COMPOUND_TOP:
910	case COMPOUND_BOTTOM:
911	    /*
912	     * Image is above or below text.
913	     */
914
915	    height += txtHeight + butPtr->padY;
916	    width = (width > txtWidth ? width : txtWidth);
917	    break;
918	case COMPOUND_LEFT:
919	case COMPOUND_RIGHT:
920	    /*
921	     * Image is left or right of text.
922	     */
923
924	    width += txtWidth + butPtr->padX;
925	    height = (height > txtHeight ? height : txtHeight);
926	    break;
927	case COMPOUND_CENTER:
928	    /*
929	     * Image and text are superimposed.
930	     */
931
932	    width = (width > txtWidth ? width : txtWidth);
933	    height = (height > txtHeight ? height : txtHeight);
934	    break;
935	case COMPOUND_NONE:
936	    break;
937	}
938	if (butPtr->width > 0) {
939	    width = butPtr->width;
940	}
941	if (butPtr->height > 0) {
942	    height = butPtr->height;
943	}
944
945	if ((butPtr->type >= TYPE_CHECK_BUTTON) && butPtr->indicatorOn) {
946	    butPtr->indicatorSpace = height;
947	    if (butPtr->type == TYPE_CHECK_BUTTON) {
948		butPtr->indicatorDiameter = (65*height)/100;
949	    } else {
950		butPtr->indicatorDiameter = (75*height)/100;
951	    }
952	}
953
954	width += 2*butPtr->padX;
955	height += 2*butPtr->padY;
956    } else {
957	if (haveImage) {
958	    if (butPtr->width > 0) {
959		width = butPtr->width;
960	    }
961	    if (butPtr->height > 0) {
962		height = butPtr->height;
963	    }
964
965	    if ((butPtr->type >= TYPE_CHECK_BUTTON) && butPtr->indicatorOn) {
966		butPtr->indicatorSpace = height;
967		if (butPtr->type == TYPE_CHECK_BUTTON) {
968		    butPtr->indicatorDiameter = (65*height)/100;
969		} else {
970		    butPtr->indicatorDiameter = (75*height)/100;
971		}
972	    }
973	} else {
974	    width = txtWidth;
975	    height = txtHeight;
976
977	    if (butPtr->width > 0) {
978		width = butPtr->width * avgWidth;
979	    }
980	    if (butPtr->height > 0) {
981		height = butPtr->height * fm.linespace;
982	    }
983	    if ((butPtr->type >= TYPE_CHECK_BUTTON) && butPtr->indicatorOn) {
984		butPtr->indicatorDiameter = fm.linespace;
985		if (butPtr->type == TYPE_CHECK_BUTTON) {
986		    butPtr->indicatorDiameter =
987			(80*butPtr->indicatorDiameter)/100;
988		}
989		butPtr->indicatorSpace = butPtr->indicatorDiameter + avgWidth;
990	    }
991	}
992    }
993
994    /*
995     * When issuing the geometry request, add extra space for the indicator,
996     * if any, and for the border and padding, plus two extra pixels so the
997     * display can be offset by 1 pixel in either direction for the raised or
998     * lowered effect.
999     */
1000
1001    if ((butPtr->image == NULL) && (butPtr->bitmap == None)) {
1002	width += 2*butPtr->padX;
1003	height += 2*butPtr->padY;
1004    }
1005    if ((butPtr->type == TYPE_BUTTON) && !Tk_StrictMotif(butPtr->tkwin)) {
1006	width += 2;
1007	height += 2;
1008    }
1009    Tk_GeometryRequest(butPtr->tkwin, (int) (width + butPtr->indicatorSpace
1010	    + 2*butPtr->inset), (int) (height + 2*butPtr->inset));
1011    Tk_SetInternalBorder(butPtr->tkwin, butPtr->inset);
1012}
1013
1014/*
1015 * Local Variables:
1016 * mode: c
1017 * c-basic-offset: 4
1018 * fill-column: 78
1019 * End:
1020 */
1021