1/*
2 * tkUnixMenubu.c --
3 *
4 *	This file implements the Unix specific portion of the menubutton
5 *	widget.
6 *
7 * Copyright (c) 1996-1997 by Sun Microsystems, Inc.
8 *
9 * See the file "license.terms" for information on usage and redistribution of
10 * this file, and for a DISCLAIMER OF ALL WARRANTIES.
11 *
12 * RCS: @(#) $Id$
13 */
14
15#include "tkInt.h"
16#include "tkMenubutton.h"
17
18/*
19 * The structure below defines menubutton class behavior by means of functions
20 * that can be invoked from generic window code.
21 */
22
23Tk_ClassProcs tkpMenubuttonClass = {
24    sizeof(Tk_ClassProcs),	/* size */
25    TkMenuButtonWorldChanged,	/* worldChangedProc */
26};
27
28/*
29 *----------------------------------------------------------------------
30 *
31 * TkpCreateMenuButton --
32 *
33 *	Allocate a new TkMenuButton structure.
34 *
35 * Results:
36 *	Returns a newly allocated TkMenuButton structure.
37 *
38 * Side effects:
39 *	Registers an event handler for the widget.
40 *
41 *----------------------------------------------------------------------
42 */
43
44TkMenuButton *
45TkpCreateMenuButton(
46    Tk_Window tkwin)
47{
48    return (TkMenuButton *)ckalloc(sizeof(TkMenuButton));
49}
50
51/*
52 *----------------------------------------------------------------------
53 *
54 * TkpDisplayMenuButton --
55 *
56 *	This function is invoked to display a menubutton widget.
57 *
58 * Results:
59 *	None.
60 *
61 * Side effects:
62 *	Commands are output to X to display the menubutton in its current
63 *	mode.
64 *
65 *----------------------------------------------------------------------
66 */
67
68void
69TkpDisplayMenuButton(
70    ClientData clientData)	/* Information about widget. */
71{
72    register TkMenuButton *mbPtr = (TkMenuButton *) clientData;
73    GC gc;
74    Tk_3DBorder border;
75    Pixmap pixmap;
76    int x = 0;			/* Initialization needed only to stop compiler
77				 * warning. */
78    int y = 0;
79    register Tk_Window tkwin = mbPtr->tkwin;
80    int fullWidth, fullHeight;
81    int textXOffset, textYOffset;
82    int imageWidth, imageHeight;
83    int imageXOffset, imageYOffset;
84    int width = 0, height = 0;
85				/* Image information that will be used to
86				 * restrict disabled pixmap as well */
87    int haveImage = 0, haveText = 0;
88
89    mbPtr->flags &= ~REDRAW_PENDING;
90    if ((mbPtr->tkwin == NULL) || !Tk_IsMapped(tkwin)) {
91	return;
92    }
93
94    if ((mbPtr->state == STATE_DISABLED) && (mbPtr->disabledFg != NULL)) {
95	gc = mbPtr->disabledGC;
96	border = mbPtr->normalBorder;
97    } else if ((mbPtr->state == STATE_ACTIVE)
98	       && !Tk_StrictMotif(mbPtr->tkwin)) {
99	gc = mbPtr->activeTextGC;
100	border = mbPtr->activeBorder;
101    } else {
102	gc = mbPtr->normalTextGC;
103	border = mbPtr->normalBorder;
104    }
105
106    if (mbPtr->image != None) {
107	Tk_SizeOfImage(mbPtr->image, &width, &height);
108	haveImage = 1;
109    } else if (mbPtr->bitmap != None) {
110	Tk_SizeOfBitmap(mbPtr->display, mbPtr->bitmap, &width, &height);
111	haveImage = 1;
112    }
113    imageWidth	= width;
114    imageHeight = height;
115
116    haveText = (mbPtr->textWidth != 0 && mbPtr->textHeight != 0);
117
118    /*
119     * In order to avoid screen flashes, this function redraws the menu button
120     * in a pixmap, then copies the pixmap to the screen in a single
121     * operation. This means that there's no point in time where the on-sreen
122     * image has been cleared.
123     */
124
125    pixmap = Tk_GetPixmap(mbPtr->display, Tk_WindowId(tkwin),
126	    Tk_Width(tkwin), Tk_Height(tkwin), Tk_Depth(tkwin));
127    Tk_Fill3DRectangle(tkwin, pixmap, border, 0, 0, Tk_Width(tkwin),
128	    Tk_Height(tkwin), 0, TK_RELIEF_FLAT);
129
130    imageXOffset = 0;
131    imageYOffset = 0;
132    textXOffset = 0;
133    textYOffset = 0;
134    fullWidth = 0;
135    fullHeight = 0;
136
137    if (mbPtr->compound != COMPOUND_NONE && haveImage && haveText) {
138	switch ((enum compound) mbPtr->compound) {
139	case COMPOUND_TOP:
140	case COMPOUND_BOTTOM:
141	    /*
142	     * Image is above or below text.
143	     */
144
145	    if (mbPtr->compound == COMPOUND_TOP) {
146		textYOffset = height + mbPtr->padY;
147	    } else {
148		imageYOffset = mbPtr->textHeight + mbPtr->padY;
149	    }
150	    fullHeight = height + mbPtr->textHeight + mbPtr->padY;
151	    fullWidth = (width > mbPtr->textWidth ? width : mbPtr->textWidth);
152	    textXOffset = (fullWidth - mbPtr->textWidth)/2;
153	    imageXOffset = (fullWidth - width)/2;
154	    break;
155	case COMPOUND_LEFT:
156	case COMPOUND_RIGHT:
157	    /*
158	     * Image is left or right of text.
159	     */
160
161	    if (mbPtr->compound == COMPOUND_LEFT) {
162		textXOffset = width + mbPtr->padX;
163	    } else {
164		imageXOffset = mbPtr->textWidth + mbPtr->padX;
165	    }
166	    fullWidth = mbPtr->textWidth + mbPtr->padX + width;
167	    fullHeight = (height > mbPtr->textHeight ? height :
168		    mbPtr->textHeight);
169	    textYOffset = (fullHeight - mbPtr->textHeight)/2;
170	    imageYOffset = (fullHeight - height)/2;
171	    break;
172	case COMPOUND_CENTER:
173	    /*
174	     * Image and text are superimposed.
175	     */
176
177	    fullWidth = (width > mbPtr->textWidth ? width : mbPtr->textWidth);
178	    fullHeight = (height > mbPtr->textHeight ? height :
179		    mbPtr->textHeight);
180	    textXOffset = (fullWidth - mbPtr->textWidth)/2;
181	    imageXOffset = (fullWidth - width)/2;
182	    textYOffset = (fullHeight - mbPtr->textHeight)/2;
183	    imageYOffset = (fullHeight - height)/2;
184	    break;
185	case COMPOUND_NONE:
186	    break;
187	}
188
189	TkComputeAnchor(mbPtr->anchor, tkwin, 0, 0,
190		mbPtr->indicatorWidth + fullWidth, fullHeight, &x, &y);
191
192	imageXOffset += x;
193	imageYOffset += y;
194	if (mbPtr->image != NULL) {
195	    Tk_RedrawImage(mbPtr->image, 0, 0, width, height, pixmap,
196		    imageXOffset, imageYOffset);
197	} else if (mbPtr->bitmap != None) {
198	    XSetClipOrigin(mbPtr->display, gc, imageXOffset, imageYOffset);
199	    XCopyPlane(mbPtr->display, mbPtr->bitmap, pixmap,
200		    gc, 0, 0, (unsigned) width, (unsigned) height,
201		    imageXOffset, imageYOffset, 1);
202	    XSetClipOrigin(mbPtr->display, gc, 0, 0);
203	}
204
205	Tk_DrawTextLayout(mbPtr->display, pixmap, gc, mbPtr->textLayout,
206		x + textXOffset, y + textYOffset, 0, -1);
207	Tk_UnderlineTextLayout(mbPtr->display, pixmap, gc, mbPtr->textLayout,
208		x + textXOffset, y + textYOffset, mbPtr->underline);
209    } else if (haveImage) {
210	TkComputeAnchor(mbPtr->anchor, tkwin, 0, 0,
211		width + mbPtr->indicatorWidth, height, &x, &y);
212	imageXOffset += x;
213	imageYOffset += y;
214	if (mbPtr->image != NULL) {
215	    Tk_RedrawImage(mbPtr->image, 0, 0, width, height, pixmap,
216		    imageXOffset, imageYOffset);
217	} else if (mbPtr->bitmap != None) {
218	    XSetClipOrigin(mbPtr->display, gc, x, y);
219	    XCopyPlane(mbPtr->display, mbPtr->bitmap, pixmap,
220		    gc, 0, 0, (unsigned) width, (unsigned) height,
221		    x, y, 1);
222	    XSetClipOrigin(mbPtr->display, gc, 0, 0);
223	}
224    } else {
225	TkComputeAnchor(mbPtr->anchor, tkwin, mbPtr->padX, mbPtr->padY,
226		mbPtr->textWidth + mbPtr->indicatorWidth,
227		mbPtr->textHeight, &x, &y);
228	Tk_DrawTextLayout(mbPtr->display, pixmap, gc, mbPtr->textLayout,
229		x + textXOffset, y + textYOffset, 0, -1);
230	Tk_UnderlineTextLayout(mbPtr->display, pixmap, gc,
231		mbPtr->textLayout, x + textXOffset, y + textYOffset,
232		mbPtr->underline);
233    }
234
235    /*
236     * If the menu button is disabled with a stipple rather than a special
237     * foreground color, generate the stippled effect.
238     */
239
240    if ((mbPtr->state == STATE_DISABLED)
241	    && ((mbPtr->disabledFg == NULL) || (mbPtr->image != NULL))) {
242	/*
243	 * Stipple the whole button if no disabledFg was specified, otherwise
244	 * restrict stippling only to displayed image
245	 */
246
247	if (mbPtr->disabledFg == NULL) {
248	    XFillRectangle(mbPtr->display, pixmap, mbPtr->stippleGC,
249		    mbPtr->inset, mbPtr->inset,
250		    (unsigned) (Tk_Width(tkwin) - 2*mbPtr->inset),
251		    (unsigned) (Tk_Height(tkwin) - 2*mbPtr->inset));
252	} else {
253	    XFillRectangle(mbPtr->display, pixmap, mbPtr->stippleGC,
254		    imageXOffset, imageYOffset,
255		    (unsigned) imageWidth, (unsigned) imageHeight);
256	}
257    }
258
259    /*
260     * Draw the cascade indicator for the menu button on the right side of the
261     * window, if desired.
262     */
263
264    if (mbPtr->indicatorOn) {
265	int borderWidth;
266
267	borderWidth = (mbPtr->indicatorHeight+1)/3;
268	if (borderWidth < 1) {
269	    borderWidth = 1;
270	}
271	/*y += mbPtr->textHeight / 2;*/
272	Tk_Fill3DRectangle(tkwin, pixmap, border,
273		Tk_Width(tkwin) - mbPtr->inset - mbPtr->indicatorWidth
274		+ mbPtr->indicatorHeight,
275		((int) (Tk_Height(tkwin) - mbPtr->indicatorHeight))/2,
276		mbPtr->indicatorWidth - 2*mbPtr->indicatorHeight,
277		mbPtr->indicatorHeight, borderWidth, TK_RELIEF_RAISED);
278    }
279
280    /*
281     * Draw the border and traversal highlight last. This way, if the menu
282     * button's contents overflow onto the border they'll be covered up by the
283     * border.
284     */
285
286    if (mbPtr->relief != TK_RELIEF_FLAT) {
287	Tk_Draw3DRectangle(tkwin, pixmap, border,
288		mbPtr->highlightWidth, mbPtr->highlightWidth,
289		Tk_Width(tkwin) - 2*mbPtr->highlightWidth,
290		Tk_Height(tkwin) - 2*mbPtr->highlightWidth,
291		mbPtr->borderWidth, mbPtr->relief);
292    }
293    if (mbPtr->highlightWidth != 0) {
294	GC gc;
295
296	if (mbPtr->flags & GOT_FOCUS) {
297	    gc = Tk_GCForColor(mbPtr->highlightColorPtr, pixmap);
298	} else {
299	    gc = Tk_GCForColor(mbPtr->highlightBgColorPtr, pixmap);
300	}
301	Tk_DrawFocusHighlight(tkwin, gc, mbPtr->highlightWidth, pixmap);
302    }
303
304    /*
305     * Copy the information from the off-screen pixmap onto the screen, then
306     * delete the pixmap.
307     */
308
309    XCopyArea(mbPtr->display, pixmap, Tk_WindowId(tkwin),
310	    mbPtr->normalTextGC, 0, 0, (unsigned) Tk_Width(tkwin),
311	    (unsigned) Tk_Height(tkwin), 0, 0);
312    Tk_FreePixmap(mbPtr->display, pixmap);
313}
314
315/*
316 *----------------------------------------------------------------------
317 *
318 * TkpDestroyMenuButton --
319 *
320 *	Free data structures associated with the menubutton control.
321 *
322 * Results:
323 *	None.
324 *
325 * Side effects:
326 *	Restores the default control state.
327 *
328 *----------------------------------------------------------------------
329 */
330
331void
332TkpDestroyMenuButton(
333    TkMenuButton *mbPtr)
334{
335}
336
337/*
338 *----------------------------------------------------------------------
339 *
340 * TkpComputeMenuButtonGeometry --
341 *
342 *	After changes in a menu button's text or bitmap, this function
343 *	recomputes the menu button's geometry and passes this information
344 *	along to the geometry manager for the window.
345 *
346 * Results:
347 *	None.
348 *
349 * Side effects:
350 *	The menu button's window may change size.
351 *
352 *----------------------------------------------------------------------
353 */
354
355void
356TkpComputeMenuButtonGeometry(
357    TkMenuButton *mbPtr)	/* Widget record for menu button. */
358{
359    int width, height, mm, pixels;
360    int	 avgWidth, txtWidth, txtHeight;
361    int haveImage = 0, haveText = 0;
362    Tk_FontMetrics fm;
363
364    mbPtr->inset = mbPtr->highlightWidth + mbPtr->borderWidth;
365
366    width = 0;
367    height = 0;
368    txtWidth = 0;
369    txtHeight = 0;
370    avgWidth = 0;
371
372    if (mbPtr->image != None) {
373	Tk_SizeOfImage(mbPtr->image, &width, &height);
374	haveImage = 1;
375    } else if (mbPtr->bitmap != None) {
376	Tk_SizeOfBitmap(mbPtr->display, mbPtr->bitmap, &width, &height);
377	haveImage = 1;
378    }
379
380    if (haveImage == 0 || mbPtr->compound != COMPOUND_NONE) {
381	Tk_FreeTextLayout(mbPtr->textLayout);
382
383	mbPtr->textLayout = Tk_ComputeTextLayout(mbPtr->tkfont, mbPtr->text,
384		-1, mbPtr->wrapLength, mbPtr->justify, 0, &mbPtr->textWidth,
385		&mbPtr->textHeight);
386	txtWidth = mbPtr->textWidth;
387	txtHeight = mbPtr->textHeight;
388	avgWidth = Tk_TextWidth(mbPtr->tkfont, "0", 1);
389	Tk_GetFontMetrics(mbPtr->tkfont, &fm);
390	haveText = (txtWidth != 0 && txtHeight != 0);
391    }
392
393    /*
394     * If the menubutton is compound (ie, it shows both an image and text),
395     * the new geometry is a combination of the image and text geometry. We
396     * only honor the compound bit if the menubutton has both text and an
397     * image, because otherwise it is not really a compound menubutton.
398     */
399
400    if (mbPtr->compound != COMPOUND_NONE && haveImage && haveText) {
401	switch ((enum compound) mbPtr->compound) {
402	case COMPOUND_TOP:
403	case COMPOUND_BOTTOM:
404	    /*
405	     * Image is above or below text.
406	     */
407
408	    height += txtHeight + mbPtr->padY;
409	    width = (width > txtWidth ? width : txtWidth);
410	    break;
411	case COMPOUND_LEFT:
412	case COMPOUND_RIGHT:
413	    /*
414	     * Image is left or right of text.
415	     */
416
417	    width += txtWidth + mbPtr->padX;
418	    height = (height > txtHeight ? height : txtHeight);
419	    break;
420	case COMPOUND_CENTER:
421	    /*
422	     * Image and text are superimposed.
423	     */
424
425	    width = (width > txtWidth ? width : txtWidth);
426	    height = (height > txtHeight ? height : txtHeight);
427	    break;
428	case COMPOUND_NONE:
429	    break;
430	}
431	if (mbPtr->width > 0) {
432	    width = mbPtr->width;
433	}
434	if (mbPtr->height > 0) {
435	    height = mbPtr->height;
436	}
437	width += 2*mbPtr->padX;
438	height += 2*mbPtr->padY;
439    } else {
440	if (haveImage) {
441	    if (mbPtr->width > 0) {
442		width = mbPtr->width;
443	    }
444	    if (mbPtr->height > 0) {
445		height = mbPtr->height;
446	    }
447	} else {
448	    width = txtWidth;
449	    height = txtHeight;
450	    if (mbPtr->width > 0) {
451		width = mbPtr->width * avgWidth;
452	    }
453	    if (mbPtr->height > 0) {
454		height = mbPtr->height * fm.linespace;
455	    }
456	}
457    }
458
459    if (! haveImage) {
460	width += 2*mbPtr->padX;
461	height += 2*mbPtr->padY;
462    }
463
464    if (mbPtr->indicatorOn) {
465	mm = WidthMMOfScreen(Tk_Screen(mbPtr->tkwin));
466	pixels = WidthOfScreen(Tk_Screen(mbPtr->tkwin));
467	mbPtr->indicatorHeight= (INDICATOR_HEIGHT * pixels)/(10*mm);
468	mbPtr->indicatorWidth = (INDICATOR_WIDTH * pixels)/(10*mm)
469		+ 2*mbPtr->indicatorHeight;
470	width += mbPtr->indicatorWidth;
471    } else {
472	mbPtr->indicatorHeight = 0;
473	mbPtr->indicatorWidth = 0;
474    }
475
476    Tk_GeometryRequest(mbPtr->tkwin, (int) (width + 2*mbPtr->inset),
477	    (int) (height + 2*mbPtr->inset));
478    Tk_SetInternalBorder(mbPtr->tkwin, mbPtr->inset);
479}
480
481/*
482 * Local Variables:
483 * mode: c
484 * c-basic-offset: 4
485 * fill-column: 78
486 * End:
487 */
488