1/*
2 * tkMacOSXButton.c --
3 *
4 *	This file implements the Macintosh specific portion of the
5 *	button widgets.
6 *
7 * Copyright (c) 1996-1997 by Sun Microsystems, Inc.
8 * Copyright 2001-2009, Apple Inc.
9 * Copyright (c) 2006-2009 Daniel A. Steffen <das@users.sourceforge.net>
10 *
11 * See the file "license.terms" for information on usage and redistribution
12 * of this file, and for a DISCLAIMER OF ALL WARRANTIES.
13 *
14 * RCS: @(#) $Id$
15 */
16
17#include "tkMacOSXPrivate.h"
18#include "tkButton.h"
19#include "tkMacOSXFont.h"
20#include "tkMacOSXDebug.h"
21
22/*
23#ifdef TK_MAC_DEBUG
24#define TK_MAC_DEBUG_BUTTON
25#endif
26*/
27
28typedef struct MacButton {
29    TkButton info;
30    NSButton *button;
31    NSImage *image, *selectImage, *tristateImage;
32#if TK_MAC_BUTTON_USE_COMPATIBILITY_METRICS
33    int fix;
34#endif
35} MacButton;
36
37#if TK_MAC_BUTTON_USE_COMPATIBILITY_METRICS
38
39int tkMacOSXUseCompatibilityMetrics = 1;
40
41/*
42 * Use the following heuristic conversion constants to make NSButton-based
43 * widget metrics match up with the old Carbon control buttons (for the
44 * default Lucida Grande 13 font).
45 */
46
47#define NATIVE_BUTTON_INSET 2
48#define NATIVE_BUTTON_EXTRA_H 2
49
50typedef struct {
51    int trimW, trimH, inset, shrinkH, offsetX, offsetY;
52} BoundsFix;
53
54#define fixForTypeStyle(type, style) ( \
55	type == NSSwitchButton ? 0 : \
56	type == NSRadioButton ? 1 : \
57	style == NSRoundedBezelStyle ? 2 : \
58	style == NSRegularSquareBezelStyle ? 3 : \
59	style == NSShadowlessSquareBezelStyle ? 4 : \
60	INT_MIN)
61
62static const BoundsFix boundsFixes[] = {
63    [fixForTypeStyle(NSSwitchButton,0)] =		{  2,  2, -1,  0, 2, 1 },
64    [fixForTypeStyle(NSRadioButton,0)] =		{  0,  2, -1,  0, 1, 1 },
65    [fixForTypeStyle(0,NSRoundedBezelStyle)] =		{ 28, 16, -6,  0, 0, 3 },
66    [fixForTypeStyle(0,NSRegularSquareBezelStyle)] =	{ 28, 15, -2, -1 },
67    [fixForTypeStyle(0,NSShadowlessSquareBezelStyle)] =	{  2,  2 },
68};
69
70#endif
71
72static void DisplayNativeButton(TkButton *butPtr);
73static void ComputeNativeButtonGeometry(TkButton *butPtr);
74static void DisplayUnixButton(TkButton *butPtr);
75static void ComputeUnixButtonGeometry(TkButton *butPtr);
76
77/*
78 * The class procedure table for the button widgets.
79 */
80
81Tk_ClassProcs tkpButtonProcs = {
82    sizeof(Tk_ClassProcs),	/* size */
83    TkButtonWorldChanged,	/* worldChangedProc */
84};
85
86
87/*
88 *----------------------------------------------------------------------
89 *
90 * TkpCreateButton --
91 *
92 *	Allocate a new TkButton structure.
93 *
94 * Results:
95 *	Returns a newly allocated TkButton structure.
96 *
97 * Side effects:
98 *	Registers an event handler for the widget.
99 *
100 *----------------------------------------------------------------------
101 */
102
103TkButton *
104TkpCreateButton(
105    Tk_Window tkwin)
106{
107    MacButton *macButtonPtr = (MacButton *) ckalloc(sizeof(MacButton));
108
109    macButtonPtr->button = nil;
110    macButtonPtr->image = nil;
111    macButtonPtr->selectImage = nil;
112    macButtonPtr->tristateImage = nil;
113
114    return (TkButton *) macButtonPtr;
115}
116
117/*
118 *----------------------------------------------------------------------
119 *
120 * TkpDestroyButton --
121 *
122 *	Free data structures associated with the button control.
123 *
124 * Results:
125 *	None.
126 *
127 * Side effects:
128 *	Restores the default control state.
129 *
130 *----------------------------------------------------------------------
131 */
132
133void
134TkpDestroyButton(
135    TkButton *butPtr)
136{
137    MacButton *macButtonPtr = (MacButton *) butPtr;
138
139    TkMacOSXMakeCollectableAndRelease(macButtonPtr->button);
140    TkMacOSXMakeCollectableAndRelease(macButtonPtr->selectImage);
141    TkMacOSXMakeCollectableAndRelease(macButtonPtr->selectImage);
142    TkMacOSXMakeCollectableAndRelease(macButtonPtr->tristateImage);
143}
144
145/*
146 *----------------------------------------------------------------------
147 *
148 * TkpDisplayButton --
149 *
150 *	This procedure is invoked to display a button widget. It is
151 *	normally invoked as an idle handler.
152 *
153 * Results:
154 *	None.
155 *
156 * Side effects:
157 *	Commands are output to X to display the button in its
158 *	current mode. The REDRAW_PENDING flag is cleared.
159 *
160 *----------------------------------------------------------------------
161 */
162
163void
164TkpDisplayButton(
165    ClientData clientData)	/* Information about widget. */
166{
167    TkButton *butPtr = (TkButton *) clientData;
168
169    butPtr->flags &= ~REDRAW_PENDING;
170    if (!butPtr->tkwin || !Tk_IsMapped(butPtr->tkwin)) {
171	return;
172    }
173
174    switch (butPtr->type) {
175    case TYPE_LABEL:
176	DisplayUnixButton(butPtr);
177	break;
178    case TYPE_BUTTON:
179    case TYPE_CHECK_BUTTON:
180    case TYPE_RADIO_BUTTON:
181	DisplayNativeButton(butPtr);
182	break;
183    }
184}
185
186/*
187 *----------------------------------------------------------------------
188 *
189 * TkpComputeButtonGeometry --
190 *
191 *	After changes in a button's text or bitmap, this procedure
192 *	recomputes the button's geometry and passes this information
193 *	along to the geometry manager for the window.
194 *
195 * Results:
196 *	None.
197 *
198 * Side effects:
199 *	The button's window may change size.
200 *
201 *----------------------------------------------------------------------
202 */
203
204void
205TkpComputeButtonGeometry(
206    register TkButton *butPtr)	/* Button whose geometry may have changed. */
207{
208    MacButton *macButtonPtr = (MacButton *) butPtr;
209
210    switch (butPtr->type) {
211    case TYPE_LABEL:
212	if (macButtonPtr->button && [macButtonPtr->button superview]) {
213	    [macButtonPtr->button removeFromSuperviewWithoutNeedingDisplay];
214	}
215	ComputeUnixButtonGeometry(butPtr);
216	break;
217    case TYPE_BUTTON:
218    case TYPE_CHECK_BUTTON:
219    case TYPE_RADIO_BUTTON:
220	if (!macButtonPtr->button) {
221	    NSButton *button = [[NSButton alloc] initWithFrame:NSZeroRect];
222	    macButtonPtr->button = TkMacOSXMakeUncollectable(button);
223	}
224	ComputeNativeButtonGeometry(butPtr);
225	break;
226    }
227}
228
229/*
230 *----------------------------------------------------------------------
231 *
232 * TkpButtonSetDefaults --
233 *
234 *	This procedure is invoked before option tables are created for
235 *	buttons. It modifies some of the default values to match the current
236 *	values defined for this platform.
237 *
238 * Results:
239 *	Some of the default values in *specPtr are modified.
240 *
241 * Side effects:
242 *	Updates some of.
243 *
244 *----------------------------------------------------------------------
245 */
246
247void
248TkpButtonSetDefaults(
249    Tk_OptionSpec *specPtr)	/* Points to an array of option specs,
250				 * terminated by one with type
251				 * TK_OPTION_END. */
252{
253#if TK_MAC_BUTTON_USE_COMPATIBILITY_METRICS
254    if (!tkMacOSXUseCompatibilityMetrics) {
255	while (specPtr->type != TK_CONFIG_END) {
256	    switch (specPtr->internalOffset) {
257	    case Tk_Offset(TkButton, highlightWidth):
258		specPtr->defValue = DEF_BUTTON_HIGHLIGHT_WIDTH_NOCM;
259		break;
260	    case Tk_Offset(TkButton, padX):
261		specPtr->defValue = DEF_BUTTON_PADX_NOCM;
262		break;
263	    case Tk_Offset(TkButton, padY):
264		specPtr->defValue = DEF_BUTTON_PADY_NOCM;
265		break;
266	    }
267	    specPtr++;
268	}
269    }
270#endif
271}
272
273#pragma mark -
274#pragma mark Native Buttons:
275
276
277/*
278 *----------------------------------------------------------------------
279 *
280 * DisplayNativeButton --
281 *
282 *	This procedure is invoked to display a button widget. It is
283 *	normally invoked as an idle handler.
284 *
285 * Results:
286 *	None.
287 *
288 * Side effects:
289 *	Commands are output to X to display the button in its
290 *	current mode. The REDRAW_PENDING flag is cleared.
291 *
292 *----------------------------------------------------------------------
293 */
294
295static void
296DisplayNativeButton(
297    TkButton *butPtr)
298{
299    MacButton *macButtonPtr = (MacButton *) butPtr;
300    NSButton *button = macButtonPtr->button;
301    Tk_Window tkwin = butPtr->tkwin;
302    TkWindow *winPtr = (TkWindow *) tkwin;
303    MacDrawable *macWin =  (MacDrawable *) winPtr->window;
304    TkMacOSXDrawingContext dc;
305    NSView *view = TkMacOSXDrawableView(macWin);
306    CGFloat viewHeight = [view bounds].size.height;
307    CGAffineTransform t = { .a = 1, .b = 0, .c = 0, .d = -1, .tx = 0,
308	    .ty = viewHeight};
309    NSRect frame;
310    int enabled;
311    NSCellStateValue state;
312
313    if (!view ||
314	    !TkMacOSXSetupDrawingContext((Drawable) macWin, NULL, 1, &dc)) {
315	return;
316    }
317    CGContextConcatCTM(dc.context, t);
318
319    /*
320     * We cannot change the background color of the button itself, only the
321     * color of the background of its container.
322     * This will be the color that peeks around the rounded corners of the
323     * button. We make this the highlightbackground rather than the background,
324     * because if you color the background of a frame containing a
325     * button, you usually also color the highlightbackground as well,
326     * or you will get a thin grey ring around the button.
327     */
328
329    Tk_Fill3DRectangle(tkwin, (Pixmap) macWin, butPtr->type == TYPE_BUTTON ?
330	    butPtr->highlightBorder : butPtr->normalBorder, 0, 0,
331	    Tk_Width(tkwin), Tk_Height(tkwin), 0, TK_RELIEF_FLAT);
332    if ([button superview] != view) {
333	[view addSubview:button];
334    }
335    if (macButtonPtr->tristateImage) {
336	NSImage *selectImage = macButtonPtr->selectImage ?
337		macButtonPtr->selectImage : macButtonPtr->image;
338	[button setImage:(butPtr->flags & TRISTATED ?
339		selectImage : macButtonPtr->image)];
340	[button setAlternateImage:(butPtr->flags & TRISTATED ?
341		macButtonPtr->tristateImage : selectImage)];
342    }
343    if (butPtr->flags & SELECTED) {
344	state = NSOnState;
345    } else if (butPtr->flags & TRISTATED) {
346	state = NSMixedState;
347    } else {
348	state = NSOffState;
349    }
350    [button setState:state];
351    enabled = !(butPtr->state == STATE_DISABLED);
352    [button setEnabled:enabled];
353    if (enabled) {
354	//[button highlight:(butPtr->state == STATE_ACTIVE)];
355	//[cell setHighlighted:(butPtr->state == STATE_ACTIVE)];
356    }
357    if (butPtr->type == TYPE_BUTTON && butPtr->defaultState == STATE_ACTIVE) {
358	//[[view window] setDefaultButtonCell:cell];
359	[button setKeyEquivalent:@"\r"];
360    } else {
361	[button setKeyEquivalent:@""];
362    }
363    frame = NSMakeRect(macWin->xOff, macWin->yOff, Tk_Width(tkwin),
364	    Tk_Height(tkwin));
365#if TK_MAC_BUTTON_USE_COMPATIBILITY_METRICS
366    if (tkMacOSXUseCompatibilityMetrics) {
367	BoundsFix boundsFix = boundsFixes[macButtonPtr->fix];
368	frame = NSOffsetRect(frame, boundsFix.offsetX, boundsFix.offsetY);
369	frame.size.height -= boundsFix.shrinkH + NATIVE_BUTTON_EXTRA_H;
370	frame = NSInsetRect(frame, boundsFix.inset + NATIVE_BUTTON_INSET,
371		boundsFix.inset + NATIVE_BUTTON_INSET);
372    }
373#endif
374    frame.origin.y = viewHeight - (frame.origin.y + frame.size.height);
375    if (!NSEqualRects(frame, [button frame])) {
376	[button setFrame:frame];
377    }
378    [button displayRectIgnoringOpacity:[button bounds]];
379    TkMacOSXRestoreDrawingContext(&dc);
380#ifdef TK_MAC_DEBUG_BUTTON
381    TKLog(@"button %s frame %@ width %d height %d",
382	    ((TkWindow *)butPtr->tkwin)->pathName, NSStringFromRect(frame),
383	    Tk_Width(tkwin), Tk_Height(tkwin));
384#endif
385}
386
387/*
388 *----------------------------------------------------------------------
389 *
390 * ComputeNativeButtonGeometry --
391 *
392 *	After changes in a button's text or bitmap, this procedure
393 *	recomputes the button's geometry and passes this information
394 *	along to the geometry manager for the window.
395 *
396 * Results:
397 *	None.
398 *
399 * Side effects:
400 *	The button's window may change size.
401 *
402 *----------------------------------------------------------------------
403 */
404
405static void
406ComputeNativeButtonGeometry(
407    TkButton *butPtr)		/* Button whose geometry may have changed. */
408{
409    MacButton *macButtonPtr = (MacButton *) butPtr;
410    NSButton *button = macButtonPtr->button;
411    NSButtonCell *cell = [button cell];
412    NSButtonType type = -1;
413    NSBezelStyle style = 0;
414    NSInteger highlightsBy = 0, showsStateBy = 0;
415    NSFont *font;
416    NSRect bounds = NSZeroRect, titleRect = NSZeroRect;
417    int haveImage = (butPtr->image || butPtr->bitmap != None), haveText = 0;
418    int haveCompound = (butPtr->compound != COMPOUND_NONE);
419    int width, height, border = 0;
420
421    butPtr->indicatorSpace = 0;
422    butPtr->inset = 0;
423    if (butPtr->highlightWidth < 0) {
424	butPtr->highlightWidth = 0;
425    }
426    switch (butPtr->type) {
427    case TYPE_BUTTON:
428	type = NSMomentaryPushInButton;
429	if (!haveImage) {
430	    style = NSRoundedBezelStyle;
431	    butPtr->inset = butPtr->defaultState != STATE_DISABLED ?
432		    butPtr->highlightWidth : 0;
433	    [button setImage:nil];
434	    [button setImagePosition:NSNoImage];
435	} else {
436	    style = NSShadowlessSquareBezelStyle;
437	    highlightsBy = butPtr->selectImage || butPtr->bitmap ?
438		    NSContentsCellMask : 0;
439	    border = butPtr->borderWidth;
440	}
441	break;
442    case TYPE_RADIO_BUTTON:
443    case TYPE_CHECK_BUTTON:
444	if (!haveImage /*|| butPtr->indicatorOn*/) { // TODO: indicatorOn
445	    type = butPtr->type == TYPE_RADIO_BUTTON ?
446		    NSRadioButton : NSSwitchButton;
447	    butPtr->inset = /*butPtr->indicatorOn ? 0 :*/ butPtr->borderWidth;
448	} else {
449	    type = NSPushOnPushOffButton;
450	    style = NSShadowlessSquareBezelStyle;
451	    highlightsBy = butPtr->selectImage || butPtr->bitmap ?
452		    NSContentsCellMask : 0;
453	    showsStateBy = butPtr->selectImage || butPtr->tristateImage ?
454		    NSContentsCellMask : 0;
455#if TK_MAC_BUTTON_USE_COMPATIBILITY_METRICS
456	    if (tkMacOSXUseCompatibilityMetrics) {
457		border = butPtr->borderWidth > 1 ? butPtr->borderWidth - 1 : 1;
458	    } else
459#endif
460	    {
461		border = butPtr->borderWidth;
462	    }
463	}
464	break;
465    }
466    [button setButtonType:type];
467    if (style) {
468	[button setBezelStyle:style];
469    }
470    if (highlightsBy) {
471	[cell setHighlightsBy:highlightsBy|[cell highlightsBy]];
472    }
473    if (showsStateBy) {
474	[cell setShowsStateBy:showsStateBy|[cell showsStateBy]];
475    }
476#if 0
477    if (style == NSShadowlessSquareBezelStyle) {
478	NSControlSize controlSize = NSRegularControlSize;
479
480	if (butPtr->borderWidth <= 2) {
481	    controlSize = NSMiniControlSize;
482	} else if (butPtr->borderWidth == 3) {
483	    controlSize = NSSmallControlSize;
484	}
485	[cell setControlSize:controlSize];
486    }
487#endif
488    [button setAllowsMixedState:YES];
489
490    if (!haveImage || haveCompound) {
491	int len;
492	char *text = Tcl_GetStringFromObj(butPtr->textPtr, &len);
493
494	if (len) {
495	    NSString *title = [[NSString alloc] initWithBytes:text length:len
496		    encoding:NSUTF8StringEncoding];
497	    [button setTitle:title];
498	    [title release];
499	    haveText = 1;
500	}
501    }
502    haveCompound = (haveCompound && haveImage && haveText);
503    if (haveText) {
504	NSTextAlignment alignment = NSNaturalTextAlignment;
505
506	switch (butPtr->justify) {
507	case TK_JUSTIFY_LEFT:
508	    alignment = NSLeftTextAlignment;
509	    break;
510	case TK_JUSTIFY_RIGHT:
511	    alignment = NSRightTextAlignment;
512	    break;
513	case TK_JUSTIFY_CENTER:
514	    alignment = NSCenterTextAlignment;
515	    break;
516	}
517	[button setAlignment:alignment];
518    } else {
519	[button setTitle:@""];
520    }
521    font = TkMacOSXNSFontForFont(butPtr->tkfont);
522    if (font) {
523	[button setFont:font];
524    }
525    TkMacOSXMakeCollectableAndRelease(macButtonPtr->image);
526    TkMacOSXMakeCollectableAndRelease(macButtonPtr->selectImage);
527    TkMacOSXMakeCollectableAndRelease(macButtonPtr->tristateImage);
528    if (haveImage) {
529	int width, height;
530	NSImage *image, *selectImage = nil, *tristateImage = nil;
531	NSCellImagePosition pos = NSImageOnly;
532
533	if (butPtr->image) {
534	    Tk_SizeOfImage(butPtr->image, &width, &height);
535	    image = TkMacOSXGetNSImageWithTkImage(butPtr->display,
536		    butPtr->image, width, height);
537	    if (butPtr->selectImage) {
538		selectImage = TkMacOSXGetNSImageWithTkImage(butPtr->display,
539			butPtr->selectImage, width, height);
540	    }
541	    if (butPtr->tristateImage) {
542		tristateImage = TkMacOSXGetNSImageWithTkImage(butPtr->display,
543			butPtr->tristateImage, width, height);
544	    }
545	} else {
546	    Tk_SizeOfBitmap(butPtr->display, butPtr->bitmap, &width, &height);
547	    image = TkMacOSXGetNSImageWithBitmap(butPtr->display,
548		    butPtr->bitmap, butPtr->normalTextGC, width, height);
549	    selectImage = TkMacOSXGetNSImageWithBitmap(butPtr->display,
550		    butPtr->bitmap, butPtr->activeTextGC, width, height);
551	}
552	[button setImage:image];
553	if (selectImage) {
554	    [button setAlternateImage:selectImage];
555	}
556	if (tristateImage) {
557	    macButtonPtr->image = TkMacOSXMakeUncollectableAndRetain(image);
558	    if (selectImage) {
559		macButtonPtr->selectImage =
560			TkMacOSXMakeUncollectableAndRetain(selectImage);
561	    }
562	    macButtonPtr->tristateImage =
563		    TkMacOSXMakeUncollectableAndRetain(tristateImage);
564	}
565	if (haveCompound) {
566	    switch ((enum compound) butPtr->compound) {
567		case COMPOUND_TOP:
568		    pos = NSImageAbove;
569		    break;
570		case COMPOUND_BOTTOM:
571		    pos = NSImageBelow;
572		    break;
573		case COMPOUND_LEFT:
574		    pos = NSImageLeft;
575		    break;
576		case COMPOUND_RIGHT:
577		    pos = NSImageRight;
578		    break;
579		case COMPOUND_CENTER:
580		    pos = NSImageOverlaps;
581		    break;
582		case COMPOUND_NONE:
583		    pos = NSImageOnly;
584		    break;
585	    }
586	}
587	[button setImagePosition:pos];
588    }
589
590    bounds.size = [cell cellSize];
591    if (haveText) {
592	titleRect = [cell titleRectForBounds:bounds];
593	if (butPtr->wrapLength > 0 &&
594		titleRect.size.width > butPtr->wrapLength) {
595	    if (style == NSRoundedBezelStyle) {
596		[button setBezelStyle:(style = NSRegularSquareBezelStyle)];
597		bounds.size = [cell cellSize];
598		titleRect = [cell titleRectForBounds:bounds];
599	    }
600	    bounds.size.width -= titleRect.size.width - butPtr->wrapLength;
601	    bounds.size.height = 40000.0;
602	    [cell setWraps:YES];
603	    bounds.size = [cell cellSizeForBounds:bounds];
604#ifdef TK_MAC_DEBUG_BUTTON
605	    titleRect = [cell titleRectForBounds:bounds];
606#endif
607#if TK_MAC_BUTTON_USE_COMPATIBILITY_METRICS
608	    if (tkMacOSXUseCompatibilityMetrics) {
609		bounds.size.height += 3;
610	    }
611#endif
612	}
613    }
614    width = lround(bounds.size.width);
615    height = lround(bounds.size.height);
616#if TK_MAC_BUTTON_USE_COMPATIBILITY_METRICS
617    if (tkMacOSXUseCompatibilityMetrics) {
618	macButtonPtr->fix = fixForTypeStyle(type, style);
619	width -= boundsFixes[macButtonPtr->fix].trimW;
620	height -= boundsFixes[macButtonPtr->fix].trimH;
621    }
622#endif
623
624    if (haveImage || haveCompound) {
625	if (butPtr->width > 0) {
626	    width = butPtr->width;
627	}
628	if (butPtr->height > 0) {
629	    height = butPtr->height;
630	}
631    } else {
632	if (butPtr->width > 0) {
633	    int avgWidth = Tk_TextWidth(butPtr->tkfont, "0", 1);
634	    width = butPtr->width * avgWidth;
635	}
636	if (butPtr->height > 0) {
637	    Tk_FontMetrics fm;
638
639	    Tk_GetFontMetrics(butPtr->tkfont, &fm);
640	    height = butPtr->height * fm.linespace;
641	}
642    }
643    if (!haveImage || haveCompound) {
644	width += 2*butPtr->padX;
645	height += 2*butPtr->padY;
646    }
647    if (haveImage) {
648	width += 2*border;
649	height += 2*border;
650    }
651#if TK_MAC_BUTTON_USE_COMPATIBILITY_METRICS
652    if (tkMacOSXUseCompatibilityMetrics) {
653	width += 2*NATIVE_BUTTON_INSET;
654	height += 2*NATIVE_BUTTON_INSET + NATIVE_BUTTON_EXTRA_H;
655    }
656#endif
657    Tk_GeometryRequest(butPtr->tkwin, width, height);
658    Tk_SetInternalBorder(butPtr->tkwin, butPtr->inset);
659#ifdef TK_MAC_DEBUG_BUTTON
660    TKLog(@"button %s bounds %@ titleRect %@ width %d height %d inset %d borderWidth %d",
661	    ((TkWindow *)butPtr->tkwin)->pathName, NSStringFromRect(bounds),
662	    NSStringFromRect(titleRect), width, height, butPtr->inset,
663	    butPtr->borderWidth);
664#endif
665}
666
667#pragma mark -
668#pragma mark Unix Buttons:
669
670
671/*
672 *----------------------------------------------------------------------
673 *
674 * DisplayUnixButton --
675 *
676 *	This procedure is invoked to display a button widget. It is
677 *	normally invoked as an idle handler.
678 *
679 * Results:
680 *	None.
681 *
682 * Side effects:
683 *	Commands are output to X to display the button in its
684 *	current mode. The REDRAW_PENDING flag is cleared.
685 *
686 *----------------------------------------------------------------------
687 */
688
689void
690DisplayUnixButton(
691    TkButton *butPtr)
692{
693    GC gc;
694    Tk_3DBorder border;
695    Pixmap pixmap;
696    int x = 0;			/* Initialization only needed to stop compiler
697				 * warning. */
698    int y, relief;
699    Tk_Window tkwin = butPtr->tkwin;
700    int width = 0, height = 0, fullWidth, fullHeight;
701    int textXOffset, textYOffset;
702    int haveImage = 0, haveText = 0;
703    int imageWidth, imageHeight;
704    int imageXOffset = 0, imageYOffset = 0;
705				/* image information that will be used to
706				 * restrict disabled pixmap as well */
707
708    border = butPtr->normalBorder;
709    if ((butPtr->state == STATE_DISABLED) && (butPtr->disabledFg != NULL)) {
710	gc = butPtr->disabledGC;
711    } else if ((butPtr->state == STATE_ACTIVE)
712	    && !Tk_StrictMotif(butPtr->tkwin)) {
713	gc = butPtr->activeTextGC;
714	border = butPtr->activeBorder;
715    } else {
716	gc = butPtr->normalTextGC;
717    }
718    if ((butPtr->flags & SELECTED) && (butPtr->state != STATE_ACTIVE)
719	    && (butPtr->selectBorder != NULL) && !butPtr->indicatorOn) {
720	border = butPtr->selectBorder;
721    }
722
723    relief = butPtr->relief;
724
725    pixmap = (Pixmap) Tk_WindowId(tkwin);
726    Tk_Fill3DRectangle(tkwin, pixmap, border, 0, 0, Tk_Width(tkwin),
727	    Tk_Height(tkwin), 0, TK_RELIEF_FLAT);
728
729    /*
730     * Display image or bitmap or text for button.
731     */
732
733    if (butPtr->image != NULL) {
734	Tk_SizeOfImage(butPtr->image, &width, &height);
735	haveImage = 1;
736    } else if (butPtr->bitmap != None) {
737	Tk_SizeOfBitmap(butPtr->display, butPtr->bitmap, &width, &height);
738	haveImage = 1;
739    }
740    imageWidth = width;
741    imageHeight = height;
742
743    haveText = (butPtr->textWidth != 0 && butPtr->textHeight != 0);
744
745    if (butPtr->compound != COMPOUND_NONE && haveImage && haveText) {
746	textXOffset = 0;
747	textYOffset = 0;
748	fullWidth = 0;
749	fullHeight = 0;
750
751	switch ((enum compound) butPtr->compound) {
752	case COMPOUND_TOP:
753	case COMPOUND_BOTTOM:
754	    /*
755	     * Image is above or below text.
756	     */
757
758	    if (butPtr->compound == COMPOUND_TOP) {
759		textYOffset = height + butPtr->padY;
760	    } else {
761		imageYOffset = butPtr->textHeight + butPtr->padY;
762	    }
763	    fullHeight = height + butPtr->textHeight + butPtr->padY;
764	    fullWidth = (width > butPtr->textWidth ? width :
765		    butPtr->textWidth);
766	    textXOffset = (fullWidth - butPtr->textWidth)/2;
767	    imageXOffset = (fullWidth - width)/2;
768	    break;
769	case COMPOUND_LEFT:
770	case COMPOUND_RIGHT:
771	    /*
772	     * Image is left or right of text.
773	     */
774
775	    if (butPtr->compound == COMPOUND_LEFT) {
776		textXOffset = width + butPtr->padX;
777	    } else {
778		imageXOffset = butPtr->textWidth + butPtr->padX;
779	    }
780	    fullWidth = butPtr->textWidth + butPtr->padX + width;
781	    fullHeight = (height > butPtr->textHeight ? height :
782		    butPtr->textHeight);
783	    textYOffset = (fullHeight - butPtr->textHeight)/2;
784	    imageYOffset = (fullHeight - height)/2;
785	    break;
786	case COMPOUND_CENTER:
787	    /*
788	     * Image and text are superimposed.
789	     */
790
791	    fullWidth = (width > butPtr->textWidth ? width :
792		    butPtr->textWidth);
793	    fullHeight = (height > butPtr->textHeight ? height :
794		    butPtr->textHeight);
795	    textXOffset = (fullWidth - butPtr->textWidth)/2;
796	    imageXOffset = (fullWidth - width)/2;
797	    textYOffset = (fullHeight - butPtr->textHeight)/2;
798	    imageYOffset = (fullHeight - height)/2;
799	    break;
800	case COMPOUND_NONE:
801	    break;
802	}
803
804	TkComputeAnchor(butPtr->anchor, tkwin, butPtr->padX, butPtr->padY,
805		fullWidth, fullHeight, &x, &y);
806
807	imageXOffset += x;
808	imageYOffset += y;
809
810	if (butPtr->image != NULL) {
811	    /*
812	     * Do boundary clipping, so that Tk_RedrawImage is passed valid
813	     * coordinates. [Bug 979239]
814	     */
815
816	    if (imageXOffset < 0) {
817		imageXOffset = 0;
818	    }
819	    if (imageYOffset < 0) {
820		imageYOffset = 0;
821	    }
822	    if (width > Tk_Width(tkwin)) {
823		width = Tk_Width(tkwin);
824	    }
825	    if (height > Tk_Height(tkwin)) {
826		height = Tk_Height(tkwin);
827	    }
828	    if ((width + imageXOffset) > Tk_Width(tkwin)) {
829		imageXOffset = Tk_Width(tkwin) - width;
830	    }
831	    if ((height + imageYOffset) > Tk_Height(tkwin)) {
832		imageYOffset = Tk_Height(tkwin) - height;
833	    }
834
835	    if ((butPtr->selectImage != NULL) && (butPtr->flags & SELECTED)) {
836		Tk_RedrawImage(butPtr->selectImage, 0, 0,
837			width, height, pixmap, imageXOffset, imageYOffset);
838	    } else if ((butPtr->tristateImage != NULL) && (butPtr->flags & TRISTATED)) {
839		Tk_RedrawImage(butPtr->tristateImage, 0, 0,
840			width, height, pixmap, imageXOffset, imageYOffset);
841	    } else {
842		Tk_RedrawImage(butPtr->image, 0, 0, width,
843			height, pixmap, imageXOffset, imageYOffset);
844	    }
845	} else {
846	    XSetClipOrigin(butPtr->display, gc, imageXOffset, imageYOffset);
847	    XCopyPlane(butPtr->display, butPtr->bitmap, pixmap, gc,
848		    0, 0, (unsigned int) width, (unsigned int) height,
849		    imageXOffset, imageYOffset, 1);
850	    XSetClipOrigin(butPtr->display, gc, 0, 0);
851	}
852
853	Tk_DrawTextLayout(butPtr->display, pixmap, gc,
854		butPtr->textLayout, x + textXOffset, y + textYOffset, 0, -1);
855	Tk_UnderlineTextLayout(butPtr->display, pixmap, gc,
856		butPtr->textLayout, x + textXOffset, y + textYOffset,
857		butPtr->underline);
858	y += fullHeight/2;
859    } else {
860	if (haveImage) {
861	    TkComputeAnchor(butPtr->anchor, tkwin, 0, 0,
862		    width, height, &x, &y);
863	    imageXOffset += x;
864	    imageYOffset += y;
865	    if (butPtr->image != NULL) {
866		/*
867		 * Do boundary clipping, so that Tk_RedrawImage is passed
868		 * valid coordinates. [Bug 979239]
869		 */
870
871		if (imageXOffset < 0) {
872		    imageXOffset = 0;
873		}
874		if (imageYOffset < 0) {
875		    imageYOffset = 0;
876		}
877		if (width > Tk_Width(tkwin)) {
878		    width = Tk_Width(tkwin);
879		}
880		if (height > Tk_Height(tkwin)) {
881		    height = Tk_Height(tkwin);
882		}
883		if ((width + imageXOffset) > Tk_Width(tkwin)) {
884		    imageXOffset = Tk_Width(tkwin) - width;
885		}
886		if ((height + imageYOffset) > Tk_Height(tkwin)) {
887		    imageYOffset = Tk_Height(tkwin) - height;
888		}
889
890		if ((butPtr->selectImage != NULL) &&
891			(butPtr->flags & SELECTED)) {
892		    Tk_RedrawImage(butPtr->selectImage, 0, 0, width,
893			    height, pixmap, imageXOffset, imageYOffset);
894		} else if ((butPtr->tristateImage != NULL) &&
895			(butPtr->flags & TRISTATED)) {
896		    Tk_RedrawImage(butPtr->tristateImage, 0, 0, width,
897			    height, pixmap, imageXOffset, imageYOffset);
898		} else {
899		    Tk_RedrawImage(butPtr->image, 0, 0, width, height, pixmap,
900			    imageXOffset, imageYOffset);
901		}
902	    } else {
903		XSetClipOrigin(butPtr->display, gc, x, y);
904		XCopyPlane(butPtr->display, butPtr->bitmap, pixmap, gc, 0, 0,
905			(unsigned int) width, (unsigned int) height, x, y, 1);
906		XSetClipOrigin(butPtr->display, gc, 0, 0);
907	    }
908	    y += height/2;
909	} else {
910 	    TkComputeAnchor(butPtr->anchor, tkwin, butPtr->padX, butPtr->padY,
911		    butPtr->textWidth, butPtr->textHeight, &x, &y);
912
913	    Tk_DrawTextLayout(butPtr->display, pixmap, gc, butPtr->textLayout,
914		    x, y, 0, -1);
915	    Tk_UnderlineTextLayout(butPtr->display, pixmap, gc,
916		    butPtr->textLayout, x, y, butPtr->underline);
917	    y += butPtr->textHeight/2;
918	}
919    }
920
921    /*
922     * If the button is disabled with a stipple rather than a special
923     * foreground color, generate the stippled effect. If the widget is
924     * selected and we use a different background color when selected, must
925     * temporarily modify the GC so the stippling is the right color.
926     */
927
928    if ((butPtr->state == STATE_DISABLED)
929	    && ((butPtr->disabledFg == NULL) || (butPtr->image != NULL))) {
930	if ((butPtr->flags & SELECTED) && !butPtr->indicatorOn
931		&& (butPtr->selectBorder != NULL)) {
932	    XSetForeground(butPtr->display, butPtr->stippleGC,
933		    Tk_3DBorderColor(butPtr->selectBorder)->pixel);
934	}
935
936	/*
937	 * Stipple the whole button if no disabledFg was specified, otherwise
938	 * restrict stippling only to displayed image
939	 */
940
941	if (butPtr->disabledFg == NULL) {
942	    XFillRectangle(butPtr->display, pixmap, butPtr->stippleGC, 0, 0,
943		    (unsigned) Tk_Width(tkwin), (unsigned) Tk_Height(tkwin));
944	} else {
945	    XFillRectangle(butPtr->display, pixmap, butPtr->stippleGC,
946		    imageXOffset, imageYOffset,
947		    (unsigned) imageWidth, (unsigned) imageHeight);
948	}
949	if ((butPtr->flags & SELECTED) && !butPtr->indicatorOn
950		&& (butPtr->selectBorder != NULL)) {
951	    XSetForeground(butPtr->display, butPtr->stippleGC,
952		    Tk_3DBorderColor(butPtr->normalBorder)->pixel);
953	}
954    }
955
956    /*
957     * Draw the border and traversal highlight last. This way, if the button's
958     * contents overflow they'll be covered up by the border. This code is
959     * complicated by the possible combinations of focus highlight and default
960     * rings. We draw the focus and highlight rings using the highlight border
961     * and highlight foreground color.
962     */
963
964    if (relief != TK_RELIEF_FLAT) {
965	int inset = butPtr->highlightWidth;
966
967	if (butPtr->defaultState == DEFAULT_ACTIVE) {
968	    /*
969	     * Draw the default ring with 2 pixels of space between the
970	     * default ring and the button and the default ring and the focus
971	     * ring. Note that we need to explicitly draw the space in the
972	     * highlightBorder color to ensure that we overwrite any overflow
973	     * text and/or a different button background color.
974	     */
975
976	    Tk_Draw3DRectangle(tkwin, pixmap, butPtr->highlightBorder, inset,
977		    inset, Tk_Width(tkwin) - 2*inset,
978		    Tk_Height(tkwin) - 2*inset, 2, TK_RELIEF_FLAT);
979	    inset += 2;
980	    Tk_Draw3DRectangle(tkwin, pixmap, butPtr->highlightBorder, inset,
981		    inset, Tk_Width(tkwin) - 2*inset,
982		    Tk_Height(tkwin) - 2*inset, 1, TK_RELIEF_SUNKEN);
983	    inset++;
984	    Tk_Draw3DRectangle(tkwin, pixmap, butPtr->highlightBorder, inset,
985		    inset, Tk_Width(tkwin) - 2*inset,
986		    Tk_Height(tkwin) - 2*inset, 2, TK_RELIEF_FLAT);
987
988	    inset += 2;
989	} else if (butPtr->defaultState == DEFAULT_NORMAL) {
990	    /*
991	     * Leave room for the default ring and write over any text or
992	     * background color.
993	     */
994
995	    Tk_Draw3DRectangle(tkwin, pixmap, butPtr->highlightBorder, 0,
996		    0, Tk_Width(tkwin), Tk_Height(tkwin), 5, TK_RELIEF_FLAT);
997	    inset += 5;
998	}
999
1000	/*
1001	 * Draw the button border.
1002	 */
1003
1004	Tk_Draw3DRectangle(tkwin, pixmap, border, inset, inset,
1005		Tk_Width(tkwin) - 2*inset, Tk_Height(tkwin) - 2*inset,
1006		butPtr->borderWidth, relief);
1007    }
1008    if (butPtr->highlightWidth > 0) {
1009	GC gc;
1010
1011	if (butPtr->flags & GOT_FOCUS) {
1012	    gc = Tk_GCForColor(butPtr->highlightColorPtr, pixmap);
1013	} else {
1014	    gc = Tk_GCForColor(Tk_3DBorderColor(butPtr->highlightBorder),
1015		    pixmap);
1016	}
1017
1018	/*
1019	 * Make sure the focus ring shrink-wraps the actual button, not the
1020	 * padding space left for a default ring.
1021	 */
1022
1023	if (butPtr->defaultState == DEFAULT_NORMAL) {
1024	    TkDrawInsetFocusHighlight(tkwin, gc, butPtr->highlightWidth,
1025		    pixmap, 5);
1026	} else {
1027	    Tk_DrawFocusHighlight(tkwin, gc, butPtr->highlightWidth, pixmap);
1028	}
1029    }
1030}
1031
1032/*
1033 *----------------------------------------------------------------------
1034 *
1035 * ComputeUnixButtonGeometry --
1036 *
1037 *	After changes in a button's text or bitmap, this procedure
1038 *	recomputes the button's geometry and passes this information
1039 *	along to the geometry manager for the window.
1040 *
1041 * Results:
1042 *	None.
1043 *
1044 * Side effects:
1045 *	The button's window may change size.
1046 *
1047 *----------------------------------------------------------------------
1048 */
1049
1050void
1051ComputeUnixButtonGeometry(
1052    register TkButton *butPtr)	/* Button whose geometry may have changed. */
1053{
1054    int width, height, avgWidth, txtWidth, txtHeight;
1055    int haveImage = 0, haveText = 0;
1056    Tk_FontMetrics fm;
1057
1058    butPtr->inset = butPtr->highlightWidth + butPtr->borderWidth;
1059
1060    /*
1061     * Leave room for the default ring if needed.
1062     */
1063
1064    if (butPtr->defaultState != DEFAULT_DISABLED) {
1065	butPtr->inset += 5;
1066    }
1067    butPtr->indicatorSpace = 0;
1068
1069    width = 0;
1070    height = 0;
1071    txtWidth = 0;
1072    txtHeight = 0;
1073    avgWidth = 0;
1074
1075    if (butPtr->image != NULL) {
1076	Tk_SizeOfImage(butPtr->image, &width, &height);
1077	haveImage = 1;
1078    } else if (butPtr->bitmap != None) {
1079	Tk_SizeOfBitmap(butPtr->display, butPtr->bitmap, &width, &height);
1080	haveImage = 1;
1081    }
1082
1083    if (haveImage == 0 || butPtr->compound != COMPOUND_NONE) {
1084	Tk_FreeTextLayout(butPtr->textLayout);
1085
1086	butPtr->textLayout = Tk_ComputeTextLayout(butPtr->tkfont,
1087		Tcl_GetString(butPtr->textPtr), -1, butPtr->wrapLength,
1088		butPtr->justify, 0, &butPtr->textWidth, &butPtr->textHeight);
1089
1090	txtWidth = butPtr->textWidth;
1091	txtHeight = butPtr->textHeight;
1092	avgWidth = Tk_TextWidth(butPtr->tkfont, "0", 1);
1093	Tk_GetFontMetrics(butPtr->tkfont, &fm);
1094	haveText = (txtWidth != 0 && txtHeight != 0);
1095    }
1096
1097    /*
1098     * If the button is compound (i.e., it shows both an image and text), the
1099     * new geometry is a combination of the image and text geometry. We only
1100     * honor the compound bit if the button has both text and an image,
1101     * because otherwise it is not really a compound button.
1102     */
1103
1104    if (butPtr->compound != COMPOUND_NONE && haveImage && haveText) {
1105	switch ((enum compound) butPtr->compound) {
1106	case COMPOUND_TOP:
1107	case COMPOUND_BOTTOM:
1108	    /*
1109	     * Image is above or below text.
1110	     */
1111
1112	    height += txtHeight + butPtr->padY;
1113	    width = (width > txtWidth ? width : txtWidth);
1114	    break;
1115	case COMPOUND_LEFT:
1116	case COMPOUND_RIGHT:
1117	    /*
1118	     * Image is left or right of text.
1119	     */
1120
1121	    width += txtWidth + butPtr->padX;
1122	    height = (height > txtHeight ? height : txtHeight);
1123	    break;
1124	case COMPOUND_CENTER:
1125	    /*
1126	     * Image and text are superimposed.
1127	     */
1128
1129	    width = (width > txtWidth ? width : txtWidth);
1130	    height = (height > txtHeight ? height : txtHeight);
1131	    break;
1132	case COMPOUND_NONE:
1133	    break;
1134	}
1135	if (butPtr->width > 0) {
1136	    width = butPtr->width;
1137	}
1138	if (butPtr->height > 0) {
1139	    height = butPtr->height;
1140	}
1141
1142	width += 2*butPtr->padX;
1143	height += 2*butPtr->padY;
1144    } else {
1145	if (haveImage) {
1146	    if (butPtr->width > 0) {
1147		width = butPtr->width;
1148	    }
1149	    if (butPtr->height > 0) {
1150		height = butPtr->height;
1151	    }
1152	} else {
1153	    width = txtWidth;
1154	    height = txtHeight;
1155
1156	    if (butPtr->width > 0) {
1157		width = butPtr->width * avgWidth;
1158	    }
1159	    if (butPtr->height > 0) {
1160		height = butPtr->height * fm.linespace;
1161	    }
1162	}
1163    }
1164
1165    if (!haveImage) {
1166	width += 2*butPtr->padX;
1167	height += 2*butPtr->padY;
1168    }
1169    Tk_GeometryRequest(butPtr->tkwin, (int) (width
1170	    + 2*butPtr->inset), (int) (height + 2*butPtr->inset));
1171    Tk_SetInternalBorder(butPtr->tkwin, butPtr->inset);
1172}
1173
1174/*
1175 * Local Variables:
1176 * mode: c
1177 * c-basic-offset: 4
1178 * fill-column: 79
1179 * coding: utf-8
1180 * End:
1181 */
1182